第15章 C程序上机调试指导及实验

更新时间:2024-05-31 08:46:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

第15章 C程序上机调试指导及实验

C语言是编译型语言,用它写的程序在运行前必须经过编译、连接等过程,对于语言学习者来说,实验环节尤其重要,学习者必须通过上机实验操作才能加深对书本上理论知识的理解,才能融会贯通,培养程序调试能力是取得良好上机实验效果的关键。这章主要就Borland Turbo C 3.0来介绍C程序上机实验及程序调试的有关知识。

15.1 Turbo C 3.0系统简介

Turbo C 3.0是BORLAND公司在1990年推出的面向对象的程序设计平台。它既可编译运行面向对象的C??程序(扩展名为cpp的源程序文件),又可编译运行ANSI标准的C程序(扩展名为c的源程序文件)。

Turbo C 3.0提供了—个功能齐全、使用十分方便的开放式集成开发环境(Integrated Development Environment),它集程序的编辑、编译、连接、调试和运行为一体,具有速度快、效率高、功能强、能与已有的Turbo C代码高度兼容等优点,为用户开发软件提供了理想的环境。

1.系统运行环境

Turbo C 3.0可运行于IBM PC系列的计算机上,其中包括XT、AT和PS/2,以及其他与IBM PC兼容的计算机。Turbo C 3.0要求DOS 2.0以上版本的操作系统支持和至少640K的内存。监视器为80列即可。对机器外部存储器最低的要求是一个硬盘驱动器和一个软盘驱动器。Turbo C 3.0支持鼠标器。

2.Turbo C 3.0的安装

Turbo C 3.0的系统文件可以存放在三张1.2M字节的高密软盘上。在1#安装盘上有一个名称为INSTALL.EXE的自动安装程序,运行它后,用户可以在安装向导的指引下完成Turbo C 3.0系统的安装。

安装时该程序将检查计算机硬件并且适当地配置Turbo C 3.0系统,根据需要创建目录并且把文件拷贝到硬盘上。安装操作是自动进行的,在需要配合时安装程序会提醒用户应做的操作。

安装Turbo C 3.0按下列步骤操作:

(1) 把1号安装盘插入到驱动器A,在windows环境下,运行A盘中的install.exe文件。 (2) 当显示安装屏幕(如图15-1所示)时,按回车键。 (3) 按照屏幕提示操作,完成余下的安装过程。

安装完毕之后,INSTALL程序将提醒你阅读README文件,这个文件中包含有Turbo C 3.0的最新信息。

? 287 ?

图15-1 Turbo C 3.0安装界面

15.2 Turbo C 3.0的集成开发环境使用简介

15.2.1 Turbo C 3.0的启动

假设Turbo C 3.0系统安装在E:\\TC30目录下。在Windows环境下,运行E盘TC30目录下的BIN子目录下的TC.EXE文件,就可以启动Turbo C 3.0。

系统启动界面如图15-2所示。

图15-2 Turbo C 3.0的启动界面

15.2.2 Turbo C 3.0 IDE的基本操作

Turbo C 3.0集成开发环境包含窗口(window)、菜单(menu)、对话框(dialog box)和状态行(status line)。其中菜单又分为下拉菜单和弹出菜单两种。

在集成开发环境下,一般有三种工作状态,它们分别是编辑状态、菜单状态和编译程序信息查询状态。工作时光标可在编辑窗、菜单窗和编译程序信息窗跳转。编辑窗是编辑C源程序文件的窗口;菜单项命令可用于设置环境选择项或指示Turbo C 3.0去执行某个任务,如装入一个C源文件、编译一个C程序等;编译程序信息窗用来显示编译和连接程序过程中出

? 288 ?

现的各种出错信息,编译和连接过程中的每一个警告或出错信息都将在该窗口列出。

1.进入菜单状态及工作窗口转换

按F10进入主菜单状态。在主菜单状态下,移动光标键可定位要选择的菜单,按Enter键完成选择,或者用高亮度的大写字母直接选择一个主菜单选项。大多数主菜单选项还会有自己的下拉子菜单,一些子菜单还含有弹出式菜单,用光标键可对它们进行选择(用户也可以通过鼠标器来完成选择操作)。

按Alt和主菜单选项的首字母(F、E、S、R、C、D、P、O、W、H)可直接进入主菜单状态,并选中相应主菜单选项。例如,按Alt?F可进入File选项子菜单。

按ESC键退出菜单状态,并进入前次工作状态。

按F6可完成多个窗口(如编辑窗口与编译程序信息窗口)之间的切换。

按Alt?F5可从集成开发环境屏幕切换到输出屏幕,以便查看程序运行输出结果。在输出屏幕按任意键后返回集成开发环境屏幕。

按F5可以放大/缩小当前活动窗口。

当要退出Turbo C 3.0返回DOS,选择File选项下的Quit或直接按Alt?X。若是要暂时退出Turbo C 3.0环境,进入DOS环境运行DOS命令,可以选择File选项下的DOS shell。 要从DOS环境下重新回到Turbo C环境,在DOS提示符下键入Exit回车即可。

2.源程序的编辑及用户文件操作

使用F3键或File选项下的Open命令,可以装入一个已存在的C源文件,也可用File|New来创建一个新的源程序文件。

在编辑窗口,用户可以输入或编辑修改源程序代码。Turbo C 3.0的集成开发环境为用户提供的编辑器专用于编辑源程序文件。它可以完成文本的插入、删除、块操作、查找、替换等编辑操作。集成开发环境编辑器的编辑命令与一些常用文字编辑软件类似。

用户编辑的源文件及编译连接过程中产生的目标文件和可执行文件可以指定存放在某个盘的某一目录下,通过File选项下的Change dir可以选择用户文件目录。

编辑好的C源文件可以存盘,存盘的方法是按F2键或File选项下的Save。 3.编译、连接并运行C程序

在集成开发环境下,建立一个新的可执行文件的一般步骤为:

(1) 设置工作环境以指示编译程序和连接程序在何处寻找和保存有关文件。

工作环境的设置是在主菜单下选择Options选项,从而指定Turbo C的标准包含文件、 库文件、输出文件以及源文件所在的路径。例如,若Turbo C 3.0安装在E盘根目录下的TC30子目录下,我们选择Options选项下的Directories,然后将Include directories和Library directories分别设置E:\\TC30\\include和E:\\TC30\\lib。如设置错误,将导致编译程序和连接程序找不到所需文件而出错。

(2) 把相应的C源文件装入到编辑窗中。如果一个C程序由多个源文件组成,则需建立一个由所有源文件组成的工程文件。

(3) 对C程序进行编译并连接,产生目标文件和可执行文件。

对编辑窗中编辑好的C源文件进行编译连接的方法是按F9键,或选择Compile菜单下的Make EXE file,该过程将产生.OBJ目标文件和.EXE可执行文件。

编译连接过程如果出现错误,这些错误信息将显示在编译程序信息窗内,同时把含有该错误的源文件行用高亮度显示。查出错误后,按F6键从编译程序信息窗进入编辑窗对C源

? 289 ?

文件进行修改,修改好后再次进行编译并连接,直到编译连接通过。这一过程是程序调试的语法错误排除阶段。程序中存在的错误有语法错误和逻辑错误两大类。所谓程序调试是指发现和修改程序中存在的错误的过程。

