C++:STL简单总结

STL的本质

通过前面的学习以及使用,我们对STL已经有了一定的认识。通俗说:STL是Standard Template Library(标准模板库),是高效的C++程序库,其采用泛型编程思想对常见数据结构(顺序表,链表,栈和队列,堆,二叉树,哈希)和算法(查找、排序、集合、数值运算…)等进行封装,里面处处体现着泛型编程程序设计思想以及设计模式,已被集成到C++标准程序库中。具体说:STL中包含了容器、适配器、算法、迭代器、仿函数以及空间配置器。 STL设计理念:追求代码高复用性以及运行速度的高效率,在实现时使用了许多技术,因此熟悉STL不仅对我们正常使用有很大帮助,而且对自己的知识也有一定的提高。

STL的六大组件

容器

容器,置物之所也。STL中的容器,可以划分为两大类:序列式容器和关联式容器。

在这里插入图片描述

算法

算法:问题的求解步骤,以有限的步骤,解决数学或逻辑中的问题。 STL中的算法主要分为两大类:与数据结构相关算法(容器中的成员函数)和通用算法(与数据结构不相干)。STL中通用算法总共有70多个,主要包含: 排序,查找,排列组合,数据移动,拷贝,删除,比较组合,运算等

C++标准函数库中,为我们提供了一个algorithm库,里面包含了十分多的常用算法,以下只列出了部分常用的算法:

算法名称算法功能
accumulate元素统计
binary_search二分查找
copy拷贝
copy_backward逆向拷贝
copy_n拷贝n个元素
count计数
count_if在特定条件下计数
equal判断两个区间相等与否
fill填充元素
fill_n填充元素n次
find循环查找
find_if循环查找符合特定条件元素
find_end查找某个子序列的最后一次出现点
find_first_of查找某个元素首次出现点
for_each对区间内的每隔一元素实行某种操作
is_heap判断某区间是否为一个heap
is_sorted判断某区间是否已排序
lexicographical_compare以字典顺序进行比较
max获取最大值
max_element最大值所在位置
merge合并两个序列
min获取最小值
min_element最小值所在位置
next_permutation获取下一个排列组合
pre_permutation获取前一个排列组合
partial_sort局部排序
partial_sum局部求和
partition分割
remove删除某类元素
remove_copy删除某类元素并将结果拷贝到另一个容器中
remove_if有条件的删除某类元素
replace替换某类元素
replace_if有条件的替换
reverse反转序列
sort排序(不稳定)
stable_partition分割并保持元素的相对次序
stable_sort分割并保持相等元素的相对位置(稳定排序算法)
unique取出重复性元素
make_heap创建堆
push_heap堆插入
pop_heap堆删除
sort_heap堆排序
迭代器
什么是迭代器

迭代器是一种设计模式,让用户通过特定的接口访问容器的数据,不需要了解容器内部的底层数据结构。 C++中迭代器本质:是一个指针,让该指针按照具体的结构去操作容器中的数据。

为什么需要迭代器

通过前面算法的学习了解到:STL中算法分为容器相关联与通用算法。所谓通用算法,即与具体的数据结构无关,比如:

template<class InputIterator, class T>
InputIterator find ( InputIterator first, InputIterator last, const T& value )
{
    for ( ;first!=last; first++){
        if ( *first==value )
            break;
	}
    return first;
}

find算法在查找时,与具体的数据结构无关,只要给出待查找数据集合的范围,find就可在该范围中查找,找到返回该元素在区间中的位置,否则返回end。

问题:对于vector、list、deque、map、unordered_set等容器,其底层数据结构均不相同,那find算法是怎么统一向后遍历呢?

vector的底层结构:

在这里插入图片描述

list的底层结构:

在这里插入图片描述

map的底层结构:

在这里插入图片描述

迭代器应该由谁负责提供

每个容器的底层结构都不同,为了降低算法使用容器时的复杂度,底层结构应该对于算法透明,迭代器就充当了算法与容器之间的转接层,因此:每个容器的迭代器应该由容器设计者负责提供,然后容器按照约定给出统一的接口即可。

比如:

// vector中:
typedef T* iterator; 
iterator begin();
iterator end(); 
find(v.beign(), v.end(), 5);

// list中
typedef list_iterator<T, T&, T*> iterator; 
iterator begin();
iterator end();
find(l.begin(), l.end(), 5);
迭代器实现原理

容器底层结构不同,导致其实现原理不同,容器迭代器的设计,必须结合具体容器的底层数据结构。 比如:

  1. vector

因为vector底层结构为一段连续空间,迭代器前后移动时比较容易实现,因此vector的迭代器实际是对原生态指针的封装,即:typedef T* iterator。

  1. list

list底层结构为带头结点的双向循环链表,迭代器在移动时,只能按照链表的结构前后依次移动,因此链表的迭代器需要对原生态的指针进行封装,因为当对迭代器++时,应该通过节点中的next指针域找到下一个节点。

如果迭代器不能直接使用原生态指针操作底层数据时,必须要对指针进行封装,在封装时需要提供以下方法:

  1. 迭代器能够像指针一样方式进行使用

