C++ 之头文件声明定义

himg

最近在学习 c++, 在编译与连接过程当中遇到了一些定义与声明的问题, 通过多处查阅资料, 基本解惑. 现记录与此, 但愿让后面人少走些弯路.html

C++ 的头文件应该用什么扩展名?

目前业界的经常使用格式以下:c++

  • implementation file
    • *.cpp
    • *.cc
    • *.cc
    • *.c
  • header file
    • *.hpp
    • *.h++
    • *.hh
    • *.hxx
    • *.h

一句话: 建议 源文件使用 .cpp, 头文件使用 .hpp程序员

关于 implementation file 并无什么说的, 使用 .cpp/.cc 都是能够的. 可是 header file 须要注意.vim

c 的头文件格式是 .h, 认为 h 表明 header, 因而有不少人也喜欢在 c++ 用 .h 做为头文件扩展名. 其实扩展名并不影响编译结果, 对于编译器来讲扩展名是不重要的 (甚至使用 .txt 也能够). 可是若是在一个 cc++ 混合使用的大型项目中, 你很难马上分辨出这是一个 cppheader file 或者是一个 cheader file; 此外, 在 vim 或者 vscode 的语法提示插件看来, .h 就是 c 语言的, 那么当你在 c 文件写了 cpp 的某些语法固然会提示不正确 (固然确定仍是能够编译经过的)数组

所以, 我认为最好的处理结果就是若是 header file 中涉及到了任何 c++ 的语法, 那么这个头文件就应该以 .hpp 为后缀, 不然都已 .h 为后缀markdown

implementation fileheader file 写什么内容

理论上来讲 implementation fileheader file 里的内容, 只要是 c++ 语言所支持的, 不管写什么均可以的, 好比你在 header file 中写函数体, 只要在任何一个 implementation file 包含此 header file 就能够将这个函数编译成 object 文件的一部分 (编译是以 implementation file 为单位的, 若是不在任何 implementation file 中包含此 header file 的话, 这段代码就形同虚设), 你能够在 implementation file 中进行函数声明, 变量声明, 结构体声明, 这也不成问题!!!函数

那为什么必定要分红 header fileimplementation file 呢? 为什么通常都在 header file 中进行函数, 变量声明, 宏声明, 结构体声明呢? 而在 implementation file 中去进行变量定义, 函数实现呢?oop

缘由以下:  学习

  1. 若是在 header file 中实现一个函数体, 那么若是在多个 implementation file 中引用它, 并且又同时编译多个 implementation file, 将其生成的 object file 链接成一个可执行文件, 在每一个引用此 header fileimplementation file 所生成的 object file 中, 都有一份这个函数的代码, 若是这段函数又没有定义成局部函数, 那么在链接时, 就会发现多个相同的函数, 就会报错.ui

  2. 若是在 header file 中定义全局变量, 而且将此全局变量赋初值, 那么在多个引用此 header fileimplementation file 中一样存在相同变量名的拷贝, 关键是此变量被赋了初值, 因此编译器就会将此变量放入 DATA 段, 最终在链接阶段, 会在 DATA 段 中存在多个相同的变量, 它没法将这些变量统一成一个变量, 也就是仅为此变量分配一个空间, 而不是多份空间, 假定这个变量在 header file 中没有赋初值, 编译器就会将之放入 BSS 段, 链接器会对 BSS 段 的多个同名变量仅分配一个存储空间.

  3. 若是在 implementation file 中声明宏, 结构体, 函数等, 那么若是要在另外一个 implementation file 中引用相应的宏, 结构体, 就必须再作一次重复的工做, 若是我改了一个 implementation file 中的一个声明, 那么又忘了改其它 implementation file 中的声明, 这不就出了大问题了, 若是把这些公共的东西放在一个头文件中, 想用它的 implementation file 就只须要引用一个就 OK 了!

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

头文件如何来关联源文件