编译连接通过后,程序才可以运行,运行的方法是按Ctrl?F9或Run菜单选项下的Run。在运行时,如果IDE发现C源文件没有编译连接,则自动完成编译、连接和运行的全过程。对编译连接所建立的可执行文件也可以回到DOS命令提示符下运行。

在集成开发环境下运行程序后,可以按Alt?F5或Window菜单项下的User screen将集成开发环境屏幕切换到输出屏幕,查看程序运行的输出结果,在输出屏幕按任意键后可返回集成开发环境屏幕;也可以选择Windows菜单项下的Output子菜单,打开一个观察输出结果的窗口,该窗口与其他窗口之间可通过F6键进行切换。

仔细分析运行结果,如果运行结果不正确,则需返回去再次检查并修改程序。这一过程是程序调试的逻辑错误排除阶段。

一个C程序正确运行通过后,可以继续编辑、编译、连接、调试、运行另一个C程序。

15.2.3 Turbo C 3.0的热键

热键也称为快捷键,即能够立即完成某一功能的键。表15-1列出了Turbo C 3.0的最常用的一些热键。其他的热键读者可以在联机帮助文档中找到相关使用说明。

表15-1 Turbo C 3.0的最常用的热键

键 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10

File|Save File|Open Run|Go to Cursor Window|Zoom Window|Next Run|Trace Into Run|Step Over Compile|Make EXE

菜单项

Help|Contents

显示帮助窗口

保存活动窗口中编辑的文件 显示打开文件对话框 执行程序到光标所在行 放大(或还原)活动窗口 在所有已打开窗口中循环切换 调试程序,并跟踪进入函数内部 调试程序,并单步执行完函数调用

将活动窗口中的文件或项目编译为EXE文件 选择主菜单

功能

15.2.4 Turbo C 3.0的主菜单

Turbo C 3.0 IDE的菜单结构由主菜单和下拉菜单组成。主菜单共有11个菜单项(包括≡、File、Edit、Search、Run、Compile、Debug、Project、Option、Window、Help),每个菜单项都有一个下拉菜单。下拉菜单中的菜单命令又分为三种:命令后有省略号(?)的,表示选择此命令后将出现一个对话框;命令后面是一个箭头的,表示选择该命令将展开另一级菜单;没有这两者的菜单项选择时直接完成集成开发环境中的一个操作命令。

15.3 Turbo C 3.0的菜单命令

1.文件子菜单(File)

使用File菜单中的相关命令项可以在IDE下打开多个编辑窗口,完成对多个源程序文件进行创建、保存、修改及打印等操作,以及转到DOS状态和退出Turbo C 3.0 IDE。如图15-3

? 290 ?

所示。

New:打开一个新的编辑窗口,用于创建一个新的文件。 Open:打开一个文件选择对话框,用于选择要编辑的文件。 Save:对当前活动编辑窗口的文件进行存盘。 Save all:将所有编辑窗口的文件进行存盘。 Change dir:设置当前工作驱动器和工作目录。

DOS shell:临时退出IDE,进入DOS状态;要从DOS状态回到IDE,键入exit即可。

Quit:退出TC 3.0。 2.运行子菜单(Run)

图15-3 File菜单

运行子菜单如图15-4所示。Run菜单提供了执行用户程序、跟踪调试程序及结束调试状态等功能。

Run:运行程序。

Program reset:撤消当前的调试操作,释放分配给被调

图15-4 Run菜单

试程序的内存空间,关闭被调试程序已打开的所有文件。

Go to cursor:不设置永久断点,使程序运行至光标所在

行。若运行时在光标所在行之前遇到永久断点,就停止执行,此时只有再用此命令。

Trace into:逐条语句执行(每按一下F7执行一条语句),并且跟踪进入调用函数内部。 Step over:逐条语句执行(每按一下F8执行一条语句),并且不跟踪进入下一级调用函数。 Arguments:允许用户输入程序运行的DOS命令行参数。 3.编译子菜单(Compile)

编译子菜单可以编译活动编辑窗口内的文件,或者make、build一个项目。如图15-5所示。

Compile:将当前活动编辑窗内的文件编译为obj目标文件,但不连接。

Make:将已定义项目文件或当前活动编辑窗口内文件的源程序编译并连接为EXE文件。

该命令只有在程序不是最新时,即被改动过才重新编译并连接。

Link:将obj文件连同必要的库文件(.lib)进行连接得到EXE文件。 Build all:编译并连接项目中的所有文件,而不论这些文件是否修改过。 4.选择项子菜单(Options)

选择项子菜单可以设置集成环境的工作方式。主要包括编译器、连接器、库和包含目录、程序运行时参数的设置等。如图15-6所示。其中Directories(目录)项用于告诉Turbo C到哪里寻找要编译、连接的文件、源文件以及设置输出文件所在的目录。详细操作项如下:

Include directories:包含目录。用于回答包含文件所在的路径。若Turbo C 3.0安装在E盘根目录下的TC30子目录下,则回答

图15-6 Options菜单

图15-5 Compile菜单

E:\\tc30\\include;若装在D盘根目录下的MYDIR目录下的TC子

? 291 ?

目录下,则回答D:\\mydir\\tc\\include。

Library directories:库文件目录。回答库文件所在的路径,若Turbo C安装在C盘根目录下的TC子目录下,则回答C:\\tc\\lib。

以上两个操作如回答错误将导致编译程序和连接程序找不到所需文件而出错。

Output directories:输出目录。设定保存编译和连接时所生成的.obj和.exe文件的路径。 5.调试子菜单(Debug)

该菜单主要针对程序调试工作而设,其下拉菜单如图15-7所示。

Inspect:打开Data Inspect对话框,这时可以输入一个要查看或修改其值的数据项或表达式。

图15-7 Debug菜单

Evaluate/modify:对一个变量或表达式求值,并显示

其结果,而且可能的话还允许修改此值。

Call stack:打开包含有调用栈的对话框。其中的CallStack窗口将把程序中执行到当前函数时的函数调用序列显示出来。

Watches:包含所有控制监视点的命令。 Toggle breakpoint:设置或清除断点。

Breakpoints:打开一个可以控制断点使用的对话框。 6.其他子菜单

Turbo C 3.0的主菜单系统还包括系统子菜单(≡)、编辑子菜单(Edit)、搜索子菜单(Search)、工程子菜单(Project)、窗口子菜单(Window)、帮助子菜单(Help)等,分别如图15-8(a)?(f)所示。

(d) 工程子菜单

(e) 窗口子菜单

(f) 帮助子菜单

(a) 系统子菜单

(b) 编辑子菜单

(c) 搜索子菜单

图15-8 Turbo C 3.0的其他子菜单

(1) ≡提供IDE的刷新及转移程序的设置操作功能。

(2) Edit菜单提供剪切(Cut)、拷贝(Copy)及粘贴(Paste)选择块等功能,供编辑窗内编辑文件时使用。

? 292 ?

(3) Search菜单提供在文件中查找、替换文本及查找函数定义等操作功能。

(4) Project菜单提供包括所有工程项目管理命令,如建立一个项目文件、在项目中增加或删除一个文件,设置工程项目选择项,查看项目中某个文件用到的include文件等。

(5) Window菜单提供对所有窗口的管理操作功能。 (6) Help菜单主要提供联机系统帮助功能。

15.4 C程序调试

15.4.1 程序的两种主要错误

1.语法错误

语法错误是指违背语法规定的错误,亦称为编译连接错误。编译连接程序通常能发现这类错误,并给出“出错信息”和出错位置。因此,这类错误比较容易发现并排除。

