Bootstrap

C语言-局部变量、全局变量、静态变量和生命周期

一、局部变量与全局变量

1、局部变量

在函数内部定义的变量是局部变量。局部变量的有效范围是局限于函数内部的,形参也是局部变量。局部变量的改变无法影响到主调函数。正是因为局部变量的有效使用范围的局限,可以最大程度的确保各函数之间的独立性,避免函数之间相互干扰。
还有一种局部变量是作用于复合语句的局部变量,它们的有效范围局限在复合语句之内,一般用作小范围内的局部变量。

int main(void)
{
	int a = 1;	/*主函数的局部变量,有效范围整个主函数*/
	{			/*复合语句开始*/
		int b = 2;	/*复合语句内的局部变量,有效范围在两个大括号之内*/
		...
	}				/*复合语句结束*/
	printf("%d", a);	/*出了复合语句变量b就无法使用了*/
	return 0;
}

注意: 局部变量一般定义在函数或复合语句的开始处,标准C规定不能定义在中间位置。但是C99就可以定义在任何位置了。C99常将for语句的循环变量定义为局部变量,其有效范围仅在for语句中,for语句结束后该局部变量即失效。

	/*C99才可以使用的语法*/
	for(int i=0; i<100; i++){	/*i仅在for语句内可以使用*/
	}

2、全局变量

与局部变量相对应的就是全局变量。它是定义在函数外的变量,不属于任何函数。其作用范围是从定义开始到程序结束,对作用范围内所有的函数都起作用。
全部变量的定义格式与局部变量完全一致,只是定义的位置不在函数内,它可以定义在函数的头部,也可以定义在两个函数的中间或程序尾部,只要在函数外部即可。
一般情况下把全局变量定义在程序的最前面,即第一个程序的前面。
全局变量和局部变量可以同名。当某函数的局部变量与全局变量同名时,那么全局变量在该函数中不起作用,局部变量起作用。若不存在同名变量的函数,那么全局变量仍然起作用。同样的,当函数局部变量与复合语句的局部变量同名时,则以复合语句定义的为准。

注: 全局变量虽然可以用于多个函数之间的数据交流,但一般情况下应尽量使用局部变量和函数参数,这样能够避免不同的函数之间的相互干扰,可以提高程序的质量。在大型程序中,这一点尤其重要。

int a = 2, b = 3;
void fun(void)
{
	int a = 6, b = 2;
	{						
		int a = 9, b = 0;	/*在这个复合语句中,这两个变量生效,覆盖掉与它同名的全局变量和局部变量*/
		printf("a = %d, b = %d\n", a, b);	/*此时:a = 9, b = 0*/
	}
	printf("a = %d, b = %d\n", a, b);	/*这里a和b生效的是该函数开头处定义的a和b,它们覆盖掉全局变量,此时:a = 6, b = 2*/
}
void fun1(void)
{
	printf("a = %d, b = %d\n", a, b);/*这里的a和b就是全局的a和b,因为没有与之同名的变量此时:a = 2, b = 3*/
}

全局变量可以解决函数多结果返回的问题,但全局变量更多地用于多函数间的全局数据表示。

例1:计算2个复数的和与积

若:c1 = x1 + (y1)i, c2 = x2 + (y2)i,则:
c1 + c2 = (x1 + x2) + (y1 + y2)i
c1 * c2 = (x1 * x2 - y1 * y2) + (x1 * y2 + x2 * y1)i

#include <stdio.h>	
double result_real, result_imag;	/*全局变量,用于存放函数结果*/

void complex_add(double real1, double imag1, double real2, double imag2);
void complex_prod(double real1, double imag1, double real2, double imag2);

int main(void)
{
	double imag1, imag2, real1, real2;	/*两个复数的实、虚部变量*/
	
	printf("Enter 1st complex number(real and imaginary):");
	scanf("%lf%lf", &real1, &imag1);	/*输入第1个复数*/
	printf("Enter 2nd complex number(real and imaginary):");
	scanf("%lf%lf", &real2, &imag2);	/*输入第2个复数*/
	complex_add(real1, imag1, real2, imag2);	/*求复数之和*/
	printf("addition of complex is %f + %fi\n", result_real, result_imag);
	complex_prod(real1, imag1, real2, imag2);	/*求复数之积*/
	printf("product of complex is %f + %fi\n", result_real, result_imag);
	
	return 0;
}

