【C++标准11-14-侯捷】---学习笔记

第一讲 语言

第一节 头文件header files

在这里插入图片描述

(1)标准库都在std名称空间中,全名为std::+名字;
(2)旧版本就是#include<stdio.h>也可以兼容使用;

1.2 看源代码需要全文检索工具

在这里插入图片描述

1.3 测试是否支持并设置C++2.0,__cplusplus

在这里插入图片描述

1.4 主要学习内容—C++2.0版本

在这里插入图片描述

第2节 Variadic Templates可变参数模板(重量级改变)

2.1 print()为例

在这里插入图片描述

(1)const Type&… args表明接收任意数量任意类型的参数;
(2)可以帮助我们做递归操作每次取出一个参数;做一个递归结束无参数的函数,终止递归;
(3)sizeof…(args)返回到底有多少个;
(4)2和3可以并存吗,是可以的!

2.2 hash function为例,依次分离出一个参数

在这里插入图片描述

(1)先调用1,形成seed和一包数据,然后调用2(特化),hash_combine将一包T融入seed变化,然后自己调用自己,再进行拆分,然后到3做为终止条件;每次调用自己的hash_val就是拆分出一个参数;

2.3 tuple,多次继承为例

在这里插入图片描述

(1)每次继承留下一个参数,继承其余一包数据;

第3节 小改变集合

3.1 Spaces in Template Expressions模板表达式中的空格

在这里插入图片描述

3.2 nullptr and std::nullptr_t

(1)用nullptr代替0和NULL;空指针;
(2)nullptr的类型是std::nullptr_t,定义在头文件中;
在这里插入图片描述

3.3 自动类型判断auto

