C++多态性
概念
C++中的多态性是面向对象编程的一个重要特征,它允许我们通过一个基类的指针或引用来操作不同派生类的对象。多态性增强了代码的灵活性和可扩展性。主要分为两种类型:编译时多态(静态多态)和运行时多态(动态多态)。
编译时多态(静态多态)
编译时多态通常通过函数重载和运算符重载实现。编译器在编译时决定调用哪个函数。
#include <iostream>
using namespace std;
class Print {
public:
void display(int i) {
cout << "Displaying integer: " << i << endl;
}
void display(double d) {
cout << "Displaying double: " << d << endl;
}
};
int main() {
Print p;
p.display(5); // 调用display(int)
p.display(5.5); // 调用display(double)
return 0;
}
运行时多态(动态多态)
运行时多态通过虚函数实现,允许在运行时根据对象的实际类型调用相应的函数。当我们定义一个虚函数并在子类中重写它时,基类的指针或引用可以指向子类的对象,并根据实际对象的类型调用相应的函数。
#include <iostream>
using namespace std;
class Base {
public:
virtual void show() { // 虚函数
cout << "Base class show function called." << endl;
}
virtual ~Base() {} // 虚析构函数
};
class Derived : public Base {
public:
void show() override { // 重写虚函数
cout << "Derived class show function called." << endl;
}
};
int main() {
Base* basePtr; // 基类指针
Derived derivedObj; // 派生类对象
basePtr = &derivedObj; // 指向派生类对象
basePtr->show(); // 调用派生类的show()
return 0;
}
原理
1.虚函数
虚函数是在基类中使用virtual关键字声明的函数,用于支持运行时多态。通过虚函数,可以在基类中定义接口,而在派生类中实现具体的逻辑。当使用基类指针或引用指向派生类对象并调用该虚函数时,C++会根据对象的真实类型来调用相应的函数实现。
2. 虚表(vtable)
每个包含虚函数的类在运行时会有一个虚表(vtable)。这个虚表是一个函数指针数组,存储了该类的所有虚函数的地址。当创建一个对象时,编译器会为该对象生成一个指向其类虚表的指针,称为虚指针(vptr)。
3. 运行时过程
运行时多态的过程大致如下:
-
1.声明和定义:在基类中用virtual关键字声明虚函数,在派生类中重写该虚函数。
-
2.对象创建:当基类指针或引用指向派生类对象时,派生类对象的虚指针会指向派生类的虚表。
3.函数调用:
当通过基类指针或引用调用虚函数时,程序会查找对象的虚表,找到对应虚函数的地址并执行。这是一个动态过程,因为决定调用哪个函数是在运行时而不是编译时。
#include <iostream>
using namespace std;
class Base {
public:
virtual void show() { // 虚函数
cout << "Base class show function called." << endl;
}
virtual ~Base() {} // 虚析构函数
};
class Derived : public Base {
public:
void show() override { // 重写虚函数
cout << "Derived class show function called." << endl;
}
};
void demonstratePolymorphism(Base* b) {
b->show(); // 调用虚函数
}
int main() {
Base baseObj; // 基类对象
Derived derivedObj; // 派生类对象
demonstratePolymorphism(&baseObj); // 调用基类的 show
demonstratePolymorphism(&derivedObj); // 调用派生类的 show
return 0;
}
- 创建基类Base和派生类Derived,Derived重写了Base中的虚函数show。
- 函数demonstratePolymorphism接受基类指针作为参数,调用show函数。实际上,将根据指针所指向的对象类型来调用相应的版本。
- 当调用show时,即使传入的是基类指针,实际执行的是派生类的show。
重载,重写,隐藏
重载(Overloading)
重载是指在同一作用域内可以定义多个同名函数,只要它们的参数列表(参数的数量或类型)不同就可以。重载发生在同一个类中,也可以在继承关系中的不同类中的同名函数。
#include <iostream>
using namespace std;
class Math {
public:
// 重载:不同参数数量和类型
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
};
int main() {
Math math;
cout << math.add(1, 2) << endl; // 调用 add(int, int)
cout << math.add(1.5, 2.5) << endl; // 调用 add(double, double)
cout << math.add(1, 2, 3) << endl; // 调用 add(int, int, int)
return 0;
}
在上面的例子中,add函数被重载,具有不同的参数类型和数量。
重写(Overriding)
重写是指在派生类中重新定义基类中已经声明的虚函数。重写允许派生类为基类的虚函数提供特定的实现。重写是在继承关系中发生的,它通常涉及到基类的虚函数和派生类的同名函数。
#include <iostream>
using namespace std;
class Base {
public:
virtual void show() { // 虚函数允许重写
cout << "Base class show function." << endl;
}
};
class Derived : public Base {
public:
void show() override { // 重写基类的虚函数
cout << "Derived class show function." << endl;
}
};
int main() {
Base* basePtr;
Derived derived;
basePtr = &derived;
basePtr->show(); // 调用 Derived::show()
return 0;
}
在这段代码中,Derived类中的show函数重写了Base类中的虚函数show。通过基类指针调用时,会执行派生类的实现。
隐藏(Hiding)
隐藏是指在派生类中定义一个与基类中同名的成员(可以是函数或变量),这会遮蔽基类中的所有同名成员。隐藏不是重载,也不是重写,它发生在同一作用域内,并且会隐藏基类中的所有同名函数和变量,不论其参数和返回类型。
#include <iostream>
using namespace std;
class Base {
public:
void func(int i) {
cout << "Base func with int: " << i << endl;
}
void func(double d) {
cout << "Base func with double: " << d << endl;
}
};
class Derived : public Base {
public:
void func(int i) { // 该函数隐藏了 Base 中的所有 named func
cout << "Derived func with int: " << i << endl;
}
};
int main() {
Derived d;
d.func(10); // 调用 Derived::func(int)
// d.func(3.14); // 错误:Base::func(double) 被隐藏,不能调用
return 0;
}
在这个例子中,Derived类中的func(int)函数隐藏了Base类中的所有同名成员函数,尽管基类中还有一个func(double)。因此,尝试调用d.func(3.14)将导致编译错误。
总结
- 重载(Overloading):同一个作用域中,可以有多个同名函数,参数列表不同。
- 重写(Overriding):在派生类中重新定义基类中的虚函数,允许多态。
- 隐藏(Hiding):在派生类中定义与基类中同名的成员,遮蔽基类中的所有同名成员。