Skip to content

C++ 继承方式:public, protected, private

1. 课件介绍

  • 授课目标: 详细理解 C++ 的三种继承方式:public、protected、private,了解它们的使用场景和对程序设计的影响。

2. 继承的基本概念

2.1. 继承的定义

  • 定义: 继承是面向对象编程的基石之一,允许一个类从另一个类中继承属性和方法,使得代码可以重用。

  • 继承的意义:

    • 代码重用: 减少重复代码
    • 代码组织: 更易于管理和扩展
    • 多态性: 让不同的子类类型可以以一种统一的方式进行处理(多态)

2.2. 示例代码

cpp
#include <iostream>

class Base {
public:
    void show() {
        std::cout << "Base class!" << std::endl;
    }
};

class Derived : public Base {
    // Inherits show() method from Base
};

int main() {
    Derived d;
    d.show();  // Output: "Base class!"
    return 0;
}

问题场景: 许多子类继承自一个父类,避免了重复实现相同的功能。


3. Public 继承

3.1. Public 继承的定义

  • 定义: 在 public 继承时,基类的所有 public 和 protected 成员在派生类中保持不变。

3.2. Public 继承的特点

  • 访问控制:

    • 基类public成员在派生类中仍为public。
    • 基类protected成员在派生类中仍为protected。
    • 基类的private成员无法在派生类中直接被访问。
  • 使用场景: 当派生类想向外界公开基类的接口时,使用public继承。

3.3. 示例代码

cpp
#include <iostream>

class Base {
public:
    int publicVar;
protected:
    int protectedVar;
private:
    int privateVar;

public:
    Base() : publicVar(1), protectedVar(2), privateVar(3) {}

    void show() {
        std::cout << "Base Public: " << publicVar << ", Protected: " << protectedVar << std::endl;
    }
};

class Derived : public Base {
public:
    void accessBaseMembers() {
        publicVar = 10;  // OK,可以访问
        protectedVar = 20;  // OK,可以访问
        // privateVar = 30;  // 错误,无法访问基类的 private 成员
    }
};

int main() {
    Derived d;
    d.accessBaseMembers();

    d.show();  // Output: Base Public: 10, Protected: 20
    return 0;
}

意义: 在安全性上,public继承保留了对基类数据的直接访问控制,并对外界公开基类的public接口。


4. Protected 继承

4.1. Protected 继承的定义

  • 定义: 在 protected 继承时,基类的所有 public 和 protected 成员在派生类中都变为 protected。

4.2. Protected 继承的特点

  • 访问控制:

    • 基类public成员在派生类中变为protected。
    • 基类protected成员在派生类中仍为protected。
    • 基类的private成员无法在派生类中直接被访问。
  • 使用场景: 当派生类希望进一步封装,并只向后续派生类公开基类的接口时。

4.3. 示例代码

cpp
#include <iostream>

class Base {
public:
    int publicVar;

protected:
    int protectedVar;

private:
    int privateVar;

public:
    Base() : publicVar(1), protectedVar(2), privateVar(3) {}

    void show() {
        std::cout << "Base Public: " << publicVar << ", Protected: " << protectedVar << std::endl;
    }
};

class Derived : protected Base {
public:
    void accessBaseMembers() {
        publicVar = 10;  // OK,作为protected成员访问
        protectedVar = 20;  // OK,可以访问
        // privateVar = 30;  // 错误,无法访问基类的 private 成员
    }

    void showDerived() {
        show();  // 派生类内部依然可以访问基类的public成员函数
    }
};

int main() {
    Derived d;
    d.accessBaseMembers();
    
    // d.publicVar = 100;  // 错误,无法访问继承自 Base 的 publicVar
    d.showDerived();  // Output: Base Public: 10, Protected: 20
    return 0;
}

意义: Protected 继承增强了封装性,使基类的接口在派生类的外部不可见,同时允许进一步继承和扩展。


5. Private 继承

5.1. Private 继承的定义

  • 定义: 在 private 继承时,基类的所有 public 和 protected 成员在派生类中都变为 private。

5.2. Private 继承的特点

  • 访问控制:

    • 基类public成员在派生类中变为private。
    • 基类protected成员在派生类中变为private。
    • 基类的private成员无法在派生类中直接被访问。
  • 使用场景: 当派生类只想使用基类的实现细节,而不希望暴露或继承基类的接口时。

5.3. 示例代码

cpp
#include <iostream>

