火龙信奥
  • 首页
  • 课程
  • 题库
  • 打卡
    • 代码对战
    • 快速对战
  • 题单
  • 团队
  • 荣誉墙
  • 商城
  • 登录 / 注册

第十一章德文翻译

作者: 作者的头像   zhong , 时间:2025-09-04 14:04:25 , 所有人可见, 阅读  24

编程导论(2024-2025冬季学期)

第11章:虚方法(Virtuelle Methoden)

课程信息

  • 授课教师:亚历山大·克劳斯博士(Dr.-Ing. Alexander Krause)
  • 所属机构:多特蒙德工业大学(Technische Universität Dortmund)应用信息学12系系统软件工作组
  • 课程链接:https://sys.cs.tu-dortmund.de/de/lehre/ws24/eidp/
  • 联系邮箱:eidp-problems@ls12.cs.tu-dortmund.de

第11章核心内容

  1. 继承中的方法绑定问题(静态绑定 vs 动态绑定)
  2. 虚方法的定义与动态绑定实现
  3. 纯虚方法与抽象类
  4. 虚析构函数(构造函数不可为虚)

1. 继承中的方法绑定问题

1.1 传统继承的局限性:静态绑定

此前学习的继承中,方法与对象的绑定发生在编译期(静态绑定)——即编译时已确定调用哪个类的方法,与运行时对象的实际类型无关。

示例:水果类层次结构(无虚方法)

定义四级类层次:Frucht(水果,父类)→ HFrucht(荚果)/Obst(水果子类,如苹果)→ SFrucht(热带水果,继承自Obst),均重写ausgabe()方法输出类标识和名称。

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

// 父类:Frucht(水果)
class Frucht {
protected:
    string name; // 水果名称
public:
    Frucht(char const* n) : name(n) {}
    // 非虚方法:静态绑定
    void ausgabe() {
        cout << "(F) " << name << endl;
    }
};

// 子类1:HFrucht(荚果,继承Frucht)
class HFrucht : public Frucht {
public:
    HFrucht(char const* n) : Frucht(n) {}
    void ausgabe() {
        cout << "(HF) " << name << endl;
    }
};

// 子类2:Obst(水果子类,如苹果,继承Frucht)
class Obst : public Frucht {
public:
    Obst(char const* n) : Frucht(n) {}
    void ausgabe() {
        cout << "(O) " << name << endl;
    }
};

// 子类3:SFrucht(热带水果,继承Obst)
class SFrucht : public Obst {
public:
    SFrucht(char const* n) : Obst(n) {}
    void ausgabe() {
        cout << "(SF) " << name << endl;
    }
};

测试代码与静态绑定问题

int main() {
    // 1. 直接调用对象方法:输出符合预期
    Frucht ruebe{"萝卜"};    // (F) 萝卜
    HFrucht erbse{"豌豆"};   // (HF) 豌豆
    Obst apfel{"苹果"};      // (O) 苹果
    SFrucht banane{"香蕉"};  // (SF) 香蕉
    ruebe.ausgabe();
    erbse.ausgabe();
    apfel.ausgabe();
    banane.ausgabe();

    // 2. 父类指针指向子类对象:问题出现(静态绑定)
    Obst* a = &apfel;        // 指向Obst对象,输出(O) 苹果(符合预期)
    a->ausgabe();

    Frucht* f = &apfel;      // 指向Obst对象,但调用Frucht::ausgabe()(不符合预期)
    f->ausgabe();            // 输出:(F) 苹果

    Obst* b = &banane;       // 指向SFrucht对象,但调用Obst::ausgabe()(不符合预期)
    b->ausgabe();            // 输出:(O) 香蕉

    return 0;
}

问题核心:父类指针/引用调用方法时,仅根据指针“声明类型”(如Frucht*)绑定方法,而非运行时“实际指向的对象类型”(如Obst/SFrucht)。

1.2 解决方案:动态绑定与虚方法

动态绑定:方法与对象的绑定发生在运行期,根据对象的实际类型确定调用的方法——需通过虚方法(virtual method) 实现。

2. 虚方法的定义与使用

2.1 虚方法的语法

在父类的方法声明前添加virtual关键字,即可将该方法标记为虚方法。子类重写该方法时,无需重复virtual(但添加也可,不影响),自动继承虚属性。

