STL和泛型编程

以STL为目标探讨泛型编程。

一.STL六大部件

image-20240309114843162

算法通过迭代器来处理容器中的数据。

容器使用分配器来管理它们的内存。分配器将内存分配和释放的实现细节封装起来,使得容器可以在运行时根据需要动态地分配内存。

适配器允许在现有的容器或迭代器之上实现不同的行为或提供新的接口,从而更加方便地操作数据。

仿函数通常用于算法、容器和其他组件中,以提供各种功能和灵活性。它们可以作为算法的参数传递,也可以用于自定义容器的排序、查找和比较操作等。使用仿函数可以在编译时将函数调用行为封装到对象中,从而提供更灵活和通用的功能。算法通常通过仿函数来完成比较、排序和其他操作,这使得算法可以适用于各种数据类型和需求。

image-20240309150135553

image-20240309150152283

"前开后闭"区间

所有容器满足:"前开后闭"区间

image-20240311105245779

二.容器

image-20240311110352371

  • array无法扩充。

  • vector尾可以扩充。

  • deque头尾都可以扩充。

set和map一般是红黑树实现,unordered_set和unordered_map一般是hash表实现(分离链接法)。

(1)顺序容器

1.array

image-20240311160445499

image-20240311160454539

image-20240311160511440

源码剖析

image-20240319191923470

2.vector

image-20240311162255981

image-20240311170616221

image-20240311170632347

image-20240311170648403

image-20240311170707731

源码剖析

image-20240312190104257

几乎所有vector的实现都是当vector需要扩充的时候,进行两倍成长。会造成大量的拷贝构造和析构。

image-20240312190748654image-20240312190757806

vector的迭代器

image-20240312191222285

3.list

image-20240311191644564

image-20240311191702102

image-20240311191924433

源码剖析

image-20240312165002089

list本身就是一个指针(link_type),link_type是一个指针(list_node*)指向了一个_list_node结构体。

list是非连续空间,因此iterator不能是指针(只有vector和array的iterator是指针,其余都是结构体)。

image-20240312171959909

迭代器的设计规则

迭代器必须定义五种类型,以便于能回答算法的问题。

image-20240312180256145

1.迭代器的类型。关于前向、双向、随机等。

2.两个迭代器间的距离。

3.迭代器指向的元素的类型。

image-20240312180450120

  1. iterator_traits<Iterator>::value_type:迭代器指向的元素的类型。
  2. iterator_traits<Iterator>::difference_type:两个迭代器之间的差值类型。这通常用于表示两个迭代器之间的距离。
  3. iterator_traits<Iterator>::iterator_category:迭代器的类型分类,如 std::input_iterator_tagstd::forward_iterator_tagstd::bidirectional_iterator_tagstd::random_access_iterator_tag。这用于区分迭代器的不同能力。
  4. iterator_traits<Iterator>::pointeriterator_traits<Iterator>::reference:这两个类型通常分别定义为 value_type*value_type&,但某些迭代器类型可能会提供特殊的指针或引用类型。

image-20240312181455515

为了应付迭代器有指针的形式,需要一个中间层traits。traits中使用了模板的偏特化,区分当参数是指针或类。

image-20240312182143408

关于重载++操作符

image-20240312172153859

重载后置++

  • 先调用拷贝构造函数将当前的对象复制给tmp
  • 然后对当前对象进行前置++操作
  • 最后返回tmp

关于返回值

  • 因为标准库中后置++不可以连做两次,因此这里值传递返回。前置++可以连做两次,返回引用。
关于重载->和*操作符

image-20240312174033534

image-20240312174422064

list的指针类型修改为指向自己的,相比于指向void更精确。

4.forward_list

只提供push_front插入,且不提供size()。

image-20240311192615727

image-20240311192730020

image-20240311192738244

源码剖析

image-20240319192235418

5.deque

image-20240311193711133

image-20240311193717925

image-20240311193723184

源码剖析

image-20240319192606545

在C++标准库中,deque(双端队列)的实现是优化了从序列两端插入和删除操作的容器。与vector相比,它不保证所有元素都在连续的内存地址中,但仍然能够提供对任何元素的直接(随机访问)访问。这是通过一个复杂的内部数据结构实现的,旨在兼顾两端操作的高效性和随机访问的能力。

底层数据结构

