1. 操作符的分类
2. 原码、反码、补码
整数的2进制表示方法有三种,即原码、反码、补码
有符号整数的三种表示方法均有符号位和数值位两部分,2进制序列中,最高位的1位是被当做符号位其余都是数值位。
符号位都是用0表示“正”,用1表示“负”。
例:分别写出1、-1的2进制
int a = 1;
int b = -1;
其中a、b都是整形变量,是4个字节,32bit位,那么a、b的2进制为:
a:00000000000000000000000000000001
b:10000000000000000000000000000001
正整数的原码、反码、补码都相同
负数的三种表示方法各有不同
原码:直接将数值按照正负数形式翻译成2进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码加1就可以得到补码。
补码得到原码也是可以使用取反加1的操作。
例:分别写出-10的原码、反码、补码
原码:10000000000000000000000000001010
反码:11111111111111111111111111110101
补码:11111111111111111111111111110110
对于整形来说:数据存放内存中其实存放的是补码。
为什么呢?
- 简化计算:使用补码可以将符号位和数值位统一处理,这样无论是正数还是负数,都可以用同一套逻辑来进行加减运算。由于计算机的CPU只有加法器,没有减法器,通过补码可以将减法运算转换为加法运算,从而简化了硬件设计。
- 避免歧义:补码的表示方法确保了0的编码只有一个,不会出现两个不同的编码对应同一个数0的情况。这对于避免数据表示上的歧义非常重要。
- 提高存储效率:原码、反码和补码是二进制数的三种不同表示方法。原码是最直观的表示方法,但它在表示负数时需要额外的符号位,并且在进行运算时需要分别处理正负号。反码是对原码的改进,它将负数的符号位保持不变,数值位取反。而补码则是在反码的基础上加1,它不仅解决了0的表示问题,还可以通过补码直接进行加减运算,无需额外的硬件电路。
- 方便逻辑电路实现:计算机由逻辑电路组成,逻辑电路通常只有两种状态,即开关的接通与断开,刚好可以表示成‘1’和‘0’。因此,计算机只存储二进制数据,而补码作为一种二进制表示方法,非常适合于计算机的逻辑电路实现。
3. 移位操作符(移动的是二进制位)
<< 左移操作符
>> 右移操作符
注:移位操作符的操作数只能是整数。
3.1 左移操作符
左移操作符有一个乘以2的n次方的效果(n为<<后面的数字)
移位规则:左边抛弃,右边补0.
例1:
上图中a的补码为:
00000000000000000000000000000110
其中b = a << 1,那么在a的补码的基础上抛弃左边的1位,然后在右边补上一个0,可得b的补码:
00000000000000000000000000001100
例2:
a的补码为:11111111111111111111111111111010
b得到的补码:11111111111111111111111111110100
3.2 右移操作符
左移操作符有一个除以2的n次方的效果(n为>>后面的数字)
移位规则:
1. 逻辑右移:左边用0填充,右边丢弃。
2. 算术右移,左边用原值的符号位(原值是正数就用0,是负数就用1)填充,右边丢弃。
注:是逻辑右移还是算术右移是取决于编译器的实现,常见的编译器都是算术右移。
算术右移:
a的补码为:11111111111111111111111111111010
b得到的补码:11111111111111111111111111111101
4. 位操作符:&、|、^、~
1. & 按位与
2. | 按位或
3. ^ 按位异或
4. ~ 按位取反
注:它们的操作数必须是整数
4.1 &
运算规则:
&左右两个数的补码中有0就取0,两个同时为1才取1
例:
3的补码为 :0000000000000000000000000000011
-5的补码为:11111111111111111111111111111011
c得到的补码为:0000000000000000000000000000011
4.2 |
运算规则:
|左右两个数的补码中有1就取1,两个同时为0才取0
例:
3的补码为 :0000000000000000000000000000011
-5的补码为:11111111111111111111111111111011
c得到的补码为:11111111111111111111111111111011
4.3 ^
运算规则:
相同为0,相异位1.
3的补码为 :0000000000000000000000000000011
-5的补码为:11111111111111111111111111111011
c得到的补码为:11111111111111111111111111111000
例题:不使用临时变量的情况下,交换a和b的值
由^的运算规则我们可以知道当^两边的数相同时,所得到的结果为0,而0^a=a,所以我们利用这个规律我们就可以在不使用临时变量的情况下,交换a和b的值,如下图:
4.4 ~
运算结果为~右边的数+1然后取相反数,例如:~1 = -2,~-1 = 0
运算规则:
对~右边的数的2进制按位取反
0的2进制为: 0000000000000000000000000000000
b得到的补码为:11111111111111111111111111111111
5练习:
练习1:
编写代码实现:求一个整数存储在内存中补码中1的个数。
法1:
由&的运算规则我们可以知道a&1=1的式子成立时,就说明a的补码最右边的数为1,从这里可以知道,依次将a的补码的32个数依次移动到最右边(这里可以使用右移操作符(>>)移动),然后统计有几个符合a&1=1,就可以求出这个整数存储在内存中补码中1的个数。
以13为例,求13在内存中补码中1的个数:
13的补码:
00000000000000000000000000001101
1的补码:
00000000000000000000000000000001
法2:
int main()
{
int n = 0;
scanf("%d", &n);
int count = 0;
while (n)
{
n = n & (n - 1);
count++;
}
printf("%d\n", count);
return 0;
}
思考题:
如何判断一个数是否是2的次方数?
2的n次方数的二进制中只有1个1,而式子n&(n-1)就可以直接把这个1去掉了,所以要想判断一个数是否是2的次方数,只需要判断等式n&(n-1)== 0是否成立就可以了。
练习2:
二进制位置1或者置0
编码代码将13二进制序列的第5位修改为1,然后再改为0
13的2进制序列:
00000000000000000000000000001101
将5位置为1后:
00000000000000000000000000011101
将5位置为0后:
00000000000000000000000000001101
int main()
{
int a = 13;
//将5位置为1
a |= 1 << 4;
printf("%d\n", a);
//将5位置为0
a &= ~(1 << 4);
printf("%d\n", a);
return 0;
}
6. 逗号表达式
exp1,exp2,exp3,....,expN
用逗号隔开的表达式就是逗号表达式。
逗号表达式,从左向右依次执行,整个表达式的结果是最后一个表达式的结果。
例1:计算下列表达式c中的结果:
int main()
{
int a = 1, b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);
printf("%d\n", c);
return 0;
}
表达式的运行过程为:a>b(为假,但不影响表达式的结果) -> a=b + 10 -> 12 -> b = a + 1 = 13
因为最终决定表示结果的是表达式:b = a + 1,所以最终的结果为:13
例2:
逗号表达式在if语句中真正起判断作用的是最后一个表达式 。
7. 下标访问[ ]、函数调用( )
7.1 [ ] 下标引用操作符
操作数:一个数组名 + 一个索引值(下标)
7.2 函数调用操作符
接受一个或多个操作数:第一个操作数是函数名,剩下的操作数就是传给函数的参数。
#include<stdio.h>
int main()
{
printf("hello world!\n");//( )函数调用操作符
return 0;
}