一.作用域
1.先谈谈作用域的定义
当变量在程序某个部分被声明的时候,它只有在程序的一定区域才能被访问。这个区域由标识符的作用域决定。标识符的作用域就是程序中该标识符可以被使用的区域。
上面的定义意味着两点:
(1).其他函数都无法通过这些变量的名字访问它们,因为不在作用域之内
(2).只要在不同的作用域内,你可以给不同的变量取同一个名字
2.四种不同类型的作用域
有分别四种不同类型的作用域:文件作用域、函数作用域、代码块作用域和原型作用域
(1)代码块作用域
首先先谈谈代码块作用域,首先你得知道代码块是什么?
位于一对{ }花括号中间的所有语句称为一个代码块。任何在代码块的开始位置声明的标识符都具有代码块作用域,表示它们可以被这个代码块中的所有语句访问
但是如果代码块出现嵌套的情况怎么办?
打个比方:
int main(){
int b;
void abc(){
......
int b;
}
}
在main函数里面这个花括号是一个代码块,里面是不是有两个int b这个变量,也就是这个情况出现了代码块的嵌套
当代码块处于嵌套状态时,声明于内层代码块的标识符作用域到达该代码块的尾部便告终止。然而,如果内层代码块中有一个标识符的名字和外层代码块中的一个标识符同名,内层的那个标识符就将隐藏外层的标识符
简单的用人话说,就是内层的代码块里面的int b只有在内层的代码块才有效,然后内层代码块中的int b会覆盖外层的int b,这个就是局部变量会覆盖全局变量的意思吧。
(2).文件作用域
任何在所有代码块之外声明的标识符都具有文件作用域,表示这些标识符从它们声明之处直到它们所在的源文件结尾处都是可以被访问的。这个也挺好理解的,类似于下面代码
int b;
int main (){
return 0;
}
这个变量b就是具有文件作用域
(3).原型作用域
原型作用域只适用于在函数原型中声明的参数名
这个应该也好理解,就是你在声明函数的时候,带参数,然后那些参数只在函数原型中有效,而且参数的名字并非必须
比如:
void max (int a,int b);
这个变量a和变量b就具有原型作用域,只在函数原型有效
(4).函数作用域
变量在声明它们的函数体以及这个函数体嵌套的任意函数体都是有定义的。这个也好理解
上面的都是基础知识,都很简单。
二.链接属性
1.链接的定义,什么是链接
我们写过代码的都知道,有的时候我们编写的代码并不只是编写一个源程序的代码,可能是很多个源程序,但是最后生成的.exe也就是可执行文件只是一个,这是为什么呢?
原来,当组成一个程序的各个源文件分别被编译后,所有的目标文件以及那些从一个或多个函数库中引用的函数链接在一起,形成可执行文件,这就是链接。
2.链接属性
那么不同的源文件肯定有可能有相同名字的不同变量吧,那么是如何区分的呢?
标识符的链接属性决定了如何处理在不同文件中出现的标识符
链接属性也有三种-----external(外部)、internal(内部)和none(无),然后依次对这三种链接属性进行解释
(1)none 没有链接属性
没有链接属性的标识符总是被当作单独的个体,也就是说不管声明多少次,声明多少次就是多少个独立不同的实体
(2) internal 内部链接属性
属于internal链接属性的标识符在同一个源文件内的所有声明都是一个实体,但是不同源文件的多个声明则就指向不同的实体
这个也好理解,也就是说你变量b如果是内部链接属性的话,你在你这个源文件使用的变量b全是一个东西,但是你如果跑到这个源文件之外,那就指的是另外一个东西了
(3)external 外部链接属性
属于external外部链接属性的标识符无论声明多少次、位于几个源文件都表示同一个实体。
下面来举一个例子说明一下:
typedef char *a;
int c (int d) {
int e;
int f (int g);
.....
}
这代码应该不难吧,第一个typedef char *a;就是定义了一个数据类型,也就是定义了一个指向字符的指针类型,这里的c和f是external链接属性,其他的链接属性都是none。
其实也很简单,第一句定义的是一个自定义数据类型,这个不算,然后c和f是函数,可以被其他的源文件反复调用的,所以是外部链接属性,external属性
关键字extern和static用于在声明中修改标识符的链接属性。
如果某个声明在正常情况下具有external链接属性,但是在前面加上static就可以把它的链接属性变成internal.
extern这个关键字是为一个标识符指定了external链接属性,这样就可以在其他位置访问这个实体。
后面还会再详细讲的
三.存储类型
变量的存储类型是指存储变量值的内存类型,变量的存储类型决定了变量何时创建、何时销毁和它的值将保持多久。
有三个地方用于存储变量:普通内存、运行时堆栈和硬件寄存器。
静态变量和自动变量
凡是再任何代码块之外声明的变量总是存储在静态内存中,也就是不属于堆栈的内存,这类变量称为静态变量。静态变量在程序运行之前创建,在程序的整个执行期间始终存在。它始终保持原先的值,也就是说一直都是那个实体。
在代码块内部声明的变量的缺省存储类型是自动的,也就是说它存储于堆栈中,称为自动变量,在程序执行到声明自动变量的代码块的时候,自动变量才被创建,当程序的执行流离开代码块时,这些自动变量便自行销毁,那你想想下列这个情况:
for (int i=0;i<n;i++) {
int b=999;
}
这个情况b被反复的创建并且赋值,如果该代码块被数次执行,例如一个函数被反复调用,这些自动变量每次都将重新创建。在代码块再次执行的时候,这些自动变量在堆栈中所占据的内存位置可能和原先的相同也可能和原先的不同。
对于在代码块内部声明的变量,如果给它加上关键字static,可以使它的存储类型从自动变成静态。
注意:修改变量的存储类型并不代表着修改该代码的作用域。并且函数的形参不能声明为静态,因为实参总是在堆栈中传递给参数,用于支持递归。比如:
int b;
int sum (int a) {
b = a + sum (a-1);
}
a的值全是通过堆栈中来的,所以不能使用静态变量。
最后,关键字register可以用于自动变量的声明,提示它们应该存储于机器的硬件寄存器而不是内存中,这类变量称为寄存器变量。通常,寄存器变量比存储于内存中的变量访问起来效率更高。但是编译器并不一定会采纳你的register。
另外,寄存器变量的创建和销毁的时间和自动变量是相同的,但是它需要一些额外的工作。在一个使用寄存器变量的函数返回之前,这些寄存器先前存储的值必须恢复,确保调用者的寄存器变量未被破坏。当函数开始执行的时候,它把需要使用的所有寄存器的内容都保存在堆栈中,当函数返回时,这些值再复制回寄存器中
在许多机器的硬件实现中,并不为寄存器指定地址,因为寄存器的值的保存和恢复,某个特定的寄存器在不同的时刻所保存的值不一定相同。
四.static关键字
前面就有提过,这里做一个总结
当它用于函数定义时,或者用于代码块之外的变量声明时,static关键字用于修改标识符的链接属性,从external改为internal,但标识符的存储类型和作用域不受影响。用这种方式声明的函数或变量只能在声明它们的源文件中使用。
当它用于代码块内部的变量声明时,static关键字用于修改变量的存储类型,从自动变量修改为静态变量,但变量的链接属性和作用域不受影响。用这种方式声明的变量在程序执行之前创建,并在程序的整个执行期间一直存在,而不是每次在代码块开始执行的时候创建,在代码块执行完毕后销毁。