15.1 c++ 继承

第十五章 继承

15.1 继承和派生

15.1.1 为什么需要继承

在这里插入图片描述

提高代码重用,提高开发效率。

15.1.2 继承的基本概念

c++最重要的特征是代码重用,通过继承机制可以利用已有的数据类型来定义新的数据类型,新的类不仅拥有旧类的成员,还拥有新定义的成员。一个B类继承于A类,或称从类A派生类B。这样的话,类A成为基类(父类),类B成为派生类(子类)。派生类中的成员,包含两大部分:一类是从基类继承过来的,一类是自己增加的成员。从基类继承过来的表现其共性,而新增的成员体现了其个性。

在这里插入图片描述

15.1.3 派生类的定义

class 父类{};
class 子类:继承方式 父类名
{
	//新增子类数据
};

继承方式:private protected public(推荐)

在这里插入图片描述

所有父类私有在子类中不可访问,公共继承 保持不变,保护继承变保护,私有继承变私有。

class Base
{
private:
    int a;
protected:
    int b;
public:
    int c;
};
class Son:public Base
{
    //子类中 能访问protected b	public c
public:
    void func(void)
    {
        cout<<b<<c<<endl;
        //cout<<a<<endl;//不可访问
    }
};
int main()
{
    Son ob;
    //cout<<ob.b<<endl;//类外无法访问
    cout<<ob.c<<endl;
}

15.2 继承中的构造和析构

15.2.1 子类的构造析构顺序

在这里插入图片描述

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:
public:
    Son()
    {
        cout<<"Son构造"<<endl;
    }
    ~Son()
    {
        cout<<"Son析构"<<endl;
    }
};
int main()
{
    Son ob;
    return 0;
}

15.2.2 子类调用成员对象、父类的有参构造

子类实例化对象时 会自动调用成员对象、父类的默认构造。

子类实例化对象时|必须死用初始化列表 调用成员对象、父类的有参构造。

初始化列表时:父类写类名称 成员对象用对像名

#include <iostream>
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:
    Other ob;
    int c;
public:
    Son()
    {
        cout<<"Son构造"<<endl;
    }
    //父类写类名称	成员对象用对象名
    Son(int a,int b,int c):Base(a),ob(b)
    {
        this->c = C;
        cout<<"Son有参构造"<<endl;
    }
    ~Son()
    {
        cout<<"Son析构"<<endl;
    }
};
int main()
{
    Son ob(10,20,30);
    return 0;
}

15.3 子类和父类的同名处理

同名成员 最简单 最安全的处理方式:加作用域

15.3.1 子类和父类 同名成员数据

子类默认优先访问 子类的同名成员

必须加父类作用域 访问父类的同名成员

class Base
{
public:
    int a;
public:
    Base(int a)
    {
        this->a = a;
    }
};

class Son:public Base
{
public:
    int a;
    Son(int x, int y):Base(x)
    {
        a = y;
    }
};

int main()
{
    Son ob(10,20);
    //子类默认优先访问 子类的同名成员
    cout<<ob.a<<endl;//10
    //必须加父类作用域 访问父类的同名成员
    cout<<ob.Base::a<<endl;//20
    return 0;
}

15.3.2 子类和父类 同名成员函数

class Base
{
public:
	void fun01()
	{
		cout<<"Base 无参的fun01"<<endl;
	}
};
class Son:public Base
{
public:
    void fun01()
    {
        cout<<"Son 无参的fun01"<<endl;
    }
};
int main()
{
    Son ob;
    //子类默认优先访问 子类的同名成员
    ob.fun01();
    //必须加父类作用域 访问父类的同名成员
    ob.Base::func01();
    return 0;
}

15.3.3 子类重定义 父类的同名函数

重载:无继承,同一作用域,参数的个数不同、顺序不同、类型不同都可重载

重定义:有继承,子类重定义父类的同名函数(参数可以不同)(非虚函数)子类一旦重定义了父类的同名函数(不管参数是否一致),子类中都将屏蔽父类所有的同名函数。

class Base
{
public:
    void fun01()
    {
        cout<<"Base 无参的fun01"<<endl;
    }
    void fun1(int a)
    {
        cout<<"Base 的fun01 int"<<endl;
    }
    void fun01(int a, int b)
    {
        cout<<"Base 的fun01 int int"<<endl;
    }
};

