C++ std::stringstream
std::stringstream 是 C++ 标准库中一个强大的字符串流类,它允许像使用输入输出流一样操作字符串,提供了丰富的格式化和数据处理功能。
基本概念和头文件
cpp
#include <sstream> // 主要头文件
#include <iostream>
#include <string>
// 三种字符串流类型:
std::stringstream ss; // 输入输出字符串流
std::istringstream iss; // 只输入字符串流
std::ostringstream oss; // 只输出字符串流
基本用法
1. 创建和基本操作
cpp
#include <sstream>
#include <iostream>
void basic_usage() {
// 创建字符串流
std::stringstream ss;
// 写入数据
ss << "Hello, ";
ss << "World! ";
ss << 42 << " ";
ss << 3.14;
// 获取字符串
std::string result = ss.str();
std::cout << "内容: " << result << std::endl;
// 输出: 内容: Hello, World! 42 3.14
// 清空流
ss.str(""); // 清空内容
ss.clear(); // 清除错误状态
}
2. 数据提取
cpp
#include <sstream>
#include <string>
void extraction_example() {
std::string data = "John 25 75000.50";
std::stringstream ss(data);
std::string name;
int age;
double salary;
// 从流中提取数据
ss >> name >> age >> salary;
std::cout << "姓名: " << name << std::endl;
std::cout << "年龄: " << age << std::endl;
std::cout << "薪资: " << salary << std::endl;
}
字符串流类型详解
1. std::ostringstream - 输出字符串流
cpp
#include <sstream>
#include <iostream>
void ostringstream_example() {
std::ostringstream oss;
// 只能输出,不能输入
oss << "计算结果: " << (10 + 20) << std::endl;
oss << "圆周率: " << 3.14159 << std::endl;
oss << "布尔值: " << std::boolalpha << true;
std::string output = oss.str();
std::cout << "输出内容:\n" << output << std::endl;
// 获取流状态
std::cout << "是否良好: " << oss.good() << std::endl;
std::cout << "是否结束: " << oss.eof() << std::endl;
}
2. std::istringstream - 输入字符串流
cpp
#include <sstream>
#include <vector>
void istringstream_example() {
std::string csv_data = "apple,banana,cherry,date,elderberry";
std::istringstream iss(csv_data);
std::vector<std::string> fruits;
std::string fruit;
// 使用逗号分隔符读取
while (std::getline(iss, fruit, ',')) {
fruits.push_back(fruit);
}
std::cout << "水果列表:" << std::endl;
for (const auto& f : fruits) {
std::cout << "- " << f << std::endl;
}
// 检查流状态
if (iss.eof()) {
std::cout << "已到达流末尾" << std::endl;
}
}
3. std::stringstream - 双向字符串流
cpp
#include <sstream>
#include <iomanip>
void stringstream_bidirectional() {
std::stringstream ss;
// 写入数据
ss << "产品A: " << std::fixed << std::setprecision(2) << 19.99;
ss << " | 产品B: " << 29.50;
// 读取数据
std::string product1, product2;
double price1, price2;
char separator;
ss >> product1 >> separator >> price1;
ss.ignore(10, '|'); // 跳过分隔符
ss >> product2 >> separator >> price2;
std::cout << product1 << ": $" << price1 << std::endl;
std::cout << product2 << ": $" << price2 << std::endl;
}
高级功能和应用
1. 类型转换工具
cpp
#include <sstream>
#include <string>
#include <stdexcept>
class StringConverter {
public:
// 任意类型转换为字符串
template<typename T>
static std::string toString(const T& value) {
std::ostringstream oss;
oss << value;
if (!oss) {
throw std::runtime_error("转换失败");
}
return oss.str();
}
// 字符串转换为任意类型
template<typename T>
static T fromString(const std::string& str) {
std::istringstream iss(str);
T value;
iss >> value;
if (!iss) {
throw std::runtime_error("转换失败: " + str);
}
return value;
}
// 检查是否可以转换
template<typename T>
static bool canConvert(const std::string& str) {
std::istringstream iss(str);
T value;
return (iss >> value) && iss.eof();
}
};
// 使用示例
void conversion_example() {
try {
// 转换为字符串
std::string str_num = StringConverter::toString(42);
std::string str_double = StringConverter::toString(3.14159);
// 从字符串转换
int num = StringConverter::fromString<int>("123");
double pi = StringConverter::fromString<double>("3.14");
// 检查转换
bool can_convert = StringConverter::canConvert<int>("456");
std::cout << "字符串数字: " << str_num << std::endl;
std::cout << "转换回整数: " << num << std::endl;
std::cout << "能否转换 'abc': "
<< StringConverter::canConvert<int>("abc") << std::endl;
} catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << std::endl;
}
}
2. 字符串格式化
cpp
#include <sstream>
#include <iomanip>
void formatting_example() {
std::ostringstream oss;
// 数字格式化
oss << "十进制: " << 42 << std::endl;
oss << "十六进制: " << std::hex << 42 << std::endl;
oss << "八进制: " << std::oct << 42 << std::endl;
// 浮点数格式化
oss << std::dec << std::fixed << std::setprecision(3);
oss << "固定小数: " << 3.14159 << std::endl;
oss << std::scientific;
oss << "科学计数: " << 3.14159 << std::endl;
// 对齐和填充
oss << std::left << std::setw(10) << "左对齐" << "|" << std::endl;
oss << std::right << std::setw(10) << "右对齐" << "|" << std::endl;
oss << std::setfill('*') << std::setw(10) << "填充" << std::endl;
std::cout << oss.str() << std::endl;
}
3. 复杂数据序列化
cpp
#include <sstream>
#include <vector>
#include <map>
class DataSerializer {
public:
// 序列化 vector 到字符串
template<typename T>
static std::string serializeVector(const std::vector<T>& vec,
const std::string& delimiter = ",") {
std::ostringstream oss;
for (size_t i = 0; i < vec.size(); ++i) {
if (i > 0) oss << delimiter;
oss << vec[i];
}
return oss.str();
}
// 从字符串反序列化到 vector
template<typename T>
static std::vector<T> deserializeVector(const std::string& str,
const std::string& delimiter = ",") {
std::vector<T> result;
std::istringstream iss(str);
std::string token;
while (std::getline(iss, token, delimiter[0])) {
std::istringstream token_stream(token);
T value;
if (token_stream >> value) {
result.push_back(value);
}
}
return result;
}
// 序列化 map 到字符串
template<typename K, typename V>
static std::string serializeMap(const std::map<K, V>& map,
const std::string& pair_sep = "=",
const std::string& entry_sep = ";") {
std::ostringstream oss;
bool first = true;
for (const auto& [key, value] : map) {
if (!first) oss << entry_sep;
oss << key << pair_sep << value;
first = false;
}
return oss.str();
}
};
void serialization_example() {
// 序列化 vector
std::vector<int> numbers = {1, 2, 3, 4, 5};
std::string serialized = DataSerializer::serializeVector(numbers);
std::cout << "序列化: " << serialized << std::endl;
// 反序列化
auto deserialized = DataSerializer::deserializeVector<int>(serialized);
std::cout << "反序列化: ";
for (int num : deserialized) {
std::cout << num << " ";
}
std::cout << std::endl;
// 序列化 map
std::map<std::string, int> scores = {{"Alice", 95}, {"Bob", 87}, {"Charlie", 92}};
std::string map_str = DataSerializer::serializeMap(scores);
std::cout << "Map 序列化: " << map_str << std::endl;
}
实际应用场景
1. 日志系统
cpp
#include <sstream>
#include <chrono>
#include <iomanip>
class Logger {
private:
std::ostringstream& getStream() {
static thread_local std::ostringstream oss;
oss.str(""); // 清空内容
oss.clear(); // 清除状态
// 添加时间戳
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
oss << "[" << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S") << "] ";
return oss;
}
public:
void log(const std::string& message) {
auto& oss = getStream();
oss << "[INFO] " << message;
std::cout << oss.str() << std::endl;
}
void error(const std::string& message) {
auto& oss = getStream();
oss << "[ERROR] " << message;
std::cerr << oss.str() << std::endl;
}
template<typename... Args>
void logFormatted(const std::string& format, Args... args) {
auto& oss = getStream();
oss << "[INFO] ";
formatMessage(oss, format, args...);
std::cout << oss.str() << std::endl;
}
private:
// 递归终止
void formatMessage(std::ostringstream& oss, const std::string& format) {
oss << format;
}
// 递归展开参数包
template<typename T, typename... Args>
void formatMessage(std::ostringstream& oss, const std::string& format,
T value, Args... args) {
size_t pos = format.find("{}");
if (pos != std::string::npos) {
oss << format.substr(0, pos);
oss << value;
formatMessage(oss, format.substr(pos + 2), args...);
} else {
oss << format;
}
}
};
void logger_example() {
Logger logger;
logger.log("应用程序启动");
logger.error("发生了一个错误");
logger.logFormatted("用户 {} 在 {} 执行了操作", "Alice", "登录");
}
2. SQL 查询构建器
cpp
#include <sstream>
#include <vector>
class QueryBuilder {
private:
std::ostringstream query_;
public:
QueryBuilder& select(const std::vector<std::string>& columns = {"*"}) {
query_ << "SELECT ";
for (size_t i = 0; i < columns.size(); ++i) {
if (i > 0) query_ << ", ";
query_ << columns[i];
}
return *this;
}
QueryBuilder& from(const std::string& table) {
query_ << " FROM " << table;
return *this;
}
QueryBuilder& where(const std::string& condition) {
query_ << " WHERE " << condition;
return *this;
}
QueryBuilder& andWhere(const std::string& condition) {
query_ << " AND " << condition;
return *this;
}
QueryBuilder& orWhere(const std::string& condition) {
query_ << " OR " << condition;
return *this;
}
QueryBuilder& orderBy(const std::string& column, bool ascending = true) {
query_ << " ORDER BY " << column << (ascending ? " ASC" : " DESC");
return *this;
}
QueryBuilder& limit(int count) {
query_ << " LIMIT " << count;
return *this;
}
std::string build() {
return query_.str();
}
void reset() {
query_.str("");
query_.clear();
}
};
void query_builder_example() {
QueryBuilder qb;
std::string query = qb.select({"id", "name", "email"})
.from("users")
.where("age > 18")
.andWhere("status = 'active'")
.orderBy("name")
.limit(10)
.build();
std::cout << "生成的 SQL: " << query << std::endl;
// 输出: SELECT id, name, email FROM users WHERE age > 18 AND status = 'active' ORDER BY name ASC LIMIT 10
}
3. 配置解析器
cpp
#include <sstream>
#include <map>
#include <algorithm>
class ConfigParser {
private:
std::map<std::string, std::string> config_;
public:
bool parse(const std::string& config_text) {
std::istringstream iss(config_text);
std::string line;
int line_num = 0;
while (std::getline(iss, line)) {
++line_num;
// 移除前后空白
line.erase(0, line.find_first_not_of(" \t"));
line.erase(line.find_last_not_of(" \t") + 1);
// 跳过空行和注释
if (line.empty() || line[0] == '#') {
continue;
}
// 解析键值对
std::istringstream line_stream(line);
std::string key, value;
if (std::getline(line_stream, key, '=')) {
if (std::getline(line_stream, value)) {
// 移除值的空白
value.erase(0, value.find_first_not_of(" \t"));
value.erase(value.find_last_not_of(" \t") + 1);
config_[key] = value;
} else {
std::cerr << "配置错误第 " << line_num << " 行: 缺少值" << std::endl;
return false;
}
}
}
return true;
}
template<typename T>
T get(const std::string& key, const T& defaultValue = T()) const {
auto it = config_.find(key);
if (it != config_.end()) {
std::istringstream iss(it->second);
T value;
if (iss >> value) {
return value;
}
}
return defaultValue;
}
std::string getString(const std::string& key, const std::string& defaultValue = "") const {
auto it = config_.find(key);
return it != config_.end() ? it->second : defaultValue;
}
};
void config_parser_example() {
std::string config_text = R"(
# 数据库配置
db.host = localhost
db.port = 3306
db.name = myapp
# 应用配置
app.name = MyApplication
app.debug = true
app.max_connections = 100
)";
ConfigParser parser;
if (parser.parse(config_text)) {
std::cout << "数据库主机: " << parser.getString("db.host") << std::endl;
std::cout << "数据库端口: " << parser.get<int>("db.port") << std::endl;
std::cout << "调试模式: " << std::boolalpha << parser.get<bool>("app.debug") << std::endl;
}
}
性能优化和最佳实践
1. 重用 stringstream 对象
cpp
#include <sstream>
class StringStreamPool {
private:
static thread_local std::stringstream ss;
public:
static std::stringstream& get() {
ss.str(""); // 清空内容
ss.clear(); // 清除状态标志
return ss;
}
template<typename T>
static std::string toString(const T& value) {
auto& stream = get();
stream << value;
return stream.str();
}
};
void performance_example() {
// 避免重复创建 stringstream
for (int i = 0; i < 1000; ++i) {
// 不好的做法:每次创建新的 stringstream
// std::stringstream ss;
// ss << i;
// std::string str = ss.str();
// 好的做法:重用 stringstream
std::string str = StringStreamPool::toString(i);
}
}
2. 错误处理
cpp
#include <sstream>
#include <stdexcept>
void robust_stringstream_usage() {
std::stringstream ss;
try {
// 写入操作
ss << "一些数据";
if (ss.fail()) {
throw std::runtime_error("写入 stringstream 失败");
}
// 读取操作
int value;
ss >> value;
if (ss.fail()) {
throw std::runtime_error("从 stringstream 读取失败");
}
// 检查是否完全消费了输入
std::string remaining;
if (ss >> remaining) {
throw std::runtime_error("输入数据有剩余: " + remaining);
}
} catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << std::endl;
// 恢复流状态
ss.clear();
ss.str("");
}
}
总结
std::stringstream 的主要优势:
- 强大的字符串格式化能力
- 类型安全的字符串转换
- 灵活的数据序列化/反序列化
- 与标准流一致的接口
常用场景:
- 字符串构建和格式化
- 类型转换(字符串 ↔ 其他类型)
- 数据序列化
- 文本解析
- 日志记录
- SQL 查询构建
最佳实践:
- 重用 stringstream 对象以提高性能
- 及时清理内容和状态(str("") 和 clear())
- 检查流操作是否成功
- 对于简单字符串拼接,考虑使用 std::string 的 += 操作
- 使用合适的字符串流类型(istringstream/ostringstream)
性能考虑:
- 在性能关键路径中避免频繁创建 stringstream
- 对于简单操作,直接使用字符串操作可能更快
- 考虑预分配缓冲区大小(C++20 的 std::ostringstream::str() 可以保留容量)
std::stringstream 是 C++ 中处理字符串格式化和转换的强大工具,正确使用可以大大提高代码的可读性和维护性。