章节目录

数字系统(十进制、二进制、十六进制和八进制)

本节阅读量:

在日常生活中,我们使用十进制来计数,其中每一位的数字可以是 0、1、2、3、4、5、6、7、8 或 9。十进制也被称为“基数 10”,因为它一共有 10 个可选的数字(0 到 9)。在这个系统中,我们是这样计数的:0、1、2、3、4、5、6、7、8、9、10、11……在 C++ 程序中,数字默认都被视为十进制。

1
int x { 12 }; // 12 默认是十进制

在二进制中,只有 2 个数字:0 和 1,因此它被称为“基数 2”。在二进制中,我们这样计数:0、1、10、11、100、101、110、111……

十进制和二进制是数字系统的两个例子。C++ 中主要有 4 种数字系统,按常用程度排序依次为:十进制(基数 10)、二进制(基数 2)、十六进制(基数 16)和八进制(基数 8)。


八进制和十六进制

八进制是基数 8——也就是说,可用的数字只有:0、1、2、3、4、5、6 和 7。在八进制中,我们这样计数:0、1、2、3、4、5、6、7、10、11、12……(注意:没有 8 和 9,因此我们从 7 直接跳到 10)。

十进制 0 1 2 3 4 5 6 7 8 9 10 11
八进制 0 1 2 3 4 5 6 7 10 11 12 13

要使用八进制字面值,在前面加上一个 0(零)即可:

1
2
3
4
5
6
7
8
#include <iostream>

int main()
{
    int x{ 012 }; // 在 C++ 中以 0 开头的数字代表八进制
    std::cout << x << '\n';
    return 0;
}

该程序的输出为:

1
10

为什么是 10 而不是 12?因为默认情况下数字是以十进制输出的,八进制的 12 等于十进制的 10。

八进制几乎很少被使用,我们建议你避免使用它。

十六进制是基数 16。在十六进制中,我们这样计数:0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F、10、11、12……

十进制 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
十六进制 0 1 2 3 4 5 6 7 8 9 A B C D E F 10 11

要使用十六进制字面值,需要在前面加上 0x。

1
2
3
4
5
6
7
8
#include <iostream>

int main()
{
    int x{ 0xF }; // 在 C++ 中以 0x 开头的数字代表十六进制
    std::cout << x << '\n';
    return 0;
}

该程序的输出为:

1
15

由于十六进制的每一位有 16 种不同的取值,因此一个十六进制数字正好可以表示 4 个比特(bit)。这样,我们就能用两位十六进制数精确地表示一个字节。

考虑一个 32 位整数,其二进制值为 0011 1010 0111 1111 1001 1000 0010 0110。由于数字很长且内容重复,这并不容易阅读。而在十六进制中,同样的值只是 3A7F 9826,看起来要简洁得多。因此,十六进制值常被用于表示内存地址或内存中的原始数据(其类型未知时)。


二进制字面值

在 C++14 之前,C++ 并不支持二进制字面值。我们通常借助十六进制字面值作为一种替代方案(你在现有代码库中可能仍会看到这种写法):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <iostream>

int main()
{
    int bin{};
    bin = 0x0001; // 对应二进制 0000 0000 0000 0001
    bin = 0x0002; // 对应二进制 0000 0000 0000 0010
    bin = 0x0004; // 对应二进制 0000 0000 0000 0100
    bin = 0x0008; // 对应二进制 0000 0000 0000 1000
    bin = 0x0010; // 对应二进制 0000 0000 0001 0000
    bin = 0x0020; // 对应二进制 0000 0000 0010 0000
    bin = 0x0040; // 对应二进制 0000 0000 0100 0000
    bin = 0x0080; // 对应二进制 0000 0000 1000 0000
    bin = 0x00FF; // 对应二进制 0000 0000 1111 1111
    bin = 0x00B3; // 对应二进制 0000 0000 1011 0011
    bin = 0xF770; // 对应二进制 1111 0111 0111 0000

    return 0;
}