deque的实现通常采用一种“分块数组”(或称为“段数组”)的策略。具体来说:

  1. 多个固定大小的数组块deque由多个数组块组成,每个块能够存储固定数量的元素。这些数组块的大小通常是实现定义的,可以根据元素的类型和容器的大小动态调整以优化性能。
  2. 映射器(Map)deque内部使用一个中心数据结构(通常是一个数组或动态数组),来维护对这些块的引用(指针或索引)。这个中心结构被称为映射器,它按序记录了所有块的位置,使得可以快速定位到任何一个元素所在的块。
操作实现
  • 随机访问:通过映射器,deque可以计算出任何元素的位置,即首先定位到正确的块,然后在该块内进行索引。这使得随机访问的时间复杂度为O(1),与vector相同。
  • 在两端插入和删除:由于deque的设计,它可以在头部或尾部添加或移除元素而无需移动其他元素。如果在一端的块已满且需要添加更多元素时,deque将分配一个新的块并更新映射器。这种操作通常比vector的动态扩容(涉及复制现有元素到新的内存地址)更高效。
  • 动态扩容:当deque增长到超出当前映射器容量时,它需要一个更大的映射器来存储更多的块引用。此时,deque将分配一个新的更大的映射器,复制旧映射器中的引用到新映射器中,然后释放旧映射器。
deque的设计

image-20240320111058160

deque的迭代器

image-20240320112755459

deque的插入

image-20240320113118465

image-20240320113306125

deque模拟连续空间

image-20240320114827113

image-20240320114834784

image-20240320114842030

image-20240320114849775

image-20240320114857650

(2)容器适配器

不提供iterator,因为一定是一端进,一端出。提供iterator会导致可以从任意地方进出容器,破坏一致性。

1.stack

image-20240311194442422

image-20240311194454801

2.queue

image-20240311194749072

image-20240311194707104

image-20240311194737285

queue和stack源码剖析

image-20240320115428548

image-20240320115439820

image-20240320115458214

image-20240320115507497

image-20240320115520364

image-20240320193044530

image-20240320193211014

(3)关联式容器

1.mutiset

image-20240312093341996

key就是value,value就是key。

image-20240312093205615

image-20240312093232533

image-20240312093251835

2.multimap

image-20240312093403401

image-20240312094237031

image-20240312094246872

image-20240312094324956

(4)哈希结构

1.unordered_multiset

image-20240312100501750

image-20240312100518642

image-20240312100529018

image-20240312100555255

image-20240312100603352

2.unordered_multimap

image-20240312104357521

image-20240312104412163

(5)元素不可以重复

1.set

image-20240312104811340

image-20240312104630735

image-20240312104640135

2.map

image-20240312104900071

image-20240312104914607

3.unordered_set

image-20240312105227341

image-20240312105237556

image-20240312105258366

image-20240312105314253

4.unordered_map

image-20240312105439175

image-20240312105450351

三.OOP VS GP

image-20240312114104629

因为在模板sort的内部,对迭代器进行了跳跃式操作,只有随机访问迭代器可以这么操作。

而list的迭代器是双向迭代器,只支持++和–。

image-20240312114345116

优点

image-20240312114418618

四.分配器

C++标准库在很多地方采用特殊对象处理内存的分配和规划,这样的对象称为分配器(allocator)。allocator表现出一种特殊内存模型,被当成一种用来把内存需求转换为内存低级调用的抽象层。

C++标准库中链表模板的完整参数定义是:

template<class T, class Alloc=allocator<T>> 
class list;

当省略最后的模板参数时,容器将采用标准中预定义的分配器std::allocator<T>。该分配器调用new/delete操作符申请和释放内存,足以满足大多数需求。而当需要定制容器中的内存操作时,可以按照标准中的分配器规范,将内存操作封装在一个新的分配器类模板中并传入容器,比如:

std::list<my_data, my_allocator<my_data>> custom_list;

标准库中可以接受分配器的容器模板包括:

  • 序列型容器vectordequelistforward_list
  • 集合容器setmultisetmapmultimap
  • 散列表容器unordered_setunordered_multisetunordered_mapunordered_multimap

具体来说:

  • 分配器是一个类(class Alloc),而不是函数或者模板

  • 分配器是和具体类型相关的,因为默认构造器std::allocator<T>随容器保存类型的不同而不同。

  • 可以想象,分配器的行为应该类似new/delete操作符,而不是malloc/free(前者需要知道空间所存放的类型而后者只关心空间的大小)。

  • 分配器内至少需要有两个成员函数分别响应容器的申请以及释放内存的请求。

  • 分配器的首要作用是申请与释放内存。通过两个成员函数allocatedeallocate来实现。无论其定义如何,两成员函数必须能够实现如下用法:

a.allocate(n); // 申请能保存n个value_type的数据, 如果申请空间失败,抛出异常std::bad_alloc
a.allocate(n, p);
a.deallocate(p, n); //要释放的空间指针p和空间大小n

image-20240312160725237

image-20240312161632458

五. 红黑树

image-20240320193335534

源码剖析

image-20240320201248287

image-20240320201255803

测试

image-20240320203128720

以红黑树为底层的容器

set、multiset

image-20240321185641182

源码剖析image-20240321190148264

image-20240321190949872

map、multimap

image-20240321192042240

源码剖析

image-20240321192202509

map独有的[]

image-20240321192821636

六. 哈希表

image-20240321194553220

空间足够的时候,假如有2的32次方元素变化,可以用4G的空间来存储。但是这是不可能的。

空间不够的时候,假如有2的32次方元素变化,因此需要取模将元素放入对应的格子中。但是会发生碰撞,因此用链表将碰撞的元素串起来。

如果某个格子串起来的元素的个数大于格子的总数,说明格子空间不够了,因此重新划分格子数目。

image-20240321194958687

源码剖析

image-20240321200157330

测试

image-20240321212229821

image-20240321212240369

对数值来说,传进去的数值就是编号。

image-20240321212618478

对字符来说,选一种方法能尽可能打乱。

image-20240321212945491

unordered容器

image-20240321214251699

七. 算法

image-20240322095645298

  • 算法的所有操作都依赖迭代器。

迭代器的分类

image-20240322100602851

随机访问迭代器:vector、array、deque

双向访问迭代器:list、set、multisest、map、multimap

单向访问迭代器:forward_list

验证容器的迭代器种类

image-20240322105912353

image-20240322105934212

image-20240322105947873

迭代器分类对算法的影响

image-20240322112212363

distance算法针对不同的迭代器使用不同的方法计算距离。

image-20240322112408725

image-20240322112444985

返回类型就是difference_type。

image-20240322112611746

image-20240322113134365

image-20240322114123090

image-20240322114214556

算法源代码剖析

1.accumulate

image-20240322133729972

image-20240322152422653

image-20240322152435592

2.for_each

image-20240322152617578

image-20240322153131374

3.replace,replace_if,replace_copy

image-20240322152854172

replace 函数用于将容器中的某个值替换为另一个值。

函数原型

template<class ForwardIt, class T, class U>  
void replace(ForwardIt first, ForwardIt last, const T& old_value, const U& new_value);
  • first, last:指定要搜索的容器范围。
  • old_value:要被替换的值。
  • new_value:替换后的新值。

示例

#include <iostream>  
#include <vector>  
#include <algorithm>  
  
int main() {  
    std::vector<int> vec = {1, 2, 3, 2, 4, 2, 5};  
    std::replace(vec.begin(), vec.end(), 2, 9);  
    for (int x : vec) {  
        std::cout << x << ' ';  
    }  
    // 输出:1 9 3 9 4 9 5  
    return 0;  
}

在这个例子中,我们将 vec 中所有的 2 替换为 9

replace_if 函数用于根据谓词(即一个返回布尔值的函数或函数对象)的条件来替换容器中的元素。

函数原型

template<class ForwardIt, class UnaryPredicate, class T>  
void replace_if(ForwardIt first, ForwardIt last, UnaryPredicate p, const T& new_value);
  • first, last:指定要搜索的容器范围。
  • p:一个一元谓词,用于测试每个元素是否应被替换。
  • new_value:替换后的新值。

示例

#include <iostream>  
#include <vector>  
#include <algorithm>  
  
bool is_even(int n) {  
    return n % 2 == 0;  
}  
  
int main() {  
    std::vector<int> vec = {1, 2, 3, 4, 5, 6};  
    std::replace_if(vec.begin(), vec.end(), is_even, 0);  
    for (int x : vec) {  
        std::cout << x << ' ';  
    }  
    // 输出:1 0 3 0 5 0  
    return 0;  
}

在这个例子中,我们使用 is_even 谓词来检查 vec 中的每个元素是否为偶数,并将所有偶数替换为 0

replace_copy 函数与 replace 类似,但它不会修改原始容器,而是将替换后的结果复制到另一个容器中。

函数原型

template<class InputIt, class OutputIt, class T, class U>  
OutputIt replace_copy(InputIt first, InputIt last, OutputIt d_first, const T& old_value, const U& new_value);
  • first, last:指定要搜索的输入容器范围。
  • d_first:指向输出容器的开始迭代器。
  • old_value:要被替换的值。
  • new_value:替换后的新值。

