标准库并未给每个容器添加大量功能,因此,通过大量泛型算法,来弥补。这些算法大多数独立于任何特定的容器,且是通用的,可用于不同类型的容器和不同的元素。

迭代器使得算法不依赖容器,但是算法依赖于元素的类型操作;例如某些算法需要元素支持"<"运算。

算法永远不会执行容器操作,它们只会运行于迭代器之上,执行迭代器操作;因此,算法不会改变底层容器的大小,算法可能会改变容器中保存的元素的值,也可能移动元素,但是不会直接添加删除元素。

注意:

  1. 大部分算法在algorithm头文件中,但是少部分数值相关的算法在numeric头文件中。下面定义在numeric中的算法,会指出来,没有特别说明的是定义在algorithm中。
  2. 向输出迭代器写入数据的算法一般都假定目标空间容量足够,因此可以将输出迭代器绑定到一个插入迭代器。
  3. 接受第二个输入序列的算法,如果只有beg2则表示它与第一个大小相同。
  4. 带_if的算法接受谓词做参数,带_copy的算法表示会拷贝元素

只读算法:

该类算法只会读取容器的值,而不改变元素。

1.iterator find(iterator::begin,iterator::end,val);

在begin和end之间找值为val的位置,找到了返回值为val的迭代器,如果找不到,则返回尾迭代器;

工作流程:从begin到end找到每一个元素与val比较,相等则返回当前的迭代器,否则找到尾部,返回尾部迭代器(注意这里返回的是传进去的尾部迭代器iterator::end,如果传的不是尾迭代器,则返回的也不会是尾迭代器)。

还可以像如下方式来使用find处理内置数组:

int a[] = {1,2,3,4,5,6,7};

int val = 4;

auto result = find(begin(a),end(a),val);//result是int *类型。

2.iterator::difference_type count(iterator::begin,iterator::end,val);

统计begin到end中val出现的次数,返回次数,是带符号的。

3.accumulate(iterator::begin,iterator::end,val);

它定义在numeric头文件中;求指定范围内元素的和。

注意:val是指求和的初值,它表明了该函数要调用哪个加法运算符和返回类型。具体来说,传入的序列的类型必须与val类型匹配或者能够转换;同时只要支持加法运算符,都可以调用该函数,即可用用该函数拼接string;

string sum = accumulate(v.cbegin(),v,cend(),srting(""));

但是注意,val不能直接传字符串字面值,即下面的做法是错误的。因为字符串字面值,保存的类型是const char*,它不支持加法运算符。

string sum = accumulate(v.cbegin(),v,cend(),"");

4.bool equal(iterator::begin,iterator::end,iterator::begin);

确定两个序列是否保存相同的值,将第一个序列的每个值与第二个序列对应的位置比较,都相同则返回true。

两个序列的类型可以不相同,只要能用==比较即可,因此可以比较vector<string>和vector<const char*>,甚至第二个序列可以是list、deque等与第一个不同的容器;但是,第二个序列必须至少和第一个序列一样长。

int arr[] = {1,2,3};

int brr[] = {1,2,3,4,5,6};

equal(cbegin(arr),cend(arr),cbegin(brr));//返回true

只写算法:

更新序列中的值,,必须注意确保序列原大小至少不能小于算法写入元素的数目。

1.void fill(iterator::begin,iterator::end,val);

将begin到end范围内元素都设置为val,只要注意begin到end是有效的范围,写入就是安全的。

还可以接受通过数量来确定范围的函数:void fill_n(iterator::begin,iterator::size_type,val);

将begin开始的size_type个元素的值赋为val。

可以通过插入迭代器,不提前给容器分配空间,而给容器赋值,它是通过插入迭代器调用push_back()函数;

vector<int>arr;//空向量

auto  it =back_inserter(arr);//返回一个与容器arr绑定的插入迭代器

fill_n(it,10,0);//想arr中添加10个0

2.iterator copy(iterator::begin,iterator::end,iterator::dest);

将begin到end的范围内元素复制到dest指向的内容之后;返回其递增后的目的位置的迭代器的值