编译连接错误有致命错误(fatal error)、错误(error)、警告(warning)和连接错误(link error)。致命错误是指导致编译失败的重大错误;错误是指程序编译时出现的所有错误;警告是指编译器对程序中一些不妥当的用法提出的警告,此时,程序虽可以编译连接成可执行文件,但一个正确的程序不仅要求没有错误,能够执行,而且最好在编译时没有警告;连接错误是指在连接时出现的错误,经常是由于函数名书写错误或者连接程序无法找到应该连接的函数而造成的。

2.逻辑错误

逻辑错误是指程序并无语法错误,但不能得到正确的执行结果,由于这类错误具有隐含性,亦称为隐含错误。这是由于程序设计人员编写的程序代码与原意不相同,出现了逻辑上的混乱。例如:

sum?0; i?l;

while (i??10)

sum?sum?i; i??;

该段程序无语法错误,本意是计算1至10的自然数之和,但while语句却仅仅告诉Turbo C:当i??10时执行“sum?sum?i;”语句,而没有包括“i??;”语句。像这种隐含错误就较难发现,这就需要使用一些调试方法和调试技巧。

3.常见错误类型

下面列举了一些初学者常易犯的错误类型,供读者参考、借鉴。 (1) 忘记定义变量。C程序要求对所有的变量都必须先定义后引用; (2) 要输入、输出的数据类型与所用格式说明不一致; (3) 使用scanf函数时忘记使用地址符; (4) 混淆“?”与“??”两个符号的区别; (5) 语句后漏添分号或在不该加的地方加了分号; (6) 复合语句忘记加花括号;

(7) 引用数组元素时误用了圆括号,或将数组的长度当成可引用的最大下标值; (8) 用数组名来替代数组的所有元素,混淆字符数组与字符指针的区别;

? 293 ?

(9) switch语句中漏写break语句; (10) 混淆字符与字符串的表示方法; (11) 没有弄清形参与实参的作用范围; (12) 混用不同类型的指针。

15.4.2 调试程序的一般过程及其调试方法

1.程序调试的一般过程

(1) 人工检查。写好程序后应对程序进行人工检查,找出由于设计人员疏忽而造成的大多数错误。

(2) 上机调试。通过编译连接程序发现语法错误,可根据编译连接时在编译程序信息窗内提示的出错信息,帮助找出程序出错之处。需说明的是,有时提示的出错行并不是真正出错的行,往往出错行在上面的一、二行中。

(3) 运行分析。程序编译连接通过了,并不保证程序完全正确,因为逻辑错误是编译连接程序发现不了的。因此我们应当运行程序,输入程序所需数据,对得到的运行结果进行认真分析,看它是否符合要求。若不符合,则可能是程序有逻辑错误。检查这类错误我们可采用下面的几种方法:

① 检查流程图是否有错,即算法是否正确; ② 对照程序流程图,推敲程序代码是否写错;

③ 在程序适当位置加入一些printf函数调用语句,输出一些中间结果,观察是否正确,逐步缩小错误所在区间;

④ 利用系统提供的debug对程序流程进行跟踪观察。 2.程序调试的方法

Turbo C集成调试器提供了一些易学易用的调试手段,包括:单步跟踪,设置断点,观察计算表达式的值,使用调用栈等。在程序调试过程中,往往是多种方法联合使用,才能把错误排除。

(1) 利用Turbo C集成调试器进行调试的几个条件:

① 在Options|Debugger对话框中将Source debugging开关置为on,这是一个源代码调试开关,设为on时才能使用Turbo C 3.0的集成调试器。

② 要将Options|Compiler|Advanced Code generation中的Debug info in OBJs选中,该设置使得调试信息能放入目标文件中。

③ 建议将Options|Compiler|Entry/Exit Code对话框中的Standard stack frame选中,建立标准栈结构,否则有可能阻止调用栈查看某个函数。

(2) 调试方法:

① 单步跟踪。所谓单步跟踪就是让程序逐条语句执行,每执行一条语句后就将控制权交给调试人员。单步跟踪又分为跟踪进入函数和单步执行两种,分别通过按F7或选择Run选项下的Trace into和按F8或选择Run选项下的Step over来实现。

② 设置断点。用户可以在某条语句位置设置一个断点,当程序执行到该语句时便会停下来,以便调试。断点的设置有永久断点和临时断点两种。永久断点的设置和关闭是通过选择Debug选项下的Toggle breakpoint或直接按Ctrl?F8来实现;临时断点的设置通过选择Run选项下的Goto cursor或直接按F4来实现,临时断点不需要关闭。

? 294 ?

在调试程序时,程序运行出现死锁或陷入死循环,可以使用Ctrl?Break中止程序的运行回到编辑器。

在程序运行中遇到断点时,程序将暂停执行并且回到编辑窗口,光标定位于设置了断点的语句上,若按Ctrl?F9,程序将继续执行;按Ctrl?F2中止调试。

③ 查看调用栈。通过选择Debug选项下的Call stack或直接按Ctrl?F3来实现查看调用栈,利用调用栈来观察函数调用、参数传递以及返回顺序,非常直观有效。

④ 观察和计算表达式的值。在调试过程中经常要观察某些变量、表达式的值,以了解它们的当前取值及变化规律。

通过选择Debug选项下的Evaluate或直接按Ctrl?F4,可以实现观察、计算、修改某个临时表达式,它包括计算域、结果域和新值域三部分。利用它可以对一个临时的表达式或变量的内容进行修改,以观察其变化后的结果。

在调试过程中,如果要随时监视变量或表达式的内容变化,可以使用Debug下的Watches选项进行设置。

3.调试实例

例15.1 输入一行字符,分别统计其中字母、空格及其他字符的个数。 /* 本例仅作调试教学用,它包含了一些错误。*/ #include ?stdio.h? main() {

char c;

int letter?0, space?0, other?0; printf(?Input a string: ?); while (c?getchar()! ??\\n?);

if (c???a? && c???z? || c???A? && c???Z?) /* 判断是否为字母 */

letter??; /* 字母计数 */ else if (c?? ?) /* 判断是否为空格 */

space??; /* 空格计数*/ else /* 否则为其他字符 */

other??; /* 其他字符计数 */

printf(?letter??d, space??d, other??d?, letter, space, other); }

用F9键将该程序编译,信息窗出现两个警告(Warning)信息,提示主函数的第七行和第十行可能是不正确的赋值。首先分析第七行,它的作用应该是:读入一个字符并赋给变量c,然后判断它是否不等于?\\n?。由于运算符! ?的优先级高于运算符?,因此该行的作用实际上是:先判断读入的字符是否不等于?\\n?,然后将判断的结果(“真”或“假”,即1或0)赋给变量c。将表达式c?getchar()!??\\n?改为(c?getchar())!??\\n?即可。再来分析第十行,它的作用应该是:判断变量c是否等于空格。由于此处混淆了赋值运算符?与关系运算符??,因此只需将“?”改为“??”即可。

改正后,再用F9键将该程序编译,信息窗没有出现任何信息。按Ctrl?F9运行,出现提示信息“Input a string: ”后输入一行字符“as 4 @#”后回车,即:

Input a string: as 4 @#?CR?

? 295 ?

