• C++17 --- 多态性、虚函数、多态的条件、联编(捆绑,绑定)


    一、多态性

    1、多态

    多态性是面向对象程序设计的关键技术之一。 若程序设计语言不支持多态性,不能称为面向对象的语言。

    多态性(polymorphism) 多态性是考虑在不同层次的类中,以及在同一类中,同名的成员函数之间的关系问题。

    函数的重载,运算符的重载,属于编译时的多态性。

    以类的虚成员函数为基础的运行时的多态性,是面向对象程序设计的标志性特征。体现了 类推和比喻的思想方法。

    多态的四种形态:

    重载多态 -- 函数重载、运算符重载
    包含多态 -- virtual 函数
    参数多态 -- 模板
    强制多态 -- 强制类型装换:static_cast , const_cast ,....
    
    • 1
    • 2
    • 3
    • 4

    2、编译时的多态和运行时的多态

    在这里插入图片描述

    3、编译时多态,早期绑定

    int max(int a,int b){ return a > b ? a : b;}
    
    char Max(char a,char b){ return a > b ? a : b;}
    
    double Max(double a,double b) { return a > b ? a : b; }
    
    int main()
    {
    	int x = Max(12, 23) ;
    	char ch = Max('c', 'b');
    	double dx = Max(12.2334.45);
    	return 0 ;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    机器代码,编译链接时的函数名称:
    在这里插入图片描述

    4、运行时的多态

    1)运行时多态的设计思想:

    对于相关的类型,确定它们之间的一些共同特征,(属性和方法) , 将共同特征被转移到基类中,然后在基类中,把这些共同的函数或方法声明为公有的虚函数接口。然后使用派生类继承基类,并且在派生类中重写这些虚函数,以完成具体的功能。这种设计使得共性很清楚,避免了代码重复,将来容易增强功能,并易于长期维护。

    客户端的代码(操作函数)通过基类的弓|用或指针来指向这些派生类型对象,对虚函数的调用会自动绑定到派生类对象上重写的虚函数。

    2)虚函数的定义:

    虚函数是一个类的成员函数,定义格式如下:

    virtual 返回类型 函数名(参数表) ;
    
    • 1

    关键字 virtual 指明该成员函数为虚函数。只能将类的成员函数定义为虚函数。当某一个类的成员函数被定义为虚函数,则由该类派生出来的所有派生类中,该函数始终保持虚函数的特征。

    3)示例:运行时的多态性,晚绑定。

    4)总结:

    运行时的多态:共有继承 + 虚函数 + (指针或引用调用虚函数)。

    二、虚函数

    1、虚表

    如果一个类包含了虚函数,不管有多少个虚函数,则增加了一个指针的大小。
    有了一个虚指针 – VPtr ,vptr 指向一个虚表(vtable),虚表里面存储本类中虚函数的入口地址。

    对于代码:

    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 main()
    {
    	A a;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述
    在这里插入图片描述

    2、虚表的大小

    不论有多少个虚函数,只要有,就多一个指针的大小。

    示例代码:

    class A
    {
    public:
    };
    
    class B
    {
    public:
    	void ffn(){}
    };
    
    class C1
    {
    public:
    	virtual void fa(){cout << "A::fa" << endl;}
    };
    
    class C2
    {
    public:
    	virtual void fa(){cout << "A::fa" << endl;}
    	virtual void fb(){cout << "A::fb" << endl;}
    };
    class C3
    {
    public:
    	virtual void fa(){cout << "A::fa" << endl;}
    	virtual void fb(){cout << "A::fb" << endl;}
    	virtual void fc(){cout << "A::fc" << endl;}
    };
    
    void main()
    {
    	cout << sizeof(A) << endl;// 其为1
    	cout << sizeof(B) << endl;// 也为1
    	cout << sizeof(C1) << endl;//4
    	cout << sizeof(C2) << endl;//4
    	cout << sizeof(C3) << endl;//4
    }
    
    • 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

    运行结果:

    在这里插入图片描述

    3、通过数组名加下标读取到虚表内的函数

    	A a;
    	typedef void (*FUN)(); //定义了一个函数指针
    	FUN pf = NULL; //pf是函数指针类型,为指向返回值为void,里面没有参数的一类函数
    	pf = (FUN)*( (int *)*(int *)( (int *)(&d) + n ) + m ); //pf指向类a内虚表所指向的第一个函数函数名,n、m为自己给的值,+n表示第几张虚表,+m表示虚表内的第几个函数
    	pf();
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4、 一个类的虚表

    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 main()
    {
    	A a;
    	typedef void (*FUN)(); //定义了一个函数指针
    	FUN pf = NULL; //pf是函数指针类型,为指向返回值为void,里面没有参数的一类函数
    	pf = (FUN)*(int *)(*(int *)(&a)); //pf指向类a内虚表所指向的第一个函数函数名
    	pf();
    	pf = (FUN)*((int *)(*(int *)(&a)) + 1);
    	pf();
    	pf = (FUN)*((int *)(*(int *)(&a)) + 2);
    	pf();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    运行结果:

    在这里插入图片描述

    在这里插入图片描述

    5、一个类继承另一个类的虚表

    同名同参的虚函数,会进行重写/覆盖

    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//B内有6个虚函数
    {
    public:
    	virtual void fb(){cout << "B::fb" << endl;}
    	virtual void gb(){cout << "B::gb" << endl;}
    	virtual void hb(){cout << "B::hb" << endl;}
    };
    
    class C:public A//C内有5个虚函数
    {
    public:
    	virtual void fa(){cout << "C::fc" << endl;}//同名同参的虚函数,会进行重写/覆盖
    	virtual void gc(){cout << "C::gc" << endl;}
    	virtual void hc(){cout << "C::hc" << endl;}
    };
    
    void main()
    {
    	cout << sizeof(A) << endl;//4
    	cout << sizeof(B) << endl;//4
    	cout << sizeof(C) << endl;//4
    	cout<<endl;
    
    	B b;
    	typedef void(*FUN)();
    	FUN pf = NULL;
    	pf = (FUN)*(int *)(*(int *)(&b));
    	pf();
    	pf = (FUN)*((int *)(*(int *)(&b)) + 1);
    	pf();
    	pf = (FUN)*((int *)(*(int *)(&b)) + 2);
    	pf();
    	pf = (FUN)*((int *)(*(int *)(&b)) + 3);
    	pf();
    	pf = (FUN)*((int *)(*(int *)(&b)) + 4);
    	pf();
    	pf = (FUN)*((int *)(*(int *)(&b)) + 5);
    	pf();
    	cout<<endl;
    
    	C c;
    	pf = (FUN)*(int *)(*(int *)(&c));
    	pf();
    	pf = (FUN)*((int *)(*(int *)(&c)) + 1);
    	pf();
    	pf = (FUN)*((int *)(*(int *)(&c)) + 2);
    	pf();
    	pf = (FUN)*((int *)(*(int *)(&c)) + 3);
    	pf();
    	pf = (FUN)*((int *)(*(int *)(&c)) + 4);
    	pf();
    }
    
    • 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

    运行结果:
    在这里插入图片描述

    三个类中虚表的内存布局:

    在这里插入图片描述

    6、一个类继承多个基类的虚函数

    多继承 – 一个类继承多个类
    基类有几个,就有几个虚表
    将函数挂到第一个继承的类虚表内

    对于多继承,在子类的对象中,每个父类都有自己的虚表,将最终与类的虚函数放在第一个父类的虚表中,这样做解决了不同的父类类型的指针指向比较清晰
    如果在子类中重写了多个父类的同名同参虚函数,那么在虚表中同样做了修改

    1)没有覆盖的情况

    class A
    {
    public:
    	virtual void fa(){cout << "A::fa" << endl;}
    	virtual void ha(){cout << "A::ha" << endl;}
    };
    
    class B
    {
    public:
    	virtual void fb(){cout << "B::fb" << endl;}
    	virtual void hb(){cout << "B::hb" << endl;}
    };
    
    class C
    {
    public:
    	virtual void fc(){cout << "C::fc" << endl;}
    	virtual void hc(){cout << "C::hc" << endl;}
    };
    
    class D:public A,public B,public C
    {
    public:
    	virtual void fd(){cout << "D::fd" << endl;}
    	virtual void hd(){cout << "D::hd" << endl;}
    };
    
    int main()
    {
    	cout << sizeof(D) << endl ;//大小为12
    	D d;
    
    	typedef void(*FUN)();
    	FUN pf = NULL;
    	pf = (FUN)*(int *)( *(int *)(&d));
    	pf();
    	pf = (FUN)*((int *)( *(int *)(&d)) + 1 );
    	pf();
    	pf = (FUN)*((int *)( *(int *)(&d)) + 2 );
    	pf();
    	pf = (FUN)*((int *)( *(int *)(&d)) + 3 );
    	pf();
    
    	cout<<endl;
    
    	pf = (FUN)*((int *)*(int *)((int *)(&d) + 1 ));
    	pf();
    	pf = (FUN)*( (int *)*(int *)( (int *)(&d) + 1 ) + 1 );
    	pf();
    
    	cout<<endl;
    
    	pf = (FUN)*( (int *)*(int *)( (int *)(&d) + 2 ));
    	pf();
    	pf = (FUN)*( (int *)*(int *)( (int *)(&d) + 2 ) + 1 );
    	pf();
    }
    
    • 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

    运行结果:

    在这里插入图片描述

    四个类的虚表内存布局

    在这里插入图片描述

    2)存在覆盖的情况

    class A
    {
    public:
    	virtual void ff(){cout << "A::fa" << endl;}//
    	virtual void ha(){cout << "A::ha" << endl;}
    };
    
    class B
    {
    public:
    	virtual void ff(){cout << "B::fb" << endl;}//
    	virtual void hb(){cout << "B::hb" << endl;}
    };
    
    class C
    {
    public:
    	virtual void ff(){cout << "C::fc" << endl;}//
    	virtual void hc(){cout << "C::hc" << endl;}
    };
    
    class D:public A,public B,public C
    {
    public:
    	virtual void ff(){cout << "D::ff" << endl;}//
    	virtual void hd(){cout << "D::hd" << endl;}
    };
    
    int main()
    {
    	cout << sizeof(D) << endl ;//大小为12
    	D d;
    
    	typedef void(*FUN)();
    	FUN pf = NULL;
    	pf = (FUN)*(int *)( *(int *)(&d));//A::fa
    	pf();
    	pf = (FUN)*((int *)( *(int *)(&d)) + 1 );
    	pf();
    	pf = (FUN)*((int *)( *(int *)(&d)) + 2 );
    	pf();
    	/*pf = (FUN)*((int *)( *(int *)(&d)) + 3 );
    	pf();*/
    
    	cout<<endl;
    
    	pf = (FUN)*((int *)*(int *)((int *)(&d) + 1 ));
    	pf();
    	pf = (FUN)*( (int *)*(int *)( (int *)(&d) + 1 ) + 1 );
    	pf();
    
    	cout<<endl;
    
    	pf = (FUN)*( (int *)*(int *)( (int *)(&d) + 2 ));
    	pf();
    	pf = (FUN)*( (int *)*(int *)( (int *)(&d) + 2 ) + 1 );
    	pf();
    }
    
    • 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

    运行结果:
    在这里插入图片描述

    四个类的内存布局

    在这里插入图片描述

    三、多态的条件

    1、覆盖(重写):

    1、最少两个类,必须是父子关系;
    2、同名同参虚函数,基类写了virtual,子类可以不写,他会将virtual继承下来
    
    • 1
    • 2

    2、多态的条件:

    1、满足覆盖
    2、基类的指针或者引用指向基类对象或者派生类对象
    
    • 1
    • 2

    四、联编(捆绑,绑定) – 函数调用 和 函数体联系的过程

    联编是指计算机程序彼此关联的过程,是把一个标识符名和一个存储地址联系在一起的过程,也就是把函数的调用和函数的入口地址相结合的过程。

    早捆绑/联编 – 在编译时
    晚捆绑 – 在运行时

    1、静态联编(static binding)早期绑定:

    静态联编是指在编译和链接阶段,就将函数实现和函数调用关联起来。

    C语言中,所有的联编都是静态联编,并且任何一种编译器都支持静态联编。
    C++语言中,函数重载和函数模板也是静态联编。
    C++语言中,使用对象名加点"."成员选择运算符,去调用对象虚函数,则被调用的虚函数是在编译和链接时确定。(称为静态联编)。

    代码示例:

    class A
    {
    public:
    	virtual void fn(){ cout << "A::fn" << endl;}
    };
    class B:public A
    {
    public:
    	virtual void fn(){ cout << "B::fn" << endl;}
    };
    
    class C:public A
    {
    public:
    	void fn(){ cout << "C::fn" << endl;}
    };
    
    void test(A aa)
    {
    	aa.fn();
    }
    
    void main()
    {
    	A a;
    	B b;
    	C c;
    	a.fn();//编译时就知道这是a的fn(),为静态联编
    	b.fn();//编译时就知道这是b的fn()
    	c.fn();
    	cout<<endl;
    
    	test(a);
    	test(b);//编译时就已经确定,test内的fn()是类a内的fn(),在编译时就已经捆绑,为静态联编
    	test(c);
    	
    }
    
    • 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

    在这里插入图片描述

    2、动态联编(dynamic binding)亦称滞后联编(late binding)或晚期绑定:

    动态联编是指在程序执行的时候才将函数实现和函数调用关联起来。
    C++语言中,使用类类型的引用或指针调用虚函数(成员选择符">"),则程序在运行时选择虚函数的过程,称为动态联编。

    代码示例:
    产生多态

    class A
    {
    public:
    	virtual void fn(){ cout << "A::fn" << endl;}
    };
    class B:public A
    {
    public:
    	virtual void fn(){ cout << "B::fn" << endl;}
    };
    
    class C:public A
    {
    public:
    	void fn(){ cout << "C::fn" << endl;}
    };
    
    void test2(A &aa)//引用,传本身
    {
    	aa.fn();
    }
    
    void test3(A *p)//指针
    {
    	p->fn();
    }
    
    
    void main()
    {
    	A a;
    	B b;
    	C c;
    	a.fn();//编译时就知道这是a的fn(),为静态联编
    	b.fn();//编译时就知道这是b的fn()
    	c.fn();
    	cout<<endl;
    
    	test2(a);//在程序执行时,才知道到调用谁的fn(),在程序执行时才将函数实现和函数调用关联起来,为动态联编,也称其为多态
    	test2(b);
    	test2(c);
    	cout<<endl;
    	
    	test3(&a);//在程序执行时,才知道到调用谁的fn(),在程序执行时才将函数实现和函数调用关联起来,为动态联编
    	test3(&b);
    	test3(&c);
    }
    
    • 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

    在这里插入图片描述

    3、不加虚函数,不会产生多态

    class A
    {
    public:
    	 void fn(){ cout << "A::fn" << endl;}
    };
    class B:public A
    {
    public:
    	 void fn(){ cout << "B::fn" << endl;}
    };
    
    
    void test(A aa)
    {
    	aa.fn();
    }
    
    void test2(A &aa)//引用,传本身
    {
    	aa.fn();
    }
    
    void test3(A *p)//指针
    {
    	p->fn();
    }
    
    void main()
    {
    	A a;
    	B b;
    	a.fn();//编译时就知道这是a的fn(),为静态联编
    	b.fn();//编译时就知道这是b的fn()
    	cout<<endl;
    
    	test(a);
    	test(b);//编译时就已经确定,test内的fn()是类a内的fn(),在编译时就已经捆绑,为静态联编
    	cout<<endl;
    	
    	test2(a);//在程序执行时,才知道到调用谁的fn(),在程序执行时才将函数实现和函数调用关联起来,为动态联编,也称其为多态
    	test2(b);
    	cout<<endl;
    	
    	test3(&a);//在程序执行时,才知道到调用谁的fn(),在程序执行时才将函数实现和函数调用关联起来,为动态联编
    	test3(&b);
    	
    }
    
    • 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
  • 相关阅读:
    uni-app学习笔记
    【图像去噪的扩散滤波】图像线性扩散滤波、边缘增强线性和非线性各向异性滤波(Matlab代码实现)
    BIO、NIO、AIO
    云IDE----效率时代的大杀器
    美团即时零售的优势不止“快”
    style样式优先级问题【display:block依旧无法显示DOM元素】
    Python - 深度学习系列38 重塑实体识别5-预测并行化改造
    带你玩转序列模型之NLP与词嵌入(二)
    神经网络解决回归问题,神经网络做回归问题
    unity的debug.log无法显示console控制台
  • 原文地址:https://blog.csdn.net/kyrie_sakura/article/details/128005951