深入理解C++继承:访问控制、虚函数机制和多重继承详解

一、简介

1 什么是 C++ 继承

C++ 继承是指派生类(子类)从基类(父类)继承属性和行为的过程。我们可以创建一个新的类,该类可以继承另一个类的数据属性和方法。

// 定义一个父类 Person
class Person {
public:
    string name;
    int age;
    string gender;
    void eat() {
        cout << name << " is eating. \n";
    }
};

// 定义一个子类 Student 继承父类 Person 
class Student : public Person {
public:
    string school;
    void study() {
        cout << name << " is studying at " << school << ". \n";
    }
};

在上述代码中,我们定义了一个父类 Person 与一个子类 Student。Student 类继承了 Person 类的属性和方法,包括 name、age、gender 和 eat() 函数。

2 继承的目的和作用

继承的主要目的是代码重用:我们可以重用现有的代码,避免重复编写相同的代码,从而提高生产效率。此外,继承还使得代码更易于管理和维护,因为我们可以通过继承将代码组织成层次结构。

// 使用继承创建 Student 对象
Student myStudent;
myStudent.name = "Tom";
myStudent.age = 18;
myStudent.gender = "Male";
myStudent.school = "ABC University";

myStudent.eat();   // 输出 "Tom is eating."
myStudent.study(); // 输出 "Tom is studying at ABC University."

二、定义

继承是面向对象编程语言中一种非常常见的概念。继承可以让类之间实现代码重用,使得程序更加易于扩展和维护。在 C++ 中可以使用派生类和基类来实现继承。

1 派生类和基类的定义

派生类是指从一个或多个基类中派生出来的类。基类是指在一个类继承另一个类的语法中被继承的那个类。在 C++ 中,我们可以通过如下语法定义一个派生类:

class DerivedClass : access-specifier BaseClass {
public:
    // DerivedClass 的公有成员
protected:
    // DerivedClass 的保护成员
private:
    // DerivedClass 的私有成员
};

在上述代码中DerivedClass是我们新创建的类的名称,access-specifier 可以是 public、protected、或 private。BaseClass 是我们想要从中继承属性和方法的类,我们可以在 access-specifier 后面指定 BaseClass 的访问类型。

代码示例:
定义一个派生类 DerivedPerson,从基类 Person 继承属性和方法 :

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

class Person {
public:
    string name;
    int age;
    void eat() {
        cout << name << " is eating.\n";
    }
};

class DerivedPerson : public Person {
public:
    void work() {
        cout << name << " is working.\n";
    }
};

int main() {
    DerivedPerson p;
    p.name = "John";
    p.age = 30;
    p.eat(); // 输出 "John is eating."
    p.work(); // 输出 "John is working."
    return 0;
}

2 公有继承和私有继承

C++ 中有三种类型的继承:公有继承、保护继承和私有继承。
公有继承是最常用的继承类型之一。在公有继承中派生类获得了基类的公有成员和保护成员,但是基类的私有成员不会被派生类直接访问。

公有继承的用法 示例:
代码定义一个基类 Base 和一个派生类 Derived
在Derived中使用公有继承来继承 Base类的属性和方法。在 main() 函数中创建一个Derived对象d,然后调用 set() 和 print() 函数来设置和获取成员变量 x 的值。需要注意的是此处通过派生类的对象访问了基类的公有成员 x 和 print()

#include <iostream>
using namespace std;

class Base {
public:
    int x;
    void print() {
        cout << "Base class: x = " << x << "\n";
    }
};

class Derived : public Base {
public:
    void set(int n) {
        x = n;
    }
};

int main() {
    Derived d;
    d.set(10);
    d.print(); // 输出 "Base class: x = 10"
    return 0;
}

除了公有继承C++ 中还有保护继承和私有继承。
在保护继承中,基类的公有成员和保护成员都成为了派生类的保护成员。
在私有继承中,基类的公有成员和保护成员都成为了派生类的私有成员。
挑选合适的继承类型对于代码的设计非常重要,在实际编码中需要仔细考虑。

三、访问控制

1 派生类对基类成员的访问权限

在C++中如果一个类派生自另一个类,则派生类可以继承基类的成员变量和成员函数。在继承过程中访问权限是非常重要的一个问题。
有三种访问权限:public、protected 和 private。当一个类派生自另一个类时继承的访问权限可以通过下列三种方式指定:

  • public 继承:基类的 public 成员在派生类中保持 public 属性,基类的 protected 成员在派生类中保持 protected 属性,基类的 private 成员在派生类中不可访问。
  • protected 继承:基类的 public 和 protected 成员在派生类中都被保护起来,不能被外部直接访问。这种方式很少使用,一般只有在继承链比较复杂的时候才使用。
  • private 继承:基类的 public 和 protected 成员在派生类中均变为 private 属性,不能被外部直接访问。这种方式也比较少使用,一般只有在需要从多个类中继承同一个基类的时候才使用。

