函数与程序结构

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

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<stdio.h>
#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]<v[left]){
            swap(v,++last,i);
        }
    }
    swap(v,last,left);
    qsort(v,left,last-1);/* 对左边进行递归 */
    qsort(v,last+1,right);/* 对右边进行递归 */
}

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 <stdio.h>
#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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272

推荐阅读更多精彩内容