Java 基础
Java 容器
Java 并发
设计模式
目录

变量与算术表达式

# 变量与算术表达式

我们来看下一个程序,使用公式℃=(5/9)(℉-32)打印下列华氏温度与摄氏温度对照表:

摄氏 华氏
1 -17
20 -6
40 4
60 15
80 26
100 37
120 48
140 60
160 71
180 82
200 93
220 104
240 115
260 126
280 137
300 148

此程序中仍然只包括一个名为 main 的函数定义。它比前面打印“hello, world”的程序 长一些,但并不复杂。这个程序中引入了一些新的概念,包括注释、声明、变量、算术表达式、循环以及格式化输出。该程序如下所示:

#include <stdio.h> 
/* 当 fahr=0,20,… ,300 时,分别
 打印华氏温度与摄氏温度对照表 */ 
int main() 
{ 
 int fahr, celsius; 
 int lower, upper, step; 
 lower = 0; /* 温度表的下限 */ 
 upper = 300; /* 温度表的上限 */ 
 step = 20; /* 步长 */ 
 fahr = lower; 
 while (fahr <= upper) { 
 celsius = 5 * (fahr-32) / 9; 
 printf("%d\t%d\n", fahr, celsius); 
 fahr = fahr + step; 
 } 
 return 0;
} 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

其中的两行:

/*当 fahr=0,20,… ,300 时,分别 打印华氏温度与摄氏温度对照表 */

称为注释,此处,它简单地解释,该程序是做什么用的。包含在/与/之间的字符序列将被编译器忽略。注释可以自由地运用在程序中,使得程序更易于理解。程序中允许出现空格、制表符或换行符之处,都可以使用注释。

在 C 语言中,所有变量都必须先声明后使用。声明通常放在函数起始处,在任何可执行 语句之前。声明用于说明变量的属性,它由一个类型名和一个变量表组成,例如:

 int fahr, celsius; 
 int lower, upper, step; 
1
2

其中,类型 int 表示其后所列变量为整数,与之相对应的,float 表示所列变量为浮点数(即,可以带有小数部分的数)。int 与 float 类型的取值范围取决于具体的机器。对于 int 类型,通常为 32 位,其取值范围在-2^31~~2^31-1之间。float 类型通常是 32 位,它至少有 6 位有效数字,取值范围一般在 10-38~1038之间。 除 int与 float类型之外,C语言还提供了其它一些基本数据类型,例如:

  • char 字符——一个字节
  • short 短整型
  • long 长整型
  • double 双精度浮点型

这些数据类型对象的大小也取决于具体的机器。另外,还存在这些基本数据类型的数组、结构、联合,指向这些类型的指针以及返回这些类型值的函教。我们将在后续相应的章节中分 别介绍。

在上面的温度转换程序中,最开始执行的计算是下列 4 个赋值语句:

 lower = 0;
 upper = 300; 
 step = 20; 
 fahr = lower; 
1
2
3
4

它们为变量设置初值。各条语句均以分号结束。

温度转换表中的各行计算方式相同,因此可以用循环语句重复输出各行。这是 while 循环语句的用途:

 while (fahr <= upper) { 
 ... 
 }
1
2
3

while 循环语句的执行方式是这样的:首先测试圆括号中的条件;如果条件为真 (fahr<=upper),则执行循环体(括在花括号中的 3 条语句);然后再重新测试圆括号中的条件,如果为真,则再次执行循环体;当圆括号中的条件测试结果为假(fahr>upper)时,循环结束,并继续执行跟在 while 循环语句之后的下一条语句。在本程序中,循环语句后没有其它语句,因此整个程序的执行终止。

while 语句的循环体可以是用花括号括起来的一条或多条语句(如上面的温度转换程序),也可以是不用花括号包括的单条语句,例如:

 while (i < j) 
 	i = 2 * i; 
1
2

在这两种情况下,我们总是把由 while 控制的语句缩进一个制表位,这样就可以很容易地看出循环语句中包含哪些语句。这种缩进方式突出了程序的逻辑结构。尽管 C 编译器并不关心程序的外观形式,但正确的缩进以及保留适当空格的程序设计风格对程序的易读性非常重要。

我们建议每行只书写一条语句,并在运算符两边各加上一个空格字符,这样可以使得运算的结合关系更清楚明了。相比而言,花括号的位置就不那么重要了。我们从比较流行的一些风格中选择了一种,读者可以选择适合自己的一种风格,并养成一直使用这种风格的好习惯。

在该程序中,绝大部分工作都是在循环体中完成的。循环体中的赋值语句

celsius = 5 * (fahr - 32) / 9;
1

用于计算与指定华氏温度相对应的摄氏温度值,并将结果赋值给变量 celsius。在该语句中,之所以把表达式写成先乘 5 然后再除以 9 而不是直接写成 5 / 9,其原因是在 C 语言及许多其它语言中,整数除法操作将执行舍位,结果中的任何小数部分都会被舍弃。由于 5 和 9 都是整数,5 / 9 相除后经截取所得的结果为 0,因此这样求得的所有摄氏温度都将为 0。

从该例子中也可以看出 printf 函数的一些功能。printf 是一个通用输出格式化函数,第 7 章将对此做详细介绍。该函数的第一个参数是待打印的字符串,其中的每个百分号(%)表示其它的参数(第二个、第三个、……参数)之一进行替换的位置,并指定打印格式。例如,%d 指定一个整型参数,因此语句 printf(" %d\t%d\n", fahr, celsius); 用于打印两个整数 fahr 与 celsius 的值,并在两者之间留一个制表符的空间(\t)。