运行后,按Alt?F5我们看到的结果是letter和space均为0,而other为1。显然这个结果是错误的,正确的结果应该是:letter、space、other分别为2、2、3。我们用Run下的Step over(F8)或Trace into(F7)来单步跟踪该程序的执行,发现第8行至第13行的if语句只执行了一次“other??;”语句,没有重复执行。由此可以怀疑是循环结构有问题,仔细检查发现第七行行尾有一分号,说明循环语句的循环体为空语句,而不包含下面的if语句。改正的办法是:将第七行行尾的分号删除。

改正后,再用Ctrl?F9运行,按如下方式输入: Input a string: as 4 @#?CR?

运行后,按Alt?F5我们看到的结果是:letter、space、other分别为2、2、3,这个结果是正确的。

例15.2 从键盘输入两个变量的值,然后交换它们的值,并且分别输出交换前后的值。 /* 本例仅作调试教学用,它包含了一些错误。*/ #include ?stdio.h? void temp(int *a, int *b) {

int t; t?a; a?b; b?t; }

void main() {

int i; j;

scanf(??d?d?, i, j);

print(?\\nbefore: i??4d, j??4d?, i, j);

temp(&i, &j);

printf(?\\nafter: i??4d, j??4d?, i, j); }

按F9对它进行编译并连接,发现在编译程序信息窗内有一些错误(Error)信息。一个错误提示为:“Undefined symbol ?j?”,经检查发现main函数的第二行“int i; j;”有错,应改为“int i, j;”才对;另一个错误提示为:“Function ?print? should have a prototype”,很显然是由于printf函数书写错误引起的,改正以后编译连接可以通过了,但还有两个警告信息。

我们先试着运行它,按Ctrl?F9,输入i、j的值,然后按Alt?F5观察运行结果,发现结果不正确。程序错在哪里呢?

我们发现第一个printf语句输出的i、j的值就不正确,这说明scanf 函数调用语句有问题,仔细检查原来是该语句变量i、j前少了地址符&。改正后再编译运行,第二个printf语句输出结果仍然不对。因此一定是temp函数有问题,我们用Run下的Step over(F8)或Trace into(F7)来单步跟踪该程序的执行。按F8使程序执行到scanf函数,然后输入两个数,如2和5。按F8或F7继续,当亮条移动到temp函数时,按Alt?F5可观察到屏幕上输出了第一个printf语句执行的结果,按任意键回到调试状态。此时改按F7跟踪temp函数内各条语句的执行,当执行到赋值“a?b;”后,按Ctrl?F4调试程序打开一个包含三个域的对话框,即计算域、结果域、新值域,在计算域输入t回车后,在结果域得到的值不为2而是一个四位的

? 296 ?

十六进制数,这可能是一个地址值。回到程序我们发现原来在这三个赋值语句中混淆了a、b与*a、*b的区别,前者是指针变量,它们的值是所指向变量的指针(即地址),后者才是所指向变量的值(该错误也可通过在程序中设置printf语句来发现,这个留给大家去实践)。

纠正这个错误后,程序就能得到正确的运行结果,至此该程序的调试工作就完成了。 上面仅是两个简单的示例,复杂的大型程序的调试工作要比它繁琐得多。读者应养成勤思考、多研究的习惯,在实践中不断积累经验。

15.5 上机实验设计

实 验 概 述

任何一门计算机语言的学习都离不开上机实践这一重要环节,C语言的学习也不例外。学习者应该培养独自编写程序及上机调试程序的能力。学习C语言应该保证至少有32小时的上机时间,与理论授课时间比最好能达到1:1。

1.实验目的

实验的目的不仅是为了验证理论课学到的知识和自己所编写的程序是否正确,更重要的在于:

(1) 熟悉计算机系统的操作方法。通过对C语言(Turbo C 3.0)程序开发环境的了解掌握,为以后学习掌握新的计算机语言开发环境打下基础,起到举一反三、触类旁通之功效。

(2) 巩固、加深对理论课的理解。教材上的一些语法规定,听起来可能枯燥无味,而通过多次上机,反复实践就能够自然地、熟练地掌握它们。

(3) 培养上机调试程序的能力。程序从编写到能够正确运行,必须经过调试这一重要环节。调试能力的高低往往取决于经验,学习者虽可以借鉴他人已有的经验,但更重要的是通过自己动手,直接实践来学习积累。学习者应变被动学习为主动学习,即除了将自己编写的程序上机调试至能正确运行外,还可对一些正确的程序人为设置一些错误和障碍并进行调试,观察并分析出现的问题,从中获得更多的收获。

2.实验步骤

对于每次上机实验,一般要求按以下步骤进行:

(1) 准备工作。上机前应将本次实验的源程序准备好,即在纸上清晰、工整的书写好源程序,并手工检查无误(如大小写、标点符号有无错误等)再上机,以提高上机的效率,对有疑问处可在纸上作上记号,以便上机实验时观察、研究。

(2) 上机输入源程序及调试程序。在TC的集成开发环境的编辑器中输入源程序,然后开始编译调试,注意当出现问题的时候,你应先自行处理,多多思考,以培养独自分析并解决问题的能力,如确实解决不了,再向旁人请教。

(3) 书写实验报告。上机完成后,应按以下几方面来整理实验报告: ① 实验题目及源程序清单。 ② 运行结果及发现的问题。

③ 本次实践的经验所得及问题分析。 ④ 实验报告单的参考格式如下页所示。

? 297 ?

C语言程序设计实验报告单 班级 实验时间 实验题目 学号 实验地点 姓名 实验目的 实验要求 实验内容 及 主要步骤 实验 程序清单 实践经验 及 问题分析 ? 298 ?

实验一 TC 3.0的集成开发环境及简单C程序的运行

1.目的及要求

(1) 了解你所使用的计算机系统的基本操作方法,并学会使用。

(2) 了解TC 3.0的集成开发环境及如何在该环境下来编辑、编译、连接和运行一个C程序。

(3) 输入并运行简单的C程序,了解并掌握C语言源程序的特点。 2.实验内容

(1) 学习从开机到进入TC 3.0系统的操作,包括机器的冷启动及TC集成环境的进入、 退出等。

(2) 学习使用TC 3.0的集成开发环境。读者试用File|New菜单来编辑下面这个简单的程序(文件名取为hello.c),在输入过程中请注意区分大小写,因为C是区分大小写的。

#include ?stdio.h? void main() {

printf(?Hello, world! \\n?); }

输入完成后,接下来是对该程序做编译、连接,用键F9或Compile|Compile、Compile|Link或Compile|Make或Compile|Build all即可完成,得到可执行的EXE文件(注意要保证你的源程序输入没有任何错误,否则还要调试修改)。之后就可以运行这个EXE文件了(用集成环境下的Run|Run菜单或按Ctrl?F9热键),要观察运行结果用Window|User screen菜单或热键Alt?F5,当然也可直接在DOS提示符下像DOS的外部命令一样运行它。如果要保存该文件,选择File|Save菜单或热键F2将该文件取一个名称(hello.c)保存到指定的路径下,今后如果要再次装入这个文件进行编辑、修改、运行,可以用File|Open(F3)菜单选择打开这个文件。

(3) 输入例1.1(取名为LT1_1.c)并编译运行。要注意的是,需要重新选择File|New菜单新建一个源程序文件,不能接着刚才的实验内容来做。

(4) 输入例1.2(取名为LT1_2.c)并编译运行,了解如何在运行时向程序输入数据。 (5) 输入例1.3、例1.4并编译运行。 注意:

以后的实验给文件取名的规则为:对例题以LT后跟编号的形式给出,如LT1_1.c,其中LT表示例题,1_1表示例题号;对习题以XT后跟编号的形式给出,如XT3_6.c,其中XT表示习题,3_6表示习题号;实验给出的例子以SY后跟编号的形式给出,如SY5_1.c,其中SY表示实验,5_1表示第五个实验的第一个例子。

实验二 数据类型及其输入和输出

1.目的及要求

(1) 掌握C语言的基本数据类型,熟悉如何定义整型、字符型、实型变量及对它们的赋值方法。

(2) 掌握以上类型的数据在C中如何用scanf()和printf()函数来进行输入输出操作。 (3) 掌握转义字符的使用。

(4) 进一步熟悉TC环境下对程序的编辑、编译、连接和运行。

? 299 ?

2.实验内容

(1) 学习整型变量的赋值方式及其输入输出,掌握输入输出函数中处理整型数据的格式符。输入以下程序(取名为SY2_1.c)。

#include ?stdio.h?

void main() {

unsigned d?65534; int a, b, c, e, f, g, h; a?3, b?014, c?0x2e; printf(?input e, f, g, h: \\n?);

scanf(??d?o?x?d?, &e, &f, &g, &h); printf(?a??d, b??d, c??d, d??d\\n?, a, b, c, d); printf(?e??o, f??x, g??d, h??u\\n?, e, f, g, h); }

编译成功后运行,当出现提示“input e, f, g, h:”时输入10□010□0x10□?3?CR?(说明:□表示空格,?CR?表示按回车键)。按Alt?F5观察输出的结果为:

a?3, b?12, c?46, d??2 e?12, f?8, g?16, h?65533

在使用scanf函数输入八进制、十六进制数时,可以不用先导“0”和“0x”,如本例运行时直接输入10□10□10□?3,效果相同。

请读者根据printf()和scanf()函数中格式控制符的含义自行分析该结果。若将本程序scanf()参数内的&符号除去,会出现什么结果?为什么?

(2) 学习字符变量的赋值方式及输入输出。初步掌握字符数据和整型数据的互相赋值方式。输入并运行以下程序(取名为SY2_2.c)。

#include ?stdio.h?

void main() {

char ch1, ch2; ch1??A?, ch2??a?;

printf(?ch1??c, ch2??c\\n?, ch1, ch2); printf(?ch1??d, ch2??d\\n?, ch1, ch2); }

在此基础上将第三行改成“int ch1, ch2;”并运行程序。 再将第四行改为“ch1?65, ch2?ch1?32;”并运行程序。 (3) 学习格式化的输出。输入并运行例4.8和4.9。 (4) 学习使用转义符。输入并运行例2.2、习题2.6。

实验三 运算符、表达式及最简单的C程序设计

1.目的及要求

(1) 熟悉C语言的算术运算符、关系运算符、逻辑运算符、逗号运算符、赋值运算符及其表达式。

(2) 进一步了解掌握scanf()。

? 300 ?

(3) 学习getchar()、putchar()的使用。 (4) 学习简单的顺序程序设计。 2.实验内容

(1) 输入以下程序,认真分析各表达式,推测正确的运行结果,然后编译运行该程序。 #include ?stdio.h? void main() {

int a, b, c; int x, y, z; a?1, b??1, c?2; x?a??; y?15?a || b??; z???b&&(c?b?a);

printf(?\\na??2d, b??2d, c??2d\\n?, a, b, c); printf(?x??2d, y??2d, z??2d\\n?, x, y, z); }

当编译成功后运行该程序,按Alt?F5观察到的结果为: a?2, b?0, c?2

x?1, y?1, z?0

读者可能推测的结果是: a?2, b?1, c??1

x?1, y?1, z?1

为什么有差异呢?我们通过集成环境中的Debug|Watches和单步跟踪工具来调试观察, 分六次选择Debug|Watches下的Add watch子项加入要观察的六个变量a、b、c、x、y、z。 之后用F8单步执行程序,当程序运行到“y?15?a || b??;”语句时,可以看到a、x的值是正确的,运行完该语句后可以发现y的值为1,b的值为?1,这说明该语句中的b??没有被执行;再用F8执行完下一条语句,发现b的值为0,说明??b被执行了,z的值为0,c的值为2,这说明赋值表达式c?b?a没有被执行。通过以上的跟踪观察我们可以看到差异的原因是由于逻辑运算符||、&&后的两个表达式没有被执行到,不被执行的原因是什么呢?回顾学过的知识,我们知道逻辑运算有时会短路。对于||运算符,当左边的操作数结果为真时,右边的操作数就不再进行计算;对于&&运算符,当左边的操作数结果为假时,右边的操作数就不再进行计算了。由于15?a等于1,所以b??不被计算;由于??b等于零,所以(c?b?a)也不被计算。

(2) 输入下面的程序并运行。 #include ?stdio.h? void main() {

char ch1, ch2;

printf(?\\n请输入两个字符: \\n?); ch1?getchar(); ch2?getchar();

printf(?\\n输出结果为: \\n?); putchar(ch1);

? 301 ?

putchar(ch2); }

当编译完成后,运行该程序,出现提示时输入a□b?回车?(此处“□”表示空格符),按Alt?F5观察得到的输出结果为a,本意是通过putchar()函数输出ab,但得到的却只有a,问题出在哪里呢?源程序是没有错误的,我们怀疑是字符变量ch1、ch2的赋值有问题,下面就用集成环境中的Debug|Watches来进行调试观察。两次选择该菜单下的Add watch子项分别加入两个要观察的变量ch1和ch2,然后按F8键来单步执行该程序的各条语句,执行到出现输入提示时重复刚才的内容。当运行完“ch2?getchar();”后,在观察窗口watch中可看到ch1的值为?a?,而ch2的值为?□?,也就是说ch2的值被赋为空格字符,这是由于getchar()函数从输入缓冲区中读数,是按字节一个一个依次进行的。对于以上输入,当然第二个getchar()函数读到的是空格符。为了避免出现这种情况,可以在两个赋值语句中间加入一语句“getchar();”,或输入时在字符中间不要加空格符。

(3) 输入并运行例3.2,学习自增自减运算符的使用。

(4) 输入并运行例4.13、4.14和4.15,掌握用scanf()函数输入数据时的正确格式。特别要注意字符数据与数值数据的混合输入问题。

(5) 以下程序是求三角形周长及面积的程序,试输入并运行。 #include ?stdio.h? #include ?math.h? void main() {

float a, b, c; /* 定义三角形的三边 */ float len, s, area;

scanf(??f?f?f?, &a, &b, &c); /* 输入能够成三角形三边的三个数 */ len?a?b?c; s?len/2;

area?sqrt(s*(s?a)*(s?b)*(s?c)); printf(?l??.2f, area??.2f\\n?, len, area); }

(6) 参照以上程序,编写已知球半径,求球表面积和体积的程序。

实验四 分支及多分支程序设计

1.目的及要求

(1) 掌握关系表达式、逻辑表达式运算结果的表示方法。 (2) 掌握if语句、switch语句的使用方法。 (3) 掌握if语句、switch语句的嵌套使用方法。 2.实验内容

(1) 下面程序用来求x的符号,sign为1,0,?1分别表示正号,无符号,负号。 #include ?stdio.h? void main() {

int x, sign;

printf(?\\n输入x的值: ?);

? 302 ?

scanf(??d?, &x); if (x?0) sign?1; else if (x?0) sign?0; else sign??1;

