【C++11】final与override关键字和类的新功能
目录
一、final与override关键字
1.1 final
1.2 override
二、类的新功能
2.2 类成员变量初始化
2.3 default关键字
2.4 delete关键字
注意:C++专栏的所有测试代码都是在 vs2019 的环境下编译运行的
一、final与override关键字
这两个关键字用于继承和多态
1.1 final
final:修饰虚函数,表示该虚函数不能再被重写
测试代码
//基类
class Person {
public://被final修饰,该虚函数不能再被重写virtual void BuyTicket() final{ cout << "Person-买票-全价" << endl; }
};
//派生类
class Student : public Person {
public://派生类的虚函数重写了父类的虚函数virtual void BuyTicket() { cout << "Student-买票-半价" << endl; }
};
编译直接报错
final 关键字也可用于实现一个不能被继承的类
如何实现一个不能被继承的类??
- 把构造函数进行私有,这是 C++98 的做法
- 类定义时 加 final 关键字,这是 C++11的做法
测试代码
//基类使用 final 修饰
class A final
{};
//派生类无法进行继承基类
class B : public A
{};
编译直接报错
1.2 override
override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错
测试代码
//基类
class Person {
public://基类的虚函数virtual void BuyTicket() { cout << "Person-买票-全价" << endl; }
};
//派生类
class Student : public Person {
public://派生类完成了虚函数的重写,编译通过virtual void BuyTicket()override{ cout << "Student-买票-半价" << endl; }
};
//派生类
class Soldier : public Person
{
public://派生类没有完成虚函数的重写,编译报错virtual void BuyTicket(int n)override{ cout << "Soldier-优先-买票" << endl;}
};
编译直接报错
二、类的新功能
2.1 默认成员函数
在C++11之前,一个类中有如下六个默认成员函数
- 1. 构造函数
- 2. 析构函数
- 3. 拷贝构造函数
- 4. 拷贝赋值重载
- 5. 取地址重载
- 6. const 取地址重载
最后重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的
C++11 新增了两个:移动构造函数和移动赋值运算符重载
默认移动构造的生成条件
如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。
移动构造的默认行为:
- 默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造
默认移动赋值的生成条件
如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。
移动赋值的默认行为:
默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋
值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
注意:如果提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值
测试代码,需要使用简化版模拟实现的string
namespace fy
{class string{public://构造函数string(const char* str = ""){_size = strlen(str);//字符串大小_capacity = _size;//构造时,容量大小默认与字符串大小相同_str = new char[_capacity + 1];//为字符串开辟空间(多开一个用于存放'\\0')strcpy(_str, str);//将C字符串拷贝到已开好的空间} //拷贝构造 -- 现代写法string(const string& s):_str(nullptr), _size(0),_capacity(0){cout << "string(const string& s) -- 深拷贝" << endl;string tmp(s._str);//复用构造函数,构造 tmp对象swap(tmp);//交换}//赋值重载 -- 现代写法1string& operator=(const string& s){cout << "string& operator=(const string& s) -- 深拷贝" << endl;if (this == &s)//检查自我赋值{return *this;}string tmp(s);//复用拷贝构造函数,用s拷贝构造出对象tmpswap(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指向的空间_str = nullptr;_size = _capacity = 0;}//交换两个字符串void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}private:char* _str;size_t _size;size_t _capacity;};
}
然后再编写一个简单的 Person类,该类使用了我们模拟实现的string
class Person
{
public://构造函数Person(const char* name = "", int age = 0):_name(name), _age(age){}
private:fy::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类没有实现拷贝构造、赋值重载、析构函数,只实现了构造函数,即满足移动赋值和移动拷贝函数的生成条件
运行结果如下
默认生成的移动构造和移动赋值函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋
值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值,这里完全符合
下面进行测试
一但 Person类的拷贝构造、拷贝赋值和析构函数实现了任意一个或者多个,不满足移动赋值和移动拷贝函数的生成条件,移动赋值和移动拷贝函数就不会生成
测试代码
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:fy::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的深拷贝函数,调不到移动赋值和移动构造函数
2.2 类成员变量初始化
C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化,这里在类和对象的篇章已经谈过
class Person
{
public://...
private://非静态成员变量,可以在成员声明时给缺省值string _name = "张三"; //缺省值int _age = 22; //缺省值static int _n; //静态成员变量不能给缺省值
};
注意:这里不是初始化,给的的值是缺省值
2.3 default关键字
default关键字的作用是:强制生成默认函数
C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成
使用:在需要强制生成默认函数的声明后面加上 =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(Person&& p) = default;
private:fy::string _name;int _age;
};int main()
{Person s1;Person s2 = s1;Person s3 = std::move(s1);return 0;
}
运行结果,使用了 default关键字后默认的移动构造可以生成,即便实现了拷贝构造函数
2.4 delete关键字
delete关键字的作用是:禁止生成默认函数
使用:在需要禁止生成默认函数的声明后面加上 =delete 即可
如果能想要限制某些默认函数的生成:
- 在C++98中,是该函数设置成private,并且只用声明不用定义,这样只要其他人想要调用就会报错。
- 在C++11中更简单,只需在该函数声明加上 =delete 即可,该语法指示编译器不生成对应函数的默认版本,称 =delete 修饰的函数为删除
测试代码:
要让一个类不能被拷贝,可以用 =delete 修饰将该类的拷贝构造和赋值重载
class A
{
public:A(){}
private://强制不允许生成A(const A&) = delete;A& operator=(const A&) = delete;
};int main()
{A a1;A a2(a1);return 0;
}
编译报错
----------------我是分割线---------------
文章到这里就结束了,下一篇即将更新