文章目录

  • 运算符 表达式和语句
    • 本章内容
    • 循环简介
    • 基本运算符
      • 赋值运算符:=
      • 几个术语:数据对象、左值、右值和运算符
      • 加法运算符:+
      • 减法运算符:-
      • 5.2.4 符号运算符:-和+
      • 乘法运算符:*
      • 除法运算符:/
      • 运算符优先级
      • 优先级和求值顺序
    • 其他运算符
      • sizeof运算符和size_t类型
      • 求模运算符:%
      • 递增运算符:++
      • 优先级
      • 不要自作聪明
    • 表达式和语句
      • 表达式
      • 语句
      • 副作用和序列点
      • 复合语句(块)
      • 风格提示
    • 类型转换
      • 强制类型转换运算符
    • 带参数的函数
    • 示例程序
    • 关键概念
    • 本章小结
    • 复习题
    • 编程练习

加粗是用来标记我的知识盲区,无特殊含义。

  • 赋值语句的读法
  • 几个术语
  • 避免混合类型
  • 负数整数除法的趋零截断
  • 优先级与求值顺序
  • 使用typedef把size_t作为unsigned int或unsigned long的别名。
  • 编译器执行相同优先级表达式时的执行顺序
  • 语句和表达式语句
  • 副作用和序列点和完整表达式
  • 基本的类型转换规则
  • 升级与降级

运算符 表达式和语句

本章内容

本章介绍以下内容:

  • 关键字——while、typedef;
  • 运算符——=、-、*、/、%、++、–、(类型名);
  • C语言的各种运算符,包括用于普通数学运算的运算符;
  • 运算符优先级以及语句、表达式的含义;
  • while循环;
  • 复合语句、自动类型转换和强制类型转换;
  • 如何编写带有参数的函数。

循环简介

/* shoes2.c -- 计算多个不同鞋码对应的脚长 */
#include <stdio.h>
#define ADJUST 7.31 // 字符常量
int main(void)
{const double SCALE = 0.333; // const变量double shoe, foot;printf("Shoe size (men's)  foot length\n");shoe = 3.0;while (shoe < 18.5) /* while循环开始 */{ /* 块开始  */foot = SCALE * shoe + ADJUST;printf("%10.1f %15.2f inches\n", shoe, foot);shoe = shoe + 1.0;} /* 块结束    */printf("If the shoe fits, wear it.\n");return 0;
}

Shoe size (men’s) foot length

3.0 8.31 inches

4.0 8.64 inches

​ 5.0 8.97 inches

6.0 9.31 inches

…

​ 16.0 12.64 inches

​ 17.0 12.97 inches

18.0 13.30 inches

If the shoe fits, wear it.

  • 下面解释一下while循环的原理。当程序第1次到达while循环时,会检查圆括号中的条件是否为真。该程序中,条件表达式如下:
    shoe < 18.5

  • 条件为真,程序进入块中继续执行,把尺码转换成英寸。然后打印计算的结果。下一条语句把shoe增加1.0,使shoe的值为4.0:

  • 此时,程序返回while入口部分检查条件。为何要返回while的入口部分?因为上面这条语句的下面是右花括号(}),代码使用一对花括号({})来标出while循环的范围。花括号之间的内容就是要被重复执行的内容。花括号以及被花括号括起来的部分被称为块

  • 该循环过程一直持续到shoe的值为19.0。此时,循环的条件是shoe<18.5,相当于19<18.5,所以该条件为假。出现这种情况后,控制转到紧跟while循环后面的第1条语句。该例中,是最后的printf()语句。

基本运算符

赋值运算符:=

  • =并不意味着“相等”,而是一个赋值运算符。下面的赋值表达式语句:

    bmw = 2002;

  • 把值2002赋给变量bmw。也就是说,=号左侧是一个变量名,右侧是赋给该变量的值。

  • 另外,上面的语句不读作“bmw等于2002”,而读作“把值2002赋给变量bmw”。赋值行为从右往左进行。

  • i = i + 1;

  • 对数学而言,这完全行不通。如果给一个有限的数加上1,它不可能“等于”原来的数。但是,在计算机赋值表达式语句中,这很合理。该语句的意思是:找出变量i的值,把该值加1,然后把新值赋值变量i

  • 在C语言中,类似这样的语句没有意义(实际上是无效的):
    2002 = bmw;

  • 概括地说,C使用可修改的左值(modifiable lvalue)标记那些可赋值的实体。

几个术语:数据对象、左值、右值和运算符

  • 用于存储值的数据存储区域统称为数据对象

  • 使用变量名是标识对象的一种方法。除此之外,还有其他方法,但是要在后面的章节中才学到。例如,可以指定数组的元素、结构的成员,或者使用指针表达式(指针中存储的是它所指向对象的地址)

  • 左值(lvalue)是C语言的术语,用于标识特定数据对象的名称或表达式。

  • 对象指的是实际的数据存储,而左值是用于标识或定位存储位置的标签。

    • 1.它指定一个对象,可以引用内存中的地址;
    • 2.它可用在赋值运算符的左侧,左值(lvalue)中的l源自left。
  • C标准新增了一个术语:可修改的左值(modifiable lvalue),用于标识可修改的对象。所以,赋值运算符的左侧应该是可修改的左值。当前标准建议,使用术语对象定位值(object locator value)更好。

  • 右值(rvalue)指的是能赋值给可修改左值的量,且本身不是左值。

  • 右值可以是常量、变量或其他可求值的表达式

  • 实际上,当前标准在描述这一概念时使用的是表达式的值(value of an expression),而不是右值。

int ex;

int why;

int zee;

const int TWO = 2;

why = 42;

zee = why;

ex = TWO * (why + zee);

  • 这里,ex、why和zee都是可修改的左值(或对象定位值),它们可用于赋值运算符的左侧和右侧。TWO是不可改变的左值,它只能用于赋值运算符的右侧(在该例中,TWO被初始化为2,这里的=运算符表示初始化而不是赋值,因此并未违反规则)。同时,42是右值,它不能引用某指定内存位置。

  • 另外,why和zee是可修改的左值,表达式(why + zee)是右值,该表达式不能表示特定内存位置,而且也不能给它赋值。它只是程序计算的一个临时值,在计算完毕后便会被丢弃。

  • 运算对象是运算符操作的对象。

  • =运算符的左侧运算对象应该是可修改的左值。

/* golf.c -- 高尔夫锦标赛记分卡 */
#include <stdio.h>
int main(void)
{int jane, tarzan, cheeta;cheeta = tarzan = jane = 68;printf("         cheeta  tarzan  jane\n");printf("First round score %4d %8d %8d\n", cheeta, tarzan, jane);return 0;
}
  • 许多其他语言都会回避该程序中的三重赋值,但是C完全没问题。赋值的顺序是从右往左:首先把68赋给jane,然后再赋给tarzan,最后赋给cheeta。

加法运算符:+

  • 加法运算符(addition operator)用于加法运算,使其两侧的值相加。

  • 相加的值(运算对象)可以是变量,也可以是常量。

