Skip to content

Latest commit

 

History

History
136 lines (96 loc) · 8.32 KB

File metadata and controls

136 lines (96 loc) · 8.32 KB

Table of Contents generated with DocToc

头文件

.h中一般放的是同名.c文件中定义的变量、数组、函数的声明,需要让.c外部使用的声明。

头文件和源文件的区别

头文件和源文件在本质上没有任何区别。

  • 只不过一般:后缀为 .h 的文件是头文件,内含函数声明、宏定义、结构体定义等内容。
  • 后缀为 .c 的文件是源文件,内含函数实现,变量定义等内容。而且是什么后缀也没有关系,只不过编译器会默认对某些后缀的文件采取某些动作。 这样分开写成两个文件是一个良好的编程风格。

.c文件和.h文件的概念与联系

//a.h文件

void foo();

//a.c文件

#include "a.h"   //我的问题出来了:这句话是要,还是不要?
void foo()
{
     return;
}

//main.c文件

#include "a.h"
int main(int argc, char *argv[])
{
    foo(); 
  return 0;
} 

三个问题:

  1. a.c 文件中的 #include "a.h" 这句话是不是多余的?--->不一定
  2. 为什么经常见 xx.c 里面 include 对应的 xx.h? -->代码规范
  3. 如果 a.c文件中不写,那么编译器是不是会自动把 .h 文件里面的东西跟同名的 .c 文件绑定在一起? -->不会

正确的概念是:从C编译器角度看,.h和.c皆是浮云,就是改名为.txt、.doc也没有大的分别。换句话说,就是.h和.c没啥必然联系。 .h文件中一般放的是同名.c文件中定义的变量、数组、函数的声明,需要让.c外部使用的声明。这个声明有啥用? 只是让需要用这些声明的地方方便引用。因为 #include "xx.h" 这个宏其实际意思就是把当前这一行删掉,把 xx.h 中的内容原封不动的插入在当前行的位置。 由于想写这些函数声明的地方非常多(每一个调用 xx.c 中函数的地方,都要在使用前声明一下子),所以用 #include "xx.h" 这个宏就简化了许多行代码——让预处理器自己替换好了。

也就是说,xx.h文件只是让需要写 xx.c 中函数声明的地方调用(可以少写几行字),至于 include 这个 .h 文件是谁,是 .h 还是 .c,还是与这个 .h 同名的 .c,都没有任何必然关系。

这样你可能会说:啊?那我平时只想调用 xx.c 中的某个函数,却 include了 xx.h 文件,岂不是宏替换后出现了很多无用的声明?没错,确实引入了很多垃圾,但是它却省了你不少笔墨,并且整个版面也看起来清爽的多。鱼与熊掌不可得兼,就是这个道理。反正多些声明(.h一般只用来放声明,而放不定义)也无害处,又不会影响编译,何乐而不为呢?

分开的原因

  1. 如果在h文件中实现一个函数体,那么如果在多个C文件中引用它,而且又同时编译多个C文件,将其生成的目标文件连接成一个可执行文件,在每个引用此头文件的C文件所生成的目标文件中,都有一份这个函数的代码,如果这段函数又没有定义成局部函数,那么在连接时,就会发现多个相同的函数,就会报错。

  2. 如果在h文件中定义全局变量,并且将此全局变量赋初值,那么在多个引用此头文件的C文件中同样存在相同变量名的拷贝,关键是此变量被赋了初值,所以编译器就会将此变量放入DATA段,最终在连接阶段,会在DATA段中存在多个相同的变量,它无法将这些变量统一成一个变量,也就是仅为此变量分配一个空间,而不是多份空间,假定这个变量在头文件没有赋初值,编译器就会将之放入 BSS段,连接器会对BSS段的多个同名变量仅分配一个存储空间 。

  3. 如果在C文件中声明宏,结构体,函数等,那么我要在另一个C文件中引用相应的宏,结构体,就必须再做一次重复的工作,如果我改了一个C文件中的一个声明,那么又忘了改其它C文件中的声明,这不就出了大问题了,如果把这些公共的东东放在一个头文件中,想用它的C文件就只需要引用一个就OK了!!!这样岂不方便,要改某个声明的时候,只需要动一 下头文件就行了

  4. 在头文件中声明结构体,函数等,当你需要将你的代码封装成一个库,让别人来用你的代码,你又不想公布源码,那么人家如何利用你的库中的各个函数呢??一种方法是公布源码,别人想怎么用就怎么用,另一种是提供头文件,别人从头文件中看你的函数原型,这样人家才知道如何调用你写的函数,就如同你调用printf函数一样,里面的参数是怎样的??你是怎么知道的??还不是看人家的头文件中的相关声明 啊!!!

头文件的内容

  • h文件里应该有什么:常量,结构,类型定义,函数,变量申明。

  • h文件不应该有什么:变量定义, 函数定义。

  • extern问题:

    1. 对于变量需要extern;

    2. 对于函数不需要因为函数的缺省状态是extern的.如果一个函数要改变为只在文件内可见,加static。

  • include的作用-->虽然申明和类型定义可以重复,不过推荐使用条件编译。 简单一句话:在include的地方,把头文件里的内容原封不动的复制到引用该头文件的地方。

    • 其实include的过程完全可以“看成”是一个文件拼接的过程,将声明和实现分别写在h文件及C文件中,或者将二者同时写在头文件中,理论上没有本质的区别。以上是所谓动态方式。

    • 对于静态方式,基本所有的C/C++编译器都支持一种链接方式被称为Static Link,即所谓静态链接。在这种方式下,我们所要做的,就是写出包含函数,类等等声明的头文件(a.h,b.h,...),以及他们对应的实现文件(a.cpp,b.cpp,...),编译程序会将其编译为静态的库文件(a.lib,b.lib,...)。在随后的代码重用过程中,我们只需要提供相应的头文件(.h)和相应的库文件(.lib),就可以使用过去的代码了。相对动态方式而言,静态方式的好处是实现代码的隐蔽性,即C++中提倡的“接口对外,实现代码不可见”。有利于库文件的转发。

作用

1.方便开发:包含一些文件需要的共同的常量,结构,类型定义,函数,变量申明;

  1. 使函数的作用域从函数声明的位置开始,而不是函数定义的位置(实践总结)

3 .提供接口:对一个软件包来说可以提供一个给外界的接口(例如: stdio.h)。

头文件格式说明

#ifndef 头文件名 	//头文件名的格式为"_头文件名_",注意要大写
#define 头文件名

头文件内容

#endif

案例: 头文件main.h

#ifndef _MAIN_H_    //如果没有定义头文件main.h,则执行下面的代码。这是防止重复定义
#define _MAIN_H_	//定义头文件

//下面的代码是头文件的内容
#include<stdio.h>//头文件
#define ADD 1 //宏定义
extern int x; //全局变量
void swap(int a, int b);//函数声明

#endif	//表示头文件结束

源文件关联头文件

  1. 系统自带的头文件用尖括号括起来,这样编译器会在系统文件目录下查找。
#include <xxx.h>
  1. 用户自定义的文件用双引号括起来,编译器首先会在用户目录下查找,然后在到C++安装目录(比如VC中可以指定和修改库文件查找路径,Unix和Linux中可以通过环境变量来设定)中查找,最后在系统文件中查找。
#includexxx.h