面向对象的程序设计语言C课件468页

PPT
  • 阅读 33 次
  • 下载 0 次
  • 页数 468 页
  • 大小 3.568 MB
  • 2022-11-24 上传
  • 收藏
  • 违规举报
  • © 版权认领
下载文档50.00 元 加入VIP免费下载
此文档由【小橙橙】提供上传,收益归文档提供者,本网站只提供存储服务。若此文档侵犯了您的版权,欢迎进行违规举报版权认领
面向对象的程序设计语言C课件468页
可在后台配置第一页与第二页中间广告代码
面向对象的程序设计语言C课件468页
可在后台配置第二页与第三页中间广告代码
面向对象的程序设计语言C课件468页
可在后台配置第三页与第四页中间广告代码
面向对象的程序设计语言C课件468页
面向对象的程序设计语言C课件468页
还剩10页未读,继续阅读
【这是免费文档,您可以免费阅读】
/ 468
  • 收藏
  • 违规举报
  • © 版权认领
下载文档50.00 元 加入VIP免费下载
文本内容

【文档说明】面向对象的程序设计语言C课件468页.ppt,共(468)页,3.568 MB,由小橙橙上传

转载请保留链接:https://www.ichengzhen.cn/view-44684.html

以下为本文档部分文字说明:

面向对象的程序设计语言C++面向对象程序设计语言前言第1节C++语言概述第2节数据类型和表达式第3节预处理和语句第4节函数和作用域第5节类和对象(一)第6节类和对象(二)第7节继承性和派生类第8节多态性和虚函数第9节C++的I/O流库面向对象的程序

设计语言1、面向对象的程序设计语言必须支持下列概念:封装的对象类和实例的概念类间的继承多态性2、面向对象程序设计语言的基本特征:对象、类、继承性、信息隐藏、强类型化、并发性、持久性。1.1C语言与面向对象的C++(1)C语言既具备高级语言的结构和编程环境,又提供类似于汇编语言那样的系统

资源操纵能力及程序执行效率。适合解决有实时要求的问题。C语言的主要特点:(2)有丰富的运算符和数据类型,表达式类型多样化,可以方便地实现在其他语言中较难实现的运算,对各种不同类型的程序设计都有良好的适应性。(3)以函

数为基础实现程序的结构化设计,支持大型程序的多文件构成及单个文件独立编译,适合大型复杂程序的设计。(4)语言简洁、紧凑,使用方便、灵活,书写形式自由。(5)可移植性好。1.1C语言与面向对象的C++C++是由C发展成为的以面向对象为主要特征的语言。作为C语言的超集,C++

继承了C的所有优点,又对数据类型做了扩充,使得编译系统可以检查出更多类型错误。C++支持面向对象程序设计,通过类和对象的概念把数据和对数据的操作封装在一起,通过派生、继承、重载和多态性等特征实现了软件重用和程序自动生成,使得

大型复杂软件的构造和维护变得更加有效和容易。1.2C++对面向对象程序设计方法的支持C++支持数据封装(数据抽象)C++中,类是支持数据封装的工具,对象则是数据封装的实现;C++中包含有私有、公有和保护成员每个可能的消息对应一个相应的方法,方法

通过函数来定义;C++中通过发送消息来处理对象C++中允许友元破坏封装性C++中允许函数名和运算符重载C++支持继承性C++支持动态联编1.2.2C++对C语言的改进增加了新的运算符:::,new,delete等;改进了类型系统,增加了安全性;引进了引用概念;允许函数重载,允许设

置缺省参数,提高了编程的灵活性;引进了内联函数,提高了程序的效率;可以根据需要随时对变量进行说明;1.3C++程序的编辑、编译和运行一、编辑:源文件的扩展名为.cpp二、编译1、预处理过程词法分析:单词语法

分析:构造程序的格式符号表:程序中的各种符号及其属性错误处理程序:生成目标代码:目标文件扩展名.obj2、编译过程3、连接过程:可执行文件扩展名.exe三、运行1.3C++程序的编辑、编译和运行(续)源程序.cpp目标文件.obj可执行文件.exe编译器连接器磁盘中的#in

clude文件C++库文件.LIB1.4.1C++的字符集大小写的英文字母:a~z,A~Z数字字符:0~9特殊字符空格!#%^&*_+=-~<>/\,“;.,()[]1.4.2词法记号1、关键字(保留字)表1-1C++的关键字autoboolbreakcasecatchchar

classconstconst_castcontinuedefaultdeletedodoubledynamic_castelseenumexplicitexternfalsefloatforfriendgotoifinlineintlongmutablenewope

ratorprivateprotectedpublicregisterreinterpret_castreturnshortsignedsizeofstaticstatic_caststructswitchtemplatethisthr

owtruetrytypedeftypeidtypenameunionunsignedvirtualvoidvolatilewhile1.4.2词法记号(续)2、标识符组成规则:以字母或下划线开始,其后跟零个或多个字母、数字或下划线;不能以数字开始正确标识符:Resul

t,DoubleList,_first,first_错误标识符:1first标识符的长度任意(受编译器限制);区分字母的大小写;不能使用系统的保留字;例如:ADD,Add,add1.4.2词法记号(续

)3、运算符单目双目三目运算符5、分隔符(){},分隔符:4、各种文字数字字符文字串文字文字布尔文字;1.4.3空白一、空白包括:空格、制表符、换行符、注释功能:指示词法记号的开始和结束位置;二、注释/*……*///1.5C++程序的结构一、C++示范程序#include<iostre

am.h>voidmain(){cout<<"Helloworld!"<<endl;}二、C++程序的组成预处理命令输入输出函数语句变量其他I/O流库,提供所有的输入输出操作cout:流类对象<<:插入符提供屏幕输出;提供键盘输入:cin:流类对象>>:提取符例如:cout<

<"Pleaseinputwointegers:";cin>>a>>b;endl:换行;1.6vc++程序设计环境1.VC++可视化集成开发环境(1)建立控制台程序的基本步骤建立一个程序,运行后输出“ThisisaC++program.

”,源程序如下:#include<iostream.h>voidmain(){cout<<"ThisisaC++program"<<endl;}创建新工程创建C++源程序文件编译连接和运行源程序关闭、打开工作区2.VC++环境下的程序开发

实例输出结果(2)基于MFC的程序开发的基本步骤这里以设计一个如下图所示的对话框窗口程序建立新的工程文件选择可视化控件拖到对话框窗口进入编辑屏幕后,首先规划对话框窗口的布局,确定对话框窗口的界面。在控件面板中选择相应的控件后拖放到对话框窗口的

适当位置设置控件的属性在属性对话框中设置控件的属性,如“StaticText”、“Button”控件的“Caption”属性为新生成的类定义公有数据成员和方法为新的对话框类添加方法即为对话框中的某些控件添加事件代码。比如本例希望单击“Button1”按钮后

,能将IDC_Edit1中的文本复制到IDC_Edit2中来,就需要为“Button1”按钮添加“单击”事件代码,下面是该事件的实现代码:voidCExampleDlg::OnButton1(){//TODO:AddyourcontrolnotificationhandlercodehereC

StringstrExpression;m_edit1.GetWindowText(strExpression);m_edit2.SetWindowText(strExpression);}最后Compile或Build程序,得到可执行的应用程序第2节数据类型和表达式2.1基本

数据类型2.2常量和变量2.3数组类型2.4枚举类型2.5指针和引用2.6运算符2.7表达式2.8类型定义2.1基本数据类型一、基本数据类型整型int浮点型(实型)字符型char基本数据类型逻辑型bool空值型void单精度浮点数float双精度浮点数d

ouble用于函数和指针2.1基本数据类型(续)二、数据类型修饰符signed:有符号unsigned:无符号short:短型long:长型说明:3)long修饰符还适用于双精度浮点数;2)上述修饰符均可用于整型和字符型;1)类型修饰符可以修饰除void、bool类型以外的其

他类型;2.1基本数据类型(续)三、基本数据类型列表表2-1C++的基本数据类型类型名字宽(字节)范围boolfalse,truechar1-128~127signedchar1-128~127unsignedchar10~255short[int

]2-32768~32767signedshort[int]2-32768~32767unsignedshort[int]20~65535int4-2147483648~2147483647signed[int]4-2147483648~21474836

47unsigned[int]40~4294967295long[int]4-2147483648~2147483647signedlong[int]4-2147483648~2147483647unsignedlong[int]40~429496

7295float43.4E10-38~3.4E1038double81.7E10-308~1.7E10308longdouble103.4E10-4932~3.4E1049322.1基本

数据类型(续)说明:1)表中的[int]可以省略,即在int之前有修饰符出现时,可以省略关键字int;2)单精度类型float、双精度类型double、长精度类型longdouble统称浮点类型;3)char类型和各种int类型统称整型类型;char类型变量

在内存中以它的ASCII码值的形式存储;4)字宽(字节)取决于操作系统和编译器的实现,可用sizeof验证;2.2.1常量一、整型常量(无小数部分)1、表示方法十进制八进制十六进制2、十进制表示由0~9的数字组成不能以0开始无前缀例:132,-3453、八进制表示由0~7的数字组成以0为前缀例:

010,-05364、十六进制表示由0~9的数字及A~F的字母(大小写均可)组成以0x或0X为前缀例:0x7A,-0X3de2.2.1常量(续)5、说明:1)长整型用L(或l)做后缀表示。例如:32765L,79

3l;2)无符号型用U(或u)做后缀表示。例如:4352U,3100u;3)unsignedlong型用后缀U(或u)和L(或l)一起表示,L与U的先后顺序无关。例如:49321ul,37825LU,41152Lu;4)无后缀时,整型常量类型按如下顺序确定:int,(unsigned),

long,unsignedlong十进制时无2.2.1常量(续)二、浮点型常量由整数部分和小数部分构成;只有十进制表示;一般表示形式(小数表示形式):整数部分与小数部分可以省去一部分,但不能全部省去;例如:5.,.25,4.072.2.1常量(续)科学表示形式:在小数表示法后面加E(或e)表示

指数;例如:23.5E6,.032E-5,.3e10指数部分可正可负,但必须为整数;浮点常量的缺省数据类型为double型;后缀F(或f)表示float类型;后缀l(或l)表示longdouble类型

;2.2.1常量(续)三、字符常量由一对单引号括起的一个字符表示;其值为所括起字符在ASCII表中的编码;所括起字符的表示方法:图形表示法该方法适用于有图形符号的可打印字符;例如:'A','a','*'转义序列表示法该方法适用于所有字符,尤其是无图

形符号的不可打印字符;2.2.1常量(续)转义序列表示方法:以反斜线(\)开头,后跟字符的ASCII码值;八进制表示:\ddd;例如:\101十六进制表示:\xhh;例如:\x41表2-2C++中常用转

义序列符符号含义\a响铃\n换行符\r回车符\t水平制表符(tab键)\b退格符(backspace键)\\反斜线\’单引号\’’双引号\0空字符2.2.1常量(续)四、布尔常量有两个值:true和false;五、字符串常

量(串常量,字符串)一对双引号括起的字符序列,字符序列可以包含空格、转义序列或任何其他字符,这些字符不一定是C++字符集中的字符,只要C++编译器支持即可;例如:"Thisisastring;"串常量与字符常量的区别:2.2.1常量(续)由一个字符型变量存放由一维数组存放字符

常量串常量用单引号括起用双引号括起字符串有一个结束符,该结束符用'\0'表示字符常量'a'在内存中占用一个字节字符串常量"a"在内存中占用两个字节可进行加、减法运算可进行连接、拷贝运算2.2.1常量(续)六

、符号常量用来表示C++中的常量,即用一个与常量相关的标识符来替代常量;优点:增加可读性,增强可维护性;例如:PI表示3.1415926定义方法:使用类型说明符const;例如:constintsize=80;定义的符号常量必须初始化;一个符号常量可看作是一个只读变量,由const定义的常量的

值不可以改变;Line1:#include<iostream.h>Line2:constdoublepi=3.1415;Line3:constdoubler;Line4:voidmain()Line5:{Line6:doubleperimeter,area;Line7:perime

ter=2*pi*r;Line8:pi=3.14;Line9:area=pi*r*r;Line10:cout<<perimeter<<","<<area<<endl;Line11:}2.2.1常量(续)constdoubler=3.2;错误

错误,不能修改pi的值地址值2.2.2变量一、变量的三个基本要素名字类型值三要素数据值地址值intc;c=5;内存5......c地址值1000H变量类型变量名数据值二、变量的定义可以在程序中随时定义变量,只要在

该变量被使用前定义即可;定义格式:<类型><变量名表>;例如:inta,b,c;doublex,y,z;2.2.2变量(续)同一程序块内不可以定义同名变量;初始值变量定义时可赋初始值;声明格式:数据类型标识符1(初始值1),…

,标识符n(初始值n);数据类型标识符1=初始值1,…,标识符n=初始值n;例如:doubleprice=15.5;intsize=100;未被初始化的变量的值或者是默认值,或者是无效值,由变量类型决定;变量可被赋值,由变量名标识;2.3数组类型数目固定、类型相同的若干个变量

的有序集合;2.3.1数组的定义1、格式<类型><数组名>[<大小1>][<大小2>]…;说明:方括号([])表示数组的维;某维的大小必须是大于1的常量表达式;2、示例inta[3];charb[3][5];constintsize=80;intm[

size];2.3.2数组的赋值1、数组元素的表示下标表示:<数组名>[<下标表达式1>][<下标表达式2>]…;说明:<下标表达式>为常量表达式;下标从0开始;各个元素在内存中按其下标的升序顺序连续存放;指针表示:2.3

.2数组的赋值(续)2、数组元素赋初值利用初始值表(由一对花括号括起来的若干数据项组成)实现;数组元素的个数要大于等于初始值表中数据项的个数;例如:inta[5]={1,2,3,4,5};inta[4]={5,4};intb[2][3]={{1,2,3},{4,5,6}};intb[2

][3]={1,2,3,4,5,6};3、数组元素的赋值例如:intm[3];m[0]=5;m[1]=3;m[2]=1;2.3.3字符数组说明:字符数组是指数组元素是char类型的数组;注意字符常量、字符数组与字符串常量的区别;例如:chars1

[4]={'a','b','c','d'};字符数组chars2[5]={'a','b','c','d','\0'};字符数组(字符串常量)等价于chars2[5]="abcd";chars3[5]="abcde";╳chars3[]="abcde";2.4

枚举类型枚举类型是若干个有名字的整型常量的集合;2.4.1枚举声明和枚举变量一、枚举声明enum<枚举名>{<枚举表>};<枚举表>由若干个枚举符组成,多个枚举符之间用逗号分隔;枚举符是用标识符表示的整型常量,又称枚举常量;枚举常量的值默认为最前边的一个为0,其后的值依次加1;枚举常量的值也

可显式定义,未显式定义的则在前一个值的基础上加1;2.4.1枚举声明和枚举变量(续)二、枚举变量例如:enumday{Sun,Mon,Tue,Wed,Thu,Fri,Sat};enumday{Sun=7,Mon=1,Tue,Wed,Thu,Fri,Sat};枚举变量的值不一定互不相同;enum

<枚举名><枚举变量名表>;多个枚举变量之间用逗号分隔;例如:enumdayd1,d2,d3;enumday{Sun,Mon,Tue,Wed,Thu,Fri,Sat}d1,d2,d3;2.4.2枚举变量的值其值是该枚举变量所属的枚举声明的枚举表的某一个枚

举符;利用枚举符所表示的整型值给枚举变量赋值时,需要进行类型强制;例如:d1=Sun;d2=Sat;d3=(enumday)4;2.5.1指针1、什么是指针指针是用来存放某个变量的地址值的一种变量;指针的类型是它所指向变量的类型;指针本身数据

值的类型是unsignedlongint型;例如:inta(5);int*p=&a;内存1000H5...a地址值1000H3000Hp2.5.1指针(续)2、如何定义指针例如:int*pi;char(*pa)[3];float*pl;int(*pf)();char*pc;int*pp;<类型>*

<指针名1>,*<指针名2>,…;3、指针的赋值(内存地址值)指针必须被赋值后才可使用;一般变量、数组元素、结构成员的地址值为变量名前加运算符&;数组的地址值用该数组名表示;2.5.1指针(续)函数的地址值

用该函数的名字表示;例如:inta,b[10];doublesin(doublex);int*p=&a,*p=&b[3];double(*pf)();inta[10],*p=a;pf=sin;3、指针的运算赋值运算。例如:inta,*p=&a,*q;q=p;一

个指针可以加上或减去一个整数值;a与&a[0]相同2.5.2指针和数组1、C++中指针与数组的关系C++通过指针访问数组中的每个元素;2、一维数组的指针表示法C++中规定:任何一个数组的名字是一个常量指针,其值是该数组的首元素的地址值;例如:inta[5];数组表

示法:a[i],i=0,1,2,3,4指针表示法:*(a+i)2.5.2指针和数组(续)3、二维数组的指针表示法intb[2][5];数组表示法:b[i][j]i=0,1;j=0,1,2,3,4指针表示法:*((b+i)+j)*(b[i]+j)(*(b+i))[j](&b[0][0]+5

*i+j)4、三维数组的指针表示法(同二维)5、示例2.5.2指针和数组(续)例2.1:分析下列程序的输出结果。#include<iostream.h>voidmain(){staticinta[5]={5,4,3,2,1};

inti,j;i=a[0]+a[4];j=*(a+2)+*(a+4);cout<<i<<endl<<j<<endl;}输出644a[1]5a[0]3a[2]2a[3]1a[4]aa+2a+42.5.3引用标识对象的一种机制;对对象存储地址的抽象,但引用不是值;引用有

类型;变量的别名;1、定义格式<类型>&<引用名>(<变量名>);或<类型>&<引用名>=<变量名>;2、初始化与赋值定义引用时必须初始化;可以将一个引用赋给某个变量;引用可被赋值;2.5.3引用(续)示例:inta=3;int&m=a;intn=m;int*p=&m;m=

m+5;3a8pm3n定义引用并初始化将引用赋值给变量a=8,对引用的操作就是对被引用者的操作3、引用的功能用做函数的参数或函数的返回值;函数不能返回对局部对象的引用;2.5.3引用(续)示例:int&f(intindex,inta[]){intr

=a[index];returnr;}错,r是局部对象4、指针与引用的区别指针通过地址间接访问某个变量,引用通过别名直接访问某个变量;引用必须初始化,一旦被初始化后不得再作为其他变量的别名;正确:int&r=a[index];为什么?2.5.3引用(续)例2.4

