文章目录
- 前言
- 列表初始化
- 用法介绍
- 原理:std::initializer_list
- 简化声明的方式
- auto
- decltype
- 右值引用
- 移动构造
- 万能引用和完美转发
- 万能引用
- 完美转发
- 类的新增功能
- 可变参数模板
- lambda表达式
- 深入探究lambda表达式
- lambda表达式带来的便利
- 结语
前言
大家好久不见,今天带大家一起了解一下关于c++11的新特性。
列表初始化
用法介绍
c++ 98允许使用花括号对数组和结构体元素统一的初始化,而c++ 11扩大了花括号初始化范围,使得可以对所有的自定义类型都初始化,使用初始化列表的时候,可以使用等于号( = ),也可以省略等于号( = )。
int a{ 0 };
int arr[10]{ 1,2,3,4,5,6,7,8,9 };
Date d1{ 2023,5,29 };
Date d2{ 2023,5,30 };
Date d3 = { 2023,6,3 };
d1.show();
d2.show();
原理:std::initializer_list
为什么能支持使用大括号来初始化,在c++11之后,所有的stl容器都增加了一个构造方法的重载,原理就是将花括号的内容构造一个initializer_list的类,拿这个类去调用对应的构造方法。
auto i1 = { 1,2,3,4,5 };
initializer_list<int>::iterator it1 = i1.begin();
//初始化列表在常量区,不能被更改
*it1 = 1;//这样是非法的
简化声明的方式
auto
由编译器自动推导类型。
//示例:
auto i1 = { 1,2,3,4,5 };
注意:
1、auto不能作参数类型
2、auto不能当函数返回值的类型
decltype
作用:将变量的类型声明为表达式的类型。
//示例:
template<class T1,class T2>
void func(T1 t1,T2 t2)
{
decltype(t1 * t2) ret;
cout << ret << endl;
}
右值引用
之前介绍c++入门的时候我们讲过一次引用,我们把之前的引用成为左值引用。左值引用和右值引用都是给一个变量起别名。
左值:可以获取它的地址 +可以对其赋值,能够出现在赋值符号的左边。
左值引用:给左值起别名。
右值:不能获取他的地址,字面常量,表达式返回值,函数返回值(除左值引用的返回),不能出现在赋值号的左边!
右值引用:给右值取别名。
int main()
{
double x = 1.1;
double y = 2.3;
10;
x + y;
Sum(x, y);
int&& r1 = 10;
double&& r2 = x + y;
double&& r3 = Sum(x, y);
const int& r4 = 10;
return 0;
}
可以看出:
1、const左值引用可以引用左值也可以引用右值。
2、普通左值引用只可以引用左值,不能引用右值。
3、右值引用可以引用move后的左值。
移动构造
纯右值:字面量等(true,false,1.2)。
将亡值:生命周期就要结束的资源。
所谓移动构造,就是窃取将亡值的资源来构造自己。
c++编译器一般优化:两个连续的构造优化为直接构造、两个连续的拷贝构造优化成直接拷贝构造。用右值引用的方式解决了左值引用留下来的问题:
1、函数中的资源不能使用左值引用返回提高资源利用率
移动构造的实现:
//移动构造:
// 拷贝构造
string(const string& s)
:_str(nullptr)
{
cout << "string(const string& s) -- 深拷贝" << endl;
string tmp(s._str);
swap(tmp);
}
// 移动构造
string(string&& s)
:_str(nullptr)
{
cout << "string(string&& s) -- 移动拷贝" << endl;
swap(s);
}
//============================================================
//移动赋值:
// 赋值重载
string& operator=(const string& s)
{
cout << "string& operator=(string s) -- 深拷贝" << endl;
string tmp(s);
swap(tmp);
return *this;
}
// s1 = 将亡值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动赋值" << endl;
swap(s);
return *this;
}
所以,移动构造和移动赋值是通过直接移动资源来提高效率。
万能引用和完美转发
万能引用
模板中的&&不代表右值引用,而是万能引用,既能接收左值又能接收右值。
接收之后都退化为左值。
为了在传递过程中保持他的左值或右值属性,需要使用完美转发。
完美转发
为了解决上面万能引用带来右值属性退化的问题,就需要使用完美转发
std::forward 完美转发在传参的过程中保留对象原生类型属性。
类的新增功能
新增两个默认成员函数:默认移动构造和默认移动赋值,但这两个函数触发条件都比较苛刻:
条件:
1、没有自己实现移动构造/移动赋值
2、没有实现析构、拷贝构造、拷贝赋值重载的任何一个
满足上述条件,这个类会自动生成一个默认的移动拷贝和移动赋值函数,和默认实现的构造函数相似
对于内置类型,执行逐成员按字节拷贝;对自定义类型,看这个成员是否实现移动构造,如果实现了就调用移动构造,如果没实现就调用拷贝构造。
可变参数模板
c++11 支持的可变参数模板让类模板和函数模板可以接收可变参数。
但可变参数的使用和C语言大不相同,我们需要一些特别的手段来获取这些参数:
// 递归终止函数
template <class T>
void ShowList(const T& t)
{
cout << t << endl;
}
// 展开函数
template <class T, class ...Args>
void ShowList(T value, Args... args)
{
cout << value << " ";
ShowList(args...);
}
还有另一种不需要递归的方式来得到可变参数:
template<class T>
int PrintArg(T t)
{
cout << t << " ";
return 0;
}
template <class ...Args>
void ShowList(Args... args)
{
int arr[] = { PrintArg(args)... };
cout << endl;
}
//其实就是编译后:
template <class ...Args>
void ShowList(char a1,int a2,string a3)
{
int arr[] = { PrintArg(a1),PrintArg(a2),PrintArg(a3)};
cout << endl;
}
lambda表达式
书写格式:
[capture-list] (parameters) mutable-> return-type{statement}
[capature-list] : 捕捉列表,编译器根据[]判断接下来的代码是否是lambda函数,捕捉列表能捕捉上下文的变量供lambda函数使用。
(parameters) :参数列表,如果不需要参数传递,可以连带着()一起省略
mutable : 默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性,使用mutable时,参数列表不可省略。
->returntype :返回值类型,用追踪返回类型形式声明函数的返回值类型。没有返回值这部分可以省略,返回类型明确也可省略,由编译器自己推导。
{statement} : 函数体,在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
lambda表达式没有名字,若要使用需要使用可以用auto关键字命令lambda表达式 ,之后就可以显示调用这个lambda表达式了。
//使用示例:
auto sum = [a,b](int x,int y) ->int
{
cout << a << " " << b << endl;
cout << x + y << endl;
return a + b;
};
捕捉列表描述了上下文哪些数据看了一被lambda使用以及使用的方式是传值还是传引用。
[=]:表示值传递方式捕获所有父作用域中的变量(包括this)。
[&]:表示引用传递方式捕获所有父作用域中的变量(包括this)。
[val] :表示值传递捕捉变量val。
[&val]:表示引用传递捕捉变量val。
[this] :表示值传递捕获当前的this指针。
注意:
1、父作用域指的是包含lambda函数的语句块。
2、捕捉列表可由多个捕捉项组成,以逗号分割。
[= , &a , &b] : 表示以引用方式捕捉ab,其他采用传值捕捉。
[& , a ,this] :表示以传值方式捕捉a和this指针,其他采用传引用捕捉。
3、捕捉列表不允许重复传递,否则编译报错。
4、块作用域以外的lambda函数捕捉列表必须为空。
5、lambda表达式之间不能相互赋值,尽管看起来类型相同!
深入探究lambda表达式
大小:
使用sizeof关键字可以查看lambda表达式的大小
auto sum = [](int x,int y) ->int
{
};
std::cout << sizeof(sum) << std::endl;
打印结果为1
在没有捕获任何变量的时候,这个 lambda表达式的大小是1,这和我们没有任何变量的类大小是一样的。
汇编:
从汇编角度来看一下lambda表达式。
int a = 1;
int b = 2;
Sub sub;
sub();
auto sum = [a,b](int x,int y) ->int
{
std::cout << a << " " << b << std::endl;
std::cout << x + y << std::endl;
return a + b;
};
sum(10,20);
从底层来看,lambda表达式也是与仿函数汇编一致的,可以看到这里lambda的类名是lambda_1,这也恰恰说明了就算lambda表达式写的一模一样,在底层看来也是两个不同的类,不能相互赋值。
lambda表达式带来的便利
int main()
{
vector<int> v;
v.push_back(20);
v.push_back(10);
v.push_back(30);
sort(v.begin(), v.end(), [](const int x,const int y) {
return x < y;
});
sort(v.begin(), v.end(), [](const int x, const int y) {
return x > y;
});
return 0;
}
lambda相比于仿函数更加灵活,需要比较的条件可以自由变换。
结语
以上就是c++11新增特性上篇,我是蓝色学者i,希望对你有所帮助,我们下次再见。