运算符代表的是各种各样的运算(操作)
已知的运算符:+ - * / =(赋值)
1.运算符的分类
运算符的分类方法很多,通常用功能或者操作数个数进行分类
功能:算数运算符 逻辑运算符 位运算符 地址运算符.......
操作数个数:单目运算符 双目运算符 三目运算符
(1)算数运算符
+ - * / %(取余)
自增运算符(++)和自减运算符(--)的作用是对变量进行加一和减一的操作
他们都是单目运算符,只需要一个操作数
他们的使用方法有两种:前操作和后操作
前操作: ++变量名; --变量名; 后操作: 变量名++; 变量名--;
前操作的优先级非常高,通常在一条语句中最先执行
后操作的优先级非常低,通常在一条语句中最后执行
注意:
不要在一个表达式中多次使用自增和自减运算符,容易造成歧义
(2)关系和逻辑运算符
关系和逻辑运算符的计算结果是一个布尔值(真or假),使用关系和逻辑运算符就可以构成逻辑表达式。
关系运算符的优先级低于算数运算符
双目的关系运算符包括 == > < >= <= !=
表达式成立,结果就为1,不成立,结果就为0
逻辑运算符可以构成复杂的逻辑表达式,逻辑运算符的优先级比关系运算符要低
逻辑运算符包括 !(取反) &&(与) ||(或)
!是一个单目的逻辑运算符,作用是将一个布尔值(逻辑表达式)取反
与(&&)和或(||)属于双目运算符,他们的作用就是构造复杂的逻辑表达式
与和或的运算规则:
记忆口诀:与运算----全1为1,其他为0
或运算----全0为0,其他为1
短路特性:
与运算和或运算具有短路的特性,如果前一个表达式的已经可以决定整个表达式的结果,此时不会去计算后一个表达式。
(3)位运算符
位运算就是对操作数进行对应的位之间的运算
以下运算都属于位运算:
& ----------- 按位与
| ------------ 按位或
^ ----------- 按位异或
~ ----------- 按位取反(单目)
<< --------- 按位左移
>> --------- 按位右移
1)按位与 ---- &
按位与将操作数对应的位进行与运算
3 & 5 = 0011 & 0101 = 0001 = 1
按位与可以将一个数字的某些二进制位清0
任何数与上0结果为0,任何数与上1结果不变
xxxx xxxx & 1111 0111 = xxxx 0xxx
2)按位或 ---- |
按位或将操作数对应的位进行或运算
3 | 5 = 0011 | 0101 = 0111 = 7
按位与可以将一个数字的某些二进制位置一
任何数或上0结果不变,任何数或上1结果位1
xxxx xxxx | 0000 1000 = xxxx 1xxx
3)按位异或 ----- ^
按位异或就是对应位进行异或运算
异或运算的运算方法是 相同为0,不同为1
3 ^ 5 = 0011^0101 = 0110 = 6
按位异或可以将一个数字的某些二进制位取反
任何一个数异或上1取反,任何一个数异或上0不变
xxxx xxxx ^ 0000 1000 = xxxx yxxx(y是x取反)
口诀:与0清0,或1置1,异或1取反
4)按位取反 ------ ~
作用是将某个数的二进制位全部取反
~0xaf = 0x50 1010 1111 -------> 0101 0000
5)按位左移 ------ << 按位右移 --------- >>
移位操作将数字的所有二进制位当成一个整体水平左右移动
移位运算符属于双目运算符,左边是要移位的数据,右边就是要移位的位数
移位会导致移位数字的某些数位丢失,也会空出一些位置
移位的操作方法:
左移移出的高位丢弃,空出的低位补0 ------- 逻辑左移
无符号数据右移移出的低位丢弃,空出的高位补0 -------- 逻辑右移
有符号数据右移移出的低位丢弃,空出的高位补符号位 ------- 算数右移
如果移位操作中没有丢失有效数据(移出的位中没有1),左移n位就相当于将数字乘以2的n次方,右移n位就相当于将数字除以2的n次方
(4)使用位运算构造一个任意的二进制数
有一个数字1111 1111,需要将第3-4位清0 --------> 1110 0111
unsigned char ch = 255; //使用按位与对某些位清0 ch & ~(0x3<<3) = 1110 0111 = 0xe7
有一个数字0000 0000,需要将第3-4位置1 --------> 0001 1000
unsigned char ch = 0; //使用按位与对某些位清0 ch | (0x3<<3) = 0001 1000 = 0x18
有一个数字1111 0000,需要将第3-4位取反 -------->1110 1000
unsigned char ch = 240; //使用按位与对某些位清0 ch ^ (0x3<<3) = 1110 1000 = 0xe8
思考:使用位运算,构造出一个0xaf的字符类型的值
先清0后置1
(4)复合赋值运算符
大多数的双目运算符,可以和赋值运算符组合形成新的复合赋值运算符
+= -= *= /= %= &= |= ^= >>== <<==
当希望把运算结果保存到某个参与运算的操作数时可以使用复合赋值运算符
a = a+b ---------> a += b a = a< a <<= b
复合赋值运算符的优先级和赋值运算符一样低。
(5)地址相关运算符
&是取地址运算符(单目),可以根据变量名获取变量的地址
int num; &num ----->变量的地址
C语言中使用%p占位符打印地址
*运算符(单目)可以根据地址获取该地址上的数据
(6)逗号运算符
逗号运算符作用用于合并两个表达式,运算时取后一个表达式的结果
逗号运算符的优先级低于赋值运算符
应用场景:
1.连接表达式 2.声明多个同类型变量 3.for循环中作为分隔符
(7)三目运算符
三目(条件)运算符的作用是根据某个表达式的结果来选择执行的内容
格式:
表达式 ? 公式1 : 公式2
如果表达式为真,执行公式1,否则执行公式2
练习:使用三目运算符,将用户输入的大写字母变为小写字母,输入的小写字母变成大写字母。
2.类型转换
(1)自动类型转换
如果一个表达式中包含多种类型的数据,C语言会将这些数据转换成同一种数据类型后再进行处理。
这个转换过程由计算机自动完成,所以叫自动类型转换(隐式类型转换)
转换规则:
1)如果不同数据类型所占空间不同,就会把占用空间小的类型转换成占用空间大的类型
2)如果不同数据类型所占空间相同,就把整数类型转换成浮点类型
3)如果不同数据类型所占空间相同,就把有符号转换成无符号
注意:在实际开发中不要使用自动类型转换,保证一个表达式中的数据类型相同
(2)强制类型转换
可以在C语言中任意给数据指定类型,这种方式就叫强制类型转换
语法:
(指定的类型)原数据
强制类型转换几乎不受任何规则约束,有可能造成数据丢失
强制类型转换的实现是计算机临时生成一个新的数据,并不会改变原有数据
新数据的值来自于原数据,类型由强转类型指定
3.运算符的优先级和结合性
优先级:当在一个表达式中出现多个不同优先级的运算符时,由优先级决定先运算还是后运算,优先级高的先运算,数字越小,优先级越高。
结合性:当在一个表达式中出现了相同优先级的运算符,就要按照结合性的顺序来进行运算,结合性分为左结合(从左到右)和右结合(从右到左)
num + num_1 + num_2 ------>从左到右 num = num_1 = num_2 ------>从右到左
运算符优先级和结合性表格
4.C语言表达式的求值顺序
int num = 1;int num_1 = num++ + num;
(1)顺序点
C语言所有的运算都在表达式中完成,表达式本身完成以下两个内容
1)得到一个运算结果
2)产生副作用,所谓副作用就是会对表达式中的变量的值进行修改
++ -- =.....i++ ------->运算结果是i的值,副作用是将i+1 ++i ------->运算结果是i+1,副作用是将i+1
顺序点表示的是副作用必须产生效果的最后时间,副作用产生效果一定在顺序点之前。
..... i++.... 顺序点 .....
以num++ + num为例讨论顺序点对表达式求值的影响
如果在运算加法的时候副作用已经生效,那么表达式的运算结果为 2*num+1
如果在运算加法的时候副作用尚未生效,那么表达式的运算结果为 2*num
(2)C语言中规定的顺序点的位置
1)完整表达式结束
表达式语句结束
return语句
分支(if switch)和循环(for while)条件中
2)函数调用语句中,在是实参赋值完成和函数第一条语句执行之前
3)运算符 && || 逗号和? :的一个操作数获取之后
num++ + num在C语言中的运算结果是多少,答案是不确定
(3)C语言为什么不规定更多的顺序点来保证表达式确定的执行顺序
C/C++不规定很多的顺序点,求值顺序可能不确定,Java严格规定了求值顺序(从左到右)。C/C++这种做法是有意的,在编译器设计和优化的时候就可以根据实际需求去调整求值顺序,得到更高执行效率的代码。java规定了确定的求值顺序,在丧失了一定的效率获得了更加清晰地程序行为。一门编程语言是否规定求值顺序是由其各自设计原则来决定的。
结论:
在C/C++中,任何依赖于特定执行顺序,依赖顺序点之间实现副作用的表达式,其结果将得不到保证。
配套视频:深度解析C语言