11 C 语言数据进制详解:常见进制、前缀表示、格式化输出、混合运算、整数转换、小数转换、转换工具

1C 语言中的整数进制

在计算机科学中,所有数据的底层表示与处理都基于二进制系统。无论是简单的数值、字符,还是复杂的多媒体信息(如图像、音频和视频),在计算机内部都会被编码为二进制形式进行存储与运算

然而,二进制(Base-2)表示法通常非常长,对于人类开发者来说,直接阅读和书写大量的 0 和 1 既不直观也极易出错。为了更方便地观察和操作底层的二进制数据,计算机科学中引入了八进制(Octal)和十六进制(Hexadecimal)。

它们作为二进制的紧凑表示形式(或称为 “简写”),极大地提高了可读性。因此,理解以下几种常见的进制系统对于学习 C 语言至关重要。

1.1 常见进制系统

二进制(Base-2):使用数字 0 和 1 表示数值。在二进制中,每一位的基数为 2,当某一位的值达到 2 时,便向高位进位,即 “逢二进一”。这是计算机内部存储和处理数据的基本方式

八进制(Base-8):使用数字 0 到 7 表示数值。在八进制中,每一位的基数为 8,当某一位的值达到 8 时,便向高位进位,即 “逢八进一”。这是一种早期用于简化二进制表示的进位制。

十进制(Base-10):使用数字 0 到 9 表示数值。在十进制中,每一位的基数为 10,当某一位的值达到 10 时,便向高位进位,即 “逢十进一”。这是人类日常生活中最广泛使用的计数系统。

十六进制(Base-16):使用数字 0 到 9 以及字母 A 到 F(或 a 到 f)表示数值,其中 A – F 分别对应十进制的 10 – 15。在十六进制中,每一位的基数为 16,当某一位的值达到 16 时,便向高位进位,即 “逢十六进一”。这是目前最常用的人类可读的二进制 “简写” 形式。

以下表格展示了十进制数值 0 到 17 在这四种进制下的对应写法:

二进制八进制十进制十六进制
0000
1111
10222
11333
100444
101555
110666
111777
10001088
10011199
10101210A
10111311B
11001412C
11011513D
11101614E
11111715F
10000201610
10001211711

1.2进制的前缀表示

在 C 语言中,整数常量可以通过特定的前缀来表示其所属的进制。这有助于开发者在代码中清晰地书写不同进制的数值,也方便编译器正确解析。具体规则如下:

  • 二进制(Binary):以 0b 或 0B 开头,后跟 0 和 1 组成的数字序列
    • 例如:0b1010表示十进制的10
    • 标准说明:请注意,此 0b 前缀在 C23 标准中才被正式采纳。在 C99/C11/C17 等早期标准中,这是一种非标准的编译器扩展(尽管 Gcc、Clang、MSVC 等主流编译器早已支持)。
  • 八进制(Octal):以数字 0 开头,后跟 0 到 7 的数字。这是 C 语言的传统标准(自 C89/C90 起)。
    • 例如:012表示十进制的10
  • 十进制(Decimal):直接书写数字,不带任何前缀。这是 C 语言中最常见的书写方式。
    • 例如:10表示十进制的10
  • 十六进制(Hexadecimal):以 0x 或 0X 开头,后跟 0 - 9 和 A - F(a - f)。与八进制一样,这也是 C 语言的传统标准(自 C89/C90 起)。
    • 例如:0xA表示十进制的10

以下示例程序演示了如何在 C 语言中使用这些前缀,并将它们以十进制形式(使用%d格式符)输出到控制台,以验证它们的值:

