(十)泛型算法

1概述

1.1定义

  • 标准库提供了一组算法,这些算法大豆独立于任何特定容器,但具有通用性(泛型)。
  • 标准库定义了一组泛型算法:

算法:实现经典算法的公共接口
泛型:可以用于多种元素类型和容器

1.2算法所在头文件

algorithm
numeric //数值泛型算法

1.3泛型算法操作

  • 一般情况下,算法不直接操作容器,而是遍历由两个迭代器指定的一个元素范围来进行操作。返回的也是迭代器。
  • 对于处理序列的子范围情况,可以使用指向子范围位置的迭代器(指针)进行操作。
  • 迭代器令算法不依赖于容器,但依赖于元素类型的操作。
  • 泛型算法不会改变底层容器的大小。可能改变容器内元素的值或者位置,但不会增删元素。

2根据元素操作方法进行算法分类

  • 大多数算法会对一个范围内的元素进行操作(输入范围),该范围用迭代器表示。遍历输入范围的方式近似,但操作元素方法不同,分为只读、改变元素和重排元素等。

2.1只读算法

  • 只读取输入范围内的元素,不改变元素。
> find
> count
> accumulate
> equal 

2.1.1 accumulate

  • 根据第三个参数的类型决定函数使用哪个加法运算符及返回值类型。
int sum = accumulate(vec.cbegin(),vec.cend(),0);//0为求和起点。
string sum = accumulate(v.cbegin(),v.cend(),string("")); //将vector中所有string连接起来,第三个参数显式创建了一个string 
string sum = accumulate(v.cbegin(),v.cend(),"")//第三个参数为空串是非法的,此时对象类型为const char*,没有+运算符

2.1.2 equal

  • 确定两个序列是否保持相同的值,都相同返回true,否则返回false。
  • 可以通过equal比较两个元素类型不同的容器中的元素,只要能用“==”比较。
  • 要求比较部分第二个序列长度大于等于第一个序列长度

对于只接受单一迭代器的第二个序列算法,假定第二个序列长度大于等于第一个序列长度。

equal(v1.cbegin(),v1.cend(),v2.cbegin());

2.2写容器元素算法

  • 将新值赋予给序列中元素,此时要确保序列原大小不小于算法要求写入的元素数目。(算法不会改变容器大小)
  • 迭代器不会检查写入数据位置大小是否足够大
> fill(beg,end,val) //将val赋给输入序列每个元素
> fill_n(dest,n,val)//从dest开始n个元素赋值为val
> back_inserter
> copy
> replace
> replace_copy

2.2.1back_inserter

  • 如果相对空容器上调用fill_n,可以使用插入迭代器,保证算法具有足够空间容纳输出数据。插入迭代器是向容器中添加元素的迭代器。
  • 头文件:iterator
vector<int> vec;
fill_n(back_inserter(vec),10,0);//通过back_inserter创建迭代器,插入10个元素到vec

2.2.2copy

  • 向目的位置迭代器指向的输出序列中元素写入数据。
  • 可以用copy实现内置数组的拷贝
   	auto iter = copy(beg,end,dest);
	int a1[] = { 1,2,3,4 };
	int a2[sizeof(a1) / sizeof(*a1)]; //计算a1中元素个数
	auto ret = copy(begin(a1), end(a1), a2); //a1拷贝到a2,ret返回end(a2)

2.2.3replace

> replace(beg,end,val1,val2); //将输入序列中val1的值替换为val2的值。
> replace_copy(beg,end,back_inserter(ivec),val1,val2); //原序列不变,将序列拷贝到Ivec,将ivec中val1的值替换为val2的值。

2.3重排容器元素算法

sort(beg,end); //利用<运算符实现排序
unique(beg,end); //重排,不重复元素排在vector开始部分,返回不重复区域之后一个位置的迭代器
	string s = "hhdyzwhy";
	cout << s << endl;  //hhdyzwhy
	sort(s.begin(), s.end());
	cout << s << endl; //dhhhwyyz
	auto iter = unique(s.begin(),s.end());
	cout << s << endl;//dhwyzyyz
	s.erase(iter, s.end());
	cout << s << endl; //dhwyz
	return 0;

3定制操作

3.1向算法传递函数

  • 可以重载sort的默认排序操作:
sort(beg,end,comp);
partition(beg,end,comp);//符合条件在前半段,不符合在后半段
  • 其中第三个参数comp为谓词(predicate)