到了 C++14,可以通过 0b 前缀来使用二进制字面值:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <iostream>

int main()
{
    int bin{};        // 假设 int 是 16 位
    bin = 0b1;        // 对应二进制 0000 0000 0000 0001
    bin = 0b11;       // 对应二进制 0000 0000 0000 0011
    bin = 0b1010;     // 对应二进制 0000 0000 0000 1010
    bin = 0b11110000; // 对应二进制 0000 0000 1111 0000

    return 0;
}

数字分隔符

由于较长的字面值可能不太好读,C++14 还新增了使用单引号(’)作为数字分隔符的功能。

1
2
3
4
5
6
7
8
9
#include <iostream>

int main()
{
    int bin { 0b1011'0010 };  // 对应二进制 1011 0010
    long value { 2'132'673'462 }; // 阅读起来更容易

    return 0;
}

还要注意,分隔符不能出现在数字的第一位之前:

1
    int bin { 0b'1011'0010 };  // error: ' 出现在了第一位之前

数字分隔符只是为了方便阅读,并不会以任何方式改变对应的数值。


以十进制、八进制或十六进制输出值

默认情况下,C++ 以十进制输出数值。不过,你可以通过 std::dec、std::oct 和 std::hex 这几个 I/O 操纵器来改变输出格式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <iostream>

int main()
{
    int x { 12 };
    std::cout << x << '\n'; // 十进制 (默认)
    std::cout << std::hex << x << '\n'; // 十六进制
    std::cout << x << '\n'; // 现在仍是十六进制
    std::cout << std::oct << x << '\n'; // 八进制
    std::cout << std::dec << x << '\n'; // 切回十进制
    std::cout << x << '\n'; // 十进制

    return 0;
}

输出结果为:

1
2
3
4
5
6
12
c
c
14
12
12

注意,一旦设置了某种 I/O 操纵器,输出格式就会一直保持,直到下次再被改变。


以二进制格式输出数值

以二进制格式输出数值会稍微麻烦一些,因为 std::cout 并没有内置这项功能。幸运的是,C++ 标准库中提供了一个名为 std::bitset 的类型(定义在 <bitset> 头文件中)可以帮我们完成这项工作。

要使用 std::bitset,我们可以定义一个 std::bitset 变量,并告诉它需要存储多少位。位数必须是编译时常量。我们可以用任意格式(包括十进制、八进制、十六进制或二进制)的整数值来初始化 std::bitset。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <bitset> // 引入 std::bitset
#include <iostream>

int main()
{
	// std::bitset<8> 表示存储 8 个 bit
	std::bitset<8> bin1{ 0b1100'0101 }; // 二进制的 1100 0101
	std::bitset<8> bin2{ 0xC5 }; // 十六进制的 1100 0101

	std::cout << bin1 << '\n' << bin2 << '\n';
	std::cout << std::bitset<4>{ 0b1010 } << '\n'; // 创建一个临时 bitset 并打印

	return 0;
}

输出结果为:

1
2
3
11000101
11000101
1010

在上面的代码中,这一行:

1
std::cout << std::bitset<4>{ 0b1010 } << '\n'; // 创建一个临时 bitset 并打印

创建了一个临时的(未命名的)4 位 std::bitset 对象,用二进制字面值 0b1010 进行初始化,以二进制格式打印其值,之后再把这个临时对象销毁。

在 C++20 和 C++23 中,借助新的格式化库(C++20)和打印库(C++23),我们有了更好的选择:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <format> // C++20
#include <iostream>
#include <print> // C++23

int main()
{
    std::cout << std::format("{:b}\n", 0b1010); // C++20
    std::cout << std::format("{:#b}\n", 0b1010); // C++20

    std::print("{:b} {:#b}\n", 0b1010, 0b1010); // C++23

    return 0;
}

输出结果为:

1
2
3
1010
0b1010
1010 0b1010

5.3 字面值常量

上一节

5.5 条件运算符

下一节