认识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对应的链表长度过长而导致元素搜索时间较长
Array:50万个元素,随机填充容器47ms, 0-32767查找20000,排序和搜索187ms(二分查找前需排序)
vector:push_back100万个元素。每次push_back申请的空间以两倍增长,会申请预留空间。size是大小,capacity是申请的内存空间大小;是另外找一个两倍大的空间。可能出现申请内存空间不足,需要try,catch抓取异常。 随机数据填充容器:3063ms;直接搜索,次序查找:0ms(搜索时间看运气,运气很好);排序后二分查找:2765ms。
list:push_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是一个命名空间