下面是一个使用 public 继承的例子,演示了派生类对基类成员的访问权限:

在代码中Derived类继承了Base类的公有成员变量 x 和成员函数 print(),以及保护成员变量y
在Derived类的成员函数中可以访问这些成员但是不能直接访问 Base 类的私有成员z

#include <iostream>
using namespace std;

class Base {
public:
    int x;
    void print() {
        cout << "Base class: x = " << x << "\n";
    }
protected:
    int y;
private:
    int z;
};

class Derived : public Base {
public:
    void set(int a, int b, int c) {
        x = a;
        y = b;
        // z = c; 无法访问基类的私有成员
    }
    void print1() {
        cout << "Derived class: x = " << x << "\n";
    }
    void print2() {
        cout << "Derived class: y = " << y << "\n";
    }
    // void print3() { cout << "Derived class: z = " << z << "\n"; } 无法访问基类的私有成员
};

int main() {
    Derived d;
    d.set(1, 2, 3);
    d.print(); // 输出 "Base class: x = 1"
    // cout << d.y; 无法访问基类的保护成员
    d.print1(); // 输出 "Derived class: x = 1"
    d.print2(); // 输出 "Derived class: y = 2"
    return 0;
}

2 如何控制访问权限

在C++中可以使用访问修饰符来控制类的成员的访问权限。访问修饰符可以放到类的定义中的任何位置,而且多次使用访问修饰符是合法的,因为后面的修饰符会覆盖之前的修饰符。

  • public:公有修饰符可以从类的任何地方访问公有成员也可以从派生类的任何地方访问公有成员。
  • protected:保护修饰符只能从类的内部或派生类的内部访问保护成员外部不能访问。
  • private:私有修饰符只能从类的内部访问私有成员派生类的内部不能访问。

下面是一个使用访问修饰符的例子,演示了如何控制成员的访问权限:

在代码中定义了MyClass类和MyDerivedClass类。
在MyClass中使用 public、protected 和 private 三种访问修饰符定义了三个不同的成员变量。在MyDerivedClass中从MyClass中继承了各种成员,并在成员函数中分别访问了这些成员。需要注意的是,在派生类中可以访问基类的 public 和 protected 成员,但是不能访问基类的 private 成员。

#include <iostream>
using namespace std;

class MyClass {
public:
    int a;          // 公有成员变量
protected:
    int b;          // 保护成员变量
private:
    int c;          // 私有成员变量

public:
    void set(int x, int y, int z) {
        a = x;
        b = y;
        c = z;
    }
    void print() {
        cout << "a = " << a << ", b = " << b << ", c = " << c << "\n";
    }
};

class MyDerivedClass : public MyClass {
public:
    void setDerived(int x, int y, int z) {
        // a 和 b 都是 public 和 protected 成员,可以直接访问
        a = x;
        b = y;
        // c 是 private 成员,无法直接访问
        //c = z;
    }
    void printDerived() {
        // a 是 public 成员,可以直接访问
        cout << "a = " << a << "\n";
        // b 是 protected 成员,在派生类的内部可以访问
        cout << "b = " << b << "\n";
        // c 是 private 成员,无法直接访问
        //cout << "c = " << c << "\n";
    }
};

int main() {
    MyClass obj;
    obj.set(1, 2, 3);
    obj.print();

    MyDerivedClass obj2;
    obj2.setDerived(4, 5, 6);
    obj2.printDerived();

    return 0;
}

四、虚函数继承

1 继承中的虚函数机制

在C++ 中如果一个类有虚函数,那么当这个类被继承时在派生类中也会有虚函数表 (V-Table)。当使用基类指针或引用指向一个派生类对象时会根据虚函数表找到正确的虚函数。这种机制被称为动态联编,也就是运行时多态或后期绑定。

代码中定义了一个Base 类和一个 Derived 类。在 Base 类中有一个虚函数 print()
Derived类继承了Base 类并重写了 print() 函数。在 main() 函数中创建了 Derived 类的对象 d并使用 Base 类的指针 b_ptr 指向它,然后调用 print() 函数输出了 "Derived"

#include <iostream>
using namespace std;

class Base {
public:
    virtual void print() {
        cout << "Base\n";
    }
};

class Derived : public Base {
public:
    void print() override {
        cout << "Derived\n";
    }
};

int main() {
    Derived d;
    Base* b_ptr = &d;
    b_ptr->print(); // Derived
    return 0;
}

2 在派生类中重写虚函数

派生类可以通过重写基类中的虚函数来改变其行为