3.1.1谓词

  • 谓词分为两种,一元谓词和二元谓词,分别接受一个参数和两个参数。
  • 谓词可调用表达式,返回结果是作为条件的值
//sort中可以设计一个二元谓词,重载sort函数,改变排序方法
	bool isshorter(const string s1, const string s2) {
		return s1.size() < s2.size();
	}
	sort(strs.begin(), strs.end(), isshorter);

3.1.2 stable_

  • stable_sort内部实现是归并排序,sort是快速排序。
  • stable_sort算法是稳定排序算法,维持了相等元素的原有顺序
	bool letterbiggerthanh(char a) { //partition接收一元谓词
	return a > 'h';
	}
	string s = { "hhdyzuhyaaa" };
	partition(s.begin(), s.end(), letterbiggerthanh);
	cout << s << endl; //yuzydhhhaaa改变字典序
	s = { "hhdyzuhyaaa" };
	stable_partition(s.begin(), s.end(), letterbiggerthanh);
	cout << s << endl; //yzuyhhdhaaa不改变字典序

3.2可调用对象

  • 对一个对象或表达式,可以对其使用调用运算符(一对圆括号()),则称它是可调用的。调用运算符中防置实参列表。
  • 可调用对象有:函数、函数指针、重载调用运算符的类、lambda表达式等。

3.3lambda表达式

  • 对于函数,严格接收一元或二元谓词,然而考虑到有些操作可能需要更多的参数,引入lambda表达式
  • lambda表达式是可调用的代码单元,可以理解为未命名的内联函数。

3.3.1 lambda表达式定义

> [capture list](parameter list) ->return type{function body}
  • lambda表达式定义时必须包含捕获列表和函数体。
> find_if(beg,end,comp)  //找第一个符合条件的
> for_each(beg,end,comp) //对每个进行判定
  • 定义过程:定义lambda,编译器生成yulambda对应的新的未命名类类型。包含一个新类型和该类型的一个对象:传参为类类型的未命名对象。
3.3.1.1 参数列表
  • lambda表达式不能有默认参数,lambda调用实参数目与形参数目相等。
	bool isshorter(const string s1, const string s2) {
		return s1.size() < s2.size();
	}
	//改为lambda表达式
	[](const string s1, const string s2){return s1.size() < s2.size();}
3.3.1.2 捕获列表
  • 使用函数中明确指明的局部变量。例如将string的size大小与捕获sz作比较:

	//改为lambda表达式
	[sz](const string s1){return s1.size() >= sz;}
  • 只有捕获的局部变量才能在lambda对应函数体中使用。
  • lambda生成的类包含对应lambda捕获变量的数据成员,并在lambda对象创建时初始化。
  • 值捕获引用捕获

值捕获:值捕获前提是变量可以拷贝,被捕获变量值在创建时拷贝,而不是调用时,常用于普通变量捕获;
引用捕获:使用的是引用所绑定的对象,有些对象引用捕获是唯一方式(捕获os),要确保绑定对象存在且具有预期的值。

  • 构造捕获列表方式如下:
构造方式说明
[ ]空捕获列表。不使用lambda所在函数里的局部变量
[names]逗号分隔开的名字列表,是lambda所在函数里的局部变量,默认背靠背,如果名字前加了&,则表示引用捕获
[&]隐式捕获列表,引用捕获方式。
[=]隐式捕获列表,值捕获方式。
[&,identifier_list]混合使用隐式捕获和显式捕获。隐式捕获为引用捕获或,显示捕获为值捕获
[=,identifier_list]混合使用隐式捕获和显式捕获。隐式捕获为值捕获或,显示捕获为引用捕获
  • 可变lambda

对于值被拷贝的变量,lambda不会改变其值(不是左值),如果希望能改变一个被捕获变量的值,要在参数列表首加上mutable

