[摘要]C++重写重载_详解C++中的函数名字重写、重载、重定义程序应用实例重载、重写是必须要知道,因为用途太广泛;至于隐藏吗,完全是C++为面试官设计的(^_^等待挨砖)。有些面试主考官总喜欢拿这三个概念... C++重写重载_详解C++中的函数名字重写、重载、重定义程序应用实例
重载、重写是必须要知道,因为用途太广泛;至于隐藏吗,完全是C++为面试官设计的(^_^等待挨砖)。有些面试主考官总喜欢拿这三个概念去为难你,考察你的C++基础是否牢固。所以为了面试、这三个概念还是需要我们去区分一下。
JAVA中语言中方法(函数)调用有两种特殊的形态:重载与重写;而C++由于增加了virtual这个虚函数关键字,给函数调用又增加了变数:除了重载、重写(也称覆盖)之外还多了隐藏这么一说。
C++中经常出现函数名字一样,但参数列表或返回值不同的函数,要搞清楚函数的正确调用关系,需理清三个概念:重写(override)、重载(overload)、重定义(redefine)。
1、重载的特征:在同一个类中;函数名字相同;参数不同;virtual 关键字可有可无。
2、重写(覆盖)特征是:分别位于派生类与基类;函数名字相同;参数相同;基类函数必须有virtual 关键字(这点非常要注意)。
C++的隐藏规则使问题复杂性陡然增加。规则如下:
1、如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
2、 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与重写混淆)。
一、三个基本概念
1、重定义(redefine):派生类对基类的成员函数重新定义,即派生类定义了某个函数,该函数的名字与基类中的函数名字一样。
特点:(1)不在同一个作用域(分别位于基类、派生类) (2)函数的名字必须相同 (3)对函数的返回值、形参列表无要求
特殊情况:若派生类定义的该函数与基类的成员函数完全一样(返回值、形参列表均相同),且基类的该函数为virtual,则属于派生类重写基类的虚函数。
作用效果:若重新定义了基类中的一个重载函数,则在派生类中,基类中该名字的函数(即其他所有重载版本)都被自动隐藏,包括同名的虚函数。
2、重载(overload):函数名字相同,但它的形参个数或者顺序,或者类型不同,但是不能靠返回类型来判断。
特点:(1)位于同一个类中 (2)函数的名字必须相同 (3)形参列表不同(可能是参数个数 or 类型 or 顺序 不同),返回值无要求
特殊情况:若某一个重载版本的函数前面有virtual修饰,则表示它是虚函数。但它也是属于重载的一个版本
不同的构造函数(无参构造、有参构造、拷贝构造)是重载的应用
作用效果和原理:编译器根据函数不同的参数表,将函数体与函数调用进行早绑定。重载与多态无关,只是一种语言特性,与面向对象无关。
3、重写(override):派生类重定义基类的虚函数,即会覆盖基类的虚函数 (多态性)
特点:(1)不在同一个作用域(分别位于基类、派生类) (2)函数名、形参列表、返回值相同 (3)基类的函数是virtual
特殊情况:若派生类重写的虚函数属于一个重载版本,则该重写的函数会隐藏基类中与虚函数同名的其他函数。
作用效果:父类的指针或引用根据传递给它的子类地址或引用,动态地调用属于子类的该函数。这个晚绑定过程只对virtual函数起作用
具体原理是由虚函数表(VTABLE)决定的,在第三节介绍。
[page]
二、程序实例
1、两个类:基类( 取名Test)和派生类( 取名XX) 名字不规范,哈哈随便取得!
基类和派生类的结构
//Base class
class Test
{
public:
int a;
Test()
{
cout<<"Test() 无参构造函数!"<<> }
Test(int data)
{
a = data;
cout<<"Test(int data) 有参构造函数!"<<> }
Test(const Test &tmp)
{
a = tmp.a;
cout<<"Test 拷贝构造函数!!"< }
//基类中对函数名f,进行了重载。其中最后一个重载函数为虚函数
void f()const
{
cout<<"调用 void Test::f()"<<> }
//overload
int f(int data) const
{
cout<<"调用 Test f(int data)"<<> return 1;
}
//overload 虚函数
virtual double f(int dataA,int dataB)
{
cout<<"调用 Test f(int a,int b)"<<> return dataA*dataB/2.0;
}
};
class XX: public Test
{
public:
Test atest;//先调用基类的构造函数,然后对象成员的构造函数,最后才是派生类的构造函数
XX()
{
cout<<"XX() 无参构造函数被调用!"<<> }
//对基类的函数名f,进行了重定义。则会隐藏基类中的其他f函数
//redefine
int f() const
{
cout<<" 调用 XX f()函数"<<> return 1;
}
//重写基类的虚函数
//redefine override
double f(int dataA,int dataB)
{
cout<<"调用 XX f(int dataA,int dataB)函数"<<> return (dataA+dataB)/2.0;
}
};
分析:基类class Test中定义了名为f的3个重载函数,其中最后一个是虚函数
派生类class XX中对f进行了重定义,所以会隐藏基类中名为f的版本。其中派生类的double f(int dataA,int dataB)属于对虚函数的重写
测试---主程序
int main()
{
//-----test 1------------------------
cout<<"-------test 1------------"<<> //Base class
Test aaTest;
aaTest.f();
aaTest.f(12);
aaTest.f(10,20);
//derived class
XX d;
d.f();
// d.f(2); //error C2661: 'f' : no overloaded function takes 1 parameters
d.f(10,20);
//--------test 2----------------------------------
cout<<"-------test 2------------"<<> Test b = d;
b.f();
b.f(10,20);//调用的是基类的函数,不发生多态
//--------test 3----------------------------------------
cout<<"-------test 3------------"<<> Test &bR = d;//引用
b.f();//f()不是虚函数,调用基类的函数
bR.f(10,20);//调用的是派生类的函数,发生多态
//--------test 4--------------------------------------
cout<<"-------test 4------------"<<> Test* pB = &d;
b.f();
pB->f(10,20);//调用的是派生类的函数,发生多态
return 1;
}
分析:(1)test 1中进行了重载测试,根据传递参数的不一样,调用不同的函数 (早绑定,与多态无关)
(2)test 2中Test b = d;定义了一个基类对象,用派生类对象来进行初始化。这会调用基类的拷贝构造函数,生成基类的对象b,基类的拷贝构造函数初始化b的VPTR,指向b的VTABLE。因此所有的函数调用都只发生在基类,不会产生多态。
这是一个对象切片过程(参见《C++编程思想.第二版》P370),对象切片是当它拷贝到一个新的对象时,会去掉原来对象的一部分,而不是像使用指针或引用那样简单地改变地址的内容。
(3)test 3和test 4中,定义的基类指针和引用,故会发生多态。
三、晚绑定原理:虚函数表
当通过基类指针做虚函数调用时(即多态调用时),编译器静态地插入能取得这个VPTR并在VTABLE表中查找函数地址的代码,这样就能调用正确的函数并引起晚绑定的发生。编译器会对每一个包含虚函数的类(或者从包含虚函数的基类派生的类)创建一个表(VTABLE),里面存放特定类的虚函数的地址。然后编译器秘密地放置一指针vpointer(VPTR),指向这个对象的vtable。
学习教程快速掌握从入门到精通的电脑知识
|