:分析下列程序的输出结果。#include<iostream.h>voidmain(){intval(5);int&refv=val;refv=refv+5;;cout<<refv<<endl;int*p=&refv,val1(refv);cout<<*p<<"\t"<<val1<<endl

;}输出1010102.6.1算术运算符1、普通算术运算符单目算术运算符:-(取负)、+(取正);双目算术运算符:+、-、*、/、%(只用于int型);单目运算符优先级高于双目运算符;2、增1和减1运算符单目运算符:++、--;++运算符的功能:由该运算

符组成的表达式具有一定的值;由该运算符组成的表达式计算后,其变量值要发生改变;2.6.1算术运算符(续)++运算符的两种方式:前缀方式与后缀方式;前缀方式与后缀方式的区别:前缀运算表达式的值为原来变量值加1;后缀运算表达式的值为原来变量

值;两种方式的变量的值都加1;示例:inta(1);++a;intb(1);b++;表达式++a的值为2,变量a的值为2;表达式b++的值为1,变量b的值为2;2.6.2关系运算符双目运算符:>、<、>=、<=、==、!=前四种优先级高于后两种;

2.6.3逻辑运算符双目运算符:&&、||优先级:&&、||、!单目运算符:!2.6.4位操作运算符1、逻辑位运算符单目逻辑位运算符:~双目逻辑位运算符:&、|、^双目逻辑位运算符的优先级:&、|、^;2、

移位运算符双目运算符:<<、>>;<<时移掉的位被丢弃,右边移出的空位补0;>>时移掉的位被丢弃,左边移出的空位补0或符号位;2.6.5赋值运算符简单赋值运算符:=复合赋值运算符:+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>=2.6.6其他运算符

1、三目运算符格式:d1?d2:d3功能:先计算d1,若其非零,表达式的值为d2的值,否则为d3的值;表达式类型:d2和d3中类型较高者的类型;2、逗号运算符格式:d1,d2,…,dn表达式的值和类型:由最后一个表达式确定;优先级在所有运算符中最低;3、s

izeof运算符2.6.6其他运算符(续)格式:功能:返回其后的类型说明符或表达式所表示的数在内存中所占的字节;4、单目运算符&和*&:取地址;*:用在指针名前,表示取指针的内容;5、强制类型运算符sizeof(<类型说明符>);或sizeof(<表达式>);格式:

<类型说明符>(<表达式>);或(<类型说明符>)<表达式>;2.6.6其他运算符(续)强制类型转换可将高类型转换为低类型,是一种不安全的转换;示例:doublef(3.85);inth;h=int(f);该转换是暂时的,一次性的;示例:inta(3),m;dou

bleb;b=3.56+double(a);m=a+5;2.7.1表达式的种类由运算符和操作数组成的式子;常见的表达式算术表达式;逻辑表达式;关系表达式;赋值表达式;条件表达式;逗号表达式;2.7.1表达式的种类(续)注意事项:连续的两个运算符之间用空格分隔;可用括号来改变运算符优先级;双

目运算符的左右可以用空格符和操作数分开;过长的表达式可分成几个表达式;2.7.2表达式的值和类型一、确定表达式的值二、表达式求值方法与确定类型的方法先确定运算符的功能;确定计算顺序:先考虑优先级,再考

虑结合性;2.7.2表达式的值和类型(续)例2.6:分析下列程序的输出结果(算术表达式)。#include<iostream.h>voidmain(){unsigneda(0xab),b(20);a&=b;a^=a;cout<<a<<"\t"<<b<<endl

;intx(-3),y(5);x>>y;x<<=y;x|=y^~y;y&=~x+1;cout<<x<<"\t"<<y<<endl;}输出:020-112.7.2表达式的值和类型(续)例2.7:分析下列程序的输出结果(

关系表达式)。#include<iostream.h>voidmain(){charx('m'),y('n');intn;n=x<y;cout<<n<<endl;n=x==y-1;cout<<n<<endl

;n=('y'!='Y')+(5>3)+(y-x==1);cout<<n<<endl;}输出:1132.7.2表达式的值和类型(续)例2.8:分析下列程序的输出结果(逻辑表达式)。#include<iostream.h>voidmain(){intx,y,z;x=y=z=1;--x

&&++y&++z;cout<<x<<"\t"<<y<<"\t"<<z<<endl;++x&&++y&&++z;cout<<x<<"\t"<<y<<"\t"<<z<<endl;++x&&y--||++z;cout<<x<<"\t"<<y<<"\t"<<z<<endl;}输出:0111222

12注意逻辑表达式的求值方法2.7.2表达式的值和类型(续)例2.9:分析下列程序的输出结果(条件表达式)。#include<iostream.h>voidmain(){inta(3),b(4),c;c=a>b?++a:++b;cout<<a<<"

,"<<b<<","<<c<<endl;c=a-b?a+b:a-3?b:a;cout<<a<<","<<b<<","<<c<<endl;}3,5,53,5,8注意三目运算符的判断条件输出2.7.2表达式的值和类型(续

)例2.10:分析下列程序的输出结果(赋值表达式)。#include<iostream.h>voidmain(){intx(1),y(3),z(5);x+=y*=z-=2;cout<<x<<","<<y<<","<<z<<endl;x*=y/=z-=x;co

ut<<x<<","<<y<<","<<z<<endl;x=y=z=2;z=(x+=2)+(y+=4)+2;cout<<x<<","<<y<<“,"<<z<<endl;}输出:10,9,3-10,-1,-74,6,122.7.2表达式的值和类型(续)例2.11:分析下列程

序的输出结果(逗号表达式)。#include<iostream.h>voidmain(){inta,b,c;a=1,b=2,c=a+b+3;cout<<a<<","<<b<<","<<c<<endl;c=(a++,a+=b,a+b);cout<<a<<

","<<b<<","<<c<<endl;}1,2,64,2,6输出2.7.3表达式中的类型转换一、隐含转换双目运算中操作数的类型转换;是一种保值转换,转换类型由低到高;转换规则:intunsignedlongunsignedlongdoubleshort,charfloat二

、强制转换显式强制转换(通过强制转换运算符实现);2.7.3表达式中的类型转换(续)隐式强制转换:在赋值表达式中,当左值(赋值运算符左边的值)和右值(赋值运算符右边的值)的类型不同时,一律将右值类型强制转换为

左值的类型;在函数有返回值的调用中,将return后面的表达式的类型强制转换为该函数的类型;显式隐式赋值表达式函数返回值隐含转换强制转换类型转换2.8类型定义1、格式typedef<已有类型名><新类型名表>;例如:typedefdoublewages,boun

s;wagesweekly;bounsmonthly;2、自定义类型的作用改善程序的可读性,增加所定义变量的信息书写简练提高程序的可移植性第3节预处理和语句3.1预处理功能3.2语句3.3选择语句3.4循环语句3.5转向语

句3.1预处理功能一、预处理命令及预处理功能1、预处理命令C++源程序中包含的各种编译命令在程序被正常编译之前执行;预处理命令不是C++语言的一部分;2、预处理功能由预处理命令实现的功能;二、常用的预处理命令文件包含命令宏定义命令3.1预处理功能(续)以"#"为引导;每条预处

理命令单独占用一行,同一行不能有其他预处理命令和语句;三、预处理命令使用说明条件编译命令停止编译命令预处理命令的位置可以放在开头、中间和结尾;预处理命令可以续行,续行符为"\";预处理命令不是语句,不能以分号(;)结束;3.1.1文件包含命令一、功能指示C++编译器将一个

文件(头文件)的内容嵌入到该指令所在的文件中该指令所在位置处;头文件指存放与标准函数有关的信息,或者存放符号常量、类型定义、类和其他复杂类型的定义以及与程序环境相关信息的.h文件;二、格式#include<文件名>由系统提供并放在指定子目录中的头文件;#include"文件名"由用

户定义,放在当前目录或其他目录下的头文件或其他源文件;3.1.1文件包含命令(续)三、说明文件包含命令一般放在程序头;一条文件包含命令只能包含一个文件;文件名:myfile.h#include"myfile2.h"#in

clude"myfile3.h"文件包含命令可以嵌套;包含文件不易过多;3.1.2宏定义命令一、功能用来将一个标识符定义为一个字符串,该标识符称为宏名,被定义的字符串称为替换文本;二、宏定义命令格式1、两种定义格式简单宏定义带参数的宏定义2、简单宏定义定义符号常量

,例如:#definePI3.1415#define<宏名><字符串>3.1.2宏定义命令(续)#define与const定义符号常量的区别const产生一个具有类型的符号#define仅产生文本替换,而不管内

容是否正确constdoublePI=3.1415;#definePI3.1415使用const可以定义一个局部常量,可局部在一个函数体内用#define定义的常量的作用域是从定义时开始,直到使用#undef取消定义时为止,如果不取消定义,直到整个文件结束使用c

onst定义常量是一个说明语句,用分号(;)结束使用#define定义常量是一个预处理命令,不能用分号(;)结束3.1.2宏定义命令(续)说明书写#define命令时,<宏名>与<字符串>之间用空格分隔,不能使

用等号连接;使用#define定义的标识符不是变量,它只用作宏替换,不占用内存;#define是一条预处理命令,不用分号结束,它所定义的标识符等价于其后的字符串;标识符被宏定义后,在取消这次宏定义之前,不允许重新对它进行宏定义;宏定义可以嵌套,已定义的标识符可以用来定义

新的字符串;3.1.2宏定义命令(续)说明<宏体>应写在一行上,如果需要写在多行时,需使用续行符(\)结束,并在其后按下回车键;<宏名>与左括号之间不能出现空格,否则空格右边都将作为宏体;定义带参数的宏体时,宏体中与参数名相同的字符序列适

当地加上括号,可以避免宏替换后出现的优先级问题;C++中,带参数的宏定义常由内联函数取代;3、带参数的宏定义#define<宏名>(<参数表>)<宏体>3.1.2宏定义命令(续)例3.1:分析下列程序

的输出结果。#include<iostream.h>voidmain(){intb(5);#defineb2#definef(x)b*(x)inty(3);cout<<f(y+1)<<endl;#undefbcout<<f(y+1)<<endl;#defi

neb(3)cout<<f(y+1)<<endl;}输出:82012简单宏定义带参数的宏定义b=2b=5b=33.1.2宏定义命令(续)4、替换正文中的操作符##与###:将它两边的操作数连接成一个符号;#:将它右边的操作数变

成一个字符串文字;例如:#defineCON(a,b)a##bdoublei=CON(5,E-10)doublei=5E-10;#defineSTR(a)#acout<<STR(Programmingisfun!)<<endl;cout<

<"Programmingisfun!"<<endl;5、取消宏定义命令#undef<标识符>3.1.3条件编译命令一、功能用来定义某些编译内容要在满足一定条件下才参与编译,否则不参与编译;可使同一源程序在不同的编译条件下产生不同的目标代码。二、格式格式一:#ifdef<标识符><程序

段1>#else<程序段2>#endif或#ifdef<标识符><程序段1>#endif3.1.3条件编译命令(续)格式二#ifndef<标识符><程序段1>#else<程序段2>#endif或#ifndef<标识符><程序

段1>#endif格式三#if<常量表达式1><程序段1>#elif<常量表达式2><程序段2>#elif<常量表达式3><程序段3>...#else<程序段n+1>#endif3.1.3条件编译命令(续)例3.2:分析下列程序的输出结果。#includ

e<iostream.h>#defineA10voidmain(){#ifA>0cout<<"a>0"<<endl;#elifA<0cout<<"a<0"<<endl;#elsecout<<"a==0"<<endl;#endif}输出:a>03.1.3条件编译命令(续)例3.3:避免重复引

用某个头文件。//main.cpp#include"myfile1.h"#include"myfile2.h"//myfile1.h#include"myhead.h"//myfile2.h#include"myhead.h"改进://myfile1.h#ifndefMYHE

AD_H#defineMYHEAD_H#include"myhead.h"#endif//myfile2.h#ifndefMYHEAD_H#defineMYHEAD_H#include"myhead.h"#endif3.1.3条件编译命令(续)例3.4:用于调试。调试时:

#defineDEBUG1...#ifDEBUG=1cout<<"OK"<<endl;#endif...调试后:#defineDEBUG0...3.1.4停止编译命令一、格式#error<字符序列>二、功能当编译器遇到该指令时,显示“字符序列”,即错误信

息,然后停止对该程序的编译,从而可以在编译阶段发现程序中的错误;三、示例假设country值为:US、ENGLAND、CHINA程序中有如下语句:3.1.4停止编译命令(续)//…#else#errorYoudefinecountryinco

rrectly#endif当country的值不是上述三者之一时,编译器将显示:然后停止编译。Youdefinecountryincorrectly3.2语句1、表达式语句和空语句表达式语句:任何一个表达式加上分号(;);空语

句:只有一个分号(;)的语句;2、复合语句和分程序复合语句:由两条或两条以上的程序构成,并由一对花括号括起;分程序:又称块结构,含有一条或多条说明语句的复合语句;3.3.1条件语句if(<条件1>)<语句1>elseif(<条件2>)<语句2>elseif(<条件3>)<语句3>...el

seif(<条件n>)<语句n>else<语句n+1>说明:if语句可以嵌套,在此情况下,else与最近的一个没有与else配对的if配对。3.3.2开关语句switch(<整型表达式>){case<整常型

表达式1>:<语句序列1>case<整常型表达式2>:<语句序列2>...case<整常型表达式n>:<语句序列n>default<语句序列n+1>}注意:在执行语句序列中如果遇到break语句,则退出switch语句,执行后面的语句;如果其后的语句序列中没有break语句,

则一直执行至switch语句结束。3.3.2开关语句(续)例3.5:分析下列程序的输出结果。#include<iostream.h>voidmain(){inti(1),j(0),m(1),n(2);sw

itch(i++){case1:m++;n++;case2:switch(++j){case1:m++;case2:n++;}case3:m++;n++;break;case4:m++;n++;}co

ut<<m<<','<<n<<endl;}输出:4,53.4循环语句1、while循环语句while(<条件>)<语句>;2、do-while循环语句do<语句>while(<条件>);do-while循环与while循环的区别:do-while循环至少执行一次循环体,whil

e循环可能一次也不执行循环体;3、for循环语句for(d1;d2;d3)<语句>;4、多重循环3.5转向语句1、goto语句格式:goto<语句编号>;goto语句只能在一个函数内进行转向;2、break语句格式:break;适用情况:用于开关语句的语句序列中,其功能是退出开关语句,执行

其后的语句;用于循环体中,其功能是用来退出该重循环;3、continue语句格式:continue;功能:在循环体中用来结束本次循环;第4章函数和作用域4.1函数的基本概念4.2函数的定义和说明4.3函数的调用4.4

函数的参数4.5内联函数4.6函数重载4.7异常处理基础4.8作用域返回类型为void类型的函数抽象为过程抽象4.1函数的基本概念函数结构:由花括号括起来的一个语句序列;函数抽象:使用标识符对语句序列进行的抽象;函数调用:函数级上的控制抽象,一种控制转移;参数化:在函数抽象中对其

所操作的值进行抽象的过程;形参与实参函数抽象与过程抽象函数抽象的目的是进行求值过程抽象的目的是更新对象C++中只有函数抽象4.2.1函数的定义格式<类型><函数名>(<参数表>){<若干条语句>}说明:<类型>为函数返回值类型,若为vo

id,则为过程调用;<参数表>中的参数为形参,在函数被调用时进行初始化,从而从被调用处获得数据;函数体4.2.2函数的说明方法(函数的声明)一、函数的说明原则如果一个函数定义在先,调用在后,调用前可以不必说明;如果一

个函数定义在后,调用在先,调用前必须说明;二、函数的说明方法(原型说明)<类型><函数名>(<参数表>);三、示例参数表中的参数名称可以省略4.2.2函数的说明方法(续)#include<iostream.h>voidfun1(),fun2(),fun3();voidmain

(){cout<<"Itisinmain."<<endl;fun2();cout<<"Itisbackinmain."<<endl;}voidfun1(){cout<<"Itisinfun1."<<endl;fun3();cout<<"Itisbackinfun1."<<endl;}例4.1:分析

下列程序的输出结果。函数原型声明4.2.2函数的说明方法(续)voidfun2(){cout<<"Itisinfun2."<<endl;fun1();cout<<"Itisbackinfun2."<<endl;

}voidfun3(){cout<<"Itisinfun3."<<endl;}Itisinmain.Itisbackinfun1.Itisinfun2.Itisbackinfun2.Itisinfun1.Itisbackinmain.Itisinfun3.输出4.3.1函数的值和类

型说明:实参表的个数由形参决定,用来初始化实参,多个实参用逗号分隔;实参的个数与类型必须与形参的个数与类型完全一致;一、函数调用格式<函数名>(<实参表>);实参对形参的初始化按其位置进行;4.3.1函

数的值和类型(续)关于return语句的说明:有返回值的return语句可以返回一个表达式的值,从而实现函数之间的信息传递;无返回值的函数必须用void说明其返回类型;二、返回语句格式格式一:return<表达式>;格式二:return;4.3.2函数的传值调用一、传值调用

的分类二、传值调用的实现机制和特点传值调用:传递变量本身的值(数据值);传址调用:传递变量的地址值;用法:调用函数的实参用常量、变量(数据)值或表达式值,被调用函数的形参用变量;实现机制:系统将实参拷贝一个副本给形参(数据值);特点:形参值的改变不影响

实参值;4.3.2函数的传值调用(续)#include<iostream.h>voidswap1(intx,inty){inttemp;temp=x;x=y;y=temp;cout<<"x="<<x<<","<<"y="<

<y<<endl;}voidmain(){inta(5),b(9);swap1(a,b);cout<<"a="<<a<<","<<"b="<<b<<endl;}例4.2:分析下列程序的输出结果(传值调用)

。输出:x=9,y=5a=5,b=94.3.3函数的传址调用传址调用的实现机制和特点用法:调用函数的实参用地址值,被调用函数的形参用指针;实现机制:让形参的指针直接指向实参;特点:可以通过改变形参所指向的变量值来影响实参值;4.3.3函数的传址调用(续)#inc

