讲栈


90年代以来,面向对象的程序设计( Object-Oriented Programming,简称OOP)异军突起,

迅速在全世界流行,一跃成为主流的程序设计技术。在软件市场中,覆盖面大、垄断市场的

新一代程序设计语言、软件开发工具和环境以及操作系统大多是面向对象的。

10.1 面向对象的概念

10.1.1 面向对象的程序结构

面向对象的程序设计是一种基于结构分析的、以数据为中心的程序设计方法。在面向对

象的程序中,活动的基本单位是对象,向对象发送消息可以激活对象的行为。为此,许多人

把面向对象的程序描述为:

程序=对象+消息传递

1. 对象

对象类似于 C语言中的变量,可以泛指自然界中的任何事务,包括具体实物和抽象概念。

对象具有一些属性、状态和行为。例如,每个人都有姓名、性别、年龄、身高、体重等属性,

有工作、学习、吃饭、睡觉等行为。所以,对象一般可以表示为:属性 +行为。在面向对象的

程序设计中,对象被表示为:数据 +操作,操作也称为方法。这就是说,面向对象程序设计中

的对象是指由一组数据和作用于其上的一组方法组成的实体。

2. 类

在面向对象的程序设计中,会涉及到许多对象,我们无法将所有的对象都描述清楚,如

果那样做的话,程序将无限长或无法描述。因此在面向对象的程序设计中引入了类的概念,

将同类的对象归于一类,同类对象具有相同的属性和行为,如各种花同属一类,各种草、植

物等也分别属于不同的类。

3. 消息

消息就是对对象进行某种操作的信息。当要求对象执行某种特定操作时,就向该对象发

送操作消息,对象接收到指定操作的消息后,就调用相应的操作方法,完成有关操作。

消息及其传递机制是面向对象程序设计的一个重要角色。对象的一切活动,都要通过消

息来驱动,消息传递是对象间通信的唯一途径。

4. 方法

方法就是对对象进行的某种操作。当对象接收到相应的消息时,就调用对应的方法完成

指定的操作,有了消息,就驱动对象工作;有了方法,就能实现消息所要求的操作。

5. 继承

继承是面向对象语言的另一个重要概念。在客观世界中,存在着整体与个体的关系、一

般与特殊的关系,继承将后者模型化。

 

206

C语言程序设计

下载

例如,对人的分类我们用图 10-1描述如下。

在类的层次结构图中,下层节点都具有上层节点的特性,都具备人的共同特点。但下层

节点较之上层节点而言,又具有新的特性,是上层节点

所不具有的。这种下层节点对上层节点的特性的保持,

就是我们所说的继承。

在面向对象语言中,类功能支持这种层次结构。除

了根结点外,每个类都有它的超类,又称为父类或基类。

除了叶结点外,每个类都有它的子类,又称为派生类。

一个子类可以从它的基类继承所有的数据和操作,并扩

充自己的特殊数据和操作。基类抽象出共同的属性和操

作,子类体现其差别。有了类的层次结构和继承性,不

件可重用的目的。

10.1.2 C++的类

C + +语言是一种面向对象的程序设计语言,是对传统 C语言的完善和扩充,并支持面向对

象的概念:对象、类、方法、消息和继承,下面给出一个 C++关于类的结构:

[例10-1]

栈操作。栈是一种后进先出的数据结构,我们利用数组这个静态的存储来实现

栈、充当栈,完成数据的压栈和出栈。

#include "iostream.h"

#define SIZE 100

// 定义栈类型

class stack/* 定义类*/

{

   int stck[SIZE];/* 数据成员,整型数组做堆栈 */

   int top;/* 栈顶指针*/

public:

   void init(void);/* 初始化成员函数*/

   void push(int i);/* 压栈*/

   int pop(void);/* 出栈*/

};

// 栈的初始化

void stack: :init(void):/* 类成员函数的定义 */

{

top=0;/* 定义栈顶指针指向数组头*/

}

//进栈操作

void stack :push(int i):

{

   if (top= =SIZE)

 {

   cout<<"The stack is full!";/* 栈满*/

   return;

海军

空军 陆军

工人

农民

解放军

图10-1 对人的分类

同对象的共同特性只需定义一次,用户就可以充分利用已有的类,进行完善和扩充,达到软

 

下载

第10章 C++入门

207

   }

 stck[top]=i;/* 压入数据到栈顶 */

 top++;/* 指针加1*/

}

// 出栈操作

int stack :pop(void):

{

   if (top= =0)

 {

 cout<<"The stack is underflow!";* 栈空,无数据 *//

   return 0;

 }

   top- -;/* 指针减1*/

   return stck[top];/* 返回栈顶元素 */

}

void main(void)

{

                ,//创建对象,stackstack1 stack2;

stack1.init();/*调用成员函数,对栈初始化 */

stack2.init();

stack1.push(1);//在stack1 栈中,压栈1。

stack2.push(2);// stack2 栈中,压栈2。在

stack1.push(3);

stack2.push(4);

cout<<stack1.pop()<<" "; 输出stack1 栈顶数据,即弹出//

cout<<stack2.pop()<<" ";

cout<<stack1.pop()<<" ";

cout<<stack2.pop()<<"\n ";

}

在该程序中,定义了一个对象类型 s t a c k,我们称为类。类 s t a c k由数据成员(变量 s t c k,

t o p)和操作成员函数(函数 i n i t,p u s h,p o p)两部分构成,这种结构类似于 C语言中的结构

体类型。不同的是,将 C中结构体类型的描述符 struct 替换成了描述符 c la s s。sta c k就是类的类

型标识符。类 s t a c k将全部信息(数据或变量、方法或函数)都封装在其自身之内,这是 C + +

语言的最基本的特征。有了类(对象类型) stack,我们就可以定义对象(对象变量),如程序

中定义的 s t a c k 1和s t a c k 2,并在对象 s t a c k 1和s t a c k 2之上进行各种不同的操作(初始化、进栈、

出栈)。

上述程序运行后输出为:

3

4

1

2

10.2 C++的输入与输出

在C + +的程序设计中,除继续使用 C语言中的标准库函数(如 p r i n t f,s c a n f)进行输入输

出外,C++还提供自己的输入输出方式。

[例10-2] 显示一行输出。

#include <iostream.h>

main()

 

208

C语言程序设计

下载

{

cout<<"Hello ,World !";

}

C++标准流的输入输出可以在 iostream.h文件中找到。cout是C++中与标准输出设备相关的

输出流, <” 是运算符,该运算符完成将引号内的字符串写到标准输出流 c o u t,简单地说,“<

就是将字符串“ H e l l o,Wo r l d !”写到标准输出设备—显示器上,为此,运行程序,我们将

在屏幕上看到:

Hello ,World!

[例10-3]

利用海伦公式,输入三角形的三条边,若满足任意两边之和大于第三边,则计

算出给定三角形的面积。

#include <iostream.h>

#include <math.h>

main()

