期中考试成绩分布 《程序设计》-2017年秋.

Slides:



Advertisements
Similar presentations
电子成绩单项目实现.
Advertisements

“八皇后”问题 崔萌萌 吕金华.
第一章 C语言概述 计算机公共教学部.
编译原理上机实习
专题研讨课二: 数组在解决复杂问题中的作用
C语言程序设计 第八章 函数.
第一章 程序设计入门.
Introduction to the C Programming Language
高级语言程序设计 主讲人:陈玉华.
由C程序结构所知,一个完整的C语言程序是由一个且只能有一个main()函数(又称主函数)和若干个其他函数组合而成的。而前面各章仅学习main()函数的编程,本章将介绍其他函数的编程,包括其他函数的定义、调用、参数传递及变量的作用域等。
主讲教师:吴琼 微信群:C语言2016 QQ群: 密码scu2016 昵称:“真名+学号”
Chap 10 函数与程序结构 10.1 函数的组织 10.2 递归函数 10.3 宏定义 10.4 编译预处理.
If … else 選擇結構 P27.
C 程式設計— 語言簡介 台大資訊工程學系 資訊系統訓練班.
C++ 程式設計— 語言簡介 台大資訊工程學系 資訊系統訓練班.
第七章 函数 目录 有参的加法函数的开发 函数定义的一般形式 函数参数和函数的值 函数的调用
计算概论 第十八讲 C语言高级编程 结构与习题课 北京大学信息学院.
Function.
第12章 從C到C++語言 12-1 C++語言的基礎 12-2 C++語言的輸出與輸入 12-3 C++語言的動態記憶體配置
第 6 章 函式.
程式撰寫流程.
Chap 8 指针 8.1 寻找保险箱密码 8.2 角色互换 8.3 冒泡排序 8.4 电码加密 8.5 任意个整数求和*
程序设计专题一 结构化程序设计与递归函数 主讲教师: 刘新国.
第八章 函数.
第5章 堆疊(Stacks) 5-1 堆疊的基礎 5-2 堆疊的表示法 5-3 堆疊的應用 - 運算式的計算與轉換
QQ: 李祥 QQ: 欢迎多种方式的学习交流,祝大家学有所成.
第7章 编译预处理 本章要求: 本章重点: 本章难点: 掌握用#define定义无参数宏和带有参数宏定义和调用方法;
1. 說明一個一維整數陣列passwd,下標範圍0至49 2. 在屏幕顯示 "Enter password"
C语言 程序设计基础与试验 刘新国、2012年秋.
計數式重複敘述 for 迴圈 P
2.1 C语言的数据类型 2.2 常量与变量 2.3 变量赋初值 2.4 各类数值型数据间的混合运算 2.5 C语言的运算符和表达式
第5讲 结构化程序设计(Part II) 周水庚 2018年10月11日.
第七章 函数及变量存贮类型 7.1 函数基础与C程序结构 7.2 函数的定义和声明 7.3 函数的调用 7.4 函数的嵌套与递归
第4章 顺序程序设计.
第6章 预 处 理.
第九章 预处理命令.
第1章 概述 本章要点: C语言程序结构和特点 C语言程序的基本符号与关键字 C语言程序的编辑及运行 学习方法建议:
C语言概述 第一章.
第1讲 C语言基础 要求: (1) C程序的组成 (2) C语言的标识符是如何定义的。 (3) C语言有哪些基本数据类型?各种基本数
C语言大学实用教程 第5章 函数与程序结构 西南财经大学经济信息工程学院 刘家芬
第 二 章 数据类型、运算符与表达式.
C语言复习2----函数.
Introduction to the C Programming Language
C语言程序设计 教案 崔武子制作
Oop8 function函式.
浙江长征职业技术学院—计算机与信息技术系—相方莉制作
Chap 5 函数 5.1 计算圆柱体积 5.2 使用函数编写程序 5.3 变量与函数.
Chap 5 函数 5.1 计算圆柱体积 5.2 数字金字塔 5.3 复数运算.
7.1 C程序的结构 7.2 作用域和作用域规则 7.3 存储属性和生存期 7.4 变量的初始化
第十四章 若干深入问题和C独有的特性 作业: 函数指针 函数作参数 函数副作用 运算 语句 位段 存储类别 编译预处理
C程序设计.
第5章 函 数.
C语言程序设计 李祥 QQ:
第2章 认识C语言 教学要点 2. 1 项目二C语言程序识读 2 .2 项目三班级成绩排名 2 .3 知识链接 返回.
<编程达人入门课程> 本节内容 为什么要使用变量? 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ:
第6章 预 处 理.
第7章 程序的结构 四、生存期与存储属性 五、extern关键字与外部连接属性 六、static关键字与内部连接属性.
第二章 类型、对象、运算符和表达式.
Introduction to the C Programming Language
#include <iostream.h>
第七章  数 组.
Chap 7 数 组 7.1 排序问题 7.2 找出矩阵中最大值所在的位置 7.3 进制转换.
第二章 数据类型、运算符和表达式 §2.1 数据与数据类型 §2.2 常量、变量和标准函数 §2.3 基本运算符及其表达式 目 录 上一章
第18讲 从C到C++ 计算机与通信工程学院.
C/C++基礎程式設計班 C語言入門、變數、基本處理與輸入輸出 講師:林業峻 CSIE, NTU 3/7, 2015.
基本資料型態 變數與常數 運算子 基本的資料處理 授課:ANT 日期:2014/03/03.
C/C++基礎程式設計班 陣列 講師:林業峻 CSIE, NTU 3/14, 2015.
Chap 10 函数与程序结构 10.1 圆形体积计算器 10.2 汉诺塔问题 10.3 长度单位转换 10.4 大程序构成.
第三章 流程控制 程序的运行流程 选择结构语句 循环结构语句 主讲:李祥 时间:2015年10月.
C++语言程序设计 C++语言程序设计 第二章 基本数据类型与表达式 第十一组 C++语言程序设计.
Presentation transcript:

