目录

一. 操作符的分类

二.进制转换

1.其它进制转换10进制

2.10进制转换其他进制

3.2进制转换8进制

4.2进制转换16进制

三.原码,反码,补码

四. 移位操作符

1 .左移操作符

2 .右移操作符

五. 位操作符: & 、 | 、 ^ 、~

六.⼀道变态的⾯试题讲解

七.单目操作符

八.单目操作符

九.下标访问[]、函数调⽤()

1.[] 下标引⽤操作符

2.() 函数调⽤操作符

3.sizeod操作符

十. 结构成员访问操作符(不做深入理解)

1.结构的声明

2.结构体变量的定义和初始化

3.结构体成员的间接访问

十一. 操作符的属性:优先级、结合性

1.优先级

10.2 结合性

十二. 表达式求值

1.整型提升

2.算术转换

(1)表达式

(2)表达式

(3)表达式

(4)表达式

(5)表达式

4 总结


一. 操作符的分类

• 算术操作符: + 、 – 、 * 、 / 、 %双目操作符
• 移位操作符: <>移动的是二进制的位
• 位操作符:& | ^ ~ 使用二进制位计算
• 赋值操作符:= 、 += 、 -= 、 *= 、 /= 、 %= 、 <>= 、 &= 、 |= 、 ^=
• 单⽬操作符:!、++、–、 & 、 * 、 + 、 – 、~、sizeof、(类型)
• 关系操作符:> 、 >= 、 < 、 <= 、 == 、 !=
• 逻辑操作符:&& 、 ||
• 条件操作符:? :
• 逗号表达式:,
• 下标引⽤:[]数组元素访问
• 函数调⽤:()
• 结构成员访问:.、->

二.进制转换

2进制,八进制,10进制,16进制只是数值的不同表示形式。
举例:15 — 10进制
15的2进制:1111
15的8进制:17
15的10进制:15
15的16进制:F
所以只是数值表达形式不同,但数值都是15

10进制每一位0~9,逢十进一 比如123,199
2进制每一位0~1
8进制每一位0~7
16进制每一位0~15,0123456789a b c de f
10 11 12 13 14 15

怎么区分写出来的进制
8进制:017;0开头的是8进制数字。
16进制:0X5 ;0x开头是16进制数组。