重载pointer operator*() / reference operator->()

  1. 能够让迭代器移动

向后移动:self& operator++() / self operator++(int)
向前移动:self& operator--() / self operator--(int) (注意:有些容器不能向前移动,比如
forward_list)

  1. 支持比较-因为在遍历时需要知道是否移动到区间的末尾
bool operator!=(const self& it)const 
bool operator==(const self& it)const
代器与类的融合
  1. 定义迭代器类

  2. 在容器类中统一迭代器名字

// 比如list:
template <class T, class Alloc = alloc> 
class list
{
// ...
    typedef __list_iterator<T, T&, T*>  iterator;
// ...
};
  1. 在容器类中添加获取迭代器范围的接口:
template <class T, class Alloc = alloc>
class list
{
// ...
    iterator begin(){ return (link_type)((*node).next);}
    iterator end(){ return node;}
    // ...
};
反向迭代器

反向迭代器:正向迭代器的适配器,即正向迭代器++往end方向移动,–往begin方向移动,而反向迭代器++则往begin方向移动,–则向end方向移动。

适配器

适配器:又接着配接器,是一种设计模式,简单的说:需要的东西就在眼前,但是由于接口不对而无法使用,需要对其接口进行转化以方便使用。即:将一个类的接口转换成用户希望的另一个类的接口,使原本接口不兼容的类可以一起工作。

STL中适配器总共有三种类型:

  • 容器适配器-stack和queue

stack的特性是后进先出,queue的特性为先进先出,该种特性deque的接口完全满足,因此stack和 queue在底层将deque容器中的接口进行了封装。

template < class T, class Container = deque<T> >
class stack { ... };

template < class T, class Container = deque<T> >
class queue { ... };
  • 迭代器适配器-反向迭代器

反向迭代器++和–操作刚好和正向迭代器相反,因此:反向迭代器只需将正向迭代器进行重新封装即
可。

仿函数

仿函数:一种具有函数特征的对象,调用者可以像函数一样使用该对象 ,为了能够“行为类似函数”,该对象所在类必须自定义函数调用运算符operator(),重载该运算符后,就可在仿函数对象的后面加上一对小括号,以此调用仿函数所定义的operator()操作,就其行为而言,“仿函数”一次更切贴。

仿函数一般配合算法,作用就是:提高算法的灵活性。

#include <vector>
#include <algorithm>

class Mul2
{
public:
    void operator()(int& data)
    { data <<= 1;}
};

class Mod3
{ public:
    bool operator()(int data)
    { return 0 == data % 3;}
};

int main() {
	// 给容器中每个元素乘2
	vector<int> v{1,2,3,4,5,6,7,8,9,0}; 
	for_each(v.begin(), v.end(), Mul2()); 
	for (auto e : v)
        cout << e << " ";
    cout << endl;

	// 删除容器中3的倍数
	auto pos = remove_if(v.begin(), v.end(), Mod3()); 
	v.erase(pos, v.end());
	
	// 将容器中的元素打印出来
	// 注意:对于功能简单的操作,可以使用C++11提供的lambda表达式来代替
	// lambda表达式实现简单,其在底层与仿函数的原理相同,编译器会将lambda表达式转换为仿函数 
	for_each(v.begin(), v.end(), [](int data){cout << data << " "; });
	cout << endl;
	return 0;
}
空间配置器

STL总结之空间配置器

STL框架

在这里插入图片描述

知识点习题

  1. 以下关于STL的描述中,____是错的。

A. STL容器是线程不安全的
B. 当容量不够时,vector内部内存扩展方式是翻倍
C. std::sort是稳定排序
D. std::bitset不是一个STL容器
E. std::stack默认是用deque实现的
F. std::string中可以存储多个’\0’字符

正确答案

C

答案解析

A:“很多程序员希望STL实现是完全线程安全的“。所以不安全。
B:vector的存在可以使开发者不必关心内存的申请和释放。但是,vector的一个缺点就是它的内存分配是按照2的倍数分配内存的。
C:错误。要知道 std::sort 不是稳定的排序算法(封装了快速排序算法),它不保证“相等”元素的相对位置,使用 std::stable_sort 来保证这一点
D:STL的容器可以分为以下几个大类:
一:序列容器, 有vector, list, deque, string.
二 : 关联容器, 有set, multiset, map, mulmap, hash_set, hash_map, hash_multiset, hash_multimap
三: 其他的杂项: stack, queue, valarray, bitset
E:正确。堆栈是一个线性表,插入删除操作都在一端进行,deque是先进先出的,操作原理和stack是一样的

  1. STL中的一级容器有:

A. vector, deque, list, set, multiset, map, multimap.
B. 序列容器,关联容器,容器适配器
C. set, multiset, map, multimap.
D. vector, deque, list.

正确答案: D

答案解析:

STL中一级容器是指, 容器元素本身是基本类型, 非组合类型。
set, multiset中元素类型是pair<key_type, key_type>;
map, multimap中元素类型是pair<key_type, value_type>;

  • 2
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值