(1)之前auto就是局部变量的意思,但是现在auto表示自动类型判断;
(2)编译器本身就具备自己判断类型的能力;
![在这里插入图片描述](https://img-blog.csdnimg.cn/a9f14097e1824c3da0ceb91f4ecb35ce.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAcGxhaW5fcm9va2ll,size_20,color_FFFFFF,t_70,g_se,x_16#pic_left =80%x80%#pic_left =70%x60%)
(3)auto关键字
在这里插入图片描述在这里插入图片描述
(4)首先判断版本支持C++2.0;标准库本身也在使用auto;
(5)这里是reverse_iterator是迭代器适配器,利用到了迭代器萃取机进行提问回答difference_type的类型作为函数返回类型;

第4节 Uniform Initialization一致性的初始化

在这里插入图片描述
(1)之前的初始化可能用到{},(),=赋值;
(2)任意的初始化都可以统一用{}进行初始化,设置初值;
(3)因为编译器遇到{}时会自动生成一个initializer_list,其中T就是int,int,string,double这些,背后 其实是一个array<T,n>,n是元素个数,然后一个一个传给调用的函数(就是int values的构造函数),如果本身接受的就是initial_list,就不用一个一个赋值,直接整个传过去;

4.1 Initializer_list

在这里插入图片描述
(1)不允许窄化转换;例如double转int是不被允许的;

4.2 initializer_list的应用

4.2.1 例子1

在这里插入图片描述
(1)此处也是对应任意数量的参数,对应生成一个initializer_list,但是注意,这里的类型只能一致指定的int,因为其背后实现是一个array数组;和tuple不同,tuple更加强大,任意数量任意类型;
(2)这也是应用initializer_list<>的一个方法;

4.2.2 实例2

在这里插入图片描述
(1)当是complex就调用版本1构造函数;
(2)如果没有版本2,q,s还可以使用,因为会被拆解成两个参数,就可以调用版本1,r则不合法操作;
(3)右侧是initailizer_list的源代码实现;其data是一个arary的迭代器(指向array的头部),size_type元素个数;编译器可以调用initializer_list类private中的一个构造函数,(外部都不可以调用),在看到{}之后就会调用此处,(在调用这个隐私构造函数前,编译器会提前创建一个array并把其头迭代器传入此函数参数中)

4.2.3 array容器新增,

(1)就可以与STL对接,数组就变成一个容器了,因为算法只跟迭代器对接;
在这里插入图片描述

4.2.4 initializer_list<>没有内含array

在这里插入图片描述
(1)并没有内含着一个array容器,构造函数只是传入一个array迭代器;因此在拷贝的时候,还是相同的元素,传递的只是指针而已(浅拷贝);原来的一包和新的一包指向同一个array

4.2.5 initializer_list<>在库中应用广泛,说明可以接收数量不定的参数

在这里插入图片描述

(1)对以上进行测试
在这里插入图片描述
(2)可看到insert传入了{0,1,2,3,4};
(3)max和min同时比较多个参数,返回其中最大/最小的一个参数;

第5节 explicit关键字—一个以上参数的构造函数

5.1 C++2.0之前的(只用于一个实参的防止隐式转换)

在这里插入图片描述
(1)关键字explicit用于构造函数,用于防止单参数构造函数的隐式转换;
只有在明确调用单参数构造函数时,才使用;左侧就会发生隐式转换;

5.2 C++2.0现在explicit也同样针对于多参数的构造函数的隐式转换

在这里插入图片描述

第6节 range-based for statement—for循环的一种特殊写法

在这里插入图片描述
(1)之前for()有三段,现在只有两部分,左侧是声明,右侧放容器;
(2)第一段是值赋值,如果是复数就是16字节的100万次搬动,下面是auto&引用,因此就是4字节的100万次搬动;而且如果for循环要改变元素的值,一定要用引用,因为上面的只是拷贝到一个新地方,并不影响原来的元素;
(3)关联式容器不允许用迭代器改变其元素;

6.2 for循环的实现—遍历容器一个一个赋值

在这里插入图片描述
(1)其实是遍历右侧容器,并将其一个一个赋值到decl中;
(2)两种调用,一个是容器的begin(),一个是全局函数begin(容器);
在这里插入图片描述
(3)左侧C就不允许单参数的隐式转换,因此这种for就会报错;

第7节 =default,=delete

在这里插入图片描述
(1)=default,默认,当你自行定义了一个构造函数,那么编译器就不会再给一个default ctor,如果强制加上=default,就可以重新使用default ctor;
(2)主要作用在构造函数,拷贝构造,搬移构造,=delete就是不要它,=deafult是要编译器默认那个;
(3)在库中的=delete使用情况,如下图,主要用于构造函数,拷贝构造,拷贝赋值和析构函数;多了与搬运构造和赋值;&&与move有关;
在这里插入图片描述
(4)=default在库中的应用,在move和copy上,下图是析构函数
在这里插入图片描述
在这里插入图片描述

7.2 =default,=delete的正确使用

在这里插入图片描述
(1)构造函数可以有多个,但是拷贝构造只能有一个;在自己定义出一个拷贝构造之后,不可以再使用=default和=delete;
(2)拷贝赋值操作同上;
(3)一般函数没有=default,但是可以使用=delete;但是很少用到;
(4)析构函数可以=default使用默认,但是不可以=delete;
**(5)=delete可以用到任何函数上,=0只能用于虚函数;**变成纯虚函数;

7.3 默认的构造/析构…之前版本

在这里插入图片描述
(1)一般带有指针都需要自己重新写拷贝构造,重载赋值和析构函数;否则一般用默认版本就足够;

7.4 no-copy,private-copy

在这里插入图片描述
(1)nocopy禁止赋值,就如第一个所示,将拷贝构造和赋值重载都delete掉;
(2)第二个将析构函数=delete,就告诉编译器不要定义它;当声明对象会报错,即使通过new赋给指针,但是delete指针也要调用析构因此也会报错;
(3)privatecopy是将复制构造和重载赋值放在Private中;仍然可以被friend和成员函数调用;

第8节 Alias Template(template typedef)化名,Alias,换一个名称

在这里插入图片描述
(1)using Vec=std::vector<T,MyAlloc>,创建容器时自动默认选择alloc,如果自己写的迭代器就每次都要<T,MyAlloc>,在using Vec之后,就可以直接用Vec来代替;
(2)而使用define宏定义和typedef是无法达到这个效果的;
(3)不能对化名,来进行特化和偏特化,还是需要对原来的对象进行特化,化名只是一个名字,不代表本尊;

8.2 Alias Template更多的用途,不只是为了少打字

尝试传入容器和元素,对不同容器进行move的测试
在这里插入图片描述
(1)左侧版本天方夜谈:因为传入的参数是一个容器(类型),但接收的是一个容器对象object,函数体中对容器类型进行区分,不太对。。。左侧有报错信息;
(2)右侧改善,传过去的是一个对象object,修改为list<>(),建立一个临时对象;然后推导出对象的类型;然后用这个类型;但是还是不对,编译器报错:Container不是模板,修改为typename Containerc,依旧报错,因为typename默认会有::;
(3)修改改善的版本如下:
在这里插入图片描述
(1)传入的是一个容器的对象object,需要在函数中取出容器的元素类型,函数第一行首先取出容器中的iterator迭代器,然后使用迭代器萃取机提问value type从而获得容器内的元素类型;
(2)如果没有iterator和traits呢?不死心继续尝试第一种的想法,模板接收一个模板template参数Continer,Container本身又是一个class template类模板,能取出Container的template参数。例如收到vector,能够取出其元素类型string;->模板模板参数

8.3 template template parameter模板模板参数—艰涩高深的模板技巧

在这里插入图片描述
(1)template
class Container是模板的模板参数;
这样写更加清晰,它本身也是一个类模板;
(2)报错,在Contianer处,期望是一个template<class _Tp,class _Alloc>class std::vector;需要自己写一个迭代器;解决方法就是化名!!!
在这里插入图片描述
(3)注意:这些化名不可以写在function之内的;

第9节 type alias+noexcept+override+final

9.1 type alias类型的化名(类似于typedef)

在这里插入图片描述
(1)typedef void(func)(int,int)说明func是一个函数指针,与下面的using func=void()(int,int)等价,但是using更加的明显,凸显func是一个类型;
(2)右侧也是等效,using value_type=T等价于typedef T value_type;

9.2 using汇总

在这里插入图片描述
(1)using namespace std;打开名称空间;或者using std::cout;
(2)using _Base::M_allocate;指定去哪里找;后续就可以只写M_allocate;
(3)第三种就是类型化名,using func=void(*)(int,int);

9.3 noexcept异常

在这里插入图片描述
(1)在函数void foo()后面加上noexcept就是表明,程序员保证这个函数不会出现异常;还可以再加上条件noexcept(true)在符合这个条件下,不会出现异常;是可以加上条件的;
(2)下面框中,表明这个swap保证不丢异常,在x.swap(y)不丢异常的情况下;
(3)出现异常会往调用函数那里寻找处理程序,不然就会一直往回追溯;如果没有找到就会触发std:;terminate()继而触发std:;sort()程序中断;
(4)在Move中必须加上noexcept!!!
在这里插入图片描述
(5)因为vector有成长扩容两倍的过程,每次都需要调用复制构造来到新的空间,如果有Move的话,就会执行move操作,会更加效率;
(6)你写出move必须通过noexcept通知vector,vector才会放心调用Move;如果没noexcept,vector就不会调用Move;

9.4 override改写,应用在虚函数

在这里插入图片描述
(1)当没有override时,重写虚函数时,当你写错编译器也不会报错,认为是新的一个虚函数,但是加上override之后,写错就会进行报错;

9.5 final关键字

在这里插入图片描述
(1)修饰类;给父类写上final就说明自己是继承的最后一级,再有类继承就会报错;
(2)修饰虚函数;就不可以再被子类重写;

第10节 decltype关键字

在这里插入图片描述
**(1)相当于typeof,**可以获取表达式的类型;但是typeof并不是标准库的,所以新的关键字decltype;就像decltype(coll)::value_type elem;

10.2主要应用有三种:表达式的类型;

在这里插入图片描述

10.3 decltype的应用一 (声明返回类型)

在这里插入图片描述
(1)decltype用来表示返回类型,一个表达式的类型就是(x+y)使用编译器来确定decltype(x+y);最终这种语句是否可以通过编译,要看具体怎么使用;
(2)注意,上面框中的decltype会编译不通过,因为编译器没有看到x和y,所以有下面写法,auto add(T1 x,T2 y)->decltype(x+y);
(3)auto->decltype的组合与lambda用法很像;
(4)标准库中的decltype的使用如下图所示;
在这里插入图片描述

10.4 decltype的应用二(适用于原编程)

在这里插入图片描述
(1)整个是一个函数模板,接受一个模板参数类型为T,
(2)只要加上::前面就加上typename;表示一种提问;帮助编译器确定T::iterator是一个typename;decltype(obj)用来获取对象的类型其实就是T;
(3)模板只是一个半成品,真正能否通过编译要看具体使用,例如如果传入一个复数,复数complex没有迭代器,就会报错;

10.5 decltype的应用三(lamda)用于通过lamda的类型

在这里插入图片描述

第10节 Lambda

在这里插入图片描述
(1)[]{}是一个类型;[]{}()就可以调用,相当于临时对象;多数会写成最后一种形式,赋予一个名称auto I=[]{};I();

10.2 语法如下:

(1)[]导入符号,mutuable是否可以被改写参数,throwSpec丢出异常,->retType表示返回类型;紫框可写可不写,但是如果写一个就必须要写();{}是函数本体;()里放函数参数;[]取用外部的变量;
在这里插入图片描述
注意:[=,&y]表示默认接收外界的所有变量和y的引用;
(2)左侧相当于右侧建立一个仿函数类,重载();
在这里插入图片描述
(3)绿色框还没做事情,只是一个定义,真正调用执行是f();注意输出为012,因为在定义时,相当于右侧的仿函数类,有自己的一个数据id,创建的时候看到了上面的id=0;自己的id=0;++id变得是自己的id数据,不会影响外界的id;
Lambda的类型是一个不知名的仿函数;
(4)因为[id]而不是[&id],因此外界id不会变;
(5)mutable表示id可以改变,没有Mutable的话,就不能改变id;与仿函数还是又一点点不完全等价;

10.3 Lambda用法比较

在这里插入图片描述
(1)第一个如上,第二个[&id]传入引用,外界会影响,操作也会影响外界;第三个,没有mutable就不允许改变[]中参数id,编译器会报错;
(2)Lambda是一个仿函数,也可以在函数主题中定义变量,返回数值;

10.4 Lambda的另一个等价代码例子

在这里插入图片描述

10.5 Lambda与decltype

在这里插入图片描述
(1)decltype用作获取Lmabda的类型;
(2)上面一段话的意思就是,lambda就一个无名的仿函数,但是每个都是独一无二的,因此,如果要声明这个类型就使用template或者auto,如果需要类型就使用decltype,例如当你需要使用lambda作为一个hash function或者排序准则传给不定序容器,具体例子代码如图所示;
(3)左侧是set<>coll(cmp);构造函数如箭头所指,
(4)下面一段话:在使用decltype获取lambda类型作为容器类型时,需要同时传入lambda作为构造函数参数,不然容器set会选择调用lambda的默认构造函数和默认赋值操作,但是lambda并没有默认构造,编译器就会报错;因此需要set<>coll(cmp);传入lambda的cmp;

10.6 lambda与functor仿函数的对比

在这里插入图片描述
(1)左侧lambda就相当于一个inline内联函数,效率会比右侧仿函数更高;但是这点效率略显微不足道;

10.7 lambda在标准库中的应用

在这里插入图片描述

第11节 重回variadic template可变参数模板(重点!!)

在这里插入图片描述
(1)template模板代表的有函数模板和成员模板;variadic是参数,参数数量和参数类型;
(2)新的关键字就是…,template<typename T,typename…Types>;注意…在三个不同位置;

11.1 例子一print()

在这里插入图片描述
(1)调用自己,并设置终止条件;隐藏条件是需要cout可以识别;模板时半成品,具体还要看实现;
(2)如果想要知道…args参数数量,就可以使用sizeof…(args);
(3)右侧,3可以与1并存吗,答案是可以并存的,谁更特化就调用谁,1更特化,会调用1;那么3就一直不会被调用;

11.2 例子二—使用variadic templates重写printf()

在这里插入图片描述
(1)实际上是根据后面的Args…args进行判断,一个一个拿出来,与前面的%d%s%p%f无关,只是为了模仿C的形式;

11.3 例子三—variadic template函数max_element

在这里插入图片描述
(1)复习:initial_list实际上是{}统一初始化的底层实现,而initial_list
本身是一个array的指针作为data,接收任意数量的参数,要求参数的类型一致,构造函数会自动将元素搬移到{}的调用容器,完成初始化;
(2)编译器看到{}就会自动形成一个initial_list对象;
(3)此处借用了initial_list完成实现;

11.4 例子四—使用variadic template实现maximum

在这里插入图片描述
(1)每个测试程序定义自己的命名空间,可以避免与标准库中名称发生冲突;并且会多次include头文件,但是头文件自己有保护机制,不会被真的include多次;
(2)这里是利用了标准库中的max,其实max现在也可以接收多个参数(类型一致),通过initial_list实现;

11.5 例子五—cout<<非一般形式处理头尾[],用到了类模板

在这里插入图片描述
(1)使用非一般的方式处理first和last元素时,例如右侧黑色输出,第一个和最后一个[7.5,最后一个42],这个时候用到类模板,之前例子都是函数模板;
(2)而且需要知道元素个数才可以区分第一个和最后一个,因此用到了sizeof…()来获取args的参数个数;
(3)同时用到元组tuple是一个可以接收任意数量任意类型的东西,称为元组;
(4)从右侧开始,希望cout<<解析make_tuple,因此需要重载cout<<运算符;
(5)get(t)是tuple提供的接口用来取元素;丢给os进行输出;
(6)调用类PRINT_TUPLE<>的print函数,print函数自己调用自己,直至MAX调用2创建特化类中的print()结束;print函数中调用了tuple的接口函数get(t)来取出元素并传给os进行输出;

11.6 例子六—tuple用递归继承

在这里插入图片描述
(1)类模板,才有继承关系;
(2)在进入tuple之后,首先第一个参数被用来作为data变量存储,然后继承另外的那一包tuple;
(3)子类的对象有一个父类的成分;
(4)typename Head::type是成员函数head()的返回类型;为了让编译器认识,只要出现::前面就加上typename;
(5)调用this()返回尾部一包,本来this指的是三格整体,但是会转为那个灰色框起来的部分,进行返回;
(6)注意在初始化的时候,inherited(vtail…)不是创建临时对象,而是赋值给继承的父类,会调用父类的构造函数;

11.6.2 编译出错,在typename Head::type head(){return m_head;}

在这里插入图片描述
(1)Head无法回答Head::type问题;
(2)改进decltype;如下;
在这里插入图片描述
(3)直接加decltype(m_head)是不可以的,因为编译器此时还没有看到m_head;借助lambda的语法形式,将decltype写在右边,就是
auto head()->decltypr(m_head)
;并且将data放在前面;
(4)后来发现更直接的返回,直接写Head!
在这里插入图片描述

11.7 例子七—递归复合/内含,tuple递归内含实现

在这里插入图片描述
(1)注意,此处composited& tail(){return m_tail};要返回引用;

11.8 C++关键字(绿色为新增)

在这里插入图片描述

第二讲 标准库

第一节 标准库源代码分布

在这里插入图片描述
在这里插入图片描述

第二节 Rvalue references and Move Semantics

2.1 Rvalue reference右值引用—减少不必要的复制

在这里插入图片描述
(1)当copy的右手边来源端是右值,那么左值可以“偷”右手边resource而不需要执行allocation;
(2)左值和右值的差别:1、左值是变量;2、不能放在左手边的就是右值;
(3)string是一个指针,对于string的红色测试都通过编译了,表达式s1+s2,临时对象string()都通过了!!!
(4)对于complex的测试也通过了测试!!!complex没有指针哦!

2.1.2 C++2.0对右值&取地址的新语法

在这里插入图片描述
(1)&foo()有()因此是对函数返回值来取地址,而函数返回值是一个右值,因此不能对右值取地址;
(2)C++2.0可以对右值取地址,引用reference;

2.2 Rvalue references and Move Semantics

在这里插入图片描述
(1)c.insert(it,Vtype(buf))如果这个Vtype(buf)临时对象有指针的话,可以“偷”,但是要有一种明确指令可以允许“偷”的行为;
(2)C++2.0的inset有两个版本,除了copy还有move版本insert(,&& x);
作为右值引用的类型的拷贝构造函数也需要对应两个版本,分别是copy拷贝构造和move拷贝构造;copy拷贝构造是深拷贝是真的会分配一个新内存;move拷贝构造Mystring(Mystring&& str),不需要新分配(指针的浅拷贝),只是将指针复制过去;两个指针指向同一个内存,是危险的操作;因此,原指针就被打叉就不能再调用**(转让所有权!!!)**;但是右值本来就是临时对象,之后也并不会被继续使用;
(3)如果是左值,但是程序员知道以后不会再使用,将其作为右值,一种语法就是M c2(std::move(c1)),相当于告诉编译器是右值引用rvalue reference;

2.3 Rvalue reference 和 Move Semantics的代码实现

在这里插入图片描述
(1)容器vector的insert()中move版本实现,G2.9是之前的insert,G4.9新增了moveb版本的insert();
(2)身为元素的MyString要写好自己的拷贝构造的两个版本;
在这里插入图片描述

2.4 Perfect forwarding完美move交接(不遗漏信息)

在这里插入图片描述

2.4.1 Unperfect Forwarding

在这里插入图片描述
(1)process(move(a))是告诉编译器请把a当做右值处理;
(2)在forward(2)测试时,forward正确调用了右值forward(int&&),但是在forward调用process(i)的时候,确调用的process(int &),而不是Process(int&&),称为“不完美的快递”;
(3)就像调用insert()函数之后insert()再调用元素的拷贝构造一样;会出现问题;

2.4.2 标准库中的完美做法—forward模板

在这里插入图片描述
(1)通过forward模板实现的;
(2)右上角是告诉编译器将左值看做右值的move()代码;

第3节 写一个move-aware class自带move意识的类

在这里插入图片描述
在这里插入图片描述
(1)copy和move依靠右值引用Rvalue reference来区分;
(2)注意在move constructor中,浅拷贝之后,将原来的len=0;data=NULL;注意这里不是将其delete,只是置为空指针,因为delete是由析构函数负责的;
而且一定要将其设置为NULL,因为如果原析构函数delete内存空间就被释放;
(3)move assignment相同,也是先进行长度和地址的浅拷贝,然后将之前的len=0;data=NULL;
(4)在析构函数中,会检查data是否为NULL,不是NULL才会delete;因为如下图中,Vtype(buf)是一个临时对象,复制完成后会调用其析构函数,要使用NULL将其连接打断,并且保存那块内存;并且其析构函数要判断,如果是NULL就不要delete,只是释放临时对象本身就可以;
在这里插入图片描述
(5)关联式容器需要检测元素大小,就写了<和==的重载函数;
(6)绿色框变量是用来测试调用的静态变,下面蓝色框是供hash容器使用的hash function;

第4节 Move-aware class对容器的效能测试

4.1 测试一:通过调用insert插入,对每一个元素做赋值,调用的也是元素的move 构造函数

在这里插入图片描述
(1)测试vector容器,元素是Mystring带有move版本和MyStrNoMove版本的;
上图中,300万个Moveable元素放入vector容器中,调用移动构造函数700多万次,下方测试300万个non-moveable元素放入vector容器,调用赋值构造函数700多万次;
(2)因为两倍增长,把所有元素全部搬移,引发复制构造/移动构造,所以是700多万次;
在这里插入图片描述
在这里插入图片描述
(3)接着有对于List,deque,multiset差别不大,因为其链表或者红黑树结果,插入方便;没有vector不断增长两倍空间,不断搬移的情况;对于Copy constructor和Move contructor;
(4)其具体差异多少与当前内存破碎或连续情况而定;
(5)但是对于copy和move copy的差别一直都巨大!!
(6)目前看只有对vector影响巨大,但是对于deuqe,如果做从中间某一处插入的动作,两边元素会往外搬移,会出现大量的copy constructor也影响巨大;
(7)容器以节点形式存在,Copy constructor还是Move constructor影响不大;

4.2 现在是测试(二)对容器本身的copy拷贝构造和move拷贝构造的测试

注意:M c1©;M c2(std::move©);

4.2.1 这个执行的是M c1©是真正的copy开辟新空间—慢

在这里插入图片描述

4.2.2 这个执行的是M c2(std::move©将c作为右值,执行Move操作—快

在这里插入图片描述
(1)只是交换了指针,就很快,其实指向的是同一个内存块;
(2)而且因为vector其实只需要3个指针构成,元素头尾和空间尾;其实就是两组指针的交换;每组有3个指针;
(3)move测试是将整个容器对象c当成右值;容器c就之后就不可以再用了!!!
当你选择用Move()将源头看做右值时,就必须确定之后不会在使用!!!

4.2.3 总结:

(1)元素本身要带有Move意识;
(2)move操作对于所有容器和容器之间的操作搬移也产生巨大影响;

第5节 容器—结构与分类,新旧对比(关于实现手法)

5.1 容器分类(红色框为新增)

在这里插入图片描述

5.2 新旧比较(实现手法)

在这里插入图片描述
(1)G2.9旧版本对于容器都是单一的一个class类,但是新版G4.9将其统一化,都是继承一个父类,父类包含一个数据类,数据类继承一个allocator分类器,数据类自己再包含一个具体的数据;

5.3 容器array

在这里插入图片描述
(1)内部就是一个数组,只是将其包装为一个容器array;使其融入STL;

5.3.2 代码实现

在这里插入图片描述
(1)注意右上角的蓝色框的三四行;

5.4 hashtable容器unordered 不定序容器

在这里插入图片描述
(1)新容器unordered背后其实就是hash table在作为支撑;

第6节 Hash function

(1)基本类型都有对应的hash function;hash可以看出基本类型的hash function应该是一个模板;
在这里插入图片描述
在这里插入图片描述
(2)上面是对hash的测试,可以看例如hash()(123),其中hash()是创建了临时对象,然后(123)应该是重载的()运算符,返回值即为hash code;
(3)在C++2.0之前,标准库中没有对于string的hash function;

6.2 标准库中hash function的实现(泛化和特化)

在这里插入图片描述
(1)一个泛化版本,和若干个特化;对于数值直接利用数值;
(2)右侧调用接口为int i=hash()(32);因此,如果自己写一个Foo的hash function也需要有相同的接口在hash特化中重载()运算符;

6.2.1 针对字符串的hash版本

在这里插入图片描述
(1)G2.9没有string的hash function,但是G4.9有提供;
(2)简单验证如下;
在这里插入图片描述
(1)上面是创建3个临时对象分别计算三个字符串的hash code;下面是创建一个有名对象,调用三次()重载运算符计算hash code;

6.3 G4.9中hash function的代码实现

6.3.1基本类型的hash function

在这里插入图片描述

6.3.2 string的hash function

在这里插入图片描述
左侧为string的特化版本;

6.3.3 怎么自己写一个hash function一个万用的hash function

(1)产生出的hash_code越乱越好,不要重复;在这里插入图片描述
(2)以类customer为例,以它为元素放在容器里,然后计算它的hash_code;
(3)上图中形式2设计为一般函数,形式1设计为成员函数;
(4)右侧调用函数时,形参为函数类型size_t(*)(const Customer&),左侧会自动产生一个函数对象;
(5)原子拆分,再相加形成hash function太过天真;分的有fname,lname都是string基本类型有自己的hash function,以及num是Long类型;就是在用hash_function的特化版本;把各自的hash_code简单相加,这样会碰撞多,一个篮子的链表过长;
(6)调用库中的函数hash_val;

6.3.4 typename… Type代表任意数量任意类型参数

在这里插入图片描述
(1)template<typename…Types>新增,可以添加任意多个模板参数;variadic template;
(2)1和2的区别在于第一个参数分别是size_t&和const Type&…;
(3)1,2,3是函数重载;用最泛化的版本分离参数个数,1分为seed和所有传入(例如3个)的参数,然后调用2,在2中调用自己hash_val(seed,args…),分为seed,val,和另外2个参数,然后重复分解;过程中依次每次取出一个参数做seed的变化用;当只剩下seed和位移参数时,到3执行;
(4)将不定量的参数不断拆解;
(5)0x9e3779b9的来由;黄金比例
在这里插入图片描述

第7节 Tuple—递归继承,可变参数类模板

在这里插入图片描述
(1)为什么是32,现在无法解释;子类是父类相加总的内存;
(2)get<0>(t1)是拿出tuple t1的第一个参数;之前7个(可变模板参数)例子之一的.tail().head()比较难用,标准库给出了直接获取参数的函数;
(3)然后是对=,<,<<的重载运算符;
(4)tie是捆绑,是将tie的三个参数给t3;tie(i1,f1,s1)=t3;
(5)右下角,tuple_size<>::value和tuple_element<>::type这种用法,这些是对类型做操作;称为meta programming原编程;

7.2 tuple历史—sutpid的做法,没有可变模板参数这种“屠龙刀”

在这里插入图片描述
(1)写到哪里处理多少个,写到15就处理到15;
(2)下图有人继承上图想法,写到了15;
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值