目录
1. 整数在计算机中的存储
1.1 整形类型
当我们敲下这样一段代码:
int a = 10;
int b = -10;
我们便在内存中分配了一块空间,并将10这个数字存了进去。
而这块空间的大小与整形的类型有关,C语言中,整形的类型有:
char //字符数据类型
short //短整型
int //整形
long //长整型
long long //更长的整形
//加上有无符号可细分为
char
unsigned char
signed char
short
unsigned short [int]
signed short [int]
int
unsigned int
signed int
long
unsigned long [int]
signed long [int]
1.2 整数在内存中的储存形式
对于1.1中的a和b,在内存中的存储分别为:
🎈 变量a在内存中的存储
🎈 变量b在内存中的存储
由于计算机只能识别二进制的数,因此在内存中,整数也是以二进制的形式存储的。但上图的结果似乎与我们观念中10和-10的二进制结果不同,因此在理解之前,需要知道原码,反码和补码的概念。
1.2.1 原码、反码、补码
计算机中的整数有三种表示方法,即原码、反码和补码。
三种表示方法均有符号位 和数值位 两部分。
符号位:用0表示“正”,用1表示“负”。
数值位:负整数的三种表示方法各不相同。
- 原码:直接将二进制按照正负数的形式翻译成二进制
- 反码:将原码的符号位不变,其他位依次按位取反
- 补码:反码+1
正整数的原、反、补码都相同。
对于整形来说:数据存放内存中存放的是补码。
为什么在计算机系统中,数值一律用补码来表示和存储?
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;
同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
例如:
int a = 10;
int b = -10;
/*
10的二进制表示为
00000000000000000000000000001010
化为十六进制为
00 00 00 0a
-10的二进制表示为
原码:10000000000000000000000000001010
反码:11111111111111111111111111110101
补码:11111111111111111111111111110110
补码的十六进制为
ff ff ff f6
*/
但似乎与之前看到的顺序是相反的,理解这点,需要知道大端存储和小端存储。
1.2.2 大小端介绍
什么是大端小端:
大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;
小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中。
假设变量x的类型为int,位于地址0x100处,它的十六进制值为0x01234567。地址范围0x100~0x103的字节顺序依赖于机器的类型:
——《深入理解计算机系统》第三版 P29
因此,对于上面所展示的10这样一个十六进制为00 00 00 0a的数而言,在小端存储的机器上得到的结果便是0a 00 00 00
2. 三种位操作符
& //按位与
| //按位或
^ //按位异或
注:他们的操作数必须是整数。
用表格总结一下:
名称 | 符号 | 运算规则 |
---|---|---|
按位与 | & | 两位同为“1”,结果才为“1”,否则为0 |
按位或 | | | 两位只要有一个“1”,结果就为“1”,否则为0 |
按位异或 | ^ | 两位 同“0”异“1” |
举例:
int main()
{
int a = 10; //补码是 00000000 00000000 00000000 00001010
int b = 3; //补码是 00000000 00000000 00000000 00000011
int i = a & b; //按位与 00000000 00000000 00000000 00000010 (2)
int j = a | b; //按位或 00000000 00000000 00000000 00001011 (11)
int k = a ^ b; //按位异或 00000000 00000000 00000000 00001001 (9)
printf("%d %d %d\n", i, j, k);
return 0;
}
打印结果为:
3. 位操作符应用实例
3.1 例题1
不能创建临时变量(第三个变量),实现两个数的交换
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("a = %d b = %d\n", a, b);
return 0;
}
3.2 例题2
编写代码实现:求一个整数存储在内存中的二进制中1的个数。
#include <stdio.h>
int main()
{
int num = -1;
int i = 0;
int count = 0;//计数
while (num)
{
count++;
num = num & (num - 1);
}
printf("二进制中1的个数 = %d\n", count);
return 0;
}
3.3 例题3
写一个函数,求两个整数之和,要求在函数体内不得使用 + 、 - 、 * 、 / 四则运算符号。
对于这个问题的详解:
由于不能使用四则运算符,因此考虑进行位操作。
对于十进制的加法如13+17,可以看成
- 十位上的数相加:1+1=2;
- 各位上的数相加:3+7=10,这里产生了进位;
- 结果就是20+10=30
二进制的数也是如此:
对于二进制的加法如3+7,可以看成0011+0111,思考方法如下:
- 先考虑不用进位的项,如0+0或0+1,可以用^将其排查出来;
- 再考虑需要进位的项,只能是1+1,可以用&将其排查出来,然后<<1左移一位相当于进位;
- 再把上面的数相加;
- 重复上述过程直到不产生进位
3+7
0011 + 0111——> 0011^0111——>0100(res1)
( 0011&0111)<<1——>0110(res2)
res1 + res2 (也可能产生进位,重复操作)
——>0010+1000
——>1010 + 0000(此时不会产生进位得到结果)
——>1010(10)
代码实现如下:
int Add(int num1,int num2)
{
while (num2 != 0)
{
int m = num1 ^ num2;
num2 = (num1 & num2) << 1;
num1 = m;
}
return num1;
}