正文

我所理解的继承及多态性2006-04-04 14:10:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/goal00001111/11914.html

分享到:

                   我所理解的继承及多态性

 现在让我们来谈谈继承及多态性,继承对面向对象编程至关重要,创建继承类的能力,从一般到具体,这样可以更好地控制程序,而且使程序更容易理解和扩展。
 我将从以下几个方面谈谈我对继承及多态性的理解:
 1。继承的特点。
 2。继承的三种方式及其特点。
 3。多层继承。
 4。多重继承。
 5。虚函数。
 6。纯虚函数和抽象类。

1.1 继承的基本概念
 对象(Object)是类(Class)的一个实例(Instance)。如果将对象比作房子,那么类就是房子的设计图纸。所以面向对象设计的重点是类的设计,而不是对象的设计。
 对于C++程序而言,设计孤立的类是比较容易的,难的是正确设计基类及其派生类。
 继承概念的基础是基类和派生类的关联,如果A是基类,B是A的派生类,那么B将继承A的数据和函数。例如:
 #include <iostream>
 using namespace std;
 
 //基类A
 class A {
 public:
  int numA;
     void SetNumA(int n)
  {
         numA = n;
     }
     int GetNumA()
  {
   return numA;
  }
 };
 //派生类B
 class B: public A
 {
 public:
  int numB;
     void SetNumB(int n)
  {
         numB = n;
     }
     int GetNumB()
  {
   return numB;
  }
 };
 // 在main()中测试基类和派生类
 int main(void)
 {
     //声明并使用B类的对象
     B obj;
     obj.SetNumB(3);       //使用自己的成员函数
     obj.SetNumA(8);        //使用基类的成员函数
 
     cout << "obj:" << endl;
     cout << "numB:" << obj.GetNumB() << endl;    //使用自己的成员函数
     cout << "numA:" << obj.GetNumA() << endl;    //使用基类的成员函数
     cout << "numB:" << obj.numB << endl;  //使用自己的成员数据
     cout << "numA:" << obj.numA << endl;  //使用基类的成员数据
    
     getchar();
  return 0;
 }
程序将正确运行并输出:
 obj:
 numB:3
 numA:8
 numB:3
 numA:8
 
 在上例中,派生类的实例既可以使用自己的成员函数,也可以使用基类的成员函数,这就说明派生类继承了基类的数据和函数。
 
1.2 基类及派生类的构造函数
 从含有构造函数的基类中派生新类时,在派生类的构造函数中必须包含有基类的构造函数。例如:
#include <iostream>
using namespace std;

//定义基类First
class First {
    int  num;
    float grade;
public:
    //构造函数带参数
    First(int n, float v ) : num(n), grade(v)
    {
        cout << "The First initialized" << endl;
    }
    void DispFirst(void)
 {
        cout << "num=" << num << endl;
        cout << "grade=" << grade << endl;
    }
};

//定义派生类Second
class Second : public First { 
    double val;
public:
    //无参数构造函数,要为基类的构造函数设置参数
    Second(void): First(10000, 0)
 {
        val = 1.0;
        cout << "The Second initialized" << endl;
    }

    //带参数构造函数,为基类的构造函数设置参数
    Second(int n, float x, double dx): First(n, x)
 {
        val = dx;
        cout << "The Second initialized" << endl;
    }
    void Disp(char *name)
 {
        cout << name << ".val=" << val << endl;
        DispFirst();
    }
};

