鼓励的话
本章内容并不轻松。我们涵盖了大量知识,也揭露了 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
...