C++候捷stl-视频笔记1

认识headers、版本、重要资源

STL的核心思想是泛型编程

新式头文件内的组件封装在命名空间std中:
using namespace std;
using std::cout;或std::vector vec;
旧式头文件内的组件不封装在命名空间std中

注:不建直接使用using namespace xxx,如果使用的头文件有被外部文件引用,有命名冲突的风险。建议打开具体的使用对象,如using std::cout;,或者使用时打开std::string a;
http://www.cplusplus.com/
https://en.cppreference.com/w/
http://gcc.gnu.org/

STL体系结构基础介绍

STL六大部件::
容器:放数据的模版类,帮我们解决掉内存的问题
分配器:是来支持容器将数据放到内存里
算法:是一个个函数来处理存放在容器里的数据,一种模版函数(模版编程,与oop不同,操作数据的函数与数据不在一起)
迭代器:一套访问容器的接口,类似泛化的指针
仿函数:作用类似函数,例如相加相减等等
适配器:有三种,分别将容器,迭代器,仿函数来进行一个转换
在这里插入图片描述
在这里插入图片描述
allocator 是分配器(一般忽略),vector 是容器,count_if 是算法
vi.begin() 是迭代器
not1,bind2nd是适配器
less 是仿函数

判断条件 predicate 为:not1(bind2nd(less(), 40)) —— 表示 >= 40 数为真(小于40的否定)

容器为前闭后开区间
在这里插入图片描述

容器之分类与各种测试

序列式容器:按照元素的存储顺序依次排列
Array:容量大小固定
Vector:单端进出,容量大小可动态扩展
Deque:双端可进可出
List:双向循环链表,不连续
Forward-List:内存占用少于双向链表
关联式容器:包括键和值,适合快速查找
集合(Set/Multiset):由红黑树(高度平衡二叉树)实现,key就是value,value就是key。mult元素可重复
映射表(Map/Multimap):由红黑树(高度平衡二叉树)实现。mult键值可重复
不定序容器:底层使用哈希表实现。
无序集合(Unordered Set/Multiset):由哈希表实现
无序映射表(Unordered Map/Multimap):由哈希表实现
哈希表包括若干个bucket,每个bucket对应一个链表。哈希冲突时,应避免每个bucket对应的链表长度过长而导致元素搜索时间较长
在这里插入图片描述
Array50万个元素,随机填充容器47ms, 0-32767查找20000,排序和搜索187ms(二分查找前需排序)
vector:push_back100万个元素。每次push_back申请的空间以两倍增长,会申请预留空间。size是大小,capacity是申请的内存空间大小;是另外找一个两倍大的空间。可能出现申请内存空间不足,需要try,catch抓取异常。 随机数据填充容器:3063ms;直接搜索,次序查找:0ms(搜索时间看运气,运气很好);排序后二分查找:2765ms。
listpush_back100万个元素。随机数据填充容器:3265ms;直接搜索:16ms;排序:2312ms。 c.sort(); 是容器自带的排序函数,如果容器自带肯定是要比全局的排序函数好的
forward_list:forward_list 只有 c.push_front(); 且没有 forward_list.back() forward_list.size()。100万个元素。随机数据填充容器:3204ms;直接搜索:15ms;排序:2656ms
deque:类似vector,两边都能扩充,实际上是分段连续的。其是通过 map(是一个vector,但在扩充时会 copy 到中间)里的指针指向各个 buffer,buffer 里再存数据,每个 buffer 的大小一致,每次扩充都是扩充一个指针指向一个新的 buffer。随机数据填充容器:2704ms;直接搜索:15ms;排序:3110ms
(关联性容器查找都非常快)
栈stack和队列queue底层依赖于deque容器,由于其没有自己的数据结构,因此不把stack和queue作为容器,而作为容器适配器;
在这里插入图片描述
在这里插入图片描述
multiset:有自己提供用于查找的find()函数时,效率更高。insert自动排序,底层结构是红黑树。随机数据填充容器:6609ms(其在填充的时候就进行排序了);直接搜索 ::find():203ms;c.find():0ms
multimap:multimap.inset(pair<int,string>),要自己组合pair<int,string>元素放进去。随机数据填充容器:4812ms(其在填充的时候就进行排序了);c.find():0m
unordered_multiset:bucker_count()篮子数量,篮子一定比元素多,每个篮子的链表不能太长(经验法则:当元素个数大于等于bucket个数时,bucket个数会扩充至约原先的2倍。)。bucket_size()可以看篮子里有多少数量。随机数据填充容器:4406ms;直接搜索 ::find():109ms;c.find():0ms;前二十个 bucket 中只有一个有24个元素
unordered_multimap与unordered_multiset类似
set:key就是value,value就是key,插入重复的值,不会增长元素
map:[]会自动合成Pair格式

