第16.1节 容器与数组入门
一、为什么需要容器?——变量可扩展性问题
1.1 问题场景
假设我们需要记录一个班级30名学生的考试成绩,并计算平均分。按照目前学过的知识,我们可能会这样做:
1 | // 定义30个独立的整型变量 |
计算平均分时:
1 | int average { (testScore1 + testScore2 + testScore3 + ... + testScore30) / 30 }; |
1.2 这种方法的问题
这种方法存在严重的可扩展性问题(scalability problem):
- 代码冗长:需要手动定义和操作每一个变量
- 容易出错:变量名容易打错,修改时容易遗漏
- 维护困难:如果新增一名学生,需要在整个代码中查找并修改所有相关位置
- 不可扩展:如果需要处理100个、1000个数据,这种方法完全不可行
思考:能否用结构体(struct)解决?
1 | struct TestScores { |
虽然结构体提供了一定的组织性,但核心问题依然存在:每个成员仍需要独立的名字和独立的访问方式。
二、容器的概念
2.1 生活中的容器类比
想象你去超市买鸡蛋:
- 你不会单独挑选12个鸡蛋放进购物车
- 而是选择一个鸡蛋盒(carton),它包含预定数量的鸡蛋
再比如早餐麦片:
- 麦片盒里有成百上千的麦片颗粒
- 你不可能把每一颗麦片单独存放
容器的本质:用一个整体来管理一组相关的对象。
2.2 编程中的容器定义
容器(Container):一种数据类型,用于存储一组无名对象(unnamed objects)的集合,这些对象称为元素(elements)。
关键特征:
- 容器本身有名字(如变量名)
- 容器内的元素没有独立的名字
- 这正是容器与结构体的本质区别
2.3 你已经使用过的容器
std::string 就是一个容器!
1 |
|
- 容器名:
name - 元素:
'A','l','e','x'(这些字符没有独立的名字)
三、容器的核心特性
3.1 元素的无名性
为什么元素不需要名字?
这是容器设计的核心理念:
- 如果每个元素都需要名字,我们又回到了最初的问题
- 无名性使得容器可以容纳任意数量的元素
- 容器提供统一的访问机制来操作这些元素
重要概念:
容器的元素没有独立的名字,这样容器才能容纳任意数量的元素,而不需要为每个元素指定唯一的名字。每种容器都提供特定的方法来访问其元素。
3.2 容器的长度
长度(Length) 或 计数(Count):容器中元素的数量。
示例:
1 |
|
输出:Alex has 4 characters
术语说明:
- Length(长度):元素的数量(推荐使用)
- Size(大小):在C++中也常用来表示元素数量,但容易与内存大小混淆
- sizeof:返回对象占用的字节数
本教程中,我们用”长度”表示元素数量,用”大小”表示内存占用。
四、容器的基本操作
回到鸡蛋盒的例子,你可以对它进行哪些操作?
- 获取一盒鸡蛋
- 打开盒子并选择一个鸡蛋
- 移除或添加鸡蛋
- 计数鸡蛋数量
4.1 容器的标准操作
大多数容器支持以下操作:
| 操作类别 | 具体操作 | 英文术语 |
|---|---|---|
| 创建 | 创建空容器、指定初始容量、从列表初始化 | Create/Initialize |
| 访问 | 获取首元素、尾元素、任意元素 | Access |
| 修改 | 插入元素、删除元素 | Insert/Remove |
| 查询 | 获取元素数量 | Query/Size |
4.2 不同容器的性能差异
不同容器类型在操作性能上有显著差异:
- 容器A:快速访问任意元素,但不支持插入/删除
- 容器B:快速插入/删除,但只能顺序访问元素
选择合适的容器类型对代码的可维护性和性能至关重要。
五、元素类型
5.1 同质性容器
在大多数编程语言(包括C++)中,容器是同质的(homogeneous):
- 容器内所有元素必须是相同类型
- 例如:整数容器只能存储整数,字符串容器只能存储字符串
5.2 C++中的类型灵活性
C++容器通常实现为类模板(class template):
1 | std::vector<int> intVector; // 整数容器 |
优势:
- 不需要为每种元素类型创建新的容器类
- 通过模板类型参数(template type argument)指定元素类型
- 一次实现,多种类型复用
5.3 异质性容器
异质容器(heterogeneous container):允许不同类型的元素。
- 常见于脚本语言(如Python)
- C++标准库不直接支持(但可通过高级技术实现)
六、C++中的容器库
6.1 容器库概述
容器库(Containers Library):C++标准库的一部分,包含多种实现常见容器的类类型。
容器类(Container Class):实现容器功能的类类型。
完整列表参见:cppreference - Containers
6.2 C++容器的严格定义
注意:C++对”容器”的定义比通用编程定义更严格。
以下类型不是C++标准意义上的容器:
- C风格数组(C-style arrays)
std::stringstd::vector<bool>
原因:它们未实现容器的所有必需接口(详见容器要求)。
但 std::string 和 std::vector<bool> 实现了大部分要求,因此被称为伪容器(pseudo-containers)。
6.3 最常用的容器
在所有容器类中,std::vector 和 std::array 使用最广泛,是本课程的重点。
七、数组简介
7.1 数组的定义
数组(Array):一种容器数据类型,以连续方式(contiguously)存储一系列值。
连续存储:每个元素放置在相邻的内存位置,没有间隙。
数组的优势:
- 快速、直接访问任意元素
- 概念简单,易于使用
- 处理相关值集合的首选方案
7.2 C++中的三种数组类型
| 数组类型 | 引入时间 | 特点 | 别名 |
|---|---|---|---|
| C风格数组 | 继承自C语言 | 核心语言特性,行为怪异,不安全 | C arrays, naked arrays, built-in arrays |
std::vector |
C++03 | 最灵活,功能丰富,动态大小 | 动态数组 |
std::array |
C++11 | 替代C风格数组,固定大小,更安全高效 | 固定数组 |
7.3 各类型数组的应用场景
C风格数组:
- 遗留代码维护
- 与C库交互
- 现代C++中应避免使用
std::vector:- 元素数量动态变化
- 需要频繁插入/删除
- 最常用的数组类型
std::array:- 元素数量固定
- 追求最高性能
- 小型数组
八、学习路线
8.1 下一步学习内容
下一节课我们将:
- 深入学习
std::vector - 解决本节开头提出的30名学生成绩问题
- 引入相关的新概念
- 解决实际应用中的挑战
8.2 学习策略
好消息:所有容器类具有相似的接口。
- 学会一个容器(如
std::vector),其他容器(如std::array)就容易多了 - 后续学习其他容器时,我们将重点关注差异点和重要特性
8.3 术语约定
本教程中:
- 容器类(container classes):指标准库中的大多数或所有容器类
- 数组(array):泛指所有数组类型,包括其他编程语言中的数组
std::vector 同时属于这两个类别,因此即使使用不同术语,也适用于 std::vector。
九、本节小结
核心概念回顾
- 容器:存储无名元素集合的数据类型
- 元素无名性:容器可扩展性的关键
- 同质性:C++容器要求元素类型一致
- 数组:连续存储的容器,支持快速访问
- 三种数组:C风格数组、
std::vector、std::array
关键英文术语
- Container(容器)
- Element(元素)
- Length/Count(长度/计数)
- Homogeneous(同质的)
- Contiguously(连续地)
- Container Class(容器类)
- C-style Array(C风格数组)
准备好了吗?让我们开始深入学习 std::vector! 🚀
