c++的多态性主要是靠关键字virtual来实现的,这个关键字代表了一种叫“延迟绑定”的技术,当你用一个基类的指针或者引用来指向一个对象的时候,编译器在编译阶段是不知道绑定的对象是基类对象还是派生类对象的,直到运行期间才得知的。编译器是如何实现这个技术的呢?更具lippman的介绍,是靠VTABLE(虚函数表)来实现的,当编译器发现一个类中有一个或者多个虚函数的时候,就会生成这样的一个表,这个表是独立于对象存在的,也就是说一个类只有一个VTABLE,对象是没有VTABLE的,而且基类和派生类都有自己的VTABLE,如果有的话。而且派生类的VTABLE和基类的是一样的排列,VTABLE实际上是一个函数的指针表,存储的是函数的指针,在基类和派生类中,虚函数对应的是一个位置。然后当生成一个对象的时候,会有一个vptr,这个vptr指向本类的VTABLE,所以,当用静态类型是基类的指针或者引用在运行的时候,此时对象已经产生,就得到了vptr,也就能区分是用基类还是派生类的函数了。例如:
class base
{public:
virtual void foo(){std::cout<<"base foo";}
};
class derived:public base
{public:
void foo(){std::cout<<"derived foo";}
};
int main()
{derived a;
base *ptr = &a;
ptr->foo();//这里会执行derived::foo()
}
然后就自然会遇到覆盖和隐藏的问题,先来看看什么是隐藏,如下的代码
class base
{public:
virtual void foo(int a){std::cout<<"base foo";}
};
class derived:public base
{public:
void foo(){std::cout<<"derived foo";}
};
int main()
{derived a;
int i = 9;
a.foo(i);
return 0;
}
我们会编译得到这样的错误提示test.cpp(22) : error C2660: “derived::foo”: 函数不接受 1 个参数
这说明什么?在derived类中没有带有一个int形参的函数foo,说明base类的foo函数在这里被隐藏了,即使他们的形参表不一致,只要有相同的名字就被隐藏了,也不在乎有没有virtual,注意这是不用指针和引用调用的时候。这个时候编译器是从派生类向基类开始查找被调用的函数,如果说在派生类找到了相同名字的函数,且匹配,那么成功,如果找到了,但是不匹配,就失败。如果在派生类里没有找到,那么就想基类查找,找到就调用基类的,否则出错。
现在来看看如果是用指针和引用来写以上的代码呢?
class base
{public:
virtual void foo(int a){std::cout<<"base foo";}
};
class derived:public base
{public:
void foo(){std::cout<<"derived foo";}
};
int main()
{derived a;
base* ptr = &a;
int i = 9;
ptr->foo(i);
return 0;
}
你会很欣慰的看见,编译运行都成功了,这个时候,是发生了隐藏,和刚才相反,是从基类向派生类查找的,也就是调用是由静态类型决定的。
我们可以这样总结一下在覆盖中,用基类指针和派生类指针调用函数foo()时,系统都是执行的派生类函数foo(),而非基类的foo(),这样实际上就是完成的“接口”功能。而在隐藏方式中,用基类指针和派生类指针调用函数foo()时,系统会进行区分,基类指针调用时,系统执行基类的foo(),而派生类指针调用时,系统“隐藏”了基类的foo(),执行派生类的foo(),也就是有静态类型决定。此时如果把derived的foo也改成int形参,则又变成了覆盖而不是隐藏了,这时候就会是调用derived::foo。
所以关键的关键是先确定是隐藏还是覆盖
阅读(5378) | 评论(2) | 转发(0) |