减法运算符:-

  • 减法运算符(subtraction operator)用于减法运算,使其左侧的数减去右侧的数。

  • +和-运算符都被称为二元运算符(binary operator),即这些运算符需要两个运算对象才能完成操作。

5.2.4 符号运算符:-和+

  • 这种方式使用的负号被称为一元运算符(unary operator)。一元运算符只需要一个运算对象

乘法运算符:*

/* squares.c -- 计算1~20的平方 */
#include <stdio.h>
int main(void)
{int num = 1;while (num < 21){printf("%4d %6d\n", num, num * num);num = num + 1;}return 0;
}

除法运算符:/

  • 整数除法和浮点数除法不同。浮点数除法的结果是浮点数,而整数除法的结果是整数。整数是没有小数部分的数。这使得5除以3很让人头痛,因为实际结果有小数部分。在C语言中,整数除法结果的小数部分被丢弃,这一过程被称为截断(truncation)。
/* divide.c -- 演示除法 */
#include <stdio.h>
int main(void)
{printf("integer division: 5/4  is %d \n", 5 / 4);printf("integer division: 6/3  is %d \n", 6 / 3);printf("integer division: 7/4  is %d \n", 7 / 4);printf("floating division: 7./4. is %1.2f \n", 7. / 4.);printf("mixed division:  7./4 is %1.2f \n", 7. / 4);return 0;
}

integer division: 5/4 is 1

integer division: 6/3 is 2

integer division: 7/4 is 1

floating division: 7./4. is 1.75

mixed division: 7./4 is 1.75

  • 浮点值除以整型值。C相对其他一些语言而言,在类型管理上比较宽容。尽管如此,一般情况下还是要避免使用混合类型。

  • 整数除法会截断计算结果的小数部分(丢弃整个小数部分),不会四舍五入结果。混合整数和浮点数计算的结果是浮点数。实际上,计算机不能真正用浮点数除以整数,编译器会把两个运算对象转换成相同的类型。

  • C99标准以前,C语言给语言的实现者留有一些空间,让他们来决定如何进行负数的整数除法。一种方法是,舍入过程采用小于或等于浮点数的最大整数。当然,对于3.8而言,处理后的3符合这一描述。

  • 但是-3.8会怎样?该方法建议四舍五入为-4,因为-4小于-3.8。但是,另一种舍入方法是直接丢弃小数部分。这种方法被称为“趋零截断”,即把-3.8转换成-3。在C99以前,不同的实现采用不同的方法。

  • 但是C99规定使用趋零截断。所以,应把-3.8转换成-3。

运算符优先级

  • C语言对此有明确的规定,通过运算符优先级来解决操作顺序的问题。每个运算符都有自己的优先级。正如普通的算术运算那样,乘法和除法的优先级比加法和减法高,所以先执行乘法和除法。

  • 许多人喜欢用表达式树(expression tree)来表示求值的顺序,如图5.3所示。该图演示了如何从最初的表达式逐步简化为一个值。

  • 如何让加法运算在除法运算之前执行?可以这样做:flour = (25.0 + 60.0 * n) / SCALE;

优先级和求值顺序

  • 运算符优先级为表达式中的求值顺序提供重要的依据,但是并没有规定所有的顺序。C给语言的实现者留出选择的余地。

y = 6 * 12 + 5 * 20;

  • 当运算符共享一个运算对象时,优先级决定了求值顺序。例如上面的语句中,12是*和+运算符的运算对象。根据运算符的优先级,乘法的优先级比加法高,所以先进行乘法运算。
  • 类似地,先对5进行乘法运算而不是加法运算。简而言之,先进行两个乘法运算6 * 12和5 * 20,再进行加法运算。
  • 但是,优先级并未规定到底先进行哪一个乘法。C语言把主动权留给语言的实现者,根据不同的硬件来决定先计算前者还是后者。可能在一种硬件上采用某种方案效率更高,而在另一种硬件上采用另一种方案效率更高。无论采用哪种方案,表达式都会简化为72+100,所以这并不影响最终的结果。
  • 但是,读者可能会根据乘法从左往右的结合律,认为应该先执行+运算符左边的乘法。
  • 结合律只适用于共享同一运算对象的运算符。例如,在表达式12 / 3 * 2中,/和*运算符的优先级相同,共享运算对象3。因此,从左往右的结合律在这种情况起作用。表达式简化为4 * 2,即8(如果从右往左计算,会得到12/6,即2,这种情况下计算的先后顺序会影响最终的计算结果)。

其他运算符

  • C语言有大约40个运算符,有些运算符比其他运算符常用得多。前面讨论的是最常用的,本节再介绍4个比较有用的运算符。

sizeof运算符和size_t类型

  • sizeof运算符以字节为单位返回运算对象的大小

  • 运算对象可以是具体的数据对象(如,变量名)或类型。

  • 如果运算对象是类型(如,float),则必须用圆括号将其括起来。

// sizeof.c -- 使用sizeof运算符
// 使用C99新增的%zd转换说明 -- 如果编译器不支持%zd,请将其改成%u或%lu
#include <stdio.h>
int main(void)
{int n = 0;size_t intsize;intsize = sizeof(int);printf("n = %d, n has %zd bytes; all ints have %zd bytes.\n", n, sizeof n, intsize);return 0;
}
  • C语言规定,sizeof返回size_t类型的值。这是一个无符号整数类型,但它不是新类型。

  • C头文件系统可以使用typedef把size_t作为unsigned int或unsigned long的别名。

求模运算符:%

  • 求模运算符(modulus operator)用于整数运算。

  • 模运算符给出其左侧整数除以右侧整数的余数(remainder)。例如,13 % 5(读作“13求模5”)得3

// min_sec.c -- 把秒数转换成分和秒
#include <stdio.h>
#define SEC_PER_MIN 60 // 1分钟60秒
int main(void)
{int sec, min, left;printf("Convert seconds to minutes and seconds!\n");printf("Enter the number of seconds (<=0 to quit):\n");scanf("%d", &sec); // 读取秒数while (sec > 0){min = sec / SEC_PER_MIN;  // 截断分钟数left = sec % SEC_PER_MIN; // 剩下的秒数printf("%d seconds is %d minutes, %d seconds.\n", sec, min, left);printf("Enter next value (<=0 to quit):\n");scanf("%d", &sec);}printf("Done!\n");return 0;
}

Convert seconds to minutes and seconds!

Enter the number of seconds (<=0 to quit):154

154 seconds is 2 minutes, 34 seconds.

Enter next value (<=0 to quit):567

567 seconds is 9 minutes, 27 seconds.

Enter next value (<=0 to quit):0

Done!

  • 如果第1个运算对象是负数,那么求模的结果为负数;如果第1个运算对象是正数,那么求模的结果也是正数:

递增运算符:++

  • 递增运算符(increment operator)执行简单的任务,将其运算对象递增1。该运算符以两种方式出现。第1种方式,++出现在其作用的变量前面,这是前缀模式;第2种方式,++出现在其作用的变量后面,这是后缀模式。两种模式的区别在于递增行为发生的时间不同。
