C++中的迭代器和泛型算法

简单的迭代器和算法

1.迭代器令算法不依赖于容器,但算法依赖于元素类型的操作。泛型算法本身不会执行容器的操作,它们只会运行于迭代器之上,执行迭代器的操作。算法永远不会改变底层容器的大小。算法可能改变容器中保存的元素的值,也可能在容器内移动元素,但永远不会直接添加或删除元素。

2.常见的只读算法

<1>accumulate

	vector v = {1,2,3};
	//这条语句将sum设置为v中元素的和,和的初值设置为0
	int sum = accumulate(v.begin(), v.end(), 0);

	vector v = {"ab","cd"};
	//通过第三个参数创建一个string,然后将vector中所有string元素连接起来
	string s = accumulate(v.begin(), v.end(),string(""));
	string s1 = accumulate(v.begin(), v.end(),"");//错误,无法从const char *转换为string,而const char *没有+运算
<2>equal:用于确定两个序列是否保存相同的值。

注意:equal是利用迭代器来完成操作,所以可以通过调用equal来比较两个不同类型的容器中的元素。而且,元素类型也不必一样。

	vector v1 = {1,2,3};
	vector v2 = { 1,2,3,4};//equal是假定第二序列至少与第一个序列一样长。否则程序就会down掉。
	int a=equal(v1.begin(), v1.end(), v2.begin());
与equal类似,那些只接受一个单一迭代器来表示第二个序列的算法,都假定第二个序列至少与第一个序列一样长。
3.常见的写算法

vector v1 = {1,2,3};
	vector v2 = {4,4,4,4,4,4,4};
	fill(v1.begin(), v1.end(), 10);//将每个元素重置为10
	fill_n(v2.begin(), 5, 10);//从v2.begin()开始的5个值,用10替换
	for (auto i : v1)
		cout << i << " " ;
	cout << endl;
	for (auto i : v2)
		cout << i << " "  ;

4.重排容器元素的算法

	vector v = {"sf","ag","vr","ag","ht","er","we","yu"};
	sort(v.begin(),v.end());
	for (auto s : v)
		cout << s << "  ";
	cout << endl;
	auto u = unique(v.begin(), v.end());//重排输入范围,使相同的字符串只出现一次,返回的是不重复区域之后第一个位置的迭代器
	v.erase(u, v.end());//删除多余元素
	for (auto s : v)
		cout << s << "  ";
注意:unique算法并不会删除任何元素,它只是从后向前覆盖相邻的重复元素,使得不重复元素出现在序列开始部分。unique返回的迭代器是指向最后一个不重复元素之后的位置。可以使用erase进行删除。

定制操作

很多算法都会比较序列中的元素。默认情况下,这类算法使用元素类型的<或==运算符完成比较。标准库还为这些算法定义了额外的版本,允许我们提供自己定义的操作来代替默认运算符。

1.谓词:是一个可调用的表达式,其返回结果是一个能用作条件的值。可分为一元谓词(只接受单一参数)和二元谓词(接受两个参数)。接受谓词参数的算法对输入序列中的元素调用谓词。所以,元素类型必须能转换为谓词的参数类型。

bool isShorter(const string &s1, const string &s2)//二元谓词
{
	return (s1.size() < s2.size());
}
2.lambda表达式

<1>根据算法接受一元谓词还是二元谓词,我们传递给算法的谓词必须严格接受一个或两个参数。但如果一个算法只接受一元谓词,但根据实际需求,确实要用到两个参数,这时候就可以利用lambda。

我们可以向一个算法传递任何类别的可调用对象。对于一个对象或者表达式,如果可以对其使用调用运算符即(),则称它为可调用的。可调用对象有:函数、函数指针、lambda表达式、重载了函数调用运算符的类。

lambda介绍:一个lambda表达式表示一个可调用的单元代码。可以将其理解为未命名的内联函数。lambda可能定义在函数内部。

  [capture list](parameter list)->return type {function}

capture list是指捕获列表,是在lambda所在函数中国定义的局部变量列表。需要注意的是:我们只对lambda所在函数中定义的(非static)变量使用捕获列表。并且,lambda可以直接使用定义在当前函数之外的名字。与普通函数不同的是,lambda必须使用尾置返回来指定返回类型。在lambda中,可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体。

auto f = [] {return 24; };
cout<
lambda的调用方式与普通函数的调用方式类似,都是使用调用运算符。
<2>向lambda传递参数

lambda不能有默认参数,所以,一个lambda调用的实参永远与形参数目相等。

