C++17的标准库头文件charconv添加了两个函数std::from_charsstd::to_chars作为底层、高性能的API。

在C++17之前,C/C++ 提供了几种字符串转换选项:

  • sprintf / snprintf
  • sscanf
  • atol及类似函数
  • strtol及类似函数
  • strstream
  • stringstream
  • from_string
  • stoi及类似函数

C++17提供了函数std::from_charsstd::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扩展到所有可用的有符号和无符号整数类型以及charbase可以是从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扩展为floatdoublelong doublechars_format是一个枚举,包含以下值:scientificfixedhexgeneralfixedscientific的组合)。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为value-initialized,即 std::errc(),也就是没有错误的状态。
无效转换 ptr等于first,且ec等于std::errc::invalid_argumentvalue未修改。
超出范围 数字太大,无法放入值类型。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;
}