lude<iostream.h>voidswap2(int*x,int*y){inttemp;temp=*x;*x=*y;*y=temp;cout<<"x="<<*x<<","<<"y="<<*y<<endl;}voidmain(){inta(5),b(9);swap2(&a,&b);cout<<

"a="<<a<<","<<"b="<<b<<endl;}输出:x=9,y=5a=9,b=5例4.3:分析下列程序的输出结果(传址调用)。4.3.4函数的引用调用(C++特有)引用调用的实现机制和特点用法:调用函数的实参用变量名,被调用函数的形

参用引用;实现机制:直接通过引用来改变实参的数据值;特点:起到传址调用的作用,但比传址调用更方便、更直接;4.3.4函数的引用调用(续)#include<iostream.h>voidswap3(int&

x,int&y){inttemp;temp=x;x=y;y=temp;cout<<"x="<<x<<","<<"y="<<y<<endl;}voidmain(){inta(5),b(9);swap3(a,b);cout<<"a

="<<a<<","<<"b="<<b<<endl;}输出:x=9,y=5a=9,b=5例4.4:分析下列程序的输出结果(引用调用)。4.3.4函数的引用调用(续)三种调用方式比较传值调用传址调用引用调用

实参常量、变量(数据)值或表达式值地址值变量名形参变量指针引用实现机制实参拷贝一个副本给形参形参指针直接指向实参通过引用直接指向实参特点形参的变化不影响实参形参的变化影响实参形参的变化影响实参4.4.1函数参数的求值顺序当一个函数带有多个参数时,

C++语言没有规定函数调用时实参的求值顺序;编译器根据对代码进行优化的需要自行规定对实参的求值顺序;在实参中注意不要使用带有副作用的运算符,此时可能产生二义性;例4.5:由于使用对参数求值顺序不同的编译器造成的二义性。4

.4.1函数参数的求值顺序(续)#include<iostream.h>intadd(intx,inty){returnx+y;}voidmain(){intx(4),y(6);intz=add(++x,x+y);cout<<z<<en

dl;}产生二义性可能的结果(按照编译器对实参求值顺序不同):自左至右,两个实参的值分别为5和11;自右至左,两个实参的值分别为5和10;注意:函数参数求值顺序与参数默认值补足顺序不同参数求值顺序:由编译器决定求值方向;参数默认值补足顺序:自左向右4.4.2设置函数参数的默认值C++中,在

函数声明或定义时可以为一个或多个参数指定缺省参数值;intadd(intx,inty=10);进行函数调用时,若未指定足够的实参,则编译器将按从左向右的顺序用函数声明或定义中的缺省值来补足所缺少的实参;add(15);add(15,10);4.4.2设置

函数参数的默认值(续)在一个指定了缺省值的参数的右边,不能出现没有指定缺省值的参数;voidf(intx,inty=1,intz);在给某个参数指定缺省值时,不仅可以是一个数值,而且可以是任意复杂的表达式;i

ntf();……voiddelay(intk,inttime=f());错误例如:f(2,4);理想:f(2,1,4),实际:z参数未被赋值4.4.2设置函数参数的默认值(续)#include<iostream.h>voidfun(inta=1,intb=3,intc=5){cout<<"a=

"<<a<<","<<"b="<<b<<","<<"c="<<c<<endl;}voidmain(){fun();fun(7);fun(7,9);fun(7,9,11);cout<<"OK!"<<endl;}输出:a=1,b=3,c=5a=7,b=3,c=5a=7,b=9,c=5

a=7,b=9,c=11OK!例4.6:分析下列程序的输出结果。普通函数内联函数4.5.1内联函数引入的原因目的解决程序中一些函数体代码不是很大,但又频繁地被调用的函数的函数调用的效率问题。解决方法以目标代码的增加为代价来换取时间上的节省;即在编译时将函数体中

的代码替换到程序中,增加目标程序代码量,进而增加空间开销,从而在时间开销上不像函数调用时那么大。4.5.2内联函数的定义方法在函数定义的前面加上关键字inlineinlineintadd(intx,inty,intz){returnx+y+z;}4.5.3使用

内联函数的注意事项在内联函数内不允许用循环语句或开关语句;内联函数的定义必须出现在该内联函数第一次被调用之前;类结构中所有在类说明内部定义的函数都是内联函数;对内联函数不能进行异常接口说明;内联函数无法递归调用;内联函数具有与带参数的宏定义#define相同的作用和相

似的机理,但内联函数具有宏定义的所有优点而没有其缺点,它消除了宏定义的不安全性。include<iostream.h>#definef(x)x*xvoidmain(){intx(2);cout<<f(x)<<endl;cout<<f(x+1)<<endl;}程序运行结果:45原因:f

(x)2*2f(x+1)2+1*2+1include<iostream.h>inlineintf(intx){returnx*x;}voidmain(){intx(2);cout<<f(x)<<endl;cout<<f

(x+1)<<endl;}程序运行结果:49原因:f(x)替换为2*2f(x+1)替换为3*34.5.3使用内联函数的注意事项4.6函数重载函数重载是指同一个函数名可以对应着多个函数的实现;要求:编译器能够唯一

确定调用一个函数时应执行哪个函数代码;条件:参数个数不同;参数类型不同;注意:返回值类型不能作为重载条件;4.6.1参数类型不同的重载函数#include<iostream.h>intadd(int,int);doubleadd(double,double);voidmain()

{cout<<add(5,10)<<endl;cout<<add(5.0,10.5)<<endl;}intadd(intx,inty){cout<<"int"<<endl;returnx+y;}例4.7:分析下列程序的输出

结果。两个add函数的参数类型不同4.6.1参数类型不同的重载函数(续)doubleadd(doublex,doubley){cout<<"double"<<endl;returnx+y;}输出int15double15.54.6.2参数个数不同的重载函数

#include<iostream.h>intmin(inta,intb);intmin(inta,intb,intc);intmin(inta,intb,intc,intd);voidmain(){cout

<<min(13,5,4,9)<<endl;cout<<min(-2,8,0)<<endl;}intmin(inta,intb){returna<b?a:b;}例4.8:分析下列程序的输出结果。三个min函数的参数个数不同4.6.2参数个数不同的重载函数(续)intmin(inta,intb,

intc){intt=min(a,b);returnmin(t,c);}intmin(inta,intb,intc,intd){intt1=min(a,b);intt2=min(c,d);returnmin(t1,t2);}输出4-24.6.3带有缺省参数时的函数重载使用缺省参数时,注意满足函数重

载条件;voidfun(intx,inty=0);voidfun(intx);fun(3);重载哪个函数?此时函数fun不能进行重载,因为编译器不能唯一确定调用哪个函数(fun(3)或fun(3,0)均可)。4.7异常处理基础一、异常当一个函数在执行过程中出现了一些不平常的情况,或

运行结果无法定义的情况,使操作不得不被中断时,就出现了异常。二、异常处理的三个关键步骤引发异常、捕获异常、异常处理三、引发异常(throw)throw<表达式>;当一个异常被一个函数引发后,执行流程返回到该函数的调用者中;表达式类型称为异常类型4.7异常处理基础(续)dou

bleDiv(doublea,doubleb){if(b==0.0)throw1.0E-38;returna/b;}引发double类型的异常四、捕获异常(try)try{<语句序列>}try块的语句在执行过程中可能会引发多种类

型的异常,由catch块进行处理;捕获异常后,执行流程立即转到catch块,进行异常处理;4.7异常处理基础(续)五、异常处理(catch)catch(<参数声明1>){<语句序列1>}……catch(<参数声明n>){<语句序列n

>}每个catch块声明了所要捕获的一种类型;catch块中的语句表示在该异常被捕获时应执行的动作;4.7异常处理基础(续)参数声明形式:通过标识符except,catch可以获得更多的异常信息;doubleexcept或double&except一般情况下,捕获异

常时只关心异常的类型;4.7异常处理基础(续)#include<iostream.h>doubleDiv(doublea,doubleb){if(b==0.0)throw1.0E-38;returna

/b;}voidmain(){doublex,y,result(1234);try{例4.9:分析下列程序的输出结果。引发异常try块捕获异常4.7异常处理基础(续)cin>>x>>y;result=Div(x,y);cout<<"Continue."<<endl;}catch(d

ouble&){cout<<"Dividedbyzero."<<endl;}cout<<result<<endl;}处理double型异常运行当输入y=0时,输出结果为:Dividedbyzero12344.7异常处理基础(续)doubl

eDiv(doublex,doubley){...throw1E-38;…}voidmain(){...try{...result=Div(x,0);...}catch(double&){…}catch(…){…}...}引发异常捕获异常异常处理4.8作用域1、什么是作用域2、作用域的分类函

数原型作用域块作用域类作用域一个声明在程序正文中有效的那一部分区域;文件作用域4.8.1函数原型作用域作用域范围:开始于函数原型声明的左括号处,结束于函数原型声明的右括号处;doubleArea(doubleRadius);函数原型声明中的变

量名可以省略;4.8.2块作用域作用域范围:块作用域中声明的标识符的作用域从其声明点开始,直到结束块的花括号处;块作用域种类:分程序、if语句、switch语句以及循环语句;4.8.3类作用域类X的一个成员M在下列情况下局部于X,或者说具有类作用域:M出现在X的成员函数内,该成

员函数中没有声明同名的局部作用域的标识符;在x.M这样的表达式中,其中x为X类型的对象;在ptr->M这样的表达式中,其中ptr为指向X类型的一个对象的指针;在X::M这样的表达式中;4.8.4文件作用域作用域范围:文件作用域中声明的标识符的作用域开始于声明点

,结束于文件尾;头文件中声明的标识符的作用域可扩展到包含它的文件作用域中;面向对象程序设计中,文件作用域中只进行类声明,其他声明在类模块内部进行;4.8.5重新定义标识符的作用域规定在某个作用范围内定义的标识符在该范围内的子范围内可以重新定义该标识符。这时,原定义的标识符

在子范围内是不可见的,但是它还是存在的,只是在子范围内由于出现了同名的标识符而暂时被地被隐藏起来。过了子范围后,它又是可见的。4.8.5重新定义标识符的作用域规定(续)#include<iostream.h>voidmain(){inta(5),b(7),c(10);cou

t<<a<<","<<b<<","<<c<<endl;{intb(8);floatc(8.8);cout<<a<<","<<b<<","<<c<<endl;a=b;{intc;例4.10:分析下列程序的输出结果。a=5,b=

7,c=10重新定义b和c;a=5,b=8,c=8.8a=8,b=8,c=8.8重新定义c;4.8.5重新定义标识符的作用域规定(续)c=b;cout<<a<<","<<b<<","<<c<<endl;}cout<<a<<","<<b<<","<<c<<endl;}cout<<a<<","<

<b<<","<<c<<endl;}a=8,b=8,c=8c的重定义取消a=8,b=8,c=8.8b和c的重定义取消,a=8,b=7,c=10输出5,7,105,8,8.88,8,88,8,8.88,7,10第5章类和对象(一)5.1类的定义5.2对象的

定义5.3对象的初始化5.4成员函数的特性5.5静态成员5.6友元5.7对象的生存期5.1类的定义类是通过抽象数据类型的方法来实现的一种数据类型;类是面向对象程序设计的核心;类是对某一类对象的抽象,对象是某一种类的实例;类是C++实现抽象数据

类型的工具;5.1.1什么是类类是一种复杂数据类型,它是将不同类型的数据和与这些数据相关的操作封装在一起的集合体;类具有更高的抽象性,类中的数据具有隐藏性;5.1.2类的定义格式说明部分:说明该类中的成员,包含数据成员的说明和成员函数的说明;实现部分:对成

员函数的定义;1、类定义格式的构成2、类的一般定义格式class<类名>{public:<成员函数或数据成员的说明>private:<数据成员或成员函数的说明>};<各个成员函数的实现>访问权限类定义关

键字说明部分实现部分语句结束符5.1.2类的定义格式(续)访问权限修饰符:公有的(public)、私有的(private)和保护的(protected);访问权限修饰符出现的先后次序无关,并且允许多次出现;3、类定义的说明缺省访问权限为

私有的;公有部分:一些操作(即成员函数),是提供给用户的接口功能;私有部分:一些数据成员,通常用来描述该类中的对象的属性;5.1.2类的定义格式(续)4、示例(tdate.h)classTDate{public:voidSetDate(inty,int

m,intd);intIsLeapYear();voidPrint();private:intyear,month,day;};voidTDate::SetDate(inty,intm,intd){year=y;month=m;成员函数定义作用域运算符5.1

.2类的定义格式(续)day=d;}intTDate::IsLeapYear(){return(year%4==0&&year%100!=0)||(year%400==0)}voidTDate::Print(){cout<<year<<"."<<month<<"."<<day<<end

l;}成员函数定义成员函数定义作用域运算符::作用:标识某个成员属于哪个类;格式:<类名>::<函数名>(<参数表>)5.1.3定义类时的注意事项在类体中不允许对所定义的数据成员进行初始化;classTDate{public:…...private:int

year(1998),month(4),day(9);};错误类中的数据成员的类型可以是任意的;包含整型、浮点型、字符型、数组、指针和引用等;另一个类的对象,可以作该类的成员;自身类的对象不可以作该类的成员;5.1.3定义类时的注意事项

(续)classN;classM{public:…...private:N*n;};classN{public:voidf(Mm);……};提前说明类N自身类的指针或引用,可以作该类的成员;当另一个类的对象作为该类的成员时,如果另一个类的定义在后,需要提前说明;一般

在类体内先说明用户感兴趣的公有成员,再说明私有成员;习惯将类定义的说明部分或者整个定义部分(包含实现部分)放到一个头文件中;n是N类的对象m是M类的对象5.2.1对象的定义格式例如:TDatedate1,date2,*Pdate,dat

e[31];<类名><对象名表>;对象的定义格式。5.2.2对象成员的表示方法1、一般对象数据成员:<对象名>.<成员名>成员函数:<对象名>.<成员名>(<参数表>).运算符:表示对象的成员例如:date1.year,date1

.month,date1.day;date1.SetDate(1998,4,9);2、指针对象数据成员:<对象名>-><成员名>成员函数:<对象名>-><成员名>(<参数表>)->运算符:表示对象的成员例如:Pdate->year,Pdate->SetDate(1998,4,9);5.2.2对象

成员的表示方法(续)->运算符与.运算符的区别->表示指向对象的指针的成员;.表示一般对象的成员;两种等价表示<对象指针名>-><成员名>(*<对象指针名>).<成员名>3、引用对象与一般对象相同;5.2.2对象成员的表示方法(续)#include<

iostream.h>#include"tdate.h"voidmain(){TDatedate1,date2;date1.SetDate(1996,5,4);date2.SetDate(1998,4,9);intleap=date1.IsLeapYear();cout<<le

ap<<endl;date1.Print();date2.Print();}输出:11996.5.41998.4.9例5.1:分析下列程序的输出结果。5.3.1构造函数和析构函数1、构造函数与析构函数的功能特殊的成员函数;构造函数

:在创建对象时,使用特定的值来将对象初始化;析构函数:用来释放对象,在对象删除前做一些清理工作;5.3.1构造函数和析构函数(续)示例(tdate1.h)classTDate1{public:TDate

1(inty,intm,intd);~TDate1();voidPrint();private:intyear,month,day;};TDate1::TDate1(inty,intm,intd){year=y;month=m;构造函数析构函数5.3.1构造函数和析构函数(续)day=d;cout

<<"Constructorcalled."<<endl;}TDate1::~TDate1(){cout<<"Destructorcalled."<<endl;}voidTDate::Print(){cout<<year<<"."<<month<<"."<<day<<endl;}5.3.1构造函数

和析构函数(续)构造函数是成员函数,函数体可写在类体内,也可写在类体外;2、构造函数的特点3、析构函数的特点构造函数的名字与类名相同;析构函数的名字在类名前加~字符;析构函数是成员函数,函数体可写在类体内,也可写在类体外;构造函数不指定返回类型,它有隐含的返回值,该值由系统内部使用;析

构函数不指定返回类型;5.3.1构造函数和析构函数(续)构造函数可以有一个或多个参数;2、构造函数的特点3、析构函数的特点构造函数可以重载;一个类中只能定义一个析构函数;析构函数没有参数;程序中不能直接调用构造函数,在创建对象时系统自动调用构造函数;析构函数在

对象存在的函数体结束时或使用delete运算符释放new运算符创建的对象时被自动调用;5.3.1构造函数和析构函数(续)#include<iostream.h>#include"tdate1.h"voidmain(){TDa

te1today(1998,4,9),tomorrow(1998,4,10);cout<<"Todayis";today.Print();cout<<"Tomorrowis";tomorrow.Print();}输出:

Constructorcalled.Constructorcalled.Todayis1998.4.9Tomorrowis1998.4.10Destructorcalled.Destructorcalled.例5.2:分析下列程序的输出结果。5.3.2缺省构造函数和缺省析构函数

<类名>::<缺省构造函数名>(){}1、缺省构造函数类定义中没有任何构造函数时,由编译器自动生成一个不带参数的缺省构造函数;缺省构造函数即参数表为空的构造函数;<类名>::~<缺省析构函数名>(){}2、缺省析构函数(定义时机同缺省构造函数)类名5.3.3拷

贝初始化构造函数1、功能用一个已知的对象来初始化一个被创建的同类对象;2、特点函数名同类名,无返回类型;只有一个参数,是对某个对象的引用;每个类都必须有一个拷贝初始化构造函数;<类名>::<类名>(const<类名>&<引用名>)3、缺省拷贝初始化构造函数如果类中没有说明拷贝初

始化构造函数,则编译系统自动生成一个具有上述形式的缺省拷贝初始化构造函数,作为该类的公有成员;5.3.3拷贝初始化构造函数(续)//tpoint.hclassTPoint{public:{TPoint(intx,inty){X=x;Y=y;}TPoint(TPoin

t&p);~TPoint(){cout<<"Destructorcalled."<<endl;}intXcoord(){returnX;}intYcoord(){returnY;}private:intX,Y;};例5.3:分析下列程序的输出结果。构造函数拷贝初始化构造函数5.3.3拷贝

初始化构造函数(续)TPoint::TPoint(TPoint&p){X=p.X;Y=p.Y;cout<<"Copy_initializationConstructorcalled.\n";}#include<iostream.h>#include"tpoint1.h"TPointf

