前言
上一期我们对右值引用和完美转发作了介绍,本期我们接着上期继续介绍C++11的新的类功能!
目录
前言
• 新的类功能
默认成员函数
类成员变量初始化
强制生成默认函数的关键字default
禁止生成默认成员函数的关键字delete
继承和多态中的final和override关键字
• 新的类功能
默认成员函数
在C++11前,原来的类中有6个默认成员函数:
1、构造函数
2、析构函数
3、拷贝构造函数
4、赋值拷贝重载
5、取地址重载
6、const 取地址重载
其中,前4个很重要,后面的那两个没有那么重要!默认成员函数是我们不写编译器会生成一个默认的。C++11又增加了两个:移动构造函数 和 移动赋值运算符重载。
默生成认移动构造 和 移动赋值的条件:
• 移动构造:自己没实现移动构造,且没有实现析构函数、拷贝构造、赋值拷贝重载的任意一个。
• 移动赋值:自己没实现移动赋值,且没有实现析构函数、拷贝构造、赋值拷贝重载的任意一个。
• 注意:如果自己提供了移动构造或者移动赋值 ,即使没有提供赋值拷贝和拷贝构造,编译器也不会生成!
默生成认移动构造 和 移动赋值 做了什么?
• 默认移动构造:对于内置类型成员会按字节拷贝; 对于自定义类型的成员,则需要看这个成员类是否实现移动构造,如果实现了移动构造就调用移动构造,否则,就调用拷贝构造!
• 默认移动赋值:对于内置类型成员会按字节拷贝; 对于自定义类型的成员,则需要看这个成员类是否实现移动赋值,如果实现了移动赋值就调用移动赋值,否则,就调用赋值拷贝!
ok, 我们来验证一下:
为了方便演示,我们还是把以前的string类给拷贝过来:
namespace cp
{
	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(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;
		}
		~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;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity; // 不包含最后做标识的\0
	};
	cp::string to_string(int value)
	{
		cp::string str;
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}
		while (value > 0)
		{
			int x = value % 10;
			value /= 10;
			str += ('0' + x);
		}
		if (flag == false)
		{
			str += '-';
		}
		std::reverse(str.begin(), str.end());
		return str;
	}
} 
我们自己写一个Person类:
class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
	{}
private:
		cp::string _name;
		int _age;
}; 
这个类中有两个成员,一个是我们自己的cp::string、一个是int _age,我们当前的Person是没有:析构、拷贝构造、赋值拷贝的!此时,Person中就会有一个默认生成的移动构造,此时,我们的cp::string上期是实现了移动构造的,所以,他就会调用它的移动构造,而_age就直接按字节拷贝了!
Person s1;
Person s2 = s1;
cout << "---------------------------" << endl;
Person s3 = std::move(s1); 

如果我们,把cp::string中的移动构造给注释掉,此时他就会去调用,拷贝构造:

再来看看移动赋值:

我们还是来把,cp::string中的移动赋值给注释掉,他就会调用赋值拷贝:

类成员变量初始化
C++11允许在类定义时给成员变量给一个缺省值,这个在以前就介绍过,主要是为了解决,不写构造调用默认构造时,自定义类型去调它的构造,内置类型不处理!这里的初始值,其实是给初始化列表,解决内置类型是随机值的缺陷的!
class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
	{}
private:
		cp::string _name = "张三";
		int _age = 10;//主要是解决这里的
}; 
强制生成默认函数的关键字default
C++11可以让你更好的控制要使用的默认函数,假设你要使用某个默认的函数,但是由于一些原因这个函数没有默认的生成。比如,你写了析构,就不会生成移动构造了,那么我们可以使用default关键字来显示的强制生成移动构造:
class Person
{
public:
	Person(const char* name, int age)
		:_name(name)
		, _age(age)
	{}
	// 我们写了构造,编译器就不会生成默认的构造了,我们可以使用default强制的生成
	Person() = default;
private:
		cp::string _name;
		int _age;
}; 
这里一定要注意,默认构造的含义:1、默认生成的 2、无参的 3、支持第一个参数是缺省的
class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
	{}
	Person(const Person& p)
		:_name(p._name)
		, _age(p._age)
	{}
	// 我们写了 拷贝构造,编译器就不会生成默认的 移动构造 了,我们可以使用default强制的生成
	Person(Person&& p) = default;
private:
		cp::string _name;
		int _age;
}; 

禁止生成默认成员函数的关键字delete
如果要限制某些默认成员函数的生成,在C++98中设置成private,并且只用声明不用定义;这样其他人调用就会报错;在C++11中更简单,只需要在该函数声明加上=delete即可,该语法指示编译器不生成默认的该函数,成=delete修饰的函数为删除函数。
class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
	{}
	Person(Person& p) = delete;// 默认删除/禁用掉 拷贝构造
private:
		cp::string _name;
		int _age;
}; 
OK,我们假设现在有一个需求,需要设置一个类,该类的对象只能在堆上!我们分别用C++98和C++11的方式实现一下:
class A
{
public:
	static A* Heap_Obj()
	{
		return new A;
	}
private:
	A() {}// 将默认的构造给禁用掉
	A(const A& a);// C++98的写法,不实现,只声明
	std::string _name;
	int _age;
}; 


再来看看C++11的:
class A
{
public:
	static A* Heap_Obj()
	{
		return new A;
	}
	A(const A& a) = delete;// C++11直接禁用掉
private:
	A() {}// 将默认的构造给禁用掉
	//A(const A& a);// C++98的写法,不实现,只声明
	std::string _name;
	int _age;
}; 
当然这个我们后面单独一期介绍,一些特殊类的设计!
继承和多态中的final和override关键字
final修饰类
被final修饰的类叫做最终类,最终类无法被继承。比如:
class NonInherit final //被final修饰,该类不能再被继承
{
	//...
};
 
final修饰虚函数
inal修饰虚函数,表示该虚函数不能再被重写,如果子类继承后重写了该虚函数则编译报错。比如:
//父类
class Person
{
public:
	virtual void Print() final //被final修饰,该虚函数不能再被重写
	{
		cout << "hello Person" << endl;
	}
};
//子类
class Student : public Person
{
public:
	virtual void Print() //重写,编译报错
	{
		cout << "hello Student" << endl;
	}
};
 
override修饰虚函数
override修饰子类的虚函数,检查子类是否重写了父类的某个虚函数,如果没有没有重写则编译报错。比如:
//父类
class Person
{
public:
    virtual void Print()
    {
        cout << "hello Person" << endl;
    }
};
//子类
class Student : public Person
{
public:
    virtual void Print() override //检查子类是否重写了父类的某个虚函数
    {
        cout << "hello Student" << endl;
    }
}; 
OK,好兄弟本期分享就到这里,我是cp我们下期再见!

















