第 13章 C到 C++
第 13章 C到 C++
13.1 面向对象技术
13.2 改进的 C语言
13.3 C++的输入与输出
13.4 类与对象
13.5 程序设计举例第 13章 C到 C++
13.1 面向对象技术
13.1.1 面向对象技术的由来和发展面向对象技术产生的背景与结构化程序设计方法产生的背景类似,面向对象程序设计方法 ( OOP) 是在结构化程序设计方法的基础上发展而来的 。
第 13章 C到 C++
13.1.2 面向对象技术的两大要素
1,对象从概念上讲,对象代表着正在创建系统中的一个实体 。
从形式上讲,对象是待处理的程序单元,是数据和方法的封装体 。 在 C++中是数据成员和成员函数的封装体 。
方法由若干操作构成 。 对象实现了信息隐藏,方法的具体实现外部是不可见的,封装的目的是阻止非法访问 。
对象通过消息与另一个对象传递信息 。 消息与方法一一对应,在 C++中,消息就是成员函数的调用 。
第 13章 C到 C++
2,类类是对象的抽象及描述,是创建对象的样板,它包含着一类对象的数据描述和方法定义 。
一个类的所有对象都有相同的数据结构,共享相同的方法,而各个对象有各自不同的状态,类是所有对象的共同行为和不同状态的集合 。
由一个特定的类所创建的对象称为这个类的实例。
第 13章 C到 C++
13.1.3
1.
封装的对象是数据和方法,支持数据封装就是支持数据抽象 。 在 C++中,类是支持数据封装的工具,对象则是数据封装的实现 。 没有封装,就没有面向对象技术 。
另外,封装还提供一种对数据访问严格控制的机制 。
因此,数据将被隐藏在封装体中,该封装体通过操作接口与外界交换信息 。
第 13章 C到 C++
2,继承类提供了说明一组对象结构的机制 。 借助于继承这一重要机制,已存在的类具有建立子类的能力,进而建立类的层次,扩充类的定义 。
继承提供了创建新类的一种方法,一个新类可以通过对已有类进行修改和扩充来定义 。 从一个类继承定义的新类,
将继承已有类的方法和属性,并且可添加不包含在父类中的新方法和属性 。 新类被称为已有类的子类,又称为派生类,
已有类称为新类的父类,又称为基类 。
C++中允许单继承和多继承,一个类可以根据需要生成派生类。
第 13章 C到 C++
3.
多态是指相同的语法结构可以代表不同类型的实体或者对不同类型的实体进行操作,即发出同样的消息被不同对象接收时导致完全不同的行为 。
C++允许函数名和运算符重载,允许一个相同的标识符或运算符代表多个不同实现的函数,这是编译时的多态性 。
C++中可以定义虚函数,通过定义虚函数来支持动态联编 。 动态联编是另一类重要的多态性,多态性形成由父类和它们的子类组成的一个树型结构 。 在这个树中的每一个子类可接收一个或多个具有相同名字的消息 。 当一个消息被这个树中一个类的一个对象接收时,这个对象动态地决定给予子类对象的消息的某种用法 。 这是执行时的多态性 。
第 13章 C到 C++
13.1.4 面向对象程序设计面向对象的程序设计方法是目前最先进的程序设计方法 。
面向对象程序设计模拟人类认识问题较高,较广层次的过程 。 结构化程序设计强调功能抽象,程序的模块化,
基于功能进行模块分解;面向对象程序设计以数据抽象为基础,综合了功能抽象和数据抽象,基于数据抽象进行模块分解 。
第 13章 C到 C++
13.2 改进的 C语言
13.2.1 C++程序
1,C++
//C++
# include ″iostream.h″
main( )
{double x,y;
cout<<″请输入两个数,″;
cin>>x>>y;
double z;
z=x+y;
cout<<″x+y=″<<z<<″\n″;
}
第 13章 C到 C++
C++程序的一般结构如下:
① 文件包含;
② 基类定义;
③ 派生类定义;
④ 成员函数定义;
⑤ 非成员函数原型说明;
⑥ 主函数;
⑦ 非成员函数。
第 13章 C到 C++
2,C++程序的实现
1) 编辑 C++
启动 Visual C++后,出现,Microsoft Developer
Studio”窗口,见图 13 - 1。 该窗口有 File,Edit,View、
Insert,Project,Build,Tools,Window及 Help 9 个菜单项 。
第 13章 C到 C++
图 13 - 1
第 13章 C到 C++
2) 编译连接和运行源程序选择菜单项 Build,出现 Build的下拉式菜单,在该下拉式菜单中选择,Compile * * * *”菜单项,这时系统开始对当前的源程序进行编译 。 在编译的过程中,将所发现的错误显示在屏幕下方的,Build”窗口中 。 所显示的出错信息指出该错误所在的行号和该错误的性质,程序员可根据这些信息采用全屏幕编辑方式修改程序 。 当用鼠标双击出错信息提示行时,该错误信息对应的行将加亮显示,或在该行前面用一个箭头加以指示 。 往往因为一个错误而出现多行出错信息,因此,常常在修改一条错误后,再重新编译,
如果有错误,再继续修改,直到没有错误为止 。
第 13章 C到 C++
在没有错误时,显示错误信息的窗口内将显示如下信息:
*****.obj-0 error( s),0 warning( s)
编译无错误后,再进行连接 。 这时选择,Build”菜单中的,Build *****.exe”选项 。 同样,对出现的错误要根据出错信息行中显示内容进行修改,直到编译连接无错误为止 。 这时,在,Build”窗口会显示如下信息:
*****.exe-0error( s),0 warning( s)
第 13章 C到 C++
13.2.2 常规改进
1.
asm catch class delete friend inline new
operator private protected public template this virtual
第 13章 C到 C++
2,注释即用,//”导引出单行注释 。 当然,C中原有的
/*和 */注释方法,仍可使用,并且常用于多行注释情况 。
第 13章 C到 C++
3,类型转换
C++支持两种不同的类型转换形式:
int i=0;
long l=( long) i; //C
long m=long( i) ; //C++
C++新风格的类型转换形式看上去像是一个函数调用,
所以可读性较好 。 而且,这种形式也适合于用户定义类型的转换 ( 用定义函数来实现 ) 。
第 13章 C到 C++
4,灵活的声明
5,const
C++中,类型限定符 const用来表示常量,所以,
C++中的常量可以是有类型的,程序员不必再用#
define创建无类型常量 。 例如:
const int size=100;
声明成 const 的变量,实际是常量,在程序中是用任何方法不可修改的 。
ANSI C从 C++中借用了 const 的概念,但实现方法有所不同。
第 13章 C到 C++
6,struct
C++的 struct后的标识符可看作是类型名,所以定义某个 struct变量比 C中更加直观 。 例如,在 C中:
struct point {int x; int y};
struct point p;
而在 C++中:
struct point {int x; int y};
point p;
union的情况也是如此 。
为了保持兼容性,C++仍然接受老用法 。 在后面会看到,
C++的类就是对 C中 struct的扩充 。
第 13章 C到 C++
7,作用域分辨运算符,∷,
,∷,是作用域分辨运算符,它用于访问在当前作用域中被隐藏的数据项 。 例如:
int a;
int main( )
{float a;
a=1.5; //访问当前作用域的 a
∷ a=2; //访问全局域的 a
……
}
第 13章 C到 C++
13.2.3 C++的动态内存分配
C程序中,动态内存分配是通过调用诸如 malloc
( ) 和 free( ) 等库函数来实现,而 C++ 给出了用
new和 delete运算符进行动态内存分配的新方法 。
第 13章 C到 C++
void func( )
{int*i=new int; //为指针 i
*i=10;
cout<<i;
delete i; //释放 i
}
用传统的 C程序实现是这样的:
void func( void)
{int *i;
i=malloc( sizeof( int)) ;
*i=10;
printf( ″%d″,*i) ;
free( i) ;
}
第 13章 C到 C++
13.2.4 引用引入引用机制后,可有如下 C++程序:
void swapint( int &a,int &b) //表示引用,类似于 pascal
{int temp=a;
a=b;
b=temp;
}
第 13章 C到 C++
调用该函数的 C++方法为:
swapint( x,y) ;
运算符,&”表示引用,可以把参数 a,b看作是调用实参的别名,C++自动把 x,y的地址作为参数传递给
swapint( ) 函数,形参与实参共享存储单元 。
当大的结构 ( 如用户定义类的对象 ) 被传递给函数时,
使用引用参数可使得参数传递效率得到提高 。 若不需改变参数的值,可用 const对参数说明加以限定,从而保护数据的安全性 。 例如:
void getdata( const int & data)
第 13章 C到 C++
13.2.5 C++中的函数
1,main( )
C并无特别规定 main( ) 函数的格式,因为通常并不关心返回何种状态给操作系统 。 然而,C++却要求 main( ) 函数匹配下面两种原型之一:
Void main( )
int main( int argc,char*argv[ ])
第 13章 C到 C++
2.
函数原型 ( prototyping) 的概念在前面章节已提及,
其实 ANSI C正是从 C++中借用了这一做法 。 函数原型实际上就是对函数的头格式进行说明,包含函数名,参数及返回值类型 。
传统 C中的函数说明只是定义函数的返回值的类型,
并不涉及参数,如:
int something( ) ;
而在 C++中的函数说明应是详细的头格式:
int something( char*str,unsigned int len);
第 13章 C到 C++
3,内联函数当函数定义是由 inline开头时,表明此函数为内联函数 。 编译后,它不是单独一段可调用的代码,而是被插入在对该函数的每一次调用处,从而完成与函数调用相同的功能 。
例如:
inline int sum( int a,int b)
{return a+b; }
这样函数调用无需栈,代码重用。
第 13章 C到 C++
4,缺省参数值
void delay( int loops=1000) ; //函数原型,给出缺省的参数值
void delay( int loops) //
{for( int i=0; i<loops; i++); }
函数原型中给出了 loops的缺省值,所以若调用 delay函数并没指定 loops的值时,程序自动将它当作 1000进行处理 。
delay( 4500) ; //loops值为 4500
delay( ) ; //loops值为 1000
一个 C++函数可以有多个缺省参数,并且 C++要求缺省参数必须连续地放在函数参数表的尾部 。 当调用具有多个缺省参数时,只能由左向右匹配,并且必须是连续的 。
第 13章 C到 C++
13.2.6 重载
1,
int abs( int i) ;
long labs( longl) ;
double fabs( double d) ;
使用 C++重载技术后,显然直观了:
int abs( int i) ;
long abs( longl) ;
double abs( double d) ;
重载函数的特点是名字相同,参数个数和类型,返回值类型不同。
第 13章 C到 C++
2,
typedef struct
{double r,i; } complex;
complex operator+( complex c1,complex c2)
{complex t;
t.r=c 1.r+c2.r;
t.i=c 1.i+c2.i;
return( t) ;
}
第 13章 C到 C++
用 operator后跟运算符号来定义重载运算符函数 。
重载运算符的使用,如同运算符原来的使用规则 。
例如:
complex x,y,sum;
sum=x+y ;
显然,通过重载,C++的语句更易于理解。
第 13章 C到 C++
13.3 C++的输入与输出
13.3.1 C++流类结构
1,iostream库
iostream库中具有 streambuf和 ios两个平行的类,这都是基本的类,分别完成不同的工作 。 streambuf类提供基本流操作,但不提供格式支持 。 类 ios为格式化 I/O提供基本操作 。
第 13章 C到 C++
2,标准流
iostream.h说明了标准流对象 cin,cout,cerr与 clog。
在包含 iostream.h以后,这些流对象就已经自动建立并打开了 。 cin是标准输入流,对应于 C的 stdin; cout是标准输出流,对应于 C的 stdout; cerr和 clog流被连到标准输出上对应于 C的 stderr。 cerr和 clog之间的区别是 cerr没有缓冲,发送给它的任何输出立即被执行,而 clog只有当缓冲区满时才有输出 。 缺省时,C++标准流被连到控制台上 。
第 13章 C到 C++
13.3.2 基本 I/O操作
1,输出 ——插入符,<<”
“<<”的左操作数为标准输出流对象,右操作数为待输出的某类型值 。 例如:
cout<<″Hello! \n″; //输出 Hello!,
这时,<<”为字串或 char*插入符 。 插入运算符返回所调用的 ostream对象的引用,由于为左结合,所以可以连写 。
第 13章 C到 C++
2,输入 ——提取符,>>”
“>>”的左操作数为标准输入流,右操作数为行输入量 。 它比 scanf( ) 函数更紧凑,且可读性更好,也不易出错 。 例如:
cin>>x;
从 cin输入值到 x。 注意它与 scanf( ) 函数不同,x前并没有地址运算符,与插入符类似,提取符,>>”也支持连写 。 程序员可以为自己定义的类 ( 型 ) 建立相应的插入和提取函数 。
第 13章 C到 C++
13.3.3 格式化 I/O
1,用 ios成员函数进行格式化在 iostream.h中,有如下有关格式化标志的枚举类型定义:
enum
{skipws=0x0001,//
left=0x0002,//
right=0x0004,//
internal=0x0008,//数据符号左对齐,
dec=0x0010,//转换基数为十进制形式第 13章 C到 C++
oct=0x0020,//
hex=0x0040,//
showbase=0x0080,//输出的数值数据全面带基数符号 ( 0或 0x)
showpoint=0x0100,//
uppercase=0x0200,//
showpos=0x0400,//正数全面带,+”
scientific=0x0800,//
fixed=0x1000,//
unitbuf=0x2000,//
stdio=0x4000 //完成操作后刷新 stdout,stderr
};
第 13章 C到 C++
格式标志存放于一个 long整数中,要设置它可用
ios的 setf( ) 函数,
long setf( long flags) ;
该函数设置参数 flags所指定的标志,返回格式更新前的标志 。 例如,要设置 showbase标志,可使用如下语句:
stream.setf( ios∷ showbase) ; //其中 stream是所涉及的流实际上,还可以一次调用 setf( ) 来同时设置多个标志 。 例如:
cout.setf( ios∷ showpos | ios∷ scientific) ;
第 13章 C到 C++
清除标志可用 unsetf( ) 函数,其原型与 setf( ) 类似 。
用 flags( ) 函数可得到当前标志值和设置新标志,
分别具有以下两种原型:
long flags( void) ;
long flags( long flags) ;
除了标志外,格式输出还可设置域宽,填充字符及输出精度 。 其原型分别为:
int width( int len) ;
char fill( char ch) ;
int precision( int num);
第 13章 C到 C++
请编译运行下面这段程序:
# include ″iostream.h″
Void main
{cout.setf( ios∷ showpos| ios∷ scientific) ;
cout<<123<<″″<<123.12<<″\n″;
cout.precision( 2) ;
cout.width( 10) ;
cout<<123<<″″<<123.12<<″\n″;
cout.fill( ′# ′) ;
cout.width( 10) ;
cout<<123<<″″<<123.12;
}
运行结果为:
+123+1.2312e+02
+123+1.23e+02
###### +123+1.23+02
第 13章 C到 C++
2,用操作子进行格式化流类库所定义操作子如下:
dec,hex,oct:数值数据采用十进制或十六进制,八进制表示 。
setbase( int n),设置数制转换基数为 n( 0,8,10,16,0表示使用缺省基数 ) 。
ws,提取空白符 。
ends,插入空字符 。
flush,刷新与流相关联的缓冲区 。
resetiosflags( long),清除参数所指定的标志位 。 setiosflags
( long),设置参数所指定的标志位 。
setfill( int),设置填充字符 。
setsprecision( int),设置浮点数输出的有效数字个数 。 setw
( int),设置输出数据项的域宽 。
第 13章 C到 C++
例如:
int i=1234;
cout<<setw( 12) <<i<<endl;
输出结果,1234
第 13章 C到 C++
13.4 类与对象
13.4.1 类的定义类的定义格式为:
class
{public:
private:
};
第 13章 C到 C++
其中,class是定义类的关键字 。 类名是一标识符,
通常用,T”字母开始的字符串作为类名,T用来表示类,以示与对象,函数名区别 。 花括号内是类的说明部分 ( 包括前面的类头 ),说明该类的成员 。
第 13章 C到 C++
例如,下面给出一个关于日期的类的定义 。 该类是对日期抽象,该类的对象将是某一个具体日期 。
日期类的说明部分:
class TDate
{public:
void SetDate( int y,int m,int d) ;
int IsLeap Year( ) ;
void Print( ) ;
private:
int year,month,day;
};
第 13章 C到 C++
类名为 TDate。 有三个公有成员,都是成员函数 。
SetDate( ) 函数是设置日期的,用它来使对象获取值;
IsLeapYear( ) 函数是一个用来判断是否是闰年的函数:
返回值为 1表示该年是闰年,返回值为 0表示该年不是闰年; print( ) 函数用来将年,月,日的具体值输出显示 。 关于这三个函数的功能通过下面的实现部分可以看出 。 还有三个私有成员,它们是 int型变量 year,month
和 day。 该类共有六个成员 。
第 13章 C到 C++
日期类的实现部分:
void TDate∷ SetDate( int y,int m,int d)
{year=y; month=m; day=d; }
int TDate∷ IsLeapYear( )
{return ( year%4==0&&year%100 ! =0 ) ||
( year%400==0) ; }
void TDate ∷ Print( )
{cout<<year<<″.″<<month<<day<<endl; }
第 13章 C到 C++
关于日期类还可如下定义:
class TDate
{public:
void SetDate( int y,int m,int d)
{year=y; month=m; day=d; }
int IsLeapYear( )
{return( year%4==0&&year%100! =0) ||(year%400==0) ; }
void Print( )
{cout<<year<<″.″<<month<<″.″<<day<<endl; }
private:
int year,month,day;
};
第 13章 C到 C++
说明:
(1) 在类体中不允许对所定义的数据成员进行初始化 。
例如,对日期类 TDate类中,下面的定义是错误的:
class TDate
{public:
……
private:
int year( 2000),month( 2),day( 18) ;
};
这里,不该对数据成员 year,month和 day进行初始化。
第 13章 C到 C++
(2) 类中的数据成员的类型可以是任意的,包含整型,
浮点型,字符型,数组,指针和引用等 。 也可以是对象,
另一类的对象,但是自身类的对象是不可以的,而自身类的指针或引用又是可以的 。 当一个类的对象作为这个类的成员时,如果另一个类的定义在后,需要提前说明 。
(3) 一般地,在类体内先说明公有成员,它们是用户所关心的;后说明私有成员,它们是用户不感兴趣的 。
在说明数据成员时,一般按数据成员的类型大小,由小至大说明,这样可提高时空利用率 。
第 13章 C到 C++
(4) 经常习惯地将类定义的说明部分或者整个定义部分 ( 包含实现部分 ) 放到一个头文件中 。 后面引用起来比较方便 。
(5) 由于抽象数据类型的要求,C++控制了对类成员的存取,
对类的私有数据成员的访问只能通过其公有的成员函数进行,
因此会造成运行效率的降低 。 但没有关系,在不得已的情况下,C++也可以不遵循面向对象的原则,去直接访问类的私有成员,这就是友元 ( friend) 所能做到的 。
一个类可以允许其它类或函数访问其私有数据,具有这种权力的其它类或函数应当显式地在该类中定义为 friend( 友元 ) 。 友元打破了封装 。
第 13章 C到 C++
13.4.2 对象的定义对象是类的实例,对象属于某个已知的类 。 因此,
定义对象之前,一定要先定义好该对象的类 。
对象在确定了它的类以后,其定义格式如下:
类名 对象名表;
第 13章 C到 C++
类名是待定的对象所属的类的名字,即所定义的对象是该类类型的对象 。 对象名表是用逗号分隔的对象名 。
对象名表中,可以是一般的对象名,还可以是指向对象的指针名或引用名,也可以是对象数组名 。 例如:
TDate date1,date2,*Pdate,date[ 31] ;
其中,TDate为日期类的类名,date1和 date2是一般的对象名,*Pdate是指向对象的指针,date是对象数组的数组名,
它有 31个元素,每个元素都是一个对象 。 这里所说的对象都是 TDate类的对象 。
第 13章 C到 C++
一个对象的成员就是该对象的类所定义的成员 。 对象成员有数据成员和成员函数,一般对象的成员表示如下:
对象名,成员名 或者 对象名,成员名 ( 参数表 )
前者用来表示数据成员,后者用来表示成员函数 。 例如,date1的成员可表示为:
date1.year,date1.month,date1.day
分别表示 TDate类的 date1对象的 year成员,month成员和 day成员 。
date1.SetDate( int y,int m,int d)
表示 TDate类的 date1对象的成员函数 SeaDate( ) 。
这里,,.”是一个运算符,该运算符的功能是得到对象的成员。
第 13章 C到 C++
13.4.3 构造函数和析构函数构造函数和析构函数是在类体中说明的两种特殊的成员函数 。
构造函数的功能是在创建对象时,使用给定的值来将对象初始化 。
析构函数的功能是用来释放一个对象的 。 在对象删除前,用它来做一些清理工作,它与构造函数的功能正好相反 。
第 13章 C到 C++
下面将重新定义前面讲过的日期类:
class TDate1
{public:
TDate1( int y,int m,int d) ;
~ TDate1( ) ;
void Print( ) ;
private:
int year,month,day;
};
第 13章 C到 C++
TDate1 ∷ TDate1( int y,int m,intd)
{year=y; month=m; day=d;
cout<<″Constructor called.\n″;
}
TDate1 ∷ ~ TDate1( )
{cout<<″Destructor called.\n″; }
void TDate1 ∷ Print( )
{cout<<year<<″.″<<month<<″.″<<day<<endl; }
第 13章 C到 C++
构造函数的特点如下:
(1) 构造函数是成员函数,函数体可写在类体内,也可写在类体外 。
(2) 构造函数是一个特殊的函数,该函数的名字与类名相同,该函数不指定类型说明,它有隐含的返回值,
该值由系统内部使用 。 该函数可以有一个参数,也可以有多个参数 。
(3) 构造函数可以重载,即可以定义多个参数个数不同的函数 。
(4) 程序中不能直接调用构造函数,在创建对象时系统自动调用构造函数。
第 13章 C到 C++
析构函数的特点如下:
(1) 析构函数是一个特殊的成员函数,它的名字同类名,并在前面加,~,字符,用来与构造函数加以区别 。 析构函数不指定数据类型,并且也没有参数 。
(2) 一个类中只能定义一个析构函数,析构函数不能重载。
第 13章 C到 C++
(3) 析构函数可以被调用,也可以由系统调用 。 在下面两种情况下,析构函数会被自动调用 。
一是如果一个对象被定义在一个函数体内,则当这个函数结束时,该对象的析构函数被自动调用;二是当一个对象是使用 new运算符被动创建的,在使用 delete运算符释放它时,delete将会自动调用析构函数 。
若在类定义时没有定义任何构造函数,则编译器自动生成一个不带参数的缺省构造函数,其格式如下类名 ∷ 缺省构造函数名 ( )
{
}
第 13章 C到 C++
按构造函数的规定,缺省构造函数名同类名 。 缺省构造函数的这样格式也可以由程序员定义在类体中 。 在程序中定义一个对象而没有指明初始化,则编译器便按缺省构造函数来初始化该对象,对象的所有数据成员都初始化为零或空 。
同理,如果一个类中没有定义析构函数时,则编译系统也生成一个缺省析构函数,其格式如下:
类名 ∷ ~缺省析构函数名 ( )
{
}
缺省析构函数名也同类名,缺省析构函数是一个空函数。
第 13章 C到 C++
13.4.4 继承性
building类的说明如下所示,它用作两个派生类的基类:
class building
{int rooms;
int floors;
int area;
public:
void set -rooms( int num) ;
int get -rooms( ) ;
void set -floors( int num) ;
int get -floors( ) ;
void set -area( int num) ;
int get -area( ) ;
};
第 13章 C到 C++
例如,下面是名为 house的派生类,注意 building是如何被继承的 。
//house是基类 building
class house,public building
{int bedrooms;
int baths;
public:
void set -bedrooms( int num) ;
int get -bedrooms( ) ;
void set -baths( int num) ;
int get -baths( ) ;
};
第 13章 C到 C++
继承的一般形式是:
class 新类名,[ access]
{
//
}
其中,access是可选的,如果出现,它必然是 public、
protected或 private。 若缺省,则认为是私有派生 private。
使用 public意味着基类的所有公有元素在继承它的派生类中也是公有的,保护成员能被继承 。
第 13章 C到 C++
例 13—1 继承性实例
//程序 13—1,
# include″iostream.h″
class building //说明一个基类 building
{int rooms; int floors; int area;
public:
void set -rooms( int num) ;
int get -rooms( ) ;
void set -floors( int num) ;
int get -floors( ) ;
void set -area( int num) ;
int get -area( ) ;
};
第 13章 C到 C++
class house,public building //说明一个由基类 building
派生出的派生类 house
{int bedrooms; int baths;
public:
void set -bedrooms( int num);
int get -bedrooms( );
void set -baths( int num);
int get -baths( );
};
class school,public buiding //说明一个由基类 building
派生出的派生类 school
{int classrooms; int offices;
第 13章 C到 C++
public:
void set -classrooms( int num);
int get -classrooms( );
void set -offices( int num);
int get -offices( );
};
void building∷ set -rooms( int num)
{rooms=num; }
void building ∷ set -floors( int num)
{floors=num; }
void building ∷ set -area( int num)
{area=num; }
int building ∷ get -rooms( )
{return rooms; }
int building ∷ get -floors( )
{return floors; }
第 13章 C到 C++
int building ∷ get -area( )
{return area; }
void house ∷ set -bedrooms( int num)
{bedrooms=num; }
void house ∷ set -baths( int num)
{baths=num; }
int house ∷ get -bedrooms( )
{return bedrooms; }
int house ∷ get -baths( )
{return baths; }
void school ∷ set -classrooms( int num)
{classrooms=num; }
void school ∷ set -offices( int num)
{offices=num; }
第 13章 C到 C++
int school ∷ get -classrooms( )
{return classrooms; }
int school ∷ get -offices( )
{return offices; }
void main(
{house h; school s; //定义派生类 house和 school下的对象 h和 s
h.set-rooms( 6); h.set-floor( 8); h.set-area( 4500); //对对象 h进行操作
h.set-bedrooms( 5); h.set-baths( 3);
cout<<″这所房屋有 ″<<h.get-bedrooms( ); //
cout<<″间卧室! \n″;
第 13章 C到 C++
s.set-rooms( 200); s.set -classrooms( 180); //对对象 s进
s.set-offices( 5); s.set -area( 25000);
cout<<″学校有 ″<<s.get -classrooms( ); //将对象的教室
coutn<<″间教室! \n″;
cout<<″学校的建筑面积是 ″<<s.get -area( ); //将对象的建筑面积输出
}
第 13章 C到 C++
13.4.5 运行时的多态性
1,指向派生类的指针指向基类指针和指向派生类指针是有联系的 。 若有基类 Bclass和 Bclass派生的 Dclass,则任何指向 Bclass对象的指针也可用于指向 Dclass对象 。 例如:
Bclass *p; //指向 Bclass
Bclass b; //Bclass
Dclass d; //由 Bclass派生的 Dclass
p=&b; //p指向 Bclass对象 b
P=&d; //p指向 Dclass对象 d
第 13章 C到 C++
2,虚函数虚函数是一个在基类中被说明为 virtual,并在一个或多个派生类中被重新定义的函数 。
虚函数有这样的特性:当用指向基类的指针访问一个虚函数时,C++要根据该基类指针在运行时实际指向的对象类型 ( 有可能指向某个派生类对象 ) 来确定调用哪一个函数 。 所以,若指向不同的派生类对象,则执行该虚函数的不同版本 。 这就是所谓通过派生类和虚函数来实现的运行时的多态性 。 这种在运行时才确定调用哪个函数的性质常被称为动态链接 ( dynamic binding) 。
第 13章 C到 C++
实现运行时的多态性应注意的关键几点:
(1) 要用指向基类的指针 。
(2) 虚函数要求函数原型相同,否则视为重载 。
(3) 虚函数必须是定义类的成员,而不是友元 。
(4) 析构函数允许是虚函数,但构造函数不可以。
第 13章 C到 C++
3,纯虚函数及抽象类纯虚函数是在某个基类中说明的虚函数,并且它在该基类中无定义,即真正为虚拟的函数 。 但该基类的任何派生类都可定义该虚函数的具体实现,从而实现运行时的多态性 。
一个基类若含有纯虚函数作为其成员,则该基类为抽象类 。 抽象类的一个重要性质是不可定义抽象类的对象,它的作用就是为派生类定义公共的成员或虚函数 。
这不仅减少了代码的重复,而且又可实现运行时的多态性 。
第 13章 C到 C++
13.5 程序设计举例
//程序 13—2
# include″iostream.h″
# define SIZE 100
class stack //说明一个基类 stack
{int stck[ SIZE] ;
int tos;
例 13—2
堆栈是一种先进后出的数据结构,堆栈的操作包括进栈 ( 压入 ) 与出栈 ( 弹出 ) 。
第 13章 C到 C++
public:
stack( ); //
~ stack( ); //
void push( int i); //
int pop( ); //
};
stack ∷ stack( ) //定义 stack下的构造函数 stack(
{tos=0; cout<<″堆栈初始化! \n″; }
stack ∷ ~ stack( ) //定义 stack下的析构函数~ stack(
{cout<<″堆栈释放! \n″; }
void stack ∷ push( int i) //定义 stack下的函数 push(
{if( tos==SIZE
{cout<<″堆栈已满! \n″; return; }
stck[ tos] =i; tos++;
}
第 13章 C到 C++
int stack ∷ pop( ) //定义 stack下的函数 pop( )
{if( tos==0
{cout<<″堆栈已空! \n″; return 0; }
tos--;
return stack[ tos];
}
void main(
{stack a,b; //定义 stack的对象 a和 b
a.push( 1);
b.push( 2);
a.push( 3);
b.push( 4);
cout<<a.pop( ) <<″ ″;
cout<<a.pop( ) <<″ ″;
cout<<b.pop( ) <<″ ″;
cout<<b.pop( ) <<″\n″;
return 0;
}
第 13章 C到 C++
运行结果:
3 1 4 2
堆栈释放!
第 13章 C到 C++
例 13—3 计算几种几何图形的面积之和 。
(1) 三角形 ( Triangle),
(2) 矩形 ( Rectangle),面积 S =H*W
(3) 正方形 ( Square),面积 S=S*S
(4) 圆 ( Circle),面积 S= 3.1415*R*R
(5) 梯形 ( Trapezoid),面积 S=
程序如下:
WH 21
HBT )(21
第 13章 C到 C++
//程序 13—3,
class Shape
{public:
virtual double Area( ) const=0;
};
class Triangle,public Shape
{public:
Triangle( double h,double w) {H=h; W=w; }
double Area( ) const {return H*W*0.5; }
private:
double H,W;
};
第 13章 C到 C++
class Rectangle,public Shape
{public:
Rectangle( double h,double w) {H=h; W=w; }
double Area( ) const {return H*W; }
private:
double H,W;
};
class Circle,public Shape
{public:
Circle( double r) {radius=r; }
double Area( ) const {return radius*radius*3.1415; }
private:
double radius;
};
第 13章 C到 C++
class Trapezoid,public Shape
{public:
Trapezoid( double top,double bottom,double high
{T=top; B=bottom; H=high; }
double Area( ) const {return ( T+B) *H*0.5; }
private:
double T,B,H;
};
class Square,public Shape
{public:
Square( double side) {S=side; }
double Area( ) const {return S*S; }
private:
double S;
};
第 13章 C到 C++
class Application
{public:
double Compute( Shape *s[ ],int n) const;
};
double Application∷ Compute( Shape *s[ ],int n) const
{double sum=0;
for( int i=0; i<n; i++
sum+=s[ i] ->Area( );
return sum;
}
class MyProgram,public Application
{public:
MyProgram( );
~ MyProgram( );
double Run( );
第 13章 C到 C++
private:
Shape * *s;
};
MyProgram∷ MyProgram(
{s=new Shape*[ 5];
s[ 0] =new Triangle( 3.0,4.0);
s[ 1] =new Rectangle( 6.0,8.0);
s[ 2] =new Circle( 6.5);
s[ 3] =new Trapezoid( 10.0,8.0,5.0);
s[ 4] =new Square( 6.7);
}
MyProgram∷ ~ MyProgram(
{for( int i=0; i<5; i++
delete s[ i];
}
第 13章 C到 C++
double MyProgram∷ Run( )
{double sum=Compute( s,5) ;
return sum;
}
void main( )
{cout<<″面积之和 =″<< MyProgram( ),Run( ) <<endl; } 输出结果,面积之和 =276.618
第 13章 C到 C++
说明:
(1) 类 Shape是一个抽象类,它相当于 Triangle等 5
个类的根 。 Shape类中的虚函数 Area( ) 在它 5个派生类中都有着不同的实现 。
(2) 这种结构对于增加不同的图形十分方便 。 例如,
再加一个平行四边形,只要增加一个 Shape类的派生类即可,在该派生类中给出求面积公式,并且在求和的数组中再增加一个元素 。