整形的原反补:
此章节是在32位cpu地址线下讲解。
整数的二进制表示方法一共有三种,即原码,反码,补码。
有符号整数的三种二进制表示方法又有符号位和数值位两部分,最高位的1被当做符号位,其余的都是数值位。如果最高位是1,就代表这个数是负数,如果是0,则这个为是正数。
上面这个二进制翻译为十进制是-1。
无符号整数的最高位是数值位。
原码:就是将整数按照正负数的形式翻译成二进制得到的就是原码。
1.正整数的原码,反码,补码都是相同的。
2.负整数的原码,反码,补码不相同。
负整数的反码:将原码的符号位不变,其余数值位按位取反(0变为1,1变为0)。
负整数的补码:将反码+1。
将补码取反+1也能够得到原码
对于整形来说,数据存放在内存中真实存放的是补码。
对于整形数据存放在内存中存放的是此整形的补码,因为在使用补码时,可将符号位和数值域统一处理,同时加法和减法也可以统一处理(cpu中只有加法器),而且补码和原码进行转化时不需要额外的硬件电路。
所以我们下面操作符操作的都是将整形翻译为二进制的补码形式。
1.位移操作符:
其操作数只能是整数。
1.1<<左移操作符:
左移操作符的规则:左边抛弃,右边补0。
#include<stdio.h>
int main()
{
int num = 10;
int n = num << 1;
printf("num=%d\n", num);
printf("n=%d\n", n);
return 0;
}
运行结果:
为什么会得到这个结果呢,我用图像演示左移操作符:左边丢弃1位,右边补0。(一般左移操作符有乘2的效果)
1.2 右移操作符:
右移操作符规则有两种:
1.逻辑右移:右边丢弃,左边补0.
2.算数右移:右边丢弃,左边补符号位。
1.2.1逻辑右移:
右边丢弃1位,左边补0。
逻辑右移比较少见,大部分编译器都是算数右移。
1.2.2算数右移:
右边丢弃一位,左边补符号位。
#include<stdio.h>
int main()
{
int num = 10;
int n = num >> 1;
printf("num=%d\n", num);
printf("n=%d\n", n);
return 0;
}
运行结果:(一般右移操作符有除2的效果)
注意:移位操作符不能移动负数位,这是标椎未定义的,左移操作符移动一位,一般对操作数会有乘2的效果,右移操作符移动一位,一般对操作数有除2的效果。
2.位操作符:
位操作符有:
&——按位与(有0为0,全1为1)
|——按位或(有1为1,全0为0)
^——按位异或(相同为0,不同为1)
~——按位取反(把0变1,把1变0)
注重讲解前面三个。
注意:它们的操作数必须是整数。
1.按位与(&):
规则:(有0为0,全1为1)
请看代码:
#include<stdio.h>
int main()
{
int a=-3,b=5;
int c=a&b;
printf("%d",c);
return 0;
}
我们对补码进行&(按位与)操作。
规则:有0为0,全1为1。
最后得到c的补码00000000000000000000000000000101,此时还是补码形式要转换为原码,又因为正整数的原反补相同,得到c的原码是00000000000000000000000000000101,翻译为十进制并打印在屏幕上为5。
2.按位或(|):
规则:(有1为1,全0为0)
#include<stdio.h>
int main()
{
int a=-3,b=5;
int c=a|b;
printf("%d",c);
return 0;
}
步骤和上面差不多
最终得到的c的补码11111111111111111111111111111101(负整数的补码),要得到原码,将补码取反加1,得到c的原码10000000000000000000000000000011原码,翻译为十进制并打印在屏幕上为-3。
3.按位异或(^):
规则:(相同为0,不同为1)
我们可以用(^)交换两个整数,并且不需要用到第三个变量。代码如下:
#include<stdio.h>
int main()
{
int a = -3, b = 5;
printf("交换前:a=%d,b=%d\n", a, b);
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("交换后:a=%d,b=%d", a, b);
return 0;
}
如果有一个数x,x^x=0,0^x=x。
x ^ 0 = x
x ^ x = 0
解读代码:a=a^b。
b=a^b,这里a的值已经变为a^b,带入后算式为b=a^b ^b,因为b^b=0,又因为a^0为a,所以b=a(赋值成功)
然后a=a^b,这里的a和b的值都已改变,带入改变后的值算式为a=a^b ^a,因为a^a=0,又因为0^b=b,所以a=b(交换值成功)
4.练习:
(1)用代码实现:求一个整数存储在内存中二进制中的1的个数:
提示:首先,我们可以用右移操作符,每次都只移动1为,每移动1位,我们都对最低位进行检查,也就是看是1还是0,若是1,计数器累加,我们共移动31次(共32位),这样就可以在最低位检查到32位的数值。问题是如何检查呢?这里就可以用到&按位与操作符,把每次移动后的结果&1,就可以检查最低位的数值,最低位为1,按位与后结果为1,最低位为0,按位与后结果为0。
#include<stdio.h>
int main()
{
int i = 0, n = 0, count = 0;
printf("请输入你要计算的数字:");
scanf("%d", &n);
for (i = 0; i < 32; i++)
{
if ((n >> i) & 1 == 1)
{
count++;
}
}
printf("一共有%d个1", count);
return 0;
}
3. 逗号表达式:
(表达式1,表达式2,表达式3,表达式4)
逗号表达式:用逗号隔开的多个表达式。
从左到右依次执行,整个表达式的结果是最后一个表达式的结果。
int a=1;
int b=2;
int c=(a>b,a=b+10,a,b=a+1);
请问c的值是多少?可知(a>b,a=b+10,a,b=a+1)是一个逗号表达式,逗号表达式规则:从左到右依次执行,结果是最后一个表达式的结果。推出c为13。
4.表达式求值:
1.整形提升:
整形提升是c语言中的一项规定:在表达式计算时,各种整形首先要提升为int类型,以保持数值的一致性和精度,如果int不足以表示则要提升为unsigned int类型,然后执行表达式计算。
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
char a=-1;
char b=1;
char c=a+b;
首先,b和c的值被提升为普通整形,然后再执行加法运算。
加法运算完成之后,结果被截断,然后储存于a中。
1.有符号整形提升,高位补符号位。
负数的整形提升:
char a=-1;(char型为8个比特位)
-1在a中二进制储存为:11111111
因为char是有符号char,整形提升的时候,最高位补充符号位
为:
11111111111111111111111111111111(32位)
正数的整形提升:
char b=1;
1在b中二进制储存为:00000001
因为char是有符号char,整形提升的时候,最高位补充符号位
为:
00000000000000000000000000000001(32位)
2.无符号整形提升:高位补0
2.算术转换:
如果某个操作符的各个操作数属于不同类型,那么除非其中一个操作数转换为另一个操作数的类型,否则操作就无法进行,下面层次体系称为寻常算数转换。
1.long double;
2.double
3.float
4.unsigned long int
5.long int
6.unsigned int
7.int
比如一个double类型的数据+int类型的数据,因为如上图,因为int类型在double类型下面,所以要进行+运算,就要将int转换为double才能进行。