【C++11】进阶——lambda&包装器详解

🌈个人主页 :@ꪔ小林Y

✨个人专栏 :《C++》,《C语言》,《数据结构入门------从原理到实战》,《拾光Linux》,《网络安全》

🍀代码信条 :每一行代码都是成长的脚印👣,每一次调试成功都是对坚持的回应

【C++11】2

1. 新的类功能

1.1移动构造和移动赋值

C++98中,有6个默认成员函数:构造函数、析构函数、拷贝构造函数、拷贝赋值重载、取地址重载、const取地址重载 。C++11新增了两个默认成员函数,移动构造函数和移动赋值运算符重载。规则如下:

  • 如果未实现移动构造函数 ,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成 一个默认移动构造。
  • 如果未实现移动赋值重载函数 ,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成 一个默认移动赋值。默认生成的移动赋值函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就用移动赋值,没有实现就调用拷贝赋值。

示例:

cpp 复制代码
namespace Lotso {
class string {
public:
		// ............
    // 移动构造:窃取右值资源
    string(string&& s) {
        cout << "string(string&& s) -- 移动构造" << endl;
        swap(s); // 交换当前对象与右值对象的资源
    }
    // 移动赋值:窃取右值资源
    string& operator=(string&& s) {
        cout << "string& operator=(string&& s) -- 移动赋值" << endl;
        swap(s);
        return *this;
    }
private:
    char* _str = nullptr;
    size_t _size = 0;
    size_t _capacity = 0;
};
}
class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
	{
	}
	/*Person(const Person& p)
	:_name(p._name)
	,_age(p._age)
	{}*/
	/*Person& operator=(const Person& p)
	{
	if(this != &p)
	{
	_name = p._name;
	_age = p._age;
	}
	return *this;
	}*/
	/*~Person()
{}*/
private:
	Lotso::string _name;
	int _age;
};
int main()
{
	Person s1;
	Person s2 = s1;
	Person s3 = std::move(s1);
	Person s4;
	s4 = std::move(s2);
	return 0;
}

1.2成员变量声明时给缺省值&&final和override

  1. 成员变量声明时给缺省值,直接在类中声明成员变量时指定缺省值,初始化列表未显式初始化时,会使用该缺省值
  2. final和override

1.3default 和delete(控制默认函数的生成)

C++98中,想限制某些默认函数的生成,将该函数设置成private,并且只声明不实现,这样其他人调用就会报错。还有若我们要使用某些默认的函数,由于某些原因没生成(例:提供了拷贝构造就不会生成移动构造),这时我们可以用到下面的关键字来解决问题:

  • default:强制编译器生成默认函数 。例如提供了拷贝构造,就不会生成移动构造了,则这时可以使用default关键字显示指定移动构造的生成。
  • delete:限制某些默认函数的生成,在该函数声明加上=delete即可。

示例:

cpp 复制代码
class Person {
public:
    Person(const char* name = "", int age = 0)
        : _name(name), 
        _age(age)
         {}
    // 强制生成默认移动构造
    Person(Person&& p) = default;
    // 禁止拷贝构造
    Person(const Person& p) = delete;
private:
    Lin::string _name;
    int _age;
};

下面这两个类就是可移动不可拷贝的例子:

1.4委托构造&继承构造

  • 委托构造函数就是允许一个构造函数调用同类中的其他构造函数,从而减少代码重复并提高可维护性 。被委托 的构造函数必须初始化所有成员变量。
cpp 复制代码
// 委托构造
class Example
{
public:
	Example(int a, int b)
		:_x(a)
		, _y(b)
	{
		cout << "目标构造函数\n";
	}

	Example(int a)
		:Example(a, 0)
	{
		cout << "委托构造函数\n";
	}

	int _x;
	int _y;
};

class Time
{
public:
	Time(int h,int m)
		:_hour(h)
		,_minute(m)
	{}
	 //error C3511: "Time": 对委托构造函数的调用应仅为成员初始值设定项,被委托函数必须已经实现了全部成员变量的初始化
    // error C2437 : "_second": 已初始化
    Time(int h, int m, int s)
        :Time(h, m)
     // , _second(s)
    {}

private:
    int _hour;
    int _minute;
    int _second = 0;
};

int main()
{
	Example(1, 2);
	Example(1);
	return 0;
}
  • 继承构造函数:允许派生类直接继承基类的构造函数,而不需要手动重新定义它们。派生类继承基类的普通构造函数,特殊的拷贝构造函数/移动构造函数不继承。继承构造函数中派生类自己的成员变量如果有缺省值会使用缺省值初始化,内置类型成员不确定,自定义类型成员使用默认构造初始化。
