文章目录
- 1.操作符的分类
- 2.⼆进制和进制转换
- 3.原码、反码、补码
- 4.移位操作符
-
- [4.1 左移操作符](#4.1 左移操作符)
- [4.2 右移操作符](#4.2 右移操作符)
- 5.位操作符:&、|、^、~
1.操作符的分类
-
算术操作符:
+
、-
、*
、/
、%
-
移位操作符:
<<
,>>
-
位操作符:
&
,|
,^
-
赋值操作符:
=
、+=
、-=
、*=
、/=
、%=
、<<=
、>>=
、&=
、|=
、^=
-
单目操作符:
!
、++
、--
、&
、*
、+
、-
、~
、sizeof
、(类型)
-
关系操作符:
>
、>=
、<
、<=
、==
、!=
-
逻辑操作符:
&&
、||
-
条件操作符:
? :
-
逗号表达式:
,
-
下标引用:
[]
-
函数调用:
()
-
结构成员访问:
.
、->
2.⼆进制和进制转换
进制转换的教程网上比比皆是,而且视频看起来肯定比文字清晰。这里就不过多赘述了。
我们需要掌握一下几个:
- 2进制
转
10进制 - 10进制
转
2进制 - 2进制
转
8进制和16进制 - 8进制和16进制
转
2进制 - 8进制
转
16进制 - 16进制
转
8进制
3.原码、反码、补码
整数的2进制表示方法有三种:
-
原码
-
反码
-
补码
有符号整数的三种表示方法均有符号位和数值位两部分,2进制序列中,最高位的1位是被当做符号位,剩余的都是数值位。
符号位都是用0表示"正",用1表示"负"。
正整数的原、反、补码都相同。
原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码。
反码:正数反码和原码一样。
补码:正数补码和原码一样。
负整数的三种表示方法各不相同。
原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码。(注意符号位)
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。
4.移位操作符
只有整数才能运用移位操作符。
4.1 左移操作符
例1:
c
#include <stdio.h>
int main()
{
int num = 10;
int n = num<<1;
//10补码: 00000000 00000000 00000000 00001010
//10<<1补码:0 00000000 00000000 00000000 00010100
//这里最前面的0被丢掉了
printf("n= %d\n", n);
printf("num= %d\n", num);
return 0;
}
打印:
c
n= 20
num= 10
例2:
c
#include <stdio.h>
int main()
{
int num = -1;
int n = num<<1;
//-1原码: 10000000 00000000 00000000 00000001
//-1反码: 11111111 11111111 11111111 11111110
//-1补码: 11111111 11111111 11111111 11111111
//-1<<1补码:0 11111111 11111111 11111111 11111110
//这里最前面的0被丢掉了
printf("n= %d\n", n);
printf("num= %d\n", num);
return 0;
}
打印:
c
n= -2
num= -1
4.2 右移操作符
右移运算分两种:
-
逻辑右移:左边用0填充,右边丢弃。
-
算术右移:左边用原该值的符号位填充(正数左边补0,负数左边补1),右边丢弃。
右移是算术右移还是逻辑右移是取决于编译器的。通常采用的都是算数右移。
例1:
c
#include <stdio.h>
int main()
{
int num = -10;
//-10原码:10000000 00000000 00000000 00001010
//-10反码:11111111 11111111 11111111 11110101
//-10补码:11111111 11111111 11111111 11110110
//逻辑右移:01111111 11111111 11111111 11111011 |0(这个0被丢弃了)
//算术右移:11111111 11111111 11111111 11111011 |0(这个0被丢弃了)
//这里采用算数右移
//右移补码:11111111 11111111 11111111 11111011
//右移反码:10000000 00000000 00000000 00000100
//右移原码:10000000 00000000 00000000 00000101--->-5
int n = num>>1;
printf("n= %d\n", n);
printf("num= %d\n", num);
return 0;
}
打印:
c
n= -5
num= -10
5.位操作符:&、|、^、~
双目操作符:
- &:按位与
- |:按位或
- ^:按位异或
单目操作符:
- ~:按位取反
5.1 &:按位与
有0则0。
c
#include <stdio.h>
int main()
{
int a = 6;
// 6的补码:00000000 00000000 00000000 00000110
int b = -7;
//-7的原码:10000000 00000000 00000000 00000111
//-7的反码:11111111 11111111 11111111 11111000
//-7的补码:11111111 11111111 11111111 11111001
int c = a & b;
// 6的补码:00000000 00000000 00000000 00000110
//-7的补码:11111111 11111111 11111111 11111001
//a & b补码::00000000 00000000 00000000 00000000-->0
printf("n= %d\n", c);
return 0;
}
打印:
c
n= 0
5.2 |:按位或
有1则1。
c
#include <stdio.h>
int main()
{
int a = 6;
// 6的补码:00000000 00000000 00000000 00000110
int b = -7;
//-7的原码:10000000 00000000 00000000 00000111
//-7的反码:11111111 11111111 11111111 11111000
//-7的补码:11111111 11111111 11111111 11111001
int c = a | b;
// 6的补码:00000000 00000000 00000000 00000110
//-7的补码:11111111 11111111 11111111 11111001
//a | b补码:11111111 11111111 11111111 11111111
//a | b反码:10000000 00000000 00000000 00000000
//a | b原码:10000000 00000000 00000000 00000001-->-1
printf("n= %d\n", c);
return 0;
}
打印:
c
n= -1
5.3 ^:按位异或
相同为0,不同为1。
c
#include <stdio.h>
int main()
{
int a = 6;
// 6的补码:00000000 00000000 00000000 00000110
int b = -7;
//-7的原码:10000000 00000000 00000000 00000111
//-7的反码:11111111 11111111 11111111 11111000
//-7的补码:11111111 11111111 11111111 11111001
int c = a ^ b;
// 6的补码:00000000 00000000 00000000 00000110
//-7的补码:11111111 11111111 11111111 11111001
//a ^ b补码:11111111 11111111 11111111 11111111
//a ^ b反码:10000000 00000000 00000000 00000000
//a ^ b原码:10000000 00000000 00000000 00000001-->-1
printf("n= %d\n", c);
return 0;
}
打印:
c
n= -1
5.4 ~:按位取反
按2进制位取反
c
#include <stdio.h>
int main()
{
int a = 0;
//0的原码:00000000 00000000 00000000 00000000
//~a补码: 11111111 11111111 11111111 11111111
//~a反码: 10000000 00000000 00000000 00000000
//~a原码: 10000000 00000000 00000000 00000001-->-1
printf("n= %d\n", ~a);
return 0;
}
打印:
c
n= -1
5.5 例题
例题1
不创建临时变量(第三个变量),实现两个整数的交换。
方法1:
c
int main() {
int a = 3;
int b = 5;
printf("交换前:a=%d b=%d\n", a, b);
a = a + b;
b = a - b;
a = a - b;
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
打印:
c
交换前:a=3 b=5
交换后:a=5 b=3
上面这个方法有点问题,如果a+b的和超过了一个整型数据的存储大小,那么就计算不了了。
方法2:
c
int main() {
int a = 3;
int b = 5;
printf("交换前:a=%d b=%d\n", a, b);
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
打印:
c
交换前:a=3 b=5
交换后:a=5 b=3
为什么呢?
因为异或操作符,相同两个数异或为0。
3^3=0
a^a=0
0异或任何数都为数的本身 。
0^3=3
0^a=a
异或是支持交换率的。
例题2
编写代码实现:求一个整数存储在内存中的二进制中1的个数。
c
int count_bit_onr(unsigned int n) {
int count = 0;
while (n) {
if ((n % 2) == 1) {
count++;
}
n = n / 2;
}
return count;
}
int main() {
int num = 0;
scanf("%d", &num);
int ret = count_bit_onr(num);
printf("%d\n", ret);
return 0;
}
输入:
c
15
打印:
c
4
也可以这么写:
c
int count_bit_onr(int n) {
int i = 0;
int count = 0;
for (i = 0; i < 32; i++) {
if ((n >> 1) & 1 == 1) {
count++;
}
}
return count;
}
int main() {
int num = 0;
scanf("%d", &num);
int ret = count_bit_onr(num);
printf("%d\n", ret);
return 0;
}
输入-1会打印32
这个算法的原理:
c
-1补码:11111111 11111111 11111111 11111111
1补码: 00000000 00000000 00000000 00000001
-1&1= 00000000 00000000 00000000 00000001
不论-1的补码前面多少个1,只要和1按位与后就只看最后一位是不是1。
然后运用移位操作符,将每一位都和1按位与,统计出一共多少个1。
其实还有更加巧妙地算法:
n=n&(n-1)
例如:n=11
c
n = 1011
n-1 = 1010
n&(n-1) = 1010
因为n=n&(n-1),所以现在新的n是1010
c
n = 1010
n-1 = 1001
n&(n-1) = 1000
因为n=n&(n-1),所以现在新的n是1000
c
n = 1000
n-1 = 0111
n&(n-1) = 0000
从这三次变化里面我们可以看到,我们执行了一次n=n&(n-1),那么n最右面那个1就会消失。把所有的1都去掉后就变成0了。
例题3
写一个代码,判断n是否为2的次方数。
首先我们先看看2的次方数:
c
000010
000100
001000
010000
100000
可以看出2的次方数里面只有1个1。
c
if (n & (n - 1) == 0) {
printf("yes")
}
例题4
编写代码,将13二进制序列的第五位修改为1,然后改回0。
第5位改成1,第5位就和1或|
,其他位都是0。
第5位改为0,第5位就和0与&
,其他位都是1。
c
int main() {
int a = 13;
//13原码:00000000 00000000 00000000 00001101
//13补码:00000000 00000000 00000000 00001101
//16补码:00000000 00000000 00000000 00010000
//16|13:00000000 00000000 00000000 00011101-->29
int n = 5;
a = a | (1 << (n - 1));
printf("%d\n", a);
a &= ~(a << (n - 1));
printf("%d\n", a);
return 0;
}
打印:
c
29
13