C++标准11-14
2. Variadic Templates
(1)可以用于递归调用函数
对于Variadic Templates来说,要定义好边界的情况
当args只有一个参数了的时候,第一个参数进去作为firstArg,后面一包的参数为空了,因此会print()一个不带参数的情况,所以我们为了处理边界,要定义一个没有参数的(1)版本的print()。
…是pack:模板参数包、函数参数类型包、函数参数包
(2)和(3)是可以并存的吗?
优先调用更加符合的特化
hash_val有三种版本:整个一包的、第一个参数是size_t的,(它们里面又分两版本、后面还有一个一包参数的)
第一次调用只有(1)符合,所以进入(1),之后固定了第一个参数为seed,加一包,因为现在来说,(2)是特化,相较(1)泛化来说更加吻合,所以调用(2)
(2)递归继承
继承了tuple<Tail…>并且把第一个参数定义为m_head;
并且把 tuple<Tail…> typedef 为了 inherited;
注意在构造函数的初始化列表当中,初始化vtail时,仍然是调用的base ctor
调用tail时,是把它转为了父类的引用的形式,再去调head,得到父类的那个值
3~4 nullptr、auto
auto只有在这个类名很长或者很复杂的时候使用
Uniform Initialization
在变量的后面直接放大括号,放初始值做初始化
在初始化vector的时候,编译器看到里面是int,所以在initializer_list的T设为int。
它的内部有一个array<T, n> n是刚刚的个数,编译器也能知道,比如这里有7个。调用时,它变成一包,然后一个一个地进行赋值初始化。
如果函数调用的参数就是initializer_list ,有一个版本就是接收它,那就是整个过去
如果它没有接受 initializer_list 的函数,编译器就会一个一个地拆解并且进行初始化
大括号里是形成一包,有的构造函数就是接收这种包,一包传给了一包
complex构造函数,没有一个是接受一包这种东西的,它只接受一个一个。编译器会把一包分成两个,然后一个一个赋值
5. Initializer list
想用{5.3} 直接赋值给 int x,是不可以的。会报出ERROR/Warning
用{} 默认赋值是 0 或者 nullptr
初始化的时候,如果用{}默认是调用一包的那个函数。如果没有那个函数,就拆开用(1)函数
P s={77,5}; 也是调用构造函数
initializer_list背后是一个array
array就是换一种形式来表达数组,这样就可以用其他算法来处理了。因为算法认的是迭代器。
除了作用在构造函数中,initializer_list还能作用在=、insert、assign当中
在forwaid_list.h、stl_map.h、stl_algo.h等等中都有initializer_list的版本
所以在max、min函数里也可以使用
7. Explicit for ctors taking more than one argument
左边是调用了隐式构造函数
如果右边的话,就没有办法隐式调用
隐式转换
左边的构造函数是non-explicit one argument ctors,只有这种的,才能调用隐式转换
8. range-based for statement
一个一个地拿出来遍历,注意拿出来的时候是要assign到左边的变量里面,如果是assign给引用,首先赋值会变的很快(如果你是16个字节,100万个,这样搬动的速率一定是低于4个字节,100万个),其次会直接改变元素内容。
什么容器不能用迭代器直接改变元素内容?
关联式容器都不可以:set、map、mutiset、multimap、unordered_set、unordered_map
begin()、end()是全局函数
对于容器,也可以用auto来直接的调用遍历,它内部是相当于用了右边的形式
注意:for-loop的时候,如果类型不一样,就要做转换。但是如果转换的源头是禁止的,那就会报错
9.=default , =delete
如果你自行定义了一个ctor,那么编译器就不会再给你一个default ctor
强制加上=default,就可以重新获得并使用default ctor
加上 =delete,就是代表不要了
下面两个分别是copy assignment 和 move assignment
ctor可以有很多,copy ctor 只能有1个。同样地,那个delete也是错的,因为已经写了怎么又delete
copy assignment 一样的,只能有一个
一般的函数没有default
delete可以用于任何函数身上
=0只能用于纯虚函数
对于空的类来说:
什么类需要有Big-Three?
只要它带有pointer member 几乎可以判定它需要有Big-Three
三法则(英语:rule of three,the Law of The Big Three,The Big Three;三法则,三大定律)在 C++ 程序设计里,它是一个以设计的基本原则而制定的定律,三法则的要求在于,假如类有明显地定义下列其中一个成员函数,那么程序员必须连其他二个成员函数也一同编写至类内,亦即下列三个成员函数缺一不可。
析构函数(Destructor)
复制构造函数(copy constructor)
复制赋值运算符(copy assignment operator)
对于字符串来说,就需要写,而且要写Big-Five
析构函数
拷贝构造函数
移动构造函数
拷贝赋值函数
移动赋值操作
上面是NoCopy,把所有Copy相关的函数都禁止掉了。
下面是NoDtor, 把拷贝函数禁止掉了
最后面是PrivateCopy,把拷贝构造。是可以由它的友元调用
进行继承的话,也继承了这样的特性。是可以被自己的家族成员和自己的朋友拷贝。
10. Alias Template
原来vector的默认分配器是allocator,现在这样写,就可以把分配器固定成MyAlloc
下面是使用模版函数,并且在函数中得到容器的类型
传给函数的,一定是一个对象。对于左边的这部分,传list, MyString, 显然是不行的
右边改动,选择传递一个对象进去list()(当然这里少了尖尖)、MyString()。但是这样也不对。编译器报错说Container不是一个template
(因为并没有结合起来得知这个容器里的类型,所以把他们两个结合一下!)
后面把它转为这样:
template + iterator + traits
容器拿到迭代器,迭代器通过traits得到value_type -> Valtype
那如果通过iterator和traits也无法得出呢?
用template template prameter!!
11. Template template prameter
以模版模版参数传入进去的时候,容器类的第一个参数是有的(看右上角),但是容器的第二个参数是没有办法推导出来的,特别是,这第二个参数值还是以第一个参数为参数,所以它推不出来。
解决办法:传Alias Template,它的第二参数是以第一参数为参数,所以这个绿色的东西只需要接收一个参数就可以了!
12. Type Alias
using
func 定义为一种函数指针
函数的名称就是地址,那这样直接赋值就是函数指针
using 可以代替typedef
之前的一些using的使用方法:
noexcept
在小括号情况下,这个函数是不会丢出异常的
爆出异常,一直没有人处理,往上一直走传递,如果没人处理就调用std::terminate(), 它里面有std::abort()
通知C++ move ctor 和 move assigenment 要声明noexcept 尤其是vector,
vector 在 grow 的时候, 搬到两倍大的地方
有move版本的,它调用的是move搬动,写了noexcepter vector 才会敢放心地去调用这个函数
链表是没有成长的
deque是一段一段的,你放数据也是两端,中间不会大幅度搬动
红黑树(是树,是节点,也不会大动的)、散列表(篮子下是链表,也不会大动)
override
所谓override,函数的签名是要完全一样的,就算是变量类型不一样,写错了。也是不一样的,他会认为你是新函数,而不是重载,因此他不会报错。但是如果你标明了override,他会给你提示。
final
(1)写了final 修饰class,就不可以再继承了
(2)如果是针对虚函数写了final,就不可以被override了
13. decltype
关键字
已经存在的typeof 是定义不完整的
所以c++11提出了decltype:通过对象取出type
它的应用分成三种:
(1) 用来宣告和声明一个return types
把+add 设计为一个模版函数
设计的时候完全不知道T1和T2是什么,说不定他们可以相加,说不定他们完全不能相加
decltype是找出一个表达式的类型(它先执行x+y,让编译器给推一下它是返回什么类型)
上面那样写是不对的,因为还不知道x,y是什么的,所以要按下面的写法。
先写成auto,最后指明是decltype(x+y)
这种指定方式(auto 、->)和lambda很像
(2)适用于元编程
函数模版,接受一个type P,下面通过obj得到这个对象的迭代器的类型
代替了使用 typedef typename T::iterator iType;
(注意有的对象没有迭代器,编译会不通过。模版只是半成品)
只要加上::,前面就要加上typename,帮助编译器确定这整个确实是一个typename,否则编译器会犹豫
(3)used to pass the type of lambda
用[]去表示lambda, 当我们需要这个函数的类型的时候
decltype(cmp)
14. Lambdas
lambda 定义一个inline的函数,并且可以用作为一个参数或者局部对象
lambda 改变了c++标准库的用法。
比如原来在调用排序算法的时候要放进去仿函数说明比较规则,现在放lambda就可以了
上面的没有意义,因为它作为临时的,也没有被调用。
中间加()的方式就是直接调用函数了。但是这和直接写里面那几行代码也没什么区别
下面的方式是好的,赋值给一个auto l,最后再用l来调用
mutable 可变的
没有返回类型,可以用->表示。
当三个optional参数有任何一个的时候,小括号就必须有。其他情况下需要有接收参数的时候才需要小括号
[…]里面放的是取用外部的变量,是传值??还是传引用
()里面放的是待新接收的参数
lambda是等同于匿名的函数对象
左边等同于右边
注意最后的调用结果。编译器在编译lamda的时候它是会认为id是0的,它不会再管你后面对id重新修改的值。
并且后面再次调用的时候,它用的已经是自己的id,在自己原来的基础上加。并且不会影响外部的id
如果不写mutable id是不能++的
如果传的是& 引用,会收到外界的影响。
可以不加mutable,注意它和外界此时就是同步了的。它还调用了参数。所以参数其实里面从7变成了9
最后右边的定义。函数是只能读不能写。
代替写成下面仿函数这样。
我们直接用lambda 来代替
在构造set的时候,传入比较函数的返回值类型。并且后面调用构造函数的时候,是调用那种带Compare的构造函数。可以用decltype 拿到 。
如果没有写出传的对象(cmp)的话,那么它就会默认调用它set类默认的Compare函数 less 函数
由于lambda奇特的语法,他并没有默认构造函数和赋值操作
如果!!!这样写
std::set<Person, decltype(cmp)>coll;
你把lambda放进来了,但是没有放后面参数进来,他就会调用set() 函数,后面紧接着会调用lamda的默认构造函数。但是lambda没有。
用lambda代替仿函数,仿函数太powerful了(在例子比较简洁的情况下)
lambda 是inline function 右边那样写不是inline
15-21. Variadic Templates
例1
(1)和 (3) 可以并存,(1)比较特化,(3)是永远不会调用起来的
例2
借用Variadic Templates 写 printf
根据后面的参数进行分割
分割之后用cout<< 进行丢
例3
设计一个函数__max_element,检测一堆数据里面最大的那个
这里是在说。如果要比较:参数个数不限的,类型都一样的,就用一个initializer_list就可以了
max(initializer_list里包含了initializer_list参数),他里面调用了max_element函数,把array的头尾指针传入了迭代器中。
__max_element里还调用了怎么样比大小的仿函数(函数对象)
例4
maximum
利用分解的形式把一包的成员和max函数结合起来
第一次调用,一和后面那一包还没法比,它是整个调用完然后返回去的
例5
类模版
以异于一般的方式处理first元素和last元素
使用sizeof…()获得元素个数
首先定义tuple,并且定义cout的操作符重载
第一个参数是ostream& os 操作符
第二个参数是那个tuple
下面调用了PRINT_TUPLE类,它的print里有递归地创建新的对象,并且给IDX+1,创建完对象后马上调用print。调用上面/下面的模版函数,下面的是边界。借由这种方式知道现在在处理第几个参数,我就知道我是不是在处理最初的/最后的元素
MAX在整个运作过程是不会变的
这里取出tuple参数是使用了get(t) -> tuple专用函数,然后打印,并且下一个输出什么?看它是不是最后一个!
注意最后一次创建对象 struct PRINT_TUPLE<MAX, MAX, Args…>时,它的print函数就定义为空了
例6
make_tuple 是一种方便的创建tuple的函数
也可以tuple<x, x, x, x> (x,x,x,x) 这样来创建
用于递归继承
1+一堆n,首先把第一个拿来声明变量,剩下的n拿来做继承
对于例子来说,
首先声明了41 然后把剩下两个拿来做继承
注意继承关系,子类对象内存里有父类的part在里面,它的父类的地址是要在它上面的
首先他们之间用private继承,因为他们之间并不是is a 的关系,只是为了要拿到内存的这一继承体系
把尾部的那一个包定义为inherited
tail() 是把自己转为 inherited类型
注意初始化列表中 inherited是调用父类的构造函数
但是在编译时是会出错的
Head::type int 、float、 string是没有type的
这样也可以,Head本身就是一个类型
私有继承:使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员,只可以在派生类的成员函数中使用
例7. 用于递归组合
可不可以内含,递归地组合?
typedef 了 tup<Tail…> 为 composited
把这个类定义为了tup
注意这里tail的返回一定要是composited& ,你拿到的仅仅是copy版本,如果后面要改值的话是改不到的
上面也要定义好空的tup<>{}
递归创建、递归继承、递归组合
template<> class tup<> {}; 这是模版特化。指定类型为空
23. Rvalue references
解决非必要的拷贝,可以去偷右边的内容。steal
临时对象也是右值
为什么对于string 右值还可以赋值?
complex 类也是如此,可以赋值
最常见的右值:临时对象
函数返回的东西是右边值,想要取它的地址,是会报错的
传进来的是右值引用(临时对象)的话,就会调用(2)
要偷的人是MyString,也就是这里的Vtype,偷的时候只是把那一块内存的地址变了过来
右值临时对象根本不会再用,就可以用move ctor
作为右值的这里的Vtype(buf),它是被偷了的,后面它就不能再用了
注意vector是一直这样往末尾插,如果要从前面插,他要往后推,那么就也要意味着做很多次不必要的构造和析构
左值怎么去move(steal)?
通过std::move(xxx) -> 就可以偷了
24. Perfect Forwarding
到了G4.9,有两个版本的insert。
一个是by reference的,一个是右值引用的
move 不仅要写 ctor,还要写assignment
Unperfect Forwarding
**forward(2) 、forward(move(a)) for进来的时候是右值,但是用这个i再去调用process的时候已经是左值了 **
forward(a) 左值是没有办法调用的
标准库中有一些散落的forward、move 可以很好地解决这个问题
用标准库的std::forward可以完美地转交
25. 写一个Move aware class
对于move ctor把指针赋值过来,长度也加过来
把原来对象的指针设置为NULL,并且给长度设置为0
注意不要删除对象,是要放到析构里做的
临时对象出了这个函数大括号 析构函数就给他杀了
析构函数时,因为被偷的已经是null了,所以不会影响data里的字符串内容
26. Move aware class 对容器的效能测试
关于对于容器,用或者不用move,对于vector影响最大,对于其他容器影响不大。除了对于deque要往里面insert的时候,要找到短的一边推一下。
容器以节点的形式存在,影响不大,它要移动就是真的要一个一个地移动
一个Vector只是由三根指针组成的
在move ctor vector的时候,它只是换了三根指针
copy ctor 是真的一个一个地在交换
28. 容器array
对于数组来说,没有ctor,dtor,所以array也没有这些
29-30. 容器Hashtable、Hashfunction
特化版本
创建hash对象并且()已经完成了操作符重载,相当于就返回了hashcode的结果
得到hashcode之后,再除以篮子个数,得到的余数就是它挂载哪个bucket上
上面是创建了三个临时对象
下面的是创建一个有名称的对象并调用三次hashcode的计算函数
声明了很多不同的特化版本:
不同特化的声明以及hash_code的计算方法
注意上方的声明都是在functional_hash.h文件中声明的
对于我们自己定义的类,或者一些非基础类,是要在他们自己的.h文件中写好hash的特化版本的
万用的Hash Function
里面还要结合Variadic Templates,并且用到了黄金分割
31. Tuple
(Variadic Templates 递归继承 递归组合)
这里讲的是运用
get<1>(t1) = get<1>(t2)
//可以直接拿tuple的元素做赋值
t1 = t2;
//也可以整个tuple赋值
cout << t1;
//注意要重载<<
tie(i1, f1, s1) = t3;
//这三个等于t3里的值