{

float s,s1 ,a1 ,a2 ,a3; // a1,a2 ,a3是三角形的三条边; s 是三角形的面积;

                                          // s1是二分之一的周长

cout<<"a1=";

cin>>a1;/* 键盘输入*/

cout<<"a2=";

cin>>a2;

cout<<"a3=";

cin>>a3;

if (((a1+a2)>a3)&&((a1+a3)>a2)&&((a2+a3)>a1))

//任意两边之和应大于第三边

    {

    s1=(a1+a2+a3)/2;

    s=sqrt(s1*(s1-a1)*(s1-a2)*(s1-a3));// 计算面积

    cout<<"area="<<s;

    cout<<"\n";

    }

    else

    cout<<"error!";

    return 0;

    }

程序中, c i n是C + +的标准输入流;“ > >”是运算符,该运算符完成从标准输入流(通常

cin与键盘相连)中读取数据。运行该程序后,可以看到如下的显示信息:

a1=3↵

a2=4↵

a3=5↵

area=6

10.3 类与对象

客观世界中的事物都包含属性和行为两个方面。在C++程序设计中,对事物的描述分别用数

据成员和成员函数来表现,并把它们封装起来,形成一个抽象的数据类型—类。这就是说,类

 

下载

10.3.1 类的定义与对象的引用

1. 类的定义

类定义的基本格式如下所示:

class 类型名

{

private:

私有成员声明;

protected:

保护成员声明;

public:

公有成员声明;

}

第10章 C++入门

209

具有两种成员:数据成员和成员函数,按照面向对象的概念,成员函数又称为方法函数或方法。

类成员分为私有成员和公有成员两部分。外界不能访问一个对象的私有成员,只能与对

象的公有成员之间进行信息交换。定义类即是确定选择成员并区分它们的访问权限。

[例10-4] C++程序结构示例。

#include <stdio.h>

class exam1// 定义类

{

    private:// 类的私有成员

          int x,y;//数据成员

    public:// 类的公有成员类的声明部分

           void init();// 成员函数

           float average();

           void print();

};

      void exam1::init()// 类成员函数的定义

      {

            x=3;

            y=4;

       }

   float exam1::average()//类成员函数的定义

   {return (x+y)/2.0;

     }

   void exam1::print()// 类成员函数的定义

   {

    printf("\nx=%d ,y=%d ,aver=%7.2f\n" ,x,y,average());

    }

        main()// 主函数

        {

              exam1 obj;//声明并创建一个对象

              obj.init();// 调用成员函数初始化

              obj.print();//输出运算结果

              return 0;

      }

类的实现部分

类的使用

 

210

C语言程序设计

下载

1) 类在 C++中用关键字 class来说明,紧跟其后的是类名 exam1,类中包含两个私有数据成

员x、y和三个公有的成员函数 init()、average()、print()。

C++允许隐藏内部状态,由 private开始的私有段成员就满足这一特性,它们只能允许该类

对象的成员函数来访问。类中的公有段既可以是数据成员,也可以是成员函数,这是类提供

给外部的接口。当然, C++还提供另一种保护段符号: protected ,下面会介绍到。

运行该程序,得到的输出显示为:

x=3 , y=4 ,

aver=

3.50

2) 类的成员在类的定义中出现的顺序可以任意,并且类的实现既可以放在类的外面,又

可以内嵌在类内,下面调整类成员的顺序为:

class exam1// 定义类

{

   public:

            float average();

            void print();

   private:

         int x,y;

   public:

          void init();

    };

3) 若类的实现定义在类的外面,在成员函数的函数头中函数名前,应使用作用域限定符::

指明该函数是哪一个类中的成员函数,即有如下格式。

类型 类名::成员函数名(参数表)

{

函数体

}

4) 除特殊指明外,成员函数操作的是同一个对象的数据成员。下面的示例将类成员函数

的实现内嵌在类中:

class exam1//定义类

{

    public:

            float average()

            {

                  return (x+y)/2.0;

            }

             void print()

                          {

                          printf("\nx=%d

                          }

   private:

               ,yint x ;

   public:

          void init()

                          {

                                   x=3;

                                   y=4;

                                   }

    };

,y=%d ,aver=%7.2f\n"

,x,y,average());

 

下载

释开始的标志,与 C语言中“/* ...... */”用法完全相同。

6) 使用public、private和protected关键字

public、private和protected关键字称为访问说明符。

第10章 C++入门

211

5) 类定义的最后一个花括号的外面一定要有分号结束。程序中出现的“ //”符号是作为注

说明为p u b l i c的类成员可以被任何函数所使用(当它们是数据成员时)或调用(当它们是

成员函数时)。调用者不必属于这个类或任何类。

说明为 p r i v a t e的类成员只能被同类中的成员函数所使用或调用,也可以被同类的友元使

用或调用。在类的成员中,若没有额外声明访问权限,则表明为 private的类成员。

说明为 p r o t e c t e d的类成员只能被同类中的成员函数或同类的友元类,或派生类的成员函

数及友元类所使用或调用。

2. 类与对象

上述类 e x a m 1提出了两个概念:类与对象。从形式上看,类与对象的关系类似于 C语言中

的数据类型与变量的关系,类是将具有相同属性和行为的事物做一个概括,它是普遍意义上

的一般概念,而对象是具有类特征的具体事物。一旦定义了类,那么就有无数的具有该属性

和行为的对象与之对应。

类在概念上是一种抽象机制,它抽象了一类对象的存储和操作特性;在系统实现中,类

是一种共享机制,它提供了一类对象共享其类的操作实现。

类是对象的模板,对象承袭了类中的数据和方法,只是各对象具有的初始化数据不同,

所表示的对象状态也不同。

一个C + +文件可以作为一个文件存储,其文件的扩展名为“ . c p p”,也可以作为几个文件

存储。若作为几个文件存储,一般说来应把类的声明部分存于“ . h”的头文件中,而把类的

实现部分和类的使用部分分别存于扩展名为“ . c p p”的文件中。包含主函数的 . c p p文件中应包

含.h和其它.cpp文件。规模较大的程序,应采用模块化的程序设计技术。

C + +程序的编辑、编译、连接及运行的方法和过程,在

作非常方便。

10.3.2 构造函数与析构函数

C + +中,类是一种数据类型,这样的类型总与存储空间相关,即要占用一定的内存资源。

当我们定义了对象时,编译系统就会为它分配存储,进行一定的初始化,由于类的结构各不

相同,所需的工作量也各不相同,为此, C + +提供构造函数来完成上述工作。构造函数是属

于某一特定类,可由用户设置,也可用系统缺省设置。与之相对应的是类的析构函数,当类

的对象退出作用域时,析构函数负责回收存储空间,并做一些必要的善后处理。析构函数也

是属于某一特定类,可由用户设置,也可用系统缺省。

1. 构造函数

当定义一个对象时,我们需要给对象开辟一个存储空间,将对象的数据成员初始化。在

使用构造函数以前,首先对构造函数作如下说明:

1) 构造函数具有与类名相同的函数名。

