C语言非常灵活,因为C语言有丰富的操作符,甚至可以直接操作到二进制的位。
挑战:要把C语言的这些操作符灵活的使用,也是需要深刻理解和长期使用的。
1.二进制
我们经常听到2进制,8进制、10进制、16进制这样的讲法,那是什么意思呢?其实2进制、8进制、10进制、16进制是数值的不同表示形式而已。
比如:数值15的各种进制的表示形式:
1 15的2进制:1111
2 15的8进制:17
3 15的10进制:15
4 15的16进制:F
首先我们先重点介绍一下二进制:
我们先从10进制讲起,其实10进制就是我们平常经常使用的,我们已经形成了很多尝试:
· 10进制中满10进1
·10进制的数字每一位都是0~9的数字组成
其中二进制也是一样的
·2进制中满2进1
·2进制的数字每一位都是0~1的数字组成
那么1101就是二进制的数字了。
1.1 2进制转10进制
其实10进制的123表示的值是一百二十三,那么为什么是这个值呢?其实十进制的每一位是权重的,10进制的数字从右向左是个位、十位、百位…,分别每一位的权重是10^0, 10^1, 10^2…
如下图:
2进制和10进制是类似的,只不过2进制的每一位的权重,从右向左是:2^0, 2^1, 2^2…
如果是2进制的1101,该怎么理解呢?
1.1.1 10进制转2进制数字
10进制的125转换的2进制:1111101
1.2 2进制转8进制和16进制
1.2.1 2进制转8进制
8进制的数字每一位是0~7, 0~7的数字,各自写成2进制,最多有3个2进制位就足够了,比如7的二进制是111,所以在2进制转8进制数的时候,从2进制序列中右边低位开始向左每3个2进制位会换算一个8进制位,剩余不够3个2进制位的直接换算。
如:2进制的01101011,换成八进制:0153,0开好头的数组,会被当做8进制。
0:000 1:001 2:010 3:011 4:100 5:101 6:110 7:111
1.2.2 2进制转16进制
16进制的数字每一位是0~9, a~f的 ,0~9, a~f的数字,各自写成2进制,最多有4个2进制位就足够了,比如f的二进制1111,所以在2进制序列中最右低位开始向左每4个2进制位会换算一个16进制位,剩余不够4个二进制位的直接换算。
如: 2进制的01101011,换成16进制:0x6b,16进制表示的时候前面加0x
1个16进制位,描述清楚需4个2进制位。
2. 原码、反码、补码(这里不涉及小数即浮点数)
整数的2进制表示方法有三种,即原码、反码和补码
三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位最高位的一位是被当作符号位,剩余的都是数值位。
正整数的原、反、补码相同。
负整数的三种表示方法各不相同。
原码: 直接将数值按照正负数的形式翻译成二进制得到的就是原码。
反码 :将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。
对于整型来说:数据存放内存中其实存放的是补码。
为什么呢?
在计算机系统中,数值⼀律⽤补码来表⽰和存储。原因在于,使⽤补码,可以将符号位和数值域统⼀
处理;
同时,加法和减法也可以统⼀处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是
相同的,不需要额外的硬件电路。
3.移位操作符
· <<左操作符
· >>右操作符
注: 移位操作符的操作数只能是整数。
3.1 左操作符
移位规则:左边抛弃、右边补0
3.2右移操作符
移位规则:首先右移运算分两种:
- 逻辑右移:左边⽤0填充,右边丢弃
- 算术右移:左边⽤原该值的符号位填充,右边丢弃
注:对于移位运算符,不要移动负数位,这个是标准未定义的。
例如:
1. int num = 10 ;
2. num>>-1 ;//error
4.位操作符:&、|、^
位操作符有:
1. & //按位与
2. | //按位或
3. ^ //按位异或
注:他们的操作数必须是整数。
#include<stdio.h>
int main()
{
int a = 5;
int b = -6;
int c = a & b;//按位异或 - 对应的二进制位上相同则为0,相异则为1
//00000000000000000000000000000101 -a
//10000000000000000000000000000110 -b
//11111111111111111111111111111001 b反
//11111111111111111111111111111010 b补
//
//00000000000000000000000000000101 a补
//11111111111111111111111111111010 b补
//00000000000000000000000000000000 &
printf("%d\n", c);
return 0;
}
#include<stdio.h>
int main()
{
int a = 5;
int b = -6;
int c = a ^ b;//按位异或 - 对应的二进制位上相同则为0,相异则为1
//00000000000000000000000000000101 -a
//10000000000000000000000000000110 -b
//11111111111111111111111111111001 b反
//11111111111111111111111111111010 b补
//
//00000000000000000000000000000101 a补
//11111111111111111111111111111010 b补
//11111111111111111111111111111111 a^b
printf("%d\n", c);
return 0;
}
接下来我们看一道变态的面试题:
不能创建临时变量(第三个变量),实现两个数的交换。
我们先用创建临时变量的方法
#include<stdio.h>
int main()
{
int a = 3;
int b = 5;
int tmp = 0;
tmp = a;
a = b;
b = tmp;
printf("a = %d b = %d", a, b);
return 0;
}
第二个思路
#include<stdio.h>
int main()
{
int a = 3;
int b = 5;
a = a + b;
b = a - b;
a = a - b;
printf("a = %d b = %d", a, b);
return 0; //有缺陷,整型存储的数值是有限制的,如果a,b值太大就会溢出
}
第三种(异或)
#include<stdio.h>
int main()
{
int a = 3;
int b = 5;
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("a = %d b = %d", a, b);
return 0;
}
异或支持交换律
这种异或操作有局限性
1.只能用于整数交换
2.代码可读性差
3.代码执行的效率也是低于使用第三个变量的方法
练习:编写代码实现:求一个整数存储在内存中的二进制中1的个数。
方法一
#include<stdio.h>
int main()
{
int a = 15;
//00001111
int count = 0;
while (a)
{
if (a % 2 == 1)
{
count++;
}
a = a / 2;
}
printf("count=%d\n", count);
return 0;
}
方法二
#include<stdio.h>
int main()
{
int a = 0;
scanf("%d", &a);
int i = 0;
int count = 0;
for (i = 0; i < 32; i++)
{
if (((a >> i) & 1) == 1)
{
count++;
}
}
printf("%d\n", count);
return 0;
}
方法三
int main()
{
int n = 0;
scanf("%d", &n);
int count = 0;
while (n)
{
n = n & (n - 1);
count++;
}
printf("%d\n", count);
return 0;
}
5.逗号表达式
1 exp1,exp2,exp3,expN
逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右一次执行。整个表达式的结果是最后一个表达式的结果。
1 int a = 1;
2 int b = 2;
3 int c = (a>b, a=b+10,a, b=a+1);//逗号表达式 最后结果是c=13.