「C++系列」继承

一、继承

C++ 中的继承(Inheritance)是面向对象编程(OOP)的一个核心概念,它允许我们定义一个基于现有类的新类,即子类(派生类)可以继承父类(基类)的属性和方法。继承机制支持代码的重用,并且使得类的层次结构更加清晰。

1. 基本概念

  • 基类(Base Class)
    基类是一个可以被其他类继承的类。它通常包含了一些通用的属性和方法,这些属性和方法对于所有派生类都是共有的或者可以被派生类所利用。在定义基类时,你可以指定哪些成员是公有的(public)、保护的(protected)还是私有的(private),这将影响派生类对这些成员的访问权限。
  • 派生类(Derived Class)
    派生类是通过继承一个或多个基类而创建的类。派生类可以继承基类的公有成员和保护成员(私有成员总是不可继承的),但它可以添加自己的新成员或重写从基类继承来的成员。在派生类中,你可以通过指定继承类型(公有继承、私有继承或保护继承)来控制基类成员的访问权限。
  • 继承:派生类获得基类属性和方法的过程。
class Base {
    
      
public:  
    void show() {
    
      
        // 显示信息的函数  
    }  
};  
  
// 公有继承  
class Derived : public Base {
    
      
    // 派生类可以添加新的成员或重写Base的成员  
};  
  
// 私有继承(较少使用)  
class DerivedPrivate : private Base {
    
      
    // Base的公有和保护成员在DerivedPrivate中变为私有  
};  
  
// 保护继承(也很少使用)  
class DerivedProtected : protected Base {
    
      
    // Base的公有和保护成员在DerivedProtected中变为保护  
};

2. 继承类型

C++ 支持多种继承类型,但最常见的是公有继承(Public Inheritance)和私有继承(Private Inheritance)。

①公有继承(Public Inheritance)

公有继承是最常用的继承类型,它表示一种“是一个”的关系。在公有继承中,基类的公有成员和保护成员在派生类中保持原有的访问级别(公有成员仍为公有,保护成员仍为保护),但基类的私有成员在派生类中仍然不可访问。

#include <iostream>

class Base {
    
    
public:
    void showPublic() {
    
     std::cout << "Public function in Base\n"; }
protected:
    void showProtected() {
    
     std::cout << "Protected function in Base\n"; }
private:
    void showPrivate() {
    
     std::cout << "Private function in Base\n"; } // 不可在Derived中访问
};

class Derived : public Base {
    
    
public:
    void test() {
    
    
        showPublic();  // 可访问
        showProtected(); // 可访问
        // showPrivate(); // 编译错误,不可访问
    }
};

int main() {
    
    
    Derived d;
    d.showPublic();  // 错误,showPublic() 是Derived的私有成员函数,这里不能直接访问
    d.test();  // 正确,通过test()访问Base的公有和保护成员
    return 0;
}

// 注意:main中直接调用d.showPublic()是错误的,因为showPublic()不是Derived的公有成员函数。
// 正确的调用方式是通过Derived的公有成员函数(如test())来间接调用Base的公有成员函数。

②私有继承(Private Inheritance)

私有继承中,基类的公有成员和保护成员在派生类中都会变成私有成员。这意味着派生类的对象不能直接访问这些成员,甚至不能通过派生类的成员函数来访问(除非这些函数是友元函数或在派生类内部定义)。

#include <iostream>

class Base {
    
    
public:
    void show() {
    
     std::cout << "Function in Base\n"; }
};

class Derived : private Base {
    
    
public:
    void accessBase() {
    
    
        show(); // 可访问,因为show()在Derived内部
    }
    // void exposeShow() { return show(); } // 错误,show()现在是Derived的私有成员
};

int main() {
    
    
    Derived d;
    // d.show(); // 错误,show()是Derived的私有成员函数
    d.accessBase(); // 正确,通过Derived的公有成员函数访问
    return 0;
}

③保护继承(Protected Inheritance)

保护继承中,基类的公有成员和保护成员在派生类中都会变成保护成员。这意味着派生类的对象不能直接访问这些成员,但派生类的派生类(即孙子类)可以访问。

#include <iostream>

class Base {
    
    
public:
    void show() {
    
     std::cout << "Function in Base\n"; }
};

class Derived : protected Base {
    
    
    // ...
};

class GrandDerived : public Derived {
    
    
public:
    void test() {
    
    
        show(); // 可访问,因为show()在GrandDerived中是保护的
    }
};

int main() {
    
    
    GrandDerived gd;
    gd.test(); // 正确,通过GrandDerived的公有成员函数访问
    // gd.show(); // 错误,show()是GrandDerived的保护成员函数
    return 0;
}

3. 继承的语法

继承的语法很简单,只需在派生类的定义中使用冒号:后跟基类名即可。

class Base {
    
    
public:
    void show() {
    
    
        // ...
    }
};

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

在上面的例子中,Derived类公有继承自Base类,因此Derived类的对象可以使用show()成员函数。

4. 构造函数和析构函数

在C++中,构造函数和析构函数是特殊的成员函数,它们分别在对象创建和销毁时自动调用。构造函数用于初始化对象,而析构函数用于清理对象占用的资源。以下是构造函数和析构函数的基本案例。

