C++ | C++11新特性(上)

目录

前言

一、列表初始化

二、声明

1、auto

2、decltype

3、nullptr

三、STL容器的变化

四、右值引用与移动语义

1、左值与左值引用

2、右值与右值引用

3、右值引用与左值引用的比较

4、右值引用的场景及意义

(1)做参数 

(2)返回值

5、完美转发 

(1)万能引用 

(2)完美转发

五、类的新功能

1、默认成员的改变

2、default与delete关键字

3、final与override


前言

        本章主要讲解一些关于C++11常用语法;不会将每个语法都介绍一边,将主要语法进行讲解;如果想要了解全部有关C++11语法可以访问下方链接网站;

C++11

一、列表初始化

        没错,就是列表初始化,并不是初始化列表,一定要搞清楚了。这里说的是列表初始化,并不是构造函数中的初始化列表;其实列表初始化在C语言中也有体现,如下代码;

// 列表初始化
struct point
{int _x;int _y;
};void test1()
{// C++98 C语言版的列表初始化int arr1[] = { 1,2,3,4,5 };int arr2[5] = { 0 };struct point p = { 1,2 };
}

        在C语言中,我们可以通过花括号的方式对数组和结构体进行初始化;C++11中,我们同样提供了这种初始化方式;万物皆可花括号初始化;其中等号一般可省略;

	// C++11 万物皆可花括号(= 可省略)int x1 = 3;int x2{ 3 };int* pa = new int[2]{ 3 };

        其实,不仅是我们内置成员可以这么初始化,连我们自定义类型也可以这样初始化,如下述代码所示;

	string str1 = { "hhhh" };string str2{ "hhhh" };vector<int> v1 = { 1,2,3,4,5,6 };vector<int> v2 { 1,2,3,4,5,6 };list<int> l1 = { 1,2,3,4,5,6 };list<int> l2 { 1,2,3,4,5,6 };Date d1 = { 2022, 8, 5 };Date d2 { 2022, 8, 5 };

        这是因为我们的C++11中,为我们提供了一种新的类型,这个类型叫initializer_list;而我们的STL容器都提供了一个这个版本的构造;我们可以通过下述代码证明initializer_list的存在;