printf(?sign??d\\n?, sign); }

输入后编译成功运行三遍,分别输入8、?5、0,得到的输出结果分别为1、?1、?1。当输入x的值为零时,得到的结果为?1,这是不正确的,说明程序中有逻辑错误。我们用F8来跟踪该程序的执行,输入为零时,发现程序跳过了语句“sign?0;”而来到“sign??1;”上,显然是“else if”中的条件判断不成立,通过检查发现,这是由于条件运算符“??”写成了赋值运算符“?”。改正后再运行,结果正确。

(2) 输入并运行例4.16,并仿照编写求三角形面积的程序,要求三边长从键盘输入,判断它们能否构成三角形,如能则求面积,否则输出“不能够形成三角形”的信息。

(3) 输入并运行例4.17、4.18和4.19。

(4) 编写习题4.5的(1)、(2)和(3)小题的程序,并上机调试。

实验五 循环程序设计

1.目的及要求

(1) 掌握C语言中用while、do-while、for等控制语句来构成循环的方法。 (2) 学习常用的程序调试方法。

(3) 掌握循环中常用算法如迭代、递推等。 2.实验内容

(1) 编写求1?3?5???99的程序,要求分别用while、do-while、for来做。 (2) 下面的程序用来输出如下图形:

* * * * * * * * * *

#include ?stdio.h?

void main() {

int i, j;

for (i?1; i??4; i??) {

for (j?1; j??i; j??)

printf(?* ?); printf(?\\n?); } }

试在此程序的基础上进行修改、调试,使得它能输出图形: $ $ $

? 303 ?

$ $ $ $ $ $ $

提示:在处理每一行时,应先输出一些空格。

(3) 设计循环程序,输出Fibonacci数列的前12个元素,其输出形式及不完整的程序如下:

x1?1 x2?1 x3?2 x4?3 x5?5 x6?8 x7?13 x8?21 x9?34 x10?55 x11?89 x12?144

#include ?stdio.h? void main() {

int a?1, b?1, i;

for (_________________) {

printf(?x??2d???5dx??2d???5d?, 2*i?1, a, 2*i, b); if (__________) printf(?\\n?); a?a?b; b?b?a; } }

请在for后和if后的括号内填入正确的表达式,并调试通过该程序。 (4) 输入并调试例4.27、4.28和4.29。

(5) 编程用辗转相除法求两个正整数的最大公约数和最小公倍数。

辗转相除法的思想是:设两个正整数m、n,求m/n的余数p,若p?0则n为最大公约数,若p≠0,则把原来的分母作为新的分子,余数p作为新的分母,继续运算??直到余数p?0,则此次运算的分母就是最大公约数。将m*n的结果除以最大公约数就可得到最小公倍数。

(6) 编程用牛顿迭代法求2x3?4x2?3x?6?0在1.5附近的根。要求误差小于1e-5。该方法又称牛顿切线法,其思想是:先任意假定一个与真实的根接近的值xk求出f(xk),再过(xk,f(xk))点作f(x)的切线,交x轴于xk?1,它作为第二次近似根;再由xk?1求出f(xk?1),再过(xk?1,f(xk?1))点作f(x)的切线,交x轴于xk?2,再求出f(xk?2),再作切线??如此进行下去,直到足够接近真正的根为止。下面是用N?S图表示的算法:

输入x(如输入1.5) x0?x f?((2*x?4)*x?3)*x?6 f1?(6x?8)*x?3 求下一个根x?x0?f/f1 当fabs(x?x0)??1e?5 输出x 实验六 函 数

1.目的及要求

(1) 掌握函数的定义和调用方法。

? 304 ?

(2) 掌握函数实参和形参间的值传递关系。 (3) 掌握函数的嵌套调用和递归调用。

(4) 掌握局部变量、全局变量的使用及变量的存储类型。 2.实验内容

(1)下面的程序中主函数调用max()函数来比较两个浮点数的大小,输入并运行它。 #include ?stdio.h?

float max(float x, float y) {

return (x?y?x:y); }

void main() {

float a, b, c;

printf(?\\n输入两个数: ?); scanf(??f?f?, &a, &b); c?max(a, b);

printf(?max is ?.2f\\n?, c); }

如你已经正确运行了它,试完成以下操作:

① 用F8键单步执行该程序,观察亮条的移动;用F7键来跟踪该程序的每一步执行, 观察现象,比较这两者的不同,大家会发现用F7亮条会进入到max()函数体内去跟踪执行,而F8则不会。

② 将max()函数的定义部分移动到main()函数定义的后面,观察编译时有什么错误出现?在main()函数中的printf()函数前加一语句“float max(float, float);”再编译,还有错误没有,为什么会这样?

③ 用F8单步将程序执行到“c?max(a, b);”语句,选择Debug|Evaluate菜单,系统将弹出一个对话框,在Evaluate中输入a回车,则Result框中会显示变量a的值,同样输入b则有b的值显示出来,但输入变量x(或y)时显示的则是“undefined symbol ?x?”这样的信息。改按F7键跟踪执行到return语句,再用刚才的方法来观察a、b、x、y这四个变量的值,则与先前恰恰相反,这说明了各函数中定义的自动变量对其他函数来说是不可见的,并且x、y分别取得了a、b的值(参数的值传递)。

(2) 下面是求x!的递归程序 #include ?stdio.h? fac(int n) {

int f;

if (n!?1) f?n*fac(n?1); else f?1;

return f; }

void main() {

int x?5, y; y?fac(x);

? 305 ?

printf(?x!??d\\n?, y); }

编译成功后,用F7来跟踪递归函数fac()的执行情况,每次进入fac()函数时及每次从该函数返回时,我们用Debug|Evaluate及Debug/Call stack来观察n、f值的变化和进栈出栈情况。

(3) 输入并运行例5.6、5.7、5.8和5.9。

(4) 调试运行习题5.2的(4)小题,并分析运行结果。

实验七 数 组

1.目的及要求

(1) 掌握一维数组和二维数组的定义方法,以及其元素的访问形式。 (2) 掌握用字符数组来处理字符串及字符串处理函数。 (3) 掌握数组处理中的有关排序和查找算法。 2.实验内容

(1) 下面的程序实现对二维数组元素进行赋值并输出,请上机调试运行。 #include ?stdio.h? #define ROW 5 #define COL 5 void main() {

int i, j;

int a[ROW][COL]; for (i?0; i?ROW; i??) {

for (j?0; j?COL; j??) {

a[i][j]?i*ROW?j; printf(??5d?, a[i][j]); }

printf(?\\n?); } }

该程序的正确输出结果为: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

试对该程序进行修改,使其输出结果为:

0 1 2 3 4 sum1?10 sum?10 5 6 7 8 9 sum2?35 sum?45 10 11 12 13 14 sum3?60 sum?105 15 16 17 18 19 sum4?85 sum?190

? 306 ?

20 21 22 23 24 sum5?110 sum?300

(2) 下面的程序用来对数组元素进行升序排序,并输出排序前后各元素的值。其中包含了一些错误(错误处均标有found的注释),请上机调试正确。

#include ?stdio.h?

/* 定义一个排序函数,采用冒泡法 */ void sort(int p[], int n){

int i, j, temp; for (i?0; i?n?1; i??)

for (j?i?1; j?n; j??)

if(p[i]?p[j]) { p[i]?p[j]; p[j]?p[i];} /* found */

}

