C++中成员函数的重载、覆盖和隐藏
C++是强大的,stl库简直就是人类智慧的结晶;C++是复杂的,在此之前我都不敢说熟悉C++,对于那些在程序中只用了cin、cout就说他的那是C++代码的程序员们,我感到很好笑。现在谈谈C++中成员方法间的重载、覆盖以及隐藏。以下文章纯属自己的感觉,大神勿喷,高手绕道,若有错误,欢迎指出。
那天去和同学去面试,他问我C++技术面会面试什么,我说就那些,继承、重载、覆盖、隐藏。听到‘隐藏’,他感到很惊讶,他说隐藏是什么,我诡异的笑了。
先来练习一下。
class CBase
{
public:
void NotVitrualFunction( double x )
{
cout <<"CBase::NotVirtualFunction( double )"<<endl;
}
void NotVitrualFunction()
{
cout<<"CBase::NotVirtualFunction()"<<endl;
}
virtual void VirtualFunction()
{
cout<<"CBase::VirtualFunction()"<<endl;
}
};
class CChild : public CBase
{
public:
void NotVirtualFunction( int x )
{
cout<<"CChild::NotVirtualFunction( int )"<<endl;
}
void VirtualFunction( int x )
{
cout<<"CChild::irtualFunction( int )"<<endl;
}
};
int main()
{
CChild *p = new CChild;
p->NotVirtualFunction( 10 );
p->NotVirtualFunction( 10.00 );
p->VirtualFunction();
p->VirtualFunction( 0 );
delete p;
return 0;
}
求最后的输出,如果能全做对,我觉得你就没必要往下看了。
答案:整个程序编译出错。
第一个输出CChild::NotVirtualFunction( int );
第二个输出CChild::NotVirtualFunction( int );
第三个编译出错;
第四个输出 CChild::VirtualFunction( int )。
上面的程序中还没有涉及到多肽。
一、非virtual成员方法。
virtual是C++实现多肽的基础之一,在此先讨论非vittual方法。
重载:所谓重载,简单的说就是函数名相同,而参数不同的一组函数。但是这儿还有个问题,“这些函数的作用域是不是必须要相同,还是可以不相同”。
覆盖:说句实话,覆盖和隐藏很像。从汉语的角度解释,所谓覆盖,指的是在原物体表面上放上别的物体。在那个位置上你仍然可以看见有物体。而所谓隐藏,指的便是在那个位置你看不到东西。
如果C++中没有隐藏,那么重载和覆盖还是比较容易区别的。但是正如其名——“隐藏”——它总是显得若隐若现。
现在讨论的非virtual函数,所以直接给出一句我的总结:如果子类中有于父类中同名的成员方法,那么,不管他们返回值类型、参数类型、参数顺序是否一样,子类都不能直接父类的该(系列)成员方法——除非将子类强制转换成父类。此谓之隐藏。
看最开始的练习。p->NotVirtualFunction( 10.00 );这句居然没有输出CBase:: NotVitrualFunction( double )。而是把10.00从double强制换成成int了。要知道,除非迫不得已,否则编译器是不会自动强制类型转换的。
在以前,我一直以为他们会构成重载。
构不成重载,何解?我只能说:“他们的范围不一样,所以构不成重载”。由于子类中出现了于父类中同名的成员函数(非virtual),所以尽管子类继承了父类的成员方法,但是他们都被隐藏了。
换个角度:
class CBase
{
public:
int m_data;
};
class CChild : public CBase
{
public:
int m_data;
};
你觉得CChild对象能调用CBase中的m_data么——它被隐藏了。
子类和父类的成员方法构不成重载。重载是编译器的特性,C++只是应用了这种特性。
至于覆盖,仅仅指的是子类重写父类的virtual方法。
二、virtual成员
如果没有virtual成员,上面还算简单。
class CBase
{
public:
virtual void VirtualFunction( double x )
{
cout <<"CBase::VirtualFunction( double )"<<endl;
}
virtual void VirtualFunction()
{
cout<<"CBase::VirtualFunction()"<<endl;
}
};
class CChild : public CBase
{
public:
void VirtualFunction( int x )
{
cout<<"CChild:: VirtualFunction ( int )"<<endl;
}
};
int main()
{
CChild *p = new CChild;
p->VirtualFunction();
p->VirtualFunction( 10.0 );
delete p;
return 0;
}
现在讨论这段程序的输出。很遗憾,整个程序编译出错,提示是VirtualFunction不能接受无参调用。
我们会想,VirtualFunction是虚函数啊,如果不在子类定义,那就是自动继承的啊。但是,既然不能调用,我就只能说“不管是不是虚函数,它都被隐藏了… …”。
上面基本都是隐藏在唱主角。现在我们来看看神秘的覆盖。
class CBase
{
public:
virtual void VirtualFunction( int x )
{
cout <<"CBase::VirtualFunction( int )"<<endl;
}
};
class CChild : public CBase
{
public:
void VirtualFunction( double x )
{
cout<<"CChild::VirtualFunction( double )"<<endl;
}
};
int main()
{
CBase *p = new CChild;
p->VirtualFunction( 10.0 );
delete p;
return 0;
}
看看这段程序输出什么。
输出的是CBase::VirtualFunction( int );
应该输出CChild::VirtualFunction( double )啊,CBase中的不是被隐藏了么。是的,确实被隐藏了,这儿的关键是“多肽”。由此可以得到另一条结论:基类指针只能调用在基类中被声明为virtual的成员,否则就要强制转换。这又联系到另外一个问题“C++中的常把析构函数声明为虚函数,为什么,有什么作用?”。
答:“防止内存泄露”。至于为什么,先看下面两个类:
class CBase
{
public:
CBase()
{
m_baseData = new char[32];
}
virtual ~CBase()
{
delete[] m_baseData;
cout<<"CBase::Destructor()"<<endl;
}
char *m_baseData;
};
class CChild : public CBase
{
public:
CChild()
{
m_childData = new char[32];
}
~CChild()
{
delete []m_childData;
cout<<"CChild::Destructor()"<<endl;
}
char *m_childData;
};
举的这两个类很对,但还要看你怎么用了。倘若你举例CChild child,然后分析其析构过程,对不起,你达不到你的目的。如果是CChild child,即使析构函数不是虚函数,child对象在析构时也会先调用自己的析构函数,再调用父类的析构函数,既然两个析构函数都调用了,就不会存在内存泄露的问题了。
但,假如是CBase *p = new CChild; delete p;
若CBase类的析构函数不是虚函数,那么delete p 的时候会调用什么?由前面的例子可以知道,会调用CBase的析构函数,这样一来,CChild中的m_childData不就造成内存泄露了?
重载:重载的函数必须有相同的作用域,子类和父类的函数永远不可能构成重载。
覆盖:子类重写父类中的虚函数。
隐藏:子类重写了父类中的非虚函数。
我们的目的不是弄清楚这三个的区别,我们的目的是在程序中能判断调用的是哪个函数。