多态:
指相同对象收到不同消息或不同对象收到相同消息时可以产生不同的动作。
静态多态(早绑定):
静态多态也称为早绑定,静态多态是利用函数的重载来实现的,程序在编译的时候根据参数的不同来确定调用哪个函数,即:相同对象收到不同的消息产生不同的动作。
举例如下:
class Calcu { public: int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; } }; int main() { Calcu cal; cout << cal.add(1, 2) << endl; cout << cal.add(1.5, 2.5) << endl; // 在这里,相同的对象cal.add()收到不同的命令, 1,2 和 1.5,2.5 产生了不同的动作,这就是静态多态(早绑定) return 0; }
输出结果:
3
4
动态多态(晚绑定):
动态多态也称为晚绑定,动态多态需要利用虚函数来实现,程序在运行的时候根据虚函数指针的具体指向来确定调用哪个函数,即:不同对象收到相同的消息产生不同的动作。
举例如下:
//define a base class class base { public: base(){} ~base(){} public: void show() { cout << "this is base" << endl; } }; //define sub class class sub1 : public base { public: sub1(){} ~sub1(){} public: void show() { cout << "this is sub1" << endl; } }; class sub2 : public base { public: sub2(){} ~sub2(){} public: void show() { cout << "this is sub2" << endl; } }; int main(void) { base *b1 = new sub1(); base *b2 = new sub2(); b1->show(); b2->show(); return 0; }
如上程序所示:
输出的结果为:
this is base
this is base
输出的结果并不是我们所想的:
this is sub1
this is sub2
输出结果分析:在这里,子类sub1和sub2都只是隐藏了父类的show函数,在子类的里放着的是父类的show函数和子类的show函数,我们的指针指向的是谁,就调用谁的东西,指针b1和b2都是指向对应的子类,那么就调用子类的东西,由于指针base类型,那么就调用子类继承下来的父类的show函数。
为了实现动态多态,我们需要用到virtual 关键字,具体如下:
//define a base class class base { public: base(){} ~base(){} public: virtual void show() { cout << "this is base" << endl; } }; //define sub class class sub1 : public base { public: sub1(){} ~sub1(){} public: virtual void show()//这里的virtual可写可不写,建议写上(便于代码的阅读),如果不加,编译器也为默认加上 { cout << "this is sub1" << endl; } }; class sub2 : public base { public: sub2(){} ~sub2(){} public: virtual void show()//这里的virtual可写可不写,建议写上(便于代码的阅读),如果不加,编译器也为默认加上 { cout << "this is sub2" << endl; } }; int main(void) { base *b1 = new sub1; base *b2 = new sub2; b1->show(); b2->show(); // b1,b2 作为不同的对象,收到相同的命令 ->show() ,却产生不同的动作,这就是我们所说的动态多态(晚绑定) return 0; }
如上所示的程序输出结果便是:
this is sub1
this is sub2
输出结果分析:首先子类sub1和sub2都继承了父类base,函数show都加上了virtual 关键字,那么子类sub1和sub2的show函数都会将父类base的show函数覆盖(重写)掉,而我们在main函数中定义的base类型的指针b1和b2,分别指向子类sub1和sub2,指针指向谁,就应该调用谁的东西,那么对于指针b1,它指向子类sub1,他就会调用子类sub1里的show函数,由于父类的show函数被子类的show函数给覆盖掉了,所以在这里就会去调用子类的show函数,同理,指针b2的调用也是一样的。
如上我们也可以知道,动态多态是以封装和继承为基础的。
一些概念:
虚函数:给类中的普通成员函数加上关键字virtual,该函数就叫作虚函数
virtual的使用限制:
(1)普通函数不能是虚函数(即:该函数必须是某一个类的成员函数,不能是全局函数,否则编译失败)
(2)静态成员函数不能是虚函数(静态是随着类的加载而加载的,它不属于任何对象,编译失败)
(3)内联函数不能是虚函数(如果内联函数被virtual修饰,编译器会会略inline而使该函数变为虚函数)
(4)构造函数不能是虚函数(编译失败)
隐藏:父类和子类只要出现同名的函数,并且没有virtual 关键字,就称为隐藏。
覆盖(重写):除了函数体不同,其他都相同才称为覆盖(函数名,返回值,参数列表,有virtual关键字,引用限定符都必须相同)
但是在上面的程序中会有严重的问题:内存泄漏
因为我们定义了父类的指针,当我们要释放内存(delete 父类指针)时,此时只会调用父类的析构函数,而不会调用子类的析构(我们new了子类,即:会走父类的构造,走子类的构造,走了父类的析构,但是却没走子类的析构,所以就导致了内存的泄漏)
解决方法:将父类的析构函数加上关键字virtual,让父类的析构函数成为虚析构,这样delete父类指针的时候,就会先走子类的析构函数,再走父类的析构函数
抽象类:含有纯虚函数的类称为抽象类
接口类:只含有纯虚函数的类称为接口类