跳至內容

C++/charconv

維基教科書,自由的教學讀本
< C++

C++17的標準庫頭文件charconv添加了兩個函數std::from_charsstd::to_chars作為底層、高性能的API。

在C++17之前,C/C++ 提供了幾種字符串轉換選項:

  • 格式化讀寫字符數組
    • sprintf / snprintf
    • sscanf
  • C風格的簡化版本的atol及類似函數
  • C風格的strtol及類似函數
  • 基於流的解析,已棄用strstream
  • 基於流的解析stringstream
  • C++風格的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;
}