
🫧个人主页:小年糕是糕手
🎨你不能左右天气,但你可以改变心情;你不能改变过去,但你可以决定未来!
目录
[1°10 进制转 x 进制](#1°10 进制转 x 进制)
[2°x 进制转 10 进制](#2°x 进制转 10 进制)
一、进制转换
我想大家应该都听过一句话:计算机底层只认识二进制,只有0和1,这里的0和1其实就是二进制,介绍其他进制之前,我们先来说说我们最常用的十进制,他是由0~9组成的,满10进1:
| 进制 | 进位规则 | 组成数字 | 基数 |
|---|---|---|---|
| 二进制 | 满2进 1 | 0、1 | 2 |
| 八进制 | 满8进 1 | 0~7 | 8 |
| 十进制 | 满10进 1 | 0~9 | 10 |
| 十六进制 | 满16进 1 | 0~9、A~F(A=10,B=11...F=15) | 16 |
下面我们来介绍一下进制间如何转换:
1.1、二进制转十进制
其实每个进制都有对应的权重,比如十进制中11是怎么来的,15就是10+5,5对应的权重就是5*10^0,所有结果就是5,10就是1*10^1所以就有了15,下面我们先尝试用其他进制来表示一下15:
15 的 2 进制:1111
15 的 8 进制:17
15 的 10 进制:15
15 的 16 进制:F
16进制的数值之前写:0x
8进制的数值之前写:0
所以二进制转十进制假设我们现在二进制是1101,我们按照对应权重进行计算:
二进制数
1101从右往左的位权是:
- 最右边第 0 位:数字 1,权重 2^0=1
- 第 1 位:数字 0,权重 2^1=2
- 第 2 位:数字 1,权重 2^2=4
- 第 3 位:数字 1,权重 2^3=8
正确的计算式应该是:1101 = 1×2^3+1×2^2+0×2^1+1×2^0=8+4+0+1=13
1.2、十进制转二进制
这里我们看一张图片即可:

下面大家可以自己去尝试写出1206的二进制:

1.3、二进制转八进制
- 八进制数字范围 :每一位只能是
0~7,因为 23=8,所以0~7中的每个数字都可以用最多 3 位二进制数 表示(例如:7的二进制是111)。- 二进制转八进制 :从二进制序列的右边低位开始向左,每 3 位二进制位为一组,换算成一个八进制位;如果最后剩余不足 3 位,就在左边补 0 后直接换算。
- 八进制转二进制:将每一位八进制数字,直接替换为对应的 3 位二进制数,拼接后去掉多余前导零即可。

8进制数转换2进制数是相关的过程,每⼀个8进制位转换成3个2进制位就行
1.4、二进制转十六进制
- 十六进制数字范围 :每一位是
0~9、a~f(A~F),因为 24=16,所以每个数字都可以用最多 4 位二进制数 表示(例如:f/F的二进制是1111)。- 二进制转十六进制 :从二进制序列的右边低位开始向左,每 4 位二进制位为一组,换算成一个十六进制位;如果最后剩余不足 4 位,就在左边补 0 后直接换算。
- 十六进制转二进制:将每一位十六进制数字,直接替换为对应的 4 位二进制数,拼接后去掉多余前导零即可。

16进制数转换2进制数是相关的过程,每⼀个16进制位转换成3个2进制位就行
总结一下就是:八进制转二进制就是3个为一组,从右往左进行拆解转换,十六进制转二进制就是4个为一组,从右往左进行拆解转换,并且八进制前加上0,十六进制前加上0x
1.5、原码、反码、补码
整数的二进制表示方法有三种,即原码、反码和补码;
整数分为有符号整数(signed)和无符号整数 (unsigned)

有符号整数的原码、反码和补码的二进制表示中均由符号位和数值位两部分组成,二进制序列中,最高位的 1 位是被当做符号位,剩余的都是数值位,符号位都是用 0 表示 "正",用 1 表示 "负"。
正整数的原、反、补码都相同。
负整数的三种表示方法各不相同,需要计算。

原码 :直接将数值按照正负数的形式翻译成二进制得到的就是原码。
反码 :将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码 :反码 +1 就得到补码。由补码得到原码也可以使用:取反,+1 的操作

cpp
int a = -10
原码:10000000 00000000 00000000 00001010
反码:11111111 11111111 11111111 11110101
补码:11111111 11111111 11111111 11110110
int a = 10
原码:00000000 00000000 00000000 00001010
反码:00000000 00000000 00000000 00001010
补码:00000000 00000000 00000000 00001010
无符号整数的三种二进制表示相同,没有符号位,每一位都是数值位:


讲清楚了整数表示的原码、反码和补码后,我们还得知道整数在内存中是以补码的形式存储的,整数在参与位运算的时候,也都是使用内存中的补码进行计算的,计算的产生的结果也是补码,需要转换成****原码 才是真实值
练习
1°10 进制转 x 进制
cpp
#include<iostream>
#include<string>
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()
{
int n, x;
cin >> n >> x;
n_to_x(n, x);
return 0;
}
2°x 进制转 10 进制
这个题目为大家提供一个思路:我们输入x,s求出s的长度,遍历字符串(从后往前遍历),找出每一个字符然后转换成整数再乘以这一位的权重,最后求和即可:
cpp
#include<string>
#include<iostream>
#include<cmath>
using namespace std;
int main()
{
int x;
string S;
cin >> x >> S;
size_t n = S.size();//求字符串的长度,我们想要的是从后向前遍历
long long sum = 0;
int ci = 0;//表示多少次方
for (int i = n - 1; i >= 0; i--)
{
if (S[i] <= '9')
sum += (S[i] - '0') * pow(x, ci);
else
sum += (S[i] - 'A' + 10) * pow(x, ci);
ci++;
}
cout << sum << endl;
}
这里还有第二种方法运用到了我们之前说过的一个函数stoi,大家不记得的话可以看下面一篇博客回忆一下:
cpp
#include<string>
#include<iostream>
using namespace std;
int main()
{
int x;
string S;
cin >> x >> S;
int ret = stoi(S, NULL, x);//头文件就是string
cout << ret << endl;
return 0;
}
3°进制转换1


cpp
#include <iostream>
#include <string>
using namespace std;
string s = "0123456789ABCDEF";
void x_to_m(int x, int m)
{
if (x >= m)
x_to_m(x / m, m);
cout << s[x % m];
}
int main()
{
int x = 0;
int m = 0;
cin >> x >> m;
x_to_m(x, m);
return 0;
}
4°进制转换2
cpp
#include<iostream>
#include<string>
using namespace std;
string str = "0123456789ABCDEF";
void x_to_m(int x, int m)
{
if (x >= m)
x_to_m(x / m, m);
cout << str[x % m];
}
int main()
{
int n;//原来进制
string s;//原来进制表示的数字
int m;//转换之后的进制
//1.将s中存放的n进制数字转换成10进制数字
cin >> n >> s >> m;
int x = stoi(s, NULL, n);
//2.将x转换成m进制
x_to_m(x, m);
return 0;
}
二、位运算操作符
先说一个前提:下面提到的位运算操作符,只适用于整数,不能应用于其他数据类型,char也属于整型家族所有也可以使用:
2.1、左移操作符
左移操作符是双目操作符,形式如下:
cpp
int num = 10;
num << i; //将num的二进制表示,左移i位
移位操作符,移动的是存储在内存中的补码的二进制序列,下面我们来看看移位规则:

下面我们来举个例子:
cpp
#include<iostream>
using namespace std;
int main()
{
int num = 10;
//原码 00000000 00000000 00000000 00001010 -- 10
//反码 00000000 00000000 00000000 00001010
//补码 00000000 00000000 00000000 00001010
int n = num << 1;
//进行左移
//补码 00000000 00000000 00000000 00010100
//反码 00000000 00000000 00000000 00010100
//原码 00000000 00000000 00000000 00010100 -- 20
cout << "n = " << n << endl;
cout << "num = " << num << endl;
return 0;
}
众所周知正数的原码、反码、补码都是相同的,下面我们来举个负数的例子看看:
cpp
#include<iostream>
using namespace std;
int main()
{
int num = -10;
//原码 10000000 00000000 00000000 00001010
//反码 11111111 11111111 11111111 11110101
//补码 11111111 11111111 11111111 11110110
int n = num << 1;
//补码 11111111 11111111 11111111 11101100
//反码 10000000 00000000 00000000 00010011
//原码 10000000 00000000 00000000 00010100 -- -20
cout << n << endl;
return 0;
}
这就是左移操作符的基本用法,我们要记住我们操作的是补码,最后看结果是多少要返回原码
2.2、右移操作符
右移操作符是双目操作符,形式如下:
cpp
num >> i;//将num的二进制表示右移i位
移位操作符,移动的是存储在内存中的补码的二进制序列,下面我们来看看移位规则:
右移运算分两种:逻辑右移和算术右移,具体采用哪种右移方式取决于编译器,大部分的编译器采用的是算术右移。两种移位方式的规则如下:
- 逻辑右移:左边用 0 填充,右边丢弃
- 算术右移:左边用原该值的符号位填充,右边丢弃


下面我们来举个例子看看:
cpp
#include <iostream>
using namespace std;
int main()
{
int num = -1;
//原码 10000000 00000000 00000000 00000001
//反码 11111111 11111111 11111111 11111110
//补码 11111111 11111111 11111111 11111111
int n = num >> 1;
//这里有逻辑右移和算术右移,取决于编译器
cout << "n = " << n << endl;
cout << "num = " << num << endl;
return 0;
}
负1的补码其实是比较特殊的,大家可以了解一下,下面为大家总结了一个结论:
总结一下是不是就是左移一位的结果就是不管正数还是负数都乘以2,右移就是如果是正数,答案就是除以二取整,如果是负数就不一定
警告⚠️:对于移位运算符,不要移动负数位,这个是标准未定义的。
cppint num = 10; num >> -1; // error
补充说明
在 C/C++ 标准中,移位运算符的右操作数必须是非负整数 ,否则行为是未定义的(UB),编译器可能报错或产生不可预期的结果,所以代码里绝对不要写
num >> -1或num << -2这类写法。
2.3、按位与操作符(&)
按位与操作符是双目操作符,形式如下:
cpp
a & b; //a和b按位与运算
规则:进行计算时写出a和b在内存中存储的补码的二进制形式进行计算,对两个数的对应二进制位进行与运算,只有对应的两个二进制都为1时,结果才为1,有一个数为0则为0:

下面为大家举个例子(大家可以自己在电脑上打印来核对结果):
cpp
#include<iostream>
using namespace std;
int main()
{
int a = 5;
//原码 00000000 00000000 00000000 00000101
//反码 00000000 00000000 00000000 00000101
//补码 00000000 00000000 00000000 00000101
int b = -7;
//原码 10000000 00000000 00000000 00000111
//反码 11111111 11111111 11111111 11111000
//补码 11111111 11111111 11111111 11111001
int c = a & b;
//a & b,a和b的补码进行按位与
//a 00000000 00000000 00000000 00000101
//b 11111111 11111111 11111111 11111001
//a&b 00000000 00000000 00000000 00000001 -- 补码
//我们算出来的是补码,但是这个补码第一个是0所以是正数
//正数的原反补相同,所以我们直接读即可 -- 1
cout << c << endl;
return 0;
}
2.4、按位或操作符(|)
按位或操作符是双目操作符,形式如下:
cpp
a | b; //a和b按位或运算
规则:进行计算时写出a和b在内存中存储的补码的二进制形式进行计算,对两个数的对应二进制位进行或运算,对应的两个二进制位只要有1,或结果就为1,两个都是0才为0:

下面为大家举个例子:
cpp
#include<iostream>
using namespace std;
int main()
{
int a = 5;
//原码 00000000 00000000 00000000 00000101
//反码 00000000 00000000 00000000 00000101
//补码 00000000 00000000 00000000 00000101
int b = -7;
//原码 10000000 00000000 00000000 00000111
//反码 11111111 11111111 11111111 11111000
//补码 11111111 11111111 11111111 11111001
int c = a | b;
//a | b,a和b的补码进行按位或
//a 00000000 00000000 00000000 00000101
//b 11111111 11111111 11111111 11111001
//a|b 11111111 11111111 11111111 11111101 -- 补码
// 10000000 00000000 00000000 00000010 -- 反码
// 10000000 00000000 00000000 00000011 -- 原码(-3)
cout << c << endl;
return 0;
}
2.5、按位异或操作符(^)
按位异或操作符是双目操作符,形式如下:
cpp
a ^ b; //a和b按位异或运算
规则:进行计算时写出a和b在内存中存储的补码的二进制形式进行计算,对两个数的对应二进制位进行异或运算,对应的两个二进制相同则为0,相异则为1:

下面为大家举个例子:
cpp
#include<iostream>
using namespace std;
int main()
{
int a = 5;
//原码 00000000 00000000 00000000 00000101
//反码 00000000 00000000 00000000 00000101
//补码 00000000 00000000 00000000 00000101
int b = -7;
//原码 10000000 00000000 00000000 00000111
//反码 11111111 11111111 11111111 11111000
//补码 11111111 11111111 11111111 11111001
int c = a ^ b;
//a ^ b,a和b的补码进行按位异或
//a 00000000 00000000 00000000 00000101
//b 11111111 11111111 11111111 11111001
//a^b 11111111 11111111 11111111 11111100 -- 补码
// 10000000 00000000 00000000 00000011 -- 反码
// 10000000 00000000 00000000 00000100 -- 原码(-4)
cout << c << endl;
return 0;
}
2.6、按位取反操作符(~)
按位取反操作符是单目操作符,形式如下:
cpp
~a; //对a的⼆进制位按位取反
规则:进行计算时写出a和b在内存中存储的补码的二进制形式进行计算,对操作数的二进制位进行按位取反运算,二进制是0的变成1,是1的变成0:

下面为大家举个例子:
cpp
#include <iostream>
using namespace std;
int main()
{
int a = -7;
//原码 10000000 00000000 00000000 00000111
//反码 11111111 11111111 11111111 11111000
//补码 11111111 11111111 11111111 11111001
int b = ~a;
//补码 00000000 00000000 00000000 00000110
//反码 00000000 00000000 00000000 00000110
//原码 00000000 00000000 00000000 00000110
//正数原反补相同
cout << b << endl;
return 0;
}
三、位运算的应用
3.1、奇偶数判断
**结论:**所有偶数的二进制表示中,最低位一定是0,所有奇数的二进制表示中,最低位一定是1,所以将一个数字与1进行按位与(&)运算,即可判断这个数是奇数还是偶数:
cpp
(x & 1) == 1, 说明x是奇数
(x & 1) == 0, 说明x是偶数
cpp
#include<iostream>
using namespace std;
int main()
{
int n;
cin >> n;
if ((n & 1) == 1)
cout << "odd" << endl;
else
cout << "even" << endl;
return 0;
}
3.2、保留二进制位中的指定位
有时候我们想去保留二进制位中的某一位或者某几位,我们该如何操作呢?实际上也非常简单,我们只需要利用按位与操作符的特性:
cpp
结果 = 原数 & 掩码
✅ 只有两位都为 1,结果才是 1 → 保留指定位
✅ 只要有一个是 0,结果就是 0 → 其他位清零
0010 1011
& 0010 1001
-----------
0010 1001 ✅ 只保留了指定位,其余全清零
这样其实就一目了然了,假设我们想要去保留最后n位,我们只需要按位与上一个000111...(n个1),如果我们想保留其中低2~4位,我们只需要去按位与00001110,结论就是想取哪几位,就按位与哪几位(那几位为1)
3.3、获取二进制位中的指定位
这个我们要区别于上一个,上一个保留的意思是我们将我们不想要保留的二进制位都设置为0,想要的位置不变保留即可,获取二进制中的指定位的意思就是我们想要去知道他在二进制位中的第i位是1还是0,我们学之前要明白他们分别代表什么意思:
核心公式与原理 💡
要获取整数
x二进制中第i位(从低到高,最低位为第 0 位)的值,使用公式:(x>>i) & 1
- 结果为 0 :表示第
i位是0- 结果为 1 :表示第
i位是1
举个例子:
确定x=00101011的第3位的值,只需要将x=00101011右移3位,第3位就到最低位,然后和1进行按位与运算即可:
cpp
x:00101011
右移三位后:
00000101
& 00000001
-----------
00000001
cpp
class Solution {
public:
uint32_t reverseBits(uint32_t n) { // 建议用uint32_t
uint32_t ret = 0;
for (int i = 0; i < 32; i++) {
// 获取第 i 位是 1 还是 0
int b = (n >> i) & 1;
// 将 b 移动到反转后的位置,合并到 ret 中
ret = ret | (b << (31 - i));
}
return ret;
}
};

3.4、将指定二进制位设置为1
核心公式
cppx |= (1 << i);意思
把 x 的第 i 位 强制变成 1 其他位保持不变
一位设置为 1
公式:
x |= (1 << i);作用:将 x 的第 i 位设为 1,其他位不变说明:
- i 从 0 开始编号
- 1<<i:生成只有第 i 位为 1 的数
- |:有 1 则 1,不影响其他位
如果我们想要将二进制位中的某几位设置位1:
核心公式
cppx |= 掩码;规则
掩码里哪几位是 1,x 的那几位就会被强制设为 1,其他位不动!
公式:
x |= m;作用:将 x 中 m 为 1 的位全部设为 1说明:
- m:想设为 1 的位为 1,其余为 0
- 不改变其他位
cpp
class Solution {
public:
int findComplement(int num) {
int ret = 0; // 存放最终的补数结果
int i = 0; // 记录当前处理到第几位(从最低位开始,第0位)
while (num) { // 只要 num 还有效位没处理完,就继续循环
// 取出 num 的最低位:(num & 1) 只能得到 0 或 1
if ((num & 1) == 0) {
// 如果最低位是 0,取反后就是 1,要把它放到 ret 的第 i 位
ret |= (1 << i);
}
// num 右移一位,丢弃已经处理的最低位,准备处理下一位
num >>= 1;
// 位数计数器 +1,下次处理更高一位
i++;
}
return ret; // 循环结束后,ret 就是所有取反为 1 的位拼接结果
}
};

💡 核心逻辑总结
- 只处理有效位 :
while(num)会自动遍历所有非前导零的二进制位。- 取反的本质 :原二进制位是
0→ 补数对应位要设为1;原二进制位是1→ 补数对应位保持0。- 位拼接 :用
ret |= (1 << i)把取反后为1的位,放到ret的正确位置上。
⚠️ 特殊情况:
num = 0题目提示
1 <= num < 2^31,所以num=0不会出现,代码不用处理这个边界。
3.5、将指定二进制位设置为0
把 第 i 位 设置为 0
公式
cppx &= ~(1 << i);作用
把 x 的第 i 位变成 0 其他位 完全不变
公式:
x &= ~(1 << i);作用:将 x 的第 i 位设为 0,其他位不变说明:
- i 从 0 开始编号
1 << i:生成第 i 位为 1 的数~:按位取反&:有 0 则 0,实现清 0
把 某几位同时设置为 0公式
cppx &= ~m;规则
- m :想清 0 的位是 1
- ~m :想清 0 的位变成 0,其他是 1
- 再
&一下 → 想清 0 的位就变成 0 了!
公式:
x &= ~m;作用:将 m 为 1 的位清 0说明:
- m:想清 0 的位写 1
- 清 0 后不影响其他位
3.6、反转指定的二进制位
反转指定二进制位
公式 :
x ^= (1 << i);作用 :将x的第i位(从低到高,最低位为第 0 位)反转,其余位保持不变。说明:
1 << i:生成仅第i位为 1 的掩码。^:异或运算,相同为 0,不同为 1,实现指定位翻转。- 多位反转:构造掩码
m(反转位为 1),执行x ^= m。
3.7、将二进制中最右边的1变为0
🔥 公式(直接背,就这一行)
cppx &= x - 1;作用
把二进制里最右边的一个 1 变成 0其他位完全不变!
cpp
class Solution {
public:
int hammingWeight(int n) {
int count = 0;
while (n) {
n = n & (n - 1);
count++;
}
return count;
}
};
cpp
class Solution {
public:
bool isPowerOfTwo(int n) {
return (n > 0) && (n & (n - 1)) == 0;
}
};
3.8、只保留二进制中最右边的1
🔥 公式(背这一行)
cppx &= -x;作用
只保留二进制最右边的 1,其余所有位全部变成 0
3.9、异或的巧用
们已经学习了异或运算符了,那异或运算符的特点:
x ^ x = 0,两个相同的数字异或结果是 0;0 ^ x = x,0 和 x 异或还是 x;a ^ b ^ a == a ^ a ^ b异或是支持交换律的;基于上述的特点,异或有一些非常巧妙的使用。
交换两个整数的值
cpp
#include <iostream>
using namespace std;
int main()
{
int a = 0;
int b = 0;
cin >> a >> b;
cout << "交换前:a = " << a << " b = " << b << endl;
a = a ^ b; //a' = a ^ b
b = a ^ b; //b' = a' ^ b == a ^ b ^ b == a
a = a ^ b; //a = a' ^ b' == a ^ b ^ a == b
cout << "交换后:a = " << a << " b = " << b << endl;
return 0;
}
使用异或交换两个数的值,只能适用于整形类型,因为异或运算仅适用于整型类型
只出现一次的数字
cpp
class Solution {
public:
int singleNumber(vector<int>& nums) {
int result = 0;
for (int num : nums) {
result ^= num; // 异或运算
}
return result;
}
};
小提示:
- 参数部分的
int * nums,这是一种指针的写法,这里不懂指针没关系,你可以直接理解成数组的形式,这里的int *nums等价于int nums[],直接使用下标操作即可。- 参数
int* nums直接改成数组的形式int nums[],提交也没问题。- 形参的名字是可以改的,如果你觉得
numsSize太长,直接改成n也行。- 算法竞赛需要指针的地方,可以有其他的方案,比如转换成数组的形式。
- 指针较难,也容易出错,从程序效率的角度和驾驭的难易程度上来看,其实在算法竞赛中指针使用的很少。一般会找其他的替代方案,比如数组。
丢失的数字
cpp
class Solution {
public:
int missingNumber(vector<int>& nums) {
int n = nums.size();
int result = 0;
// 异或数组中的所有元素
for (int num : nums) {
result ^= num;
}
// 异或 0..n 的所有数字
for (int i = 0; i <= n; ++i) {
result ^= i;
}
return result;
}
};
总结
| 应用 | 位运算表达式 | 说明 | 示例(假设二进制从低位起,位索引从0开始) |
|---|---|---|---|
| 奇偶数判断 | x & 1 |
如果结果为1,则为奇数;结果为0,则为偶数 | 5 & 1 = 1(奇数) 4 & 1 = 0(偶数) |
| 保留二进制位中的指定位 | x & mask |
mask 中为1的位保留,为0的位清零 |
x = 0b1101, mask = 0b1010 → 0b1000 |
| 获取指定位的值 | (x >> k) & 1 |
获取第k位的值(0或1) | x = 0b1101, k=2 → (0b1101 >> 2) & 1 = 1 |
| 将指定位设置为1 | `x | (1 << k)` | 将第k位设置为1,其余位不变 |
| 将指定位设置为0 | x & ~(1 << k) |
将第k位设置为0,其余位不变 | x = 0b1010, k=1 → 0b1000 |
| 反转指定位 | x ^ (1 << k) |
将第k位取反(0变1,1变0) | x = 0b1010, k=1 → 0b1000 |
| 将二进制中最右边的1变为0 | x & (x - 1) |
消除最低位的1 | x = 0b1010 → 0b1000 |
| 只保留二进制中最右边的1 | x & -x |
提取最低位的1(其余位清零) | x = 0b1010 → 0b0010 |
| 异或的巧用 | a ^ b ^ a == b a ^ b ^ b == a |
利用异或的自反性实现交换、找只出现一次的数、变量交换等 | 交换两个数:a ^= b; b ^= a; a ^= b 找唯一出现奇数次的数:全部异或 |