已知 header file a.h 声明了一系列函数 (仅有函数原型, 没有函数实现), b.cpp 中实现了这些函数, 那么若是我想在 c.cpp 中使用 a.h 中声明的这些在 b.cpp 中实现的函数, 一般都是在 c.cpp 中使用 #include "a.h", 那么 c.cpp 是怎样找到 b.cpp 中的实现呢?  

编译器预处理时, 要对 #include 命令进行 文件包含处理: 将 a.h 的所有内容复制到#include "a.h" 处. 这也正说明了, 为何不少编译器并不 care 到底这个文件的后缀名是什么 - 由于 #include 预处理就是完成了一个 复制并插入代码 的工做.  

程序编译的时候, 并不会去找 b.cpp 文件中的函数实现, 只有在 link 的时候才进行这个工做. 咱们在 b.cppc.cpp 中用 #include "a.h" 其实是引入相关声明, 使得编译能够经过, 程序并不关心实现是在哪里, 是怎么实现的. 源文件编译后成生成 obj file, 在此文件中, 这些函数和变量就视做一个个符号. 在 link 的时候, 须要在 makefile 里面说明须要链接哪一个 obj 文件 (在这里是 b.cpp 生成的 .obj 文件), 此时, 链接器会去 .obj 文件中找在 b.cpp 中实现的函数, 再把他们 buildmakefile 中指定的那个能够执行文件中.  

Clion 中, 通常状况下不须要本身写 makefile, 只须要将须要的文件都包括在 project 中, Clion 会自动帮你把 makefile 写好.  

一般, 编译器会在每一个 .o.obj 文件中都去找一下所须要的符号, 而不是只在某个文件中找或者说找到一个就不找了. 所以, 若是在几个不一样文件中实现了同一个函数, 或者定义了同一个全局变量, 连接的时候就会提示redefined.

什么是声明? 什么是定义?

  • 根据 cpp 标准的规定, 一个变量声明必须知足两个条件, 不然就是定义:
    1. 必须使用 extern;
    2. 不能为变量赋予初始值;
  • 一个变量 / 函数能够被多处声明, 可是只能定义在一处;

是定义仍是声明与其位于 header file 仍是 implementation file 无关.

根据以上规定, 咱们能够有以下的结论:

extern int a; // 声明
int a; // 定义
int a = 0; // 定义
extern int a = 0; // 定义
复制代码

许多程序员对定义变量和声明变量混淆不清, 定义变量和声明变量的区别在于定义会产生内存分配的操做, 是汇编阶段的概念; 而声明则只是告诉包含该声明的模块在链接阶段从其它模块寻找外部函数和变量.

如何跨文件使用全局变量 / 全局函数?

咱们在编译模块中的任意一个文件中书写的变量/函数在此模块中其余文件中均可以被访问到, 可是其余编译模块的文件是没有访问此变量的权限的. 那么如何跨模块共享变量 / 函数呢?

答案就是使用 extern. 在这里请在作的各位紧紧记住它的定义: 标示所修饰的变量或函数的可能位于其余模块.

必定要紧紧记住上面的定义, 带着定义咱们就能够想明白如下问题

  • 为何在一个 implementation file 中使用一个外部变量要先 extern 声明该变量 (或者导入该变量所在的 header file)?
  • 为何 header file 中要使用 extern 声明一个变量?

全局变量

这样当咱们编译某个单元时, 编译器发现了使用 extern 修饰的变量, 若是正好本模块中有其相关定义, 那么就直接使用; 若是没有相关定义, 那么就挂起, 在编译后续其余模块的时候进行查找, 若是到最后尚未找到, 那么在连接阶段就会报错 ld: symbol(s) not found for architecture x86_64;

正确方式

  1. test1.hpp 中声明 extern int a;
  2. test1.cpp 中定义 int a = 10; (或者使用 int a; 定义, 这样的话值是默认值 0)
  3. test2.cpp#include "test1.hpp", 这样即可以在 test2.cpp 中直接使用 a 变量了.

错误方式 1

在头文件 test1.hpp 中直接 extern int a = 10;

