C++11

●🧑个人主页:你帅你先说.
●📃欢迎点赞👍关注💡收藏💖
●📖既选择了远方,便只顾风雨兼程。
●🤟欢迎大家有问题随时私信我!
●🧐版权:本文由[你帅你先说.]原创,CSDN首发,侵权必究。

1.C++11简介

在2003年C++标准委员会曾经提交了一份技术勘误表(简称TC1),使得C++03这个名字已经取代了C++98称为C++11之前的最新C++标准名称。不过由于TC1主要是对C++98标准中的漏洞进行修复,语言的核心部分则没有改动,因此人们习惯性的把两个标准合并称为C++98/03标准。从C++0x到C++11,C++标准10年磨一剑,第二个真正意义上的标准珊珊来迟。相比于C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。相比较而言,C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率。

2.列表初始化

在C++98中,标准允许使用花括号{}对数组元素进行统一的列表初始值设定。比如:

int arr1[] = {1,2,3,4,5};
int arr2[5] = {0};

但对于自定义类型,这种语法就不支持了。

所以C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。

#include <iostream>
using namespace std;
class A
{
public:
	A(int a, int b)
	{
		_a = a;
		_b = b;
	}
private:
	int _a;
	int _b;
};
int main()
{
	int a = 2;
	int a{ 2 };
	
	int arr1 = {1,2,3,4,5};
	int arr1[]{ 1,2,3,4,5 };
	//前面两种就省略了一个赋值符号,没有看出来新语法有什么优势,接下来这三种则是C++98所不能支持的写法
	A a = {1,2};
	int* ptr1 = new int[5]{ 1,2,3 };
	A* ptr2 = new A[2]{ {1,2},{3,4} };
}

在C++11里提供了一个容器initializer_list,用于解决自定义类型初始化的问题。
例如有了这个容器,标准库里的其它容器就能这样初始化了

vector<int> v = {1,2,3,4,5,6,7,8};
list<int> lt = {1,2,3,4,5};
map<string,int> = {{"苹果",5},{"西瓜",10}};

除了初始化列表,initializer_list还提供了赋值(即operator=),例如:

vector<int> v = {1, 2, 3, 4, 5};
v = {6, 7, 8, 9, 10};
//此时容器里的内容被修改

3.变量类型推导

3.1auto的使用

我们前面说过,auto可以自动推导变量的类型,这一特点可能对于普通类型没有很大的优势,而对于迭代器类型有这很大的便利,迭代器类型一般前缀名字很长,有了auto就可以这样:

//正常写法
list<int>::iterator it = lt.begin();
//使用auto
auto it = lt.begin();

3.2decltype类型推导

decltype是根据表达式的实际类型推演出定义变量时所用的类型。

为什么需要decltype?
auto使用的前提是:必须要对auto声明的类型进行初始化,否则编译器无法推导出auto的实际类型。但有时候可能需要根据表达式运行完成之后结果的类型进行推导,因为编译期间,代码不会运行,此时auto也就无能为力。
在这里插入图片描述
decltype的使用

int x;
decltype(x) z = 2;

4.STL新增内容

在C++11里STL新增了以下几个内容。
在这里插入图片描述
除了array,其余的在前面我们都讲过了。
在这里插入图片描述
我们发现array其实就是一个静态数组,但静态数组我们本身就可以直接定义了,为什么还要再封装成一个容器使用?
有两个原因:
1.支持迭代器,更好兼容STL容器玩法
2.对于越界的检查

除了增加容器,还新增了
1.移动构造、移动赋值
2.右值引用版本的插入接口

5.右值引用

在说右值之前,我们先来了解一下什么是左值。

左值是一个表示数据的表达式(变量名或解引用的指针)。我们可以获取它的地址也可以对它赋值,左值既可以出现在赋值符号的左边也可以出现在右边,右值只能出现在赋值符号的右边。

int main()
{
	int a = 10;
	int& r1 = a;
	int* p = &a;
	int& r2 = *p; 
	//a、p、*p都是左值 
}

思考一下,接下来这个是左值吗?

const int b = 10;

这种情况我们也认为是左值,所以换言之,可以取地址的对象,就是左值。
铺垫完了左值,我们再来看看右值。

右值是一个表示数据的表达式(字面常量、表达式返回值、传值返回函数的返回值)。右值引用就是对右值进行引用,给右值取别名,右值不能取地址。

例如:

int main()
{
	int x = 1;
	int y = 2;
	
	10;//字面常量
	x + y;//表达式返回值
	int ret = fmin(x,y);//传值返回函数的返回值
	//右值引用
	int&& r1 = 10;
	int&& r2 = x + y;
	int&& r3 = fmin(x,y);//假设fmin返回两者中最小值
}

大家思考一下:
左值引用能否引用右值?
在这里插入图片描述
很明显,无法直接引用,但我们可以通过加const对右值进行引用。

右值引用能否引用左值?
这个问题比较复杂了,在这里直接说答案,后面会再介绍。
不能直接引用,但是右值引用可以引用move以后的左值。
这里还有一点说明:
右值引用后的变量可以进行赋值、取地址,相当于右值引用后又变成了一个左值,因为右值引用后会开一块空间来存储这个值。

右值引用是用来解决左值引用的不足。

string operator+(char ch)
{
	string tmp(*this);
	push_back(ch);
	
	return tmp;
}

这段代码会导致返回时有一次深拷贝,影响程序效率。
左值引用针对于下面这种场景完美解决了拷贝的问题。

string& operator+=(char ch)
{
	push_back(ch);
	return *this;
}

但对于+的情况没办法很好的解决。
所以就利用右值引用来解决这个问题,通过增加一个移动构造函数。

//在这里补充一个概念,C++将右值分为两种:纯右值、将亡值(即自定义类型的返回值)
string(string&& s):_str(nullptr), _size(0), _capacity(0)
{
	this->swap(s);//本质就是资源转移,传过来的右值即将被销毁不如把资源让出来。
}

有了移动构造函数,就能解释前面我们所说的编译器对传值返回的优化问题,我们先来回顾一下。

string sayHello(string& str)
{
	string ret("hello ");
	ret += str;
	return ret;
}
int main()
{
	string s("world");
	string ret = sayHello(s);
	return 0;
}

在编译器没有优化的情况下
在这里插入图片描述
此时发生了两次拷贝构造。
编译器优化了之后
在这里插入图片描述
此时只发生了一次拷贝构造。
有了移动构造函数,还能再进行优化。
相信有了上面的铺垫你也能猜到,直接在函数结束前拿sayHello里的ret直接去与main函数里的ret交换资源,一次拷贝构造都不用调用,这对性能的影响是非常重大的。
💡:这里编译器会进行优化是因为构造了一个新对象,如果是下面这种场景,编译器无法进行优化。

string sayHello(string& str)
{
	string ret("hello ");
	ret += str;
	return ret;
}
int main()
{
	string s("world");
	cout << sayHello(s) << endl;
	return 0;
}

既然有移动构造,相应的就会有移动赋值。

string& operator=(string&& s)
{
	swap(s);
	return *this;
}
string sayHello(string& str)
{
	string ret("hello ");
	ret += str;
	return ret;
}
int main()
{
	string s("world");
	string ret;
	ret = sayHello(s);
}

这个过程发生了一次移动构造,一次移动赋值。
调用sayHello函数,在函数结束前ret与临时对象交换资源,然后临时对象再与main函数里的ret交换资源。
与右值引用相关联的还有万能引用

模板中的&&不代表右值引用,而是万能引用,既能接收左值又能接收右值。

但前面我们说过,右值引用后会开一块空间来存储,相当于又变成了左值引用,所以在后续使用中都退化成了左值。
为了解决这个问题C++给出了完美转发的概念,即在传参过程中保留对象原生类型属性。

template <typename T>
void Fun(T& t)
{
	cout << "左值引用" << endl;
}
template <typename T>

void Fun(T&& t)
{
	cout << "右值引用" << endl;
}
template <typename T>
void PerfectForward(T&& t)
{
	Fun(forward<T>(t));//保证t的属性不被改变
}
int main()
{
	PerfectForward(10);
    return 0;
}

6.lambda表达式

6.1语法