#include <stdio.h> int main(void) { // 1. 使用不同进制前缀定义同一个数值:十进制的 10 int decimal_val = 10; // 十进制 (无前缀) int octal_val = 012; // 八进制 (以 0 开头) int hex_val = 0xA; // 十六进制 (以 0x 开头) // 注意:0b 前缀是 C23 标准或编译器扩展 int binary_val = 0b1010; // 二进制 (以 0b 开头) printf("--- 演示使用不同进制表示十进制的 10 ---\n"); printf("十进制 '10' 的值是: %d\n", decimal_val); printf("八进制 '012' 的值是: %d\n", octal_val); printf("十六进制 '0xA' 的值是: %d\n", hex_val); printf("二进制 '0b1010' 的值是: %d\n", binary_val); // 2. 演示另一个值 (十进制 31) int hex_complex = 0x1F; // 十六进制 1F = 1*16 + 15 = 31 int oct_complex = 037; // 八进制 37 = 3*8 + 7 = 24 + 7 = 31 printf("\n--- 演示使用不同进制表示十进制的 31 ---\n"); printf("十六进制 '0x1F' 的值是: %d\n", hex_complex); printf("八进制 '037' 的值是: %d\n", oct_complex); return 0; }

程序运行结果如下(环境:Win64 + VS Code + MinGW-w64 UCRT):

1.3进制的格式化输出

在 1.2 节中,我们学习了如何在代码中书写不同进制的整数。本节我们将学习如何使用 printf 函数,将一个(在内存中以二进制存储的)整数,以不同的进制形式输出(打印)到控制台。

printf 通过 “格式占位符” 来控制输出的进制。

常见格式占位符:

  • %d%i有符号十进制形式输出整数
  • %o无符号八进制形式输出整数(不含前缀 0)
  • %x无符号十六进制形式输出整数,使用小写字母 a - f(不含前缀 0x)
  • %X无符号十六进制形式输出整数,使用大写字母 A - F(不含前缀 0X)

带前缀的格式化输出:

我们可以使用 # 标志来强制 printf 输出该进制的标准前缀:

  • %#o无符号八进制输出,并自动添加前缀 0
  • %#x无符号十六进制(小写)输出,并自动添加前缀 0x
  • %#X无符号十六进制(大写)输出,并自动添加前缀 0X

⚠️ 注意:%o 与 %x 的无符号特性

