第八讲:操作符
1.原码、补码、反码
注意:
有符号整数的三种表示方法中,均有符号位和数值位两部分,最高位被当作是符号位,其余的为数值位
符号位:0表示正,1表示负
1.1正数
正数的原码,补码,反码相同
1.2负数
负数的原码:就是其二进制位表示
负数的反码为:原码取反(符号位不变)
负数的补码为:反码+1
例如:
注意:
在计算机中存储的是数值的补码,原因在于:
1.使用补码,可以将符号和数值统一处理
2.解决了减法和加法同一处理的问题(CPU只有加法器)
3.补码和原码相互转换,其原理是相同的,不需要加入额外的硬件电路
2.位移操作符
注意:
位移操作符的操作数只能是整数,而且移动的是整数的补码,分为:
1.<< : 左移操作符
2.>> : 右移操作符
2.1左移操作符
移位规则:
左边抛弃,右边补零
2.2右移操作符
右移操作符有两种规则:
1.逻辑右移:左边用0填充,右边丢弃
2.算术右移:左边用符号位填充,右边丢弃
3.位操作数:&、 |、^、 ~
注意:
位操作符的操作数必须是整数!
1.按位与&:只有对应位置都为1才为1,否则为0
2.按位或|:对应位置有一个为1便为1,其余为0
3.按位异或^:相同为0,相异为1
4.取反操作符~:将符号位和数值位的0和1都取反,但得到的是补码
位操作符常用的一些规律:
1.x & 1是奇数返回1,是偶数返回0,可以放在if中判断奇偶
2.x |= (1<<j)相当于x += pow(2,j)
3.x << i 在10进制中相当于乘上 2 的 i 次方
4.x ^ x = 0
5.x ^ 0 = x
6.x ^ (x-1)能够消除x最右边的1
6.x ^ y ^ y = y ^ x ^ y = x(满足交换率)
3.1练习1:不能创建临时变量,实现两个数字的交换
3.1.1解法1
这个方法有个缺点:当a和b数值很大时,a+b在运算时就会产生错误
3.1.2解法2
这种方法较难想到,也较难以理解,但是我们可以看出来:
a ^ b ^ b = b ^ a ^ b = a
3.2练习2:编写代码实现:求⼀个整数存储在内存中的⼆进制中1的个数。
3.2.1解法1
这个方法参考了10进制转换成2进制的方法
3.2.2解法2
这个方法缺陷在于必须要循环32次
3.2.3解法3
a & (a-1)可以消除a最右边的1,例如:
3.3练习3:编写代码将13⼆进制序列的第5位修改为1,然后再改回0
4.逗号表达式
逗号表达式,从左向右依次进行运算,整个表达式的结果为最后一个表达式的结果
5.下标访问【】和函数调用()
5.1下标引用操作符【】
操作数:一个数组名+一个索引值(下标)
int arr[10];
arr[9] = 10;
//[]的两个操作数是arr和9
5.2函数调用操作符()
第一个操作数是函数名,其余的操作数是传递给函数的参数,可以接受一个或多个操作数,而一个函数调用至少有一个操作数
6.结构体成员访问操作符
7.操作符的属性:优先级、结合性
7.1优先级
优先级是指如果一个表达式存在多个操作符,操作符优先级更高的那个先执行
7.2结合性
如果两个操作符的优先级相同,那么优先计算哪个,由结合形来决定
7.3优先级和结合性一览
链接: 优先级和j结合性
8.整型提升和算数转换
8.1整形提升
8.1.1什么是整形提升
c语言中整形算术运算总是以默认整形的精度来进行的,为了获得这个精度,短整型和字符操作数在使用之前会被转换成普通整形,这种转换被称为整型提升
8.1.2整形提升的意义
1.表达式的整形运算在CPU中相应的运算器内执行,CPU内整形运算器的操作数的字节长度一般为int类型的字节长度,所以即使是两个char类型的相加,也会先将char类型的操作数提升为标准类型长度(一般为int类型的字节长度(一般为4个字节))
2.通用CPU是难以实现两个8字节直接相加运算的,所以,各种操作数可能小于int长度的整形值,都会先转换为signed int或unsigned int,然后才能送去CPU进行计算
8.1.2.1例子
char a, b, c;
a = b + c;
1.b和c先转换成普通整形,在进行计算。
2.运算完成后,将结果进行截断,再存储在a中
8.1.3如何进行整形提升
规则:
1.有符号整数的提升按照符号位来提升
2.无符号整数的提升,高位补0
8.2算数转化
如果某个操作符的各个操作数属于不同的类型,那么除非一个操作数转换成另一个操作数的类型,否则操作就无法执行,算术转换一般遵循下面规律:
8.3各种问题表达式
8.3.1表达式1
a * b + c * d + e * f
比+的优先级高,但是我们只能确定比+先进行运算,但是还是有不同的运算方式:
8.3.2表达式2
c + --c
操作符的优先级只能决定⾃减 – 的运算在 + 的运算的前⾯,但是我们并没有办法得知, + 操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。
8.3.3表达式3
int main()
{
int i = 10;
i = i-- - --i * ( i = -3 ) * i++ + ++i;
printf("i = %d\n", i);
return 0;
}
无法预测程序运行的结果,在不同的编译器中有着不同的运行结果
8.3.4表达式4
int fun()
{
static int count = 1;
return ++count;
}
int main()
{
int answer;
answer = fun() - fun() * fun();
printf( "%d\n", answer);//输出多少?
return 0;
}
结果1:2 - 3 * 4
结果2:4 - 2 * 3
只展示了两个结果
8.3.5表达式5
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
printf("%d\n", ret);
printf("%d\n", i);
return 0;
}
结果1:4 + 4 + 4
结果2:3 + 3 + 4
仍然只展示两个结果
8.4总结
所以说,即使有了操作符的优先级和结合性,我们写出的表达式依然有可能不能通过操作符的属性确定唯⼀的计算路径,那这个表达式就是存在潜在⻛险的,建议不要写出特别复杂的表达式。