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. 参考资料
C/C++中的static关键字详解
C++项目中的extern “C” {}
浅谈C/C++中的static和extern关键字