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