1. 容器
1.1 容器的定义
在C++中,容器的概念是指一种对象类型,它可以持有其他对象或指向其他对象的指针。这种对象类型在数据存储上提供了一种有效的方式来管理一组元素。容器在C++中通常是模板类的形式。
一般来说,容器内的元素都是相同类型的。即如果该容器内存放的是int类型的数据,那么其他所有的数据都是int类型的。如果你想存储多种类型的数据,建议使用结构体。
容器和数组的联系与区别
- 联系:数组在广义上可以被视为一种容器,因为它用于存储一组相同类型的元素。然而,在C++标准库的语境中,当我们提到"容器",通常不包括数组。
- 区别:
- 创建和大小调整:
- 数组:在创建数组时,必须指定其大小、且一旦创建,数组的大小就是固定的,不能动态改变。
- 容器:容器的创建不需要预先指定大小,且其大小可以动态改变,根据需要添加或删除元素。
- 存储方式:
- 数组:数组在内存空间上是连续存储的,这意味着数组元素在内存中的位置是连续的,可以通过下标直接访问。
- 容器:不同的容器有不同的存储方式。例如,std::vector 和 std::deque 是连续存储的,而 std::list 则是链式存储的。
- 访问效率:
- 数组:通过数组下标可以直接访问对应位置的元素,因此访问效率非常高。
- 容器:连续存储的容器(如 std::vector 和 std::deque)也支持通过下标访问元素,但链式存储的容器(如 std::list)则不支持,需要遍历链表来查找元素。
- 元素操作:
- 数组:数组的元素操作通常包括赋值、读取等,但由于其连续,在数组中间位置插入或删除元素比较麻烦,需要移动其他元素。
- 容器:容器提供了更丰富的元素操作。例如,std::list 支持在容器中间位置插入或删除元素,而无需移动其他元素。
- 创建和大小调整:
为什么要学习和使用容器
- 动态内存管理:容器类负责动态分配和释放内存,简化了内存管理的复杂性。使用容器时,我们无需担心数组越界、内存泄漏等问题。
- 自动扩展和收缩:一些容器类(如 std::vector)能够自动扩展和收缩,以适应存储元素数量的变化。这意味着我们可以将元素添加到容器中,而无需预先分配固定大小的内存空间。
- 代码复用和可维护性:使用容器类可以提高代码的复用性和可维护性。由于容器类提供了通用的数据结构和操作接口,我们可以将更多的精力放在实现业务逻辑上。
1.2 容器的分类
基于容器内部数据的组织方式、访问方式以及操作特性,容器大致可分为两大类:顺序(序列)容器和关联容器。
1.2.1 顺序容器
顺序容器是将单一类型的元素聚集起来成为容器,并根据元素的插入位置来存储和访问这些元素。顺序容器的元素排列次序与元素值无关,而是由元素添加到容器里的次序决定。
顺序容器常用的方法主要包括以下方面:
- 构造函数:用于创建容器对象
- 赋值操作:使用赋值运算符(=)赋值
- 元素访问:通过下标运算符([])或迭代器访问
- 容器大小:使用 size() 获取元素数量
- 添加元素:push_back()、push_front() 等方法
- 删除元素:erase()、pop_back()、pop_front() 等方法
- 修改元素:通过下标或迭代器修改
- 关系运算符:比较两个容器
- 获取迭代器:begin() 和 end() 成员函数
1.2.1.1 array
在C++标准模板库(STL)中,array 是一个顺序容器,它封装了固定大小的数组,并提供了与自定义数组类似的接口,但增加了类型安全和一些额外的功能。
主要特点:
- 固定大小
- 随机访问
- 类型安全
- 空间连续
常用方法:
cpp
#include <array>
#include <iostream>
int main()
{
// 构造函数
std::array<int, 5> arr1; // 默认构造
std::array<int, 5> arr2 = {1, 2, 3, 4, 5}; // 列表初始化
// 元素访问
arr2[2] = 10; // 通过下标访问
std::cout << arr2.at(3) << std::endl; // 安全访问
// 容量和大小
std::cout << "Size: " << arr2.size() << std::endl;
// 迭代器
for(auto it = arr2.begin(); it != arr2.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 比较操作
if(arr1 == arr2) {
std::cout << "Arrays are equal" << std::endl;
}
return 0;
}1.2.1.2 vector
在C++中,vector(向量)是一个顺序容器,它封装了动态大小数组的功能。
主要特性:
- 动态大小
- 连续内存
- 迭代器支持
常用方法:
cpp
#include <vector>
#include <iostream>
int main()
{
// 创建vector
std::vector<int> vec;
// 添加元素
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
// 访问元素
std::cout << "Element at 1: " << vec[1] << std::endl;
// 大小和容量
std::cout << "Size: " << vec.size() << ", Capacity: " << vec.capacity() << std::endl;
// 插入元素
vec.insert(vec.begin() + 1, 10);
// 删除元素
vec.erase(vec.begin() + 2);
// 遍历
for(const auto& elem : vec) {
std::cout << elem << " ";
}
std::cout << std::endl;
return 0;
}1.2.1.3 deque
std::deque(双端队列)是C++标准库中的一个容器,它允许在序列的两端进行元素的快速插入和删除操作。
主要特点:
- 快速的头部和尾部操作
- 中间操作的效率优于vector
- 随机访问
- 内存使用不如vector紧凑
常用方法:
cpp
#include <deque>
#include <iostream>
int main()
{
std::deque<int> dq;
// 头部插入
dq.push_front(1);
dq.push_front(2);
// 尾部插入
dq.push_back(3);
dq.push_back(4);
// 访问元素
std::cout << "Front: " << dq.front() << ", Back: " << dq.back() << std::endl;
// 中间插入
dq.insert(dq.begin() + 2, 5);
// 删除元素
dq.pop_front();
// 遍历
for(const auto& elem : dq) {
std::cout << elem << " ";
}
std::cout << std::endl;
return 0;
}1.2.1.4 list
std::list 是 C++ 标准模板库(STL)中的一个容器,它表示一个双向链表。
主要特性:
- 双向迭代
- 任意位置插入删除高效
- 非连续内存
- 迭代器稳定性
常用方法:
cpp
#include <list>
#include <iostream>
int main()
{
std::list<int> lst;
// 添加元素
lst.push_back(10);
lst.push_back(20);
lst.push_front(5);
// 插入元素
auto it = lst.begin();
std::advance(it, 1);
lst.insert(it, 15);
// 删除元素
lst.remove(10);
// 反转和排序
lst.reverse();
lst.sort();
// 遍历
for(const auto& elem : lst) {
std::cout << elem << " ";
}
std::cout << std::endl;
return 0;
}总结
| 容器 | 特点 | 适用场景 |
|---|---|---|
| array | 固定大小,连续存储 | 需要固定大小数组时 |
| vector | 动态数组,连续存储 | 需要随机访问,尾部操作频繁 |
| deque | 双端队列,分块存储 | 需要头尾操作频繁 |
| list | 双向链表,非连续存储 | 需要任意位置插入删除 |
根据具体需求选择合适的容器可以优化程序性能和内存使用。
