【C++11】可变参数模板、lambda表达式、包装器类及 bind函数 (下)

前言
上一篇我们已经将C++11开了个头,详细讲述了C++11中的 { } 列表初始化和右值引用,本章将继续讲解C++11中的一些新的且比较实用的功能.

1.1万能模板:

模板中的&& 不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
万能模板的有样式:

           template< typename  T>
           void PerfectForward(T&& t)
  • 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值
  • 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力
  • 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值
void Fun(int &x){ cout << "左值引用" << endl; }
void Fun(const int &x){ cout << "const 左值引用" << endl; }
void Fun(int &&x){ cout << "右值引用" << endl; }
void Fun(const int &&x){ cout << "const 右值引用" << endl; }

/ 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,


  • 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,
  • 我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发std::forward

1.2 完美转发

std::forward 完美转发在传参的过程中保留对象原生类型属性
在这里插入图片描述

void Fun(int &x){ cout << "左值引用" << endl; }
void Fun(const int &x){ cout << "const 左值引用" << endl; }
void Fun(int &&x){ cout << "右值引用" << endl; }
void Fun(const int &&x){ cout << "const 右值引用" << endl; }
// std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
template<typename T>
void PerfectForward(T&& t)
{
     Fun(std::forward<T>(t));
}
int main()
{
    PerfectForward(10); // 右值
    int a;
    PerfectForward(a); // 左值
    PerfectForward(std::move(a)); // 右值

}
  • C++11新提供的forward函数模板,可以解决上述问题,使得传右值不会退化成左值

完美转发的使用场景:

   template<class T>
struct ListNode
{
ListNode* _next = nullptr;
ListNode* _prev = nullptr;
T _data;
};
template<class T>
class List
{
typedef ListNode<T> Node;
public:
List()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
void PushBack(T&& x)
{
//Insert(_head, x);
Insert(_head, std::forward<T>(x));
}
void PushFront(T&& x)
{
//Insert(_head->_next, x);
Insert(_head->_next, std::forward<T>(x));
}
void Insert(Node* pos, T&& x)
{
Node* prev = pos->_prev;
Node* newnode = new Node;
newnode->_data = std::forward<T>(x); // 关键位置
// prev newnode pos
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = pos;
pos->_prev = newnode;
}
void Insert(Node* pos, const T& x)
{
Node* prev = pos->_prev;
Node* newnode = new Node;
newnode->_data = x; // 关键位置
// prev newnode pos
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = pos;
pos->_prev = newnode;
}
private:
Node* _head;
};

1.3 可变参数模板:

首先在学C语言的时候,我们用的printf函数是一个可变参数函数,printf从语法上说是可以写多个参数,然后该函数自己识别。

C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板。 而
C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。

可变模版参数比较抽象,使用起来需要一定的技巧,所以这块还是比较晦涩的。现阶段呢,我们掌握一些基础的可变参数模板特性就够我们用了

可变参数模板:

                    **template** <class ...Args>
                  void ShowList1(Args...args)      

解释:

  • Args是一个模板参数包,args是一个函数形参参数包
  • 声明一个参数包 Args…args,这个参数包中可以包含0到任意个模板参数
template <class ...Args>
void ShowList1(Args... args)
{
	//参数个数
	cout << sizeof...(args) << endl;
}

//不一定非要用Args也可以取别的名字
template <class ...X>
void ShowList2(X... y)
{
	cout << sizeof...(y) << endl;
}

int main()
{
	ShowList1(1, 'x', 1.1);
	ShowList2(1, 2, 3, 4, 5);

	return 0;
}

可变模板参数包展开:

/方法一:
%Args... args代表N个参数表(N>=0)
//递归到最后一个,就找最匹配的那一个
template <class T> ---0
void ShowList(const T& val)
{
	cout << val << "->" << typeid(val).name() << "end" << endl;
}

//编译时递归去推
//整个推导的过程是在编译的时候进行的(这是个编译的过程 -- 编译时决议)
template <class T, class ...Args>
void ShowList(const T& val, Args... args)
{
	cout << sizeof...(args) << endl;
	cout << val << "->" << typeid(val).name() << endl;

	//去递归解析
	ShowList(args...);
}

