多态(Polymorphism) 是面向对象编程(OOP)的三大特性之一(另外两个是 封装 和 继承)。多态的意思是“多种形态”,它允许不同的对象对同一消息作出不同的响应。简单来说,多态是指通过统一的接口调用不同的实现。
多态的核心思想是:
同一操作作用于不同的对象,可以有不同的解释,产生不同的结果。
例如,动物都会“叫”,但不同的动物(如猫、狗)的叫声是不同的。通过多态,我们可以用统一的“叫”接口调用不同动物的叫声。
多态可以分为两类:
编译时多态(静态多态):
在编译时确定具体的函数调用。
例如:函数重载(Overloading)和运算符重载。
运行时多态(动态多态):
在运行时确定具体的函数调用。
例如:通过基类的指针或引用调用派生类的重写函数(虚函数)。
在 C++ 中,多态主要通过以下方式实现:
函数重载是指在同一个作用域内定义多个同名函数,但这些函数的参数列表不同(参数类型、参数个数或参数顺序)。
#include
using namespace std;
void print(int i)
{
cout << "Integer: " << i << endl;
}
void print(double d)
{
cout << "Double: " << d << endl;
}
void print(string s)
{
cout << "String: " << s << endl;
}
int main()
{
print(10); // 调用 void print(int)
print(3.14); // 调用 void print(double)
print("Hello"); // 调用 void print(string)
return 0;
}
输出:
Integer: 10
Double: 3.14
String: Hello
编译时确定调用哪个函数。
属于静态多态。
运算符重载是指为自定义类型定义运算符的行为。
#include
using namespace std;
class Complex
{
public:
double real, imag;
Complex(double r = 0, double i = 0) : real(r), imag(i) {}
// 重载 + 运算符
Complex operator+(const Complex& other)
{
return Complex(real + other.real, imag + other.imag);
}
};
int main()
{
Complex c1(1, 2), c2(3, 4);
Complex c3 = c1 + c2; // 调用重载的 + 运算符
cout << "c3 = (" << c3.real << ", " << c3.imag << ")" << endl;
return 0;
}
编译时确定调用哪个运算符函数。
属于静态多态。
虚函数是实现运行时多态的关键。通过基类的指针或引用调用派生类的重写函数。
#include
using namespace std;
class Animal
{
public:
virtual void speak()//虚函数
{
cout << "Animal speaks " << endl;
}
};
class Dog :public Animal//继承
{
public:
void speak()override//重写虚函数
{
cout << "Dogs barks" << endl;
}
};
class Cat :public Animal//继承
{
public:
void speak()override
{
cout << "Cat meows " << endl;
}
};
int main()
{
Animal* animal1 = new Dog();
Animal* animal2 = new Cat();
animal1->speak();
animal2->speak();
delete animal1;
delete animal2;
return 0;
}
输出:
Dogs barks
Cat meows
虚函数(Virtual Function) 是 C++ 中实现 运行时多态(Runtime Polymorphism) 的关键机制。它允许通过基类的指针或引用调用派生类的重写函数,从而实现动态绑定(Dynamic Binding)。下面我会详细讲解虚函数的概念、用法、原理以及注意事项。
虚函数是在基类中使用 virtual
关键字声明的成员函数。派生类可以重写(Override)虚函数,从而提供自己的实现。
实现 运行时多态:在程序运行时,根据实际对象的类型调用相应的函数。
提供 统一的接口:通过基类的指针或引用调用派生类的函数。
在基类中使用 virtual
关键字声明虚函数:
class Base
{
public:
virtual void show()
{
cout << "Base class show()" << endl;
}
};
在派生类中重写虚函数时,可以使用 override
关键字(C++11 引入)来显式表明重写:
class Derived : public Base
{
public:
void show() override
{ // 重写基类的虚函数
cout << "Derived class show()" << endl;
}
};
#include
using namespace std;
class Base//基类
{
public:
virtual void show()
{
cout << "Base class show()" << endl;
}
};
class Derived :public Base//继承
{
public:
void show() override
{
cout << "Derived class show()" << endl;
}
};
int main()
{
Base* baseptr;
Derived derivedobj;
baseptr = &derivedobj;//基类指针指向派生对象
baseptr->show();
}
输出:
Derived class show()
basePtr
是基类指针,但它指向派生类对象 derivedObj
。
通过基类指针调用 show()
时,实际调用的是派生类的 show()
函数。
每个包含虚函数的类都有一个虚表。
虚表是一个函数指针数组,存储了该类所有虚函数的地址。
每个对象在内存中都有一个隐藏的虚表指针,指向其类的虚表。
当调用虚函数时,程序通过虚表指针找到虚表,再从虚表中找到对应的函数地址并调用。
在编译时,编译器无法确定基类指针指向的具体对象类型。
在运行时,程序通过虚表指针和虚表确定实际调用的函数。
纯虚函数是在基类中声明但没有实现的虚函数,语法如下:
virtual void func() = 0;
包含纯虚函数的类称为抽象类。
抽象类不能实例化,只能作为基类。
派生类必须实现纯虚函数,否则派生类也会成为抽象类。
#include
using namespace std;
class Shape
{
public:
virtual void draw() = 0;//纯虚函数
};
class Circle :public Shape
{
public:
void draw()override
{
cout << "Drawing a circle" << endl;
}
};
int main()
{
//Shape shape;//错误,不能实例化抽象类
Circle circle;
circle.draw();
return 0;
}
输出:
Drawing a circle
构造函数在对象创建时调用,此时虚表指针尚未初始化,因此不能使用虚函数机制。
如果基类的析构函数不是虚函数,通过基类指针删除派生类对象时,只会调用基类的析构函数,导致派生类的资源泄漏。
#include
using namespace std;
class Base
{
public:
virtual ~Base()//虚析构函数
{
cout << "Base destructor" << endl;
}
};
class Derived :public Base
{
public:
~Derived()
{
cout << "Derived destructor" << endl;
}
};
int main()
{
Base* baseptr = new Derived();
delete baseptr;//调用析构函数
return 0;
}
输出:
Derived destructor
Base destructor
虚函数需要通过虚表查找函数地址,因此比普通函数调用稍慢。
在性能敏感的代码中,应避免过度使用虚函数。
虚函数是实现运行时多态的关键机制。
通过虚表和虚表指针,程序可以在运行时确定实际调用的函数。
纯虚函数和抽象类用于定义接口,强制派生类实现特定功能。
析构函数应该是虚函数,以确保正确释放资源。
回到多态:
代码复用:
通过基类接口调用派生类的方法,减少重复代码。扩展性:
可以轻松添加新的派生类,而不需要修改基类代码。灵活性:
程序可以在运行时根据实际对象类型调用相应的方法。图形绘制:
基类 Shape
定义虚函数 draw()
,派生类 Circle
、Rectangle
实现各自的 draw()
方法。
游戏开发:
基类 Enemy
定义虚函数 attack()
,派生类 Monster
、Boss
实现各自的 attack()
方法。
插件系统:
通过基类接口调用不同插件的功能。
多态是面向对象编程的重要特性,允许通过统一的接口调用不同的实现。
多态分为 编译时多态(如函数重载、运算符重载)和 运行时多态(如虚函数)。
运行时多态通过虚函数实现,是 C++ 中最常用的多态方式。