《TCPL》 第4章 函数与程序结构

这一章主要介绍以下几个方面的内容:

函数的功能与使用方法,包括函数的声明与定义。名字的作用域的问题,自动变量,寄存器变量,外部变量等特点与用法,最后讲解了C语言预处理器相关的知识。

4.1 函数的基本知识

书中是通过一个例子来说明函数的基本用法,以及函数的一个模块化设计的思想,程序要求把输出中包含特定模式的行给输出,这里面特定模式是一个通用性的设计,这里我们把它设定为包含指定字符串。

函数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(){},该函数不执行任何操作也不返回从任何值,这种不执行操作的函数有时候很有用,它可以在程序开发期间用以保留位置,留待以后填充。

函数之间可以通过参数,返回值,外部变量进行通信。

4.2 返回非整形值的函数

前面有提到过的函数返回类型一般为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;
}

4.3 外部变量

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;
}

4.4 作用域规则

外部变量和函数的作用域是从它们声明的地方到文件的结尾

如果要在外部变量之前使用变量,或者外部变量在另一个文件中要使用变量时,必须用extern关键字进行声明。注意声明与定义的区别,变量在被定义时分配了存储空间,而声明并没有重新建立变量或分配空间。

4.5 头文件

在实际的大型程序中,往往会把程序放在多个文件中,不同的文件一般完成指定的功能,如4.3中的例子,可以把push与pop函数及相关声明放在stack.c文件中,把getop放在getop.c文件中,把getch与ungetch放在getch.c文件中,而几个文件可能共用了一些定义与声明,我们可以将他们在放一个单独的头文件cal.h里。

4.6 静态变量

外部变量或函数前面加上关键字 static后,该外部变量或函数只能被所在的文件所引用,不能被其他文件使用。

自动变量前面加上关键字statci后,该变量会一直存在内存中,不会因为程序块或函数的退出而消失。

4.7 寄存器变量

如要某些变量在程序中使用的频率很高,则可以声明为寄存器变量,寄存器变量只能适用于自动变量及函数参数的形式:

f(register unsigned m,register long n)
{
    register int i;
    ...
}

寄存器变量表明变量保存在系统的寄存器里,值得注意的就是寄存器变量是没有地址的。

4.8 程序块结构

程序块中的变量属于自动变量,其变量名可以与程序块外面的变量名重复,应该区别对待。

4.9 初始化

在不进行显式初始化的情况下,外部变量与静态变量被初始化为0,自动变量与寄存器变量的初值没有意义。

数组与字符数组的初始化,字符数组可以直接用一个字符串来进行初始化。

4.10 递归

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;
}

4.11 预处理器

文件包含#inlcude,可以将一些声明语句的头文件包含进来。

宏替换#define

你可能感兴趣的:(tcp)