(TPointQ);voidmain(){TPointM(20,35),P(0,0);TPointN(M);M为已知对象,N是正在创建的对象5.3.3拷贝初始化构造函数(续)P=f(N);cout<<"P="<<P.Xcoo

rd()<<","<<P.Ycoord()<<endl;}TPointf(TPointQ){cout<<"OK!"<<endl;intx,y;x=Q.Xcoord()+10;y=Q.Ycoord()+20;TPointR(x,y);returnR

;}传值调用将R的值作为返回值5.3.3拷贝初始化构造函数(续)输出:Copy_initializationConstructorcalled.Copy_initializationConstructorcalled.OK!Copy_initializationConstruc

torcalled.Destructorcalled.Destructorcalled.Destructorcalled.P=30,55Destructorcalled.Destructorcalled.Destru

ctorcalled.匿名对象5.3.3拷贝初始化构造函数(续)4、使用拷贝初始化构造函数初始化的三种情况明确表示由一个对象初始化另一个对象时;例如:TPointN(M);当对象作为函数实参传递给函数形参时(传值调用);例如

:P=f(N);当对象作为函数返回值时(数据值);例如:returnR;5.3.4赋值1、功能用于更新类类型的对象;2、特点该函数的函数名是一个操作符,必须与关键字operator合用;该函数只有一个参数,是对该类某个对象的常引用;一般赋值操作返回对被赋值对象的引用;5

.3.4赋值(续)类中未声明赋值操作时,编译器自动生成一个公有的赋值操作;赋值操作的等价表示形式:<类名>&<类名>::operator=(const<类名>&<引用名>)B=A;等价于B.operator=(A);每个类都有一个赋值操作;5.3.4赋

值(续)#include<iostream.h>classLocation{public:{Location(intxx=0,intyy=0){X=xx;Y=yy;}Location(Location&p){X=p.X;Y=p.y;}Location&operator

=(Location&p);intGetX(){returnX;}intGetY(){returnY;}private:intX,Y;};例5.4:分析下列程序的输出结果。赋值函数5.3.4赋值(续)Location&Location::operator=(

Location&p){X=p.X;Y=p.Y;cout<<"Assignmentoperatorcalled."<<endl;return*this;}voidmain(){LocationA(1,2),B;B=A;cout<<"B="<<

B.GetX()<<","<<B.GetY()<<endl;};输出Assignmentoperatorcalled.B=1,25.3.4赋值(续)3、何时需要在类中定义拷贝初始化和赋值操作当类中声明有指针数据成员时,必须定义拷贝初始化和赋值操作,否

则编译器生成的拷贝初始化操作和赋值操作的执行将导致程序在运行时产生问题;classAA::A(inti){{public:p=newint(i);A(inti);}~A();A::~A()private:{int*p;deletep;};}5.3.4赋值(续)

voidmain(){Aa(5);Ab(a);…...Ac(5),d(10);d=c;…...};拷贝初始化:a和b对象的指针指向同一个对象,执行析构函数将删除同一个对象两次;赋值:d对象的p指针被更新为c对

象的p指针的值,则:(1)d对象的p指针原先所指向的值将无法访问;(2)执行析构函数将删除同一个对象两次;5a.pb.p510c.pd.pd.p5.3.4赋值(续)//修改A::A(A&r){p=newint(*r.p);}A&A::ope

rator=(A&r){if(this==&r)return*this;p=r.p;return*this;};5a.p510c.pd.p5b.p55.4.1内联函数和外联函数内联函数:定义在类体内的成员函数,或定义在类体外,但使用inlin

e关键字进行说明的成员函数;外联函数:说明在类体内,定义在类体外的成员函数;5.4.1内联函数和外联函数(续)例5.5:分析下列程序的输出结果。#include<iostream.h>classA{public:A(intx,inty){X=x;Y=y;}inta()

{returnX;}intb(){returnY;}intc();intd();private:intX,Y;};内联函数5.4.1内联函数和外联函数(续)inlineA::c(){returna()+b();}inlinei

ntA::d(){returnc();}voidmain(){Am(3,5);inti=m.d():cout<<"d()return:"<<i<<endl;}输出:d()return:8内联函数内联函数5.4.2重载性构造函

数可以重载;析构函数不能重载;一般成员函数可以重载;5.4.2重载性(续)例5.6:分析下列程序的输出结果。#include<iostream.h>classM{public:M(intx,inty){X=x;Y=y;}M(intx){X=

x;Y=x*x;}intAdd(intx,inty);intAdd(intx);intAdd();intXout(){returnX;}intYout(){returnY;}private:intX,Y;};构造函数重载一般成员函数重载5.4.2重载性(续)intM::Add(intx,i

nty){X=x;Y=y;returnX+Y;}intM::Add(intx){X=Y=x;returnX+Y;}intM::Add(){returnX+Y;}5.4.2重载性(续)voidmain(){Ma(10,20),b(4);

cout<<"a="<<a.Xout()<<","<<a.Yout()<<endl;cout<<"b="<<b.Xout()<<","<<b.Yout()<<endl;inti=a.Add();intj=a.Add(3,9);intk=a.Add

(5);cout<<i<<endl<<j<<endl<<k<<endl;}输出a=10,20b=4,163012105.4.3设置参数的缺省值一般成员函数和构造函数都可以被设置缺省参数值。5.4.3设置参数

的缺省值(续)例5.7:分析下列程序的输出结果。#include<iostream.h>classN{public:N(inta=3,intb=5,intc=7);intAout(){returnA;}intBout(){returnB;}intCout(){returnC;}privat

e:intA,B,C;};intN::N(inta,intb,intc)构造函数设置缺省参数值5.4.3设置参数的缺省值(续){A=a;B=b;C=c;}voidmain(){NX,Y(9,11),Z(13,15,17);cout<<"X="<<X.Aout()<<

","<<X.Bout()<<","<<X.Cout()<<endl;cout<<"Y="<<Y.Aout()<<","<<Y.Bout()<<","<<Y.Cout()<<endl;cout<<"Z="<<Z.Aout()<<","<<Z

.Bout()<<","<<Z.Cout()<<endl;}输出:X=3,5,7Y=9,11,7Z=13,15,175.5静态成员目的:解决数据共享问题,即不通过全局对象,而实现多个对象之间的数据共享。5.5.1静态数据成员1、静态数据成员是类的所有对象共享的成员,而不是某个对象

的成员;对多个对象来说,静态数据成员只存储在一个地方,供所有对象使用;静态数据成员的值对每个对象都是一样的,但其值可以被任何一个对象更新;2、使用方法与注意事项静态数据成员在定义或说明时前面加上关键字sta

tic;private:staticints;s是私有的静态数据成员;5.5.1静态数据成员(续)静态数据成员是静态存储的,它是静态生存期,必须对它进行初始化;静态数据成员的初始化与一般数据成员初始化不同,格式如下:说明:初始化在类体外进行,前面不加static,以免与一般静态变量或对象混淆;<数

据类型><类名>::<静态数据成员名>=<值>;初始化时不加该成员的访问权限控制符(静态数据成员初始化位置与访问权限无关);初始化时使用作用域运算符表明它所属的类;引用格式:<类名>::<静态成员名>5.5.1静态数据成员(续)例5.8:分析下列程序的输出结果。#include<iost

ream.h>classMyclass{public:Myclass(inta,intb,intc);voidGetNumber();voidGetSum();private:intA,B,C;staticintSum;};intMyclas

s::Sum=0;私有静态数据成员Sum静态数据成员Sum初始化5.5.1静态数据成员(续)Myclass::Myclass(inta,intb,intc){A=a;B=b;C=c;Sum+=A+B+C;}voidMyc

lass::GetNumber(){cout<<"Number="<<A<<","<<B<<","<<C<<endl;}voidMyclass::GetSum(){cout<<"Sum="<<Sum<<endl;}5.5.1静态数据成员(续)voidmain(){Myclas

sM(3,7,10),N(14,9,11);M.GetNumber();N.GetMumber();M.GetSum();N.GetSum();}输出Number=3,7,10Number=14,9,11Sum=54Sum=54Myclass类Sum对象

a对象e对象i对象z5.5.2静态成员函数1、作用操作静态数据成员;3、注意事项静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员;静态成员函数中要引用非静态成员时,可以通过对象来引用;2、使用格式<类名>

::<静态成员函数名>(<参数表>)5.5.2静态成员函数(续)例5.9:分析下列程序的输出结果。#include<iostream.h>classM{public:M(inta){A=a;B+=a;}

staticvoidf1(Mm);private:intA;staticintB;};voidM::f1(Mm){私有静态数据成员B公有静态成员函数静态成员函数体5.5.2静态成员函数(续)cout<<"A="<<m.A<<endl;cout<<"B

="<<B<<endl;}intM::B=0;voidmain(){MP(5),Q(10);M::f1(P);M::f1(Q);}调用静态成员函数<类名>::<静态成员函数名>(<参数表>)通过对象引用非静态成员直接引用静态成

员私有静态数据成员初始化输出A=5B=15A=10B=155.5.2静态成员函数(续)例5.10:某商店经销一种货物,货物成箱购进,成箱卖出,购进和卖出时以重量为单位,各箱的重量不一样,因此,商店需要记录下目前库存的货物的总重量,现在要求用C++语言来模拟商店货物购进和卖出的情

况。5.5.2静态成员函数(续)#include<iostream.h>classGoods{public:Goods(intw);~Goods();intWeight();staticintTotalWeight();pr

ivate:intweight;staticinttotalWeight;};Goods::Goods(intw){现有库存货物购进货物卖出5.5.2静态成员函数(续)weight=w;totalWeight+=w;}Goods::~Goods(){totalWeight-=weight

;}intGoods::Weight(){returnweight;}intGoods::TotalWeight(){returntotalWeight;}5.5.2静态成员函数(续)intGoods::totalWeight=0;void

main(){intw;cin>>w;Goodsg1(w);cin>>w;Goodsg2(w);cout<<Goods::TotalWeight()<<endl;}5.6友元1、为什么引入友元在对某些成员函数多次调用时,由于参数传递、类型检查和安全

性检查等都需要时间开销,从而影响了程序的运行效率,引入友元后可以提高程序的运行效率;3、友元的分类是一种定义在类外部的类或普通函数,但需要在类体内进行说明(前面加friend关键字);友元类;2、使用格式友元函数;不是成员函数,但可以访问类中的私有成员

;5.6.1友元函数例5.11:分析下列程序的输出结果。#include<iostream.h>classTime{public:Time(intnew_hours,intnew_minutes){hours=new_hours;minu

tes=new_minutes;}friendvoidTime12(Timetime);friendvoidTime24(Timetime);private:inthours,minutes;};voidTime12(Timetime){友元函数的

定义友元函数的说明5.6.1友元函数(续)if(time.hours>12){time.hours-=12;cout<<time.hours<<":"<<time.minutes<<"PM"<<endl;}elsecout<<time.hours<<":"<<time.minutes<<"AM"<

<endl;}voidTime24(Timetime){cout<<time.hours<<":"<<time.minutes<<endl;}友元函数的定义友元函数中使用私有数据成员5.6.1友元函数(续)voidmain(

){Timetime1(20,30),time2(10,45);Time12(time1);Time24(time1);Time12(time2);Time24(time2);}友元函数的调用输出8:30PM20:3010:45AM10:45问题:若友元函数

Time12和Time24的形参为引用,输出结果是什么?5.6.2友员类例5.12:分析下列程序的输出结果。#include<iostream.h>classX{friendclassY;public:voidSet(inti){x=

i;}voidDisplay(){cout<<"x="<<x<<","<<"y="<<y<<endl;}private:intx;staticinty;};友元类5.6.2友员类(续)classY{public:Y(inti,int

j);voidDisplay();private:Xa;};intX::y=1;Y::Y(inti,intj){a.x=i;X::y=j;}子对象,类X的对象a作为类Y的数据成员类Y中访问类X的私有数据成员x和y;5.6.2友员类(续)voidY::Display()

{cout<<"x="<<a.x<<",";cout<<"y="<<X::y<<endl;}voidmain(){Xb;b.Set(5);b.Display();Yc(6,9);c.Display();b.Display();}类Y中访问类X的私有数据成

员x和y;输出:x=5,y=1x=6,y=9x=5,y=9如果类X中定义了参数非空的构造函数,结5.7对象的生存期1、对象的生存期指对象从被创建开始到被释放为止的时间;局部对象:被定义在一个函数体或程序块内,作用域小

,生存期短;2、按生存期对对象的分类静态对象:被定义在一个文件中,它的作用域从定义时起到文件结束时止;它的作用域较大,生存期也较长;全局对象:被定义在某个文件中,它的作用域在包含该文件的整个程序中;它的作用域最大,生存期最长;5.7对象的生存期(续)例5.13:分析下列程序的输出结果。#in

clude<iostream.h>#include<string.h>classA{public:A(char*st);~A();private:charstring[50];};A::A(char*st){strcpy(string,st);cout<<"Construc

torcalledfor"<<string<<endl;5.7对象的生存期(续)}A::~A(){cout<<"Destructorcalledfor"<<string<<endl;}voidfun(){AFu

nObject("FunObject");staticAStaticObject("StaticObject");cout<<"Infun()"<<endl;}AGlobalObject("GlobalObjec

t");voidmain(){AMainObject("MainObject");5.7对象的生存期(续)cout<<"Inmain(),beforecalledfun()"<<endl;fun();cout<<"Inmain(),aftercalledfun()"<<

endl;}输出ConstructorcalledforGlobalObjectConstructorcalledforMainObjectInmain(),beforecalledfun()Constructorcalledfor

FunObjectConstructorcalledforStaticObjectInfun()DestructorcalledforFunObjectInmain(),aftercalledfun()DestructorcalledforMainObjectDes

tructorcalledforStaticObjectDestructorcalledforGlobalObject第6章类和对象(二)6.1对象指针和对象引用6.2数组6.3常类型6.4子对象和堆对象6.1.1对象指针和对象

引用作函数参数1、对象指针作函数参数优点实现传址调用。可在被调用函数中改变调用函数的参数对象的值,实现函数之间的信息传递;使用对象指针实参仅将对象的地址值传递给形参,而不进行副本的拷贝,这样可以提高运行效率,减少时间开销;6.1.1对象指针

和对象引用作函数参数(续)例6.1:分析下列程序的输出结果。#include<iostream.h>classM{public:M(){x=y=0;}M(inti,intj){x=i;y=j;}voidcopy(M

*m);voidsetxy(inti,intj){x=i;y=j;}voidprint(){cout<<x<<","<<y<<endl;}private:intx,y;};6.1.1对象指针和对象引用作函数参数(续)voidM

::copy(M*m){x=m->x;y=m->y;}voidfun(Mm1,M*m2);voidmain(){Mp(5,7),q;q.copy(&p);fun(p,&q);p.print();q.print();}

6.1.1对象指针和对象引用作函数参数(续)voidfun(Mm1,M*m2){m1.setxy(12,15);m2->setxy(22,25);}输出5,722,256.1.1对象指针和对象引用作函数参数(续)2

、对象引用作函数参数该方法除了具有对象指针作函数参数的优点外,还更简单更直接,应用更广;6.1.1对象指针和对象引用作函数参数(续)例6.2:分析下列程序的输出结果。#include<iostream.h>classM{public:M(){x=

y=0;}M(inti,intj){x=i;y=j;}voidcopy(M&m);voidsetxy(inti,intj){x=i;y=j;}voidprint(){cout<<x<<","<<y<<end

l;}private:intx,y;};6.1.1对象指针和对象引用作函数参数(续)voidM::copy(M&m){x=m.x;y=m.y;}voidfun(Mm1,M&m2);voidmain(){Mp(5,7),q;q.copy(p);fun(p,

q);p.print();q.print();}6.1.1对象指针和对象引用作函数参数(续)voidfun(Mm1,M&m2){m1.setxy(12,15);m2.setxy(22,25);}输出5,722,

256.1.2this指针该指针是隐含于每一个类的成员函数中的特殊指针;该指针指向正在被某个成员函数操作的对象;*this标识调用该成员函数的对象;6.1.2this指针(续)例6.3:分析下列程序的输出结果。#i

nclude<iostream.h>classA{public:A(){a=b=0;}A(inti,intj){a=i;b=j;}voidcopy(A&aa);voidprint(){cout<<a<<","<<b<<endl;}private:i

nta,b;};6.1.2this指针(续)voidA::copy(A&aa){if(this==&aa)return;*this=aa;}voidmain(){Aa1,a2(3,4);a1.copy(a2);a1.print();}

输出:3,46.2.1对象数组1、对象数组的定义<类名><数组名>[<大小>]...例如:DATEdates[7];DATEdate2[3][5];2、对象数组赋初值与赋值DATEdates[3]={DATE(7,22,1998),DATE(7,23,19

98),DATE(7,24,1998)};dates[0]=DATE(7,22,1998);dates[1]=DATE(7,23,1998);dates[2]=DATE(7,24,1998);注意数组元素的赋初值方式;数组元素通过匿名对象赋值,即:DATEd1(7,22,1998);dates

[0]=d1;释放d1;6.2.1对象数组(续)例6.4:分析下列程序的输出结果。#include<iostream.h>classDATE{public:DATE(){month=day=year=0;cout<<"Defaultconstructorcalled."<<endl;}DA

TE(intm,intd,inty){month=m;day=d;year=y;cout<<"Constructorcalled."<<day<<endl;缺省构造函数构造函数6.2.1对象数组(续)}~DATE(){cout<<"Destructorc

alled."<<day<<endl;}voidPrint(){cout<<"Month="<<month<<",Day="<<day<<",Year="<<year<<endl;}private:intmonth,day,year;};voidmain(){DATEdates[5]={DAT

E(7,22,1998),析构函数数组元素赋初值6.2.1对象数组(续)DATE(7,23,1998),DATE(7,24,1998)};dates[3]=DATE(7,25,1998);dates[4]=DATE(7,26,1998);for(inti=0;i

<5;i++)dates[i].Print();}数组元素赋值输出Constructorcalled.22Constructorcalled.23Constructorcalled.24Defaultconstructorcalled.Defaultconstructorcalled.C

onstructorcalled.25Destructorcalled.25数组元素赋初值缺省构造函数赋初值匿名对象赋值6.2.1对象数组(续)Constructorcalled.26Destructorcalled.26Month=7,D

ay=22,Year=1998Month=7,Day=23,Year=1998Month=7,Day=24,Year=1998Month=7,Day=25,Year=1998Month=7,Day=26,Year=1998Destructorcalled.26Destructorca

lled.25Destructorcalled.24Destructorcalled.23Destructorcalled.22匿名对象赋值析构函数的执行顺序与构造函数相反6.2.2指向数组的指针和指针数组1、指向数组的指针<类型说明符>(*<指针名>)[<大小>]...例如:

int(*p)[3];DATE(*pl)[4];6.2.2指向数组的指针和指针数组(续)例6.5:分析下列程序的输出结果。#include<iostream.h>classM{public:M(){a=b=0;}M(inti,intj){a=i;b=j;}voidPrint(){cout<<

a<<","<<b<<endl;}private:inta,b;};voidmain(){Mm[2][4];intx=10,y=10;for(inti=0;i<2;i++)对象数组6.2.2指向数组的指针和指针数组(续)for(intj=

0;j<4;j++)m[i][j]=M(x+=2,y+=10);M(*pm)[4](m);for(i=0;i<2;i++){cout<<endl;for(j=0;j<4;j++)(*(*(pm+i)+j)).Print();}cout<<endl;}指向对象

数组的指针输出12,2014,3016,4018,5020,6022,7024,8026,906.2.2指向数组的指针和指针数组(续)2、指针数组<类型名>*<数组名>)[<大小>]...例如:int*pa[3];DATE*pb[4];6.2.2指向数组的指针和指针数组(续)例6

.6:分析下列程序的输出结果。#include<iostream.h>classA{public:A(inti=0,intj=0){a=i;b=j;}voidPrint(){cout<<a<<","<<b<<endl;}private:inta,b;};voidmain(){Aa1(7,8),

a2,a3(5,7);A*b[3]={&a3,&a2,&a1};for(inti=0;i<3;i++)b[i]->Print();}输出:5,70,07,8指针数组的定义及赋值6.3常类型常类型:使用类型修饰符const说明的类型;常类型的变量或对象的值是不能被更新的;定

义或说明常类型时必须初始化;const修饰其左边的类型;6.3.1一般常量和对象常量1、一般常量(简单类型的常量)<类型说明符>const<常量名>或const<类型说明符><常量名>intconstx=2;或constintx=2;intconst

a[3]={1,2,3};或constinta[3]={1,2,3};数组元素的值是常量,不能更新;2、常对象<类名>const<对象名>classAintx,y;{};public:constAa1(3,

4);A(inti,intj){x=i;y=j;}private:Aconsta1(3,4);常对象A6.3.2常指针和常引用1、常指针char*constptr1=strptr1;ptr1是一个常量指针;ptr1=strptr2;*ptr1="m";ptr1不可以更新

ptr1所指向的变量可以更新;const的位置constchar*ptr2=strptr1;ptr2是一个指向字符串常量的指针;ptr2=strptr2;*ptr2="m";ptr2可以更新ptr2所指向的字符串不可以更新;

错误正确正确错误6.3.2常指针和常引用(续)2、常引用常引用所引用的对象不能被更新;const<类型说明符>&<引用名>doublex=1.2;constdouble&v=x;则:v=12.3错误。3、常指针

与常引用的作用使用常参数表明该函数不会更新某个参数所指向或所引用的对象,并使该函数具有更大的适应性;实参形参常类型非常类型常类型√非常类型√√6.3.2常指针和常引用(续)例6.7:分析下列程序的输出结果。#include<iostream.h

>constintN=6;voidprint(constint*p,intn);voidmain(){intarray[N];for(inti=0;i<N;i++)cin>>array[i];print(array,N);}voidprint

(constint*p,intn){cout<<"{"<<*p;for(inti=0;i<N;i++)cout<<","<<*(p+1);cout<<"}"<<endl;}整型常量常指针作形参输入:123456输出:{1,2,3,4,5,6}形参为常指针,实参为一般数组6.

3.2常指针和常引用(续)一种类型的变量或对象能够用于另一种类型的变量或对象可以使用的环境;类型适应非常对象使用在常对象中,就是类型适应,反之则不是;例如:6.3.2常指针和常引用(续)例6.8:分析下列程序的输出结果

。#include<iostream.h>classK{public:K(inti){k=i;}intsetk()const{returnk;}private:intk;};intadd(constK&g1,constK&g2);voidmain(){Kk1(8)

,k2(17);常成员函数常引用作形参6.3.2常指针和常引用(续)ints=add(k1,k2);cout<<s<<endl;}intadd(constK&g1,constK&g2){intsum=g1.setk()+g2

.setk();returnk;}形参为常引用,实参为非常对象类型适应输出256.3.3常成员函数1、常成员函数使用const关键字进行说明的成员函数;<类型说明符><函数名>(<参数表>)const;const是函数类型的一个组成部分,在函数实现部分必须带有const关键字;说明

:只有常成员函数才能操作常对象;常成员函数一般成员函数常对象可以不可以一般对象可以可以表成员函数与对象之间的操作关系6.3.3常成员函数(续)例6.9:分析下列程序是否正确。#include<iostream.h>classM{public:M(intx,inty){X=x;Y

=y;}voidMove(intx,inty){X=x;Y=y;}voidPrint()const{cout<<X<<","<<Y<<endl;}private:intX,Y;};一般成员函数常成员函

数6.3.3常成员函数(续)voidmain(){constMm1(1,2);m1.Move(3,3);m1.Print();Mm2(3,4);m2.Move(3,3);m2.Print();}常对象m1一般对象m2错误,一般成员函数不能操作常对象6.3.3常成员函

数(续)例6.10:分析下列程序的输出结果。#include<iostream.h>classR{public:R(intr1,intr2){R1=r1;R2=r2;}voidPrint(){cout<<R1<<":"<<R2<<endl;}voidPrint()const{

cout<<R1<<";"<<R2<<endl;}private:intR1,R2;};voidmain(){Ra(5,4);6.3.3常成员函数(续)a.Print();constRb(20,52);b.Print();}输出5:420;52对重载条件的补充:voidPr

int();voidPrint()const;可重载;常对象调用常成员函数,一般对象调用一般成员函数6.3.4常数据成员const类型对象必须被初始化,并且不能被更新;常数据成员只能通过成员初始化列表的方法进行初始化;6.3.4常数据成员(续)例6.11:分析

下列程序的输出结果。#include<iostream.h>classA{public:A(inti);voidPrint();private:constinta;staticconstintb;constint&r;};constintA:

:b=10;A::A(inti):a(i),r(a){}私有成员,常引用r私有成员,常量a私有常静态数据成员b成员初始化列表,常成员(除常静态成员外)在此初始化私有常静态数据成员b初始化6.3.4常数据成员(续)voidA::Print(){cout<<a<<":"<<b<<

":"<<r<<endl;}voidmain(){Aa1(100),a2(0);a1.Print();a2.Print();}输出100:10:1000:10:06.4.1子对象子对象即对象成员;当类中出现了子对象(对象成员)时,该类的构造函数要包含对子对象的初始化,通常采用成员初始化列表的方法来

初始化子对象;子对象:当一个类的成员是另一个类的对象时,该对象就为子对象;6.4.1子对象(续)例6.12:分析下列程序的输出结果。#include<iostream.h>classA{public:A(inti,intj){A1=i;A2=j;}voidPrint(){cout<

<A1<<","<<A2<<endl;}private:intA1,A2;};classB{public:B(inti,intj,intk):a(i,j),b(k){}成员初始化列表6.4.1子对象(续)voidPrint();priv

ate:Aa;intb;};voidB::Print(){a.Print();cout<<b<<endl;}voidmain(){Bb(6,7,8);b.Print();}子对象a对子对象成员函数的调用输出:6,786.4.1子对象(续)例6.13:分析下列程序的输出结果

。//part.h#if!defined(_PART_H)#define_PART_HclassPart{public:Part();Part(inti);~Part();voidPrint();private:intval;};#

endif6.4.1子对象(续)//part.cpp#include<iostream.h>#include"part.h"Part::Part(){val=0;cout<<"DefaultConstructorofPart."<<en

dl;}Part::Part(inti){val=i;cout<<"ConstructorofPart"<<val<<endl;}Part::~Part(){6.4.1子对象(续)cout<<"DestructorofPart"<<val<<endl;}voidP

art::Print(){cout<<val<<endl;}//whole.h#if!defined(_WHOLE_H)#define_WHOLE_H#include"part.h"classWhole{public:Whole();6.4.1子对象(续)Whole(inti,intj,in

tk);~Whole();voidPrint();private:Partone;Parttwo;intdate;};#endif//whole.cpp#include<iostream.h>#include"whole.h"Whole::Whole(){子对象one、two6.4.1子对象(续)

date=0;cout<<"DefaultconstructorofWhole."<<endl;}Whole::Whole(inti,intj,intk):two(i),one(j),date(k){cou

t<<"ConstructorofWhole."<<endl;}Whole::~Whole(){cout<<"DestructorofWhole."<<endl;}voidWhole::Print(){o

ne.Print();two.Print();成员初始化列表构造函数体6.4.1子对象(续)cout<<date<<endl;}//ex613.cpp#include"whole.h"voidmain(){WholeanOb

ject(5,6,10);anObject.Print();}输出:ConstructorofPart6ConstructorofPart5ConstructorofWhole.6510DestructorofW

hole.DestructorofPart5DestructorofPart66.4.1子对象(续)#include"whole.h"voidmain(){WholeanObject;anObject.Pr

int();}输出:DefaultconstructorofPart.DefaultconstructorofPart.DefaultconstructorofWhole.000DestructorofWhole.DestructorofPart0DestructorofPart

06.4.1子对象(续)子对象必须在成员初始化列表中初始化;说明:建立一个对象时,它的所有子对象一起建立;先执行子对象构造函数,再执行对象的构造函数体;析构函数的执行顺序与构造函数的执行顺序严格相反;构造函数的调用顺序仅与子对象在类中声明的顺序

有关,而与成员初始化列表中给出的对构造函数的调用顺序无关;构造函数的成员初始化列表中未给出对子对象的调用,则表示使用子对象的缺省构造函数;6.4.2堆对象在程序运行过程中根据需要可以随时建立或删除的对象;1、堆对象n

ew、delete2、堆对象运算符动态创建堆对象;3、new运算符new<类型说明符>(<初始值列表>)new运算符返回一个与new所分配对象类型相匹配的指针;如果new运算符不能分配到所需要的内存,将返回0,这时

为空指针;6.4.2堆对象(续)new创建数组:new<类型名>[<算术表达式>]使用new创建对象数组或一般数组时,不能为该数组指定初始值,其初始值为缺省值;使用new运算符创建对象时,它可以根据其参数来选择适当的构造函数;例如:A*ptr;ptr=newA[

5];使用new[]创建对象数组时,类中必须说明缺省构造函数;4、delete运算符6.4.2堆对象(续)功能:删除用new创建的对象或一般类型的指针;delete<指针名>例如:A*ptr;ptr=newA(5,6);…deleteptr;删除对象

数组delete[]<指针名>例如:A*ptr;ptr=newA[5];…delete[]ptr;6.4.2堆对象(续)必须用于由运算符new返回的指针;注意事项:该运算符也适用于空指针(即其值为0的指针);对一个指针只能使用一次

delete操作;指针名前只用一对方括号符([]),并且不管所删除数组的维数,忽略方括号内的任何数字;6.4.2堆对象(续)例6.14:分析下列程序的输出结果。//aa.h#include<iostream.h>classAA{public:AA();AA(inti

,intj);~AA();voidSet(inti,intj);voidPrint();private:intA,B;};AA:AA()6.4.2堆对象(续){A=B=0;cout<<"Defaultconstructorcalled."<<endl;

}AA::AA(inti,intj){A=i;B=j;cout<<"Constructorcalled."<<endl;}AA::~AA(){cout<<"Destructorcalled."<<endl;}voidAA::Set(inti,intj){6.4.2堆对象

(续)A=i;B=j;}AA::Print(){cout<<A<<","<<B<<endl;}//ex614.cpp#include"aa.h"voidmain(){AA*a1,*a2;a1=newAA(1,2);a2=newAA(5,6);a1.Print();6.4.2堆对象(续)a2.

Print();deletea1;deletea2;}输出Constructorcalled.Constructorcalled.1,25,6Destructorcalled.Destructorcalled.6.4.2堆对象(续)#incl

ude"aa.h"voidmain(){AA*ptr;ptr=newAA[2];ptr[0].Set(1,2);ptr[1].Set(5,6);ptr[0].Print();ptr[1].Print();delet

e[]ptr;}输出:Defaultconstructorcalled.Defaultconstructorcalled.1,25,6Destructorcalled.Destructorcalled.第7章继承性和派生性7.1基类和派生类7.2单继承7.3多继承7.4虚基类7

.1基类和派生类1、基类与派生类基类(父类):已存在的用来派生新类的类;派生类(子类):由已存在的类派生出的新类;2、单继承与多继承单继承:从一个基类派生的继承;多继承:从多个基类派生的继承;基类派生类ABACB单继承多继承7.1.1派生类的定义格式1、单继承cl

ass<派生类名>:<继承方式><基类名>{<派生类新定义成员>};2、多继承class<派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>...{<派生类新定义成员>};7.1.1派生类的定义格式(续)3、继承方式public:公有继承;private:私有继承;protect

ed:保护继承;作用:控制基类中声明的成员在多大的范围内能被派生类的用户访问;私有成员公有成员保护成员私有成员公有成员保护成员基类部分新定义部分派生类派生类成员派生类的构成7.1.1派生类的定义格式(续)7.1.2派生类的三种

继承方式基类派生类派生类基类实例派生类实例继承方式:publicprivateprotected(J)水平访问(H)垂直访问(V)直接继承(P)7.1.2派生类的三种继承方式(续)表:继承对基类成员的访问能力公有继承私有继承保护继承JPHVJPHVJPHV私有成员

公有成员√√√√√√√√√保护成员√√√√√(私)(私)(保)7.1.2派生类的三种继承方式(续)私有成员不参与继承的访问控制;说明:基类实例(J):与继承方式无关,遵循访问控制权限的定义;直接继承(P):可以访问基类中

的公有成员和保护成员,但成员的权限随继承方式而改变;水平访问(H)=P+J;垂直访问(V)=P+P;保护成员:在垂直访问(V)时相当于公有成员,在水平访问(H)时相当于私有成员;保护继承:在垂直访问(V)时相当于公有继承,在水平访问(H)时相当于私有继承;7.1.2派生类的三种继承方式(

续)例7.1:分析下列程序中的访问权限。classLocation{public:voidInitL(intxx,intyy);voidMove(intxOff,intyOff);intGetX(){returnX;}intGetY(){retu

rnY;}private:intX,Y;};voidLocation::InitL(intxx,intyy){X=xx;Y=yy;7.1.2派生类的三种继承方式(续)}voidLocation::Move(intxOff,intyOff){X+=

xOff;Y+=yOff;}classRectangle:publicLocation{public:voidInitR(intx,inty,intw,inth);intGetH(){returnH;}intGet

W(){returnW;}private:intH,W;};voidRectangle::InitR(intx,inty,intw,inth)公有继承7.1.2派生类的三种继承方式(续){InitL(

x,y);W=w;H=h;}#include<iostream.h>voidmain(){Rectanglerect;rect.InitR(2,3,20,10);rect.Move(3,2);cout<<rect.Get

X()<<“,"<<rect.GetY()<<","<<rect.GetH()<<","<<rect.GetW()<<endl;}水平访问输出:5,5,10,207.1.2派生类的三种继承方式(续)//派生类cla

ssV:publicRectangle{public:voidFunction();};voidV::Function(){Move(3,2);}公有继承垂直访问,正确若继承方式为private,Move(3,2)是否正确?为什么?若继承方式为private,Move(3,2)仍然正

确。原因:由于类Rectangle对类Location是公有继承,而类V对类Rectangle是直接继承,直接继承时不考虑继承方式,因此在类V内可以访问基类Location的公有成员;7.1.2派生类的三种继承方式(续)cl

assRectangle:privateLocation{public:voidInitR(intx,inty,intw,inth);intGetH(){returnH;}intGetW(){returnW;}

private:intW,H;};voidRectangle::InitR(intx,inty,intw,inth){InitL(x,y);W=w;H=h;}私有继承直接继承,正确7.1.2派生类的三种继承方式(续)#in

clude<iostream.h>voidmain(){Rectanglerect;rect.InitR(2,3,20,10);rect.Move(3,2);cout<<rect.GetX()<<","

<<rect.GetY()<<","<<rect.GetH()<<","<<rect.GetW()<<endl;}水平访问错误//修改classRectangle:privateLocation{public:voidInitR(intx,inty,intw,inth);voidMove(int

xOff,intyOff)7.1.2派生类的三种继承方式(续){Location::Move(xOff,yOff);}intGetX(){returnLocation::GetX();}intGetY(){returnLocation::GetY();}i

ntGetH(){returnH;}intGetW(){returnW;}private:intW,H;};voidRectangle::InitR(intx,inty,intw,inth){InitL(x,y);W=w;H=h;}通过成员名限定符(::)

指明调用基类中的成员7.1.2派生类的三种继承方式(续)classV:publicRectangle{public:voidFunction();};voidV::Function(){Move(3,2);}公有继承垂直访问,错误若继承方式为private,Move(3,

2)是否正确?为什么?若继承方式为private,Move(3,2)仍然错误。原因:由于类Rectangle对类Location是私有继承,而类V对类Rectangle是直接继承,直接继承时不考虑继承方式,因此在类V内不可以访问基类Location的公有成员;7.1.2派

生类的三种继承方式(续)classA{protected:intX;};voidmain(){Aa;a.X=5;}classB:publicA{public:voidFunction();};voidB::Funct

ion(){X=5;}错误,水平访问时保护成员相当于私有成员正确,垂直访问时保护成员相当于公有成员7.1.3基类与派生类的关系基类是对若干个派生类的抽象,而派生类是基类的具体化;基类抽取了它的派生类的公共特征,而派生类通过增加行为将抽象类变

为某种有用的类型。1、派生类是基类的具体化2、派生类是基类定义的延续派生类将其自身与基类区别开来的方法是添加数据成员和成员函数;3、派生类是基类的组合7.2.1成员访问权限的控制例7.2:分析下列程序中的访问权限,并回答

问题。#include<iostream.h>classA{public:voidf1();protected:intj1;private:inti1;};classB:publicA{public:void

f2();7.2.1成员访问权限的控制(续)protected:intj2;private:inti2;};classC:publicB{public:voidf3();};回答下列问题,并说明原因。1、派生类B中成员函数f2()能否访问基类A中的成员:f1()、j1和i1

