C++——多态调用和普通调用的本质区别

       目录      

一.多态特性

        回顾一下多态特性的含义:

        回顾多态特性的两大形成条件:

而普通调用和多态调用的本质区别在于:

二.理解调用

例一: 

        普通调用的理解1:

        普通调用的理解2

        注:错误代码的使用

        普通调用的理解3:

 

        例二:注:例二与例一的父子类代码完全相同!

        多态调用的理解1:

代码解析:

        多态调用的理解2:

三.案例总结:


一.多态特性

        在学习多态特性的过程中,我们总是不明白什么样的函数调用就是多态调用,所以我们今天来梳理一下多态调用。       

        回顾一下多态特性的含义:

        多态!通俗来说,就是多种形态,具体上就是去完成某个行为,即当不同的对象去完成某个行为时会产生出不同的状态。

        回顾多态特性的两大形成条件:

1.被调用的函数必须是虚函数,且子类必须对父类的虚函数进行重写;
2.必须是父类指针指向子类对象,然后父类指针调用重写后的函数;

 

        之前我们在一般情况下调用的类成员函数,几乎都是普通调用,

而普通调用和多态调用的本质区别在于:

        多态调用:父类指针-->与指向的类或对象有关,指向的对象是什么类,就调用什么类的函数;

        普通调用:当对象调用函数时,与对象的类型有关,什么类型的对象或者指针调用函数,就调用什么类型的函数。

上面这几行内容是我们理解这两种调用的核心理念,所以我将会通过多个例子来帮助大家去理解多态调用和普通调用的本质。

二.理解调用

例一: 

        普通调用的理解1:
class Base {
public:
	virtual void Func1() {
		cout << "Base::Func1()" << endl;
	}

	 void Func3() {
		cout << "Base::Func3()" << endl;
	}

protected:
	string _name;
};

class Derive :public Base {
private:
	 virtual void Func1() {
		cout << "Derive::Func1()" << endl;
	}
	void Func3() {
		cout << "Derive::Func3()" << endl;
	}
};

int main() {
    cout << "普通调用1:" << endl;
    Base bb;
    Derive dd;	

	bb.Func1();
	bb.Func3();
	//以上两次都是父类对象调用自身的函数

	dd.Func1();
	dd.Func3();
    //以上两次都是子类对象调用自身的函数
	    return 0;

}

代码解析:

        这是两个不同类的类对象对各自成员函数调用,从而调用相应的重写函数。是类对象直接调用,并不符合多态特性形成的条件——这叫普通调用

        普通调用的理解2:

int main(){
    cout << "普通调用2:" << endl;

	Base bb;
	Derive dd;

	Derive* dtr = &dd;
	dtr->Func1();		//普通调用
	
    Base* bpr=&bb;
    bpr->Func1();       //普通调用

    return 0;
    }

代码解析:

        这两个类指针对象调用虽然都调用了虚函数Func1,但本质上是各类的指针对象去指向各自类的对象,进而调用相应的成员函数,所以这也是各类的指针对象调用各个类的函数,不符合多态特性形成的条件2——所以也是普通调用。

注:错误代码的使用
 Derive* dpr = &bb;	

        对于上面这句代码来讲是会报错的,因为编译器不支持将父类对象向下转换子类对象——也就是说子类指针不能指向父类对象。

 Derive* dpr = new Base();  

        这句代码报错的原因也是与上面的一样,子类指针去指向父类的匿名对象,也是不支持的.

        普通调用的理解3:

int main(){
    cout << "普通调用3:" << endl;
 
	Base bb;
	Derive dd;

 //父类指针指向子类对象
    Base* bbtr =&dd;
	bbtr->Func3();

    return 0;
    }

        父类指针指向子类的对象,符合多态形成的条件2,但也仅会指向子类对象中从父类继承过来的成员变量和成员函数,并不会指向子类对象自己新建的成员,因为bbtr调用Func3时,发现虚表中没有该函数,就确定了Func3并不是虚函数,采用的仍是普通调用(当对象调用函数时,与调用对象类型有关,类型是Base*,那么就调用Base类的Func3()函数!!!)。

        而bbtr得类型是父类Base,所以还是调用子类对象中从父类继承过来的Func3函数,所以结果是Base:Func3()

        以上就是在同一个案例中,不同的方式执行的普通调用,对于多态形成的两个条件而言,只要有一条不符合那就一定是普通调用!


 

例二:注:例二与例一的父子类代码完全相同!

        多态调用的理解1:

class Base {
public:
	virtual void Func1() {
		cout << "Base::Func1()" << endl;
	}

	 void Func3() {
		cout << "Base::Func3()" << endl;
	}

protected:
	string _name;
};

class Derive :public Base {
public:
	 virtual void Func1() {
		cout << "Derive::Func1()" << endl;
	}
	void Func3() {
		cout << "Derive::Func3()" << endl;
	}
};