int main(){int a = 017;//15printf("%d\n", a);int b = 0x17;//23printf("%d\n", b);}

二进制没区分,因为不会拿2进制初始化一个值。

1.其它进制转换10进制

其它进制是几,权重就是几。其实10进制的123表⽰的值是⼀百⼆⼗三,为什么是这个值呢?其实10进制的每⼀位是权重的,10进制的数字从右向左是个位、⼗位、百位….,分别每⼀位的权重是 10^0, 10^1, 10^2 …

123百位 十位 个位
10进制的位: 12 3
权重10^2 10^1 10^0
权重值 10010 1
求值 1*100 +2*10 + 3*1=123
2进制类似道理……

2.10进制转换其他进制

短除法/除基取余法(循环除基直到商0,再从小往上数)
补充:转换成几进制,就用几进制除,取余数。

3.2进制转换8进制

最多3位2进制描述1位八进制
8进制
0:0
1:1
2:10
3:11
4:100
5:101
6:110
7:111

所以在2进制转8进制数的时候,从2进制序列中//从右边低位开始向左每3个2进制位会换算⼀个8进制位,剩余不够3个2进制位的直接换算。
如:2进制的01101011,换成8进制:0153,0开头的数字,会被当做8进制。
2进制 01 101 011
8进制 153

4.2进制转换16进制

最多4位2进制描述1位16进制
16进制
0 0 a:1010
1:1 b:1011
2:10c:1100
3:11d:1101
4 100e:1110
5:101f:1111
6:110
7:111
8:1000
9:1001
10:1010
所以在2进制转16进制数的时候,从2进制序列中//从右边低位开始向左每4个2进制位会换算⼀个16进制位,剩余不够4个⼆进制位的直接换算。
如:2进制的01101011,换成16进制:0x6b,16进制表⽰的时候前⾯加0x
2进制0110 1011
16进制6 b

三.原码,反码,补码

整数的2进制表⽰⽅法有三种,即原码、反码和补码。
有符号整数的三种表⽰⽅法均有符号位和数值位两部分,2进制序列中,最⾼位的1位是被当做符号位,剩余的都是数值位。
符号位都是⽤0表⽰“正”,⽤1表⽰“负;。正整数的原、反、补码都相同。

负整数的三种表⽰⽅法各不相同。
原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。
反码:将原码的符号位不变其他位依次按位取反就可以得到反码。
补码:反码 + 1就得到补码。 + 1的操作。

举例:int main(){int num = 10;//signed int num = 10;//10是存放在变量num中,站4个字节 == 32bit位//0 00000000000000000000000000000001010 - 源码//0 00000000000000000000000000000001010 - 反码//0 00000000000000000000000000000001010 - 补码int num2 = -10;//1 00000000000000000000000000000001010 - 源码//1 11111111111111111111111111111110101 - 反码//1 11111111111111111111111111111110110 - 补码 return 0;}

对于整形来说:数据存放内存其实存放的是补码;计算机对于这种整数计算用的都是补码
为什么?

计算机系统中,数值⼀律⽤补码来表⽰和存储。原因在于,使⽤补码,可以将符号位和数值域统⼀
处理;同时,加法和减法也可以统⼀处理(CPU只有加法器)。cpu本来只能算加法,有了补码的概念,减法能转换成加法运算。

总结:
1.原码,反码,补码怎么算、
2.同时计算机内存整数存的是补码,输出是原码。

四. 移位操作符

<< 左移操作符
>> 右移操作符
注:移位操作符的操作数只能是整数。只能对整数进行移位,移动的是补码。

1 .左移操作符

移位规则:左边抛弃、右边补0

//举例:int main(){int m = 10;int n = m << 1;printf("n = %d\n", n);//20printf("n = %d\n", m);//10//m的结果不会变,是移位后的结果return 0;}//00000000000000000000000000001010 10的原码//m<<1//00000000000000000000000000010100

2 .右移操作符

移位规则:⾸先右移运算分两种:
1. 逻辑右移:左边⽤0填充,右边丢弃。
2. 算术右移:左边⽤原该值的符号位填充,右边丢弃。

//举例int main(){int m = 10;int n = m >> 1;printf("n = %d\n", m);//10printf("n = %d\n", n);//5int m = -10;int n = m >> 1;printf("n = %d\n", m);//-10//11111111111111111111111111110110 -10的补码printf("n = %d\n", n);//-5//11111111111111111111111111111011-5的补码//10000000000000000000000000000101-5的原码//存储的是补码,打印出来是原码return 0;}

右移到底是算术还是逻辑右移?取决于编译器实现,大部分的编译器是算术右移。

警告⚠️:对于移位运算符,不要移动负数位,这个是标准未定义的。
例如:
int num = 10;
num >> -1;不要移动负数,要在有效范围内移动。

五. 位操作符: & 、 | 、 ^ 、~

按(2进制位)
& 按位与:对应的二进制位进行与运算,只要有0就是0,两个同为1才是1。
| 按位或:对应的二进制位进行或运算,只要有1就是1,两个同为0才是0。
^ 按位异或:对应的二进制位进行奇或运算,相同为0,相异为1。
~ 按位取反:全部取反(包括符号位。)
操作的都是二进制位,是用于计算机补码比较和计算。

//按位与int main(){int a = 3;int b = -5;int c = a & b;//3//00000000000000000000000000000011 -- 3的补码//11111111111111111111111111111011 -- -5d补码//00000000000000000000000000000011 -- 结果3return 0;}
//按位或int main(){int a = 3;int b = -5;int c = a | b;//-5//00000000000000000000000000000011 -- 3的补码//11111111111111111111111111111011 -- -5的补码//11111111111111111111111111111011 -- 负数是补码,要转换原码10000000000000000000000000000101 -- 结果-5return 0;}
//按位异或int main(){int a = 3;int b = -5;int c = a ^ b;//-8//00000000000000000000000000000011 -- 3的补码//11111111111111111111111111111011 -- -5d补码//11111111111111111111111111111000 -- 负数是补码,要转换原码10000000000000000000000000001000 -- 结果 -8return 0;}
//按位取反:int main(){int a = 1;int b = ~a;//-2//00000000000000000000000000000001 -- 1的补码//11111111111111111111111111111110~ (取反)1000000000000000000000000000000010 --结果-2return 0;}

六.⼀道变态的⾯试题讲解

int main(){int a = 3;int b = 5;//方法1://这种效率比异或效率高,实际开发中用这种int c = a;a = b;b = c;//不能有变量方法2:a = a + b;b = a - b;//b = 3a = a - b;//a = 5//这种写法的缺陷是:a和b如果非常大,求和后结果超过了整形的最大值,就出问题了//方法3:要理解,代入法1带入2计算完带入3 1.a = a ^ b; 2.b = a ^ b;//a ^ b ^ b= a 3.a = a ^ b;//a ^ b ^ a = b printf("%d %d", a, b);return 0; //异或计算为什么不溢出:异或不会进位,没有创建变量}

异或操作符的特点:相同为0,相异为1
a^a = 0;a和a这2个数相同,所以对应所有2进制位都相同,为0。
假如 a = 3
0^a = a
000 ^ 011 = 011 = a
异或支持交换计算,比如:3^5^3 = 5

七.单目操作符

!、++、–、 & 、 * 、 + 、 – 、~、sizeof、(类型)
单⽬操作符的特点是只有⼀个操作数,在单⽬操作符中只有 & 和 * 没有介绍,这2个操作符,我们放在学习指针的时候学习。

八.单目操作符

1 exp1, exp2, exp3, …用逗号隔开的表达式叫逗号表达式。
特点:从左向右以次计算,整个表达式的结果是取最后一个表达式的结果,前面的表达式
可能会影响最后一个表达式结果。

//代码1int main(){int a = 1;int b = 2;int c = (a > b,a = b + 10,a,b=a+1);//逗号表达式 为假 0,12 13printf("%d",c)//输出13return 0;}//代码2,伪代码a = get_val();count_val(a);while (a > 0){//业务处理 a = get_val();//这2句冗余count_val(a);}如果使⽤逗号表达式,改写:while (a = get_val(), count_val(a), a > 0){//业务处理}

九.下标访问[]、函数调⽤()

1.[] 下标引⽤操作符

操作数:⼀个数组名 + ⼀个索引值(下标)
int arr[10];创建数组
arr[5] = 6;[] — 下标引用操作符,2个操作数
[] 的两个操作数是arr5

2.() 函数调⽤操作符

接受⼀个或者多个操作数:第⼀个操作数是函数名,剩余的操作数就是传递给函数的参数。

3.sizeod操作符

sizeof是操作符,不是函数,为什么?

十. 结构成员访问操作符(不做深入理解)

内置类型:
char
short
int
long
float
double
……C语言本身具有的类型。
自定义类型:
结构体–>描述相对比较复杂的对象。
数组
……根据自己实际需求定义一些类型。

结构是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量
如:标量数组指针甚⾄是其他结构体

1.结构的声明

2.结构体变量的定义和初始化

描述一个学生:名字+年龄+成绩。

结构体可以包含结构体。

struct Point{int x;//struct结构体里面可以是普通的变量,也可以是结构体成员。int y;};struct Data{int num;struct Point p;};int main(){struct Data d ={200,{10,11}};//结构体包含结构体初始化return 0;}

总结:用struct 关键字可以创建自定义变量1个或多个struct也可以初始化值;结构体可以包含结构体。用于复杂类型。

3.结构体成员的间接访问

运用在有时候你拿到的不是结构体变量的本身,拿到的是结构体变量地址。
ps是结构体指针里面放的是s1的地址,通过ps打印除数据
strect Stu * ps = &s1;取出s1的地址;
printf(“%d %d %d\n”, ps->name, ps->age, ps->score);
指针ps存了s1的地址相当于ps指向了s1,ps—>name意思是:ps指向s1成员name可以通过指针到成员里面的位置。
总结:箭头的左边是结构体指针,右边是成员名。//结构体指针—>成员名。
举例:

struct Stu//结构体类型{char name[15];// 名字int age;// 年龄};void print_stu(struct Stu s){printf("%s %d\n", s.name, s.age);}void set_stu(struct Stu* ps){strcpy(ps->name, "李四");ps->age = 28;}int main(){struct Stu s = { "张三", 20 };//通过结构体类型创建一个s对象。print_stu(s);set_stu(&s);print_stu(s);//可以把s对象这个结构体变量传给函数;也可以把s地址传给函数,但形参得用指针来接收ps。return 0;}

总结:箭头的左边是结构体指针,右边是成员名。//结构体指针—>成员名。

十一. 操作符的属性:优先级、结合性

(很重要)操作符是应用表达式中;目的是进行计算的;那如何影响表达式求值运算的?
C语⾔的操作符有2个重要的属性:优先级、结合性,这两个属性决定了表达式求值的计算顺序。

1.优先级

优先级指的是,如果⼀个表达式包含多个运算符,哪个运算符应该优先执⾏。各种运算符的优先级是不⼀样的。

举例:
1.3 + 4 * 5;
上⾯⽰例中,表达式3 + 4 * 5⾥⾯既有加法运算符(+),⼜有乘法运算符(*)。
由于乘法的优先级⾼于加法,所以会先计算4 * 5,⽽不是先计算3 + 4。
优先级:相邻操作符,优先级高的先执行,优先级低的后执行2个操作符相差十万八千里是没有优先级。

10.2 结合性

如果两个运算符优先级相同,优先级没办法确定先计算哪个了,这时候就看结合性了,则根据运算符。
是左结合,还是右结合,决定执⾏顺序。⼤部分运算符是左结合(从左到右执⾏),少数运算符是右结合(从右到左执⾏),⽐如赋值运算符(=)。

举例:
1.5 * 6 / 2;
*和/的优先级相同,它们都是左结合运算符,所以从左到右执⾏,先计算5 * 6,计算6 / 2。
运算符的优先级顺序很多,下⾯是部分运算符的优先级顺序(按照优先级从⾼到低排列),建议⼤概记住这些操作符的优先级就⾏,其他操作符在使⽤的时候查看下⾯表格就可以了。

总结:逗号表达式优先级最低,其次是赋值赋值运算符优先级非常低。

十二. 表达式求值

1.整型提升

缺省整形–默认整形int
c语⾔中整型算术运算总是⾄少以缺省整型类型的精度来进⾏的。
为了获得这个精度,表达式中的字符短整型操作数在使⽤之前被转换为普通整型
这种转换称为整型提升

如何进⾏整体提升呢?
1. 有符号整数提升是按照变量的数据类型的符号位来提升的。最高位是1或0;全部补符号位提升
2. ⽆符号整数提升,⾼位补0

int main(){//char类型的取值范围是-128-127//cahr是占用1个字节,1个字节8个bit位char c1 = 125;//00000000000000000000000001111101//125放到c1里面去//125是整形存放到char类型;这么多数据放不下,会发生截断,只保存低位的数据8个bit位//01111101 --c1截断后的数据 -- 有符号char//char c2 = 10;//00000000000000000000000000001010//10放到c2里面去//00001010 -- c2截断后的数据 -- 有符号char//整形提升,根据符号位,补成整形形式相加//00000000000000000000000001111101 --c1 - 有符号char//00000000000000000000000000001010 --c2 - 有符号char//00000000000000000000000010000111这个结果最终会放到c3里面去,整形放在char,得截断//10000111 -- c3截断后放在char里面的数据char c3 = c1 + c2;//c3的结果//11111111111111111111111110000111//%d是打印有符号的整数,所以打印的时候也要提升//10000000000000000000000001111000//10000000000000000000000001111001//提升后结果是负数要补码转换成原码//c1 + c2转换成整形计算,结果放入c3;printf("%d", c3);//-121。printf("%c", c3);//打印c3;//ASCLL没有-121这个值。printf("%c", c1 + c2);//135return 0;}

大部分编译器都是有符号char;signed char,在vs编译器下是有符号char。
总结:类型是小于整形类型用于char和short字节长度不到整形长度,才有整形提升。让他们变成整形计算。

2.算术转换

如果某个操作符的各个操作数属于不同的类型,那么除⾮其中⼀个操作数的转换为另⼀个操作数的类型,否则操作就⽆法进⾏。下⾯的层次体系称为寻常算术转换。

如果某个操作数的类型在上⾯这个列表中排名靠后,那么⾸先要转换为另外⼀个操作数的类型后执⾏运算。

举例:

总结:类型已经是大于等于整形类型不同类型数据进行计算的时候,转换成高位相同类型计算。

3. 问题表达式解析

(1)表达式

abcdef是变量计算感觉没差别,要是他们里面分别是表达式,里面计算后的值可能影响表达式后的变量的值。所以即使知道操作符结合性,优先级顺序依然会不知道一个表达式表达式求值顺序。

(2)表达式

有2种结果,到底是那种有时候是不知道的,所以我们要拆分开写。
有时候我们进行计算的时候,有些值会提前放在寄存器里边,给我们用。

(3)表达式

这个表达式在不同编译器都有不同结果,而且可读性差,不要写出这种代码。

(4)表达式

这3个函数调用不知道是谁先调用,函数先后调用可能会影响值,取决于编译器,不要写出这种代码。

(5)表达式

在不同编译器有不同的结果,这种就是垃圾代码。

4 总结

即使有了操作符的优先级和结合性,我们写出的表达式依然有可能不能通过操作符的属性确定唯⼀的计算路径,那这个表达式就是存在潜在⻛险的,建议不要写出特别复杂的表达式。上面5种表达式代码不要写。