zl程序教程

您现在的位置是:首页 >  后端

当前栏目

C++ :全局变量(extern int a;)、全局函数【可跨文件/模块使用】【只在头文件中做“声明”】

C++文件模块 函数 全局 int 声明 全局变量
2023-09-27 14:20:39 时间

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

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

一定要牢牢记住上面的定义, 带着定义我们就可以想明白以下问题:

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

一、全局变量

这样当我们编译某个单元时, 编译器发现了使用 extern 修饰的变量,

  1. 如果正好本模块中有其相关定义, 那么就直接使用;
  2. 如果本模块中没有相关定义, 那么就挂起, 在编译后续其他模块的时候进行查找,
  3. 如果到最后还没有找到, 那么在链接阶段就会报错 ld: symbol(s) not found for architecture x86_64;

1、正确方式

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

2、错误方式 

2.1、错误方式01

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

这样属于在头文件中直接定义, 我们已经说了 一个变量可以被多处声明, 但只能定义在一处, 在这种情况下如果有多个 implementation file 都 #include "test1.h", 那么会造成在 obj 文件的 链接 阶段发现多处存在同一个变量的定义, 这时会报错 ld: 1 duplicate symbol for architecture x86_64

在头文件中定义一个变量属于非常业余的做法, 请不要争相模仿

2.2、错误方式02

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

这样做可以避免多处重复定义的问题, 但是这样的话 test1.h 定义的其他变量与方法都不可以使用了, 必须全部使用 extern *** 的形式进行声明然后使用, 这样会及其得不偿失.

3、总结

所以我们可以得出结论:只在头文件中做声明!!!

真理总是这么简单!

二、全局函数

函数与变量类似, 也分为定义与声明. 但是与变量在声明时必须要包含 extern 不同, 由于函数的定义和声明是有区别的:

  • 定义函数要有函数体;
  • 声明函数没有函数体;

所以函数定义和声明时都可以将 extern 省略掉, 反正其他文件也是知道这个函数是在其他地方定义的, 所以不加 extern 也行.

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

1、static使用

当 static 用于修饰类中的变量/函数,表明该变量/函数是一个静态成员变量/函数

  • 类加载的时候会分配内存
  • 可以通过类名直接访问

当 static 用于修饰类之外的变量/函数,表明该变量/函数是一个普通的全局静态成员变量/函数

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

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

2、const

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

3、ifndef 的使用与意义

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

 一般格式:

#ifndef <标识>
#define <标识>
...
...
#endif

使得编译器可以根据这个名字是否被定义,再决定要不要继续编译该头文中后续的内容。

<标识> 在理论上来说可以是自由命名的,但每个头文件的这个<标识>都应该是唯一的。标识的命名规则一般是:

  • 头文件名全大写,前后加下划线,
  • 并把文件名中的“.”也变成下划线,

这个方法虽然简单,但是写头文件时一定记得写进去。

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

  1. header file 中是对于该模块接口的声明, 接口包括该模块提供给其它模块调用的外部函数及外部全局变量, 对这些变量和函数都需在 header file 中冠以 extern 关键字声明
  2. 模块内的静态函数全局变量需在 implementation file 开头冠以 static 关键字声明
  3. 永远不要在 header file 中定义变量
  4. 如果要用其它模块定义的变量和函数, 直接 #include 其 header 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

C++ 之头文件声明定义 - 知乎