/* add_one.c -- 递增:前缀和后缀 */
#include <stdio.h>
int main(void)
{int ultra = 0, super = 0;while (super < 5){super++;++ultra;printf("super = %d, ultra = %d \n", super, ultra);}return 0;
}

运行该程序后,其输出如下:

super = 1, ultra = 1

super = 2, ultra = 2

super = 3, ultra = 3

super = 4, ultra = 4

super = 5, ultra = 5

  • 紧凑结构的代码让程序更为简洁,可读性更高。这些运算符让程序看起来很美观。
/* post_pre.c -- 前缀和后缀 */
#include <stdio.h>
int main(void)
{int a = 1, b = 1;int a_post, pre_b;a_post = a++; // 后缀递增pre_b = ++b;  // 前缀递增printf("a a_post  b  pre_b \n");printf("%1d %5d %5d %5d\n", a, a_post, b, pre_b);return 0;
}

如果你的编译器没问题,那么程序的输出应该是:

a a_post b pre_b

2 1 2 2

  • a和b都递增了1,但是,a_post是a递增之前的值,而pre_b是b递增之后的值。这就是++的前缀形式和后缀形式的区别

a_post = a++; // 后缀:使用a的值之后,递增a

pre_b = ++b; // 前缀:使用b的值之前,递增b

  • 单独使用递增运算符时(如,ego++;),使用哪种形式都没关系。但是,当运算符和运算对象是更复杂表达式的一部分时(如上面的示例),使用前缀或后缀的效果不同。

  • 如果使用前缀形式和后缀形式会对代码产生不同的影响,那么最为明智的是不要那样使用它们。

  • 递减运算符:-- 略

优先级

  • 递增运算符和递减运算符都有很高的结合优先级,只有圆括号的优先级比它们高。

不要自作聪明

  • 如果一次用太多递增运算符,自己都会糊涂。
while (num < 21)
{printf("%10d %10d\n", num, num * num++);
}
  • 这个想法看上去不错。打印num,然后计算num*num得到平方值,最后把num递增1。

  • 但事实上,修改后的程序只能在某些系统上能正常运行。该程序的问题是:当printf()获取待打印的值时,可能先对最后一个参数(num*num++)求值,这样在获取其他参数的值之前就递增了num。所以,本应打印:
    5 25
    却打印成:
    6 25

  • 它甚至可能从右往左执行,对最右边的num(++作用的num)使用5,对第2个num和最左边的num使用6,

  • 结果打印出:
    6 30

  • 在C语言中,编译器可以自行选择先对函数中的哪个参数求值。这样做提高了编译器的效率,但是如果在函数的参数中使用了递增运算符,就会有一些问题。

ans = num/2 + 5*(1 + num++);
  • 同样,该语句的问题是:编译器可能不会按预想的顺序来执行。你可能认为,先计算第1项(num/2),接着计算第2项(5(1+num++))。*
  • 但是,编译器可能先计算第2项,递增num,然后在num/2中使用num递增后的新值。因此,无法保证编译器到底先计算哪一项。
n = 3;
y = n++ + n++;
  • 可以肯定的是,执行完这两条语句后,n的值会比旧值大2。但是,y的值不确定。

  • 在对y求值时,编译器可以使用n的旧值(3)两次,然后把n递增1两次,这使得y的值为6,n的值为5。

  • 或者,编译器使用n的旧值(3)一次,立即递增n,再对表达式中的第2个n使用递增后的新值,然后再递增n,这使得y的值为7,n的值为5。两种方案都可行。

  • 对于这种情况更精确地说,结果是未定义的,这意味着C标准并未定义结果应该是什么。

  • 遵循以下规则,很容易避免类似的问题:

    • ·如果一个变量出现在一个函数的多个参数中,不要对该变量使用递增或递减运算符;
    • ·如果一个变量多次出现在一个表达式中,不要对该变量使用递增或递减运算符。

表达式和语句

表达式

  • 表达式(expression)由运算符和运算对象组成

  • 下面是一些表达式:

    4

    -6

    4+21

    a*(b + c/d)/20

    q = 5*2

    x = ++q % 3

    q > 3

  • 一些表达式由子表达式(subexpression)组成(子表达式即较小的表达式)。

  • C表达式的一个最重要的特性是,每个表达式都有一个值。

  • 虽然最后一个表达式看上去很奇怪,但是在C中完全合法(但不建议使用),因为它是两个子表达式的和,每个子表达式都有一个值。

语句

  • 语句(statement)是C程序的基本构建块。一条语句相当于一条完整的计算机指令。在C中,大部分语句都以分号结尾。因此,legs = 4
    只是一个表达式(它可能是一个较大表达式的一部分),而下面的代码则是一条语句:
    legs = 4;

  • C把末尾加上一个分号的表达式都看作是一条语句(即,表达式语句)。因此,像下面这样写也没问题:

    8;

    3 + 4;

  • 但是,这些语句在程序中什么也不做,不算是真正有用的语句。更确切地说,语句可以改变值或调用函数:

    x = 25;

    ++x;

    y = sqrt(x);

  • 虽然一条语句(或者至少是一条有用的语句)相当于一条完整的指令,但并不是所有的指令都是语句。

x = 6 + (y = 5);

  • 该语句中的子表达式y = 5是一条完整的指令,但是它只是语句的一部分。因为一条完整的指令不一定是一条语句,所以分号用于识别在这种情况下的语句

  • 注意,声明不是表达式语句。也就是说,如果删除声明后面的分号,剩下的部分不是一个表达式,也没有值:

    int port /* 不是表达式,没有值 */

  • while语句有3个不同的部分(见图5.6)。

  • 首先是关键字while;然后,圆括号中是待测试的条件;

  • 最后如果测试条件为真,则执行while循环体中的语句。该例的while循环中只有一条语句。可以是本例那样的一条语句,不需要用花括号括起来,也可以像其他例子中那样包含多条语句。

  • 多条语句需要用花括号括起来。这种语句是复合语句,稍后马上介绍。

副作用和序列点

  • **副作用是对数据对象或文件的修改。**例如,语句:

    states = 50;

  • 它的副作用是将变量的值设置为50。副作用?这似乎更像是主要目的!但是从C语言的角度看,主要目的是对表达式求值。

  • 给出表达式4+6,C会对其求值得10;给出表达式states = 50,C会对其求值得50。对该表达式求值的副作用是把变量states的值改为50。跟赋值运算符一样,递增和递减运算符也有副作用,使用它们的主要目的就是使用其副作用。

  • 类似地,调用printf()函数时,它显示的信息其实是副作用(printf()的返回值是待显示字符的个数)。

  • 序列点(sequence point)是程序执行的点,在该点上,所有的副作用都在进入下一步之前发生

  • 在C语言中,语句中的分号标记了一个序列点。意思是,在一个语句中,赋值运算符、递增运算符和递减运算符对运算对象做的改变必须在程序执行下一条语句之前完成。后面我们要讨论的一些运算符也有序列点。另外,任何一个完整表达式的结束也是一个序列点。

  • 所谓完整表达式(full expression),就是指这个表达式不是另一个更大表达式的子表达式。

