一、虚函数的定义
虚函数就是在基类中被关键字virtual说明,并且在派生类中重新定义的函数。虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。
在基类中的某个成员函数被声明为虚函数后。此虚函数就可以在一个或多个派生类中被重新定义。在派生类中重新定义时,其函数原型,包括函数类型、函数名、参数个数、参数类型的顺序,都必须与基类中的原型完全相同。
声明虚函数
虚函数的声明形式与普通成员函数类似,但在基类中需要加上 virtual
关键字。派生类中可以重写这个虚函数(重写时可以加上 override
关键字进行显式标注,但不是必须的)。
virtual 函数类型 函数名(形参表)
{
函数体
}
二、虚函数示例
虚函数声明
首先,我们定义一个基类 Animal
,其中包含一个虚函数 makeSound
。
#include <iostream>
// 基类 Animal
class Animal {
public:
// 虚函数 makeSound
virtual void makeSound() const {
std::cout << "Some generic animal sound" << std::endl;
}
// 虚析构函数
virtual ~Animal() {
std::cout << "Animal destructor called" << std::endl;
}
};
派生类中重写虚函数
接下来,我们定义两个派生类 Dog
和 Cat
,它们重写基类中的虚函数 makeSound
。
// 派生类 Dog
class Dog : public Animal {
public:
// 重写 makeSound 函数
void makeSound() const override {
std::cout << "Woof" << std::endl;
}
~Dog() override {
std::cout << "Dog destructor called" << std::endl;
}
};
// 派生类 Cat
class Cat : public Animal {
public:
// 重写 makeSound 函数
void makeSound() const override {
std::cout << "Meow" << std::endl;
}
~Cat() override {
std::cout << "Cat destructor called" << std::endl;
}
};
使用虚函数
现在我们编写主程序,展示如何使用基类指针和引用调用虚函数。基类指针法:
int main() {
// 创建具体的形状对象
Animal* animal1 = new Dog();
Animal* animal2 = new Cat();
// 通过基类指针调用虚函数
animal1->makeSound(); // 输出: Woof
animal2->makeSound(); // 输出: Meow
// 确保调用派生类的析构函数
delete animal1; // 输出: Dog destructor called
// Animal destructor called
delete animal2; // 输出: Cat destructor called
// Animal destructor called
return 0;
}
基类引用法
#include <iostream>
using namespace std;
// 基类 Animal
class Animal {
public:
// 虚函数 makeSound,基类中提供通用接口
virtual void makeSound() const {
cout << "Some generic animal sound" << endl;
}
// 虚析构函数,确保正确调用派生类的析构函数
virtual ~Animal() {}
};
// 派生类 Dog,继承自 Animal
class Dog : public Animal {
public:
// 重写 makeSound 方法
void makeSound() const override {
cout << "Woof! Woof!" << endl;
}
};
// 派生类 Cat,继承自 Animal
class Cat : public Animal {
public:
// 重写 makeSound 方法
void makeSound() const override {
cout << "Meow! Meow!" << endl;
}
};
// 函数展示动物叫声,使用基类引用
void showAnimalSound(const Animal& animal) {
animal.makeSound();
}
int main() {
// 创建基类和派生类对象
Animal genericAnimal;
Dog dog;
Cat cat;
// 使用基类引用调用虚函数,展示多态性
showAnimalSound(genericAnimal); // 输出 "Some generic animal sound"
showAnimalSound(dog); // 输出 "Woof! Woof!"
showAnimalSound(cat); // 输出 "Meow! Meow!"
return 0;
}
三、虚函数与重载函数的关系
虚函数是用于实现运行时多态性的。通过在基类中声明虚函数,派生类可以重写这些虚函数,从而在运行时根据实际对象类型调用相应的函数实现。这种机制依赖于动态绑定(动态调度),即在运行时决定调用哪一个版本的函数。
虚函数特点
- 定义:在基类中用
virtual
关键字声明。 - 用途:实现运行时多态性。
- 动态绑定:函数调用在运行时进行解析。
- 重写:派生类可以重写基类中的虚函数。
示例
#include <iostream>
using namespace std;
class Base {
public:
virtual void show() const {
cout << "Base show" << endl;
}
virtual ~Base() {}
};
class Derived : public Base {
public:
void show() const override {
cout << "Derived show" << endl;
}
};
void display(const Base& obj) {
obj.show(); // 调用的是具体对象的 show() 方法
}
int main() {
Base b;
Derived d;
display(b); // 输出 "Base show"
display(d); // 输出 "Derived show"
return 0;
}
重载函数
重载函数是指在同一个作用域中,函数名相同但参数列表不同的多个函数。编译器根据调用时的参数列表决定调用哪个函数版本。这是一种编译时的多态性(静态多态性)。
特点
- 定义:同名函数在同一作用域中具有不同的参数列表。
- 用途:实现编译时多态性,提高函数的可读性和可用性。
- 静态绑定:函数调用在编译时进行解析。
- 参数列表:必须不同(参数类型、个数或顺序不同)。
示例
#include <iostream>
using namespace std;
class Printer {
public:
void print(int i) {
cout << "Printing int: " << i << endl;
}
void print(double f) {
cout << "Printing float: " << f << endl;
}
void print(const char* c) {
cout << "Printing string: " << c << endl;
}
};
int main() {
Printer p;
p.print(10); // 调用 void print(int)
p.print(5.5); // 调用 void print(double)
p.print("Hello"); // 调用 void print(const char*)
return 0;
}
小结:虚函数的动态绑定是通过虚函数表和虚函数指针实现的,使得程序在运行时能够根据实际对象类型调用正确的虚函数版本。动态绑定是实现多态性的关键机制,使得代码更加灵活和可扩展,能够处理不同类型的对象而无需在编译时知道具体的对象类型。