【C语言】从空间和时间两个角度看一个变量


一、从空间角度看变量

1、作用域

变量被定义的位置决定变量的作用范围,即作用域。

(1)代码块作用域

什么是代码块?
答:位于一对大括号之间的所有语句

在代码块中定义的变量具有代码块作用域。

如:
函数中定义的变量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);
}

由此可看出形参也具有代码块作用域。

拥有代码块作用域的只有局部变量;局部变量只有代码块作用域!
也就是代码块作用域和局部变量是绑定的,谁都插不进来,局部变量就是代码块作用域的代名词

局部变量 == 代码块作用域

(2)文件作用域

在代码块外定义的变量具有文件作用域。

因此全局变量具有文件作用域,另外函数名定义在代码块之外,因此也具有文件作用域。

具有文件作用域的标识符作用域是从声明位置开始到文件结尾。
上面这句话有两层意思:
a、具有文件作用域的标识符有效范围并不是整个文件!
b、具有文件作用域的标识符有效范围并不局限在当前文件!只要其他文件有声明该标识符,在另一个文件中,有效范围同样是从声明位置开始到文件结尾。

以下面这个程序为例:

#include<stdio.h>
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的作用范围为红框所示范围:
在这里插入图片描述

(3)函数原型作用域

在函数声明(函数原型)中定义的形参具有函数原型作用域。

函数原型作用域是4种作用域中作用范围最小的,其作用范围为形参定义处到原型声明结束。
如下所示,红框显示的是形参i的作用范围。
在这里插入图片描述
作用范围这么小意味着函数原型重点是形参数据类型,形参名是否和函数定义时一致无关紧要,甚至没有都可以。
在这里插入图片描述

(4)函数作用域

仅适用于goto语句的标签。

只要函数中出现goto语句的标签,该标签的作用范围就是整个函数。

由于编程中要避免使用goto语句,所以该部分不再细讲。


下面对四种作用域进行了总结:

作用域定义位置作用范围
代码块作用域(掌握)局部变量,包括代码块中定义的变量;形参;for(int i;i<5;i++)中的i{…}之间
文件作用域(掌握)函数名,全局变量声明位置到文件结尾
函数原型作用域(了解)函数声明处形参定义处到原型声明结束
函数作用域(无视)函数中整个函数

2、链接属性

应用场景:不同文件中出现相同标识符,怎么判定是不是同一个实体?

功能:用于认定不同文件的标识符(变量名、函数名)是否是同一个实体。

更通俗地说,就是在两个不同文件中的变量、函数声明是否指向同一个实体。

比如:a、b文件同时声明了变量c,链接属性就指定了这两处变量c是否是同一个c。

(1)external(外部的)~与extern配合使用

多个文件中声明的同名标识符表示同一个实体。

程序的全局变量、所有函数默认的链接属性为external。

用extern关键字在声明中指定以引用其他文件中定义的相同标识符。

具有文件作用域的标识符默认具有external链接属性,在b文件中声明就可以使用a文件中定义的全局变量,最好加上extern关键字以表明这是声明而不是定义!函数名可加可不加extern。

//文件test.c
#include<stdio.h>
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);就会报错。

(2)internal(内部的)~与static配合使用

单个文件中声明的同名标识符表示同一个实体。

用static关键字在声明中指定让标识符变为该文件私有。

用static关键字可以使得原先具有external属性的标识符变为internal属性
这句话需要注意的是:
1)只能对具有external属性的标识符使用,才会生效,即只能用于全局变量和函数,在标识符前面加上static可以将它们变成静态全局变量和静态函数,作用范围仅限所在文件,其他文件无法访问。
2)修改不可逆,一旦将链接属性变为internal,就不能在后面再变回去了。

注意:被internal修饰的标识符依然具有文件作用域;也就是说具有文件作用域的标识符可能有internal属性,也可能有external属性。

(3)none(无)~无对应关键字

声明的同名标识符被当做独立不同的实体

除了全局变量、所有函数,其余标识符的默认链接属性为none,如局部变量,函数形参,标签


链接属性谁的默认链接属性是它对应关键字
external具有文件作用域的标识符extern
internal/static
none除了具有文件作用域的标识符其余都是/

二、从时间角度看变量