int main() {

    Base b;
    Derive d;
    //多态调用
	cout << "多态调用1:" << endl;
	Base* btr = &b;
	btr->Func1();

	btr = &d;
	btr->Func1();

    return 0;
}
代码解析:

        Base* btr = &b;
        btr->Func1();

        由上可知,父类和子类各创建了一个对象而父类创建了一个指针,指向了父类对象,那么btr就会指向整个父类对象的地址,btr根据父类对象的地址找到vfptr(父类对象的虚表指针),调用函数Func1就是指针通过虚函数表中寻找该函数Func1的地址,找到后进行调用。 

代码解析:

    btr = &d;
    btr->Func1();

       多态调用完成(以上代码完全符合多态特性的形成条件)形成多态调用的基本要素:父类有指针指向父类自己的对象,且调用了虚函数,那么指向谁的对象,就调用谁的函数;

        父类创建的指针btr,又指向了子类对象的地址,那么btr指向的只是子类对象中从父类继承过来的那部分成员变量和函数罢了——切片思想!


       btr根据子类对象的地址找到了子类对象d的vfptr,因为子类Derive是继承的父类Base,那么父类的虚函数也会被继承下来,那么子类的虚函数表就是将父类的虚表拷贝一份到子类中,又因为子类重写了要调用的函数Func1,该函数的地址会进行更换,从而当btr调用函数时,从虚表中寻找该函数的地址,因为更换过Func1的地址,那么btr调用的就是子类的Func1了。

        多态调用的理解2:

        这是多态调用的另一种方式:父类指针指向类时,指向的是谁的类,调用的就是哪个类相应的函数。

int main(){
    cout << "多态调用2:" << endl;
	Base* btr2 =new Base;
	btr2->Func1();

	 btr2 = new Derive;
	btr2->Func1();
	cout << endl;
    return 0;
}


案例总结:

案例的难点在于多态调用1的例子:

        当父类的指针指向了父类对象的地址时,父类指针指向的是整个父类对象,而当父类的指针指向了子类对象的地址时,父类指针指向的仅是子类对象中从父类继承过来的成员变量与成员函数,所以当父类指针指向子类对象地址后,我来解释一下:结果所调用的函数为什么是父类的函数!例:

Base* bbtr=&dd;                //dd是子类对象  

bbtr->Func3();    

    bbtr调用的这个Func3函数有两个,从两个类的情况来看,一种是父类继承给子类的Func3(),一种是子类自己新建的Func3()。 而执行结果后,显示的是:指针调用了Base:Func3()

        对于bbtr指针的路线,它并不是去到父类中调用Func3,而是因为它指向了子类对象的地址,这个地址说具体就是指向了子类对象从父类继承过来的Func3函数地址,然后bbtr指针发现Func3并不是虚函数后,就直接去调用了子类对象中从父类继承过来的Func3(),因为bbtr是父类指针,它并不认识子类自己新建的Func3(),由于切片思想,它指向的那块虚表地址空间(虚表也是从父类继承过来的!)中并没有子类自己新建的Func3()  !!!   

        所以就是一句话,父类的指针在指向子类对象时,所调用的重写虚函数是如何能形成多态的,原因就在于父类指针能够轻松的的访问到子类的虚表地址空间中的相应虚函数。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
俄罗斯方块是一款经典的游戏,多年来一直受到广大玩家的喜爱。MFC(Microsoft Foundation Class)是一套用于Windows编程的C++类库,可以帮助开发者快速创建Windows界面应用程序。 在俄罗斯方块游戏中,我们可以使用多态调用MFC来实现游戏的各种功能。 首先,我们可以创建一个基类TetrisBlock,其中包含俄罗斯方块的基本属性和方法,比如获取方块形状、旋转方块等。 然后,我们可以创建多个继承自TetrisBlock的子类,每个子类代表不同类型的俄罗斯方块,比如I型、T型、L型等。这些子类可以重写基类的方法,以实现自身特定的功能。 接下来,我们可以使用MFC提供的类和方法来创建游戏界面,比如使用CWnd类创建一个窗口,使用CDC类在窗口中绘制游戏界面等。 在游戏进行过程中,我们可以使用多态调用来实现不同类型的俄罗斯方块的移动和旋转。比如,通过创建一个指向基类TetrisBlock的指针,将其指向任意一个子类对象,然后调用指针所指向对象的方法来进行操作。 此外,我们还可以使用MFC提供的类和方法实现用户交互,比如通过CButton类创建按钮,通过CDialog类创建对话框等。这样,玩家就可以使用鼠标或键盘来控制俄罗斯方块的移动、旋转和其他操作。 总之,通过多态调用MFC,我们可以方便地实现俄罗斯方块游戏的各种功能和界面。这样,开发者可以更加专注于游戏的逻辑和用户体验,而无需过多关注底层的实现细节。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橙予清的zzz~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值