C语言学习笔记-初阶(8)变量的存储类型

我们把程序操作的数据实体称为对象。按照此定义,变量、 数组、函数等都可以称之为对象。C语言中的对象具有两个属性:数据类型存储类型。 

数据类型:确定了对象在内存中分配存储单元的大小。

存储类型:决定对象在内存中的存储地点,对象的存储地点决定了对象的作用域生命周期

本章主要介绍变量的作用域规则存储属性。 

一、C程序的结构

1.1 模块的概念

C是一种结构化、模块化的程序设计语言!这意味着:一个C语言程序一般由若干个模块组成。那么,什么是模块呢?具体地说,模块是指为完成特定的任务而建立的相对独立的程序单元,是一段连续的、相邻的程序序列。它被边界元素“{ }”限制在一定的范围内,并有一个标识符从整体上代表这些程序语句序列。这个标识符就是此模块的名字,它可以被其它模块中的语句所引用。根据以上概念,在C中,可以被叫作模块的有:①函数。模块名就是函数名,边界元素为“{ }”。②源程序。模块名是源文件名,边界由“同一个源文件”界定。下图显示了一个C语言程序结构,它由多个源程序模块(源文件)组成,一个源程序模块由一个或多个函数模块组成,程序从主函数模块开始执行,主函数模块调用其他函数模块, 其他函数模块之间也可以相互调用,所有调用结束以后最终程序的执行流程要返回主函数模块。所以,一个可执行的C程序必须在某个源程序文件中,有且仅有一个主函数。

C语言程序由函数组成,每个函数中都要使用若干个变量,C程序的规模越大,完成的任务越复杂,组成C程序的函数就越多,涉及的变量就越多。

1.2 模块与信息隐藏

程序设计之所以要模块化就是为了隐藏信息。对一个模块而言,若它能被程序中的其它模块调用,则我们说:对调用它的模块而言,该模块可见。若模块内部的某些信息(函数,变量等)不被隐藏,则这部分就可被其它程序所引用

结构化程序设计要求各个模块中的数据要各自独立。例如,在函数模块之间实现数据传送主要通过“实参一形参”相结合的方式,这样的程序结构清楚,可读性好,且便于程序的移植。

但是在程序设计中,有时为了在模块之间共享数据,我们又希望有其他的数据传送方式,在函数模块之间或多个源文件模块之间实现数据共享。作用域规则访问控制连接属性可将一个模块的信息隐藏或开放,从而在C程序的模块中实现数据共享。

二、作用域与作用域规则

程序中,每个对象的作用域指的是程序正文中该对象可以被使用的那部分区域。例如,C程序中的每个变量对象都要经历分配存储空间→用以存放该变量的值→在程序中使用变量→使用结束后(离开当前模块作用域后)释放所占有的存储空间(该变量无法再被使用)——这一过程。这个过程经历的时间段称为变量的生存(命)(周)期,在变量的生存期内,该变量可以使用的范围称为变量的作用域。在变量作用域内引用变量,称变量在此作用域内"可见"。作用域是一个空间的概念,由定义对象的位置决定,根据对象定义语句位置的不同,作用域分为块作用域函数作用域以及文件作用域

2.1 块作用域 & 局部变量

具有块作用域的对象是定义在一对{ }代码块)之内的。块作用域的范围从程序中对象定义处到块结束处的“}”止。具有块作用域的变量叫局部变量。即局部变量是定义在一对 { } 之内的。局部变量又叫内部变量,意指其起作用的范围限制在{ }之内。内部变量根据其所对应的存储空间(编译系统为其分配的)的划分又分为动态局部变量(又叫:自动变量)静态局部变量(后面会详细讨论数据在内存中存储位置的分类)。

假设一个局部变量定义在第17行,}在第20行,局部变量起作用的范围(能被使用的范围)就在17~20行之间,在这之外使用这个局部变量,系统就会报错。

2.2 函数作用域 & 形参变量

