作者邮箱:z_zhanghaobo@163.com
github相关: https://github.com/HaoboLongway/simple_examples
以下讨论不只限于函数中变量的声明周期于作用域
我们可以用存储期来描述对象,所谓存储期表示对象在内存中保留了多长时间。
标识符用来访问对象,可以用作用域和链接描述标识符,标识符的作用域和链接表明了程序中的哪些部分可以使用它。不同的存储类型有着不同的存储期,作用域以及链接。
-
作用域
一个C++变量得到作用域可以是块作用域,函数作用域,函数原型作用域或文件作用域。-
块是用一对花括号
{}
括起来的代码区域,例如,整个函数体是一个块,而一个复合语句也是一个块。块作用域的可见范围是从定义处到包含该定义的块的末尾。下面我们看一个例子
-
块是用一对花括号
...
void main()
{
blocky();
}
void blocky()
{
{
int i=1;
{
cout<<"i = "<<i<<endl;
{
int q=1;
cout<<"q = "<<q<<endl;
}
cout<<"q = "<<q<<endl; // There's a mistake, can you see it?
}
cout<<"i = "<<i<<endl;
}
cout<<"i = "<<i<<endl; // There's a mistake too
}
上面的代码实际是不能运行的,注意到其中加有There's a mistake
的注释了吗,实际上编译器会抛出这样的错误信息:
error: 'q' was not declared in this scope|
由于编译器顺序编译,所以第二处同样的错误并没有被发现,我们关注到变量q
是在最里层被定义的,而报错的一行则在花括号外使用了该变量,显然这超出了q
的作用域,难怪这里会出错
下面我们可以改正这个程序,把错误的两处删去,输出结果为
i = 1
q = 1
i = 1
与我们的预期一致
那么如果内层块与外层块变量同名会怎么样?这里内层块会隐藏外层块的定义,但是离开了内层块后,外层块变量的作用域又回到了原来,看下面的例子
...
int main(){
int x=30; //primitive
cout<<"x in outer block:"<<setw(30)<<x<<" at "<<&x<<endl;
{
int x=77;
cout<<"x in inner block:"<<setw(30)<<x<<" at "<<&x<<endl;
x += 33;
cout<<"x in inner block after certain operation:"<<setw(6)<<x<<" at "<<&x<<endl;
}
cout<<"x in outer block again:"<<setw(24)<<x<<" at "<<&x<<endl;
}
输出为
x in outer block: 30 at 0x6dfeec
x in inner block: 77 at 0x6dfee8
x in inner block after certain operation: 110 at 0x6dfee8
x in outer block again: 30 at 0x6dfeec
符合前面提及的规则
现在根据C99标准,for
循环,while
循环,do while
循环和if
语句所控制的代码即使没有用花括号括起来,也算是块的一部分,比如下面的例子
... //Let's suppose there's no variable i before.
for(int i=1;i<11;i++){
i ++;
}
cout<<i; //A mistake, out of scope!!!
是非法的。
-
函数作用域 函数中的变量是局部变量,它们无法被外部访问,函数作用域仅用于
goto
语句的标签,如果在两个块中使用相同的标签会很混乱,标签的作用域防止了这样事情发生。不过事实上,我们很少使用goto
语句,使用goto
语句也不是什么好主意,所以这一点仅做了解。 - 函数原型作用域 用于函数原型中的形参名,其作用范围是从形参定义处到原型声明结束,这意味着编译器在处理函数原型中的形参时只关心它的类型,如下面的例子只有在变长数组中形参名才会有用:
void use_a_VLA(int n, int m, ar[n][m]);
不过我们并不多接触变长数组,因此基本上也仅作了解
- 文件作用域中的变量,从定义处到该定义所在文件的末尾均可见,如
#include <iostream>
int global_var = 1;
int main(){
cout<<global_var;
...
}
这里的global_var
具有文件作用域,文件作用域变量也被称为全局变量。
-
存储期
一般变量的存储是自动的,对于函数来说,如果函数执行完毕,如果没有特殊情况,其中的变量就会“死亡”,也就是它们的地址不再有效。
不过所谓的静态变量在存储上有所不同,静态的意思是该变量在内存中原地不动,静态变量又可分为块作用域 和 外部链接 两种。-
块作用域的静态变量
看下面的例子:
-
块作用域的静态变量
int main()
{
for(int i=0;i<10;i++){
cout<<"Times called self_add: "<<i<<setw(20)<<"Return: "<<self_add()<<endl;
cout<<"Times called self_add_static: "<<i<<" Return: "<<self_add_static()<<endl;
cout<<"------------------\n";
}
return 1;
}
int self_add(){
int counter=0;
return ++counter;
}
int self_add_static(){
static int counter=0;
return ++counter;
}
注意这里self_add()
函数中的变量counter
是通常的,它的生命周期即是函数作用的周期,当主调函数结束对self_add()
的调用后,counter
即被销毁;与此不同的是,我们使用static
关键字指出一个变量是静态的,静态变量在该函数首次调用时被初始化且只初始化一次,当self_add_static()
函数执行完毕后,它的counter
变量仍会保留,所以我们看到输出结果是下面这样:
Times called self_add_static: 1 Return: 2
------------------
Times called self_add: 2 Return: 1
Times called self_add_static: 2 Return: 3
------------------
Times called self_add: 3 Return: 1
Times called self_add_static: 3 Return: 4
------------------
...
另一方面,我们还可以看到这两个函数中同名变量互不影响。
-
外部链接的静态变量
这里主要想说明外部变量具有静态存储期,例如下面的例子
int Whole_bunch; //外部定义的变量
double Up[100]; //外部定义的数组
extern char stars; //如果要使用外部文件中的stars变量,则需要这样声明
其中Whole_bunch
, double Up
, stars
都是全局性的(从作用域考虑),且有静态存储期。
当我们在该文件某处,如use_it()
函数中使用时,我们可以直接使用,也可以使用extern
关键字显式声明,表明这里使用的Up[]
与全局变量中的Up[]
一致,像是下面的示例
int use_up_array(void){
extern double Up[];
...
}
当然,如果我们不作extern
开头的声明,也可直接使用Up[]
数组,因其是全局变量.
网友评论