MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C++派生新类步骤对类层次结构的影响

2024-10-167.3k 阅读

C++派生新类步骤概述

在C++编程中,通过派生新类来构建类的层次结构是实现代码复用和功能扩展的重要手段。派生新类意味着创建一个新类,这个新类基于已有的类(称为基类),并继承基类的成员(包括数据成员和成员函数),同时还可以添加新的成员或者重写基类的成员。

派生类的定义语法

派生类的定义语法如下:

class DerivedClassName : access - specifier BaseClassName {
    // 新类的成员声明
};

其中,access - specifier指定了从基类继承成员的访问权限,常见的有publicprotectedprivatepublic继承表示基类的publicprotected成员在派生类中保持原有的访问属性;protected继承表示基类的publicprotected成员在派生类中变为protectedprivate继承表示基类的publicprotected成员在派生类中变为private

派生类的初始化

派生类对象的初始化需要考虑基类的初始化。在派生类的构造函数中,通过初始化列表调用基类的构造函数来完成基类部分的初始化。例如:

class Base {
public:
    Base(int value) : baseValue(value) {}
private:
    int baseValue;
};

class Derived : public Base {
public:
    Derived(int baseVal, int derivedVal) : Base(baseVal), derivedValue(derivedVal) {}
private:
    int derivedValue;
};

在上述代码中,Derived类的构造函数通过初始化列表先调用Base类的构造函数,然后再初始化自身的成员derivedValue

对类层次结构中成员访问权限的影响

不同继承方式下基类成员访问权限变化

  1. public继承public继承中,基类的public成员在派生类中仍然是publicprotected成员仍然是protectedprivate成员不可直接访问。例如:
class Base {
public:
    void publicFunction() {
        std::cout << "Base's public function" << std::endl;
    }
protected:
    void protectedFunction() {
        std::cout << "Base's protected function" << std::endl;
    }
private:
    void privateFunction() {
        std::cout << "Base's private function" << std::endl;
    }
};

class PublicDerived : public Base {
public:
    void callBaseFunctions() {
        publicFunction();
        protectedFunction();
        // privateFunction(); // 编译错误,无法访问基类的private成员
    }
};

PublicDerived类中,可以调用基类的publicFunctionprotectedFunction,但不能调用privateFunction

  1. protected继承 当使用protected继承时,基类的publicprotected成员在派生类中都变为protectedprivate成员同样不可直接访问。例如:
class ProtectedDerived : protected Base {
public:
    void callBaseFunctions() {
        publicFunction();
        protectedFunction();
        // privateFunction(); // 编译错误,无法访问基类的private成员
    }
};

class SubProtectedDerived : public ProtectedDerived {
public:
    void callBaseFunctions() {
        publicFunction(); // 编译错误,publicFunction在ProtectedDerived中是protected
        protectedFunction();
    }
};

ProtectedDerived类中可以调用基类的publicFunctionprotectedFunction,但在SubProtectedDerived类中,由于publicFunctionProtectedDerived中变为protected,所以不能直接调用。

  1. private继承 private继承会使基类的publicprotected成员在派生类中变为privateprivate成员依然不可直接访问。例如:
class PrivateDerived : private Base {
public:
    void callBaseFunctions() {
        publicFunction();
        protectedFunction();
        // privateFunction(); // 编译错误,无法访问基类的private成员
    }
};

class SubPrivateDerived : public PrivateDerived {
public:
    void callBaseFunctions() {
        publicFunction(); // 编译错误,publicFunction在PrivateDerived中是private
        protectedFunction(); // 编译错误,protectedFunction在PrivateDerived中是private
    }
};

PrivateDerived类中可以调用基类的publicFunctionprotectedFunction,但在SubPrivateDerived类中,由于它们在PrivateDerived中变为private,所以都不能直接调用。

访问权限对类层次结构关系的影响

