C++/charconv
< C++
C++17的标准库头文件charconv
添加了两个函数std::from_chars
和std::to_chars
作为底层、高性能的API。
在C++17之前,C/C++ 提供了几种字符串转换选项:
sprintf
/snprintf
sscanf
atol
及类似函数strtol
及类似函数strstream
stringstream
from_string
stoi
及类似函数
C++17提供了函数std::from_chars
和std::to_chars
,具有以下特点:
- 不会抛出异常,而是通过返回的
from_chars_result
结构体来报告错误,与std::stoi
等可能抛出异常的函数形成对比 - 不会在内部执行动态内存分配(不会调用
new
/malloc
),直接在给定的内存区域上操作,与std::stringstream
形成对比,后者需要在内部分配缓冲区 - 不支持本地化设置(locale),比如不同地区的数字分隔符(如欧洲用 "," 而不是 "." 作为小数点)
- 内存安全。不进行动态内存分配;使用显式的字符范围边界
(first, last)
- 防止缓冲区溢出; - 严格的错误检查 - 通过
from_chars_result
返回转换结果和错误信息,提供关于转换结果的额外信息 - 作为API,偏底层和基础,直接使用可能不够友好(如需要手动处理指针和错误码),但可以封装它,创建一个更易用的高级接口。
std::from_chars
编辑整数类型:
std::from_chars_result from_chars(const char* first,
const char* last,
TYPE &value,
int base = 10);
其中 TYPE
扩展到所有可用的有符号和无符号整数类型以及char
。base
可以是从2到36的数字。
浮点类型:
std::from_chars_result from_chars(const char* first,
const char* last,
FLOAT_TYPE& value,
std::chars_format fmt = std::chars_format::general);
FLOAT_TYPE
扩展为float
、double
或 long double
。chars_format
是一个枚举,包含以下值:scientific
、fixed
、hex
和 general
(fixed
和scientific
的组合)。chars_format::general
支持两种浮点数格式,1. 固定小数点格式(fixed
),如 "123.45";2. 科学计数法格式(scientific
),如 "1.2345e+2",所以一个字符串可以用任意一种格式表示浮点数,from_chars
都能正确解析。
std::from_chars
的返回值都是 from_chars_result
,包含有关转换过程的重要信息:
struct from_chars_result {
const char* ptr;
std::errc ec;
};
返回条件 | from_chars_result 状态
|
---|---|
成功 | ptr 指向第一个不匹配模式的字符,或者如果所有字符都匹配,则等于 last ,且ec为 |
无效转换 | ptr 等于first ,且ec 等于std::errc::invalid_argument 。value 未修改。
|
超出范围 | 数字太大,无法放入值类型。ec 等于 std::errc::result_out_of_range ,且 ptr 指向第一个不匹配模式的字符。value 未修改。
|
例子
编辑整数类型
#include <charconv> // from_char, to_char
#include <string>
#include <iostream>
int main() {
const std::string str { "12345678901234" };
int value = 0;
const auto res = std::from_chars(str.data(),
str.data() + str.size(),
value);
if (res.ec == std::errc())
{
std::cout << "value: " << value
<< ", distance: " << res.ptr - str.data() << '\n';
}
else if (res.ec == std::errc::invalid_argument)
{
std::cout << "invalid argument!\n";
}
else if (res.ec == std::errc::result_out_of_range)
{
std::cout << "out of range! res.ptr distance: "
<< res.ptr - str.data() << '\n';
}
}
浮点数版本:
#include <charconv> // from_char, to_char
#include <string>
#include <iostream>
int main() {
const std::string str { "16.78" };
double value = 0;
const auto format = std::chars_format::general;
const auto res = std::from_chars(str.data(),
str.data() + str.size(),
value,
format);
if (res.ec == std::errc())
{
std::cout << "value: " << value
<< ", distance: " << res.ptr - str.data() << '\n';
}
else if (res.ec == std::errc::invalid_argument)
{
std::cout << "invalid argument!\n";
}
else if (res.ec == std::errc::result_out_of_range)
{
std::cout << "out of range! res.ptr distance: "
<< res.ptr - str.data() << '\n';
}
}
std::to_chars
编辑 // 初等数值输出转换
struct to_chars_result { // 独立
char* ptr;
errc ec;
friend bool operator==(const to_chars_result&, const to_chars_result&) = default;
constexpr explicit operator bool() const noexcept { return ec == errc{}; }
};
to_chars_result to_chars(char* first, char* last, // 独立
/* integer-type */ value, int base = 10);
to_chars_result to_chars(char* first, char* last, // 独立
bool value, int base = 10) = delete;
to_chars_result to_chars(char* first, char* last, // 独立
/* floating-point-type */ value);
to_chars_result to_chars(char* first, char* last, // 独立
/* floating-point-type */ value, chars_format fmt);
to_chars_result to_chars(char* first, char* last, // 独立
/* floating-point-type */ value,
例子
编辑int test_charconv()
{
constexpr int val_int{ 88 };
constexpr float val_float{ 66.66f };
constexpr double val_double{ 88.8888 };
const std::string str1{ "123" }, str2{ "456.789" };
// std::to_chars: converts an integer or floating-point value to a character sequence
std::array<char, 10> buffer;
// 注意:std::errc没有到bool的隐式转换,所以你不能像下面这样检查: if (res.ec) {} or if (!res.ec) {}
if (auto [ptr, ec] = std::to_chars(buffer.data(), buffer.data() + buffer.size(), val_int); ec == std::errc()) {
std::cout << "str:" << std::string_view(buffer.data(), ptr - buffer.data()) << "\n"; // str:88
*ptr = '\0'; // 保证结尾有一个空字符
std::cout << "buffer:" << buffer.data() << "\n"; // buffer:88
} else {
std::cerr << "fail to call to_char:" << std::make_error_code(ec).message() << "\n";
return -1;
}
buffer.fill(0);
if (auto [ptr, ec] = std::to_chars(buffer.data(), buffer.data() + buffer.size(), val_float); ec == std::errc()) {
std::cout << "str:" << std::string_view(buffer.data(), ptr - buffer.data()) << "\n"; // str:66.66
*ptr = '\0'; // 保证结尾有一个空字符
std::cout << "buffer:" << buffer.data() << "\n"; // buffer:66.66
} else {
std::cerr << "fail to call to_char:" << std::make_error_code(ec).message() << "\n";
return -1;
}
buffer.fill(0);
if (auto [ptr, ec] = std::to_chars(buffer.data(), buffer.data() + buffer.size(), val_double); ec == std::errc()) {
std::cout << "str:" << std::string_view(buffer.data(), ptr - buffer.data()) << "\n"; // str:88.8888
} else {
std::cerr << "fail to call to_char:" << std::make_error_code(ec).message() << "\n";
return -1;
}
// std::from_chars: converts a character sequence to an integer or floating-point value
int val1{ 0 };
auto [ptr, ec] = std::from_chars(str1.data(), str1.data() + str1.size(), val1);
if (ec == std::errc()) // 注意:std::errc没有到bool的隐式转换,所以你不能像下面这样检查: if (res.ec) {} or if (!res.ec) {}
std::cout << "val1:" << val1 << "\n"; // val1:123
else if (ec == std::errc::invalid_argument)
std::cerr << "Error: this is not a number: " << str1 << "\n";
else if (ec == std::errc::result_out_of_range)
std::cerr << "Error: this number is larger than an int: " << str1 << "\n";
else
std::cerr << "fail to call from_chars:" << std::make_error_code(ec).message() << "\n";
float val2{ 0.f };
auto [ptr2, ec2] = std::from_chars(str2.data(), str2.data() + str2.size(), val2);
if (ec2 == std::errc())
std::cout << "val2:" << val2 << "\n"; // val2:456.789
else if (ec2 == std::errc::invalid_argument)
std::cerr << "Error: this is not a number: " << str2 << "\n";
else if (ec2 == std::errc::result_out_of_range)
std::cerr << "Error: this number is larger than an float: " << str2 << "\n";
else
std::cerr << "fail to call from_chars:" << std::make_error_code(ec2).message() << "\n";
// std::stringstream: int/float/double->std::string
std::stringstream ss1;
ss1 << val_int << "," << val_float << "," << val_double;
std::string str = ss1.str();
std::cout << "str:" << str << "\n"; // str:88,66.66,88.8888
std::stringstream ss2;
ss2 << str1 << "," << str2;
str = ss2.str();
std::cout << "str:" << str << "\n"; // str:123,456.789
// std::to_string: int/float/double->std::string
str = std::to_string(val_int);
std::cout << "str:" << str << "\n"; // str:88
str = std::to_string(val_float);
std::cout << "str:" << str << "\n"; // str:66.660004
str = std::to_string(val_double);
std::cout << "str:" << str << "\n"; // str:88.888800
// std::stoi, std::stol, std::stoll: std::string->int/long/long long
auto ret1 = std::stoi(str1);
std::cout << "ret1:" << ret1 << "\n"; // ret:123
// std::stoul, std::stoull: std::string->unsigned long/unsigned long long
auto ret2 = std::stoul(str1);
std::cout << "ret2:" << ret2 << "\n"; // ret2:123
// std::stof, std::stod, std::stold: std::string->float/double/long double
auto ret3 = std::stof(str2);
std::cout << "ret3:" << ret3 << "\n"; // ret3:456.789
return 0;
}