int a1[] = {0,1,2,3,4,5,6,7,8,9};

int a2[sizeof(a1)/sizeof(a1[0])];//分配与a2一样的大小

//ret指向拷贝到a2的尾元素之后的位置

auto ret = copy(begin(a1),end(a1),a2);//把a1的内容拷贝给a2

3.replace(iterator::begin,iterator::end,type src,type dest);

将begin到end之间的元素中值为src全部用dest代替;

如果希望保证原容器的内容不变可以使用

replace_copy(iterator::begin,iterator::end,inserter::insert,type src,type dest);

例如:replace_copy(arr.cbegin(),arr.cend(),back_inserter(ivec),0,42);

上面的arr并没有改变,ivec中不仅包含arr的拷贝,还将其中的0都替换未42。

replace的用法可参考:http://blog.csdn.net/zwj1452267376/article/details/46821527

重拍容器中的元素的算法

1.sort(iterator::begin,iterator::end)

对容器中begin到end之间的元素排序,也可以通过第三个参数来调整是升序还降序,默认是升序。

它是利用<运算符来比较的,所以容器的元素类型必须支持<运算符,否则就要自己提供,详细的后面讲到。

2.iterator:: end_unique unique(iterator::begin,iterator::end)

消除已序容器相邻的相同元素,并返回一个指向不重复值范围末尾的迭代器。

但是,他并没有真正删除重复元素在返回的迭代器end_unique到end之间元素仍存在,但是我们知道它的值。

void elimDups(vector<string> words){

  //按照字典序排序words,以便查找重复单词

  sort(words,begin(),words.end());

  //将消除前面的重复元素

  auto end_unique = unique(words.begin(),words.end());

  //删除重复单词

  words.erase(end_unique,words.end());

}

谓词

谓词是一个可调用的表达式,其返回结果是一个能用作条件的值。分为一元谓词和二元谓词(一元二元是指的参数个数)。但是算法的输入序列中的元素类型必须能转换为谓词的参数类型。

例如:将单词按照长度排序;

bool isShorter(const string &s1,const string &s2){

  return s1.size() < s2.size();

}

//按照长度由短至长排序

sort(words.begin(),words.end(),isShorter);

lambda表达式

当传入的参数超过两个是就需要使用lambda表达式。现阶段经常使用的可调用对象包括函数、函数指针、重载了函数调用运算符的类以及lambda表达式。

一个lambda表达式表示一个可调用的代码单元。它包含一个返回类型、一个参数列表和一个函数体,但是lambda可以定义在函数内部。

[ capture ] ( params ) mutable exception attribute -> return { body } (1)  
[ capture ] ( params ) -> return { body } (2)  
[ capture ] ( params ) { body } (3)  
[ capture ] { body } (4)  

其中

  • (1) 是完整的 lambda 表达式形式,
  • (2) const 类型的 lambda 表达式,该类型的表达式不能改捕获("capture")列表中的值。
  • (3)省略了返回值类型的 lambda 表达式,但是该 lambda 表达式的返回类型可以按照下列规则推演出来:
    • 如果 lambda 代码块中包含了 return 语句,则该 lambda 表达式的返回类型由 return 语句的返回类型确定。
    • 如果没有 return 语句,则类似 void f(...) 函数。
  • 省略了参数列表,类似于无参函数 f()。

mutable 修饰符说明 lambda 表达式体内的代码可以修改被捕获的变量,并且可以访问被捕获对象的 non-const 方法。

exception 说明 lambda 表达式是否抛出异常(noexcept),以及抛出何种异常,类似于void f() throw(X, Y)。

attribute 用来声明属性。

另外,capture 指定了在可见域范围内 lambda 表达式的代码内可见得外部变量的列表,具体解释如下:

  • [a,&b] a变量以值的方式呗捕获,b以引用的方式被捕获。
  • [this] 以值的方式捕获 this 指针。
  • [&] 以引用的方式捕获所有的外部自动变量。
  • [=] 以值的方式捕获所有的外部自动变量。
  • [] 不捕获外部的任何变量。

