ANSI标准进一步明确了名字的作用域规则,特别要求每个外部对象只能有一个定义。初始化的适用范围也更加广泛了,自动数组与结构都可以进行初始化。

C语言预处理器的功能也得到了增强。新的预处理器包含一组更完整的条件编译指令(一种通过宏参数创建带引号的字符串的方法),对宏扩展过程的控制更严格。

4.1 函数的基本知识

#include <stdio.h>
#define MAXLINE 1000

int getline(char line[], int max);
int strindex(char sourcep[], char searchfor[]);

char pattern[] = "ould";

main()
{
    char line[MAXLINE];
    int found = 0;

    while (getline(line, MAXLINE) > 0)
        if (strindex(line, pattern) >= 0) {
            printf("%s", line);
            found++;
        }
    return found;
}

/* getline函数:将行保存到s中,并返回该行的长度 */
int getline(char s[], int lim)
{
    int c, i;
    i = 0;
    while (--lim > 0 && (c=getchar()) != EOF && c != '\n')
        s[i++] = c
    if (c == '\n')
        s[i++] = c;
    s[i] = '\0';
    return i;
}

/* strindex函数:返回t在s中的位置,若未找到则返回-1 */
int strindex(char s[], char t[])
{
    int i, j, k;

    for (i=0; s[i] != '\0'; i++) {
        for (j=0, k=0; t[k]!='\0' && s[j]==t[k]; j++, k++)
        ;
        if (k > 0 && t[k] == '\0')
            return i;
    }
    return -1;
}

最简单的函数

dummy() {}

4.2 返回非整型值的函数

#include <ctype.h>

/* atof函数:把字符串s转换为相应的双精读浮点数 */
double atof(char s[])
{
    double val, power;
    int i, sign;

    for (i=0; isspace(s[i]); i++)
        ;
    sign = (s[i] == '-') ? -1 : 1;
    if (s[i] == '+' || s[i] == '-')
        i++;
    for (val = 0.0; isdigit(s[i]); i++)
        val = 10.0 * val + (s[i] - '0');
    if (s[i] == '.')
        i++;
    for (power = 1.0; isdigit(s[i]); i++) {
        val = 10.0 * val + (s[i] - '0');
        power *= 10.0;
    }
    return sign * val /power;
}

main()
{
    double sum, atof(char []);
    char line[MAXLINE];
    int getline(char line[], int max);

    sum = 0;
    while (getline(line, MAXLINE) > 0)
        printf("\t%g\n", sum += atof(line));
    return 0;
}

如果先前没有声明过的一个名字出现在某个表达式中,并且其后紧跟一个左圆括号,那么上下文就会认为该名字是一个函数名字,该函数的返回值将被假定为int类型,但上下文并不对其参数作任何假设。

如果函数声明中不包括参数,例如:double atof();那么编译程序也不会对函数atof的参数作任何假设,并会关闭所有的参数检查。

提倡的做法,函数带有参数,则要声明它们;如果没有参数,则使用void进行声明。

/* atoi函数:利用atof函数把字符串s转换为整数 */
int atoi(char s[])
{
    double atof(char s[]);

    return (int) atof(s);
}

4.3 外部变量

外部变量与函数具有下列性质:通过同一个名字对外部变量的所有引用(即使这种引用来自与单独编译的不同函数)实际上都是引用同一个对象(标准中把这一性质称为外部链接)。

#include <stdio.h>
#include <stdlib.h>

#define MAXOP 100
#define NUMBER '0'

int getop(char []);
void push(double);
double pop(void);

/* 逆波兰计算器 */
main()
{
    int type;
    double op2;
    char s[MAXOP];

    while ((type = getop(s)) != EOF) {
        switch (type) {
        case NUMBER:
            push(atof(s));
            break;
        case '+':
            push(pop() + pop());
            break;
        case '*':
            push(pop() * pop());
            break;
        case '-':
            op2 = pop();
            push(pop() - op2);
            break;
        case '/':
            op2 = pop();
            if (op2 != 0.0)
                push(pop() /op2);
            else
                printf("error: zero divisor\n");
            break;
        case '\n':
            printf("\t%.8g\n", pop());
            break;
        default:
            printf("error: unknown command %s\n", s);
            break;
        }
    }
    return 0;
}

#define MAXVAL 100
int sp = 0;
double val[MAXVAL];

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

#include <ctype.h>

int getch(void);
void ungetch(int);