<3>lambda捕获

当定义一个lambda时,编译器生成一个与lambda对应的新的类类型。也就是说,当向一个函数传递一个lambda时,同时定义了一个新类型和该类型的一个对象:传递的参数就是此编译器生成的类类型的未命名的对象。类似的,当使用auto定义一个用lambda初始化的变量时,定义了一个从lambda生成的类型的对象。

类似参数传递,变量的捕获方式也可以是值或者引用。与传值参数类似,采用值捕获的前提是变量可以拷贝。与参数不同的是,被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝。

void fun()
{
	size_t v = 42;
	auto f = [v] {return v; };
	v = 24;
	auto j = f();//j的值还是42,因为是值捕获,被捕获的变量是在创建时拷贝的
}
引用捕获与返回引用有着相同的问题和限制。所以要确保被引用的对象在lambda执行的时候是存在的。


<4>指定lambda返回类型
如果一个lambda体包含return之外的任何语句,则编译器假定此lambda返回void。当我们需要一个lambda定义返回类型时,必须使用尾置返回类型。

void fun()
{
	vector v{1,3,-2,6,8,-6};
	transform(v.begin(), v.end(),v.begin(),
		[](int i)->int
	{if (i < 0) return -i; else return i; });
}
3.参数绑定,标准库bind函数

作用就是将bind函数看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表。

auto newCallable = bind(callable, arg_list);
newCallable是一个新的可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。即,当我们调用newCallable时,newCallable会调用callable,并传递给它arg_list中的参数。

auto g=bind(f,a,b,_2,c,_1);//f这个可调用对象有5个参数,_1和_2这两个参数由g这个新的调用对象提供,也就是
	                             //新调用对象将它自己的参数作为第三个和第五个参数传递给f。f的第一个、第二个、
	                             //和第四个参数分别被绑定到给定的值a、b和c上。

再探迭代器

以下几个迭代器都是在头文件iterator中。

1.插入迭代器

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

back_inserter                                         创建一个使用push_back的迭代器
front_inserter 创建一个使用push_front的迭代器
inserter 创建一个使用insert的迭代器。此函数接受两个参数,这个参数必须是一个指向给定容器的迭代器。元素将被插入到给定迭代器所表示的元素之前

int main()
{
	list li1 = {1,2,3,4};
	list li2,li3,li4;
	copy(li1.cbegin(), li1.cend(), back_inserter(li2));
	copy(li1.cbegin(), li1.cend(), front_inserter(li3));
	copy(li1.cbegin(), li1.cend(), inserter(li4,li4.begin()));
	for (auto i : li2)
		cout << i << " ";
	cout << endl;
	for (auto i : li3)
		cout << i << " ";
	cout << endl;
	for (auto i : li4)
		cout << i << " ";
	cout << endl;
	system("pause");
	return 0;
}
C++中的迭代器和泛型算法_第1张图片

2.iostream迭代器

iostream类型不是容器,但标准库定义了可以用于这些IO类型对象的迭代器isteram_iterator读取输入流,ostream_iterator向一个输出流写数据。这些迭代器将它们对应的流当作一个特定类型的元素序列来处理。通过使用流迭代器。我们可以用泛型算法从流对象读取数据以及向其写入数据。

int main()
{
	vector v;
	istream_iterator in_iter(cin);
	istream_iterator eof;//不为它指定istream对象,它即代表了end-of-file(文件尾)
	while (in_iter != eof)
		v.push_back(*in_iter++);
	for (auto i : v)
		cout << i << " ";
	system("pause");
	return 0;
}

C++中的迭代器和泛型算法_第2张图片



int main()
{
	vector v = { 'I','a','w' };
	ostream_iterator out_iter(cout, "   ");
	for (auto e : v)
		*out_iter++ = e;
	system("pause");
	return 0;
}
C++中的迭代器和泛型算法_第3张图片

3.反向迭代器

是在容器中从尾元素反向移动的迭代器。此时,++it会移动到前一个元素;--it会移动到下一个元素。

int main()
{
	string line = "FIRST,MIDDLE,LAST";
	auto comma = find(line.crbegin(), line.crend(), ',');
	cout << string(line.crbegin(), comma) << endl;//反向迭代器,内容也反着出来了
	cout << string(comma.base(), line.cend()) << endl;//从后向前找,输出是正常迭代器
	system("pause");
	return 0;
}

通过调用reverse_iterator的base成员函数能够返回普通迭代器。

C++中的迭代器和泛型算法_第4张图片



你可能感兴趣的:(C++)