类的继承

前言
类的继承是面向对象编程(OOP)的核心特性之一,核心价值在于 代码重用逻辑分层,通过抽取不同对象的共性抽象为父类,子类基于父类扩展特有属性和方法,减少重复代码并提升框架稳定性。


类继承的核心概念

定义

类的继承是对对象概念的 纵向抽象模拟:将不同对象的共性属性/方法抽离为 父类(基类,Base Class),再通过 派生 生成 子类(派生类,Derived Class),子类自动拥有父类的非私有成员,同时可扩展自身特有成员。

核心优势

  • 代码重用:无需重复编写父类已定义的共性逻辑
  • 逻辑清晰:构建对象间的层级关系,符合现实世界认知
  • 易于维护:修改父类共性逻辑时,所有子类自动受益

经典示例(现实世界抽象)

哺乳动物的继承关系图

示例逻辑:猫、狗均属于哺乳动物,共性(体温、体重)抽象到 Mammal 父类,子类仅关注特有行为(喵喵叫、汪汪叫)。


继承的基本语法

语法格式

// 父类定义
class 父类名 {
    // 成员属性 + 成员方法
};

// 子类继承语法:class 子类名 : 继承方式 父类名
class 子类名 : 继承方式 父类名 {
public:
    // 子类构造函数(需显式调用父类构造函数)
    子类名(参数列表) : 父类名(父类参数列表) {
        // 子类初始化逻辑
    }
    // 子类特有属性 + 方法
};

实战示例(哺乳动物→猫/狗)

#include <iostream>
using namespace std;

// 父类:哺乳动物
class Mammal {
    float heat;   // 体温(共性属性)
    float weight; // 体重(共性属性)
public:
    // 父类构造函数(默认参数)
    Mammal(float h = 0.0f, float w = 0.0f) : heat(h), weight(w) {}

    // 父类共性方法
    void showInfo() const {
        cout << "体温:" << heat << "℃" << endl;
        cout << "体重:" << weight << "kg" << endl;
    }

    // 【补充】提供访问私有成员的接口(供子类间接访问)
    float getHeat() const { return heat; }
    float getWeight() const { return weight; }
};

// 子类:狗(公有继承 Mammal)
class Dog : public Mammal {
public:
    // 子类构造函数:显式调用父类构造函数
    Dog(float h, float w) : Mammal(h, w) {}

    // 子类特有方法
    void bark() const {
        cout << "汪汪!" << endl;
    }
};

// 子类:猫(公有继承 Mammal)
class Cat : public Mammal {
public:
    // 子类构造函数:显式调用父类构造函数
    Cat(float h, float w) : Mammal(h, w) {}

    // 子类特有方法
    void miaow() const {
        cout << "喵喵!" << endl;
    }
};

// 测试代码
int main() {
    Dog dog(38.5f, 20.0f);
    cout << "=== 狗的信息 ===" << endl;
    dog.showInfo(); // 继承自父类的方法
    dog.bark();     // 子类特有方法

    Cat cat(38.7f, 5.0f);
    cout << "\n=== 猫的信息 ===" << endl;
    cat.showInfo(); // 继承自父类的方法
    cat.miaow();    // 子类特有方法

    return 0;
}

三种继承方式与访问控制

继承方式决定了父类成员在子类中的 访问权限,核心是控制子类及子类对象对父类成员的访问范围。

访问权限基础(父类成员本身的权限)

权限修饰符 父类内部 子类内部 类外部(对象) 比喻(便于理解)
private 父亲的日记(仅自己看)
protected 家族祖传手艺(父子可用,外人不可)
public 父亲的代步车(所有人可用)

. 公有继承(public inheritance)

核心逻辑

  • 表达 is-a 关系(子类是父类的一种),最常用、最符合现实逻辑
  • 父类成员权限在子类中 保持不变(除 private 成员不可直接访问)

语法规则

  1. 父类 private 成员:子类不可直接访问,需通过父类提供的 public 接口间接访问
  2. 父类 protected 成员:子类中仍为 protected(子类内可访问,对象不可访问)
  3. 父类 public 成员:子类中仍为 public(子类内、对象均可访问)

代码示例

