【C++11】—— 类的新功能

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目录

一、移动构造和移动赋值的特点

二、类成员变量初始化

三、强制生成默认函数的关键字default

四、禁止生成默认函数的关键字delete

五、继承和多态中的fifinal与override关键字


一、移动构造和移动赋值的特点

默认成员函数 

原来C++类中有6个默认成员函数
  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 拷贝赋值重载
  5. 取地址重载
  6. const 取地址重载

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

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

针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下
  • 如果你没有自己实现移动构造函数且没有实现析构函数 、拷贝构造、拷贝赋值重载都没有实现。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数对于内置类型成员会执行逐成员按字节拷贝自定义类型成员则需要看这个成员是否实现移动构造如果实现了就调用移动构造没有实现就调用拷贝构造。
  • 如果你没有自己实现移动赋值重载函数且没有实现析构函数 、拷贝构造、拷贝赋值重载都没有实现那么编译器会自动生成一个默认移动赋值。默认生成的移动赋值函数对于内置类型成员会执行逐成员按字节拷贝自定义类型成员则需要看这个成员是否实现移动赋值如果实现了就调用移动赋值没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
  • 如果你提供了移动构造或者移动赋值编译器不会自动提供拷贝构造和拷贝赋值。
namespace mlg
{
	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=(const 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;   
			_size = 0;        
			_capacity = 0;   
		}
	private:
		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()
	{}

private:
	mlg::string _name; //姓名
	int _age;          //年龄
};

int main()
{
	Person s1("张三", 21);
	Person s2 = std::move(s1); //想要调用Person默认生成的移动构造
	return 0;
}

        如上面代码所示如果你的Person类实现了构造函数、析构函数或者拷贝赋值重载那么执行main函数时我们想的是调用mlg::string里的移动构造但是结果显示如下

如果你将Person类实现了构造函数、析构函数或者拷贝赋值重载全部注释掉结果显示如下

        两次运行结果不同的原因就在于移动构造是一个特殊的默认成员函数s1是一个左值通过move函数转变了性质变成了右值属性赋值给了Person s2 按照正常的逻辑应该回去调用s2的移动构造然后去调用s1的移动构造但是当你在Person类实现了构造函数、析构函数或者拷贝赋值重载的时候编译器就不会生成默认的移动构造函数就会去调用拷贝构造进而调用s1的拷贝构造是一次深拷贝

如果你想要验证移动赋值代码如下

int main()
{
	Person s1("张三", 21);
	Person s2;
	s2 = std::move(s1); //想要调用Person默认生成的移动赋值
	return 0;
}

最后执行的结果和上面的是类似的通过这样的测试就能明白移动构造和移动赋值相比其他的默认成员函数有什么样的区别。

二、类成员变量初始化

        默认生成的构造函数对于自定义类型的成员会调用其构造函数进行初始化但并不会对内置类型的成员进行处理。于是C++11支持非静态成员变量在声明时进行初始化赋值默认生成的构造函数会使用这些缺省值对成员进行初始化。比如

class Person
{
public:
	/*****/
private:

	mlg::string _name = "张三"; //姓名
	int _age = 20;             //年龄
	static int _a; //静态成员变量不能给缺省值
};

三、强制生成默认函数的关键字default

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

class Person
{
public:
	//拷贝构造函数
	Person(const Person& p)
		:_name(p._name)
		, _age(p._age)
	{}
private:
	mlg::string _name; //姓名
	int _age;          //年龄
};

int main()
{
	Person s; //没有合适的默认构造函数可用
	return 0;
}

        执行上面的代码编译器会提示Person类没有默认的构造函数在类和对象章节我们知道对于构造函数是这样定义的我们不写编译器会默认生成一个构造函数如果写了则不会生成。Person类中的构造函数虽然是拷贝构造但它也是构造函数我们实例化出来的s对象应该去调用它的默认构造函数但是编译器没有生成因为你有了拷贝构造。

        出现这样的情况我们可以自己写也可以使用default关键字显示指定移动构造生成

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

	//拷贝构造函数
	Person(const Person& p)
		:_name(p._name)
		, _age(p._age)
	{}

private:
	mlg::string _name; //姓名
	int _age;         //年龄
};

默认成员函数都可以用default关键字强制生成包括移动构造和移动赋值。

四、禁止生成默认函数的关键字delete

如果要限制某些默认函数的生成
  • 在C++98中是将该函数设置成private并且只用声明不用定义这样当外部调用该函数时就会报错。这样只要其他人想要调用就会报错。
  • 在C++11中更简单只需在该函数声明加上=delete即可该语法指示编译器不生成对应函数的默认版本称=delete修饰的函数为删除函数。
class Person
{
public:
    Person(const char* name = "", int age = 0)
        :_name(name)
        , _age(age)
    {}
    Person(const Person& p) = delete;
private:
    mlg::string _name;
    int _age;
};

int main()
{
    Person s1;
    Person s2 = s1;
    Person s3 = std::move(s1);
    return 0;
}

五、继承和多态中的fifinal与override关键字

        在之前多态的博客中C++对函数重写的要求比较严格但是有些情况下由于疏忽可能会导致函数名字母次序写反而无法构成重载而这种错误在编译期间是不会报出的只有在程序运行时没有得到预期结果才来debug会得不偿失因此C++11提供了override和fifinal两个关键字可以帮助用户检测是否重写。

fifinal

  • 修饰类的时候表示该类不能被继承
  • 修饰虚函数的时候表示该虚函数不能再被重写
class A final //直接限制不能被继承也称最终类
{
private:
	A(int a = 0)
		:_a(a)
	{}
protected:
	int _a;
};
 
class B :public A //不能被继承
{
 
};
/****************************************************/
class c 
{
public:
	virtual void f() final//限制它不能被子类中的虚函数重写
	{
		cout << "c::f()" << endl;
	}
};
 
class d :public c
{
public:
	virtual void f() //不能被重写
	{
		cout << "d::f()" << endl;
	}
};

override: 检查子类虚函数是否重写了父类某个虚函数如果没有重写编译报错

class Car 
{
public:
	virtual void Drive()
	{}
};
 
class Benz :public Car 
{
public:
	virtual void Drive() override //检查是否完成重写
	{ cout << "Benz-舒适" << endl; }
};

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: c++

“【C++11】—— 类的新功能” 的相关文章