第一章c语言概述 1.1重点、难点 1.c语言的特点 (1)语言简洁、紧凑、使用方便灵活 (2)运算符丰富 (3)具有结构化的控制语句 (4)语法限制不太严格,程序设计自由度大 (5)c语言允许用户直接访问物理地址,能进行位操作,可以直接对硬件进行操作。 2.源程序的书写规则 (1)c 语言书写自由,一行内可以写几个语句,并且一个语句可以写在多行上。 (2)c程序没有行号,每个语句和数据定义的最后必须有一个分号。 (3)c语言分号是语句中必不可少的。即使是程序的最后一个语句也应该包含分号。 (4)c 语言中的注释可以用“/*”开始,用“*/”结束,注释可以在任何允许插入空格符的地方插入。且“/”与“*”间不允许有空格。 (5)c语言的注释不允许用嵌套,注释可以用西文也可以用中文。 3.c程序的组成 (1)一个c 程序由一个或多个源程序文件组成 (2)一个c源程序文件是由若干个函数组成的。函数是c 程序的基本单位。在这些函数中有且只用一个主函数main(),主函数由系统提供。各个函数在程序中所处的位置不是固定的。 (3)一个源程序文件是一个编译单位,即以源文件为单位进行编译,而不是以函数为单位进行编译。C语言源程序文件的扩展名为.c。 (4)任何c程序都是从主函数开始执行的,调用其他函数后,回到main()主函数,在main()主函数中结束程序运行。 4.函数的组成 一个函数由两部分组成 (1)函数的首部 函数类型、函数名、函数参数类型、函数参数名 函数名后必须是一对圆括弧,但可以没有函数参数 (2)函数体 函数体是最外层{}括起来的部分,包括变量的声明和执行两部分 1.2例题 【例题1-1】判断 (1)c语言是以函数为程序的基本单位,便于实现程序的模块化() 解析:c程序是由函数组成的,一个c源程序至少包含一个主函数main()。函数是c程序的基本单位。C语言的这种特点使c程序容易实现模块化。 答案:正确 (2)c程序的执行总是从程序第一句开始() 解析:一个c程序总是从main()函数开始执行,而不论main()函数处在程序中的任何位置。 答案:错误 (3)c程序中可以不使用函数() 解析:c语言是函数式语言,一个c源程序至少包含有一个main函数。 答案:错误 (4)c语言提供了一个输入语句scanf( )和一个输出语句printf() 解析:c语言本身没有输入输出语句,输入输出操作由库函数scanf和printf等函数来完成。 答案:错误 (5)c程序的一条语句必须写在一行中() 解析:c程序书写格式自由,一行内可以写几个语句,一个语句可以分写在多行上。 答案:错误 (6)c程序中注释说明只能写在一条语句的后面() 解析: c 语言中的注释可以用“/*”开始,用“*/”结束,注释可以在任何允许插入空格符的地方插入。且“/”与“*”间不允许有空格。 答案:错误 【例题1-2】下列程序中格式错在何处 A. main( ) B. {int a, b, z; C. a=2:b=3; D. z=a+b;} 解析:A,B,D没有错误,C中,在a=2后应该是分号“;”或逗号“,”,不应该是冒号“:”,因此c处有错误。 答案:c 【例题1-3】c语言的程序一行写不下时,可以 A.用逗号换行 B.用分号换行 C.在任意空格处换行 D.用回车符换行 解析:c语言可以在任何一个分隔符或空格处换行 答案:c 【例题1-4】下列程序段中那一个是错误的注释方法? A. #in/*包含*/clued<stdio.h> B.#include<stdio.h> void main( ) void main/* */(/* 主函数*/) { { } } C. #include<stdio.h> D.#include <stdio.h> void main( ) void main( ) {int x/*初始化*/=10; {int x=10; /*打印*/printf(“%d”,x); printf(“%d”,x); } /*打印X的值*“*=10*/ } 解析:根据c语言的规定:注释不能插在标识符的中间,在其他有分隔符的地方都可以插入注释,上面四个程序段中只有A是将注释插入一 标识符的中间了,因此A的注释是错误的。 答案:A 【例题1-5】下列程序中的错误在() main() {…….. { {……..} } 解析:从上面的c程序的结构中,可以明显看出花括号不是成对出现的,必须在上面 的程序中再加上对应的花括号,可以加到最后,也可以加在第四行或第五行,看程序的具体情况而定。 答案:程序中少了一个花括号“}” 第三章 数据类型、运算符与表达式 3.1重点、难点 1.关键字 关键字又称保留字,是一种语言中用于固定用途的名字。C语言的关键字共有32个。,所有关键字都用小写的英文单词来表示。 c语言的关键字如下: auto break case char const continue default do double else enum extern float for goto if int long regist return short signed sizeof static struct switch typedef union unsigned void volatile while 2.标识符 1、 概念: 用来标识变量名、符号常量名、函数名、数组名、类型名、文件名的有效字符序列称为标识符。简单地说,标识符就是一个名字。 2、c语言的标识符命名规则 c语言的标识符只能由字母、数字、下划线三种字符组成。且第一个字符必须为字母或下划线。 3、标识符的分类 关键字:是一些特殊的标识符,又称保留字,这些保留字不允许用户对它重新定义。 标准标识符: 也是特殊含义的标识符,例如库函数(printf()函数)、编译预处理命令等 用户自定义标识符 4、对标识符的说明 用户标识符长度最好不要超过8个字符 用户标识符不要和32个关键字同名 用户标识符最好不要与c语言中的库函数和其他一些命令(如编译预处理命令)重名 大小写是不同的标识符 用户标识符应做到见名知意 3.c语言的数据类型 在C语言中,数据类型可分为:基本数据类型,构造数据类型,指针类型,空类型四大类。 1.基本数据类型 基本数据类型最主要的特点是,其值不可以再分解为其它类型。也就是说,基本数据类型是自我说明的。 2.构造数据类型 是根据已定义的一个或多个数据类型用构造的方法来定义的。也就是说,一个构造类型的值可以分解成若干个“成员”或“元素”。每个“成员”都是一个基本数据类型或又是一个构造类型。在C语言中,构造类型有以下几种: ·数组类型 ·结构类型 ·联合类型 3.指针类型 指针是一种特殊的,同时又是具有重要作用的数据类型。其值用来表示某个量在内存储器中的地址。虽然指针变量的取值类似于整型量,但这是两个类型完全不同的量,因此不能混为一谈。 4.空类型 在调用函数值时,通常应向调用者返回一个函数值。这个返回的函数值是具有一定的数据类型的,应在函数定义及函数说明中给以说明。但是,也有一类函数,调用后并不需要向调用者返回函数值, 这种函数可以定义为“空类型”。其类型说明符为void。 4.常量和变量 对于基本数据类型量,按其取值是否可改变又分为常量和变量两种。 (一)常量 概念:常量(即常数)是程序运行过程中值不可改变的量,它又分为直接常量和符号常量。具体分为以下几类。 1.普通整型常量 整型常量 有三种表示形式 十进制数:123,-456,0 八进制数:(0开头) 0123即(123)8 十六制数:(0x开头) 0x123,即 (123)16、0x2bf 即(2bf)16 整型常量 占2个字节 整型常量表示范围为 -32768——32767即215-1。 2.长整型常量 在一个整数后加字母l或L则变成长整型常量 长整型常量占4个字节 长整型常量表示范围为231-1 3.无符号整型常量: 一个整型常量后加一个字母u或U则认为它是无符号整型数据。在内存中按无符号整型规定的形式存放。如果是负数后加字母u或U则先把负数转换成补码,然后按无符号数存储。例如:-12345u先将-12345转换成补码53191,然后按无符号数存储。 4.实型常量:又称浮点数 实型常量有两种表示形式:十进制小数表示形式和指数表示形式 十进制小数表示形式:由整数、小数点、和小数三部分组成,而且必须有小数点。 例如 0.123 ,0.23、45.0、3.67、0.0等 当小数部分或整数部分为0时0可以省略,但两部分不能同时省略。例如45.0可以写成45. ,0.23可以写成.23。0.0可以写成.0或0.。 指数形式:由尾数、E或e和指数三部分组成。例如123e3或123E3(123 × 103)或1.23E5(1.23 × 105) 注意: 1、尾数和指数都不能省略。即e或E的前后必须有数字。 例如: 3.58e4是正确的表示形式。1.23e或e5都是错误的表示形式。 2、e或E后面的指数必须为整数。例如 :2.34e4.5是错误的表示形式。 3、小数点的前面有且只有一位非0的数字称为规范化的指数形式。 例如:5.67e3 4、如果一个实数在用指数形式输出时,是按规范化的指数形式输出的。例如将5689.65指定按指数形式输出会输出5.68965e+003或5.68965e+03 实型常量占8个字节64位 按双精度方式处理 如果在实数的后面加字母f或F则系统会按单精度处理。如1.65f。 5.字符常量:是一对单引号括起来的一个字符。如‘a’ 、‘A’、‘%’等。 字符常量占一个字节 字符常量存储时存储的是字符的ASCII 值 大小写是不同的字符常量。例如 ‘A’和‘a’,’C’和‘c’等是不同的字符常量 c语言还使用特殊形式的字符常量,就是以“\”开头的字符序列,称为转义字符。常用的转义字符如下: \n 回车换行,将光标移到下一行行首。 \t 横向跳格(跳到下一个输出区,一个输出区8列,下个输出区到第9列) \b 回退一格 \r 回车 光标回到本行行首 \f 走纸换页 \\ 反斜线符"\" \' 单引号符 \a 鸣铃 \” 输出双引号 \ddd 1~3位八进制数所代表的字符 \xhh 1~2位十六进制数所代表的字符 广义地讲,C语言字符集中的任何一个字符均可用转义字符来表示。上述的\ddd和\xhh正是为此而提出的。ddd和hh分别为八进制和十六进制的ASCII代码。如\101表示字符"A" ,\102表示字母"B",\134表示反斜线,\x0A表示换行等。 6.字符串常量:字符串常量是一对双引号括起来的字符序列。如“boy”、“ABCDE”等。 每个字符串的结尾加一个字符串结束标志(‘\0’)系统根据此标志判断字符串是否结束。 “\0“是一个ASCII为0的字符,是空操作符,由系统自动加到字符串的结尾,不是人工操作。例“china”实际内存中:c h i n a \0 字符串常量所占的字节数是它的字符的个数加1。而字符串的长度是从第一个字符开始到第一个’\0’之间的字符的个数。例如:“qwert\0gs”的字节数是9而长度为5。 7.符号常量: 是用一个标识符标识一个常量。在使用过程中,这个标识符就代表了该常量。若程序的开头有 #define PI 3.1415926 则PI即是符号常量,在程序中代表3.1415926 (二)变量: 概念:在程序执行过程中取值可变的量称为变量。它们可与数据类型结合起来分类。例如,可分为整型常量、整型变量、浮点常量、浮点变量、字符常量、字符变量、枚举常量、枚举变量。在程序中,常量是可以不经说明而直接引用的,而变量则必须先说明后使用。 变量定义的形式:存储类型名 类型标识符 变量名1[=表达式1],……. 变量名n[=表达式n]; 1.整形变量 (1)整形变量分类:基本型(int)、短整型(short [int])、长整形(long [int])、无符号整形(unsigned int 、unsigned short、unsigned long)。 基本型 类型说明符为int或signed int,在内存中占2个字节,其取值范围为-32768-32767 短整型 类型说明符为short int或short。所占字节和取值范围均与基本型相同。 长整型 类型说明符为long int或long ,在内存中占4个字节,其取值同长整常数的取值范围。 无符号型 类型说明符为unsigned。 无符号型又可与上述三种类型匹配而构成: 无符号基本型 类型说明符为unsigned int或unsigned。 无符号短整型 类型说明符为unsigned short 无符号长整型 类型说明符为unsigned long 各种无符号类型量所占的内存空间字节数与相应的有符号类型量相同。但由于省去了符号位,故不能表示负数。 下表列出了Turbo C中各类整型量所分配的内存字节数及数的表示范围。 类型说明符    数的范围      分配字节数 int        -32768~32767 ■■ short int     -32768~32767 ■■ long int  -2147483648~2147483647  ■■■■ unsigned int   0~65535 ■■ unsigned short   0~65535 ■■ unsigned long   0~4294967295    ■■■■ (2)整型变量的说明 变量说明的一般形式为: 类型说明符 变量名标识符,变量名标识符 例如: int a,b,c; (a,b,c为整型变量) long x,y; (x,y为长整型变量) unsigned p,q; (p,q为无符号整型变量) (3)在书写变量说明时,应注意以下几点: 允许在一个类型说明符后,说明多个相同类型的变量。各变量名之间用逗号间隔。类型说明符与变量名之间至少用一个空格间隔。 最后一个变量名之后必须以“;”号结尾。 变量说明必须放在变量使用之前。一般放在函数体的开头部分。 2.实型变量 (1)实型变量分类:单精度(float)、 双精度(double)、长双精度(long double) 在Turbo C中单精度型占4个字节(32位)内存空间,其数值范围为-3.4E-38~3.4E+38,只能提供7位有效数字。 双精度型占8 个字节(64位)内存空间,其数值范围为-1.7E-308~1.7E+308,可提供16位有效数字。 (2)实型变量说明的格式和书写规则与整型相同。 例如: float x,y; (x,y为单精度实型量)     double a,b,c; (a,b,c为双精度实型量) 3.字符型变量:用char来定义 字符型变量用来存放一个字符型常量,且只能放一个字符。实际在内存中存放的是该字符的ASCII值。 C语言中字符型数据和整形数据间可以通用,一个字符型数据既可以按字符型数据处理也可以按整形数据处理,按整形数据处理时将ASCII值作为整数处理,故字符型数据可以进行算术运算。 字符型数据占一个字节,只能存放0-255之间的整数。 字符变量类型说明的格式和书写规则都与整型变量相同。 4.在c语言中没有专门的字符串变量,如果要将字符串放在变量中,应使用字符数组。 5.变量的初值和类型转换 (一)变量赋初值 在程序中常常需要对变量赋初值,以便使用变量。语言程序中可有多种方法。在定义时赋以初值的方法,这种方法称为初始化。在变量说明中赋初值的一般形式为: 类型说明符 变量1= 值1,变量2= 值2,……; 例如: (1)对被定义的变量均赋初值 float x=3.2, y=3f, z=0.75; char ch1='K', ch2='P'; (2)对被定义的变量的一部分赋初值 int a,b,c=5; (3)对几个变量赋同一初值 int a=3,b=3,c=3; (二)变量类型的转换 变量的数据类型是可以转换的。转换的方法有两种, 一种是自动转换,一种是强制转换。 1、自动转换 自动转换发生在不同数据类型的量混合运算时,由编译系统自动完成。自动转换遵循以下规则: 若参与运算量的类型不同,则先转换成同一类型,然后进行运算。 转换按数据长度增加的方向进行,以保证精度不降低。如int型和long型运算时,先把int量转成long型后再进行运算。 所有的浮点运算都是以双精度进行的,即使仅含float单精度量运算的表达式,也要先转换成double型,再作运算。 char型和short型参与运算时,必须先转换成int型。 在赋值运算中,赋值号两边量的数据类型不同时, 赋值号右边量的类型将转换为左边量的类型。 如果右边量的数据类型长度比左边长时,将丢失一部分数据,这样会降低精度。图2.1表示了类型自动转换的规则。 2、强制类型转换 强制类型转换是通过强制类型转换运算符来实现的。其一般形式为: (类型说明符) (表达式) 其功能是把表达式的运算结果强制转换成类型说明符所表示的类型。例如: (float) a 把a转换为实型,(int)(x+y) 把x+y的结果转换为整型。在使用强制转换时应注意以下问题: 类型说明符和表达式都必须加括号(单个变量可以不加括号),如把(int)(x+y)写成(int)x+y则成了把x转换成int型之后再与y相加了。 无论是强制转换或是自动转换,都只是为了本次运算的需要而对变量的数据长度进行的临时性转换,而不改变数据说明时对该变量定义的类型。 6.运算符 (一)算术运算符和算术表达式 1.加法运算符“+”:加法运算符为双目运算符,即应有两个量参与加法运算。如a+b,4+8等。具有左结合性。 2.减法运算符“-”:减法运算符为双目运算符。但“-”也可作负值运算符,此时为单目运算,如-x,-5等具有左结合性。 3.乘法运算符“*”:双目运算,具有左结合性。 4.除法运算符“/”:双目运算,具有左结合性。参与运算量均为整型时, 结果也为整型,舍去小数。如果运算量中有一个是实型,则结果为双精度实型。 void main( ) { printf("\n\n%d,%d\n",20/7,-20/7); printf("%f,%f\n",20.0/7,-20.0/7); } 结果为:2,-2 2.857143,-2.857143 本例中,20/7,-20/7的结果均为整型,小数全部舍去。而20.0/7和-20.0/7由于有实数参与运算,因此结果也为实型。 5.求余运算符(模运算符)“%”:双目运算,具有左结合性。要求参与运算的量均为整型。 求余运算的结果等于两数相除后的余数。 void main(){ printf("%d\n",100%3); } 结果:1 “%”为双目运算,具有左结合性。求余运算符% 要求参与运算的量均为整型。本例输出100除以3所得的余数1。 6.算术表达式 算术表达式是由算术运算符和括号连接起来的、符合c语法规则的式子, 以下是算术表达式的例子: a+b   (a*2)/c  (x+r)*8-(a+b)/7   ++i  sin(x)+sin(y)   (++i)-(j++)+(k--) (二)自增1,自减1运算符 自增1运算符记为“++”,其功能是使变量的值自增1。自减1运算符记为“- - ”,其功能是使变量值自减1。自增1,自减1运算符均为单目运算,都具有右结合性。可有以下几种形式: ++i i自增1后再参与其它运算。 - -i i自减1后再参与其它运算。 i++ i参与运算后,i的值再自增1。 i- - i参与运算后,i的值再自减1。 在理解和使用上容易出错的是i++和i--。 特别是当它们出现在较复杂的表达式或语句中时,常常难于弄清,因此应仔细分析。 注意:1、自增、自减运算符只能用于变量不能用于常量或表达式 2、自增、自减运算符的结合方向为自右向左。 例如:若i的原值为3则执行语句printf(“%d”,-i++);的结果为-3。(先取出i的值3输出-i的值-3,然后i 增值为4) 3、对于i+++j类表达式c编译系统在处理时自左而右尽可能多的将若干个字符组成一个运算符。所以它相当于(i++)+j 4、在函数调用时对函数参数的求值顺序是自右而左。 例如: 若i的初值位3则执行printf(“%d,%d”,i,i++);的结果为4,3 (三)赋值运算符和赋值表达式 简单赋值运算符和表达式,简单赋值运算符记为“=”。由“= ”连接的式子称为赋值表达式。 其一般形式为: 变量=表达式 例如: x=a+b w=sin(a)+sin(b) y=i+++--j 赋值表达式的功能是计算表达式的值再赋予左边的变量。赋值运算符具有右结合性。因此 a=b=c=5 可理解为 a=(b=(c=5)) 如果赋值运算符两边的数据类型不相同, 系统将自动进行类型转换,即把赋值号右边的类型换成左边的类型。具体规定如下: 1、实型赋予整型,舍去小数部分。 整型赋予实型,数值不变,但将以浮点形式存放, 即增加小数部分(小数部分的值为0)。 3、字符型赋予整型,由于字符型为一个字节, 而整型为二个字节,故将字符的ASCII码值放到整型量的低八位中,高八位为0或为1视系统而定。 4、整型赋予字符型,只把低八位赋予字符量。 5、将unsigned int型数据赋给long int 型变量时,只需将高位补0。 6、将非unsigned型数据赋给长度相同的unsigned型变量,也是原样照赋。 (四)复合赋值符及表达式 在赋值符“=”之前加上其它二目运算符可构成复合赋值符。 例如:+=,-=,*=,/=,%= 构成复合赋值表达式的一般形式为: 变量 双目运算符=表达式 它等效于 变量=变量 运算符 表达式 例如: a+=5 等价于a=a+5  x*=y+7 等价于x=x*(y+7)  r%=p 等价于r=r%p 复合赋值符这种写法,对初学者可能不习惯, 但十分有利于编译处理,能提高编译效率并产生质量较高的目标代码。 (五)逗号运算符和逗号表达式 C语言中逗号“,”也是一种运算符,称为逗号运算符。 其功能是把两个表达式连接起来组成一个表达式, 称为逗号表达式。 其一般形式为: 表达式1,表达式2 其求值过程是分别求两个表达式的值,并以表达式2的值作为整个逗号表达式的值。 说明两点: 1、逗号表达式中的表达式1和表达式2 也可以又是逗号表达式。 例如: 表达式1,(表达式2,表达式3) 形成了嵌套情形。 因此可以把逗号表达式扩展为以下形式: 表达式1,表达式2,…表达式n 整个逗号表达式的值等于表达式n的值。 2、程序中使用逗号表达式,通常是要分别求逗号表达式内各表达式的值,并不一定要求整个逗号表达式的值。 3、并不是在所有出现逗号的地方都组成逗号表达式,如在变量说明中,函数参数表中逗号只是用作各变量之间的间隔符。 3.2例题 【例题3-1】下列哪些是c语言提供的合法的数据关键字 A. Float B. signed C. integer D. Char 解析:此题的选项A和D中有一个字母是大写字母,c语言的关键字不允许用大写字母,所以是不正确的,c也不对,在c语言中,用int 而不用 integer做为整形数据定义的关键字,只有B是对的,signed是有符号的意思,是c语言中的一个关键字。 答案:B 【例题3-2】下面标识符中,合法的用户标识符为 A.变量a B. fast+1 c. printf D. 2ab 解析:标识符只能由字母、数字、下划线三种字符组成。且第一个字符必须为字母或下划线。A是汉字不是上述三种情况之一,不能用做用户标识符。而B含有加号,加号不能用做标识符。D是以数字开头,不符合标识符命名规则。C选项是c语言的库函数,可以做用户标识符。 答案:C 【例题3-3】下列属于整形常量的是 A.0X12A B.12x C.-12. D.1E2 解析:十六进制整数是以0x开头,所以选项B不合法。选项C有小数点所以是实型常量的十进制表示形式,D是实型常量的指数表示形式。c语言能用来表示整常数的有三种:十进制、八进制、十六进制。A是整形常量的十六进制表示形式。 答案:A 【例题3-4】下述四项中,合法的c语言整型常量是 A. -087 B. 5L C. (long)123456 D. 1.23e+2 解析:选项A中087虽然是以0开头的,但它不是一个八进制数,因为一个八进制数的数字字符在0—7之间,八进制数前可以带负号。选项C是一个表达式而不是一个常量。选项D是一个实形常量。选项B是在一个整数后加L构成一个长整形常量。 答案:B 【例题3-5】以下不正确的实型常量是() A. –2. B.123E-2 C.-.543 D.2.1E3.5 解析:1、实型常量的十进制表示形式中,小数部分或整数部分可省略,但不能同时省略。2、在指数形式(fem或fEm)的表示中,f为十进制整数或浮点数,m必须是整数,并且f和m都不能省略,“+”号可以省略,“-”号不能省略。)观察以上A、B、C三个选项都是合法实型常量。选项D中指数部分不是整数,是错误的。 答案:D 【例题3-6】若有以下定义和语句: int u=010,v=0x10,w=10; printf(“%d,%d,%d”,u,v,w); 则结果输出为( ) A.8,16,10 B. 10,10,10 C. 8,8,10 D. 8,10,10 解析:函数printf要求三个整形变量u,v,w按十进制形式输出,所以八进制数“010”要转换成十进制数8,十六进制数0x10要转换成十进制16。 答案:A 【例题3-7】下述常量中,哪些是不合法的常量 A.’\12’ B.“ ” C.‘’ D.“\483” 解析:显然,B和D是字符串常量,A是字符常量。选项A是一个普通的八进制转义字符,ASCII值为10。B是空字符串。选项D初看起来是一个八进制转义序列。但因为8超过了八进制范围,系统自动将其识别为’\4’、’8’和’3’组成,在输出时显示为“◇83”。选项c仅由两个连续的单引号组成,是错误的字符常量。 答案:C 【例题3-8】以下不合法的常量是() A. ‘\6’ B. "" C.‘\286’ D.”\286” 解析:B选项""是空字符串常量,此串仅占用一个字节。A选项为转义字符。D选项为字符串常量,而C选项看似为转义字符,但超过了\后的八进制的表示范围,是不合法的。 答案:C 【例题3-9】字符串”\ \ \ ” A A A \ 1 2 3 \ x A A \ t”的长度为 A.8 B.17 C.14 D.10 解析:c语言对上述问题的处理方法是从左向右尽可能多的将连续的字符组成一个有意义的项。所以\\为转义字符\,\”为转义字符输出为”。AAA是三个普通字符。\123也是转义字符。\XAA是转义字符。 \t为一个转义字符。 答案:A 【例题3-10】下列选项中,( )是正确的转义字符。 A.' \ xef' B.' \ 082' C.' xab ' D.' \' 解析:B选项“\”后应为1-3位八进制,但8超过了八进制的表示范围,故是错误的。C选项不是转义字符的表示形式。D选项不是转义字符的正确表示形式。A选项表示十六进制ef所表示的字符∩。 答案:A 【例题3-11】下面程序段的结果是 int i=65536; printf(“%d\n”, i); A. 65536 B. 0 C. 有语法错误,无出结果 D. 1 解析:c语言规定,int 类型变量占两个字节的存储单元,表示的数值范围为-32768——32767,题目中的i赋值为65536,已超过两个字节所能表示的数值,c语言认为这并不是出错,而是自动取低位两个字节的内容。将该数减去整型数据类型的模,所以65536-65536=0 答案:B 【例题3-12】若int类型数据占两个字节,则执行以下语句的输出为() int k=-1; printf(“%d,%u\n”,k,k); A. –1,-1 B.-1,32767 C.-1,32768 D.-1,65535 解析:c语言中,负数在内存中是以补码形式存在的。k=-1在内存中的存储形式是1111 1111 1111 1111,当把k的值按“%d”格式(有符号整数)输出时为-1。按“%u”格式(无符号整数)输出时为65535。 答案:D 补充:求负数的补码(以-7为例) (1)写出绝对值|-7|的二进制表示形式 0000 0000 0000 0111 (2)按位取反(即将0变为1,将1变为0) 1111 1111 1111 1000 (3)末位加1即得到-7在内存中的表示形式 1111 1111 1111 1001 【例题3-13】在下述输出语句的判定中,正确的是() int x=0x123456L; long int y=0xe; printf(“%x,%ld”,x,y); A.输出值为3456,14 B.非法赋值 C.输出值不确定 D.输出值为0x123456,0xe 解析:Long型数据0x123456L赋给int 型变量x,系统自动对其进行类型转换,将数据中低16位3456赋值给x,舍弃高16位的0x0012。将0xe赋值给long型变量y,其值送到y的低16位,因为0xe是正数,所以变量y 的高16位补0。十六进制0xe的十进制为14。故选项A是正确的。 答案:A 【例题3-14】下列程序的输出是 #include <stdio.h> main( ) { printf(“%d”,null);} A. 0 B. 变量无定义 C. –1 D. 1 解析:(1)要把此处的null与c语言中预定义表标识符NULL区分开。NULL是在头文件stdio.h中定义的的标识符,它代表‘\0’;而null是小写拼音字母拼写,因此不能将它当做NULL,而是一般的用户标识符。 (2)c语言规定,程序中用到的所有变量在使用前必须先进行定义,而本题中的程序在对null做输出处理之前对它没有给出明确的变量定义 答案:B 【例题3-15】若k为整形变量,则以下语句 int a=-2L; printf(“%d\n”,a); A. 赋值不合法 B. 输出值为-2 C. 输出为不确定值 D.输出值为2 解析:本题的主要关键是要弄清c语言 中常量的表示方法和有关的赋值原则。在一个整形常量后面加一个L则被认为是长整形常量。一个整常量,如果其值在-32768-32767之间内,可以赋给一个int型或long int 型变量。但如果整常量的值超过了上述范围,而在-2147483648---2147483674范围内,则应将其值赋给一个long int型变量。本例中-2L虽然为long int但是其值为-2,因此可以通过类型转换把长整形转换为短整形,然后赋给整形变量a,并按照%d格式输出该值即为-2。 答案:B 【例题3-16】在c语言中,若下面的变量都是int类型 的,则输出的结果是 sum=pad=5; PAD=sum++,PAD++, ++PAD; printf(“%d\n”,pad); A. 7 B. 6 C. 5 D. 4 解析:在c语言中,标识符的大小写含义是不同的,因而本题中的pad和PAD分别代表两个不同的变量名称。由于pad已赋值为5,所以显示pad的结果也是5。 答案:C 【例题3-17】已知ch是字符变量,以下不正确的赋值语句是() A. ch=“a”; B. ch=’a’; C. ch=’\141’; D. ch=’\x61’+3; 解析:‘a’是字符占用一个字节。“a”是字符串占用两个字节,不能赋值给字符型变量,因为字符型变量只能存放一个字节的内容。所以A应该是错误的。C选项和D选项是转义字符分别用八进制和十六进制的ASCII码表示字符常量‘a’和‘d’。 答案:A 【例题3-18】下面选项中,()是对字符变量不正确的赋值方式 A.ch=’$’ B.ch=’7’+3 C.ch=‘\t’ D.ch=‘a+b’ 解析:A选项是将字符$赋给字符变量。选项B 是数字字符7与整数3相加做算术运算。C语言中字符型数据和整形数据间可以通用,一个字符型数据既可以按字符型数据处理也可以按整形数据处理。按整形数据处理时将ASCII值作为整数处理,故字符数据可以进行算术运算。选项c为转义字符。选项D单引号括起来三个字符。而字符常量是单引号括起来的一个字符,字符型变量用来存放一个字符型常量,且只能放一个字符。故选项D为错误。 答案:D 【例题3-19】若有以下定义和语句 char c1=’b’,c2=’e’; printf(“%d,%c\n”,c2-c1,c2-‘a’+’A’); 则输出结果是 A.2,M B.3,E C.2,e D.输出项与对应的格式控制不一致,输出结果不稳定 解析:c语言中规定,字符型常量在内存中占一个字节,用于存放字符的ASCII值。所有字符型常量都作为整形量处理,其对应的整数值就是ASCII字符集中该字符的序号。执行表达式“c2-c1”相当于字符‘e’与字符‘b’的ASCII码值相减,即101-98得3。而“c2-‘a’+’A’”即101-97+65得69,而ASCII码值69对应的字符是‘E’。 答案:B 【例题3-20】请选出合法的c语言赋值语句 A.a=b==58 B.i++; C.a=58,b=58 D.k=int(a+b); 解析:由于c语言规定,c程序中的任一语句都必须有一个分号做结束标志,分号是构成任何语句的重要成分,所以答案A、C均错。答案D中的书写int(a+b)是完全错误的,根据c语言规定,使用强制类型转换运算符的书写形式为:(类型名)(表达式),所以答案D应改写为:k=(int)(a+b)。答案 B 当于语句i=i+1;由此可见,该语句是合法的赋值语句。 答案:B 【例题3-21】已知:‘a’的ASCⅡ码值为97,‘0’的ASCⅡ码值为48,下述程序的输出结果是 #include<stdio.h> main() {printf(“%s”,”\tabc%L\’\”\064\x41\085”);} A.□□□□□□□□abc%L’”4A B.\tabc%L\’\”\064\x41\085 C.abc%L’”0A D. abc%L’”0A\085 解析:(1)转义字符“\t”的作用是使后面的输出跳到下一个输出区。一个输出区为8列。(2)abc为三个普通字符,原样输出。(3)对应“\’ \”” 的输出是单撇号和双撇号。(4)“%”后面不是格式字符, 它作为普通字符输出,即输出一个“%”。“L”是普通字符,原样输出。(5)转义字符的ASCII码表示一个字符,,八进制的转义字符“\064”为八进制,转换为十进制为52,表示字符’4’。十六进制的“\x41”表示字符‘A’。(6)“\085”不是八进制的ASCII码表示的一个字符,而是‘\0’、‘8’、 ‘5’字符,输出时输出到第一个空字符‘\0’时结束,所以,‘8’、 ‘5’不输出。 答案:A 【例题3-22】下列语句中,符合c语言语法的赋值语句是 A.a=7+b+c=a+c; B.a=7+b++=a+7; C. a=7+b,b++,a++ D.a=7+b,c=a+7; 解析:答案A 和B均错,c语言中的赋值运算符的左边只能是变量或表示一个存储单元的表达式。答案c错,c 语言中规定任何语句都必须有一个分号来结束。答案D 正确,c语言允许用逗号运算符把若干个表达式连接起来形成逗号表达式。 答案:D 【例题3-23】经过下述赋值后,变量x的数据类型是() int x=3; double y; y=(double)x; A.int B.char C.float D.double 解析:将变量x的值3强制转换成double类型,再赋值给y,变量x的数据类型还是int型。因为任何变量定义后其数据类型不能改变。 答案:A 【例题3-24】以下程序的输出结果( ) main( ) {float x=3.6; int i; i=(int)x; printf(“x=%f,i=%d”,x,i); } A.x=3.600000,i=4 B. x=3, i=3 C. x=3.600000,i=3 D. x=3 i=3.600000 解析:变量被作强制类型转换后,其类型不会改变,改变的只有表达式的值,因此,执行语句i=(int )x 后,x的值仍为3.6 ,但i的值为3。 答案:C 【例题3-25】设x=2.5,a=7,y=4.7,算术表达式x+a%3*(int)(x+y)%2/4 的值 A.2.5 B.7 C.4.7 D.2.25 解析:取模运算只能在整形数据之间进行,a为整形变量。因为%、*、/这三个运算符的优先级别相同,类型转换符优先于*、/运算符,a的值为7,7%3=1,又因为(int)(2.5+4.7)=7,1*7=7,7%2=1,1/4=0,所以表达式 x+a%3*(int)(x+y)%2/4=x+0=2.5 答案:A 【例题3-26】下述程序的输出结果 main() {int a=016;a%=6-1; printf(“%d,”,a); a+=a*=a/=3; printf(“%d,”,a++); printf(“%d”,++a);} A.4,0,3 B.4,0,2 C. 4,2,4 D. 4,1,3 解析:(1)016是八进制整形常量,十进制表示为14。复合的赋值运算符的优先级别比算术运算符“-”的优先级别低,先计算6-1=5,在计算a%5(即14%5得4)赋值给a,所以a的值是4,先输出4。 (2)计算a/=3(即4/3等于1,赋值给a,a的值为1)。计算a*=1(a*1等于1*1,赋值给a,a的值为1),计算a+=1(a+1等于1+1=2,赋值给a,a的值为2)。 (3)执行printf(“%d,”,a++);语句,先输出a的值为2,然后,变量a的值在自增1(a 的值为3)。 (4)执行printf(“%d,”,++a);语句,先将变量a的值自增1(a 的值为4),再输出a的值4。 答案:C 【例题3-27】以下程序的输出结果是() main( ) {int a=1,b=2,c=3,k; k=a+++b+++c++; printf(“\n%d,%d,%d,%d”,a,b,c,k);} A.1,2,3,6 B.2,3,4,6 C.1,3,3,7 D.1,3,3,6 解析:表达式a+++b+++c++应理解为:(a++)+(b++)+(c++)所以执行此语句后,k的值是6,a的值是2,b的值是3,c的值是4,输出结果为:2,3,4,6。 答案:B 【例题3-28】下面正确的语句是() A. int x=y=25; B.int z=(x+y)++; C. x=+8= =7; D.x % =4.5; 解析:在c 语言中,可以在定义变量时对变量初始化,例如:int x=25,但是,对几个变量赋同一初值时不允许以A方式定义变量或对变量初始化。选项B中++运算符只能用于变量不能用于常量或表达式,在本题中不能用于表达式(x+y)。选项D 中,%运算符的操作是只能是整形数据,不能是4.5。选项c中的运算符“==“比”=“的优先级高,先进行”+8==7“的运算,其值为假(0),再赋值给x,选项c是正确的赋值语句。况且排除A、B、D只有C选项是正确的。 答案:C 【例题3-29】若x,y都是整形变量,x=100,y=200,执行完printf(“%d”,(x,y));后,输出结果是 A.200 B.100 C.100 200 D. 输出格式不符,输出不确定的值 解析:c语言将逗号作为一种特殊的运算符,其求值过程是分别求两个表达式的值,并以表达式2的值作为整个逗号表达式的值。 答案:A 【例题3-30】若有说明语句int i,j; 则计算表达式i=(j=3,j++,j=5,j+5)后i的值为 A.3 B.4 C.5 D.10 解析:求逗号表达式的过程是:先求表达式1的值,再求表达式2的值,最后求表达式n的值,整个逗号表达式的值等于表达n的值) 答案:D 【例题3-31】下述程序运行的结果是: main( ) {int x,y; x=sizeof 3.14*5; y=sizeof(3.14*5); printf(“%d,%d\n”,x,y);} A. 15,4 B. 40,8 C. 8,8 D. 20,8 解析:sizef运算符的优先级高于算术运算符的优先级,所以sizeof 3.14*5相当于(sizeof 3.14)*5为8*5得40。sizeof(3.14*5)为sizeof(15.70)其值为8(c语言中规定每个实型常量当作双精度处理)。 答案:B 第四章最简单的C 程序设计 ——顺序程序设计 4.1重点、难点 1.C语言中的语句 C语言中的语句用来向计算机发出命令,一个语句经编译后产生若干条机器指令。一个C源程序由若干函数构成,每个函数应包含若干条语句。在C语言中所有语句都是“可执行语句”,没有非执行语句。 C语言中的语句分为简单句和复合句两大类,简单句由分号(;)结尾,表示一个语句的终结,分号必不可少;复合句用一对花括号({ })把一些语句括起来。注意:“}”后不必加“;”,但复合语句中最后一个语句中的“;”不能忽略不写,例如:{y=a+b;printf(“%d”,y);}。复合语句中的每一个语句可以是任意语句,当然包括复合语句本身,复合语句在形式上是多个语句的组合,但在语法上只相当于一个简单语句,可出现在任何简单语句可以出现的地方。 C语言中的语句按语句功能又可分为以下四类: (1)控制语句(共9种),完成一定的控制功能。 ①if(条件) 语句 else 语句 (条件语句) ②for(表达式1;表达式2;表达式3)循环体 (循环语句) ③while(条件)循环体 (循环语句) ④do 循环体 while(条件) (循环语句) ⑤continue (结束本次循环语句) ⑥break (中止执行switch或循环语句) ⑦switch (多分支选择语句) ⑧goto (转向语句) ⑨return (从函数返回语句) (2)函数调用语句。由一次函数调用加一个分号(;)构成。例:printf(“This a C program”); (3)表达式语句。由一个表达式加一个分号(;)构成。例:area=3.1415926*r*r; (4)空语句。只有一个分号(;)构成,它什么也不做。有时用来做循环体,表示循环体什么也不做。 2.常用的顺序执行语句 1.赋值语句 由赋值表达式加上分号构成。注意:a=x+y 和a=x+y; 意义不同,前者是表达式,后者是表达式语句。 2.输入输出函数调用语句 C语言本身没有提供输入输出语句,而是在标准库函数中定义了一系列的输入输出函数,通过对这些函数的调用来实现数据的输入输出,如果在调用这些函数的结尾加上“;”,就构成了输入输出函数调用语句。注意:在使用标准输入输出(I/O)库函数时,要用#include“stdio.h”预编译命令将“stdio.h”文件包括到用户源文件中。 (1)字符输入函数 调用形式:getchar() 作用:从终端(或系统隐含指定的输入设备)输入一个字符。注意:getchar没有参数,函数值是从输入设备得到的字符。该函数可以不赋给任何变量直接输出,例putchar(getchar())。 (2)字符输出函数 调用形式:putchar(c) 作用:向终端输出一个字符。函数值是输出的字符。C可以是字符型常量、字符型变量、整形常量、整形变量及其表达式。 (3)格式输出函数 形式:printf(格式控制字符串,输出表列) 例如:printf(“%d,%d”,a,b) 作用:选择输出数据的格式,向终端(或系统隐含指定的输出设备)输出若干任意类型的数据。 说明: ①格式控制字符串是用双引号括起来的字符串,用于描述输出项的输出方式,包括两类,一类是普通字符和转义字符,普通字符(如上例中的“,”)按原样输出,转义字符按转义后的字符输出;另一类是以%号开头的格式说明符,用于描述输出数据的显示格式。若输出%本身,应在%前加一个%,即两个%连写。 ②输出表列为若干个输出项,应尽可能与格式控制串中以%号开头的格式控制说明符在数量、数据类型、顺序上保持一致。如果格式控制说明符与输出项数据类型不一致,以格式说明符为主;如果输出项的个数多余格式控制说明符的个数,没有格式控制说明符的输出项不输出;如果格式控制说明符的个数多余输出项的个数,按格式控制说明符输出对应的输出项的值,对应多余的格式控制说明符输出随机值。 ③常用格式说明如下: 1)d格式(输出十进制整数) %d 按数据实际长度输出; %md m为正整数,是指定的输出字段宽度,若实际数据位数<m ,则数据左端补空格,若实际数据位数≥m ,按实际位数输出(保证数据正确,突破场宽)。 %-md m是指定的输出字段宽度,若实际数据位数<m ,则数据右端补空格,若实际数据位数≥m ,按实际位数输出。 %ld 输出长整形数据,也可在l之前加上m。 2)o格式(输出八进制整数) 把内存单元(连同符号位)一起按八进制形式输出,因此不会输出负号,同d格式符一样,有%o,%mo,%lo等形式。 3)x格式(输出十六进制整数) 类似o格式,也有%x,%mx,%lx等形式,不会输出负值。 4)u格式(以十进制形式输出unsigned数据类型) 5)c格式(用以输出一个字符,其范围为ASCII码在0——127之间) 6)s格式(用于输出字符串) %s 按原样输出字符串。例:printf(“%s”,“China”);输出China(注意:不输出双引号); %ms 当字符串长度大于m时,按实际长度输出,否则左边补空格; %-ms 当字符串长度小于m时右边补空格; %m.ns 输出m列,但只取字符串左边的n个字符,左边补空格; %-m.ns 同上,但n个字符右边补空格。 7)f格式(以小数点形式输出实数) %f不指定宽度时,由系统自动确定,整数部分全部输出,小数部分输出六位,但有效数位单精度为7位,双精度为16位。 8)e格式(以指数形式输出实数) %e数值按规格化数形式输出(即小数点前有一位非零数字),系统自动确定6位小数部分和4位阶码部分(其中e本身占一位,阶符一位,阶二位,例如e+03)。 %m.ne %-m.ne m为总列数,n为小数点后位数。 9)g格式(输出实数,系统自动选用e或f且不输出无意义的0) 系统自动在e或f格式中选择输出宽度小的一种方式。 表4-1、表4-2总结了printf的各种格式字符及附加格式字符(修饰符)。 4-1 printf格式字符表 格式字符 说 明 格式字符 说 明  d,i 以带符号的十进制形式输出整数(正数不输出+) f 以小数形式输出单、双精度数(小数部分占6位)  o 以八进制无符号形式输出整数(不输出前导符0) E,e 以指数形式输出单、双精度数(6位小数,4位阶码)  X,x 以十六进制无符号形式输出整数(不输出前导符0x),用X时,则以大写字母输出 g 选%f或%e格式中输出宽度较短的一种格式,不输出无意义的0  u 以无符号的十进制形式输出整数 s 输出字符串  c 以字符形式输出一个字符    表4-2 printf附加格式字符表 附加字符 说 明  字母l 用于长整形数据的输出,可加在d,o,x,u的前面  正整数m 指定输出数据的宽度,实际数据超出时,按实际输出,可加在d、o、x、u的前面  正整数n 输出实数时,保留n位小数;输出字符串时,表示截取n个字符  - 输出项在输出域内左对齐排放,不加-号的格式输出项按默认的右对齐输出  + 无论正、负数,输出时都带符号位  表4-3 scanf格式字符表 格式字符 说 明 格式字符 说 明  d,i 输入有符号的十进制整数 c 输入单个字符  u 输入无符号的十进制整数 s 输入字符串,将字符串送入一个字符数组中, 输入时以非空白字符开始,以第一个空白字符结束。字符串以串结束标志’\0’作为其最后一个字符  o 输入无符号的八进制整数 f 输入实数,可以用小数形式或指数形式  X,x 输入无符号的十六进制整数 (大小写作用相同) E, e, G, g 与f 作用相同, e与f, g可以互相替换(大小写作用相同)  表4-4 scanf附加格式字符表 附加字符 说 明  字母l 用于长整形数据的输入(加在d,o,x的前面)及double型数据(%lf、%le)的输入  字母h 用于短整形数据的输入  正整数m 指定输入数据的宽度,按指定宽度取值赋给相应变量  * 对应的输入项不赋给相应的变量,跳过此数据  (4)格式输入函数 形式:scanf(格式控制字符串,地址表列) 作用:按格式控制串的格式从终端为一个或多个不同类型的变量赋值。 说明: ①scanf函数的格式控制字符串与printf函数的格式控制字符串类似,其格式字符及附加格式字符见表4-3和表4-4。注意:除表中给出的大写字符外,其它格式说明符只能用小写字母,如%d不能写成%D。 ②地址表列由若干个地址组成,可以是变量的地址(如&a)或已经赋值的指针变量(第十章介绍)。 ③在输入数据时,数据分隔方法。 1)当格式控制字符之间不包括其它字符,且输入的是数值型数据(整形或浮点型)时,数据之间可以用空格、Tab键或回车键(<CR>)分隔; 2)当指定了数据宽度时,可连续输入数据。 例如:假设 int a;float b,c;执行scanf(“%2d%3f%4f”,&a,&b,&c);语句时,输入数据123456789<CR>,相当于a=12,b=345.000000, c=6789.000000 3)当输入的是数值型(整形和浮点型)和字符型,且两种类型交错出现,可连续输入,系统自动识别。 例如:假设 int a;char b;float c;执行scanf(“%d%c%f”,&a,&b,&c);语句时,输入数据1234c6789<CR>,相当于a=1234,b=’c’, c=6789.000000。当遇到“c”时,系统知道相应于“&x”项的输入已经结束,“c”是相应于“&y”项的输入,只接收一个字符,余下的是相应于“&z”的输入。 4)如果格式控制字符串中有其它字符,输入数据时必须在相应位置输入这些字符。当这类普通字符出现在数值型数据之间时,可以当成数据分隔符。 例如:假设 int x;float z;执行scanf(“%d,%f”,&x,&z);语句时,输入数据12,34<CR>,相当于x=12,z=34.000000,普通字符“,”看成数据间隔符。 5)当输入字符数据时,空格、回车、Tab键和转义字符均为有效字符输入,所以此时不能使用间隔符, 例如:scanf(“%c%c%c”,&c1,&c2,&c3);当输入数据abc<CR>,相当于c1=‘a’,c2=‘b’,c3=‘c’。 ④输入数据时不能规定实型(即浮点型)数据精度,例如:scanf(“%7.2f”,&a)是不合法的。 ⑤输入数据时遇到下列情况之一,认为输入数据结束。 空格、回车、Tab键 遇到宽度满足时,例如:“%4d”,只取4列。 遇到非法输入。例如:scanf(“%d%c%f”,&x,&y,&z);当输入数据12345a12o.56<CR>(其中把数字0错打为英文字母o,则x=12345,y=‘a’,z=12.000000。 ⑥“%”后的附加说明符“*”用来表示本输入项在读入后不赋值给相应的变量,即跳过它对应的数据。 例如:int a;char c;scanf(“%2c:%*2d%d”,& c,& a); 运行时,输入数据AB:1020<CR> “AB”是对应格式“%2c“的输入,则c=‘A’,普通字符“:”原样输入,“10”是对应格式“%*2d”的输入,跳过这两位数10,将20赋值给变量a,使a=20。 4.2例题 一、填空 【例4-1】已知boy=54,girl=46, (1)如果想输出boy=54%,girl=46%,则输出部分应为: ; (2)如果想输出boy=54%,girl=46% 则输出部分应为: ; 解析:(1)中的“boy=”、“,”、“girl=”为普通字符,原样输出,在格式串中直接写出,“%” 在格式串中用“%%”表示,“54 ” 在格式串中用%d控制,与输出项boy对应,“46 ” 在格式串中也用%d控制,与输出项girl对应。(2)与(1)不同之处在于分两行输出,用“\n”控制。 答案:(1)printf(“boy=%d%%,girl=%d%%”,boy,girl); (2)printf(“boy=%d%%\ngirl=%d%%”,boy,girl); 【例4-2】若有程序 main() { int i,j; scanf("i=%d,j=%d", &i,&j); printf("i=%d,j=%d\n",i,j); } 要求给i赋10,给j赋20,则应该从键盘输入 。 解析:scanf函数中的格式串中的“i=,j=”为普通字符,原样输入,其中“,j=”又在两个%d中间起分隔作用。 答案:i=10,j=20<CR> 【例4-3】若使用语句:scanf(" %d ,%d ",&x1,&x2); scanf(" %3c%c ",&x3,&x4); 将10、20、a、b分别赋值给整型变量x1、x2、x3、x4,应按 方式输入数据。 解析:本题考查的是scanf函数的数据输入间隔问题。" %d ,%d "用普通字符“,”间隔,输入时,原样输入;%d和%3c之间属于不同数据类型的控制字符,自动分隔,不用间隔符;%3c%c用字符宽度间隔,也不用间隔符。 答案:10,20a**b (说明:**也可换成其它字符) 【例4-4】以下程序运行后的输出结果是 _______ 。 main() { int x,y; x=97; y=097; printf(“%d %d\n”,x,y); printf(“%c %c\n”,x,y); } 解析:x,y均为整型变量,第一个输出语句按十进制整型输出,且数据之间有一个空格,按原样输出, 第二个输出语句按字符型数据输出。注意:y应转成十进制形式输出。 答案:97 79 a 0 【例4-5】写出下面程序的结果 。 main() { int x=34;float y=45.9867; printf(“%6D,%6.2F\n”,x,y); /* ①输出为:%6D,%6.2F */ printf(“%6d,%6.2f\n”,x,y); /* ②输出为:□□□□34,□45.99 */ printf(“%-6d\n”,x); /* ③输出为:34□□□□ (右补4个空格)*/ printf(“%06d\n”,x); /* ④输出为:000034(左补4个0)*/ printf(“%-06d\n”,x);} /* ⑤输出为:34(0将失去作用) */ 解析:格式控制符应为小写字母,输出语句①由于D、F是大写字母,所以它们被当成普通字符,原样输出;输出语句②的d、f是格式控制字符,6和6.2是附加格式说明符,要求x按6位输出,(左补4个空格),y按6位输出,四舍五入保留2位小数(左补一个空格,小数点占一位);输出语句③要求x按6位输出,附加格式说明符-要求右补4个空格;输出语句④要求x按6位输出,附加格式说明符0要求左补4个0;输出语句⑤要求x按6位输出,附加格式说明符-0要求右补4个0,0将失去作用,否则出现错误。 答案:输出结果在题右侧。 【例4-6】以下程序段的输出结果是 。 int a,c; unsigned d; float b,e; a=3.5+3/2; b=23; c=’\xe0’; d=-1; e=2.555555555; printf(“\n%d,%f,%d,%u,%f”,a,b,c,d,e); 解析:(1)变量a的值是4(3.5+3/2=3.5+1再取整,而不是3.5+1.5)。(2)b=23;将整形数据转换成实型数据后再输出。(3)转义字符’\xe0’在内存中占一位,其存储形式为:1110 0000,将其赋值给int型变量c,系统将其赋给c的低八位,自动将高八位补1111 1111,即c变量的值为1111 1111 1110 0000,十进制输出是-32。(4)d=-1;-1在内存中表示为:1111 1111 1111 1111,数据中16位原样赋值给变量d。输出65535。(5)e=2.555555555;将double浮点数按四舍五入转换成单精度赋值给变量e。 本题主要考查不同类型的数据之间的赋值、数据在内存中的存储形式。提示:我们在编写程序时也应像本题一样,注意输出项的数据类型与格式控制符保持一致。 答案:4,23.000000,-32,65535,2.555556 二、选择 【例4-1】下列语句中,不正确的是 。 A.c=2*5; B.{x++= =8; x/=4; } C.; D.c=a+b 解析:本题主要考查语句的基本概念,c语句由分号(;)结尾,表示一个语句的终结,分号必不可少,所以D错。B是复合语句; C是空语句。 答案:D 【例4-2】以下 是非法函数调用语句。 A.char c=’T’; B.char c; putchar(c); getchar(c) ; C.putchar(0x61); D.putchar(‘a’+1); 解析:getchar函数没有参数,调用函数形式为getchar( ) putchar函数有参数,调用函数形式为putchar(c), c可以是字符型常量、字符型变量、整型常量及其表达式。 答案:B 【例4-3】若输入12345、abc下列程序的输出结果为: 。 main() { int a; char ch; scanf(“%3d%3c”,&a,&ch); printf(“%d,%c”,a,ch); } A.123,abc B.123,4 C.123,a D.12345,abc 解析:本题输入项为两个不同的数据类型,且指定了数据宽度,所以按指定宽度截取数据为变量赋值,整形变量a=123,字符型变量ch为一个字符,取截取的三个字符中的第一个,即ch=‘4’;输出时,注意“,”原样输出。 答案:B 【例4-4】有如下语句:long int a;double x ;unsigned y ;char ch;则 是合法的输入语句。 A. scanf(" %ld %lf%x%c ",a,x, y,ch); B. scanf(" %d%5.2f %d%c ",&a,&x,&y,&ch); C. scanf(" %ld %lf %o%c",&a,&x,&y,&ch); D. scanf(" %ld%f %x%c",&a,&x,&y,&ch); 解析:本题考查的是输入语句的格式控制字符串应与输入项在数据类型上应保持一致。,所以B错,另外B的%5.2f加了精度限制,也属于错误之处;双精度型变量x应与格式控制符%lf对应,所以D错;无符号型变量y应与格式控制符%u、%x或%o对应;字符型变量ch应与格式控制符%c或%d对应,用%d作为格式控制符时,从键盘上输入的数据应为输入字符的ASCII码值。A错在输入项是变量名本身,应该是变量的地址。 答案:C 【例4-5】有如下定义和输入语句,若要求a,b,c,d的值分别为23,45,’M’,’N’,正确的输入应该是 。 int a,b; char c,d; scanf(“%d%d”,&a,&b); scanf(“%c%c”,&c,&d); A.2345MN<CR> B.23 45<CR> MN<CR> C.23 45 MN<CR> D.23 45MN<CR> 解析:A是错误的,它使a=2345。因为输入数据时,23和45之间没有数据分隔符。选项B是错误的,它使a=23,b=45,c是“回车”,d=’M’。选项C也是错误的,它使a=23,b=45,c=’□’,d=’M’。因为以%c格式输入字符数据时,对应的变量只接收一个字符。“空格”和“回车”也是字符数据。 本题主要考查scanf函数的使用。注意:以%c格式输入字符数据时,对应的变量只接收一个字符。“空格”和“回车”也是字符数据。 答案:D 【例4-6】以下程序段的输出结果是 。 float x=32.76888888; printf(“%6.3f,%f”,x,x); A.32.769,32.76888888 B. 32.769,32.768890 C. 32.76888888,32.7688888 D.有语法错误,无输出 解析:%6.3f要求输出占6位(包括小数点),小数部分按四舍五入取值,占3位,输出32.769。%f 要求正常按实数输出,7位有效数字,小数部分取6位,输出32.768890,3,2,7,6,8,8,9为有效数字,0为随机值。 答案:B 【例4-7】若int类型数据占两个字节,则执行以下语句的输出为 。 int x=-7; printf(“%d,%u\n”,x,x); A. -7,-7 B.-7,32762 C.-7,-32762 D.-7,65529 解析:首先明确变量的值在内存中的存放形式(以补码存放),x=-7在内存中的存放形式是:1111 1111 1111 1001,当把x的值按“%d”格式(有符号整数)输出时为-7;按“%u”格式(无符号整数)输出时为65529。本题如果将k以无符号整数格式“%o”和 “%x”输出时结果为17771和fff9。 答案:D 【例4-8】以下程序段的输出结果是 。 int k=32768; printf(“%d”,k); A.0 B.-32768 C.-1 D.有语法错误,无输出 解析:int 型数据的范围是:-32768~32767,k值超出了int型数据的范围,系统做出了溢出处理,将该数减去整型数据类型的模(即该类型数据能表示的数据的个数,基本整形的模为65536),所以32768-65536=-32768。 答案:B 【例4-9】在下述输出语句的输出结果中,正确的是 。 int x=0xabcdefL; long int y=0x11; printf(“%x,%ld”,x,y); A.输出值为cdef,17 B.非法赋值 C.输出值不确定 D.输出值为0xabcdef,0x11 解析:Long型数据0xabcdef赋给int 型变量x,系统自动对其进行类型转换,将数据中低16位cdef赋值给x,舍弃高16位的0x00ab。 将0x11赋值给long型变量y,其值送到y的低16位,因为0x11是正数,所以,变量y的高16位补0。 高16位 低16位  整形变量0xabcdefL 0x00ab 0xcdef  int型变量x得到 - 0xcdef  整形常量0x11 - 0x0011  long型变量y得到 0x0000 0x0011  答案:A 【例4-10】已知字母‘a’的ASCⅡ码值是97,以下程序的输出是 。 #include<stdio.h> main() {char a=’A’; int b=20; printf(“%d,%o”,(a=a+1,a+b,b),a+’a’-‘A’,b); } A.66,141 B.20,141 C.20,141,20 D.66,97 解析:本题printf函数的格式控制字符串“%d,%o”有两个格式描述项,输出项有三个(a=a+1,a+b,b),a+’a’-‘A’,b,那么,只能有两个数据输出,多余的输出项b不输出。 第一个输出项是逗号表达式,其值为20;第二个输出项是char型数据参与算术运算,‘a’的ASCII码值是97,所以第二个输出项的值的十进制表示是97,以八进制格式输出为141。 本题主要考查格式输出printf函数。如果输出项的个数多余格式控制说明符的个数,没有格式控制说明符的输出项不输出;如果格式控制说明符的个数多余输出项的个数,按格式控制说明符输出对应的输出项的值,对应多余的格式控制说明符输出随机值。 答案:B 【例4-11】已知:’a’的ASCⅡ码值为97,’0’的ASCⅡ码值为48,下述程序的输出结果是 。 #include<stdio.h> main() {printf(“%s”,”\tabc%L\’\”\060\x61\081”);} A.□□□□□□□□abc%L’”0a B.\tabc%L\’\”\060\x61\081 C.abc%L’”0a D.abc%L’”0a\081 解析:(1)转义字符“\t”(横向跳格,跳8个字符)使后面的输出跳到下一个输出位(第9个字符位置)。(2)abc为普通字符,原样输出。(3)对应“\’\””的输出是“’””。(4)“%”后面“L”不是格式字符,所以“%”作为普通字符原样输出。也可以用连续两个“%”,输出一个“%”。(5)“\060”和“\x61”是转义字符,“\060”是八进制的转义字符,八进制数“060”作为ASCII码值表示的是字符’0’,“\x61”是十六进制的转义字符,十六进制数“61”作为ASCII码值表示的是字符’a’。(6)\081不是八进制数“081”作为ASCII码值表示的转义字符,因为数字8不是八进制数码,所以’\0’、’8’、’1’分别作为字符处理,’\0’是空字符,字符串到此结束,因此,’8’、’1’没有输出。 本题主要考查printf函数及转义字符。 %s格式表示输出字符串,遇到第一个空字符’\0’结束。注意’\0’和’0’是不同的两个字符,’\0’的ASCII码值为0,’0’的ASCII码值为48。 特别提醒:读者在输出“\、’、”、%”等符号时,最好加在“\”之后,但在能够分辨它是普通字符时,不加“\”也可以,如“a’b”能够认出这里的“’”是普通字符。在不能够分辨时,就会成为错误常量,如”a”b”,不能够认出这里的“””是普通字符,还是括起字符串的双引号,此时必须写成”a\”b”,才能认为是要输出“””号。 答案:A 三、判断下列叙述是否正确,错的加以改正 【例4-1】语句printf(“%s”,’a’); 的输出结果为字符a。 解析:此语句是错误的,因为‘a’是一个字符,占用一个字节,而%s是字符串格式控制符,二者类型不一致,输出结果不确定。 改正:语句printf(“%s”,”a”); 的输出结果不确定。 【例4-2】语句printf(“%c”, ”a”); 的输出结果为字符a。 解析:错误。 因为”a”是字符串,占用两个字节,而%c是单个字符格式控制符。与上题错误类型一样,编写程序时要注意:输出项和格式控制符在数据类型上应保持一致。 改正:语句printf(“%c”,’a’);的输出结果不确定。 第五章 选择结构程序设计 5.1重点、难点 1.关系运算符和关系表达式: 关系运算符: (1)关系运算符有六种:>(大于)、<(小于)、>=(大于等于)、<=(小于等于)、==(等于)、!=(不等于)。 (2)关系运算符的优先级:前四种关系运算符(>、<、>=、<=)的优先级相同,后两种关系运算符(= =、!=)的优先级也相同,前四种关系运算符的优先级高于后两种关系运算符的优先级。此外,所有关系运算符的优先级别都低于算术运算符。 例如:表达式a>b<c等价于(a>b)<c;表达式a= =b>c等价于a= =(b>c);表达式a<b!=b>=c等价于(a<b)!=(b>=c);表达式a= =b+c>d等价于a= =((b+c)>d)。 (3)结合方向:自左向右。 关系表达式:用关系运算符将两个表达式连接起来,构成关系表达式,其值是一个逻辑值,即“真”或“假”,以1代表“真”,以0代表“假”。 注意: (1)赋值运算符“=”与关系运算符“= =”是不同的。在语言中=表达赋值,= =表示判定,书写时注意区分。 (2)关系运算符“<=”、“>=”“!=”不要写成“≤”、“≥”、“≠”。 2.逻辑运算符和逻辑表达式: 逻辑运算符:!(逻辑非)、&&(逻辑与)、||(逻辑或)。三种运算符的优先次序按!、&&、||的顺序递降。 逻辑表达式: 用逻辑运算符将关系表达式或任意数据类型(除void外)的数据连接起来构成逻辑表达式。 逻辑表达式的值是一个逻辑值“真”或“假”,C语言编译系统时,在给出逻辑运算结果时,将一个非零数值认作为“真”,以数值1代表“真”,以数值“0”代表“假”。 在逻辑表达式的求解中,并不是所有有运算符都一定被执行,只有必须执行下一步逻辑运算符才能求出表达式的解时才执行该运算符。 注意: (1)在逻辑运算中,并不是只有1才能代表真,而是所有非0的值都表示逻辑真,只有0表示逻辑假,例如:常数7或‘A’若用于逻辑操作都可以代表逻辑真。 (2)算术运算符、逻辑运算符、关系运算符三者的优先级关系为: 逻辑非!→算术运算符→关系运算符→逻辑与&&、逻辑或|| (高) (低) 逻辑运算 x y !x !y x&&y x||y  真 真 假 假 真 真  真 假 假 真 假 真  假 真 真 假 假 真  假 假 真 真 假 假   3.条件运算符: 条件运算符“?:”要求有三个操作对象,是C语言中惟一的一个三目运算符。由条件运算符构成的条件表达式的一般形式为: 表达式1?表达式2:表达式3 当表达式1的值为非零时,取表达式2的值作为此条件表达式的值。 当表达式1的值为零时,取表达式3的值作为此条件表达式的值。 条件运算符具有自右向左的结合性,其优先级比关系运算符、逻辑运算符和算术运算符都低。 4.if 语句 if语句的三种形式: (1)if(表达式) 语句 当表达式为“真”(非零)时,执行该语句,否则不执行该语句。 例如:if(x>y) printf(“%d”,x); (2)if(表达式) 语句1 else 语句2 当表达式为“真”(非零)时,执行语句1,否则执行语句2 例如:if(x>y) printf(“%d”,x); else printf(“%d”,y); (3)if(表达式1) 语句1 else if(表达式2) 语句2 else if(表达式3) 语句3   ┊ else if(表达式m) 语句m else 语句n 例如: if(n>500) cost=0.15 else if(n>300) cost=0.10 else if(n>100 ) cost=0.075 else if(n>50) cost=0.05 else cost=0 注意: (1)if语句中的表达式通常为关系表达式或逻辑表达式,实际上它可以是任意表达式,只要表达式的值非0即为真。 例如:if(3) x=10; if(‘a’) x=6; (2)if ….else….是一个语句(不是两个语句),else子句不能作为语句单独使用,它必须是if语句的一部分与if配对使用。 (3)if语句的嵌套:在if语句中又包含一个或多个if语句称为if语句的嵌套。在if 语句的嵌套中,有多个if和else。else和if的配对原则是:一个else应与其之前距离最近且没有同其它else配对的if配对。 (4)如果if或else后面的语句包括多条,则必须用一对花括号括起来作为一条复合语句使用,复合语句是一条完整的语句,花括号后面不需要再加分号。 例如:if(a+b>c&&b+c>a&&c+a>b) {s=0.5*(a+b+c); area=sqrt(s*(s-a)*(s-b)*(s-c)); printf(“area=%6.2f\n”,area); } else printf(“it is not a triangle\n”); 这里首先判断a,b,c能否构成三角形,若能,则计算并打印三角形面积;否则打印有关信息。 5.switch语句: switch语句是多分支语句。例如:学生成绩分类,人口统计分类,工资统计分类,银行存款分类等等,当然这些可以用嵌套if语句来处理,但如果分支较多,则嵌套的if语句层数多、程序复杂、可读性降低,所以,C语句提供switch语句来处理多分支选择,它的一般格式如下: switch(表达式e) {case 常量表达1:语句组1 case 常量表达2:语句组2      ┊ case 常量表达n:语句组n default:语句组n+1 注意: switch之后的表达e应为定点类型,包括整形、字符型和枚举类型。若使用了其他类型,如float类型,将被截断转换为定点数。 每一个case子句之后只能是定点常量表达式,如3+2,或NUM+10,其中NUM是符号常量,注意不能含有变量或是非定点数。 一个switch语句中,任何两个case子句之后的值不能相等。 每一个“case值”只是一个入口,一旦某个值与表达式e的值相等,则从此入口,开始一直执行,直到switch语句的最未或遇到break语句才会结束,其他的“case”值全部失效,不会再与表达式e进行比较。 break语句是可选的,也可以有若干个case子句共用一个break语句结束。 default子句是可选的,也不必一定要置于最后的位置。它的地位与其他case子句相同,也表示一种情况。无论default子句置于什么位置,含义是固定的,只有在与其他case子句都不匹配时才与此情况匹配。 switch语句可以嵌套,case子句中可以再使用其他switch语句。 只要合理安排各种情况的结束语句(如break语句),这些情况的排列次序不会影响switch语句的运算结果。 5.2例题 【例5-1】语句:printf(“%d”,(a=3)&&(b=-3));的输出结果是( ) A.无输出 B.结果不确定 C.-1 D.1 解析:C语言中,非零为“真”,“真”用1表示;(零为“假”;“假”用0表示。用&&表示逻辑与,||表示逻辑或),本题中,a、b都为真,相与后也为真。 答案:D 【例5-2】当C的值不为0时,在下列选项中,能正确将C的值赋给变量a、b的是( )。 A.c=b=a; B.(a=c)||(b=c); C.(a=c)&&(b=c); D.a=c=b; 解析:A、D明显不符合要求;B选项中表达式(a=c)的值由题目C的值不为0时的条件可知为真,逻辑或只要有一个真,结果就是真,所以不再对(b=c)求值。 答案:C 【例5-3】设int x=2,y=1;表达式(!x||y––)的值是( ) A.-2 B.1 C.2 D.-1 解析:从运算符的优先次序上分析,表达式!x||y––的!和–处在同一优先级别,高于||运算符,而y––是用了y以后自减,y的初值为1,即真,||运算只要有一个是真,结果就是真。 答案:B 【例5-4】当m=4,n=3,p=1时,表达式k=m>n>p的值是: A.1 B.0 C.true D.false 解析:m>n>p相当于(m>n)>p,m>n的值为1,1>p为假,用0表示,所以k的值为0。 答案:B 【例5-5】设有x=1,y=2,z=3,w=4,a=2,b=2;执行(a=x>y)&&(b=z>w)后,a和b的值为: A.0和0   B.0和2    C.3和1      D.4和2 解析:因为x=1,y=2所以表达式(a=x>y)的值为假,对于(b=z>w)并没执行,所以b的值还是2,a的值为0。 答案:B 【例5-6】下列程序的输出结果是: # include <stdio.h> main( ) {int x=-1,y=4; int k; k=x++<=0&&!(y--<=0); printf(“%d,%d,%d”,k,x,y); } A.0,0,3 B.0,1,2 C.1,0,3 D.1,1,2 解析:本题的主要测试点也在运算符的优先级和结合性,表达式k=x++<=0&&!(y--<=0)的运算顺序是:(先取x的值与0比较,-1<=0成立,为真,x再进行自增,值为0,&&运算符前面的表达式为真,所以要计算后面的表达式。(2)表达式(y--<=0)的计算顺序是先取y的值与0比较,为假,y再进行自减,值为3。(3)表达式!(y--<=0)是对(y--<=0)的值取反,所以为真。(4)&&运算符后面的表达式也为真,所以整个表达式结果为真,其值表示为1最后赋给变量k. 答案:C 【例5-7】若x=3,y=4,z=5,a是一个变量,试计算出下述表达式的值。 (1)!(x>y)&&!z||1 (2)!(a=x)&&y>z&&0 (3)!(x+y)+z-1&&y+z/2 解析:对于表达式(1),由优先次序知,该表达式等价于(!(x>y))&&!z)||1,相当于m||1,不论m是何值,表达式皆为1。 对于表达式(2),类似地,它相当于m&&0,故结果为0。注意a=x为赋值表达式而非关系表达式,但对计算整个表达式的值无影响。 对于表达式(3),因为表达式!(x+y)+z-1的值为!7+4=0+4=4,表达式y+z/2=4+2=6,说明运算符&&的两个运算数都为真,故原表达式的值为1. 结果:(1)1 (2)0 (3)1 【例5-8】下列程序的输出结果是: # include <stdio.h> void main( ) {int a=0,b=0,c=0; if(++a>0||++b>0) ++c; printf(“\na=%d,b=%d,c=%d”,a,b,c); } A.a=0,b=0,c=0 B.a=1,b=1,c=1 C.a=1,b=0,c=1 D.a=0,b=1,c=1 解析:if中的表达式执行顺序为(1)先执行++a,a的值为1,再执行比较运算a>0,此时a>0为真;(2)运算符||前的操作数为真,不管后面的操作数是否为真,整个表达式的值均为真;所以C语言规定,若运算符“||”前面的操作数为真,后面不必计算,整个表达式为真;同理,若运算符&&前面的操作数为假,后面不必计算,整个表达式值为假,本题中的表达式++b>0不用计算。 答案:C 【例5-9】以下程序的输出结果为: int a,b,c; a=1;b=5;c=3; if(a>b) a=b, b=c; c=a; printf(“a=%d,b=%d,c=%d”,a,b,c); A.a=1,b=5,c=1 B.a=1,b=3,c=1 C.a=5,b=3,c=1 D.a=5,b=3,c=5 解析:注意到a=b,b=c;是一个语句,是if的子句,语句c=a;与if语句无关,且表达式a>b为假,故只有c=a;被执行。 答案:A 【例5-10】以下程序的运行结果是: # include <stdio.h> void main( ) {int a=1,b=0,x=0,y=0; swich(a) {case 1: switch(b) {case 0:x++;break; case 1:y++;break; } case 2: x++;y++;break; case 3: x++;y++; } printf(“\nx=%d,y=%d”,x,y); } A.x=1,y=0 B.x=2,y=1 C.x=1,y=1 D.x=2,y=2 解析:程序开始执行时a=1,故执行内嵌的switch语句。因为b=0,计算x++,使变量x的值为1,并中止内层switch结构,回到外层。然后,程序继续执行“case 2:”后面的语句“x++;y++;”,这使变量x、y的值分别为2和1,外层switch语句结束。 答案:B 【例5-11】假设k分别为7,6,5,下面程序的输出结果是: # include <stdio.h> void main( ) {int s=0,k; scanf(“%d”,&k); switch (k) {case 1: case 4: case 7: s++; break; case 2: case 3: case 6: break; case 0: case 5: s+=2; break; } printf(“s=%d”,s);} 解析:(1)当k=7时,执行switch语句中的case 7:后的语句块,变量s的值为1。(2)此时变量k的值为6,执行switch语句中的case 6:后的语句块,变量s的值不变,(3)此时变量k的值为5,执行switch语句中的case 5:后面的语句块,变量s的值为3。(4)此时变量k的值为4,输出s的结果,结束程序。 答案:s=3 【例5-12】读下面的程序,从选项中选择出正确的输出结果: # include <stdio.h> main( ) {int a=50,b=20,c=10; int x=5,y=0; if(a<b) if(b!=10) if(!x) x=1; else if(y) x=10; x=-9; printf(“%d”,x);} A.10 B.-9 C.1 D.5 解析:本题主要测试对if语句和if语句的嵌套的理解,C语言规定:else总是与其前面的没有配对的最近的if语句配对。程序中的else是与if(!x)配对的,不是与if(b!=10),更不是与(if(a<b)配对。我们可以通过对程序画框架来理解: # include <stdio.h> main( ) {int a=50,b=20,c=10; int x=5,y=0; if(a<b) if(b!=10) if(!x) x=1; else if(y) x=10; x=-9; printf(“%d”,x); } 从上面可以看出语句x=-9不属于任一个if语句。 答案:B 【例5-13】指出下面程序的输出结果: # include <stdio.h> main( ) { float c=3.0,d=4.0; if(c>d) c=5.0; else if(c==d) c=6.0; else c=7.0; printf(“%.1f\n”,c); } 解析:本题主要测试对if语句和if语句的嵌套的理解,if语句嵌套在else语句中,请注意C语言中if语句的灵活的书写格式。 答案:7.0 【例5-14】读下面的程序,从选项中选出正确的输出结果: # include <stdio.h> main( ) {int x=10,y=5; switch(x) { case 1 :x++; default :x+=y; case 2:y--; case 3:x--;} printf(“x=%d,y=%d”,x,y);} A.x=15,y=5 B.x=10,y=5 C.x=14,y=4 D.x=15,y=4 解析:switch语句的执行过程如下,首先计算表达式的值,然后用此值来查找各个case后的常量表达式,直到找到一个等于表达式值的常量表达式,则转向该case后面的语句去执行;若表达式的值不等于任何一个case后面的常量表达式的值,则转向default后面的语句去执行,不管default放在switch语句模块中的任何位置,如果没有default部分,则将不执行switch开关语句中的任何语句,而直接去执行switch后面的语句。如果要跳出switch语句,则要用break语句,否则一直执行下去直到switch语句结束。此程序中没有一个常量表达式的值与x的值相等,因此执行default后面的语句,因为没有break语句,所以一直执行下去,依次执行语句y-- 和x--。 答案:C 第六章 循环控制 本部分的重点是程序设计的循环结构,几乎所有实用的程序都包含循环。循环结构是结构化程序设计的基本结构之一,也是答好阅读程序题、完善程序题和程序改错题的关键。要掌握三种循环语句的形式、特点及执行过程。 6.1 重点·难点 1.while语句的一般形式: while(表达式) 语句 2.do……while语句的一般形式: do 语句 while(表达式); 3.for语句的一般形式: for(表达式1;表达式2;表达式3) 语句 说明: 表达式(表达式2)是循环控制条件简称循环条件。三种循环语句共同的特点是:当循环控制条件非零时,执行循环体语句(循环过程中要多次执行的语句),否则终止循环。 while循环和do_while循环的循环控制条件不能为空,for循环的循环控制条件可以为空,为空时C认为循环条件始终为真。 语句可以是任何语句,简单语句、复合语句、空语句均可以。它被称为循环体。 while和for循环先判断循环控制条件,do……while循环后判断循环控制条件,所以while和for循环的循环体可能一次也不执行,而do……while循环的循环体至少也要执行一次。 在循环体内或循环条件中必须有使循环趋于结束的语句,否则,会出现死循环等异常情况。 三种循环语句是可以互相嵌套的,即在一个循环语句内部可以出现另一个循环语句。 4.continue语句和break语句 continue语句只能用于while、for和do……while语句的循环结构中,其作用为:结束本次循环体的执行,直接进行循环控制条件判断,根据判断的结果决定是否再次执行循环体。 continue语句格式为: continue; break语句用在循环语句中,其作用为:终止循环语句的执行。 break语句的格式为: break; 5.goto语句 goto语句为无条件转向语句,其作用为:使程序的执行无条件地转移到指定处。Goto语句是一种非结构化语句,结构化程序设计方法不提倡使用goto语句,因为滥用goto语句将使程序流程无规律,可读性差,但也不是绝对禁止使用,只有在能够大大提高程序效率时才使用。 goto语句的形式为: goto 语句标号; 执行语句时,程序转移到以标号为前缀的语句处继续执行。 C语言允许在任何语句前加一个标号,作为goto语句转向的目标。 给语句加标号的形式: 标号:语句 标号是一个标识符,标号只在其所在的函数内部有效。 6.2例题 (一)选择题 【例6-1】 在循环语句结构中,当循环条件为( )时,执行循环体语句。 A.0 B.false C.true D.非0 解析:当循环控制条件表达式的值非零时为“真”,执行循环体语句。 答案:D 举一反三:在循环语句结构中,循环条件为( )时,结束循环。(答案:0) 【例6-2】 以下程序的运行结果是() main() { int sum=0,i=11; do { sum=sum+i; i++; } while(i<=10); prinf(“sum=%d,i=%d”,sum,i);} A. sum=0,i=11 B. sum=0,i=12 C. sum=11,i=11 D. sum=11,i=12 解析:do-while循环语句的执行顺序是先执行循环体再判断,因此循环体至少执行一次。本题先执行循环体sum=sum+i=11;i++;i增值为12,再判断循环条件(i<=10)不满足,结束循环。 答案:D 举一反三:若将此题循环部分改为: while(i<=10) { sum=sum+i; i++;} 则输出结果为:sum=0,i=11. while循环的执行顺序是先判断循环条件满足后再执行循环体。 【例6-3】 对循环语句:for(表达式1;表达式2;表达式3)语句,以下叙述正确的是( )。 for语句中的三个表达式一个都不能少 for语句中的循环体至少要被执行一次 for语句中的循环体可以是一个复合语句 for语句只能用于循环次数已经确定的情况 解析:选项A是错误的。 如果初始条件使表达式2为零,循环体就不会被执行,所以,选项B错误。for语句对于循环次数不确定的情况也是可以处理的,实际上while语句是能够实现的,for语句都可以实现。选项D是不对的。正确的应该是选项C。 for语句中的三个表达式都是可以省略的。 循环体可以是任何类型的语句。 答案:C 【例6-4】 以下程序段执行后i的值是( )。 int i; for(i=1; i<5; ) i++; A.3 B.4 C.5 D.6 解析:本题的for语句中表达式3省略了,循环变量的改变放在循环体中,先判断表达式i<5,然后执行循环体i++。当i=5时,比较i<5不满足条件,退出循环,此时循环体被执行了4次。 for语句中表达式1~3可以省略,但格式中的“;”不能省略。 答案:C 【例6-5】 以下程序段执行后输出结果是( )。 int i; for(i=1; ;i++); printf("%d",i++); A.1 B.2 C.3 D.死循环 解析:本例中for循环语句的循环控制条件始终为“真”,循环体又是一个空语句,没有break语句可以跳出循环,所以,循环是一个死循环。 for循环语句中省略了表达式2,即认为循环控制条件始终为“真”。 答案:D 【例6-6】 以下程序输出结果是()。 main() { int y=9; for( ;y>0;y--) if(y%3==0) printf("%d",--y);} A.741 B.852 C.963 D.87521 解析:在循环之前已经给y赋初值9,for语句中省略了表达式1,y > 0执行循环体,当y % 3 = = 0为“真”时,输出- - y;否则,不输出,继续执行循环……,当y > 0不成立时,退出循环。 在for循环语句中,通常表达式1是给循环语句中的变量赋初值,如果省略了表达式1,那么要在循环之前给循环语句中的变量赋初值。 答案:B 【例6-7】 执行完循环语句for(i=1,s=0;i++<10;i++) s=s+i; 后,变量i,s的值各是多少?循环体被执行了几次? i的值是11,s的值是30,循环体执行了6次 i的值是12,s的值是30,循环体执行了6次 i的值是11,s的值是30,循环体执行了5次 i的值是12,s的值是30,循环体执行了5次 解析:本题的for语句中循环变量的改变放在表达式2与表达式3中,先判断表达式2:i++<10,其中自增运算符“++”的优先级高于关系运算符“<”,所以先执行表达式i++,表达式的值是i的值,而后i本身再加1,如第一次循环i=1,s=0,表达式i++的值为1,1<10条件成立,而i本身增值为2,此时执行循环体s=0+2=2,然后执行表达式3:i++;i增值为3。依次类推,当i=9时,9〈10条件满足,i本身=10,s=2+4+6+8+10=30,之后执行表达式3:i++;i增值为11,此时比较11<10(不满足条件),i的值为12,退出循环,此时循环体被执行了5次(i=1,3,5,7,9时,执行循环体)。 答案:D 【例6-8】 若x,y是int型变量,执行以下语句后x的值是( )。 for(y=1,x=1;y<=50;y++) { if(x>=10) break; if(x%2==1) {x+=5;continue;} x-=3; } A.1 B.6 C.7 D.10 解析:当x > = 10为“真”执行break;语句或当y < = 50为“假”时,结束循环。 当x % 2 = = 1为“真”时,执行x + = 5;和continue;循环体语句中x - = 3;语句将不被执行,重新开始下一次的循环判断。运行情况见表6-1。 表达式1是一个逗号表达式,可以一次完成对多个变量赋初值的功能,这是for语句很有用的一个特性,要善于使用。 答案:D 表6-1 程序运行情况 执行循环体 x,y的值 x>=10 break; x%2==1 x+=5;continue x-=3; 完成本次循环后x的值  第一次 x=1,y=1 假 不执行 真 执行 不执行 6  第二次 x=6,y=2 假 不执行  假 不执行 执行 3  第三次 x=3,y=3 假 不执行 真 执行 不执行 8  第四次 x=8,y=4 假 不执行  假 不执行 执行 5  第五次 x=5,y=5 假 不执行 真 执行 不执行 10  第六次 x=10,y=6 真 执行,结束循环     【例6-9】 对以下程序段,描述正确的是()。 int k=10; while(k=0) k=k-1; 循环体执行了10次 B. 循环是无限循环 C. 循环体语句一次也不执行 D. 循环体语句执行了一次 解析:表达式k=0是一个赋值表达式而不是一个关系表达式,将0赋值给k,且此表达式的值也为0,所以,循环体语句一次也不执行。 答案:C 举一反三:如果将程序段改为: int k=10; while(k!=0) k=k-1; 那么,循环体将被执行10次。 若再改为: int k=10; while(k==0) k=k-1; 那么,循环体一次也不执行。 【例6-10】 对以下程序段,描述正确的是()。 int k=10; while(k>0) printf("%d,",k); k=k-1; A.被执行了10次 B. 循环是无限循环 C.循环体一次也不执行 D. 循环体语句执行了1次 解析:循环体语句是printf(“%d,”,k);没有使循环趋于结束的语句,导致死循环,k=k-1;不是循环体内的语句。 答案:B 举一反三:如果将程序段改为: int k=10; 或 int k=10; while(k>0) while(k>0) { printf(“%d,”,k); printf(“%d,”,k), k=k-1;} k=k-1; 加一对{}括号,使循环体是一个复合语句或使用逗号表达式语句,那么,循环将是有限次的。 还可以用以下方法,使循环成为有限的。 将原题的printf(“%d,”,k);改成:printf(“%d,”,k - -); 将原题的(k>0)改成:(k - - > 0)并且去掉k=k-1;等等。 【例6-11】 下面程序的运行结果是()。 main() { int i,j,k,x=0; for(i=0;i<2;i++) { x++; for(j=0;j<3;j++) { if(j%2) continue; x++; } x++; } k=i+j; printf("k=%d,x=%d\n",k,x);} A.k=8,x=4 B.k=5,x=8 C.k=3,x=6 D.k=8,x=12 解析:本例是一个二重循环结构,内层循环在j=3时终止,外层循环在i=2时终止,故k=5。本程序的运行情况见表6-2。 表6-2 程序运行情况 初值x=0  i=0 执行x++;(x=1)   外 循 环 j=0 j%2为假 执行x++;(x=2)    j=1 j%2为真 执行continue;    j=2 j%2为假 执行x++;(x=3)    j=3 j<3为假 结束内循环   执行x++;(x=4)   执行x++;(x=5)  i=1 内 循 环 j=0 j%2为假 执行x++;(x=6)    j=1 j%2为真 执行continue;    j=2 j%2为假 执行x++;(x=7)    j=3 j<3为假 结束内循环   执行x++;(x=8)  i=2,i<2为假, 结束外循环(k=i+j=5)  答案:B 举一反三:读者要从循环体的有限次运行中,找出规律,推出问题的结论。在上述程序中,如果将外层循环的条件条件表达式“i<2”改成“i<100”,将内层循环的条件表达式“j<3”改成“j<10”,那么,程序的运行结果又如何呢?(答案:k=110,x=700) (二)阅读程序题 【例6-1】 以下程序的输出结果是() main() { int n; for(n=1;n<=20;n++) { if(n%3==0) continue; printf(“%d,”,n); } } 解析:当n能被3整除时,执行continue语句,结束本次循环(即跳过printf函数语句),只有n不能被3整除时才执行printf函数,所以此题是求在1~20内不能被3整除的数。 答案:1,2,4,5,7,8,10,11,13,14,16,17,19,20, 举一反三:若将本题中的continue;语句改为break;语句,则结果为:1,2,。 break语句的功能是当满足条件时,跳出循环体,结束循环。因此,当n=3时,执行break语句,循环结束。 (三)完善程序题 【例6-1】 为了输出如下图形,请在程序的 处填入合适的内容。 * * * * * * * * * * * * * * * * * * * * main() { int i,j; for(i=1;i<6;i++) { for(j=1;j<5;j++) (1) ; (2) ; } } 解析:使用循环嵌套。循环变量i控制输出行数(共5行),循环变量j控制每行输出“*”的个数,此外,还要考虑在每一行输出完后,光标要换行,所以,第一个空处应该填入printf(“*”),第二个空处应该填入printf(“\n”). 注意:printf(“\n”);语句是外循环体中的一条语句,但它不是内循环体中的语句, 答案:(1)printf(“*”); printf(“\n”); 举一反三: 打印如下图形: * * * * * * * * * * * * * * * * * * * * * * * * * main() { int i,j; for(i=1;i<6;i++) { for(j=1;j<10-i;j++) printf(“ ”); for(j=1;j<6;j++) printf(“*”); printf(“\n”); } } 打印如下图形: * * * * * * * * * * * * * * * * * * * * * * * * * main() { int i,j, k; for(i=1;i<=5;i++) { for(j=1;j<=5-i;j++) /*每行前输出空格*/ printf(“ ”); for(k=1; ;k++) /*每行输出“*”号*/ printf(“*”); printf(“\n”); } } 答案:k<=2*i-1 打印如下图形: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * main() { int i,j; for(i=1;i<10;i++) { if(i<6) { for(j=1;j<6-i;j++) /*输出上半部分三角形*/ printf(“ ”); for(j=1; (1) ;j++) printf(“*”); } else { for(j=1;j<=i-5;j++) /*输出下半部分三角形*/ printf(“ ”); for(j=1; (2) ;j++) printf(“*”); } printf(“\n”); } } 答案:(1)j<=2*i-1 (2)j<=19-2*i 【例6-2】用π/4=1-1/3+1/5-1/7+……公式求π的近似值,直到某一项的绝对值小于10-6为止 #include<math.h> main() { int s; float n,t,pi; t=1;pi=0;n=1.0;s=1; while( ) { pi=pi+t; n=n+2; s=-s; t=s/n;} pi=pi*4; printf(“pi=%f\n”,pi); } 解析:while循环中,若想找到满足循环的条件,可以先判断出循环的结束条件,它的相反值即循环条件。本题的循环结束条件为某一项的绝对值小于10-6,则循环条件为某一项的绝对值大于10-6。 答案:fabs(t)>1e-6 【例6-3】判断m是否素数 main() { int m,i,k; scanf(“%d”,&m); for(i=2;i<m;i++) if( ) break; if(i>=m)printf(“%d是一个素数\n”,m); else printf(“%d不是一个素数\n”,m); } 解析:判断一个数m是否素数的条件是m能否被2~m-1之中任何一个整数整除,如果能整除,则该数不是素数,提前结束循环,此时i必然小于m;如果不能被整除,则在完成最后一次循环后,i还要加1,因此i=m,然后才终止循环。由于有两种情况结束循环,所以在循环之后判别i的值是否大于或等于m,若是,则表明未曾被2~m-1之间任一整数整除过,该数是素数,否则,不是素数。因此本题空白处应填提前结束循环的条件(m%i==0)。 答案:m%i==0 (四)程序改错题(以下程序只有一处错误,请写出程序中的错误行和改正后的程序) 【例6-1】 下述程序按公式: 求和并输出结果。 main() { float s=0; int k; for(k=1;k<=10;k++) s+=1/k; printf(“sum=%f”,s); } 解析:本题的错误在于进行算术运算“/”运算时,当两个运算数都为整形数据时表示取两数相除的整数部分即1/2结果为0,所以本题的结果为1.000000,不能达到题目的要求。 答案:错误行:s+=1/k; 正确行:s+=1.0/k; 第七章 数 组 7.1 重点、难点 数组是一种构造类型数据,是有序数据的集合,数组中的每一个元素都属于同一个数据类型。数组中的所有元素占连续的存储单元,数组的首地址是数组中第一个元素的存储地址,用一个统一的名称和下标来唯一地确定数组中的元素。数组包括一维和多维。 1.一维数组 (1)一维数组的定义 定义一个一维数组的一般方法为: 类型说明符 数组名[常量表达式]; 说明: ①数组名命名规则与变量名相同,遵循标识符命名规则。 ②数组名后用方括号[ ],不能用圆括号()。 ③常量表达式表示元素的个数,即数组长度。例如有定义:int a[10];则定义了名字是a,长度是10的一维数组,数组的每一个元素都是int类型的。数组有10个元素,下标从0开始,即a[0],a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8],a[9]。注意:不要使用元素a[10],由于C语言对数组下标不进行越界检查,所以在使用数组元素时,应该特别注意。 ④常量表达式必须是正的整形常量,不一定要求是整常数。 C语言不允许对数组的大小作动态定义,即定义数组时,数组的长度必须是确定的。例如:int a[100]; 若在定义语句前有以下预定义行: #define N 100 则a数组定义语句也可写成: int a[N];(此处N为常量) C语言编译系统对数组名的处理是:数组名代表数组的起始地址,数组的元素在内存中是连续存储的。 (2)一维数组元素的引用 表示形式为:数组名[下标] 下标可以是整形常量、整形变量或整形表达式。 例如:有定义 int a[20],b[10],i=8; 则a[2*i],a[i],b[i-3]都是对数组元素的合法使用。 (3)一维数组元素的初始化 C语言允许在定义数组的同时对数组的元素进行初始化。 ①在定义数组时对数组元素赋以初值,将初始值依次写在花括号{}内。 例如:int a[5] ={10,20,30,40,50}; 相当于a[0]=10,a[1]=20,a[2]=30,a[3]=40,a[4]=50。 ②当花括号{}内提供的初值个数(至少要有一个)少于数组元素的个数时,系统自动用0赋值。例如:有定义int b[5]={6,7}; 相当于:b[0]=6,b[1]=7,b[2]=0,b[3]=0,b[4]=0。 当花括号{}内初值个数多于元素个数时,将导致编译出错。 ③C语言允许通过所赋初值的个数来定义数组的长度,即在定义数组时不指明数组的长度,在编译时系统会根据花括号中提供的初值个数确定数组的实际长度。 例如:int a[ ] ={1,2,3,4,5}; 相当于 int a[5]={1,2,3,4,5}; 2、二维数组 (1)二维数组的定义 定义一个二维数组的一般方法为: 类型说明符 数组名[常量表达式1][常量表达式2]; 例如:float b[2][3]; 该语句定义了2行3列6个元素的实型二维数组b,数组b的6个元素分别为b[0][0],b[0][1],b[0][2],b[1][0],b[1][1],b[1][2],每一维下标都是从0开始的,下标最大元素为: 数组名[常量表达式1-1][常量表达式2-1]。 在C语言中二维数组元素在内存中按行存放。 (2)二维数组元素的初始化 ①分行给二维数组元素赋初值。例如:int b[2][3]={{1,2,3},{4,5,6}};将第一个内层花括号内的数据依次赋值给数组第一行的各元素,第二个内层花括号的数据依次赋值给数组第二行的各元素。 当内层花括号中的数据个数少于对应行元素个数(列数)时,系统会自动用0赋值。 例如: int b[2][3]={{1},{4,6}}; 相当于b[0][1]=0,b[0][2]=0,b[1][2]=0。 ②按数组的元素在内存中排列的顺序赋初值。 例如:int b[2][3]={1,2,3,0,5,-16}; ③在给出二维数组全部元素的初值时,可以省略第一维数组的长度,但是不能省略第二维的长度。 例如:int b[ ][3]={1,2,3,4,5,6,7,8,9}; 系统将按3行处理。 ④分行给出二维数组部分元素的初值时,可以省略第一维的长度。 例如:int a[ ][4]={{1,2},{3,4,5,6},{7}}; 系统将按3行处理。 (3)二维数组元素的引用 表示形式为:数组名[下标][下标] 同一维数组一样,下标可以是整形常量、整形变量或整形表达式。 例如:定义 int b[2*5][3*4],i=15;那么b[2*3][0],b[1][i-5]都是对数组元素的合法使用。 3、字符数组 字符数组就是数组中各元素的数据类型为字符型的数组。 字符数组的定义、初始化和元素的引用方法与一般的数组相同。 对字符数组初始化可以逐个字符对数组初始化,未被赋值的元素自动定为空字符(即’\0’)。 例如:char ch[10]={‘B’, ‘O’, ‘Y’, ‘’,‘G’, ‘I’, ‘R’, ‘L’}; 也可以使用字符串常量来对字符数组初始化。 例如:char ch[ ]={ “china”}; 例如:char ch[ ]= “china”; 字符串常量是用双引号括起来的一串字符,由系统自动加上一个字符串结束标志’\0’。 ’\0’ 它占内存,但不计入字符串长度。在C语言中用字符数组来处理字符串,每个元素存放一个字符型数据。 例如:char ch[ ]=“china”;数组的长度是6,而字符串的长度是5。 需要说明的是:字符数组本身并不要求它的最后一个元素一定是’\0’,例如:char str[2]={‘A’, ‘B’};是合法的。为了使处理方法一致,在字符数组中也常常人为地加上一个值为’\0’的元素。 字符数组的输入输出: (1)逐个字符输入输出。scanf( )函数和printf( )函数中使用格式 “%c”输入或输出一个字符。 (2)将整个字符串一次输入或输出。scanf( )函数和printf( )函数中使用%s描述符,可以进行字符串的整体输入输出,以空格作为字符串的结束。 例如: char a[20]; scanf(“%s”,a); printf(“%s”,a); 当输入为How are you时,输出结果为How。 4、字符串的处理函数 C语言中提供了一些处理字符串的标准函数,用来完成一些常见的字符串操作。要掌握其功能和调用形式。 ①字符串输出puts()函数 使用形式:puts(字符数组) 作用:将一个字符串输出到终端。在输出时,将字符串结束标志转换成’\n’,即输出完字符串后换行。 例如:puts( “study”);puts( “hard”);运行后输出: ②字符串输入gets()函数 使用形式:gets(字符数组) 作用:从终端输入一个字符串到字符数组中,并且得到一个函数值为字符数组的起始地址。以回车作为字符串的结束。 例如:char str[20];gets(str);运行后从键盘输入:Great Wall<CR>将字符串 “Great Wall”送到字符数组str中。 ③字符串测长度strlen()函数 使用形式:strlen(字符数组) 作用:计算字符串长度,函数值是字符串中’\0’之前的字符个数(不包括’\0’字符)。 ④字符串比较函数strcmp()函数 使用形式:strcmp(字符串s1,字符串s2) 作用:比较两个字符串,比较的原则是:两个字符串从左到右逐个对应字符按其ASCII值大小相比较,直到出现不同的字符或遇到’\0’为止。 若字符串s1与s2的所有字符全部相等,函数的返回值为0,否则按第一对不相同字符比较,当字符串s1中字符大于字符串s2中对应字符时,函数返回一个正整数。当字符串s1中字符小于字符串s2中对应字符时,函数返回一个负整数。 ⑤字符串连接strcat()函数 使用形式:strcat(字符数组1,字符数组2) 作用:连接两个字符数组中的字符串,把字符串2连接到字符串1的后面,连接后的字符串放在字符数组1中,函数值是字符数组1的地址。 注意:字符数组1必须足够大,以便能够容纳连接后新的字符串。 ⑥字符串拷贝strcpy()函数 使用形式:strcpy(字符数组1,字符串2) 作用:将字符串2拷贝到字符数组1中,函数值是字符数组1的地址。 注意:字符数组1必须足够大以便能容纳被拷贝的字符串2。 不能使用赋值语句将一个字符串常量或字符数组直接赋值给一个字符数组。 例如:char str[20]; str=“abcde”; 这是不合法的。 7.2 例题 (一)选择题 要求定义具有8个、int类型元素的一维数组,错误的定义语句是________。 A.int n= 8; B.#define N 3 int a[n]; int a[2*N+2]; C.int a[ ]={0,1,2,3,4,5,6,7}; D.int a[1+7]={0}; 解析: (1)在选项B中数组a[2*N+2]的方括号内是常量表达式,因此是正确的。 (2)选项C中,通过赋初值确定a数组的大小。 (3)选项D中,a[1+7]的方括号内是常量表达式,并给a数组的8个元素赋初值0。 (4)选项A中,n是变量,不是常量,虽然n有值也不合法。 答案:A 若有以下定义语句:int a[ ]={1,2,3,4,5,6,7,8,9,10};则值为5 的表达式是________。 A.a[5] B.a[a[4]] C.a[a[3]] D.a[a[5]] 解析: (1)选项A中,元素a[5]的值是6。 (2)选项B中,元素a[4]的值是5,因此a[a[4]] 就是a[5],其值为6。 (3)选项C中,元素a[3]的值是4,因此a[a[3]] 就是a[4],其值为5。 (4)选项D中,a[a[5]] 就是a[6],其值为7。 答案:C 以下语句中:当字符串s1和s2相等时,能够输出“OK”的是________。 A.if(s1==s2) printf(“OK”); B.if(!strcmp(s1,s2)) puts(“OK”); C.if(s1[0]==s2[0]) printf(“OK”); D.if(strcmp(s1,s2)) puts(“OK”); 解析:比较字符串不能直接使用关系运算符,例如以s1>s2,s1<s2或s1==s2方式比较两个字符串都是错误的。因此选项A是错误的,选项C是比较s1[0]和s2[0]两个元素,选项D给出的是当两个字符串不相等时,输出OK。 答案:B 以下数组的定义中,________是错误的。 A.int a[10]={‘0’,’1’,’2’,’3’,’4’,’5’}; B.char a[10]={65,66,67,68,69}; C.char a[5]=“hello”; D.char a[6]=“hello”; 解析:C语言中字符数据和整形数据之间可以通用,所以,选项A、B是正确的。选项C的字符串常量 “hello”需要6个字节的存储空间,而数组的长度是5,所以选项C是错误的。 答案:C 以下程序的输出结果是________。 main( ) {int a[5]={1,2,3,4,5},i; for(i=1;i<=5;i++) printf( “%d”,a[i]); } A.12345 B.01234 C.不确定的值 D.23451 解析:本程序输出了a[1]到a[5]的值,其中a[5]下标越界,输出的值不确定。 答案:C 以下选项中,不能将字符串正确赋给数组的是________。 A.char str[ ]={“china”}; B.char str[ ]=“china”; C.char str[ ]={‘c’, ‘h’, ‘i’, ‘n’, ‘a’}; D.char str[6]={ ‘c’, ‘h’, ‘i’, ‘n’, ‘a’}; 解析: (1)C语言中允许在定义语句中给字符数组以字符串常量作为初值,此时,字符串常量外侧的大括号可有可无。采用这种赋初值方式时,只要有存储空间,系统就会自动在其末尾加入串结束标志’\0’。选项A、B都未指定具体空间,系统将按字符串中实际的字符个数再加上一个(’\0’),来定义数组的大小。因此选项A和B都是正确的。 (2)选项C是以单个字符的形式为字符数组赋初值,并以字符个数来确定数组的大小,相当于定义了具有5个元素的一维数组。因为所赋初值的末尾没有串结束标志,所以不能作为字符串使用。 (3)选项D在赋初值的形式上与选项C看似一样,但因数组元素的个数定义为6,而所赋初值字符的个数只有5个。在这种情况下,系统会自动在其后加入一个’\0’。所以选项D也是正确的。 答案:C 以下程序的输出结果是________。 main( ) {char ss[16]="test\0\n\"" ; printf("%d,%d\n",strlen(ss),sizeof(ss));} A.4,16 B.7,7 C.16,16 D.4,7 解析: (1)程序中定义了一个有16个元素的字符型一维数组ss,并为它赋有初值。C语言中的字符串都是借助于字符数组来存储的,并以’\0’作为串结束标志。 (2)库函数strlen用于求字符串的长度,即第一个’\0’前面的字符个数。在本例中数组ss在定义时开辟了16个字节的存储单元,无论ss中是否存了数据、存多少数据,它占的字节数固定为16。 答案:A (二)阅读程序题: 以下程序的输出结果是________。 #include <stdio.h> main( ) {char b[ ]=“abcde”; b[2]=‘0’; puts(b); } 解析: (1)程序中定义了一个字符数组b,并给它赋予了字符串 “abcde”。 (2)赋值语句b[2]=‘0’;这个’0’不是数值0,也不是’\0’,只是一个普通的字符,用它置换掉了字符串中的字符’c’。所以输出字符串是ab0de。 答案:ab0de 执行以下程序时,若输入end<回车>,则输出结果是________。 main( ) {char ch[10]=“Begin”; gets(&ch[1]); puts(ch); } 解析: (1)程序中首先为字符数组ch赋予了字符串 “Begin”。 (2)在通过gets函数读入字符串 “end”时,所给出的存放字符串的首地址是ch数组中下标为1的元素的地址。因此,ch中下标为0的元素中的值得以保留。输入时〈回车〉符将转换为串结束标志。 (3)执行语句puts(ch);时,是从数组的下标为0的元素开始输出,故结果为Bend。 答案:Bend 以下程序运行时,从键盘上输入:How do you do <CR>。输出为________。 #include <stdio.h> main( ) {char a[20]; scanf(“%s”,a); puts(a); } 解析:运行时输入:How do you do <CR>,a数组接收的字符串是“How ”。 答案:How 以下程序运行时,从键盘上输入:How do you do <CR>,输出为________。 #include <stdio.h> main( ) {char a[20]; gets(a); puts(a); } 解析:运行时输入:How do you do <CR>, a数组接收的字符串是“How do you do”。 答案: How do you do (三)完善程序题: 下面程序的功能是分别求出N*N二维数组中两个对角线元素的和值,并输出。请在________处填入正确的内容。 #define N 3 main( ) {int a[N][N]={1,2,3,4,5,6,7,8,9},i,s1=0,s2=0; for(i=0;i<N;i++) {s1=________; /*s1为主对角线的和值*/ s2=________;} /*s2为次对角线的和值*/ printf(“s1=%d,s2=%d”,s1,s2); } 解析: (1)已知二维数组有以下数据: 1 2 3 4 5 6 7 8 9 则第一个(主)对角线上元素的值为1+5+9,第二个(次)对角线上元素的值为3+5+7。 (2) 主对角线上元素的特点是行下标和列下标相同,若二维数组名是a,通过以下循环可求得主对角线上元素的和值: for(i=0;i<N;i++) s1=s1+a[i][i]; (3) 次对角线上元素的特点是行下标与列下标相加等于N-1,通过以下循环可求得次对角线上元素的和值: for(i=0;i<N;i++) s2=s2+a[i][N-1-i]; 答案:s1+a[i][i] s2+a[i][N-1-i] 第八章 函数 8.1 重点、难点 1.库函数 标准C提供了一百多个库函数,Turbo C提供了三百多个库函数,使很多基本、常用的功能可以直接通过库函数的调用来实现。库函数的调用有3种形式。 ★单独成为一个语句,括号后面有一个分号。这样函数的调用相当于执行了一个子过程,执行完后返回到调用点的下一条语句。 ★作为表达式中的运算量,其调用后的返回值参与所在表达式的计算。 ★作为另一个函数的参数,其调用后的返回值作为另一个函数的实际参数进行传递。 注意:在调用某个库函数之前,需要把该函数对应的头文件用文件包含命令包含到同一源文件中,否则库函数无法顺利地被调用。 2.函数的定义方法 函数头的定义包括定义函数的返回值类型(return后括号里的表达式的类型,不返回值则返回类型名为空类型void)、函数名称和函数的形式参数(函数名后的括号里给出形参名及其数据类型,或者下一行再给出各形参的数据类型)。函数体用一对大括号括起,包括变量说明部分、由语句序列组成的执行部分和return语句。根据函数的具体功能进行编制。 函数定义的基本格式如下: 返回值类型 函数名(形式参数表) { 变量说明 语句段 [return语句] } 函数的类型分为无参函数、有参函数和空函数,无参函数没有形式参数及其定义 ,空函数的函数体中没有语句,调用时什么事也不做,往往是编制程序的开始起到占位的作用,以后再对空函数逐一补充内容。 注意:C语言的函数定义是互相平行、独立的,一个函数内不能包含另一个函数的定义,即不能嵌套定义。 3.函数的类型和返回值 有的函数需要在调用后得到一个结果值,再对结果进行处理,这个结果值就是函数的返回值。返回值的类型即为函数的类型,需要在函数的头部进行定义,即函数名前的返回值类型,一般要与该函数调用完毕后返回表达式值的类型一致。如果没有定义函数的返回值类型,会按整型进行处理,因此返回值为整型的情况可以不作函数类型的说明。 函数的返回值则在函数体中通过return语句指明,格式是: return(表达式); 也可以不要圆括号。表达式的值即为该函数的返回值。在执行return语句之前,要确保表达式能计算出确定的值。 如果返回值的类型与定义的函数类型不一致,会以函数类型为标准,返回值类型进行自动类型转换。但并非所有情况都能转换,应保持二者类型的一致。 如果函数体中没有return语句,则不会返回确定的值,可能返回不确定的无用的值,没有意义,不要对这种情况的返回值进行引用。 对于确定不返回值的函数,可以用void将函数定义为“无类型”和“空类型”,这样可以保证不让函数返回任何值,避免错误或无意义的引用。 4.参数值的传递 函数定义中函数头里定义的变量是形式参数,简称形参;函数调用的括号里出现的表达式是实际参数,简称实参,实参可以是常量、变量或表达式。把调用其他函数的函数称为“主调函数”,把被调用的函数称为“被调函数”。则实参和形参起到在主调函数和被调函数之间传递数据的作用。 实参与形参要在个数、类型和位置上一一对应,在调用语句执行时,先把实参的值按对应关系传递给形参,作为形参的初始值参与在被调用函数中运算。因此实参和形参的关系对于函数的顺利调用非常重要。 实参到形参的传递过程分为两种情况: ★单向传值过程:把实参的值传递给形参作为其初始值开始运算,但形参变量的值在被调用函数中的变化不会影响实参变量的值。 ★传地址过程:往往实参是含运算符&的变量地址、数组名或指针变量,形参用指针变量或数组来接收,通过地址的传递,实参和形参指向同一个存储单元或区域,那么形参变量对该存储单元或区域的值的改变,同时也是对实参对象的值的改变,即形参的变化会反过来影响实参的变化。 要注意分清参数之间的关系到底是哪一种传递性质,是传值还是传地址。 5.函数的调用 函数的正确调用 函数调用的一般形式为: 函数名(实际参数); 无参函数则没有实参数表。和库函数的调用一样,自定义函数也有3种调用方式:作为单独的函数语句、作为表达式中的运算对象和作为其他函数的实际参数。 如果在源文件中,被调函数的定义在主调函数之后,则需要在主调函数中对被调函数的类型进行说明,形式如下: 类型标识符 被调函数名(); 另外,也可以在源文件的开始、函数的外部对被调函数进行类型说明。如果函数的返回值为整型,可以不作函数说明。 ★函数的嵌套调用 是指在调用一个函数的过程,又调用另一个函数,甚至进行多层地嵌套。但每个函数的定义是独立的。 注意:如果被调用函数的定义在调用函数之后,要在调用前对被调函数作类型说明。不管函数的调用在哪个层次,其被调用的规则、该函数又去调用其他函数的规则以及该函数执行完毕后返回调用它的主调函数的规则,都与函数的调用和返回一致,没有特殊的地方。 ★函数的递归调用。是指在调用一个函数的过程中又出现直接或间接地调用该函数本身。可以出现多层递归调用,但总值因某个条件的满足而停止向下继续递归,随后会层层地向上返回每一级调用处,因此递归调用往往要与选择判断语句相结合。 6.局部变量和全局变量 从变量的定义位置和作用范围上分,可以分为局部变量和全局变量。 局部变量在函数内定义,只能在定义它的函数及该函数调用的函数范围内使用,除此之外的任何函数不能使用。主函数内定义的局部变量的作用范围也仅限于主函数及其调用的函数,其他函数不能引用。局部变量甚至可以在函数内的复合语句中定义,这样的变量就只在该复合语句中有效。形参也是局部变量。因此不同的函数可以定义名称相同的变量,它们代表不同的存储单元,是不同的变量。 全局变量在函数之外定义,全局变量的作用范围是从定义位置开始到源文件结束为止。在全部程序运行过程中,全局变量都要占用固定的存储单元。一般在源程序的开始定义全局变量,如果全局变量没有在文件的开头定义,而定义点之前的函数又想引用该全局变量,则应该在该函数中作全局变量说明,格式如下: extern 类型标识符 全局变量变量名; 几乎文件中的所有函数都能使用全局变量,客观上全局变量就起到了在函数间传递数据的作用,甚至可以减少形参和实参的数量。当然在享用它的好处时,也要慎重使用,避免全局变量过多带来的降低函数通用性的负作用,以及存储单元的浪费。 注意:如果在一个文件中,某全局变量与某函数内的局部变量同名,则在该函数的执行过程中,局部变量发挥作用,全局变量被屏蔽而不起作用。而程序流程一旦离开该函数,全局变量又会恢复其作用。 7.变量的存储类别、作用域和生存期 变量的作用域是指变量能够发挥作用(被赋值、被引用、被修改)的范围,即变量能被文件中的哪些函数使用。作用域是可以从空间角度分析变量特性的一个名词。从变量的作用域上分,可以分为局部变量和全局变量。 变量的生存期是指变量在程序执行过程中的哪一段时期内存在、拥有分配的存储单元,即变量在哪些函数的执行期内被分配有存储单元。生存期则是从时间角度分析变量特性的名词。 从变量的生存期上分,变量可以分为动态存储变量和静态存储变量。 动态存储变量是指在动态存储区存放、动态分配存储单元的数据,包括自动变量、形参变量和寄存器变量。自动变量是局部变量的默认存储类别,也可用auto说明,寄存器变量用register说明。动态存储变量的特点是该变量所占用的存储单元随着所在函数的调用而分配、又随着所在函数的调用结束而释放,下一次调用时再重新分配以及释放存储单元。 对动态存储变量,其作用域和生存期有比较密切的联系,动态存储变量只在其作用区域的函数的执行期内存在并发挥作用,一旦程序流程离开其作用域,动态存储变量就被释放而无法发挥作用。 静态存储变量是指在静态存储区存放、在程序开始执行时就分配固定存储单元的数据,包括静态局部变量和全局变量。 静态局部变量在函数内定义,用static说明,静态存储变量的特点是在编译时分配存储单元并赋值,以后在整个文件的运行中始终保持不变。因此,虽然静态局部变量的作用域仍然只限于定义它的函数,只能在该函数中引用,但其他函数运行时,静态局部变量仍然存在,保持其分配的存储单元。静态局部变量不随其所在函数的调用结束而释放,而会保留本次调用结束时的值,并作为下一次调用的初始值参与运算。因此静态局部变量的作用域和生存期就不具有对等的关系。 全局变量更是在所有函数执行过程中均存在,也属于静态存储变量的范畴。如果在一个源文件内定义的全局变量需要在另一个文件内引用,全局变量的定义点不需要作额外的定义,但需要在引用它的另一个文件中用extern作说明,格式如下: extern 类型标识符 全局变量变量名; 这样全局变量的作用域就扩大到有extern说明的其他源文件。 但有时不希望全局变量被其他的文件引用,则需要在定义全局变量时用static加以界定,格式如下: static 类型标识符 全局变量变量名; 这样,即使其他的文件中用extern作了说明,也无法引用该全局变量。因此把这一类的全局变量称为静态全局变量。 8.内部函数与外部函数 根据函数能否被其他源文件调用,将函数分为内部函数和外部函数。 内部函数又称为静态函数,如果在函数返回类型前加static说明,表示该函数只能被所在文件的其他函数调用,不能被其他文件中的函数调用。 外部函数是函数的隐含类型,也可以加extern说明,该类函数可以被其他源文件中的函数调用,但需要调用外部函数的文件中,一般用extern对该函数先进行说明,格式如下: extern 函数名(形参表); 8.2例题 一:选择题 【例8-1】:以下对C语言函数的有关描述中,正确的是 A)在C中,调用函数时,只能把实参的值传送给形参,形参的值不能传送给实参 B)C函数既可以嵌套定义又可以递归调用 C)函数必须有返回值,否则不能使用函数 D)C程序中有调用关系的所有函数必须放在同一个源程序文件中 解析:C函数不能嵌套定义,可以没有返回值,并且一个函数可以调用不属于同一个源文件的外部函数,所以选项B、C、D都是错误的。 答案:A 【例8-2】:以下叙述中不正确的是 A)在C中,函数的自动变量可以赋值,每调用一次,赋一次初值 B)在C中,在调用函数时,实际参数和对应形参在类型上只需赋值兼容 C)在C中,外部变量的隐含类别是自动存储类别 D)在C中,函数形参可以说明为register变量 解析:外部变量的隐含类别是全局变量,不是自动存储类别。选项A、B、D正确。 答案:C 【例8-3】:以下叙述中不正确的是 A)在不同的函数中可以使用相同名字的变量 B)函数中的形式参数是局部变量 C)在一个函数内定义的变量只在本函数范围内有效 D)在一个函数内的复合语句中定义的变量在本函数范围内有效 解析:在一个函数内复合语句中定义的变量,只能在该复合语句范围内有效,因此选项D是错误的。 答案:D 【例8-4】:当调用函数时,实参是一个数组名,则向函数传送的是 A)数组的长度 B)数组的首地址 C)数组每一个元素的地址 D)数组每个元素中的值 解析:C语言中的函数调用分为传值调用和传地址调用,而数组名其实存放的是该数组的首地址,因此当实参是数组名时,向函数传送的是该数组的首地址。 答案:B 【例8-5】:函数调用:strcat(strcpy(str1,str2),str3)功能是 A)将串str1复制到串str2中后再连接到串str3之后 B)将串str1连接到串str2之后再复制到串str3之后 C)将串str2复制到串str1中后再将串str3连接到串str1之后 D)将串str2连接到串str1之后再将串str1复制到串str3中 解析:题目中所给函数的功能是:先用函数strcpy把参数串str2复制到参数串str1中去,然后用函数strcat把参数串str3连接到经过修改后的参数串str1后,因此,选项C正确。 答案:C 【例8-6】:有以下程序: void fun(int a,int b,int c) {a=456;b=567;c=678;} main() {int x=10,y=20,z=30; fun (x,y,z); printf("%d,%d,%d\n",z,y,x); } 输出结果是 A)30,20,10 B)10,20,30 C)456,567,678 D)678,567,456 解析:函数fun的x、y、z参数都是传值调用,函数体中对形参值的改变不会带回到主函数,因此主函数中输出z,y,x的值没变,为30、20、10。 答案:A 【例8-7】:以下程序运行后,输出结果是 A)8,15 B)8,16 C)8,17 D)8,8 func(int a,int b) { static int m=0,i=2; i+=m+1; m=i+a+b; return(m); } main() { int k=4,m=1,p; p=func(k,m);printf("%d,",p); p=func(k,m);printf("%d\n",p); } 解析:本题主要考察静态变量的应用,k=4,m=1时,第一次执行函数func(4,1),i+=m+1使得i=2+(0+1)=3,m=i+a+b=3+4+1=8;主函数中并没有改变k和m的值,因此第二次仍然执行函数func(4,1),只是此时func中不再对m和i赋初值,m=8,i=3,于是i=3+(8+1)=12,m=i+a+b=12+4+1=17,返回主函数并输出17,程序结束。 答案:C 【例8-8】:以下程序运行后,输出结果是 A)84 B)99 C)95 D)44 int d=1; fun(int p) { int d=5; d+=p++; printf(“%d”,d); } main() { int a=3; fun(a); d+=a++; printf(“%d\n”,d); } 解析:本题主要考察全局变量和局部变量的作用域,如果全局变量和局部变量的名字相同,那么全局变量在局部变量所在的区域不起作用。本题执行函数 fun(3)中,d+=p++使得局部变量d=5+3=8并输出,然后在主函数的d+=a++使得全局变量d=1+3=4并输出。 答案: A 【例8-9】:以下程序的输出结果是 A)54321 B)012345 C)12345 D)543210 main() { int w=5; fun(w); printf(“\n”);} fun(int k) { if(k>0)fun(k-1); printf(“%d”,k); } 解析:本题主要考察递归函数的调用。只有当实参等于0时,才会输出,然后依次递归输出1、2、3、4、5。 答案: B 【例8-10】:以下程序的输出结果是 A)6 9 9 B)6 6 9 C)6 15 15 d)6 6 15 int d=1; fun(int p) { static int d=5; d+=p; printf(“%d”,d); return(d):} main() { int a=3; printf(“%d\n”,fun(a+fun(d))); } 解析:本题关键在于静态变量d,第一次执行 fun(d),即fun(1),在函数fun里对静态变量赋初值5,得到d=d+p=5+1=6并输出,然后返回6,第二次执行fun(a+fun(d)),即fun(3+6)=fun(9),在函数fun中直接执行d=d+p=6+9=15并输出,然后返回15,再一次输出。 答案:C 【例8-11】:下面程序执行后的输出结果是 A) hello B) hel C) hlo D) hlm func1(int i); func2(int i); char st[ ]=”hello,friend!”; func1(int i) { printf(“%c”,st[i]); if(i<3){i+=2;func2(i);}} func2(int i) {printf(“%c”,st[i]); if(i<3){i+=2;func1(i);}} main() {int i=0;func1(i);printf(“\n”);} 解析:i=0,调用func1(0)函数输出st[0],h,i=2;i=2,调用func2(2)输出st[2],l,i=4;i=4;调用func1(4)函数输出st[4],o;输出换行。 答案:C 【例8-12】:有如下函数调用语句: fun(rec1,rec2+rec3,(rec4+rec5)); 该函数调用语句中,含有的实参个数是 A)3 B)4 C)5 D)有语法错误 解析:实参是以逗号分开的,其中rec2+rec3是算术表达式,(rec4,rec5)是逗号表达式,都只能算作一个参数。 答案:A 【例8-13】:有如下程序 int func(int a,int b) {return(a+b);} main() {int x=2,y=5,z=8,r; r=func(func(x,y),z); printf(“%d\n”,r);} 该程序的输出结果是 A)12 B)13 C)14 D)15 解析:func(x,y)的值是7,然后又作为func的第一个参数,结果为15 答案:D 【例8-14】:有如下程序 long fib(int n) {if(n>2)return(fib(n-1)+fib(n-2)); else return(2);} main() {printf(“%ld\n”,fib(3));} 该程序的输出结果是 A)2 B)4 C)6 D)8 解析:该题主要考察函数的递归调用,由函数fib的定义,fib(1)=2,fib(2)=2,因此fib(3)=fib(2)+fib(1)=2+2=4 答案:B 【例8-15】:在c语言中,函数的隐含存储类别是 A)auto B)static C)extern D)无存储类别 解析:C语言中如无特别说明,函数的隐含存储类别为外部extern类别。 【答案】C 【例8-16】:以下所列的各函数首部中,正确的是 A)void play(var:Integer,var b:Integer) B)void play(int a,b) C)void play(int a,int b) D)Sub play(a as integer,b as integer) 解析:C语言中函数首部中的各参数,必须分开单独定义。因此选项C才是正确的。 答案:C 【例8-17】:以下程序的输出结果是 A)0 B)29 C)31 D)无定值 fun(int x,int y,int z) {z=x*x+y*y;} main() {int a=31; fun(5,2,a); printf(“%d”,a);} 解析:实参a与形参z之间是单向传值的关系,z值的变化不会回传给a;因此a的值保持不变。该题主要考察,函数参数之间传值时,形参值的改变,不会改变实参变量的值。 答案:C 【例8-18】:以下只有在使用时才为该变量分配内存单元的存储类说明是 A)auto和static B)auto和register C)register和static D)extern 和register 解析:动态存储变量是指在动态存储区存放、动态分配存储单元的数据,包括自动变量、形参变量和寄存器变量,又随着所在函数的调用结束而释放,下一次调用时再重新分配并释放存储单元。而静态存储变量在程序编译时分配存储单元,全局变量占用固定的存储单元。 答案:B 【例8-19】:以下程序的输出结果是 A)1 B)2 C)3 D)4 long fun(int n) {long s; if(n==1||n==2)s=2; else s=n-fun(n-1); return s;} main() {printf(“%ld\n”,fun(3));} 解析:这是个简单的函数调用。根据该函数定义,fun(3)=3-fun(2)=3-2=1。 答案:A 【例8-20】:以下正确的说法是: 定义函数时,形参的类型说明可以放在函数体内 return语句后面不能为表达式 如果return后表达式的类型与函数的类型不一致,以定义函数时的函数类型为准 如果形参与实参的类型不一致,以实参类型为准 解析:如果返回值的类型与定义的函数类型不一致,会以函数类型为标准,返回值类型进行自动类型转换 答案:C 二、写出下面程序的运行结果 【例8-1】以下程序的输出结果是 #include<stdio.h> int s=10; func(int a) { int c; c=a+s; return c;} main() { int x=8,y=9,t; t=func((x+y,x--,y++)); printf(“%d\n”,t);} 解析:函数func的参数只有一个,调用函数func时的实参是逗号表达式,其值为表达式y++的值,为9,s为全局变量,故函数func中执行语句c=a+s;后得c=19,c值返回给主函数的变量t,因此,输出为19。 答案: 19 【例8-2】以下程序的输出结果是 int t(int x,int y,int cp,int dp) { cp=x*x+y*y; dp=x*x-y*y;} main() { int a=4,b=3,c=5,d=6; t(a,b,c,d); printf(“%d %d\n”,c,d);} 解析:由于函数t中的4个参数都是传值调用。函数体对形参值的改变不会返回给实参,因此主函数中c和d的值没有改变。 答案:5 6 【例8-3】以下程序的输出结果是 void fun() { static int a=0; a+=2; printf(“%d”,a);} main() { int cc; for(cc=1;cc<4;cc++)fun(); printf(“\n”);} 解析:该题主要考察静态变量的引用,C语言中静态变量的初始化只执行一次,以后直接引用。在本题中,cc从1~3共执行3次循环。第一次调用函数fun,对静态变量a赋出值0,并加2后输出;第二次再调用函数,直接加2输出。 答案: 246 【例8-4】以下程序的输出结果是 #include<stdio.h> void fun() { static int x=0; int y=0; printf(“x=%d,y=%d\n”,++x,++y); } main() { int k=0; while(k++<3) fun();} 解析:x为局部静态变量,只在编译时,赋一次初值,每次函数调用是在原值的基础上加1,y是auto存储类别的局部变量,每次调用函数临时分配内存并赋初值0,在初值0的基础上加1。 答案: x=1,y=1 x=2,y=1 x=3,y=1 【例8-5】下面程序从终端输入123<CR>,则输出结果为 #include<stdio.h> main() {int reverse(); reverse(); printf(“\n”);} int reverse() { char c; if((c=getchar())!=’\n’) reverse(); putchar( c); return(0);} 解析:程序递归调用reverse函数,每调用一次,从终端读取一个字符,直到读取到行结束标志‘\0’,最后读取字符为3。如果不细心,很容易认为只是输出字符3。但不要忘了,在执行if语句后,要执行putchar(c);语句,因此,每调用一次reverse函数,最终都要执行一次putchar(c);语句,最后一次调用时最先执行putchar(c);语句,从而实现反向输出输入的字符串。 答案:321 三:程序填空 【例8-1】以下程序中,主函数调用了LineMax函数,实现在3行4列的二维数组中,找出每一行上最大值。 void LineMax(int x[3][4]) {int i,j,p; for(i=0;i<3;i++) { p=0; for(j=1;j<4;j++) if(x[i][p]<x[i][j]) ① ; printf(“The max value in line %d is %d\n,i”, ② );}} main() {int x[3][4]={1,5,7,4,2,6,4,3,8,2,3,1}; ③ ; } 解析:解答该题的关健在于理解i,j,p的意义,i和j不难理解,i是行循环的横坐标,j是列循环的纵坐标,而p是每一行里最大值的纵坐标。 首先,在i循环里的p=0是假设每一行的第一个元素为最大,然后在j循环里对当前最大元素x[i][p]与该行中的每一个元素x[i][j]比较,如果前者小,那么就要更新p的值,使p=j。显而易见,printf语句所输出当前行的最大值即为x[i][p]。 主函数中主要考察函数调用,该题又包含传地址的调用,将实参数组x的地址传给函数LineMax中的形参x。 答案:① p=j ②x[i][p] ③ LineMax(x) 【例8-2】函数 fun 的功能是:使一个字符串按逆序存放。 void fun(char str[ ]) { char m; int i,j; for(i=0,j=strlen(str);i< ① ;i++,j--) {m=str[i]; str[i]=②; str[j-1]=m;} printf(“%s\n”,str);} 解析:i和j分别指向字符串的首尾字符,通过中间字符变量m交换下标为i和j-1(因为最后一个字符是字符串结束号‘\0’)的字符,有 str[i]=str[i-1].再通过循环变量的改变i++,j--,从两头往中间搜索交换,达到逆序存放字符串。因此,循环共执行字符串长度的一半,有i<strlen(str)/2。 答案: ①strlen(str)/2 ② str[j-1] 四、程序改错 【例8-1】下面函数把两个字符串s1与s2连接起来 con(char s1[],s2[]) {int i=0,j=0; while(s1[i]!=’\0’)i++; while(s2[j]!=’\0’)s1[i++]=s2[j++]; s1[i]=’\0’;} 解析:本题考点是函数形参的使用。在定义函数时,函数名后面括号中的变量称为形参,形参之间以逗号分隔。形参是函数的内部变量,只在函数的内部才有意义。对每个形参必须单独指明其数据类型和名字。 答案:错误:第[1]行 改正:con(char s1[],char s2[]) 第九章 编译预处理 9.1 重点、难点 1.了解编译预处理的含义、作用以及编译预处理命令行的正确表示形式 不要混淆预处理命令行和C语句的概念。在本质上,预处理命令行并不是C语言本身的组成部分,它必须经编译预处理程序进行处理之后,才能进行通常意义下的编译。在表示形式上,C语句必须以分号结束;预处理命令行的末尾则不能加分号,而且必须以“#”作为命令行的开头。 2.掌握不带参数的宏的定义形式及使用方法 不带参数的宏定义的一般形式为: #define标识符字符串 其中的标识符称为"宏名",不带参数的宏定义作用是用宏名来代替一个字符串。在预编译时把宏名替换成指定的字符串。 宏定义中的字符串不能以";"结尾,否则分号会作为字符串的一部分参加宏替换,造成编译错误。字符串结束后一定要换行。 宏定义的有效范围为定义之处到本文件结束。C语言允许宏定义出现在程序中的任何位置,但一般情况下它总是写在文件的开头。 3.掌握带参数的宏的定义及引用形式,能够理解、分析宏替换的过程和结果 带参数的宏定义的一般形式为: #define宏名(参数表)字符串 在程序中定义带参数的宏时,一定要考虑到宏展开后的结果,以避免产生与原设计目的不一致的情况。 例如,设计者试图定义一个实现两个参数相乘的宏,并写成如下形式: #define MUL(x,y) x*y 若有语句: s=MUL(3,2); 经宏展开后为: s=3*2; 可以正常执行。但若有语句: s=MUL(2+3,1+2); 则宏展开后为: s=2+3*1+2; 显然,这一语句表达的并不是两个参数的乘积。特别提请读者注意的是,宏展开时,是将参数原样代入,不可以先算出值再代入。 4.了解带参数的宏与函数调用的区别 略。 5.掌握文件包含命令的正确使用 在C语言中,可以根据需要在程序的适当位置利用#include命令将另一个源文件(此文件的后缀不受限制)包含进来,从而避免重复劳动,提高效率。 文件包含命令的一般形式为: # include"文件名"或#include<文件名> 这两种形式都能使编译系统把指定的被包含文件嵌入到# include的源文件。但二者的搜索路径却有所不同,""扩起来的命令搜索指定文件时,先在当前工作目录中去查找,若找不到再到指定的标准目录中去查找,而<>扩起来的命令直接到系统指定的标准目录中去查找。 9.2 例题 【例9-1】下面是对宏定义的描述,不正确的是_____。 A.宏不存在类型问题,宏名无类型,它的参数也无类型 B.宏替换不占用运行时间 C.宏替换时先求出实参表达式的值,然后代入形参运算求值 D.其实,宏替换只不过是字符替代而已 解析:因为宏替换只不过是简单的字符串替代而已,没有计算求值功能,所以选项C是错误的。 答案:C。 【例9-2】以下叙述中正确的是_____。 A.在程序的一行上可以出现多个有效的预处理命令 B.使用带参数的宏时,参数的类型应与宏定义时的一致 C.宏替换不占用运行时间,只占用编译时间 D.在以下定义中,CR是称为"宏名"的标识符 #defineCR045 解析:选项A是错误的,C语言只允许在程序的一行有一个预处理命令。定义带参数的宏时,宏定义不需要提供参数的类型,因此选项B是错误的。宏替换是在编译之前预处理时完成的,只是占用编译时间,而不占用程序运行时间,因此选项C是正确的。选项D定义宏名时,宏名"CR"之间不应该有空格,否则认为标识符"C"为宏名,"R045"为字符串。 答案:C。 【例9-3】若有以下宏定义: #defineN5 #defineX(n)((N+5)*n) 则执行语句z=3*(N+X(5))后的结果是_____。 A.语句有错误B.z=165C.z=105D.z=150 解析:C语言规定,在一个宏定义中允许出现已经存在的宏名。经过宏替换后,z的表达式为z=3*(5+((5+5)*5)),经计算得z=165。 答案:B。 【例9-4】设有定义:#define STR “12345”; ,则以下叙述中正确的是_____。 A.宏名STR替代的是数值常量12345 B.宏定义以分号结束,编译时一定会产生错误信息 C.宏名STR代替的是字符串”12345”; D.宏名STR代替的是字符串常量”12345”; 解析:宏名STR代替的既不是数值常量,也不是字符串常量,而是定义语句中给出的整个替换文本——字符串”12345”;,包括双引号和分号。所以选项A和D都是错误的。 答案:C。 【例9-5】若将文件file.c包含到本程序中,应在程序头加上_____。 A.#include"file.c"B.#INCLUDE"FILE.C" C.include"file.c"D.什么也不加 解析:C程序中,将一个源文件包含到另一个源文件中是通过使用#include命令来完成的。它的一般形式为:#include"文件名"或#include<文件名>。B选项采用大写字母,不正确;选项C缺少预处理符号"#"。 答案:A。 【例9-6】设有两个圆,大圆半径R,小圆半径r,以下四组宏定义和对宏的引用中,不能够计算出大圆面积减去小圆面积的一组是_____。 A.#define AREA(r) (3.1415*r*r) S=AREA(R)- AREA(r); B.#define AREA(R) 3.1415*R*R #define AREA(r) 3.1415*r*r s= AREA(R)- AREA(r); C.#define AREA(A,B) 3.1415*(A*A-B*B) s=AREA(R,r); D.#define AREA(r) (3.1415*r*r) s=AREA(R-r); 解析:选项A宏引用展开后为:s=3.1415*R*R-3.1415*r*r;,显然符合题意。选项B中重复定义了宏名AREA。C语句中规定,当两个宏定义命令行完全一致时,重复定义在语法上是允许的。按照定义,展开后的宏为:s=3.1415*R*R-3.1415*r*r;,也是正确的。展开选项C的宏引用s=3.1415*(R*R-r*r);,同样正确。选项D展开为:s=3.1415*R-r*R-r,显然是错误的。 答案:D。 【例9-7】以下程序的运行结果是_____。 #defineMIN(x,y)(x)<(y)?(x):(y) main() { inti=10,j=15,k; k=10*MIN(i,j); printf("%d\n",k); } 解析:本例中,宏替换时,k值为k=10*(i)<(j)?(i):(j),再进行参数替换,k=10*(10)<(15)?(10):(15)。由于算术运算符优先级高于其他运算符,因此先计算10*(10)=100,再计算条件运算符,经计算得k=15。 答案:15。 【例9-8】下列程序的输出结果是_____。 #include"stdio.h" #defineSQR(x)x*x main() { inta,k=3; a=++SQR(k+1); printf("%d\n",a); } 解析:相关语句宏展开后成为: a=++k+1*k+1; 因为k=3,计算得到a=9。 答案:9 【例9-9】设有以下程序,完成的功能是:将键盘输入字符中的大写字母转换为小写,其他不变。请在_____处添入适当命令行,使之正确运行。 #include<stdio.h> ___(1)____ lower(x) (‘A’<=x&&x>=’Z’)?(x=x-‘A’+’a’):x voidmain() { intc; while((c=getchar())!=EOF) putchar(___(2)____); } 解析:带参数的宏定义一般形式为: #define宏名(参数表)字符串 所以(1)处应填入#define。(2)处是使用宏来实现对变量c的转换,所以填入带实参的宏lower(c)。 答案:(1)#define(2)lower(c) 【例9-10】以下程序求一个整数x的绝对值,其中有一处错误的地方,请指出并改正_____。 ① #define ABS (x>0?-x:x) ② main( ) ③ { int x; ④ printf("x="); ⑤ scanf("%d",&x); ⑥ printf("|%d|=%d\n"x,ABS(x)); ⑦ } 解析:用宏来完成求一个整数的绝对值,需要定义一个带参数的宏,而程序中①处定义的宏名为ABS的宏正缺少形参x。 答案:①有错误,改正后为:#define ABS(x) (x>0?x:-x)。 【例9-11】以下关于宏与函数的叙述中正确的是_____。 A.使用函数或宏命令对C的源程序都没有影响 B.函数具有类型,宏不具有类型 C.函数调用和带参的宏调用都是将实参的值传给形参 D.使用函数比使用宏运行速度快 解析: 函数调用是在运行时进行的,不影响源程序;而宏经编译预处理程序展开后,往往使源程序加长。故选项A不正确。 函数调用要求对应的实参与形参类型必须一致;而宏调用对参数没有类型要求。选项B是正确的。 函数调用时,先要对实参表达式求值后再传给形参;而宏调用只是进行简单的文本和参数替换,并不对实参求值。所以选项C是错误的。 函数调用发生在程序运行过程中,必然占用运行时间;而宏替换是在预编译时完成的,不占用运行时间。故选项D不正确。 答案:B。 第十章 指针 指针是C语言中一个重要的概念,也是比较难掌握的一个概念,正确地掌握指针的概念并且熟练地使用指针就能设计出高效的程序。 10.1重点、难点 1.指针的概念 (1) 地址:变量编译后,系统在内存中分配的空间号。 (2)内容:内存中存放的数值。 (3)指针:一个变量的地址称为该变量的指针 (4)指针变量:用来存放变量地址的变量。 2.指针变量 (1)定义 存储类型 类型标识符 *变量名 例:int *a,*b; int:代表指针变量指向的变量的类型。 a,b:分别代表指针变量的名称 *:说明符 定义了两个指向整型变量的指针变量a和b。 例:char *a,*b; 定义了两个指向字符型变量的指针变量。a、b指针变量都为一级指针。 注:无论定义指向何种变量类型的指针变量,系统为指针变量一律分配2个字节的存储单元 ,用来存储某一变量的地址。 (2)指针变量的赋值 #include ″stdio.h″ main() { int a,*p1,*p2,*p3; p1=&a;/*第一种赋值方式*/ p2=p1;/* 第二种赋值方式*/ p3=NULL;/*等价于:p3=‘\0’;或p3=0;第三种赋值方式*/ . . } (3)指针变量可以进行的计算 可以对指向同一连续存储单元的两个指针进行加上或减去一个整数的运算(相当于移动指针)。 可以对指向同一连续存储单元的两个指针进行相减的运算。 可以对指向某一连续存储单元的指针进行++、--运算(相当于移动指针)。 指向同一类型的两个指针变量可以相互赋值。 可以对指向同一连续存储单元的两个指针进行比较。 例: int p=&a,q=&b; if(p<q) printf(″%d″,p); (4)几点注意: ①指针变量中只能存放地址即指针,不能将一个整型量赋给一个指针变量。 例: main( ) { int a=3,*b; char *d,c=‘a’; b=&a;/*不能b=2000*/ d=&c; printf(″%d,%d\n″,a,*b); printf(″%c,%c,%c\n″,c,*d,*&c); } 结果:a,a,a ②在程序中一旦定义一个指针变量,必须确定它指向哪一个变量。否则,程序错误。 例: main() { int a,*p; *p=3; printf(″%d\n″,*p); } 结果:程序编译出错 ③在一个程序中一个指针变量至始至终指向同一类型的变量。 例: main( ) { int a=3,*b; char c=‘a’; b=&c;/*语句错误*/ printf(″%d,%d\n″,a,*b); printf(″%c\n″,c); } 3.一维数组和指针: C规定:一维数组名代表数组的首地址,也就是第一个元素的地址,是一个地址常量。一维数组名是一级指针。 C规定:当指针变量p指向数组时,p+1指向数组的下一个元素。假设一个整型元素占两个字节的内存,p+1是使p的地址加2个字节。 例: int a[8],*p; p=a;/*相当于p=&a[0]*/ 说明: ①a++:是错误的,因为常量不能自加。而p++是正确的,因为p是指针变量。 ②a+i:表示a[i]的地址。(实际为:a的首地址+i*数组元素所占的字节数) 则:a+1,a+2,a+3分别表示&a[1]、 &a[2]、 &a[3] 同样:p+1,p+2,p+3也分别表示&a[1]、 &a[2]、 &a[3] ③*(a+1)、*(a+2)、*(a+3)分别代表a[1]、a[2]、a[3] 同样:*(p+1)、*(p+2)、*(p+3)等价于p[1]、p[2]、p[3],也分别代表a[1]、a[2]、a[3]。 4.二维数组和指针: 说明:(以int a[3][4]为例) ①二维数组名a代表二维数组的首地址,是一个地址常量,二维数组名是二级指针。 a和a[0]的相同点: a和a[0]的值都为&a[0][0]。 a和a[0]的不同点: a是基类型具有三个元素的数组类型,是二级指针;而a[0]是基类型具有四个元素的数组类型,是一级指针。 注意:不可以进行a++,a=a+i;a[0]++等操作。 ②a[0]与a+0都代表第0行的首地址。 a[1]与a+1都代表第1行的首地址。a+1中的1数值单位是4*2个字节。 a[0]+0:代表第0行第0列的地址。既&a[0][0]或*(a+0)+0。 a[0]+1:代表第0行第1列的地址。a[0]+1中1数值单位是2个字节。 a、a+0、*(a+0)都代表第0行的首地址。 ③二维数组的地址可用以下五种表达式表示(例数组为a[3][4]) &a[i][j] *(a+i)+j a[i]+j &a[0][0]+i*4+j a[0]+4*i+j ④二维数组元素的表示方式(例数组为a[3][4]) a[i][j] *(a[i]+j) *(*(a+i)+j) (*(a+i))[j]/*圆括号不能省略。因为[]的优先级高于*,省略变成*(a+i)[j]即为*(a+i+j)*/ *(&a[0][0]+4*i+j) 5.通过指针数组引用二维数组的元素 例:int *p[3],a[3][2],i; p是一个一维指针数组,每个元素都是一个指向整形变量的指针变量。指针数组的下标与a数组的行下标相同。 指针数组的赋值方式: for(i=0;i<3;i++) p[i]=a[i]; 执行结果:p[0]、p[1]、p[2]分别指向a数组三行的首数组元素。 通过指针数组p来引用a数组元素的几种方法:(假设:0≤i≤2、0≤j≤1) ①*(p[i]+j)等价于:*(a[i]+j) ②*(*(p+i)+j)等价于:*(*(a+i)+j) ③(*(p+i))[j]等价于(*(a+i))[j] ④p[i][j]等价于:a[i][j] 6.通过建立一个行指针来引用二维数组元素 例: int a[3][2],(*p)[2]; 定义一个指针p,p指向一个一维数组,该数组中有2个元素,每个元素都是整型的。P指向的一维数组下标与a数组的列下标相同。 则赋值方式有一种: p=a; 7. 指向指针的指针变量 变量的内容是另外一个指针变量的地址,则该变量称为指向指针的指针变量。 一般形式:类型标识符 **指针变量名 例: int **p1,*p2,i=3;/* p1是指向指针的指针是二级指针*/ p2=&i;p1=&p2; 8.返回指针值的函数 调用函数的返回值是一个地址值。 一般形式:函数返回值类型 *函数名(形参列表) {函数体} 例: int *fun(int ,int); main() { int *p,i=5,j=9; p=fun(i,j); printf(″i=%d,j=%d,*p=%d\n″,i,j,*p); } int *fun(int a,int b) { if(a>b)return &a; return &b; } 9.指向函数的指针变量 一般形式:类型标识符 (*指针变量名)()/*参数可有可无*/ 在C语言中函数名代表该函数的入口地址,因此可以定义一种指向函数的指针来存放这种地址。例如: double fun(int a,int *p) {… …} main() { double (*fp)(int ,int *);/*定义指向函数的指针变量*/ double y; int n; fp=fun;/*给指向函数的指针变量赋值*/ . . y=(*fp)(56,&n);/*通过指向函数的指针变量调用自定义函数*/ . . } 10. 传给main的参数 一般形式: main(int argc,char **argv) {函数体} 或 main(int argc,char *argv[]) {函数体} 说明: ①argc和argv是两个参数名,由用户任意定义。但类型是固定的。 ②argc中存放命令行中的字符串的个数,它的值最少为1,argv的元素依次指向各个字符串的起始地址。 11.字符指针变量 一般形式: char *指针变量名; 字符串在内存中是以数组形式存放的。它有一个起始地址占一片连续的存储单元,并以‘\0’结束。为了对字符串操作,可以定义一个字符指针。 例1: main() { char s[]="language",*p,a[20]; p=s; a=″abcd″;/*错误。数组名是地址常量不能被赋值*/ printf("%s\n%s",s,a); printf("%s\n",p); } 例2: main( ) { char *p="language",*q; q=″Beijing″;/*不允许{″Beijing″}*/ printf("%s\n%s\n%d\n",p,q,strlen(q+3)); } 12.二维数组名作为实参时,实参和形参之间的数据传递 例:若主函数中有以下定义和函数调用语句: #define M 5 #define N 3 main() { double s[M][N]; . . fun(s); . . } 则fun函数的首部可以是以下三种形式之一: ①fun(double (*a)[N]) ②fun(double a[][N]) ③fun(double a[M][N]) 注意:列下标不可缺。无论是哪种方式,系统都将把a处理成一个行指针。和一维数组相同,数组名传送给函数的是一个地址值,因此,对应的形参也必定是一个类型相同的指针变量,在函数中引用的将是主函数中的数组元素,系统只为开辟一个存放地址的存储单元,而不可能在调用函数时为形参开辟一系列存放数组的存储单元。 13.指针数组作为实参时,实参和形参之间的数据传递 例:若主函数中有以下定义和函数调用语句: #define M 5 #define N 3 main() { double s[M][N],*ps[M]; . . for(t=0;t<M;t++) ps[t]=s[t]; fun(ps); . . } 则fun函数的首部可以是以下三种形式之一: ①fun(double *a[M]) ②fun(double *a[]) ③ fun(double **a) 10.2 例题 (一)选择题 【例10-1】设有定义:int a[10],*p=a;,对数组元素的正确引用是_______ A. a[p] B.p[a] C. *(p+2) D. p+2 解析:a和p都表示数组的起始地址,不是数组的合法下标,所以A和B都是错误的,C与a[2]等价,D与&a[2]等价,即a[2]的地址。 答案:C 【例10-2】若有如下定义,则不能表示数组a元素的表达式是______ int a[10]={1,2,3,4,5,6,7,8,9,10},*p=a; A. *p B. a[10] C. *a D. a[p-a] 解析:C语言中,数组a的元素的下标从0到9,A、C和D都与a[0]等价,是正确的, B是错误的。 答案:B 【例10-3】若有如下定义,则值为3的表达式是______ int a[10]={1,2,3,4,5,6,7,8,9,10},*p=a; A. p+=2,*(p++) B. p+=2,*++p C.p+=3,*p++ D. p+=2,++*p 解析:四个表达式都是逗号表达式,表达式A的值3,表达式B的值4,表达式C的值4,表达式D的值4,所以A是正确的。 答案:A 【例10-4】设有定义:char a[10]="ABCD",*p=a;,则*(p+4)的值是_____ A. "ABCD" B. ’D’ C. ’\0’ D.不确定 解析:*(p+4)的值就是数组元素a[4]的值,即’\0’。 答案:C 【例10-5】 将p定义为指向含4个元素的一维数组的指针变量,正确语句为______ A. int (*p)[4]; B. int *p[4]; C. int p[4]; D. int **p[4]; 解析:A定义p为指向含4个整型元素的一维数组的指针变量。B定义了一个有四个元素的指针数组,数组名为p,数组中的每一个元素都是指向整型变量的指针变量。C定义了一个有四个元素的整型数组,数组名为p。D定义了一个有四个元素的二级指针数组,数组名为p,数组中的每一个元素都是指向整型指针变量的指针变量。 答案:A 【例10-6】若有以下定义和说明 fun(int *c) {…} main() {int (*a)()=fun,*b(),w[10],c; ┇ } 在必要的赋值之后,对fun函数的正确调用语句是_______ A. a=a(w); B. (*a)(&c) C. b=*b(w) D. fun(b) 解析:因为fun()函数中的参数是一个指针变量,所以它接收的一定是一个地址量。同时a是指向fun()函数的指针变量。 答案:B 【例10-7】若有定义int a[3][4];,则输入其3行2列元素的正确语句为______ A. scanf("%d",a[3,2]); B. scanf("%d",*(*(a+2)+1)) C. scanf("%d",*(a+2)+1); D. scanf("%d",*(a[2]+1)); 解析:输入3行2列元素的值,应使用其地址,即&a[2][1],或*(a+2)+1,或a[2]+1,所以C是正确的。 答案:C 【例10-8】下面对指针变量的叙述,正确的是______ A. 指针变量可以加上一个指针变量。 B. 可以把一个整形数赋给指针变量。 C. 指针变量的值可以赋给指针变量。 D. 指针变量不可以有空值,即该指针变量必须指向某一变量。 解析:指针变量加上一个指针变量没有意义,所以A是错误的。不能把一个整形数直接赋给指针变量,因为类型不匹配,所以B是错误的。指针变量可以有空值(NULL),即不指向任何变量,所以D是错误的。指针变量的值可以赋给指针变量,同类型可以直接赋值,不同类型可通过强制类型转换,所以C是正确的。 答案:C 【例10-9】设有定义:int a[10],*p=a+6,*q=a;,则下列运算哪种是错误的_____ A. p-q B. p+3 C. p+q D. p>q 解析:指向同一个数组的两个指针变量可以相减,其值是两个指针之间的元素个数。指向同一个数组的两个指针变量也可以比较,指向前面元素的指针“小于”指向元素的指针。指向数组的指针变量可以加减一个整型数c,加c后指向其后面的第c个元素,减后指向其前面的第c个元素。但指向同一个数组的两个指针变量进行加法运算没有意义。 答案:C 【例10-10】C语言中,数组名代表______ A. 数组全部元素的值 B. 数组首地址 C. 数组第一个元素的值 D. 数组元素的个数 解析:C语言规定,数组名代表数组的首地址。 答案:B 【例10-11】设有定义:int t[3][2];,能正确表示t数组元素地址的表达式是_____ A. &t[3][2] B. t[3] C. t[1] D. *t[2] 解析:C语言中,数组元素的下标从0开始,所以A和B是错误的,D表示数组元素a[2][0],C表示第二行的首地址,所以C是正确的。 答案:C 【例10-12】要求函数的功能是交换x和y的值,且通过正确函数调用返回交换结果。能正确执行此功能的函数是( ) ① funa(int *x,int *y) { int *p; *p=*x;*x=*y;*y=*p; } ② funb(int x,int y) { int t; t=x;x=y;y=t; } ③ func(int *x,int *y) { *x=*y;*y=*x;} ④ fund(int *x,int *y) { *x=*x+*y;*y=*x-*y;*x=*x-*y;} 解析:在①中的指针p指向不确定;在②中形参变量值交换的结果不能被带到主调函数中;③中的赋值语句破坏了指针x所指向的数据。 答案:④ (二)阅读程序题 【例10-1】下列程序的输出结果是 int ast(int x,int y,int *cp,int *dp) { *cp=x+y; *dp=x-y; } main() { int a,b,c,d; a=4;b=3; ast(a,b,&c,&d); printf("%d %d\n",c,d); } 解析:自定义函数中的指针变量cp和指针变量dp,分别指向主函数中的c变量和d变量。 答案:7 1 【例10-2】下列程序的运行结果是______ main() { int a[]={2,4,6,8,10}; int y=1,x,*p; p=&a[1]; for(x=0;x<3;x++) y+=*(p+x); printf("%d\n",y); } 解析:此程序的功能是把数组a中下标为1、2和3的元素值累加到变量y上,即: y=1+4+6+8=19。 答案:19 【例10-3】下列程序的运行结果是_______ main() { char ch[2][5]={"6937","8254"},*p[2]; int i,j,s; for(i=0;i<2;i++) p[i]=ch[i]; for(i=0;i<2;i++) { s=0; for(j=0;p[i][j]!=’\0’;j++) s=s*10+p[i][j]-’0’; printf("%5d",s); } } 解析:此程序的功能是把数字串转换成对应的数值。 答案:6937 8254 【例10-4】下列程序的运行结果是 #include "stdio.h" main( ) { int a=2,*p,**pp; pp=&p; p=&a; a++; printf("%d,%d,%d\n",a,*p,**pp); } 解析:由于p指向a,pp又指向p,所以a、*p和**pp等价,其值都为3。 答案:3,3,3 【例10-5】下列程序的输出结果是 char *fun(char *s,char c) { while(*s&&*s!=c) s++; return s; } main() { char *s="abcdefg",c='c'; printf("%s",fun(s,c)); } 解析:在执行自定义函数时,指针s指向字符c时退出循环体返回到主函数。 答案:cdefg 【例10-6】有以下一个主函数,它所在的文件名为f1.c,运行时若从键盘输入:f1 abc bcd↙,则输出结果是 main(int argc ,char *argv[]) { while(argc>1) { printf("%c",*(*(++argv))); argc--; } } 解析:argc的值为3,argv[0]指针变量指向“f1”字符串,argv[1]指针变量指向“abc”字符串,argv[2]指针变量指向“bcd”字符串 答案:ab (三)完善程序题 【例10-1】下列程序的功能是从键盘输入若干个字符(以回车键作为结束)组成一个字符串存入一个字符数组,然后输出该数组中的字符串。 #include "stdio.h" main() { char str[81],*ptr; int i; for(i=0;i<80;i++) { str[i]=getchar(); if(str[i]==’\n’) break; } str[i]=___________; ptr=str; while(*ptr) putchar(__________); } 解析:根据while(*ptr)可知,字符串末尾应有’\0’,所以第一个空应填’\0’。当ptr所指向的字符不为’\0’时,将其输出,并且使ptr指向下一个字符,所以第二个空应填*ptr++。 答案:第一个空为:’\0’, 第二个空为:*ptr++ 【例10-2】以下函数把b字符串连接到a字符串的后面,并返回a中新串的长度。 strcen(char a[],char b[]) { int num=0,n=0; while(*(a+num)!=______) num++; while(b[n]){ *(a+num)=b[n];num++; _____;} *(a+num)='\0'; return (num); } 答案:第一个空应为'\0';第二个空应为n++。 【例10-3】以下函数用来在w数组中插入值x。n所指向的存储单元中存放w数组中字符个数。数组w中的字符已按从小到大的顺序排列,插入后数组w中的字符仍有序。 void fun(char *w,char x,int *n) { int i,p; p=0; w[*n]=x; while(x>w[p]) p++; for(i=*n;i>p;i--) w[i]=_____; w[p]=x; ++*n; } 解析:该函数为先找到应该插入的位置,然后在将该位置开始的数据后移,再将欲插入的数据存放到该位置。 答案:w[i-1] 【例10-4】下列程序的功能是从键盘输入若干个字符(以回车键作为结束)组成一个字符串存入一个字符数组,然后输出该数组中的字符串。 #include "stdio.h" main() { char str[81],*ptr; int i; for(i=0;i<80;i++) { str[i]=getchar(); if(str[i]==’\n’) break; } str[i]=___________; ptr=str; while(*ptr) putchar(__________); } 解析:根据while(*ptr)可知,字符串末尾应有’\0’,所以第一个空应填’\0’。当ptr所指向的字符不为’\0’时,将其输出,并且使ptr指向下一个字符,所以第二个空应填*ptr++。 答案:第一个空为:’\0’, 第二个空为:*ptr++ (四)程序改错 【例10-1】下列程序的功能是复制字符串a 到b中。 main() { char *str1=a,*str2,a[20]="abcde",b[20]; str2=b; while(*str2++=*str1++); } 解析:程序中用数组名a去初始化指针变量str1,但此时数组a还没有定义,C语言规定,变量或数组必须先定义后使用,所以,必须先定义数组a,然后再定义指针变量str1,并用a对其初始化。 错误行:char *str1=a,*str2,a[20]="abcde",b[20]; 改正行:char a[20]="abcde",*str1=a,*str2, b[20]; 【例10-2】 函数str_space()的功能是统计字符串中空格数。 str_space(char *str,int *num) { *num=0; while(*str++!='\0') if(ch==' ') (*num)++; } 错误行:if(ch==' ') num++; 改正行:if(ch==' ') (*num)++; 【例10-3】函数fun()的功能是:将a所指字符串中的字符和b所指字符串中的字符,按排列的顺序交叉合并到c所指数组中,过长的剩余字符接在c所指数组的尾部。 fun(char a,char b,char c) { while(*a&&*b) { *c=*a;c++;a++; *c=*b;c++;b++; } if(*a=='\0') while(*b){ *c=*b;c++;b++;} else while(*a){*c=*a;c++;a++; } *c='\0'; } 错误行:fun(char a,char b,char c) 改正行:fun(char *a,char *b,char *c) 【例10-4】函数fun()的功能是在串s中查找子串t的个数。 int fun(char *s,char *t) { int n;char *p,*r; n=0; while(*s) { p=s;r=t; while(*r) if(*r==*p) { r++;p++;} else break; if(r=='\0') n++; s++; } return n; } 错误行:if(r=='\0') n++; 改正行:if(*r=='\0') n++; 【例10-5】函数my_cmp()的功能是比较字符串s和t的大小,当s等于t时返回0,否则返回s和t的第一个不同的字符的ASCII码差值,即当s>t时返回正值,当s<t时返回负值。 my_cmp(char *s, *t) { while(*s==*t) { if(*s=='\0') return(0); s++;t++; } return(*s-*t); } 错误行:my_cmp(char *s, *t) 改正行:my_cmp(char *s,char *t) (五)程序设计题 【例10-1】从键盘上输入一个字符串,统计字符串中的字符个数。不许使用求字符串长度函数strlen()。 #include "stdio.h" main() { char str[81],*p=str; int num=0; printf("input a string:\n"); gets(str); while(*p++) num++; printf("length=%d\n",num); } 【例10-2】输入一个字符串存入数组a,对字符串中的每个字符用加3的方法加密并存入数组b,再对b中的字符串解密存入数组c。最后依次输出数组a、b、c中的字符串。 #include "stdio.h" main() { char a[81],b[81],c[81]; char *pa=a,*pb=b,*pc=c; printf("input array a:\n"); gets(a); while(*pa) {*pb=*pa+3; pa++;pb++;} *pb='\0'; pb=b; while(*pb) {*pc=*pb-3; pb++;pc++;} *pc='\0'; printf("array a:%s\n",a); printf("array b:%s\n",b); printf("array c:%s\n",c); } 【例10-3】输入一个字符串,输出每个大写英文字母出现的次数。 解题思路:定义一个有26个元素的一维整型数组num,依次用来存放各个大写英文字母出现的个数。由于各大写英文字母的ASCII码与'A'的ASCII的差,正好是用来存放该大写英文字母个数的数组元素的下标,所以,若当前字符*p为大写英文字母,则执行num[*p-'A']++即可使指定的数组元素值加1。 #include "stdio.h" main() { char str[81],*p=str; int num[26]={0},i; printf("input a string:\n"); gets(str); while(*p) { if(*p>='A'&&*p<='Z') num[*p-'A']++; p++; } for(i='A';i<='Z';i++) printf("%3c",i); printf("\n"); for(i=0;i<26;i++) printf("%3d",num[i]); printf("\n"); } 【例10-4】把从键盘输入的字符串逆置存放并输出。 解题思路:定义两个字符指针变量p和q,分别指向第一个字符和最后一个字符,交换p和q指向的字符,然后 p指向下一个字符,q指向前一个字符,再交换p和q指向的字符,如此下去,直到p>=q为止。 #include "stdio.h" main() { char str[81],*p,*q,ch; printf("input a string:\n"); gets(str); p=str;q=p; while(*q) q++; q--; while(p<q) {ch=*p;*p++=*q;*q--=ch;} printf("turn string:%s\n",str); } 【例10-5】统计一个英文句子中含有英文单词的个数,单词之间用空格隔开。 解题思路:单词的个数可以由空格出现的次数决定(连续的若干空格作为出现一次空格,一行开头的空格不统计在内)。如果当前字符不是空格,而它前面的字符是空格,则表示新单词开始了,此时计数器加1。如果当前字符个它前面的字符都不是空格,则意味着仍然是原来单词的继续,计数器不能加1。前一个字符是否为空格,可以用一个变量word来标识,若word=0,则表示前一个字符为空格,若word=1,则表示前一个字符不为空格。 #include "stdio.h" main() { char str[81],*p=str; int num=0,word=0; printf("input a string:\n"); gets(str); while(*p) { if(*p==' ') word=0; else if(word==0) {num++; word=1; } p++; } printf("num=%d\n",num); } 【例10-6】编写程序,实现两个字符串的比较。不许使用字符串比较函数strcmp()。 #include "stdio.h" main() { char str1[81],str2[81],*p1=str1,*p2=str2; printf("input string str1:"); gets(str1); printf("input string str2:"); gets(str2); while(*p1&&*p2) if(*p1==*p2) {p1++;p2++;} else break; printf("%d\n",*p1-*p2); 【例10-7】将一个英文句子中的前后单词逆置(单词之间用空格隔开)。 如:how old are you 逆置后为:you are old how 解题思路:先将整个英文句子逆置,然后再将每个单词逆置。 #include "stdio.h" main() { char str[81],*p1,*p2,*p,ch; printf("input a english sentence:\n"); gets(str); p1=str;p2=str; while(*p2) p2++; p2--; while(p1<p2) {ch=*p1;*p1++=*p2;*p2--=ch;} p=str; while(*p) { p1=p; while(*p1==' ') p1++; p2=p1; while(*p2&&*p2!=' ') p2++; p=p2; p2--; while(p1<p2) {ch=*p1;*p1++=*p2;*p2--=ch;} } printf("turn english sentence:\n"); puts(str); } 【例10-8】将一个小写英文字符串重新排列,按字符出现的顺序将所有相同字符存放在一起。 如:acbabca 排列后为:aaaccbb 解题思路:新开辟一个数组,从字符串的第一个非空格字符开始,把该字符和与该字符相同的字符都存入该数组,同时用空格代替;再从字符串的第一个非空格字符开始,把该字符和与该字符相同的字符都存入该数组,同时用空格代替,依次下去,直到字符串变为空格串为止。 #include "stdio.h" #include "string.h" main() { char str1[81],str2[81],*p1,*p2,*p,ch; printf("input a string(a-z):\n"); gets(str1); p=str1;p2=str2; while(*p) { while(*p==' ') p++; p1=p; ch=*p; while(*p1) if(*p1==ch) {*p2=ch;*p1=' ';p1++;p2++;} else p1++; } *p2='\0'; printf("result:\n"); strcpy(str1,str2); puts(str2); } 【例10-9】编写程序,通过函数调用方式删除字符串中的非英文字符。 解题思路:该程序通过重新赋值的方式滤掉串中的非英文字符。 delnoalpha(char *s) { char *p,*q; p=q=s; while(*q) { if(*q>='a'&&*q<='z'||*q>='A'&&*q<='Z') *p++=*q; q++; } *p='\0'; } main() { char str[80]; gets(str); delnoalpha(str); puts(str); } } 【例10-10】编写程序,通过函数调用方式将一个十进制数转换成相应的二进制数。 dtob(unsigned d,int b[],int *n) { *n=0; while(d) { b[(*n)++]=d%2; d/=2; } } main() { unsigned x;int bin[16],n; scanf("%u",&x); dtob(x,bin,&n); while(--n>=0) printf("%d",bin[n]); printf("\n"); } 注意事项:因为C语言中没有二进制数的输入输出方式,所以只能借助于一组维数组存放二进制数,其中每一个数组单元存放一个二进制数字。 【例10-11】编写程序,通过函数调用方式统计一个英文句子中最长的单词的字符数。 #include "ctype.h" lwordn(char *s) { int len=0,k; while(*s) { while(!isalpha(*s)) s++; k=0; while(isalpha(*s)){s++;k++;} if(len<k) len=k; } return len; } main() { char str[80];int n; gets(str); n=lwordn(str); printf("%d\n",n); } 注意事项:英文单词都是由英文字母构成的,所以这里用了isalpha()函数来判断一个字符是否是一个字母,但在使用该函数时一定要包含头文件ctype.h。 第十一章 结构体与共用体 11.1 重点、难点 1、结构体类型 结构体类型的定义 一般形式: struct 结构体名 {类型名1 结构体成员1; 类型名2 结构体成员2; …… 类型名n 结构体成员n; }; 说明: (1)struct是关键字,是结构体类型的标志。struct 与结构体名合称类型标识符。 (2)结构体名为用户定义的标识符。 (3)由若干个成员组成。 (4)定义结构体类型时,不分配内存单元。 (5)分号不要丢失。 定义结构体类型变量的方式 (1)先定义结构体类型,再定义变量。 (2)定义结构体类型同时定义变量。 (3)直接定义结构体变量。 3. 给结构体变量、数组赋初值 对结构体变量赋初值时,C 编译程序按每个成员在结构体中的顺序一一对应赋初值。不允许跳过前面的成员给后面的成员赋初值,但可以只给前面的若干个成员赋值,对于后面未赋值的成员,对于数值型和字符型数据,系统自动赋初值零。 4.引用结构体中的数据 (1)通过成员运算符“·”引用结构体变量的成员,一般形式为: 结构体变量名·成员名 (2)通过指针运算符“*”引用结构体变量的成员,一般形式为: (*指针变量名)·成员名 (3)通过指向运算符“->”引用结构体变量的成员,一般形式为: 指针变量名->成员名 5.结构体类型变量的存储形式 结构体变量的成员在内存中占用连续的存储区域,所占内存单元的大小为结构体中每个成员所占内存单元的长度之和。 6.结构体变量在函数中作参数 用结构体变量的成员作为函数的参数,或者用结构体变量作为函数的参数,用法同普通变量,属于“值传递方式”。 用指向结构体变量或数组的指针作实参,将结构体变量或数组的地址传递给形参。 7.用指针处理链表 结构体是构造动态数据结构非常有用的工具,在结构体的成员项中,增加指向同类型结构的指针成员项,来描述它与同类型数据的连接关系,就构成动态的数据结构。 链表是指把若干个数据项按一定的原则连接起来的表。链表中的每个数据称为结点。结点之间用指针来连接,前一个结点指向下一个结点,最后一个结点的指针值为空(NULL)。 处理动态链表所需要的函数 1)malloc函数 一般形式:malloc(size); 用来分配size个字节的存储区,返回值的类型为void*型(无值型)即任何指针变量都不指向该存储区域,若没有足够的内存单元供分配,函数返回空(NULL);要求size的类型为unsigned int 2)calloc函数 一般形式:calloc(n,size); 用来给n个、同一类型的数据项分配连续的存储空间。每个数据项的长度为size个字节。若分配成功,函数返回存储空间的首地址;否则返回空(即NULL)。由该函数所分配的存储单元,系统自动置初值0。要求n和size的类型都为unsigned int。函数返回值的类型为void。 3)free函数 一般形式: free(p); 释放指针p所指存储空间。此函数无返回值。指针变量p必须指向由malloc函数或calloc函数分配的地址。由动态分配得到的存储单元没有名字,只能靠变量来引用它。一旦指针改变方向,原存储单元及所存数据都无法再引用。 链表的建立 链表的插入操作 链表的删除操作 链表的遍历操作 2、共用体类型 使几个不同的变量共占同一段内存的结构称为“共用体”类型的结构。 1.一般形式: union 共用体标识名 {类型名1 共用体成员名1; …… 类型名n 共用体成员名n; }; 2.共用体变量的定义(定义共用体方式与定义结构体相同) (1)先定义共用体类型,再定义变量。 (2)定义共用体类型同时定义变量 (3)直接定义共用体变量 3.共用体变量的引用 (1)通过成员运算符“·”引用共用体变量的成员,一般形式为: 共用体变量名·成员名 (2)通过指针运算符“*”引用共用体变量的成员,一般形式为: (*指针变量名)·成员名 (3)通过指向运算符“->”引用共用体变量的成员,一般形式为: 指针变量名->成员名 共用体变量的存储形式 共用体中的所有成员共占同一段存储区,因此一个共用体变量在某一时刻只能存放某一成员的值,而不能同时存放多个成员的值。存储区长度与占字节数最多的那个成员字节数相等。变量中的所有成员的首地址相同。 3、用typedef定义类型 1.一般形式:typedef 类型名 标识符 2.功能:用“标识符”来代表已经存在的“类型名”,并未产生新的数据类型,原有类型名依然有效。 3.说明: (1)类型名必须是在此语句之前已定义的类型标识符。 (2)标识符:可以说是类型名的别名,为了便于识别,一般大写字母表示。 11.2例题 【例11-1】设有以下说明语句 struct exa {int x; float y; }z; 则下面的叙述不正确的是 。 A.x和y都是结构体成员名 B.struct exa是用户定义的结构体类型 C.struct是结构体类型的关键字 D.z是用户定义的结构体类型名 解析:选项A中x和y都是结构体exa的成员名,是正确的。选项B中struct exa是用户定义的结构体类型也是正确的。选项C中struct是结构体类型的关键字也是正确的。选项D中z是用户定义的结构体类型名是错误的,它混淆了结构体类型与变量的概念,z是结构体类型变量。 答案:D。 知识点: 结构体类型的定义和结构体类型变量的定义。 结构体类型和结构体类型变量是不同的概念,不能混淆。定义结构体类型变量时,先定义结构体类型,然后再定义变量属于该类型。 【例11-2】在16位IBM-PC机上使用C语言,若有如下定义: struct data {int i; char ch; float x; double y; }a; 则结构体变量a占用内存的字节数是 。 A.1 B.4 C.15 D.8 解析:结构体类型变量a占用内存的字节数是所有成员占用内存长度之和,故为: 2 + 1 + 4 + 8 = 15 知识点:结构体变量的存储。 定义了一个结构体类型后,系统并没有为所定义的成员项分配相应的存储空间。只有定义一个结构体类型变量,系统才为所定义的变量分配相应的存储空间。一个结构体变量占用内存的实际大小可用sizeof运算符求出。 【例11-3】定义如下结构体来描述一个人的基本情况: struct date {int year; int month; int day; }; struct person {char name[20]; char sex; struct date birthday; }man; 如果某人的生日是1988年10月9日,下列对生日的正确赋值是 。 A.man·birthday·year=1988 B.birthday·year=1988 man·birthday·.month=10 birthday·month=10 man·birthday·day=9 birthday·day=9 C.man·year=1988 D.year=1988 man·month=10 month=10 man·day=9 day=9 解析:选项B、C、D均不是逐级访问结构体成员,忽略了结构体成员的所属关系,因此是错误的。选项A从外层到内层用“.”展开,清楚地说明了成员的所属关系,故选项A是正确的。 答案:A。 知识点:结构体成员的引用。 如果一个结构体中又嵌套一个结构体,则要访问一个成员时,应采取从外层向内层逐级访问的方法。 【例11-4】根据下面的定义,能打印出字母M的语句 是。 struct person {char name[20]; int age; }; struct person man[30]={“John”,17,”Paul”,19,”Maul”,18,”Robert”,20}; A.printf(”%c\n”,man[2] ·name[1]; B.printf(”%c\n”,man[2] ·name[0]); C.printf(”%c\n”,man[3] ·name[1]; D.printf(”%c\n”,man[3] ·name[0]); 解析:数组name是结构体person的成员,数组man是结构体person类型的数组,数组元素的下标都是从0开始,选项A输出的是“Marry”中的字母a,选项B输出的是“Marry”中的字母M,选项C输出的是“Robot”中的字母o,选项D输出的是“Robot”中的字母R。 答案:B。 知识点:结构体数组的定义、初始化以及结构体数组元素的引用。 结构体数组的定义与结构体变量的定义相似,有三种方法。 结构体数组中第一个元素的下标从0开始,数组名表示该结构体数组的存储首地址。 结构体数组在内存中占有一段连续的存储空间,所占内存字节数为结构体类型的大小乘以数组元素的个数。 结构体数组元素的引用与结构体变量的引用方法相同。 【例11-5】以下对结构体类型的变量定义中不正确的是 。 解析:选项A先定义了一个符号常量STUD来代表结构体类型struct student,这样在程序中二者是完全等效的。选项A采用了先定义结构体类型,然后再定义该结构体类型变量的方法。选项B采用在定义结构体的同时定义结构体类型变量的方法。选项C采用直接定义结构体类型变量的方法。上述三种方法定义的结构体变量都是正确的。选项D直接定义了结构体类型变量student,但下面又用结构体变量student来定义结构体变量stud1,stud2,因此不正确。 答案:D。 知识点:结构体类型变量的定义。 【例11-6】struct person {char name[20]; int age; struct {int year; int month; int day; }birth; }stu1={“lin tao”,20,{1980,1,22}},stu2; 下面对变量stu1和stu2的操作中正确的是 。 A.stu2={“lin tao”,20,{1980,1,22}} B.stu2=stu1 C.printf(“%s,%d,%d,%d,%d”,stu1); D.scanf(%s”,&stu1); 解析:选项A把一组常量赋给结构体变量,在C语言中不允许这样操作。选项B把一结构体变量作为一个整体赋给同一结构体类型的变量,是正确的。选项C和D把结构体变量作为一个整体输入、输出,是错误的。 答案:B。 知识点:结构体变量的初始化、结构体变量的整体引用和结构体变量的输入输出。 C语言允许将一个结构体变量作为一个整体赋给另一个同类型的结构体变量,但不允许把一组常量直接赋给一个结构体变量。结构体变量的输入输出,要求必须指明结构体变量所对应的各成员项。不允许把一个结构体变量作为一个整体进行输入输出操作。 【例11-7】若有以下的说明: struct student {char name[20]; int age; char sex; }a = {“wanghong”,22,’M’},*p = &a; 则字符串“wanghong”的引用方式不可以是()。 A.(*p)·name B.p·name C. a·name D. p - > name 解析:选项A和D用结构体指针引用成员。选项C用结构体变量名引用成员,都是结构体成员正确的引用方法。选项B直接用结构体指针引用成员是错误的。 答案:B。 知识点:结构体成员的各种引用方法。 【例11-8】设有以下语句,下列表达式中值为6的是 。 struct b {int x; struct b *next; }; struct b a[3] = {5,&a[1],7,&a[2],9,’\0’},*p; p = &a[0]; A.( * p).x + + B.p + + - > x C.+ + p - > x D.p - > x + + 例题分析:选项A是先取p所指向的结构体变量a[0]的x成员值,再使x的值增1,故表达式的值为5。选项B是取p->x的值5,然后再使p++指向下一个元素a[1]。选项D中的表达式是取选项A的表达式的另一种形式,因此表达式的值也是5。选项C是使p->x的值5增1,取其值6作为表达式的值。 答案:C。 知识点:结构体变量的运算。 指向运算符“- >”的优先级高于自增运算符“+ +”,当“- >”与“+ +”混合运算时,还要注意到 + + 在前与在后的作用不相同。 【例11-9】阅读程序,程序运行结果正确的是 。 struct s {int x; int *y; }*p; int b[4]={10,20,30,40}; struct s a[4]={100,&b[0],200,&b[1],300,&b[2],400,&b[3]}; main() {p = a; printf(“%d”,+ + ( * p - > y)); printf(“%d”,( + + p ) - > x); printf(“%d”,+ + p - > x); A.10,11,12 B.100,200,300 C.11,200,300 D.11,200,201 解析:结构体数组a初始化后的状态如图11-1所示。 表达式 + +(* p - > y)是先取p所指向的y的内容10,使其增1变为11。表达式(+ + p)- >x是使p加1使其指向a[1],再取p所指向的x的值为200。表达式+ + p -> x是使p所指向的x(为200)增1,变为201。 答案:D。 知识点:运算符 –>的使用。 【例11-10】下面程序的运行结果是 。 strut data {int x; float y; }; main() { struct data a={115,123.4}; fun(a); printf(“%d,%f\n”,a.x,a.y); } fun(struct data b) { b·x=100; b·y=101.123; } A.100,101.123 B.115,101.123 C.100,123.4 D.115,123.400000 解析:由于结构体变量a作为函数的实参,函数的参数间是值传递的过程,因此a把结构体的各成员值传递给形参结构体变量b。b的成员函数fun()中得到了新的值,但并没改变实参结构体变量a的值,因此程序输出值为:115,123.400000 答案:D。 知识点:结构体变量作函数的参数。 C语言允许直接用结构体变量作为函数的参数。当使用结构体变量作为函数的参数时,直接将实参结构体变量的各个成员的值全部对应传递给形参的结构体变量,参数的传递仍是“值传递方式”。在函数间传递结构体变量的方式除了上述方法之外,还有用结构体的成员作为函数的参数和用结构体的地址作为函数的参数等两种方法。 【例11-11】以下对C语言中共用体类型数据的叙述不正确的是 。 A.可以对共用体变量名直接赋值 B.一个共用体变量的各个成员使用同一存储区域 C.一个共用体变量所占内存单元数为其成员中占内存单元最长者的长度 个共用体变量中不能同时存放其所有成员 解析:选项A可以对共用体变量名直接赋值是错误的,选项B、C和D叙述了共用体变量的某些特征,因此都是正确的。 答案:A。 知识点:共用体类型及共用体类型的变量。 【例11-12】已知: union u { int i; char ch; } temp; 执行语句temp.i=266;后,temp.ch的十进制值为 。 A.266 B.256 C.10 D.1 解析:共用体类型u变量temp定义后,要占用两个字节的存储单元存放其成员项的值。如下图所示: 当执行语句temp·i=266后,两个字节的存储单元数据为: 由于temp·ch共用低字节,而0000 1010的十进制值为10,即temp·ch的值为10,选项C正确,其余皆错误。 答案:C。 知识点:共用体变量的引用及存储形式。 【例11-13】建立如下所示的存储结构(即每个结点有两个域,data是数据域,next是指向结点的指针域),请将定义补充完整。 struct st { int data; ; }node; 解析:链表结点的定义用含有指针项的结构体变量就行,而且该指针项指向的是struct st类型的变量,因此空白处应填: struct st *next 答案:struct st *next 知识点:链表结点的定义。 链表是指把若干个数据项按一定的原则连接起来的表。链表中的每个数据称为结点。链表的连接原则是:前一个结点指向下一个结点,只有通过前一个结点才能找到下一个结点。因此链表的每个结点应包括两部分(称之为域):用于存储数据的数据域和用于存储下一个结点的地址的指针域。在链表中为确定第一个结点,设置一个指向第一个结点的头指针;为标识链表中的最后一个结点,把最后一个结点的指针设置为空(NULL)。结点的指针域是指针变量,指向与它所在的结点是同一类型的结点,结点的数据域可以是各种类型的数据。链表结构与数组结构不同,它在逻辑上是有序的,而在物理上(即内存的实际存储位置)则可能是无序的,而数组元素占用连续的存储单元,在物理上是有序的,在逻辑上也是有序的。 【例11-14】完善程序.设有一描述学生的数据结构如下: 姓名 Name  学号 Num  指针 Next  下面程序建立了10个学生的链表: #include <stdlib.h> #include <stdio.h> #define NULL 0 #define LEN sizeof (struct students) struct students { char name[20]; int num; (1) ; }; main() { struct students *head, *p; int i; head=NULL; for(i=0;i<10;i++) { p= (2) ; scanf(“%s,%d”,p->name,&p->num); p->next=head; head=p; } p=head; for(i=0;i<10;i++) { printf(“%s,%d\n”,p->name,p->num); p=p->next;} } 例题分析:建立链表有两种方法:一种是如图11-2所示的倒序建立链表(即先建立的结点在链表的尾部,后建立的结点在链表的首部),一种是如图11-3所示的正序建立链表(即先建立的结点在链表的首部,后建立的结点在链表的尾部)。本题为前一种方法。 结构体struct students是链表中的结点结构,必须含有指向struct students类型的指针变量,因此空白(1)应填:struct students *next 链表的建立过程: struct students head,p; head=NULL; 定义了两个指向学生结构体的指针head和p,并使head指向NULL(空)。 先使用malloc()函数为每个结点分配存储空间,并将该空间的起始地址赋给p,使p指向该结点。因此空白(2)应填:(struct students *)malloc(LEN) (如图11-4)。 通过scanf()为name和num域赋值(如图11-5)。 把head值赋给p->next,即p->next值为NULL,再把p值赋给head,使head也指向该结点(如图11-6),即p->next=head;head=p; 重复2 ~4就可完成具有n个结点的单项链表(如图11-7 ~ 图11-9)。这样建立的链表,结点顺序是从右向左,并且头指针指向最后建立的结点(如图11-10)。 答案:(1)struct students *next (2)(struct students *)malloc(LEN) 知识点:链表的建立。 【例11-15】下面的函数creat()用来建立一个带头结点的单向链表,新产生的结点总是插在链表的末尾.单向链表的头指针作为函数的返回值。请填空。 #include<stdlib.h> #include<stdio.h> #define NULL 0 struct list { char data; struct list * next; }; struct list * creat( ) { struct list *h, *p, *q; char ch; h= (1) malloc(sizeof(struct list)); p = q = h; ch = getchar(); while( ch ! =’?’) { p= (2) malloc(sizeof(struct list)); q - > data = ch; q - > next = p; q = p; ch = getchar(); } p - > next = NULL; (3) ; } 解析:结构体struct list是链表中的结点结构。 正序链表建立过程: struct list *h,*p,*q; 定义了三个指向结构体list的指针。 h = (1) malloc(sizeof(struct list)); p = q = h; 在内存为第一个结点分配存储空间,并使头指针h指向该结点,同时使另两个结构体指针指向该结点,完成初始化工作(如图11-11)。由于h指向该结点,因此malloc()函数返回值应是结构体类型list的地址。因此,空白(1)处应填:(struct list *) ch = getchar(); 字符型变量ch用于从键盘接收字符,用于判断是否再向链表添加新结点,若要添加,输入字符为非“?”,并把它赋给结构体的data项。若不再添加,输入字符为“?”。 while(ch! = ‘?’) { p = (2) malloc(sizeof(struct list)); p -> data = ch; q -> next = p; q = p; ch = getchar(); } 通过循环控制向链表添加新结点。首先,p指向待加入链表的新结点,并为其data域赋值(如图11-12)。然后,把它与链表相接,即把前一个结点(用q指向)的next域赋值为p(如图11-13)。最后新结点已加入链表,移动指针q使之指向链表中最后加入的结点(如图11-14),并从键盘接收字符进入下一次循环,向链表添加新结点(如图11-15)。因此空白(2)处应填:(struct list *) 5.p -> next = NULL; (3) ; 链表建立结束后,应使最后一个结点的指针域为空值(不再指向任何结点)。由于creat()函数要返回链表的头指针,因此空白(3)处应填: return(h) 答案:(1)(struct list *) (2)(struct list *) (3)return(h) 知识点:正序链表的建立。 正序链表与倒序链表建立的不同之处在于:倒序链表建立过程中只用到两个结构指针:一个指向下一个待进入链表的结点,一个指向刚进入链表的结点。头指针总是指向刚进入链表的结点。而正序链表的建立时需要三个结构指针(如本例),一个是指向第一个结点的头指针,头指针总是指向第一个进入链表的结点;一个是指向刚进入链表的结点;一个是指向下一个待进入链表的结点。 【11-16】下面的函数完成将一个结点插入到一个已有的链表中,并按照链表中结点的数据域data从小到大顺序构成链表。请填空。 #include<stdio.h> struct stu {int data; struct stu *next; }; struct stu *insert(h,p) struct stu *h, *p; {struct stu *new, *p1, *p2; p2 = h; new = p; if(h = = NULL) {h = new; new->next=NULL;} else {while((new - > data > p2 - > data) && (p2 - > next ! = NULL)) {p1 = p2; p2 = p2 - > next;} if(new - > data < = p2 - > data) if(h = = p2) { (1) ; h = new;} else { p1 - > next = new; (2) ; } else { p2 - > next = new; (3) ; } } return(h); } 解析:链表插入操作的过程如下: 1.指针变量p2指向链表中的第一个结点,并使new指向待插入的结点。见图11-16。 2.当链表为空表(即h与NULL相等)时,使new赋给头指针h,则待插入的结点为该链表的第一个结点,同时使new的next域赋为空值,即该链表只有一个结点。见图11-17。 3.当该链表不为空表时,先要找到待插入的结点new欲插入的位置,这一点通过while循环来实现。即new -> data与 p2 -> data想比较:若new -> data > p2 -> data,而且p2不是最后一个结点时,则p2后移,同时使p1指向原来p2所指的结点,从而找到new待插入的位置。见图11-18。 4.当p2与h相同时,则待插入的结点插在链表首位置,即第一个结点之间,因此空白(1)处应填写把p2值赋给new -> next,同时头指针h指向new。见图11-19。 5.当new -> data < p2 -> data时,表明要把待插入的结点new插在p1与p2之间,因此使new赋给p1 -> next,同时空白(2)处应使p2的值赋给new -> next。见图11-20。 6.当new -> data的值大于链表中所有data值时,new应插在链表尾,此时把new赋给p2 -> next,同时空白(3)处应使new -> next的值为空(NULL)。见图11-21。 答案:(1)new -> next = p2 (2)new -> next = p2 (3)new -> next = NULL 知识点:链表的插入操作。 在一个有序链表结构中,插入一个新结点,首先要找到新结点应该插入的位置,然后根据新结点是插在链表首、链表尾或链表中间的不同情况,采用不同的方法来处理。 【例11-17】已知头指针h指向链表中的第一个结点,下面函数delete()从该链表中删除数据域data值为m的某一结点。请填空。 struct stud {int data; struct stu *next; }; struct stu *delete(struct stu *h,int m) {struct stu *p1, *p2; if(h = = NULL) printf(“Empty\n”); else {p1 = h; while( (1) ) {p2 = p1; p1 = p1 - > next; } if( m = = p1 - > data) {if(p1 = = h) (2) ; else (3) ; printf(“delete:%d\n”,m); } else printf(“%d not been found\n”,m); } return(h); } 解析:删除操作的过程如下: 判断待删除结点的链表是否为空表,若为空表(h = = NULL),则输出相应提示信息“Empty”。 设置两个指针变量p1和p2,使p1指向头指针h。见图11-22。 3.While循环用来寻找待删除结点,若当前p1指向的结点不是待删除结点,修改指针使p1指向下一结点,而p2指向该结点。因此空白(1)处应填m ! = p1 - > data && p1 - > next ! =NULL。 见图11-23。 4.若待删除结点为第一个结点,则应该修改头指针,因此空白(2)处应填:h = p1 - > next。见图11-24。 5.若待删除结点在链表中,则要修改该结点的前后指针,删除该结点。因此空白(3)处应填: p2 - > next = p1 - > next。见图11-25。 若删除结点在链表中没找到时,输出相应的提示信息。 答案:(1)m ! = p1 - > data && p1 - > next ! =NULL (2)h = p1 - > next (3)p2 - > next = p1 - > next 知识点:链表的删除操作。 在链表结构中删除某一个结点,首先要找到该结点的位置,然后根据该结点在链表中的不同位置采用不同的方法来删除。 【例11-18】已知head是一个单链表的头指针,下面的函数fmin()用于求出链表中所有结点的数据域值最小的结点的位置,由指针变量s传回调用程序,请填空。 struct data {int num; struct data *next; }; fmin(struct data *head, (1) ) {struct data *p; p = (*head).next; *s = p; while( p ! = NULL) {if((*p).num< (3) ) *s = p; { p= (2) ; } } main() {struct data *head, *q; …… fmin(head,&q); printf(“min=%d\n”,q - > num); …… } 解析:函数fmin()的第二个形参应与实参&q类型相一致,由于q是指向struct data的指针变量,因此fmin()的第二个形参应为二级指针而且指向struct data。因此空白(1)处应填: struct data * * s 函数fmin()查找数据域最小的结点,用指针变量s指向该结点,用指针变量p遍历链表中的结点。 首先使p指向连表中的第一个结点,并把第一个结点的数据域值作为临时最小值。P用于遍历链表中的结点,因此空白(2)处应填:p - > next 当逐个结点的数据域与临时最小值(用s指向)相比较,若比最小值小,就更新最小值(* d = p)。因此空白(3)处应填:(* * s).num或( * s ) - > num 答案:(1)struct data * * s (2)p - > next (3)(* * s).num或( * s ) - > num 知识点:链表的遍历操作及函数间的指针传递。 链表的遍历操作是指依次访问链表中的各结点,从而求出结点中数据域值最小的那个结点。 函数的调用过程中,主函数调用fmin()时,第二个实参是指针变量q的地址(&q),因此fmin()函数的第二个形参类型应与之相匹配才行。 第十二章 位运算 12.1 内容提要 1.位运算符 (1)位运算符 位逻辑运算符号:~(按位求反)、&(按位与)、|(按位或)、^(按位异或) 位移位运算符:<<(按位左移)>>(按位右移) 位自反赋值运算符:&=、|=、^=、<<=、>>= (2)位运算符的运算规则 位运算符的运算规则是: ~1=0~0=1 0&0=00&1=01&0=01&1=1 0^0=00^1=11^0=11^1=0 0|0=00|1=11|0=11|1=1 位移运算符:<<(按位左移)、>>(按位右移)。位移时,移出的位数全部丢弃,移出的空位补入的数与左移还是右移有关。如果是左移,则规定补入的数全都是0;如果是右移,还与被移位的数据是否带符号有关。若是不带符号的数据,则补入的数全都为0;若是带符号的数据,则补入的数据为原数的符号位上的数。 (3)位运算符的优先级 位运算符自身的优先级为(从高到低):~、<<、>>、&、^、| 位运算符与其他运算符相比较优先级为(从高到低): ~、算术运算符、<<、>>、关系运算符、&、^、|、逻辑运算符、条件运算符、赋值(自反赋值)运算符、逗号运算符。 12.2 例题 【例12-1】下列表达式中不正确的是_____。 A.~’a’ B.’c’&’R’ C.1.1|2 D.2^’F’ 解析:位运算中除~以外,均为二目运算符,即要求两侧各有一个运算量,且运算量只能是整型或字符型的数据,不能为实型数据。故选项C不正确。 答案:C。 【例12-2】表达式~0x13的值是_____。 A.0xFFEC B.0xFF71 C.0xFF68 D.0xFF17 解析:按位求反运算符的运算。按位求反运算符是单目运算符,用来对一个二进制数按位求反,即1变成0,0变成1。0x13在C语言里是表示十六进制数13,对应的二进制数为0000 0000 0001 0011,按位求反后变成:1111 1111 1110 1100。对应的十六进制数为FFEC。 答案:A。 【例12-3】若有下面的说明和语句,则输出结果为_____。 char a=9,b=020; printf("%o\n",~a&b<<1); A.0377 B.40 C.23 D.以上答案均不正确 解析:十进制数a化为二进制数为0000 1001,八进制数b化为二进制数为0001 0000。由于按位取反运算符号优先级最高,故~a的值为1111 0110。左移运算符优先于按位与运算符,因此将b左移一位得0010 0000。最后进行按位与运算: 1111 0110 & 0010 0000 0010 0000 输出为其八进制数数值40。因此选项A、C、D都是错误的。 答案:B。 【例12-4】设有以下说明: struct packed_data { unsigned a:2; unsigned b:3; unsigned c:4; int i; }; struct packed_data data; 则以下段数据的引用中不能得到正确数值的是_____。 A.data.a=4; B.data.b=7; C.data.c=9; D.data.i=-4; 解析:使用位段的时候要特别注意位段允许的最大值范围,位段中存放的数值不能超过位段在内存中指定的位数。本例中data.a占2个二进制位,占data.b占3个二进制位,data.c占4个二进制位,data.i占16个二进制位。因此,选项B、C、D所在的位段都能得到正确的数值,而选项A之data.a只占2个二进制位,最大值为3。在此情况下,自动赋予它的数的低位。如4的二进制数形式为0100,而data.a只有2位,取0100的低2位,则data.a得值0。 答案:A。 【例12-5】下面程序运行的结果是_____。 main( ) { unsigned char a,b; a=0x9d; b=0xa5; printf("a AND b:%x\n",a&b); printf("a OR b:%x\n",a|b); printf("a NOR b:%x\n",a^b); } 解析:按位与、按位或、按位异或运算符的运算。字符型变量a与b对应的二进制值为1001 1101与1010 0101。 由于 1001 1101 1001 1101 1001 1101 & 1010 0101 | 1010 0101 ^ 1010 0101 1000 0101 1011 1101 0011 1000 因此a&b、a|b、a^b的十六进制值为:85,bd,38。 答案:a AND b:85 a OR b:bd a NOR b:38 【例12-6】下面程序运行的结果为_____。 main( ) { unsigned u=0123; int n=-72; printf("无符号八进制数:%o,%o\n",u<<2,u>>3); printf("有符号十进制数:%d,%d\n",n<<2,n>>3); } 解析:位移位运算符的运算。无符号八进制数u=0123对应的二进制数为0000 0000 0101 0011。当把它左移二位后(u<<2)得到0000 0001 0100 1100,其八进制值数值为514;当把它右移3位后(u>>3)得到0000 0000 0000 1010,其八进制值数值为12。带符号十进制数n=-72,以补码形式存储在内存单元,它的补码为1111 1111 1011 1000。当把它左移2位后(n<<2)得到1111 1110 1110 0000,其十进制数值为-288;当把它右移3位后(n>>3),得到1111 1111 1111 0111,其十进制数的数值为-9。 答案:无符号八进制数:514,12 有符号十进制数:-288,-9 【例12-7】下面的程序是取一个整数a从右段开始的5~8位,并将结果d输出。请在_____处添入适当命令行,使之正确运行。 main( ) { unsigned a,b,c,d; scanf("%o",&a); b=a>>5; c=~(~0<<4); d=___ ____; printf("%o",d); } 解析:要取整数a从右段开始的5~8位,首先使a右移5位得到b,.即a的从右段开始的5~8位成为b中低4位;然后设置一个低4位全为1,其余全为0的数c=~(~0<<4);最后b&c,可将这4位保留下来。 答案:b&c 【例12-8】此程序交换两个变量的值,不使用临时变量。其中有一处错误,请指出并改正。 ① main( ) ② { float a,b; ③ scanf("%d,%d",&a,&b); ④ a=a^b; ⑤ b=b^a; ⑥ a=a^b; ⑦ printf("a=%d,b=%d",a,b); ⑧ } 解析:此程序使用"异或"运算符来实现交换两个值,"异或"运算符是二目位运算符,要求运算量只能是整型或字符型的数据,所以变量a和b不能是float类型的。 答案:②有错误,改正后为:int a,b; 【例12-9】编写一个C程序,实现一个整数的右循环移位。 答案: main( ) { unsigned a,b,c; int n; scanf("%o,%d",&a,&n); b=a<<(16-n); c=a>>n; c=c|b; printf("%o\n%o",a,c); } 解析:将a的右端n位先放到b中的高n位中,由语句"b=a<<(16-n);"来实现;将a右移n位,其左面高位n位补0,由语句"c=a>>n;"来实现;将c与b进行按位或运算,由语句"c=c|b;"来实现,c值就是将a循环右移n位后的结果。 第十三章 文件 13.1 重点、难点 1.文件的概念及分类 文件(file)是指存储在外部介质上(如磁盘)数据的集合。C语言把文件看作是一个字符(字节)的序列,即由一个一个字符(字节)的数据顺序组成,称之为流式文件。 C语言对文件的操作实际上是指对文件的读写。文件的读操作就是从文件中读出数据,即将文件中的数据读入计算机;文件的写操作是向文件中写入数据,即向文件输出数据。C语言对文件的处理过程就是对文件的输入输出过程。 文件操作应遵循的规则:在C语言中,使用文件要遵循一定的规则。在使用文件之前应该打开文件,使用结束后应该关闭该文件,即:打开文件、使用文件、关闭文件。 C语言文件根据数据在内存中的组织形式不同,可分为两类文件:文本文件(ASCII码文件)和二进制文件。 C语言文件根据读写方式的不同可以分为两类:顺序文件和随机文件。 2.文件类型指针 C语言中对已打开的文件进行输入输出操作是通过指向该文件的指针变量进行的。每个被使用的文件都在内存中开辟一个缓冲区,用来存入文件的有关信息。只有通过文件指针才能实现对文件的读写操作。 文件指针的说明形式如下: FILE*文件指针名; 其中FILE是文件结构体的标识符,它是由系统定义的,如 FILE*fp; fp是一个指向文件结构体类型的指针变量,一般指向被打开的文件。 3.文件的打开与关闭 (1)文件的打开(fopen函数) fopen函数的调用方式通常为: FILE*fp; fp=fopen(文件名,文件使用方式); 其中"文件名"为所要打开的文件名,必须是用双引号引起的字符串常量。"文件使用方式"是具有特定含义的符号。 函数的作用:以指定的方式打开指定的文件。fopen函数调用成功后,以指定方式打开指定的文件,这是会返回指向该文件的指针;若fopen函数调用不成功,即不能打开指定的文件,这时的返回值是空指针值NULL。 (2)文件的关闭(fclose函数) fclose函数调用的一般形式为: fclose(文件指针); 函数的作用:关闭该文件,把缓冲区中的数据(未装满缓冲区的数据)输出到磁盘上,释放文件指针。当文件关闭成功时,fclose函数的返回值为0;否则返回值为EOF(-1),表示关闭文件时出错。 4.文件的读写函数的使用 文件的字符读写函数:fputc(),fgetc() 文件的数据块读写函数:fwrite(),fread() 文件的格式读写函数:fprintf(),fscanf() 文件的整数读写函数:putw(),getw() 文件的字符串读写函数:fputs(),fgets() 5.文件的定位函数的使用 文件位置指针置于文件头的函数:rewind() 改变文件位置指针的当前位置的函数:fseek() 返回文件位置指针的当前值的函数:ftell() 6.文件操作的出错检测 文件读写错误检测函数:ferror() 清除文件错误标志函数:fclearerr() 文件结束检测函数:feof() 13.2 例题 【例13-1】下列关于C语言文件操作叙述正确的是_____。 A.先关闭文件 B.先打开文件 C.对顺序无要求 D.先测试文件是否存在,然后再打开文件 解析:文件不一定先测试再打开,例如写文件时源文件根本不存在,当然也不需要测试,所以选项D不正确;关闭一个不存在的文件,反而会出错,所以选项A也是错误的;选项C也是错误的,对文件的读写操作应该是先打开文件,所以选项B是正确的。 答案:B。 【例13-2】若fp是指向某文件的指针,且已读到文件的末尾,则表达式feof(fp)的返回值是_____。 A.EOF B.-1 C.非零值 D.NULL 解析:因为feof( )函数,在读到文件末尾时函数值为1,否则为0。在stdio.h头文件中符号常量EOF定义为-1,4个选项中,选项A、B虽然都是非零值,但不是1,都是错误的;因为NULL是0,选项D也是错误的。选项C是正确的。 答案:C。 【例13-3】C语言中库函数fgets(str,n,fp)的功能是_____。 A.从文件fp中读取长度n的字符串存入str指向的内存 B.从文件fp中读取长度不超过n-1的字符串存入str指向的内存 C.从文件fp中读取n个字符串存入str指向的内存 D.从str读取至多n个字符到文件fp 解析:因为fgets(str,n,fp)函数从fp中读取至多n-1个字符(n用来指定字符数),并把它们放入str指定的字符数组中。它读取字符直到遇到换行符或EOF为止,或者读入了所限定的字符个数为止。读完后它在数组的最后放入一个终止符"\0"。换行符将被保留,并且是str中的一员。所以本题正确答案是B。 答案:B。 【例13-4】已知一个文件中存放若干学生记录,其数据结构如下: struct st { char num[10]; int age; floar s[5]; }; 定义一个数组:struct st a[10]; 假定文件已正确打开,不能正确地从文件中读出10名学生数据到数组中的是_____。 A.fread(a,sizeof(struct st),10,fp); B.for(i=0;i<10;i++)fread(a[i],sizeof(struct st),1,fp); C.for(i=0;i<10;i++)fread(a+i,sizeof(struct st),1,fp); D.for(i=0;i<5;i+=2)fread(a+i,sizeof(struct st),2,fp); 解析:因为函数fread( )的格式为fread(void buf,int size,int count,FILE fp),该函数是从fp指向的流中读取count(字段数)个字段,每个字段为size(个字符),并把它们放到buf(缓冲区)指向的字符数组中。文件指针随着所读取的字符数而增加。fread()返回实际已读取的字段数。若读取的字段数少于在函数调用时所要求的数目,就可能出现错误,或者已达到了文件末尾。这里,要求buf是指针,而选项B中a[i]是数组元素,所以是错误的。选项A也可以写成:fread(a,10*sizeof(struct st),1,fp);。 答案:B。 【例13-5】下列程序向文件输出的结果是_____。 #include "stdio.h" main( ) { FILE *fp=fopen("test","wb"); fprintf(fp,"%d%5.0f%c%d",58,76273.0,‘-’,2278); fclose(fp); } 解析:fprintf()对文本文件和二进制文件都是适用的,不论是文本还是二进制方式,fprintf()和fscanf()函数都是按数据的各位数字的字符形式存放的。数据项之间没有空格,第二个数据也没有小数位。 答案:5876273-2278。 【例13-6】下面的程序从键盘输入一行字符,输出到磁盘文件file.txt中。请在_____处添入适当命令行,使之正确运行。 #include<stdio.h> main() {FILE *5p; char str[80]; if( (1) = =NULL) { printf("can’t open file.\n"); exit(0); } while(strlen(gets(str))>0) {fputs(str,fp); fputs("\n",fp);} __ _(2)_ __; } 解析:关于文件的打开与关闭操作。C语言中文件操作的次序为:打开文件、处理文件、关闭文件。有时在打开文件时,可能由于某些原因,fopen()函数不能完成打开操作,返回错误信息,这时后面的关于文件的操作都不能进行,程序将终止运行,因此在程序中应能处理这种异常情况。 答案:(1)(fp=fopen("file.txt","w")) (2)fclose(fp) 【例13-7】 若要打开D盘上me 子目录下名为 myfile.dat 的二进制文件进行读操作,下面符合此要求的fopen 函数调用是_____。 A.fopen("D:\\me\\myfile.dat","rb") B.fopen("D:\\me\\myfile.dat","r") C.fopen("D:\ me\myfile.dat","rb") D.fopen("D:\me\myfile.dat","wb") 解析:fopen 函数要求两个字符串参数,第一个参数是打开读文件名,本题指定文件名和路径是D:\me\myfile.dat, 因为必须用字符串的形式表示,则字符’\’一定要用转义字符’\\’来表示,所以第一参数应当写成:"D:\\me\\myfile.dat"。按要求是对二进制文件进行读操作,所以第二个参数应当写成:"rb"。选项B中第二个参数"r"用于指定对文本文件进行读操作。选项C中第一参数"D:\me\myfile.dat"不正确。 答案: A。 【例13-8】 以下叙述中正确的叙述是_____。 A.C程序中,文件由记录组成。 B.C程序在执行结束时将自动关闭所以文件,因此在程序中可省略对文件的关闭操作。 C.C程序既可生成顺序存取文件也可生成随机存取文件。 D.在C程序中生成的文件,都用EOF作为文件的结束标志。 解析:C程序中生成的数据文件是数据流文件,系统并不在输出数据中添加符号,因此C程序中生成的数据文件不是由记录组成。C程序在执行结束时将自动关闭所有文件,但并不负责处理数据缓冲区中的剩余数据,而造成数据丢失,只有在fclose函数关闭文件时,函数将首先对缓冲区的剩余数据进行处理然后关闭文件,因此不可省略对文件的关闭操作。在C程序中只有生成的文本数据文件,用EOF作为文件的结束标志。只有选项C中的叙述是正确的。 答案:C。 【例13-9】 有以下程序: # include <stdio.h> main() { FILE *fp; int i=20,j=30,k=99,n=88; fp=fopen("d:\\d16_3.dat","w"); fprintf(fp,"%d",i);fprintf(fp,"%d\n",j); fclose(fp); fp=fopen("d:\\d16_3.dat","r");   fscanf(fp,"%d%d",& k,&n );printf("%d%d\n",k,n); fclose(fp); } 程序运行后的输出结果是_____。 A)20 30 B)20 88 C)2030 88 D)99 88 解析: (1)程序在d根目录下新建一个名为 d16_3.dat文本文件,并通过fprint函数输出20和30,在这个数据之间没有加入任何间隔符,在文件中两数以:2030这样的形式存放。输出后文件被关闭。 (2)文件随后以读的方式被打开,用fscanf(fp,"%d% d",&k,&n);语句从文件中读数据,变量k从文件中读入2030,变量n没有读到数据,因此维持原有数据不变,所以输出结果如选项C所示是2030 88。 (3)通过本例可知,当调用fprintf函数输出数据时需要在数据之间人为地添加分隔符,以便以后的输入,分隔符最好用空格或换行符。 答案: C。 【例13-10】有以下程序: # include <stdio.h> void fun(char *fname,char *st) { FILE *myf; int i; myf=fopen(fname,"w"); for(i=0;i<strlen(st);i++)fputc(st[i],myf); fclose(mfy); } main() {fun("test.t","new world"); fun("test.t", "hello,");} 程序执行后,文件test.t中的内容是_____。 A)hello B)new worldhello C)new world D)hello,rld 解析:函数fun的功能是打开名fname所指字符串所代表的文件,把st所指字符串中的字符依次输出到fname所指的文件中,退出函数时关闭文件。 主函数两次调用fun函数,每次都是对名为test.t的文件进行操作,即两次调用都对test.t文件输出字符。第一次调用fun函数时把字符串new world输出到文件中。第二次调用fun函数时把字符串hello,输出到文件中。由于test.t是顺序存取文本文件(打开方式是 "w"),每打开一次文件将从头开始写文件,每写一次,test.t中原有内容不再保留,因此,第二次写后,文件中的内容已经刷新成:hello,。所以程序执行后,文件test.t中的内容如选项A中所示。 答案:A。 【例13-11】 以下程序把从终端输入的M个字符串存放到d:\stdata.dat文件中,然后从此文件中的字符串原样读入到r字符串数组中,并输出到屏幕。 # include <stdio.h> #define N 80 #define M 5 main() { char s[M][N+1],r[M][N+1]; int i; FILE *fp; for(i=0;i<M;i++)gets(s[i]); fp=fopen("d:\\stdata.dat","w"); for(i=0;i<M;i++) {_________________} fclose(fp); fp=fopen("D :\\stdata.dat","r"); i=0; fgets(s[i],N,fp); while(!feof(fp)) { puts(s[i]);i++;fgets(s[i],N,fp);} fclose(fp); } 请在下划线处填入正确的选项。 A)fputs(s[i],fp); B)fputs(s[i],fp); fputs("\ n",fp); C)fputs(fp,s[i]); D)fputs(s[i],fp); fputs("\0",fp); 解析:fputs函数把字符串输出到指定的文件中,,输出时不包括字符串结束标志’\0’,因此当连续输出时所有的串首尾相接而无法区分原来的串。因此在输出的每一个字符串之后,应当人为地输出一个换行符,以便输入时区分,所以在下划线处应填选项B中的语句。注意,在读入时添加的换行符也被放在字符串中,应当设法去除。 答案:B。 【例13-12】feof函数用于判断是否读到了____(1)____,若是,函数值为____(2)____。 解析:当位置指针移到文件结束标志之后时,feof函数的值为1,否则feof函数的值为0。 答案:(1) 文件结束标志 (2) 1