本文转载自The Four Polymorphisms in C++
C++中的四种多态
当人们谈论C ++中的多态性时,通常是指通过基类指针或引用使用派生类的事情,这称为子类型多态性
。 但是他们经常忘记,C ++中还有各种各样的其他多态性,例如参数多态性
,ad-hoc多态性
和强制多态性
。
这些多态性在C ++中也有不同的名称,
- 子类型多态也称为运行时多态。
- 参数多态也称为编译时多态。
- 临时多态性也称为重载。
- 强制转换也称为(隐式或显式)强制转换。
在本文中,我将通过C ++语言的示例来说明所有多态性,并深入介绍为什么它们具有其他各种名称。
子类型多态性(运行时多态子类型多态是每个人在C ++中说“多态”时所理解的。 通过基类指针和引用使用派生类的能力。
这是一个例子。 假设您有各种猫科动物,例如这些猫科动物,
polymorphic-cats.gif
由于它们都是Felidae的生物学家族,并且都应该能够喵叫,因此可以将它们表示为从Felid
基类继承并覆盖meow
纯虚拟功能的类,
// file cats.h
class Felid {
public:
virtual void meow() = 0;
};
class Cat : public Felid {
public:
void meow() { std::cout << "Meowing like a regular cat! meow!\n"; }
};
class Tiger : public Felid {
public:
void meow() { std::cout << "Meowing like a tiger! MREOWWW!\n"; }
};
class Ocelot : public Felid {
public:
void meow() { std::cout << "Meowing like an ocelot! mews!\n"; }
};
现在,主程序可以通过Felid
(基类)指针互换使用Cat
,Tiger
和Ocelot
,
#include <iostream>
#include "cats.h"
void do_meowing(Felid *cat) {
cat->meow();
}
int main() {
Cat cat;
Tiger tiger;
Ocelot ocelot;
do_meowing(&cat);
do_meowing(&tiger);
do_meowing(&ocelot);
}
在这里,主程序将指向cat
,tiger
和ocelot
的指针传递给do_meowing
函数,该函数期望指向Felid
的指针。 由于它们都是猫科动物,因此程序会为每个猫科动物调用正确的meow
函数,并且输出为:
Meowing like a regular cat! meow!
Meowing like a tiger! MREOWWW!
Meowing like an ocelot! mews!
由于种种原因,子类型多态也称为运行时多态
。 多态函数调用的解析是在运行时通过虚拟表通过间接进行的。 另一种解释方式是,编译器不在编译时定位要调用的函数的地址,而是在程序运行时通过在虚拟表中取消引用右指针来调用该函数。
在类型理论中,它也称为包含多态性
。
参数多态性(编译时多态性)
参数多态性提供了一种对任何类型执行相同代码的方法。 在C ++中,参数多态性是通过模板实现的。
最简单的示例之一是泛型max
函数,该函数找到两个参数中的最大值,
#include <iostream>
#include <string>
template <class T>
T max(T a, T b) {
return a > b ? a : b;
}
int main() {
std::cout << ::max(9, 5) << std::endl; // 9
std::string foo("foo"), bar("bar");
std::cout << ::max(foo, bar) << std::endl; // "foo"
}
在这里,max
函数在类型T
上是多态的。但是,请注意,它不适用于指针类型,因为比较指针会比较内存位置而不是内容。 为了使它适用于指针,您必须专门针对指针类型使用模板,该模板不再是参数多态性,而是ad-hoc多态性。
由于参数多态性是在编译时发生的,因此也称为编译时多态性
。
临时多态性(重载)
临时多态性允许具有相同名称的函数对于每种类型的行为有所不同。 例如,给定两个整数和+运算符,它将它们加在一起。 给定两个std::strings
,将它们连接在一起。 这称为重载
。
这是一个为int
和string
实现函数add
的具体示例,
#include <iostream>
#include <string>
int add(int a, int b) {
return a + b;
}
std::string add(const char *a, const char *b) {
std::string result(a);
result += b;
return result;
}
int main() {
std::cout << add(5, 9) << std::endl;
std::cout << add("hello ", "world") << std::endl;
}
如果您专门研究模板,则临时多态性也会在C ++中出现。 返回上一个有关max
函数的示例,这是您如何为两个char *
编写max的方法,
template <>
const char *max(const char *a, const char *b) {
return strcmp(a, b) > 0 ? a : b;
}
现在,您可以调用::max(“ foo”,“ bar”)
来查找字符串“ foo”和“ bar”的最大值。
强制多态性(转换)
当将一个对象或原始类型强制转换为另一个对象类型或原始类型时,会发生强制转换。 例如,
float b = 6; // int gets promoted (cast) to float implicitly
int a = 9.99 // float gets demoted to int implicitly
当使用C的类型转换表达式,例如(unsigned int *)
或(int)
或C ++的static_cast
,const_cast
,reinterpret_cast
或dynamic_cast
时,会发生显式转换。
如果类的构造函数不是explicit
的,则也会发生强制转换,例如,
#include <iostream>
class A {
int foo;
public:
A(int ffoo) : foo(ffoo) {}
void giggidy() { std::cout << foo << std::endl; }
};
void moo(A a) {
a.giggidy();
}
int main() {
moo(55); // prints 55
}
如果将A的构造函数设为explict
,则将不再可能。 使构造函数显式以避免意外转换始终是一个好主意。
同样,如果类为T
类型定义了转换运算符,则可以在需要T
类型的任何地方使用它。
例如,
class CrazyInt {
int v;
public:
CrazyInt(int i) : v(i) {}
operator int() const { return v; } // conversion from CrazyInt to int
};
CrazyInt
定义了一个转换运算符来键入int
。 现在,如果我们有一个以int
作为参数的print_int
函数,我们还可以将CrazyInt
类型的对象传递给它,
#include <iostream>
void print_int(int a) {
std::cout << a << std::endl;
}
int main() {
CrazyInt b = 55;
print_int(999); // prints 999
print_int(b); // prints 55
}
我前面讨论的子类型多态实际上也是强制性多态,因为派生类已转换为基类类型。
玩弄所有有关多态性的新知识,下次再见!
网友评论