此外,params 指定 lambda 表达式的参数列表,return是返回类型,body是函数体,但是lambda表达式必须使用尾置返回。

lambda表达式不能有默认参数,因此其形参数目一定与实参数目相同。

例如:与isShorter函数相同功能的lambda表达式:

[](const string& s1,const string& s2){return s1.size() < s2.size();}

因此可以用stable_sort来调用上面的lambda表达式:

//按照单词长度稳定排序

stable_sort(words.begin(),words.end(),[](const string& s1,const string& s2){return s1.size() < s2.size();});

使用捕获列表获取其所在函数的局部变量;

例如,捕获局部变量sz(表示大小);[sz](const string &a){return a.size() >= sz;};

使用find_if找到第一个长度不小于sz的元素:auto it = find_if(words.begin(),words.end(),[sz](const string &a){return a.size() >= sz;});

lambda的捕获与返回

当定义一个lambda时,编译器生成了一个与lambda对应的新的类类型(类似Java的匿名内部类)。向一个函数传一个lambda时,同时定义新类型和它的对象;默认情况下,从lambda生成的类都包含一个对应该lambda所捕获的变量的数据成员。

1.lambda的值捕获:与参数不同,值捕获时被捕获的变量的值是在lambda创建时拷贝,而不是调用是拷贝。

2.lambda的引用捕获:当捕获一个引用时,必须确保在lambda执行时,变量是存在的。引用捕获有时候是必要的,例如当我们要捕获一个IO流对象时,就必须捕获它的引用,因为IO流对象不允许拷贝或赋值。

注意:为了减少潜在风险,应该避免捕获指针或引用,保持lambda的变量捕获简单化。

lambda可以隐式捕获,让编译器根据代码推断捕获列表,但是为了指示是值捕获还是引用捕获,应该在捕获列表中写上&(引用捕获)或=(值捕获)。甚至可以在捕获列表中混合使用值捕获和引用捕获,此时第一个元素必须是&或=,它表明默认的捕获方式。

可以使用mutable来改变值捕获得到的变量值。

lambda的返回类型

默认情况下,如果一个lambda体包含return之外的任何语句,则编译器认为此lambda表达式返回void。

例如:transform(vi.begin(),vi.end(),vi.begin(),[](int i){return i < 0 ? -i : i;});//求绝对值的lambda表达式,没有问题。

   transform(vi.begin(),vi.end(),vi.begin(),[](int i){if(i < 0)return -i;else return i;});//编译出错,因为有了if语句,编译器认为lambda返回void。

改成 transform(vi.begin(),vi.end(),vi.begin(),[](int i) -> int {if(i < 0)return -i;else return i;});//使用尾置返回类型

lambda表达式应该用于只有一两个地方使用的简单操作,多个地方使用相同操作,或者操作复杂就应该使用函数。

如果需要捕获变量时也应该使用lambda,但是不需要捕获变量时一般还是使用函数来代替lambda表达式。但是并不是一定没有办法解决在函数中使用局部变量的问题,可以使用bind的标准库函数。

bind函数

bind函数定义在头文件functional中,我们可以将bind函数看作一个通用的函数适配器,它的一般形式如下

auto newFun = bind(oldFun,arg_list);

  1. 参数oldFun是需要bind封装的源函数,
  2. newFun是封装了参数后的old_Fun,
  3. arg_list是一个逗号分割的参数列表,对应oldFun的参数,即当我们调用newFun是,它会调用oldFun并且会把参数列表中的参数传给oldFun
  4. arg_list中会包含_n的名字,此类名字的参数又名”占位符”,因为其占据了newCallable的参数的位置,其中_n中的n表示它占据了new_Fun函数中的第几个参数。
#include<iostream>
#include<vector>
#include<algorithm>
#include<functional>
using namespace std; bool check_size(const int x,int sz)
{
return x > sz;
} int main(void)
{
vector<int> v = {,,,,,,,,}; int n = ;
//有bind函数新封装生成的函数,其内部调用了check_size
auto new_check_size = bind(check_size,std::placeholders::_1,n);//_1对应x,n对应sz auto it = find_if(v.begin(),v.end(),new_check_size); cout<<"第一个大于n的数为:"<<*it<<endl; return ;
}