/*定义求复数之和函数*/
void complex_add(double real1, double imag1, double real2, double imag2)
{
	result_real = real1+real2;
	result_imag = imag1+imag2;
}

/*定义求复数之积函数*/
void complex_prod(double real1, double imag1, double real2, double imag2)
{
	result_real = real1*real2-imag1*imag2;
	result_imag = real1*imag2+real2*imag1;
}

Enter 1st complex number(real and imaginary):-4 8
Enter 2nd complex number(real and imaginary):2.5 1.6
addition of complex is -1.500000 + 9.600000i
product of complex is -22.800000 + 13.600000i

两个自定义函数的运算结果有两个数值,分别是复数的实部和虚部,而return语句是无法返回两个及以上的数值的。这里将他们定义成了全局变量,result_real和result_imag分别代表运算的实部和虚部。它们分别在main函数和另外两个自定义的函数中使用,最后的结果也可以影响到主调函数。该例程当虚部为负数和0的情况没有进行处理。

例2:财务现金记账

先输入操作类型(1收入,2支出,0结束),在输入操作金额,计算现金剩余额,经多次操作直到输入操作类型为0时结束。

#include <stdio.h>

double cash;	//定义全局变量,保存现金余额

/**********函数声明*************/ 
void income(double number);
void expend(double number);	

int main(void)
{
	int choice;
	double value; 
	
	cash = 0;				//初始金额=0
	printf("Enter operate choice(0--end, 1--income, 2--expend):");
	scanf("%d", &choice);	//输入操作现金额
	while(choice != 0)		//若输入类型为0,循环结束 
	{
		if (choice==1 || choice==2)	
		{
			printf("Enter cash value:");	//输入操作现金额
			scanf("%lf", &value);
			if (choice == 1)
			{
				income(value);		//函数调用,计算现金收入 
			} else {
				expend(value);		//函数调用,计算现金支出 
			} 
			printf("current cash:%.2f\n", cash);
		}
		printf("Enter operate choice(0--end, 1--income, 2--expend):");
		scanf("%d", &choice);		//继续输入操作类型 
	}
	
	return 0;
}

/*定义计算现金收入函数*/
void income(double number)
{
	cash = cash + number;		//改变全局变量cash 
}

/*定义计算现金支出函数*/
void expend(double number)
{
	cash = cash - number;		//改变全局变量cash	
} 

Enter operate choice(0–end, 1–income, 2–expend):1
Enter cash value:673
current cash:673.00
Enter operate choice(0–end, 1–income, 2–expend):2
Enter cash value:34
current cash:639.00
Enter operate choice(0–end, 1–income, 2–expend):2
Enter cash value:551
current cash:88.00
Enter operate choice(0–end, 1–income, 2–expend):1
Enter cash value:167
current cash:255.00
Enter operate choice(0–end, 1–income, 2–expend):0

本例中变量cash是全局变量,它保存的是现金余额,被主调函数、现金收入与支出函数共用,使用场合及其意义与数值都是明确和唯一的。

二、生命周期

1、变量生存周期

生存周期也称生命周期。变量是保存变化数据的工作单元,计算机用内存单元来对应实现。一旦程序中定义了变量,计算机在执行过程中就会根据变量类型分配对应的内存单元供变量保存数据。
对于一般的程序,计算机都是从主函数开始运行的,使得main()函数中所有的局部变量,一开始就在内存数据区中分配了存储单元。而其他的函数被调用前,它们的局部变量并未分配存储单元,只有当函数被调用时,其形参和局部变量才会被分配相应存储单元;一旦函数调用结束返回主调函数,在函数中定义的所有形参和局部变量都不复存在,相应的存储单元被系统收回。根据这种特性,把局部变量称为自动变量,即函数被调用时,系统自动为其局部变量分配存储单元;一旦该函数调用结束(不一定是整个程序运行结束),所有分配给局部变量的单元由系统自动回收。变量从定义开始分配存储单元,到运行结束存储单元被回收,整个过程称为变量生存周期。
自动变量定义形式为:

	auto 类型名 变量表;
