STL模板库超详细讲解

HIM玩Minecraft 2024-08-16 16:35:03 阅读 65

STL模板库 

        标准模板库STL(‌Standard Template Library)‌是C++语言中的一个重要组成部分,‌它提供了一套通用的数据结构和算法,‌旨在提高软件开发的效率和可重用性。‌ STL主要由容器、‌算法和迭代器三个核心部分组成,‌这些组件共同构成了C++标准程序库的一部分,‌尽管STL本身并不是C++标准程序库的全部。‌

容器:‌STL容器是用于存储数据的类,‌包括向量(‌vector)‌、‌双端队列(‌deque)‌、‌表(‌list)‌、‌队列(‌queue)‌、‌堆栈(‌stack)‌、‌集合(‌set)‌、‌多重集合(‌multiset)‌、‌映射(‌map)‌和多重映射(‌multimap)‌等。‌每种容器都有其独特的特点和适用场景,‌例如,‌向量适合频繁访问元素且可能在尾部进行大量插入和删除操作的场景,‌而集合则用于存储不重复的元素。‌

算法:‌STL算法是一组用于解决常见问题的有限步骤函数,‌它们不依赖于特定的容器类型,‌而是通过迭代器访问容器中的元素。‌这些算法可以高度通用,‌能够应用于多种不同的容器类型,‌例如排序(‌sort)‌、‌查找(‌find)‌、‌替换等。‌

迭代器:‌迭代器是STL中的一个核心概念,‌它提供了一种统一的方法来访问容器中的元素。‌迭代器类似于指针,‌但比指针更加安全,‌因为它们被设计为只能进行有限的、‌安全的操作。‌

        STL的代码从广义上讲分为三类:‌algorithm(‌算法)‌、‌container(‌容器)‌和iterator(‌迭代器)‌,‌几乎所有的代码都采用了模板类和模板函数的方式,‌这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。‌STL的出现,‌使得C++编程语言在有了同Java一样强大的类库的同时,‌保有了更大的可扩展性。‌

        此外,‌STL还强调了类型参数的使用,‌通过使用<code>typename或class关键字声明类型参数,‌使得STL具有高度的灵活性,‌能够适应不同的数据类型和需求。

一、序列容器

1.vector

        vector容器提供了许多操作,‌如push_back、‌pop_back、‌insert、‌erase等,‌以方便用户对容器中的元素进行添加、‌删除和修改。‌此外,‌vector还支持随机访问,‌即可以通过索引直接访问容器中的元素。‌

        vector的构造函数有多种形式,‌包括默认构造函数、‌带有初始大小的构造函数、‌带有初始元素值和大小的构造函数等。‌例如:‌

默认构造函数:‌vector<int> vec1; 创建一个空的vector。‌带有初始大小的构造函数:‌vector<int> vec2(10); 创建一个包含10个默认初始化元素的vector。‌带有初始元素值和大小的构造函数:‌vector<int> vec3(10, 6); 创建一个包含10个元素,‌初始值都为6的vector。‌vector<int> vec4(vec2.begin(), vec2.begin() + 3); 通过vec2的迭代器范围构造一个新的vector,‌包含vec2的前三个元素。‌class Person { /*...*/ };vector<Person> people; 创建一个存储Person对象的vector。‌

        此外,‌vector还支持预留空间、‌插入和删除元素等操作,‌使得它成为一个非常实用的数据结构。‌总的来说,‌STL中的vector是一个功能强大且灵活的容器,‌适用于需要动态调整大小的场景。

2.array

        STL中的array是一个固定大小的数组容器,‌它提供了比原生数组更安全、‌更易于使用的接口。‌

         sarray的主要特点包括:‌

固定大小:‌与原生数组类似,‌array也是一个固定大小的容器,‌这意味着在定义时就需要确定其大小,‌并且之后不能改变这个大小。‌这种特性使得array在处理需要固定大小数据的情况时非常有用。‌

类型安全:‌array提供了类型安全保证,‌因为它在编译时检查数组的大小和类型,‌这有助于减少运行时错误。‌

支持随机访问:‌array支持随机访问元素,‌即可以通过索引直接访问数组中的任何元素。‌

提供了一系列成员函数:‌包括size()、‌front()、‌back()、‌data()等,‌这些函数提供了对数组元素的便捷访问和操作。‌

支持迭代器和范围for循环:‌array支持迭代器访问,‌这使得它可以与STL中的其他算法和容器进行交互。‌同时,‌也支持C++11引入的范围for循环,‌使得遍历数组元素变得更加简单。‌

非类型模板参数:‌array是一个模板类,‌其中包含一个非类型模板参数,‌用于指定数组的大小。‌这使得array可以定义具有不同大小的数组实例。‌

3.deque(‌双端队列)‌

        deque,‌即双端队列,‌是STL(‌Standard Template Library)‌中的一个序列式容器,‌它允许在容器的头部和尾部进行插入和删除操作。‌与vector容器相比,‌deque特别适合在需要频繁进行前后端操作的应用中,‌因为它对头部的插入和删除操作具有较高的效率。‌deque的物理结构由多块离散且连续的存储空间组成,‌通常这些存储空间是定长的数组缓冲区,‌通过一个中控器(‌一般是map结构)‌来维护这些缓冲区的指针,‌从而实现双端队列的功能。‌

        deque的主要特点包括:‌

