C提供了多种不同的模型或存储类别在内存中存储数据。从硬件方面来看,被存储的每个值都占有一定的物理内存,C语言把这样的一块内存称为对象。对象可以存储一个或多个值。一个对象可能并未存储实际的值,但是它在存储适当的值时一定具有相应的大小(面向对象编程中的对象指的是类对象,其定义包括数据和允许对数据进行的操作,C不是面向对象编程语言)。
从软件方面看,程序需要一种方法访问对象。这可以通过声明变量来完成:
int a = 3;
该声明创建了一个名为a的标识符。标识符可以用来指定对象的内容。在该例中,标识符a即是软件(即C程序)指定硬件内存中的对象的方式。
存储类别有:自动、寄存器、静态块作用域、静态外部链接、静态内部链接、动态分配存储
作用域描述程序中可访问标识符的区域。一个C变量的作用域可以是块作用域、函数作用域、函数原型作用域或文件作用域。
块是用一对花括号括起来的代码区域。例如,整个函数体是一个块,函数中的任意复合语句也是一个块。
定义在块中的变量具有块作用域,块作用域变量的可见范围是从定义处到包含该定义的块的末尾。另外,虽然函数的形式参数声明在函数的左花括号之前,但是它们也具有块作用域,属于函数体这个块。
int name(int a)
{
int b=1;
return b;
}
这里a和b都具有块作用域。声明在内层块中的变量,其作用域仅局限于该声明所在的块:
int name(int a)
{
int b=1;
int i=0;
for(i=0;i<10;i++)
{
int c = a*i;
b *= c;
}
return b;
}
在该例中,c的作用域仅限于内层快,只有内层快中的代码casino能访问c。
函数作用域是指在函数内部定义的变量只能在函数内部访问,而在函数外部无法访问这些变量。这是因为函数内部的变量具有局部作用域,只在函数内部有效。如果想在函数外部使用函数内部的值,可以通过函数的返回值return来获取。
以下是一个示例,演示了函数作用域的概念:
def my_function():
x = 10 # 在函数内部定义的变量x
print("Inside the function:", x)
my_function() # 调用函数,输出:Inside the function: 10
print("Outside the function:", x) # 在函数外部访问变量x,会报错
在上面的示例中,变量x是在函数内部定义的,所以只能在函数内部访问。在函数外部访问变量x会导致NameError错误。
函数原型作用域用于函数原型中的形参名(变量名),如下所示:
int might(int mouse, int large);
函数原型作用域的范围是从形参定义处到原型声明结束。这意味着,编译器在处理函数原型中的形参时只关心它的类型,而形参名(如果有的话)通常无关紧要。而且,即使有形参名,也不必与函数定义中的形参名像匹配。
变量的定义在函数的外面,具有文件作用域。具有文件作用域的变量,从它的定义处到该定义所在的文件的末尾均可见。文件作用域变量也称为全局变量。
C变量有3种链接属性:外部链接、内部链接或无连接。
具有块作用域、函数作用域或函数原型作用域的变脸都是无连接变量,这意味着这些变量属于定义它们的块、函数或原型私有。
具有文件作用域的变量可以时外部链接。带有存储类别说明符static的是内部链接,无static或有exten的是外部链接。
C对象有4种存储期:静态存储期、线程存储期、自动存储期、动态分配存储期。
所有的文件作用域变量都具有静态存储期。如果对象具有静态存储期,那么它在程序的执行期间一直存在。
线程存储期用于并发程序设计,程序执行可被分为多个线程。具有线程存储期的对象,从被声明时到线程结束一直存在。以关键字_thread_local 声明一个对象时,每个线程都获得该变量的私有备份。
块作用域的变量通常都具有自动存储期。(进入就分配内存,离开就释放内存)例如,在下面的代码中,变量number和index在每次调用bore()函数时被创建,在离开函数时被销毁:
void bore(int number)
{
int index;
for(index=0;index<number;index++)
printf("*");
return 0;
}
然而,块作用域变量也能具有静态存储期。为了创建这样的变量,要把变量声明在块中,且在声明前面加上关键字static:
void more(int number)
{
int index;
static int ct =0;
//...
return 0;
}
这里,变量ct存储在静态内存中,它从程序被载入到程序结束期间都存在。但是,它的作用域定义在more()函数块中。只有在执行该函数时,程序才能使用ct访问它所指定的对象(但是,该函数可以给其他函数提供该存储区的地址以便间接访问对象,例如通过指针形参或返回值)。
存储类别 | 存储期 | 作用域 | 链接 | 声明方式 |
自动 | 自动 | 块 | 无???????? | 块内 |
寄存器 | 自动 | 块 | 无 | 块内,使用关键字register |
静态外部链接 | 静态 | 文件 | 外部 | 所有函数外 |
静态内部链接 | 静态 | 文件 | 内部 | 所有函数外,使用关键字static |
静态无链接 | 静态 | 块 | 无 | 块内,使用关键字static |
属于自动存储类别的变量具有自动存储期、块作用域且无链接。默认情况下,声明在块或函数头中的任何变量都属于自动存储类别。
关键字auto是存储类说明符。但是auto关键字在C++中的用法完全不同,如果编写C/C++兼容的程序,最好不要使用auto作为存储类别说明符。
块作用域和无链接意味着只有在变量定义所在块中才能通过变量名访问该变量。另一个函数可以使用同名变量,但是该变量是存储在不同内存位置上的另一个变量。
变量具有自动存储期意味着,程序在进入该变量声明所在的块时变量存在,程序在退出该块时变量消失。原来该变量占用的内存位置现在可做他用。
//嵌套快
int loop(int n)
{
int m; //m的作用域
scanf("%d",&m);
{
int i; //m和i的作用域
for(i=m;i<n;i++)
puts("abc\n");
}
return m; //m的作用域,i已经消失
}
如果內层块中声明的变量语外层块中发变量同名会怎样?内层块会隐藏外层块的定义。但是离开内层块后,外层块变量的作用域又回到了原来的作用域。
自动变量不会初始化,除非显示初始化它。如果不初始化自动变量,那么该变量的值是之前占用分配给变量的空间中的任意值(如果有的话),别指望这个值是0。
变量通常存储在计算机内存中i。寄存器变量存储在CPU的寄存器中,或者概括地说,存储在最快的可用内存中。与普通变量相比,访问和处理这些变量的速度更快。由于寄存器变量存储在寄存器而非内存中,所以无法获取寄存器变量的地址。绝大多数方面,寄存器和自动变量都一样,也就是说,它们都是块作用域、无链接和自动存储期。存储类别说明符 register可声明寄存器变量。
静态变量:静态的意思是该变量在内存中原地不动,但值可能会变。
外部链接的静态变量具有文件作用域、外部链接和静态存储期,属于该类别的变量被称为外部变量。把变量的定义性声明放在所有函数的外面便创建了外部变量。当然,为了指出该函数使用了外部变量,可以在函数中使用关键字extern再次声明。
如果位初始化外部变量,它们会被自动初始化为0。与自动变量的情况不同,只能使用常量表达式初始化文件作用域变量,不能用含有变量的表达式。
外部变量在该文件中的所有函数都可以使用。
#include <stdio.h>
int tern=1;
int main()
{
extern int tern;
return tern;
}
第1次声明被称为定义式声明,第2次声明被称为引用式声明。关键字extern表明该声明不是定义,因为它指示编译器去别处查询其定义。
内部链接的静态变量具有静态存储期、文件作用域和内部链接。用存储类别说明符static定义的变量具有这种存储类别。
static int a=1; //静态变量,内部链接
注意,如果外部变量定义在一个文件中,那么其他文件在使用该变量之前必须吓死你声明它(用extern关键字)。也就是说,在某文件中对外部变量进行定义式声明只是单方面允许其他文件使用该变量,其他文件在用extern声明之前不能直接使用它。
C语言有6个关键字作为存储类别说明符:auto、register、static、extern、_Thread_local和typedef。typedef关键字与任何内存存储无关,把它归于此类有一些语法上的原因。
auto说说明符表明变量是自动存储期,只能用于块作用域的变量声明中。由于在块中声明的变量本身就具有自动存储期,所以使用auto主要是为了明确表达要使用与外部变量同名的局部变量的意图。
register说明符也只用于块作用域的变量,它把变量归为寄存器存储类别,请求最快速度访问该变量。同时,还保护了该变量的地址不被获取。
用static说明符创建的对象具有静态存储期,载入程序时创建对象,程序结束时对象消失。可用于块作用域和文件作用域。块作用域的静态变量无链接。文件作用域的静态变量具有内部链接。
extern说明符表明声明的变量在别处。如果包含extern的声明具有文件作用域,则引用的变量必须具有外部链接。如果包含extern的声明具有苦熬作用域,则引用的变量可能具有外部链接或内部链接,这取决于该变量的定义式声明。
函数也有存储类别,可以是外部函数(默认)或静态函数。外部函数可以被其他文件的函数访问,但是静态函数只能用于其定义所在的文件。
double game(double ); //该函数默认为外部函数
static double beta(int ,int );
extern double delta(double,int );
在同一个程序中,其他文件的函数可以调用game()和delta(),但是不能调用beta(),因为以static存储类别说明符创建的函数属于特定模块私有。