多态是什么?
多态(Polymorphism)是面向对象编程(OOP)中的一个重要概念。它指的是能够使用相同的接口来操作不同类型的数据或对象。多态的字面意思是“多种形态”或“多种形式”。在编程中,这意味着同一个方法或函数可以根据传递给它的对象的类型来执行不同的操作。
多态的两种形态
编译时多态(或静态多态)
通常是通过方法重载实现的,即在同一个类中创建多个具有相同名称但参数不同的方法
运行时多态(或动态多态)
通常是通过方法覆盖实现的,即子类重写父类的方法,使得同一个方法调用可以表现出不同的行为。在Java和C#等语言中,这是通过使用继承和接口实现的。
C++中的多态
在 C++ 中,多态是一种允许函数或对象表现出多种不同行为的机制,主要依靠继承和虚函数来实现。当然普通的静态多态也是可以完成的,大多是情况下的多态都是指动态多态。
下面是一个多态的例子
#include <iostream>
using namespace std;
// 静态多态:函数重载
void print(int i) {
cout << "Printing int: " << i << endl;
}
void print(double f) {
cout << "Printing float: " << f << endl;
}
// 动态多态的基础:基类
class Base {
public:
virtual void show() {
cout << "Base class show" << endl;
}
};
// 派生类
class Derived : public Base {
public:
void show() override {
cout << "Derived class show" << endl;
}
};
int main() {
// 静态多态
print(10);
print(10.10);
// 动态多态
Base *b;
Derived d;
b = &d;
// 调用虚函数,执行派生类中的函数
b->show();
return 0;
}
静态多态(重载)
void print(int i) {
cout << "Printing int: " << i << endl;
}
void print(double f) {
cout << "Printing float: " << f << endl;
}
这里,print
函数被重载了两次:一次接受 int
类型的参数,另一次接受 double
类型的参数。当这个函数被调用时,编译器会根据传递的参数类型决定调用哪个版本的 print
函数。这是在编译时决定的,因此称为静态多态。
动态多态(重写)*
class Base {
public:
virtual void show() {
cout << "Base class show" << endl;
}
};
class Derived : public Base {
public:
void show() override {
cout << "Derived class show" << endl;
}
};
首先,定义了一个基类 Base
和一个派生自 Base
的派生类 Derived
。在 Base
类中声明了一个虚函数 show
。
在 Derived
类中,重写 Override了基类中的 show
方法。这里的 virtual
关键字表明 show
可以在派生类中被重写,而 override
关键字是 C++11 引入的,用于明确指出该方法是重写基类中的虚函数。
虚函数
虚函数:值得是被virtual修饰的类成员函数
class Base {
public:
virtual void show() {
cout << "Base class show" << endl;
}
};
虚函数的重写(覆盖)
虚函数的重写(覆盖)
class Base {
public:
virtual void show() {
cout << "Base class show" << endl;
}
};
class Derived : public Base {
public:
virtual void show() {
cout << "Derived class show" << endl;
}
//在重写基类虚函数的过程中,派生的虚函数可以不加virtual关键字,
//(因为继承后基类的虚函数也会被继承下来,在派生类依旧保持虚函数的属性)
//但这种写法容易搞混,不建议
};
在 C++ 中,重写(Override)一个虚函数需要满足以下几个条件:
- 相同的函数名:重写函数必须与基类中的虚函数具有相同的名称。
- 相同的参数列表:重写函数必须具有与基类虚函数完全相同的参数列表。参数的类型、数量和顺序都必须一致。
-
相同的返回类型或协变返回类型:通常,重写函数必须有与基类虚函数相同的返回类型。
-
相同的访问权限(或更宽松):如果基类中的虚函数是
public
,那么在派生类中重写该函数时,它也应该是public
。不能降低访问级别。
虚函数重写的特例:协变和析构函数的重写
协变
协变返回类型(Covariant Return Type)是 C++ 中允许在重写虚函数时使用的一种特性。当派生类重写基类的虚函数时,它允许派生类的函数返回基类函数返回类型的派生类型。这主要适用于返回类型是指针或引用的情况。
class Base {
public:
virtual Base* clone() const {
return new Base(*this);
}
// ... 其他成员 ...
};
class Derived : public Base {
public:
// 协变返回类型:返回 Derived* 而不是 Base*
virtual Derived* clone() const override {
return new Derived(*this);
}
// ... 其他成员 ...
};
-
基类
Base
定义了一个名为clone
的虚函数,它返回一个Base*
类型的指针。这个函数的目的是创建当前对象的一个副本并返回指向该副本的指针。 -
派生类
Derived
重写了基类的clone
函数,但是更改了返回类型。在Derived
类中,clone
函数返回一个Derived*
类型的指针。由于Derived
继承自Base
,因此这个更改是合法的,并且符合协变返回类型的规则。
析构函数
析构函数的重写可以被视为虚函数重写的一个特殊例子。
class Base {
public:
virtual ~Base() {
// 基类析构逻辑
cout << "Destructing Base" << endl;
}
};
class Derived : public Base {
public:
virtual ~Derived() {
// 派生类析构逻辑
cout << "Destructing Derived" << endl;
}
};
-
虚析构函数:
- 在
Base
类中,析构函数被声明为虚(virtual ~Base()
)。这是管理动态分配的对象时面向对象编程中的一个重要实践。 - 当一个类被设计为基类时,将析构函数声明为虚是非常重要的。这确保了当通过基类指针删除派生类对象时,正确的析构函数(即派生类的析构函数,后跟基类的析构函数)被调用。
- 在
-
派生类析构函数:
Derived
类重写了析构函数。这里,使用virtual
关键字是可选的,因为一旦基类的析构函数被声明为虚的,派生类中的析构函数自动成为虚函数。- 在
Derived
的析构函数中,可以实现清理派生类特有资源的逻辑。
-
析构顺序:
- 当删除
Derived
类型的对象时,首先调用Derived
类的析构函数,然后调用Base
类的析构函数。 - 这个顺序是重要的,因为它确保了派生类的资源首先被释放,然后是基类的资源。这有助于防止内存泄漏和其他资源管理问题。
- 当删除