不同的继承方式决定了派生类与基类之间成员访问的紧密程度,进而影响类层次结构的关系。public继承保持了基类与派生类之间的“是一种”(is - a)关系,例如,“猫”是“动物”的一种,“猫”类可以public继承“动物”类,并且可以在合适的场景下使用“猫”对象代替“动物”对象。而protectedprivate继承更多用于实现细节的封装,弱化了这种“是一种”的直观关系,强调派生类对基类实现的复用,而不是类型的替代。

对类层次结构中多态性的影响

静态多态与动态多态在派生类中的体现

  1. 静态多态 静态多态主要通过函数重载和模板来实现。在派生类中,函数重载可以在基类成员函数的基础上进行扩展。例如:
class Base {
public:
    void print(int num) {
        std::cout << "Base: Integer " << num << std::endl;
    }
};

class Derived : public Base {
public:
    void print(double num) {
        std::cout << "Derived: Double " << num << std::endl;
    }
};

在上述代码中,Derived类通过定义与基类print函数不同参数类型的print函数实现了函数重载,这是静态多态的一种表现形式。在编译时,编译器根据调用函数时传递的参数类型来决定调用哪个版本的print函数。

  1. 动态多态 动态多态通过虚函数和指针或引用实现。当基类中的成员函数被声明为虚函数,派生类可以重写该虚函数。通过基类指针或引用调用虚函数时,实际调用的是派生类中重写的版本。例如:
class Base {
public:
    virtual void print() {
        std::cout << "Base's print" << std::endl;
    }
};

class Derived : public Base {
public:
    void print() override {
        std::cout << "Derived's print" << std::endl;
    }
};

void callPrint(Base* basePtr) {
    basePtr->print();
}

在上述代码中,Base类中的print函数被声明为虚函数,Derived类重写了该函数。callPrint函数接受一个Base类指针,当传入Derived类对象的指针时,会调用Derived类中重写的print函数,实现了动态多态。

虚函数重写规则对类层次结构的影响

  1. 函数签名一致性 派生类重写虚函数时,函数签名(包括函数名、参数列表和返回类型)必须与基类中的虚函数一致(返回类型可以是协变的,即派生类重写函数的返回类型可以是基类虚函数返回类型的派生类)。例如:
class Base {
public:
    virtual Base* clone() {
        return new Base();
    }
};

class Derived : public Base {
public:
    Derived* clone() override {
        return new Derived();
    }
};

在上述代码中,Derived类重写clone函数时,返回类型Derived*是基类clone函数返回类型Base*的派生类类型,这是符合协变返回类型规则的。

  1. override关键字 在C++11及以后,使用override关键字可以明确标识派生类中的函数是对基类虚函数的重写。如果不小心写错了函数签名,编译器会报错,有助于发现潜在的错误。例如:
class Base {
public:
    virtual void print() {
        std::cout << "Base's print" << std::endl;
    }
};

class Derived : public Base {
public:
    void print(int num) override { // 编译错误,函数签名与基类不一致
        std::cout << "Derived's print with int " << num << std::endl;
    }
};

正确使用override关键字可以提高代码的可读性和维护性,同时在类层次结构中更清晰地体现虚函数的重写关系。

对类层次结构中内存布局的影响

派生类对象的内存布局

  1. 单一继承下的内存布局 在单一继承的情况下,派生类对象的内存布局是基类部分在前,派生类新增的成员在后。例如:
class Base {
public:
    int baseData;
};

class Derived : public Base {
public:
    int derivedData;
};

在内存中,Derived类对象的布局是先存储baseData,然后存储derivedData。这种布局方式使得派生类对象可以兼容基类对象的访问方式,因为基类部分的内存布局与基类对象本身的布局是一致的。

  1. 多重继承下的内存布局 多重继承时,派生类对象包含多个基类部分。例如:
class Base1 {
public:
    int base1Data;
};

class Base2 {
public:
    int base2Data;
};

class Derived : public Base1, public Base2 {
public:
    int derivedData;
};

