C++ : static, extern前缀意义

一般的编译过程(gcc)是长下面的样子的

我们人类用高阶语言撰写了各种source file,也就是各种的.h 和.cpp 档
然后每个独立的.h 和.cpp 档都应该要可以各自被compile 成.o 档而不报错

也就是说下列的问题可以被检查出来:

  1. 语法错误
  2. 不存在或没宣告过的变数,如果有变数是透过include 而来的,在检查过程中也会去参考其他有include 到的档案该变数是否被宣告过
  3. 变数型态错误

Compiler范例

假设现在有三个档案main.cpp, module1.h, module1.cpp
main.cpp

#include <iostream>
#include "module1.h"
using namespace std;
int main() {
    greeting();
    return 0;
}

模块1.h

#ifndef MODULE_1_H
#define MODULE_1_H
void greeting();
#endif

模块1.cpp

#include <iostream>
#include "module1.h"
using namespace std;
void greeting() {
    cout << "Hello World!" << endl;
}

从上面例子我们可以看到greeting() 这个function 在module1.h 档案里面只有function signature, 但没有实作的部分,但是我们可以使用g++ 的-c针对main.cpp 先进行compile:
g++ -c main.cpp
此时是可以成功产出一个main.o这个中间过程档的。同样的,我们一样可以对module1.cpp进行compile:
g++ -c module1.cpp
此时也可以产出一个中间过程档module1.o。最后我们只要把两个.o 档Link 在一起-o,就可以产出一个能运行的exectuable file:
g++ -o main main.o module1.o

从上面这个例子我们也可以看出,有一些东西即使没把实作写出来,每个独立的.cpp 档都应该要可以被compile 成.o 档,因为在coimplie 成.o 档的过程中要的只有定义而已。从这里我们也可以了解一件事,就是在Link 的过程中两个.o 档才有了互动,此时也会有更进一步的检查。而这些繁琐的步骤,就是IDE 帮我们处理掉的部分。

外部

现在我们知道每个.cpp 档都可以独自被compile,假如今天我们有一个变数要在多个档案之间共用,该怎么处理呢? 这就是extern 的作用。extern 告诉compiler 这个变数的存在,但是并不是由这个这个档案做宣告。我们沿用上一个范例,加入更多的东西

主程序

#include <iostream>
#include "module1.h"
using namespace std;
int main() {
    greeting();
    cout << "In main, a = " << a << endl;
    a = 0;
    cout << "In main, a = " << a << endl;
    return 0;
}

模块1.h

#ifndef MODULE_1_H
#define MODULE_1_H
extern int a;
void greeting();
#endif

模块1.cpp

#include <iostream>
#include "module1.h"
using namespace std;
int a = 1;
void greeting() {
    cout << "Hello World!" << endl;
    cout << "In greeting, a = " << a << endl;
}

我们增加了一个变数a,在module1.h 档中有extern int a,我们很清楚的告诉了会include module1.h 的人,这个模组里面有一个int a 变数可以用,但是这个变数的宣告并不在此,而是有某个include 了module1.h 的人会去做宣告,这个工作在这里我们让module1.cpp 做。同时我们也修改了greeting,会把变数a 的值印出来。在main 里面我们也可以取用变数a。实际运行一次以后可以得到如下输出:

Hello World!
In greeting, a = 1
In main, a = 1
In main, a = 0
Hello World!
In greeting, a = 0

同时我们可以看到在main 里面看到的a 跟在module1.cpp 里面看到的a 是同一个,因此在main 里面修改a 的值,module1 里面拿到的a 也就改变了

静止的

看完了extern,接下来就要来解释static 了。相比之下extern 算是很好理解的了,static 则比较混乱一点。static 之所以混乱,是因为他出现在不同地方,他的意义就会不同,也就是说static 会被overload,但每个单独的定义其实也都很好了解。在C 里面因为没有class,所以static 只会有两种定义,而在C++ 中因为多了class,所以会再多两种定义

static 的意义就是“被修饰的东西,会从程式一开始执行就存在,且不会因为离开scope 就消失,会一直存在到程式结束”。 static 出现在哪里及用什么定义如下:

  • static 出现在variable 之前,且该variable 宣告在某个function 之中(C/C++)
  • static 出现在variable 之前,且该variable 并不是宣告在某个function 中(C/C++)
  • static 出现在class 的member variable 之前(C++ only)
  • static 出现在class 的member function 之前(C++ only)

1. static 出现在variable 之前,且该variable 宣告在某个function 之中

这个算是非常好理解的,一般我们写funcion 时,里面宣告的变数在funcion 结束之时也会跟着消失。但如果我们在某个变数之前加上static,该变数就不会因为function 结束而消失。最经典的例子大概就是要计算这个function 被呼叫几次

void greeting() {
    static int counter = 0;
    ++counter;
    cout << "Greeting function has been called " 
         << counter << "times" << endl;
}

2. static 出现在variable 之前, 且该variable不是宣告在某个function中