_n定义在placeholders的命名空间中,而这个命名空间定义在std中,所以上面使用_1时要加上命名空间。

还可以使用bind重排参数顺序:

  //按照长度由长到短排序

  sort(words.begin(),words.end(),bind(isShorter,_2,_1));

bind函数的参数是拷贝进去的如果想传递IO流对象就不行了,因为IO流对象不能拷贝;此时可以使用标准库中的另一个函数ref(定义在functional中),它返回一个对象包含给定的引用,此对象可以拷贝。(还有cref函数可以生成一个保存const引用的类)

  ostream &print(ostream &os, const string &s,char c){

    return os << s << c;

  }

  for_each(words.begin(),words.end(),bind(print,ref(os),_1, ' '));//通过ref使得os对象可以拷贝获取

function对象

我们知道,在C++中,可调用实体主要包括函数,函数指针,函数引用,可以隐式转换为函数指定的对象,或者实现了opetator()的对象(即C++98中的functor)。C++0x中,新增加了一个std::function对象,std::function对象是对C++中现有的可调用实体的一种类型安全的包裹(我们知道像函数指针这类可调用实体,是类型不安全的)。我们来看几个关于function对象的例子:

#include < functional>  

std::function< size_t (const char*) > print_func;  

/// normal function -> std::function object
size_t CPrint(const char*) { ... }
print_func = CPrint;
print_func("hello world"): /// functor -> std::function object
class CxxPrint
{
public:
size_t operator()(const char*) { ... }
};
CxxPrint p;
print_func = p;
print_func("hello world");

在上面的例子中,我们把一个普通的函数和一个functor赋值给了一个std::function对象,然后我们通过该对象来调用。其它的C++中的可调用实体都可以像上面一样来使用。通过std::function的包裹,我们可以像传递普通的对象一样来传递可调用实体,这样就很好解决了类型安全的问题。了解了std::function的基本用法,下面我们来看一些使用过程中的注意事项:

  • (1)关于可调用实体转换为std::function对象需要遵守以下两条原则:
    a. 转换后的std::function对象的参数能转换为可调用实体的参数
    b. 可高用实体的返回值能转换为std::function对象的(这里注意,所有的可调用实体的返回值都与返回void的std::function对象的返回值兼容)。
  • (2)std::function对象可以refer to满足(1)中条件的任意可调用实体
  • (3)std::function object最大的用处就是在实现函数回调,使用者需要注意,它不能被用来检查相等或者不相等

里面还有些东西没讲清楚,后面再补充,有错的地方欢迎指出。

参考:http://blog.csdn.net/shreck66/article/details/48198357

     http://blog.csdn.net/augusdi/article/details/11771699

