C++11(1)——右值引用、统一初始化、C++发展史

一、C++的发展史

1.C++的产生

C++的起源可以追溯到1979年,当时本贾尼(C++创始人)在贝尔实验室从事计算机科学与软件工程的研究工作。面对项目中复杂的软件开发任务,特别是模拟和操作系统的开发工作,他感受到了现有语言(如C)在表达能力、可维护性和可拓展性方面的不足。
1983年,他在C语言的基础上添加了面向对象编程的特性,设计出了C++语言的雏形,此时的C++已经有了类、封装、继承等核心概念,为后来的面向对象编程奠定了基础,这一年该语言被正式命名为C++。
C++的标准化工作与1989年开始,并成立一个ANSI和ISO国际标准化组织的联合标准化委员会。1994年标准化委员会提出了第一个标准化草案。该草案增加了部分新特征。
在完成C++标准化的第一个草案不久,STL是惠普实验室开发的一系列软件的统称。通过了标准化的第一个草案之后,联合标准化委员会投票并通过了将STL包含到C++标准中的提议。STL对C++的扩展超出C++的最初定义范围。虽然增加STL是个很重要的决定,但也因此延缓了C++标准化的进程。
1997年通过了最终草案,1998年C++的ANSI/ISO标准被投入使用。

2.C++11简介

相比于C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。相比较而言,C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更
强大,而且能提升程序员的开发效率,公司实际项目开发中也用得比较多,所以我们要作为一个重点去学习。C++11增加的语法特性非常篇幅非常多,我们这里没办法一 一讲解,所以本篇文章主要讲解实际中比较实用的语法。

二、统一的列表初始化

注意:列表初始化和初始化列表不是一个东西,初始化列表是我们构造函数体和函数声明中间的那个东西(之前有提到过)。

1.{}初始化

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

struct Point
 {
 int _x;
 int _y;
 };
 int main()
 {
 int array1[] = { 1, 2, 3, 4, 5 };
 int array2[5] = { 0 };
 Point p = { 1, 2 };
 return 0;
 }

我们在C语言中创建数组会经常这么写。

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

struct Point
 {
 int _x;
 int _y;
 };
 int main()
 {
 int x1 = 1;
 int x2{ 2 };//等价于int x2=2;
 int array1[]{ 1, 2, 3, 4, 5 };
 int array2[5]{ 0 };
 Point p{ 1, 2 };
 }
 // C++11中列表初始化也可以适用于new表达式中
int* pa = new int[4]{ 0 };
 

创建对象时也可以使用列表初始化方式调用构造函数初始化。

class Date
 {
 public:
 Date(int year, int month, int day)
 :_year(year)
 ,_month(month)
 ,_day(day)
     {
 cout << "Date(int year, int month, int day)" << endl;
     }
 //....
 }
 int main()
 {
 Date d1(2022, 1, 1); // 老方法
 // C++11支持的列表初始化,这里会调用构造函数初始化
Date d2{ 2022, 1, 2 };//d2和d3的结果相同
 Date d3 = { 2022, 1, 3 };
 return 0;
 }

2、std::initializer_list

initializer_list就是初始化链表,它一般是作为构造函数的参数,C++11对STL中的不少容器就增加
std::initializer_list作为参数的构造函数,这样初始化容器对象就更方便了。也可以作为operator=的参数,这样就可以用大括号赋值。比如我们常见的vector,map,list等。

vector< int > v = { 1,2,3,4 };
list< int > lt = { 1,2 };
// 使用大括号对容器赋值
v = {10, 20, 30};

需要区分的是,initializer_list和前面的{}初始化并不是同一原理,上面的{}可以理解为隐式类型转换,只能传规定数量的参数(Date类只能传3个,多传就会报错)而initializer_list可以传入多个参数。

三、声明

1、auto

在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃auto原来的用法,将其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。

2、decltype

关键字decltype将变量的类型声明为表达式指定的类型。相当于它接收之前的变量类型来声明新的变量。

 int main()
 {
 const int x = 1;
 double y = 2.2;
 decltype(x * y) ret; // ret的类型是double
 decltype(&x) p;      
// p的类型是int*
 cout << typeid(ret).name() << endl;
 cout << typeid(p).name() << endl;
 }
 return 0;

3、nullptr

由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。
在之前的内容中我们多次使用过这个空指针就不作过多解释了。

4、范围for

它的底层是迭代器,由于之前也使用过多次在此不过多解释。
auto与范围for的讲解请点此访问

5、STL中的一些变化

