跳到主要内容

complex-program

源文件

程序由任意数量的源文件组成。源文件的扩展名是.c,包含程序的部分内容,主要是函数和变量的定义。其中一个源文件必须包含main函数,程序从这里开始运行。

把程序分为多个源文件有许多优点:

  • 把相关的函数和变量分组放在同一个文件夹中可以让程序结构清晰
  • 可以分别对每个源文件进行编译,更快
  • 把函数分组放在不同的源文件有利于复用

头文件

#include指令使各个源文件共享信息成为可能,这些信息包括:函数原型、宏定义、类型定义等。

#include指令告诉预处理器打开指定的文件,并将此文件的内容插入到当前文件中。因此想让几个源文件可以访问相同的信息,可以将此信息放入一个文件中,然后使用该指令将文件内容带进每个源文件。把按照此种方式包含的文件称为头文件(包含文件)。惯例扩展名为 .c。

C标准的术语“源文件”指程序员编写的所有文件,本文档只指 .c 文件。

#include

//只搜寻系统头文件所在目录
#include <文件名>

//先搜寻当前目录,在搜寻系统头文件所在的目录
#include "文件名"

文件名使用相对路径比使用绝对路径要好。

共享宏定义和类型定义

大多数大型程序包含需要由几个源文件共享的宏定义和类型定义。这些定义应该放到头文件中。然后在需要它们的源文件中添加一行 include 。

共享函数原型

函数不声明而直接使用会编译失败。处理函数的互相调用、维护函数声明的一致性的解决方案是:

  • 将函数原型放进头文件Xxx.h
  • 在调用的地方include Xxx.h
  • 在Xxx.c中include Xxx.h 以让不要去可以样子函数的原型是否匹配函数定义

共享变量声明

extern:告诉编译器,变量在程序的其他位置定义,先不要给其分配空间。

extern int i;       //声明但是不定义i
extern int a[]; //因为不定义,编译器不需要知道数组的大小

将共享变量的声明放到头文件中在需要的地方include,只在一个地方定义。

保护头文件

如果源文件包含同一个头文件两次将产生编译错误。当头文件包含其他头文件时这种问题十分普遍。解决这个问题的方案是:

//boolean.h中加上:
#ifndef BOOLEAN_H
#define BOOLEAN_H

...

#endif

把程序划分成多个文件

  • 将每个函数集合放入一个不同的源文件中
  • 每类函数集有两个文件:X.h和X.c,它们一一对应
  • X.c包含X.h
  • main函数出现在和程序同名的文件中

构建多文件程序

和单文件程序类似,多文件程序为:

  • 编译:必须对程序中每个 .c 文件进行编译(.h文件因为被包含了会自动被编译)。每个源文件会参数一个目标文件,UNIX系统的扩展名是.o,在Windows中是.obj
gcc -o application_name a1.c a2.c ...
  • 链接:链接器吧目标文件和库文件结合在一起生产可执行文件

makefile

每次将所有的源文件的名字放在命令行中不可能工程化,而且如果重新编译所有源文件而不是仅仅修改过的源文件将浪费很多时间。 UNIX系统发明了makefile的概念:包含构建程序的必要信息。

application_name: application.o a.o b.o
gcc -o application_name application.o a.o b.o

application_name.o: application.c a.h b.h
gcc -c application.c //编译成目标文件但是不链接

a.o: a.c a.h
gcc -c a.c

b.o: b.c b.h
gcc -c b.c

示例中有4组代码行,每组是一条规则。 每条规则:

  • 第一行给出目标文件,后面跟着其所依赖的文件
  • 第二行是待执行的命令

根据上面的规则就可以得出某个文件修改后哪些文件需要重新编译。

更多规则:

  • 每个命令前都要有一个制表符
  • makefile通常存储在一个名为Makefile或者makefile中,make命令会自动在当前目录搜索这些文件
  • 用下面的命令调用make
make  目标 //根据makefile编译当前程序
  • 如果make没有定目标文件,默认使用makefile中第一条规则的目标文件
  • 除了第一条规则比较特殊,其他的规则的顺序是任意的。

在程序外定义宏

gcc -DDEBUG=1 foo.c
//==>
#define DEBUG 1