修正示例:将Frucht::ausgabe()设为虚方法

class Frucht {
protected:
    string name;
public:
    Frucht(char const* n) : name(n) {}
    // 虚方法:动态绑定(仅需在父类声明时添加virtual)
    virtual void ausgabe() {
        cout << "(F) " << name << endl;
    }
};

// 子类重写时无需重复virtual(自动继承虚属性)
class Obst : public Frucht {
public:
    Obst(char const* n) : Frucht(n) {}
    void ausgabe() { // 自动为虚方法
        cout << "(O) " << name << endl;
    }
};

class SFrucht : public Obst {
public:
    SFrucht(char const* n) : Obst(n) {}
    void ausgabe() { // 自动为虚方法
        cout << "(SF) " << name << endl;
    }
};

2.2 虚方法的动态绑定效果

重新运行测试代码,结果符合预期——方法调用由对象实际类型决定:

int main() {
    // 父类指针指向子类对象:动态绑定到实际类型的方法
    Frucht* f = &apfel;      // 指向Obst对象,调用Obst::ausgabe()
    f->ausgabe();            // 输出:(O) 苹果

    Obst* b = &banane;       // 指向SFrucht对象,调用SFrucht::ausgabe()
    b->ausgabe();            // 输出:(SF) 香蕉

    return 0;
}

2.3 虚方法的关键规则

  1. 仅需父类声明virtual:子类重写时无需重复,自动继承虚属性(添加virtual仅为代码可读性,无语法意义)。
  2. 动态绑定仅适用于指针/引用:直接使用对象(非指针/引用)时,仍为静态绑定(因对象类型编译期确定)。 ```cpp SFrucht kiwi{"猕猴桃"}; Obst x = kiwi; // 对象拷贝(静态绑定),调用Obst::ausgabe() x.ausgabe(); // 输出:(O) 猕猴桃

Obst& xr = kiwi; // 引用(动态绑定),调用SFrucht::ausgabe() xr.ausgabe(); // 输出:(SF) 猕猴桃 3. **未重写则继承父类实现**:若子类未重写虚方法,将调用最近的父类中的虚方法实现。cpp class X { public: virtual void druck() { cout << "X"; } }; class Y : public X { public: void druck() { cout << "Y"; } // 重写虚方法 }; class Z : public Y {}; // 未重写druck(),继承Y的实现

int main() { X* arr[3] = {new X, new Y, new Z}; for (int i=0; i<3; i++) arr[i]->druck(); // 输出:XYY(动态绑定) return 0; } ```

3. 纯虚方法与抽象类

3.1 纯虚方法的定义

纯虚方法:无具体实现的虚方法,用于强制子类必须重写该方法。
语法:在虚方法声明后添加= 0,且不提供方法实现(父类中仅声明)。

示例:将Frucht::ausgabe()设为纯虚方法

class Frucht {
protected:
    string name;
public:
    Frucht(char const* n) : name(n) {}
    // 纯虚方法:无实现,强制子类重写
    virtual void ausgabe() = 0;
};

3.2 抽象类的特性

  • 抽象类定义:包含至少一个纯虚方法的类称为“抽象类(abstrakte Klasse)”。
  • 核心限制:抽象类无法实例化对象,仅能作为父类被继承(“蓝图类”)。
  • 子类要求:子类必须重写父类的所有纯虚方法,否则子类也会成为抽象类(无法实例化)。

示例:抽象类AusgabeGeraet(输出设备)

class AusgabeGeraet {
protected:
    bool kannFarben; // 是否支持彩色输出
    string data;     // 输出数据
public:
    AusgabeGeraet(bool farbe, string d) : kannFarben(farbe), data(d) {}
    // 普通方法:调用纯虚方法(无需实现纯虚方法即可使用)
    void Drucke() {
        if (kannFarben) {
            FarbDruck(); // 调用纯虚方法
        } else {
            cout << data; // 黑白输出
        }
    }
    // 纯虚方法:强制子类实现彩色输出逻辑
    virtual void FarbDruck() = 0;
};

