STL 总结

STL

  • 在 C++ 标准模板库(STL)中,主要包含了一系列的容器迭代器算法函数对象适配器

容器

  • 容器是用于存储数据的类模板。STL 容器可以分为序列型容器、关联型容器和链表型容器三类:
  • 序列型容器:vectordequearray
  • 关联型容器:setmapmultisetmultimap
  • 链表型容器:forward_listlistunordered_setunordered_mapunordered_multisetunordered_multimap

迭代器

算法

  • STL 算法通过迭代器与容器进行交互,进行数据处理和操作。
  • 非修改序列算法:findcount
  • 修改序列算法:copymovetransform
  • 排序算法:sortstable_sort
  • 二分搜索算法:lower_boundupper_bound
  • 数值算法:accumulateinner_product

函数对象

  • STL 中的函数对象是实现了 operator() 的对象,常用于算法中作为策略或条件表达式。包括算术运算类、关系运算类、逻辑运算类。

适配器

  • 适配器用于修改容器或迭代器的接口。
  • 容器适配器:stackqueuepriority_queue

一、vector

vector 底层实现原理

  • 底层实现了一个动态数组
  • 类构成:
    • protected 的方式继承自 _Vector_base,基类的 public 在子类中将变为 protected,其他的权限不变。
      class vector : protected _Vector_base<_Tp, _Alloc> {
             }
      
    • _Vector_base 组成:
      在这里插入图片描述
      • _M_start:指向第一个元素的位置 → vec.begin()
      • _M_finish:指向最后一个实际存储元素的下一个位置 → vec.end()
      • _M_end_of_storage指向为 vector 所分配的内存块的末尾之后的第一个位置
      • _M_start_M_finish 之间的内存是 vector 实际使用的空间 → vec.size()
      • _M_start_M_end_of_storage 之间的内存是 vector 可以用来存储元素的空间 → vec.capacity()
      • _M_finish_M_end_of_storage 之间的内存是已经分配好可以随时使用的,但是目前未使用的。
      template<typename _Tp, typename _Alloc>
      struct _Vector_base
      {
             
      	struct _Vector_impl_data
      	{
             
      		pointer _M_start;
      		pointer _M_finish;
      		pointer _M_end_of_storage;
      		...
      	}
      	...
      }
      
  • 构造函数:
    • 无参构造函数:不会申请动态内存,保证性能优先。
    • 初始化元素个数的构造函数:一次性申请足够的动态内存避免多次申请动态内存,影响性能。
  • 插入元素:
    • 往最后位置插入:
      • 检查空间是否需要动态分配内存,是否需要扩容(_M_finish 是否等于 _M_end_of_storage)。
      • 插入到最后:push_back()emplace_back()++_M_finish
    • 往其他位置插入。
      • 检查空间是否需要动态分配内存,是否需要扩容。
      • 将插入位置之后的元素往后平移一位,然后插入元素:insert()
  • 删除元素:不会释放现有已经申请的内存
    • 删除最后一个元素 pop_back()_M_finish 往前移动一位(--_M_finish)。
    • 删除其他元素 erase():将待删元素之后的元素向前平移一位,_M_finish 往前移动一位。
  • 读取元素:返回的是具体元素的引用
    • 操作符[]
    • at():比操作符[] 多了一个检查越界的动作,越界后会抛出错误。
  • 修改元素:vector 不支持直接修改某个位置的元素
    • 通过读取元素,获取引用,然后进行修改。
    • 先删除后插入。
  • 释放空间:
    • swap() 交换一个空容器。
      std::vector<int>().swap(vec); 
      
    • clear(),然后 shrink_to_fit()释放掉未使用的内存

vector 内存增长机制

  • 特点:
    • 内存空间只会增加不会减少
    • vector 的内存是连续的
    • 不同平台的增长方式不一样,Linux 下是以翻倍的方式进行增长
  • 增长特征:
    • 无参构造,连续插入一个,增长方式:1、2、4、8 …
    • 有参构造,连续插入一个,增长方式:10、20、40 …
  • 增长时的具体操作:
    • 此时 _M_finish == _M_end_of_storage将内存空间大小翻倍并产生新的内存空间。
    • 将容器原来内存空间中的数据复制到新的内存空间中
    • 释放容器原来的内存空间
    • 插入新元素。
  • 注意:vector 中的元素为指针时,不会调用析构函数,需要手动释放内存

vector 中 reserve 和 resize 的区别

  • 共同点:
    • 调用它们,容器内原有的元素不受影响
    • 它们都起到增加的作用,对于缩小操作直接无视。
  • 区别:
  • reserve 能增加 vectorcapacity,但是它的 size 不会改变。
  • resize 既增加了 vectorcapacity,又增加了 size
  • 应用场景:
    • reserve 用来避免多次内存分配
    • resize 确保操作符[] at() 的安全性

