目录

NO.36十六届蓝桥杯备战位运算和操作符属性进制转换原码反码补码左移右移按位与按位或按位异或按位取反C

NO.36十六届蓝桥杯备战|位运算和操作符属性|进制转换|原码反码补码|左移|右移|按位与|按位或|按位异或|按位取反(C++)

操作符的分类

分类操作符
算术操作符+、-、 * 、/、%
移位操作符«、»
位操作符&、
赋值操作符=、+=、-=、 *= 、/=、%=、«=、»=、&=、
单⽬操作符!、++、–、&、 * 、+、-、~、sizeof、(类型)
关系操作符>、>=、<、<=、 == 、!=
逻辑操作符&&、
条件操作符? :
下标引⽤[]
函数调⽤()

⼆进制和进制转换

15  2 进制:1111  
15  8 进制:17  
15  10 进制:15  
15  16 进制:F

//16进制的数值之前写:0x  
//8进制的数值之前写:0
⼆进制

⾸先我们还是得从 10 进制讲起,其实 10 进制是我们⽣活中经常使⽤的,我们已经形成了很多常识:

• 10 进制中满 10 进 1

• 10 进制的数字是由 0~9 的数字组成的

其实⼆进制也是⼀样的

• 2 进制中满 2 进 1

• 2 进制的数字是由 0~1 的数字组成的

那么 1101 就是⼆进制的数字了。

2进制转10进制

其实 10 进制的 123 表⽰的值是⼀百⼆⼗三,为什么是这个值呢?其实 10 进制的每⼀位是有权重的, 10 进制的数字从右向左是个位、⼗位、百位…,分别每⼀位的权重是

1 0 0 , 1 0 1 , 1 0 2 … 10^0,10^1,10^2\dots

1

0

0

,

1

0

1

,

1

0

2

百位十位个位
10进制的位123
权重10^210^110^0
权重值100101
求值1*1002*103*1123

2 进制和 10 进制是类似的,只不过 2 进制的每⼀位的权重,从右向左是

2 0 , 2 1 , 2 2 … 2^0,2^1,2^2\dots

2

0

,

2

1

,

2

2

1101

2进制的位1101
权重2^32^22^12^0
权重值8421
求值1*81*40*21*113

8进制数转10进制数,⽅法类似,只是权重是

8 0 8^0

8

0 开始,然后依次是:

8 0 , 8 1 , 8 2 … 8^0,8^1,8^2\dots

8

0

,

8

1

,

8

2

16进制数转10进制数,⽅法类似,只是权重是

1 6 0 16^0

1

6

0 开始,然后依次是:

1 6 0 , 1 6 1 , 1 6 2 … 16^0,16^1,16^2\dots

1

6

0

,

1

6

1

,

1

6

2

10进制转2进制数字

https://i-blog.csdnimg.cn/direct/a588a3a8e27a4b7781e522fada0ac5d5.png

2进制转8进制

8 进制的数字每⼀位是 07 中的数字, 07 中的数字,各⾃写成 2 进制,最多有 3 个 2 进制位就⾜够了,⽐如 7 的⼆进制是 111 ,所以在 2 进制转 8 进制数的时候,从 2 进制序列中右边低位开始向左每 3 个 2 进制位会换算⼀个 8 进制位,剩余不够 3 个 2 进制位的直接换算。

如: 2 进制的 01101011 ,换成 8 进制: 0153 , 0 开头的数字,会被当做 8 进制

8进制数转换2进制数是相关的过程,每⼀个8进制位转换成3个2进制位就⾏

2进制转16进制

16 进制的数字每⼀位是 09 , af 的, 09 , af 的数字,各⾃写成 2 进制,最多有 4 个 2 进制位就⾜够了,⽐如 f 的⼆进制是 1111 ,所以在 2 进制转 16 进制数的时候,从 2 进制序列中右边低位开始向左每 4 个 2 进制位会换算⼀个 16 进制位,剩余不够 4 个⼆进制位的直接换算。

如: 2 进制的 01101011 ,换成 16 进制: 0x6b , 16 进制表⽰的时候前⾯加 0x 。

https://i-blog.csdnimg.cn/direct/e28ebfdcec21430798eb8b71a0df7284.png

16进制数转换2进制数是相关的过程,每⼀个16进制位转换成3个2进制位就⾏

原码、反码、补码

其实说到 2 进制,在计算机内部,数值是以 2 进制的形式来进⾏表⽰和存储。后⾯要讲解的位运算的操作符就是针对整数的⼆进制来进⾏运算的

移位运算符:>> <<  
位运算符  & | ^

