字符串转数字

std::strtol & std::strtoll & std::strtoul & std::strtoull

函数原型

#include <cstdlib>

long strtol( const char *str, char **str_end, int base );
long long strtoll( const char *str, char **str_end, int base );

unsigned long strtoul( const char *str, char **str_end, int base );
unsigned long long strtoull( const char *str, char **str_end, int base );
#include <cwchar>

long wcstol( const wchar_t* str, wchar_t** str_end, int base );
long long wcstoll( const wchar_t* str, wchar_t** str_end, int base );

unsigned long wcstoul( const wchar_t* str, wchar_t** str_end, int base );
unsigned long long wcstoull( const wchar_t* str, wchar_t** str_end, int base );

说明

str 指向的字符串转换为整数

跳过任何空白字符,直到找到第一个非空白字符,然后获取尽可能多的有效的整数字符(基数为 base)。有效整数字符定义如下:

  • 加号(+)或减号(-)
  • 前置 0 表示 base8(八进制)
  • 前置 0x0X,表示 base16(十六进制)
  • 一系列的“数字”(对于 base 大于 10 的还包括字母)

base 的有效值为 {0,2,3,…,36}。二进制的有效数字为 {0, 1},三进制的有效数字为 {0, 1, 2},以此类推。对于 base 大于 10 的情况,有效“数字”包括字母,11 进制包括 A(a)12 进制包括 B(b),…,36 进制包括 Z(z)

如果 base0,将自动检查进制。如果前缀是 0,则为 8 进制,如果前缀为 0x0X,则为 16 进制,其他情况为 10 进制。

str_end 指向需要处理的字符的结尾的下一个字符,如果为空,则忽略。

如果 str 为空或者不满足转换要求,该函数将不会进行转换。

返回值

  • 如果成功,则直接返回转换后的数字类型数值
  • 如果返回值超出类型的范围(out of range),将会产生返回错误,设置 errnoERAGE,并返回对应返回类型的最大或最小值

    • longLONG_MAX, LONG_MIN
    • long longLLONG_MAX, LLONG_MIN
    • unsigned long: ULONG_MAX
    • unsigned long long: ULLONG_MAX
  • 如果不能执行转换,则返回 0

代码例子

#include <cerrno>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <string>

// 打印值
template<typename T>
void print(const std::string &str, const T &value) {
std::string tmp = "std::strtol(\"" + str + "\") ";
std::cout << std::setw(30) << std::left << tmp << "= [" << value << "]";
}

void test(const std::string &str) {
errno = 0;

char *p_end{nullptr};
print(str, std::strtol(str.c_str(), &p_end, 10));
std::cout << ", p_end point to: [" << std::string(p_end) << "]";

if (errno == ERANGE) {
std::cout << ", [out of range]";
}

std::cout << '\n';
}

int main() {
test(" 12345");
test("12345 ");
test(" 12345 ");
test("12345.abc");
test("abc 12345");
test("12345123451");
test("-12345123451");
}
代码输出结果
std::strtol("    12345")      = [12345], p_end point to: []
std::strtol("12345 ") = [12345], p_end point to: [ ]
std::strtol(" 12345 ") = [12345], p_end point to: [ ]
std::strtol("12345.abc") = [12345], p_end point to: [.abc]
std::strtol("abc 12345") = [0], p_end point to: [abc 12345]
std::strtol("12345123451") = [2147483647], p_end point to: [], [out of range]
std::strtol("-12345123451") = [-2147483648], p_end point to: [], [out of range]

std::strtof & std::strtod & std::strtold

函数原型

#include <cstdlib>

float strtof( const char* str, char** str_end );
double strtod( const char* str, char** str_end );
long double strtold( const char* str, char** str_end );
#include <cwchar>

float wcstof( const wchar_t* str, wchar_t** str_end );
double wcstod( const wchar_t* str, wchar_t** str_end );
long double wcstold( const wchar_t* str, wchar_t** str_end );

说明

str 指向的字符串转换为浮点数