2) 构造函数没有返回类型,即使void也不可以。它的返回值是隐含的,是指向类本身的指针。

D O S 下,与 C语言基本一样。

Borland C++或是Turbo C++,均有一个集成开发环境,易学易用(与 Turbo C大同小异),操

 

212

C语言程序设计

下载

3) 构造函数在对象被定义时自动调用,作相应的初始化。

4) 构造函数可以有参数,也可无参数。

5) 构造函数名可以重载。

6) 当类中无与类名相同的构造函数时, C++编译系统为其设置缺省的构造函数。

[例10-5] 构造函数应用举例

#include <stdio.h>

classA{

                                         // 缺省访问权限,为私有数据成员int a,b,c;

public:// 公有段

       A(int=1 ,int=2 ,int=3);// 构造函数1

       A(double ,double ,double);// 构造函数2

       A(long);// 构造函数3

       A(A&);// 构造函数4(拷贝构造函数)

       void show()// 公有成员函数

            {

            printf("%d , %d , %d\n" ,a,b,c);

            }

      };

                   ,int I2,int I3)// 构造函数1的实现A::A(int I1

     {

       a=I1; b=I2;c=I3;

       }

                      ,double f2,double f3)// 构造函数2的实现A::A(double f1

     {

       a=(int)f1;b=(int)f2;c=(int)f3;

       }

     A::A(long n)// 构造函数3的实现

      {

      a=b=c=(int)n;

     }

     A::A(A& other)// 构造函数4的实现

             {

             a=other.a;

             b=other.b;

             c=other.c;

             }

    main()

    {

    Ax1;// 定义对象x1,调用缺省参数的构造函数1

    x1.show();// 调用公有段成员函数 show()

    Ax2(3);// 定义对象x2,调用构造函数 1

    x2.show();// 调用公有段成员函数 show()

                ,1// 定义对象x3 ,调用构造函数 1Ax3(3 );

    x3.show();// 调用公有段成员函数 show()

                   ,2// 定义对象x4 ,调用构造函数 2Ax4(3.14 .414 ,6.28);

    x4.show();// 调用公有段成员函数 show()

    Ax5(53L);// 定义对象x5 ,调用构造函数 3

    x5.show();// 调用公有段成员函数 show()

 

下载

A x6=x5;

x6.show();

return 0;

}

第10章 C++入门

213

// 定义对象x6,调用拷贝构造函数 4

// 调用公有段成员函数show()

运行上述程序,得如下输出:

1,2,3

3,2,3

3,1,3

3,2,6

53 ,53,53

53 ,53,53

可以提供不带参数的构造函数,即是缺省的构造函数。例如:

class

A{

...

A();

...

};

()

b=0;

{

c=0;

}

A::A

a=0;

但要注意的是,不能将可缺省参数的构造函数与缺省的构造函数一起使用,以免编译系

统混淆。例如:

class

A{

...

A( );

A(int=1 ,

...

};

int=2 ,

int=3);

voidmain( )

{

 Aobj;

...

}

// 编译系统无法区分应调用哪一个构造函数

2. 析构函数

与构造函数对应的是析构函数。 C++用析构函数来处理对象的善后工作,在对象撤销时自

动调用,并可能要释放一些动态的存储空间等。析构函数具有如下的一些特点:

1) 与类同名,之前冠以波浪线,以区别构造函数。

2) 不指定返回类型。

3) 不能指定参数。

4) 一个类只能有一个析构函数。

析构函数可以这样写:

class A{

      ...

 

214

 

          public:

          ...

          ~A();

         };

A::A(){...}

// 析构函数定义

[例10-6]

我们将构造函数进行编号,在程序的运行中去发现对象被定义后,调用的构造

函数及对象被撤销时,调用的析构函数的处理过程。

#include <stdio.h>

classA{

                                         // 类的私有成员,number 用于标志构造函数int a,b,c,number;

       public:

                                              // 类的构造函数A(int=1 ,int=2 ,int=3);

      A(double ,double ,double);

     A(long);

      A(A&);

     ~A(){// 类的析构函数

        printf("object x destroyed by constr %d created\r\n"ber);,num

           }

      void show()

            {

            printf("%d , %d, %d\n" ,a,b,c);

            }

    };

                  ,int i2,int i3)// 构造函数1A::A(int i1

    {

      a=i1; b=i2;c=i3; number=1;

      }

                      ,double f2,double f3)// 构造函数2A::A(double f1

    {

     a=(int)f1;b=(int)f2;c=(int)f3;number=2;

     }

    A::A(long n)// 构造函数3

    {

    a=b=c=(int)n;number= 3;

    }

    A::A(A& other)// 拷贝的构造函数4

            {

            a=other.a;

            b=other.b;

            c=other.c;

            number=4;

            }

   main()

   {

   Ax1;// 定义对象X1 调用构造1

   x1.show();

   Ax2(3);// 定义对象X2 调用构造1

   x2.show();

               ,1// 定义对象X3调用构造1Ax3(3 );

   x3.show();

Ax4(3.14 .414 ,6.28);,2

x4.show();

Ax5(53L);

x5.show();

A x6=x5;

x6.show();

return 0;

}

//定义对象X4 调用构造2

//定义对象X5 调用构造3

// 定义对象X 6调用构造4

//各对象调用相应的析构函数。

程序的输出:

1,2,3

3,2,3

3,1,3

3,2,6

53 ,53,53

53 ,53,53

object x destroyed

object x destroyed

object x destroyed

object x destroyed

object x destroyed

object x destroyed

by

by

by

by

by

by

constr

constr

constr

constr

constr

constr

4

3

2

1

1

1

created

created

created

created

created

created

从上述输出结果来看,对象一旦定义,就必须要调用构造函数,退出时要调用析构函数。

先定义的对象最后被毁灭或后定义的对象最先使用析构函数。

10.3.3 函数重载

上述程序中出现的构造函数,在形式上看,参数各不相同。正是由此,被定义的对象根

据参数形式的不同,调用不同的构造函数,这个过程我们称为函数的重载。当然,不仅构造

函数可以重载,其它的类成员函数也同样可以重载。

[例10-7] 类的成员函数的重载。

#include <stdlib.h>

#include <string.h>

#include <iostream.h.>

class string{

          int length;

          char str[256];

          public:

          string(){ length=0;

string(char *);

char * search(char);

char * search(char *);

       };

string::string(char *text)

{

 if (strlen(text)<256)

       strcpy(str ,text);

strcpy(str");},"

     // 类的构造函数 1,完成字符串的初始化

     //重载的构造函数 2

     // 返回字符指针的成员函数1

     // 返回字符指针的重载成员函数 2

// 构造函数2的定义

// 若串text 的长度小于256

// 将串text 复制给串str

 

216

 

// 若串text 的长度超过255

// 则将串text 的255 个字符复制给串 str

else

 strncpy(str ,text ,255);

length=strlen(str);

   }

   char *string::search(char arg) / 成员函数1的定义/

   {

        return strchr(str rg);,a// 返回串str 中第一次出现字符 arg 的位置

    }

  char *string::search(char *arg) / 重载成员函数2的定义/

  {

        return strstr(str rg);,a// 返回串str 在串arg 中第一次出现的位置

  }

void main()

{

 string hellomsg='Hello here ,I'm a string!';,t

                                          // 定义串string 的对象,自动调用构造函数1

 char *found;// 定义字符串found

 cout<<hellomsg.search('t')<<"\r\n";

                                          // 输出对象的成员函数 1的返回值

 cout<<hellomsg.search("string")<<"\r\n";

                                          // 输出对象的成员函数 2的返回值

 }

运行程序,输出为 :

Hello, there,I'm a string!

string!

10.3.4 友元

在类成员的介绍中,我们对类的三种成员做过详细的说明,特别是强调了类的私有成员

和类保护段成员的隐蔽性,它们只能被类对象的成员函数所访问,这也同样表明,类的成员

函数可以访问类中的所有成员。友元是 C + +提供给外部的类或函数访问类的私有成员和保护

成员的一种途径。

在一个类中,将friend加在某个函数或某个类的前面,则该函数或类将成为所在类的友元。

友元不受它们在类中出现次序的影响,而仅表明其是类的友元。

[例10-8] 说明类的友元函数的作用及与成员函数的区别。

#include <iostream.h>

#include <string.h>

class stud{

   char *name num ,*tel;,*

   public:

     stud(char *na,char *nu char *pho ),

     {

       name=new char[strlen(na)+1];

// 类的私有成员:姓名,学号,电话号码

// 类的构造函数

strcpy(name ,na);

//使用运算符 new 向系统申请 name 所需的存

  储空间

  // 将参数na的值复制给姓名 name

 

下载

第10章 C++入门

217

num=new char[strlen(nu)+1];

                           // 使用运算符new 向系统申请num 所需的存储空间

strcpy(num ,nu);// 将参数nu的值复制给学号 num

tel=new char[strlen(pho)+1];

                           // 使用运算符new 向系统申请tel 所需的存储空间

strcpy(tel ,pho);// 将参数pho 的值复制给电话号码 tel

}

void show(stud&);// 类的公有成员函数

friend void show(stud&);/ 类的友元函数/

~stud()// 类的析构函数

  { delete name; delete num; delete tel;}

                           // 使用运算符delete 释放类的各数据成员所占存储空间

 };

void show(stud &student)/友元函数的定义/

{

 cout<<" 类的友元函数的调用:\n";

 cout<<"student\"s name is:"<<student.name

     <<"\nstudent\"s number:"<<student.num

     <<"\nstudent\"s telephone:"<<student.tel<<"\n";

 cout<<" 友元函数调用类的成员函数: \n";

 student.show(student);//调用类的成员函数

 }

 void stud::show(stud &student) 类的成员函数的定义//

{

 cout<<" 类的成员函数的调用:\n";

 cout<<"student\"s name is:"<<student.name

     <<"\nstudent\"s number:"<<student.num

     <<"\nstudent\"s telephone:"<<student.tel<<"\n";

 }

 void main()

 {

   studx("li-ling" ,"j98-10323" ,"0285533123");// 定义类的对象 x

   show(x);//调用类的友元函数

   x.show(x);// 调用类的成员函数

  }

上述程序中,类的友元函数和类的成员函数完成相同的功能,并且函数名也完全相同,

不同的只是说明和定义的形式。让我们先看一下程序运行后的输出:

类的友元函数的调用:

student\'s name is: li-ling

student\'s number: j98-10323

student\'s telephone: 0285533123

友元函数调用类的成员函数:

student\'s name is: li-ling

student\'s number: j98-10323

student\'s telephone: 0285533123

student\'s name is: li-ling

student\'s number: j98-10323

student\'s telephone: 0285533123

顺便需要说明的是:类的友元函数的定义既可放在类内,也可在类外,与成员函数是一

致的。如放在类内,则不受段访问特性的影响。

友元可以是多个类的友元,即可以跨类访问。

关于跨类友元的访问,程序能验证其各位数的立方和等于其数本身的任一个三

位数,如: 13+53+33=150等。

#include <iostream.h>

class hundreds; // 类的声明。在类未定义前需使用时,必须先声明。

class cubic// 类cubic 的定义

{

  int sum;

  public:

                       ,int b,int c){ sum=a*a*a+b*b*b+c*c*c;}void set_sum(int a

                          // 类的成员函数用于求其各位数的立方和。

  friend int equal(cubic ,hundreds h);c// 类的友元函数

    } cub;// 在类定义的同时定义对象cub

  class hundreds{ / 类hundreds 的定义/

    int num;

    public:

                        ,int b,int c){void set_num(int a

    num=100*a+10*b+c;

   }

                          // 类的成员函数用于求其三个数构成的三位数

      friend int equal(cubic ,hundreds h);//c类的友元函数

      void display(void){cout<<num<<endl;}类的成员函数//

      }hun; // 在类定义的同时定义对象hun

      int equal(cubic c,hundreds h){ return !(c.sum-h.num);}

             // 类cubic 和类hundreds 的友元函数equal 的定义,用于验证由三个数构成的

      main(void)

      {

         int d3,d2,d1;

   for (d3=1;d3<10;d3++)

     for (d2=0;d2<10;d2++)

      for (d1=0;d1<10;d1++)

      {

        cub.set_sum(d3 ,d2 ,d1);

                          // 对象cub 调用其成员函数求得三个数的立方和

                          hun.set_num(d3 ,d2 ,d1);

                          // 对象hun 调用其成员函数求得构成的三位数

        if (equal(cub,hun)) hun.display(); // 等则显示出三位数相

        }

   return 0;

   }

该程序的输出是:

150

370

371

407

219

一定请注意,友元函数在使用时,与成员函数的使用有所区别,它不能在函数名前加上

类名作函数调用的限定,即不能写成 cubic::equal或hundreds::equal。同时,我们也可以将类声

明为友元。

   class Y;//类的声明

   class X{

   friendsY;//说明类Y是类X的友元

   int data1;

   …

   void fun_mem1( { …}…)

   …

   };

class Y{

   float data2;

          …

          void fun_mem2(…){ …}

          …

};

上述定义形式表明,类 Y的全部成员均可访问类 X的成员。

10.4 对象指针

当我们需要动态地在存储空间上使用对象时,就需要定义对象指针。正如在 C语言中使用

指针变量一样,什么时候需要,就使用 m a l l o c ( )为其分配内存,使用完毕,就利用 f r e e ( )释放

其占用的内存。

1. 运算符new与delete

在C + +中,运算符 n e w和d e l e t e可以让用户创建任意持续时间的动态变量,也可以让用户

为任意种类的 C语言的对象分配内存空间。通常,我们用 n e w来为C或C+ +的对象动态地分配

存储空间,用 delete来将占用内存的对象移走或称为释放,其意义与 C语言提供的 malloc()功能

与free()功能类似。

运算符new和delete的用法很简单,它们均为单目运算符:

例:我们定义对象指针如下:

 int*num1;

 float*num2;

 double *num3

 char*str1;

        …}class X{ *objx;

为对象指针分配内存空间:

num1=new

num2=new

num3=new

str1=new

objx=new

int;

float;

double;

char[80];

X;

利用new运算符分配空间的对象,使用完毕后应通过 delete对所占空间进行释放。

 

220

delete

delete

delete

delete

delete

C语言程序设计

下载

num1;

num2;

num3;

[]str1;

objx;

2. 动态地创建类对象

生活中常会遇到的实际问题是处理的数据量不固定,数据的个数在动态地改变。链表技

术就是为解决这一问题应运而生的,这种特定的数据结构中每一个节点用于存放一组数据,

随着数据的不断增加,链表也在不断增长,占用的内存不断增加。当问题得到解决后,数据

不再需要保存,占用的内存就释放掉,这个过程就是数据的动态存储。

[例10-10] 动态存放一组字符串在一双向链表,链表结构如图 10-2所示。

head

NULL

nodebody

nodenum

next

prev

nodebody

nodenum

next

prev

nodebody

nodenum

NULL

图10-2 链表结构示意

其中,h e a d是头指针,指向链表的头; p r e v指向链表的前一个节点; n e x t指向链表的下一

个节点;nodebody是链表节点存放的字符串; nodenum是链表节点存放的字符串长度。

链表节点的结构为:

class tnode{

   tnode *prev;

   tnode *next;

   char * nodebody;

   intnodenum;

   }

双向链表的操作有:

1) 移动指针到链表头。

2) 移动指针到链表尾。

