c++中自定义排序函数的实现示例+lambda表达式的基础运用

在实际编程或者工程中会经常遇到排序问题,而且很多情况下的排序不仅仅是按大小排序的,有可能按字典顺序排名称;或者多个排序规则:(例如:考试成绩排名:先按成绩排,然后相同成绩学号小的排在前面);或者规定必须按照其他数据中出现的顺序排等等 各种各样的排序规则。

这个时候系统自带的排序 就很难或者说实现起来会很麻烦,可能需要排序好几次。

那么就可以考虑自定义排序函数,来实现排序的需求。

C++11后STL中自带sort函数,

sort(1, 2 , 3) 参数一二分别为数据结构需要排序的范围(开始和结束),参数3为可选的自定义排序函数 返回类型为bool(表示大小判断的结果)

做法1:直接自定义数据结构,写好赋值和判断大小的函数

好处:更加灵活,但是编程会复杂,容易出bug

如果要自己定义STL容器的元素类最好满足STL容器对元素的要求
    必须要求:
     1、Copy构造函数
     2、赋值=操作符
     3、能够销毁对象的析构函数
    另外:
     1、可用的缺省构造函数,序列型容器必须,用于初始化元素
     2、==操作符定义,用于判断相等
     3、<操作符定义,关联型容器必须,用于缺省排序

你可在struct內加入 operator < ,就可以使struct有排序能力.
因為而你的pcd struct內沒有指針,所以不須要有copy constructor
和copy assignment, 編譯器會為你提供的, 你不須要自己做的.
當你要排序時只要寫 sort( obj.begin(), obj.end() )就可.

做法2:使用STL提供的数据结构,自定义排序函数即可

虽然没那么灵活,但也能满足排序的需求,编程容易

这里可以额外写排序函数

方法1:写成全局的函数 ,返回bool类型,使用的适合只需要在sort()的第三个参数写上函数名称即可

例如:根据string的长度排序:


#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;

bool lengthSortLess(const string &s1, const string &s2)
{
	return s1.length() > s2.length();  //长度大为true,则说明是按长度从大到小排序
}
bool lengthSortGreater(const string &s1, const string &s2)
{
	return s1.length() < s2.length();  //长度大为true,则说明是按长度从大到小排序
}

int main()
 {
	vector<string> re = { "2","156","0","8","4564","489798798" };
	sort(re.begin(), re.end(), lengthSortLess);
	cout << "全局函数形式的自定义排序" << endl;
	for (auto i : re)
	{
		cout << i << endl;
	}

	cout << "lambda表达式的自定义排序" << endl;
	sort(re.begin(), re.end(), [&](string s1,string s2) {
		return s1.length() < s2.length();
	});
	for (auto i : re)
	{
		cout << i << endl;
	}
	return 0;
}

(上面lambda表达式是从短到长排序的)

方法2:使用lambda表达式的形式和sort写到一起,但是这样就会导致,其他地方不能灵活使用

Lambda表达式(也叫lambda函数,或简称lambda),是从C++ 11开始引入并不断完善的,是能够捕获作用域中变量的匿名函数对象。因为C++是不能嵌套定义函数的,所以lambda就成了我们构造闭包的主要手段,不过在对象的生命周期上还是有点不同。本文主要展示lambda的基本使用

 Lambda 表达式名字的由来。实际上这个名字来自微积分数学中的 λ,其涵义是声明为了表达一个函数具体需要什么。更确切的说,它描述了一个数学逻辑系统,通过变量结合和替换来表达计算。

lambda的基本语法如下:

当定义一个lambda时,编译器生成一个与lambda对应的新的(未命名的)类类型。下面对重要的组成部分进行说明:

1.捕获列表 [ ]

捕获列表是零或多个捕获符的逗号分隔符列表,可选地以默认捕获符开始(仅有的默认捕获符是 & 和 = )。默认情况下,从lambda生成的类都包含一个对应该lambda所捕获变量的数据成员。类似任何普通类地数据成员,lambda的数据成员也在lambda对象创建时被初始化。类似参数传递,变量的捕获方式也可以是值或引用。

&为引用捕获,=为值捕获

值捕获:

 
  1. void func()

  2. {

  3. int i = 100;//局部变量

  4. //将i拷贝到明位f的可调用对象

  5. auto f = [i] { return i; };

  6. i = 0;

  7. int j = f(); //j=100,因为i是创建时拷贝的

  8. }

引用捕获:

 
  1. void func()

  2. {

  3. int i = 100;//局部变量

  4. //对象f包含i的引用

  5. auto f = [&i] { return i; };

  6. i = 0;

  7. int j = f(); //j=0,传递的是引用

  8. }

