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

第十章德文翻译

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

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

第10章:继承(Vererbung)

课程信息

  • 授课教师:亚历山大·克劳斯博士(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

第10章核心内容

  1. 类的核心目标回顾与继承的必要性
  2. 继承的基本概念与语法
  3. 访问权限控制(public/protected/private)
  4. 继承中的构造函数与方法重写
  5. 多类继承示例与循环依赖解决方案
  6. 抽象类与纯虚方法

1. 继承的背景与必要性

1.1 类的核心目标回顾

此前学习的类的核心特性包括: - 数据封装:隐藏私有属性,仅通过方法访问 - 方法封装:将类专属函数与数据绑定 - 构造函数/析构函数:对象的初始化与资源释放 - 方法与构造函数重载:同一名称多实现(参数签名不同) - 运算符重载:自定义类的运算符行为

新增核心目标:高效代码复用——通过继承(Vererbung) 实现。

1.2 传统代码复用的问题

以“银行账户”建模为例: - 已定义Konto(账户)类,包含账户基本属性(如余额)和方法(如存款、取款) - 需新增Girokonto(支票账户)类,其功能基于Konto,但需额外添加“透支额度”属性和“转账”方法

传统方案:复制Konto代码→重命名为Girokonto→修改/新增属性和方法
缺陷: 1. 维护成本高:若Konto需新增功能(如“利息计算”),需手动同步到Girokonto 2. 错误率高:复制过程中易遗漏或修改错误 3. 代码冗余:重复存储相同属性和方法的实现

2. 继承的基本概念与语法

2.1 继承的核心定义

  • ** Oberklasse(父类/基类)**:提供基础属性和方法的类(如Konto、KString)
  • ** Unterklasse(子类/派生类)**:从父类继承并扩展功能的类(如Girokonto、KVersalien)
  • 继承关系:子类自动获得父类的属性和方法,可新增属性/方法,或重写(override)父类方法
  • 对象兼容性:子类的每个实例同时也是父类的实例(如“支票账户”一定是“账户”)

2.2 C++继承语法

// 父类定义
class 父类名 {
    // 父类属性与方法
};

// 子类定义:public继承(最常用)
class 子类名 : public 父类名 {
    // 子类新增的属性与方法
    // 可重写父类的方法(需保持参数签名一致)
};

示例:定义KVersalien(大写字符串类)继承自KString(基础字符串类)

// 父类:基础字符串类
class KString {
protected:
    char* mString;  // 字符串指针(指向char数组)
    int mLength;    // 字符串长度(不含'\0')
public:
    KString(char const* s);       // 构造函数
    ~KString();                    // 析构函数
    void setString(char const* s); // 设置字符串
    char* getString();             // 获取字符串
    int length();                  // 获取长度
    void print();                  // 输出字符串
};

// 子类:大写字符串类(继承自KString)
class KVersalien : public KString {
private:
    void up();  // 私有方法:将字符串转为大写
public:
    KVersalien(char const* s);     // 子类构造函数
    void setString(char const* s); // 重写父类方法
    void print();                  // 重写父类方法
};

3. 继承中的访问权限控制

3.1 三种访问权限的核心区别

父类的属性/方法通过不同访问权限修饰,决定子类和外部的访问范围:

访问权限修饰符 父类内部 子类内部 类外部(其他对象)
public ✅ 可访问 ✅ 可访问 ✅ 可访问
protected ✅ 可访问 ✅ 可访问 ❌ 不可访问
private ✅ 可访问 ❌ 不可访问 ❌ 不可访问

3.2 访问权限的实际意义

  1. private(私有):完全隐藏的“私有信息”
    父类仅自身可访问,子类和外部均无法直接操作(如KString中若mString为private,KVersalien无法直接修改字符串内容)。

  2. protected(受保护):仅家族可见的“家族信息”
    父类和其子类(及子类的子类)可访问,外部不可访问(如KString的mString设为protected,KVersalien可直接操作字符串,而外部对象无法直接修改)。

  3. public(公开):对外暴露的“接口信息”
    父类、子类、外部均能访问(如KString的length()方法,所有对象均可调用)。

3.3 继承方式对访问权限的影响

C++支持三种继承方式(public/protected/private),会改变父类成员在子类中的访问权限:

父类成员权限 public继承(常用) protected继承(少见) private继承(极少见)
public 子类中仍为public 子类中变为protected 子类中变为private
protected 子类中仍为protected 子类中仍为protected 子类中变为private
private 子类中不可访问 子类中不可访问 子类中不可访问

说明: - public继承:保持父类接口开放性,是绝大多数场景的选择(如“支票账户”继承“账户”,外部仍可调用存款方法)。 - protected/private继承:限制子类对外暴露父类接口,仅用于特殊封装场景(如隐藏父类的部分公开方法)。

4. 继承中的构造函数与方法重写

4.1 子类构造函数的执行顺序

子类对象创建时,先执行父类构造函数,再执行子类构造函数,确保父类属性先初始化。

示例:KVersalien(子类)构造函数

// KVersalien.h(子类声明)
class KVersalien : public KString {
private:
    void up(); // 转为大写的私有方法
public:
    KVersalien(char const* s); // 子类构造函数
    // 重写父类方法
    void setString(char const* s);
    void print();
};

// KVersalien.cpp(子类实现)
#include <cctype> // 包含toupper()等字符处理函数
#include "KVersalien.h"

// 子类构造函数:初始化列表调用父类构造函数
KVersalien::KVersalien(char const* s) : KString(s) {
    up(); // 父类构造后,执行子类逻辑(转为大写)
}

// 私有方法:将字符串转为大写
void KVersalien::up() {
    for (int i = 0; i < mLength; ++i) {
        if (islower(mString[i])) { // 判断是否为小写字母
            mString[i] = toupper(mString[i]); // 转为大写
        }
    }
}

执行流程: 1. 调用KString(s)(父类构造函数):分配内存,复制字符串到mString,设置mLength。 2. 执行KVersalien构造函数体:调用up(),将mString中的小写字母转为大写。

4.2 方法重写(Override)

子类可重写父类的方法(需保持参数签名完全一致,区别于“重载”),实现子类专属逻辑。

示例1:重写setString方法

// 父类KString的setString方法(基础实现)
void KString::setString(char const* s) {
    int newLength = strlen(s); // 计算新字符串长度
    if (newLength > mLength) { // 若新长度超过当前容量,重新分配内存
        delete[] mString;
        mString = new char[newLength + 1]; // +1存储'\0'
    }
    strcpy(mString, s); // 复制新字符串
    mLength = newLength; // 更新长度
}

// 子类KVersalien的setString方法(重写,新增大写转换)
void KVersalien::setString(char const* s) {
    KString::setString(s); // 先调用父类方法,完成基础字符串设置
    up(); // 子类扩展逻辑:转为大写
}

示例2:重写print方法

// 父类KString的print方法
void KString::print() {
    cout << mString << endl;
}

// 子类KVersalien的print方法(重写,添加类标识)
void KVersalien::print() {
    cout << "KVersalien::print: " << mString << endl;
}

4.3 继承的方法复用

子类自动继承父类未重写的方法,无需重复实现。
例如KVersalien未重写length()和getString(),直接使用KString的实现:

int main() {
    KVersalien v1{"test"};
    v1.print(); // 调用子类重写的print:输出"KVersalien::print: TEST"
    cout << "长度: " << v1.length() << endl; // 调用父类length:输出4
    return 0;
}

5. 多类继承示例与循环依赖解决

5.1 多类继承场景:人员建模

构建三级类层次结构: - 父类Person:存储性别无关的属性(姓名、父母)和方法(设置父母、输出信息)。 - 子类Mann(男性):继承Person,新增“妻子”属性和“结婚”方法。 - 子类Frau(女性):继承Person,新增“丈夫”属性和“结婚”方法。

类声明(含循环依赖问题)

#include <string>
using namespace std;

// 问题:Person引用Mann和Frau,Mann/Frau又继承Person,形成循环依赖
class Person {
private:
    string* mName;    // 姓名(动态分配内存)
    Frau* mMutter;    // 母亲(指向Frau对象)
    Mann* mVater;     // 父亲(指向Mann对象)
public:
    Person(string name);
    string Name();
    void setzeVater(Mann* m); // 设置父亲
    void setzeMutter(Frau* f); // 设置母亲
    void ausgabe(char const* s); // 输出信息
    ~Person();
};

class Frau : public Person {
private:
    Mann* mEhemann; // 丈夫
public:
    Frau(string name);
    Frau(Person* p); // 从Person对象构造
    void heirateMann(Mann* m); // 与男性结婚
    Mann* EhefrauVon(); // 获取丈夫
};

class Mann : public Person {
private:
    Frau* mEhefrau; // 妻子
public:
    Mann(string name);
    Mann(Person* p); // 从Person对象构造
    void heirateFrau(Frau* f); // 与女性结婚
    Frau* EhemannVon(); // 获取妻子
};

5.2 循环依赖的解决方案:前向声明

问题原因:Person类中使用Frau和Mann作为指针类型,但这两个类尚未定义,编译器无法识别。
解决方案:使用前向声明(Vorwärtsdeklaration),提前告知编译器“该类存在”。

修正后的类声明

#include <string>
using namespace std;

// 前向声明:告知编译器Frau和Mann是类(无需知道具体实现)
class Frau;
class Mann;

// 父类Person:此时Frau和Mann已声明,可作为指针类型使用
class Person {
private:
    string* mName;
    Frau* mMutter;
    Mann* mVater;
public:
    Person(string name);
    string Name();
    void setzeVater(Mann* m);
    void setzeMutter(Frau* f);
    void ausgabe(char const* s);
    ~Person();
};

// 子类Frau和Mann:此时Person已完整定义,可正常继承
class Frau : public Person {
private:
    Mann* mEhemann;
public:
    Frau(string name);
    Frau(Person* p);
    void heirateMann(Mann* m);
    Mann* EhefrauVon();
};

class Mann : public Person {
private:
    Frau* mEhefrau;
public:
    Mann(string name);
    Mann(Person* p);
    void heirateFrau(Frau* f);
    Frau* EhemannVon();
};

5.3 核心方法实现

1. 父类Person的实现

// 构造函数:初始化姓名和父母指针
Person::Person(string name) : mMutter(nullptr), mVater(nullptr) {
    mName = new string{name}; // 动态分配字符串内存
}

// 析构函数:释放姓名内存
Person::~Person() {
    delete mName;
}

// 获取姓名
string Person::Name() {
    return *mName;
}

// 设置父亲
void Person::setzeVater(Mann* m) {
    mVater = m;
}

// 设置母亲
void Person::setzeMutter(Frau* f) {
    mMutter = f;
}

// 输出信息(递归输出父母信息)
void Person::ausgabe(char const* s) {
    cout << s << " 姓名: " << *mName << endl;
    if (mMutter != nullptr) {
        cout << s << " 母亲: ";
        mMutter->ausgabe(""); // 递归调用母亲的ausgabe
    }
    if (mVater != nullptr) {
        cout << s << " 父亲: ";
        mVater->ausgabe(""); // 递归调用父亲的ausgabe
    }
}

2. 子类Frau和Mann的实现

// Frau构造函数1:直接初始化
Frau::Frau(string name) : Person(name), mEhemann(nullptr) {}

// Frau构造函数2:从Person对象构造
Frau::Frau(Person* p) : Person(p->Name()), mEhemann(nullptr) {}

// Frau:与男性结婚
void Frau::heirateMann(Mann* m) {
    mEhemann = m;
}

// Frau:获取丈夫
Mann* Frau::EhefrauVon() {
    return mEhemann;
}

// Mann构造函数1:直接初始化
Mann::Mann(string name) : Person(name), mEhefrau(nullptr) {}

// Mann构造函数2:从Person对象构造
Mann::Mann(Person* p) : Person(p->Name()), mEhefrau(nullptr) {}

// Mann:与女性结婚
void Mann::heirateFrau(Frau* f) {
    mEhefrau = f;
}

// Mann:获取妻子
Frau* Mann::EhemannVon() {
    return mEhefrau;
}

完整举例

// 使用常用标准库(推荐替代 #include <bits/stdc++.h>)
#include <iostream>
#include <string>

using namespace std;

// 前向声明(Forward Declarations)
// 因为 Frau 和 Mann 在 Person 中作为指针使用,只需声明,无需包含完整定义
class Frau;
class Mann;

// =========================
// 基类:Person(人)
// 包含姓名、父母指针,是 Mann 和 Frau 的父类
// =========================
class Person {
private:
    string* mName;     // 姓名(动态分配)
    Frau* mMutter;     // 指向母亲的指针
    Mann* mVater;      // 指向父亲的指针

public:
    // 构造函数:初始化姓名,父母初始为空
    Person(string name);

    // 获取姓名(注意:函数名应为 Name,与声明一致)
    string Name();

    // 设置父母
    void setzeVater(Mann* vater);   // 设置父亲
    void setzeMutter(Frau* mutter); // 设置母亲

    // 输出信息
    void ausgabe(char const* prefix);

    // 析构函数:释放动态分配的 mName
    ~Person();
};

// -------------------------
// Person 成员函数实现
// -------------------------

// 构造函数:使用初始化列表设置 mName, mMutter, mVater
Person::Person(string name)
    : mMutter(0)  // 母亲指针初始为空
    , mVater(0)   // 父亲指针初始为空
{
    mName = new string(name); // 动态分配字符串
}

// 析构函数:释放 mName
Person::~Person() {
    delete mName;
}

// 获取姓名(注意:函数名是 Name,不是 name)
string Person::Name() {
    return *mName;
}

// 设置父亲
void Person::setzeVater(Mann* vater) {
    mVater = vater;
}

// 设置母亲
void Person::setzeMutter(Frau* mutter) {
    mMutter = mutter;
}



// =========================
// 派生类:Frau(女性)
// 继承自 Person,并添加配偶(丈夫)指针
// =========================
class Frau : public Person {
private:
    Mann* mEhemann; // 丈夫指针

public:
    // 构造函数:通过姓名创建女性
    Frau(string name);

    // 构造函数:通过 Person 对象创建女性(复制姓名)
    Frau(Person* p);

    // 结婚:嫁给某个男性
    void heirateMann(Mann* mann);

    // 获取丈夫(谁是我丈夫?)
    Mann* EhemannVon(); // 更正:应为“EhemannVon”表示“丈夫是谁”
};

// -------------------------
// Frau 成员函数实现
// -------------------------

Frau::Frau(string name)
    : Person(name)      // 调用基类构造函数
    , mEhemann(0) // 初始无配偶
{}

Frau::Frau(Person* p)
    : Person(p->Name()) // 复制传入 Person 的姓名
    , mEhemann(0)
{}

// 女性嫁给某个男性
void Frau::heirateMann(Mann* mann) {
    mEhemann = mann;
}

// 返回丈夫指针
Mann* Frau::EhemannVon() {
    return mEhemann;
}

// =========================
// 派生类:Mann(男性)
// 继承自 Person,并添加配偶(妻子)指针
// =========================
class Mann : public Person {
private:
    Frau* mEhefrau; // 妻子指针

public:
    // 构造函数:通过姓名创建男性
    Mann(string name);

    // 构造函数:通过 Person 对象创建男性
    Mann(Person* p);

    // 结婚:娶某个女性
    void heirateFrau(Frau* frau);

    // 获取妻子(谁是我妻子?)
    Frau* EhefrauVon(); // 更正:应为“EhefrauVon”表示“妻子是谁”
};

// -------------------------
// Mann 成员函数实现
// -------------------------

Mann::Mann(string name)
    : Person(name)       // 调用基类构造函数
    , mEhefrau(nullptr)  // 初始无配偶
{}

Mann::Mann(Person* p)
    : Person(p->Name())  // 复制姓名
    , mEhefrau(nullptr)
{}

// 男性娶某个女性
void Mann::heirateFrau(Frau* frau) {
    mEhefrau = frau;
}

// 返回妻子指针
Frau* Mann::EhefrauVon() {
    return mEhefrau;
}

// =========================
// 全局函数:结婚与离婚
// =========================

// 让一位女性和一位男性结婚
// 返回 true 表示成功,false 表示失败(如已婚)
bool verheirate(Frau* f, Mann* m) {
    // 检查指针是否为空
    if (f == 0 || m == 0)
        return false;

    // 检查是否已婚:女性已有丈夫 或 男性已有妻子
    if (f->EhemannVon() != nullptr || m->EhefrauVon() != nullptr)
        return false;

    // 双向绑定:女性嫁给男性,男性娶女性
    f->heirateMann(m);
    m->heirateFrau(f);

    return true;
}

// 输出信息,带前缀(如 "A: ")
void Person::ausgabe(char const* prefix) {
    cout << prefix << "Name: " << *mName << endl;

    // 如果母亲存在,递归输出母亲信息
    if (mMutter != nullptr) {
        cout << prefix << "Mutter: ";
        mMutter->ausgabe(""); // 递归调用,输出母亲姓名
    }

    // 如果父亲存在,递归输出父亲信息
    if (mVater != nullptr) {
        cout << prefix << "Vater: ";
        mVater->ausgabe(""); // 递归调用,输出父亲姓名
    }
}

// 让一对夫妻离婚
// 返回 true 表示成功
bool trenne(Frau* f, Mann* m) {
    // 检查指针有效性
    if (f == nullptr || m == nullptr)
        return false;

    // 检查是否确实是夫妻
    if (f->EhemannVon() != m || m->EhefrauVon() != f)
        return false;

    // 解除婚姻关系:设为 nullptr
    f->heirateMann(nullptr);
    m->heirateFrau(nullptr);

    return true;
}

// =========================
// 主函数:测试
// =========================
int main() {
    // 创建两个人:Anton(男)和 Bertha(女)
    Mann* Anton = new Mann("Anton");
    Frau* Bertha = new Frau("Bertha");

    // 创建 Carl,他是 Anton 和 Bertha 的儿子
    Mann* Carl = new Mann("Carl");
    Carl->setzeVater(Anton);   // 设置父亲
    Carl->setzeMutter(Bertha); // 设置母亲

    // 输出 Anton 和 Bertha 的信息
    Anton->ausgabe("A: ");
    Bertha->ausgabe("B: ");
    cout << endl;

    // 输出 Carl 的信息(会递归输出父母)
    Carl->ausgabe("\tC: ");
    cout << endl;

    // 两人结婚
    verheirate(Bertha, Anton);
    cout << "Hochzeit: "
         << Bertha->Name() << " + " << Anton->Name()
         << endl << endl;

    // 验证婚姻关系
    if (Bertha->EhemannVon() != nullptr) {
        cout << Bertha->Name() << "s Ehemann ist " 
             << Bertha->EhemannVon()->Name() << endl;
    }

    if (Anton->EhefrauVon() != nullptr) {
        cout << Anton->Name() << "s Ehefrau ist " 
             << Anton->EhefrauVon()->Name() << endl;
    }

    // 最后释放所有动态分配的内存
    delete Anton;
    delete Bertha;
    delete Carl;

    return 0;
}

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

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


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



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





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

微信登录

微信登录二维码

正在生成二维码...

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

绑定手机号

📱

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

请您尽快绑定手机号码