3) 在链表尾部追加一个字符串。

4) 从链表开始,按字母的排列顺序插入一个字符串。

5) 释放链表各节点所占内存。

程序如下:

#include "stdlib.h"

#include <stdio.h>

#include <string.h>

#include <conio.h>

class tnode{

   public:

   tnode *prev;

// 链表节点的数据结构

// 指向前一个节点

tnode *next;

char *nodebody;

int nodenum;

   };

class dbllist{

 tnode *head;

//指向下一个节点

// 节点所存字符串

// 节点字符串的长度

// 链表结构

// 链表的头指针

第10章 C++入门

221

      tnode *base;

      tnode *hold;//base 与hold 用于跟踪链表增长

      tnode *create(char *);//为一个节点分配存储空间

      public:

       dbllist();// 链表的构造函数

       ~dbllist();// 链表的析构函数

        void clear();// 释放链表所占空间

        tnode *gohead();//将指针移到链表头

        tnode *gotail();// 将指针移到链表尾

        tnode *gonext();// 将指针移到链表的下一个节点

        tnode *goprev();//将指针移到链表的前一个节点

        tnode *append(char *); // 追加一个字符串

        tnode *insert(char *); // 插入一个字符串

        char* accept(tnode *); //接收指定节点的字符串

        };

        dbllist::dbllist()

        {

          head=base=hold=NULL;// 链表指针初始化

          }

       dbllist::~dbllist()//析构函数

          {

          clear();

          }

     void dbllist::clear()/ 释放链表各节点/

     {

     base=head;// 头指针

     while(base)

          {// 链表非空

      hold=base->next;

// 删除节点如

// 图10-3 所示

      deletebase;

      base=hold;//在跟踪链表的过程中释放各节点

      }

        head=base=hold=NULL;

        }

      tnode * dbllist::gohead() //将指针移到链表头

      {

      base=head;

      if (base) return base;// 返回头指针

      else return NULL;

      }

   tnode * dbllist::gotail()//将指针移到链表尾

   {

      if (base)

// 跟踪链表到尾,返回尾指针

 while(base->next) base=base->next;

  return base;

  }

else return NULL;

   }

 tnode* dbllist::gonext()

 {

  if (base)

  {

        if (base->next)

                {

                   base=base->next;

                return base;

                }

        else return NULL;

       }

   else return NULL;

   }

tnode * dbllist::goprev()

  {

 if (base)

  {

   if (base->prev)

  {

                    base=base->prev;

                    return base;

                    }

            else return NULL;

        }

    else return NULL;

    }

 tnode* dbllist::append(char * str)

 {

 tnode *temp;

 if((temp=create(str))==NULL)

     return NULL;

     gotail();

     if (!base)

   {

        head=base=temp;

        }

   else

   {

        base->next=temp;

        temp->prev=base;

        base=temp;

        }

        return base;

// 将指针移到链表的下一个节点

// 节点指针后移

// 将指针移到链表的前一个节点

// 节点指针前移

// 在尾部追加一个字符串

// 申请创建一个新节点(分配存储空间)

// 找到尾节点

// 链表无节点,连接到头

// 追加到尾部

    }

 tnode * dbllist::insert(char* str)//插入一个字符串

 {

    tnode *temp;

    gohead();//指向链表头

    if (!base) return(append(str));// 若是空链表,直接追加后返回

    if ((temp=create(str))==NULL)// 申请一个新节点 temp

        return NULL;

    while (base->next&&memcmp(str ase->nodebody ,strlen(str)+1)>0),b

     base=base->next;

    // 若当前节点不是尾,同时被插字符串按字母表顺序排在该节点的前面,则指针后移

      if (!base->next&&memcmp(str ase->nodebody ,strlen(str)+1)>0),b

      {// 插入位置是链表尾。

            base->next=temp;

     //插入链表尾的操作如图 10-4

//

//

      temp->prev=base;

      base=temp;

      }

      else

      {// 非尾节点,将新插节点连结到链表内

     hold=base->prev;

     temp->prev=hold;

     temp->next=base;

     base->prev=temp;

     if (!hold) head=temp;// 插入位置在表头

     else hold->next=temp;

     base=temp;

     }

     return base;

   }

   tnode * dbllist::create(char* str)//为新节点分配空间

    {

       hold=new tnode;// 申请新节点

       hold->nodebody=new char[strlen(str)+1];

                               // 申请插入字符串所占空间

      memmove(hold->nodebody,str ,strlen(str)+1);// 复制字符串到该节点

       hold->prev=hold->next=NULL;// 该节点的指向前后的指针为空

        hold->nodenum=strlen(str)+1;// 节点字符串的长度

       return hold;

       }

       char* dbllist::accept(tnode *ptr)//返回节点字符串的值

       {

       return ptr->nodebody;

       }

     void main()// 主程序