跳过任何空白字符,直到找到第一个非空白字符,然后获取尽可能多的有效的浮点数字符。浮点数字符定义如下:

  • 十进制浮点表达式

    • 加号(+)或减号(-)
    • 可选包含小数点字符的十进制数字的非空序列
    • eE 后跟可选的减号(-)或加号(+)以及非空的十进制数字序列
  • 十六进制浮点表达式

    • 加号(+)或减号(-)
    • 0x0X
    • 可选包含小数点字符的十六进制数字的非空序列
    • pP 后跟可选的减号(-)或加号(+)以及非空的十进制数字序列
  • 无穷大表达式

    • 加号(+)或减号(-)
    • INFINFINITY(忽略大小写)
  • 非数字表达式

    • 加号(+)或减号(-)
    • NANNAN(char_sequence) (NAN 忽略大小写),char_sequence 只能包含数字、拉丁字母和下划线

str_end 指向需要处理的字符的结尾的下一个字符,如果为空,则忽略

16 进制浮点数也有指数表达方式。其指数表达形式和 10 进制的指数表达形式相似,只是将 e 换为了 p。但是意思略有不同。

对于 16 进制浮点数指数,xPn 表示 x 的 16 进制数乘以 $2^n$

返回值

  • 如果成功,则直接返回转换后的数字类型数值
  • 如果返回值超出类型的范围(out of range),将会产生返回错误,设置 errnoERAGE,并返回 HUGE_VAL, HUGE_VALFHUGE_VALL

    • longLONG_MAX, LONG_MIN
    • long longLLONG_MAX, LLONG_MIN
    • unsigned long: ULONG_MAX
    • unsigned long long: ULLONG_MAX
  • 如果不能执行转换,则返回 0

代码例子

#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <string>

// 打印值
template<typename T>
void print(const std::string &str, const T &value) {
std::string tmp = "std::strtof(\"" + str + "\") ";
std::cout << std::setw(30) << std::left << tmp << "= [" << value << "]";
}

void test(const std::string &str) {
errno = 0;

char *p_end{nullptr};
print(str, std::strtof(str.c_str(), &p_end));
std::cout << ", p_end point to: [" << std::string(p_end) << "]";

if (errno == ERANGE) {
std::cout << ", [out of range]";
}

std::cout << '\n';
}

int main()
{
test("123.456789123");
test("-123.456789123");
test("1.2345e3");
test("1.2345e-3");
test("-1.2345e-3");
test("0x12.6");
test("0x12.6p3");
test("nan");
test("inf");
}
代码输出结果
std::strtof("123.456789123")  = [123.457], p_end point to: []
std::strtof("-123.456789123") = [-123.457], p_end point to: []
std::strtof("1.2345e3") = [1234.5], p_end point to: []
std::strtof("1.2345e-3") = [0.0012345], p_end point to: []
std::strtof("-1.2345e-3") = [-0.0012345], p_end point to: []
std::strtof("0x12.6") = [18.375], p_end point to: []
std::strtof("0x12.6p3") = [147], p_end point to: []
std::strtof("nan") = [nan], p_end point to: []
std::strtof("inf") = [inf], p_end point to: []

std::atoi & std::atol & std::atoll

函数原型

#include <cstdlib>

int atoi( const char *str);
long atol( const char *str);
long long atoll( const char *str);

说明

str 指向的字符串转换为整数

跳过任何空白字符,直到找到第一个非空白字符,然后获取尽可能多的有效的整数字符(基数为 base)。有效整数字符定义如下:

  • 加号(+)或减号(-)
  • 数字

atoi 系列的函数的缺点:

  • 当转换出现错误时,函数不会抛异常也不会报错
  • 对于不执行转换时会返回零,对于原来字符串就是 0 是不好进行判断

返回值

  • 如果成功,则直接返回转换后的数字类型数值
  • 如果返回值超出类型的范围(out of range),返回值为未定义
  • 如果不能执行转换,则返回 0

代码例子

#include <cstdlib>
#include <iostream>
#include <string>
#include <iomanip>

// 打印值
template<typename T>
void print(const std::string &str, const T &value) {
std::string tmp = "std::atoi(\"" + str + "\") ";
std::cout << std::setw(30) << std::left << tmp << "= [" << value << "]\n";
}

void test(const std::string& str)
{
print(str, std::atoi(str.c_str()));
}

int main()
{
test("12345");
test("123.45");
test("12345 with word");
test("words 12345");
test(" 12345");
test("12345 ");
test(" 12345 ");
test("12345123451");
test("-12345");
}
代码输出结果
std::atoi("12345")            = [12345]
std::atoi("123.45") = [123]
std::atoi("12345 with word") = [12345]
std::atoi("words 12345") = [0]
std::atoi(" 12345") = [12345]
std::atoi("12345 ") = [12345]
std::atoi(" 12345 ") = [12345]
std::atoi("12345123451") = [2147483647]
std::atoi("-12345") = [-12345]

