多态性(Polymorphism)是面向对象编程的核心特性之一,意指相同的操作或方法可以对不同的数据类型或对象表现出不同的行为。多态性提高了代码的灵活性和可扩展性,使程序可以更好地适应变化。
在 C++ 中,多态性可以分为两种:
- 编译时多态(静态多态):在编译时决定函数调用的具体版本。
- 运行时多态(动态多态):在运行时根据对象的实际类型决定函数调用的具体版本。
C++中如何实现多态?
1. 编译时多态
编译时多态通常通过 函数重载 和 运算符重载 来实现。
函数重载:
- 同一个函数名可以有多个版本,但参数列表必须不同
#include <iostream>
using namespace std;class Calculator {
public:int add(int a, int b) {return a + b;}double add(double a, double b) {return a + b;}
};int main() {Calculator calc;
cout << calc.add(3, 4) << endl; // 输出 7
cout << calc.add(3.5, 4.5) << endl; // 输出 8.0return 0;
}
运算符重载:
- 使用 operator 关键字为类定义自定义运算符。
#include <iostream>
using namespace std;class Complex {
public:double real, imag;Complex(double r, double i) : real(r), imag(i) {}
Complex operator+(const Complex& other) {return Complex(real + other.real, imag + other.imag);}
};int main() {Complex a(1.0, 2.0), b(3.0, 4.0);Complex c = a + b;
cout << "Real: " << c.real << ", Imag: " << c.imag << endl; // 输出 Real: 4, Imag: 6return 0;
}
2. 运行时多态
在 C++ 中,多态的核心特性是通过 父类指针或引用 来调用 子类对象 的方法。在多态场景下,调用的具体方法由 子类的类型 决定,而不是由父类的类型决定。这种行为是通过 虚函数 和 动态绑定实现的。
基本示例:父类指针调用子类对象
#include <iostream>
using namespace std;class Animal {
public:virtual void speak() {
cout << "Animal makes a sound" << endl;}virtual ~Animal() {} // 虚析构函数,确保派生类对象正确析构
};class Dog : public Animal {
public:void speak() override {
cout << "Dog 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(); // 输出 "Dog barks"
animal2->speak(); // 输出 "Cat meows"delete animal1;delete animal2;return 0;
}
说明:
- 虚函数:
- 在父类中使用 virtual 关键字声明的函数。
- 如果没有 virtual,调用会根据指针的静态类型决定(编译时绑定),而不是动态类型(运行时绑定)。
- 运行时绑定:
- 当父类指针调用虚函数时,会根据对象的动态类型决定调用哪一个版本的函数。
- 虚析构函数:
- 如果不定义虚析构函数,子类对象在销毁时可能不会调用其析构函数,从而导致内存泄漏。
使用父类引用调用子类对象
父类引用和父类指针的行为类似,运行时会根据对象的动态类型调用相应的函数版本。
#include <iostream>
using namespace std;class Shape {
public:virtual void draw() {
cout << "Drawing Shape" << endl;}virtual ~Shape() {}
};class Circle : public Shape {
public:void draw() override {
cout << "Drawing Circle" << endl;}
};class Square : public Shape {
public:void draw() override {
cout << "Drawing Square" << endl;}
};void render(Shape& shape) {
shape.draw(); // 调用的版本由具体对象决定
}int main() {Circle c;Square s;render(c); // 输出 "Drawing Circle"render(s); // 输出 "Drawing Square"return 0;
}
说明:
- 使用父类引用调用时,动态绑定依然有效,调用的是子类重写的函数版本。
- 与父类指针类似,父类引用可以提高代码的通用性和灵活性。
注意事项
- 非虚函数不会实现多态: 非虚函数的调用是静态绑定的,与对象的类型无关。
class Base {
public:void sayHello() {
cout << "Hello from Base" << endl;}
};class Derived : public Base {
public:void sayHello() {
cout << "Hello from Derived" << endl;}
};int main() {
Base* base = new Derived();base->sayHello(); // 输出 "Hello from Base"(静态绑定)
delete base;return 0;
}
虚函数开销: 虚函数引入了额外的开销,包括虚表(vtable)和动态绑定的时间开销。
避免 slicing(切片)问题: 如果通过值传递子类对象到父类类型的函数,子类部分会被切割,只保留父类部分。