多文件代码
预处理指令#include
预处理指编译器编译源文件时 不认识函数printf 所以需要先让编译器认识这个函数 就需要预处理指令#include<stdio.h>
#include会将文件<stdio.h>中的代码复制到该预处理指令出现处 并删除该预处理指令 修改后的代码将另存为中间文件或直接输入编译器 然后使用经过预处理后的代码进行编译 编译器先读到了printf的定义 就认识它啦
#include的两种形式
#include<文件名>尖括号 在编译器的包含目录中搜索文件#include"文件名"双引号 先在当前目录中搜索文件再到编译器包含目录中搜索文件
多文件代码
从代码到可执行文件要经历几个过程
- 预处理 执行预处理文件 修改源代码
- 编译 将预处理后的源文件代码转换为二进制目标文件
- 链接 将需要用到的目标文件合并为可执行文件
那么自己写一个print模拟一下printf吧
注意
main.c中不能写#include"print.c" 因为编译器是单个独立编译的 编译完成后目标文件main.obj中含有print函数 print.obj中也有一份print函数 链接时会出现同名函数的现象导致链接失败 我们只需要让编译器识别main.c中的标识符print就好了 因此只需要函数声明 不用定义
更不能#include<print.c> 仅会在编译器包含的目录中搜索文件 而编译器的目录文件中没有文件print.c
头文件
把多个类似print的函数声明写在一个文件里 那谁需要使用这种函数 就只需要包含这个文件
这种文件称为头文件 后缀是.h 不需要经过编译 仅供被其他文件包含
下面这个print的用法就非常类似printf了
系统自带函数的源文件被预先编译了库 编译器默认链接了该库 所以无需其他配置也看不到实现这些函数的源文件
多文件代码举例
main.c
#include "person.h"//人员相关代码的使用者 也需要包含头文件person.h
int main()
{
person p;
p = newperson();
printperson(&p);
return 0;
}
person.c
//将一系列人员相关的函数聚合到一个单独的文件person.c中
#include<stdio.h>
#include"person.h"//文件person.c 需要头文件person.h 中的声明或定义 因此文件person.c需要包含文件 person.h
person newperson()
{
person p;
printf("sizeof person in person.c %d", sizeof(person));
printf("intput name (No more than %d letters): ",name_length);
scanf("%s", p.name);
printf("input gender ( 1.male 2.female) : ");
scanf("%d", &p.gender);
printf("input gender ( 1.male 2.female) : ");
scanf("%d", &p.gender);
printf("intput weight: ");
scanf("%1f", &p.weight);
return p;
}
void printPerson(const person* p)
{
printf(" \nname \tgender\theight\tweight\n");
printf("%s\t%d \t%.2f\t%.2f\n", p->name,p->gender,p->height,p->weight);
}
person.h
//符号常量 宏函数 函数声明 结构声明 类型定义 放置于对应的头文件person.h 中
#define name_length 20//方便修改名字长度限制
typedef struct {
char name[name_length + 1];
int gender;
double height;
double weight;
}person;
person newperson();
void printperson(const person* p);
总结
- 源文件person.c :函数定义。
- 头文件 person.h:符号常量、函数宏、函数声明、结构声明、类型定义。
- 源文件 person.c需要头文件 person.h中的声明或定义。因此,需要在源文件中#include"person.h"
- 使用者,例如文件main.c。包含头文件person.h后,即可使用头文件中的声明或定义以及调用头文件中声明过的函数。
头文件守卫
如果使用了两个头文件 并且两个头文件有交集 就会出现标识符重定义而编译失败
条件编译
可以借助条件编译实现同一个头文件只使用一次的目标
person.h
#ifndef PERSON_H
#define PERSON_H
...
头文件代码
...
#endif
预处理指令#ifndef用于测试其后跟着的宏是否没有被定义。
若没有被定义 则保留从#ifndef到#endif之间的代码
若被定义 则删除从#ifndef到#endif之间的代码
#pragma once指令
如果你的编译器支持#pragma once 指令 也可以在头文件首部使用#pragma once 达到头文件守卫的效果
person.h
#pragma once
...
头文件代码
...