派生类,继承,虚函数

派生类(derived class)即子类,关系遵循着:父类--子类,基类--派生类

派生不影响基类本身的各种访问权限,该是啥是啥

一、如何创建一个派生类?

class Base{
    //基类
};
class Derived: public Base
{
    //派生类
};

1. 一般格式

class 派生类名∶派生方式 基类名
{
// 派生类新增的数据成员和成员函数
} ;

2. 派生方式

  • 公有派生:public 关键字

  • 私有派生:private 关键字

无论哪种派生方式,基类的private里的东西:

  • 不允许外部函数直接访问

  • 不允许派生类的成员函数访问

  • 可通过基类的public的成员函数访问

公有派生:基类的public在派生类中也都是public的

私有派生:基类的public在派生类中只能是private的

二、私有派生,class a: private A,访问权限

1. 派生类对基类的访问权限分析

基类中public的部分,成为派生类中private的部分

这意味着什么?

派生类的实例对象无法直接调用基类的public中的函数、变量

派生类的实例对象,只能通过自己的成员函数来访问到基类的public中的东西

假设Animal类中的public中有一个Eat函数,如果你想通过一个私有派生的Dog类的实例对象dog来直接使用Eat函数,是非法的。

class Animal:
{
public:
    Eat();
};
class Dog: private Animal
{
    ……
};
Dog dog = Dog();
dog.Eat();//非法

2. 如何设计来保证访问的畅通?

  • 基类的public中的成员函数对自己的private中的东西有访问权。

  • 基类的public成为派生类的private

  • 派生类的public中的成员函数可以访问自己的private,在这之中就包括了基类的public。

那么派生类的public成员函数中,访问基类的public(相当于访问自己的private),进而就“有可能访问到”基类的private。

  • 前提是基类的public中提供了访问基类的private的接口

三、公有派生,class a: public A

基类中的公有成员在派生类中仍是公有成员,外部函数和派生类中的成员函数可直接访问。

  • 派生类以公有派生的方式继承了基类, 并不意味着派生类可以访问基类的私有成员

  • 之前提到了,无论何种继承,基类的private不可以被派生类直接访问,也不可以被外部函数直接访问

1. 同名,覆盖

派生类中声明的函数,与基类中的函数重名,且在派生类的成员函数中使用时,将被覆盖。(可以参考同名变量,局部会覆盖全局)

class X
{
public:
    int f() ;
};
class Y∶public X
{
public:
    int f();
    int g() ;
};
void Y∷g()
{
    f() ;   // 表示被调用的函数是 Y∷f( ), 而不是 X∷f( )
}

派生类的对象,调用这样的同名函数,仍然是覆盖的

Y obj;
obj.f();//调用的是Y::f()

如果不想被覆盖,则需要作用域运算符限定

obj.X::f();

四、保护成员,protected

保护成员可以被派生类的成员函数访问,但是对于外界是隐藏起来的, 外部函数不能访问它。因此,为了便于派生类的访问, 可以将基类私有成员中需要提供给派生类访问的成员定义为保护成员。

1. 保护成员在不同派生类型下的访问权限

  • 公有派生, 基类中的protected在派生类中也为protected

  • 私有派生, 则基类中的protected在派生类中成为private

五、派生类的构造与析构

派生类不能继承基类中的构造函数和析构函数, 需要自行完成。

1. 派生类构造函数、析构函数的执行顺序

通常情况下

  • 派生类对象被创建时,基类的构造函数先执行,再执行派生类的构造函数

  • 派生类对象被析构时,先执行派生类的析构函数,再执行基类的析构函数

遵循所有构造与析构的规则:先构造的后析构,又或者:构造的执行顺序是和析构的顺序相反的

特别的,当派生类中含有对象成员时,构造函数执行顺序:

  • 基类的构造函数

  • 对象成员的构造函数

  • 派生类的构造函数

2. 派生类的构造函数设计规则

派生类不能继承基类中的构造函数和析构函数。

当基类的构造函数没有参数,或没有显式定义构造函数时, 派生类可以不向基类传递参数,甚至可以不定义构造函数。

当基类含有带参数的构造函数时,派生类必须定义构造函数,以提供把参数传递给基类构造函数的途径

  • 派生类构造函数一般格式

派生类构造函数名(参数表) :基类构造函数名( 参数表)
{
/ / …
}
  • 当派生类中含有对象成员时

派生类构造函数名(参数表) :基类构造函数名( 参数表),对象成员名 1 (参数表), …,对象成员名 n (参数表)
{
    // …
}

3. 注意事项

  • 只要基类的构造函数有参数,它的所有派生类都必须定义构造函数,以用于传递参数

  • 如果派生类的基类也是一个派生类, 则每个派生类只需负责其直接基类的构造,依次上溯。

  • 由于析构函数是不带参数的, 在派生类中是否要定义析构函数与它所属的基类无关,故基类的析构函数不会因为派生类没有析构函数而得不到执行, 它们各 自是独立的。

