摘要
这一章讲述了C语言中的多态和虚函数,解决了许多难题。我们了解了多态的基本原理,虚函数的实现方式,以及虚表的内存布局。在菱形继承体系中,虚表在每个类中的布局也得到了合理的解决。
正文
C 多态
C 多态与虚函数
此章內容致力于处理下列好多个难题:
- 什么叫 C 多态, C 多态的完成基本原理是啥
- 什么叫虚函数,虚函数的完成基本原理是啥
- 什么叫虚表,虚表的运行内存构造合理布局怎样,虚表的第一项(或第二项)是啥
- 棱形承继(类 D 另外承继 B 和 C,B 和 C 又承继自 A)管理体系下,虚表在每个类中的合理布局怎样,假如类 B 和类 C 另外有一个组员发生变化 m,m 怎样在 D 目标的内存地址上遍布的,是不是会互相遮盖
- 存有虚函数的类目标size测算
什么叫 C 多态, C 多态的完成基本原理是啥
在 C 编程设计中,泛素化就是指具备不一样作用的涵数可以用同一个涵数名,那样就可以用一个涵数名启用不一样內容的涵数。在面向对象编程方式 中,一般是那样描述泛素化的:向不一样的目标推送同一个信息,不一样的目标在接受的时候会造成不一样的个人行为(即方式 );换句话说,每一个目标可以用自身的方法去回应一同的信息说白了信息,便是函数调用,不一样的个人行为是指不一样的完成,即实行不一样的涵数。换句话说,可以用一样的插口浏览作用不一样的涵数,进而完成“一个插口,多种多样方式 ”。在C 中关键分成静态数据多态和动态性多态二种,在程序执行前就进行联编的称之为静态数据多态,关键根据函数重载和模版完成,动态性多态在程序执行时才进行联编,关键根据虚函数完成。
函数重载实例:
void print_hello(string name){
cout << "hello " << name << endl;;
}
void print_hello(string pre_, string name){
cout << "hello, " << pre_ << " " << name << endl;
}
int main(){
print_hello("fan");
print_hello("Mr.", "fan");
return 0;
}
// out
/*
hello fan
hello, Mr. fan
*/
函数模板实例:
template <class T>
T add_two_num(T a, T b){
return a b;
}
int main(){
cout << add_two_num<int>(1, 2) << endl;
cout << add_two_num<float>(1.0, 2.0) << endl;
cout << add_two_num(1.0, 2.0) << endl; // c语言编译器全自动推论
// cout << add_two_num(1, 2.0) << endl; // error
int va = 10;
cout << add_two_num<decltype(va)>(va, 2.0) << endl;
return 0;
}
// out
/*
3
3
3
12
*/
虚函数实例:
class A{
public:
virtual void fun(){
cout << "hello A" << endl;
}
};
class B:public A{
public:
virtual void fun(){
cout << "hello B" << endl;
}
};
int main(){
A *a = new A();
a->fun();
a = new B();
a->fun();
return 0;
}
// out
/*
hello A
hello B
*/
运作期多态完成基本原理
针对一个承继管理体系而言,假如在基类的涵数前再加上virtual关键词,在派生类中调用该涵数,运作时可能依据目标的具体种类来启用相对应的涵数。假如目标种类是派生类,就启用派生类的涵数;假如目标种类是基类,就启用基类的涵数。运作期多态便是根据虚函数和虚函数表完成的。一个带有虚函数的类中最少都是有一个虚函数表表针,且有一个虚表,虚函数指针偏向虚函数表。虚表能够 承继,假如派生类沒有调用虚函数,那麼派生类虚表中依然会出现该涵数的详细地址,只不过是这一详细地址偏向的是基类的虚函数完成。假如基类有3个虚函数,那麼基类的虚表中就会有三项(虚函数详细地址),派生类也会出现虚表,最少有三项,假如调用了相对应的虚函数,那麼虚表中的详细地址便会更改,偏向本身的虚函数完成。假如派生类有自身的虚函数,那麼虚表中便会加上此项。派生类的虚表中虚函数详细地址的顺序排列和基类的虚表中虚函数详细地址顺序排列同样。
什么叫虚函数,虚函数的完成基本原理是啥
形象化上而言,虚函数便是类中应用 virtual 关键词叙述的涵数。虚函数的功效主要是完成了多态的体制,基类界定虚函数,派生类能够 调用该涵数;在派生类中对基类界定的虚函数开展调用时,必须在派生类中申明该方式 为虚方式 ,不然可能产生遮盖。虚函数的最底层完成体制根据虚函数表 虚表表针。
c语言编译器解决虚函数的方式 是:为每一个类目标加上一个掩藏组员,掩藏组员中储存了一个偏向涵数详细地址二维数组的表针,称之为虚表表针(vptr),这类二维数组变成虚函数表(virtual function table, vtbl),即,每一个类应用一个虚函数表,每一个类目标用一个虚表表针。假如派生类调用了基类的虚方式 ,该派生类虚函数表将储存调用的虚函数的详细地址,而不是基类的虚函数详细地址。假如基类中的虚方式 沒有在派生类中调用,那麼派生类将承继基类中的虚方式 ,并且派生类中虚函数表将储存基类中未被调用的虚函数的详细地址。留意,假如派生类中界定了新的虚方式 ,则该虚函数的详细地址也将被加上到派生类虚函数表中,虚函数不管多少个都只必须在目标中加上一个虚函数表的详细地址。启用虚函数时,程序流程将查询储存在目标中的虚函数表详细地址,转为相对应的虚函数表,应用类申明中界定的几个虚函数,程序流程就应用二维数组的几个涵数详细地址,并实行该涵数。
详尽请参照
什么叫虚表,虚表的运行内存构造合理布局怎样,虚表的第一项(或第二项)是啥
针对每一个存有虚函数的类而言,其都带有一个虚函数表与最少一个虚表针。虚函数表表针(vfptr)偏向虚函数表(vftbl)的某一项,虚函数表中依照目标承继的排列顺序目标的虚函数详细地址,虚基类表格中依照目标承继的排列顺序目标的立即虚继承类到虚基类的偏位。
针对虚基类而言,虚表中按申明次序先后储存全部虚函数详细地址。
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
int main(){
Base *b = new Base();
}
虚表明例:
表格中最终的一个点表明虚函数完毕标示
一般承继的状况下,虚函数依照其申明次序放在表格中,且父类的虚函数在派生类的虚函数前边。
class Derive: public Base{
public: virtual void f1() { cout << "Base::f" << endl; }
virtual void g1() { cout << "Base::g" << endl; }
virtual void h1() { cout << "Base::h" << endl; }
};
一般承继且存有虚函数遮盖的状况,遮盖的虚函数将被放进虚表中原先父类虚函数的部位,沒有被遮盖的涵数按以前的次序储存,最终在表格中加上派生类添加的虚函数详细地址。
class Derive: public Base{
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g1() { cout << "Base::g" << endl; }
virtual void h1() { cout << "Base::h" << endl; }
};
多种承继(无虚函数遮盖)时,每一个父类都是有自身的虚表,且派生类的友元函数被放进了第一个父类的表中。
class Base1 {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
class Base2 {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
class Base3 {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
class Derive: public Base1, public Base2, public Base3{
public:
virtual void f1() { cout << "Base::f" << endl; }
virtual void g1() { cout << "Base::g" << endl; }
};
多种承继(有虚函数遮盖)时,父类虚表中相匹配的虚函数详细地址将褥子类的虚函数详细地址遮盖,派生类添加的虚函数详细地址将被加上到第一个父类的虚函数表以后。
class Derive: public Base1, public Base2, public Base3{
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g1() { cout << "Base::g" << endl; }
};
综上所述,虚表的第一项(或第二项)是父类或是派生类申明的第一(二)个虚函数详细地址。
棱形承继(类 D 另外承继 B 和 C,B 和 C 又承继自 A)管理体系下,虚表在每个类中的合理布局怎样,假如类 B 和类 C 另外有一个组员发生变化 m,m 怎样在 D 目标的内存地址上遍布的,是不是会互相遮盖
虚表会另外存有2个A,运行内存遍布与多继承一致,即存有2个虚表针偏向2个父类虚表(B, C),B和C的虚表中又另外存有A的虚表。虚表运行内存实体模型以下:
class Base
{
public:
Base (int a = 1):base(a){}
virtual void fun0(){cout << base << endl;}
int base;
};
class Base1:public Base
{
public:
Base1 (int a = 2):base1(a){}
virtual void fun1(){cout << base1 << endl;}
int base1;
};
class Base2:public Base
{
public:
Base2 (int a = 3):base2(a){}
virtual void fun2(){cout << base2 << endl;}
int base2;
};
class Derive: public Base1, public Base2
{
public:
Derive (int value = 4):derive (value){}
virtual void fun3(){cout << derive << endl;}
int derive;
};
最先得出结果,不容易相互之间遮盖,但是立即应用 m 得话可能导致二义性难题,它是能够 应用类名 引入的方法开展启用
class A{
};
class B:public A{
public:
int n;
B(){
A();
n = 2;
}
};
class C: public A{
public:
int n;
C(){
A();
n = 3;
}
};
class D: public B, public C{
public:
void fun(){
cout << B::n << endl;
cout << C::n << endl;
cout << sizeof(D);
}
};
int main(){
D d;
d.fun();
return 0;
}
// out
/*
2
3
8
*/
运行内存遍布为
D |
---|
B::n |
C::n |
存有虚函数的类目标size测算
空类的尺寸为1,由于在C 中一切目标都必须有一个详细地址,最少为1。针对存有虚函数的类而言,最少存有一个虚函数指针,表针尺寸与设备有关(int),在64位的设备上,应是8字节,在32位系统的设备上为4字节。在开展测算的情况下也要留意1. 不一样的基本数据类型会开展两端对齐 2.针对多种承继,多种承继好多个基类就几个虚表针。
class A{
int n;
};
class B{
int n;
double m;
};
class C{
int n;
int l;
double m;
};
class D {
int n;
double m;
int l;
};
int main(){
A a;
B b;
C c;
D d;
cout << sizeof(a) << " " << sizeof(b) << " " << sizeof(c) << " " << sizeof(d);
return 0;
}
// out
/*
4
16 // int和double两端对齐 (4->8)
16
24 // n向m两端对齐,随后l和m两端对齐
*/
class A {
virtual void fun() {
}
};
class B {
virtual void fun() {
}
};
class C : A, B {
virtual void fun() {
}
};
class D : A {
virtual void fun() {
}
};
class E : C {
virtual void fun() {
}
};
int main() {
A a;
B b;
C c;
D d;
E e;
cout << sizeof(a) << " " << sizeof(b) << " " << sizeof(c) << " " << sizeof(d) << " " << sizeof(e) << endl;
}
// out
/*
4
4
8
4
8
*/
一个带有虚函数的类中带有的虚函数表表针数量
一个带有虚函数的类中最少都是有一个虚函数表表针,由于虚函数的详细地址要被放进虚函数表(虚表)中。当存有多种承继时,多种承继了好多个基类,派生类将带有好多个虚表针,而且此表针具备传递性。
class A {
virtual void fun() {
}
};
class B {
virtual void fun() {
}
};
class C : A, B {
virtual void fun() {
}
};
class D : A {
virtual void fun() {
}
};
class E : C {
virtual void fun() {
}
};
int main() {
A a;
B b;
C c;
D d;
E e;
cout << sizeof(a) << " " << sizeof(b) << " " << sizeof(c) << " " << sizeof(d) << " " << sizeof(e) << endl;
}
// out
/*
4
4
8
4
8
*/
参照连接
后台开发:关键技术与运用实践活动 — C
C 之泛素化
C 函数模板
C 虚函数和虚函数表基本原理
C | 虚函数表运行内存合理布局
虚函数完成基本原理
c 中虚基类表和虚函数表的合理布局
c 承继归纳(单承继、多继承、虚继承、棱形承继)
C 承继运行内存合理布局 – 多继承(无虚继承)
关注不迷路
扫码下方二维码,关注宇凡盒子公众号,免费获取最新技术内幕!
评论0