示例

#include <iostream>  
#include <vector>  
#include <algorithm>  
  
int main() {  
    std::vector<int> vec = {1, 2, 3, 2, 4, 2, 5};  
    std::vector<int> result;  
    result.resize(vec.size());  // 预先分配足够的空间  
    std::replace_copy(vec.begin(), vec.end(), result.begin(), 2, 9);  
    for (int x : result) {  
        std::cout << x << ' ';  
    }  
    // 输出:1 9 3 9 4 9 5  
    // 注意:原始 vec 容器没有被修改  
    return 0;  
}

在这个例子中,我们将 vec 中所有的元素复制到result容器中,如果遇到值为2的元素,将其改为9复制到result中。

4.count,count_if

image-20240322154305877

count 函数用于统计容器中等于某个特定值的元素数量。

函数原型

template<class InputIt, class T>  
size_t count(InputIt first, InputIt last, const T& value);
  • first, last:指定要搜索的容器范围。
  • value:要搜索的值。

示例

#include <iostream>  
#include <vector>  
#include <algorithm>  
  
int main() {  
    std::vector<int> vec = {1, 2, 3, 2, 4, 2, 5};  
    size_t count_of_two = std::count(vec.begin(), vec.end(), 2);  
    std::cout << "Number of 2 in vec: " << count_of_two << std::endl;  
    // 输出:Number of 2 in vec: 3  
    return 0;  
}

count_if 函数用于统计容器中满足某个谓词(即一个返回布尔值的函数或函数对象)的元素数量。

函数原型

template<class InputIt, class UnaryPredicate>  
size_t count_if(InputIt first, InputIt last, UnaryPredicate p);
  • first, last:指定要搜索的容器范围。
  • p:一个一元谓词,用于测试每个元素是否应被计入统计。

示例

#include <iostream>  
#include <vector>  
#include <algorithm>  
  
bool is_even(int n) {  
    return n % 2 == 0;  
}  
  
int main() {  
    std::vector<int> vec = {1, 2, 3, 4, 5, 6};  
    size_t count_of_evens = std::count_if(vec.begin(), vec.end(), is_even);  
    std::cout << "Number of even numbers in vec: " << count_of_evens << std::endl;  
    // 输出:Number of even numbers in vec: 3  
    return 0;  
}

在这个例子中,我们定义了一个 is_even 谓词来检查一个整数是否为偶数,并使用 count_if 统计 vec 中偶数的数量,结果是 3

5.find,find_if

image-20240322154849145

6.sort

image-20240322154925491

image-20240325083954007

image-20240325084047277

image-20240325084359374

7.binary_search

image-20240325085245238

八. 仿函数(函数对象)

image-20240325090231071

image-20240325091559902

image-20240325091639926

STL仿函数编写条件

image-20240325091845074

unary_function:一个参数

binary_function:两个参数

九. 适配器

image-20240325100027715

image-20240325100901342

1.binder2nd

image-20240325100928123

注意区分小括号是直接调用函数还是创建函数对象。

2.not1

image-20240325132454414

3.bind

image-20240325132504909

image-20240325132519892

image-20240325140629007

image-20240325140705588

image-20240325140719303

7411967)]

八. 仿函数(函数对象)

[外链图片转存中…(img-FKy1J6zp-1711347411967)]

[外链图片转存中…(img-K8lnpskn-1711347411967)]

[外链图片转存中…(img-4fNkLvv0-1711347411967)]

STL仿函数编写条件

[外链图片转存中…(img-LQs5p3Y0-1711347411967)]

unary_function:一个参数

binary_function:两个参数

九. 适配器

[外链图片转存中…(img-ssSnydJm-1711347411968)]

[外链图片转存中…(img-Wan0qRXc-1711347411968)]

1.binder2nd

[外链图片转存中…(img-Zzkjq2ht-1711347411968)]

注意区分小括号是直接调用函数还是创建函数对象。

2.not1

[外链图片转存中…(img-eVhrrKOu-1711347411968)]

3.bind

[外链图片转存中…(img-XHB9sF1W-1711347411968)]

[外链图片转存中…(img-hDiOBKs8-1711347411968)]

[外链图片转存中…(img-8kZRm5Ds-1711347411968)]

[外链图片转存中…(img-PgOUiWZA-1711347411968)]

[外链图片转存中…(img-CM2boWpx-1711347411968)]

image-20240325140730809

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值