class Son:public Base
{
public:
    void fun01(string a)
    {
        cout<<"Son 的fun01 char"<<endl;
    }
};
int main()
{
    Son ob;
    ob.fun01("hello");
    ob.fun01();
    ob.fun01(10);
    ob.fun01(10,20);
    return 0;
}

在这里插入图片描述

需要将父类的作用域 才能识别 屏蔽的函数

在这里插入图片描述

15.4 子类不能继承父类的成员

不是所有的函数都能自动从基类继承到派生类中。构造函数析构函数用来处理对象的创建和析构操作,构造和析构函数只知道对它们的特定层次的对象做什么,也就是说构造函数和析构函数不能被继承,必须为每一个特定的派生类分别创建。另外**operator=**也不能被继承,因为它完成类似构造函数的行为。也就是说尽管我们知道如何由=右边的对象如何初始化=左边的对象的所有成员,但是这个并不意味着对其派生类依然有效。在继承的过程中,如果没有创建这些函数,编译器会自动生成他们。

15.5 多继承

15.5.1 多继承的概念

我们可以从一个类继承,我们也可以能同时从多个类继承,这就是多继承。但是由于多继承是非常受争议的,从多个类继承可能会导致函数、变量等同名导致较多的歧义。

15.5.2 多继承的格式

class 父类1{};
class 父类2{};
class 子类:继承方式1 父类1, 继承方式2 父类2
{
    //新增子类数据
};

子类就拥有了父类1,父类2的大部分数据。

class Base1
{
public:
    int a;
};
class Base2
{
public:
    int b;
};

class Son:public Base1,public Base2
{
public:

};
int main()
{
    Son ob;
    cout<<ob.a<<" "<<ob.b<<endl;
    return 0;
}

15.5.3 多继承中的同名成员处理

如果多继承中 遇到同名成员 需要加父类作用域解决。

class Base1
{
public:
	int a;
	Base1(int a):a(a){}
};
class Base2
{
public:
	int a;
    Base2(int a):a(a){}
};

class Son:public Base1,public Base2
{
public:
    int a;
    Son(int a, int b, int c):Base1(a),Base2(b),a(c){}
};

int main()
{
    Son ob(10,20,30);
    cout<<ob.a<<endl;//子类a
    cout<<ob.Base1::a<<endl;//Base1 a
    cout<<ob.Base2::a<<endl;//Base2 a
    return 0;
}

15.6 菱形继承

菱形继承:有公共祖先的继承 叫菱形继承。

最底层的子类 数据 会包含多分(公共祖先的数据)

在这里插入图片描述

class Animal
{
public:
    int data;
};
class Sheep :public Animal {};
class Tuo :public Animal {};
class SheepTuo:public Sheep.public Tub{};
int main()
{
    SheepTuo ob;
    memset(&ob,0, sizeof(SheepTuo));
    
    //cout<<ob.data<<endl;//二义性
    cout<<ob.Sheep::data<<endl;
    cout<<ob.Tuo::data<<endl;
}

怎么才能只要公共祖先的一份数据呢?

15.7 虚继承

虚继承 解决菱形中 多份公共祖先素据的问题。

15.7.1 虚继承的方式

在继承方式 前加virtual修饰,子类虚继承父类 子类只会保存一份公共数据。

#include<iostream>
#include<string.h>
using namespace std;
class Animal
{
public:
    int data;
};
class Sheep :virtual public Animal{
};
class Tuo :virtual public Animal {
};
class SheepTuo:public Sheep,public Tuo{};
int main()
{
    SheepTuo ob;
    cout<<ob.data<<endl;
    return 0;
}

15.7.2 虚继承的实现原理(VS有效)

1、打开命令行开发模式

在这里插入图片描述

2、找到类所在的源文件路径

在这里插入图片描述

3、在命令行中导出类的布局

cl /d1 reportSingleClassLayoutAnimal main.cpp

Animal布局:

在这里插入图片描述

Sheep布局:

在这里插入图片描述

Tuo布局:

在这里插入图片描述

SheepTuo布局:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

虚继承 会在子类中产生 虚基类指针(vbptr)指向虚基类表(vbtable),虚基类表记录的是通过该指针访问公共祖先的数据的偏移量。

注意:

​ 虚继承只能解决具备公共祖先的多继承所带来的二义性问题,不能解决没有公共祖先的多继承的。 工程开放中真正意义上的多继承是几乎不被使用,因为多重继承带来的代码复杂性远多于其带来的便利,多重继承对代码维护性上的影响是灾难性的,在设计方法上,任何多继承都可以用单继承代替。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值