%o、%x、%X 格式符(以及带 # 的版本)总是将参数视为无符号整数。若传入一个负数(例如 -1),printf 会将其解释为该负数在内存中对应的那个巨大的无符号正整数。

例如,在 32 位系统上,将 -1 作为 %x 格式输出,会打印出 ffffffff。

📚 扩展:理解有符号(signed)与无符号(unsigned)

你可能想知道为什么 %x 会把 -1 打印成 ffffffff。这涉及到 C 语言中整数的 “有符号” 和 “无符号” 特性:

  • 有符号(signed):int 类型默认是 “有符号” 的,意味着它可以存储正数、负数和零。它会使用最高的一个位(符号位)来区分正负
  • 无符号(unsigned):unsigned int 类型假定数值永远非负(0 或正数)。因此,它将所有的位(包括那个本应是 “符号位” 的最高位)都用来表示数值大小,能表示的最大正数约是 int 的两倍,但不能表示负数。

这就解释了 printf 的行为:在 C 语言的内部规则中,-1 在内存中恰好是以所有位都为 1 的二进制模式来表示的:

  1. 当使用 %d(有符号)时,printf 检查符号位,理解这个 “全 1” 模式代表 -1
  2. 但当你使用 %x(无符号)时,它会忽略 “符号位” 的规则,将这个 “全 1” 模式当作一个巨大的正数来打印(即 ffffffff)

⚠️ 注意:printf 不支持二进制输出

C 语言的 printf 函数不支持直接输出二进制形式(即没有类似 %b 的标准格式符)。如果需要以二进制形式输出整数,必须自行编写函数来实现。

以下示例程序将演示上述所有格式占位符的功能,特别是对比 %d(有符号)和 %x(无符号)在处理正数与负数(-1)时的区别:

#include <stdio.h> int main(void) { // --- 1. 演示一个正数 (65) --- // 65 (十进制) = 101 (八进制) = 41 (十六进制) int positive_num = 65; printf("--- 演示正数 (65) ---\n"); // 使用转义字符 %% 来输出一个百分号 % printf("十进制 (%%d): %d\n", positive_num); printf("八进制 (%%o): %o\n", positive_num); printf("十六进制 (%%x): %x\n", positive_num); printf("十六进制 (%%X): %X\n\n", positive_num); printf("--- 演示带前缀的格式 (65) ---\n"); printf("带前缀八进制 (%%#o): %#o\n", positive_num); printf("带前缀十六进制 (%%#x): %#x\n", positive_num); printf("带前缀十六进制 (%%#X): %#X\n\n", positive_num); // --- 2. 演示负数 (-1) --- // (在 32 位系统上, -1 的十六进制内存表示为 0xFFFFFFFF) int negative_num = -1; printf("--- 演示负数 (-1),验证无符号特性 ---\n"); // %d 是有符号的,它会检查符号位,正确输出 -1 printf("有符号十进制 (%%d): %d\n", negative_num); // %x 是无符号的,它会忽略符号位, // 将内存中 -1 的二进制模式 (全 1) 视为一个巨大的正数打印 printf("无符号十六进制 (%%x): %x\n", negative_num); // %o 也是无符号的 printf("无符号八进制 (%%o): %o\n", negative_num); return 0; }

程序运行结果如下(环境:Win64 + VS Code + MinGW-w64 UCRT):

1.4 进制的混合运算

在 C 语言中,我们可以在源代码中使用多种进制形式来书写整数常量,包括二进制(0b...)、八进制(以 0 开头)、十进制(无前缀)和十六进制(0x...)。

一个核心概念是:这些进制表示方式仅用于源代码中的 “书写形式”,并不会影响整数在计算机内存中的 “实际存储” 和 “处理方式”

运算原理说明:

  1. 统一存储:无论你在代码中写的是 10、012 还是 0xA,当 C 语言编译器读取它们时,都会立即将它们转换为相同的二进制模式存储在内存中(都代表十进制的 10)
  2. 统一运算:在计算机内部,CPU 进行数学运算时,操作的始终是这些统一的二进制数据
  3. 结论:这意味着,不同进制的整数可以在表达式中自由混合使用,编译器会自动处理,完全不会影响运算结果

例如,以下表达式是完全合法的:

int result = 10 + 015 + 0x20;
  1. 10 是十进制整数;
  2. 015 是八进制整数,编译器将其解析为十进制的 13;
  3. 0x20 是十六进制整数,编译器将其解析为十进制的 32;
  4. CPU 实际执行的运算是 10 + 13 + 32,最终 result 的值为 55。

以下示例程序演示了 C 语言如何处理不同进制的混合运算:

#include <stdio.h> int main(void) { // 1. 定义不同进制的整数变量 int decimal_val = 10; // 十进制: 10 int octal_val = 015; // 八进制: 015 (等于十进制 13) int hex_val = 0x20; // 十六进制: 0x20 (等于十进制 32) // C23 标准或编译器扩展 int binary_val = 0b1001; // 二进制: 1001 (等于十进制 9) // 2. 混合运算:将所有值相加 // 预期结果: 10 + 13 + 32 + 9 = 64 int result = decimal_val + octal_val + hex_val + binary_val; // 3. 打印每个变量的十进制值,以证明编译器已正确解析 printf("--- 变量在内存中的十进制值 ---\n"); printf("源码 '10' (十) => %d\n", decimal_val); printf("源码 '015' (八) => %d\n", octal_val); printf("源码 '0x20' (十六) => %d\n", hex_val); printf("源码 '0b1001' (二) => %d\n\n", binary_val); // 4. 打印最终的混合运算结果 printf("--- 混合运算结果 ---\n"); printf("源码: result = 10 + 015 + 0x20 + 0b1001\n"); printf("实际计算: result = %d + %d + %d + %d\n", decimal_val, octal_val, hex_val, binary_val); printf("最终结果 (十进制): %d\n", result); return 0; }

程序运行结果如下(环境:Win64 + VS Code + MinGW-w64 UCRT):


2 常见进制间的相互转换

在第 1 章中,我们了解了 C 语言如何表示不同进制。而在实际编程和学习中,我们还经常需要在这几种进制之间手动进行换算。

本章将聚焦于这些常见进制间的相互转换(主要针对整数),并逐一讲解最核心的几对转换的具体方法。

2.1 二进制 ↔ 十进制

二进制十进制

规则:采用按权展开法,从二进制数的最低位(最右侧)开始,将其定为第 0 位,每一位的值乘以 2 的对应位数,然后将所有乘积累加求和

示例:将二进制数 1011 转换为十进制数,如下图所示:

十进制二进制

规则:采用除基取余,逆序排列法,将十进制数反复除以基数 2,记录每次的余数,直到商为 0。然后将所有余数按逆序(即从下往上)排列,即可得到对应的二进制表示

示例:将十进制数 56 转换为二进制数,如下图所示:

2.2 八进制 ↔ 十进制

八进制十进制

规则:采用按权展开法,从八进制数的最低位(最右侧)开始(第 0 位),每一位的值乘以 8 的对应位数,然后累加求和

示例:将八进制数 101 转换为十进制数,如下图所示:

十进制八进制

规则:采用除基取余,逆序排列法,将十进制数反复除以基数 8,记录每次的余数,直到商为 0,然后将余数按逆序排列

示例:将十进制数 156 转换为八进制数,如下图所示:

2.3 十六进制 ↔ 十进制

十六进制十进制

规则:采用按权展开法,从十六进制数的最低位(最右侧)开始(第 0 位),每一位的值(A=10,B=11 ... F=15)乘以 16 的对应位数,然后累加求和

示例:将十六进制数 0x34A 转换为十进制数,如下图所示:

十进制 →十六进制

规则:采用除基取余,逆序排列法,将十进制数反复除以基数 16,记录余数,直到商为 0。若余数大于 9,需用对应的十六进制字母(A=10,B=11 ... F=15)表示。最后将余数按逆序排列

示例:将十进制数 356 转换为十六进制数,如下图所示:

2.4 二进制 ↔ 八进制

由于 8 = 2^3,所以一位八进制数恰好对应三位二进制数

二进制八进制

规则:采用取三合一法,从二进制数的最低位(右端)起,将二进制数每三位一组(最高位不足三位时,在左侧补零),然后将每三位二进制数组合转换为对应的八进制数(0 - 7)

示例:将二进制数 1101011 转换为八进制数,如下图所示:

八进制二进制

规则:采用取一分三法,将八进制数的每一位数字,都转换为对应的三位二进制数。最后将所有二进制数组合(可省略最终结果最左侧的 0)

示例:将八进制数 153 转换为二进制数,如下图所示:

2.5 二进制 ↔ 十六进制

由于 16 = 2^4,所以一位十六进制数恰好对应四位二进制数

二进制十六进制

规则:采用取四合一法,从二进制数的最低位(右端)起,将二进制数每四位一组(最高位不足四位时,在左侧补零),然后将每四位二进制数组合转换为对应的十六进制数(0 - F)

示例:将二进制数 1001011 转换为十六进制数,如下图所示:

十六进制二进制

规则:采用取一分四法,将十六进制数的每一位数字(A=10,B=11 ... F=15),都转换为对应的四位二进制数。最后将所有二进制数组合(可省略最终结果最左侧的 0)

示例:将十六进制数 0x23B 转换为二进制数,如下图所示:

💡 提示:利用二进制作为高效 “桥梁”

在进行非二进制与十进制的转换时(例如 八进制 ↔ 十六进制),或者在进行十进制八/十六进制转换时,若觉得 “除基取余法” 计算繁琐,可始终先转换为二进制,再利用分组法(三合一、四合一)快速得到结果

总结:利用二进制作为中转,可以显著降低复杂进制转换的计算量,是一种高效、直观的转换方法。


3 小数与通用进制转换方法

在第 2 章中,我们演示了最常见的几对整数进制转换。

本章我们将深入探究这些转换背后的通用方法(即 “原理”),并在此基础上,将转换规则从整数部分正式扩展到小数领域。

3.1 整数部分的通用转换

其他进制整数 → 十进制整数

  • 规则:按权展开法
  • 方法:将整数的每一位乘以其对应权值(基数的 n 次方),然后求和

十进制整数 → 其他进制整数

  • 规则:除基取余,逆序排列
  • 方法:将十进制数反复除以目标基数(如 2、8、16),记录余数,直至商为 0,最后将余数逆序排列

3.2 小数部分的通用转换

其他进制小数 → 十进制小数

  • 规则:按权展开法(使用负指数)
  • 方法:与整数部分类似,但小数部分的权值从小数点后第一位开始,分别为基数的 -1,-2,-3 ... 次方
  • 示例:将二进制小数 0.101 转换为十进制小数。

十进制小数 → 其他进制小数

  • 规则:乘基取整,顺序排列
  • 方法:
    • 将十进制小数部分乘以目标基数(如 2),取其结果的整数部分
    • 再将剩余的小数部分继续乘以基数,再次取整 ... 以此类推
    • 直到小数部分为 0 或达到所需精度
    • 最后,将取出的所有整数按顺序(从上到下)排列
  • 示例:将十进制小数 0.625 转换为二进制小数。

3.3 转换的注意事项

混合数处理

对于包含整数和小数部分的数(如 12.625),应将整数部分和小数部分拆开,分别使用各自的规则进行转换,最后再将结果合并

无限循环小数

某些十进制小数在转换为其他进制(特别是二进制)时,无法被精确表示,会出现无限循环的情况。这也是 C 语言中浮点数(如 float、double)产生精度误差的根本原因。

精度控制

由于存在无限循环小数,在实际应用中(例如 C 语言 printf 打印小数时),程序无法无限计算下去。

C 语言中用于处理小数的浮点数类型(主要是 float 和 double)都会设定一个固定的精度限制。例如:

  • float类型大约能保留6 - 7有效的十进制数字;
  • double类型大约能保留15 -16 位,超出部分会被截断或舍入。

关于 float、double 以及 C 语言浮点数在内存中如何存储的详细知识,我们将在后续的教程章节中进行深入讲解。目前你只需要了解 “十进制小数(如 0.1)在转换为二进制时可能不精确” 这个结论即可。


4 进制转换工具

在日常开发或技术交流中,我们经常会遇到进制转换的问题。对于较小的数值,建议熟练掌握基本的转换方法,这不仅能提升工作效率,也有助于展现专业素养。而对于较大数值,手动转换容易出错且耗时。此时可以借助工具简化操作。

4.1 计算器程序

Windows 系统自带的计算器是一款实用且便捷的工具,支持二进制、八进制、十进制与十六进制之间的快速转换,操作方法如下所示:

1.打开计算器:按下键盘上的 Win 键,在搜索栏中输入 “计算器”,然后点击打开程序:

2.切换为程序员模式:点击计算器左上角的 “” 菜单图标,选择 “程序员” 模式:

3.输入数据并查看结果:在对应进制的输入框中输入数值,其他进制区域将自动显示转换后的结果:

4.2 在线转换工具

Windows 计算器(程序员模式)虽然方便,但它有两个主要限制:它不支持小数(浮点数)的转换,并且最大只支持到十六进制。

如果你需要处理第 3 章中提到的小数进制转换,或进行如 36 进制等更大范围的进制转换,那么功能更强大的 “在线进制转换工具” 将是更优的选择。其主要优势包括:

  • 功能强大:支持 2 ~ 36 进制之间的任意转换。
  • 支持小数:它们能处理浮点型数据,这对于验证第 3 章的 “乘基取整法”(如 0.1 转二进制)非常有用。
  • 跨平台:无需安装,有浏览器即可使用(对 Mac 和 Linux 用户友好)。

例如,oschina 工具箱(https://tool.oschina.net/hexconvert)就是一个广受好评的在线工具。它不仅支持 2 ~ 36 进制的任意转换,也支持浮点型数据。具体操作界面如下所示: