C++ STL 组成部分、常见容器和原理、空间适配器、迭代器。

1. STL 的基本组成部分

STL(Standard Template Library)是 C++ 标准库中核心的一部分,它提供了一套通用的类和函数模板,用于数据结构和算法的实现。STL 的组成部分不仅限于容器、算法和迭代器,还包括仿函数、适配器和空间配置器。可以按以下六个部分来归纳:

  1. 容器(Container)
    容器用于存储和管理一组数据。STL 中有多种容器,分为序列容器和关联容器。常见的容器包括 vector, deque, list, set, map 等。

  2. 算法(Algorithm)
    STL 提供了一系列通用的算法操作,如排序、查找、修改等。这些算法大多是通过迭代器作用于容器上的。比如 sort(), find(), copy()

  3. 迭代器(Iterator)
    迭代器提供了访问容器内元素的方式,可以被视为通用指针。STL 中有五类迭代器:输入迭代器、输出迭代器、前向迭代器、双向迭代器和随机访问迭代器。不同容器支持不同的迭代器。

  4. 仿函数(Function Object, Functor)
    仿函数是一种类似函数的对象。它通过重载 operator() 来使对象像函数一样调用。STL 中许多算法可以接受仿函数来定义自定义行为,比如在 sort 中传入自定义排序规则。

  5. 适配器(Adaptor)
    适配器是对现有组件的封装或调整,用于改变接口或行为。容器适配器(如 stack, queue)将底层的容器封装成特定的数据结构,而迭代器适配器(如 reverse_iterator)则改变了迭代器的行为。

  6. 空间配置器(Allocator)
    allocator 负责内存的分配与回收。STL 容器使用分配器来管理内存。默认分配器是 std::allocator,但可以提供自定义分配器来优化内存管理。

组成部分功能示例
容器存储数据的结构,提供不同的数据存储方式vector, deque, list, map, set
算法对容器中的数据进行操作,如排序、查找、修改等sort, find, transform, accumulate
迭代器提供对容器元素的访问方式,类似通用指针begin(), end(), rbegin(), rend(), advance()
仿函数像函数一样使用的对象,常用于自定义算法行为plus<int>, minus<int>, 自定义排序函数对象
适配器修改现有组件的行为或接口,用于特定需求容器适配器 stack, queue,迭代器适配器 reverse_iterator
空间配置器管理内存分配和回收,容器默认使用 std::allocator自定义内存分配器 allocator

2. STL 中常见的容器及其实现原理

STL 提供了多种容器用于不同的数据存储和操作场景,常见的容器主要包括:

a. 序列容器(Sequence Containers)

序列容器用于存储元素的线性序列,插入和访问顺序与元素的存储顺序一致。

  • vector
    vector 是一个动态数组,支持随机访问。它在内存中使用连续的内存块存储数据。当容量不足时,vector 会重新分配更大的内存,并将原数据复制过去。插入和删除操作通常在末尾处最有效,复杂度为 O(1)。但在中间插入元素的效率较低,通常需要移动后续的所有元素,复杂度为 O(n)。

  • deque
    deque 是双端队列,允许在两端进行常量时间的插入和删除。它并不是像 vector 那样连续存储,而是使用一系列小块的内存段进行存储。deque 在内存中使用多个段,段与段之间不一定是连续的。

  • list
    list 是双向链表,每个元素由一个节点组成,每个节点包含指向前后节点的指针。list 支持在任意位置快速插入和删除元素(O(1)),但不支持随机访问,访问元素的时间复杂度为 O(n)。

b. 关联容器(Associative Containers)

关联容器存储键值对(或仅键),并根据键进行排序。

  • map
    map 是基于红黑树实现的有序映射。红黑树是一种自平衡二叉搜索树,确保插入、删除和查找操作的时间复杂度为 O(log n)。map 自动根据键的顺序进行排序,且不允许重复键。

  • unordered_map
    unordered_map 基于哈希表实现,元素的顺序无关紧要。哈希表通过哈希函数将键映射到存储桶(bucket)中,查找、插入和删除的平均复杂度为 O(1),最坏情况下为 O(n)(当哈希冲突严重时)。