增加了一些新容器,比如unordered_set和unordered_map这两个容器我们在上篇内容已经详细的讲解了,除此之外还有array(静态数组)和forward_list(实际中并不常用)
哈希表与unordered_set和unordered_map
除此之外还有一些新接口:比如提供了cbegin和cend方法返回const迭代器等等,但是实际意义不大,因为begin和end也是可以返回const迭代器的,这些都是属于锦上添花的操作。但其中有一个很重要的内容——右值引用。接下来我们就讲解一下有关右值引用的相关内容。

四、右值引用与移动语义

1、左值引用与右值引用

传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们之前学习的引用(&)就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。
那什么是左值?什么是左值引用?
左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,**左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。(但左值可以出现在赋值符号右边)**定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。​​​​​​​
因此,我们区分是否是左值的方法就是看其是否可以取地址。

 // 以下的p、b、c、*p、d都是左值
int* p = new int(0);
 int b = 1;
 const int c = 2;
 const int d = b;
 // 以下几个是对上面左值的左值引用
int*& rp = p;
 int& rb = b;
 const int& rc = c;
 int& pvalue = *p;

什么是右值?什么是右值引用?
右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。

double x = 1.1, y = 2.2;
 // 以下几个都是常见的右值,常量临时对象,匿名对象
10;
 x + y;
 string("11111");

左值引用就是我们之前熟悉的引用(给左值取别名),那右值引用同理,就是给右值取别名,但语法规则有些不同。

 // 以下几个都是对右值的右值引用
int&& rr1 = 10;
 double&& rr2 = x + y;

发现与左值引用相比,右值引用就是多了一个&进行区分。
那左值引用能否给右值取别名呢?即

int &x=10;
const int &y=0;

运行结果我们发现,第一行不成立,第二行成立,也就是说左值引用不能直接给右值取别名,但const左值引用可以。
那反过来,右值引用能不能给左值取别名呢?答案是不能直接引用,需要move(左值)后才可引用。这个move是std中的一个函数,它会返回一个右值引用的值实现引用。(其本质是强制类型转换)

int &&rx1=b;
int &&rx2=move(b);

第一行就会报错,第二行就能正常引用。

2、右值引用的使用场景和意义

我们知道,引用的意义就是减少拷贝提高效率,那么右值引用出现的原因就是有些情况是左值引用没有解决的。左值引用的作用有传参和传返回值,但传返回值的情况并没有完全解决。比如:有些场景只能用传值返回。
在这里插入图片描述
返回的是一个局部变量,出了这个作用域就会销毁,如果用左值引用就是野引用了。且传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造,拷贝构造的优化,之前提到过)。为了解决这个问题,引入一个新概念——移动构造。它们的函数参数有些不一样。

string(const string&s) //拷贝构造
string(string &&s)//移动构造

如果不写移动构造的话,传左值和右值都会走拷贝构造,但如果有移动构造的话,传左值会走拷贝构造,传右值会走移动构造。
刚才在上面列举了右值的几种常见类型,但大致分类两类:纯右值:内置类型右值。将亡值:类类型的右值。(比如匿名对象等临时创建的对象,用完就要销毁)。移动构造的思路就是:反正你出了作用域也没有了,不如把这个资源给我,我就不用再拷贝了。所以移动构造是一种抢夺资源的行为。
在这里插入图片描述
我们看一下有移动构造的情况下的优化
优化前:

str先拷贝成一个右值的临时对象然后再通过移动构造赋给s1
优化后:
在这里插入图片描述
中间不产生临时对象,直接把str隐式move成右值。
注意:只有深拷贝的类,移动拷贝才有意义。
除此之外,还有一个概念——移动赋值
在这里插入图片描述
道理和移动构造类似。
不仅是传值返回,C++11在push_back中也使用了移动构造
在这里插入图片描述
其原理与上面的相似。insert函数也如此。
接下来我们来分析一种情况:

//假设我们已经写好了左值和右值的insert
void push_back(T&&x)
{
insert(end(),x);
)

当我们想验证结果时,发现其使用了右值的插入函数,这是我们的预期,但它又走了左值的insert。
这是因为,右值的右值引用其本质是左值。为什么会有这种退化式的设计呢?因为,如果其还是右值,那么我在进行资源交换的时候就无法实现,为了解决这一问题,我们可以用move

void push_back(T&&x)
{
insert(end(),move(x));
)

注意,**x的本质未变,只是为了让编译器识别此处传的是右值。**这种操作可以方便我们一步步传参时保持右值的属性,减少拷贝。
但并不是所有情况move都可以解决问题,下面我们介绍一个新的概念。

3.完美转发

我们通过刚才函数的左值和右值版本发现,貌似构造,插入等函数想提高效率都需要单独写一个右值版本的函数。但C++11时就有一个提问:以后所有函数都要写两个版本吗?会不会太麻烦了?所以,C++在模板部分在此有了一些调整:

template<class T>
void func(T&&x)
{
//....
}

其中“&&”并不是右值引用版本,而被称为万能引用,从结构上看,只是多了一个模板,别的好像并没有太大区别。但虽然这个地方像右值引用,但我可以通过你传的参数进行推导,这样就不用写两个版本了,你传左值他就会生成左值版本的,右值也同理。我们也称引用折叠。

如果模板实例化是左值引用,保留属性直接进行下一步传参;模板实例化是右值引用,右值引用属性会退化成左值,需要转换成右值再进行下一步传参。

但是,编译器是不支持用语法判断所传的类型,比如上面的实例,我们不可能用T==“&”。因此,完美转发的作用就来了。语法如下:

template<class T>
void func(T&&x)
{
  Fun(forward<T>(t));
}

传的是左值则不变,若传的是右值但退化成左值,完美转发就会把t重新以右值的身份传给Fun函数。完美转发适用于我不知道传入的是左值还是右值,不像我们前面明确知道是左右值的情况下可以用move,上面这个情况如果我写成move那么如果传入左值就达不到想要的结果了。他会一律处理成右值。

五、类的新功能

我们在类与对象部分提到,类有6个默认成员函数,但C++又新增了两个:移动构造函数和移动赋值运算符重载。
针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:

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

但一般打破这种规则的时候都是深拷贝(有资源要释放),那么需要我们自己写拷贝、析构、赋值重载,当然移动构造和移动赋值也要自己写了。

强制生成默认函数的关键字default:
C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。

class Person
 {
 public:
 Person(const char* name = "", int age = 0)
 :_name(name)
 , _age(age)
 {}
 Person(const Person& p)
 :_name(p._name)
 ,_age(p._age)
 {}
比特就业课
Person(Person&& p) = default;
 private:
 bit::string _name;
 int _age;
 };
 int main()
 {
 Person s1;
 Person s2 = s1;
 Person s3 = std::move(s1);
 return 0;
 }

禁止生成默认函数的关键字delete:
如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁
已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即
可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/907101.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机毕业设计Spark+大模型知识图谱中药推荐系统 中药数据分析可视化大屏 中药爬虫 机器学习 中药预测系统 中药情感分析 大数据毕业设计

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

javascript-Web APLs (三)

事件流 指的是事件完整执行过程中的流动路 说明&#xff1a;假设页面里有个div&#xff0c;当触发事件时&#xff0c;会经历两个阶段&#xff0c;分别是捕获阶段、冒泡阶段 简单来说&#xff1a;捕获阶段是 从父到子 冒泡阶段是从子到父 实际工作都是使用事件冒泡为主 事件…

11.Three.js使用indexeddb前端缓存模型优化前端加载效率

11.Three.js使用indexeddb前端缓存模型优化前端加载效率 1.简述 在使用Three.js做数字孪生应用场景时&#xff0c;我们常常需要用到大量模型或数据。在访问我们的数字孪生应用时&#xff0c;每次刷新都需要从web端进行请求大量的模型数据或其他渲染数据等等&#xff0c;会极大…

keepalive+mysql8双主

1.概述 利用keepalived实现Mysql数据库的高可用&#xff0c;KeepalivedMysql双主来实现MYSQL-HA&#xff0c;我们必须保证两台Mysql数据库的数据完全一致&#xff0c;实现方法是两台Mysql互为主从关系&#xff0c;通过keepalived配置VIP&#xff0c;实现当其中的一台Mysql数据库…

C++ 实现俄罗斯方块游戏

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

项目实战:基于Linux的Flappy bird游戏开发

一、项目介绍 项目总结 1.按下空格键小鸟上升&#xff0c;不按小鸟下落 2.搭建小鸟需要穿过的管道 3.管道自动左移和创建 4.小鸟撞到管道游戏结束 知识储备 1.C语言 2.数据结构-链表 3.Ncurses库 4.信号机制 二、Ncurses库介绍 Ncurses是最早的System V Release 4.0 (SVr4)中…

nginx上传文件超过限制大小、响应超时、反向代理请求超时等问题解决

1、文件大小超过限制 相关配置&#xff1a; client_max_body_size&#xff1a; Syntax:client_max_body_size size;Default:client_max_body_size 1m;Context:http, server, location 2、连接超时: proxy_read_timeout&#xff1a; Syntax:proxy_read_timeout time;Default…

C++ --- 多线程的使用

目录 一.什么是线程&#xff1f; 线程的特点&#xff1a; 线程的组成&#xff1a; 二.什么是进程&#xff1f; 进程的特点&#xff1a; 进程的组成&#xff1a; 三.线程与进程的关系&#xff1a; 四.C的Thread方法的使用&#xff1a; 1.创建线程&#xff1a; 2.join(…

