大家好,先做个自我介绍,我是天蓬,欢迎阅读本篇博文。

由于本人理解能力不是很好,阅读他人文章时,常常看得晕头晕脑,这让我很是头疼,我想,世界上一定还有和我一样的人(哈哈,不是说你么笨哦)。所以,我将会立足于读者的角度,以读者思维来进行讲解,如果你阅读完后觉得还不错的话,可以点个赞鼓励一下,当然如果对你没有任何帮助,你也可以在评论区吐槽我,让我意识到有哪些地方需要改进。

好了,我们步入正题,今天来说说C语言中的头文件的作用,到底什么时候需要我们自己写一个头文件,如何来写,以及动态库和静态库的扩展,带着这些疑问我们往下看。


目录

1.什么时候需要头文件

2.如何自定义头文件,以及定义头文件时的注意事项

3.扩展:动态库与静态库

4.总结


1.什么时候需要头文件

首先,来看一个最基本的程序。

#include <stdio.h>int main(int argc, char const *argv[])
{printf("hello world");return 0;
}

这里要是没了头文件,一定会编译不过。理由很简单,我们使用了printf()函数,而这个函数就是在#include <stdio.h>这个头文件中定义的。只不过,这个头文件和这个函数并不需要我们去编写,这是由于在C语言标准中就已经帮我们实现好了的,当然C语言标准中并不只是提供了这一个头文件。当我们需要使用某个函数我们去查询对应的头文件,然后引入即可(linux中的man手册是个好动西)。

所以,通过这里我们知道,什么时候需要头文件?当然是我需要调用某个函数时,而这个函数是已经定义好的,我们直接引用对应的头文件即可(相当于将头文件中的内容引入到当前文件,如果你还不熟悉头文件或者没有尝试过自己编写,请继续往下看);


2.如何自定义头文件,以及定义头文件时的注意事项

继续看下面的代码。

#include <stdio.h>int add(int a, int b);int main(int argc, char const *argv[])
{printf("hello world\n");int a=500;int b=20;printf("%d\n", add(a,b));return 0;
}int add(int a, int b){return a+b;
}

这里我们实现两个整数相加,定义了一个名为add的加法函数,并在main函数前声明。对于才开始学习C语言的人来说,经常会以这种方式来编写函数,但是,你有没有想过,写一个这样的函数还好,万一你要自定义一百个,一千个或者更多的函数,难道我们还是得在同一个.c中写吗,显然这样是十分不规范的。
        当我们需要自定义函数时,我们通常会将函数单独编写,并书写对应的头文件即可。这样既符合规范,提高代码的重用性,也便于我们去维护代码,毕竟,在一些复杂的项目中,一份易于维护的代码非常关键。

那么,我们该如何将这个函数单独编写,并书写对应的头文件?

(1)编写一个c文件,这里我叫做myadd.c,然后实现加法函数。

int add(int a, int b){return a+b;
}

很简单,我们将add()函数的实现写入到myadd.c中。

(2)编写对应的头文件,myadd.h,最好是和c文件名对应,如果你偏不这样干,那么也是完全可以的。

#ifndef _MYADD_H_
#define _MYADD_H_int add(int a, int b);#endif

头文件中写入函数的声明即可。这里的ifndef,define为条件编译,是为了防止函数等被重复定义。

(3)主函数中,引入头文件即可。

#include <stdio.h>
#include "myadd.h"int main(int argc, char const *argv[])
{printf("hello world\n");int a=500;int b=20;printf("%d\n", add(a,b));return 0;
}

整个目录结构如下:


       然后,我们使用gcc main.c myadd.c -o main 编译运行即可。

通过以上三步,就完成了自定义头文件和源文件的编写,是不是非常简单。细心一点的读者可能会发现,myadd.c中就只有函数的实现,是不是少了点什么,的确,按照惯例来说,它缺少了对自身头文件的引用。(其实,从这里我们可以看出,头文件和源文件除了函数名称对应以外,并没有其它的任何关联,即头文件的定义和源文件是相互独立的,是两个文件,真正的关联是在函数运行过程中进行的,也就是说,我们程序运行时,当我们调用到这个函数后,才会通过你调用的函数名称去寻找对应的函数并执行(即函数的入栈),但是在编译阶段,编译器并不关心这个函数的实现,它只关心有没有这个函数,比如这里调用的add加法函数,在编译阶段,编译器找到了在myadd.c中已经定义了这个函数,编译器就认为是没有问题的,至于你有几个参数,参数是什么类型,它才不会管)。由于C语言中没有涉及多态的概念,这就从本质上决定了和C++的编译方式是有区别的,那是什么区别?