cpp 复制代码
// 继承构造
class Base
{
public:
	Base(int x,double d)
		:_x(x)
		,_d(d)
	{}

	Base(int x)
		:_x(x)
	{}

	Base(double d)
		: _x(d)
	{}

protected:
	int _x = 0;
	double _d = 0;
};

// 传统的派生类实现构造,很麻烦复杂
//class Derived : public Base {
//public:
//    Derived(int x) : Base(x) {}
//    Derived(double d) : Base(d) {}
//    Derived(int x, double d) : Base(x, d) {}
//};

// C++11继承基类的所有构造函数
class Derived : public Base {
public:
    using Base::Base;
	// 这样的继承构造仅仅适合子类没有变量需要构造的,或者通过缺省值就可以了的。
    /*protected:
        int _i = 0;
        string _s;*/
};
int main()
{
    Derived d1(1);
    Derived d2(1.1);
    Derived d3(2, 2.2);
    return 0;
}

2.lamda(重点)

2.1语法格式

  • lambda表达式本质是一个匿名函数对象 ,跟普通函数不同的是它可以定义在函数内部。一般用auto或者模板参数定义的对象接收lambda对象。
  • lambda表达式的格式:[capture-list] (parameters)-> return type {function boby }
  • [capture_list]:捕捉列表 ,编译器根据\[\]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用,捕捉列表可以传值和传引用捕捉 。捕捉列表为空也不能省略。
  • (parameters):数列表,与普通函数功能类似,不需要参数传递则可以连同()一起省略。
  • ->return type:返回值类型,用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略 。一般返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
  • {function boby}::函数体 ,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量,函数体为空也不能省略。
cpp 复制代码
int main()
{
	//一个简单的lambda表达式
	auto add1=[](int x,int y)->int{return x+y;};
	//也可写成:
	//auto add1=[](int x,int y){return x+y;};
	cout<<add1(1,2)<<endl;
}

2.2捕捉列表

  • lambda 表达式中默认只能用lambda 函数体和参数中的变量 ,如果想用外层作用域中的变量就需要进行捕捉。有下面三种捕捉方式:

【1】第一种捕捉方式是在捕捉列表中显示的传值捕捉和传引用捕捉 ,捕捉的多个变量用逗号分割。x,y, \&z 表示x和y值捕捉,z引用捕捉。

cpp 复制代码
int x = 0;
auto func1 = []()
	{
		x++;
	};
int main()
{
	//只能当前lambda局部域捕捉的对象和全局对象
	//捕捉列表的意义,本质更方便的使用当前局部域的对象
	int a = 0, b = 1, c = 2, d = 3;
	auto func1 = [a, &b]
		{
			//值捕捉的变量不能修改,引用捕捉的变量可以修改
			//a++;
			b++;
			int ret = a + b;
			x++;
			return ret;
			return ret;
		};
	cout << func1() << endl;
	return 0;
}

【2】第二种捕捉方式是在捕捉列表中隐式捕捉 ,我们在捕捉列表写一个 =表示隐式值捕捉,在捕捉列表写一个&表示隐式引用捕捉。

cpp 复制代码
//隐式引用捕捉
//用了哪些变量就捕捉哪些变量
auto func3 = [&]
	{
		a++;
		c++;
		d++;
	};
func3();
cout << a << " " << b << " " << c << " " << d << endl;

【3】第三种捕捉方式是在捕捉列表中混合使用隐式捕捉和显示捕捉 。例:=, \&x表示其他变量隐式值捕捉,x引用捕捉;\&, x, y表示其他变量引用捕捉,x和y值捕捉。当使用混合捕捉时,第一个元素必须是&或= ,并且&混合捕捉时,后面的捕捉变量必须是值捕捉,同理=混合捕捉时,后面的捕捉变量必须是引用捕捉。

cpp 复制代码
//混合捕捉
auto func4 = [&,a,b]
	{
		//a++;
		//b++;
		c++;
		d++;
		return a + b + c + d;
	};
func4();
cout << a << " " << b << " " << c << " " << d << endl;
  • lambda 表达式如果在函数局部域 中,他可以捕捉 lambda 位置之前定义的变量 ,不能捕捉静态局部变量和全局变量,也不需要捕捉 , lambda 表达式中可以直接使用。这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空。