?7.2.1成员访问权限的控制(续)2、派生类B的对象b1能否访问基类A中的成员:f1()、j1和i1?可以访问f1()和j1,不可以访问i1;原因:类B对类A是直接继承,可以访问A中的公有成员和保护成员,而不可以访问私有成员;可以访问f1(),不可以访问j1和

i1;原因:类B的对象b1对类A中的成员是水平访问,可以访问A中的公有成员,而不可以访问保护成员和私有成员;3、派生类C中的成员函数f3()能否访问直接基类B中的成员:f2()、j2和i2?能否访问间接基类A中的成员:f1()、j

1和i1?7.2.1成员访问权限的控制(续)4、派生类C的对象c1能否访问直接基类B中的成员:f2()、j2和i2?能否访问间接基类A中的成员:f1()、j1和i1?可以访问直接基类中的f2()和j2以及

间接基类中的f1()和j1,不可以访问i2和i1;原因:类C对类B是直接继承,原因同1;类C对类A是垂直访问,可以访问A中的公有成员和保护成员,而不可以访问私有成员;可以访问直接基类中的f2()以及间接基类中的f1(),其他都不可以访问;原因:类C的对

象c1对B中的成员是水平访问,原因同2;c1对A的访问,相当于先直接继承后水平访问;5、如将上述两处继承方式由public改为private,试回答上述问题。7.2.1成员访问权限的控制(续)例7.3:分析下列程序,并回答问题。#include<iostream.h>clas

sA{public:voidf(inti){cout<<i<<endl;}voidg(){cout<<"g"<<endl;}};classB:A{public:voidh(){cout<<"h"<<endl;A::f;};缺省继承方式为private将基类中的成员说明

为派生类中的成员7.2.1成员访问权限的控制(续)voidmain(){Bd1;d1.f(6);d1.g();d1.h();}回答下列问题,并说明原因。1、执行该程序时,哪个语句会出现编译错?2、去掉出错语句后,执行结果是

什么?3、类B从类A继承时的缺省继承方式是什么?4、派生类B中,A::f的含义是什么?5、将B的继承方式改为public,输出结果是什么?编译错。B以私有继承方式继承A,因此B的对象b1不能访问A的成员函数输出(2):6h输出(5):6gh7.2.2构

造函数和析构函数1、派生类构造函数基类子对象:派生类的对象中由基类中说明的数据成员和操作所构成的封装体;基类子对象由基类中的构造函数进行初始化;构造函数不能被继承;派生类构造函数的工作:对自己的数据成员进行初始化;负责调用基类构造函数使基类的数据成员得以初始化;调用子对象的构造函数,对派生类中的子

对象进行初始化;注意与基类的子对象的区别必须在成员初始化列表中进行7.2.2构造函数和析构函数(续)派生类构造函数格式:若某项的参数表为空,则该项可从成员初始化列表中省略,表示使用缺省构造函数初始化该基类子对象;<派生类名>(<派生类构造函数总参数表>):<基类构造函数>(<参数表1

>),<子对象名>(<参数表2>){<派生类中数据成员初始化>}说明:7.2.2构造函数和析构函数(续)派生类构造函数调用顺序:基类的构造函数;子对象的构造函数;派生类构造函数体;7.2.2构造函数和析构函数(续)例7.4:分析

下列程序的输出结果。#include<iostream.h>classB{public:B();B(inti);~B();voidPrint()const;private:intb;};B:B(){b=0;c

out<<"B'sdefaultconstructorcalled."<<endl;7.2.2构造函数和析构函数(续)}B::B(inti){b=i;cout<<"B'sconstructorcalled."<<end

l;}B::~B(){cout<<"B'sdestructorcalled."<<endl;}voidB:Print()const{cout<<b<<endl;}classC:publicB{7.2.2构造函数和析构函数(续)public:C();C(in

ti,intj);~C();voidPrint()const;private:intc;};C::C(){c=0;cout<<"C'sdefaultconstructorcalled."<<endl;}C::C(inti

,intj):B(i){c=j;7.2.2构造函数和析构函数(续)cout<"C'sconstructorcalled."<<endl;}C::~C(){cout<<"C'sdestructorcalled."<<en

dl;}voidC::Print()const{B::Print();cout<<c<<endl;}voidmain(){Cobj(5,6);obj.Print();}输出:B'sconstructorcalled.C'sconstructorcalled.56C'sdestructorcall

ed.B'sdestructorcalled.7.2.2构造函数和析构函数(续)2、派生类析构函数执行派生类的析构函数时,基类的析构函数也将被调用;析构函数不能被继承;析构函数的执行顺序与构造函数严格相反;派生类的析构函数;基类的析构函数;7.2.2构造函数和析构函数(续)例7.5:分析下

列程序的输出结果。#include<iostream.h>classM{public:M();M(inti,intj);~M();voidPrint();private:intm1,m2;};M:M(){m1=m2=0;}Print()函数是否可以定义为常成员函数?7.2.2构造

函数和析构函数(续)M::M(inti,intj){m1=i;m2=j;cout<<"M'sconstructorcalled."<<m1<<","<<m2<<endl;}M::~M(){cout<<"M'sdestructorcalled."<<m1<<","<<m2<<endl;}voi

dM::Print(){cout<<m1<<","<<m2<<",";}7.2.2构造函数和析构函数(续)classN:publicM{public:N(){n=0;}N(inti,intj,intk);~N();void

Print();private:intn;};M::M(inti,intj,intk):M(i,j),n(k){cout<<"N'scostructorcalled."<<n<<endl;}N::~N(){7.2.2构造函数和析构函数(续)cout<<"N'sdestructorcalled.

"<<n<<endl;}voidN::Print(){M::Print();cout<<n<<endl;}voidmain(){Nn1(5,6,7),n2(-2,-3,-4);n1.Print();n2.Print();}输出:M'sconstructorcal

led.5,6N'sconstructorcalled.7M'sconstructorcalled.-2,-3N'sconstructorcalled.-45,6,7-2,-3,-4N'sdestructorcalled.-4M'sdestructorcalled.-2,-3N

'sdestructorcalled.7M'sdestructorcalled.5,67.2.2构造函数和析构函数(续)3、派生类构造函数使用中应注意的问题派生类构造函数的定义中可以省略对基类构造函数的调用,其条件是在

基类中必须有缺省的构造函数或者根本没有定义任何构造函数;当基类的构造函数使用一个或多个参数时,派生类必须定义构造函数,提供将参数传递给基类构造函数的途径;编译器自动生成缺省构造函数设基类数据成员为m个,派生类数据成员为n个,

派生类的参数个数为x,则:0=<x<=m+n;7.2.2构造函数和析构函数(续)例7.6:分析下列程序的输出结果。#include<iostream.h>classA{public:A(){a=0;}A(inti){a=i;}voidPrint(){cout<<

a<<",";}private:inta;};classB:publicA{public:B(){b1=b2=0;}B(inti){b1=i;b2=0;}7.2.2构造函数和析构函数(续)B(inti,intj,intk):A(i),b1(j),b2(k){}voidPrint(){

A::Print();cout<<b1<<","<<b2<<endl;}private:intb1,b2;};voidmain(){Bd1,d2(5),d3(4,5,6);d1.Print();d2.Print();d3.Prin

t();}输出:0,0,00,5,04,5,67.3.1多继承的概念class<派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>……{<派生类类体>};7.3.2多继承的构造函数<派生类名>(<总参数表>):<基类

名1>(<参数表1>),<基类名2>(<参数表2>)……<子对象名>(<参数表n+1>),…...{<派生类构造函数体>}多继承构造函数格式:7.3.2多继承的构造函数(续)派生类构造函数负责所有基类构造函数的调用;

派生类构造函数执行顺序:执行所有基类的构造函数;执行所有子对象的构造函数;执行派生类构造函数体;处于同一层次的各基类构造函数的执行顺序取决于定义派生类时所指定的各基类顺序,与派生类构造函数中所定义的成员初始化列表中的各项顺序无关;7.3.2多继承的构造函数(续)例7.7:分析下列程序的输出结果

。#include<iostream.h>classB1{public:B1(inti){b1=i;cout<<"ConstructorB1."<<endl;}voidPrint(){cout<<b1<<endl;}private:intb1;};c

lassB2{public:B2(inti){b2=i;cout<<"ConstructorB2."<<endl;}voidPrint(){cout<<b2<<endl;}7.3.2多继承的构造函数(续)private:intb2;

};classB3{public:B3(inti){b3=i;cout<<"ConstructorB3."<<endl;}intGetb3(){returnb3;}private:intb3;};classA:publicB2,publicB1{public:A(in

ti,intj,intk,intl);多继承7.3.2多继承的构造函数(续)voidPrint();private:inta;B3bb;};A::A(inti,intj,intk,intl):B1(i),B2(j),bb(k){a=l;cout<<"ConstructorA.

"<<endl;}voidA::Print(){B1::Print();B2::Print();cout<<a<<bb.Getb3()<<endl;子对象基类构造函数调用顺序与定义时的顺序不同7.3.2多继承

的构造函数(续)}voidmain(){Aaa(1,2,3,4);aa.Print();}输出ConstructorB2.2ConstructorB1.1ConstructorB3.3ConstructorA.4124,37.3.3

二义性问题1、产生二义性的原因在多继承情况下,造成的对基类中某个成员的访问出现的不唯一的情况;classAclassC:publicA,publicB{{public:public:voidf();voidg();};voidh();classB};{public:voidf

();voidg();};voidf();c1.f()A.f()B.f()C7.3.3二义性问题(续)问题:若定义Cc1;,则c1.f()是否正确?答:c1.f()将产生二义性;原因:不能识别是调用类A或类B的f函数;解决方法:a.区别出

是类A或类B的f函数;c1.A::f();或c1.B::f();b.在类中定义同名函数f;当一个派生类从多个基类派生,而这些基类又有一个共同的基类,则对该基类中说明的成员进行访问时,可能会出现二义性;7.3.3二义性问题(续)classAclassB2:publicA

{{public:private:inta;intb2;};};classB1:publicAclassC:publicB1,publicB2{{private:public:intb1;intf();};private:intc;};

c1.aA.aB1.b1CB2.b2A.a7.3.3二义性问题(续)问题:若定义Cc1;,则c1.a与c1.A::a是否正确?答:c1.a与c1.A::a将产生二义性;原因:不能识别是通过类B1或类B2调用类A的a;解决方法:a.区别出是通过类B1或

类B2调用类A的a;c1.B1::a;或c1.B2::a;b.虚基类;2、解决方法利用成员名限定法消除二义性;在类中定义一个同名成员;虚基类;7.3.3二义性问题(续)3、支配规则(定义同名成员方法)支配规则

:类X中的名字N支配类Y中同名的名字N是指类X以类Y为它的一个基类;如果一个名字支配另外一个名字,则二者之间不存在二义性。当选择该名字时,使用支配者的名字;4、说明一个类不能从同一个类中直接继承一次以上;二义性检查在访问控制和类型检查之前进行,访问控制和类型检查不能解决二义性问题;7

.3.3二义性问题(续)classA{public:voidfun();};classB{private:voidfun();};classC:publicA,publicB{};voidmain(){Cobj;obj.fun();}o

bj.fun()产生二义性7.3.3二义性问题(续)例7.8:分析下列程序的输出结果。#include<iostream.h>classA{public:A(inti){a=i;cout<<"ConstructorA."<<i<<endl;}~A(){

cout<<"DestructorA."<<endl;}voidPrint(){cout<<a<<endl;}private:inta;};classB1:publicA{public:B1(inti,intj):

A(i){b1=j;7.3.3二义性问题(续)cout<<"ConstructorB1."<<endl;}~B1(){cout<<"DestructorB1."<<endl;}voidPrint(){A::Print();cout<<b1<<endl;}private:intb1;};cla

ssB2:publicA{public:B2(inti,intj):A(i){b2=j;cout<<"ConstructorB2."<<endl;}~B2(){cout<<"DestructorB2."<<endl;}voidPrint(){A::Print();cout<<b2<<en

dl;}private:intb2;};7.3.3二义性问题(续)classC:publicB1,publicB2{C(inti,intj,intk,intl,intm):B1(i,j),B2(k,l),c(m){cout<<"ConstructorC."<<endl;}~C(){cout<<"

DestructorC."<<endl;}voidPrint(){B1::Print();B2::Print();cout<<c<<endl;}private:intc;};voidmain(){Cc1(1,2,3,4,5);c1.Print();}a1.ab1.b

1c1.cb2.b2a2.a7.3.3二义性问题(续)ConstructorA.ConstructorB1.ConstructorA.ConstructorB2.ConstructorC.12345DestructorC.DestructorB2.DestructorA.Destr

uctorB1.DestructorA.执行结果注意基类A的实例的数目如果a是类A的公有成员,obj.a是否正确?7.4.1虚基类的引入和说明引入目的:解决二义性问题;格式:virtual<继承方式><基类名>说明:关键字virtual与关键字public或private的相对位置

无关,但必须位于虚基类名之前,且virtual只对紧随其后的基类名起作用;例如:classD:virtualpublicA,privateB,virutalpublicC其中:类A和类C是虚基类,而类B是非虚基类;7.4.1虚基类的引入和说明(续)classA{public

:voidf();protected:inta;};classB:virtualpublicA{protected:intb;};classC:virtualpublicB{protected:int

c;虚基类虚基类7.4.1虚基类的引入和说明(续)};classD:publicB,publicC{public:intg();private:intd;};下列各语句是否正确?Dn;n.f();voidD::g(){f()

;}BD.g()CA.f()n.f()正确能够唯一确定调用类A的f();7.4.1虚基类的引入和说明(续)虚基类非虚基类BCADBCDAA虚基类与非虚基类的存储结构7.4.2虚基类的构造函数派生类中只有一个虚基类子对象;虚基类构造函数必须只

被调用一次,目的是要保证虚基类子对象只被初始化一次;最派生类:继承结构中建立对象时所指定的类;虚基类子对象由最派生类的构造函数通过调用虚基类的构造函数进行初始化;在一个成员初始化列表中出现对虚基类和对非虚基类构造函数的调用时,虚基类的构造函数先于非虚

基类的构造函数的执行;最派生类的构造函数的成员初始化列表中必须给出对虚基类的构造函数的调用;如果未列出,则相应的虚基类必须有缺省构造函数;7.4.2虚基类的构造函数(续)若定义Ee;则E是最派生类;若定义Dd;则D是最派生类;若定义Bb;则B是最派生类;若定义Cc;则C是最派生类;

最派生类BDCAEB(…):A(…)...C(…):A(…)...D(…):B(...),C(…),A(…)...E(…):D(...),A(…)...构造函数7.4.2虚基类的构造函数(续)例7.9:分析下列程序的输

出结果。#include<iostream.h>classA{public:A(constchar*s){cout<<s<<endl;}~A(){}};classB:virtualpublicA{public:

B(constchar*s1,constchar*s2):A(s1){cout<<s2<<endl;}};classC:virtualpublicA7.4.2虚基类的构造函数(续){public:C(constchar*s1,constchar*s2):A(s1){cout<<s2<

<endl;}};classD:publicB,publicC{public:D(constchar*s1,constchar*s2,constchar*s3,constchar*s4):B(s4,s2),C(s2,s3)

,A(s1){cout<<s4<<endl;}};voidmain(){7.4.2虚基类的构造函数(续)D*ptr=newD("classA","classB","classC","classD");deleteptr;}输出

classAclassBclassCclassD虚基类子对象由最派生类构造函数进行初始化;虚基类子对象只被初始化一次;虚基类构造函数先于非虚基类构造函数执行;说明:7.4.2虚基类的构造函数(续)基类子对象数据成员初始化虚基类非虚基类常数据成员静态数据成员一般数据成

员存在多个基类时,执行顺序取决于派生类定义时的顺序存在多个子对象时,执行顺序取决于类中声明的顺序成员初始化列表类外初始化第8章多态性和虚函数8.1重载8.2静态束定与动态束定8.3虚函数8.4纯虚函数与抽象

类8.5虚析构函数第8章多态性和虚函数多态性:发出同样的消息被不同类型的对象接受导致完全不同的行为;多态可分为:静态多态性与动态多态性;动态多态性必须存在于继承的环境之中;8.1.1函数重载在类中,构造函数可以重载,普通成员函数也可以重载;构造函数重载给初始化带来了多种方式,

为用户提供了更大的灵活性。8.1.1函数重载(续)例8.1:分析下列程序的输出结果。#include<iostream.h>#include<string.h>classstring{public:string(char*s);string(string&s1);string(

intsize=80);~string(){deletesptr;}intGetLen(){returnlength;}voidPrint(){cout<<sptr<<endl;}private:char*sptr;intlength;构造函数重载8.1.1函数重载(续)};string::str

ing(char*s){length=strlen(s);sptr=newchar[length+1];strcpy(sptr,s);}string::string(string&s1){length=s1.length;sptr=newchar[length+1];strcpy(sptr

,s1.sptr);}string::string(intsize){带有指针成员的拷贝初始化构造函数8.1.1函数重载(续)length=size;sptr=newchar[length+1];*sptr='\0';}voidmain(){s

tringstr1("Thisisastring.");str1.Print();cout<<str1.GetLen()<<endl;char*s1="Thatisaprogram.";stringstr2(s1);stringstr3(str2);s

tr3.Print();cout<<str3.GetLen()<<endl;}输出:Thisisastring.17Thatisaprogram.188.1.2运算符重载的几个问题1、哪些运算符可以重载?算术运算符:+、-、*、/、%、++、--;位操作运算符:&、|、~、^

、<<、>>;逻辑运算符:!、&&、||;比较运算符:>、<、>=、<=、==、!=;赋值运算符:=、+=、-=、*=、/=、%=、&=、|=、~=、<<=、>>=;其他运算符:[]、()、->、'、new、delete、new[]、d

elete[]、->*;不允许重载的运算符:.、.*、::、?:、sizeof;8.1.2运算符重载的几个问题(续)2、编译程序如何选用哪一个运算符函数?运算符实质上是函数,遵循函数重载原则;3、运算符重载时必须遵循哪些原则?重载运算符含义必须清楚;重载运算符不能有二义性;4、重载运算符有哪些

限制?不可臆造新的运算符;重载运算符坚持4个“不能改变”:8.1.2运算符重载的几个问题(续)不能改变运算符操作数的个数;不能改变运算符原有的优先级;不能改变运算符原有的结合性;不能改变运算符原有的语法

