c++:类和对象(6),继承

继承是面向对象三大特性之一。

继承的好处

代码重用性:通过继承,子类可以继承父类的属性和方法,从而实现代码的复用。子类不需要从头开始编写相同的代码,而是可以直接使用父类已有的代码,减少了代码的冗余。

继承的基本语法

语法:class 子类:继承方式: 父类

子类  也称  派生类

父类  也称  基类

例子: 

#include 
using namespace std;

// 定义父类
class 形状 
{
public:
    void 显示() 
    {
        cout << "这是一个形状。" << endl;
    }
};

// 定义子类,并继承父类
class 矩形 : public 形状 
{
public:
    void 显示() 
    {
        cout << "这是一个矩形。" << endl;
    }
};

int main() 
{
    矩形 r;
    r.显示();  // 调用子类的显示方法
    r.形状::显示();  // 调用父类的显示方法

    return 0;
}

在这个例子中,Shape类被重命名为形状类,其display方法被重命名为显示方法。矩形类继承自形状类,并重写了显示方法。在main函数中,我们创建了一个矩形对象r,并分别调用了子类和父类的显示方法。运行程序将输出:

这是一个矩形。
这是一个形状。


 继承方式

 父类
          +----------------+
          |                |   
     公有 |   公有成员     |
          |                |   
          +----------------+
          |                |   
     保护 |   保护成员     |
          |                |   
          +----------------+
          |                |   
     私有 |   私有成员     |
          |                |   
          +----------------+
                  ^
                  |
                  |
              继承方式

在这个示例中,我们可以看到:

  • 公有继承:子类不仅继承了父类的公有成员,还继承了父类的保护成员,这两者在子类中仍然是公有的。
  • 私有继承:子类继承了父类的公有成员和保护成员,但在子类中它们变为私有的,不可被外部访问。
  • 保护继承:子类继承了父类的公有成员和保护成员,这些成员在子类中都变为保护的,不可被外部访问。

 优先级:私有>保护>共有(子类共有改不了父;,子类保护将父类共有改为保护,其余不变;子类私有将父类所以的都改为私有)注意:父类私有不可访问。

示例:

#include 

// 父类
class Parent 
{
public:
    void publicMethod() 
    {
        std::cout << "This is a public method in the Parent class." << std::endl;
    }

protected:
    void protectedMethod() 
    {
        std::cout << "This is a protected method in the Parent class." << std::endl;
    }

private:
    void privateMethod() 
    {
        std::cout << "This is a private method in the Parent class." << std::endl;
    }
};

// 公有继承
class PublicChild : public Parent 
{
public:
    void accessBaseClassMembers() 
    {
        publicMethod();       // 可以访问父类的公有成员
        protectedMethod();    // 可以访问父类的保护成员
        //privateMethod();    // 无法访问父类的私有成员
    }
};

// 私有继承
class PrivateChild : private Parent 
{
public:
    void accessBaseClassMembers() 
    {
        publicMethod();       // 可以访问父类的公有成员
        protectedMethod();    // 可以访问父类的保护成员
        //privateMethod();    // 无法访问父类的私有成员
    }
};

// 保护继承
class ProtectedChild : protected Parent 
{
public:
    void accessBaseClassMembers() 
    {
        publicMethod();       // 可以访问父类的公有成员
        protectedMethod();    // 可以访问父类的保护成员
        //privateMethod();    // 无法访问父类的私有成员
    }
};

int main() 
{
    PublicChild publicObj;
    publicObj.accessBaseClassMembers();

    PrivateChild privateObj;
    privateObj.accessBaseClassMembers();

    ProtectedChild protectedObj;
    protectedObj.accessBaseClassMembers();

    return 0;
}

在这个示例代码中,我们定义了一个父类 Parent,其中包括了一个公有成员函数 publicMethod()、保护成员函数 protectedMethod() 和私有成员函数 privateMethod()

然后,我们创建了三个不同的子类 PublicChildPrivateChild 和 ProtectedChild 分别进行公有继承、私有继承和保护继承。

在 accessBaseClassMembers() 方法中,我们可以看到:

  • 公有继承:在 PublicChild 类中,可以直接访问父类的公有成员和保护成员,但无法访问私有成员。
  • 私有继承:在 PrivateChild 类中,可以通过子类的成员函数间接访问父类的公有成员和保护成员,但无法直接访问私有成员。


继承中的对象模型

在单继承(只有一个父类)的情况下,常见的对象模型是使用虚函数表(Virtual Function Table,简称VTable)来实现的。每个类都有一个对应的VTable,其中包含了该类的虚函数的地址。当对象调用虚函数时,实际上是通过VTable来确定要调用的函数。

在多重继承(多个父类)的情况下,对象模型的实现可以有所不同。常见的对象模型有水平继承和垂直继承两种:

  • 水平继承(水平分割):在水平继承中,每个子类都包含其各个父类的成员变量和函数,形成一个扁平的对象。这种模型比较简单,但会导致数据冗余。

  • 垂直继承(垂直分割):在垂直继承中,每个子类只包含其特定父类的成员变量和函数。通过使用指针或引用来引用其他父类的对象。这种模型较为灵活,减少了数据冗余,但需要额外的指针或引用来访问其他父类的成员。


继承中构造和析构顺序

对象的构造顺序:

  • 首先,先调用父类的构造函数,从顶层父类向子类逐级调用,按照继承链的顺序进行。
  • 子类的构造函数在调用父类构造函数后才会执行,子类中先执行自己的构造逻辑。

