C++第十三篇:继承

目录

一、继承的格式

二、继承的实现

三、菱形继承

四、虚继承

在 C++ 中,继承是面向对象编程的三大核心特性之一(另外两个是封装和多态),它允许一个类(子类 / 派生类)继承另一个类(父类 / 基类)的属性和方法,从而实现代码复用和层次化设计。

什么时候使用继承呢?

is a的关系。(就是说子类时父类的一个特殊的类型,就好比”圆形“和“图形”的关系,圆形是归属图形的一种,圆形有图形的特征,需要继承自图形。在好比打王者荣耀时的“韩信”和“英雄”的关系,韩信是一个英雄,它有英雄的基本特征,如血条,蓝条等等。)

存在层次关系和实现多态的场景会使用继承。

在没有必要使用继承时,尽量去减少继承的使用,否则可能会导致类关系复杂,难以梳理。

一、继承的格式

class 派生类名:继承方式 基类名1,继承方式 基类名2,....
{
派生类成员;
};

这里继承的有不同方式,分别有public、protected、private继承,继承方式的作用是限制基类成员在派生类中的访问级别。下面是不同继承下的访问权限:

这张图的意思就是,基类的private成员,不论派生类使用哪种继承,都是不可访问的;基类的protected成员,在protected和public继承下是属于protected的,在private继承下属于private;基类的public的成员在,在三种不用的继承下,分别属于不同的级别。

这里顺便在说一下三种级别的区别:

  • public:类内、派生类内、类外(通过对象)都能访问。
  • protected:仅类内和派生类内可访问,类外不可。
  • private:仅类内可访问,派生类和类外都不可。

二、继承的实现

举例:

#include <iostream>
#include <string>
using namespace std;

// 基类:英雄类
class Hero {
protected:
    string name;       // 英雄名称
    int health;        // 生命值
    int attack;        // 攻击伤害
    float moveSpeed;   // 移动速度
public:
    // 构造函数:初始化英雄基本属性
    Hero(string n, int h, int a, float ms) 
        : name(n), health(h), attack(a), moveSpeed(ms) {
        cout << "英雄 [" << name << "] 已创建!" << endl;
    }
    // 析构函数
     ~Hero() {
        cout << "英雄 [" << name << "] 已销毁!" << endl;
    }
};

// 派生类:韩信(继承自英雄类)
class HanXin : public Hero {
private:
    int skillDamage;   // 技能额外伤害
public:
    // 构造函数
    HanXin() : Hero("韩信", 3800, 180, 4.5), skillDamage(350) {
        cout << "韩信:\"到达胜利之前,无法回头!\"" << endl;
    }
    // 析构函数
    ~HanXin()  {
        cout << "韩信:\"我的心,可不冷...\"" << endl;
    }
};

int main() {
    HanXin hanxin;          // 创建韩信对象
    return 0;
}

结果如下:

这就是一个继承的实例,在运行的结果我们可以看到构造函数的调用顺序,先构造基类,然后是派生类,释放时先调用派生类的析构,再调用基类的析构函数。

这就是一个继承,上面介绍的时候就说了,继承也是为了多态的实现。那下来就来来看看什么事多态。

三、菱形继承

菱形继承是多继承中一种特殊且容易引发问题的场景,因类继承关系图呈菱形而得名。

菱形继承的关系。

  • B 和 C 都继承自 A
  • D 同时继承 B 和 C
  • 此时 D 会间接包含两份 A 的成员(一份来自 B,一份来自 C),可能导致问题。

代码示例:

#include <iostream>
using namespace std;

// 基类 A
class A {
public:
    int a;
    A() : a(10) { cout << "A 构造" << endl; }
};

// 基类 B(继承 A)
class B : public A {
public:
    B() { cout << "B 构造" << endl; }
};

// 基类 C(继承 A)
class C : public A {
public:
    C() { cout << "C 构造" << endl; }
};

// 派生类 D(同时继承 B 和 C)
class D : public B, public C {
public:
    D() { cout << "D 构造" << endl; }
};

int main() {
    D d;
    // cout << d.a << endl;   错误:二义性
    // 必须显式指定来源
    cout << "B::a = " << d.B::a << endl; // 来自 B 继承的 A
    cout << "C::a = " << d.C::a << endl; // 来自 C 继承的 A
    return 0;
}

结果如下:

从结果上发现,这个A被构造了两次,同时在访问数据上,必须要指明数据的来源。这就是菱形继承带来的问题,数据冗余和二义性。

怎么解决呢?这里又有了虚继承。

四、虚继承

虚继承是为了解决菱形继承所带来的数据冗余和二义性。直接看代码:

#include <iostream>
using namespace std;

class A {
public:
    int a;
    A() : a(10) { cout << "A 构造" << endl; }
};

// 虚继承 A
class B : virtual public A {
public:
    B() { cout << "B 构造" << endl; }
};

// 虚继承 A
class C : virtual public A {
public:
    C() { cout << "C 构造" << endl; }
};

class D : public B, public C {
public:
    D() { cout << "D 构造" << endl; }
};

int main() {
    D d;
    // 正常访问:仅一份 A::a
    cout << "d.a = " << d.a << endl; // 10
    return 0;
}

结果如下:

可以看到,A类现在只被创建了一次,同时数据也可以直接访问了。

这个虚继承的思想很好理解。实际就是虚基类(A)的实例只被创建一次,并被所有间接继承它的中间基类(BC)和派生类(D共享。有个虚基类表和虚基类指针的作用是记录这个唯一实例的地址,确保BCD都能通过统一的路径访问A的成员,从而避免数据冗余和二义性。

五、注意事项

对于继承来说,如果基类派生类同时声明了名字相同的成员函数或者成员属性,但是参数和类型不一样,那么这里就要注意的是,派生类中会隐藏基类中的成员函数和成员属性。注意,这不是函数重载,函数重载是在同一作用域的,这里的函数分别在基类和派生类,很明显不是一个作用域。

这里还是举个例子看一下:

#include <iostream>
#include <string>
using namespace std;

// 基类:英雄类
class Hero {
protected:
    string name;       // 英雄名称
public:
    Hero(string heroName) : name(heroName) {}  // 构造函数初始化英雄名称

    void show() {
        cout << "英雄名称: " << name << endl;
    }
};

// 派生类:韩信(继承自英雄类)
class HanXin : public Hero {
private:
    int skillDamage;   // 技能额外伤害
public:
    HanXin():Hero("韩信") { // 调用基类构造函数初始化名称
        
    }

    void show(int damage){
        skillDamage = damage;     // 初始化技能伤害值
        std::cout <<"技能伤害:"<< skillDamage << std::endl; // 返回技能伤害值
    }
};

int main() {
    
    HanXin hanxin;          // 创建韩信对象

    hanxin.show();                    // 调用基类方法显示英雄名称
    hanxin.show(200);         // 调用派生类方法显示技能伤害
    return 0;
}

结果如下:

我们会发现没有匹配的函数,派生类中是有参的show,基类中是无参的show,这里实际就是将基类的成员函数隐藏了,成员属性也是一个道理,这是继承这里需要注意的一个点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值