目录:
一、操作符分类
- 算数操作符:
+ - * / %
- 移位操作符:
<< >>
- 位操作符:
& | ^
- 赋值操作符:
= += -= *= /= %= >>= <<= &= |= ^=
- 单目操作符·:
! - + & sizeof ~ -- ++ * (类型)
- 关系操作符:
> >= < <= != ==
- 逻辑操作符:
&& ||
- 条件操作符:
exp1 ? exp2 : exp3
- 下表引用操作符、函数调用操作符、结构体成员访问操作符:
[] () . ->
二、具体分析
2.1 算数操作符:+ - * / %
以上操作符分别为:加法、减法、乘法、除法、取模
取模操作符要求两个操作数都必须为整数,结果是整除后的余数。另外几个操作符可以用于整数和浮点数,但应注意计算中的类型转化。
- 特别注意:除法操作 / ,当两个操作数都为整数进行整数除法,除此之外都进行浮点数除法。
2.2 移位操作符:<< >>
<< 左移操作符;>>右移操作符
不管是左移操作符还是右移操作符都是相对于整数而言,而且必须是整数类型,整数类型在内存中按其32位补码存放。
左移操作符:
对于右移操作符分为,算术移位和逻辑移位,具体情况因编译器而定,常见的编译器大都采用算术移位。
算术右移位:
逻辑右移位:
移位操作符不改变变量的原始值。
2.3 位操作符:& | ^
- &:逻辑AND,同1为1,否则为0
- |:逻辑OR,同0为0,否则为1
^:逻辑XOR,相同为0,相异为1
位操作符同样只适用于两个整形的操作数
2.4 赋值操作符:= += -= *= /= %= >>= <<= &= |= ^=
当需要改变变量的值时,就需要赋值操作符,复合赋值符实在原有赋值符的基础上为了简化代码而产生的。
2.5 单目操作符·:! - + & sizeof ~ -- ++ * (类型)
以上操作符分别为:逻辑反、负值、正值、取地址、求操作数的类型长度、对一个数的二进制按位取反、前置/后置–、前置/后置++、解引用操作符、强制类型转换
单目操作符表示其只有一个操作数。
(1)!不同于 ~,C语言规定,0为假,非0为真,逻辑反可以改变其真假;而 ~ 是把操作数所对应二进制补码按位取反,0变1,1变0。
(2)&取地址操作符
用于得到一个操作数在内存中的存放地址。
#include <stdio.h>
int main()
{
int arr[10] = {0};
printf("%p\n", arr);//数组名相当于首元素的地址
printf("%p\n", arr+1);
printf("%p\n", &arr);//&数组名相当于取出的是整个数组的地址
printf("%p\n", &arr+1);
return 0;
}
很明显,数组名+1,地址偏移了一个元素的大小,&数组名+1,地址偏移了整个数组的大小。
- (3)通过sizeof求一个操作数的类型的大小,单位bit(sizeof不是一个函数,是一个操作符,其后的表达式不参与运算,是因为sizeof作为一个表达式,是在源文件编译期间处理,而其后如果有表达式,是需要在程序运行期间计算,所以表达式未计算)
#include <stdio.h>
int main()
{
int a = 1;
short b = 2;
printf("%d\n",sizeof(b=a+3));
printf("%d\n",a);
printf("%d\n",b);
return 0;
}
(4)前置++/– 先自加/减再使用;后置++/– 先使用后自加/减;指针的++/–与指针的类型有关(指针的类型决定了指针+/-1能走多远和解引用(*)能访问的内存大小)
(5)解引用操作符:通过一个指针,间接访问存放在该地址所对应内存上的变量。
(6)强制类型转换操作符:根据特殊需要,将精度高的操作数转化为精度小的操作数,会造成精度丢失。
2.6 关系操作符:> >= < <= != ==
用于比较两个操作数的大小,结果返回真假。
2.7 逻辑操作符:&& ||
&&:同真为真,否则为假;||:同假为假,否则为真
a && b && c(左图) ; a || b || c (右图)
一旦后面的表达式被跳过,将不再进行任何操作,因为前面的结果已经能够确定整个表达式的结果。
2.8 条件操作符:exp1 ? exp2 : exp3
如果表达式1的结果为真,则执行表达式2,为假执行表达式3。
if (a>b)
{
max = a;
}
else
{
max = b;
}
//上面的代码等价于底下的代码
max = (a>b) ? a : b;
同样如果表达式1为真,表达式3将不再执行;为假表达式1不再执行。求值顺序的改变会对关联变量的赋值产生影响,从而影响程序运行结果,需注意!
2.9 下表引用操作符、函数调用操作符、结构体成员访问操作符:[] () . ->
- 下表引用操作符:
[]
下标引用操作符是一个双目操作符,它需要数组名+下标共同完成索引
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
//以下方式都可以成功调用数组中第五个元素
printf("%d\n", arr[4]);
printf("%d\n", *(arr+4));
printf("%d\n", *(4+arr);
printf("%d\n", 4[arr]);
return 0;
}
函数调用操作符接受的操作数决定于函数的参数,参数为void时,只接受一个操作数,即函数名,参数为多个时,调用的操作数也相应增加。
结构体成员访问操作符:. ->
. 是相对于结构体变量而言,-> 是相当于结构体指针而言
#include <stdio.h>
#include <string.h>
struct Stu
{
char name[20];
int age;
float score;
};
int main()
{
struct Stu s;
struct Stu* ps = &s;
strcpy(s.name, "zhangsan");
s.age = 20;
s.score = 55.0f;
printf("%s %d %f\n", s.name, s.age, s.score);//通过结构体访问其成员
printf("%s %d %f\n", ps->name, ps->age, ps->score);//通过结构体指针访问其成员
return 0;
}
三、操作符属性及由其带来的问题
操作符的属性对复杂表达式的求值有三方面的影响:
(1)操作符的优先级
(2)操作符的结合性
(3)是否控制求值顺序
操作符 | 描述 | 结合性 | 是否控制求职顺序 |
---|---|---|---|
( ) | 聚组 | 不适用 | 否 |
( ) | 函数调用 | L-R | 否 |
[ ] | 下表引用 | L-R | 否 |
. | 访问结构成员 | L-R | 否 |
-> | 访问结构指针成员 | L-R | 否 |
++ | 后置自加 | L-R | 否 |
– | 后置自减 | L-R | 否 |
! | 逻辑反 | R-L | 否 |
~ | 按位取反 | R-L | 否 |
+ | 单目,表示正值 | R-L | 否 |
- | 单目,表示负值 | R-L | 否 |
++ | 前置自加 | R-L | 否 |
– | 前置自减 | R-L | 否 |
* | 间接访问 | R-L | 否 |
& | 取地址 | R-L | 否 |
sizeof | 取其长度,以字节表示 | R-L | 否 |
(类型) | 类型转换 | R-L | 否 |
* | 乘法 | L-R | 否 |
/ | 除法 | L-R | 否 |
% | 整数取余 | L-R | 否 |
+ | 加法 | L-R | 否 |
- | 减法 | L-R | 否 |
<< | 左移位 | L-R | 否 |
>> | 右移位 | L-R | 否 |
> | 大于 | L-R | 否 |
>= | 大于等于 | L-R | 否 |
< | 小于 | L-R | 否 |
<= | 小于等于 | L-R | 否 |
== | 等于 | L-R | 否 |
!= | 不等于 | L-R | 否 |
& | 位与 | L-R | 否 |
^ | 位异或 | L-R | 否 |
| | 位或 | L-R | 否 |
&& | 逻辑与 | L-R | 是 |
|| | 逻辑或 | L-R | 是 |
?: | 条件操作符 | 不适用 | 是 |
= | 赋值 | R-L | 否 |
, | 逗号 | L-R | 是 |
上表是按操作符的优先级依次排列,对于一个复杂表达式,两个相邻操作符的执行顺序由它们的优先级决定,如果优先级相同,则再考虑结合性,而控制求值顺序的操作符仅有:逗号、&&、||、和?:四个,只要满足上面这三点,编译器可以对表达式的计算顺序任意决定,这便会产生问题:
a * b + c * d + e * f
每个乘法只需保证在其相邻的加法执行前执行即可,则这个表达式的计算方式将不唯一,虽然在此式中不同的计算顺序对表达式的结果没有产生影响,但是如果变量的值在求解的过程中被重新赋值,求解路径不唯一结果将不唯一,如下:
#include <stdio.h>
int main()
{
int a = 1;
printf("%d\n",(++a)+(++a)+(++a));
return 0;
}
这段代码将会在不同的编译器下产生不同的结果:
所以要避免这样代码的产生,以提高其跨平台性。