存储类、作用域和编译预处理
存储分类
数据类型确定变量所需存储单元的大小,存储类别确定变量在内存中的存储位置,从而确定其作用域和生存周期。
在C语言中,存储类别可以分为自动类和静态类两种。
局部变量既可以说明成自动类,也可以说明成静态类;全局变量只能是静态类。
与存储类别有关的说明符有:
- auto(自动的)。
- register(寄存器的)。
- static(静态的)。
- extern(外部的)。
内存中的存储映像
局部变量的存储类和作用域
在函数内部或复合语句内部定义的变量,函数的行参也属于局部变量。
auto局部变量
定义局部变量时,若不对其存储类别进行说明,则默认为auto变量,其存储单元将分配在动态存储区内。
auto局部变量的作用域时从定义的位置起,到函数体(或复合语句)结束为止。
register局部变量
register(寄存器)变量也是自动类变量。这种变量的值保留在CPU的寄存器。
程序运行时,访问存于寄存器内的值要比访问内存中的值快得多。
说明:
- 一个计算机系统中的寄存器数目时有限的,因此只能定义少量的寄存器变量。当没有足够的寄存器或编译程序认为该变量不适合放在寄存器中时,将按auto变量来处理。
- 由于register变量的值时放在寄存器内,所以它没有内存地址,也就不能对它进行求地址运算。
- 为了提高寄存器的利用率,register变量应尽量做到即定义即使用,用完就释放。所以,只有局部自动变量和形式参数可以定义为register变量,全局变量和局部静态变量不可以。
static局部变量
说明:
- 静态局部变量的作用域仍是从定义点到函数体的结束。
- 在整个程序的运行期间,静态局部变量在内存的静态存储区中将长期占用存储单元并保留其值(即使函数执行结束),一直到程序运行结束。但是该变量不能被其他函数使用。
- 静态局部变量的赋初值操作是一次性的,是在编译期间赋予的,在程序运行期间不再赋予初值。对于未赋值的静态局部变量,C编译程序自动给它赋初值0。
- 静态局部变量的使用场景①需要保留函数上一次调用结束时的值;②变量初始化后,变量只被引用而不改变其值。
全局变量的存储类和作用域
全局变量时在函数外部任意位置上定义的变量,它的作用域时从定义点到整个源文件结束。
全局变量只有static一种类别,放在静态存储区。
说明:
- 全局变量为函数之间的数据传递提供了一条通道。由于同一文件的所有函数都能引用全局变量的值,因此如果在一个函数中改变了全局变量的值,就能影响到其他函数。
- 全局变量的生存期是整个程序的运行期间。
- 若全局变量与某一函数中的局部变量同名,则在该函数中,全局变量被屏蔽,只会使用局部变量。
static全局变量
当定义全局变量时,若加上static关键字说明,则其作用域仅限于本编译单位(本文件)。
static对局部变量和全局变量的作用效果不同 -
对局部变量:作用域没变,存储类该为静态;
对全局变量:存储类没变,作用域仅限于本文件(小全局)。
全局变量的缺点
- 长期占用存储空间。
- 影响了函数的独立性,不便于移植。
- 难以确定某一时刻的当前值。
全局变量的作用域扩展
- 当全局变量定义在后,引用它的函数在前时,应该在引用它的函数中用extern对此全局变量进行声明,使其作用域从extern声明处起,延伸到函数末尾。也可将extern写在函数之外,使其作用域延伸至文件末尾。
- 全局变量的初始化只在全局变量的定义处进行,而不能在全局变量的声明处进行。
- 当一个程序由多个源文件组成时,每个文件都是独立的编译单位。如果多个文件都要用同一个全局变量,这时若在每一个文件中都定义一个同名的全局变量,则单独编译各个文件时不会产生错误,编译程序将按定义分别为它们分配的存储空间;但当连接时,将产生统一变量的重复定义的错误。
函数的存储类和作用域
外部函数
当定义一个函数时,若在函数返回值的类型前加上extern(或缺省)时,称此函数为外部函数。它可被其他编译单位中的函数调用。
当函数调用语句与被调函数不在同一编译单位,且函数的返回值为非整型时,应该在调用语句所在函数的声明部分用extern对所调用的函数进行声明。
内部函数
当定义一个函数时,若在函数返回值的类型前加上static时,称此函数为静态函数或内部函数,它只能被本编译单位中的其他函数所调用。
使用静态函数,可避免不同的编译单位因为函数同名而引起的混乱。
编译预处理
编译预处理就是在编译前,由编译预处理程序按照编译预处理命令行的指示对源程序进行处理,C有三类预处理:
- 宏定义;
- 文件包含;
- 条件编译;
凡是以#开头的行,都称为编译预处理命令行。
编译预处理命令行可以根据需要出现在程序的任何一行的开始位置,其作用一直持续到文件末尾。
编译预处理命令行不是C语句,行末不需加分号。
宏定义
宏定义只是用宏名代替一个字符串,编译预处理时,只作简单替换,并不作正确性检查。
宏定义一般写在程序的开头,其作用一直延续到文件的末尾。
同一宏名不能被重复定义(除非完全一致)。
替换文本中可以包含已定义过的宏名。
替换文本不能替换双引号中与宏名相同的字符串。
不带参数的宏定义
#define 宏名 [替换文本] /*替换文本可以没有,此时仅说明标识符被定义 */
带参数的宏定义
#define 宏名(行参表) 替换文本
说明:
- 调用带参数的宏名时,形式与函数调用类似。
- 替换文本中的行参和整个表达式应该用括号括起来,以保证经替换后得到预期的结果,不产生歧义。
- 函数调用时,先求出表达式的值,然后代入行参。而使用带参数的宏只是进行简单的字符串替换。
- 函数调用是在程序运行时处理的,占用运行时间。宏替换是在预编译时进行的。
- 函数中的实参和行参都要定义类型,而宏名无类型,它的参数也无类型,只是一个符号代表。
终止宏定义
#undef 宏名
文件包含
#include “文件名” #include <文件名>
说明:
#include行应书写在所有文件的开头,故一般称其为“头文件”。
当包含文件修改后,对包含该文件的源程序必须重新进行编译连接。
一个#include命令只能指定一个被包含文件。
条件编译
一般情况下,源程序中的所有行都参加编译过程。但有时会希望其中一部分在满足一定的条件时才进行编译。
形式一
#ifdef 标识符 /*如果标识符被定义,则编译1,否则编译2 */ 程序段1; #else 程序段2; #endif
形式二
#inndef 标识符 /* 若果标识符没有被定义,则编译1,否则编译2 */ 程序段1; #else 程序段2; #endif
形式三
#if 表达式 /* 当表达式为真时,则编译1,否则编译2 */ 程序段1; #else 程序段2; #endif