前言
封装性,继承性,多态性是面向对象语言的三大特性。其中封装,继承好理解,而多态的概念让许多初学者感到困惑。本文将讲述C++中多态的概念以及多态的实现机制。
什么是多态?
多态就是多种形态,就是许多情况下可以互换地使用基类型和派生类型的多种形态。
多态的实现
依赖于动态绑定机制。
动态绑定机制相关
动态绑定是函数实际参数和形式参数绑定的一种方式,它是指我们能够在函数接口中使用继承层次中任意类型的对象,无需关心对象的具体类型。
动态执行接口函数的对象参数的哪个函数得在程序实际执行的时候才能确定
C++中默认不使用动态绑定,要触发动态绑定必须满足两个条件:
1. 接口函数的形式参数必须是引用类型或者指针类型。
2. 动态执行函数(对象参数的成员函数而非接口函数)必须是声明为虚成员函数。
代码实例一
下面代码创建基类对象a,然后创建其派生对象b,当将a,b作为参数传入函数printNum()后,函数让它们分别调用自己的函数getNum():
1 #include <iostream> 2 3 using namespace std; 4 5 // 基类 6 class A { 7 public: 8 A() {a=0;} 9 // **需要动态执行的函数必须声明为虚函数,满足了上条件2。 10 virtual int getNum() { 11 return a; 12 } 13 private: 14 int a; 15 }; 16 17 // 派生类 18 class B : public A { 19 public: 20 B() {b=1;} 21 // 基类中已经声明过为虚函数了不需要再次声明 22 int getNum() { 23 return b; 24 } 25 private: 26 int b; 27 }; 28 29 // **接口函数形参声明为引用类型,满足了上条件1。 30 void printNum(A & a) { 31 // 当实参为基类对象则调用基类对象的getNum,实参为派生类对象则调用派生类对象的getNum。 32 cout << a.getNum() << endl; 33 } 34 35 int main() 36 { 37 A a; 38 B b; 39 40 printNum(a); 41 printNum(b); 42 43 return 0; 44 }
运行结果:
可以观察到顺利实现了动态绑定,a b分别执行自己的getNum() 函数。
代码实例二
下面代码同样创建基类对象a,然后创建其派生对象b,当将a,b作为参数传入函数printNum()后,函数让它们分别调用自己的函数getNum()(但本例接口函数的形式参数改成了值类型 ):
1 #include <iostream> 2 3 using namespace std; 4 5 // 基类 6 class A { 7 public: 8 A() {a=0;} 9 // **需要动态执行的函数必须声明为虚函数,满足了上条件2。 10 virtual int getNum() { 11 return a; 12 } 13 private: 14 int a; 15 }; 16 17 // 派生类 18 class B : public A { 19 public: 20 B() {b=1;} 21 // 基类中已经声明过为虚函数了不需要再次声明 22 int getNum() { 23 return b; 24 } 25 private: 26 int b; 27 }; 28 29 // **接口函数形参声明为值类型,不满足上条件1。 30 void printNum(A a) { 31 // 未有实现动态绑定,因此不论实参是何种类型,均执行基类的getNum()函数。 32 cout << a.getNum() << endl; 33 } 34 35 int main() 36 { 37 A a; 38 B b; 39 40 printNum(a); 41 printNum(b); 42 43 return 0; 44 }
运行结果:
可以观察到没有实现动态绑定,a b都执行a的getNum() 函数。这意味着,对象本身并不支持多态,它刚进入函数就被彻底地转换成了形参类型。因此,实现多态要靠的是对象的指针或者引用,而不是对象本身。这也是《C++ Primer》一书中不断强调的东西。
说明
1. 派生类和基类的虚函数类型要一致,只有一种例外 --- 返回对基类类型的引用的虚函数。派生类中的虚函数可以返回基类函数所返回类型的派生类的引用。
2. 基类和派生类的虚函数的默认实参要相同,不然会引起混淆。
小结
封装保证了类的重用( 安全方面 ),继承实现了类的重用,多态则实现了接口的重用。这三个机制体现了C++代码的可重用性,反映了C++在处理大型程序的优势。