《c语言程序设计基础》

《c语言程序设计基础》

ID:11636511

大小:2.89 MB

页数:0页

时间:2018-07-13

上传者:xinshengwencai
《c语言程序设计基础》_第页
预览图正在加载中,预计需要20秒,请耐心等待
资源描述:

《《c语言程序设计基础》》由会员上传分享,免费在线阅读,更多相关内容在教育资源-天天文库

《C语言程序设计基础》教学大纲草案2021-10-237:25《C语言程序设计基础》教案清华大学自动化系李宛洲1/247 《C语言程序设计基础》教学大纲草案2021-10-237:25目录第一章程序设计概述.................................................................81.1计算机基本概念.................................................................................................................81.2程序与计算机.....................................................................................................................91.3开关电路与二进制数.......................................................................................................101.3.1开关电路概念.......................................................................................................101.3.2二进制数...............................................................................................................111.3.3ASCII码................................................................................................................131.3.4存储器...................................................................................................................131.3.5程序编译与执行..................................................................................................131.3.6计算机的各种语言形式......................................................................................151.4C程序概貌........................................................................................................................161.4.1程序与变量............................................................................................................161.4.2C程序结构............................................................................................................181.4.2.1C语言程序要素......................................................................................181.4.2.2C语言程序的结构化设计风格..............................................................201.5程序与算法.......................................................................................................................241.5.1人类的认知能力与计算机智能的上限..............................................................241.5.2算法概念...............................................................................................................251.5.3算法与程序效率..................................................................................................261.6C语言的关键字................................................................................................................27第二章数据与变量...................................................................292.1数据的概念........................................................................................................................292.1.1数据是客观事物属性的描述..............................................................................292.1.2C语言的数据类型...............................................................................................302.1.3常量与变量...........................................................................................................322.1.3.1常量...........................................................................................................322.1.3.2变量............................................................................................................322.2C数据类型详解................................................................................................................332.2.1int类型................................................................................................................332.2.2char(字符)类型..................................................................................................352.2.2.1字符常量..................................................................................................352.2.2.2字符变量...................................................................................................362.2.2.3字符串常量...............................................................................................362.2.3_Bool类型............................................................................................................382.2.4实型(float)类型............................................................................................382.2.5复数和虚数类型..................................................................................................402.2.6各类数据类型的混合运算..................................................................................412.2.6.1自动转换...................................................................................................412.2.6.1强制类型转换...........................................................................................422.3小结....................................................................................................................................432/247 《C语言程序设计基础》教学大纲草案2021-10-237:252.3.1关键概念................................................................................................................432.3.2易犯错误................................................................................................................43第三章输入输出方式...............................................................453.1缓冲区与流的概念............................................................................................................453.1.1缓冲区...................................................................................................................453.1.2流...........................................................................................................................463.2交互式程序的例子...........................................................................................................473.3格式I/O函数scanf()和printf()..............................................................................493.3.1标准输出函数printf().....................................................................................493.3.1.1printf函数调用的一般形式.................................................................493.3.1.2格式字符..................................................................................................503.3.2标准输入函数scanf().......................................................................................513.3.2.1调用格式..................................................................................................513.3.2.2使用scanf函数必须注意的问题..........................................................533.4字符I/O函数....................................................................................................................543.4.1单字符函数getche()和putchar......................................................................543.4.2单字符缓冲输入函数getchar().......................................................................553.4.3字符串输入/出函数gets()、puts()...............................................................563.5缓冲区中的回车符滞留问题..........................................................................................573.6cout和cin函数..............................................................................................................593.7小结....................................................................................................................................60第四章运算符与表达式...........................................................624.1运算符................................................................................................................................624.1.1赋值运算符:=......................................................................................................634.1.2加减乘除法运算符:+、-*和/...........................................................................644.1.3算术表达式和运算符的优先级与结合性...........................................................654.2其它运算符........................................................................................................................664.2.1自增、自减运算符:++和--..............................................................................664.2.2取模运算符:%....................................................................................................674.2.3逗号运算符和逗号表达式..................................................................................684.2.4复合赋值符及表达式..........................................................................................694.2.5sizeof运算符和size_t类型...........................................................................694.3C语言的语句....................................................................................................................704.3.1语句........................................................................................................................704.3.2复合语句................................................................................................................714.4小结....................................................................................................................................73第五章程序流分支控制结构...................................................755.1本章概要............................................................................................................................755.2if语句..............................................................................................................................755.2.1程序的三种基本结构..........................................................................................755.2.2if语句基本结构.................................................................................................773/247 《C语言程序设计基础》教学大纲草案2021-10-237:255.2.3if与else配对...................................................................................................805.2.4阶梯式if-else-if结构....................................................................................815.3关系表达式与逻辑运算符...............................................................................................825.4条件运算符和条件表达式...............................................................................................845.5并行选择结构switch语句和break.............................................................................855.6程序分支控制结构小结..................................................................................................88第六章循环控制结构...............................................................906.1本章概要............................................................................................................................906.2数组....................................................................................................................................906.3for、while和dowhile循环结构...............................................................................926.3.1for语句基本结构...............................................................................................926.4while循环结构................................................................................................................946.5dowhile循环结构..........................................................................................................966.6循环中止控制.................................................................................................................1006.6.1break语句..........................................................................................................1006.6.2continue语句...................................................................................................1026.7循环结构小结..................................................................................................................103第七章函数...........................................................................1047.1本章要点.........................................................................................................................1047.2函数与C程序结构..........................................................................................................1047.2.1程序的函数分解实例.........................................................................................1057.2.2函数分类..............................................................................................................1067.2.3函数的基本概念.................................................................................................1067.2.4函数声明与形式参数.........................................................................................1077.2.5函数的参数和函数的值.....................................................................................1087.2.5.1形式参数和实际参数.............................................................................1087.2.5.2函数的返回值..........................................................................................1107.3作用域规则.....................................................................................................................1117.3.1变量作用域..........................................................................................................1127.3.1.1局部变量..................................................................................................1137.3.1.2全局变量..................................................................................................1157.3.2变量的动态存储与静态存储.............................................................................1197.3.3C语言中Project、源程序、函数、变量的关系..........................................1207.4指针的概念.....................................................................................................................1227.4.1指针是一个存储地址的变量............................................................................1237.4.2指针声明.............................................................................................................1237.4.3地址运算符“&”和间接运算符“*”............................................................1267.4.4指针与数据结构................................................................................................1267.5函数调用.........................................................................................................................1287.5.1调用形式.............................................................................................................1287.5.2参数传递.............................................................................................................1287.5.2.1传递参数值............................................................................................1284/247 《C语言程序设计基础》教学大纲草案2021-10-237:257.5.2.2数组传递方法.........................................................................................1297.5.2.3传递参数地址........................................................................................1317.6递归函数与分治算法......................................................................................................1337.6.1分治法的基本思想............................................................................................1357.6.2递归程序设计实例............................................................................................1367.6.2.1对半检索(BinarySearch)..............................................................1367.6.2.2汉诺塔算法.............................................................................................1397.7小节.................................................................................................................................141第八章数组、指针与结构.....................................................1428.1要点..................................................................................................................................1428.2数组.................................................................................................................................1428.2.1一维数组.............................................................................................................1428.2.1.1存储结构................................................................................................1428.2.1.2通过指针引用数组元素........................................................................1428.2.2二维数组.............................................................................................................1438.2.2.1存储结构................................................................................................1438.2.2.2二维字符串数组....................................................................................1458.2.3指针数组.............................................................................................................1468.3指向指针的指针.............................................................................................................1498.4指针型函数.....................................................................................................................1518.5函数型指针---指向函数的指针...................................................................................1538.6数据结构与数据元素......................................................................................................1578.6.1结构体定义.........................................................................................................1578.6.2结构体变量说明................................................................................................1598.6.3访问结构元素....................................................................................................1618.6.4结构变量赋值....................................................................................................1618.6.5结构数组.............................................................................................................1638.6.6指向结构变量的指针........................................................................................1648.6.7结构指针变量作函数参数................................................................................1668.7动态存储分配..................................................................................................................1678.8指针应用综合训练..........................................................................................................1708.8.1指针在数据节点之间的关联作用.....................................................................1708.8.2链表的概念..........................................................................................................1718.8.3链表的设计..........................................................................................................172第九章位域、联合、枚举.....................................................1779.1要点.................................................................................................................................1779.2位域..................................................................................................................................1779.3共用体(union)............................................................................................................1809.4枚举..................................................................................................................................1839.5类型定义符typedef.......................................................................................................1879.6小结...............................................................................................................................188第十章位运算.........................................................................1895/247 《C语言程序设计基础》教学大纲草案2021-10-237:2510.1位运算符C语言提供了六种位运算符.......................................................................18910.2按位与运算....................................................................................................................18910.3按位或运算....................................................................................................................19010.4按位异或运算................................................................................................................19010.5求反运算........................................................................................................................19110.6左移运算........................................................................................................................19110.7右移运算........................................................................................................................19210.8本章小结........................................................................................................................193第十一章文件输入/输出........................................................19412.1文件的概念....................................................................................................................19412.2文件指针........................................................................................................................19512.3文件的打开与关闭........................................................................................................19612.3.1文件打开函数fopen()....................................................................................19612.3.2文件关闭函数fclose().................................................................................19812.4文件的读写....................................................................................................................19912.4.1字符读写函数fgetc和fputc........................................................................19912.4.2字符串读写函数fgets和fputs....................................................................20312.4.3数据块读写函数fread和fwtrite...............................................................20612.4.4格式化读写函数fscanf和fprintf.............................................................20812.5文件的随机读写............................................................................................................20912.5.1文件定位............................................................................................................20912.5.2文件的随机读写..............................................................................................21012.6文件检测函数................................................................................................................21112.7实用文件程序--链表数据保存...................................................................................21312.8本章小结........................................................................................................................217附录1二进制与十进制转换及补码表示..............................218附1.1二进制与十进制转换...............................................................................................218附1.2二进制补码表示........................................................................................................218附录2程序的版式..................................................................221附2.1空行............................................................................................................................221附2.2代码行........................................................................................................................221附2.3代码行内的空格........................................................................................................222附2.4对齐............................................................................................................................223附2.5长行拆分....................................................................................................................224附2.6修饰符的位置............................................................................................................225附2.7注释............................................................................................................................225附2.8类的版式....................................................................................................................226附录3命名规则......................................................................228附3.1共性规则....................................................................................................................228附3.2简单的Windows应用程序命名规则........................................................................2296/247 《C语言程序设计基础》教学大纲草案2021-10-237:25附3.3运算符的优先级.........................................................................................................231附3.4复合表达式................................................................................................................231附录4算法的流程图表示......................................................233附录5编程步骤......................................................................234附录6范围和参数传递转换..................................................236附录7ASCII码表...................................................................237附录8变量的动态存储与静态存储......................................239附8.1存储方式....................................................................................................................239附8.2内部函数和外部函数...............................................................................................2467/247 《C语言程序设计基础》教学大纲草案2021-10-237:25第一章程序设计概述1.1计算机基本概念我们平常说的计算机包含两个概念:计算机的硬件系统和软件系统。现在我们所使用的计算机硬件系统的结构一直沿用了由美籍著名数学家冯·诺依曼提出的串行工作模型,它由运算器、控制器、存储器、输入设备、输出设备五大功能部件组成。半个世纪以来,计算机已发展成为一个庞大的家族,尽管各种类型的计算机在性能、结构、应用等方面存在着差异,但是它们的基本组成结构却是相同的。各种各样的信息,通过输入设备,进入计算机的存储器,然后送到运算器,运算完毕把结果送到存储器存储,最后通过输出设备显示出来。整个过程由控制器进行控制。计算机的整个工作过程及基本硬件结构如图1.1所示。外存储器(硬盘)输入取操作数(数据,程序)输入设备内存储器运算器(键盘,扫描仪)存操作数中央处理单元输入命令存取指令(CPU)程序指令输出设备控制器(显示器,打印机)输出命令图1.1计算机系统的基本硬件组成及工作原理随着信息技术的发展,各种各样的信息,例如:文字、图像、声音等经过编码处理,都可以变成数据。于是,计算机就能够实现多媒体信息的处理,如图1.2所示。8/247 《C语言程序设计基础》教学大纲草案2021-10-237:251.2程序与计算机人的大脑负责存储信息和管理四肢活动,而计算机的大脑是它的中央处理单元(CPU)与存储器和操作系统的结合,我们可以将电脑和人脑进行比较:一个新出生的婴儿好比一台只有硬件系统的计算机,他(它)什么也不懂,人只是具有了思维的机理(大脑),计算机只是具备程序安装环境(CPU、存储器等硬件基础)。随着婴儿的长大,他逐渐地学会了协调四肢运动(它安装了操作系统掌控各种资源分配与运行)。他学会了思维方法(它安装了各种工具软件),聪明的婴儿能更好的开发自己的大脑思维能力(计算机要安装更先进的操作系统)。现在他开始学习认字(它安装了OFFICE2000工具软件),他学会了说话与社会进行信息交流(它通过显示器表达计算机输出的信息),用眼睛观察世界(从键盘读入信息)。他的上学过程就是我们给计算机安装各色应用软件的过程。如果他学文科,我们给它安装了OFFICE2000就足够了,或者他还喜欢美术和广告一类的技艺,我们可以安装PHOTOSHOP一类的图像处理软件。如果他偏于工科,那么学习内容就比较复杂。它可能需要安装VC6.0(适用于各种工作要求的C语言)、FORTRAN语言(科学计算用语言)、ORCAD(电子线路设计工具)、AUTOCAD(机械制图工具)、MATLAB(数学工具)等。这一过程如图1.1所示。应用开发程序OFFICE2000VC6.0BIOSWINDOWS数据库CPU中央处理单元外存储器键盘(硬盘)显示器内存储器图1.3计算机组成现在他学会了很多技能,和我们有了从事某一种业务的共同语言,比如,我们可以用C语言开始具体的工作。因为我们擅长思维,计算机擅长处理,所以我们提出方法(编制程序),它计算处理(带入数据运行)。程序是计算机某种语言的语句集合,我们平常意义上说的各种软件就是由程序和数据构9/247 《C语言程序设计基础》教学大纲草案2021-10-237:25成的。编制程序就是用计算机语言描述一个特定的任务,程序的运行就可以让计算机完成该任务。比如计算函数yaxb,我们告诉计算机函数的求解方法(在计算机上编制一段程序),它进行数据运算处理(运行程序),因为我们有共同的语言(标准的C语法格式),所以它能正确理解程序,我们也可以读懂它输出在显示器屏幕上的结果信息。这就是我们计算机之间的交流方式。1.3开关电路与二进制数1.3.1开关电路概念人脑思维的核心机理是大脑皮层上的神经元系统,计算机运行程序的核心机理是二进制开关电路,也称之为数字电路。因此,程序运行的基础就是基于二进制码的计算机指令与数据的集合。所谓开关电路,就是只能表示0或1状态的电路,比如图1.4所示。控制信号V开关闭合灯亮,表示逻辑1开关打开灯灭,表示逻辑0图1.4数字电路原理在控制信号作用下,开关或者闭合,或者打开,使得电路的输出(灯泡的亮灭)有两种状态表现,我们分别定义为逻辑1和逻辑0。因为在不施加外界条件(控制信号不动作)情况下开关电路的这种状态可以一直保持下去,我们称之为记忆,这时,开关电路就成了记忆的载体,它能记住两个不同的状态。一个开关的闭合或者打开只能表示一路输出信号的状态,我们称之为一个数据位(Bit),现在,我们如果用多路类似于图1.4的开关电路组合,就可以记忆,或者描述多个数据位,比如图1.5有4路开关信号,就是具有4个Bit的开关电路。根据4个控制信号动作的组合,图1.5的输出逻辑状态是4个Bit的“0”或者“1”状态的组合关系,见表1.1。因为每一个Bit只能取值为1或者0,我们称之为二进制数。二进制数与十进制数存在着等价关系。比如,表1.1从序号1向序号2计数时,最低输出位(Bit0)的状态由1变为0,同时产生的进位使第二位(Bit1)的状态由0变为1,即,二进制的10等价于十进制的2,显然,从表1.1可知二进制的11等价于十进制的3,…,依此类推到二进制的1111等价于十进制的15。10/247 《C语言程序设计基础》教学大纲草案2021-10-237:25控制信号0Bit0V控制信号1Bit1V控制信号2Bit2V控制信号3Bit3V图1.5能记忆4个Bit的开关电路表1.1图1.5输出逻辑真值表序号控制信号输出Bit状态32103210000000000100010001200100010300110011401000100501010101601100110701110111810001000910011001101010101011101110111211001100131101110114111011101511111111之所以计算机采用开关电路,是因为开关电路的物理实现非常简单可靠,能大规模集成化。而十进制与二进制数之间的等价关系,使得计算机通过开关电路存储或运算二进制数,与我们平时的十进制数运算完全等价,因此,计算机就能存储大容量的数据,以及进行非常快的数据处理工作。1.3.2二进制数计算机之所以能够处理数值、文字、声音、图像等信息,实际上它是把这些信息转换成计算机开关电路所能存储、运算的形式,也就是说,计算机中所有的信息都用“0”和“1”两个数字符号组合的二进制数来表示。11/247 《C语言程序设计基础》教学大纲草案2021-10-237:25数值、图形、文字等各种形式的信息,需要计算机加工处理时,首先必须按一定的法则转换成二进制数。然而,日常生活中使用的数是十进制数,它的特征是:(1)有10个数字:0、1、2、3、4、5、6、7、8、9。(2)运算时逢十进一。(3)每个数字在不同的数位上,其权值的大小是不同的。(4)数位:个,十,百,千,万,……01234权值:10,10,10,10,10,……那么二进制数的特征是什么呢,我们列出如下:(1)有2个数字:0,1。(2)运算时逢二进一。01234(3)每个数字在不同数位上,其权值以2的倍数递增。即2,2,2,2,2,…。用二进制数表示一个数值时位数比较长,不便书写和记忆,由于有下面的关系:23=8及24=16,所以人们常用八进制数或十六进制数来表示二进制数。八进制数的特征是:(1)有八个数字:0,1,2,3,4,5,6,7。(2)运算时逢八进一。十六进制数的特征:(1)有十六个数字:0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F。(2)运算时逢十六进一。(3)在十六进制中分别用A、B、C、D、E、F来表示十进制数的10、11、12、13、14、15。表1.2所示的是二进制、八进制、十进制与十六进制的特征对照。表1.2进制代表数字特点例0、1最大为1,最小为0,超110111二进制过2就进位。+1010110011000、1、2、3、4、5、6、7最大为7,最小为0,超12765八进制过8就进位。+3412164770、1、2、3、4、5、6、7、8、9最大为9,最小为0,超12765十进制过10就进位。+3412161770、1、2、3、4、5、6、7、8、9、最大为f(15),最小为0,12765十六进制a、b、c、d、e、f。超过16就进位。+341216B7712/247 《C语言程序设计基础》教学大纲草案2021-10-237:251.3.3ASCII码计算机内部采用二进制数方式,那么它为什么又能识别十进制数和各种字符、图形呢?其实,不论是数值数据还是文字、图形,在计算机内部都采用了一种编码标准。通过编码标准可以把所有信息转换成二进制数来进行处理,计算机将这些信息处理完毕再转换成可视的信息显示出来。常用的字符代码是ASCII码,它原来是美国的国家标准,1967年被定为国际标准。ASCII码由8位二进制数组成。其中最高位为校验位,用于检验传输过程中的数据正确与否。其余7位二进制数表示一个字符,共有128种组合。如回车的ASCII码为0001101(13),空格的ASCII码为0100000(32),“0”的ASCII码为0110000(48),“A”的ASCII码为1000001(65),“a”的ASCII码为1100001(97)。ASCII码表见附录3。1.3.4存储器因为电路的开关状态和控制信号的状态都需要电源维护,因此,用数字电路做成的记忆体依赖电源,失去电源后(计算机关机)数据就会遗失,因此,用电路做成的存储器称之为易失性存储器,也叫随机存储器(RandomAccessMemory),所以称之为随机存储器,是因为计算机上电的时候存储器里的数据是随机的。随机存储器作为计算机的内存储器只是用于程序或者数据的工作区域(这里没有讨论EPROM的概念)。和开关电路一样具有两种转换形态,控制性好,且具有非掉电易失性的存储介质是磁。我们熟知磁场有两极N和S,电路原理告诉我们,通过外加电场可以方便的转换磁性介质的极性,假定磁场一端是极性N代表逻辑0而是极性S则代表逻辑1,当在控制信号作用下磁场极性的翻转就是逻辑状态的变化,并且,在控制信号消失(计算机断电)时它的状态也能长久保持,这就是硬磁盘的工作原理。大容量的硬盘存储二进制数据的能力远远超过随机存储器,并且可以断电后不会造成数据的丢失,我们用它作为计算机的外存储器使用,保存程序和数据文件。1.3.5程序编译与执行如果想叫计算机工作,就得先把程序编出来,然后通过输入设备送到存储器中保存起来,即程序存储,接着就是执行程序的问题了。根据冯.诺依曼的设计,计算机应能自动执行程序,因为计算机语言(比如C、FORTRAN等)是为了计算机和人沟通,以我们容易理解的13/247 《C语言程序设计基础》教学大纲草案2021-10-237:25语法形式设计的,而计算机内部的CPU系统根据数字电路设计的不同,具有自己特殊的命令系统,从程序语言到CPU指令系统之间需要一个翻译步骤,所以,一条程序语句对应着多条CPU的指令集合,因此,执行程序又归结为CPU逐条执行指令,图1.6是CPU内部结构图(8086系列)。可以让计算机执行动作的操作指令是二进制代码,我们称之为机器码(机器语言代码)。我们已经知道了计算机用二进制描述外部信息的方法,那么控制计算机内部运行的指令是如何实现的呢?计算机的指令系统按功能可以分为:数据传送,算术运算,逻辑运算与移位、控制转移以及处理器控制。执行一条指令又可分为以下三个基本操作:①取出指令从存储器某个地址中取出要执行的指令送到CPU内部的指令寄存器暂存;②分析指令把保存在指令寄存器中的指令送到指令译码器,译出该指令对应的微操作;③执行指令根据指令译码,向各个部件发出相应控制信号,完成指令规定的各种操作;因此,需要执行的程序和数据首先必须翻译成计算机的机器码形式,其次,程序代码存储在计算机存储器的某一地址区域内,最后,CPU从那个地址开始,逐条读入程序的指令,执行相应的操作。把某种计算机语言翻译成机器码的过程,我们称之为编译,任何一种计算机语言都有特14/247 《C语言程序设计基础》教学大纲草案2021-10-237:25定的编译器进行这一工作,比如C语言编译器。不同的厂商生产的计算机,或者不同类型的计算机就有不同的编译器,比如同是c语言编译器,面向大型机的和小型机的不同,当然也不同于个人计算机的c语言编译器,因为它们的机器码不同。另外,基于计算机的操作系统不同,编译器也不同。WINDOWS平台下的编译器都是集成开发环境的,是便于编程人员操作的视窗形式。而UNIX环境是字符界面的,它的语言编译器相应就是命令行启动的。图1.7是从我们写好的计算机语言的源程序转换成计算机最终可执行的指令码程序的过程。其中,编译只是机器码翻译,还需要经过链接器(LINK),将程序中使用的标准库函数(比如输入输出语句,数学运算函数等)插入到程序中,并且给定启动代码(应用程序和操作系统之间的接口),最终才形成计算机可执行的程序文件。比如C语言程序源程序编译器机器码程序也称为目标文件标准库函数链接器启动码可执行程序也称为可执行文件图1.7编译器和链接器在WINDOW操作系统环境下,编译器和链接器被集成在一个窗口内,包括源程序编辑器(书写源程序的工具),我们称之为集成开发环境IDE。1.3.6计算机的各种语言形式初学者可能会问,我们为什么不能直接使用机器码编制程序,这样就可以省略编译与链接过程。事情当然不会这样简单。早期计算机确实是使用机器码编程的,它要求程序编制工程师需要非常专门的计算机硬件系统知识,熟悉指令系统的寻址方式,CPU内部工作原理,机器动作时序等的复杂工作背景,这样编制的程序才能最大限度的发挥机器性能。当时的计算机主要应用在工业控制和科学计算领域,根本不涉及数据结构和算法研究方面的问题。随着计算机在商业领域的大规模应用,以及知识管理,智能研究领域的深入,计算机主要工作是数据的分析与处理,知识推理与求解等问题。这时,程序设计主要考虑的是数据结构设计,算法分析等内容,我们要求软件设计工程师从硬件系统背景中分离出来,不需记忆大量的二进制机器码,把代码优化问题交给编译程序处理,专心研究算法与数据结构。这就15/247 《C语言程序设计基础》教学大纲草案2021-10-237:25需要一种面向程序设计的计算机语言,而不是面向机器的指令代码。这种计算机语言更近似自然语言规则,容易理解,无需记忆,我们称之为高级程序设计语言。根据高级语言的发展过程,主要有大众化的BASIC语言,面向科学计算用的FORTRAN语言,面向商业数据处理的COBLE语言,PASIC语言,以及当今最流行的结构化程序设计语言C,面向对象的程序设计语言C++,VisualC++,JAVA等。计算机高级语言的发展,给我们设计算法提供了有力工具。但是,在很多场合,比如在工业控制领域方面,在操作系统对处理器实时管理方面等,我们需要最大限度的发挥计算机CPU的处理性能,编制高效率的程序,设计紧凑的程序代码段,具有快速的响应处理时间。这时,需要对某一类型的CPU提供专门的语言工具,它比机器码容易记忆,而编译效率优于高级语言,是面向机器硬件环境的编程语言,我们称之为汇编语言。本门课程教授C语言程序设计方法,C语言是当今最为重要的计算机软件工具,它几乎可以适用任何一种工业、商业应用领域,它的编程效率高于一般的计算机程序设计语言,近似于汇编语言的紧凑,在不同种类的计算机上可以移植性好,是C++,VisualC++,JAVA等的基础。计算机语言的种类很多,适合各种不同的应用领域。1.4C程序概貌1.4.1程序与变量在刚开始学习一门语言的时侯,往往是基本语句都掌握了,但就是不会解决实际的问题。这就是还没有理解计算机是怎么解决实际问题的。其实这个问题很好解决,记住这么一条:计算机只能抽象的描述事物。所以我们在用程序解决某一个问题时,首先把程序要处理事物的特性抽象出来,在计算机中用这些抽象出来的特性来代表一个具体的事物。举例来说,如图1.8所示,我们现在根据电压和电流的关系编写一个程序,由用户输入一个电压值和一个电阻值,由计算机计算出相应的电流值并显示在屏幕上。电压U电阻R电流I图1.8电压与电流的关系编程之前,我们先思考一下大脑是如何根据电阻、电压参数计算电流的。16/247 《C语言程序设计基础》教学大纲草案2021-10-237:25(1)根据题意判断出是一个欧姆定律的解题问题;(2)设电流变量为I、电阻变量为R、电压变量为U,则:UIR(3)用眼睛扫描已知条件,即电压、电阻的具体参数,带入计算公式;(4)运算,并用手写出计算结果,解题完毕。程序解题也是如此模式进行。(1)定义三个变量I、R、U,告诉程序它们分别代表电流、电阻和电压;(2)把键盘看成计算机的眼睛,从键盘输入R、U这两个变量的数值;(3)告诉程序欧姆定律公式,让计算机根据输入参数运算;(4)计算机把计算结果写在显示器屏幕上,解题完毕。下面是程序:例1.1intmain(void){intI,U,R;//定义变量printf("Pleaseinputthevoltageandresistance ");//提示我们输入参数scanf("%d,%d",&U,&R);//输入参数I=U/R;//计算printf("Thecurrentis%d ",I);//输出结果}运行结果:Pleaseinputthevoltageandresistance10,2(键盘输入)Thecurrentis5这个例子告诉我们两点:(1)程序只能用来描述客观世界中的抽象信息。如果想用计算机来描述事物的某一属性,必须将事务的该属性写进程序,计算机才能予以描述。(2)例1.1程序中使用了3个变量U、R和I来描述电压、电阻和电流参数。这是一个17/247 《C语言程序设计基础》教学大纲草案2021-10-237:25重要的概念:程序是用变量来描述事务属性的。所有需要计算机处理的属性都有相应的变量描述,属性不同,变量类型不同。比如电压、电阻、电流大小用整数描述,因此变量U、R和I是整数型变量,假设电阻的重量用实数描述,就需要定义一个能表示小数的浮点型变量。而语句:intI,U,R;是变量声明语句。程序使用变量前需要声明,也称之为变量定义,程序给变量赋值(从键盘输入,或者直接用运算符“=”赋值),相当于数学函数中给自变量取值。1.4.2C程序结构1.4.2.1C语言程序要素例1.2是一个简单的C语言程序,我们用它来解释C程序的结构。一个完整的C语言程序,是由头部文件、一个名称为main()的主函数和若干个其它函数构成的,简单的程序可以仅有一个main()函数构成。例1.2#includeintmain(void)//一个简单的C程序{intnum;//定义一个名字为num的变量num=1;//为num赋值printf("Iamasimple");//输出信息到屏幕printf("computer. ");printf("Myfavoritenumberis%dbecauseitisfirst. ",num);return(0);}运行结果:Iamasimplecomputer.Myfavoritenumberis1becauseitisfirst.下面我们仔细阅读这个程序。一、头部文件#include语句是预处理指令,并不是C语言的可执行语句,它只是指定了程序引用的头18/247 《C语言程序设计基础》教学大纲草案2021-10-237:25部文件。头文件的作用是:(1)通过头文件来调用库功能。在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库功能,而不必关心接口怎么实现的。编译器会从库中提取相应的代码。(2)头文件能加强类型安全检查。如果某个接口被实现或被使用时,其方式与头文件中的声明不一致,编译器就会指出错误,这一简单规则能大大减轻程序员调试、改错的负担。头部文件是C语言中使用的标准库函数文件的计算机目标码,比如输入函数printf()需要使用I/O库函数文件stdio.h,三角函数sin()需要使用数学库函数文件math.h等。库文件由#include预处理指令指定后,在链接时候被嵌入到程序的目标码中。二、主函数例1.2中包括一个名字为main的函数,圆括弧表示main()是一个函数名称。int表示main()返回一个整数,void表示main()不接受任何参数。这是美国国家标准化协会(ANSI)制定的ANSIC标准99中规定的格式。函数是C语言程序的基本单位。main()函数是程序执行的入口和结构主体,可以理解成一篇文章的标题索引,我们在主函数中给出程序的各工作环节,好像对任务画龙点睛,具体怎么做,由其它子函数来描述,可以把子函数的作用看成相当于文章的各段章节,具体叙述工作的方法。C语言程序总是从main()函数开始执行。一个C语言程序必定须要一个主函数,比如我们要盖一栋房子,必须有一个地基,可以没有子函数,好像我们可以把地基铺的很大,所有楼层房间的功能都展宽到平面上实现,功能虽然可以实现,但是这栋房子就是没有结构可说。程序一定是从main()函数开始执行,而不论主函数其在程序中的位置,当主函数执行完毕时,亦即程序执行完毕。习惯上,将主函数main()放在最前头。三、函数结构任何函数,包括主函数main()都是由函数说明和函数体两部分组成。其一般结构如下:函数类型函数名(参数表){函数体(执行语句)}花括弧“{”表示函数开始,花括弧“}”表示函数结束,括弧中间包含的C语言语句称为函数体。函数体一般由若干条可执行的C语句构成。一个程序按任务分解,可以由若19/247 《C语言程序设计基础》教学大纲草案2021-10-237:25干个函数构成,每个函数完成一个特定的功能。读解程序需要一些必要的辅助信息。符号“//”之后是程序的注释信息,这是为了便于读程序,它仅在一行内有效。另一种注释方法是用符号对“/*”和“*/”,其中,“/*”表示注释开始,结尾符号“*/”表示注释结束,它能跨越多行对程序注释,但是必须配对应用,否则编译出错。intnum;//变量声明语句这条语句表明你将使用这个变量,并且它是整数(int)类型的。在C程序设计中,所有变量必须预先声明,否则编译程序不予承认。声明语句的位置在函数体内的C语句之前。变量声明在C语言中非常重要。它完成两件事:(1)在函数中你可以使用一个名字为num的变量;(2)int是C语言的关键字,说明num是一个整型量。程序据此在内存中为num分配一个2字节的存储空间。C99标准遵循C++规则,允许变量声明放在代码块的任意位置,但是,必须是在首次使用该变量之前进行声明定义。一个声明语句可以定义多个变量,变量之间用“,”隔开。Num=1;//给变量赋值该语句把值1赋值给了num。Printf("Iamasimple");//输出信息到屏幕printf("computer. ");这是输出语句,它在屏幕上输出“Iamasimplecomputer.”,符号“ ”是输出语句的换行命令而不是输出信息,它让下一次的屏幕输出位置从新的一行开始,而不是接着屏幕当前的字符位置继续。printf("Myfavoritenumberis%dbecauseitisfirst. ",num);该条输出语句中的“%d”指示输出num变量的位置和形式,它把num内嵌在用引号引起来的词组中进行输出。Return(0);这是一个函数返回语句。C函数通过它可以给它的调用者返回一个数值,好像数学函数y=f(x)中的y,该数值由函数类型决定。1.4.2.2C语言程序的结构化设计风格所谓结构化程序设计,是把程序总体任务分解成多层子任务,相应于C程序中的函数。20/247 《C语言程序设计基础》教学大纲草案2021-10-237:25于是,我们可以用函数构造出一个程序框架,具体的功能实现在函数体内的语句完成。函数之间的接口,也就是子任务之间的数据衔接,可以在函数调用过程中的参数传递。因此,主函数main()就是任务大纲,它不负责功能实现,只是呈现任务完成的路线,纲举目张,各功能函数就是目。软件设计模块化方法的基本思想是为了解决一个大的任务,可以:1把它分成多级子任务,每个子任务实现单元功能;2分别解决每个小任务;3把各小任务的功能组合起来,即可实现原任务的功能。这是所谓分而治之策略来解决程序设计问题。图1.9是标准的C程序结构要素。典型C程序#include预处理指令intmain(void)程序一定是从main()函数开始执行statements函数由语句构成functiona()一般函数模块statements函数由语句构成C程序中的5类语句functionb()statementsdeclaration关键字函数是C程序的构造单元assignment标识符function函数control运算符null数据图1.9C程序结构我们试用下面的例子来说明。有两个多边形平面如图1.10所示,我们根据输入参数求它们面积。其中图1.10(a)的平面多边形输入参数是h、w和l。图1.10(b)的平面多边形输入参数是h、r、w和a。设各参数均是实数。例1.3是我们为此项任务编制的一个程序。弓形面积1222Sararah2hwwa注:a是圆心角弧度wrl(a)图1.10计算多边形面积(b)例1.321/247 《C语言程序设计基础》教学大纲草案2021-10-237:25#include//指定I/O库函数#include//指定数学库函数#definePI3.14//定义常数intmain(void){floath,w,a,l,r;//定义变量printf(“Inputtheheight: ”);//输入高度scanf(“%f”,&h);printf(Inputthewidth: ");//输入宽度scanf(“%f”,&w);printf(Inputthelength: ");//输入长度scanf(“%f”,&l);printf(Inputtheradius: ");//输入半径scanf(“%f”,&r);printf(Inputthearc: ");//输入圆心角弧度scanf(“%f”,&a);printf(“theareaofSa=%f ”,w*h/2+w*l);//输出面积aprintf(“theareaofSb=%f ”,PI*r*r+w*h/2-2*(a*r*r/2-a*(sqrt(r*r-a*a))));//输出面积breturn(0);//返回}这个程序从结果上看,它能根据输入参数正确的计算两个多边形的面积。但是从结构上看,我们说它没有功能函数模快,主函数好像一个平面,把所有任务都放到了一起,换句话说它没有体现结构化的程序设计方法。当程序任务非常复杂的时候,程序规模将达到上万条语句,在一个平面上要顺序的完成所有任务,不可避免地要使用GOTO语句,这样的程序读起来非常的晦涩难懂,要知道,你编制的程序是要给人读的,为此,仅仅是注释并不足以让程序清晰可读。好像一篇论文,好的论文主题突出,结构清晰,表明你的思维非常有条理,能让人层次分明地看到你所要表达的设计思想。。现在看一下结构化的程序设计方法是什么。例1.4#include//指定I/O库函数#include//指定数学库函数22/247 《C语言程序设计基础》教学大纲草案2021-10-237:25#definePI3.14//定义常数voidinput(float*,float*,float*,float*,float*);//函数声明floatarea_Sa(float*,float*,float*);floatarea_Sb(float*,float*,float*,float*);//以下是程序intmain(void){floath,w,a,l,r;//定义变量input(&h,&w,&l,&r,&a);//调用参数输入函数printf(“theareaofSa=%f ”,area_Sa(w,h,l));//调用Sa面积计算函数printf(“theareaofSb=%f ”,area_Sb(w,a,r,h));//调用Sb面积计算函数return(0);}//inputtheparametervoidinput(float*h,float*w,float*l,float*r,float*a){printf(“Inputtheheight: ”);//输入高度scanf(“%f”,h);printf(Inputthewidth: ");//输入宽度scanf(“%f”,w);printf(Inputthelength: ");//输入长度scanf(“%f”,l);printf(“Inputtheradius: ");//输入半径scanf(“%f”,r);printf(“Inputthearc: ");//输入圆心角弧度scanf(“%f”,a);}//theareaofSafloatarea_Sa(floatw,floath,floatl){23/247 《C语言程序设计基础》教学大纲草案2021-10-237:25return(w*h/2+w*l);}//theareaofSbfloatarea_Sa(floatw,floata,floatr,floath){return(PI*r*r+w*h/2-2*(a*r*r/2-a*(sqrt(r*r-a*a))));}例1.4完成的任务和前面的例1.3相同,但是它读起来非常的清晰明了,结构如图1.11所示。主函数给出了任务实现的模块结构,好像我们在WORD下打开一个用大纲结构写出的文档,函数就是标题,读主函数相当于看文档结构图,读细节内容可以在相应标题下,也就是子函数体内找到。intmain(void)intmain(void)调用语句输入参数计算三角形多边形面积输入参数函数计算圆弧形多边形面积三角形多边形面积函数(a)平面结构程序圆弧形多边形面积函数(b)模块化结构程序图1.111.5程序与算法1.5.1人类的认知能力与计算机智能的上限针对某一个问题求解,必须设计出解决该问题的算法,才能编制出优秀的程序。算法就是我们解决现实问题的方法,程序是它在计算机上的实现。人类能认知世界,首先是因为大脑具有近乎无限制的记忆能力,其次是大脑具有快速分析和处理海量存储数据之间内在关联信息的能力,就是思维方式。比如外语学习,我们具有某种语言的听说能力,反映了你记忆这种语言单词量的多寡,认识一个单词的过程可以分解为如下步骤:将听到或看到的单词记忆下来,以后每当大脑看见这个单词,通过快速搜索我们的记忆体,找到并识别出这个单词,我们称之为检索功能,这同时也是又一次新的学习过程,过程重复的越多,大脑对这个单词的记忆越深刻,记忆越久远。一个人单词量多,说明他的记忆能力非常好,能记住海量单词并迅速的在大脑中反映(检24/247 《C语言程序设计基础》教学大纲草案2021-10-237:25索)出来。计算机的记忆体就是存储器,大脑是CPU和操作系统的功能结合。现在,我们如果要计算机具有英语单词识别能力,首先是给它输入并存储一本英汉字典数据(记忆词库),然后编制一段检索程序,每当从键盘读入一个单词,CPU就搜索词库内的每一条词目,找到一个匹配的单词,并将它的注释信息输出到显示器上,作为单词的认知结果告诉我们。计算机的聪明与否,取决于我们对客观世界的认知能力。比如计算机处理英汉字典的功能非常强,因为我们非常清楚人类大脑简单的识别一个单词的思维模式,也就是知道查找字典的方法,所以很容易设计一个程序算法让计算机运行,因为计算机的海量存储功能和精确计算与搜索的功能比我们人类更好,所以它记忆单词的能力和识别反映能力比人类更好。但是计算机很难翻译,或者理解一段语言文字,也可以说它很难拼写、叙述一段正确的有意义的语言信息。这是因为我们可以看懂或听懂一段语言,但是我们不清楚大脑是如何简洁明了的理解自然语言中的语法关系,如何通过分析单词之间上下文关系理解语义的,这种思维模式的描述困难,使得我们无法有效的编制程序让计算机理解自然语言。至今为止,计算机科学家仍然在致力于探索大脑理解自然语言的机理,有科研成果,但远未实用,甚至不如一个儿童对自然语言的理解与交流能力。人类大脑的思维能力是通过后天学习获得的,而计算机的思维与运算处理能力是通过我们编制的程序赋予的。1.5.2算法概念算法(Algorithm)是在有限步骤内求解某一问题所使用的一组定义明确的规则。通俗点说,就是计算机解题的过程。在这个过程中,无论是形成解题思路还是编写程序,都是在实施某种算法。前者是推理实现的算法,后者是操作实现的算法。一个算法应该具有以下五个重要的特征:(1)有穷性:一个算法必须保证执行有限步之后结束;(2)确切性:算法的每一步骤必须有确切的定义;(3)输入:一个算法有0个或多个输入,以刻画运算对象的初始情况,所谓0个输入是指算法本身给定了初始条件;(4)输出:一个算法有一个或多个输出,以反映对输入数据加工后的结果。没有输出的算法是毫无意义的;(5)可行性:算法原则上能够精确地运行,而且人们用笔和纸做有限次运算后即可完成。25/247 《C语言程序设计基础》教学大纲草案2021-10-237:25虽然设计一个好的求解算法更像是一门艺术,而不像是技术,但仍然存在一些行之有效的能够用于解决许多问题的算法设计方法,你可以使用这些方法来设计算法,并观察这些算法是如何工作的。一般情况下,为了获得较好的性能,必须对算法进行细致的调整。但是在某些情况下,算法经过调整之后性能仍无法达到要求,这时就必须寻求另外的方法来求解。1.5.3算法与程序效率先看一个例子。我们知道学校教务中心有一个学生数据库,可以从中检索学生的各种信息。假设现在要打印自动化系自31班学生花名册,并且假定班上有50名学生,要求花名册按照同学姓名的拼音顺序排列。我们看一下,不同的程序设计(算法实现)所得到不同的检索效率。一个直接的算法是将50个学生所有可能排列的表都打出来,然后从中挑选一张符合拼音顺序的表。要知道,50个人的不同排列有50!种,即这样的表有50!张,它大约是3×1064。这个数目之大,用每秒100万次的计算机不停地运算需要9.6×1048世纪,显然,用排列组合方式构造的检索方法是不能实施的。我们为此设计一个算法来提高程序的检索效率,也就是常用的排序算法。随机的将50名同学名字排列在一起,也就是说初始无序,如图1.12所示。取第二位同学的名字依拼音顺序和第一位的名字比较一次,如果顺序,则仍然放在第二位置,否则交换他们的位置,使之顺序。现在开始比较第三位,第三位则需要和前两位的名字比较至多两次,交换至多两次。依次类推,第k位至多要比较k-1次,第50位至多需要比较49次,交换至多49次。于是,比较和交换次数最多都是1+2+…+49=49×50/2=1225次,就完成了排序过程。当参加排序的个数是n时,第一种算法需要运算n!(当n>25,n!>10n)次,第二种算法至多需要(n–1)n/2次,约是n2数量级。前者的次数随n的增加,按超10n的指数方式增加,后者则只按n的二次多项式的方式增加。一般地,假如在一个问题中有n个数据需要处理,而处理的算法的计算次数依指数n方式增加,称之为指数算法;若按n的多项式方式增加,则称为多项式算法。当今的计算机无法承受指数算法的运算。因此寻找各种问题的多项式算法,乃是数学发展的一个重大的关节点。因此,算法的优劣程度就是程序执行的效率高低。26/247 《C语言程序设计基础》教学大纲草案2021-10-237:25BDEACGF.....①初始序列的顺序是任意BDEACGF.....②取第二位依拼音顺序和第一位比较有序?是,认为前2个已经按字母顺序有序BDEACGF.....③取第三位顺序和前二位比较有序?是,因为前2个已经按字母顺序有序,所以,不用比较第一位元素BDEACGF.....④继续取第四位和前三位比较有序?否,交换位置BDAECGF.....⑧⑨有序?否,交换位置BADECGF.....有序?否,交换位置⑤连续交换三次比较三次后找到合适位置,ABDECGF.....继续从第五位开始排序有序?否,交换位置ABDCEGF.....有序?否,交换位置⑤交换二次比较二次后找到合适位置,ABCDEGF.....继续从第六位开始排序有序?是,前6个已经按字母顺序有序ABCDEGF.....⑥继续排序有序?否,交换位置⑦交换二次比较二次后前七个元素ABCDEFG.....已经排好序,继续对后续元素排序图1.12插入排序思想1.6C语言的关键字表1.3是C语言关键字。关键字是C语言自己的词汇,因此,不能用关键字作为程序中的标识符,也就是函数或变量名。ANSIC一共只有32个关键字,C99又新增了5个。如果试图用一个关键字作为变量名,编译过程会作为语法错误出现。此外,还有一些C语言的保留字,虽然编译不产生错误,但是,作为C语言的保留字27/247 《C语言程序设计基础》教学大纲草案2021-10-237:25很容易产生意想不到的问题。所谓保留字包括以下划线开始的标识符和标准库函数名,比如printf()、sin()等。表1.3C语言关键字autodoubleinlinesizofvolatilebreakelseIntstaticwhilecaseenumLongstruct_Boolcharexternregisterswitch_complexconstfloatrestricttypedef_imaginarycontinueforreturnuniondefaultgotoshortunsigneddoifsignedvoid表1.3中的斜体关键字为C99新增的5个。注意,在C语言中关键字都是小写的。28/247 《C语言程序设计基础》教学大纲草案2021-10-237:25第二章数据与变量2.1数据的概念一个程序应该包括两个方面的内容:(1)数据的描述。(2)操作步骤,即动作的描述。数据是操作的对象,操作的结果会改变数据的状况。打个比方,厨师作菜肴,需要有菜谱,菜谱上应该包括:①配料,指出应使用那些原料;②操作步骤,指出如何使用这些原料按规定的步骤加工成所需的菜肴,没有原料是无法加工的。面对同一些原料可以加工成不同风味的菜肴。作为程序设计人员,必须认真考虑和设计数据结构和操作步骤,即算法。2.1.1数据是客观事物属性的描述计算机用变量来描述客观事物的属性,数据就是变量的取值。早期计算机只是用来做科学计算。它要处理的数据就是数学上的实数,整数,字符等。随着计算机应用领域的普及,它现在处理的数据包括声音,图像,文字,表格等,包罗万象。比如,我们经常说数字化中国,数字化北京,学生管理数字化等等。一个客观事物具有多种属性,每一个属性可以用一个变量来描述。比如前面图1.8描述的电阻和导线,它们有各自的特性参数。电阻的属性包括物理尺寸、颜色、重量、阻值,导线的属性有线径、长度、材质、外包绝缘材料等。我们知道,电阻的物理尺寸、重量取值是实数,于是我们用浮点型变量描述,阻值取值是整数,我们就用整型变量描述,颜色是文字,我们可以用字符变量描述。同样,导线的线径、长度、材质、外包绝缘材料分别用整数变量和文字变量描述。还有,学生也是客观存在的实体。他有学号,姓名,性别,出生年月日等多种属性。学号是一个整数,相应的变量就是整数类型,姓名由多个汉字组成,于是变量就是一个字符串,性别、出生年月日也可以用字符串描述。因此,我们用表2.1属性集合作为学生这个客观实体的特征描述。将表2.1编制到程序中,通过对表2.1变量集合的取值,即通过键盘将学生信息输入到计算机中,就是一个特定的学生的数据信息,也称之为一条纪录。计算机因此能清晰的刻画一个学生个体。就是所谓学生信息数字化,或者说是把学生信息存储到计算机中。29/247 《C语言程序设计基础》教学大纲草案2021-10-237:25表2.1学生属性属性学号姓名性别出生日期民族家庭住址系别变量numberNamesexbirthdaynationaddressDepartment比如,自动化系学生李远的数据记录信息在计算机上如表2.2所示,通过对李远数据纪录的检索,就可以得知该学生的基本情况。表2.2一条学生记录就是属性集合的取值numbernamesexBirthdaynationaddressdepartment031005李远男1985年11月汉杭州黄龙洞自动化系现在,我们知道要想让计算机描述各种客观事物,它就必须具有多种类型变量的定义能力,也就是计算机语言的数据类型问题。C语言之所以能广泛的应用于各个领域,除去它的编程效率以外,基本数据类型丰富,以及自定义复合数据类型(数据结构)的能力,是一个非常重要的原因。2.1.2C语言的数据类型图2.1是c语言数据类型描述。长整型整数型整型短整型字符型单精度基本类型实数型(浮点型)双精度枚举型布尔型(逻辑型)数组型数据类型构造类型结构体型指针类型共用体型空类型图2.1C语言数据类型图2.1c语言数据类型描述一个数据,其实还应包括它在计算机中的存储形式,因为变量需要存储在计算机的内存中。我们以字节为单位划分变量在内存中占用的存储单元长度(一个字节由二进制的8个bit组成),不同数据类型的变量其长度是不同的,比如整数型变量的长度是2字节,字符型变量的长度是单字节,程序据此对不同数据类型的变量分配内存单元。表2.3是C语言数据类型关键字及存储单元长度参考,变量所占存储长度应该根据所使30/247 《C语言程序设计基础》教学大纲草案2021-10-237:25用的系统环境确定,比如Windows98和NT系统上的int长度就是4字节。表2.3C数据类型关键字K&R关键字C90关键字C99关键字数据类型存储单元(字节)int整型变量2long长整型变量4short短整型变量1Unsigned无符号数2char字符型变量1float浮点型变量4double双精度浮点数8signedvoid_Bool布尔型变量1_Complex复数_imaginary虚数基本数据类型就是C语言支持的基本数据定义能力,除此以外,C语言不再支持其它类型数据变量的定义,比如,学生的出生年月日,我们不能直接定义一个日期型数据变量进行描述,只能通过定义一个字符类型数组的变量描述它。构造类型是说,我们可以通过C语言支持的数据结构定义能力,将所需要的基本型数据汇集到一起,成为一个新的数据类型,这个新数据类型定义之后,可以直接在你设计的程序中如同使用基本数据类型一样的进行引用。为什么需要构造类型变量?比如,表2.2的学生实体是由多个基本类型的数据变量描述的,假定现在要在程序中描述一个学生的数据,你不能直接将学生看成一个变量,因为程序语言不支持这种形式的变量,所以,我们需要在程序中将学生的各个属性变量分别写出来,非常麻烦。可是,根据C语言的结构体定义形式,我们可以构造一个复合数据类型:structstudent{intnumber;charname[20];charsex;charBirthday[8];charnation;charaddress[40];chardepartment[20];};31/247 《C语言程序设计基础》教学大纲草案2021-10-237:25struct说明了一个新的数据类型student,它就是学生变量,由花括弧里的基本类型变量组成。于是,我们可以直接在程序中使用我们自己定义的,新的学生数据类型,比如:structstudentrecord;定义了一个学生数据类型(student类型)的学生结构变量record。C语言构造结构体的能力,我们也称之为数据结构的节点定义方法。它和指针类型变量结合,是我们在数据结构设计中使用c语言的根本原因,因此,几乎所有的关系数据库以及操作系统的内核都用C语言编写。指针是一种特殊的数据类型,在其它语言中一般没有。因为程序中的变量是存储在内存中的,指针就是指向变量的地址,即指针是存贮单元的地址。因为不同类型的变量其占用的存储单元长度不同,因此,根据所指的变量类型不同,C语言有整型指针(int*)、浮点型指针(float*)、字符型指针(char*)、结构指针(struct*)和联合指针(union*)。指针的概念在C语言学习中比较难以理解,因为它需要熟悉内存与变量存储的关系,也就是需要一些硬件知识。有关结构体、指针的内容将在后续章节中介绍。2.1.3常量与变量2.1.3.1常量在程序运行中,其值不能被改变的量称为常量。常量分为不同的类型,如12、0、-3为整型常量,4.6、-3.15、6.1为实型常量,‘a’、‘d’为字符型常量。常量一般从其字面形式即可判别。也可以用一个标识符代表一个常量。如:#definePI3.1415这是在程序中用"#define"命令行定义PI代表实型常量3.1415,此后,凡是在程序中出现的PI都是3.1415。2.1.3.2变量其值可以改变的量称为变量。一个变量应该有一个名字,在内存中占据一定的存储单元。程序在该存储单元中存放变量的值。请注意区分变量名和变量值的概念。和其它高级语言一样,用来表示变量名、符号常量名、函数名、数组名、类型名、文件名的有效字符序列称为标识符。简单地说,标识符就是一个名字。C语言规定标识符只能由字母、数字和下划线三种字符组成,且第一个字符必须为字母或下划线。注意,大写字母和小写字母被认为是两个不同的字符。因此,SUN和sun是两个不同的变量名。习惯上,变32/247 《C语言程序设计基础》教学大纲草案2021-10-237:25量名用小写字母表示,可以增加程序的可读性。注意,在C语言中,要求对所有用到的变量作强制定义,也就是“先定义、后使用”。好的习惯是在函数体开始就声明所有要用的变量,并同时初始化。表2.4列出了常量与变量的基本概念。表2.4常量与变量常量定义在程序运行过程中,其值不能被改变的量,它不占用内存单元习惯习惯上,符号常量名用大写,变量名用小写1.含义清晰,见名知意好处2.需要改变一个常量时能做到一改全改,程序中所有引用的地方全部根据定义而改变注意符号常量不同于变量,它的值在其作用域内不能改变,也不能被赋值正确例子#definePRICE30定义PRICE为常量,其值为30错误例子PRICE=40;变量定义可以改变的量组成变量名(实际上是一个地址),变量值(变量的数值),它需要存储单元存放数值在程序编译过程中,系统给每个变量名分配一个内存地址。在程序中从变量中取值,实原理际上是通过变量名找到相应的内存地址,从其存储单元中读取数据。格式只能用字母、数字、下划线组成,必须以字母开头在C中,要求对所用的变量作强制定义,也就是“先定义,后使用”注意在C中对名称是区分大小写的,即大写字母和小写字母被认为是两个不同的字符。2.2C数据类型详解计算机中的任何一种数据类型都有它的取值范围,也就是它所能表示的数值大小,超出这个范围,我们称之为溢出。一旦数据发生溢出,就会产生运算错误。为此,我们必须根据具体任务的要求来选择不同类型的变量。选择的标准是:(1)变量的精度和取值范围能满足程序要求;(2)在满足要求的前提下,选择存储长度短的变量类型,比如,能用整数表达的不要用长整型变量,单精度浮点数能满足要求的就不要选择双精度,那样既占用内存又增加运算时间。所以,C语言有短整型数,整型数和长整型数、浮点数等多种格式供我们选择。2.2.1int类型理解int类型变量的实质,是要清楚它在计算机中的存储形式。整数以二进制形式、以字节为单位存储在计算机的存储器中。它有正负之分,但它没有小数,比如2、-23、245633/247 《C语言程序设计基础》教学大纲草案2021-10-237:25都是整数,但是2.0、-23.0、2456.00就不是整数。int类型变量所能表达的范围是多少?为此,我们看一下计算机是如何描述和存储一个int类型变量的,见图2.2所示。21528272000000000000000000215282720111111111111111165535(a)2字节二进制数的表示范围intsowa;逻辑定义==>分配内存==>物理存储(b)int型变量sows定义之后在内存占用一个存储单元intsowa=2;2逻辑定义==>分配内存==>物理存储(b)int型变量sows定义并且初始化之后在内存占用一个存储单元并且存储初值为2图2.2整型变量说明图2.2(a)实际上是一个无符号的整型数,它的取值范围在0~216-1,超出这个范围,它就是溢出。比如:unsignedinti=65536;因为65536的二进制形式是1’0000’0000’0000’0000,而i被定义成2字节的无符号整型数,根本没有第16位,在第16位上的1就是溢出。因此,如上初始化的整型量i,它在内存的值实际上就是0000’0000’0000’0000。有符号的整数是以补码形式存储在计算机中的,因为符号占用了一个二进制位,所以它所表达的数值范围与同字节长度的无符号数有所不同,详细内容见附录一。表2.5和表2.6分别列出了整型数的表示方法,以及特点。表2.5整型常量十进制整数如123八进制整数以0开头如0123十六进制整数以0x开头如0x123特点整型常量值在-32768~+32767范围内,编译器认为是int类型如123整型常量的值超过上述范围,而在--2147483648~+2147483647范围内,编译器认为是long类型在整型常量的值后面加字母L(大写L或小写l),则告诉编译器,把例:123L、0L该整型常量作为long类型处理常量无unsigned型34/247 《C语言程序设计基础》教学大纲草案2021-10-237:25表2.6整型变量数据在内存中的数据在内存中是以二进制补码形式表示的(附录1),每个整型变存放形式量在内存中占2个字节。按数字的范围定义:基本整型(int),短整型(short),长整型(long)整型变量的分类按符号:有符号(signed),无符号(unsigned)整型变量的定义数据类型变量名(如inta,b;//定义a,b两个整型)表2.7ANSI标准定义的整数类型类型比特数范围取值范围int16-215~215-1-32768~32767unsignedint160~216-10~65535short16-215~215-1-32768~32767unsignedshort160~216-10~65535long32-231~231-1-2147483648~2147483647unsignedlong320~232-10~4294967295注:C标准没有具体定义各类数据所占的字节数,只要求long不短于int,short不长于int2.2.2char(字符)类型char类型数据用于存储字符和标点符号一类的符号,在内存中字符以ASCII码存储,其存储方式与整数类似。从而,C语言允许字符和整数之间进行运算。2.2.2.1字符常量字符常量是用单引号(撇号)括起来一个字符,如:'a'、'x'、'D'、'?'、'$'。注意,'a'和'A'是不同的字符常量,因为它们的ASCII码不同。以""开头的字符序列,称为“转义序列”,“”使其后面的字符变为另外的意义,见表2.9。表2.9转义字符字符形式功能 换行符t横向跳格:跳到下一个输出区(每一输出区为8个字符位置)v竖向跳格b退格r回车(回到本行起始字符位置)f走纸换页\反斜杠字符\'单引号(撇号)'ddd1~3位八进制数所代表的字符。如101表示'A'xhh1~2位十六进制数所代表的字符。如x40表示'A'转义序列主要用来控制打印机和屏幕输出。如:printf(" sumis%d ",sum);35/247 《C语言程序设计基础》教学大纲草案2021-10-237:25有关字符常量的几个例子如下:charboiled;//声明一个字符变量boiled=’T’;//赋值为常量Tboiled=T;//编译错误,T被看成一个变量boiled=”T”;//编译错误,”T”被看成由三个字符组成的字符串boiled=65;//编译把65看成字符A的ASCII码,所以65等同于A注意转义字符的运用:boilde=' ';//n被转义为换行命令printf("theisboiledvalue=%c",boilde);//屏幕上无任何显示,仅将指示当前位置的光标换行boilde='n';//字符常量nprintf("theisboiledvalue=%c",boilde);//屏幕上显示字符n2.2.2.2字符变量字符变量在内存中占一字节,以ASCII码存放,因为其存储方式与整数类似,所以它被看成整型数,因而对unsignedchar类型来说,其取值范围是0~255,对于char来说就是-128~+127。如下是字符变量定义与赋值。charc1,c2;//定义c1、c2为字符变量c1='a';c2='b';//赋值为字符a和b表2.10列出了字符类型的特点。表2.10字符类型字符常量C的字符常量是用单引号‘’括起一个字符。如‘a’C中还用一种特殊形式的字符常量,就是以开头的字符序列。字符变量定义一个常量chara,b;//定义a,b两个字符型变量chara='y';输出格式用%c格式输出一个字符型的变量printf("%c,a");在C语言中,字符型数据和整型数据之间可以通用,它们在内存的存储形式一样。但字符注意数据只占一个字节,只能放0~255内的整数,同时也可以定义signedchar型,它的取值范围是-128~127,并用%d格式输出(把字符型变量当作整型变量时采用这种格式)。2.2.2.3字符串常量字符串常量与字符常量的区别是:(1)字符常量:单引号括起来的一个字符。36/247 《C语言程序设计基础》教学大纲草案2021-10-237:25(2)字符串常量:双引号括起来的字符序列(0~N个字符)。如:"Howdoyoudo.","CHINA","a","$123.45"字符串常量在内存中的存放是每一个字符均以其ASCII码存放,且最后添加一个“空字符”,即二进制00000000,记为NULL或(注意字符'0'的ASCII码是0x30即00100000)。例如,字符串常量"CHINA"存放在内存中的情况是:(6字节存储器,不是5字节)CHINA因此,字符'a'和字符串"a"的区别是:字符'a':1字节(值为97)字符串"a":2字节(值为97,0)因为字符串在内存需要占用一段连续的区域,所以,定义字符串的时候必须使用字符型数组的形式。表2.11字符串常量定字符串常量是一对双引号“”括起来的字符序列义在C中规定,在每个字符串的结尾加一个“字符串结束标志”,以便系统判断字符串是否结束。C规定规以字符‘’作为字符串结束标志。书写的时候不必加上‘’,它是系统自动加上的,所以字符串“CHINA”定的实际长度是6个字符。注在C中没有专门的字符串变量,如果有需要可以用数组来存放字符串意如何将一个字符串赋值给一个字符型数组?所谓数组,就是在内存中连续存放的某一种数据类型的元素,比如字符型数组,它在内存中一个区域内连续的存放字符型数据元素,所以它可以存放字符串。你不能像赋值给变量一样,将一个字符串赋值给数组,如:charname[20];name=”tsinghua.edu.cn”;将字符串赋值给数组有如下几种方法:(1)变量声明时的初值charname[20]=”tsinghua.edu.cn”;(2)strcpy()库函数strcpy(name,”tsinghua.edu.cn”);//头部函数是string.h(3)scanf()函数scanf(“name=%s ”,name);前两个例子适用于字符串常量,第三个例子适用于字符串变量,但是strcpy()函数同样37/247 《C语言程序设计基础》教学大纲草案2021-10-237:25可以将一个字符数组拷贝到另一个数组中。2.2.3_Bool类型_Bool类型由C99以後标准引入,用于表示逻辑变量值,即逻辑值true(真)与flase(假)。因为C语言用值1表示true,用值0表示flase,所以_Bool类型其实仍是整型类型。_Bool类型在程序中用来选择执行流向,见后面的程序流设计内容。2.2.4实型(float)类型实型也称为浮点型,实型常量也称为实数或者浮点数。在C语言中,实数只采用十进制。它有二种形式:十进制数形式和指数形式。(1)十进制数形式由数码0~9和小数点组成。例如:0.0,.25,5.789,0.13,5.0,300.,-267.8230等均为合法的实数。(2)指数形式由十进制数,加阶码标志“e”或“E”以及阶码组成,阶码只能为整数,可以带符号。其一般形式为aEn,其中a为十进制数,n为十进制整数,其值为a×10n。如:2.1E5=2.1×105,3.7E-2=3.7×10-2,0.5E7=0.5×107,-2.8E-2=-2.8×10-2等。下述这些写法不是合法的实数:345(无小数点),E7(阶码标志E之前无数字),-5(无阶码标志),53.-E3(负号位置不对),2.7E(无阶码)。标准C允许浮点数使用后缀。后缀为“f”或“F”即表示该数为浮点数。如356f和356.是等价的。实型变量说明的格式和书写规则与整型相同。例如:floatx,y;//x,y为单精度实型量doublea,b,c;//a,b,c为双精度实型量实型数据类型主要用于数学计算和财务统计上,float类型占用内存是4个字节,当float类型精度不够时,进而可以采用双精度数据类型double,它占用了8个字节。图2.3是浮点数在内存中的存储形式。指数占1个字节2720202728215216223假想的小数点位置尾数占3个字节图2.3浮点数(float)的存储形式实型常数不分单、双精度,都按双精度double型处理。38/247 《C语言程序设计基础》教学大纲草案2021-10-237:25程序处理上,实数数据类型要特别注意精度造成的四舍五入问题,表2.12是实数数据类型的有效位数和取值范围说明。例2.1说明了float、double的不同之处。表2.12浮点数类型实型常量十进制小数形式.123注意:字母e前必须有数字,后面的指数必须是两种表示方式指数形式:1.23e-1整数1.234e123被称为“规范化的指数形式”实型变量指数部分采用规范化的指数形式。在实际的计算机中是用二进制来表示小数部分以及在内存中的表示用2的幂次来表示指数部分实型变量的分类类型字节数/比特数小数部分/指数部分有效数字取值范围float4/3224/8710-37~10+37double8/6448/161610-308~10+308Longdouble16/12896/32不低于double例2.1intmain(){floata;doubleb;a=3333333.333;b=3333333333333333.333;printf("a=%f b=%f ",a,b);return(0);}运行结果是(winXP、奔4迅腾、VisualC++6.0):a=3333333.250000b=3333333333333333.50000若改动a和b数值为:a=33333.33333;b=33333.33333333333333;则运行结果是:a=33333.332031b=33333.333333从本例可以看出,由于a是单精度浮点型,有效位数只有七位,b是双精度浮点型,39/247 《C语言程序设计基础》教学大纲草案2021-10-237:25有效位数是16位。它们都是只保留小数点后6位数,其余部分四舍五入。使用浮点类型数据有一个上下溢出问题。在C语言中单精度型其数值范围为3.4E-38~3.4E+38,双精度型其数值范围为1.7E-308~1.7E+308。超出这个范围就是溢出,比如:floata=3.4E+38*100.0f;会产生上溢。那么下溢是什么?小于它能表示的最小数值就会产生下溢。对单精度数来说,比如用0.1234E-38除以10,所的结果是0.0123E-38,损失一个有效位,我们称之为下溢。因为浮点类型数只是数据的近似值表示,它必然存在截断误差,程序中如果不注意,经常会造成错误的结果出现。看例2.2所示:例2.2#includeintmain(){floata,b;b=2.0e20+1.0;a=b-2.0e20;printf("a=%f ",a,;return(0);}运行结果是:a=-13584010575872.000000(TC)a=-1,54008175468544.000000(VC6.0)理论上说,将一个数加一再减去一,仍能得到原来的数,但是使用浮点数计算,考虑不周的时候就可能产生如例2.2的结果。之所以这样,是因为数字2.0e20后面有20个零,加一是在其第21位上产生变化,但是单精度数只有7位有效数据,不能正确计算。如果是2.0e4代替2.0e20,程序就能计算出正确的结果,因为变化位在第五位上,单精度数据可以反映出来。2.2.5复数和虚数类型复数和虚数类型用于数学计算。它是C99以後支持的标准。一般说,有3种复数类型,即分别是float_Complex、double_Complex和longdouble_Comlex。Float_Complex包含两个float值,一个表示虚部,一个表示实部。与之相同,虚数也有3种类型:float_Imaginary、40/247 《C语言程序设计基础》教学大纲草案2021-10-237:25double_Imaginary和longdouble_Imaginary。如果引用了复数和虚数类型的头部文件complex.h,则可以用complex代替_Complex,用imaginary代替_Imaginary,而符号I表示-1的平方根。2.2.6各类数据类型的混合运算整型、实型(包括单、双精度)、字符型数据可以混合运算,例如:10+'a'+1.5-8765.1234*'b'混合运算时,先转换成同一种类型,然后进行运算。转换有自动转换和强制转换两种情况。2.2.6.1自动转换自动转换发生在不同数据类型的变量混合运算时,由编译系统自动完成。自动转换遵循以下规则:(1)若参与运算量的类型不同,则先转换成同一类型,然后进行运算。(2)转换按数据长度增加的方向进行,以保证精度不降低。如int型和long型运算时,先把int量转成long型后再进行运算。(3)所有的浮点运算都是以双精度进行的,即使仅含float单精度量运算的表达式,也要先转换成double型,再作运算。(4)char型和short型参与运算时,必须先转换成int型。(5)在赋值运算中,赋值号两边量的数据类型不同时,赋值号右边量的类型将转换为左边量的类型。如果右边量的数据类型长度左边长时,将丢失一部分数据,这样会降低精度,丢失的部分按四舍五入向前舍入。图2.4表示了类型自动转换的规则。以如下程序运算为例:41/247 《C语言程序设计基础》教学大纲草案2021-10-237:25intmain(void){floatPI=3.14159;ints,r=5;s=r*r*PI;printf("s=%d ",s);return(0);}显示程序运行结果:s=78本例程序中,PI为实型;s,r为整型。在执行s=r*r*PI语句时,r和PI都转换成double型计算,结果也为double型。但由于s为整型,故赋值结果仍为整型,舍去了小数部分。上述转换过程是由系统自动完成的,程序员也可以强制进行某种转换。2.2.6.1强制类型转换强制类型转换是通过类型转换运算来实现的。其一般形式为:(类型说明符)(表达式);其功能是把表达式的运算结果强制转换成类型说明符所表示的类型。例如:(float)a把a转换为实型变量,(int)(x+y)把x+y的结果转换为整型。在使用强制转换时应注意以下问题:(1)类型说明符和表达式都必须加括号(单个变量可以不加括号),如把(int)(x+y)写成(int)x+y,则成了把x转换成int型之后再与y相加。(2)无论是强制转换或是自动转换,都只是为了本次运算的需要而对变量的数据长度进行的临时性转换,而不改变数据说明时对该变量定义的类型。例如程序:Intmain(void){floatf=5.75;printf("(int)f=%d,f=%f ",(int)f,f);return(0);}执行后结果是:42/247 《C语言程序设计基础》教学大纲草案2021-10-237:25(int)f=5,f=5.75程序将浮点类型的f强制转换成整型类型,但是,f虽强制转为int型,但只在运算中起作用,是临时的,f本身的类型并不改变。因此,(int)f的值为5(删去了小数)而f的值仍为5.75。2.3小结2.3.1关键概念数据有变量和常量之分。变量一定要有存储单元存放输入的数据,因此,它的名字对应着一个内存地址。常量只是编译过程中的数值带入,它不需要存储,因此没有地址问题。C语言包含多种数据类型,不同的数据类型其存储和运算有很大的不同。即使同一种数据类型,不同的计算机其存储方式也可能不同,需要具体甄别。C语言允许数据类型混合运算,其结果向数值范围大、精度高的数据类型转换。C语言以ASCII码表示所有字符。使用浮点数计算必须考虑有效数据位的概念,以使程序能计算出正确的结果。2.3.2易犯错误初学者往往容易混淆变量名、数值和地址的关系。因为地址将涉及到指针的概念,我们会在指针一节内容中详细讨论。另外,表达式中不同数据类型之间的运算转换关系,也需要特别注意。比如,在§1.4.1的程序1.1中,假设电阻和电压值均为整数,但电流计算值仍可能会产生小数,则需定义电流变量为浮点数,设程序修改如下:#includeintmain(void){intU,R;floatI;printf("Pleaseinputthevoltageandresistance ");scanf("%d,%d",&U,&R);43/247 《C语言程序设计基础》教学大纲草案2021-10-237:25I=U/R;printf("Thecurrentis%f ",I);return(0);}设输入电压为220(V)、电阻为100(Ω),理论上电流值是2.2(A),而实际运行结果是:Pleaseinputthevoltageandresistance220,100(键盘输入,注意两个输入数值之间是用“,”间隔)Thecurrentis2.000000为什么会这样呢?我们仔细看一下表达式就会发现,U和R都是整数,它们相除的结果也是整数,因此,小数部分在表达式计算中就已经被舍去,然后再赋值给I,因而即使I为浮点数,仍不能取得计算值的小数部分。注意我们一开始就提到的概念,“=”是赋值语句,将“=”右边的数赋值给“=”左边变量,因此,I=U/R是将表达式U/I的计算结果赋值给I,而不是说I等于U/R。我们可以通过强制类型转换解决这个问题:I=(float)U/R;请同学考虑,如下面的类型转换是否可以?I=(float)(U/R);有关printf()语句中的转义字符运用问题以及显示参数的格式问题,详细见printf()语句说明。44/247 《C语言程序设计基础》教学大纲草案2021-10-237:25第三章输入输出方式我们在这章主要学习以下内容:(1)物理设备与流的概念;(2)如何使用scanf()和printf();(3)如何使用格式符;(4)I/O函数getche()、getch()、getchar()、gets()、putchar()、puts();(5)有关字符输入/输出以及缓冲和非缓冲输入方式的区别;图3.1是基本的输入输出函数一览。混合型scanf()缓冲型getchar()字符型输入函数cingets(s)getche()非缓冲型getch()混合型printf()输出函数coutputchar()字符型puts()图3.1基本的输入输出函数3.1缓冲区与流的概念3.1.1缓冲区C语言的输出输入由库函数完成,C有两组I/O操作函数,即:(1)缓冲型文件系统(bufferedfilesystem),也称之为格式文件系统(formatted)。(2)非缓冲型文件系统(unbufferedfilesystem),也称之为非格式文件系统(unformatted),因为它由UNIX系统定义而来,也叫做UNIX型文件系统。缓冲,是指输入字符数据经过一个暂存队列,只在输入结束(回车键按下)时刻才被送入到计算机处理,这样,在回车键按下之前,你可以修改输入错误的字符,而当回车键按下时,最终再发送正确的输入数据到计算机。缓冲方式经常用于数据的输入。45/247 《C语言程序设计基础》教学大纲草案2021-10-237:25非缓冲方式是字符没有经过缓冲队列,每个输入的字符立刻被送到程序处理,如在游戏中,希望按下的键被立刻响应,程序去执行某个命令。图3.2显示了缓冲与非缓冲输入情形。字符逐个输入计算机对每个输入字符实时响应输入HI!HI!(a)非缓冲输入的情形遇到回车符之后计算机响应字符逐个输入输入HI!HI!HI!(b)行缓冲输入的情形图3.2缓冲与非缓冲输入的情形缓冲型输入分为两类:完全缓冲(fullbuffered)I/O和行缓冲(line-buffered)I/O。前者是在缓冲区队列完全满时被清空,将内容发送出去。这种方式一般用于文件输入,缓冲的大小取决于操作系统设定,达到设定值时候,开始I/O发送操作。行缓冲方式是输入一个换行符(回车键按下)时将缓冲区清空,将字符序列发送出去。缓冲型I/O函数的原型说明包含在头部文件stdio.h中,非缓冲I/O函数的原型说明包含在头部文件io.h中(TC2.0)。stdio.h是standardinput&output的缩写,它包含了与标准I/O库有关的变量定义和宏定义。在需要使用标准I/O库中的函数时,应在程序前使用上述预编译命令。注意,在VC6.0中,非缓冲I/O函数的原型说明包含在头部文件conio.h中。3.1.2流在讨论I/O操作之前,必须理解“流”和“文件”在概念上的区别。C语言I/O系统为程序设计人员提供了一个统一的逻辑界面,与具体的被访问设备无关,即,C语言I/O系统在程序员和物理设备之间提供了一个转换接口,或者说是一层抽象的界面,我们称它为“流”。而具体的物理实现(包括物理设备、物理存储)称之为文件。缓冲型文件系统在设计上可以支持多种不同的设备,包括终端(键盘、显示器)、磁盘驱动器、磁带机等。虽然各种设备的物理特性差别很大,但是缓冲型文件系统把每个设备都转换成一个逻辑设备:“流”,所有的流具有相同的行为(输入/输出数据),因而用来进行磁盘文件写入的函数也可以进行键盘、显示器等的读写操作,逻辑上相同,仅是物理层驱动不同,如图3.3所示。C语言I/O系统的一个基本概念就是,所有的流是相同的,但文件是不同的。C语言I/O系统通过I/O重定向让操作系统将应该传送到某一设备的数据改变传输路线,46/247 《C语言程序设计基础》教学大纲草案2021-10-237:25传送到另一指定的设备。流文件键盘物理驱动显示器I/O读写操作磁盘打印机磁带机图3.2流与I/O设备C语言中有两种类型的流:(1)文本流(textstream)。一个文本流是一行字符组成,换行符表示一行结束。(2)二进制流(binarystream)。一个二进制流对应写入到设备的内容,由字节序列组成,没有字符翻译。3.2交互式程序的例子程序的运行过程是数据输入、CPU处理、结果输出。为此,我们首先要考虑的必定是和计算机之间的数据交换方式。也就是我们如何输入数据给计算机,如何从计算机那里得到程序执行的结果,即,人机交互界面,我们也称为I/O界面。键盘是计算机的一种基本输入界面,显示器是计算机的一种基本输出界面。C语言通过格式化的输入输出函数为我们提供了控制键盘和显示器输入与输出方式的手段,或者说是人机交互界面的设计工具。我们先看程序4.1的例子。例3.1talkback.c程序#include#include//提供字符串长度计算函数strlen()的原型intmain(void){charname[40];//name是一个长度为40的字符数组intsize,letters;floatemolument;47/247 《C语言程序设计基础》教学大纲草案2021-10-237:25printf(“你好,请输入你的名字 ”);scanf(“%s”,name);printf(“%s,你在清华大学自动化系? ”,name);printf(“%s,你能告诉我你的薪水? ”,name);scanf(“%f”,&emolument);size=sizeofname;letters=strlen(name);printf(“%s,这真的是你的薪水?只有%2.2f元? ”,name,emolument);printf(“%s,你的名字有%d字节, ”,name,letters);printf(“%s,实际上你可以使用%d字节来存储你的名字. ”,name,size);return(0);}运行结果是:你好,请输入你的名字LWZ//从键盘输入LWZ,你在清华大学自动化系?LWZ,你能告诉我你的薪水?1234.1234LWZ,这真的是你的薪水?只有1234.12元?LWZ,你的名字有3字节。LWZ,实际上你可以使用40字节来存储你的名字。现在解读程序。(1)它使用了一个字符类型的数组name来存放一个字符串,因为字符串是一个字符序列,比如我们姓名拼音,它需要一个连续的区域存放多个字符类型数据,在内存开辟一个连续区域方法就是定义数组,因为数组被声明为长度40,所以字符序列的长度最多只能是40个字符。(2)它使用%s转换说明符(conversionspecification)来处理字符串的输入和输出格式。注意,在scanf()中,程序对变量weight使用了&前缀符,而name没有使用,这是因为scanf函数要求给出变量地址,如给出变量名则会出错,前缀符&是取得变量的地址,即&weight是地址,而数组的名字本身就是地址,无需&前缀符。48/247 《C语言程序设计基础》教学大纲草案2021-10-237:25(3)格式字符“%s不但说明了输出变量的类型是字符串,同时也给出了它在输出非格式字符序列中的位置,如语句printf(“%s,你在清华大学自动化系? ”,name)将名字放在非格式字符串“你在清华大学自动化系?”之前,即:LWZ,你在清华大学自动化系?(4)语句printf(“啊!%s,这真的是你的薪水?只有%2.2f元? ”,name,emolument)中,浮点数格式符%2.2f设定了数字输出的格式,精度格式符“.”前的数字为2,是设定输出数据总位数的宽度,“.”之后的数字2,是设定输出的小数位数。(5)程序使用strlen()函数取得字符串的长度。3.3格式I/O函数scanf()和printf()C语言本身不提供输入输出语句,输入和输出操作是由函数来实现的。在C的标准函数库中提供了一些输入输出函数,例如printf函数和scanf函数。在使用它们时,千万不要简单地认为它们是C语言的“输入输出语句”。printf和scanf不是C语言的关键字。完全可以不用printf和scanf这两个名字,而另外编写两个函数,另用其他函数名。C提供的函数以库的形式存放在系统中,它们不是C语言文本中的组成部分。因此各个函数的功能和名字,在各种不同的计算机系统中有所不同。不过printf和scanf在各种计算机系统中都提供,成为各种计算机系统的标准函数(标准输入输出库的一部分)。在程序编译连接时,用户程序与标准库文件相连,允许在程序中可以直接使用printf和scanf函数而不需要头部文件指定。本节介绍的是向标准输出设备显示器输出数据的语句,printf函数printf函数称为格式输出函数,其关键字最末一个字母f即为“格式”(format)之意。其功能是按用户指定的格式,把指定的数据显示到显示器屏幕上。在前面的例题中我们已多次使用过这个函数。3.3.1标准输出函数printf()3.3.1.1printf函数调用的一般形式printf函数是一个标准库函数,它的函数原型在头文件“stdio.h”中。但作为一个特例,不要求在使用printf函数之前必须包含stdio.h文件。printf函数调用的一般形式为:printf(“格式控制”,输出表列);“格式控制”是用双引号括起来的字符串,也称“转换控制字符”,它包括两种信息:(1)格式说明。由“%”和格式字符组成,例如如%d%f等。它的作用是指定输入49/247 《C语言程序设计基础》教学大纲草案2021-10-237:25的数据的格式。格式说明总是由“%”字符开始,在%后面跟有各种格式字符,以说明输出数据的类型、形式、长度、小数位数等。如“%d”表示按十进制整型输出,“%Ld”表示按十进制长整型输出,“%c”表示按字符型输出等。后面将专门给予讨论。(2)普通字符(非格式字符串)。即需要原样输出的字符,非格式字符串在输出时原样照印,在显示中起提示作用。“输出列表”是需要输出的一些数据,可以是表达式。例如:inta=2,b=3;printf("%d",a+b);初学者务必注意,输出表列中给出了各个输出项,要求格式字符串和各输出项在数量和类型上应该一一对应。例3.2printf函数的格式控制与输出表列intmain(void){inta=88,b=89;printf("%d%d ",a,b);printf("%d,%d ",a,b);printf("%c,%c ",a,b);printf("a=%d,b=%d",a,b);}程序中四次输出了a,b的值,但由于格式控制串不同,输出的结果也不相同:第四行的输出语句格式控制串中,两格式串%d之间加了一个空格(非格式字符),所以输出的a,b值之间有一个空格。第五行的printf语句格式控制串中加入的是非格式字符逗号,因此输出的a,b值之间加了一个逗号。第六行的格式串要求按字符型输出a,b值。第七行中为了提示输出结果又增加了非格式字符串。3.3.1.2格式字符在C语言中格式字符串的一般形式为:[标志][输出最小宽度][精度][长度][类型]。其中,50/247 《C语言程序设计基础》教学大纲草案2021-10-237:25方括号[]中的项为可选项。格式字符类型有如下几种类型:(1)数据类型格式字符;(2)标志格式字符;(3)长度格式符;(4)输出最小宽度格式字符;格式符和意义如表3.1所示。表3.1格式字符说明格式字符数据类型说明符d以带符号的十进制形式输出整数(正数不输出符号)o以8进制无符号形式输出整数(不输出前缀0)x以16进制无符号形式输出整数(不输出前缀0x)u以十进制形式输出无符号整数c输出单个字符s输出字符串p指针f以小数形式输出单、双精度数,隐含输出6位小数e以标准指数形式输出单、双精度数,数字部分小数位数为6位g选用%f或%e格式中输出宽度较短的一种格式,不输出无意义的0标志格式说明符-输出的数字或字符在域内向左靠+输出符号(正或负),输出值为正时冠以空格,为负时冠以负号对c,s,d,u类无影响;对o类,在输出时加前缀o;#对x类,在输出时加前缀0x;对e,g,f类当结果有小数时才给出小数点。长度格式说明符h按短整型量输出l按长整型量输出,用于长整型数据,可加在格式符d,o,x,u前面精度格式说明符精度格式符以“.”开头,后跟十进制整数。意义是:如果输出数字,则表.示小数的位数;如果输出是字符,则表示输出字符的个数;若实际位数大于所定义的精度数,则截去超过的部分。宽度格式说明符数据最小宽度。对实数,表示输出n位小数;对字符串,表示截取的m(代表一个正整数)字符个数。若实际位数多于定义的宽度,则按实际位数输出,若实际位数少于定义的宽度则补以空格或0。3.3.2标准输入函数scanf()3.3.2.1调用格式scanf函数使用的实质和printf函数相同,只是变为从键盘输入数据。scanf函数也是一个标准库函数,它的函数原型在头文件“stdio.h”中。与printf函数相同,C语言也允许在使用scanf函数之前不必包含stdio.h文件。scanf函数调用的一般形式为:scanf(“格式控制字符串”,地址表列);51/247 《C语言程序设计基础》教学大纲草案2021-10-237:25与printf函数相比,scanf函数有两个特殊点:(1)格式控制字符串的作用与printf函数相同,但不能显示非格式字符串,也就是不能显示提示字符串。(2)地址表列中给出的是各变量的地址。地址是由地址运算符“&”后跟变量名组成的。例如,&a,&b分别表示变量a的地址和变量b的地址。这个地址就是编译系统在内存中给a,b变量分配的地址。C语言中使用地址这个概念,我们应该把变量的值和变量的地址这两个不同的概念区别开来。变量的地址是C编译系统分配的,用户不必关心具体的地址是多少。scanf函数在本质上也是给变量赋值,但要求写变量的地址,如&a。&是一个取地址运算符,&a是一个表达式,其功能是求变量的地址。例3.3scanf函数格式控制与输入地址表列intmain(void){inta,b,c;printf("inputa,b,c ");scanf("%d%d%d",&a,&b,&c);printf("a=%d,b=%d,c=%d ",a,b,c);return(0);}在本例中,由于scanf函数本身不能显示提示信息,故先用printf函数在屏幕上输出提示,请用户输入a、b、c的值。程序运行结果如下:inputa,b,c123a=1,b=2,c=3“%d%d%d”表示按十进制整数形式输入数据。因为在scanf语句的格式串中由于没有非格式字符在“%d%d%d”之间作输入时的间隔,因此在输入时要用一个以上的空格或回车键作为每两个输入数之间的间隔。也可以用跳格键(Tab)。下面的这些输入方法都是合法的(□表示空格键;表示回车键;→表示Tab键):①3□4□5②34□5③3→4□552/247 《C语言程序设计基础》教学大纲草案2021-10-237:25④345而用逗号作为数字的分隔符是非法的:3,4,5如果将scanf()输入格式改为:scanf("%d,%d,%d",&a,&b,&c);那么,每两个输入数之间的间隔必需用“,”其它空格键都是非法的。程序运行结果如下:inputa,b,c1,2,3a=1,b=2,c=33.3.2.2使用scanf函数必须注意的问题使用scanf函数还必须注意以下几点:(1)scanf()的返回值是成功读入的项目个数。如果它没有读取任何项目(比如,当期望输入的是数字,而实际输入了一个非数字字符串时就会发生这种情况),scanf()会返回值0。当它检测到“文件结尾”时,它返回EOF(EOF是在头部文件stdio.h中定义的特殊值:#difineEOF(-1),即EOF的值是-1,因为-1不会和ASCII码的任何键值混淆)。(2)当scanf()的期望输入的是数字,而实际输入了空格、回车等,scanf()将跳过这些字符,继续等待正确的(数字)输入。如果输入的是一个字符串时,scanf()不会该字符读入给程序,而是直接返回,执行下一语句。(3)如果在“格式控制”字符串中除了格式说明以外还有其它字符,则在输入数据时应输入与这些字符相同的字符。例如:scanf("%d;%d",&a,&b);3;4正确的输入(注意3后面有一个分号“;”,这和上面的格式相同)34错误的输入3,4错误的输入(4)在用“%c”格式输入数据时,空格字符和“转义字符”都作为有效字符输入:scanf("%c%c%c",&a,&b,&c);而输入的数据为a□b□c,则变量a中存入的是字符‘a’,变量b中存入的是53/247 《C语言程序设计基础》教学大纲草案2021-10-237:25“空格”,变量c中存入的是字符‘b’。(5)在输入数据时,遇以下情况时该数据认为结束:①遇空格或按“回车”或“跳格”(Tab)键;②遇宽度结束,如“%3d”,表示只取到数的百位;③遇非法输入。(6)“*”符。用以表示该输入项读入后不赋予相应的变量,即跳过该输入值。如:scanf("%d%*d%d",&a,&b);当输入为:1□2□3时,把1赋予a,2被跳过,3赋予b。(7)宽度。用十进制整数指定输入的宽度(即字符数)。例如:scanf("%5d",&a);输入为:12345678时,只把12345赋予变量a,其余部分被截去。(8)scanf函数中没有精度控制,如:scanf("%5.2f",&a);是非法的。不能企图用此语输入小数为2位的实数。3.4字符I/O函数3.4.1单字符函数getche()和putchargetche()和putchar()是最基本的I/O函数。getche()是非缓冲型输入函数,用于从键盘读入一个字符。其功能是等待从键盘键入一个字符,返回字符的值并在屏幕上自动回显该字符。putchar()的功能是把一个字符显示在屏幕上,即把putchar()中的字符参数显示在光标当前的位置,putchar()函数的作用等同于printf("%c",ch)。见下面的程序例子。例3.4sputchar函数#includeittmain(void){charc,ch;ch=getche();//从键盘上带回显的读入一个字符送给字符变量chputchar(ch);//在当前屏幕光标位置上输出该字符renturn(0);}getche()有两个重要的变体。第一个是缓冲型输入函数getchar(),它是UNIX的字符54/247 《C语言程序设计基础》教学大纲草案2021-10-237:25输入函数原型,这个函数的缺点是,你必须按下回车符才能将输入字符送给程序。这样可能在getchar()返回之后留下回车符在缓冲队列当中,这样有可能对后续函数调用存在问题,除非你认为需要修改字符输入错误(回车按下之前可以修改),否则不要使用该函数。第二个变体是getch()函数,它与getche()的使用基本一样,只是它不把读入的字符回显在屏幕上。我们可以利用这点它编制程序,避免不必要的显示,它在调试程序中十分有用,在程序某处放置一个getch()函数,可以让程序执行到该函数的时候暂停在此处,并在键入一个键后继续运行。比如,下面的程序说明了如何实现提示和等待输入信息的。等功能。例3.5getch()用于程序暂停#include#include//在VC++6.0上要使用getch()和getche()函数必须指定conio.h头部文件intmain(void){charc,s[20];printf("Name:");gets(s);printf("Pressanykeytoconfinue...");getch();//等待输入任一键return(0);}3.4.2单字符缓冲输入函数getchar()getchar函数的功能是从键盘上输入一个字符。其一般形式为:getchar();getchar函数没有参数,函数返回的值就是从输入设备得到的字符。通常把输入的字符赋予一个字符变量,构成赋值语句,如:#includeintmain(void){charc;55/247 《C语言程序设计基础》教学大纲草案2021-10-237:25printf("inputacharacter ");c=getchar();putchar(c);return(0);}使用getchar函数应注意,getchar函数只能接受单个字符,输入数字也按字符处理。输入多于一个字符时,只接收第一个字符。getchar函数是缓冲型输入方式。在运行时如果从键盘输入字符‘a’,输入‘a’后按回车键,字符才送到内存。3.4.3字符串输入/出函数gets()、puts()gets()函数用来从标准输入设备(键盘)读取字符串直到回车结束,但回车符不属于这个字符串,由一个空格在串的最后代替它,同时gets()函数返回。其调用格式为:gets(s);s为字符串变量(字符串数组名或字符串指针)。gets(s)函数与scanf("%s",&s)相似,但不完全相同。使用scanf("%s",&s)函数输入字符串时存在一个问题,就是如果输入了空格会认为输入字符串结束,空格后的字符将作为下一个输入项处理,但gets()函数将接收输入的整个字符串直到回车为止。比如:#includeintmain(void){chars[20],*f;printf("What'syourname? ");gets(s);//等待输入字符串直到回车结束puts(s);//将输入的字符串输出puts("Howoldareyou?");gets(f);puts(f);return(0);}注意,gets(s)函数中的变量s为一字符串。如果为单个字符,编译连接不会有错误,56/247 《C语言程序设计基础》教学大纲草案2021-10-237:25但运行后会出现"Nullpointerasignmemt"的错误。puts()函数用来向标准输出设备(屏幕)写字符串并换行,其调用格式为:puts(s);其中s为字符串变量(字符串数组名或字符串指针)。puts()函数的作用与printf("%s ",s)相同。程序例子如下:例3.6puts()#includeintmain(void){chars[20],*f;//定义字符串数组和指针变量strcpy(s,"Hello!TurboC2.0");//字符串拷贝,对数组变量赋值f="Thankyou";//字符串指针变量赋值puts(s);puts(f);return(0);}程序说明如下:(1)puts()函数只能输出字符串,不能输出数值或进行格式变换。(2)可以将字符串直接写入puts()函数中。如:puts("Hello,TurboC2.0");puts()可以使用与printf()一样的反斜杠控制符,它比printf()函数的冗余操作少,仅用来输出一字符串,不能输出数值或者进行格式变换,它占用内存少,好的程序设计中经常使用这一函数来提高程序运行效率。puts()返回一个指向该字符串的指针。3.5缓冲区中的回车符滞留问题我们前面提到,缓冲型输入函数getchar()有一个特点,它必须按下回车符才能将输入字符送给程序。这样,在getchar()返回之后,会将回车符留在缓冲队列当中,有可能对后续函数调用产生错误输入。实际上,缓冲型输入函数scanf()也存在类似情况。我们现在看一下,缓冲型输入函数输入情况下,缓冲区使用不当所带来的后果。例3.757/247 《C语言程序设计基础》教学大纲草案2021-10-237:25#includeintmain(void){intnum;charname[40];charsex;inttelnum;charbirthday[12];printf("请输入学号: ");scanf("%d",&num);printf("请输入姓名: ");scanf("%s",name);printf("请输入性别: ");scanf("%c",&sex);printf("请输入电话号码: ");scanf("%d",&telnum);printf("请输入出生年月日: ");scanf("%s",birthday);return(0);}程序一次读取学生的一种属性,如下是程序运行结果:请输入学号:123456请输入姓名:周远请输入性别:请输入电话号码:123456请输入出生年月日:2000年12月12日58/247 《C语言程序设计基础》教学大纲草案2021-10-237:25程序执行了输入姓名的语句之后,没有从键盘输入性别,直接跳到了输入电话号码的语句。原因很简单,前一次用scanf()输入姓名的时候,其回车符被滞留在输入缓冲区,成为下一次的scanf()输入,因而直接读出了回车符作为性别输入。解决这个问题可以有多种选择,比如改用非缓冲函数getche(),或者对scanf()做技术处理。作为习题,请同学仍然使用scanf()函数,给出一种解决问题的方法并写出改进后的程序及运行结果。3.6cout和cin函数cout和cin分别是consoleoutput和consoleinput的缩写。C++语言在输入/输出上是比较简便的:(1)输入语句cin作用:将从键盘得到的数据存入指定的变量。格式:cin>>变量名;可以将”>>”看作表示方向的符号,在这里表示数据从输入设备流到变量,这就是流这个名称的由来。(2)输出语句cout作用:将指定的变量输出到屏幕上.格式:cout<<变量名1<<变量名2<<”字符”.<下面的程序用cin和cout形式重新改写了例3.7。例3.8#include#includeintmain(void){intnum;charname[40];charsex;inttelnum;59/247 《C语言程序设计基础》教学大纲草案2021-10-237:25charbirthday[12];cout<<"请输入学号姓名性别电话号码出生年月日 "<>num>>name>>sex>>telnum>>birthday;cout<<"学号="<>=,&=,^=,|=等等。4.1运算符C语言使用的运算符如表4.1所示。表4.1运算符汇总算术运算符+、-、*、/、%关系运算符>、<、==、>=、<=、!=逻辑运算符!、&&、||位运算符<<、>>、~、|、∧、&赋值运算符=条件运算符?、:逗号运算符,指针运算符*、&求字节数运算符sizeof强制类型转换运算符(类型)分量运算符.(小数点)、→下标运算符[]其他函数调用运算符()它划分为以下几类:(1)算术运算符用于各类数值运算。包括加(+)、减(-)、乘(*)、除(/)、求余(或称模运算,%)、自增(++)、自减(--)共七种。(2)关系运算符用于比较运算。包括大于(>)、小于(<)、等于(==)、大于等于(>=)、小于等于(<=)和不等于(!=)六种。(3)逻辑运算符用于逻辑运算。包括与(&&)、或(||)、非(!)三种。62/247 《C语言程序设计基础》教学大纲草案2021-10-237:25(4)位操作运算符参与运算的量,按二进制位进行运算。包括位与(&)、位或(|)、位非(~)、位异或(^)、左移(<<)、右移(>>)六种。(5)赋值运算符用于赋值运算,分为简单赋值(=)、复合算术赋值(+=,-=,*=,/=,%=)和复合位运算赋值(&=,|=,^=,>>=,<<=)三类共十一种。(6)条件运算符这是一个三目运算符,用于条件求值(?:)。(7)逗号运算符用于把若干表达式组合成一个表达式(,)。(8)指针运算符用于取内容(*)和取地址(&)二种运算。(9)求字节数运算符用于计算数据类型所占的字节数(sizeof)。(10)特殊运算符有括号(),下标[],成员(→,.)等几种。4.1.1赋值运算符:=在C语言中,符号“=”不代表相等,而是一个赋值运算符,下面的语句将值2002赋给名字为bmw的变量:bmw=2002;也就是说,符号“=”的左边是一个变量名,右边是赋给改变量的值,符号“=”被称为赋值运算符(assingnmentoperator)。注意,不要把这条语句读成“bmw=2002”,而应该读做“将值2002赋给变量bmw”。赋值运算符的动作是从右到左。变量名和变量值有什么不同?我们看如下语句:i=i+1;这条语句的意思是说,找到名字为i的变量,将i的值加一,结果再赋值给i。见图4.1所示。ii=i+1;ii=22+1;2223i=23;图4.1语句i=i+1运算过程63/247 《C语言程序设计基础》教学大纲草案2021-10-237:25记住,你可以给变量赋值,但是不能给常量赋值,比如:2002=bmw;没有意义,因为常量不能记忆(存储)任何数据,它没有存储单元。实际上,符值运算符的左边必须与一个地址关联,这样才能使得数值可以存储(赋值)。一个变量的名字和地址相关联,因为每一个变量一定有一个地址,地址代表的存储单元存储变量的值。我们学习了指针之后,也可以用指针指向的地址存储赋值运算符左边的数值或变量的内容。C语言中允许多重复值操作:inti,j,k;i=j=k=68;这条语句的执行是从右到左进行赋值操作,结果相当于:k=68;j=68;i=68;4.1.2加减乘除法运算符:+、-*和/加法运算符使得它两侧的值被加在一起。例如:printf(“%d”,2+24);将打印到屏幕上的是值26,而不是表达式2+24。被加数可以是变量也可以是常量,所以,语句:i=j+k;使计算机先查找到右边两个变量的值,然后相加,并将结果赋值给i。减法运算符“-”与值完全类似。加法运算符“+”加法运算符为双目运算符,即应有两个量参与加法运算。如a+b,4+8等。具有右结合性。减法运算符“-”减法运算符为双目运算符。但“-”也可作负值运算符,此时为单目运算,如-x,-5等具有左结合性。乘法运算符“*”是双目运算,具有左结合性。除法运算符“/”是双目运算,具有左结合性。参与运算量均为整型时,结果也为整型,舍去小数。如果运算量中有一个是实型,则结果为双精度实型。比如:printf(" %d,%d ",20/7,-20/7);64/247 《C语言程序设计基础》教学大纲草案2021-10-237:25printf("%f,%f ",20.0/7,-20.0/7);本例中,20/7,-20/7的结果均为整型,小数全部舍去。而20.0/7和-20.0/7由于有实数参与运算,因此结果也为实型。4.1.3算术表达式和运算符的优先级与结合性算术表达式就是用算术运算符和括号将运算对象(也称操作数,如常量、变量、函数等)连接起来、符合C语法规则的式子。如:a*b/c-1.5+'a'在算术表达式中,各种操作的执行顺序非常重要,C语言通过建立一个运算符的优先级来控制表达式的操作顺序,如表4.2所示。其中,运算符的结合性是指运算对象两侧的运算符优先级相同时,运算符的结合方向(左、右)。左结合性:结合方向为从左至右(先左后右,简称左结合)。比如,算术运算符为左结合。右结合性:结合方向为从右至左(先右后左,简称右结合)。比如,赋值运算符“=”为右结合。例如下面两个表达式:a-b+c……①a=b+c……②由于算术运算符为左结合,故表达式①先执行a-b,再执行加c的运算。而表达式②由于赋值运算符为右结合,它先执行右边的b+c,再赋值给a。表4.2C语言运算符优先级一览优先级运算符含义要求运算对象的个数结合方向()圆括号[]下标运算符1自左至右->指向结构体成员运算符.结构体成员运算符!逻辑非运算符~按位取反运算符++自增运算符--自减运算符12-负号运算符自右至左(单目运算符)(类型)类型转换运算符*指针运算符&地址运算符sizeof长度运算符*成法运算符3/除法运算符2(双目运算符)自左至右%求余运算符+加法运算符42(双目运算符)自左至右-减法运算符<<左移运算符52(双目运算符)自左至右>>右移运算符续表4.2优先级运算符含义要求运算对象的个数结合方向65/247 《C语言程序设计基础》教学大纲草案2021-10-237:25<<=6关系运算符2(双目运算符)自左至右>>===等于运算符72(双目运算符)自左至右!=不等于运算符8&按位与运算符2(双目运算符)自左至右9^按位异或运算符2(双目运算符)自左至右10|按位或运算符2(双目运算符)自左至右11&&逻辑与运算符2(双目运算符)自左至右12||逻辑或运算符2(双目运算符)自左至右13?:条件运算符3(三目运算符)自右至左=+=-=*=/=14%=赋值运算符2自左至右>>=<<=&=^=|=15,逗号运算符(顺序求值运算符)算术表达式是由常量、变量、函数和运算符组合起来的式子。一个表达式有一个值及其类型,它们等于计算表达式所得结果的值和类型。表达式求值按运算符的优先级和结合性规定的顺序进行。单个的常量、变量、函数可以看作是表达式的特例。4.2其它运算符4.2.1自增、自减运算符:++和--自增1运算符记为“++”,功能是变量的值自增1。自减1运算符记为“--”,功能是变量的值自减1。自增、减1运算符均为单目运算,都具有右结合性。可有表4.3几种形式。表4.3++i自增1后再参与其它运算。前缀操作--i自减1后再参与其它运算。i++i参与运算后,i的值再自增1。后缀操作i--i参与运算后,i的值再自减1。在理解和使用上容易出错的是i++和i--。特别是当它们出在较复杂的表达式或语句中时,常常难于弄清,因此应仔细分析。例如:j=3;j=++i;//先使i加1,再赋给j。执行后:j=4,i=4j=3;66/247 《C语言程序设计基础》教学大纲草案2021-10-237:25j=i++;//先把i赋给j,再使i加1。执行后:j=3,i=4例4.1自增、自减1运算符操作intmain(void){inti=5,j=5,p,q;p=(i++)+(i++);q=(++j)+(++j);//注意分析q=(++j)+(++j)的值是14=7+7,而不是13=6+7printf("%d,%d,%d,%d",p,q,i,j);return(0);}运行结果是(VC6.0编译环境):10,14,7,7我们来分析一下程序:首先,程序中对p=(i++)+(i++)应理解为二个i相加之后,最后操作i++步骤,故p值为15。然后i再自增1二次相当于加2,故i的最后值为7。对于q值如何理解?注意,j先自增一次,值为6,再自增一次值是7,而q=(++j)+(++j)是14=7+7,不是13=6+7。我们要清楚前缀操作和后缀操作基本概念。另外还需注意,++和--只能用于变量,不能用于常量和表达式。例如:5++;(a+b)++;#defineONE1;ONE++;使用自增自减运算符是表达式缩减形式,之所以这样,是为了程序简洁,易于阅读。缺点是容易发生计数错误。初学者需要特别留神。4.2.2取模运算符:%取模运算符“%”是双目运算,具有左结合性,要求参与运算的量均为整型。它用运算符右边的整数除以运算符左边的数,并将两数相除后的余数作为取模运算的结果。比如,13%5(读成“13除5取模”)的结果是3。取模运算在数论、数据结构中非常有用,在日常生活中也经常使用,比如程序4.2的例子。例4.2把秒转换为分钟和秒67/247 《C语言程序设计基础》教学大纲草案2021-10-237:25#include#difineSEC_PER_MIN60intmain(void){intsec,min,left;printf(“Convertsecondstominutesandseconds! ”);printf(“Enterthenumberofsecondsyouwishtoconvert. ”);scanf(“%d”,&sec);min=sec/SEC_PER_MIN;left=sec%SEC_PER_MIN;printf(“%dsecondsis%dminutes,%dseconds. ”,sec,min,lef);return(0);}运行结果是:Convertsecondstominutesandseconds!Enterthenumberofsecondsyouwishtoconvert.234234secondsis3minutes,54seconds.4.2.3逗号运算符和逗号表达式C语言中逗号“,”也是一种运算符,称为逗号运算符。其功能是把两个表达式连接起来组成一个表达式,称为逗号表达式,并保证最左边的表达式最先计算,整个表达式的值是右边表达式的值。其一般形式为:表达式1,表达式2其求值过程是分别求两个表达式的值,并以表达式2的值作为整个逗号表达式的值。例如:b=(a=3*5,a*4);逗号表达式的值b等于“表达式2”的值,即b=60。还有:x=(y=3,(z=++y+2)+5);逗号表达式的运算过程是把y赋值为3,y值自增至4,然后是Z=4+2,最后是x=z+5,即表达式值等于11。68/247 《C语言程序设计基础》教学大纲草案2021-10-237:25对于逗号表达式还要说明两点:(1)逗号运算符在所有运算符中优先级最低;(2)逗号表达式一般形式中的表达式1和表达式2也可以又是逗号表达式。例如:表达式1,(表达式2,表达式3)形成了嵌套情形。因此可以把逗号表达式扩展为以下形式:表达式1,表达式2,…表达式n整个逗号表达式的值等于表达式n的值。(3)程序中使用逗号表达式,通常是要分别求逗号表达式内各表达式的值,并不一定要求整个逗号表达式的值。(4)并不是在所有出现逗号的地方都组成逗号表达式,如在变量说明中,函数参数表中逗号只是用作各变量之间的间隔符。4.2.4复合赋值符及表达式在赋值符“=”之前加上其它二目运算符可构成复合赋值符。如:+=,-=,*=,/=,%=,<<=,>>=,&=,^=,|=等。构成复合赋值表达式的一般形式为:变量双目运算符=表达式;表4.4列出基本用例。表4.4简写表达式等于a+=5;a=a+5;a-=5;a=a-5;a*=5;a=a*5;a/=5;a=a/5;a%=5;a=a%5;注意,简写运算符的优先级与“=”相同,比如:x*=3*y+12;等同于x=x*(3*y+12);复合赋值符这种写法,对初学者可能不习惯,但十分有利于编译处理,能提高编译效率并产生质量较高的目标代码。4.2.5sizeof运算符和size_t类型sizeof是一个运算符,它作用于变量、或者是类型,目的是取得操作数以字节为单位的存储长度,也就是说它的返回值是一个整型数。如果操作数是变量,不需要加括弧,如果是类型,需要把操作数括弧起来。我们经常使用sizeof取得一个操作数的内存长度。比如:例4.369/247 《C语言程序设计基础》教学大纲草案2021-10-237:25#includeintmain(void){intn=0,l=0,i;chars[40]="tsinghua.edu.cn";l=sizeof(int);i=sizeof(char);printf("n=%d,nhas%ubytes:allintshave%ubytes. ",n,sizeofn,l);printf("s=%s,shas%ubytes:allintshave%ubytes. ",s,sizeofs,i);return(0);}运行结果是:n=0,nhas4bytes:allintshave4bytes.s=tsinghua.edu.cn,shas40bytes:allchrshave1bytes.4.3C语言的语句4.3.1语句语句(statement)是构造程序的基本单位,程序(prigram)是一系列语句的集合,在C语言中,语句的结束处用一个分号“;”标识。什么是语句?看如下例子:例4.4#includeintmain(void){intn=0,l=0,i;//声明语句chars[40]="tsinghua.edu.cn";//声明语句l=sizeof(int);//运算语句i=sizeof(int);//运算语句printf("n=%d,nhas%ubytes:allintshave%ubytes. ",n,sizeofn,l);//函数语句printf("s=%s,shas%ubytes:allintshave%ubytes. ",s,sizeofs,i);//函数语句70/247 《C语言程序设计基础》教学大纲草案2021-10-237:25return(0);}4.3.2复合语句看一个例子。王萍在公司工作,年底发奖金,有三等奖级:7000元、5000元和3000元,她想用这笔钱买一个手机和MP3,她盘算着:如果奖金是7000元钱,她可以买一个SONYNW-MS11(128M,3000元)和三星T208(全中文双屏双频彩色屏幕16和弦铃声GPRS手机,3180元);如果奖金是5000元钱,她可以买一个SONYNW-MS7(64M,2400元)和三星T108彩屏手机(2580元);如果奖金是3000元钱,她就只能买一个三星T108彩屏手机(2580元);intbonus,balance;charmp3[20];charmobile[20];scanf("inputyoubonus=%d ",&bonus);if(bonus==7000)yesbonus=7000?nostrcpy(mp3,"SONYNW-MS11");strcpy(moblie,"SAMGSUNGT208");balance=7000-3000-3180;yesbonus=5000?nostrcpy(mp3,"SONYNW-MS7");strcpy(moblie,"SAMGSUNGT108");balance=5000-2400-2580;strcpy(mp3,"nomoney");strcpy(moblie,"SAMGSUNGT108");balance=3000-2580;printf("mp3=%s,moblie=%s ",mp3,moblie);printf("balance%d ",balance);exit图4.2复合语句问题现在编制一段程序,根据输入的奖金数,分别打印王萍可能购买的商品和奖金余额,程序描述如下:例4.5复合语句实例71/247 《C语言程序设计基础》教学大纲草案2021-10-237:25#include#includeintmain(void){charmp3[20];intbonus,balance;charmobile[20];printf("pleasbonus=");scanf("%d",&bonus);if(bonus==7000){strcpy(mp3,"SONYNW-MS11");strcpy(moblie,"SAMGSUNGT208");balance=7000-3000-3180;}else{if(bonus==5000){strcpy(mp3,"SONYNW-MS7");strcpy(mobile,"SAMGSUNGT108");balance=5000-2400-2580;}else{strcpy(mp3,"nomoney");strcpy(mobile,"SAMGSUNGT108");balance=3000-2580;}}printf("mp3=%s,moblie=%s ",mp3,mobile);printf("balance%d ",balance);return(0);}72/247 《C语言程序设计基础》教学大纲草案2021-10-237:25通过这个例子,我们知道,之所以使用复合语句,是因为很多时候一个任务需要一段程序语句来完成,即一个代码块。为此,使用花括弧{}将这个代码块封装在一起,从语句外部看来,就好像一条语句的作用,我们称之为复合语句。4.4小结本章中设计到了很多程序设计技巧方面的概念,使用得当,会简化程序,使用不当,就会产生意料之外的问题,主要有以下几点:(1)赋值运算符“=”和关系运算符“==”这是初学者最容易弄错的地方。他们经常在逻辑判别条件中将“==”误写为“=”。比如程序4.5中的第一个判别语句:if(bonus==7000){…}else{…}如果误为:if(bonus=7000){…}else{…}那么if语句从(bonus==7000)?的逻辑判别关系变成了给变量bonus赋值为7000的表达式,于是bonus的值始终都是等于7000,程序永远也不会执行else对应的复合语句。(2)自增1运算符的前缀和后缀操作自增1运算符的前缀和后缀操作在表达式中有着完全不同的效果,前缀是在表达式之前自增操作数,操作数增1后其值影响表达式结果。比如,设j初值为1,如下两个操作的结果完全不同:q=(++j)+(++j);j值自增2后,再带入到表达式中运算,所以q值是6,而j值是3。而后缀操作:p=(i++)+(i++);却不会影响表达式求值,它是先将j值带入表达式运算,求得q值为2,然后再对j自增2,使得j值变为3。自增1运算符的引入是为了简写程序,阅读清晰,但是我们不推荐自增1运算符的复合操作,它非常容易产生混淆。(3)if…else在讨论复合语句的概念中,本章首次引入了if…else逻辑条件判别语句,它是程序流控制语句,和逻辑运算符结合,构成了控制程序执行流转向条件分支的能力。我们将在下章中73/247 《C语言程序设计基础》教学大纲草案2021-10-237:25继续讨论它在程序设计上的应用。(4)运算符的优先级C语言通过建立一个运算符的优先级来控制表达式的操作顺序。当运算对象两侧的运算符优先级相同时,C语言根据运算符的结合性安排操作顺序。对于这些细节伤得概念,我们都必须非常的清楚。74/247 《C语言程序设计基础》教学大纲草案2021-10-237:25第五章程序流分支控制结构5.1本章概要在本章主要学习如下内容:(1)关键字if、else、switch、break、default(2)怎样使用if和ifelse语句以及如何嵌套;(3)怎样使用逻辑运算符将关系表达式组合为更加复杂的判断表达式;(4)C的条件运算符;(5)Switch语句;(6)break;5.2if语句描述if语句,可以形象的比喻成我们语文课上的造句:如果(条件成立)就……否则是……也就是说,if-else给我们提供了一种根据当前程序运行条件,来选择不同处理方法(程序分支)的手段。5.2.1程序的三种基本结构结构化程序由三种基本结构组成,每一个基本结构可以包含一个或若干个语句。(1)顺序结构顺序执行就是一条语句(或函数)执行完毕后,继续执行下一条语句(或函数),如图5.1所示。75/247 《C语言程序设计基础》教学大纲草案2021-10-237:25(2)选择结构程序的执行流向依据条件p的检验结果来确定,条件检验产生一个逻辑值,任何非零p值(包括负值)均为逻辑真,而p值为零表示逻辑假。由逻辑真或假,选择执行程序的某部分,但无论选择哪部分,程序均将汇集到同一个出口。如图5.2所示。(3)循环结构(a)当型循环结构。当条件p成立(“真”)时,反复执行A操作,直到p为“假”时才停止循环。特点是:①先判别条件,若条件满足,则执行A;②在第一次判别条件时,若条件不满足,则A一次也不执行。如图5.3所示。(b)直到型循环结构。先执行A操作,再判别条件p是否为“假”,若为“假”,再执行A,如此反复,直到p为“真”为止。特点是:①先执行A再判条件,若条件满足再执行A。②A至少被执行一次。76/247 《C语言程序设计基础》教学大纲草案2021-10-237:25如图5.4所示。(4)多分支选择结构选择结构可以派生出“多分支选择结构”。根据k的值(k1、k2、...、kn)不同而执行A1,A2,...,An之一。我们也称之为开关(switch)语句,如图5.5所示。三种基本结构可以处理任何复杂的问题。如图5.6程序可以分解为顺序结构(A和B)及当型循环结构(B)。本章只学习分支控制结构,有关循环程序设计问题将在下一章讨论。5.2.2if语句基本结构if语句是最基本的程序分支语句,或者说是选择语句,因为它提供了一个交汇点,在这里,程序根据条件检验值的真、或假,选择两个分支中的一条前进,一般形式如下:if(条件为真)statement这语句的意思是说,如果条件为真(非零),则程序执行statement语句(statement可以是花括弧中的复合语句)。如果条件为假(零),则程序跳过statement语句,直接执行if语77/247 《C语言程序设计基础》教学大纲草案2021-10-237:25句的后续语句。如图5.7所示。yes逻辑条件真?执行A语句statementnoC语句图5.7if语句基本结构比如,用一个整型量grade描述学生的民族属性,grade值为1代表汉族,为2代表苗族,为3代表壮族等等。设学生高考成绩是x,投挡时检查每一位学生的民族属性grade,如果某学生是少数民族,则最终录取分数mark可以加20分,用if语句描述如下:scanf(“%d%d”,&grade,&x);if((grade==2)||(grade==3))x+=20;mark=x;其中,(grade==2)||(grade==3)是逻辑表达式,起到选择分支条件功能,用文字描述是说,如果学生是苗族或者是壮族,则条件检验值为真(非零),否则为假(零)。假定所有少数民族的学生都可以加20分,那么修改逻辑检验条件为:if(grade>1)x+=20;只要是少数民族生源,逻辑表达式grade>1都为真。当然,你可以换一种逻辑关系描述:if(grade!=1)x+=20;只要不是汉族生源,逻辑表达式grade!=1都为真。问题是,如果输入了一个零,程序认为grade!=1依然为真,继续加分20,但你并不知道它代表哪个民族。所以,你必须仔细考虑你的逻辑式描述,以及输入越界的处理方法。为了防止输入越界,下面这种方法称之为边界检查,或者是输入确认:scanf(“%d%d”,&grade,&x);if((grade<1)||(grade>56))return(-1);//如果输入不在1~56范围之内,越界返回。if(grade!=1)x+=20;mark=x;现在,我们这样要求程序,当输入边界正确的时候,处理加分步骤,否则,提示出错信息,如图5.8所示。78/247 《C语言程序设计基础》教学大纲草案2021-10-237:25elseif(逻辑条件真)执行B语句提示错误信息是执行A语句加分处理C语句图5.8if-else结构就是说,根据逻辑表达式值的真假,程序选择两个并列的分枝执行:或者是A或者是B。对于这种结构,我们称之为if-else语句。scanf(“%d%d”,&grade,&x);if((grade>1)&&(grade<56)){//A语句if(grade!=1)x+=20;mark=x;}else{//B语句mark=-1;//给mark打上出错标记。printf(thegradeover);//如果输入不在1~56范围之内,提示出错信息。}……//在if-else语句执行完毕后继续要执行的C语句前面几个例子中,逻辑表达式都是检验数字条件,逻辑表达式同样也适用于字符变量的情况,比如:例5.1检查一个字符串输入,除非是空格,否则所有字符均被加上一个常数。#include#defineSPACE‘’//SPACE作为空格引用intmain(void){charch;ch=getchar();while(ch!=’ ’){if(ch==SPACE)putchar(ch);//回显elseputchar(ch+1);79/247 《C语言程序设计基础》教学大纲草案2021-10-237:25ch=getchar();}putchar(ch);return(0);}运行结果是:CALLMYHAL.BBMMNZIBM/5.2.3if与else配对if语句中最容易混淆的就是if的嵌套形式。所谓if语句嵌套,就是一个if语句的对象仍然是一个if语句或else语句。而嵌套if语句出错的原因是,我们不知道哪个else与哪个if语句配对。比如:假定C语言课的上机作业在主楼501机房,其它课程作业去图书馆,如果不做作业我们也去图书馆看书,如果用下面这段程序描述的话,就非常很含混:if(做作业?)if(上机作业?)printf(“去中央主楼501”);elseprintf(“去图书馆”);我们如何知道else语句打印出来的“去图书馆”是做作业还是看书?就是说,else语句与哪个if有关?C语言提供一个简单的规则来解决这个问题。它规定,else连接到在同一层最接近它的if语句、且该if语句又没有其它else与之相匹配。因此,上面的例子中,else显然是与if(y)相匹配。如果你想让else与if(x)相连,必须使用括弧:if(x){if(y)printf(“1”);}elseprintf(“2”);这样,根据C语言的作用域规则,if(y)被隐藏在一个复合语句之内,else看不见它,因此,本质上说,else和if(y)不在一个程序块内,是两个并行的分支。80/247 《C语言程序设计基础》教学大纲草案2021-10-237:255.2.4阶梯式if-else-if结构设《C语言程序设计》卷面考试判分是百分制,但登录到数据库是按A、B、C、D四个等级划分,其中,成绩>=85为A,否则,成绩>=75为B,进而,成绩>=60为C,其余的成绩均为D。用流程表示如图5.9所示。elseif(成绩>=85)else是if(成绩>=75)等级为Aelse等级为Bif(成绩>=60)等级为D等级为C图5.9if-else-if结构这些条件按从上到下的次序逐个判别,一旦发现条件满足时,就执行与它有关的语句,并跳过其它剩余的阶梯。若条件没有一个被满足的时候,则执行最后一个else语句。常常把最后这个else语句称之为缺省条件(defaultcondition)。如果没有缺省条件(即最后的else语句),则当所有条件均失败的时候,程序什么也不执行。图5.9的C语言实现是程序5.2。程序5.2if-else-if结构#includeintmain(void){chargrade;unsignedchari;printf("请输入成绩: ");scanf("%d",&i);if(i>=85)grade='A';else{if(i>=75)grade='B';else{if(i>=60)grade='C';81/247 《C语言程序设计基础》教学大纲草案2021-10-237:25elsegrade='D';}}printf("等级为:%c ",grade);return(0);}5.3关系表达式与逻辑运算符关系表达式在我们程序控制中经常使用,如大于关系“>”,小于关系“<”,大于等于关系“>=”,小于等于关系“<=”,不等于关系“!=”。请同学注意一个基本概念,关系表达式的结果是逻辑值,非0即1。如:5>0的值为“真”,即为1。(a=3)>(b=5)由于3>5不成立,故其值为假,即为0。而下述语句:intx=100;printf(“%d”,x>10);因为关系x>100为真,所以表达式结果是1。逻辑运算符“&&”,“||”和“!”则是连接多个关系表达式的值,进行逻辑运算。表5.1运算符逻辑意义优先级(参考表4.2)&&逻辑与11||逻辑或12!逻辑反2例如:(a>0)&&(b>0)这语句的意思是说,仅当a大于0的关系为真、并且b大于0的关系也为真的时候,逻辑运算(a>0)&&(b>0)的值才为1(逻辑真)。而:(a>0)||(b>0)的意思是说,只要a大于0的关系成立,或者b大于0的关系成立,逻辑运算(a>0)||(b>0)的值就为1。逻辑运算的求值规则如下:(1)与运算“&&”。参与运算的两个量都为真时,结果才为真,否则为假。例如,5>0&&4>2,由于5>0为真,4>2也为真,相与的结果也为真。82/247 《C语言程序设计基础》教学大纲草案2021-10-237:25(2)或运算“||”。参与运算的两个量只要有一个为真,结果就为真。两个量都为假时,结果为假。例如:5>0||5>8,由于5>0为真,相或的结果也就为真(3)非运算“!”参与运算量为真时,结果为假;参与运算量为假时,结果为真。例如:!(5>0)的结果为假。用逻辑关系描述图5.9是:如果成绩大于等于85并且小于等于100,等级为A;如果成绩大于等于75并且小于85,等级为B;如果成绩大于等于75并且小于85,等级为B;如果成绩大于等于60并且小于75,等级为C;如果成绩大于等于0并且小于60,等级为D。程序5.3#includeintmain(void){chargrade=-1;unsignedchari;printf("请输入成绩: ");scanf("%d",&i);if((i>=85)&&(i<=100))grade='A';else{if((i>=75)&&(i<85))grade='B';else{if((i>=60)&&(i<85))grade='C';else{if((i>=0)&&(i<60))grade='D';elsegrade=-1;}}}printf("等级为:%c ",grade);return(0);}83/247 《C语言程序设计基础》教学大纲草案2021-10-237:255.4条件运算符和条件表达式如果在条件语句中只执行单个的赋值语句时,常可使用条件表达式来实现。不但使程序简洁,也提高了运行效率。条件运算符为“?”和“:”是一个三目运算符,即有三个参与运算的量。由条件运算符组成条件表达式的一般形式为:表达式1?表达式2:表达式3其求值规则为:如果表达式1的值为真,则以表达式2的值作为条件表达式的值,否则以表达式3的值作为整个条件表达式的值。条件表达式通常用于赋值语句之中。例如条件语句:if(a>b)max=a;elsemax=b;可用条件表达式写为:max=(a>b)?a:b;执行该语句的语义是:如a>b为真,则把a赋予max,否则把b赋予max。使用条件表达式时,还应注意以下几点:(1)条件运算符的运算优先级低于关系运算符和算术运算符,但高于赋值符。因此max=(a>b)?a:b可以去掉括号而写为max=a>b?a:b,但为了程序清晰易读,建议保留圆括弧。(2)条件运算符?和:是一对运算符,不能分开单独使用。(3)条件运算符的结合方向是自右至左。例如:a>b?a:c>d?c:d应理解为:a>b?a:(c>d?c:d)这也就是条件表达式嵌套的情形,即其中的表达式3又是一个条件表达式。用条件表达式对输出a和b两个数中大数的程序段编程如下:#includeintmain(void){inta,b,max;84/247 《C语言程序设计基础》教学大纲草案2021-10-237:25printf(" inputtwonumbers:");scanf("%d%d",&a,&b);printf("max=%d ",a>b?a:b);return(0);}5.5并行选择结构switch语句和break虽然if-else-if语句可以实现阶梯式的多重选择关系,但是在编程时很容易出错,当阶梯层次很深的时候,非常容易混淆。举例来说,一个班有30名学生,学号依次为3000~3029,已知每个学号对应一个学生姓名,程序根据输入学号打印出该学生的姓名。程序如下:intnum;printf(“请输入学号:”);scanf(“%d”,&num);if(num==3000)printf(“%d的姓名是:%s”,num,”张三”);else{if(num==3001)printf(“%d的姓名是:%s”,num,”李四”);else{if(num==3002)printf(“%d的姓名是:%s”,num,”王五”);else{if(num==3003)printf(“%d的姓名是:%s”,num,”赵六”);else…….}}}30名学生我们需要30层的阶梯判断。非常麻烦。一般我们认为,层深不宜超过4层以上,否则程序读起来非常晦涩。另外,if-else-if语句的效率也不高,连续的多重判断影响程序执行速度。为此,C语言提供了一种并行的分支判断语句switch()。其一般形式为:switch(表达式){case常量表达式1:85/247 《C语言程序设计基础》教学大纲草案2021-10-237:25语句段1;break;case常量表达式2:语句段2;break;…case常量表达式n:语句段n;break;default:语句段n+1;}其语义是:计算表达式的值,并逐个与其后的常量表达式值相比较,当表达式的值与某个常量表达式的值相等时,称之为配对,即执行其后的语句段,直到break语句,然后跳出整个switch语句。当表达式的值与所有case后的常量表达式均不相同时,则执行default后的语句。default语句可以缺省,此时程序直接退出switch()。用switch()语句处理上面的学号与姓名匹配关系就非常简洁,易懂。程序如下:程序5.4switch()应用#includeintmain(void){intnum;printf("请输入学号:");scanf("%d",&num);switch(num){case3000:printf("%d的姓名是:%s ",num,"张三");break;case3001:printf("%d的姓名是:%s ",num,"李四");86/247 《C语言程序设计基础》教学大纲草案2021-10-237:25break;case3002:printf("%d的姓名是:%s ",num,"王五");break;case3003:printf("%d的姓名是:%s ",num,"赵六");break;case3029:printf("%d的姓名是:%s ",num,"钱八");break;default:printf("查无此人 ");break;}return(0);}掌握switch()语句的几个要点:(1)表达式的值只能是整数或字符类型,小心溢出;(2)switch()只检验表达式值是否与sase的常量值相等,不能计算关系或逻辑表达式。(3)同一层的switch()不能有两个相同的case常数。(4)break语句在switch中是任选的,如果break语句被遗忘,则程序执行完匹配的case语句段之后,继续执行后续的case语句,直到遇见一条break语句或走到switch()语句的结尾。(5)在case后允许有多个语句,可以不用{}括起来。(6)各case和default子句的先后顺序可以变动,而不会影响程序执行结果。程序5.5给出了case语句段之后,缺省break语句的情况。我们假定对自3班的10名同学按学号分配寝室,学号为3000~3004的同学分配在寝室25-201室,学号为3005~3009的同学分配在寝室25-202室。程序5.587/247 《C语言程序设计基础》教学大纲草案2021-10-237:25#includeintmain(void){intnum;printf("请输入学号:");scanf("%d",&num);switch(num){case3000:case3001:case3002:case3003:case3004:printf("你们的寝室在25-201室 ");break;case3005:case3006:case3007:case3008:case3009:printf("你们的寝室在25-202室 ");break;default:printf("查无寝室 ");break;}return(0);}5.6程序分支控制结构小结我们需要掌握的关键概念是:88/247 《C语言程序设计基础》教学大纲草案2021-10-237:25(1)关系表达式的值是布尔型逻辑变量;(2)多个关系值之间的连接可以用逻辑运算符描述;(3)分支选择结构有多重选择形式和并行选择形式两种;(4)条件表达式中的“?”和“:”必须配对使用。编程的基本要求是:(1)熟练掌握if语句的三种形式,掌握if语句的基本结构以及if语句的嵌套,并能将条件运算符给出的语句转化成if语句的形式。(2)掌握switch语句的一般形式,并能把复杂的分支选择性结构化成switch语句来解决问题。(3)分析总结常用的程序分支控制设计方法及特点,并能编写相应的程序。89/247 《C语言程序设计基础》教学大纲草案2021-10-237:25第六章循环控制结构6.1本章概要(1)数组的基本概念;(2)for,while,dowhile三种循环结构;(3)break和continue语句;6.2数组数组是构成循环程序结构的基本数据结构。虽然同学在程序中已经能用数组变量定义数据,但是,对数组的基本概念,还需要仔细理解。要求如下:(1)数组是数据的物理存储形式数组是一组具有相同数据类型的变量(元素)的排队(或着说是集合),它描述了一组变量(元素)之间的关系,即,数组内的元素连续地存储在内存中的一个区域内。数组的类型,表示它是由C语言的某种基本数据类型变量、或者结构类型变量的排列组成,比如是由char类型变量、或者folat类型变量构成的队列等。数组的大小,表示了定义的这种数据类型的队列内,能容纳的元素个数有多少,也就是我们在内存中开辟了多长的存储区域,这个尺寸用方括弧内的整型数字描述,比如:chararray[100];定义了一个字符型数组变量,说明它可以存放100个字符型变量。其中,array[0]表示第一个元素,array[1]表示第二个元素,…,array[i]表示第i+1个元素,i取值从零到99。我们称i为数组元素的下标。下标相邻的元素,其存储单元的位置也相邻,比如下图所示。假想的内存好像一个抽屉654321card[4]0card[3]在抽屉中连续存放card[2]图书检索卡片cardcard[1]card[0]图6.1数组与内存90/247 《C语言程序设计基础》教学大纲草案2021-10-237:25变量名变量值变量地址array[0]'A'4000数组在内存中的地址array[1]'B'4001变量是连续的存放...地址是连续的array[i]'X'4000+i...图2数组存储在内存的一个区域中我们称之为,元素在物理上的相邻关系表达了元素在逻辑上的相邻关系,因此说,数组内的元素之间存在着线性相邻关系,是一种线性的数据结构。既然数组也是变量,那么什么是数组的地址呢?C语言规定,数组变量的地址,就是它第一个元素,即数组的头元素所在的存储单元地址。如图2中的数组变量array,其地址就是4000。即array和&array[0]都是数组第一个元素的存储地址,我们称之为数组array的地址。(2)数组的大小必须用常数声明程序使用数组如同使用变量,必须进行变量声明,不同于一般变量的是,声明中还必须用常数说明数组的大小,绝对不能用变数定义数组大小,因为这样做的结果是计算机不知道给你的数组分配多少存储单元。比如,如下数组定义是正确的:intnum[100];floatarray[100];charname[20];如下定义是错误的:intn=10;charname[n];//数组长度不能是变量在C程序设计中一个重要的原则是,所有在程序中使用的常量,都应该用宏“define”在头部文件说明,因而,一旦常量的值需要改变,仅仅是头部定义处需要修改,与程序中的引用该常量的语句无关。比如,一个学生班有30人,所有与人数有关的数据引用定义为宏名字:#defineAMOUNT30intnum[AMOUNT];//学号的数组charname[20*AMOUNT];//花名册91/247 《C语言程序设计基础》教学大纲草案2021-10-237:25(3)绝对不能越出数组的存储空间界限之外存放元素数组的长度一定要大于或等于要存放的变量数目,否则程序运行时,过多的输入变量可能占用其它程序使用的数据单元,因而让计算机产生严重的错误(大部分计算机操作系统都有边界检查,越界使用存储单元发生的程序错误会给出提示信息并终止程序运行)。6.3for、while和dowhile循环结构所谓循环,就是反复做相同的动作,在程序设计语言中,反复执行的程序语句段称为“循环体”。循环控制常用于数学迭代、对象遍历等问题的求解,几乎所有实用程序都包含循环。特别是在现代多媒体处理程序(图像、声音、通讯)中,循环更是必不可少。6.3.1for语句基本结构在试验一的程序中,我们需要输入三个学生、每人五门课程的成绩。因为每个人的姓名、课程成绩的输入步骤都是相同的,只有输入数据不同,因而是重复执行的语句段。我们将重复执行的这些语句看成循环体,可以设计成一个循环程序结构。为此,需要处理几个问题:(1)如何知道程序已经循环(重复执行)的次数?(2)什么时候结束循环?(3)怎样循环?解决第一个问题的方法是设置一个循环变量,它是一个整型数(肯定不会有一个动作需要重复0.5次或者是3.5次之类的小数),对循环体语句重复执行的次数进行计数(它好像一个计数器),比如,每完成一次循环就加一,是累加计数。解决第二个问题的方法是每完成一次循环,就用一个表达式对循环变量当前值进行检验,看它的值是否达到我们预定的次数,如果是,就结束循环,跳出循环体,去执行循环体外面的程序,否则继续循环体内的动作。比如,设定需要循环10次,如果循环变量从零开始累加计数,当它的值等于10的时候,表明完成循环。所以,开始循环过程的时候,我们还必须对循环变量设定初始值,表明它从某一特定条件开始的。有时候,循环变量在每次循环动作之后,不一定是累加的形式递增。比如,同样是设定循环10次,我们可以设初值是1,然后每次循环计数是乘2,即2,4,8,…,当循环变量的值等于1024时候,表明程序已经循环了10次,循环完成。最后一个问题,让程序循环执行的方法是采用C语言提供的实现循环结构的语句for、92/247 《C语言程序设计基础》教学大纲草案2021-10-237:25while、dowhile。比如,图6.2是for语句结构。for语句常用于循环次数已知的循环控制,也可以灵活用于其他循环控制。for语句的一般形式:for(表达式1;表达式2;表达式3){循环体语句}执行过程如图6.2所示:(1)求表达式1,设置循环初始条件;(2)求表达式2,判别循环条件,若为“真”则执行循环体语句;若为假转第(5)步;(3)求表达式3,修改循环条件;(4)转第(2)步;(5)执行for语句之后的语句。我们在for语句中,用三个表达式分别说明循环变量的初始值、结束条件和每次循环后n循环变量的递增形式。如程序1说明了用循环结构求解自然级数Si的方法。i1程序1:自然级数求和#includeintmain(void){inti,n,Sum=0;printf("请输入求和项n=");scanf("%d",&n);printf(" ");93/247 《C语言程序设计基础》教学大纲草案2021-10-237:25for(i=1;i<=n;i++){Sum+=i;printf(“i=%dSum=%d ”,i,Sum);}return(0);}注意,for语句的循环条件检验总是在循环头部进行,这就是说,如果条件为假,循环体内的语句根本不会被执行。归纳起来,循环结构有三要素:(1)设置循环变量的初值,称之为循环初始条件;(2)设定循环条件检验表达式。在for语句执行过程中,每次循环体执行前,首先检验循环变量是否达到退出循环的条件,称之为循环条件检验表达式;(3)根据题意设置循环变量修改表达式,使循环变量在每次循环中正确的进行循环计数。6.4while循环结构for语句中需要使用三个表达式描述一个循环结构,有些烦。很多时候我们只是根据一个条件来判断循环是否继续,比如,从键盘输入一个数,如果它非零,求其平方,否则程序结束。这种单一条件判断的循环结构如图6.3所示,称之为“当型”循环结构。当“表达式”为真(非0)时,执行循环体语句。如果“表达式”条件不成立(值为0)的时候,退出循环体。“当型”循环结构执行时,先判别“表达式(条件)”,如果条件不成立(值为0),则94/247 《C语言程序设计基础》教学大纲草案2021-10-237:25循环体内语句一次也不执行。C语言提供的这种只检验一个循环条件是否满足循环关系,来确定继续还是退出的循环结构语句是while()。它的一般形式是:while(表达式){循环体语句}在下面的程序中,只是根据t=readnum()是否为零决定退出还是继续。程序2while()循环结构#include#includeintreadnum();voidsqrnum(int);intmain(void){intt;while(t=readnum())sqrnum(t);return(0);}intreadnum(){intt;cout<<"inputanum: ";cin>>t;return(t);}voidsqrnum(intnum){cout<<"sqr("<#includeintmain(void){inti=0,sum=0;//要特别注意i的初始化while(i<=100){sum+=i;i++;cout<<"sum="<#includeintmain(void){inti=0,sum=0;//要特别注意i的初始化do{sum+=i;i++;cout<<"sum="<97/247 《C语言程序设计基础》教学大纲草案2021-10-237:25intmain(void){inti,sum=0;cout<<"请输入i=";cin>>i;while(i<=10){sum+=i;i++;}cout<<"sum="<intmain(void){inti,sum=0;cout<<"请输入i=";cin>>i;do{sum+=i;i++;}while(i<=10);cout<<"sum="<intmain(void){charch;cout<<"如果输入1:中午去学7食堂吃饭 ";cout<<"如果输入2:中午去荷圆餐厅吃饭 ";cout<<"如果输入3:中午什么也不吃 ";do{cin>>ch;switch(ch){case'1':cout<<"中午我去学7食堂吃饭 ";break;case'2':cout<<"中午我去荷圆餐厅吃饭 ";break;case'3':cout<<"中午什么也不吃 ";break;default:cout<<"输入错误,请重新选择 ";}}while((ch!='1')&&(ch!='2')&&(ch!='3'));99/247 《C语言程序设计基础》教学大纲草案2021-10-237:25return(0);}6.6循环中止控制6.6.1break语句作用:跳出所在的多分支switch语句,跳出所在的while、do-while、for循环语句(提前结束循环)。图6.7是带有break语句的循环结构说明。break语句有两种用法,第一种用法已经在switch语句中介绍过。第二种用法就是在当前for循环语句中跳过循环条件检验,强制中止一个循环。即当一个循环体内的break语句被执行的时候,循环立即中止,并跳出循环体外,执行该循环体外的下一条语句。break语句经常用于在循环体内需要一个特殊条件来立即中止循环的情况。比如,下面程序在每次循环中都等待从键盘按下一字符,当输入字符为‘Q’时,中止自然级数的求和过程。程序6.15#include#includeintmain(void){inti=0,sum=0;100/247 《C语言程序设计基础》教学大纲草案2021-10-237:25for(;;){sum+=i;cout<<"sum="<#include#includeintmain(void){inti,count;for(i=0;i<100;++i){count=1;for(;;){cout<<"count="<intmain(void){intn;for(n=100;n<=200;n++){if(n%3==0)continue;cout<<”n=”<#includeintreadnum();voidsqrnum(int);intmain(void)函数在程序中就像一条C语句,{函数体是语句的内容intt;while(t=readnum())sqrnum(t);return(0);main()}intreadnum(){while(t=readnum())readnum()intt;cout<<"inputanum: ";cin>>t;sqrnum(t);return(t)return(t);}t!=0?voidsqrnum(intnum){return(0);cout<<"sqr("<#includeintsearch(char*s,char);//函数声明,形式参数表:字符型数组,字符intmain(void){inti;chars[40],x;cout<<"输入字符串s:";107/247 《C语言程序设计基础》教学大纲草案2021-10-237:25cin>>s;cout<<"搜索字符x是:";cin>>x;i=search(s,x);//实际参数表if(i>0)cout<<"i="<#includeints(int);intmain(void){intn;printf("inputnumber ");scanf("%d",&n);s(n);//如果改为:n=s(n);如何?printf("n=%d ",n);109/247 《C语言程序设计基础》教学大纲草案2021-10-237:25return(0);}ints(intn){inti;for(i=n-1;i>=1;i--)n+=i;printf("n=%d ",n);return(n);}in本程序中定义了一个函数s,该函数的功能是求i的值。在主函数中输入n值,并作i1为实参,在调用时传送给s函数的形参量n(注意,本例的形参变量和实参变量的标识符都为n,但这是两个不同的量,各自的作用域不同)。在主函数中用printf语句输出一次n值,这个n值是实参n的值。在函数s中也用printf语句输出了一次n值,这个n值是形参最后取得的n值。从运行情况看,输入n值为100。即实参n的值为100。把此值传给函数s时,形参n的初值也为100,在执行函数过程中,形参n的值变为5050。返回主函数之后,输出实参n的值仍为100。可见实参的值不随形参的变化而变化。那么,如果把调用函数语句的形式改成:n=s(n);程序运行的情况就会完全不一样,调用s(n)之后,s(n)把函数的运行结果通过return语句赋值给主函数的n,好象数学表达式赋值一样。虽然主函数和函数s的两个n作用域仍然不同,但是通过函数返回值,程序正确的获取了函数s运行结果。7.2.5.2函数的返回值函数的值是指函数被调用之后,执行函数体中的程序段所取得的并返回给主调函数的值。如在程序1中主函数调用search函数取得的i值,在程序2中通过s()函数取得部分和的n值等。对函数的值(或称函数返回值)有以下一些说明:1)函数的值只能通过return语句返回主调函数。return语句的一般形式为:return表达式;110/247 《C语言程序设计基础》教学大纲草案2021-10-237:25或者为:return(表达式);该语句的功能是计算表达式的值,并返回给主调函数。在函数中允许有多个return语句,但每次调用只能有一个return语句被执行,因此只能返回一个函数值。2)函数值的类型和函数定义中函数的类型应保持一致。如果两者不一致,则以函数类型为准,自动进行类型转换。3)不返回函数值的函数,可以明确定义为“空类型”,类型说明符为“void”。一旦函数被定义为空类型后,就不能在主调函数中使用被调函数的函数值了。例如,在定义s为空类型后,在主函数中写下述语句:sum=s(n);就是错误的。为使程序有良好的可读性并减少出错,凡不要求返回值的函数都应定义为空类型。7.3作用域规则什么是作用域?仅在一个有限区域内有效的规则、数据,就是我们所说的作用域。比如,清华的教师工作证编码规则、学号编码规则(6位数字、留学生含有W)等等,和其他院校完全无关。仅在清华校内信息系统中有效。假若在北航信息系统中输入一个清华学号,没有任何意义。假设,清华、北航信息系统内部的学号编码规则完全由教育部统一制定,有完全相同学号出现在两个系统内,我们也不必担心两个人的信息混淆,因为他们分别存在完全独立两个系统内,没有互通的可能。一种语言的作用域规则决定了一段程序是否被另一段程序所“知道”,或者说能否被另一段程序访问。C语言中每个函数都是独立的代码块,函数代码归该函数所有,除了对函数的调用以外,其它任何函数中的任何语句都不能访问它。例如,使用跳转语句goto就不能从一个函数跳进其它函数的内部。组成函数体的程序代码与程序的其余部分相互独立,这个概念非常重要,我们称之为作用域规则。除非使用全程变量,否则一个函数内部定义的程序代码和数据,不会与另一个函数内的程序代码和数据相互影响,即使它们名字相同也无妨,因为它们的作用域不同,数据和代码的存储区域不同。所以,作用域规则限定了代码、变量只在定义它的函数体内有效。C语言中一个函数对其它函数的调用是全程的,即使函数在不同的文件中,也可以通过头部文件说明而被另一函数调用,我们称之为函数对于整个程序都是可见的。111/247 《C语言程序设计基础》教学大纲草案2021-10-237:25为什么函数要有私密性?非常容易理解,比如,你的钱包按照你的安排在不同的夹层中放有硬币、零钞、百元大钞、VISA卡、长城卡,餐卡、学生证等。假设有人给你发钱或者借钱,你一定是要他经过你的手传递,你绝不会希望他直接翻动你的钱包,我是说,可能他会搞乱你放钱的习惯。这里,钱包是你的函数,钱是你的数据。在函数内部定义的变量称为局部变量,当进入到这个函数的时候它们才开始存在,一旦退出这个函数,它们就消失了,因为数据的存储区域是临时性的。因此,局部变量不能在函数调用之间保存其值。与局部变量相对应的是全局变量。同学可能对数据的临时效应不容易理解,为什么它仅存在于程序进入函数体内部运行的一段时间?大家对计算机的运作原理不一定清楚,但是一定清楚剧院里的旋转舞台。为什么有旋转舞台?(1)舞台空间有限,不能把所有场景同时展现在舞台上;(2)剧情是随时间流水地发展、观众思维也是步进地,把所有场景摊大饼的摆在舞台上会造成混乱。所以,场景、人物随剧情流水般的出现在舞台上,我们只把这一时间段的剧情有关场景表现在舞台。(3)为了不间断观众思维,通过旋转舞台能快速的把当前需要的场景切换到前台,下一场剧情的场景可以在后台布置。计算机的工作内存就像一个旋转舞台,程序就是剧情,我们是观众。程序运行是串行地、流水式递进的,符合我们人类思维模式。某一时间段内只有一个程序(函数)代码段在执行,内存有限,只能存放当前函数所需的数据变量,该函数执行完毕,程序会调用下一个代码段执行,内存的数据就会被新进入函数的数据覆盖。这就是局部变量(形参变量)的生存之道。它们时空上是分开的,所以不怕重名,也不能互通联系。如同旋转舞台,无论什么场景,乐队总是必需的,因此,我们不会去旋转乐池。同样,任何一程序总有一些数据是公用的,计算机在内存中也保留了一个不会消失的公共区域存储这些数据,我们称之为全局变量。7.3.1变量作用域在讨论函数的形参变量时曾经提到,形参变量只在被调用期间才分配内存单元,调用结束立即释放。这一点表明形参变量只有在函数内才是有效的,离开该函数就不能再使用了。112/247 《C语言程序设计基础》教学大纲草案2021-10-237:25这种变量有效性的范围称变量的作用域。不仅对于形参变量,C语言中所有的量都有自己的作用域。变量说明的方式不同,其作用域也不同。C语言中的变量,按作用域范围可分为两种,即局部变量和全局变量。7.3.1.1局部变量局部变量也称为内部变量。局部变量是在函数内作定义说明的。其作用域仅限于函数体内,离开该函数后再使用这种变量是非法的。那么,在主函数中声明的的变量是不是全局函数?当然不是,因为主函数与其他任何函数都是平等的,也是函数。局部变量是在各个层次的子函数中加以声明的。在任何函数中,变量声明只允许在一个函数体的开头处,它的作用域仅在该函数内,当程序执行到出函数体时,它将不复存在。图7.2是程序7.2运行过程中,函数f1和f2的参数存储区域示意。它们的存储空间都是临时的,称之为数据栈区,仅在函数执行的过程中存在,并且在物理地址上是分开的。main()f1(intj){{仅在函数调用f1(intk,intj)仅在函数调用inti=1;inti=2;时开辟的临时{时开辟的临时f1(i);f2(j,i);存储区域inti=3;存储区域.....................i12000j14000k150002001i24001j2500120024003i35002.........主调函数main()存储区被调函数f1(intj)存储区被调函数f2(intk,intj)存储区图7.2实参传递过程与临时存储区关系程序7.2#includevoidf1(int);voidf2(int,int);intmain(void){inti=1;//提问:i是不是局部变量?当然是,因为main()也是函数,i是在它之内定义的局部变量。f1(i);return(0);}voidf1(intj)113/247 《C语言程序设计基础》教学大纲草案2021-10-237:25{inti=2;f2(j,i);}voidf2(intk,intj){inti=3;printf("主函数的i=%d,f1的i=%d,当前函数的i=%d ",k,j,i);}运行结果为:主函数的i=1,f1的i=2,当前函数的i=3main()参数表i=1....f1(i);f1(intj)参数表j=1i=2....f2(k,j);f2(intk,intj)数据k=1j=2i=3....从程序运行的结果不难看出程序中各变量之间的关系,以及各个变量的作用域。关于局部变量的作用域还要说明以下几点:主函数中定义的变量也只能在主函数中使用,不能在其它函数中使用。同时,主函数中也不能使用其它函数中定义的变量。因为主函数也是一个函数,它与其它函数是平行关系。这一点是与其它语言不同的,应予以注意。形参变量是属于被调函数的局部变量,实参变量是属于主调函数的局部变量。允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰,也不会发生混淆。如程序7.2中,形参和实参的变量名都为i,是完全允许的。如何理解不同的函数中使用相同的变量名?比如,清华和北航的学生数据库系统,假定学号的编码规则相同,都是6位数字。他们也有计算机系和自动化系,但是,你在北航查询033210学生的所有信息(学号、单位、人名等)绝对不会和清华的033210学生信息相混淆,因为他们的作用域不同,是两个逻辑上完全不相干的应用对象。如果把清华、北航学生管理系统归置在教育部属下,那么你必须增加学校识别码,假定学号由7位数字组成,其中1代表清华,2代表北大,3代表北航,…,等等,否则,查询序号都是033210的时候就会发生信息定义混淆。这也就说明了,不同函数之间的变量可以同名,114/247 《C语言程序设计基础》教学大纲草案2021-10-237:25但是同一函数内的变量绝对不允许重名的原因。在复合语句中也可定义变量,其作用域只在复合语句范围内,例如程序7.3所示。程序7.3#includeintmain(void){inti=2,j=3,k;k=i+j;{intk=8;printf("%d",k);}printf(",%d ",k);}运行结果为:8,57.3.1.2全局变量全局变量也称为外部变量,它是在函数外部定义的变量。它不属于哪一个函数,它属于一个源程序文件。其作用域是整个源程序。在函数中使用全局变量,一般应作全局变量说明。只有在函数内经过说明的全局变量才能使用。全局变量的说明符为extern。但在一个函数之前定义的全局变量,在该函数内使用可不再加以说明。程序7.4给出了全局变量的例子。程序7.4#includevoidf1(int);voidf2(int,int);intext;//定义一个全局变量intmain(void){inti=1;115/247 《C语言程序设计基础》教学大纲草案2021-10-237:25printf("请输入一个整数");scanf("%d",&ext);f1(i);return(0);}voidf1(intj){inti=2;f2(j,i);}voidf2(intk,intj){inti=3;printf("主函数的i=%d,f1的i=%d,当前函数的i=%d,外部变量ext=%d ",k,j,i,ext);}运行结果为:请输入一个整数:9主函数的i=1,f1的i=2,当前函数的i=3,外部变量ext=9程序7.4在主函数之前声明了一个外部变量ext,在主函数中从键盘读入一个整数并赋值给ext,最后在函数f2中打印了它的值。和程序7.2相对比,显然,外部变量ext在源程序范围内全程有效。图7.3是全局变量与局部变量的存储区域关系示意图。全局变量生存在整个程序运行期间,局部变量仅生存在函数运行期间。116/247 《C语言程序设计基础》教学大纲草案2021-10-237:25ext91000在程序运行时开辟1001的数据存储栈区:1000~55001002...程序7.1数据存储区main()f1(intj)f1(intk,intj){仅在函数调用{仅在函数调用{仅在函数调用inti=1;时开辟的临时inti=2;时开辟的临时inti=3;时开辟的临时f1(i);存储区域f2(j,i);存储区域.......存储区域..............i12000j14000k150002001i24001j2500120024003i35002.........主调函数main()存储区被调函数f1(intj)存储区被调函数f2(intk,intj)存储区图7.3全局变量与局部变量的存储区域关系程序7.5输入正方体的长宽高分别是l,w,h。求体积及三个面x*y,x*z,y*z的面积。#includeints1,s2,s3;intvs(int,int,int);intmain(void){intv,l,w,h;printf(" inputlength,widthandheight ");scanf("%d%d%d",&l,&w,&h);v=vs(l,w,h);printf("v=%ds1=%ds2=%ds3=%d ",v,s1,s2,s3);return(0);}intvs(inta,intb,intc){intv;v=a*b*c;s1=a*b;s2=b*c;117/247 《C语言程序设计基础》教学大纲草案2021-10-237:25s3=a*c;returnv;}运行结果为:inputlength,widthandheight333v=27s1=9s2=9s3=9程序中定义了三个外部变量s1,s2和s3,用来存放三个面积,其作用域为整个程序。函数vs用来求正方体体积和三个面积,函数的返回值为体积v。由主函数完成长宽高的输入及结果输出。由于C语言规定函数返回值只有一个,当需要增加函数的返回数据时,用外部变量是一种很好的方式。本例中如不使用外部变量,在主函数中就不可能取得v,s1,s2,s3四个值。而采用了外部变量,在函数vs中求得的s1,s2和s3值在main中仍然有效。因此外部变量是实现函数之间数据通讯的有效手段。对于全局变量还有以下几点说明:(1)定义和说明对于局部变量的定义和说明,可以不加区分。而对于外部变量则不然,外部变量的定义和外部变量的说明并不是一回事。外部变量定义必须在所有的函数之外,且只能定义一次。而外部变量说明出现在要使用该外部变量的各个函数内,在整个程序内,可能出现多次。(2)尽量不要使用全局变量外部变量可加强函数模块之间的数据联系,但是又使函数要依赖这些变量,因而使得函数的独立性降低。从模块化程序设计的观点来看这是不利的,因此,在不必要时尽量不要使用全局变量。(3)全局变量和局部变量的关系在同一源文件中,允许全局变量和局部变量同名。在局部变量的作用域内,全局变量不起作用。程序7.6#includeintvs(int,int);intmain(void){externintw,h;118/247 《C语言程序设计基础》教学大纲草案2021-10-237:25intl=5;printf("v=%d ",vs(l,w));}intvs(intl,intw){externinth;intv;v=l*w*h;returnv;}intl=3,w=4,h=5;运行结果为:v=100本例程序中,外部变量在最后定义,因此在前面函数中对要用的外部变量必须进行说明。外部变量l,w和vs函数的形参l,w同名。外部变量都作了初始赋值,mian函数中也对l作了初始化赋值。执行程序时,在printf语句中调用vs函数,实参l的值应为main中定义的l值,等于5,外部变量l在main内不起作用;实参w的值为外部变量w的值为4,进入vs后这两个值传送给形参l,wvs函数中使用的h为外部变量,其值为5,因此v的计算结果为100,返回主函数后输出。7.3.2变量的动态存储与静态存储前面介绍了,从变量的作用域(即从空间)角度来分,可以分为全局变量和局部变量。从另一个角度看,从变量值存在的作时间(即生存期)角度来分,可以分为静态存储方式和动态存储方式。静态存储方式:是指在程序运行期间分配固定的存储空间的方式。动态存储方式:是在程序运行期间根据需要进行动态的分配存储空间的方式。计算机为一个用户分配的存储空间由三部分组成:程序区:存放二进制形式的CPU指令代码程序;静态存储区;动态存储区;119/247 《C语言程序设计基础》教学大纲草案2021-10-237:25变量的存储类型是指变量占用内存空间的方式,也称为存储方式。变量的存储方式可分为“静态存储”和“动态存储”两种。静态存储变量通常是计算机执行某一程序的时候,直接在程序公共数据区为该变量分配了指定的存储单元,并一直保持不变,直至整个程序结束。全局变量即属于此类存储方式。动态存储变量是在程序执行过程中,使用它时才分配存储单元,使用完毕立即释放。典型的例子是函数的形式参数,在函数定义时并不给形参分配存储单元,只是在函数被调用时,才予以分配,调用函数完毕立即释放。如果一个函数被多次调用,则反复地分配、释放形参变量的存储单元。从以上分析可知,静态存储变量在程序生存期间是一直存在的,而动态存储变量则时而存在时而消失。我们又把这种由于变量存储方式不同而产生的特性,称为变量的生存期。生存期表示了变量存在的时间。生存期和作用域是从时间和空间这两个不同的角度来描述变量的特性,这两者既有联系,又有区别。一个变量究竟属于哪一种存储方式,并不能仅从其作用域来判断,还应有明确的存储类型说明。在C语言中,对变量的存储类型说明有以下四种:auto自动变量register寄存器变量extern外部变量static静态变量自动变量和寄存器变量属于动态存储方式,外部变量和静态变量属于静态存储方式。在介绍了变量的存储类型之后,可以知道对一个变量的说明不仅应说明其数据类型,还应说明其存储类型。因此变量说明的完整形式应为:存储类型说明符数据类型说明符变量名,变量名…;C语言规定,函数内凡未加存储类型说明的变量均视为自动变量。有关变量的存储方式的详细介绍请同学看附录8。7.3.3C语言中Project、源程序、函数、变量的关系任何一个大型软件都是一个工程(Project)意义上的开发工作。它由一个团队协同作业,每个人的作业之间必需互通互连,而且还要保证函数和数据的私密性。即使一个人进行一个工程项目,一部份模块和另一部份之间具有明显的任务分类,比如,数据输入模块是由一堆输入输出函数和文件读写函数构成,数据处理模块是由一堆数学函数构成。前面已经指120/247 《C语言程序设计基础》教学大纲草案2021-10-237:25出,一个C工程项目可以由多个源程序构成,作为一个C程序设计人员,良好的设计风格是把一个整体工作按性质划分,不同性质的任务规划到不同的源程序完成,因此,一个projects是一组分工明确、性质不同的源程序构成。有关工程、源程序和执行文件的基本概念如图7.4所示。源程序APrcject源程序B编译执行文件...源程序n图7.4project、源程序、执行文件的关系源程序之间共用函数的方法—外部函数声明在一个源程序A中可以使用由另一个源程序B定义的函数,对A说也就是A的外部函数,其说明形式如同在头部文件的函数声明,只是标注出extern关键字,说明该函数体在另一个源程序中,编译软件就会在其它源程序中寻找,外部函数声明的一般形式为:extern类型说明函数名(参数表);程序Atest.prj程序B#include#includevoidinput(int*,int*);floatsqr(intx)externfloatsqr(int);{externintmodel(int,int);return(sqrt(x));intmain(void)}{intmodel(intx,intm)intx,y,m;{floata;return(x%m);input(&x,&m);}a=sqr(x);y=model(x,m);printf("sqr(%d)=%.2f ",x,a);printf("%d/%dresidual=%d ",x,m,y);return(0);}voidinput(int*x,int*m){printf("输入xm: ");scanf("%d%d",x,m);图7.5project、源程序、函数的关系}121/247 《C语言程序设计基础》教学大纲草案2021-10-237:25程序Atest.prj程序B#include#includeexternvoidinput();intx,m;externvoidoutput(int,int,float,int);floatsqr(int);voidoutput(intx,intm,floata,inty)intmodel(int,int);{externintx,m;printf("sqr(%d)=%.2f ",x,a);intmain(void)printf("%d/%dresidual=%d ",x,m,y);{}inty;voidinput()floata;{input();printf("输入xm: ");a=sqr(x);scanf("%d%d",&x,&m);y=model(x,m);}output(x,m,a,y);return(0);}floatsqr(intx){return(sqrt(x));}intmodel(intx,intm){return(x%m);图7.6project、源程序、变量的关系}源程序之间共用变量的方法—外部变量声明我不赞成通过共用外部变量在程序之间传递参数,但是,C语言提供了通过外部变量在源程序之间传递参数的能力。既然一个C工程项目可以由多个源程序构成,在一个源程序A中使用由另一个源程序B定义的全程变量,对A说也就是A的外部变量,其说明一般形式为:extern类型说明符变量名,变量名,…;外部变量在定义时就已分配了内存单元,外部变量仅允许在定义时候进行初始赋值,外部变量说明的时候不能再赋值,只是说明在函数内要使用该外部变量。图7.5例子中,函数input输入了两个变量x和m,通过函数之间的参数传递计算了x的平方根和模m的数值,图7.6的例子则给出了将input作为外部函数,x和m作为外部变量在程序之间的传递过程。7.4指针的概念指针是C语言中最为困惑的一个概念。什么是指针?如何理解指针也是一个变量?如何分清变量的地址和变量的值之间关系?不同数据类型的指针有什么不同?为什么要对指针初始化?这都需要我们对计算机存储机制、甚至是CPU内部寄存器的运作原理有一个清楚地了解。122/247 《C语言程序设计基础》教学大纲草案2021-10-237:257.4.1指针是一个存储地址的变量指针与变量的关系可以用图7.7来说明。指针变量名指针变量的地址门牌号码1001寻宝图宝藏地点10010001魔法书地点10410002门牌号码1101...0003指针存储着数据变量的地址,指引程序找到所需的数据。门牌号码1201门牌号码1041图7.7指针是一个存储地址的变量寻宝图上有宝藏地点和魔法书地点单元项。如果我们想寻找宝藏,寻宝图的宝藏地点(第一个单元格)内存储着放宝藏的房间号码;如果是想要魔法书,我们可以在寻宝图的魔法书地点(第二个单元格)内找到存放魔法书的房间号码,它们存储的内容(房间号码),可以指引我们找到所需要的宝物。因此,我们称它们(宝藏地点和魔法书地点单元项)为指针。所以,指针存储的内容是一个地址,是一个数据变量(宝藏或魔法书)所在的地址。假定,现在将宝藏从1001房间转移到1101房间,我们只需要把寻宝图上的宝藏地点里的内容改为1101,我们就仍然可以从宝藏地点单元格里找到正确的宝藏存放房间号码。就是说,指针的内容可以根据数据变量实际地址的变化而改变。所以,指针它也是一个变量,可以赋值,即将某一数据变量的地址写进指针所在的地址单元内。定义如下:指针是一个地址变量,其操作对象是某个数据变量的存储地址,通过获取变量的地址,即,指针指向一个数据对象,指引出变量的存储位置,从而可以访问(读写)该变量的值。7.4.2指针声明如下是一个整型变量和字符型变量的说明:inti_a=20;123/247 《C语言程序设计基础》教学大纲草案2021-10-237:25charc_a=’A’;它们在内存里都有一个存储位置,即变量i_a对应着一个地址,如图7.8(a),变量c_a也对应一个地址,如图7.8(b)所示。C语言中每一变量都归属于某种数据类型,其占用的存储字节数不同,比如整型数2个字节,浮点型4个字节等。指针也是变量,那么,指针是什么数据类型?从如图7.8(c)和(d)看,2个指针变量占用的存储单元地址数相同。变量名变量值=20变量地址变量名变量值='A'变量地址i_a000FF000Hc_a'A'0FF010H200FF001H0FF011H........(a)2字节整型变量存储位置(b)单字节字符型变量存储位置变量名变量值i_p=&i_a变量地址变量名变量值c_p=&c_a变量地址i_p00H0FF050Hc_p10H0FF040HF0H0FF051HF0H0FF041H0FH0FF052H0FH0FF042H....0FF043H0FF044H0FF045H(c)指向整型变量指针(d)指向字符类型变量的指针图7.8进一步考虑,计算机存储器的地址宽度(二进制位数)是固定的,比如是6个字节,或者8个字节。也就是说,描述变量地址所需的字节数是相同的,那么,是不是指针的数据类型只有一种?指针的类型当然不只一种。指针的数据类型与它所指向的数据变量的类型相同。比如,一个指向字符变量的指针,是字符型指针,而一个指向浮点型变量的指针,是浮点型指针。为什么地址宽度相同,C语言还要区分不同数据类型的指针?可以这样理解指针的数据类型的含义。假定有一个字符型数组c_array[N]和整型数数组i_array[N],两个指针p1和p2分别指向它们的首地址,如图7.9所示。程序7.7遍历输出两个数组的每一个元素。124/247 《C语言程序设计基础》教学大纲草案2021-10-237:25变量名变量地址变量名变量地址p1c_array[0]'a'0FF000Hp2i_array[0]000FF010H*(p1+1)c_array[1]'b'0FF001H10FF011H*(p1+2)c_array[2]'c'0FF002H*(p2+1)i_array[1]000FF012H*(p1+3)c_array[3]' '0FF003H20FF013H*(p2+2)i_array[2]000FF014H30FF015H*(p2+3)i_array[3]000FF016H40FF017H变量值变量值变量名p1=&c_array[0]变量地址变量名p2=&i_array[0]变量地址p100H0FF040Hp210H0FF043HF0H0FF041HF0H0FF044H0FH0FF042H0FH0FF045H(c)指向字符型数组变量的指针图7.9(d)指向整型数组变量的指针程序7.7#includeintmain(void){charc_array[4]="abc",*p1;inti,i_array[4]={1,2,3,4},*p2;p1=c_array;p2=i_array;for(i=0;i<4;i++){printf("c_array[%d]=%c,",i,*(p1+i));printf("i_array[%d]=%d ",i,*(p2+i));}return(0);}这里,p1是一个字符型指针,每次i加1增量,修正p1的指向值也加1,因而能正确的指向c_array字符型数组的下一个元素存储位置。而p2是一个整型数指针,每次i加1增量,修正p2的指向值是加2,因而也能正确的指向i_array整型数数组的下一个元素存储位置。如果指针都是同一数据类型,则对指针操作的时候,C语言就不知道它指向的数据变量的类型,也不知道正确的地址修正量是多少,程序会出现混淆。所以,指针的数据类型必需与它所指向的数据变量的类型相同。因此,指针的声明包含了指针变量说明和指针类型说明。(注意,在学习了结构概念之后,需要特别说明如何定义125/247 《C语言程序设计基础》教学大纲草案2021-10-237:25一个指向该结构类型的指针,比如用在结构数组的时候,指针加一的概念)C语言用“*”说明一个变量是指针,比如:char*p;声明了一个字符型指针变量p。7.4.3地址运算符“&”和间接运算符“*”如何让指针指向一个变量?很简单,通过地址运算符“&”获取变量地址,并赋值给指针,从而让指针获取了变量的地址,也就是指向该变量。int*p,i;p=&i;通过上述语句,指针p就已经指向了i。那么,如何操作指针所指向的变量的值?使用间接运算符“*”可以直接给指针所指向的变量赋值,比如上述语句之后,p已经指向i,则:*p=6;就是把6赋值给i。程序7.8说明了两者的用法。程序7.8#includeintmain(void){inti,*p;p=&i;*p=6;printf("%#x,%d ",&p,*p);return(0);}运行结果是:0x12ff78,6注意,这里的0x12ff78是程序运行时操作系统分配给变量i的地址,它取决于计算机当前内存使用状况,是一个随机数值。7.4.4指针与数据结构既然指针变量的值是一个地址,那么这个地址不仅可以是某一数据变量的地址,也可126/247 《C语言程序设计基础》教学大纲草案2021-10-237:25以是其它数据结构的地址。比如,可以通过指针取得一个数组的地址:intarray[N],*p;p=array;在一个指针变量中存放一个数组的首地址有何意义呢?因为数组元素在内存中是连续存放的。通过访问指针变量取得了数组的首地址,也就找到了该数组的所有元素。这样一来,凡是出现数组的地方,仅需要用一个指针变量来表示就足够了,这样做得好处是使程序的概念十分清楚,程序本身也精练,高效。在C语言中,一种数据类型或数据结构往往都占有一组连续的内存单元。用“数组地址”或“结构地址”的概念并不能很好地描述一种数据类型或数据结构,而“指针”虽然实际上也是一个地址,但它却是一个数据结构的首地址,它是“指向”一个数据结构的,因而概念更为清楚,表示更为明确。这也是引入“指针”概念的一个重要原因。在C语言程序中,还有什么在内存是连续存在的?显然是就函数。函数代码和数据也是连续的占用一组内存单元。让一个指针变量指向(存放)一个函数的首地址的意义,和让指针指向一个数组或数据结构的意义完全一样。任何一个函数都可以用一个指针变量来表示。对于指向数组的指针变量,可以加上或减去一个整数i。设pa是指向数组a的指针变量,则:pa+=i;pa-=i;pa++;++pa;pa--;--pa;以上运算都是合法的。指针变量加或减一个整数i的意义是把指针指向的当前位置(指向某数组元素)向前或向后跨过i个位置。注意,数组指针变量向前或向后移动一个位置和地址加1或减1在概念上是不同的。因为数组可以有不同的类型,各种类型的数组元素所占的字节长度是不同的。如指针变量加1,即向后移动1个位置表示指针变量指向下一个数据元素的首地址。而不是在原地址基础上加1。例如:inta[5],*pa;127/247 《C语言程序设计基础》教学大纲草案2021-10-237:25pa=a;/*pa指向数组a,也是指向a[0]*/pa=pa+2;/*pa指向a[2],即pa的值为&pa[2]*/指针变量的加减运算只能对数组指针变量进行,对指向其它类型变量的指针变量作加减运算是毫无意义的。而两个指针变量之间的运算只有是指向同一数组的两个指针变量之间才能进行运算,否则运算毫无意义。引入指针变量后,就可以用两种方法来访问数组元素了。第一种方法为下标法,即用a[i]形式访问数组的第i个元素。第二种方法为指针法,即采用*(pa+i)形式,用间接访问的方法来访问数组的第i个元素。给诸位同学一个忠告:千万不要使用一个没有赋值的指针,也就是没有指向任何变量的空指针。否则,后患无穷。比如:int*p;*p=100;一个好的操作系统会提醒你内存引错误,并中止程序执行,使用空指针的后果无法预料。7.5函数调用7.5.1调用形式C语言调用函数的的方法是直接使用函数名和实参,也就是将要赋给被调用函数的参量,按该函数声明的参数形式传递过去,然后进入子函数运行,运行结束后再按子函数规定的数据类型返回一个值给调用函数。函数调用方式:有以下三种:·赋值如:c=max(x,y);·表达式中如:c=1+max(x,y);printf(“Max=%d ”,max(x,y));·执行函数如:max(x,y);实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应预先用赋值,输入等办法使实参获得确定值。7.5.2参数传递7.5.2.1传递参数值用户编写的函数一般在对其说明和定义时就规定了形式参数类型。因此,调用这些函数128/247 《C语言程序设计基础》教学大纲草案2021-10-237:25时的参量,必须与子函数中形式参数的数据类型、顺序和数量完全相同,否则在调用中将会出错,得到意想不到的结果。程序7.10求两个输入整数之和,参数值传递如图7.10所示。#includeintadd(int,int);intmain(void){inta,b;printf("输入a和b: ");scanf("%d%d",&a,&b);printf("a+b=%d ",add(a,b));return(0);}intadd(inta,intb){return(a+b);}main()voidsub(intc,intd){{inta=10,b=20;printf("add=%d",c+d);.......}a102000c4000b202001d4001主调函数main()存储区被调函数sub(intc,intd)存储区(a)调用sub(a,b);传递顺序与参数排列顺序一致执行sub()函数代码a102000c104000b202001d204001(b)主调函数main()存储区被调函数sub(intc,intd)存储区图7.10实参传递7.5.2.2数组传递方法如果需要把数组作为实参向被调用函数传递,显然,我们不可能把数组的所有元素都罗129/247 《C语言程序设计基础》教学大纲草案2021-10-237:25列在参数表中,那么,如何传递数组呢?回忆一下我们对数组的定义:数组是某类型数据元素在内存中的连续存储形式,既,逻辑相邻的元素,其物理地址也相邻。因此,可以用数组的第一个元素地址来代表数组。于是,我们如果把数组起始地址传递给被调函数,就等于把整个数组传递过去了。因此,数组作为实参是传递起始地址,而不是将整个数组元素都复制到被调函数中去,即用数组名作为实参调用。当然,数组变量的类型在两个函数中必须相同。图7.11说明了数组参数传递与普通参数传递的区别,程序7.11描述了方法实现。程序7.11#includevoidsub(char*);intmain(void){chara[5]=“auto”;sub(a);return(0);}voidsub(char*p)//p是指针变量{inti;for(i=0;i<4;i++)printf("p[%d]=%c ",i,*(p+i));}130/247 《C语言程序设计基础》教学大纲草案2021-10-237:25main()voidsub(int*p){{chara[5]="auto";inti;.......for(i=0;i<4;i++)printf("a[%d]=%d ",i,p[i]);}a[0]'a'2000p4000a[1]'u'2001a[2]'t'2002a[3]'o'2003a[4]' '2004主调函数main()存储区(a)被调函数sub(int*p)存储区执行语句sub(a);a[0]'a'2000p20004000a[1]'u'2001't'2002a[2]a[3]'o'2003a[4]' '2004(b)主调函数main()存储区被调函数sub(int*p)存储区图7.11数组作为实参传递注意,如果只是传递数组中的某个元素时,仍然是将该元素作为实参,此时,按使用其它简单变量的方法传递数组元素。7.5.2.3传递参数地址有两种方法可以终止函数运行并返回到调用它的函数中:(1)执行到函数的最后一条语句后返回;(2)执行到语句return时返回。第一种方法只在无返回值函数调用中使用。若要返回一个值就必须用return语句,并在return语句中指定返回的值。语句的一般形式为:return表达式;或者为:return(表达式);该语句的功能是计算表达式的值,并返回给主调函数。在函数中允许有多个return语句,但每次调用只能有一个return语句被执行,因此只能返回一个函数值。虽然return语句可以向调用函数返回值,但这种方法只能返回一个参数,在许多情况下要返回多个参数,这时用return语句就不能满足要求。当需要多个参数返回,或者需要改变调用函数的变量值的时候,C语言提供了另一种参数传递的方法,就是调用函数向被调用函数传递的不是变量的值,而是传递变量的地址,我们知道,任何一个变量都具有地址,131/247 《C语言程序设计基础》教学大纲草案2021-10-237:25变量的值存储在该地址当中。如果在被调函数中向某一个变量的地址写入不同的数值之后,也就改变了调用函数中相应变量的值,从而达到了返回多个变量的目的。图7.12说明了在函数中改变主调用函数内的变量值方法。程序7.13给出了实例。&ii02000m20004000j02001&jn20014001...主调函数main()存储区被调函数sub(int*p)存储区(a)变量地址作为实参传递(2000)<--10执行sub函数i102000*m=10;j202001*n=20;...(2001)<--20主调函数main()存储区(b)给m和n代表的存储单元赋值图7.12在函数中改变主调用函数内的变量值方法程序7.13#includevoidinput(int*,int*);intadd(int,int);intmain(void){inti,j;input(&i,&j);/*以传送参数地址的方式调用子函数*/printf("i+j=%d ",add(i,j));return(0);}intadd(inta,intb){return(a+b);}voidinput(int*a,int*b){132/247 《C语言程序设计基础》教学大纲草案2021-10-237:25printf("输入a和b: ");//a和b是地址,给该地址所代表的存储单元赋值scanf("%d%d",a,b);}7.6递归函数与分治算法一个函数在它的函数体内调用它自身的过程,称为递归调用。这种函数称为递归函数。C语言允许函数的递归调用。在递归调用过程中,主调函数又是被调函数。执行递归函数将反复调用其自身。每调用一次就进入新的一层,如果编程中没有设定可以中止递归调用的出口条件,则递归过程会无限制的进行下去,最终会造成系统溢出错误。一个C语言程序是否应该设计成一个递归调用的形式,完全取决于实际应用问题本身的特性,只有在待处理对象本身具有递归结构特征的情况下,程序才应该设计为递归结构。比如,在现实世界中描述一棵树的定义如下:树是一个或多个节点组成的有限集合,其中:a)必有且仅有一个特定的称为根(root)的节点。b)剩下的节点被分成m≥0个互不相交的集合T1,T2,···,Tm,而且其中的每一元素又都是一棵树,称为根的子树(Subtree)。arootd3bc度深efghij图7.13树的形式显然树的定义是递归的,所以,有关树的函数结构都是递归形式的。如果任务对象本身不具备递归性质,比如,计算一个高阶方程式,就不可能设计递归形式的程序。对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。这种算法设计策略叫做分治法。使用分治法处理问题的一个例子是求n的阶乘。一个循环结构求n的阶乘程序如下:程序7.13#includeintmain(void)133/247 《C语言程序设计基础》教学大纲草案2021-10-237:25{longi,n,sum=1;printf("请输入n: ");scanf("%d",&n);for(i=1;i<=n;i++)sum*=i;printf("n!=%d ",sum);return(0);}从减小n的规模考虑,n的阶乘可以看成是n(n-1)!,而求(n-1)!与求n!之间互相独立且问题形式相同:(n-1)!=(n-1)(n-2)!,显然,这是一个递归求解,因为我们追求将问题的规模一直分解到它的原子形式,也就是1!=1,这就是出口条件,从底层回头,再将各子问题的解合并得到原问题的解,于是,2!=2,3!=6,…。程序7.14#includeintf(intn);intread();intmain(void){longn,sum=1;n=read();if(n>0)sum=f(n);printf("n!=%d ",sum);return(0);}intf(intn){if(n==1)return(1);return(n*f(n-1));}intread()134/247 《C语言程序设计基础》教学大纲草案2021-10-237:25{intn;printf("请输入n值: ");scanf("%d",&n);return(n);}本节介绍分治算法与递归函数的概念。介绍分治法的基本思想和基本步骤,通过实例讨论了利用分治策略设计递归函数的途径。包括:分治法的设计思想分治法的适用条件分治法的基本步骤7.6.1分治法的基本思想分治法的设计思想是,将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。如果原问题可分割成k个子问题,1intsearch(int*,int);intmain(void){inti,key;intarray[6]={3001,3456,3345,1234,2345,3012};scanf("%d",&key);i=search(array,key);136/247 《C语言程序设计基础》教学大纲草案2021-10-237:25printf("i=%d ",i);return(0);}intsearch(int*array,intkey){inti;for(i=0;i<6;i++)if(key==*(array+i))break;return(i);}比较自然的想法是使用for语句结构,从表头开始,一个一个地扫描L的所有元素,直到找到x为止。这种方法对于有n个元素的线性表在最坏情况下需要n次比较,此时,待查元素在线性表的末尾,最好情况下需要1次比较,此时,待查元素就在表头,因此平均需要n1比较次数是。2下面我们考虑一种简单的情况。假设该线性表已经排好序了,不妨设它按照学号的递增顺序排列(即由小到大排列)。在这种情况下,我们是否有改进查找效率的可能呢?如果线性表里只有一个元素,则只要比较这个元素和x就可以确定x是否在线性表中。因此,这个问题满足分治法的第一个适用条件;同时,我们注意到对于排好序的线性表L有以下性质:比较x和L中任意一个元素L[i],若x=L[i],则x在L中的位置就是i;如果xL[i],同理我们只要在L[i]的后面查找x即可。无论是在L[i]的前面还是后面查找x,其方法都和在L中查找x一样,只不过是线性表的规模缩小了。这就说明了此问题满足分治法的第二个和第三个适用条件。很显然,此问题分解出的子问题相互独立,即在L[i]的前面或后面查找x是独立的子问题,因此满足分治法的第四个适用条件。那么,每次在子问题处理的线性表中如何选取i的位置?前面指出,一般是采取将原问题规模分解成2个规模相等的子问题,即对半处理。于是,我们得到利用分治法在有序表中查找元素的算法,也称之为对半检索。137/247 《C语言程序设计基础》教学大纲草案2021-10-237:25N1a[]2后半子集a[N]a[1]前半子集图7.15对半检索问题的递归求解算法:设L为排好序的线性表,x为需要查找的元素,high,low分别为x的位置的上下界,即如果x在L中,则x在L[low...high]中。每次我们用L中间的元素L[m]与x比较,从而确定x的位置范围。然后递归地缩小x的范围,直到找到x。functionBinary_Search(L,low,high,x);beginiflow>highthenreturn(-1)elsebeginm:=(low+high)div2;ifx=L[m]thenreturn(m)elseifx>L[m]thenreturn(Binary_Search(L,m+1,high,x));elsereturn(Binary_Search(L,low,m-1,x));end;end;可以证明,在最坏情况下二分查找法的复杂度为O(logn)。2程序7.16对半检索#includeintBinary_Search(int,int*,int,int);intread();intmain(void){ints[11]={5,13,19,21,37,56,64,75,80,88,92};//测试数据intx,i;x=read();i=Binary_Search(x,s,0,10);138/247 《C语言程序设计基础》教学大纲草案2021-10-237:25printf("i=%d ",i);return(0);}intBinary_Search(intx,int*p,intlow,inthigh){intmid;if(low>high)return(-1);mid=(low+high)/2;if(x==*(p+mid))return(mid);else{if(x>*(p+mid))Binary_Search(x,p,mid+1,high);elseBinary_Search(x,p,low,mid-1);}}intread(){intx;printf("请输入检索值: ");scanf("%d",&x);return(x);}7.6.2.2汉诺塔算法Hanoi塔问题:一个平面上有三根立柱:A,B,C。A柱上套有n个大小不等的圆盘,大的在下,小的在上。如图7.14所示。要把这n个圆盘从A柱上移动C柱上,每次只能移动一个圆盘,移动可以借助B柱进行。但在任何时候,任何柱上的圆盘都必须保持大盘在下,小盘在上。求移动的步骤。分析方法:①简化问题:设盘子只有一个,则本问题可简化为a→c。②对于大于一个盘子的情况,逻辑上可分为两部分:第n个盘子和除n以外的n-1个盘139/247 《C语言程序设计基础》教学大纲草案2021-10-237:25子。如果将除n以外的n-1个盘子看成一个整体,则要解决本问题,可按以下步骤:a、将a杆上n-1个盘子借助于c先移到b杆;a→b(n-1,a,c,b)b、将a杆上第n个盘子从a移到c杆;a→cc、将b杆上n-1个盘子借助a移到c杆。b→c(n-1,b,a,c)move(n-1,b,a,c);move(n-1,a,c,b);111222333n-1n-1n-1nA[n]B[n]C[n]if(n==1)c[n]=a[n];图7.14Hanoi塔问题的递归求解程序7.17汉诺塔#includevoidmove(int,int*,int*,int*);intmain(void){intn,i;inta[40],b[40],c[40];printf(" inputnumber: ");scanf("%d",&n);for(i=1;i<=n;i++)a[i]=n-i;move(n,a,b,c);for(i=1;i<=n;i++){printf("a[%d]=%d,c[%d]=%d ",i,a[i],i,c[i]);}return(0);}voidmove(intn,int*a,int*b,int*c){if(n==1)*(c+n)=*(a+n);else{140/247 《C语言程序设计基础》教学大纲草案2021-10-237:25move(n-1,a,c,b);*(c+n)=*(a+n);move(n-1,b,a,c);}}7.7小节(1)变量作用域同一源程序的函数之间直接共享数据全局变量:在一个project内一直存在定义与说明意义不同变量不同源程序之间通过extern说明共享数据仅在一个函数内存在局部变量,定义等同于说明通过实参在函数之间传递数据(2)函数作用域同一源程序的函数直接声明函数不同源程序之间的函数通过extern声明(3)参数传递传递参数值:被调函数参数与主调函数完全隔离,互不影响实参传递传递参数地址:被调函数取得指向主调函数参数的指针,可以直接操作主调函数的参数值(4)指针指针是一个变量,存储另一个变量的地址指针指针指向一个对象,操作指针等同于操作对象指针的数据类型与其所指向数据同类141/247 《C语言程序设计基础》教学大纲草案2021-10-237:25第八章数组、指针与结构8.1要点(1)二维数组的逻辑结构与存储结构;(2)指向函数的指针;(3)指针数组;(4)结构变量的概念。理解结构体的含义,掌握结构体类型变量的定义方法,掌握结构体类型变量的访问方法,了解结构体数组的定义和数组元素的访问方法,熟悉结构指针的应用。(5)动态内存分配。8.2数组数组是我们遇见的第一种数据结构。它描述了数据元素在内存中的物理存储方式。通过数据元素ai和ai+1(i=0,1,2,…)在内存物理地址上的相邻存储关系,表达了逻辑上的线性有序关系:。8.2.1一维数组8.2.1.1存储结构一维数组实际上就是具有相同数据类型的元素表,比如,一个字符型数组的逻辑描述是:charnames[7];它有7个字符元素,假设内存起始地址是1000,则物理描述如下表所示:有7个字符元素的起始地址为1000的数组地址1000100110021003100410051006元素下标01234568.2.1.2通过指针引用数组元素C语言规定,如果指针变量p已指向数组a(p的初值为p=a),就可以用两种方法来访问数组元素:下标法,即用a[i]形式访问数组元素。在前面介绍数组时都是采用这种方法。指针法,即采用*(a+i)或*(p+i)形式,用间接访问的方法来访问数组元素,其中a是数组名,p是指向数组的指针变量。于是:*(p+i)或*(a+i)就是p+i或a+i所指向的数组元素,142/247 《C语言程序设计基础》教学大纲草案2021-10-237:25即a[i]。例如,*(p+5)或*(a+5)就是a[5]。另外,指向数组的指针变量也可以带下标,如p[i]与*(p+i)等价。图8.1访问数组的两种方法8.2.2二维数组8.2.2.1存储结构二维数组说明形式如下:类型数组名[第二维数组长度][第一维数组长度];比如,一个二维的字符型数组定义如下:charname[4][4];有4×4个字符元素,起始地址为1000的数组地址1000100110021003下标0,00,10,20,310041005100610071,01,11,21,310081009101010112,02,12,22,310121013101410153,03,13,23,3也就是说,二维数组和一维数组完全一样,在内存中是连续排列的,排列方式是按行展开。对于一个n×m二维数组,若已知起始地址L,则第i行j列元素的地址是:ADDR(i,j)=L+(i*m+j)*sizes(sizes是该数据类型字节数)访问二维数组元素的方法也有两种:下标法:name[i][j];143/247 《C语言程序设计基础》教学大纲草案2021-10-237:25指针法:C语言允许把一个二维数组分解为多个一维数组来处理。因此,数组name可分解为四个一维数组,即:a[0],a[1],a[2],a[3]它们等同于四个向量指针,指向每一个一维数组:*(name+0),*(name+1),*(name+2),*(name+3)每一个向量指针指向一个一维数组的首地址,每个一维数组又含有四个元素,分别是:*(*(name+0)+0),*(*(name+0)+1),*(*(name+0)+2),*(*(name+0)+3)*(*(name+1)+0),*(*(name+1)+1),*(*(name+1)+2),*(*(name+1)+3)*(*(name+2)+0),*(*(name+2)+1),*(*(name+2)+2),*(*(name+2)+3)*(*(name+3)+0),*(*(name+3)+1),*(*(name+3)+2),*(*(name+3)+3)name1000100210041006name[0]1000name[0][0]name[0][1]name[0][2]name[0][3]name[1]10081008101010121014name[1][0]name[1][1]name[1][2]name[1][3]name[2]10161016101810201022name[2][0]name[2][1]name[2][2]name[2][3]name[3]10241024102610281032name[3][0]name[3][1]name[3][2]name[3][3]二维数组是由多个一维数组构成的,左下标是指向一维数组的指针下面的例子给出了访问二维数组的方法:程序8.2#includeintmain(void){intname[4][4],(*p)[4];//定义一个指向二维数组的指针变量。inti,j;for(i=0;i<4;i++)for(j=0;j<4;j++)name[i][j]=i*4+j+1;for(i=0;i<4;i++){for(j=0;j<4;j++)printf("name[%d][%d]=%d",i,j,name[i][j]);printf(" ");}printf(" ");for(i=0;i<4;i++){for(j=0;j<4;j++)printf("*(*(name+%d)+%d)=%d",i,j,*(*(name+i)+j));144/247 《C语言程序设计基础》教学大纲草案2021-10-237:25printf(" ");}p=name;for(i=0;i<4;i++){for(j=0;j<4;j++)printf("*(*(p+%d)+%d)=%d",i,j,*(*(p+i)+j));printf(" ");}return(0);}运行结果是:name[0][0]=1name[0][1]=2name[0][2]=3name[0][3]=4name[1][0]=5name[1][1]=6name[1][2]=7name[1][3]=8name[2][0]=9name[2][1]=10name[2][2]=11name[2][3]=12name[3][0]=13name[3][1]=14name[3][2]=15name[3][3]=16*(*(name+0)+0)=1*(*(name+0)+1)=2*(*(name+0)+2)=3*(*(name+0)+3)=4*(*(name+1)+0)=5*(*(name+1)+1)=6*(*(name+1)+2)=7*(*(name+1)+3)=8*(*(name+2)+0)=9*(*(name+2)+1)=10*(*(name+2)+2)=11*(*(name+2)+3)=12*(*(name+3)+0)=13*(*(name+3)+1)=14*(*(name+3)+2)=15*(*(name+3)+3)=16*(*(p+0)+0)=1*(*(p+0)+1)=2*(*(p+0)+2)=3*(*(p+0)+3)=4*(*(p+1)+0)=5*(*(p+1)+1)=6*(*(p+1)+2)=7*(*(p+1)+3)=8*(*(p+2)+0)=9*(*(p+2)+1)=10*(*(p+2)+2)=11*(*(p+2)+3)=12*(*(p+3)+0)=13*(*(p+3)+1)=14*(*(p+3)+2)=15*(*(p+3)+3)=16注意,把二维数组分解为一维数组name[0],name[1],name[2],name[3]之后,设p为指向二维数组的指针变量,也称之为向量指针,它一般形式的说明为:类型说明符(*指针变量名)[长度]它表示p是一个向量指针变量,它指向包含4个元素的一维数组。若指向第一个一维数组name[0],其值等于name,或&name[0]等。而p+i则指向一维数组name[i]。从前面的分析可得出*(p+i)+j是二维数组i行j列的元素的地址,而*(*(p+i)+j)则是i行j列元素的值。8.2.2.2二维字符串数组二维字符串数组的左下标是定义字符串的的数量,右下标是每一字符串长度,比如:charnames[4][12];定义了4个长度为12个字符的字符串。访问每个字符串的简洁形式是直接给出字符串的左145/247 《C语言程序设计基础》教学大纲草案2021-10-237:25下标:比如:gets(names[2]);等同于:gets(&names[2][0]);names[4][12]01234567891011names[0]char_array‘ ’names[1]tsinghua‘ ’names[2]automation‘ ’names[3]sftoware‘ ’下面的例子给出了访问二维字符串数组的方法:程序8.3#includeintmain(void){charnames[5][40];inti;for(i=0;i<5;i++){printf("inputname: ");scanf("%s",names[i]);}for(i=0;i<5;i++)printf("names[%d]=%s ",i,names[i]);return(0);}8.2.3指针数组指针也是一个变量,它也可以构成数组,就是一组指针变量的集合,比如:char*p[5];是一个具有5个元素(指针变量)的数组,使用指针数组改写程序8.3如下:程序8.4#includeintmain(void){146/247 《C语言程序设计基础》教学大纲草案2021-10-237:25charnames[5][40],*p[5];inti;for(i=0;i<5;i++){printf("inputname: ");*(p+i)=names[i];//*(p+i)是指针数组的第i个元素,也是指针。scanf("%s",*(p+i));}for(i=0;i<5;i++)printf("names[%d]=%s ",i,*(p+i));return(0);}再看一个例子。从键盘任意输入5个英文单词(设每个单词字符串的长度小于20),然后按字典编辑顺序打印在屏幕上。程序8.5#include#includevoidinput(char**);//数组名是指针,数组的元素是指针,所以形参就是指针的指针voidcomp(char**,char**);voidlist(char**);intmain(void){charword[5][20],*p[5],*sp[5];inti;for(i=0;i<5;i++)*(p+i)=word[i];//*(p+i)是指针,指向第i个字符串input(p);comp(p,sp);list(sp);return0;}voidinput(char**p){147/247 《C语言程序设计基础》教学大纲草案2021-10-237:25inti;for(i=0;i<5;i++){printf("请输入单词 ");scanf("%s",*(p+i));}}voidcomp(char**p,char**sp){inti,j;for(j=0;j<5;j++){*(sp+j)=*(p+0);for(i=1;i<5;i++)strcmpi(*(sp+j),*(p+i))>0?*(sp+j)=*(p+i):*(sp+j);for(i=0;i<5;i++)strcmpi(*(sp+j),*(p+i))==0?*(p+i)="zzzzzzzzzzzzzz":*(p+i);}}voidlist(char**sp){inti;for(i=0;i<5;i++)printf("sp(%d)=%s ",i,*(sp+i));}程序8.5每次在指针数组中找到一个指向最小串的指针,然后赋给另一个指针数组,是用指针数组省去了复制字符串操作。程序8.6是说明传递一个指针数组给一个函数的方法,与普通数组传递方法完全相同的即不带下标,直接用指针数组名调用函数。程序8.6#includevoiddisp(int**);intmain(void){int*q[5],var[5]={0,1,2,3,4},i;148/247 《C语言程序设计基础》教学大纲草案2021-10-237:25for(i=0;i<5;i++)q[i]=&var[i];//取得各变量的地址disp(q);return(0);}voiddisp(int**p){inti;printf(" ");for(i=0;i<5;i++)printf("p[%d]=%#x,*p[%d]=%d ",i,p[i],i,*p[i]);}8.3指向指针的指针如果一个指针变量的值是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。在前面已经介绍过,通过指针访问变量称为间接访问。由于指针变量直接指向变量,所以称为“单级间址”。而如果通过指向指针的指针变量来访问变量则构成“二级间址”。指针变量p数据变量a数据a的地址值指针变量p指针变量q数据变量a指针q的地址数据a的地址值图8.2指向指针的指针定义一个指向指针型数据的指针变量形式如下:char**p;p前面有两个*号,相当于*(*p)。显然*p是指针变量的定义形式,如果没有最前面的*,那就是定义了一个指向字符数据的指针变量。现在它前面又有一个*号,表示指针变量p是指向一个字符指针型变量的。*p就是p所指向的另一个指针变量。从图8.3可以看到,name是一个指针数组,它的每一个元素是一个指针型数据,其值为地址。既然name是一个数组,它的每一个元素都有相应的地址。数组名name代表该指针数组的首地址。name+i是mane[i]的地址。name+i就是指向指针型数据的指针(地址)。还可149/247 《C语言程序设计基础》教学大纲草案2021-10-237:25以设置一个指针变量p,使它指向指针数组元素。p就是指向指针型数据的指针变量,我们称之为指针的指针。图8.3指针数组的元素是指针程序8.7intmain(void){char*name[4]={"Followme","BASIC","FORTRAN","Computerdesighn"};char**p;inti;for(i=0;i<4;i++){p=name+i;printf("%#x=%s ",*p,*p);}return(0);}其输出为:0x420020=Followme0x42002c=BASIC0x420f9c=FORTRAN0x420fd8=Computerdesighn程序8.7中,printf函数语句里的第一个*p输出name[i]的值(它是一个地址),第二个*p以字符串形式(%s)输出*p所指向的字符串。程序8.8一个指针数组的元素指向数据的简单例子。150/247 《C语言程序设计基础》教学大纲草案2021-10-237:25#includeintmain(void){inta[5]={1,3,5,7,9};int*num[5]={&a[0],&a[1],&a[2],&a[3],&a[4]};int**p,i;p=num;for(i=0;i<5;i++){printf("%dt",**p);p++;}printf(" ");return(0);}其输出为:135798.4指针型函数函数类型就是函数返回值的类型。在C语言中允许一个函数的返回值是一个指针(即地址),这种返回指针值的函数称为指针型函数。定义指针型函数的一般形式为:类型说明符*函数名(形参表){函数体return(与函数类型相同的指针);}其中,函数名之前加了“*”号表明这是一个指针型函数,即返回值是一个指针。类型说明符表示了返回的指针值所指向的数据类型。如:int*ap(intx,inty){函数体151/247 《C语言程序设计基础》教学大纲草案2021-10-237:25return(整型类型的指针);}表示ap是一个返回指针值的指针型函数,它返回的指针指向一个整型变量。程序8.9通过指针函数输入一个1~7之间的整数,输出对应的星期名。程序8.9#include#includechar*day_name(int);intmain(void){inti;printf("inputDayNo: ");scanf("%d",&i);if(i<0)exit(0);printf("DayNo:%2d-->%s ",i,day_name(i));return(0);}char*day_name(intn){char*name[]={"Illegalday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"};return((n<1||n>7)?name[0]:name[n]);}程序8.9中定义了一个指针型函数day_name,它的返回值指向一个字符串。该函数中定义了一个指针数组name。name数组初始化赋值为八个字符串,分别表示各个星期名及出错提示。形参n表示与星期名所对应的整数。主函数中把输入的整数i作为实参,在printf语句中调用day_name函数并把i值传送给形参n。day_name函数中的return语句包含一个条件表达式,n值若大于7或小于1则把name[0]指针返回主函数输出出错提示字符串“Illegalday”。否则返回,输出对应的星期名。主函数中的第7行是个条件语句,其语义是,如输入为负数(i<0)则中止程序运行退出程序。152/247 《C语言程序设计基础》教学大纲草案2021-10-237:25exit是一个库函数,exit(1)表示发生错误后退出程序,exit(0)表示正常退出。8.5函数型指针---指向函数的指针C语言中的函数代码在内存中也是连续存放的,函数名就是该函数所占内存区的首地址,也称为它的入口地址。如果用一个指针取得该地址,就是取得了调用该函数的能力,从而可以用指针代表该函数。它使得函数也能作为参数传递给其他函数。这种指向函数的指针变量称为“函数指针变量”。函数指针变量的定义与它所指向的函数定义必须完全相同,只是用“(*指针变量名)”代替函数名:类型说明符函数名(参数表);//函数声明类型说明符(*指针变量名)(参数表);//函数指针变量声明例如:intmax(intint);//声明函数,形参表为(int,int)int(*pf)(intint);//声明指向具有int返回值,且形参表为(int,int)的指针变量表示pf是一个指向函数入口的指针变量,该函数的返回值是整型,参数表有两个整型变量。函数的地址用不带括号和参数名得到,类似于数组地址的获得方法,即,只用不带下标的数组名:pf=max;现在指针pf已经指向了函数max的入口地址。程序8.10给出了实际例子,函数max返回的是两个输入变量中的最大者。程序8.10#includeintmax(int,int);intmain(void){intx,y,z;int(*fp)(int,int);//声明指向具有int返回值,且形参表为(int,int)的指针变量fp=max;//直接取得max地址printf("inputtwonumbers: ");scanf("%d%d",&x,&y);153/247 《C语言程序设计基础》教学大纲草案2021-10-237:25z=(*fp)(x,y);//调用max函数printf("maxmum=%d ",z);return(0);}intmax(inta,intb){if(a>b)returna;elsereturnb;}因为指针变量可以作为参数传递,所以,我们也可以传递一个函数指针变量,换句话说,我们可以把函数作为参数传递,程序8.11是将指向max函数的指针传递给另一个函数add的例子。程序8.11#includeintmax(int,int);intadd(int(*pmax)(int,int),int,int);//参数表中第一项是一个函数指针变量,它指向一个//具有int返回值,且形参表为(int,int)的函数intmain(void){intx,y,z;int(*fp)(int(*pmax)(int,int),int,int);//定义一个函数指针变量,它指向一个具有int//返回值,且形参表为(int(*pmax)(int,int),int,int)的函数int(*pmax)(int,int);//声明指向具有int返回值,且形参表为(int,int)的指针变量pmax=max;//直接取得max地址fp=add;//直接取得add地址printf("inputtwonumbers: ");scanf("%d%d",&x,&y);z=(*fp)(pmax,x,y);//调用addprintf("maxmum=%d ",z);return(0);}154/247 《C语言程序设计基础》教学大纲草案2021-10-237:25intmax(inta,intb){if(a>b)returna;elsereturnb;}intadd(int(*pmax)(int,int),intx,inty){return((*pmax)(x,y)+10);}虽然可以定义指向任何一个函数的指针,但是我们好像没有办法定义一个指向库函数的指针,因为我们无法说明库函数的形式。程序8.12说明了如何通过一个自定义函数中转之后,调用库函数的方法。程序8.12#include#includevoidcheck(char*,char*,int(*cmp)(char*,char*));intscmp(char*,char*);intmain(void){chars1[40],chars2[40];int(*cmp)(char*,char*);cmp=scmp;gets(s1);gets(s2);check(s1,s2,cmp);return(0);}voidcheck(char*a,char*b,int(*cmp)(char*,char*)){printf("testing ");155/247 《C语言程序设计基础》教学大纲草案2021-10-237:25if(!(*cmp)(a,b))printf("equal ");elseprintf("notequal ");}intscmp(char*a,char*b){return(strcmp(a,b));}我们什么时候,有什么必要使用这种函数指针的形式?程序8.13给出了一个例子,程序根据键盘输入的学号是数字(比如:031652)还是字母(比如:03W101),函数check传递的指针不同。程序8.13#include#include#include#includevoidcheck(char*,char*,int(*)(char*,char*));intscmp(char*,char*);intnumcmp(char*,char*);intmain(void){chars1[40],chars2[40];int(*cmp)(char*,char*);int(*ip)(char*,char*);cmp=scmp;ip=numcmp;printf("inputanum ");gets(s1);printf("inputtheotheronenum ");gets(s2);if(isalpha(*(s1+2)))check(s1,s2,cmp);//03W101156/247 《C语言程序设计基础》教学大纲草案2021-10-237:25elsecheck(s1,s2,ip);//031652return(0);}voidcheck(char*a,char*b,int(*cmp)(char*,char*)){switch((*cmp)(a,b)){case0:printf("是自动化系的留学生 ");break;case2:printf("是自动化系学生 ");break;default:printf("不是自动化系学生 ");}}intscmp(char*a,char*b){return(strcmp(a,b));}intnumcmp(char*a,char*b){if(atoi(a)==atoi(b))return(2);elsereturn(1);}8.6数据结构与数据元素8.6.1结构体定义结构:不同数据类型的基本变量的集合。157/247 《C语言程序设计基础》教学大纲草案2021-10-237:25在实际问题中,一组数据往往具有不同的数据类型。例如,学生信息登记表中,学号为字符型;姓名为字符型;班级为字符型;出生年月为字符型;性别为字符型;电话号码为整型。显然,我们无法用一个数组来存放这一组数据。因为数组中各元素的类型和长度都必须一致。为了解决这个问题,C语言可以根据事物的客观属性,自己构造数据类型,即:“结构(structure)”或叫“结构体”。结构是一种数据类型,如同基本数据类型中的字符型和整型数据类型一样,不同的是,结构由基本类型数据组成,组成方式由我们自定义。因此,结构的根本意义在于,它给我们提供了封装一组数据在一个节点内的能力。结构既是一种“构造”而成的数据类型,那么在说明和使用之前必须先定义它,也就是构造它。这如同在说明和调用函数之前要先定义函数一样。定义一个结构的一般形式为:struct结构名{成员列表};成员表列由若干个成员组成,每个成员都是该结构的一个组成部分。对每个成员也必须作类型说明,其形式为:类型说明符成员名;比如表8.1可以看成由一组记录组成,记录是表的数据结构元素,定义结构如下:表8.1学生信息记录表学号姓名班级性别生年月日联系电话numnameclasssexbirthdaytel030094李肖肖土木工程系男1984.12.155241234030101金花土木工程系女1984.11.1255244567031499冯雅喆自动化系女1984.2.155248910031501戎珂自动化系男1984.5.75524112203W101全朱姬自动化系女1984.11.3055241213Structstudent{charnum[20];charname[40];charclass[40];intsex;charbirthday[20];inttel;158/247 《C语言程序设计基础》教学大纲草案2021-10-237:25};数据元素取值记录1节点1列记录2节点2排素记录3节点3元据记录4节点4数数据表--数组--结构表结构表达的记录之间关系是,所以称表结构是线性的,可以用C语言的数组变量定义相应的数据关系:structstudents[4];其中:s0=(030094,李肖肖,土木工程系,男,1984.12.1,55241234)s1=(030101,金花,土木工程系,女,1984.11.12,55244567)s2=(031499,冯雅喆,自动化系,女,1984.2.1,55248910)s3=(031501,戎珂,自动化系,男,1984.5.7,55241122)s4=(03W101,全朱姬,自动化系,女,1984.11.30,55241213)8.6.2结构体变量说明说明结构变量有以下几种方法:1.先定义结构类型,再说明结构变量structstu{intnum;charname[20];charsex;floatscore;};structstuboy1,boy2;说明了两个变量boy1和boy2为stu结构类型。也可以用宏定义使一个符号常量来表示一个结构类型。例如:#defineSTUstructstuSTU{159/247 《C语言程序设计基础》教学大纲草案2021-10-237:25intnum;charname[20];charsex;floatscore;};STUboy1,boy2;2.在定义结构类型的同时说明结构变量structstu{intnum;charname[20];charsex;floatscore;}boy1,boy2;二种方法中说明的boy1,boy2变量都具有图8.1所示的存储结构,它在内存中占用连续的一块存储区域。numname[20]sexscoreboy12000200220222023图8.1结构体的存储形式说明了boy1,boy2变量为stu类型后,即可向这两个变量中的各个成员赋值。在上述stu结构定义中,所有的成员都是基本数据类型或数组类型。成员也可以又是一个结构,即构成了嵌套的结构。例如,图8.2给出了另一个数据结构。birthdayscorenumname[20]sexmonthdayyear图8.2结构体的嵌套定义形式按图可给出以下结构定义:structdate{intmonth;intday;intyear;160/247 《C语言程序设计基础》教学大纲草案2021-10-237:25};struct{intnum;charname[20];charsex;structdatebirthday;floatscore;}boy1,boy2;首先定义一个结构date,由month(月)、day(日)、year(年)三个成员组成。在定义并说明变量boy1和boy2时,其中的成员birthday被说明为data结构类型。成员名可与程序中其它变量同名,互不干扰。8.6.3访问结构元素结构体中的单个元素可以用操作符“.”来访问。在程序中使用结构变量时,往往不把它作为一个整体来使用。在ANSIC中除了允许具有相同类型的结构变量相互赋值以外,一般对结构变量的使用,包括赋值、输入、输出、运算等都是通过结构变量的成员来实现的。表示结构变量成员的一般形式是:结构变量名.成员名例如:boy1.num即第一个人的学号boy2.sex即第二个人的性别如果成员本身又是一个结构则必须逐级找到最低级的成员才能使用。例如:boy1.birthday.month即,第一个人出生的月份成员可以在程序中单独使用,与普通变量完全相同。8.6.4结构变量赋值结构变量的赋值就是给各成员赋值。可用输入语句或赋值语句来完成。程序8.13给结构变量赋值并输出其值#includeintmain(void){161/247 《C语言程序设计基础》教学大纲草案2021-10-237:25structstu{intnum;char*name;charsex;floatscore;}boy1,boy2;boy1.num=102;strcpy(boy1.name,"Zhangping");//头部文件string.hprintf("inputsexandscore ");scanf("%c%f",&boy1.sex,&boy1.score);boy2=boy1;printf("Number=%d Name=%s ",boy2.num,boy2.name);printf("Sex=%c Score=%.2f ",boy2.sex,boy2.score);return(0);}和其他类型变量一样,对结构变量可以在定义时进行初始化赋值。程序8.14#includeintmain(void){structstu{/*定义结构*/intnum;char*name;charsex;floatscore;}boy2,boy1={102,"Zhangping",'M',78.5};boy2=boy1;printf("Number=%d Name=%s ",boy2.num,boy2.name);printf("Sex=%c Score=%f ",boy2.sex,boy2.score);return(0);162/247 《C语言程序设计基础》教学大纲草案2021-10-237:25}8.6.5结构数组既然结构变量是数据变量,它就可以连续的存储在内存,也就是说可以构成结构型数组。结构数组的每一个元素都是具有相同结构类型的数据元素。例如:structstu{intnum;char*name;charsex;floatscore;}boy[5];定义了一个结构数组boy,共有5个元素,boy[0]~boy[4]。每个数组元素都具有structstu的结构形式。对结构数组可以作初始化赋值。例如:程序8.14计算学生的平均成绩和不及格的人数。#includestructstu{intnum;char*name;charsex;floatscore;}boy[5]={{101,"Liping",'M',45},{102,"Zhangping",'M',62.5},{103,"Hefang",'F',92.5},{104,"Chengling",'F',87},{105,"Wangming",'M',58},};intmain(void){inti,c=0;163/247 《C语言程序设计基础》教学大纲草案2021-10-237:25floatave,s=0;for(i=0;i<5;i++){s+=boy[i].score;if(boy[i].score<60)c+=1;}printf("s=%.2f ",s);ave=s/5;printf("average=%.2f count=%d ",ave,c);return(0);}当对全部元素作初始化赋值时,也可不给出数组长度。本例程序中定义了一个外部结构数组boy,共5个元素,并作了初始化赋值。在main函数中用for语句逐个累加各元素的score成员值存于s之中,如score的值小于60(不及格)即计数器C加1,循环完毕后计算平均成绩,并输出全班总分,平均分及不及格人数。8.6.6指向结构变量的指针结构变量在内存是连续存储的,一个指针指向一个结构变量,就是指向结构的首地址,指针称之为结构指针。通过结构指针即可访问该结构变量,这与数组指针和函数指针的情况是相同的。结构指针变量说明的一般形式为:struct结构名*结构指针变量名;例如,前面例题中定义了stu结构,若要说明一个指向stu的指针变量pstu,则如下操作:structstu*pstu;当然,也可在定义stu结构时同时说明pstu。与前面讨论的各类指针变量相同。结构指针变量也必须要先赋值后才能使用。赋值是把结构变量的首地址赋予该指针变量,不能把结构名赋予该指针变量。如果boy是被说明为stu类型的结构变量,则:pstu=&boy是正确的,而:pstu=&stu是错误的。结构名和结构变量是两个不同的概念,不能混淆。结构名只能表示一个结构形式,好像基本数据类型的char和int一样,编译系统并不对它分配内存空间。只有当某变量被164/247 《C语言程序设计基础》教学大纲草案2021-10-237:25说明为这种类型的结构时,才对该变量分配存储空间。因此上面&stu这种写法是错误的,不可能去取一个结构名的首地址。有了结构指针变量,就能更方便地访问结构变量的各个成员。其访问的一般形式为:(*结构指针变量).成员名或为:结构指针变量->成员名例如:(*pstu).num或者:pstu->num应该注意(*pstu)两侧的括号不可少,因为成员符“.”的优先级高于“*”。如去掉括号写作*pstu.num则等效于*(pstu.num),这样的意义就完全不对了。下面通过例子来说明结构指针变量的具体说明和使用方法。程序8.16#include"stdio.h"structstu{intnum;char*name;charsex;floatscore;}boy1={102,"Zhangping",'M',78.5},*pstu;intmain(void){pstu=&boy1;printf("Number=%d Name=%s ",boy1.num,boy1.name);printf("Sex=%c Score=%.2f ",boy1.sex,boy1.score);printf("Number=%d Name=%s ",(*pstu).num,(*pstu).name);printf("Sex=%c Score=%.2f ",(*pstu).sex,(*pstu).score);165/247 《C语言程序设计基础》教学大纲草案2021-10-237:25printf("Number=%d Name=%s ",pstu->num,pstu->name);printf("Sex=%c Score=%.2f ",pstu->sex,pstu->score);return(0);}本例程序定义了一个结构stu,定义了stu类型结构变量boy1并作了初始化赋值,还定义了一个指向stu类型结构的指针变量pstu。在main函数中,pstu被赋予boy1的地址,因此pstu指向boy1。然后在printf语句内用三种形式输出boy1的各个成员值。从运行结果可以看出:结构变量.成员名(*结构指针变量).成员名结构指针变量->成员名这三种用于表示结构成员的形式是完全等效的。8.6.7结构指针变量作函数参数如果需要在函数之间传送一个结构变量,应该和传递数组一样,传递一个指向结构的指针。虽然C语言允许用结构变量作函数参数传送。但是这种传送要将全部成员逐个进行,当成员包含有数组时,将会使传送的时间和空间开销很大,降低程序效率。程序8.17计算一组学生的平均成绩和不及格人数,用结构指针变量作函数参数编程。#include"stdio.h"voidave(structstu*);structstu{intnum;char*name;charsex;floatscore;}boy[5]={{101,"Liping",'M',45},{102,"Zhangping",'M',62.5},{103,"Hefang",'F',92.5},{104,"Chengling",'F',87},166/247 《C语言程序设计基础》教学大纲草案2021-10-237:25{105,"Wangming",'M',58},};intmain(void){structstu*ps;ps=boy;//取得结构数组首地址ave(ps);return(0);}voidave(structstu*ps){intc=0,i;floatave,s=0;for(i=0;i<5;i++,ps++){s+=ps->score;if(ps->score<60)c+=1;}printf("s=%.2f ",s);ave=s/5;printf("average=%.2f count=%d ",ave,c);}本程序中定义了函数ave,其形参为结构指针变量ps。boy被定义为外部结构数组,因此在整个源程序中有效。在main函数中定义说明了结构指针变量ps,并把boy的首地址赋予它,使ps指向boy数组。然后以ps作实参调用函数ave。在函数ave中完成计算平均成绩和统计不及格人数的工作并输出结果。8.7动态存储分配C语言中,数组的长度是预先定义好的,在整个程序中固定不变。C语言中不允许动态数组类型。例如:intn;167/247 《C语言程序设计基础》教学大纲草案2021-10-237:25scanf("%d",&n);inta[n];用变量表示长度,想对数组的大小作动态说明,这是错误的。但是在实际的编程中,往往会发生这种情况,即所需的内存空间取决于实际输入的数据,而无法预先确定。对于这种问题,用数组的办法很难解决。为了解决上述问题,C语言提供了一些内存管理函数,这些内存管理函数可以按需要动态地分配内存空间,也可把不再使用的空间回收待用,为有效地利用内存资源提供了手段。常用的内存管理函数有以下三个:分配内存空间函数malloc调用形式:(类型说明符*)malloc(size)功能:在内存的动态存储区中分配一块长度为"size"字节的连续区域。函数的返回值为该区域的首地址。“类型说明符”表示把该区域用于何种数据类型。(类型说明符*)表示把返回值强制转换为该类型指针。“size”是一个无符号数。例如:pc=(char*)malloc(100);表示分配100个字节的内存空间,并强制转换为字符数组类型,函数的返回值为指向该字符数组的指针,把该指针赋予指针变量pc。分配内存空间函数calloccalloc也用于分配内存空间。调用形式:(类型说明符*)calloc(n,size)功能:在内存动态存储区中分配n块长度为“size”字节的连续区域。函数的返回值为该区域的首地址。(类型说明符*)用于强制类型转换。calloc函数与malloc函数的区别仅在于一次可以分配n块区域。例如:ps=(struetstu*)calloc(2,sizeof(structstu));其中的sizeof(structstu)是求stu的结构长度。因此该语句的意思是:按stu的长度分配2块连续区域,强制转换为stu类型,并把其首地址赋予指针变量ps。释放内存空间函数free调用形式:168/247 《C语言程序设计基础》教学大纲草案2021-10-237:25free(void*ptr);功能:释放ptr所指向的一块内存空间,ptr是一个任意类型的指针变量,它指向被释放区域的首地址。被释放区应是由malloc或calloc函数所分配的区域。程序8.18分配一块区域,输入一个学生数据#include#include#includeintmain(void){structstu{intnum;char*name;charsex;floatscore;}*ps;ps=(structstu*)malloc(sizeof(structstu));//申请内存if(!ps){//不成功则退出printf("memorizerover ");exit(-1);}ps->num=102;ps->name="Zhangping";ps->sex='M';ps->score=62.5;printf("Number=%d Name=%s ",ps->num,ps->name);printf("Sex=%c Score=%.2f ",ps->sex,ps->score);free(ps);//释放内存return(0);}本例中,定义了结构stu,定义了stu类型指针变量ps。然后分配一块stu大内存区,169/247 《C语言程序设计基础》教学大纲草案2021-10-237:25并把首地址赋予ps,使ps指向该区域。再以ps为指向结构的指针变量对各成员赋值,并用printf输出各成员值。最后用free函数释放ps指向的内存空间。整个程序包含了申请内存空间、使用内存空间、释放内存空间三个步骤,实现存储空间的动态分配。8.8指针应用综合训练8.8.1指针在数据节点之间的关联作用我们如何在计算机中描述一组节点之间的逻辑关系?如果是线性关系,可以用数组,因为数组元素的物理地址连续,因而能表达节点之间的线性逻辑关系。如果节点之间是非线性连接,比如一棵树,数组就无法在内存中再现元素之间的逻辑关系。一种自然的选择就是指针。指针在数据结构起到关联节点的作用,让指针从一个节点元素指向另一个节点元素,换句话说,通过指针连接节点元素之间的存储位置,从而让它们之间关联在一起,进而表达了它们之间的逻辑关系。线性连接非线性连接节点之间的逻辑关联让指针从一个节点指向另一(或者是多个)节点,需要在节点定义中加入指针变量,指针在节点内,它指向下一个节点,如果你能找到当前节点位置,就能根据指针找到后续节点所在,这就是节点关联。现在讨论如何用指针关联两个节点元素,我们看例8.19。例8.19用节点内部指针关联两个节点。如下一个学生数据节点的定义:structstudent_node{intnumber;charname[40];chargender;structnode*student_next;//指针域};在这个结构体内,我们不但提供了描述学生个体属性的基本变量聚合,而且还有该节点170/247 《C语言程序设计基础》教学大纲草案2021-10-237:25类型的指针变量next,用next可以指向学生集合中的其它个体或者说是节点,从而表达了集合中节点之间的线性关系,使它们关联在一起,比如:structnode*head,*q;设指针head已指向内存里的一个节点a1,当再申请一个节点比如a2时,通过对a1的next赋值使其指向a2,从而让a1与a2关联起来,看如下程序段:q=(structstudent_node*)malloc(sizeof(student_node));q->number=2006231;q->name=”刘东”q->next=Null;head->next=q;a1a2headDATAnextDATAnext=null用指针实现节点之间的逻辑关联8.8.2链表的概念假设我们建设一个学生数据库存储学生信息,如果用结构数组也可以完成该工作。但如果预先不能准确把握学生人数,也就无法确定数组大小。而且当学生留级、退学之后也不能把该元素占用的空间从数组中释放出来。用动态存储的方法可以很好地解决这些问题。采用动态分配的办法为每一个结构记录分配内存空间。每一次分配一块空间可用来存放一个学生的数据,称之为一个结点。有多少个学生就应该申请分配多少块内存空间,也就是说要建立多少个结点。于是,我们无须预先确定学生的准确人数,某学生退学,可删去该结点,并释放该结点占用的存储空间。从而节约存储资源。另一方面,用数组的方法必须占用一块连续的内存区域。而使用动态分配时,每个结点之间可以是不连续的(结点内是连续的)。结点之间的联系可以用指针实现。即在结点结构中定义一个结构指针,用来存放下一结点的首地址,我们称之为指针域。可在第一个结点的指针域内存入第二个结点的首地址,在第二个结点的指针域内又存放第三个结点的首地址,如此串连下去直到最后一个结点。最后一个结点因无后续结点连接,其指针域可赋为0。这样一种连接方式,在数据结构中称为“链表”。171/247 《C语言程序设计基础》教学大纲草案2021-10-237:25数据域指针域(a)节点结构NULL头指针第一个节点直接后继节点尾指针为空(b)单链表结构尾指针指向首节点头指针直接后继节点(c)循环单链表结构只要初始化链表头指针head,让它指向头节点a1,那么,通过把每次输入纪录ai的地址赋值给其前驱节点ai-1所含的指针next,就可以让ai-1指向ai:ai-1->next=ai;它描述了的逻辑关系。线性表的链式存储结构特点是用一组任意的存储单元来存储线性表的数据元素,而关系是用节点指针域所含的后继节点地址信息来表达的。即节点分为数据域与指针域两部分,如上图所示,所有节点指针的指向形成了一条数据链。链表设计要注意头指针的作用,当为空表时指针亦为空。链表中的每一个结点都是同一种结构类型。节点定义方式如下:例如,一个存放学生学号和成绩的结点应为以下结构:structstu{intnum;//学生学号intscore;//成绩structstu*next;//指针域};前两个成员项组成数据域,后一个成员项next构成指针域,它是一个指向stu类型结构的指针变量。8.8.3链表的设计通过链表设计使我们熟练掌握指针应用。链表设计首先要定义节点结构,沿用前面的例子,一个具体的单链表设计是如下步骤:一、建立空表并定义节点结构structstu{intnum;//学生学号172/247 《C语言程序设计基础》教学大纲草案2021-10-237:25intscore;//成绩structstu*next;//指针域};在主程序中定义一个头部指针并完成初始化:structstu*head=NULL;链表用malloc()函数动态申请内存分配,节点插入时,我们用它每次从内存申请一个节点所需的内存,即前面所说的链表的动态成长。二、插入节点生成链表单链表建立后,输入新的节点可以看成是对单链表的插入运算,下图给出了插入节点的过程示意,设节点递增有序。它表明了指针的修改方法及要点。在p节点前插入S时,插入函数要区分三种不同的情况:①表空,S成为表头;②表中无p节点,S插入链尾;③找到p节点,将S在其之前插入。我们对插入时指针修改的顺序特别注意,要在切断q与p的节点链之前,先把S的指针指向p,以免丢失指针链信息,原则上是:<1>修改S指针指向p节点,取得后继节点指针信息:S-->next=P;//定位S<2>修改p节点前趋q的指针指向S,插入S到链表中:q-->next=S;//修改q指针现在,请读者参考图示读下面的例子,定义的节点结构是前述的str单链表节点。链表按关键字递增有序插入。插入前节点序列是:head-->...q,p,...n插入后是:head-->...q,S,p,...n空闲内存S节点系统栈指针1.取得后继节点地址2.调整前节点指针iNULL头节点q节点p节点图1.14单链表插入:在p节点前插入S程序8.20递增有序的单链表节点插入173/247 《C语言程序设计基础》教学大纲草案2021-10-237:25structstu*dls_store(structstu*s,structstu*head){structstu*p,*q;//定义中间变量if(!head){//表空,返回S为头部节点head=s;s->next=NULL;return(s);}p=head;//从头开始搜索p节点*/q=p;while(p){if(p->numnum){//当前节点关键字值小于S节点关键字值,搜索下一个节点q=p;p=p->next;}else{//找到i值节点pif(p==head){//是头部?s->next=head;head=s;return(s);}q->next=s;//是链表中间插入S节点s->next=p;//因有中间变量定义,所以指针修改顺序可以不考虑return(head);}}//走出循环体,则该表非空且无关键字值大于S节点,S插入链尾q->next=s;//如用p-->next=S则错,因此时p为空s->next=NULL;return(head);174/247 《C语言程序设计基础》教学大纲草案2021-10-237:25}函数被定义为指针型函数,它返回一个指向头节点的指针。此函数调用形式为:head=dls_store(s,head);现在我们可以给出单链表插入的完整程序是:程序8.21单链表生长#include#include#include#includestructstu{intnum;//学生学号intscore;//成绩structstu*next;//指针域};structstu*dls_store(structstu*,structstu*);voidenter(structstu*);voidlist(structstu*);intmain(void){structstu*head=NULL,*s;inti=0;while(i==0){printf("select:iorlorq ");switch(getch()){case'i':s=(structstu*)malloc(sizeof(stu));//向内存申请一个节点if(!s)exit(-1);//如果失败则返回enter(s);head=dls_store(s,head);//节点插入list(head);//遍历链表175/247 《C语言程序设计基础》教学大纲草案2021-10-237:25break;case'l':list(head);//遍历链表break;case'q':i=1;break;}}return(0);}voidenter(structstu*s){printf("输入学号:");scanf("%d",&(s->num));//输入关键字值printf(" 输入成绩:");scanf("%d",&(s->score));//输入关键字值}voidlist(structstu*head){inti=1;if(!head)exit(-1);while(head){printf("序号%d学号:%d成绩:%d ",i,head->num,head->score);i++;head=head->next;}}176/247 《C语言程序设计基础》教学大纲草案2021-10-237:25第九章位域、联合、枚举9.1要点(1)位域的概念,掌握位域结构变量的定义方法;(2)理解共同体的含义,掌握共同体类型变量的定义方法;(3)了解枚举类型的定义,及枚举类型的输入输出;(4)了解TYPEDEF的作用;9.2位域以前我们说C语言处理的数据类型是以字节为单位的,比如整型量是2字节,浮点数是4个字节等。但是,在很多场合我们处理的变量其取值范围很窄,比如布尔变量(_Bool),变量的值不是0就是1。_Bool类型用于表示逻辑变量值,即逻辑值true(真)与flase(假)。因为C语言用1表示true,用0表示flase,所以,_Bool类型可以用一个字节中的1位来表达。多个布尔变量可以定义在同一个字节中。位域是就是按二进制的位来处理信息。因为有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是以一个字节为单位,将其中的8个二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。位域的定义和位域变量的说明位域定义与结构定义相仿,其形式为:struct位域结构名{类型说明符位域名1:位域长度;类型说明符位域名2:位域长度;…};位域变量的类型说明符必须是int,unsigned或signed中的一种,长度为1的位域变量被认为是unsigned,因为它不可能有符号。例如:structbs{177/247 《C语言程序设计基础》教学大纲草案2021-10-237:25inta:8;//占用一个字节intb:2;//占用一个字节的2位intc:6;//占用一个字节的6位};215282720bs.cbs.bbs.a图9.1位域结构图9.1说明了位域类型的存储结构。位域变量的说明与结构变量说明的方式相同。可采用先定义后说明,同时定义说明或者直接说明这三种方式。例如:structbs{inta:8;intb:2;intc:6;}data;说明data为bs变量,共占两个字节。其中位域a占8位,位域b占2位,位域c占6位。对于位域的定义尚有以下几点说明:一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:structbs{unsigneda:4;unsigned:0;//空域unsignedb:4;//从下一单元开始存放unsignedc:4;};在这个位域定义中,a占第一字节的4位,后4位填0表示不使用,b从第二字节开始,占用4位,c占用4位。由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位。访问位域访问位域和访问结构成员的相同,使用运算符“.”访问位域变量:178/247 《C语言程序设计基础》教学大纲草案2021-10-237:25位域变量名.位域名程序举例程序9.1位域操作#includeintmain(void){structbs{unsigneda:1;unsignedb:3;unsignedc:4;}bit,*pbit;bit.a=1;bit.b=7;bit.c=15;printf("%d,%d,%d ",bit.a,bit.b,bit.c);pbit=&bit;pbit->a=0;pbit->b&=3;pbit->c|=1;printf("%d,%d,%d ",pbit->a,pbit->b,pbit->c);return(0);}程序9.1中定义了位域结构bs,三个位域为a,b,c。说明了bs类型的变量bit和指向bs类型的指针变量pbit。这表示位域也是可以使用指针的。程序首先分别给三个位域赋值(应注意赋值不能超过该位域的允许范围)。接着以整型量格式输出三个域的内容。然后把位域变量bit的地址送给指针变量pbit,用指针方式给位域a重新赋值为0。程序又用复合位运算符"&="让位域变量b与数值3进行了逻辑与操作,该行相当于:pbit->b=pbit->b&3位域b中原有值为7,与3作按位与运算的结果为3(111&011=011,十进制值为3)。最后,程序使用复合位运算符"|="对位域变量c进行了逻辑或操作,相当于:179/247 《C语言程序设计基础》教学大纲草案2021-10-237:25pbit->c=pbit->c|1其结果为15(1111|0001=1111)。程序用指针方式输出了这三个域的值。9.3共用体(union)程序运行时,内存中的数据和程序代码工作区域是有限的,如果有必要提高内存的利用效率,有时候我们考虑不同的变量,分时复用同一块内存单元区域。比如,自3年级的程序设计课程是每周一的9:50~12:15使用1教101教室,从教师或自3年级同学来说,程序设计课程时有专用教室的。实际上,在其它时段1教101教室可以提供其它班级上课,或者给自3年级上数学或英语之用。这就是不同班级,或不同课程复用一个教室的情况,我们称之为共用体。其它教材多翻译成“联合”一词,我个人认为,共用体更能描述union的本质。共用体说明和共用体变量定义union也是一种新的数据类型,它是一种特殊形式的变量。union说明和union变量定义与结构十分相似。其形式为:union共用体名{数据类型成员名;数据类型成员名;...}共用体变量名;共用体表示几个变量公用一个内存位置,在不同的时间保存不同的数据类型和不同长度的变量。下例表示说明一个共用体a_bc:uniona_bc{inti;charmm;};再用已说明的共用体可定义共用体变量。例如用上面说明的共用体定义一个名为lgc的共用体变量,可写成:uniona_bclgc;在共用体变量lgc中,整型量i和字符mm公用同一内存位置。当一个共用体被说明时,编译程序自动地产生一个变量,其长度为共用体中最大的变量长度。180/247 《C语言程序设计基础》教学大纲草案2021-10-237:25共用体访问其成员的方法与结构相同。同样共用体变量也可以定义成数组或指针,但定义为指针时,也要用"->"符号,此时共用体访问成员可表示成:共用体名->成员名另外,共用体既可以出现在结构内,它的成员也可以是结构。例如:union{intage;charsex;struct{inti;char*ch;}x;}y;内存长度y.sexy.agey.xy.x.*ch(假设指针4字节)yy.x.i图9.2共用体结构图9.2给出了它的存储结构。若要访问共用体变量y中结构x的成员i,可以写成:y.x.i;若要访问y中x的字符串指针ch可写成:*y.x.ch;程序9.2#includeintmain(void){chara;union{intage;181/247 《C语言程序设计基础》教学大纲草案2021-10-237:25charsex;struct{inti;char*ch;}x;}y;y.age=10;y.x.i=20;//覆盖了y.agey.sex='b';//覆盖了y.x.iy.x.ch=&a;//y.x.ch在地址上与共用体内其它变量无关*(y.x.ch)='a';printf("y.x.i=%d ",y.x.i);printf("y.age=%d ",y.age);printf("y.sex=%c ",y.sex);printf("*(y.x.ch)=%c ",*(y.x.ch));return(0);}运行结果:y.x.i=98y.age=98y.sex=b*(y.x.ch)=aPressanykeytocontinue结构和共用体的区别1.结构和共用体都是由多个不同的数据类型成员组成,但在任何时刻,共用体中只存放了一个被选中的成员,而结构的所有成员都存在。2.对于共用体的不同成员赋值,将会对其它成员重写,原来成员的值就不存在了,而对于结构,不同成员赋值是互不影响的。因此,共用体中的指针操作需要特别小心,它很容易被误操作。下面举一个例了来加对深共用体的理解。182/247 《C语言程序设计基础》教学大纲草案2021-10-237:25程序9.3共用体的应用#includeintmain(void){union{/*定义一个共用体*/inti;struct{/*在共用体中定义一个结构*/charfirst;charsecond;}half;}number;number.i=0x4241;/*共用体成员赋值*/printf("%c%c ",number.half.first,number.half.second);number.half.first='a';/*共用体中结构成员赋值*/number.half.second='b';printf("%x ",number.i);return(0);}输出结果为:AB6261Pressanykeytocontinue从结果可以看出:当给i赋值后,其低八位也就是first和second的值;当给first和second赋字符后,这两个字符的ASCII码也将作为i的低八位和高八位。9.4枚举在实际问题中,有些变量的取值被限定在一个有限的范围内。例如,一个星期内只有七天,一年只有十二个月,一个班每周有五门课程等等。如果把这些量说明为整型,字符型或其它类型显然是不妥当的。为此,C语言提供了一种称为“枚举”的类型。在“枚举”类型的定义中列举出所有可能的取值(穷举),被说明为该“枚举”类型的变量取值不能超过定义的范183/247 《C语言程序设计基础》教学大纲草案2021-10-237:25围。应该说明的是,枚举类型是一种基本数据类型,而不是一种构造类型,因为它不能再分解为任何基本类型。枚举的定义枚举类型定义的一般形式enum枚举名{枚举值表};在枚举值表中应罗列出所有可用值。这些值也称为枚举元素。例如:enumweekday{sun,mou,tue,wed,thu,fri,sat};该枚举名为weekday,枚举值共有7个,即一周中的七天。凡被说明为weekday类型变量的取值只能是七天中的某一天。注意,枚举中每个成员(标识符)结束符是“,”,不是“;”,最后一个成员可省略“,”。枚举变量的说明如同结构和联合一样,枚举变量也可用不同的方式说明,即先定义后说明,同时定义说明或直接说明。设有变量a,b,c被说明为上述的weekday,可采用下述任一种方式:enumweekday{......};enumweekdaya,b,c;或者为:enumweekday{......}a,b,c;枚举类型变量的赋值和使用枚举类型在使用中有以下规定:(1)枚举值是常量,不是变量。不能在程序中用赋值语句再对它赋值。例如,对枚举weekday的元素再作以下赋值:sun=5;mon=2;sun=mon;都是错误的。184/247 《C语言程序设计基础》教学大纲草案2021-10-237:25(2)枚举元素本身由系统定义了一个表示序号的数值,从0开始顺序定义为0,1,2…。如在weekday中,sun值为0,mon值为1,…,sat值为6。例如:#includeintmain(void){enumweekday{sun,mon,tue,wed,thu,fri,sat}a,b,c;a=sun;b=mon;c=tue;printf("%d,%d,%d ",a,b,c);return(0);}运行结果是:0,1,2Pressanykeytocontinue如果枚举没有初始化,即省掉“=整型常数”时,则从第一个标识符开始,顺次赋给标识符0,1,2,...。但当枚举中的某个成员赋值后,其后的成员按依次加1的规则确定其值。例如,下列枚举说明后,x1,x2,x3,x4的值分别为0,1,2,3。enumstring{x1,x2,x3,x4}x;当定义改变成:enumstring{x1,x2=0,x3=50,x4}x;则x1=0,x2=0,x3=50,x4=51。程序如下:#includeintmain(void)185/247 《C语言程序设计基础》教学大纲草案2021-10-237:25{enumstring{x1,x2=0,x3=50,x4}x;printf("%d,%d,%d,%d ",x1,x2,x3,x4);return(0);}运行结果是:0,0,50,51Pressanykeytocontinue(3)初始化时枚举变量可以赋负数,以后的标识符仍依次加1。如:#includeintmain(void){enumstring{x1=-2,x2,x3,x4}x;printf("%d,%d,%d,%d ",x1,x2,x3,x4);return(0);}运行结果是:-2,-1,0,1Pressanykeytocontinue(4)只能把枚举值赋予枚举变量,不能把元素的数值直接赋予枚举变量。如:a=sum;b=mon;是正确的。而:a=0;b=1;是错误的。如一定要把数值赋予枚举变量,则必须用强制类型转换,如a=(enumweekday)2;其意义是将顺序号为2的枚举元素赋予枚举变量a,相当于:186/247 《C语言程序设计基础》教学大纲草案2021-10-237:25a=tue;还应该说明的是,枚举元素不是字符常量也不是字符串常量,使用时不要加单、双引号。9.5类型定义符typedefC语言不仅提供了丰富的数据类型,而且还允许由用户自己定义类型说明符,也就是说允许由用户为数据类型取“别名”。类型定义符typedef即可用来完成此功能。例如,有整型量a,b,其说明如下:inta,b;其中,int是整型变量的类型说明符。int的完整写法为integer,为了增加程序的可读性,可把整型说明符用typedef定义为:typedefintINTEGER于是,程序就可用INTEGER来代替int作整型变量的类型说明了。例如:INTEGERa,b;它等效于:inta,b;用typedef定义数组、指针、结构等类型将带来很大的方便,不仅使程序书写简单而且使意义更为明确,因而增强了可读性。例如:typedefcharNAME[20];表示NAME是字符数组类型,数组长度为20。然后可用NAME说明变量,如:NAMEa1,a2,s1,s2;完全等效于:chara1[20],a2[20],s1[20],s2[20];又如:typedefstructstu{charname[20];intage;charsex;}STU;定义STU表示stu的结构类型,然后可用STU来说明结构变量:STUbody1,body2;187/247 《C语言程序设计基础》教学大纲草案2021-10-237:25typedef定义的一般形式为:typedef原类型名新类型名其中,原类型名中含有定义部分,新类型名一般用大写表示,以便于区别。有时也可用宏定义来代替typedef的功能,但是宏定义是由预处理完成的,而typedef则是在编译时完成的,后者更为灵活方便。在练习十的第一题中,我们遇到如何定义函数型指针数组的问题,应用typedef很容易处理:typedefvoid(*array)();//说明array是函数型指针的类型arrayfp[4]={enter,del,review,quit};//定义array数组9.6小结位域在本质上也是结构类型,不过它的成员按二进制位分配内存。其定义、说明及使用的方式都与结构相同。位域提供了一种手段,使得可在高级语言中实现数据的压缩,节省了存储空间,同时也提高了程序的效率。共用体表示几个变量公用一个内存位置,在不同的时间保存不同的数据类型和不同长度的变量。除去节省空间外,它还为我们提供了同一个数据结构内,处理不同类型数据的手段。枚举是一种基本数据类型。枚举变量的取值是有限的,枚举元素是常量,不是变量。枚举变量通常由赋值语句赋值,而不由动态输入赋值。枚举元素虽可由系统或用户定义一个顺序值,但枚举元素和整数并不相同,它们属于不同的类型。因此,也不能用printf语句来输出元素值(可输出顺序值)。类型定义typedef向用户提供了一种自定义类型说明符的手段,照顾了用户编程使用词汇的习惯,又增加了程序的可读性。188/247 《C语言程序设计基础》教学大纲草案2021-10-237:25第十章位运算前面介绍的各种运算都是以字节作为最基本位进行的。但在工业系统程序中,常要求在位(bit)一级进行运算或处理。C语言提供了位运算的功能,因此,C语言具有汇编语言级别意义上的控制系统硬件工作的能力。10.1位运算符C语言提供了六种位运算符&按位与|按位或^按位异或~取反<<左移>>右移10.2按位与运算按位与运算符"&"是双目运算符。其功能是参与运算的两数各对应的二进位相与。只有对应的两个二进位均为1时,结果位才为1,否则为0。参与运算的数以补码方式出现。例如,9&5算式如下:00001001(9的二进制补码)&00000101(5的二进制补码)00000001(1的二进制补码)即9&5=1。按位与运算通常用来对某些位清0或保留某些位。例如,把a的高8位清0,保留低8位数据,可作a&255运算(255的二进制数为0000000011111111)。程序10.1#includeintmain(void){inta=0x9,b=0x5,c;c=a&b;189/247 《C语言程序设计基础》教学大纲草案2021-10-237:25printf("a=%#x,b=%#x,c=%#x ",a,b,c);return(0);}运算结果是:a=0x9,b=0x5,c=0x1Pressanykeytocontinue10.3按位或运算按位或运算符“|”是双目运算符。其功能是参与运算的两数各对应的二进位相或。只要对应的二个二进位有一个为1时,结果位就为1。参与运算的两个数均以补码出现。例如,9|5可写算式如下:00001001|0000010100001101(十进制为13)即9|5=13。程序10.2#includeintmain(void){inta=0x9,b=0x5,c;c=a|b;printf("a=%#x,b=%#x,c=%#x ",a,b,c);return(0);}运算结果是:a=0x9,b=0x5,c=0xdPressanykeytocontinue10.4按位异或运算按位异或运算符“^”是双目运算符。其功能是参与运算的两数各对应的二进位相异或,190/247 《C语言程序设计基础》教学大纲草案2021-10-237:25当两对应的二进位相异时,结果为1,相同则为0。参与运算数仍以补码出现,例如,9^5可写成算式如下:00001001^0000010100001100(十进制为12)程序10.3#includeintmain(void){inta=0x9,b=0x5,c;c=a^b;printf("a=%#x,b=%#x,c=%#x ",a,b,c);return(0);}运算结果是:a=0x9,b=0x5,c=0xcPressanykeytocontinue10.5求反运算求反运算符~为单目运算符,具有右结合性。其功能是对参与运算的数的各二进位按位求反。例如,~9的运算为:~(0000000000001001)结果为:111111111111011010.6左移运算左移运算符“<<”是双目运算符。其功能把“<<”左边的运算数的各二进位全部左移若干位,由“<<”右边的数指定移动的位数,高位丢弃,低位补0。例如:a<<4是把a的各二进位向左移动4位。如a=00000011(十进制3),左移4位后为00110000(十进4制48)。相当于a×2,是乘2整数幂的快捷方法。191/247 《C语言程序设计基础》教学大纲草案2021-10-237:2510.7右移运算右移运算符“>>”是双目运算符。其功能是把“>>”左边的运算数的各二进位全部右移若干位,“>>”右边的数指定移动的位数。例如,设a=16,则:a>>2-2表示把000010000右移为00000100(十进制4)。所以,相当于a×2,是除2整数幂的快捷方法。应该说明的是,对于有符号数,在右移时,符号位将随同移动。当为正数时,最高位补0,而为负数时,符号位为1,最高位是补0或是补1取决于编译系统的规定。很多系统规定为补1。程序10.4#includeintmain(void){unsigneda,b;printf("inputanumber:");scanf("%d",&a);b=a>>5;b=b&15;printf("a=%dtb=%d ",a,b);return(0);}程序10.5#includeintmain(void){chara='a',b='b';intp,c,d;p=a;p=(p<<8)|b;192/247 《C语言程序设计基础》教学大纲草案2021-10-237:25d=p&0xff;c=(p&0xff00)>>8;printf("a=%d b=%d c=%d d=%d ",a,b,c,d);return(0);}10.8本章小结位运算是C语言的一种特殊运算功能,它是以二进制位为单位进行运算的。位运算符只有逻辑运算和移位运算两类。位运算符可以与赋值符一起组成复合赋值符。如&=,|=,^=,>>=,<<=等。利用位运算可以完成汇编语言的某些功能,如置位,位清零,移位等。还可进行数据的压缩存储和并行运算。193/247 《C语言程序设计基础》教学大纲草案2021-10-237:25第十一章文件输入/输出12.1文件的概念所谓“文件”是指一组相关数据的有序集合。这个数据集有一个名称,叫做文件名。实际上在前面的各章中我们已经多次使用了文件,例如源程序文件、目标文件、可执行文件、库文件(头文件)等。文件通常是驻留在外部介质(如磁盘等)上的,在使用时才调入内存中来。从不同的角度可对文件作不同的分类。从用户的角度看,文件可分为普通文件和设备文件两种。普通文件是指驻留在磁盘或其它外部介质上的一个有序数据集,可以是源文件、目标文件、可执行程序;也可以是一组待输入处理的原始数据,或者是一组输出的结果。对于源文件、目标文件、可执行程序可以称作程序文件,对输入输出数据可称作数据文件。设备文件是指与主机相联的各种外部设备,如显示器、打印机、键盘等。在操作系统中,把外部设备也看作是一个文件来进行管理,把它们的输入、输出等同于对磁盘文件的读和写,如图12.1所示。键盘FILE*fp显示器流磁盘内存文件操作打印机图12.1文件与流磁带机通常把显示器定义为标准输出文件,一般情况下在屏幕上显示有关信息就是向标准输出文件输出。如前面经常使用的printf,putchar函数就是这类输出。键盘通常被指定标准的输入文件,从键盘上输入就意味着从标准输入文件上输入数据。scanf,getchar函数就属于这类输入。从文件编码的方式来看,文件可分为ASCII码文件和二进制码文件两种。我们在第三章介绍过流的概念。C语言中有两种类型的流:(1)文本流(textstream)。一个文本流是以一行字符组成,换行符表示一行结束。(2)二进制流(binarystream)。一个二进制流对应写入到文件的内容,由字节序列组成,没有字符翻译。ASCII文件也称为文本文件,这种文件在磁盘中存放时每个字符对应一个字节,用于存194/247 《C语言程序设计基础》教学大纲草案2021-10-237:25放对应的ASCII码。例如,数5678的存储形式为:ASCII码:00110101001101100011011100111000十进制码:5678共占用4个字节。ASCII码文件可在屏幕上按字符显示,例如源程序文件就是ASCII文件,用DOS命令TYPE可显示文件的内容。由于是按字符显示,因此能读懂文件内容。二进制文件是按二进制的编码方式来存放文件的。例如,数5678的存储形式为:0001011000101110只占二个字节。二进制文件虽然也可在屏幕上显示,但其内容无法读懂。C系统在处理这些文件时,并不区分类型,都看成是字符流,按字节进行处理。输入输出字符流的开始和结束只由程序控制而不受物理符号(如回车符)的控制。因此也把这种文件称作“流式文件”。图12.2是文件操作步骤。字符读写函数fgetc和fputc字符串读写函数fgets和fputs定义指针:关闭文件fp=fopen("文件名","打开方式")FILE*fpfclose(fp)数据块读写函数fread和fwtrite格式化读写函数fscanf和fprintf图12.2文件操作12.2文件指针在C语言中用一个指针变量指向一个文件,这个指针称为文件指针。通过文件指针就可对它所指的文件进行各种操作。定义说明文件指针的一般形式为:FILE*指针变量标识符;其中FILE应为大写,它实际上是由系统定义的一个结构,该结构中含有文件名、文件状态和文件当前位置等信息。在编写源程序时不必关心FILE结构的细节。例如:FILE*fp;表示fp是指向FILE结构的指针变量,通过fp即可找存放某个文件信息的结构变量,然后按结构变量提供的信息找到该文件,实施对文件的操作。习惯上也笼统地把fp称为指向一个文件的指针。195/247 《C语言程序设计基础》教学大纲草案2021-10-237:2512.3文件的打开与关闭文件在进行读写操作之前要先打开,使用完毕要关闭。所谓打开文件,实际上是建立文件的各种有关信息,并使文件指针指向该文件,以便进行其它操作。关闭文件则断开指针与文件之间的联系,也就禁止再对该文件进行操作。在C语言中,文件操作都是由库函数来完成的。在本章内将介绍主要的文件操作函数。12.3.1文件打开函数fopen()fopen函数用来打开一个文件,其调用的一般形式为:文件指针名=fopen(“文件名.扩展名”,”打开文件方式”);其中:文件指针名必须是被说明为FILE类型的指针变量;文件名是被打开文件的文件名,扩展名是文件类型说明,可以省略;打开文件方式是指文件流的类型和读写操作以及新建还是追加在文件尾部要求。例如:FILE*fp;fp=("filea","r");其意义是在当前目录下打开文件filea,只允许进行“读”操作,并使fp指向该文件。又如:FILE*fphzkfphzk=("c:\hzk16","rb")其意义是打开C驱动器磁盘的根目录下的文件hzk16,这是一个二进制文件,只允许按二进制方式进行读操作。两个反斜线“\”中的第一个表示转义字符,第二个表示根目录。使用文件的方式共有12种,表12.1给出了它们的符号和意义。表12.1文件打开方式意义“rt”只读打开一个文本文件,只允许读数据“wt”只写打开或建立一个文本文件,只允许写数据“at”追加打开一个文本文件,并在文件末尾写数据“rb”只读打开一个二进制文件,只允许读数据“wb”只写打开或建立一个二进制文件,只允许写数据“ab”追加打开一个二进制文件,并在文件末尾写数据“rt+”读写打开一个文本文件,允许读和写“wt+”读写打开或建立一个文本文件,允许读写“at+”读写打开一个文本文件,允许读,或在文件末追加数据196/247 《C语言程序设计基础》教学大纲草案2021-10-237:25“rb+”读写打开一个二进制文件,允许读和写“wb+”读写打开或建立一个二进制文件,允许读和写“ab+”读写打开一个二进制文件,允许读,或在文件末追加数据文件打开方式有以下几点说明:文件使用方式由r,w,a,t,b,+六个字符拼成,各字符的含义是:r(read):读w(write):写a(append):追加t(text):文本文件,可省略不写b(banary):二进制文件+:读和写凡用“r”打开一个文件时,该文件必须已经存在,且只能从该文件读出。用“w”打开的文件只能向该文件写入。若打开的文件不存在,则以指定的文件名建立该文件,若打开的文件已经存在,则将该文件删去,重建一个新文件。若要向一个已存在的文件追加新的信息,只能用“a”方式打开文件。但此时该文件必须是存在的,否则将会出错。在打开一个文件时,如果出错,fopen将返回一个空指针值NULL。在程序中可以用这一信息来判别是否完成打开文件的工作,并作相应的处理。因此常用以下程序段打开文件:if((fp=fopen("c:\hzk16","rb")==NULL){printf(" erroronopenc:\hzk16file!");getch();exit(-1);}这段程序的意义是,如果返回的指针为空,表示不能打开C盘根目录下的hzk16文件,则给出提示信息“erroronopenc:hzk16file!”,下一行getch()的功能是从键盘输入一个字符,但不在屏幕上显示。在这里,该行的作用是等待,只有当用户从键盘敲任一键时,程序才继续执行,因此用户可利用这个等待时间阅读出错提示。敲键后执行exit(-1)退出程序。把一个文本文件读入内存时,要将ASCII码转换成二进制码,而把文件以文本方式写入磁盘时,也要把二进制码转换成ASCII码,因此文本文件的读写要花费较多的转换时间。对二进制文件的读写不存在这种转换。197/247 《C语言程序设计基础》教学大纲草案2021-10-237:25标准输入文件(键盘),标准输出文件(显示器),标准出错输出(出错信息)是由系统打开的,可直接使用。12.3.2文件关闭函数fclose()文件一旦使用完毕,应用关闭文件函数把文件关闭,以避免文件的数据丢失等错误。fclose函数调用的一般形式是:fclose(文件指针);例如:fclose(fp);正常完成关闭文件操作时,fclose函数返回值为0。如返回非零值则表示有错误发生。程序12.1给出了一个文件写入的例子.程序12.1//案例代码文件名:aa.cpp//程序功能:从键盘上输入一个字符串,存储到一个磁盘文件lwz.dat中//使用格式:可执行文件名=要创建的磁盘文件名#include"stdio.h"#include#includeintmain(void){FILE*fp;charch;if((fp=fopen("lwz.dat","w"))==NULL){//打开文件失败printf("cannotopenthisfile ");getch();exit(-1);}//以下程序是输入字符,并存储到指定文件中,以输入符号“@”作为文件结束printf("输入字符 ");for(;(ch=getchar())!='@';)fputc(ch,fp);//输入字符并存储到文件中198/247 《C语言程序设计基础》教学大纲草案2021-10-237:25fclose(fp);//关闭文件return(0);}该程序在当前目录下建立一个文件名为lwz.dat的文件,并向文件写入键盘输入的字符,直到输入为“@”为止。注意,使用文本文件向计算机系统输入数据时,系统自动将回车换行符转换成一个换行符;在输出时,将换行符转换成回车和换行两个字符。因此,输入换行(Enter键),程序会把换行符写入到文件中,而不是结束字符串的输入过程,程序仅在输入字符“@”的时候结束字符串输入。12.4文件的读写对文件的读和写是最常用的文件操作。在C语言中提供了多种文件读写的函数,使用这些库函数都要求包含头文件stdio.h。字符读写函数:fgetc和fputc字符串读写函数:fgets和fputs数据块读写函数:freed和fwrite格式化读写函数:fscanf和fprinf12.4.1字符读写函数fgetc和fputc字符读写函数是以字符(字节)为单位的读写函数。每次可从文件读出或向文件写入一个字符。1.读字符函数fgetcfgetc函数的功能是从指定的文件中读一个字符,函数调用的形式为:字符变量=fgetc(文件指针);例如:ch=fgetc(fp);其意义是从打开的文件fp中读取一个字符并送入ch中。对于fgetc函数的使用有以下几点说明:在fgetc函数调用中,读取的文件必须是以读或读写方式打开的。读取字符的结果也可以不向字符变量赋值,例如:fgetc(fp);199/247 《C语言程序设计基础》教学大纲草案2021-10-237:25但是读出的字符不能保存。在文件内部有一个位置指针。用来指向文件的当前读写字节。在文件打开时,该指针总是指向文件的第一个字节。使用fgetc函数后,该位置指针将向后移动一个字节。因此可连续多次使用fgetc函数,读取多个字符。应注意文件指针和文件内部的位置指针不是一回事。文件指针是指向整个文件的,须在程序中定义说明,只要不重新赋值,文件指针的值是不变的。文件内部的位置指针可以理解为一个偏移量,用以指示文件内部的当前读写位置,每读写一次,该指针均向后移动,它不需在程序中定义说明,而是由系统自动设置的。文件文件指针fp当前位置i程序12.2读入文件lwz.dat在屏幕上输出#include#includeintread(void);intwrite(void);intmain(void){write();read();return(0);}intwrite(void){FILE*fp;charch;if((fp=fopen("lwz.dat","w"))==NULL){printf("cannotopenthisfile ");getch();200/247 《C语言程序设计基础》教学大纲草案2021-10-237:25exit(-1);}printf("输入字符 ");for(;(ch=getchar())!='@';)fputc(ch,fp);fclose(fp);return(0);}intread(void){FILE*fp;charch;if((fp=fopen("lwz.dat","rt"))==NULL){//打开当前目录下的文件printf(" Cannotopenfilestrikeanykeyexit!");getch();exit(-1);}printf(" openfile: ");ch=fgetc(fp);while(ch!=EOF){putchar(ch);//显示读出的字符ch=fgetc(fp);//继续读出}fclose(fp);printf(" fileend ");return(0);}本例程序的功能是写入一个文件到当目录,然后再打开。其中,文件写入函数write()就是程序12.1。函数read()定义了文件指针fp,以读文本文件方式打开当前目录下的文件“lwz.dat”,并使fp指向该文件,从文件中逐个读取字符,在屏幕上显示。如打开文件出错,给出提示并退出程序。201/247 《C语言程序设计基础》教学大纲草案2021-10-237:25函数先读出一个字符,然后进入循环,只要读出的字符不是文件结束标志(每个文件末有一结束标志EOF),就把该字符显示在屏幕上,再读入下一字符。每读一次,文件内部的位置指针向后移动一个字符,文件结束时,该指针指向EOF。执行本程序将显示整个文件。2.写字符函数fputcfputc函数的功能是把一个字符写入指定的文件中,函数调用的形式为:fputc(字符量,文件指针);其中,待写入的字符量可以是字符常量或变量,例如:fputc('a',fp);其意义是把字符a写入fp所指向的文件中。对于fputc函数的使用也要说明几点:被写入的文件可以用写、读写、追加方式打开,用写或读写方式打开一个已存在的文件时将清除原有的文件内容,写入字符从文件首开始。如需保留原有文件内容,希望写入的字符以文件末开始存放,必须以追加方式打开文件。被写入的文件若不存在,则创建该文件。每写入一个字符,文件内部位置指针向后移动一个字节。fputc函数有一个返回值,如写入成功则返回写入的字符,否则返回一个EOF。可用此来判断写入是否成功。程序12.3从键盘输入一行字符,写入一个文件,再把该文件内容读出显示在屏幕上。#include#include#includeintmain(void){FILE*fp;charch;if((fp=fopen("lwz.dat","wt+"))==NULL){printf("Cannotopenfilestrikeanykeyexit!");getch();exit(-1);}printf("inputastring: ");202/247 《C语言程序设计基础》教学大纲草案2021-10-237:25ch=getchar();while(ch!=' '){fputc(ch,fp);ch=getchar();}rewind(fp);//复位位置偏移量到初始状态ch=fgetc(fp);while(ch!=EOF){putchar(ch);ch=fgetc(fp);}printf(" ");fclose(fp);return(0);}程序中以读写文本文件方式打开文件。从键盘读入一个字符后进入循环,当读入字符不为回车符时,则把该字符写入文件之中,然后继续从键盘读入下一字符。每输入一个字符,文件内部位置指针向后移动一个字节。写入完毕,该指针已指向文件末。如要把文件从头读出,须把指针移向文件头,程序用rewind函数用于把fp所指文件的内部位置指针移到文件头。最后读出文件中的一行内容。12.4.2字符串读写函数fgets和fputs1.读字符串函数fgets函数的功能是从指定的文件中读一个字符串到字符数组中,函数调用的形式为:fgets(字符数组名,n,文件指针);其中的n是一个正整数。表示从文件中读出的字符串不超过n-1个字符。在读入的最后一个字符后加上串结束标志''。例如:fgets(str,n,fp);的意义是从fp所指的文件中读出n-1个字符送入字符数组str中。程序12.4从lwz.dat文件中读入一个含10个字符的字符串203/247 《C语言程序设计基础》教学大纲草案2021-10-237:25#include#include#includeintmain(void){FILE*fp;charstr[40];if((fp=fopen("lwz.dat","rt"))==NULL){printf(" Cannotopenfilestrikeanykeyexit!");getch();exit(-1);}fgets(str,11,fp);printf(" %s ",str);fclose(fp);return(0);}本例定义了一个字符数组str,以读文本文件方式打开文件lwz.dat后,从中读出10个字符送入str数组,在数组最后一个单元内将加上'',然后在屏幕上显示输出str数组。对fgets函数有两点说明:在读出n-1个字符之前,如遇到了换行符或EOF,则读出结束。fgets函数也有返回值,其返回值是字符数组的首地址。2.写字符串函数fputsfputs函数的功能是向指定的文件写入一个字符串,其调用形式为:fputs(字符串,文件指针);其中字符串可以是字符串常量,也可以是字符数组名,或指针变量,例如:fputs(”abcd”,fp);其意义是把字符串”abcd”写入fp所指的文件之中。程序12.5以字符串格式读写lwz.dat文件#include204/247 《C语言程序设计基础》教学大纲草案2021-10-237:25#include#includeintwr_string(char*,FILE*);intrd_string(char*,FILE*);intmain(void){charstr[40];FILE*fp;if((fp=fopen("lwz.dat","wt+"))==NULL){printf("Cannotopenfilestrikeanykeyexit!");getch();exit(-1);}wr_string(str,fp);rewind(fp);//复位位置偏移量到初始状态rd_string(str,fp);fclose(fp);return(0);}intrd_string(char*str,FILE*fp){fgets(str,11,fp);printf(" %s ",str);return(0);}intwr_string(char*str,FILE*fp){printf("inputastring: ");scanf("%s",str);fputs(str,fp);205/247 《C语言程序设计基础》教学大纲草案2021-10-237:25return(0);}本例打开一个lwz.dat文件,输入一个字符串,并用fputs函数把该串写入文件lwz.dat。程序最后调用函数rd_string()读出文件,显示在屏幕上。这里,说明了一个打开的文件指针fp作为实参传递的方法。12.4.3数据块读写函数fread和fwtriteC语言还提供了用于整块数据的读写函数。可用来读写一组数据,如一个数组元素,一个结构变量的值等。读数据块函数调用的一般形式为:fread(buffer,size,count,fp);写数据块函数调用的一般形式为:fwrite(buffer,size,count,fp);buffer:一个指针。在fread函数中,它表示存放输入数据的首地址。在fwrite函数中,它表示存放输出数据的首地址;size:表示数据块的字节数;count:表示要读写的数据块块数;fp:表示文件指针;例如:fread(fa,4,5,fp);其意义是从fp所指的文件中,每次读4个字节(一个实数)送入实数组fa中,连续读5次,即读5个实数到fa中。程序12.6从键盘输入两个学生数据,写入一个文件中,再读出文件,显示在屏幕上。#include#include#include#includestructstu{charname[10];intnum;intage;206/247 《C语言程序设计基础》教学大纲草案2021-10-237:25charaddr[15];}boya[2],boyb[2],*p,*q;intmain(void){FILE*fp;charch;inti;p=boya;q=boyb;if((fp=fopen("stu_list","wb+"))==NULL){//读写打开或建立一个二进制文件printf("Cannotopenfilestrikeanykeyexit!");getch();exit(-1);}for(i=0;i<2;i++,p++){printf(" inputdata(%d) ",i+1);scanf("%s%d%d%s",p->name,&p->num,&p->age,p->addr);}p=boya;fwrite(p,sizeof(structstu),2,fp);//写入2次,每次长度是stu字节数rewind(fp);fread(q,sizeof(structstu),2,fp);printf(" nametnumbertagetaddr ");for(i=0;i<2;i++,q++)printf("%st%dt%dt%s ",q->name,q->num,q->age,q->addr);fclose(fp);return(0);}本例程序定义了一个结构stu,说明了两个结构数组boya和boyb以及两个结构指针变量p和q。让p指向boya,q指向boyb。程序以读写方式打开二进制文件“stu_list”,输入二个学生数据之后,写入该文件中,然后把文件内部位置指针移到文件首,读出两块学生207/247 《C语言程序设计基础》教学大纲草案2021-10-237:25数据后,在屏幕上显示。12.4.4格式化读写函数fscanf和fprintffscanf函数,fprintf函数与前面使用的scanf和printf函数的功能相似,都是格式化读写函数。两者的区别在于fscanf函数和fprintf函数的读写对象不是键盘和显示器,而是磁盘文件。这两个函数的调用格式为:fscanf(文件指针,格式字符串,输入表列);fprintf(文件指针,格式字符串,输出表列);例如:fscanf(fp,"%d%s",&i,s);fprintf(fp,"%d%c",j,ch);用fscanf和fprintf函数也可以完成程序12.6功能。修改后的程序如程序12.7所示。程序12.7从键盘输入两个学生数据,写入一个文件中,再读出文件,显示在屏幕上。#include#include#include#includestructstu{charname[10];intnum;}boya[2],boyb[2],*p,*q;intmain(void){FILE*fp;charch;inti;p=boya;q=boyb;if((fp=fopen("stu_list.dat","wb+"))==NULL){printf("Cannotopenfilestrikeanykeyexit!");208/247 《C语言程序设计基础》教学大纲草案2021-10-237:25getch();exit(-1);}for(i=0;i<2;i++,p++){printf(" inputdata ");scanf("%s%d",p->name,&p->num);}p=boya;for(i=0;i<2;i++,p++)fprintf(fp,"%s%d ",p->name,p->num);rewind(fp);for(i=0;i<2;i++,q++)fscanf(fp,"%s%d ",q->name,&q->num);printf(" nametnumber ");q=boyb;for(i=0;i<2;i++,q++)printf("%st%dt ",q->name,q->num);fclose(fp);return(0);}与程序12.6相比,本程序中fscanf和fprintf函数每次只能读写一个结构数组元素,因此采用了循环语句来读写全部数组元素。还要注意指针变量p和q,由于循环改变了它们的值,因此程序在循环后分别对它们重新赋予了数组的首地址。12.5文件的随机读写前面介绍的对文件的读写方式都是顺序读写,即读写文件只能从头开始,顺序读写各个数据。但在实际问题中常要求只读写文件中某一指定的部分。为了解决这个问题可移动文件内部的位置指针到需要读写的位置,再进行读写,这种读写称为随机读写。实现随机读写的关键是要按要求移动位置指针,这称为文件的定位。12.5.1文件定位移动文件内部位置指针的函数主要有两个,即rewind函数和fseek函数。rewind函数前面已多次使用过,其调用形式为:209/247 《C语言程序设计基础》教学大纲草案2021-10-237:25rewind(文件指针);它的功能是把文件内部的位置指针移到文件首。下面主要介绍fseek函数。fseek函数用来移动文件内部位置指针,其调用形式为:fseek(文件指针,位移量,起始点);其中:“文件指针”:指向被移动的文件。“位移量”:表示移动的字节数,要求位移量是long型数据,以便在文件长度大于64KB时不会出错。当用常量表示位移量时,要求加后缀“L”。“起始点”:表示从何处开始计算位移量,规定的起始点有三种:文件首,当前位置和文件尾。其表示方法如下表。起始点表示符号数字表示文件首SEEK_SET0当前位置SEEK_CUR1文件末尾SEEK_END2例如:fseek(fp,100L,0);其意义是把位置指针移到离文件首100个字节处。还要说明的是fseek函数一般用于二进制文件。在文本文件中由于要进行转换,故往往计算的位置会出现错误。12.5.2文件的随机读写在移动位置指针之后,即可用前面介绍的任一种读写函数进行读写。由于一般是读写一个数据据块,因此常用fread和fwrite函数。下面用例题来说明文件的随机读写。程序12.8在学生文件stu_list中读出第二个学生的数据。#include#include#include#includestructstu{charname[10];intnum;intage;charaddr[15];210/247 《C语言程序设计基础》教学大纲草案2021-10-237:25}boy,*q;intmain(void){FILE*fp;charch;inti=1;q=&boy;if((fp=fopen("stu_list","rb"))==NULL){printf("Cannotopenfilestrikeanykeyexit!");getch();exit(-1);}fseek(fp,i*sizeof(structstu),0);fread(q,sizeof(structstu),1,fp);printf(" nametnumbertagetaddr ");printf("%st%dt%dt%s ",q->name,q->num,q->age,q->addr);fclose(fp);return(0);}文件stu_list已由程序12.6建立,本程序用随机读出的方法读出第二个学生的数据。程序中定义boy为stu类型变量,q为指向boy的指针。以读二进制文件方式打开文件,程序用fseek()函数移动文件位置指针。其中的i值为1,表示从文件头开始,移动一个stu类型的长度,然后再读出的数据即为第二个学生的数据。12.6文件检测函数C语言中常用的文件检测函数有以下几个。文件结束检测函数feof函数调用格式:feof(文件指针);功能:判断文件是否处于文件结束位置,如文件结束,则返回值为1,否则为0。注意,当211/247 《C语言程序设计基础》教学大纲草案2021-10-237:25返回值为1的时候,最近一次读取文件操作已经达到了尾部,如果使用while(feof(fp))语句判别,比如:while(feof(fp)){p=(structstu*)malloc(sizeof(stu));if(!p)exit(-1);fread(p,sizeof(structstu),1,fp);//当前操作可能让feof(fp)为1达到结尾head=dls_store(p,head);//可能产生文件结尾时仍然在当作正常记录处理情形}实际上,最近一次文件读操作已经达到尾部(在尾部的时候没有正常读出记录数据),而程序仍有可能将尾部错误的数据当作正常纪录在处理,改进的方法如下:p=(structstu*)malloc(sizeof(stu));if(!p)exit(-1);fread(p,sizeof(structstu),1,fp);//先做一次读出文件操作while(!feof(fp)){head=dls_store(p,head);//不是文件结尾就正常插入节点p=(structstu*)malloc(sizeof(stu));//准备下一次插入节点空间if(!p)exit(-1);fread(p,sizeof(structstu),1,fp);//继续读出操作}读写文件出错检测函数ferror函数调用格式:ferror(文件指针);功能:检查文件在用各种输入输出函数进行读写时是否出错。如ferror返回值为0表示未出错,否则表示有错。文件出错标志和文件结束标志置0函数clearerr函数调用格式:clearerr(文件指针);功能:本函数用于清除出错标志和文件结束标志,使它们为0值。212/247 《C语言程序设计基础》教学大纲草案2021-10-237:2512.7实用文件程序--链表数据保存在程序8.21单链表例子中,我们给出了链表动态生长方法。链表的特点是表的存储长度根据输入纪录数目而增长。当内存纪录数目很大的时候,必须考虑链表的保存问题,也就是如何将指针表达的逻辑关系存储在硬盘,以及如何将硬盘打开的文件恢复在内存。注意,文件纪录在硬盘的存储方式只有顺序邻接一种。链表文件硬盘流式文件如何保存链表?需要保存指针吗?如何恢复链表?程序12.9给出了链表保存与恢复的实际例子。程序12.9链表保存与恢复#include#include#include#include#includestructstu{intnum;//学生学号intscore;//成绩structstu*next;//指针域};structstu*dls_store(structstu*,structstu*);structstu*load(FILE*);voidsave(structstu*,FILE*);voidenter(structstu*);voidlist(structstu*);intmain(void){structstu*head=NULL,*s;FILE*fp;inti=0;charfilename[20];while(i==0){printf("select:i(插入)orp(打印)orq(退出)orl(取盘)ors(存盘)ord(删除) ");switch(getch()){case'i':s=(structstu*)malloc(sizeof(stu));//向内存申请一个节点213/247 《C语言程序设计基础》教学大纲草案2021-10-237:25if(!s)exit(-1);//如果失败则返回enter(s);head=dls_store(s,head);//节点插入list(head);//遍历链表break;case'p':list(head);//遍历链表break;case'q':i=1;break;case's':printf("输入文件名: ");scanf("%s",filename);if((fp=fopen(filename,"ab+"))==NULL){//读写打开二进制文件,尾部操作纪录printf("Cannotopenfilestrikeanykeyexit!");getch();exit(-1);}save(head,fp);//链表存盘fclose(fp);break;case'l':printf("输入文件名: ");scanf("%s",filename);if((fp=fopen(filename,"ab+"))==NULL){//读写打开二进制文件,尾部操作纪录printf("Cannotopenfilestrikeanykeyexit!");getch();exit(-1);}head=load(fp);//打开一个文件恢复链表fclose(fp);break;case'd':printf("输入文件名: ");scanf("%s",filename);if((fp=fopen(filename,"ab+"))==NULL){//读写打开二进制文件printf("Cannotopenfilestrikeanykeyexit!");getch();exit(-1);}fclose(fp);if(unlink(filename)==-1)printf("Filedoesn'texist ");//删除文件失败elseprintf("deleteone'sFile ");//成功删除文件214/247 《C语言程序设计基础》教学大纲草案2021-10-237:25break;}}return(0);}structstu*load(FILE*fp){structstu*p,*head=NULL;p=(structstu*)malloc(sizeof(stu));//申请一个节点空间if(!p)exit(-1);fread(p,sizeof(structstu),1,fp);//预读一次文件while(!feof(fp)){//不是结尾,则插入链表head=dls_store(p,head);p=(structstu*)malloc(sizeof(stu));if(!p)exit(-1);fread(p,sizeof(structstu),1,fp);//继续读文件}list(head);return(head);}voidsave(structstu*head,FILE*fp){structstu*p;p=head;while(p){fwrite(p,sizeof(structstu),1,fp);//每次写入一个长度是stu字节数的纪录p=p->next;}}voidenter(structstu*s){printf("输入学号:");scanf("%d",&(s->num));//输入关键字值printf(" 输入成绩:");scanf("%d",&(s->score));//输入关键字值}voidlist(structstu*head){inti=1;if(!head)exit(-1);while(head){printf("序号%d学号:%d成绩:%d ",i,head->num,head->score);i++;head=head->next;215/247 《C语言程序设计基础》教学大纲草案2021-10-237:25}}structstu*dls_store(structstu*s,structstu*head){structstu*p,*q;//定义中间变量if(!head){//表空,返回S为头部节点head=s;s->next=NULL;return(s);}p=head;//从头开始搜索p节点*/q=p;while(p){if(p->numnum){//当前节点关键字值小于S节点关键字值,搜索下一个节点q=p;p=p->next;}else{//找到i值节点pif(p==head){//是头部?s->next=head;head=s;return(s);}q->next=s;//是链表中间插入S节点s->next=p;//因有中间变量定义,所以指针修改顺序可以不考虑return(head);}}q->next=s;//如用p-->next=S则错,因此时p为空s->next=NULL;return(head);}程序12.9的插入、打印列表、节点输入函数以及主函数结构已经在程序8.21解释过,这里我们主要分析链表保存与恢复函数save()、load():(1)节点数据与指针的区别节点数据是刻画客观实体属性特征,指针是节点在内存物理地址描述,反映节点逻辑关系的。文件存盘就是保存数据,磁盘介质存储记录的方式是顺序排列,相邻节点之间逻辑关系也是相邻,如同数组元素在内存的物理地址关系。文件保存在硬盘上,假若打开文件再次读出到内存,计算机动态分配的地址已经变化,因而没有保存指针的意义。(2)打开一个硬盘文件等同于从键盘输入一个节点数据216/247 《C语言程序设计基础》教学大纲草案2021-10-237:25就流的概念而言,从键盘读入数据与从硬盘读入数据没有本质的区别,因此:fread(p,sizeof(structstu),1,fp);//读入一个节点数据等同于从键盘输入一个节点数据的函数enter()。程序首先申请一个节点内存,再从硬盘文件中读入一个节点数据,并把它插入到链表中。(3)用追加方式打开一个硬盘文件因为我们没有链表长度数据,只能从head节点开始循环直至链表尾部,逐个节点写入到文件中,因此,必须用追加方式打开文件,每次将节点写入文件的尾部。(4)防止文件尾部误读的处理当while()判断文件已经结束的时候,当前读出的数据已经不是文件的记录数据,如果不能正确处理文件尾部,会产生错误数据进入链表。因此,应该先读取数据,再进入while()循环判别是否文件结尾。(5)文件删除处理程序的”d”选项可以删除当前目录下指定的文件。12.8本章小结C系统把文件当作一个“流”,按字节进行处理。C文件按编码方式分为二进制文件和ASCII文件。C语言中,用文件指针标识文件,当一个文件被打开时,可取得该文件指针。文件在读写之前必须打开,读写结束必须关闭。文件可按只读、只写、读写、追加四种操作方式打开,同时还必须指定文件的类型是二进制文件还是文本文件。文件可按字节,字符串,数据块为单位读写,文件也可按指定的格式进行读写。文件内部的位置指针可指示当前的读写位置,移动该指针可以对文件实现随机读写。217/247 《C语言程序设计基础》教学大纲草案2021-10-237:25附录1二进制与十进制转换及补码表示附1.1二进制与十进制转换我们知道,某一位十进制数的数值,可以表达为它在该位的数值与该十进制位的权(幂)的乘积关系,比如十进制数4097可以表达为:32104097410010910710某一位的二进制数的数值,也可以表达为它在该位的数值(0或者1)与该二进制位的权(幂)的乘积关系,比如二进制数1,0000,0000,0001可以表达为:12111001,0000,0000,0001120202...12409614097这就是十进制与二进制数的换算关系,为了清楚起见,习惯上我们把二进制数每隔4位用逗号或者空格隔开。有了上述转换关系,计算机通过开关电路存储或运算二进制数,与我们平时的十进制数存储和运算完全等价,而开关电路的电子设计实现非常简单可靠,能大规模集成化,因此,计算机就能存储大容量的数据,以及进行非常快的数值运算工作。要将十进制转为二进制,只需除以二进制的权值,取得其余数,第一次的余数当最低位01数,其权值是2,第二次余数的权值是2,其余依此类推,直到被除数小于权值,最后的被除数当最高位数。例如十进制的55转换为二进制数表达:255余数二进制位数271权为20131权为2161权为2230权为2311权为241权为25转换结果:11,0111附1.2二进制补码表示在十进制中整数有正负之分,比如23和-23。那么二进制整数也必须能区分正负。以int类型为例,C语言规定其最高位是符号位,即0为正整数,1为负整数。于是int类型变量中如图2.2(a)的最高位(数据位D1515)为1并不代表其权为2,只是表示该变量为负数,218/247 《C语言程序设计基础》教学大纲草案2021-10-237:25见图附1.1所示。215282720000000000000000002152827200111111111111111327672152827201000000000000000-327682152827201111111111111111-1图附1.1int变量值的取值范围是-32768至+32767不过,为了某些应用上的方便,C语言也给我们提供了定义无符号整数的选择,即Unsignedint类型,它的取值范围就是图2.2(a)的2字节二进制数取值范围。同学也许会有疑问,为什么图附1.1的1000,0000,0000,0000值是-32768?我们说这是因为它是补码形式表达的负数。计算机的二进制运算中,使用补码将减法运算转换成加法运算。补码在我们日常生活当中最典型的应用就是时钟。图附1.2时钟图附1.2时钟的时间是10点10分,假定现在想把时钟调整到8点10分,即减去2个小时。有两种办法:①逆时针方向回拨时针(相当于减法)2圈,使之指向8点10分;②顺时针方向正拨时针10圈(相当于加法),同样也能让时钟正确的指向8点10分。之所以加10相当于减2,是因为时钟是12进制的,也说其模是12。10加10,进位溢出后余8,正好相当于10减2。于是,我们说以12为模的时钟,其10是2的补码。219/247 《C语言程序设计基础》教学大纲草案2021-10-237:25减2相当于加10。也就是说,减去一个数,同等于加上这个数的补码,即:被减数-减数=被减数+减数的补码于是,通过补码运算,我们可以将减法转换为加法,因此,计算机中使用补码表示法。那么,如何求一个数的补码?图附1.3以12进制的时钟计数为例进行说明:进位被溢出后的余数模10-2=10+(12-2)=10+10=8减数图附1.3模为12的补码运算求一个数的补码,必须确定它的模。比如,单字节变量的模显然是256,因为它最大只能表示1111’1111,加一产生溢出后,又重新回到零。那么,如何求二进制的补码?图附1.4给出了二进制补码运算的例子,因为8位二进制的模为256(1’00000000),它减去00000001(减数)后结果为11111111,也就是-1的补码。模1'000000000000000100001000-00000001=00001000+1111111100000001的补码:(-=000001110'11111111图附1.4二进制的补码运算补码表示法中,最高位还是符号位,正数为0,负数为1。正数的补码就是它本身,负数的补码等于它的反码加上1,也就是说,负数的补码等于其对应正数的原码按位求反,再加上1。例如:[+1]补=00000001,[+127]补=01111111[-1]补=11111111,[-127]补=10000001由上面可以看出,八位二进制补码表示的数的范围是-128~127。采用补码表示法时,计算机中的加减法运算都可以通过“加法”来实现,即补码的加法,并且两个数的补码之和等于两数和的补码,符号位一起参与运算,结果仍为补码。比如:11110001(-15)01111111(127)11111110(-2)10000001(-128)1'11101111(-17)(+11111110(-1)(+溢出(a)(b)图附1.5补码运算220/247 《C语言程序设计基础》教学大纲草案2021-10-237:25附录2程序的版式版式虽然不会影响程序的功能,但会影响可读性。程序的版式追求清晰、美观,是程序风格的重要构成因素。可以把程序的版式比喻为“书法”。好的“书法”可让人对程序一目了然,看得兴致勃勃。差的程序“书法”如螃蟹爬行,让人看得索然无味,更令维护者烦恼有加。请程序员们学习程序的“书法”,弥补大学计算机教育的漏洞,实在很有必要。附2.1空行空行起着分隔程序段落的作用。空行得体(不过多也不过少)将使程序的布局更加清晰。空行不会浪费内存,虽然打印含有空行的程序是会多消耗一些纸张,但是值得。所以不要舍不得用空行。【规则2.1】在每个类声明之后、每个函数定义结束之后都要加入空行,进行分割。参见示例2.1(a)【规则2.2】在一个函数体内,逻揖上密切相关的语句之间不加空行,其它地方应加空行分隔。参见示例2.1(b)//空行//空行voidFunction1(…)while(condition){{statement1;…//空行}if(condition)//空行{voidFunction2(…)statement2;{}…else}{//空行statement3;}voidFunction3(…)//空行{statement4;…}}示例2.1(a)函数之间的空行示例2.2(b)函数内部的空行附2.2代码行【规则2.3】一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样的代码容易阅读,并且方便于写注释。221/247 《C语言程序设计基础》教学大纲草案2021-10-237:25【规则2.4】所有复合语句自占一行,执行语句不得紧跟其后。不论执行语句有多少都要加{}。这样可以防止书写失误。示例2.3(a)为风格良好的代码行,示例2.3(b)为风格不良的代码行。intwidth;//宽度intwidth,height,depth;//宽度高度深度intheight;//高度intdepth;//深度x=a+b;X=a+b;y=c+d;z=e+f;y=c+d;z=e+f;if(width=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等二元操作符的前后应当加空格。【规则2.10】一元操作符如“!”、“~”、“++”、“--”、“&”(地址运算符)等前后不加空格。【规则2.11】象“[]”、“.”、“->”这类操作符前后不加空格。建议对于表达式比较长的for语句和if语句,为了紧凑起见可以适当地去掉一些空格,如for(i=0;i<10;i++)和if((a<=b)&&(c<=d))voidFunc1(intx,inty,intz);//良好的风格voidFunc1(intx,inty,intz);//不良的风格if(year>=2000)//良好的风格if(year>=2000)//不良的风格if((a>=b)&&(c<=d))//良好的风格if(a>=b&&c<=d)//不良的风格for(i=0;i<10;i++)//良好的风格for(i=0;i<10;i++)//不良的风格for(i=0;I<10;i++)//过多的空格x=aFunction();//不要写成b->Function();示例2.4代码行内的空格附2.4对齐【规则2.12】程序的分界符‘{’和‘}’应独占一行并且位于同一列,同时与引用它们的语句左对齐。【规则2.13】{}之内的代码块在‘{’右边数格处左对齐。示例2.5(a)为风格良好的对齐,示例2.5(b)为风格不良的对齐。223/247 《C语言程序设计基础》教学大纲草案2021-10-237:25voidFunction(intx)voidFunction(intx){{…//programcode…//programcode}}if(condition)if(condition){{…//programcode…//programcode}}else{else…//programcode{}…//programcode}for(initialization;condition;update)for(initialization;condition;update){{…//programcode…//programcode}}While(condition)while(condition){{…//programcode…//programcode}}如果出现嵌套的{},则使用缩进对齐,如:{…{…}…}示例2.5(a)风格良好的对齐示例2.5(b)风格不良的对齐附2.5长行拆分【规则2.14】代码行最大长度宜控制在70至80个字符以内。代码行不要过长,否则眼睛看不过来,也不便于打印。if((very_longer_variable1>=very_longer_variable12)&&(very_longer_variable3<=very_longer_variable14)&&(very_longer_variable5<=very_longer_variable16)){dosomething();}virtualCMatrixCMultiplyMatrix(CMatrixleftMatrix,CMatrixrightMatrix);for(very_longer_initialization;very_longer_condition;very_longer_update){224/247 《C语言程序设计基础》教学大纲草案2021-10-237:25dosomething();}示例2.6长行的拆分【规则2.15】长表达式要在低优先级操作符处拆分成新行,操作符放在新行之首(以便突出操作符)。拆分出的新行要进行适当的缩进,使排版整齐,语句可读。附2.6修饰符的位置修饰符*和&应该靠近数据类型还是该靠近变量名,是个有争议的活题。若将修饰符*靠近数据类型,例如:int*x;从语义上讲此写法比较直观,即x是int类型的指针。上述写法的弊端是容易引起误解,例如:int*x,y;此处y容易被误解为指针变量。虽然将x和y分行定义可以避免误解,但并不是人人都愿意这样做。【规则2.16】应当将修饰符*和&紧靠变量名。例如:char*name;int*x,y;//此处y不会被误解为指针附2.7注释C语言的注释符为“/*…*/”。C++语言中,程序块的注释常采用“/*…*/”,行注释一般采用“//…”。注释通常用于:(1)版本、版权声明;(2)函数接口说明;(3)重要的代码行或段落提示。虽然注释有助于理解代码,但注意不可过多地使用注释。参见示例2.7。【规则2.17】注释是对代码的“提示”,而不是文档。程序中的注释不可喧宾夺主,注释太多了会让人眼花缭乱。注释的花样要少。【规则2.18】如果代码本来就是清楚的,则不必加注释。否则多此一举,令人厌烦。例如i++;//i加1,多余的注释【规则2、.19】边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。【规则2.20】注释应当准确、易懂,防止注释有二义性。错误的注释不但无益反而有害。225/247 《C语言程序设计基础》教学大纲草案2021-10-237:25【规则2.21】尽量避免在注释中使用缩写,特别是不常用缩写。【规则2.22】注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不可放在下方。【规则2.23】当代码比较长,特别是有多重嵌套时,应当在一些段落的结束处加注释,便于阅读。/*if(…)*函数介绍:{*输入参数:…*输出参数:while(…){*返回值:…*/}//endofwhilevoidFunction(floatx,floaty,floatz)…{}//endofif…}示例2.7程序的注释附2.8类的版式类可以将数据和函数封装在一起,其中函数表示了类的行为(或称服务)。类提供关键字public、protected和private,分别用于声明哪些数据和函数是公有的、受保护的或者是私有的。这样可以达到信息隐藏的目的,即让类仅仅公开必须要让外界知道的内容,而隐藏其它一切内容。我们不可以滥用类的封装功能,不要把它当成火锅,什么东西都往里扔。类的版式主要有两种方式:(1)将private类型的数据写在前面,而将public类型的函数写在后面,如示例2.8(a)所示那样。采用这种版式的程序员主张类的设计“以数据为中心”,重点关注类的内部结构。(2)将public类型的函数写在前面,而将private类型的数据写在后面,如示例2.8(b)采用这种版式的程序员主张类的设计“以行为为中心”,重点关注的是类应该提供什么样的接口(或服务)。226/247 《C语言程序设计基础》教学大纲草案2021-10-237:25classAclassA{{private:public:inti,j;voidFunc1(void);floatx,y;voidFunc2(void);……public:private:voidFunc1(void);inti,j;voidFunc2(void);floatx,y;……}}示例2.8(a)以数据为中心版式示例2.8(b)以行为为中心的版式很多C++教课书受到BiarneStroustrup第一本著作的影响,不知不觉地采用了“以数据为中心”的书写方式,并不见得有多少道理。建议读者采用“以行为为中心”的书写方式,即首先考虑类应该提供什么样的函数。这是很多人的经验——“这样做不仅让自己在设计类时思路清晰,而且方便别人阅读。因为用户最关心的是接口,谁愿意先看到一堆私有数据成员!”227/247 《C语言程序设计基础》教学大纲草案2021-10-237:25附录3命名规则比较著名的命名规则当推Microsoft公司的“匈牙利”法,该命名规则的主要思想是“在变量和函数名中加入前缀以增进人们对程序的理解”。例如所有的字符变量均以ch为前缀,若是指针变量则追加前缀p。如果一个变量由ppch开头,则表明它是指向字符指针的指针。“匈牙利”法最大的缺点是烦琐,例如inti,j,k;floatx,y,z;倘若采用“匈牙利”命名规则,则应当写成intiI,iJ,ik;//前缀i表示int类型floatfX,fY,fZ;//前缀f表示float类型如此烦琐的程序会让绝大多数程序员无法忍受。据考察,没有一种命名规则可以让所有的程序员赞同,程序设计教科书一般都不指定命名规则。命名规则对软件产品而言并不是“成败悠关”的事,我们不要化太多精力试图发明世界上最好的命名规则,而应当制定一种令大多数项目成员满意的命名规则,并在项目中贯彻实施。附3.1共性规则本节论述的共性规则是被大多数程序员采纳的,我们应当在遵循这些共性规则的前提下,再扩充特定的规则,如3.2节。【规则3.1】标识符应当直观且可以拼读,可望文知意,不必进行“解码”。标识符最好采用英文单词或其组合,便于记忆和阅读。切忌使用汉语拼音来命名。程序中的英文单词一般不会太复杂,用词应当准确。例如不要把CurrentValue写成NowValue。【规则3.2】标识符的长度应当符合“min-length&&max-information”原则。几十年前老ANSIC规定名字不准超过6个字符,现今的C++/C不再有此限制。一般来说,长名字能更好地表达含义,所以函数名、变量名、类名长达十几个字符不足为怪。那么名字是否越长约好?不见得!例如变量名maxval就比maxValueUntilOverflow好用。单字符的名字也是有用的,常见的如i,j,k,m,n,x,y,z等,它们通常可用作函数内的局部变量。【规则3.3】命名规则尽量与所采用的操作系统或开发工具的风格保持一致。228/247 《C语言程序设计基础》教学大纲草案2021-10-237:25例如Windows应用程序的标识符通常采用“大小写”混排的方式,如AddChild。而Unix应用程序的标识符通常采用“小写加下划线”的方式,如add_child。别把这两类风格混在一起用。【规则3.4】程序中不要出现仅靠大小写区分的相似的标识符。例如:intx,X;//变量x与X容易混淆voidfoo(intx);//函数foo与FOO容易混淆voidFOO(floatx);【规则3.5】程序中不要出现标识符完全相同的局部变量和全局变量,尽管两者的作用域不同而不会发生语法错误,但会使人误解。【规则3.6】变量的名字应当使用“名词”或者“形容词+名词”。例如:floatvalue;floatoldValue;floatnewValue;【规则3.7】全局函数的名字应当使用“动词”或者“动词+名词”(动宾词组)。类的成员函数应当只使用“动词”,被省略掉的名词就是对象本身。例如:DrawBox();//全局函数box->Draw();//类的成员函数【规则3.8】用正确的反义词组命名具有互斥意义的变量或相反动作的函数等。例如:intminValue;intmaxValue;intSetValue(…);intGetValue(…);建议尽量避免名字中出现数字编号,如Value1,Value2等,除非逻辑上的确需要编号。这是为防止程序员偷懒,不肯为命名动脑筋而导致产生无意义的名字(因为用数字编号最省事)。附3.2简单的Windows应用程序命名规则作者对“匈牙利”命名规则做了合理的简化,下述的命名规则简单易用,比较适合于Windows应用软件的开发。229/247 《C语言程序设计基础》教学大纲草案2021-10-237:25【规则3.9】类名和函数名用大写字母开头的单词组合而成。例如:classNode;//类名classLeafNode;//类名voidDraw(void);//函数名voidSetValue(intvalue);//函数名【规则3.10】变量和参数用小写字母开头的单词组合而成。例如:BOOLflag;intdrawMode;【规则3.10】常量全用大写的字母,用下划线分割单词。例如:constintMAX=100;constintMAX_LENGTH=100;【规则3.11】静态变量加前缀s_(表示static)。例如:voidInit(…){staticints_initValue;//静态变量…}【规则3.12】如果不得已需要全局变量,则使全局变量加前缀g_(表示global)。例如:intg_howManyPeople;//全局变量intg_howMuchMoney;//全局变量【规则3.13】类的数据成员加前缀m_(表示member),这样可以避免数据成员与成员函数的参数同名。例如:voidObject::SetValue(intwidth,intheight){m_width=width;m_height=height;}【规则3.14】为了防止某一软件库中的一些标识符和其它软件库中的冲突,可以为各种标识符加上能反映软件性质的前缀。例如三维图形标准OpenGL的所有库函数均以gl开头,所有常量(或宏定义)均以GL开头。230/247 《C语言程序设计基础》教学大纲草案2021-10-237:25附3.3运算符的优先级C++/C语言的运算符有数十个,运算符的优先级与结合律如表附3.1所示。注意一元运算符+-*的优先级高于对应的二元运算符。优先级运算符结合律()[]->.从左至右!~++--(类型)sizeof从右至左从+-*&*/%从左至右高+-从左至右<<>>从左至右到<<=>>=从左至右==!=从左至右低&从左至右^从左至右排|从左至右&&从左至右列||从右至左?:从右至左=+=-=*=/=%=&=^=从左至右|=<<=>>=附表3.1运算符的优先级与结合律【规则3.15】如果代码行中的运算符比较多,用括号确定表达式的操作顺序,避免使用默认的优先级。由于将附表3.1熟记是比较困难的,为了防止产生歧义并提高可读性,应当用括号确定表达式的操作顺序。例如:word=(high<<8)|lowif((a|b)&&(a&c))附3.4复合表达式如a=b=c=0这样的表达式称为复合表达式。允许复合表达式存在的理由是:(1)书写简洁;(2)可以提高编译效率。但要防止滥用复合表达式。【规则3.16】不要编写太复杂的复合表达式。例如:i=a>=b&&c=80是否条件成立不成立打印ni,giABi+1i(d)选择结构直到i>50(e)循环-选择结构图附4.2N-S流程图233/247 《C语言程序设计基础》教学大纲草案2021-10-237:25附录5编程步骤程序设计步骤如图附5.1所示,希望初学者据此建立良好的工作习惯。(1)程序规划根据任务,定义程序的输入输出数据,规划人机交互界面的功能设计。简单的程序,可能就是用scanf()和printf()语句输入输出一些行信息,比如例1.3。复杂的任务根据要求,可能会涉及到操作系统平台接口,窗口界面,数据的输入输出格式设计等。(2)程序结构设计程序规划是概念上的,现在就需要进行任务逻辑分解。简单的C程序设计就是模块设计,如何组织函数实现,参数传递等。复杂的多线程C++程序需要确定开几个线程,线程通讯机制等。(3)编制程序逻辑结构确定之后,就是各函数体的语句实现,需要注意的是语句简洁,注释清楚。同样的功能实现,优秀程序员编制出来程序结构清晰,语句简练。反之,就是杂乱无章的代码和臃肿程序。虽然功能相同,但它们的效率不同,可读性不同,也就是维护成本不同。(4)编译编制出来的程序很少有一次就能编译通过的,总会有一些语法上的错误,就是我们对c语法的熟练程度不同,犯错误的几率不同而已。因此,我们必须注意资系的阅读编译报告,根据给定的错误代码迅速的判断出错语句所在的行,以及格式错误原因。(5)运行程序编译通过后我们得到的是可执行文件(后缀是.exe的文件),我们可以在操作系统平台上直接运行该文件,也可以在C的集成开发环境下运行程序。对于学习程序设计者来说,应选择在集成开发环境下运行,这样调试比较方便。因为集成开发环境给程序员提供了调试工具,详细见后面的学习内容。(6)程序调试程序一定会存在一些错误,它不是语法错误,所以编译程序无法发现,它是算法方面的错误,俗称Bug。因此,如何用调试工具(Debug)解决错误就非常关键。Debug提供了程序单步运行、设置程序运行暂停点(断点),观测指定变量的窗口等。任何一个程序设计人员必须要掌握程序动态调试方法。(7)程序修改234/247 《C语言程序设计基础》教学大纲草案2021-10-237:25良好的程序设计风格不但是结构清晰,而且有简单明了的注释。一个实际的应用程序会不断的增加功能或修正算法,也就是版本更新,注释可以让我们回忆设计细节,或者让其他人能方便的了解程序概貌,以便进行程序的修改与完善工作。235/247 《C语言程序设计基础》教学大纲草案2021-10-237:25附录6范围和参数传递转换236/247 《C语言程序设计基础》教学大纲草案2021-10-237:25附录7ASCII码表十进十六进字十进制十六进字符注解注解制数制数符数制数NUL000Null@6440SOH101StartofHeadingA6541STX202StartofTextB6642ETX303EndofTextC6743EOT404EndofTransmissionD6844ENQ505EnquiryE6945ACK606AcknowledgeF7046BEL707BellG7147BS808BackspaceH7248SH909HorisontalTabulationI7349LF100ALineFeesJ744AVT110BVerticalTabulationK754BFF120CFormFeedL764CCR130DCarriageReturnM774DSO140EShiftOutN784ESI150FShiftInO794FDEL1610DataLinkEscapeP8050DC11711DeviceControl1Q8151DC21812DeviceControl2R8252DC31913DeviceControl3S8353DC42014DeviceControl4T8454NAK2115NegativeAcknowledgeU8555SYN2216synchronousIdleV8656ETB2317EndofTransmissionBlockW8757CAN2418CancelX8858EM2519EndofMediumY8959SUB261ASubsituteZ905AESC271BEscape[915BFS281CFileSeparator925CGS291DGroupSeparator]935DRS301EUnitSeprator^945E237/247 《C语言程序设计基础》教学大纲草案2021-10-237:25续前表十进十六进字十进制十六进字符注解注解制数制数符数制数US311FSpace-955FSP3220'9660!3321a9761"3422b9862#3523c9963$3624d10064%3725e10165&3826f10266'3927g10367(4028h10468)4129i10569*422Aj1066A+432Bk1076B,442Cl1086C_452Dm1096D.462En1106E/472Fo1116F04830p1127014931q1137125032r1147235133s1157345234t1167455335u1177565436v1187675537w1197785638x1207895739y12179:583Az1227A;593B{1237B<603C|1247C=613D}1257D>623E~1267E?633FDEL1277FDelete238/247 《C语言程序设计基础》教学大纲草案2021-10-237:25附录8变量的动态存储与静态存储附8.1存储方式变量的存储类型各种变量的作用域不同,就其本质来说是因变量的存储类型相同。所谓存储类型是指变量占用内存空间的方式,也称为存储方式。变量的存储方式可分为“静态存储”和“动态存储”两种。静态存储变量通常是在变量定义时就分定存储单元并一直保持不变,直至整个程序结束。全局变量即属于此类存储方式。动态存储变量是在程序执行过程中,使用它时才分配存储单元,使用完毕立即释放。典型的例子是函数的形式参数,在函数定义时并不给形参分配存储单元,只是在函数被调用时,才予以分配,调用函数完毕立即释放。如果一个函数被多次调用,则反复地分配、释放形参变量的存储单元。从以上分析可知,静态存储变量是一直存在的,而动态存储变量则时而存在时而消失。我们又把这种由于变量存储方式不同而产生的特性称变量的生存期。生存期表示了变量存在的时间。生存期和作用域是从时间和空间这两个不同的角度来描述变量的特性,这两者既有联系,又有区别。一个变量究竟属于哪一种存储方式,并不能仅从其作用域来判断,还应有明确的存储类型说明。在C语言中,对变量的存储类型说明有以下四种:auto自动变量register寄存器变量extern外部变量static静态变量自动变量和寄存器变量属于动态存储方式,外部变量和静态变量属于静态存储方式。在介绍了变量的存储类型之后,可以知道对一个变量的说明不仅应说明其数据类型,还应说明其存储类型。因此变量说明的完整形式应为:存储类型说明符数据类型说明符变量名,变量名…;例如:staticinta,b;说明a,b为静态类型变量autocharc1,c2;说明c1,c2为自动字符变量239/247 《C语言程序设计基础》教学大纲草案2021-10-237:25staticinta[5]={1,2,3,4,5};说明a为静整型数组externintx,y;说明x,y为外部整型变量下面分别介绍以上四种存储类型:一、自动变量的类型说明符为auto。这种存储类型是C语言程序中使用最广泛的一种类型。C语言规定,函数内凡未加存储类型说明的变量均视为自动变量,也就是说自动变量可省去说明符auto。在前面各章的程序中所定义的变量凡未加存储类型说明符的都是自动变量。例如:{inti,j,k;charc;……}等价于:{autointi,j,k;autocharc;……}自动变量具有以下特点:1.自动变量的作用域仅限于定义该变量的个体内。在函数中定义的自动变量,只在该函数内有效。在复合语句中定义的自动变量只在该复合语句中有效。例如:intkv(inta){autointx,y;{autocharc;}/*c的作用域*/……}/*a,x,y的作用域*/2.自动变量属于动态存储方式,只有在使用它,即定义该变量的函数被调用时才给它240/247 《C语言程序设计基础》教学大纲草案2021-10-237:25分配存储单元,开始它的生存期。函数调用结束,释放存储单元,结束生存期。因此函数调用结束之后,自动变量的值不能保留。在复合语句中定义的自动变量,在退出复合语句后也不能再使用,否则将引起错误。例如以下程序:main(){autointa;printf(" inputanumber: ");scanf("%d",&a);if(a>0){autoints,p;s=a+a;p=a*a;}printf("s=%dp=%d ",s,p);}s,p是在复合语句内定义的自动变量,只能在该复合语句内有效。而程序的第9行却是退出复合语句之后用printf语句输出s,p的值,这显然会引起错误。3.由于自动变量的作用域和生存期都局限于定义它的个体内(函数或复合语句内),因此不同的个体中允许使用同名的变量而不会混淆。即使在函数内定义的自动变量也可与该函数内部的复合语句中定义的自动变量同名。例如以下程序表明了这种情况。main(){autointa,s=100,p=100;printf(" inputanumber: ");scanf("%d",&a);if(a>0){autoints,p;s=a+a;p=a*a;printf("s=%dp=%d ",s,p);241/247 《C语言程序设计基础》教学大纲草案2021-10-237:25}printf("s=%dp=%d ",s,p);}本程序在main函数中和复合语句内两次定义了变量s,p为自动变量。按照C语言的规定,在复合语句内,应由复合语句中定义的s,p起作用,故s的值应为a+a,p的值为a*a。退出复合语句后的s,p应为main所定义的s,p,其值在初始化时给定,均为100。从输出结果可以分析出两个s和两个p虽变量名相同,但却是两个不同的变量。4.对构造类型的自动变量如数组等,不可作初始化赋值。二、外部变量的类型说明符为extern。在前面介绍全局变量时已介绍过外部变量。这里再补充说明外部变量的几个特点:1.外部变量和全局变量是对同一类变量的两种不同角度的提法。全局变是是从它的作用域提出的,外部变量从它的存储方式提出的,表示了它的生存期。2.当一个源程序由若干个源文件组成时,在一个源文件中定义的外部变量在其它的源文件中也有效。例如有一个源程序由源文件F1.C和F2.C组成://F1.Cinta,b;/*外部变量定义*/charc;/*外部变量定义*/main(){……}//F2.Cexterninta,b;/*外部变量说明*/externcharc;/*外部变量说明*/func(intx,y){……}在F1.C和F2.C两个文件中都要使用a,b,c三个变量。在F1.C文件中把a,b,c都定义为外部变量。在F2.C文件中用extern把三个变量说明为外部变量,表示这些变量已在其它文件242/247 《C语言程序设计基础》教学大纲草案2021-10-237:25中定义,并把这些变量的类型和变量名,编译系统不再为它们分配内存空间。对构造类型的外部变量,如数组等可以在说明时作初始化赋值,若不赋初值,则系统自动定义它们的初值为0。三、静态变量静态变量的类型说明符是static。静态变量当然是属于静态存储方式,但是属于静态存储方式的量不一定就是静态变量,例如外部变量虽属于静态存储方式,但不一定是静态变量,必须由static加以定义后才能成为静态外部变量,或称静态全局变量。对于自动变量,前面已经介绍它属于动态存储方式。但是也可以用static定义它为静态自动变量,或称静态局部变量,从而成为静态存储方式。由此看来,一个变量可由static进行再说明,并改变其原有的存储方式。1.静态局部变量在局部变量的说明前再加上static说明符就构成静态局部变量。例如:staticinta,b;staticfloatarray[5]={1,2,3,4,5};静态局部变量属于静态存储方式,它具有以下特点:(1)静态局部变量在函数内定义,但不象自动变量那样,当调用时就存在,退出函数时就消失。静态局部变量始终存在着,也就是说它的生存期为整个源程序。(2)静态局部变量的生存期虽然为整个源程序,但是其作用域仍与自动变量相同,即只能在定义该变量的函数内使用该变量。退出该函数后,尽管该变量还继续存在,但不能使用它。(3)允许对构造类静态局部量赋初值。在数组一章中,介绍数组初始化时已作过说明。若未赋以初值,则由系统自动赋以0值。(4)对基本类型的静态局部变量若在说明时未赋以初值,则系统自动赋予0值。而对自动变量不赋初值,则其值是不定的。根据静态局部变量的特点,可以看出它是一种生存期为整个源程序的量。虽然离开定义它的函数后不能使用,但如再次调用定义它的函数时,它又可继续使用,而且保存了前次被调用后留下的值。因此,当多次调用一个函数且要求在调用之间保留某些变量的值时,可考虑采用静态局部变量。虽然用全局变量也可以达到上述目的,但全局变量有时会造成意外的副作用,因此仍以采用局部静态变量为宜。[例1]243/247 《C语言程序设计基础》教学大纲草案2021-10-237:25voidf();/*函数说明*/intmain(void){inti;for(i=1;i<=5;i++)f();/*函数调用*/}voidf()/*函数定义*/{autointj=0;++j;printf("%d ",j);}程序中定义了函数f,其中的变量j说明为自动变量并赋予初始值为0。当main中多次调用f时,j均赋初值为0,故每次输出值均为1。现在把j改为静态局部变量,程序如下:voidf();intmain(void){inti;for(i=1;i<=5;i++)f();}voidf(){staticintj=0;++j;printf("%d ",j);}由于j为静态变量,能在每次调用后保留其值并在下一次调用时继续使用,所以输出值成为累加的结果。读者可自行分析其执行过程。2.静态全局变量244/247 《C语言程序设计基础》教学大纲草案2021-10-237:25全局变量(外部变量)的说明之前再冠以static就构成了静态的全局变量。全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。从以上分析可以看出,把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。因此static这个说明符在不同的地方所起的作用是不同的。应予以注意。四、寄存器变量上述各类变量都存放在存储器内,因此当对一个变量频繁读写时,必须要反复访问内存储器,从而花费大量的存取时间。为此,C语言提供了另一种变量,即寄存器变量。这种变量存放在CPU的寄存器中,使用时,不需要访问内存,而直接从寄存器中读写,这样可提高效率。寄存器变量的说明符是register。对于循环次数较多的循环控制变量及循环体内反复使用的变量均可定义为寄存器变量。[例2]求∑200intmain(void){registeri,s=0;for(i=1;i<=200;i++)s+=i;printf("s=%d ",s);}本程序循环200次,i和s都将频繁使用,因此可定义为寄存器变量。对寄存器变量还要说明以下几点:1.只有局部自动变量和形式参数才可以定义为寄存器变量。因为寄存器变量属于动态存储方式。凡需要采用静态存储方式的量不能定义为寄存器变量。2.在TurboC,MSC等微机上使用的C语言中,实际上是把寄存器变量当成自动变量处理的。因此速度并不能提高。而在程序中允许使用寄存器变量只是为了与标准C保持一致。3.即使能真正使用寄存器变量的机器,由于CPU中寄存器的个数是有限的,因此使用245/247 《C语言程序设计基础》教学大纲草案2021-10-237:25寄存器变量的个数也是有限的。附8.2内部函数和外部函数函数一旦定义后就可被其它函数调用。但当一个源程序由多个源文件组成时,在一个源文件中定义的函数能否被其它源文件中的函数调用呢?为此,C语言又把函数分为两类:一、内部函数如果在一个源文件中定义的函数只能被本文件中的函数调用,而不能被同一源程序其它文件中的函数调用,这种函数称为内部函数。定义内部函数的一般形式是:static类型说明符函数名(形参表)例如:staticintf(inta,intb)内部函数也称为静态函数。但此处静态static的含义已不是指存储方式,而是指对函数的调用范围只局限于本文件。因此在不同的源文件中定义同名的静态函数不会引起混淆。二、外部函数外部函数在整个源程序中都有效,其定义的一般形式为:extern类型说明符函数名(形参表)例如:externintf(inta,intb)如在函数定义中没有说明extern或static则隐含为extern。在一个源文件的函数中调用其它源文件中定义的外部函数时,应用extern说明被调函数为外部函数。例如:F1.C(源文件一)externintf1(inti);/*外部函数说明,表示f1函数在其它源文件中*/intmain(void){……}F2.C(源文件二)externintf1(inti);/*外部函数定义*/{246/247 《C语言程序设计基础》教学大纲草案2021-10-237:25……}247/247

当前文档最多预览五页,下载文档查看全文

此文档下载收益归作者所有

当前文档最多预览五页,下载文档查看全文
温馨提示:
1. 部分包含数学公式或PPT动画的文件,查看预览时可能会显示错乱或异常,文件下载后无此问题,请放心下载。
2. 本文档由用户上传,版权归属用户,天天文库负责整理代发布。如果您对本文档版权有争议请及时联系客服。
3. 下载前请仔细阅读文档内容,确认文档内容符合您的需求后进行下载,若出现内容与标题不符可向本站投诉处理。
4. 下载文档时可能由于网络波动等原因无法下载或下载错误,付费完成后未能成功下载的用户请联系客服处理。
关闭