对象的析构顺序:

  • 先调用子类的析构函数,从最底层的子类向顶层父类逐级调用,按照继承链的逆序进行。
  • 父类的析构函数在子类析构函数执行完毕后才会被调用。

继承中同名成员的处理方式

访问子类同名成员 直接访问即可

#include 

class Parent 
{
public:
    int value = 10;
};

class Child : public Parent 
{
public:
    void printValue() 
    {
        std::cout << "Child value: " << value << std::endl;  // 直接访问子类的同名成员
    }
};

int main() 
{
    Child child;
    child.printValue();

    return 0;
}

输出结果为:Child value: 10

在上述代码中,子类 Child 直接访问了继承自父类 Parent 的同名变量 value


访问父类同名成员 需要加作用域

#include 

class Parent 
{
public:
    int value = 10;
};

class Child : public Parent 
{
public:
    int value = 20;

    void printValues() 
    {
        std::cout << "Child value: " << value << std::endl;                   // 访问子类的同名成员
        std::cout << "Parent value: " << Parent::value << std::endl;         // 访问父类的同名成员
    }
};

int main() 
{
    Child child;
    child.printValues();

    return 0;
}

输出结果为:

Child value: 20
Parent value: 10

在上述代码中,子类 Child 中存在同名成员变量 value,通过 Parent::value 的方式可以访问父类 Parent 中的同名成员变量。


 继承中的同名静态成员处理成员方式

当在继承关系中存在同名的静态成员时,子类会隐藏父类的同名静态成员。访问同名静态成员时,需要使用作用域解析运算符(::)来指明所访问的是哪个类的静态成员。

#include 

class Parent 
{
public:
    static int staticMember;
    static void staticMethod() 
    {
        std::cout << "Parent's staticMethod" << std::endl;
    }
};

int Parent::staticMember = 10;

class Child : public Parent 
{
public:
    static int staticMember;
    static void staticMethod() 
    {
        std::cout << "Child's staticMethod" << std::endl;
    }
};

int Child::staticMember = 20;

int main() 
{
    std::cout << "Parent staticMember: " << Parent::staticMember << std::endl;    // 访问父类的静态成员
    std::cout << "Child staticMember: " << Child::staticMember << std::endl;       // 访问子类的静态成员

    Parent::staticMethod();   // 调用父类的静态方法
    Child::staticMethod();    // 调用子类的静态方法

    return 0;
}

输出结果为:

Parent staticMember: 10
Child staticMember: 20
Parent's staticMethod
Child's staticMethod

在上述代码中,父类 Parent 和子类 Child 都定义了同名的静态成员和静态方法。通过使用作用域解析运算符(::),可以确定所访问的是父类还是子类的同名静态成员。

需要注意的是,子类的同名静态成员会隐藏父类的同名静态成员。这意味着,如果直接通过子类类型的对象访问同名静态成员,实际上会访问的是子类的同名静态成员,而不是父类的同名静态成员。例如:

cpp

Child child; std::cout << "Child staticMember: " << child.staticMember << std::endl; // 访问的是子类的同名静态成员

输出结果为:

Child staticMember: 20

通过子类对象访问同名静态成员将会访问子类的同名静态成员,而不是父类的同名静态成员。


多继承语法

c++允许一个子类继承多个类

语法:class 子类:继承方式 父类1  ,继承方式  父类2

#include 

// 父类1
class Parent1 
{
public:
    void method1() 
    {
        std::cout << "Parent1's method1" << std::endl;
    }
};

// 父类2
class Parent2 
{
public:
    void method2() 
    {
        std::cout << "Parent2's method2" << std::endl;
    }
};

// 子类继承自父类1和父类2
class Child : public Parent1, public Parent2 
{
public:
    void childMethod() 
    {
        std::cout << "Child's method" << std::endl;
    }
};

int main() 
{
    Child child;
    
    child.method1();   // 可以访问从父类1继承来的公有成员
    child.method2();   // 可以访问从父类2继承来的公有成员
    child.childMethod();
    
    return 0;
}

输出结果为:

Parent1's method1
Parent2's method2
Child's method

在上述示例中,子类 Child 继承了 Parent1 和 Parent2 的公有成员,可以直接访问它们的方法。


菱形继承

菱形继承问题是指在面向对象编程中,存在一个类继承了两个不同的父类,而这两个父类又共同继承了同一个父类,从而形成了一个菱形的继承关系。这种继承关系可能导致一些问题。

一个常见的问题是多重继承会导致命名冲突。如果两个父类中有相同的方法或属性,在子类中调用这些方法或属性时就会产生冲突,编译器无法确定具体使用哪个父类的方法或属性。

另一个问题是菱形继承会增加代码的复杂性和混乱度。由于存在多个继承链,子类需要管理不同父类的状态和方法,导致代码难以理解和维护。

在C++中,可以使用虚拟继承来解决菱形继承问题。在定义继承关系时使用关键字"virtual",如下所示:

class A 
{
public:
    // A的成员变量和方法
};

class B : virtual public A 
{
public:
    // B的成员变量和方法
};

class C : virtual public A 
{
public:

    // C的成员变量和方法
};

class D : public B, public C 
{
public:
    // D的成员变量和方法
};

通过使用虚拟继承,B和C类都会继承自A,但A类中的成员只会在D类中保留一份,避免了重复继承和冲突问题。

你可能感兴趣的:(c++,开发语言,算法)