文章目录
- c++ 顺序容器概述
- 容器通用操作
- 1. 构造和析构
- 示例:`std::vector` 的构造
- 2. 迭代器操作
- 示例:使用迭代器遍历 `std::vector`
- 3. 容量查询
- 示例:检查 `std::vector` 的大小
- 4. 元素访问
- 示例:访问 `std::vector` 的元素
- 5. 修改操作
- 示例:修改 `std::vector`
- 完整代码示例
- 迭代器
- 1. 迭代器的概念和类型
- 2. 迭代器的操作
- 示例:使用迭代器遍历 `std::vector`
- 3. 迭代器的高级应用
- 示例:使用迭代器进行插入和删除
- 4. 注意事项
- 完整代码示例
- 容器定义和初始化
- 示例代码
- 解释
- C++ 容器赋值和 Swap
- 1. 赋值操作
- 示例:`std::vector` 的赋值
- 2. Swap 操作
- 示例:使用 `swap` 交换 `std::vector` 的内容
- 完整代码示例
- 容器大小操作
- 1. 检查是否为空
- 示例:检查 `std::vector` 是否为空
- 2. 获取大小
- 示例:获取 `std::vector` 的大小
- 3. 改变大小
- 示例:改变 `std::vector` 的大小
- 4. 添加和删除元素
- 示例:在 `std::vector` 中添加和删除元素
- 完整代码示例
- 顺序容器操作
- 1. 访问元素
- 示例:访问 `std::vector` 中的元素
- 2. 前后元素访问
- 示例:访问 `std::vector` 的第一个和最后一个元素
- 3. 迭代器
- 示例:遍历 `std::vector`
- 4. 修改容器
- 示例:修改 `std::vector`
- 5. 容器大小
- 示例:检查 `std::vector` 的大小和状态
- 完整代码示例
- 特殊的 forward_list 操作
- 1. 在指定位置前插入元素
- 示例:在 `std::forward_list` 中插入元素
- 2. 删除元素
- 示例:删除 `std::forward_list` 中的元素
- 3. 改变容器大小
- 4. 添加和删除元素
- 示例:在 `std::forward_list` 的开头添加和删除元素
- 完整代码示例
- 容器操作可能使迭代器失效
- 1. 添加或删除元素
- 示例:`std::vector` 添加元素导致迭代器失效
- 2. `std::vector` 和 `std::string` 的动态内存重新分配
- 3. `std::list` 和 `std::forward_list` 的元素删除
- 示例:`std::list` 删除元素导致迭代器失效
- 4. `std::map` 和 `std::set` 的元素删除
- 防止迭代器失效的策略
- 完整代码示例
- Vector 对象的增长机制
- 1. 动态内存分配
- a. 计算新的容量
- b. 分配新内存
- c. 移动元素
- d. 释放旧内存
- 2. 复杂性和性能
- 3. 保留额外容量
- 示例
- 额外的 string 操作
- 1. 字符串连接
- 示例:字符串连接
- 2. 查找和替换
- 示例:查找和替换
- 3. 插入和删除
- 示例:插入和删除
- 4. 子字符串
- 示例:提取子字符串
- 5. 字符串比较
- 示例:字符串比较
- 完整代码示例
- 容器适配器
- 1. Stack(栈)
- 示例:使用 `std::stack`
- 2. Queue(队列)
- 示例:使用 `std::queue`
- 3. Priority Queue(优先队列)
- 示例:使用 `std::priority_queue`
c++ 顺序容器概述
C++ 中的顺序容器是标准模板库(STL)的一部分,它提供了一组用于存储和管理对象序列的模板类。这些容器在内存中以线性顺序存储元素,允许快速顺序访问。顺序容器的主要类型包括:
vector
:动态数组,支持快速随机访问。在向量的末尾添加或删除元素比较高效,但在中间或开头插入或删除元素可能较慢。deque
(双端队列):与 vector 类似,但设计用于在两端快速插入和删除元素。list
:双向链表,支持在任何位置快速插入和删除元素,但不支持快速随机访问。forward_list
:单向链表,与 list 类似,但仅支持向前遍历。相比 list,它使用更少的内存。array
:固定大小的数组。它提供与内置数组类似的功能,但增加了一些额外的功能,如 STL 兼容的迭代器。string
:专门用于字符的容器,提供了对字符串进行操作的丰富功能。
容器通用操作
在 C++ 中,标准模板库(STL)提供了多种容器,每种容器都支持一些通用操作。下面是这些操作的详细介绍
1. 构造和析构
构造:容器可以通过多种方式构造,如默认构造、复制构造、范围构造等。
析构:容器对象离开其作用域时,其析构函数会被自动调用,释放所有资源。
示例:std::vector
的构造
2. 迭代器操作
begin()
和 end()
:返回指向容器第一个元素和尾后元素的迭代器。rbegin()
和 rend()
:返回反向迭代器。
示例:使用迭代器遍历 std::vector
3. 容量查询
empty()
:检查容器是否为空。size()
:返回容器中元素的数目。
示例:检查 std::vector
的大小
4. 元素访问
operator[]
或 at()
:用于访问元素。front()
和 back()
:访问第一个和最后一个元素。
示例:访问 std::vector
的元素
5. 修改操作
insert()
:在指定位置插入元素。erase()
:删除一个或一范围的元素。push_back()
和 pop_back()
:在末尾添加和移除元素。
示例:修改 std::vector
完整代码示例
下面是一个综合示例,综合了上述所有操作。
输出:
- 原始向量:打印了初始向量中的元素(
1 2 3 4 5
)。 - 容器状态查询:
- 向量非空(
Is empty: 0
,0
表示false
)。 - 向量的初始大小为
5
。
- 元素访问:
- 第一个元素是
1
。 - 最后一个元素是
5
。
- 修改向量:
- 向量末尾添加了一个元素
6
。 - 从向量开头移除了第一个元素(原来的
1
)。
- 修改后的向量:最终,修改后的向量元素为
2 3 4 5 6
。
迭代器
1. 迭代器的概念和类型
概念:迭代器是一个允许程序员在容器(如数组或STL容器)上进行遍历的对象。它类似于指针,但提供了更高层次的抽象。
类型:
- 输入迭代器:仅用于从容器中读取数据。
- 输出迭代器:仅用于向容器写入数据。
- 前向迭代器:用于单向读写操作。
- 双向迭代器:支持向前和向后遍历。
- 随机访问迭代器:支持直接访问任意元素,类似数组。
2. 迭代器的操作
操作:
- 解引用:通过
*iterator
访问迭代器指向的元素。 - 自增:
++iterator
移动到下一个元素。 - 自减:对于双向迭代器,
--iterator
移动到前一个元素。 - 比较:使用
==
和!=
比较迭代器。
示例:使用迭代器遍历 std::vector
3. 迭代器的高级应用
应用:迭代器用于容器的许多操作,如插入、删除元素。
示例:使用迭代器进行插入和删除
4. 注意事项
注意:
- 容器被修改时,迭代器可能失效。
- 某些容器(如
std::list
)不支持随机访问迭代器。
完整代码示例
输出:
容器定义和初始化
std::vector
- 定义:
std::vector
是一个动态数组,可以存储任意数量的同类型元素。 - 初始化:
- 空的
vector
:std::vector<int> vec;
- 初始化列表:
std::vector<int> vec = {1, 2, 3, 4, 5};
- 指定大小和初始值:
std::vector<int> vec(10, 0);
(10个元素,每个都是0)
std::list
- 定义:
std::list
是一个双向链表,适用于频繁的插入和删除操作。 - 初始化:
- 空的
list
:std::list<int> lst;
- 初始化列表:
std::list<int> lst = {1, 2, 3, 4, 5};
std::map
- 定义:
std::map
是一个基于键-值对的关联容器,每个键都是唯一的。 - 初始化:
- 空的
map
:std::map<int, std::string> mp;
- 初始化列表:
std::map<int, std::string> mp = {{1, "one"}, {2, "two"}};
示例代码
以下是一些容器定义和初始化的示例代码。
解释
std::vector<int> vec = {1, 2, 3, 4, 5};
- 这行代码创建了一个
std::vector
,并使用初始化列表{1, 2, 3, 4, 5}
进行初始化。
std::list<int> lst = {1, 2, 3, 4, 5};
- 类似地,这行代码创建了一个
std::list
并使用同样的初始化列表。
std::map<int, std::string> mp = {{1, "one"}, {2, "two"}};
- 这里创建了一个
std::map
,其中每个元素都是一个键-值对。
C++ 容器赋值和 Swap
在 C++ 标准模板库(STL)中,容器支持赋值操作和 swap
函数。这些操作是容器类的基本功能,允许容器间的内容交换和赋值。以下是这些操作的详细介绍:
1. 赋值操作
赋值:容器可以使用赋值操作符(=
)将一个容器的内容复制到另一个容器中。
示例:std::vector
的赋值
2. Swap 操作
swap()
:swap
函数交换两个同类型容器的内容。这是一个非常高效的操作,通常只交换容器内部的指针,而不是复制整个容器的数据。
示例:使用 swap
交换 std::vector
的内容
完整代码示例
下面是一个综合示例,演示了赋值和 swap
操作。
输出:
- 赋值操作:将
vec1
的内容赋值给vec2
,使得vec2
的内容变为1 2 3
。 - Swap 操作:交换
vec1
和vec3
的内容,使得vec1
的内容变为4 5 6
,而vec3
的内容变为1 2 3
。
容器大小操作
在 C++ 标准模板库(STL)中,容器提供了多种操作来管理和查询其大小。这些操作允许你检查容器是否为空,获取容器的大小,调整容器的大小,以及在容器的末尾添加或删除元素。以下是这些操作的详细介绍:
1. 检查是否为空
empty()
:这个函数用于检查容器是否为空(即不包含任何元素)。
示例:检查 std::vector
是否为空
2. 获取大小
size()
:返回容器中元素的数量。
示例:获取 std::vector
的大小
3. 改变大小
resize()
:改变容器的大小。如果新大小大于当前大小,则在容器末尾添加元素。如果新大小小于当前大小,则从容器末尾删除元素。
示例:改变 std::vector
的大小
4. 添加和删除元素
push_back()
:在容器末尾添加一个新元素。pop_back()
:删除容器末尾的元素。
示例:在 std::vector
中添加和删除元素
完整代码示例
下面是一个综合示例,演示了上述所有大小操作。
输出:
- 检查是否为空:
vec
不为空(Is empty: 0
,0
表示false
)。 - 获取大小:初始
vec
的大小为3
。 - 改变大小:改变后的
vec
大小为5
,多出的位置用默认值0
填充。 - 添加和删除元素:添加了一个元素
4
然后又删除了,所以最终元素没有变化。
顺序容器操作
在 C++ 的标准模板库(STL)中,顺序容器如 vector
、deque
、list
等提供了一系列操作,使得元素的管理和访问变得灵活高效。以下是顺序容器的一些常用操作的详细介绍:
1. 访问元素
operator[]
和 at()
:用于访问指定位置的元素。at()
方法与 operator[]
相似,但它会检查索引是否越界。
示例:访问 std::vector
中的元素
2. 前后元素访问
front()
和 back()
:分别返回容器中的第一个和最后一个元素的引用。
示例:访问 std::vector
的第一个和最后一个元素
3. 迭代器
begin()
, end()
, rbegin()
, rend()
:分别返回指向容器第一个元素、尾后元素的迭代器,以及对应的反向迭代器。
示例:遍历 std::vector
4. 修改容器
insert()
, erase()
, push_back()
, pop_back()
, clear()
:用于在容器中插入和删除元素。
示例:修改 std::vector
5. 容器大小
size()
, empty()
, resize()
:获取容器的大小,检查是否为空,或者改变容器的大小。
示例:检查 std::vector
的大小和状态
完整代码示例
下面是一个综合示例,展示了上述所有操作。
输出:
特殊的 forward_list 操作
在 C++ 标准模板库(STL)中,std::forward_list
是一个单向链表,它提供了一些特殊的操作,适用于单向链表的特性。这些操作与其他顺序容器(如 std::vector
、std::list
)有所不同。以下是 std::forward_list
的一些特殊操作的详细介绍:
1. 在指定位置前插入元素
insert_after()
:在指定迭代器位置之后插入一个或多个元素。
示例:在 std::forward_list
中插入元素
2. 删除元素
erase_after()
:删除指定迭代器之后的元素。
示例:删除 std::forward_list
中的元素
3. 改变容器大小
由于 std::forward_list
是单向链表,它没有提供 resize()
方法。相应的,你需要使用迭代器或其他方法来改变大小。
4. 添加和删除元素
push_front()
和 pop_front()
:在容器的开头添加或删除元素。
示例:在 std::forward_list
的开头添加和删除元素
完整代码示例
下面是一个综合示例,展示了 std::forward_list
的这些特殊操作。
输出:
容器操作可能使迭代器失效
在 C++ 中使用标准模板库(STL)的容器时,特别需要注意容器操作可能导致迭代器失效的情况。迭代器失效意味着迭代器不再指向有效的容器元素或容器的末尾。以下是导致迭代器失效的一些常见原因及其解释:
1. 添加或删除元素
影响:在 std::vector
、std::string
、std::deque
等容器中添加或删除元素可能导致所有指向容器元素的迭代器、指针和引用失效。
示例:std::vector
添加元素导致迭代器失效
2. std::vector
和 std::string
的动态内存重新分配
影响:如果容器需要扩展其底层的动态数组来容纳更多元素,所有现有迭代器、指针和引用都将失效。
3. std::list
和 std::forward_list
的元素删除
影响:在 std::list
或 std::forward_list
中删除元素会导致指向被删除元素的迭代器失效,但其他迭代器不受影响。
示例:std::list
删除元素导致迭代器失效
4. std::map
和 std::set
的元素删除
影响:删除 std::map
或 std::set
中的元素会导致指向该元素的迭代器失效,但其他迭代器不受影响。
防止迭代器失效的策略
- 使用返回的迭代器:某些操作,如
erase()
,返回新的有效迭代器。 - 避免在容器迭代过程中进行插入或删除:在迭代容器时,避免执行可能导致迭代器失效的操作。
- 使用索引而非迭代器:对于支持随机访问的容器,比如
std::vector
,使用索引而不是迭代器进行元素访问。
完整代码示例
这是一个展示如何处理迭代器可能失效的情况的示例:
输出:
在这个示例中,std::vector
在迭代之后进行了添加操作,以避免迭代器失效。对于 std::list
,使用 erase()
方法返回的新迭代器来避免迭代器失效的问题。
Vector 对象的增长机制
在 C++ 中,std::vector
是一个动态数组,它可以在运行时根据需要自动调整其大小。std::vector
的增长机制是通过动态内存分配和拷贝构造来实现的。以下是 std::vector
增长过程的详细介绍:
1. 动态内存分配
当向 std::vector
添加元素且当前容量不足以容纳更多元素时,它会执行一次动态内存分配。这个过程包括以下几个步骤:
a. 计算新的容量
新的容量通常是当前容量的两倍,这种增长策略旨在平衡内存使用和复制操作的次数,以实现渐进式扩展。这种增长策略称为几何增长。
b. 分配新内存
std::vector
会分配一个更大的内存块来存储其元素。新分配的内存大小至少与计算出的新容量一致。
c. 移动元素
现有元素被移动或复制到新的内存位置。这通常通过调用元素的移动构造函数或复制构造函数完成。
d. 释放旧内存
一旦所有元素都移动到新的内存位置,std::vector
会释放原来的内存块。
2. 复杂性和性能
由于重新分配涉及复制或移动所有现有元素到新的内存位置,因此这个操作的时间复杂度是线性的,即 O(n),其中 n 是 std::vector
中的元素数量。然而,由于几何增长策略,这种重分配不是经常发生的。这意味着平均情况下,向 std::vector
添加一个元素的时间复杂度仍然是常数级的,即 O(1)。
3. 保留额外容量
通过 reserve()
成员函数,可以预先分配足够的内存以避免频繁的内存重新分配。这在已知 std::vector
将要存储大量元素时非常有用。
示例
在这个示例中,每次向 std::vector
添加元素时,打印其容量,以观察其如何增长。不过要注意的是,具体的增长策略可能因编译器和库的实现而异。
额外的 string 操作
在 C++ 中,std::string
类提供了许多强大的操作,用于处理和操作字符串数据。除了基本的赋值、访问和大小操作之外,std::string
还提供了一些额外的功能,使得字符串处理更加灵活和高效。以下是一些常用的额外 std::string
操作:
1. 字符串连接
operator+=
和 append()
:用于连接字符串或字符到现有的 std::string
对象。
示例:字符串连接
2. 查找和替换
-
find()
:查找子字符串或字符在字符串中的位置。 -
replace()
:替换字符串中的某部分为另一个字符串。
示例:查找和替换
3. 插入和删除
-
insert()
:在指定位置插入字符串或字符。 -
erase()
:删除字符串中的一部分。
示例:插入和删除
4. 子字符串
substr()
:从字符串中提取子字符串。
示例:提取子字符串
5. 字符串比较
compare()
:比较两个字符串。
示例:字符串比较
完整代码示例
这是一个展示上述 std::string
操作的综合示例:
输出:
容器适配器
在 C++ 标准模板库(STL)中,容器适配器是一种特殊类型的容器,它在现有容器的基础上提供了一定的功能封装。容器适配器并不是真正的容器,而是在一定程度上改变或增加了基础容器的行为。常见的容器适配器包括 stack
、queue
和 priority_queue
。
1. Stack(栈)
特性:std::stack
提供了后进先出(LIFO)的数据结构。它只允许从一端(顶部)进行元素的添加和移除。
- 基础容器:默认情况下,
stack
是基于std::deque
实现的,但也可以使用std::list
或std::vector
。 - 主要操作:
-
push()
:在栈顶添加一个元素。 -
pop()
:移除栈顶元素。 -
top()
:访问栈顶元素。 -
empty()
、size()
:检查栈是否为空,获取栈中元素的数量。
示例:使用 std::stack
2. Queue(队列)
特性:std::queue
提供了先进先出(FIFO)的数据结构。它允许在一端(队尾)添加元素,在另一端(队头)移除元素。
- 基础容器:默认情况下,
queue
是基于std::deque
实现的,但也可以使用std::list
。 - 主要操作:
-
push()
:在队尾添加一个元素。 -
pop()
:移除队头的元素。 -
front()
、back()
:访问队头和队尾元素。 -
empty()
、size()
:检查队列是否为空,获取队列中元素的数量。
示例:使用 std::queue
3. Priority Queue(优先队列)
特性:std::priority_queue
提供了一种方式,使得队头总是包含最大元素(或根据指定的比较函数确定的顺序)的队列。
- 基础容器:默认情况下,
priority_queue
是基于std::vector
实现的,但也可以使用std::deque
。 - 主要操作:
-
push()
:添加一个元素。 -
pop()
:移除最大元素(位于队头)。 -
top()
:访问最大元素。 -
empty()
、size()
:检查优先队列是否为空,获取元素的数量。
示例:使用 std::priority_queue