2. 其它运算符

2.1. 复合赋值运算符

复合赋值运算符(Compound Assignment Operator)包括 *= /= %= += -= <<= >>= &= ^= |=,一边做运算一边赋值。例如a += 1相当于a = a + 1。但有一点细微的差别,前者对表达式a只求值一次,而后者求值两次,如果a是一个复杂的表达式,求值一次和求值两次的效率是不同的,例如a[i+j] += 1a[i+j] = a[i+j] + 1。那么仅仅是效率上的差别吗?对于没有 Side Effect 的表达式,求值一次和求值两次的结果是一样的,但对于有 Side Effect 的表达式则不一定,例如a[foo()] += 1a[foo()] = a[foo()] + 1,如果 foo()函数调用有 Side Effect,比如会打印一条消息,那么前者只打印一次,而后者打印两次。

第 3 节 “for 语句”讲自增、自减运算符时说 ++i 相当于 i = i + 1 ,其实更准确地说应该是等价于 i += 1 ,表达式 i 只求值一次,而 --i 等价于 i -= 1

2.2. 条件运算符

条件运算符(Conditional Operator)是 C 语言中唯一一个三目运算符(Ternary Operator),带三个操作数,它的形式是 表达式 1 ? 表达式 2 : 表达式 3 ,这个运算符所组成的整个表达式的值等于表达式 2 或表达式 3 的值,取决于表达式 1 的值是否为真,可以把它想像成这样的函数:

if (表达式 1)
	return 表达式 2;
else
	return 表达式 3;

表达式 1 相当于 if 语句的控制表达式,因此它的值必须是标量类型,而表达式 2 和 3 相当于同一个函数在不同情况下的返回值,因此它们的类型要求一致,也要做 Usual Arithmetic Conversion。

下面举个例子,定义一个函数求两个参数中较大的一个。

int max(int a, int b)
{
	return (a > b) ? a : b;
}

2.3. 逗号运算符

逗号运算符(Comma Operator)也是一种双目运算符,它的形式是 表达式 1, 表达式 2 ,两个表达式不要求类型一致,左边的表达式 1 先求值,求完了直接把值丢掉,再求右边表达式 2 的值作为整个表达式的值。逗号运算符是左结合的,类似于+ - * /运算符,根据组合规则可以写出 表达式 1, 表达式 2, 表达式 3, ..., 表达式 n 这种形式, 表达式 1, 表达式 2 可以看作一个子表达式,先求表达式 1 的值,然后求表达式 2 的值作为这个子表达式的值,然后这个值再和表达式 3 组成一个更大的表达式,求表达式 3 的值作为这个更大的表达式的值,依此类推,整个计算过程就是从左到右依次求值,最后一个表达式的值成为整个表达式的值。

注意,函数调用时各实参之间也是用逗号隔开,这种逗号是分隔符而不是逗号运算符。但可以这样使用逗号运算符:

f(a, (t=3, t+2), c)

传给函数 f 的参数有三个,其中第二个参数的值是表达式 t+2 的值。

2.4. sizeof 运算符与 typedef 类型声明

sizeof 是一个很特殊的运算符,它有两种形式:“sizeof 表达式”和“sizeof(类型名)”。这个运算符很特殊,“sizeof 表达式”中的子表达式并不求值,而只是根据类型转换规则求得子表达式的类型,然后把这种类型所占的字节数作为整个表达式的值。有些人喜欢写成“sizeof(表达式)”的形式也可以,这里的括号和return(1); 的括号一样,不起任何作用。但另外一种形式“sizeof(类型名)”的括号则是必须写的,整个表达式的值也是这种类型所占的字节数。

比如用 sizeof 运算符求一个数组的长度:

int a[12];
printf("%d\n", sizeof a/sizeof a[0]);

在上面这个例子中,由于 sizeof 表达式 中的子表达式不需要求值,所以不需要到运行时才计算,事实上在编译时就知道 sizeof a 的值是 48, sizeof a[0] 的值是 4,所以在编译时就已经把 sizeof a/sizeof a[0] 替换成常量 12 了,这是一个常量表达式。

sizeof 运算符的结果是size_t 类型的,这个类型定义在stddef.h 头文件中,不过你的代码中只要不出现size_t 这个类型名就不用包含这个头文件,比如像上面的例子就不用包含这个头文件。C 标准规定size_t 是一种无符号整型,编译器可以用typedef 做一个类型声明:

typedef unsigned long size_t;

那么 size_t 就代表 unsigned long 型。不同平台的编译器可能会根据自己平台的具体情况定义 size_t 所代表的类型,比如有的平台定义为 unsigned long 型,有的平台定义为 unsigned long long 型,C 标准规定 size_t 这个名字就是为了隐藏这些细节,使代码具有可移植性。所以注意不要把 size_t 类型和它所代表的真实类型混用,例如:

unsigned long x;
size_t y;
x = y;

如果在一种 ILP32 平台上定义 size_t 代表 unsigned long long 型,这段代码把 y 赋给 x 时就把高位截掉了,结果可能是错的。

typedef 这个关键字用于给某种类型起个新名字,比如上面的typedef 声明可以这么看:去掉typedef 就成了一个变量声明unsigned long size_t; size_t 是一个变量名,类型是unsigned long ,那么加上typedef 之后,size_t 就是一个类型名,就代表unsigned long 类型。再举个例子:

typedef char array_t[10];
array_t a;

这相当于声明 char a[10]; 。类型名也遵循标识符的命名规则,并且通常加个 _t 后缀表示 Type。