y = (4 + x++) + (6 + x++);

  • 表达式4+x++不是一个完整的表达式,所以C无法保证x在子表达式4+x++求值后立即递增x。这里,完整表达式是整个赋值表达式语句,分号标记了序列点。
  • 所以,C保证程序在执行下一条语句之前递增x两次。C并未指明是在对子表达式求值以后递增x,还是对所有表达式求值后再递增x。因此,要尽量避免编写类似的语句。

复合语句(块)

  • 复合语句(compound statement)是用花括号括起来的一条或多条语句,复合语句也称为块
/* 程序段 1 */
index = 0;
while (index++ < 10)sam = 10 * index + 2;
printf("sam = %d\n", sam);
/* 程序段 2 */
index = 0;
while (index++ < 10)
{sam = 10 * index + 2;printf("sam = %d\n", sam);
}
  • 程序段1,while循环中只有一条赋值表达式语句。没有花括号,while语句从while这行运行至下一个分号。循环结束后,printf()函数只会被调用一次。
  • 程序段2,花括号确保两条语句都是while循环的一部分,每执行一次循环就调用一次printf()函数。根据while语句的结构,整个复合语句被视为一条语句

风格提示

  • 再看一下前面的两个while程序段,注意循环体中的缩进。缩进对编译器不起作用,编译器通过花括号和while循环的结构来识别和解释指令。这里,缩进是为了让读者一眼就可以看出程序是如何组织的。

  • 程序段2中,块或复合语句放置花括号的位置是一种常见的风格。另一种常用的风格是:

    while (index++ < 10)
    {sam = 10*index + 2;printf("sam = %d \n", sam);
    }
    
  • 这种风格突出了块附属于while循环,而前一种风格则强调语句形成一个块。对编译器而言,这两种风格完全相同。

  • 总而言之,使用缩进可以为读者指明程序的结构。

类型转换

  • 通常,在语句和表达式中应使用类型相同的变量和常量。但是,如果使用混合类型,C不会像Pascal那样停在那里死掉,而是采用一套规则进行自动类型转换。虽然这很便利,但是有一定的危险性,尤其是在无意间混合使用类型的情况下

  • 基本的类型转换规则。

    1. 当类型转换出现在表达式时,无论是unsigned还是signed的char和short都会被自动转换成int,如有必要会被转换成unsigned int(如果short与int的大小相同,unsigned short就比int大。这种情况下,unsigned short会被转换成unsigned int)。在K&R那时的C中,float会被自动转换成double(目前的C不是这样)。由于都是从较小类型转换为较大类型,所以这些转换被称为升级(promotion)。
    2. 涉及两种类型的运算,两个值会被分别转换成两种类型的更高级别。
    3. 类型的级别从高至低依次是long double、double、float、unsigned long long、long long、unsigned long、long、unsigned int、int。例外的情况是,当long和int的大小相同时,unsigned int比long的级别高。之所以short和char类型没有列出,是因为它们已经被升级到int或unsigned int。
    4. 在赋值表达式语句中,计算的最终结果会被转换成被赋值变量的类型。这个过程可能导致类型升级或降级(demotion)。所谓降级,是指把一种类型转换成更低级别的类型。
    5. 当作为函数参数传递时,char和short被转换成int,float被转换成double。第9章将介绍,函数原型会覆盖自动升级。
  • 类型升级通常都不会有什么问题,但是类型降级会导致真正的麻烦。原因很简单:较低类型可能放不下整个数字。例如,一个8位的char类型变量存储整数101没问题,但是存不下22334。

  • 待赋值的值与目标类型不匹配时,规则如下。

    1. 目标类型是无符号整型,且待赋的值是整数时,额外的位将被忽略。例如,如果目标类型是8位unsigned char,待赋的值是原始值求模256。
    2. 如果目标类型是一个有符号整型,且待赋的值是整数,结果因实现而异。
    3. 如果目标类型是一个整型,且待赋的值是浮点数,该行为是未定义的。
  • 如果把一个浮点值转换成整数类型会怎样?当浮点类型被降级为整数类型时,原来的浮点值会被截断。例如,23.12和23.99都会被截断为23,-23.5会被截断为-23。

/* convert.c -- 自动类型转换 */
#include <stdio.h>
int main(void)
{char ch;int i;float fl;fl = i = ch = 'C';                                  /* 第9行 */printf("ch = %c, i = %d, fl = %2.2f\n", ch, i, fl); /* 第10行 */ch = ch + 1;                                        /* 第11行 */i = fl + 2 * ch;                                    /* 第12行 */fl = 2.0 * ch + i;                                  /* 第13行 */printf("ch = %c, i = %d, fl = %2.2f\n", ch, i, fl); /* 第14行 */ch = 1107;                                          /* 第15行 */printf("Now ch = %c\n", ch);                        /* 第16行 */ch = 80.89;                                         /* 第17行 */printf("Now ch = %c\n", ch);                        /* 第18行 */return 0;
}

ch = C, i = 67, fl = 67.00

ch = D, i = 203, fl = 339.00

Now ch = S

Now ch = P

  • 第9行和第10行:字符’C’被作为1字节的ASCII值存储在ch中。整数变量i接受由’C’转换的整数,即按4字节存储67。最后,fl接受由67转换的浮点数67.00。
  • 第11行和第14行:字符变量’C’被转换成整数67,然后加1。计算结果是4字节整数68,被截断成1字节存储在ch中。根据%c转换说明打印时,68被解释成’D’的ASCII码。
  • 第12行和第14行:ch的值被转换成4字节的整数(68),然后2乘以ch。为了和fl相加,乘积整数(136)被转换成浮点数。计算结果(203.00f)被转换成int类型,并存储在i中。
  • 第13行和第14行:ch的值(‘D’,或68)被转换成浮点数,然后2乘以ch。为了做加法,i的值(203)被转换为浮点类型。计算结果(339.00)被存储在fl中。
  • 第15行和第16行:演示了类型降级的示例。把ch设置为一个超出其类型范围的值,忽略额外的位后,最终ch的值是字符S的ASCII码。或者,更确切地说,ch的值是1107 % 256,即83。
  • 第17行和第18行:演示了另一个类型降级的示例。把ch设置为一个浮点数,发生截断后,ch的值是字符P的ASCII码。

强制类型转换运算符

  • 通常,应该避免自动类型转换,尤其是类型降级。但是如果能小心使用,类型转换也很方便。

  • 有时需要进行精确的类型转换,或者在程序中表明类型转换的意图。这种情况下要用到强制类型转换(cast),即在某个量的前面放置用圆括号括起来的类型名,该类型名即是希望转换成的目标类型。圆括号和它括起来的类型名构成了强制类型转换运算符(cast operator),其通用形式是:
    (type)

mice = 1.6 + 1.7;

mice = (int)1.6 + (int)1.7;

  • 第1行使用自动类型转换。首先,1.6和1.7相加得3.3。然后,为了匹配int类型的变量,3.3被类型转换截断为整数3。第2行,1.6和1.7在相加之前都被转换成整数(1),所以把1+1的和赋给变量mice。

带参数的函数

