复合赋值运算符(Compound Assignment Operator)包括*=
/=
%=
+=
-=
<<=
>>=
&=
^=
|=
,在赋值的同时做一个运算。例如a += 1
相当于a = a + 1
。但有一点细微的区别,前者对表达式a
只求值一次,而后者求值两次,如果a
是一个复杂的表达式,求值一次和求值两次的效率是不同的,例如a[i+j] += 1
和a[i+j] = a[i+j] + 1
。仅仅是效率上的差别吗?对于没有Side Effect的表达式,求值一次和求值两次的结果是一样的,但对于有Side Effect的表达式则不一定,例如a[foo()] += 1
和a[foo()] = a[foo()] + 1
,如果foo()
函数调用有Side Effect,比如会打印一条消息,那么前者只打印一次,而后者打印两次。
在“for语句”一节讲自增、自减运算符时说++i
相当于i = i + 1
,其实更准确地说应该是等价于i += 1
,而--i
等价于i -= 1
。
条件运算符(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; }
逗号运算符(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
的值。
sizeof
是一个很特殊的运算符,它有两种形式: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
这个类型名就不用包含这个头文件,比如像上面的例子就不用包含这个头文件。size_t
这个类型是我们讲过的整型中的某一种,编译器可能会用typedef
做一个类型声明:
typedef unsigned long size_t;
那么size_t
类型就是unsigned long
类型。之所以不直接规定sizeof
的值是unsigned long
型的,而要规定一个size_t
类型,是为了允许不同的编译器根据自己平台的情况定义size_t
为不同的类型,这样使用size_t
类型的代码就具有很好的可移植性,但不管编译器怎么实现,C标准明确规定sizeof
的值是无符号整型的。
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。