多态的问题:一个指针可以指向父类对象也可以指向子类对象,通过这个指针调用的重写函数时,究竟是调用父类的函数还是子类的函数。
准则:这个指针如果指向父类对象,就调用父类的函数,否则就调用子类的函数。
1函数重写
1.父类中被重写的函数依然会继承个子类,2.默认情况下子类中重写的函数讲隐藏父类中的函数,3.通过作用于分辨符::可以访问到父类中被隐藏的函数。
//函数重写 #include <iostream> #include <cstdlib> using namespace std; class Parent { public: void print() { cout<<"I'm Parent..."<<endl; } }; class Child : public Parent { public: void print() { cout<<"I'm Child..."<<endl; } }; int main() { Child child; child.print(); child.Parent::print();//通过作用域分辨符访问父类 cout << "Press the enter key to continue... !" << endl; cin.get(); return EXIT_SUCCESS; }
输出:
2.当函数重写遇到赋值兼容性问题。
上个函数中main函数改回如下:
int main() { Child child; Parent* pp = &child; Parent& rp = child; child.print(); pp->print(); rp.print(); cout << "Press the enter key to continue... !" << endl; cin.get(); return EXIT_SUCCESS; }
输出:
C++与C相同,是静态编译型语言,在编译时,编译器自动根据指针的类型判断指向的是一个什么样的对象,所以编译器认为父类指针指向的是父类对象(根据赋值兼容性原则,这个假设合理),由于程序没有运行,所以不可能知道父类指针指向的具体是父类对象还是子类对象。从程序安全的角度,编译器假设父类指针只指向父类对象,因此编译结果为调用父类的成员函数。
如果父类指针指向的是父类对象则调用父类中定义的函数
如果父类指针指向的是子类对象则调用子类中定义的重写函数。
多态:同样的调用语句有多种不同的表现形态。面向对象中的多态:根据实际的对象类型决定函数调用语句的具体调用目标。
C++中的多态支持
C++通过virtual关键字对多态进行支持 使用virtual声明的函数被重写后即可展现多态特性。让编译器不必从程序安全的角度假设父类指针只指向父类对象,而是根据实际类型指向对应对象。
//函数重写 #include <iostream> #include <cstdlib> using namespace std; class Boss { private: static Boss* cInstance; Boss() { } public: static Boss* GetInstance() { if(cInstance == NULL) { cInstance = new Boss(); } return cInstance; } int fight() { return 10; } }; class Master { public: virtual int eightSwordKill() { return 8; } }; class NewMaster : public Master { public: virtual int eightSwordKill() { return Master::eightSwordKill()*2; } }; void fieldPK(Master* master,Boss* boss) { int k = master->eightSwordKill(); int b = boss->fight(); if(k<b) { cout<<"Mater is killed..."<<endl; } k = master->eightSwordKill(); if(k<b) { } else { cout<<"BOSS is killed..."<<endl; } } Boss* Boss::cInstance = NULL; int main() { Boss* boss = Boss::GetInstance(); cout<<"Mater vs Boss"<<endl; Master master; NewMaster newmaster; fieldPK(&master,boss); cout<<"NewMater vs Boss"<<endl; fieldPK(&newmaster,boss); cout << "Press the enter key to continue... !" << endl; cin.get(); return EXIT_SUCCESS; }
重载和重写有什么区别?什么时候是重载,什么时候是重写?
函数重载必须在同一个类中极性,子类无法重载父类的函数,父类同名函数将被覆盖。重载是在编译时期根据参数类型和个数决定调用函数(效率较高)。
函数重写必须发生于父类和子类之间,并且父类与子类中函数必须有完全相同的原形,使用virtual声明之后能够产生多态。多态是在运行期间根据具体对象的类型决定调用函数(效率较低)。
是否可以将类的每个成员函数都声明为虚函数?:出于效率考虑,没有必要将所有的成员函数都声明为虚函数。
C++中多态的实现原理:
1 当类中声明虚函数时,编译器会在类中生成一个虚函数表。
2 虚函数表是一个存储类成员函数指针的数据结构。
3 虚函数表是有编译器自动生成与维护的。
4 virtual成员函数会被编译器放入虚函数表里
5 存在虚函数时,每个对象中都有一个指向虚函数表的指针。
编译器确定func()是否虚函数,如果是,编译器在对象VPTR(虚函数表指针)所指向的虚函数表中查找func(),并调用,查找和调用在运行时完成(查找并调用过程是由编译器产生的代码,在程序运行的时候才调用,)。
如果不是,编译器直接可以确定并找到被调用的成员函数。
查找的过程需要通过寻址才能确定真正应该调用的函数,而普通成员函数就是在编译时就确定了调用的函数。在效率上,虚函数的效率要低的多。
对象中的VPTR指针什么时候被初始化?
对象在创建的时候由编译器对VPTR指针进行初始化,只有当对象的构造完全结束后VPTR的指向才最终确定,父类对象的VPTR指向父类虚拟函数包,子类对象的VPTR指向子类的虚拟函数表。
结论:构造函数中调用虚拟函数无法实现多态。
#include <iostream> #include <cstdlib> using namespace std; class Parent { public: Parent() { func(); } virtual void func() { cout<<"void Parent::func()"<<endl; } }; class Child:public Parent { public: void func() { cout<<"void Child::func()"<<endl; } }; int main() { Parent p; Child c; cout << "Press the enter key to contiune..." << endl; cin.get(); return 0; }
分析:Child c;先执行父类构造函数,在这之前先要初始化父类的VPTR,所以VPTR指向父类,父类的构造函数通过VPTR(指向父类)调用父类的func(),接着子类调用自己的默认构造函数,在执行构造函数之前,初始化VPTR,VPTR指向子类。
添加Child的构造函数
class Child:public Parent { public: Child() { func(); } void func() { cout<<"void Child::func()"<<endl; } };
则输出为:
纯虚函数:纯虚函数是只声明函数原型,而故意不定义函数体的虚函数。
面向对象中的抽象类:
1. 抽象类可用于表示现实世界中的抽象概念;
2. 抽象类是一种只能定义类型,而不能产生对象的类
3. 抽象类只能被继承并重写相关函数
4. 抽象类的直接特征是纯虚函数
抽象类与纯虚函数:
1.抽象类不能用于定义对象 2.抽象类只能用于定义指针和引用 3.抽象中的纯虚函数必须被子类重写。
//Shape类为抽象类,其中的area()为纯虚函数 #include <iostream> #include <cstdlib> using namespace std; class Shape { public: virtual double area() = 0;//area是纯虚函数, =0告诉编译器,这个函数故意只声明不定义 }; class Rectangle : public Shape { double m_a; double m_b; public: Rectangle(double a,double b) { m_a = a; m_b = b; } double area() { return m_a*m_b; } }; class Circle : public Shape { double m_r; public: Circle(double r) { m_r = r; } double area() { return 3.14*m_r*m_r; } }; double area(Shape* s) { cout<<s->area()<<endl; } int main() { Rectangle rect(2,3); Circle circle(4); Shape s;//编译不通过 area(&rect); area(&circle); return 0; }