C++的重载与虚函数

 

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(string) const{
      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(int) const
{
    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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值