容器类型实现原理特点
vector序列容器动态数组,连续内存支持随机访问,末尾插入/删除高效
deque序列容器分段连续内存双端插入/删除高效
list序列容器双向链表,节点指针任意位置插入/删除高效,但不支持随机访问
map关联容器红黑树键值对有序存储,插入/删除/查找复杂度 O(log n)
unordered_map关联容器哈希表键值对无序存储,插入/删除/查找平均复杂度 O(1)

3. STL 中 maphashtabledequelist 的实现原理

a. map 实现原理

map 使用红黑树(Red-Black Tree)作为底层数据结构。红黑树是一种自平衡二叉搜索树,它在插入和删除节点时通过重新调整节点颜色(红或黑)和旋转来保持树的平衡。红黑树的操作(如插入、删除和查找)的时间复杂度是 O(log n)。

b. hashtable 实现原理

unordered_map 是基于哈希表的无序关联容器。哈希表通过哈希函数将键映射到存储桶中,然后在每个存储桶中存储对应的值。为了处理哈希冲突,哈希表通常采用链表法(链地址法)或开放地址法。哈希表的平均时间复杂度为 O(1),但在最坏情况下(所有键哈希到同一个桶)会退化到 O(n)。

c. deque 实现原理

deque 使用分段连续的内存块来存储元素。每个块都是一段连续的内存,这样既允许快速的随机访问(如 vector),又允许在前后进行高效的插入和删除。deque 的设计旨在优化双端插入和删除的性能,而不像 vector 那样需要在首部插入时移动所有元素。

d. list 实现原理

list 是基于双向链表实现的容器。每个节点存储数据和两个指针,分别指向前驱节点和后继节点。双向链表允许在常量时间内进行插入和删除操作,但不支持随机访问。遍历链表的时间复杂度为 O(n)。


4. STL 的空间配置器(Allocator)

allocator 是 STL 中用于管理内存分配的组件。每个容器都使用分配器来处理内存的分配和释放,默认分配器是 std::allocator。分配器的工作流程包括以下几个步骤:

  • allocate:为指定数量的对象分配未构造的内存。
  • construct:在已分配的内存上构造对象。
  • destroy:调用对象的析构函数,销毁对象但不释放内存。
  • deallocate:释放先前分配的内存。

分配器的核心函数包括:

template <typename T>
struct MyAllocator {
    // 分配内存
    T* allocate(size_t n) {
        return static_cast<T*>(::operator new(n * sizeof(T)));
    }
    
    // 构造对象
    void construct(T* p, const T& val) {
        new(p) T(val);
    }

    // 销毁对象
    void destroy(T* p) {
        p->~T();
    }

    // 释放内存
    void deallocate(T* p, size_t n) {
        ::operator delete(p);
    }
};

功能函数作用
分配内存allocate分配一段未构造的内存
构造对象construct在已分配的内存上构造对象
销毁对象destroy调用对象的析构函数
释放内存deallocate释放分配的内存

两种C++类对象实例化方式的异同 在c++中,创建类对象一般分为两种方式:一种是直接利用构造函数,直接构造类对象,如 Test test();另一种是通过new来实例化一个类对象,如 Test *pTest = new Test;那么,这两种方式有什么异同点呢?

我们知道,内存分配主要有三种方式:

(1)静态存储区分配:内存在程序编译的时候已经分配好,这块内存在程序的整个运行空间内都存在。如全局变量,静态变量等。

(2) 栈空间分配:程序在运行期间,函数内的局部变量通过栈空间来分配存储(函数调用栈), 当函数执行完毕返回时,相对应的栈空间被立即回收。主要是局部变量。

(3)堆空间分配:程序在运行期间,通过在堆空间上为数据分配存储空间,通过malloc和new创 建的对象都是从堆空间分配内存,这类空间需要程序员自己来管理,必须通过free()或者是delete() 函数对堆空间进行释放,否则会造成内存溢出。

那么,从内存空间分配的角度来对这两种方式的区别,就比较容易区分:

(1)对于第一种方式来说,是直接通过调用Test类的构造函数来实例化Test类对象的,如果该实例 化对象是一个局部变量,则其是在栈空间分配相应的存储空间。

