晏文靖 yanwenjing@wxic.edu.cn 第七单元 应用函数程序设计 从现在开始,将详细讲述C语言的方方面面。第一章中的所有疑惑,都将一一消灭。 本章将讲述类型、变量、常量、数组等。这些概念的建立是进行进一步C语言学习的必要条件。同时,这些概念也是各种高级语言的共通概念。 晏文靖 yanwenjing@wxic.edu.cn
内容提要 函数定义、函数调用、函数原型、函数返回值 全局变量、自动变量、静态变量、寄存器变量 预处理命令 结构设计与模块化 难点:函数的参数传递与返回值 全局变量、自动变量、静态变量、寄存器变量 难点:变量的作用域与存储类型 预处理命令 结构设计与模块化
大话三国 懿问曰:“孔明寝食及事之烦简若何?” 使者曰:“丞相夙兴夜寐,罚二十以上皆亲览焉。所啖之食,日不过数升。” 懿顾谓诸将曰:“孔明食少事烦,其能久乎?”
函数(function)和模块(module) 读多少行的程序能让你不头疼? main()当中能放多少行程序?
例 计算两个整数的平均数 /* 函数功能: 计算平均数 函数入口参数: 整型x,存储第一个运算数 整型y,存储第二个运算数 函数功能: 计算平均数 函数入口参数: 整型x,存储第一个运算数 整型y,存储第二个运算数 函数返回值: 平均数 */ int Average(int x, int y) { int result; result = (x + y) / 2; return result; }
例 使用了Average函数的main() int Average(int x, int y) { int result; result = (x + y) / 2; return result; } main() int a = 12; int b = 24; int ave; ave = Average(a, b); printf(……); 数据传递 执行顺序 main() { int a = 12; int b = 24; int ave; ave = Average(a, b); printf("Average of %d and %d is %d.\n", a, b, ave); }
函数的分类 函数是C程序的基本单位 库函数 自定义函数 ANSI C定义的标准库函数 第三方库函数 自己编写的函数 包装后,也可成为函数库,供别人使用
函数定义(definition) 类型 函数名(类型 参数1, 类型 参数2, ……) { 函数体; return 表达式; } 形式参数 返回值类型 标识符 类型 函数名(类型 参数1, 类型 参数2, ……) { 函数体; return 表达式; } 返回值 函数出口
函数定义(definition) 函数是这样的一种运算: 函数名说明运算规则 参数是运算的操作数 返回值是运算的结果 当函数执行到return语句或}时,函数的运算停止。程序从当次调用函数的地方继续执行 函数可以有多个return,但最好只有一个且是最后一行 用void定义返回值类型 函数没有运算结果,没有返回值 return语句之后不需要任何表达式 用void定义参数,表示没有参数 参数表里的参数(叫形式参数,parameter)也是函数的语句块内的变量
函数调用(call) 函数名(表达式1, 表达式2, ……); 调用一个函数之前,先要对其返回值类型、函数名和参数进行声明(declare) 不对函数进行声明是非常危险的 函数定义也有声明函数的效果 调用函数时,提供的表达式(叫实际参数, argument)和该函数的形式参数必须匹配 数目一致 类型一一对应(会发生自动类型转换) 表达式的值赋值给对应的参数 返回值可以按需处理 RealEqual() realeql.c
现场编程 利用函数完成求2个数的乘积并输出 利用函数完成求2个数的最大值并输出 利用函数完成求3个数的最大值并输出
现场编程 利用函数完成求1+2+3+…+n 利用函数完成求n!
函数原型(prototype) 调用一个函数之前,先要对其返回值类型、函数名和参数进行声明(declare) 不对函数进行声明是非常危险的 声明时不要省略参数以及返回值的类型
例 #include <stdio.h> int Average(int x, int y); /*声明Average()函数*/ main() { int a = 12; int b = 24; int ave = Average(a, b); printf("Average of %d and %d is %d.\n", a, b, ave); } /* 函数功能: 计算平均数 函数入口参数: 整型x,存储第一个运算数 整型y,存储第二个运算数 函数返回值: 平均数 */ int Average(int x, int y) int result; result = (x + y) / 2; return result; 例
现场编程 输入一个数,判断其是否素数
函数的嵌套调用 在被调函数中,又调用了函数----嵌套调用 main() { … ... } a 函数 { … } b函数 { … } ③ ① ② ④ ⑥ ⑦ ⑤
函数的递归调用 在被调函数中,又调用了自身----递归调用 利用递归求n! n!=n*(n-1)! n>1 n!=1 n=1
变量的作用域 指在源程序中定义变量的位置及其能被读写访问的范围 分为 局部变量(Local Variable) 全局变量(Global Variable )
局部变量 局部变量 在语句块内定义的变量 形参也是局部变量 定义时不会自动初始化,除非程序员指定初值 进入语句块时获得内存,仅能由语句块内语句访问,退出语句块时释放内存,不再有效 并列语句块各自定义的同名变量互不干扰
全局变量 全局变量 使函数之间的数据交换更容易,也更高效 尽量少用全局变量。不得不用时,要严格控制对它的改写 在所有函数之外定义的变量 在程序中定义它的位置以后都有效 自动初始化为0 从程序运行起即占据内存,程序运行过程中可随时访问,程序退出时释放内存 在定义点之前或在其他文件中引用,应该进行如下声明: extern 类型名 变量名; 使函数之间的数据交换更容易,也更高效 但是并不推荐使用 因为谁都可以改写全局变量,所以很难确定是谁改写了它 尽量少用全局变量。不得不用时,要严格控制对它的改写
变量的存储类型 指数据在内存中存储的方式,即编译器为变量分配内存的方式,它决定变量的生存期 动态存储 静态存储 程序区 静态存储区 根据需要临时分配存储空间,离开即释放 静态存储 在程序运行期间分配固定的存储空间不释放 程序区 静态存储区 动态存储区 形参、自动变量、函数调用的现场等 全局变量、 静态变量
自动变量 (auto ) “自动”体现在 动态局部变量 标准定义格式 缺省的存储类型 进入语句块时自动申请内存,退出时自动释放内存 不初始化时,值是不确定的
静态变量(static) 一个函数的内部变量在函数退出后失效。再次进入函数,变量值重新初始化 static int i; 静态变量和全局变量都是静态存储类型 自动初始化为0 从静态存储区分配,生存期为整个程序运行期间 但作用域不同
预编译指令 编译器在开始正式编译之前处理的指令,叫预编译指令 文件包含:#include 它们不会存在于最后生成的目标代码中 用#include指定的文件内容替换#include所在的行 用<>或者""括上文件名 <>表示在编译器的include目录内查找文件 ""表示在当前目录查找文件 文件名中可以带有路径
#define #define 宏名字 替换文本 在#define之后,所有独立出现“宏名字”的地方(除了字符串内)都被“替换文本”替换 “替换文本”中可以有空格 宏可以有参数 #define max(A,B) ((A) > (B) ? (A) : (B)) 能想出带参数的宏和函数的区别吗? 定义宏的时候注意替换发生后产生的非预想结果 一般用括号可以避免,如上例 宏名中间不要有空格 带参数的宏可以获得比函数执行效率更高的代码,而且不用固定参数类型
与#define配套者 #undef,从现在开始取消#define的定义 #if, #else,#elif,#endif #undef MAXLINE #if, #else,#elif,#endif #ifdef,#ifndef 这些预编译指令通常用来处理多文件工程和程序多版本的问题。(程序多版本一般是不同平台的版本,不同用户等级的版本,不同开发阶段的版本等)
使用预编译指令的目的 增强程序可读性 精简源代码,提取变化 不编译无用代码,精炼目标代码 但是调错时宏可能带来很多难题 这一点更多时候用函数的效果更好,但宏也有其不可替代的优势 不编译无用代码,精炼目标代码
模块和链接 将一个程序分解成若干个模块,分别放在几个源文件中,形成一个项目(Project) 然后,对每一个源文件分别单独进行编译 再将它们的目标代码连同标准函数库中的函数链接在一起,形成可执行文件。 主模块 main()所在的文件也是一个模块 模块之间通过互相调用函数和共享全局变量联系起来 头文件是联系的纽带 头文件里对全局变量的声明要加上extern关键字,用以说明该变量为外部变量