目录
一、算数操作符
二、移位操作符
1.左移操作符
2.右移操作符
(1) 逻辑右移
(2) 算术右移
(3)小总结
三、位操作符
四、赋值操作符
五、单目操作符
六、关系操作符
七、逻辑操作符
八、 条件操作符
九、逗号表达式
十、下标引用、函数调用和结构成员
1. []下标引用操作符
2. ( )函数调用操作符
3. 访问一个结构的成员
十一、表达式求值
1.隐式类型转换
2.算术转换
3.操作符的属性
十二、操作符优先级
一、算数操作符
+-* / %
- 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数
- 对于 / 操作符如果两个操作数都为整数,执行整数除法。但是,只要有浮点数参与运算就是浮点数除法
- % 要求两个操作数必须为整数,得到的是余数
二、移位操作符
>>右移操作符
<<左移操作符
注意:移位操作符的操作数只能是整数
1.左移操作符
规则巧记:左边抛弃,右边补0
这里假设存的是 int n =15;的二进制 进行左移 n<<1;如果n在没有被左移赋值的情况下,n自身的值是不会发生变化。(这里与自增自减不一样)
在深层次看一下,一个数 n<<1, 发现相当于该数 n * 2^1。n << 2; 等于 n*2^2
2.右移操作符
(1) 逻辑右移
移位规则:右边舍弃,左边补0
假设内存 存放的是 -1 补码的二进制
(2) 算术右移
移位规则:右边舍弃,左边用原值的符号位进行填充
(3)小总结
如果 算术右移与 逻辑右移 总是分不清。巧记:(算术右移,可以想象成 算数,既然算数肯定会有正负,进而想到 左边填充的是原符号位)
注意:对于位操作符,不存在移动负数位,C语言标准并未规定
int num = 10;
num>> -1; //error(不要多次一举)
num << 1; // ok 右移 -1 这不相当于 左移 1 嘛。
三、位操作符
&//按位与 巧记:有0则0,其中 一个数 a&1 可求 该数的每一个二进制位
|//按位或 巧记:有1则1
^//按位异或 巧记:相同为0,相异为1
注:操作数必须是整数
【例】1
#includeint main() {int a = 1;//0001int b = 2;//0010/** a& b;* 0001* 0010* 0000* * a|b* 0001* 0010* 0011* * a^b* 0001* 0010* 1100*/return 0;}
接下来看一道面试题
【例】2不能创建临时变量(第三变量),实现两个数的交换
解法一:
两个数进行来回加减来进行两个数的交换,通过调试的监视的窗口我们可以看到两个数的交换
解法二:
#includeint main(){int a = 10;int b = 20;a = a ^ b;b = a ^ b;a = a ^ b;printf("%d %d",a,b);return 0;}
从打印结果可以看到a 与 b的值进行了交换
【解析】^按位异或 相同为0,相异为1(可以理解为 相同假,相异为真)
前面,提到 可以用a&1 该数的每一个二进制位,看下方例题
【例】编写代码,求一个整数存储在内存中的二进制中1的个数
#includeint main(){int n = 10;int count = 0;//0000 1010//0000 0001int i = 0;for (i = 0; i >1;}printf("%d",count);return 0;}
这里用到了 >> 和 ^
解法二:
#includeint main(){int n = 10;int count = 0;while (n) {if (n%2 == 1){count++;}n /= 2;}return 0;}
这里的思路是 因为计算机存储是二进制 0 和1 ,%2取余判断是否为1,/2进行下一位
解法三:
#includeint main(){int n = -1;int i = 0;int count = 0;while (n){count++;n = n & (n - 1);//1111 1111 1111 1111 1111 1111 1111 1111//1111 1111 1111 1111 1111 1111 1111 1110//1111 1111 1111 1111 1111 1111 1111 1110}printf("%d ", count);return 0;}
这里的优化 是借助 两个数差1 进行按位与,一个一个位找1
四、赋值操作符
= 这是赋值符,不是等于!!!
赋值操作符 给一个变量进行赋值 注意赋值操作符的优先级比较低(包括复合赋值符)
int age = 18;age = 20;//对变量进行赋值
赋值操作符支持连续使用;
int a = 0;int b = 10;int c = 1;a = b = c+1;//这里是连续赋值
虽然可以连续赋值,但是代码的可读会下降
b = c+1;a = b;//这样子是不是看着更用以理解
代码的可读性也是很重要的
接下来看复合赋值符都有哪些
+= 、-=、*= 、/= 、%= 、>>= 、<<= 、&= 、 |= 、^=
int a = 10;a = a+10;//可以写成这样a += 10;
其他复合赋值符同上方用法一致
五、单目操作符
单目操作符就是操作数只有一个
sizeof ()括号里面不是类型时可以省略(),说到sizeof ,其中 size_t 是一种类型,是一种无符号整型的,size_t 就是为sizeof专门设计的一种类型,打印时使用%zd,但是size_t 在不同平台下可能是 unsigned int 也有可能是 unsigned long long int
sizeof(数组名) ,计算的是整个数组的大小
++前置,先对该数+1,然后再去使用这个数
++后置,相当于 先使用该数,然后在对该数进行 +1
— 与上方的++同理
六、关系操作符
> 、>= 、 < 、 <= 、!= 、==
注意:== 这个是 等于
= 这个是 赋值
七、逻辑操作符
&&逻辑与
|| 逻辑或
要区分
& 与 && ; | 与 ||
1&2 —> 0
1&&2 —> 1 (左边为假,右边无需计算,直接为假)
1 | 2 —> 3
1 || 2 —> 1(左边为真,右边无需计算,直接为真)
【例】1笔试题,求结果输出的值
#includeint main(){int i = 0, a = 0, b = 2, c = 3, d = 4;i = a++ && ++b && d++;printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);return 0;}
【结果】a = 1 b = 2 c = 3 d = 4
解析 首先 a = 0 ,a++ ,是先使用后 +1,又有&&(左边为假,右边无需计算,直接为假)
打印的是 a = 1,其他值正常打印
【例】2对这个题进行改编 a = 1
#includeint main(){int i = 0, a = 1, b = 2, c = 3, d = 4;i = a++ && ++b && d++;printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);return 0;}
【结果】 a = 2 b = 3 c = 3 d = 5
解析 a = 1时,左边为真,后面表达式继续进行计算,进而 a = 2, b = 3,c =3 ,d= 5
【例】3对这个题进行改编 && 改为 || , 并求出i 的值
#includeint main(){int i = 0, a = 0, b = 2, c = 3, d = 4;i = a++ || ++b || d++;printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);return 0;}
【结果】 a = 1 b = 3 c = 3 d = 4 i = 1
解析 因为时逻辑或(左边为真,右边无需计算,直接为真)虽然先使用 a 为假,但接下来的操作数 ++b , b = 3为真 进而后面无需计算。而 i 表达式里面有真 则 i = 1, 为真
八、 条件操作符
表达式1 ?表达式2 :表达式3;
唯一 一个三目操作符
在这里 看一个 求最大值的代码
a>b” />求三个数的最大值
#includeint main() {int a = 1;int b = 2;int c = 3;int max = 0;max = a > b ? a : b;max = max > c ? max : c;printf("%d",max);return 0;}
这里是使用两次条件操作符进行计算三个数中的最大值
九、逗号表达式
表达式1 , 表达式2 ,… , 表达式n
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
//代码1int a = 1;int b = 2;int c = (a>b, a=b+10, a, b=a+1);//逗号表达式
c 的结果是 13
逗号表达式可以对代码进行优化
a = get_val();count_val(a);while (a > 0){//业务处理a = get_val();count_val(a);}//使用逗号表达式可以改写为while (a = get_val(), count_val(a), a > 0){//业务处理}
十、下标引用、函数调用和结构成员
1. []下标引用操作符
注意 操作数为 一个数组名 + 一个索引值
int arr[20]; //创建数组// [ ] 的操作数 是 arr和 20
2. ( )函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数
#include void test1(){printf("hehe\n");}void test2(const char* str){printf("%s\n", str);}int main(){test1(); //()作为函数调用操作符。test2("hello bit.");//()作为函数调用操作符。return 0;}
3. 访问一个结构的成员
.结构体.成员名
-> 结构体指针 -> 成员名
#include struct Stu{char name[10];int age;char sex[5];double score;};void set_age1(struct Stu stu){stu.age = 18;}void set_age2(struct Stu* pStu){pStu->age = 18;//结构成员访问}int main(){struct Stu stu;struct Stu* pStu = &stu;//结构成员访问stu.age = 20;//结构成员访问set_age1(stu);pStu->age = 20;//结构成员访问set_age2(pStu);return 0;}
十一、表达式求值
表达式求值的顺序一部分是由操作符的 优先级 和 结合性 决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
1.隐式类型转换
整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提升的意义
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个(8bit)字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转
换为int或unsigned int,然后才能送入CPU去执行运算。
//例char a,b,c;a = b + c;
在上述代码中,b和c的值被提升为普通整型(int),然后再执行加法运算,加法运算完成之后,结果将被截断,然后再存储于a中
整型提升
整形提升是按照变量的数据类型的符号位来提升的
char c1 = -1;变量c1的二进制位(补码)中只有8个比特位:1111111因为 char 为有符号的 char所以整形提升的时候,高位补充符号位,即为1提升之后的结果是:11111111111111111111111111111111//正数的整形提升char c2 = 1;变量c2的二进制位(补码)中只有8个比特位:00000001因为 char 为有符号的 char所以整形提升的时候,高位补充符号位,即为0提升之后的结果是:00000000000000000000000000000001
无符号整形提升,高位补0
【例】1
int main(){char a = 0xb6;short b = 0xb600;int c = 0xb6000000;if (a == 0xb6)printf("a");if (b == 0xb600)printf("b");if (c == 0xb6000000)printf("c");return 0;}
【结果】c
实例的a,b要进行整型提升,但是c不需要整形提升a,b整形提升之后,变成了负数,所以表达式 a==0xb6 , b==0xb600 的结果是假,但是c不发生整形提升,则表达式 c==0xb6000000 的结果是真.
【a】1101 0110 整型提升1111 1111 1111 1111 1111 1111 1101 0110
【b】1101 0110 0000 0000 整型提升1111 1111 1111 1111 1101 0110 0000 0000
【例】2
int main(){char c = 1;printf("%u\n", sizeof(c));printf("%u\n", sizeof(+c));printf("%u\n", sizeof(-c));return 0;}
【结果】1 4 4
实例2中的,c只要参与表达式运算,就会发生整型提升,表达式 +c ,就会发生提升,所以 sizeof(+c) 是4个字节。表达式 -c 也会发生整形提升,所以 sizeof(-c) 是4个字节,但是 sizeof(c) ,就是1个字节
2.算术转换
如果某个操作符的 各个操作数的类型不一样,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面 寻常算术转换
long double
double
unsigned long int
long int
unsigned int
int
向上转换如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
注意: 算数转换要合理,否则可能会出现精度丢失
float pi = 3.14;int num = f;//num = 3 精度丢失
3.操作符的属性
复杂表达式的求值有三个影响的因素。
- 操作符的优先级
- 操作符的结合性
- 是否控制求值顺序
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
十二、操作符优先级
点击下方链接查看
链接:操作符优先级
注意:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。