// 子类1:激光打印机(支持彩色)
class LaserDrucker : public AusgabeGeraet {
public:
    LaserDrucker(bool farbe, string d) : AusgabeGeraet(farbe, d) {}
    // 重写纯虚方法:实现彩色输出
    void FarbDruck() override { // override关键字可选,增强可读性
        cout << "[彩色激光打印] " << data << endl;
    }
};

// 子类2:黑白打印机(不支持彩色,仍需重写纯虚方法)
class SchwarzWeissDrucker : public AusgabeGeraet {
public:
    SchwarzWeissDrucker() : AusgabeGeraet(false, "") {}
    // 重写纯虚方法(虽不支持彩色,仍需满足语法要求)
    void FarbDruck() override {
        cout << "错误:该打印机不支持彩色输出!" << endl;
    }
};

int main() {
    // AusgabeGeraet g; // 错误:抽象类无法实例化
    LaserDrucker ld(true, "报告文档");
    ld.Drucke(); // 输出:[彩色激光打印] 报告文档
    return 0;
}

4. 虚析构函数(构造函数不可为虚)

4.1 构造函数不可为虚的原因

  • 构造函数的作用是初始化对象(分配内存、初始化属性),而虚方法的动态绑定依赖对象的“虚函数表”(运行时确定类型)。
  • 对象创建时,虚函数表尚未初始化,无法实现动态绑定——因此C++禁止将构造函数声明为virtual。

4.2 析构函数必须为虚的场景

当通过父类指针删除子类对象时,若父类析构函数非虚,仅会调用父类析构函数,导致子类的动态内存未释放(内存泄漏)。
解决方案:将父类析构函数声明为虚析构函数,确保运行时调用对象实际类型的析构函数。

示例:非虚析构函数的问题

class Base {
public:
    // 非虚析构函数
    ~Base() {
        cout << "Base析构" << endl;
    }
};

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

int main() {
    Base* b = new Derived; // 父类指针指向子类对象
    delete b; // 仅调用Base析构,Derived的arr未释放(内存泄漏)
    // 输出:Base析构
    return 0;
}

修正:父类析构函数设为虚

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

class Derived : public Base {
    // ... 同前 ...
};

int main() {
    Base* b = new Derived;
    delete b; // 先调用Derived析构,再调用Base析构(正确释放内存)
    // 输出:
    // Derived析构
    // Base析构
    return 0;
}

4.3 最佳实践

  • 父类析构函数优先设为虚:只要类可能被继承,就将其析构函数声明为virtual,避免内存泄漏风险。
  • 子类析构函数无需重复virtual:父类析构函数为虚时,子类析构函数自动继承虚属性(即使不写virtual)。

5. 本章总结

  1. 静态绑定 vs 动态绑定:
  2. 静态绑定:编译期根据指针声明类型绑定方法(传统非虚方法)。
  3. 动态绑定:运行期根据对象实际类型绑定方法(需virtual关键字)。
  4. 虚方法:父类声明virtual,子类重写实现动态绑定,支持多态。
  5. 纯虚方法与抽象类:
  6. 纯虚方法:virtual 方法名(参数) = 0,无实现,强制子类重写。
  7. 抽象类:含纯虚方法的类,无法实例化,仅作父类使用。
  8. 虚析构函数:父类析构函数设为virtual,确保通过父类指针删除子类对象时正确释放资源(构造函数不可为虚)。

—— 本文来自火龙信奥(义乌睿码科技):义乌青少年信息学奥赛与编程教育平台,专注 CSP-J/S、NOIP、GESP 竞赛培训,线上线下融合教学,助力编程升学。网址:hlcoding.com

©2026加盟我们 | 关于我们 | ACM课程 | 常见问题 | 成果墙 | 评测记录 | 浙ICP备2021013995号
在线画图 | OI WIki | 打字练习
火龙信奥
请输入登录信息


请完成安全验证
验证码底图 滑块
向右拖动滑块完成验证
请输入用户名 / 绑定的手机号码



请输入注册信息(手机号验证码注册)





验证码5分钟有效,60秒内不可重复获取,每日最多3次

微信登录

微信登录二维码

正在生成二维码...

账号已过期,请续期。
去续期

绑定手机号

📱

为了更好地保护您的账号安全,享受完整的平台服务

请您尽快绑定手机号码