在C++中,继承和多态是面向对象编程的核心概念,它们使得代码更加模块化、可重用和易于维护。
继承:
继承是一种代码复用机制,它允许一个类(子类)继承另一个类(父类)的属性和方法。通过继承,子类可以获得父类的功能,并可以根据需要扩展或修改这些功能。
在C++中,继承主要分为三种类型:
-
公有继承(Public Inheritance):子类继承父类的公有成员和保护成员,但私有成员不可直接访问。公有继承表示“是一个”关系,即子类是一种特定类型的父类。
-
保护继承(Protected Inheritance):子类继承父类的公有成员和保护成员,并且这些成员在子类中仍然保持保护属性。私有继承表示子类只能访问父类的公有和保护成员。
-
私有继承(Private Inheritance):子类继承父类的公有成员和保护成员,但是这些成员在子类中成为私有的。私有继承表示实现细节的共享,而不是“是一个”的关系。
多态:
多态是指允许不同子类对象被统一对待的特性。在C++中,多态主要通过虚函数实现。当一个基类指针或引用指向派生类对象时,通过虚函数调用可以根据对象的实际类型来执行相应的函数。
多态分为两种:
-
静态多态(Static Polymorphism):也称为编译时多态,主要通过函数重载和模板实现。编译器在编译时期根据函数参数的类型和数量来确定调用哪个函数。
-
动态多态(Dynamic Polymorphism):也称为运行时多态,主要通过虚函数实现。在运行时,根据对象的实际类型来决定调用哪个函数。
虚函数:
-
虚函数是在基类中声明的,它可以在派生类中被重写(override)。
-
当基类指针或引用调用虚函数时,会发生动态绑定,即实际调用的是对象所属类的版本。
-
虚函数的调用是多态的基础,它使得不同子类的对象可以通过相同的接口进行操作。
纯虚函数和抽象类:
-
纯虚函数是没有函数体的虚函数,它在基类中的声明以
= 0
结尾。 -
拥有纯虚函数的类被称为抽象类,抽象类不能被实例化。
-
抽象类通常作为基类,用于定义接口,其子类必须实现所有纯虚函数才能成为具体类。
构造函数和析构函数与继承:
-
构造函数不能被继承,每个子类都必须定义自己的构造函数。
-
析构函数可以被继承,但如果子类没有重写析构函数,则会调用基类的析构函数。
-
在多态场景下,正确的析构顺序很重要,通常应该先删除最派生的对象。
多态性的局限性:
-
多态性只适用于有继承关系的类。
-
多态性要求至少有一个虚函数。
-
通过基类指针或引用调用虚函数时,只能访问在基类中定义的接口。
案例一:公有继承
假设我们有一个基类Animal
,它代表了所有动物共有的基本特征和行为。
class Animal {
public:
void eat() {
cout << "Eating..." << endl;
}
virtual void makeSound() {
cout << "Making a generic animal sound..." << endl;
}
};
现在,我们想要创建一个子类Dog
,它继承自Animal
,并添加了一些特有的行为。
class Dog : public Animal {
public:
void bark() {
cout << "Barking!" << endl;
}
void makeSound() override { // 覆盖基类的makeSound函数
bark();
}
};
在这个例子中,Dog
类通过公有继承获得了Animal
类的eat
方法和makeSound
方法。Dog
类还添加了一个新的方法bark
,并且覆盖了makeSound
方法,使得当通过Dog
对象调用makeSound
时,执行的是Dog
类的bark
方法。
案例二:保护继承
假设我们有一个基类Vehicle
,它代表了所有车辆的基本特征和行为。
class Vehicle {
protected:
int speed;
public:
Vehicle(int s) : speed(s) {}
void accelerate(int increment) {
speed += increment;
}
};
现在,我们想要创建一个子类Car
,它继承自Vehicle
,并添加了一些特有的行为。
class Car : protected Vehicle {
public:
Car(int s) : Vehicle(s) {}
void honk() {
cout << "Honking the horn!" << endl;
}
};
在这个例子中,Car
类通过保护继承获得了Vehicle
类的速度和加速功能。由于速度是通过保护成员继承的,因此只有Car
类及其子类可以访问速度属性。Car
类还添加了一个新的方法honk
。
案例三:私有继承
假设我们有一个基类Shape
,它代表了所有形状的基本特征和行为。
class Shape {
public:
void draw() {
cout << "Drawing a generic shape..." << endl;
}
};
现在,我们想要创建一个子类Square
,它继承自Shape
,并添加了一些特有的行为。
class Square : private Shape {
public:
void draw() {
Shape::draw(); // 调用基类的draw方法
cout << "Drawing a square..." << endl;
}
};
在这个例子中,Square
类通过私有继承获得了Shape
类的draw
方法。由于draw
方法是通过私有成员继承的,因此它只能在Square
类内部被访问。Square
类还添加了一个新的方法draw
,它首先调用了基类的draw
方法,然后添加了绘制正方形的特定逻辑。
案例四:多态
假设我们有一个基类Animal
和几个派生类Dog
、Cat
和Bird
,它们分别代表不同种类的动物。
class Animal {
public:
virtual void makeSound() = 0; // 纯虚函数,使Animal成为抽象类
};
class Dog : public Animal {
public:
void makeSound() override {
cout << "Barking!" << endl;
}
};
class Cat : public Animal {
public:
void makeSound() override {
cout << "Meowing!" << endl;
}
};
class Bird : public Animal {
public:
void makeSound() override {
cout << "Chirping!" << endl;
}
};
现在,我们可以创建一个函数,它接受Animal
类型的引用作为参数,并调用其makeSound
方法。由于多态,实际调用的将是对象所属类的makeSound
方法。
void playSound(Animal& animal) {
animal.makeSound();
}
int main() {
Dog dog;
Cat cat;
Bird bird;
playSound(dog); // 输出: Barking!
playSound(cat); // 输出: Meowing!
playSound(bird); // 输出: Chirping!
return 0;
}