一.原码、反码、补码
整数的二进制表示方法分三种,即原码、反码、补码。
有符号整数的三种表示方法均由两部分组成:符号位+数值位。最左边一位1表示负数,0表示正数
正数的原反补码相同;
负数的原反补各不相同:
原码:直接将数值按照正负数形式翻译成二进制。如-1的原码为100000000000000000000001;
反码:将原码除了符号位,全部取反。-1的反码:11111111111111111111111111111110;
补码:反码+1为补码。-1的补码为:11111111111111111111111111111111;
简言之,三者关系为:
二.移位操作符
>>右移操作符 <<左移操作符
注意:移动的是二进制位,且操作数只能是整数!!
2.1 左移操作符
左边移出一位,右边补0;
例如下面代码演示:
#include <stdio.h>
int main()
{
int a=5;
int b=(a<<1);
printf("b=%d\n",b);
printf("a=%d\n",a);
return 0;
}
具体操作步骤为:a为整型,4个字节,32个bit位
运算结果为:
可看到a经过运算并没有改变它原本的值,这一点需要注意!若要改变则a经过运算后赋值给自己,即复合赋值,写为a<<=1。
2.2 右移操作符
右移操作符分为两种情况:
- 逻辑右移:右边移出一位(丢弃),左边补0
- 算数右移:右边丢弃,左边补上原该值符号位
所以右移究竟采用哪一种,取决于编译器的实现,常见数编译器采用算术右移,更加合理。
注意:移位不要移动负数位,这是标准未定义的。
如:-1 在内存中的补码为11111111111111111111111111111111
逻辑右移后补码是 01111111111111111111111111111111(正数)(右边丢弃,左边补0)
取反 01111111111111111111111111111111
原码 01111111111111111111111111111111
算术右移 11111111111111111111111111111111 -1(左边补1,右边丢弃)
三.位操作符
位操作符有:
1.& //逻辑按位与
2.| //逻辑按位或
3.^ //按位异或
4.~ //按位取反
注意:它们的操作数必须是整数
3.1 & 逻辑-按位与
二个二进制数对每一位进行比较,一个为0,则该位置为0;两个都为1,该位置为1;
例如:1的二进制数的补码是 00000000000000000000000000000001
3的二进制数的补码是 00000000000000000000000000000011
则1&3的二进制数的补码是 00000000000000000000000000000001
利用&的“遇到0就是0”功能,可轻松将二进制中某一位由1变0;或者判断该位数是否为1;
例题:计算某位数的二进制中1的个数?
求1的个数就要知道每一位都是什么,我们知道在十进制中求每一位是用n%10和n/10循环使用 ,类比下来是不是用n%2和n/2就能知道n的二进制数的每一位呢?
3.1.1方法一
如13的二进制数 00000000000000000000000000001101
13%2=1 13/2=6
6%2=0 6/2=3
3%2=1 3/2=1
1%2=1 1/2=0(停止)
我们发现这样的循环确实可以实现统计二进制中1的个数,每到n%2=1时就1的个数就+1,,由此代码如下:
#include <stdio.h>
int main()
{
int n = 0; int count = 0;
scanf("%d", &n);
while (n)
{
if (n % 2 == 1)
count++;
n /= 2;
}
printf("%d\n", count);
return 0;
}
但是当我们把负数带进去,结果就不是预想的那样: 我们知道-1的补码里有32个1,可在该代码结果却是0,这是负数%2还是负数,无法使得count变化。
3.1.2 方法二
想办法使编译器将负数认成“正数”,当我把n的类型变成无符号整数(unsigned int)就很好解决这一问题,即便我输入负数,编译器就不会受符号影响,相当于把符号位变成了数值位,但数字不变。
#include <stdio.h>
int main()
{
unsinged int n=0;int count=0;
while(n)
{
if(n%2==1)
count++;
n/=2;
}
printf("%d/n",count);
return 0;
}
3.1.3 方法三
使用n=n&(n-1)算法,该算法可使二进制中最右边的1去掉
如n=13 简化一下它的二进制 n=1101 n-1=1100 n=n&(n-1)=1100
n=1100 n-1=1011 n=n&(n-1)=1000
n=1000 n-1=0111 n=n$(n-1)=0000
n=0000
由此可知代码如下:
#include <stdio.h>
int main()
{
int n = 0; int count = 0;
scanf("%d", &n);
while (n)
{
n = n & (n - 1);//这个表达式能将二进制中最右边的1去掉
count++;
}
printf("%d\n", count);
return 0;
}
3.2 | 逻辑-按位或
二个二进制数对每一位进行比较,一个为1,则该位置为1;两个都为0,该位置为0;
利用|的“遇到1就是1”功能,可轻松将二进制中某一位由0变1.
3.3 ~取反
对二进制数进行每一位取反,包括符号位,1取反为0,0取反为1
例题:将13二进制数的第五位改成1 再改成0
13的二进制数 00000000000000000000000000001101
由0变1,用|,且保障其他位不变
则用 00000000000000000000000000010000 去和该数进行逻辑或 而这个数是1<<4而来
在由1变0,用&,且保障其他位不变
改完之后 00000000000000000000000000011101
则用 1111111111111111111111111111111101111去和改之后的数去进行逻辑与
而该数是对1<<4取反
代码如下:
#include <stdio.h>
int main()
{
//改1
int n = 13;
n |= (1 << 4);//要把n的值改了
printf("%d\n", n);
//改0
//13 00000000000000000000000000001101
//29 00000000000000000000000000011101
//按位与 11111111111111111111111111101111
//该数对1<<4取反
n &= ~(1 << 4);
printf("%d\n", n);
return 0;
}
3.4 ^按位异或
两个二进制数每一位相比,相同为0,不同为1;
按此规律,n^n=0,0^n=n
例题:不要创建临时变量,将a,b交换数值
#include <stdio.h>
int main()
{
int a=3,b=5;
printf("之前a=%d,b=%d\n",a,b);
a=a^b;//将其看作一把钥匙,求谁带谁
b=a^b;//b=a^b^b,b^b=0,a^0=a
a=a^b;//a=a^b^a,a^a=0,b^0=a
printf("现在a=%d,b=%d\n",a,b);
return 0;
}
四、逗号表达式
遵循从左到右依次计算的原则,整个表达式结果是最后一个表达式的结果
五、下标访问[],函数调用()
5.1数组访问[]
操作数:一个数组名+一个索引值
int arr[]={1,2,3,4}//创建数组并初始化
arr[2]=3//对数组进行下表访问
//[]的两个操作数是arr和2
5.2函数调用
操作数:一个函数名+传给函数的参数
#include <stdio.h>
void greet()
{
printf("Hello World!")
}
int main()
{
greet()//这里的()就是函数调用操作符
return 0;
}