C语言中,绝不允许同名函数的存在,即使参数不同也不行。但是C++中就可以。比如,在C语言中,写一个整数的加法和浮点数的加法,那么就意味着你必须定义两个函数名称不同的方法。而在C++中大可不必,这是由于C语言编译时,并不会去对参数做具体的判断。那么问题来了,如果不对参数做判断,那我们有一天突然在写代码时,打瞌睡写成这个样子了,会怎样?

int add(int a, int b,int c){return a+b;
}

这里我们参数多加了一个,但如果你编译的话,会惊奇的发现居然通过了。但是,我们如果运行这个程序,肯定是会报错的,因为我们调用时只给函数传了两个参数,实际上它却是三个参数。所以,这样的话,如果我们稍不留神,就会导致定义和声明不一致,编译却通过了,这种问题排查起来就会比较麻烦又耗费时间,领导就会来找你麻烦了,那么有没有解决的方式?肯定是有的。

我们在自定义头文件时,引用自己的头文件即可,这样就相当于将函数的定义和声明,放到同一个文本中【别把头文件和c文件想得多高大上,其实就是个普通的问本文件而已,相当于王麻子改了个名叫老王】,如果定义和声明不一致,在编译阶段就会报错 ,将错误扼杀在摇篮当中。如下:

#include "myadd.h"
int add(int a, int b){return a+b;
}

这样,就必须保证函数的定义和头文件中的一致。并且,引用自己的头文件并不只是这个作用,我们如果需要使用头文件中定义的其它内容时,也要引用,这里的其它内容在书写头文件的注意事项中有介绍。所以,我们在源文件中正常情况下都要引用自身的头文件。有很多小伙伴都是只知道要这样写,并不知道这样写的意义是什么,或者理解得不够全面。

这里,我简单的总结一下自定义头文件有哪些注意事项:

a.源文件名和头文件名称保持一致(如myadd.h和myadd.c)

b.源文件中引入自身头文件,这样避免了函数的声明和定义不一致带来的麻烦

c.头文件中只能存在函数的原型(声明),结构体,联合体,宏,枚举,变量。不要将函数定义写在头文件中,当然这样写也不会报错,因为本质上它们都是文本,但是不报错不代表就是对的。

d.include<>,include " "的区别,上面的例子中,我们就是使用了#include "myadd.h",这个和<>的唯一区别就在于搜索路径不同。<>代表从定义的标准库中去查找,而" "代表从当前路径查找,显然,我么定义的myadd.c在当前路径,并不在标准库中。


3.动态库与静态库

通过前面的讲解,我们已经知道如何为源文件添加对应的头文件了。我们之所以要这样做,无非是有两个目的,一是方便自己,使得自己的代码更具有工程思维,也便于维护。二是方便他人,我们可以将写好的源文件和和头文件,提供给别人使用,这样,也可以便于合作开发。但是,现实中,我们并不会直接将源文件提供给别人,而是以库的形式来提供给第三方。

为什么要提供库,而不是直接将源文件和头文件给别人呐?那是因为提供库比直接提供源文件有以下好处:

a.可以保护知识产权;假如我们花费了三个月写的代码,需要提供给其他公司,但是我也并不想其它公司的人可以看到我是如何写的,这个时候就可以将代码封装成库再提供。

b.易于迭代;假如你们公司最近开发了一个软件,并且已经批量的给客户使用了,但是,客户在使用的过程中,突然发现有bug,这个时候怎么办?当然是修改代码,然后修复后重新提供一个新的版本给客户使用就行了。但是,这就意味着,你只是修改了一小点,而用户需要更新全部,下载新的版本,这样显然不可取。可取的是,我们将函数功能封装成库,提供给用户。我们修复bug时,只需要重新生成一个新的库,而不必重新提供整个版本,这就是我们常说的打补丁了。