/方法二:

//0个模板参数走这个 -- 作为递归的终结
void ShowList()
{}
  • 整个推导的过程是在编译的时候进行的(这是个编译的过程 – 编译时决议)

1.4 emplace_back:

在这里插入图片描述
C++11STL中新增了一个尾插的方法:

  • emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象
  • 那么在这里我们可以看到除了用法上,和push_back没什么太大的区别
  • 可以不传pair,可以直接传参数
  • 拿到参数自己构建对象(保证了底层一定是右值 —— 直接去构造的)
    在这里插入图片描述
int main()
{
/ 下面我们试一下带有拷贝构造和移动构造的bit::string,再试试呢
/ 我们会发现其实差别也不到,emplace_back是直接构造了,
/ push_back是先构造,再移动构造,其实也还好。
std::list< std::pair<int, bit::string> > mylist;
mylist.emplace_back(10, "sort");
mylist.emplace_back(make_pair(20, "sort"));
mylist.push_back(make_pair(30, "sort"));
mylist.push_back({ 40, "sort"});
return 0;
}

1.5 lambda表达式:

在仿函数那一节我们知道,如果我们用每次为了实现一个sort算法,因为每次比较的数据类型不同,都要重新去写一个类(仿函数),如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便,因此,在C++11语法中出现了Lambda表达式

1.5.1 lambda表达式的定义:

定义了一个可以调用的对象 / 匿名函数,一般定义在局部,特点是可以深度绑定了局部的数据。
lambda表达式书写格式:

      [capture-list] (parameters) mutable -> return-type { statement }
       捕捉列表         参数         控制属性     返回值         函数体
  • lambda表达式,实际上是一个匿名函数,实际上是在定义一个局部函数
  • lambda表达式,通常是来定义小函数
  • lambda表达式,在局部是很好用的
  • 普通函数有函数名,lambda函数没有函数名,他是一个整体。

auto Add1 = [](int x, int y)->int{return x + y; }; Add叫lambda表达式的对象 –-lambda定义的是一个对象

lambda表达式各部分说明:

  1. [capture-list] :捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
  2. (parameters): 参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。
  3. mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
  4. ->returntype: 返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
  5. {statement}: 函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的量。

注意:

  • 在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空
  • 因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

1.5.2 lambda表达式的用法:

