第十章 泛型算法

本文深入介绍了C++中的泛型算法,包括只读和写容器的算法,如find、count、accumulate和sort,强调了算法对迭代器而非容器的操作。此外,讲解了自定义操作,如lambda表达式和bind函数,以及如何用它们作为谓词进行排序和查找。同时,讨论了特殊迭代器如插入迭代器、流迭代器和反向迭代器的使用。最后,提到了泛型算法的设计原则和特定容器算法的适用性。
摘要由CSDN通过智能技术生成


介绍

c++Primer第十章学习心得


一、泛型算法概述

通过对迭代器进行操作,而非容器本身,使得算法的实现不再依赖于容器,这是其优点。大部分在中
1.只读算法
find(iter1,iter2,val);找到第一个val的位置
count(iter1,iter2,val);统计val出现次数
accumulate(iter1,iter2,val);计算总和,val为初值,该函数在numeric中,要求元素类型和val匹配
equal(iter1,iter2,iter3),将iter1与iter3比较至iter2,若不同返回false,但是若2比1长,前面相同仍然返回true;

	vector<double> nums{1,1,1,2,2,2,3,3,4,4,5,6};
	vector<double> n{1,1,1,2,2};
	bool flag = false;
	flag = equal(n.begin(),n.end(),nums.begin());//1
	cout<<flag;

2.写容器的算法
由于算法不执行容器操作,所以写入时不能修改容器本身的大小!
且算法不检查写操作,哪怕对未定义空间写入,普通迭代器是通过赋值。可以通过插入迭代器保证有足够的元素空间,它是通过push_back将值插入到容器中。

fill(vec.begin(),vec.end(),val);//将每个元素置为val
fill_n(vec.begin(),10,0);//若vec的值少于10个,该行为是未定义的
fill_n(back_insert(vec),10,0);//安全

auto it = back_insert(vec);
*it = 15;//容器增加一个元素15

copy(it1,it2,it3)将it1和it2之间的元素拷贝到it3后,返回拷贝的最后一个元素之后,同样地,需要保证it3之后的空间与it1、it2之间的空间大小保持一致

3.重排容器元素的算法
sort()排序,unique()将相邻的重复元素“删除”,返回不重复元素后的迭代器,可以通过容器本身的删除操作完整实现

vector<int> num{1,1,2,3,4,5,5,6,2,3}];
sort(num.begin(),num.end());
auto it = unique(num.begin(),num.end());
num.erase(it,num.end());

二、定制操作

可以通过自定义的操作来代替默认运算符,类似sort中的自定义函数,是一个谓词
谓词分为一元谓词和二元谓词
1.lambda表达式

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

capture list是一个局部变量的列表,通常为空但必须存在,其他和普通函数一样,但必须使用尾置返回类型。可以忽略参数列表和返回类型,但必须有捕获列表和函数体。当函数类型被忽略时,会根据函数体中的代码推断返回类型,如果函数体仅含一个return语句,则返回类型由返回表达式的类型推断,否则返回类型为void。
lambda函数不能有默认参数,所以实参数量和形参数量相同。

关于捕获列表的理解:
[]形式意为不捕获任意外部变量,[&]是以引用形式捕获所有外部变量,允许改变,[=]是以拷贝形式捕获,且即使是拷贝也不能改变大小.[var]是捕获具体的某个变量,也可用=、&修饰它。还可以捕获this指针,用于访问类中的成员。当捕获值时,该值在创建lambda函数时被确定,而非调用时;当捕获引用时,局部变量必须存在。

int val = 42;
auto f() = [val] -> {return val;};
val = 0;
auto j = f();//j=42

关于可变lambda
使用值捕获时,lambda函数默认值不可改变,因此可以在参数列表后加上关键词mutable。引用捕获是否可变取决于引用本身是否为const。

关于返回类型
如果lambda函数中只有一个return语句,可以省略返回类型,依靠编译器自己判断;否则为void类型,无法返回值。需要通过尾置类型主动控制返回类型。

2.bind函数
定义在头文件functional中,通常与占位符结合。使用占位符可以加命名空间,也可以用限定符。

#include <functional>
using namespace std::placeholders;
auto f = bind(fun,_1);//_1就就是占位符,表示该参数为fun中第一个形参
auto f = bind(fun,std::placeholders::_1);//此方式也可

bind函数的主要目的是绑定参数,可以直接绑定值,也可以绑定形参,还可以用于重排参数顺序。在使用自定义函数做一元谓词或二元谓词时,多余的参数可以通过bind绑定。

三、再谈迭代器