①构造函数案例

#include <iostream>
using namespace std;

class MyClass {
    
    
private:
    int value;

public:
    // 构造函数
    MyClass(int val) : value(val) {
    
    
        cout << "构造函数被调用,value = " << value << endl;
    }

    // 默认构造函数(如果需要的话)
    // MyClass() : value(0) {
    
    
    //     cout << "默认构造函数被调用,value = " << value << endl;
    // }

    // 其他成员函数...
};

int main() {
    
    
    MyClass obj1(10); // 调用带参数的构造函数
    // MyClass obj2; // 如果定义了默认构造函数,这行代码会调用它;否则,编译错误

    return 0;
}

在这个例子中,MyClass类有一个私有成员变量value和一个公有的构造函数,该构造函数接受一个整型参数来初始化value。当在main函数中创建MyClass的实例obj1时,会调用构造函数,并输出相应的信息。

②析构函数案例

析构函数在对象生命周期结束时自动调用,用于执行清理工作,如释放分配的内存等。析构函数的名称由波浪线~后跟类名组成,且不接受任何参数和返回类型(连void都不写)。

#include <iostream>
using namespace std;

class MyClass {
    
    
private:
    int* pValue;

public:
    // 构造函数
    MyClass(int val) {
    
    
        pValue = new int(val); // 分配内存
        cout << "构造函数被调用,pValue = " << *pValue << endl;
    }

    // 析构函数
    ~MyClass() {
    
    
        delete pValue; // 释放内存
        cout << "析构函数被调用" << endl;
    }

    // 其他成员函数...
};

int main() {
    
    
    MyClass obj(10); // 调用构造函数
    // 当obj的生命周期结束时(离开作用域),析构函数会自动调用

    // 注意:在main函数结束时,所有局部变量(包括obj)的生命周期都会结束,
    // 因此它们的析构函数会被自动调用。但在这个简单的例子中,你可能看不到析构函数的输出,
    // 因为程序会立即退出。为了看到输出,可以在析构函数中添加一个暂停点(如cin.get()),
    // 或者在析构函数中写入日志文件。

    // cin.get(); // 暂停程序,以便查看析构函数的输出(可选)

    return 0;
}

在这个例子中,MyClass类有一个指向整型的指针pValue作为成员变量。构造函数使用new操作符为pValue分配内存,并初始化它指向的值。析构函数则使用delete操作符释放pValue指向的内存。当MyClass的实例objmain函数的末尾离开其作用域时,其析构函数会自动被调用,执行清理工作。

5. 访问控制

继承中的访问控制规则决定了派生类如何访问基类的成员。这些规则基于基类中成员的访问级别和继承的类型。

访问控制是通过关键字publicprotectedprivate来实现的,这些关键字用于指定类成员的访问级别。下面是一个包含这些访问控制关键字的C++案例,展示了它们是如何工作的。

#include <iostream>
using namespace std;

class MyClass {
    
    
private:
    // 私有成员,只能在类内部访问
    int privateVar;

protected:
    // 保护成员,可以在类内部、派生类(子类)中访问,但不能通过类的对象直接访问
    int protectedVar;

public:
    // 公有成员,可以通过类的对象直接访问
    int publicVar;

    // 构造函数
    MyClass(int pv, int ptv, int ppv) : publicVar(pv), protectedVar(ptv), privateVar(ppv) {
    
    
        // 构造函数体可以访问所有成员
    }

    // 公有成员函数,可以访问类的所有成员(包括私有和保护成员)
    void showVars() {
    
    
        cout << "publicVar = " << publicVar << endl;
        cout << "protectedVar = " << protectedVar << endl; // 公有成员函数可以访问保护成员
        // cout << "privateVar = " << privateVar << endl; // 这行代码通常不会出现在这里,因为私有成员不应直接暴露给外部
    }

    // 尝试从外部访问私有和保护成员(这将导致编译错误)
    // void externalAccess() {
    
    
    //     MyClass obj;
    //     cout << obj.privateVar << endl; // 编译错误:私有成员不能从类外部访问
    //     cout << obj.protectedVar << endl; // 编译错误(在严格意义上),但技术上可以通过派生类访问
    // }

    // 友元函数或友元类(示例略)可以访问类的私有和保护成员
};

// 派生类示例(用于展示保护成员的访问)
class DerivedClass : public MyClass {
    
    
protected:
    // 可以在派生类中访问基类的保护成员
    void accessProtectedVar() {
    
    
        cout << "Derived class accessing protectedVar = " << protectedVar << endl;
        // cout << "Derived class cannot access privateVar = " << privateVar << endl; // 编译错误
    }
};

int main() {
    
    
    MyClass obj(1, 2, 3); // 创建MyClass对象,初始化公有、保护和私有成员
    obj.showVars(); // 调用公有成员函数显示变量值

    // 尝试直接访问私有和保护成员(这将导致编译错误)
    // cout << obj.privateVar << endl; // 编译错误
    // cout << obj.protectedVar << endl; // 编译错误(虽然技术上可以通过派生类访问,但这里直接访问会失败)

    // 派生类使用示例
    DerivedClass derivedObj(4, 5, 6);
    derivedObj.accessProtectedVar(); // 派生类成员函数访问基类的保护成员

    return 0;
}

