news 2026/11/5 2:11:58

C++教程之进制转换的实现方法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++教程之进制转换的实现方法

前言


进制转换是编程里出现频率很高的一类小任务:调试时把内存字节打印成十六进制、把权限位打印成八进制、把算法中间状态打印成二进制、把短链接的 ID 编码成 36 进制。C++ 提供了好几套工具,从 C 风格的printf到流操纵符,再到 C++17 的std::to_chars,还有手写的逐位除法。工具多,坑也多。


常见的误解有三个。第一,以为std::hex只影响紧接着的那一次输出。它是流的状态标志,一旦设置就会一直生效,后面所有整数都按十六进制打印,直到你显式改回std::dec。第二,以为std::stoi能自动识别任意进制。它默认按十进制解析,遇到"FF"会抛std::invalid_argument;要按十六进制解析必须传第三个参数16,传0才是「按前缀自动判断」。第三,以为负数转成二进制就是「符号位加绝对值」。C++ 标准不规定有符号整数的内部表示(C++20 起才要求二进制补码),可移植代码不应依赖补码细节。


本文先回顾进位制的表示法,再依次介绍标准库的四种转换途径,最后给出一套手写的任意进制转换函数,并说明各自的适用边界。示例以 C++17 为基准。


一、进位制的基本表示


一个b进制数由若干位组成,第i位(从右往左、从 0 开始)的权值是b的i次方。十进制255等于二进制11111111、八进制377、十六进制FF。进制b的合法数字是0到b-1;超过 9 的数字用字母表示,10用A,11用B,依此类推。常用进制的范围是 2 到 36,因为 10 个数字加 26 个字母刚好 36 个符号。


C++ 源码里可以直接写几种进制字面量:十进制255、八进制0377(前导0)、十六进制0xFF。C++14 起还支持二进制字面量0b11111111和数字分隔符1'000'000。注意八进制的前导0是个历史遗留陷阱:int x = 010;的值是 8,不是 10。




进制前缀/写法255 的表示常用场景



20b(C++14)0b11111111位运算、标志位

8前导00377Unix 文件权限

10无255日常数值

160x0xFF内存、颜色、哈希



二、标准库的四种途径


1. 流操纵符:std::hex / std::oct / std::dec


<ios>提供的操纵符修改流的整型基数字段。它既能用于输出,也能用于输入。


// 适用于 C++17
#include <iomanip>
#include <iostream>
#include <sstream>

int main() {
std::cout << std::hex << std::uppercase << 255 << '\n'; // FF
std::cout << std::oct << 255 << '\n'; // 377
std::cout << std::dec << 255 << '\n'; // 255,必须显式改回

std::istringstream in("ff");
int value = 0;
in >> std::hex >> value; // value == 255
return 0;
}

关键点是粘性:std::hex设置后一直有效,std::uppercase也是。如果函数内部设置了却没恢复,调用方后续的输出会莫名其妙变成十六进制。稳妥的写法是保存旧标志再恢复:


// 适用于 C++17
#include <ios>
#include <iostream>
#include <sstream>

std::string to_hex_stream(unsigned value) {
std::ostringstream os;
os.setf(std::ios::hex, std::ios::basefield); // 只改基数字段
os << value;
return os.str();
}

这里用setf(mask, field)而不是os << std::hex,是因为前者只改动基数字段,不会顺带影响其他格式标志。对独立的ostringstream来说差别不大,但对std::cout这类共享流就很关键。


2. std::stoi / stol / stoll:字符串转整数


<string>里的这组函数接受(字符串, 起始位置指针, 进制)。进制为0时按 C 语言规则自动判断:0x开头按十六进制,0开头按八进制,否则按十进制。


// 适用于 C++17
#include <iostream>
#include <string>

int main() {
std::size_t pos = 0;
int a = std::stoi("1010", nullptr, 2); // 10
int b = std::stoi("0xFF", nullptr, 16); // 255,strtol 允许 0x 前缀
int c = std::stoi("0xFF", nullptr, 0); // 255,自动判断
int d = std::stoi("42abc", &pos, 10); // 42,pos == 2
std::cout << a << ' ' << b << ' ' << c << ' ' << d << ' ' << pos << '\n';
return 0;
}

两个行为要记住:解析会在第一个非法字符处停止,不报错,只通过pos告诉你停在哪里;如果开头就没有合法数字,抛std::invalid_argument;如果数值超出返回类型的范围,抛std::out_of_range。因此解析用户输入时必须捕获异常,并检查pos是否覆盖了整个字符串,否则"12abc"会被当成合法的 12。