std::atof

函数原型

#include <cstdlib>

double atof(const char *str);

说明

这里 str 的定义同 std::strtof & std::strtod & std::strtold 中的定义,具体内容见上方:arrow_up:

返回值

  • 如果成功,则直接返回转换后的数字类型数值
  • 如果返回值超出类型的范围(out of range),返回值为未定义
  • 如果不能执行转换,则返回 0.0

代码例子

#include <cstdlib>
#include <iostream>
#include <string>
#include <iomanip>

// 打印值
template<typename T>
void print(const std::string &str, const T &value) {
std::string tmp = "std::atof(\"" + str + "\") ";
std::cout << std::setw(30) << std::left << tmp << "= [" << value << "]\n";
}

void test(const std::string str) {
print(str, std::atof(str.c_str()));
}

int main() {
test("0.000000012345");
test("123.45");
test("1.2345 with word");
test("words 1.2345");
test(" 12.345");
test("12.345 ");
test(" 12.345 ");
test("-12.345");
test("12345123451");
test("15e16");
test("0x12.6p3");
test("INF");
test("NAN");
}
代码输出结果
std::atof("0.000000012345")   = [1.2345e-08]
std::atof("123.45") = [123.45]
std::atof("1.2345 with word") = [1.2345]
std::atof("words 1.2345") = [0]
std::atof(" 12.345") = [12.345]
std::atof("12.345 ") = [12.345]
std::atof(" 12.345 ") = [12.345]
std::atof("-12.345") = [-12.345]
std::atof("12345123451") = [1.23451e+10]
std::atof("15e16") = [1.5e+17]
std::atof("0x12.6p3") = [147]
std::atof("INF") = [inf]
std::atof("NAN") = [nan]

std::stoi & std::stol & std::stoll & std::stoul & std::stoull

函数原型

#include <string>

int stoi( const std::string& str, std::size_t* pos = nullptr, int base = 10 );
int stoi( const std::wstring& str, std::size_t* pos = nullptr, int base = 10 );

long stol( const std::string& str, std::size_t* pos = nullptr, int base = 10 );
long stol( const std::wstring& str, std::size_t* pos = nullptr, int base = 10 );

long long stoll( const std::string& str, std::size_t* pos = nullptr, int base = 10 );
long long stoll( const std::wstring& str, std::size_t* pos = nullptr, int base = 10 );

unsigned long stoul( const std::string& str, std::size_t* pos = nullptr, int base = 10 );
unsigned long stoul( const std::wstring& str, std::size_t* pos = nullptr, int base = 10 );

unsigned long long stoull( const std::string& str, std::size_t* pos = nullptr, int base = 10 );
unsigned long long stoull( const std::wstring& str, std::size_t* pos = nullptr, int base = 10 );

说明

stoi 系列的函数可以支持窄字符和宽字符字符串,其内部实现是调用的 strtol 系列的函数

  • stoi 调用 std::strtol(str.c_str(), &ptr, base)std::wcstol(str.c_str(), &ptr, base)
  • stol 调用 std::strtol(str.c_str(), &ptr, base)std::wcstol(str.c_str(), &ptr, base)
  • stoll 调用 std::strtoll(str.c_str(), &ptr, base)std::wcstoll(str.c_str(), &ptr, base)
  • stoul 调用 std::strtoul(str.c_str(), &ptr, base)std::wcstoul(str.c_str(), &ptr, base)
  • stoull 调用 std::strtoull(str.c_str(), &ptr, base)std::wcstoull(str.c_str(), &ptr, base)

其中 str 字符串的定义和 std::strtol 系列函数中定义相同

如果 pos 不为空,字符串中第一个未处理的字符的索引将存储在 *pos 中,即 strtol 系列函数中 str_end 指向字符的索引

因为 stoi 系列的函数会抛出异常,所以在使用该系列函数式一定要注意加上 try .. catch .. 块

返回值

  • 如果成功,则直接返回转换后的数字类型数值
  • 如果失败,则会抛出异常
    • std::invalid_argument: 参数无效,不执行转换时抛出该异常
    • std::out_of_range: 转换后超出了指定类型的范围时抛出该异常

