【C++】C++11中的常见语法(上)

C++11

  • 一、C++11简介
  • 二、统一的列表初始化
    • 1.{}初始化
    • 2. std::initializer_list
  • 三、声明
    • 1. auto
    • 2. decltype
    • 3. nullptr
  • 四、右值引用和移动语义
    • 1. 左值引用和右值引用
    • 2. 左值引用与右值引用比较
    • 3. 右值引用使用场景和意义
    • 4. 右值引用引用左值及其一些更深入的使用场景分析
    • 5. 完美转发
  • 五、新的类功能
    • 1. 默认成员函数
    • 2. 类成员变量初始化
    • 3. 强制生成默认函数的关键字 default
    • 4. 禁止生成默认函数的关键字 delete
    • 5. 继承和多态中的 final 与 override 关键字

一、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++11的来源:1998年是 C++ 标准委员会成立的第一年,本来计划以后每 5 年视实际需要更新一次标准,C++ 国际标准委员会在研究 C++03 的下一个版本的时候,一开始计划是 2007 年发布,所以最初这个标准叫 C++07。但是到06年的时候,官方觉得2007年肯定完不成 C++07,而且官方觉得 2008 年可能也完不成。最后干脆叫C++ 0x。x 的意思是不知道到底能在07还是08还是09年完成。结果 2010 年的时候也没完成,最后在2011年终于完成了 C++ 标准。所以最终定名为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 = { 0, 1 };
			
				return 0;
			}

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

			struct Point
			{
				int _x;
				int _y;
			};
			
			int main()
			{
				int array1[]{ 1,2,3,4,5 };
				int array2[5]{ 0 };
			
				Point p{ 0, 1 };
			
				// C++11中列表初始化也可以适用于new表达式中
				int* pa = new int[4]{ 1,2,3,4 };
			
				return 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;
				}
			private:
				int _year;
				int _month;
				int _day;
			};
			
			int main()
			{
				Date d1(2022, 1, 1); // old style
			
				// C++11支持的列表初始化,这里会调用构造函数初始化
				// 构造+拷贝构造->优化直接构造
				Date d2{ 2022, 1, 2 };
				Date d3 = { 2022, 1, 3 };
				return 0;
			}

在这里插入图片描述

2. std::initializer_list

std::initializer_list 的介绍文档:std::initializer_list

我们先来看看 std::initializer_list 是什么类型的:

			int main()
			{
				auto i = { 10,20,30 };
				cout << typeid(i).name() << endl;
			
				return 0;
			}

在这里插入图片描述

首先我们来看一个问题,以下代码中,v1、l1、d1 的初始化方式是一样的吗?

			int main()
			{
				vector<int> v1 = { 1,2,3,4,5 };
				list<int> l1 = { 10, 20, 30 };
				Date d1 = { 2024, 1, 9 };
			
				return 0;
			}

其中,v1 和 l1 的初始化方式是一样的,v1 和 l1 的 {} 内的数据会被识别成 initializer_list 类型,这是 C++11 新增加的类型,每个容器都增加了使用 initializer_list 的构造函数,数据被识别成 initializer_list 类型后再调用相应的构造函数进行初始化,参考文档:

在这里插入图片描述

在这里插入图片描述

但是 d1 是多参数构造类型转换,是构造+拷贝构造经过优化之后直接构造!{ 2024, 1, 9 }; 会被识别成一个 Date 对象,构造完成之后再去拷贝构造 d1,但是这个过程会被编译器进行优化;这种情况当且仅当 {} 内的参数个数和 Date 中的构造函数的参数个数一样的时候!当他们的参数个数不匹配的时候,{} 内也会被识别成 initializer_list 类型,这时候由于参数个数不匹配会报错!

所以我们如果在以前模拟实现的 vector 中使用 initializer_list 去初始化对象的时候,是会报错的,因为我们以前没有写相应的构造函数,initializer_list 的构造函数也很简单,我们可以简单写一个,如下:

			vector(initializer_list<T> lt)
			{
				reserve(lt.size());
				for (auto& e : lt)
				{
					push_back(e);
				}
			}

