• 多态--下


    概念

    优先使用组合、而不是继承;
    继承会破坏父类的封装、因为子类也可以调用到父类的函数;
    子类必须实现父类的纯虚函数;
    内联函数没有地址,他是直接在定义的地方展开。所以不能是虚函数、虚函数是要有地址的;
    虚函数不能是static函数,因为没有this指针、等于说是全局的,虚表要的是对象里面的;
    普通函数继承是实现继承,就是不去重写父类的函数体,直接用的;虚函数的继承目的就是要重写这个函数体,自改成自己想要的;
    虚函数在编译阶段就生成了,并且存在代码段;

    析构函数最好写成多态。多态就是指向谁调谁,这样子类就可以调用自己的析构函数
    构造函数不能写成多态;因为对象中的虚表指针实在初始化列表时开始初始化的;

    多态如何实现的指向谁调谁?

    虚函数的地址存在虚函数表中,通过虚函数表来调用虚函数;
    同类型的对象共用一个虚表;
    子类不重写父类的虚函数,也是和父类共用同一张虚表;重写就不会共用虚表

    例子
    #define _CRT_SECURE_NO_WARNINGS
    
    #include 
    using namespace std;
    
    class person
    {
    public:
    	virtual void BuyTicket()
    	{
    		cout << "成人票" << endl;
    	}
    };
    
    class student : public person
    {
    public:
    	virtual void BuyTicket()
    	{
    		cout << "学生票" << endl;
    	}
    };
    
    
    void buy(person& b)
    {
    	b.BuyTicket();
    }
    
    
    int main()
    {
    	student s;
    	person p;
    	buy(s);
    	buy(p);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    分析

    1、子类继承后,虚函数表是一样的,重写后就把虚函数表copy出来换一个地址,把新的内容覆盖了;
    image.png
    2、调用时传过去的参数累心是谁的就指向谁的虚函数表,s是student类型那就是用student里面的函数;
    image.png
    2、子类虚函数不重写会怎样
    如果不重写,那父级和子级虚函数表的地址是相同的
    image.png

    含有虚函数类的大小是多少?

    普通函数不占空间,虚函数有个指针、所以站指针的大小空间,32位的4字节、64位的8字节;
    在类里面:虚函数占一个指针的大小,不管有多少个虚函数就只占一个指针的大小;对象里面只是存了一个指针指向这个虚表
    虚表的大小才要看有几个虚函数;
    多继承的子类,继承了几个父类就有几个父类的指针大小
    如下就一个虚函数64位
    image.png
    计算方法和结构体其实一样,内存对齐
    加了一个int变量,理应8+4 = 12,但是最小对齐数为8,类的大小是其成员最小对齐数的整数倍;所以是16;
    image.png

    虚函数地址

    #define _CRT_SECURE_NO_WARNINGS
    
    #include 
    using namespace std;
    
    class person
    {
    public:
    	virtual void BuyTicket()
    	{
    		cout << "成人票" << endl;
    	}
    private:
    	int a;
    };
    
    class student : public person
    {
    public:
    	virtual void BuyTicket()
    	{
    		cout << "学生票" << endl;
    	}
    };
    
    
    void buy(person& b)
    {
    	b.BuyTicket();
    }
    
    
    class base
    {
    public:
    	virtual void func1()
    	{
    		cout << "base::func1" << endl;
    	}
    private:
    	int _b = 1;
    };
    
    
    
    void func()
    {
    	person b1;
    	printf("vftptr:%p\n", *(int*) & b1);
    
    	int i = 0;
    	int* p1 = &i;
    	int* p2 = new int;
    	const char* p3 = "sad";
    	printf("栈变量:%p\n", p1);
    	printf("堆变量:%p\n", p2);
    	printf("代码段常量:%p\n", p3);
    	printf("代码段函数地址:%p\n", &base::func1);
    
    }
    
    
    int main()
    {
    	student s;
    	person p;
    	buy(s);
    	buy(p);
    	int size = sizeof(s);
    	cout << size << endl;
    	func();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    问题:虚函数存在哪?虚函数表存在哪?
    答:虚函数和普通函数都存在代码段、虚函数表也存在代码段
    image.png
    普通的函数、函数名就是地址(首地址)
    成员函数的地址要加取地址符号&,以及要指定属于哪个类,也就是这样 &类名::函数名

    虚表地址

    class Base
    {
    public:
    	virtual void fuc1(){cout << "fuc1" << endl;}
    	virtual void fuc2(){cout << "fuc2" << endl;}
    private:
    	int a;
    };
    
    class derive : public Base
    {
    public:
    	virtual void fuc1(){ cout << "fuc1111" << endl; }
    	virtual void fuc3(){cout << "fuc3" << endl;}
    };
    
    
    
    typedef void(*VF_PTR)();//函数指针类型怎么写的
    void PrintVFTable(VF_PTR* pTable)
    {
    	for(size_t i = 0; pTable[i] != 0; ++i)
    	{
    		printf("vfTable[%d]:%p->", i, pTable[i]);
    		VF_PTR f = pTable[i];
    		f();//运行函数
    	}
    	cout << endl;
    }
    
    
    
    
    int main()
    {
    	//student s;
    	//person p;
    	//buy(s);
    	//buy(p);
    	//int size = sizeof(s);
    	//cout << size << endl;
    	//func();
    
    	derive a;
    	Base b;
    	PrintVFTable((VF_PTR*)(*(int*)&b));
    	PrintVFTable((VF_PTR*)(*(int*)&a));
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    父类上面两行、子类为下面三行;
    子类fuc1重写过了虚表地址就变了,没写fuc2但是继承了fuc2的虚表地址;
    image.png
    强转两次,(int*)是要取前4个字符,(VF_PTR*)强转函数指针
    image.png
    函数指针怎么写 void(*)()
    type void(*a)(),这个a就代表函数指针了

    多继承的子类的大小怎么计算?

    继承几个父类就含有几个指针;

    class Base1
    {
    public:
    	virtual void fuc1() { cout << "b1:fuc1" << endl; }
    	virtual void fuc2() { cout << "b1:fuc2" << endl; }
    private:
    	int a1;
    };
    
    
    class Base2
    {
    public:
    	virtual void fuc1() { cout << "b2:fuc1" << endl; }
    	virtual void fuc2() { cout << "b2:fuc2" << endl; }
    private:
    	int a2;
    };
    
    class derive2 : public Base1, public Base2
    {
    	virtual void fuc1() { cout << "b2:fuc1" << endl; }
    	virtual void fuc3() { cout << "b2:fuc2" << endl; }
    
    private:
    	int a3;
    };
    
    
    int main()
    {
    	//student s;
    	//person p;
    	//buy(s);
    	//buy(p);
    	//int size = sizeof(s);
    	//cout << size << endl;
    	//func();
    
    	//derive a;
    	//Base b;
    	//PrintVFTable((VF_PTR*)(*(int*)&b));
    	//PrintVFTable((VF_PTR*)(*(int*)&a));
    
    	cout << sizeof(derive2) << endl;
    	derive2 a1;
    	PrintVFTable((VF_PTR*)(*(int*)&a1));
    	PrintVFTable((VF_PTR*)(*(int*)((char*)&a1 + sizeof(Base1))));
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    image.png
    继承两个父类,两个指针的大小;
    不同父类的虚表继承后不会共用;
    derive2成员函数fuc3的虚表和Base1的虚表共用了,随机共用一个父类的虚表;

    练习题

    1、
    image.png
    2、
    类中就只有变量、按顺序往下排;
    p3是指向整块区域,所以首地址也是b1存的首地址;
    p1也是b1存的首地址;
    p2是b2的首地址;
    所以选c;
    image.png

    虚函数和虚继承

    image.png
    是两个完全不同的概念;
    用的都是virtual;

    虚继承
    image.png
    菱形继承时,B、C继承A,D继承BC;

    如上图使用虚继承,就会把公共的变量_a,放到一个地址上;
    如果不用虚继承的话,就会有三个_a的地址;造成数据的冗余性;
    二义性也是不用虚继承造成的,就是再回去找_a时,有很多_a;

  • 相关阅读:
    Abp框架Web站点的安全性提升
    【MAUI】页面导航-await Shell.Current.GoToAsync()与Navigation.PushAsync()
    Sliver C2 实战 vulntarget-f
    C++数据结构:线性表查找
    IOS开发者自带弱网测试工具界面说明NETWORK LINK CONDITIONER
    Mac无法打开CORE Keygen
    解决错误代码0x0000011b的方法,教你一键修复0x0000011b
    王熙凤穿越到 2022 年,一定会采购的单点登录服务
    C#高效查表算法及线性插值算法实例
    自增自减运算符i++与++i的区别
  • 原文地址:https://blog.csdn.net/user2604530353/article/details/137299307