C++ STL 容器完全指南(二):vector 深入与 stringstream 实战

引言

在上一篇文章中,我们学习了 C++ IO 流体系的基础------标准输入输出流 cin/cout 和文件流 fstream。本文将继续深入两个重要主题:

  1. 字符串流 stringstream :以 string 为操作对象的流,用于数据格式转换和字符串拼接

  2. 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 同时继承了 istreamostream,可以先读后写先写后读

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 和迭代器管理动态数组。

相关推荐
澈2076 小时前
C++并查集:高效解决连通性问题
java·c++·算法
郝学胜-神的一滴7 小时前
Qt 入门 01-01:从零基础到商业级客户端实战
开发语言·c++·qt·程序人生·软件构建
测试员周周7 小时前
【Appium 系列】第06节-页面对象实现 — LoginPage 实战
开发语言·前端·人工智能·python·功能测试·appium·测试用例
宏笋7 小时前
C++ thread的detach()方法详解
c++
旖-旎7 小时前
深搜练习(单词搜索)(12)
c++·算法·深度优先·力扣
摇滚侠7 小时前
@Autowired 和 @Resource 的区别
java·开发语言
Wy_编程8 小时前
go语言中的结构体
开发语言·后端·golang
SeaTunnel8 小时前
(八)收官篇 | 数据平台最后一公里:数据集成开发设计与上线治理实战
java·大数据·开发语言·白鲸开源
大卡片8 小时前
C++的基础知识点
开发语言·c++