这一章主要介绍以下几个方面的内容:
函数的功能与使用方法,包括函数的声明与定义。名字的作用域的问题,自动变量,寄存器变量,外部变量等特点与用法,最后讲解了C语言预处理器相关的知识。
书中是通过一个例子来说明函数的基本用法,以及函数的一个模块化设计的思想,程序要求把输出中包含特定模式的行给输出,这里面特定模式是一个通用性的设计,这里我们把它设定为包含指定字符串。
函数strindex(s,t)将返回字符串t在字符串s中出现的位置,如果没有出现则为0。我们这里把包含特定模式设计为一个函数,以增强程序的拓展性。
#include<stdio.h> #define LIM 100 int getline(char *s,int lim); int strindex(char *s,char *t); int main(void) { char row[LIM]; char pattern[]="get"; while(getline(row,LIM)>0) { if(strindex(row,pattern)>=0) printf("%s",row); } return 0; } /*从输入中读取一行,并返回读取字符的个数*/ int getline(char *s,int lim) { char c; int n=0; while(--lim>0 && (c=getchar())!='\n' && c!=EOF) { *(s++)=c; n++; } if(c=='\n') { *(s++)=c; n++; } *s='\0'; return n; } /*strindex(s,t)返回字符串t在字符串s中的位置,如果存在s中返回-1*/ int strindex(char *s,char *t) { int i=0,j=0,k=0; while(s[i]!='\0') { for(j=i;t[k]!='\0'&&t[k]==s[j];j++,k++) ; if(t[k]=='\0') return i; i++; } return -1; }
最简单的函数结构为dummy(){},该函数不执行任何操作也不返回从任何值,这种不执行操作的函数有时候很有用,它可以在程序开发期间用以保留位置,留待以后填充。
函数之间可以通过参数,返回值,外部变量进行通信。
前面有提到过的函数返回类型一般为void或者是整形的,其实函数返回类型其实丰富多样,下面通过将字符串转换成浮点数的程序,说明返回浮点型的函数atof。
/*atof(s)将字符串s转换为浮点数,能处理小数点*/ double atof1(char s[]) { int i,sign; double var; double p; for(i=0;isspace(s[i]);i++) ; sign=(s[i]=='-'?-1:1); if(s[i]=='+'||s[i]=='-') i++; for(var=0;isdigit(s[i]);i++) var=10*var+(s[i]-'0'); if(s[i]=='.') i++; for(p=1;isdigit(s[i]);i++) { var=10*var+(s[i]-'0'); p*=10; } return sign*var/p; }
C语言程序可以看成是由一系列外部变量组成的,它们可能是函数也可能是变量。由于C语言中函数内部不能定义函数,所以所有的函数都是外部的,外部变量定义在函数外部,可以让多个函数调用使用。
正如前面提过,外部变量可以作为函数之间通信的一种方式,但是不要过份依赖外部变量,这样程序变得危险。这一部分通过一个堆栈实现简单的算术运算来说明外部变量的使用。
计算器计算采用逆波兰式:
(1-2)*(4+5)写成逆波兰式为:1 2 – 4 5 + *
所以使用栈来实现这一功能最合适,而栈的两个基本操作出栈与入栈和栈的定义则写成外部变量的形式。
下面先定义一栈,固定分配
#include<stdio.h> #define BUFSIZE 100 double stackbuf[BUFSIZE]; int p=-1;/*栈顶位置*/ /*进栈操作*/ void push(double f) { if(p<BUFSIZE-1) stackbuf[++p]=f; else printf("错误:栈已满!\n"); } /*出栈操作*/ double pop(void) { if(p>=0) return stackbuf[p--]; else printf("错误:栈为空"); }
这个程序中,很重要的一个部分是在读取数字和操作符的部分,这里我们可以利用前面把字符串转换成数字的这个函数,这就把问题转换成了如何从输入取出一个操作数或操作符的字符串。
这里给出了一个getop函数,从输入中读取一个数字或一个操作符
int getop(char s[]) { extern int Getch(void); extern void unGetch(int c); int c,i=0; while((c=Getch())==' '||c=='\t') ; if(!isdigit(c)&&c!='.') return c; if(isdigit(c)) { s[i]=c; while(isdigit(s[++i]=c=Getch())) ; } if(c=='.') { s[i]=c; while(isdigit(s[++i]=c=Getch())) ; } s[i]='\0'; if(c!=EOF) unGetch(c); return NUMBER; }
上面这段程序中Getchar与unGetchar两个函数是功能是实现读取与返还一个字符。
在程序中往往由于要确定是否读入了足够的字符,因为会多读取一个字符进行判断,这样在再次再读取时,这个字符必须重新使用,所以,再读取后需要将这个字符放回去。
这里程序是用了一个缓冲区域,如果缓冲区为空的时候,Getch就从getchar读取一个字符,否则就从缓冲区中取出一个字符,而unGetch将字符放回到缓冲区中。
int Getch() { if(qq>=0) return Buf[qq--]; else return getchar(); } void unGetch(int c) { if(qq>=BUFSIZ-1) printf("数字太多!"); else Buf[++qq]=c; }
外部变量和函数的作用域是从它们声明的地方到文件的结尾
如果要在外部变量之前使用变量,或者外部变量在另一个文件中要使用变量时,必须用extern关键字进行声明。注意声明与定义的区别,变量在被定义时分配了存储空间,而声明并没有重新建立变量或分配空间。
在实际的大型程序中,往往会把程序放在多个文件中,不同的文件一般完成指定的功能,如4.3中的例子,可以把push与pop函数及相关声明放在stack.c文件中,把getop放在getop.c文件中,把getch与ungetch放在getch.c文件中,而几个文件可能共用了一些定义与声明,我们可以将他们在放一个单独的头文件cal.h里。
外部变量或函数前面加上关键字 static后,该外部变量或函数只能被所在的文件所引用,不能被其他文件使用。
自动变量前面加上关键字statci后,该变量会一直存在内存中,不会因为程序块或函数的退出而消失。
如要某些变量在程序中使用的频率很高,则可以声明为寄存器变量,寄存器变量只能适用于自动变量及函数参数的形式:
f(register unsigned m,register long n) { register int i; ... }
寄存器变量表明变量保存在系统的寄存器里,值得注意的就是寄存器变量是没有地址的。
程序块中的变量属于自动变量,其变量名可以与程序块外面的变量名重复,应该区别对待。
在不进行显式初始化的情况下,外部变量与静态变量被初始化为0,自动变量与寄存器变量的初值没有意义。
数组与字符数组的初始化,字符数组可以直接用一个字符串来进行初始化。
C语言中允许让函数本身调用本身,这种形式叫递归,递归并不节省存储器的开销,因为在递归的过程中系统必须维护一个存储处理值的栈,递归的执行速度并不快,但是程序会比较紧凑。
下面通过一个快速排序的例子说明函数的递归调用,程序先选取数组中间的一个元素作为阈值,将数组分为2部分,比阈值小的和比阈值大的,然后再把两个子数组依次分类下去,直到子数组只有一个数字为止。
/*快速排序的递归写法*/ void Qsort(int ar[],int left,int right) { int i; int last; if(right-left<1) return; swap(ar,left,(left+right)/2); for(i=left+1,last=left;i<=right;i++) { if(ar[i]<ar[left]) swap(ar,++last,i); } swap(ar,left,last); Qsort(ar,left,last); Qsort(ar,last+1,right); } void swap(int ar[],int i,int j) { int temp; temp=ar[i]; ar[i]=ar[j]; ar[j]=temp; }
文件包含#inlcude,可以将一些声明语句的头文件包含进来。
宏替换#define