C语言入门(十四)变量的作用域和存储类型

变量的作用域和存储类型

一、作用域和生存期

C程序的标识符作用域有三种:局部、全局、文件。标识符的作用域决定了程序中的哪些语句可以使用它,换句话说,就是标识符在程序其他部分的可见性。通常,标识符的作用域都是通过它在程序中的位置隐式说明的。

1.局部作用域

前面各个例子中的变量都是局部作用域,他们都是声明在函数内部,无法被其他函数的代码所访问。函数的形式参数的作用域也是局部的,它们的作用范围仅限于函数内部所用的语句块。

void add(int);

main()
{
int num=5;
add(num);
printf(%d\n,num); /*输出5*/
}
void add(int num)
{
num++;
printf(%d\n,num); /*输出6*/
}

上面例子里的两个num变量都是局部变量,只在本身函数里可见。前面我们说了,在两个函数出现同名的变量不会互相干扰,就是这个道理。所以上面的两个输出,在主函数里仍然是5,在add()函数里输出是6。

2.全局作用域

对于具有全局作用域的变量,我们可以在程序的任何位置访问它们。当一个变量是在所有函数的外部声明,也就是在程序的开头声明,那么这个变量就是全局变量。

void add(int);

int num;
main()
{
int n=5;
add(n);
printf(%d\n,num); /*输出6*/
}
void add(num) /*形式参数没有指定类型*/
{
num++;
printf(%d\n,num); /*输出6*/
}

上面的main()和add()里面,并没有声明num,但是在最后输出的时候却要求输出num,这是由于在程序的开始声明了num是全局变量,也就是在所有函数里都可以使用这个变量。这时候一个函数里改变了变量的值,其他函数里的值也会出现影响。上面的例子输出都是6,因为在add()函数里改变了num的值,由于num是全局变量,就好象它们两个函数共用一个变量,所以在main()函数里的num也随之改变了。

3.文件作用域

