一、二进制数
日常生活中我们所接触的数字都是10进制,对于其他进制的数字可能听过但不一定真正的清楚并了解,比如除了10进制,还有2进制,8进制,16进制。其实由称呼就能知道的区别,比如10进制就是每十个数进一位,1 2 3 4 5...9->10,这样就是10进制,(生活中也有很多不同进制的数字,比如年月日中年和月的进制就是12进制,或者小时和分,分和秒的进制是60。)其他进制的数字也同样是一个道理。
9的各种进制形式
16进制:9 //10以上的数字分别用ABCDEF代替
8进制:11
2进制:1001
//16进制的数值之前写:0x
//8进制的数值之前写:0
二、原码 反码 补码
数字在内存中存储时是二进制数字。而一般将数据存放在内存中时存放的是补码。
原码:就是将数字转换成2进制数字的形式就是原码。
反码:将原码中的0换成1,1换成0就是反码。
补码:在反码的基础上+1就是补码。
内存中二进制数最左边的数字是符号位,0表示"正",1表示"负"。
三、位移操作符
位移操作符分为两种,分别是左移操作符“<<”和右移操作符“>>”,它们的作用是将二进制位的数相应的向左或向右挪移。
1.左移操作符
比如左移操作符使二进制数向左移动1位,那么最左边的数就被挤出去了,而右边也是同理,光说可能不太明白,没关系,我们看个图。
像这样就是一个为10的数字向左位移1位的样子了,通过代码是这样实现的:
#include <stdio.h>
int main()
{
int a = 10;
printf("%d\n", a);//10
printf("%d\n", a << 1);//20
return 0;
}
有没有发现什么?向左位移一次就让结果变成了2倍,这是巧合吗?让我们再试一次。
#include <stdio.h>
int main()
{
int a = 10;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d\n", a << i);
}
return 0;
}
运行结果:
看来往左移动多少位,数字就会乘以相应次数的2。
注:它们都只能作用于整数!!!
2.右移操作符
和左移操作符基本一样,有一点不同的是:左移操作符是向右补位,没有正负之分,而右移操作符是向左补位,有正负之分,于是右移操作符有两种形式。
逻辑右移:左边用0补充,右边丢弃。
算术右移:左边用原来的符号位补充,右边丢弃。
(还需要注意的是位移操作符不能移动负数位)
四、位操作符
常见的位操作符有四种,它们分别为&,|,^,~。前两种大概大家早在之前学习分支和循环语句时就已经见过了,它们作用在整形数据中使用二进制表示的数据中的每一位。
- &(与):同为1才为1,否则为0;
- |(或):有1就为1;
- ^(异或):不同为1,否则为0;
- ~(非):按位取反,0变1,1变0;
或许通过文字这样来看,表达出的意思还不够清晰,让我们直接来上代码。
1.&(与)操作符
同为1才为1,否则为0;
int a = -5;
int b = 7;
printf("%d\n", a & b);
int型在32位机器上占四个字节,那么高位补零,让我们看一下-5和7的原码:
a->10000000000000000000000000000101
b->00000000000000000000000000000111
分别求出a和b的补码,先对原码取反,然后再对反码+1。
a->10000000000000000000000000000101
11111111111111111111111111111010//求反
11111111111111111111111111111011//+1,补码
b->00000000000000000000000000000111//正数的补码与反码相同
//&:同为1才为1,否则为0;
得 a&b为00000000000000000000000000000011//输出结果为3
运行结果展示:这就是&(与)位操作符啦。
2.|(或)操作符
有1就为1;
接下来我们看|(或)操作符,同样是用a与b的补码来进行计算
11111111111111111111111111111011//a补码
00000000000000000000000000000111//b反码
//|(或)操作符:有1就为1;
a|b->11111111111111111111111111111111//(还得取反+1变成原码才行)
->10000000000000000000000000000000
->10000000000000000000000000000001//输出-1
运行结果展示:
3.^(异或)操作符
不同为1,否则为0;
接下来我们看^(异或)操作符,同样是用a与b的补码来进行计算
11111111111111111111111111111011//a补码
00000000000000000000000000000111//b反码
//^(异或):不同为1,否则为0;
a^b->11111111111111111111111111111100//(还得取反+1变成原码才行)
->10000000000000000000000000000011
->10000000000000000000000000000100//输出-4
运行结果展示:
4.~(非)操作符
按位取反,0变1,1变0;
这个与前三种不太一样,就是字面意思的取反效果,我们换一个表达式来体现它的效果和作用。
printf("%d\n", ~0);
//00000000000000000000000000000000
//取反
//11111111111111111111111111111111(再取反+1)
//10000000000000000000000000000001(-1)
练习题:判断一个整数存储在内存中的二进制中1的个数
我们先思考一下,这题应该怎么样去做呢?因为我们之前做的计算题大部分都是十进制为基础来进行运算的,而在内存中的二进制数字对于我们来讲还是比较陌生的,但其实仔细想想,这样类似的题我们之前其实经常做的~
判断二进制数字中1的个数...其实这个和之前我们做过的,打印出一个数字的每一位数,是一个道理,打印出一个数字的每一位数,需要我们对这个数字的个位,十位,百位分别对10取余,然后再分别打印出来,是一个%10,/10,%10,/10的顺序。我们先来看一下如何打印出数字的每一位数。
#include <stdio.h>
void Print(int n)
{
if (n > 9)
{
Print(n / 10);//这里运用到了递归函数,在下一篇文章我会为大家更详细的讲解
} //进入后一直/10,直到只剩一个最高位数,然后从最高位,第二位,第三位...
//一直打印到最后一位
printf("%d ", n % 10);
}
int main()
{
int a;
scanf("%d", &a);
Print(a);
return 0;
}
通过这样的方法就能够打印出一个数字的每一位数。
我们转变一下思路,那我们是否能用%2,/2,%2,/2...的方法将一个数字转换成二进制数字呢?
我们假设要将19转换为二进制数;
19 % 2 = 9......1;
19 / 2 = 9;
9 % 2 = 4......1;
9 / 2 = 4;
4 % 2 = 2......0;
4 / 2 = 2;
2 % 2 = 1......0;
2 / 2 = 1;
1 % 2 = 0......1;
1 / 2 = 0;
最后我们得到的二进制数字是10011(取余数从下往上读),对应的数字正好就是19,那么这次实验也验证了我们的想法,证明这是可行的,我们只需要用取余的方式将数字对应的二进制数字的每一位求出来,然后再算出一共有多少个1就可以了。
#include <stdio.h>
int Print(int n)
{
int num = 0;
while (n)
{
if (n % 2 == 1)
{
num++;
}
n = n / 2;
}
return num;
}
int main()
{
int a;
scanf("%d", &a);
int m = Print(a);
printf("%d", m);
return 0;
}
运行结果展示:。
(但其实这个代码只能适用于正数!比如-1进入,第一次循环-1/2直接等于0,输出0。但其实-1在内存当中的二进制是11111111111111111111111111111111(32个1)。)
那么最终我们得出这个代码只适用于部分情况,那到底什么做法才能让它适用于所有情况呢?我们再看刚才所举的数字19,它在内存中的形式是00000000000000000000000000010011,如果我们打破之前的思想,利用我们今天新接触的知识来进行思考...有了!&(与)操作符的作用是:同为1才为1,否则为0;那我们能不能做到对二进制的32位数挨个对1按位与运算符&(num & 1),检查出一个1,我们的计数数字num就+1,这样是否能求出二进制中1的个数呢?首先我们要先做到遍历32位数字,如果用我们刚刚学习到的位移操作符,位移31次就能做到全部遍历一遍了!思路有了,敲代码吧~
#include <stdio.h>
int Print(int n)
{
int num = 0;
int i = 0;
int m = 0;
for (i = 0; i < 32; i++)
{
if (((n >> i) & 1) == 1)
{
num++;
}
}
return num;
}
int main()
{
int a;
scanf("%d", &a);
int m = Print(a);
printf("%d", m);
return 0;
}
运行结果展示:。
五、逗号表达式
顾名思义,就是逗号组成的表达式。用一个个逗号把好几个式子分别分隔开来,就是逗号表达式。
逗号表达式是以从左往右的顺序计算,最终的结果就是最右方表达式的结果。
int main()
{
int a = 0;
int b = 7;
int c = 2;
int m=(++a, b = a + c, a = a + 3, a );
printf("%d", m);
return 0;
}
我们用这个代码为例,这就是一个逗号表达式,我们需要从左到右的顺序进行运算。首先++a,a变成1,b=a+c,使b变成了3,然后a又自增了3变成了4,最后表达式为a,a=4,则m=4,运行结果也就为4。
运行结果展示:
好了,那今天关于二进制及其相关操作符的知识就给大家分享到这里了,如果有讲的不好或者不对的地方还望指出,希望我能和大家一起共同进步,那我们下次见啦。