函数与程序结构

函数可以把大的计算任务分解成若干个较小的任务,程序设计人员可以基于函数进一步构造函数,而不需要重新编写一些代码。一个设计得当的函数可以把程序中不需要了解的具体操作细节隐藏起来,从而使整个程序结构更加清晰,并降低修改程序的难度。

C语言在设计中考虑了函数的高效性和易用性这两个因素。C语言程序一般都由许多小的函数组成,而不是由少量较大的函数组成。一个程序可以保存在一个或者多个源文件中。各个文件可以单独编译,并可以与库中已编译过的函数一起加载。

函数的定义形式如下:

返回值类型 函数名(参数声明表)
{
    声明和语句
}

被调用函数通过return语句向调用者返回值,return语句的后面可以跟任何表达式:

return 表达式;

当return语句后面没有表达式时,函数将不会向调用者返回值。当被调用函数执行到最右花括号而结束执行时,控制同样也会返回给调用者。
例子:

/* 从标准输入流得到至多为lim的字符串,存储在s中,并返回数量 */
int getline(char s[],int lim){
    char c;
    int i = 0;
    while(--lim>0&&(c = getchar())!=EOF&&c!='\n'){
        s[i++] = c;
    }
    if(c == '\n'){
        s[i++] = c;
    }
    s[i] = '\0';
    return i;
}

/* 返回t在s中的位置,如果没有找到,就返回-1 */
int strindex(char s[],char t[]){
    int i,j;
    for(i=0;s[i]!='\0';i++){
        for(j=0;t[j]!='\0'&&t[j]==s[i+j];j++)
            ;
        if(t[j] == '\0'){
            return i;
        }
    }
    return -1;
}

外部变量

C语言程序可以看成由一系列的外部对象构成,这些外部对象可能是变量或函数。外部变量定义在函数之外,因此可以被许多函数使用。通过同一个名字对外部变量的引用实际上都是引用同一个对象。因为外部变量可以在全局范围内访问,这就为函数之间数据交换提供了一种可以代替函数参数与返回值得方式。任何函数都可以通过名字访问一个外部变量,当然这个名字需要某种方式进行声明。
例子:

#include
#define MAXVAL 100

double val[MAXVAL]; /* 栈 */
static int sp = 0; /* 指向下一个位置 */

void push(double f){
    if(sp < MAXVAL){
        val[sp++] = f;
    }else{
        printf("error: stack full,can't push %g\n",f);
    }
}

double pop(void){
    if(sp > 0){
        return val[--sp];
    }else{
        printf("error: stack empty\n");
        return 0.0;
    }
}

作用域规则

名字的作用域规则指的是程序中可以使用该名字的部分。对于在函数开头声明的自动变量来说,其作用域是声明该变量名的函数。不同函数中声明的具有相同名字的各个局部变量之间没有任何关系。函数的参数也是这样的,实际上可以将它看作是局部变量。

外部变量或函数的作用域从声明它的地方开始,到其所在的文件的末尾结束。也就是说,前面的函数无法调用后面的函数,如果后面的函数没有事先声明的话。

如果要在外部变量的定义之前使用该变量,或者外部变量的定义与变量的使用不在同一个源文件中,则必须在相应的变量声明中强制地使用关键字extern。这一点区分了声明和定义。变量声明说明了变量的属性,而变量的定义除此以外还将引起存储器的分配。如:

double val[MAXVAL];/* 定义 */
extern double val[];/* 声明 */

头文件

把一个程序分割到若干源文件后,考虑到定义和声明在这些程序之间的共享问题,尽可能把共享的部分集中在一起,这样就只需要一个副本。要用到该头文件时通过#include指令将它包含进来。

静态变量
用static声明外部变量与函数,可以将其后声明的对象的作用域限定为被编译源文件的剩余部分。static也可用声明内部变量,static类型的内部变量是一种只能在某个特定函数中使用但一直占据存储空间的变量。

寄存器变量
register声明告诉编译器,它所声明的变量在程序中使用频率高。其思想是,将register变量放在机器的寄存器中。这个关键字使用的很少,它只适用于自动变量以及函数的形式参数。如:

register int x;
void fun(register int x){
    ...
}

初始化

在不进行显示初始化的情况下,外部变量和静态变量都将被初始化为0,而自动变量和寄存器变量初值则是没有定义(即初值为无用信息)。在定义标量时,可以在变量名后面紧跟一个等号和一个表达式来初始化变量:

int x = 1;
char squote = '\';
long day = 1000L * 60L *24L;

数组的初始化可以在声明后面紧跟一个初始化列表,初始化表达式列表用花括号括起来,各初值表达式之间通过逗号分隔。

int arr[] = {1,2,3,4,5,6,7,8,9,10};

字符数组的初始化比较特殊:可以用一个字符串来代替用花括号括起来并用逗号分隔的初始化表达式序列。如:

char pattern[] = "ould";

它等同于下面的声明:

char pattern[] = {'0','u','l','d','\0'};

递归

C语言中的函数可以递归调用,即函数可以直接或间接调用自身。函数递归调用自身时,每次都会得到一个与以前的自动变量集合不同的新的自动变量的集合。一个经典的例子就是快速排序:

/* 交换v中v[i]和v[j]的值 */
void swap(int v[],int i,int j){
    int temp = v[i];
    v[i] = v[j];
    v[j] = temp;
}

void qsort(int v[],int left,int right){
    /* 如果元素少于两个,不需要排序,递归出口 */
    if(left>=right){
        return;
    }
    int i,last;
    /* 把中间的数作为pivot,并且把它移动到最左边 */
    swap(v,left,(left+right)/2);
    last = left;/* last指向最后一个小于等于pivot的数 */
    for(i=left+1;i<=right;i++){
        if(v[i]

C预处理

文件包含指令(#include指令)使得处理大量的#define指令以及声明更加方便。在源文件中,任何形如:

#include"文件名"

#include<文件名>

的行都将被替换为由文件名所指定的文件的内容。如果文件名用引号引起来,则在源文件所在的位置查找该文件;如果该位置没有找到文件,或者如果文件名使用尖括号括起来的,则将根据相应的规则查找该文件,这个规则同具体的实现有关(通常是去某个特定文件夹下面查找)。被包含的文件本身也可包含#include指令。

宏替换

宏定义的形式如下:

#define 名字 替换文本

这是一种最简单的宏替换——后续所有出现名字记号的地方都将被替换为替换文本。#define指令中的名字与变量名的命名方式相同,替换文本可以使任意字符串。如:

#define forever for(;;)

宏定义也可以带参数,这样可以对不同的宏调用使用不同的替换文本。如下面定义了一个max宏;

#define max(A, B) ((A) > (B) ? (A) : (B))

使用宏max看起来很像函数调用,

int x = max(1,2);

但宏调用直接将替换文本插入到代码中。宏定义可以避免调用函数所需的运行时的开销,如getchar和putchar函数就使用了宏定义。

如果在替换文本中,参数名以#作为前缀则结果将被扩展为由实际参数替换该参数的带引号的字符串。如果替换文本中的参数与##相邻,则该参数将被实际删除替换,##与前后的空白字符将被删除,并对替换后的结构进行重新扫描。下面是一个例子:

#include 
#define dprint(expr) printf(#expr " = %d\n", expr)
#define paste(front, back) front##back

void fun(int x)
{
    printf("x = %d\n", x);
}

int main(int argc, char const *argv[])
{
    int x = 1;
    int x1 = 2;
    dprint(x); /* 输出x = 1 */
    fun(paste(x, 1)); /* 传入的参数是x1 */
    return 0;
}

条件包含

可以使用条件语句对预处理本身进行控制,这种条件条件语句的值是在预处理执行的过程中进行计算。这种方式为在编译过程中根据计算所得的条件值选择性地包含不同代码提供了一种手段。#if语句对之中的常量表达式进行求值,若该表达式的值不等于0,则包含其后的各行,知道遇到#endif,#elif或#else语句为止。在#if语句中可以使用表达式defined(名字),该表达式的遵循下列规则:当名字已经定义时,其值为1;否则,其值为0。如:

#if !defined(HDR)
#define HDR
/* hdr.h文件内容放在这里 */
#endif

它等价于下面的形式:

#ifndef HDR
#define HDR
/* hdr.h文件内容放在这里 */
#endif

还可以根据不同的值,来包含不同的内容:

#if SYSTEM == SYSV
    #define HDR "sysv.h"
#elif SYSTEM == BSD
    #define HDR "bsd.h"
#elif SYSTEM == MSDOS
    #define HDR "msdos.h"
#else
    #define HDR "default.h"
#endif
#include HDR

你可能感兴趣的:(函数与程序结构)