#include <iostream>
using namespace std;

// 父类
class Base {
private:
    int a; // 私有成员:仅父类内部访问
protected:
    int b; // 保护成员:父类+子类内部访问
public:
    int c; // 公有成员:任意位置访问

    // 父类构造函数
    Base(int a, int b, int c) : a(a), b(b), c(c) {}

    // 提供访问私有成员a的公有接口
    int getA() const { return a; }
};

// 子类:公有继承父类
class Derived : public Base {
public:
    // 子类构造函数:显式调用父类构造函数
    Derived(int a, int b, int c) : Base(a, b, c) {
        cout << "子类构造完成" << endl;
    }

    // 子类内部访问父类成员
    void showMembers() {
        // cout << a; // 错误:无法直接访问父类private成员
        cout << "父类private成员a(通过接口):" << getA() << endl;
        cout << "父类protected成员b:" << b << endl; // 正确:子类内可访问
        cout << "父类public成员c:" << c << endl;   // 正确:子类内可访问
    }
};

// 测试
int main() {
    Derived d(10, 20, 30);
    d.showMembers(); // 子类内访问父类成员

    // 子类对象访问父类成员
    // cout << d.a; // 错误:private成员不可访问
    // cout << d.b; // 错误:protected成员不可通过对象访问
    cout << "子类对象访问父类public成员c:" << d.c << endl; // 正确

    return 0;
}

典型应用场景

  • 猫 is-a 哺乳动物、汽车 is-a 交通工具、单选框 is-a 按钮
  • 所有能对父类执行的操作,子类均可执行(如哺乳动物能维持体温,猫也能)

保护继承(protected inheritance)