除了自己列出捕获列表的变量,还可以让编译器根据lambda中代码来推断我们要使用哪些变量(隐式捕获),用过使用&或=指示编译器推断捕获列表。&则采用引用捕获的方式,=则采用值捕获的方式。混合使用隐式捕获和显示捕获,则两者须使用不同的方式,一个为引用捕获,一个为值捕获。

lambda捕获列表:

  • [ ]。空捕获列表,lambda不能使用所在函数中的变量。
  • [=]。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
  • [&]。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。
  • [this]。函数体内可以使用Lambda所在类中的成员变量。
  • [a]。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
  • [&a]。将a按引用进行传递。
  • [=,&a, &b]。除a和b按引用进行传递外,其他参数都按值进行传递。
  • [&, a, b]。除a和b按值进行传递外,其他参数都按引用进行传递。

悬垂引用:

若以引用隐式或显式捕获非引用实体,而在该实体的生存期结束之后调用lambda对象的函数调用运算符,则发生未定义行为。C++ 的闭包并不延长被捕获的引用的生存期。这同样适用于被捕获的this指针所指向的对象的生存期。

1.2.形参列表 ( ) 

lambda形参列表和一般的函数形参列表类似,但不允许默认实参(C++14 前)。当以 auto 为形参类型时,该 lambda 为泛型 lambda(C++14 起)。与一个普通函数调用类似,调用一个lambda时给定的实参被用来初始化lambda的形参。

 
  1. //代码在VS2019中测试

  2. void func()

  3. {

  4. int i = 1, j = 2;

  5. auto f = [](int a,int &b) {

  6. a = 10;

  7. b = 20;

  8. //输出:10 20

  9. std::cout << a << " " << b << std::endl;

  10. };

  11. f(i,j);

  12. //输出:1 20

  13. std::cout << i << " " << j << std::endl;

  14. }

 
  1. //代码在VS2019中测试

  2. void func()

  3. {

  4. auto f = [](auto a,int b=10) {

  5. std::cout << a << " " << b << std::endl;

  6. };

  7. f(1.5, 2);

  8. f(true);

  9. }

1.3.说明符 

允许以下说明符:

  • mutable:允许 函数体 修改各个复制捕获的对象,以及调用其非 const 成员函数;
  • constexpr:显式指定函数调用运算符为 constexpr 函数。此说明符不存在时,若函数调用运算符恰好满足针对 constexpr 函数的所有要求,则它也会是 constexpr;  (C++17 起)
  • consteval:指定函数调用运算符为立即函数。不能同时使用 consteval 和 constexpr。(C++20 起)

默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。假如我们希望能改变一个被捕获的变量的值,就必须在参数列表后面加上关键字mutable。而一个引用捕获的变量则不受此限制。 

 
  1. //代码在VS2019中测试

  2. void func()

  3. {

  4. int i = 10, j = 10;

  5. //加上mutable才可以在lambda函数中改变捕获的变量值

  6. auto f = [i, &j]() mutable {

  7. i = 100, j = 100;

  8. };

  9. i = 0, j = 0;

  10. f();

  11. //输出:0 100

  12. std::cout << i << " " << j << std::endl;

  13. }

1.4.返回类型 ->

当我们需要为一个lambda定义返回类型时,需要使用尾置返回类型。返回类型若缺省,则根据函数体中的 return 语句进行推断(如果有多条return语句,需要保证类型一直,否则编译器无法自动推断)。默认情况下,如果一个lambda函数体不包含return语句,则编译器假定返回void。

 
  1. void func()

  2. {

  3. auto f = []() ->double {

  4. if (1 > 2)

  5. return 1;

  6. else

  7. return 2.0;

  8. };

  9. std::cout << f() << std::endl;

  10. }

如果不显示指定返回类型,则int和double两种返回类型会导致推断冲突。

1.5 lambda表达式产生的函数如何存储起来使用:

例如:

  1. std::function<int(int)> func2 = [](int i) { return i + 4; };    //<>中第一个int是函数返回类型,第二个int是函数的形参类型

  2. std::cout << "func2: " << func2(6) << '\n';

也可以多个参数,就跟正常函数一样,只不过一般lambda表达式代表的函数都比较短

    std::function<int(int,int)> fun2 = [=](int a,int b) {return a+b; };
    cout << "fun2(3)=" << fun2(3,5) << endl;

以上lambda表达式部分来源于下面大佬的链接

https://blog.csdn.net/gongjianbo1992/article/details/105128849

猜你喜欢

转载自blog.csdn.net/weixin_42067304/article/details/109953768
今日推荐