本文详细介绍了STL容器教程,涵盖vector、list、deque、set、map、stack和queue等多种容器类型及其基本操作和应用场景。文章深入讲解了每种容器的特性和使用方法,并提供了丰富的代码示例。通过本文,读者可以全面了解并掌握STL容器的使用技巧。STL容器教程旨在帮助开发者高效管理和操作数据。
STL容器简介STL(Standard Template Library)是C++标准库的一部分,它提供了一系列通用的容器、算法和迭代器,使得程序具有高度的可重用性和灵活性。STL容器的主要作用是存储和管理一组元素,以便在程序中进行高效、便捷的操作。容器对象本身并不处理算法,但它们与算法紧密配合,提供了一种强大的编程工具。
主要容器类型介绍
STL提供了多种容器类型,每种容器都有其特定的用途和特性。
- vector:动态数组,支持随机访问,元素连续存储在内存中。它提供了高效的随机访问和常量时间的尾部插入与删除操作。
- list:双向链表,支持高效的插入和删除操作,但不支持随机访问,需要遍历才能访问特定元素。
- deque:双端队列,支持高效的头部和尾部插入与删除操作,同时也支持随机访问。
- set:基于红黑树实现的集合,自动保持元素唯一性,支持高效的插入、删除和查找操作。
- map:基于红黑树实现的关联容器,用于存储键值对,键唯一且有序,支持高效的查找、插入和删除操作。
- unordered_set:基于哈希表实现的集合,不保证元素顺序,支持高效的插入、删除和查找操作。
- unordered_map:基于哈希表实现的关联容器,用于存储键值对,不保证键的顺序,支持高效的查找、插入和删除操作。
- stack:后进先出(LIFO)的容器适配器,封装了deque或vector,实现栈的功能。
- queue:先进先出(FIFO)的容器适配器,封装了deque或vector,实现队列的功能。
vector是一个动态数组,用于存储一系列相同类型的元素。它提供了一种简单而高效的方式来管理元素集合,支持随机访问,并且在尾部添加和删除元素时效率较高。
vector的基本操作
vector支持多种基本操作,如构造、赋值、访问元素、修改元素、插入和删除元素等。
构造与赋值
#include <vector>
#include <iostream>
int main() {
// 构造一个空vector
std::vector<int> vec1;
// 构造一个包含初始化元素的vector
std::vector<int> vec2 = {1, 2, 3, 4, 5};
// 使用迭代器对vector进行赋值
std::vector<int> vec3(vec2.begin(), vec2.end());
// 使用数组初始化vector
std::vector<int> vec4 = {1, 2, 3, 4, 5};
// 使用指定大小和初始值构造vector
std::vector<int> vec5(5, 10);
// 使用另一个vector进行赋值
vec1 = vec2;
return 0;
}
访问与修改元素
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 访问元素
for (size_t i = 0; i < vec.size(); ++i) {
std::cout << "Element " << i << ": " << vec[i] << std::endl;
}
// 修改元素
vec[2] = 10;
// 使用at方法访问元素(提供边界检查)
try {
std::cout << vec.at(2) << std::endl;
} catch (const std::out_of_range& e) {
std::cout << "访问超出范围" << std::endl;
}
return 0;
}
插入与删除元素
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 在尾部插入元素
vec.push_back(6);
// 在指定位置插入元素
vec.insert(vec.begin() + 2, 7);
// 删除元素
vec.erase(vec.begin() + 2);
// 删除范围内的元素
vec.erase(vec.begin() + 2, vec.end());
return 0;
}
vector的常用成员函数解析
vector提供了丰富的成员函数,方便进行各种操作。
size()
: 返回容器的元素数量。empty()
: 检查容器是否为空。begin()
: 返回指向容器第一个元素的迭代器。end()
: 返回指向容器最后一个元素之后位置的迭代器。front()
: 返回容器的第一个元素。back()
: 返回容器的最后一个元素。push_back()
: 在容器尾部添加一个元素。pop_back()
: 删除容器尾部的元素。insert()
: 在指定位置插入一个或多个元素。erase()
: 删除指定位置或范围内的元素。clear()
: 清空容器中的所有元素。resize()
: 改变容器的大小。
vector的性能分析与应用场景
vector的优点在于随机访问高效,尾部插入和删除高效,且内存连续,易于管理和操作。然而,当需要频繁在头部或中间插入和删除元素时,vector的性能会下降,因为这会导致内存的重新分配和元素的移动。
vector适用于需要高效随机访问、尾部插入和删除操作的场景,如动态数组、缓冲区管理等。
list与deque容器基础list和deque是两种不同的容器,分别基于双向链表和双端队列实现。它们在插入和删除操作上有不同的性能表现,适用于不同的应用场景。
list与deque的基本操作
list是一个双向链表,deque是一个双端队列。它们都支持高效的插入和删除操作,但deque还支持随机访问。
构造与赋值
#include <list>
#include <deque>
#include <iostream>
int main() {
// 构造一个空list
std::list<int> list1;
// 构造一个包含初始化元素的list
std::list<int> list2 = {1, 2, 3, 4, 5};
// 使用迭代器对list进行赋值
std::list<int> list3(list2.begin(), list2.end());
// 使用数组初始化list
std::list<int> list4 = {1, 2, 3, 4, 5};
// 使用指定大小和初始值构造list
std::list<int> list5(5, 10);
// 构造一个空deque
std::deque<int> deque1;
// 构造一个包含初始化元素的deque
std::deque<int> deque2 = {1, 2, 3, 4, 5};
// 使用迭代器对deque进行赋值
std::deque<int> deque3(deque2.begin(), deque2.end());
// 使用数组初始化deque
std::deque<int> deque4 = {1, 2, 3, 4, 5};
// 使用指定大小和初始值构造deque
std::deque<int> deque5(5, 10);
return 0;
}
访问与修改元素
#include <list>
#include <deque>
#include <iostream>
int main() {
std::list<int> list = {1, 2, 3, 4, 5};
std::deque<int> deque = {1, 2, 3, 4, 5};
// 访问list元素
for (auto it = list.begin(); it != list.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 访问deque元素
for (auto it = deque.begin(); it != deque.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 修改list元素
list.front() = 10;
// 修改deque元素
deque.back() = 10;
return 0;
}
插入与删除元素
#include <list>
#include <deque>
#include <iostream>
int main() {
std::list<int> list = {1, 2, 3, 4, 5};
std::deque<int> deque = {1, 2, 3, 4, 5};
// 在list尾部插入元素
list.push_back(6);
// 在deque尾部插入元素
deque.push_back(6);
// 在指定位置插入元素
list.insert(list.begin() + 2, 7);
deque.insert(deque.begin() + 2, 7);
// 删除list元素
list.erase(list.begin() + 2);
// 删除deque元素
deque.erase(deque.begin() + 2);
// 删除范围内的元素
list.erase(list.begin() + 2, list.end());
deque.erase(deque.begin() + 2, deque.end());
return 0;
}
list与deque的成员函数对比
list和deque都提供了一系列成员函数,但有些函数的实现方式和性能表现有所不同。
-
list:
size()
: 返回容器的元素数量。empty()
: 检查容器是否为空。begin()
: 返回指向容器第一个元素的迭代器。end()
: 返回指向容器最后一个元素之后位置的迭代器。front()
: 返回容器的第一个元素。back()
: 返回容器的最后一个元素。push_back()
: 在容器尾部添加一个元素。pop_back()
: 删除容器尾部的元素。push_front()
: 在容器头部添加一个元素。pop_front()
: 删除容器头部的元素。insert()
: 在指定位置插入一个或多个元素。erase()
: 删除指定位置或范围内的元素。clear()
: 清空容器中的所有元素。
- deque:
size()
: 返回容器的元素数量。empty()
: 检查容器是否为空。begin()
: 返回指向容器第一个元素的迭代器。end()
: 返回指向容器最后一个元素之后位置的迭代器。front()
: 返回容器的第一个元素。back()
: 返回容器的最后一个元素。push_back()
: 在容器尾部添加一个元素。pop_back()
: 删除容器尾部的元素。push_front()
: 在容器头部添加一个元素。pop_front()
: 删除容器头部的元素。insert()
: 在指定位置插入一个或多个元素。erase()
: 删除指定位置或范围内的元素。clear()
: 清空容器中的所有元素。resize()
: 改变容器的大小。at()
: 通过索引访问元素(提供边界检查)。operator[]
: 通过索引访问元素(不提供边界检查)。
list与deque的性能差异与使用场合
list的优点在于插入和删除操作在任意位置都可以高效完成,但随机访问效率较低。deque的优点在于支持高效的头尾插入和删除操作,同时也支持随机访问,但插入和删除操作在中间位置的效率较低。
list适用于需要频繁插入和删除元素,但不需要高效随机访问的场景,如链表实现的队列或栈等。deque适用于需要高效头尾插入和删除操作,同时需要支持随机访问的场景,如缓冲区管理等。
list的性能分析与应用场景
list适用于需要频繁插入和删除元素,但不需要高效随机访问的场景。如链表实现的队列或栈,链表结构允许在任意位置高效插入和删除元素,特别适用于需要频繁插入和删除操作的场景。
deque的性能分析与应用场景
deque适用于需要高效头尾插入和删除操作,同时需要支持随机访问的场景。如缓冲区管理,双端队列结构允许在头部和尾部高效插入和删除元素,同时也支持随机访问,适用于需要高效头尾操作和随机访问的场景。
set与map容器入门set和map是基于红黑树实现的容器,它们都支持高效的插入、删除和查找操作。
set和map的数据结构与性质
set是一个集合,存储唯一的元素,并且元素按升序排列。map是一个关联容器,用于存储键值对,键唯一且有序。
set的基本操作
#include <set>
#include <iostream>
int main() {
std::set<int> s;
// 插入元素
s.insert(1);
s.insert(3);
s.insert(2);
// 查找元素
if (s.find(2) != s.end()) {
std::cout << "找到了2" << std::endl;
} else {
std::cout << "未找到2" << std::endl;
}
// 删除元素
s.erase(2);
// 遍历元素
for (auto it = s.begin(); it != s.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
map的基本操作
#include <map>
#include <iostream>
int main() {
std::map<std::string, int> m;
// 插入键值对
m["one"] = 1;
m["two"] = 2;
m["three"] = 3;
// 查找键值
if (m.find("two") != m.end()) {
std::cout << "找到了two" << std::endl;
} else {
std::cout << "未找到two" << std::endl;
}
// 删除键值对
m.erase("two");
// 遍历键值对
for (auto it = m.begin(); it != m.end(); ++it) {
std::cout << it->first << " -> " << it->second << std::endl;
}
return 0;
}
set与map的基本操作与成员函数
set和map都提供了一系列成员函数,方便进行各种操作。
-
set:
size()
: 返回容器的元素数量。empty()
: 检查容器是否为空。begin()
: 返回指向容器第一个元素的迭代器。end()
: 返回指向容器最后一个元素之后位置的迭代器。rbegin()
: 返回指向容器最后一个元素的逆向迭代器。rend()
: 返回指向容器第一个元素之前的逆向迭代器。count()
: 检查容器中是否存在指定元素。find()
: 查找指定元素。insert()
: 插入一个或多个元素。erase()
: 删除指定元素或范围内的元素。clear()
: 清空容器中的所有元素。
- map:
size()
: 返回容器的元素数量。empty()
: 检查容器是否为空。begin()
: 返回指向容器第一个元素的迭代器。end()
: 返回指向容器最后一个元素之后位置的迭代器。rbegin()
: 返回指向容器最后一个元素的逆向迭代器。rend()
: 返回指向容器第一个元素之前的逆向迭代器。count()
: 检查容器中是否存在指定键。find()
: 查找指定键。insert()
: 插入一个或多个键值对。erase()
: 删除指定键或范围内的元素。clear()
: 清空容器中的所有元素。at()
: 通过键访问值(提供边界检查)。operator[]
: 通过键访问值(不提供边界检查)。
set与map的性能分析与应用场景
set适用于需要存储唯一元素集合的场景,如去重操作、集合运算等。map适用于需要存储键值对的场景,如配置文件解析、用户信息管理等。
案例:使用set去重
#include <set>
#include <iostream>
int main() {
std::set<int> s;
// 插入元素
s.insert(1);
s.insert(2);
s.insert(1);
s.insert(3);
s.insert(2);
// 遍历元素
for (auto it = s.begin(); it != s.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
案例:使用map存储配置信息
#include <map>
#include <iostream>
int main() {
std::map<std::string, std::string> config;
// 插入键值对
config["host"] = "127.0.0.1";
config["port"] = "8080";
config["username"] = "admin";
// 查找键值
std::cout << "Host: " << config["host"] << std::endl;
std::cout << "Port: " << config["port"] << std::endl;
std::cout << "Username: " << config["username"] << std::endl;
return 0;
}
容器适配器stack与queue
容器适配器stack和queue利用底层容器实现特定的数据结构和操作。它们分别封装了deque或vector,实现了栈和队列的功能。
stack与queue的定义与使用
stack是一种后进先出(LIFO)的数据结构,常常用于需要处理后插入数据优先处理的场景。queue是一种先进先出(FIFO)的数据结构,通常用于需要处理先插入数据优先处理的场景。
构造与赋值
#include <stack>
#include <queue>
#include <iostream>
int main() {
// 构造一个空stack
std::stack<int> stack;
// 使用vector初始化stack
std::stack<int, std::vector<int>> stack1({1, 2, 3, 4, 5});
// 使用deque初始化stack
std::stack<int, std::deque<int>> stack2({1, 2, 3, 4, 5});
// 构造一个空queue
std::queue<int> queue;
// 使用vector初始化queue
std::queue<int, std::vector<int>> queue1({1, 2, 3, 4, 5});
// 使用deque初始化queue
std::queue<int, std::deque<int>> queue2({1, 2, 3, 4, 5});
return 0;
}
基本操作
#include <stack>
#include <queue>
#include <iostream>
int main() {
std::stack<int> stack;
std::queue<int> queue;
// 向stack中添加元素
stack.push(1);
stack.push(2);
stack.push(3);
// 向queue中添加元素
queue.push(1);
queue.push(2);
queue.push(3);
// 从stack中移除元素
while (!stack.empty()) {
std::cout << stack.top() << " ";
stack.pop();
}
std::cout << std::endl;
// 从queue中移除元素
while (!queue.empty()) {
std::cout << queue.front() << " ";
queue.pop();
}
std::cout << std::endl;
return 0;
}
stack与queue的成员函数介绍
stack和queue提供了基本的操作,但它们并没有提供底层容器的所有成员函数。
-
stack:
size()
: 返回栈的元素数量。empty()
: 检查栈是否为空。top()
: 返回栈顶元素。push()
: 向栈顶添加一个元素。pop()
: 删除栈顶元素。swap()
: 交换两个栈的内容。emplace()
: 向栈顶添加一个元素(使用构造函数)。
- queue:
size()
: 返回队列的元素数量。empty()
: 检查队列是否为空。front()
: 返回队列的队首元素。back()
: 返回队列的队尾元素。push()
: 向队尾添加一个元素。pop()
: 删除队首元素。swap()
: 交换两个队列的内容。emplace()
: 向队尾添加一个元素(使用构造函数)。
stack与queue的性能分析与应用场景
stack和queue的实现原理是基于deque或vector的插入和删除操作。stack利用了deque或vector的尾部操作,queue利用了deque或vector的头尾操作。stack适用于需要后进先出的数据结构,如括号匹配、递归等。queue适用于需要先进先出的数据结构,如任务队列、消息队列等。
练习与实例在实际编程中,选择合适的容器类型和组合使用容器是非常重要的。正确的选择能够提高程序的性能和可读性。
如何选择合适的容器类型
选择合适的容器类型需要考虑以下几个因素:
- 访问方式:是否需要随机访问、插入和删除操作,是头部、尾部还是中间?
- 存储需求:元素是否需要排序,是否需要唯一性,是否存在键值对映射关系?
- 性能要求:插入和删除操作是否频繁,是否需要常数时间复杂度?
示例:选择合适的容器类型
#include <vector>
#include <list>
#include <deque>
#include <set>
#include <map>
#include <iostream>
int main() {
// 需要随机访问和尾部插入、删除操作
std::vector<int> vec = {1, 2, 3, 4, 5};
vec.push_back(6);
vec.pop_back();
// 需要高效的插入和删除操作,但不需要随机访问
std::list<int> list = {1, 2, 3, 4, 5};
list.push_back(6);
list.pop_back();
// 需要高效的头尾插入和删除操作,同时需要随机访问
std::deque<int> deque = {1, 2, 3, 4, 5};
deque.push_back(6);
deque.pop_back();
// 需要存储唯一元素,并按升序排列
std::set<int> s = {1, 2, 3, 4, 5};
s.insert(6);
s.erase(6);
// 需要存储键值对,并按键的升序排列
std::map<std::string, std::string> m = {
{"key1", "value1"},
{"key2", "value2"},
{"key3", "value3"}
};
m["key4"] = "value4";
m.erase("key4");
return 0;
}
复杂场景下容器的组合使用
在复杂场景下,容器的组合使用可以实现更高效和灵活的程序结构。例如,使用vector和map的组合实现高效的数据检索,使用list和set的组合实现高效的数据管理等。
示例:使用vector与map的组合实现高效的数据检索
#include <vector>
#include <map>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
std::map<int, int> map;
// 将vector中的元素存入map
for (int i = 0; i < vec.size(); ++i) {
map[vec[i]] = i;
}
// 通过key快速检索元素位置
if (map.find(3) != map.end()) {
std::cout << "找到了元素3的位置: " << map[3] << std::endl;
} else {
std::cout << "未找到元素3" << std::endl;
}
return 0;
}
常见错误与调试技巧
在使用STL容器时,常见的错误包括内存泄漏、迭代器失效、容器越界访问等。调试技巧包括使用断言、日志输出、调试工具等。
示例:避免迭代器失效
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 使用迭代器遍历并修改元素
for (auto it = vec.begin(); it != vec.end(); ++it) {
if (*it % 2 == 0) {
vec.erase(it);
// 注意:在这里直接erase会使得迭代器失效,需要使用erase返回的迭代器更新it
it = vec.erase(it);
--it;
}
}
// 打印修改后的vector
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
``
以上是STL容器教程的详细介绍,希望能帮助你更好地理解和使用STL容器。
共同學習,寫下你的評論
評論加載中...
作者其他優質文章