书写格式:[capture-list] (parameters) mutable -> return-type { statement }

  • [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
  • (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
  • mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。默认情况下捕获的值不能修改,有了mutable就可以修改捕获的值。
  • ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
  • {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

💡:在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。
因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

6.2捕捉列表说明

捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。

  • [var]:表示值传递方式捕捉变量var
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递方式捕捉当前的this指针

还有几点需要注意的:

a. 父作用域指包含lambda函数的语句块。
b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量 [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量。
c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。 比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a导致重复。
d. 在块作用域以外的lambda函数捕捉列表必须为空。
e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。即在全局作用域中捕捉列表必须为空
f. lambda表达式之间不能相互赋值,即使看起来类型相同

6.3lambda表达式的使用

int main()
{
	 // 最简单的lambda表达式, 该lambda表达式没有任何意义
	 []{}; 
	 
	 // 省略参数列表和返回值类型,返回值类型由编译器推导为int
	 int a = 3, b = 4;
	 [=]{return a + 3; }; 
	 
	 // 省略了返回值类型,无返回值类型
	 auto fun1 = [&](int c){b = a + c; }; 
	 fun1(10)
	 cout<<a<<" "<<b<<endl;
	 
	 // 各部分都很完善的lambda函数
	 auto fun2 = [=, &b](int c)->int{return b += a+ c; }; 
	 cout<<fun2(10)<<endl;
	 
	 // 赋值捕捉x
	 int x = 10;
	 auto add_x = [x](int a) mutable { x *= 2; return a + x; }; 
	 cout << add_x(10) << endl; 
	 return 0; 
}

7.类的新功能

7.1default关键字

在类里面,我们知道,如果我们不写构造函数,编译器会自动生成一个默认构造函数,若写了构造函数,编译器则不会生成。
但经常会有这样一个场景,写了有参构造函数,没写无参构造函数,在定义对象时需要无参的情况,这个时候再自己去写无参构造函数显的太麻烦了。default关键字可以强制编译器生成默认构造函数。

class A
{
public:
	A() = default;
	A(int a,int b)
	{
		_a = a;
		_b = b;
	}
private:
	int _a;
	int _b;
};
int main()
{
	A a;
	A aa(1,2);
}

7.2delete关键字

default是强制生成构造函数,对应的delete是强制不生成构造函数。
使用场景是有的时候我们不想要发生拷贝,而编译器是会自动生成默认拷贝构造函数的,所以用delete关键字可以防止拷贝。

7.3新增的默认构造函数

注意了,前面我们说过一个类有6个默认成员函数,那是在C++98里,到了C++11里多了移动构造和移动赋值构造两个,即C++11里有8个默认成员函数。
与之前6个不同,它们的生成规则更复杂了。

  • 如果你没有自己实现移动构造函数,且析构函数 、拷贝构造、拷贝赋值重载都没有
    实现。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型 成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如
    果实现了就调用移动构造,没有实现就调用拷贝构造。
  • 如果你没有自己实现移动赋值重载函数,且析构函数 、拷贝构造、拷贝赋值重载都
    没有实现,那么编译器会自动生成一个默认移动赋值。默认生成的移动赋值函数,对于内置 类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋 值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造
    完全类似)
  • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

7.4类成员变量初始化

我们知道,类成员如果是自定义类型,它会去调用它的构造函数,而针对于内置类型,则不会处理,C++11则增加了给缺省值来给内置成员变量初始化。

class A
{
private:
	int a = 10;
	string s;
};

8.可变参数模板

8.1Args…

C++11的新特性可变参数模板能够接受可变参数的函数模板和类模板,相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。

// Args是一个模板参数包,args是一个函数形参 参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}

由于语法不支持使用args[i]这样方式获取可变参数,所以我们的用一些奇招来一一获取参数包的值。

// 递归终止函数
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...);
}
int main()
{
	 ShowList(1);
	 ShowList(1, 'A');
	 ShowList(1, 'A', std::string("sort"));
	 return 0; 
}

可能有的人没看懂这个过程,我们以ShowList(1, 'A', std::string("sort"))为例,
第一次调用函数
value = 1 args包含'A'"sort"
第二次调用函数(即进入递归)
value = ‘A’ args = “sort”
第三次调用函数
此时调用的是重载版本的ShowList(const T& t)
其实这就一次次拆包的过程,把参数包里的参数一个个拿出来。

8.2emplace_back

可变参数模板的应用

template <class... Args>
void emplace_back(Args&&... args);

emplace系列的接口,支持模板的可变参数,并且万能引用。

int main()
{
	 std::list< std::pair<int, char> > mylist;
	 // emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象
	 // 除了用法上,和push_back没什么太大的区别
	 mylist.emplace_back(10, 'a');
	 mylist.emplace_back(20, 'b');
	 mylist.emplace_back(make_pair(30, 'c'));
	 mylist.push_back(make_pair(40, 'd'));
	 mylist.push_back({ 50, 'e' });

	 return 0; 
}

喜欢这篇文章的可以给个一键三连点赞👍关注💡收藏💖

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你帅你先说.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值