int main()
{
// 最简单的lambda表达式, 该lambda表达式没有任何意义
[]{};
// 省略参数列表和返回值类型,返回值类型由编译器推导为int
int a = 3, b = 4;
[=]{return a + 3; };
// 省略了返回值类型,无返回值类型
auto fun1 = [&](int c){b = a + c; };
fun1(10)
cout<<a<<" "<<b<<endl;
// 各部分都很完善的lambda函数
auto fun2 = [=, &b](int c)->int{return b += a+ c; };
cout<<fun2(10)<<endl;
// 复制捕捉x
int x = 10;
auto add_x = [x](int a) mutable { x *= 2; return a + x; };
cout << add_x(10) << endl;
return 0;
}
  • 一般是局部匿名函数 也可以写到全局(一般都不写返回值
  • 参数列表(无参的时候)和返回值类型(表达式自动推导)也可以省略掉
  • lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量

1.5.3 捕捉列表的用法:

捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。

  • [var]:表示值传递方式捕捉变量var
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递方式捕捉当前的this指针
int main()
{
	int a = 0, b = 200;

	//方法一:
	auto Swap1 = [](int& x, int& y)->void {
		int tmp = x;
		x = y;
		y = tmp;
	};

	Swap1(a, b);
	cout << a << " " << b << endl;

	//方法二:
	//mutable 只是让传值捕捉变量const属性去掉了
	//mutable要和()参数列表配在一起
	//可以认为里面的a,b还是外面a,b的拷贝 -- 里面改变外面还是不变
	//所以mutable实际没什么价值
	//这样写还是没有交换 -- 只是编译通过了但是达不到我们想要的效果
	//auto Swap2 = [a, b]()mutable->void {
	//	int tmp = a;
	//	a = b;
	//	b = tmp;
	//};
int main()
{
	int c = 2, d = 3, e = 4, f = 5, g = 6, ret;

	//传值的方式捕捉全部对象
	auto Func1 = [=] {
		return c + d * e / f + g;
	};

	cout << Func1() << endl;

	//传引用捕捉全部对象
	auto Func2 = [&] {
		ret = c + d * e / f + g;
	};

	Func2();
	cout << ret << endl;

	//混着捕捉
	auto Func3 = [c, d, &ret] {
		ret = c + d;
	};

	Func3();
	cout << ret << endl;

	//ret传引用捕捉 其他全部传值捕捉
	auto Func4 = [=, &ret] {
		ret = c + d * e / f + g;

		//传值捕捉默认是加了const的
		//c = 1;
	};

	Func4();
	cout << ret << endl;

	return 0;
}

1.5.4 函数对象与lambda表达式:

函数对象,又称为仿函数,即可以想函数一样使用的对象,就是在类中重载了operator()运算符的类对象

class Rate
{
public:
Rate(double rate): _rate(rate)
{}
double operator()(double money, int year)
{ return money * _rate * year;}
private:
double _rate;
};

在这里插入图片描述

  • 从使用方式上来看,函数对象与lambda表达式完全一样
  • 将lambda表达式赋值给相同类型的函数指针
  • 仿函数的名称后面叫uuid,一组随机字符串,通过某个算法得到的,使得每个lambda表达式的名字都不同
    在这里插入图片描述

1.6 包装器:

function包装器
function包装器 也叫作适配器。 C++中的function本质是一个类模板,也是一个包装器。
那么我们来看看,我们为什么需要function呢?
在这里插入图片描述
模板参数说明:

  • Ret: 被调用函数的返回类型
  • Args…: 被调用函数的形参
template<class F, class T>
T useF(F f, T x)
{
	static int count = 0;
	cout << "count:" << ++count << endl;
	cout << "count:" << &count << endl;

	return f(x);
}

double f(double i)
{
	return i / 2;
}

struct Functor
{
	double operator()(double d)
	{
		return d / 3;
	}
};

int main()
{
	//函数名
	cout << useF(f, 11.11) << endl;
	//函数对象
	cout << useF(Functor(), 11.11) << endl;
	//lamber表达式
	cout << useF([](double d)->double{ return d / 4; }, 11.11) << endl;

	return 0;
}
  • func可能是函数名?函数指针?函数对象(仿函数对象)?也有可能是lamber表达式对象?
  • 这些都是可调用的类型!

1.7 Std:: bind函数:

在这里插入图片描述

  • Std:Bind函数是一个函数模板,可以用来调整参数的顺序
  • 它就像一个函数包装器(适配器)接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。
    在这里插入图片描述
// 参数调换顺序
std::function<int(int, int)> func4 = std::bind(&Sub::sub, s,
placeholders::_2, placeholders::_1);
cout << func3(1, 2) << endl;
cout << func4(1, 2) << endl;

1.7.1 bind返回值类型:

std::bind函数的返回类型是一个未指定的对象类型,它是一个函数对象,但具体的类型是实现相关的,并且非常复杂。

使用代码举例:

   // 使用举例
#include <functional>
int Plus(int a, int b)
{
return a + b;
}
class Sub
{
public:
int sub(int a, int b)
{
return a - b;
}
};
int main()
{
//表示绑定函数plus 参数分别由调用 func1 的第一,二个参数指定
std::function<int(int, int)> func1 = std::bind(Plus, placeholders::_1,
placeholders::_2);
//auto func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
//func2的类型为 function<void(int, int, int)> 与func1类型一样
//表示绑定函数 plus 的第一,二为: 1, 2
auto func2 = std::bind(Plus, 1, 2);
cout << func1(1, 2) << endl;
cout << func2() << endl;
Sub s;
// 绑定成员函数
std::function<int(int, int)> func3 = std::bind(&Sub::sub, s,
placeholders::_1, placeholders::_2);

尾声
看到这里,相信大家对这个C++有了解了。
如果你感觉这篇博客对你有帮助,不要忘了一键三连哦

  • 25
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值