第一讲 语言 第一节 头文件header files
(1)标准库都在std名称空间中,全名为std::+名字;
(2)旧版本就是#include
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)编译器本身就具备自己判断类型的能力;  (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
(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
(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
!
!
(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
(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
第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;
