C++STL学习第四讲(讲解STL周边内容)

C++STL学习第四讲(讲解STL周边内容)

1. 一个万用的 Hash Function(hash_val)

1. 大致思路
  • 将 object 分解为基本单元(数值和字符串),使用已有的基本单元的 Hash Function 构建 object 的 Hash Function;
2. Hash Function 实现形式
  • ① function object;

  • ② 一般 function:

    • 使用时,模板参数需要传入 function pointer(图中的 function pointer 没有名称);
    • 构造参数需要传入 function pointer 指定的 function 名称;
      在这里插入图片描述
  • ③ 在 std namespace 下特化 struct hash:

    • unordered_set、unordered_map 等以 hashtable 为底层实现的 container,第二参数(hash function)的缺省值是 hash;
      在这里插入图片描述

    • 特化 struct hash 示例:

      • namespace std	// 在namespace std下
        {
            // 模仿已有的hash样式
            template<>
            struct hash<object>
            {
                size_t
                operator()(cosnt object&) const noexcept{
                    ...
                }
            };
        }
        
    • G4.9 所有的基本类型都有 hash 特化版本
      在这里插入图片描述

3. 用例介绍
  • 假设 Customer 类有三个 data:

    • string fname;
    • string lname;
    • long no;
  • 示例中使用 function object 实现 hash function;

  • 单纯地相加基本单元的 hash code:

    • 分别使用 std::hash 计算三个 data 的 hash code,然后相加作为 Customer 的 Hash Function,根据经验得到,这种做法容易产生碰撞,导致 hashtable 中的链表变长,不利于查找;
      在这里插入图片描述
4. hash_val 介绍
  • hash_val 是从 TR1(C++98和C++11之间的过渡版本)版本开始使用的;

  • hash_val 使用了可变模板参数 typename… T(C++11);

    • 【对可变模板参数的处理,一般是递归进行拆分,每次都拆分成 1 + (n - 1) 个;】
  • hash_val 有三个重载版本:

    • 三个版本因为参数类型不同或者参数个数不同,所以可以进行重载;
  • hash_val 调用过程:

    • // 示例代码
      return hash_val(c.fname, c.lname, c.no);
      
      • ① 先调用 hash_val 的第 1 版本,因为第一参数不符合第 2、3 版本;

        • 初始化种子 seed,继续调用 hash_val;
      • ② 调用 hash_val 的第 2 版本;

        • 第 2 版本将可变参数拆分成 1 + (n - 1) 个
        • 将 seed 、拆分的第一个参数作为函数参数,调用 hash_combine,更新 seed;
        • 递归调用自己(以 seed、剩下的参数作为函数参数);
      • ③ 当第 2 版本拆分成 1 + 1 个参数,这时调用 hash_val 的第 3 版本(arg… 只剩一个参数);

        • 调用最后一次 hash_combine;
      • ④ 返回的 seed 就是得到的 hash code;
        在这里插入图片描述


2. tuple 元组

1. tuple 介绍
  • tuple 在 TR1 版本加入;
  • tuple 是一组有序对象的集合,tuple 可以存放任意类型(即 tuple 中各个 object 的类型可以不同);
2. tuple 使用
1. 初始化
  • // 1.默认构造
    tuple<string, int, int, complex<double>> t;
    
    // 2.带参构造
    tuple<int, float, string> t1(41, 6.3, "nico");
    
    // 3.借助make_tuple和auto,不需要指定类型
    auto t2 = make_tuple(22, 44, "stacy");
    
2. 获取内容
  • // 1.获取tuple对应位置的内容,不能越界!!!
    int p = get<0>(t1);
    get<0>(t1);
    get<1>(t1);
    
    // 2.可以重新赋值tuple的内容
    get<1>(t1) = get<1>(t2);	// 类型要对应或者可以隐式转换
    
3. 重载函数
  • // 1.重载operator<()
    if (t1 < t2) {
        ...
    }
    // 2.拷贝赋值
    t1 = t2;
    
    // 3.cout重载了operator<<(tuple)版本
    cout << t1;
    
4. 集体赋值
  • tuple<int, float, string> t3(77, 1.1, "more light");
    int i1;
    float f1;
    string s1;
    // 1.借助tie,使用tuple的内容进行集体赋值
    tie(i1, f1, s1) = t3;
    
5. 获取 tuple 的属性信息
  • typedef tuple<int, float, string> TupleType;
    // 1.借助tuple_size<TupleType>::value获取tuple的元素个数
    tuple_size<TupleType>::value; // 3
    
    // 2.tuple_element<index, TupleType>::type,获取tuple对应index的元素类型
    tuple_element<1, TupleType>::type f1 = 3.3;
    typedef tuple_element<1, TupleType>::type t1dd;
    

在这里插入图片描述