//main()函数中创建和使用派生类对象
int main() 
{
    //调用派生类的无参数构造函数
 cout << "Second s1;" << endl;
    Second s1;
 cout << "s1.Disp(\"s1\");" << endl;
 s1.Disp("s1");

    //调用派生类的有参数构造函数
 cout << "Second s2(10002,95.7,3.1415926); " << endl;
    Second s2(10002, 95.7, 3.1415926);
 cout << "s2.Disp(\"s2\");" << endl; 
    s2.Disp("s2");
   
    getchar();
 return 0;
}
程序将正确运行并输出:
 Second s1;
 The First initialized
 The Second initialized
 s1.Disp(\"s1\");
 s1.val=1
 Second s2(10002,95.7,3.1415926);
 The First initialized
 The Second initialized
s2.Disp(\"s2\");
s2.val=3.14159
num=10002
grade=95.7

如上所示,基类的构造函数在派生类的构造函数之前执行。
类对象够创建必然就有析构过程,派生类对象的析构过程首先是调用派生类的析构过程,再调用基类的构造函数,正好和创建过程相反。
 
1.3 受保护成员
 c++允许声明受保护的类成员,从基类派生的类完全能够访问这些受保护的成员,但派生类的对象却不能访问它们。例如:
 #include <iostream>
 using namespace std;
 
 //基类First
 class First {
 protected:
     int val1;
     void SetVal1(int v)
  {
         val1 = v;
     }
     void show_First(void)
  {
         cout << "val1=" << val1 << endl;
     }
 };
 //派生类Second
 class Second: public First {   //默认为private模式
     int val2;
 public:
     void SetVal2(int v1, int v2)
  {
         SetVal1(v1);     //可见,合法
         val2 = v2;
     }
     void show_Second(void)
  {
         show_First();   //可见,合法
         cout << "val1=" << val1 << endl;  //可见,合法
         cout << "val2=" << val2 << endl;
     }
 };
 //main()函数中创建和使用派生类对象
 int main()
 {
     Second s1;
     //s1.SetVal1(1);    //不可见,非法
     s1.SetVal2(2, 3);    //合法
     //s1.show_First();  //不可见,非法
     s1.show_Second();
     //cout << "val1=" << s1.val1 << endl; //不可见,非法
    
     First s2;
     //s2.SetVal1(6);   //不可见,非法
     //s1.show_First();  //不可见,非法
     //cout << "val1=" << val1 << endl; //不可见,非法
    
     getchar();
  return 0;
 }
 
 可以看到派生类访问了基类的受保护成员,但是派生类的对象却不能访问它们,甚至连基类的对象也不能访问这些受保护成员。这说明受保护成员在外部是不可见的。

2.1 继承的三种方式及其特点
 派生类和派生类成员的对象对基类成员的访问具有权限的规定。在前面的练习中我们一直在使用public的继承方式,即共有继承方式,对于protected和private继承方式,即保护继承与私有继承方式我们并没有讨论。现在我将对继承的三种方式及其特点做一个基本介绍。
A 对于公有继承方式:
a. 基类成员对其对象的可见性:公有成员可见,其他不可见。这里保护成员同于私有成员。
b. 基类成员对派生类的可见性:公有成员和保护成员可见,而私有成员不可见。这里保护成员同于公有成员。
c. 基类成员对派生类对象的可见性:公有成员可见,其他成员不可见。
 所以,在公有继承时,派生类的对象可以访问基类中的公有成员;派生类的成员函数可以访问基类中的公有成员和保护成员;派生类的对象仅可访问基类中的公有成员。这里,一定要区分清楚派生类的对象和派生类中的成员函数对基类的访问是不同的。
B 对于私有继承方式:
 a. 基类成员对其对象的可见性: 公有成员可见,其他不可见。
 b. 基类成员对派生类的可见性:公有成员和保护成员可见,而私有成员不可见。
 c. 基类成员对派生类对象的可见性:所有成员都是不可见的。
 所以,在私有继承时,基类的成员只能由直接派生类访问,而无法再往下继承。
C 对于保护继承方式:
 a. 基类成员对其对象的可见性: 公有成员可见,其他不可见。
 b. 基类成员对派生类的可见性:公有成员和保护成员可见,而私有成员不可见。
 c. 基类成员对派生类对象的可见性:所有成员都是不可见的。
 所以,保护继承方式与私有继承方式相同,对于单个类来说,讨论保护继承与私有继承的区别意义是不大的,他们的区别只在多级继承的情况中体现,即保护继承时,对于垂直访问同于公有继承,对于水平访问同于私有继承。
 看起来真的是有点糊涂,为了明确这些区别,我们还是来看一个例子。
//程序作者:管宁   
//站点:www.cndev-lab.com   
//所有稿件均有版权,如要转载,请务必著名出处和作者   
 
#include <iostream> 
using namespace std; 
//------------------------------------------------------------------------------ 
class Base 

    public://公有的 
        int a1; 
        virtual void test() = 0; 
    protected://受保护的 
        int a2; 
    private://私有的 
        int a3; 
}; 
//------------------------------------------------------------------------------ 
class ProtectedClass: protected Base//保护继承 

    public: 
        void test() 
        { 
            a1 = 1;//a1在这里被转变为protected 
            a2 = 2;//a2在这里被转变为protected 
            //a3=3;//错误,派生类不能访问基类的私有成员 
        } 
}; 
//------------------------------------------------------------------------------
class ControlProtectedClass: public ProtectedClass//以public方式继承ProtectedClass类 

    public: 
        void test() 
        { 
            a1 = 1;//a1在这里仍然保持为a1在这里被转变为protected 
            a2 = 2;//a2在这里仍然保持为a1在这里被转变为protected 
            //a3=3;//错误,由于Base类成员为私有的,即使是上级父类是保护继承,也不能改变Base类成员的控制类型 
        } 
}; 
//------------------------------------------------------------------------------ 
class PrivateClass: private Base//私有继承 

    public: 
        void test() 
        { 
            a1 = 1;//a1在这里被转变为private 
            a2 = 2;//a2在这里被转变为private 
            //a3=3;//错误,基类私有成员对文件区域与派生类区域都是不可访问的 
        } 
}; 
//------------------------------------------------------------------------------
class ControlPrivateClass: public PrivateClass//以public方式继承PrivateClass类 

    public: 
        void test() 
        { 
            //a1=1;//错误,由于基类PrivateClass为私有继承,a1已经转变为private 
            //a2=2;//错误,由于基类PrivateClass为私有继承,a1已经转变为private 
            //a3=3;//错误,由于Base类成员为私有的,PrivateClass类也为私有继承 
        } 
}; 
//------------------------------------------------------------------------------ 
class PublicClass: public Base//共有继承有区别与其它方式的继承,继承后的各成员不会其改变控制方式 

    public: 
        void test() 
        { 
            a1 = 1;//a1仍然保持public 
            a2 = 2;//a2仍然保持protected 
            //a3=3;//错误,派生类不能操作基类的私有成员 
        } 
}; 
//------------------------------------------------------------------------------
class ControlPublicClass: public PublicClass//以public方式继承PublicClass类 

    public: 
        void test() 
        { 
            a1 = 1;//a1仍然保持public 
            a2 = 2;//a2仍然保持protected 
            //a3=3;//错误,由于Base类成员为私有成员,即使是上级父类是公有继承,也不能改变Base类成员的控制类型 
        } 
}; 
//------------------------------------------------------------------------------ 
int main() 

    getchar();
 return 0;
}
 认真看完了例子,相信细心的读者对于共有继承、保护继承与私有继承的区别与特点已经了解,最后再提醒一下读者,在继承关系中,基类的private成员不但对应用程序隐藏,即使是派生类也是隐藏不可访问的,而基类的保护成员只对应用程序隐藏,对于派生类来说是不隐藏的,保护继承与私有继承在实际编程工作中使用是极其少见的,他们只在技术理论上有意义。(摘自www.cndev-lab.com
 
 3.1 多层继承
 派生类继承了基类的数据和成员,而该派生类又可以派生新的类,这样就出现了多层继承。在多层继承中,每个类继承其上一层类的特性。具体分析请看下面的例子。
 #include <iostream>
 
 using namespace std;
 
 //定义最低层基类First,它作为其他类的基类
 class First {
     int val1;
 public:
     First()
  {
         cout << "The First initialized" << endl;
     }
     ~First() {
         cout << "The First destroyed" << endl;
     }
 };
 //定义派生类Second,它作为其他类的基类
 class Second : public First {   //默认为private模式
     int val2;
 public:
     Second()
  {
         cout << "The Second initialized" << endl;
     }
     ~Second()
  {
         cout << "The Second destroyed" << endl;
     }
 };
 //定义最上层派生类Three
 class Three : public Second {
     int val3;
 public:
     Three()
  {
         cout << "The Three initialized" << endl;
     }
     ~Three()
  {
         cout << "The Three destroyed" << endl;
     }
 };
 //main()函数中测试构造函数和析构函数的执行情况
 int main()
 {
  cout << "First f1;" << endl;
     First f1;
    
     cout << "Second s1;" << endl;
     Second s1;
    
     cout << "Three t1;" << endl;
     Three t1;
    
     getchar();
  return 0;
 }
 
 可见,在多层继承中,执行构造函数的顺序是先执行最高层基类构造函数,再执行第二层的构造函数,最后执行最后一层派生类自身的构造函数。执行析构函数的顺序则恰好相反。
 
4.1 多重继承
 多重继承可以看成是单继承的扩展。所谓多重继承是指派生类具有多个基类,派生类与每个基类之间的关系仍然可以看成是一个单继承。
 多继承的定义格式如下:
 class  派生类名:继承方式1 基类名1,继承方式2,基类名2,…
  {
    派生类类体
 };
 这里的继承方式仍属公有、私有和受保护。
在多重继承的情况下,派生类的构造函数格式如下:派生类名(总参数表):基类名1(参数表1),基类名2(参数表2)…,子对象名(参数表n+1),…
 派生类构造函数总参数表中的“总”字十分恰当,它包含了派生类构造函数所需的参数加上基类构造函数所需的参数以及子对象所需要的参数。
 执行派生类构造函数的顺序是先执行所有的基类构造函数,再执行子对象的构造函数,最后执行派生类自身的构造函数。对于各个基类构造函数的执行次序,取决于定义派生类时所指定的次序,而不是定义派生类的构造函数时的成员初始化表。
 具体分析请看下面的例子。
 /*多重继承*/
 #include <iostream>
 #include <cstring>
 using namespace std;
 
 //基类Cover
 class Cover {
 protected:
  char title[256];
 public:
     Cover(char *title)
     {
   strcpy(Cover::title, title);
  }
 };
 //基类Page
 class Page {
 protected:
  int lines;
  char *text;
 public:
     Page(int lines = 55)
     {
   Page::lines = lines;
  }
 };
 //派生类Book
 class Book: public Cover, public Page {
     float cost;
     char author[256];
 public:
     Book(char *author, char *title, float cost): Cover(title), Page(60)
  {
   strcpy(Book::author, author);
         strcpy(Book::title, title);
         Book::cost = cost;
     }
     void ShowBook(void)
  {
      cout << title << endl;
      cout << author << "\t" << cost;
     }
 };
 //main()函数中创建和使用派生类对象
 int main() 
 {
     Book text1("Me", "Science", 100);
     Book text2("Mee", "Science Science", 1000);
    
     text1.ShowBook();
     text2.ShowBook();
    
     getchar();
  return 0;
 }

4.2 避免二义性
 我们看下面的例子:
#include <iostream>
using namespace std;

//定义基类Base1
class Base1 {
public:
    void ShowB()
    {
   cout << "Base1" << endl;
 }
};
//定义基类Base2
class Base2 {
public:
    void ShowB()
    {
   cout << "Base2" << endl;
 }
};
//定义派生类Derived
class Derived : public Base1, public Base2{
public:
    void ShowD()
    {
   cout << "Derived" << endl;
 }
};
//main()函数中测试派生类Derived的执行情况
int main()
{
 Derived obj;
 
 obj.ShowD(); //可以正常编译
// obj.ShowB();  //类成员的含糊导致编译失败
   
    getchar();
 return 0;
}

 在上例中一旦把注释行恢复,则编译出错,为什么?这里obj.ShowB()是调用Base1类的ShowB()还是Base2类的ShowB()函数?造成了二义性。二义性在计算机软件技术中是一个十分令人讨厌的但又极易产生的错误。本例的二义性是名称冲突所引发的。也许大家很自然地想到二个类中的成员函数不要同名,分别为ShowB1()和ShowB2()。这种想法是善良的,在客观世界里很难做到。函数名、文件名通常都是具有一定的物理意义或数学意义来命名。其次,基类不一定是你自己定义的类或是以前定义的类,在当时的环境下尚未造成二义性。为此C++允许有多种方法,其一是在函数名前加上基类名如:obj.Base1::ShowB();//进一步强调对象名后加.类名后加::。
 仔细分析以上二个ShowB()都是在基类中,在同一层次。如果一个在派生类,另一个出现在基类,此时不会出现二义性。使用obj.ShowB()时,系统会自动调用派生类的ShowB(),而不会到基类中去。从中得到启发,在派生类中再定义一个同名的ShowB()函数,通过调用派生类的ShowB()间接地调用基类中的同名函数。具体做法是在派生类 Derived中增加一个成员函数
void ShowB()//用成员名限定法消除二义性
{
 cout << "Base" << endl;

  
改进后的例程:
#include <iostream>
using namespace std;

//定义基类Base1
class Base1 {
public:
    void ShowB()
    {
   cout << "Base1" << endl;
 }
};
//定义基类Base2
class Base2 {
public:
    void ShowB()
    {
   cout << "Base2" << endl;
 }
};
//定义派生类Derived
class Derived : public Base1, public Base2{
public:
    void ShowD()
    {
   cout << "Derived" << endl;
 }
 void ShowB()//用成员名限定法消除二义性
    {
   cout << "Base" << endl;
 }
};
//main()函数中测试派生类Derived的执行情况
int main()
{
 Derived obj;
 
 obj.ShowD(); //可以正常编译
 obj.Base1::ShowB(); //可以正常编译
 obj.Base2::ShowB(); //可以正常编译
 obj.ShowB();  //可以正常编译
   
    getchar();
 return 0;
}
                
  为简单起见,上例忽略了数据成员,若涉及到数据成员,由于数据成员不是公有的(若是公有的则失去了类和对象的特性)必须由成员函数来赋值或者通过构造函数来自动初始化。
 上例通过使用双冒号和成员名限定法解决了二义性问题。解决二义性的另一种技术是使用虚基类。

4.3 虚基类
 派生类继承多个类,这些类派生于单个基类,此时派生类可能包含一些成员,这些成员对每个基类是唯一的,但在派生类中却享有相同的名字。这种情况发生时,类成员的含糊导致编译失败。如下例所示。
/*虚基类*/
#include <iostream>
using namespace std;

//基类
class Base {
public:
    int i;
};
//派生类
class Derived1: public Base{
public:
    int j;
};
//派生类
class Derived2: public Base{
public:
    int k;
};
//派生类
class Derived3: public Derived1, public Derived2{
public:
    int sum;
};
//main()函数中创建和使用派生类对象
int main()  
{
   Derived3 obj;
  
   obj.i = 10;
   obj.j = 20;
   obj.k = 30;
   obj.sum = obj.i + obj.j + obj.k;
  
   cout << obj.i << "+" << obj.j << "+" << obj.k << "=" << obj.sum << endl;
   
    getchar();
 return 0;
}
 因为Base类含有公有成员i,Derived1类和Derived2类都派生于Base类,所以Derived3类包含公有成员i的两个实例。由于编译器不能解决i成员的混淆,因而返回三个编译错误,这些错误都是由Derived3类的obj变量混淆使用i成员引起的。为了避免这种情况,在给定对象中应该避免使用基类的多个拷贝。为了使程序不含基类的多个拷,可用virtual关键字声明基类。如下所示:
 /*虚基类*/
#include <iostream>
using namespace std;

//基类
class Base {
public:
    int i;
};
//派生类
class Derived1: virtual public Base{
public:
    int j;
};
//派生类
class Derived2: virtual public Base{
public:
    int k;
};
//派生类
class Derived3: public Derived1, public Derived2{
public:
    int sum;
};
//main()函数中创建和使用派生类对象
int main()  
{
   Derived3 obj;
  
   obj.i = 10;
   obj.j = 20;
   obj.k = 30;
   obj.sum = obj.i + obj.j + obj.k;
  
   cout << obj.i << "+" << obj.j << "+" << obj.k << "=" << obj.sum << endl;
   
    getchar();
 return 0;
}

5.1 指向类的指针
 随着程序的复杂,可能会使用对象指针,如下程序创建一个简单的基类及派生类,该程序用new运算符为每个类的实例动态分配内存,并用指针间接的调用每个实例的方法。
#include <iostream>
using namespace std;

//定义基类Base
class Base {
public:
    void ShowB()
    {
   cout << "Base" << endl;
 }
};

//定义派生类Derived
class Derived : public Base{
public:
    void ShowD()
    {
   cout << "Derived" << endl;
 }
};
//main()函数中测试派生类Derived的执行情况
int main()
{
 Base *pB = new Base;
 Derived *pD = new Derived;
 
 pB->ShowB();
 pD->ShowD();
   
    getchar();
 return 0;
}

5.2 使用相同的指针指向不同的类
 在上例中我们使用了两个不同的指针变量来指向不同的类。实际上当程序使用继承时,c++允许基类的指针指向派生类。然而使用基类的指针时,只能访问原始基类的成员,不能访问派生类的成员。如下例所示:

#include <iostream>
using namespace std;

//定义基类Base
class Base {
public:
    void Show()
    {
   cout << "Base" << endl;
 }
};

//定义派生类Derived
class Derived : public Base{
public:
    void Show()
    {
   cout << "Derived" << endl;
 }
};
//main()函数中测试派生类Derived的执行情况
int main()
{
 Base *pB = new Base;
 pB->Show();
 
 pB = new Derived; //使用基类的指针时,只能访问原始基类的成员,不能访问派生类的成员
 pB->Show();
   
    Derived *pD = new Derived;
    pD->Show();
   
    getchar();
 return 0;
}

程序将正确运行并输出:
Base
Base
Derived

我们可以看到,当基类和派生类有同名成员并且把基类指针指向派生类时,只能执行基类成员。如果想让c++调用派生类成员,必须把基类成员定义成虚函数。

5.3 虚函数
 一个类继承另一个类的方法时,类成员名可能有冲突,如果使用基类指针访问派生类并调用和基类成员名相同的成员,则只能执行基类成员。如果想让c++调用派生类成员,必须把基类成员定义成虚函数。创建虚函数,只需简单地在函数名前放置virtual关键字,每个虚函数的返回值类型和参数必须相同。
 如下例所示:
#include <iostream>
using namespace std;

//定义基类Base
class Base {
public:
    virtual void Show()  //把基类成员定义成虚函数
    {
   cout << "Base" << endl;
 }
};

//定义派生类Derived
class Derived : public Base{
public:
     void Show()
    {
   cout << "Derived" << endl;
 }
};
//main()函数中测试派生类Derived的执行情况
int main()
{
 Base *pB = new Base;
 pB->Show();
 
 pB = new Derived; //把基类成员定义成虚函数,使用基类的指针时,能访问派生类的成员
 pB->Show();
   
    Derived *pD = new Derived;
    pD->Show();
   
    getchar();
 return 0;
}

程序将正确运行并输出:
Base
Derived
Derived
 
我们还可以举一个稍微复杂点的例子:
#include <iostream>
using namespace std;
//定义有两个虚函数的基类
class Base {
public:
    //定义两个虚函数
    virtual void aFn1(void)
 {   
        cout << "aFnl is in Base class." << endl;
    }
    virtual void aFn2(void)
 {   
        cout << "aFn2 is in Base class." << endl;
    }
    //定义非虚函数
    void aFn3(void)
 {   
        cout << "aFn3 is in Base class." << endl;
    }
};

//派生类Derived_1中重新定义了基类中的虚函数aFn1
class Derived_1: public Base
{
public:
    void aFn1(void)
 {   //覆盖aFn1()函数
        cout << "aFnl is in First derived class." << endl;
    }
    void aFn3(void)
 {   //语法错误
        cout << "aFn3 is in First derived class." << endl;
    }
};

//派生类Derived_2中重新定义了基类中的虚函数aFn2
class Derived_2: public Base{
public:
    void aFn2(void)
 {   //覆盖aFn2()函数
        cout << "aFn2 is in Second derived class." << endl;
    }
    void aFn3(void)
 {  // 语法错误
     cout << "aFn3 is in Second derived class." << endl;
    }
};
//main()函数的定义
int main()
{
    //创建和使用基类Base的对象
    Base *p = new Base;
   
    cout << "Base:" << endl;
    p->aFn1();
    p->aFn2();
    p->aFn3();
    cout << "----------------------" << endl;

    //创建和使用派生类Derived_1的对象
    Derived_1 d1;
    p = &d1;
    cout << "Derived_1:" << endl;
    p->aFn1();
    p->aFn2();
    p->aFn3();
    cout << "----------------------" << endl;

    //创建和使用派生类Derived_2的对象
    Derived_2 d2;
    p = &d2;
    cout << "Derived_2:" << endl;
    p->aFn1();
    p->aFn2();
    p->aFn3();

 getchar();
 return 0;
}

多态特性的工作依赖虚函数的定义,在需要解决多态问题的重载成员函数前,加上virtual关键字,那么该成员函数就变成了虚函数,从上例代码运行的结果看,系统成功的分辨出了对象的真实类型,成功的调用了各自的重载成员函数。
 多态特性让程序员省去了细节的考虑,提高了开发效率,使代码大大的简化,当然虚函数的定义也是有缺陷的,因为多态特性增加了一些数据存储和执行指令的开销,所以能不用多态最好不用。

6.1 纯虚函数和抽象类
  上面我们讨论虚函数时,可以在基类中定义一个虚函数,并且在派生类中也定义虚函数,派生类中的同名函数自动成为虚函数(基类中为虚函数的前提),它们各自有不同的实现,在时间t的作用下,进行动态联编。在绝大多数情况,基类中不能为虚函数给出一个有意义的定义(不让基类的虚函数有任何的操作),这时可以将它说明成纯虚函数,它的定义留给派生类来做,为程序的扩充带来了更加广阔的前景。
 从上面的讨论中我们已知说明虚函数的格式为:
 virtual <ret_type> <fun_name> (arg_list)
 那么纯虚函数的格式则在虚函数格式基础上末尾加=0
 即  virtual <ret_type> <fun_name> (arg_list) =0
纯虚函数与虚函数一样必须是包含在类中的成员函数。
让我们来看一个使用纯虚函数的例子:

#include <iostream>
using namespace std;
//定义抽象类
class Base {
public:
    //定义两个纯虚函数
    virtual void aFn1(void) = 0;
    virtual void aFn2(void) = 0;
};

//派生类Derived_1中覆盖了基类中的纯虚函数
class Derived_1: public Base
{
public:
    void aFn1(void)
 {
        cout << "aFnl is in First derived class." << endl;
    }
    void aFn2(void)
 {
        cout << "aFn2 is in First derived class." << endl;
    }
};

//派生类Derived_2中覆盖了基类中的纯虚函数
class Derived_2: public Base{
public:
    void aFn1(void)
 {
        cout << "aFn1 is in Second derived class." << endl;
    }
    void aFn2(void)
 {
        cout << "aFn2 is in Second derived class." << endl;
    }
};

//main()函数中测试抽象类及其派生类的对象
int main()
{
    //用抽象类不能创建对象
    //    Base b;  语法错误
    //    b.aFn1();
    //    b.aFn2();

    //创建和使用Derived_1类的对象
    Derived_1 d1;
    cout << "Derived_1 d1:" << endl;
    d1.aFn1();
    d1.aFn2();
    cout << "------------------" << endl;

    //创建和使用Derived_2类的对象
    Derived_2 d2;
    cout << "Derived_2 d2:" << endl;
    d2.aFn1();
    d2.aFn2();
   
    cout << "------------------" << endl;
    cout << "------------------" << endl;
    Base *p = new Derived_1;
    cout << "Derived_1 d1:" << endl;
    p->aFn1();
    p->aFn2();
    cout << "------------------" << endl;
   
    p = new Derived_2;
    cout << "Derived_2 d2:" << endl;
    p->aFn1();
    p->aFn2();
   
    getchar();
 return 0;
}

 该例中有一个基类Base,以及二个派生类Derived_1和Derived_2,基类Base中含有二个纯虚函数aFn1( )和aFn2( ),同时在二个派生类中有分别定义了同名的aFn1( )和aFn2( )函数。注意,纯虚函数本身没有实际意义的实现,它只是为下面的派生类作贡献。在动态联编的束定下智能地进行正确的选择相应的成员函数。
 对纯虚函数的限制除了前面我们讨论过的纯虚函数必须是成员函数,并且它不能是静态成员函数之外,内联函数也不能是纯虚函数,构造函数也不能为纯虚函数,理由是在构造时,对象还是一片未定型的空间,只有在构造完成后,对象才能成为一个类的名副其实的实例。 当一个虚函数的值为0,它则变成了纯虚函数。纯虚函数是一个没有定义函数语句的基类虚函数。包含有纯虚函数的类称为抽象类。一个抽象类只能作为基类,由此基类再派生新类。一个抽象类不能有实例对象,即不能定义属于该类的对象。仍以上例为例。在主函数假如定义Base b;企图定义类Base的对象b是错误的,因为类Base中包含了至少一个纯虚函数将,类Base已经是一个抽象类,定义一个抽象类的目的是通过基类派生的诸多派生类,以达到程序的扩充。

 

阅读(3876) | 评论(0)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

暂无评论
您需要登录后才能评论,请 登录 或者 注册