代码例子

#include <string>
#include <iostream>
#include <iomanip>

// 打印值
template<typename T>
void print(const std::string &str, const T &value) {
std::string tmp = "std::stoi(\"" + str + "\") ";
std::cout << std::setw(30) << std::left << tmp << "= [" << value << "]";
}

void printException(const std::string &str, const std::exception& e) {
std::string tmp = "std::stoi(\"" + str + "\") ";
std::cout << std::setw(30) << std::left << tmp << "= [exception: " << e.what() << "]\n";
}

void test(const std::string& str)
{
try {
std::size_t pos{0};
print(str, std::stoi(str, &pos, 10));
std::cout << ", pos: " << pos << '\n';
} catch(const std::exception& e) {
printException(str, e);
}
}

int main() {
test("45");
test("3.14159");
test("12345 with word");
test("word 12345");
test("12345123451");
}
代码输出结果
std::stoi("45")               = [45], pos: 2
std::stoi("3.14159") = [3], pos: 1
std::stoi("12345 with word") = [12345], pos: 5
std::stoi("word 12345") = [exception: invalid stoi argument]
std::stoi("12345123451") = [exception: stoi argument out of range]

std::stof & std::stod & std::stold

函数原型

#include <string>

float stof( const std::string& str, std::size_t* pos = nullptr );
float stof( const std::wstring& str, std::size_t* pos = nullptr );

double stod( const std::string& str, std::size_t* pos = nullptr );
double stod( const std::wstring& str, std::size_t* pos = nullptr );

long double stold( const std::string& str, std::size_t* pos = nullptr );
long double stold( const std::wstring& str, std::size_t* pos = nullptr );

说明

stof 系列的函数可以支持窄字符和宽字符字符串,其内部实现是调用的 strtof 系列的函数

  • stof 调用 std::strtof(str.c_str(), &ptr)std::wcstof(str.c_str(), &ptr)
  • stod 调用 std::strtod(str.c_str(), &ptr)std::wcstod(str.c_str(), &ptr)
  • stold 调用 std::strtold(str.c_str(), &ptr)std::wcstold(str.c_str(), &ptr)

其中 str 字符串的定义和 std::strtof 系列函数中定义相同

如果 pos 不为空,字符串中第一个未处理的字符的索引将存储在 *pos 中,即 strtof 系列函数中 str_end 指向字符的索引

因为 stof 系列的函数会抛出异常,所以在使用该系列函数式一定要注意加上 try .. catch .. 块

返回值

  • 如果成功,则直接返回转换后的数字类型数值
  • 如果失败,则会抛出异常
    • std::invalid_argument: 参数无效,不执行转换时抛出该异常
    • std::out_of_range: 转换后超出了指定类型的范围时抛出该异常

代码例子

#include <string>
#include <iostream>
#include <iomanip>

// 打印值
template<typename T>
void print(const std::string &str, const T &value) {
std::string tmp = "std::stof(\"" + str + "\") ";
std::cout << std::setw(30) << std::left << tmp << "= [" << value << "]";
}

void printException(const std::string &str, const std::exception& e) {
std::string tmp = "std::stof(\"" + str + "\") ";
std::cout << std::setw(30) << std::left << tmp << "= [exception: " << e.what() << "]\n";
}

void test(const std::string& str)
{
try {
std::size_t pos{0};
print(str, std::stof(str, &pos));
std::cout << ", pos: " << pos << '\n';
} catch(const std::exception& e) {
printException(str, e);
}
}

int main() {
test("123.456789123");
test("-123.456789123");
test("1.2345e3");
test("1.2345e-3");
test("-1.2345e-3");
test("0x12.6");
test("0x12.6p3");
}
代码输出结果
std::stof("123.456789123")    = [123.457], pos: 13
std::stof("-123.456789123") = [-123.457], pos: 14
std::stof("1.2345e3") = [1234.5], pos: 8
std::stof("1.2345e-3") = [0.0012345], pos: 9
std::stof("-1.2345e-3") = [-0.0012345], pos: 10
std::stof("0x12.6") = [18.375], pos: 6
std::stof("0x12.6p3") = [147], pos: 8

使用 std::stringstream

#include <iostream>
#include <sstream>
#include <iomanip>