{

  tnode *pointer=NULL;// 节点类对象指针

  dbllist lex;// 链表类对象

  clrscr();// 清屏幕

  lex.append("aaaaa");// 追加字符串

  lex.append("bbbbbbbb");

  lex.append("cccccc");

  lex.append("aaaaaaaaaaaa");

  pointer=lex.gohead();// 得到链表头指针

  while(pointer)

  {// 非空链表,顺序输出字符串

   printf("%s\n" ,lex.accept(pointer));

   pointer=lex.gonext();

   }

   pointer=lex.gotail();// 得到链表尾指针

    while(pointer)

  {// 非空链表,从后向前输出字符串

    printf("%s\n" ,lex.accept(pointer));

    pointer=lex.goprev();

    }

    lex.clear();// 释放链表

   lex.insert("xxxxxx");// 按字母表顺序插入字符串

   lex.insert("yyyyyy");

   lex.insert("zzzzzz");

   lex.insert("aaaaaaa");

     pointer=lex.gohead();

   while(pointer)

  {

       printf("%s\n" ,lex.accept(pointer));

        pointer=lex.gonext();

   }

   pointer=lex.gotail();

   while(pointer)

  {

        printf("%s\n" ,lex.accept(pointer));

        pointer=lex.goprev();

    }

}

base

base

next

hold

temp

next

next

Prev

next

图10-3 删除节点

图10-4 在链表尾插入一个节点

程序运行后,输出为:

aaaaa

bbbbbbbb

cccccc

aaaaaaaaaaaa

aaaaaaaaaaaa

 

下载

cccccc

bbbbbbbb

aaaaa

aaaaaaa

xxxxxx

yyyyyy

zzzzzz

zzzzzz

yyyyyy

xxxxxx

aaaaaaa

第10章 C++入门

225

程序按不同接入链表的方法,将会按正序和逆序输出各节点所存字符串。

最后,我们再重申, new和delete的用法为:

obj_ptr=new obj_type(new_initializer);

delete obj_ptr;

delete [ ]obj_ptr;

其中,obj_ptr是对象指针或是变量指针; obj_type是类类型或变量类型;若 new_initializer

不空,则表示分配空间的大小。若 o b j _ p t r是指针,则通过“ delete obj_ptr;”来释放空间;若

obj_ptr是指针数组,则通过“ delete [ ]obj_ptr;”来释放所占内存空间。

10.5 派生类与继承类

在C++中派生类是指从某一类派生出新类的过程,它能继承基类或父类的属性和功能,所

以,我们也称派生类为继承类。派生或继承的过程类似与我们在 C语言中的一些代码的可重用。

各种C的编译版本事先为使用者开发出尽可能多的标准函数,以方便用户使用,使用者无需了

解函数实现的具体细节,就能方便灵活地使用。

在软件的开发过程中,要充分利用系统提供的各种资源,以减少开发人员的劳动。 C++对

系统或用户开发的代码即类的实现补充了更为广大的发展空间,既可以做类代码的再利用,

也可以做包含继承性的类的派生;既可以做某一个类的继承或派生,也可以做多个类的继承

或派生,这就是我们要谈到的单继承的派生和多继承的派生。

10.5.1 单继承的派生类

通过基类或父类继承产生新类的过程称派生,新类则称为派生类,旧的代码或旧类称为

基类。

从一个类派生出另一个类的语法非常简单:

class base{……};

……

class derived:base{ …};…

……

对于类 b a s e来说,它作为基类,应有完整的定义和说明,只有名字,不能作为一个基类。

所有基类成员相对派生类来说都是局部成员,换句话说,对派生类是隐蔽的、不可访问的,

如果需要对基类成员进行访问,则需在基类的类名前加上访问限制符如下:

class base{……};

 

226

C语言程序设计

下载

……

class derived:public base{ ;……}

……

这种派生或继承的方法也称为公有派生。存取访问限制符分为 p u b l i c、p r i v a t e、p r o t e c t e d

三种,也就是对基类的派生或继承有三种,分别说明如下。

1. 声明一个基类为 public

存取访问符 p u b l i c使基类成员保持基类成员的特性,原来是 p u b l i c的成员,在派生类中继

续保持公有,原为 p r i v a t e的成员, 在派生类中继续保持私有,原来是 p r o t e c t e d的成员,在派生

类中继续保持其保护特性。

2. 声明一个基类为 private

存取访问符 p r i v a t e使基类成员中原为 p u b l i c和p r o t e c t e d的成员派生为私有成员,而原为私

有的成员对派生类来说,则是隐蔽的,不透明的,不可访问的。

3. 声明一个基类是 protected

存取访问符 p r o t e c t e d使基类成员中原为 p u b l i c的成员成为派生类中的 p r o t e c t e d成员,原为

protected的成员成为派生类中的 private成员。

[例1 0 - 11]

项目来实现:

stack.prj

list.h

stack.h

list.cpp

stack.cpp

exam.cpp

利用一单链表派生为一堆栈,实现堆栈的出入功能。我们通过创建一个工程

接下来分段介绍:

头文件list.h清单:

const int Max_elem = 10;

class List// 定义单链表

{

    int *list;// 整型数组

    int nmax;//数组大小

    int nelem;//数组下标

public:

    List(int n = Max_elem) {list = new int[n]; nmax = n; nelem = 0;};

                  // 单链表的构造函数(长度为 10 的整型数组)

    ~List() {delete list;};// 析构函数

    int put_elem(int, int);// 成员函数

    int get_elem(int&, int);

    void setn(int n) {nelem = n;};

    int getn() {return nelem;};

    void incn() {if (nelem < nmax) ++nelem;};

    int getmax() {return nmax;};

    void print();

};

文件list.cpp清单:

#include <iostream.h>

#include "list.h"

int List::put_elem(int elem, int pos)

{

   if (0 <= pos && pos < nmax)

   {

       list[pos] = elem;// 放一整数安

       return 0;

   }

   else

       return -1;//出错返回

}

int List::get_elem(int& elem, int pos)

{

   if (0 <= pos && pos < nmax)

   {

       elem = list[pos];//取数组元素

       return 0;

   }

   else

       return -1;//出错返回

}

void List::print()// 顺序输出数组元素

{

   for (int i = 0; i < nelem; ++i)

       cout << list[i] << " ";

   cout<<"\n"

}

// 在数组的指定位置安放一整数

//在指定位置获取元素

派生类stack的定义,文件stack.h清单:

#include "list.h"

class Stack : public List//关于基类的共有派生

{

    int top;// 栈顶指针

public:

    Stack() {top = 0;};//构造函数

    Stack(int n) : List(n) {top = 0;};

                        // 重载的构造函数,包括对基类的初始化

    int push(int elem); // 压栈

    int pop(int& elem); //出栈

    void print();// 输出

};

派生类stack的成员函数定义,文件 stack.cpp清单:

#include <iostream.h>

#include "stack.h"

int Stack::push(int elem)

//压栈

 

228

{

int m = getmax();

if (top < m)

{

   put_elem(elem,top++);

   return 0;

}

else

   return -1;

}

int Stack::pop(int& elem)// 出栈

{

   if (top > 0)

   {

      get_elem(elem,--top);

      return 0;

   }

   else

      return -1;

}

void Stack::print()// 输出

{

   int elem;

   for (int i = top-1; i >= 0; --i)

   { // 按先进后出的顺序

       get_elem(elem,i);

      cout << elem << " ";

   }

 cout<<"\n"

}

