c/c++-虚函数和重载

来源:互联网 发布:linux 中断ping 编辑:程序博客网 时间:2024/05/22 23:35
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;
     
return1
;
   }

  
  
int f(string)const{
     
return1
;
   }

}
;

这看起来跟全局的没什么区别,但是当涉及到继承的时候,事情就变得麻烦起来

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

2. 重写与虚函数
2.1 基本知识
虚函数在多态中经常用到。你只要有一个基类的指针或引用,编译器会为你调用该指针真正对应的函数

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

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

}
;

class Derived:public
Base
{
public
:
 
virtualvoid 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:
 
virtualvoid
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;
}