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

文章目录

  • 一、从空间角度看变量
    • 1、作用域
      • (1)代码块作用域
      • (2)文件作用域
      • (3)函数原型作用域
      • (4)函数作用域
    • 2、链接属性
      • (1)external(外部的)~与extern配合使用
      • (2)internal(内部的)~与static配合使用
      • (3)none(无)~无对应关键字
  • 二、从时间角度看变量
    • 1、生存期
      • (1)静态存储器——高寿
      • (2)自动存储器——短命
  • 三、存储类型
    • 1、自动auto——局部变量的默认存储类型
    • 2、寄存器变量——用register修饰的局部变量
    • 3、静态外部链接——用extern修饰的全局变量或函数
    • 4、静态内部链接——用static修饰全局变量或函数
    • 5、静态无链接——用static修饰的局部变量
  • 四、总结
  • 五、答疑
    • 1、多文件编程中的文件作用域
    • 2、具有文件作用域的标识符作用范围最多有多大?
    • 3、全局变量的存储类型是什么?
    • 4、哪些是局部变量?
    • 5、哪些变量默认是0?
  • 参考资料


一、从空间角度看变量

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
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的作用范围为红框所示范围:
【C语言】从空间和时间两个角度看一个变量_第1张图片
count的作用范围为红框所示范围:
【C语言】从空间和时间两个角度看一个变量_第2张图片

(3)函数原型作用域

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

函数原型作用域是4种作用域中作用范围最小的,其作用范围为形参定义处到原型声明结束。
如下所示,红框显示的是形参i的作用范围。
【C语言】从空间和时间两个角度看一个变量_第3张图片
作用范围这么小意味着函数原型重点是形参数据类型,形参名是否和函数定义时一致无关紧要,甚至没有都可以。
【C语言】从空间和时间两个角度看一个变量_第4张图片

(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
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
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),静态存储期。

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

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

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

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

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

用static修饰局部变量,该变量的生存期将由自动存储期变为静态存储期,跟全局变量和函数一样,直到程序结束才销毁。

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

不能用static修饰形参!


存储类型 作用域 链接属性 生存期 说明
自动 代码块作用域 none 自动存储期 局部变量默认的存储类型
寄存器 代码块作用域 none 自动存储期 用register修饰局部变量
静态外部链接 文件作用域 external 静态存储期 全局变量或函数的默认存储类型,加不加extern都一样
静态内部链接 文件作用域 internal 静态存储期 用static修饰全局变量或函数
静态无链接 代码块作用域 none 静态存储期 用static修饰局部变量

对比静态内部链接和静态无链接:
【C语言】从空间和时间两个角度看一个变量_第5张图片

四、总结

从空间角度看变量,即想要认清一个变量,更准确的说法是一个标识符,它的势力范围是什么。

一个标识符的定义位置决定了它的作用域:
(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链接属性,全局变量或者函数的作用范围跨文件了。
【C语言】从空间和时间两个角度看一个变量_第6张图片

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

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

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

4、哪些是局部变量?

(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

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

你可能感兴趣的:(C语言总结,c语言,开发语言)