整数的 2 进制表⽰⽅法有三种,即原码、反码和补码;整数分为有符号整数(signed)和⽆符号整数(unsigned)。

有符号整数的原码、反码和补码的⼆进制表⽰中均由符号位和数值位两部分组成, 2 进制序列中,最⾼位的 1 位是被当做符号位,剩余的都是数值位,符号位都是⽤ 0 表⽰“正”,⽤ 1 表⽰“负”。

正整数的原、反、补码都相同。

负整数的三种表⽰⽅法各不相同,需要计算。

https://i-blog.csdnimg.cn/direct/42979191fa024b5987a6685431a6c45d.png

原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。

反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。

补码:反码 +1 就得到补码。

由补码得到原码也是可以使⽤:取反, +1 的操作。

int a = -10;  
原码:10000000 00000000 00000000 00001010  
反码:11111111 11111111 11111111 11110101  
补码:11111111 11111111 11111111 11110110  
int a = 10;  
原码:10000000 00000000 00000000 00001010  
反码:10000000 00000000 00000000 00001010  
补码:10000000 00000000 00000000 00001010

⽆符号整数的三种 2 进制表⽰相同,没有符号位,每⼀位都是数值位。

https://i-blog.csdnimg.cn/direct/cffd4dbef7204c2d8d39cf319491290e.png

整数在内存中是以补码的形式存储的,整数在参与位运算的时候,也都是使⽤内存中的补码进⾏计算的,计算的产⽣的结果也是补码,需要转换成原码才是真实值。

在计算机系统中,整数的数值⼀律⽤补码来表⽰和存储。原因在于,使⽤补码,可以将符号位和数值域统⼀处理;同时,加法和减法也可以统⼀处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路

练习

#include <bits/stdc++.h>
using namespace std;

string s = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

void n_to_x(int n, int x)
{
    if (n >= x)
        n_to_x(n / x, x);
    
    cout << s[n % x];
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    int n, x;
    cin >> n >> x;
    n_to_x(n, x);
    
    return 0;
}
#include <bits/stdc++.h>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    int x;
    string s;
    cin >> x >> s;
    size_t n = s.size();
    int j = 0;
    int sum = 0;
    for (int i = n-1; i >= 0; i--)
    {
        if(s[i] <= '9')
            sum += (s[i] - '0') * pow(x, j);
        else
            sum += (s[i] - 'A' + 10) * pow(x, j);
        j++;
    }
    cout << sum << endl;
    
    return 0;
}
#include <bits/stdc++.h>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    int x;
    string s;
    cin >> x >> s;
    int ret = stoi(s, NULL, x);
    cout << ret << endl;
    return 0;
}
#include <bits/stdc++.h>
using namespace std;

string x = "0123456789ABCDEF";
void n_to_m(int t, int m)
{
    if (t >= m)
        n_to_m(t / m, m);
    cout << x[t % m];
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    int n, m;
    string s;
    cin >> n >> s >> m;
    int t = stoi(s, NULL, n);
    n_to_m(t, m);
    
    return 0;
}

位运算操作符

<< //左移操作符  
>> //右移操作符  
& //按位与操作符
| //按位或操作符
^ //按位异或操作符
~ //按位取反操作符
左移操作符

左移操作符是双⽬操作符,形式如下:

int num = 10;  
num << i; //将num的⼆进制表⽰,左移i位
#include <iostream>  
using namespace std;  
int main()  
{  
	int num = 10;  
	int n = num << 1;  
	cout << "n = " << n << endl;  
	cout << "num = " << num << endl;  
	
	return 0;  
}

https://i-blog.csdnimg.cn/direct/0339fb6c41ea48eeb8066925a1d7f378.png

右移操作符

右移操作符是双⽬操作符,形式如下:

num >> i;//将num的⼆进制表⽰右移i位
#include <iostream>  
using namespace std;  
int main()  
{  
	int num = -1;  
	int n = num >> 1;  
	cout << "n = " << n << endl;  
	cout << "num = " << num << endl;  
	
	return 0;  
}

右移运算分两种:逻辑右移和算术右移,具体采⽤哪种右移⽅式取决于编译器,⼤部分的编译器采⽤的是算术右移。两种移位⽅式的规则如下:

  1. 逻辑右移:左边⽤ 0 填充,右边丢弃

  2. 算术右移:左边⽤原该值的符号位填充,右边丢弃

    逻辑右移

    https://i-blog.csdnimg.cn/direct/b28a2b9b009d4636af42747dcbe95e13.png

算数右移

https://i-blog.csdnimg.cn/direct/4c385420b4084846b98171bad59eed8c.png

对于移位运算符,不要移动负数位,这个是标准未定义的

