一直以来没有细细探究,对于变量的作用域,当遇到 extern, const 等的时候到底会发生什么变化,有什么意义,全局变量到底是怎么样的,所以就会茫然不知所措,所以来细细的看一下一番。

我们知道,C/C++ 的源文件一般都包括 .c, .cpp, .h 文件,那么这是怎么样工作的呢?这两者有什么区别,在这些文件里面定义或者写的代码到底有什么不同呢?

首先我们要大概的了解一个过程,就是从我们写的人可读的代码和机器可以执行的代码间还有两步工作需要做,这个是以编译器,和链接器来完成的。

  • 编译。将我们写的代码编译成 obj 文件。
  • 链接。将我们的 obj 文件以一定的格式合并成一个。

我们可以确定的就是,对于一个 .cpp, .c 文件最终肯定会编译出一个 obj 文件的。那么为什么 .h 没有? 这就是我们使用 .h 文件的用法有关了。

通常,我们会以 #include "header.h" 这样的形式来使用头文件,这样做的意义是什么?这样做的意义是将 header.h 里面包含的内容,复制到 #include 这个位置而已。所以,最终编译的是 .c/.cpp 文件。

事实上我们可以测试一下,我们在一个头文件内声明一个函数,但是并没有对应的 .c/.cpp 实现这个函数,在编译阶段是不会出错的,但在链接阶段却会链接不了。

可能你会问,为什么我包含 stdio.h 的时候却不会?因为编译器已经有了默认实现了。

官方说明

一个程序由一个或多个翻译单元组成。而一个翻译单元由一个实现文件(.cpp, .c 等)及其直接或间接包含的 .h 头文件来组成。编译器会将每个翻译单元独立编译,然后由链接器将他们合并为一个程序。

声明与定义

我们先来了解这两者之间的区别。

比较笼统概括的说法就是:声明为程序引入了一个符号,而定义指定了这个符号所描述的数据或者代码。编译器需要已经声明的符号有实现才能分配存储空间。

声明

所有符号使用前都必须声明不然就会出错。一个符号在程序中可以多次声明,我们可以在不同的编译单元内进行声明同一个符号,但是同一个符号的声明必须是一致的。大多数时候,进行声明的时候其实就已经执行了定义这个操作了,但有几个情况是例外的:

  • 声明的是一个函数原型(没有函数体)
  • 包含 extern 关键词,但并没有进行初始化或者函数体。这表示在当前的翻译单元内定义不是必须的,同时这个符号是外部链接的
  • 对一个类的声明中,声明了一个 static 数据成员。因为静态类成员被所有的类的实力所共享,那么静态类成员就必须在类的声明外进行定义和初始化。
  • 是一个类名称声明,但并没有跟随定义。如 class T;
  • typedef 语句

一个符号在声明符(declarator)后即进行了声明,之后才会进行初始化。

一个对象的声明也是一出,除非其包含 extern 存储类修饰符。(这个我还没弄清楚)。

定义

如此我们就能看出声明与定义的区别了。定义是需要分配存储空间的,而大多数时候其实声明就进行了定义。

extern

经常我们会用到这个关键词,其实更多的还是我会用到 extern "C" 这个才对。但是,当我们在全局变量上应用此关键词的时候,其意义会依环境而有所不同。

extern 作用于全局变量、函数或者模板声明来指定此符号是链接到外部的。根据 extern 出现上下文的不同,其有四种意义:

  1. 在非常量的全局变量中, extern 指明变量或者函数定义在其他的翻译单元内。除了在定义此变量或者函数的文件内不需要 extern,所有其他单元内都必须指定 extern。
  2. 在 const 全局变量中,在定义处也要指定 extern。因为,const 全局变量默认只在本单元内可见。
  3. extern “C” 指定该函数在其他地方定义,并使用 C 语言的调用约定。
  4. 在模板声明中,它指定该模板,也实例化其他位置。

非常量的全局变量应用 extern

当编译前在一个全局变量前发现 extern 时,它会在其他的翻译单元中查找全局变量的定义。

// fileA.cpp
int i = 43; //声明和定义全局变量 i

// fileB.cpp
extern int i; // 仅仅声明

// fileC.cpp
extern int i; // 仅仅声明

// fileD.cpp

int i = 43; // 报错, i 已经定义过了
extern int i = 43; // 报错, i 已经定义过了

const 全局变量应用 extern

一个 const 全局变量默认情况下是内部链接的,我们可以使用 extern 来将其定义为外部链接的:

// fileA.cpp
extern const int i = 42;// extern const 定义

// fileB.cpp
extern const int; // extern const 声明

static

static 可以用来全局作用域、命名空间作用域、类作用域声明变量和函数。 static 变量也可以在本地作用域进行声明。

static 周期意味着此对象或者变量自程序开始运行时就会分配内存然后在程序终止时回收内存。默认情况下,一个在全局命名空间定义的变量或对象是静态的生命周期和外部的链接域。 static 可以在下面几种情况使用:

  1. 当怎么在文件作用域(全局或命名空间作用域)声明变量或函数时,static 说明此变量或者函数是内部链接的,外部不可见。当我们声明一个变量,变量有静态生命周期的时候,编译器会将其初始化为0.
  2. 当我们在函数内声明变量时,static 会在多次调用函数中保持此变量的状态。
  3. 当我们在类声明内声明一个数据成员时,static 表明此成员被所有此类的实例所共享。static 数据成员必须在文件作用域进行定义。
  4. 当我们在类声明中生命一个成员函数时,static 指明此方法为所有的此类实例所共享。静态成员方法不可以调用实例方法,因为其不含有 this 指针。想要达到这个目的,可以将实例作为一个参数传递给 static 方法。