C 语言提供了非常丰富的操作符,根据不同的语义选取不同的操作符,在程序中使用。根据操作符功能或使用方式对它们进行如下分类。
算术操作符
C 提供了以下几种算术操作符:
-*/% 、-、*、/ 既适用于浮点类型又适用于整数类型。/ 操作符的两个操作数都是整数时,执行整除运算,其他情况下执行浮点数除法。% 操作符为取模操作符,接受两个整型操作数,把左操作数除以右操作数,返回的值是余数而不是商。移位操作符
C 提供了以下移位操作符:
<<>><< 左移位操作符。把一个值的位向左移动,值最左边的几位被丢弃,右边多出来的几个空位由 0 补齐。>> 右移位操作符。把一个值的位向右移动,左边移入新位时,有两种方案可选。一种是逻辑移位,左边移入的位用 0 填充;另一种是算术移位,左边移入的位由原先该值的符号位决定,符号位为 1 则移入的位均为 1,符号位为 0,则移入的均为 0,这样能保证原数的正负形式不变。01101101//移位前01101101//左移后三位01101101000//移位后补零01101000//最终结果
当使用移位操作符时,左操作数的值将移动由右操作数指定的位数。两个操作数都必须是整型类型。
位操作符
C 提供了以下位操作符:
&|^& 按位与操作符,执行 AND 逻辑操作。如果两个位都是 1,结果为 1,否则结果为 0。| 按位或操作符,执行 OR 逻辑操作。如果两个位都是 0,结果为 0,否则结果为 1。^ 按位异或操作符,执行 XOR 逻辑操作。如果两个位不同,结果为 1,如果相同,结果为 0。
位操作符要求操作数必须是整数的二进制数,它们对操作数对应的位进行指定的操作,每次对左右操作数的各一位进行操作。举例说明,假定变量 a 的二进制值为 00101110,变量 b 的二进制值为 01011011。a & b 的结果是 00001010,a|b 的结果是 01111111,a ^b的结果是 01110101。
赋值操作符
C 提供了 = 作为赋值操作符。赋值是表达式的一种,而不是某种类型的语句。所以,只要是允许出现表达式的地方,都允许进行赋值。如下所示:
a=x=y 3;
上述表达式语句中 = 的操作数是变量 x 和 表达式 y 3 的值。= 把右操作数的值存储于左操作数的指定的位置。而上面的语句根据赋值操作符的结合性是从右到左,所以这个表达式相当于:
a=(x=y 3);
与下面的语句组合完全相同:
x=y 3;a=x;
但 a 和 x 不是被赋予了相同的值,这要看 x 和 a 的数据类型。如果 x 是一个字符型变量,a 是一个整型变量,那么 y 3 的值就会被截去一段,以便容纳于字符类型的变量中,那么 a 所赋的值就是这个被截短后并在被提升为整型的值。
除了 = 赋值操作符外,还有= 与其他操作符合在一起的复合赋值符,如下所示:
=-=*=/=%=<<=>>=&=^=|=
这些操作符使用基本都相似,下面给出 = 操作符的用法:
a =expres
功能相当于下面的表达式:
a=a (expres)
唯一不同之处是 = 操作符的左操作数只求值一次。这里的括号是确保表达式在执行加法运算前已被完整求值,即使内部包含有优先级低于加法运算的操作符。
单目操作符
C 提供了以下单目运算符:
单目操作符为只接受一个操作数的操作符。
下面给出 操作符的例子。
inta,b,c,d;…a=b=10;//a和b得到值10c= a;//a增加至11,c得到的值为11d=b ;//b增加至11,但d得到的值仍为10
前缀和后缀形式的增值操作符是复制了一份变量值的拷贝,用于周围表达式的值正是这份拷贝。前缀操作符在进行复制之前增加变量的值,后缀操作符在进行复制之后才增加变量的值。这些操作符的结果不是被它们所修改的变量,而是变量值的拷贝,因此不能像 a=10 这样使用操作符,因为 a 的结果是 a 值的拷贝,并不是变量本身,因此无法向一个值进行赋值。
关系操作符
C 提供了以下关系操作符,用于测试操作数之间的各种关系。
>>=<<=!===> 操作符。左值大于右值,为真;否则为假。>= 操作符。左值大于等于右值,为真;否则为假。< 操作符。左值小于右值,为真;否则为假。<= 操作符。左值小于等于右值,为真;否则为假。!= 操作符。左值与右值不相等,为真;否则为假。== 操作符。左值与右值相等,为真;否则为假。
关系操作符的结果是一个整形值,而不是布尔值。如果两端的操作数符合操作符指定的关系,表达式的结果是 1,被认为是真,如果不符合,表达式的结果是 0,被认为是假。
!= 与 == 在 if 语句中可以有一些简写方法。
if(e!=0)…等价于if(e)…if(e==0)…等价于if(!e)…
C99之前还未提供布尔类型,使用的是整数代替,其规则是零是假,任何非零值皆为真。
逻辑操作符
C 提供了以下逻辑操作符,用于表达式求值,测试值是真还是假。
&&||&& 操作符。逻辑与操作符,两边表达式同时为真,结果为真;否则为假。当最前面的表达式为假,后面不用执行,结果为假。|| 操作符。逻辑或操作符,两边表达式只要有一个为真,结果为真;否则为假。当最前面的表达式为真,后面不用执行,结果为真。
位操作符与逻辑操作符较为相似,但不要混淆,它们不可互换,逻辑操作符具有短路性质,根据左操作数就可决定,而位操作符两边都需要进行求值;逻辑操作符用于测试零值和非零值,而位操作符用于比较它们的操作数中对应的位。
条件操作符
C 提供了接收三个表达式的条件操作符:
?:
条件操作符也叫三元操作符,使用方式如下所示。
a>5?b-2:c/3
当 a>5 为真时,整个表达式的值就是 b-2;如果为假,整个表达式的值就是 c/3。
逗号操作符
C 提供逗号操作符的用法如下所示。
expre1,expre2,…,expreN
通过逗号,将多个表达式分割开来,自左向右逐个求值,整个逗号表达式的值就是最后那个表达式的值。如 if (b 1, c/2, d > 0) ,只需判断 d 是否大于 0 即可。
逗号和分号不易区分,避免过度使用。
下标引用
C 提供了下标引用,用于访问数组下标中对应的值,下标引用符号是一对方括号 []。
array[index]array 要访问的数组名称。index 要访问的数组的下标索引。
下标引用并不仅限于数组名。C 的下标值是从 0 开始,并且不会对下标值进行有效性检查。除了优先级不同之处,下标引用操作和间接访问表达式是等价的。
array[index]*(array (index))函数调用
C 提供了函数调用操作符,用于接受一个或多个操作数。函数调用操作符用圆括号 ()。
intnum(inta,intb){returna b;}intmain(){inta=10,b=20;intc=num(a,b);//调用num函数使用的()就是函数调用操作符return0;}
num(a, b) 函数调用时,函数名为 num,而函数调用操作符里是传递给函数的参数。
结构成员
C 提供了 . 和 -> 操作符,用于访问结构的成员。
typedefstruct{intdata;//数据域structBiTNode*lchild,*rchild;//左右孩子指针}BiTNode;
当声明一个结构体变量时,可以使用 . 和 ->。
BiTNodebi;bi.data=1;printf(“%d\n”,bi.data);
bi 是结构变量,可以通过 bi.data 访问 bi 中名为 data 的成员。
BiTNode*pi=malloc(sizeof(BiTNode));pi->data=1;printf(“%d”,pi->data);
pi 是一个指向 BiTNode 结构的指针,使用 -> 操作符访问它的成员。
左值和右值
C 语言中的左值是出现在赋值符号左边的值,右值是出现在赋值符号右边的值。如下所示:
x=y 5;
x 是左值,标识了一个可以存储结果值的地点。y 5 是右值,指定了一个值。但是左右值不能互换,因为 y 5并未标识一个特定的位置。
类型转换
在C 语言的语句和表达式中,应使用类型相同的变量和常量。当使用混合类型时,C 语言会采用一定规则转换为某种共同的类型,但是具有一定的危险性。
inta=3.14;//将double类型的3.14隐式转换为int类型doubleb=314;//将int类型的314隐式转换为double类型
由上述可知,在赋值表达式语句中,计算的最终经过会被转换成被赋值变量的类型,这个过程可能导致类型升级或降级。
所谓升级 promotion,指的是把一种类型转换成更高级别的类型;所谓降级 demotion,指的是把一种类型转换成更低级别的类型。
整型提升 intergral promotion 是隐式转换中类型升级的一种,是表达式中的字符型和短整型操作数在使用之前被转换为普通整型,如下所示。
chara,b,c;···a=b c;
b 和 c 的值被提升为普通整型,然后再执行加法运算。加法运算的结果将被截短,然后再存储于 a 中。
不同类型的操作数进行算术运算时,会转换为同一种类型进行运算,否则操作无法操作。寻常算术转换 usual arithmetic conversion 的层次结构如下所示。
longdoubledoublefloatunsignedlongintlongintunsignedintint
如果操作数的类型在上面列表中排名较低,那首先将转换为另外一个操作数的类型然后执行操作。但算术转换要合理,否则会存在一些潜在的问题,如下所示。
inta=5000;intb=25;longc=a*b;
表达式 a*b 以整型进行计算,获取的值在 32 位整数的机器上没有问题,但在 16 位整数的机器上,运算会产生溢出,c 会被初始化为错误的值。解决方案是在执行前把其中一个或两个操作数转换为长整型。
longc=(long)a*b;
整型值转换为 float 型时,可能会损失精度。float 型值转换为整型时,小数部分被舍弃,并不进行四舍五入。如果浮点数的值过于庞大,无法容纳于整型值时,其结果将是未定义的。
操作符的属性
复杂表达式的求值顺序由 3 个因素决定:
操作符的优先级。决定多个操作符的顺序,取决于操作符的优先级,当优先级相同时,执行顺序由结合性决定。操作符的结合性。结合性是一串操作符是从左向右依次执行还是从右向左逐个执行。操作符是否控制执行的顺序。可以对整个表达式求值顺序施加控制,根据不同条件施加不同的求值过程。
下表给出了操作符的所有属性。
当表达式中的操作符超过一个时,其执行顺序由操作符的优先级决定。如果优先级相同,执行顺序由结合性决定,除此之外,编译器可以自由决定使用任何顺序对表达式进行求值,只要不违背逗号、&&、|| 和 ?: 操作符所施加的限制。