第15章 面向对象编程(3)
15.2.4 virtual与其他成员函数
C++中的函数调用默认不使用动态绑定。要出动动态绑定,必须满足两个条件:第一,只有指定为虚函数的成员函数才能进行动态绑定,成员函数默认为非虚函数,非虚函数不进行动态绑定;第二,必须通过基类类型的引用或指针进行函数调用。
1. 从派生类到基类的转换
因为每个派生类对象都包括基类部分,所以可将基类类型的引用绑定到派生类对象的基类部分,也可以用指向基类的指针指向派生类对象。
Item_base *item1=new Bulk_item();
Item_base &item2=*item1;
因为可以使用基类类型的指针或引用来引用派生类型对象,所以,使用基类类型的引用或指针时,不知道指针或引用所绑定的对象的类型:基类类型的引用或指针可以引用基类类型的对象,也可以引用派生类型对象。无论实际对象具有哪种类型,编译器都将它当作基类类型对象。将派生类对象当作基类对象是安全的,因为每个派生类对象都拥有基类子对象。而且,派生类继承基类的操作,即,任何可以在基类对象上执行的操作也可以通过派生类对象使用。
基类类型引用和指针的关键点在于静态类型(static type,在编译时可知的引用类型或指针类型)和动态类型(dynamic type,指针或引用所绑定的对象的类型,这是在运行时可知的)可能不同。
2. 可以运行时确定virtual函数的调用
Item_base *item1=new Bulk_item();
cout<<item1->net_price(2)<<endl; //Bulk_item::net_price() called
将基类类型的引用或指针绑定到派生类对象对基对象没有影响,对象本身不会改变,仍未派生类对象。对象的实际类型可能不同于该对象的引用或指针的静态类型,这是C++中动态绑定的关键。
通过引用或指针调用虚函数时,编译器将生成代码,在运行时确定调用哪个函数,被调用的是与动态类型相对应的函数。
引用和指针的静态类型与动态类型可以不同,这是C++用以支持多态性的基石。
通过基类引用或指针调用基类中定义的函数时,我们并不知道执行函数的对象的确切类型,执行函数的对象可能是基类类型的,也可能是派生类型的。
如果调用非虚函数,则无论实际对象是什么类型,都执行基类类型所定义的函数。如果调用虚函数,则直到运行时才能确定调用哪个函数,运行时的虚函数是引用所绑定的或指针所指向的对象所属类型定义的版本。
另一方面,对象是非多态的——对象类型已知且不变。对象的动态类型总是与静态类型相同,这一点与引用或指针相反。运行的函数(虚函数或非虚函数)是由对象的类型定义的。
3. 编译时确定非virtual调用 www.zzzyk.com
cout<<item1->book()<<endl; //Item_base::book() called
即使Bulk_item定义了自己的book函数版本,这个调用也会调用基类中的版本。
非虚函数总是在编译时根据调用该函数的对象、引用或指针的类型而确定。
4. 覆盖虚函数机制
在某些情况下,希望覆盖虚函数机制并强调函数调用使用虚函数的特定版本,这时可以使用作用域操作符。
cout<<item1->Item_base::net_price(2)<<endl; //Item_base::net_price() called
只有成员函数中的代码才应该使用作用域操作符覆盖虚函数机制。
派生类虚函数调用基类版本时,必须显式使用作用域操作符。如果派生类函数忽略了这样做,则函数调用会在运行时确定并且将是一个自身调用,从而导致无穷递归。
5. 虚函数与默认实参
像其他任何函数一样,虚函数也可以有默认实参。通常,如果有用在给定调用中的默认实参值,该值将在编译时确定。如果一个调用省略了具有默认值的实参,则所有的值由调用该函数的类型定义,与对象的动态类型无关。通过基类的引用或指针调用虚函数时,默认实参为在基类虚函数声明中指定的值,如果通过派生类的指针或引用调用虚函数,则默认实参是在派生类的版本中声明的值。
在同一虚函数的基类版本和派生类版本中使用不同的默认实参几乎一定会引起麻烦。如果通过基类的引用或指针调用虚函数,但实际执行的是派生类中定义的版本,这是就可能会出现问题。在这种情况下,为虚函数的基类版本定义的默认实参将传递给派生类定义的版本,而派生类版本是用不同的默认实参定义的。
摘自 xufei96的专栏
补充:软件开发 , C++ ,