期中考试成绩分布 《程序设计》-2017年秋

第9讲 作用域规则和编译预处理命令 周水庚 2018年11月15日

提要 局部变量和全局变量 存储类 编译预处理命令简介 《程序设计》-2017年秋

提要 局部变量和全局变量 存储类 编译预处理命令简介 《程序设计》-2017年秋

变量分类 变量可以在程序中三个地方定义:函数内部、函数的形参定义中和所有的函数外部 根据所定义位置的不同,变量可分为局部变量、形式参数和全局变量 从变量作用的空间范围来看,变量可以分为:全局变量和局部变量 从变量作用的时间范围来分,可以有静态存储变量和动态存储变量之分 《程序设计》-2017年秋

程序对象作用域 一个C程序通常由多个函数组成,可分放在若干源程序文件中,且允许各源程序文件分别单独编译 程序用标识符命名程序对象,能直接用标识符引用它代表的程序对象的程序代码范围称为该对象的作用域 本章以变量为例讨论程序对象的作用域和存储类,有关结论可类似地适用于函数名、类型名、语句名等其它程序对象 《程序设计》-2017年秋

全局变量(Global Variable) 全局变量也称为外部变量,是在函数的外部定义的。它的作用域从变量定义处开始,到本程序文件的末尾 在其它地方(它的定义之前或别的源程序文件中)要用变量名引用该变量,需在引用前对它作外部说明 全局变量存放在静态存储区,在程序开始执行时给全局变量分配存储区,程序执行完毕释放 局部变量能和全局变量重名,但是局部变量会屏蔽全局变量 函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量 《程序设计》-2017年秋

局部变量(Local Variable) 局部变量在函数内部或复合语句内定义 局部变量的作用域是定义它的函数体或复合语句 在定义它的函数或复合语句内能用变量名引用它 在定义它的函数体或复合语句外不能使用它 局部变量只有在程序执行到定义它的函数或者复合语句模块时才被赋予内存空间,一旦退出该函数或者复合语句模块,该变量消失 《程序设计》-2017年秋