结构;8.1.3运算符重载函数的两种形式1、重载为类的成员函数重载一元运算符,不再显式说明参数;重载二元运算符,只显式说明一个参数;该参数为操作数的右操作数,左操作数由this指针(指向调用该成员函数的对象)提供;重载为成员函数时,隐含了一

个参数(this指针);8.1.3运算符重载函数的两种形式(续)例8.2:分析下列程序的输出结果。#include<iostream.h>classcomplex{public:complex(doubler=0,doublei=0);complexoperator+(con

stcomplex&c);complexoperator-(constcomplex&c);complexoperator-();voidprint()const;private:doublereal,i

mag;};complex::complex(doubler,doublei){+运算符-运算符求负运算符8.1.3运算符重载函数的两种形式(续)real=r;imag=i;}complexcomplex::operator+(constcompl

ex&c){doubler=real+c.real;doublei=imag+c.imag;returncomplex(r,i);}complexcomplex::operator-(constcomplex&c){doubler=real-c.

real;doublei=imag-c.imag;returncomplex(r,i);}8.1.3运算符重载函数的两种形式(续)complexcomplex::operator-(){returncomplex(-real,-imag);}voidcomplex:

:print()const{cout<<'('<<real<<','<<imag<<')'<<endl;}voidmain(){complexc1(2.5,3.7),c2(4.2,6.5);complexc;c=c1-c2;c.print();c=c1+c

2;c=c1.operator-(c2);c=c1.operator+(c2);8.1.3运算符重载函数的两种形式(续)c.print();c=-c1;c.print();}输出(-1.7,-2.8)(

6.7,10.2)(-2.5,-3.7)c=c1.operator-();8.1.3运算符重载函数的两种形式(续)2、重载为友元函数重载为友元函数时,没有隐含的参数this指针,即不改变原有运算符的语法结构;下列运算符不能重载为友元函数:=、()、[]、->重载为友元函数

的运算符重载函数的格式:friend<类型说明符>operator<运算符>(<参数表>){……}8.1.3运算符重载函数的两种形式(续)例8.3:分析下列程序的输出结果。#include<iostream.h>classcomplex

{public:complex(doubler=0,doublei=0);friendcomplexoperator+(constcomplex&c1,constcomplex&c2);friendcomplexoperator-(constc

omplex&c1,constcomplex&c2);friendcomplexoperator-(constcomplex&c);voidprint()const;private:doublereal,imag

;};+运算符-运算符求负运算符8.1.3运算符重载函数的两种形式(续)complex::complext(doubler,doublei){real=r;imag=i;}complexoperator+(constcomplex&c1,constc

omplex&c2){doubler=c1.real+c2.real;doublei=c1.imag+c2.imag;returncomplex(r,i);}complexoperator-(constcomple

x&c1,constcomplex&c2){8.1.3运算符重载函数的两种形式(续)doubler=c1.real-c2.real;doublei=c1.imag-c2.imag;returncomplex(r,i);}complexope

rator-(constcomplex&c){returncomplex(-c.real,-c.imag);}voidcomplex::print()const{cout<<'('<<real<<','<<imag<<')'<<endl;}voidmai

n(){complexc1(2.5,3.7),c2(4.2,6.5);8.1.3运算符重载函数的两种形式(续)complexc;c=c1-c2;c.print();c=c1+c2;c.print();c=-c1;c.print();}输出(-1.7,-2.8)(6

.7,10.2)(-2.5,-3.7)c=operator-(c1,c2);c=operator+(c1,c2);c=operator-(c1);8.1.3运算符重载函数的两种形式(续)3、两种重载形式的比较一般情况下,单目运算符最好重载为成员函数;双目运算符则最好重载为友元

函数;有些双目运算符重载为成员函数较好:如:赋值运算符=;8.1.3运算符重载函数的两种形式(续)例8.4:指出程序中的错误,并改正。classinteger{public:integer(inti=0){value=i;}integeroperator+(in

tegeri){returninteger(value+=i.value);}private:intvalue;};voidmain(){integeri1=10;integeri2=i1+5;integer

i3=3+i2;}错误原因:3+i2等价于3.operator+(i2)8.1.3运算符重载函数的两种形式(续)改正:classinteger{public:integer(inti=0){value=i;}friendintegeroperator+(integeri1,inte

geri2);private:intvalue;};integeroperator+(integeri1,integeri2){integertemp=i1.value+i2.value;return

temp;}8.1.4运算符重载示例例8.5:++运算符。#include<iostream.h>classcounter{public:counter(){value=0;}counteroperator++();counteroperator++(int)

;voiddisplay(){cout<<value<<endl;}private:intvalue;};countercounter::operator++(){value++;前缀运算符后缀运算符8.1.4

运算符重载示例(续)return*this;}countercounter::operator++(inti){countert;t.value=value++;returnt;}voidmain(){counterc;c.display();

for(inti=0;i<10;i++)c++;c.display();后缀运算符变量值加1,表达式值不变前缀运算符变量值与表达式值均加1c.operator++(0);8.1.4运算符重载示例(续)for(i=0;i<10;i++)++c

;c.display();}输出01020c.operator++();8.1.4运算符重载示例(续)例8.6:下标运算符。#include<iostream.h>classvector{public:vector(intsize){v=newint[size];}~v

ector(){delete[]v;}int&operator[](inti);private:int*v;};int&vector::operator[](inti){returnv[i];}8.1.4运算符重载示例(续)voidmain(){vectora[5];fo

r(inti=0;i<5;i++)a[i]=i;a[2]=12;for(inti=0;i<5;i++)cout<<a[i]<<endl;}输出0112348.1.4运算符重载示例(续)例8.7:用函数调用运算符实现函数f(x,y)

=x*y+5。#include<iostream.h>classF{public:doubleoperator()(doublex,doubley)const;};doubleF::operator()

(doublex,doubley)const{returnx*y+5;}voidmain(){Ff;cout<<f(5.2,2.5)<<endl;}输出:188.2.1子类型化和类型适应1、子类型化子类型:有一个特定的类型S,当且仅当它至少提供了

类型T的行为时,称类型S是类型T的子类型;公有继承可以实现子类型;子类型关系是不可逆的;派生类S是基类T的子类型子类型关系可以传递;8.2.1子类型化和类型适应(续)例8.8:分析下列程序的输出结果。#include<iostream.h>class

A{public:voidPrint()const{cout<<"A::Print()called."<<endl;}};classB:publicA{public:voidf(){}};voidf1(

constA&r){公有继承,B是A的子类型8.2.1子类型化和类型适应(续)r.Print();}voidmain(){Bb;f1(b);}输出A::Print()called.问题:1、形参与实参类型不匹配?2、若类型匹配是正确的,输出结果是什么?类型适应8.2.1子类型

化和类型适应(续)2、类型适应类型S的对象能被用于类型T的对象所能使用的场合,称为类型S适应类型T。S*或S&适应T*或T&;S*或S&适应constT*或constT&;constS*或constS&适应constT*或constT&;类型适应的三种情况:说明此处的对象一般是指针或引用;8

.2.1子类型化和类型适应(续)3、子类型的作用一个函数可被重用于处理T类型的对象和T的各个子类型的对象,而不必为处理这些子类型的对象去重载该函数,可以减轻程序人员编写程序代码的负担;8.2.1子类型化和类型适应(续)例8.9:分析下列程序的输出结果。#include<io

stream.h>classA{public:A(){a=0;}A(inti){a=i;}voidPrint(){cout<<a<<endl;}intGeta(){returna;}private:inta;};classB:publicA{public:公有继承,B是A的子类型8.2

.1子类型化和类型适应(续)B(){b=0;}B(inti,intj):A(i),b(j){}voidPrint(){A::Print();cout<<b<<endl;}private:intb;};v

oidfun(A&d){cout<<d.Geta()*10<<endl;}voidmain(){Bbb(9,5);Aaa(5);aa=bb;8.2.1子类型化和类型适应(续)aa.Print();A*pa=newA

(8);B*pb=newB(1,2);pa=pb;pa->Print();fun(bb);}类型适应,B&适应A&输出9190调用A.Geta()*10;8.2.2静态束定例8.10:分析下列程序的输出结果。#include<iostream.h>c

lassPoint{public:Point(doublei,doublej){x=i;y=j;}doubleArea()const{return0;}private:doublex,y;};classRectangle:p

ublicPoint{public:Rectangle(inti,intj,intk,intl);doubleArea()const{returnw*h;}private:公有继承,Rectangle是Point的子类型8.2

.2静态束定(续)doublew,h;};Rectangle::Rectangle(inti,intj,intk,intl):Point(i,j){w=k;h=l;}voidfun(Point&s){cout<<s.Area()<<endl;}

voidmain(){Rectanglerect(3.0,5.2,15.0,25.0);fun(rect);}类型适应输出:0调用Point::Area();8.2.2静态束定(续)静态束定:在编译时进行的束定,即编译时就确定了程序中的操作调用与执行该操作代码之间的关系。8.

2.3动态束定动态束定:在程序执行时进行的束定;实现:C++动态束定在虚函数的支持下实现;动态束定的原因:当实现一个子类型时变动了其基类中相应行为的实现时,必须将这种变动告诉编译器,即进行动态束定;8.3虚函数1、虚函数虚函数是动态束定的基础;虚函数是非static成员函数;virtual<类型

说明符><函数名>(<参数表>)说明方法:含义:若类中一成员函数被说明为虚函数,则该成员函数在派生类中可能有不同的实现。当使用该成员函数操作指针或引用所标识的对象时,对该成员函数调用可采用动态束定方式。8

.3虚函数(续)操作方法:动态束定只能通过指针或引用标识对象来操作虚函数。如果采用一般类型的标识对象来操作虚函数,则将采用静态束定方式调用虚函数。8.3虚函数(续)例8.11:分析下列程序的输出结果。#include<iostream.h>classPoint{public:Point(

doublei,doublej){x=i;y=j;}virtualdoubleArea()const{return0;}private:doublex,y;};classRectangle:publicPoint{public:Rectangle(inti,intj,intk,

intl);virtualdoubleArea()const{returnw*h;}private:虚函数虚函数8.3虚函数(续)doublew,h;};Rectangle::Rectangle(inti,intj,intk,intl):Poi

nt(i,j){w=k;h=l;}voidfun(Point&s){cout<<s.Area()<<endl;}voidmain(){Rectanglerect(3.0,5.2,15.0,25.0);fun(rect);}输出:3758.3虚函数(续)例8.12:分析下列程序的输

出结果,并回答问题。#include<iostream.h>classA{public:virtualvoidact1(){cout<<"A::act1()called."<<endl;}voidact2()

{act1();}};classB:publicA{public:voidact1(){cout<<"B::act1()called."<<endl;}};公有继承,B是A的子类型虚函数8.3虚函数(续)voidmain(){Bb;b.act2

();}回答下列问题:(1)、该程序执行后的输出结果是什么?为什么?输出结果为:B::act1()called.原因:a.B从A公有继承,B是A的子类型;b.B中的act1()为虚函数;c.b.act2()调用A中的act2(),进一步调用act1(),产生动态束定,运行时选择B::

act1();为什么?8.3虚函数(续)(2)、如果将A::act2()的实现改为:voidA::act2(){this->act1();}输出结果是什么?为什么?输出结果与(1)相同,即:B::act1()called.原因:this指向操作该成员

函数的对象,基于与(1)相同的原因,此处调用B::act1()。8.3虚函数(续)(3)、如果将A::act2()的实现改为:voidA::act2(){A::act1();}输出结果是什么?为什么?输出结果:A::act1()called.原

因:此处增加了成员名限定,因此要进行静态束定,即调用的是A::act1()。8.3虚函数(续)动态束定实现的三个条件:要有说明的虚函数;建立子类型关系;调用虚函数操作的是指向对象的指针或者对象引用;或者是由成员函数调用虚函数;8

.3虚函数(续)派生类中对基类的虚函数进行替换时,要求派生类中说明的虚函数与基类中的被替换的虚函数之间满足下列条件:参数个数:与基类的虚函数有相同的参数个数;参数类型:与基类的虚函数的对应参数类型相同;返回值类型:与基类的虚函数的返回值

类型相同;都返回指针或引用,并且派生类虚函数所返回的指针或引用的基类型是基类中被替换的虚函数所返回的指针或引用的基类型的子类型;满足上述条件的派生类虚函数,可不加virtual说明。8.3虚函数(续)例8.13:分析下列程序的输出结果。#include

<iostream.h>classA{public:A(){}virtualvoidf(){cout<<"A::f()called."<<endl;}};classB:publicA{public:B(){f();}voidg(){f();}};构造函数调用虚函数一般成员函

数调用虚函数8.3虚函数(续)classC:publicB{public:C(){}virtualvoidf(){cout<<"C::f()called."<<endl;}};voidmain(){Cc

;c.g();}输出A::f()called.C::f()called.8.3虚函数(续)构造函数与析构函数调用虚函数:构造函数中调用虚函数时,采用静态束定,即构造函数调用的虚函数是自己类中实现的虚函数,如果自

己类中没有实现这个虚函数,则调用基类中的虚函数,而不是任何派生类中实现的虚函数;析构函数中调用虚函数同构造函数,即析构函数所调用的虚函数是自身类中的或者基类中实现的虚函数;BAC构造(析构)函数一般成员函数调用虚函数8.4.1纯虚函数1、引入在基类中不

能为虚函数给出一个有意义的实现时,可将其声明为纯虚函数,其实现留待派生类完成;2、作用为派生类提供一个一致的接口;3、声明格式class<类名>{virtual<类型><函数名>(<参数表>)=0;……}8.4.1纯虚函数(续)例8.14:分析下列程序的

输出结果。#include<iostream.h>classNumber{public:Number(inti){val=i;}virtualvoidshow()=0;protected:intval;

};classHextype:publicNumber{public:Hextype(inti):Number(i){}纯虚函数8.4.1纯虚函数(续)virtualvoidshow(){cout<<he

x<<val<<dec<<endl;}};classDectype:publicNumber{public:Dectype(inti):Number(i){}virtualvoidshow(){cout<<dec<<val<<endl;}}

;voidfun(Number&n){n.Show();}8.4.1纯虚函数(续)voidmain(){Dectyped(50);fun(d);Hextypeh(16);fun(h);}输出:5010要增加一种表示方式

:八进制表示方式,应如何8.4.1纯虚函数(续)//定义八进制表示方式类OcttypeclassOcttype:publicNumber{public:Octtype(inti):Number(i){}v

irtualvoidshow(){cout<<oct<<val<<dec<<endl;}};voidmain(){...Octtypeo(9);fun(o);}输出:5010118.4.1纯虚函数(续)例8.15:分析下列程序的输出结果。#include<iost

ream.h>classPoint{public:Point(inti=0,intj=0){x0=i;y0=j;}virtualvoidSet()=0;virtualvoidDraw()=0;protected:intx0,y0;};cl

assLine:publicPoint{public:8.4.1纯虚函数(续)Line(inti=0,intj=0,intm=0,intn=0):Point(i,j){x1=m;y1=n;}voidSet(){cout<<"Line

::Set()called."<<endl;}voidDraw(){cout<<"Line::Draw()called."<<endl;}protected:intx1,y1;};classEllipse:publicPoint{public:Ellipse(inti=0,intj=0,intp

=0,intq=0):Point(i,j){x2=p;y2=q;}voidSet(){cout<<"Ellipse::Set()called."<<endl;}8.4.1纯虚函数(续)voidDraw(){cout<<"Ellipse::Draw()ca

lled."<<endl;}protected:intx2,y2;};voidDrawObj(Point*p){p->Draw();}voidSetObj(Point*p){p->Set();}voidmain()8.4.1纯虚函数(续){Line*lineobj=newLine;Ellipse*

ellipseobj=newEllipse;DrawObj(lineobj);DrawObj(ellipseobj);cout<<endl;SetObj(lineobj);SetObj(ellipseo

bj);cout<<endl<<"Redrawtheobject…"<<endl;DrawObj(lineobj);DrawObj(ellipseobj);}8.4.1纯虚函数(续)Line::Draw()ca

lled.Ellipse::Draw()called.Line::Set()called.Ellipse::Set()called.Redrawtheobject...Line::Draw()called.Ellipse::Draw()called.执

行结果8.4.2抽象类带有纯虚函数的类称为抽象类;抽象类只能作为基类使用,其纯虚函数的实现由派生类给出;但派生类仍可不给出纯虚函数的定义,继续作为抽象类存在;抽象类不能定义对象,一般将该类的构造函数说明为保护的访问控制权限;抽象类的作用:用作基类:在一个继承层次结构中,提供一个

公共的根,并基于抽象类的操作设计出对抽象类所描述的一类对象进行操作的公共接口,其完整的实现由派生类完成;8.4.2抽象类(续)用作指针或引用的基类型:保证进入继承层次的每个类都具有(提供)纯虚函数所要求的行为;在成员函数内可以调用纯虚函数,但在构造函数或析构函数内不能调用纯虚函数(纯

虚函数没有实现代码);classA{public:virtualvoidf()=0;voidg(){f();}A(){f();}}正确错误8.4.2抽象类(续)例8.16:计算各类图形的总面积。//shape.h#if!defined(_SHAPE_H)#define_SHAPE_Hc

lassShape{public:virtualdoubleArea()const=0;};#endif//_SHAPE_H//program.h#if!defined(_PROGRAM_H)#define_PROGRAM_H8.4.2抽象类(续)#include"shap

e.h"classApplication{public:doubleCompute(Shape*s[],intn)const;};#endif//_PROGRAM_H//program.cpp#include"program.h"doubleApplication::Compute(Shap

e*s[],intn)const{doublesum=0;for(inti=0;i<n;i++)8.4.2抽象类(续)sum+=s[i]->Area();returnsum;}//polygon.h#if!defin

ed(_POLYGON_H)#define_POLYGON_H#include<iostream.h>#include"shape.h"classPolygon:publicShape{public:doubleArea()const=0;};8.4.2抽象类(续)classTrian

gle:publicPolygon{public:Triangle(doubleh,doublew){H=h;W=w;}doubleArea()const{returnH*W*0.5;}private:doubleH,W;};classR

ectangle:publicPolygon{public:Rectangle(doubleh,doublew){H=h;W=w;}doubleArea()const{returnH*W;}private:8.

4.2抽象类(续)doubleH,W;};classCircle:publicPolygon{public:Circle(doubler){radius=r;}doubleArea()const{returnradius*radius*

3.14;}private:doubleradius;};#endif//_POLYGON_H//ex816.cpp#include<iostream.h>8.4.2抽象类(续)#include"polygo

