栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++11

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++11

目录

列表初始化

变量类型推导

STL中一些变化

右值引用

lambda表达式

新的类功能

可变参数模板

包装器

线程库


在C++ 98中,内置类型可以用列表进行初始化,但自定义类型不行,需要用到它的构造函数

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

内置类型

自定义类型

上图中的将{1,2}赋值给p2时,涉及到了隐式类型转换的问题,如果在它的构造函数前加个explicit,则就不能进行隐式类型转换,也就无法完成赋值

 

 

 对于new出来的对象,也能使用列表完成初始化

 

对于容器

比如vector,list,map等等 

而之所以可以这样初始化,是因为该类(模板类)有一个带有initializer_list类型参数的构造函数

变量类型推导

atuo

有些情况下可能不知道需要实际类型怎么给,或者类型写起来特别复杂,就需要自动推导类型

比如下图中的a与b相加,然后把值赋值给C,如果给成short,就会发生截断,造成数据丢失,所以这里c前面可以用auto自动推导类型

再比如需要用到map的迭代器时,会很复杂,也可以用auto来自动推导 

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

decltype

也能像auto一样推导出类型

decltype的一个使用场景

比如下图中,vector的模板参数类型,就不能用auto,但用decltype就可以

STL中一些变化

新增了一些容器,比如array,forward_list(单链表)

array,即静态数组,和vector差不多,区别在于一个是静态的,一个是动态的,但此容器作用不大,显得比较鸡肋

作用:支持迭代器,更好兼容STL容器玩法;对于越界的检查

右值引用

前面我们提到的&都是左值引用,而C++11则提出了一种新的引用,即右值引用&&

左值:可以取地址对象,比如下图中的a,*p

const修饰后的对象,还是左值 

右值:不能取地址对象,比如下图中的10,x+y表达式

对右值的引用

上图中的10不能取地址,但给它取别名后的rr1可以取地址,也能被修改,如果想不被修改,可以在前面加个const,如下图

 

交叉引用

左值引用不能引用右值,但加了const后可以

右值引用不能引用左值,但引用move以后可以

 

左值引用作参数,基本完美的解决所有问题,但左值引用作返回值时,只能解决部分问题,如下图,在重载+运算符就没法传值引用返回,只能传值返回

原因:因为str是局部变量,出了作用域就会销毁,而传值返回,就会进行一次深拷贝,赋值给临时对象,然后局部变量再被析构 ,如下图,效率就会很低

而C++11为了解决这个问题,则提出了移动构造,将右值分为了纯右值和将亡值

将需要被销毁的局部变量的资源转移,就能减少一次深拷贝,比如:首先上图中的str会进行一次移动构造,而不再是拷贝构造,将资源转移给临时对象

 

如下图,左值对象要想充当右值传参,调用移动构造,那就得被move,但这里会有代价,那就是被move且被赋值后的对象,资源会没有了

如果没有被赋值,只是被move,那就不会出现上述情况

只有拷贝构造时,由于编译器优化,所以减少了一次拷贝构造

拷贝构造和移动构造都有时,由于编译器优化,所以只有一次移动构造

优化原因,对于返回值数据很小时,临时对象是存在寄存器中的,而数据较大时,比如string类型数据,就会存放在主调函数中,如下图

 移动赋值  

对于容器的插入接口,都会提供一个右值引用版本 

Henry::string只有拷贝构造时

Henry::string既有拷贝构造,也有移动构造时

完美转发

 

对于上述的代码,直接调用下图的函数,而下图中的参数明明是右值引用,出现的结果却全是左值引用,原因在于t作为一个右值引用,它也能取地址,所以传参时,被当作左值传参

而解决方法则用到了完美转发,C++11通过forward函数来实现完美转发

注意:模板中的&&不是右值引用,而是万能引用

只要右值引用,再传递其它函数调用,要保持右值属性,必须用完美转发

lambda表达式

可调用对象:C语言的函数指针,C++98中的仿函数,而C++11则提出了lambda表达式

如上图, 如果我们要按照商品的几种属性来排序,那就需要写很多仿函数,太麻烦,所以采用lambda表达式就能很好地解决这个问题

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

[capture-list]:捕捉列表 、(parameters)参数列表、->return-type返回值类型、{ statement }函数体

mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修
饰符时,参数列表不可省略(即使参数为空)

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

实现一个两个数相加的lambda表达式,下图中的返回值类型(->int)可以省略 

 没有参数时,可以省略参数列表

 

捕捉列表

编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用

[var]:表示值传递方式捕捉变量var

[=]:表示值传递方式捕获所有父作用域中的变量(包括this)

[&var]:表示引用传递捕捉变量var

[&]:表示引用传递捕捉所有父作用域中的变量(包括this)

[this]:表示值传递方式捕捉当前的this指针

注意

父作用域指包含lambda函数的语句块

语法上捕捉列表可由多个捕捉项组成,并以逗号分割,如[=, &a, &b]