核心逻辑

  • 语法允许,但 极少使用(不符合现实逻辑)
  • 父类所有非 private 成员,在子类中均变为 protected(最高权限为 protected

语法规则

  1. 父类 private 成员:子类不可直接访问
  2. 父类 protected 成员:子类中仍为 protected
  3. 父类 public 成员:子类中变为 protected(对象不可访问)

代码示例

#include <iostream>
using namespace std;

class Base {
private:
    int a;
protected:
    int b;
public:
    int c;
};

// 保护继承
class Derived : protected Base {
public:
    void f() {
        // a = 100; // 错误:无法访问父类private成员
        b = 100; // 正确:子类内可访问protected成员
        c = 100; // 正确:父类public成员在子类中变为protected,子类内可访问
    }
};

int main() {
    Derived d;
    // d.a = 100; // 错误:private成员不可访问
    // d.b = 100; // 错误:protected成员不可通过对象访问
    // d.c = 100; // 错误:父类public成员已变为protected,对象不可访问
    return 0;
}

私有继承(private inheritance)

核心逻辑

  • 表达 has-a 关系(子类包含父类的实现),极少使用(推荐用「类复合」替代)
  • 父类所有非 private 成员,在子类中均变为 private(最高权限为 private

语法规则

  1. 父类 private 成员:子类不可直接访问
  2. 父类 protected 成员:子类中变为 private(仅子类内可访问)
  3. 父类 public 成员:子类中变为 private(仅子类内可访问)

代码示例

#include <iostream>
using namespace std;

// 父类:轮子(被包含的组件)
class Wheel {
private:
    int radius; // 半径
protected:
    int width;  // 宽度
public:
    int getRadius() const { return radius; }
    Wheel(int r, int w) : radius(r), width(w) {}
};

// 子类:汽车(私有继承轮子,表示“汽车包含轮子”)
class Car : private Wheel {
public:
    Car(int r, int w) : Wheel(r, w) {}

    void showWheelInfo() {
        // cout << radius; // 错误:无法直接访问父类private成员
        cout << "轮子宽度:" << width << endl; // 正确:子类内可访问(已变为private)
        cout << "轮子半径:" << getRadius() << endl; // 正确:通过父类接口访问
    }
};

int main() {
    Car car(18, 255);
    car.showWheelInfo(); // 子类提供接口访问父类实现
    // cout << car.width; // 错误:子类中已变为private,对象不可访问
    // cout << car.getRadius(); // 错误:父类public成员已变为private,对象不可访问
    return 0;
}

注意事项

  • 私有继承的意义:继承父类的实现,但隐藏父类的接口
  • 替代方案:用「类复合」(子类中定义父类对象作为成员)更清晰,避免逻辑混乱
    // 类复合(推荐替代私有继承)
    class Car {
    private:
        Wheel wheel; // 汽车包含轮子对象(has-a)
    public:
        Car(int r, int w) : wheel(r, w) {}
        void showWheelInfo() {
            cout << "轮子半径:" << wheel.getRadius() << endl;
        }
    };
    

三种继承方式权限总结表

父类成员权限 公有继承(public) 保护继承(protected) 私有继承(private)
private 不可直接访问 不可直接访问 不可直接访问
protected 保持 protected 保持 protected 变为 private
public 保持 public 变为 protected 变为 private
继承方式与权限对应关系图

类继承中的构造与析构

构造函数的调用规则

核心逻辑

  • 构造顺序:先构造父类,再构造子类(先有地基,再盖房子)
  • 若父类无默认构造函数(无参/全缺省),子类必须显式调用父类构造函数

代码示例(基础调用)

#include <iostream>
using namespace std;

class Base {
public:
    // 父类构造函数
    Base() { cout << "✅ 父类 Base 构造" << endl; }
};

class Derived : public Base {
public:
    // 子类构造函数
    Derived() { cout << "✅ 子类 Derived 构造" << endl; }
};

int main() {
    Derived d; // 先调用父类构造,再调用子类构造
    // 输出顺序:
    // ✅ 父类 Base 构造
    // ✅ 子类 Derived 构造
    return 0;
}

代码示例(父类无默认构造函数)

#include <iostream>
using namespace std;

class Base {
private:
    int x;
public:
    // 父类无默认构造函数(必须传参)
    Base(int x) : x(x) {
        cout << "✅ 父类 Base 构造(x=" << x << ")" << endl;
    }
};

class Derived : public Base {
private:
    int y;
public:
    // 子类必须显式调用父类构造函数(否则编译错误)
    Derived(int x, int y) : Base(x), y(y) {
        cout << "✅ 子类 Derived 构造(y=" << y << ")" << endl;
    }
};

int main() {
    Derived d(10, 20);
    // 输出顺序:
    // ✅ 父类 Base 构造(x=10)
    // ✅ 子类 Derived 构造(y=20)
    return 0;
}

析构函数的调用规则

核心逻辑

  • 析构顺序:先析构子类,再析构父类(先拆房子,再拆地基)
  • 若子类有动态内存分配,需显式定义析构函数;若涉及多态,父类析构函数需定义为 virtual(避免内存泄漏)

代码示例(基础调用)

#include <iostream>
using namespace std;

class Base {
public:
    Base() { cout << "✅ 父类 Base 构造" << endl; }
    ~Base() { cout << "❌ 父类 Base 析构" << endl; } // 析构函数
};

class Derived : public Base {
public:
    Derived() { cout << "✅ 子类 Derived 构造" << endl; }
    ~Derived() { cout << "❌ 子类 Derived 析构" << endl; } // 析构函数
};

int main() {
    Derived d;
    // 输出顺序:
    // ✅ 父类 Base 构造
    // ✅ 子类 Derived 构造
    // ❌ 子类 Derived 析构
    // ❌ 父类 Base 析构
    return 0;
}

📌 拓展:虚析构函数

当父类指针指向子类对象时,若父类析构函数非虚函数,会导致子类析构函数不被调用,造成内存泄漏:

#include <iostream>
using namespace std;

class Base {
public:
    Base() { cout << "✅ 父类 Base 构造" << endl; }
    virtual ~Base() { cout << "❌ 父类 Base 析构" << endl; } // 虚析构函数
};

class Derived : public Base {
private:
    int* arr; // 动态内存分配
public:
    Derived() {
        arr = new int[10];
        cout << "✅ 子类 Derived 构造" << endl;
    }
    ~Derived() {
        delete[] arr; // 释放动态内存
        cout << "❌ 子类 Derived 析构" << endl;
    }
};

int main() {
    Base* ptr = new Derived(); // 父类指针指向子类对象
    delete ptr; // 若父类析构非虚函数,仅调用父类析构,子类析构不执行(内存泄漏)
    // 输出顺序(虚析构情况下):
    // ✅ 父类 Base 构造
    // ✅ 子类 Derived 构造
    // ❌ 子类 Derived 析构
    // ❌ 父类 Base 析构
    return 0;
}

拓展知识(进阶要点)

继承中的访问控制总结

访问场景 public继承 protected继承 private继承
子类内部访问父类成员 ✅(除private) ✅(除private) ✅(除private)
子类对象访问父类成员 ✅(仅public)
子类的子类访问父类成员 ✅(除private) ✅(除private)
表达的关系 is-a 无明确关系 has-a(不推荐)

is-a 与 has-a 关系深入理解

关系类型 含义 实现方式 示例
is-a 子类是父类的一种 public继承 猫 is-a 哺乳动物
has-a 类包含另一个类的对象 类复合(成员对象) 汽车 has-a 轮子

注意:不要用private继承表达has-a关系,类复合更直观、易维护!

单继承与多继承

单继承(原文档示例)

  • 定义:子类仅继承一个父类
  • 优点:逻辑清晰、无歧义,推荐使用
  • 示例:class Dog : public Mammal { ... }

多继承(拓展)

  • 定义:子类继承多个父类(class 子类 : 继承方式 父类1, 继承方式 父类2 { ... }
  • 优点:可同时重用多个父类的代码
  • 问题:菱形继承(钻石继承)导致的二义性
    [图示位置:菱形继承结构图]

菱形继承问题示例

#include <iostream>
using namespace std;

// 顶层父类
class Animal {
public:
    void eat() { cout << "动物进食" << endl; }
};

// 中间父类1
class Dog : public Animal { ... };

// 中间父类2
class Wolf : public Animal { ... };

// 子类:同时继承Dog和Wolf(菱形继承)
class WolfDog : public Dog, public Wolf { ... };

int main() {
    WolfDog wd;
    // wd.eat(); // 错误:二义性(Dog::eat 和 Wolf::eat 冲突)
    wd.Dog::eat(); // 需显式指定父类,解决二义性
    return 0;
}

解决菱形继承:虚继承(virtual inheritance)

通过 virtual 关键字让中间父类共享顶层父类的实例,避免重复继承:

// 中间父类1:虚继承Animal
class Dog : virtual public Animal { ... };

// 中间父类2:虚继承Animal
class Wolf : virtual public Animal { ... };

int main() {
    WolfDog wd;
    wd.eat(); // 正确:无歧义(仅一个Animal实例)
    return 0;
}

继承与虚函数

  • 虚函数:父类中用 virtual 声明的成员函数,子类可重写(override)
  • 作用:实现多态(父类指针指向子类对象时,调用子类的重写方法)
  • 示例:
#include <iostream>
using namespace std;

class Mammal {
public:
    virtual void makeSound() { cout << "哺乳动物发出声音" << endl; } // 虚函数
};

class Dog : public Mammal {
public:
    void makeSound() override { cout << "汪汪!" << endl; } // 重写虚函数
};

class Cat : public Mammal {
public:
    void makeSound() override { cout << "喵喵!" << endl; } // 重写虚函数
};

int main() {
    Mammal* m1 = new Dog();
    Mammal* m2 = new Cat();
    m1->makeSound(); // 输出:汪汪!(调用子类重写方法)
    m2->makeSound(); // 输出:喵喵!(调用子类重写方法)
    delete m1;
    delete m2;
    return 0;
}

核心要点总结

  1. 继承的核心是 代码重用逻辑分层,优先使用 public 继承(is-a关系)
  2. 继承方式决定父类成员在子类的访问权限,private 成员永远不可被子类直接访问
  3. 构造顺序:先父后子;析构顺序:先子后父,多态场景下父类析构需为虚函数
  4. 避免用 protected/private 继承,has-a关系用「类复合」实现更清晰
  5. 多继承易引发菱形继承问题,可通过「虚继承」解决
  6. 虚函数是实现多态的基础,子类重写时可加 override 关键字明确意图
posted @ 2025-12-24 08:37  Jaklin  阅读(11)  评论(0)    收藏  举报