C++11必看总结知识点,看完都懂的是老鸟
1.引言
由于C++11的重要性和冗杂性,其中更新了很多有用的功能,却也引进了很多无用的功能。所以在这里把C++11总结收个尾,记录我自己遇到的一些重要的C++11的知识点,也为大家提供参考
2.右值引用
1.什么是左值? 左值是一个数据表达式(变量名/解引用指针),如:int a。 **什么是左值引用?**例如:int& ref = a;
2.**什么是右值?**右值是一个数据表达式,如10,x+y; 什么是右值引用? 例如: int&& r1 = 10;
3.左值右值最大区分点是什么? 看能否取地址&,能取地址的往往是左值a,不能取地址的往往是右值x+y。
4.普通对象既可以当左值,也可以当右值。
5.()匿名对象是右值,因为其不可以取地址,只能出现在表达式中。
6.左值引用一般不能给右值取别名(权限放大),但是左值引用可以引用加const之后的右值。
7.右值引用一般不能给左值取别名(),但是右值引用可以引用move()之后的左值。
8.右值引用最大的产生意义是能够生成移动语义,从而减少拷贝,提高效率。例如:
string ret1(“hello”)
string ret2= ret1;
string ret3(“ret2” + “a”);
这里ret3会产生移动构造而不会深拷贝,属于直接转移走将亡值的资源,而避免了进一步的拷贝消耗。
9.若自己未能实现移动构造函数,且未实现析构函数、拷贝构造函数、拷贝赋值重载中的任意一个,那么编译器会默认生成一个移动拷贝构造,对于内置类型按照字节拷贝,对于自定义类型看其是否实现移动构造,若实现则移动构造,否则是拷贝构造。
10.若不想被拷贝可以加delete关键字,Person(const Person& p ) = delete; 实际中也有不希望被拷贝的类,如:IO流:istream、ostream,擅自拷贝或许会面临缓冲区不一致的问题。
3.万能引用+完美转发
在实际编程中,经常会遇到需要将一个函数的参数原封不动地转发给另一个函数的情况。如果直接将参数传递给另一个函数,会导致参数类型和值的改变。例如:
template<typename T>
void Func(T&& t){...}/这里的T&& t是万能引用玩法
int main()
{
Func(10); //10为右值,但是Func(10)退化成左值
Func(a); //左值
Func(move(a)); //右值
return 0;
}
例子中Func(10)中10为右值,但是Func(10)退化为了左值引用,在后续使用中,我们希望它保留本身的属性,这种情况就需要使用到完美转发(Perfect Forwarding). 使用方式为:
template<typename T>
void Func(T&& t){...}/这里的T&& t是万能引用玩法
void PerfectForwarding(T&& t)
{
Func(forward<T>(t));//完美转发,加上forward<T>(t)前缀
}
int main()
{
PerfectForwarding(10); //再次使用即可以保留原来属性
return 0;
}
完美转发,加上forward(t)前缀,再次使用即可以保留原来属性。
4.Lambda表达式
书写格式:【捕捉列表】(参数)->返回值类型{函数体;};
除了捕捉列表和函数体不能省略,其余的基本都能省略,一般**->和返回值类型**需要一起省略:
auto add = [](int x, int y)->int{return x + y; };//原本lambda格式
//省略写法;
auto add2 = [](int x, int y) {return x + y; }; //省略-> 和 int
auto add3 = [] {return 1; }; //省略-> 和 int还有参数,一般->和返回值类型需要一起省略
lambda捕捉列表用法,这里以swap交换函数为例
//1.
auto swap[x,y]() mutable //这里加上mutable关键字是因为这里x,y是传值捕捉,
//有const属性会报错,所以加上mutable异变修饰成可,变的
{
int tmp = x;
x=y;
y = tmp;
};
//2.
auto swap2 = [&x,&y](){...} //引用捕捉,将外部参数捕捉进lambda
//3.
auto swap = [&](){...} //全部引用捕捉
auto swap = [=](){...} //全部传值捕捉
auto swap = [&,x](){...}//全部引用捕捉 x传值捕捉
lambda大小为1字节,使用方法类似于仿函数。
5.Function包装器
Function包装器就是套了一层壳,是一种适配器而已,将可调用对象类型进行再封装适配。头文件
包装器格式:function<返回值(参数)> f1 = f;
Function包装器可以包装什么东西? 函数指针、仿函数、lambda、函数等等
例子:
int f(int a,int b){return a+b};
function<int(int,int)> f1 = f; //包装器包装函数,第一个int为返回值,括号内为参数
cout<<f1(1,2)<<endl;
struct Functor
{
public:
int operator()(int a,int b){return a+b;}
}
function<int(int,int)> f1 = Functor; //包装器包装仿函数
cout<<f2(10,20)<<endl;
还可以将包装器作为类型放入map中
map<string,function<int(int,int)> > CountMap = {"+",[](int a,int b){return a+b;}}
,{"-",[](int a,int b){return a-b;}}
6.可变参数模板
在模板中发现有**“…”**代表可变参数模板。它允许定义一个可变数量的模板参数列表,从而实现更加通用和灵活的模板函数或类。可变参数模板通常用于编写具有不定参数个数的函数或类,例如打印多个参数、拼接多个字符串等操作。
template<class... Args> //Args是一个模板参数包
void showList(Args... args){} //args是一个形参参数包
showList(); #0
showList('x'); #1
showList('x','y'); #2
那么如何解析出可变参数包呢? 采用一种递归思维来解析
void showList(const T& val,Args...args)
{
cout<<val<<" ";
showList(args...); //递归思维调用自己
}