vector 中的元素类型为什么不能是引用

  • 引用的特征:
    • 引用必须要进行初始化,不能初始化为空对象,初始化后不能改变指向。
    • 引用是别名,不是对象,没有实际的地址,不能定义引用的指针,也不能定义引用的引用。
  • 不能为引用分配内存 → 不能定义引用的指针。
  • push_back() 传入左值时会调用拷贝构造函数 → 不能定义引用的引用。
  • 基于操作符[]at(),将会获取引用的引用 → 不能定义引用的引用。

vector 中 push_back 和 emplace_back 的区别

  • push_back 如果传入的是左值,会调用拷贝构造函数;如果传入的是右值,会调用移动构造函数。
  • emplace_back 利用传入的参数在容器内存中直接构造元素,而无需首先创建临时对象再将其拷贝或移动到容器中,它使用 forward 完美转发将参数直接传递给元素的构造函数。
    vector<string> vec;
    
    // 使用 push_back 添加字符串
    string str = "Hello";
    vec.push_back(str);                   // 调用拷贝构造函数
    vec.push_back(string("World"));       // 调用移动构造函数
    
    // 使用 emplace_back 添加字符串
    vec.emplace_back("Hello, World");     // 直接在容器中构造字符串
    
    vector<string> vec;
    
    string hello = "Hello";
    // 这里会调用拷贝构造函数,因为传递的是一个现有的 string 对象
    vec.emplace_back(hello);
    
    // 这里直接在容器中构造一个新的 string,不调用拷贝或移动构造函数
    vec.emplace_back("World");
    

二、list

  • 底层实现了一个双向循环链表
  • 类构成:
    • protected 的方式继承自 _List_base
      class list : protected _List_base<_Tp, _Alloc> {
             }
      
    • _List_base_List_impl_List_node_M_storage:存储具体的值。
    • _List_node 继承自 _List_node_base_M_next_M_prev
      _List_node_base* _M_next;
      _List_node_base* _M_prev;
      
  • 构造函数:
    • 不管如何构造,初始都会构建一个空节点 dummyHead
    • 空节点用来表示整个双向循环链表。
  • 迭代器:
    • ++ 往下移动指针 → _M_node = _M_node->_M_next
    • -- 向上移动指针 → _M_node = _M_node->_M_prev
  • 获取第一个元素:空节点的下一个节点 → this->_M_impl._M_node._M_next
  • 获取最后一个元素:空节点的上一个节点 → 先获取空节点 this->_M_impl._M_node,再 --
  • 插入元素:每插入一个元素,都临时为该节点分配内存,修改指向,size++

在这里插入图片描述


三、deque

  • 目的是实现双端数组,底层实现了一个双向开口的连续线性空间
  • 类构成:
    • protected 的方式继承自 _Deque_base
      class deque : protected _Deque_base<_Tp, _Alloc> {
             }
      
    • _Deque_base_Deque_impl
      • _M_map指针数组
      • _M_map_size_M_map 的容量。
      • _M_start:记录 _M_map 指针数组中首个连续空间的信息。
      • _M_finish:记录 _M_map 指针数组中最后一个连续空间的信息。
  • 迭代器 _Deque_iterator
    • _M_cur:指向当前正在遍历的元素。
    • _M_first:指向当前连续空间的首地址。
    • _M_last:指向当前连续空间的尾地址。
    • _M_node: 指向 _M_map 指针数组中存储的指向连续空间的指针。
  • __deque_buf_size:连续空间中能容纳的元素个数;如果元素大小小于 512 字节,则能容纳 512 / 元素大小个元素,否则只能容纳一个元素。
  • _M_initialize_map
    • 创建 _M_map,最小为 8,并配置缓冲区。
    • _M_start_M_finish 指向中间的位置,方便公平地往上或者向下扩展空间。
  • push_back
    • 先看当前连续空间够不够,够就直接插入。
    • 不够的话,再看 _M_map 空间够不够,够就生成新的连续空间。
    • 不够的话,就生成新的 _M_map,把旧的_M_map 中的数据挪到新的 _M_map 中。
  • pop_back:删除当前连续空间的最后一个元素,如果当前连续空间没有数据了,则释放该连续空间。

在这里插入图片描述


四、vector、list、deque 使用场景

  • 如果需要高效地快速访问(随机存取),并且不在乎插入和删除的效率,使用 vector
  • 如果需要大量地插入和删除,并且不关心快速访问(随机存取),使用 list
  • 如果需要快速访问(随机存取),并且关心两端数据的插入和删除,使用 deque

