多态性是面向程序设计的关键技术之一。若程序设计语言不支持多态性,不能称为面向对象的语言。
多态性是考虑在不同层次的类中,以及在同一类中,同名的成员的关系问题。
说白了就是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。
多态又分为编译时的多态性(静态的多态性),函数重载,运算符的重载都属于编译时的多态
以类的虚成员函数为基础的运行时的多态
虚函数是一个类的成员函数,定义格式如下:
virtual返回类型 函数名 (参数表)
关键字virtual指明该成员函数为虚函数。virtual仅用于类定义中,如果虚函数在类外定义,则不可加virtual。当某一个类的一个类成员函数被定义为虚函数,则由该类派生出来的所有派生类中该函数始终保持虚函数的特征。
注意:运行时的多态性:公有继承+基类和子类有的同名同参的虚函数+基类的指针或者引用指向基类对象或者派生类对象
定义父类A,子类B继承A,编写测试用例父类对象传入指针,引用
class A { public: virtual void fn() { cout << "A::fn" << endl; } }; class B :public A { public: virtual void fn() { cout << "B::fn" << endl; } }; void test1(A a) { a.fn(); } void test2(A& pa) { pa.fn(); } void test(A* pa) { pa->fn(); } void main() { A a; B b; a.fn();//a直接调用fn b.fn();//b直接调用fn test(&a);//传入a对象地址,找到A下虚表 test(&b);//传入b对象地址,找到B下虚表 test2(a);//同理,找到相应类下虚指针,指向所对应虚表,调用对应虚函数 test2(b); }
虚函数的默认参数是静态绑定的,在重新定义虚函数时候,不重新定义继承而来的参数值
除非在调用时候实际的传递想要的参数
- class Parent
- {
- public:
- virtual void fn(int a = 10)
- {
- cout << "parent fn a = " << a << endl;
- }
- };
- class Child :public Parent
- {
- public:
- virtual void fn(int b = 20)
- {
- cout << "child fn b = " << b << endl;
- }
- };
- void main()
- {
- Child cc;
- Parent* p = &cc;
- p->fn(); //child fn b=10 默认参数静态绑定
- p->fn(100); //child fn b=100
- }
给一个例题看看输出结果:
- class Parent
- {
- public:
- void print()
- {
- cout << "parent print" << endl;
- test();
- }
- virtual void test()
- {
- cout << "parent test" << endl;
- }
- };
- class Child :public Parent
- {
- public:
- void show()
- {
- cout << "Child show" << endl;
- print();
- }
- virtual void test()
- {
- cout << "child test" << endl;
- }
- };
- void main()
- {
- Child cc;
- cc.show();
- }
- 理解函数调用顺序
- 运行结果:
- Child show
- parent print
- child test
注意以下几点:
- 派生类中定义虚函数必须和基类中的虚函数同名外,还必须同参数表,同返回类型。否则被认为是重载,而不是虚函数。如基类中返回基类指针,派生类中返回派生类指针是允许的,这是一个例外
- 只有类的成员函数才能说明为虚函数,这是因为虚函数仅仅适用于有继承关系的类对象。友元函数和全局函数不能作为虚函数
- 静态成员函数,是所有同一类对象共有,不受限于某个对象,不能作为虚函数。
- 内联函数每个对象一个拷贝,无映射关系,不能作为虚函数。
- 构造函数和拷贝构造函数不能作为虚函数。构造函数和拷贝构造函数是设置虚表指针。
- 析构函数可以定义为虚函数,构造函数不能定义为虚函数,因为在调用函数构造时对象还没有完成实例化(虚表指针没有设置)。在基类中及其派生类中都有动态分配的内存空间时,必须把析构函数定义为虚函数,实现撤销对象时的多态性。
- 实现动态多态性时,必须使用基类类型的指针变量或引用,使该指针向基类的不同派生类的对象,并通过该指针指向虚函数,才能实现动态的多态性。
- 函数执行速度要稍微慢一些,为了实现多态性,每一个派生类中均要保存相应虚函数的入口地址表,函数的调用机制也是间接实现。所以多态性总要是付出一定代价,但通用性是一个更高的目标。
- 如果定义放在类外,virtual只能加在函数声明前面,不能加在函数定义前面,正确的定义必须不包括virtual。
void ( *signal( int, void (*func) ( int, int )) ) ( int );
//signal是个函数名,往左看,是个*,说明返回值是个指针,向右看,右边是个(int),说明这个指针指向一个函数,再往左看,左边是函数的返回值类型
//函数指针函数
- void fn()
- {
- cout << "fn" << endl;
- }
- void main()
- {
- void (*p)(); //函数指针---右左法则
- void (*q[3])();//找到q,向右看是个数组,所以q是个数组名,往左看,左边是个*(说明是指针),也就是说数组q中有3个指针,再继续向右看,右边是个(),说明是函数
- //函数指针数组 q[0],q[1],q[2]
- fn(); //地址
- }
- int max(int a, int b)
- {
- return a > b ? a : b;
- }
- int min(int a, int b)
- {
- return a < b ? a : b;
- }
- int Add(int a, int b)
- {
- return a + b;
- }
- void main()
- {
- int (*pf[3])(int, int) = { max,min,Add };
- //pf[0]指向的是max,pf[1]指向的是min,pf[2]指向的是Add
- for (int i = 0; i < 3; i++)
- cout << pf[i](2, 4) << endl;;
- }
当类中存在虚函数时,就会产生虚函数指针vfptr和虚函数表vbtable。
有以下概念:
- 一个类一张虚表,所有对象共享虚函数表vftable。
- 为了实现共享 :每一个对象中有一个指针,称为虚函数指针vfptr,指向虚函数表,这样就可以实现虚函数表的共享。系统会在每一个类中提供虚函数指针vfptr,我们看不见,所以一旦有virtual关键字,那么类的大小就会多加4个,多出来的就是虚函数指针的大小。
- 类中的布局:虚函数指针在前,其他成员变量在后,因为虚函数指针的布局优先级最高(在没有虚继承时)。
- 虚函数表结构,三部分:
- RTTI:run-time type infornation运行时类型信息,在运行阶段提取出来的类型,可以使用typeid()函数获取。
- 偏移:虚函数指针相对于整体作用域的偏移,用整体作用域-vfptr的位置得到。
- 虚函数入口地址
示例1:
- /*只要有虚函数,不管有多少个虚函数,都只多了4个字节,多了一个Vfptr
- vfptr虚指针指向了一个vftable,vftable中存储了当前类的虚函数的入口地址
- */
- #if 0
- class A
- {
- public:
- virtual void fa() { cout << "A::fa" << endl; }
- virtual void fb() { cout << "A::fb" << endl; }
- virtual void fc() { cout << "A::fc" << endl; }
- void fd() { cout << "A::fd" << endl; }
- private:
- int m_i;
- };
- void main()
- {
- cout<<sizeof(A)<
- A a;
- a.fa();
- a.fb();
- //A a;
- //typedef void (*Fun)();
- //Fun pf = NULL; //函数指针pf可以指向类A中的fa,fb,fc
- //pf = (Fun) * ((int*)(*(int*)(&a)));
- //pf(); //A::fa
-
- //pf = (Fun) * ((int*)*(int*)(&a) + 1);
- //pf(); //A::fb
- //pf = (Fun) * ((int*)*(int*)(&a) + 2);
- //pf(); //A::fc
- }
示例2:
- class A
- {
- public:
- virtual void fa() { cout << "A::fa" << endl; }
- virtual void ga() { cout << "A::ga" << endl; }
- virtual void ha() { cout << "A::ha" << endl; }
- };
- class B :public A
- {
- public:
- virtual void fb() { cout << "B::fb" << endl; }
- virtual void gb() { cout << "B::gb" << endl; }
- };
- void main()
- {
- cout << sizeof(B) << endl;
- B b;
- typedef void (*Fun)();
- Fun pf = NULL; //函数指针pf可以指向类B中的fa,fb,fc,fb,gb
- pf = (Fun) * ((int*)(*(int*)(&b)));
- pf(); //A::fa
- pf = (Fun) * ((int*)*(int*)(&b) + 1);
- pf(); //A::ga
- pf = (Fun) * ((int*)*(int*)(&b) + 2);
- pf(); //A::ha
- pf = (Fun) * ((int*)*(int*)(&b) + 3);
- pf(); //B::fb
- pf = (Fun) * ((int*)*(int*)(&b) + 4);
- pf(); //B::gb
- }
根据看监视:
每个类里面只有一个虚指针,指向一个虚标
发现B中的虚表中有5个虚函数,前三个是从A继承过来的虚函数的入口地址,后面两个是B类自己的虚函数
内存分布如下
单一继承,有覆盖:
- class A
- {
- public:
- virtual void fa() { cout << "A::fa" << endl; }
- virtual void fb() { cout << "A::fb" << endl; }
- virtual void fc() { cout << "A::fc" << endl; }
- };
- class B :public A
- {
- public:
- /*
- 覆盖 子类重写了基类的同名同参的virtual函数,在B类的对象模型中,在继承下来虚表的同一处地址修改成B的重写的函数
- */
- virtual void fa() { cout << "B::fa" << endl; }
- virtual void gb() { cout << "B::gb" << endl; }
- private:
- int m_i;
- int m_j;
- };
- void main()
- {
- B b;
- }
多继承下,无覆盖:
对于多继承,每个父类都有自己的虚表
将最终子类的虚函数放在第一个父类的虚表中
这样做解决了不同的父类类型的指针指向比较清晰
- class A
- {
- public:
- virtual void fa() { cout << "A::fa" << endl; }
- virtual void ga() { cout << "A::ga" << endl; }
- };
- class B
- {
- public:
- virtual void fb() { cout << "B::fb" << endl; }
- virtual void gb() { cout << "B::gb" << endl; }
- };
- class C
- {
- public:
- virtual void fc() { cout << "C::fc" << endl; }
- virtual void gc() { cout << "C::gc" << endl; }
- };
- class D :public A, public B, public C
- {
- public:
- virtual void fd() { cout << "D::fd" << endl; }
- virtual void gd() { cout << "D::gd" << endl; }
- };
-
- void main()
- {
- D d;
- cout << sizeof(D) << endl;
- }
监视其内存:
多继承下,有覆盖:
不相同的子类虚函数会贴在第一个父类虚表后面的位置。
相同的全部覆盖。
- class A
- {
- public:
- virtual void f() { cout << "A::f" << endl; }
- virtual void ga() { cout << "A::ga" << endl; }
- };
- class B
- {
- public:
- virtual void f() { cout << "B::f" << endl; }
- virtual void gb() { cout << "B::gb" << endl; }
- };
- class C
- {
- public:
- virtual void f() { cout << "C::f" << endl; }
- virtual void gc() { cout << "C::gc" << endl; }
- };
- class D :public A, public B, public C
- {
- public:
- virtual void f() { cout << "D::f" << endl; }
- virtual void gd() { cout << "D::gd" << endl; }
- };
-
- void main()
- {
- D d;
- cout << sizeof(D) << endl;
- //函数指针
-
- typedef void(*Fun)();
- //第一个虚表
- Fun pf = (Fun) * (((int*)*(int*)(&d)));
- pf();//D:f
- pf = (Fun) * (((int*)*(int*)(&d)) + 1);
- pf();//A:ga
- pf = (Fun) * (((int*)*(int*)(&d)) + 2);
- pf();//D:gd
- //第二个虚表
- pf = (Fun) * ((int*)*(int*)((int*)(&d) + 1));
- pf();//D:f
- pf = (Fun) * ((int*)*(int*)((int*)(&d) + 1) + 1);
- pf();//B:gb
- //第三个虚表
- pf = (Fun) * ((int*)*(int*)((int*)(&d) + 2));
- pf();//D:f
- pf = (Fun) * ((int*)*(int*)((int*)(&d) + 2) + 1);
- pf();//c:gc
-
- }
内存分布:
日常巩固复习