目录

一、算数操作符

二、移位操作符

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.操作符的属性

复杂表达式的求值有三个影响的因素。

  • 操作符的优先级
  • 操作符的结合性
  • 是否控制求值顺序

两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。

十二、操作符优先级

点击下方链接查看​​​​​​​

链接:操作符优先级

注意:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。