3. tuple 源码
1. 关键部分
  • tuple 使用了可变模板参数 typename… T(C++11);

    • 通过继承实现递归拆分 Tail…,形成一组继承关系;
  • tuple 有一个泛化版本,一个特化版本:

    • 泛化版本:

      • template<typename Head, typename... Tail> 
        class tuple<Head, Tail...> : private tuple<Tail...> 
        {
            ...
        };
        
    • 特化版本作为拆分终止条件

      • // 模板参数和class内容为空
        template<typename... Values> 
        class tuple<> {};
        
2. tuple 内容
  • data:

  • Head m_head :记录可变参数的第一个参数;

  • typedef:

    • typdef tuple<Tail…> inherited;
  • function:

    • // 返回tuple的第一个元素
      typename Head::type head() {return m_head;}
      
      /*
      	由于在继承关系中,先构造父类数据,再构造子类数据;
      	所以在内存中,父类的数据
      */
      inherited& tail() {return *this;}
      

在这里插入图片描述

3. 继承过程
  • 示例 tuple:

    • tupe<int, float, string> t(41, 6.3, "nino");
      
  • 继承过程【通过继承递归划分】:

    • ① 开始调用了 tupe<int, Tail…>,head() 返回 41
      • Tail… 包含 float 和 string;
    • ② 第①步继承了 tuple<Tail…>,实际调用了 tupe<float, Tail…>,head() 返回 6.3
      • Tail… 包含 string;
    • ③ 第②步继承了 tuple<Tail…>,实际调用了 tupe<string, Tail…>,head() 返回 “nino”
      • Tail… 为空;
    • ④ 第③步继承了 tuple<Tail…>,实际调用了 tupe<>(拆分终止条件);
4. tail() function 讲解
  • 预备知识:
    • 在继承关系中,会先调用父类的构造函数,再调用子类的构造函数;
    • 因此在内存中,会先存储父类的变量成员,再保存子类的变量成员(和构造顺序相同);
    • 以上述的示例 tuple 为例:
      • 继承关系如图右边所示;
      • 因此 tuple 的数据在内存中的分布,如图下方所示;
      • 从上往下依次是:string,float,int(倒序);
  • inherited& tail() { return *this; }:
    • 根据父类和子类的 class member 在内存中的分布,可以得知,子类和继承的父类的地址指向位置是相同的(图中的圆圈位置);
    • tail() function 返回自身 *this,且返回类型为继承的父类;
    • 【将子类 object 转型为 父类 object,会发生内容截断;】
    • 当 tuple<int, float, string> 调用 tail(),会返回继承的父类 tuple<float, string>;
    • tuple<float, string> 在内存中的范围如图深灰色方框所示;
5. 注意点
  • 实际使用没有必要调用 head 和 tail 来获取 tuple 内容;
    • 之前的 tuple 使用小结中,有获取内容的辅助函数;
      在这里插入图片描述

3. type traits

1. G2.9 版 type traits
  • G2.9 版 type traits 包含 5 个 associated types(第一个没用);
    • has_trivial_default_constructor;
    • has_trivial_copy_constructor;
    • has_trivial_default_constructor;
    • has_trivial_assignment_constructor;
    • is_POD_type;
      • POD == Plain Old Data 平淡的旧数据,即只有 data,没有 function 的 class / struct
  • __true_type 和 __false_type 是两个空类,这和 iterator_category 的五个类型是一样的;
  • G2.9 type traits 为每个基本类型都进行了特化
  • 对于自定义的 class,需要自己特化 type traits,根据自己的理解设置 ture or false;
    • 这种使用方式不够方便;
      在这里插入图片描述
2. C++11 的 type traits
  • C++11 为每个 type trait 属性都单独定义了一个 template class,而不是集中放在 type traits 中;

  • 使用时直接将 class object 传入某个 type trait,编译器就会自动根据传入 class 的内容判断 true or false(很强大)

  • C++11的 type traits 图示:
    在这里插入图片描述
    在这里插入图片描述

3. C++11 type traits 使用
1. 使用形式
  • [type name]::value,返回 0 或 1;
    在这里插入图片描述
2. Foo class(is POD)
  • Foo 只有 data,所以 Foo is_pod == 1;
    在这里插入图片描述
3. Goo class(has virtual function)
  • is_polymorphic:是多态吗;
    • 多态类指:声明 or 继承一个 virtual function 的 类;
      在这里插入图片描述
4. Zoo class(涉及 C++11 新语法)
  • = delete:C++11 新语法,表示这个 member function 不能被调用;
  • = default:C++11 新语法,表示创建默认成员函数(只能用于特殊的成员函数,构造、析构等);
  • &&:C++11 新语法,和 move 操作相关:
    • move 构造;
    • move 赋值;
      在这里插入图片描述