n.h"#include"program.h"classMyProgram:publicApplication{public:MyProgram();~MyProgram();intRun();private

:Shape**s;};MyProgram::MyProgram(){s=newShape*[4];8.4.2抽象类(续)s[0]=newTriangle(3.0,4.0);s[1]=newRectangle(2.0,5.0);s[2]=newCircle(5.0);s[

3]=newCircle(8.0);}MyProgram::~MyProgram(){for(inti=0;i<4;i++)deletes[i];delete[]s;}intMyProgram::Run(){doublesum=Compute(s,4);8.4.2抽象类(续)c

out<<sum<<endl;return0;}intmain(){returnMyProgram().Run();}输出293.468.4.2抽象类(续)ShapePolygonApplicationMyProg

ramTriangleRectangleCircleMyProgram()~MyProgram()Run()抽象类8.5虚析构函数虚析构函数在析构函数前加关键字virtual进行说明,则该析构函数称为虚析构函数;格式:classB{public

:virtual~B();…...}8.5虚析构函数(续)如果一个类的析构函数被说明为虚析构函数,则它的派生类中的析构函数也是虚析构函数,不管它是否使用了关键字virtual进行说明;目的:使用delete运算符删除一个对象时,能保证析构函数被正

确地执行;8.5虚析构函数(续)例8.17:分析下列程序的输出结果。#include<iostream.h>classA{public:virtual~A(){cout<<"A::~A()called."<<endl;}};classB:publicA{public:B

(inti){buf=newchar[i];}virtual~B(){deletebuf;8.5虚析构函数(续)cout<<"B::~B()called."<<endl;}private:char*b

uf;};voidfun(A*a){deletea;}voidmain(){A*a=newB(15);fun(a);}输出:B::~B()called.A::~A()called.进行动态束定,调用B的析构函数,而B是A的派生类

,进一步调用A的析构函数;8.5虚析构函数(续)问:若类A的析构函数不是虚析构函数,则输出结果是什么?为什么?会导致什么后果?输出结果为:A::~A()called.原因:此时进行静态束定,直接调用基类A的析构函数;后果:此时将

不能释放类B中的资源;说明:子类型化要求析构函数被声明为虚函数,尤其是在析构函数要完成一些有意义的工作时;构造函数不能被声明为虚函数;第9章C++的I/O流库9.1流抽象的继承结构9.2预定义的插入符与提取符9.3插入符和提取符的重载9.4磁盘文件的输入和输出9.5字符串流9.1流

抽象的继承结构1、流的基本概念流的引入流:数据从一个对象流动到另一个对象,这种流动抽象为流;流的操作:建立流、删除流、提取、插入2、C++流的继承结构iosstreambufistreamostreamiostream9.1流抽象的继承结构(续)ios:对流状态进行设置,虚基类;istrea

m、ostream、iostream:提取与插入;3、文件的继承结构fstreambasefilebufifstreamofstreamiofstreamstreambuf:提供对数据的缓冲支持;fstreambase:公共基类;9.1流抽象的继承结

构(续)ifstream、ofstream、iofstream:文件操作;4、字符串类filebuf:提供对上述类的缓冲支持;提供处理内部初始化字符序列的操作;istrstream:从序列中取字符;ostrstream:将字符放入序列;5、预定义的流cin:istream类对

象,处理标准输入,即键盘输入;9.1流抽象的继承结构(续)cout:ostream类对象,处理标准输出,即屏幕输出;cerr:ostream类对象,处理标准出错信息,提供不带缓冲区的输出;clog:ostream类对象,处理标准出错信息,提供带缓冲区

的输出;9.2.1预定义的插入符1、预定义插入符的格式ostream&ostream::operator<<(consttype&obj);其中:type为char、int、short、long类型和它们的unsigned和signed类型,以及float、doub

le、longdouble、char*和void*;2、说明一般情况下将插入符作用于cout对象;输出语句中可以串联多个插入运算符,输出多个数据项;9.2.1预定义的插入符(续)插入运算符后可以是任意复杂的表达式,系统可自动计算其值并传给插入符;指针类型的地址值,默认为十六进制显示

,用类型long强制后才可以十进制显示;字符指针的地址值的输出格式为:(void*)s或void*(s),此时仍为十六进制格式;9.2.1预定义的插入符(续)例9.1:分析下列程序的输出结果。#includ

e<iostream.h>#include<string.h>voidmain(){char*str="Hello";inta=100;int*pa=&a;cout<<"*pa="<<*pa<<endl;cout<<"&pa="<<&pa<<"or"<<l

ong(&pa)<<endl;cout<<"Thestringis"<<str<<endl;cout<<"Theaddressis"<<(void*)str<<"or"<<long((void*)str)<<endl;}字符指针字符指针地址地址值的十六进制表示方法地址值的十进

制表示方法9.2.1预定义的插入符(续)输出*pa=100&pa=0x0065FDECor6684144ThestringisHelloTheaddressis0x00426064or43500529.2.1预定义的插入符(续)3、使用put()输出一个字符ostream&o

stream::put(charc);4、使用write()输出一个字符ostream&ostream::write(char*buf,intn);说明:这些成员函数既可用于文本流,也可用于二进制流,尤其适用于二进制流;9

.2.1预定义的插入符(续)例9.2:分析下列程序的输出结果。#include<iostream.h>voidmain(){cout<<'a'<<','<<'b'<<'\n';cout.put('a').put(',').put('b').put('\n');charc1='A',c

2='B';cout.put(c1).put(c2).put('\n');}输出a,ba,bAB9.2.1预定义的插入符(续)例9.3:分析下列程序的输出结果。#include<iostream.h>#include<string.h>voidPrintString(char*s){cout.w

rite(s,strlen(s)).put('\n');cout.write(s,6)<<"\n";}voidmain(){charstr[]="IloveC++";cout<<"Thestringis:"

<<str<<endl;PrintString(str);PrintString("thisisastring");}9.2.1预定义的插入符(续)输出Thestringis:IloveC++IloveC++Ilovethisisastringt

hisi9.2.2预定义的提取符1、预定义提取符的格式istream&istream::operator>>(type&obj);其中:type为char、int、short、long类型和它们的uns

igned和signed类型,以及float、double、longdouble、char*;2、说明一般情况下将提取符作用于cin对象;输入语句中可以串联多个提取运算符,每个提取符后为一表达式,该表达式是获得输入值的变量或对象;9.2.2预定义的提取符(续)提取操作时,

空白符(空格、tab键、换行符)只用于字符的分隔符,而本身不作为从输入流中提取的字符;提取符可从输入流中读取一个字符串,该字符串是以空白符结束的一个字符序列,由系统自动加上'\0'字符;9.2.2预定义的提取符(续)例9.4:分析

下列程序的输出结果。#include<iostream.h>#include<string.h>voidmain(){constintSIZE=20;charbuf[SIZE];char*largest;intcurLen,maxLen=-1,cnt=

0;cout<<"Inputwords:"<<endl;while(cin>>buf){curLen=strlen(buf);cnt++;if(curLen>maxLen){9.2.2预定义的提取符(续)maxLen=curLen;largest=buf;}}cout<<endl

;cout<<cnt<<endl;cout<<maxLen<<endll;cout<<largest<<endl;}输入Inputwords:ifelsereturndowhilecontinue<ctrl+z>输出

68continue输入ctrl+z键后,cin>>buf的值为0,退出while循环;9.2.2预定义的提取符(续)3、使用get()获取一个字符istream&istream::get(char&c);intistream::get();4、使用getline()获取多个字符

istream&istream::getline(char*buf,intLimit,Deline='\n');说明:getline()最多可读取Limit-1个字符;getline()函数结束操作的条件:9.2.2预定义的提取符

(续)从输入流中读取Limit-1个字符后;从输入流中读取换行符或其他终止符后;从输出流中读取到文件或输入流结束符后;getline()通常用来读取一行字符:5、使用read()读取一串字符istream&istream::read(char*,int

);9.2.2预定义的提取符(续)例9.5:分析下列程序的输出结果。#include<iostream.h>voidmain(){charch;cout<<"Input:";while((ch=cin

.get())!=EOF)cout.put(ch);cout<<"OK!"<<endl;}输入abcxyz123<Enter><ctrl+z>输出abcxyz123<Enter>9.2.2预定义的提取符(续)例9.6:分析下列程序的输出结果。#include<iost

ream.h>voidmain(){constintS=80;charbuf[S]="";cout<<"Input…"<<endl;cin>>read(buf,s);cout<<endl;cout<<buf<<endl;}输出Input…a

bcdefghijkl输入:Input...abcd<Enter>efgh<Enter>ijkl<Enter>9.3插入符和提取符的重载1、重载为友元函数ostream&operator<<(ostream&s,con

sttype&p);istream&operator>>(istream&s,type&p);2、函数调用形式ostrm<<obj;等价于operator<<(ostrm,obj);ostrm<<obj1<<obj2;等价

于operator<<(operator<<(ostrm,obj1),obj2);istrm>>obj;等价于operator>>(istrm,obj);istrm>>obj1>>obj2;等价于operator>>(operator>>(istrm,obj1),obj2);

9.3插入符和提取符的重载(续)例9.7:分析下列程序的输出结果。#include<iostream.h>classDate{public:Date(inty,intm,intd){Year=y;Month=m;Day

=d;}friendostream&operator<<(ostream&stream,Date&date);friendistream&operator>>(istream&stream,Date&date);9.3插入符和提取符

的重载(续)private:intYear,Month,Day;};ostream&operator<<(ostream&stream,Date&date){stream<<date.Year<<"/

"<<date.Month<<"/"<<date.Day<<endl;returnstream;}istream&operator>>(istream&stream,Date&date){stream>>da

te.Year>>date.Month>>date.Day;returnstream;}voidmain()9.3插入符和提取符的重载(续){DateCDate(1999,10,22);cout<<"Curr

entdate:"<<CDate<<endl;cout<<"Enternewdate:";cin>>CDate;cout<<"Newdate:"<<CDate<<endl;}输出Currentdate:1999/10/22Enter

newdate:20011022Newdate:2001/10/229.4.1磁盘文件的打开和关闭1、打开文件voidfstream::open(constchar*fname,intmode,int=filebuf::openprot);fstream::fstream(c

onstchar*fname,intmode,int=filebuf::openprot);voidofstream::open(constchar*fname,intmode=ios::out,int=fi

lebuf::openprot);ofstream::ofstream(constchar*fname,intmode=ios::out,int=filebuf::openprot);voidifstream::ope

n(constchar*fname,intmode=ios::in,int=filebuf::openprot);ifstream::ifstream(constchar*fname,intmode=ios::in,int

=filebuf::openprot);9.4.1磁盘文件的打开和关闭(续)2、关闭文件voidfstream::close();voidofstream::close();voidifstream::close();9.4.1磁盘文件的打开和关闭(续)

例9.8:分析下列程序的输出结果。#include<iostream.h>voidmain(){ofstreamostrm;ostrm.open("f1.dat");ostrm<<120<<endl;ostrm<<310.85<<endl;ostrm.clos

e();ifstreamistrm("f1.dat");intn;doubled;istrm>>n>>d;cout<<n<<","<<d<<endl;istrm.close();}输出:120,310.859.4.2文本文件的读写操作例9.9:将文本写入指定的文件。#include<iostre

am.h>#include<fstream.h>#include<stdlib.h>voidmain(){fstreamoutfile;outfile.open("f2.dat",ios::out);if(!outfile){cout<<"f2.datcan'topen."<<endl;

abort();}outfile<<"thisisaprogram."<<endl;outfile.close();}退出程序9.4.2文本文件的读写操作(续)例9.10:从文本文件中读出信息。#include

<iostream.h>#include<fstream.h>#include<stdlib.h>voidmain(){fstreaminfile;infile.open("f2.dat",ios::in);if(!infile){cout<<"f2.datcan'topen."

<<endl;abort();}chars[80];while(!infile.eof())9.4.2文本文件的读写操作(续){infile.getline(s,sizeof(s));cout<<s<<endl;}infil

e.close();}输出thisisaprogram.9.4.2文本文件的读写操作(续)例9.11:使用get()和put()函数读写文件。#include<iostream.h>#include<fstream.h>#include<std

lib.h>#include<string.h>voidmain(){fstreamoutfile,infile;outfile.open("f3.dat",ios::out);if(!outfile){cout<<"f3

.datcan'topen."<<endl;abort();}charstr[]="thisisac++program.";9.4.2文本文件的读写操作(续)for(inti=0;i<strlen(str);i++)outfile.put(str[i]);outf

ile.close();infile.open("f3.dat",ios::in);if(!infile){cout<<"f3.datcan'topen."<<endl;abort();}charch;while(inf

ile.get(ch))cout<<ch;cout<<endl;infile.close();}输出:thisisac++program.9.4.2文本文件的读写操作(续)例9.12:将一文件内容拷贝到另一文件。#i

nclude<iostream.h>#include<fstream.h>#include<stdlib.h>voidmain(){fstreamoutfile,infile;infile.open("f2.dat",ios::in);if(!infile){

cout<<"f2.datcan'topen."<<endl;abort();}outfile.open("f4.dat",ios::out);if(!outfile)9.4.2文本文件的读写操作(续){cout<<"f4.da

tcan'topen."<<endl;abort();}charch;while(infile.get(ch))outfile.put(ch);infile.close();outfile.close();}

9.4.3二进制文件的读写操作例9.13:对一二进制文件进行读写操作。#include<iostream.h>#include<fstream.h>#include<stdlib.h>structperson

{charname[20];doubleheight;intage;}structpersonpeople[4]={"Wang",1.65,25,"Zhang",1.72,24,"Li",1.89,21,"Huang",1.70,22};voidmain(){fstreamoutfile,i

nfile;9.4.3二进制文件的读写操作(续)outfile.open("f5.dat",ios::out|ios::binary);if(!outfile){cout<<"f5.datcan'topen."<<endl;abort()

;}for(inti=0;i<4;i++)outfile.write((char*)&people[i],sizeof(people[i]));outfile.close();infile.open("f5.dat",ios::in|ios

::binary);if(!infile){cout<<"f5.datcan'topen."<<endl;abort();9.4.3二进制文件的读写操作(续)}for(inti=0;i<4;i++){outfil

e.read((char*)&people[i],sizeof(people[i]));cout<<people[i].name<<"\t"<<people[i].height<<"\t"<<people[i].age<<endl;}infil

e.close();}输出Wang1.6525Zhang1.7224Li1.6921Huang1.7229.4.4随机访问数据文件1、读文件指针istream&istream::seekg(streampos);istream&istr

eam::seekg(streamoff,ios::seek_dir);streamposistream::tellg();streampos为long型;seek_dir的值:cur=1,相对于当前读指针指定的位置;beg=0,相对于流的开始位置;end=

2,相对于流的结尾位置;9.4.4随机访问数据文件(续)2、写文件指针ostream&ostream::seekp(streampos);ostream&ostream::seekp(streamoff,ios::

seek_dir);streamposostream::tellp();9.4.4随机访问数据文件(续)例9.14:分析下列程序的输出结果。#include<iostream.h>#include<fstream.h>#include

<stdlib.h>voidmain(){structstudent{charname[20];longnumber;doubletotalscore;}structstu[5]={"Ma",97001,85.72,"Li",97002,92.62,"Hu",97003,89.

25,"Yan",97004,90.84,"Lu",97005,80.92};9.4.4随机访问数据文件(续)fstreamfile1;studentone;file1.open("f6.dat",ios::out|ios::in|ios::binar

y);if(!file1){cout<<"f6.datcan'topen."<<endl;abort();}for(inti=0;i<5;i++)file1.write((char*)&stu[i],sizeof(student));file1.seekg

(sizeof(student)*4);file1.read((char*)&one,sizeof(student));cout<<one.name<<"\t"<<one.number<<"\t"<<one.tota

lscore<<endl;file1.seekg(sizeof(student)*1);9.4.4随机访问数据文件(续)file1.read((char*)&one,sizeof(student));cout<<one.name<

<"\t"<<one.number<<"\t"<<one.totalscore<<endl;file1.close();}输出Lu9700580.92Li9700292.629.5.1ostrstream类1、功能将不同类

型的信息格式化为字符串,并存放到一个字符数组中;但ostrstream并不在输出流的末尾自动添加空字符,必须在程序中显式添加该空字符;2、格式ostrstream::ostrstream();ostrstre

am::ostrstream(char*s,intn,intmode=ios::out);9.5.1ostrstream类(续)例9.15:分析下列程序的输出结果。#include<iostream.h>#includ

e<strstrea.h>constintN=80;voidmain(){charbuf[N];ostrstreamout1(buf,sizeof(buf));inta=50;for(inti=0;i<6;i++,a

+=10)out1<<"a="<<a<<";"out1<<'\0';cout<<"Buf:"<<buf<<endl;doublepi=3.14159265;9.5.1ostrstream类(续)out1.setf(ios::fixed|ios::sho

wpoint);out1.seekp(0);out1<<"Thevalueofpiis"<<pi<<'\0';out1<<buf<<endl;char*pstr=out1.str;cout<<pstr<<endl;}输出Buf:a=50;a=60;a=70;a=80;a=90;a

=100;Thevalueofpiis3.14159265Thevalueofpiis3.141592659.5.2istrstream类1、功能将文本项转换为变量所需要的内部格式;2、格式istrstream::istrstream(ch

ar*s);istrstream::istrstream(char*s,intn)9.5.2istrstream类(续)例9.16:分析下列程序的输出结果。#include<iostream.h>#include<s

trstrea.h>voidmain(){charbuf[]="12345.67";inta;doubleb;istrstreamss(buf);ss>>a>>b;cout<<a+b<<endl;}输出168.679.5.2istr

stream类(续)例9.17:分析下列程序的输出结果。#include<iostream.h>#include<strstrea.h>voidmain(){charbuf[]="12345";inti,j;istrstreams1(buf);s1>>i;is

trstreams2(buf,3);s2>>j;cout<<i+j<<endl;}输出:12468

小橙橙
小橙橙
文档分享,欢迎浏览!
  • 文档 25747
  • 被下载 7
  • 被收藏 0
相关资源
广告代码123
若发现您的权益受到侵害,请立即联系客服,我们会尽快为您处理。侵权客服QQ:395972555 (支持时间:9:00-21:00) 公众号
Powered by 太赞文库
×
确认删除?