zhgw01

C++的重载与虚函数

其实真正要说的是虚函数,不过其中要扯倒重载,所以顺便也说了下重载

1. 重载
1.1 简单重载
      在C++中,是允许同名函数的存在
int add(int i,int j);
float add(float i,float);
      而在c中,函数名是唯一的,所以为了区分int和float版本的add,你需要给它们起不同的名字,比如将int的命名为add_int,将float的命名为add_float,这样做的坏处就是程序员要记住很多的函数名,虽然这些函数的功能是一样的,而且也不直观。
      重载函数的存在,使得这种情况不在存在,它可以根据参数的类型,自动调用合适的重载函数,程序员只要记住要使用加法是调用add函数就可以,不同再像C中那样猜测int版的add函数函数名是怎样的,float版的函数名又是怎样的。实际上,这两个版本的add函数名在编译后是不一样的,编译器自动为它们进行了修饰,比如int的修饰成add_int_int,float的修饰成add_float_float,不过这都只是编译生成后的结果,对程序员来说,他只需知道add这个函数,但他要调用的时候,比如使用了int参数,编译器根据参数推出该调用的版本,这里就是add_int,所有这一切程序员都是看不见的,也不需要关心,从而减轻了程序员的工作。
      从上面的说法也可以看出,函数重载也不是能乱重载的,重载的要求是:
      1. 函数的参数类型不一样,像上面的int和float的
      2. 函数的参数个数不一样
      这是因为编译器在修饰生成的函数名时,一般用所有的参数类型来进行修饰,比如void add(int,float) 修饰成add_int_float,void add(int,int,int) 修饰成add_int_int_int,这样符合上面2条要求的重载函数最后生成的函数名是不一样的。
      可能有人会认为,为什么不用返回值来区分,如果编译器能推测出函数调用该返回什么值那自然没什么问题,但很多时候,往往只是调用函数,使用函数的副作用,而不要求返回值,这个时候编译器就推测不出了,比如
 
void f();
int f();

int main()
{
  f();
}

这个时候编译器怎么知道调用哪个函数
1.2 类中的重载函数
不仅仅是全局函数可以重载,类中的函数也可以重载
class Base
{
public:
   
int f() const{
      cout
<<"Base:f()"<<endl;
      
return 1;
   }

   
   
int f(stringconst{
      
return 1;
   }

}
;

这看起来跟全局的没什么区别,但是当涉及到继承的时候,事情就变得麻烦起来
class Derived1: public Base
{
public:
  
//Redefinition
  int f() const{
    cout
<<"Derived1:f()"<<endl;
    
return 1;
  }

}
;

class Derived2: public Base
{
public:
  
//change Return type
  void f()const
  
{
    cout
<<"Derived2:f()"<<endl;
   }

}


class Derived3: public Base
{
public:
  
//change argument list
  int f(intconst
  
{
    cout
<<"Dervide3:f()"<<endl;
    
return 1;
   }

}

子类中定义了跟父类同名的函数,这个时候该如何办?其实说起来也很简单,只要子类定义了跟父类同名的函数,不管是重写了函数内容(Dervied1),改变了返回类型(Derived2),还是改变了参数列表(Derived3),结果都一样,子类中的同名函数将父类中的同名函数给隐藏了,只要子类中的函数是可见的,通过子类的对象调用父类的同名函数是不合法的,只能调用子类自身的同名函数。这就是所谓的名字隐藏。

2. 重写与虚函数
2.1 基本知识
虚函数在多态中经常用到。你只要有一个基类的指针或引用,编译器会为你调用该指针真正对应的函数
class Base
{
public
  
virtual void f()
  
{
   cout
<<"Base:f()"<<endl;
   }

}
;

class Derived:public Base
{
public:
   
//You can also ingore virtual here
   virtual void f()
   