双端操作:‌允许在容器的头部和尾部进行元素的插入和删除。‌高效性:‌相对于vector,‌deque在头部和尾部的操作效率更高,‌尤其是在需要频繁进行前后端操作的应用中表现更佳。‌动态管理:‌通过中控器管理多个缓冲区,‌当某个缓冲区满时,‌中控器会自动为其分配新的缓冲区;‌当不再需要某个缓冲区时,‌中控器会回收该缓冲区,‌实现动态内存管理。‌随机访问:‌deque提供双向随机访问迭代器,‌支持通过索引直接访问元素。‌

        deque的成员函数包括但不限于:‌

插入和删除:‌push_front()push_back()pop_front()pop_back()insert()erase() 等,‌用于在头部和尾部插入或删除元素。‌访问元素:‌front()back()at(idx)operator[] 等,‌用于访问容器中的元素。‌大小和清空:‌size()empty()clear() 等,‌用于获取容器大小、‌判断是否为空或清空容器。‌赋值和交换:‌assign()swap() 等,‌用于为容器赋值或与另一个容器交换内容。‌

        此外,‌deque还支持一些标准容器的通用算法,‌如排序、‌查找等。‌需要注意的是,‌虽然deque和vector在很多操作上相似,‌但它们在内部实现、‌性能特点以及适用场景上有所区别。‌例如,‌vector在连续内存空间中存储元素,‌因此随机访问速度较快,‌但在头部插入或删除元素时效率较低,‌尤其是当数据量较大时。‌相比之下,‌deque通过维护多个缓冲区的方式,‌使得头部和尾部的操作更加高效。

4.list

        STL模板库中的list是一个双向链表容器,‌它提供了一种灵活且高效的方式来管理对象的集合。‌

        STL中的list容器是一种线性数据结构,‌它允许在序列中的任何位置进行插入和删除操作,‌而不需要移动其他元素。‌这种特性使得list特别适合于需要频繁在中间位置进行插入和删除的操作场景。‌与数组和vector等线性数据结构相比,‌list的随机访问性能较差,‌因为它不支持通过索引直接访问元素。‌然而,‌它在处理需要动态添加和删除元素的情况时表现出色。‌

        list容器支持以下基本操作:‌

初始化:‌可以通过多种方式初始化list容器,‌包括使用默认构造函数创建空列表、‌使用push_back或push_front函数向列表中添加元素、‌使用列表初始化语法创建列表、‌使用指定大小和默认值创建列表、‌使用迭代器创建列表、‌复制另一个列表来创建一个新列表、‌以及使用移动语义从另一个列表创建一个新列表。‌

常用接口:‌

push_back 和 push_front:‌在列表的末尾和开头添加元素。‌pop_front 和 pop_back:‌从列表的开头和末尾删除元素。‌front 和 back:‌分别返回列表开头的元素和末尾的元素的引用。‌begin 和 end:‌返回指向列表开始和结束的迭代器。‌clear:‌删除列表中的所有元素。‌

        list容器还支持迭代器的++和--操作,‌通过访问prev和next指针实现,‌这使得它可以在不改变其他元素位置的情况下,‌方便地在链表中前后移动。‌此外,‌list容器还支持随机存取迭代器,‌允许通过索引访问链表中的元素,‌尽管其插入和删除操作并不依赖于索引。‌

        与vector容器相比,‌list的主要区别在于它的内部实现机制。‌vector通常实现为连续的内存块,‌支持随机访问,‌而list则实现为双向链表,‌支持在任意位置进行插入和删除操作,‌但随机访问性能较差。‌这两种容器各有优势,‌选择使用哪一种取决于具体的应用场景和需求。

5.forward_list

        STL模板库中的forward_list是一种序列容器,‌它允许在常量时间内对任何位置进行插入和删除操作。‌forward_list的实现基于单向链表,‌每个元素都存储在位置无关的存储空间中,‌它们的顺序由每个元素的next指针决定。‌与list相比,‌forward_list的主要区别在于它内部只有一个指向下一个元素的指针,‌而list的每个元素都有两个指针,‌一个指向下一个元素,‌另一个指向前一个元素。‌这使得list可以在两个方向上进行高效迭代,‌但每个元素会消耗更多的存储空间,‌并且插入和删除的时间开销会稍微大一些。‌因此,‌尽管只能进行前向迭代,‌forward_list对象比list对象更高效。‌

         forward_list的一个主要缺点是无法直接通过位置访问元素。‌例如,‌为了访问forward_list中的第六个元素,‌必须从第一个元素一个一个迭代到第六个元素,‌这个操作是线性复杂度的。‌位置距离起始点越远,‌消耗的时间就越长。‌此外,‌forward_list也会消耗额外的内存来记录每一个元素之间的指针信息,‌对于非常小的对象,‌这种消耗尤为明显。‌

        创建forward_list容器的方式有多种,‌包括创建一个没有任何元素的空forward_list容器、‌创建一个包含n个元素的forward_list容器(‌每个元素的值默认为相应类型的默认值)‌,‌以及为每个元素指定初始值等。‌此外,‌还可以通过拷贝已存在的forward_list容器或从其他类型的容器(‌或普通数组)‌中指定区域内的元素来创建新的forward_list容器。‌

        由于效率的考虑,‌forward_list类模板被设计得非常高效,‌有意不提供size成员函数。‌这是因为如果要实现size成员函数,‌就必须保持一个内部计数器记录forward_list的大小,‌这会消耗额外的存储空间,‌并且会让插入、‌删除操作变得更慢。

