计算机二级第十三讲
第九章 编译预处理
内容:
§8.9 变量的存储类别 §8.10 内部函数和外部函数 §9.1 宏定义 §9.2 文件包含
§8.9 动态存储变量和静态存储变量
一、内存中用户区存储空间的分配:
二、变量的存储类别 1. 按照变量的存在时间:
(1)动态存储:调用函数时分配存储单元,函数调用结束时释放所占存储单元。即离开函数后变量的值不能被引用。下列变量属于动态存储:自动变量、形式参数、寄存器变量。
(2)静态存储:定义变量时分配存储单元,函数调用结束时并不释放所占存储单元。离开函数后变量的值仍能被引用。下列变量属于静态存储:静态局部变
量、静态外部变量、外部变量。
(3)寄存器变量:如果某个变量在程序运行中使用频繁,为提高效率,C语言允许将少数几个变量放在运算器中的寄存器中,叫做寄存器变量。 2. 按照变量值存放的位置:
(1)内存中静态存储区:存放外部变量、静态局部变量。 (2)内存中动态存储区:存放自动变量、形参变量。 (3)CPU中的寄存器:存放寄存器变量。 3. 存储类别小结:
(1)自动变量(auto): 定义在函数内部或者作为形参,存放在内存中的动态存储区,生存期随函数调用的结束而结束,作用域在函数内部。
(2)静态局部变量(static):定义在函数内部,存放在内存中的静态存储区,生存期等于整个程序的执行周期,作用域在函数内部。
(3)静态外部变量(static):定义在函数外部,存放在内存中的静态存储区,生存期等于整个程序的执行周期。作用域为本源程序文件。
(4)外部变量:定义在函数外部,存放在内存中的静态存储区,生存期等于整个程序的执行周期。作用域为本源程序文件和其它程序文件。
(5)寄存器变量:定义在函数内部或作为形参,存放在运算器中的寄存器中,生存期随函数,作用域在函数内部或一个复合语句内部。 三、局部变量的存储方式 1. 函数中的局部变量的存储方式
函数中的局部变量的存储方式有3种:静态的、动态的和寄存器的: (1)自动变量:当存储类别为auto或存储类别省略时,系统为这些变量在动态存储区分配存储单元,叫做自动变量。
(2)静态局部变量:存储类别为static的局部变量,其存储区域为内存中的静态存储区,这类变量叫做静态局部变量。
(3)寄存器变量:变量的数据存储在CPU中的寄存器中。 2. 函数中局部变量的说明:
(1)静态局部变量是在编译时赋初值的,而且只赋初值一次,以后每次调用函
数时不再重新赋初值,而只是保留上次函数调用结束时的值.自动变量不是在编译时赋初值的,而是在每一次调用函数时重新使给各个变量赋一次初值。 (2)如果局部变量在定义时不赋初值,则对于静态局部变量而言,在编译时自动赋初值0或空字符,而对于自动变量而言,其值则是随机的。
(3)尽管局部静态变量在函数调用结束后仍然存在,但其他函数不能引用它。 (4)只有局部自动变量和形式参数才能作为寄存器变量,全局变量和静态局部变量都不能作为寄存器变量。
(5)形式参数不能定义为静态局部变量。
例1 (教材P173例8.17) 局部静态变量的一个实例。 程序如下: f(a) int a;
{auto int b=0; /* 自动变量b,每次调用时都赋初值0 */ static int c=3; /* c为静态局部变量,只在编译时赋初值3 */ /* 第2次以后再调f函数时,c中保留上次调用后的值 */
/* 即第1次调用时c的值为3;第2次调用时c的值为4;第3次调用时c的值为5 */
b=b+1; c=c+1; return a+b+c; } main() {int a=2,i; for(i=0;i<3;i++)
printf(\"%d \/* 在循环中三次调用函数f,实参均为a */ }
程序运行结果为: 7 8 9
在函数中,如果有静态局部变量而且又2次以上调用该函数,一定要注意静态局部变量与自动变量在赋初值上的区别,以免出错。
想一想,如果将f函数中的变量b改为自动变量,程序运行的结果是什么?
下面一个程序也是静态局部变量的使用程序,请分析该程序输出的结果是什么? fun() {int a=2; static int b=4; a=a+2; b+=2;
printf(\"a=%d b=%d\\n\} main() {fun(); fun(); fun(); }
四、全局变量的存储方式
1. 存储区域: 全局变量在编译时被分配在静态存储区。 2. 注意:
(1)静态外部变量:又叫作函数外部静态变量,定义在函数外部,只能被本文件中的函数所引用,不能被其它文件中的函数引用,其定义格式为: static 类型 全局变量名;
(2)外部变量:定义在函数外部,既能被本文件中的函数引用又能被其它文件中的函数引用,全局变量在定义时如果省略了存储类别说明,则为外部变量。
定义格式为: 类型 外部变量名;
其它文件在引用外部变量时,要在文件开头对这些外部变量进行说明,说明格式为:
extern 类型 外部变量名;
(3)如果外部变量定义在引用它的函数之后,则需要在引用前加以说明,说明格式为:
extern 类型 外部变量名;
也就是说,extern 类型 变量名; 这种用法,如果用在函数内部,是对一个在本源程序文件中在后面定义的一个外部变量进行说明(如§7.8例1);如果用在函数外部,是对一个在其它源程序文件中定义的一个外部变量进行说明(如§7.8例2)。
例2 (教材P例8.21) 在其它文件中使用外部变量。
程序的功能是: 给定b的值,输入a和m,求a*b和a^m的值,main函数在文件file1.c中,外部变量a在文件file1.c中定义;函数power的功能是求形参n的阶乘,在文件file2.c中定义,在file2.c中引用了在file1.c中定义的外部变量a。
程序如下: file1.c中的内容:
#include \"file2.c\" /* 此处必须包含文件file2.c使之成为该源程序文件的一部分 */ int a; /* 定义可以由其它程序文件使用的外部变量a */ main()
{int power(); /* 对调用函数进行说明 */ int b=3,c,d,m;/* 给定b的值 */
printf(\"enter the number a and its power:\\n\"); scanf(\"%d,%d\/* 输入a,m的值 */ c=a*b; /* 求a*b的值 */
printf(\"%d*%d=%d\\n\/* 输出a*b的值 */ d=power(m); /* 调用文件file2.c中的函数power求a^m的值 */ printf(\"%d^%d=%d\\n),a,m,d); /* 输出a^m的值 */ }
file2.c中的内容:
extern int a; /* 对于不在本文件中定义的外部变量进行说明 */ power(n) /* 定义函数power求a^n */ int n;
{int i,y=1; /* 变量y中存放连乘积,一定要赋初值1 */ for(i=1;i<=n;i++) y*=a; return y; }
程序运行时,从键盘上输入: 2,5 想一想,文件file1.c中对定义在其它文件中的函数的说明语句 int power(); 可以省略吗? 五、变量的存储类别小结 名称 定义方法 int a auto int a; 定义位置 存放位置 生存期 作用域 自动变量 函数内或复合语句内或形参 动态存储区 函数 函数内或复合语句内 静态局部变量 static int a; 外部变量 int a; 函数内或复合语句内 函数外 函数外 静态存储区 本程序 静态存储区 本程序 静态存储区 本程序 函数内或复合语句内 本程序或其它程序 本程序内 函数内或复合语句内 静态外部变量 static int a; 寄存器变量 regeiter int a; 函数内或复合语句内或形参 CPU寄存器 函数 对于上表,可以进行下面一些比较: 1. 比较自动变量和静态局部变量的不同:定义方法、存放位置、生存期 2. 比较自动变量和静态局部变量的不同:定义方法、定义位置、作用域 3. 比较外部变量和静态外部变量的不同:定义方法、作用域 4. 比较自动变量和寄存器变量的不同:定义方法、存放位置 5. 比较自动变量和外部变量的不同:、定义位置、存放位置、生存期、作用域 §8.10 内部函数和外部函数 一、内部函数 1.内部函数的概念: 内部函数又叫静态函数,只限于本文件内部使用,而不允许其它文件调用。 2.内部函数的定义: 在定义函数时在函数名前加上 static,即可使该函数局部化。定义格式为: static 函数类型 函数名(形参表列) 形参说明 { 函数体 } 二、外部函数 1. 外部函数的概念: 外部函数不仅可供本文件中的其它函数调用,而且可供其它文件中的函数调用。 2. 外部函数的定义: 在定义外部函数时,在函数名前加上 extern 或者省略 extern,则该函数可被其它文件中的函数调用,在调用时要对外部函数进行说明,说明的格式是: extern 外部函数名(); 注意以下几种用法的区别: 项 目 外部变量的定义 外部变量的说明 类型变量名; extern 类型 变量名; extern 类型 函数名(形参表列) 外部函数的定义 类型 函数名(形参表列) 外部函数的说明 extern 类型 函数名(); 主调文件开头 任意 格 式 位 置 在函数外 文件或主调函数开头 第九章 编译预处理 在编写程序中,我们经常会遇到这样的问题,在一个文件中多次使用有3.1415926,为了简化编程的书写,C语言允许对常量PI进行预处理,即用符号常量PI来代替数值3.1415926,称之为预处理.所谓预处理就是C语言的编译系统在对程序进行通常的编译之前,先对这些特殊的命令进行预处理,然后将预处理的结果和源程序一起再进行通常的编译处理,以得到目标代码。 C 语言的预处理主要有以下三种: 1. 宏定义 2. 文件包含 3. 条件编译 §9.1 宏定义 一、不带参的宏定义 1. 格式: #define 标识符 字符串 2. 宏展开:定义中的标识符叫宏名, 在预编译时将宏名替换成字符串,这个过程称为宏展开。 3. 说明: (1) 宏名一般用大写字母表示以便与变量分开,但不是规定而只是习惯; (2) 使用宏定义可以减少程序中的重复书写,修改常量方便; (3) 宏定义只是用宏名代替一个字符串,不作语法检查; (4) 宏定义不是C语句,末尾不加分号; (5) 宏定义出现在函数之外,则有效范围为本源程序文件,出现在函数内部则只在本函数内有效,一般宏定义出现在文件开头,函数之前,在此文件内有效; (6) 使用 #undef 命令可以终止宏定义的作用; (7) 在宏定义时可以引用已经定义过的宏名,在编译预处理时层层置换; (8) 程序中双引号中字符串内的字符即使与宏名相同也不进行宏替换。 例如: 求半径为3的圆的面积和周长 #define R 3.0 /* 编译时用3.0替换R */ #define PI 3.14159 /* 编译时用3.14159 替换PI */ #define L PI*R*2 /* 编译时用3.14159*3.0*2 替换L */ #define S PI*R*R /* 编译时用3.14159*3.0*3.0 替换S */ main() {printf(\"R=%f\\n\/* 相当于 printf(\"R=%f\\n\双引号中的R不替换 */ printf(\"S=%f\\n\/* 相当于 printf(\"S=%f\\n\ printf(\"L=%f\\n\/* 相当于 printf(\"L=%f\\n\ } 程序运行结果为: R=3.000000 S=28.274310 L=18.849540 二、带参数的宏定义 1. 格式: #define 标识符(参数表) 字符串 2. 说明: (1) 参数表中的参数必须为变量,称为形参; (2) 带参的宏定义展开是从左到右依次将实参代字符串替形参字符串,若实参多于或少于形参个数则在宏展开时出错,而宏定义并不出错; (3) 当实参为表达式时,注意对表达式加括号; (4) 在宏定义时,宏名与带参的括号之间不应留空格,否则将空格及其之后的字符都按替代字符串处理。 例如: #define S(x,y) x*y #define A(x,y) x+y main() {int a=3,b=4,c=5,d=6; printf(\"a*b=%d\\n\/*以a代替S中的x,以b代替S中的y,展开为 printf(\"a*b=%d\\n\ printf(\"c+d=%d\\n\/*以c代替A中的x,以d代替A中的y,展开为 printf(\"c+d=%d\\n\ printf(\"a+b*c+d=%d\\n\ /*以a+b代替S中的x,以c+d代替S中的y,展开为 printf(\"a+b*c+d=%d\\n\*/ } 程序运行的结果为: a*b=12 c+d=11 a+b*c+d=29 想一想,如果将最后一个输出语句改为: printf(\"a+b*c+d=%d\\n\程序运行结果会变化吗?如果变化,结果应该如何呢? 三、带参的宏定义和函数的区别 函 数 带参的宏定义 参数替换 替换时间 类型要求 返回值 程序长度 占用时间 调用时先计算出实参之值,然后代入形参 展开时只进行字符串与宏名之间的简单替换 程序运行时进行 形参和实参要求定义类型 只能得到一个返回值 调用时程序长度不发生变化 占用运行时间 编译时进行替换 无类型要求 可以得到多个返回值 宏展开使程序变长 不占用运行时间只占用编译时间 §9.2 \"文件包含\"处理 如果要想在某一个源文件中使用另外一个源文件中的全部内容,可以用C语言提供的\"文件包含\"处理.所谓\"文件包含\"就是将另外一个源程序文件插入本源程序文件中,使之成为本源程序文件的一部分。因此可以看出,使用文件包含可以避免重复劳动。 一、“文件包含”的格式 1. #include \"文件名\" 先在引用文件所在的目录中查找,若找不到再按系统指定的标准方式(在TC环境中设置)去检索其它目录。 2. #include <文件名> 不检索引用文件所在的目录,只按系统标准方式检索文件目录。 二、对\"文件包含\"的说明 1. 一个 include 命令只能指定一个被包含文件.多个文件要用多个命令。 2. 被包含的文件一定要出现在使用该文件的语句或其它包含命令之前。 3. 文件可以嵌套包含。 4. 文件名可以用双引号括起来,也可以用尖括号括起来.使用尖括号时仅按系统指定的标准方式检索文件,而不在引用被包含文件的源文件所在的目录中寻找要包含的文件。 5. 被包含的文件和引用被包含的源文件在经预处理以后,成为同一个文件,而不是两个文件。 6. 被包含的文件和引用被包含的源文件在编译时不是作为两个文件而是作为一个源程序文件进行编译的。 包含文件的使用可参阅教材P195例9.6。 因篇幅问题不能全部显示,请点此查看更多更全内容