最后给出main函数exam.cpp实现堆栈的操作:

#include "stack.h"

main()

{

    Stack s(5);// 定义堆栈Stack 类的对象s ,堆栈大小为5

    int i = 0;

    // 插入1~5

    while (s.push(i+1) == 0)

        ++i;

    s.print();// 输出

    return 0;

}

程序运行的结果为 :

5

4

3

2

1

上述程序在定义类对象 s时,调用了类的构造函数 Stack(int n) : List(n) {top = 0;}进行初始

化,由于是派生类,还需对基类进行初始化,当然需要调用基类的构造函数

List(int n =

Max_elem) {list = new int[n]; nmax = n; nelem = 0;}使其基类初始化为能存放 5个元素的整型数

组作为堆栈。通过对基类的公有派生过程,类 s t a c k实际具有成员相当于基类的全部成员与派

生类定义的成员总和,我们同样可以在派生类中对基类成员进行调用,只要满足派生类对基

类的访问要求即可。下面对程序的 m a i n函数进行修改,充分利用派生类中的各成员。修改后

的exam.cpp如下:

#include "stack.h"

main()

{

    Stack s(5);

    int i = 0;

    // Insert the numbers 1 through 5

    while (s.push(i+1) == 0)

        ++i;

    s.print();

     List l(5);//定义基类对象并初始化

     i = 0;

    // 调用基类成员写入 1~5 在整型数组中

    while (l.put_elem(i+1,i) == 0)

        ++i;

    l.setn(i);

    l.print();//输出数组各元素

    i = 0;

    // 利用派生类对象调用基类成员

     while (s.put_elem(i+1,i) == 0)

        ++i;

    s.setn(i);

    s.List::print();//派生类对象访问基类的同名成员函数

    return 0;

}

重新运行程序,得输出为:

5

1

1

4

2

2

3

3

3

2

4

4

1

5

5

由于派生类与基类均有 print(),所以为避免冲突通过派生类访问基类的同名函数,则需要

在基类成员的前面加上类的限制符,即“派生类对象

s.List.print().

     还有一种情况也是我们应当引起重视的:

class A{......};

class B:public A{......};

class C:public B{......};

. 基类名: 成员”:,在程序中为

这种结构是按层次进行派生,每层派生类都只有一个直接基类,类 C继承类 A和类B的成

员特性,当然受到派生访问控制符的限制。

下面的例子是一个单基多级派生的问题。定义一个基类 Location用于定义点坐标,在此基

础上公有派生出类 Point, 以完成一个定位象素点的输出。由于 Point类继承了基类 Location的坐

标点,我们在此基础之上公有派生类 C i r c l e,利用此坐标点做圆心在屏幕上绘 制一个圆,并

 

230

C语言程序设计

下载

画出大小不一的、圆心、半径均不同的各种圆。程序设计的思路是:首先定义一个基本类

L o c a t i o n,它是针对一个象素点的坐标及初始化:在此基础上派生一个类 P o i n t,该类具有关

于点的属性及绘制点的基本操作。最后,定义一个公有派生类 Circle画圆。

[例10-12] 做工程项目:

point.h

demo.prj

point2.cpp

circle.cpp

文件point.h清单

enum Boolean {false, true}; // 定义枚举类型

class Location {// 基类Location ,用于设置点坐标。

protected:// 允许派生类访问的保护段成员

    int X;// 坐标点

    int Y;

public:// 允许派生类访问

    Location(int InitX, int InitY); // 构造函数

    int GetX();

    int GetY();

};

class Point : public Location

         // 从类Location 一级派生,用于绘制点。

protected:

    Boolean Visible; //下级派生类可以访问的 protected 段

public:

    Point(int InitX, int InitY);//构造函数

    void Show();

    void Hide();

    Boolean IsVisible();

    void MoveTo(int NewX, int NewY);

};

类Point是从基类 Location公有派生而来,对基类中的 protected 段和public 段成员保持属性

不变。下面在point2.cpp文件中对基类和一级派生类各成员函数进行定义。

文件point2.cpp清单

#include "point.h"

#include <graphics.h>

// 类Location 的成员函数

Location::Location(int InitX, int InitY) { 构造函数//

   X = InitX;// 设置点

   Y = InitY;

};

int Location::GetX(void) {// 类Location 的成员函数,返回点的 x

   return X;

};

int Location::GetY(void) {// 类Location 的成员函数,返回点的 Y

   return Y;

};

// 类Point 的成员函数的定义

// 类Point 的构造函数,包括对基类的初始化 .

Point::Point(int InitX, int InitY) : Location(InitX,InitY) {

   Visible = false;// make invisible by default

};

void Point::Show(void) {

   Visible = true;

   putpixel(X, Y, getcolor());//用当前字符颜色写一个像素点

};

void Point::Hide(void) // 擦除一个像素点{

   Visible = false;

   putpixel(X, Y, getbkcolor()); //用屏幕背景色画一个像素点

};

Boolean Point::IsVisible(void) {

   return Visible;

};

void Point::MoveTo(int NewX, int NewY) {// 移动屏幕上的一点

   Hide();// 擦除点

   X = NewX;// 改变坐标X和Y到新位置

   Y = NewY;

   Show();// 在新位置显示点

};

文件circle.cpp清单

#include <graphics.h>// graphics library declarations

#include "point.h"// Location and Point class declarations

#include <conio.h>// for getch() function

// link with point2.obj and graphics.lib

class Circle : Point {//从类Point 和类Location 的二级派生

    int Radius;//私有成员

public:

    Circle(int InitX, int InitY, int InitRadius);

    void Show(void);

    void Hide(void);

    void Expand(int ExpandBy);

    void MoveTo(int NewX, int NewY);

    void Contract(int ContractBy);

};

Circle::Circle(int InitX, int InitY, int InitRadius) : Point(InitX,InitY)

{

    Radius = InitRadius;

};

void Circle::Show(void)// 画圆

{

    Visible = true;// 显示标志

    circle(X, Y, Radius);//利用标准函数画圆

}

void Circle::Hide(void)

{

                                用于存放当前屏幕色彩unsigned int TempColor;//

    TempColor = getcolor();//读取当前屏幕色彩

    setcolor(getbkcolor());//设置当前屏幕色彩为背景色

    Visible = false;

                                删除圆circle(X, Y, Radius);//

    setcolor(TempColor);//恢复当前屏幕色彩

};

void Circle::Expand(int ExpandBy) //放大圆

{

                                   擦除圆Hide();//

    Radius += ExpandBy;//修改圆半径

    if (Radius < 0)

        Radius = 0;

    Show();//画圆

};

void Circle::Contract(int ContractBy) 缩小圆//

{

   Expand(-ContractBy);//利用成员函数修改

};// 圆半径,画圆

void Circle::MoveTo(int NewX, int NewY) 移动圆//

{

                                擦除圆Hide();//

    X = NewX;//设置新圆心坐标

    Y = NewY;

                                重画圆Show();//

};

                                测试函数main()//

