C++进阶---C++11

本篇主要是介绍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 ;     // undefined
template < 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);

 

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

‘(尐儍苽-℡

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

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

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

打赏作者

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

抵扣说明:

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

余额充值