/* pound.c -- 定义一个带一个参数的函数  */
#include <stdio.h>
void pound(int n); // ANSI函数原型声明
int main(void)
{int times = 5;char ch = '!'; // ASCII码是33float f = 6.0;pound(times); // int类型的参数pound(ch);    // 和pound((int)ch);相同pound(f);     // 和pound((int)f);相同return 0;
}
void pound(int n) // ANSI风格函数头
{ // 表明该函数接受一个int类型的参数while (n-- > 0)printf("#");printf("\n");
}

void pound(int n)

  • 如果函数不接受任何参数,函数头的圆括号中应该写上关键字void。由于该函数接受一个int类型的参数,所以圆括号中包含一个int类型变量n的声明。参数名应遵循C语言的命名规则。

  • 声明参数就创建了被称为形式参数(formal argument或formal parameter,简称形参)的变量。该例中,形式参数是int类型的变量n。像pound(10)这样的函数调用会把10赋给n。在该程序中,调用pound(times)就是把times的值(5)赋给n。我们称函数调用传递的值为实际参数(actual argument或actual parameter),简称实参。所以,函数调用pound(10)把实际参数10传递给函数,然后该函数把10赋给形式参数(变量n)。也就是说,main()中的变量times的值被拷贝给pound()中的新变量n。

  • 我们可以说形参是变量,实参是函数调用提供的值,实参被赋给相应的形参。因此,在程序清单5.15中,times是pound()的实参,n是pound()的形参。类似地,在函数调用pound(times + 4)中,表达式times + 4的值是该函数的实参。

  • 变量名是函数私有的,即在函数中定义的变量名不会和别处的相同名称发生冲突。如果在pound()中用times代替n,那么这个times与main()中的times不同。也就是说,程序中出现了两个同名的变量,但是程序可以区分它们。

  • 现在,我们来学习函数调用。第1个函数调用是pound(times),times的值5被赋给n。因此,printf()函数打印了5个井号和1个换行符。第2个函数调用是pound(ch)。这里,ch是char类型,被初始化为!字符,在ASCII中ch的数值是33。但是pound()函数的参数类型是int,与char不匹配。程序开头的函数原型在这里发挥了作用。原型(prototype)即是函数的声明,描述了函数的返回值和参数。pound()函数的原型说明了两点:

    ·该函数没有返回值(函数名前面有void关键字);
    ·该函数有一个int类型的参数。

  • 该例中,函数原型告诉编译器pound()需要一个int类型的参数。相应地,当编译器执行到pound(ch)表达式时,会把参数ch自动转换成int类型。在我们的系统中,该参数从1字节的33变成4字节的33,所以现在33的类型满足函数的要求。与此类似,最后一次调用是pound(f),使得float类型的变量被转换成合适的类型。

  • 在ANSI C之前,C使用的是函数声明,而不是函数原型。函数声明只指明了函数名和返回类型,没有指明参数类型。为了向下兼容,C现在仍然允许这样的形式:

    void pound(); /* ANSI C之前的函数声明 */

  • 如果用这条函数声明代替pound.c程序中的函数原型会怎样?第1次函数调用,pound(times)没问题,因为times是int类型。第2次函数调用,pound(ch)也没问题,因为即使缺少函数原型,C也会把char和short类型自动升级为int类型。第3次函数调用,pound(f)会失败,因为缺少函数原型,float会被自动升级为double,这没什么用。虽然程序仍然能运行,但是输出的内容不正确。在函数调用中显式使用强制类型转换,可以修复这个问题

  • 注意,如果f的值太大,超过了int类型表示的范围,这样做也不行。

示例程序

// running.c -- A useful program for runners
#include <stdio.h>
const int S_PER_M = 60;         // 1分钟的秒数
const int S_PER_H = 3600;       // 1小时的秒数
const double M_PER_K = 0.62137; // 1公里的英里数
int main(void)
{double distk, distm; // 跑过的距离(分别以公里和英里为单位)double rate;         // 平均速度(以英里/小时为单位)int min, sec;        // 跑步用时(以分钟和秒为单位)int time;            // 跑步用时(以秒为单位)double mtime;        // 跑1英里需要的时间,以秒为单位int mmin, msec;      // 跑1英里需要的时间,以分钟和秒为单位printf("This program converts your time for a metric race\n");printf("to a time for running a mile and to your average\n");printf("speed in miles per hour.\n");printf("Please enter, in kilometers, the distance run.\n");scanf("%lf", &distk); // %lf表示读取一个double类型的值printf("Next enter the time in minutes and seconds.\n");printf("Begin by entering the minutes.\n");scanf("%d", &min);printf("Now enter the seconds.\n");scanf("%d", &sec);time = S_PER_M * min + sec; // 把时间转换成秒distm = M_PER_K * distk;    // 把公里转换成英里rate = distm / time * S_PER_H; // 英里/秒×秒/小时 = 英里/小时mtime = (double)time / distm;  // 时间/距离 = 跑1英里所用的时间mmin = (int)mtime / S_PER_M;   // 求出分钟数msec = (int)mtime % S_PER_M;   // 求出剩余的秒数printf("You ran %1.2f km (%1.2f miles) in %d min, %d sec.\n", distk, distm, min, sec);printf("That pace corresponds to running a mile in %d min, ", mmin);printf("%d sec.\nYour average speed was %1.2f mph.\n", msec, rate);return 0;
}

This program converts your time for a metric race

to a time for running a mile and to your average

speed in miles per hour.

Please enter, in kilometers, the distance run.10.0

Next enter the time in minutes and seconds.

Begin by entering the minutes.36

Now enter the seconds.23

You ran 10.00 km (6.21 miles) in 36 min, 23 sec.

That pace corresponds to running a mile in 5 min, 51 sec.

Your average speed was 10.25 mph.

关键概念

  • C通过运算符提供多种操作。每个运算符的特性包括运算对象的数量、优先级和结合律。当两个运算符共享一个运算对象时,优先级和结合律决定了先进行哪项运算。每个C表达式都有一个值。如果不了解运算符的优先级和结合律,写出的表达式可能不合法或者表达式的值与预期不符。这会影响你成为一名优秀的程序员。
  • 虽然C允许编写混合数值类型的表达式,但是算术运算要求运算对象都是相同的类型。因此,C会进行自动类型转换。尽管如此,不要养成依赖自动类型转换的习惯,应该显式选择合适的类型或使用强制类型转换。这样,就不用担心出现不必要的自动类型转换。