局部变量及其作用域 void fullch(char ch, char *chpt) { *chpt = ch; /* 这里可以使用 fullch(),形参 ch,chpt */ } void makestr(char ch, int x, char *str) { char s = ch; /* 这里可以使用 fullch(),makestr(), 形参 ch, x,str, 局部变量 s */ if (x == 0) *str = '\0'; else { makestr(ch+1, x-1, str+1); fullch(s, str);} 《程序设计》-2017年秋

局部变量及其作用域(续) 复合语句内定义的变量只在它的复合语句内有效 (接前页) #include <stdio.h> void main() { char s[120]; int m = 26; /*这里可使用fullch(),makestr(),局部变量s,m,以及stdio,h中定义的程序对象*/ makestr('A', m, s); printf("STR = %s\n", s); } 复合语句内定义的变量只在它的复合语句内有效 { /* 这里实现交换整型变量 x 和 y 的值 */ int temp; /* temp只在本复合语句内有效 */ temp = x; x = y; y = temp; 《程序设计》-2017年秋

全局变量及其作用域 float scale = 1.0 ; /* 带初值的全局变量定义 */ extern int k; /* 对k作外部说明 */ int f1( int x) { int i; /* 局部变量 */ … /*因全局部量k有外部说明,这里也可使用k,但不能直接使用t*/ } int k; /* 全局变量 */ int t; /* 全局变量 */ float f2(float a) scale 的自动有效范围 { int i,j; … k 和t的自动有效范围 void main() { int n; float m; … 《程序设计》-2017年秋

全局变量及其作用域(续) 全局变量存在性 全局变量的作用 不要过多使用全局变量 全局变量在程序执行期间一直存在。全局变量可在定义之前的函数和别的源程序文件中使用,但需在使用之前对全局变量作外部说明 全局变量的作用 在函数间提供一个直接数据传递通道 在某些应用中,若干函数共享一组全局变量,函数的执行效果记录在全局变量中;在另一些应用中,通过全局变量来减少函数调用时实参与形参之间的数据传递 不要过多使用全局变量 因程序中的所有函数都能使用全局变量,某个函数改变全局变量的值可能会影响其他函数的执行,产生函数调用的副作用 全局变量在程序执行期间一直占用内存空间 《程序设计》-2017年秋

提要 局部变量和全局变量 存储类 编译预处理命令简介 自动 (auto) 静态 (static) 外部 (extern) 寄存器 (register) 编译预处理命令简介 《程序设计》-2017年秋

变量的可见性与存在性 变量具有可见性和存在性两种基本属性 变量的可见性是指用变量名对变量的可访问性。在程序的某个范围内的所有语句可以用变量的名访问变量,这个范围就是变量的作用域 变量的存在性是指在程序执行的过程中,变量在某个时段或某种情况下在内存中存在,占有着存储空间,它的值被保留着 可见性与存在性在某些场合是一致的,但在有些场合则不一致:可见必存在,存在未必可见。 存在这样的情况,变量虽然存在,但程序的当前位置不可访问它 《程序设计》-2017年秋

存储类(Storage Class) 存储类用于告诉编译器变量的存储方式 C语言用变量的存储类和变量在程序中定义的位置来控制变量的存在性和可见性。其中变量的存储类共有四类 自动 (auto) 静态 (static) 外部 (extern) 寄存器 (register) 《程序设计》-2017年秋

自动变量 在函数或复合语句内定义,在类型说明符之前冠或不冠以关键字auto 自动变量主要特点是临时性 如 auto int x = 2; double y; 定义x和y是自动的 自动变量主要特点是临时性 在函数体内定义的自动变量,只在该函数被调用时,系统临时为它们分配存储单元,函数执行结束时,系统就回收它们的存储单元。自动变量作用域是定义它的函数或复合语句,其它函数或复合语句可用同样的标识符定义其它程序对象 《程序设计》-2017年秋

自动变量(续) 在函数内部,自动变量的可见性和存在性是一致的 自动变量随函数被调用或进入复合语句而存在,函数返回或离开复合语句而消失。一次函数调用到下一次函数调用之间或相继两次进入复合语句之间,自动的变量之值是不保留的。 在每次进入函数或复合语句时,第一次对自动变量的引用是为它设定初值 特别指出:函数的形参也按自动变量处理 《程序设计》-2017年秋

静态变量 静态变量可以是局部的,也可以是全局的。在变量定义时,在类型说明符之前冠以关键字static即可 如static int z = 2;定义z是整型静态的变量 静态变量的特点 静态变量在程序编译时预分配,并在程序执行前就被确定存储单元 静态的局部变量的作用域与自动变量一样,局限于定义它的函数或复合语句 但是它又与自动变量不同,静态的局部变量在程序执行过程中,始终存在,但在它的作用域之外不可直接用其名对它访问 静态的局部变量在其作用域内提供了专有的、永久性的存储。函数体内定义的静态局部变量能保存函数前一次调用后的值,供下一次调用时继续使用 静态局部变量的存在性和可见性可能不一致 《程序设计》-2017年秋

静态局部变量与自动变量的区别-示意程序 #include <stdio.h> p( int x ) /* 函数 p() 的定义 */ { auto int y = 1; /*定义自动的变量y “auto”可不写*/ static int z = 2; /*定义静态的局部变量z*/ y++; z++; return x+y+z; } void main() { printf("%d\t", p(3)); printf("%d\t", p(3)); printf("%d\n", p(3)); 程序运行将输出 8 9 10 《程序设计》-2017年秋

静态局部变量与自动变量的区别 定义静态局部变量时,如同时指定初值,该初值是在程序开始执行前就被设定的。以后每次调用该函数时不再重新设定初值,而是保留上次函数调用结束时的值 自动变量置初值是在每次函数被调用时进行。函数每次被调用,它就被重新创建,并重新设定初值 在定义静态局部变量时,如未指定初值,则系统自动给静态的局部变量赋一个二进位信息全为0的初值 为便于程序移植、阅读和修改,建议程序明确给出静态局部变量的初值 对于自动变量,如定义时未给定初值,它的初值是不确定的。函数在引用定义时未给定初值的自动变量时,必须先为它设定值 《程序设计》-2017年秋

静态全局变量 静态全局变量对于同一源程序文件中的函数是可见的。但与一般的全局变量不同,它不能被任何其它源程序文件中的函数可见 静态全局变量让同一源程序文件中的函数共享数据,但不让其它源程序文件中的函数使用它 静态全局变量的存在性与可见性也可能不一致 《程序设计》-2017年秋

静态全局变量示例 255 static int intStack[256]; /*静态全局变量*/ static int sp = 0; /*静态全局变量*/ int pop(int *ipt) { if (sp == 0) return 0; *ipt = intStack[--sp]; return 1; } int push(int e) { if (sp == 256) return 0; intStack[sp++] = e; return 1; void initStack() { sp = 0; 2 1 sp 1)函数pop()、push()和initStack()共享数组intStack[]和整型变量sp,又不让位于其它源程序文件中的函数使用 2)sp指向当前未存放数据位置 《程序设计》-2017年秋

静态函数 在C程序中,函数是全局的,即函数能让整个程序中的其它函数使用 也可以把函数定义成静态的,从而使该函数不能让别的源程序文件中的函数使用 把函数定义成静态的,只要在函数定义的类型说明符之前加上“static” 《程序设计》-2017年秋

静态的意义 “静态”包含两方面的意义 从程序对象在程序执行期间的存在性来看,静态表示该程序对象“永久”存在 从程序对象可访问或可调用(即可见性)来看,静态表示该程序对象的专用特性,亦即对可见性进行限制 静态局部变量只有定义它的函数中可访问;静态全局变量和静态函数只有在定义它的源程序文件中的函数中可访问或可调用 静态全局变量和静态函数为把数据对象及数据对象上的操作隐藏起来、为描述抽象数据类型提供了手段。这正是结构化程序设计方法和程序设计语言所追求的 需要指出的是,C语言用静态来实现这个要求并不是最好的 《程序设计》-2017年秋

外部变量 外部变量是指该变量是一个在函数之外某处定义的全局变量。在变量说明的类型说明符之前冠以关键字extern 如extern int x,a[ ];说明整型变量x和数组a是外部的 变量x和a被说明为外部后,在该说明之后,就可访问全局整型变量x和数组a的元素 一个C程序可以由一个或多个源程序文件组成,而全局变量可直接被引用的范围是从它的定义处开始到定义它的源程序文件的末尾。如果在全局变量定义之前的函数中要引用该全局变量,需在访问之前对它作外部说明。同样地,如果在定义全局变量源程序文件之外的其它源程序文件中要访问该全局变量,也需在访问之前对它作外部说明 《程序设计》-2017年秋

外部变量示例1 extern int k; /* 说明整型变量 k 是外部的 */ float f2( ); /* 对f2()的说明,或写成外部说明 extern float f2(); */ int f1(int x) { /* 因前面已有对 k和f2 的外部说明, 故这里可使用 k和f2 */ ┇ } int k; /* 全局变量k的定义 */ float f2(float a) /* 因本函数位于变量k定义之后,可使用k */ 《程序设计》-2017年秋

外部变量示例2 extern int k; /*对全局变量k的外部说明*/ int f1(); /*对函数f1()的说明*/ float f2(); /*对函数f2()的说明*/ int f3() { /*因前面的外部说明,故这里可引用变量 k、函数f1()和f2()*/ ┇ } 在一个源程序文件中,调用另一个源程序文件中定义的函数时,也要对被调用的函数作外部说明。但由于函数总是全局的,对函数的外部说明常常不用关键词extern 《程序设计》-2017年秋

寄存器存储类 只有int型、char型及指针类型的自动变量和形参才可以是寄存器存储类的。全局变量或其它复杂数据类型的变量都不可以是寄存器存储类 定义变量是寄存器存储类,只要在变量定义时,在类型说明符之前冠以关键字“register”即可 如 register int i; register char ch; 参见如下形式的函数定义 f(i, j) register int i; /* 形参 i 是寄存器的 */ int j; /* 形参j不是寄存器的 */ { register int k; /* 自动的变量 k 是寄存器的 */ … } 《程序设计》-2017年秋

寄存器存储类(续) 将一个自动变量的存储类指定为寄存器,是提醒编译程序,这个变量在程序中使用得十分频繁。在为该变量分配存储时,有可能的话,尽可能为它分配寄存器,因为访问寄存器要比访问内存储单元快 将一个形参的存储类指定为寄存器,可能是因为要访问某些特殊设备的驱动程序,这些设备的驱动程序要求以寄存器为参数与系统传递信息 《程序设计》-2017年秋

寄存器存储类(续) 含有寄存器存储类的形参或自动变量的函数,当它被调用时,该函数将会用一些寄存器存放形参或变量的值,函数调用结束释放它占用的寄存器 因计算机寄存器数目有限,只能有很少几个寄存器存储类的形参或变量 因实现C语言的系统环境不同,不同系统在实现C语言的各种设施的方法也有差异 某些C系统把寄存器存储类自动变量全部作为一般自动变量处理,为它们分配存储单元,而并不真正为它们分配寄存器 需特别指出:寄存器存储类的变量不能施取地址运算(用运算符&)和不能是静态的 《程序设计》-2017年秋

存储类程序设计实例 下面程序实例说明局部变量、全局变量和静态全局变量的作用和用法 编制一个能完成加(+)、减(-)、乘(*)、除(/)四则运算数值表达式的计算程序 数值表达式的书写规则与平常习惯一致,严格定义可分成四级 实数是表达式最简单的形式 接着是因子表达式,因子表达式有两种可能形式:或是实数,或是带园括号的表达式 对若干因子表达式组成的连续乘除运算,构成项表达式 由若干项表达式组成的连续加减运算构成表达式 《程序设计》-2017年秋

存储类程序设计实例(续) 编制一个能完成加(+)、减(-)、乘(*)、除(/)四则运算的数值表达式的计算程序(续) 数值表达式的基本词法单位有以下几种:实数、四则运算符、圆括号、换行符、结束符、其它非法符号 数值表达式的计算程序由几个函数组成 单词分析函数getToken()从输入字符列,分析出一个独立单词,并得到其内部标记 如单词是实数,函数将返回数值运算分量的内部标记 如单词是运算符或是其它字符,返回对应字符的内部标记 《程序设计》-2017年秋

存储类程序设计实例(续) 完成四则运算的数值表达式的计算程序(续) 把完成因子表达式计算、项表达式计算和表达式计算分别编写成独立的函数 将程序中有关常量和函数说明写在头文件trans.h中。头文件trans.h主要定义有关常量,各单词的内部标记和分散在程序文件中的函数说明等 extern int token; int getToken(char *, double *); int getch(void); void ungetch(int); void initBuf(void); double expr(void); double term(void); double factor(void); #define NL 6 #define LP 7 #define RP 8 #define END -1 #define ERR 0 /*头文件trans.h*/ #define NUM 1 #define ADD 2 #define SUB 3 #define MUL 4 #define DIV 5 《程序设计》-2017年秋

存储类程序设计实例(续) 完成四则运算的数值表达式的计算程序(续):主函数调用表达式计算函数实现表达式的计算,代码如下 /* 设主函数文件名为 expmain.c */ #include <stdio.h> #include “trans.h” void main() { double result; char ans; while (1){ initBuf(); printf("Enter numeric expression!\n"); result=expr(); printf("The expression's result is %.6f\n", result); printf("Continue?(y/n)\n"); scanf(" %c", &ans); if (ans != 'y' && ans != 'Y') break; while(getchar() != '\n'); /* 掠过回答字符后的回车符 */ } 《程序设计》-2017年秋

存储类程序设计实例(续) /* 输入单词函数的源程序文件,设文件名为 gettoken.c */ #include <ctype.h> #include "trans.h" int symble[][2] = {'+', ADD, '-', SUB, '*', MUL, '/', DIV, '(', LP, ')', RP, '\n', NL, -1, EOF, 0, ERR}; int token; int getToken(char *chp, double *dp) /* 返回输入类型 */ { int k, c; double num, rad; while ((c = getch()) == ' ' || c == '\t') ; if ((c < ‘0’ || c > ‘9’) && c != ‘.’) { *chp = c; / * 不是数!!! */ for(k = 0; symble[k][0]; k++) if (c == symble[k][0]) break; return symble[k][1]; /* 返回一个符号 */ } 《程序设计》-2017年秋

存储类程序设计实例(续) num = 0.0; while(c >= ‘0’ && c <= ‘9’) { /* 计算整数部分 */ num = num *10.0 + c - '0'; c = getch(); } if (c == ‘.’) { /* 计算小数部分 */ rad = 0.1; while ((c = getch()) >= '0' && c <= '9') { num += (c - '0')*rad; rad /= 10.0; ungetch(c); /* 把最后一个字符存起来 */ *dp = num; return NUM; /* 返回一个具体的参与运算的数 */ 《程序设计》-2017年秋

存储类程序设计实例(续) buft . bufp /* 输入字符函数和返还输入字符函数的源程序文件。设文件名为 getch.c */ #define BUFSIZE 10 static int buf[BUFSIZE]; static int bufp, buft; /* bufp表示buf中下一个要取的数的位置 */ /* buft表示buf中下一个要存储数据的位置 */ int getch() { int c; if (bufp == buft) return getchar(); c = buf[bufp]; bufp = (bufp + 1) % BUFSIZE; return c; } buft BUFSIZE . bufp 《程序设计》-2017年秋

存储类程序设计实例(续) void ungetch(int c) /* 返还字符函数 */ { int t = (buft+1) % BUFSIZE; if (t == bufp) /* 检查存了该数后,buf是否满? */ printf("ungetch's buffer is FULL!\n"); else {buf[buft] = c; buft = t; } } void initBuf(void) { bufp = buft = 0; 《程序设计》-2017年秋

存储类程序设计实例(续) 以下是定义函数expr()、term()和factor()的源程序文件。设文件名为 exp.c #include <math.h> #define Epsilon 1.0e-5 #include ”trans.h” /* 表达式计算函数调用项计算函数完成连续的加减计算 */ double expr(void) { double ev, tv; char op, ch; ev = term(); while ((op = getToken(&ch, &tv))== ADD || op == SUB) { tv = term(); if (op == ADD) ev += tv; else ev -= tv; } ungetch(ch); return ev; 《程序设计》-2017年秋

存储类程序设计实例(续) /* 项计算函数调用因子计算函数完成连续的乘除计算 */ double term() { double tv, fv, non; char op, ch; tv = factor(); while ((op = getToken(&ch, &non)) == MUL || op == DIV) { fv = factor(); if (op == MUL) tv *= fv; else if (fabs(fv) > Epsilon) tv /= fv; else { printf("Error! Divided by zero.\n"); return 1.0; } ungetch(ch); return tv; 《程序设计》-2017年秋

存储类程序设计实例(续) /*因子计算函数或获得一个实数值,或递归调用表达式计算函数获得因子的值 */ double factor(void) { char ch; double res, non; token = getToken(&ch, &res); if(token == NUM) return res; if (token = LP) { res = expr(); if (getToken(&ch, &non) == RP) return res; ungetch(ch); printf("Error! no right parenthesis\n"); return 1.0; } ungetch(ch); printf("Error! Expect factor.\n"); 《程序设计》-2017年秋

存储类程序设计实例(续) 要将上述源程序文件构成一个程序,在TURBO C环境,可预设以下项目文件(设文件名为exp.prj),并编译该文件即可 getch.c gettoken.c exp.c expmain.c 《程序设计》-2017年秋

expr()-->term() -->factor 这是一个递归程序! main() expr()-->term() -->factor 《程序设计》-2017年秋

变量定义与变量说明 在C中,变量定义和变量说明的作用是不同的 变量定义: 如int a[100]; 出现在任何函数之外,则它定义了全局变量,在程序执行之前就要为它分配存储单元。同时,该定义还作为该源程序文件余下部分的变量说明,使源程序文件余下部分可直接引用它 变量说明:只是告诉编译系统,某标识符所代表的意义,不要求为说明提及的变量分配存储单元。如变量说明 extern int a[]; 只是对源程序文件余下部分说明标识符a是数组,它的元素是int型的,并不要求为它分配存储单元。因此在数组说明中,数组元素个数不必指定 《程序设计》-2017年秋

变量分类 从存储类别 自动变量 静态变量 外变量 寄存器变量 从定义位置 局部变量 全局变量 《程序设计》-2017年秋

变量的可见性与存在性 局部变量:存在即局部可见 寄存器变量:存在即局部可见 全局变量:存在即全局可见 外部变量:同全局变量 静态局部变量 长期存在,局部短期可见 静态全局变量 《程序设计》-2017年秋

提要 局部变量和全局变量 存储类 编译预处理命令简介 宏定义、文件包含、条件编译和行控制 《程序设计》-2017年秋

编译预处理命令简介 编译预处理是指在对源程序作正常编译之前(包括词法和语法分析、代码生成、优化等),先对源程序中一些特殊的预处理命令作解释,产生一个新的源程序;然后再对新的源程序进行常规的编译 C语言提供的预处理命令(preprocessor directive)主要有 宏定义、文件包含、条件编译和行控制 在源程序中,为区别预处理命令和一般的C代码,所有预处理命令行都以字符"#"和预处理命令开头 《程序设计》-2017年秋

编译预处理命令:宏定义 宏定义两种形式 不带形式参数的宏定义 不带形式参数宏定义 带形式参数宏定义 一般形式为: #define 标识符 字符序列 #define之后标识符称为宏定义名(简称宏名),要求宏名与字符序列之间用空格符分隔 这种宏定义要求编译预处理程序将源程序中随后出现的所有宏名 (注释与字符串常量中的除外)均用字符序列替换 前面经常使用的定义符号常量是宏定义的最简单应用 《程序设计》-2017年秋

编译预处理命令:宏定义(续) 不带形式参数的宏定义例子 如有 #define TRUE 1 #define FALSE 0 则在它们被定义的源程序文件中,后面程序正文中凡出现TRUE的地方将用1替代之,FALSE的地方用0替代之 以下是经常使用的不带参数宏定义的例子 #define PI 3.1415926 #define NL printf("\n") #define EOF (-1) 《程序设计》-2017年秋

不带形式参数的宏定义 不带形式参数的宏定义的格式说明及辖区 在宏定义的#之前可以有若干空格、制表符,但不允许有其它字符 宏定义在源程序中单独另起一行,换行符是宏定义的结束标志。如果一个宏定义太长,一行不够时,可采用续行的方法。续行是在键入回车符之前先键入符号‘\’。 注意回车要紧接在符号‘\’之后,中间不能插入其它符号 宏定义的有效范围称为宏定义名的辖域,辖域从宏定义的定义结束处开始到其所在的源程序文件末尾。宏定义名的辖域不受程序结构的影响。可以用预处理命令#undef终止宏定义名的辖域 《程序设计》-2017年秋

不带形式参数的宏定义 不带形式参数的宏定义的辖区示例 如 #define Macro 5.4 main() { ┇ } #undef Macro f() ┇ 在以上正文中,由于预处理命令#undef Macro,使Macro的辖域在#undef行处终止。因此,从函数f()开始,Macro已不再有定义 《程序设计》-2017年秋

不带形式参数的宏定义 不带形式参数的宏定义的重复定义 在新的宏定义中,可以使用前面已定义的宏名 如 #define R 2.5 #define PI 3.1415926 #define Circle 2*PI*R #define Area PI*R*R 程序中Circle展为2*3.1415926*2.5, Area为3.1415926*2.5*2.5 如有必要,同一宏名可被重复定义。被重复定义后, 宏名原先的意义被新的意义所代替 如 #define PI 3.14 ┇ #define PI 3.1415926 则随后的宏名 PI 就代表 3.1415926 《程序设计》-2017年秋

不带形式参数的宏定义 不带形式参数的宏定义的应用 多用于定义常量 用宏表示常量值,便于程序前后统一,不易出错;便于修改,可提高程序可读性和可移植性 给数组元素个数一个宏定义,用宏名定义数组元素个数能部分弥补数组元素个数固定的不足 注意,预处理程序在处理宏定义时,只作字符序列的替换工作, 不作任何语法的检查。如果宏定义不当,错误要到预处理之后的编译阶段才能发现 《程序设计》-2017年秋

不带形式参数的宏定义 不带形式参数的宏定义的换行 宏定义以换行结束,不需要分号等符号作分隔符 如有以下宏定义 #define PI 3.1415926; 原希望用PI求园的周长的语句 circle = 2*PI*r; 经宏展开后,变成 circle = 2*3.1415926; *r; 这就不能达到希望的要求 《程序设计》-2017年秋

编译预处理命令:宏定义(续) 带形式参数的宏定义 带形式参数的宏定义进一步扩充了不带形式参数宏定义的能力,在字符序列替换同时还能进行参数替换 带形式参数宏定义的一般形式为 #define 标识符(形式参数表) 字符序列 形式参数表中的形式参数之间用逗号分隔,字符序列中应包含形式参数表中的形式参数 在定义带形式参数的宏时,标识符(宏名)与左园括号之间不允许有空白符,应紧接在一起,否则变成了不带形式参数的宏定义 《程序设计》-2017年秋

带形式参数的宏定义 带形式参数的宏定义示例 宏定义: #define MAX(A, B) ((A) > (B) ? (A) : (B)) 代码 : y = MAX(p+q, u+v) 将被替换成 y =((p+q) > (u+v) ? (p+q) : (u+v)) 程序中宏调用是被替换展开的,即分别用宏调用中实参数字符序列替换宏定义字符序列中对应所有出现的形式参数(如用p+q替代所有形式参数A,用u+v替代所有形式参数B),而宏定义字符序列中不是形式参数的其它字符则保留。这样形成的字符序列,即为宏调用的展开替换结果 宏调用提供实在参数个数须与宏定义中形式参数个数相同 《程序设计》-2017年秋

带形式参数的宏定义 带形式参数的宏调用与函数调用的区别 函数调用时,实参表达式分别独立求值在前,执行函数体在后 宏调用是实在参数字符序列替换形式参数,替换后实在参数字符序列就与相邻的字符自然连接,实在参数的独立性就不一定依旧存在 如宏定义#define SQR(x) x*x希望实现表达式的平方计算。对于宏调用p=SQR(y)能得到希望的宏展开p=y*y。但对于宏调用q=SQR(u+v)得到的宏展开是q=u+v*u+v。显然,后者的展开结果不是程序设计者所希望的 《程序设计》-2017年秋

带形式参数的宏定义 宏调用与函数调用的区别 为保持实在参数替换后的独立性,应在宏定义中给形式参数加上括号 如:SQR宏定义改写成 #define SQR(x) (x)*(x) 为了保证整个宏调用的独立性,有时还需将整个宏定义表达式加上括号 如:SQR宏定义改写成:#define SQR(x) ((x)*(x)) 《程序设计》-2017年秋

带形式参数的宏定义 宏调用与函数调用的区别 函数调用在程序运行时实行,而宏展开是在编译的预处理阶段进行 函数调用占用程序运行时间,宏调用只占编译时间 函数调用对实参有类型要求,而宏调用实在参数与宏定义形式参数之间没有类型的概念,只有字符序列的对应关系 函数调用可返回一个值,宏调用获得希望的C代码 《程序设计》-2017年秋

带形式参数的宏定义示例 如宏定义: #define EXCHANGE(x, y) t = x; x = y; y = t; #define PI 3.1415926 #define CIRCLE(R,L,S,V) L = 2*PI*(R);\ S = PI*(R)*(R);\ V = 4.0/3.0*PI*(R)*(R)*(R); /* R两边的园括号不能少 */ 宏调用EXCHANGE(a, b)的宏展开为 t = a; a = b; b = t; 而宏调用CIRCLE(r+2.0, clen, area, volume)的宏展开为 clen = 2*3.1415926*(r+2.0); area = 3.1415926*(r+2.0)*(r+2.0); volume=4.0/3.0*3.1415926*(r+2.0)*(r+2.0)*(r+2.0); 《程序设计》-2017年秋

宏定义示例 对于简短的表达式计算函数,或为了提高程序的执行效率,可将函数定义改写成宏定义。所以合理使用宏定义,可以使程序更简洁 下面的例子能说明这样的应用。程序先将使用的输出格式定义成宏,然后调用宏实现数据的输出 #define PR printf #define NL "\n" #define F "%6.3f" #define F1 F NL #define F2 F F NL #define F3 F F F NL 《程序设计》-2017年秋

宏定义示例 main () { float x, y, z; x = 1.2; y = 2.3; z = 3.4; PR (F1, x); PR (F2, x, y); PR (F3, x, y, z); } 运行该程序,输出结果: 1.200 1.200 2.300 1.200 2.300 3.400 程序用PR代表printf,以NL代表换行,F代表浮点型输出格式。又用以上三个宏定义定义输出一个浮点数的格式F1,输出两个浮点数的格式F2和输出三个浮点数的格式F3 《程序设计》-2017年秋

宏定义的字符序列 在宏定义的字符序列中有用双引号括起来的字符串,并且字符串中有与形式参数名相同的内容,则在宏调用展开时,整个字符串被完整复制,不会用实在参数的内容替代形式参数 如:#define PRINT(v) printf ("v = %d\n", v) 宏调用PRINT(i)和PRINT(j)分别被展开成 printf(“v = %d\n”, i) 和 printf(“v = %d\n”, j) 而不会展成printf(“i = %d\n”, i) 和 printf("j = %d\n", j) 《程序设计》-2017年秋

宏定义的字符序列(续) 预处理程序将宏定义中的字符序列看作由语言基本单位组成的序列(也称单词序列),即由标识符、字符串、常量、定义符等构成的序列,以此来识别出字符序列中的形式参数 对预处理程序来说,用双引号括起来的字符序列是字符串,它已是不能再细分的基本词法单位,不再考虑在字符串之内是否还有形式参数出现等情况 为了实现象前述例子中后者所示那样的替换要求,ANSI C采用变相的方法,增加了两个特殊的预处理字符“#”和“##” 《程序设计》-2017年秋

宏定义的字符序列(续) 在宏定义的字符序列中,若一个形式参数标识符前有#号,则该形式参数与#号一同被相应实在参数字符列替换,并在实在参数字符列的前和后都加上双引号,所以字符#能将实在参数字符列构成字符串 如有宏定义#define STR(pt,str) pt = #str 代码STR(s, a string)被替换成 s = "a string“ 而宏定义 #define PRINT(v) printf (#v“ = %d\n”, v) 使代码 PRINT(i) 和 代码PRINT(j) 将分别被替换成 printf(“i = %d\n”, i) 和 printf("j = %d\n", j) 《程序设计》-2017年秋

宏定义的字符序列(续) 在宏定义的字符序列中,若一个形式参数前有双字符##,则在进行宏替换时,先用实在参数字符列替换形式参数,再把得到的字符列中的双字符##都删去,并将其前后两个相邻单词合成一个单词 如 #define DEBUG(s,t) printf("y"#s"=%d,y"#t"=%d",y##s,y##t) 代码“DEBUG(1,2)”先被替换成 printf("y" "1" "=%d,y" "2" "=%d", y##1, y##2) 由于两个紧接的字符串常量会自动连接成一个字符串常量,并将所得字符序列中的##删去,上述替换最终结果为 printf("y1=%d,y2=%d", y1, y2) 《程序设计》-2017年秋

宏定义的进一步说明-1 宏可以嵌套定义。为处理可能有嵌套的宏定义,预处理程序对替换后得到的串要重新扫描 例如 #define X Y/10 X+c #define Y 8+z*X 对上述两个X+c,前一个被替换成Y/10+c;后一个将被替换成8+z*X/10+c 《程序设计》-2017年秋

宏定义的进一步说明-1(续) 宏定义的预处理过程采用多遍处理的方法实现 宏定义本身不会被别的宏定义替换成新的宏定义,如宏定义“#define Y 8+z*X”不被替换成“#define Y 8+z*Y/10” 宏定义的预处理过程采用多遍处理的方法实现 如所得字符序列还有宏名,且该宏名在此位置是有效的,这个宏名还未被处理过,则将对该宏名作替换处理 若新产生的字符序列又出现已被替换过的宏名、或新产生的宏名在该处是无效的,则不再对该宏名作替换处理 如有 #define Z (Z),代码 Z+Y 被替换成 (Z)+Y。对替换产生的Z是已替换过的宏名,不再对它进行再替换。如前面的后一个X+c,第一遍处理后,它被替换成Y/10+c。第二遍处理时,因Y的宏定义对它有效,再被替换成8+z*X/10+c。以后因宏X已被处理过,对新产生的X就不再替换 《程序设计》-2017年秋

宏定义的进一步说明-2 带形式参数宏定义的形式参数表是一个用逗号分隔的标识符序列 而宏调用时,除实在参数个数须与形式参数个数相同外,实在参数可以是任意字符序列,可以包含配对的括号,及配对的括号中可以包含逗号 如 #define A(x) u x + v x 代码 A((a, b)) 被替换成 u(a, b) + v (a, b) 《程序设计》-2017年秋

文件包含预处理命令 文件包含预处理命令实现将指定文件的内容作为当前源程序的一部分 文件包含预处理命令(简称文件包含命令)一般形式为 #include “文件名” 或 #include <文件名> 《程序设计》-2017年秋

文件包含预处理命令示例 如有两个不同的文件: 文件example.c的内容是 #include <stdio.h> #include "format.h" void main( ) { float x, y, z; x = 1.2; y = 2.3; z = 3.4; PR(F1, x); PR(F2, x, y); PR(F3,x, y, z); } 文件format.h的内容是 #define PR printf #define NL "\n" #define F "%6.3F" #define F1 F NL #define F2 F F NL #define F3 F F F NL 文件example.c中有文件包含命令#include ”format.h“,使文件format.h的全部内容被复制并插在此文件包含命令处,形成新的文件。在接着往下进行的编译过程中,就以上述预处理后的新文件作为编译的源文件 《程序设计》-2017年秋

文件包含预处理命令(续) 文件包含命令为组装大程序和程序文件复用提供了一种手段 在编写程序时,习惯将公共的常量定义、数据类型定义和全局变量的外部说明构成一个源文件。 称这类没有执行代码的文件为头文件,并以“.h”为文件名的后缀 其他程序文件凡要用到头文件中定义或说明的程序对象时,就用文件包含命令使它成为自已的一部分 这样编程的好处是各程序文件使用统一的数据结构和常量,能保证程序的一致性,也便于修改程序 头文件如同标准零件一样被其他程序文件使用,减少了重复定义的工作量 《程序设计》-2017年秋

文件包含预处理命令(续) 头文件常被别的文件所包含,如头文件有修改,则所有包含它的文件都得重新编译 一个文件包含命令只能指定一个被包含文件。如要包含多个文件,就得用多个文件包含命令 在有多个文件包含命令的情况下,文件包含命令的出现顺序也是有关系的 如果文件file1.c包含文件 head1.h和head2.h,且head2.h要引用文件head1.h中定义或说明的程序对象,则包含 head1.h 的文件包含命令必须出现在包含head2.h的文件包含命令之前 《程序设计》-2017年秋

文件包含预处理命令(续) 一个被包含文件也可有文件包含命令,包含别的其他文件 在某些情况下,因多个文件都要包含一连串相关的文件,可编写一个全由文件包含命令组成的头文件包含这组相关文件 这样,一条包含这个文件的文件包含命令就能实现包含一组文件的作用 《程序设计》-2017年秋

两种包含命令的区别 有两种包含命令,如 #include <stdio.h> 和 #include "format.h" 对于用双引号括住文件名的文件包含命令,预处理程序首先在当前文件所在的文件目录中寻找被包含文件。如找不到,再按预先指定的寻找顺序检索其他目录 对于用尖括号括住文件名的文件包含命令,预处理程序不检查当前目录,直接按系统指定的寻找顺序检索文件目录 一般地,用户自己编写的被包含文件多用双引号方式的文件包含命令;系统提供的被包含文件用尖括号方式的文件包含命令 《程序设计》-2017年秋

第6章 小结 局部变量和全局变量 存储类 变量定义 编译预处理命令简介 自动的(auto)、静态的(static) 外部的(extern)、寄存器的(register) 变量定义 编译预处理命令简介 宏定义、文件包含 条件编译和行控制 《程序设计》-2017年秋

作业 习题五 第1、2、3、5、10、14题 《程序设计》-2017年秋