按位与操作符

按位与操作符是双⽬操作符,形式如下:

a & b; //a和b按位与运算

对两个数的对应⼆进制位进⾏与运算,只有对应的两个⼆进位都为 1 时,结果才为 1

#include <iostream>  
using namespace std;  
int main()  
{  
	int a = -5;  
	int b = 7;  
	int c = a & b;  
	cout << c << endl;  
	
	return 0;  
}

https://i-blog.csdnimg.cn/direct/222163a3174f4debbf51aea307df367d.png

-5的原码: 10000000 00000000 00000000 00000101

-5的反码: 11111111 11111111 11111111 11111010

-5的补码: 11111111 11111111 11111111 11111011

7的补码:00000000 00000000 00000000 00000111

-5的补码和7的补码按位与运算如下

https://i-blog.csdnimg.cn/direct/00b5dc48005446d3845927650d38e3f2.png

计算得到⼆进制是: 00000000 00000000 00000000 00000011 ,这个⼆进制序列当作补码时,且最⾼是 1 ,说明是正数,所以原码也是这个,所以最终 c 为 3

按位或操作符

按位或操作符是双⽬操作符,形式如下

a | b; //a和b按位或运算

对两个数的对应⼆进制位进⾏或运算,对应的两个⼆进位只要有 1 ,或结果就为 1

https://i-blog.csdnimg.cn/direct/baa8d0db3edc4eae8e0c712f646a8fd4.png

#include <iostream>  
using namespace std;  
int main()  
{  
	int a = -5;  
	int b = 7;  
	int c = a | b;  
	cout << c << endl;  
	
	return 0;  
}

https://i-blog.csdnimg.cn/direct/69d030218fcc4b3c8b99fcc76fd88073.png

-5的原码: 10000000 00000000 00000000 00000101

-5的反码: 11111111 11111111 11111111 11111010

-5的补码: 11111111 11111111 11111111 11111011

7的补码:00000000 00000000 00000000 00000111

-5的补码和7的补码按位或运算如下:

https://i-blog.csdnimg.cn/direct/3d93de7408f440bb91b2dd2bb0ac7feb.png

计算得到⼆进制是: 11111111 11111111 11111111 11111111 ,这个⼆进制序列当作补码

时,且最⾼是 1 ,说明是负数,求的原码为: 10000000 00000000 00000000 00000001 ,所以最终c为 -1 。

按位异或操作符

按位异或操作符是双⽬操作符,形式如下:

a ^ b; //a和b按位异或运算

对两个数的对应⼆进制位进⾏异或运算,对应的两个⼆进位相同则为 0 ,相异则为 1

https://i-blog.csdnimg.cn/direct/7dcb6f26e0fc45a2a09e0d5fb24a62e9.png

#include <iostream>  
using namespace std;  
int main()  
{  
	int a = -5;  
	int b = 7;  
	int c = a ^ b;  
	cout << c << endl;  
	
	return 0;  
}

https://i-blog.csdnimg.cn/direct/7b265ca56e7742d5b4ed8c284ec457df.png

-5的原码: 10000000 00000000 00000000 00000101

-5的反码: 11111111 11111111 11111111 11111010

-5的补码: 11111111 11111111 11111111 11111011

7的补码:00000000 00000000 00000000 00000111

-5的补码和7的补码按位异或运算如下:

https://i-blog.csdnimg.cn/direct/063ca6345faf47b38f802b5f4e866442.png

计算得到⼆进制是: 11111111 11111111 11111111 11111100 ,这个⼆进制序列当作补码

时,且最⾼是 1 ,说明是负数,求的原码为: 10000000 00000000 00000000 00000100 ,所以最终 c 为 -4

按位取反操作符

按位取反操作符是单⽬操作符,形式如下:

~a; //对a的⼆进制位按位取反

对操作数的⼆进制位进⾏按位取反运算, 2 进制是 0 的变成 1 ,是 1 的变成 0

#include <iostream>  
using namespace std;  
int main()  
{  
	int a = -5;  
	int b = ~a;  
	cout << b << endl;  
	
	return 0;  
}

https://i-blog.csdnimg.cn/direct/a63d038cc1ce4864810ec825e38f5753.png

-5的原码: 10000000 00000000 00000000 00000101

-5的反码: 11111111 11111111 11111111 11111010

-5的补码: 11111111 11111111 11111111 11111011

对-5的补码按位取反运算如下:

计算得到⼆进制是: 00000000 00000000 00000000 00000100 ,这个⼆进制序列当作补码时,且最⾼是 0 ,说明是正数,所以原码也是这个,最终 c 为 -4