在代码中使用了上一个示例中的 Base 类和 Derived 类并在 Derived 类中重写了print() 函数
main() 函数中通过Base类和Derived类的指针调用 print() 函数。当使用 Base 类指针 b_ptr2 指向 Base 类的对象时,调用的是 Base 类中的 print() 函数,输出了 "Base"。当使用 Base 类指针 b_ptr 指向 Derived 类的对象时,调用的是 Derived 类中的 print() 函数,输出了 "Derived"

#include <iostream>
using namespace std;

class Base {
public:
    virtual void print() {
        cout << "Base\n";
    }
};

class Derived : public Base {
public:
    void print() override {
        cout << "Derived\n";
    }
};

int main() {
    Derived d;
    Base* b_ptr = &d;
    b_ptr->print(); // Derived

    Base base;
    Base* b_ptr2 = &base;
    b_ptr2->print(); // Base
    return 0;
}

五、多重继承

在C++ 中一个派生类可以同时继承多个基类,这种继承方式被称为多重继承。

1 语法实现

多重继承是指一个派生类可以同时继承多个基类,其中每个基类可以有自己的成员函数和成员变量。一个类可以声明对多个基类的继承方式语法如下:

class Derived : access-specifier Base1, access-specifier Base2, ... {
    // Derived class members and functions
};

其中,access-specifier 指定了基类成员在派生类中的访问权限,可以用 publicprotectedprivate 指定。如果没有指定,则默认为 private

2 派生类和基类成员的访问规则

在多继承中派生类对象同时包含多个基类对象的数据成员和成员函数。根据继承方式的不同,基类成员的访问规则也会有所不同。

2.1 共同基类的继承

如果多个基类都派生自同一个基类,那么这个基类的数据成员和成员函数只会在派生类中出现一次。在访问这个基类的成员时,将使用这个共同基类的数据成员和成员函数。

代码示例:
在代码中创建了 AnimalMammalDogCatPersianCat 类。
MammalCat 都是直接继承自 AnimalDogPersianCat 分别是直接继承自 MammalCat。在 PersianCat 类中同时使用了 CatMammal 作为基类

main() 函数中创建了一个 PersianCat 对象,并调用了它的 show_info() 函数。在这个函数中调用了 AnimalMammalCat 这三个基类的一些成员函数,它们分别是在不同的层级中定义的。由于它们都继承自共同的基类 Animal,因此在访问这个基类的成员时,它们都使用同一个 Animal 对象的数据成员和成员函数。在控制台输出中可以看到不同的动物都调用了正确的成员函数

#include <iostream>
using namespace std;

class Animal {
public:
    void eat() {
        cout << "Animal is eating\n";
    }
};

class Mammal : public Animal {
public:
    void run() {
        cout << "Mammal is running\n";
    }
};

class Dog : public Mammal {
public:
    void bark() {
        cout << "Dog is barking\n";
    }
};

class Cat : public Animal {
public:
    void meow() {
        cout << "Cat is meowing\n";
    }
};

class PersianCat : public Cat, public Mammal {
public:
    void show_info() {
        eat(); // 使用Animal中的eat()函数
        run(); // 使用Mammal中的run()函数
        meow(); // 使用Cat中的meow()函数
        cout << "A Persian cat\n";
    }
};

int main() {
    PersianCat persian_cat;
    persian_cat.show_info(); 
    return 0;
}

2.2 不同基类的继承

当派生类继承了多个不同的基类时,会出现基类成员的二义性,需要使用作用域解析运算符来解决。

代码示例:
代码中创建了 ABC 类。C 类同时继承自 AB。在 C 类的 show_info() 函数中,我们使用作用域解析运算符 :: 来指定调用哪个基类的 func() 函数。在 main() 函数中,我们创建了 C 对象,并调用了它的 show_info() 函数。在控制台输出中可以看到它分别调用了两个基类的 func() 函数。

#include <iostream>
using namespace std;

class A {
public:
    void func() {
        cout << "class A" << endl;
    }
};

class B {
public:
    void func() {
        cout << "class B" << endl;
    }
};

class C : public A, public B {
public:
    void show_info() {
        A::func();
        B::func();
    }
};

int main() {
    C c;
    c.show_info(); // class A \n class B
    return 0;
}

六、小结回顾

C++中访问控制通过 public、protected 和 private 关键字来实现。public 表示公开的可以被任何地方访问。protected 表示受保护的只有本类和其子类可以访问。private 表示私有的只有本类可以访问。

C++中的虚函数继承可以实现多态性。子类可以重载父类的虚函数,并且子类的对象可以看做是父类的对象从而实现多态性。

多重继承是 C++ 中的一个重要特性允许一个类从多个基类中继承属性和方法。在实现上如果一个类同时继承了多个基类就可能出现菱形继承问题。为了避免这种问题C++ 提供了虚继承来解决。虚继承可以实现让派生类只继承一份基类的数据成员,从而避免数据冗余和数据不一致的问题。

  • 10
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

格林希尔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值