14. static 和 extern

14.1. 静态全局变量

在全局变量前,加上关键字 static,该变量就被定义成为一个静态全局变量。 特点:

  • 该变量在 全局数据区 分配内存;

  • 未经初始化的静态全局变量会被程序自动初始化为 0;(自动变量的值是随机的,除非它被显式初始化)

  • 静态全局变量在声明它的整个文件都是可见的,而在 文件之外是不可见的 ,其它文件中可以定义相同名字的变量,不会发生冲突。

14.2. 静态函数

在函数的返回类型前加上 static 关键字,函数即被定义为静态函数。 静态函数与普通函数不同,它 只能在声明它的文件当中可见 ,不能被其它文件使用。 其它文件中可以定义相同名字的函数,不会发生冲突,这点与静态全局变量相似。

Note

如果在头文件中定义 static 全局变量/函数,在已经 include 该头文件的源文件中是可以直接使用这个 static 全局变量/函数(相当于头文件的内容在当前文件中展开)。

Static variables are local to the compilation unit . A compilation unit is basically a .cpp file with the contents of the .h file inserted in place of each #include directive.

多个源文件 include 该头文件也可以编译通过,相当于各个源文件中都定义了只在各自文件内可见的 static 全局变量/函数。

14.3. 静态局部变量

在局部变量前,加上关键字 static,该变量就被定义成为一个静态局部变量。 特点:

  • 该变量在全局数据区分配内存;

  • 静态局部变量在程序执行到该对象的声明处时被首次初始化,即 以后的函数调用不再进行初始化

  • 静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为 0;

  • 它始终驻留在全局数据区,其生命周期一直持续到整个程序执行结束。但其作用域仍为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束。一般情况下,对于局部变量是存放在栈区的,并且局部变量的生命周期在该语句块执行结束时便结束了。

 1void func()
 2{
 3    static int a = 1; // 初次调用func()时才会执行初始化
 4    cout << a << endl;
 5    a ++;
 6}
 7
 8int main()
 9{
10    func(); // 1
11    func(); // 2
12    return 0;
13}

14.4. 静态成员变量

在类内数据成员的声明前加上关键字 static,该数据成员就是类内的静态数据成员。 特点:

  • 对于非静态数据成员,每个类对象都有自己的拷贝。而静态数据成员被当作是类的成员。无论这个类的对象被定义了多少个,静态数据成员在程序中也只有一份拷贝,由该类型的所有对象共享访问。在没有产生类的实例时,我们就可以操作它。

  • 静态数据成员存储在全局数据区。静态数据成员定义时要分配空间,所以不能在类声明中定义。

  • 静态数据成员和普通数据成员一样遵从 public/protected/private 访问规则。

  • (类定义体外部)静态数据成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式为: <数据类型> <类名>::<静态数据成员名> = <值>

  • 类的静态数据成员有两种访问形式: <类对象名>.<静态数据成员名> <类类型名>::<静态数据成员名>

14.5. 静态成员函数

普通的成员函数一般都隐含了一个 this 指针, this 指针指向类的对象本身,因为普通成员函数总是具体的属于某个类的具体对象的。 通常情况下, this 是缺省的,如函数 fn() 实际上是 this->fn() 。 但是与普通函数相比,静态成员函数由于不是与任何的对象相联系,因此它 不具有this指针 。 从这个意义上讲,它 无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数 。 非静态成员函数可以任意地访问静态成员函数和静态数据成员。

 1class Myclass
 2{
 3private:
 4    int a, b, c;
 5    static int sum;  //声明静态数据成员
 6public:
 7    Myclass(int a, int b, int c);
 8    void GetSum();
 9};
10
11int Myclass::sum = 0;   //定义并初始化静态数据成员
12
13Myclass::Myclass(int a, int b, int c)
14{
15    this->a = a;
16    this->b = b;
17    this->c = c;
18    sum += a + b + c;
19}
20void Myclass::GetSum()
21{
22    cout << "sum=" << sum << endl;
23}

Note

静态成员函数可以调用构造函数,调用构造函数并不需要 this 指针。当构造函数是私有的,不能像普通类那样实例化,这时候可以通过静态成员函数调用构造函数(比如在实现单例的时候)。

14.6. extern: 修饰函数、变量

修饰符 extern 用在变量或者函数的声明前,用来说明 “此变量/函数是在别处定义的,要在此处引用” 。 在别的文件中如果想调用 file1.c 中的变量 a ,只须用 extern 进行声明即可调用 a

extern int a; // file2.c
extern "C" int a; // file3.cpp

在这里要注意 extern 声明的位置对其作用域也有关系,如果是在 main 函数中进行声明的,则只能在 main 函数中调用,在其它函数中不能调用。 其实要调用其它文件中的函数和变量,只需把该文件用 #include 包含进来即可,但是用 extern 会加速程序的编译过程,这样能节省时间。

Note

  • 全局变量是不显式用 static 修饰的全局变量,全局变量默认是有外部链接性的,作用域是整个工程。在一个文件内定义的全局变量,在另一个文件中,通过 extern 全局变量名的声明,就可以使用该全局变量。

  • 全局静态变量是显式用 static 修饰的全局变量,作用域是声明此变量所在的文件,其他的文件即使用 extern 声明也不能使用。

  • C++ const 全局常量具有静态/内部链接,这与 C 语言不同。C++ 编译器优化全局常量,不为其保留任何空间。如果尝试在其他文件中通过 extern 使用该全局常量,则出现编译错误( 无法解析的外部符号 unresolved external symbol )。解决此错误的一种方式是在头文件中进行 const 全局常量初始化,在需要使用该全局常量的源文件中 include 该头文件。在不同源文件中对该常量进行取址( & ),会得到不同的地址,这说明每个源文件都有一份常量的定义,而不是共享一个常量。C 编译器对定义在头文件的 const 常量会报错。

14.7. extern “C” {}

 1#ifndef HEADER_INCLUDED // 条件编译,避免重复包含头文件
 2#define HEADER_INCLUDED
 3
 4#ifdef __cplusplus // extern "C" 只用在 c++ 文件中
 5extern "C" {
 6#endif /* __cplusplus */
 7
 8#include "c.h"
 9
10char* strcpy(char*,const char*);
11
12/*.................................
13 * do something else
14 *.................................
15 */
16
17#ifdef __cplusplus
18}
19#endif /* __cplusplus */
20
21#endif /* HEADER_INCLUDED */

extern "C" 中的 C ,表示的一种编译和连接规约,表明它按照类 C 的编译和连接规约来编译和连接,而不是一种语言。 C 表示 符合C语言的编译和连接规约的任何语言 ,如 Fortran、assembler 等。 extern "C" 的真实目的是实现 类C 和 C++ 的混合编程。

14.8. 参考资料

  1. C/C++中的static关键字详解

  1. C++项目中的extern “C” {}

  1. 浅谈C/C++中的static和extern关键字