2.5作用域
标识符的作用域:程序中该标识符可以被使用的区域。
编译器可以识别4种不同类型的作用域:
文件作用域:任何在所有代码块之外声明的标识符都具有文件作用域
代码块作用域:(代码块:位于一对花括号之间的所有语句)
任何在代码块开始位置声明的标识符都具有代码块作用域。函数定义的形式参数也具有代码块作用域。
当代码块处于嵌套状态时,声明于内层代码块的标识符作用域到达该待毛孔的尾部便告终止。
当内层代码有一个标识符与外层代码的一个标识符同名,外层代码的那个标识符无法在内层代码块中通过名字访问。
原型作用域:从变量定义一直到原型声明的末尾
intceshi(int b);
intmain(void){
return 0;
}
intceshi(int a)
{
return (a+1);
}
函数作用域:适用于语句标签goto,最好不要用该方式,难维护!
2.6链接属性
那么问题来了,如果相同的标识符出现在几个不同的源文件中时,它们表示的是同一个实体,还是不同的实体?
标识符的链接属性决定如何处理在不同文件中出现的标识符,标识符的作用域与它的链接属性有关,但这两个属性并不相同。
链接属性分为3种:external、internal、none
1.none的标识符总是被当作单独的个体,也就是说该标识符的多个声明被当作独立不同的实体;具有代码块作用域或者函数原型作用域的变量有空链接,意味着它们是由其定义所在的代码块或函数原型私有。
2.internal的标识符在同一个源文件内的所有声明中都指同一个实体,但位于不同源文件的多个声明则分属不同的实体,用static声明
external的标识符不论声明多少次、位于几个源文件都表示同一个实体,用extern声明
具有文件作用域的变量可能有内部或者外部链接,例如:全局变量、子函数
关键字extern和static用于在声明中修改标识符的链接属性
1.如果某个声明在正常情况下具有external属性,在它前面加上static关键字可以使它的链接属性变为internal。
例:
int b;//全局变量,extern链接
staticint b;//internal链接,变量为这个源文件所私有。
staticint c(int b)//防止被其他源文件调用
2.extern关键字的规则更为复杂,它为一个标识符指定external链接属性,这样便可以访问在其他任何位置定义的这个外部变量/全局变量。
当extern关键字用于源文件中第一个标识符的第1次声明时,它指定该标识符具有external链接属性
但是,如果它用于该标识符的第2次或以后的声明时,它并不会更改由第1次声明所指定的链接属性。
例:
static inti;
intfunc()
{
extern int i;
}
2.7存储类型
变量的存储类型是指存储变量值的内存类型。
三个地方用于存储变量:普通内存、运行时堆栈、硬件寄存器。
变量的缺省存储类型取决于它的声明位置。
1.静态变量:存储于静态内存中的变量(例如全局变量和static申明的局部变量)。在程序运行之前创建,在整个执行期间时钟存在,它始终保持原先的值,除非赋予它不同的值或者程序结束。
2.自动变量:在代码块内部声明的变量,存储于堆栈中。在程序执行到声明自动变量的代码时,自动变量才被创建,当程序执行离开该代码块时,这些自动变量便自行销毁。
如果给自动变量加上static,使它的存储类型从自动变成静态,则变量在整个程序执行过程中一直存在,但是变量的作用域不会改变,仍然是在该代码块内部。
3.寄存器变量:register用于声明自动变量,这些变量应该存储于机器的硬件寄存器而不是内存之中。
编译器有自己的一套寄存器优化方案,可能忽略register。
在典型情况下,你希望把使用频率最高的那些变量声明为寄存器变量。在有些计算机中,如果把指针声明为寄存器变量,程序的效率将得到提高。
还可以把函数的形参声明为寄存器变量,编译器会在函数的起始位置生成指令,把这些值从堆栈复制到寄存器中,但是有可能反而更费时间。
初始化
补充:c里等号的意义是分两种情况,一种是在表达式里, 而另一种是在变量声明中。在变量声明中的等号不可以看作是赋值,它只是初始化,也就是说它的意义只是告诉编译器如何初始化这个内存空间。而表达式中的 = 就是赋值的意思了。
静态变量的初始化,可以把可执行程序文件想要初始化的值放在变量将会使用的位置,当可执行文件载入到内存时,已经保存了正确初始值的位置将赋值给那个变量。缺省值为0。
自动变量的初始化,当程序链接时还无法判断自动变量的存储位置。显式初始化,将在代码块的起始处插入一条隐式的赋值语句。
/*****************************************************************************/
静态变量:其初始化值被放在程序执行时将使用的位置,所以不需额外时间,也不需额外指令。如果不显示初始化,静态变量将被初始化为0。
自动变量:程序链接时仍无法判断变量的存储位置。所以,自动变量没有缺省的初始值,而显示初始化将在代码的起始处插入一条隐式赋值语句。
这个技巧造成4个后果:
1.自动变量初始化较之赋值语句效率并无提高。除了声明为const的变量之外。
2.自动变量所在函数块每次执行时,都要对自动变量重新赋值。静态变量(全局变量和静态局部变量)只是在程序开始执行前初始化一次。
3.由于初始化在运行时执行,因此可以使用任何表达式作为初始化值。
4.除非对自动变量显式初始化,否则当自动变量创建时,它的值一定是垃圾。
那么问题来了,为什么静态变量的初始化不能使用表式?例如:static int a = b + c
因为静态变量在程序执行之前创建!如果b是自动变量的话,b的值不确定,从而a的值也无法确定。即使b是静态变量也不行!
#include<stdio.h>
int a=0;
int b=1;
intc=a+b;
intmain(int argc, char *argv[]) {
printf("%d %d %d",a,b,c);
return 0;
}
报错:int c=a+b;[Error]initializer element is not constant
/*****************************************************************************/
2.8static关键字
第一种情况:函数定义、代码块之外的变量声明
static用于修饰标识符的链接属性,从external变成internal,但标识符的存储类型和作用域不受影响。用这种方式声明的函数或变量只能在声明它们的源文件中访问。
第二种情况:代码块内部的变量声明,static关键字用于修改变量的存储类型,从自动变量变成静态变量,但变量的链接属性和作用域不受影响。用这种方式声明的变量在程序执行之前创建,并在程序的整个执行期间一直存在,而不是在每次代码开始执行时创建,在程序执行完之后销毁!。
总结:
变量类型 | 声明的位置 | 是否存于堆栈 | 作用域 | 如果声明static |
全局 | 所有代码块之外 | 否 | 从声明处到文件尾 | 不允许从其他源文件访问 |
局部 | 代码块起始处 | 是 | 整个代码块 | 程序执行之前,变量存于静态内存中,在程序执行期一直保存 |
形式参数 | 函数头部 | 是 | 整个函数 | 不允许 |