二、关联容器

1.set/multiset

        STL模板库中的set和multiset是两种重要的数据结构,‌它们分别用于处理唯一键值和允许重复键值的情况。‌

STL set 是一个容器,‌其中所包含的元素的值是唯一的。‌这意味着在set中,‌每个元素只能出现一次。‌set中的元素按一定的顺序排列,‌并被作为集合中的实例。‌具体实现采用了红黑树的平衡二叉树的数据结构,‌这使得对set进行的查找、‌插入和删除操作的时间与集合中元素个数的对数成比例关系。‌此外,‌当游标指向一个已删除的元素时,‌删除操作无效。‌

STL multiset 与set类似,‌但允许集合中的元素值重复。‌这意味着在multiset中,‌同一值可以出现多次。‌multiset同样提供了查找、‌插入和删除操作,‌并且这些操作的时间与multiset中元素个数的对数成比例关系。‌与set不同的是,‌multiset中的元素可以重复,‌这使得它在处理需要存储重复值的数据集时非常有用。‌

        使用STL set和multiset时,‌需要注意以下几点:‌

插入数据:‌set容器使用insert()函数插入数据,‌而multiset也使用相同的函数插入数据,‌但允许重复值。‌大小和交换:‌可以使用size()函数统计set容器中元素的数目,‌使用empty()函数判断容器是否为空。‌swap()函数用于交换两个集合容器的内容。‌查找、‌插入和删除:‌由于set和multiset的实现基于平衡二叉树,‌因此它们提供了快速的查找、‌插入和删除操作。‌这些操作的时间复杂度与集合中元素的数量成对数关系,‌这使得它们在处理大量数据时仍然能够保持高效的性能。‌

        总的来说,‌STL中的set和multiset提供了快速、‌高效的集合操作,‌适用于需要快速查找、‌插入和删除元素的应用场景。‌选择使用哪种容器取决于是否需要存储唯一的元素(‌set)‌还是允许元素重复(‌multiset)‌。‌

2.map/multimap

        STL模板库中的map和multimap是两种关联式容器,‌它们分别用于存储键值对,‌其中map不允许重复的键,‌而multimap允许重复的键。‌

map容器:‌map对象是一个模板类,‌需要两个模板参数:‌一个用于关键字的模板参数和另一个用于存储对象的模板参数。‌例如,‌map<int, string>定义了一个使用int作为索引并关联到string的容器。‌map容器中的元素根据键值进行排序存储,‌不允许有重复的键值。‌在查找、‌插入和删除操作时,‌map的性能通常优于multimap,‌因为它不需要处理重复键的情况。‌

multimap容器:‌与map类似,‌multimap也是一个关联式容器,‌但它允许存储多个具有相同键值的元素。‌multimap的第三个模板参数是一个比较函数,‌用于确定键值的排序方式。‌例如,‌multimap<string, int, Compare>定义了一个字符串作为键,‌整数作为值的容器,‌其中Compare是一个自定义的比较函数。‌由于multimap需要处理重复键的情况,‌因此在某些操作上可能比map稍慢。‌

        总结:‌map和multimap都是STL模板库中的关联式容器,‌用于存储键值对。‌map不允许重复的键值,‌而multimap允许。‌选择使用哪种容器取决于你的具体需求:‌如果你需要存储唯一的键值对并且关心性能,‌map可能是更好的选择;‌如果你需要存储多个具有相同键的值,‌并且不介意性能略有下降,‌那么multimap可能更适合你的需求。

三、其他容器

1.unordered_map

         unordered_map是C++标准库中的一个模板类,用于存储键值对的关联容器。它允许通过键来快速查找、添加或删除元素。

         unordered_map的特点:

不会对元素进行排序。

可以通过键直接访问元素。

查找、添加和删除操作的平均时间复杂度接近于常数O(1)。

不提供自动排序功能,但是可以通过sort对键或值进行排序。

不支持反向迭代器。

 2.unordered_set

        unordered_set 是 C++ STL 库中的一个类模板,用于存储唯一元素的集合,其元素无序且不可以被直接访问。它是基于哈希表的实现,每个元素都被映射到一个特定的位置。

         unordered_set 的优点是插入和查找操作的平均时间复杂度都是常数阶O(1),是数组、列表和集合等其他数据结构的有效替代。

结尾

        选择STL容器时,‌应根据具体需求和情况来决定,‌考虑因素包括数据的访问模式、‌插入和删除操作的频率、‌以及是否需要保持数据的排序等。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。