C++----范型算法

来源:互联网 发布:兴城淘宝美工招聘 编辑:程序博客网 时间:2024/06/06 09:16

算法永远不会执行容器的操作

泛型算法本身不会执行容器的操作,它们只会运行于迭代器上,执行迭代器的操作。泛型算法运行于迭代器智商而不会执行容器操作的特性带来了一个令人惊讶但非常必要的编程假定:算法永远不会改变底层容器的大小。算法可能改变容器中保存的原始的值,也可能子啊容器内移动元素,但永远不会直接添加或删除元素。

初识

除了少数例外,标准库算法都对一个范围内的元素进行操作。我们将此元素范围成为“输入范围”。带有输入范围参数的算法总是使用头两个形参标记该范围。这两个形参是分别指向要处理的第一个元素和最后一个元素的下一位置的迭代器。

只读算法

例如:

find(begin(a),end(a),val);

再例如accumulate,头两个形参指定要累加的元素范围。第三个形参则是累加的初值

int sum = accumulate(vec.cbegin(),vec.cend(),0);

accumulate将第三个参数作为求和起点,这蕴含着一个编程假定:将元素类型加到和的类型上的操作必须可行。即,序列中元素的类型必须与第三个参数匹配,或者能够转换为第三个参数的类型。在上例中,vec元素可以是int,或者double,或者long long等。
下面是另一个例子,由于string定义了+运算符,所以可以有:

string sum = accumulate(v.begin(),v.end(),string(""));

注意,我们显式地创建了一个string。将空串当做一个字符串字面值传递给了第三个参数是不可以的,原因在于 “” 对象类型是const char*.而如前所述,此类型决定了使用哪个+运算符。由于const char*没有+运算符。此调用会产生错误。

写容器元素的算法

一些算法会自己向输入范围写入元素。写入到输入序列的算法本质上是安全的——只会写入与指定输入范围数量相同的元素。
例如:

fill(vec.begin(),vec.end(),0);   //将每个元素重置为0

算法不检查写操作
程序员的责任是保证输入合法,因为算法并不会帮我们检查输入是否合法。例如fill_n接受一个迭代器、一个计数值和一个值。它将给定值赋予迭代器指向的元素开始的指定个元素。一个初学者非常容易犯的错误就是在一个空容器上调用fill_n(或者类似写元素的算法)

vector<int> vec;//空向量//灾难:修改vec中的10个(不存在)元素fill_n(vec.begin(),10,0);

定制操作

向算法传递函数

很多算法都会比较输入序列中的元素。默认情况下,这类算法使用元素类型的<或==运算符完成比较。标准库还为这些算法定义了额外的版本,允许我们提供自己定义的操作来代替默认运算符。
例如sort。
谓词谓词是一个可调用的表达式,其返回结果是一个能作为条件的值。标准库算法使用的谓词分为两类:一元谓词(unary predicate,意味着它们只接受单一参数)和二元谓词(binary predicate,意味着它们有两个参数)】

bool isShorter(const string &s1, const string &s2){    return s1.size() < s2.size();}//按长度由短至长排序wordssort(words.begin(),words.end(),isShorter);

lambda表达式

根据算法接受一元谓词还是二元谓词,我们传递给算法的谓词必须严格接受一个或两个参数。但是,有时候我们希望接受更多参数,超出了算法谓词的限制。就要用到lambda表达式。我们可以向一个算法传递任何类型的可调用对象(callable object)。对于一个对象或一个表达式,如果可以对其使用调用运算符,则称它为可调用的。调用运算符=======》 ()
与任何函数类似,一个lambda表达式具有一个返回类型,一个参数列表和一个函数体。但是与函数不同,lambda可能定义在函数内部。一个lambda表达式具有如下形式:

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

其中,capture list(捕获列表)是一个lambda所在函数中定义的局部变量的列表(通常为空);return type, parameter list, function body与任何普通函数一样。但是lambda必须使用尾置返回来指定返回类型。
我们可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体

auto f = [] {return 42;};cout << f << endl;

向lambda传递参数
lambda不能有默认参数,因此调用的实参数目与形参数目相等。我们可以编写一个与isShorter函数完成相同功能的lambda:

sort(words.begin(),words.end(),[](const string &a,const string &b){return a.size()<b.size()};});

使用捕获列表
lambda表达式可以使用调用函数的局部变量,但是必须指定。如要捕获sz:

[sz](const string &a){return a.size() >= sz;};

嫌麻烦就隐式,lambda表达是自己判断

[=](const string &a){return a.size() >= sz;};

上面是值捕获,下面是引用捕获
[&](const string &a){return a.size() >= sz;};
下面是os显示捕获,引用捕获方式;其他所有(c)隐式捕获,值捕获

[=,&os](const string &s){os<<s<<c;};

需要注意的
一般lambda表达式只有一个return语句,类型自己就可以判断。如果多余一条,则默认返回值为void,需要指定。指定要用尾置返回。

再探迭代器

除了为每个容器定义的迭代器之外,标准库在头文件iterator中还定义了额外几种迭代器。包括

  • 插入迭代器(insert iterator):这些迭代器被绑定到一个容器上,可用来向容器插入元素。
  • 流迭代器(stream iterator):这些迭代器被绑定到输入或输出流上,可用来遍历所关联的IO流。
  • 移动迭代器(move iterator):专用来移动容器中的元素。

插入迭代器

它接受一个容器,生成一个迭代器,能实现向给定容器添加元素。有三种类型:

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

iostream迭代器

虽然iostream不是容器,但是有迭代器。通过使用流迭代器,我们可以用泛型算法从流对象读取数据以及向其写入数据。
例如下面是一个用istream_iterator从标准输入读取数据,存入一个vector的例子:

istream_iterator<int> in_iter(cin);//从cin中读取intistream_iterator<int> eof; //当有数据可供读取时//后置递增运算读取流,返回迭代器的旧值//解引用迭代器,获得从流读取的前一个值while(in_iter != eof)    vec.push_back(*in_iter++);

eof被定义为空的istream_iterator,从而可以当尾后迭代器来使用。对于一个绑定到流的迭代器,一旦其关联的流遇到文件尾或遇到IO错误,迭代器的值就与尾后迭代器相等。

反向迭代器

可以用rbegin,rend,crbegin,crend来获得。

sort(vec.rbegin(),vec.rend);//逆序排序
原创粉丝点击