C++基础之泛型算法
标准库并未给每个容器添加大量功能,因此,通过大量泛型算法,来弥补。这些算法大多数独立于任何特定的容器,且是通用的,可用于不同类型的容器和不同的元素。
迭代器使得算法不依赖容器,但是算法依赖于元素的类型操作;例如某些算法需要元素支持"<"运算。
算法永远不会执行容器操作,它们只会运行于迭代器之上,执行迭代器操作;因此,算法不会改变底层容器的大小,算法可能会改变容器中保存的元素的值,也可能移动元素,但是不会直接添加删除元素。
注意:
- 大部分算法在algorithm头文件中,但是少部分数值相关的算法在numeric头文件中。下面定义在numeric中的算法,会指出来,没有特别说明的是定义在algorithm中。
- 向输出迭代器写入数据的算法一般都假定目标空间容量足够,因此可以将输出迭代器绑定到一个插入迭代器。
- 接受第二个输入序列的算法,如果只有beg2则表示它与第一个大小相同。
- 带_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);
- 参数oldFun是需要bind封装的源函数,
- newFun是封装了参数后的old_Fun,
- arg_list是一个逗号分割的参数列表,对应oldFun的参数,即当我们调用newFun是,它会调用oldFun并且会把参数列表中的参数传给oldFun
- 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++基础之泛型算法的更多相关文章
- C++ 泛型算法
<C++ Primer 4th>读书笔记 标准容器(the standard container)定义了很少的操作.标准库并没有为每种容器类型都定义实现这些操作的成员函数,而是定义了一组泛 ...
- Chapter10:泛型算法
泛型算法的基础是迭代器. 迭代器令算法不依赖于容器,但是算法依赖于元素类型的操作.也即:算法永远不会执行容器的操作. 那么,如果想向容器中添加元素或者执行其他的一些操作呢?标准库提供了插入迭代器来完成 ...
- c++11の泛型算法
一.泛型算法泛型算法这个概念是针对容器操作的,我们知道,c++11的顺序容器有vector,list,deque等,对于这些容器,c++11并没给出相应的增删改查方法,而是定义了一组泛型算法 一般的泛 ...
- c++ primer 11 泛型算法
使用泛型算法必须包含头文件#inlucde <algorithm> 标准库还定义一组泛化的算术算法,其命名习惯与泛型算法相同,包含头文件#include <numeric> f ...
- [.net 面向对象编程基础] (18) 泛型
[.net 面向对象编程基础] (18) 泛型 上一节我们说到了两种数据类型数组和集合,数组是指包含同一类型的多个元素,集合是指.net中提供数据存储和检索的专用类. 数组使用前需要先指定大小,并且检 ...
- C++的那些事:容器和泛型算法
一.顺序容器 1,标准库定义了3种类型的顺序容器:vector.list和deque.它们的差别主要在于访问元素的方式,以及添加或删除元素相关操作运算代价.标准库还提供了三种容器适配器:stack.q ...
- C++ Primer : 第十章 : 泛型算法 之 只读、写和排序算法
大多数算法都定义在<algorithm>头文件里,而标准库还在头文件<numeric>里定义了一组数值泛型算法,比如accumulate. ● find算法,算法接受一对迭代 ...
- 【STL】帮你复习STL泛型算法 一
STL泛型算法 #include <iostream> #include <vector> #include <algorithm> #include <it ...
- C++ Primer 5th 第10章 泛型算法
练习10.1:头文件algorithm中定义了一个名为count的函数,它类似find,接受一对迭代器和一个值作为参数.count返回给定值在序列中出现的次数.编写程序,读取int序列存入vector ...
随机推荐
- WPF DataGrid点击列头选择全列并具有背景色
完成这个操作,主要是XAML的代码. 主要思路是通过绑定多路数据,在多路转换器中返回布尔值,在通过数据触发器来设置被选择的全列的背景色. XAML页面主要代码 首先定义DataGridCell < ...
- restapi(6)- do it the functional way, 重温函数式编程
再次看了看上篇博客的源代码,发现连自己都看不懂了.想是为了赶时间交货不知不觉又回到OOP行令模式了,看看下面这段代码: (post & parameters('pid,'desc.?,'wid ...
- 16.XML语法、CDATA、约束(DTD、Schema)讲解
xml主要用来描述数据,比如配置文件,网络之间传输数据等,并且在android中也经常用xml来布局,,接下来便来学习xml常用的东西 1.XML语法 xml语法分为: 1.1 文档声明 必须位于文档 ...
- VMware Workstation 15 Pro 永久激活密钥
VMware Workstation 15 Pro 永久激活密钥 一. 激活密钥 YG5H2-ANZ0H-M8ERY-TXZZZ-YKRV8 UG5J2-0ME12-M89WY-NPWXX-WQH ...
- 并查集(不相交集合)详解与java实现
目录 认识并查集 并查集解析 基本思想 如何查看a,b是否在一个集合? a,b合并,究竟是a的祖先合并在b的祖先上,还是b的祖先合并在a上? 其他路径压缩? 代码实现 结语 @(文章目录) 认识并查集 ...
- (四十九)c#Winform自定义控件-下拉框(表格)
前提 入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章. GitHub:https://github.com/kwwwvagaa/NetWinformControl 码云:ht ...
- Spring学习之旅(三)--装配Bean
装配 Bean 的方式 在 XML 中进行显式配置 在 Java 中进行显式配置 隐式的 Bean 发现机制和自动装配 Spring 提供了以上三种方式进行 Bean 的配置,可以根据自己的需求选择一 ...
- python 32 操作系统与进程
目录 1. 操作系统 1.1 作用 1.2 操作系统的发展 2. 进程的理论 2.1 相关名词 2.2 进程的创建 2.3 进程的状态: 1. 操作系统 管理.控制.协调计算机硬件与软件资源的计算 ...
- HDU 6299
题意略. 思路: 我们先把所有字符串处理成 ")))((((" 这样的形式.然后我们把这些字符串排个序,按照min(l,r)来排,小的在前,在我的代码中,l为 ( 的个数,r 为 ...
- 理解Java反射机制
理解Java反射机制 转载请注明出处,谢谢! 一.Java反射简介 什么是反射? Java的反射机制是Java特性之一,反射机制是构建框架技术的基础所在.灵活掌握Java反射机制,对学习框架技术有很大 ...