引言
在上一篇文章中,我们学习了 C++ IO 流体系的基础------标准输入输出流 cin/cout 和文件流 fstream。本文将继续深入两个重要主题:
-
字符串流 stringstream :以
string为操作对象的流,用于数据格式转换和字符串拼接 -
STL 容器 vector:最常用的顺序容器,动态数组的完整使用指南
这两个工具在日常开发中使用频率极高------stringstream 是字符串和数值互转的神器,vector 是 C++ 中最常用的容器,没有之一。

第一部分:字符串流 stringstream
一、什么是字符串流
字符串流(stringstream)是 C++ 提供的以 string 字符串为操作对象 的流。它允许你像操作 cin/cout 一样操作字符串------从字符串中读取数据,或将数据格式化写入字符串。

与缓冲流 strstream 的区别:
| 对比项 | strstream(已废弃) |
stringstream(推荐) |
|---|---|---|
| 操作对象 | char* 字符数组 |
string 对象 |
| 内存管理 | 手动管理 char* |
自动管理,无需手动分配释放 |
| 标准状态 | C++98 标记废弃 | C++ 标准推荐 |
| 头文件 | <strstream> |
<sstream> |
二、istringstream:从字符串读取数据
istringstream 允许你像用 cin 从键盘读数据一样,从一个 string 中读取数据。
cpp
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main() {
string s1 = "123 456";
istringstream stream1(s1); // 绑定到字符串 s1
int a, b;
stream1 >> a >> b; // 从字符串中读取两个整数
cout << "a = " << a << endl; // 输出:123
cout << "b = " << b << endl; // 输出:456
return 0;
}
原理图解:

应用场景:解析字符串中的数值
cpp
// 解析 "21.54345+35.7481" 这样的字符串
string s = "21.54345+35.7481";
istringstream iss(s);
double x, y;
char op;
iss >> x >> op >> y; // x = 21.54345, op = '+', y = 35.7481
三、ostringstream:向字符串写入数据
ostringstream 允许你像用 cout 输出一样,把数据格式化后写入到一个 string 中。
cpp
#include <iostream>
#include <sstream>
#include <iomanip>
#include <string>
using namespace std;
int main() {
int a = 123, b = 456;
// 创建输出字符串流,可以指定初始内容和打开模式
ostringstream os("ask:", ios::out | ios::app);
// 像 cout 一样向流中写入
os << hex << showbase << a << "+" << b << "=" << (a + b) << endl;
// .str() 获取流中的完整字符串
cout << os.str() << endl;
// 输出:ask:0x7b+0x1c8=0x243
return 0;
}
ostringstream 打开模式:
| 模式 | 说明 |
|---|---|
ios::out |
输出模式(默认) |
ios::app |
追加模式(不清空原有内容) |
ios::ate |
打开时定位到末尾 |
注意 :如果不指定 ios::app,每次 << 会清空之前的内容。指定 ios::app 后,新内容追加到已有内容后面。

四、stringstream:同时读写
stringstream 同时继承了 istream 和 ostream,可以先读后写 或先写后读。
cpp
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main() {
string s3 = "21.54345+35.7481";
// 创建读写字符串流
stringstream ss(s3, ios::in | ios::out);
double x, y;
char ch;
// 1. 先从字符串中读取数据
ss >> x >> ch >> y;
// 2. 计算
double result = 0;
switch (ch) {
case '+': result = x + y; break;
case '-': result = x - y; break;
case '*': result = x * y; break;
case '/': result = x / y; break;
}
// 3. 清除状态后,向同一个流写入结果
ss.clear();
ss << x << ch << y << "=" << result;
// 4. 获取最终字符串
cout << ss.str() << endl;
// 输出:21.5434+35.7481=57.2915
return 0;
}
注意 :读取完后流的状态可能变为 eof,需要用 ss.clear() 清除状态标志,才能继续写入。
五、stringstream 的实际应用
5.1 数值与字符串互转(比 sprintf 更安全)
cpp
// 数值 → 字符串
template<typename T>
string toString(T value) {
ostringstream oss;
oss << value;
return oss.str();
}
// 字符串 → 数值
template<typename T>
T fromString(const string& str) {
T value;
istringstream iss(str);
iss >> value;
return value;
}
// 使用
string s = toString(3.14159); // "3.14159"
double d = fromString<double>("2.71828"); // 2.71828
5.2 格式化输出为 JSON
cpp
class Person {
private:
int pid;
string name;
string sex;
int age;
public:
Person() : pid(0), name(""), sex("男"), age(1) {}
friend istream& operator>>(istream& cin, Person& p) {
cout << "请输入人员信息:";
cin >> p.pid >> p.name >> p.sex >> p.age;
return cin;
}
friend ostream& operator<<(ostream& cout, Person& p) {
ostringstream os;
os << "{\"pid\":" << p.pid
<< ",\"name\":\"" << p.name << "\""
<< ",\"sex\":\"" << p.sex << "\""
<< ",\"age\":" << p.age << "}";
cout << os.str() << endl;
return cout;
}
};
5.3 拼接 SQL 语句
cpp
string buildInsertSQL(const string& table, const vector<pair<string,string>>& fields) {
ostringstream sql;
sql << "INSERT INTO " << table << " (";
for (size_t i = 0; i < fields.size(); i++) {
if (i > 0) sql << ", ";
sql << fields[i].first;
}
sql << ") VALUES (";
for (size_t i = 0; i < fields.size(); i++) {
if (i > 0) sql << ", ";
sql << "'" << fields[i].second << "'";
}
sql << ")";
return sql.str();
}
第二部分:STL 概述
一、STL 六大组件