int v1 = 42;
auto f = [v1]()mutable {return ++v1; }; //加mutable,v1可以修改,v1被拷贝传递进去,此时值为42
v1 = 0;
cout << f() << endl; //43
cout << v1 << endl; //0
v1 = 42;
auto f3 = [v1](){return ++v1; }; //报错,v1不是可修改左值
v1 = 0;
cout << f3() << endl;
cout << v1 << endl;
v1 = 42;
auto f2 = [&v1]() {return ++v1; }; //引用v1
v1 = 0;
cout << f2() << endl; //1
cout << v1 << endl; //1
return 0;
3.3.1.3 返回类型
  • lambda必须使用尾置返回。
  • 当编写lambda只包含单一return语句时,编译器可以推算返回类型。当lambda函数体包含除return之外任何语句时,编译器假定此lambda返回void。
	transform(s.begin(), s.end(),s.begin(), [c](char s_c) {return (s_c > 'h'? s_c : 'h'); });
	std::cout << s << endl;
	transform(s.begin(), s.end(), s.begin(), [](char s_c) ->char{if (s_c > 'h') return s_c; else return 'h'; });
	std::cout << s << endl;
	transform(s.begin(), s.end(), s.begin(), [](char s_c) {if (s_c > 'h') return s_c; else return 'h'; });  //这个版本按理应当错误,但在vs2022可以通过
	std::cout << s << endl;
3.3.1.4 函数体
  • 只对lambda所造函数中定义的非static的局部变量使用捕获列表,lambda可以直接使用局部static变量和它所在函数之外声明的名字,如定义在头文件中的名字。

3.3.2 调用lambda表达式

  • 可以使用调用运算符调用lambda表达式。
auto f = []{return 42;};
//auto f = []() -> int {return 42; };  //全写
cout<<f()<<endl;  //42
  • 泛型编程中作为comp:
for_each(iter, strs.end(), [](const string& a) {cout << a << " "; });
auto iter = find_if(strs.begin(), strs.end(), [sz](const string& a) {return a.size() >= sz; });

3.4bind函数

  • lambda适用于在少数地方使用,且函数体语句数目少的情况。
  • 其他情况,如果lambda捕获列表为空,可以用函数替代。
    • 对于需要捕获局部变量lambda,且使用函数表达的情况:
bool check_size(const string &s,string::size_type sz){
	return s.size()>=sz;
}
  • 如果使用find_if调用check_size函数,考虑find_if只接受一元谓词,需要解决向sz形参传递参数的问题。可以使用bind函数
3.4.1定义
  • bind函数可以看作一个通用函数适配器,他接受一个可调用对象并生成一个新的可调用对象适应原对象参数列表。
  • 头文件:
#include<functional> 
  • 函数形式:调用newCallable时,newCallable调用callable,向callable传递arg_list中的参数。
auto newCallable = bind(callable,arg_list); 
newCallable:可调用对象
callable:目标调用对象
arg_list:逗号分隔参数列表,对应callable参数。
3.4.2占位符_n
  • arg_list中可能出现**_n**,其中n是整数,表示可调用对象中参数位置。这个参数是占位符,占据该位置,可以在函数调用时输入该参数。
  • 名字_n定义在std::placeholders命名空间中,该命名空间在头文件#include<functional>中。使用_n需要分别对对应名字进行声明,或直接声明对应命名空间:
using std::placeholders::_1; //分别对对应名字进行声明
using namespace std::placeholders; //直接声明对应命名空间
//eg:bind绑定check_size,并使用find_if调用
bool check_list(const string& a, int b) {
	return a.size() > b;
}
int main(){
vector<string> strs = {...};
int sz = 6;
auto checksz = bind(check_list, placeholders::_1, sz);
auto iter = find_if(strs.begin(), strs.end(), checksz);
}
  • bind参数绑定

调用对象通过bind绑定给定可调用对象中的参数时,可以重新安排其顺序。

auto g = (f,a,b,_2,c,_1);

g(_1,_2)
f(a,b,_2,c,_1)
  • bind中不是占位符的参数被拷贝到bind返回的可调用对象中,如果希望采用引用的方式传递,必须使用标准库refcref函数。

所在头文件:#include<functional>
ref:采用引用方式传递参数。
cref:生成保存const引用的类。

4再探迭代器

  • 除容器定义迭代器外,标准库在头文件iterator中还定义了以下迭代器:插入迭代器、流迭代器、反向迭代器和移动迭代器。

头文件:#include<iterator>

4.1插入迭代器

  • 绑定到容器中,用来向容器插入元素;

4.1.1插入迭代器定义

  • 插入迭代器是一种迭代器适配器,接受一个容器生成迭代器,实现向给定容器添加元素。

4.1.2插入迭代器操作

it= t; //在it指定位置插入值t
*it,++it,it++ //返回it

4.1.3三种插入迭代器

  • 对应插入迭代器要在容器可使用对应函数的情况下使用。
