C++11:列表初始化、新增关键字和新增的默认成员函数

目录

一. 列表初始化

1.1 {}列表初始化的方法

1.2 列表初始化实现的原理

二. C++11新增关键字

2.1 decltype -- 自动类型推断

2.2 nullptr -- 空指针

2.3 default -- 强制生成默认成员函数

2.4 delete -- 禁止生成默认成员函数

2.5 final -- 禁止类被继承/禁止虚函数被重写

2.6 override -- 检查子类虚函数是否重写了某个函数

三. C++11新增的两个默认成员函数


一. 列表初始化

1.1 {}列表初始化的方法

在C++98中,可以使用{}来初始化数组、结构体等,在C++11中,列表初始化的用途被扩大到所有的内置类型变量和自定义类型变量,也可以用于在new中给定构造函数的参数。在使用{}初始化时,我们甚至可以不带=。

进入C++11后,所有的STL容器也都会支持列表{}初始化。

代码1.1:列表初始化

int main()
{
	//{}初始化内置类型成员变量
	int i1 = { 1 };
	int i2{ 2 };

	//{}初始化结构体(类对象)

	A a = { 1,3 };
	B b = { 1,2 };

	//{}用于给new出来的对象初值
	B* pb = new B[2]{ {2,4}, {6,8} };
	delete[] pb;

	//STL容器对象列表初始化
	std::vector<int> v = { 1,2,3,4,5 };
	std::list<int> lt = { 1,2,3,4,5 };

	return 0;
}

1.2 列表初始化实现的原理

为了实现列表初始化,C++11单独引入了一个名为initializer_list的类,这个类中包含iterator和const_iterator迭代器成员变量,还包括begin()和end()两个成员函数,用于查找列表的头部和尾部,C++11的STL容器中都会加入列表初始化函数。initializer_list的声明和成员以及部分SLT容器的列表初始化函数声明见图1.1和1.2。

  • typeid(变量名).name() -- 获取变量类型(以字符串的形式表示)

代码1.2:打印initializer_list变量的类型

int main()
{
	auto x = { 1,2,3,4 };
	std::cout << typeid(x).name() << std::endl;   //输出:class std::initializer_list<int>
	return 0;
}

initializer_list的文档:initializer_list - C++ Reference

图1.1 nitializer_list的声明和成员
图1.2 C++11部分STL容器列表构造函数声明

代码1.3为支持列表构造的vector类的简单模拟实现,只需要声明构造函数的参数类型为列表,用迭代器依次遍历链表的每个数据,将其push_back到vector中即可。

代码1.3:支持列表构造的vector类

namespace zhang
{
	template<class T>
	class vector
	{
	public:
		vector()   //默认构造
			: _a(nullptr)
			, _size(0)
			, _capacity(0)
		{ }

		vector(const std::initializer_list<T>& lt)  //列表构造函数
		{
			typename std::initializer_list<T>::const_iterator it = lt.begin();

			while (it != lt.end())
			{
				push_back(*it);
				it++;
			}
		}

		void push_back(const T& x)  //尾插数据函数
		{
			if (_capacity == 0 || _size == _capacity)
			{
				size_t newCapacity = _capacity == 0 ? 4 : 2 * _capacity;
				_capacity = newCapacity;

				T* tmp = new T[newCapacity];
				memmove(tmp, _a, _size * sizeof(T));
				std::swap(_a, tmp);

				delete[] tmp;
				tmp = nullptr;
			}

			_a[_size] = x;
			++_size;
		}

	private:
		T* _a;
		size_t _size = 0;
		size_t _capacity = 0;
	};
}

二. C++11新增关键字

2.1 decltype -- 自动类型推断

decltype用于自动获取某个数据表达式的类型,可以用decltype获得的类型,去定义新的变量,decltype可以获取变量、字面常量、表示式的类型。

C++11中auto也可以用于自动类型推断,但其与decltype不同的是:auto定义变量时根据所赋的初值确定类型,而decltype在定义新变量时变量类型由传给decltype的数据表达式确定,与所赋初值无关。

代码2.1:decltype自动类型推断

int main()
{
	int i = 10;
	decltype(i) a = 10;
	decltype(i) b = 10.5;  //获取变量类型
	auto c = 10.5;

	std::cout << "a = " << a << std::endl;
	std::cout << "b = " << b << std::endl;
	std::cout << "c = " << c << std::endl;

	decltype(1.55) d = 10.5;   //获取字面常量类型
	decltype(a * d) e = 20.5;  //获取表达式类型

	std::cout << "d = " << d << std::endl;
	std::cout << "e = " << e << std::endl;

	return 0;
}
图2.1 代码2.1的运行结果

2.2 nullptr -- 空指针

在C++98中,NULL被定义为字面常量0,如果用将NULL作为参数传递给函数,那么NULL会被识别为int类型的数据,如代码2.2中,将NULL传给func函数,我们希望运行参数类型为void*形式的重载函数,但实际上运行了int型参数的func,这样就存在调用错误。

为了解决上面的问题,C++11引入了一个新的空指针宏nullptr,用于取代C++98中的NULL,nullptr的定义为:#define nullptr ((void*)0)

如果将nullptr作为参数调用func,则void func(void* x)被正确调用。

代码2.2:NULL和nullptr做参数

void func(void* x) 
{ 
	std::cout << "void func(void* x)" << std::endl; 
}

void func(int x) 
{ 
	std::cout << "void func(int x)" << std::endl; 
}

int main()
{
	func(NULL);
	func(nullptr);
	return 0;
}
图2.2 代码2.2的运行结果

2.3 default -- 强制生成默认成员函数

假设我们在自定义一个类的时候,显示定义了拷贝构造函数,那么编译器就会认为构造函数已经存在,不会再生成默认构造函数。但是,如果我们还是希望编译器生成默认构造函数,可以使用default强制生成,default强制生成默认成员函数语法为:函数声明 = default。

代码2.3:default强制生成成员函数

class A
{
public:
	A() = default;   //强制生成默认构造

	A(const A& a)
		: _a1(a._a1)
		, _a2(a._a2)
	{ }

	A& operator=(const A& a) = default;  //强制生成拷贝构造函数

	A& operator=(A&& a)  //移动拷贝
	{
		_a1 = a._a1;
		_a2 = a._a2;
		return *this;
	}

private:
	int _a1  =10;
	int _a2 = 20;
};

2.4 delete -- 禁止生成默认成员函数

如果我们希望定义一个不允许被赋值(operator=)的类,在C++98的标准下,我们需要定义一个继承体系,将基类的赋值运算符重载函数声明为私有类型,然后将禁止被赋值的类定义为这个基类的派生类。这样,如果我们试图赋值,程序会报错。

代码2.4:通过定义继承体系禁止类对象被赋值(C++98)

class Forbid
{
private:
	Forbid& operator=(const Forbid& f) { };
};

//Derive继承Forbid类,Forbid类中的operator=函数为私有
//Derive类对象不能被赋值
class Derive: public Forbid
{
public:
	Derive(int x = 10, int y = 20)
		: Forbid()
		, _x(x)
		, _y(y)
	{ }

private:
	int _x;
	int _y;
};

int main()
{
	Derive d1;
	Derive d2(20, 30);
	d1 = d2;  //编译报错
	return 0;
}

但是C++98通过继承禁止拷贝赋值十分复杂繁琐,且实际上并没有让operator=函数消失,只是由于父类的operator=函数被声明为了私有,无法被调用而已。

为此,C++11赋予了关键字delete新的用途 -- 禁止生成某个默认成员函数。

语法:函数声明 = delete

代码2.5:delete禁止特定成员函数的生成

class A
{
public:
	A(int a1 = 10, int a2 = 20)
		: _a1(a1)
		, _a2(a2)
	{ }

	//禁止生成拷贝构造和赋值运算符重载函数
	A(const A& a) = delete;
	A& operator=(const A& a) = delete;

private:
	int _a1 = 10;
	int _a2 = 20;
};

2.5 final -- 禁止类被继承/禁止虚函数被重写

final关键字的两种作用:

  • 声明某个类不能被继承
  • 声明某个虚函数不能被重写

代码2.6:final关键字的使用

//Base类不能被继承
class A final
{ };

class B: public A  //报错
{ };

class AA
{
public:
	virtual void func() final { std::cout << "class AA" << std::endl; }
};

class BB : public AA
{
public:
	//非法重写func函数,报错
	virtual void func() { std::cout << "class BB" << std::endl; }
};

2.6 override -- 检查子类虚函数是否重写了某个函数

override关键字:放在子类中虚函数声明的后面,如果子类虚函数没有重写某个父类的虚函数,那么就编译报错。

代码2.7:override检查子类虚函数重写

class AA
{
public:
	//virtual void func() { std::cout << "class AA" << std::endl; }
};

class BB : public AA
{
public:
	//func为重写父类虚函数,报错
	virtual void func() override { std::cout << "class BB" << std::endl; }
};

int main()
{
	BB bb;
	return 0;
}

三. C++11新增的两个默认成员函数

在C++98中,我们认为编译器自动生成的默认构造函数有6个,分别为:构造函数、析构函数、拷贝构造函数、赋值运算符重载函数、对于普通对象的取地址运算符重载函数、对于const对象的的取地址运算符重载函数。

图3.1 C++11的8个默认成员函数

由于C++11新增了右值引用,为了减少拷贝的消耗,C++11中默认成员函数增加到了8个,新增的两个为:移动构造函数、移动赋值函数。

  • 编译器自动生成移动构造函数的条件:不显示定义移动构造函数、不显示定义拷贝构造、赋值运算符重载、析构三个默认成员函数的任意一个。
  • 编译器自动生成的移动构造函数进行的工作:对于内置类型成员变量进行浅拷贝,对于自定义类型成员变量,如果有移动构造函数,调用其移动构造函数,如果没有,调用其拷贝构造函数。
  • 移动赋值函数自动生成的条件与移动赋值函数相同,进行的工作与移动构造函数基本一致,内置类型值拷贝,自定义类型调用其拷贝赋值函数或移动赋值函数。

为了观察默认生成的移动构造函数进行的工作,编写代码3.1,类BB中包含类AA的自定义类型成员变量,将BB类的拷贝构造、拷贝赋值和析构函数全部注释掉,然后在主函数中利用拷贝构造和移动构造创建对象dd2和dd3,运行代码(结果见图3.2),可以观察到aa的移动构造函数被bb默认生成的移动构造函数调用了。

注意:由于VS2013不能很好的支持C++11的一些新特性,应当用VS2019演示代码3.1。

代码3.1:默认生成移动构造函数的功能验证

class AA
{
public:
	AA(int a1 = 1)
		: _a1(a1)
	{ }

	AA(const AA& aa)
		: _a1(aa._a1)
	{  
		std::cout << "拷贝构造 -- AA" << std::endl;
	}

	AA(AA&& aa)
		: _a1(aa._a1)
	{
		std::cout << "移动构造 -- AA" << std::endl;
	}

private:
	int _a1 = 1;
};

class BB
{
public:
	BB(int aa = 1, int b1 = 2)
		: _aa(AA(1))
		, _b1(b1)
	{ }

	/*BB(const BB& bb)
		: _aa(bb._aa)
		, _b1(bb._b1)
	{ }*/

	/*BB& operator=(const BB& bb)
	{
		_b1 = bb._b1;
	}*/

	/*~BB(){ }*/

private:
	AA _aa;
	int _b1;
};

int main()
{
	BB bb1(10, 20);
	BB bb2(bb1);
	BB bb3(std::move(bb1));

	return 0;
}
图3.2 代码3.1的运行结果
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值