1、第第9章章 C语言与程序设计补遗语言与程序设计补遗 9.1 变量的存储类别与生命期9.2 指向函数的指针变量9.3 带参数的主函数main9.4 编译预处理命令9.5 枚举类型9.6 位运算 9.1 变量的存储类别与生命期变量的存储类别与生命期 1.生命期的概念生命期的概念从变量生命期(即由创建到撤消)来分,可以将变量分为静态存储变量和动态存储变量两类:(1)静态存储变量:在程序运行时固定分配存储空间的变量。(2)动态存储变量:在程序运行中根据需要动态分配存储空间的变量。程序运行时对应的内存分配示意如图9-1所示。图9-1 程序运行时对应的内存分配示意全局变量和静态局部变量(static变量)
2、存放在静态数据区,程序开始执行时给它们分配内存单元,程序执行结束时再释放这些内存单元。也即在程序的整个执行过程中这些变量都存在(有自己的内存单元),它们的生命期为程序的整个执行过程。动态数据区存放自动局部变量、形参变量和用于中断现场的保护数据。自动局部变量是指未加staic声明的局部变量;形参变量是指函数的形参。在函数调用时为自动局部变量和形参变量在动态数据区分配内存单元,当函数执行结束时释放这些内存单元。也即在函数的整个执行过程中这些变量都存在,它们的生命期为函数的整个执行过程。在C语言中,每个变量都有两个属性:数据类型和数据的存储类别。前面各章节中,我们在定义变量时只涉及它的数据类型,其实
3、还可以定义变量的存储类别,它决定这个变量的存放位置(是静态数据区还是动态数据区)和生命期。变量定义的一般形式如下:存储类别 类型标识符 变量名;其中,方括号“”中的内容为可选项。C语言中的变量可以有4种存储类别:自动变量、寄存器变量、静态变量和外部变量,分别用存储类别auto、register、static和extern。下面仅对自动变量、寄存器变量和静态变量进行介绍。2.自动变量在函数体内或复合语句内定义变量时,如果没有指定存储类别或使用了“auto”存储类别,则系统都认为所定义的变量为自动局部变量,简称为自动变量。此外,函数首部中的形参也是自动变量。例如:auto int a=2,b;in
4、t a=2,b;上述两种定义方法是等价的,即都定义了a和b为自动变量。每当进入函数体或复合语句时,系统在动态数据区为自动变量分配临时内存单元,退出时自动释放这些内存单元;再次进入函数或复合语句时,系统又为它们重新分配临时内存单元,退出时又自动释放这些内存单元。因此,释放后自动变量的值不可能保留,这类变量的作用域及生命期只存在于定义它的函数体内或复合语句内。自动变量在动态数据区分配内存单元,并随着程序的运行可能不断释放和重新分配内存单元,也即这个内存单元的位置是不固定的,因此自动变量中的值也会随之改变。所以,自动变量在使用之前必须赋值,否则它的值是不确定的。此外,在不同函数中使用的同名自动变量也
5、不会相互影响。例9.1 分析下面程序的运行结果。#includevoid fun();void main()fun();fun();void fun()int n=2;/*自动变量*/n+;printf(n=%dn,n);解 在程序中,函数fun中定义的n为自动变量,其作用域只在函数fun内。第一次调用fun时,为n分配临时内存单元且n的初值为2,执行“n+;”后n值为3,因此输出结果为3;第一次调用fun结束,此时分配给n的内存单元被释放。第二次调用fun时,又为n重新分配了内存单元,函数fun的执行过程与第一次一样,因此输出的结果仍是3。程序执行的动态图如图9-2所示。图9-2 程序执行的
6、动态图程序执行后的输出结果为:n=3n=33.寄存器变量寄存器变量也是自动变量,它与一般自动变量的区别在于,寄存器变量的值是存储于CPU内的寄存器中,而一般的自动变量则存储于内存中。由于从寄存器中读取数据要比从内存中读取数据的速度快,所以为了提高运算速度,可以将一些频繁使用的局部变量或形参变量定义为寄存器变量。寄存器变量只要在定义时加上存储类别register即可。例如:register int a;使用寄存器变量时要注意以下几点:(1)寄存器变量本身是一个自动变量,因此只有函数内定义的变量或形参才可以定义为寄存器变量。(2)CPU中的寄存器个数有限,所以只能将少数的变量定义为寄存器变量。(3
7、)受寄存器长度的限制,寄存器变量只能是char、int和指针类型的变量。(4)由于寄存器变量是保存在CPU的寄存器中而不是保存在内存中,因此不能进行取地址运算。(5)在调用函数时,函数中的寄存器变量才占用寄存器存放其值,当函数调用结束时就释放寄存器,也即寄存器变量消失。例9.2 编写求n!的程序。解 程序如下:#includelong fac(int n);void main()int n;long f;printf(Input n=);scanf(%d,&n);f=fac(n);printf(%d!=%ldn,n,f);long fac(int n)register long t=1;reg
8、ister int k;for(k=2;k=n;k+)t=t*k;return(t);运行结果:Input n=55!=120在程序中,由于函数fac中的变量t和k频繁使用,故将其定义为寄存器变量。4.静态变量静态变量的存储空间为内存中的静态数据区,该区域中的数据在整个程序运行期间一直占用分配给它们的存储空间,直到整个程序结束。特别要注意的是,函数体内如果在定义静态变量(称为局部静态变量)的同时进行了初始化,则以后程序不再对其进行初始化操作。这是由于第一次遇见局部静态变量时,系统即为局部静态变量分配了专用的内存单元并将初始化值送入这个内存单元,此后该局部静态变量就一直使用这个内存单元,而无论函
9、数的调用或结束;也即,下一次函数调用时,这个局部静态变量仍然使用这个内存单元,而且并不重新初始化。这样,局部静态变量就可以保存前一次函数调用得到的值而用于下一次函数调用;这一点是局部静态变量与自动变量的本质区别。局部静态变量的初值是在程序编译时赋予的,在程序执行过程中不再赋值。对没有赋初值的局部静态变量,编译系统自动给它赋初值0。auto型局部变量与static型局部变量的区别如表9.1所示。表表9.1 auto型局部变量与型局部变量与static型局部变量的区别型局部变量的区别例9.3 分析下面程序的运行结果。#includevoid fun();void main()fun();fun()
10、;void fun()static int n=2;/*局部静态变量*/n+;printf(n=%dn,n);解 在程序中,函数fun中定义的n为局部静态变量;其作用域只在函数fun内。在程序执行开始前,系统已为n分配了内存单元且n的初值为2,第一次函数fun调用时,执行“n+;”后n值为3,输出3;第一次调用fun函数结束,但系统分配给n的内存单元并不释放。第二次调用fun时,执行“n+;”后n值由3变为4(注意,不执行对静态局部变量的初始化操作static int n=2),故输出结果为4。所以,程序执行后的输出结果为:n=3n=4 例9.4 分析下面程序的运行结果。#includeint
11、 fun(int x,int y);void main()int j=4,m=1,k;k=fun(j,m);printf(%d,k);k=fun(j,m);printf(%dn,k);int fun(int x,int y)static int m=0,i=2;i=i+m+1;m=i+x+y;return m;解 程序执行的动态图如图9-3所示。图9-3 程序执行的动态图由于静态局部变量在每次函数调用结束时并不消失,所以在动态图中,我们将静态局部变量m和i放置于函数fun空间的开始处,并且当函数fun结束时,它们仍然存在(在动态图上是用一条横线将它们与局部自动变量分开,且这条线一直持续到程序结
12、束)。在下一次调用函数fun时,仍可使用它们的值。由动态图可知,程序的运行结果为:8,17。9.2 指向函数的指针变量指向函数的指针变量在C语言中,一个函数所对应的程序代码总是存放在一段连续的内存区域内,并且像数组名代表数组的首地址一样,函数名就代表该函数代码所占内存区域的首地址。不同的函数有不同的首地址。因此,函数名可以看做为是一个广义的变量,我们可以把这个“变量”函数名(函数的首地址)赋给一个指针变量,使该指针变量指向这个函数,然后通过指针变量就可以找到并且调用执行这个函数。这种指向函数的指针变量就称为“函数指针变量”。1.函数指针变量的定义与初始化函数指针变量定义的一般形式为类型说明符(
13、*指针变量名)();其中,类型说明符表示被指向的那个函数的返回值类型,“(*指针变量名)”表示“*”后面的变量名是一个指针变量,最后的空括号“()”表示这个指针变量的指向是一个函数。例如:int(*p)();定义了p是一个指向函数的指针变量,该函数的返回值为整型。p是用来存放函数的入口地址的,在没有赋值前它不指向任何一个具体函数,并具有一个空指针值。想要通过函数指针变量来实现对某个函数的调用,还必须对函数指针变量进行初始化,即将需要调用的某个函数入口地址赋给它;由于函数名代表该函数的入口地址,因此是将某个函数名赋给函数指针变量。函数指针变量初始化的方式有两种:一种是直接赋值;一种是加地址运算符
14、“&”赋值。格式如下:函数指针变量名=函数名;或者函数指针变量名=&函数名;也可以在函数指针变量定义时进行初始化:类型说明符(*指针变量名)(形式参数表)=函数名;或者类型说明符(*指针变量名)(形式参数表)=&函数名;函数的指针变量与普通指针变量都能实现间接访问,其唯一的区别是:普通指针变量指向的是内存的数据存储区,而函数的指针变量指向的是内存的程序代码区。因此,普通指针变量的“*”运算是访问内存中的数据,而函数的指针变量执行“*”运算时,其结果是使程序控制转移到由函数指针变量所指向的函数入口地址,并开始执行该函数。此外,形式参数表也与第5章函数中的形式参数表不同,只能给出形参的类型。2.用
15、函数指针变量调用函数定义了函数指针变量并初始化后,就可以在程序中通过函数指针变量来调用所需要的函数了。调用函数的一般形式为(*指针变量名)(实参表)由于优先级不同,所以“*指针变量名”必须用圆括号“()”括起来,表示间接调用指针变量所指向的函数,而后面的圆括号“()”中的内容为传递给被调函数的实参。例9.5 用函数指针调用函数的方式实现在两数中找出最大数的程序。#includeint max(int a,int b);void main()int(*p)(int,int);int x,y,z;p=max;printf(Input two numbers:n);scanf(%d,%d,&x,&y
16、);z=(*p)(x,y);printf(max=%dn,z);int max(int a,int b)if(ab)return(a);elsereturn(b);运行结果:Input two numbers:88,66max=88从程序中可以看出,用函数指针变量形式调用函数的步骤如下:(1)先定义函数指针变量;如程序中的“int(*p)(int,int);语句”。(2)将被调函数的入口地址(即函数名)赋给函数指针变量;如程序中的“p=max;”语句。(3)用函数指针变量形式来调用函数;如程序中的“z=(*p)(x,y);”语句。例9.6 分析下面程序运行的结果。#includefloat f
17、1(float n);float f2(float n);void main()float(*p1)(float),(*p2)(float),(*t)(float),y1,y2;p1=f1;p2=f2;y1=p2(p1(2.0);t=p1;p1=p2;p2=t;y2=p2(p1(2.0);printf(%3.0f,%3.0fn,y1,y2);float f1(float n)return n*n;float f2(float n)return 2*n;解 程序中,函数f1实现的是返回参数值的平方值,函数f2实现的是返回参数值的2倍。在主函数main中定义了三个函数指针变量p1、p2和t,语句“
18、p1=f1;p2=f2;”让函数指针变量p1指向函数f1,函数指针变量p2指向函数f2,然后调用“p2(p1(2.0)”,即先让2.0平方后再乘以2,即结果为8并赋给变量y1。接下来,语句“t=p1;p1=p2;p2=t;”交换了p1和p2的指向,即此时p1指向f2,p2指向f1。再次调用“p2(p1(2.0)”,则是先让2.0乘以2然后再平方,即结果为16并赋给y2。因此,最后输出的y1和y2值为:8,16。使用函数指针变量还应注意以下几点:(1)函数指针变量不能进行算术运算,这与数组指针变量不同;数组指针变量加减一个整数可以使指针移向后面或前面的数组元素,而函数指针的移动则毫无意义。(2)
19、函数指针变量定义时“(*指针变量名)”的圆括号“()”不能缺省,有了括号指针变量名先和“*”结合,表示定义的变量名是一个指针变量。如果缺省了圆括号“()”,即如下面所示:int*p(int,int);则表示定义的p是一个函数;p为函数名,其前面的“*”表示函数p是返回指针值的函数,也即意思和功能完全不同了。(3)要注意函数指针变量与指向二维数组的指针变量之间的区别。如:“int(*p)(int);”和“int(*p)4;”,前者是函数指针变量,后者是指向二维数组的指针变量。3.函数的指针变量作为函数的参数函数的形参可以是各种类型的变量,也可以是指向函数的指针变量。形参是指向函数的指针变量时可以
20、接受实参传来的不同函数,这种参数传递不是传递任何数据或普通变量的地址,而是传递函数的入口地址。当函数参数在两个函数之间传递时,调用函数的实参应该是被调函数的函数名,而被调函数的形参应该是接受函数地址的函数指针变量。例9.7 任意输入两个整数,求它们的和、差、积、商。解 程序设计如下:#includeint add(int x,int y);int sub(int x,int y);int mul(int x,int y);int div(int x,int y);int fun(int(*p)(int,int),int x,int y);void main()int a,b;printf(In
21、put a,b=);scanf(%d,%d,&a,&b);printf(%d+%d=%dn,a,b,fun(add,a,b);printf(%d-%d=%dn,a,b,fun(sub,a,b);printf(%d*%d=%dn,a,b,fun(mul,a,b);printf(%d/%d=%dn,a,b,fun(div,a,b);int add(int x,int y)return x+y;int sub(int x,int y)return x-y;int mul(int x,int y)return x*y;int div(int x,int y)return x/y;int fun(int
22、(*p)(int,int),int x,int y)int z;z=(*p)(x,y);return z;运行结果:Input a,b=12,412+4=1612-4=812*4=4812/4=3程序中的add、sub、mul和div是已经定义过的函数,函数fun中的形参p是函数指针变量,主函数main调用fun函数:fun(add,a,b)则将函数add的入口地址传给了函数指针变量p,而a、b值分别传给了形参x、y;即函数fun中的语句“z=(*p)(x,y);”此时相当于语句“z=add(x,y);”,从而实现了对a与b的求和;其他函数调用也是如此。9.3 带参数的主函数带参数的主函数ma
23、in 前面各章介绍的主函数main都是不带参数的,因此main后的括号是空括号“()”。实际上主函数main是可以带参数的,这个参数可以认为是主函数main的形参。C语言规定主函数main的参数只能有两个:argc和argv,并且第一个形参argc必须是整型变量,第二个形参argv必须是指向字符串的指针数组。也即,主函数main的一般形式为:void main(int argc,char*argv)函数体其中,argc称做参数计数器,它的值是包括命令名在内的参数个数,因此其值至少为1;argv指针数组的作用是存放命令行中命令名及每个参数字符串的首地址。由于主函数main是最先执行的,因此不可能
24、在程序内部获得实参值。所以,主函数main的实参值是从操作系统的命令行上获得的。当需要运行一个可执行文件时,在DOS提示符下键入文件名及实参值,即可把这些实参传给main的形参。带参数的主函数main的调用形式如下:可执行文件名 参数1 参数2 参数n上面这一行字符称为命令行,是在DOS系统提示符下键入的。其中,可执行文件名称为命令名,其后的参数称为命令行参数,命令名与各参数之间用空格分隔。例如:c:file1 China Beijing由于命令名file1本身也算是一个参数,所以共有三个参数,因此argc的值取3,argv指针数组中元素的值为命令行中各字符串(参数均按字符串处理)的首地址。指
25、针数组的大小即为参数个数;数组元素的初值由系统自动赋予。上述命令行参数在赋给带参主函数main中的argv指针数组后示意见图9-4。图9-4 带参主函数main中的argv示意例如,在c盘根目录下的文件file1.c的内容如下:#includevoid main(int argc,char*argv)while(argc1)+argv;printf(%sn,*argv);argc-;用VC+6.0中的工具栏Build中的Build命令先将file1.c生成可执行文件file1.exe(保存于Debug子目录中),其后再将file1.exe由Debug目录移至c盘根目录下,然后点击桌面上“开始”
26、按钮中的运行框并输入。c:file1 China Beijing则输出为:ChinaBeijing例9.8 有以下程序:#include#includevoid main(int argc,char*argv)int i,len=0;for(i=1;iargc;i=i+2)len=len+strlen(argvi);printf(%dn,len);经编译链接后生成的可执行文件是ex.exe,若运行时输入以下带参数的命令行:ex abcd efg h3 k44则执行后输出的结果是 。A)14 B)12 C)8 D)6 解 本题的argv示意如图9-5所示,argc的值为5。在主函数main中,f
27、or循环执行了两次;当i=1时,len=0+strlen(argv1),argv1=abcd;故此时len值为4。当i=3时,len=4+strlen(argv3),其中argv3=h3,故此时len值为6。当i=5时,退出循环。所以最后输出的len值为6,即应选D项。图9-5 argv示意9.4 编译预处理命令编译预处理命令 预处理是C语言所具有的一种对源程序处理的功能。所谓预处理,就是指在正常编译之前对源程序进行预先处理。也即,源程序在正常编译之前先执行源程序中的预处理命令进行预处理,然后再编译源程序。通常把预处理看做编译的一部分,是编译中最先执行的部分。预处理功能包含了一组预处理命令,不
28、同的预处理命令实现不同的功能;最常使用的是文件包含命令和宏定义,在调试程序时也可使用条件编译命令。预处理命令都是以“#”开头,每个预处理命令必须单独占一行,并且未尾不加分号(这就是命令与语句的区别)。预处理命令可以出现在程序的任何地方,但一般都将预处理命令放在源程序的首部,其作用域从预处理命令在程序中该命令说明的位置开始到程序的结束。预处理命令参数是一种替换功能,这种替换功能只是简单的替代,不做语法检查;例如,宏定义就是用定义的字符串来替代宏名;又如,文件包含命令就是一个用文件内容替代被包含的文件名;这样的替代目的是为了使程序的书写更加简洁。C语言提供的预处理命令有宏定义、文件包含和条件编译。
29、下面只对宏定义和文件包含这两个命令进行介绍。9.4.1 宏定义命令宏定义命令C语言中,允许用一个标识符来表示一个字符串,称之为宏。宏是一种编译预处理命令,被定义为宏的标识符称为宏名。在编译预处理时,程序中所有出现的宏名都用宏定义中的字符串去替换,称为宏代换或宏展开。注意,宏定义是由程序中的宏定义命令完成的,而进行宏代换的操作则是由预处理程序在编译之前自动完成的,根据是否带参数将宏定义分为不带参的宏定义和带参宏定义。1.不带参数的宏定义不带参数的宏定义一般形式如下:#define 标识符 字符串其中,define是关键字,它表示宏定义命令;标识符为所定义的宏名,它的写法应符合C语言标识符的规则;
30、字符串可以是常数、表达式及格式串等。例如:#define PI 3.14159#define SUM 10+20#define NL printf(n)#define TRUE 1这里,PI、SUM、NL、TRUE都是宏名,而3.14159、10+20、printf(n)和1都是被定义的字符串。宏定义是将PI、SUM、NL、TRUE分别定义为3.14159、10+20、printf(n)和1;替换时,将程序中出现的PI、SUM、NL和TRUE分别用对应的字符串替换。例9.9 利用如下公式计算圆周率的近似值,直到最后一项的绝对值小于(=10-7)。解 程序如下:#include#include#
31、define Epsilon 1e-7void main()int i,m=1,sign=1;double pi=4,t=4;for(i=1;fabs(t)Epsilon;i+)sign=-sign;m+=2;t=sign*4.0/m;pi+=t;printf(pi=%fn,pi);运行结果:pi=3.1415932.带参数的宏定义带参数的宏定义的一般形式如下:#define 标识符(形参表)字符串其中,括号“()”中的形参表由一个或多个形参组成,当形参多于一个时,形参之间用逗号隔开,对带参数的宏展开也是用字符串替换宏名,而形参则被对应的实参替换,其他的字符仍然保留在字符串内。带参宏调用的一般
32、形式如下:宏名(实参表)例如:#define s(a,b)a*bc=s(x+y,x-y);宏调用时,用x+y替换形参a,用x-y替换形参b,其余字符不变,即“*”仍保留在字符串内。经预处理宏展开后的语句为:c=x+y*x-y;注意,定义带参数的宏时,宏名与括号“()”之间不得有空格。带参的宏和函数有相似之处,但它们在本质上是不同的:(1)函数调用时先求实参表达式的值,然后将该值传递给对应的形参;而带参数的宏展开时,只是用实参字符串替换对应的形参。(2)函数调用是在程序运行中进行的,当调用到这个函数时才为函数的形参分配临时内存单元并接受实参的值;而宏展开是在编译之前进行的,不分配内存单元也不进行
33、值传递,更没有返回值。(3)函数中要求实参和形参都要定义数据类型,且二者的类型应一致,否则应进行类型转换;而宏名没有类型,并且所带参数也没有类型,展开时用指定的字符串替换宏名即可,并且宏定义时的字符串可以是任何类型的数据。(4)函数调用不会使源程序的长度发生变化,而宏展开可以使源程序的长度发生改变。(5)函数调用要占用运行时间,而宏展开是在编译之前处理的,不占用运行时间。例9.10 分析下面程序的运行结果。#include#define HDY(A,B)A/B#define PRINT(Y)printf(y=%dn,Y)void main()int a=1,b=2,c=3,d=4,k;k=HD
34、Y(a+c,b+d);PRINT(k);解 在主函数main中,“K=HDY(a+c,b+d);”语句在宏展开后为“K=a+c/b+d;”,运行后K被赋值为1+3/2+4=6,而“PRINT(K);”展开后为“printf(y=%dn,k);”故输出结果为:y=6。例9.11 有以下程序#include#define N 5#define M N+1#define f(x)(x*M)void main()int i1,i2;i1=f(2);i2=f(1+1);printf(%d,%dn,i1,i2);程序运行后的输出结果是 。A)12,12 B)11,7 C)11,11 D)12,7解 宏替换
35、只是字面上的替换,在编译之前完成。程序中第1条要替换的语句“i1=f(2);”展开后是“i1=(2*M);”,再展开为“i1=(2*N+1);”,最后展开为“i1=(2*5+1);”,结果是i1的值为11。而第2条要替换的语句“i2=f(1+1);”,展开后是“i2=(1+1*M);”,再展开为“i2=(1+1*N+1);”,最后展开为“i2=(1+1*5+1);”,结果是i2的值为7。故应选B。9.4.2 文件包含命令文件包含命令文件包含是指将指定文件中的内容嵌入到当前源程序文件中,文件包含命令的一般形式为:#include或#include 文件名其功能是:在编译预处理时把“文件名”所指的
36、文件内容嵌入到当前的源程序文件中,然后再对嵌入后的源程序文件进行编译。对于第1种由尖括号“”括住文件名的文件包含命令,这种方式告诉编译预处理程序,被包含的文件存放在C编译系统所在的目录下。该方式适用于嵌入C系统提供的头文件,因为C系统提供的头文件都存放在编译系统所在的目录下。对于第2种用双引号“”括住文件名的文件包含命令,编译预处理程序首先到当前文件所在的文件目录下查找被包含的文件,如果找不到再到编译系统所在的目录下查找。当然,也可以在文件名前给出路径名,直接告诉编译预处理程序被包含文件所在的确切位置。在使用文件包含命令时应注意以下几点:(1)一个#inlude命令只能包含一个文件,如果要包含
37、多个文件,就得用多个文件包含命令。(2)文件包含可以嵌套,即在一个被包含的文件中又可以包含另一个文件。如文件1包含文件2,而文件2又要用到文件3的内容,可在文件1中用两个#indule命令分别包含文件3和文件2,并且文件3的包含命令应在文件2的包含命令之前,这样文件1和文件2都能使用文件3的内容。(3)被包含的文件2与其所在的文件1,经过预处理后成为一个文件即文件1,而不是两个文件。因此,在文件2中定义的全局变量此时在文件1中有效,即无需使用extern声明。(4)#include命令用于包含扩展名为.c的源程序文件或扩展名为.h的“头文件”。例9.12 计算xy。解 文件filel.c中的内
38、容#include#include d:cfile2.h /*指明要包含文件file2.c的完整路径*/int main()int x,y;double power(int,int);/*函数声明*/printf(Input x,y:);scanf(%d,%d,&x,&y);printf(%d*%d=%fn,x,y,power(x,y);return 0;d盘C目录下file2.h文件中的内容为;double power(int m,int n)int i;double y=1.0;for(i=1;i=n;i+)y*=m;return y;运行结果为:Input x,y:2,102*10=10
39、24.000000例9.13 用包含排序文件的方法实现从高分到低分输出学生成绩。解 文件px.c如下:void sort(int x,int n)/*选择排序文件*/int i,j,k,t;for(i=0;in-1;i+)k=i;for(j=i+1;jn;j+)if(xkxj)k=j;if(k!=i)t=xi;xi=xk;xk=t;文件cj.c如下:#include#include px.cvoid main()int num,score50,k;printf(Input num=);/*输入班级人数*/scanf(%d,&num);printf(Input score of student:
40、n);for(k=0;knum;k+)scanf(%d,&scorek);/*输入学生成绩*/sort(score,num);for(k=0;knum;k+)printf(%4d,scorek);/*从高到低输出学生的成绩*/if(k+1)%10=0)printf(n);printf(n);例9.14 现有两个C程序文件T1.c和myfun.c同在TC系统目录(文件夹)下,其中T1.c文件如下:#include#include myfun.cvoid main()fun();printf(n);myfun.c文件如下:void fun()char s20,c;int n=0;while(c=g
41、etchar()!=n)sn+=c;n-;while(n=0)printf(%c,sn-);当编译链接通过后,运行程序T1时,输入Thank!求输出的结果。解 本题源程序相当于:#includevoid fun();void main()fun();printf(n);void fun()char s20,c;int n=0;while(c=getchar()!=n)sn+=c;n-;while(n=0)printf(%c,sn-);从程序来看,主函数main只调用了一次函数fun,然后输出一个回车换行符n。所以,程序的重点在函数fun。函数fun中有两个while循环,第1个while语句“
42、while(c=getchar()!=n)sn+=c;”的作用是:从键盘上读入字符到变量c,若读入的不是回车换行符n,就将它存入数组s。n为数组s的下标,用来控制字符顺序放入数组s中。所以当第1个while语句结束时,s数组放入了“Thank!”,而n则是下一个将要放入字符的数组元素下标。接下来的“n-;”语句,使n退回到字符为“!”的下标值。第2个while语句“while(n=0)printf(%c,sn-);”,则由数组s中的字符“!”开始由后向前(由语句“n-;”控制)直至第1个字符“T”为止,逐个字符进行输出,所以输出的结果即为输入字符串的逆序:!KnahT。9.5 枚举类型枚举类型
43、 枚举表示一类数据的个数有限,即可穷举。枚举类型是一类有限离散的符号数据量的集合。例如:真假、性别、星期、时辰、职称、年级等。而实型则不是这样,实型数据是连续的、无法穷举的。C语言引入了枚举类型,即在枚举类型定义中列举出所有可能的取值,被说明为该枚举类型的变量只能取定义中列举出来的值。由于C语言没有像PASCAL语言那样提供集合的并、交、差运算和判断某元素是否属于某个集合的属于运算等,所以C语言中的枚举类型的功能有限,在程序中很少使用。1.枚举类型和枚举变量的定义定义枚举类型的一般形式为:enum 枚举类型名枚举常量名表;其中,enum是关键字,称为枚举类型定义标识符;枚举常量名表形式如下:标
44、识符1,标识符2,标识符n这些标识符不得重名,它们表示的是枚举类型定义中所有可能出现的枚举值,因此是枚举常量。例如:enum weekdaysun,mon,tue,wed,thu,fri,sat;定义了一个枚举类型enum weekday,它有7个枚举常量。枚举变量如同结构体变量和共用体变量一样,也可以有三种定义形式:(1)先定义枚举类型再定义枚举变量。例如:enum weekdaysun,mon,tue,wed,thu,fri,sat;enum weekday a,b,c;(2)在定义枚举类型的同时定义枚举变量。例如:enum weekdaysun,mon,tue,wed,thu,fri,s
45、ata,b,c;(3)直接定义枚举变量。例如:enum sun,mon,tue,wed,thu,fri,sata,b,c;使用枚举类型时应注意以下几点:(1)枚举变量的取值范围限定在枚举类型定义时的枚举常量名表内,即不得出现枚举常量名表以外的标识符。(2)枚举常量只是一个标识符,不能与变量混淆,即不得用赋值语句给枚举常量标识符赋值。(3)枚举常量(标识符)本身是有值的。枚举类型定义时,每个枚举常量(标识符)的值就确定了,即按定义时出现的顺序依次为0、1、2、;如上面枚举类型enum weekday 中,sun的值为0、mon的值为1、。枚举常量(标识符)的值可以输出,但枚举常量即标识符不能直接
46、输出。(4)也可以在定义时强制改变枚举常量的值。例如:enum weekdaysun,mon=3,tue,wed,thu,fri,sata,b,c;则sun的值为0,而mon的值为3,其后的枚举常量值顺序加1,即tue的值为4、wed值为5、。(5)若要将整数值赋给枚举变量必须作强制类型转换。例如:a=(enum weekday)0;这相当于:a=sun;(6)枚举常量由于本身有值,所以可以比较大小,也可以作为循环控制变量。例如:if(amon)或者:for(a=mon;a=sat;a+)printf(%2d,a);2.枚举变量的使用枚举变量的值只能用赋值语句获得,不能用scanf函数直接读入
47、枚举常量(即标识符)。通常是先输入一个整数,然后再通过switch语句给枚举变量赋值。例如:enum weekdaysun,mon,tue,wed,thu,fri,satworkday;scanf(%d,&n);switch(n)case 0:workday=sun;case 1:workday=mon;case 2:workday=tue;case 3:workday=wed;case 4:workday=thu;case 5:workday=fri;case 6:workday=sat;此外,也不能通过printf函数直接输出枚举变量的值标识符形式的枚举常量,枚举变量的值通常也是通过swi
48、tch语句以字符串形式输出对应的信息。例如:swith(workday)case sun:printf(Sundayn);case mon:printf(Mondayn);case tue:printf(Tuesdayn);case wed:printf(Wednesdayn);case thu:printf(Thursdayn);case fri:printf(Fridayn);case sat:printf(Saturdayn);例9.15 已知一个不透明的布袋中装有红、蓝、黄、绿、紫色圆球各一个,现从中一次抓出两个,问可能抓到的两个球都有哪些颜色组合。解 分析见例6.11。本题采用枚举变
49、量求解。程序如下:#includeenum colorred,blue,yellow,green,purple;void print(enum color c);void main()int s=0,i,j;for(i=0;i=3;i+)for(j=i+1;j=4;j+)if(i=j)continue;s+;printf(%5d ,s);print(enum color)i);print(enum color)j);printf(n);void print(enum color c)switch(c)case red:printf(red );break;case blue:printf(bl
50、ue );break;case yellow:printf(yellow );break;case green:printf(green );break;case purple:printf(purple );运行结果:1 red blue2 red yellow3 red green4 red purple5 blue yellow6 blue green7 blue purple8 yellow green9 yellow purple10 green purple9.6 位运算位运算 所谓位运算,是指二进制位的运算。例如,将一个内存单元中存储的数据按二进制位左移或右移,两个数按位相加等。