3C++ Primer(第5版)

字符串、向量和数组

标准库容器

stringvectorarray迭代器

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第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的初始化

cpp
#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的操作

cpp
#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是动态数组,可以在运行时自动增长:

cpp
#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在空间不足时会自动扩容,通常容量翻倍:

cpp
#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 迭代器

迭代器是访问容器中元素的通用方式,类似于指针:

cpp
#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;
}

迭代器失效

某些操作会使已有的迭代器失效:

cpp
#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简化了遍历操作:

cpp
#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

cpp
#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 文本处理程序

cpp
#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 动态数组实现栈

cpp
#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操作)。

实现一个简易的文本编辑器,支持插入、删除、查找操作。


3/12字符串、
0%