C++11 核心语法特性总结:初始化、移动语义、万能引用、完美转发、lambda、function、bind
一:列表初始化
C++98中一般数组跟结构体可以用{}进行初始化
struct Point
{
int _x;
int _y;
};
int main()
{
int array1[] = {1,2,3,4,5};
int array2[] = {0};
Point p = {1,2};
return 0;
}
C++11中的{}
1.试图一切皆可用{}初始化,{}初始化也叫做列表初始化
2.内置类型支持,自定义类型也支持,自定义类型本质是类型转换,中间会产生临时对象,最后优化了以后变成直接构造
3.{}初始化的过程中,可以省略=
4.C++11列表初始化本意是想实现一个大统一的初始化方式,其次在一些场景下带来了不少的便利,例如容器push/list多参数构造的对象时,{}初始化会很方便
C++11中的std::initializer_list
1.本意是一个数组,内部有两个指针分别指向数组的开始和结束
2.也就是说支持迭代器遍历
3.容器支持一个std::initializer_list的构造函数,也就支持任意多个值构成的{x1,x2,x3…}进行初始化。STL中的任意容器支持任意多个值构成的{x1,x2,x3}进行初始化,也就是通过std::initializer_list的构造函数支持
二:右值引用和移动语义
1.C++98的语法中就有引用的语法
2.C++11中新增了右值引用语法特性
3.C++98学习的引用叫做左值引用
4.无论是左值引用还是右值引用,都是给对象取别名
上面是左值引用跟右值引用的介绍,那么再来介绍一下什么是左值,什么是右值
左值:
1.变量名或解引用的指针,一般是持久状态,存储在内存中
2.可以获得它的地址
3.左值可以出现在赋值符号的左边,也可以在右边
4.定义const修饰后的左值,不能给他赋值,但是可以取它的地址
右值:
1.要么是字面值常量
2.要么是表达式求值过程中创建的临时对象等
3.只能出现在赋值符号的右边
4.右值不能取地址
&10
&(x+y)
&string(""11111)
上面的都是错误写法,右值是不能取地址的
左值引用:
Type& r1 = x;
1.就是给左值取别名
2.左值引用不能直接引用右值,但const左值引用可以引用右值
右值引用:
Type&& rr1 = y;
1.就是给右值取别名
2.右值引用不能直接引用右值,但是右值引用可以引用move(左值)
move是库里面的一个函数模板,本质内部是进行强制类型转换,还涉及一些引用折叠
注意:变量表达式都是左值属性,也就意味着一个右值被右值引用绑定后,右值变量表达式的属性是左值
引用延长生命周期
右值引用可用于为临时对象延长生命周期,const的左值引用也能延长临时对象生存周期,但这些对象无法被修改
回顾左值引用的主要使用场景
1.函数中左值引用传参和左值引用传返回值时减少拷贝,同时还可以修改实参和修改返回对象的价值。
2.左值引用已经解决大多数场景的拷贝效率问题,但是有些场景不能使用传左值引用返回
3.当然右值引用返回也无法概念对象已经析构销毁的事实
就比如函数里面创建一了一个string对象,返回string对象时,string对象已经销毁,如果返回的是引用,那就变成野引用了,所以只能传值拷贝,如果是vector<vector>的传值拷贝,代价就太大了
所以就轮到移动构造和移动赋值登场了
移动构造:
移动构造函数是⼀种构造函数,类似拷贝构造函数,移动构造函数要求第⼀个参数是该类类型的引用,但是不同的是要求这个参数是右值引用,如果还有其他参数,额外的参数必须有缺省值。
移动赋值:
移动赋值是⼀个赋值运算符的重载,他跟拷贝赋值构成函数重载,类似拷贝赋值函数,移动赋值函数要求第⼀个参数是该类类型的引用,但是不同的是要求这个参数是右值引用。
作用:
对于string/vector这样的深拷贝的类或者包含深拷贝的成员变量的类,移动构造跟移动赋值很有意义
右值引⽤和移动语义解决传值返回问题
在传值返回,如果是像vector这样深拷贝的类,传值返回不可避免,所以就可以传参的时候传临时对象,接受的形参也是右值引用,这样子还不能解决传值返回的问题,所以编译器进行了优化,就可以一步到位,直接构造,记得用场景一的写法,编译器才能一步到位的优化成直接构造,第二种场景太复杂了,先是用了默认构造初始化,然后又构造出一个临时对象,再进行移动构造,所以编译器不能直接构造
场景一:
int main()
{
bit::string ret = bit::addStrings("11111", "2222");
cout << ret.c_str() << endl;
return 0;
}
场景二:
int main()
{
bit::string ret;
ret = bit::addStrings("11111", "2222");
cout << ret.c_str() << endl;
return 0;
}


所以更推荐第一种写法





赋值的也一样,就不多写了
右值引用和移动语义在传参中的提效
类型分类
右值划分为纯右值
move(x)后变为将亡值
泛左值包含左值跟将亡值
引用折叠
万能引用:
template<class T>
void f(T&& x)
{}
由于引用折叠,传左值就是左值引用,传右值就是右值引用
完美转发
forward,是参数保持原来的属性,就比如传实参给参数是,虽然传的是右值,由于万能引用,弄出了右值引用接受了这个右值,但是形参那个变量的属性是左值,所以在函数内部在传递这个变量时,在变量前面加一个foeward,所以完美转发就是为了替万能引用服务的
三:可变参数模板

template<class ...Args>
void Print(Args&&... args)
{
cout << sizeof...(args) << endl;
}
int main()
{
double x = 2.2;
Print(); //包里0个参数
Print(1); //包里1个参数
Print(1,string("xxxx")); //包里2个参数
Print(1.1,string("xxxx"),x); //包里3个参数
return 0;
}

