一、单继承
1、概述
C++最重要的特征是代码重用,通过继承机制可以利用已有的数据类型来定义新的数据类型,新的类不仅拥有旧类的成员,还拥有新定义的成员。
例如一个B类继承于A类,或称从类A派生类B。这样的话,类A成为基类(父类),类B成为派生类(子类)。派生类中的成员,包含两大部分:一类是从基类继承过来的,一类是自己增加的成员。从基类继承过过来的表现其共性,而新增的成员体现了其个性。
2、子类的定义形式
定义形式:
class 父类{};
class 子类:继承方式 父类名
{//新增子类数据
};
继承方式:private protected public(推荐)
公共继承 保持不变,保护继承变保护,私有继承变私有,所有父类私有在子类中不可见。
以public为例:
3、子类的构造析够顺序
class Base
{
public:Base(){cout<<"父类构造函数"<<endl;}~Base(){cout<<"父类析构函数"<<endl;}
};
class Other
{
public:Other(){cout<<"Other构造函数"<<endl;}~Other(){cout<<"Other析构函数"<<endl;}
};
class Son :public Base
{
public:Other ob;
public:Son(){cout<<"子类构造函数"<<endl;}~Son(){cout<<"子类析构函数"<<endl;}
};
void test()
{Son ob;
}
运行结果:
4、子类调用成员对象和父类的有参构造
一般情况下,子类 会自动调用 成员对象和父类的默认构造。
调用成员对象和父类的有参构造:子类 必须使用有参构造和初始化列表
初始化列表时:父类写类名称,成员对象用对象名。
#include<iostream>
#include<string>
using namespace std;
class Base
{
public:int a;
public:Base(){cout << "父类默认构造函数" << endl;}Base(int a){this->a = a;cout << "父类有参构造函数" << endl;}~Base(){cout << "父类析构函数" << endl;}
};
class Other
{
public:int b;
public:Other(){cout << "Other默认构造函数" << endl;}Other(int b){this->b = b;cout << "Other有参构造函数" << endl;}~Other(){cout << "Other析构函数" << endl;}
};
class Son :public Base
{
public:int c;Other ob;
public:Son(){cout << "子类默认构造函数" << endl;}//子类要给成员中b赋值,父类中的a赋值//不能直接赋值,父类会先执行存在,调用父类默认构造Son(int a,int b,int c):Base(a),ob(b)//用初始化列表,父类写类名称 成员对象用对象名{this->c=c;cout << "子类有参构造函数" << endl;}~Son(){cout << "子类析构函数" << endl;}
};
void test()
{Son ob(10,20,30);
}
int main(int argc, char* argv[])
{test();return 0;
}
5、子类和父类同名成员处理
处理方式:加作用域
1、子类和父类 同名成员数据
默认优先访问 子类的同名成员,加父类作用域 访问父类的同名成员。
2、子类和父类 同名成员函数
注:子类会重定义父类的同名函数:
重载:无继承,同一作用域,参数的个数、顺序、类型不同 都可重载
重定义:有继承, 子类 重定义 父类的同名函数(参数可以不同)(非虚函数)
子类一旦 重定义了父类的同名函数(不管参数是否一致),子类中都将屏蔽父类所有的同名函数。必须加父类作用域 访问父类的同名成员。
class Base
{
public:void fun(){cout<<"父类fun函数"<<endl;}void fun(int a){cout<<"父类带一个参数fun函数"<<endl;}void fun(int a,int b){cout<<"父类带两个参数fun函数"<<endl;}
};
class Son :public Base
{
public:void fun(){cout<<"子类fun函数"<<endl;}
};
void test()
{Son ob;//类默认优先访问 子类的同名成员ob.fun();//必须加父类作用域 访问父类的同名成员ob.Base::fun(10);//OB.fun(10);会报错ob.Base::fun(10,20);
}
6、子类不能继承的父类成员
父类的构造、析够、重载赋值运算符operator= 不能被子类 继承。
二、多继承
1、多继承的格式
class 父类1{};
class 父类2{};class 子类:继承方式1 父类1, 继承方式2 父类2
{//新增子类数据
};
子类拥有父类1,父类2的大部分数据
2、多继承中同名成员
三、菱形继承
两个派生类继承同一个基类而又有某个类同时继承者两个派生类,即有公共祖先的继承。这种继承被称为菱形继承,或者钻石型继承。
最底层的子类 数据 会包含多份公共祖先的数据和基类派生的派生类的数据
形象图:
问题:
1、羊继承了动物的数据和函数,鸵同样继承了动物的数据和函数,当草泥马调用函数或者数据时,就会产生二义性,通过加作用域解决。
2、草泥马继承自动物的函数和数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以,通过虚继承解决。
class Animal
{
public:int Data;
};
class Sheep:public Animal{};
class Tuo:public Animal{};
class SheepTuo:public Sheep,public Tuo{};void test()
{SheepTuo ob;//cout<<ob.Data;二义性报错cout<<ob.Sheep::Data<<endl;cout<<ob.Tuo::Data<<endl;
}
四、虚继承
虚继承 解决 菱形继承中 多份公共祖先数据的问题
1、虚继承的概述
子类虚继承父类 子类只会保存一份公共数据。
方式:在继承方式 前加virtual修饰
2、分析虚继承的实现原理(vs端)
从电脑开始中的目录找找到 Developer Command Prompt for VS 2022 并打开
在vs复制完整路径后进入对应文件夹。
dir命令查看文件
导出单个类的布局:
cl /d1 reportSingleClassLayoutAnimal main.cpp
Sheep布局:
Tuo布局:
SheepTuo布局:
虚继承 会在子类中产生 虚基类指针(vbptr) 指向 虚基类表(vbtable), 虚基类表纪录的是 通过该指针访问公共祖先的数据的偏移量。
注意:
1、虚继承只能解决具备公共祖先的多继承所带来的二义性问题,不能解决没有公共祖先的 多继承的。
2、工程开发中真正意义上的多继承是几乎不被使用,因为多重继承带来的代码复杂性远多 于其带来的便利,多重继承对代码维护性上的影响是灾难性的,在设计方法上,任何多继承 都可以用单继承代替