字符转换

字符转换
苏丙榅1. 为什么我们需要新的转换
在 C++17 之前,C++ 程序员在处理字符和数字转换时经常面临两个痛苦的选择:
C 语言风格函数 (
atoi,strtol,sprintf):类型不安全(容易崩溃),不支持
std::string,功能受限,无法区分错误(比如输入123abc会被当成123处理)。C++ 流 (
std::stringstream/std::to_string):std::stringstream- 极其慢(涉及内存分配、状态设置),代码冗长(写一行转换要三五行代码),不仅影响性能,还容易写错。std::to_string- 可能抛出bad_alloc异常,并且无法控制精度、格式等
C++17 引入了 <charconv> 头文件,提供了一组全新的函数( std::to_chars 和 std::from_chars)。这些函数不仅速度极快(被认为是目前通用 C++ 库中最快的),而且极其方便(不分配内存,不抛异常)。
1 |
|
<charconv> 的优势:
- 无异常:所有错误通过返回码处理
- 高性能:比传统方法快
5-10倍 - 无内存分配:使用栈上缓冲区
- 支持任意字符类型:
char,wchar_t,char16_t,char32_t - 区域设置无关:始终使用
C区域设置,意思是说该库不遵循电脑或操作系统的本地化习惯(如中文、德文等),而是严格按照 C 语言的默认标准来处理数字格式。
在 C++ 中,“C” 区域设置是最基础、最底层的标准格式。这主要影响两个方面:数字的分隔符 和 小数点符号。
小数点符号(最直观的影响)
在某些国家(如德国、法国、部分南美国家),小数点不是写成
.(点),而是写成,(逗号)。- 普通流(受区域影响):
如果电脑区域设置成了德国,那么当使用传统的scanf或std::cin读取数字3,14时,它能识别出这是3.14。 <charconv>(无区域影响):无论电脑设置在哪个国家,<charconv>只认.(英文的点)。- 它能解析
3.14-> 成功 (3.14) - 它遇到
3,14-> 失败 / 错误。
- 它能解析
- 普通流(受区域影响):
千位分隔符(千分位)
- 普通流:在很多地区,数字写作
1,000,000(美式)或者1.000.000(欧式)。传统的库如果在本地模式下,可能会尝试去读取这些逗号或点。 <charconv>:它拒绝处理任何千位分隔符。1000000-> 成功。1,000,000-> 失败。因为它只认识纯数字和唯一的那个小数点。
- 普通流:在很多地区,数字写作
2. 数字转字符串 - std::to_chars
std::to_chars函数用于将整数或浮点数转换为字符序列。
1 | std::to_chars_result to_chars(char* first, char* last, integer-type value, int base = 10); |
参数详解:
char* first: 缓冲区的起始指针。char* last: 缓冲区的结束指针(不包含 last,即[first, last)半开区间)。Value: 要转换的数值(支持int,double,float等)。base(仅整数有效): 进制,默认10,合法范围是 2 到 36(包含 2 和 36)。fmt(仅浮点数):格式控制,是一个枚举类:std::chars_format::scientific: 科学计数法(如1.23e+5)。std::chars_format::fixed: 定点格式(如123.456)。std::chars_format::general: 自动选择(默认),在科学计数法和普通计数法之间选择最短的一种。std::chars_format::hex: 十六进制浮点数(如1.23p+5)。
precision(仅浮点数): 要使用的浮点精度。-1,表示使用该格式下能表示该数字的最短精度。
返回值 to_chars_result,其定义如下:
1 | struct to_chars_result |
- 如果成功:
ec == std::errc{},ptr指向写入内容的末尾。 - 如果失败(缓冲区太小):
ec == std::errc::value_too_large。
std::to_chars_result 是 C++17 引入的一个结构体,用于返回 std::to_chars 函数的执行状态和结果。
它的设计非常轻量级,不包含任何虚函数或复杂的内存管理,仅仅包含两个成员变量。下面是对这两个成员及其逻辑的详细解析:
char* ptr:含义:这是一个指针,指向写入字符串结束位置的下一个字符。
作用:
- 它并不像 C 语言字符串那样以
\0结尾,而是通过这个指针告诉你写到了哪里。 - 你可以通过计算
ptr - first来获得实际生成的字符串长度。 - 它指向的位置是安全的(即
ptr <= last),你可以在这个位置手动添加'\0'来结束字符串。
- 它并不像 C 语言字符串那样以
类比:它类似于标准库算法中的“尾后迭代器”。
std::errc ec:含义:这是一个枚举类型(
std::errc),用于存储错误码。可能的取值:
std::errc()(即默认值0):表示成功。转换顺利完成。std::errc::value_too_large:表示失败。这是最常见的错误,意味着转换后的数字太长,超出了提供的缓冲区范围[first, last)。
注意:不像其他 C++ 函数会抛出异常,
std::to_chars不会抛出异常,所有的错误信息都通过这个ec传递。
示例代码:
1 |
|
3. 字符串转数字 - std::from_chars
std::from_chars函数的作用是把字符序列解析为整数或浮点数。函数原型如下:
1 | std::from_chars_result from_chars(const char* first, const char* last, |
参数详解:
first: 字符串起始位置。last: 字符串结束位置。base: 进制(默认 10),合法范围是 2 到 36(包含 2 和 36)。value: 引用类型,用来存放解析成功后的结果。fmt(仅浮点数): 告诉函数期望什么样的格式。注意:如果输入是1.23e4而你设置fmt = fixed,解析会失败。如果解析整数,此参数通常不需要。
返回值 from_chars_result,其原型如下:
1 | struct from_chars_result |
std::from_chars_result 是 C++17 引入的 std::from_chars 函数的返回类型。它的作用是告诉调用者:字符串解析在哪里停止了,以及是否发生了错误。与 to_chars_result 类似,它也是一个轻量级的结构体,旨在提供最高性能的数值转换。下面是对其两个成员变量的详细解析:
const char* ptr:- 含义:指向解析操作停止位置的那个字符。
- 作用:
- 如果解析完全成功,它通常指向字符串中最后一个数字后面的那个字符(可能是空格、逗号、字符串结束符
\0或其他非数字字符)。 - 如果解析失败(例如第一个字符就不是数字),它会等于传入的起始指针
first(即没有移动任何位置)。
- 如果解析完全成功,它通常指向字符串中最后一个数字后面的那个字符(可能是空格、逗号、字符串结束符
- 应用场景:常用于解析格式化的数据流(例如
"123, 456"),可以利用ptr快速跳过已处理的数字,直接定位到下一个分隔符。
std::errc ec- 含义:错误码,指示解析的结果状态。
- 可能的取值:
std::errc()(默认值 0):成功。成功解析出了数值。std::errc::invalid_argument:无效参数。无法解析任何数字。例如字符串是"abc",无法转换为数字。此时,输出的value参数不会被修改。std::errc::result_out_of_range:结果超出范围。字符串确实包含数字,但数值太大,超出了目标类型的存储范围。例如将"99999999999999999999"解析为int。此时,输出的value参数不会被修改。
from_chars 最大的优点是严格。它不喜欢多余的垃圾字符。
1 |
|
4. 注意事项
尽管std::to_chars 和 std::from_chars这两个函数很强大,但使用时也有不少门槛:
不添加 null 终止符 (
\0)std::to_chars不会 在输出末尾自动加\0。如果想要把它当 C 字符串用,必须手动加:1
2
3
4
5auto res = std::to_chars(buf, buf+10, 42);
if (res.ec == std::errc{})
{
*res.ptr = '\0'; // 必须手动!
}缓冲区溢出由使用者负责
使用者必须确保提供的缓冲区足够大。对于 32 位整数,
char buf[12]足够;但对于double,可能需要几十个字节。如果给的空间不够,函数会返回错误值,不会越界写入,但需要检查返回值。编译器支持差异
C++17 标准规定了接口,但早期的编译器(如
GCC 8, MSVC 19.14)只实现了整数版本。浮点数版本的std::to_chars是后来才补全的(GCC 9+, Clang 7+, MSVC 19.2x+)。如果编译浮点数版本报错,请检查编译器版本。格式限制
std::from_chars目前不支持像%03d这样的填充格式。它只做纯数值转换。如果需要0012这种格式,请自己补0。
如果我们需要编写对性能敏感的底层 C++ 代码,请务必抛弃 std::stringstream 和 sprintf,拥抱 C++17 的 std::to_chars 和std::from_chars。

















