操作符:
- 算术操作符 + - * / %
%操作符的两个操作数必须为整数
- 移位操作符
<< 左移操作符
>> 右移操作符:逻辑移位(左边⽤0填充)、算术移位(左边⽤原该值的符号位填充)
注意:对于移位运算符,不要移动负数位,这个是标准未定义的
- 位操作符
& 按位与 | 按位或 ^ 按位异或
注:他们的操作数必须是整数
- 赋值操作符 = :复合赋值符 += -+ *= >>= <<= &= |=
- 单⽬操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型⻓度(以字节为单位)
~ 对⼀个数的⼆进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引⽤操作符)
(类型) 强制类型转换
- 关系操作符 > >= < <= != ==
- 逻辑操作符 && 逻辑与 | | 逻辑或
&&的特性:若&&前面已经为0(假),就没有必要进行后面的运算
| | 的特性:若||前面遇到0,则继续;若遇到非0,就停下
- 条件操作符 exp1 ? exp2 : exp3
- 逗号表达式 exp1, exp2, exp3, …expN
逗号表达式,就是⽤逗号隔开的多个表达式。
逗号表达式,从左向右依次执⾏。整个表达式的结果是最后⼀个表达式的结果。
- 下标引⽤[ ]、函数调⽤( )和结构成员./->
表达式求值
隐式类型转换
C的整型算术运算总是⾄少以缺省整型类型的精度来进⾏的。
整型提升:表达式中的字符和短整型操作数在使⽤之前被转换为普通整型。
char a,b,c; a = b + c;
b和c的值被提升为普通整型,然后再执⾏加法运算。加法运算完成之后,结果将被截断,然后再存储于a中。
char a,b; a = (~a ^ b << 1)>> 1;
由于存在求补和左移操作,所以8位的精度是不够的。标准要求进⾏完整的整型求值,所以对于这类表达式的结果,不会存在歧义.
算术转换
如果某个操作符的各个操作数属于不同的类型,那么除⾮其中⼀个操作数的转换为另⼀个操作数的类型,否则操作就⽆法进⾏。
寻常算术转换:
- long double
- double
- float
- unsigned long int
- long int
- unsigned int
- int
如果某个操作数的类型在上⾯这个列表中排名较低,那么⾸先要转换为另外⼀个操作数的类型后执⾏运算。
float f = 3.14; int num = f;//隐式转换,会有精度丢失
上面的例子可以看出:算术转换要合理,要不然会有⼀些潜在的问题。
操作符的属性
复杂表达式的求值:
- 操作符的优先级
- 操作符的结合性
- 是否控制求值顺序
两个相邻的操作符先执⾏哪个?取决于他们的优先级。如果优先级相同,取决于他们的结合性。
来看看一些问题表达式:
//表达式一 a*b + c*d + e*f;
表达式一:由于 * 比 + 的优先级高,只能保证第一个 * 比第一个 + 计算的早,但优先级并不能保证第三个 * 比第一个 + 执行的早。
//表达式二 c + --c;
表达式二:操作符的优先级只能决定⾃减 -- 的运算在 + 的运算的前⾯,但是我们并没有办法得知 + 操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,有歧义。
//表达式三
int main()
{
int i = 10;
i = i-- - --i * ( i = -3 ) * i++ + ++i;
printf("i = %d\n", i); return 0;
}
表达式三:是一个非法表达式,在不同编译器中有不同的结果。
//表达式四
int fun()
{
static int count = 1;
return ++count;
}
int main()
{
int answer;
answer = fun() - fun() * fun();
printf("%d\n", answer);//输出多少?
return 0;
}
总结:不要写出依赖求值顺序的表达式。这样的表达式是不可移植的。一定要避免。