在把浮点数转换成字符串时,不少初学 C++ 的小伙伴都会觉得奇怪。明明变量里保存的是 36.5,调用 std::to_string() 转换后,末尾却多出了一串 0,变成了 "36.500000"。
这是因为浮点数的文本转换通常还涉及小数位数的保留规则。在 C++ 中,我们既可以把 double 转为字符串并按需控制小数位数,也可以反过来把字符串解析为 double 数值。
double 转 string
将 double 浮点数转换为 std::string 字符串主要有以下 2 种方式,我们可以根据是否需要控制小数位数来选择。
1. 使用 std::to_string() 默认转换
如果我们只是想把浮点数快速转成字符串,对末尾的小数位数没有特别要求,直接调用 <string> 头文件中的 std::to_string() 函数即可。
语法:
std::to_string(浮点数)说明:
std::to_string() 会将传入的浮点数转换为一个新的 std::string 并返回。除了 double 之外,它也支持 float 和 long double 类型。
示例 1:默认转换为 string
#include <iostream>
#include <string>
int main() {
double temp = 36.5;
std::string text = std::to_string(temp);
std::cout << text << '\n';
std::cout << "当前体温:" << text << "℃" << '\n';
return 0;
}运行结果如下。
36.500000
当前体温:36.500000℃分析:
为什么 36.5 经过 std::to_string(temp) 转换后会变成 "36.500000" 呢?因为 std::to_string() 在转换浮点数时,底层规则跟 C 语言的 printf("%f") 相似,默认固定保留 6 位小数。
当小数不足 6 位时,它会在末尾自动补 0;当小数超过 6 位时,则会按四舍五入保留 6 位。比如 std::to_string(3.1415926) 会得到 "3.141593",而极小的 std::to_string(0.0000001) 则会变成 "0.000000"。
2. 转换时控制小数位数(推荐)
在实际开发中,像评分、体温、商品单价这类数据,转成字符串时通常只需要保留 1 位或 2 位小数,并不希望末尾拖着一串多余的 0。
由于 std::to_string() 不支持指定小数位数,我们可以借助 <sstream> 头文件中的 std::ostringstream(输出字符串流)。你可以把它看作 “内存里的 std::cout”:平时 std::cout 是把内容打印到屏幕上,而它是把内容写进内存字符串里。
写入浮点数时,我们只需要配合前面学过的 std::fixed 和 std::setprecision(),就能自由控制保留几位小数。
语法:
std::ostringstream 流名称;
流名称 << std::fixed << std::setprecision(位数) << 浮点数;
std::string 结果字符串 = 流名称.str();说明:
其中,std::fixed 表示采用固定小数点格式,配合 <iomanip> 头文件中的 std::setprecision(位数),即可按四舍五入规则保留指定位数的小数。写入完成后,调用成员函数 str() 就能取出生成好的 std::string 字符串。
示例 2:转换时保留 2 位小数
#include <iomanip> // 提供 std::setprecision()
#include <iostream>
#include <sstream> // 提供 std::ostringstream
#include <string>
int main() {
double score = 9.456;
std::ostringstream out;
out << std::fixed << std::setprecision(2) << score;
std::string text = out.str(); // 取出格式化后的字符串
std::cout << "最终得分:" << text << '\n';
return 0;
}运行结果如下。
最终得分:9.46分析:
在这个例子中,我们将 9.456 写入字符串流 out,并通过 std::fixed 和 std::setprecision(2) 指定保留 2 位小数。四舍五入后,调用 out.str() 取出的结果就是 "9.46"。如果把 score 换成 9.4,末尾也会自动补齐为 "9.40"。
回到文章开头的疑问:对于 36.5 这样的日常小数,如果我们只想去掉末尾多余的 0 得到 "36.5",应该怎么写呢?其实只要不加 std::fixed,直接把数值写入字符串流就可以了:
std::ostringstream out;
out << 36.5;
std::string cleanStr = out.str(); // 得到 "36.5"需要注意的是,不加 std::fixed 时流默认最多显示 6 位有效数字,遇到像 1234567.5 这样位数较多的数会自动变成科学计数法。关于 stringstream 字符串流的更多用法,后面很快就会学到。
提示 在 C++20 标准中,我们还可以使用 <format> 头文件提供的 std::format() 函数,直接写 std::format("{:.2f}", score) 就能保留 2 位小数生成字符串。
string 转 double:std::stod()
反过来,如果我们手里拿到的是浮点数字符串,希望解析成真正的 double 数值参与计算,可以使用 <string> 头文件中的 std::stod() 函数。其中,stod 是英文 “string to double(字符串转浮点数)” 的缩写。
1. 基本用法
我们将包含小数的 std::string 字符串传给 std::stod(),就能得到对应的 double 浮点数。它不仅支持普通小数,还支持像 "3.14e2" 这样的科学计数法字符串。
语法:
std::stod(字符串)说明:
std::stod() 会自动跳过字符串开头的空格,然后将后面的浮点数文本解析为 double 并返回。
示例 3:字符串解析为 double
#include <iostream>
#include <string>
int main() {
std::string text = "12.5";
double num = std::stod(text);
// 解析科学计数法字符串
double sciVal = std::stod("3.14e2");
std::cout << num << '\n';
std::cout << num + 0.5 << '\n';
std::cout << sciVal << '\n';
return 0;
}运行结果如下。
12.5
13
314分析:
std::stod(text) 表示将字符串 "12.5" 解析为 double 数值 12.5,因此后面的 num + 0.5 执行的是浮点数加法,得到 13。另外,"3.14e2" 表示 3.14 乘以 10 的 2 次方,std::stod() 同样能识别并转出 314。
根据目标浮点数精度的不同,<string> 头文件中一共提供了以下 3 个转换函数,如下表所示。
| 函数名 | 返回类型 | 说明 |
|---|---|---|
| std::stof() | float | 转换为单精度浮点数 |
| std::stod() | double | 最常用,转换为双精度浮点数 |
| std::stold() | long double | 转换为扩展精度浮点数 |
2. 部分解析与异常边界
与前面学过的 std::stoi() 类似,std::stod() 在遇到 "19.8元"、"36.5℃" 这类末尾带有单位的字符串时,也会把开头的有效浮点数提取出来,并支持通过第 2 个参数 &pos 记录停止解析的字符下标。
示例 4:记录停止解析的位置
#include <iostream>
#include <string>
int main() {
std::string priceText = "19.8元";
std::size_t pos = 0;
double price = std::stod(priceText, &pos);
std::cout << "单价:" << price << '\n';
std::cout << "停止下标:" << pos << '\n';
return 0;
}运行结果如下。
单价:19.8
停止下标:4分析:
字符串 "19.8元" 开头的 19.8 连同小数点一共占了 4 个字符(下标 0 ~ 3),解析在汉字 "元" 所在的下标 4 处停下,因此变量 pos 的值为 4。
这里小伙伴们要留意符号出现的位置:"19.8元" 能够解析成功,是因为数字排在最开头。如果货币符号或文字出现在最前面,函数从第 1 个字符就无法识别出数字,下面这种写法在运行时就会直接抛出异常。
double badVal = std::stod("¥19.8"); // 异常:开头不是数字具体来说,开头没有合法浮点数时会抛出 std::invalid_argument 异常,数值超出 double 范围(如 "1e500")时会抛出 std::out_of_range 异常。大家在处理外部输入时,记得配合 try...catch 捕获异常。
