本 Wiki 开启了 HTTPS。但由于同 IP 的 Blog 也开启了 HTTPS,因此本站必须要支持 SNI 的浏览器才能浏览。为了兼容一部分浏览器,本站保留了 HTTP 作为兼容。如果您的浏览器支持 SNI,请尽量通过 HTTPS 访问本站,谢谢!
这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录前一修订版 | |||
cs:programming:cpp:boolan_cpp:stl_generic_1 [2024/01/14 13:46] – 移除 - 外部编辑 (未知日期) 127.0.0.1 | cs:programming:cpp:boolan_cpp:stl_generic_1 [2024/01/14 13:46] (当前版本) – ↷ 页面programming:cpp:boolan_cpp:stl_generic_1被移动至cs:programming:cpp:boolan_cpp:stl_generic_1 codinghare | ||
---|---|---|---|
行 1: | 行 1: | ||
+ | ======STL与泛型编程 第一周====== | ||
+ | 本页内容是作为 //Boolan// C++ 开发工程师培训系列的笔记。\\ | ||
+ | <wrap em> | ||
+ | ---- | ||
+ | ====标准模板库STL==== | ||
+ | |||
+ | C++ 标准库提供了强大的类库和相关的函数。在标准库中,C++ 标准模板库STL(// | ||
+ | |||
+ | ===STL的提供方式=== | ||
+ | |||
+ | STL通过 '' | ||
+ | * 新版本的 '' | ||
+ | * 旧版本的 '' | ||
+ | |||
+ | ===STL相关资源=== | ||
+ | |||
+ | 相关网站: | ||
+ | * [[http:// | ||
+ | * [[http:// | ||
+ | * [[http:// | ||
+ | 相关书籍: | ||
+ | * The C++ standard Library 2nd | ||
+ | * STL 源码剖析 | ||
+ | |||
+ | ====STL体系简介==== | ||
+ | |||
+ | STL 主要分为六的大部分: | ||
+ | * 容器(// | ||
+ | * 分配器(// | ||
+ | * 算法(// | ||
+ | * 迭代器(// | ||
+ | * 适配器(// | ||
+ | * 仿函数(// | ||
+ | \\ | ||
+ | 具体的关系图如下图: | ||
+ | \\ | ||
+ | \\ | ||
+ | \\ | ||
+ | < | ||
+ | <img src="/ | ||
+ | </ | ||
+ | </ | ||
+ | \\ | ||
+ | |||
+ | ===相关代码示例=== | ||
+ | |||
+ | 侯老师在课程中给出了一个典型的例子: | ||
+ | <code cpp linenums: | ||
+ | int ia[6] = { 27, 210, 12,47, 109, 83 }; | ||
+ | vector< | ||
+ | cout << count_if(vi.begin(), | ||
+ | return 0; | ||
+ | </ | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | |||
+ | ===容器的范围与遍历=== | ||
+ | |||
+ | 绝大多数情况下,容器的范围都可以通过两个迭代器来决定:'' | ||
+ | $$[Begin, end)$$ | ||
+ | \\ | ||
+ | 通过上述的特点,我们可以使用迭代器对容器进行遍历: | ||
+ | <code cpp linenums: | ||
+ | Container< | ||
+ | for (; iter != c.end(); ++iter) | ||
+ | .... | ||
+ | </ | ||
+ | 而在 C++11 中,我们可以通过 '' | ||
+ | <code cpp linenums: | ||
+ | for (auto iter = c.begin(); iter != c.end(); ++iter) | ||
+ | .... | ||
+ | </ | ||
+ | |||
+ | ====容器相关简介==== | ||
+ | |||
+ | STL 提供了一系列的容器来作为内存使用的方案。容器按在内存中的存储结构分为三种:**顺序容器**(// | ||
+ | \\ | ||
+ | \\ | ||
+ | 常见的顺序容器如下: | ||
+ | * STL //Array//, 特点是定长 | ||
+ | * // | ||
+ | * // | ||
+ | * // | ||
+ | * // | ||
+ | \\ | ||
+ | 关联容器的一个特点就是查找特别快,因为其通过关键字查找。常见的关联容器如下: | ||
+ | * //Set// / // | ||
+ | * //Map// / // | ||
+ | \\ | ||
+ | 无序容器和关联容器的一个不同在于无需容器使用哈希表实现。 | ||
+ | \\ | ||
+ | \\ | ||
+ | 以上内容均会在稍后详细讨论。 | ||
+ | |||
+ | ===容器的性能=== | ||
+ | |||
+ | STL 中的各种容器是基于各种不同的基础数据结构来实现的;因此对于特定的需求选择合适的容器会有效的提高程序运行的效率。容器的运行效率并不是独立存在的,有时候必须要搭配恰当的算法才能达到更高效的水准。因此,我们有必要做一个粗略的效率测试来评估容器的性能。而对于这些数据结构的测试,我们一般考虑其读写效率:也就是**存储**和**查找**。 | ||
+ | |||
+ | \\ | ||
+ | \\ | ||
+ | 我们首先来看看容器的读写性能总览: | ||
+ | \\ | ||
+ | \\ | ||
+ | < | ||
+ | <img src="/ | ||
+ | </ | ||
+ | </ | ||
+ | \\ | ||
+ | \\ | ||
+ | \\ | ||
+ | 值得说明的是,对于大部分容器,STL 都提供了泛型算法;但与此同时,该容器类很可能定义了自己的、用于实现与泛型算法相同功能的成员函数。**在两者都有的情况下,优先使用容器的成员函数**。 | ||
+ | |||
+ | ====顺序容器==== | ||
+ | |||
+ | 接下来我们对每个容器简单介绍一下其原理,以及测试的小细节。// | ||
+ | |||
+ | ===Array=== | ||
+ | |||
+ | 标准 //Array// 是 C++11 的新内容。相比起内置的数组来说,标准库 //Array// 功能更强大,可以以对象为单位存储数据。标准库 //Array// 和传统数组的特性完全相同:**定长**,长度一旦确定后就不可更改。因此在内存里可以表现为如下结构: | ||
+ | \\ | ||
+ | \\ | ||
+ | < | ||
+ | <img src="/ | ||
+ | </ | ||
+ | </ | ||
+ | \\ | ||
+ | 标准库 //Array// 的定义,及相关的算法使用如下: | ||
+ | <code cpp linenums: | ||
+ | array< | ||
+ | a.size(); // return the number of element that hold by container | ||
+ | a.front(); // return the first element in container | ||
+ | a.back(); // return the last element in container | ||
+ | a.data(); // return the starting address of the container | ||
+ | </ | ||
+ | |||
+ | ===Vector=== | ||
+ | |||
+ | 相比 //Array//, //Vector// 是一种可变长的容器。// | ||
+ | \\ | ||
+ | \\ | ||
+ | < | ||
+ | <img src="/ | ||
+ | </ | ||
+ | </ | ||
+ | \\ | ||
+ | \\ | ||
+ | 标准库 //Vector// 的定义,及相关的算法使用如下: | ||
+ | <code cpp linenums: | ||
+ | vector< | ||
+ | /* Accessors */ | ||
+ | vi.size(); // return the number of element that hold by container | ||
+ | vi.capacity(); | ||
+ | vi.front(); // return the first element in container | ||
+ | vi.back(); // return the last element in container | ||
+ | vi.data(); // return the starting address of the container | ||
+ | /* Modifiers */ | ||
+ | v.push_back(value); | ||
+ | </ | ||
+ | \\ | ||
+ | 值得注意的是,// | ||
+ | |||
+ | ===List=== | ||
+ | |||
+ | //List// 的是通过双向链表数据结构来实现的容器,在内存中的结构可以表现为: | ||
+ | \\ | ||
+ | \\ | ||
+ | < | ||
+ | <img src="/ | ||
+ | </ | ||
+ | </ | ||
+ | \\ | ||
+ | \\ | ||
+ | 标准库 //List// 的定义,及相关的算法使用如下: | ||
+ | <code cpp linenums: | ||
+ | list< | ||
+ | /* Accessors */ | ||
+ | li.size(); // return the number of element that hold by container | ||
+ | li.max_size(); | ||
+ | li.front(); // return the first element in container | ||
+ | li.back(); // return the last element in container | ||
+ | /* Modifiers */ | ||
+ | li.push_front(value); | ||
+ | l.push_back(value); | ||
+ | li.sort(); //List has its own sort function with O(n log n) complexity | ||
+ | </ | ||
+ | \\ | ||
+ | 因为是链表,所以对 //List// 的删除添加操作的速度是非常快的;而因为是双向链表,所以我们可以在头部和尾部添加元素。同是我们也知道,链表是变长的,其内存增长的策略是按元素单位增长。相比起 //Vector// 来说,// | ||
+ | |||
+ | ===Forward_list=== | ||
+ | |||
+ | // | ||
+ | \\ | ||
+ | \\ | ||
+ | < | ||
+ | <img src="/ | ||
+ | </ | ||
+ | </ | ||
+ | \\ | ||
+ | \\ | ||
+ | 标准库 // | ||
+ | <code cpp linenums: | ||
+ | forward_list< | ||
+ | /* Accessors */ | ||
+ | fls.max_size(); | ||
+ | fls.front(); | ||
+ | /* Modifiers */ | ||
+ | fls.push_front(value); | ||
+ | </ | ||
+ | 因为 // | ||
+ | * 没有 '' | ||
+ | * 不能从尾部添加元素(即使用 '' | ||
+ | // | ||
+ | |||
+ | ===Deque=== | ||
+ | |||
+ | // | ||
+ | \\ | ||
+ | \\ | ||
+ | < | ||
+ | <img src="/ | ||
+ | </ | ||
+ | </ | ||
+ | \\ | ||
+ | \\ | ||
+ | 标准库 //Deque// 的定义,及相关的算法使用如下: | ||
+ | <code cpp linenums: | ||
+ | Deque< | ||
+ | /* Accessors */ | ||
+ | ds.size(); | ||
+ | ds.max_size(); | ||
+ | ds.front(); | ||
+ | ds.back(); | ||
+ | /* Modifiers */ | ||
+ | ds.push_front(value); | ||
+ | ds.push_back(value); | ||
+ | </ | ||
+ | \\ | ||
+ | 从前面的图我们能发现,// | ||
+ | |||
+ | ==Queue 和 Stack== | ||
+ | |||
+ | //Queue// 和 //Stack// 作为两种独特的数据结构,在 STL 中都是基于 //Deque// 来实现的。由于这两种容器都没有单独定义自身的实现,因此我们又把这两种容器称为**容器适配器**(// | ||
+ | \\ | ||
+ | \\ | ||
+ | 标准库 //Stack// 的定义,及相关的算法使用如下: | ||
+ | <code cpp linenums: | ||
+ | stack< | ||
+ | /* Accessors */ | ||
+ | ss.top(); // Return the top element. | ||
+ | ss.size(); // Return current number of elements. | ||
+ | /* Modifiers */ | ||
+ | ss.push(value); | ||
+ | ss.pop(); // Pop value from top. | ||
+ | </ | ||
+ | \\ | ||
+ | 标准库 //Queue// 的定义,及相关的算法使用如下: | ||
+ | <code cpp linenums: | ||
+ | queue< | ||
+ | /* Accessors */ | ||
+ | qs.size(); | ||
+ | qs.front(); | ||
+ | qs.back(); | ||
+ | /* Modifiers */ | ||
+ | qs.push(value); | ||
+ | qs.pop(); // Remove value from front | ||
+ | </ | ||
+ | \\ | ||
+ | 需要注意的是:**我们不能使用迭代器对这两种容器进行操作**。如果提供了迭代器,那么就意味着我们可以使用迭代器对上述容器适配器任意位置的元素进行添加和删除;这样一来就破坏了这两种数据结构独有的特性(想想一下釜底抽薪和强行加塞)。 | ||
+ | |||
+ | ====关联容器==== | ||
+ | |||
+ | STL 中关联容器是使用红黑树实现的。因为其并不是顺序结构,因此我们不能用 '' | ||
+ | \\ | ||
+ | \\ | ||
+ | 关联容器主要分为两个版本:// | ||
+ | \\ | ||
+ | \\ | ||
+ | 关联容器和顺序容器的本质差别在于:关联容器通过键(// | ||
+ | |||
+ | ===Set / Multiset=== | ||
+ | |||
+ | 在 // | ||
+ | \\ | ||
+ | \\ | ||
+ | < | ||
+ | <img src="/ | ||
+ | </ | ||
+ | </ | ||
+ | \\ | ||
+ | \\ | ||
+ | 标准库 //Set// / // | ||
+ | <code cpp linenums: | ||
+ | set< | ||
+ | /* Accessors */ | ||
+ | si.find(key); | ||
+ | si.lower_bound(key); | ||
+ | si.upper_bound(key); | ||
+ | si.count(key); | ||
+ | s.size(); // Return current number of elements | ||
+ | /* Modifiers */ | ||
+ | s.insert(key); | ||
+ | </ | ||
+ | //Set// 的删除操作主要依赖于泛型算法中的 '' | ||
+ | |||
+ | ===Map / Multimap=== | ||
+ | |||
+ | //Map// 可以想象成是广义的 // | ||
+ | \\ | ||
+ | \\ | ||
+ | < | ||
+ | <img src="/ | ||
+ | </ | ||
+ | </ | ||
+ | \\ | ||
+ | \\ | ||
+ | 标准库 Map/ Multimap 的定义,及相关的算法使用如下: | ||
+ | <code cpp linenums: | ||
+ | map<int, string> mis; // a map with integer key and string value element | ||
+ | /* Accessors */ | ||
+ | mis.find(key); | ||
+ | mis.lower_bound(key); | ||
+ | mis.upper_bound(key); | ||
+ | mis.size(); | ||
+ | /* Modifiers */ | ||
+ | mis[key] = value; // Store value under key in map | ||
+ | mis.insert(pair); | ||
+ | </ | ||
+ | ===无序容器与哈希表=== | ||
+ | |||
+ | **无序容器**(// | ||
+ | |||
+ | ==哈希表的应用== | ||
+ | |||
+ | 要理解无序容器的一些特性,我们需要了解一下哈希表的结构。 | ||
+ | \\ | ||
+ | \\ | ||
+ | 哈希表在内存中的结构大致如下图: | ||
+ | \\ | ||
+ | \\ | ||
+ | < | ||
+ | <img src="/ | ||
+ | </ | ||
+ | </ | ||
+ | \\ | ||
+ | \\ | ||
+ | 按照上图,具体的来说,无序容器在存储上组织为一组**桶**(// | ||
+ | \\ | ||
+ | \\ | ||
+ | 值得注意的是,哈希表中,桶和元素的关系是有一定联系的。试想如果一个桶包含的元素太多,势必影响查找的效率。因此,对于桶的管理,哈希表的策略是:**如果元素的数量超过桶的数量,那么桶数量又会重新扩充到大约两倍元素的数量**。 | ||
+ | |||
+ | ==无序容器的应用== | ||
+ | |||
+ | 无序容器提供了一组如下管理桶的函数: | ||
+ | <code cpp linenums: | ||
+ | /* Accessors */ | ||
+ | c.bucket_count(); | ||
+ | c.max_bucket_count(); | ||
+ | c.bucket_size(n); | ||
+ | c.bucket(key); | ||
+ | /* Hash related */ | ||
+ | c.load_factor(); | ||
+ | c.max_load_factor(); | ||
+ | c.rehash(n); | ||
+ | c.reserve(n); | ||
+ | </ | ||