template <typename T>
void string_to_number(const std::string& str, T& number) {
std::stringstream ss;
ss << str;
ss >> number;
std::cout << "stringstream " << std::setw(7) << std::left << typeid(number).name() << " (" << std::quoted(str) << ") = [" << number << "]\n";
}

template <typename T>
void test(const std::string& str) {
T number;
string_to_number(str, number);
}

int main() {
test<int>("12345");
test<int>("-12345");
test<int>("12345abc");
test<int>(" 12345 abc");

test<float>("123.456789123");
test<float>("-123.456789123");
test<float>("1.2345e3");
test<float>("1.2345e-3");
test<float>("0x12.6");
test<float>("0x12.6p3");
}
代码输出结果
stringstream int     ("12345") = [12345]
stringstream int ("-12345") = [-12345]
stringstream int ("12345abc") = [12345]
stringstream int (" 12345 abc") = [12345]
stringstream float ("123.456789123") = [123.457]
stringstream float ("-123.456789123") = [-123.457]
stringstream float ("1.2345e3") = [1234.5]
stringstream float ("1.2345e-3") = [0.0012345]
stringstream float ("0x12.6") = [0]
stringstream float ("0x12.6p3") = [0]

std::from_chars (c++ 17)

函数原型

#include <charconv>

struct from_chars_result {
const char* ptr;
std::errc ec;
};

enum class chars_format {
scientific = /*unspecified*/,
fixed = /*unspecified*/,
hex = /*unspecified*/,
general = fixed | scientific
};

std::from_chars_result from_chars(const char* first, const char* last,
/*see below*/& value, int base = 10);
std::from_chars_result from_chars(const char* first, const char* last, float& value,
std::chars_format fmt = std::chars_format::general);
std::from_chars_result from_chars(const char* first, const char* last, double& value,
std::chars_format fmt = std::chars_format::general);
std::from_chars_result from_chars(const char* first, const char* last, long double& value,
std::chars_format fmt = std::chars_format::general);

说明

解析满足指定模式的字符序列 [first, last)。如果没有字符与模式匹配,或者通过解析匹配字符获得的值在值的类型中无法表示,value 不会被修改,否则,匹配模式的字符将被转换为对应的类型的值,保存在 value 中。

具体的模式如下:

  • 整数解析器:与 std::strtol 在默认 (“C”) 语言环境中使用的模式相同,除了以下情况

    • base16 时,0x0X 不能被识别
    • 只能识别减号(-),并且仅适用于有符号整数类型的值
  • 浮点数解析器:与 std::strtod 在默认 (“C”) 语言环境中使用的模式相同,除了以下情况

    • 在指数之外无法识别加号(仅允许在开头使用减号)
    • 如果 fmt 设置了 std::chars_format::scientific 但没有设置 std::chars_format::fixed,则指数部分是必需的(否则它是可选的)
    • 如果 fmt 设置了 std::chars_format::fixed 但没有设置 std::chars_format::scientific,则不允许使用可选指数
    • 如果 fmtstd::chars_format::hex,则不允许前缀 0x0X(字符串 0x123 解析为值 0,未解析余数为x123)。
    • 在任何情况下,在根据 std::round_to_nearest 舍入后,结果值是最接近匹配模式的字符串值的至多两个浮点值之一。

该系列的函数不会抛出异常(non-throwing),与语言环境无关(locale-independent),不会分配内存(non-allocating)

需要注意的点:

  • 不支持前置空格
  • 不支持加号(+)开始
  • 对于 16 进制的浮点数,不支持前置的 0x0X
  • 对于浮点数,如果设置 fmtstd::chars_format::scientific,则字符串中必须表示为科学计数表达式(包含 eE 部分)

返回值

  • 转换成功,std::from_chars_resultptr 指向与模式不匹配的第一个字符或者 lastvalue 为转换后的值
  • 如果一个也不匹配(不能转换),std::from_chars_resultptr 指向 firstecstd::errc::incalid_argumentvalue 值保持不变
  • 如果转换后数值超出了返回值类型的范围,则 std::from_chars_resultptr 指向 firstecstd::errc::result_out_of_rangevalue 保持不变

性能

  • On GCC it’s around 4.5x faster than stoi, 2.2x faster than atoi and almost 50x faster than istringstream.
  • On Clang it’s around 3.5x faster than stoi, 2.7x faster than atoi and 60x faster than istringstream!
  • MSVC performs around 3x faster than stoi, ~2x faster than atoi and almost 50x faster than istringstream

