这是一篇关于C++
变量的作用域与链接性的笔记,整理了各种相关概念以备疏忘。
存储持续性
按照变量在内存的生存周期,可以将变量分成三种类型
类型 | 内容 |
---|---|
自动存储 | 在函数中声明的变量(含函数参数),它们在函数被执行时被创建,执行结束后其内存被释放。C++ 有二种自动变量。 |
静态存储 | 在函数外声明,或者函数内使用static 声明的变量,它们在程序运行过程中一直存在。C++ 有三种静态变量。 |
动态存储 | 使用new 声明的变量,其获得的内存持续存在于【堆区】,直到使用delete 释放其内存或者程序运行结束。 |
作用域与链接性
作用域描述了名称(变量名,或者函数名)的有效范围。例如,在函数中声明的变量,只能在该函数中被使用,而无法在其它函数中被使用。
变量的作用域有多种。全局作用域的变量在函数之外被声明,其作用域自声明位置至文件结尾;局部作用域的变量在代码块(例如,函数或者for
循环)中被声明,其作用域自声明位置至代码块结束,自动变量的作用域为局部。静态变量的作用域取决于其声明的位置。
函数的作用域是整个类或者整个名称空间,即全局。如果其作用域是局部,意味着它的名称仅对自己可见,因此无法被其它函数调用,这样的函数是无法运行的。所以编译器禁止在代码块内部定义函数。
链接性描述了名称如何在不同单元间被共享使用。具有外部链接性的名称可在文件之间共享,内部链接性的名称只在本文件内部使用。自动变量【无】链接性,因为它们不能被共享。
自动变量
通常,在函数内声明的函数参数和变量为自动的、局部的、无链接性,它们保存在【栈区】。在函数内的代码块定义的变量,其作用域仅在该代码块内有效。而且,该变量会隐藏之前定义的具有相同名称的变量。例如
void main(){
int first = 1;
int second = 2;
int end = 1000;
for(int first = 0; first < end; first ++){
std::cout << "Here is inner block:" << std::endl;
std::cout << "Variable first = " << first << std::endl;
std::cout << "Variable second = " << second << std::endl;
}
std::cout << "Here is outer block:" << std::endl;
std::cout << "Variable first = " << first << std::endl;
std::cout << "Variable second = " << second << std::endl;
}
第一次定义的变量first
, 和变量second
,end
,作用域为整个main
函数。但是在代码块for
循环里面,第二次定义的同名变量first
【隐藏】了之前定义的变量first
,使其在该代码块内失效。
程序使用两个指针来管理栈,一个指针指向栈底,即栈开始的位置;另一个指向栈顶,即下一个可用内存单元。程序在调用函数的时候,其自动变量会被压入栈中,获得内存,栈顶指针挪向下一个可用内存单元。待函数执行结束,自动变量被弹出栈,释放内存,栈顶指针指回调用函数之前的位置。也就是说,【只有在函数被执行的时候,自动变量才拥有内存】。
静态变量
静态变量有三种链接性:
- 外链接性:可在其它文件访问
- 内连接性:只能在本文件访问
- 无链接性:只能在本代码块(函数也是一种代码块)访问
所谓静态指的是,其内存地址由编译器自动分配,在程序运行期间都不会改变。而自动变量,在每次函数调用的时候都被分配到新的内存位置,用完后释放掉。另外,如果没有显示地初始化静态变量,编译器将默认设置为〇。
int global = 1000;
static int inner_file = 50;
int main(){
func(); // auto_virable = 10, inner_file = 30
func(); // auto_virable = 10, inner_file = 40
}
void func(){
static int inner_func = 30;
int auto_virable = 10;
std::cout << "auto: " << auto_virable << endl;
std::cout << "static: " << innner_file << endl;
inner_file = inner_file + 10;
}
定义在【函数之外】的变量均为静态变量,如果无关键字static
修饰,则具有外链接性,上例中global
即是如此;否则具有内链接性,例如变量inner_file
。在代码块之内有【static
】修饰的变量为静态变量,不具有链接性,否则是自动变量。静态变量【一定】拥有内存,在上例中,即使函数func()
没有被调用,编译器依然为变量inner_func
分配内存,而自动变量只有被运行时才拥有内存。
静态变量的另一个「静态」指的是,在函数两次调用期间,其值保持不变。在上例中,第一次执行完func()
以后,变量auto_virable
被释放掉,而inner_file
依旧存在于内存之中,值为40
;第二次调用func()
,编译器并不会再次初始化inner_file
,因此它的值依旧为40
。
如要使用同目录下的其它文件中的全局变量,需要使用extern
声明,且不能初始化,否则变声明为定义,违反「单定义原则」。
// file1.cpp
extern int cat = 20; // definition, "extern" is not obligatory
int dogs = 22; // definition
...
// file2.cpp
extern int cats; // declaration, defined in file1.cpp
extern int dogs; // declaration
...
同样的,变量隐藏也适用于静态变量。通常,使用extern
修饰的变量在文件之间共享数据,使用static
修饰的变量在文件内的各个代码块之间共享数据。
一个容易和静态变量混淆的概念是常量变量,由关键字const
修饰的变量。此类变量一旦被定义,其内存里的内容在生命期内都不能被改变,而静态变量是内存地址不会变。在【函数外】定义的常量变量具有内链接性,除非使用extern
修饰。
// file1.cpp
const int fingers = 10; // internal linkage
extern const int eyes = 2; // external linkage
...
// file2.cpp
extern int eys; // defined in file1.cpp
...
函数的链接性
函数的存储特性是静态的,即无论是否调用,被定义的函数代码块都会获得内存地址。
同样地,函数也具有链接性。首先函数不可能是无连接性,因此禁止在函数内部定义另外一个函数。通常,函数具有外部链接性,可在文件间共享。可以在函数原型中使用extern
指出此函数在另外一个文件中被定义,
// file1.cpp
extern int external_func();
...
// file2.cpp
int external_func(){
...
}
...
也可以使用static
来限制函数为内链接性,但必须同时在函数原型与函数定义中使用static
修饰,此类函数仅限本文件使用。
static int internal_func(); // prototype
...
static int internal_func(){ // definition
...
}
和变量类似,在定义静态函数的文件中,静态函数可隐藏外部定义,即使在其它文件中定义了同名的外部函数,此文件仍将使用该静态函数。