目录
算术操作符
算术操作符
+ - * / % // "/"两端都为整数时, 则只输出整数部分,如 7/2=3 ;“%”两端必须是整数。
移位操作符
移位操作符
>> << // 二进制操作 , 只能对整数进行操作, 移动的位数只能是正数
整数的二进制表示有:原码,补码,反码
正整数的原码,补码,反码相同。
负整数的原码,补码,反码需要计算。
eg:
7: 整型占4个字节,每个字节8个比特位,第一位是符号位
所以7 的原反补是 00000000 00000000 00000000 00000111
-7:
原码:10000000 00000000 00000000 00000111
反码:11111111 11111111 11111111 11111000 // 补码 = 原码符号位不变,其他位按位取反
补码:11111111 11111111 11111111 11111001 // 补码 = 反码 + 1
整数在内存中是以补码的形式存放的。
所以,移位是对补码进行操作。
左移
左移:左边丢弃, 右边补0
例子: 7 左移一位
以下输出是 14:
左移前:【00000000 00000000 00000000 00000111】 // 补码
左移后:0【00000000 00000000 00000000 00001110】
括号中的值变为 十进制的 14
int main() {
int a = 7;
// 左移操作符 移动的是二进制位
int b = a << 1;
printf("%d\n", b);
return 0;
}
例子:-7 左移一位
左移前:【11111111 11111111 11111111 11111001】 // 补码
左移后: 1【1111111 11111111 11111111 111110010】 // 补码
转为 反码 :【1111111 11111111 11111111 111110001】
转为原码:【10000000 00000000 00000000 000001110】
括号中的十进制表示 -14
所以,左移有乘2的效果
右移
右移操作符:
算术移位:右边丢弃,左边补原符号位
逻辑移位:右边丢弃,左边补0
以 7 为例
移动前:【00000000 00000000 00000000 00000111】
算术移位:【000000000 00000000 00000000 0000011】1
逻辑移位:【000000000 00000000 00000000 00000111】1
以 -7 为例
移动前:【11111111 11111111 11111111 11111001】 // 补码
算术移位:【111111111 11111111 11111111 1111100】1
对应原码:【10000000 000000000 00000000 00000100】 → -4
逻辑移位:【011111111 11111111 11111111 1111100】1
对应原码:【000000000 00000000 00000000 00000100】→ 4
编译器使用的是 算术右移
位操作符
位操作符
&(与) ^(异或) |(或) //二进制操作
按位与
两个二进制位的补码进行按位与操作:有 0 则 0 ,全 1 则 1
按位或
两个二进制位的补码进行按位或操作:有 1 则 1 ,全 0 则 0
按位异或
两个二进制位的补码进行按位或操作:相同为 0, 相异为 1
一个数和 0 异或得到的是原值,和 1 异或得到的是这个数取反。
a ^ a 的 结果是 0
异或支持交换律 a^a^b = a^b^a
// & | ^
int main() {
int a = 3; // 补码:00000000 00000000 00000000 00000011
int b = -5; // 补码:11111111 11111111 11111111 11111011
int c = a & b; //按位与:00000000 00000000 00000000 00000011 → 3
int d = a | b; //按位或:11111111 11111111 11111111 11111011
// 原码: 10000000 00000000 00000000 00000101 → -5
int e = a ^ b; //按位异或:11111111 11111111 11111111 11111000
// 原码: 10000000 00000000 00000000 00001000 → -8
// %d 以为着打印有符号整数
printf("%d\n", c);
printf("%d\n", d);
printf("%d\n", e);
return 0;
}
例子: 不创建临时变量, 交换 a 和 b 的值(应用)
答1:只适用于整型
// 不创建临时变量, 交换 a 和 b 的值
int main() {
int a = 3;
int b = 5;
printf("交换前:%d %d\n", a, b);
// 异或满足交换律
a = a ^ b; // a=a^b
b = a ^ b; // b = (a^b)^b = a(原a)
a = a ^ b; // a = (a^b)^((a^b)^b) = b(原b)
printf("交换后:%d %d\n", a, b);
return 0;
}
答2:
// 不创建临时变量, 交换 a 和 b 的值
int main() {
int a = 3;
int b = 5;
printf("交换前:%d %d\n", a, b);
// 使用该方法的前提是 a+b 不能溢出
a = a + b; // 此时 a 中存放的是 a,b 的和
b = a - b; // 此时 b 中存放的是 原来 a 的值
a = a - b; // 此时 a 中存放的是 原来 b 的值
printf("交换后:%d %d\n", a, b);
return 0;
}
赋值操作符
赋值操作符
= += -= *= /= &= ^= |= >>= <<= // “a+=b”等同于 a = a + b
单目操作符
单目操作符:即只对一个元素进行操作 ,如符号两边都有元素则是双目操作符
! : 逻辑反操作
- : 取负值
+ : 取正值
& : 取地址
sizeof() : 操作数的类型长度(以字节为单位)
可以计算类型sizeof(int),也可以计算变量的长度;计算变量可以省略括号 即:sizeof x
如计算数组,则计算的是整个数组的大小,即使数组中只有一个元素,单位是字节。
~ : 对一个数的二进制按位取反
int main() { int a = 0; // 00000000 00000000 00000000 00000000 补码 // 11111111 11111111 11111111 11111111 取反 ~a // 10000000 00000000 00000000 00000001 ~a 的原码 → -1 printf("%d\n", ~a); return 0; }
-- : 前置、后置-- // 前置的使用规则是先 -- , 后使用
++ : 前置、后置++ // 后置的使用规则是先使用,后 ++
* : 间接访问操作符(解引用操作符)
(类型) : 强制类型转换
关系操作符
关系操作符
>
>=
<
<=
!= 用于测试不相等
== 用于测试相等
逻辑操作符
逻辑操作符
&& 逻辑与 // a && b 如果左边为0, 则&&右边的操作不会再运算
|| 逻辑或 // a || b 如果左边为1, 则 || 右边的操作不会再运算
条件操作符
条件操作符(三目操作符)
exp1?exp2 : exp3
exp1为真,则执行exp2, 为假则执行exp3
逗号表达式
逗号表达式
exp1, exp2, exp3, ... , expn
// 逗号表达式就是由逗号隔开的一系列表达式
// 逗号表达式的特点是 :从左到右依次计算, 整个表达式的结果是最后一个表达式的结果。
下标引用、函数调用和结构成员
下标引用、函数调用和结构成员
[] () . ->
// 结构体对象 . 成员
// 结构体指针 -> 成员
表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样, 有些表达式的操作数在求值的过程中可能需要转换为其他类型。
隐式类型转换
C的整型算术运算总是至少以整形类型的精度来进行的。(即如果两个char进行运算,会先强制转换为整型)
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
如何进行整型提升?
//负数的整形提升
char c1=-1;
变量c1的二进制位(补码)中只有8个比特位:1111111
因为char 为有符号的char所以整形提升的时候,高位补充符号位,即为1提升之后的结果是:11111111 11111111 11111111 11111111
//正数的整形提升
char c2 =1;变量c2的二进制位(补码)中只有8个比特位:00000001
因为 char 为有符号的char所以整形提升的时候,高位补充符号位,即为0提升之后的结果是:00000000 00000000 00000000 00000001
// 无符号位整型提升,直接高位补0
算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
警告:算术转换要合理,要不然会有一些潜在的问题。
操作符的属性
复杂表达式的求值有三个影响的因素。
1.操作符的优先级
2.操作符的结合性
3.是否控制求值顺序。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。操作符优先级
优先级顺序:
括号操作符(“()”优先级最高) > 单目操作符 > 算术操作符 > 移位操作符 > 关系操作符 > 位操作符 > 逻辑操作符 > 赋值操作符 > 逗号表达式