变量被定义的位置决定变量的作用范围,即作用域。
什么是代码块?
答:位于一对大括号之间的所有语句
在代码块中定义的变量具有代码块作用域。
如:
函数中定义的变量a:
int fun(int x, int y)
{
int a;
a = x + y;
return a;
}
main函数中定义的res:
int main()
{
int res;
res = fun(1, 2);
cout << res << endl;
system("pause");
return 0;
}
while循环中的m:
int main()
{
int res;
res = fun(1, 2);
printf("%d\n",res);
while(res--)
{
int m=0;
printf("%d\n",m++);
}
return 0;
}
for循环中定义的i:
for(int i=0;i<3;i++)
{
printf("%d\n",i);
}
打印结果
0
1
2
for内部定义的m:
for(int i=0;i<3;i++)
{
int m=0;
printf("%d\n",m++);
}
以上变量都具有代码块作用域
另外,比较特殊的是函数的形参也具有代码块作用域:
void swap (int x, int y)
{
int temp;
temp = x;
x = y;
y = temp;
printf ("x = %d, y = %d\n", x, y);
}
调用函数时相当于进行如下操作:
void swap (...)
{
int x=a; //←
int y=b; //←注意这里,头两行是调用函数时的隐含操作
int temp;
temp = x;
x = y;
y = temp;
printf ("x = %d, y = %d\n", x, y);
}
由此可看出形参也具有代码块作用域。
拥有代码块作用域的只有局部变量;局部变量只有代码块作用域!
也就是代码块作用域和局部变量是绑定的,谁都插不进来,局部变量就是代码块作用域的代名词局部变量 == 代码块作用域
在代码块外定义的变量具有文件作用域。
因此全局变量具有文件作用域,另外函数名定义在代码块之外,因此也具有文件作用域。
具有文件作用域的标识符作用域是从声明位置开始到文件结尾。
上面这句话有两层意思:
a、具有文件作用域的标识符有效范围并不是整个文件!
b、具有文件作用域的标识符有效范围并不局限在当前文件!只要其他文件有声明该标识符,在另一个文件中,有效范围同样是从声明位置开始到文件结尾。
以下面这个程序为例:
#include
void func(void);
int main()
{
extern int count;
func();
count++;
printf("In main,count= %d\n",count);
return 0;
}
int count;
void func(void)
{
count++;
printf("In func,count = %d\n",count);
}
程序中的func,main,count都具有文件作用域。
其中func的作用范围为红框所示范围:
count的作用范围为红框所示范围:
在函数声明(函数原型)中定义的形参具有函数原型作用域。
函数原型作用域是4种作用域中作用范围最小的,其作用范围为形参定义处到原型声明结束。
如下所示,红框显示的是形参i的作用范围。
作用范围这么小意味着函数原型重点是形参数据类型,形参名是否和函数定义时一致无关紧要,甚至没有都可以。
仅适用于goto语句的标签。
只要函数中出现goto语句的标签,该标签的作用范围就是整个函数。
由于编程中要避免使用goto语句,所以该部分不再细讲。
下面对四种作用域进行了总结:
作用域 | 定义位置 | 作用范围 |
---|---|---|
代码块作用域(掌握) | 局部变量,包括代码块中定义的变量;形参;for(int i;i<5;i++)中的i | {…}之间 |
文件作用域(掌握) | 函数名,全局变量 | 声明位置到文件结尾 |
函数原型作用域(了解) | 函数声明处 | 形参定义处到原型声明结束 |
函数作用域(无视) | 函数中 | 整个函数 |
应用场景:不同文件中出现相同标识符,怎么判定是不是同一个实体?
功能:用于认定不同文件的标识符(变量名、函数名)是否是同一个实体。
更通俗地说,就是在两个不同文件中的变量、函数声明是否指向同一个实体。
比如:a、b文件同时声明了变量c,链接属性就指定了这两处变量c是否是同一个c。
多个文件中声明的同名标识符表示同一个实体。
程序的全局变量、所有函数默认的链接属性为external。
用extern关键字在声明中指定以引用其他文件中定义的相同标识符。
具有文件作用域的标识符默认具有external链接属性,在b文件中声明就可以使用a文件中定义的全局变量,最好加上extern关键字以表明这是声明而不是定义!函数名可加可不加extern。
//文件test.c
#include
void a(void);
int count;
int main()
{
a();
printf("%d\n",count);
return 0;
}
//文件a.c
extern int count;
void a(void)
{
count++;
}
以上两个文件存在一个目录下面,使用gcc test1.c a.c -Wall && ./a.out执行结果为1。
但如果删掉声明部分extern int count;以及void a(void);就会报错。
单个文件中声明的同名标识符表示同一个实体。
用static关键字在声明中指定让标识符变为该文件私有。
用static关键字可以使得原先具有external属性的标识符变为internal属性
这句话需要注意的是:
1)只能对具有external属性的标识符使用,才会生效,即只能用于全局变量和函数,在标识符前面加上static可以将它们变成静态全局变量和静态函数,作用范围仅限所在文件,其他文件无法访问。
2)修改不可逆,一旦将链接属性变为internal,就不能在后面再变回去了。
注意:被internal修饰的标识符依然具有文件作用域;也就是说具有文件作用域的标识符可能有internal属性,也可能有external属性。
声明的同名标识符被当做独立不同的实体
除了全局变量、所有函数,其余标识符的默认链接属性为none,如局部变量,函数形参,标签
链接属性 | 谁的默认链接属性是它 | 对应关键字 |
---|---|---|
external | 具有文件作用域的标识符 | extern |
internal | / | static |
none | 除了具有文件作用域的标识符其余都是 | / |
生存期用来描述一个标识符从建立到销毁的时间长度。
具有文件作用域的变量具有静态存储器,即全局变量和函数名。
全局变量和函数名一旦定义,直到程序关闭才被释放,因为他们存在全局区。
具有代码块作用域的变量具有自动存储器,即局部变量,形参等。
变量在代码块运行结束时就自动释放存储空间,因为他们存在栈区。
生存期 | 寿命 | 对应作用域 |
---|---|---|
静态存储期 | 程序结束才销毁 | 文件作用域 |
自动存储期 | 代码块结束就销毁 | 代码块作用域 |
定义一个变量,实际的格式为:
[存储类型] [数据类型] 变量名;
我们常用的int a;
其实是简写版,完整版为:auto int a;这里auto可以省略。
C语言的标识符通过作用域、链接属性、生存期可组合成多种存储方案。
在代码块中声明的变量默认存储类型为auto,包括局部变量、形参等。
它具有代码块作用域,无链接属性(none),自动存储期。
auto不能修饰全局变量!
寄存器存在于CPU内部,寄存器的读写速度是最快的。
将一个变量声明为寄存器变量,该变量就有可能被存在寄存器中,因为寄存器空间十分有限,不是你想存就能存的,编译器会自己判断是否存入寄存器。如果编译器认为没有必要存入寄存器,那么该变量就会退化为auto。
它也具有代码块作用域,无链接属性(none),自动存储期。
register只能修饰局部变量不能修饰全局变量!因为修饰全局变量会一直占用寄存器。
register变量必须是能被CPU寄存器接受的类型。这意味着register必须是一个单个的值,其长度应小于等于整形的长度。
也就是最多定义一个存储类型为register的int变量:
{//定义在代码块中,表示a为局部变量
register int a=10;
}
不能用&获取register变量的地址!无论这个变量是否被实际地存放在寄存器里了
用extern修饰全局变量或者函数名,其他文件将可以访问该全局变量或者调用该函数。
如下所示:
extern int count;
void a(void)
{
count++;
}
#include
int count=3;
extern void a(void);
int main()
{
a();
printf("%d\n",count);
return 0;
}
其实跟auto一样,extern不加也行:
int count;
void a(void)
{
count++;
}
#include
int count=3;
void a(void);
int main()
{
a();
printf("%d\n",count);
return 0;
}
但程序中int count;会让人误以为又定义了一个新变量,其实它只是一个声明而已了,因此,最好还是加上extern。
静态外部链接存储类型具有文件作用域,外部链接属性(external),静态存储期。
用static修饰全局变量或者函数,其链接属性将由external变为internal,其作用范围被限制在当前文件,其他文件无法访问。
如果某个全局变量或者函数仅在当前文件有使用到,可以加上static。
静态内部链接存储类型具有文件作用域,internal链接属性和静态存储期。
用static修饰局部变量,该变量的生存期将由自动存储期变为静态存储期,跟全局变量和函数一样,直到程序结束才销毁。
它具有代码块作用域,无链接属性(none),静态存储期。
不能用static修饰形参!
存储类型 | 作用域 | 链接属性 | 生存期 | 说明 |
---|---|---|---|---|
自动 | 代码块作用域 | none | 自动存储期 | 局部变量默认的存储类型 |
寄存器 | 代码块作用域 | none | 自动存储期 | 用register修饰局部变量 |
静态外部链接 | 文件作用域 | external | 静态存储期 | 全局变量或函数的默认存储类型,加不加extern都一样 |
静态内部链接 | 文件作用域 | internal | 静态存储期 | 用static修饰全局变量或函数 |
静态无链接 | 代码块作用域 | none | 静态存储期 | 用static修饰局部变量 |
从空间角度看变量,即想要认清一个变量,更准确的说法是一个标识符,它的势力范围是什么。
一个标识符的定义位置决定了它的作用域:
(1)在代码块中,在形参中,在for中定义的标识符具有代码块作用域;
(2)在代码块之外定义的标识符,包括全局变量和函数名具有文件作用域;
(3)函数声明中的形参具有函数原型作用域;(了解即可)
(4)单独对于goto语句的标签,其具有函数作用域。(知道就行)
无论什么标识符,都自带链接属性:
(1)具有文件作用域的标识符,即全局变量和函数名,具有external链接属性,拥有此属性也只是其他文件能访问的必要条件,而不是充分条件,还需要在其他文件中声明才能访问该变量(最好加上extern修饰)。
(2)除此之外,其他标识符都具有none链接属性,即具有代码块作用域,函数原型作用域,函数作用域的标识符都具有none链接属性。
(3)没有标识符天生就具有internal属性,但只有具有external链接属性的标识符才有资格变为internal属性,且该过程是不可逆的,通过static修饰全局变量或者函数,他们将变成静态全局变量和静态函数,其他文件无法访问,为本文件专用,就像皇家卫兵一样只服从于自己的King!
从时间角度来看,标识符有长寿的也有短命的
(1)有文件作用域的标识符长寿(静态存储期)
(2)有代码块作用域的标识符短命(自动存储期)
C语言的标识符通过作用域、链接属性、生存期可组合成多种存储方案:
其中存储期有2种,作用域主要的有2种,链接属性有3种,故理论上有12种存储方案:
作用域 | 生存期 | 链接属性 | 声明方式 | 存储类别 |
---|---|---|---|---|
代码块作用域 | 静态 | external | / | |
代码块作用域 | 静态 | internal | / | |
代码块作用域 | 静态 | none | 用static修饰局部变量 | 静态无链接 |
代码块作用域 | 自动 | external | / | |
代码块作用域 | 自动 | internal | / | |
代码块作用域 | 自动 | none | 局部变量或者register修饰的局部变量 | 自动/寄存器 |
文件作用域 | 静态 | external | 用extern修饰全局变量或者函数 | 静态外部链接 |
文件作用域 | 静态 | internal | 用static修饰全局变量或者函数 | 静态内部链接 |
文件作用域 | 静态 | none | / | |
文件作用域 | 自动 | external | / | |
文件作用域 | 自动 | internal | / | |
文件作用域 | 自动 | none | / |
其中只有5个有意义。
待解答
文件作用域的作用范围是4种作用域中范围最广的!
具有文件作用域的标识符作用范围最多覆盖整个文件!而不是整个工程!
全局变量不是可以在其他文件访问吗?那它的作用范围不应该是整个工程吗?
这个其实是链接属性发挥的作用,全局变量或者函数具有外部链接属性,只要在其他文件中声明了(变量最好加上extern,函数不用),在其他文件中就可以访问该变量或者函数。
借助external链接属性,全局变量或者函数的作用范围跨文件了。
全局变量默认具有静态外部链接存储属性,加不加extern都一样,但最好是加上。
用static修饰的全局变量具有静态内部链接属性,仅在当前文件可以被访问。
(1)函数中包括main函数中定义的变量为局部变量
(2)形参变量为局部变量
(3)无缘无故定义一个代码块,代码块中定义的变量为局部变量
看下面这个例子:
#include
int main()
{
int i=0;
//无缘无故定义一个代码块
{
int i=1;
printf("代码块中i= %d\n",i);
}
printf("代码块外i= %d\n",i);
return 0;
}
代码块中i= 1
代码块外i= 0
(4)for中定义的变量
#include
int main()
{
for(int i=0;i<3;i++)
{
printf("i=%d\n",i);
}
//printf("i=%d\n",i);超出i的作用域
return 0;
}
i=0
i=1
i=2
(1)全局变量
(2)static修饰的局部变量
1、https://www.cnblogs.com/p0ise/p/c-language-linkage.html#:~:text=什么是链接属性,是否是同一个c。
2、https://blog.css8.cn/post/13758733.html
3、《C Primer Plus》
4、《带你学C带你飞》