C++基础之泛型算法的更多相关文章

  1. C++ 泛型算法

    <C++ Primer 4th>读书笔记 标准容器(the standard container)定义了很少的操作.标准库并没有为每种容器类型都定义实现这些操作的成员函数,而是定义了一组泛 ...

  2. Chapter10:泛型算法

    泛型算法的基础是迭代器. 迭代器令算法不依赖于容器,但是算法依赖于元素类型的操作.也即:算法永远不会执行容器的操作. 那么,如果想向容器中添加元素或者执行其他的一些操作呢?标准库提供了插入迭代器来完成 ...

  3. c++11の泛型算法

    一.泛型算法泛型算法这个概念是针对容器操作的,我们知道,c++11的顺序容器有vector,list,deque等,对于这些容器,c++11并没给出相应的增删改查方法,而是定义了一组泛型算法 一般的泛 ...

  4. c++ primer 11 泛型算法

    使用泛型算法必须包含头文件#inlucde <algorithm> 标准库还定义一组泛化的算术算法,其命名习惯与泛型算法相同,包含头文件#include <numeric> f ...

  5. [.net 面向对象编程基础] (18) 泛型

    [.net 面向对象编程基础] (18) 泛型 上一节我们说到了两种数据类型数组和集合,数组是指包含同一类型的多个元素,集合是指.net中提供数据存储和检索的专用类. 数组使用前需要先指定大小,并且检 ...

  6. C++的那些事:容器和泛型算法

    一.顺序容器 1,标准库定义了3种类型的顺序容器:vector.list和deque.它们的差别主要在于访问元素的方式,以及添加或删除元素相关操作运算代价.标准库还提供了三种容器适配器:stack.q ...

  7. C++ Primer : 第十章 : 泛型算法 之 只读、写和排序算法

    大多数算法都定义在<algorithm>头文件里,而标准库还在头文件<numeric>里定义了一组数值泛型算法,比如accumulate. ●  find算法,算法接受一对迭代 ...

  8. 【STL】帮你复习STL泛型算法 一

    STL泛型算法 #include <iostream> #include <vector> #include <algorithm> #include <it ...

  9. C++ Primer 5th 第10章 泛型算法

    练习10.1:头文件algorithm中定义了一个名为count的函数,它类似find,接受一对迭代器和一个值作为参数.count返回给定值在序列中出现的次数.编写程序,读取int序列存入vector ...

随机推荐

  1. MyBatis的parameterType传入参数类型

    在mybatis映射接口的配置中,有select,insert,update,delete等元素都提到了parameterType的用法,parameterType为输入参数,在配置的时候,配置相应的 ...

  2. STM32F0系列芯片SPI发送一字节数据却输出16个CLK时钟的解决办法

    问题 上一个项目在用寄存器操作STM32F0芯片的SPI_DR寄存器的时候,发现一个问题: 我给DR寄存器赋值一个uint8_t一字节大小的数据,SPI引脚能正确输出数据和时钟,但前面八位正确的数据输 ...

  3. 给 asp.net core 写个中间件来记录接口耗时

    给 asp.net core 写个中间件来记录接口耗时 Intro 写接口的难免会遇到别人说接口比较慢,到底慢多少,一个接口服务器处理究竟花了多长时间,如果能有具体的数字来记录每个接口耗时多少,别人再 ...

  4. unity游戏开发_对象池

    现在假设游戏中我们需要实现一个这样功能,按下鼠标左键,发射一颗子弹,3秒之后消失.在这个功能中,我们发射了上百上千发子弹,就需要实例化生成上百上千次.这时候,我们就需要使用对象池这个概念,每次实例化生 ...

  5. unity_小功能实现(客户端相互通信功能)

    服务器端:在VS中新建项目,用于服务器的搭建 using System;using System.Collections.Generic;     using System.Net.Sockets;u ...

  6. python request接口测试笔记(1)

    python request接口测试笔记(1) 涉及到的功能说明: 需要登录拿到token,才能进行下一个接口的请求 读取csv文件中的信息,作为接口的参数 将接口响应结果,写入csv文件,以便分析统 ...

  7. Leetcode之二分法专题-154. 寻找旋转排序数组中的最小值 II(Find Minimum in Rotated Sorted Array II)

    Leetcode之二分法专题-154. 寻找旋转排序数组中的最小值 II(Find Minimum in Rotated Sorted Array II) 假设按照升序排序的数组在预先未知的某个点上进 ...

  8. Leetcode之深度优先搜索(DFS)专题-515. 在每个树行中找最大值(Find Largest Value in Each Tree Row)

    Leetcode之深度优先搜索(DFS)专题-515. 在每个树行中找最大值(Find Largest Value in Each Tree Row) 深度优先搜索的解题详细介绍,点击 您需要在二叉树 ...

  9. CodeForces Round 525

    A:Ehab and another construction problem #include<bits/stdc++.h> using namespace std; #define F ...

  10. Prometheus安装

    Prometheus安装 下载地址: https://prometheus.io/download/ 现在时间是: 2019.09.07 安装环境: Linux centos7 minimal 虚拟机 ...