目录标题
-
- [1. 继承的基本概念](#1. 继承的基本概念)
-
- [1.1 为什么使用继承](#1.1 为什么使用继承)
- [1.2 继承的语法](#1.2 继承的语法)
- [2. 继承的类型](#2. 继承的类型)
- [3. 继承中的成员访问规则](#3. 继承中的成员访问规则)
-
- [3.1 访问控制](#3.1 访问控制)
- [3.2 派生类的访问权限](#3.2 派生类的访问权限)
- [4. 继承的构造和析构过程](#4. 继承的构造和析构过程)
-
- [4.1 构造过程](#4.1 构造过程)
- [4.2 析构过程](#4.2 析构过程)
- [5. 派生类的特殊成员函数](#5. 派生类的特殊成员函数)
-
- [5.1 派生类的构造函数](#5.1 派生类的构造函数)
- [5.2 派生类的析构函数](#5.2 派生类的析构函数)
- [6. 继承中的多态](#6. 继承中的多态)
-
- [6.1 虚函数](#6.1 虚函数)
- [6.2 纯虚函数和抽象类](#6.2 纯虚函数和抽象类)
- [7. 继承中的问题](#7. 继承中的问题)
-
- [7.1 钻石问题](#7.1 钻石问题)
- [8. 实际例子](#8. 实际例子)
- [9. 总结](#9. 总结)
继承是面向对象编程语言如C++的一个核心概念,它允许创建基于已存在类(称作基类或父类)的新类(称作派生类或子类),继承其属性和行为,同时还可以添加或修改功能。本文将详细介绍C++中的继承机制,包括基类和派生类的创建、继承类型和多态。
1. 继承的基本概念
1.1 为什么使用继承
继承可以:
- 提高代码的重用性。
- 建立类之间的层次关系。
- 为基于共享接口的多态性提供支持。
1.2 继承的语法
在C++中,继承的语法如下:
cpp
class BaseClass {
// 基类的成员
};
class DerivedClass : public BaseClass {
// 派生类的成员
};
在这里,DerivedClass
继承了BaseClass
的公共和保护成员。
2. 继承的类型
2.1 公共继承(Public Inheritance)
在C++中,公共继承是一种最常用的继承方式,它体现了"是一个"("is-a")的关系。当一个类以公共的方式继承另一个类时,基类的公共成员和保护成员在派生类中保持其原有的访问级别。
公共继承的特性
- 基类的公共成员:在派生类中仍然是公共的,这意味着它们可以在任何可以访问派生类对象的地方被访问。
- 基类的保护成员:在派生类中仍然是保护的,这意味着它们只能在派生类内部以及派生类的子类中被访问。
- 基类的私有成员:在派生类中不可直接访问,即使通过公共接口也不行。私有成员被封装在基类中,派生类无法访问。
公共继承的语法
cpp
class BaseClass {
public:
void publicFunction() {
// 公共成员函数
}
protected:
void protectedFunction() {
// 保护成员函数
}
private:
void privateFunction() {
// 私有成员函数
}
};
class DerivedClass : public BaseClass {
public:
void useBaseFunctions() {
publicFunction(); // 可以访问,因为它是公共的
protectedFunction(); // 可以访问,因为它是保护的
// privateFunction(); // 错误!无法访问基类的私有成员
}
};
在上面的例子中,DerivedClass
以公共的方式继承了BaseClass
。因此,BaseClass
的publicFunction
在DerivedClass
中仍然是公共的,protectedFunction
仍然是保护的,而privateFunction
则无法在DerivedClass
中访问。
公共继承的实际应用
公共继承常用于创建类的层次结构,其中派生类是基类的一种特殊类型。例如,我们可以有一个Shape
基类,然后有Circle
、Square
等派生类,每个派生类都是一种特殊的形状。
cpp
class Shape {
public:
virtual void draw() = 0; // 纯虚函数,使Shape成为抽象基类
};
class Circle : public Shape {
public:
void draw() override {
// 实现绘制圆形的代码
}
};
class Square : public Shape {
public:
void draw() override {
// 实现绘制正方形的代码
}
};
在这个例子中,Circle
和Square
都是Shape
的子类,它们都继承了Shape
的draw
方法,并提供了自己的实现。这种设计允许我们编写处理Shape
对象的代码,而不必关心具体的形状类型,从而实现多态性。
2.2 保护继承(Protected Inheritance)
在C++中,保护继承(Protected Inheritance)是一种继承方式,它介于公共继承和私有继承之间。当一个类以保护的方式继承另一个类时,基类的公共成员和保护成员在派生类中都变为保护成员,而基类的私有成员仍然不可访问。
保护继承的特性
- 基类的公共成员:在派生类中变为保护的,这意味着它们只能在派生类内部以及派生类的子类中被访问。
- 基类的保护成员:在派生类中仍然是保护的,这意味着它们只能在派生类内部以及派生类的子类中被访问。
- 基类的私有成员:在派生类中不可直接访问,即使通过公共接口也不行。私有成员被封装在基类中,派生类无法访问。
保护继承的语法
cpp
class BaseClass {
public:
void publicFunction() {
// 公共成员函数
}
protected:
void protectedFunction() {
// 保护成员函数
}
private:
void privateFunction() {
// 私有成员函数
}
};
class DerivedClass : protected BaseClass {
public:
void useBaseFunctions() {
publicFunction(); // 可以访问,因为它是保护的
protectedFunction(); // 可以访问,因为它是保护的
// privateFunction(); // 错误!无法访问基类的私有成员
}
};
在上面的例子中,DerivedClass
以保护的方式继承了BaseClass
。因此,BaseClass
的publicFunction
在DerivedClass
中变成了保护的,protectedFunction
仍然是保护的,而privateFunction
则无法在DerivedClass
中访问。
保护继承的实际应用
保护继承通常用于创建类的层次结构,其中派生类需要将基类的接口和实现细节作为自己的保护成员,以便在进一步的派生中使用。这种方式不对外暴露基类的公共接口,而是将其作为派生类的内部实现的一部分。
cpp
class Base {
public:
void basePublicFunction() {
// 基类的公共成员函数
}
protected:
void baseProtectedFunction() {
// 基类的保护成员函数
}
private:
void basePrivateFunction() {
// 基类的私有成员函数
}
};
class Derived : protected Base {
public:
void derivedFunction() {
basePublicFunction(); // 可以访问,因为它是保护的
baseProtectedFunction(); // 可以访问,因为它是保护的
// basePrivateFunction(); // 错误!无法访问基类的私有成员
}
};
class FurtherDerived : public Derived {
public:
void furtherDerivedFunction() {
basePublicFunction(); // 可以访问,因为它是保护的
baseProtectedFunction(); // 可以访问,因为它是保护的
// basePrivateFunction(); // 错误!无法访问基类的私有成员
}
};
在这个例子中,Derived
类以保护的方式继承了Base
类,因此Base
的公共成员在Derived
中变成了保护的。FurtherDerived
类以公共的方式继承了Derived
类,因此它可以访问Derived
中的保护成员,这些成员实际上是Base
类的公共成员。
2.3 私有继承(Private Inheritance)
私有继承是面向对象编程中的一个特性,其中一个类(称为派生类)继承另一个类(称为基类),但基类的公共成员和保护成员都成为派生类的私有成员。私有继承意味着这些继承来的成员不可以通过派生类的对象被外界访问,只能在派生类内部使用。
私有继承的特点:
- 基类的公共成员和保护成员:在派生类中变为私有成员。
- 基类的私有成员:在派生类中仍然是不可访问的。
- 外部访问:派生类的对象不能使用基类的成员,这些成员对于派生类外部是隐藏的。
私有继承的语法:
cpp
class Base {
public:
void basePublicMethod() {}
protected:
int baseProtectedAttribute;
private:
int basePrivateAttribute;
};
class Derived : private Base {
// Base的公共成员和保护成员现在是Derived的私有成员。
public:
void derivedPublicMethod() {
basePublicMethod(); // 正确:Base的公共成员在Derived中可以访问,但在Derived外不可访问。
baseProtectedAttribute = 5; // 正确:Base的保护成员在Derived中可以访问。
// basePrivateAttribute; // 错误:Base的私有成员在Derived中无法访问。
}
};
int main() {
Derived d;
// d.basePublicMethod(); // 错误:现在basePublicMethod()是d的私有成员。
return 0;
}
在这个例子中,Derived
类私有继承了Base
类,所以Base
类的公共和保护成员都成为Derived
类的私有成员。从外部看,Derived
类的对象不能使用它从Base
类继承来的成员,因为它们对外界是不可见的,即使它们在Base
类中是公共或保护的。
私有继承的使用场景:
私有继承通常用于实现细节的隐藏和复用,而不是为了表示"is-a"关系。当你希望使用一个类的实现,但不想继承其接口时,私有继承是合适的选择。这样,派生类不会暴露基类的接口,但可以利用基类的实现。
私有继承可以被看作是一种强化的组合关系,而不是传统的"is-a"继承关系。在某些情况下,组合可能是更合适的选择,因为它能够提供更清晰的设计和更强的封装。但当需要访问基类的保护成员或需要重写虚函数时,私有继承可能更为合适。
3. 继承中的成员访问规则
3.1 访问控制
- 公共成员:在任何地方都可访问。
- 保护成员:在基类本身、派生类及其友元中可访问。
- 私有成员:仅在基类本身及其友元中可访问。
3.2 派生类的访问权限
- 公共继承:基类的公共成员在派生类中仍是公共的,保护成员仍是保护的,私有成员不可访问。
- 保护继承:基类的公共和保护成员在派生类中都变成保护的。
- 私有继承:基类的公共和保护成员在派生类中都变成私有的。
4. 继承的构造和析构过程
4.1 构造过程
当创建派生类对象时,首先调用基类的构造函数,然后是派生类的构造函数。
4.2 析构过程
析构过程与构造相反,先调用派生类的析构函数,然后是基类的析构函数。
5. 派生类的特殊成员函数
5.1 派生类的构造函数
派生类的构造函数可以使用成员初始化列表来调用基类的构造函数。
cpp
class DerivedClass : public BaseClass {
public:
DerivedClass(int value) : BaseClass(value) {
// 派生类构造体内的代码
}
};
5.2 派生类的析构函数
派生类的析构函数不需要特别调用基类的析构函数,这会自动发生。
6. 继承中的多态
6.1 虚函数
通过在基类中声明函数为virtual
,派生类可以重写该函数,实现运行时多态。
6.2 纯虚函数和抽象类
一个包含纯虚函数的类是抽象类,不能实例化。派生类必须重写所有纯虚函数,才能创建其对象。
7. 继承中的问题
7.1 钻石问题
当两个派生类继承同一个基类,并且另一个类又继承这两个派生类时,会产生钻石问题,可以通过虚继承解决。
cpp
class BaseClass {};
class DerivedClass1 : virtual public BaseClass {};
class DerivedClass2 : virtual public BaseClass {};
class FinalClass : public DerivedClass1, public DerivedClass2 {};
8. 实际例子
cpp
#include <iostream>
// 基类
class Animal {
public:
virtual void speak() {
std::cout << "Some animal sound" << std::endl;
}
virtual ~Animal() = default; // 确保派生类能调用正确的析构函数
};
// 派生类
class Dog : public Animal {
public:
void speak() override { // 重写基类函数
std::cout << "Woof!" << std::endl;
}
};
int main() {
Dog myDog;
myDog.speak(); // 输出: Woof!
return 0;
}
9. 总结
C++的继承是一个功能强大的机制,它不仅能够帮助程序员创建层次化的类结构,还能够实现代码复用和多态性。理解如何正确使用继承对于编写高效和可维护的C++程序至关重要。