C++ 泛型算法学习笔记

C++ 泛型算法学习笔记(一)

关键概念:泛型算法永远不会执行容器的操作
算法可能改变容器中保存的元素的值,也可能在容器内移动元素,但永远不会直接改变容器的大小。

初识泛型算法

只读算法
  • 求和算法accumulate()
// 对vec中的元素求和,和的初值设为0
// 前提是容器元素的类型有定义+运算符
int sum = accumulate(vec.cbegin(), vec.cend(), 0);
  • 比较算法equal()
// 要求:roster2中的元素数目应该至少与roster1一样多
equal(roster1.cbegin(), roster1.cend(), roster2.cbegin());
写容器元素的算法
  • fill()
    将给定的值赋予输入序列中的每个元素
fill(vec.begin(), vec.end(), 0);
// or
fill(vec.begin(), vec.begin(),+vec.size()/2, 0);

注:算法不检查写操作,因此我们要自己确保写的位置是安全的

  • fill_n()
vector<int> vec(10); // 空vector,必须指定vec的长度
fill_n(vec.begin(), 10, 0); // 修改vec中的10个元素,vec的长度必须大于等于10
// or
fill_n(vec.begin(), vec.end(), 0);
  • back_inserter()
    back_inserter接受一个指向容器的引用,返回一个与该容器绑定的插入迭代器。当我们通过此迭代器赋值时,赋值运算符会调用push_back将一个具有给定值的元素添加到容器中:
vector<int> vec; // 空变量
auto it = back_inserter(vec); // 通过它赋值会将元素添加到vec中
*it = 42; // vec中现在又一个元素, 值为42
// True: 插入迭代器可用来向vec添加元素
fill_n(back_inserter(vec), 10, 99);

// 结果: vec = {42 99 99 99 99 99 99 99 99 99 99}
  • copy()
int a1[] = {0, 1, 2, 3, 4};
int a2[sizeof(a1)/sizeof(*a1)]; // 确保a1和a2长度相同
// ret指向拷贝到a2的尾元素之后的位置
auto ret = copy(begin(a1), end(a1), a2);
for(auto &val:a2)
{
    cout<<val<<" ";
}
重排容器元素的算法
  • sort()
    注:使用sort前要加入头文件algorithm
#include "algorithm"

调用sort会重排输入序列中的元素,使之有序,它是利用元素类型的<运算符实现排序的(意思是:容器元素的类型必须支持<运算)

vector<string> svec = {"q", "w", "e", "r", "t", "y", "t", "t", "u"};
sort(svec.begin(), svec.end());
// 结果: svec = {e q r t t t u w y}
  • unique()
    unique算法重排输入序列,重复的元素被调到容器的尾部
vector<string> svec = {"q", "w", "e", "r", "t", "y", "t", "t", "u"};
sort(svec.begin(), svec.end());
// unique重排输入范围,使得每个元素只出现一次
// 排列在范围的前部,返回指向不重复区域之后一个位置的迭代器
auto end_unique = unique(svec.begin(), svec.end());
// 使用向量操作erase删除重复元素
svec.erase(end_unique, svec.end());

// 结果: svec = {e q r t u w y}

定制操作

向算法传递函数

谓词:谓词是一个可调用的表达式,其返回结果是一个能用作条件的值。谓词可分为两类:一元谓词(只接受单一参数)和二元谓词(有两个参数)

  • 重载sort()
// 比较函数,用来按长度排序
bool isbigger(const string &s1, const string &s2)
{
    return s1.size() < s2.size();
}

int main()
{
    vector<string> svec = {"qsa", "wggvcxv", "esaswq", "rjuyu", "t", "ysa"};
    sort(svec.begin(), svec.end(), isbigger);
    for(auto &val:svec)
    {
        cout<<val<<" ";
    }

    getchar();
    return 0;
}

结果: t qsa ysa rjuyu esaswq wggvcxv
lambda表达式

一个lambda表达式表示一个可调用的代码单元。可以将其理解为一个未命名的内联函数。与任何函数类似,一个lambda具有一个返回类型、一个参数列表和一个函数体。但与函数不同,lambda可能定义在函数内部。通用形式:

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

例如:
简单应用

auto f = [] { return 42; };
cout<<f()<<endl; // 打印42 

代替谓词

vector<string> svec = {"qsa", "wggvcxv", "esaswq", "rjuyu", "t", "ysa"};
sort(svec.begin(), svec.end(), 
    [](const string &s1, const string &s2) 
        {return s1.size() < s2.size();} );
结果: svec = {t qsa ysa rjuyu esaswq wggvcxv}
  • 使用捕获列表和find_if
这里与find_if合作,查找第一个长度大于3的元素

vector<string> svec = {"qsa", "wggvcxv", "esaswq", "rjuyu", "t", "ysa"};
size_t a = 3;
// 获取一个迭代器,指向第一个满足size()>a的元素
auto wc = find_if(svec.begin(), svec.end(), 
    [a](const string &s1) 
        {return s1.size() > a;} );

cout<<*wc<<endl;

结果: wggvcxv
  • for_each
    此算法接受一个可调用的对象,并对输入序列中每个元素调用此对象
vector<string> svec = {"qsa", "wggvcxv", "esaswq", "rjuyu", "t", "ysa"};
for_each(svec.begin(), svec.end(), 
         [](const string &s1){cout<<s1<<" ";});

