为什么需要多态?
为了最大限度地减少代码,提高可读性
1.虚函数
虚函数是C++中的一种特殊成员函数,它允许在派生类(也称为子类)中重写(覆盖)基类的实现,使用virtual
进行声明
在C++中,如果基类中的成员函数不是虚函数,派生类中的同名函数并不会覆盖或重写基类中的函数 ,而是产生函数隐藏 ,意味着如果你通过基类类型的指针或引用调用该函数,实际上调用的是基类中的版本,而不是派生类中的版本。
不使用虚函数:
cpp
#include <iostream>
using namespace std;
class Base {
public:
// 普通函数,不是虚函数
void func() {
cout << "Base func" << endl;
}
};
class Derived : public Base {
public:
// 看起来像是重写,实际上是函数隐藏
void func() {
cout << "Derived func" << endl;
}
};
int main() {
Base* basePtr = new Derived();
basePtr->func(); // 调用 Base::func,而不是 Derived::func
//输出结果为Base func
delete basePtr;
system("pause");
return 0;
}
使用虚函数
cpp
#include <iostream>
using namespace std;
class Base {
public:
// 声明为虚函数
virtual void func() {
cout << "Base func" << endl;
}
};
class Derived : public Base {
public:
//真正地重写
void func() {
cout << "Derived func" << endl;
}
};
int main() {
Base* basePtr = new Derived();
basePtr->func(); // 正确调用 Derived::func
//输出结果为Derived func
delete basePtr;
system("pause");
return 0;
}
2.使用虚函数实现多态行为
通过函数引用实现
cpp
#include <iostream>
using namespace std;
// 基类 Fish,定义了鱼类的通用行为
class Fish {
public:
// 虚函数 swim,允许派生类重写,实现多态
virtual void swim() const {
cout << "Fish is swimming" << endl;
}
};
// 派生类 Tuna,继承自 Fish
class Tuna : public Fish {
public:
// 重写 Fish 类的 swim 函数,实现 Tuna 类特有的游泳行为
void swim() const override {
cout << "Tuna is swimming fast" << endl;
}
};
// 派生类 Carp,继承自 Fish
class Carp : public Fish {
public:
// 重写 Fish 类的 swim 函数,实现 Carp 类特有的游泳行为
void swim() const override {
cout << "Carp is swimming slowly" << endl;
}
};
// 函数,使用 Fish 类的引用参数来实现多态
void makeFishSwim(const Fish& fish) {
fish.swim(); // 根据传入对象的实际类型调用相应的 swim 方法
}
int main() {
Tuna tuna;
Carp carp;
// 通过引用传递给函数,实现多态
makeFishSwim(tuna); // 输出 "Tuna is swimming fast"
makeFishSwim(carp); // 输出 "Carp is swimming slowly"
system("pause");
return 0;
}
通过指针实现:
cpp
#include <iostream>
using namespace std;
// 基类 Fish,定义了鱼类的通用行为
class Fish {
public:
// 虚函数 swim,允许派生类重写,实现多态
virtual void swim() {
cout << "Fish is swimming" << endl;
}
// 虚析构函数
virtual ~Fish() {
cout << "Fish is deconstructed" << endl;
}
};
// 派生类 Tuna,继承自 Fish
class Tuna : public Fish {
public:
// 重写 Fish 类的 swim 函数,实现 Tuna 类特有的游泳行为
void swim() override {
cout << "Tuna is swimming fast" << endl;
}
// Tuna 类的析构函数
~Tuna() {
cout << "Tuna is deconstructed" << endl;
}
};
// 派生类 Carp,继承自 Fish
class Carp : public Fish {
public:
// 重写 Fish 类的 swim 函数,实现 Carp 类特有的游泳行为
void swim() override {
cout << "Carp is swimming slowly" << endl;
}
// Carp 类的析构函数
~Carp() {
cout << "Carp is deconstructed" << endl;
}
};
int main() {
// 创建派生类对象
Fish* fish = new Tuna();
fish->swim(); // 调用 Tuna::swim,输出 "Tuna is swimming fast"
Fish* carp = new Carp();
carp->swim(); // 调用 Carp::swim,输出 "Carp is swimming slowly"
// 删除对象,调用相应的析构函数
delete fish;
delete carp;
system("pause");
return 0;
}
3.虚函数的工作原理------虚函数表
虚函数表(通常称为vtable)是C++中实现运行时多态的一种机制。当一个类包含至少一个虚函数时,编译器会为这个类创建一个虚函数表,这张表包含了类中所有虚函数的地址。
工作流程如下:
1.虚函数表的创建: 当一个类中包含至少一个虚函数时,编译器会为这个类创建一个虚函数表。这个表包含了该类所有虚函数的地址。
2.虚函数表指针: 编译器为每个对象添加一个指针,指向其类的虚函数表。这个指针通常存储在对象的内存布局的最前面。
3.调用虚函数: 当你通过一个基类指针或引用调用一个虚函数时,编译器生成的代码首先会访问对象的虚函数表指针,然后查找并调用表中对应的函数。
4.动态绑定: 由于虚函数表的存在,函数调用的解析是在运行时进行的,这称为动态绑定或晚期绑定。这意味着即使基类指针指向的是派生类对象,调用的也是派生类中重写的函数版本。
cpp
class Base {
public:
virtual void show() {
std::cout << "Base show" << std::endl;
}
virtual ~Base() {} // 虚析构函数
};
class Derived : public Base {
public:
void show() override { // 重写基类中的虚函数
std::cout << "Derived show" << std::endl;
}
};
int main() {
Base* basePtr = new Derived(); // 创建Derived对象的指针,但声明为Base类型
basePtr->show(); // 调用show(),虽然basePtr是Base类型,但实际调用的是Derived的show()
delete basePtr;
return 0;
}
4.抽象基类和纯虚函数
抽象基类: 至少包含一个纯虚函数,而且无法被实例化,只能用于派生其他类,简称为ABC
纯虚函数: 它在基类中声明但故意不提供实现,其声明的函数体部分使用 = 0 来标识
cpp
virtual ReturnType FunctionName() = 0;
抽象基类使用方法如下:
cpp
#include <iostream>
using namespace std;
// 抽象基类
class Shape {
public:
// 纯虚函数,用于定义绘制形状的接口
virtual void draw() const = 0;
// 虚析构函数,确保派生类的析构函数被正确调用
virtual ~Shape() {}
};
// 派生类 Circle,表示圆形
class Circle : public Shape {
public:
// 实现 Circle 的 draw 方法
void draw() const override {
std::cout << "Drawing a circle." << std::endl;
}
};
// 派生类 Rectangle,表示矩形
class Rectangle : public Shape {
public:
// 实现 Rectangle 的 draw 方法
void draw() const override {
std::cout << "Drawing a rectangle." << std::endl;
}
};
int main() {
// 创建一个指向 Shape 的指针数组,用于存储不同形状的指针
Shape* shapes[] = { new Circle(), new Rectangle() };
// 使用基类指针调用 draw 方法,实现多态
for (Shape* shape : shapes) {
shape->draw(); // 根据对象的实际类型调用相应的派生类的 draw 方法
}
// 释放动态分配的内存
for (Shape* shape : shapes) {
delete shape;
}
system("pause");
return 0;
}
5.使用虚继承解决菱形问题
菱形问题: 即一个派生类继承自两个中间基类,而这两个中间基类又都继承自同一个基类时。这种继承结构在类图上看起来像一个菱形,因此得名。
田园犬类同时继承狗类和哺乳类,而哺乳类和狗类又同时继承动物类,呈现一个菱形结构。
在这个例子中田园犬类会分别从狗类和哺乳类中各自继承 一个动物类,导致内存浪费 和潜在的一致性问题,所以为了解决这个问题,可以使用虚函数继承来解决
cpp
#include <iostream>
using namespace std;
// 定义基类 Animal
class Animal {
public:
// 动物的呼吸方法
virtual void breathe() { cout << "Animal breathes" << endl; }
// 虚析构函数,确保派生类可以正确释放资源
virtual ~Animal() {}
};
// 定义中间基类 Mammal,使用虚继承自 Animal
class Mammal : virtual public Animal {
public:
// 哺乳动物特有的哺育行为
void nurse() { cout << "Mammal nurses its young" << endl; }
// 虚析构函数
virtual ~Mammal() {}
};
// 定义中间基类 Dog,使用虚继承自 Animal
class Dog : virtual public Animal {
public:
// 狗的吠叫行为
void bark() { cout << "Dog barks" << endl; }
// 虚析构函数
virtual ~Dog() {}
};
// 定义派生类 Poodle,同时继承自 Dog 和 Mammal
class Poodle : public Dog, public Mammal {
public:
// 贵宾犬特有的行为
void prance() { cout << "Poodle prances" << endl; }
// 虚析构函数
virtual ~Poodle() {}
};
// 主函数
int main() {
// 创建 Poodle 对象
Poodle myPoodle;
// 调用从各个基类继承来的方法
myPoodle.bark(); // Dog 类的 bark 函数
myPoodle.nurse(); // Mammal 类的 nurse 函数
myPoodle.breathe(); // Animal 类的 breathe 函数
myPoodle.prance(); // Poodle 类的 prance 函数
system("pause"); // 用于在控制台程序结束前暂停,以便查看输出
return 0;
}
6.表明覆盖意图的限定符override
使用override关键字有助于编译器检查函数签名是否与基类中的虚函数相匹配,从而提高代码的可读性和安全性。
使用方法如下:
cpp
class Base {
public:
virtual void function() {
// 基类
}
};
class Derived : public Base {
public:
void function() override { // 使用 override 明确指出重写
// 派生类
}
};
7.使用final禁止覆盖函数
final关键字用于阻止派生类进一步重写(覆盖)基类中的虚函数。当你希望某个虚函数在派生类中保持最终实现,不允许任何进一步的重写时,可以使用final
关键字。
cpp
class Base {
public:
virtual void function() final {//使用final禁止覆盖
// 基类实现
}
};
class Derived : public Base {
public:
void function() override { // 这里会编译错误,因为 Base::function() 被声明为 final
// 派生类实现
}
};