/*主函数*/ void main() {

int num(9), i; /*found*/ void sort(); /*found*/ printf(?enter 9 numbers: \\n?); for (i?0; i?9; i??)

scanf(??d?, &num[i]); printf(?before the sorting: \\n?);

for (i?0; i??9; i??) /* found */

/* 每行输出三个数组元素 */

printf(?num(?d)??2d?2c?, i, num[i], i?3??0??\\n?: ? ?); /* found */ sort(num, 9);

printf(?\\nafter the sorting: \\n?); for (i?0; i??9; i??) /* found */

printf(?num(?d)??2d?2c?, i, num[i], i?3??0??\\n?: ? ?); /* found */

}

(3) 下面的程序实现对四个串按字典次序排序,其中有一些错误,请上机调试正确。 #include ?stdio.h??#include ?string.h? void main() {

char temp[4], str[4][]?{?asd?, ?abd?, ?wkl?, ?fgh?}; /* found */ int i, j, k;

printf(?\\nbefore sorting: \\n?); for (i?0; i?4; i??)

printf(??c\\n?, str[i]); /* found */ /* 选择排序法 */ for (i?0; i?3; i??) {

k?i;

for (j?i?1; j?4; j??)

? 307 ?

if (strcmp(str[k], str[j])?0) k?j; if (k!?i) {

temp?str[i]; /* found */ str[i]?str[k]; /* found */ str[k]?temp; /* found */ } }

printf(?\\nafter sorting: \\n?); for (i?0; i?4; i??)

printf(??s\\n?, str[i]);

}

(4) 下面是实现十进制数向二进制数转换的程序,请在do?while循环中的三个括号内填入正确的表达式,并上机调试运行该程序。

#include ?stdio.h? void main() {

int bin[64], i; long num;

printf(?\\nenter a number: \\n?); scanf(??ld?, &num); i?0; do {

bin[i]?(___________); i??;

num?(__________); } while (_________); for (??i; i??0; i??)

printf(??d?, bin[i]);

}

(5) 下面是用数组来实现栈操作的程序。栈是数据的一种线性表结构,对表内的数据访问一般是按后进先出的原则来进行。对栈的基本操作有两种,出栈和压栈。压栈是把数据放到该表的尾部,出栈是把表尾的数据取走。上机调试该程序。

#include ?stdio.h??

#include ?conio.h?

#define MAX 10 /* 定义栈的大小 */ float stack[MAX];

int top?0; /* top表示栈内数据个数 */ /* 定义压栈函数,即向栈内存放一个数据 */ void push(float x) {

if (top??MAX)

{ printf(?\\n\\t\\t\\tstack full! OK! \\n?); getch();}

? 308 ?

else

{ stack[top]?x; top??;} }

/* 定义出栈函数,取走并显示最后放入栈中的数据 */ void pop() {

if (top??0) printf(?\\n\\t\\t\\tstack empty! OK! \\n?); else { top??; printf(??.2f\\n?, stack[top]);} getch(); }

/* 定义按后进先出的原则打印栈内各元素的函数,不改变栈的状况 */ void prnstack() {

int i?top;

if (i??0) printf(?\\n\\t\\t\\tstack empty! OK!?); else

for (; i?0; i??)

printf(??.2f?s?, stack[i?1], (i?1)?0??????:??); printf(?\\n?); }

void main() { char ch; float f;

for (; ;) {

clrscr(); /* 清屏 */ printf(?\\n\\t\\t\\t1---压栈---push\\n?); printf(?\\t\\t\\t2---出栈---pop\\n?); printf(?\\t\\t\\t3---打印---print\\n?); printf(?\\t\\t\\t4---退出---exit\\n?); printf(?\\t\\t\\tinput your choice: ?); scanf(??c?, &ch); getchar(); switch (ch) {

case ?1?: /* 输入数据并压到栈中 */

printf(?\\t\\t\\tinput a number: ?); scanf(??f?, &f); getchar(); push(f); break;

case ?2?: /* 取走栈顶元素,并显示该元素 */

pop(); break;

case ?3?:

prnstack(); getch(); break; case ?4?: exit(0);

? 309 ?

} } }

实验八 指 针

1.目的及要求

(1) 掌握指针的基本概念及其定义方法,学会使用指针变量。 (2) 学习掌握数组的指针和指向数组的指针变量。 (3) 学习掌握字符串的指针和指向字符串的指针变量。 (4) 学习了解指向函数的指针变量。 (5) 学习了解指向指针的指针。 2.实验内容

(1) 下面两个小程序都是按逆序输出数组元素的值,试上机调试并比较异同。 /* 程序一:用数组下标变量直接处理 */ #include ?stdio.h? void main() {

int a[6]?{1, 2, 3, 4, 5, 6}, i, temp; printf(?\\nbefore: \\n?); for (i?0; i?6; i??) printf(??2d?, a[i]);

for (i?0; i?3; i??) {

temp?a[i]; a[i]?a[5?i]; a[5?i]?temp; }

printf(?\\nafter: \\n?); for (i?0; i?6; i??)

printf(??2d?, a[i]);

}

/* 程序二:用指针变量处理 */ #include ?stdio.h? void main() {

int a[6]?{1, 2, 3, 4, 5, 6}, i, temp; int *p?a, *q?a?5; printf(?\\nbefore: \\n?); for (i?0; i?6; i??)

printf(??2d?, *(p?i)); for (; p?q; p??, q??) {

temp?*p; *p?*q; *q?temp; }

printf(?\\nafter: \\n?); for (i?0; i?6; i??)

? 310 ?

printf(??2d?, a[i]);

}

(2) 下面的程序用二维数组的行指针处理数组元素的输出,其中包含了一些错误,请上机调试正确(标有found的注释语句为有错误语句)。

#include ?stdio.h?

void main() {

int a[3][]?{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; /* found */ int *p[4]?a, i, j; /* found */ for (i?0; i?3; i??) {

for (j?0; j?4; j??)

printf(??5d?, *(p?i?j)); /* found */ printf(?\\n?); } }

(3) 上机调试运行例题8.13?8.16。

(4) 下面的程序中分别用函数名和函数的指针来调用函数max(),试上机运行。 #include ?stdio.h?

int max(int x, int y) { /* 定义函数max */

return (x?y?x:y); }

void main() {

int a, b, m1, m2;

int (*p)(); /* 定义一个指向函数的指针变量 */ scanf(??d?d?, &a, &b);

m1?max(a, b); /* 用函数名直接调用max()函数 */ printf(?max output 1: ?5d\\n?, m1); p?max;

m2?*p(a, b); /* 通过函数指针形式来调用函数 */ printf(?max output 2: ?5d\\n?, m2); }

(5) 下面的程序用指向指针的指针来访问数组元素,试上机调试运行。 #include ?stdio.h??#include ?stdlib.h? void main() {

int i, j, a[2][4]?{1, 2, 3, 4, 5, 6, 7, 8};

int **p; /* 定义指向指针的指针变量 */ p?(int **)malloc(2*sizeof(int *)); /* 语句1 */ *p?*a; *(p?1)?*(a?1); /* 语句2 */ printf(?\\n?); for (i?0; i?2; i??)

? 311 ?

for (j?0; j?4; j??)

printf(??5d?, *(*(p?i)?j)); /* 计算变量的指针并输出变量的值 */

free(p); }

