第1章 C语言概述
1.1 C语言发展过程及特点 1.2 C语言的基本词汇 1.3 C程序结构
1.4 C语言程序上机调试过程
1.1 C语言发展过程及特点
C语言是在20世纪70年代初问世的。 总结起来C语言有以下几个特点。
(1)语言简洁、紧凑,使用方便、灵活。 (2)C语言运算符丰富,共有34种运算符。
(3)C语言的数据结构也比较丰富,具有现代计算机语言的各种数据结构。 (4)具有结构化的流程控制语句,如选择语句、循环语句等。 (5)流程控制和数据操作灵活多样,程序设计有较大的自由度。
(6)C语言允许直接访问物理地址,能进行位运算,能实现汇编语言的大部分功能,可以直接对硬件进行操作。
(7)用C语言编写的程序可移植性好。
1.2 C语言的基本词汇
1.组成C语言基本词汇的基本字符
(1)数字10个(0~9);
(2)大、小写英文字母各26个(A~Z,a~z); (3)特殊符号,如表3-1所示。
表1-1 C语言的特殊符号
~ ) [ \\
2.保留字
! - ] \
# ? < '
% ? > |
^ { ? ;
* & : .
( } /
_(下划线) 空格 ,
C语言的保留字是具有特定含义的标识符,主要包括关键字和特定字两大类。关键字和特定字
的主要区别是所有的特定字都必须在使用前加“#”,如使用特定字define,应书写为#define。
1
(1)关键字
表1-2 C语言的关键字
auto break case doublelse enum e int long register struct switch typedef
(2)特定字
特定字主要用于编译预处理,主要有define,undef,include ifdef,ifndef,endif。 3.标识符
一个合理的标识符是由英文字母或下划线“_”开头的字母、下划线、数字字符组成的字符序列。 使用标识符时应注意以下几点。 (1)标准C不限制标识符的长度,但它受各种版本的C 语言编译系统限制,同时也受到具体机器的限制。 (2)在标识符中,大小写是有区别的。 (3)标识符是用于标识某个对象的符号。 (4)用户定义的标识符不应与保留字相同。
char extern return union const float short unsigned
continue
for signed void default goto sizeof volatile do if static while
1.3 C程序结构
例1.3 编写C程序,求3个数的和。 程序代码如下:
/*定义函数f1,求3个实数的和*/
float f1(float x,float y,float z) /*这部分是函数头。给出函数名、函数返回值的数据类型、形式参数x,y,z及它们的数据类型*/ {
float temp; /*定义函数f1内部的变量*/ temp=x+y+z; /*相加*/
return (temp); /*返回变量temp的值给调用处,并且结束本函数的运行*/ }
/*主函数*/ main( ) {
float a,b,c,sum;
printf(\ /*显示提示信息,可有可无*/
scanf(\ /*从键盘输入数据存储到a,b,c中*/ sum=f1(a,b,c); /*调用函数f1求3个数a,b,c的和*/ printf(\}
C语言程序的结构如下:
(1)程序是由一个或多个函数构成的,但必须有而且只有一个主函数main()。
(2)C程序的执行从主函数开始,也从主函数结束。其他函数的执行是通过主函数调用或别的函数调用来完成的。
(3)函数是构成C语言程序的基本单位,它完成相对独立的功能。它由函数头和函数体两部分组成。 (4)C语言程序的语句必须以分号结尾,复合语句除外。
(5)C语言本身没有输入输出语句,输入输出功能通过调用标准库函数来完成。
(6)一个C语言程序可根据需要适当加入一些注释行,以增加程序的可读性。注释的形式为“/*注释内容*/”。
2
(7)C语言程序书写格式自由,一行可写几条语句,一条语句也可写成几行。
2.4C语言程序上机调试过程
C语言是计算机高级语言,用C语言写好的程序不能直接上机运行,必须经过翻译变成机器语言程序后才能被计算机执行,即要经过“编辑→编译→连接→运行”的过程。假定编辑好的C源程序是study.c。要运行study.c就必须将其经过编译生成目标程序文件study.obj,目标程序文件study.obj也不能直接运行,必须经过连接将study.obj和C库函数经过连接生成可执行程序文件study.exe。
Turbo C 2.0 系统是一个集编辑、编译、连接、调试和运行等功能为一体的C语言程序集成开发系统。在使用Turbo C 2.0系统前,首先要在硬盘上创建一个子目录,命名该子目录名为TC,然后把Turbo C 2.0系统的内容拷贝到其中,执行可执行文件TC.EXE即可进入Turbo C 2.0系统环境。 图1.1 Turbo C 2.0系统主界面
图1.1 Turbo C 2.0系统主界面
Turbo C 2.0系统主界面
从上到下分为4个区:主菜单区、编辑区、信息区和功能键提示区。 (1)主菜单区
主菜单区有如下菜单项:
File Edit Run Compile Project Options Debug Break/watch (2)编辑区
编辑区供用户建立、修改源程序。 (3)信息区
信息区供显示编译、连接时出现的各种提示信息。 (4)功能键提示区
常用的功能键如表1-3所示。 表1-3 Turbo C常用功能键
F1 F2 F3 F4 F5
帮助 文件存盘 装入文件
程序运行到光标所在行
放大或缩小活动窗口(指光标所在窗口)
F6 F7 F8 F9 F10
开、关活动窗口
单步执行(跟踪进入函数内)
单步执行(不跟踪进入函数内)
编译、连接
光标在主菜单和编辑窗口间切换
3
下面以一简单实例说明基本上机步骤。 1.进入Turbo C 2.0系统 2.建立新文件
(1)在“File”菜单中选择“Load”子菜单项,出现如图1.2所示的对话框,在“*”处键入自己所需定义的文件名(如study.c)并回车。 图1.2 装入程序文件
3.录入程序
用户新建文件后可在编辑区录入程序,如图1.3所示。
4.保存文件
4
5.运行程序
6.查看结果
7.退出Turbo C 2.0系统
5
第2章
数据类型及表达式
2.1 数 据 结 构
数据结构是程序设计中计算机的操作对象以及操作对象之间关系和操作的描述。一个函数体中包括两大部分内容:其一是数据描述部分,其二是算法实现部分。要完成数据描述部分的内容,就必须寻找问题中的操作对象以及它们之间的关系,然后用计算机语言加以描述。 著名的计算机科学家沃斯(Nikilaus Wirth)提出了公式: 数据结构+算法=程序
该公式表明除算法之外,一个程序中的数据结构是编写程序首先要解决的问题。例4.1中的“链表”、“树”和“图”就是数据结构中的重要类型。当然一个好的程序除了应该具备公式中两个主要因素外,还应该选择合适的程序设计语言和程序设计方法。
C语言提供的数据结构是以数据类型形式表示的。
基本型(int,2byte) 长整型(long,4byte) 整型 短整型(short,2byte) 无符号整型(unsigned,2byte) 基本类型 单精度浮点型 (float,4byte)浮点型 双精度浮点型 (double,8byte) 字符型(char,1byte) 无值型(void) 数据类型 数组类型 结构体类型 构造类型 联合体类型 枚举类型 指针类型 2.2 C语言程序中数据的表示方法
数据是组成程序的必要元素,根据数据在程序中值的变化与否,数据在C语言程序中以常量和变量两
种形式表示。
4.2.1 常量
1.整型常量
数据类型为整型的常量即为整型常量,又称整常数。在C语言中,使用的整型常量有八进制、十六进制和十进制3种。其中八进制、十六进制主要用于表示整型常量在内存中的存储形式,也就是表示某整型常量的机器码;十进制表示外部数据,其值表示的是真值。
(1)八进制整型常量
八进制整型常量必须以0开头,即以0作为八进制数的前缀。其数码取值为0~7。由于八进制数主要用于表示某整型常量的机器码,所以当表示某数机器码时,八进制数前面没有符号;如果有符号,则是对其所代表的真值取反。如?0123,0123代表的真值为83,?0123代表的常量则为?83。 (2)十六进制整型常量
十六进制整型常量的前缀为0X或0x。其数码取值为0~9,A~F或a~f。当十六进制数中出现a~f或A~F时,数符码中出现字母时字母的大小写应和前缀相匹配,即要么全部大写,要么全部小写。十六进制数主要也是用于表示某整型常量的机器码,所以当表示某数机器码时,十六进制数前面没有符号;如
6
果有符号,则是对其所代表的真值取反。如?0x23,0x23代表的真值为35,?0x23代表的常量则为?35。 (3)十进制整型常量
十进制整型常量没有前缀。 整型常量的几点说明如下。 ① 常量的类型
常量的类型可以根据整型常量描述的数值来确定其类型。
当整型常量的值为?32768~?32767时,可以看作基本型整型常量。 当整型常量的值为0~65535时,可以看作无符号整型常量。
当整型常量的值在大于32767或小于?32768时,可以看作长整型常量。 ② 整型常数的后缀
十进制无符号整型常量的范围为0~65535,有符号数为?32768~?32767。 八进制数表示的机器码范围为0~0177777。
十六进制数表示的机器码范围为0X0~0XFFFF。
如果希望将整型常量按照长整型常量来运算,可以使用长整型数的后缀“L”或“l”来表示。例如, 十进制长整型常量 158L 358000L 2.浮点常量
实型也称为浮点型。实型常量也称为实数或者浮点数。实型常量不分单、双精度,都按双精度double型处理,其有效位数为15~16位。 1)小数形式
小数形式由数码0~ 9和小数点组成。当某浮点数整数部分或小数部分为0时,0可以省略,但小数点不可省略。 例如:
0.0,.25,5.789,0.13,5.0,300.,?267.8230,?123.等均为合法的实数。 345(无小数点),a7.(数码不可以是字母)等均为非法的浮点数。 (2)指数形式
指数形式由尾数加阶码标志e或E以及阶码(只能为整数,可以带符号)组成。 其一般形式为a En(a为尾数,n为阶码)其值为 a×10n 例如,2.1×105可以表示为2.1E5,3.7×可以表示为3.7E?2 3.单字符常量
单字符常量是用单引号括起来的一个字符。单字符常量可以有如下几种表示方法。
(1)直接形式
直接形式即在单引号内直接书写字符。例如'a'、'b'、'?'、'?'、'?? (2)八进制形式
八进制形式格式为'\\ddd',其中“ddd”表示1~3位八进制数,其值代表的是某字符的ASCII值。“\\”是转义字符。八进制形式可以表示所有的字符。例如'\\101'(等于'A'),'\\007'(响铃控制字符),'\\343'(表示ASCII值为227的字符π)。
(3)十六进制形式
十六进制形式格式为'\\xhh',其中“hh”表示1~2位十六进制数,其值代表的是某字符的ASCII值,“\\”是转义字符,“x”是十六进制前缀。十六进制形式可以表示所有的字符。例如'\\x41'(等于'A'),'\\x07'(响铃控制字符),'\\xe3'(表示ASCII值为227的字符π)。 (4)转义字符
转义字符是一种特殊的字符常量。转义字符以反斜线“\\”开头,后跟一个或几个字符。转义字符具有特定的含义,不同于字符原有的意义,故称“转义”字符。
7
表2-5
转 义 字 符 \\n \\t \\v \\b \\\
常用的转义字符
转 义 字 符 \\r \\f \\\\ \\'
转义字符的意义
回车
走纸换页
反斜线符\
单引号符
转义字符的意义
回车换行
横向跳到下一制表位置
竖向跳格 退格 双引号
4.字符串常量
字符串常量是由一对双引号括起的字符序列,可以没有字符,也可以只有一个字符。例如,\,\, \,\,\,\等都是合法的字符串常量。 字符串 \在内存存储的形式如图2.1所示。
p r o g r a m \\0
该字符串占用8个字节的存储空间,但其有效字符的个数却是7,最后一个字节存储字符串结束标志“\\0”。 字符串的有效字符的个数是第一个“\\0”前字符的个数的总和,有效字符的个数也称为字符串长度。
例如:
\长度为8。 \长度为1。
\长度为5,而不是16,因为“\\0”后的字符是无意义的,“\\n”是一个转义字符,“\\345”是八进制表示的一个字符。
\长度为3,因为“\\\、“\\\\”与“\\'”分别为一个字符。 5.符号常量
在C语言中,可以用一个标识符来表示一个常量,称之为符号常量。符号常量在使用之前必须先定义,其一般形式为: #define 标识符 常量
其功能是把该标识符定义为其后的常量值。
例2.2
#define PI 3.14159 /*定义符号常量PI*/ main() {
float area,r; /*定义area,r为float型变量*/ r=5; /*使r的值为5*/
8
area=PI*r*r; /*计算以r为半径的圆面积*/ printf(\ /*输出圆面积*/ }
2)一个变量占据一定字节的存储空间。 在存储空间中存放变量的值。由于变量所表示的数据可以是除void以外的任意类型的数据,所以变量在内存中所占的存储空间根据数据类型的不同而不同。
每一个变量有而且只能有一种类型,变量的类型决定了该变量在内存中所占的字节数。在变量进行相关运算时,变量的类型是编译系统检查运算是否合法的依据。 3)一个变量在使用前必须有一个确定的值。
变量定义后,由于变量占据一定的内存空间,所以变量是有值的,但是该值是不确定。如果用该不确定的值参与运算,所得到的值也是不确定的。 2.变量定义的格式
变量定义的一般形式为:
[修饰符] 类型说明符 变量表列; [ ]是可选项
定义变量时应注意以下几点。
(1)变量定义应该位于函数体的数据描述部分。
(2)类型符说明变量从属的类型,有:int、float、double和char。 (3)修饰符部分可有可无,如int有long、short和unsigned修饰符。
(4)“[修饰符] 类型符”部分决定了变量的类型,确定了变量在内存中所占的字节数。
(5)变量表列部分是所定义变量的变量名,如果定义多个同一类型变量的话,变量名中间用“,”作为分隔符构成变量表列。
(6)变量定义的每一项应有空格作为分隔符。 (7)变量定义的结束符是分号。
3.变量的初始化
变量在使用前应该有确定的值。在程序中常常需要对变量赋初值,以便使用变量。 [修饰符] 类型说明符 变量1= 值1,变量2= 值2,……; 例如:
int a =5; /*初始化变量a为5*/
float x=3.2,y=1.2e10; /*初始化x为3.2,y为1.2x1010*/ char ch='K'; /*初始化ch为'K'*/ int d=5; /*初始化d为5*/
int b=d; /*初始化b为5,在int b=d;定义时d应有确定的值*/ 4.不同类型变量的说明
(1)整型变量
整型变量可分为基本型、短整型、长整型和无符号型4类。 ① 基本型
基本型的类型说明符为int,在内存中占2个字节,其取值的范围为?215~215?1,即?32768~32767。 ② 短整型
短整型的类型说明符为short int或short,所占字节和取值范围均与基本型相同。 ③ 长整型
长整型的类型说明符为long int或long,在内存中占4个字节,其取值范围是?231~231?1,即?2147483648~2147483647。 ④ 无符号型
无符号型的类型说明符为unsigned。无符号型又可与上述3种类型匹配而构成。 ? 无符号基本型。类型说明符为unsigned int或unsigned,取值范围是0~65535。 ? 无符号短整型。类型说明符为unsigned short,与无符号基本型一样。
? 无符号长整型。类型说明符为unsigned long int 或unsigned long,取值范围为0~232?1,即0~4294967295。
9
整型变量的说明形式为: [修饰符] int 变量表列;
(2)浮点变量
浮点变量分为单精度浮点型和双精度浮点型两类。单精度浮点型类型说明符为float,双精度浮点型类型说明符为double。 浮点变量说明的格式为: float变量表列; 或
double 变量表列;
例如:
float x,y; /*定义x,y为单精度浮点型*/ double a,b,c; /*定义a,b,c为双精度浮点型*/
float x=1234.56789; /*定义x为单精度浮点型,并初始化值为1234.567*/
(3)字符型变量
字符型变量的取值是字符型数据。字符型变量的类型说明符是char。 字符型变量类型说明的格式: char 变量表列; 例如:
char c1,c2; /*定义c1,c2为字符型*/
2.3 运算符及其表达式
2.3.1 C运算符概述
运算是对数据进行加工的过程,描述各种不同运算的符号称为运算符。参加运算的数据称为运算对象或操作数。用运算符和括号将运算对象连接起来的符合C语言语法规则的式子称为C语言表达式。 C语言的运算符不仅具有不同的优先级,而且还有结合性的特性。表达式中各运算对象参与运算的先后顺序不仅要遵守运算符优先级别的规定,还要受运算符结合性的制约,以便确定运算符是和左侧的运算对象结合,还是和右侧的运算对象结合。
2.3.2 算术运算符及算术表达式 1.算术运算符
C语言提供了5个基本的算术运算符:
? 加法运算符
- 减法运算符,负号运算符 * 乘法运算符 / 除法运算符
% 取余运算符或称模运算符
运算符?,?的优先级相同,*,/,%的优先级相同,*,/,%的优先级高于?和?运算符,算术运算符都是左结合的运算符。 2.算术表达式
用算术运算符和括号运算符将运算对象连接起来的符合C语言规则的式子,称为算术表达式。
算术表达式的计算符合运算符优先级和结合性的原则。计算表达式时的具体过程是:对表达式自左向右扫描运算对象,然后考察运算对象两侧的运算符。如果优先级不相同,则该运算对象和高优先级的运算符结合,当某运算符所需的运算对象全部都和该运算符结合后进行运算,运算后的结果就是下一个被处理的运算对象,否则继续扫描下一个运算对象;如果两侧的运算符优先级相同,则按照运算符的结合性原则去结合运算。如果运算符是左结合的,则和左侧的运算符结合,如果是右结合的,则和右侧的运算符结合。
2.3.3 自增自减运算符及含自增自减表达式
C语言中提供了两个特殊的运算符,自增运算符??和自减运算符??。它们都是单目运算符,运算
10
对象可以位于运算符前面,也可以位于运算符后面。当运算符位于运算对象前面时,称为前缀运算符,如?? i和??i;当运算符位于运算对象后面时,称为后缀运算符,如i??和i??。 自增自减运算符的功能是将运算对象加1或减1后,再将结果保存到运算对象中去,如i??等同于i?i?1。自增自减运算符的运算对象只能是变量。
当表达式中出现自增自减运算符时,表达式的求值过程如下。
(1)自增自减运算符是后缀运算符时,应先使用自增自减运算符的运算对象计算整个表达式的值,然后再计算自增自减。 例4.4 main() {
int i=2,j=3; int k;
k=(i++)+(j++)+4;
printf(\}
程序运行结果为: result:k=9,i=3,j=4
表达式k?(i??)?(j??)?4的运算可理解为:先运算表达式k?i?j?4,再计算i??和j??。
(2)自增自减运算符是前缀运算符时,应先完成自增自减,然后使用运算后的结果计算整个表达式的值。 例4.5 main() {
int i=2,j=3; int k;
k=(++i)+(++j)+4;
printf(\}
程序运行结果为: result:k=11,i=3,j=4
表达式k?(??i)?(??j)?4的运算可理解为:先运算??i和??j,再计算表达式k?i?j?4。
(3)当表达式中既含有后缀自增自减运算符,又含有前缀自增自减运算符时,应先计算所有的前缀自增自减运算,再计算整个表达式的值,最后计算所有的后缀自增自减运算。 例2.6 main() {
int i=2,j=3; int k;
k=(i++)+(j++)+4+(++i)+(++j);
printf(\}
程序运行结果为: result:k=18,i=4,j=5
表达式k?(i??)?(j??)?4?(??i)?(??j)的运算可理解为:先运算??i和??j,再计算表达式k?i?j?4?i?j,最后计算i??和j??。
注意:在运算的时候变量i,j是变化的,但在某瞬间值是确定的。
2.3.4 赋值运算符和赋值表达式 1.简单的赋值运算符和赋值表达式
C语言的赋值运算符是“?”,该运算符是双目运算符,它的优先级仅高于逗号运算符,是右结合性的。 由赋值运算符构成的赋值表达式的格式为: 变量?确定的值
赋值表达式会得到两个值。一个是赋值运算符左侧变量的值;另一个是赋值表达式的值,表达式的值与变量的值是相同的。
下列表达式是合乎C语言表达式规则的表达式:
11
a=123 a=123.456 c=a+'A' x=a+c
a+(x=a+4)/c /*算术表达式*/
(x=a)+(b=3) /*合法的算术表达式*/
2.复合的赋值运算符和复合的赋值表达式
在赋值运算符“?”前加上其他的运算符,可以构成复合的赋值运算符。如在“?”前加上“?”运算符就构成了“??”运算符。
C语言提供了10种复合的赋值运算符,它们是 +=,-=,*=,/=,%=,<<=,>>=,&=,^=,|=
其中前5个是复合的算术赋值运算符,后5个是复合的位运算赋值运算符。复合的赋值运算符是双目运算符,优先级和赋值运算符相同,也是右结合性的。 由复合的赋值运算符构成的赋值表达式的格式为: 变量 <运算符= >确定的值 它等效于
变量=变量 <运算符 >确定的值 例如:
a+=5 等价于a=a+5 x*=y+7 等价于x=x*(y+7) r%=p 等价于r=r%p
2.3.5 逗号运算符和逗号表达式
在C语言中逗号“,”也是一种运算符,称为逗号运算符。 逗号运算符的优先级是最低的。其功能是把两个表达式连接起来组成一个表达式,称为逗号表达式。
逗号表达式的一般形式为: 表达式1,表达式2
逗号表达式的求值过程是顺序求解表达式1、表达式2的值,并以表达式2的值作为整个逗号表达式的值。 例2.7 main() {
int a=2,b=4,c=6,x,y; y=(x=a+b),(b+c);
printf(\}
程序运行结果为: y=6,x=6
本例中的y等于6,而逗号表达式的值y?(x?a?b),(b?c)是10。 2.3.6 表达式小结
判断表达式类型应根据表达式中出现的运算符的优先级来判定。如果某一运算符在整个表达式的运算过程中优先级是最低的,或者是最后运算的运算符,那么表达式的类型就是该运算符所从属的类型。例如:
x=(a=3,b*3) 是赋值表达式; x=a=3,6*a 是逗号表达式; (x=8)>(y=9)+6 是关系表达式; (k=i++)/3*a 是算术表达式。
12
2.4 C语言中的类型转换
C语言规定不同类型的数据不能够直接进行运算,只有相同类型的数据才可以。但是在描述表达式时,参与运算的运算对象却可以是不同类型的数据,于是在表达式进行运算的时候存在类型的转换问题。C语言中有两种形式的类型转换。
2.4.1 类型的自动转换
1.赋值表达式中的类型转换
其转换的依据是将赋值运算符右侧的确定的值按照左侧变量定义的类型存储,即要把确定的值转换为变量定义的类型。
为了方便描述,以下所有的示例都假定有如下的变量说明: int a;
long int b; unsigned int c; float x; char c1;
(1)浮点型数据赋给整型变量
浮点型数据赋给整型变量转换的方法是舍去浮点型数据的小数部分,将整数部分赋给整型变量。 (2)整型数据赋给浮点型变量
整型数据赋给浮点型变量转换的方法是数值保持不变,只是将整型数据以浮点型数据的存储形式存储到相应的浮点变量中。 (3)字符型数据赋给整型变量
具体转换的方法是将字符型数据的ASCII码存储到整型变量的低字节中,整型变量的高字节的所有位存储的是低字节的“高位扩展”。如果低字节的最高位为0,则高字节的所有位全部扩展为0;如果低字节的最高位为1,则高字节的所有位全部扩展为1。 (4)整型数据赋给字符型变量
整型数据赋给字符型变量时的具体转换方法是将整型数据的低字节存储的内容存储到字符型变量中去,即“高位截断”。例如: c1=254
运算后c1的ASCII码值为254。 5)int型数据赋给long int型变量
int型数据赋给long int型变量时,具体转换的方法是将int型数据的值存储到long int变量的低字中。
(6)long int型数据赋给int 型或unsigned int型变量
具体转换的方法是将long int型数据的低字的内容存储到int型或unsigned int型变量中去 7)unsigned int型数据赋给long int型变量
具体转换的方法是将unsigned int型数据的值存储到long int型变量的低字中,long int型变量的高字的所有位存储的是0。
8)相同长度的整型数据赋给相同长度的整型变量
相同长度的整型数据赋给相同长度的整型变量转换的规则是存储形式不发生变化,但代表的真值不一定相同。
2.不同类型数据的混合运算
当某一运算符两侧的运算对象类型不相同时,应进行类型转换,转换的依据是低类型转换为高类型。
类型的高低是相对于某类型的数据在内存中所占的字节数而言的,占字节数少的数据类型就低,占字节数多的数据类型就高。在所有的数据类型中,字符型数据的类型是最低的,双精度浮点型的类型是最高的。
C语言规定在运算时char 数据必须转换为int型,float型数据必须转换为double型参与运算。float型数据
13
转换为double型的主要目的是为了提高运算的精度,因为double型提供的有效位数多于float型。这种情况下的类型转换称为必然的转换。
当类型不相同的数据参与运算时,应该将低类型的数据转换为高类型的数据,类型的高低依次是int→unsigned int →long→unsigned long int→double。这种情况下的转换称为可能的转换。低类型转化为高类型的转化规则按照赋值表达式转化规则转换。 例如,某函数的数据描述部分有: int a=3;
long int b=10; float x=4.2; 则表达式
a+'a'*b-'\\373'*x
的运算次序及类型转换情况为:先计算'a'*b,将'a'转换为整型数97,运算结果为970;再运算a?970,不存在类型转换,运算结果为973;然后运算'\\373'*x,'\\373'必然转换为整型?5(读者自己分析原因),x必然转化为双精度浮点型,?5和x类型不相同,存在可能的转换,将?5转换为双精度浮点型?5.0,运算结果为21.0;最后计算973?21.0,存在可能的转换,将973转换为双精度浮点型973.0,运算结果为952.0。表达式的结果为952.0,是双精度浮点型。 2.4.2 强制的类型转换
当自动的类型转换不能达到目的时,可以使用强制类型转换的方法。强制类型转换是通过类型转换运算来实现的。
类型转换运算符是: (类型符)
强制转换表达式的一般形式为: (类型符) (表达式)
其功能是把表达式的运算结果强制转换成类型说明符所表示的类型。 例如:
(float) a /* 把a代表的值转换为实型*/ (int)(x+y) /*把x+y的结果转换为整型*/
(int)x+y /*把x的值转换为int型,再和y相加*/ (int)x%5 /*把x的值转换为int型,再和5取余*/
14
第3章
顺序结构程序设计
3.1 语 句 3.2 库函数的使用 3.3 顺序结构程序设计 3.1 语 句 3.1.1 语句的概念
语句是是算法实现的程序表示,是算法实现的最小单位。语句说明了一种行为,它是用计算语言编写的控制计算机完成确定操作的句子。 3.1.2 C语句的分类
C语言将语句分为以下几类: (1)表达式语句; (2)流程控制语句; (3)函数调用语句; (4)跳转语句; (5)标号语句; (6)复合语句; (7)空语句。
3.1.3 语句和表达式的区别
任意类型的表达式都是有值的,而语句是向计算机发出的完成表达式运算的一个动作,语句是没有值的。表达式可以作为运算对象参与其他表达式的运算,而语句则不能。例如, x=3*(a=4)
是合法的表达式,而 x=3*(a=3;)
则不是合法的表达式。
3.2 库函数的使用
在C语言中函数占有及其重要的地位,这里首先来简单介绍一下函数的概念。函数是事先编写好的完成一定功能的程序段。
下面以Turbo C为例,讲述部分输入输出和数学库函数的使用。 3.2.1 库函数的使用
标准的库函数使用方法较为简单,但是由于库函数是存放在函数库中的,因此在使用时必须告诉计算机该库函数属于哪一个库,以便计算机及时查找并执行其程序体,这一过程一般称为函数的声明。 #include \main() {
float x,y; … y=sin(x); … }
其中#include \就是对函数的声明过程。
include 是C语言的特定字,是包含的意思,在使用前要加符号“#”。被包含的文件又称为头文件。
函数的使用称为调用,在函数调用后加“;”就构成了函数调用语句,函数也可以作为运算的运算对象。函数名括号里面的量值称之为参数,当给定这些参数的量值时,编译系统就可通过给定的量值计算出所需的结果
3.2.2 常用的输出函数
1.多种类型数据输出函数 printf()
15
printf(格式控制,输出表列)
该函数的功能是将输出表列中的数据按照格式控制的格式输出到标准输出设备。
第一部分称为格式控制,是用双引号括起来的字符串,包含普通字符和以“%”开头的格式控制字符两类成份构成;第二部分是需要输出的数据,可以任意多个,每个数据之间用逗号隔开。
格式字符和输出表列中的数据项具备一一对应关系,其作用是控制对应的输出项按照指定的格式输出;普通字符指的是除格式字符外的任意字符,可以用字符的各种表示形式去描述,其作用是按照该字符的含义输出。
格式字符种类较多,下面作较为详细的讲解。 (1)%c
%c用以输出单个字符。 例如:
printf(\
表示把字符'a' 以%c的格式输出到计算机的屏幕上。在用户屏幕上看到如下内容: The character is: a (2)%d
%d按照十进制形式输出整型数据。 例如:
printf(\ 屏幕上看到输出的结果为: The number is: 18 (3)%o
%o按照八进制格式输出整型数据。以该格式输出数据时,按照八进制的形式输出对应数据项的机器码。
(4)%x
%x按照十六进制格式输出整型数据。以该格式输出数据时,按照十六进制的形式输出对应数据项的机器码。 5)%u
%u按照无符号形式输出整型数据,把对应内存中的数据以无符号数的形式输出。
以上的5种格式字符既可用于输出字符型数据,也可用于输出整型数据。看下列的程序及运行结果。
例3.4 main() {
int a=65; char c='A';
printf(\ printf(\}
运行结果: A, 65 A, 65
以上的4种整型数据的格式字符前加上类型修饰符后可用于输出长整型数据,有%ld,%lo,%lx,%lu 4种格式。看下列的程序及运行结果。 例3.5
main() {
long int a=8,b=-1;
printf(\ printf(\}
程序运行结果为:
16
8,10,8,8
-1,37777777777,ffffffff,4294967295
若将上述程序改为: main() {
long int a=8,b=-1;
printf(\ printf(\}
将不能正确的输出结果,因为a和b是长整型变量,不能以整型格式输出。 以上5种格式均有含有域宽修饰的扩展形式。如:%±mc 、%±md、%±mo、%±mx、%±mu、%±mld、%±mlo、%±mlx、%±mlu等,其中m是一个直接形式的整型常量,是域宽修饰符,表示输出数据应当占用的列宽。如果数据实际输出所占列宽小于m,m表示左端补足空格,m前有负号时右端补足空格,如果数据实际输出所占列宽大于m,m不起作用。 例3.6 main() {
int a=-1;
printf(\}
程序运行后,输出如下结果:
□□□-1,177777□□□□,□□□□ffff (6)%s
%s输出字符串。%±m.ns格式为其扩展格式,+,?,m的含义和上述相同。n也是一个直接形式的常量,表示取字符串中的左起n个字符,n修饰符必须和“.”结合使用才是有意义的。字符串结束标志“\\0”是%s格式判断输出是否结束的标志。 例如:
printf(\ 其输出结果为:
China□□□□□China,Chi□□□□□ (7)%f 例3 8)%e
%e以指数形式输出浮点型数据,默认格式下尾数部分输出1位整数和5位小数,阶码部分输出符号及2位或者3位(阶码≥100时)阶码。%±m.ne格式为其扩展格式,n表示取尾数部分n?1位小数,m表示该浮点数输出所占的列宽,±表示m大于实际列宽时补空格的方式。 例如,以%e格式输出例5.7中的变量m: printf(\其输出结果为:
1.23457e+02, 1.23e+102□□□
(9)%g
按照输出时所占的列数,自动选取%f或%e格式输出符点型数据,选取的依据是输出所占列数的多少。 .7 main() {
float m=123.4567891;
printf(\}
其输出结果为:
123.456789,123.457□
(8)%e
17
%e以指数形式输出浮点型数据,默认格式下尾数部分输出1位整数和5位小数,阶码部分输出符号及2位或者3位(阶码≥100时)阶码。%±m.ne格式为其扩展格式,n表示取尾数部分n?1位小数,m表示该浮点数输出所占的列宽,±表示m大于实际列宽时补空格的方式。 例如,以%e格式输出例5.7中的变量m: printf(\其输出结果为:
1.23457e+02, 1.23e+102□□□
(9)%g
按照输出时所占的列数,自动选取%f或%e格式输出符点型数据,选取的依据是输出所占列数的多少。
2.字符格式输出函数putchar()
putchar()函数的功能是向屏幕输出一个字符。 该函数的一般格式为: putchar( 参数)
一般来说该函数是作为独立的函数调用语句来实现对其的调用。putchar()只有一个参数,此参数可以是常量,可以是变量,也可以是任意整型表达式,参数的值代表的是某字符对应的ASCII码值。 调用该函数之前必须加上函数的声明部分:#include \例3.11
#include \main() {
char c='A';
putchar(c); /*语句Ⅰ*/
putchar('\\t'); /*输出转义字符?\\t?的功能,转到下一个输出区*/ putchar('A'); /*语句Ⅱ*/ putchar('\\t');
putchar(65); /*语句Ⅲ*/ putchar('\\n'); }
3.2.3 常用的输入函数
1.多种类型数据输入函数scanf()
scanf()函数的功能是从标准的输入设备读取各种类型的数据到相应的变量中。
首先介绍一个取地址运算符&,该运算符是单目运算符,运算对象一般来讲是变量,其作用是得到参与“&”运算的运算对象的地址,如“&a”则得到变量a的地址。 该函数的一般格式为: scanf(格式控制,地址表列);
第一部分为格式控制部分,其构成和printf()一样,scanf()函数可以使用的格式字符可参见表5-3,普通字符是需要原样输入的字符,格式字符和地址表列中对应的变量具备一一对应关系,格式字符控制的不是地址,而是该地址对应的变量的类型;第二部分为对应变量的内存地址,是一个地址表列。
例如,我们要把输入的3个整数存储到a、b、c这3个变量中,假设a、b、c为整型变量,具体用法为:
scanf(\ 使用scanf()应注意以下问题。
(1)当格式控制部分中相邻的两个格式字符不含有普通字符时,在输入的时候可以采取scanf()函数默认的分隔方式。 (2)当格式控制部分中相邻的两个格式字符包含%c格式时,输入时不能以空格、Tab键或回车键加以区分,因为空格、Tab键或回车键本身也是字符,只能严格按照格式控制规定按数据类型加以区分某项数据是否输入结束。 (3)在scanf函数中允许使用域宽m来控制输入,m表示的是输入数据所占的列宽,实际输入不足m位时,可在输入的数据左侧或右侧加空格。
(4)修饰符*。*的含义是“跳过”,表示在地址表列中没有对应的控制项,但在输入时必须输入数据。 (5)程序中如果有多个scanf(),系统会将多个scanf()结合为一个函数来处理,所以读者在使用时要多加
18
练习,灵活掌握。
(6)格式控制后最好不要加字符'\\n',否则在输入时容易引起不便。
(7)通常为了使程序在输入数据时易于操作,可使用下列的方式来提高程序的可读性。 例3.14 main() {
int a,b; float x,y;
printf(\ scanf(\ scanf(\}
在使用scanf()函数时应首先在输入之前书写一个printf()函数,给出应输入的数据个数、类型及分隔方式的提示信息。读者可以使用易于理解的提示,增加程序的可读性。
2.字符读取函数getchar()
字符读取函数为无参函数,其功能是从键盘读入一个字符。
调用该函数之前必须加上对于函数的声明部分:#include \。 例3.15
#include \main() {
char c;
c=getchar(); putchar(c);
putchar(getchar()); }
格 式 字 符
d o x c s f e 修饰符 m * l
用于输入十进制数
用于输入八进制数
用于输入十六进制数
用于输入单个字符
用于输入字符串(非空格开始,空格结束,字符串以'\\0'结尾)
用于输入实数(小数或指数均可)
与f相同(可与f互换)
域宽控制
跳过
用于长整型数(%ld、%lo、%lx)或double型实数(%lf、%le)
说 明
19
3.2.4 数学函数
C标准库函数提供了大量的数学函数以供使用,数学函数均为有参有返回值函数,使用时应注意函数的用法和使用环境。
1.开方函数
开方函数的一般格式为: double sqrt(double x)
其作用是求参数x的开方值,参数为双精度浮点型数据,返回值为双精度浮点型数据。如对4开方可编写如下程序。 例3.16
#include \main() {
float a;
a=sqrt(4.0); printf(\}
3.3 顺序结构程序设计
例3.18 已知三角形的三边长为4,6,7,求三角形的面积。
分析:该例题已知三角形的三边长,我们可以通过多种方法求得其面积。 算法三:
上例可以做如下处理,定义三个边长分别为a、b、c三个变量,利用scanf()函数从键盘读取三边的具体数值,从而扩展程序的通用性。可得到如图5.4所示的流程图。 输入a、b、c的值
t=(a+b+c)/2
s=t(t?a)(t?b)(t?c) 输出s 根据算法三编写程序: #include \main() {
float a,b,c,t,s;
pritnf(\
scanf(\ t=(a+b+c)/2;
s=sqrt((t*(t-a)*(t-b)*(t-c)); printf(\ }
例3.19 将任意小写字母,转换为对应的大写字母并输出。
小写或大写字母均为字符,字符参与运算时实际是将字符转换为整型数据运算。根据ASCII表规律可知对应大小写字母ASCII值相差32。可得到如图5.5所示的流程图。 程序如下:
#include "stdio.h" #include "stdio.h" main() main() { {
char c; int c;
20
c=getchar(); c=getchar(); c=c-32; c=c-32; putchar(c); putchar(c); } }
21
第4章
选择结构程序设计
选择结构的N-S流程图如图4。1所示
1 P 0
A B
P为选择的条件,对于执行A还是B要根据条件P的判断结果来决定,如果条件P的结果为真则执行A,如果条件P的结果为假则执行B。这里条件P的结果就成为执行A还是B的关键所在。P是一个判断的条件,其结果为真或者假,将其称为“逻辑量”。C语言中没有提供逻辑型变量,可以使用整型数据去描述1表示真,0表示假。在C语言中任意确定的值都可作为逻辑量处理,当作为逻辑量处理时,如果该值为非0,则表示真(1),如果该值为0,则表示假(0)。下面讲述条件P的逻辑量表示方法。 4.1 逻辑量的表示方法 4.2 if语句 4.3 switch语句
4.4 选择结构程序设计举例
4.1 逻辑量的表示方法
C语言中逻辑量是用整型数据来表示的,用1来表示真或成立,用0来表示假或不成立。逻辑量的表示是非常灵活的,具体表示方法有以下几种方式。 4.1.1 关系表达式 1.关系运算符
关系运算是一种比较运算符两侧运算对象大小的运算,完成两个运算对象比较,运算结果为成立与不成立,用1和0表示。
关系运算符有以下6种:
>,>=,<,<=,==,!=
关系运算符是双目运算符。其中前4种的优先级相同但要高于后2种,后2种优先级相同。所有6种运算符的优先级都高于赋值运算符,但都低于算术运算符。此6种关系型运算符均为左结合性。 2.关系表达式
用关系运算符将运算对象连接起来的合乎C语言规则的表达式称为关系表达式。 例如:
a>b,3+7!=10,a>'a' a++>(b=a++) 都是合法的C语言关系表达式。
关系表达式的求解遵循表达式求解规则,关系运算的结果只有两种可能,要么关系成立为真(1),要么关系不成立为假(0)。
例如有整型变量a,b,且a?3,b?5, 求解表达式:a?b>'a'
算术运算符“?”的优先级高于关系运算符“>”,因此先计算a?b的值为8,之后运算8>'a',而'a'参加运算时需转换为整型数97运算,实际运算的是8>97,判断后其结果为不成立,表达式的运算结果为0。 若有关系表达式0 4.1.2 逻辑运算和逻辑表达式 22 1.逻辑运算符 C语言中逻辑运算符一共有3个:&&,||和!,其中“!”运算的优先级最高,仅次于小括号,为左结合性。“&&”的优先级高于“||”,它们的优先级都低于关系运算符,高于赋值运算符。 2.逻辑表达式 用逻辑运算符将逻辑量连接而形成的合乎C语言规则的表达式称为逻辑表达式。 参与逻辑运算的运算对象是逻辑量,在C语言中任意表达式都可以作为逻辑量来处理,具体处理规则是表达式值非0则为1,否则为0。逻辑运算的结果和关系运算一样,也是逻辑量。 例如有整型变量a?3,b?4,c?5,求解表达式!(x?a)&&(y?b)||0。 x?a为赋值表达式,其值为变量的值。 原式?!3&&4||0 !3为逻辑运算要把3转化为逻辑量1 ?0&&4||0 0&&4中要把4转化为逻辑量1 ?0&&1||0 ?0||0 ?0 4.1.3 实际问题中逻辑量的描述 (1)图6.4所示x轴上阴影区间的数据描述 -1 0 1 2 x 从该图可知,x的值应该在?1到2之间,其值既要满足x>=?1又要满足x<=2,即x>=?1和x<=2同时成立,两个过程同时成立其结果才成立,此运算为逻辑与,可得表达式x>=?1&&x<=2。注意,?1>=x<=2是一个关系表达式,不能表示该区间。 (3)闰年问题 假定闰年为year,判定是否闰年的标准为:如果该年能被4整除且不能被100整除,或者该年能被400整除,则都为闰年。能被4整除且不能被100整除,可表示为 year%4??0&&year0!?0; 能被400整除可表示为year@0??0。两者只要有一者成立即为闰年,是逻辑或关系,闰年问题的表达式为 year%4??0&&year0!?0||year@0??0, 也可表示为!(year%4)&&year0||!(year@0 4.2 if语句 4.2.1 if语句的格式 if语句的格式为: if(逻辑量) 语句1 [else 语句2 ] if和else是构成if语句的关键字,语句中的逻辑量是选择结构的条件,[ ]表示else分支可有可无。 if语句的N-S流程图如图6.5所示。 语句1和语句2都只能是单条语句,如果在为真或为假的分支中需执行多个操作,应该使用复合语句将多个操作构成一条语句。语句1是选择结构必需的语句,else分支可有可无。 if语句是实现选择结构算法的具体语句 P 1 0 A B 使用if语句实现选择结构时,语句中的逻辑量对应于选择条件P,语句1对应于A操作,语句2对应于B操作。if语句执行时,首先判断逻辑量的值,如果逻辑量为1,执行语句1;如果逻辑量为0,执行else分支对应的语句2。 例4.2 求出两整数中的最大值。 输入m,n Y N 输出n m>n 23 输出m 根据流程图得到程序: main() { int m,n; scanf(\ if(m>n) else printf("The max is %d\\n",n); } 使用if语句要注意以下几点。 (1)if语句是一条语句。 (2)逻辑量是if语句选择判断的条件,C语言中任意确定的值都可以作为逻辑量处理。 (3)逻辑量为1和为0的分支都只能是单条语句,如果要执行多个操作的话,应该将多个操作复合为单条语句才能出现在if的分支结构中。 (4)分支中出现的语句1是复合语句的时候,{}后不应该有“;”。 (5)语句中语句1是条件语句中必不可少的部分。 (6)正确理解if语句和N-S选择结构流程图的对应关系。 4.2.2 if语句的嵌套及多条件结构的实现 嵌套主要用于处理多条件的题目。设计嵌套选择结构时,应清晰描述各条件之间的约束关系。嵌套的if语句可以简单描述如下: 嵌入的if语句是在当外围的if语句的条件p1的逻辑量值为1时才可以执行,即其作为外围if语句的条件成立时的执行语句,嵌入在外围if内。当然语句1、语句2、语句3也可以是if语句,具体的嵌套形式和具体题目中的多条件是密切相关的, if语句的嵌套形式应建立在对具体问题的分析上。 例4.4 求函数中x为任意值时 x?0?1/x y? ? 0 的值。 x ?? 0 ??1/2xx?0? 由于if选择结构仅有两个分支,而此分段函数x的定义域为三分支,因此当确定x的关系表达式时,其中必然有一个分支包含另外两部分定义域,需在此基础上作进一步的条件判断。 图4.9 例4.4的流程图1 图4.10 例4.4的流程图2 输入x 输入x Y x>0 N Y x>0 N Y x??0 N Y x<0 N y?1/x y??1/2x y?/x y?0 y?0 y??1/2x 输出y 输出y 根据流程图得到如下程序: main() main() { { float x,y; float x,y; scanf("%f",&x); scanf("%f",&x); if(x>0) y=1/x; if(x>0) y=1/x; else if(x==0) y=0; else if(x<0) y=-1/(2*x); else y=-1/(2*x); else y=0; printf("%8.3f\\n",y); printf("%8.3f\\n",y); 24 } } 4.2.3 条件表达式 条件运算符由两个符号“?”和“:”复合而成,此运算符为C语言中惟一的一个三目运算符,其优先级只比赋值运算符和逗号运算符高,结合性为从右到左。 条件表达式的一般格式为: 表达式1?表达式2:表达式3 其中表达式1是作为逻辑量处理的。 条件表达式中由于有条件限制,所以应先进行条件表达式的运算,根据条件表达式1表示的逻辑量值选取表达式2或表达式3的结果作为整个表达式的值。 其执行过程如下。 当表达式1的逻辑量值为1时,选取表达式2的结果作为整个表达式的值。 当表达式1的逻辑量值为0时,选取表达式3的结果作为整个表达式的值。 假定a?5,b?3,则表达式max? (a>b)?a:b 的求解过程如下:此表达式右边为一条件表达式,由于条件运算符的优先级高于赋值运算符,应先计算条件表达式的值,再通过赋值运算符把得到的值赋给max。在条件表达式中,首先计算表达式a>b的逻辑量值,根据题意,a>b表达式的逻辑量值为1,则a为条件表达式的结果,为5,那么条件表达式的值为5,可得max的值为5。 4.3 switch语句 如果题目中的多条件是有规律的,则可以采用switch语句来实现。switch语句称为分支语句,又称为开关语句。 switch的具体形式如下: switch(表达式) { case 整型常量表达式1:语句组1 [break] case 整型常量表达式2:语句组2 [break] case 整型常量表达式n:语句组n [break] default :语句组n+1 } switch,case,default和break都是构成多分支语句的关键字。[ ]表示break可有可无。 其中表达式是任意类型的表达式,但运算结果会自动转换为整型。整型常量表达式只能由整型常量构成。break语句的作用是结束switch语句,执行switch的后续语句。语句组可以是单条语句,也可以是多条语句,多条语句无需用复合语句去表示。而在if~else结构中的语句1和语句2只能是单条语句。 switch语句中的一对花括号是必须书写的,是switch语句构成的必要部分。 switch语句的具体执行过程为:根据switch表达式的值,寻找switch语句的执行入口。自上而下和case后的整型常量表达式的值进行比较,如果相等则执行其后的语句组,假定入口是整型常量表达式2,那么该语句执行语句组2,当语句组2执行完毕后,若有break语句,则中断switch语句的执行,否则继续执行语句组3;如果没有和表达式的值相匹配的整型常量表达式,则执行default后的语句组。 case后的整型常量表达式的值实际上就是switch后括号内的表达式的各种可能的取值。如果能穷尽表达式各种可能的取值,则语句中可省去default分支;否则最好不要省略default,因为default表示的是switch语句在没有找到匹配入口时的语句执行入口。 例4.6 输入一同学的成绩,判断其成绩等级。 等级范围为: 90 以上 等级为A 89~80 等级为B 79~70 等级为C 69~60 等级为D 60 以下 等级为E 假定成绩为score,可以得到表达式(int)(score/10)。当表达式的值为10和9时,对应于90分以上的条件分支,为8时对应于89~80分段的条件分支,以下的取值和对应的分支可以依次类推。60分以下可用switch中default分支来描述。程序如下: main() { float score; scanf("%f",&score); 25 switch(score/10) { case 10: case 9 : printf("Your score is A\\n"); break; case 8 : printf("Your score is B\\n"); break; case 7 : printf("Your score is C\\n"); break; case 6 : printf("Your score is D\\n"); break; default : printf("Your score is E\\n"); } } score/10中score得到的结果为浮点型,系统会自动转换为整型。常量表达式为10的分支,由于与9的分支均为A级,利用switch的特点可以不写值为10的分支对应的语句。 使用switch语句应注意以下几点。 (1)switch 语句中表达式可为任意类型,但运算结果为整型。case后的表达式必须是整型常量表达式。 (2)每个case后的常量表达式的值不能相同,否则会自相矛盾,无法判断。 (3)case及default的顺序对运行结果不产生影响。 (4)若无break语句进行switch语句的强制跳出,则从该处顺序执行其余语句,直至跳出或执行结束。 (5)case和其后的整型常量表达式中间应有空格 4.4 选择结构程序设计举例 例4.10 批发钢材,每吨批发金额为1000元,计算批发金额,批发折扣如表6-3所示。 表4-3 例4.10折扣表1 表4-4 例4.10折扣规律表1 批发量(吨) 折扣金额(%) main() { float t,d,m; scanf(\ if(t<50) d=0; else if(t<100) d=5./100; else if(t<250) d=8./100; else if(t<500) <50 <100 <250 <500 ≥500 0 5 8 10 15 批发量(吨) <50 <100 <250 <500 ≥500 t/50相除结果(取整) 0 1 2、3、4 5、6、7、8、9 其他 26 d=10./100; else d=15./100 m=t*1000*(1-d); printf("%.0f\\x9d\\n",m); /*\\x9d是字符¥的ASCII码*/ } main() { float t,d,m; scanf("%f",&t); switch(t/50) { case 0: d=0; break; case 1: d=0.01; break; case 2: case 3: case 4: d=0.08; break; case 5: case 6: case 7: case 8: case 9: d=0.1; break; default : d=0.15; } m=t*1000*(1-d); printf("%.0f\\x9d \\n",m); } 27 第5章 循环结构程序设计 5.1 循环的概念 5.2 实现循环的语句 5.3 break和continue语句 5.4 循环的嵌套 5.5 循环结构程序设计举例 5.1 循环的概念 当所要解决的问题存在重复执行内容时,应该使用循环结构来实现,具体的设计步骤可归纳如下。 (1)构造循环体。将问题中需要重复执行的部分,利用C语言规则归纳出一组程序段。在归纳的过程中应充分利用变量是一个变化的量的概念。如本列中变量score在不同的时刻代表的是不同学生的成绩。 2)寻找控制循环的变量。有的题目循环的次数是确定的,可以使用计数器来控制循环;有的题目循环的次数不是确定的,那么使用计数器就不合适了,设计者应从题目中去寻找规则变化的量来控制循环体完成规定的次数。 (3)找出控制变量的3个要素。 ? 循环控制变量的初值。 ? 循环的条件。 ? 使循环趋于结束的部分。 5.2 实现循环的语句 5.2.1 while语句 while语句是实现当型结构循环的语句,其一般形式为 while(逻辑量) 循环体语句 其中,while是C语言的关键字,语句中的逻辑量为循环条件。 while语句的流程图如图7.2所示。 N 条件P 循环条件(P) Y 循环体A 循环体() 使用while语句实现循环时,语句中的逻辑量对应于循环条件PA,循环体语句对应于循环体A。while 语句执行时,首先判断逻辑量的值,如果逻辑量为1,则首先执行循环体语句,然后继续判断逻辑量;如果逻辑量为0,则结束循环,执行循环的后续语句。 例5.3 求1到100的和。 根据流程图写出程序: sum=0,i=1 main() { i<=100 int i,sum; sum=sum+i sum=0; i++ i=1; while(i<=100) 输出累加和 sum { sum=sum+i; i++; 28 printf(\ } } 例5.4 求n!。 main() fac=1,i=2 { float fac; 当i<=n时 int i,n; fac=1; fac=fac*i i=2; scanf(\ i++ while(i<=n) { 输出阶乘fac fac=fac*i; i++; } printf(\} 5.2.2 do-while语句 do-while语句是实现直到型循环结构的语句,其一般形式为: do 循环体语句 while(逻辑量); do-while语句的流程图如图5.6所示。 循环体(A) 循环体A Y循环条件(P) 条件P N 使用do-while语句实现循环时,语句中的逻辑量对应于循环条件P,循环体语句对应于循环体A。do-while语句执行时,首先执行循环体语句,然后判断逻辑量,如果逻辑量为1,则继续执行循环体语句;如果逻辑量为0,则结束循环,执行循环的后续语句。 例5.9 求两个数的最大公约数 输入 m,n 输入m,n m Y N Y N m?n m?n r=m%n r=m%n r!=0 m=n,n=r m=n,n=r r=m%n r!=0 输出最大公约数 n 输出最大公约数m (a)当型结构 29 (b)直到型结构 图5.8 例子的两种结构的N-S图 根据流程图可得到程序: main() main() { { int m,n,r; int m,n,r; scanf(\ scanf(\ if(m r=m,m=n,n=r; r=m,m=n,n=r; r=m%n; do while(r!=0) { { m=n; r=m%n; n=r; m=n; r=m%n; n=r; } }while(r!=0); printf(\ printf(\ } } 5.2.3 for语句 for语句是实现当型循环结构的语句,for语句是C语言所提供的功能更强、使用更广泛的一种循环语句。其一般形式为: for(表达式1;逻辑量;表达式2) 循环体语句; 表达式1通常用来给循环变量赋初值,一般是赋值表达式。也允许在for语句外给循环变量赋初值,此时可以省略该表达式。逻辑量通常是循环条件,一般为关系表达式或逻辑表达式,也可以是任意“确定的值”。表达式2通常是使循环趋于结束的部分,一般是赋值表达式。 求解表达式1 for(表达式1;逻辑量;表达式2) N 逻辑量 循环体语句 求解表达式2 Y 循环的后循环体语句 续语句 图5.9 for 语句的传统流程图 图5.10 for语句的N-S流程图 5 2(xx )d例5.11 求 ? 0 ? x main() sum?0 { 输入h float sum=0,s=0; float x,h; for(x?0;x<5;x?x?h) scanf(\ 22 for(x=0;x<5;x=x+h) s?h[x?x?(x+h)?(x?h)]/2 { sum?sum?s s=(x*x+x+(x+h)*(x+h)+x+h)*h/2 输出积分sum 30 sum=sum+s; } printf(\} 使用for语句应注意以下几点。 (1)for语句中的各表达式都可省略,但间隔符分号不能省略。 例如: for(;逻辑量;表达式2) 省去了表达式1。 for(表达式1;;表达式2) 省去了逻辑量。 for(表达式1; 逻辑量;) 省去了表达式2。 for(;;) 省去了全部表达式。 (2)在循环变量已赋初值时,可省去表达式1。 例5.12 main() { int a,n; a=0; scanf(\ for(;n>0;n--) { a++; printf(\ } printf(\} (3)可省去表达式2,应该在循环体部分增加使循环趋于结束部分。 例5.13 main() { int a,n; a=0; for(n=9;n>0;) { printf(\ n--; } printf(\} (4)省略逻辑量,则循环条件为真,那么在循环体中应有循环结束语句,否则是死循环。(5)省略表达式1和表达式2,for语句相当于while语句。 例5.14 main() /*计算xn */ { float x,xexpn; int n,i; scanf(\ xexpn=1; i=1; for(;i<=n;) /*等同于while(i xexpn*=x; i++; } 31 printf(\} 6)循环体可以是空语句。 例5.15 #include \main() { int count; count=0; printf(\ for(;getchar()!='\\n';count++); printf(\} 无论for语句如何省略括号内的内容,循环的三个要素是必须要体现的,所以在使用for语句时,要充分理解for语句的执行过程。 例5.17 判断某个一位数是否在一个不高于四位的整数中出现。 flag=1(假定n没有在m中出现) 输入 m,n 当m!=0时 图512 例5.17的N-S流程图 n== m Y N flag=0 break m=m/10 输出 flag 32 main() { int m,n; int flag; flag=1; scanf(\ while(m!=0) { if(n==m) { flag=0; break; } m=m/10; } if(flag==0) printf(\ else printf(\} 5.3.2 continue语句 continue语句只能用在循环体中。 其一般格式是: continue; continue语句的功能是结束本次循环,即不再执行循环体中continue 语句之后的语句,转入下一次循环执行。应注意的是,该语句只结束本层本次的循环,并不跳出循环。 使用continue语句时,应该清楚在不同的循环语句中下次循环的起点是什么。在while和do-while语句中,下次循环的起点是判断逻辑量;在for语句中,下次循环的起点是计算表达式2的值 例5.18 输出1~1000之间能被3、5、7同时整除的数。 main() { int n; for(n=1;n<=1000;n++) { if (n%3!=0) continue; if (n%5!=0) continue; if (n%7!=0) cntinue; printf(\ } printf(\} 5.4 循环的嵌套 当一个循环体内又包含另一个循环结构时,称为循环的嵌套。被嵌入的循环又可以嵌套其他的循环,这就是多重循环。 例7.19 分析以下循环内外层循环控制变量的变化。 main() { int i,j; int mul; for(i=1;i<10;i++) { for(j=1;j<10;j++) 33 { mul=i*j; printf(\ } printf(\ } } 程序运行的结果为: 1*1=1 1*2=2 1*3=3 1*4=4 1*5=5 1*6=6 1*7=7 1*8=8 1*9=9 2*1=2 2*2=4 2*3=6 2*4=8 2*5=10 2*6=12 2*7=14 2*8=16 2*9=18 3*1=3 3*2=6 3*3=9 3*4=12 3*5=15 3*6=18 3*7=21 3*8=24 3*9=27 4*1=4 4*2=8 4*3=12 4*4=16 4*5=20 4*6=24 4*7=28 4*8=32 4*9=36 5*1=5 5*2=10 5*3=15 5*4=20 5*5=25 5*6=30 5*7=35 5*8=40 5*9=45 6*1=6 6*2=12 6*3=18 6*4=24 6*5=30 6*6=26 6*7=42 6*8=48 6*9=54 7*1=7 7*2=14 7*3=21 7*4=28 7*5=35 7*6=42 7*7=49 7*8=56 7*9=63 8*1=8 8*2=16 8*3=24 8*4=32 8*5=40 8*6=48 8*7=56 8*8=64 8*9=72 9*1=9 9*2=18 9*3=27 9*4=36 9*5=45 9*6=54 9*7=63 9*8=72 9*9=81 程序输出的是一个完整的九九乘法表。控制外层循环的循环控制变量是i,控制内层循环的循环控制变量是j。每当i变化一次,j就要从1变化到9,可见内层循环的变化频率是高于外层循环的。在内层循环的循环体内使用的i,j的值正好是i(1~9),j(1~9)的各种组合,那么就可以使用嵌套的循环描述组合问题 5.5 循环结构程序设计举例 2 43xxx例5.20 求ex?1?x? ? ? +…… 2!4!3! fac=1,n=1,xn=1,ex=1 输入 x fac=fac*n; xn=xn*x ex=ex+xn/fac n++ xn/fac>10e-6 输出 ex main() float x,fac,ex,xn; int n; fac=1; ex=1; n=1; xn=1; scanf(\ do { fac=fac*n; xn=xn*x; ex=ex+xn/fac; n++; }while(xn/fac>10e-6); printf(\} 例5.21 求Fibonacci数列的前20项之和。 34 分析:本例是一种“递推”的题目。所谓“递推”是指在前面一个(或几个)结果的基础上推出下一个结果的方法。 f1=1,f2=1,f3=f1+f2 sum=f1+f2+ f3 for(i=3;i<=20;i++) t=f2,f2=f3,f1=t f3=f1+f2 sum=sum+f3 输出sum 根据流程图编写程序: main() { float sum; int f1,f2,f3; int i,t; f1=1; f2=1; f3=f1+f2; sum=f1+f2+f3; for(i=4;i<=20;i++) { t=f2; f2=f3; f1=t; f3=f1+f2; sum+=f3; } printf(\} 35 第6章数组 61 数组的概念 6.2 一 维 数 组 6.3 多维数组的定义和使用 6.4 字符数组及字符串 6.5 数组作函数参数 6.1 数组的概念 数组是指一组相同类型数据的有序集合,由一个统一的数组名标识这一组数据。数组的每一个成员称作数组元素,用下标来标识数组元素在该数组中的位置 把数学中的集合和数组对照一下。 假如有集合 s={1,2,3,4,5,6,7,8,9,10} 要表示集合中某个元素,数学中可以描述为si ,其中s是集合的名字,i表示该元素在集合 中的序号。为了表示C语言数组中的每一个元素,也必须确定数组名和该元素在数组中的位置信息。 数组名和该元素在数组中的位置信息是确定数组元素两个不可缺少的要素。在C语言中,数组元素的位置信息是由下标表示的,下标等同于集合中的序号。 6.2 一 维 数 组 6.2.1 一维数组的定义 由单一下标标识数组元素的数组称为一维数组。 一维数组的定义形式为: 类型标识符 数组名[元素个数]; 数组名必须符合标识符的规定。元素个数,又称为数组长度,只能用整型常量表达式表示。 数组和变量一样,也必须占据一定的存储空间。进行存储空间分配时,系统根据数组的长度和数组元素的类型确定分配存储单元的个数,C语言中数组元素的存储是连续的,数组名就是所分配内存空间的起始地址。 如整型数组a,共有10个元素,每个元素分配2个字节,共分配2×10个字节的存储单元, ? 2000H 56 第0个元素 2001H 2002H 100 第1个元素 2003H ? ? ? 2012H 78 第9个元素 2013H ? 6.2.2 一维数组元素的初始化 数组初始化的一般形式如下: 类型标识符 数组名[元素个数]={元素值表列}; (1){}中的数组元素值与数组的每一个元素具备左对应关系。 36 (2){}中的元素值表列,可以是数组所有的元素的初值,也可以是前面部分元素的初值,元素值之间使用逗号分割。 (3)当给数组部分元素赋初值时,其余元素的值为0。 (4)当对全部元素赋初值,数组长度可以省略,但[ ]不能省略。 (5)数组初始化的赋值方式只能用于数组的定义,定义之后再赋值只能一个元素一个元素地赋值。 6.2.3 一维数组元素的引用 定义一个数组,相当于一次定义了许多变量。 一维数组元素的引用形式: 数组名[下标] 例如:a[0]、a[k]、a[i+1]等。 下标可以是整型常量、整型变量或者整型表达式,其范围是0~L ?1(L表示数组长度)。 C语言不检查数组的边界。所以当引用数组元素的下标超越边界时,系统不会提示错误,但是可能导致其他变量甚至程序被破坏。 例6.1 数组元素引用示例。 示例1: main( ) { int i, a[10]; for (i=0;i<10;i++) scanf(\%d\ for (i=0;i<10;i++) printf(\%5d\ printf(\} 6.2.4 一维数组举例 使用一维数组可以实现实际应用中的多种数学模型,如队列、数列、堆栈等数据结构。在一维数组中,数组中的各个元素都是相关联的,它们是相同数据类型的数据的集合。在实际应用中,程序设计者应注意分析数据间的关系,合理地使用数组元素,才能编制出高效率的程序。本节从下面几个例子介绍一维数组的编程方法。 例6.2 利用数组求出Fibonacci数列的前40项。 在循环一章中讲述了利用变量计算Fibonacci数列之和的题目。数列的数据模型常用数组来表示。 main( ) { int j; float f[40]; f[0]=1;f[1]=1; for(j=2;j<40;j++) f[j]=f[j-1]+f[j-2]; for (j=0;j<40;j++) printf(\%10.0f\ printf(\} 例6.4 输入10个整数,输出最大值。 37 main() { int array[10],max,k; for(k=0;k<10;k++) scanf(\%d\ max=array[0]; for(k=1;k<10;k++) if(max printf(\%d]=%3d\ printf(\ printf(\%3d\\n\} 例6.5 用选择法对N个数排序 #define N 10 输入10个数据到a数组中 main( ) { i=1 ~ 9 int a[N],min,temp,i,j; min=i-1 for (i=0;i j=i ~ 9 scanf(\%d\ a[min]>a[j] for(i=1;i Y { N min=i-1; for(j=i;j a[i-1] a[min] temp=a[min];a[min]=a[i-1];a[i-1]=temp; } 输出排序后的数组a for(i=0;i printf(\%5d\ printf(\} 6.3 多维数组的定义和使用 6.3.1 多维数组的定义 1.二维数组的定义 二维数组的定义形式为: 类型标识符 数组名[行元素个数][列元素个数]; 行元素个数是数组的行数(假定用M表示),列元素个数是数组的列数(假定用N表示)。 例如: int a[2][3]; 表示数组a由2行3列共6个整型元素组成。 二维数组中元素的存放顺序是按行存放的,即按顺序先存放第0行的数组元素,再存放第1行的数组元素。 2.多维数组的定义 多维数组的定义类似二维数组的定义,格式为: 类型标识符 多维数组名[元素个数1][元素个数2]…[元素个数n]; 例如, float a[10][20][30]; 6.3.2 二维数组元素的初始化 所谓初始化就是给数组元素赋初始值。 38 二维数组的初始化方法有以下几种形式。 (1)按行对二维数组初始化。 例如: int a[2][3]={{1,1,1},{4,4,4}}; (2)按数组的存储顺序赋初值。 例如: int b[2][2]={1,2,3,4}; 表示将1初始化给b[0][0],2初始化给b[0][1],3初始化给b[1][0],4初始化给b[1][1]。 二维数组初始化时,注意以下几点。 (1)对部分元素赋值,其余元素的值为0。 (2)如果对数组元素全部赋初值,定义数组时行长度可以省略,列长度不能省略。 (3)同一维数组一样,二维数组初始化的赋值方式只能用于数组的定义,定义之后再赋值只能单个元素赋值。 6.3.3 多维数组元素的使用 1.二维数组中元素的引用形式 数组名[下标1][下标2] 程序运行的结果如下: array x: 0 0 0 0 1 1 1 1 2 2 2 2 array y: 0 1 2 3 0 1 2 3 0 1 2 3 例6.8 分别求某班数学成绩、语文成绩的最高分、最低分和平均分。 设某班有N个学生,应该定义两个一维数组存储该班的数学成绩和语文成绩。或者,定义一个二维数组存储该班的数学和语文成绩。例如,定义数组 float score[2][N];用第0行数组元素存储数学成绩,第1行数组元素存储语文成绩,列号表示学生的学号。 求最高分、最低分和平均分就是求成绩的最大值、最小值和平均值。定义另一个数组float result[2][3],第0行数组元素、第1行数组元素分别存储数学、语文的最高分、最低分和平均分。具体算法如图6.9所示。 输入2门课程的成绩到数组score中 根据流程图编写程序如下。 #define M 2 /*功课门次 */ i= 0 to M-1 #define N 35 /*班级人数*/ max=min=score[i][0] #define K 3 /*三种成绩*/ sum=0 main( ) { j=0 to N-1 float sum,score[M][N]; sum=sum+score[i][j] float max,min, result[M][K]; float t; max score[i][j] Y N { min=score[i][j] scanf(\%f\ score[i][j]=t; result[i][0]=max resilt[i][1]=min result[i][2]=sum/N } for(i=0;i 输出最后结果数组result的值 { max= score[i][0]; 39 min=score[i][0]; sum=0; for(j=0;j sum+=score[i][j]; if(max if(min>score[i][j]) min=score[i][j]; } result[i][0]=max; result[i][1]=min; result[i][2]=sum/N; } for (i=0;i printf(\第%d门课程的成绩:\\n\ printf(\最高分:%.2f \ printf(\最低分:%.2f \ printf(\平均分:%.2f \ } pritnf("\\n"); } 6.4 字符数组及字符串 字符数组是指数组元素是字符型数据的数组,它的每个数组元素能存储一个字符型数据。 6.4.1 字符数组的定义、引用及初始化 1.字符数组的定义 字符数组的定义方法同前面介绍的整型、实型数组的定义相同。 例如: char s1[10],s2[2][80]; 表示定义s1是一维字符数组,s2是二维字符数组。 2.字符数组和字符串的关系 C语言通过字符数组来描述字符串。如果某一字符数组中包含有字符'\\0'时,该字符数组可以作为字符串处理,此时,字符数组名类似于其他高级语言中的字符串变量名,简称串名。 在使用字符串时,程序设计者只需关心字符串的有效字符。字符数组中'\\0'前的字符的个数就是字符串有效字符的个数,通常称为字符串长度。在前面章节中讲述过printf函数使用%s格式输出字符串时,printf()函数判断输出是否结束的标志就是'\\0'。 3.字符数组元素的初始化 (1)按字符方式初始化。 (2)把一个字符串赋给字符数组。 例如: char s1[20]={\ /*数组长度是20,把字符串常量赋给字符数组*/ char s2[]=\ /*花括号可以省略*/ char s3[3][20]={ \ /*2个字符串常量赋给二维字符数组*/ 4.字符数组元素的引用 可以单个引用字符数组元素,也可以将字符数组作为字符串来使用。 (1)按数组元素引用。 具体引用形式如下。 数组名[下标] 例如: #include \ 40 main() { char f[20]; int k; for (k=0;k<20;k++) scanf(\%c\ for(k=0;k<20;k++) putchar(f[k]); } (2)按字符串引用。例如: main() { char str1[128],str1[128]; scanf(\%s%s\,str1,str2); printf(\%s\\n,%s\\n\} 6.4.2 字符串及字符串处理函数 下面介绍几个常用的字符串处理函数。 1.字符串输入函数gets() gets()函数调用形式为: gets(字符数组名) 其功能是从终端输入一个字符串,并把该字符串存储到字符数组中。 例如: #include \main() { char s[128]; gets(s); printf(\%s\\n\} 2.字符串输出函数puts() puts()函数的调用形式为: puts(数组名) 或 puts(字符串常量) 3.字符串拷贝函数strcpy() strcpy()函数的调用形式为: strcpy(字符数组1,字符串2) 其功能是将字符串2复制到字符数组1。字符数组1只能是字符数组,字符串1既可以是字符数组,也可以是字符串常量。 4.字符串连接函数strcat() strcat()函数的调用形式为: strcat(字符数组1,字符串2) 其功能是连接两个字符串,把字符串2连接到字符数组1后面,结果放到字符数组1中。 5.字符串比较函数strcmp() strcmp()函数的调用形式为: strcmp(字符串1,字符串2) 其功能是比较两个字符串的大小。根据函数的返回值来确定大小。两个字符串比较大小的规则是:对两个字符串自左向右逐个字符相比,直到出现不相同的字符为止。函数返回值决定了字符串比较的结果。 (1)字符串1等于字符串2,函数返回值是0。 (2)字符串1大于字符串2,函数返回值是一个正整数。 41 (3)字符串1小于字符串2,函数返回值是一个负整数。 6.计算字符串长度函数strlen() strlen()函数的调用形式为: strlen(字符数组名) 其功能是求字符串的有效字符的个数。 6.4.3 字符数组举例 例6.10 统计一行字符中的英文字母、数字、空格和其他字符的个数。 根据流程图编写程序如下。 输入一行字符到字符数组,并且给统计变量赋零 #include \ main() c不是字符串的结束字符'\\0' { char c,string[128]; Y c是字母 N int i; int letter,digital,sapce,other ; Y c是数字 N letter=0,digital=0,space=0,other=0; gets(string); letter++ Y c是空格 N for(i=0;(c=string[i])!='\\0';i++) digtal++ if(c>='a'&&c<='z' || c>='A'&&c<='Z') space++ other++ letter++; else 输出统计变量的值 if(c>='0'&&c<='9') digtal++; else if(c==' ') space++; else printf(\%d,digtal=%d \\n\ printf(\%d,other=%d \\n\ } 6.5 数组作函数参数 数组元素作函数的参数与普通变量作函数的参数本质相同。数组元素作函数实参时,仅仅是将其代表的值作为实参处理。 数组中元素作为函数的实参,与简单变量作为实参一样,结合的方式是单向的值传递。 例6.12 数组元素作函数的参数示例。 float max(float x,float y) { if(x>y) return x; else return y; } main() { float m,a[]={34.2,100,12.3,50,67,65,78,98,89,-20}; int k; m=a[0]; for(k=1;k<10;k++) m=max(m,a[k]); printf(\%.2f\\n\} 程序运行的结果为: 100.00 例6.13 编写函数实现数组的逆序存放。 程序如下。 void change(x) 42 int x[]; { temp=x[0];x[0]=x[1];x[1]=temp; } main() { void change(); int a[2]={12,24}; printf("before:a[0]=%d,a[1]=%d\\n",a[0],a[1]); change(a); printf("after:a[0]=%d,a[1]=%d\\n",a[0],a[1]); } 程序运行的结果为: before:a[0]=12,a[1]=24 after:a[0]=24,a[1]=12 显然,用数组名作函数的参数,才能真正实现两个数据的交换。因为,数组名作函数的形参和实参时,调用函数把实参数组的首地址传递给形参数组,这样两个数组共享存储单元,在函数调用时对形参数组元素值的交换,实质上也是对实参数组元素值的交换。 用数组名作为函数参数应注意以下几点。 (1)数组名作函数参数时,可省略数组的长度。 (2)形参数组可以和实参数组同名。 (3)实参数组应足够大,即实参数组提供的内存空间应大于或等于形参数组需要的内存空间。 (4)数组名作函数参数时,应将数组的长度也作为函数的参数,这样编写的函数具备通用性。 例6.15 编写函数用“起泡法”排序。 “起泡法”的基本思想是对N个数构成的序列两两比较求出最大值。假设有5个数8,3,9,4,1,存储到a数组中,采用“起泡法”从大到小排序的过程如下。 (1)对5个数两两比较,如果相临的两个数不是从小到大排列的,则交换使之变为从小到大排列,比较结束后最大值在序列底部,于是得到序列(3,8,4,1,9)。 (2)对前4个数两两比较得到序列(3,4,1,8,9)。 (3)对前3个数两两比较得到序列(3,1,4,8,9)。 (4)对前2个数两两比较得到序列(1,3,4,8,9)。 排序工作结束。 从分析编写程序如下。 #define N 10 main() { void sort(); int a[N],i; for(i=0;i scanf(\%d\ sort(a,N); for(i=0;i for(i=0;i<5;i++) printf(\%8d\ printf(\} void sort(array,n) int array[],n; { int i,j,t; 43 for(i=n-1;i>0;i--) for(j=0;j if(array[j]>array[j+1]) { t=array[j]; array[j]=array[j+1]; array[j+1]=t; } } 运行程序,输入: 123 456 -123 -456 1 23 5885 34 101 10↙ 则输出: -456 -123 1 10 23 34 101 123 456 5885 -456 -123 10 23 44 1 第7章 函数 7.1 概 述 7.1.1 模块化程序设计思想 面对一项复杂任务,通常采取模块化的解决方法。首先,分解该复杂任务成几个大的功能模块,根据需要还可以继续细分,直到分解成一个个功能独立的模块为止。分解的结果可以描述为一棵倒立的大树,如图7.1所示。 大任务 小任务 小任务 小任务 小任务 小任务 小任务 小任务 小任务 小任务 小任务 小任务 小任务 7.1.2 C语言程序结构 函数是构成C语言程序的基本功能模块,它完成一项相对独立的任务。一个C语言程序是若干函数构成的,在构成C程序的诸多函数中有而且只有一个主函数。函数是程序的最小组成单位。 所有函数之间的关系是平行的,没有从属的概念。函数的平行关系使得函数的编写相对独立,便于模块化程序设计的实现。 C程序的执行总是从主函数开始,又从主函数结束,其他函数只有通过调用关系发生作用。 7.1.4 函数的分类 1.从用户角度分 从用户角度,函数可分为以下两类。 (1)库函数 (2)自定义函数 ① 定义函数; ② 声明函数; ③ 调用函数。 2.从函数形式分 从函数形式角度,函数可分为以下两类。 (1)无参函数,函数不带参数。 (2)有参函数,函数带有至少一个参数。 7.2 函数的定义 函数由两部分构成:函数头和函数体。 函数头给出函数相关信息(类似“黑盒子”中的入口和出口),而函数体具体实现函数的功能。 7.2.1 函数的定义形式 函数定义的一般形式是: [类型标识符] 函数名(形式参数表列) 形式参数类型说明 { 数据描述部分 算法实现部分 45 } 前两行是函数头。形式参数(又简称形参)表列和形式参数类型说明部分体现的是一个函数的入口参数的个数及其类型。 类型标识符说明了函数返回值的类型,也简称函数类型。 函数体用一对花括号{}括起来。函数体中不仅可以使用数据描述部分描述的变量,而且还可以使用形式参数。 例7.1 函数定义示例。 float max(x, y) float x, y; { float temp; if (x>y) temp=x; else temp=y; return (temp); } 7.2.2 函数的返回值 在函数定义时需要描述函数类型,但没有给出函数如何得到返回值。调用有值函数时,要求被调函数返回数据给主调函数,返回的数据称为函数返回值,简称函数值。得到函数返回值的方法是使用return语句。 return语句的功能有3个。 (1)返回一个值给主调函数。 (2)释放在函数的执行过程中分配的所有内存空间。 (3)结束被调函数的运行,将流程控制权交给主调函数。 return语句使用的一般形式为: return(表达式) return语句应书写在函数体的算法实现部分,圆括号可以省略。 7.2.3 形参和返回值的设定 编写函数时,应分析该函数中哪些量是函数的已知量,那些是函数需要得到的结果。设计时将已知数据作为函数的形参,已知数据有几个,形参就有几个。未知数据正是函数需要得到的结果。除需要分析已知和未知外,还需要确定已知和未知的数据类型,从而完成对函数头的设计。 7.3 函数的调用 当函数被调用时,函数对应的程序代码才开始执行,才能实现相应的函数功能。 7.3.1 对被调用函数的声明 对被调用函数的声明有两种方式:外部声明和内部声明。在主调函数内对被调函数所作的声明称为内部声明,也称为局部声明;在函数外进行的函数声明称为外部声明,如果声明在程序最前端,外部声明又称为全局声明。 内部声明过的函数只能在声明它的主调函数内调用。外部声明过的函数,从声明处到本程序文件结束都可以被调用。内部声明应放在主调函数的数据描述部分,外部声明可以出现在程序中任何函数外。 对被调用函数的声明具体形式为: 函数类型 函数名( ); 例7.5 函数声明示例。 main() { int m; float c; float sum(); /*在主函数main()内对被调函数sum()作局部声明,*/ scanf(\ /*只能在主函数内调用声明过的函数max()*/ c=sum(m); 46 printf(\} float sum(int n) /*功能是计算数列1/2,2/3,3/5,5/8……的前n项之和*/ { float a,b,t,s; int k; a=1,b=2;s=0.5; for(k=2;k<=n;k++) { t=a,a=b,b=a+t; s=s+a/b; } return (s); } 7.3.2 函数调用的一般形式 1.函数调用的一般形式 函数名(实际参数表列) 实际参数表列是函数入口参数的实际值。如例7.5中的c?sum(m)中的m就是有确定值的实际参数,sum(m)是对函数的调用,调用结束后得到返回值赋值给变量c。 2.形式参数和实际参数 有参函数在调用时,主调函数和被调函数之间有数据传递,主调函数传递数据给被调函数。主调函数传递来的数据称为实际参数,简称实参。函数定义时形式参数仅仅是数据的抽象代表,没有具体值,称为形参。 (1)形式参数 定义函数时,函数名后的参数称作形式参数,简称形参。 在定义函数时,系统并不给形参分配存储单元,当然形参也没有具体的数值,所以称它是形参,也叫作虚参。 形参在函数调用时,系统暂时给它分配存储单元,以便存储调用函数时传来的实参。一旦函数结束运行,系统马上释放相应的存储单元。 2)实际参数 在调用函数时,函数名后的参数称作实际参数,简称实参。 调用函数时,实参有确定的值,所以称它是实际参数。它可以是变量、常量、表达式等任意“确定的值”。 (3)实参和形参之间的关系 实参的个数、类型应该和形参的个数、类型一致。调用函数时,系统给形参分配存储单元,并且把实参的数值传递给形参。 实参和形参分别属于主调函数和被调函数,具有不同的内存单元。所以,在函数调用时形参发生改变,不会影响到实参。 3.实参和形参的结合方式 C语言中实参和形参的结合采取的是“单向值传递”方式,只有实参传递参数给形参,形参不回传参数给实参。下面用例8.7讲述实参和形参的具体结合方式。 例7.7 实参和形参的结合方式示例。 main( ) { float a,b,sum; float add(); scanf(\ sum=add(a,b); printf(\} float add( x, y) float x,y; 47 { float z; z=x+y; return(z); } 程序从主函数开始执行,首先输入a,b的数值(假如输入3,5),接下来调用函数add(a,b)。具体调用过程如下。 (1)给形参x,y分配内存空间。 (2)将实参b的值传递给形参y,a的值传递给形参x,于是y的值为5,x的值为3。 (3)执行函数体。 ① 给函数体内的变量分配存储空间。即给z分配存储空间。 ② 执行算法实现部分,得到z的值为8。 ③ 执行return语句,完成以下功能。 ? 将返回值返回主调函数,即将z的值返回给main()。 ? 释放函数调用过程中分配的所有内存空间,即释放x,y,z的内存空间。 ? 结束函数调用,将流程控制权交给主调函数。 调用结束后继续执行main()函数直至结束。 函数调用前后实参、形参的变化情况如图7.4所示。 调用时 调用结束后 主 a b a b 调 50 45 50 45 函 数 被 调 x,y不存在 函 50 45 数 x y 图7.4 例7.4实参和形参变化示意图 7.3.3 函数调用的具体形式 有些函数有返回值,有些没有返回值,这两种函数的调用形式不同。 1.有值函数的调用形式 (1)函数调用作为表达式的一部分。即函数返回值参与表达式的运算。 (2)作为函数参数。即函数返回值又作为另一个函数的实参。 2.无值函数的调用形式 无值函数调用是作为独立的函数调用语句出现的语句,其功能类似于一个过程。 (1)实参的类型应和形参的类型匹配。 (2)实参和形参的结合方向是自右向左的。 (3)实参的个数和形参应该一致。 (4)实参可以是任意能够代表“确定的值”的内容。 (5)有值函数才可以参于表达式的运算。 7.4 函数的嵌套及递归调用 7.4.1 函数的嵌套调用 嵌套调用指的是在函数的调用过程中又出现了另外一种函数调用,称为函数的嵌套调用。 例7.8 函数嵌套示例。 以下程序的功能是计算x2?sinx在区间[0,5]的定积分。程序由3个函数构成,分别是主函数main()、 48 函数f1()、函数f2()。main()调用函数f2(),在函数f2()的执行过程中又调用了函数f1(), main()嵌套调用了函数f1()。 main() ① f2(x,h) ③ sin(x ) f1(x) ⑤ { { … ② { … ④ { … ⑥ … s+=f2(x,h); f1(x) sin(x) ⑦ …(13) (12) … (11) (10) … ⑨ ⑧ } } } } 图7.5 例7.10函数嵌套调用过程 #include \ main() { float f2(); float s=0, h=0.00005,x; for(x=0;x<5;x+=h) s+=f2(x,h); printf(\} float f2(x,h) float x,h; { float f1(); return ((f1(x)+f1(x+h))*h/2 ); } float f1(x) float x; { return (x*x+sin(x)); } 7.4.2 函数的递归调用 函数的递归调用是函数嵌套调用的特殊形式。一个函数在它的函数体内直接或间接地调用了自己的函数称为函数的递归调用。 例7.9 用递归法计算n!。 float fac(int n) { float f; if(n==0||n==1) f=1; else f=fac(n-1)*n; return(f); } main()函数如下: main() { int n; float y; float fac(); printf(\ scanf(\ y=fac(n); 49 printf(\} 7.5 局部变量和全局变量 7.5.1 局部变量 在一个函数内定义的变量称为局部变量。局部变量的作用范围是定义它的函数。 关于局部变量的作用域需要说明以下几点。 (1)主函数中定义的变量也只能在主函数中使用,不能在其他函数中使用。 (2)形参属于被调函数的局部变量,实参属于主调函数的局部变量。 (3)允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰。 (4)在复合语句中也可定义变量,其作用域只在本复合语句范围内。 7.5.2 全局变量 函数外定义的变量称作全局变量。全局变量可以被定义它的文件中的所有函数使用。全局变量的作用范围是从定义变量的位置开始到它所在源文件的结束。 7.6 变量的存储类别 C语言中的变量不仅有类型属性,而且还有存储类别的属性。 完整的变量定义应该确定它的两种属性:存储类型和数据类型。 变量定义的完整形式为: [存储类型] 类型说明符 变量名表列; C语言中,变量有4种存储类型,分别为自动类型、静态类型、外部类型和寄存器类型。 变量在计算机内存的存储情况分为静态存储和动态存储两种。 7.6.1 自动存储类型 关键字auto表示变量是自动存储类型。 自动存储类型的变量具有动态性。自动存储类型变量的作用范围仅局限于定义它的函数。自动存储类型变量的存储单元分配在动态数据区。 7.6.2 寄存器存储类型 关键字register表示变量是寄存器存储类型。 例如,register int a,b; 表示定义变量a,b是整型并且是寄存器存储类型。 寄存器型变量具有动态性。寄存器存储类型变量的作用范围也是仅局限于定义它的函数。 7.6.3 外部存储类型 关键字extern表示变量是外部存储类型。 例如,extern double x,y; 表示定义变量x,y是双精度浮点型并且是外部存储类型。 外部存储类型变量具有静态性。外部存储类型变量定义在函数外部,它的作用域为从变量的定义处开始,到本程序文件的末尾 7.6.4 静态存储类型 关键字static表示变量是静态存储类型。 例如,static double x,y; 表示定义变量x,y是双精度浮点型并且是静态存储类型。 静态存储类型变量具有静态性。静态存储类型变量可以定义在函数内部,也可以定义在函数外部。在整个程序运行期间,静态型变量都占据存储单元。 举 例 例7.16 编写两个函数分别求两个整数的最大公约数和最小公倍数。 int max(int x,int y) { int k; for(k=x if(x%k==0&&y%k==0) return(k); flag=1 } int min(int x,int y) k= j=2~k m j是m的因子 1 0 50