c.更加高效和规范;你想,我们在开发过程中,常常会 写很多功能相同的函数,如果每一次都要重复去写,那就很费时费力了。通常的做法是,我们把这些功能相同的函数,用库封装起来,下一次编写时,就不用再去书写了,而是直接导入头文件即可,省时省力。

上面说了这么多,也还没说库到底是个啥,我们又如何去将我们的.c生成为库呐?不急我们接着往下看。

先来个三联问:什么是库?库的种类与区别?如何将源文件打包成库?如何使用库?下面我们围绕这几个问题来展开讲解。

(1)什么是库?

库又叫函数库,见名知意,它一般是我们在程序中需要反复使用的一些函数,比如,我们常写的#include <stdio.h>中就有我们常常需要使用的scanf()和printf()函数。

(2)库的种类与区别?

我们一般将库分为动态库和静态库;其中静态库的命名以lib为开头.a为后缀,比如libadd.a;动态库的命名以lib为开头.so为后缀,比如libadd.so。这是命名上的区别。

其次,静态库和动态库的编译方式和载入时机都是不一样的,这里先说载入时机;静态库是在我们编译源文件时,就将静态库和源文件一起编译,生成可执行的二进制文件。 而动态库是在我们程序运行时才会去加载,如果你对C语言编译的四个阶段还不太了解的话,可以先补一下这方面的知识。刚刚说道,静态库时编译时就加载的,动态库运行时加载,那么这就意味着,我们开发的同一个程序,如果采取两种不同的方式,那么编译得到的可执行程序的文件大小一定是静态库大于动态库的,因为动态库编译时并不会加载库文件,只有 运行时才会加载。还值得一说的是,既然静态库编译时已经加载了,那是不是编译后就不需要它,程序就可以独立运行了呐?的确如此。相反,动态库是运行时才加载,所以如果程序运行时找不到这个动态库,那么一定会出现异常。在项目中要根据实际情况来决定具体要使用哪一种库,没有绝对的好坏之分。看了这些,如果你觉得有点抽象,那么你接着看看他们是如何制作与使用后,你就会恍然大悟了。

(3)如何将源文件打包成库?

还是沿用之前的例子,以加法为例来演示动态库和静态库的制作过程,我们这里简单介绍一下制作过程。这里还是再看看整个代码结构。

一个主函数main.c,myadd.c里面是一个加法函数,myadd.h是对应的头文件,以此为例。

静态库:将myadd.c打包成静态库

a.gcc myadd.c -o myadd.o -c【myadd.o为重定向文件,如果你不知道这个的话,建议先熟悉一下C语言编译的4个过程】执行后会在当前文件生成一个myadd.o的新文件。如果我们有多个.c怎么办,简单,分别执行这条命令,myadd.o换成对应的名字即可。

b.ar cr libadd.a myadd.o 【ar 为打包工具,cr为参数,如果需要请自行查询即可,再次说明man手册是非常强大的,如果你还不会,执行一下 man ar会有关于ar命令的详解;这里的libadd.a是我们根据静态库的命名规范自定义的一个名称;如果有多个重定向文件(.o)怎么办,末尾继续追加就可】这样就得到了名为libadd.a的库函数。如图:

动态库:将myadd.c打包成动态库

a.gcc  -shared -fPIC  -o libadd.so  myadd.c 【-shared表示生成动态库,fPIC为参数,libadd.so为动态库的命名规范,myadd.c表示要编译的源文件,如果有多个源文件要同时编译问一个动态库,直接后面追加即可】一步到位,就是这么简单,来看看生成的文件,如图:

通过上述过程,我们知道了如何将原文件打包为库文件,有了库文件我么如何使用呢?

(4)如何使用库?

我们的函数入口为main.c,里面我们调用了加法add函数,现在我么就可以不依靠myadd.c来编译代码了,因为myadd.c被编译成了库函数。现在我们删除myadd.c和myadd.o,中保留主函数,头文件和库函数,来看看如何利用库函数来编译主函数的?