其中,语句1的作用是利用malloc()函数动态分配两个用来存储指针的存储单元,并将第一个存储单元的指针赋给变量p,这样p和p?1就分别指向了这两个单元;语句2的作用是给malloc()函数所分配的两个存储单元(分别是p和p?1所指向的存储单元)赋值,赋值后它们分别指向a[0][0]和a[1][0]数组元素。如下图所示:

*a p p+1

a[0][0] a[0][1] a[0][2] a[0][3] *a *(a+1) *(a+1) p malloc()函数分配的2个 a[1][0] a[1][1] a[1][2] a[1][3] 存储指针的存储单元

数组a所分配的存储单元

实验九 结构体、共用体及位运算

1.目的及要求

(1) 掌握结构体、共用体类型变量的定义和使用。 (2) 掌握结构体类型数组的定义及应用。

(3) 掌握位运算、位段的基本概念,学习使用位运算符。 (4) 学习掌握利用位运算来实现对某些位的操作。 2.实验内容

(1) 以下是输出结构体数组各元素值的程序。输出时分别采用了数组下标的访问方式和指针访问方式,上机调试该程序,分析这两种方式的异同。

#include ?stdio.h? void main() {

struct node {

char s[6]; int i; } n[4], *p; int k; p?n;

strcpy(n[0].s, ?one?); n[0].i?1; strcpy(n[1].s, ?two?); n[1].i?2; strcpy(n[2].s, ?three?); n[2].i?3; strcpy(n[3].s, ?four?); n[3].i?4; printf(?\\nOutput 1: \\n?); for (k?0; k?4; k??)

printf(??s_?d?s?, n[k].s, n[k].i, k<3??--??:??); printf(?\\n\\nOutput 2: \\n?);

? 312 ?

for (k?0; k?4; k??, p??)

printf(??s_?d?s?, p-?s, p-?i, k<3??--??:??);

}

(2) 输入并运行例9.3、9.4、9.5和9.6。

(3) 编写习题9.1、9.2、9.3和9.4的程序,并上机调试。 (4) 有程序如下: #include ?stdio.h? void main() {

int x, i, bin[16];

printf(?\\nEnter a number: ?); scanf(??d?, &x); printf(?\\nThe binary is: ?); for (i?0; i?16; i??) {

/* 从右到左将该数的每一位分离出来,并保存到数组bin中 */ bin[i]?(x&&0x0001);

x???1; /* 进行右移位操作 */ }

for (i?15; i??0; i??)

printf(??d?, bin[i]);

}

该程序输入一个十进制整数,通过位操作处理后,输出它对应的二进制补码。 思考:若将上程序中的右移位操作改为左移位操作,该如何编写此程序?请上机完成该程序的编写。

(5) 输入并运行例11.1。

(6) 编写程序,实现将一个十进制整数对应的二进制数的原码输出。

实验十 文 件 操 作

1.目的及要求

(1) 掌握缓冲文件和非缓冲文件系统及文件指针的基本概念。 (2) 掌握文件的打开、关闭、读、写等操作的函数。 (3) 学会使用缓冲文件进行文件操作。 2.实验内容

(1) 下面的程序输入源文件和目标文件的名称,实现对文件的拷贝功能。在横线上填入正确的表达式后上机调试通过。

#include ?stdio.h? void main() {

char sname[20], tname[20], ch; FILE *fp1, *fp2; printf(?\\nenter source: ?);

scanf(??s?, sname);

? 313 ?

printf(?\\nenter target: ?); scanf(??s?, tname);

if ((fp1?fopen(sname, ?rb?))??NULL) {

printf(?source file open error! \\n?); exit(0); }

if ((fp2?fopen(tname, ?wb?))??NULL) { printf(?target file open error! \\n?); exit(0); }

ch?getc(fp1);

while (!feof(fp1)) { fputc(__________);

ch?getc(_____); }

fclose(fp1); fclose(fp2); }

(2) 有程序如下:

#include ?stdio.h?

void main(int argc, char *argv[]) {

FILE *s; char c;

if ((s?fopen(argv[1], ?r?))??NULL) {

printf(?\\ncannot open file! \\n?); exit(0); }

while ((c?getc(s))!?EOF)

putchar(c); fclose(s); }

该程序采用命令行参数形式来运行,它模拟DOS方式下的type命令显示文件内容。请编译该程序后在DOS提示符下运行得到的EXE文件。

(3) 分析总结以上两程序,编写模拟DOS下copy命令的程序(要求用命令行参数形式)。 (4) 下面的这个程序以ASCII和十六制形式显示任意类型文件的内容。每次显示文件的128个字节,并可前后移动。输出形式同DEBUG工具中的D命令相似。当在每个段输入的提示下输入?1时,退出程序。上机调试运行此程序。

#include ?ctype.h? #include ?stdio.h?

unsigned char buffer[128]; /* 定义输出函数 */

? 314 ?

void display(int num) {

int i, j;

for (i?0; i?num/16; i??) {

for (j?0; j?16; j??) /* 十六进制形式输出 */

printf(??3x?, buffer[i*16?j]);

printf(? ?);

for (j?0; j?16; j??) /* ASCII形式输出 */

if (isprint(buffer[i*16?j])) printf(??c?, buffer[i*16?j]); else printf(?.?); printf(?\\n?); } }

void main() {

FILE *fp; char name[20]; int sec, num;

printf(?\\nInput a file name: ?);

scanf(??s?, name); /* 输入要观看的文件名 */ if ((fp?fopen(name, ?rb?))??NULL) {

printf(?\\nconnot open file! ?); exit(0); } for (; ;) {

printf(?\\nEnter ?1 quit: ?); scanf(??d?, &sec); if (sec???1) break;

if (fseek(fp, sec*128, SEEK_SET)) // 移动文件指针到段的启始位置

printf(?\\nSeek error! ?);

num?fread(buffer, 1, 128, fp); // 读文件的内容并返回读到的字符个数 display(num); // 调用函数显示读到内存中的数据 if (num!?128) {

printf(?\\nEnd file! \\n?); getch(); break; } } }

(5) 下面的程序是将屏幕上格式输出的内容同样输出到一个文本文件(名为student.txt)中。试上机调试运行,成功运行后,退到DOS状态下查看是否存在该文本文件,并用type命令显示其内容。

#include ?stdio.h??#include ?conio.h?

? 315 ?

typedef struct {

char name[8]; int age; } STUDENT;

void tofile (STUDENT *stud) {

int i; FILE *fp;

if ((fp?fopen(?student.txt?, ?wt?))??NULL) {

printf(?\\nconnot create file! ?); exit(0); }

for (i?0; i?5; i??) {

fprintf(fp, ?\\nNO.?d student?s message: ?, i?1); fprintf(fp, ?\\nname is: ?s?, stud[i].name); fprintf(fp, ?\\tage is: ?d\\n?, stud[i].age); } fclose(fp); }

void main() {

STUDENT stud[5]; int i; clrscr();

for (i?0; i?5; i??) {

printf(?\\nPlease input NO.?d student?s message: \\n?, i?1); printf(?name is: ?); scanf(??s?, &stud[i].name); printf(?age is: ?); scanf(??d?, &stud[i].age); }

for (i?0; i?5; i??) {

printf(?\\nNO.?d student?s message: ?, i?1); printf(?\\nname is: ?s?, stud[i].name); printf(?\\tage is: ?d\\n?, stud[i].age); }

tofile(stud); }

(6) 输入并运行例14.3、14.6。

? 316 ?

本文来源:https://www.bwwdw.com/article/qe06.html

Top