1、算法algorithm
(1)算法看不到容器,只能看到迭代器,通过迭代器去处理容器;根据迭代器的类型,判断怎么提供哪种最优的方式。
(2)Algorithm看不见容器,对其一无所知,所以,需要的信息都必须从迭代器中获得,因此迭代器必须可以回到算法的提问,才能搭配算法的所有操作;
1 迭代器的分类(category)
(1)array,vector,deque,都是随机访问迭代器;(连续的)
(2)list,set,multiset,map,multimap,都是双向访问迭代器;(不连续),forward list是单向访问迭代器;
(3)unordered (非不定序的)容器判断是双向还是单向就根据篮子的链表的性质;
(4)为什么不用12345来表示5种迭代器类型,而用5种结构?
A、利用函数重载一条语句就可以对应到上方函数,简洁明了;
(5)只有ostream_iterator的是output_iterator;
(6)利用标准库的typeid操作符,打印出系统定义的迭代器名称类型;
(7)编译器之后,会增加名称前缀;
1.2 istream_iterator的iterator_category
(1)不同的实现但要有相同的接口;
(2)继承父类,但是父类只有typedef没有别的数据;为了少打几行typedef;
1.3 ostream_iterator
2 迭代器分类(category)对算法的影响
2.1 distance()
(1)算法需要知道迭代器的分类;
(2)distance函数是计算两个指针之间的距离,调用重载函数__distance,输入迭代器和随机访问器都不同处理;同时返回值是两迭代器距离类型的值,问萃取机iterator_traits中的5个之一difference_type是什么;
(3)cataegory()创建临时对象,作为第三参数进行函数重载调用;
(4)很多都采用,一个主函数调用分函数重载的形式;
2.2 advance()
(1)注意,__advance返回值是void,采用的引用方式,
(2)为什么不用12345来表示5种迭代器类型,而用5种结构?
B、可以充分利用类继承的方便;
2.3 copy(),原地址的头尾区间+目的地指针
(1)过程,首先如果是字符串,采用C字符串的拷贝memmove()简单快捷,否则进入第一个泛化,再次检查是否为指针类型,否则进入第二个泛化,检查是否为随机访问迭代器,执行循环搬运时会调用拷贝赋值,for的检查条件是n<直接相减得到,如果是输入访问迭代器,搬运时for条件就是每次都要判断是否到last()效率较低,回过头看指针,判断是否重要,不重要执行memmove(),重要的话同随机访问一样;
(2)copy()设计精细以最大限度提高算法效率;
2.4 destory(),摧毁一个对象,调用析构函数
算法的效率与迭代器的分类判断有很重要的关系!!!
2.5 __unique_copy(),只copy独一无二的值
(1)__下划线一般不是主函数,内部调用的子函数
(2)如果不对Outputiterator进行编写,就会到forwarditerator,中有读操作,因此有一个专门针对output_iterator,避免有read操作;
(3)算法对迭代器没有强制要求,语法没有这种限制功能,只有暗示;像sort需要randomiterator,distance()input就可以;
3 算法的源代码剖析(11个例子)
3.1 accumulate—累计
(1)accumulate有两个版本,增加第四个参数,对应不同的操作;
3.2 for_each()---指定范围内对元素做同一操作
(1)右下角应用了for()范围运算,在C++11新增的;
3.3 replace,replace_if,replace_copy
(1)prodicate是模板参数,叫什么都可以,但是有暗示,predicate就是判断式返回真假
3.4 count,count_if
(1)左边是全局算法,右边是容器中有同名的成员函数;
3.5 find,find_if
(1)右边自带find的容器都有自己更高效的查找find方法;
3.6 sort()
(1)rbegin(),rend()逆向排序;
(2)sort()要求随机访问迭代器,而List,forward_list不符合,有自己的成员函数,框起来的容器本身就是排序好的,不需要sort();
3.7 binary_search—必须是排序好的容器
(1)lower_bound帮我找到不破坏排序安插下去的最低位置;upper_bound为最高位置;
此处之后开始学的比较吃力,作为标记,以后可以再次温习
2、仿函数functors
4.1 仿函数---只服务于算法,使用仿函数作为特有的参数指定特定操作
STL六大部件:容器;分配器;算法;迭代器;适配器;仿函数;
(1)仿函数必须重载()操作符;
(2)仿函数只服务于算法;
(3)GC独有的一些仿函数;
(4)sort()函数,第三个参数就是仿函数,实现不同的功能;
(5)标准库中仿函数继承的基类,binary_function和unary_function;如果自己写的仿函数没有继承基类,就没有融合到库中;
基类就是接收模板参数,并给模板参数赋一个简单名字,基类大小为0,实际是1,但是如果真的被继承时,大小就是0;
(6)仿函数可以被修改的条件就是继承合适的基类,用于回答问题;仿函数适配器要问问题,仿函数回答问题;
(7)仿函数就是一个class重载了()运算法,称为函数对象;是一个对象,但像一个函数;
3、适配器adapter
5.1 多种适配器adapter(改造器)---换皮肤,小工程
(1)修改既有的一个东西,可能是容器,函数,迭代器;浅蓝色的框框;
(2)adapter是一个包装后的一个桥梁;使用原有的好东西,继承或者包含,这里都是包含;
(3)适配器与内含的东西也是通过提问题和回达问题;
(4)stack和queue都被称为容器适配器,它们都内含一个sequence,默认是一个deque;下图中是对已有函数的改名,也是一种改造;换成stack先进后出,queue先进先出的特性;
5.2 函数适配器:binder2nd
(1)适配器去修改一个仿函数,自己也要变成仿函数的形式;重载()操作符;
(2)适配器通过建立一个大类实现,但类的调用比较复杂,因此用一个辅助函数来包装大类;
(3)这个类的构造函数,会将仿函数的参数代入构造函数,形成自己的数据,然后开始()重载实现,这个过程需要知道仿函数的参数类型以及返回类型,也就是typename的三个提问,因此必须仿函数必须继承基类(包含3个typename)才能回答适配器的问题,被适配器改造;同样,适配器也是一个仿函数,也需要继承基类;
(4)仿函数可适配的条件就是继承基类(3个typename);
(5)在提问中,不仅仅需要作用域解析,而且要加上typename以通过编译;
(6)其中在辅助函数中,利用形参自动推导在binder2nd<Operation>中的Operation判断类型,注意这里是建立大类的一个对象;开始调用真正的大类;
(7)真正调用大类重载()的地方在count_if()中,第三参数其实是大类的一个对象(仿函数),在pred(*first)此处进行()重载,是一个对象,行为像一个函数;
5.3 函数适配器:not1
5.4 新型适配器bind合并了bind1st,bind2st
(1)binder2nd是辅助函数,bind2nd是一个大类;
5.5 仿函数适配器bind
(1)函数对象function objects,就是一个重载了()的类,因为是一个对象,但行为像一个函数;
(2)bind可以绑定函数,函数对象,成员函数,数据成员;
(3)_1,_2就是占位符,当bind(my_divide,_2,_1)时,改造了顺序,(10,2)=0.2;
(4)bind<int>(my_divide,_1,_2)这里绑定了一个模板参数,会改变return type,会返回int;
(5)MyPair ten_two {10,2};创建对象并赋初值;
(6)自己推导返回类型复杂,就用auto代替;
(7)在绑定成员函数时,有一个参数,this,因此有一个_1;之后调用就传入对象,bound_memfn(ten_two);
(8)之后直接绑定ten_two,就不用在传参数;没有绑定,调用的时候就要传参数;
5.6 迭代器适配器reverse_iterator
(1)reverse_iterator是一个迭代器适配器,所以它一定会包含一个迭代器就是正向迭代器;
(2)重点在于取值,对逆向取值就是对正向迭代器退一位取值;
5.7 迭代器适配器inserter
(1)copy是不管目的端够不够的问题,所以这里提前预留了7个位置;
(2)inserter就不需要担心这个问题,就负责内存的问题;inserter(foo,it)就是利用了inserter插入迭代器适配器,前面advance(it,3)是将it前进三个位置,指向4;
(3)inserter是辅助函数,方便使用,实现大类中有准备data来放置传入的两个参数,
(4)重点在于=的重载,copy不动,通过=重载来改变行为;调用容器的insert操作;
5.8 X适配器:ostream_iterator
(1)ostream_iterator和istream_iterator不属于容器函数迭代器,称X迭代器;
(2)copy的代码不变,通过不同的迭代器中不同的操作符重载来实现不同的功能;
(3)delimiter就是分隔符的意思;(4)通过重载=将元素和分隔符丢到cout中;
5.9 X适配器:istream_iterator
(1)重载前置++操作符;
(2)创建一个对象eos,只要没有参数,就代表一个end-of-stream iterator;作为一个标记进行比对;当等于eos时,就表示结束了;
(1)注意,在创建对象的时候,构造函数就已经调用++重载开始读取第一个数据了!;
4、C++STL周边的知识点
4.1 一个万用的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;
4.1.2 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的来由;黄金比例
4.1.3 hash_function测试
4.1.4 新版本对基本类型都有hash_function
(1)模仿右侧标准库中对string的hash_function对自己的类也写一个hash<T>
4.2 Tuple用例----一堆东西的组合,标准库中的
(1)string是一个指针,为4字节在32位电脑上,complex<double>就是复数,大小16;
(2)get<0>(t1)表示取出t1的第一个元素;make_tuple是创建一个tuple对象并初始化;
(3)可以比大小,赋值;说明进行了重载;
(4)装任意数量,任意类型的数据;填入模板参数;
(5)tie(il,f1,s1)=t3;是将t3中的三个变量赋值到或者链接到il,f1,s1,;
(6)tuple_size<TupleType>::value(提问的方式),知道value的值;
(7)tuple_element<a,TupleType>::type 也是提问的方式,typedef给T;
4.2.2 Tuple的实现 ---元之组合,数之组合
(1)这里也用到了…对于任意数量,任意类型的模板参数;
(2)分为Head,Tail…;接收任意类型的Head和任意数量类型的尾巴;看右侧,变成继承尾巴形成自己,5个继承4个,4个继承3个,3个继承2个…自动递归,自动继承;
(3)对于head就声明出一个变量,protected:Head m_head;
(4)当是0是,有一个全特化,template<>class tuple<>{};结束继承;
(5)所以它的大小应该是自己+所有父类大小,所以是12;但那个32暂时无法理解;
(6)head()会得到第一个参数,tail()会得到父类成分的起点;返回类型inherited就是前面typename尾部形成的一个tuple;
(7)Tuple是依次继承运行的;
4.3 type traits---萃取机
(1)之前接触过是iterator_traits;迭代器萃取机
(2)默认为false_type就是重要,trivial是不重要的意思;默认情况下,假设所有类型都重要,但是有的类型确实不重要(赋值,复制构造,析构等),因此有特化版本;
(3)新版本变得强大了,不需要自己对自己的类写一个type traits;
4.3.2 type traits测试
(1)对string的测试,原名为basic_string,而且析构函数并不是虚函数,说明本意不应该将string作为一个基类;
(2)测试Foo(自己构建的类)
(3)测试Goo(自己构建的类),有一个虚析构函数
其中has_virtual_destructor是肯定的,自己判断出来的;
(4)测试Zoo
4.4 type traits的实现
4.4.1 is_void实现
(1)新版本不需要用户写偏特化配合;
(2)利用模板中的typedef;
(3)remove_const是泛化版本,remove_const<__Tp const>是偏特化;拿掉const,下面示例的偏特化用于拿掉volatile;其实是用typedef作为提问的回应;
(4)先调用remove_cv拿掉const和volatile再调用helper回答真假,默认泛化为假,如果传入void则为真;
4.4.2 is_integral实现—是否为整数类型
(1)泛化版本是false-type,偏特化都是true_type;
4.4.3 is_class,is_union,is_enum,is_pod实现
(1)之前都是利用泛化和偏特化实现;
4.4.4 is_move_assignable实现
4.5 cout---一个对象
(1)extern说明可以被外界使用;cout是_IO_ostream_withassign类型,继承ostream,ostream虚继承ios;
(2)标准库中一些操作符<<重载如下;
4.6 moveable元素对于vector速度效能的影响
(1)上图中,300万个Moveable元素放入vector容器中,调用移动构造函数700多万次,下方测试300万个non-moveable元素放入vector容器,调用赋值构造函数700多万次;
(2)因为两倍增长,把所有元素全部搬移,引发复制构造/移动构造,所以是700多万次;
(3)接着有对于List,deque,multiset差别不大,因为其链表或者红黑树结果,插入方便;没有vector不断增长两倍空间,不断搬移的情况;
(4)其具体差异多少与当前内存破碎或连续情况而定;
4.7 一个moveable class
(1)以上两张投影片是程序连续的;
(2)“浅拷贝”/转移所有权就是move动作!move和copy就只差了一点&&(reference of reference);上图中灰色部分都是Move动作,与其余copy动作对比;如灰色部分右图,转移所有权!!!
(3)由于浅拷贝,析构函数增加了对于指针的检查!
4.8 测试函数
4.8.1 vector的copy ctor(深拷贝)
4.8.2 vector的move ctor(浅拷贝)
4.8.3 string是否有moveable版本(有&&就是)