需要注意的是,当使用大括号对容器赋值 v = {10, 20, 30}; 这个时候调的是赋值重载,而不是 initializer_list 的构造。

同样,map 也支持 initializer_list 去初始化,文档中也有相应的构造函数:

在这里插入图片描述

例如代码:

			int main()
			{
				map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
				return 0;
			}

首先,{"sort", "排序"}{"insert", "插入"} 会被识别成一个 pair 类型,而这两个使用的 {} 括起来就被识别成 initializer_list 类型,从而去初始化对象。

三、声明

c++11 提供了多种简化声明的方式,尤其是在使用模板时。

1. auto

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

auto 我们以前用得也不少了,经常用来推断比较长的类型和范围 for. 这里就不再多进行介绍。

2. decltype

关键字 decltype 将变量的类型声明为表达式指定的类型。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的类型是double
				decltype(&x) p;       // p的类型是int*
				
				// 类型以字符串形式获取到
				cout << typeid(ret).name() << endl;
				cout << typeid(p).name() << endl;
			
				vector<decltype(ret)> v;   // 使用 ret 的类型去实例化 vector
			
				F(1, 'a');
				return 0;
			}

3. nullptr

由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11中新增了 nullptr,用于表示空指针。

			#ifndef NULL
			#ifdef __cplusplus
			#define NULL   0
			#else
			#define NULL   ((void *)0)
			#endif
			#endif

四、右值引用和移动语义

1. 左值引用和右值引用

传统的 C++ 语法中就有引用的语法,而 C++11 中新增了的右值引用语法特性,所以从现在开始我们之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。

首先我们需要知道,什么是左值?什么是左值引用?

左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址 + 可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时 const 修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名

例如以下左值和左值引用:

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

那么什么是右值?什么是右值引用?

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

例如以下右值和右值引用:

			int main()
			{
				double x = 1.1, y = 2.2;
			
				// 以下几个都是常见的右值
				10;
				x + y;
				fmin(x, y);
			
				// 以下几个都是对右值的右值引用
				int&& rr1 = 10;
				double&& rr2 = x + y;
				double&& rr3 = fmin(x, y);
			
				// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
				10 = 1;
				x + y = 1;
				fmin(x, y) = 1;
			
				return 0;
			}

需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址,也就是说例如:不能取字面量 10 的地址,但是 rr1 引用后,可以对 rr1 取地址,也可以修改 rr1;如果不想 rr1 被修改,可以用 const int&& rr1 去引用,是不是感觉很神奇,这个了解一下实际中右值引用的使用场景并不在于此,这个特性也不重要。

例如以下代码:

			int main()
			{
				double x = 1.1, y = 2.2;
			
				int&& rr1 = 10;
				const double&& rr2 = x + y;
			
				rr1 = 20;	// 可以修改
				rr2 = 5.5;  // 报错
			
				return 0;
			}

2. 左值引用与右值引用比较

左值引用总结:

  1. 左值引用只能引用左值,不能引用右值。

  2. 但是 const 左值引用既可引用左值,也可引用右值。

     		int main()
     		{
     			// 左值引用只能引用左值,不能引用右值。
     			int a = 10;
     			int& ra1 = a;		// ra1 为 a 的别名
     		
     			//int& ra2 = 10;    // 编译失败,因为10是右值,左值不能引用右值
     			
     			// const左值引用既可引用左值,也可引用右值。
     			const int& ra3 = 10;
     			const int& ra4 = a;
     		
     			return 0;
     		}
    

右值引用总结:

  1. 右值引用只能右值,不能引用左值。
  2. 但是右值引用可以 move 以后的左值。

其中,move 的作用就是将一个左值强制转换为右值,使它具有右值的性质。

			int main()
			{
				// 右值引用只能右值,不能引用左值。
				int&& r1 = 10;
			
				// error C2440: “初始化”: 无法从“int”转换为“int &&”
				// message : 无法将左值绑定到右值引用
				int a = 10;
				int&& r2 = a; 	// error
			
				// 右值引用可以引用 move 以后的左值
				int&& r3 = std::move(a);
			
				return 0;
			}