对象的函数作用域是指对象在定义它的整个函数的范围内都有效。C语言中,具有函数作用域的对象有函数的形参变量。函数的形参变量起作用的范围从形参变量的定义开始处到该函数定义的结尾处。形参变量也是内部变量(局部变量),因为其起作用的范围也是限制在一对{ }之内。

2.3 文件作用域 & 全局变量

对象的文件作用域是指从对象的定义处到整个源文件模块结束处。全局变量是指定义在本源文件中所有函数之前(所有函数代码块之外)的变量。其作用域从定义位置开始,直到程序结束。在一个源文件中,函数一经定义后,其作用域从定义位置开始,直到该源文件结束。main函数内不能声明结构体类型、不能定义函数,但是可以声明结构体变量)C语言中,具有文件作用域的对象有全局变量和函数名(函数名就代表函数对象)。

全局变量的使用,需要做如下说明:
① 由于全局变量可以被同一源文件中的所有函数模块使用,因此全局变量提供了函数间除“实参一形参”相结合传送数据之外的另一种数据传送的渠道,实际应用中, 函数调用时通过return语句只能返回一个值,而通过全局变量可以共享多个数据。 

② 由于全局变量在所有函数之前定义,同源文件中的所有函数都可以直接引用, 不需另外说明。 

③ 使用全局变量时,如在一个函数中改变了全局变量的值,将影响到同源文件中的其他函数使用该全局变量。实际应用中,有时在一个函数中不经意地改变全局变量的值,将使整个程序的运行出现错误。 

④ 全局变量使用过多,将降低程序的可读性,不便于分析每个全局变量的瞬时变化情况。另外全局变量的使用增强了函数之间的数据联系,但同时又使得函数过分依赖这些全局变量,降低了函数的独立性,因此从结构化程序设计的角度来讲,要限制全局变量的使用。 

2.4 作用域规则

在一个源文件模块中,块作用域、函数作用域和文件作用域之间的包含关系如图所示:

C语言的作用域需遵循以下规则: 

(1)内层作用域不能延伸到外层。 

(2)在同一源文件中,若内层和外层中声明了同名标识符,则内层的标识符屏蔽外层的同名标识符。 ★★★★★

说明:由于对象的作用域受限于其定义所在的模块内部,在一个模块中定义的内部变量在其它模块中是不可见的。因此,在一个模块内可以为局部变量取任意符合标识符命名规则的名字,而不用担心与其他模块内部使用的变量同名。这个特点便于多人用C语言共同合作开发一个应用程序。使用内部变量很好地实现了不同模块之间的数据隐蔽

(3)在同一源文件中,如果内层想引用外层具有文件作用域的同名标识符(如同名的全局变量),需要使用全局作用域运算符::。 

(4)如果要在一个源文件模块中引用另一个源文件模块中定义的全局变量,可使用关键字extern在使用它的源文件中进行引用性声明。★★★★★

引用性声明不分配存储空间,声明格式为:(例如:extern int x; )

extern typeneme name;

通过此声明,一个文件中定义的全局变量即可在另一个源文件中使用,即关键字extern可以将全局变量的作用域进行延伸。

三、存储属性 & 生命周期(生存期)

3. 1 变量的存储属性

在C语言程序中使用的每个变量都具有两个属性:数据类型存储类型。变量的数据类型确定了变量在内存中分配的存储单元的多少(空间:多大的空间 & 怎么分配这么大的空间),如:char型变量占1个内存单元。 变量的存储类型指数据在内存中的存储方式,它决定了变量所分配的存储区的类型, 而存储区的类型又决定了变量的生存期。所以变量的存储类型决定了变量的生存期。(时间) 由此可见,变量的存储类型是变量在时间方面的属性。 

