C++基础(五)虚函数、重载、覆盖、隐藏

虚函数总是跟多态联系在一起,引入虚函数可以使用基类指针对继承类对象进行操作!

虚函数:继承接口(函数名,参数,返回值),但是实现不继承(函数体)

非虚函数:继承接口,也继承实现;

1)虚析构函数(当一个类打算作为基类使用时候,其析构函数必须是虚函数)

构造函数可以为虚函数吗?  不可以,在生成对象的时候,必须向编译器明确指定要生成什么类型的对象,因而不存在虚函数的问题;只有当对象已经存在,我用什么接口去操作它的问题;

例子:

class A
{
public:
      A();
      virtual~A();
};
class B: public  A
{
public: 
       B();
      ~B();
};
int main()
{
      A *pA=new B;      //调用B的构造函数
      delete pA;            
      return 0;       
}

这里通过指针A去调用B类的析构函数,但是如果 ~A()不是虚析构函数,就不存在多态,就会去调用A中的析构函数,最后结果可能是B对象中的A成分被析构了,其他           还残留;所以当一个类作为基类时候,其析构函数必须是虚函数,这样防止出现析构不完全的情况;

2)当虚函数出现在构造、析构函数中时(即在构造函数中调用虚函数),函数退化为普通函数。为什么? <<effective C++>>中条款

例子:(转自<<effective C++>>中条例9)

class Transaction 

{
public:
      Transaction();
      virtual void LogTransaction() const = 0;
       // ...
};
Transaction::Transaction()
{
      // ...
      LogTransaction();
}
class BuyTransaction : public Transaction 

{
public:
      virtual void LogTransaction() const;
       // ...
};

这里调用BuyTransaction的构造函数,因为BuyTransaction是继承类所以先调用基类的构造函数,此时派生类独有的那部分还未初始化,<<effective  c++>>中这么解释:“这个对象内的BuyTransaction成分还未初始化,最安全的办法是当他不存在,对象在derived成分还未初 始化时该对象不会成为一个derived对象”所以Transaction 的构造函数会调用基类的 LogTransaction() const;而且此处是个纯虚函数,会报错

3)虚函数与覆盖,重载,隐藏

重载首先出现在非继承关系当中,当同一个类中,两个函数的参数不同,名字相同,返回值类型无所谓(函数返回值不作为重载的参考,因为函数调用时候不出现返回值);

注意两个函数可以仅仅因为const与非const的差别来实现重载;

虚函数与覆盖均出现在一个继承体系中,覆盖针对的是普通函数,当父类子类中出现同名(相同返回值,相同参数,相同函数名)要求绝对一致

虚函数在父类子类中,首先在父类中声明该函数为virtual,那么子类可以重新定义该函数的实现,这里主要涉及多态,就是覆盖的情况加上virtual,通过指针或者引用实现多态;

隐藏显得简单粗暴,在继承类中只要出现于父类同名(只要求同样的函数名/变量名,其他返回值,virtual  非virtual不管)的函数,则用对象调用同名函数时候,基类对应的同名函数、变量隐藏,如果要访问其父类的同名成员应该明确使用  基类名::成员来访问;

例子:

class    A
 {
public:
         virtual ~A(){};
          void process(int i,char c);           //重载,编译期间即可确定该调用哪个函数
         char  process(double d,int c);      //只要函数名相同,返回无所谓,参数不同
         void process(int i);        
         void process(int i)const;               // 此处相当于void process(const A *this,int i);
         void process(consttint i);              //这里仅仅因为const属性不同即可实现重载
         virtual int  foo(int, char){...};         
         int foo2(){};
         void foo3(int,int ){};
}
class    B:   public A
{
public:
           int  foo(int ,char){...};             //虚函数,这里函数接口要严格一致(大部分编译器要求返回值也要一致)
           int   process(){...};                 //隐藏基类函数  
           int foo2(){};                            //  这里不含虚属性,会覆盖
           int foo3(){return 0;};               //会隐藏A类的foo3
}
int main()
{
             B    b;
            A *pA=&b;
            pA->foo2();                         //这里调用A类的foo2();不涉及多态;
             pA->foo(3,‘c‘);                    //B类foo() 涉及多态
}

总结:

1)覆盖与虚函数是一对兄弟,要求函数的返回值,函数名,参数严格一致,虚函数是覆盖加上virtual的情况;

2) 隐藏是覆盖的推广,覆盖是隐藏的特例,只要求函数名一样,其他不管,在继承体系中,子类的同名函数会将父类的同名函数隐藏;

3)当使用指针时候,指向基类的指针会根据实际对象的类型,选择相应的虚函数执行,如果派生类没有重新定义基类的虚函数,那么依然执行基类的虚函数;

4)当不存在虚函数的情况下,使用基类的指针,不会下降到派生类中去搜索函数;所以virtual属性相当于告诉基类指针:当执行我时,请到相应对象中搜索对                应的虚函数;

5)纯虚函数所在的类是抽象类,不能实例化,定义了纯虚函数意味着这个函数只能为父类,其负责定义接口而不负责实现;

6)当使用对象来调用相应的函数时候,主要考虑的是对基类同名函数的隐藏(包括覆盖),而不需要考虑多态;

7)注意构造析构函数中不能调用虚函数,当执行派生类的构造函数时候,先构造的是其基类成分,再执行派生类成分的构造,虚函数此时无意义;

8)c++是个细节非常多,非常复杂的语言;



参考:《effective c++》

C++基础(五)虚函数、重载、覆盖、隐藏,布布扣,bubuko.com

C++基础(五)虚函数、重载、覆盖、隐藏

上一篇:C++ Primer 3rd 读书笔记1/2


下一篇:C/C++中static关键字的用法