3. 右值引用使用场景和意义

前面我们可以看到左值引用既可以引用左值和又可以引用右值,那为什么 C++11 还要提出右值引用呢?是不是画蛇添足呢?下面我们来看看左值引用的短板,右值引用是如何补齐这个短板的!

我们先看看以前实现的 string 类:

			namespace Young
			{
				class string
				{
				public:
					typedef char* iterator;
			
					iterator begin()
					{
						return _str;
					}
			
					iterator end()
					{
						return _str + _size;
					}
			
					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)
					{
						::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(tmp);
					}
			
					// 赋值重载
					string& operator=(const string& s)
					{
						cout << "string& operator=(string s) -- 深拷贝" << endl;
						string tmp(s);
						swap(tmp);
						return *this;
					}
			
					~string()
					{
						delete[] _str;
						_str = nullptr;
					}
			
					char& operator[](size_t pos)
					{
						assert(pos < _size);
						return _str[pos];
					}
			
					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;
					}
			
					string to_string(int val)
					{
						string str;
			
						return str;
					}
			
				private:
					char* _str;
					size_t _size;
					size_t _capacity; // 不包含最后做标识的\0
				};
			}

左值引用的使用场景:

  • 引用传参和引用返回都能提高效率

      		void func1(Young::string s)
      		{}
      		
      		void func2(const Young::string& s)
      		{}
      		
      		int main()
      		{
      			Young::string s1("hello world");
      		
      			// func2 的调用我们可以看到左值引用做参数减少了拷贝,提高效率的使用场景和价值
      			func1(s1);
      			func2(s1);
      		
      			// string operator+=(char ch) 传值返回存在深拷贝
      			// string& operator+=(char ch) 传左值引用没有拷贝提高了效率
      			s1 += '!';
      		
      			return 0;
      		}
    

左值引用的短板:

但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回。例如:Young::string to_string(int x) 函数中可以看到,这里只能使用传值返回,传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。

例如:

				Young::string to_string(int x)
				{
					Young::string ret;
					while (x)
					{
						int val = x % 10;
						x /= 10;
						ret += ('0' + val);
					}
			
					reverse(ret.begin(), ret.end());
			
					return ret;
				}

				int main()
				{
					Young::string ret = Young::to_string(10);
				
					return 0;
				}

在这里插入图片描述

其中上述的拷贝过程如下图所示:

在这里插入图片描述

上述过程本应该是两次拷贝构造,但是一般会被编译器优化成一次拷贝构造。

这就是左值引用的短板,当返回值是一个局部对象的时候还是只能进行传值返回,这样如果是自定义类型的话,会造成深拷贝的代价。这时候右值引用的价值就体现出来了,可以使用右值引用和移动语义解决上述问题。

右值引用和移动语义:

首先我们在 Young::string 中增加移动构造移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己,为什么可以直接窃取别人的资源呢?首先我们先将右值分为以下两种:

  1. 纯右值:内置类型右值
  2. 将亡值:自定义类型的右值

在上述例子中,to_string 中的返回值 ret 就是一个自定义类型的右值,即将亡值,这时候我们如果加上移动语义的构造和赋值,那么在 to_string 返回的时候,ret 被识别成一个将亡值,就会去调移动语义的构造,由于 ret 是一个将亡值,所以我们可以直接窃取它的资源来构造自己;反正你已经是一个将亡值了,倒不如把你的资源给我,这样就省去了深拷贝的代价,就是这个意思。

下面我们在 Young::string 中增加移动语义的构造和赋值:

				// 移动构造
				string(string&& s)
					:_str(nullptr)
					, _size(0)
					, _capacity(0)
				{
					cout << "string(string&& s) -- 移动语义" << endl;
					swap(s);
				}
		
				// 移动赋值
				string& operator=(string&& s)
				{
					cout << "string& operator=(string&& s) -- 移动语义" << endl;
					swap(s);
					return *this;
				}