cpp 复制代码
// 局部的静态和全局变量不能捕捉,也不需要捕捉
static int m = 0;
 auto func6 = []
 {
 int ret = x + m;
 return ret;
 };
  • 默认情况下, lambda 捕捉列表是被const修饰的 ,也就是说传值捕捉的过来的对象不能修改 ,mutable加在参数列表的后面可以取消其常量性 ,也就说使用该修饰符后,传值捕捉的对象就可以修改了,但是修改还是形参对象,不会影响实参。使用该修饰符后,参数列表不可省略(即使参数为空)。auto func1 = [a, &b] () mutable

2.3lambda的应用

cpp 复制代码
struct Goods
{
	string _name; // 名字
	double _price; // 价格
	int _evaluate; // 评价
// ...
Goods(const char* str, double price, int evaluate)
			:_name(str)
			, _price(price)
			, _evaluate(evaluate)
			{}
	};
struct ComparePriceLess
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price < gr._price;
	}
};
struct ComparePriceGreater
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
	return gl._price > gr._price;
	}
};
int main()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "⾹蕉", 3, 4 }, { "橙⼦", 2.2, 3
	}, { "菠萝", 1.5, 4 } };
// 类似这样的场景,我们实现仿函数对象或者函数指针⽀持商品中
// 不同项的⽐较,相对还是⽐较⿇烦的,那么这⾥lambda就很好⽤了
	sort(v.begin(), v.end(), ComparePriceLess());
	sort(v.begin(), v.end(), ComparePriceGreater());
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) 
	{
	return g1._price < g2._price;
});
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
	return g1._price > g2._price;
});
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
	return g1._evaluate < g2._evaluate;
});
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
	return g1._evaluate > g2._evaluate;
});
	return 0;
}

2.4lambda的原理

  • lambda 的原理和范围for很像,编译后从汇编指令层的角度看,压根就没有 lambda 和范围for这样的东西。范围for底层是迭代器,而lambda底层是仿函数对象,也就说我们写了⼀个lambda 以后,编译器会生成⼀个对应的仿函数的类。
  • 仿函数的类名是编译按一定规则生成的,保证不同的 lambda 生成的类名不同 ,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是生成的仿函数类的成员变量。
cpp 复制代码
class A
{
public:
	void func()
	{
		int x = 0, y = 1;
		//捕捉this本质是可以访问成员变量
		auto f3 = [x, this]
			{
				_a1++;
				return x + _a1 + _a2;
			};
		cout << f3() << endl;
		
	}
private:
	int _a1 = 0;
	int _a2 = 1;
};
class lambda5
{
public:
	lambda5(int a_,int& b_)
		:a(a_)
		,b(b_)
	{ }
	int operator()(int x)
	{
		++b;
		return a + b + x;
	}
private:
	const int a;
	int& b;
};
int main()
{
auto func5 = [a, &b](int x)
	{
		++b;
		return a + b + x;
	};
//等价于lambda func5(a,b);
func5(1);
return 0;
}

3.包装器

3.1function

std::function是一个类模板,也是一个包装器,其实例对象可包装存储其他可调用对象 (函数指针,仿函数,lambda,bind表达式等)。被定义在 #include< functional > 头文件中。

cpp 复制代码
#include<functional>
int f(int a, int b)
{
	return a + b;
}
struct Functor
{
public:
	int operator() (int a, int b)
{
	return a + b;
}
};
class Plus
{
public:
	Plus(int n = 10)
		:_n(n)
	{}
	static int plusi(int a, int b)
{
		return a + b;
}
	double plusd(double a, double b)
{
		return (a + b) * _n;
}
private:
 	int _n;
 };
 int main()
 {
 //类型擦除,包装各种可调用对象
 	function<int(int, int)> f1 = f;
 	function<int(int, int)> f2 = Functor();
 	function<int(int, int)> f3 = [](int a, int b) {return a + b; };
 	cout << f1(1, 1) << endl;
 	cout << f2(1, 1) << endl;
 	cout << f3(1, 1) << endl;
 
	vector<function<int(int,int)>> v;
	v.push_back(f);
	v.push_back(Functor());
	v.push_back([](int a,int b){return a+b;});
	
	//包装静态成员函数
	function<int(int, int)> f4 = Plus::plusi;
    cout << f4(1, 1) << endl;
   //包装非静态成员函数
	function<double(Plus*,double, double)> f5= &Plus::plusd;
    Plus ps;
    cout << f5(&ps,1.1, 1.1) << endl;
   
	 function<double(Plus, double, double)> f6 = &Plus::plusd;
   //cout << f6(ps, 1.1, 1.1) << endl;两种写法都可以
   cout << f6(Plus(), 1.1, 1.1) << endl;
   
   function<double(Plus&&, double, double)> f7 = &Plus::plusd;
   cout << f7(Plus(), 1.1, 1.1) << endl;
   
   auto pf1=&Plus::plusd;
   Plus* ptr=&ps;
   cout<<(ps.*pf1)(1.1,1.1)<<endl;//对象调用成员函数指针时,使用.*运算符
   cout<<(ptr->*pf1(1.1,1.1)<<endl;
	 return 0;
	 }

逆波兰表达式求值

cpp 复制代码
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        //map存运算符;将运算符及其对应的运算映射起来了
        map<string,function<int(int,int)>> opFuncMap=
        {
            {"+",[](int a,int b){return a+b;}},
            {"-",[](int a,int b){return a-b;}},
            {"*",[](int a,int b){return a*b;}},
            {"/",[](int a,int b){return a/b;}}
            //灵活可变,有其他运算符可以接着写
        };
        stack<int> st;
        for(auto& str:tokens)
        {
            if(opFuncMap.count(str))
            {
                //运算符
                int right=st.top();
                st.pop();
                int left=st.top();
                st.pop();
               
               int ret=opFuncMap[str](left,right);//将匹配的运算符作为key调用operator[]返回对应的value
               st.push(ret);//运算结果入栈
            }
            else
            {
                //运算数
                st.push(stoi(str));//字符串转整形

            }
        }
       return st.top();
    }
};