操作系统将程序运行时使用的存储空间分为程序区数据区。其中:程序区用于存放编译链接后的程序代码,数据区用于存放程序运行过程中涉及的所有数据。在源程序中,变量是用来存放程序要操作的数据的,所以,数据区就是变量在内存中的映像。程序中涉及的所有的变量都是在内存数据区分配存储空间的。数据区又分为:静态数据存储区动态数据存储区

静态存储区在整个程序运行期间都存在,直到程序运行结束才释放所占有的存储空间。静态存储的典型例子是全局变量。全局变量在程序执行过程中, 占有固定的存储单元,其生存期为整个程序运行期。静态存储区中存放了程序的全局数据静态数据

动态存储区在程序运行过程中,根据需要,使用时才分配存储空间,使用结束后立即释放。动态存储的典型的例子是函数的形式参数。函数定义时,形式参数并不分配存储单元,只有在调用时,才给形参分配存储单元,函数调用结束后立即释放形参所占有的存储单元。动态存储区中存放了以下数据:函数调用时的现场保护和返回地址函数的形式参数内部变量中的自动变量。 

C语言中提供了4个关键字用于定义变量的存储类型,他们是:auto、extern、static 和register。分别对应于四种存储类型:

①自动型;②外部型;③静态型;        ——内存中

④寄存器型。                                        ——CPU中

所以,程序中要使用的变量的完整定义格式为:

存储类型 数据类型 变量名(表);

 现在,我们可以根据变量的存储类型,将变量分为四种类型的变量:①自动变量(auto);②外部变量(extern);③静态变量(static);④寄存器变量(register)。

3.2 自动变量

自动变量定义时,前面可以加关键字auto,其格式为: 

auto 类型说明符 变量名(表); 

例如,以下左右两个程序段是完全等价的: 

{                             {
    auto i, j;                    int i, j;
    auto double x, y;             double x, y;
    ……                            ……
}                             }

说明: 
① 关键字 auto 一般可以缺省,一般见到的函数内所定义的变量都是自动变量。 
② 自动变量存放在内存的动态数据区,其作用域范围内的代码一旦执行完,这些变量的存储空间就释放了。 
③ 由于自动变量是内部变量,其作用域仅局限于它定义所在的模块内。 

3.3 外部变量

外部变量定义时,前面可以加关键字extern,其格式为: 

extern 类型说明符 变量名(表); 

说明:
① 外部变量定义在所有函数之外,其作用域从它定义处到源文件结束处,具有文件作用域。 

② 关键字 extern 一般可以缺省,一般函数体外所定义的变量都是外部变量。 

③ 外部变量存放在内存的静态数据区,这些存储空间会一直存在直到整个程序运行结束。 

④ 在一个源文件中,如外部变量要在其定义之前使用,用关键字 extern 在使用它的函数中进行引用性声明。 或者在直接全局进行引用性声明。

显然,全局变量就是外部变量,二者的区别仅在于划分变量类别的角度不一样。实际上,全局变量和外部变量在使用上基本没有差别。所以,并未对二者进行严格的区分。跟前面的全局变量一样,通过使用extern同样可以将外部变量的作用域延伸到另一个源文件。

3.4 静态变量

定义静态变量时,前面必须加关键字static,其格式为: 

static 类型说明符 变量名(表); 

在编译时,系统为静态变量在静态数据存放区中分配的存储单元,该存储单元在程序运行过程中始终存在,直到源程序运行结束,因此静态变量的生存期为整个源程序运行期间。 

根据变量的作用域的不同,静态变量可分为静态局部变量静态全局变量。 

1. 静态局部变量

函数内定义的静态变量为静态局部变量。 

静态局部变量的作用域同自动变量,即为所在的函数,两者区别在于生存期的不同。 函数调用结束后,自动变量的存储空间被释放而静态局部变量的存储空间一直要到源程序运行结束。因此,静态局部变量能保留上次调用时的值。此外,编译程序对静态局部变量仅在编译时初始化一次。 

因此使用static修饰局部变量,能延长它的生存期。

