本篇主要是介绍C++11中新添加的一些特性。
文章目录
- 1.C++11简介
- 2.列表初始化
- 3.变量类型推导
- 4.新增容器---静态数组array
- 5.右值引用
- 6.lambda表达式
- 7.包装器
- 8.新的类功能
- 9.可变参数模板
一、C++11简介
在2003
年
C++
标准委员会曾经提交了一份技术勘误表
(
简称
TC1)
,使得
C++03这个名字已经取代了
C++98称为
C++11
之前的最新
C++
标准名称。不过由于
C++03(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能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更
强大,而且能提升程序员的开发效率,公司实际项目开发中也用得比较多,所以我们要作为一个
重点去学习。
C++11增加的语法特性非常篇幅非常多,我们这里没办法一 一讲解,所以本节课程
主要讲解实际中比较实用的语法。
附加:c++参考文档:
https://cplusplus.com/reference/
二、列表初始化
1.{}初始化
在C++98
中,标准允许使用花括号
{}
对数组或者结构体元素进行统一的列表初始值设定。
C++11扩大了用大括号括起的列表
(
初始化列表
)的使用范围,使其可用于所有的内置类型和用户自
定义的类型,
使用初始化列表时,可添加等号(=),也可不添加。
如下程序可以反映出这个问题:
程序结果如下所示:
2.利用std::initializer_list
采用auto来自动推导{}
程序结果如下:
std::initializer_list使用场景:
std::initializer_list一般是作为构造函数的参数,C++11对STL中的不少容器就增加
std::initializer_list作为参数的构造函数,这样初始化容器对象就更方便了。也可以作为operator=
的参数,这样就可以用大括号赋值
。
总结:C++11以后一切对象都可以用列表初始化。但是普通对象还是用以前的方式初始化,容器如果有需要可以用列表初始化。
三、变量类型推导
1.auto
在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局
部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃auto原来的用法,将
其用于实现自动类型腿断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初
始化值的类型。
2.
decltype
关键字
decltype
将变量的类型声明为表达式指定的类型。
3.nullptr
由于
C++
中
NULL
被定义成字面量
0
,这样就可能回带来一些问题,因为
0
既能指针常量,又能表示
整形常量。所以出于清晰和安全的角度考虑,
C++11
中新增了
nullptr
,用于表示空指针。
#ifndef NULL#ifdef __cplusplus#define NULL 0#else#define NULL ((void *)0)#endif#endif
四、新增容器---静态数组array
五、右值引用和移动语义
1、左值引用和右值引用
左值:是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号的左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。
左值引用:是左值的引用,相当于给左值起别名。
右值:是一个表示数据的表达式,如:字面常量、表达式的返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现在赋值符号的左边,右值不能取地址。
右值引用:是右值的引用,对右值取地址。
需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可
以取到该位置的地址,也就是说例如:不能取字面量
10
的地址,但是
rr1
引用后,可以对
rr1
取地
址,也可以修改
rr1
。如果不想
rr1
被修改,可以用
const int&& rr1
去引用,是不是感觉很神奇,
这个了解一下实际中右值引用的使用场景并不在于此,这个特性也不重要。
2. 左值引用与右值引用比较
左值引用总结:
1. 左值引用只能引用左值,不能引用右值。
2. 但是
const
左值引用既可引用左值,也可引用右值。
右值引用总结:
1. 右值引用只能右值,不能引用左值。
2. 但是右值引用可以
move
以后的左值。
3.右值引用使用场景和意义
右值划分:内置类型:纯右值,自定义类型:将亡值。
1.左值引用的使用场景:
做参数和做返回值都可以提高效率。
左值引用的短板:
但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,
只能传值返回。例如:
bit::string to_string(int value)
函数中可以看到,这里只能使用传值返回,
传值返回会导致至少
1
次拷贝构造
(
如果是一些旧一点的编译器可能是两次拷贝构造
)
。
右值引用和移动语义解决上述问题:
在bit::string
中增加移动构造,移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不
用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。
4.STL容器插入接口函数也增加了右值引用版本:
5.完美转发
std::forward 完美转发在传参的过程中保留对象原生类型属性
完美转发实际中的使用场景:
六.lambda表达式
lambda表达式书写格式:
[capture-list] (parameters) mutable -> return-type { statement
}
1. lambda表达式各部分说明
[capture-list] : 捕捉列表
,该列表总是出现在
lambda
函数的开始位置,
编译器根据
[]来
判断接下来的代码是否为lambda
函数
,
捕捉列表能够捕捉上下文中的变量供lambda
函数使用。
(parameters):参数列表。与
普通函数的参数列表一致
,如果不需要参数传递,则可以
连同()一起省略.
mutable:默认情况下,
lambda
函数总是一个
const
函数,
mutable可以取消其常量
性。使用该修饰符时,参数列表不可省略(
即使参数为空
)。
->returntype
:返回值类型
。用
追踪返回类型形式声明函数的返回值类型
,没有返回
值时此部分可省略。
返回值类型明确情况下,也可省略,由编译器对返回类型进行推
导
。
{statement}
:函数体
。在该函数体内,除了可以使用其参数外,还可以使用所有捕获
到的变量。
注意:
在
lambda
函数定义中,
参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为
空
。因此
C++11
中
最简单的
lambda
函数为:
[]{}
;
该
lambda
函数不能做任何事情。
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表达式之间不能相互赋值, 即使看起来类型相同 。
函数对象与lambda表达式
在底层编译器对于
lambda
表达式的处理方式,完全就是按照函数对象的方式处理的,即:如
果定义了一个
lambda
表达式,编译器会自动生成一个类,在该类中重载了
operator()
。
七.包装器
function包装器
function
包装器 也叫作适配器。
C++
中的
function
本质是一个类模板,也是一个包装器。
包装器可以很好的解决上面的问题
std::function 在头文件 < functional >// 类模板原型如下template < class T > function ; // undefinedtemplate < class Ret , class ... Args >class function < Ret ( Args ...) > ;模板参数说明:Ret : 被调用函数的返回类型Args… :被调用函数的形参
bind
std::bind
函数定义在头文件中,
是一个函数模板,它就像一个函数包装器
(
适配器
)
,
接受一个可
调用对象(
callable object
),生成一个新的可调用对象来
“
适应
”
原对象的参数列表
。一般而
言,我们用它可以把一个原本接收
N
个参数的函数
fn
,通过绑定一些参数,返回一个接收
M
个(
M
可以大于
N
,但这么做没什么意义)参数的新函数。同时,使用
std::bind
函数还可以实现参数顺
序调整等操作。
//
原型如下:
template < class Fn , class ... Args >/* unspecified */ bind ( Fn && fn , Args && ... args );// with return type (2)template < class Ret , class Fn , class ... Args >/* unspecified */ bind ( Fn && fn , Args && ... args );
可以将
bind
函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对
象来
“
适应
”
原对象的参数列表。
调用
bind
的一般形式:
auto newCallable = bind(callable,arg_list);
其中,
newCallable
本身是一个可调用对象,
arg_list
是一个逗号分隔的参数列表,对应给定的
callable
的参数。
当我们调用
newCallable
时,
newCallable
会调用
callable,
并传给它
arg_list
中
的参数。
arg_list
中的参数可能包含形如
_n
的名字,其中
n
是一个整数,这些参数是
“
占位符
”
,表示
newCallable
的参数,它们占据了传递给
newCallable
的参数的
“
位置
”
。数值
n
表示生成的可调用对
象中参数的位置:
_1
为
newCallable
的第一个参数,
_2
为第二个参数,以此类推。
八、新的类功能
默认成员函数
原来
C++
类中,有
6
个默认成员函数:
1.
构造函数
2.
析构函数
3.
拷贝构造函数
4.
拷贝赋值重载
5.
取地址重载
6. const
取地址重载
最后重要的是前
4
个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。
C++11
新增了两个:移动构造函数和移动赋值运算符重载。
针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:
如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任
意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类
型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,
如果实现了就调用移动构造,没有实现就调用拷贝构造。
如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中
的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内
置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋
值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。
(
默认移动赋值跟上面移动构造
完全类似
)
如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
强制生成默认函数的关键字
default:
C++11
可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原
因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以
使用
default
关键字显示指定移动构造生成。
禁止生成默认函数的关键字delete:
如果能想要限制某些默认函数的生成,在C++98
中,是该函数设置成
private,并且只声明补丁
已,这样只要其他人想要调用就会报错。在C++11
中更简单,只需在该函数声明加上
=delete即
可,该语法指示编译器不生成对应函数的默认版本,称=delete
修饰的函数为删除函数。
九、可变参数模板
上面的参数
args
前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为
“
参数
包
”
,它里面包含了
0
到
N
(
N>=0
)个模版参数。我们无法直接获取参数包
args
中的每个参数的,
只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特
点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用
args[i]
这样方式获取可变
参数,所以我们的用一些奇招来一一获取参数包的值。
STL
容器中的
empalce
相关接口函数:
template < class ... Args >void emplace_back ( Args && ... args);