C++z作用域与生命周期
C++的作用域大致可以分为全局作用域、局部作用域和文件作用域三种。还有一种更细的分法,按照作用域范围从大到小分为程序作用域,文件作用域,类作用域,函数作用域和块作用域5种类型。
①程序作用域:
指一个标识符在整个程序范围内有效。若一个程序由多个文件组成,具有这种作用域的标识符可以在该程序的各文件中应用。具有程序作用域的标识符只能在某个文件中定义一次,在要使用它的文件中用 extern声明。例如,如果有10个文件都要用到某个变量,这个变量也只能在一个文件中定义,在其他9个文件中必须用extern声明后才能使用。
②文件作用域:
指在一个文件中所有函数定义之外定义的名字(包括函数名),其有效范围为从定义它的语句位置开始,直到文件结束。具有文件作用域的名字只能在定义它的文件中使用,但不能在组成同一程序的其他文件中使用。
③函数作用域:
指在函数范围内有效的标志符。
④块作用域:
写在{}内的一条或多条语句就构成了一个语句块,在其中定义的标识符就只能在这对“{}”中使用,而且只在定义(或声明)它的语句位置到离它最近的“}”之间有效,即只能在这段代码区域内引用它,这就是块作用域
在C++中,任何在“{}”中定义或声明的标识符都具有块作用域。局限在一个函数内部的标识符都具有块作用域,包括在函数内部定义的变量或对象、函数的形式参数等。
⑤作用域限定符:
在函数中,一旦在当前作用域中找到了需要的名字,编译器就会忽略外层作用域中的同名实体。也就是说,若局部变量和某个全局变量同名,局部变量名会隐藏全局变量名。在这种情况下,可用作用域限定符“:”存取全局变量的值。
/* 最外层的i和函数f()具有文件作用域 */
/* f()内为块作用域 */
int i;
int f() {
int i;
i = 1; // 修改f()中定义的局部变量i
::i = 0; // 修改全局变量i
{
int j = 0;
static int k;
i = 2; // 修改f()中定义的局部变量i
::i = 3; // 修改全局变量i
}
j = 2; // error,k已被回收
return k; // k已失去作用域
}
/* 由于局部变量在函数运行结束时它的那个内存地址会被回收以重新分配给其他数据,所以不要在函数内返回局部变量的地址和引用。 */
/* if语句里的作用域 */
if(int i = 5) { // i的作用域自此开始
int p = 0; // p的作用域自此开始
} // p的作用域到此结束
else {
i = 1;
p = 2 // error, p已无定义
} // i的作用域到此结束
/* switch语句中的变量作用域 */
void f(int i) {
switch(int j = i) { // j的作用域自此开始
case 1: j = j + 1;
case 2:
case 3: cout << j;
} // j的作用域到此结束
cout << j << endl; // error,j已无定义
}
/* 循环语句中的作用域 */
// C++新标准中,for循环中允许初始化i变量
void f1(int z) {
for(int i = 0; i < z; i++) { // i的作用域开始
int j = i;
cout << i * j << endl;
} // i的作用域到此结束
cout << i << endl; // error, i已无定义
}
变量类型与生命期
根据变量的作用域范围,变量可分为全局变量和局部变量两大类。在函数内部定义的变量就是局部变量(包括函数参数),它们只能在定义它的函数中使用;在函数之外且不在任何一对“{”内定义的变量(不属于任何函数)就是全局变量,其有效范围从其在文件中的定义位置开始到文件结束。 变量的生命期是指变量在内存中存在的时间,生命期与变量所在的内存区域有关。为了更清楚地理解这个问题,先看看运行程序对内存的应用情况。个程序在其运行期间,它的程序代码和数据会被分别存储在4个不同的内存区域中,如图所示。 |
程序代码区:程序代码(即程序的各函数代码)存放在此区域中。全局数据区:程序的全局数据(如全局变量)和静态数据( static)存放在此区域中。栈区:程序的局部数据(在函数中定义的数据)存放在此区域中。堆区:程序的动态数据(new、 malloc就在此区域中分配存储空间)存放在此区域中。
全局数据区中的数据由C++编译器建立,对于定义时没有初始化的变量,系统会自动将其初始化为0.这个区域中的数据一直保存,直到程序结束时才由系统负责回收。
堆区的数据由程序员管理,程序员可用new或malloc分配其中的存储单元给指针变量,用完之后,由程序员用delete或free将其归还系统,以便其他程序使用。
在函数中定义的局部变量(除了static类型的局部变量外,static类型的变量在全局数据区中),只有当函数被调用时,系统才会为函数建立堆栈,并在栈区中为函数中定义的局部变量分配存储空间,且不会对分配的存储单元做初始化工作。一旦函数调用完成,系统就会回收这些变量在栈区中的存储单元。
全局变量和静态变量存储在全局数据区中,它们具有较长的生命期。非静态的局部变量存储在栈区中,其生命期很短,只在函数调用期间有效。
静态变量可分为静态全局变量和静态局部变量,前者的作用域是整个程序范围,后者的作用域局限于定义它的语句块。静态局部变量的作用域与普通局部变量的作用域是相同的,但它与全局变量有着同样长的生命期,即程序结束时它才会被释放。普通局部变量的生命期只有函数调用期向才存在,函数调用完成后就结束了。
#include <iostream>
using namespace std;
static int n; // n被初始化为0
void f() {
static int i; // i被初始化为0
int j = 0;
i += 2;
j += 2;
cout << "i = " << ", ";
cout << "j = " << endl;
}
void main() {
n += 5;
f(); // 输出i = 2, j = 2
i = 2; // error,i作用域为f()内部
f(); // 输出i = 4, j = 2
} // i, n的生命期到此结束
初始化列表,变量初始化与赋值
变量在被创建时就获得一个指定的值,称为初始化。初始化值可以是任意复杂的表达式,当同时定义多个变量时,位于前面的变量马上就能够用于初始化另一个变量,即int i = 10, j = i * 10。
初始化的方式一般有以下几种:
/* 这四种初始化方式是等价的 */
int x = 0;
int x(0);
/* 后两种称为初始化列表方式 */
int x = {0};
int x{0};
后两种方式是C++ 11新标准的一部分,”{}”除了用于变量初始化,还可用于赋值。而在此前的C++标准中,只有部分场合才允许使用这种初始化方式,如数组初始化。
初始化与赋值:
<!–hexoPostRenderEscape:
int x = 10;
int x;
x = 10;
:hexoPostRenderEscape–>虽然x的最终值都是10,但
x = 10是赋值语句,可以理解为:先除掉x对应内存单元中的值,再写入10,而int x = 10没有这个过程,它是在为x分配内存单元的同时就写入10。全局变量,静态变量与局部变量的存储位置:
变量初始化的默认规则是:如果定义变量时提供了初始值表达式,系统就用这个表达式的值作为变量的初值:如果定义变量时没有为它提供初值,则全局数据区中的变量将被系统自动初始化为0,栈和堆中的变量不被初始化。
全局变量、命名空间的变量、静态变量会被保存在全局数据区中,所以它们会被系统自动初始化为0;局部变量(也叫自动变量)被存储在栈区中;动态分配的变量(用malloc和new建立)被存储在堆区中,它们都不会被系统用默认值初始化。
/* 全局变量,静态变量,局部变量的初始化 */
#include <iostream>
using namespace std;
int n; // 初始化为0
void f() {
static int i; // 初始化为0
int j; // 未初始化,j值不确定
cout << "i = " << ", ";
cout << "j = " << endl;
}
int *p1; // p1被初始化为0
void main() {
int *p2; // p2未被初始化,值未知
int m; // m未被初始化,值未知
f(); // 输出i = 0, j = ?, ?表示一个不确定的一个值
cout << "n = " << n << endl; // 输出n = 0
cout << "m = " << m << endl; // 输出m = ?, ?表示一个不确定的一个值
if (p1)
cout << "p1 = " << p1 << endl; // p1 = 0, 无输出
if (p2)
cout << "p2 = " << p2 << endl; // 输出p2 = ?, ?表示不确定地址
}
综上所述,最好对变量初始化,以免局部变量的不确定值引起程序错误。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!
