发新话题
打印

C语言教学视频(19讲)

本主题由 小草帽 于 2007-4-6 14:32 加入精华

[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("lease 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("lease 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("lease 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)输出部分。
    良 好 的 源 程 序 书 写 风 格──顺序程序段左对齐
    顺序程序段中的所有语句(包括说明语句),一律与本顺序程序段的首行左对齐。
  • TOP

    [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。*//*功能:求一元二次方程的解。*/ #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: /*该入口值的含义*/ 语句组; } 如果条件成立时(或入口值)的含义,已经很明确了,也可不再加以注释。

    TOP

    [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(循环条件); /*循环继续条件的含义*/ 如果循环嵌套,还应说明每层循环各控制什么。

    TOP

    [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个整数,用冒泡法按从小到大地排序,并在屏幕上显示出来。*/ #include "stdio.h" #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%的比例计算总评成绩,并在屏幕上显示出来。按空格键继续循环,其他键终止循环。*/ #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数组赋值,并在屏幕上显示出来。*/ #define Row 2 #define Col 3 #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*/ /*功能:计算个人平均成绩与各科平均成绩,并在屏幕上显示出来。*/ #define NUM_std 5 /*定义符号常量人数为5*/ #define NUM_course 4 /*定义符号常量课程为4*/ #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*/ /*功能:简单密码检测程序*/ #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)函数功能:将字符串中小写字母转换成大写,其它字符(包括大写字母和非字母字符)不转换。

    TOP

    [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*/ /*功能:函数的嵌套调用*/ #define K 4 #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”在不同的地方所起的作用是不同的。

    TOP

    [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.无参宏定义的一般格式 #define 标识符 语言符号字符串 其中:“define”为宏定义命令;“标识符”为所定义的宏名,通常用大写字母表示,以便于与变量区别;“语言符号字符串”可以是常数、表达式、格式串等。 2.使用宏定义的优点 (1)可提高源程序的可维护性 (2)可提高源程序的可移植性 (3)减少源程序中重复书写字符串的工作量 [案例8.1] 输入圆的半径,求圆的周长、面积和球的体积。要求使用无参宏定义圆周率。 /*案例代码文件名:AL8_1.C*/ /*程序功能:输入圆的半径,求圆的周长、面积和球的体积。 */ #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)宏定义命令#define出现在函数的外部,宏名的有效范围是:从定义命令之后, 到本文件结束。通常,宏定义命令放在文件开头处。 (5)在进行宏定义时,可以引用已定义的宏名 。 (6)对双引号括起来的字符串内的字符,即使与宏名同名,也不进行宏展开。 8.1.2 符号常量 在定义无参宏时,如果“语言符号字符串”是一个常量,则相应的“宏名”就是一个符号常量。 恰当命名的符号常量,除具有宏定义的上述优点外,还能表达出它所代表常量的实际含义,从而增强程序的可读性。 #define EOF -1 /*文件尾*/ #define NULL 0 /*空指针*/ #define MIN 1 /*极小值*/ #define MAX 31 /*极大值*/ #define STEP 2 /*步长*/ 8.1.3 有参宏定义 1.带参宏定义的一般格式 #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)编译预处理时,预处理程序将查找指定的被包含文件,并将其复制到#include命令出现的位置上。 (2)常用在文件头部的被包含文件,称为“标题文件”或“头部文件”,常以“h”(head)作为后缀,简称头文件。在头文件中,除可包含宏定义外,还可包含外部变量定义、结构类型定义等。 (3)一条包含命令,只能指定一个被包含文件。如果要包含n个文件,则要用n条包含命令。 (4)文件包含可以嵌套,即被包含文件中又包含另一个文件。 8.3 条件编译 条件编译可有效地提高程序的可移植性,并广泛地应用在商业软件中,为一个程序提供各种不同的版本。 8.3.1 #ifdef ~ #endif和#ifndef ~ #endif命令 8.3.2 #if ~ #endif 8.3.1 #ifdef ~ #endif和#ifndef ~ #endif命令 1.一般格式 #ifdef 标识符 程序段1; [#else 程序段2;] #endif 2.功能:当“标识符”已经被#define命令定义过,则编译程序段1,否则编译程序段2。 (1)在不同的系统中,一个int 型数据占用的内存字节数可能是不同的。 (2)利用条件编译,还可使同一源程序即适合于调试(进行程序跟踪、打印较多的状态或错误信息),又适合高效执行要求。 3.关于#ifndef ~ #endif命令 格式与#ifdef ~ #endif命令一样,功能正好与之相反。 8.3.2 #if ~ #endif 1.一般格式 #if 常量表达式 程序段1; [#else 程序段2;] #endif 2.功能:当表达式为非0(“逻辑真”)时,编译程序段1,否则编译程序段2。 [案例8.2] 输入一个口令,根据需要设置条件编译,使之能将口令原码输出,或仅输出若干星号“*”。 /*案例代码文件名:AL8_2.C*/ #define PASSWORD 0 /*预置为输出星号*/ main() { …… /*条件编译*/ #if PASSWORD /*源码输出*/ …… #else /*输出星号*/ …… #endif …… } 下一章,我们将讲述指针。届时欢迎阅读。

    TOP

    [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_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*/ /*程序功能:实现动态数组*/ #include “alloc.h” #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--等运算是没有意义的。

    TOP

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