     cout
<<"Derived:f()"<<endl;
   }

}
;

int main()
{
  Base
* p=new Derived();
  p
->f();
  delete p;
}


程序输出Derived::f(),这就是虚函数的作用。你可以不用关心基类指针到底指向那个子类,编译器会为你调用正确的函数。
这是因为编译器使用了晚捆绑的缘故。
当一个类中有一个虚函数时(可以是因为在类中声明了一个虚函数,也可以是因为基类中有虚函数,通过继承得到)。编译器就为这个类创造一个虚表(VTABLE),它当中的虚函数位置是固定的,即使被继承到子类中也一样。当定义了一个这个类的对象时,编译器会在这个对象中放入一个虚指针(VPTR)指向这个表。当调用虚函数时,编译器在汇编代码中插入
一段代码,这个代码首先找到虚表,然后在通过偏移调用正确的函数。
当一个带有虚函数的基类被继承时,这个VTABLE会被完整赋值,当然对应的函数地址会改成子类中的函数地址。如果子类另外声明了虚函数,就会在原来的虚函数后面添加上新的条目。
重写其实就是在子类中对父类的虚函数进行重定义,因为一般子类有自己的特性。
2.2 虚函数与重载
如果子类中只是改写了父类中虚函数的内容,这就只是重写(overriding),函数前面的virtual可以忽略掉
但如果子类中改变了父类中虚函数的参数类型或个数,那么父类中的同名函数就会被隐藏掉,这同普通的重载一样,有一点不一样的是,不可以通过改变返回类型来隐藏父类中的同名函数。
2.3 切片
当用子类对象来初始化父类时(如函数中的call by value),新生成的父类对象会正确初始化它自身的vtable,而不会使用子类的vtable。

注:
虽然通过基类指针调用虚函数,最后调用的是子类的函数,但是如果使用的确实基类的默认参数
class Base
{
public:
  
virtual void f(int i=0)
  
{
    cout
<<i<<endl;
   }

}
;

class Derived: public Base
{
public:
  
virtual void f(int i=1)
  
{
    cout
<<i<<endl;
  }

}
;


int main()
{
  Base
* p=new Derived();
  p
->f(); //输出的是0
}

  

 
注2:
发生在private继承时的问题,父类中的虚函数是private的,当它被private继承时,子类是无法访问到这个函数的,不过子类仍然可以override这个函数
class Base
{
public:
  
void nvi()
  
{
     vfun();
  }

private:
  
virtual void vfun()
  
{
    cout
<<"Base::vfun()"<<endl;
   }

}
;

class Derived1:private Base
{
 
public:
   
void df()
   
{
     nvi(); 
//调用base的nvi,由于这里没有override vfun,所以输出的是Base:vfun()
   }


//事实上,这里不能直接调用base中的vfun,因为它是private继承来的
}


class Derived2:private Base
{
public:
  
void df()
  
{
    nvi();
//调用了Dervied2的vfun
   }

private:
  
void vfun();//要想override,必须重新声明
}
;

void Derived2::vfun()
{
  cout
<<"Derived2::vfun()"<<endl;
}

posted on 2008-10-17 14:45 apacs 阅读(5462) 评论(1)  编辑 收藏 引用 所属分类: c++

Feedback

# re: C++的重载与虚函数 2012-07-26 11:54 曾经半夏

您这里说的 名字隐藏:
子类中定义了跟父类同名的函数,这个时候该如何办?其实说起来也很简单,只要子类定义了跟父类同名的函数,不管是重写了函数内容(Dervied1),改变了返回类型(Derived2),还是改变了参数列表(Derived3),结果都一样,子类中的同名函数将父类中的同名函数给隐藏了,只要子类中的函数是可见的,通过子类的对象调用父类的同名函数是不合法的,只能调用子类自身的同名函数。这就是所谓的名字隐藏。
和后面虚函数与重载中所说的:
但如果子类中改变了父类中虚函数的参数类型或个数,那么父类中的同名函数就会被隐藏掉,这同普通的重载一样,有一点不一样的是,不可以通过改变返回类型来隐藏父类中的同名函数。
应该不是一个概念吧?
因为我这里可以通过直接引用父类的虚函数f()来调用,没有在子类隐藏掉父类的函数。
class Base
{
public:
virtual void f()
{
cout<<"Base:f()"<<endl;
}
};

class Derived:public Base
{
public:

void f(int i=0)
{
cout<<"Derived:f()"<<endl;
}

};
int _tmain(int argc, _TCHAR* argv[])
{
Base* p=new Derived();
p->f();
delete p;
int i;
cin>>i;


return 0;
}
  回复  更多评论   



只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


My Links

Blog Stats

常用链接

留言簿(1)

随笔分类

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