基于Spring Boot的医疗陪护系统设计与实现(源码+定制+开发)病患陪护管理平台、医疗服务管理系统、医疗陪护信息平台

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

ViT面试知识点

文章目录 VITCLIPSAMYOLO系列问题 VIT 介绍一下Visual Transformer&#xff1f; 介绍一下自注意力机制&#xff1f; 介绍一下VIT的输出方式 介绍一下VIT做分割任务 VIT是将NLP的transformer迁移到cv领域&#xff0c;他的整个流程大概如下&#xff1a;将一张图片切成很多个pat…

【Comsol教程】计算流道中的流量

在进行微流控方面的仿真的时候可能需要计算某一流道中流量的大小&#xff0c;下面展示如何计算。 流量分为质量流量和体积流量&#xff0c;我们常采用体积流量。在COMSOL中有两种方法计算&#xff0c; 1.使用Comsol内置的函数 这里我使用的是蠕动流模块【spf】,定义了3个开放边…

LeetCode 3226. 使两个整数相等的位更改次数

. - 力扣&#xff08;LeetCode&#xff09; 题目 给你两个正整数 n 和 k。你可以选择 n 的 二进制表示 中任意一个值为 1 的位&#xff0c;并将其改为 0。 返回使得 n 等于 k 所需要的更改次数。如果无法实现&#xff0c;返回 -1。 示例 1&#xff1a; 输入&#xff1a; n …

项目升级到.Net8.0 Autofac引发诡异的问题

前两天把项目升级到.Net8.0了&#xff0c;把.Net框架升级了&#xff0c;其他一些第三方库升级了一部分&#xff0c;升级完以后项目跑不起来了&#xff0c;报如下错误&#xff1a; An unhandled exception occurred while processing the request. DependencyResolutionExcepti…

RabbitMQ 七种工作模式介绍

目录 1.简单模式队列 2.WorkQueue(⼯作队列) 3 Publish/Subscribe(发布/订阅) 4 Routing(路由模式) 5.Topics(通配符模式) 6 RPC(RPC通信) 7 Publisher Confirms(发布确认) RabbitMQ 共提供了7种⼯作模式供我们进⾏消息传递,接下来一一介绍它的实现与目的 1.简单模式队列…

自动化测试类型与持续集成频率的关系

持续集成是敏捷开发的一个重要实践&#xff0c;可是究竟多频繁的集成才算“持续”集成&#xff1f; 一般来说&#xff0c;持续集成有3种常见的集成频率&#xff0c;分别是每分钟集成、每天集成和每迭代集成。项目组应当以怎样的频率进行集成&#xff0c;这取决于测试策略&…

操作系统期中复习2-4单元

Chapter-2 第一个图形界面——Xerox Alto 早期操作系统&#xff1a;规模小&#xff0c;简单&#xff0c;功能有限&#xff0c;无结构(简单结构)。&#xff08;MS-DOS,早期UNIX&#xff09; 层次结构&#xff1a;最底层为硬件&#xff0c;最高层为用户层&#xff0c;自下而上构…

2-141 怎么实现ROI-CS压缩感知核磁成像

怎么实现ROI-CS压缩感知核磁成像&#xff0c;这个案例告诉你。基于matlab的ROI-CS压缩感知核磁成像。ROI指在图像中预先定义的特定区域或区域集合&#xff0c;选择感兴趣的区域&#xff0c;通过减少信号重建所需的数据来缩短信号采样时间&#xff0c;减少计算量&#xff0c;并在…

Android中同步屏障(Sync Barrier)介绍

在 Android 中&#xff0c;“同步屏障”&#xff08;Sync Barrier&#xff09;是 MessageQueue 中的一种机制&#xff0c;允许系统临时忽略同步消息&#xff0c;以便优先处理异步消息。这在需要快速响应的任务&#xff08;如触摸事件和动画更新&#xff09;中尤为重要。 在 An…

【tomcat系列漏洞利用】

Tomcat 服务器是一个开源的轻量级Web应用服务器&#xff0c;在中小型系统和并发量小的场合下被普遍使用。主要组件&#xff1a;服务器Server&#xff0c;服务Service&#xff0c;连接器Connector、容器Container。连接器Connector和容器Container是Tomcat的核心。一个Container…

【压力测试】如何确定系统最大并发用户数?

一、明确测试目的与了解需求 明确测试目的&#xff1a;首先需要明确测试的目的&#xff0c;即为什么要确定系统的最大并发用户数。这通常与业务需求、系统预期的最大用户负载以及系统的稳定性要求相关。 了解业务需求&#xff1a;深入了解系统的业务特性&#xff0c;包括用户行…