我们继续调用 to_string 观察是否还会进行深拷贝:

在这里插入图片描述

但是如果是以下场景又会有一些变化:

				int main()
				{
					Young::string ret;
					ret = Young::to_string(10);
				
					return 0;
				}

该场景和上述场景的区别在于,该场景使用一个已存在的对象接收 to_string 的返回值。我们观察会有什么区别:

在这里插入图片描述

这里运行后,我们看到调用了一次移动构造和一次移动赋值。因为如果是用一个已经存在的对象接收,编译器就没办法优化了。Young::to_string 函数中会先用返回的 ret 生成构造生成一个临时对象,但是我们可以看到,编译器把 ret 识别成了右值,即将亡值,调用了移动构造。然后在把这个临时对象做为 Young::to_string 函数调用的返回值赋值给接收的 ret,这里调用的移动赋值。结合下图理解:

在这里插入图片描述

STL的容器在C++11以后,都增加了移动构造和移动赋值,如下图:

在这里插入图片描述
在这里插入图片描述

4. 右值引用引用左值及其一些更深入的使用场景分析

按照语法,右值引用只能引用右值,但右值引用一定不能引用左值吗?因为:有些场景下,可能真的需要用右值去引用左值实现移动语义。当需要用右值引用引用一个左值时,可以通过 move 函数将左值转化为右值。C++11 中,std::move() 函数位于 头文件中,该函数名字具有迷惑性,它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。

下面我们看一个问题,如下代码:

				int main()
				{
					Young::string s1("hello, world!\n");
					Young::string s2(s1);
					Young::string s3 = move(s1);
				
					return 0;
				}

我们将上述代码中的 s1 进行 move 操作,然后 move 返回一个 s1 的右值,再去构造 s3,此时会出现的问题是什么呢?我们调试观察:

在这里插入图片描述

如上图,当我们构造完 s3 之后,由于我们将 s1 转换为了右值,所以这里调用的是移动构造,将 s3s1 的资源互换,此时 s1 就变成了空串!所以我们不能随便将一个左值进行 move 操作,否则可能会产生意想不到的结果!

STL容器插入接口函数也增加了右值引用版本:

在这里插入图片描述
在这里插入图片描述

如下代码:

				int main()
				{
					list<Young::string> lt;
					Young::string s1("1111");
				
					// 这里调用的是拷贝构造
					lt.push_back(s1);
				
					// 下面调用都是移动构造
					lt.push_back("2222");
					lt.push_back(move(s1));
				
					return 0;
				}

在这里插入图片描述

下面我们将以前模拟实现的 list 拿过来,我们自己实现一个右值引用的 push_backinsert

				// 插入节点 --- 左值版本
				iterator insert(iterator pos, const T& x)
				{
					Node* newnode = new Node(x);
					Node* cur = pos._node;
		
					Node* prev = cur->_prev;
		
					prev->_next = newnode;
					newnode->_prev = prev;
					newnode->_next = cur;
					cur->_prev = newnode;
		
					++_size;
		
					return newnode;
				}
		
		
				// 插入节点 --- 右值版本
				iterator insert(iterator pos, T&& x)
				{
					Node* newnode = new Node(x);
					Node* cur = pos._node;
		
					Node* prev = cur->_prev;
		
					prev->_next = newnode;
					newnode->_prev = prev;
					newnode->_next = cur;
					cur->_prev = newnode;
		
					++_size;
		
					return newnode;
				}
		
		
				// 尾插 --- 左值版本
				void push_back(const T& x)
				{
					insert(end(), x);
				}
		
				// 尾插 --- 右值版本
				void push_back(T&& x)
				{
					insert(end(), x);
				}

下面我们测试一下我们模拟实现的 list 的右值版本的插入:

在这里插入图片描述