代码例子

#include <charconv>
#include <iostream>
#include <string>
#include <string_view>
#include <iomanip>

template<typename T>
void print(const std::string &str, const T &value) {
std::string tmp = "std::from_chars(\"" + str + "\") ";
std::cout << std::setw(35) << std::left << tmp << "= [" << value << "]";
}

template<typename T>
void test_integer(const std::string &str, int base = 10) {
T result;
auto[p, ec] = std::from_chars(str.data(), str.data() + str.size(), result, base);
if (ec == std::errc()) {
print(str, result);
std::cout << ", p point to [" << p << "]";
} else if (ec == std::errc::invalid_argument) {
print(str, "invalid argument");
std::cout << ", p point to [" << p << "]";
} else if (ec == std::errc::result_out_of_range) {
print(str, "result out of range");
std::cout << ", p point to [" << p << "]";
}

std::cout << '\n';
}

template<typename T>
void test_float(const std::string &str, std::chars_format fmt = std::chars_format::general) {
T result;
auto[p, ec] = std::from_chars(str.data(), str.data() + str.size(), result, fmt);
if (ec == std::errc()) {
print(str, result);
std::cout << ", p point to [" << p << "]";
} else if (ec == std::errc::invalid_argument) {
print(str, "invalid argument");
std::cout << ", p point to [" << p << "]";
} else if (ec == std::errc::result_out_of_range) {
print(str, "result out of range");
std::cout << ", p point to [" << p << "]";
}

std::cout << '\n';
}

int main() {
test_integer<int>("12345");
test_integer<int>(" 12345");
test_integer<int>("12345 ");
test_integer<int>(" 12345 ");
test_integer<int>("12345 with word");
test_integer<int>("word 12345");
test_integer<int>("+12345");
test_integer<int>("-12345");
test_integer<int>("12", 16);
test_integer<int>("0x12", 16);

test_float<float>("123.456789123");
test_float<float>("-123.456789123");
test_float<float>("1.2345e3");
test_float<float>("1.2345e-3");
test_float<float>("1.2345e+3");
test_float<float>("12.6", std::chars_format::hex);
test_float<float>("12.6p3", std::chars_format::hex);
}
代码输出结果
std::from_chars("12345")           = [12345], p point to []
std::from_chars(" 12345") = [invalid argument], p point to [ 12345]
std::from_chars("12345 ") = [12345], p point to [ ]
std::from_chars(" 12345 ") = [invalid argument], p point to [ 12345 ]
std::from_chars("12345 with word") = [12345], p point to [ with word]
std::from_chars("word 12345") = [invalid argument], p point to [word 12345]
std::from_chars("+12345") = [invalid argument], p point to [+12345]
std::from_chars("-12345") = [-12345], p point to []
std::from_chars("12") = [18], p point to []
std::from_chars("0x12") = [0], p point to [x12]
std::from_chars("123.456789123") = [123.457], p point to []
std::from_chars("-123.456789123") = [-123.457], p point to []
std::from_chars("1.2345e3") = [1234.5], p point to []
std::from_chars("1.2345e-3") = [0.0012345], p point to []
std::from_chars("1.2345e+3") = [1234.5], p point to []
std::from_chars("12.6") = [18.375], p point to []
std::from_chars("12.6p3") = [147], p point to []

数字转字符串

std::to_string & std::to_wstring

函数原型

std::string to_string( int value );
std::string to_string( long value );
std::string to_string( long long value );
std::string to_string( unsigned value );
std::string to_string( unsigned long value );
std::string to_string( unsigned long long value );
std::string to_string( float value );
std::string to_string( double value );
std::string to_string( long double value );

说明

  • 对于浮点类型 std::to_string 可能会产生意外的结果,因为返回的字符串中的有效位数可能为零
  • std::to_string 依赖当前语言环境进行格式化,因此从多个线程并发调用 std::to_string 可能导致部分序列化

代码例子

#include <iostream>
#include <string>
#include <iomanip>

template <typename T>
void test(T value) {
std::string str = std::to_string(value);
std::cout << "std::to_string(\"" << value << "\") = [" << str << "]\n";

std::wstring wstr = std::to_wstring(value);
std::wcout << L"std::to_wstring(\"" << value << L"\") = [" << wstr << L"]\n";
}

