您的位置:首页 > 其它

(1.1.10)虚函数、纯虚函数和虚继承的区别以及“覆盖”的概念

2015-03-11 16:36 232 查看
首先:强调一个概念

定义一个函数为虚函数,不代表函数为不被实现的函数。

定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。

定义一个函数为纯虚函数,才代表函数没有被实现。

定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。

1、简介

假设我们有下面的类层次:

[cpp] view
plaincopy

class A

{

public:

virtual void foo()

{

cout<<"A::foo() is called"<<endl;

}

};

class B:public A

{

public:

void foo()

{

cout<<"B::foo() is called"<<endl;

}

};

int main(void)

{

A *a = new B();

a->foo(); //B::foo() is called 在这里,a虽然是指向A的指针,但是被调用的函数(foo)却是B的!

return 0;

}

这个例子是虚函数的一个典型应用,通过这个例子,也许你就对虚函数有了一些概念。它虚就虚在所谓“推迟联编”或者“动态联编”上,一个类函数的调用并不是在编译时刻被确定的,而是在运行时刻被确定的。由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被成为“虚”函数。

class parent

{

public:

vitual void foo(){cout < <"foo from parent";};

void foo1(){cout < <"foo1 from parent";};

};

class son:public parent

{

void foo(){cout < <"foo from son";};

void foo1(){cout < <"foo1 from son";};

};

int main()

{

parent *p=new son();

p->foo();

p->foo1();

return 0;

}

其输出结果是:

foo from son,foo1 from parent

(2)在一个基类被实例化为对象后,对象中保存一个虚表指针,指向虚表,虚表中存放类中的虚函数的地址。 该虚标为顺序存储结构,非链表。

虚函数只能借助于指针或者引用来达到多态的效果。

C++纯虚函数

一、定义

 纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”

 virtual void funtion1()=0

二、引入原因

  1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。

  2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。

  为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。

声明了纯虚函数的类是一个抽象类。所以,用户不能创建类的实例,只能创建它的派生类的实例。

纯虚函数最显著的特征是:它们必须在继承类中重新声明函数(不要后面的=0,否则该派生类也不能实例化),而且它们在抽象类中往往没有定义。

定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口。

纯虚函数的意义,让所有的类对象(主要是派生类对象)都可以执行纯虚函数的动作,但类无法为纯虚函数提供一个合理的缺省实现。所以类纯虚函数的声明就是在告诉子类的设计者,“你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它”。

(3)虚继承

虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。
注意:虚继承的父类的构造函数,优先于普通继承的父类构造函数

类D继承自B类和C类,而B类和C类都继承自类A,因此出现下图所示情况:

A A

\ /

B C

\ /

D

而类D中会出现两次A。为节省内存空间,可以将B、C对A的继承定义为虚拟继承,而A就成了虚拟基类。最后形成如下图所示情况:

A

/ \

B C

\ /

D

代码如下:

class A;

class B:public virtual A;

class C:public virtual A;

class D:public B,public C;

例子1:

class A

{

.

.

.

public:

int a;

.

.

.

};

class B:virtual public A

{

.

.

.

};

class C:virtual public A

{

.

.

.

};

class D:public B,public C

{

public:

void f(int i)

{

a=i;

}

};

此例中,对于类D而言,类A是类C的虚基类,是类B的真基类【前为真,后为虚】;但对于类C而言,类A仍是类C的真基类,虚基类只是一个相对的概念。

如果把上例子稍稍修改一下:

.

.

.

class D:pbulic C,public B

.

.

.

则对于类D而言,类A是类B的虚基类,是类C的真基类。

(4)覆盖

题:以下代码的输出结果是什么?【中国著名门户网站W公司2007年9月校园招聘面试题】

[cpp] view
plaincopy

// P123_example1.cpp : Defines the entry point for the console application.

//

#include "stdafx.h"

#include <iostream>

class A

{

protected:

int m_data;

public:

A(int data = 0)

{

m_data = data;

}

int GetData()

{

return doGetData();

}

virtual int doGetData()

{

return m_data;

}

};

class B : public A

{

protected:

int m_data;

public:

B(int data = 1)

{

m_data = data;

}

int doGetData()

{

return m_data;

}

};

class C : public B

{

protected:

int m_data;

public:

C(int data = 2)

{

m_data = data;

}

};

int _tmain(int argc, _TCHAR* argv[])

{

C c(10);

std::cout<<c.GetData()<<std::endl; 1

std::cout<<c.A::GetData()<<std::endl; 1

std::cout<<c.B::GetData()<<std::endl; 1

std::cout<<c.C::GetData()<<std::endl; 1

std::cout<<c.doGetData()<<std::endl; 1

std::cout<<c.A::doGetData()<<std::endl; 0

std::cout<<c.B::doGetData()<<std::endl; 1

std::cout<<c.C::doGetData()<<std::endl; 1

return 0;

}

解析:

构造函数从最初始的基类开始构造,各个类的同名变量没有形成覆盖,都是单独的变量。理解这两个重要的C++特性后解决这个问题就比较轻松了。下面我们详解这几条输出语句。

[cpp] view
plaincopy

std::cout<<c.GetData()<<std::endl;

本来是要调用C类的GetData(),C中未定义,故调用B中的,但是B中也未定义,故调用A中的GetData(),因为A中的doGetData()是虚函数,所以调用B类中的doGetData(),而B类的doGetData()返回B::m_data,故输出1。

[cpp] view
plaincopy

std::cout<<c.A::GetData()<<std::endl;

因为A中的doGetData()是虚函数,又因为C类中未重定义该接口,所以调用B类中的doGetData(),而B类的doGetData()返回B::m_data,故输出1。

[cpp] view
plaincopy

std::cout<<c.B::GetData()<<std::endl;

肯定返回1了。

[cpp] view
plaincopy

std::cout<<c.C::GetData()<<std::endl;

因为C类中未重定义GetData(),故调用从B继承来的GetData(),但是B类也未定义,所以调用A中的GetData(),因为A中的doGetData()是虚函数,所以调用B类中的doGetData(),而B类的doGetData()返回B::m_data,故输出1。

[cpp] view
plaincopy

std::cout<<c.doGetData()<<std::endl;

肯定是B类的返回值1了。

[cpp] view
plaincopy

std::cout<<c.A::doGetData()<<std::endl;

因为直接调用A的doGetData(),返回输出0。

[cpp] view
plaincopy

std::cout<<c.B::doGetData()<<std::endl;

因为直接调用了B的doGetData(),所以输出1。

[cpp] view
plaincopy

std::cout<<c.C::doGetData()<<std::endl;

因为C类中未重定义该接口,所以调用B类中的doGetData(),而B类的doGetData()返回B::m_data,故输出1。这里要注意存在一个就近调用,如果父辈存在相关接口则优先调用父辈接口,如果父辈接口也不存在相关接口则调用祖父辈接口。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