(2)对于第二种方式来说,就显得比较复杂。这里主要以new类对象来说明一下。new一个类对象, 其实是执行了两步操作:首先,调用new在堆空间分配内存,然后调用类的构造函数构造对象的内 容;同样,使用delete释放时,也是经历了两个步骤:首先调用类的析构函数释放类对象,然后调 用delete释放堆空间。

2. C++ STL空间配置器实现很容易想象,为了实现空间配置器,完全可以利用new和delete函数并对其进行封装实现STL的空间配置器,的确可以这样。但是,为了最大化提升效率,SGI STL版本并没有简单的这样做,而是采取了一定的措施,实现了更加高效复杂的空间分配策略。由于以上的构造都分为两部分,所以,在SGI STL中,将对象的构造切分开来,分成空间配置和对象构造两部分。 内存配置操作: 通过alloc::allocate()实现 内存释放操作: 通过alloc::deallocate()实现 对象构造操作: 通过::construct()实现 对象释放操作: 通过::destroy()实现 关于内存空间的配置与释放,SGI STL采用了两级配置器:一级配置器主要是考虑大块内存空间, 利用malloc和free实现;二级配置器主要是考虑小块内存空间而设计的(为了最大化解决内存碎片问题,进而提升效率),采用链表free_list来维护内存池(memory pool),free_list通过union结构实现,空闲的内存块互相挂接在一块,内存块一旦被使用,则被从链表中剔除,易于维护。

5. STL 容器查找的时间复杂度是多少,为什么?

1. vector 采用一维数组实现,元素在内存连续存放,不同操作的时间复杂度为: 插入: O(N) 查看: O(1) 删除: O(N)

2. deque 采用双向队列实现,元素在内存连续存放,不同操作的时间复杂度为: 插入: O(N) 查看: O(1) 删除: O(N)

3. list 采用双向链表实现,元素存放在堆中,不同操作的时间复杂度为: 插入: O(1) 查看: O(N) 删除: O(1)

4. map、set、multimap、multiset 上述四种容器采用红黑树实现,红黑树是平衡二叉树的一种。不同操作的时间复杂度近似为: 插入: O(logN) 查看: O(logN) 删除: O(logN)

5. unordered_map、unordered_set、unordered_multimap、 unordered_multiset 上述四种容器采用哈希表实现,不同操作的时间复杂度为: 插入: O(1),最坏情况O(N) 查看: O(1),最坏情况O(N) 删除: O(1),最坏情况O(N)

注意:容器的时间复杂度取决于其底层实现方式。

6 迭代器什么时候会失效?

1. 对于序列容器vector,deque来说,使用erase后,后边的每个元素的迭代器都会失效,后边每个 元素都往前移动一位,erase返回下一个有效的迭代器。

2. 对于关联容器map,set来说,使用了erase后,当前元素的迭代器失效,但是其结构是红黑树,删除当前元素,不会影响下一个元素的迭代器,所以在调用erase之前,记录下一个元素的迭代器即可。

3. 对于list来说,它使用了不连续分配的内存,并且它的erase方法也会返回下一个有效的迭代器,因此上面两种方法都可以使用。

7.STL中迭代器的作用,有指针为何还要迭代器?

1. 迭代器的作用

(1)用于指向顺序容器和关联容器中的元素

(2)通过迭代器可以读取它指向的元素

(3)通过非const迭代器还可以修改其指向的元素

2. 迭代器和指针的区别

迭代器不是指针,是类模板,表现的像指针。他只是模拟了指针的一些功能,重载了指针的一些操 作符,-->、++、--等。迭代器封装了指针,是一个”可遍历STL( Standard Template Library)容 器内全部或部分元素”的对象,本质是封装了原生指针,是指针概念的一种提升,提供了比指针更 高级的行为,相当于一种智能指针,他可以根据不同类型的数据结构来实现不同的++,--等操作。 迭代器返回的是对象引用而不是对象的值,所以cout只能输出迭代器使用取值后的值而不能直接输 出其自身。

3. 迭代器产生的原因 Iterator类的访问方式就是把不同集合类的访问逻辑抽象出来,使得不用暴露集合内部的结构而达 到循环遍历集合的效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值