泛型算法
-
实现经典算法的公共接口,可以用于不同类型元素和多种容器类型(标准库类型、内置数组类型)。
-
算法不直接操作容器,而是遍历由两个迭代器指定的元素范围来进行操作。
-
运行于迭代器之上,执行迭代器的操作。
①find、accumulate、equal,只读算法
②fill、fill_n,写操作
③copy,拷贝操作
④replace、replace_copy,拷贝操作
⑤sort、unique、stable_sort、elimDups,重排容器元素算法
⑥find_if、for_each、make_plural,查找算法 -
操作两个序列,算法接受三个迭代器,前两个表示第一个序列的范围,第三个表示第二个序列的首元素,或接受四个迭代器,后两个表示第二个序列的范围。
-
用单一迭代器表示第二个序列的算法都假定第二个序列至少跟第一个一样长。
-
back_inserter,写容器的算法,算法不检查写操作。
①接受指向容器的引用,返回与该容器绑定的插入迭代器,当调用此迭代器赋值,赋值运算符会调用push_back将给定值元素添加到容器中。
②当写入长度超过迭代器长度,使用插入迭代器能保证算法有足够空间容纳输出数据。
定制操作
-
向算法传递函数,谓词是一个可调用的表达式,其返回结果是能用作条件的值。
-
接受谓词参数的算法对输入序列中的元素调用谓词,元素类型必须能转换为谓词的参数类型。
-
谓词分一元谓词和二元谓词,一元谓词即调用对象的参数为一元。
lambda表达式
-
如果可以对一个对象或表达式使用调用运算符,称为可调用对象。
-
函数和函数指针是常见可调用对象,重载了函数调用运算符的类以及lambda也是可调用对象。
-
lambda表达式表示一个可调用的代码单元,理解为一个未命名的内联函数,表示为:
①[capture list] (parameter list) -> return type { function body }
②capture list捕获列表,是lambda所在函数块中定义的非static局部变量列表。
③函数体可使用局部static变量和定义在当前函数之外的变量。
④必须使用尾置返回指定返回类型。
⑤忽略括号和参数列表时指定一个空参数列表。
⑥忽略返回类型可根据函数体中代码推断出返回类型,否则返回类型为void。 -
当向一个函数传递一个lambda时,同时定义了一个未命名新类型和该类型的对象,传递的参数即该未命名新类型的对象。
①默认情况下,lambda生成代码的捕获变量类似普通类的数据成员。
②lambda数据成员在lambda对象创建时初始化。 -
类似参数传递,变量的捕获方式可以是值或引用。
①采用值捕获的前提是变量可以拷贝。
②与参数传递不同,捕获变量的值在lambda创建时拷贝,不是调用时拷贝,之后修改局部变量的值不影响lambda捕获的变量值。
③使用引用捕获时,须确保引用对象在lambda执行时是存在的,如果lambda在函数结束后执行,引用捕获的局部变量已经消失。 -
可变lambda
①默认情况下lambda不会改变拷贝的变量,如果希望能改变捕获变量的值,必须在参数列表后加上关键字mutable。 -
指定lambda返回类型
①lambda是单一的return语句时,无须指定返回类型,可以根据return类型推断出来。
②当return语句不单一时需要为lambda定义返回类型时,必须使用尾置返回类型。
隐式捕获
-
除了显式捕获需要使用的变量,编绎器可以根据lambda体中的代码推断使用哪些变量。
-
捕获列表中,&采用引用捕获方式,=采用值捕获方式。
-
可混合隐式捕获和显式捕获,混合捕获时列表中的第一个元素必须是&或=,且显式捕获方式与隐式捕获方式必须不同。
①[],空捕获列表,lambda不能使用所在函数块中的变量。
②[names],names是逗号分隔的名字列表,是lambda所在函数块的局部变量,默认情况下,捕获列表中的变量都被拷贝,如果名字前使用了&,则采用引用捕获方式。
③[&],隐式捕获列表,采用引用捕获方式。
④[=],隐式捕获列表,采用值捕获方式。
⑤[&, identifier_list],identifier_list是逗号分隔的列表,包含0到多个所在函数变量,变量采用值捕获方式,名字前不可使用&,隐式捕获变量采用引用捕获方式。
⑥[=, identifier_list],identifier_list中的变量采用引用捕获方式,列表中不可包含this,且名字前必须使用&,隐式捕获的变量采用值捕获方式
参数绑定
-
bind函数看成一个通用的函数适配器,接受一个可调用对象,生成新的可调用对象并适应原对象的参数列表。
①auto newCallable = bind(callable, arg_list);
②newCallable是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应callable的参数。
③当调用newCallable时,newCallable会调用callable,并传递给它arg_list中的参数。
④arg_list中的参数可能包含形如_n的占位符,表newCallable中参数的位置。
⑤调用newCallable时实际调用callable,用newCallable参数替代占位符。_1占位符定义在std::placeholders命名空间中。 -
默认情况下,bind参数列表中不是占位符的参数以拷贝方式绑定。
①当绑定的参数需要以引用方式传递时,可使用lambda引用捕获方式。
②bind使用标准库ref或cref函数,例对os的引用时使用ref(os),ref返回一个对象包含给定的引用。
迭代器
- 插入迭代器、流迭代器、反向迭代器、移动迭代器。
插入迭代器
- 插入迭代器是一种迭代器适配器,它接受容器生成迭代器,实现向给定容器添加元素。
①back_inserter创建一个使用push_back的迭代器,容器需要支持push_back。
②front_inserter创建一个使用push_front的迭代器,容器需要支持push_front。
③inserter创建一个使用insert的迭代器,函数第二个参数必须是指向给定容器的迭代器,元素插入到迭代器指向元素之前,返回指向原来元素的迭代器。
④front_inserter元素插入到容器第一个元素之前。
iostream迭代器
-
创建流迭代器时必须指定迭代器要读写的数据类型,将流迭代器绑定到一个流上。
-
迭代器可默认初始化,当作尾后迭代器使用。
-
流迭代器使用>>运算符读取数据、<<运算符写入数据,流迭代器的数据类型必须定义了<<和>>运算符。
-
istream_iterator读取输入流,允许使用懒惰求值,当迭代器绑定到流时,标准库不一定立即从流读取数据,可以推迟从流中读取数据流,直到使用迭代器时才真正读取。
①istream_iterator<T> in(is); in从输入流is读取类型为T的值。
②istream_iterator<T> end; 读取类型为T的istream_iterator迭代器,表尾后位置。
③in1 == in2, in1 != in2; in1和in2读取类型相同,或都是尾后迭代器,或绑定到相同的输入,则相等。
④*in; 返回从流中读取的值,在第一次解引用迭代器之前,标准库确保从流中读取数据的操作已经完成。
⑤in->mem; 与(*in).mem含义相同。
⑥++in, in++; 使用流所定义的>>运算符从输入流中读取下一个值,前置版本返回指向递增后迭代器的引用,后置版本返回旧值。 -
ostream_iterator写入输出流,必须将ostream_iterator绑定到指定的流,不允许空的或表示尾后位置的流迭代器。
①ostream _iterator<T> out(os); out将类型为T的值写到输出流os中。
②ostream _iterator<T> out(os, d); out将类型为T的值写到输出流os中,每个值后面都输出一个d,d指向一个空字符结尾的字符数组。
③out = val; 用<<运算符将val写入到out所绑定的ostream中,val的类型必须与out可写类型兼容
④*out, ++out, out++; 运算符存在,但不对out做任何事情,每个运算符都返回out。
反向迭代器,forward_list不支持
-
标准容器上的其他迭代器都支持递增和递减运算,流迭代器不支持递减运算。
-
反向迭代器可通过reverse_iterator的base成员函数转换成普通迭代器。
①rbegin()/rend()/crbegin()/crend()。 -
普通迭代器与反向迭代器都反映了左闭合区间特性。
①rcomma和rcomma.base()必须生成相邻位置而不是相同位置。
泛型算法结构
-
算法所要求的迭代器操作可分为5个迭代器类别。
-
输入迭代器,只读,不写,单遍扫描,只能递增,支持:
①比较两个迭代器的相等和不相等运算符(==、!=)。
②推进迭代器的前置和后置递增运算(++)。
③读取元素的解引用运算符( * ),只出现在赋值运算符的右侧。
④箭头运算符(->)等价于(* it).member,解引用迭代器并提取对象的成员。
⑤例算法find、accumulate要求输入迭代器,istream_iterator是一种输入迭代器。 -
输出迭代器,只写,不读,单遍扫描,只能递增,输出迭代器只可赋值一次,支持:
①推进迭代器的前置和后置递增运算(++)。
②解引用运算符( * ),只出现在赋值运算符的左侧。
③例:算法copy函数第三个参数就是输出迭代器。
④ostream_iterator是一种输出迭代器。 -
前向迭代器,可读写,多遍扫描,只能递增。
①可读写,在序列中沿一个方向移动,支持所有输入和输出迭代器的操作,可多次读写同一个元素,可保存前向迭代器的状态,使用前向迭代器的算法可以对序列进行多遍扫描。
②算法replace要求前向迭代器,forward_list上的迭代器是前向迭代器 -
双向迭代器,可读写,多遍扫描,可递增递减。
①可以正向/反向读写序列中的元素,支持所有前向迭代器的操作,支持前置和后置递减运算符。
②算法reverse要求双向迭代器,标准库提供除forward_list之外所有容器符合双向迭代器要求的迭代器。 -
随机访问迭代器,可读写,多遍扫描,支持全部迭代器运算。
①提供在常量时间内访问序列中任意元素的能力,支持双向迭代器所有操作,并支持:
②比较两个迭代器相对位置的关系运算符 (<、<=、>、>=)。
③迭代器和一个整数值的加减运算。
④两个迭代器上的减法运算符得到两个迭代器的位置。
⑤下标运算符iter[n]与*(iter[n])等价。
⑥算法sort要求随机访问迭代器,array、deque、string、vector的迭代器都是随机访问迭代器,用于访问内置数组元素的指针也是。
算法形参模式
-
大多数算法具有以下4种形式之一:
①alg(beg, end, other args);
②alg(beg, end, dest, other args);
③alg(beg, end, beg2, other args);
④alg(beg, end, beg2, end2, other args); -
alg是算法名字,beg和end表示算法所操作的输入范围,dest参数表示算法可写入目标位置迭代器。
-
向输出迭代器写入数据的算法都假定目标空间足够容纳写入的数据,接受单独beg2的算法假定从beg2开始的序列与beg和end所表示的范围至少一样大。
算法重载
-
算法使用重载形式传递谓词。
①unique(beg, end); 使用==运算符比较元素。
②unique(beg, end, comp); 使用谓词comp比较元素。
③_if版本算法。
④find(beg, end, val); 查找输入范围内val第一次出现的位置。
⑤find_if(beg, end, pred); 查找第一个令pred为真的元素。 -
区分拷贝元素版本和不拷贝元素版本。
①reverse(beg, end); 反转输入范围内元素的顺序。
②reverse_copy(beg, end, dest); 将元素逆序拷贝到dest。 -
部分算法同时提供_copy和_if版本。
特定容器算法
-
链表类型list和forward_list定义了独有的成员函数形式算法,sort、merge、remove、reverse和unique,通用版本sort要求随机访问迭代器,不可用于链表。
①lst.merge(lst2); lst.merge(lst2, comp); 将来自lst2的元素合并入lst,lst和lst2有序,第一个版本使用<运算符合并,第二个版本使用给定的比较操作,合并后lst2为空。
②lst.remove(val); lst.remove_if(pred); 调用erase删除与给定值相等或令一元谓词为真的所有元素。
③lst.reverse(); 反转lst中的元素顺序。
④lst.sort(); lst.sort(comp); 使用<或给定比较操作排序元素。
⑤lst.unique(); lst.unique(pred); 调用erase删除同一个值的连续拷贝,第一个版本使用==,第二个版本使用给定的二元谓词
⑥lst.splice(args)或flst.splice_after(args)是链表数据结构特有。 -
splice参数列表:
①(p, lst2),p是指向lst中元素的迭代器,或指向flst首前位置的迭代器,函数将lst2的所有元素移动到lst中p之前或flst中p之后的位置,将元素从lst2中删除,lst2的类型与lst和flst一致,且不能是同一个链表。
②(p, lst2, p2),p2是指向lst2中位置的有效迭代器,将p2指向的元素移动到lst中,或将p2之后的元素移动到flst中,lst2与lst或flst可以是相同链表。
③(p, lst2, b, e),b和e表示lst2中的合法范围,将给定范围中的元素从lst2移动到lst或flst,lst2与lst或flst可以是相同链表,但p不可指向范围内元素。 -
链表特有版本与通用版本间最重要的区别是链表会改变底层的容器。