捕捉列表不允许变量重复传递,否则就会导致编译错误,如[=,a]

在块作用域以外的lambda函数捕捉列表必须为空

lambda表达式之间不能相互赋值,即使看起来类型相同

在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错

lamber 底层,其实是被处理成一个lambda_uuid的一个仿函数类

新的类功能

default

强制编译器生成默认构造函数 

delete

在C++98中,防拷贝的方法:1、只声明不实现 2、声明为私有,如下图

但上图中的方法,还是有些问题,比如在类外实现后,在类里面可以调用,所以不够严谨,而C++11为了解决这个问题,则提出了delete来解决这个问题

初始化问题

在C++11之前,没有写构造函数时,一个对象会对它的自定义类型成员调用它成员的默认构造函数,而对于它的内置类型则不做处理,而C++11为了解决这个问题,则打了个补丁,就是在它的内置类型成员声明的时候,给一个缺省值,如下图

注意:上述只是声明,不是初始化 

C++11新增的两个默认成员函数:移动构造和移动赋值

移动构造生成条件:没有自己实现移动构造函数,且析构函数 、拷贝构造、拷贝赋值重载都没有实现

默认移动构造的作用:对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造

移动赋值生成条件:没有自己实现移动赋值函数,且析构函数 、拷贝构造、拷贝赋值重载都没有实现

默认移动赋值的作用:对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值

可变参数模板

上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数 

通过sizeof来计算它的参数个数 

递归函数方式展开参数包

逗号表达式展开参数包 

上图中的两种方法都行

emplace_back接口

push_back支持一种写法

emplace_back下面两种都可以

原因在于它是模板的可变参数

 

emplace_back是直接构造,而push_back则是构造+移动构造,两者效率其实也相差不多

包装器

头文件

如下图,useF函数模板实例化了三份,而解决这个问题的方法则是包装器

function包装器

function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器

Ret是被调用函数的返回类型

Args...被调用函数的形参

有了function包装器后,如下图

注意:如果要得到非静态成员函数的地址,需要在前面加&符号,而普通函数的函数名就是地址

bind

std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可
调用对象,生成一个新的可调用对象来“适应”原对象的参数列表

调整参数顺序 

调整参数个数 

线程库

使用线程,必须引头文件 

无参构造函数

有参构造函数

fn是一个可调用对象

 

thread可执行函数参数不能是左值引用

解决方法

用一个ref的函数 

创建多线程

下图中是将资源移动赋值给了t

线程不支持拷贝构造和赋值,但支持移动构造和移动赋值

注意:线程使用后,不要忘记join

线程安全

例:用两个线程来对一个全局变量++操作

当++的次数较少时,多线程还不会出现线程安全问题

 

++的次数较多时,就会出现线程安全问题

解决办法则是加锁

头文件

定义一个全局对象

加锁加在外面

加锁加在里面 

注意:两者在结果上是没区别的,都是正确的,但是在效率上会有所区别,如果只是单纯的只有++x操作,那加锁在外面,效率会更高,也就是串行运行,但失去了多线程的意义,而加锁在里面,效率会较低一些,但是是交替并行运行的,不过如果执行的指令较多时,还是加锁在里面,效率会更高

加锁在里面效率低的原因

++x操作太快了,会导致t1和t2线程频繁切换上下文

加锁在里面,解决方法

不要使用互斥锁,使用自旋锁更好,但C++库里面没有

获取线程id

get_id()

注意:获取到的id不是一个整数,而是一个结构体,目的是为了兼容windows和linux

原子操作

解决线程安全问题的另一方法则是原子操作

头文件

在C++11中,原子类型只能从其模板参数中进行构造,不允许原子类型进行拷贝构造、移动构造以及operator=等

  

统计几种方法的时间

原子操作

 加锁在外面

 

 加锁在里面

死锁问题

如上图,如果push_back失败了,就会抛异常,没法解锁,造成死锁 

如上图,模拟push_back失败抛异常

解决方法,在catch中解锁

但此方法不太好,如果有多个异常就不好处理

另一解决方法

首先定义一个锁的守卫的类

然后定义一个它的对象即可

注意:库里面,不需要自己写

 

两个线程交替打印,一个打印奇数,一个打印偶数

 

上述代码有两个问题没解决,第一个是打印偶数的代码虽然放在前面,但不一定就会先打印,第二个则是不一定能完成交替打印,解决方法则需要用到条件变量

条件变量

头文件

阻塞函数,pred是可调用对象,只有当其返回值为true时,阻塞才会结束

 

唤醒函数

首先定义一个flag,以及条件变量

然后对于线程t1

对于线程t2

 

这样做能保证先运行线程t1,再运行线程t2,且交替运行,因为就算线程t2先抢到锁,也会被阻塞,无法打印,而后面调整flag,也是为了能交替运行,从而不会出现某个线程连续打印的情况

 如上图,就算让t2休眠3s,也能交替打印

 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1009804.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号