文章目录
什么是多态?
多态的分类:
?运行时的多态
?虚函数的定义:
运行时的多态(晚绑定)
虚函数注意:
虚函数:
前提知识:函数指针的判别
重载、覆盖(重写)、隐藏(重定义)的对比?
?虚函数表
单一继承,无虚函数覆盖:?
单一继承,有覆盖:
多继承下,无覆盖:
多继承下,有覆盖:
什么是多态?
? ? ? ? 多态性是面向程序设计的关键技术之一。若程序设计语言不支持多态性,不能称为面向对象的语言。
? ? ? ? 多态性是考虑在不同层次的类中,以及在同一类中,同名的成员的关系问题。? ??
? ? ? ? 说白了就是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。
多态的分类:
? ? ? ? 多态又分为编译时的多态性(静态的多态性),函数重载,运算符的重载都属于编译时的多态
? ? ? ? 以类的虚成员函数为基础的运行时的多态
?运行时的多态
?虚函数的定义:
? ? ? ? 虚函数是一个类的成员函数,定义格式如下:
? ? ? ? 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)<<endl;
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
}
?内存分布:
日常巩固复习
|