示例:使用静态局部变量计算1~6的阶乘

需要注意的是,在上面的情况下,fact函数能计算出阶乘值,但我们将主函数中循环语句的方向改一下,由从1~6的递增循环,改为从6~1的递减循环,即将for循环语句改为:

for(int i = 6; i >= 1; i--)
{
    printf("%d! = %d\n", i, fact(i));
}

程序就无法输出正确的6!、5!、……等值了。所以这里定义的阶乘函数严重依赖于外部for循环计算的顺序,它的可重用性很差,尽管可以不使用循环,减少了计算的次数,但并不是一个好的程序。

2.静态全局变量

全局变量和静态全局变量的存储方式均为静态存储方式,两者的区别在于作用域扩展上的不同。静态全局变量作用域为其所在的源文件,即只能被该源程序中的函数使用;而全局变量可以通过关键字extern将作用域扩展到其他源程序文件。

在由多个源程序文件组成的C程序中,如要限制全局变量不能在其他源程序文件中使用,可以将其定义为静态全局变量,即使用static修饰全局变量,能将它的作用域限制在本源文件中,所以在其他源文件中就无法引用它了,否则会报出链接错误:error LNK2001:无法解析的外部符号“…”。

好处就是当多人用C语言合作开发一个应用程序的时候,在不同的源程序中可以使用相同的全局变量名★★★★★,而不用担心全局变量同名带来的相互影响。

3.5 寄存器变量

由于CPU在寄存器(在CPU中)的存取速度远高于对内存的存取速度,因此,可以将使用频繁的局部变量定义为寄存器变量,那么,该变量的值就存放在了CPU的寄存器中,以此提高程序的运算效率。定义寄存器变量时,前面加关键字register,其格式为: 

register 数据类型 变量名(表); 

使用寄存器变量,需注意以下事项:

① 只有内部变量和形参变量可以定义为寄存器变量。 

② 函数调用时为定义为寄存器变量的形参变量分配寄存器,调用结束后就释放所分配的寄存器。 

③ 寄存器变量是内部变量,其作用域为块作用域或函数作用域,其生命期为函数的每次调用。 

例如,循环次数i比较大(成千上万次)时,可以使用寄存器变量 i 作循环变量,它的速度能比使用普通变量更快一些,这就是寄存器变量的意义所在,除此之外,它与普通变量是一样的。

四、变量的初始化

4.1 内部变量的初始化(auto)

这里的内部变量指自动变量。自动变量的作用域仅局限于所在的函数,函数调用结束后,自动变量的存储空间被释放。自动变量的初始化在函数调用时进行的,每调用一次函数都将重新进行一次初始化。 

如果定义自动变量时未进行初始化,或自动变量未被赋值,则其值将不确定,这引起一个错误的结果,在程序设计中应注意避免。 

4.2 外部变量的初始化(extern)

外部变量可以被本源程序文件中其他函数使用,其作用域从其定义的位置开始,一直到本源程序结束。外部变量在程序执行过程中,占有固定的存储单元,其生存期为整个程序。

在定义外部变量时,如未进行初始化,则数值型外部变量的值缺省为0,字符型外部变量的值缺省为字符'\0'。

4.3 静态变量的初始化

静态变量,不管是静态全局变量,还是静态局部变量,都是在编译时,系统分配固定的存储单元,并在程序运行过程中始终存在,直到源程序运行结束。

如在定义静态变量时进行初始化,则静态变量只在第一次使用时被赋初值

如未进行初始化,则数值型静态变量的缺省值为0,字符型静态变量的缺省值为字符’\0'。 

4.4 寄存器变量的初始化

寄存器变量作用域为所定义的函数,函数调用结束后,寄存器变量的存储空间被释放。寄存器变量的初始化是在函数调用时进行的,如未进行初始化,其值将不确定,这一点与自动变量相同。 

动态局部变量(自动变量)、静态局部变量、全局变量——作用域、生存期小结:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值