第3章:字符串、向量和数组(Strings, Vectors, and Arrays)
一、导读
1.1 本章学习目标
第3章介绍C++标准库最重要的容器:string和vector,以及内置数组。这些是C++编程中最常用的数据结构。本章核心目标:
- 掌握string类的丰富操作:创建、拼接、比较、查找、子串
- 理解vector的动态数组特性:push_back、size、capacity的关系
- 学会使用迭代器:访问容器的通用方式
- 掌握范围for语句:C++11的简化遍历方式
- 理解内置数组的局限性:为什么推荐使用vector和string
- 了解array容器:C++11的固定大小数组
1.2 为什么本章如此重要
string和vector是C++编程中使用频率最高的类型。几乎所有C++程序都会用到它们。理解它们的接口、性能特征和正确使用方式,是编写高效C++代码的基础。特别是迭代器的概念,它是连接容器和算法的桥梁,贯穿整个STL。
1.3 核心问题引导
string的 size() 和 capacity() 有什么区别?什么时候需要 reserve()?
vector的扩容机制是怎样的?为什么push_back的摊还时间是O(1)?
迭代器失效是什么意思?什么操作会导致迭代器失效?
范围for和迭代器遍历有什么本质区别?
为什么C++推荐使用string而不是字符数组?
多维数组在内存中是如何存储的?
二、核心概念详解
2.1 string类
2.1.1 string的初始化
#include <string>
#include <iostream>
int main() {
std::string s1; // 空字符串
std::string s2 = "Hello"; // 拷贝初始化
std::string s3("World"); // 直接初始化
std::string s4(5, 'A'); // 5个'A'
std::string s5(s2); // 拷贝构造
std::string s6(s2, 0, 3); // 从s2的0位置开始取3个字符:"Hel"
std::cout << s1 << "|" << s2 << "|" << s3 << "|"
<< s4 << "|" << s5 << "|" << s6 << std::endl;
return 0;
}2.1.2 string的操作
#include <string>
#include <iostream>
int main() {
std::string s = "Hello, World!";
// 大小
std::cout << "Size: " << s.size() << std::endl; // 13
std::cout << "Empty: " << s.empty() << std::endl; // 0
std::cout << "Capacity: " << s.capacity() << std::endl;
// 访问元素
std::cout << s[0] << std::endl; // H
std::cout << s.at(7) << std::endl; // W
std::cout << s.front() << std::endl; // H
std::cout << s.back() << std::endl; // !
// 拼接
std::string s1 = "Hello";
std::string s2 = " World";
std::string s3 = s1 + s2; // "Hello World"
s1 += s2; // "Hello World"
s1.append("!"); // "Hello World!"
// 比较
std::string a = "apple";
std::string b = "banana";
std::cout << (a < b) << std::endl; // 1(字典序比较)
std::cout << a.compare(b) << std::endl; // 负数
// 查找
std::string text = "Hello, World!";
auto pos = text.find("World"); // 7
auto pos2 = text.find("xyz"); // std::string::npos
if (pos2 == std::string::npos) {
std::cout << "Not found" << std::endl;
}
// 子串
std::string sub = text.substr(7, 5); // "World"
// 替换和插入
text.replace(7, 5, "C++"); // "Hello, C++!"
text.insert(7, "dear "); // "Hello, dear C++!"
// 删除
text.erase(7, 6); // "Hello, C++!"
return 0;
}2.2 vector类
vector是动态数组,可以在运行时自动增长:
#include <vector>
#include <iostream>
int main() {
// 初始化
std::vector<int> v1; // 空vector
std::vector<int> v2 = {1, 2, 3, 4, 5}; // 列表初始化
std::vector<int> v3(5, 42); // 5个42
std::vector<int> v4(v2); // 拷贝构造
// 添加元素
v1.push_back(10);
v1.push_back(20);
v1.push_back(30);
// 大小和容量
std::cout << "Size: " << v1.size() << std::endl; // 3
std::cout << "Capacity: " << v1.capacity() << std::endl; // >= 3
std::cout << "Empty: " << v1.empty() << std::endl; // 0
// 访问元素
std::cout << v1[0] << std::endl; // 10
std::cout << v1.at(1) << std::endl; // 20(带边界检查)
std::cout << v1.front() << std::endl; // 10
std::cout << v1.back() << std::endl; // 30
// 删除元素
v1.pop_back(); // 删除最后一个
// 插入
v1.insert(v1.begin() + 1, 15); // 在位置1插入15
// 删除
v1.erase(v1.begin()); // 删除第一个
// 清空
v1.clear();
// 预分配空间
v1.reserve(100); // 预分配100个元素的空间
std::cout << "Capacity after reserve: " << v1.capacity() << std::endl;
return 0;
}vector的扩容机制
vector在空间不足时会自动扩容,通常容量翻倍:
#include <vector>
#include <iostream>
int main() {
std::vector<int> v;
std::cout << "Initial capacity: " << v.capacity() << std::endl;
for (int i = 0; i < 20; i++) {
v.push_back(i);
std::cout << "Size: " << v.size()
<< ", Capacity: " << v.capacity() << std::endl;
}
// 可以看到capacity在size超过capacity时翻倍增长
return 0;
}2.3 迭代器
迭代器是访问容器中元素的通用方式,类似于指针:
#include <vector>
#include <string>
#include <iostream>
int main() {
std::vector<std::string> words = {"hello", "world", "cpp"};
// 使用迭代器遍历
for (auto it = words.begin(); it != words.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 修改元素
for (auto it = words.begin(); it != words.end(); ++it) {
*it = "prefix_" + *it;
}
// 反向迭代器
for (auto it = words.rbegin(); it != words.rend(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// const迭代器
for (auto it = words.cbegin(); it != words.cend(); ++it) {
// *it = "new"; // 错误:不能通过const迭代器修改
std::cout << *it << " ";
}
return 0;
}迭代器失效
某些操作会使已有的迭代器失效:
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
auto it = v.begin() + 2; // 指向3
// push_back可能使迭代器失效(如果发生扩容)
v.push_back(6);
// *it; // 可能崩溃!迭代器可能已失效
// insert也会使迭代器失效
it = v.begin() + 2; // 重新获取
v.insert(it, 100);
// it现在也失效了
// erase同样
it = v.begin() + 2;
v.erase(it);
// it失效
return 0;
}2.4 范围for语句
C++11的范围for简化了遍历操作:
#include <vector>
#include <string>
#include <iostream>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
// 只读遍历
for (int x : nums) {
std::cout << x << " ";
}
std::cout << std::endl;
// 修改元素(使用引用)
for (int &x : nums) {
x *= 2;
}
// 使用auto
for (const auto &x : nums) {
std::cout << x << " ";
}
std::cout << std::endl;
// 遍历string
std::string s = "Hello";
for (char c : s) {
std::cout << c << " ";
}
std::cout << std::endl;
return 0;
}2.5 内置数组和array
#include <array>
#include <iostream>
int main() {
// 内置数组(不推荐)
int arr[5] = {1, 2, 3, 4, 5};
int len = sizeof(arr) / sizeof(arr[0]);
// C++11 array(推荐替代内置数组)
std::array<int, 5> a = {1, 2, 3, 4, 5};
std::cout << "Size: " << a.size() << std::endl;
std::cout << a[0] << " " << a.at(1) << std::endl;
// array的优势:知道自身大小,不会退化为指针
// 可以使用迭代器
for (auto it = a.begin(); it != a.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 多维数组
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// 使用vector实现多维数组(更灵活)
std::vector<std::vector<int>> grid = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
for (const auto &row : grid) {
for (int x : row) {
std::cout << x << " ";
}
std::cout << std::endl;
}
return 0;
}三、实战案例
3.1 文本处理程序
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
int main() {
std::string text = "The quick brown fox jumps over the lazy dog";
// 分词
std::vector<std::string> words;
std::istringstream iss(text);
std::string word;
while (iss >> word) {
words.push_back(word);
}
// 统计
std::cout << "Words: " << words.size() << std::endl;
// 查找最长单词
auto longest = words.begin();
for (auto it = words.begin(); it != words.end(); ++it) {
if (it->size() > longest->size()) {
longest = it;
}
}
std::cout << "Longest: " << *longest << " (" << longest->size() << " chars)" << std::endl;
// 反转每个单词
for (auto &w : words) {
std::string reversed(w.rbegin(), w.rend());
w = reversed;
}
// 重新拼接
std::string result;
for (size_t i = 0; i < words.size(); i++) {
if (i > 0) result += " ";
result += words[i];
}
std::cout << "Reversed: " << result << std::endl;
return 0;
}3.2 动态数组实现栈
#include <vector>
#include <iostream>
#include <stdexcept>
template<typename T>
class Stack {
std::vector<T> data;
public:
void push(const T &item) {
data.push_back(item);
}
T pop() {
if (data.empty()) throw std::runtime_error("Stack is empty");
T item = data.back();
data.pop_back();
return item;
}
const T& top() const {
if (data.empty()) throw std::runtime_error("Stack is empty");
return data.back();
}
bool empty() const { return data.empty(); }
size_t size() const { return data.size(); }
};
int main() {
Stack<int> s;
for (int i = 1; i <= 5; i++) s.push(i);
while (!s.empty()) {
std::cout << s.pop() << " ";
}
std::cout << std::endl; // 5 4 3 2 1
return 0;
}四、本章小结
第3章介绍了C++最常用的数据结构:
string:功能丰富的字符串类,支持拼接、查找、子串等操作。比C风格字符串更安全、更方便。
vector:动态数组,自动扩容。push_back摊还O(1),随机访问O(1)。是最常用的容器。
迭代器:访问容器的通用方式,连接容器和算法的桥梁。注意迭代器失效问题。
范围for:C++11简化遍历的语法糖,底层使用迭代器实现。
array:C++11的固定大小数组,替代内置数组,知道自身大小。
五、思考与练习
思考题
string的npos是什么值?为什么用它表示"未找到"?
vector的size和capacity有什么区别?什么时候需要reserve?
什么操作会导致vector迭代器失效?如何避免?
为什么范围for中使用 const auto & 而不是 auto?
内置数组和std::array有什么本质区别?
编程练习
实现一个字符串处理函数,统计文本中每个单词出现的频率。
使用vector实现一个队列(enqueue和dequeue操作)。
实现一个简易的文本编辑器,支持插入、删除、查找操作。