class Base {
public:
    int publicVar;

protected:
    int protectedVar;

private:
    int privateVar;

public:
    Base() : publicVar(1), protectedVar(2), privateVar(3) {}

    void show() {
        std::cout << "Base Public: " << publicVar << ", Protected: " << protectedVar << std::endl;
    }
};

class Derived : private Base {
public:
    void accessBaseMembers() {
        publicVar = 10;  // OK,作为private成员访问
        protectedVar = 20;  // OK,可以访问
        // privateVar = 30;  // 错误,无法访问基类的 private 成员
    }

    void showDerived() {
        show();  // 内部可以使用基类方法
    }
};

int main() {
    Derived d;
    d.accessBaseMembers();

    // d.publicVar = 100;  // 错误,Derived类对外部隐藏了Base的接口
    d.showDerived();  // Output: Base Public: 10, Protected: 20
    return 0;
}

意义: Private 继承使得基类的接口完全隐藏,从而增强了派生类的封装性,基类的实现方式可以作为派生类的细节不对外暴露。


6. 三种继承方式的对比

6.1. 访问控制对比

继承方式Base public membersBase protected membersBase private members
Publicpublicprotected不可访问
Protectedprotectedprotected不可访问
Privateprivateprivate不可访问

6.2. 使用场景总结

  • Public 继承: 对于父类接口需要保留的情况。
  • Protected 继承: 对于派生类需要进一步继承扩展但不公开接口的情况。
  • Private 继承: 对于完全封装基类而不暴露任何接口的情况。

好的!让我们继续从上面讲解的部分接下来进行总结和实际应用场景的探讨。


6.4. 实际应用场景讨论

1. 接口继承(Public 继承)

  • 应用场景: 继承基类时,子类意图保留基类的接口和行为。例如,在实现一个通用库时,可能会使用 public 继承来创建一组类型,它们都支持一个共同的接口。

  • 示例:

    • 基类 Animal 可以定义行为接口,如 speak()
    • 子类 Dog, Cat 等可以继承 Animal 并实现各自的 speak() 行为。
cpp
class Animal {
public:
    virtual void speak() {
        std::cout << "Animal sound" << std::endl;
    }
};

class Dog : public Animal {
public:
    void speak() override {
        std::cout << "Bark" << std::endl;
    }
};

class Cat : public Animal {
public:
    void speak() override {
        std::cout << "Meow" << std::endl;
    }
};

int main() {
    Animal* p = new Dog();
    p->speak();  // 输出 "Bark"

    p = new Cat();
    p->speak();  // 输出 "Meow"

    return 0;
}

2. 部分封闭设计(Protected 继承)

  • 应用场景: 派生类只希望继承基类的功能,但不希望对外暴露基类的接口。例如,如果用户不希望派生类公开基类的某些属性,而只对进一步的派生类可见。

  • 示例:

    • 基类 Storage 负责底层数据的存储和查询。
    • 派生类 Cache, DBStorage 继承 Storage 并提供不同的存储方式,但不希望用户直接调用原 Storage 的接口。
cpp
class Storage {
protected:
    int size;
public:
    Storage(int s) : size(s) {}
    void read() {
        std::cout << "Read data of size: " << size << std::endl;
    }
};

class Cache : protected Storage {
public:
    Cache(int size) : Storage(size) {}

    void cacheRead() {
        read();  // OK
    }

    // 其他Cache类特有行为
};

int main() {
    Cache c(1024);
    c.cacheRead();  // 正确,通过Cache类的public接口访问
    // c.read();  // 不合法,无法通过Cache访问Storage的public函数
    return 0;
}

3. 完全封闭继承(Private 继承)

  • 应用场景: 子类想要复用基类的实现,但完全不希望暴露基类的接口。常用于“实现继承”,而非“接口继承”。

  • 示例:

    • 基类 Counter 可以简单计数。
    • 子类 SpecialCounter 利用 Counter 实现更特定的功能,但不希望外界知晓它使用了 Counter
cpp
class Counter {
private:
    int count;
public:
    Counter() : count(0) {}
    void increment() {
        ++count;
    }
    int getCount() const {
        return count;
    }
};

class SpecialCounter : private Counter {
public:
    void increase() {
        increment();  // 利用Counter的功能
    }
    int getVal() const {
        return getCount();
    }
};

int main() {
    SpecialCounter sc;
    sc.increase();
    std::cout << "Counter value: " << sc.getVal() << std::endl;  // 正确
    // std::cout << sc.getCount();  // 不合法,无法访问Counter的public成员
    return 0;
}

