一、操作符
包括:算术操作符,移位操作符,位操作符,赋值操作符,单目操作符,关系操作符,逻辑操作符,条件操作符,下标引用,函数调用,结构体成员调用。
1、算术操作符
加、减、乘、除、取模(%)
取模的两个操作数只能是整型值。
2、移位操作符
左移位:<< 最左边几位丢弃,最右边几位补0;
右移位:>> (1)逻辑移位:最右边几位丢弃,左边补0;
(2)算术移位:最右边几位丢弃,左边符号位为0,则全移入0,为1则全移入1。
标准规定无符号数都执行逻辑移位,而有符号数执行的是逻辑移位还是算术移位由编译器决定,若移位的位数超过操作数的位数,则具体情况由编译器决定。
所以出现有符号数移位操作的程序是不可移植的。
3、位操作符
& | ^ (与,或,异或)
通常用于位操作,修改变量指定位的值。
举例:
value的第bit_number位置1:
value |= 1 << bit_number;
value的第bit_number位置0:
value &= ~ (1 << bit_number);
测试指定位是否为1,为1则表达式非0:
value &= 1 << bit_number;
4、赋值操作符
赋值是表达式的一种,而不是某种类型的语句,所以只要允许出现表达式的地方都可以进行赋值。
举例:
a = x = y + 3;
a和x被赋的值并不是同一个值
若x的长度不够,则x被赋予的y+3值会被截短,再存储于x中,之后这个被截短的值再被赋予到a中。
复合赋值符:+=,-=,|=等等,多使用可以使代码更简洁。
5、单目操作符
!; ++; -- ; + ;-; ~ ;& ;* ;sizeof ; (类型)
- sizeof():判断操作数的类型长度,以字节为单位。
sizeof (int):必须加括号;
sizeof x:可以不加括号;
举例:
16位机器中:int arry[10];
则sizeof(arry)=20
sizeof(arry[0])=2
sizeof(arry)/sizeof(arry[0])=10,即数组中元素的个数。
sizeof(a = b + 3):sizeof可以判断表达式的长度,此时不需要求表达式的值,所以a并没有被赋值;表达式返回a的大小。
(类型):强制类型转换(cast),具有很高的优先级,所以注意:把强制类型转换放在表达式的前面只会改变第一个项目的类型,要操作整个表达式的话,就要加括号。
float(a)
:获得a对应的浮点数值。增值++和减值--操作符。
前缀操作符在变量被使用之前改变变量的值;
后缀操作符在变量被使用之后改变变量的值。
举例:
c = ++a; d=a++;
其中,c得到a增加之前的值,d得到a增加之后的值。
前缀和后缀形式的增值操作都复制了一份变量值的拷贝,用于周围表达式的值是这份拷贝来的值(赋值表达式);
前缀形式,在变量值增加之后复制,后缀形式,在变量值增加之前复制。
因此这些操作符的结果不是被他们修改的变量,而是变量值的拷贝。
++a = 10;
这条语句时错误的,++a的结果是变量值的拷贝,不是变量a本身,因此无法向一个值进行赋值。
6、关系操作符
>;>=;<;<=;!=;==
关系操作符的结果是一个整型值,而不是布尔值,可以将结果赋值给整型变量。(C语言中没有布尔类型,所以用整数来代替,非0位真,0为假)
7、逻辑操作符
逻辑与&&,逻辑或||,都会控制表达式的顺序执行:
从左到右依次执行,&&若左操作数为假,则后续右操作数不再求值,整个表达式为假,||逻辑或同理。这个行为被称为“短路求值”(short-circuited evaluation)。
举例:
if(a<b && c>d)
if(a<b & c>d)
和
if(a && b)
if(a & b)
第一组语句的结果一样,因为关系操作符的结果只能是0或1,而第二组语句结果不一样,因为若a,b非0,则第一个一定为真,而a,b按位与的值并不一定非0,所以不一定为真。
8、条件操作符
expression1 ? expression2 : expression3
会控制子表达式的求值顺序,expression2和expression3只会执行其中一个。
b = a > 5 ? 3 : -20;
a > 5则b = 3,否则b = -20。
条件操作符的作用有时类似于if,else,且比它更简洁。
9、逗号操作符
expression1,expression2,expression3,......,expressionN
将多个表达式用逗号分开,并从左到右依次执行,整个表达式的值为最后那个表达式的值。
while(a = get_value(), count_value( a ), a>0)
{
expression;
}
因为依次执行,所以用在循环语句的测试表达式中时,获得下一个测试值语句只出现一次,修改时只需要在一个地方修改,方便程序的维护。
二、表达式求值
- 表达式的求值顺序由所包含操作符的优先级和结合性决定;
- 求值过程中的类型转换。
1、隐式类型转换(implicit conversion)
-
发生的情况:
- 算术表达式或逻辑表达式的操作数类型不相同时;(执行常用算术转换,即usual arithmetic conversion)
- 操作符两边的变量类型不相同时;
- 函数调用时,实参与形参不匹配时;
- return语句中表达式类型和函数返回值类型不匹配时。
隐式转换规则
long double
double
float
unsigned long int
long int
unsigned int
int
排名较低的操作数首先转换为另一个操作数的类型,即低精度数像高精度数转换。
但是,在32位机器上,int类型和long字长相同,这时unsigned int的精度就比long精度高。整型运算符的精度至少是缺省整型类型,则运算过程中,字符型和短整型在使用之前被转换成普通整型,这种转换过程被称为整型提升(integral promotion)。
提升精度往往无害,但降低精度可能会导致问题,低精度类型可能不够大,不能容纳高精度的完整数据。
(1)算术转换
举例:
char a,b,c;
statement;
a = b + c;
首先,b和c的值被提升为整型,然后,执行加法运算,最后,将结果截短后赋值并存储于a中。
当然,也可以使用强制类型转换执行显示转换(explicit conversion):(int)a = b + c;
(2)显示转换
举例:
int a = 5000;
int b = 25;
long c = a * b;
在32位机器上,int和long int都是32位,这段代码运行起来没有问题;但在16位机器上,int是16位,long是32位,a*b的值应该是int型,但由于数据类型不够大(max = 2^16 - 1 = 65535),所以发生溢出,c会被赋一个无意义的值,因此需要进行强制类型转换:
long c = (long)a*b;
注意:
long c = (long)(a*b);
是错误的,因为溢出在强制转换之前就已经发生了,所以这样做不会有任何改变。-
数据溢出:
- 有符号数的数据溢出是未定义的;
- 无符号数的数据溢出:溢出后的数以2^(8*sizeof(type))作模运算。比如用unsigned char型变量存储258,其实存进去的是258-2^8=2。
(3)操作符两边表达式的转换
举例:
unsigned int a = 6;
int b = -20;
int c = (a + b) > 6 ? 1 : 2;
在加法运算中a和b类型不一致,会发生隐式转换,将int型转换为unsigned int型,b=-20转换为无符号数会变成一个很大的整数,因为无符号负数转换为的正数是用负数的补码所表示的,正数的源码,反码,和补码相同。所以,程序输出1,而不是2。
举例:
if(strlen(arry) < 10)
if(strlen(arry) - 10 < 0)
这两条语句不等价,因为strlen函数返回值是unsigned int型,两个无符号数运算所得的结果仍为无符号数,而无符号数肯定大于0。所以,要尽量避免使用第二个表达式。
2、操作符的优先级
表达式的运算规则:
两个相邻操作符的执行顺序由他们的优先级决定,若优先级相同,则由他们的结合性决定。
结合性:当多个相同优先级的运算符出现在表达式中时,先执行左边的叫具有左结合性,先执行右边的叫具有右结合性。
- 举例:
a*b + c*d + e*f
相邻的加法和乘法运算符中,乘法运算符先执行;两个加法运算,根据加法的左结合性,是左边的加法运算先执行。但对于哪个乘法运算先执行,以及是否在所有乘法执行完后再执行加法运算,这些都由编译器决定,所以表达式就会有多种执行顺序。
c + --c
相邻的+和--操作符是--先执行,但对于表达式c和--c并不知道哪个先执行,又因为--c具有副作用,所以这两个表达式的执行顺序会对结果有影响。
- 编译器只要不违背优先级和结合性规则,就可以任意决定复杂表达式的取值顺序。