10分钟理解虚继承与覆盖!从零开始的C++(继承与覆盖)

一、多重继承、钻石继承、虚继承

  1. 多重载继承
    在C++中一个子类可以有多个父类,在继承表中按照顺序继承多个父类中的属性和行为,并按照顺序表中的调用父类的构造函数。
    按照从低到高的地址顺序排列父类,子类中会标记每个父类存储位置。
    当子类指针转换成父类的隐式指针时候,编译器会自动计算父类中的内存所在子类的中的位置,地址会自动进行偏移计算。

  2. 名字冲突
    如果父类中有同名的成员,可以正常继承,但如果直接使用会造成歧意,需要 类名::成员名 进行访问。

  3. 钻石继承
    假有一个类A ,类B继承类A,类C也继承类A ,然后类D继承类B和类C。
    一个子类继承多个父类,这些父类有一个共享的祖先,这种继承叫钻石继承。
    注意:钻石继承不会导致继承错误,但访问祖先类中的成员时每次需要使用 类名::成员名 ,重点是这种继承会造成冗余。

也可以将钻石继承的结构理解成总分总

#include <iostream>
#include <stdio.h>

using namespace std;

class A
{
public:
	int a;
};

class B:public A 
{
};

class C:public A
{
};

class D:public B, public C 
{
};

int main()
{
	cout << sizeof(D) << endl;
}
  1. 虚继承 virtual
    当进行钻石继承时,祖先类中的内容会有冗余,而进行虚继承后,在子类中的内容只会保留一份。
    注意:使用虚继承时子类中会多了一些内容(指向从祖先类继承来的成员)。

下面来看一个虚继承的代码段

class A
{
public:
	int a;
	A(void)
	{
		cout << "A类的构造函数" << endl;
	}
	A(int num)
	{
		cout << "A有参构造" << num << endl;
	}
};

class B:virtual public A
{
public:
	B(void):A(1)
	{
		cout << "B类的构造函数" << endl;
	}
};

class C:virtual public A
{
public:
	C(void):A(2)
	{
		cout << "C类的构造函数" << endl;
	}
};

class D:virtual public B, virtual public C
{
public:
	D(void):A(3)
	{
		cout << "D类的构造函数" << endl;
	}
};

int main()
{
	D d;
	cout << sizeof(B) << endl;
}
  1. 构造函数
    一旦进行了虚继承(钻石)祖先类的构造函数只执行一次,由孙子类直接调用,祖先类的在参构造也需要在孙子类中显式调用。
    !!!这里指的是虚继承的构造函数

  2. 拷贝构造
    在虚拟继承(钻石)中祖先类拷贝构造也由孙子类直接调用,子类中不再调用祖先类的拷贝构造,在手动实现的拷贝构造时(深拷贝),祖先类中的内容也由孙子类负责拷贝,

  3. 同理赋值构造也一样。

二、虚函数、覆盖、多态

  1. 虚函数
    类的成员函数前加 virtual 这种函数就叫做虚函数。

  2. 覆盖
    在子类会覆盖父类的虚函数。

  3. 多态
    当子类覆盖了父类的虚函数时,通过父类指针指向子类对象时,调用虚函数,会根据具体的对象是谁来决定执行谁的函数,这就是多态。

三、覆盖和多态的条件

  1. 覆盖的条件
    必须虚函数
    必须是父子类之间
    函数签名必须相同(参数列表完全一致,const属性也会影响覆盖的结果)。
    返回值必须是同类或父子类类(子类的返回什要能向父类隐式转换)。
    访问属性不会影响覆盖
    常函数属性也会影响覆盖
  2. 重载、隐藏、覆盖(重写)的区别
    重载:同一作用域下的同名函数,函数签名不同,构成重载关系。
    覆盖:必须是虚函数,必须是父子之间,返回值必须是同类或者父子类。
    隐藏:父子类之间的同名成员如果没有形成覆盖,且成通过编译,必定构成隐藏。
三者 作用域 virtual 函数名 形参列表 返回值
重载 相同 可有可无 相同 不同 可同可不同
隐藏 不同 可有可无 相同 可同可不同 可同可不同
覆盖(重写) 不同 必须要有 相同 相同 相同

3、多态的条件

  1. 父子类之间有的函数有覆盖关系。
  2. 父类的指针或引用指向子类对象。
  3. 在构造、析构函数中调用虚函数
    • 在父类的构造函数中调用虚函数,此时子类还没创建完成(回顾构造函数的调用过程),因此只能调用父类的虚函数,而不是覆盖版本的虚函数。
    • 在父类的析构函数中调用虚函数,此时子类已经释放完成,因此只能调用父类的虚函数,而不是覆盖版本的虚函数。

三、纯虚函数和抽象类

  1. 纯虚函数
    在虚函数的声明的后面添加=0,这种虚函数就叫纯虚函数,可以不实现,但如果实现必须在类外(只能在父类的构造函数、析构函数中调用)。
    virtual 返回值 函数名(参数) =0;

  2. 抽象类
    成员函数中有纯虚函数,这种类叫抽象类,抽象类不能实例化(不能创建对象)。
    抽象类必须被继承且纯虚函数被覆盖后,由子类实例化对象。
    如果继承抽象类,但没有覆盖纯虚函数,那么子类也将成为抽象类,不能实例化。

  3. 纯抽象类
    所有成员函数都是纯虚函数,这种只能被继承的类叫纯抽象类。
    这种类一般用来设计接口,这种类在子类被替换后不需要修改或少量的修改即可继续使用。

猜你喜欢

转载自blog.csdn.net/weixin_45050225/article/details/99877688
今日推荐