在内存中,Derived类对象的布局是先存储Base1类部分(包含base1Data),然后是Base2类部分(包含base2Data),最后是derivedData。多重继承会使内存布局变得复杂,可能导致对象体积增大,同时也增加了访问成员的复杂性,例如可能会出现菱形继承问题。

虚函数表与内存布局

  1. 虚函数表的概念 当类中包含虚函数时,编译器会为该类生成一个虚函数表(vtable)。虚函数表是一个函数指针数组,存储了类中虚函数的地址。每个包含虚函数的类对象都会有一个指向虚函数表的指针(vptr)。

  2. 派生类虚函数表的变化 在派生类中,如果重写了基类的虚函数,虚函数表中相应的函数指针会被替换为派生类重写函数的地址。如果派生类新增了虚函数,虚函数表会在末尾添加新的函数指针。例如:

class Base {
public:
    virtual void virtualFunction() {
        std::cout << "Base's virtual function" << std::endl;
    }
};

class Derived : public Base {
public:
    void virtualFunction() override {
        std::cout << "Derived's virtual function" << std::endl;
    }
    virtual void newVirtualFunction() {
        std::cout << "Derived's new virtual function" << std::endl;
    }
};

Base类对象的虚函数表中,virtualFunction的指针指向Base类的virtualFunction实现。在Derived类对象的虚函数表中,virtualFunction的指针被替换为Derived类的virtualFunction实现,并且在虚函数表末尾添加了newVirtualFunction的函数指针。这种虚函数表的变化机制是实现动态多态的基础,通过对象的虚函数表指针和虚函数表,可以在运行时根据对象的实际类型调用正确的虚函数。

对类层次结构中代码复用与可维护性的影响

代码复用在派生类中的实现

  1. 继承基类成员实现复用 通过派生新类,派生类自动继承基类的成员,从而实现代码复用。例如,在图形绘制的程序中,可以定义一个Shape基类,包含一些通用的属性和方法,如颜色、位置等。然后派生出CircleRectangle等类,这些派生类继承Shape类的属性和方法,减少了重复代码的编写。
class Shape {
public:
    void setColor(const std::string& color) {
        this->color = color;
    }
    std::string getColor() const {
        return color;
    }
private:
    std::string color;
};

class Circle : public Shape {
public:
    Circle(double radius) : radius(radius) {}
    double getRadius() const {
        return radius;
    }
private:
    double radius;
};

在上述代码中,Circle类继承了Shape类的setColorgetColor方法,实现了代码复用。

  1. 重写与扩展实现复用 派生类可以重写基类的成员函数,在保留基类部分功能的基础上进行扩展。例如,Shape类有一个draw虚函数,Circle类重写draw函数来实现圆形的绘制逻辑,同时复用了Shape类的一些属性设置方法。
class Shape {
public:
    virtual void draw() const {
        std::cout << "Drawing a shape" << std::endl;
    }
};

class Circle : public Shape {
public:
    void draw() const override {
        std::cout << "Drawing a circle with color " << getColor() << std::endl;
    }
};

对可维护性的影响

  1. 正向影响 合理的类层次结构通过派生新类可以提高代码的可维护性。当需要修改某个功能时,如果该功能在基类中实现,只需要修改基类的代码,所有派生类都会自动继承修改后的行为。例如,如果要统一修改图形的颜色设置方式,只需要在Shape类中修改setColor函数即可,CircleRectangle等派生类无需额外修改。

  2. 负向影响 然而,如果类层次结构设计不合理,例如过度复杂的多重继承或者虚函数重写关系混乱,会增加代码的维护难度。多重继承可能导致菱形继承问题,使得代码难以理解和调试。虚函数重写如果没有遵循良好的命名和注释规范,也会让代码的调用关系变得模糊,增加维护成本。

综上所述,C++派生新类的步骤对类层次结构在成员访问权限、多态性、内存布局以及代码复用与可维护性等方面都有着深刻的影响。开发者在使用派生类构建类层次结构时,需要充分理解这些影响,以设计出高效、可维护的代码。