C++中的多态性主要分为三种实现方式:静态多态、非继承多态和常规多态。下面我们将分别介绍它们的实现方式。
静态多态指的是通过函数重载或者运算符重载实现的多态,在编译时确定函数调用或操作的具体实现。这种多态性也称为编译时多态,因为在程序编译阶段就已经决定了要调用哪个函数或使用哪个运算符。静态多态的实现方式如下:
(1)函数重载
函数重载是指在同一作用域内定义多个函数,它们的函数名相同但参数列表不同,从而实现对不同参数类型的适应和处理。例如:
void print(int i) {
cout << "int: " << i << endl;
}
void print(double d) {
cout << "double: " << d << endl;
}
int main() {
print(10);
print(3.14);
return 0;
}
在上述代码中,我们定义了两个名为print
的函数,一个接受整数参数,一个接受双精度浮点型参数。当我们调用print
函数时,编译器会根据传入参数的类型来确定调用哪个print
函数。这就是函数重载实现的静态多态性。
(2)运算符重载
运算符重载是指对C++中的运算符进行重新定义,使之能够适用于用户自定义类型。例如:
class Complex {
private:
double real;
double imag;
public:
Complex(double r = 0, double i = 0) :real(r), imag(i) {}
Complex operator+(const Complex& c2) {
return Complex(real + c2.real, imag + c2.imag);
}
};
int main() {
Complex c1(1, 2), c2(3, 4);
Complex c3 = c1 + c2;
return 0;
}
在上述代码中,我们定义了一个名为Complex
的类,并对加法运算符进行了重载。当我们使用+
运算符对两个Complex
对象进行相加时,编译器会调用我们定义的operator+
函数,从而实现自定义类型的加法操作。
非继承多态指的是通过模板和函数对象实现的多态性,它不依赖于类的继承关系。这种多态性也称为参数化多态或泛型编程,因为它可以处理不同类型的数据,具有很强的通用性。非继承多态的实现方式如下:
(1)模板
模板是一种用于生成特定类型的代码的机制,它允许程序员编写与类型无关的代码。通过模板,可以在编译时自动生成特定类型的代码,从而实现对不同数据类型的处理。例如:
template <typename T>
T max(T x, T y) {
return x > y ? x : y;
}
int main() {
int a = 10, b = 20;
double c = 1.5, d = 2.5;
cout << max(a, b) << endl; //输出20
cout << max(c, d) << endl; //输出2.5
return 0;
}
在上述代码中,我们定义了一个名为max
的函数模板,可以比较任意类型的数据并返回其中最大值。当我们调用max
函数时,编译器会根据传入参数的数据类型来自动生成相应的代码。这就是模板实现的非继承多态性。
(2)函数对象
函数对象是指将一个函数封装成一个对象,从而使得这个函数可以像一个普通对象一样进行传递和操作。在实现非继承多态时,我们可以使用函数对象来实现对不同类型数据的处理。例如:
struct Square {
template <typename T>
T operator() (const T& x) const {
return x * x;
}
};
int main() {
Square square;
int a = 10;
double b = 1.5;
cout << square(a) << endl; //输出100
cout << square(b) << endl; //输出2.25
return 0;
}
在上述代码中,我们定义了一个名为Square
的函数对象,并通过重载operator()
实现了对任意类型数据的平方运算。当我们调用square
对象时,编译器会根据传入参数的类型自动选择相应的版本进行处理。
常规多态指的是基于类的继承关系和虚函数机制实现的多态性。它使用基类指针或引用来访问派生类对象的方法,在运行时确定函数调用的具体实现。这种多态性也称为运行时多态,因为在程序运行阶段根据实际情况来确定要调用哪个函数。常规多态的实现方式如下:
(1)基类指针或引用
基类指针或引用是指将基类类型的指针或引用指向派生类对象,从而实现对派生类方法的调用。在基类中,我们将需要被派生类改写的方法声明为虚函数,并使用virtual
关键字来修饰。例如:
class Shape {
public:
virtual double area() const = 0;
};
class Rectangle : public Shape {
private:
double width;
double height;
public:
Rectangle(double w, double h) :width(w), height(h) {}
double area() const override {
return width * height;
}
};
int main() {
Shape* shape1 = new Rectangle(2, 3);
cout << shape1->area() << endl; //输出6
delete shape1;
return 0;
}
在上述代码中,我们定义了一个名为Shape
的基类和一个继承自Shape
的派生类Rectangle
。在Shape
类中,我们声明了一个纯虚函数area
,并使用virtual
关键字来修饰,表示该方法是可重写的。在Rectangle
类中,我们重写了area
方法,并使用override
关键字来使编译器检查该方法是否确实是虚函数的覆盖。
在main
函数中,我们定义了一个指向Rectangle
对象的Shape
指针,通过该指针调用area
方法时,编译器会根据实际情况选择相应的版本进行处理。这就是基类指针或引用实现常规多态的方式。
(2)虚函数表
虚函数表是编译器生成的一个用于存储虚函数地址的表格,每个类中都有自己的虚函数表。在派生类继承了基类中的虚函数时,它会将这些虚函数的指针放到自己的虚函数表中,并可能添加新的虚函数指针。
在程序运行时,当我们使用基类指针或引用来访问派生类对象的方法时,编译器会根据该指针或引用所指向的对象类型,在相应的虚函数表中查找正确的虚函数地址,然后调用对应的函数。这就是基于虚函数表实现常规多态的机制。
总结
C++中的多态性主要分为三种实现方式:静态多态、非继承多态和常规多态。其中,静态多态指的是利用模板和函数对象等技术实现对不同数据类型的处理,而非继承多态则是利用函数对象来实现。常规多态则是基于类的继承关系和虚函数机制实现的,使用基类指针或引用来访问派生类对象的方法,在运行时确定函数调用的具体实现。
静态多态主要采用函数重载和模板实现,可以在编译期间确定函数调用的具体实现,具有高效性和灵活性;非继承多态则适合处理不同类型数据之间的相似操作,可以通过函数对象进行实现,具有代码复用和可维护性等优点;常规多态则通过虚函数表实现,在运行时确定函数调用的具体实现,具有灵活性和可扩展性等优点。
不同的多态方式适用于不同的场景和需求,我们需要根据实际情况选择合适的方式。在实践中,常规多态是最常用的一种方式,可以通过基类指针或引用来实现对派生类对象的访问,提高代码的可维护性和可扩展性。同时,静态多态和非继承多态也具有自己的特点和优势,在某些场景下可以发挥重要作用。