前言:
我们上一章一节已经接触了C++11的一些特性,本章将继续更深入介绍C++11中其他使用且重要的一些功能。
目录
(一)万能模版和完美转发
1、万能模版
2、完美转发
(二)可变参数模版
1、可变参数模版的使用
2、emplace_back
(三)lambda表达式
1、引入
2、lambda表达式的定义
3、lambda表达式的用法
(四)包装器function及bind绑定
1、包装器
2、bind绑定
万能模版样式:
这里还是太抽象,我们来看一个例子:
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; }
// 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
// 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,
// 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,
// 我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发
template
void PerfectForward(T&& t)
{
Fun(t);
}
int main()
{
PerfectForward(10); // 右值
int a;
PerfectForward(a); // 左值
PerfectForward(std::move(a)); // 右值
const int b = 8;
PerfectForward(b); // const 左值
PerfectForward(std::move(b)); // const 右值
return 0;
}
这段代码的输出结果是:
输出全是左值引用,很显然这并不是我们想要的,因为无论传的是左值还是右值,都将退化成左值。
那怎么解决这一问题呢???这就要用到了我们下面讲解的完美转发。
我们为了解决类似上述场景的问题,希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的:
完美转发:
这里就用到了我们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的原生类型属性。
template
void PerfectForward(T&& t)
{
//完美转发,按照原封不动的方式进行转发
//Fun(t);
Fun(std::forward(t));
}
int main()
{
PerfectForward(10); //右值
int a;
PerfectForward(a); //左值
PerfectForward(std::move(a)); //右值
const int b = 8;
PerfectForward(b); //const 左值
PerfectForward(std::move(b)); //const 右值
return 0;
}
运行结果:
C++11新提供的forward函数模板,可以解决上述问题,使得传右值不会退化成左值。
首先在学C语言的时候,我们用的printf
函数是一个可变参数函数,printf
从语法上说是可以写多个参数,然后该函数自己识别。
可变参数模版:
解释:
首先,我们介绍如何参数个数的方法:
template
void ShowList1(Args... args)
{
//参数个数
cout << sizeof...(args) << endl;
}
//不一定非要用Args也可以取别的名字
template
void ShowList2(X... y)
{
cout << sizeof...(y) << endl;
}
int main()
{
ShowList1(1, 'x', 1.1);
ShowList2(1, 2, 3, 4, 5);
return 0;
}
主要使用sizeof...( )形式来获取参数个数。
那么我们怎么一一获取参数包的值的呢?有下面两种方法:
// 递归终止函数
template
void ShowList(const T& t)
{
cout << t << endl;
}
// 展开函数
template
void ShowList(T value, Args... args)
{
cout << value <<" ";
ShowList(args...);
}
int main()
{
ShowList(1);
ShowList(1, 'A');
ShowList(1, 'A', std::string("sort"));
return 0;
}
2、逗号表达式展开参数包
template
void PrintArg(T t)
{
cout << t << " ";
}
//展开函数
template
void ShowList(Args... args)
{
int arr[] = { (PrintArg(args), 0)... };
cout << endl;
}
int main()
{
ShowList(1);
ShowList(1, 'A');
ShowList(1, 'A', std::string("sort"));
return 0;
}
对比:
push_back():
emplace_back():
两者的底层实现的机制不同。push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。但其实两者差别并不大。
C++98中,我们会遇到下面这种场景:
#include
#include
int main()
{
int array[] = {4,1,8,5,3,7,0,9,2,6};
// 默认按照小于比较,排出来结果是升序
std::sort(array, array+sizeof(array)/sizeof(array[0]));
// 如果需要降序,需要改变元素的比较规则
std::sort(array, array + sizeof(array) / sizeof(array[0]), greater());
return 0;
}
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 v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
3 }, { "菠萝", 1.5, 4 } };
sort(v.begin(), v.end(), ComparePriceLess());
sort(v.begin(), v.end(), ComparePriceGreater());
}
定义了一个可以调用的对象 / 匿名函数,一般定义在局部,特点是可以深度绑定了局部的数据。
auto Add1 = [](int x, int y)->int{return x + y; }; Add叫lambda表达式的对象 – lambda定义的是一个对象。
注意:
int main()
{
int a = 0, b = 200;
//一般是局部匿名函数 也可以写到全局(一般都不写返回值)
//参数列表(无参的时候)和返回值(表达式自动推导)也可以省略掉
auto Add1 = [](int x, int y)->double {return (x + y) / 3.0; };
auto Add2 = [](int x, int y)->int {return (x + y) / 3.0; };
//参数列表可以省略了
auto Add3 = [a, b] {return (a + b) / 3.0; };
//调用是和普通的函数一样的
cout << Add1(a, b) << endl;
cout << Add2(a, b) << endl;
//调用没有区别,但是没有实参,因为捕捉了
cout << Add3() << endl;
}
捕捉列表的用法:
用lambda表达式来实现交换两个数:
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;
//};
//用引用的方式捕捉(按值捕捉不能改变)
auto Swap2 = [&a, &b]()->void {
int tmp = a;
a = b;
b = tmp;
};
Swap2();
cout << a << " " << b << endl;
}
方法一:
方法二:
所以只能按照引用的方式捕捉
各种混合捕捉:
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;
}
每一个lambda表达式类型名字不一样:
int main()
{
auto Add = [](int x, int y)->int { return x + y; };
cout << typeid(Add).name() << endl;
return 0;
}
仿函数的名称后面叫uuid,一组随机字符串,通过某个算法得到的,使得每个lambda表达式的名字都不同。
注意:
lambda表达式之间不能相互赋值。
void (*PF)();
int main()
{
auto f1 = []{cout << "hello world" << endl; };
auto f2 = []{cout << "hello world" << endl; };
//f1 = f2; // 编译失败--->提示找不到operator=()
// 允许使用一个lambda表达式拷贝构造一个新的副本
auto f3(f2);
f3();
// 可以将lambda表达式赋值给相同类型的函数指针
PF = f2;
PF();
return 0;
}
模板参数说明:
看下面一段程序:
template
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;
}
如此丰富的类型,可能会导致模板的效率低下!
包装器可以很好的解决上面的问题:
#include
template
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()
{// 函数名
std::function func1 = f;
cout << useF(func1, 11.11) << endl;
// 函数对象
std::function func2 = Functor();
cout << useF(func2, 11.11) << endl;
// lamber表达式
std::function func3 = [](double d)->double { return d /
4; };
cout << useF(func3, 11.11) << endl;
return 0;
}
注意,特殊的包装:
简介:
std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。 一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺序调整等操作。
可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。
bind是个函数模板,调整了调用对象参数,用来调整个数和顺序,生成一个新的可调用对象:
包装器的特点就是统一了类型,但是值得注意的是包装时一定要注意匹配,如下:
这就在包装时不匹配,所在包装时一定要匹配。
下面的场景我们存一个map,这时就要求function包装器要统一了:
不过不免会有一些特殊情况:
func1和func2是可以的,但是func3就难受了呀,不匹配啊…
此时我们就可以通过bind函数来调整一下参数个数:
还可以调整参数顺序:
#include
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 func1 = std::bind(Plus, placeholders::_1,
placeholders::_2);
//auto func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
//func2的类型为 function 与func1类型一样
//表示绑定函数 plus 的第一,二为: 1, 2
auto func2 = std::bind(Plus, 1, 2);
cout << func1(1, 2) << endl;
cout << func2() << endl;
Sub s;
// 绑定成员函数
std::function func3 = std::bind(&Sub::sub, s,
placeholders::_1, placeholders::_2);
// 参数调换顺序
std::function func4 = std::bind(&Sub::sub, s,
placeholders::_2, placeholders::_1);
cout << func3(1, 2) << endl;
cout << func4(1, 2) << endl;
return 0;
}
实际例子:
int Div(int a, int b)
{
return a / b;
}
int Plus(int a, int b)
{
return a + b;
}
int Mul(int a, int b, double rate)
{
return a * b * rate;
}
class Sub
{
public:
int sub(int a, int b)
{
return a - b;
}
};
using namespace placeholders;
// 11:50继续
int main()
{
// 调整个数, 绑定死固定参数
function funcPlus = Plus;
//function funcSub = &Sub::sub;
function funcSub = bind(&Sub::sub, Sub(), _1, _2);
function funcMul = bind(Mul, _1, _2, 1.5);
map> opFuncMap =
{
{ "+", Plus},
{ "-", bind(&Sub::sub, Sub(), _1, _2)}
};
cout << funcPlus(1, 2) << endl;
cout << funcSub(1, 2) << endl;
cout << funcMul(2, 2) << endl;
cout << opFuncMap["+"](1, 2) << endl;
cout << opFuncMap["-"](1, 2) << endl;
int x = 2, y = 10;
cout << Div(x, y) << endl;
// 调整顺序 -- 鸡肋
// _1 _2.... 定义在placeholders命名空间中,代表绑定函数对象的形参,
// _1,_2...分别代表第一个形参、第二个形参...
//bind(Div, placeholders::_1, placeholders::_2);
//auto bindFunc1 = bind(Div, _1, _2);
//function bindFunc2 = bind(Div, _2, _1);
//cout << bindFunc1(x, y) << endl;
//cout << bindFunc2(x, y) << endl;
return 0;
}
感谢您的阅读!