STL 分为多个组件,包括容器(Containers)、迭代器(Iterators)、算法(Algorithms)、函数对象(Function Objects)和适配器(Adapters)等。
使用 STL 的好处:
- 代码复用:STL 提供了大量的通用数据结构和算法,可以减少重复编写代码的工作。
- 性能优化:STL 中的算法和数据结构都经过了优化,以提供最佳的性能。
- 泛型编程:使用模板,STL 支持泛型编程,使得算法和数据结构可以适用于任何数据类型。
- 易于维护:STL 的设计使得代码更加模块化,易于阅读和维护。
一、容器
STL 中的容器可以分为三类:
1、序列容器:存储元素的序列,允许双向遍历。
- std::vector:动态数组,支持快速随机访问。
- std::deque:双端队列,支持快速插入和删除。
- std::list:链表,支持快速插入和删除,但不支持随机访问。
2、关联容器:存储键值对,每个元素都有一个键(key)和一个值(value),并且通过键来组织元素。
- std::set:集合,不允许重复元素。
- std::multiset:多重集合,允许多个元素具有相同的键。
- std::map:映射,每个键映射到一个值。
- std::multimap:多重映射,允许多个键映射到相同的值。
3、无序容器(C++11 引入):哈希表,支持快速的查找、插入和删除。
- std::unordered_set:无序集合。
- std::unordered_multiset:无序多重集合。
- std::unordered_map:无序映射。
- std::unordered_multimap:无序多重映射。
vector:
// 创建一个向量存储 int
vector<int> vec;
int i;
// 显示 vec 的原始大小
cout << "vector size = " << vec.size() << endl;
// 推入 5 个值到向量中
for(i = 0; i < 5; i++){
vec.push_back(i);
}
// 显示 vec 扩展后的大小
cout << "extended vector size = " << vec.size() << endl;
// 访问向量中的 5 个值
for(i = 0; i < 5; i++){
cout << "value of vec [" << i << "] = " << vec[i] << endl;
}
// 使用迭代器 iterator 访问值
vector<int>::iterator v = vec.begin();
while( v != vec.end()) {
cout << "value of v = " << *v << endl;
v++;
- push_back( ) 成员函数在向量的末尾插入值,如果有必要会扩展向量的大小。
- size( ) 函数显示向量的大小。
- begin( ) 函数返回一个指向向量开头的迭代器。
- end( ) 函数返回一个指向向量末尾的迭代器。
list:
- 包含头文件:
#include <list>
- 声明列表:
std::list<T> mylist;
,其中T
是存储在列表中的元素类型。 - 插入元素:
mylist.push_back(value);
- 删除元素:
mylist.pop_back();
或mylist.erase(iterator);
- 访问元素:
mylist.front();
和mylist.back();
- 遍历列表:使用迭代器
for (auto it = mylist.begin(); it != mylist.end(); ++it)
set:
语法
包含头文件:#include <set>
声明 set 容器std::set<元素类型> 容器名;
常用操作
insert(元素)
: 插入一个元素。erase(元素)
: 删除一个元素。find(元素)
: 查找一个元素。size()
: 返回容器中元素的数量。empty()
: 检查容器是否为空。
map:
定义和特性
- 键值对:
map
存储的是键值对,其中每个键都是唯一的。 - 排序:
map
中的元素按照键的顺序自动排序,通常是升序。 - 唯一性:每个键在
map
中只能出现一次。 - 双向迭代器:
map
提供了双向迭代器,可以向前和向后遍历元素。
基本语法
包含头文件:#include <map>
声明 map 容器:
std::map<key_type, value_type> myMap;
key_type
是键的类型。value_type
是值的类型。
插入元素:myMap[key] = value;
访问元素:value = myMap[key];
遍历 map:
for (std::map<key_type, value_type>::iterator it = myMap.begin(); it != myMap.end(); ++it) { std::cout << it->first << " => " << it->second << std::endl; }
二、迭代器
迭代器(Iterator)是C++标准模板库(STL)中的一个重要概念,它提供了一种访问容器中元素的方法,而无需暴露容器的内部实现细节。迭代器可以被视为一种“智能指针”,它能够在容器内部进行遍历,同时提供对容器元素的访问。
定义
迭代器并不是一个具体的类型,而是一个抽象的概念。在C++中,迭代器通常是通过模板类实现的,这些模板类提供了对容器元素的访问和操作。迭代器必须至少支持*
(解引用)、++
(递增)、--
(递减,如果支持的话)、==
(相等性比较)和!=
(不等性比较)等操作。
迭代器类型
根据迭代器的功能,C++ STL将迭代器分为以下五类(从低到高):
- 输入迭代器(Input Iterators):只能向前移动,并支持解引用和相等性比较操作。
- 输出迭代器(Output Iterators):只能向前移动,并支持赋值操作。
- 前向迭代器(Forward Iterators):是输入迭代器的超集,也支持多次通过迭代器访问同一元素。
- 双向迭代器(Bidirectional Iterators):是前向迭代器的超集,支持向前和向后移动。
- 随机访问迭代器(Random Access Iterators):是双向迭代器的超集,支持通过索引直接访问元素,以及进行迭代器间的算术操作。
使用方法
迭代器的基本使用方法包括获取迭代器、遍历容器、访问元素以及修改元素(如果迭代器支持)。
获取迭代器
容器通常提供成员函数来返回指向容器首元素和尾后元素(即最后一个元素的下一个位置,不指向任何有效元素)的迭代器。
begin()
:返回指向容器首元素的迭代器。end()
:返回指向容器尾后元素的迭代器。
遍历容器
可以使用循环结构(如for
循环、while
循环)结合迭代器来遍历容器。
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 使用C++11范围for循环,更加简洁
for (int val : vec) {
std::cout << val << " ";
}
std::cout << std::endl;
return 0;
}
访问和修改元素
- 访问元素:使用解引用操作符
*
来访问迭代器指向的元素。 - 修改元素:如果迭代器支持(通常是双向迭代器或随机访问迭代器),可以直接通过解引用和赋值来修改元素。
if (it != vec.end()) {
*it = 10; // 修改迭代器指向的元素
}
注意事项
- 迭代器失效:在某些容器操作中(如
vector
的push_back
可能会导致重新分配内存),迭代器可能会失效。在使用迭代器之前,需要确保它们仍然有效。 - 迭代器比较:只能比较来自同一容器的迭代器。
- 迭代器算术操作:只有随机访问迭代器支持算术操作,如
it + n
、it - n
、it[n]
等。
三、算法(Algorithms)
C++ 标准库中的 <algorithm>
头文件提供了一组用于操作容器(如数组、向量、列表等)的算法。这些算法包括排序、搜索、复制、比较等,它们是编写高效、可重用代码的重要工具。
<algorithm>
头文件定义了一组模板函数,这些函数可以应用于任何类型的容器,只要容器支持迭代器。这些算法通常接受两个或更多的迭代器作为参数,表示操作的起始和结束位置。
常用算法包括:
- sort:对一个序列进行排序。
- find:在一个序列中查找指定元素。
- count:统计一个序列中某个元素的个数。
- max_element:找到一个序列中的最大元素。
- min_element:找到一个序列中的最小元素。
- accumulate:对一个序列中的元素进行累加。
- reverse:将一个序列进行反转。
- unique:将一个序列中的重复元素去重。
- fill:将一个序列中的所有元素设置为指定值。
- copy:将一个序列中的元素复制到另一个序列中。
- transform:对一个序列中的元素进行转换后,存储到另一个序列中。
- binary_search:在一个有序序列中查找指定元素。
- next_permutation:生成一个序列的下一个排列。
- prev_permutation:生成一个序列的上一个排列。
语法:
大多数 <algorithm>
中的函数都遵循以下基本语法:
algorithm_name(container.begin(), container.end(), ...);
这里的 container
是一个容器对象,begin()
和 end()
是容器的成员函数,返回指向容器开始和结束的迭代器。
1、sort排序
定义:对容器中的元素进行排序
语法:sort(container.begin(), container.end(), compare_function);
2. 搜索算法
函数:find
定义:在容器中查找与给定值匹配的第一个元素。
语法:
auto it = find(container.begin(), container.end(), value);
如果找到,it 将指向匹配的元素;如果没有找到,it 将等于 container.end()。
3. 复制算法
函数:copy
定义:将一个范围内的元素复制到另一个容器或数组。
语法:copy(source_begin, source_end, destination_begin);
4. 比较算法
函数:equal
定义:比较两个容器或两个范围内的元素是否相等。
语法:bool result = equal(first1, last1, first2);
或 bool result = equal(first1, last1, first2, compare_function);
四、函数对象
定义一个类需要使用关键字 class,然后指定类的名称,并类的主体是包含在一对花括号中,主体包含类的成员变量和成员函数。
定义一个类,本质上是定义一个数据类型的蓝图,它定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作。
特点与优势
- 参数与返回值:函数对象可以像普通函数一样有参数和返回值,这使得它们非常灵活。
- 状态保持:与普通函数不同,函数对象可以有自己的状态(例如,成员变量),这允许它们在多次调用之间保持状态。这种能力使得函数对象能够记录调用次数、累积结果等。
- 可重用性:由于函数对象是类的实例,因此可以轻松地创建和使用多个具有相同行为但不同状态的实例。
- 封装性:函数对象可以将数据和操作这些数据的函数封装在同一个类中,从而提高代码的模块化和可维护性。
- 灵活性:函数对象可以作为参数传递给其他函数,也可以作为返回值从函数中返回。这使得它们能够灵活地与其他函数和对象进行交互。
五、适配器
STL(Standard Template Library,标准模板库)中的适配器(Adapter)是一种设计模式,它允许将某个类的接口转换为客户端所期望的另一个接口,从而使原本因接口不兼容而不能一起工作的类可以一起工作。在STL中,适配器主要用于容器、迭代器和函数对象,分别称为容器适配器、迭代器适配器和函数适配器
1. 容器适配器
容器适配器是一种特殊的容器,它们通过封装另一个容器(如vector
、deque
或list
)来提供特定的接口。STL提供了三种容器适配器:stack
、queue
和priority_queue
。这些适配器通过限制对底层容器的直接访问,只提供符合特定数据结构(如栈、队列或优先队列)操作的接口。
- stack:提供后进先出(LIFO)的操作接口。
- queue:提供先进先出(FIFO)的操作接口。
- priority_queue:提供基于优先级的队列操作接口,元素总是按照特定的顺序(如最大堆或最小堆)被移除。
2. 迭代器适配器
迭代器适配器用于改变迭代器的行为,使它们表现得像另一种迭代器。STL提供了多种迭代器适配器,如insert_iterator
、reverse_iterator
和istream_iterator
等。
- insert_iterator:允许通过迭代器进行插入操作,而不是替换操作。
- reverse_iterator:允许以逆序遍历容器。
- istream_iterator:可以将输入流(如
std::cin
)转换为迭代器,用于读取输入流中的元素。
3. 函数适配器
函数适配器用于改变函数对象(也称为仿函数)的行为。它们可以接受一个或多个函数对象作为参数,并返回一个新的函数对象,该对象的行为是对原函数对象行为的某种修改或扩展。STL中的函数适配器包括std::bind
、std::mem_fn
、std::not1
和std::not2
等。
- std::bind:用于绑定函数对象的某些参数到具体的值,从而生成一个新的函数对象。
- std::mem_fn:用于生成一个可调用对象,该对象可以像调用普通函数一样调用类的成员函数。
- std::not1和std::not2:用于对一元和二元函数对象的返回值进行逻辑否定。