3.2bind

  • bind是一个函数模板,也是一个可调用对象的包装器,可用来调整参数个数和参数顺序,它也在#include< functional >头文件中。
  • 调用bind一般形式:auto newCallable = bind(callable,arg_list);其中newCallable本身是一个可调用对象 ,arg_list是一个逗号分隔的参数列表 ,对应给定的callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。
cpp 复制代码
#include<functional>
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
int Sub(int a, int b)
{
	return (a - b) * 10;
}
int SubX(int a, int b, int c)
{
	return (a - b - c) * 10;
}
class Plus
{
public:
	static int plusi(int a, int b)
{
	return a + b;
}
double plusd(double a, double b)
{
	return a + b;
}
};
int main()
{
	//bind本质返回的一个仿函数对象
	//调整参数顺序
	//_1代表第一个实参,_2代表第二个实参
	auto f1=bind(Sub,_1,_2);
	auto f2=bind(Sub,_2,_1);
	cout<<f1(10,5)<<endl;
	cout<<f2(10,5)<<endl;
	
	//调整参数个数
	auto f3=bind(SubX,10,_2,_1);
	cout<<f3(15,5)<<endl;
	//底层operator(),调用SubX,第一个参数10,5,15

	// 成员函数对象进⾏绑死,就不需要每次都传递了
   function<double(Plus, double, double)> f6 = &Plus::plusd;
   cout << f6(Plus(), 1.1, 1.1) << endl;
   // bind⼀般⽤于,绑死⼀些固定参数
   function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);
   cout << f7(1.1, 1.1) << endl;
}

🎊到这儿,本期【C++11】进阶------lambda&包装器的内容就结束了。如果文中有表述不准的地方,或是你有更清晰的理解思路,强烈欢迎在评论区留言交流------ 技术路上多碰撞,才能更快进步

觉得内容对你有帮助的话,别忘了点赞❤️➕收藏🌟,方便后续回顾复习;想跟着一起系统学习数据结构的朋友,也可以点击关注,下一期我们会聚焦更进一步的学习。不见不散✌️

相关推荐
.YM.Z9 小时前
【C++11】初识——右值引用和移动语义,可变参数模板
c++·c++11
多弗朗皮卡丘1 天前
C++ 智能指针
开发语言·c++·c++11·智能指针
Tairitsu_H1 个月前
[C++] C++11 Lambda与包装器深度解析
开发语言·c++·c++11·lambda·包装器
Tairitsu_H1 个月前
[C++] C++11类和STL的新特性
开发语言·c++·stl·c++11
Tairitsu_H1 个月前
[C++] C++11右值引用与移动语义揭秘
开发语言·c++·c++11·右值引用·移动语义
Tairitsu_H1 个月前
[C++] C++11新增构造:列表初始化和std::initializer_list
开发语言·c++·c++11·构造
j7~2 个月前
【C++】智能指针的使用及其原理--详解
c++·c++11·内存泄漏·智能指针·raii·boost智能指针
无小道2 个月前
C/C++——异步编程小记
开发语言·c++·c++11
ao-weilai3 个月前
C++基础:C++11部分内容
开发语言·c++·c++11