final
一般来说,如果想要设计一个不能被继承的类,可以让父类的构造函数定义为私有,这样一来子类没办法调用父类的构造函数初始化成员,相当于间接限制了父类不能被继承。
但是C++中有一个关键字final可以直接限制父类不能被子类继承,final关键字加在类或者虚函数后面,这样的内容就不能被继承。
class A final
{
public:
virtual void Func()
{
}
protected:
int _a;
};
class B :public A
{
public:
virtual void Func()
{
}
};
这里子类想要继承被final修饰的父类会直接报错。
class A
{
public:
virtual void Func() final
{
}
protected:
int _a;
};
class B :public A
{
public:
virtual void Func()
{
}
};
同理,这里想要重写被final修饰的虚函数也会直接报错。
override
override这个关键字是为了针对子类中的虚函数是否正确重写了父类的虚函数,如果没有正确重写则会进行编译报错。
class A
{
public:
virtual void Func()
{
cout << "A" << endl;
}
protected:
int _a;
};
class B :public A
{
public:
virtual int Func() override
{
cout << "B" << endl;
}
};
重载/重写/重定义
重载、重写和重定义这三个概念听起来很相似,但是他们直接有很大的区别。
重载
重载:需要两个函数在同一作用域才能构成重载,同时还需要函数名相同,参数不同,与返回值类型无关,这样才可以构成重载。
重写
重写(覆盖):构成重写的两个函数需要分别位于父类和子类的作用域中。除此以外,函数必须是虚函数类型,而且要满足三同原则即函数名,参数,返回值都必须相同(协变除外)。
重定义
重定义:重定义即隐藏,两个函数如果分别在子类和父类的作用域中,只要两个函数名相同就可以构成重定义。可以这样认为子类和父类中的同名函数,如果不构成重写就是重定义。
抽象类
在定义虚函数的时候在虚函数后面加上=0这样定义出来的函数叫做纯虚函数。包含纯虚函数的类叫做抽象类也叫做接口类。这种类的特点是不能实例化出对象。子类继承后也不能实例化出对象,只有重写纯虚函数,子类才能实例化出对象。这一点和override类似,相当于强制要求子类进行重写。一般而已,纯虚函数只进行声明不会进行实现,因为他不能实例化出对象,即使实现了也没用。
class Car
{
public:
//纯虚函数一般只声明,不实现,实现没有价值
virtual void Drive() = 0//在虚函数后面加上=0
{
cout << "virtual void Drive()=0" << endl;
}
};
class Benz :public Car
{
public:
virtual void Drive()
{
cout << "Benz-舒适" << endl;
}
};
int main()
{
Car* p = new Benz;
p->Drive();
return 0;
}
这种继承方式是一种很典型的接口继承。普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。
虚函数表
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
private:
int _b = 1;
};
int main()
{
cout << sizeof(Base) << endl;
return 0;
}
这里定义了一个包含虚函数的类Base,按照正常计算类的大小的规则,Base类的大小应该为4才对,但是这个结果却是8,这就说明其中还有隐藏的成员变量,占4个字节。
通过调试窗口可以看的,这里除了_b这个成员变量以外,还有一个_vfptr指针,这个指针叫做虚函数表指针,虚函数地址会被存放到虚函数表中,这个指针就是指向该虚函数表的地址。