在我们解释extern 的范例中,我们会遇到变数在不同档案中要共用,只要include 某个档案以后,就可以使用其中的变数。但这会导致一个比较麻烦的问题,假设今天有两个档案a.cpp 和b.cpp ,各自有各自的.h 档。 a.cpp include 了bh,但a.cpp 跟b.cpp 里面各自宣告了一个在function 外的bool debug 的变数,分别是用来启动或关闭a 和b 的debug 功能。两个compile 后的.o 档在link 的过程中就会因为名字相同而产生冲突的错误。对存在在funciton 外的变数来说,基本上都是global variable,static 的用意就是要让这样的global 只限定在该档案内,而不是整个程式中。因此,如果我们把a.cpp 和b.cpp 中的debug 在宣告时前面都加上static,这样compiler 在处理前期替换掉的名字就会不同,因此link 的过程中也不会有名字冲突的问题。

为什么要这么麻烦不直接include a.cpp 和b.cpp 呢?

这是因为如果include 某个档案后,里面所有的东西对于include 的人就通通都看得到了,也因此如果直接include a.cpp 和b.cpp,对于main.cpp 来说,他就看到有两个地方都宣告了debug 这个变数,所以compile 的时候直接就会被检查到而失败。

我只能include .h 档,又没有include .cpp 档,那main.cpp 不就还是没看到两个cpp 里的debug,那为什么还要用static 呢?

这是因为如果没写static,则在-c 这个compile 的过程中产生的.o 档里面会带有这个档案转化后的资讯,而所有不在function 等等scope 的变数通通会被视为是global variable 。在把main.o, ao 和bo link 的过程中,compiler 看到ao 和bo 里面都有一个debug 的变数,就会报错了。

3. static出现在class的member variable之前

static 出现在class 的member variable 的意思是该variable 并不属于某个instance,他属于这个class,所有以此class 生成出来的instance 都共用这个variable。最经典的范例就是用来计数这个class 总共生成了多少个instance

4. static 出现在class 的member function 之前

static 出现在member function 的意思是该function 并不属于某个instance,他也属于这个class,所有以此class 生成出来的instance 都共用这个function。也因此,即便我们没有产生instance 出来,我们也随时可以取用这个function

所以如果今天想定义全域变数应该怎么做?

如果直接将全域变数定义在preheader.h里面的话, 会发生重复引用的编译错误
所以如果想定义全域变数的话可以采用下面两种方式

  1. 透过一个preheader.h里面extern int a;, 并且透过一个.cpp实作这个int a = 0;, 全部的人都要引用这个header#include "preheader.h"
  2. 透过将preheader.h里面static int a;, 如此一来即使重复引用, 因为static的前缀会让编译器知道a 变数是属于preheader.h, 如此就不会有重复定义的问题了

相关推荐

  1. 学习前端之HTML5中的`<!DOCTYPE>`声明有什么意义

    2024-06-10 17:36:02       55 阅读
  2. Spuer().__init__的意义

    2024-06-10 17:36:02       36 阅读
  3. CMMI认证有什么意义

    2024-06-10 17:36:02       65 阅读
  4. CDN的特点及意义

    2024-06-10 17:36:02       58 阅读
  5. TensorFlow 用 hashtable 的意义

    2024-06-10 17:36:02       32 阅读
  6. 发展低空经济的意义

    2024-06-10 17:36:02       33 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-06-10 17:36:02       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-06-10 17:36:02       106 阅读
  3. 在Django里面运行非项目文件

    2024-06-10 17:36:02       87 阅读
  4. Python语言-面向对象

    2024-06-10 17:36:02       96 阅读

热门阅读

  1. 华为坤灵路由器配置telnet

    2024-06-10 17:36:02       34 阅读
  2. Position定位

    2024-06-10 17:36:02       33 阅读
  3. Docker日志相关命令

    2024-06-10 17:36:02       37 阅读
  4. TiDB Distributed NewSQL Database

    2024-06-10 17:36:02       36 阅读
  5. qt c++ 大小端字节序数据获取与转换

    2024-06-10 17:36:02       27 阅读
  6. GMT legend设置

    2024-06-10 17:36:02       38 阅读
  7. docker-compose部署mysql+nginx+redis

    2024-06-10 17:36:02       37 阅读
  8. vue面试题三

    2024-06-10 17:36:02       35 阅读
  9. C语言考试内容

    2024-06-10 17:36:02       28 阅读
  10. 881救生艇

    2024-06-10 17:36:02       32 阅读
  11. 音视频主要概念

    2024-06-10 17:36:02       33 阅读
  12. Dubbo的Cluster策略与Directory实现

    2024-06-10 17:36:02       29 阅读
  13. 单节点离线部署TiDB 6.1用于测试

    2024-06-10 17:36:02       27 阅读
  14. AtCoder Beginner Contest 355

    2024-06-10 17:36:02       37 阅读
  15. Docker镜像加速

    2024-06-10 17:36:02       29 阅读