多态(Polymorphism) 允许不同的对象对同一消息做出响应,即同一个接口,使用不同的实例而执行不同操作。多态性提供了代码的扩展性和复用性,使得代码更加灵活和易于维护。
多态的基本概念
- 编译时多态:也称为静态多态或方法重载(Overloading),发生在编译时期。它是通过方法重载(同名但参数列表不同的方法)来实现的。
- 运行时多态:也称为动态多态或方法重写(Overriding),发生在运行时期。它是通过子类对父类中的方法进行重写(同名且参数列表相同的方法),并在运行时根据对象的实际类型来确定调用哪个版本的方法来实现的。
多态的限制条件
- 继承:多态性是通过继承关系实现的。子类必须继承自父类,才能使用父类的引用指向子类的对象,从而表现出多态性。
- 重写:子类必须重写父类中的方法(即方法名和参数列表必须与父类中被重写的方法一致),才能体现出多态性。如果子类没有重写父类中的方法,那么调用该方法时仍然会执行父类中的方法。
- 向上转型:在调用方法时,父类引用可以指向子类对象(即向上转型),这是多态性的前提。通过向上转型,可以隐藏子类的特性,只表现出父类的特性,从而实现代码的复用和扩展。
- 访问权限:子类在重写父类方法时,不能降低方法的访问权限(例如将父类的public方法重写为protected或private方法)。这是为了保证子类对象在调用该方法时仍然能够访问到该方法。
- 异常类型:子类在重写父类方法时,可以抛出父类方法所声明的异常,但不能抛出父类方法没有声明的异常(除非该异常是运行时异常RuntimeException或其子类)。这是为了保证子类对象在调用该方法时不会抛出意外的异常。
具体分析
#include <iostream> class Animal { public: virtual void makeSound() { // 声明为虚函数以支持多态 std::cout << "The animal makes a sound" << std::endl; } // 析构函数也通常声明为虚函数,以避免删除子类对象时的潜在问题 virtual ~Animal() {} }; class Dog : public Animal { public: void makeSound() override { // 使用override关键字确保重写父类虚函数 std::cout << "The dog barks" << std::endl; } }; class Cat : public Animal { public: void makeSound() override { std::cout << "The cat meows" << std::endl; } }; int main() { Animal* animal1 = new Dog(); // 向上转型,使用指针 Animal* animal2 = new Cat(); // 向上转型,使用指针 animal1->makeSound(); // 输出 "The dog barks",体现了多态性 animal2->makeSound(); // 输出 "The cat meows",体现了多态性 // 释放动态分配的内存 delete animal1; delete animal2; // 如果在子类中不将父类的虚函数声明为虚函数,则不会表现出多态性 // 如果子类试图重写父类的非虚函数,则只是隐藏了父类的函数,而不是重写 // 如果子类降低了父类虚函数的访问权限(如从public变为private),这将导致编译错误 // 如果子类抛出了父类虚函数未声明的异常,并且没有在子类的函数签名中捕获或声明,则可能导致运行时异常 return 0; }
在上面的示例中,我们定义了一个Animal类和一个Dog类(继承自Animal)以及一个Cat类(也继承自Animal)。每个类都有一个makeSound()方法,用于发出声音。在Main类的main()方法中,我们创建了Dog和Cat的实例,并将它们赋值给Animal类型的引用。当我们调用这些引用的makeSound()方法时,会根据对象的实际类型(Dog或Cat)来执行相应的方法,从而体现了多态性。同时,我们也展示了如果子类违反了多态性的限制条件(如降低访问权限或抛出未声明的异常),将会导致编译错误或运行时异常。