结果: qsa wggvcxv esaswq rjuyu t ysa
  • 隐式捕获
    编译器可以根据lambda体中的代码来推断我们要使用哪些变量。为了指示编译器推断捕获列表,应在捕获列表中写一个&=&告诉编译器采用捕获引用方式,=表示采用值捕获方式。
vector<string> svec = {"qsa", "wggvcxv", "esaswq", "rjuyu", "t", "ysa"};
size_t a = 3;
// 获取一个迭代器,指向第一个满足size()>a的元素
auto wc = find_if(svec.begin(), svec.end(), 
    [=](const string &s1) 
        {return s1.size() > a;} );
  • 混合捕获
    如果我们希望对一部分变量采用值捕获,对其他变量采用引用捕获,可以混合使用隐式捕获和显式捕获:
vector<string> svec = {"qsa", "wggvcxv", "esaswq", "rjuyu", "t", "ysa"};
size_t a = 3;
int i = 0;
// 获取一个迭代器,指向第一个满足size()>a的元素
auto wc = find_if(svec.begin(), svec.end(),
// a是隐式值捕获, i 是显式引用捕获 
    [=, &i](const string &s1) 
        {   i++;
            cout<<i<<" ";
            return s1.size() > a;} );

结果: 1 2
参数绑定
  • bind()
    find_if接受一个一元谓词,还有其他容器算法接受一元谓词或者二元谓词。但是如果我们的谓词函数需要超过2个输入参数,那么应该怎么办呢?
    例如函数:
bool check_size(const string &s, string::size_type sz)
{
	return s.size() >= sz;
}

我们可以使用bind函数让目标函数与参数绑定。调用bind的一般形式是:

auto newCallable = bind(callable, arg_list);

其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。即,当我们调用newCallable时,newCallable会调用callable,并传递给它arg_list中的参数。

arg_list中的参数可能包含形如_n的名字,其中n是一个整数。这些参数是“占位符”,表示newCallable的参数,她们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。

范例:

// check 6是一个可调用对象,接受一个string类型的参数
// 并用此string和值6来调用check_size
auto check6 = bind(check_size, _1, 6)

此bind调用只有一个占位符,表示check6只接受单一参数。占位符出现再arg_list的第一个位置,表示check6的此参数对应check_size的第一个参数。此参数是一个const string&。

string s = "hello";
//等同于调用check_size(s, 6)
bool b1 = check6(s);

因此,将check_size函数作为一元谓词作用于find_if中的方法:

auto wc = find_if(svec.begin(), svec.end(), bind(check_size, _1, sz));

注:使用bind函数前必须如下操作:

#include "functional"

_n都定义在std::placeholders的命名空间,因此使用占位符时要使用:

std::placeholders::_1

或者

using namespace std::placeholders;

绑定多参数情况

#include "iostream"
#include "vector"
#include "string"
#include "algorithm"
#include "functional"

using namespace std;
using namespace std::placeholders;

bool isbigger(const string &s1, int num1, const string &s2, int num2, int num3)
{
    cout<<num1<<" "<<num2<<" "<<num3<< endl;
    return s1.size() > s2.size();
}

int main()
{
    vector<string> svec = {"qsa", "wggvcxv", "esaswq", "rjuyu", "t", "ysa"};
    sort(svec.begin(), svec.end(), bind(isbigger, _1, 4, _2, 5, 6));

    for(auto &val:svec)
    {
        cout<<val<<" ";
    }

    getchar();
    return 0;
}

结果:
4 5 6
4 5 6
4 5 6
4 5 6
4 5 6
4 5 6
4 5 6
4 5 6
4 5 6
4 5 6
4 5 6
4 5 6
wggvcxv esaswq rjuyu qsa ysa t

用bind重排参数顺序

刚刚的程序改成:调换_1和_2的位置
排列顺序会反转
sort(svec.begin(), svec.end(), bind(isbigger, _2, 4, _1, 5, 6));

结果
4 5 6
4 5 6
4 5 6
4 5 6
4 5 6
4 5 6
4 5 6
4 5 6
4 5 6
4 5 6
4 5 6
4 5 6
4 5 6
4 5 6
4 5 6
t qsa ysa rjuyu esaswq wggvcxv

绑定引用参数
默认情况下,bind的那些不是占位符的参数被拷贝到bind返回的可调用对象中。如果我们希望传递给bind一个引用对象,就必须使用标准库ref函数:

#include "iostream"
#include "vector"
#include "string"
#include "algorithm"
#include "functional"

using namespace std;
using namespace std::placeholders;

bool isbigger(const string &s1, const string &s2, int num1, int num2, int& res)
{
    res = num1 + num2;
    return s1.size() > s2.size();
}

int main()
{
    vector<string> svec = {"qsa", "wggvcxv", "esaswq", "rjuyu", "t", "ysa"};
    int result = 0;
    sort(svec.begin(), svec.end(), bind(isbigger, _1, _2, 5, 6, ref(result)));

    cout<<"result = "<<result<<endl;

    getchar();
    return 0;
}

结果:
result = 11

注:标准库还有一个cref,生成一个保护const引用的类。refcref也定义在头文件functional中。

特定容器算法

对于list和forward_list, 应该优先使用成员函数版本的算法而不是通用算法。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
链表特有的操作会改变容器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值