{

                              // 初始化图形系统

    int graphdriver = DETECT, graphmode;

    initgraph(&graphdriver, &graphmode, "..\\bgi");

                                      定义类circle 的对象Circle MyCircle(100, 200, 50);//

    MyCircle.Show();//显示圆

    getch();//等待一个按键

    MyCircle.MoveTo(200, 250);//移动圆心到(200,250 )、重画圆。

    getch();// 按一键

    MyCircle.Expand(50);//增加圆半径50 ,放大圆

    getch();

    MyCircle.Contract(75);//缩小圆

    getch();

    closegraph();// 关闭图形系统。

    return 0;

}

运行上述程序:会看到一个圆,在键盘上按键,圆移动;再按任一键,圆放大;再按任

一键,圆缩小。

派生类只有一个基类时,称为单基继承或单基派生;若具有多个基类时,称为多基继承

或多基派生。那么多基继承或多基派生在语法上与单继承有所不同,其语法结构为:

class A{......};

......

class B{......};

......

class c:public A,public B{......};

由类A和类B共同派生出新类 C,这样一来,类 C继承了类A和类B的成员。

在前面的例子中,我们定义了点,并继承点的特性后再画圆。由于派生类只有一个直接

基类,所以称为单继承。

[例10-13] 通过上述画圆的派生过程,我们在圆内写一个字符串,也就是要作多基派生。

先定义一个 G M e s s a g e的类,该类完成在 x和y坐标点处开始写一个字符串,利用前面定义

的C i r c l e的类,显示一个圆。在此基础上,再定义一个新类 M C i r c l e,既要画圆,又要写字符

串,应具有 GMessage类和Circle类的共同特性。见图 10-5。

Class Location

int x;

int y; ....

Class Location

int x;

int y; ....

Class Point:

Public Location

{....

Class GMessage:

Public Location

{....

Class Circle:

Public Point

{....

Class Mcircle:

Circle, GMessage

{....

图 10-5

定义项目CIRCLESTR.PRJ

Point.h

Point2.cpp

Mcircle.cpp

    /* point.h--Example from Getting Started */

//***********************************************

// point.h 包含两个类:

// class Location

// class Point

enum Boolean {false, true};//定义枚举类型

class Location {// 定义类

protected://可继承的受保护成员

    int X;

    int Y;

public://公有成员

    Location(int InitX, int InitY);

    int GetX();

    int GetY();

};

class Point : public Location {// class Location的派生

protected:

    Boolean Visible;// 可继承的受保护成员

public:

    Point(int InitX, int InitY);// constructor

    void Show();// 显示

    void Hide();// 隐藏

    Boolean IsVisible();

    void MoveTo(int NewX, int NewY); // 移动

};

 /* POINT2.CPP--Example from Getting Started */

//**********************************************

// POINT2.CPP 包含Point 类和Location 类的说明

#include "point.h"

#include <graphics.h>

// Location类的成员函数

Location::Location(int InitX, int InitY) {

    X = InitX;

    Y = InitY;

};

int Location::GetX(void) {

    return X;

};

int Location::GetY(void) {

    return Y;

};

// Point类的成员函数

Point::Point(int InitX, int InitY) : Location(InitX,InitY) {

   Visible = false;// make invisible by default

};

void Point::Show(void) {

   Visible = true;

// uses default color

};

void Point::Hide(void) {

    Visible = false;

    putpixel(X, Y, getbkcolor()); // uses background color to erase

};

Boolean Point::IsVisible(void) {

    return Visible;

};

void Point::MoveTo(int NewX, int NewY) {

    Hide();// make current point invisible

    X = NewX;// change X and Y coordinates to new location

    Y = NewY;

    Show();// show point at new location

};

// MCIRCLE.CPP

//******************************************************

#include <graphics.h> // Graphics library declarations

#include "point.h"// Location and Point class declarations

#include <string.h>// for string functions

#include <conio.h>// for console I/O

// link with point2.obj and graphics.lib

// The class hierarchy:

//

// (Circle and CMessage)->MCircle

class Circle : public Point { // Location->Point->Circle

                                                            // 多重派生

protected:

    int Radius;

public:

    Circle(int InitX, int InitY, int InitRadius);

    void Show(void);

};

class GMessage : public Location

// 在图形屏幕显示字符串

    char *msg;//被显示信息

    int Font;//文字字体

    int Field;//字型

public:

    // 构造函数初始化

    GMessage(int msgX, int msgY, int MsgFont, int FieldSize,

              char *text);

    void Show(void);//显示信息

};

class MCircle : Circle, GMessage { //多类继承

public:

    MCircle(int mcircX, int mcircY, int mcircRadius, int Font,

             char *msg);

    void Show(void);//画带字符串的圆

};

// Circle类的成员函数

//Circle 类的构造函数

Circle::Circle(int InitX, int InitY, int InitRadius) :

    Point (InitX, InitY)//构造函数的初始化

//包括对基类构造函数的初始化

{

    Radius = InitRadius;

};

void Circle::Show(void)

{

    Visible = true;

    circle(X, Y, Radius); //画圆

}

// Gmessage类的成员函数

//Gmessage 类的构造函数的初始化

GMessage::GMessage(int msgX, int msgY, int MsgFont,

              int FieldSize, char *text) :

              Location(msgX, msgY)

//对基类构造函数的处理

{

    Font = MsgFont;// standard fonts defined in graph.h

    Field = FieldSize; // width of area in which to fit text

    msg = text;// point at message

};

void GMessage::Show(void)

{

    int size = Field / (8 * strlen(msg));// 8 pixels per char.

    settextjustify(CENTER_TEXT, CENTER_TEXT); // centers in circle

    settextstyle(Font, HORIZ_DIR, size);// magnify if size > 1

    outtextxy(X, Y, msg);// display the text

}

// Mcircle类的成员函数

//Mcircle 类的构造函数

MCircle::MCircle(int mcircX, int mcircY, int mcircRadius, int Font,

                  char *msg) : Circle (mcircX, mcircY, mcircRadius),

                  GMessage(mcircX,mcircY,Font,2*mcircRadius,msg)

               //多继承应处理其多个基类的构造函数

{

}

void MCircle::Show(void)

{

    Circle::Show();//画圆

    GMessage::Show(); // 字符串写

}

main()//画圆并写入字符串

{

    int graphdriver = DETECT, graphmode;

initgraph(&graphdriver, &graphmode, "..\\bgi");

setbkcolor(15); //背景色

setcolor(4);// 景色前

MCircle Small(250, 100, 25, SANS_SERIF_FONT, "You");

Small.Show();

MCircle Medium(250, 150, 100, TRIPLEX_FONT, "World");

Medium.Show();

MCircle Large(250, 250, 225, GOTHIC_FONT, "Universe");

Large.Show();

getch();

closegraph();

return 0;

}

运行程序显示为:

 

posted @ 2013-05-24 14:02  herizai  阅读(312)  评论(0编辑  收藏  举报