本章小结

  • C语言有许多运算符,如本章讨论的赋值运算符和算术运算符。一般而言,运算符需要一个或多个运算对象才能完成运算生成一个值。只需要一个运算对象的运算符(如负号和sizeof)称为一元运算符,需要两个运算对象的运算符(如加法运算符和乘法运算符)称为二元运算符。
  • 表达式由运算符和运算对象组成。在C语言中,每个表达式都有一个值,包括赋值表达式和比较表达式。运算符优先级规则决定了表达式中各项的求值顺序。当两个运算符共享一个运算对象时,先进行优先级高的运算。如果运算符的优先级相等,由结合律(从左往右或从右往左)决定求值顺序。
  • 大部分语句都以分号结尾。最常用的语句是表达式语句。用花括号括起来的一条或多条语句构成了复合语句(或称为块)。while语句是一种迭代语句,只要测试条件为真,就重复执行循环体中的语句。
  • 在C语言中,许多类型转换都是自动进行的。当char和short类型出现在表达式里或作为函数的参数(函数原型除外)时,都会被升级为int类型;float类型在函数参数中时,会被升级为double类型。在K&R C(不是ANSI C)下,表达式中的float也会被升级为double类型。当把一种类型的值赋给另一种类型的变量时,值将被转换成与变量的类型相同。当把较大类型转换成较小类型时(如,long转换成short,或double转换成float),可能会丢失数据。根据本章介绍的规则,在混合类型的运算中,较小类型会被转换成较大类型。
  • 定义带一个参数的函数时,便在函数定义中声明了一个变量,或称为形式参数。然后,在函数调用中传入的值会被赋给这个变量。这样,在函数中就可以使用该值了。

复习题

  1. 假设所有变量的类型都是int,下列各项变量的值是多少:

    a.x = (2 + 3) * 6;

    b.x = (12 + 6)/2*3;

    c.y = x = (2 + 3)/4;
    d.y = 3 + 2*(x = 7/2);

  2. 假设所有变量的类型都是int,下列各项变量的值是多少:

a.x = (int)3.8 + 3.3;

b.x = (2 + 3) * 10.5;

c.x = 3 / 5 * 22.0;

d.x = 22.0 * 3 / 5;

  1. 对下列各表达式求值:

a.30.0 / 4.0 * 5.0;

b.30.0 / (4.0 * 5.0);

c.30 / 4 * 5;

d.30 * 5 / 4;

e.30 / 4.0 * 5;

f.30 / 4 * 5.0;

  1. 请找出下面的程序中的错误。
   int main(void){int i = 1,float n;printf("Watch out! Here come a bunch of fractions!\n");while (i < 30)n = 1 / i;printf(" %f", n);printf("That's all, folks!\n");return;}
  1. 这是程序清单5.9的另一个版本。从表面上看,该程序只使用了一条scanf()语句,比程序清单5.9简单。请找出不如原版之处。
   #include <stdio.h>#define S_TO_M 60int main(void){int sec, min, left;printf("This program converts seconds to minutes and ");printf("seconds.\n");printf("Just enter the number of seconds.\n");printf("Enter 0 to end the program.\n");while (sec > 0){scanf("%d", &sec);min = sec / S_TO_M;left = sec % S_TO_M;printf("%d sec is %d min, %d sec. \n", sec, min, left);printf("Next input?\n");}printf("Bye!\n");return 0;}
  1. 下面的程序将打印出什么内容?
   #include <stdio.h>#define FORMAT "%s! C is cool!\n"int main(void){int num = 10;printf(FORMAT, FORMAT);printf("%d\n", ++num);printf("%d\n", num++);printf("%d\n", num--);printf("%d\n", num);return 0;}
  1. 下面的程序将打印出什么内容?
   #include <stdio.h>int main(void){char c1, c2;int diff;float num;c1 = 'S';c2 = 'O';diff = c1 - c2;num = diff;printf("%c%c%c:%d %3.2f\n", c1, c2, c1, diff, num);return 0;}
  1. 下面的程序将打印出什么内容?
   #include <stdio.h>#define TEN 10int main(void){int n = 0;while (n++ < TEN)printf("%5d", n);printf("\n");return 0;}
  1. 修改上一个程序,使其可以打印字母a~g。

  2. 假设下面是完整程序中的一部分,它们分别打印什么?

    a.
    int x = 0;

    while (++x < 3)

printf("%4d", x);
b.
int x = 100;

while (x++ < 103)

​ printf("%4d\n",x);

printf("%4d\n",x);
c.
char ch = ‘s’;

while (ch < ‘w’)

{

​ printf("%c", ch);

​ ch++;

}

printf("%c\n",ch);

  1. 下面的程序会打印出什么?
 #define MESG "COMPUTER BYTES DOG"#include <stdio.h>int main(void){int n = 0;while (n < 5)printf("%s\n", MESG);n++;printf("That's all.\n");return 0;}
  1. 分别编写一条语句,完成下列各任务(或者说,使其具有以下副作用):
    a.将变量x的值增加10
    b.将变量x的值增加1
    c.将a与b之和的两倍赋给c
    d.将a与b的两倍之和赋给c

  2. 分别编写一条语句,完成下列各任务:
    a.将变量x的值减少1
    b.将n除以k的余数赋给m
    c.q除以b减去a的差,并将结果赋给p
    d.a与b之和除以c与d的乘积,并将结果赋给x


  1. a.30
    b.27(不是3)。(12+6)/(2*3)得3。
    c.x = 1,y = 1(整数除法)。
    d.x = 3(整数除法),y = 9。

  2. a.6(由3+3.3截断而来)
    b.52
    c.0(0 * 22.0的结果)
    d.13(66.0 / 5或13.2,然后把结果赋给int类型变量)

  3. a.37.5(7.5 * 5.0的结果)
    b.1.5(30.0 / 20.0的结果)
    c.35(7 * 5的结果)
    d.37(150 / 4的结果)
    e.37.5(7.5 * 5的结果)
    f.35.0(7 * 5.0的结果)

    • 第0行:应增加一行#include <stdio.h>。
    • 第3行:末尾用分号,而不是逗号。
    • 第6行:while语句创建了一个无限循环。因为i的值始终为1,所以它总是小于30。推测一下,应该是想写while(i++ < 30)。
    • 第6~8行:这样的缩进布局不能使第7行和第8行组成一个代码块。由于没有用花括号括起来,while循环只包括第7行,所以要添加花括号。
    • 第7行:因为1和i都是整数,所以当i为1时,除法的结果是1;当i为更大的数时,除法结果为0。用n = 1.0/i,i在除法运算之前会被转换为浮点数,这样就能得到非零值。
    • 第8行:在格式化字符串中没有换行符(\n),这导致数字被打印成一行。
    • 第10行:应该是return 0;
      下面是正确的版本:
    #include <stdio.h>
    int main(void)
    {int i = 1;float n;printf("Watch out! Here come a bunch of fractions!\n");while (i++ < 30){n = 1.0 / i;printf(" %f\n", n);}printf("That's all, folks!\n");return 0;
    }
    
  4. 这个版本最大的问题是测试条件(sec是否大于0?)和scanf()语句获取sec变量的值之间的关系。具体地说,第一次测试时,程序尚未获得sec的值,用来与0作比较的是正好在sec变量内存位置上的一个垃圾值。一个比较笨拙的方法是初始化sec(如,初始化为1)。这样就可通过第一次测试。不过,还有另一个问题。当最后输入0结束程序时,在循环结束之前不会检查sec,所以0也被打印了出来。因此,更好的方法是在while测试之前使用scanf()语句。可以这样修改:

    scanf("%d", &sec);

    while ( sec > 0 ) {

    min = sec/S_TO_M;

    left = sec % S_TO_M;

    printf("%d sec is %d min, %d sec. \n", sec, min, left);

    printf(“Next input?\n”);

    scanf("%d", &sec);

    }

    while循环第一轮迭代使用的是scanf()在循环外面获取的值。因此,在while循环的末尾还要使用一次scanf()语句。这是处理类似问题的常用方法。

  5. 下面是该程序的输出:

    %s! C is cool!%s! C is cool!

    11

    11

    12

    11

    解释一下。第1个printf()语句与下面的语句相同:

    printf("%s! C is cool!\n","%s! C is cool!\n");

    第2个printf()语句首先把num递增为11,然后打印该值。第3个printf()语句打印num的值(值为11)。第4个printf()语句打印n当前的值(仍为12),然后将其递减为11。最后一个printf()语句打印num的当前值(值为11)。

  6. 下面是该程序的输出:

SOS:4 4.00

表达式c1 -c2的值和’S’ - '0’的值相同(其对应的ASCII值是83 - 79)。

  1. 把1~10打印在一行,每个数字占5列宽度,然后开始新的一行:
    1 2 3 4 5 6 7 8 9 10

  2. 下面是一个参考程序,假定字母连续编码,与ASCII中的情况一样。

    #include <stdio.h>
    int main(void)
    {char ch = 'a';while (ch <= 'g')printf("%5c", ch++);printf("\n");return 0;
    }
    
  3. 下面是每个部分的输出:
    a.1 2
    注意,先递增x的值再比较。光标仍留在同一行。
    b.101
    102
    103
    104
    注意,这次x先比较后递增。在示例a和b中,x都是在先递增后打印。另外还要注意,虽然第2个printf()语句缩进了,但是这并不意味着它是while循环的一部分。因此,在while循环结束后,才会调用一次该printf()语句。
    c.stuvw
    该例中,在第1次调用printf()语句后才会递增ch。

  4. 这个程序有点问题。while循环没有用花括号把两个缩进的语句括起来,只有printf()是循环的一部分,所以该程序一直重复打印消息COMPUTER BYTES DOG,直到强行关闭程序为止。

  5. a.x = x + 10;
    b.x++; or ++x; or x = x + 1;
    c.c = 2 * (a+b);
    d.c = a + 2* b;

  6. a.x–; or --x; or x = x - 1;
    b.m = n % k;
    c.p = q / (b - a);
    d.x = (a + b) / (c * d);

编程练习

  1. 编写一个程序,把用分钟表示的时间转换成用小时和分钟表示的时间。使用#define或const创建一个表示60的符号常量或const变量。通过while循环让用户重复输入值,直到用户输入小于或等于0的值才停止循环。
  2. 编写一个程序,提示用户输入一个整数,然后打印从该数到比该数大10的所有整数(例如,用户输入5,则打印5~15的所有整数,包括5和15)。要求打印的各值之间用一个空格、制表符或换行符分开。
  3. 编写一个程序,提示用户输入天数,然后将其转换成周数和天数。例如,用户输入18,则转换成2周4天。以下面的格式显示结果:
    18 days are 2 weeks, 4 days.
    通过while循环让用户重复输入天数,当用户输入一个非正值时(如0或-20),循环结束。
  4. 编写一个程序,提示用户输入一个身高(单位:厘米),并分别以厘米和英尺、英寸为单位显示该值,允许有小数部分。程序应该能让用户重复输入身高,直到用户输入一个非正值。其输出示例如下:
    Enter a height in centimeters: 182
    182.0 cm = 5 feet, 11.7 inches
    Enter a height in centimeters (<=0 to quit): 168.7
    168.7 cm = 5 feet, 6.4 inches
    Enter a height in centimeters (<=0 to quit): 0
    bye
  5. 修改程序addemup.c(程序清单5.13),你可以认为addemup.c是计算20天里赚多少钱的程序(假设第1天赚$1、第2天赚$2、第3天赚$3,以此类推)。修改程序,使其可以与用户交互,根据用户输入的数进行计算(即,用读入的一个变量来代替20)。
  6. 修改编程练习5的程序,使其能计算整数的平方和(可以认为第1天赚$1、第2天赚$4、第3天赚$9,以此类推,这看起来很不错)。C没有平方函数,但是可以用n * n来表示n的平方。
  7. 编写一个程序,提示用户输入一个double类型的数,并打印该数的立方值。自己设计一个函数计算并打印立方值。main()函数要把用户输入的值传递给该函数。
  8. 编写一个程序,显示求模运算的结果。把用户输入的第1个整数作为求模运算符的第2个运算对象,该数在运算过程中保持不变。用户后面输入的数是第1个运算对象。当用户输入一个非正值时,程序结束。其输出示例如下:
    This program computes moduli.
    Enter an integer to serve as the second operand: 256
    Now enter the first operand: 438
    438 % 256 is 182
    Enter next number for first operand (<= 0 to quit): 1234567
    1234567 % 256 is 135
    Enter next number for first operand (<= 0 to quit): 0
    Done
  9. 编写一个程序,要求用户输入一个华氏温度。程序应读取double类型的值作为温度值,并把该值作为参数传递给一个用户自定义的函数Temperatures()。该函数计算摄氏温度和开氏温度,并以小数点后面两位数字的精度显示3种温度。要使用不同的温标来表示这3个温度值。下面是华氏温度转摄氏温度的公式:
    摄氏温度 = 5.0 / 9.0 * (华氏温度 - 32.0)
    开氏温标常用于科学研究,0表示绝对零,代表最低的温度。下面是摄氏温度转开氏温度的公式:
    开氏温度 = 摄氏温度 + 273.16
    Temperatures()函数中用const创建温度转换中使用的变量。在main()函数中使用一个循环让用户重复输入温度,当用户输入q或其他非数字时,循环结束。scanf()函数返回读取数据的数量,所以如果读取数字则返回1,如果读取q则不返回1。可以使用==运算符将scanf()的返回值和1作比较,测试两值是否相等。

  1. #include<stdio.h>
    int main(void)
    {const int sixty = 60;int min, hour;printf("Please input minute(s) number: ");scanf("%d", &min);while (min > 0){hour = min / sixty;min %= 60;printf("%d hour(s) %d min(s)\n", hour, min);printf("Please input minute(s) number: ");scanf("%d", &min);}printf("Done!");return 0;
    }
    
  2. #include <stdio.h>
    int main(void)
    {int num;printf("Please input a number:");scanf("%d", &num);int end = num + 10;while (num <= end){printf("%d ", num);num++;}printf("\n");return 0;
    }
    
  3. #include <stdio.h>
    int main(void)
    {const int day_per_week = 7;printf("Please input a number of day(s):");int day;scanf("%d", &day);while (day > 0){/* code */int temp = day;int week = day / day_per_week;day %= day_per_week;printf("%d days are %d weeks, %d days.\n", temp, week, day);printf("Please input a number of day(s):");scanf("%d", &day);}printf("Done!\n");return 0;
    }
    
  4. #include <stdio.h>
    int main(void)
    {float tall;printf("Enter a height in centimeters (<=0 to quit): ");scanf("%f", &tall);while (tall > 0){tall;printf("%.1f cm = %d feet, %.1f inches\n", tall, (int)(tall / 30.48), tall / 2.54 - (int)(tall / 30.48) * 12);printf("Enter a height in centimeters (<=0 to quit): ");scanf("%f", &tall);}printf("bye\n");return 0;
    }
    
  5. #include <stdio.h>
    int main(void)
    {int day;printf("Please enter a number of day(s): ");scanf("%d", &day);int total, current;current = 1;// !别忘记初始化total,否则它是内存中的任意值,不会得到正确结果total = 0;while (current <= day){/* code */total += current;current++;printf("total:%d\n", total);}printf("You get $%d in %dday(s)!\n", total, day);return 0;
    }
    
  6. #include <stdio.h>
    int main(void)
    {int day;printf("Please enter a number of day(s): ");scanf("%d", &day);int total, current;current = 1;// !别忘记初始化total,否则它是内存中的任意值,不会得到正确结果total = 0;while (current <= day){/* code */total += current * current;current++;printf("total:%d\n", total);}printf("You get $%d in %dday(s)!\n", total, day);return 0;
    }
    
  7. #include <stdio.h>
    double getCube(double n);
    int main(void)
    {double num;printf("Please enter a number: ");scanf("%lf", &num);printf("The cube of %g is %g\n", num, getCube(num));return 0;
    }
    double getCube(double n)
    {return n * n * n;
    }
    
  8. #include <stdio.h>
    int main(void)
    {printf("This program computes moduli.\n");int first, second;printf("Enter an integer to serve as the second operand: ");scanf("%d", &second);printf("Now enter the first operand: ");scanf("%d", &first);while (first > 0){/* code */printf("%d %% %d is %d\n", first, second, first % second);printf("Enter next number for first operand (<= 0 to quit): \n");scanf("%d", &first);}printf("Done\n");return 0;
    }
    
  9. #include <stdio.h>
    void Temperatures(double F);
    int main(void)
    {double f;while (1){printf("Please enter a number of temperature(s): ");int judge = scanf("%lf", &f);if (judge != 1){break;}Temperatures(f);}printf("Done!\n");return 0;
    }
    void Temperatures(double F)
    {const double tempF = F;double C,K;C = 5.0/9.0*(tempF-32.0);K = C+273.16;printf("F:%.2f C:%.2f K:%.2f\n",F,C,K);return;
    }
    }
    double getCube(double n)
    {return n * n * n;
    }
    