如上图,第一次深拷贝是初始化的结果,不用管,但是我们使用的 push_back 不应该都是移动构造吗?为什么会有一次深拷贝?下面我们画图分析一下:

在这里插入图片描述

实质上,右值被右值引用引用以后的属性是左值,即上图中,to_string 返回的值是右值,所以会匹配右值引用的 push_back 版本,但是在 push_back 中,x 的属性却是左值,所以在调用 insert 时,会调用左值版本 insert 也就会导致深拷贝。

那么为什么右值被右值引用引用以后的属性是左值呢? 因为必须只能是左值,因为右值是不能直接修改,但是右值被右值引用以后,需要被修改,例如我们上面实现的移动构造就足以说明,例如下图:

在这里插入图片描述

那么我们上面那个问题应该如何解决呢?我们可以将右值引用后的左值使用 move 变为右值,继续使用右值去处理,这里需要改动的就比较多,我们需要一层一层地去改,例如下图:

在这里插入图片描述

最后我们看结果,确实完成了移动拷贝:

在这里插入图片描述

5. 完美转发

模板中的&& 万能引用:

模板中的 && 不代表右值引用,而是万能引用,其既能接收左值又能接收右值。模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发。例如下面代码是函数模板的万能引用:

				template<typename T>
				void PerfectForward(T&& t)
				{
				 	Fun(t);
				} 

我们可以尝试验证一下是否会按照我们的需求调用相应的函数:

				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);
				}
				
				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;
				}

结果如下:

在这里插入图片描述

为什么全部都是左值引用呢?我们上面解释过,因为右值被右值引用后,还是左值的属性,所以 t 就是左值。那么我们想保持实参的属性应该怎么做呢?这时候就要用到完美转发了。

std::forward 完美转发在传参的过程中保留对象原生类型属性:

				// forward<T>(t)在传参的过程中保持了 t 的原生类型属性。
				template<typename T>
				void PerfectForward(T&& t)
				{
				 	Fun(forward<T>(t));
				}

上述代码 Fun(forward<T>(t)); 就是完美转发的使用,它能保持原对象的属性。注意,完美转发要和模板的万能引用搭配使用,因为如果不是万能引用,那么它就只能是普通的右值引用,此时左值不能传参。

所以完美转发的使用场景有哪些呢?其实我们已经接触过了,上面的 push_back 的问题就可以使用完美转发解决,我们将 move 改成完美转发的形式,并且推荐使用完美转发的形式,如下图:

在这里插入图片描述

总结:右值引用的移动语义出来以后,对深拷贝的类的影响比较大,自定义类的深拷贝传值返回影响也较大,因为移动构造和移动赋值出来以后减少了它们的深拷贝;一些容器的插入接口也新增了右值版本,也减少了深拷贝。但是右值引用对于浅拷贝的类是没有意义的,因为它们没有资源可以转移。

五、新的类功能

1. 默认成员函数

原来 C++ 类中,有 6 个默认成员函数:

  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 拷贝赋值重载
  5. 取地址重载
  6. const 取地址重载

最后重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。

C++11 新增了两个:移动构造函数移动赋值运算符重载。

针对移动构造函数移动赋值运算符重载有一些需要注意的点如下:

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

2. 类成员变量初始化

C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化,这个我们在类和对象已经介绍过了,这里就不再细讲了。

3. 强制生成默认函数的关键字 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& operator=(const Person& p)
					{
						if(this != &p)
						{
							_name = p._name;
							_age = p._age;
						}
						return *this;
					}*/
				
					// 强制编译器生成
					Person(Person&& p) = default;
					Person(const Person& p) = default;
				
					~Person()
					{}
				
				private:
					Young::string _name;
					int _age;
				};

在这里插入图片描述

4. 禁止生成默认函数的关键字 delete

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

				Person(Person&& p) = delete; 		// 不让生成实现
				Person(const Person& p) = default;  // 强制编译器生成

5. 继承和多态中的 final 与 override 关键字

这个我们在继承和多态的时候已经介绍过,这里也不再做多介绍。

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

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

相关文章

如何使用Postman创建Mock Server?

这篇文章将教会大家如何利用 Postman&#xff0c;通过 Mock 的方式测试我们的 API。 什么是 Mock Mock 是一项特殊的测试技巧&#xff0c;可以在没有依赖项的情况下进行单元测试。通常情况下&#xff0c;Mock 与其他方法的主要区别就是&#xff0c;用于取代代码依赖项的模拟对…

Vue3 + TS + Element-Plus —— 项目系统中封装表格+搜索表单 十分钟写五个UI不在是问题

前期回顾 纯前端 —— 200行JS代码、实现导出Excel、支持DIY样式&#xff0c;纵横合并-CSDN博客https://blog.csdn.net/m0_57904695/article/details/135537511?spm1001.2014.3001.5501 目录 一、&#x1f6e0;️ newTable.vue 封装Table 二、&#x1f6a9; newForm.vue …

【数据库】视图索引执行计划多表查询笔试题

文章目录 一、视图1.1 概念1.2 视图与数据表的区别1.3 优点1.4 语法1.5 实例 二、索引2.1 什么是索引2.2.为什么要使用索引2.3 优缺点2.4 何时不使用索引2.5 索引何时失效2.6 索引分类2.6.1.普通索引2.6.2.唯一索引2.6.3.主键索引2.6.4.组合索引2.6.5.全文索引 三、执行计划3.1…

性能测试中TPS上不去的几种原因浅析

昨晚在某个测试群看到有人问了一个问题&#xff1a;压力测试中TPS一直上不去&#xff0c;是什么原因&#xff1f;稍微整理了下思路&#xff0c;列举性的简略回答了他的问题。 这篇博客&#xff0c;就具体说说在实际压力测试中&#xff0c;为什么有时候TPS上不去的原因。如有遗…

一包多语言——使用FontForge合并字体

大家好&#xff0c;我是阿赵。   比较多游戏做了一个游戏包里面包含了多种语言&#xff0c;可以游戏内切换。这里分享一个合并多种语言字体的方法。 一、遇到的问题 假设我们游戏需要同时显示简体中文、泰文、老挝文三种语言。 解决方案有多种&#xff1a; 1、准备多种字体 …

【清华社机器之心】视频生成前沿研究与应用特别活动

在视频生成即将迎来技术和应用大爆发之际&#xff0c;为了帮助企业和广大从业者掌握技术前沿&#xff0c;把握时代机遇&#xff0c;机器之心AI论坛就将国内的视频生成技术力量齐聚一堂&#xff0c;共同分享国内顶尖力量的技术突破和应用实践。 论坛将于2024.01.20在北京举办&am…

FineBI实战项目一(18):每小时上架商品个数分析开发

点击新建组件&#xff0c;创建每小时上架商品个数组件。 选择线图&#xff0c;拖拽cnt&#xff08;总数&#xff09;到纵轴&#xff0c;拖拽hourStr到横轴。 修改横轴和纵轴的文字。 调节连线样式。 添加组件到仪表板。

ride导入常用的库

1、打开程序 安装ride成功后&#xff0c;直接在cmd中打开&#xff0c;过程中可以捕获到日志记录 输入&#xff1a;ride.py 2、新建测试套件 右键文件夹&#xff0c;选择--》new Suite 3、导入 Library 导入成功的是黑色&#xff0c;失败的是红色&#xff0c;可以再cmd中查看…

Jenkins基础篇--添加节点

节点介绍 Jenkins 拥有分布式构建(在 Jenkins 的配置中叫做节点)&#xff0c;分布式构建能够让同一套代码在不同的环境(如&#xff1a;Windows 和 Linux 系统)中编译、测试等。 Jenkins 运行的主机在逻辑上是 master 节点&#xff0c;下图是主节点和从节点的关系。 添加节点 …

Pytorch常用的函数(六)常见的归一化总结(BatchNorm/LayerNorm/InsNorm/GroupNorm)

Pytorch常用的函数(六)常见的归一化总结(BatchNorm/LayerNorm/InsNorm/GroupNorm) 常见的归一化操作有&#xff1a;批量归一化&#xff08;Batch Normalization&#xff09;、层归一化&#xff08;Layer Normalization&#xff09;、实例归一化&#xff08;Instance Normaliza…

WindowsServer安装mysql最新版

安装 下载相应mysql安装包&#xff1a; MySQL :: Download MySQL Installer 选择不登陆下载 双击运行下载好的mysql-installer-community-*.*.*.msi 进入类型选择页面&#xff0c;本人需要mysql云服务就选择了server only server only&#xff08;服务器&#xff09;&#x…

第8章-第2节-Java中流的简单介绍

1、什么是流 我们可以先想象水流是怎样的&#xff1f;溪水不断流动&#xff0c;最终融入大海&#xff1b;我们今天的学习IO其实如同水流一样&#xff0c;当我们读取文件信息或者写入信息时&#xff0c;如同水流一样&#xff0c;不断读取或者写入&#xff0c;直到业务流程结束。…

【AI视野·今日CV 计算机视觉论文速览 第286期】Tue, 9 Jan 2024

AI视野今日CS.CV 计算机视觉论文速览 Tue, 9 Jan 2024 Totally 121 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computer Vision Papers Dr$^2$Net: Dynamic Reversible Dual-Residual Networks for Memory-Efficient Finetuning Authors Chen Zhao, Shuming Li…

Vercel配置自定义域名

首先你需要有一个域名 1.点击部署的项目设置 2.找到Domains 3.输入自己的域名 点击添加之后按要求去域名服务商添加解析即可 4.显示下面内容就设置完成了&#xff0c;

vscode配置Todo Tree插件

一、在VSCode中安装插件Todo Tree ​​​​ 二、按下快捷键ctrlshiftP&#xff0c;输入setting.jspn 选择相应的配置范围&#xff0c;我们选择的是用户配置 Open User Settings(JSON)&#xff0c;将以下代码插入其中。 {//todo-tree 标签配置从这里开始 标签兼容大小写字母(…

The Sandbox 线下联动|「友邦嘉年华」地主专享门票免费放送

我们很高兴与票务合作伙伴 0xMoongate 合作&#xff0c; 为各位地主们准备了免费的“友邦嘉年华”门票&#xff01; “友邦嘉年华”介绍&#xff1a; The Sandbox 是香港最大户外盛事之一“友邦嘉年华”的荣誉合作伙伴&#xff01; 我们将这份兴奋延伸到现实世界&#xff0c…

【.NET Core】可为null类型详解

【.NET Core】可为null类型详解 文章目录 【.NET Core】可为null类型详解一、概述二、可为空的值类型2.1 声明和赋值2.2 检查可为空值类型2.3 基础类型与可为空的值类型互换2.4 可为空的值类型装箱和取消装箱2.5 如何确定可为空的值类型 三、可为 null 的引用类型 一、概述 nu…

Elasticsearch安装Windows版

目录 1.&#xff1a;下载安装包&#xff0c;选择指定的版本&#xff0c;这里选择了7.8.0&#xff0c;官网下载地址&#xff1a; ​编辑 2&#xff1a;下载好之后解压&#xff0c;解压之后是这样的&#xff1a; 3&#xff1a;配置环境变量&#xff0c;跟JDK一样&#xff0c;…

金和OA jc6 GetAttOut SQL注入漏洞复现

0x01 产品简介 金和OA协同办公管理系统软件(简称金和OA),本着简单、适用、高效的原则,贴合企事业单位的实际需求,实行通用化、标准化、智能化、人性化的产品设计,充分体现企事业单位规范管理、提高办公效率的核心思想,为用户提供一整套标准的办公自动化解决方案,以帮助…

大文件分片上传,断点续传,秒传 示例(待更新...)

1.html代码 <template><div class="card content-box"><el-upload ref="upload" class="upload-demo" action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15":limit="1" :on-change=&quo…