设为首页收藏本站

教师网络培训和服务平台

 找回密码
 注册
搜索
楼主: 没完没了

C语言教学视频(19讲)

[复制链接]

687

主题

2

好友

35万

积分

发表于 2004-8-21 10:59:36 |显示全部楼层

[2月23日更新]C语言教学视频(19讲)

第3章 顺序结构程序设计
为了让计算机处理各种数据,首先就应该把源数据输入到计算机中;计算机处理结束后,再将目标数据信息以人能够识别的方式输出。C语言中的输入输出操作,是由C语言编译系统提供的库函数来实现。
3.1 格式化输出——printf()函数
3.2 格式化输入——scanf()函数
3.3 单个字符输入输出——getchar()和putchar()函数
3.4 顺序结构程序设计
良好的源程序书写风格──顺序程序段左对齐
3.1 格式化输出——printf()函数
printf()函数的作用:向计算机系统默认的输出设备(一般指终端或显示器)输出一个或多个任意类型的数据。
3.1.1 printf()函数的一般格式
[案例3.1] 已知圆半径radius=1.5,求圆周长和圆面积。/*案例代码文件名:AL3_1.C。*/
main()
{float radius,length,area,pi=3.1415926; radius=1.5; length=2*pi*radius; /*求圆周长*/ area=pi*radius*radius; /*求圆面积*/
printf(“radius=%f\n”,radius);/*输出圆半径*/
printf(“length=%7.2f,area=%7.2f\n”,length,area); /*输出圆周长、面积*/
}
程序运行结果如下:
radius=1.500000
length= 9.42,area= 7.07
printf()函数的一般格式如下:
printf("格式字符串" [,输出项表]);
1.格式字符串。“格式字符串”也称“转换控制字符串”,可以包含三种字符:
(1)格式指示符。格式指示符的一般形式如下:
%[标志][宽度][.精度][F|N|h|L][类型]
常用的标志字符如表3-1所示,常用的宽度指示符如表3-2所示,常用的精度指示符如表3-3所示,长度修饰符如表3-4所示,类型转换字符如表3-5所示。
(2)转义字符
例如,[案例3.1]中printf()函数中的`\n`就是转义字符,输出时产生一个“换行”操作。
(3)普通字符──除格式指示符和转义字符之外的其它字符。格式字符串中的普通字符,原样输出。
例如,[案例3.1]中“printf("radius=%f\n", radius);”语句中的“radius=”,“printf("length=%7.2f,area=%7.2f\n", length,area);”语句中的“length=”、“area=”等都是普通字符。
2.输出项表
输出项表是可选的。如果要输出的数据不止1个,相邻2个之间用逗号分开。下面的printf()函数都是合法的:
(1)printf("I am a student.\n");
(2)printf("%d",3+2);
(3)printf("a=%f b=%5d\n", a, a+3);
必须强调:“格式字符串”中的格式指示符,必须与“输出项表”中、输出项的数据类型一致,否则会引起输出错误。
3.1.2 格式指示符
输出不同类型的数据,要使用不同的类型转换字符。
1.类型转换字符d──以带符号的十进制整数形式输出。
[案例3.2] 类型转换字符d的使用。
/*案例代码文件名:AL3_2.C。*/
main()
{int num1=123;
long num2=123456;
/*用3种不同格式,输出int型数据num1的值*/
printf("num1=%d,num1=%5d,num1=%-5d,num1=%2d\n",
num1,num1,num1,num1);
/*用3种不同格式,输出long型数据num2的值*/
printf("num2=%ld,num2=%8ld,num2=%5ld\n",num2,num2,num2);
printf("num1=%ld\n",num1);
}
程序运行结果如下:
num1=123,num1=□□123,num1=123□□,num1=123
num2=123456,num2=□□123456,num2=123456
num1=16908411
对于整数,还可用八进制、无符号形式(%o(小写字母o))和十六进制、无符号形式(%x)输出。对于unsigned型数据,也可用%u格式符,以十进制、无符号形式输出。
所谓无符号形式是指,不论正数还是负数,系统一律当作无符号整数来输出。例如,printf("%d,%o,%x\n",-1,-1,-1);
2.类型转换字符f──以小数形式、按系统默认的宽度,输出单精度和双精度实数。
[案例3.3] 类型转换字符f的使用。
/*案例代码文件名:AL3_3.C。*/
main( )
{float f=123.456;
double d1,d2;
d1=1111111111111.111111111;
d2=2222222222222.222222222;
printf("%f,%12f,%12.2f,%-12.2f,%.2f\n",f,f,f,f,f);
printf("d1+d2=%f\n",d1+d2);
}
程序运行结果如下:
123.456001,□□123.456001,□□□□□□123.46,123.46□□□□□□,123.46
d1+d2=3333333333333.333010
本案例程序的输出结果中,数据123.456001和3333333333333.333010中的001和010都是无意义的,因为它们超出了有效数字的范围。
对于实数,也可使用格式符%e,以标准指数形式输出:尾数中的整数部分大于等于1、小于10,小数点占一位,尾数中的小数部分占5位;指数部分占4位(如e-03),其中e占一位,指数符号占一位,指数占2位,共计11位。
也可使用格式符%g,让系统根据数值的大小,自动选择%f或%e格式、且不输出无意义的零。
3.类型转换字符c──输出一个字符(只占一列宽度)。
[案例3.4] 类型转换字符c的使用。
/*案例代码文件名:AL3_4.C。*/
main()
{char c=`A`;
int i=65;
printf("c=%c,%5c,%d\n",c,c,c);
printf("i=%d,%c",i,i);
}
程序运行结果如下:
c=A,□□□□A,65
i=65,A
需要强调的是:在C语言中,整数可以用字符形式输出,字符数据也可以用整数形式输出。将整数用字符形式输出时,系统首先求该数与256的余数,然后将余数作为ASCII码,转换成相应的字符输出。
4.类型转换字符s──输出一个字符串。
[案例3.5] 类型转换字符s的使用。
/*案例代码文件名:AL3_10.C。*/
main()
{printf("%s,%5s,%-10s","Internet","Internet","Internet");
printf("%10.5s,%-10.5s,%4.5s\n","Internet","Internet","Internet");
}
程序运行结果如下:
Internet,Internet,Internet□□,□□□□□Inter,Inter□□□□□,Inter
注意:系统输出字符和字符串时,不输出单引号和双引号。
3.1.3 使用说明
(1)printf()可以输出常量、变量和表达式的值。但格式控制中的格式说明符,必须按从左到右的顺序,与输出项表中的每个数据一一对应,否则出错。
例如,printf("str=%s, f=%d, i=%f\n", "Internet", 1.0 / 2.0, 3 + 5, "CHINA");是错误的。
(2)格式字符x、e、g可以用小写字母,也可以用大写字母。使用大写字母时,输出数据中包含的字母也大写。除了x、e、g格式字符外,其它格式字符必须用小写字母。
例如,%f不能写成%F。
(3)格式字符紧跟在“%”后面就作为格式字符,否则将作为普通字符使用(原样输出)。
例如,“printf(”c=%c, f=%f\n“, c, f);”中的第一个c和f,都是普通字符。
3.2 格式化输入——scanf()函数
scanf()函数是用来从外部输入设备向计算机主机输入数据的。
3.2.1 scanf()函数的一般格式
[案例3.6] 已知圆柱体的底半径radius=1.5,高high=2.0,求其体积。/*案例代码文件名:AL3_6.C。*/
main()
{ float radius=1.5, high=2.0, pi=3.14159, vol; vol=pi*radius*radius*high; /*求体积*/
printf(“vol=%7.2f\n”,vol); /*输出求出的体积*/
}
[案例3.7] 已知圆柱体的底半径为radius,高为high,求其体积。
/*案例代码文件名:AL3_7.C。*/
/*功能:说明函数scanf()的格式及作用。*/
main()
{float radius,high,vol,pi=3.1415926;
printf("Please input radius & high: ");
scanf("%f%f",&radius,&high); /*从键盘输入两个实数赋给变量r,h*/
vol=pi*radius*radius*high;
printf("radius=%7.2f, high=%7.2f, vol=%7.2f\n",
radius,high,vol);
}
程序运行结果如下:
Please input radius & high: 1.5□2.0↙
radius=□□□1.50,high=□□□2.00,vol=□□14.14
在程序中给计算机提供数据,可以用赋值语句,也可以用输入函数。在C语言中,可使用scanf()函数,通过键盘输入,给计算机同时提供多个、任意的数据。
1. scanf()函数的一般格式
scanf("格式字符串", 输入项首地址表);
(1)格式字符串。格式字符串可以包含3种类型的字符:格式指示符、空白字符(空格、Tab键和回车键)和非空白字符(又称普通字符)。
格式指示符与printf()函数的相似,空白字符作为相邻2个输入数据的缺省分隔符,非空白字符在输入有效数据时,必须原样一起输入。
(2)输入项首地址表──由若干个输入项首地址组成,相邻2个输入项首地址之间,用逗号分开。
输入项首地址表中的地址,可以是变量的首地址,也可以是字符数组名或指针变量。
变量首地址的表示方法: &变量名
其中“&”是地址运算符。例如,[案例3.7]中的“&radius”是指变量radius在内存中的首地址。
2.scanf()函数的功能:从键盘上接收格式化输入。
运行[案例3.7]的程序时,从键盘上输入2个实数,分别存入&radius、&high起始的存储单元中,即输入两个实数分别赋给radius和high。
3.2.2 格式指示符
格式指示符的一般形式为: %
  • [宽度] [F|N] [h|l] 类型字符
    1.类型字符
    类型字符如表3-6所示。例如,在[案例3.7]的scanf()函数语句中,格式字符串“%f%f”。
    2.宽度n
    指定该项输入数据所占列数为n。
    换句话说,读取输入数据中相应的n位,但按需要的位数赋给相应的变量,多余部分被舍弃。
    例如,scanf("%3c%3c",&ch1,&ch2);
    printf("ch1=%c,ch2=%c\n",ch1,ch2);
    假设输入“abcdefg”,则系统将读取的“abc”中的“a”赋给变量ch1;将读取的“def”中的“d”赋给变量ch2,所以printf()函数的输出结果为:ch1=a,ch2=d
    3.赋值抑制字符*
    表示本输入项对应的数据读入后,不赋给相应的变量(该变量由下一个格式指示符输入)。
    例如,scanf("%2d%*2d%3d",&num1,&num2);
    printf("num1=%d,num2=%d\n",num1,num2);
    假设输入“123456789”,则系统将读取“12”并赋值给num1;读取“34”、但舍弃掉(“*”的作用);读取“567”并赋值给num2。所以,printf()函数的输出结果为:num1=12,num2=567。
    4.类型修饰符──F、N、h、l。
    其含义与printf()中的一样,分别为远指针、近指针、短整型和长整型。
    3.2.3 数据输入操作
    1.如果相邻2个格式指示符之间,不指定数据分隔符(如逗号、冒号等),则相应的2个输入数据之间,至少用一个空格分开,或者用Tab键分开,或者输入1个数据后,按回车,然后再输入下1个数据。
    例如,scanf("%d%d",&num1,&num2);
    假设给num1输入12,给num2输入36,则正确的输入操作为:12□36↙
    或者:12↙
    36↙
    注:使用“↙”符号表示按回车键操作,在输入数据操作中的作用是,通知系统输入操作结束。
    2.“格式字符串”中出现的普通字符(包括转义字符形式的字符),务必原样输入。
    例如,scanf("%d,%d",&num1,&num2);
    假设给num1输入12,给num2输入36,正确的输入操作为:12,36↙
    另外,scanf()函数中、格式字符串内的转义字符(如\n),系统并不把它当转义字符来解释,从而产生一个控制操作,而是将其视为普通字符,所以也要原样输入。
    例如:scanf("num1=%d,num2=%d\n",&num1,&num2);
    假设给num1输入12,给num2输入36,正确的输入操作为:
    num1=12,num2=36\n↙
    提高人机交互性建议:为改善人机交互性,同时简化输入操作,在设计输入操作时,一般先用printf()函数输出一个提示信息,再用scanf()函数进行数据输入。
    例如,将scanf("num1=%d,num2=%d\n",&num1,&num2
    );改为:
    printf("num1="); scanf("%d",&num1);
    printf("num2="); scanf("%d",&num2);
    3.输入数据时,遇到以下情况,系统认为该数据结束:
    (1)遇到空格,或者回车键,或者Tab键。
    (2)遇到输入域宽度结束。例如“%3d”,只取3列。
    (3)遇到非法输入。例如,在输入数值数据时,遇到字母等非数值符号(数值符号仅由数字字符0-9、小数点和正负号构成)。
    4.使用格式说明符“%c”输入单个字符时,空格和转 义字符均作为有效字符被输入。
    例如,scanf("%c%c%c",&ch1,&ch2,&ch3);
    printf("ch1=%c,ch2=%c,ch3=%c\n",ch1,ch2,ch3);
    假设输入:A□B□C↙,则系统将字母`A`赋值给ch1,空格`□`赋值给ch2,字母`B`赋值给ch3。
    3.3 单个字符输入输出——getchar()和putchar()函数
    3.3.1 单个字符的输出──putchar()函数
    [案例3.8] putchar() 函数的格式和使用方法。
    /*案例代码文件名:AL3_8.C*/
    /*功能:说明putchar()函数的格式和使用方法。*/
    #include "stdio.h" /*编译预处理命令:文件包含*/
    main()
    {char ch1=`N`, ch2=`E`, ch3=`W`;
    putchar(ch1); putchar(ch2); putchar(ch3); /*输出*/
    putchar(`\n`);
    putchar(ch1); putchar(`\n`); /*输出ch1的值,并换行*/
    putchar(`E`); putchar(`\n`); /*输出字符`E`,并换行*/
    putchar(ch3); putchar(`\n`);
    }
    程序运行结果如下:
    NEW
    N
    E
    W
    1.putchar()函数的格式: putchar(ch);
    其中ch可以是一个字符变量或常量,也可以是一个转义字符。
    2.putchar()函数的作用:向终端输出一个字符。
    (1)putchar()函数只能用于单个字符的输出,且一次只能输出一个字符。另外,从功能角度来看,printf()函数可以完全代替putchar()函数。
    (2)在程序中使用putchar()函数,务必牢记:在程序(或文件)的开头加上编译预处理命令(也称包含命令),即:
    #include "stdio.h"
    表示要使用的函数,包含在标准输入输出(stdio)头文件(.h)中。
    3.3.2 单个字符的输入──getchar()函数
    [案例3.9] 说明getchar()函数的格式和作用。
    /*案例代码文件名:AL3_9.C*/
    /*功能:说明getchar()函数的格式和作用。*/
    #include "stdio.h" /*文件包含*/
    main()
    {char ch;
    printf("Please input two character: ");
    ch=getchar(); /*输入1个字符并赋给ch */
    putchar(ch);putchar(`\n`);
    putchar(getchar()); /*输入一个字符并输出*/
    putchar(`\n`);
    }
    程序运行情况如下:
    Please input two characters: ab↙
    a
    b
    1.getchar()函数的格式:getchar();
    2.getchar()函数的作用:从系统隐含的输入设备(如键盘)输入一个字符。另外,从功能角度来看,scanf()函数可以完全代替getchar()函数。
    (1)getchar()函数只能用于单个字符的输入,一次输入一个字符。
    (2)程序中要使用getchar()函数,必须在程序(或文件)的开头加上编译预处理命令:
    #include "stdio.h“
    3.4 顺序结构程序设计
    在顺序结构程序中,各语句(或命令)是按照位置的先后次序,顺序执行的,且每个语句都会被执行到。
    [案例3.10] 输入任意三个整数,求它们的和及平均值。 /*案例代码文件名:AL3_10.C *//*功能:设计一个顺序结构程序,求三个整数的和及平均值。*/ main()
    {int num1,num2,num3,sum; float aver;
    printf("Please input three numbers:");
    scanf("%d,%d,%d",&num1,&num2,&num3);/*输入三个整数*/ sum=num1+num2+num3;/*求累计和*/
    aver=sum/3.0; /*求平均值*/
    printf("num1=%d,num2=%d,num3=%d\n",num1,num2,num3);
    printf("sum=%d,aver=%7.2f\n",sum,aver);
    }
    思考题:能否将“aver=sum/3.0;”中“3.0”改为“3”?
    [案例3.11] 求方程ax2+bx+c=0的实数根。a,b,c由键盘输入,a≠0且b2-4ac>0。
    /*案例代码文件名:AL3_11.C。*/
    /*功能:设计一个顺序结构程序,求方程的根。*/
    #include "math.h" /*为使用求平方根函数sqrt(),包含math.h头文件 */
    main()
    {float a,b,c,disc,x1,x2;
    printf("Input a, b, c: ");
    scanf("%f,%f,%f",&a,&b,&c); /*输入方程的三个系数的值*/
    disc=b*b-4*a*c; /*求判别式的值赋给disc*/
    x1=(-b+sqrt(disc))/(2*a);
    x2=(-b-sqrt(disc))/(2*a);
    printf("\nx1=%6.2f\nx2=%6.2f\n",x1,x2);
    }
    [案例3.12] 从键盘输入一个小写字母,要求用大小写字母形式输出该字母及对应的ASCII码值。
    /*案例代码文件名:AL3_12.C。*/
    #include "stdio.h"
    main()
    {char c1,c2;
    printf("Input a lowercase letter: ");
    c1=getchar();
    putchar(c1);printf(",%d\n",c1);
    c2=c1-32; /*将大写字母转换成对应的小写字母*/
    printf("%c,%d\n",c2,c2);
    }
    程序运行情况如下:
    Input a lowercase letter: a↙
    a,97
    A,65
    在顺序结构程序中,一般包括以下几个部分:
    1.程序开头的编译预处理命令。
    在程序中要使用标准函数(又称库函数),除printf()和scanf()外,其它的都必须使用编译预处理命令,将相应的头文件包含进来。。
    2.顺序结构程序的函数体中,是完成具体功能的各个语句和运算,主要包括:
    (1)变量类型的说明。
    (2)提供数据语句。
    (3)运算部分。
    (4)输出部分。
    良 好 的 源 程 序 书 写 风 格──顺序程序段左对齐
    顺序程序段中的所有语句(包括说明语句),一律与本顺序程序段的首行左对齐。
  • 687

    主题

    2

    好友

    35万

    积分

    发表于 2004-8-21 11:01:41 |显示全部楼层

    [2月23日更新]C语言教学视频(19讲)

    第4章 选择结构程序设计
    要设计选择结构程序,要考虑两个方面的问题:一是在C语言中如何来表示条件,二是在C语言中实现选择结构用什么语句。
    在C语言中表示条件,一般用关系表达式或逻辑表达式,实现选择结构用if语句或switch语句。
    4.1 关系运算及其表达式
    4.2 逻辑运算及其表达式
    4.3 if语句
    4.4 switch语句
    4.5 选择结构程序举例
    良好的源程序书写风格──注释
    4.1 关系运算及其表达式
    所谓“关系运算”实际上就是“比较运算”,即将两个数据进行比较,判定两个数据是否符合给定的关系。
    例如,“a > b”中的“>”表示一个大于关系运算。如果a的值是5,b的值是3,则大于关系运算“>”的结果为“真”,即条件成立;如果a的值是2,b的值是3,则大于关系运算“>”的结果为“假”,即条件不成立。
    4.1.1 关系运算符及其优先次序
    1.关系运算符
    C语言提供6种关系运算符:
    <(小于), <=(小于或等于), >(大于),
    >=(大于或等于), ==(等于), !=(不等于)
    注意:在C语言中,“等于”关系运算符是双等号“= =”,而不是单等号“= ”(赋值运算符)。
    2.优先级
    (1)在关系运算符中,前4个优先级相同,后2个也相同,且前4个高于后2个。
    (2)与其它种类运算符的优先级关系
    关系运算符的优先级,低于算术运算符,但高于赋值运算符。
    4.1.2 关系表达式
    1.关系表达式的概念
    所谓关系表达式是指,用关系运算符将两个表达式连接起来,进行关系运算的式子。
    例如,下面的关系表达式都是合法的:
    a>b,a+b>c-d,(a=3)<=(b=5),`a`>=`b`,(a>b)= =(b>c)
    2.关系表达式的值——逻辑值(非“真”即“假”)。
    由于C语言没有逻辑型数据,所以用整数“1”表示“逻辑真”,用整数“0”表示“逻辑假”
    例如,假设num1=3,num2=4,num3=5,则:
    (1)num1>num2的值=0。
    (2)(num1>num2)!=num3的值=1。
    (3)num1 思考题:任意改变num1或num2的值,会影响整个表达式的值吗?为什么?
    (4)(num1 再次强调:C语言用整数“1”表示“逻辑真”,用整数“0”表示“逻辑假”。所以,关系表达式的值,还可以参与其它种类的运算,例如算术运算、逻辑运算等。
    4.2 逻辑运算及其表达式
    关系表达式只能描述单一条件,例如“x>=0”。如果需要描述“x>=0”、同时“x<10”,就要借助于逻辑表达式了。
    4.2.1 逻辑运算及其优先次序
    1.逻辑运算符及其运算规则
    (1)C语言提供三种逻辑运算符:
    && 逻辑与(相当于“同时”)
    || 逻辑或(相当于“或者”)
    ! 逻辑非(相当于“否定”)
    例如,下面的表达式都是逻辑表达式:
    (x>=0) && (x<10) ,(x<1) || (x>5) ,! (x= =0),
    (year%4==0)&&(year%100!=0)||(year%400==0)
    (2)运算规则
    1)&&:当且仅当两个运算量的值都为“真”时,运算结果为“真”,否则为“假”。
    2) || :当且仅当两个运算量的值都为“假”时,运算结果为“假”,否则为“真”。
    3) ! :当运算量的值为“真”时,运算结果为“假”;当运算量的值为“假”时,运算结果为“真”。
    例如,假定x=5,则(x>=0) && (x<10)的值为“真”,(x<-1) || (x>5)的值为“假”。
    2.逻辑运算符的运算优先级
    (1)逻辑非的优先级最高,逻辑与次之,逻辑或最低,即:
    !(非) → &&(与) → ||(或)
    (2)与其它种类运算符的优先关系
    !→ 算术运算 → 关系运算 → &&→ || → 赋值运算
    4.2.2 逻辑表达式
    1.逻辑表达式的概念
    所谓逻辑表达式是指,用逻辑运算符将1个或多个表达式连接起来,进行逻辑运算的式子。在C语言中,用逻辑表达式表示多个条件的组合。
    例如,(year%4==0)&&(year%100!=0)||(year%400==0)就是一个判断一个年份是否是闰年的逻辑表达式。
    逻辑表达式的值也是一个逻辑值(非“真”即“假”)。
    2.逻辑量的真假判定──0和非0
    C语言用整数“1”表示“逻辑真”、用“0”表示“逻辑假”。但在判断一个数据的“真”或“假”时,却以0和非0为根据:如果为0,则判定为“逻辑假”;如果为非0,则判定为“逻辑真”。
    例如,假设num=12,则: !num的值=0 ,num>=1 && num<=31的值=1 ,num || num>31的值=1。
    3.说明
    (1)逻辑运算符两侧的操作数,除可以是0和非0的整数外,也可以是其它任何类型的数据,如实型、字符型等。
    (2)在计算逻辑表达式时,只有在必须执行下一个表达式才能求解时,才求解该表达式(即并不是所有的表达式都被求解)。换句话说:
    1)对于逻辑与运算,如果第一个操作数被判定为“假”,系统不再判定或求解第二操作数。
    2)对于逻辑或运算,如果第一个操作数被判定为“真”,系统不再判定或求解第二操作数。
    例如,假设n1、n2、n3、n4、x、y的值分别为1、2、3、4、1、1,则求解表达式“(x=n1>n2)&&(y=n3>n4)”后,x的值变为0,而y的值不变,仍等于1!
    4.3 if语句和条件运算符
    4.3.1 if语句
    [案例4.1] 输入任意三个整数num1、num2、num3,求三个数中的最大值。
    /*案例代码文件名:AL4_1.C。*//*功能:说明if 语句的格式。*/
    main()
    {int num1,num2,num3,max;
    printf("Please input three numbers:");
    scanf("%d,%d,%d",&num1,&num2,&num3);
    if (num1>num2)
    max=num1;
    else
    max=num2;
    if (num3>max)
    max=num3;
    printf("The three numbers are:%d,%d,%d\n",num1,num2,num3);
    printf("max=%d\n",max);
    }
    程序运行情况如下: Please input three numbers:11,22,18↙
    The three numbers are:11,22,18
    max=22
    本案例中的第1个if语句,可优化为如下不带else子句的形式:
    max=num1;
    if(num2>max) max=num2;
    这种优化形式的基本思想是:首先取一个数预置为max(最大值),然后再用max依次与其余的数逐个比较,如果发现有比max大的,就用它给max重新赋值,比较完所有的数后,max中的数就是最大值。这种方法,对从3个或3个以上的数中找最大值的处理,非常有效。请读者仔细体会。
    [案例4.2]输入任意三个数num1、num2、num3,按从小到大的顺序排序输出。
    /*案例代码文件名:AL4_2.C。*/
    main()
    {int num1,num2,num3,temp;
    printf("Please input three numbers:");
    scanf("%d,%d,%d",&num1,&num2,&num3);
    if (num1>num2) {temp=num1;num1=num2;num2=temp;}
    if (num2>num3) {temp=num2;num2=num3;num3=temp;}
    if (num1>num2) {temp=num1;num1=num2;num2=temp;}
    printf("Three numbers after sorted: %d,%d,%d\n",num1,num2,num3);
    }
    程序运行情况如下:
    Please input three numbers:11,22,18↙
    Three numbers after sorted: 11,18,22
    1.if语句的一般格式
    if(表达式)
    {语句组1;}
    [else
    {语句组2;} ]
    (1)if语句中的“表达式”必须用“(”和“)”括起来。
    (2)else子句(可选)是if语句的一部分,必须与if配对使用,不能单独使用。
    (3)当if和else下面的语句组,仅由一条语句构成时,也可不使用复合语句形式(即去掉花括号)。
    2.if语句的执行过程
    (1)缺省else子句时
    当“表达式”的值不等于0(即判定为“逻辑真”)时,则执行语句组1,否则直接转向执行下一条。如图4-1(a)所示。
    (2)指定else子句时
    当“表达式”的值不等于0(即判定为“逻辑真”)时,则执行语句组1,然后转向下一条语句;否则,执行语句组2。如图4-1(b)所示。
    3.if语句的嵌套与嵌套匹配原则
    if语句允许嵌套。所谓if语句的嵌套是指,在“语句组1”或(和)“语句组2”中,又包含有if语句的情况。
    if语句嵌套时,else子句与if的匹配原则:与在它上面、距它最近、且尚未匹配的if配对。
    为明确匹配关系,避免匹配错误,强烈建议:将内嵌的if语句,一律用花括号括起来。
    [案例4.3] 写一程序,从键盘上输入1年份year(4位十进制数),判断其是否闰年。闰年的条件是:能被4整除、但不能被100整除,或者能被400整除。
    算法设计要点:
    (1)如果X能被Y整除,则余数为0,即如果X%Y的值等于0,则表示X能被Y整除!
    (2)首先将是否闰年的标志leap预置为0(非闰年),这样仅当year为闰年时将leap置为1即可。这种处理两种状态值的方法,对优化算法和提高程序可读性非常有效,请读者仔细体会。参考程序如下:
    /*案例代码文件名:AL4_3.C。*/
    /*功能:说明if语句的嵌套格式和用法。*/
    main()
    {int year,leap=0; /* leap=0:预置为非闰年*/
    printf("Please input the year:");
    scanf("%d",&year);
    if (year % 4==0)
    {if (year % 100 != 0) leap=1;}
    else
    {if (year%400==0) leap=1; }
    if (leap)
    printf("%d is a leap year.\n",year);
    else
    printf("%d is not a leap year.\n",year);
    }
    利用逻辑运算能描述复杂条件的特点,可将上述程序优化如下:
    main()
    {int year;
    printf("Please input the year:");
    scanf("%d",&year);
    if ((year%4==0 && year%100!=0)||(year%400==0))
    printf("%d is a leap year.\n",year);
    else
    printf("%d is not a leap year.\n",year);
    }
    4.说明
    (1)if后面的“表达式”,除常见的关系表达式或逻辑表达式外,也允许是其它类型的数据,如整型、实型、字符型等。
    (2)if语句允许嵌套,但嵌套的层数不宜太多。在实际编程时,应适当控制嵌套层数(2~3层)。
    (3)“语句组1”和“语句组2”,可以只包含一个简单语句,也可以是复合语句。
    务必牢记:不管是简单语句,还是复合语句中的各个语句,每个语句后面的分号必不可少!
    例如,[案例4.1]中的:
    if (num1>num2) max=num1;
    else max=num2;语句:
    if行后面的赋值语句“max=num1;”分号不能省略。但不要误认为if和else是2个独立的语句,它们都属于if语句中的一部分,else是if语句的子句。
    4.3.2 条件运算符
    1.一般格式: 表达式1?表达式2:表达式3
    条件表达式中的“表达式1”、“表达式2”、“表达式3”的类型,可以各不相同。
    2.运算规则
    如果“表达式1”的值为非0(即逻辑真), 则运算结果等于“表达式2”的值;否则,运算结果等于“表达式3”的值。如图4-2所示。
    3.运算符的优先级与结合性
    条件运算符的优先级,高于赋值运算符,但低于关系运算符和算术运算符。其结合性为“从右到左”(即右结合性)。
    [例4.4] 从键盘上输入一个字符,如果它是大写字母,则把它转换成小写字母输出;否则,直接输出。
    /*案例文件名:AL4_4.C*/
    main()
    { char ch;
    printf("Input a character: ");
    scanf("%c",&ch);
    ch=(ch>=`A` && ch<=`Z`) ? (ch+32) : ch;
    printf("ch=%c\n",ch);
    }
    4.4 switch语句
    C语言提供了switch语句直接处理多分支选择。
    [案例4.5] 从键盘上输入一个百分制成绩score,按下列原则输出其等级:score≥90,等级为A;80≤score<90,等级为B;70≤score<80,等级为C;60≤score<70,等级为D;score<60,等级为E。/*案例代码文件名:AL4_5.C。*/
    main()
    {int score, grade;
    printf(“Input a score(0~100): ”);
    scanf(“%d”, &score);
    grade = score/10; /*将成绩整除10,转化成switch语句中的case标号*/ switch (grade)
    {case 10:
    case 9: printf(“grade=A\n”); break;
    case 8: printf("grade=B\n"); break;
    case 7: printf("grade=C\n"); break;
    case 6: printf("grade=D\n"); break;
    case 5:
    case 4:
    case 3:
    case 2:
    case 1:
    case 0: printf(“grade=E\n”); break;
    default: printf(“The score is out of range!\n”);
    }
    }
    程序运行情况如下:
    Input a score(0~100): 85↙
    grade=B
    1.switch语句的一般形式
    switch(表达式)
    { case 常量表达式1:语句组;break;
    case 常量表达式2:语句组;break;
    ......
    case 常量表达式n:语句组;break;
    [default:语句组;[break; ]]
    }
    2.执行过程
    (1)当switch后面“表达式”的值,与某个case后面的“常量表达式”的值相同时,就执行该case后面的语句(组);当执行到break语句时,跳出switch语句,转向执行switch语句的下一条。
    (2)如果没有任何一个case后面的“常量表达式”的值,与“表达式”的值匹配,则执行default 后面的语句(组)。然后,再执行switch语句的下一条。
    3.说明
    (1)switch后面的“表达式”,可以是int、char和枚举型中的一种。
    (2)每个case后面“常量表达式”的值,必须各不相同,否则会出现相互矛盾的现象(即对表达式的同一值,有两种或两种以上的执行方案)。
    (3)case后面的常量表达式仅起语句标号作用,并不进行条件判断。系统一旦找到入口标号,就从此标号开始执行,不再进行标号判断,所以必须加上break语句,以便结束switch语句。
    思考题:如果去掉[案例4.5]程序中的所有break语句,且输入的成绩为75,输出会如何?
    (4)各case及default子句的先后次序,不影响程序执行结果。
    (5)多个case子句,可共用同一语句(组)。
    例如,在[案例4.5]中的“case 10: ”和“case 9: ”共用语句“printf("grade=A\n"); break;”,“case 5: ”~“case 0: ”共用语句“printf("grade=E\n"); break;”。
    (6)用switch语句实现的多分支结构程序,完全可以用if语句或if语句的嵌套来实现。
    4.5 选择结构程序设计举例
    [案例4.6] 求一元二次方程ax2+bx+c=0的解(a≠0)。/*案例代码文件名:AL4_6.C。*//*功能:求一元二次方程的解。*/
    &#35;include "math.h"
    main()
    {float a,b,c,disc,x1,x2,p,q;
    scanf(“%f,%f,%f”, &a, &b, &c); disc=b*b-4*a*c;
    if (fabs(disc)<=1e-6) /*fabs():求绝对值库函数*/ printf(“x1=x2=%7.2f\n”, -b/(2*a)); /*输出两个相等的实根*/ else
    { if (disc>1e-6)
    {x1=(-b+sqrt(disc))/(2*a); /*求出两个不相等的实根*/
    x2=(-b-sqrt(disc))/(2*a);
    printf("x1=%7.2f,x2=%7.2f\n", x1, x2);
    }
    else
    {p=-b/(2*a); /*求出两个共轭复根*/
    q=sqrt(fabs(disc))/(2*a);
    printf(“x1=%7.2f + %7.2f i\n“, p, q);/*输出两个共轭复根*/ printf(”x2=%7.2f - %7.2f i\n“, p, q);
    }
    }
    }
    说明:由于实数在计算机中存储时,经常会有一些微小误差,所以本案例判断disc是否为0的方法是:判断disc的绝对值是否小于一个很小的数(例如10-6)。
    思考题:如果将系数a、b、c定义成整数,能否直接判断disc是否等于0?
    [案例4.7] 已知某公司员工的保底薪水为500,某月所接工程的利润profit(整数)与利润提成的关系如下(计量单位:元):
    profit≤1000 没有提成;
    1000<profit≤2000 提成10%;
    2000<profit≤5000 提成15%;
    5000<profit≤10000 提成20%;
    10000<profit 提成25%。
    算法设计要点:
    为使用switch语句,必须将利润profit与提成的关系,转换成某些整数与提成的关系。分析本题可知,提成的变化点都是1000的整数倍(1000、2000、5000、……),如果将利润profit整除1000,则当:
    profit≤1000 对应0、1
    1000<profit≤2000 对应1、2
    2000<profit≤5000 对应2、3、4、5
    5000<profit≤10000 对应5、6、7、8、9、10
    10000<profit 对应10、11、12、……
    为解决相邻两个区间的重叠问题,最简单的方法就是:利润profit先减1(最小增量),然后再整除1000即可:
    profit≤1000 对应0
    1000<profit≤2000 对应1
    2000<profit≤5000 对应2、3、4
    5000<profit≤10000 对应5、6、7、8、9
    10000<profit 对应10、11、12、……
    /*案例代码文件名:AL4_7.C。*/
    main()
    {long profit;
    int grade;
    float salary=500;
    printf("Input profit: ");
    scanf("%ld", &profit);
    grade= (profit – 1) / 1000; /*将利润-1、再整除1000,转化成 switch语句中的case标号*/
    switch(grade)
    { case 0: break; /*profit≤1000 */
    case 1: salary += profit*0.1; break; /*1000<profit≤2000 */
    case 2:
    case 3:
    case 4: salary += profit*0.15; break; /*2000<profit≤5000 */
    case 5:
    case 6:
    case 7:
    case 8:
    case 9: salary += profit*0.2; break; /*5000<profit≤10000 */
    default: salary += profit*0.25; /*10000<profit */
    }
    printf("salary=%.2f\n", salary);
    }
    良好的源程序书写风格──注释
    必要的注释,可有效地提高程序的可读性,从而提高程序的可维护性。
    在C语言源程序中,注释可分为三种情况:(1)在函数体内对语句的注释;(2)在函数之前对函数的注释;(3)在源程序文件开始处,对整个程序的总体说明。
    函数体内的语句,是由顺序结构、选择结构和循环结构等三种基本结构构成的。在什么地方加以注释的原则是:如果不加注释,理解起来就会有困难,或者虽无困难、但浪费时间。 (1)顺序结构
    在每个顺序程序段(由若干条语句构成)之前,用注释说明其功能。除很复杂的处理外,一般没有必要每条语句都加以注释。
    (2)选择结构
    在C语言中,选择结构是由if语句和switch语句来实现的。一般地说,要在前面说明其作用,在每个分支条件语句行的后面,说明该分支的含义,如下所示:
    1)
    if语句 /*……(说明功能)*/
    if(条件表达式) /*条件成立时的含义*/
    {……}
    else /*入口条件含义*/
    {……}
    2)switch语句 /*……(说明功能) */
    switch(表达式)
    { case 常量表达式1: /*该入口值的含义*/
    语句组; ……
    case 常量表达式n: /*该入口值的含义*/
    语句组; default: /*该入口值的含义*/
    语句组;
    }
    如果条件成立时(或入口值)的含义,已经很明确了,也可不再加以注释。

    687

    主题

    2

    好友

    35万

    积分

    发表于 2004-8-21 11:03:52 |显示全部楼层

    [2月23日更新]C语言教学视频(19讲)

    第5章 循环结构程序设计
    5.1 循环语句概述
    5.2 for语句和while语句
    5.3 直到型循环do-while语句
    5.4 break语句与continue语句
    5.5 应用举例
    良好的源程序书写习惯──注释(续)
    5.1 循环语句概述
    求1~100的累计和。
    根据已有的知识,可以用“1+2+……+100”来求解,但显然很繁琐。现在换个思路来考虑:
    首先设置一个累计器sum,其初值为0,利用sum += n来计算(n依次取1、2、……、100),只要解决以下3个问题即可:
    (1)将n的初值置为1;
    (2)每执行1次“sum += n”后,n增1;
    (3)当n增到101时,停止计算。此时,sum的值就是1~100的累计和。
    根据已有的知识,单独实现每一步都不难。但是,由于需要经常使用这种重复计算结构(称为循环结构),C语言提供了3条循环语句来实现,以简化、并规范循环结构程序设计。
    在C语言中,可用以下语句实现循环:
    (1)用for语句。
    (2)用do-while语句。
    (3)用while语句。
    (4)用goto语句和if语句构成循环。使用goto语句实现求解1~100累计和的程序可以如下:
    main()
    { int n=1, sum=0;
    loop: sum += n; n++;
    if (n<=100) goto loop;
    printf(“sum=%d\n”, sum);
    }
    其中“loop:”为语句标号(格式:标号: 语句行),其命名遵循标识符命名规则。goto语句格式:goto 标号,功能为:使系统转向标号所在的语句行执行。
    注意:结构化程序设计方法,主张限制使用goto语句。因为滥用goto语句,将会导致程序结构无规律、可读性差。另外,从功能上说,for语句可完全代替当型循环语句while,所以该语句也不是必需的.
    5.2 for语句和while语句
    在3条循环语句中,for语句最为灵活,不仅可用于循环次数已经确定的情况,也可用于循环次数虽不确定、但给出了循环继续条件的情况。 [案例5.1] 求1~100的累计和。/*案例代码文件名:AL5_1.C*/
    /*程序功能:求1~100的累计和*/
    main()
    { int i,sum=0; /*将累加器sum初始化为0*/
    for(i=1; i<=100; i++) sum += i; /*实现累加*/
    printf("sum=%d\n",sum); }
    程序运行情况如下:
    sum=5050
    [案例5.2] 求n的阶乘n!(n!=1*2*……*n)。/*案例代码文件名:AL5_2.C*/
    /*程序功能:求n!*/
    main()
    { int i, n; long fact=1; /*将累乘器fact初始化为1*/
    printf(“Input n: ”); scanf(“%d”, &n);
    for(i=1; i<=n; i++) fact *= i;/*实现累乘*/
    printf("%d ! = %ld\n", n, fact);
    }
    程序运行情况如下:
    Input n: 5↙
    5 ! = 120
    1.for语句的一般格式
    for([变量赋初值];[循环继续条件];[循环变量增值])
    { 循环体语句组;}
    2.for语句的执行过程
    执行过程如图5-1所示。
    (1)求解“变量赋初值”表达式。
    (2)求解“循环继续条件”表达式。如果其值非0,执行
    (3);否则,转至(4)。
    (3)执行循环体语句组,并求解“循环变量增值”表达式,然后转向(2)。
    (4)执行for语句的下一条语句。
    3.说明
    (1)“变量赋初值”、“循环继续条件”和“循环变量增值”部分均可缺省,甚至全部缺省,但其间的分号不能省略。
    (2)当循环体语句组仅由一条语句构成时,可以不使用复合语句形式,如上例所示。
    (3)“循环变量赋初值”表达式,既可以是给循环变量赋初值的赋值表达式,也可以是与此无关的其它表达式(如逗号表达式)。
    例如,for(sum=0;i<=100;i++) sum += i;
    for(sum=0,i=1;i<=100;i++) sum += i;
    (4)“循环继续条件”部分是一个逻辑量,除一般的关系(或逻辑)表达式外,也允许是数值(或字符)表达式。
    4.while语句
    (1)一般格式
    while(循环继续条件)
    {循环体语句组;}
    (2)执行过程
    执行过程如图5-2所示。
    1)求解“循环继续条件”表达式。如果其值为非0,转2);否则转3)。
    2)执行循环体语句组,然后转1)。
    3)执行while语句的下一条。
    显然,while循环是for循环的一种简化形式(缺省“变量赋初值”和“循环变量增值”表达式)。
    [案例5.3] 用while语句求1~100的累计和。/*案例代码文件名:AL5_3.C*/
    /*程序功能:求1~100的累计和*/
    main()
    { int i=1,sum=0; /*初始化循环控制变量i和累计器sum*/
    while( i<=100 )
    { sum += i; /*实现累加*/
    i++; /*循环控制变量i增1*/
    }
    printf(“sum=%d\n”,sum);
    }
    程序运行情况如下:
    sum=5050
    5.循环嵌套
    (1)循环语句的循环体内,又包含另一个完整的循环结构,称为循环的嵌套。循环嵌套的概念,对所有高级语言都是一样的。
    (2)for语句和while语句允许嵌套,do-while语句也不例外。
    5.3 直到型循环do-while语句
    1.一般格式
    do
    { 循环体语句组; }
    while(循环继续条件); /*本行的分号不能缺省*/
    当循环体语句组仅由一条语句构成时,可以不使用复合语句形式。
    2.执行过程
    执行过程如图5-3所示。
    (1)执行循环体语句组。
    (2)计算“循环继续条件”表达式。如果“循环继续条件”表达式的值为非 0(真),则转向(1)继续执行;否则,转向(3)。
    (3)执行do-while的下一条语句。
    do-while循环语句的特点是:先执行循环体语句组,然后再判断循环条件。
    [案例5.4] 用do-while语句求解1~100的累计和。
    /*案例代码文件名:AL5_4.C*/
    /*程序功能:求1~100的累计和*/
    main()
    { int i=1, sum=0; /*定义并初始化循环控制变量,以及累计器*/
    do
    { sum += i; /*累加*/
    i++;
    } while(i<=100); /*循环继续条件:i<=100*/
    printf(“sum=%d\n”,sum);
    }
    do-while语句比较适用于处理:不论条件是否成立,先执行1次循环体语句组的情况。除此之外,do-while语句能实现的,for语句也能实现,而且更简洁。
    5.4 break语句与continue语句
    为了使循环控制更加灵活,C语言提供了break语句和continue语句。
    1.一般格式: break;
    continue;
    2.功能
    (1)break:强行结束循环,转向执行循环语句的下一条语句。
    (2)continue:对于for循环,跳过循环体其余语句,转向循环变量增量表达式的计算;对于while和do-while循环,跳过循环体其余语句,但转向循环继续条件的判定。
    3.break和continue语句对循环控制的影响如图5-4所示。
    4.说明
    (1)break能用于循环语句和switch语句中,continue只能用于循环语句中。
    (2)循环嵌套时,break和continue只影响包含它们的最内层循环,与外层循环无关。
    5.5 应用举例
    [例5.5] 求Fibonacci数列的前40个数。该数列的生成方法为:F1=1,F2=1,Fn=Fn-1+Fn-2(n>=3),即从第3个数开始,每个数等于前2个数之和。
    算法设计,请参见第2章第1节(2.1)。
    参考源程序如下:
    /*案例代码文件名:AL5_5.C*/
    main()
    {
    long int f1=1,f2=1; /*定义并初始化数列的头2个数*/
    int i=1; /*定义并初始化循环控制变量i*/
    for( ; i<=20; i++ ) /*1组2个,20组40个数*/
    {
    printf(“%15ld%15ld”, f1, f2); /*输出当前的2个数*/
    if(i%2==0) printf(“\n”); /*输出2次(4个数),换行*/
    f1 += f2; f2 += f1; /*计算下2个数*/
    }
    }
    [例5.6] 输出10~100之间的全部素数。所谓素数n是指,除1和n之外,不能被2~(n-1)之间的任何整数整除。
    >算法设计要点:
    (1)显然,只要设计出判断某数n是否是素数的算法,外面再套一个for循环即可。
    (2)判断某数n是否是素数的算法:根据素数的定义,用2~(n-1)之间的每一个数去整除n,如果都不能被整除,则表示该数是一个素数。
    判断一个数是否能被另一个数整除,可通过判断它们整除的余数是否为0来实现。
    参考源程序如下:
    main()
    { int i=11, j, counter=0;
    for( ; i<=100; i+=2) /*外循环:为内循环提供一个整数i*/
    { for(j=2; j<=i-1; j++) /*内循环:判断整数i是否是素数*/
    if(i%j= =0) /*i不是素数*/
    break; /*强行结束内循环,执行下面的if语句*/
    if(counter%10= =0) /*每输出10个数换一行*/
    printf(“\n”);
    if( j >= i ) /*整数i是素数:输出,计数器加1*/
    {
    printf(“%6d”,i);
    counter++;}
    }
    }
    思考题:外循环控制变量i的初值从11开始、增量为2的作法有什么好处?为提高运行速度,如何优化内循环?(提示:从减少计算次数角度来考虑)
    良好的源程序书写习惯──注释(续)
    (3)循环结构
    在C语言中,循环结构由循环语句for、while和do...while来实现。
    作为注释,应在它们的前面说明其功能,在循环条件判断语句行的后面,说明循环继续条件的含义,如下所示。
    1)for语句
    /*功能*/
    for(变量初始化;循环条件;变量增值) /*循环继续条件的含义*/
    { …… }
    2)while语句
    /*功能说明*/
    while(循环条件) /*循环继续条件的含义*/
    { …… }
    3)do...while语句
    /*功能说明*/
    do { …… }
    while(循环条件); /*循环继续条件的含义*/
    如果循环嵌套,还应说明每层循环各控制什么。

    687

    主题

    2

    好友

    35万

    积分

    发表于 2004-8-21 11:05:16 |显示全部楼层

    [2月23日更新]C语言教学视频(19讲)

    [这个贴子最后由没完没了在 2004/08/21 05:06pm 第 1 次编辑]

    第6章 数 组
    为了解决比较复杂的问题,本章介绍C语言提供的一种最简单的构造类型──数组。
    6.1 1维数组的定义和引用
    6.2 2维数组的定义和引用
    6.3 字符数组与字符串
    6.1 1维数组的定义和引用
    6.1.1 1维数组的定义
    6.1.2 1维数组元素的引用
    6.1.3 1维数组元素的初始化
    6.1.4 1维数组应用举例
    6.1.1 1维数组的定义
    [案例6.1] 从键盘上任意输入10个整数,要求按从小到大的顺序在屏幕上显示出来。
    排序的方法有很多,本题采用冒泡法。
    冒泡法的基本思想:通过相邻两个数之间的比较和交换,使排序码(数值)较小的数逐渐从底部移向顶部,排序码较大的数逐渐从顶部移向底部。就像水底的气泡一样逐渐向上冒,故而得名。
    由A[n]~A[1]组成的n个数据,进行冒泡排序的过程可以描述为:
    (1)首先将相邻的A[n]与A[n-1]进行比较,如果A[n]的值小于A[n-1]的值,则交换两者的位置,使较小的上浮,较大的下沉;接着比较A[n-1]与A[n-2],同样使小的上浮,大的下沉。依此类推,直到比较完A[2]和A[1]后,A[1]为具有最小排序码(数值)的元素,称第一趟排序结束。
    (2)然后在A[n]~A[2]区间内,进行第二趟排序,使剩余元素中排序码最小的元素上浮到A[2];重复进行n-1趟后,整个排序过程结束。
    /*案例代码文件名:AL6_1.C*/
    /*功能:从键盘上任意输入n个整数,用冒泡法按从小到大地排序,并在屏幕上显示出来。*/
    &#35;include "stdio.h"
    &#35;define NUM 10 /*定义符号常量(数据个数N)*/
    main()
    { int data[NUM]; /*定义1个1维整型数组data*/
    int i,j,temp; /*定义循环变量和临时变量*/
    clrscr(); /*库函数clrscr():清屏*/
    printf("Please input 10 numbers:\n");
    for(i=0; i scanf("%d", &data);
    /*冒泡法排序*/
    for(i=0; i for(j=NUM-1; j>i; j--) /*内循环:进行每趟比较*/
    if(data[j] {temp=data[j];
    data[j]=data[j-1];
    data[j-1]=temp;
    };
    /*输出排序后的数据*/
    printf("\nthe result of sort:\n");
    for(i=0; i printf("%d ",data);
    getch(); /*等待键盘输入任一字符,目的使程序暂停*/
    }
    数组同变量一样,也必须先定义、后使用。
    1维数组是只有1个下标的数组,定义形式如下:
    数据类型 数组名[常量表达式][, 数组名2[常量表达式2]……];
    (1)“数据类型”是指数组元素的数据类型。
    (2)数组名,与变量名一样,必须遵循标识符命名规则。
    (3)“常量表达式”必须用方括号括起来,指的是数组的元素个数(又称数组长度),它是一个整型值,其中可以包含常数和符号常量,但不能包含变量。
    注意:C语言中不允许动态定义数组。
    特别说明:在数组定义时,“常量表达式”外的方括号;以及元素引用时,“下标表达式”外的方括号,都是C语言语法规则所要求的,不是本书所约定的可选项的描述符号!
    (4)数组元素的下标,是元素相对于数组起始地址的偏移量,所以从0开始顺序编号。
    (5)数组名中存放的是一个地址常量,它代表整个数组的首地址。同一数组中的所有元素,按其下标的顺序占用一段连续的存储单元。
    6.1.2 数组元素的引用
    引用数组中的任意一个元素的形式:
    数组名[下标表达式]
    1.“下标表达式”可以是任何非负整型数据,取值范围是0~(元素个数-1)。
    特别强调:在运行C语言程序过程中,系统并不自动检验数组元素的下标是否越界。因此在编写程序时,保证数组下标不越界是十分重要的。
    2.1个数组元素,实质上就是1个变量,它具有和相同类型单个变量一样的属性,可以对它进行赋值和参与各种运算。
    3.在C语言中,数组作为1个整体,不能参加数据运算,只能对单个的元素进行处理。
    6.1.3 1维数组元素的初始化
    初始化格式:
    数据类型 数组名[常量表达式]={初值表}
    (1)如果对数组的全部元素赋以初值,定义时可以不指定数组长度(系统根据初值个数自动确定)。如果被定义数组的长度,与初值个数不同,则数组长度不能省略。
    (2)“初值表”中的初值个数,可以少于元素个数,即允许只给部分元素赋初值。
    (3)根据存储类型的不同,数组有静态数组(static)和动态数组(auto)之分;根据定义的位置不同,数组有内部数组(在函数内部定义的数组)和外部数组(在函数外部定义的数组)之分。
    6.1.4 1维数组应用举例
    [案例6.2] 已知某课程的平时、实习、测验和期末成绩,求该课程的总评成绩。其中平时、实习、测验和期末分别占10%、20%、20%、50%。
    /*案例代码文件名:AL6_2.C*/
    /*功能:从键盘上循环输入某课程的平时、实习、测验和期末成绩,按10%,20%,20%,50%的比例计算总评成绩,并在屏幕上显示出来。按空格键继续循环,其他键终止循环。*/
    &#35;include “stdio.h”
    main()
    { int i=1,j; char con_key=‘\x20’;/* ‘\x20’ 空格键的ASCII码*/ float score[5],ratio[4]={0.1,0.2,0.2,0.5}; /*定义成绩、比例系数数组*/
    while(con_key==`\x20`)
    {clrscr();
    printf("输入第%2d个学生的成绩\n", i++);
    printf("平时 实习 测验 期末成绩\n");
    score[4]=0; /* score[4]:存储总评成绩*/
    for(j=0; j<4; j++)
    {scanf("%f",&score[j]);
    score[4] += score[j] * ratio[j];
    }
    printf("总评成绩为:%6.1f\n", score[4]);
    printf("\n按空格键继续,其它键退出");
    con_key=getch(); /*getch()函数等待从键盘上输入一个字符*/
    }
    }
    6.2 2维数组的定义和引用
    6.2.1 2维数组的定义
    6.2.2 2维数组元素的引用
    6.2.3 2维数组元素的初始化
    6.2.4 2维数组应用举例
    6.2.1 2维数组的定义
    [案例6.3] 给一个2*3的2维数组各元素赋值,并输出全部元素的值。
    /*案例代码文件名:AL6_3.C*/
    /*功能:从键盘上给2*3数组赋值,并在屏幕上显示出来。*/
    &#35;define Row 2
    &#35;define Col 3
    &#35;include "stdio.h"
    main()
    { int i, j, array[Row][Col]; /*定义1个2行3列的2维数组array*/
    for(i=0; i for(j=0; j {printf("please input array[%2d][%2d]:",i,j);
    scanf("%d",&array[j]); /*从键盘输入a[j]的值*/
    }
    printf("\n");
    /*输出2维数组array*/
    for(i=0;i{ for(j=0;j printf("%d\t",array[j]); /*将a[j]的值显示在屏幕上*/
    printf("\n");
    }
    getch();
    }
    2维数组的定义方式如下:
    数据类型 数组名[行常量表达式][列常量表达式][, 数组名2[行常量表达式2][列常量表达式2]……];
    1.数组元素在内存中的排列顺序为“按行存放”,即先顺序存放第一行的元素,再存放第二行,以此类推。
    2. 设有一个m*n的数组x,则第i行第j列的元素x[j]在数组中的位置为:i*n+j(注意:行号、列号均从0开始计数)。
    3.可以把2维数组看作是一种特殊的1维数组:它的元素又是一个1维数组。
    例如,对x[3][2],可以把x看作是一个1维数组,它有3个元素:x[0]、x[1]、x[2],每个元素又是一个包含2个元素的1维数组,如图6-4所示。即把x[0]、x[1]、x[2]看作是3个1维数组的名字。
    6.2.2 2维数组元素的引用
    引用2维数组元素的形式为:
    数组名[行下标表达式][列下标表达式]
    1.“行下标表达式”和“列下标表达式”,都应是整型表达式或符号常量。
    2.“行下标表达式”和“列下标表达式”的值,都应在已定义数组大小的范围内。假设有数组x[3][4],则可用的行下标范围为0~2,列下标范围为0~3。
    3.对基本数据类型的变量所能进行的操作,也都适合于相同数据类型的2维数组元素。
    6.2.3 2维数组元素的初始化
    1.按行赋初值
    数据类型 数组名[行常量表达式][列常量表达式]={{第0行初值表},{第1行初值表},……,{最后1行初值表}};
    赋值规则:将“第0行初值表”中的数据,依次赋给第0行中各元素;将“第1行初值表”中的数据,依次赋给第1行各元素;以此类推。
    2.按2维数组在内存中的排列顺序给各元素赋初值
    数据类型 数组名[行常量表达式][列常量表达式]={初值表};
    赋值规则:按2维数组在内存中的排列顺序,将初值表中的数据,依次赋给各元素。
    如果对全部元素都赋初值,则“行数”可以省略。注意:只能省略“行数”。
    6.2.4 2维数组应用举例
    [案例6.4] 有M个学生,学习N门课程,已知所有学生的各科成绩,编程:分别求每个学生的平均成绩和每门课程的平均成绩。
    /*案例代码文件名:AL6_4.C*/
    /*功能:计算个人平均成绩与各科平均成绩,并在屏幕上显示出来。*/
    &#35;define NUM_std 5 /*定义符号常量人数为5*/
    &#35;define NUM_course 4 /*定义符号常量课程为4*/
    &#35;include "stdio.h"
    main()
    { int i,j;
    static float score[NUM_std+1][NUM_course+1]={{78,85,83,65},
    {88,91,89,93}, {72,65,54,75},
    {86,88,75,60}, {69,60,50,72}};
    for(i=0;i {for(j=0;j { score[NUM_course] += score[j];/*求第i个人的总成绩*/
    score[NUM_std][j] += score[j]; /*求第j门课的总成绩*/
    }
    score[NUM_course] /= NUM_course;/*求第i个人的平均成绩*/
    }
    for(j=0;j score[NUM_std][j] /= NUM_std; /*求第j门课的平均成绩*/
    clrscr();
    /*输出表头*/
    printf("学生编号 课程1 课程2 课程3 课程4 个人平均\n");
    /*输出每个学生的各科成绩和平均成绩*/
    for(i=0;i { printf("学生%d\t",i+1);
    for(j=0;j printf("%6.1f\t",score[j]);
    printf("\n");
    }
    /*输出1条短划线*/
    for(j=0;j<8*(NUM_course+2);j++)
    printf("-");
    printf("\n课程平均");
    /*输出每门课程的平均成绩*/
    for(j=0;j printf("%6.1f\t",score[NUM_std][j]);
    printf("\n");
    getch();
    }
    6.3 字符数组与字符串
    6.3.1 字符数组的逐个字符操作
    6.3.2 字符数组的整体操作
    6.3.3 常用的字符串处理函数
    6.3.1 字符数组的逐个字符操作
    [案例6.5]从键盘输入一个字符串,回车键结束,并将字符串在屏幕上输出。
    /*案例代码文件名:AL6_5.C*/
    main()
    {int i;
    static char str[80];
    clrscr();
    for(i=0;i<80;i++)
    { str=getch(); /*逐次给数组元素str赋值,但不回显在屏幕上*/
    printf("*"); /*以星号代替输入字符的个数*/
    if(str==`\x0d`) break;/*若输入回车则终止循环*/
    }
    i=0;
    while(str!=`\x0d`)
    printf("%c",str[i++]); /*逐次输出字符数组的各个元素*/
    printf("\n");
    getch(); /*程序暂停*/
    }
    1.字符数组的定义
    1维字符数组,用于存储和处理1个字符串,其定义格式与1维数值数组一样。
    2维字符数组,用于同时存储和处理多个字符串,其定义格式与2维数值数组一样。
    2.字符数组的初始化
    字符数组的初始化,可以通过为每个数组元素指定初值字符来实现。
    3.字符数组的引用
    字符数组的逐个字符引用,与引用数值数组元素类似。
    (1)字符数组的输入
    除了可以通过初始化使字符数组各元素得到初值外,也可以使用getchar()或scanf()函数输入字符。
    例如:
    char str[10];
    ……
    for(i=0; i<10; i++)
    { scanf("%c", &str);
    fflush(stdin); /*清除键盘输入缓冲区*/
    }
    ……
    (2)字符数组的输出
    字符数组的输出,可以用putchar()或printf()函数。
    例如:
    char str[10]="c language";
    ……
    for(i=0; i<10; i++) printf("%c", str);
    printf("\n");
    ……
    注意:逐个字符输入、输出时,要指出元素的下标,而且使用“%c”格式符。另外,从键盘上输入字符时,无需输入字符的定界符──单引号;输出时,系统也不输出字符的定界符。
    6.3.2 字符数组的整体操作
    [案例6.6] 字符数组的整体输入与输出。
    /*案例代码文件名:AL6_6.C*/
    /*功能:将2维字符数组进行初始化,并在屏幕上输出*/
    main()
    { int i;
    char name[5][9]={"张三山", "李四季", "王五魁", "刘六顺", "赵七巧"};
    for(i=0;i<5;i++)
    printf("\n%s\t",name); /*name代表该行数组元素的首地址*/
    getch();
    }
    1.字符串及其结束标志
    所谓字符串,是指若干有效字符的序列。C语言中的字符串,可以包括字母、数字、专用字符、转义字符等。
    C语言规定:以‘\0’作为字符串结束标志(‘\0’代表ASCII码为0的字符,表示一个“空操作”,只起一个标志作用)。因此可以对字符数组采用另一种方式进行操作了──字符数组的整体操作。
    注意:由于系统在存储字符串常量时,会在串尾自动加上1个结束标志,所以无需人为地再加1个。
    另外,由于结束标志也要在字符数组中占用一个元素的存储空间,因此在说明字符数组长度时,至少为字符串所需长度加1。
    2.字符数组的整体初始化
    字符串设置了结束标志以后,对字符数组的初始化,就可以用字符串常量来初始化字符数组。
    3.字符数组的整体引用
    (1)字符串的输入
    除了可以通过初始化使字符数组各元素得到初值外,也可以使用scanf()函数输入字符串。
    (2)字符串的输出
    printf()函数,不仅可以逐个输出字符数组元素,还可以整体输出存放在字符数组中的字符串。
    6.3.3 常用的字符串处理函数
    字符串标准函数的原型在头文件string.h中。
    1.输入字符串──gets()函数
    (1)调用方式:gets(字符数组)
    (2)函数功能:从标准输入设备(stdin)──键盘上,读取1个字符串(可以包含空格),并将其存储到字符数组中去。
    (3)使用说明
    1)gets()读取的字符串,其长度没有限制,编程者要保证字符数组有足够大的空间,存放输入的字符串。
    2)该函数输入的字符串中允许包含空格,而scanf()函数不允许。
    2.输出字符串──puts()函数
    (1)调用方式:puts(字符数组)
    (2)函数功能:把字符数组中所存放的字符串,输出到标准输出设备中去,并用‘\n’取代字符串的结束标志‘\0’。所以用puts()函数输出字符串时,不要求另加换行符。
    ( 3)使用说明
    1)字符串中允许包含转义字符,输出时产生一个控制操作。
    2)该函数一次只能输出一个字符串,而printf()函数也能用来输出字符串,且一次能输出多个。
    3.字符串比较──strcmp()函数
    (1)调用方式:strcmp(字符串1 ,字符串2)
    其中“字符串”可以是串常量,也可以是1维字符数组。
    (2)函数功能:比较两个字符串的大小。
    如果:字符串1=字符串2,函数返回值等于0;
    字符串1<字符串2,函数返回值负整数;
    字符串1>字符串2,函数返回值正整数。
    (3)使用说明
    1)如果一个字符串是另一个字符串从头开始的子串,则母串为大。
    2)不能使用关系运算符“==”来比较两个字符串,只能用strcmp() 函数来处理。
    [案例6.7] gets函数和strcmp函数的应用。
    /*案例代码文件名:AL6_7.C*/
    /*功能:简单密码检测程序*/
    &#35;include "stdio.h"
    main()
    {char pass_str[80]; /*定义字符数组passstr*/
    int i=0;
    /*检验密码*/
    while(1)
    {clrscr();
    printf("请输入密码\n");
    gets(pass_str); /*输入密码*/
    if(strcmp(pass_str,“password”)!=0) /*口令错*/
    printf("口令错误,按任意键继续");
    else
    break; /*输入正确的密码,中止循环*/
    getch();
    i++;
    if(i==3) exit(0); /*输入三次错误的密码,退出程序*/
    }
    /*输入正确密码所进入的程序段*/
    }
    4.拷贝字符串──strcpy()函数
    (1)调用方式:strcpy(字符数组, 字符串)
    其中“字符串”可以是串常量,也可以是字符数组。
    (2)函数功能:将“字符串”完整地复制到“字符数组”中,字符数组中原有内容被覆盖。
    (3)使用说明
    1)字符数组必须定义得足够大,以便容纳复制过来的字符串。复制时,连同结束标志`\0`一起复制。
    2)不能用赋值运算符“=”将一个字符串直接赋值给一个字符数组,只能用strcpy()函数来处理。
    5.连接字符串──strcat()函数
    (1)调用方式:strcat(字符数组, 字符串)
    (2)函数功能:把“字符串”连接到“字符数组”中的字符串尾端,并存储于“字符数组”中。“字符数组”中原来的结束标志,被“字符串”的第一个字符覆盖,而“字符串”在操作中未被修改。
    (3)使用说明
    1)由于没有边界检查,编程者要注意保证“字符数组”定义得足够大,以便容纳连接后的目标字符串;否则,会因长度不够而产生问题。
    2)连接前两个字符串都有结束标志`\0`,连接后“字符数组”中存储的字符串的结束标志`\0`被舍弃,只在目标串的最后保留一个`\0`。
    6.求字符串长度──strlen()函数(len是length的缩写)
    (1)调用方式:strlen(字符串)
    (2)函数功能:求字符串(常量或字符数组)的实际长度(不包含结束标志)。
    7.将字符串中大写字母转换成小写──strlwr()函数
    (1)调用方式:strlwr(字符串)
    (2)函数功能:将字符串中的大写字母转换成小写,其它字符(包括小写字母和非字母字符)不转换。
    8.将字符串中小写字母转换成大写──strupr()函数
    (1)调用方式:strupr(字符串)
    (2)函数功能:将字符串中小写字母转换成大写,其它字符(包括大写字母和非字母字符)不转换。

    687

    主题

    2

    好友

    35万

    积分

    发表于 2004-8-21 11:14:44 |显示全部楼层

    [2月23日更新]C语言教学视频(19讲)

    第7章 函 数
    C语言是通过函数来实现模块化程序设计的。所以较大的C语言应用程序,往往是由多个函数组成的,每个函数分别对应各自的功能模块。
    7.1 函数的定义与调用
    7.2 函数的嵌套调用与递归调用
    7.3 数组作为函数参数
    7.4 内部变量与外部变量
    7.5 内部函数与外部函数
    7.6 变量的动态存储与静态存储
    7.1 函数的定义与调用
    7.1.1 函数的定义
    7.1.2 函数的返回值与函数类型
    7.1.3 对被调用函数的说明和函数原型
    7.1.4 函数的调用
    7.1.5 函数的形参与实参
    7.1 .1 函数的定义
    1.任何函数(包括主函数main())都是由函数说明和函数体两部分组成。根据函数是否需要参数,可将函数分为无参函数和有参函数两种。
    (1)无参函数的一般形式
    函数类型 函数名( void )
    { 说明语句部分;
    可执行语句部分;
    }
    注意:在旧标准中,函数可以缺省参数表。但在新标准中,函数不可缺省参数表;如果不需要参数,则用“void”表示,主函数main()例外。
    (2)有参函数的一般形式
    函数类型 函数名( 数据类型 参数[,数据类型 参数2……] )
    { 说明语句部分;
    可执行语句部分;
    }
    有参函数比无参函数多了一个参数表。调用有参函数时,调用函数将赋予这些参数实际的值。
    为了与调用函数提供的实际参数区别开,将函数定义中的参数表称为形式参数表,简称形参表。
    [案例7.1] 定义一个函数,用于求两个数中的大数。
    /*案例代码文件名:AL7_1.C*/
    /*功能:定义一个求较大数的函数并在主函数中调用*/
    int max(int n1, int n2) /*定义一个函数max()*/
    { return (n1>n2?n1:n2);
    }
    main()
    { int max(int n1, int n2); /*函数说明*/
    int num1,num2;
    printf("input two numbers:\n");
    scanf("%d%d", &num1, &num2);
    printf("max=%d\n", max(num1,num2));
    getch(); /*使程序暂停,按任一键继续*/
    }
    2.说明
    (1)函数定义不允许嵌套。
    在C语言中,所有函数(包括主函数main())都是平行的。一个函数的定义,可以放在程序中的任意位置,主函数main()之前或之后。但在一个函数的函数体内,不能再定义另一个函数,即不能嵌套定义。
    (2)空函数──既无参数、函数体又为空的函数。其一般形式为:
    [函数类型] 函数名(void)
    { }
    (3)在老版本C语言中,参数类型说明允许放在函数说明部分的第2行单独指定。
    7.1.2 函数的返回值与函数类型
    C语言的函数兼有其它语言中的函数和过程两种功能,从这个角度看,又可把函数分为有返回值函数和无返回值函数两种。
    1.函数返回值与return语句
    有参函数的返回值,是通过函数中的return语句来获得的。
    (1)return语句的一般格式: return ( 返回值表达式 );
    (2)return语句的功能:返回调用函数,并将“返回值表达式”的值带给调用函数。
    注意:调用函数中无return语句,并不是不返回一个值,而是一个不确定的值。为了明确表示不返回值,可以用“void”定义成“无(空)类型”。
    2.函数类型
    在定义函数时,对函数类型的说明,应与return语句中、返回值表达式的类型一致。
    如果不一致,则以函数类型为准。如果缺省函数类型,则系统一律按整型处理。
    良好的程序设计习惯:为了使程序具有良好的可读性并减少出错,凡不要求返回值的函数都应定义为空类型;即使函数类型为整型,也不使用系统的缺省处理。
    7.1.3 对被调用函数的说明和函数原型
    在ANSI C新标准中,采用函数原型方式,对被调用函数进行说明,其一般格式如下:
    函数类型 函数名(数据类型[ 参数名][, 数据类型[ 参数名2]…]);
    C语言同时又规定,在以下2种情况下,可以省去对被调用函数的说明:
    (1)当被调用函数的函数定义出现在调用函数之前时。因为在调用之前,编译系统已经知道了被调用函数的函数类型、参数个数、类型和顺序。
    (2)如果在所有函数定义之前,在函数外部(例如文件开始处)预先对各个函数进行了说明,则在调用函数中可缺省对被调用函数的说明。
    7.1.4 函数的调用
    在程序中,是通过对函数的调用来执行函数体的,其过程与其它语言的子程序调用相似。
    C语言中,函数调用的一般形式为: 函数名([实际参数表])
    切记:实参的个数、类型和顺序,应该与被调用函数所要求的参数个数、类型和顺序一致,才能正确地进行数据传递。
    在C语言中,可以用以下几种方式调用函数:
    (1)函数表达式(2)函数语句。C语言中的函数可以只进行某些操作而不返回函数值,这时的函数调用可作为一条独立的语句。
    (3)函数实参。函数作为另一个函数调用的实际参数出现。这种情况是把该函数的返回值作为实参进行传送,因此要求该函数必须是有返回值的。
    说明:
    (1)调用函数时,函数名称必须与具有该功能的自定义函数名称完全一致。
    (2)实参在类型上按顺序与形参,必须一一对应和匹配。如果类型不匹配,C编译程序将按赋值兼容的规则进行转换。如果实参和形参的类型不赋值兼容,通常并不给出出错信息,且程序仍然继续执行,只是得不到正确的结果。
    (3)如果实参表中包括多个参数,对实参的求值顺序随系统而异。有的系统按自左向右顺序求实参的值,有的系统则相反。Turbo C和MS C是按自右向左的顺序进行的 。
    7.1.5 函数的形参与实参
    函数的参数分为形参和实参两种,作用是实现数据传送。
    形参出现在函数定义中,只能在该函数体内使用。发生函数调用时,调用函数把实参的值复制1份,传送给被调用函数的形参,从而实现调用函数向被调用函数的数据传送。
    [案例7.3] 实参对形参的数据传递。
    /*实参对形参的数据传递。*/
    /*案例代码文件名:AL7_3.C*/
    void main()
    { void s(int n); /*说明函数*/
    int n=100; /*定义实参n,并初始化*/
    s(n); /*调用函数*/
    printf("n_s=%d\n",n); /*输出调用后实参的值,便于进行比较*/
    getch();
    }
    /* */
    void s(int n)
    { int i;
    printf("n_x=%d\n",n); /*输出改变前形参的值*/
    for(i=n-1; i>=1; i--) n=n+i; /*改变形参的值*/
    printf("n_x=%d\n",n); /*输出改变后形参的值*/
    }
    说明:
    (1)实参可以是常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。
    因此,应预先用赋值、输入等办法,使实参获得确定的值。
    (2)形参变量只有在被调用时,才分配内存单元;调用结束时,即刻释放所分配的内存单元。
    因此,形参只有在该函数内有效。调用结束,返回调用函数后,则不能再使用该形参变量。
    (3)实参对形参的数据传送是单向的,即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。
    (4)实参和形参占用不同的内存单元,即使同名也互不影响。
    7.2 函数的嵌套调用和递归调用
    7.2.1 函数的嵌套调用
    函数的嵌套调用是指,在执行被调用函数时,被调用函数又调用了其它函数。这与其它语言的子程序嵌套调用的情形是类似的.
    案例7.4] 计算s=1k+2k+3k+……+N k
    /*案例代码文件名:AL7_4.C*/
    /*功能:函数的嵌套调用*/
    &#35;define K 4
    &#35;define N 5
    long f1(int n,int k) /*计算n的k次方*/
    { long power=n;
    int i;
    for(i=1;i return power;
    }
    long f2(int n,int k) /*计算1到n的k次方之累加和*/
    { long sum=0;
    int i;
    for(i=1;i<=n;i++) sum += f1(i, k);
    return sum;
    }
    main()
    { printf("Sum of %d powers of integers from 1 to %d = ",K,N);
    printf("%d\n",f2(N,K));
    getch();
    }
    7.2.2 函数的递归调用
    函数的递归调用是指,一个函数在它的函数体内,直接或间接地调用它自身。
    C语言允许函数的递归调用。在递归调用中,调用函数又是被调用函数,执行递归函数将反复调用其自身。每调用一次就进入新的一层。
    为了防止递归调用无终止地进行,必须在函数内有终止递归调用的手段。常用的办法是加条件判断,满足某种条件后就不再作递归调用,然后逐层返回。
    [案例7.5] 用递归法计算n!。
    /*案例代码文件名:AL7_5.C*/
    /*功能:通过函数的递归调用计算阶乘*/
    long power(int n)
    { long f;
    if(n>1) f=power(n-1)*n;
    else f=1;
    return(f);
    }
    main()
    { int n;
    long y;
    printf("input a inteager number:\n");
    scanf("%d",&n);
    y=power(n);
    printf("%d!=%ld\n",n,y);
    getch();
    }
    7.3 数组作为函数参数
    数组用作函数参数有两种形式:一种是把数组元素(又称下标变量)作为实参使用;另一种是把数组名作为函数的形参和实参使用。
    7.3.1 数组元素作为函数参数
    7.3.2 数组名作为函数的形参和实参
    7.3.1 数组元素作为函数参数
    数组元素就是下标变量,它与普通变量并无区别。数组元素只能用作函数实参,其用法与普通变量完全相同:在发生函数调用时,把数组元素的值传送给形参,实现单向值传送。
    [案例7.6] 写一函数,统计字符串中字母的个数。
    /*案例代码文件名:AL7_6.C*/
    /*功能:数组元素作为函数实参*/
    int isalp(char c)
    { if (c>=`a`&&c<=`z`||c>=`A`&&c<=`Z`)
    return(1);
    else return(0);
    }
    main()
    { int i,num=0;
    char str[255];
    printf("Input a string: ");
    gets(str);
    for(i=0;str!=`\0`;i++)
    if (isalp(str)) num++;
    puts(str);
    printf("num=%d\n",num);
    getch();
    }
    说明:
    (1)用数组元素作实参时,只要数组类型和函数的形参类型一致即可,并不要求函数的形参也是下标变量。换句话说,对数组元素的处理是按普通变量对待的。
    (2)在普通变量或下标变量作函数参数时,形参变量和实参变量是由编译系统分配的两个不同的内存单元。在函数调用时发生的值传送,是把实参变量的值赋予形参变量。
    7.3.2 数组名作为函数的形参和实参
    数组名作函数参数时,既可以作形参,也可以作实参。
    数组名作函数参数时,要求形参和相对应的实参都必须是类型相同的数组(或指向数组的指针变量),都必须有明确的数组说明
    [案例7.7] 已知某个学生5门课程的成绩,求平均成绩。
    /*案例代码文件名:AL7_7.C*/
    float aver(float a[ ]) /*求平均值函数*/
    { int i;
    float av,s=a[0];
    for(i=1;i<5;i++) s += a
    av=s/5;
    return av;
    }
    void main()
    { float sco[5],av;
    int i;
    printf("\ninput 5 scores:\n");
    for(i=0;i<5;i++) scanf("%f",&sco);
    av=aver(sco); /*调用函数,实参为一数组名*/
    printf("average score is %5.2f\n",av);
    getch();
    }
    说明:
    (1)用数组名作函数参数,应该在调用函数和被调用函数中分别定义数组,且数据类型必须一致,否则结果将出错。例如,在本案例中,形参数组为a[],实参数组为sco[],它们的数据类型相同。
    (2)C编译系统对形参数组大小不作检查,所以形参数组可以不指定大小。例如,本案例中的形参数组a[]。
    如果指定形参数组的大小,则实参数组的大小必须大于等于形参数组,否则因形参数组的部分元素没有确定值而导致计算结果错误。
    7.4 内部变量与外部变量
    C语言中所有的变量都有自己的作用域。变量说明的位置不同,其作用域也不同,据此将C语言中的变量分为内部变量和外部变量。
    7.4.1 内部变量
    7.4.2 外部变量
    7.4.1 内部变量
    在一个函数内部说明的变量是内部变量,它只在该函数范围内有效。
    也就是说,只有在包含变量说明的函数内部,才能使用被说明的变量,在此函数之外就不能使用这些变量了。所以内部变量也称“局部变量”。
    例如:
    int f1(int a) /*函数f1*/
    { int b,c;
    ……
    } /*a,b,c作用域:仅限于函数f1()中*/

    int f2(int x) /*函数f2*/
    { int y,z;
    ……
    } /*x,y,z作用域:仅限于函数f2()中*/

    main()
    { int m,n;
    ……
    } /*m,n作用域:仅限于函数main()中*/
    关于局部变量的作用域还要说明以下几点:
    1.主函数main()中定义的内部变量,也只能在主函数中使用,其它函数不能使用。同时,主函数中也不能使用其它函数中定义的内部变量。因为主函数也是一个函数,与其它函数是平行关系。这一点是与其它语言不同的,应予以注意。
    2.形参变量也是内部变量,属于被调用函数;实参变量,则是调用函数的内部变量。
    3.允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰,也不会发生混淆。
    4.在复合语句中也可定义变量,其作用域只在复合语句范围内。
    7.4.2 外部变量
    在函数外部定义的变量称为外部变量。以此类推,在函数外部定义的数组就称为外部数组。
    外部变量不属于任何一个函数,其作用域是:从外部变量的定义位置开始,到本文件结束为止。
    外部变量可被作用域内的所有函数直接引用,所以外部变量又称全局变量。
    [案例7.9] 输入长方体的长(l)、宽(w)、高(h),求长方体体积及正、侧、顶三个面的面积。
    /*案例代码文件名:AL7_9.C*/
    /*功能:利用全局变量计算长方体的体积及三个面的面积*/
    int s1,s2,s3;
    int vs(int a,int b,int c)
    { int v;
    v=a*b*c; s1=a*b; s2=b*c; s3=a*c;
    return v;
    }
    main()
    {int v,l,w,h;
    clrscr();
    printf("\ninput length,width and height: ");
    scanf("%d%d%d",&l,&w,&h);
    v=vs(l,w,h);
    printf("v=%d s1=%d s2=%d s3=%d\n",v,s1,s2,s3);
    getch();
    }
    对于全局变量还有以下几点说明:
    (1)外部变量可加强函数模块之间的数据联系,但又使这些函数依赖这些外部变量,因而使得这些函数的独立性降低。
    从模块化程序设计的观点来看这是不利的,因此不是非用不可时,不要使用外部变量。
    (2)在同一源文件中,允许外部变量和内部变量同名。在内部变量的作用域内,外部变量将被屏蔽而不起作用。
    (3)外部变量的作用域是从定义点到本文件结束。如果定义点之前的函数需要引用这些外部变量时,需要在函数内对被引用的外部变量进行说明。外部变量说明的一般形式为:
    extern 数据类型 外部变量[,外部变量2……];
    注意:外部变量的定义和外部变量的说明是两回事。外部变量的定义,必须在所有的函数之外,且只能定义一次。而外部变量的说明,出现在要使用该外部变量的函数内,而且可以出现多次。
    [案例7.10] 外部变量的定义与说明。
    /*案例代码文件名:AL7_10.C*/
    int vs(int xl,int xw)
    { extern int xh; /*外部变量xh的说明*/
    int v;
    v=xl*xw*xh; /*直接使用外部变量xh的值*/
    return v;
    }
    main()
    { extern int xw,xh; /*外部变量的说明*/
    int xl=5; /*内部变量的定义*/
    printf("xl=%d,xw=%d,xh=%d\nv=%d",xl,xw,xh,vs(xl,xw));
    }
    int xl=3,xw=4,xh=5; /*外部变量xl、xw、xh的定义*/
    7.5 内部函数和外部函数
    当一个源程序由多个源文件组成时,C语言根据函数能否被其它源文件中的函数调用,将函数分为内部函数和外部函数。
    7.5.1 内部函数(又称静态函数)
    7.5.2 外部函数
    7.5.3 多个源程序文件的编译和连接
    7.5.1 内部函数(又称静态函数)
    如果在一个源文件中定义的函数,只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用,这种函数称为内部函数。
    定义一个内部函数,只需在函数类型前再加一个“static”关键字即可,如下所示:
    static 函数类型 函数名(函数参数表)
    {……}
    关键字“static”,译成中文就是“静态的”,所以内部函数又称静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件。
    使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名,因为同名也没有关系。
    7.5.2 外部函数
    外部函数的定义:在定义函数时,如果没有加关键字“static”,或冠以关键字“extern”,表示此函数是外部函数:
    [extern] 函数类型 函数名(函数参数表)
    {……}
    调用外部函数时,需要对其进行说明:
    [extern] 函数类型 函数名(参数类型表)[,函数名2(参数类型表2)……];
    [案例7.11] 外部函数应用。
    (1)文件mainf.c
    main()
    { extern void input(…),process(…),output(…);
    input(…); process(…); output(…);
    }
    (2)文件subf1.c
    ……
    extern void input(……) /*定义外部函数*/
    {……}
    (3)文件subf2.c
    ……
    extern void process(……) /*定义外部函数*/
    {……}
    (4)文件subf3.c
    ……
    extern void output(……) /*定义外部函数*/
    {……}
    7.5.3 多个源程序文件的编译和连接
    (1)一般过程
    编辑各源文件 → 创建Project(项目)文件 → 设置项目名称 → 编译、连接,运行,查看结果。
    (2)创建Project(项目)文件
    用编辑源文件相同的方法,创建一个扩展名为.PRJ的项目文件:该文件中仅包括将被编译、连接的各源文件名,一行一个,其扩展名.C可以缺省;文件名的顺序,仅影响编译的顺序,与运行无关。
    注意:如果有某个(些)源文件不在当前目录下,则应在文件名前冠以路径。
    (3)设置项目名称
    打开菜单,选取Project/Project name,输入项目文件名即可。
    (4)编译、连接,运行,查看结果
    与单个源文件相同。编译产生的目标文件,以及连接产生的可执行文件,它们的主文件名,均与项目文件的主文件名相同。
    注意:当前项目文件调试完毕后,应选取Project/Clear project,将其项目名称从“Project name”中清除(清除后为空)。否则,编译、连接和运行的,始终是该项目文件!
    (5)关于错误跟踪
    缺省时,仅跟踪当前一个源程序文件。如果希望自动跟踪项目中的所有源文件,则应将Options/Environment/Message Tracking开关置为“All files ”:连续按回车键,直至“All files”出现为止。此时,滚动消息窗口中的错误信息时,系统会自动加载相应的源文件到编辑窗口中。
    也可关闭跟踪(将“Message Tracking”置为“Off”)。此时,只要定位于感兴趣的错误信息上,然后回车,系统也会自动将相应源文件加载到编辑窗口中。

    7.6 变量的动态存储与静态存储简介
    在C语言中,对变量的存储类型说明有以下四种:自动变量(auto)、寄存器变量(register)、外部变量(extern)、静态变量(static)。自动变量和寄存器变量属于动态存储方式,外部变量和静态内部变量属于静态存储方式。
    7.6.1 内部变量的存储方式
    7.6.2 外部变量的存储方式
    7.6.1 内部变量的存储方式
    1.静态存储──静态内部变量
    (1)定义格式: static 数据类型 内部变量表;
    (2)存储特点
    1)静态内部变量属于静态存储。在程序执行过程中,即使所在函数调用结束也不释放。换句话说,在程序执行期间,静态内部变量始终存在,但其它函数是不能引用它们的。
    2)定义但不初始化,则自动赋以"0"(整型和实型)或`\0`(字符型);且每次调用它们所在的函数时,不再重新赋初值,只是保留上次调用结束时的值!
    (3)何时使用静态内部变量
    1)需要保留函数上一次调用结束时的值。
    2)变量只被引用而不改变其值。
    2.动态存储──自动局部变量(又称自动变量)
    (1)定义格式:[auto] 数据类型 变量表;
    (2)存储特点
    1)自动变量属于动态存储方式。在函数中定义的自动变量,只在该函数内有效;函数被调用时分配存储空间,调用结束就释放。
    在复合语句中定义的自动变量,只在该复合语句中有效;退出复合语句后,也不能再使用,否则将引起错误。
    2)定义而不初始化,则其值是不确定的。如果初始化,则赋初值操作是在调用时进行的,且每次调用都要重新赋一次初值。
    3)由于自动变量的作用域和生存期,都局限于定义它的个体内(函数或复合语句),因此不同的个体中允许使用同名的变量而不会混淆。即使在函数内定义的自动变量,也可与该函数内部的复合语句中定义的自动变量同名。
    建议:系统不会混淆,并不意味着人也不会混淆,所以尽量少用同名自动变量!
    [案例7.13]自动变量与静态局部变量的存储特性。
    /*案例代码文件名:AL7_13.C*/
    void auto_static(void)
    { int var_auto=0; /*自动变量:每次调用都重新初始化*/
    static int var_static=0; /*静态局部变量:只初始化1次*/
    printf(“var_auto=%d, var_static=%d\n”, var_auto, var_static);
    ++var_auto;
    ++var_static;
    }
    main()
    { int i;
    for(i=0; i<5; i++) auto_static();
    }
    3.寄存器存储──寄存器变量
    一般情况下,变量的值都是存储在内存中的。为提高执行效率,C语言允许将局部变量的值存放到寄存器中,这种变量就称为寄存器变量。定义格式如下:
    register 数据类型 变量表;
    (1)只有局部变量才能定义成寄存器变量,即全局变量不行。
    (2)对寄存器变量的实际处理,随系统而异。例如,微机上的MSC和TC 将寄存器变量实际当作自动变量处理。
    (3)允许使用的寄存器数目是有限的,不能定义任意多个寄存器变量。
    7.6.2 外部变量的存储方式
    外部变量属于静态存储方式:
    (1)静态外部变量──只允许被本源文件中的函数引用
    其定义格式为: static 数据类型 外部变量表;
    (2)非静态外部变量──允许被其它源文件中的函数引用
    定义时缺省static关键字的外部变量,即为非静态外部变量。其它源文件中的函数,引用非静态外部变量时,需要在引用函数所在的源文件中进行说明:
    extern 数据类型 外部变量表;
    注意:在函数内的extern变量说明,表示引用本源文件中的外部变量!而函数外(通常在文件开头)的extern变量说明,表示引用其它文件中的外部变量。
    静态局部变量和静态外部变量同属静态存储方式,但两者区别较大:
    (1)定义的位置不同。静态局部变量在函数内定义,静态外部变量在函数外定义。
    (2)作用域不同。静态局部变量属于内部变量,其作用域仅限于定义它的函数内;虽然生存期为整个源程序,但其它函数是不能使用它的。
    静态外部变量在函数外定义,其作用域为定义它的源文件内;生存期为整个源程序,但其它源文件中的函数也是不能使用它的。
    (3)初始化处理不同。静态局部变量,仅在第1次调用它所在的函数时被初始化,当再次调用定义它的函数时,不再初始化,而是保留上1次调用结束时的值。而静态外部变量是在函数外定义的,不存在静态内部变量的“重复”初始化问题,其当前值由最近1次给它赋值的操作决定。
    务必牢记:把局部变量改变为静态内部变量后,改变了它的存储方式,即改变了它的生存期。把外部变量改变为静态外部变量后,改变了它的作用域,限制了它的使用范围。因此,关键字“static”在不同的地方所起的作用是不同的。

    687

    主题

    2

    好友

    35万

    积分

    发表于 2004-8-21 11:16:01 |显示全部楼层

    [2月23日更新]C语言教学视频(19讲)

    第8章 编译预处理
    所谓编译预处理是指,在对源程序进行编译之前,先对源程序中的编译预处理命令进行处理;然后再将处理的结果,和源程序一起进行编译,以得到目标代码。
    8.1 宏定义与符号常量
    8.2 文件包含
    8.3 条件编译
    8.1 宏定义与符号常量
    在C语言中,“宏”分为无参数的宏(简称无参宏)和有参数的宏(简称有参宏)两种。
    8.1.1 无参宏定义
    8.1.2 符号常量
    8.1.3 有参宏定义
    8.1.1 无参宏定义
    1.无参宏定义的一般格式
    &#35;define 标识符 语言符号字符串
    其中:“define”为宏定义命令;“标识符”为所定义的宏名,通常用大写字母表示,以便于与变量区别;“语言符号字符串”可以是常数、表达式、格式串等。
    2.使用宏定义的优点
    (1)可提高源程序的可维护性
    (2)可提高源程序的可移植性
    (3)减少源程序中重复书写字符串的工作量
    [案例8.1] 输入圆的半径,求圆的周长、面积和球的体积。要求使用无参宏定义圆周率。
    /*案例代码文件名:AL8_1.C*/
    /*程序功能:输入圆的半径,求圆的周长、面积和球的体积。 */
    &#35;define PI 3.1415926 /*PI是宏名,3.1415926用来替换宏名的常数*/
    main()
    { float radius,length,area,volume;
    printf("Input a radius: ");
    scanf("%f",&radius);
    length=2*PI*radius; /*引用无参宏求周长*/
    area=PI*radius*radius; /*引用无参宏求面积*/
    volume=PI*radius*radius*radius*3/4; /*引用无参宏求体积*/
    printf("length=%.2f,area=%.2f,volume=%.2f\n", length, area, volume);
    }
    3.说明
    (1)宏名一般用大写字母表示,以示与变量区别。但这并非是规定。
    (2)宏定义不是C语句,所以不能在行尾加分号。否则,宏展开时,会将分号作为字符串的1个字符,用于替换宏名。
    (3)在宏展开时,预处理程序仅以按宏定义简单替换宏名,而不作任何检查。如果有错误,只能由编译程序在编译宏展开后的源程序时发现。
    (4)宏定义命令&#35;define出现在函数的外部,宏名的有效范围是:从定义命令之后, 到本文件结束。通常,宏定义命令放在文件开头处。
    (5)在进行宏定义时,可以引用已定义的宏名 。
    (6)对双引号括起来的字符串内的字符,即使与宏名同名,也不进行宏展开。

    8.1.2 符号常量
    在定义无参宏时,如果“语言符号字符串”是一个常量,则相应的“宏名”就是一个符号常量。
    恰当命名的符号常量,除具有宏定义的上述优点外,还能表达出它所代表常量的实际含义,从而增强程序的可读性。
    &#35;define EOF -1 /*文件尾*/
    &#35;define NULL 0 /*空指针*/
    &#35;define MIN 1 /*极小值*/
    &#35;define MAX 31 /*极大值*/
    &#35;define STEP 2 /*步长*/
    8.1.3 有参宏定义
    1.带参宏定义的一般格式
    &#35;define 宏名(形参表) 语言符号字符串
    2.带参宏的调用和宏展开
    (1)调用格式:宏名(实参表)
    (2)宏展开:用宏调用提供的实参字符串,直接置换宏定义命令行中、相应形参字符串,非形参字符保持不变。
    3.说明
    (1)定义有参宏时,宏名与左圆括号之间不能留有空格。否则,C编译系统将空格以后的所有字符均作为替代字符串,而将该宏视为无参宏。
    (2)有参宏的展开,只是将实参作为字符串,简单地置换形参字符串,而不做任何语法检查。在定义有参宏时,在所有形参外和整个字符串外,均加一对圆括号。
    (3)虽然有参宏与有参函数确实有相似之处,但不同之处更多,主要有以下几个方面:
    1)调用有参函数时,是先求出实参的值,然后再复制一份给形参。而展开有参宏时,只是将实参简单地置换形参。
    2)在有参函数中,形参是有类型的,所以要求实参的类型与其一致;而在有参宏中,形参是没有类型信息的,因此用于置换的实参,什么类型都可以。有时,可利用有参宏的这一特性,实现通用函数功能。
    3)使用有参函数,无论调用多少次,都不会使目标程序变长,但每次调用都要占用系统时间进行调用现场保护和现场恢复;而使用有参宏,由于宏展开是在编译时进行的,所以不占运行时间,但是每引用1次,都会使目标程序增大1次。
    8.2 文件包含
    1.文件包含的概念
    文件包含是指,一个源文件可以将另一个源文件的全部内容包含进来。
    2.文件包含处理命令的格式
    #include “包含文件名” 或 #include <包含文件名>
    两种格式的区别仅在于:
    (1)使用双引号:系统首先到当前目录下查找被包含文件,如果没找到,再到系统指定的“包含文件目录”(由用户在配置环境时设置)去查找。
    (2)使用尖括号:直接到系统指定的“包含文件目录”去查找。一般地说,使用双引号比较保险。
    3.文件包含的优点
    一个大程序,通常分为多个模块,并由多个程序员分别编程。有了文件包含处理功能,就可以将多个模块共用的数据(如符号常量和数据结构)或函数,集中到一个单独的文件中。这样,凡是要使用其中数据或调用其中函数的程序员,只要使用文件包含处理功能,将所需文件包含进来即可,不必再重复定义它们,从而减少重复劳动。
    4.说明
    (1)编译预处理时,预处理程序将查找指定的被包含文件,并将其复制到&#35;include命令出现的位置上。
    (2)常用在文件头部的被包含文件,称为“标题文件”或“头部文件”,常以“h”(head)作为后缀,简称头文件。在头文件中,除可包含宏定义外,还可包含外部变量定义、结构类型定义等。
    (3)一条包含命令,只能指定一个被包含文件。如果要包含n个文件,则要用n条包含命令。
    (4)文件包含可以嵌套,即被包含文件中又包含另一个文件。

    8.3 条件编译
    条件编译可有效地提高程序的可移植性,并广泛地应用在商业软件中,为一个程序提供各种不同的版本。
    8.3.1 &#35;ifdef ~ &#35;endif和&#35;ifndef ~ &#35;endif命令
    8.3.2 &#35;if ~ &#35;endif
    8.3.1 &#35;ifdef ~ &#35;endif和&#35;ifndef ~ &#35;endif命令
    1.一般格式
    #ifdef 标识符
    程序段1;
    [#else
    程序段2;]
    #endif
    2.功能:当“标识符”已经被&#35;define命令定义过,则编译程序段1,否则编译程序段2。
    (1)在不同的系统中,一个int 型数据占用的内存字节数可能是不同的。
    (2)利用条件编译,还可使同一源程序即适合于调试(进行程序跟踪、打印较多的状态或错误信息),又适合高效执行要求。
    3.关于&#35;ifndef ~ &#35;endif命令
    格式与&#35;ifdef ~ &#35;endif命令一样,功能正好与之相反。
    8.3.2 &#35;if ~ &#35;endif
    1.一般格式
    #if 常量表达式
    程序段1;
    [#else
    程序段2;]
    #endif
    2.功能:当表达式为非0(“逻辑真”)时,编译程序段1,否则编译程序段2。
    [案例8.2] 输入一个口令,根据需要设置条件编译,使之能将口令原码输出,或仅输出若干星号“*”。
    /*案例代码文件名:AL8_2.C*/
    &#35;define PASSWORD 0 /*预置为输出星号*/
    main()
    { ……
    /*条件编译*/
    &#35;if PASSWORD /*源码输出*/
    ……
    &#35;else /*输出星号*/
    ……
    &#35;endif
    ……
    }

    下一章,我们将讲述指针。届时欢迎阅读。

    687

    主题

    2

    好友

    35万

    积分

    发表于 2004-8-21 11:23:08 |显示全部楼层

    [2月23日更新]C语言教学视频(19讲)

    <第9章 指 针
    指针是C语言中的重要概念,也是C语言的重要特色。使用指针,可以使程序更加简洁、紧凑、高效。
    9.1 指针和指针变量的概念
    9.2 指针变量的定义与应用
    9.3 数组的指针和指向数组的指针变量
    9.4 字符串的指针和指向字符串的指针变量
    9.5 返回指针值的函数
    9.6 指针数组与主函数main()的形参
    9.7 函数的指针和指向函数的指针变量
    <9.1 指针和指针变量的概念
    1.内存地址──内存中存储单元的编号
    (1)计算机硬件系统的内存储器中,拥有大量的存储单元(容量为1字节)。
    为了方便管理,必须为每一个存储单元编号,这个编号就是存储单元的“地址”。每个存储单元都有一个惟一的地址。
    (2)在地址所标识的存储单元中存放数据。
    注意:内存单元的地址与内存单元中的数据是两个完全不同的概念。
    2.变量地址──系统分配给变量的内存单元的起始地址
    假设有这样一个程序:
    main()
    { int num;
    scanf("%d",&num);
    printf("num=%d\n", num);
    }
    C编译程序编译到该变量定义语句时,将变量num 登录到“符号表”中。符号表的关键属性有两个:一是“标识符名(id)” ,二是该标识符在内存空间中的“地址(addr)” 。
    为描述方便,假设系统分配给变量num的2字节存储单元为 3000 和3001,则起始地址3000就是变量num在内存中的地址。
    3.变量值的存取──通过变量在内存中的地址进行
    系统执行“scanf(”%d“,&num);”和“printf(”num=%d\n“, num);”时,存取变量num值的方式可以有两种:
    (1)直接访问──直接利用变量的地址进行存取
    1)上例中scanf(“%d”,&num)的执行过程是这样的:
    用变量名num作为索引值,检索符号表,找到变量num的起始地址3000;然后将键盘输入的值(假设为3)送到内存单元3000和3001中。此时,变量num在内存中的地址和值,如图9-1所示。
    2)printf("num=%d\n",num)的执行过程,与scanf()很相似:
    首先找到变量num的起始地址3000,然后从3000和3001中取出其值,最后将它输出。
    (2)间接访问──通过另一变量访问该变量的值
    C语言规定:在程序中可以定义一种特殊的变量(称为指针变量),用来存放其它变量的地址.
    例如,假设定义了这样一个指针变量num_pointer,它被分配到4000、4001单元,其值可通过赋值语句“num_pointer=&num;”得到。此时,指针变量num_pointer的值就是变量num在内存中的起始地址3000,如图9-1所示。
    通过指针变量num_pointer存取变量num值的过程如下:
    首先找到指针变量num_pointer的地址(4000),取出其值3000(正好是变量num 的起始地址); 然后从3000、3001中取出变量num的值(3)。
    (3)两种访问方式的比较
    两种访问方式之间的关系,可以用某人甲(系统)要找某人乙(变量)来类比。
    一种情况是,甲知道乙在何处,直接去找就是(即直接访问)。
    另一种情况是,甲不知道乙在哪,但丙(指针变量)知道,此时甲可以这么做:先找丙,从丙处获得乙的去向,然后再找乙(即间接访问)。
    4.指针与指针变量
    (1)指针──即地址
    一个变量的地址称为该变量的指针。通过变量的指针能够找到该变量。
    (2)指针变量──专门用于存储其它变量地址的变量
    指针变量num_pointer的值就是变量num的地址。指针与指针变量的区别,就是变量值与变量的区别。
    (3)为表示指针变量和它指向的变量之间的关系,用指针运算符“*”表示。
    例如,指针变量num_pointer与它所指向的变量num的关系,表示为:
    *num_pointer,即*num_pointer等价于变量num。
    因此,下面两个语句的作用相同:
    num=3; /*将3直接赋给变量num*/
    num_pointer=&num; /*使num_pointer指向num */
    *num_pointer=3; /*将3赋给指针变量num_pointer所指向的变量*/
    9.2 指针变量的定义与应用
    9.2.1 指针变量的定义与相关运算
    [案例9.1] 指针变量的定义与相关运算示例。
    /*案例代码文件名:AL9_1.C*/
    main()
    { int num_int=12, *p_int; /*定义一个指向int型数据的指针变量p_int */
    float num_f=3.14, *p_f;/*定义一个指向float型数据的指针变量p_f */ char num_ch=’p’, *p_ch;/*定义一个指向char型数据的指针变量p_ch */ p_int=&num_int; /*取变量num_int的地址,赋值给p_int */ p_f=&num_f; /*取变量num_f的地址,赋值给p_f */ p_ch=&num_ch; /*取变量num_ch的地址,赋值给p_ch */
    printf(“num_int=%d, *p_int=%d\n”, num_int, *p_int);
    printf(“num_f=%4.2f, *p_f=%4.2f\n”, num_f, *p_f);
    printf(“num_ch=%c, *p_ch=%c\n”, num_ch, *p_ch);
    }
    程序运行结果:
    num_int=12, *p_int=12
    num_f=3.14, *p_f=3.14
    num_ch=p, *p_ch=p
    程序说明:
    (1)头三行的变量定义语句──指针变量的定义
    与一般变量的定义相比,除变量名前多了一个星号“*” (指针变量的定义标识符)外,其余一样:
    数据类型 *指针变量[, *指针变量2……];
    注意:此时的指针变量p_int、p_f、p_ch,并未指向某个具体的变量(称指针是悬空的)。使用悬空指针很容易破坏系统,导致系统瘫痪。
    (2)中间三行的赋值语句──取地址运算(&)
    取地址运算的格式: &变量
    例如,&num_int、&num_f、&num_ch的结果,分别为变量num_int、num_f、num_ch的地址。
    注意:指针变量只能存放指针(地址),且只能是相同类型变量的地址。
    例如,指针变量p_int、p_f、p_ch,只能分别接收int型、float型、char型变量的地址,否则出错。
    (3)后三行的输出语句──指针运算(*)
    使用直接访问和间接访问两种方式,分别输出变量num_int、num_f、num_ch的值。
    注意:这三行出现在指针变量前的星号“*”是指针运算符,访问指针变量所指向的变量的值,而非指针运算符
    [案例9.2] 使用指针变量求解:输入2个整数,按升序(从小到大排序)输出。
    /*案例代码文件名:AL9_2.C*/
    /*程序功能:使用指针变量求解2个整数的升序输出*/
    main()
    { int num1,num2;
    int *num1_p=&num1, *num2_p=&num2, *pointer;
    printf(“Input the first number: ”); scanf(“%d”,num1_p);
    printf(“Input the second number: ”); scanf(“%d”,num2_p);
    printf(“num1=%d, num2=%d\n”, num1, num2);
    if( *num1_p > *num2_p ) /*如果num1>num2,则交换指针*/
    pointer= num1_p, num1_p= num2_p, num2_p=pointer;
    printf(“min=%d, max=%d\n”, *num1_p, *num2_p);
    }
    程序运行情况:
    Input the first number:9←┘
    Input the second number:6←┘
    num1=9, num2=6
    min=6, max=9
    程序说明:
    (1)第5行的if语句
    如果*num1_p>*num2_p (即num1>num2),则交换指针,使num1_p指向变量num2(较小值),num2_p指向变量num1(较大值)。
    (2)printf(“min=%d, max=%d\n”, *num1_p, *num2_p); 语句:通过指针变量,间接访问变量的值。
    本案例的处理思路是:交换指针变量num1_p 和num2_p的值,而不是变量num1和num2的值(变量num1和num2并未交换,仍保持原值),最后通过指针变量输出处理结果。
    9.2.2 指针变量作函数参数
    1.指针变量,既可以作为函数的形参,也可以作函数的实参。
    2.指针变量作实参时,与普通变量一样,也是“值传递”,即将指针变量的值(一个地址)传递给被调用函数的形参(必须是一个指针变量)。
    注意:被调用函数不能改变实参指针变量的值,但可以改变实参指针变量所指向的变量的值。
    [案例9.3] 使用函数调用方式改写[案例9.2],要求实参为指针变量。
    /*案例代码文件名:AL9_3.C*/ /******************************************************/ /*exchange()功能:交换2个形参指针变量所指向的变量的值 */ /*形参:2个,均为指向整型数据的指针变量 */ /*返回值:无 */ /******************************************************/ void exchange(int *pointer1, int *pointer2) { int temp; temp=*pointer1, *pointer1=*pointer2, *pointer2=temp; }
    /*主函数main()*/
    main()
    { int num1,num2;
    /*定义并初始化指针变量num1_p和 num2_p */
    int *num1_p=&num1, *num2_p=&num2;
    printf(“Input the first number: ”); scanf(“%d”, num1_p);
    printf(“Input the second number: ”);
    scanf(“%d”, num2_p);
    printf(“num1=%d, num2=%d\n”, num1, num2);
    if( *num1_p > *num2_p ) /* 即num1>num2)*/
    exchange(num1_p, num2_p); /*指针变量作实参*/
    /*输出排序后的num1和num2的值*/
    printf(“min=%d, max=%d\n”, num1, num2);
    }
    程序运行情况:
    Input the first number:9←┘
    Input the second number:6←┘
    num1=9, num2=6
    min=6, max=9
    调用函数exchange()之前、之时、结束时和结束后的情况,如图9-5所示。
    形参指针变量pointer1(指向变量num1)和pointer2(指向变量num2),在函数调用开始时才分配存储空间,函数调用结束后立即被释放。
    虽然被调用函数不能改变实参指针变量的值,但可以改变它们所指向的变量的值。
    总结:为了利用被调用函数改变的变量值,应该使用指针(或指针变量)作函数实参。其机制为:在执行被调用函数时,使形参指针变量所指向的变量的值发生变化;函数调用结束后,通过不变的实参指针(或实参指针变量)将变化的值保留下来.
    [案例9.4] 输入3个整数,按降序(从大到小的顺序)输出。要求使用变量的指针作函数调用的实参来实现。
    /*案例代码文件名:AL9_4.C*/
    /******************************************************/
    /*exchange()功能:交换2个形参指针变量所指向的变量的值 */
    /*形参:2个,均为指向整型数据的指针变量 */
    /*返回值:无 */
    /******************************************************/
    void exchange(int *pointer1, int *pointer2)
    { int temp;
    temp=*pointer1, *pointer1=*pointer2, *pointer2=temp;
    }
    /*主函数main()*/
    main()
    { int num1,num2,num3;
    /*从键盘上输入3个整数*/
    printf(“Input the first number: ”); scanf(“%d”, &num1);
    printf(“Input the second number: ”); scanf(“%d”, &num2);
    printf(“Input the third number: ”); scanf(“%d”, &num3);
    printf(“num1=%d, num2=%d, num3=%d\n”, num1, num2, num3);
    /*排序*/
    if( num1 < num2 ) /*num1 exchange( &num1, &num2 );
    if( num1 < num3 ) exchange( &num1, &num3 );
    if( num2 < num3 ) exchange( &num2, &num3 );
    /*输出排序结果*/
    printf(“排序结果: %d, %d, %d\n”,num1,num2,num3);
    }
    程序运行情况:
    Input the first number:9←┘
    Input the second number:6←┘
    Input the third number:12←┘
    num1=9, num2=6, num3=12
    排序结果: 12, 9, 6
    --------------------
    9.3 数组的指针和指向数组的指针变量
    9.3.1 概述
    1.概念
    数组的指针──数组在内存中的起始地址,数组元素的指针──数组元素在内存中的起始地址。
    2.指向数组的指针变量的定义
    指向数组的指针变量的定义,与指向普通变量的指针变量的定义方法一样。
    例如,int array[10], *pointer=array(或&array[0]); 或者: int array[10], *pointer;
    pointer=array;
    注意:数组名代表数组在内存中的起始地址(与第1个元素的地址相同),所以可以用数组名给指针变量赋值。
    3.数组元素的引用
    数组元素的引用,既可用下标法,也可用指针法。使用下标法,直观;而使用指针法,能使目标程序占用内存少、运行速度快。
    9.3.2 通过指针引用数组元素
    如果有“int array[10],*pointer=array;” ,则:
    (1)pointer+i和array+i都是数组元素array的地址,如图9-6所示。
    (2)*(pointer+i)和*(array+i)就是数组元素array
    (3)指向数组的指针变量,也可将其看作是数组名,因而可按下标法来使用。例如,pointer等价于*(pointer+i)。
    注意:pointer+1指向数组的下一个元素,而不是简单地使指针变量pointer的值+1。其实际变化为pointer+1*size(size为一个元素占用的字节数)。
    例如,假设指针变量pointer的当前值为3000,则pointer+1为3000+1*2=3002,而不是3001。
    [案例9.5] 使用指向数组的指针变量来引用数组元素。
    /*案例代码文件名:AL9_5.C*/
    /*程序功能:使用指向数组的指针变量来引用数组元素*/
    main()
    { int array[10], *pointer=array, i;
    printf(“Input 10 numbers: ”);
    for(i=0; i<10; i++)
    scanf(“%d”, pointer+i); /*使用指针变量来输入数组元素的值*/
    printf(“array[10]: ”);
    for(i=0; i<10; i++)
    printf(“%d ”, *(pointer+i)); /*使用指向数组的指针变量输出数组*/
    printf(“\n”);
    }
    程序运行情况:
    Input 10 numbers: 0 1 2 3 4 5 6 7 8 9←┘
    array[10]: 0 1 2 3 4 5 6 7 8 9
    程序说明:
    程序中第3行和第6行的2个for语句,等价于下面的程序段:
    for(i=0; i<10; i++,pointer++)
    scanf(“%d”,pointer);
    printf(“array[10]: ”);
    pointer=array; /*使pointer重新指向数组的第一个元素*/
    for(i=0; i<10; i++,pointer++)
    printf(“%d”,*pointer);
    思考题:
    (1)如果去掉“pointer=array;”行,程序运行结果会如何?请上机验证。
    (2)在本案例中,也可以不使用i来作循环控制变量,程序怎么修改?提示:指针可以参与关系运算。
    说明:
    (1)指针变量的值是可以改变的,所以必须注意其当前值,否则容易出错。
    (2)指向数组的指针变量,可以指向数组以后的内存单元,虽然没有实际意义。
    (3)对指向数组的指针变量(px和py)进行算术运算和关系运算的含义
    1)可以进行的算术运算,只有以下几种:
    px±n, px++/++px, px--/--px, px-py
    ·px±n:将指针从当前位置向前(+n)或回退(-n)n个数据单位,而不是n个字节。显然,px++/++px和px--/--px是px±n的特例(n=1)。
    ·px-py:两指针之间的数据个数,而不是指针的地址之差。
    2)关系运算
    表示两个指针所指地址之间、位置的前后关系:前者为小,后者为大。
    例如,如果指针px所指地址在指针py所指地址之前,则px〈py的值为1。
    9.3.3 再论数组作函数参数
    数组名作形参时,接收实参数组的起始地址;作实参时,将数组的起始地址传递给形参数组。
    引入指向数组的指针变量后,数组及指向数组的指针变量作函数参数时,可有4种等价形式(本质上是一种,即指针数据作函数参数):
    (1)形参、实参都用数组名
    (2)形参、实参都用指针变量
    (3)形参用指针变量、实参用数组名
    (4)形参用数组名、实参用指针变量
    9.3.4 2维数组的指针及其指针变量
    1. 2维数组的指针
    假设有如下数组定义语句: int array[3][4];
    (1)从2维数组角度看,数组名array代表数组的起始地址, 是一个以行为单位进行控制的行指针:
    ·array+i:行指针值,指向2维数组的第i行。
    ·*(array+i):(列)指针值,指向第i行第0列(控制由行转为列,但仍为指针)。
    ·*(*(array+i)):数组元素array[0]的值。
    用array作指针访问数组元素array[j]的格式:
    *(*(array+i)+j)
    注意:行指针是一个2级指针,如图9-7所示。
    (2)从1维数组角度看,数组名array和第1维下标的每一个值, 共同构成一组新的1维数组名array[0]、array[1]、array[2],它们均由4个元素组成。
    C语言规定:数组名代表数组的地址,所以array是第i行1维数组的地址, 它指向该行的第0列元素,是一个以数组元素为单位进行控制的列指针:
    ·array+j:(列)指针值,指向数组元素array[j]。
    ·*(array+j):数组元素array[j]的值。
    如果有“int array[3][4],*p=array[0];”,则p+1指向下一个元素,如图9-8所示。
    用p作指针访问数组元素array[j]的格式:
    *(p+(i*每行列数+j) )
    2.行指针变量──指向由n个元素组成的一维数组的指针变量
    (1)定义格式
    数据类型 (*指针变量)[n];
    注意:“*指针变量”外的括号不能缺,否则成了指针数组——数组的每个元素都是一个指针──指针数组(本章第6节介绍)。
    (2)赋值
    行指针变量 = 2维数组名 | 行指针变量;
    [案例9.6] 使用行指针和列指针两种方式输出2维数组的任一元素。
    (1) 使用行指针
    /*案例代码文件名:AL9_6_1.C*/
    /*程序功能:使用行指针输出2维数组的任一元素*/
    main()
    { int array[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
    int (*pointer)[4], row, col;
    pointer=array;
    printf(“Input row = ”); scanf(“%d”, &row);
    printf(“Input col = ”); scanf(“%d”, &col);
    printf(“array[%1d][%1d] = %d\n”, row, col, *(*(pointer+row)+col));
    }
    程序运行情况:
    Input row = 1←┘
    Input col = 2←┘
    array[1][2] = 7
    思考题:本题也可以直接使用数组名array作指针,应如何修改?
    (2)使用列指针
    /*案例代码文件名:AL9_6_2.C*/
    /*程序功能:使用列指针输出2维数组的任一元素*/
    main()
    { int array[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
    int *pointer, row, col; /*定义一个(列)指针变量pointer*/
    pointer=array[0]; /*给(列)指针变量pointer赋值*/
    printf(“Input row = ”); scanf(“%d”,&row);
    printf(“Input col = ”); scanf(“%d”,&col);
    printf(“array[%1d][%1d] = %d\n”, row, col, *(pointer+(row*4+col)));
    }
    3. 2维数组指针作函数参数
    2维数组的指针作函数实参时,有列指针和行指针两种形式。相应的,用来接受实参数组指针的形参,必须使用相应形式的指针变量,如下所示:
    实参: 列指针 行指针
    ↓ ↓
    形参: (列)指针变量 行指针变量
    9.3.5 动态数组的实现
    在程序运行过程中,数组的大小是不能改变的。这种数组称为静态数组。静态数组的缺点是:对于事先无法准确估计数据量的情况,无法做到既满足处理需要,又不浪费内存空间。
    所谓动态数组是指,在程序运行过程中,根据实际需要指定数组的大小。
    在C语言中,可利用内存的申请和释放库函数,以及指向数组的指针变量可当数组名使用的特点,来实现动态数组。
    动态数组的本质是:一个指向数组的指针变量。
    [案例9.7] 动态数组的实现。
    /*案例代码文件名:AL9_7.C*/
    /*程序功能:实现动态数组*/
    &#35;include “alloc.h”
    &#35;include “stdlib.h”
    main()
    { int *array=NULL, num, i;
    printf(“Input the number of element: ”); scanf(“%d”, &num);
    /*申请动态数组使用的内存块*/
    array=(int *)malloc( sizeof(int) * num );
    if ( array==NULL ) /*内存申请失败:提示,退出*/
    { printf(“out of memory, press any key to quit……”);
    exit(0); /*exit():终止程序运行,返回操作系统*/
    }
    /*提示输入num个数据*/
    printf(“Input %d elements: ”, num);
    for (i=0; i /*输出刚输入的num个数据*/
    printf(“%d elements are: ”, num);
    for (i=0; i printf(“\b ”); /*删除最后一个数据后的分隔符“,”*/
    free(array); /*释放由malloc()函数申请的内存块*/
    }
    程序运行情况:
    Input the number of element: 3←┘
    Input 3 elements: 1 2 3←┘
    3 elements are: 1,2,3
    程序说明:
    (1) array=(int *)malloc( sizeof(int) * num );语句──malloc()函数和sizeof运算符
    1)库函数malloc()
    ·用法:void *malloc(unsigned size)
    ·功能:在内存的动态存储区分配1个长度为size的连续空间。
    ·返回值:申请成功,则返回新分配内存块的起始地址;否则,返回NULL。
    ·函数原型:alloc.h,stdlib.h。
    malloc()函数的返回值是一个无类型指针,其特点是可以指向任何类型的数据。但在实际使用malloc()函数时,必须将其返回值强制转换成被赋值指针变量的数据类型,以免出错。
    2)运算符sizeof
    ·格式:sizeof(变量名/类型名)
    ·功能:求变量/类型占用的内存字节数(正整数)。例如,在IBM-PC机上,sizeof(int)=2。
    思考题:在该语句中,使用sizeof(int)求出1个int型数据占用的内存字节数,而不是使用常量“2”,为什么?
    (2) scanf(“%d”, &array);语句和printf(“%d,”, array);语句
    将指向数组的指针变量当作数组名使用,所以就必须按引用数组元素的语法规则来使用。
    (3) printf(“\b ”);语句
    “\b” 在该语句中的作用是,使光标定位到最后一个数据后的分隔符“,”上,然后再输出一个空格,以达到删除之目的。
    (4) free(array);语句──库函数free()
    ·用法:void free(void *ptr)
    ·功能:释放由ptr指向的内存块(ptr是调用malloc() 函数的返回值)。
    ·返回值:无。
    ·函数原型:stdlib.h,alloc.h。
    原则上,使用malloc()函数申请的内存块,操作结束后,应及时使用free()函数予以释放。尤其是循环使用malloc()函数时,如果不及时释放不再使用的内存块,很可能很快就耗尽系统的内存资源,从而导致程序无法继续运行。
    9.4 字符串的指针和指向字符串的指针变量
    字符串在内存中的起始地址称为字符串的指针,可以定义一个字符指针变量指向一个字符串。
    9.4.1 字符串的表示与引用
    在C语言中,既可以用字符数组表示字符串,也可用字符指针变量来表示;引用时,既可以逐个字符引用,也可以整体引用。
    1.逐个引用
    [案例9.8] 使用字符指针变量表示和引用字符串。
    /*案例代码文件名:AL9_8.C*/
    main()
    { char *string=”I love Beijing.”;
    for(; *string!=’\0’; string++) printf(“%c”, *string);
    printf(“\n”);
    }
    程序运行结果:
    I love Beijing.
    程序说明:char *string="I love Beijing.";语句
    定义并初始化字符指针变量string:用串常量“I love Beijing.”的地址(由系统自动开辟、存储串常量的内存块的首地址)给string赋初值。
    该语句也可分成如下所示的两条语句:
    char *string;
    string="I love Beijing.";
    注意:字符指针变量string中,仅存储串常量的地址,而串常量的内容(即字符串本身),是存储在由系统自动开辟的内存块中,并在串尾添加一个结束标志’\0’。
    2.整体引用
    [案例9.9] 采取整体引用的办法,改写[案例9.8]。
    /*案例代码文件名:AL9_9.C*/
    /*程序功能:使用字符指针变量表示和引用字符串*/
    main()
    { char *string=”I love Beijing.”;
    printf(“%s\n”,string);
    }
    程序说明:printf("%s\n",string);语句
    通过指向字符串的指针变量string,整体引用它所指向的字符串的原理:系统首先输出string指向的第一个字符,然后使string自动加1,使之指向下一个字符;重复上述过程,直至遇到字符串结束标志。
    注意:其它类型的数组,是不能用数组名来一次性输出它的全部元素的,只能逐个元素输出。
    例如:
    int array[10]={……};
    ......
    printf("%d\n",array); /*这种用法是非法的*/
    ......
    3.字符指针变量与字符数组之比较
    虽然用字符指针变量和字符数组都能实现字符串的存储和处理,但二者是有区别的,不能混为一谈。
    (1)存储内容不同。
    字符指针变量中存储的是字符串的首地址,而字符数组中存储的是字符串本身(数组的每个元素存放一个字符)。
    (2)赋值方式不同。
    对字符指针变量,可采用下面的赋值语句赋值:
    char *pointer;
    pointer="This is a example.";
    而字符数组,虽然可以在定义时初始化,但不能用赋值语句整体赋值。下面的用法是非法的:
    char char_array[20];
    char_array="This is a example."; /*非法用法*/
    (3)指针变量的值是可以改变的,字符指针变量也不例外;而数组名代表数组的起始地址,是一个常量,而常量是不能被改变的。
    9.4.2 字符串指针作函数参数
    [案例9.10] 用函数调用方式,实现字符串的复制。
    /*案例代码文件名:AL9_10.C*/
    /**********************************************************/
    /*string_copy()函数:复制一个字符串 */
    /*形参:字符指针str_from接收源串,字符指针 str_to存储目标串 */
    /*返回值:无 */
    /**********************************************************/
    void string_copy(char *str_from, char *str_to)
    { int i=0;
    for(; (*(str_to+i)=*(str_from+i))!=’\0’; i++) ; /*循环体为空语句*/
    }
    main()
    {char array_str1[20]=”I am a teacher.”;
    char array_str2[20];
    string_copy(array_str1, array_str2); /*数组名作实参*/
    printf(“array_str2=%s\n”, array_str2);
    }
    程序运行结果:
    I am a teacher.
    程序说明:for(; (*(str_to+i)=*(str_from+i))!=’\0’; i++) ;
    语句的执行过程为:首先将源串中的当前字符,复制到目标串中;然后判断该字符(即赋值表达式的值)是否是结束标志。如果不是,则相对位置变量i的值增1,以便复制下一个字符;如果是结束标志,则结束循环。其特点是:先复制、后判断,循环结束前,结束标志已经复制。
    在C语言中,用赋值运算符、而不是赋值语句来实现赋值操作,能给某些处理带来很大的灵活性,该语句(实现字符串的复制)的用法就是最好的例证。
    9.5 返回指针值的函数
    一个函数可以返回一个int型、float型、char型的数据,也可以返回一个指针类型的数据。 返回指针值的函数(简称指针函数)的定义格式如下:
    函数类型 *函数名([形参表])
    [案例9.11] 某数理化三项竞赛训练组有3个人,找出其中至少有一项成绩不合格者。要求使用指针函数实现。
    /*案例代码文件名AL9_11.C*/
    /*************************************************************/
    /*seek()函数:判断是否有不格成绩*/
    /*形参:指向由3个int型元素组成的1维数组的行指针变量*/
    /*返回值:(1)有不合格成绩,则返回指向本行首列的一个(列)指针*/
    /*(2)没有有不合格成绩,返回值为指向下一行的一个(列)指针*/
    /*************************************************************/
    int *seek( int (*pnt_row)[3] )
    { int i=0, *pnt_col; /*定义一个(列)指针变量pnt_col */
    pnt_col=*(pnt_row+1); /*使pnt_col指向下一行之首(作标志用)*/
    for(; i<3; i++)
    if(*(*pnt_row+i)<60) /*某项成绩不合格*/
    { pnt_col=*pnt_row; /*使pnt_col指向本行之首*/
    break; /*退出循环*/
    }
    return(pnt_col);
    }
    /*主函数main()*/
    main()
    { int grade[3][3]={{55,65,75},{65,75,85},{75,80,90}};
    int i,j,*pointer; /*定义一个(列)指针变量pointer */
    for(i=0; i<3; i++) /*控制每个学生*/
    { pointer=seek(grade+i); /*用行指针作实参,调用seek()函数*/
    if(pointer==*(grade+i)) /*该学生至少有一项成绩不合格*/
    { /*输出该学生的序号和各项成绩*/
    printf(“No.%d grade list: ”, i+1);
    for(j=0; j<3; j++) printf(“%d ”,*(pointer+j));
    printf(“\n”);
    }
    }
    }
    程序运行结果:
    No.1 grade list: 55 65 75
    程序说明:
    (1) 主函数中的pointer=seek(grade+i);语句
    调用seek()函数时,将实参grade+i(行指针)的值,复制到形参pnt_row(行指针变量)中,使形参pnt_row指向grade数组的第i行。
    (2)在指针函数seek()中:
    1) pnt_col=*(pnt_row+1);语句
    *(pnt_row+1)将行指针转换为列指针,指向grade数组的第i+1行第0列,并赋值给(列)指针变量pnt_col。
    2) if(*(*pnt_row+i)<60)行
    pnt_row是一个行指针,指向数组grade的第i行;*pnt_row使指针由行转换为列,指向数组grade的第i行0列;*pnt_row+j的值还是一个指针,指向数组的第i行第j列;*(*pnt_row+j)是一个数据(数组元素grade[j]的值)。
    9.6 指针数组与主函数main()的形参
    9.6.1 指针数组
    1.概念
    数组的每个元素都是一个指针数据。指针数组比较适合用于指向多个字符串,使字符串处理更加方便、灵活。
    2.定义格式
    数据类型 *数组名[元素个数]
    注意:与行指针变量定义格式“<数据类型>(*行指针变量)[<元素个数>]”的差别。
    [案例9.12] 有若干计算机图书,请按字母顺序,从小到大输出书名。解题要求:使用排序函数完成排序,在主函数中进行输入输出。
    /*案例代码文件名:AL9_12.C*/ /*程序功能:指针数组应用示例*/
    /***********************************************/
    /* sort()函数:对字符指针数组进行排序 */
    /*形参:name——字符指针数组,count——元素个数*/
    /*返回值:无 */
    /***********************************************/
    void sort(char *name[], int count)
    { char *temp_p;
    int i,j,min;
    /*使用选择法排序*/
    for(i=0; i { min=i; /*预置本次最小串的位置*/
    for(j=i+1; j if(strcmp(name[min],name[j])>0) /*存在更小的串*/
    min=j; /*保存之*/
    if(min!=i) /*存在更小的串,交换位置*/
    temp_p=name,name=name[min],name[min]=temp_p;
    }
    }
    /*主函数main()*/
    main()
    { char *name[5]={“BASIC”,”FORTRAN”,”PASCAL”,”C”,”FoxBASE”};
    int i=0;
    sort(name,5); /*使用字符指针数组名作实参,调用排序函数sort()*/
    /*输出排序结果*/
    for(; i<5; i++) printf(“%s\n”,name);
    }
    程序运行结果:
    BASIC
    C
    FORTRAN
    FoxBASE
    PASCAL
    程序说明:
    (1)实参对形参的值传递:
    sort( name , 5 );
    ↓ ↓
    void sort(char *name[], int count)
    (2)字符串的比较只能使用strcmp()函数。形参字符指针数组name的每个元素,都是一个指向字符串的指针,所以有strcmp(name[min],name[j])。
    9.6.2 主函数main()的形参
    在以往的程序中,主函数main()都使用其无参形式。实际上,主函数main()也是可以指定形参的。
    [案例9.13] 用同一程序实现文件的加密和解密。约定:程序的可执行文件名为lock.exe, 其用法为:lock +|- <被处理的文件名>,其中“+”为加密,“-”为解密。
    /*案例代码文件名:AL9_13.C*/
    /*程序功能:带参主函数的应用示例*/
    main(int argc, char *argv[])
    { char c;
    if (argc != 3) printf("参数个数不对!\n");
    else
    { c=*argv[1]; /*截取第二个实参字符串的第一个字符*/
    switch(c)
    { case `+`: /*执行加密*/
    { /*加密程序段*/
    printf("执行加密程序段。\n");
    }
    break;
    case `-`: /*执行解密*/
    { /*解密程序段*/
    printf("执行解密程序段。\n");
    }
    break;
    default: printf("第二个参数错误!\n");
    }
    }
    }
    1.主函数main()的有参形式
    main(int argc, char *argv[])
    { … …}
    2.实参的来源
    运行带形参的主函数,必须在操作系统状态下,输入主函数所在的可执行文件名,以及所需的实参,然后回车即可。
    命令行的一般格式为:
    可执行文件名?实参[ 实参2……]
    例如,本案例程序的用法:lock +|- <被处理的文件名>←┘
    ●在TC的集成环境下,也可直接利用Options | Arguments 项,输入主函数所需要的实参:只须输入各参数(相邻参数间用空格分开),可执行文件名可省略。
    就本案例而言,输入“+|- <被处理的文件名>”即可
    3.形参说明
    (1)形参argc是命令行中参数的个数(可执行文件名本身也算一个)。
    在本案例中,形参argc的值为3(lock、+|-、文件名)。
    (2)形参argv是一个字符指针数组,即形参argv首先是一个数组(元素个数为形参argc的值),其元素值都是指向实参字符串的指针。
    在本案例中,元素argv[0]指向第1个实参字符串“lock”,元素argv[1] 指向第2个实参字符串“+|-”,元素argv[2]指向第3个实参字符串“被处理的文件名”。
    9.6.3 指向指针的指针变量简介
    在[案例9.12]的主函数main()中,数组name是一个字符指针数组,即数组的每一个元素都是一个指向字符串的指针。
    既然name是一个数组,则它的每一个元素也同样有相应的地址,因此可以设置一个指针变量pointer,使其指向指针数组的元素(元素的值还是一个指针),称pointer为指向指针的指针变量。显然,指向指针的指针变量是一个两级的指针变量。
    1.指向指针的指针变量的定义
    数据类型 **指针变量[, **指针变量2……];
    2.指向指针的指针变量的赋值
    指针变量 = 指针数组名 + i
    9.7 函数的指针和指向函数的指针变量简介
    1.函数指针的概念 一个函数在编译时,被分配了一个入口地址,这个地址就称为该函数的指针。
    可以用一个指针变量指向一个函数,然后通过该指针变量调用此函数。
    2.指向函数的指针变量
    (1)定义格式
    函数类型 (*指针变量)( );
    注意:“*指针变量”外的括号不能缺,否则成了返回指针值的函数。
    例如,int (*fp)(); /* fp为指向int函数的指针变量*/
    (2)赋值
    函数名代表该函数的入口地址。因此,可用函数名给指向函数的指针变量赋值。 指向函数的指针变量=[&]函数名;
    注意:函数名后不能带括号和参数;函数名前的“&”符号是可选的。
    (3)调用格式
    (*函数指针变量)([实参表])
    3.指向函数的指针变量作函数参数
    指向函数的指针变量的常用用途之一,就是将函数指针作参数,传递到其它函数。
    函数名作实参时,因为要缺省括号和参数,造成编译器无法判断它是一个变量还是一个函数,所以必须加以说明。函数说明的格式,与第7章中介绍的一样。
    注意:对指向函数的指针变量,诸如p+i、p++/p--等运算是没有意义的。


    687

    主题

    2

    好友

    35万

    积分

    发表于 2004-8-21 11:24:56 |显示全部楼层

    [2月23日更新]C语言教学视频(19讲)

    第10章 结构与链表
    本章涉及到简单的数据结构
    为将不同数据类型、但相互关联的一组数据,组合成一个有机整体使用,C语言提供一种称为“结构”的数据结构。
    10.1 结构类型与结构变量的定义
    10.2 结构变量的引用与初始化
    10.3 结构数组
    10.4 指向结构类型数据的指针
    10.5 链表处理──结构指针的应用
    10.6 共用型和枚举型
    10.7 定义已有类型的别名
    10.1 结构类型与结构变量的定义
    C语言中的结构类型,相当于其它高级语言中的“记录”类型。
    10.1.1 结构类型定义
    struct 结构类型名 /* struct是结构类型关键字*/ {数据类型 数据项1; 数据类型 数据项2; …… …… 数据类型 数据项n;
    }; /* 此行分号不能少!*/
    [案例10.1] 定义一个反映学生基本情况的结构类型,用以存储学生的相关信息。 /*案例代码文件名:AL10_1.C。*/ /*功能:定义一个反映学生基本情况的结构类型*/
    struct date /*日期结构类型:由年、月、日三项组成*/
    {int year;
    int month;
    int day;
    };
    struct std_info /*学生信息结构类型:由学号、姓名、性别和生日共4项组成*/
    {char no[7];
    char name[9];
    char sex[3];
    struct date birthday;
    };
    struct score /*成绩结构类型:由学号和三门成绩共4项组成*/
    {char no[7];
    int score1;
    int score2;
    int score3;
    };
    (1)“结构类型名”和“数据项”的命名规则,与变量名相同。
    (2)数据类型相同的数据项,既可逐个、逐行分别定义,也可合并成一行定义。
    例如,本案例代码中的日期结构类型,也可改为如下形式:
    struct date
    {int year, month, day;
    };
    (3)结构类型中的数据项,既可以是基本数据类型,也允许是另一个已经定义的结构类型。
    例如,本案例代码中的结构类型std_info,其数据项“birthday”就是一个已经定义的日期结构类型date。
    (4)本教程将1个数据项称为结构类型的1个成员(或分量)。
    10.1.2 结构变量定义
    用户自己定义的结构类型,与系统定义的标准类型(int、char等)一样,可用来定义结构变量的类型。
    1.定义结构变量的方法,可概括为两种:
    (1)间接定义法──先定义结构类型、再定义结构变量
    例如,利用[案例10.1]中定义的学生信息结构类型std_info,定义了一个相应的结构变量student:
    struct std_info student;
    结构变量student:拥有结构类型的全部成员,其中birthday成员是一个日期结构类型,它又由3个成员构成。
    注意:使用间接定义法定义结构变量时,必须同时指定结构类型名。
    (2)直接定义法──在定义结构类型的同时,定义结构变量
    例如,结构变量student的定义可以改为如下形式:
    struct std_info
    {……
    } student;
    同时定义结构类型及其结构变量的一般格式如下:
    struct [结构类型名]
    { ……
    } 结构变量表;
    2.说明
    (1)结构类型与结构变量是两个不同的概念,其区别如同int类型与int型变量的区别一样。
    (2)结构类型中的成员名,可以与程序中的变量同名,它们代表不同的对象,互不干扰。

    10.2 结构变量的引用与初始化
    [案例10.2] 利用[案例10.1]中定义的结构类型struct std_info,定义一个结构变量student,用于存储和显示一个学生的基本情况。
    /*案例代码文件名:AL10_2.C*/
    &#35;include"struct.h"/*定义并初始化一个外部结构变量student */
    struct std_info
    student={"000102","张三","男",{1980,9,20}};
    main()
    { printf("No: %s\n",student.no);
    printf("Name: %s\n",student.name);
    printf("Sex: %s\n",student.sex);
    printf("Birthday: %d-%d-%d\n",student.birthday.year,
    student.birthday.month, student.birthday.day);
    }
    程序运行结果:
    No: 000102
    Name: 张三
    Sex: 男
    Birthday:1980-9-20
    1.结构变量的引用规则
    对于结构变量,要通过成员运算符“.”,逐个访问其成员,且访问的格式为:
    结构变量.成员 /*其中的“.”是成员运算符*/
    例如,案例中的student.no,引用结构变量student中的no成员;student.name引用结构变量student中的name成员,等等。
    如果某成员本身又是一个结构类型,则只能通过多级的分量运算,对最低一级的成员进行引用。
    此时的引用格式扩展为:
    结构变量.成员.子成员.….最低1级子成员
    例如,引用结构变量student中的birthday成员的格式分别为:
    student.birthday.year
    student.birthday.month
    student.birthday.day
    (1)对最低一级成员,可像同类型的普通变量一样,进行相应的各种运算。
    (2)既可引用结构变量成员的地址,也可引用结构变量的地址。
    例如,&student.name ,&student 。
    2.结构变量的初始化
    结构变量初始化的格式,与一维数组相似:
    结构变量={初值表}
    不同的是:如果某成员本身又是结构类型,则该成员的初值为一个初值表。
    例如,[案例10.2]中的student={"000102", "张三", "男", {1980,9,20}}。
    注意:初值的数据类型,应与结构变量中相应成员所要求的一致,否则会出错。
    10.3 结构数组
    结构数组的每一个元素,都是结构类型数据,均包含结构类型的所有成员。
    [案例10.3] 利用[案例10.1]中定义的结构类型struct std_info,定义一个结构数组student,用于存储和显示三个学生的基本情况。
    /*案例代码文件名:AL10_3.C*/
    &#35;include"struct.h"/*定义并初始化一个外部结构数组student[3] */struct std_info student[3]={{“000102”,“张三”,“男”,{1980,9,20}}, {“000105”,“李四”,“男”,{1980,8,15}}, {“000112”,“王五”,“女”,{1980,3,10}} };
    /*主函数main()*/
    main()
    { int i;
    /*打印表头: " □"表示1个空格字符*/
    printf("No.□□□□Name□□□□□Sex□Birthday\n");
    /*输出三个学生的基本情况*/
    for(i=0; i<3; i++)
    { printf("%-7s",student.no);
    printf("%-9s",student.name);
    printf("%-4s",student.sex);
    printf("%d-%d-%d\n",student.birthday.year,
    student.birthday.month, student.birthday.day);
    }
    }
    程序运行结果:
    No. Name Sex Birthday
    000102 张三 男 1980-9-20
    000105 李四 男 1980-8-15
    000112 王五 女 1980-3-10
    与结构变量的定义相似,结构数组的定义也分直接定义和间接定义两种方法,只需说明为数组即可。
    与普通数组一样,结构数组也可在定义时进行初始化。初始化的格式为:
    结构数组[n]={{初值表1},{初值表2},...,{初值表n}}
    例如,本案例中的结构数组student[3]。

    10.4 指向结构类型数据的指针
    结构变量在内存中的起始地址称为结构变量的指针。
    10.4.1 指向结构变量的指针 [案例10.4] 使用指向结构变量的指针来访问结构变量的各个成员。
    /*案例代码文件名:AL10_4.C*/
    &#35;include“struct.h”
    struct std_info student={“000102”,“张三”,“男”{1980,9,20}};
    main()
    { struct std_info *p_std=&student;
    printf("No: %s\n", p_std->no);
    printf("Name: %s\n", p_std->name);
    printf("Sex: %s\n", p_std->sex);
    printf("Birthday: %d-%d-%d\n", p_std->birthday.year,
    p_std->birthday.month, p_std->birthday.day);
    }
    通过指向结构变量的指针来访问结构变量的成员,与直接使用结构变量的效果一样。一般地说,如果指针变量pointer已指向结构变量var,则以下三种形式等价:
    (1)var.成员
    (2)pointer->成员
    (3)(*pointer).成员 /* “*pointer”外面的括号不能省!*/
    注意:在格式(1)中,分量运算符左侧的运算对象,只能是结构变量,;而在格式(2)中,指向运算符左侧的运算对象,只能是指向结构变量(或结构数组)的指针变量,否则都出错。
    思考题:如果要求从键盘上输入结构变量student的各成员数据,如何修改程序?
    10.4.2 指向结构数组的指针
    [案例10.5] 使用指向结构数组的指针来访问结构数组。
    /*案例代码文件名:AL10_5.C*/
    &#35;include"struct.h"
    /*定义并初始化一个外部结构数组student */
    struct std_info student[3]={{"000102","张三","男",{1980,5,20}},
    {"000105","李四","男",{1980,8,15}},
    {“000112”,“王五”,“女”,{1980,3,10}} };
    main()
    { struct std_info *p_std=student;
    int i=0;
    /*打印表头*/
    printf("No.□□□□Name□□□□□Sex□Birthday\n");
    /*输出结构数组内容*/
    for( ; i<3; i++, p_std++)
    { printf("%-7s%-9s%-4s", p_std->no, p_std->name, p_std->sex);
    printf("%4d-%2d-%2d\n", p_std->birthday.year,
    p_std->birthday.month, p_std->birthday.day);
    }
    }
    如果指针变量p已指向某结构数组,则p+1指向结构数组的下一个元素,而不是当前元素的下一个成员。
    另外,如果指针变量p已经指向一个结构变量(或结构数组),就不能再使之指向结构变量(或结构数组元素)的某一成员。
    10.4.3 指向结构数据的指针作函数参数
    [案例10.6] 用函数调用方式,改写[案例10.5]:编写一个专门的显示函数display(),通过主函数调用来实现显示。
    /*案例代码文件名:AL10_6.C*/
    &#35;include"struct.h"
    /*定义并初始化一个外部结构数组student */
    struct std_info student[3]={{"000102","张三","男",{1980,5,20}},
    {"000105","李四","男",{1980,8,15}},
    {“000112”,“王五”,“女”,{1980,3,10}} };
    /*主函数main()*/
    main()
    { void display(); /*函数说明*/
    int i=0;
    /*打印表头*/
    printf("No.□□□□Name□□□□□Sex□Birthday\n");
    /*打印内容*/
    for( ; i<3; i++)
    { display( student + i );
    printf("\n");
    }
    }
    void display(struct std_info *p_std)
    { printf("%-7s%-9s%-4s", p_std->no,
    p_std->name, p_std->sex);
    printf("%4d-%2d-%2d\n", p_std->birthday.year,
    p_std->birthday.month, p_std->birthday.day);
    }
    10.5 链表处理──结构指针的应用
    10.5.1 概述
    1.链表结构
    链表作为一种常用的、能够实现动态存储分配的数据结构,在《数据结构》课程中有详细介绍。为方便没有学过数据结构的读者,本书从应用角度,对链表作一简单介绍。图10-1所示为单链表。
    (1)头指针变量head──指向链表的首结点。
    (2)每个结点由2个域组成:
    1)数据域──存储结点本身的信息。
    2)指针域──指向后继结点的指针。
    (3)尾结点的指针域置为“NULL(空)”,作为链表结束的标志。
    2.对链表的基本操作
    对链表的基本操作有:创建、检索(查找)、插入、删除和修改等。
    (1)创建链表是指,从无到有地建立起一个链表,即往空链表中依次插入若干结点,并保持结点之间的前驱和后继关系。
    (2)检索操作是指,按给定的结点索引号或检索条件,查找某个结点。如果找到指定的结点,则称为检索成功;否则,称为检索失败。
    (3)插入操作是指,在结点ki-1与ki之间插入一个新的结点k’,使线性表的长度增1,且ki-1与ki的逻辑关系发生如下变化:
    插入前,ki-1是ki的前驱,ki是ki-1的后继;插入后,新插入的结点k’成为ki-1的后继、ki的前驱,如图10-2所示。
    (4)删除操作是指,删除结点ki,使线性表的长度减1,且ki-1、ki和ki+1之间的逻辑关系发生如下变化:
    删除前,ki是ki+1的前驱、ki-1的后继;删除后,ki-1成为ki+1的前驱,ki+1成为ki-1的后继,如图10-3所示。
    3.C语言对链表结点的结构描述
    在C语言中,用结构类型来描述结点结构。例如:
    struct grade
    { char no[7]; /*学号*/
    int score; /*成绩*/
    struct grade *next; /*指针域*/
    };
    10.5.2 创建一个新链表
    [案例10.7] 编写一个create()函数,按照规定的结点结构,创建一个单链表(链表中的结点个数不限)。
    基本思路:
    首先向系统申请一个结点的空间,然后输入结点数据域的(2个)数据项,并将指针域置为空(链尾标志),最后将新结点插入到链表尾。对于链表的第一个结点,还要设置头指针变量。
    另外,案例代码中的3个指针变量head、new和tail的说明如下:
    (1)head──头指针变量,指向链表的第一个结点,用作函数返回值。
    (2)new──指向新申请的结点。
    (3)tail──指向链表的尾结点,用tail->next=new,实现将新申请的结点,插入到链表尾,使之成为新的尾结点。
    /*案例代码文件名:AL10_7.C*/
    &#35;define NULL 0
    &#35;define LEN sizeof(struct grade) /*定义结点长度*/
    /*定义结点结构*/
    struct grade
    { char no[7]; /*学号*/
    int score; /*成绩*/
    struct grade *next; /*指针域*/
    };
    /*create()函数: 创建一个具有头结点的单链表*/
    /*形参:无*/
    /*返回值:返回单链表的头指针*/
    struct grade *create( void )
    { struct grade *head=NULL, *new, *tail;
    int count=0; /*链表中的结点个数(初值为0)*/
    for( ; ; ) /*缺省3个表达式的for语句*/
    { new=(struct grade *)malloc(LEN); /*申请一个新结点的空间*/
    /*1、输入结点数据域的各数据项*/
    printf("Input the number of student No.%d(6 bytes): ", count+1);
    scanf("%6s", new->no);
    if(strcmp(new->no,"000000")==0) /*如果学号为6个0,则退出*/
    { free(new); /*释放最后申请的结点空间*/
    break; /*结束for语句*/
    }
    printf("Input the score of the student No.%d: ", count+1);
    scanf("%d", &new->score);
    count++; /*结点个数加1*/
    /*2、置新结点的指针域为空*/
    new->next=NULL;
    /*3、将新结点插入到链表尾,并设置新的尾指针*/
    if(count==1) head=new; /*是第一个结点, 置头指针*/
    else tail->next=new; /*非首结点, 将新结点插入到链表尾*/
    tail=new; /*设置新的尾结点*/
    }
    return(head);
    }
    思考题:在设计存储学号数据的字符数组时,其元素个数应为学号长度+1。为什么?
    10.5.3 对链表的插入操作
    [案例10.8] 编写一个insert()函数,完成在单链表的第i个结点后插入1个新结点的操作。当i=0时,表示新结点插入到第一个结点之前,成为链表新的首结点。
    基本思路:
    通过单链表的头指针,首先找到链表的第一个结点;然后顺着结点的指针域找到第i个结点,最后将新结点插入到第i个结点之后。
    /*案例代码文件名:AL10_8.C*/
    /*函数功能:在单链表的第i个结点后插入1个新结点*/
    /*函数参数:head为单链表的头指针,new指向要插入的新结点,i为结点索引号*/
    /*函数返回值:单链表的头指针*/
    struct grade *insert(struct grade *head, struct grade *new, int i)
    { struct grade *pointer;
    /*将新结点插入到链表中*/
    if(head==NULL) head=new, new->next=NULL; /*将新结点插入
    到1个空链表中*/
    else /*非空链表*/
    if(i==0) new->next=head, head=new; /*使新结点成为 链表
    新的首结点*/
    else /*其他位置*/
    { pointer=head;
    /*查找单链表的第i个结点(pointer指向它)*/
    for(; pointer!=NULL && i>1; pointer=pointer->next, i--) ;
    if(pointer==NULL) /*越界错*/
    printf("Out of the range, can’t insert new node!\n");
    else /*一般情况:pointer指向第i个结点 */
    new->next=pointer->next, pointer->next=new;
    }
    return(head);
    }
    10.6 共用型和枚举型简介
    10.6.1 共用型
    1.概念
    使几个不同的变量占用同一段内存空间的结构称为共用型。
    2.共用类型的定义──与结构类型的定义类似
    union 共用类型名
    {成员列表;};
    3.共用变量的定义──与结构变量的定义类似
    (1)间接定义──先定义类型、再定义变量
    例如,定义data共用类型变量un1,un2,un3的语句如下: union data un1,un2,un3;
    (2)直接定义──定义类型的同时定义变量
    例如,union [data]
    { int i;
    char ch;
    float f;
    } un1, un2, un3;
    共用变量占用的内存空间,等于最长成员的长度,而不是各成员长度之和。
    例如,共用变量un1、un2和un3,在16位操作系统中,占用的内存空间均为4字节(不是2+1+4=7字节)。
    4.共用变量的引用──与结构变量一样,也只能逐个引用共用变量的成员
    例如,访问共用变量un1各成员的格式为:un1.i、un1.ch、un1.f。
    5.特点
    (1)系统采用覆盖技术,实现共用变量各成员的内存共享,所以在某一时刻,存放的和起作用的是最后一次存入的成员值。
    例如,执行un1.i=1, un1.ch=`c`, un1.f=3.14后,un1.f才是有效的成员。
    (2)由于所有成员共享同一内存空间,故共用变量与其各成员的地址相同。
    例如,&un1=&un1.i=&un1.ch=&un1.f。
    (3)不能对共用变量进行初始化(注意:结构变量可以);也不能将共用变量作为函数参数,以及使函数返回一个共用数据,但可以使用指向共用变量的指针。
    (4)共用类型可以出现在结构类型定义中,反之亦然。
    10.6.2 枚举型
    1.枚举类型的定义
    enum 枚举类型名 {取值表};
    例如,enum weekdays {Sun,Mon,Tue,Wed,Thu,Fri,Sat};
    2.枚举变量的定义──与结构变量类似
    (1)间接定义
    例如,enum weekdays workday;
    (2)直接定义
    例如,enum [weekdays]
    {Sun,Mon,Tue,Wed,Thu,Fri,Sat } workday;
    3.说明
    (1)枚举型仅适应于取值有限的数据。
    例如,根据现行的历法规定,1周7天,1年12个月。
    (2)取值表中的值称为枚举元素,其含义由程序解释。
    例如,不是因为写成“Sun”就自动代表“星期天”。事实上, 枚举元素用什么表示都可以。
    (3)枚举元素作为常量是有值的──定义时的顺序号(从0开始),所以枚举元素可以进行比较,比较规则是:序号大者为大!
    例如,上例中的Sun=0、Mon=1、……、Sat=6,所以Mon>Sun、Sat最大。
    (4)枚举元素的值也是可以人为改变的:在定义时由程序指定。
    例如,如果enum weekdays {Sun=7, Mon=1 ,Tue, Wed, Thu, Fri, Sat};则Sun=7,Mon=1,从Tue=2开始,依次增1。
    10.7 定义已有类型的别名
    除可直接使用C提供的标准类型和自定义的类型(结构、共用、枚举)外,也可使用typedef定义已有类型的别名。该别名与标准类型名一样,可用来定义相应的变量。定义已有类型别名的方法如下:
    (1)按定义变量的方法,写出定义体;
    (2)将变量名换成别名;
    (3)在定义体最前面加上typedef。
    [案例10.9] 给实型float定义1个别名REAL。
    (1)按定义实型变量的方法,写出定义体:float f;
    (2)将变量名换成别名: float REAL;
    (3)在定义体最前面加上typedef:typedef float REAL;
    [案例10.10] 给如下所示的结构类型struct date定义1个别名DATE。
    struct date
    { int year, month, day;
    };
    (1)按定义结构变量的方法,写出定义体:struct date {……} d;
    (2)将变量名换成别名: struct date {……} DATE;
    (3)在定义体最前面加上typedef: typedef struct date {……} DATE;
    说明:
    (1)用typedef只是给已有类型增加1个别名,并不能创造1个新的类型。就如同人一样,除学名外,可以再取一个小名(或雅号),但并不能创造出另一个人来。
    (2)typedef与&#35;define有相似之处,但二者是不同的:前者是由编译器在编译时处理的;后者是由编译预处理器在编译预处理时处理的,而且只能作简单的字符串替换。

    687

    主题

    2

    好友

    35万

    积分

    发表于 2004-8-21 11:26:26 |显示全部楼层

    [2月23日更新]C语言教学视频(19讲)

    第11章 位运算
    为了节省内存空间,在系统软件中常将多个标志状态简单地组合在一起,存储到一个字节(或字)中。C语言是为研制系统软件而设计的,所以她提供了实现将标志状态从标志字节中分离出来的位运算功能。
    所谓位运算是指,按二进制位进行的运算。
    11.1 数值在计算机中的表示
    11.2 位运算
    11.3 位段
    11.1 数值在计算机中的表示
    1.二进制位与字节 计算机系统的内存储器,是由许多称为字节的单元组成的,1个字节由8个二进制位(bit)构成,每位的取值为0/1。最右端的那1位称为“最低位”,编号为0;最左端的那1位称为“最高位”,而且从最低位到最高位顺序,依次编号。图11-1是1个字节各二进制位的编号。
    |7|6|5|4|3|2|1|0|
    图11-1 1个字节各二进制位的编号
    2.数值的原码表示
    数值的原码表示是指,将最高位用作符号位(0表示正数,1表示负数),其余各位代表数值本身的绝对值(以二进制形式表示)的表示形式。为简化描述起见,本节约定用1个字节表示1个整数。
    例如,+9的原码是00001001
    └→符号位上的0表示正数
    -9的原码是10001001。
    └→符号位上的1表示负数
    3.数值的反码表示
    数值的反码表示分两种情况:
    (1)正数的反码:与原码相同。
    例如,+9的反码是00001001。
    (2)负数的反码:符号位为1,其余各位为该数绝对值的原码按位取反(1变0、0变1)。
    例如,-9的反码:因为是负数,则符号位为“1”;其余7位为-9的绝对值+9的原码0001001按位取反为1110110,所以-9的反码是11110110。
    4.数值的补码表示
    数值的补码表示也分两种情况:
    (1)正数的补码:与原码相同。
    例如,+9的补码是00001001。
    (2)负数的补码:符号位为1,其余位为该数绝对值的原码按位取反;然后整个数加1。
    例如,-9的补码:因为是负数,则符号位为“1”;其余7位为-9的绝对值+9的原码0001001按位取反为1110110;再加1,所以-9的补码是11110111。
    已知一个数的补码,求原码的操作分两种情况:
    (1)如果补码的符号位为“0”,表示是一个正数,所以补码就是该数的原码。
    (2)如果补码的符号位为“1”,表示是一个负数,求原码的操作可以是:符号位不变,其余各位取反,然后再整个数加1。
    例如,已知一个补码为11111001,则原码是10000111(-7):因为符号位为“1”,表示是一个负数,所以该位不变,仍为“1”;其余7位1111001取反后为0000110;再加1,所以是10000111。
    5.数值在计算机中的表示──补码
    在计算机系统中,数值一律用补码表示(存储),原因在于:使用补码,可以将符号位和其它位统一处理;同时,减法也可按加法来处理。另外,两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃。
    11.2 位 运 算
    11.2.1 位运算及其运算符
    1.按位与──&
    (1)格式:x&y
    (2)规则:对应位均为1时才为1,否则为0:3&9=1。
    例如,3&9=1: 0011
    & 1001
    ────
    0001=1
    (3)主要用途:取(或保留)1个数的某(些)位,其余各位置0。
    2.按位或──|
    (1)格式:x|y
    (2)规则:对应位均为0时才为0,否则为1:3|9=11。
    例如,3|9=11: 0011
    | 1001
    ────
    1011=11
    (3)主要用途:将1个数的某(些)位置1,其余各位不变。
    3.按位异或──^
    (1)格式:x^y
    (2)规则:对应位相同时为0,不同时为1:3^9=10。
    (3)主要用途:使1个数的某(些)位翻转(即原来为1的位变为0,为0的变为1),其余各位不变。
    4.按位取反──~
    (1)格式:~x
    (2)规则:各位翻转,即原来为1的位变成0,原来为0的位变成1:在IBM-PC机中,~0=0xffff,~9=0xfff6。
    (3)主要用途:间接地构造一个数,以增强程序的可移植性。
    5.按位左移──<<
    (1)格式:x<< 位数
    (2)规则:使操作数的各位左移,低位补0,高位溢出:5<<2=20。
    6.按位右移──>>
    (1)格式:x>>位数
    (2)规则:使操作数的各位右移,移出的低位舍弃;高位:
    1)对无符号数和有符号中的正数,补0;
    2)有符号数中的负数,取决于所使用的系统:补0的称为“逻辑右移”,补1的称为“算术右移”。例如,20 >> 2=5。
    说明:
    (1)x、y和“位数”等操作数,都只能是整型或字符型数据。除按位取反为单目运算符外,其余均为双目运算符。
    (2)参与运算时,操作数x和y,都必须首先转换成二进制形式,然后再执行相应的按位运算。
    例如,5<<2=20:0101 → 10100,20 >> 2=5:10100 → 00101。
    (3)实现&、|、^运算主要用途的方法
    1)构造1个整数:该数在要取(或保留)的位、或要置1的位、或要翻转的位上为1,其余均为0。
    2)进行按位与、或按位或、或按位异或操作。
    (4)实现按位取反主要用途的方法
    1)求~0,间接地构造一个全1的数;
    2)按需要进行左移或右移操作,构造出所需要的数。
    例如,直接构造一个全1的数,在IBM-PC机中为0xffff(2字节),而在VAX-11/780上,却是0xffffffff(4字节)。如果用~0来构造,系统可以自动适应。具体应用,请参见[案例11.1]。
    11.2.2 应用举例
    [案例11.1] 从键盘上输入1个正整数给int变量num,输出由8~11位构成的数(从低位、0号开始编号)。
    基本思路:
    (1)使变量num右移8位,将8~11位移到低4位上。
    (2)构造1个低4位为1、其余各位为0的整数。
    (3)与num进行按位与运算。
    /*案例代码文件名:AL11_1.C*/
    /*程序功能:输出一个整数中由8~11位构成的数*/
    main()
    { int num, mask;
    printf("Input a integer number: ");
    scanf("%d",&num);
    num >>= 8; /*右移8位,将8~11位移到低4位上*/
    mask = ~ ( ~0 << 4); /*间接构造1个低4位为1、其余各位为0的整数*/
    printf("result=0x%x\n", num & mask);
    }
    程序运行情况:
    Input a integer number:1000 ←┘
    result=0x3
    程序说明:~ ( ~0 << 4)
    按位取0的反,为全1;左移4位后,其低4位为0,其余各位为1;再按位取反,则其低4位为1,其余各位为0。这个整数正是我们所需要的。
    [案例11.2] 从键盘上输入1个正整数给int变量num,按二进制位输出该数。
    /*案例代码文件名:AL11_2.C*/
    /*程序功能:按二进制位输出一个整数*/
    &#35;include "stdio.h"
    main()
    { int num, mask, i;
    printf("Input a integer number: ");
    scanf("%d",&num);
    mask = 1<<15; /*构造1个最高位为1、其余各位为0的整数(屏蔽字)*/
    printf("%d=" , num);
    for(i=1; i<=16; i++)
    { putchar(num&mask ? ’1’ : ‘0’); /*输出最高位的值(1/0)*/
    num <<= 1; /*将次高位移到最高位上*/
    if( i%4==0 ) putchar(‘,’); /*四位一组,用逗号分开*/
    }
    printf("\bB\n");
    }
    程序运行情况:
    Input a integer number:1000 ←┘
    1000=0000,0011,1110,1000B
    11.2.3 说明
    1.复合赋值运算符
    除按位取反运算外,其余5个位运算符均可与赋值运算符一起,构成复合赋值运算符: &=、|+、^=、<<=、>>=
    2.不同长度数据间的位运算──低字节对齐,短数的高字节按最高位补位:
    (1)对无符号数和有符号中的正数,补0;
    (2)有符号数中的负数,补1。
    11.3 位段简介
    有时,存储1个信息不必占用1个字节,只需二进制的1个(或多个)位就够用。如果仍然使用结构类型,则造成内存空间的浪费。为此,C语言引入了位段类型。
    1. 位段的概念与定义
    所谓位段类型,是一种特殊的结构类型,其所有成员均以二进制位为单位定义长度,并称成员为位段。
    例如,CPU的状态寄存器,按位段类型定义如下:
    struct status
    { unsigned sign: 1; /*符号标志*/
    unsigned zero: 1; /*零标志*/
    unsigned carry: 1; /*进位标志*/
    unsigned parity: 1; /*奇偶/溢出标志*/
    unsigned half_carry: 1; /*半进位标志*/
    unsigned negative: 1; /*减标志*/
    } flags;
    显然,对CPU的状态寄存器而言,使用位段类型(仅需1个字节),比使用结构类型(需要6个字节)节省了5个字节。
    2.说明
    (1)因为位段类型是一种结构类型,所以位段类型和位段变量的定义,以及对位段(即位段类型中的成员)的引用,均与结构类型和结构变量一样。
    (2)对位段赋值时,要注意取置范围。一般地说,长度为n的位段,其取值范围是:0~(2n-1)。
    (3)使用长度为0的无名位段,可使其后续位段从下1个字节开始存储。
    例如,
    struct status
    { unsigned sign: 1; /*符号标志*/
    unsigned zero: 1; /*零标志*/
    unsigned carry: 1; /*进位标志*/
    unsigned : 0; /*长度为0的无名位段*/
    unsigned parity: 1; /*奇偶/溢出标志*/
    unsigned half_carry: 1; /*半进位标志*/
    unsigned negative: 1; /*减标志*/
    } flags;
    原本6个标志位是连续存储在1个字节中的。由于加入了1个长度为0的无名位段,所以其后的3个位段,从下1个字节开始存储,一共占用2个字节。
    (4)1个位段必须存储在1个存储单元(通常为1字节)中,不能跨2个。如果本单元不够容纳某位段,则从下1个单元开始存储该位段。
    (5)可以用%d、%x、%u和%o等格式字符,以整数形式输出位段。
    (6)在数值表达式中引用位段时,系统自动将位段转换为整型数。

    687

    主题

    2

    好友

    35万

    积分

    发表于 2004-8-21 11:28:52 |显示全部楼层

    [2月23日更新]C语言教学视频(19讲)

    第12章 文 件
    在程序运行时,程序本身和数据一般都存放在内存中。当程序运行结束后,存放在内存中的数据被释放。
    如果需要长期保存程序运行所需的原始数据,或程序运行产生的结果,就必须以文件形式存储到外部存储介质上。
    12.1 C语言文件概述
    12.2 文件的打开与关闭
    12.3 文件的读写操作
    12.4 位置指针与文件定位
    12.5 出错检测
    12.1 C语言文件概述
    1.文件与文件名
    文件是指存放在外部存储介质上的数据集合。
    为标识一个文件,每个文件都必须有一个文件名,其一般结构为:主文件名[.扩展名]
    文件命名规则,遵循操作系统的约定。
    2.文件分类
    可以从不同的角度对文件进行分类:
    (1)根据文件的内容,可分为程序文件和数据文件,程序文件又可分为源文件、目标文件和可执行文件。
    (2)根据文件的组织形式,可分为顺序存取文件和随机存取文件。
    (3)根据文件的存储形式,可分为ASCII码文件和二进制文件。
    ASCII码文件的每1个字节存储1个字符,因而便于对字符进行逐个处理。但一般占用存储空间较多,而且要花费转换时间(二进制与ASCII码之间的转换)。
    二进制文件是把内存中的数据,原样输出到磁盘文件中。可以节省存储空间和转换时间,但1个字节并不对应1个字符,不能直接输出字符形式。
    3.读文件与写文件
    所谓读文件是指,将磁盘文件中的数据传送到计算机内存的操作。
    所谓写文件是指,从计算机内存向磁盘文件中传送数据的操作。
    4.构成文件的基本单元与流式文件
    C语言将文件看作是由一个一个的字符(ASCII码文件)或字节(二进制文件)组成的。将这种文件称为流式文件。
    而在其它高级语言中,组成文件的基本单位是记录,对文件操作的基本单位也是记录。
    5.文件类型FILE
    系统给每个打开的文件都在内存中开辟一个区域,用于存放文件的有关信息(如文件名、文件位置等)。这些信息保存在一个结构类型变量中,该结构类型由系统定义、取名为FILE。
    注意:结构类型名“FILE”必须大写。
    6.ANSI C的缓冲文件系统
    所谓缓冲文件系统是指,系统自动地在内存区为每个正在使用的文件开辟一个缓冲区。
    从内存向磁盘输出数据时,必须首先输出到缓冲区中。待缓冲区装满后,再一起输出到磁盘文件中。
    从磁盘文件向内存读入数据时,则正好相反:首先将一批数据读入到缓冲区中,再从缓冲区中将数据逐个送到程序数据区。

    12.2 文件的打开与关闭
    了标准输入输出函数库,用fopen()函数打开一个文件,用fclose()函数关闭一个文件。
    12.2.1 文件的打开──fopen对文件进行操作之前,必须先打开该文件;使用结束后,应立即关闭,以免数据丢失。
    C语言规定()函数
    1.用法: FILE *fopen("文件名","操作方式");
    2.功能:返回一个指向指定文件的指针。
    3.函数原型:stdio.h 。
    注:对文件操作的库函数,函数原型均在头文件stdio.h中。后续函数不再赘述。
    (1)“文件名”是指要打开(或创建)的文件名。如果使用字符数组(或字符指针),则不使用双引号。
    (2)“操作方式”如表12-1所示。
    例如,FILE *fp;
    fp=fopen("data.99","r");
    3.说明
    (1)如果不能实现打开指定文件的操作,则fopen()函数返回一个空指针NULL (其值在头文件stdio.h中被定义为0)。
    为增强程序的可靠性,常用下面的方法打开一个文件:
    if((fp=fopen("文件名","操作方式"))==NULL)
    { printf("can not open this file\n");
    exit(0);
    }
    ●关于exit()函数
    1)用法:void exit([程序状态值]);
    2)功能:关闭已打开的所有文件,结束程序运行,返回操作系统,并将“程序状态值”返回给操作系统。当“程序状态值”为0时,表示程序正常退出;非0值时,表示程序出错退出。
    (2)“r(b)+”与“a(b)+”的区别:使用前者打开文件时,读写位置指针指向文件头;使用后者时,读写指针指向文件尾。
    (3)使用文本文件向计算机系统输入数据时,系统自动将回车换行符转换成一个换行符;在输出时,将换行符转换成回车和换行两个字符。
    使用二进制文件时,内存中的数据形式与数据文件中的形式完全一样,就不再进行转换。
    (4)有些C编译系统,可能并不完全提供上述对文件的操作方式,或采用的表示符号不同,请注意所使用系统的规定。
    (5)在程序开始运行时,系统自动打开三个标准文件,并分别定义了文件指针:
    1)标准输入文件——stdin:指向终端输入(一般为键盘)。如果程序中指定要从stdin所指的文件输入数据,就是从终端键盘上输入数据。
    2)标准输出文件——stdout:指向终端输出(一般为显示器)。
    3)标准错误文件——stderr:指向终端标准错误输出(一般为显示器)。
    12.2.2 文件的关闭──fcolse()函数
    1.用法: int fclose(FILE *文件指针);
    2.功能:关闭“文件指针”所指向的文件。如果正常关闭了文件,则函数返回值为0;否则,返回值为非0。
    例如,fclose(fp);/*关闭fp所指向的文件*/
    12.3 文件的读写操作
    文件打开之后,就可以对它进行读与写的操作了。
    12.3.1 读/写文件中的一个字符
    12.3.2 读/写一个字符串
    12.3.3 读/写一个数据块
    12.3.4 对文件进行格式化读/写
    12.3.5 读/写函数的选用原则
    12.3.1 读/写文件中的一个字符
    1.将一个字符写到文件中──fputc()函数 [案例12.1] 将键盘上输入的一个字符串(以“@”作为结束字符),以ASCII码形式存储到一个磁盘文件中。
    /*案例代码文件名:AL12_1.C*/
    /*程序功能:从键盘上输入一个字符串,存储到一个磁盘文件中*/
    /*使用格式:可执行文件名 要创建的磁盘文件名*/
    &#35;include “stdio.h”
    main(int argc, char *argv[])
    { FILE *fp;
    char ch;
    if(argc!=2) /*参数个数不对*/
    { printf("the number of arguments not correct\n\n");
    printf(“Usage: 可执行文件名 filename \n”);
    exit(0);
    }
    if ((fp=fopen(argv[1],"w"))==NULL) /*打开文件失败*/
    { printf("can not open this file\n");
    exit(0);
    }
    /*输入字符,并存储到指定文件中*/
    for( ; (ch=getchar()) != `@` ; )
    fputc(ch,fp); /*输入字符并存储到文件中*/
    fclose(fp); /*关闭文件*/
    }
    程序运行情况:
    abcdefg1234567@←┘
    库函数fputc():
    1)用法:int fputc(字符数据,文件指针);
    其中“字符数据”,既可以是字符常量,也可以是字符变量。
    2)功能:将字符数据输出到“文件指针”所指向的文件中去,同时将读写位置指针向前移动1个字节(即指向下一个写入位置)。
    如果输出成功,则函数返回值就是输出的字符数据;否则,返回一个符号常量EOF(其值在头文件stdio.h中,被定义为-1)。
    2.从文件中读入一个字符──fgetc()函数和feof()函数
    [案例12.2] 顺序显示[案例12.1]创建的磁盘ASCII码文件。
    /*案例代码文件名:AL12_2.C*/
    /*程序功能:顺序显示一个磁盘ASCII码文件*/
    /*参数:带参主函数,使用格式:可执行文件名 源文件名*/
    &#35;include "stdio.h"
    main(int argc, char *argv[])
    { FILE *fp;
    char ch;
    if(argc!=2) /*参数个数不对*/
    { printf("the number of arguments not correct\n");
    printf(“\n Usage: 可执行文件名 源文件名");
    exit(0);
    }
    if ((fp=fopen(argv[1],"r"))==NULL)
    { printf("can not open source file\n");
    exit(0);
    }
    /*顺序输出文件的内容*/
    for(; (ch=fgetc(fp))!=EOF; )
    putchar(ch); /*顺序读入并显示*/
    fclose(fp); /*关闭打开的文件*/
    }
    程序运行情况:
    abcdefg1234567
    (1)库函数fgetc()
    1)用法:int fgetc(文件指针);
    2)功能:从“文件指针”所指向的文件中,读入一个字符,同时将读写位置指针向前移动1个字节(即指向下一个字符)。该函数无出错返回值。
    例如,fgetc(fp)表达式,从文件fp中读一个字符,同时将fp的读写位置指针向前移动到下一个字符。
    (2)关于符号常量EOF
    在对ASCII码文件执行读入操作时,如果遇到文件尾,则读操作函数返回一个文件结束标志EOF(其值在头文件stdio.h中被定义为-1)。
    在对二进制文件执行读入操作时,必须使用库函数feof()来判断是否遇到文件尾。
    [案例12.3] 实现制作ASCII码文件副本的功能。
    /*案例代码文件名:AL12_2.C*/
    /*程序功能:制作ASCII码文件的副本*/
    /*使用格式:可执行文件名 源文件名 目标文件名*/
    &#35;include "stdio.h"
    main(int argc, char *argv[])
    { FILE *input, *output; /* input:源文件指针, output:目标文件指针 */
    char ch;
    if(argc!=3) /*参数个数不对*/
    { printf("the number of arguments not correct\n");
    printf("\n Usage: 可执行文件名 source-file dest-file");
    exit(0);
    }
    if ((fp=fopen(argv[1],"r"))==NULL) /*打开源文件失败*/
    { printf("can not open source file\n");
    exit(0);
    }
    if ((fp=fopen(argv[2],"w"))==NULL) /*创建目标文件失败*/
    { printf("can not create destination file\n");
    exit(0);
    }
    /*复制源文件到目标文件中*/
    for( ; (!feof(input)) ; ) fputc(fgetc(input),output);
    fclose(input); fclose(output); /*关闭源文件和目标文件*/
    }
    库函数feof(): 1)用法:int feof(文件指针); 2)功能:在执行读文件操作时,如果遇到文件尾,则函数返回逻辑真(1);否则,则返回逻辑假(0)。feof()函数同时适用于ASCII码文件和二进制文件。 例如,!feof(input))表示源文件(用于输入)未结束,循环继续。
    12.3.2 读/写一个字符串──fgets()和fputs()
    [案例12.4] 将键盘上输入的一个长度不超过80的字符串,以ASCII码形式存储到一个磁盘文件中;然后再输出到屏幕上。
    /*案例代码文件名:AL12_4.C*/
    /*参数:可执行文件名 要创建的磁盘文件名*/
    &#35;include "stdio.h"
    main(int argc, char *argv[])
    { FILE *fp;
    char string[81]; /*字符数组用于暂存输入输出的字符串*/
    if(argc>2) /*参数太多,提示 出错*/
    { printf("Too many parameters…\n\n");
    printf("Usage: 可执行文件名 filename\n");
    exit(0);
    }
    if(argc= =1) /*缺磁盘文件名,提示输入*/
    { printf("Input the filename: ");
    gets(string); /*借用string暂存输入的文件名*/
    argv[1]=(char *)malloc(strlen(string)+1);/*给文件名参数申请内存空间*/
    strcpy(argv[1],string);/*复制文件名到形参中*/
    }
    if ((fp=fopen(argv[1],"w"))==NULL) /*打开文件失败*/
    { printf("can not open this file\n");
    exit(0);
    }
    /*从键盘上输入字符串,并存储到指定文件中*/
    printf("Input a string: "); gets(string); /*从键盘上输入字符串*/
    fputs(string, fp); /*存储到指定文件*/
    fclose(fp);
    /*重新打开文件,读出其中的字符串,并输出到屏幕上*/
    if ((fp=fopen(argv[1],"r"))==NULL) /*打开文件失败*/
    { printf("can not open this file\n");
    exit(0);
    }
    fgets(string, strlen(string)+1, fp); /*从文件中读一个字符串*/
    printf("Output the string: "); puts(string); /*将字符串输出到屏幕上*/
    fclose(fp);
    }
    (1)为增强程序的可靠性,程序中对参数过多的情况,提示出错、并终止程序运行;而遗漏文件名时,提示用户输入。
    同时,为增强程序的人机交互性,凡是需要用户输入数据的地方,都设置提示输入的信息;凡是输出数据的地方,都设置输出说明信息。
    (2) 库函数fputs()──向指定文件输出一个字符串
    1)用法:int fputs(字符串,文件指针);
    其中“字符串”可以是一个字符串常量,或字符数组名,或字符指针变量名。
    2)功能:向指定文件输出一个字符串,同时将读写位置指针向前移动strlength(字符串长度)个字节。如果输出成功,则函数返回值为0;否则,为非0值。
    (3) 库函数fgets()──从文件中读一个字符串
    1)用法:char *fgets(指针,串长度+1,文件指针);
    2)功能:从指定文件中读入一个字符串,存入“字符数组/指针”中,并在尾端自动加一个结束标志`\0`;同时,将读写位置指针向前移动strlength(字符串长度)个字节。
    如果在读入规定长度之前遇到文件尾EOF或换行符,读入即结束。
    12.3.3 读/写一个数据块──fread()和fwrite()
    实际应用中,常常要求1次读/写1个数据块。为此,ANSI C 标准设置了 fread( ) 和fwrite()函数。
    1.用法:
    int fread(void *buffer,int size,int count,FILE *fp);
    int fwrite(void *buffer,int size,int count,FILE *fp);
    2.功能:
    fread()──从fp所指向文件的当前位置开始,一次读入size个字节,重复count次,并将读入的数据存放到从buffer开始的内存中;同时,将读写位置指针向前移动size* count个字节。
    其中,buffer是存放读入数据的起始地址(即存放何处)。
    fwrite()──从buffer开始,一次输出size个字节,重复count次, 并将输出的数据存放到fp所指向的文件中;同时,将读写位置指针向前移动size* count个字节。
    其中,buffer是要输出数据在内存中的起始地址(即从何处开始输出)。
    如果调用fread()或fwrite()成功,则函数返回值等于count。
    fread()和fwrite()函数,一般用于二进制文件的处理。
    12.3.4 对文件进行格式化读/写──fscanf()和fprintf()函数
    与scanf()和printf()函数的功能相似,区别在于:fscanf()和fprintf()函数的操作对象是指定文件,而scanf()和printf()函数的操作对象是标准输入(stdin)输出(stdout)文件。
    int fscanf(文件指针,"格式符",输入变量首地址表);
    int fprintf(文件指针,"格式符",输出参量表);
    例如,......
    int i=3; float f=9.80;
    ......
    fprintf(fp,"%2d,%6.2f", i, f);
    ......
    fprintf()函数的作用是,将变量i按%2d格式、变量f按%6.2f格式, 以逗号作分隔符,输出到fp所指向的文件中:□3,□□9.80(□表示1个空格)。
    12.3.5 读/写函数的选用原则
    从功能角度来说,fread()和fwrite()函数可以完成文件的任何数据读/写操作。 但为方便起见,依下列原则选用:
    1.读/写1个字符(或字节)数据时:选用fgetc()和fputc()函数。
    2.读/写1个字符串时:选用fgets()和fputs()函数。
    3.读/写1个(或多个)不含格式的数据时:选用fread()和fwrite()函数。
    4.读/写1个(或多个)含格式的数据时:选用fscanf()和fprintf()函数。
    12.4 位置指针与文件定位
    文件中有一个读写位置指针,指向当前的读写位置。每次读写1个(或1组)数据后,系统自动将位置指针移动到下一个读写位置上。
    如果想改变系统这种读写规律,可使用有关文件定位的函数。
    12.4.1 位置指针复位函数rewind()
    1.用法:int rewind(文件指针);
    2.功能:使文件的位置指针返回到文件头。
    12.4.2 随机读写与fseek()函数
    对于流式文件,既可以顺序读写,也可随机读写,关键在于控制文件的位置指针。
    所谓顺序读写是指,读写完当前数据后,系统自动将文件的位置指针移动到下一个读写位置上。
    所谓随机读写是指,读写完当前数据后,可通过调用fseek()函数,将位置指针移动到文件中任何一个地方。
    1.用法:int fseek(文件指针,位移量,参照点);
    2.功能:将指定文件的位置指针,从参照点开始,移动指定的字节数。
    (1)参照点:用0(文件头)、1(当前位置)和2(文件尾)表示。
    在ANSI C标准中,还规定了下面的名字:
    SEEK_SET──文件头,
    SEEK_CUR──当前位置,
    SEEK_END──文件尾
    (2)位移量:以参照点为起点,向前(当位移量>0时)或后(当位移量<0时)移动的字节数。在ANSI C标准中,要求位移量为long int型数据。
    fseek()函数一般用于二进制文件。
    12.4.3 返回文件当前位置的函数ftell()
    由于文件的位置指针可以任意移动,也经常移动,往往容易迷失当前位置,ftell()就可以解决这个问题。
    1.用法:long ftell(文件指针);
    2.功能:返回文件位置指针的当前位置(用相对于文件头的位移量表示)。
    如果返回值为-1L,则表明调用出错。例如:
    offset=ftell(fp);
    if(offset= =-1L)printf(“ftell() error\n”);
    12.5 出错检测
    12.5.1 ferror()函数 在调用输入输出库函数时,如果出错,除了函数返回值有所反映外,也可利用ferror()函数来检测。
    1.用法: int ferror(文件指针);
    2.功能:如果函数返回值为0,表示未出错;如果返回一个非0值,表示出错。
    (1)对同一文件,每次调用输入输出函数均产生一个新的ferror()函数值。因此在调用了输入输出函数后,应立即检测,否则出错信息会丢失。
    (2)在执行fopen()函数时,系统将ferror()的值自动置为0。
    12.5.2 clearerr()函数
    1.用法: void clearerr(文件指针);
    2.功能:将文件错误标志(即ferror()函数的值)和文件结束标志(即feof()函数的值)置为0。
    对同一文件,只要出错就一直保留,直至遇到clearerr()函数或rewind()函数,或其它任何一个输入输出库函数。

    136

    主题

    9

    好友

    29万

    积分

    Member

    发表于 2004-8-21 13:58:50 |显示全部楼层

    [2月23日更新]C语言教学视频(19讲)

    太多了,缺氧!

    687

    主题

    2

    好友

    35万

    积分

    发表于 2004-8-21 14:10:57 |显示全部楼层

    [2月23日更新]C语言教学视频(19讲)

    这个是书的提纲,我还没有讲解,先占个位置

    136

    主题

    9

    好友

    29万

    积分

    Member

    发表于 2004-8-21 19:56:31 |显示全部楼层

    [2月23日更新]C语言教学视频(19讲)

    最好像刚开始那样,让我们能容易接受。

    687

    主题

    2

    好友

    35万

    积分

    发表于 2004-8-22 10:13:17 |显示全部楼层

    [2月23日更新]C语言教学视频(19讲)

    恩。好的

    687

    主题

    2

    好友

    35万

    积分

    发表于 2004-8-22 11:53:04 |显示全部楼层

    [2月23日更新]C语言教学视频(19讲)

    作业:
    尝试运行TC,并输入运行以下程序
    &#35;include <stdio.h>
    main()
    {
    int a;
    float b;
    double c;
    printf("please input abc:");
    scanf("%d%f%e",&a,&b,&c);
    printf("%d%f%e\n",a,b,c);
    printf("a=%d,b=%f,c=%e\n",a,b,c);
    getch();
    }

    687

    主题

    2

    好友

    35万

    积分

    发表于 2004-8-22 11:53:31 |显示全部楼层

    [2月23日更新]C语言教学视频(19讲)

    [这个贴子最后由没完没了在 2004/08/22 09:33pm 第 1 次编辑]

    687

    主题

    2

    好友

    35万

    积分

    发表于 2004-8-22 15:43:22 |显示全部楼层

    [2月23日更新]C语言教学视频(19讲)

    [这个贴子最后由没完没了在 2004/08/22 09:45pm 第 1 次编辑]

    1

    136

    主题

    9

    好友

    29万

    积分

    Member

    发表于 2004-8-23 06:42:41 |显示全部楼层

    [2月23日更新]C语言教学视频(19讲)

    看到!

    687

    主题

    2

    好友

    35万

    积分

    发表于 2004-8-23 10:32:27 |显示全部楼层

    [2月23日更新]C语言教学视频(19讲)

    [这个贴子最后由没完没了在 2004/08/23 04:32pm 第 1 次编辑]

    [color=&#35;DC143C]更正一些错误
    直接按下F9键可以对文件完成编译和连接的操作,生成.EXE文件但并不运行程序
    按下ctrl+F9可以一次完成编译,连接 生成.EXE文件。同时运行程序.
    按下Alt+F9的功能是不进行时间和日期检查的编译,生成.obj文件。(一个操作)

    [color=&#35;DC143C]应为我们老师的失误造成了我理解的失误,给大家带来了错误的解释,实在抱歉

    136

    主题

    9

    好友

    29万

    积分

    Member

    发表于 2004-8-25 12:21:53 |显示全部楼层

    [2月23日更新]C语言教学视频(19讲)

    今天试了几个程序,感觉还不错,只是有时少输入行后面的分号而经常出错:)
    您需要登录后才可以回帖 登录 | 注册

    Archiver|手机版|人教网 ( 京ICP备05019902号   

    GMT+8, 2017-9-26 09:44

    回顶部