C++ 数组、Vector 与容器:总结与章节测验

鼓励的话

本章内容并不轻松。我们涵盖了大量知识,也揭露了 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::vectorstd::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 个默认初始化 intstd::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
...

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"