3. std::bitset:二进制展示


<bitset>的to_string()输出固定宽度的二进制串,宽度由模板参数决定,不足补前导零。它适合展示位标志,不适合任意进制。


// 适用于 C++17
#include <bitset>
#include <iostream>
#include <string>

int main() {
std::bitset<8> bits(5);
std::cout << bits.to_string() << '\n'; // 00000101

std::bitset<8> parsed(std::string("11110000"));
std::cout << parsed.to_ulong() << '\n'; // 240
return 0;
}

std::bitset的构造函数接受unsigned long long或std::string。字符串里出现非0/1字符会抛std::invalid_argument,长度超过模板参数会抛std::out_of_range。位宽在编译期固定,所以它无法处理运行期才知道宽度的数。


4. std::to_chars / std::from_chars:C++17 的无分配转换


<charconv>提供了一对底层转换函数。整数版本支持 2 到 36 进制,不依赖 locale,不分配内存,通过返回的std::errc报告错误。


// 适用于 C++17
#include <array>
#include <charconv>
#include <iostream>
#include <string>
#include <system_error>

int main() {
std::array<char, 32> buf{};
auto [ptr, ec] = std::to_chars(buf.data(), buf.data() + buf.size(), 255, 16);
if (ec == std::errc{}) {
std::string s(buf.data(), ptr); // "ff",小写
std::cout << s << '\n';
}

int value = 0;
const char* text = "777";
auto [p2, ec2] = std::from_chars(text, text + 3, value, 8);
if (ec2 == std::errc{}) {
std::cout << value << '\n'; // 511
}
return 0;
}

to_chars写入的是不以空字符结尾的字符序列,必须用返回的ptr确定长度。缓冲区不够时返回std::errc::value_too_large,此时不写入任何内容。from_chars同样不做越界检查之外的校验,解析失败返回std::errc::invalid_argument,溢出返回std::errc::result_out_of_range。整数版本的to_chars对大于 10 的进制输出小写字母,没有大写选项,需要大写就自己转换。


三、手写任意进制转换


标准库覆盖了常见需求,但有时需要完全控制输出(比如固定宽度、补零、大写、自定义字符表)。核心算法只有两步:转出用「除基取余、逆序排列」,转入用「乘基累加」。


// 适用于 C++17
#include <algorithm>
#include <limits>
#include <stdexcept>
#include <string>

