概述
通过继承联系在一起的类具有层次关系,在层次的根部具有一个基类,其他类从基类继承而来,叫做派生类。
定义类时,如果下层类除了拥有上一层类具有的特性,还具有本身的特性,可以考虑使用继承
语法:class 子类 : 继承方式 父类
继承中的对象模型
父类中所有非静态成员都会被子类继承,包括私有成员,只是被隐藏了,子类访问不到。
继承的分类
单继承
继承中的访问问题:
多继承
一个类继承多个基类
语法:class 子类:继承方式 父类1, 继承方式 父类2 …
多继承可能会引发父类中有同名成员出现,需要加作用域区分,C++实际开发中不推荐使用多继承。
多继承优点:可以调用多个类的接口
多继承缺点:二义性
菱形继承
概念:两个派生类同时继承一个基类,又有某个类同时继承这两个派生类,这种继承被称为菱形继承。
class Animal
{
public:
int m_age;
};
class Sheep:virtual public Animal{};
class Tuo:virtual public Animai{};
class SheepTuo:public Sheep, public Tuo
{
public:
int m_Age;
};
虚函数
概念
声明方式:在基类中,需要被子类重写的函数由virtual修饰。
如果派生类不打算重新定义基类的任何成员函数,则声明都不需要,默认使用基类的虚函数;如果派生类要重新定义基类的某个虚函数,则需要在派生类中重新声明该函数,然后编写函数体,要求从函数名到参数列表均与基类一致,virtual可写可不写。
例:
class Base
{
public:
virtual void func(int i)
{
cout << "Base::func(int):" << i << endl;
}
};
class Derived:public Base
{
public:
void func(int i)
{
cout << "Derived::func(int):" << i << endl;
}
};
int main()
{
Base b;
Base *bp = new Derived();
bp->func(999);
return 0;
}
输出结果:Derived::func(int):999
虚函数实现机制
编译器会为每个具有虚函数的类创建一个虚函数表,该虚函数表被该类的所有对象共享。类中的每个虚函数占据虚函数表中的一行。派生类的虚函数表存放重写的虚函数。
所有同类对象共享一个虚函数表,每个派生类对象都有一个自己的虚表指针指向虚函数表。
纯虚函数与抽象基类
在基类中,纯虚函数只作声明,不作实现,并在声明后直接加"=0",在派生类中需要给出所有纯虚函数的实现。
包含纯虚函数的基类被称为抽象基类,抽象基类不能创建对象。
例如:
class Person
{
public:
virtual void print_info()=0;
}
注意:派生类必须重新定义全部的纯虚函数,否则它也属于抽象基类,不能创建对象。
虚函数的代价
- 带有虚函数的类,每个类都会产生一个虚函数表,用来存储指向虚成员函数的指针,增大类的内存;
- 带有虚函数的每一个对象,都会有一个指向虚表的指针,增大对象的内存空间;
- 虚函数不能再是内联函数,因为内联函数在编译阶段进行替代,而虚函数在运行阶段才能确定采用的是哪种函数,所以虚函数不能是内敛函数。
哪些函数不能是虚函数
- 构造函数:构造函数初始化对象,派生类必须知道基类函数干了什么,才能进行构造;当有虚函数时,每一个类存在一个虚表,每一个对象有一个虚表指针,虚表指针在构造函数中初始化;
- 内联函数:内敛函数在编译时进行函数体的替换操作,而虚函数在运行阶段确定类型,所以内联函数不能是虚函数;
- 静态函数:静态函数属于类,不属于对象,静态成员函数没有this指针。
- 友元函数
- 普通函数
析构函数必须为虚函数的情况
在实现多态时,当一个类被作为基类,并且该基类指针操作派生类对象时,在析构时防止只析构基类而不析构派生类的状况发生。
把基类的析构函数设计为虚函数可以在基类的指针指向派生类对象时,用基类的指针删除派生类对象,避免内存泄漏。
比如:
#include <iostream>
using namespace std;
class Base
{
public:
~Base()
{
cout << "delete Base" << endl;
}
};
class Derived:public Base
{
public:
~Derived()
{
cout << "delete Derived" << endl;
}
};
void main()
{
Derived *d = new Derived;
delete d;
}
输出为:
delete Derived
delete Base
但是当使用基类指针操作派生类对象的时候
Base *p = new Derived;
delete p;
输出为:
delete Base
仅仅析构了基类而派生类并没有完成析构,造成内存泄漏
所以需要定义析构函数为虚函数
#include <iostream>
using namespace std;
class Base
{
public:
virtual ~Base()
{
cout << "delete Base" << endl;
}
};
class Derived:public Base
{
public:
virtual ~Derived()
{
cout << "delete Derived" << endl;
}
};
void main()
{
Derived *d = new Derived;
delete d;
}