在很多C语言书上,都没有说明文件作用域,或者只是略微的提到,其实文件作用域在较大程序中很有作用(在多文件系统中)。文件作用域是指外部标识符仅在声明它的同一个转换单元内的函数汇总可见。所谓转换单元是指定义这些变量和函数的源代码文件(包括任何通过#include指令包含的源代码文件)。static存储类型修饰符指定了变量具有文件作用域。

static int num;
static void add(int);
main()
{
scanf(%d,&num);
add(num)
printf(%d\n,num);
}
void add(num)
{
num++;
}

上面的程序中变量num和函数add()在声明是采用了static存储类型修饰符,这使得它们具有文件作用域,仅爱定义它们的文件内可见。

由于我们提到的大多数程序都只有一个编译文件组成,所以这种写法没有实际意义。但是实际工程上的文件有很多,它们不是由一个人写成的,由很多人共同完成,这些文件都是各自编译的,这难免使得某些人使用了一样的全局变量名,那么为了以后程序中各自的变量和函数不互相干扰,就可以使用static修饰符,这样在连接到同一个程序的其他代码文件而言就是不可见的。

二、变量存储类型

前面我们说了,声明变量时用如下类似的形式:

int num;

float total;

它们都没有存储类型修饰符,我们在声明时也可以通过存储类型修饰符来告诉编译器将要处理什么类型的变量。存储类型有以下四种:自动(auto)、静态(static)、外部(extern)、寄存器(regiser)。

1.自动存储类型

自动存储类型修饰符指定了一个局部变量为自动的,这意味着,每次执行到定义该变量的语句块时,都将会为该变量在内存中产生一个新的拷贝,并对其进行初始化。实际上,如果不特别指明,局部变量的存储类型就默认为自动的,因此,加不加auto都可以。

main()
{
auto int num=5;
printf(%d\n,num);
}

在这个例子中,不论变量num的声明是否包含关键字auto,代码的执行效果都是一样的。函数的形式参数存储类型默认也是自动的。

2.静态存储变量

前面已经使用了static关键字,但是对于局部变量,静态存储类型的意义是不一样的,这时,它是和自动存储类型相对而言的。静态局部变量的作用域仍然近局限于声明它的语句块中,但是在语句块执行期间,变量将始终保持它的值。而且,初始化值只在语句块第一次执行是起作用。在随后的运行过程中,变量将保持语句块上一次执行时的值。看下面两个对应的程序:

/*1.C*/ /*2.C*/

int add(); int add();

main()
{
int result; int result;
result=add() result=add();
printf(%d ,result); printf(%d ,result);
result=add(); result=add();
printf(%d ,result); printf(%d ,result);
result=add(); result=add();
printf(%d,result); printf(%d,result);
} 

int add()
{
int num=50; static int num=50;
num++; num++;
return num; return num;
} 

上面两个源文件,只有函数add()里的变量声明有所不同,一个是自动存储类型,一个是静态存储类型。

对于1.C文件,输出结果为51 51 51;这很好理解,每次初始值都是50,然后加1上来。

对于2.C文件,输出结果为51 52 53;这是由于变量是静态的,只在第一次初始化了50,以后都是使用上次的结果值。当第一次调用add()时,初始化为50,然后加1,输出为51;当第二次调用时,就不初始化了,这时num的值为上次的51,然后加1,输出52;当第三次调用时,num为52,加1就是53了。

比较就会发现它们的不同之处了。静态变量在下一节要说的递归函数中经常使用到。

当第一次不指明静态变量的初始值时,默认为0。

下面举一个例子,把我们说到的静态变量理解一下。

求1+2+……+100的值

void add();
int result;
main()
{
int i;
result=0;
for(i=0;i<100;i++) add();
printf(%d\n,result);
}

void add()
{
static int num=0;
num++;
result+=num;
}

add()函数被调用了100次,num的值从1一直变到100,这样就可以求出它们的和了。如果写成int num=0;那就是求1+1+……+1这100个1的值了。

实际上类似的这类问题我们可以通过递归函数来解决,什么是递归,我们下一节介绍。

3.外部存储类型

外部存储类型声明了程序将要用到的、但尚未定义的外部变量。通常,外部存储类型都是用于声明在另一个转换单元中定义的变量。下面举一个例子,这个例子包括两个文件。

/*1.C*/
void a();
main()
{
extern int num;
a();
printf(%d\n,num);
}

/*2.C*/
int num;
void a()
{
num=5;
}

这两个程序是分别编译的,然后连接成一个执行文件。具体如何操作,可以查看一些手册,这儿我简单说了一下。把上面两个文件都编译好后,再制作一个.prj文件,里面的内容是:

1.c

2.c

只有这两行,这可在编辑状态下写成,存盘,取名为1.prj。

然后选择project选项,选择project name,填入1.prj文件名,按F9后,即可生成1.exe文件。

main()函数中变量num是在另一个文件中定义的。因此,当编译器编译1.c时,无法确定该变量的地址。这时,外部存储类型声明告诉编译器,把所有对num的引用当作暂且无法确定的引用,等到所有便宜好的目标代码连接成一个可执行程序模块时,再来处理对变量num的引用。

外部变量的声明既可以在引用它的函数的内部,也可以在外部。如果变量声明在函数外部,那么同一转换单元内的所有函数都可以使用这个外部变量。反之,如果在函数内部,那么只有这一个函数可以使用该变量。

前面说了文件作用域的问题,如果在声明全局变量时,加上static修饰符,那么该变量只在当前文件内可见,而extern又可以引用其它文件里的变量。所以在一个大型程序中,每个程序员只是完成其中的一小块,为了让自己的变量不让其他程序员使用,保持一定的独立性,经常在全局变量前加static。我们可以这样来说明一下:

还是上面的两个文件,现在再增加一个文件3.c,内容为:

static int num;
void a()
{
num=6;
}

把1.prj文件后面加上3.c 这样,我们生成的1.exe文件,执行时输出是5,而不是6。因为3.c文件的num变量增加了文件作用域,在其他文件中是无法使用它的。

4.寄存器存储类型

被声明为寄存器存储类型的变量,除了程序无法得到其地址外,其余都和自动变量一样。至于什么是变量地址,以后说指针时会详细介绍。

main()
{
register int num;
num=100;
printf(%d,num);
}

使用寄存器存储类型的目的是让程序员指定某个局部变量存放在计算机的某个硬件寄存器里而不是内存中,以提高程序的运行速度。不过,这只是反映了程序员的主观意愿,编译器可以忽略寄存器存储类型修饰符。

寄存器变量的地址是无法取得的,因为绝大多数计算机的硬件寄存器都不占用内存地址。而且,即使编译器忽略寄存器类型修饰符把变量放在可设定地址的内存中,我们也无法取地址的限制仍然存在。

要想有效的利用寄存器存储类型,必须象汇编语言程序员那样了解处理器的内部构造,知道可用于存放变量的寄存器的数量和种类,以及他们是如何工作的。但是,不同计算机在这些细节上未必是一样的,因此对于一个可移植的程序来说,寄存器存储类型的作用不大。特别是现在很多编译器都能提供很好的优化效果,远比程序员来选择有效的多。不过,寄存器存储类型还是可以为优化器提供重要的参考。

时间: 2024-11-05 11:57:24

C语言入门(十四)变量的作用域和存储类型的相关文章

C/C++中变量的作用域和存储类型简介

写在开头 对于很多C/C++的初学者来说,很容易理不清变量的作用域和存储类型这一块的一些概念,也容易将其中的一些概念搞混淆.作为一个C/C++的初学者,笔者希望在这里能够尝试着去理一理这些较为繁杂的概念,主要当作自己备忘之用.当然,由于笔者水平有限,经验不足,在这里也只能罗列一些常用概念和使用方法,且可能会有些许不专业之处,望君理解并指正. 一定要把变量的作用域和存储类型分开来看 很多人很容易把变量的作用于和存储类型搞混,但其实只要我们从它们定义出发来看,这个问题是很容易解决的.作用域是一个变量

[WebGL入门]十四,绘制多边形

注:文章译自http://wgld.org/,原作者杉本雅広(doxas),文章中如果有我的额外说明,我会加上[lufy:],另外,鄙人webgl研究还不够深入,一些专业词语,如果翻译有误,欢迎大家指正. 这是本次的demo的运行结果 绘制流程 这次终于该绘制多边形了,之前的文章(十一,着色器的编译和连接)中介绍了HTML,顶点着色器和片段着色器,这次看一下javascript从开始到最终的全部处理.如果前两篇文章介绍的内容完全理解的话,这次的内容也应该不难了.或许会有不容易理解的地方,不要着急

C语言第十四回合:结构体大集合

C语言第十四回合:结构体大集合 [学习目标] 1.        结构体 2.        结构体数组 3.        结构体指针 结构体:是数据结构类型,把有内在联系的不同类型的数据统一成一个整体,使它们相互关联.是变量的集合,可以单独使用其的成员. A:结构体的定义 使用关键字:struct struct 结构体名 { 类型标识符  成员名1; 类型标识符  成员名2; -- };     //分号一定不能省 PS: (1)结构定义并不预留内存,结构体变量的定义才引起存储分配 (2) 

11-黑马程序员------C 语言学习笔记---C语言的变量的作用域和储存类型

黑马程序员------<a href="http://www.itheima.com" target="blank">Java培训.Android培训.iOS培训..Net培训</a>.期待与您交流! ------- 五 变量的作用域和储存类型 01 变量的作用域是指该变量有效地区域,C语言中得变量分为局部变量和全局变量. 02 局部变量: *局部变量的作用域仅限于定义它的代码块内,离开代码块内便失去作用. *形参只有在函数内有效,其也属于局

C语言第九回合:作用域和存储类型

C语言第九回合:作用域和存储类型  [学习目标] 1.        局部变量 2.        全局变量 3.        存储类型 4.        内存 A: 局部变量 局部变量也称为内部变量.局部变量是在函数内定义说明. 作用域:仅在声明的函数或复合语句内内,有效区也在函数或复合语句内. B: 全局变量 全局变量也称为外部变量,是在函数外定义的变量. 作用域:整个与程序文件 PS: 局部变量和全局变量是按照作用域来划分的. #include <stdio.h> int numub

C变量和函数的存储类型

C变量和函数的存储类型 在上一篇<C程序内存管理>的文章中,已经知道了C语言编译后的可执行文件的存储结构以及运行时的内存布局,本文则记录C语言中变量和函数的存储类型,以及在内存中的一些行为. C语言中变量的声明/定义格式如下: 存储类型    类型修饰符    数据类型    变量名; 存储类型:用来指明变量的存储位置,即运行该变量在哪一段分配内存空间,常见的存储位置有auto.extern.register.static,在一段执行程序中,可以为变量分配存储空间的有BSS.数据区.栈区.堆区

C和指针 3.9作用域、存储类型示例

1 int a = 5; 2 extern int b; 3 static int c; 4 5 int d( int e ) 6 { 7 int f = 15; 8 register int b; 9 static int g = 20; 10 extern int a; 11 ... 12 { 13 int e; 14 int a; 15 extern int h; 16 ... 17 } 18 ... 19 { 20 int x; 21 int e; 22 ... 23 } 24 ...

C语言(四)变量的作用域

一.变量的作用域 C语言根据变量的作用域不同,将变量分为局部变量和全局变量. 1.局部变量 1)定义:在函数内部定义的函数,称为局部变量.形参也属于局部变量. 2)作用域:局部变量只在定义它的函数内部有效,即局部变量只有在定义它的函数内部使用,其它函数不能使用它. 2.全局变量 1)定义:在所有函数外部定义的变量,称为全局变量. 2)作用域:全局变量的作用范围是从定义变量的位置开始到源程序结束,即全局变量可以再其定义位置之后共享. 二.变量的存储类型 变量存储类型就是指变量存储在什么地方. 有三

处女男学Android(十四)---Android 重量级数据存储之SQLite

前言 不知不觉的Android基础系列已经写了十三篇了,这是第十四篇~上一篇blog记录了Android中的一种数据存储方案,即共享参数(Sharedpreferences)的使用(处女男学Android(十三)---Android 轻量级数据存储之SharedPreferences).最近初学如何在Android中应用SQLite,写了一个基于ListView的增删查的小例子,本篇blog就记录一下我学习到的如何在Android中操作SQLite持久化客户端数据. 初始化SQLite 关于SQ