在日常生活中,我们习惯使用逢十进一的十进制,由 0~9 这 10 个数字组成。而计算机底层使用的是逢二进一的二进制,只包含 0 和 1 两个数字。
在 C++ 中编写这两个进制的转换代码时,我们通常使用字符串来保存二进制的每一位 '0' 和 '1',这样既方便逐位拼接,也便于按位遍历计算。
十进制转二进制
把一个非负十进制整数转换成二进制,最经典的方法就是 “不断除以 2 取余数”。因为二进制只有 0 和 1 两个数字,所以每次除以 2 得到的余数只可能是 0 或 1。
不妨拿十进制数 13 手算一遍。每次记录除以 2 之后的商和余数,再用商继续除以 2,直到商变成 0 为止:
13 / 2 = 6 ... 余 1
6 / 2 = 3 ... 余 0
3 / 2 = 1 ... 余 1
1 / 2 = 0 ... 余 1按计算顺序依次得到的余数是 1、0、1、1,把它们从后往前倒过来排列,就能得到 13 对应的二进制 1101。
为什么算出来的余数要倒过来排呢?其实这和拆十进制数是一个道理:拿 123 除以 10,第 1 次取余得到的 3 是最右边的个位,最后算出来的才是百位。换成除以 2 也一样,第 1 次算出的余数是最右边的最低位,最后 1 次算出的才是最左边的最高位。
因此,我们可以配合 while 循环,把每次算出来的余数依次拼接到字符串末尾。
等循环结束后,我们再引入 <algorithm> 头文件,调用 std::reverse() 函数把字符串反转过来即可。下面来看具体实现。
示例 1:除 2 取余转二进制
#include <algorithm> // 提供 std::reverse()
#include <iostream>
#include <string>
// 将十进制非负整数转换为二进制字符串
std::string decimalToBinary(int n) {
if (n == 0) {
return "0"; // 单独处理 0 的边界情况
}
std::string bin = "";
while (n > 0) {
int rem = n % 2; // 取出除以 2 的余数
bin += (rem == 0 ? '0' : '1');
n /= 2; // 商作为下一轮的被除数
}
// 余数从低位到高位记录,最后反转字符串
std::reverse(bin.begin(), bin.end());
return bin;
}
int main() {
std::cout << "0 的二进制是:" << decimalToBinary(0) << '\n';
std::cout << "13 的二进制是:" << decimalToBinary(13) << '\n';
std::cout << "42 的二进制是:" << decimalToBinary(42) << '\n';
return 0;
}运行结果如下。
0 的二进制是:0
13 的二进制是:1101
42 的二进制是:101010分析:
当我们向 decimalToBinary() 传入 0 时,函数开头的 if (n == 0) 会直接返回字符串 "0"。这是因为如果 n 一开始就是 0,后面的 while (n > 0) 条件不成立,循环体一次都不会执行,不提前处理就会返回空字符串 ""。
当 n 大于 0 时,循环内先通过 int rem = n % 2; 算出余数,再转为字符 '0' 或 '1' 拼到 bin 末尾。由于 '0' 和 '1' 的编码是连续的,这里的拼接语句也可以写成 bin += static_cast<char>('0' + rem);。
比如传入 13 时,4 轮循环结束后 bin 里保存的是反过来的 "1011"。最后调用 std::reverse() 掉转高低位顺序,就能得到正确的 "1101"。
提示 在实际开发中,如果只需要把整数转换为 8 位或 16 位等固定长度的二进制字符串,我们也可以直接使用 <bitset> 头文件中的 std::bitset。例如写 std::bitset<8>(13).to_string(),就能得到补齐前导零的 8 位二进制字符串 "00001101"。
二进制转十进制
反过来,如果我们拿到了一个二进制字符串(比如 "1101"),应该怎样把它还原成十进制整数呢?
先想一想平时熟悉的十进制:从左往右读字符串 "123" 时,每往右多看一位,前面算出的结果就要乘以 10 再加上新的一位。二进制是逢二进一,道理完全一样:我们先准备结果变量 result = 0,从左往右逐个读取字符,每读入一位就把 result 乘以 2 再加上当前位。
拿 "1101" 来说,result 初始值为 0,从左往右读入 4 个字符的计算过程如下:
读入 '1':result = 0 * 2 + 1 = 1
读入 '1':result = 1 * 2 + 1 = 3
读入 '0':result = 3 * 2 + 0 = 6
读入 '1':result = 6 * 2 + 1 = 13小伙伴们要注意,从字符串里取出来的 '0' 和 '1' 属于字符类型(char),不能直接当成整数 0 和 1 相加。借助前面学过的 char 与 int 互转方法,我们只需拿数字字符减去字符 '0'(即写成 ch - '0'),就能得到对应的整数 0 或 1。
下面我们配合范围 for 循环从左往右遍历二进制字符串,把整个转换过程写成代码。
示例 2:按位乘 2 转十进制
#include <iostream>
#include <string>
// 将二进制字符串转换为十进制整数
int binaryToDecimal(const std::string& bin) {
int result = 0;
for (char ch : bin) {
int bit = ch - '0'; // 字符转为数字 0 或 1
result = result * 2 + bit; // 乘 2 再加上当前位
}
return result;
}
int main() {
std::string b1 = "1101";
std::string b2 = "101010";
std::cout << b1 << " 转为十进制是:" << binaryToDecimal(b1) << '\n';
std::cout << b2 << " 转为十进制是:" << binaryToDecimal(b2) << '\n';
return 0;
}运行结果如下。
1101 转为十进制是:13
101010 转为十进制是:42分析:
int bit = ch - '0'; 这句代码表示把当前读取的字符 '0' 或 '1' 转换成对应的整数 0 或 1,刚好和前面的 '0' + rem 互为逆操作。紧接着 result = result * 2 + bit; 将前面的结果乘 2 并加上当前位,不需要调用幂函数计算 2 的几次方,写起来非常简洁。
对于上面这个例子,小伙伴们可以把测试字符串改成带有前导零的 "001101" 运行试试,看看算出来的结果为什么依然是 13。