4. 多继承与菱形问题

  • 应用场景: 在C++多继承特性下,当公共的基类通过不同路径被多次继承时,需要考虑菱形继承引发的问题。通过公共继承树的共享避免冗余。

  • 示例:

    • Animal 是一个公共基类,MammalBird 派生自 AnimalBat 继承自 MammalBird
cpp
class Animal {
public:
    Animal() { std::cout << "Animal constructor" << std::endl; }
    virtual void move() {
        std::cout << "Animal moves" << std::endl;
    }
};

class Mammal : public virtual Animal {
public:
    Mammal() { std::cout << "Mammal constructor" << std::endl; }
};

class Bird : public virtual Animal {
public:
    Bird() { std::cout << "Bird constructor" << std::endl; }
};

class Bat : public Mammal, public Bird {
public:
    Bat() { std::cout << "Bat constructor" << std::endl; }
};

int main() {
    Bat bat;
    bat.move();  // 调用Animal的move,且只构造一次Animal
    return 0;
}

讨论要点:

  • 讨论涉及的多继承特性和虚拟基类的使用方式,通过示例演示如何避免多继承中的冗余继承问题。

课后作业:C++ 继承方式应用

作业目标

  • 理解和运用 publicprotectedprivate 继承方式。
  • 在实际的类设计中选择恰当的继承方式。

作业内容

任务描述

请设计一个简单的类层次结构来表示三种不同类型的账户:账户(Account)、储蓄账户(SavingsAccount)和信用账户(CreditAccount)。要求如下:

  1. 基类 Account

    • 公共属性 balancedouble类型):表示账户余额。
    • 受保护方法 deposit(double amount):将 amount 存入 balance 中。
    • 公共方法 showBalance():输出当前余额。
  2. 派生类 SavingsAccount(使用 public 继承):

    • 受保护属性 interestRatedouble类型):表示利率。
    • 公共方法 applyInterest():根据利率计算利息并将其存入余额。
  3. 派生类 CreditAccount(使用 private 继承):

    • 私有属性 creditLimitdouble类型):表示信用额度。
    • 公共方法 withdraw(double amount):判断余额和 creditLimit 是否足够,并进行取款操作。
  4. 请在 main() 函数中执行以下操作:

    • 创建一个 SavingsAccount 对象,存入1000元,并应用利率调整余额,最后显示余额。
    • 创建一个 CreditAccount 对象,设置信用额度为5000元,尝试取出3000元和6000元,并显示每次取款后的余额。

提示

  • CreditAccount 中,deposit 方法和 balance 属性在 Account 类中是 protectedpublic 的,但由于 private 继承,这些成员在 CreditAccount 中会变为 private,无法直接调用或访问。
  • 请合理设计构造函数初始化类成员。

提供的一份参考如下:

c++
#include <iostream>

class Account {
public:
    double balance;

    Account(double initialBalance) : balance(initialBalance) {}

    void showBalance() const {
        std::cout << "Current balance: " << balance << std::endl;
    }

protected:
    void deposit(double amount) {
        balance += amount;
    }
};

class SavingsAccount : public Account {
protected:
    double interestRate;

public:
    SavingsAccount(double initialBalance, double rate)
        : Account(initialBalance), interestRate(rate) {}

    void applyInterest() {
        double interest = balance * interestRate;
        deposit(interest);
    }
};

class CreditAccount : private Account {
private:
    double creditLimit;

public:
    CreditAccount(double initialBalance, double limit)
        : Account(initialBalance), creditLimit(limit) {}

    void withdraw(double amount) {
        if (balance + creditLimit >= amount) {
            balance -= amount;
            std::cout << "Withdraw " << amount << ". New balance: " << balance << std::endl;
        } else {
            std::cout << "Withdrawal denied! Insufficient funds." << std::endl;
        }
    }

    void showBalance() const {
        Account::showBalance();
    }
};

int main() {
    // 创建一个 SavingsAccount 对象
    SavingsAccount savings(1000.0, 0.05);
    savings.showBalance();
    savings.applyInterest();
    savings.showBalance();

    std::cout << "----------------------" << std::endl;

    // 创建一个 CreditAccount 对象
    CreditAccount credit(2000.0, 5000.0);
    credit.showBalance();
    credit.withdraw(3000.0);
    credit.showBalance();
    credit.withdraw(6000.0);
    credit.showBalance();

    return 0;
}