2.4 声明
所有变量在使用前必须声明,尽管某些特定的声明可以由上下文隐式地做出。声明指定了类型,并包含一个或多个该类型的变量列表,如
int lower, upper, step;
char c, line[1000];
多个变量能以任何方式分布在多个声明之中;上面的列表可以等价地写成:
int lower;
int upper;
int step;
char c;
char line[1000];
后面一种形式会占用更多空间,但在为每个声明增加注释或是做后续修改时,会很方便。
变量也可以在声明中初始化,如果名字后面跟着一个等号和一个表达式,则表达式作为初始化式(initializer),如
char esc = '\\';
int i = 0;
int limit = MAXLINE + 1;
float eps = 1.0e-5;
若该变量不是自动变量,则初始化只会做一次,从概念上来说是在程序执行之前,并且初始化式必须为常量表达式。显式初始化的自动变量,在每次进入其所在的函数或块的时候被初始化。外部和静态变量默认被初始化为0。未显式初始化的自动变量的值是未定义的(即垃圾值)。
修饰符 const 可用于任意变量的声明,以指明它的值不会改变。对数组而言,const 修饰符说明其元素不能被改变。
const double e = 2.71828182845905;
const char msg[] = "warning: ";
const 声明也能与数组参数一同使用,以指示函数不会修改数组:
int strlen(const char[]);
如果试图修改一个const,其结果是由实现定义的。
2.5 算术操作符
二元算术操作符包括加减乘除 +, -, *, /,以及取模操作符 % 。整数相除会截断小数部分。表达式
x % y
得到 x 除以 y 的余数,而当 x 能被 y 整除时就为0。例如,闰年能被4整除而不能被100整除,除非可以被400整除。因此有:
if ((year % 4 == 0 && y % 100 != 0) || y % 400 == 0)
printf("%d is a leap year\n", year);
else
printf("%d is not a leap year\n", year);
% 操作符不能用于 float 和 double。对于负数,除法 / 的截断方向 和 取模%结果的符号是依赖于机器的,正如发生了上溢和下溢时,所采取的操作也是依赖于机器的。
二元操作符 + 和 - 有相同的优先级,都比 * / 和 % 低,而它们又低于一元操作符正号和符号 + -。算术操作符从左到右结合。
本章结尾的表 2-1总结了所有操作符的优先级和结合性。
2.6 关系和逻辑操作符
关系操作符有
> >= < <=
它们的优先级都相同。优先级仅次于它们的是相等性操作符:
== !=
关系操作符的优先级比算术操作符低,因此表达式如 i < lim - 1 就等于 i < (lim -1),符合大家的预期。
更有趣的是逻辑操作符 && 和 || 。由 && 或 || 连接的多个表达式是从左到右求值的,而且一旦结果的真假得到确定就会马上停止求值。大部分的C程序都依赖这两个特性。比如,下面这个循环来自第一章的读取输入的 getline 函数:
for (i=0; i<lim-1 && (c=getchar()) != '\n' && c != EOF; ++i)
s[i] = c;
在读入一个新字符之前,需要检查字符串 s 是否有空间,因此必须先测试 i < lim-1 。 而且如果测试失败,则不能继续读入下一个字符。
类似地,不能在调用getchar之前去检查 c 是否为EOF,因此调用getchar和对c赋值必须发生在检查字符c之前。
&& 的优先级比 || 高,而两者的优先级都低于关系和相等性操作符,因此表达式如
i<lim-1 && (c=getchar()) != '\n' && c != EOF
不需要多余的括号。但由于 != 的优先级高于赋值,因此下面的表达式需要括号
(c = getchar()) != '\n'
以保证先将值赋给c,再和'\n'进行比较。
根据定义,当关系为真时,关系和逻辑表达式的数值为1,当关系为假时值为0。
一元取反操作符 ! 将非0的操作数转换为0,将操作数0 转换为 1。操作符 ! 通常用在这样的结构中:
if (!valid)
以取代
if (valid == 0)
很难一概而论哪种形式更好。!valid 这种结构读起来更好(“如果不合法”),但更复杂的结构会很让人难理解。
练习2-2,重写上面的for循环,但不用 && 和 ||