局部变量和全局变量
- 局部变量
函数内部定义的变量,所以不同函数的变量无法相互访问
例:
#include<stdio.h>
int main()
{
int i = 520;
printf("before,i = %d\n",i);
for(int i = 0;i<10;i++)//局部变量
{
printf("%d\n",i);
}
printf("after,i = %d\n",i);
return 0;
}
编译结果:
before,i = 520
0
1
2
3
4
5
6
7
8
9
after,i = 10
这里在main函数内部定义的两个i都是局部变量,for循环中的i只在循环中有定义,出了循环就无定义(这里循环中和循环外用了同一个字母i),循环外在定义的i如果循环内无重新定义的i,那么循环中的i就和循环外一样,这里循环中也定义了i,因此两个i不一样。
值得注意的是在程序书写过程中用到变量再定义的写法,C-99才支持。
- 全局变量
函数外部定义的变量,当需要在多个函数中用到同一个变量时,就可以定义全局变量,方便使用。
例:
#include<stdio.h>
void a();
void b();
void c();
int count = 0;//全局变量count
void a()
{
count++;
}
void b()
{
count++;
}
void c()
{
count++;
}
int main()
{
a();
b();
c();
b();//调用函数a,b,c,d
printf("%d\n",count);
return 0;
}
编译结果:4
注:
(1)如果不对全局变量进行初始化,那么他会自动初始化为0
(2)如果在函数的内部存在一个与全局变量同名的局部变量,编译器并不会报错,而是在函数中屏蔽全局变量(也就是说在函数中,全局变量不起作用)。--跟上述的for语句类似
例:
#include<stdio.h>
void func();
int a;
int b = 520;
void func()
{
int b;
a = 880;
b = 120;
printf("In func: a = %d,b = %d\n ",a,b);
}
int main()
{
printf("In main: a = %d,b = %d\n ",a,b);
func();
printf("In main: a = %d,b = %d\n ",a,b);
return 0;
}
编译结果:
In main: a = 0,b = 520
In func: a = 880,b = 120
In main: a = 880,b = 520
程序先执行main函数,全局变量a初始化为0,全局变量b = 520;
调用func函数,func函数中修改全局变量a = 880,定义局部变量b = 120(和全局变量b重名),故屏蔽全局变量b
最后打印被修改后的全局变量a和未被修改的全局变量b。
- extern关键字
前面讲到用到某一个变量时,不用写在程序开头,可以用到的时候再定义。
那么如果先用变量再定义呢?
例:
#include<stdio.h>
void func();
void func()
{
count++;
}
int count = 0;
int main()
{
func;
printf("%d\n",count);
return 0;
}
编译结果:
[Error] 'count' undeclared (first use in this function)
用extern关键字就可以实现先使用后定义:
extern关键字就相当于告诉编译器:这个变量我定义了,你先别着急报错。
#include<stdio.h>
void func();
void func()
{
extern count;//加入extern关键字
count++;
}
int count = 0;
int main()
{
func;
printf("%d\n",count);
return 0;
}
事实上这是一个技巧,还可以用于多个源文件之间使用同一个变量
- 不要大量的使用全局变量
因为:
(1)使用全局变量会使你的程序占用更多的内存,因为全局变量从被定义时候开始,直到程序退出才被释放。
(2)会污染命名空间,虽然局部变量会屏蔽全局变量,但这样一来也会降低程序的可读性,人们往往很难一下子判断出每个变量的含义和作用范围。
(3)提高了程序的耦合性,牵一发而动全身,时间久了,代码长了,你都不知道全局变量被那些函数修改过。
记住:在模块化程序设计的指导下,我们应该尽可能的设计那些内聚性强,耦合性弱的模块即要求函数的功能尽可能的单一,与其他函数的关系尽可能的少。
作用域和链接属性
- 作用域
当变量被定义在程序的不同位置时,它的作用范围是不一样的,这个作用范围就是我们所说的作用域。
C语言编译器可以确认4中不同类型的作用域:
(1)--代码块作用域
所谓代码块就是位于一对花括号之间的所有语句。
在代码块中定义的变量,具有代码块作用域。作用范围是从变量定义的位置开始,到标志该代码块结束的右大括号处。
另外尽管函数的形参不在大括号内定义,但其同样具有代码块作用域,隶属于包含函数体的代码块
#include<stdio.h>
int main(void)
{
int i = 100;//i1
{
int i = 120;//i2
{
int i = 240;//i4
printf("i4 = %d\n",i);
}
//i = 120
{
printf("i = %d\n",i);
int i = 360;//i3
printf("i3 = %d\n",i);
}
printf("i2 = %d\n",i);
}
printf("i1 = %d\n",i);
return 0;
}
编译结果:
i4 = 240
i = 120
i3 = 360
i2 = 120
i1 = 100
注:如果函数没有参数的话,那么将int main()写成int main(void),表示不需要任何参数,当调用这个函数时,如果不小心传入了参数,编译器就会帮忙检查
(2)--文件作用域
任何在代码块之外声明的标识符都具有文件作用域,作用范围是从他们的声明位置开始,到文件的结尾处都是可以访问的。
函数名也具有文件作用域,因为函数名本身也是在代码块之外,还有全局变量等。
例:
#include<stdio.h>
void func(void);
int main(void)
{
extern count;
func();
count++;
printf("in main:count = %d\n",count);
return 0;
}
int count;//默认初始化为0
void func(void)
{
count++;
printf("in func:count = %d\n",count);
}
编译结果:
in func:count = 1
in main:count = 2
在这个程序中count、func、和main都是属于文件作用域
(3)--原型作用域
原型作用域只适用于那些在函数原型中声明的参数名。函数在声明的时候可以不用写参数的名字(但参数类型是必须要写上的),其实函数原型的参数名还可以随便写一个名字,不必与形参相匹配(当然这样做没有任何意义!)。
例:void func(int a,int b,int c)
void func(int d,int e,int f)
{
......
}
(4)--函数作用域
函数作用域只适用于goto语句的标签,作用将goto语句的标签限制在同一个函数内部,以及防止出现重名标签。
- 定义和声明
定义:当一个变量被定义的时候,编译器为变量申请内存空间并填充一些值
声明:当一个变量被声明的时候,编译器就知道该变量被定义在其他地方
(声明是通知编译器该变量名及相关的类型已存在,不需要再为此申请内存空间。)
注:局部变量既是定义又是声明;
定义只能来一次,否则就叫重复定义某个同名变量;而声明可以有很多次。
- 链接属性
简单的来说编译器将源文件变成可执行文件总共需要两个步骤,即编译和链接。
凡是大型的程序,都有好些个源文件构成,不同文件中的同名标志符,编译器是如何处理的,就要看链接属性。
C语言中链接属性共有三种:
external(外部的)
——多个文件中声明的同名标识符表示同一个实体
internal(内部的)
——单个文件中声明的同名标识符表示同一个实体
none(无)
——声明的同名标识符被当做独立不同的个体
注:
(1)只有具备文件作用域的标识符(如函数名、全局变量)才能拥有external或internal的链接属性,其他作用域的标识符都是none属性。
(2)默认情况下,具备文件作用域的标识符拥有external属性。也就是说该标识符允许跨文件访问。对于external属性的标识符,无论在不同文件中声明多少次,表示的都是同一个实体。
(3)使用static关键字可以使得原先拥有external属性的标识符变为internal属性。这里有两点需要注意:
——使用static关键字修改链接属性,只对具有文件作用域的标识符生效(对于拥有其他作用域的标识符是另一种功能)
——链接属性只能修改一次,也就是说一旦将标识符的链接属性变为internal,就无法变回external了
如果想限制一个函数只能在某个文件中被调用或者保护一个全局变量不被其他函数随意的访问修改时,那么也可以在函数名或变量名的前面加上static关键字
生存期和存储类型
前面已经从空间的角度了解了变量的作用域和链接属性,那么从时间角度来分析,就谈到了变量的生存期和存储类型。
- 生存期
C语言的变量拥有两种生存期:
——静态存储期(static storage duration)
——自动存储期(automatic storage duration)
区别:
具有文件作用域的变量属于静态存储期。属于静态存储期的变量在程序执行期间将一直占据存储空间,直到程序关闭才释放。(如全局变量和函数名)
具有代码块作用域的变量一般情况下属于自动存储期。属于自动存储期的变量在代码块结束时将自动释放存储空间。(如局部变量和函数的形式参数)
例:
#include<stdio.h>
int A;
static int B;//将具有文件属性的全局变量B的链接属性由external改为internal
extern int C;//声明变量C在其他位置定义过,去找找吧,不要报错
void func(int m,int n)
{
int a,b,c;
}
int main(void)
{
int i,j,k;
return 0;
}
上面程序中的全局变量B、C函数func、main都是具有静态存储期的,而形式参数m、n和局部变量a、b、c、i、j、k都是具有自动存储期的。
- 存储类型
变量的存储类型其实是指存储变量值的内存类型,C语言提供了5中不同的存储类型:
——auto
——register
——static
——extern
——typedef
(1)自动变量auto:
在代码块中声明的变量默认的存储类型就是自动变量,使用关键字auto来描述。
(因此函数中的形式参数,局部变量包括复合语句中定义的局部变量都是自动变量,自动变量拥有代码块作用域,自动存储期以及空链接属性)
#include<stdio.h>
int main()
{
auto int i,j,k;
return0;
}
这里i、j、k是局部变量,事实上是要用关键字auto来描述的,由于这是默认的存储类型,所以不写auto是完全没问题的。
那什么时候写上auto比较好呢?
比如说如果想强调局部变量屏蔽同名的全局变量时,在代码量比较多且同名的全局变量和局部变量距离比较远时加上 auto就更容易分的清楚。
(2)寄存器变量register:
首先要知道:寄存器存在于CPU的内部,CPU对寄存器的读取和存储没有任何延迟。
- 将一个变量声明为寄存器变量,那么该变量就有可能被存放于CPU的寄存器中。
(因为CPU的寄存器空间是十分有限的,所以编译器不会将所有声明为register的变量存放在寄存器中;事实上有可能所有的register的关键字都被忽略,因为编译器有自己的一套优化方案,而那些被忽略的register的变量他们会变成普通的自动变量)
因此
- 寄存器变量和自动变量在很多方面是一样的,他们拥有代码块作用域,自动存储期和空链接属性。
注意:当你将变量声明为寄存器变量,那么你就没办法通过取址运算符获得该变量的地址。
#include<stdio.h>
int main()
{
register int i = 520;
printf("Address of i = %p\n",&i);
return 0;
}
编译结果:
[Error] address of register variable 'i' requested
(3)静态局部变量(static)
前面已经讲到关键字static可以使得默认情况下文件作用域的标识符的external属性变为internal属性,作用范围也由多文件共享变为单文件独享。
那么如果将static用于描述局部变量的话:
- 使用static来声明局部变量,那么就可以将局部变量指定为静态局部变量。(默认情况下局部变量是auto即自动变量)
- static使得局部变量具有静态存储期,所以它的生存期与全局变量一样,直到程序结束才释放。
例:
#include<stdio.h>
void func(void)
{
static int count = 0;
printf("count = %d\n",count);
count++;
}
int main(void)
{
int i;
for(i = 0;i<10;i++)
{
func();
}
return 0;
}
编译结果:
count = 0
count = 1
count = 2
count = 3
count = 4
count = 5
count = 6
count = 7
count = 8
count = 9
这里可以看到每次调用func函数后变量count并不会销毁,而是在之前的基础上加1,因为他是静态局部变量,生存期已经发生改变,与全局变量一样,但是需要注意的是作用域并没有发生改变,仍然是仅限于func函数内部的作用域。
static和extern:
作用于文件作用域的static和extern,static关键字使得默认具有external链接属性的标识符变成internal链接属性,而extern关键字是用于告诉编译器这个变量或函数在别的地方定义了,先去别的地方找一找,不要急着报错。(但事实上不加extern编译器也不会报错)
例:
新建一个项目,包含两个.c文件
#include<stdio.h>
int count;//声明变量count,但未加关键字extern
void func(void)
{
printf("count = %d\n",count);
}
#include<stdio.h>
void func();
int count = 520;
int main()
{
func();
return 0;
}
编译结果:
count = 520
声明全局变量count,编译器寻找变量定义位置,执行main函数...这里并不是count默认初始化为0,
所以加上extern的话就会使代码更加清晰易懂(跟前面的auto有些类似),所以最好加上extern。
所以:
#include<stdio.h>
extern int count;
void func(void)
{
printf("count = %d\n",count);
}
#include<stdio.h>
extern void func();
int count = 520;
int main()
{
func();
return 0;
}
编译结果:
count = 520
总的来说使用auto和register关键字声明的变量具有自动存储期,使用static和extern关键字声明的变量具有静态存储期。