在 C++ 中,通过 基类的指针或引用 调用派生类的重写方法,而不需要知道具体的派生类类型。
1. 为什么要用基类引用/指针调用虚函数?
C++ 的多态性允许我们使用 基类的引用或指针 来操作派生类对象,从而在运行时调用 派生类重写的虚函数,这提高了代码的可扩展性和灵活性。
2. 代码实现示例
(1)基类和派生类的关系
我们定义一个 基类 Animal
,它有一个 虚函数 speak()
,然后定义 两个派生类 Dog
和 Cat
,它们重写了 speak()
方法。
#include <iostream>
using namespace std;
class Animal {
public:
virtual void speak() { // 虚函数
cout << "Animal makes a sound!" << endl;
}
};
class Dog : public Animal {
public:
void speak() override { // 重写虚函数
cout << "Dog barks: Woof woof!" << endl;
}
};
class Cat : public Animal {
public:
void speak() override { // 重写虚函数
cout << "Cat meows: Meow meow!" << endl;
}
};
(2)使用基类引用调用虚函数
在 main
函数中,我们可以创建 Dog
和 Cat
的实例,但通过 基类 Animal&
的引用 来调用 speak()
,这样就能在运行时调用派生类的 speak()
。
int main() {
Dog myDog;
Cat myCat;
Animal& animalRef1 = myDog; // 用基类引用指向派生类对象
Animal& animalRef2 = myCat;
animalRef1.speak(); // 调用 Dog::speak()
animalRef2.speak(); // 调用 Cat::speak()
return 0;
}
(3)运行结果
Dog barks: Woof woof!
Cat meows: Meow meow!
这里,animalRef1.speak()
并不会调用 Animal::speak()
,而是 动态绑定 到 Dog::speak()
,因为 speak()
是虚函数。
3. 也可以使用基类指针
类似地,我们也可以使用 基类指针 而不是 引用。
int main() {
Dog myDog;
Cat myCat;
Animal* animalPtr1 = &myDog; // 用基类指针指向派生类对象
Animal* animalPtr2 = &myCat;
animalPtr1->speak(); // 调用 Dog::speak()
animalPtr2->speak(); // 调用 Cat::speak()
return 0;
}
运行结果与引用的方式一致:
Dog barks: Woof woof!
Cat meows: Meow meow!
4. 关键点总结
-
虚函数 + 基类引用/指针 = 运行时多态
- 只有 虚函数(
virtual
关键字)才能在运行时实现动态绑定,否则会调用基类的函数。
- 只有 虚函数(
-
基类引用或指针指向派生类对象
- 这样可以在 不改变代码 的情况下扩展新功能,例如新增
Bird
类,也可以直接使用Animal&
处理,而不影响原有代码。
- 这样可以在 不改变代码 的情况下扩展新功能,例如新增
-
使用
override
关键字override
可以避免函数签名错误,确保派生类确实重写了基类的虚函数。
5. 为什么不用对象本身?
如果我们直接使用 对象本身 而不是基类引用/指针,那么即使 speak()
是虚函数,也不会有动态绑定:
int main() {
Dog myDog;
myDog.speak(); // 直接调用 Dog::speak()
Animal myAnimal = myDog; // 发生了 "对象切片"(slicing),只保留基类部分
myAnimal.speak(); // 调用的是 Animal::speak(),而不是 Dog::speak()
return 0;
}
结果:
Dog barks: Woof woof!
Animal makes a sound!
因为 Animal myAnimal = myDog;
发生了对象切片(slicing),只保留了 Animal
的部分,所以 myAnimal.speak()
调用的是 Animal::speak()
,而不是 Dog::speak()
。
6. 什么时候用基类引用调用虚函数?
-
面向对象编程中的多态
- 让代码更具扩展性,例如可以新增不同的
Animal
,而不改变现有代码。
- 让代码更具扩展性,例如可以新增不同的
-
接口设计
- 例如
std::ostream&
是cout
、ofstream
等类的基类,ostream&
可以调用不同的write
方法。
- 例如
-
避免对象切片
- 直接使用对象(值传递)可能导致对象切片,而引用/指针不会。
7. 结论
- 虚函数让基类的引用/指针可以调用派生类的方法,实现 运行时多态。
- 如果不使用引用或指针,而是使用对象本身,会导致对象切片,失去多态性。
- 在设计模式、框架开发中,基类引用/指针调用虚函数是非常常见的模式。
这样,使用 虚函数 + 基类引用/指针 可以写出更灵活、可扩展的代码!✨