void test2()
{// initializer_list  头文件<initializer_list>auto li1 = { 1,2,3,4,5,6 };initializer_list<int> li2 = { 1,2,3,4,5,6 };cout << typeid(li1).name() << endl;cout << typeid(li2).name() << endl;}

        一个是通过auto自动识别类型,一个是显示声明;可输出的结果都相同;

        自C++11后,许多容器都提供了这种初始化的方式; 

二、声明

1、auto

        auto其实在C++98时就存在了,但由于在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了。而在C++11中,废除了以前的用法,实现了自动类型推断,在很多场景下都变得好用了许多;

void test4()
{map<int, int> m1;map<int, int>::iterator it1 = m1.begin();auto it2 = m1.begin();
}

2、decltype

        auto用于类型自动识别,而我们的则是可以推导我们的表达式类型,并用该类型进行初始化;具体用法如下;

void test3()
{// decltypeint a = 1;int b = 2;decltype(a + b) c = 10;cout << typeid(c).name() << endl;// 应用vector<decltype(a + b)> v1;cout << typeid(v1).name() << endl;
}

3、nullptr

        没错,我们平常使用的nullptr也是C++11后推出的,那为什么不用NULL呢?实际上NULL的定义存在BUG;具体细节可以移步下方链接最后一个知识点;

C++初阶

三、STL容器的变化

        在我们C++11后,我们的容器也有了很大的变化;如下图所示;

        其中,array对标的是我们C语言的数组,可实际上,我们有非常好用的vector了,因此array使用并不怎么需要了;但C++11中unordered系类的容器有非常显著的效果,确实挺好用,比起map与set,虽然unordered系类并不排序,但是效率明显会比map与set高很多,其增删查改的时间复杂度都接近O(1);同时C++11的容器中提供了const迭代器版本cbegin。cend,但实际上用的也不多;

四、右值引用与移动语义

        C++11增加的右值相关语法进一步提高了我们代码的效率;是一个非常有用的知识点;下面我们首先了解什么叫左值,什么又叫右值;

1、左值与左值引用

        左值是一个表示数据的表达式;我们可以对他进行取地址以及赋值;左值既可以出现在=的左边,也可以出现在=的右边;左值引用就是左值的引用,左值的别名;

void test5()
{// a/b/p都是左值int a = 1;const int b = 4;int* p = new int(10);// ra/rb/rp都是左值引用int& ra = a;const int& rb = b;int*& rp = p;
}

2、右值与右值引用

        右值也是一个表示数据的表达式,但是不同的是右值通常是字面量、表达式返回值、函数返回值等;右值只能出现在=的右边,不能出现在=的左边;且右值无法取地址;这是二者最本质的区别;右值引用就是右值的引用,右值的别名;

void test6()
{int a = 1;int b = 4;// 以下均为右值a + b;123;func();// 以下均为右值引用int&& r1 = a + b;int&& r2 = 123;int&& r3 = func();
}

注意:关于上述,我们区分左值和右值的可通过是否可以取地址来进行判断,若可取地址,则必定是左值,若不可则是右值,这里还有一个小小的补充,右值引用在引用右值后,保存进了一个变量,该变量是左值,可以进行取地址;

void test7()
{int a = 3;int b = 6;// a + b是右值,ra是右值引用,ra变量是左值int&& ra1 = a + b;const int&& ra2 = a + b;cout << &ra1 << endl; // 可以取地址// cout << &(a + b) << endl; // err 右值不能取地址ra1 = 20; // 正确// ra2 = 20; // err const修饰的右值引用不可修改
}

3、右值引用与左值引用的比较

左值引用:

1、可以引用左值

2、const修饰后既可引用左值,也可引用右值

右值引用:

1、可以引用右值

2、可以引用move后的左值

void test8()
{int a = 2;int b = 5;// 左值引用可以引用左值// const佐治引用可以引用右值int& ra1 = a;//int& ra2 = (a + b); // err 不能引用右值const int& ra3 = (a + b); // const修饰的左值引用可以引用右值// 右值引用可以引用右值// 右值引用可以引用move后的左值int&& ra4 = (a + b);// int&& ra5 = a; // err 不可引用左值int&& ra6 = move(a); // 可引用move后的左值
}

4、右值引用的场景及意义

        关于右值引用的场景与意义,我们使用我们之前封装的简略版string进行展示; 

namespace MySpace
{class string{public:string(const char* str = ""):_size(strlen(str)), _capacity(_size){cout << "string(char* str)" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}// s1.swap(s2)void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}// 拷贝构造string(const string& s):_str(nullptr){cout << "string(const string& s) -- 深拷贝" << endl;string tmp(s._str);swap(tmp);}// 赋值重载string& operator=(const string& s){cout << "string& operator=(string s) -- 深拷贝" << endl;string tmp(s);swap(tmp);return *this;}string operator+(char ch){string tmp(*this);tmp.push_back(ch);return tmp;}~string(){delete[] _str;_str = nullptr;}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void push_back(char ch){if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';}//string operator+=(char ch)string& operator+=(char ch){push_back(ch);return *this;}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity; // 不包含最后做标识的\0};
}
(1)做参数 

        我们给上述的string类添加一个移动构造版本; 形参是右值时才会调用这个版本;一般右值都是一些声明周期快要介绍的值;可能即将会被销毁;我们可以直接偷走右值的资源来减少深拷贝的次数;代码如下所示;

		// 移动构造(拷贝构造特殊版本)string(string&& s):_str(nullptr), _size(0), _capacity(0){cout << "string(string&& s)  -- 移动语义" << endl;swap(s);}

        我们再对上述情况进行分析;当我们传入一个右值时,我们调用移动构造,直接转移其资源,而不进行深拷贝;

(2)返回值

        我们的左值引用解决了当我们传回非局部对象时的拷贝问题;但是对于局部对象,我们还是需要进行深拷贝;下面以to_string函数作为举例;

        但是在我们C++11后,引入了右值引用的概念;我们同样使用这段代码; 

        同样,不仅有移动构造,还有移动赋值,原理是一样的,通过识别形参是左值还是右值选择不同的接口, 如果是左值就采用深拷贝的方式进行;如果是右值,则采用移动资源的方式来进行;同样库里的STL容器也更新了许多关于移动语义的接口;

5、完美转发 

有如下程序,猜测如下程序的输出结果是什么?

void Fun(int& x) 
{ cout << "左值引用" << endl; 
}
void Fun(const int& x) 
{ cout << "const 左值引用" << endl; 
}
void Fun(int&& x) 
{ cout << "右值引用" << endl; 
}
void Fun(const int&& x) 
{ cout << "const 右值引用" << endl; 
}
template<typename T>
void PerfectForward(T&& t)
{Fun(t);
}void test10()
{int a = 10;PerfectForward(a);PerfectForward(move(a));const int b = 20;PerfectForward(b);PerfectForward(move(b));
}

结果都是我们的左值引用,这个结果你猜到了吗? 

(1)万能引用 

        在上述代码中,我们将模板中的&&成为万能引用;也叫折叠引用; 

 

(2)完美转发

        完美转发的情况就诞生在上述情况中,当我们用右值引用接收右值时,我们的右值引用对象接收右值,并找个位置储存起来;此时我们的引用对象实际上是左值;当我们想往下一层调用传达右值就无法做到了;如上述我们PerfectForward接收后,用右值引用对象 t 保存了下来,此时 t 实际上已经是左值了,所以我们传给下一层时,传的时左值引用的接口;我们可以称这种现象为丢失了右值的属性;此时我们可以用我们的 forward 保持其原有的右值属性,这种方式我们称之为完美转发;如下述代码;

void Fun(int& x) 
{ cout << "左值引用" << endl; 
}
void Fun(const int& x) 
{ cout << "const 左值引用" << endl; 
}
void Fun(int&& x) 
{ cout << "右值引用" << endl; 
}
void Fun(const int&& x) 
{ cout << "const 右值引用" << endl; 
}
template<typename T>
void PerfectForward(T&& t)
{Fun(forward<T>(t));
}void test10()
{int a = 10;PerfectForward(a);PerfectForward(move(a));const int b = 20;PerfectForward(b);PerfectForward(move(b));
}

五、类的新功能

1、默认成员的改变

        之前类与对象中我们提过,类有六大默认成员,分别为默认构造、拷贝构造、赋值重载。析构函数、取地址重载以及const取地址重载;C++11后,又新增了两个默认的成员函数,分别为移动构造函数移动赋值重载函数

移动构造函数与移动赋值重载函数具有以下特性:

1、若自己未显式声明没有实现析构函数、拷贝构造、赋值重载中任意一个,则生成默认的

2、默认生成的移动构造/移动赋值对内置成员会逐字节拷贝,对于自定义成员则调用他们的移动构造/移动赋值,若该自定义成员没有移动构造/移动赋值,则调用他们的拷贝构造/赋值;

class Person
{
public:Person(int age = 18, MySpace::string name = "Jack"):_age(age),_name(name){}
private:int _age;MySpace::string _name;
};

        此时我们的Person类满足上述自动生成默认移动构造与移动赋值的条件;并且我们发现对于自定义成员sting也调用了其移动构造与移动赋值; 

2、default与delete关键字

        default关键字可以让编译器帮我们生成默认的成员函数函数,delete关键字可以不让编译器生成默认的成员函数,即使已经满足自动生成条件;如下所示;

class A
{
public:// 生成默认的构造A() = default;A(int a) :_a(a){}// 不允许生成拷贝构造A(const A& a) = delete;
private:int _a;
};void test12()
{A a1;A a2 = a1; // err 拷贝构造函数被禁止生成了}

3、final与override

        final作用有二,其一是修饰类,使得该类不允许被继承下去了;其二是修饰虚函数,使得该虚函数不能被继续重写了;

        override的作用则是修饰虚函数,检查子类虚函数是否重写;

class Base
{
public:virtual void func1() final // 该虚函数无法被重写{cout << "virtual void func1() final" << endl;}virtual void func2(){cout << "virtual void func2()" << endl;}
private:int _b;
};class Deriver : public Base
{
public:/*void func1(){cout << "void func1()" << endl;}*/void func2() override  // 检查是否重写父类虚函数{cout << "class Deriver : public Base" << endl;}
private:int _d;
};

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java数据流、对象流

文章目录 数据流与对象流说明对象流API认识对象序列化机制如何实现序列化机制反序列化失败问题 数据流与对象流说明 如果需要将内存中定义的变量&#xff08;包括基本数据类型或引用数据类型&#xff09;保存在文件中&#xff0c;那怎么办呢&#xff1f; int age 300; char …

Javascript 数据结构[入门]

作者&#xff1a;20岁爱吃必胜客&#xff08;坤制作人&#xff09;&#xff0c;近十年开发经验, 跨域学习者&#xff0c;目前于海外某世界知名高校就读计算机相关专业。荣誉&#xff1a;阿里云博客专家认证、腾讯开发者社区优质创作者&#xff0c;在CTF省赛校赛多次取得好成绩。…

新手注意事项-visual studio 来实现别踩白块儿

自己之前为了熟悉easyx练习过一个简单的项目&#xff0c;别踩白块儿&#xff0c;链接在这里&#xff0c;别踩白块儿&#xff0c;当时比较稚嫩&#xff0c;很多东西都不会&#xff0c;可以说是只知道最基本的语法&#xff0c;头文件都不知道&#xff0c;一个一个查资料弄懂的&am…

FreeRTOS(vTaskList与vTaskGetRunTimeStats)

目录 1、Cube配置 ①配置SYS ②配置TIM3 ③配置USART2 ④配置FreeRTOS ⑤配置中断优先级 2、代码添加改动 ①在main函数合适位置开启TIM3中断 ②修改HAL_TIM_PeriodElapsedCallback函数 ③完善两个相关函数 ④vTaskList与vTaskGetRunTimeStats的使用 vTaskList&#xff…

c语言每日一练(2)

前言&#xff1a; 每日一练系列&#xff0c;每一期都包含5道选择题&#xff0c;2道编程题&#xff0c;博主会尽可能详细地进行讲解&#xff0c;令初学者也能听的清晰。每日一练系列会持续更新&#xff0c;暑假时三天之内必有一更&#xff0c;到了开学之后&#xff0c;将看学业情…

VUE框架:vue2转vue3全面细节总结(3)路由组件传参

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a; lqj_本人_python人工智能视觉&#xff08;opencv&#xff09;从入门到实战,前端,微信小程序-CSDN博客 最新的uniapp毕业设计专栏也放在下方了&#xff1a; https://blog.csdn.net/lbcy…

嵌入式开发学习(STC51-18-LCD液晶显示)

内容 在LCD1602液晶上显示字符信息&#xff1b; LCD1602介绍 简介 1602液晶也叫1602字符型液晶&#xff0c;它能显示2行字符信息&#xff0c;每行又能显示16个字符&#xff1b; 它是一种专门用来显示字母、数字、符号的点阵型液晶模块&#xff1b; 它是由若干个5x7或者5x…

防火墙第二次作业

一、什么是防火墙&#xff1f; 百度给出个一个定义&#xff1a;防火墙技术是通过有机结合各类用于安全管理与筛选的软件和硬件设备&#xff0c;帮助计算机网络于其内、外网之间构建一道相对隔绝的保护屏障&#xff0c;以保护用户资料与信息安全性的一种技术。 通俗的来讲&#…

C高级 作业 day4 8/7

1.整理思维导图 2.写一个函数&#xff0c;获取用户的uid和gid并使用变量接收 3.整理冒泡排序、选择排序和快速排序的代码 冒泡排序 //冒泡排序 void bubble_sort(int a[],int len) {int count0;for(int i1;i<len;i) //控制轮数{for(int j0;j<len-i;j) //控制每一轮交换…

深度学习和OpenCV的对象检测(MobileNet SSD视频流实时识别)

上期文章,我们分享了如何使用opencv 与MobileNet SSD模型来检测给定的图片,有网友反馈能否提供一下视频流的实时检测代码,其实我们在分享人脸识别的时候,分享了如何使用cv2.videoCpature 类来从视频中实时提取视频中的图片,进行人脸的识别,视频流的对象检测跟opencv的人脸…

【雕爷学编程】MicroPython动手做(33)——物联网之天气预报2

天气&#xff08;自然现象&#xff09; 是指某一个地区距离地表较近的大气层在短时间内的具体状态。而天气现象则是指发生在大气中的各种自然现象&#xff0c;即某瞬时内大气中各种气象要素&#xff08;如气温、气压、湿度、风、云、雾、雨、闪、雪、霜、雷、雹、霾等&#xff…

【分布式应用】ELK企业级日志分析系统

目录 一、ELK 简介 1.1 ELK各组件介绍 ElasticSearch&#xff1a; Kiabana&#xff1a; Logstash&#xff1a; 1.2 可以添加的其它组件&#xff1a; Filebeat&#xff1a; 缓存/消息队列&#xff08;redis、kafka、RabbitMQ等&#xff09;&#xff1a; Fluentd&#xf…