迭代器说明应用
back_inserter使用push_back的迭代器c.push_back(val);
front_inserter使用push_front的迭代器,it指向新的首元素c.push_front(val);
inserter使用insert的迭代器,it指向定义元素的位置,跟着元素变化it = c.inserter(it,val);++it;
	vector<string> strs = { "hhdy","ssssdfg","asdcfgd","hh","xx","hhdyzwhy" };
	auto it = inserter(strs, strs.begin()+1);
	it = "abs";
	auto it1 = back_inserter(strs);
	*it1 = "hhh";  //it和*it=val都可以
	vector<string> strs1;
	copy(strs.begin(), strs.end(), inserter(strs1, strs1.begin()));//hhdy abs ssssdfg asdcfgd hh xx hhdyzwhy hhh
	list<string> strs2;
	copy(strs.begin(), strs.end(), front_inserter(strs2));//hhh hhdyzwhy xx hh asdcfgd ssssdfg abs hhdy
	vector<string> strs3;
	copy(strs.begin(), strs.end(), back_inserter(strs3));//hhdy abs ssssdfg asdcfgd hh xx hhdyzwhy hhh
	vector<string> strs4;
	copy(strs.begin(), strs.end(), inserter(strs1, strs4.end()));//hhdy abs ssssdfg asdcfgd hh xx hhdyzwhy hhh

4.2流迭代器

  • 绑定带输入或输出流上,遍历相关IO流
  • 标准库定义了用于IO类型对象的迭代器
istream_iterator //读取输出流
ostream_iterator //输出流写数据

4.2.1istream_iterator

4.2.1.1istream_iterator 操作
  • 尾后迭代器:
  • 标准库保证在第一次解引用迭代器之前,从流中读取数据的操作已经完成。

当关联流遇到文件文件为或者IO错误,迭代器值与尾后迭代器相等。

istream_iterator<T> in(is); //in从输入流is读取类型为T的值
istream_iterator<T> end; //创建了一个相当于尾后迭代器使用的空的流迭代器。

in1 = in2;  //必须读取相同类型,在都是尾后迭代器或者帮到相同输入情况下相等
in1 !=in2;

*in
in->men //相当于(*in).mem

++in,in++
4.2.1.2istream_iterator应用

1.从cin读取int值存入vec

//从cin读取int值存入vec
	vector<int> vec;
	istream_iterator<int> int_it(cin),int_eof;
	while (int_it != int_eof) {
		vec.push_back(*int_it++);
	}

//相当于下方代码
	istream_iterator<int> in_iter(cin), eof;
	vector<int> vec(in_iter, eof);

2.从文件流读取string存入vecstr

	ifstream in("afile.txt");
	istream_iterator<string> str_int(in),str_eof;
	vector<string> vecstr;
	while (str_int != str_eof) {
		vecstr.push_back(*str_int++);
	}

3.使用泛型算法操作流迭代器

  • 可以用某些泛型算法操作流迭代器。
	istream_iterator<int> intit(cin), inteof;
	cout<<accumulate(intit, inteof, 0)<<endl;

4.2.2ostream_iterator

  • 可以对任何具有输出运算符(<<)的类型定义ostream_iterator。
  • ostream_iterator必须绑定到一个指定的流,不允许空的或表示尾后位置的ostream_iterator。
4.2.2.1ostream_iterator操作
ostream_iterator<T> out(os); //out将类型T的值写入输出流os中
ostream_iterator<T> out(os,d); out将类型T的值写入输出流os中,每个值后都输出一个d。d指向一个空字符结尾字符数组。

out = val; //用<<运算符将val写入到out绑定输出流os中。
*out++ = val;

*out,++out,out++ //不对out做任何事,返回out
4.2.2.2ostream_iterator应用

1.输出

ostream_iterator<int> out_iter(cout, " ");
for(int i:vec){
 out_iter = i;
 //*out_iter++ = i;
 }
 cout<<endl;

2.通过泛型函数打印输出

	ostream_iterator<int> out_iter(cout, " ");
	copy(vec.begin(), vec.end(), out_iter);
	cout << endl;

4.2.3使用流迭代器处理类类型(待补)

  • 需要重载各种运算符

4.3反向迭代器

4.3.1反向迭代器定义

  • 反向迭代器是在容器从尾元素向首元素反向移动的迭代器。反向迭代器++it会移动到前一个元素,–it会移动到后一个元素。
  • 由于反向迭代器需要递减运算符,所以除单向链表和流迭代器外可以使用。
c.rbegin(),c.crbegin(),c.rend(),c.crend()

反向迭代器应用

