鼓励的话
本章内容并不轻松。我们涵盖了大量知识,也揭露了 C++ 的一些“疮疤”。祝贺你坚持到了现在!
数组是解锁 C++ 程序强大能力的关键之一。
本章回顾
容器(container)
一种数据类型,用于存储一组无名对象(元素)。当需要处理彼此相关的一组值时,通常使用容器。
元素数量常称为长度(length)或个数(count)。在 C++ 中,也常用 size 表示容器元素数量。
绝大多数语言(含 C++)的容器均为同构(homogenous):所有元素必须类型相同。
容器库(Containers library)
C++ 标准库的一部分,内含多种容器类(container class)实现。
数组(array)
一种容器,连续存储元素序列(元素在内存中紧邻,无间隙),支持快速直接访问任意元素。
C++ 主要提供三种数组:
- C 风格数组
- std::vector容器类
- std::array容器类
std::vector
定义于 <vector> 头文件,为类模板,模板实参指定元素类型,如 std::vector<int>。
容器通常提供列表构造函数(list constructor),可用初始化列表构造实例:
std::vector v{1, 2, 3}; // 列表初始化
下标访问
最常见的方式是使用下标运算符(operator[]),提供 0-based 索引(首元素索引 0)。operator[] 不检查越界,传入无效索引将导致未定义行为。
数组支持随机访问(random access):任何元素可直接、等速访问。
列表构造与其他构造
列表构造优先于其他匹配构造。若用非元素值初始化容器,使用直接初始化:
std::vector v1{5};  // 1 个元素,值为 5
std::vector v2(5);  // 5 个元素,值初始化
std::vector 可设为 const,但不能设为 constexpr。
size_type
标准库容器均含嵌套 typedef size_type,通常为 std::size_t 别名。
访问时需完整限定,如 std::vector<int>::size_type。
长度获取
- size()成员函数返回无符号- size_type长度。
- C++17:std::size()非成员函数。
- C++20:std::ssize()返回带符号长度(通常为std::ptrdiff_t)。
at() 与边界检查at() 成员函数运行时检查越界,越界抛出 std::out_of_range 异常;若未捕获,程序终止。
索引与符号转换operator[] 与 at() 期望 size_type(无符号),非 constexpr 有符号索引会触发符号转换警告。
传参与返回
- 按值传递 std::vector会拷贝,通常用(const)引用避免拷贝。
- 函数模板可接受任意元素类型的 std::vector;可用assert确保长度正确。
复制语义与移动语义
- 复制语义(copy semantics):定义对象如何复制。
- 移动语义(move semantics):当源对象为右值且类型支持移动时,低成本转移数据所有权。
- std::vector、- std::string等可返回按值,自动触发移动。
遍历
顺序访问容器元素称为遍历(traversal)或迭代(iteration)。
- 传统 for 循环需警惕差一错误。
- 基于范围的 for 循环(range-based for) 无需显式索引,优先使用。
- 结合 auto推导类型;元素声明用const auto&避免拷贝,除非需修改或复制。
枚举索引
非作用域枚举可作索引,提升可读性;末尾加计数枚举器,用于断言数组长度。
固定 vs 动态数组
- 固定长度数组:长度实例化时确定,不可改(C 风格数组、std::array)。
- 动态数组:实例化后可改大小(std::vector)。
调整大小与容量
- resize(newLen):改变长度,必要时重分配。
- capacity():返回已分配容量。
- 重新分配(reallocation):代价高昂,尽量规避。
- shrink_to_fit():非强制请求将容量缩减至长度。
栈行为
- LIFO:后进先出。
- push_back()/- pop_back()/- back()实现栈操作。
- reserve()预分配容量,避免频繁重分配。
emplace_back() vs push_back()
- 创建临时对象时,emplace_back()更高效。
- 其余情况优先 push_back()。
std::vector
特化实现,每字节存 8 位,但非真正容器,与标准库不完全兼容,通常应避免。
测验
问题 1
使用 CTAD 写出下列定义:
a) 含前 6 个偶数的 std::vector
b) 含常量值 1.2, 3.4, 5.6, 7.8 的常量 std::vector
c) 含常量 std::string_view 元素 “Alex”, “Brad”, “Charles”, “Dave” 的常量 std::vector
d) 含单元素 12 的 std::vector
e) 含 12 个默认初始化 int 的 std::vector
问题 2
游戏背包系统:玩家可携带 3 类物品:生命药水、火把、箭矢。
步骤 1
- 在命名空间内定义非作用域枚举表示物品类型。
- 定义 std::vector存储每种物品数量,初始为 1、5、10。
- 用计数枚举器断言初始化器数量正确。
You have 16 total items
步骤 2
修改程序,输出:
You have 1 health potion
You have 5 torches
You have 10 arrows
You have 16 total items
- 用循环输出物品数量及名称,处理复数形式。
问题 3
编写函数,接收 std::vector,返回 std::pair<索引, 索引> 表示最小/最大元素下标。
测试向量:
std::vector v1{ 3, 8, 2, 5, 7, 8, 3 };
std::vector v2{ 5.5, 2.7, 3.3, 7.6, 1.2, 8.8, 6.6 };
期望输出:
With array ( 3, 8, 2, 5, 7, 8, 3 ):
The min element has index 2 and value 2
The max element has index 1 and value 8
With array ( 5.5, 2.7, 3.3, 7.6, 1.2, 8.8, 6.6 ):
The min element has index 4 and value 1.2
The max element has index 5 and value 8.8
问题 4
修改上题,让用户输入任意整数,以 -1 结束。
样例输入:3 8 5 2 3 7 -1
输出:
Enter numbers to add (use -1 to stop): 3 8 5 2 3 7 -1
With array ( 3, 8, 5, 2, 3, 7 ):
The min element has index 3 and value 2
The max element has index 1 and value 8
- 首个输入即为 -1 时给出合理处理。
问题 5
实现游戏 C++man(Hangman 变体):
详见原文分步任务(Step #1–#4),包含随机选词、状态显示、字母输入、胜负判定等完整逻辑。
以下为 Question #5 的完整实现,按 Step #1 ~ Step #4 分步给出,代码可直接编译运行。
(已合并所有步骤,保持与原文示例输出一致)
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <random>
#include <limits>
// ---------- Step 1: 词库 ----------
namespace WordList
{
    const std::vector<std::string_view> words {
        "mystery", "broccoli", "account", "almost",
        "spaghetti", "opinion", "beautiful", "distance", "luggage"
    };
    std::string pickRandomWord()
    {
        static std::mt19937 rng{ std::random_device{}() };
        std::uniform_int_distribution<std::size_t> dist(0, words.size() - 1);
        return std::string{ words[dist(rng)] };
    }
}
// ---------- Step 2~4: 游戏会话 ----------
class Session
{
public:
    explicit Session(const std::string& word)
        : m_word{ word }, m_display(word.size(), '_'),
          m_wrongGuessesLeft{ 6 }, m_guessed(26, false)
    {}
    void play()
    {
        std::cout << "Welcome to C++man (a variant of Hangman)\n"
                  << "To win: guess the word.  To lose: run out of pluses.\n\n";
        while (!gameOver())
        {
            display();
            char guess = getUserGuess();
            processGuess(guess);
        }
        displayFinal();
    }
private:
    bool gameOver() const
    {
        return m_wrongGuessesLeft == 0 || m_display == m_word;
    }
    void display() const
    {
        std::cout << "The word: " << m_display
                  << "   Wrong guesses: ";
        for (int i = 0; i < m_wrongGuessesLeft; ++i) std::cout << '+';
        for (char c : m_wrongLetters) std::cout << c;
        std::cout << "\n";
    }
    char getUserGuess()
    {
        while (true)
        {
            std::cout << "Enter your next letter: ";
            std::string input;
            std::getline(std::cin, input);
            if (input.size() != 1 || !std::isalpha(input[0]))
            {
                std::cout << "That wasn't a valid input.  Try again.\n";
                continue;
            }
            char c = std::tolower(input[0]);
            if (m_guessed[c - 'a'])
            {
                std::cout << "You already guessed that.  Try again.\n";
                continue;
            }
            return c;
        }
    }
    void processGuess(char ch)
    {
        m_guessed[ch - 'a'] = true;
        bool hit = false;
        for (std::size_t i = 0; i < m_word.size(); ++i)
            if (m_word[i] == ch)
            {
                m_display[i] = ch;
                hit = true;
            }
        if (hit)
            std::cout << "Yes, '" << ch << "' is in the word!\n";
        else
        {
            std::cout << "No, '" << ch << "' is not in the word!\n";
            --m_wrongGuessesLeft;
            m_wrongLetters.push_back(ch);
        }
    }
    void displayFinal() const
    {
        if (m_display == m_word)
            std::cout << "Congratulations, you won!  The word was: " << m_word << '\n';
        else
            std::cout << "You lost!  The word was: " << m_word << '\n';
    }
    const std::string m_word;
    std::string m_display;
    int m_wrongGuessesLeft;
    std::vector<bool> m_guessed;
    std::string m_wrongLetters;
};
// ---------- 主函数 ----------
int main()
{
    Session game{ WordList::pickRandomWord() };
    game.play();
    return 0;
}
运行示例(一次可能的结果):
Welcome to C++man (a variant of Hangman)
To win: guess the word.  To lose: run out of pluses.
The word: ________   Wrong guesses: ++++++
Enter your next letter: a
No, 'a' is not in the word!
The word: ________   Wrong guesses: +++++a
Enter your next letter: b
Yes, 'b' is in the word!
The word: b_______   Wrong guesses: +++++a
Enter your next letter: c
Yes, 'c' is in the word!
The word: b__cc___   Wrong guesses: +++++a
...