std::string to_base(unsigned long long value, int base) {
if (base < 2 || base > 36) {
throw std::invalid_argument("base must be in [2, 36]");
}
static const char kDigits[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
if (value == 0) {
return "0";
}
std::string out;
const auto ub = static_cast<unsigned long long>(base);
while (value != 0) {
out.push_back(kDigits[value % ub]);
value /= ub;
}
std::reverse(out.begin(), out.end());
return out;
}

unsigned long long from_base(const std::string& text, int base) {
if (base < 2 || base > 36) {
throw std::invalid_argument("base must be in [2, 36]");
}
if (text.empty()) {
throw std::invalid_argument("empty input");
}
const auto ub = static_cast<unsigned long long>(base);
const auto max = std::numeric_limits<unsigned long long>::max();
unsigned long long result = 0;
for (char c : text) {
int digit = -1;
if (c >= '0' && c <= '9') {
digit = c - '0';
} else if (c >= 'a' && c <= 'z') {
digit = c - 'a' + 10;
} else if (c >= 'A' && c <= 'Z') {
digit = c - 'A' + 10;
}
if (digit < 0 || digit >= base) {
throw std::invalid_argument("invalid digit for base");
}
const auto d = static_cast<unsigned long long>(digit);
if (result > (max - d) / ub) {
throw std::overflow_error("value does not fit in 64 bits");
}
result = result * ub + d;
}
return result;
}

几个设计取舍值得说明。返回类型用unsigned long long,是为了让「溢出」有一个明确的边界;如果业务需要负数,可以额外维护符号位,把绝对值交给这两个函数。溢出检查写成result > (max - d) / ub,而不是先乘后判断——先乘就已经溢出了,而无符号溢出虽然回绕有定义,但结果已经错了。字符表放在函数内的static const char[],避免每次调用构造字符串。


对固定宽度的需求(比如把每个字节打印成两位十六进制),在转换结果前补零即可:


// 适用于 C++17
#include <iomanip>
#include <sstream>
#include <string>

std::string byte_to_hex(unsigned char byte) {
std::ostringstream os;
os << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<unsigned>(byte);
return os.str();
}

这里必须先转成unsigned。如果把unsigned char直接交给流,它会按字符类型输出(打印成字母或不可见字符),而不是数字。


常见坑点



  1. ❌ 用完std::hex不恢复,导致后续所有输出都变成十六进制。


✅ 显式写std::dec恢复;或在独立的ostringstream里做格式化。



  1. ❌std::stoi("FF")期望得到 255。


✅ 必须传进制:std::stoi("FF", nullptr, 16)。



  1. ❌ 解析用户输入时只捕获异常,不检查pos,"12abc"被当成 12。


✅ 检查pos == text.size(),或自行拒绝含多余字符的输入。



  1. ❌char c = 0xFF;之后直接拿它做数值运算。


✅char是否有符号是实现定义行为;涉及字节数值时用unsigned char或std::uint8_t。



  1. ❌ 手写转换时先乘后判溢出:result = result * base + d;再比较。


✅ 先判result > (max - d) / base,再执行乘加。



  1. ❌ 用to_chars后把缓冲区当 C 字符串,直接strlen或构造std::string(buf.data())。


✅ 结果不以空字符结尾,必须用返回的ptr计算长度。



  1. ❌ 用std::stoi解析超过int范围的大十六进制数。


✅ 换std::stoll/std::stoull,或直接用std::from_chars并检查错误码。



  1. ❌ 认为负数转二进制就是「符号位加绝对值」,并依赖这个结果做位运算。


✅ 标准不规定有符号整数的内部表示;需要位模式时先转成对应的无符号类型。


总结




方式头文件支持进制是否分配内存适用场景



流操纵符<ios>/<iomanip>8/10/16可能快速输出、调试打印

std::stoi家族<string>2~36可能解析配置、命令行

std::bitset<bitset>2是固定宽度位展示

std::to_chars<charconv>2~36否高性能、无异常转换

手写除法无任意是自定义字符表、固定宽度



日常需求优先用标准库:输出调试信息用流操纵符,解析输入用stoi家族并检查异常与pos,追求无分配和高性能用to_chars/from_chars。只有在需要完全控制格式(补零、大写、自定义字符集、任意进制)时,才值得手写那两个函数。无论用哪种方式,都要记住三件事:进制范围要校验、溢出要检查、流的状态要恢复。




版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/5 2:10:48

稀疏终点奖励多智能体PPO:从观测建模到稳定到达实战

简介&#xff1a;面向腾讯开悟赛题与深度强化学习实践者的「重返秘境」模型包&#xff0c;聚焦“仅到终点”这一目标&#xff0c;提供可参考的DQN训练与推理实现&#xff0c;适合想理解智能体决策流程、复现实验结果或对照改进的中高级学习者。压缩包共56个文件、约4.69MB&…

作者头像 李华
网站建设 2026/11/5 2:06:28

dpdk实现tcp滑动窗口-阶段1

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/5 2:06:28

3个实力派降AI率软件,让你的论文轻松过关AIGC检测[必看]

最近后台被不少同学刷屏&#xff0c;说论文明明是自己一字一句敲出来的&#xff0c;就用AI帮忙理了理框架、捋了捋逻辑&#xff0c;结果提交前一查&#xff0c;AIGC检测率直接冲到30%以上&#xff0c;整个人都懵了。这真不是个例&#xff0c;现在各大高校和查重平台陆续上线AI识…

作者头像 李华
网站建设 2026/11/5 2:06:22

我用 WorkBuddy + Lexmount 整理招聘信息:从网页读取到岗位对照表

三个名字里都带着“AI”的岗位&#xff0c;放到一起看&#xff0c;做的事却差得挺远&#xff1a;一个偏海外增长&#xff0c;一个做企业里的 Agent 平台&#xff0c;一个做游戏中的 AI 模块。 这次我用 WorkBuddy 搭配 Lexmount 云浏览器&#xff0c;把腾讯招聘官网的三个深圳…

作者头像 李华
网站建设 2026/11/5 2:06:22

机器人很热闹,但乌镇真正的主角没上台

《机器人很热闹&#xff0c;但乌镇真正的主角没上台》 ——真正改变生产力的&#xff0c;不是会打拳的机器&#xff0c;而是把想法兑换成现实的速度乌镇今年最红的展品&#xff0c;是一位“拳击手”。它出拳、侧闪、一记高踢&#xff0c;落地还能扎稳马步&#xff0c;引来一片惊…

作者头像 李华