这样属于在头文件中直接定义, 咱们已经说了 一个变量能够被多处声明, 但只能定义在一处, 在这种状况下若是有多个 implementation file#include "test1.hpp", 那么会形成在 obj 文件的 连接 阶段发现多处存在同一个变量的定义, 这时会报错 ld: 1 duplicate symbol for architecture x86_64

同时, 在头文件中定义一个变量属于很是业余的作法, 请不要争相模仿

错误方式 2

在头文件 test1.hpp 中 直接 extern int a = 10;, 在 test2.cpp 中直接使用 extern int a;(没有 #include test1.hpp)

这样作能够避免多处重复定义的问题, 可是这样的话 test1.hpp 定义的其余变量与方法都不可使用了, 必须所有使用 extern *** 的形式进行声明而后使用, 这样会及其得不偿失.

总结

因此咱们能够得出结论:

  • 只在头文件中作声明

真理老是这么简单!

全局函数

函数与变量相似, 也分为定义与声明. 可是与变量在声明时必需要包含 extern 不一样, 因为函数的定义和声明是有区别的, 定义函数要有函数体, 声明函数没有函数体, 因此函数定义和声明时均可以将 extern 省略掉, 反正其余文件也是知道这个函数是在其余地方定义的, 因此不加 extern 也行.

因此在 cpp 中, 若是在一个函数前添加了 extern, 那么仅表示此函数可能在别的模块中定义; 或者也可让咱们在只使用了某个头文件的这个方法时不用 #include <***.hpp>

static 使用

static 用于修饰类中的变量/函数

是一个静态成员变量/函数

  • 类加载的时候会分配内存
  • 能够经过类名直接访问

static 用于修饰类以外的变量/函数

是一个普通的全局静态成员变量/函数

  • 用于修饰变量时表示其存储在全局(静态)区, 不存储在栈上面;
  • 只对本编译模块有效(即便在外部使用 extern 声明也不能够), 不是真正意义的全局(普通的函数默认是 extern 的)
  • 声明与定义时同时发生的
  • 当局部变量不想在函数结束时被释放的时候可使用 static, 好比函数中要返回一个数组, 不想让这个数组函数结束时被释放, 那么可使用 static 修饰此局部变量

static 使变量只在本编译模块内部可见, 这样的话若是两个编译模块各自都有一个 value 变量的话, 那么千万不要将两个编译模块内 static 修饰的变量认为是同一分内存, 他们其实是两分内存, 修改其中一个不会影响另一个

static 针对的做用域是编译模块, 如何理解?

  • 若是一个 implementation file 及其全部的 #include ... 文件内所组成的一个编译模块中有多个 static int a = 0, 那么会报错 error: redefinition of 'a'
  • 若是test.hppstatic int a = 0, test1.cpptest2.cpp 分别都有 #include "test.hpp", 那么这就是两个编译模块各有一个 static int a, 这时是 cpp 容许的, 能够顺利经过编译并运行的

const

const 单独使用时它就与 static 相同, 而当与 extern 一块儿合做的时候, 它的特性就跟 extern 的同样了

ifndef 的使用与意义

#ifndef 能保证你的头文件在本编译模块只被编译一次(可是多个模块都编译此段代码的话则仍是会有重复代码)

总结一些 头文件 & 声明 & 定义 的规则

  1. header file 中是对于该模块接口的声明, 接口包括该模块提供给其它模块调用的外部函数及外部全局变量, 对这些变量和函数都需在 header file 中冠以 extern 关键字声明
  2. 模块内的函数和全局变量需在 implementation file 开头冠以 static 关键字声明
  3. 永远不要在 header file 中定义变量
  4. 若是要用其它模块定义的变量和函数, 直接 #includeheader file 便可.

若是工程很大, 头文件不少, 而有几个头文件又是常常要用的, 那么

  1. 把这些头文件所有写到一个 header file 里面去, 好比写到 preh.h
  2. 写一个 preh.cpp, 里面只一句话: #include "preh.h"
  3. 对于 preh.c, 在 project setting 里面设置 create precompiled headers, 对于其余 c++ 文件, 设置 use precompiled header file

参考

相关文章
相关标签/搜索