c++11(三)

一、可变参数

1、可变参数模板

c语言中的 scanf 和 printf 可以支持我们传入任意个数的参数,原理就是用了参数包。

//可变参数包
template<class ...Args>
void Print(Args... args)
{}

Args:模板参数包

args:函数形参参数包

声明一个参数包:Args... args  

Args... 表示有0~n个类型,args 表示模板参数定义的形参参数包。

sizeof...(args):求参数包中参数个数

2、参数包在编译时的参数推导递归(方法一)

//最后参数包个数是0时打印换行
void _Printf()
{
	cout << endl;
}

//子函数递归调用 把参数包中的值全部打印
template<class T, class ...Args>
void _Printf(T& t, Args... args)
{
	cout << t << endl;
	_Printf(args...);
}

//主函数调用子函数传入参数包
template<class ...Args>
void Printf(Args... args)
{
	_Printf(args...);
}

int main()
{
	Printf(1, 2.2, "string");
	return 0;
}

结果:

3、运用数组初始化展开参数包(方法二)

template<class T>
int Printf(T t)
{
	cout << t << endl;
	return 0;
}


template<class ...Args>
void showList(Args... args)
{
	//定义数组是为了在构造时展开参数包
	int a[] = { Printf(args)... };//函数Printf推导参数包个数
	cout << endl;
}

int main()
{
	showList(1, "hello", 'x');
	return 0;
}

a 数组创建时,会根据 { } 中的参数进行初始化,可以在此直接将可变参数包展开,展开过程中就完成了参数的解析工作。

结果:

4、emplace系列函数

在stl容器中插入数据可以使用emplace系列函数,这样在多数情况下效率最高。

可以看到用的就是参数包形式的传参,在模板中 Args&&... args 是万能引用。

下面我们对比push_back和emplace_back来讨论emplace系列函数的优势

int main()
{
	std::list<bit::string> l;

	bit::string str1 = "Hello";
	bit::string str2 = "Hello";

	// 插入左值
	l.push_back(str1);
	l.emplace_back(str2);
	cout << endl;

	// 插入 move 出来的右值
	l.push_back(move(str1));
	l.emplace_back(move(str2));
	cout << endl;

	return 0;
}

结论1:参数是左值和move得到的右值,两者并无区别。

int main()
{
	// 插入纯右值
	l.push_back("World");
	l.emplace_back("World");

	return 0;
}

emplace_back函数直接都没有移动构造,emplace 系列函数可以直接将纯右值作为参数传递,传递途中不展开参数包,直到构造函数才把参数包展开,体现可变参数包 的优势(直接传递参数)

结论2:在插入纯右值,并且构造函数能正常接收时,emplace 系列函数可以直接构造,省去了调用移动构造函数时的开销。

总结

1、对于深拷贝的类型,push_back是构造 + 移动构造,emplace_back是直接构造

2、对于浅拷贝的类型,push_back是构造 + 拷贝构造,emplace_back是直接构造

3、由于push_back函数参数写死,所以只能先构造再拷贝构造,但是emplace_back函数参数是参数包,能够随函数一层一层传递下去,到构造函数时一起构造,省去中间所有构造。

4、在插入单参数,左值,move 之后的右值时两者没有区别,当插入多参数时 emplace 参数包就会随函数调用一层一层向下传,最后直接构造。

类别说明
emplace或push / insert 右值对象构造 + 移动构造
emplace参数包构造
emplace或push / insert 左值对象构造 + 拷贝构造
emplace参数包构造
int main()
{
    list<string> l;                 
    string s("111");                //构造
    l.emplace_back(s);              //左值深拷贝
    l.emplace_back(move(s));        //右值移动构造
    l.emplace_back("111");          //单参数参数包最后直接构造
    l.emplace_back(string("111"));  //构造 + 移动构造
}

二、包装器

1、作用

首先我们先了解可调用对象

可调用对象缺点
函数指针类型定义复杂
仿函数对象要定义一个类
lambda没有类型概念

C++中的 function 本质是一个类模板,也是一个包装器。为了函数调用方便,统一可调用对象的调用方式,function包装器用来包装对象,由于底层使用的是operator()函数重载,所以调用方式就能得到统一。