/* getop函数: 获取下一个运算符或数值操作数 */
int getop(char s[])
{
    int i, c;

    while ((s[0] = c = getch()) == ' ' || c == '\t')
        ;
    s[1] = '\0';
    if (!isdigit(c) && c != '.')
        return c;
    i = 0;
    if (isdigit(c))
        while (isdigit(s[++i] = c = getch()))
            ;
    if (c == '.')
        while (isdigit(s[++i] = c = getch()))
            ;
    s[i] = '\0';
    if (c != EOF)
        ungetch(c);
    return NUMBER;
}

#define BUFSIZE 100

char buf[BUFSIZE];
int bufp = 0;

int getch(void)
{
    return (bufp > 0) ? buf[--bufp] : getchar();
}

void ungetch(int c)
{
    if (bufp >= BUFSIZE)
        printf("ungetch: too many characters\n");
    else
        buf[bufp++] = c;
}

4.4 作用域规则

外部变量或函数的作用域从声明它的地方开始,到其所在的(待编译的)文件的末尾结束。

要在外部变量的定义之前使用该变量,或者变量的定义与变量的使用不再同一个源文件中,则必须在相应的变量声明中强制性地使用关键字extern.

将外部变量的声明和定义严格区分开来很重要。变量声明用于说明变量的属性(主要是变量的类型),而变量定义除此以外还将引起存储器的分配。

// 定义外部变量,并为之分配存储单元
int sp;
double val[100];

// 声明外部变量
extern int sp;
extern double val[];

在一个源程序的所有源文件中,一个外部变量只能在某个文件中定义一次,而其他文件可以通过extern声明来访问它。外部变量的定义中必须指定数组的长度,但extern声明则不一定要指定数组的长度。

外部变量的初始化只能出现在其定义中。

// file1
extern int sp;
extern double val[];

// file2
int sp = 0;
double val[100];

4.5 头文件

考虑定义和声明在这些文件之间的共享问题。我们尽可能把共享的部分集中在一起,这样就只需要一个副本,改进程序时也很容易保证程序的正确性。我们把这些公共部分放在头文件calc.h中。

4.6 静态变量

用static声明限定外部变量与函数,可以将其后声明的对象的作用域限定为被编译源文件的剩余部分。通过static限定外部对象,可以达到隐藏外部对象的目的。

static用于声明内部变量时。static类型的内部变量是一种只能在某个特定函数中使用但一直占据存储空间的变量。

4.7 寄存器变量

register声明高速编译器,此变量在程序中使用频率较高。放在寄存器中访问最快。

register声明只适用于自动变量以及函数的形式参数。

f(register unsigned m)
{
    register int i;
}

过量的寄存器声明并没有害处,编译器可以忽略过量的或不支持的寄存器变量声明。寄存器变量的地址是不能访问的。

4.8 程序块结构

变量声明(包括初始化)除了可以紧跟在函数开始的花括号之后,还可以紧跟在任何其他标识复合语句开始的花括号之后。以这种方式声明的变量可以隐藏程序块与之同名的变量,它们之间没有任何关系。

int (n > 0) {
    int i;
    for (i=0; i < n; i++)
        ...
}

变量i的作用域是if语句的“真”分支,与程序块外声明的i无关。

4.9 初始化

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

外部变量与静态变量,初始化表达式必须是常量表达式

如果初始化表达式的个数比数组元素数少,则对外部变量、静态变量和自动变量来说,没有初始化表达式的元素将被初始化为0。

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

4.10 递归

4.11 C预处理器

4.11.1 文件包含

#include "文件名"
#include <文件名>
的区别

include指令,它们用以包含常见的#define语句和extern声明,或从头文件中访问库函数的函数原型声明,如

4.11.2 宏替换

#define 名字 替换文本
#define max(A, B) ((A) > (B) ? (A) : (B))

作用域从其定义点开始,到被编译的源文件的末尾处结束。

参数表达式存在副作用(如自增运算符),会出现不正确的情况。

使用圆括号以保证计算次序的正确性。

// 取消名字的宏定义
#undef getchar 

// 以#作为前缀的参数会被参数名字符串所替代
#define dprint(expr) printf(#expr " = %g\n", expr)
dprint(x/y) ==> printf("x/y" " = %g\n", x/y);

// ##为宏扩展提供了一种连接实际参数的手段
#define paste(front, back) front ## back
paste(name,1) ==> name1

4.11.3 条件包含

#if !defined(HDR)
#define HDR
...
#endif

#ifndef HDR
#define HDR
...
#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