说明:
本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。
QQ 群 号:513683159 【相互学习】
内容来源:
CSDN-官方认证平平无奇说废话小天才-C语言编写头文件
CSDN-mirror207-C语言头文件编写的几个基本规则
CSDN-懒羊羊是程序猿-如何理解头文件?
上一篇:基础知识:篇1-单源文件编译过程
下一篇:基础知识:篇3-静态库与动态库
目录:
一、头文件的相关描述:
在实现C语言模块化编程时,通常会用到*.h式的头文件,那头文件的作用是什么?头文件有什么固定格式?
1、头文件的作用
1.定义可用的函数列表,方便查阅可调用的函数
2定义宏定义(一些全局静态变量的定义),方便只修改头文件内容,程序便可做相应修改,无需修改源文件。
3.头文件只是声明,不占内存空间,要知道其执行过程(具体函数定义),需进入对应的源文件里才可看到。
4.源文件若包含某头文件,就等于赋予调用某些函数的权限。
2、头文件的格式
头文件一般包含:函数声明,变量声明,常数定义,宏的定义等。
【头文件通用格式】
//第一模块:注释,版权,作者,重大修订记录等信息
/*
注释,版权,作者,重大修订记录等信息
*/
//第二模块: 防重入开关,也就是常见的 #ifndef… #define… #endif
#ifndef HEAD_FILE_NAME
#define HEAD_FILE_NAME
/① ifndef = if not define
/② 表示:若没有定义HEAD_FILE_NAME(标识符)则定义标识符HEAD_FILE_NAME并包含头文件内容。
/ 若定义HEAD_FILE_NAME(标识符)则不包含头文件内容
/ 作用:故当多个源文件重复包含同一个头文件时,不会多次包含相同内容(不会造成大量声明冲突)。
/③ 标识符命名:自由命名但需唯一,常用规则:头文件名全大写,前后和.均为_.
/ 如:delay.h 可写为 _DELAY_H_
//第三模块:C++ 编译器自适应开关,也就是常见的 #ifdef __cplusplus… extern “C” { } #endif
/若编译器是C++编译器,而调用的函数是C编译器生成的库,则需要该机制,否则编译后连接会提示找不到该函数
/① extern "C" {...}
/表示:为C++编译器下正确链接到C库函数
/② __cplusplus 为所有C++编译器同意的宏定义。
/故#ifdef _cplusplus 表示:若C++编译器则执行下面内容
//第四模块: include 所有该文件中所使用的其它接口头文件,两层含义:
//一是说头文件应做到自包含,即使用头文件的用户不需要再为该头文件 include 其它头文件;
//二是从模块耦合内聚角度来说,头文件中本身不应该 include 太多其它头文件,一般就是通用数据类型定义,include 其它头文件意味着强耦合——引用了其它头文件中的类型定义,宏或是函数
/包含头文件,可有可无(不推荐头文件包含头文件)
#include<stdio.h>
//第五模块: 接口声明及注释,包括函数,结构体等,
// 注意:不应出现全局变量和static 类型的接口,这些应放置在源文件中。
//函数注释:应包括功能说明,参数使用方法,可能的返回值,及其它注意事项。
//结构体注释:应包括每个成员变量所表示的含义
/函数声明,可有可无
void abcfun(int a,int b);
/结构体声明,可有可无
typedef struct{
int a;
}ABC;
/宏定义,可有可无
#define MAX 100
#define MIN 0
#endif
3、头文件的调用方式 ( include <> 与include “” 的区别?)
#include < >
引用的是编译器的类库路径里面的头文件。
#include " "
引用的是程序目录的相对路径中的头文件,若在程序目录没有找到引用的头文件则到编译器的类库路径的目录下找该头文件。
二、情景说明:
ubuntu16.04-server下利用gcc编译器进行多源文件编译后并执行。
第一步:创建文件
1️⃣mymath.c文件
#include<stdio.h>
#include "add.h"
#include "sub.h"
int main(int argc, char **argv)
{
int a = 10 ,b = 5;
printf("%d + %d = %d\n",a,b,add(a,b));
printf("%d - %d = %d\n",a,b,sub(a,b));
return 0;
}
2️⃣add.c文件
#include "add.h"
int add(int a, int b)
{
return a + b;
}
3️⃣add.h文件
#ifndef _ADD_H_
#define _ADD_H_
int add(int a, int b);
#endif
4️⃣sub.c文件
#include "sub.h"
int sub(int a, int b)
{
return a - b;
}
5️⃣sub.h文件
#ifndef _SUB_H_
#define _SUB_H_
int sub(int a, int b);
#endif
以上五个文件与以下文件等效
#include<stdio.h>
int add(int a,int b);
int sub(int a,int b);
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int main(int argc, char **argv)
{
int a = 10 ,b = 5;
printf("%d + %d = %d\n",a,b,add(a,b));
printf("%d - %d = %d\n",a,b,sub(a,b));
return 0;
}
第二步:编译文件
情景一:该五个文件均在同一文件夹/home/xsndz/mymath。
输入指令:gcc mymath.c add.c sub.c -o mymath
情景二:mymath.c、add.c、sub.c位于/home/xsndz/mymath
add.h、sub.h位于/home/xsndz/mymath/include
输入指令:gcc mymath.c add.c sub.c -I ./include/ -o mymath
PS:
①-I 绝对路径/相对路径
:指定头文件的位置
(若不在同一文件夹下且没有指定头文件位置则会报错)
②生成可执行文件后,删除头文件/源文件,对头文件无影响。
第三步:执行文件
输入指令:./mymath
则可在屏幕上打印出:
10 + 5 = 15
10 - 5 = 5
三、编译过程详解(第二步的详细说明):
1️⃣预编译过程
输入指令:gcc -E mymath.c add.c sub.c -I ./include/
不会输出对应的预编译文件(并不会产生.i文件),但会直接输出到屏幕。
PS:
gcc选项-o
与-c、-S、-E
搭配时不能有多个输入文件。
原文提示:gcc: fatal error: cannot specify -o with -c, -S or -E with multiple files
2️⃣编译过程
输入指令:gcc -S mymath.c add.c sub.c -I ./include/
会输出对应的汇编文件:mymath.s、add.s、sub.s
3️⃣汇编过程
输入指令:gcc -c mymath.c add.c sub.c -I ./include/
会输出对应的目标文件:mymath.o、add.o、sub.o
4️⃣链接过程
输入指令:gcc mymath.c add.c sub.c -I ./include/
会输出对应的可执行文件:a.out
PS:
gcc选项:
-v选项:显示制作GCC工具自身时的配置命令;同时显示编译器驱动程序、预处理器、编译器的版本号。
-o选项:任意阶段均可使用,指定文件名。
-E选项:提示编译器执行完预处理就停下来,后边的编译、汇编、链接就先不执行了。
-S选项:提示编译器执行完编译就停下来,不去执行汇编和链接了。
-c选项:提示编译器执行完汇编就停下来。
注意:-E、-S、-c选项相当于是限定了编译器执行操作的停止时间,而不是单独的将某一步拎出来执行。
四、验证:
情景一:主函数中添加未在头文件声明的函数:(源文件定义,头文件未声明)
1️⃣修改mymath.c文件
#include<stdio.h>
#include "add.h"
#include "sub.h"
int main(int argc, char **argv)
{
int a = 10 ,b = 5;
printf("%d + %d = %d\n",a,b,add(a,b));
printf("%d - %d = %d\n",a,b,sub(a,b));
add_printf(a,b);
return 0;
}
2️⃣修改add.c文件
#include<stdio.h>
#include "add.h"
int add(int a, int b)
{
return a + b;
}
int add_printf(int a, int b)
{
return printf("%d + %d = %d\n",a,b,a+b);
}
3️⃣编译文件
输入指令:gcc mymath.c add.c sub.c -I ./include/
报错信息如下:
原因:main函数未找到add_printf()函数。(头文件中未定义)
解决方案:修改add.h文件
#ifndef _ADD_H_
#define _ADD_H_
int add(int a, int b);
int add_printf(int a,int b);
#endif
情景二:主函数中添加未具体定义的函数(头文件声明,但源文件未定义)
1️⃣修改mymath.c文件
#include<stdio.h>
#include "add.h"
#include "sub.h"
int main(int argc, char **argv)
{
int a = 10 ,b = 5;
printf("%d + %d = %d\n",a,b,add(a,b));
printf("%d - %d = %d\n",a,b,sub(a,b));
add_printf(a,b);
return 0;
}
2️⃣修改add.h文件
#ifndef _ADD_H_
#define _ADD_H_
int add(int a, int b);
int add_printf(int a,int b);
#endif
3️⃣编译文件
输入指令:gcc mymath.c add.c sub.c -I ./include/
报错:
原因:链接过程失败,没有链接到该函数的相关定义。
解决方案:修改add.c文件
#include<stdio.h>
#include "add.h"
int add(int a, int b)
{
return a + b;
}
int add_printf(int a, int b)
{
return printf("%d + %d = %d\n",a,b,a+b);
}
总结:
以上两种情景证明,若以头文件,源文件方式进行链接,缺少任一都会造成编译出错,无法生成可执行文件。
即:这边的链接过程会将程序所需函数都整合到可执行文件中,一旦生成可执行文件,你删除头文件或源文件均不会对可执行文件有影响。
文件大小:8760