除了常规迭代器外,还存在一些特殊迭代器,被定义在iterator头文件中。包括:插入迭代器、流迭代器、反向迭代器、移动迭代器。
1.插入迭代器
迭代器不是容器操作,因此不能直接改变容器。而插入迭代器可以调用容器操作,向容器的指定位置插入一个元素。
back_inserter:创建一个调用push_back的迭代器
front_inserter:调用push_front
inserter:调用insert
不同迭代器由于调用的不同,所以效果是不同的,对于inserter而言,插入位置固定,而front_inserter可以保证每次都是插入到容器第一个元素前。
2.iostream迭代器
无iostream容器,但存在可以用于该对象的迭代器。istream_iterator通常使用时会创建两个迭代器,一个指向对象,一个指向空。

    istream_iterator<int> in_iter(cin);
    istream_iterator<int> eof_iter;
    while(in_iter!=eof_iter){
        vec.emplace_back(*in_iter++);
    }
    //另一种写法
    istream_iterator<int> in_iter(cin),eof_iter;
    vector<int> vec(in_iter,eof_iter);

ostream_iterator需要指定对象,可附加一个c字符串,用于每次输出之后额外输出一个该字符串。使用时,通过赋值即可自动输出。

   ostream_iterator<int> out_iter(cout," ");
   for(auto e : vec){
       *out_iter++ = e;//实际无效,等价于out_iter = e,此处为了与其他迭代器保持一致
   }
   //另一种写法
   copy(vec.begin(),vec.end(),out_iter);
//使用流迭代器读取输入,并将其从大到小排列后输出
    istream_iterator<int> in_iter(cin), eof_iter;
    vector<int> vec(in_iter, eof_iter);
    sort(vec.begin(), vec.end(), [](const int &a, const int &b)
         { return a > b; });
    ostream_iterator<int> out_iter(cout, " ");
    copy(vec.begin(), vec.end(), out_iter);

3.反向迭代器
使用rbegin()和rend()获取反向,此时的++实际是向第一个元素移动,–为向最后一个元素移动,使用时需要格外注意。此外,反向与对应的普通迭代器(.base())所表示的范围实际是不同的相邻,因为是左闭合区间。

四、泛型算法结构

设计泛型算法时,应当指定使用迭代器的类型。根据操作不同,将迭代器分为五类,分别是:

迭代器类型特点
输入迭代器只读不写,单次扫描,只能递增
输出迭代器只写不读,单次扫描,只能递增
前向迭代器可读写,多次扫描,只能递增
双向迭代器可读写,多次扫描,可递增递减
随机访问迭代器支持所有操作

由上往下等级递增,高层迭代器支持底层迭代器所有操作(输出不支持输入)
算法应当指定每个迭代器参数的最小类别

算法通过重载传递一个谓词或者通过重命名方式,例如

sort(vec.begin(),vec.end(),[](const int &a,const int &b){return a>b;});
sort(vec.begin(),vec.end());//重载
find(vec.begin(),vec.end(),val);//val为特定值
find_if(vec.begin(),vec.end(),谓词);//重命名

部分算法需要区分拷贝和不拷贝的版本,通常通过_copy进行区别

reverse(beg,end);
reverse_copy(beg,end,dest);//将beg和end反转后拷贝到dest

五、特定容器算法

对于list和forward_list而言,不能使用双向迭代器,因此需要使用特定的成员函数,部分通用算法可以用,但性能低于特定容器本身的成员函数。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 什么是泛型泛型的作用? 泛型是Java语言中的一种参数化类型机制,它允许在定义类、接口和方法时使用类型参数,从而实现代码的灵活性和重用性。泛型的作用有以下几个方面: - 提高代码的安全性和可读性:使用泛型能够在编译期间检查类型,并避免在运行时出现类型转换异常。 - 代码重用和灵活性:使用泛型能够实现代码的重用,减少代码冗余,同时也能够使代码更加灵活。 - 编写更加通用的算法数据结构:使用泛型能够编写更加通用的算法数据结构,使得它们能够处理不同类型的数据。 2. 什么是有界泛型? 有界泛型是指限定泛型参数的类型范围,通过指定泛型参数必须是某个类或接口的子类或实现类来限定泛型参数的类型范围。Java中的有界泛型有以下两种形式: - 上界限定:使用extends关键字指定泛型参数必须是某个类的子类或实现类。 - 下界限定:使用super关键字指定泛型参数必须是某个类的超类或实现类。 有界泛型可以提高代码的安全性和可读性,同时也能够减少类型转换的问题。 3. 类泛型和方法泛型有什么区别? 类泛型和方法泛型是Java中两种不同的泛型形式。它们的区别如下: - 类泛型:类泛型是指在类或接口中定义泛型类型参数,可以在类或接口的实例化过程中指定具体的类型参数。 - 方法泛型:方法泛型是指在方法中定义泛型类型参数,可以在方法调用时指定具体的类型参数。 类泛型和方法泛型在使用上有一些不同: - 对于类泛型,类型参数的作用域是整个类或接口,可以在类或接口中的任何地方使用。对于方法泛型,类型参数的作用域是整个方法体,只能在方法体中使用。 - 类泛型的类型参数可以被类内部的任何成员方法使用,而方法泛型的类型参数只能被定义它的方法使用。 - 类泛型的类型参数可以被继承子类所使用,而方法泛型的类型参数只能被定义它的方法所使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值