二、容器分类
| 分类 | 容器 | 底层实现 | 特点 |
|---|---|---|---|
| 顺序容器 | vector |
动态数组 | 连续存储,尾部操作快,随机访问 |
| 顺序容器 | deque |
多数组 | 双端操作快,分段连续 |
| 顺序容器 | list |
双向链表 | 任意位置插入删除快,无随机访问 |
| 关联容器 | set/map |
红黑树 | 自动排序,O(log n) 查找 |
| 无序容器 | unordered_set/map |
哈希表 | O(1) 查找,无序 |
第三部分:vector 深度详解
一、vector 的底层结构
vector 的底层是一个动态数组 ,在堆上分配连续内存。

二、创建方式
cpp
#include <vector>
#include <iostream>
using namespace std;
int main() {
// 1. 默认构造:空 vector
vector<int> v1;
// 2. 初始化列表(C++11)
vector<int> v2 = {5, 2, 1, 7, 9, 6};
vector<int> v2b({5, 2, 1, 7, 9, 6}); // 等价写法
// 3. 从其他容器的迭代器范围构造 [first, last)
vector<int> v3(v2.begin(), v2.begin() + 3); // {5, 2, 1}
// 4. 从数组构造
int arr[] = {11, 21, 33, 8};
vector<int> v4(arr, arr + 4); // {11, 21, 33, 8}
// 5. 指定大小和初始值
vector<int> v5(10); // 10 个元素,默认值 0
vector<int> v6(10, 42); // 10 个元素,初始值都是 42
return 0;
}
构造方式对比:
| 构造方式 | 语法 | 说明 |
|---|---|---|
| 默认 | vector<T> v |
空的 |
| 初始化列表 | vector<T> v = {a, b, c} |
C++11 |
| 迭代器范围 | vector<T> v(first, last) |
左闭右开 |
| 数组 | vector<T> v(arr, arr+n) |
指针即迭代器 |
| 指定大小 | vector<T> v(n) |
n 个默认值 |
| 指定大小+值 | vector<T> v(n, val) |
n 个 val |
三、大小与容量
cpp
vector<int> v = {1, 2, 3};
cout << v.size() << endl; // 3 --- 元素个数
cout << v.capacity() << endl; // 可能 3 或更大 --- 容量
cout << v.empty() << endl; // 0 --- 是否为空
cout << v.max_size() << endl; // 理论最大容量
size 与 capacity 的关系:

四、元素访问
cpp
vector<int> v = {10, 20, 30, 40, 50};
// 1. operator[] --- 不检查越界
int a = v[0]; // 10
int b = v[2]; // 30
// v[100]; // 越界!未定义行为
// 2. at() --- 检查越界,越界抛出 out_of_range 异常
int c = v.at(0); // 10
// v.at(100); // 抛出 std::out_of_range
// 3. front() / back()
int first = v.front(); // 10
int last = v.back(); // 50
// 4. data() --- 返回底层数组的指针
int* ptr = v.data();
// ptr[0] == 10, ptr[1] == 20, ...
| 访问方式 | 越界检查 | 适用场景 |
|---|---|---|
v[i] |
❌ 不检查 | 确定不越界时使用,性能最好 |
v.at(i) |
✅ 检查 | 不确定时使用,安全 |
front() |
--- | 第一个元素 |
back() |
--- | 最后一个元素 |
五、添加元素
cpp
vector<int> v = {1, 2, 3};
// 1. push_back() --- 尾部追加(最常用,最高效)
v.push_back(4); // {1, 2, 3, 4}
// 2. emplace_back() --- 尾部原地构造(比 push_back 更高效)
v.emplace_back(5); // {1, 2, 3, 4, 5}
// 3. insert() --- 指定位置插入
v.insert(v.begin(), 0); // 在开头插入 0
v.insert(v.begin() + 1, {7, 8, 9}); // 在第二个位置插入多个
// 4. emplace() --- 指定位置原地构造
v.emplace(v.end(), 10); // 在末尾原地构造
push_back vs emplace_back 的区别:
cpp
struct Point {
int x, y;
Point(int x, int y) : x(x), y(y) {
cout << "构造 Point(" << x << "," << y << ")" << endl;
}
Point(const Point& p) : x(p.x), y(p.y) {
cout << "拷贝 Point" << endl;
}
};
vector<Point> v;
v.reserve(10);
// push_back:先构造临时对象,再拷贝到 vector
v.push_back(Point(3, 4));
// 输出:构造 Point(3,4) → 拷贝 Point
// emplace_back:直接在 vector 内部构造,省去拷贝
v.emplace_back(5, 6);
// 输出:构造 Point(5,6)
插入位置与效率:
| 插入位置 | 时间复杂度 | 说明 |
|---|---|---|
尾部 (push_back) |
O(1) 均摊 | 扩容时 O(n) |
头部/中间 (insert) |
O(n) | 需要移动后续元素 |
六、删除元素
cpp
vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8};
// 1. pop_back() --- 删除最后一个元素
v.pop_back(); // {1, 2, 3, 4, 5, 6, 7}
// 2. erase() --- 删除指定位置的元素
v.erase(v.begin()); // 删除第一个 → {2, 3, 4, 5, 6, 7}
// 3. erase() --- 删除指定范围 [first, last)
v.erase(v.begin(), v.begin() + 3); // 删除前3个 → {5, 6, 7}
// 4. clear() --- 清空所有元素
v.clear(); // size = 0, capacity 不变
删除效率:
| 删除位置 | 时间复杂度 | 说明 |
|---|---|---|
尾部 (pop_back) |
O(1) | |
头部/中间 (erase) |
O(n) | 需要移动后续元素 |
全部 (clear) |
O(n) |
七、遍历
cpp
vector<int> v = {1, 2, 3, 4, 5};
// 方式1:下标遍历
for (size_t i = 0; i < v.size(); i++) {
cout << v[i] << " ";
}
// 方式2:范围 for(C++11,最简洁)
for (int val : v) {
cout << val << " ";
}
// 方式3:范围 for 引用(可修改元素)
for (int& val : v) {
val *= 2; // 每个元素翻倍
}
// 方式4:迭代器(可读可写)
for (vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
cout << *it << " ";
}
// 方式5:只读迭代器
for (vector<int>::const_iterator it = v.cbegin(); it != v.cend(); ++it) {
cout << *it << " ";
// *it = 10; // 编译错误!只读
}
迭代器类型:

八、容量管理
cpp
vector<int> v;
// 1. reserve() --- 预留空间,避免多次扩容
v.reserve(1000); // 提前分配 1000 个元素的空间
for (int i = 0; i < 1000; i++) {
v.push_back(i);
// 这些 push_back 都不会触发扩容!
}
// 2. shrink_to_fit() --- 释放多余容量
v.shrink_to_fit(); // capacity 缩小到 size
// 3. resize() --- 改变 size
v.resize(500); // 截断到 500 个元素(或扩充,新增默认值)
为什么要 reserve?

九、扩容机制
cpp
vector<int> v;
for (int i = 0; i < 20; i++) {
v.push_back(i);
cout << "size: " << v.size()
<< ", capacity: " << v.capacity() << endl;
}
典型输出(GCC/Clang,扩容因子 2):

扩容因子的区别:
| 编译器 | 扩容因子 | 说明 |
|---|---|---|
| GCC/Clang | 2 | 每次翻倍 |
| MSVC | 1.5 | 每次增加 50% |
扩容的代价 :重新分配更大的空间 → 拷贝/移动旧数据 → 释放旧空间。这就是为什么 reserve() 很重要。
十、vector 完整操作速查
| 操作 | 方法 | 复杂度 |
|---|---|---|
| 尾部插入 | push_back(x) / emplace_back(x) |
O(1) 均摊 |
| 任意插入 | insert(pos, x) / emplace(pos, x) |
O(n) |
| 尾部删除 | pop_back() |
O(1) |
| 任意删除 | erase(pos) |
O(n) |
| 清空 | clear() |
O(n) |
| 大小 | size() |
O(1) |
| 容量 | capacity() |
O(1) |
| 判空 | empty() |
O(1) |
| 访问 | [i] / at(i) / front() / back() |
O(1) |
| 预留 | reserve(n) |
O(n) |
| 收缩 | shrink_to_fit() |
O(n) |
第四部分:缓冲流 strstream(了解即可)
strstream 是早期 C++ 的字符数组流,操作 char* 而非 string。已被标记为废弃,仅用于维护旧代码。
cpp
#include <strstream>
int main() {
// 输入:从 char 数组读取
char buf[] = "hi,C++IO流";
istrstream iss(buf, 128);
char buf1[3] = {0};
iss.read(buf1, 2); // 读 2 字节 → "hi"
cout << buf1 << endl;
iss.ignore(4); // 跳过 4 字节
char buf2[5] = {0};
iss.read(buf2, 4); // 读 4 字节
cout << buf2 << endl;
// 输出:向 char 数组写入
char buf3[20] = {0};
ostrstream oss(buf3, 20);
oss << "verygood";
cout << oss.str() << endl; // 返回 char*
return 0;
}
不推荐使用 ,新代码一律使用 stringstream。
总结
一、字符串流要点
| 类 | 头文件 | 操作对象 | 用途 |
|---|---|---|---|
istringstream |
<sstream> |
string |
从字符串读取 |
ostringstream |
<sstream> |
string |
向字符串写入 |
stringstream |
<sstream> |
string |
读写字符串 |
二、vector 核心要点

三、一句话记忆
stringstream 把字符串当 IO 流操作,vector 把数组当容器管理------前者用 >> 和 << 读写字符串,后者用 push_back 和迭代器管理动态数组。