4. C++11 type traits 实现
1. is_void
  • ① 获取 remove_cv<_Tp>::type(去除关键字)
    • 先获取 remove_volatile<_Tp>::type,在这基础上获取 remove_const<_Tp>::type;
    • 【即传入 _Tp,获取去除 volatile 和 const 关键字的 type;】
  • remove_volatile 有一个泛化版本,一个偏特化版本
    • 泛化版本直接返回 _Tp;
    • 偏特化版本针对模板参数为 _Tp volatile 的情况,返回 _Tp(去除 volatile 关键字);
  • remove_const 有一个泛化版本,一个偏特化版本
    • 泛化版本直接返回 _Tp;
    • 偏特化版本针对模板参数为 _Tp const 的情况,返回 _Tp(去除 const 关键字);
  • ② 将 remove_cv<_Tp>::type 传入 is_void_helper<>,获取 type;
    • is_void_helper 有一个泛化版本,一个偏特化版本
      • 泛化版本,继承 false_type class;
      • 偏特化版本针对模板参数为 void 的情况,继承 true_type class;
      • 【false_type 和 true_type 中有一个 value 成员变量,为 bool 类型;】
        在这里插入图片描述
2. is_integral
  • ① 获取 remove_cv<_Tp>::type(去除关键字)
    • 先获取 remove_volatile<_Tp>::type,在这基础上获取 remove_const<_Tp>::type;
    • 【即传入 Tp,获取去除 volatile 和 const 关键字的 type;】
  • ② 将 remove_cv<_Tp>::type 传入 is_integral_helper<>,获取 type;
    • is_integral_helper 有一个泛化版本,多个全特化版本
      • 泛化版本,继承 false_type class;
      • 全特化版本针对所有数值类型,继承 true_type class;
      • 【false_type 和 true_type 中有一个 value 成员遍历,为 bool 类型;】
        在这里插入图片描述
3. 涉及 class 内部信息的 type traits
  • 图中蓝色部分在 C++ 标准库源代码找不到;
  • 可以猜测,这些判断 class 内部信息的工作,都交给了编译器,因为编译器在编译时肯定知道 class 内部的所有信息;
    在这里插入图片描述

4. cout

1. cout 简单介绍
  • cout 是一个 object
    • cout 的 class 是 ostream;
    • 在 iostream 头文件中有一个 extern object,命名为 cout,使其他用户可以直接使用 cout;
  • extern 关键字
    • 修饰符 extern 用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”。
    • extern声明不是定义,即不分配存储空间;
    • extern 实现了声明和定义分离;
  • ostream 重载了所有基本类型的 cout << 操作符;
    在这里插入图片描述
2. G4.9版的 cout
  • G4.9版的 cout 又重载了很多常用类型的 << 操作符;
3. 重载 cout
  • 如果想要使用 cout 输出自定义的 class,需要重载一个 operator<<()函数:
    • 一般 operator<<() 函数 是全局函数,然后在 class 中将 operator<<() 设置为 friend function;
      在这里插入图片描述

5. 简单介绍 move

1. move 的实质
  • 对于带有 pointer 的 class:

    • copy 就是深拷贝;
    • move 类似于浅拷贝,在浅拷贝的基础上,断开源指针的连接;
  • 【当传入参数是一个右值,编译器会自动调用 move 版本的函数(如果 class 有实现的话);】

  • 使用 move 构造或者 move 赋值后,源数据(带有 pointer 的 class)作废,不能再使用;

2. 实现一个 moveable class
  • 关键部分,实现 move 构造和 move 赋值;

    • move-ctor:
      • 使用 initialization list(构造函数都应该用列表初始化),初始化 data(包括 pointer);
      • 断开源数据的 pointer 连接;
    • move-asgn:
      • 和 move-ctor 思路相同,类似浅拷贝操作;
    • 析构函数
      • 先判断 pointer 是否为空指针,然后再手动 delete;
      • 对于带有 pointer 的 class,同时实现了 move 相关操作,一定要这么写;
        • 避免一个 pointer 被 delete 两次,发生一些未知的情况;
        • 没有实现 move 操作,也可以养成习惯;
  • 【移动构造和拷贝赋值同时存在(移动赋值和拷贝赋值),调用哪个???】
    在这里插入图片描述
    在这里插入图片描述

3. moveable class 对容器的insert速度效率影响
  • 容器的内容都是 string(带有指针的 class);
1. vector
  • 对于 vector 这种扩容需要重新拷贝数据的容器:
    • 使用 move 操作,速度效率能提升很大;
      在这里插入图片描述
2. 其他容器
  • 对于 list、deque、multiset、unordered_multiset 容器:
    • move 操作更快,但和 copy 操作不会差很多;
4. 测试程序
  • 通过 iterator_traits 获取容器的 value_type;
  • 测试 moveable:
    • 往容器中插入 300000 个字符串(计算时间);
      • 对于 moveable class:
        • 由于 insert 传入的是右值 object,编译器会自动调用 move 构造或者 move 赋值;
  • 对容器进行 copy 构造(计算时间);
  • 对容器进行 move 构造(计算时间);
    • 使用 std::move 强制将左值转为右值;
  • 对容器进行 swap(计算时间);
  • 测试 non-moveable:

    • 在这里插入图片描述
5. vector 的 move ctor
  • vector 的 move 构造只是交换三个 pointer(图右);
    在这里插入图片描述
6. string 是否是 moveable
  • string 有 move 构造和 move 赋值;
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值