int main() {
test(12345);
test(-12345);
test(123.45);
test(1.23e3);
test(1e-20);
test(1e30);
}
代码输出结果
std::to_string("12345") = [12345]
std::to_wstring("12345") = [12345]
std::to_string("-12345") = [-12345]
std::to_wstring("-12345") = [-12345]
std::to_string("123.45") = [123.450000]
std::to_wstring("123.45") = [123.450000]
std::to_string("1230") = [1230.000000]
std::to_wstring("1230") = [1230.000000]
std::to_string("1e-20") = [0.000000]
std::to_wstring("1e-20") = [0.000000]
std::to_string("1e+30") = [1000000000000000019884624838656.000000]
std::to_wstring("1e+30") = [1000000000000000019884624838656.000000]

sprintf & snprintf & sprintf_s & snprintf_s

函数原型

#include <cstdio>

int sprintf( char *restrict buffer, const char *restrict format, ... );
int snprintf( char *restrict buffer, size_t bufsz, const char *restrict format, ... );
int sprintf_s(char *restrict buffer, rsize_t bufsz, const char *restrict format, ...);
int snprintf_s(char *restrict buffer, rsize_t bufsz, const char *restrict format, ...);

说明

格式字符串由普通的多字节字符组成(% 除外),并按指定的格式进行转换:

转换字符 描述
% 格式转换标志,如果想要转换为 %,需要使用 %%
c 单个字符
s 字符串
d & i 十进制带符号数
o 八进制无符号数
x & X 十六进制无符号数
u 十进制无符号数
f & F 十进制浮点数
e & E 十进制指数浮点数
a & A 十六进制浮点数
g & G 十进制浮点数或十进制指数浮点数
  • 浮点转换函数将无穷大转换为 infinfinity。使用哪一个是实现定义的
  • 非数字转换为 nannan(char_sequence)

返回

sprintf: 写入缓冲区的字符数(不包括终止空字符),如果发生编码错误(对于字符串和字符转换说明符),则为负值
snprintf: 如果忽略 bufsz,返回为将写入缓冲区的字符数(不包括终止空字符),或者如果发生编码错误(对于字符串和字符转换说明符)则为负值
sprintf_s: 写入缓冲区的字符数,不包括空字符(只要缓冲区不是空指针并且 bufsz 不为零且不大于 RSIZE_MAX),或运行时约束违规为零,编码错误为负值
snprintf_s: 不包括终止空字符的字符数(只要缓冲区不是空指针并且 bufsz 不为零且不大于 RSIZE_MAX,则始终写入),如果 bufsz 被忽略,它将被写入缓冲区,如果运行时约束违反或编码错误发生,则为负值

代码例子

#include <cstdio>
#include <iostream>
#include <iomanip>

int main() {
std::cout << std::setw(8) << std::left << "format" << std::setw(2) << "n" << " " << " string" << '\n';
std::cout << std::setw(20) << std::setfill('-') << '-' << std::setfill(' ')<< std::endl;
char buf[30] = {""};
int n = sprintf(buf, "%d", 12345);
std::cout << std::setw(8) << std::left << "%d:" << std::setw(2) << n << " " << buf << '\n';

n = sprintf(buf, "%d", -12345);
std::cout << std::setw(8) << std::left << "%d:" << std::setw(2) << n << " " << buf << '\n';

n = sprintf(buf, "%x", 256);
std::cout << std::setw(8) << std::left << "%x:" << std::setw(2) << n << " " << buf << '\n';

n = sprintf(buf, "%o", 256);
std::cout << std::setw(8) << std::left << "%o:" << std::setw(2) << n << " " << buf << '\n';

n = sprintf(buf, "%f", 12.345);
std::cout << std::setw(8) << std::left << "%f:" << std::setw(2) << n << " " << buf << '\n';

n = sprintf(buf, "%.4f", 12.345);
std::cout << std::setw(8) << std::left << "%.4f:" << std::setw(2) << n << " " << buf << '\n';

n = sprintf(buf, "%.4f", 12.345);
std::cout << std::setw(8) << std::left << "%.4f:" << std::setw(2) << n << " " << buf << '\n';

n = sprintf(buf, "%e", 1234.5);
std::cout << std::setw(8) << std::left << "%e:" << std::setw(2) << n << " " << buf << '\n';

n = sprintf(buf, "%.4a", 147.0);
std::cout << std::setw(8) << std::left << "%.4a:" << std::setw(2) << n << " " << buf << '\n';
}
代码输出结果
format  n   string
--------------------
%d: 5 12345
%d: 6 -12345
%x: 3 100
%o: 3 400
%f: 9 12.345000
%.4f: 7 12.3450
%.4f: 7 12.3450
%e: 12 1.234500e+03
%.4a: 11 0x1.2600p+7