编译命令:gcc main.c -o main -I ./ -L ./ -ladd 【-I 表示头文件的路径名,头文件在同一路径,所以使用./代替,-L表示库的路径,这里也在当前路径下,所以也使用./,最后的-ladd,其中l是必须的表示需要的库,add表示去掉前缀lib和后缀.so或者.a后的库名,不能分开书写】执行后即可生成main可执行文件。

【注意!】因为无论是使用静态库编译或者使用动态库编译我们都是使用的同一命令,所以在编译的时候最好是先将一个库拷贝出来,让这个目录下只含有一个库,或者说将其中一个放到其它文件下,我们使用-L 时加上相对路径就好。通过以上操作,我们就使用库编译好了可执行的二进制文件,现在,如果你是编译的静态库,你就可以只保留可执行的二进制文件,然后就可以正常运行了,但是,如果你是编译的动态库的话,你会发现运行的时候就会失败,这是为什么呐?这是因为,我们说过,使用静态库编译是在编译时就将源文件和库一起编译,生成可执行的二进制文件,固然它是可以独立运行的;但是,动态库是在运行时才会去加载[注意,我们编译生成二进制文件时也需要动态库的支持,而且它加载的路径是在/usr/lib或者/lib/目录下,当程序运行的时候,就会去这两个路径下去找对应的动态库了,所以我们将他拷贝到这两个目录的其中一个就可以运行了。此外,还可以添加环境变量的方式,将库放在任何目录下,这里不建议这样做,但是还是说一下,我们只需要向bashrc的末尾添加 export LD_LIBRARY_PATH=动态库的绝对路径   然后保存重启或者使用source一下就可以了,这样以后去搜索库时,除了搜索/usr/lib/,/lib/目录下,还会搜索我们刚刚添加的这个路径,这样的操作是永久生效的,如果你是想临时验证,直接执行 export LD_LIBRARY_PATH=动态库的绝对路径  这个命令就好了,如果你不知道环境变量,或者source这些概念,甚至你还不会linux基本命令,那么就需要补一下这方面的知识了。通过上面的操作,我们使用动态库编译的程序就可以运行了,现在,如果你需要升级,更改了函数实现,比如将加法里面再添加一句打印,你想想,是不是直接修改后提供给用户新的库就好了,而不需要向用户提供整个可执行文件了。


4.总结

前面我么说了这么多,总的来说还算详细,如果你还 遇到了其它问题,欢迎评论区和各位探讨。现在,我么们总结一下前面说的:

(1)什么时候需要头文件:当我们使用某个库中的函数,结构体或者变量等,我么就需要使用头文件。这个头文件可以是C语言标准中实现的,也可以是自己实现的。

(2)如何自定义头文件,以及定义头文件时的注意事项:自定义头文件时要在原文件中引入自己的头文件,这样在编译阶段,会检测到函数的声明和定义是否一致,防止错误发生在运行时阶段。头文件要有ifndef,define等条件编译的宏,这是为了防止重复定义。此外要知道头文件中不要出现函数的具体实现,只能是函数声明,宏,结构体,联合体,变量,枚举,还有内联函数,这是一种规范。

(3)动态库和静态库:我们介绍了头文件的一些知识点,就牵涉到了我们如何将自己写的函数打包成库,这里我们需要知道库的种类,使用库和直接使用源文件有何不同,要清楚库的编译方式以及如何去使用它们。

好了,说到这就结束了,我们江湖见。

详解C语言中头文件的作用相关推荐

  1. C语言中头文件怎么写?(本文来源网络,由黑乌鸦进一步完善)

    c语言头文件怎么写?我一直有这样的疑问,但是也一直没去问问到底咋回事:所以今天一定要把它弄明白! 其实学会写头文件之后可以为我们省去不少事情,可以避免书写大量的重复代码.有利于整理思路.使代码脉络更加 ...

  2. 详解MAC硬盘中各个文件夹

    详解MAC硬盘中各个文件夹 打开Macintosh HD你会发现内中有四个文件夹(一般情况下,隐藏文件夹是不可见的,而且,可能会更多,比如安装xcode后会有developer文件夹). 分别有--应 ...

  3. c语言 字符串 strncpy,详解c语言中的 strcpy和strncpy字符串函数使用

    详解c语言中的 strcpy和strncpy字符串函数使用 strcpy 和strcnpy函数--字符串复制函数. 1.strcpy函数 函数原型:char *strcpy(char *dst,cha ...

  4. 武林c语言,详解C语言中条件编译

    预处理器提供条件编译,程序的不同部分可以在不同的条件下编译,从而产生不同的目标代码文件,这对于程序移植和调试非常有用,本文是武林技术频道小编给为大家带来的详解中条件编译,一起来了解一下吧! 通常情况, ...

  5. java调用项目中的文件_详解eclipse项目中.classpath文件的使用

    1 前言 在使用eclipse或者myeclipse进行java项目开发的时候,每个project(工程)下面都会有一个.classpath文件,那么这个文件究竟有什么作用? 2 作用 .classp ...

  6. C语言中头文件和源文件的注意事项

    C语言中头文件和源文件的注意事项 文章目录 C语言中头文件和源文件的注意事项 0.前言 1.实现步骤 1.1 拆分前 1.2 拆分后 2.总结 2.1头文件内容 2.2 源文件(.c)内容 2.3 函 ...

  7. C语言中头文件包含的处理原则

    很多事不深入以为自己懂了,但真正用到项目上,才发现了问题.曾以为自己写C语言已经轻车熟路了,特别是对软件文件的工程管理上,因为心里对自己的代码编写风格还是有自信的.(毕竟刚毕业时老大对我最初的训练就是 ...

  8. c语言iomanip头文件的作用,#includeiomanip.h在C语言中代表什么

    满意答案 tracyofme 2013.12.01 采纳率:44%    等级:11 已帮助:4995人 iomanip.h是I/O流控制头文件,就像C里面的格式化输出一样. 在新版本的c++中头文件 ...

  9. 详解C语言中的#define、#undef、#indef、#ifndef、#else、#endif,#if,#elif

    1. 明示常量#define #define为C语言的一个预处理指令,通常用于进行宏定义.每行#define(逻辑行)一般由以下三部分组成,第一部分是#define指令本身,第二部分为宏,第三部分为称 ...

最新文章

  1. xshell连接Linux、ngix部署
  2. laytpl语法_layui语法基础
  3. 我是如何在尼日利亚的沃里创立Google Developers Group GDG分会的,并达到了100位成员...
  4. Centos 6.5下一个SNMP简单配置(snmp protocol v3,监控宝)
  5. python3 发送邮件
  6. Oracle-AWR管理包DBMS_WORKLOAD_REPOSITORY.MODIFY_SNAPSHOT_SETTINGS
  7. python字典 items函数
  8. tomcat 虚拟路径 与 虚拟主机配置
  9. 设计模式C++实现--Decorator模式
  10. 2015-7-24 从新开始博客之路
  11. 持续集成部署Jenkins工作笔记0013---配置远程触发构建的TOKEN值
  12. python 其他语言_谈谈Python和其他语言的区别
  13. 如何加强云端的SSH安全性 TechTarget中国原创内容,原文链接:http://www.searchcl
  14. Jmeter 及 JDK 下载 安装教程
  15. 计算机通信技术【计算机网络】学习
  16. DayDayUp:罗振宇—2018.12.31年终秀——《时间的朋友》跨年演讲重点概览【文字+视频】
  17. Centos7 下配置Samba服务器---犯二的经历
  18. 让0球平局怎么算_古迪逊公园默郡德比,平局德比丨第30轮
  19. 解决服务器上传的tar格式的中不可以解压tar格式的压缩包 zip解压中文会在文件中显示乱码
  20. 高企认定人员及研发费要求?

热门文章

  1. ERP和CRM的分工
  2. 计算机调剂深圳大学2017,深圳大学2017年考研调剂信息
  3. 查看.pb文件的结构
  4. Conway‘s Law
  5. 以“国土资源云”统领国土资源信息化建设
  6. 泰山OFFICE技术讲座:同一行不同字号的字如何对齐绘制
  7. 信创办公--基于WPS的Word最佳实践系列(实现标题自动编号)
  8. 汇编语言——第10章 CALL和RET指令
  9. 120xa变频器调试参数_最全的变频器基本参数的调试都在这了,看过的电工都学会了!...
  10. 简述计算机仿真的基本步骤,计算机仿真技术试卷