分配器之测试

容器需要分配器allocator作为支撑;每个容器类都有默认的容器分配器allocator(模板参数使用默认值)。

template<typename _Tp, typename _Alloc = std::allocator<_Tp>>	// 使用默认参数
class vector : protected _Vector_base<_Tp, _Alloc>
    
template<typename _Tp, typename _Alloc = std::allocator<_Tp>>	// 使用默认参数
class list : protected _List_base<_Tp, _Alloc>
    
template<typename _Tp, typename _Alloc = std::allocator<_Tp>>	// 使用默认参数
class deque : protected _Deque_base<_Tp, _Alloc>
    
template<typename _Key, typename _Compare = std::less<_Key>,
			typename _Alloc = std::allocator<_Key>>
class set ...
    
template<typename _Key, typename _Tp, typename _Compare = std::less<_Key>,
			typename _Alloc = std::allocator<std::pair<_Key, _Tp>>
class map ...
    
template<class _Value, class _Hash = hash<_Value>, 
			class _Pred = std::equal_to<_Value>, class _Alloc = std::allocator<_Value>>
class unordered_set ...
    
template<class _Key, class _Tp, class _Hash = hash<_Key>, 
			class _Pred = std::equal_to<_Key>,
			class _Alloc = std::allocator<std::pair<_Key, _Tp>>
class unordered_map ...

在这里插入图片描述
不建议手动使用分配器直接管理内存 allocate()和deallocate()函数释放内存时需释放指针和对应大小的内存。

面向对象编程(OOP) vs. 泛型编程(GP)

OOP=数据+操作,OOP是想要把data和method关联在一起打包

// list容器的迭代器不支持随机访问
template <class T, class Alloc = alloc>
class list {
    ...
    void sort();	// list容器类的成员函数sort()
};

标准库中很少有继承关系,因此虚函数很少,关系比较简单

GP是将data和methods分开来
容器类

// vector容器的迭代器支持随机访问
template <class T, class Alloc = alloc>
class vector {
    ...
};

// deque容器的迭代器支持随机访问
template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
    ...
};

Alogorithm是全局函数

/* STL标准库提供的全局::sort() */
// ::sort(c.begin(), c.end())
template <class RandomAccessIterator>	// 随机访问迭代器
inline void sort(RandomAccessIterator first, RandomAccessIterator last) {
	...
}

template <class RandomAccessIterator, class Compare> // 比较规则
inline void sort(RandomAccessIterator first, RandomAccessIterator last, Compare comp) {
	...
}

Containers和Alogorithm可以各自闭门造车,然后以iterator联通既可
Alogorithm通过Iterator确定操作范围,并通过Iterators取用Continer元素;

/* max函数的重载 */
template <class T>
inline const T& max(const T& a, const T& b) {
    return a < b ? b : a;
}

// Compare 自定义的比较规则
template <class T, class Compare>
inline const T& max(const T& a, const T& b, Compare comp) {
    return comp(a, b) ? b : a;
}

在标准库中操作符重载变得至关重要

为什么list不能使用::sort()排序?
List的迭代器不能跳来跳去,不能随机访问,只能一个一个往前跳,不能直接+5,-5;标准库sort()所用到的随机访问迭代器,而list迭代器不足以达到要求;
在这里插入图片描述

所有Algorithms,其内最终设计元素本身的操作,无非就是比大小

技术基础:操作符重载and模板(泛化,全特化,偏特化)

操作符重载示例
在这里插入图片描述
类模版
调用者在使用时再指定泛型的具体类型。

template<typename T>	// 类模板
class complex{
private:
    T re, im;	// 模板
    friend complex& __doap1 (complex *ths, const complex& r); 	// 友元函数
public:
    complex(T r = 0, T i = 0) : re(r), im(i){}	// 默认参数、初始化列表
    complex& operate+=(const complex&);	// 重载运算符
    T real() const { return re; }		// 成员函数-常函数
    T imag() const { return im; }
};

类模板使用时,需显式指定具体的泛型类型。编译器不会对类模板进行类型推导

{
    complex<double> c1(1.1, 2.2);
    complex<int> c2(3, 4);
}

函数模版
class可替换为typename。
函数模板使用时,不必显式指定具体的泛型类型。编译器会对函数模板进行实参推导/类型推导(argument deduction)。类模板使用时,需显式指定具体的泛型类型。

// 函数模板
template<class T>
inline const T& min(const T& a, const T& b){
    return b < a ? b : a;
}
class stone{
public:
    stone(int w, int h, int we) : _w(w), _h(h), _weight(we){}
    bool operator< (const stone& rhs){
        return this->_weight < rhs._weight;
    }
private:
    int _w, _h, _weight;
}
int main(){
    stone r1(2, 3), r2(3, 3), r3;
    r3 = min(r1, r2);	
}