六、多重继承

一个派生类继承自多个基类,称为多重继承

一般形式如下

class 派生类名: 派生方式 1 基类名 1, …,派生方式 n 基类名 n
{
// 派生类新增的数据成员和成员函数
};
  • 冒号后面的部分称基类表,各基类之间用逗号分隔。

  • 派生方式 i”( i = 1, 2, …, n )规定了派生类从基类中按什么方式继承: private 或 public

  • 缺省的派生方式是 private

  • 对于不同的基类成员的访问权限,由继承方式决定

1. 多重继承,同名函数带来的潜在二义性

假设类C,同时继承了A、B,A和B中都有一个 f() ,C的对象不可以直接使用 f(),需要作用域限定符

2. 多重继承的构造函数与析构函数,执行顺序

派生类构造函数名(参数表) :基类 1 构造函数名 (参数表), …,基类 n 构造函数名(参数表)
{
    // …
};

多重继承的构造函数的执行顺序与单继承构造函数的执行顺序相同, 也是遵循先执行基类的构造函数,再执行对象成员的构造函数, 最后执行派生类构造函数的原则。

在多个基类之间, 则严格按照派生类声明时从左到右的顺序来排列先后。而析构函数的执行顺序则刚好与构造函数的执行顺序相反

什么是声明顺序?

class Dog : public Animal, public mamal {
public:
    //Dog() : Animal(), mamal() {
        //cout << "Dog constructor" << endl;
    //}
    Dog() : mamal(), Animal() {
        cout << "Dog constructor" << endl;
    }
};

以上代码的第一行从左到右即为声明顺序。而不是构造函数冒号右边的先后

七、钻石继承,纯虚类(抽象类),虚继承

此节讨论,派生类在多重继承时,继承了派生自同一基类的几个类,可能会产生的二义性问题

1. 虚函数,纯虚函数

 

  • 虚函数:类中,声明前添加了virtual,并且实现了的成员函数

class Base{
public:
    Base(){}
    virtual void print(){
        printf("this is a virtual func");
    }
    ~Base(){}
};
  • 纯虚函数,声明为virtual,且后缀为0

class Base {
public:
    virtual void print() const = 0;
    virtual ~Base() = 0;
};

纯虚函数也称为“抽象函数”或“抽象方法”,一种特殊的虚函数,在类中没有给出该虚函数的实现,它的实现留给该基类的派生类去做

且派生类一定要实现该函数,否则无法通过编译

2. 具体类,虚类,纯虚类,接口

  • 具体类:没有纯虚函数(可以有虚函数)

  • 虚类:含虚函数,不含纯虚函数

  • 纯虚类(抽象类):含有纯虚函数,且含有非虚函数。

  • 接口:只有纯虚函数

纯虚类(抽象类)和接口都是不可以创建实例对象的

3. 虚继承,概念,写法,内存分析,访问权限

虚继承主要用于解决菱形继承问题,解决一个孙子继承爷爷类两次的问题。防止出现二义性。

虚继承的结果是:当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次,也就是说,基类只保留一次

也就是说,在不正常的菱形继承中,基类被保留了不止一次。


写法,在继承方式前添加virtual关键字

class base
{
};
class base_1: virtual public base
{
    
};
class base_2: virtual public base
{
    
};
class base_3: virtual base_1, base_2
{
    
};

八、总结:一个派生类的构造函数的执行顺序

  • 是否有虚继承?如果有虚继承+菱形继承(因为用到虚继承时多半要涉及菱形继承),先执行虚基类(爷爷)的构造函数,再按照类声明顺序执行虚基类的派生类(爸爸)的构造函数

  • 虚继承进行完后,是否继承了其他独立的基类?如果有,则继续按照类声明的相对顺序执行他们的构造函数

  • 最后,执行当前这个派生类的构造函数(孙子)

  • 在以上过程中,类中如果有对象成员,则要先进行对象成员的构造。

#include <iostream>
using namespace std;
class life {
public:
    life() {
        cout << "life" << endl;
    }
};
class Animal: virtual public life{
public:
    Animal() {
        cout << "Animal constructor" << endl;
    }
    //virtual void print() = 0;
};
class mamal: virtual public life{
public:
    mamal()
    {
        cout << "mamal constructor"  << endl;
    }
};
class A {
public:
    A() {
        cout << "A construtor" << endl;
    }
};
class Dog : public A, virtual public mamal, virtual public Animal {
//此类声明的输出结果为:life, mamal, Animal, A, Dog
    
//class Dog : public A, virtual public Animal, virtual public mamal
//此类声明的输出结果为: life, Animal, mamal, A, Dog
public:
    //Dog() : Animal(), mamal() {
        //cout << "Dog constructor" << endl;
    //}
    Dog() : mamal(), Animal() {
        cout << "Dog constructor" << endl;
    }//
    void print() {
        cout << "dog";
    }
};

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值