时间:2021-11-28 11:31:06 | 栏目:C代码 | 点击:次
在c中变量有三种性质:
1、存储期限:变量的存储期限决定了变量占用的内存空间什么时候会被释放,具有动态存储期限的变量会在所属的程序块被执行时获得内存空间,在结束时释放内存空间。具有静态存储期限的变量在程序运行的整个期间都会占用内存空间。
2、作用域:变量有块作用域也有文件作用域,结合序章第一张图可以明白块作用域是在某些程序块内起作用,文件作用域是在整个c文件之内起作用。
3、链接:链接是各个文件之间的关系,具有内部链接的变量只在本文件内起作用,具有外部链接的变量可以在不同文件内起作用。具有无链接的变量只在某一个文件的某个函数内起作用。
错误的定义C语言变量的作用域、链接和存储区在某种程度上会直接影响程序的设计,我们通过这篇博文介绍如何正确合理的定义一个C变量。
从定义一个C变量开始成为一个C高手
C语言中的对象含义与C++的完全不同,C语言中的对象指的是存储数据的一块内存。对象可以存储一个或多个值,一个对象可能并未存储实际的值,但是在存储适当的值时一定具有相应的大小。对象可以存在于程序的执行期,也可以仅存在于它所在函数的执行期。
标识符就是一个名称,通过这个标识符可以修改对象的内容。标识符可以在源代码的多文件中共享、可用于特定文件的任意函数中、可仅限于特定函数中使用,甚至只在函数中的某部分使用。
举个例子,我们定义一个int型变量point:
int point;
对象与标识符的关系如下图
存储期用来描述对象,所谓的存储期就是指对象在内存中可以保留多长时间。
标识符用于访问对象,使用作用域(scope)和链接(linkage)描述标识符,标识符的作用域和链接表明了程序的哪些部分可以使用它。
作用域描述程序中可访问标识符的区域
一个C变量的作用域可以是块作用域、函数作用域、函数原型作用域和文件作用域,下面我们详细讲解每个作用域的含义。
块是用一对花括号括起来的代码区域,定义在块中的变量具有块作用域,块作用域变量的可见范围是从定义处到包含该定义的块的末尾。
另外,虽然函数的形式参数声明在函数的左花括号之前,但它们也具有块作用域,属于函数体这个块。
int Star_CongShanRuoShui(int user_id) { int res = 0; ...... return }
👆上面code中“user_id”和“res”都具有块作用域
int Star_CongShanRuoShui(int user_id[] , int n) { int res = 0; for(int i=0;i<n;++i) { ...... int q=0; //q的作用域开始 ...... } //q的作用域结束 ...... return }
👆上面code中变量“q”的作用域仅限于for循环的循环体中
3.1.1 块概念的扩展
C99将块概念扩展到包括for循环、while循环、do while循环和if语句所控制的代码,即使这些代码没有使用花括号括起来,也算是块的一部分,下面的code👇
#include<stdio.h> int main() { int Star_CSRS = 8; printf("[main] value of Star_CSRS is %d | address of Star_CSRS:%p\n", Star_CSRS ,&Star_CSRS); for(int Star_CSRS=0;Star_CSRS<4;++Star_CSRS) { printf("[for index] value of Star_CSRS is %d | address of Star_CSRS:%p\n", Star_CSRS ,&Star_CSRS); int Star_CSRS = 6; printf("[for] value of Star_CSRS is %d | address of Star_CSRS:%p\n", Star_CSRS ,&Star_CSRS); ++Star_CSRS; } printf("[main] value of Star_CSRS is %d | address of Star_CSRS:%p\n", Star_CSRS ,&Star_CSRS); return 0; }
输出如下:
函数作用域的概念仅限于goto语句的标签。这意味着即使一个标签首次出现在函数的内层块中,它的作用域也延伸至整个函数中(详细可以参考我的这篇博文)。
用于函数原型的形参变量名,如下所示:
int Star_CongShanRuoShui(int user_id);
函数原型作用域的范围是从形参定义处到原型声明结束。对于函数原型中的形参编译器只关心形参类型,不关系具体的形参名,即使有形参名也不必与函数定义中的形参名相匹配。
只有在变长数组中,形参名才有意义,如下例(变长数组详细参考我的这篇博文):
int Star_CongShanRuoShui(int n ,int user_id[n]);
3.4 文件作用域
定义在所有函数外的变量具有文件作用域。具有文件作用域的变量,从它的定义处到该定义所在文件的末尾均可见(文件作用域变量也称为全局变量)。
翻译单元与文件
编译器将一个源代码文件和所有的头文件都看成一个包含信息的单独文件,这个文件被称为翻译单元一个翻译单元包括一个源代码文件和它所include的文件如果一个程序有多个源代码文件,那么这个程序也将有多个翻译单元一个文件作用域变量的可见范围其实是整个翻译单元(一个源代码文件+头文件)
C变量有3中链接属性:无链接、内部链接、外部链接
具有块作用域、函数作用域和函数原型作用域的变量都是无链接变量。这意味着这些变量属于定义它们的块、函数或原型私有。
内部链接变量只能在一个翻译单元中使用,该变量使用存储类别说明符static修饰,如下:
int Star_CSDN=1; //文件作用域,外部链接 static int Star_CongShanRuoShui=2; //文件作用域,内部链接 int main() { ...... return 0 }
外部链接变量可以在多个文件中使用。外部链接变量的声明分为“定义性声明”和“引用性声明”。C编译器要求一个变量只能定义一次,重复定义编译器会报错。如果需要在其它文件中使用外部链接变量需要使用extern引用性声明这个变量,如下面cdoe:
//file a.c //在文件a.c中定义一个外部链接变量Star_CongShanRuoShui int Star_CongShanRuoShui = 2; ......
//file b.c //文件b.c中使用extern引用性声明变量Star_CongShanRuoShui extern int Star_CongShanRuoShui; ......
C对象有4种存储期:静态存储期、线程存储期、自动存储期、动态分配存储期。
具有静态存储期的对象,它在程序的执行期间一直存在。文件作用域变量具有静态存储期
注意,对于文件作用域变量,关键字static表明了其链接属性,而非存储期。以static声明的文件作用域变量具有内部链接属性,但是无论是内部链接还是外部链接,所有文件作用域变量都具有静态存储期。
线程存储期用于并发程序设计,程序执行可被分为多个线程。具有线程存储期的对象,从被声明时到线程结束一直存在。以关键字__Thread_local声明一个对象时,每个线程都获得这个变量的私有备份。
块作用域的变量通常都具有自动存储期。当程序进入定义这些变量的块时,为这些变量分配内存;当退出这个块时,释放刚才为这些变量分配的内存。通过这种做法可以实现内存的重复利用。
变长数组稍有例外,它的存储期从声明处到块的末尾,而不是从块的开始处到块的末尾
我们上面说块作用域的变量通常都具有自动存储期,但是也能具有静态存储期。为了创建这样的变量,要把变量声明在块中,且在声明前加上关键字static,如下例:
int Star_CongShanRuoShui(int user_id) { static int isStar = 0; ...... return }
变量isStar存储在静态内存中,它从程序被载入到程序结束期间都存在。但是,它的作用域定义在Star_CongShanRuoShui()函数块中,只有在执行该函数时,程序才能使用isStar访问它所指定的对象(当然,也可以存储该变量的地址实现间接访问该对象)
程序运行时通过malloc()等内存分配函数分配的对象具有动态分配存储期,这样的对象需要使用free()函数进行销毁。
动态内存分配和变长数组在功能上有些重合,但是还是有所不同:
变长数组是自动存储类型用malloc函数创建的数组不必局限在一个函数中使用