目录
二、需要一个媒介——头文件,才能知道函数到底要什么,又返回什么
一、问题,我们在max.c里面声明的一个全局变量gAll怎么让main.c也知道有这个全局变量呢?
下一篇:c语言格式化的输入输出
多个.c文件
一、why?
main()里的代码太长了适合分成几个函数
一个源码文件太长了适合分成几个文件
两个独立的源代码文件不能编译形成可执行的程序
二、如何将多个.c组合成一个?
需要使用编译器新建项目,然后把源代码放进去,编译器会把项目中所有的源代码编译后,链接起来
编译器有编译和构建,编译指对单个源码进行编译,后者是对整个项目做链接
编译单元:一个.c是一个编译单元,编译器每次编译只能处理一个编译单元
三、示例
两个文件
max.c的内容
int max(int a,int b)
{
return a>b?a:b;
}
main.c的内容
#include <stdio.h>
int max(int a,int b);
int main(int argc,char const *argv[]){
int a=5;
int b=6;
printf("%d\n",max(a,b));
}
结果
头文件
一、引入
在上一章中
我们在main.c中留下了max函数的声明
在调用的时候,编译器知道我们对max函数的调用是正确的
但我们把声明去掉后?
虽然有红色提示但是还是能编译
还有结果
这是因为c语言古老的传统,会猜测max的参数全是int
假如我们把max.c的int类型改为double还能成功吗?
double max(double a,double b)
{
return a>b?a:b;
}
但是编译又过了,但是里面肯定有问题
结果:
在mian.c和max.c都编译完成后
main中调用max.c的时候会链接max.c,可是传进去的东西错了,传出来的也错了
二、需要一个媒介——头文件,才能知道函数到底要什么,又返回什么
把函数原型放到一个头文件以.h结尾,在需要调用这个函数的源码文件.c中#include这个头文件,就能让编译器在编译的时候知道函数的原型
会发现这里就报错了,因为需要的是double类型传入的是int
将max函数改为int类型后运行正常
当然将%d改为%f,max函数类型是double也没问题
三、关于#include
#include是一个编译处理指令,和宏一样,在编译之前就处理了
它把那个文件的全部文本内容原封不动地插入到它所在的地方
所以也不是一定要在.c文件的最前面#include
现在我们用gcc看编译过程
gcc main.c max.h
使用gcc编译链接
执行成功
现在只编译main.c gcc --save-temps main.c -c
查看main.i tail -n 50 main.i
所以include就是把max.c整个放了进来,注意这里的#是注释
四、include的两种形式<> 还是""
""要求编译器首先在当前目录(.c文件所在的目录)寻找这个文件,如果没有,到编译器指定的目录去找
<>让编译器只在指定的目录去找
编译器自己知道自己的标准库的头文件在哪里
linux和unix的库在这里
windows的在各种编译器下面的include里面
环境变量和编译器命令行参数也可以指定寻找头文件的目录
小结:一般来说自己写的用"",系统的用<>
看看stdio.h里面有什么?开始是版权声明,后面就是各种东西的定义
五、#include的误区
#include不是用来引入库的,做的只有一件事把这个文件原封不动的放进来
stdio.h里面只有printf的原型,printf的代码在另外的地方,某个.lib(Windows下)或.a(Unix)中
现在c语言的编译器默认会引入所有的标准库,当然用不到的会拿掉
#include<stdio.h>只是为了让编译器知道printf函数的原型,保证你调用时给出的参数值是正确的类型
所以说头文件:
在使用和定义(检查你对外宣称的和实际的函数的定义是不是一不一致)这个函数的地方都应该#include这个头文件
一般的做法就是任何.c都有对应的同名.h,把所有对外公开的函数的原型和全局变量(全局变量可以在多个.c中共享)的声明都放进去
那么要是有个函数不希望别人用?
在函数前面叫上static就使得它成为只能在所在的编译单元中被使用的函数
在全局变量前面加上static就使得它成为只能在编译单元(这个.c)中被使用的全局变量
较为推荐的头文件使用方式:
这里的max.c是max函数的原型,max.h是声明
声明
一、问题,我们在max.c里面声明的一个全局变量gAll怎么让main.c也知道有这个全局变量呢?
在max.h中声明
int max(int a, int b);
extern int gAll;
这样做后,我们告诉编译器在整个项目的某个地方有个叫gAll的东西
就可以在main函数中这么做了
printf("%d\n",max(a,gAll));
结果:
但是没有这条声明编译就会报错
int I;是变量的定义
extern int i;是变量的声明,当然不能赋值,定义是定义声明是声明,不能搞混
声明是不产生代码的东西(就是只是默默的记下来)
函数原型
变量声明
结构声明
宏声明
枚举声明
类型声明
inline函数
而定义是产生代码的东西(函数、全局变量)
一个规则:只有声明可以被放在头文件中
是规则不是法律
否则会造成一个项目中多个编译单元有重名的实体
某些编译器允许几个编译单元中存在同名的函数,或者用weak修饰符来强调这种存在
二、关于重复声明
同一个编译单元里,同名的结构不能被重复声明
如果你的头文件里有结构的声明,很难这个头文件不会在一个编译单元里被#include多次
所以需要“标准头文件结构”
例如:
struct Node
{
int value;
char* name;
};
struct Node
{
int value;
char* name;
};
int main(int argc,char const *argv[]){
在一个main中定义两个同名的Node结构就会导致编译报错
当然我们把这个结构放入max.h中只用include就能用这个结构了
这时我们有另一个.h文件
也include了max.h,这是很常见的事情,可能有某个结构什么的要用到
我们同时也在main.c里面include了main.h
就相当于将max.h插入到main.c里面两遍,就产生了前面的问题,重复定义了两次Node
当程序结构很复杂的时候很难避免这样的事情
所以我们这么做
在max.h里面加入橙色的部分
#ifndef _MAX_H_ //判断该文件内有没有定义过这个宏
#define _MAX_H_ //若没有则定义
int max(int a, int b);
extern int gAll;
struct Node
{
int value;
char* name;
};
#endif // !_MAX_H_ 结束 如果已经定义过这个宏就不会放入绿色部分的代码
注意:这种宏的定义的名字最好要加_ _保证和要用到的宏不重名
用这个机制,保证只引入过一次max.h
现在编译就对了
#include <stdio.h>
#include"max.h"
#include"main.h"
int main(int argc,char const *argv[]){
int a=5;
int b=6;
printf("%d\n",max(a,gAll));
return 0;
}
#pragma once 也能起到同样的作用,但是不是所有编译器都支持,例如gcc