《C++程序设计》(第二版)教学指导书
——供任课教师使用
通常认为C++是一个非纯粹的面向对象的程序设计语言,因为它是从面向过程的C发展而来的,对它有种种诟病。然而编者认为符合ISO14882标准的C++语言是一种先进的面向对象与参数化程序设计语言。因而本教材从面向对象和参数化程序设计两个方面来展开C++程序设计的教学,这在编者所见过的教材中是仅见的(其他的教材是从面向过程和面向对象两个方面来展开教学),这也是本教材的教学体系先进所在。
本教材的出发点是:与时俱进和实事求是。与时俱进是指教学内容要跟上计算机技术的最新发展;实事求是是指教学要切合当今大学生的实际情况,切合本课程在大一同时开设的课程中的地位。
现有的C++的教材的教学体系大致可分两类:
第一类是经典的,按语法顺序讲授基本知识、面向过程的程序设计、基于对象的程序设计和面向对象的程序设计。
第二类是尽早进入面向对象的程序设计的讲授。侧重于面向对象。 本教材可属于第二类,但对教学体系做了全面的改革,力求建立全新的面向对象与参数化程序设计的C++教学体系。将最新发展的知识传授给学生,教学内容的选定以ISO14882 C++语言标准为基础。抛弃传统的C++教学面向对象内容以语法为主的教学模式,突出面向对象和参数化程序设计关键技术的教学,让学生获得面向对象C++程序设计的真实本领。这一全新的教学体系经过4年大范围的教学实践已经成熟。
本教材是通用教材,可以用于对计算机知识要求相当深入的专业,包括电类、机电一体化、计算机专业等等。尽管随后续课程组织不同,教学侧重有些不同,但本教材均可适用。
课程特色
第一,突出面向对象与参数化程序设计关键技术的教学:
强调类对象个性实现的关键技术——多态,包括重载(编译时的多态,包括函数与运算符重载)、层次结构中的同名覆盖与超载(运行时的多态)。在介绍函数时就引入函数重载,在教学刚涉及类对象时就引入运算符的重载。引入的越早应用的机会越多。普遍使用这些技术是面向对象的C++的标志。
强调参数化程序设计,突出模板相关内容的教学。不是将模板作为一种语法现象,而是作为一个有力的工具用于本教材所涉及的全部数据结构基本知识,包括顺序表、链表、栈、队、二叉树(可选)以及查找排序算法。同时介绍标准模板库的简单使用方法。
提倡完善的类对象封装,不仅封装数据和对数据的操作,而且封装资源(尤其是内存)的动态分配与释放,形成一个完备的子系统。在一个有层次
《C++程序设计》教学指导书 2
结构的类体系中内存的动态分配与释放最好封装在成员对象(聚合)中,如同使用标准的string字符串类那样。
介绍怎样在面向对象的程序设计中使用异常处理技术来处理一些很难查找甚至是无法避免的运行时错误和异常。
总之,不是泛泛介绍面向对象的C++的语法和框架,而是突出实用技术,包括完善的封装、派生、多态和模板,在构造函数中动态分配资源、在析构函数中释放资源和异常处理,这是面向对象的C++程序设计的精髓。
本教材要求学生能熟练应用多态(重载和超载),熟练应用模板,熟练应用派生。习惯在构造函数中动态分配资源、在析构函数中释放资源和异常处理的方式。
第二,强调算法,注意介绍有关于任何特定编程语言的算法概念和结构,即突出程序设计而不是语法。强调算法,不是忽视语法,而是不要繁琐的钻牛角尖的语法,我们要的是基本的常用的语法,但更多的是模仿。不是知道的语法越多,程序编得越好,而是自己动手编程越多,程序编得越好。
第三,培养面向对象程序设计能力。掌握怎样从客观事物中抽象出类来的方法。基础教学与实践教学相结合。在基础教学中采用Windows平台下的控制台方式(命令行方式)以突出编程能力的培养。在实践部分比较全面地学习标准的Windows图形界面编程。采用研究型学习进行课程设计。
本教材第二版做了大幅改动。首先例题和习题解答主体按三个熟练应用和一个习惯的要求,进行改编与重组,更加突出了面向对象C++程序设计的实用技术;采用统一的规范的编程模式,不再体现编程的多样性,降低了学习难度。其次是调整章节安排与精简教学内容。第三,为了更好地配合精讲多练,安排了二十九个(其中两个选做)与教学内容紧密相关的同步实验。第四,更新部分不完全符合ISO14882 C++语言标准的内容。
性质与任务
程序设计课程与数学、物理、外语等一样,是大学生的通识教育课程,也是从技术角度学习计算机的主要基础课,包括面向对象程序设计及最基本的数据结构和软件工程的知识。其任务是培养学生的面向对象的编程能力,也锻炼大学生的逻辑思维能力(计算机思维方式)。
学时安排
本教程建议授课时数48学时,习题课8学时,上机实验56学时(含课外上机)。另有课程设计(小型软件设计的实践环节),16学时加上机实验32学时(含课外上机)。
课堂教学内容
分四部分,共十二章。
第一部分,包括第一到第三章,是基础部分。介绍C++程序的基本组成和编写方法,函数的递归以及最简单的常用算法。这一阶段代码的编写方法,应该是课堂教学的重点。
《C++程序设计》教学指导书 3
第二部分,包括第四到第五章,是面向对象的入门部分。介绍类对象的组成,构造函数和析构函数的编写,运算符的重载,还有数组与指针的概念和两者之间的对应关系。本阶段逐步过渡到以算法为重点,逐渐淡化代码的编写细节。
第三部分,包括第六到第八章,是核心部分。介绍通用算法的编写方法,特别是数据结构的基本算法,和软件的再利用。第六第七两章采用模板技术,第八章采用继承与派生。一般来讲,本阶段以算法为重点,不再细讲代码的编写。
第四部分,包括第九到第十一章(标准模板库选读),是扩展部分。包括:输入输出重载、在构造函数中动态分配资源、在析构函数中释放资源和异常处理、以及标准模板库。本阶段主要学习面向对象的C++程序框架构造知识。
考虑到各专业相关课程配置不同,生源不同,授课内容可有筛选。选读内容,可以作为学有余力的同学的自学内容,做到因材施教。其实讲哪些内容,不讲哪些内容,教师完全可以自主掌握,关键是要学生掌握课程特色中所言的三个熟练应用和一个习惯。。
教学方法
现实情况是:第一,在大一的基础课程中,大学生将程序设计排在数学、外语和物理之后,不可能投入过多的精力,大学生如果要放弃某门课以保证集中精力通过其他课时,最不熟悉的本课程列在首位;第二,大学生对这门课学习的期望值很高,但对学习时可能遇到的困难估计不足;第三,本科生总学时数下降,尽管计算机课程重要性上升,但总学时不可能增加;第四,大学生上机实践的条件大大改善,特别有利于贯彻先进的精讲多练的教学思想。所以在学时有限和学生所投精力有限的条件下,围绕课程特色中提到的面向对象关键技术进行精讲多练是第一条。为此安排了29个同步实验。
一年级大学生认为应试教育天经地义,大学应延续中学的应试教育,不懂得主动学习。要培养大学生的自学能力。预习是本课程对学生的基本要求。
整个教学强调过程,知识积累的过程,能力培养的过程,使学生能快乐地学习。(但绝不是自学,实践经验指出,自学出来的学生实际应用时编出来的程序往往效率低,错误百出,而且别人看不懂,无法交流,原因就在不会规范化地编程。)一定要避免应试教育。教学要求高于考试要求,必须给学生真本事。考试可以多样化,面要广,难度要适中。切忌题目过偏过难,切忌出弯弯绕的语法题,要考学生编程的实际能力,这是大学的第一门计算机课,不要把多数学生的自信心毁了。
授课时要采用案例教学法,从实例引出语法,尽管书中多数地方为了阅读方便是将语法条文单列,甚至放在前面,但上课时不要顺序往下讲。
现在提供的电子教案只是参考,修改和重编的过程也是熟悉教学内容和发现问题的过程。要求每位老师自己做有自己个性的教案。
面向对象程序设计的原代码通常比较庞大,原因是数据与数据的操作封装在一起,原则上包含的操作要全面,正是众多的成员函数使学生认为自己面对的是一个庞然大物,吓也吓蒙了。教师应该指出成员函数是一个个的操作,每一个成员函数都是简单的。可以给学生讲讲庖丁解牛的故事,要
《C++程序设计》教学指导书 4
求学生做到目无全牛,也就是面向对象的程序要一个一个函数来编。
习题与实验都是学生的实践机会,具体指导和讲评是必要的。特别是在学生尚未入门时具体指导尤其重要,最好是在实验室里配大屏幕显示,学生与教师演示同步操作,或用同步操作的教学软件。如无此条件,至少教师在第二章和第三章课堂教学时多做控制台应用程序设计全过程演示。在辅导实验时只可能解决少数学生的少数问题,提倡上机时学生互相讨论互相帮助。做习题,也提倡较难的题可以同学之间先讨论再完成。也可以把部分习题的参先发给学生作为参考,毕竟我们要求规范化编程,主要是灵活应用通用算法,不是创造别人看不懂的算法,初级阶段主要是模仿,见多识广后就能编出好的程序。
讲评是在学生做完习题和实验之后,针对学生实际发生的最常见的错误进行的,也可以介绍一些同学的好作业,这是一个总结提高的过程。
面向对象C++程序设计的习题基本是在一个C++的类或类模板定义中添加一些成员函数。建议可以给学生提供完整的类定义和需添加的成员函数的声明,以及检测其功能的主函数代码,同时给出需添加的成员函数的思路或提示,仅让学生编写需添加的成员函数,可能效果会更好。建议将给教师使用的习题参,删去要求编写的函数,添加所需的说明,再发给同学去做习题,这样即降低了难度也突出了重点,便于学生调通程序,同时提高了学生的信心和学习的效率。
实验也可做类似的考虑。
该教学体系培养的学生所编的程序给人的第一影响应该是:这是规范的面向对象的程序。
对于需要计算机知识较多的专业,程序设计课程应考虑后续课程的需要。尤其是电气电子信息类专业的学生的后续课程中需要大量的面向过程的程序设计的基础知识,包括汇编语言的编程,单片机、嵌入式系统和DSP的C语言编程。面向对象的程序设计其实与面向过程的程序设计是密不可分的。在本课程中,算法的描述实际上是面向过程的,而面向对象是一种包装,它使程序的整体组织更合理,使用起来更方便。教学中应该合理地将两个方面有机地结合起来,即细节上算法的编程和程序总体上的把握并重。
在前三章的教学中对算法的描述,必须侧重于采用C++语言编程的细节的讲解,即培养学生对算法的编程能力。要求学生学会先分析算法,再画UML的活动图或普通的流程图,最后进行编程。特别是在“基本控制结构程序设计”和“函数”这两章的教学中要严格贯彻这一要求。
“类与对象”和“数组与指针”两章是过渡阶段,对编程的细节的讲授随教学推进而逐渐淡化,对程序的整体掌握的要求逐渐加强,使学生的编程能力上一个台阶。
从第六章起,算法表述的细节基本留给学生自己看,教师重点讲解脱离具体C++语言的算法本身的描述和程序整体的构造。一方面提高学生的自学能力,另一方面引导学生的编程的大局观。
要引导学生,不要做学生的尾巴,片面地降低要求。
《C++程序设计》教学指导书 5
目录
第一章 C++基础知识
第二章 基本控制结构程序设计 第三章 函数
第四章 类与对象 第五章 数组与指针 第六章 模板与数据结构
第七章 动态内存分配与数据结构 第八章 类的继承与派生 第九章 输入/输出流类库 第十章 异常处理
第十一章 标准模板库 (选读) 同步实验部分 课程设计部分 UML活动图 UML类图
疑难问题解答索引
C++类的构造函数的作用的讨论 const引用
forward与向后移动
if语句和switch语句的对应关系 Node类和List类的复制构造函数讨论 this指针的应用 try块应用
VC++的随机数
VC++与标准名字空间 编程中附加条件的安排 标准流文件结束符的使用 迟缓错误检测
重载提取和插入运算符 初学编程常见错误
传统运行库和新的标准库中的输入输出流的差异 传值调用与引用调用讨论 递归算法的讨论 调用树的使用 动态分配的讨论
数组与多级指针 构造函数与虚函数 关键字索引
函数返回值与返回引用
《C++程序设计》教学指导书 6
基类中protected成员的使用 静态变量
聚合的复制构造函数 矩形类讨论
类模板的插入和提取运算符的重载 类模板的派生的讨论 类型的转换
流类库中的控制枚举常量的定义 模板编译模式
模板与派生在实现通用性上的对比 默认参数函数 默认的构造函数 内存分配图 派生常见错误
派生类的插入和提取运算符的重载 使用多态完成定积分的通用性 矢量类的边界
输出运算符(插入运算符<<)的讨论 数字与数字串
顺序队列转化为循环队列的算法讨论 图解法阅读程序
图解法分析算法——Fibonacci级数 文件中的数据格式 下标运算符重载 前向引用声明
循环控制变量使用要点 虚函数、同名覆盖与重载 异常处理匹配原则
一个流文件一次只能与一个磁盘文件建立联系 引用的讨论 优先级与结合性
约瑟夫问题求解过程
状态字state及其使用技巧 指向类成员的指针 指针与数组的差异
《C++程序设计》教学指导书 7
第一章 C++基础知识
教学目的
使大学生了解C++数据和运算的表述方法,简单的输入输出流类,引入广义的数据类型的概念。
建议学时安排
授课5学时
初识C++程序,关键字与标识符,数据类型,变量与常量。2学时。 运算符、表达式和语句,数组。2学时。 简单的C++输入输出。1学时。
教学方法提示
从形式上看本章与面向过程的教材差异不大,但教学上要从面向对象的思维方式出发:数据类型总是与相关的算法封装在一起。不要说成算法是的。某种算法可以用于那些数据,这是面向过程的说法,并不切合实际。实际上不同数据类型是重载了运算符。例如加法,对于整型数,执行的是定点运算;而浮点数,首先要对阶。再如除法,对于整型数,执行的是整除,商为整数,另有求余数的运算;而浮点数,商为浮点数,没有求余数的运算。从不同数据有不同算法出发,很容易理解为什么要将函数附属于数据类型。这样考虑问题,学生易于接受面向对象的思想。
在本章中,存储方式的概念要建立起来,每一个变量总是在内存中分配有一块内存单元,不仅本章中的左值、表达式、字面常量和sizeof()与之有关,后面的算法的分析、函数的传值等等全与之有关。介绍数据的存储方式,并在教学的过程中经常利用它们,也是本书的教学特点之一。
基本数据类型,相关的算术、关系和逻辑运算,是本章最基本的知识点。 有关运算符的结合性是决定同一优先级的运算符组合在一起时的运算次序,同一优先级的运算符有相同的结合性。如+、-的结合性是从左到右(左结合),则a+b+c-d的运算次序为:
((a+b)+c)-d //先算a+b,再加c,最后减d
赋值号=的结合性是从右到左(右结合),则a=b=c+d的运算次序为:
a=(b=(c+d)) //算出c+d的和,赋给b,再将b赋给a
又如前++和单目负-的结合性是从右到左(右结合),则-++a的运算次序为:
-(++a) //先做++a,再取相反数(加负号)
这对算法描述是必须的,每一步做什么必须确定。
运算符++,学生往往没有真正理解,普通+的结果放在暂存器(累加器)中,而++的值要返回给变量,变量的值变了。画一个演示图应该好一些。要讲清楚a++与a+1的不同。在第五章的运算符重载教学中,++无返回值,+=有返回值,前者友元方式时参数要使用引用,学生不理解,说看不懂,细问发现不懂在++的运算过程。
《C++程序设计》教学指导书 8
简单的数组概念的建立对算法的描述是很重要的,所以提前在这里引入。强调3点:第一,数组下标从0开始,这与简化大量的算法有关,不从0开始,相关的典型算法往往是错的;第二,数组不能作为整体处理,只能按元素处理;第三,数组的初始化方法。
字符数组与字符串现在是要求能用,因为它的应用实在太广了。本章中只能讲清组织方式。特别是串结束符,有了它,串的处理方式与数组大为不同。教师必须在这里给学生点明,输出串是输出串内容,输出数组是输出数组首地址。
输入输出方式简介,强调会用。
输入方面:第一,提醒学生字符输入容易出错,特别是数字与字符混合输入时,回车符的吸收问题要交待清楚。第二要讲清cin和cin.get()的差异,cin会自动跳过所有空白符(包括空格、制表、回车等等与格式有关的符号),cin.get()不会。第三,输入一系列数字时,用空格或回车分隔,不可以用逗号分隔。第四,输入字符串使用cin.getline()的方法,提醒同学:它提取回车符但不保存回车符,而是加一个串结束符。
其他错误解答请参考本指导书第9章。
输出方面:格式控制演示交代清楚,学生就会理解。字符输出要提醒同学,左值输出的才是字符,表达式输出的是ASCII码值,因为在表达式中已转为整数。
第二版中,C++程序改用标准库头文件,无后缀.h,并加using namespace std。教师应作简单说明。
注意新标准要求main()函数必须返回int型,0为正常,否则返回其他整数。这样可以取代exit()等函数(exit()库函数通常在程序出错时用来退回到操作系统,详细见教材4.3.2节)。一般函数返回为int的也不可省略,否则编译器发出警告。
疑难解答
优先级与结合性
优先级高的运算先做怎样理解?请参见下式: a*b+(-d) &&++c 等效为:
(a*b+(-d))&&(++c) 完全按优先级,先做(-d),第2步做(++c),第3步作a*b,第4步做(a*b+(-d)),第5步做(a*b+(-d))&&(++c)。这是人工的做法。
计算机是从最低优先级开始,先处理最低优先级的“与”运算符左边的(因为与运算符的结合性是左→右)操作数(a*b+(-d)),括号内先做a*b,再做(-d),然后将两项相加;其次处理“与”运算符右边的操作数(++c);最后完成“与”操作。如果“与”操作符的左操作数为0,则直接完成与操作。即第1步做a*b,第2步做(-d),第3步做(a*b+(-d)),第4步做(++c),第5步做(a*b+(-d))&&(++c)。如果第3步结果为0,则第4步不做。 只要交换律成立,这两种处理方法是等效的。计算机处理表达式的优先级和结合性是借用操作数栈和运算符栈来完成的,参见教材 栈的应用。最后的运算次序就是首先将最低优先级运算符各操作数项按该优先级的结合
《C++程序设计》教学指导书 9
性依次处理,如果某操作数项为表达式,可同样按上面的规律处理。
设有表达式:a+++b,等效于(a++)+b还是a+(++b)?应该是前者,因为编译器在解释运算符时优先取尽可能长的符号。
而a++b是错误的表达式,改为a+(+b)才是正确的。
++a++,后++优先级高,等效于++(a++),是错的,因为(a++)后++未做,不是左值,不能进行前++。
而(++a)++是正确的,(++a)前++已完成,是左值,后++可以进行。 同样++++a是正确的,而a++++是错误的。
++-a是错的,因为第3优先级结合性是左→右,先做++,但-a不是左值。++(-a)也是错的,尽管(-a)优先级高,但结果是右值。
-++a,等效-(++a),是对的。---a,等效--(-a)是错的。而+++a,等效++(+a),表面是错的,但至少VC++中单目正被忽略,所以是对的。
(-a)++是错的,因为(-a)先做,(-a)不是左值。但-a++是对的,后++优先级高先做,a是左值。
既然后++是在表达式运算结束后做++,那么该运算符的优先级有何意义?并且比前++高一级?从前面的例子可见优先级是有意义的,而且前++优先级定为第3级是合理的,由于后++结合性是左→右,与前++不同,不可同列第3级,列第4级或单列一个级别也不合适,所以归入第2级。
VC++的随机数
VC++输出的随机数,并非随机数,而是特定值,如整型数,为0xcccccccc (-8593460),float型为-1.07374e+008,duoble型为-9.25596e+061。
关键字索引
and 运算符 代替&&; and_eq 运算符 代替&=; asm 运算符 用于标识混编的C++源程序中的汇编语言代码; auto 说明符 说明变量为自动变量,见主教材(下同)P.84; bitand 运算符 代替&; bitor 运算符 代替 |; bool 说明符 用于说明布尔型变量,见P.7; break 语句 结束循环语句或开关语句的执行,见P.50; case 标号 用于开关语句,见P.40; catch 语句 用于异常处理中捕获异常子句,见P.343; char 说明符 用于说明字符型变量,见P.6; class 说明符 用于说明类类型,见P.106; compl 运算符 代替~; const 、136、165、193和200; const_cast 运算符 常量强制类型转换运算符,详见后文; continue 语句 用于循环语句,结束本次循环,见P.52; default 标号 用于开关语句,表示其他情况,见P.40; delete 运算符 用于回收动态存储空间,见P.220; do 语句 用于循环语句,实现直到型循环,见P.44; double 说明符 用于说明双精度实
dynamic_cast else enum explicit export extern false float for friend goto if inline int; long ; mutable namespace; new not not_eq operator or or_eq private 267;protected public register reinterpret_cast return sizeof short; signed static static_cast struct switch template this throw true; try typedef typeid typename
《C++程序设计》教学指导书 10
运算符
动态强制类型转换运算符,详见后文;; 语句 用于组成双分支结构条件语句,见P.34; 说明符 用于说明枚举类型,见P.59;
说明符 取消构造函数隐式数据类型转换的功能,见P.357; 说明符 用于模板分离编译模式,见本指导书第六章模板; 说明符 说明变量为外部变量,见P.84; 逻辑值 对应0,表示逻辑假,见P.7;
语句 用于一种当型循环语句,见P.46;
说明符 用于说明友元函数和友元类,见P.135和139; 语句 用于无条件转移程序执行次序,见P.52; 语句 用于组成双分支结构条件语句,见P.34; 说明符
用于说明内联函数,见P.96和110;
说明符 使const对象和成员函数的数据成员可修改,见后文; 运算符 用于分配动态存储空间,见P.220; 运算符 代替 !; 运算符 代替 !=;
说明符 用于定义运算符重载函数的函数名,见P.130; 运算符 代替 ||; 运算符 代替 |=;
说明符 访问限定或派生方式和267; 说明符 访问限定或派生方式和267;
说明符 说明变量为寄存器变量,见P.84;
运算符 重解释强制类型转换运算符,详见后文; 语句 作为函数返回语句,见P.2和69;
运算符 计算类型或变量占用内存的字节数,见P.19; 说明符 用于说明有符号整型变量,见P.7; 说明符 说明静态变量或类的静态成员和139; 运算符 静态强制类型转换运算符,详见后文; 说明符 用于说明结构类型,见P.142;
语句 用于组成多分支结构开关语句,见P.40; 说明符 用于参数化程序设计的模板构成,见P.186; 说明符 指向具体类对象本身的指针,见P.166; 说明符 用于异常处理中抛出异常,见P.342; 语句 用于异常处理测试程序块,见P.342; 说明符 用于类型定义,见P.173;
运算符 运行时类型信息运算符,详见后文;
说明符
用于模板定义时类型参数的说明,见P.186;
《C++程序设计》教学指导书 11
union unsigned using virtual void volatile wchar_t while xor xor_eq 说明符 说明符 说明符 说明符 说明符 说明符 说明符 语句 运算符 运算符 用于说明联合类型,见P.144; 用于说明无符号整型变量,见P.7;
用于using声明或using指示符,见P.145; 用于说明虚基类或虚函数,见P.278或2; 用于说明无值类型,见P.7;
修饰函数,表示表示该函数不做编译优化,见P.356; 宽字符类型,见P.6;
用于循环语句,见P.43和44; 代替 ^; 代替 ^=;
强制类型转换及其他运算符说明如下: static_cast 静态强制数据类型转换。格式如下:
double a;
int k;
k=static_cast 特别注意a本身的类型不变,仍是双精度,所有转换都是数值而非变量的转换。允许在两种类型之间进行标准转换(如void*转换为char*,int转换为float等等),包括允许基类指针到派生类指针的向下类型转换。但不能在const类型和非const类型转换之间转换,不能在非公有派生的基类和派生类的指针(引用)间转换,不能转换为无相应构造函数或转换运算符的类型。 const_cast 专用于转换const或volatile类型。格式如下: char *string_copy(char*); const char *p_str; …… char *pc=string_copy(const_cast 注意:const_cast不能用于强制转换常变量的常量性。 mutable 在const对象和成员函数中,用mutable修饰的数据成员也 可以被修改。使用格式如下: class student{ int id; mutable string name;//使name可以被修改 public: void setname(string Name)const{name=Name;};//合法 …… }; int main(){ const student stu1; …… } 《C++程序设计》教学指导书 12 reinterpret_cast C++利用重解释转换进行非标准转换(void*转换为int*、int* 转换为char*等等)。重解释转换不可用于标准转换,如duoble到int的转换。该转换可能导致严重的运行时错误。 dynamic_cast 动态强制类型转换运算符用于运行时多态,也就是说用于派 生体系,而且基类至少包含一个虚函数。它可以沿继承树向上(向基类)或向下(向派生类)进行类型转换,改变指针(引用)的类型。使用时包含 protected: int b; base(int x=0){b=x;} virtual void vertFunc(){} void show(){cout<<\"基类:b=\"<class derv:public base{ int d; public: derv(int x=0,int y=0):base(x){d=y;} void show(){cout<<\"派生类:d=\"< base* pb; derv* pd=new derv(23,29); pb=dynamic_cast pb->show();//输出 基类:b=23 转换遵从赋值兼容规则 pb=new derv(31,37); pd=dynamic_cast return 0;//输出 派生类:b=31,d=37 } 它可以将指向某派生类对象的基类指针转换为该派生类指针,如类型有误,返回NULL,可进而判断对象属于哪一种派生类。 在VC++中需要使用运行时类型识别(RTTI)必须选择Project|setting项,单击标签C/C++,在Category列表框中选择C++ Language,选中“Enable Run-Time Type Information”,单击OK。 typeid 作为运行时类型信息运算符可获得未知对象类型的信息。这 些信息放在type_info类中(其确切定义与编译器有关): class type_info{ type_info(const type_info&);//不再有默认构造函数 type_info& operator=(const type_info&); public: virtual ~type_info(); 《C++程序设计》教学指导书 13 } type_info& operator==(const type_info&) const; type_info& operator!=(const type_info&) const; const char* name()const;//返回type_info对象所表示类型的名字 用户不能在自己的程序中定义type_info对象。在程序中创建type_info对象的唯一途径是使用typeid运算符。如: employee *pe=new manager; cout< 第二章 基本控制结构程序设计 教学目的 使大学生掌握基本控制结构程序设计的C++语言实现和常用算法(递推、迭代、穷举、筛选等)。 建议学时安排 授课6学时,习题课2学时 分支结构程序设计:if 语句,条件运算符“?:”,switch语句。2学时。 循环结构程序设计:while语句,do…while语句,for语句,循环的嵌套;转向语句。2学时。 常用算法应用实例。枚举类型定义,输入输出文件简介。2学时。 教学方法提示 本章是用C++语言来描述程序的3种基本结构和介绍常用算法,这是学生编程的入门,也是重点。必须给学生指明,算法就是对数据的操作方法,让学生始终处于面向对象程序设计的氛围中。 在本章教学中要求学生养成先分析算法,再画流程图或UML的活动图,最后进行编程的良好习惯。在本章和下一章的教学中要严格贯彻这一要求。现在不严格要求,形成随意直接编程的坏习惯,要改也难了。 不要指望在本章中让学生入门,他们还是在门口徘徊,要有几个反复。到第4章学完,站得高了,回过头来看,才能比较深刻地理解,才会初步入门。千万不要在这里堆砌过多的学时,学生总会说不懂,要跟他说大胆的往前走。实际上后面的教学内容不断反复使用3种基本结构,在这个过程中学生会逐步熟练起来的。 教师在第二章和第三章课堂教学时应多做控制台应用程序设计全过程演示。学生调试程序的能力主要在这时培养,不仅是C++,而且以后“微机系统”等后续课程用Debug调试各种程序(汇编,C51,DSP的C语言编程)的基本功都是在这里打下的。辅导上机,主要指导学生怎样跟踪程序的运行,怎样设置被监视的变量,怎样设置断点,怎样单步运行,怎样使用Debug 《C++程序设计》教学指导书 14 工具。授之以鱼,不如授之以渔。帮同学调通一个程序,不如教会同学怎样去调程序。 讲解3种基本结构时不要按书上的顺序一个一个地讲,建议把相关的语句对比起来讲解。比如3种循环语句对比起来讲,效率和效果都要好得多。这样学时是绰绰有余。而有关switch语句,后讲不加break的例2.9。 注意不要按C语言的习惯,在for语句括号表达式中定义控制变量。教师首先不要那样做。理由在下一章介绍。 本章的例题非常多,并不是要题题全讲,那样学时是不够的,能讲70%就很多了。编书的出发点是多给学生一些各种编程类型的样板。 学习编程与算法有些象外语,模仿是重要的第一步。有同学特别希望有编程题的标准答案,这是错误的,标准答案是不存在的。但有一个规范化编程样板,对初学者却是非常重要的。大学生往往忽视了这一点,教师应该时时提醒。比如实验四中的约瑟夫算法,由于学生不愿模仿,把数组0号元素放弃,从1号开始,结果书上提供的算法就全错了,陷入死循环。学生不善于模仿,不要去批评他,碰了钉子,学生才有更深的印象。例题源程序有主教材例题和同步实验范例两类,课堂教学完全可以任意选择。非精讲的程序要求学生一定要自己读懂,见多识广,水平才能提高;正如外语只抠精讲部分,不看泛读,考试能对付,水平肯定高不了。 本教材强调算法,不是忽视语法,而是不要繁琐的转牛角尖的语法,不要让这种语法把学生的时间占光了,我们要的是基本的常用的语法,更多的是要模仿。 不是知道的语法越多,程序编得越好,而是自己动手编程越多,程序编得越好。从本章起,开始有大量的实验,一定要抓紧。 为了突出算法,本书主要采用命令行程序设计,教师在本章和下一章课堂教学时应多做控制台应用程序设计全过程演示,否则学生做实验时会卡在工程文件的建立上,而且错误是五花八门不胜枚举,使学生信心大减。 输入输出文件简介,放在这儿是一个创新。就是让学生先用起来,知道文件可以保存数据,并依样使用文件。文件输入输出用两个例子分别对同一个文件进行写入和读取,让同学切实体会到程序退出后信息确实没有丢失。 注意:教师认为文件引入很自然,但学生还没这个概念,认为屏显示与文件保存没什么两样,“数据丢失,大不了再算一遍”,请教师务必讲清楚。 疑难解答 初学编程常见错误 最常见的初级错误有:赋值号(=)与比较运算的相等号(==)混淆,循环语句中两个分号误为逗号,使用了数学符号而不是运算符等等。 在条件语句和循环语句中最常见的错误之一是不会使用复合语句。如: if(a<=b) temp=a;a=b;b=temp; 其目的是当a<=b时,交换a、b两变量,学生误认为将实现交换的三条语句放在同一行就可以了。但if语句后面跟的仅是一条语句,必须将三条语句用花括号括起来。而且学生发现的错误是有时输出的b是随机数,他始终想 《C++程序设计》教学指导书 15 不到是错在未用复合语句。 再如,被改为: int i=0; ifstream baiji; baiji.open(\"baiji.txt\"); do{ baiji.get(a[i]); i++; }while(a[i]!='\\n'); 其原意是从baiji文件中读取一行文字。当读完一行,a[i]中放了换行符,但随后i++,判断循环条件时,比较的是数组的下一个元素,陷入死循环。而原例题判断是在i++之前。学生不认为这里会错,而奇怪我的步骤没错,怎么文件读不出来? 常用算法应用实例是一个总结提高,有利于开拓学生的思路、提高编程的能力。这几道题都是经典的。 注意:文本文件阅读可以用的程序很多,常见的有,记事本、写字板、Word、Explorer、Visual Sdutio等文本编辑器,其中记事本对程序生成的没有回车的文本往往无法读出。学生往往只用记事本读程序生成的文本文件,有时发现只有黑方块,但用自编的程序读出却完全正确,于是用了大量时间去查错,不知是记事本的要求不同。 注意:当文本文件中既有文字又有数字时,请在文字后加回车(或endl),否则用自编程序读文件时可能会出错,因为文本文件中数字是用数字串存储的,提取数据时系统自动将串转换为数,有回车时从哪里开始转换十分清楚。 if语句和switch语句的对应关系 if语句和switch语句有一个呼应关系。if语句的嵌套方式对应于有break的switch语句,它实现互不相干的分段,每次仅处理一个分段;而并列的if语句对应没有break的switch语句,它依次处理各个分段,但各分段的方法不同。前者例子很多,后者见书上例2.9和习题2.4。 习题 编程计算个人所得税。个人所得税率表如下:月收入1200元起征,超过起征点500元以内部分税率5%,超过500元到2,000元部分税率10%,超过2,000元到5,000元部分税率15%,超过5,000元到20,000元部分税率20%,超过20,000元到40,000元部分税率25%,超过40,000元到60,000元部分税率30%,超过60,000元到80,000元部分税率35%,超过80,000元到100,000元部分税率40%,超过100,000元部分税率45%。 #include } return 0; } else income-=1200; if(income>20000){//收入超过起征点20000以上各段,使用不包含break的switch语句 k=income/20000; switch(k){ default: tax+=(income-100000)*0.45;income=100000; case 4: tax+=(income-80000)*0.40;income=80000; case 3: tax+=(income-60000)*0.35;income=60000; case 2: tax+=(income-40000)*0.30;income=40000; case 1: tax+=(income-20000)*0.25;income=20000; } } if(income>5000){ //收入超过起征点20000以下各段,使用并列的if语句 tax+=(income-5000)*0.20; income=5000; } if(income>2000){ tax+=(income-2000)*0.15; income=2000; } if(income>500){ tax+=(income-500)*0.10; income=500; } tax+=income*0.05; cout<<\"应征所得税:\"< 循环控制变量使用要点 循环语句中的循环控制变量最好不要参加运算,即使参加也只能参加不改变其本身值的运算,如习题2.11求完全数解答中的求某数的因子的运算不会改变原数的值。这是规范的做法。除前面常见错误小节中举的读文件的例子外,下面再看同步实验练习5.2求水仙花数学生的解法: #include 《C++程序设计》教学指导书 17 } for(j=0;j<=2;j++){ //将该数分解为各位数字,并求各位数字的立方和 a[j]=i%10; i=i/10; k=k+a[j]*a[j]*a[j]; } if(i==k) cout<return 0; 成了死循环,原因是循环变量参加的运算改变了循环变量本身的值。应该添加一个变量m来代替i参加将该数分解为各位数字的运算。 #include 学生在初学编程时,对题目提出的各种条件应如何实现心中无数,必须多加指点。如穷举法采用循环,对须剔除的情况,应在循环体内用条件语句实现,并使用continue语句,不可用break。有同学将附加条件放在循环条件中,当然出错。例如:0到4五个数字,组成五位数,每个数字用一次,但十位和百位不能为3(当然万位不能为0),输出所有可能的五位数。 #include n=i*10000+j*1000+k*100+l*10+m; cout< 《C++程序设计》教学指导书 18 } } } } return 0; } 学生往往会把排除条件放在循环条件中,不知道应放在循环体中,并用continue跳过,不再输出。如(为了简单,不考虑每个数只用一次): int main(){ int i,j,k,l,m,n,count=0; for(i=1;i<=4;i++) for(j=0;j<=4;j++) for(k=0;k<=4&&k<>3;k++) //k到3循环就停止,4没做 for(l=0;l<=4&&l<>3,l++) //同上 for(m=0;m<=4,m++){ n=i*10000+j*1000+k*100+l*10+m; cout< 标准流文件结束符的使用 例2.13采用函数 int cin.get(),当键盘输入Ctrl+z时它可以返回文件结束符EOF,即-1,其后输入的内容全部忽略。但是Ctrl+z必须在的一行的开头输入,否则Ctrl+z被忽略。而函数cin.get(字符变量)中的字符变量(实参)不能取得文件结束符,并发生输入流出错,以后跳过该语句,进入死循环(参见第9章)。但可以如下使用: cout<<\"输入一段文本(无空行):\"< 函数cin.get(字符变量)的返回值是该输入流对象cin的引用(参见第九章),该函数作为循环条件放在while语句中,实际是以函数的返回值流对象cin的引用作为循环条件,当键盘输入Ctrl_z后,标准输入流结束,循环条件为假,循环结束。详见本指导书第九章的解释。 实践教材实验四范例2也可同样处理: while(cin.get(c)){ //c必须为字符型 switch (c){ case '0': case '1': case '2' :case '3' :case '4': case '5': case '6': case '7' :case '8': case '9': 《C++程序设计》教学指导书 19 } } nDigit++; break; case ' ': case '\\n': case '\': nWhite++; break; default: nOther++; break; 这两题的源代码在教学资源例题源代码Ex_2_13_1和同步实验范例源 代码Exp_4_2_1中。 图解法阅读程序 程序设计能力的培养包括两个方面:编写程序能力和阅读程序能力。图解法在读懂程序方面可以带来很大方便,跟踪程序执行,实际上是填写变量表格完成的。参照的解答。 图解法分析算法——Fibonacci级数 是已知Fibonacci级数的公式来编程的,如果不知道,可以用图解法。安排四个变量:兔子总数s,成年兔数a,一月兔数b,初生兔数c;则由下图可以得到具体算法。下图每一个变量的内存图中列出的数据从右到左按月变化,绘制时也可以从左到右按月变化,比变量表格方便。 321100a:成年兔211010b:一月兔321101c:初生兔853211d:兔子总数a=a+b;//本月成年兔为上月一月兔与成年兔之和b=c; //本月一月兔数量为上月初生兔c=a; //本月初生兔数量为本月成年兔(每对生一对)s=a+b+c; //本月兔总数代入初值:a=0; b=0; c=1; 就可以进行递推。 第二种考虑,安排三个变量:本月兔子总数i,上个月兔子总数j,前个月兔子总数k,前个月兔子总数恰好是本月可生育兔子的数量,即本月新生兔子数量,所以本月兔子数等于上月兔子数加前月兔子数之和。列出递推算法,即: k:前月总数321100j:上月总数532110i:本月总数853211 k=j; //新的一个月来临,应将原前个月的兔子总数更新为原上个月的总数 《C++程序设计》教学指导书 20 j=i; //原上个月的兔子总数更新为原本月的总数 i=j+k; //新的一个月最终兔子总数为上月兔子数加前月兔子数之和 与Fibonacci级数的公式一样。 第二种考虑方式,也可以安排两个变量:本月兔子总数m,上个月兔子总数n。进入新的一月,新本月兔子数等于原本月兔子数m加原上月(可生育)兔子数n之和,新本月兔子总数放入临时变量temp,更替上月兔子数为原本月兔子数,再用temp更新本月兔子数。列出递推算法,即: temp=m+n;n=m;m=temp; 以上3种方式,列在前面的更加易读易懂。 数字与数字串 例2.23为什么要用字符输入二进制序列,不用数字,学生不理解。教师应加说明,对应数字,cin输入数字串是自动转换为数字的,也就是本例题的算法已经包含在cin对象中。 也可以输入数字,变成字符保存,参见下例:请输入一组数字,它们是一字符串的ASCII码,将它们转换为字符串保存。 #include 约瑟夫问题求解过程 n 个人围坐成一圈,从 1 开始顺序编号;游戏开始,从第一个人开始由 1 到 m 循环报数,报到 m 的人退出圈外,问最后留下的那个人原来的序号。 《C++程序设计》教学指导书 21 1Jose[8]1数组元素值为1代表在局182以数组取代11环形队列11731011数组下标取115代序列编号111i=(i+1)%800出局 参见上图,本题首先要定义一个数组,其元素个数为n(n定义为常变量,以便定义数组)。数组下标代表参加者编号,由0开始。数组元素的值标识该人是否出局,1在圈内,0出局。值为0的元素不参加报数。 数组是线性排列的,而人是围成圈的,用数组表示要有一种从数组尾部跳到其头部的技巧,即下标加1除以n求余数。 可用一个整型数k做计数器,采用倒计数,记录留下的人数。 上图n=8,m=4,i为数组下标,代表参加者编号。数组右二图表示2人出局,左图3人出局,注意下标i加到8因求余运算自动回到0。 #include } else i=(i+1)%n; //否则仅下标加1,寻找下一个有效数组元素 } } k--; } cout<<\"最后留下的是第\"<循环语句A是关键,该代码比较复杂,原因是一般报数和出局的算法差异大,表述困难。可用break语句简化。 while(1){ if(jose[i]==1)//元素值为1,该员在局 if(j==m){//报数到m,元素清0,退出 jose[i]=0; cout<如果计数从0开始则程序稍有变化。 for(k=n;k>=1;k--){ j=0; while(1){ if(jose[i]==1){ j++; if(j==m){ jose[i]=0; cout<第三章 函数 教学目的 函数是作为C++程序的基本模块出现,使大学生具备有关函数较全面的知识,对变量的作用域和存储方式有深刻理解,重点与难点是递归算法。了解和使用多文件组织、工程文件及运行库函数。 《C++程序设计》教学指导书 23 建议学时安排 授课5学时,习题1学时 函数定义与调用,函数的参数传递,返回值及函数声明,全局变量与局部变量,函数调用机制。2学时。 函数的递归调用。2学时。 函数重载,默认变元,内联函数。1学时。 作用域,变量的存贮类型,生命期与可见性。1学时。 教学方法提示 要求讲出面向对象的特色,即对数据的操作总是封装在函数中,一个函数描述一种操作。不要完全按模块思想讲。也是让学生处于面向对象程序设计的氛围中。这样第4章的教学会顺利一些。 本章有两个重点:函数的基本使用方法和函数的递归调用。 函数的基本使用方法要求熟练掌握,特别是传值、在函数中重新分配内存的思想,一定要讲透彻。教材中画了不少堆栈内存分配图,就是为了帮助学生理解。这个思想有了,局部变量,生命期等等非常重要的思想都很容易理解。当然在教学中,后者也进一步帮助学生理解在栈中重新分配内存的思想,相辅相成。 其实在面向对象的程序设计中对函数参数的传递的要求大大降低,绝大多数情况下,直接访问数据,如同在第2章main()函数中描述算法。 全局变量与局部变量、作用域与存储类型是对函数的深入理解,这些知识对函数的安全应用是关键的,对第4章封装性的理解也是重要的基础。 关于块域有一点需要说明:在VC++中,只认花括号,for语句括号表达式中定义的变量在for语句之外仍有效,如在for语句外加花括号,则只在for语句之内有效。所以不要按C语言的习惯,在for语句括号表达式中定义控制变量。 递归调用是一个难点,学生学习起来会有困难。其表述则非常简单,写出来的程序易读易懂。但学生自己编就会错误百出,还是不善于模仿,授课时要让同学多模仿。有错不怕,只有遇到挫折,解决了,才会有成就感,就会信心大增。有些问题用递归设计算法非常简单,用其他方法则十分困难甚至现在编不出来。但也要指出递归消耗了大量的内存空间和运行时间,可用Fibonacii例演示,把它改成计算前40项,就非常明显,耗时按指数上升。并与第二章的递推法Fibonacii例运行相比较。要指出:递归法中循环是隐式的,递推法中循环是显式的。 函数的一些高级议题是为第4章服务的,仅选用了与之相关的内容。 函数重载与第4章中的运算符重载是面向对象程序设计的关键技术之一。在教学次序上现在是尽可能前移,原因之一是模板(参数化程序设计)教学已经大大前移,而模板必须使用重载。 《C++程序设计》教学指导书 24 默认变元(参数)在构造函数中应用非常频繁,其他方面也是常用的。一个参数只能在一个文件被指定一次默认实参,习惯上,默认参数在公共头文件包含的函数声明中指定,否则默认实参只能用于包含该函数定义的文件中的函数调用。 内联函数的本意是:在源程序中原地扩展。在普通的面向过程程序设计中原地扩展是调用处有一份副本,好像宏一样;在第4章类对象定义处可看作原来使用指针指向共享的一份成员函数,现在是原地扩展,自己有一份的副本。但内联只是一个建议,编译器有权决定是否这样做。 头文件与多文件结构可简单地讲一下,学生自学为主,称为泛读。应用是很多的,主要是在后面的应用中掌握。 本书中有两个呼应,课堂教学与实验呼应,实验滞后一些,同时起复习巩固作用;另一个就是课本中前后内容呼应,不指望学习一次完成。这样对学生效果要好一些,教师要辛苦一些。 疑难解答 静态变量 静态局部变量何时建立的是一个有趣的问题。实际上在生命期的教学内容中已经指出:静态生命期(Static extent或Static storage duration)指的是标识符从程序开始运行时就存在,具有内存空间,到程序运行结束时消亡,释放内存空间。所以与全局变量一样是程序开始运行时建立,如有显式初始化,则在第一次进入该静态局部变量所在局部域时进行唯一的一次初始化。 内存分配图 使用内存分配图来分析函数调用的结果是很有效的,参见下面的例子: .6 设有函数说明如下: int f(int x, int y){ return x%y+1; } 假定a=10,b=4,c=5,下列语句的执行结果分别是 (1) 和 (2) 。 (1) cout< amain()域:10xf(a,b)域:10xf(a,c)域:10bc5f(a,b)返回值3f(a,c)返回值14y4return x%y+1;y5return x%y+1; (2)答案:5 《C++程序设计》教学指导书 25 amain()域:10bc5xf(f(a+c,b),f(b,c))返回值:5yreturn x%y+1;54f(f(a+c,b),f(b,c))域:xf(a+c,b)域:xf(b,c)域:415y44return x%y+1;y5return x%y+1; .7下列程序的输出结果分别为 (1) 和 (2) 。 (1)