二、重载
学过或是了解C/C++的都应该知道重载,而且还会纠结重载、重写、多态性以及覆盖啊,或是静态、动态,我就是了。
C/C++的函数重载和运算符重载比较好理解,就是指同一个函数名可以对应着多个函数的实现。每种实现对应着一个函数体,这些函数的名字相同,但是函数的参数的类型不同。这就是函数重载的概念。在编译的时候就已经确定要使用的是那个函数,毕竟参数不一样吗。函数重载和运算符重载都是编译时的多态性,即程序在编译的时就能根据重载的情况而确定需要调用的函数。
到这里又不得不说到多态性。多态性也好理解,打个比方就是某日一个小经理说:“老子要出差!”,那小经理周边的人都是会有不同的反应,比如老婆要准备衣服,秘书准备材料,保安准备枪支弹药等等。从广义上说,多态性是指一段程序能够处理多种类型对象的能力;具体地讲,多态性就是对不同对象发出同样的指令时,不同对象会有不同的行为。这样很好理解吧。
我们常说的或者本文所说的多态是采用动态绑定技术的一种情况。是类的多态!!也就是说,通过一个基类指针来操作对象,如果对象是基类对象,就会调用基类中的那个函数,如果对象实际是派生类对象,就会调用派声类中的那个函数,调用哪个函数并不由函数的参数表决定,而是由函数的实际类型决定。实现运行中的多态是要靠我们的关键字virtual(虚函数),以下是一个小例子
#include <stdio.h>
class Parent{public: void virtual foo(){printf("foo from parent");} void foo1(){printf("foo1 from parent");}};
class Son : public Parent{public: void foo(){printf("foo from son");} void foo1(){printf("foo1 from son");}};
int main(){ Parent *p = new Son; p->foo(); p->foo1(); return 0;}
结果是foo from son foo1 from parent
我们先来看 p->foo1()的结果,本来想要的son的成员函数,但是得到却是Parent的结果,我们从两个角度来分析这个情况
1. 编译的角度
C++编译器在编译的时候,要确定某个对象的需要调用的非虚函数的地址,前面讲过一点,这也称为静态绑定,当把son对象的地址赋给p时,C++编译器进行了类型转换,此时编译器就认为p所保存的地址是Parent对象的地址,所有调用就是基类的函数。
2. 内存的角度
下面是son对象的内存模型
Parent对象所占的内存
Son对象所占的内存
Parent对象所占的内存
Son对象所占的内存
完整的son对象所占据的内存
要构造son对象,首先会调用parent的构造函生产Parent类对象,然后才调用子类的构造函数完成自身部分构造,由两部分凑称一个完整的son对象。当son对象转换为parent类型时,该对象就被认为是原对象在内存的上半部分那么当我们利用类型转换后的对象指针去调用它的方法时,当然也就是调用它所在的内存中的方法。
那么foo的结果却是我们想要的呢?可以看到在基类成员函数foo是虚函数,那么在子类中的该函数也是虚函数,只是一般子类的virtual没有写而已。
大致的过程是这样的,编译器在编译的时候,发现Parent中有虚函数,就会为该类创建一个虚表,虚表是一个一维的数组,成员是各个虚函数的地址,当然基类有,那么该基类的派生类都是存在该表的,如果子类的没有重写基类的虚函数的,那么子类的虚表中的地址都是指向基类的虚函数实现,而且顺序是一样的。如果子类重写了某一个虚函数,那么在子类的虚表中其他的成员指向基类的实现,但是重写的虚函数是指向本类的虚函数地址。编译器在编译的时候发现Parent的foo是虚函数,就会采用迟绑定,动态的。就是在编译的时候不确定具体调用哪个函数,而是在运行时,依据对象的类型来确定调用哪一个函数。
在运行时是如何定位虚表呢?编译器为每一个类型的对象提供了一个虚表指针vptr,这个指针指向该类对象所属的虚表。从而程序在运行时,对象在调用函数时,就会根据对象的类型去查找正确的函数,我们可以想象,每一个类型的对象在构造的时候就都有指向本类的虚表的虚指针(vptr),对于上面的程序,由于指针p实际指向的对象类型是son,因此对象的虚指针都是指向son类的vtable,当调用foo时,就会查找son虚表的中的函数。记住一点构造完son对象后,其虚表指针都是指向本类的虚表,所有不管怎么转换都是会找到正确的虚函数。
程序一般运行时,找到类,如果它有基类,再找它的基类,最后运行的是基类中的函数,这时,它在基类中找到的是virtual标识的函数,它就会再回到子类中找同名函数。这句话不一定说的清楚,但是记清楚一点对象的虚指针是指向对象所属于类型的虚表,不管怎么转换,这个是不变的。
最后一句函数重载和运算符重载是早绑定,是静态的多态,而虚函数多态性是迟绑定,是程序运行的时确定的。
说了这样多,好像没有说到我想说的,这是因为,我觉得perl的重载,这样的说法有点怪怪的。
Perl中的重载作用也是提高代码的重用性,使得实现方法更加灵活。在子类中对基类的方法进行重写(重载),当调用一个方法时候,首先会在该对象所属的类中查找方法,如果没有找到就到该类的基类中去查找。
我理解就是,先看下面的例子吧
##基类
package Employee2;
########################
#小程序演示类的重载
########################
use strict;
use warnings;
use DDate;
sub new
{
my $type=shift;
my $class=ref($type)||$type;#确定是何种类型
my $self={
firstname=>shift,
lastname=>shift};
my $hireday=new DDate;
if($_[0])
{
my($m,$d,$y)=split(,$_[0]);
$hireday->day($d);
$hireday->year($y);
$hireday->month($m);
}
$self->{hiredate}=$hireday;
bless $self,$class;
return $self;
}
sub lastname
{
my $this=shift;
$this->{lastname}=shift if(@_);
return $this->{lastname};
}
sub firstname
{
my $this=shift;
$this->{firstname}=shift if(@_);
return $this->{firstname};
}
sub hireday
{
my $this=shift;
if(@_)
{
$this->{hiredate}->setdate(@_);
}
else
{
$this->{hiredate}->myprint();
}
}
sub mywrite
{
my $this=shift;
print("hello,my name is ".$this->{firstname}."./n");
print("i was hired on ");
$this->hireday();
# print"./n";
}
return 1;
#####子类1
完整的son对象所占据的内存