从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值
目录
1. 列表初始化initializer_list
2. 前面提到的一些知识点
2.1 小语法
2.2 STL中的一些变化
3. 右值和右值引用
3.1 右值和右值引用概念
3.2 右值引用类型的左值属性
3.3 左值引用与右值引用比较
3.4 右值引用的使用场景
3.4.1 左值引用的功能和短板
3.4.2 移动构造
3.4.3 移动赋值
3.4.4 插入右值时减少深拷贝
4. 完美转发
4.1 万能引用(引用折叠)
4.2 完美转发
5. 新的类功能
5.1 默认生成的移动构造/赋值
5.2 类里新的关键字
本篇完。
在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增加的语法特性非常篇幅非常多,我们这里没办法一 一讲解,所以本主要讲解实际中比较实用的语法。
1. 列表初始化initializer_list
- 列表:花括号:{ }就被叫做列表。
我们之前可以使用列表来初始化数组,初始化结构体变量,初始化元素类型为结构体变量的数组等等。
- C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。
#include <iostream>
using namespace std;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;}protected:int _year = 1;int _month = 1;int _day = 1;
};int main()
{int x1 = 1;int x2 = { 2 }; // 要能看懂,但是不建议使用int x3{ 2 };Date d1(2023, 1, 1); // 都是在调用构造函数Date d2 = { 2023, 2, 2 }; // 要能看懂,但是不建议使用Date d3{ 2023, 3, 3 };return 0;
}
可以不加等号进行初始化,如上图代码所示,但是强烈不建议使用。
这其实很鸡肋,没有什么价值,继续使用C++98中的方式就挺好的,而且容易理解,C++11中的方式反而不太好理解了。C++中这种鸡肋的语法被很多人吐槽,理性看待。
列表初始化真正有意义的地方是用于初始化STL中的容器:
之前提到:vector和list以及map等STL中的容器也可以像普通数组一样使用初始化列表来初始化了。这是因为列表初始化本身就是一个类模板:
如上图所示,这是C++11才有的一个类型,该类型叫做列表初始化,而且还有自己的成员函数,包括构造函数,计算列表大小的接口,获取列表迭代器位置。(但几乎都不用)
C++11为这些容器提供了新的构造函数,该构造函数是使用列表来初始化对象的,它的形参就是initializer_list,所以列表初始化才可以初始化STL中的容器。
赋值运算符重载函数也有一个列表的重载版本:
#include <iostream>
#include <vector>
#include <list>
#include <map>
using namespace std;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;}protected:int _year = 1;int _month = 1;int _day = 1;
};int main()
{int x1 = 1;int x2 = { 2 }; // 要能看懂,但是不建议使用int x3{ 2 };Date d1(2023, 1, 1); // 都是在调用构造函数Date d2 = { 2023, 2, 2 }; // 要能看懂,但是不建议使用Date d3{ 2023, 3, 3 };// 调用支持list (initializer_list<value_type> il)类似这样的构造函数vector<int> v1 = { 1, 2, 3, 4, 5, 6 };vector<int> v2 { 1, 2, 3, 4, 5, 6 };list<int> lt1 = { 1, 2, 3, 4, 5, 6 };list<int> lt2{ 1, 2, 3, 4, 5, 6 };auto x = { 1, 2, 3, 4, 5, 6 };cout << typeid(x).name() << endl; // 打印初始化列表的类型vector<Date> v3 = {d1, d2, d3};vector<Date> v4 = { { 2022, 1, 1 }, {2022, 11, 11} };string s1 = "11111";map<string, string> dict = { { "sort", "排序" }, { "insert", "插入" } }; // 构造initializer_list<pair<const string, string>> kvil = { { "left", "左边" }, { "right", "右边" } }; // 赋值重载dict = kvil; // 上面的类型就不能用auto推导,编译器不知道那里是一个pairreturn 0;
}
2. 前面提到的一些知识点
2.1 小语法
C++11提供了一些新的小语法,很多我们都接触过甚至是使用过。
c++11提供了多种简化声明的方式,尤其是在使用模板时。这里讲auto和decltype
auto:这个关键字我们已经使用过很多了
在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃auto原来的用法,将其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。
#define _CRT_SECURE_NO_WARNINGS 1#include <iostream>
#include <map>
using namespace std;int main()
{int i = 10;auto p = &i;auto pf = strcpy;cout << typeid(p).name() << endl;cout << typeid(pf).name() << endl;map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };//map<string, string>::iterator it = dict.begin();auto it = dict.begin();return 0;
}
decltype:
关键字decltype将变量的类型声明为表达式指定的类型。
使用typeid().name()只能打印出类型的名称,并不能用这个名称继续创建变量,而decltype可以:
template<class T1, class T2>
void F(T1 t1, T2 t2)
{decltype(t1 * t2) ret;cout << typeid(ret).name() << endl;
}int main()
{const int x = 1;double y = 2.2;decltype(x * y) ret; // ret的类型是doubledecltype(&x) p; // p的类型是int*cout << typeid(ret).name() << endl;cout << typeid(p).name() << endl;F(1, 'a');return 0;
}
使用decltype可以自动推演类型,并且可以用推演出的结果继续创建变量,如上图所示,对于一些不同类型直接的运算结果,decltype有奇效。
nullptr:
由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。
在C++中存在条件编译:(以后用nullptr就行了)这算是修复了一个bug
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void*)0)
#endif
#endif
范围for循环
范围for我们也一直都在使用,这是C++11提供的语法糖,使用起来非常方便,它的底层就是迭代器,只是编译器给自动替换了,这里就不再讲解了。
2.2 STL中的一些变化
新容器:
红色框中的是C++11增加的新容器,基本只有unordered_map和unordered_set有用,其他很鸡肋。容器array对标的是静态数组,array也是一个静态的,也就是在栈区上的,大小是通过一个非类型模板参数确定的。容器forward_list是一个单链表,也很鸡肋,因为绝大部分场景双链表都可以满足要求,而且更加方便,唯一使用到单链表的地方就是哈希桶中。前面都提到过。
至于unordered_map和unordered_set,这两个容器的底层是哈希桶,虽然不能实现排序,但是可以降重。而且在查找时具有其他容器无法比拟的效率。这两个容器是非常实用的,而且也是我们经常使用的。
容器中的使用新方法:
1. 使用列表构造
在前面就讲解过了,几乎每个容器都增加了新的接口,使用std::initializer_list类型来构造。2. 移动构造和移动赋值
在下面讲解了右值引用就可以明白了。3. emplace_xxx插入接口或者右值引用版本的插入接口。
同样在后面才能学习到。
3. 右值和右值引用
3.1 右值和右值引用概念
什么是左值?什么是右值?
- 左值:一个表示数据的表达式,如变量名或者指针解引用。
- 特点:可以对左值取地址 + 可以对左值赋值。
上图代码中所示的变量都属于左值,要牢记左值可以取地址这一个特性。
- 定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。
- 左值可以出现在赋值符号的左边,也可以出现在赋值符号的右边。
- 右值:也是一个表示数据的表达式。如:字面常量,表达式返回值,函数返回值,类型转换时的临时变量等等。
- 特点:右值不可以取地址,不可以赋值。
要牢记右值特性:不能取地址不能赋值。
- 右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边。
什么是右值引用?
左值引用是给左值取别名,右值引用显而易见就是给右值取别名。
- 右值引用使用两个&符号。
上图代码中的rr1,rr2,rr3就是三个右值的别名,也就是右值引用。
3.2 右值引用类型的左值属性
- 右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址。
对于内置类型的右值,如字面常量,一旦右值引用以后,就会被存储到特定的位置,
并且可以取到该地址,而且还可以修改。
int main()
{int&& rr1 = 10;cout << rr1 << endl;rr1 = 5;cout << rr1 << endl;const double&& rr2 = (1.1 + 2.2);//rr2 = 5.5; // 不能修改return 0;
}
字面常量10原本是不可以被修改的,但是右值引用以后,在特定的位置开辟了变量来存放10,所以就可以被修改了。
表达式或者函数的返回值,会有一个临时变量来存放返回值,我们知道这样的临时变量具有常性,也是右值。对于这种右值引用,编译器会修改它的属性,将常性修改,并且存储在特定位置。
注意const类型的右值,即便开辟了变量存放该右值也是不可以被修改的,因为被const修饰了。
内置类型的右值被称为纯右值。
自定义类型的右值被称为将亡值。
对于自定义类型的右值,如容器的临时变量,它确确实实会被销毁,而不会被存放。
自定义类型的右值才能体现出右值存在的意义,后面会详细讲解。
- 右值引用是右值的别名,它所指向的右值是不可以被修改的。
- 但是右值引用本身也是一种类型,并且它的属性是左值,可以取地址,可以赋值。
3.3 左值引用与右值引用比较
思考:左值引用可以引用右值吗?
我们要知道,右值引用是C++11才出来的,右值传参给函数还是右值,那我们以前写的函数都用不了右值传参了?
template<class T>
void Func(const T& x)
{}
这里去掉const肯定是不能传参的,为了给右值传参(当然还有其它原因),所以const的左值引用可以引用右值。总结:普通的左值引用不可以引用右值,const的左值引用可以引用右值:
思考:右值引用可以引用左值吗?
右值引用不可以引用普通的左值,可以引用move以后的左值:(move这个语法先记住)
左值经过move以后就变成了右值,如:
int main()
{// 左值引用可以引用右值吗? const的左值引用可以double x = 1.1, y = 2.2;//double& r1 = x + y;const double& r1 = x + y;// 右值引用可以引用左值吗?可以引用move以后的左值int b = 7;//int&& rr5 = b;int&& rr5 = move(b);return 0;
}
成功编译:
3.4 右值引用的使用场景
namespace rtx
{class string{public:string(const char* str = ""):_size(strlen(str)), _capacity(_size){_str = new char[_capacity + 1];strcpy(_str, str);}void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}string(const string& s) // 拷贝构造:_str(nullptr){cout << "string(const string& s) -- 拷贝构造(深拷贝)" << endl;//string tmp(s._str);//swap(s);_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}string& operator=(const string& s) // 拷贝赋值{cout << "string& operator=(string s) -- 拷贝赋值(深拷贝)" << endl;string tmp(s);swap(tmp);return *this;}protected:char* _str;size_t _size;size_t _capacity;};
}
先自己实现一个string,只有拷贝构造函数,赋值运算符重载函数,析构函数,以及一个普通的构造函数。无论是拷贝构造还是赋值运算符重载,都会进行深拷贝,采用现代写法来实现:
namespace rtx
{class string{public:string(const char* str = ""):_size(strlen(str)), _capacity(_size){_str = new char[_capacity + 1];strcpy(_str, str);}const char* c_str() const{return _str;}void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}string(const string& s) // 拷贝构造:_str(nullptr), _size(0), _capacity(0){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(){delete[] _str;_str = nullptr;}protected:char* _str;size_t _size;size_t _capacity;};
}
左值引用的场景:
使用普通传值调用,存在一次深拷贝:
void Func(rtx::string s)
{}int main()
{rtx::string s("hello world");Func(s);return 0;
}
使用传拷贝引用时,不存在深拷贝,Func函数直接使用main函数中的s1对象:
void Func(rtx::string& s)
{}int main()
{rtx::string s("hello world");Func(s);return 0;
}
函数返回参数和上面一样,传引用返回有时确实能提高效率,
3.4.1 左值引用的功能和短板
左值引用的功能:
1、做参数。a、减少拷贝,提高效率。b、做输出型参数。
2、做返回值。a、减少拷贝,提高效率。b、引用返回,可以修改返回对象(比如: operator[ ])。
但是左值引用做返回值只解决了70%的问题,在类似 to_string 函数中:
- 传值返回时,存在一次深拷贝。
- rtx::string to_string(int value)
要知道深拷贝的代价是比较大的,深拷贝次数减少可以很大程度上提高代码的效率。
- 传左值引用返回时,不存在深拷贝。(可以吗?)
- rtx::string& to_string(int value)
但是你敢传引用返回吗?我们把int value 转换成string,此时的 string 是一个形参。出了函数就销毁了。外面拿到的就是被销毁了的栈帧。
所以左值引用存在的短板:
前面我们在调用 to_string 函数的时候,我们把int value 转换成string,此时的 string 是一个形参。
所以只能传值返回,此时mian函数中拿到 to_string 中的 string 对象要进行两次深拷贝。
第一次深拷贝,to_string函数返回时,会将string对象放在一个临时变量中,此时发生的深拷贝。函数返回时,如果是内置类型等几个字节的变量,会将函数中的临时变量放在寄存器中返回,如果是自定义类型所占空间比较大,就会放在临时变量中压栈到上一级栈帧中。
第二次深拷贝,main函数中,ret接收函数返回了的string对象时会再发生一次深拷贝。
但是编译器会进行优化,将两次深拷贝优化成一次。虽然只有一次,但有些情况代价还是很大的。
C++98是如何解决上面的问题?
那就是输出型参数:rtx::string to_string(int value)变成rtx::void to_string(int value,string& s)
但是这样不太符合使用习惯。
- 有没有办法让它符合使用习惯,并且一次深拷贝都没有?那就要用到下面的C++11新增的移动构造和移动赋值了
3.4.2 移动构造
此时用右值引用就可以解决这个问题。
右值引用的价值之一:补齐临时对象不能传引用返回这个短板。
前面的深拷贝是拷贝构造产生的:string(const string& s) // 拷贝构造(形参是左值引用)
演示在string类中增加一个移动构造函数:
前面提到过:内置类型的右值被称为纯右值。
自定义类型的右值被称为将亡值。(这里的传右值就是将亡值)
基于拷贝构造:无论是左值还是右值都老老实实地开空间:
string(const string& s) // 拷贝构造:_str(nullptr), _size(0), _capacity(0){cout << "string(const string& s) -- 拷贝构造(深拷贝)" << endl;string tmp(s._str);swap(tmp);}
左值因为还要使用,肯定要开空间的,这里的右值是将亡值,没用了,所以也不用开空间了,
因为不用开空间了,所以深拷贝也没了,而是资源转移(直接swap):
string(string&& s) // 移动构造:_str(nullptr), _size(0), _capacity(0){cout << "string(string&& s) -- 移动构造(资源转移)" << endl;swap(s);}
- 移动构造的形参是右值引用。
从to_string中返回的string对象是一个临时变量,具有常性,也就是我们所说的右值。
- 用右值来构造string对象时,会自定匹配移动构造函数。(以前没有移动构造时,右值传参会走拷贝构造,因为const 的左值引用可以接收右值,但是这不是最优方案,现在写了移动构造,右值传参就会走移动构造)
3.4.3 移动赋值
拷贝赋值移动赋值和拷贝构造移动构造类似:
string& operator=(const string& s) // 拷贝赋值{cout << "string& operator=(string s) -- 拷贝赋值(深拷贝)" << endl;string tmp(s);swap(tmp);return *this;}string& operator=(string&& s) // 移动赋值{cout << "string& operator=(string s) -- 移动赋值(资源移动)" << endl;swap(s);return *this;}
总结:右值引用和左值引用减少拷贝的原理不太一样。
- 左值引用是别名,直接在原本的对象上起作用。
- 右值引用是间接起作用,通过右值引用识别到右值,然后在移动构造和移动赋值中进行资源转移。
使用移动构造和移动赋值时,被转移资源的对象必须是个将亡值(像to_string的使用一样),因为会被销毁。
C++11的STL标准库中也提供了移动构造和移动赋值函数。
3.4.4 插入右值时减少深拷贝
C++11在STL库容器中的所有插入接口都提供了右值版本,push_back,insert等。
在我们写的string恢复这两个接口:
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';}
然后分别像库里的 list 插入左值和右值
int main()
{list<rtx::string> lt;rtx::string s1("hello"); // 左值lt.push_back(s1); // 插入左值cout << "----------------------------------" << endl;lt.push_back(rtx::string("world")); // 插入右值//lt.push_back("world");return 0;
}
如果没有移动构造那么下面的也是深拷贝了。
4. 完美转发
4.1 万能引用(引用折叠)
写多个重载函数,根据实参类型调用不同函数。
- 形参类型分别是左值引用,const左值引用,右值引用,const右值引用:
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; }// 万能引用(引用折叠):t既能引用左值,也能引用右值
template<typename T>
void PerfectForward(T&& t)
{Fun(t); // 此时t变成了左值/const左值
}int main()
{PerfectForward(10); // 右值int a;PerfectForward(a); // 左值PerfectForward(std::move(a)); // 右值const int b = 8;PerfectForward(b); // const 左值PerfectForward(std::move(b)); // const 右值return 0;
}
代码中的perfectForward函数模板被叫做万能引用模板,
无论调用该函数时传的是什么类型,它都能推演出来:
在函数模板推演的过程中会发生引用折叠:模板参数T&&中的两个&符号折叠成一个。
当传入的实参是左值时,就会发生引用折叠,是右值时就不会发生引用折叠。
- 无论传的实参是什么,都不用改变模板参数T&&,编译器都能够自己推演。
- 这就是万能引用,只需要一个模板就可以搞定,不需要分类去写。
上面万能模板中,虽然推演出来了各自实参类型,但是由于右值引用本身是左值属性,所以需要使用move改变属性后才能调用对应的重载函数。
有没有办法不用move改变左值属性,让模板函数中的t保持它推演出来的类型。答案是有的,完美转发就能够保持形参的属性不变。
4.2 完美转发
完美转发同样是C++11提供的,它也是一个模板:
完美转发:完美转发在传参的过程中保留对象原生类型属性。
实参传递过来后,推演出的形参是什么类型就保持什么类型继续使用。
这里会语法就行:
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; }// 万能引用(引用折叠):t既能引用左值,也能引用右值
template<typename T>
void PerfectForward(T&& t)
{Fun(std::forward<T>(t)); // 完美转发:保持t引用对象属性
}int main()
{PerfectForward(10); // 右值int a;PerfectForward(a); // 左值PerfectForward(std::move(a)); // 右值const int b = 8;PerfectForward(b); // const 左值PerfectForward(std::move(b)); // const 右值return 0;
}
此时再使用万能引用的时候,在函数模板中调用重载函数时只需要使用完美转发就可以保持推演出来的属性不变,右值引用仍然是右值,const右值引用也仍然是右值。
需要注意的是:
虽然右值不可以被修改,但是右值引用以后具有了左值属性,才能被转移,一旦被const修饰以后就无法转移了。所以我们在使用右值引用的时候,不要使用const来修饰。
5. 新的类功能
在原来的C++类中,有6大默认成员函数:
1. 构造函数 2. 析构函数 3. 拷贝构造函数 4. 拷贝赋值重载 5. 取地址重载 6. const 取地址重载
最后重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的,而且完全符号我们使用的需求。
5.1 默认生成的移动构造/赋值
C++11中新增了两个:移动构造和移动赋值运算符重载,此时C++11一共有8个默认成员函数了。
这两个成员函数在前面已经介绍过了,这里站在默认成员函数的角度继续谈谈。
满足下列条件,编译器会自定生成移动构造函数:
- 没有自己显示定义移动构造函数
- 且没有实现析构函数,拷贝构造函数,拷贝赋值重载中的任何一个。
此时编译器会自定生成一个默认的移动构造函数。
- 默认生成的移动构造函数,对于内置类型会逐字节进行拷贝。
- 对于自定义类型,如果实现了移动构造就调用移动构造,没有实现就调用拷贝构造。
满足下列条件,编译器会自动生成移动赋值重载函数
- 自己没有显示定义移动赋值重载函数。
- 且且没有实现析构函数,拷贝构造函数,拷贝赋值重载中的任何一个。
此时编译器会自动生成一个默认移动赋值函数。
- 对于内置类型会按字节拷贝。
- 对于自定义类型,如果实现了移动赋值就调用移动赋值,如果没有实现就调用拷贝赋值。
创建一个类,屏蔽掉拷贝构造,拷贝赋值,以及析构函数,成员变量有一个是我们自己实现的string,里面有移动构造和移动赋值。
namespace rtx
{class string{public:string(const char* str = ""):_size(strlen(str)), _capacity(_size){_str = new char[_capacity + 1];strcpy(_str, str);}void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}string(const string& s) // 拷贝构造:_str(nullptr), _size(0), _capacity(0){cout << "string(const string& s) -- 拷贝构造(深拷贝)" << endl;string tmp(s._str);swap(tmp);}string(string&& s) // 移动构造:_str(nullptr), _size(0), _capacity(0){cout << "string(string&& s) -- 移动构造(资源转移)" << endl;swap(s);}string& operator=(const string& s) // 拷贝赋值{cout << "string& operator=(string s) -- 拷贝赋值(深拷贝)" << endl;string tmp(s);swap(tmp);return *this;}string& operator=(string&& s) // 移动赋值{cout << "string& operator=(string s) -- 移动赋值(资源移动)" << endl;swap(s);return *this;}~string(){delete[] _str;_str = nullptr;}protected:char* _str;size_t _size;size_t _capacity;};
}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& operator=(const Person& p) // 拷贝赋值//{// if (this != &p)// {// _name = p._name;// _age = p._age;// }// return *this;//}//~Person()//{}protected:rtx::string _name;int _age;
};int main()
{Person s1;Person s2 = s1;Person s3 = std::move(s1);Person s4;s4 = std::move(s2);return 0;
}
此时Person就自动生成了移动构造函数,并且调用了string中的移动构造和移动赋值函数来构造string对象。
将Person中的拷贝构造,拷贝赋值,析构函数任意放出一个来。(这里只放出了析构)
使用右值构建string对象时,都会调用string的拷贝构造和拷贝赋值函数。
- 编译器默认生成的移动赋值和移动构造非常类型。
- 如果符合条件就生成,内置内心按字节处理,自定义类型调用自定义类型的移动赋值或者移动构造,如果没有的化就调用它们的拷贝赋值或者拷贝构造。
- 如果不符合条件,就直接调用自定义类型的拷贝复制或者拷贝构造。
5.2 类里新的关键字
强制生成默认函数的关键字default:
这个default并不是switch中的default,而是C++11的新用法。
- 假设类中的某个默认成员函数没有自动生成,但是我们需要它,就可以用default,强制让编译器自动生成默认函数。
5.1里的代码:将Person中的拷贝构造,拷贝复制,析构函数都显示定义,此时就破坏了自动生成移动构造的条件。把Person里的注释放开,使用default强制生成默认的移动构造函数
从结果中可以看到,仍然调用了string中的移动构造函数,而不是调用的拷贝构造(深拷贝)。
- 说明Person中仍然生成了默认的移动构造函数。
禁止生成默认成员函数的关键字delete:
如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁 已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即 可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
C++98不生成默认成员函数的方法:直接一个分号(要放到保护或者私有里,这里就不放了)
在Person类中不显示定义拷贝构造函数,拷贝复制函数,析构函数,此时符合自动生成默认移动构造的条件。 声明移动构造函数,但是没有定义(要放到保护或者私有里,防止类外实现,这里就不放了)。此时在编译的时候就会报错,这是C++98中的方式,利用链接时找不到函数的定义报错。C++11就新增delete关键字使其在编译阶段就报错:
- C++11中,使用delete同样可以实现不让自动生成默认成员函数。
同样在编译时报错了。编译器会自动生成移动构造函数,但是此时使用了delete,编译器就会报错,告诉我们这里生成了移动构造。
这是为了在编译阶段就报错,而不是运行时再报错。
以前提到的一道题:
// 要求delete关键字实现,一个类,只能在堆上创建对象
class HeapOnly
{
public:HeapOnly(){_str = new char[10];}~HeapOnly() = delete;//void Destroy() // 如果要销毁只能这样//{// delete[] _str;// operator delete(this);//}private:char* _str;//...
};
继承和多态中的final与override关键字
这两个关键字在继承和多态部分详细讲解过,这里不再详细讲解。
final
- 在继承中,被final修饰的类叫做最终类,是无法继承的。
- 在多态中,被final修饰的虚函数是无法进行重写的。
override
- 在多态中,用来检查虚函数是否完成了重写。
本篇完。
C++11中的很多东西虽然让C++越来越不像C++,比如列表初始化等内容,但是还是有一些非常有用的东西的:比如今天讲到的右值引用,和下一篇学的lambda表达式。
下一篇:从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题。
相关文章:
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值
目录 1. 列表初始化initializer_list 2. 前面提到的一些知识点 2.1 小语法 2.2 STL中的一些变化 3. 右值和右值引用 3.1 右值和右值引用概念 3.2 右值引用类型的左值属性 3.3 左值引用与右值引用比较 3.4 右值引用的使用场景 3.4.1 左值引用的功能和短板 3.4.2 移动…...
如何在Linux系统中处理PDF文件?
如何在Linux系统中处理PDF文件? 1.查看PDF文档2.合并PDF文档3.压缩PDF文档4.提取PDF文本 PDF文件是一种特殊的文件格式,它可以在不同的操作系统中实现跨平台的文件传输和共享。Linux系统作为一种自由开放的操作系统,拥有丰富的PDF文件处理工具…...
SpringBoot实现热部署/加载
在我们修改完项目代码后希望不用重启服务器就能把项目代码部署到服务器中(也就是说修改完项目代码后不用重启服务器修改后的项目代码就能生效)。 一、实现devtools原理 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-…...
我是如何使用Spring Retry减少1000 行代码
使用 Spring Retry 重构代码的综合指南。 问题介绍 在我的日常工作中,我主要负责开发一个庞大的金融应用程序。当客户发送请求时,我们使用他们的用户 ID 从第三方服务获取他们的帐户信息,保存交易并更新缓存中的详细信息。尽管整个流程看起来…...
ARM开发(stm32 cortex-A7核IIC实验)
1.实验目标:采集温湿度传感器值; 2.分析框图(模拟IIC控制器); 3.代码; ---iic.h封装时序协议头文件--- #ifndef __IIC_H__ #define __IIC_H__ #include "stm32mp1xx_gpio.h" #include "st…...
「Java」《Java集合框架详解:掌握常用集合类,提升开发效率》
Java集合框架详解:掌握常用集合类,提升开发效率 摘要:一. 引言二. 集合框架概述三. 集合接口详解四. 集合类的选择五. 泛型和类型安全六. 集合的线程安全七. 高级集合类和算法八、Java集合实践操作示例1. 创建和初始化集合:2. 遍历…...
游戏出海需知:Admob游戏广告变现策略
越来越多的出海游戏公司更加重视应用内的广告变现,而 AdMob因为其提供的丰富的广告资源,稳定平台支持,被广泛接入采用。 Admob推出的广告变现策略包括bidding、插页式激励视频、开屏广告、各种细分功能的报告等等。 一、Bidding 竞价策略 …...
【linux】NFS调试总结
文章目录 00. ENV10. 简述20. 下载、安装、配置30. 使用1. 从uboot中设置NFS启动文件系统2. 调试 80. 问题1. NFS版本不匹配问题 90. 附件91. 服务端NFS配置项简述 00. ENV ubuntn1804 10. 简述 百度百科:https://baike.baidu.com/item/%E7%BD%91%E7%BB%9C%E6%96%87…...
wireshark进行网络监听
一、实验目的: 1)掌握使用CCProxy配置代理服务器; 2)掌握使用wireshark抓取数据包; 3)能够对数据包进行简单的分析。 二、预备知识: 包括监听模式、代理服务器、中间人攻击等知识点…...
时间复杂度
一、时间复杂度 时间复杂度是计算机科学中用来衡量算法运行时间随输入规模增加而增长的速度。简单来说,它是一个衡量算法执行效率的指标,表示算法运行所需时间与输入数据量之间的关系。 时间复杂度通常用大O符号(O)来表示&#…...
Unity实现广告滚动播放、循环播放、鼠标切换的效果
效果: 场景结构: 特殊物体:panel下面用排列组件horizent layout group放置多个需要显示的面板,用mask遮罩好。 using System.Collections; using System.Collections.Generic; using DG.Tweening; using UnityEngine; using Unity…...
LangChain + Streamlit + Llama:将对话式AI引入本地机器
推荐:使用 NSDT场景编辑器 助你快速搭建可二次编辑的3D应用场景 什么是LLMS? 大型语言模型 (LLM) 是指能够生成与人类语言非常相似的文本并以自然方式理解提示的机器学习模型。这些模型使用包括书籍、文章、网站和其他来源在内的…...
Python 读写 Excel 文件库推荐和使用教程
文章目录 前言Python 读写 Excel 库简介openpyxl 处理 Excel 文件教程pandas 处理 Excel 文件教程总结 前言 Python 读写 Excel 文件的库总体看还是很多的, 各有其优缺点, 以下用一图总结各库的优缺点, 同时对整体友好的库重点介绍其使用教程…...
“深入解析JVM:理解Java虚拟机的工作原理和优化技巧“
标题:深入解析JVM:理解Java虚拟机的工作原理和优化技巧 摘要:本文将深入探讨Java虚拟机(JVM)的工作原理和优化技巧。我们将从JVM的基本结构开始,逐步介绍其工作原理,并提供一些实际示例代码&am…...
解决SEGGER Embedded Studio无法显示Nordic MCU外设寄存器问题
如果使用SES调试NRF52840的时候发现,官方例程只能显示CPU寄存器,但是无法显示外设寄存器时,解决办法如下: 1.在解决方案右键→Options→Debug→Debugger,然后Target Device选择正确的型号。 2.Register Definition Fil…...
Oracle-day1:scott用户、查询、取整、截取、模糊查询、别名——23/8/23
整理一下第一天软件测试培训的知识点 1、scott用户 -- 以system管理员登录锁定scott用户 alter user scott account lock;-- 以system管理员登录解锁scott用户 alter user scott account unlock;-- 以system管理员用户设置scott用户密码 alter user scott identfied by tiger…...
stm32之3.key开关
假设key电阻为40kΩ,则key0 的电压3.3v*4/52.64v 2.key开关代码 ② GPIO_OType_PP//推挽输出 GPIO_OType_PP//开漏输出 推挽输出是指输出端口可以同时提供高电平和低电平输出,而开漏输出则是指输出端口只能提供低电平输出,高电平时需要借…...
GPT带我学-设计模式-代理模式
什么是代理模式 代理模式(Proxy Pattern)是设计模式中的一种结构型模式,它为其他对象提供一种代理以控制对这个对象的访问。 代理模式有三个主要角色:抽象主题(Subject)、真实主题(Real Subje…...
VMware Workstation Pro 无法使用开机状态下拍的快照来克隆虚拟机,怎么解决?
环境: VMware Workstation Pro16.0 Win10 专业版 问题描述: VMware Workstation Pro有台虚拟机在开机状态下拍了个6.7快照这个win10初始版,现在想在这个快照下直接克隆,无法使用开机状态下拍的快照创建克隆 解决方案: 1.关闭当前虚拟机 2.到虚拟机文件夹复制一份Wind…...
【JAVA】XML及其解析技术、XML检索技术、设计模式
XML XML(Extensible Markup Language)是可扩展标记语言的缩写,它是一种数据表示格式,可以描述复杂的数据结构,常用于传输和存储数据 作用: 用于进行存储数据和传输数据作为软件的配置文件 第一行是文档声明 <?xml version&q…...
Ansible 自动化安装软件
例子如下: 创建一个名为/ansible/package.yml 的 playbook : 将 php 和 mariadb 软件包安装到 dev、test 和 prod 主机组中的主机上 将 RPM Development Tools 软件包组安装到 dev 主机组中的主机上 将 dev 主机组中主机上的所有软件包更新为最新版本 --- - name:…...
简单介绍 React Native 整合 Formik 实现表单校验
Formik 是 React 和 React Native 开源表单库,Formik 负责处理重复且烦人的事情——跟踪值/错误/访问的字段、编排验证和处理提交——所以您不必这样做。而简化字段校验的话我们可以使用yup工具来实现。 首先安装Formik 和 Yup npm i formik npm i yupFormik 与 R…...
蓝帽杯半决赛2022
手机取证_1 iPhone手机的iBoot固件版本号:(答案参考格式:iBoot-1.1.1) 直接通过盘古石取证 打开 取证大师和火眼不知道为什么都无法提取这个 手机取证_2 该手机制作完备份UTC8的时间(非提取时间):(答案…...
电路学习+硬件每日学习十个知识点(40)23.8.20 (希腊字母读音,阶跃信号和冲激信号的关系式,信号的波形变换,信号的基本运算,卷积积分,卷积和)
文章目录 1.信号具有时间特性和频率特性。2.模拟转数字,抽样、量化、编码3.阶跃信号和冲激信号4.信号的波形变换(时移、折叠、尺度变换)5.信号的基本运算(加减、相乘、微分与积分、差分与累加)5.1 相加减5.2 相乘5.3 微…...
Python——列表(list)推导式
本文基于python3。 目录 1、Python推导式2、列表(list)推导式2.1、定义2.2、实际操作2.2.1、一个表达式,后面为一个 for 子句2.2.2、一个表达式,后面为一个 for 子句,然后,跟着if 子句。2.2.3、一个表达式,后面为一个…...
代码随想录算法训练营day43 | LeetCode 1049. 最后一块石头的重量 II 494. 目标和 474. 一和零
1049. 最后一块石头的重量 II(题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台) 思路:把全部石头重量加起来,然后除以二,就等于背包的最大容量。然后就可以按照背包问题…...
Linux安装jdk、mysql、并部署Springboot项目
😜作 者:是江迪呀✒️本文关键词:Linux、环境安装、JDK安装、MySQL、MySQL安装☀️每日 一言:知行合一! 文章目录 一、前言二、安装步骤2.1 安装JDK(1)创建文件夹(便于后…...
tomcat更改端口号和隐藏端口号
因为默认端口:8080不会自动隐藏,因此为了更显格调需要将其改为:80 进入tomcat的server文件 将其改为80,之后将tomcat重新启动即可 tomcat启动流程 [rootshang ~]# cd /usr/local/tomcat/apache-tomcat-8.5.92 [rootshang apache-tomcat-8.5.92]# cd b…...
生信分析Python实战练习 2 | 视频19
开源生信 Python教程 生信专用简明 Python 文字和视频教程 源码在:https://github.com/Tong-Chen/Bioinfo_course_python 目录 背景介绍 编程开篇为什么学习Python如何安装Python如何运行Python命令和脚本使用什么编辑器写Python脚本Python程序事例Python基本语法 数…...
wps设置其中几页为横版
问题:写文档的时候,有些表格列数太多,页面纵向显示内容不完整,可以给它改成横向显示。 将鼠标放在表格上一页的底部,点击‘插入-分页-下一页分节符’。 将鼠标放在表格页面的底部,点击‘插入-分页-下一页分…...
上市公司协会网站建设汇报/无锡百度公司王东
题目链接 https://begin.lydsy.com/JudgeOnline/problem.php?id3850 题解 由于相邻颜色不能相同,因此可以将原矩阵转化成一个序列,其中第iii个元素是第iii列三种颜色中没有出现的那种,可以证明三种颜色只会不出现一种。 由于一个222\times 2…...
wordpress网站无法登陆/wordpress自助建站
本文是对《【硬刚大数据之学习路线篇】从零到大数据专家的学习指南(全面升级版)》的JVM部分补充。 1. String的基本特性 1.String:字符串,使用一对""引起来表示。 2.String声明为final的,不可被继承。 3.String实现了Serializable接口:表示字符串是支持序列化的…...
钓鱼网站怎么做防护教程/链接点击量软件
AES加密: GCM和CBC模式的区别Difference between GCM and CBC导语 项目代码的构建扫描结果中,Sonarsource Rule 中建议用 AES - GCM 来替代 AES-CBC,这两者究竟有什么区别呢?可以按照建议替换掉么?趁着这个机会,学习了…...
辛集网站建设哪家好/沈阳seo关键字优化
2005: [Noi2010]能量采集 Time Limit: 10 Sec Memory Limit: 552 MBSubmit: 3312 Solved: 1971[Submit][Status][Discuss]Description 栋栋有一块长方形的地,他在地上种了一种能量植物,这种植物可以采集太阳光的能量。在这些植物采集能量后,…...
用flask做的网站有哪些/海淀区seo全面优化
来源:blog.csdn.net/qq_33840251/article/details/90482170单例bean与原型bean的区别1.画图分析2.源码分析单例bean的优势1.减少了新生成实例的消耗2.减少jvm垃圾回收3.可以快速获取到bean单例bean的劣势总结Spring 为啥把bean默认设计成单例?单例有啥劣…...
网站没有索引量是什么/网站首页排名
第一步,安装cnpm,通过淘宝的镜像: npm i -g cnpm --registryhttps://registry.npm.taobao.org第二步:设置npm的config npm config set registry https://registry.npm.taobao.org第三步:安装vue的脚手架 npm install -g vue/cl…...