printf 函数的第一个参数中的各个%分别对应于第二个、第三个、……参数,它们在数目和类型上都必须匹配,否则将出现错误的结果。

顺便指出,printf 函数并不是 C 语言本身的一部分,C 语言本身并没有定义输入/输出功能。printf 仅仅是标准库函数中一个有用的函数而已,这些标准序函数在 C 语言程序中通常都可以使用。但是,ANSI 标准定义了 printf 函数的行为,因此,对每个符合该标准的编译器和库来说,该函数的属性都是相同的。

为了将重点放到讲述 C 语言本身上,我们在第 7 章之前的各章中将不再对输入/输出做 更多的介绍,并且,特别将格式化输入推后到第 7 章讲解。如果读者想了解数据输入,可以 先阅读 7.4 节中对 scanf 函数的讨论部分,scanf 函数类似于 printf 函数,但它用于读输 入数据而不是写输出数据。

上述的温度转换程序存在两个问题。比较简单的问题是,由于输出的数不是右对齐的, 所以输出的结果不是很美观。这个问题比较容易解决:如果在 printf 语句的第一个参数的 %d 中指明打印宽度,则打印的数字会在打印区域内右对齐。例如,可以用语句

printf(" %3d %6d\n", fahr, celsius); 
1

打印 fahr 与 celsius 的值,这样,fahr 的值占 3 个数字宽,celsius 的值占 6 个数字宽, 输出的结果如下所示:

 0  -17 
 20 -6 
 40 4 
 60 15 
 80 26 
 100 37 
 ...
1
2
3
4
5
6
7

另一个较为严重的问题是,由于我们使用的是整型算术运算,因此经计算得到的摄氏温度值不太精确,例如,与 0℉对应的精确的摄氏温度应该为-17.8℃,而不是-17℃。为了得到 更精确的结果,应该用浮点算术运算代替上面的整型算术运算。这就需要对程序做适当修改。 下面是该程序的又一种版本

 #include <stdio.h> 
 /* print Fahrenheit-Celsius table 
 for fahr = 0, 20, ..., 300; floating-point version */ 
 int main() 
 { 
   float fahr, celsius; 
   float lower, upper, step; 
   lower = 0; /* lower limit of temperatuire scale */ 
   upper = 300; /* upper limit */ 
   step = 20; /* step size */ 
   fahr = lower; 
   while (fahr <= upper) { 
     celsius = (5.0/9.0) * (fahr-32.0); 
     printf("%3.0f %6.1f\n", fahr, celsius); 
     fahr = fahr + step; 
   } 
   return 0;
 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

这个程序与前一个程序基本相同,不同的是,它把 fahr 与 celsius 声明为 float 类 型,转换公式的表述方式也更自然一些。在前一个程序中,之所以不能使用 5 / 9 的形式, 是因为按整型除法的计算规则,它们相除并舍位后得到的结果为 0。但是,常数中的小数点表 明该常数是一个浮点数,因此,5.0 / 9.0 是两个浮点数相除,结果将不被舍位。

如果某个算术运算符的所有操作数均为整型,则执行整型运算。但是,如果某个算术运 算符有一个浮点型操作数和一个整型操作数,则在开始运算之前整型操作数将会被转换为浮 点型。例如,在表达式 fahr – 32 中,32 在运算过程中将被自动转换为浮点数再参与运算。 不过,即使浮点常量取的是整型值,在书写时最好还是为它加上一个显式的小数点,这样可 以强调其浮点性质,便于阅读。

第 2 章将详细介绍把整型数转换为浮点型数的规则。在这里需要注意,赋值语句 fahr = lower;

与条件测试语句 while (fahr <= upper) 也都是按照这种方式执行的,即在运算之前先把 int 类型的操作数转换为 float 类型的操作数。

printf 中的转换说明%3.0f 表明待打印的浮点数(即 fahr)至少占 3 个字符宽,且不带小数点和小数部分;%6.1f 表明另一个待打印的数(celsius)至少占 6 个字符宽,且小数点后面有 1 位数字。其输出如下所示:

 0 -17.8 
 20 -6.7 
 40 4.4 
 ... 
1
2
3
4

格式说明可以省略宽度与精度,例如,%6f 表示待打印的浮点数至少有 6 个字符宽;%.2f指定待打印的浮点数的小数点后有两位小数,但宽度没有限制;%f 则仅仅要求按照浮点数打印该数。

  • %d 按照十进制整型数打印
  • %6d 按照十进制整型数打印,至少 6 个字符宽
  • %f 按照浮点数打印
  • %6f 按照浮点数打印,至少 6 个字符宽
  • %.2f 按照浮点数打印,小数点后有两位小数
  • %6.2f 按照浮点数打印,至少 6 个字符宽,小数点后有两位小数

此外,printf 函数还支持下列格式说明:%o 表示八进制数;%x 表示十六进制数;%c表示字符;%s 表示字符串;%%表示百分号(%)本身。

# 练习 1-3 修改温度转换程序,使之能在转换表的顶部打印一个标题。

# 练习 1-4 编写一个程序打印摄氏温度转换为相应华氏温度的转换表。