1.反向打印元素

	vector<int> vec = { 1,2,3,4,5 };
	ostream_iterator<int> outer_vec(cout, " ");
	copy(vec.rbegin(), vec.rend(), outer_vec);  //5,4,3,2,1
	cout<<endl;
	for (auto iter = vec.rbegin(); iter != vec.rend(); ++iter) {
		cout << *iter << " "; //5,4,3,2,1
	}
	cout << endl;
	sort(vec.rend()-7, vec.rend()-2); //1 2 7 6 5 4 3 8 9 10
	copy(vec.begin(), vec.end(), outer_vec);
	cout << endl;

2.逆序排序

sort(vec.rbegin(),vec.rend());

3.输出逗号分割列表中倒数第一个单词

  • 由于普通迭代器是左闭右开,反向迭代器是左开右闭。二者不能混合使用。从一个普通迭代器初始化一个反向迭代器,或是给一个反向迭代器赋值时,结果迭代器和原迭代器指向的不是同一个元素。
  • 可以使用reverse_iterator的base函数,使反向迭代器返回对应的普通迭代器。
	string words = "hhdy,zwhy,hhdyzwhy";
	auto lword_iter = find(words.rbegin(), words.rend(), ',');
	cout << string(words.rbegin(),lword_iter) << endl; //yhwzydhh 
	cout << string(lword_iter.base(),words.end()) << endl;//hhdyzwhy
	

4.4移动迭代器

  • 不拷贝其中元素,而是移动他们。

5泛型算法结构

  • 分类方式

1.根据迭代器类别分类
2.根据是否读、写和重排序列中元素分类

5.1 五种迭代器类别

  • 算法基本特性是它要求其迭代器提供哪些操作。算法所要求的迭代器操作可分为5个迭代器类别。
类型说明
输入迭代器只读不写,单遍扫描,递增。需要有相等、递增、解引用运算符。输入迭代器只用于单遍扫描的顺序访问算法,如find和accumulate中。istream_iterator是输入迭代器。
输出迭代器只写不读,单遍扫描,递增。需要递增和解引用运算符。只用于单遍扫描算法,如copy的第三个参数。ostream_iterator是输出迭代器。
前向迭代器可读写,多遍扫描,递增。支持所有输入输出迭代器操作,沿一个方向运动,可以多次读写同一个元素,保存前向迭代器的状态,并对序列进行多次扫描,如replace中需要。forward_list上的迭代器是前向迭代器。
双向迭代器可读写,多遍扫描,可递增递减。支持前向迭代器操作,此外还支持递减,reverse中需要。除forward_list外其他容器迭代器。
随机访问迭代器可读写,多遍扫描,支持全部迭代器运算,包括比较关系、与整数加减换算迭代器位置,两个迭代器求距离,下标运算符等。提供常量时间内访问序列中任意元素的能力,sort中需要。array、deque、string、vector、访问内置元素的指针的迭代器。

5.2参数传递规范

  • 大多数算法具有以下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);
  • 对于dest,向输出迭代器写入参数时,假定目标空间足够容纳写入数据。
  • 如果只接受beg2,假定beg2开始范围与beg和end所表示范围至少一样大。

5.3命名规范

5.3.1 使用函数重载默认比较方式:

  • 接收额外谓词参数
unique(beg,end);
unique(beg,end,comp);
  • _if版本
find(beg,end,val);
find_if(beg,end,pred);

5.3.2 拷贝与不拷贝元素版本:

  • 某些函数提供另外一个版本,将元素写道一个指定的输出目的位置。这种函数会在名字后边加上_copy
reverse(beg,end);
reverse(beg,end,dest);
  • 存在_copy_if版本:remove_copy_if

6特定容器算法

  • 对于链表类型list和forward_list定义了独有的成员函数版本。使用时优先于通用算法。
  • 相比通用版本,链表版本会改变底层得容器
//返回void
lst.merge(lst2) //合并,合并后lst2为空
lst.merge(lst2,comp)

lst.remove(val)  //删除
lst.remove_if(pred)

lst.reverse() //返转

lst.sort()
lst.sort(comp)

lst.unique()
lst.unique(pred)

lst.splice(args) //移动lst2中的元素到lst1中。
lst.splice_after(args)
	list<int> lst1 = { 1,2,3,4,5 };
	list<int> lst2 = { 2,3,4,5,6 };
	lst1.merge(lst2); //1 2 2 3 3 4 4 5 5 6
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值