本 Wiki 开启了 HTTPS。但由于同 IP 的 Blog 也开启了 HTTPS,因此本站必须要支持 SNI 的浏览器才能浏览。为了兼容一部分浏览器,本站保留了 HTTP 作为兼容。如果您的浏览器支持 SNI,请尽量通过 HTTPS 访问本站,谢谢!
这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录前一修订版后一修订版 | 前一修订版 | ||
cs:programming:cpp:boolan_cpp:stl_generic_5 [2024/01/14 13:46] – 移除 - 外部编辑 (未知日期) 127.0.0.1 | cs:programming:cpp:boolan_cpp:stl_generic_5 [2024/01/14 13:47] (当前版本) – ↷ 链接因页面移动而自动修正 codinghare | ||
---|---|---|---|
行 1: | 行 1: | ||
+ | ======STL与泛型编程 第五周====== | ||
+ | 本页内容是作为 //Boolan// C++ 开发工程师培训系列的笔记。\\ | ||
+ | <wrap em> | ||
+ | ---- | ||
+ | |||
+ | ====万用哈希函数==== | ||
+ | |||
+ | 在第三周的内容里提到,哈希表需要通过哈希函数来为每一个元素生成一个 //Hash code// 来作为该元素在哈希表中的 // | ||
+ | \\ | ||
+ | \\ | ||
+ | 有一种思路是:因为对象都是由 | ||
+ | |||
+ | ===哈希函数的形式与使用=== | ||
+ | |||
+ | 在讨论哈希函数的实现之前,我们先来看一看哈希函数的调用。哈希函数分为三种类型: | ||
+ | * 非成员哈希函数 | ||
+ | * 成员哈希函数 | ||
+ | * 偏特化版本的哈希函数 | ||
+ | 非成员哈希函数的是通过函数来表现的: | ||
+ | <code cpp linenums: | ||
+ | sizt_t customer_hash_func(const Customer& | ||
+ | .... | ||
+ | } | ||
+ | </ | ||
+ | 而使用时候需要指定**元素类型**和**哈希函数类型**: | ||
+ | <code cpp linenums: | ||
+ | unordered_set< | ||
+ | </ | ||
+ | 如果是成员哈希函数,需要以运算符重载的形式实现函数功能: | ||
+ | <code cpp linenums: | ||
+ | std::size_t operator() (const Customer& | ||
+ | </ | ||
+ | 调用的时候在实例化类的时候指定该哈希参数即可: | ||
+ | <code cpp linenums: | ||
+ | unordered_set< | ||
+ | </ | ||
+ | 偏特化版本是成员函数的另外一种形式,是使用结构体实现的: | ||
+ | <code cpp linenums: | ||
+ | template < | ||
+ | class unordered_set; | ||
+ | </ | ||
+ | 使用的方法与类成员函数的使用方法一致,也是通过 '' | ||
+ | <code cpp linenums: | ||
+ | template<> | ||
+ | struct hash< | ||
+ | ... | ||
+ | sizt_t operator () (const MyString& | ||
+ | return hash< | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ===万用哈希函数的实现=== | ||
+ | |||
+ | 哈希函数需要针对元素的内容来进行 //Hash code// 的计算。因此,我们需要哈希函数有能力接收**不同类型和不同数量**的参数。// | ||
+ | \\ | ||
+ | \\ | ||
+ | 为了实现这个方法,我们需要按递归的原理确定几个 case: | ||
+ | * 基础情况:单个 //Seed// 与单个元素的基础部分。该情况应该直接使用指定的算法算出新的 // | ||
+ | * 多个参数情况:该情况需要先将参数列表进行分割: | ||
+ | * 如果未指定 //Seed// 的默认值,则需要在函数里设定 //Seed// 的默认值(// | ||
+ | * 如果指定了 //Seed// 的默认值,那么直接进行元素的分割操作(// | ||
+ | 根据以上的思路,我们不难得出相应的实现方法: | ||
+ | \\ | ||
+ | \\ | ||
+ | 基础形式(// | ||
+ | <code cpp linenums: | ||
+ | template< | ||
+ | inline voild hash_val(sizet& | ||
+ | hash_combine(seed, | ||
+ | </ | ||
+ | 多个参数形式,无 //Seed// 初始值(// | ||
+ | <code cpp linenums: | ||
+ | template< | ||
+ | inline size_t hash_val(const Types& | ||
+ | | ||
+ | | ||
+ | | ||
+ | </ | ||
+ | 多个参数形式,有 //Seed// 初始值(// | ||
+ | <code cpp linenums: | ||
+ | template< | ||
+ | inline size_t hash_val(size_t& | ||
+ | hash_combine(seed, | ||
+ | hash_val(seed, | ||
+ | } | ||
+ | </ | ||
+ | \\ | ||
+ | 基础的 '' | ||
+ | <code cpp linenums: | ||
+ | inline void hash_combine(size_t& | ||
+ | seed ^=std:: | ||
+ | } | ||
+ | </ | ||
+ | 值得注意的是,以上所有的函数在传递 //Seed// 的时候都是通过引用来传递的。// | ||
+ | \\ | ||
+ | \\ | ||
+ | //PS: '' | ||
+ | |||
+ | ====Tuple的用例==== | ||
+ | |||
+ | //Tuple// 是除了 //STL// 内容之外,标准库定义的一种数据结构。这种数据结构可以指定存放**任意个元素**,每个元素的**类型**也可以是**任意的**。// | ||
+ | <code cpp linenums: | ||
+ | // | ||
+ | tuple< | ||
+ | tuple< | ||
+ | auto t2 = make_tuple(41, | ||
+ | //access | ||
+ | get< | ||
+ | tuple_size< | ||
+ | tuple_element< | ||
+ | //other operations: | ||
+ | t1 < t2; t1 = t2; | ||
+ | cout << t1; | ||
+ | tie(i, f, s) = t2; // read elements from t2 and assign them to the parameters in tie() in order; type must match | ||
+ | </ | ||
+ | |||
+ | ===Tuple的实现细节=== | ||
+ | |||
+ | //Tuple// 是一个类,本身也是通过 C++ 中模板的无限参数的功能来实现的。// | ||
+ | \\ | ||
+ | \\ | ||
+ | < | ||
+ | <img src="/ | ||
+ | </ | ||
+ | </ | ||
+ | \\ | ||
+ | \\ | ||
+ | 从上图可以看出来,'' | ||
+ | \\ | ||
+ | \\ | ||
+ | 首先,'' | ||
+ | <code cpp linenums: | ||
+ | template<> | ||
+ | class tuple< | ||
+ | .... | ||
+ | } | ||
+ | </ | ||
+ | 在类中,每一个类都存储当前的头部元素: | ||
+ | <code cpp linenums: | ||
+ | Head m_head; | ||
+ | tpyename Head::type head() { return m_head; } // member function that return current head | ||
+ | </ | ||
+ | 同时,当前的类有一个指针,指向当前类中尾部的开头: | ||
+ | <code cpp linenums: | ||
+ | inherited& | ||
+ | </ | ||
+ | 看到这里你可能会问:'' | ||
+ | \\ | ||
+ | \\ | ||
+ | 实际上, '' | ||
+ | \\ | ||
+ | \\ | ||
+ | < | ||
+ | <img src="/ | ||
+ | </ | ||
+ | </ | ||
+ | \\ | ||
+ | |||
+ | ====Type Traits==== | ||
+ | |||
+ | **类型萃取机**(// | ||
+ | \\ | ||
+ | \\ | ||
+ | 在 //G 2.9// 版本中,// | ||
+ | * //Dummy member// | ||
+ | * // | ||
+ | * // | ||
+ | * // | ||
+ | * // | ||
+ | * is_POD_Type:类中是否有传统的 C 数据结构? | ||
+ | 我们知道面向对象设计中,如果类中含有指针,是一定要设计 //Big Three// 的。因此以上的内容对于算法判断类的具体类型,是非常重要的。 | ||
+ | \\ | ||
+ | \\ | ||
+ | 而在 C++11 中,类型萃取机的内容被进行了大量扩展;从此,对类的内容判断的手段也就更加多样化了。需要进一步了解C++ 11的 //Type Traits// 可以查阅网站:[[http:// | ||
+ | |||
+ | ===Type Traits的使用=== | ||
+ | |||
+ | | ||
+ | <code cpp linenums: | ||
+ | type_traits_output(const T& x) { | ||
+ | cout << is_void< | ||
+ | ..... | ||
+ | } | ||
+ | </ | ||
+ | C++ 11中的 //Type Traits// 可以对各种类型做出判断,详情可以见侯老师的几个例子,也可以查询上面的文档;本处就不再赘述。 | ||
+ | |||
+ | ===部分Type Traits的实现=== | ||
+ | |||
+ | //Type Traits// 实现的本质是模板对类型的操作。对于一些复杂的判断,比如对类、结构体、C中的结构(POD)或者是 // | ||
+ | \\ | ||
+ | \\ | ||
+ | 首先,无论是 '' | ||
+ | '' | ||
+ | * 泛化版本:接收一个参数,返回参数的同类型,也就是不对参数的类型做任何处理 | ||
+ | * 特化版本:接收一个带 '' | ||
+ | 经过以上的处理,所有带有额外关键字的类型,都会被转化为其对应的普通类型。 | ||
+ | \\ | ||
+ | \\ | ||
+ | 接下来,得到了存粹的类型以后,这两个 //Type Traits// 会通过自身的一个 '' | ||
+ | |||
+ | ====Cout==== | ||
+ | |||
+ | //Cout// 是一个类对象。它属于 '' | ||
+ | \\ | ||
+ | \\ | ||
+ | //Cout// 所属的类中定义了绝大部分对基本类型的输出。但如果要使用 //Cout// 对复杂的类型进行输出的时候,需要在该类型所属类中进行相应的运算符重载。标准库中的重载接收两个参数: | ||
+ | * 第一参数:运算符左边的类型,比如 //Cout// 对应的就是 '' | ||
+ | * 第二参数:运算符右边的类型,比如 //String// 对应的就是 '' | ||
+ | 表现为代码如下: | ||
+ | <code cpp linenums: | ||
+ | operator<< | ||
+ | </ | ||
+ | 该形式还可以应用到对其他类型的重载,比如智能指针,线程,正则表达式等等。 | ||
+ | |||
+ | ====Move拷贝构造==== | ||
+ | |||
+ | '' | ||
+ | * 经过 '' | ||
+ | * '' | ||
+ | |||
+ | ===Move拷贝构造对容器的影响=== | ||
+ | |||
+ | 如果元素是支持 '' | ||
+ | \\ | ||
+ | \\ | ||
+ | 除此之外,容器自身的结构也对拷贝构造有一些影响。某些容器需要在空间不足的情况下扩容,从而进行大量的拷贝构造(比如 // | ||
+ | \\ | ||
+ | \\ | ||
+ | \\ | ||