to_chars

函数原型

#include <charconv>

struct to_chars_result {
char* ptr;
std::errc ec;
};

enum class chars_format {
scientific = /*unspecified*/,
fixed = /*unspecified*/,
hex = /*unspecified*/,
general = fixed | scientific
};

std::to_chars_result to_chars(char* first, char* last,/*see below*/ value, int base = 10);
std::to_chars_result to_chars(char*, char*, bool, int = 10) = delete;

std::to_chars_result to_chars(char* first, char* last, float value);
std::to_chars_result to_chars(char* first, char* last, double value);
std::to_chars_result to_chars(char* first, char* last, long double value);

std::to_chars_result to_chars(char* first, char* last, float value, std::chars_format fmt);
std::to_chars_result to_chars(char* first, char* last, double value, std::chars_format fmt);
std::to_chars_result to_chars(char* first, char* last, long double value, std::chars_format fmt);

std::to_chars_result to_chars(char* first, char* last, float value,
std::chars_format fmt, int precision);
std::to_chars_result to_chars(char* first, char* last, double value,
std::chars_format fmt, int precision);
std::to_chars_result to_chars(char* first, char* last, long double value,
std::chars_format fmt, int precision);

说明

value 值填充范围到[first, last) 指向的字符串,其中 [first, last) 必须是有效范围

返回

  • 成功时,返回 to_chars_result 类型的值,其中 ec 等于值初始化的 std::errcptr 是写入字符结尾的后一个指针。请注意,该字符串不是以 NUL 结尾的
  • 出错时,返回 to_chars_result 类型的值,其中 ec 等于 std::errc::value_too_large

代码例子

#include <iostream>
#include <charconv>
#include <string_view>
#include <iomanip>
#include <string>

template<typename T>
void print(T value, std::string_view str) {
std::cout << "std::to_chars(" << std::setw(15) << std::right << std::setprecision(12) << value << ") = [" << str << "]\n";
}

template<typename T>
void test_integer(std::string& str, T value, int base = 10) {
auto[p, ec] = std::to_chars(str.data(), str.data() + str.size(), value, base);
if (ec == std::errc()) {
print(value, std::string_view(str.data(), p - str.data()));
} else if (ec == std::errc::value_too_large) {
print(value, str);
}
}

template<typename T>
void test_float(std::string& str, T value, std::chars_format fmt = std::chars_format::general) {
auto[p, ec] = std::to_chars(str.data(), str.data() + str.size(), value, fmt);
if (ec == std::errc()) {
print(value, std::string_view(str.data(), p - str.data()));
} else if (ec == std::errc::value_too_large) {
print(value, str);
}
}

template<typename T>
void test_float_precision(std::string& str, T value, std::chars_format fmt = std::chars_format::general, int precision = 6) {
auto[p, ec] = std::to_chars(str.data(), str.data() + str.size(), value, fmt, precision);
if (ec == std::errc()) {
print(value, std::string_view(str.data(), p - str.data()));
} else if (ec == std::errc::value_too_large) {
print(value, str);
}
}

int main()
{
std::string str;
str.resize(20);

test_integer(str, 12345);
test_integer(str, -12345);
test_integer(str, 100, 8);

test_float(str, 123.456789123);
test_float_precision(str, 123.456789123, std::chars_format::general, 6);
test_float(str, -123.456789123);
test_float(str, 1234.5);
test_float(str, 18.375, std::chars_format::hex);
test_float(str, 147.0, std::chars_format::hex);
}
代码输出结果
format  n   string
--------------------
%d: 5 12345
%d: 6 -12345
%x: 3 100
%o: 3 400
%f: 9 12.345000
%.4f: 7 12.3450
%.4f: 7 12.3450
%e: 12 1.234500e+03
%.4a: 11 0x1.2600p+7