成员模版
类模板中的成员(函数/属性)也为模板
STL中的拷贝构造函数和有参构造函数常设计为成员模板
成员模板的泛型类型是类模板的泛型类型的子类。成员模板的泛型类型U1/U2,继承自类模板的泛型类型T1/T2。反之不成立。

class Base1 {};
class Derived1 : public Base1 {};
class Base2 {};
class Derived2 : public Base2 {};

pair</* U1 */Devired1, /* U2 */Devired2> p;
pair<Base1, Base2> p2(p);
/* 等价写法 */
// 使用成员模板创建匿名对象,用于类的拷贝构造函数
pair</*T1*/Base1, /*T2*/Base2> p2(pair</*U1*/Devired1, /*U2*/Devired2>());

如智能指针子类shared_ptr有参构造函数的参数(被包装的指针),是智能指针父类__shared_ptr有参函数构造函数的参数(被包装的指针)的子类。

template<typename _Tp>
class shared_ptr : public __shared_ptr<_Tp>
{
    ...
    /* 成员模板 */
    template<typename _Tp1>
    // 构造函数
    // 子类构造函数shared_ptr的参数__p,对父类构造函数__shared_ptr<_Tp>赋值
    explicit shared_ptr(/*Devired*/_Tp1* __p) : __shared_ptr</*Base*/_Tp>(__p) {}
    ...
};

/* 指针类的多态 */
Base1 *ptr = new Devired1;	// 向上转型up-cast
/* 智能指针类的多态 */
// 使用子类对象
shared_ptr<Base1*> sptr(new Devired1);	// 模拟向上转型up-cast

特化 specialization
泛化(generalization):使用时再指定模板参数的具体类型。
特化(specialization):将模板泛化的部分进行局部的特征化。显式指定部分或全部模板参数的具体类型
在这里插入图片描述
在这里插入图片描述
特化的优先级高于泛化。当存在合适的特化版本时,编译器优先调用相应的特化版本。
全特化的所有模板参数均被指定,故模板参数列表为空,即template <>。

全特化(full specialization):显式指定全部模板参数的具体类型。
偏特化/局部特化(partial specialization):指定部分模板参数的具体类型。

偏特化示例:两个模板参数,锁定其中一个
在这里插入图片描述
锁定范围的偏特化,如T是一个指针在这里插入图片描述

分配器

所有分配动作最终都会到malloc()函数
在这里插入图片描述
内存分配,蓝色是块是实际我们需要malloc给我们分配的空间,灰色的块是debug mode需要添加的内存空间,上下两个红色的块是cookie,它记录分配内存的大小(《深度探索c++对象模型》书上称为记录元素的大小,这个指的是delete时,该delete多大的内存空间,即delete掉的数组维度大小),绿色的部分指的是为了内存大小的对齐
vc6默认分配器allocators
在这里插入图片描述
VC6+的allocator只是以::operator new和::operator delete完成allocate()和deallocate(),没有任何特殊的设计
BC5编译器对于allocator分配内存和回收内存用的还是operator new和operator delete,底部还是用c语言的malloc和free完成,和VC6的实现一样

容器分配大量小的元素时,由于元素内存分配都用malloc,元素内存都很小,但额外开销很大。通常容器的元素都是较小的,因此额外开销比例较大,是不好接受的。

都是对malloc和free的各种包装,比起直接调用反而变慢了
在这里插入图片描述
GNU C++(G++) 2.9版本所附的标准库,allocator分配内存和回收内存用的还是operator new和operator delete,底部还是用c语言的malloc和free实现。但是自己的容器不用这个allcators,右下角说明这个分配器并没有被SGI STL header引入,即没有被使用
在这里插入图片描述
在这里插入图片描述
GC使用的是alloc分配器,核心诉求是尽量减少malloc的次数
共有16条链表,每一条链表负责特定大小的区块,编号为#0的链表负责大小为8B的内存块,编号为#1的链表负责大小为8 x 2 = 16B的内存块,编号为#3的链表负责大小为8 x 3 = 24B的内存块,…,以此类推,每次增加8B。每一条链表都是一次用malloc分配的一大块内存(带cookie),然后切分成小的标准块(不带cookie),当容器需要内存的时候,alloc就会按照需要的大小(变成8B的倍数)在链表中查找进行分配,这样实现在分配小块的时候就不需要存储cookie的开销了

G++4.9又不再使用alloc分配器,而是继续调用operator new 和operator delete
在这里插入图片描述
G++4.9的__pool_alloc就是G++2.9版本的alloc分配器
用例

vector<string, __gnu_cxx::__pool_alloc<string>> vec;// __gnu_cxx是一个命名空间

容器之间的实现关系与分类

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值