大程序设计
1.多个源代码文件
多个.c文件如何在一起组合使用
- 问题:
- main()里面的代码太长了适合分成多个函数
- 一个源代码文件太长了,适合分成几个文件
- 两个独立的源代码文件不能编译形成可执行程序
我们编写下面的代码:
#include<stdio.h>
int max(int a,int b);
int main()
{
int a=5;
int b=8;
printf("%d",max(a,b));
}
int max(int a,int b)
{
return a>b?a:b;
}
加入此时的函数过多,我们想将函数独立出来新建成一个.c文件,此时我们将第12行到15行的代码剪切下来,新建一个max.c文件,如下:
此时我们再运行下面的代码:
#include<stdio.h>
int max(int a,int b);
int main()
{
int a=5;
int b=8;
printf("%d",max(a,b));
}
这时候会报错:没法找到max;
- 如何解决:
- 在dev c++里面新建一个项目,然后把几个源代码文件放进去
- 对于项目,Dev C++的编译会把一个项目中的所有源代码文件都编译后,链接起来
- 有的IDE分开编译C和构建两个代码,前者是对单个源代码文件的编译,后者是对整个项目做链接
Dev C++比较特殊,允许建多个.C文件然后建项目,而其他的IDE在多个.C文件的情况下往往要求先建立项目,再往其中添加.c文件。
编译单元
一个.c文件是一个编译单元,编译器每次只处理一个编译单元。
编译完成后会形成.obj文件,.obj文件是目标文件,是编译后形成的二进制文件,在通过链接器和资源管理器链接之后就形成了exe文件了。
我们的IDE在compile时进行编译,build之后才将文件链接起来形成.exe。Dev C++将Compile和Build合并成了一个
2.头文件
特别地,我们注意到,在上面的代码中,我们再main.c文件中保存了函数的声明部分:
int max(int a,int b);
这样的写法保证了程序运行的正确性,假如删掉这行代码,且将max函数中的参数值和返回值类型都更改为double,那么你会发现再次运行这行代码的结果不再正确。
原因是,编译器会自动地猜测传入的参数值都是Int类型,这就与我们的实际相违背了。编译不会有问题,但是链接两个程序,传入的参数类型错误,传出的参数类型也出现了错误,导致我们最后的输出的结果也是错误的。
怎眼保证我们在删除了main函数中的下面代码
int max(int a,int b);
即在这样的情况下:
#include<stdio.h>
//int max(int a,int b);
int main()
{
int a=5;
int b=8;
printf("%d",max(a,b));
}
运行的结果依然是正确的呢?我们需要一个中间媒介——头文件
把函数原型放到一个头文件.h结尾中,在需要调用这个函数的源代码文件(.c文件)中#include这个头文件,就能让编译器在编译的时候知道这个函数的原型。
我们在项目中再新建一个文件,将代码int max(int a,int b);
拷贝到该文件中,新建文件为max.h
紧接着,在main.c和max.c中,我们都加上include"max.h"
这段代码
全部的代码变为:
main.c
#include<stdio.h>
#include"max.h"
int main()
{
int a=5;
int b=8;
printf("%d",max(a,b));
}
max.c
#include"max.h"
int max(int a,int b)
{
return a>b?a:b;
}
max.h
int max(int a,int b);
此时,如果我们不改变max.h中的代码,仅将max.c中的所有的int改为double,然后再运行代码,程序就会出现问题:编译器发现了不一致并报错
include
- #include是一个编译预处理指令,和宏一样,在编译之前就处理了
- 它只做了一件事:把.h文件的全部文本内容原封不动地插入到include语句所在的地方
- 所以也不是一定要在.c文件的最前面#include
""和<>
注意到在上面的代码中,我们在Include之后使用了""和<>两种写法:
#include<stdio.h>
#include"max.h"
有什么区别呢?
这两种方式都是指出include要插入的文件
- “”的写法要求编译器首先在当前目录(.c文件所在目录)寻找这个文件,如果没有,到编译器指定的目录去找
- <>让编译器只在指定的目录去找
- 编译器自己知道自己标准库的头文件在哪
- 环境变量和编译器命令行参数也可以指定寻找头文件命令的目录
简单地说就是一般地系统的头文件使用<>,自己编写的头文件使用“”
include的误区
- include不是用来引进库的
- stdio.h里面只有printf的原型,printf的代码在另外的地方,某个.lib(Windows)或.a(Unix)中
- 现在的C语言编译器会默认引入所有的标准库
- #include<stdio.h>只是为了让编译器知道printf的原型,保证你调用时给出的参数值是正确的类型
include的使用
我们在使用和定义这个函数的地方都应该#include这个头文件,就比如上面的main.c和max.c中,我们都需要加上include"max.h"
这个头文件
使用这个函数的地方加上#include,编译器会帮我们检查对函数的调用是否是正确的,函数的参数是否是正确的
定义这个函数的地方加上#include,编译器会帮我们检查对外宣称的函数的原型和函数真正的原型是不是一致的
一般的做法就是任何的.c(除了main.c)都有对应的同名的.h,把所有对外公开的函数的原型和全局变量的声明都放进去。全局变量是可以在.c文件之间共享的,前提是全局变量的原型也要正确表述。
不对外公开的函数
在函数前面加上static
使得它成为只能在所编译的单元中使用的函数
在全局变量上加上static
使它成为只能在所在的编译单元中被使用的全局变量
3.声明
上面提到全局变量也是可以在.c文件之间共享的,只要对变量进行了正确的声明即可。
仍以上面的代码为例,我们想要在main.c中使用max.c中的gAll参数:
main.c
#include<stdio.h>
#include"max.h"
int main()
{
int a=5;
printf("%d",max(a,gAll));
}
max.c
#include"max.h"
int gAll = 12;
int max(int a,int b)
{
return a>b?a:b;
}
这样我们必须在max.h文件中指明这个全局变量,采用代码:extern int gAll;
进行声明:
变量的声明和变量的定义
- 变量的定义:int i;
- 变量的声明:extern int i;
定义和声明在c语言中是不同的东西,如:函数的声明和函数的定义,再如变量的定义和变量的声明,都是不同的。变量的声明是不进行初始化的。
声明是不产生代码的东西:
- 函数原型
- 变量声明
- 结构声明
- 宏声明
- 枚举声明
- 类型声明
- inline函数
定义是产生代码的东西
- 函数
- 全局变量
声明与头文件
只有声明可以被放在头文件中,这是一条规则,但不是法律
否则会造成一个项目中的多个编译单元里有重名的实体
而当我们的项目较大,头文件较多时,就很难避免不出现重名的实体的情况,这时候我们使用标准头文件指令来解决这个问题:
代码如下:
main.c:
#include<stdio.h>
#include"max.h"
int main()
{
int a=5;
printf("%d",max(a,gAll));
}
max.c:
#include"max.h"
int gAll = 12;
int max(int a,int b)
{
return a>b?a:b;
}
max.h
#ifndef _MAX_H_
#define _MAX_H_
int max(int a,int b);
extern int gAll;
struct Node{
int value;
char* name;
};
#endif
min.h
#include "max.h"
标准头文件指令保证了不吹出现重复命名的实体。
运用条件编译和宏,保证这个头文件在一个编译单元中只会被#include一次
int max(int a,int b)
{
return a>b?a:b;
}
max.h
```c
#ifndef _MAX_H_
#define _MAX_H_
int max(int a,int b);
extern int gAll;
struct Node{
int value;
char* name;
};
#endif
min.h
#include "max.h"
标准头文件指令保证了不吹出现重复命名的实体。
运用条件编译和宏,保证这个头文件在一个编译单元中只会被#include一次
#progrma once也能起到相同的作用,但不是所有的编译器都支持