《C Primer Plus》第五章-运算符 表达式和语句(笔记、复习题、编程题,副作用和序列点,升降级)相关推荐

  1. 第五章运算符表达式和语句

    title: 第五章 运算符.表达式和语句 author: HardyDragon tags: C Notes 第五章 运算符.表达式和语句 5.1 循环简介 5.2 基本运算符 5.2.1 赋值运算 ...

  2. c primer plus 第五章编程练习

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 目录 文章目录 前言 ##1.编写一个程序,把用分钟表示的时间转换成用小时和分钟表示的时间.使用#define或const创 ...

  3. 【JAVA SE】第三章 运算符、条件语句、switch结构、while循环、do…while循环、for循环、break关键字以及break和continue关键字

    第三章 运算符.条件语句.switch结构.for循环.break关键字以及break和continue关键字 文章目录 第三章 运算符.条件语句.switch结构.for循环.break关键字以及b ...

  4. linux 两个驱动 竞争,Linux设备驱动第五章(并发和竞争)读书笔记(国外英文资料).doc...

    Linux设备驱动第五章(并发和竞争)读书笔记(国外英文资料) Linux设备驱动第五章(并发和竞争)读书笔记(国外英文资料) The fifth chapter is concurrency and ...

  5. 第五章 数据库设计和事务 ① 笔记

    第五章 数据库设计和事务 ① 笔记 1.内容回顾 2.本章重点 3.具体内容 3.1:数据库三大范式(设计规则)  面试题:(重要) 第一范式(1NF): 第二范式(2NF): 第三范式设计表: 3 ...

  6. 机器学习原来这么有趣!第五章:Google 翻译背后的黑科技:神经网络和序列到序列学习

    第一章:全世界最简单的机器学习入门指南 https://blog.csdn.net/wskzgz/article/details/89917343 第二章:用机器学习制作超级马里奥的关卡 https: ...

  7. 《C Primer Plus》中文第六版 编程练习答案 第五章 运算符、表达式和语句

    C Primer Plus 第5章 运算符.表达式和语句 编程练习答案 ***先说一下关于 i++和 ++i 情况.*** 1.编写一个程序,把用分钟表示的时间转换成用小时和分钟表示的时间.使用#de ...

  8. 【C++ Primer】第五章学习笔记(循环和关系表达式)

    一,for循环       1,在C++中每一个表达式都有值:                int  a=0//声明表达式                23+17   //数学表达式       ...

  9. C Primer Plus 第五章 复习题编程练习 答案

    第五章 复习题&编程练习 复习题 1. 假设所有变量的类型都是int,下列各项变量的值是多少: 2. 假设所有变量的类型都是int,下列各项变量的值是多少: 3. 对下列各表达式求值: 4. ...

最新文章

  1. 我国网络安全人才培养缺口巨大
  2. 最新数据:一图看清全球393家独角兽公司
  3. php使用imagemagick,PHP的ImageMagick使用;
  4. 单片机平台的最小偏差圆弧插补算法
  5. C++for_each| bind1st | ptr_fun | std::function的用法
  6. 2019 年编写现代 JavaScript 代码的5个小技巧
  7. Visual Stdio下安装WTL的向导Widzard
  8. Asp.Net中WebApi多图片上传(附前后端代码)
  9. php indexof(,JavaScript indexOf() 方法
  10. 计算机随机数是如何生成的?(平分取中法、线性同余法)
  11. java手动销毁对象_java对象的销毁详解
  12. HttpResponse 417
  13. 头歌-自己动手画CPU(第五关)-寄存器文件设计-Logisim
  14. [SPRD CAMERA] 5 HAL Camera open流程二
  15. 7人制足球技战术要点
  16. 76、多边形一些基本操作(自相交、尖刺、保证逆时针、求交)
  17. 华为认证三个等级区别在哪?
  18. C#毕业设计——基于C#+asp.net+sqlserver的设备管理系统设计与实现(毕业论文+程序源码)——设备管理系统
  19. xlwt/xlrd库的区别
  20. win10 强制进入bios页面

热门文章

  1. 【GD32F310开发板试用】Contiki-NG在GD32F310的移植
  2. AE2022 Ver22.3内容更新点汇总 一文了解AE2022最新版本
  3. Mybatis阶段常用单词
  4. 从黑格尔的正反合理论看人生的三个阶段
  5. vue+flv.js实现视频播放
  6. java计算指定日期的上个月
  7. 订单系统设计 —— 数据同步与监控
  8. [构造] Codeforces Gym 101173 CERC 16 K BZOJ 4796 Key Knocking
  9. 深度学习理论与实践——课程笔记(一)
  10. 3dmax 管子动画_3DMax水管里流水的动画怎么做呢?