例如:
	auto int x, y;

定义自动变量时,auto可以省略,一般我们所定义的变量都是自动变量。
当main()函数调用其他函数时,由于main()函数还未运行结束,所以它的局部变量仍然存在,还在生存周期中,但是由于局部变量的作用范围,main()函数中的局部变量单元不能在其他函数中使用。只有会到主函数后,那些局部变量才可以继续使用。变量的作用范围和生存周期是两个不同的概念。
而全局变量,它在整个程序的执行开始一直到程序的结束,都有效,对应的存储单元始终保持,因此它的生存周期是整个程序的执行周期。

2、变量存储的内存分布

自动变量和全局变量的生存周期不同。为了便于计算机存储管理,C语言把保存所有变量的数据区分成动态存储区与静态存储区。它们的管理方式完全不同,动态存储区是使用堆栈来管理的,适合函数动态分配与回收存储单元。而静态存储区相对固定,它用于存放全局变量和静态变量。下图是例1的存储分布。
在这里插入图片描述
从上图可以看到,用户存储区包括程序区和数据区,程序代码与数据变量是分开存放的,且动态存储区中的变量按函数组织,main()函数的real1与complex_add()函数的real1分属不同单位,有各自的内存单元。

3、静态变量

静态存储区中,除了全局变量外,还有一种特殊的局部变量-----静态局部变量。静态局部变量存放在静态存储区,不会像普通局部变量因为函数调用结束而被系统收回,它的生存周期也会持续到程序结束。由于存储单元被保留,一旦含有静态局部变量的函数被再次调用,则静态局部变量会被重新激活,上一次函数调用和的值仍然保存着,可以供本次调用继续使用。
静态变量定义的格式:

	static 类型名 变量表

静态局部变量与自动变量:自动变量若没有赋初值,那么其存储单元是随机值。而静态局部变量如果定义时没有赋初值,系统会自动赋0。静态局部变量的赋初值只在函数第一次调用时起作用,以后调用都按前一次调用保留的值使用。静态局部变量的生存周期始于函数的第一次调用,贯穿于整个程序。当函数第一次调用时,静态局部变量的内存单元得到分配,并赋初值,当函数再次调用时,它的局部变量单元已经存在了,计算机并不会再次为它分配内存单元,就不会再赋初值。但是静态局部变量的作用范围有限,不能作用于其他的函数(包括主函数)。
静态局部变量与全局变量的共同点和不同点:静态局部变量与全局变量都位于静态存储区,生存周期贯穿整个程序的执行过程,若没有被赋初值,则系统都会赋值0。它们的区别是作用范围不同,全局变量可以作用于所有的函数,而静态变量只能作用于它所定义的函数。
静态变量很多情况下是用来替代全局变量的,因为静态变量是无法影响到其他函数,而全局变量使用多的话会给程序的维护增加困难。
同样也有静态全局变量,静态全局变量的作用范围也是有限制的,它是限制在当前它所定义的文件内。
在这里插入图片描述

例3:输出阶乘表

输入正整数n,输出1! ~ n!的值。要求定义并调用含静态变量的函数fact_s(n)计算n!。

#include <stdio.h>

double fact_s(int n);

int main(void)
{
	int i, n;
	
	printf("Input n:");
	scanf("%d", &n);
	for (i=1; i<=n; i++)
	{
		printf("%3d! = %.f\n", i, fact_s(i));	//输出i和i! 
	}
	
	return 0;
}	

double fact_s(int n)
{
	static double f = 1;	//定义静态变量,第一次赋值为1
	
	f = f*n;
	
	return (f); 
}

Input n:7
  1! = 1
  2! = 2
  3! = 6
  4! = 24
  5! = 120
  6! = 720
  7! = 5040

该例程fact_s()函数并没有使用循环语句,它是靠静态变量f保存上次函数调用时,计算得到(n-1)!的值,再乘上n,就得到n!。注意此处的fact_s()属于一种特殊的计算方式,仅靠单次调用是无法得到n!的。

参考 C语言程序设计(第4版)/何钦铭,颜晖
例题及课后习题参考程序https://gitee.com/sgxgitee/mooc-c

;