START
Hi,大家好!昨天技术群里在讨论一个C++多态问题,今天我们来聊一聊C++中的多态机制。
在 C++ 中,多态
(Polymorphism)是一种面向对象编程的重要概念,它允许不同类的对象对同一消息做出不同的响应。具体来说,多态性允许基类的指针或引用在运行时指向派生类的对象,并且根据对象的实际类型来调用相应的成员函数。
多态性是通过虚函数来实现的。当一个基类的成员函数被声明为虚函数时,派生类可以通过覆盖(重写)这个函数来提供自己的实现。在运行时,调用这个虚函数的时候,实际上调用的是指向对象的实际类型的版本。
C++ 中的多态性有两种形式:静态多态(编译时多态)和动态多态(运行时多态)。
静态多态(也称为编译时多态或早期多态)是指在编译时就确定函数调用的方式,主要通过函数重载和模板来实现。在静态多态中,编译器在编译时根据函数的签名(函数名称和参数列表)来确定调用哪个函数版本。
静态多态主要有两种形式:
函数重载: 函数重载允许在同一作用域内声明多个函数,它们具有相同的名称但参数列表不同。在调用函数时,编译器根据传递的参数的数量、类型和顺序来选择匹配的函数。
#include <iostream>
// 函数重载示例
void print(int x) {
std::cout << "Integer: " << x << std::endl;
}
void print(double x) {
std::cout << "Double: " << x << std::endl;
}
int main() {
print(5); // 调用第一个 print 函数
print(3.14); // 调用第二个 print 函数
return 0;
}
模板: 模板是一种通用编程技术,允许编写与特定类型无关的代码。通过使用模板,可以在不同类型的参数上执行相同的操作,而无需为每种类型编写不同的函数。
#include <iostream>
// 模板示例
template <typename T>
void print(T x) {
std::cout << "Value: " << x << std::endl;
}
int main() {
print(5); // 实例化一个 int 类型的 print 函数
print(3.14); // 实例化一个 double 类型的 print 函数
print("Hello"); // 实例化一个 const char* 类型的 print 函数
return 0;
}
在静态多态中,函数调用的决定在编译时完成,因此性能更高。然而,静态多态的缺点是在编写代码时必须明确指定每个函数的具体版本,如果有大量的重载或模板,可能会导致代码量增加和可读性降低。
动态多态(也称为运行时多态或晚期多态)是指在程序运行时根据对象的实际类型来决定调用哪个函数版本。动态多态性通过虚函数和继承来实现,在编译时无法确定函数调用的具体版本,而是在运行时根据对象的类型动态确定。
动态多态的实现需要满足以下两个条件:
下面是一个简单的示例,演示了动态多态的用法:
#include <iostream>
// 基类
class Animal {
public:
// 虚函数
virtual void makeSound() {
std::cout << "Animal makes a sound" << std::endl;
}
};
// 派生类
class Dog : public Animal {
public:
// 重写基类的虚函数
void makeSound() override {
std::cout << "Dog barks" << std::endl;
}
};
// 派生类
class Cat : public Animal {
public:
// 重写基类的虚函数
void makeSound() override {
std::cout << "Cat meows" << std::endl;
}
};
int main() {
// 创建派生类对象
Dog dog;
Cat cat;
// 基类指针指向派生类对象
Animal* ptr1 = &dog;
Animal* ptr2 = &cat;
// 通过基类指针调用虚函数,实现多态
ptr1->makeSound(); // 调用的是派生类 Dog 的 makeSound() 函数
ptr2->makeSound(); // 调用的是派生类 Cat 的 makeSound() 函数
return 0;
}
Animal
类有一个虚函数 makeSound()
,而 Dog
和 Cat
类分别继承自 Animal
类并重写了 makeSound()
函数。在 main()
函数中,我们创建了 Dog
和 Cat
类的对象,并将基类指针指向这些对象,然后通过基类指针调用虚函数 makeSound()
。由于 makeSound()
是虚函数,所以在运行时根据对象的实际类型来决定调用哪个版本的函数,从而实现了动态多态性。
在 C++ 中,可以使用父类的指针来指向子类的对象,这是实现多态的一种常见方式。这种行为被称为向上转型(upcasting),它允许您通过基类的接口来操作派生类的对象。这在面向对象编程中是非常有用的,因为它使代码更加灵活和可扩展。
下面是一个简单的示例说明了如何使用父类的指针来指向子类的对象:
#include <iostream>
// 基类
class Base {
public:
virtual void display() {
std::cout << "Base class display() called" << std::endl;
}
};
// 派生类
class Derived : public Base {
public:
void display() override {
std::cout << "Derived class display() called" << std::endl;
}
};
int main() {
// 创建派生类对象
Derived derivedObj;
// 使用基类指针指向派生类对象
Base* basePtr = &derivedObj;
// 通过基类指针调用虚函数,实现多态
basePtr->display(); // 调用的是派生类的 display() 函数
return 0;
}
Base
是基类,Derived
是派生类。Base
类有一个虚函数 display()
,Derived
类重写了 display()
函数。在 main()
函数中,我们创建了 Derived
类的对象 derivedObj
,然后使用 Base
类的指针 basePtr
指向了 derivedObj
。最后,通过 basePtr
调用 display()
函数,由于 display()
函数是虚函数,所以调用的是 Derived
类中的版本,实现了多态行为。
在 C++ 中,如果父类通过指针或引用调用一个虚函数,而这个虚函数在子类中被重写(override),那么调用的实际方法将取决于指针或引用所指向的对象的类型。这就是多态的体现。
具体来说,如果父类指针或引用指向的是子类对象,那么调用的方法将是子类中重写的版本;如果指针或引用指向的是父类对象,那么调用的方法将是父类中的版本。
下面是一个示例来说明这一点:
#include <iostream>
// 基类
class Base {
public:
virtual void show() {
std::cout << "Base class show()" << std::endl;
}
};
// 派生类
class Derived : public Base {
public:
void show() override {
std::cout << "Derived class show()" << std::endl;
}
};
int main() {
// 创建派生类对象
Derived derivedObj;
// 使用基类指针指向派生类对象
Base* basePtr = &derivedObj;
// 通过基类指针调用虚函数,实现多态
basePtr->show(); // 调用的是派生类的 show() 函数
return 0;
}
Base
类是基类,Derived
类是派生类。在 main()
函数中,我们创建了 Derived
类的对象 derivedObj
,然后使用 Base
类的指针 basePtr
指向了 derivedObj
。最后,通过 basePtr->show()
调用 show()
函数,由于 show()
是虚函数,因此调用的是 Derived
类中的版本,而不是 Base
类中的版本。