五、priority_queue

  • 底层实现了一个堆(Heap),堆是一种高效维护集合中最大或最小元素的数据结构。
    • 大根堆:根节点最大的堆,用于维护和查询 max
    • 小根堆:根节点最小的堆,用于维护和查询 min
    • 堆是一棵树,并且满足堆性质:
      • 大根堆任意节点的关键码 ≥ \geq 它所有子节点的关键码(父 ≥ \geq 子)
      • 小根堆任意节点的关键码 ≤ \leq 它所有子节点的关键码(父
  • 15
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: STL(Standard Template Library)算法是C++标准库中的一组通用算法,用于处理各种类型的数据结构。STL算法提供了一系列函数对象和迭代器,以便能够对容器中的元素进行常见的操作,如查找、排序、删除、替换等。 STL算法主要分为以下几类: 1. 非修改性算法:这类算法不会改变容器中的元素,例如查找、计数、比较等。常用的函数包括find、count、equal等。 2. 修改性算法:这类算法会修改容器中的元素,例如排序、删除、替换等。常用的函数包括sort、remove、replace等。 3. 排序算法:STL提供了多种排序算法,如快速排序、归并排序等。常用的函数包括sort、stable_sort等。 4. 数值算法:这类算法主要用于数值计算,如求和、积累、内积等。常用的函数包括accumulate、inner_product等。 5. 集合操作:这类算法主要用于集合的操作,如交集、并集、差集等。常用的函数包括set_intersection、set_union等。 STL算法的优势在于其通用性和高效性。通过使用STL算法,我们可以在不同类型的容器上进行相同的操作,避免了针对不同容器实现不同操作的麻烦。此外,STL算法的底层实现经过优化,通常比手动编写的算法更高效。 总之,STL算法是C++标准库中提供的一组通用算法,用于处理各种类型的数据结构。通过使用STL算法,我们可以方便地对容器进行各种操作,提高开发效率并保证程序的高效性。 ### 回答2: STL算法是C++标准模板库(Standard Template Library)中的一部分,它提供了丰富的算法操作,方便我们在处理数据时进行快速、高效的操作。 STL算法包含很多算法函数,比如排序(sort)、查找(find)、替换(replace)、去重(unique)、合并(merge)等等。这些算法函数可以处理各种数据容器,比如数组、容器、字符串等。 STL算法的使用非常方便,我们只需要包含<algorithm>头文件,并使用其中的函数即可。例如,我们可以使用sort函数对一个数组进行排序,使用find函数在容器中查找某个元素,使用replace函数替换容器中的某个值等等。 STL算法的好处是它提供了统一的接口和一致的命名规范,使得我们在处理不同的数据结构时都能使用相同的函数名来调用相应的操作。这样可以大大提高代码的复用性和可读性,减少了我们编写和维护代码的工作量。 此外,STL算法还可以通过函数对象(function object)、谓词(predicate)等机制来灵活地进行自定义操作。我们可以自定义比较函数来实现按照特定条件进行排序,或者自定义判断函数来在查找时指定特定的条件等。这样可以让我们的代码更加灵活和可扩展。 总之,STL算法是C++中非常重要和强大的一部分,它提供了丰富的算法函数,方便我们在处理数据时进行各种操作。它的使用简单方便,可以提高代码的复用性和可读性,是C++开发中不可或缺的工具之一。 ### 回答3: STL(Standard Template Library,标准模板库)是C++中的一个重要部分,其中的算法部分(Algorithm)是其核心。STL Algorithm提供了一组强大的算法,用于对各种容器(如数组、向量、列表等)中的元素进行各种操作和处理。 STL算法可以分为若干类别,其中包括常用算法、排序算法、查找算法、数值算法等等。常用算法如copy(复制)、find(查找)、transform(转换)等,它们可以快速且高效地完成各种常见的操作。排序算法如sort(排序)、stable_sort(稳定排序)等,可以根据指定的规则对容器中的元素进行排序。查找算法如binary_search(二分查找)和lower_bound(寻找下界)等,可以在有序容器中快速查找指定元素。数值算法如accumulate(累加)、partial_sum(部分求和)等,用于对数值进行处理和计算。 STL的优势在于其算法的可重用性和通用性。STL Algorithm是基于模板的设计,它可以适用于不同类型的容器和元素。无论是基本数据类型还是自定义类型,只要满足一定的要求(如支持迭代器),就可以使用STL算法对其进行操作。这种通用性使得STL Algorithm可以被广泛应用于各种场景,大大提高了开发效率。 总结来说,STL算法是C++中的一组强大工具,用于对各种容器中的元素进行各种操作和处理。它的通用性和可重用性使得开发者可以更加高效地完成各种任务,提高代码的可读性和可维护性。无论是在日常开发中还是在算法竞赛中,STL Algorithm都扮演着重要的角色。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值