为什么要使用多态,而不直接使用派生类对象调用虚函数?在构造函数以基类引用作形参,以派生类对象作传入的实参。

c++中基类指针指向派生类时,调用的虚函数是派生类的。那为何不直接用派生类的对象来调用就行了,设计虚函数的真正意图是什么?

直接用派生类的对象是可以的,但是虚函数的作用体现在多态上。在实际编程中,往往不直接通过派生类对象来调用其成员函数,而是通过一个指向派生类对象的指针(或引用)来调用其成员函数。这在面向对象编程中非常常见。
其好处是:通过这个基类指针或者引用来调用虚函数的时候,实际执行的是派生类对象的函数,使用这个指针或者引用的一方的代码不必随着派生类的不同而改变,却可以达到执行最适合这个派生类的函数(也就是这个派生类自己的成员函数)的目的;
另一方面可以使程序模块具有很好的可替换性,用一个派生类替换另一个派生类,程序的其它部分不需要做任何改动就可以正常运行而且发挥出新的派生类的特性。
tip:从底层往上访问,而非从顶层往下执行。
下面举个例子:
比如类pri_student是个基类表示小学生,而大学生、硕士生由于与小学生存在一些共性,所以由小学生继承而来,分别为undergraduate、graduate类。他们有各自的“交学费”函数为virtual void fun()。
现在有一群这些类的对象,我要写一个类University表示大学,它有成员函数void charge_tuition()实现以某个对象为参数,使其执行“交学费”这一操作。由于每个学生类的“交学费”操作是不同的,所以为了实现这一目的,这个成员函数在University中要重载两次,分别为:
charge_tuition(undergraduate& x)
{x.fun();};

charge_tuition(graduate& x)
{x.fun();};
这样才能区分传进去的是哪个类的对象在调用fun,但这样在继承得多了的时候显得很麻烦。比如现在增加一个类为博士生phd类,那么需要在University中再重载一次charge_tuition(),这次的参数类型变为phd& 。
而利用虚函数的多态机制就可以解决这样的问题。
在类University中只用写一次charge_tuition(pri_student* x); (或引用)

此时传undergraduate* A、graduate* B、phd* C三个指针(或者引用)进去,就可以相应地调用各自的fun()了,不用写三种chanrge_tuition的函数。下面以代码说明:

class pri_student{  
    public:  
    virtual void fun(){  
    cout<<"小学生交学费"<<endl;  
    }  
};  
class undergraduate: public pri_student{  
    public:  
    virtual void fun(){  
    cout<<"本科生交学费"<<endl;  
    }  
};  
class graduate: public pri_student{  
    public:  
    virtual void fun(){  
    cout<<"硕士交学费"<<endl;  
    }  
};  
class phd:public pri_student{  
    public:  
    virtual void fun(){  
    cout<<"博士生交费"<<endl;  
    }  
};  
class University{  
    public:  
    void charge_tuition(undergraduate& x){ x.fun(); } //使用这种重载的方法,则fun()加不加virtual都行  
    void charge_tuition(graduate& x){ x.fun(); }  
    void charge_tuition(phd& x){ x.fun(); }  
};  
int main(){  
University univ;  
graduate  B;  
univ.charge_tuition(B);//输出:硕士生交学费  
}  

如果改成多态的方式,则为:
class University{
	public:
	void charge_tuition(pri_student& x){  
		x.fun();                            //使用这种多态的方法,则fun()必须是虚函数
	}
};
其他部分不变,在main()中实现的效果是一样的。
故可以看出,使用多态,可避免对已有的程序结构进行过大的改动(不需要重新修改University类)。 不使用多态的话就要针对不同的派生类逐一定义方法。若一个类有很多很多派生类,那么代码量是非常庞大的。使用了多态,利用一个基类引用引用不同的派生类对象,从而实现不同的方法。
当然,上面的方式只是多态的一种表现形式:即派生类对象可以赋值给基类引用。也可以采用另外的一种多态的表现形式:即派生类的指针可以赋值给基类指针(基类指针指向派生类对象)。
class University{
	public:
	void charge_tuition(pri_student* x){
		x->fun();
	}
};
int main(){
University univ;
phd  B;
univ.charge_tuition(&B);
}

这里是把charge_tuition()的参数类型由基类引用变为基类指针,由该指针调用fun()。在调用charge_tuition()的时候实参是派生类对象地址,形参为基类指针,从而实现了基类指针指向派生类对象。


在上面的例子中,我们是在普通的成员函数中使用基类引用作形参。当然,我们也可以在构造函数中采用这种用法。不同的是,构造函数往往是为了使用参数来初始化一些成员变量。下面用一个例子说明如何在构造函数中使用基类引用作参数,而传入的实参是派生类对象。

class pri_student{                                //定义小学生,作为基类
    public:  
    virtual void fun(){  
    cout<<"小学生交学费"<<endl;  
    }  
};  
class graduate: public pri_student{               //硕士生由小学生派生而来
    public:  
    virtual void fun(){  
    cout<<"硕士交学费"<<endl;  
    }  
};  

class University{                                //定义大学,有成员函数是"向某个学生对象收费",该成员函数形参为基类引用
    public:  
    void charge_tuition(pri_student& x){  
        x.fun();  }  
};
class City{             
//定义城市,其构造函数以基类引用为形参,调用构造函数时传入的实参为派生类对象
//有成员变量"学生对象"也是个基类引用,有成员函数是"命令大学向学生收费"
    public:
	City(pri_student& x);
	pri_student& x;                //注意,这里是个基类引用。在City初始化后,成员x其实就是外部传入的对象的一个"别名"。
	void ask_univ_charge_x();
};
City::City(pri_student& x):x(x)        //构造函数用于初始化"学生"成员。
{}
void City::ask_univ_charge_x(){        //成员函数"命令大学向学生收费"中,创建大学,并调用大学的成员函数"向学生收费"
    University univ;
	univ.charge_tuition(x);
}

int main(){  
graduate  B;                                       //构造了派生类对象"硕士生"
pri_student A;                                     //构造了基类对象"小学生"
City city(B);                                      //构造"城市",这里既可以以"硕士生"作实参进行初始化,也可以以"小学生"作实参初始化
city.ask_univ_charge_x();  //输出"硕士生交费"
}  
从这个例子可看出,"城市"类的构造函数中的形参是基类引用,成员x也是基类引用,那么当初始化"城市"时传入的实参是个派生类对象"硕士生"时,那么成员x基类引用就指向了外部的“硕士生”对象(称为别名)。进一步调用city.ask_univ_charge_x()——>调用 univ.charge_tuition(x)——>x.fun()的时候,本质上是在调用外部对象“硕士生”的fun()函数。 















猜你喜欢

转载自blog.csdn.net/qq_41230365/article/details/79327965