在这个案例中,MyClass类有三个成员变量(公有、保护和私有各一个)以及一个构造函数和一个公有成员函数showVars。构造函数用于初始化所有成员变量,而showVars函数则展示了如何在类的成员函数内部访问所有类型的成员变量。

main函数中展示了如何创建MyClass的对象,并通过其公有成员函数访问公有成员变量。尝试直接访问私有和保护成员变量会导致编译错误,因为它们的访问权限被限制在类内部(对于私有成员)和类内部及派生类内部(对于保护成员)。

此外,还展示了如何通过派生类DerivedClass访问基类MyClass的保护成员变量。这说明了保护成员在继承关系中的访问特性。

6. 继承与多态

继承通常与多态(Polymorphism)一起使用,以实现接口的重用和动态绑定。多态允许通过基类的指针或引用来调用派生类对象的成员函数,这增加了程序的灵活性和可扩展性。

继承和多态是面向对象编程的两大核心概念。继承允许我们定义一个类(称为派生类或子类)来继承另一个类(称为基类或父类)的属性和方法。多态则允许我们通过基类的指针或引用来调用派生类的成员函数,这种调用在运行时确定实际调用的函数版本,这通常通过虚函数实现。

下面是一个C++中继承与多态的简单案例:

#include <iostream>
using namespace std;

// 基类
class Animal {
    
    
public:
    // 虚函数
    virtual void speak() const {
    
    
        cout << "Some animal sound" << endl;
    }

    // 虚析构函数,确保通过基类指针删除派生类对象时正确调用析构函数
    virtual ~Animal() {
    
    }
};

// 派生类 Dog
class Dog : public Animal {
    
    
public:
    void speak() const override {
    
     // 使用override关键字明确表示这是一个重写函数
        cout << "Woof!" << endl;
    }
};

// 派生类 Cat
class Cat : public Animal {
    
    
public:
    void speak() const override {
    
    
        cout << "Meow!" << endl;
    }
};

// 多态演示函数
void makeItSpeak(Animal* animal) {
    
    
    animal->speak(); // 运行时确定调用哪个版本的speak()
}

int main() {
    
    
    Dog myDog;
    Cat myCat;

    // 尝试使用基类指针指向派生类对象
    Animal* myAnimal1 = &myDog; // 向上转型(隐式)
    Animal* myAnimal2 = &myCat;

    // 调用多态函数
    makeItSpeak(myAnimal1); // 输出: Woof!
    makeItSpeak(myAnimal2); // 输出: Meow!

    // 注意:虽然下面的代码在技术上可行,但它违反了多态的初衷
    // 直接通过派生类类型的指针调用函数将不会展示多态性
    myDog.speak(); // 输出: Woof!
    myCat.speak(); // 输出: Meow!

    return 0;
}

在这个例子中,Animal是基类,它有一个虚函数speak()和一个虚析构函数。DogCat是派生自Animal的类,它们各自重写了speak()函数。makeItSpeak函数接受一个指向Animal的指针,并调用该指针所指向对象的speak()函数。由于speak()是虚函数,因此调用哪个版本的speak()是在运行时根据指针实际指向的对象类型确定的,这展示了多态性。

请注意,虽然可以通过派生类类型的指针直接调用speak()函数(如myDog.speak();),但这并不会展示多态性,因为编译器在编译时就已经确定了调用哪个函数版本。多态性的真正展示是在通过基类指针或引用调用虚函数时。

此外,虚析构函数的使用是处理通过基类指针删除派生类对象时资源释放问题的关键。如果基类析构函数不是虚函数,那么通过基类指针删除派生类对象时,只会调用基类的析构函数,而不会调用派生类的析构函数,这可能导致资源泄漏。
在这里插入图片描述

二、相关链接

  1. Visual Studio Code下载地址
  2. Sublime Text下载地址
  3. 「C++系列」C++简介、应用领域
  4. 「C++系列」C++ 基本语法
  5. 「C++系列」C++ 数据类型
  6. 「C++系列」C++ 变量类型
  7. 「C++系列」C++ 变量作用域
  8. 「C++系列」C++ 常量知识点-细致讲解
  9. 「C++系列」C++ 修饰符类型
  10. 「C++系列」一篇文章说透【存储类】
  11. 「C++系列」一篇文章讲透【运算符】
  12. 「C++系列」循环
  13. 「C++系列」判断
  14. 「C++系列」函数/内置函数
  15. 「C++系列」数字/随机数
  16. 「C++系列」数组
  17. 「C++系列」字符串
  18. 「C++系列」指针
  19. 「C++系列」引用
  20. 「C++系列」日期/时间
  21. 「C++系列」输入/输出
  22. 「C++系列」数据结构
  23. 「C++系列」vector 容器
  24. 「C++系列」类/对象

猜你喜欢

转载自blog.csdn.net/xuaner8786/article/details/141460352