包扩展


// 本质可以理解为编译器编译时,包的扩展模式
// 将上面的函数模板扩展实例化为下面的函数
// 是不是很抽象,C++11以后,只能说委员会的大佬设计语法思维跳跃得太厉害
// void Print(int x, string y, double z)
// {
// Arguments(GetArg(x), GetArg(y), GetArg(z));
// }
empalce系列接口

四:新的类功能
默认的移动构造和移动赋值

default跟delete
五:STL中的一些变化

六:lambda
int main()
{
// ⼀个简单 lambda 表达式
auto add1 = [](int x, int y)->int {return x + y; };
cout << add1(1, 2) << endl;
// 1、捕捉为空也不能省略
// 2、参数为空可以省略
// 3、返回值可以省略,可以通过返回对象⾃动推导
// 4、函数题不能省略
auto func1 = []
{
cout << "hello bit" << endl;
return 0;
};
func1();
int a = 0, b = 1;
auto swap1 = [](int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
};
swap1(a, b);
cout << a << ":" << b << endl;
return 0;
}
捕捉列表

auto func7 = [=]()mutable
{
a++;
b++;
c++;
d++;
return a + b + c + d;
};






lambda的应用


七:包装器

#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 = 0;
};
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;
//包装静态成员函数,在前面可加可不加&
//成员函数要指定类域并且前面加&才能获取地址
function<int(int, int)> f4 = &Plus::plusi;
cout << f4(1, 1) << endl;
//包装普通成员函数
//普通成员函数还有一个隐含的this指针参数,所以绑定时传对象或者对象的指针过去都可以
function<double(Plus*, double, double)> f5 = &Plus::plusd;
Plus pd;
cout << f5(&pd, 1.1, 1.1) << endl;
function<double(Plus, double, double)> f6 = &Plus::plusd;
cout << f6(pd, 1.1, 1.1) << endl;
function<double(Plus&&, double, double)> f7 = &Plus::plusd;
cout << f7(move(pd), 1.1, 1.1) << endl;
cout << f7(Plus(), 1.1, 1.1) << endl;
return 0;
}

这道题的新写法,不需要用switch case ,转为用map加上function
使用map映射string和function的方式实现
这种方式的最大优势之⼀是方便扩展,假设还有其他运算,我们增加map中的映射即可
class Solution {
public:
int evalRPN(vector<string>& tokens) {
map<string,function<int(int,int)>> mapo = {
{"+",[](int x,int y){return (x+y);}},
{"-",[](int x,int y){return (x-y);}},
{"*",[](int x,int y){return (x*y);}},
{"/",[](int x,int y){return (x/y);}}
};
for(auto& e: tokens)
{
if(mapo.count(e))
{
int right = _s.top();
_s.pop();
int left = _s.top();
_s.pop();
_s.push(mapo[e](left,right));
}
else
{
_s.push(stoi(e));
}
}
return _s.top();
}
private:
stack<int> _s;
};
再来看看原来的写法
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> s;
for(auto& e: tokens)
{
if(e=="+"||e=="-"||e=="*"||e=="/")
{
int right = s.top();
s.pop();
int left = s.top();
s.pop();
switch(e[0])
{
case '+':
s.push(left+right);
break;
case '-':
s.push(left-right);
break;
case '*':
s.push(left*right);
break;
case '/':
s.push(left/right);
break;
default:
break;
}
}
else
{
s.push(stoi(e));
}
}
return s.top();
}
};
bind
bind本质返回的一个仿函数对象
_1代表第一个形参
_2代表第二个形参
1.调用参数顺序(不常用)
2.调用参数个数(常用)
3.分别绑死第123个参数
4.成员函数对象进行绑死,就不需要每次都传递了
#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:
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 = 0;
};
int main()
{
auto sub1 = bind(Sub, _1, _2);
cout << sub1(10, 5) << endl;
//bind本质返回的一个仿函数对象
//调用参数顺序(不常用)
//_1代表第一个形参
//_2代表第二个形参
auto sub2 = bind(Sub, _2, _1);
cout << sub2(10, 5) << endl;
//调用参数个数(常用)
auto sub3 = bind(Sub, 100, _1);
cout << sub3(5) << endl;
auto sub4 = bind(Sub, _1, 100);
cout << sub4(5) << endl;
//分别绑死第123个参数
auto sub5 = bind(SubX, 100,_1,_2);
cout << sub5(5,1) << endl;
auto sub6 = bind(SubX, _1,100,_2);
cout << sub6(5,1) << endl;
auto sub7 = bind(SubX, _1,_2,100);
cout << sub7(5,1) << endl;
//成员函数对象进行绑死,就不需要每次都传递了
function<double(Plus&&, double, double)> f6 = &Plus::plusd;
Plus pd;
cout << f6(move(pd), 1.1, 1.1) << endl;
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;
//计算复利的lambda
auto func1 = [](double rate, double money, int year)->double {
double ret = money;
for (int i = 0; i < year; i++)
{
ret += ret * rate;
}
return ret - money;
};
//绑死一些参数,实现出支持不同年华利率,不同金额和不同年份计算出复利的结算利息
auto func3_1_5 = bind(func1, 0.015, _1, 3);
auto func5_1_5 = bind(func1, 0.015, _1, 5);
auto func10_2_5 = bind(func1, 0.025, _1, 10);
auto func30_3_5 = bind(func1, 0.035, _1, 30);
cout << func3_1_5(1000000) << endl;
cout << func5_1_5(1000000) << endl;
cout << func10_2_5(1000000) << endl;
cout << func30_3_5(1000000) << endl;
return 0;
}
更多推荐

所有评论(0)