12. STL的原理

目录

[1. 容器、迭代器、算法](#1. 容器、迭代器、算法)

什么是迭代器?

迭代器的作用?

迭代器的类型?

迭代器失效

迭代器的实现细节:

[2. 适配器](#2. 适配器)

什么是适配器?

适配器种类:

[3. 仿函数](#3. 仿函数)

什么是仿函数?

仿函数与算法和容器的关系:

4.空间配置器

5.STL的优缺点是什么?设计的好的地方和不好的地方?


STL包含六大组件,这些组件是杂糅到一起的,互相之间有关联。

问到就详细讲容器和迭代器

1. 容器、迭代器、算法

上一篇讲了容器,这里主要介绍它和其他组件的关系

组件 与容器的关系 典型协作场景
迭代器 容器提供迭代器接口 begin()、end() 算法通过迭代器访问容器内容
算法 通过迭代器操作容器 sort(), find()等算法处理容器数据
仿函数 定义容器排序/比较规则 set的比较函数
适配器 基于容器构建特殊接口 stack基于deque实现
分配器 控制容器内存管理 自定义内存分配策略
  • 算法通过迭代器操作容器

  • 同一算法可作用于不同容器

  • 不同算法对迭代器类别有不同要求:

    • sort需要随机访问迭代器(不能用于list

    • find只需要输入迭代器(可用于所有容器)

什么是迭代器?

迭代器的设计非常的天才,它充分体现面向对象的封装特性,容器的底层各不相同(哈希、树、数组等),容器内部是私有的,外部访问不了他们的内部结构,使用迭代器封装,**在不需要暴露底层复杂结构的同时,还提供了统一、简单的方式访问容器,**也就是它提供了一种统一的方法来访问容器中的元素,而不需要了解容器的内部结构。

迭代器的作用?

迭代器把容器和算法连接了起来,作为算法和容器之间的桥梁,算法通过迭代器直接访问各种容器,如果不符合算法要求又可以直接报错。比如sort算法,在设计时设计的是需要给他传一个随机迭代器,如果给他一个其他类型的迭代器则会报错。

算法的具体实现,直接脱离了具体的存储结构 ,也就是各种容器,直接通过迭代器来实现,依赖各种迭代器的类型,体现了设计角度的复用,使得同一算法可应用于任何满足迭代器要求的容器

指针本身就是随机访问迭代器,使得传统数组也能参与算法:

cpp 复制代码
int arr[10] = {...};
std::sort(arr, arr + 10);

迭代器的类型?

在C++标准库中,不同容器支持不同类型的迭代器:

  • std::list:使用双向迭代器,支持前向和后向遍历。
  • std::vector 和 std::deque、std::string:使用随机访问迭代器,支持任意位置的访问和高效的算术运算。
  • std::forward_list:使用前向迭代器,仅支持单向遍历。
  • std::set, std::map, std::multiset, std::multimap:使用双向迭代器。
  • std::unordered_set, std::unordered_map, std::unordered_multiset, std::unordered_multimap:使用前向迭代器。

层级低的容器不能访问层级高的算法。层级高的容器可以访问层级低的函数。比如:vector和string就可以调用reverse(双向)。

按功能从弱到强排列如下:

  1. 输入迭代器(Input Iterator)
  2. 输出迭代器(Output Iterator)
  3. 前向迭代器(Forward Iterator)
  4. 双向迭代器(Bidirectional Iterator)
  5. 随机访问迭代器(Random Access Iterator)

比如说 sort 使用的就是随机迭代器, list 就不能调用它,因为 list 使用的是双向迭代器。

迭代器失效

迭代器失效是指一个原本指向容器元素的迭代器在容器被修改后,可能变得无效。使用失效的迭代器会导致程序运行错误甚至崩溃。

对于 std::vector 来说,以下操作可能导致迭代器失效:

  1. 内存重新分配:

    • 扩容时,底层存储数据的内存地址会重新分配。
    • 所有现有的迭代器、指针和引用都会失效
  2. 元素插入或删除

    • 插入或删除可能会导致部分迭代器失效,尤其是插入删除位置的迭代器

vector扩容,也就是说vector底层旧空间被释放掉,而在打印时,it还使用的是释放前的旧空间,在对it进行操作时,实际操作的是一块已经被释放的空间,从而引起崩溃。

erase删除某位置的元素后,pos位置之后的元素会向前搬移,没有导致底层空间的改变,理论上迭代器应该不会失效,但是如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效。

其他:

string下的失效, 与vector类似,string在插入+扩容操作+erase后,迭代器也会失效,不能再访问。erase函数执行后,it所指向的节点已经被删除,因此it无效,在下一次使用it时,必须先给其赋值。也就是用迭代器接受一下erase的返回值。

list 的底层结构为带头结点的双向循环链表,因此在list中进行插入 时是不会导致l ist的迭代器失效的,只有在被删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。

迭代器的实现细节:

08.vector-CSDN博客

2. 适配器

什么是适配器?

适配器是一种结构型设计模式 ,它将一个类的接口转换成客户所期望的另一个接口。在STL中,适配器主要作用于容器和迭代器,通过封装现有组件并提供新的接口来满足不同的需求 。

主要和容器有关,也可能和迭代器有关,比如说反向迭代器就是一个适配器 ,适配器体现的是封装复用 。所以可以分为容器适配器和迭代器适配器

适配器种类:

容器适配器:10.stack 和 queue_queue 与stack-CSDN博客

迭代器适配器(以反向迭代器为例):10.stack 和 queue_queue 与stack-CSDN博客

cpp 复制代码
正向序列:  [A][B][C][D][E]
           ↑  ↑  ↑  ↑  ↑   ↑
正向迭代器:begin           end

反向序列:      [E][D][C][B][A]
            ↑  ↑  ↑  ↑  ↑  ↑
反向迭代器:rbegin          rend
对应正向:  end            begin
  1. rbegin() 的实现

    • 内部实际上存储的是 end() 迭代器

    • 但解引用时返回的是 end()-1 位置的元素

  2. rend() 的实现

    • 内部实际上存储的是 begin() 迭代器

    • 解引用时会试图访问 begin()-1(这是未定义行为)

  3. 对称性

    • rbegin().base() == end()

    • rend().base() == begin()

3. 仿函数

什么是仿函数?

仿函数(Functor),也称为函数对象,是C++中一种特殊的类。它通过重载operator() 操作符,使得该类的对象可以像普通函数一样被调用

针对算法可以实现各种功能,最经典的就是比大小,特点就是可以控制规则

仿函数与算法和容器的关系:

算法 通过仿函数参数实现灵活的行为控制:

cpp 复制代码
// 排序算法使用仿函数定义比较规则
sort(vec.begin(), vec.end(), greater<int>());

某些容器 依赖仿函数定义元素的组织方式

cpp 复制代码
// set使用less仿函数默认升序排列
std::set<int, std::less<int>> ascendingSet;

// priority_queue使用greater仿函数实现小顶堆
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;

C++11后,lambda表达式成为创建仿函数的便捷方式:

cpp 复制代码
sort(v.begin(), v.end(), [](auto& a, auto& b) {
    return a.value < b.value;
});

4.空间配置器

空间配置器是STL中负责内存管理的核心组件 ,它为容器提供统一的内存分配和释放接口

主要职责是:

  • 内存的分配与释放

  • 对象的构造与析构

  • 提供标准化的内存管理接口

所有STL容器都使用空间配置器管理内存 ,默认使用std::allocator:

cpp 复制代码
template <class T, class Allocator = std::allocator<T>>
class vector;

template <class T, class Allocator = std::allocator<T>>
class list;

其他功能:

  • 解耦内存策略:将内存管理与容器实现分离

  • 灵活扩展:允许自定义内存管理策略(如内存池、共享内存等)

5.STL的优缺点是什么?设计的好的地方和不好的地方?

STL缺点:

  • 不支持线程安全(注意容器不是线程安全的)
  • 没有持续更新,增加新容器(跳表。。。)
  • 有一些冗余设计,如array,forward_list 等

STL优点:

1. 泛型编程(核心优势)

STL通过模板实现了类型无关的通用数据结构和算法 *,*一套代码可处理任意数据类型,极大提高了代码复用率。

2. 高性能(核心竞争力)

采用编译时多态(模板)替代运行时多态(虚函数),算法经过深度优化(如sort用内省排序),内存管理可定制(如内存池),效率接近手写C代码。

3. 组件化设计(架构优势)

六大组件通过迭代器解耦,扩展性强且维护成本低。

  1. 空间配置器给容器提供统一的内存分配和释放接口
  2. 适配器作用于容器,通过封装现有组件并提供新的接口来满足不同的需求(stack、queue)
  3. 适配器作用于迭代器,通过封装现有组件并提供新的接口来满足不同的需求(反向迭代器)
  4. 容器提供了迭代器接口(begin、end)
  5. 迭代器用来实现了算法,算法脱离具体的存储结构
  6. 算法通过统一简单的方式:迭代器,操作各种底层不同容器
  7. 迭代器作为算法和容器之间的桥梁存在
  8. 仿函数给容器提供了元素组织方式(默认建大堆→建小堆)
  9. 仿函数给算法提供了更灵活的机制(sort默认排升序→排降序)
相关推荐
小赖同学吖10 分钟前
Java 中的继承与多态:面向对象编程的核心特性
java·开发语言
萧鼎17 分钟前
Python WebSockets 库详解:从基础到实战
开发语言·python
长潇若雪20 分钟前
《STL 六大组件之容器篇:简单了解 list》
开发语言·c++·经验分享·list·类和对象
君义_noip38 分钟前
信息学奥赛一本通 1524:旅游航道
c++·算法·图论·信息学奥赛
西元.40 分钟前
线程等待与唤醒的几种方法与注意事项
java·开发语言
独好紫罗兰1 小时前
洛谷题单2-P5717 【深基3.习8】三角形分类-python-流程图重构
开发语言·python·算法
落榜程序员1 小时前
Java基础-25-继承-方法重写-子类构造器的特点-构造器this的调用
java·开发语言
forestsea1 小时前
Java 应用程序CPU 100%问题排查优化实战
java·开发语言
啊阿狸不会拉杆1 小时前
第十八章:Python实战专题:北京市水资源数据可视化与图书馆书籍管理应用开发
开发语言·python·信息可视化·用户界面
喾颛顼1 小时前
Mac下小智AI本地环境部署
人工智能·经验分享·macos