讲栈
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; |
} |
运行程序显示为: |