1、生存期

生存期用来描述一个标识符从建立到销毁的时间长度。

(1)静态存储器——高寿

具有文件作用域的变量具有静态存储器,即全局变量和函数名。

全局变量和函数名一旦定义,直到程序关闭才被释放,因为他们存在全局区。

(2)自动存储器——短命

具有代码块作用域的变量具有自动存储器,即局部变量,形参等。

变量在代码块运行结束时就自动释放存储空间,因为他们存在栈区。


生存期寿命对应作用域
静态存储期程序结束才销毁文件作用域
自动存储期代码块结束就销毁代码块作用域

三、存储类型

定义一个变量,实际的格式为:

[存储类型] [数据类型] 变量名;

我们常用的int a;
其实是简写版,完整版为:auto int a;这里auto可以省略。

C语言的标识符通过作用域、链接属性、生存期可组合成多种存储方案。

1、自动auto——局部变量的默认存储类型

在代码块中声明的变量默认存储类型为auto,包括局部变量、形参等。

它具有代码块作用域,无链接属性(none),自动存储期。

auto不能修饰全局变量!

2、寄存器变量——用register修饰的局部变量

寄存器存在于CPU内部,寄存器的读写速度是最快的。

将一个变量声明为寄存器变量,该变量就有可能被存在寄存器中,因为寄存器空间十分有限,不是你想存就能存的,编译器会自己判断是否存入寄存器。如果编译器认为没有必要存入寄存器,那么该变量就会退化为auto。

它也具有代码块作用域,无链接属性(none),自动存储期。

register只能修饰局部变量不能修饰全局变量!因为修饰全局变量会一直占用寄存器。

register变量必须是能被CPU寄存器接受的类型。这意味着register必须是一个单个的值,其长度应小于等于整形的长度。
也就是最多定义一个存储类型为register的int变量:

{//定义在代码块中,表示a为局部变量
	register int a=10;
}

不能用&获取register变量的地址!无论这个变量是否被实际地存放在寄存器里了

3、静态外部链接——用extern修饰的全局变量或函数

用extern修饰全局变量或者函数名,其他文件将可以访问该全局变量或者调用该函数。

如下所示:

extern int count;
void a(void)
{
   count++;
}

#include<stdio.h>
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<stdio.h>
int count=3;
void a(void);
int main()
{
  a();
  printf("%d\n",count);
  return 0;
}

但程序中int count;会让人误以为又定义了一个新变量,其实它只是一个声明而已了,因此,最好还是加上extern。

静态外部链接存储类型具有文件作用域,外部链接属性(external),静态存储期。

4、静态内部链接——用static修饰全局变量或函数

用static修饰全局变量或者函数,其链接属性将由external变为internal,其作用范围被限制在当前文件,其他文件无法访问。

如果某个全局变量或者函数仅在当前文件有使用到,可以加上static。

静态内部链接存储类型具有文件作用域,internal链接属性和静态存储期。

5、静态无链接——用static修饰的局部变量

用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个有意义。

五、答疑

1、多文件编程中的文件作用域

待解答

2、具有文件作用域的标识符作用范围最多有多大?

文件作用域的作用范围是4种作用域中范围最广的!

具有文件作用域的标识符作用范围最多覆盖整个文件!而不是整个工程!

全局变量不是可以在其他文件访问吗?那它的作用范围不应该是整个工程吗?

这个其实是链接属性发挥的作用,全局变量或者函数具有外部链接属性,只要在其他文件中声明了(变量最好加上extern,函数不用),在其他文件中就可以访问该变量或者函数。

借助external链接属性,全局变量或者函数的作用范围跨文件了。
在这里插入图片描述

3、全局变量的存储类型是什么?

全局变量默认具有静态外部链接存储属性,加不加extern都一样,但最好是加上。

用static修饰的全局变量具有静态内部链接属性,仅在当前文件可以被访问。

4、哪些是局部变量?

(1)函数中包括main函数中定义的变量为局部变量
(2)形参变量为局部变量
(3)无缘无故定义一个代码块,代码块中定义的变量为局部变量
看下面这个例子:

#include<stdio.h>
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<stdio.h>
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

5、哪些变量默认是0?

(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带你飞》

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值