Ret:返回值类型

Args:参数包

2、使用包装器

(1)定义包装器

​#include<functional>
function<返回类型(参数包类型)> fc;

(2)举例

​int f(int a, int b)
{
	return a + b;
}

struct add
{
public:
	int operator()(int a, int b)
	{
		return a + b;
	}
};

int main()
{
    //函数指针包装
	function<int(int, int)> fc1 = f;
	//仿函数对象包装
    function<int(int, int)> fc2 = add();
	//lambda表达式包装
    function<int(int, int)> fc3 = [](int a, int b) {return a + b; };
	return 0;
}

注意:包装器只是包装可调用对象,不是定义可调用对象。

3、理解代码

map<string, functional<int(int, int)> func = 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;}},
};

这是命令与动作的对应实现代码,用到了 initializer_list 初始化4个命令与动作的对应操作。

通过包装器封装的函数最后调用函数的代码就会统一,简单。

function<int(int, int)> func = opFuncMap["+"];
func(1, 2);

4、如何包装静态 / 普通成员函数

class Func
{
public:
    static int plusi(int a, int b) {return a + b; };
    
    int plusd(double a, double b) {return a + b; };
}


int main()
{
    //包装静态成员函数
    function<int(int, int)> f1 = &Func::plusi;
    
    //包装普通成员函数(用对象指针)
    function<double(Func*, double, double)> f2 = &Func::plusd;
    Func f;
    f2(&f, 3.1, 2.1);

    //包装普通成员函数(用对象)
    function<double(Func, double, double)> f3 = &Func::plusd;
    f3(Func(), 3.1, 2.1);
}

(1)获取成员函数函数指针方法:&类型 : : 函数名

(2)普通成员函数参数要包含 this 指针,所以对于普通成员函数的 function 定义就一定要带上 this 指针的参数个数(静态函数因为没有 this 指针不用考虑),这时就有两种方法,一种是定义对象指针,一种是定义对象。

问题:要传的参数是对象的指针,为什么第二种用对象传也可以?

首先 this 指针不能显示传递,所以其实 function 底层就是operator() 用传入的对象调用函数,所以在底层就不关心是不是对象的指针,只关心是不是传对象。

三、绑定

1、作用

调整可调用对象的参数个数或参数顺序。

bind 是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象,生成一个新的可调用对象来 “适应” 原对象的参数列表

第二个模板中的 Ret 如果需要修改原来函数对象的返回值,就可以显示实例化来确定特殊的返回类型。

万能引用里面的参数包包括了绑定值和参数(placeholders::_1, placeholders::_2,....)

其中 placeholders::_1 表示传进函数的第一个实参,以此类推。

2、作用一:调整参数顺序

int Sub(int a, int b)
{
	return a - b;
}


int main()
{
	auto f1 = Sub;
	cout << f1(10, 5) << endl;

	 //调整顺序
	auto f2 = bind(Sub, placeholders::_2, placeholders::_1);
	cout << f2(10, 5) << endl;
    return 0;
}

结果:

原理图:

绑定函数时交换实参的位置就能交换参数。

3、作用二:调整参数个数

原理:绑死几个参数到bind函数里面,调用时参数减少。

class Sub
{
public:
	Sub(int x)
		:_x(x)
	{}
	int sub(int a, int b)
	{
		return a - b;
	}
private:
	int _x;
};

int main()
{
    auto f3 = bind(&Sub::sub, placeholders::_1, placeholders::_2, placeholders::_3);
    
    cout << f3(Sub(1), 10, 5) << endl;

    Sub sub(1);
    cout << f3(&sub, 10, 5) << endl;

    // 绑定,调整参数个数
    auto f4 = bind(&Sub::sub, Sub(1), placeholders::_1, placeholders::_2);
    cout << f4(10, 5) << endl;

    auto f5 = bind(&Sub::sub, &sub, placeholders::_1, placeholders::_2);
    cout << f5(10, 5) << endl;

    return 0;
}

上面的代码绑定了调用成员函数所用到的对象,这样每次调用函数就不用传对象。

结果:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值