c语言程序设计武春岭,C语言程序设计(武春岭)电子课件

44cb7578e1df5412b94317daaa3307ba.gifC语言程序设计(武春岭)电子课件

C语言程序设计(武春岭)电子课件,语言程序设计,武春岭,电子,课件

程序设计基础 C语言 第5章模块化程序设计 函数 项目引导 在高校学生成绩管理系统项目中 总共有6个功能 退出系统功能不包含在内 如果这些功能都通过主函数main 来实现 是相当复杂 且难度较大 如果分别采用功能模块来实现 就能简化程序 提供程序执行效率 我们知道C语言程序是由函数组成的 而函数是实现模块化程序设计的 所以在这一章我们主要介绍函数的使用 我们知道巧妇难为无米之炊 同样要实现具有一定功能的模块也需要原材料 各种数据 比如成绩统计需要有成绩作为原材料 原材料可以是各种类型的数据 有时需要借助于形式参数来传递 至于什么是形参是如何传递的 也是本章将要介绍的内容 第5章模块化程序设计 函数 本章重点 本章难点 理解使用称为 函数 的小程序块构造程序模块的好处熟悉C标准库中常见的库函数 并能在程序中灵活地运用它们根据实际需要能够自己定义功能函数并会调用它们 掌握函数声明的必要条件理解函数之间的信息 参数 传递机制理解全局变量与局部变量的 生命权限周期 范围了解编译预处理命令的编译方法和特点 功能函数的定义函数的调用 参数传递 嵌套调用 递归调用 第5章模块化程序设计 函数 C语言的源程序是由函数组成的 函数是C语言源程序的基本模块 通过对函数模块的调用实现特定的功能 本章首先介绍函数的定义和声明格式以及调用的一般形式 接着通过若干例子详细讲解嵌套调用 递归调用函数的应用 介绍全局变量和局部变量的相关知识 最后介绍编译预处理命令 5 1概述5 2函数的定义与声明5 3函数的调用5 4变量类型5 5编译预处理5 6模块化编译链接5 7学生成绩管理系统 学生成绩录入 统计模块设计 模块化程序设计简介 现在我们已经进入编程的高级阶段 模块化编程 其基本思想是 将一个大的程序按功能进行分割成一些模块 使每一个模块都成为功能单一 结构清晰 接口简单 容易理解的小程序 模块化编程特点 各模块相对独立 功能单一 结构清晰 接口简单控制了程序设计的复杂性提高元件的可靠性缩短开发周期避免程序开发的重复劳动易于维护和功能扩充开发方法 自上向下 逐步分解 分而治之 5 1概述 在第1章中已经介绍过 C源程序是由函数组成的 虽然在前面各章的程序中都只有一个主函数main 但实用程序往往由多个函数组成 函数是C源程序的基本模块 通过对函数模块的调用实现特定的功能 C语言中的函数相当于其他高级语言的子程序 C语言不仅提供了极为丰富的库函数 如TurboC MSC都提供了三百多个库函数 还允许用户建立自己定义的函数 用户可把自己的算法编成一个个相对独立的函数模块 然后用调用的方法来使用函数 可以说C程序的全部工作都是由各式各样的函数完成的 所以也把C语言称为函数式语言 由于采用了函数模块式的结构 C语言易于实现结构化程序设计 使程序的层次结构清晰 便于程序的编写 阅读和调试 5 1概述 整个C语言程序项目大致结构如下图 C程序结构 5 1概述 1 函数的定义 在数学中 一个函数 function 表示每个输入值对应唯一输出值 函数f中对应输入值x的输出值的标准符号为f x 包含某个函数所有的输入值的集合被称作这个函数的定义域 包含所有的输出值的集合被称作值域 在计算机中 函数是一个代码块 是一个用于执行某个工作的过程 函数过程中的这些语句用于完成某些有意义的工作 通常是处理文本 控制输入或计算数值 通过在程序代码中引入函数名称和所需的参数 可在该程序中执行 或称调用 该函数 在有些编程语言中有过程和函数之分 过程是没有返回值的一段代码 函数一般都有一个返回值 它们都可在自己结构里面调用自己 称为递归 但在C语言中只有函数没有过程的说法 2 函数的分类 在 语言中可从不同的角度对函数分类 1 从函数定义的角度看 函数可分为库函数和用户定义函数两种 1 库函数 由C系统提供 用户无须定义 也不必在程序中作类型说明 只需在程序前包含该函数原型的头文件即可在程序中直接调用 例如常用的printf scanf getchar putchar等函数均属此类 2 用户定义函数由用户按需要写的函数 对于用户自定义函数 不仅要在程序中定义函数本身 而且在主调函数模块中还必须对该被调函数进行类型说明 然后才能使用 2 函数的分类 2 从对函数返回值的需求状况 语言的函数又可分为有返回值函数和无返回值函数两种 1 有返回值函数2 无返回值函数 3 从主调函数和被调函数之间数据传送的角度看又可分为无参函数和有参函数两种 1 无参函数 函数定义 函数说明及函数调用中均不带参数 主调函数和被调函数之间不进行参数传送 此类函数通常用来完成一组指定的功能 可以返回或不返回函数值 2 有参函数 也称为带参函数 在函数定义及函数说明时都有参数 称为形式参数 简称为形参 在函数调用时也必须给出参数 称为实际参数 简称为实参 进行函数调用时 主调函数将把实参的值传送给形参 供被调函数使用 2 函数的分类 4 C语言提供了极为丰富的库函数 这些库函数又可从功能角度分为多种类型 比如数学库函数 字符串库函数 标准输入输出函数等 函数与C程序总结 C是函数式语言每个完整的C程序必须有且只能有一个名为main的主函数C程序的执行总是从main函数开始 在main中结束函数不能嵌套定义 可以嵌套调用 5 1概述 5 2函数的定义与声明 函数类型函数名 形参类型列表 说明部分语句部分 函数返回值为int或char类型时 可缺省 无返回值时 可用void类型 合法标识符 函数体 5 2 1函数的定义 注意事项 1 函数类型是函数返回值的类型 若不关心函数返回值 则函数类型可定义为void类型 即空类型 2 函数名的命名必须符合标识符的要求 3 形参是实现函数功能所要用到的传输数据 它是函数间进行交流通信的唯一途径 由于形参是由变量充当的 所以必须定义类型 那么 定义形参时 就在函数名后的括号中定义 但有些功能函数不一定要形参 是否有形参将会根据具体情况来定 4 函数体是由实现函数功能的若干程序语句组成的 在函数体内也许还会定义除形参之外要用到的其他变量 5 函数可以没有参数 但圆括号不能省略 函数的定义举例 例无参函数voidprintstar printf n 或voidprintstar void printf n 例空函数dummy 函数体为空 例5 1 写一个求n 的函数 5 2函数的定义与声明 程序如下 intfac intn inti f f 1 for i n i 1 i f f i return f 返回函数的值f 5 2函数的定义与声明 例5 2 编一个求任意数的绝对值的函数 程序如下 floatabs value floatx return x 0 x x 5 2函数的定义与声明 程序如下 voidspc intn inti for i 0 i n i printf return 此时不需要spc的返回值 该语句可不要 例5 3 编一个打印n个空格的函数 5 2函数的定义与声明 程序如下 intmax inta intb if a b returna elsereturnb main intx y z printf inputtwonumbers n scanf d d 例5 4 编一个完整的程序 用函数完成求两个数中的最大值 5 2函数的定义与声明 重点 关于函数类型的几点说明如下 1 int型与char型函数在定义时 可以不定义类型 系统隐含指定为int型 2 对不需要使用函数返回值的函数 应定义为void型 且函数体中可不要return语句 补充 函数的返回值 返回语句的几种形式 return 表达式 return表达式 return 此时 表示不需要返回值 也可不写 功能说明 使程序控制从被调用函数返回到主调函数中 同时把返回值带给主调函数 关于函数返回值的说明 1 函数中可有多个return语句 2 若无return语句 遇到功能函数最后的 时 自动返回主调函数 3 若函数类型与return语句中表达式值的类型不一致 按前者为准 自动转换 函数调用时的数据转换 4 void型函数是无返回值函数 可不要return 或用 return 例无返回值函数voidswap intx inty inttemp temp x x y y temp intlarg inta intb if a b returna elsereturnb 5 2 2函数的声明 函数声明的基本格式 函数类型函数名 形参类型 形参名 例 main doublestyle floata doublex doublestyle floata doublex 函数体语句 doublestyle float double 函数声明的条件 当功能函数的类型非int或char类型 且功能函数写在主调函数后时 一定要在主调函数中对其进行声明 见书P80下面的 1 4 关于声明的说明 见书P81倒数第5行 在函数声明中 形参名可以不写 但形参类型必须写 5 2 2函数的声明 函数声明的原则如下 1 在函数声明中 形参名可以不写 但形参类型必须写 2 在同一文件中 当函数定义写在前面 主调函数写在后面时 可不要函数声明 当函数返回值为int或char类型时 可不要函数声明 3 函数定义与函数声明不是一回事 定义的功能是创建函数 函数是由函数首部和函数体组成的 而声明的作用是把函数的名字 函数类型及形参类型 个数顺序通知编译系统 以便在调用函数时系统按此对照检查 5 2 2函数的声明 5 2 2函数的声明 程序如下 include stdio h main floatsub floatx floaty 函数的声明 floatn1 n2 result scanf f f 例5 6 从键盘上输入两个实数 求这两个数的差 5 3函数的调用 函数调用在前面的程序中已经用过 在程序中是通过对函数的调用来执行函数体的 其过程与其他语言的子程序调用相似 C语言中 函数调用的一般形式为 函数名 实际参数表 提醒 对无参函数调用时则无实际参数表 实际参数表中的参数可以是常数 变量或其他构造类型数据及表达式 各实参之间用逗号分隔 5 3函数的调用 5 3 1函数的一般调用方式 在C语言中 可以用以下几种方式调用函数 1 函数表达式 函数作表达式中的一项出现在表达式中 以函数返回值参与表达式的运算 这种方式要求函数是有返回值的 例如 z max x y 是一个赋值表达式 把max的返回值赋予变量z 2 函数语句 前面函数调用的一般形式加上分号即构成函数语句 例如 printf d a scanf d 即是把max调用的返回值又作为printf函数的实参来使用的 提醒 printf函数输出列表中有多项表达式时 运算顺序为从右至左例如 如下程序 main inti 6 printf d d d n i i i 结果为 5 4 6 5 3 1函数的一般调用方式 程序如下 main inta 3 b 5 voidswap int int 函数声明 swap a b printf a d b d n a b voidswap intx inty inttemp temp x x y y temp printf x d y d n x y 运行结果 x 5 y 3a 3 b 5 例5 7 编一个实现交换功能的函数 并写出主函数 5 3 1函数的一般调用方式 重点 1 调用函数时 函数名必须与具有该功能的自定义函数名称完全一致 2 实参在类型上按顺序与形参一一对应和匹配 如果类型不匹配 C编译程序将按赋值兼容的规则 例如实型可以兼容整型和字符类型 进行转换 如果实参和形参的类型不赋值兼容 通常并不给出出错信息 且程序仍然继续执行 只是得不到正确的结果 3 C程序中 允许函数直接或间接地自己调用自己 称为递归调用 有关递归调用的内容将在下一节中介绍 5 3 2函数的嵌套调用与递归调用 1 函数的嵌套调用 C语言中函数的定义都是互相平行 独立的 一个函数的函数体内不能包含另一个函数的定义 这就是说C语言是不能嵌套定义函数的 但C语言允许嵌套调用函数 所谓嵌套调用就是在调用一个函数并在执行该函数过程中 又调用另一个函数的情况 5 3 2函数的嵌套调用与递归调用 程序如下 intmax intx inty intMax if x y Max y elseMax x return Max main inta b c d intmax int int scanf d d d 例5 8 输入三个数求最大者 嵌套调用C规定 函数定义不可嵌套 但可以嵌套调用函数 函数的嵌套调用 includevoidmain inta b c d scanf d d d 求最大数 intmax intx inty intz intr r x y x y return r z r z 求最小数 intmin intx inty intz intr r x y x y return r z r z 函数的嵌套调用举例 求三个数中最大数和最小数的差额 函数的嵌套调用举例 2 函数的递归调用 一个函数在它的函数体内调用它自身称为递归调用 这种函数称为递归函数 C语言允许函数的递归调用 在递归调用中 主调函数又是被调函数 执行递归函数将反复调用其自身 每调用一次就进入新的一层 例如有函数f如下 intf intx inty z f y returnz 这个函数是一个递归函数 但是运行该函数将无休止地调用其自身 这当然是不正确的 为了防止递归调用无终止地进行 必须在函数内有终止递归调用的手段 常用的办法是加条件判断 满足某种条件后就不再作递归调用 然后逐层返回 下面举例说明递归调用的执行过程 5 3 2函数的嵌套调用与递归调用 程序如下 longff intn longf if n 0 printf n 0 inputerror elseif n 0 n 1 f 1 elsef ff n 1 n return f main intn longy printf ninputainteagernumber n scanf d 例5 9 用递归法计算n intf intx inty z z f y return 2 z intf1 intx inty z z f2 y return 2 z intf2 intt inta c c f1 a return 3 c 函数的递归调用 直接调用 间接调用 5 3 3函数的参数 函数的参数分为形参和实参两种 形参出现在函数定义中 在整个函数体内都可以使用 离开该函数则不能使用 实参出现在主调函数中 进入被调函数后 实参变量也不能使用 形参和实参的功能是作数据传送 发生函数调用时 主调函数把实参的值传送给被调函数的形参从而实现主调函数向被调函数的数据传送 5 3 3函数的参数 函数的形参和实参具有以下特点 1 形参变量只有在被调用时才分配内存单元 在调用结束时 即刻释放所分配的内存单元 因此 形参只有在函数内部才有效 函数调用结束返回主调函数后则不能再使用该形参变量 2 实参可以是常量 变量 表达式 函数等 无论实参是何种类型的量 在进行函数调用时 它们都必须具有确定的值 以便把这些值传送给形参 因此应预先用赋值 输入等办法使实参获得确定值 3 实参和形参在数量 类型 顺序上应严格一致 否则会发生 类型不匹配 的错误 4 函数调用中发生的数据传送是单向的 即只能把实参的值传送给形参 而不能把形参的值反向地传送给实参 因此在函数调用过程中 形参的值发生改变 而实参中的值不会变化 例比较两个数并输出大者 main inta b c scanf d d 函数的参数及传递方式 例5 7参数传递举例 值传递举例 main inta 3 b 5 swap a b printf a d b d n a b voidswap intx inty inttemp temp x x y y temp printf x d y d n x y 函数的参数传递方式举例 例子图解 值传递方式函数调用时 为形参分配单元 并将实参的值复制到形参中 调用结束 形参单元被释放 实参单元仍保留并维持原值 特点 1 形参与实参占用不同的内存单元2 单向值传递 只能由实参传向形参 而形参的值不会影响到实参的值 地址传递方式将在第6章和7章讲解 函数的参数值传递方式总结 5 4变量类型 C语言中所有的变量都有自己的作用域 变量说明的方式不同 其作用域也不同 C语言中的变量 按作用域范围可分为两种 即局部变量和全局变量 5 4 1局部变量 局部变量也称为内部变量 局部变量是在函数内作定义说明的 其作用域仅限于函数内 离开该函数后再使用这种变量是非法的 5 4 1局部变量 提醒 1 在复合语句中定义的变量 仅在本复合语句范围内有效 2 有参函数中的形参也是局部变量 只在其所在的函数范围内有效 3 允许在不同的函数中使用相同的变量名 它们代表不同的对象 分配不同的单元 互不干扰 相互独立 4 局部变量所在的函数被调用或执行时 系统临时给相应的局部变量分配存储单元 一旦函数执行结束 则系统立即释放这些存储单元 所以在各个函数中的局部变量起作用的时刻是不同的 5 4 2全局变量 全局变量也称为外部变量 它是在函数外部定义的变量 它不属于哪一个函数 而属于一个源程序文件 其作用域是整个源程序 在函数中使用全局变量 一般应作全局变量说明 只有在函数内经过说明的全局变量才能使用 全局变量的说明符为extern 但在一个函数之前定义的全局变量 在该函数内使用可不再加以说明 5 4 2全局变量 例如 1 inta 3 b 5 a b为全局变量 main printf d d n a b fun void printf d d n a b 2 voidgx externintx y 声明x y是外部变量 x 135 y x 20 printf d y 5 4 2全局变量 提醒 全局变量的有效范围从定义位置开始到文件结束 但是若在同一个程序中 有全局变量与局部变量名相同 则在局部变量的作用域里 全局变量自动失效 5 4 2全局变量 思考 分析下面的程序运行结果 inta 3 b 5 max inta intb intc c a b a b return c main inta 8 printf d max a b 运行结果 8 全局变量 全局变量 也称为外部变量 它是在函数外部定义的变量 说明 全局变量不属于哪一个函数 它属于所在的源程序文件 其作用域是从定义位置开始到文件结束 但是若在同一个程序中 有全局变量与局部变量名相同 则在局部变量的作用域里 全局变量自动失效 在一个函数之前定义的全局变量 在该函数内可不加说明直接使用 若在函数体内要将某个变量说明为全局变量 必须在该变量前加extern 全局变量示例 1 inta 3 b 5 main printf d d n a b fun void printf d d n a b a b有效范围 3 voidgx externintx y x 135 y x 20 printf d y x y是全局变量 inta 3 b 5 max inta intb intc c a b a b return c main inta 8 printf max d max a b 全局变量和局部变量的应用 全局变量和局部变量同名时 在局部变量的作用范围全局变量无效 运行结果 max 8 全局变量的应用 externcharc1 c2 5 4 3变量的存储方式 前面介绍的局部变量和全局变量是从变量的作用域 即从空间 来划分的 若从变量值存在的时间长短 即变量的生存期 或称时域 来划分的话 变量还可分为动态存储变量和静态存储变量 也就是说 变量的生存期取决于变量的存储方式 在C语言中 变量的存储方式可分为动态存储方式和静态存储方式 而变量的存储类型说明有以下4种 提醒 自动变量和寄存器变量属于动态存储方式 外部变量和静态变量属于静态存储方式 5 4 3变量的存储方式 在介绍了变量的存储类型之后 可以知道对一个变量的说明不仅应说明其数据类型 还应说明其存储类型 因此变量说明的完整形式应为 存储类型说明符数据类型说明符变量名 变量名 例如 staticinta b 说明a b为静态类型变量 autocharc1 c2 说明c1 c2为自动字符变量 staticinta 5 1 2 3 4 5 说明a为静态整型数组 externintx y 说明x y为外部整型变量 5 4 3变量的存储方式 1 动态存储方式所谓动态存储方式 是指在程序运行期间根据需要为相关的变量动态分配存储空间的方式 在C语言中 变量的动态存储方式主要有自动型存储方式和寄存器型存储方式两种形式 下面分别加以介绍 1 自动型存储方式 这种存储类型是C语言程序中使用最广泛的一种类型 自动存储方式的变量建立和撤销 都是由系统自动进行的 该方式进行存储的变量叫自动变量 C语言规定 函数内凡未加存储类型说明的变量均视为自动变量 也就是说自动变量可省去说明符auto 在前面各章的程序中所定义的变量凡未加存储类型说明符的都是自动变量 例如 5 4 3变量的存储方式 自动变量的特点如下 1 自动变量属于局部变量范畴 自动变量的作用域仅限于定义该变量的个体内 在函数中定义的自动变量只在该函数内有效 在复合语句中定义的自动变量只在该复合语句中有效 例如 5 4 3变量的存储方式 2 自动变量属于动态存储方式 只有在使用它 即定义该变量的函数被调用时才给它分配存储单元 开始它的生存期 函数调用结束 释放存储单元 结束生存期 因此函数调用结束之后 自动变量的值不能保留 在复合语句中定义的自动变量 在退出复合语句后也不能再使用 否则将引起错误 例如以下程序 main autointa printf ninputanumber n scanf d 程序分析 s p是在复合语句内定义的自动变量 只能在该复合语句内有效 而程序的第9行却是退出复合语句之后用printf语句输出s p的值 这显然会引起错误 5 4 3变量的存储方式 3 由于自动变量的作用域和生存期都局限于定义它的个体内 函数或复合语句内 因此不同的个体中允许使用同名的变量而不会混淆 即使在函数内定义的自动变量也可与该函数内部的复合语句中定义的自动变量同名 5 4 3变量的存储方式 例5 10 分析下面程序中各个自动变量的作用域 程序如下 main autointa s 100 p 100 printf ninputanumber n scanf d 程序分析 本程序在main函数中和复合语句内两次定义了变量s p为自动变量 按照C语言的规定 在复合语句内 应由复合语句中定义的s p起作用 故s的值应为a a p的值为a a 退出复合语句后的s p应为main所定义的s p 其值在初始化时给定 均为100 从输出结果可以分析出两个s和两个p虽变量名相同 但却是两个不同的变量 5 4 3变量的存储方式 2 寄存器型存储方式 上述各类变量都存放在内存储器内 因此当对一个变量频繁读写时 必须要反复访问内存储器 从而花费大量的存取时间 为此 C语言提供了寄存器存储方式 采用寄存器存储方式的变量 称为寄存器变量 这种变量存放在CPU的寄存器中 使用时不需要访问内存 而直接从寄存器中读写 这样可提高效率 寄存器变量的说明符是register 对于循环次数较多的循环控制变量及循环体内反复使用的变量 均可定义为寄存器变量 5 4 3变量的存储方式 例5 11 求 程序如下 main registeri s 0 for i 1 i 200 i s s i printf s d n s 程序分析 本程序循环200次 i和s都将频繁使用 因此可定义为寄存器变量 5 4 3变量的存储方式 对寄存器变量还要说明以下几点 1 只有局部自动变量和形式参数才可以定义为寄存器变量 因为寄存器变量属于动态存储方式 凡需要采用静态存储方式的量不能定义为寄存器变量 2 在TurboC MSC等计算机上使用的C语言中 实际上是把寄存器变量当成自动变量处理的 因此速度并不能提高 而在程序中允许使用寄存器变量只是为了与标准C保持一致 3 即使能真正使用寄存器变量的计算机 由于CPU中寄存器的个数是有限的 因此使用寄存器变量的个数也是有限的 5 4 3变量的存储方式 2 静态存储方式所谓静态存储方式 是指在程序编译时就给相关的变量分配固定的存储空间 即在程序运行的整个期间内部不变 的方式 1 静态存储的局部变量 由静态存储方式存储的局部变量也可称为静态局部变量 该类变量就是在局部变量前面加static修饰符 其中static是静态存储方式类别符 不可省略 例如 staticinta b staticfloatarray 5 1 2 3 4 5 5 4 3变量的存储方式 静态局部变量属于静态存储方式 它具有以下特点 1 静态局部变量在函数内定义 但不像自动变量那样 当调用时就存在 退出函数时就消失 静态局部变量始终存在着 也就是说它的生存期为整个源程序 2 静态局部变量的生存期虽然为整个源程序 但是其作用域仍与自动变量相同 即只能在定义该变量的函数内使用该变量 退出该函数后 尽管该变量还继续存在 但不能使用它 3 对基本类型的静态局部变量 若在说明时未赋以初值 则系统自动赋予零值 而对自动变量不赋初值 则其值是不定的 根据静态局部变量的特点 可以看出它是一种生存期为整个源程序的量 虽然离开定义它的函数后不能使用 但如果再次调用定义它的函数时 它又可继续使用 而且保存了前次被调用后留下的值 因此 当多次调用一个函数且要求在调用之间保留某些变量的值时 可考虑采用静态局部变量 虽然用全局变量也可以达到上述目的 但全局变量有时会造成意外的副作用 因此仍以采用局部静态变量为宜 5 4 3变量的存储方式 程序如下 main inti voidf 函数说明 for i 1 i 5 i f 函数调用 voidf 函数定义 autointj 0 j printf d n j 程序分析 程序中定义了函数f 其中的变量j说明为自动变量并赋予初始值为0 当主函数中多次调用f时 j均赋初值为0 故每次输出值均为1 例5 12 分析程序 5 4 3变量的存储方式 2 静态全局变量 在全局变量 外部变量 的说明之前再冠以static就构成了静态的全局变量 全局变量本身就是静态存储方式 静态全局变量当然也是静态存储方式 这两者在存储方式上并无不同 这两者的区别在于非静态全局变量的作用域是整个源程序 当一个源程序由多个源文件组成时 非静态的全局变量在各个源文件中都是有效的 而静态全局变量则限制了其作用域 即只在定义该变量的源文件内有效 在同一源程序的其他源文件中不能使用它 由于静态全局变量的作用域局限于一个源文件内 只能为该源文件内的函数公用 因此可以避免在其他源文件中引起错误 5 4 3变量的存储方式 3 用extern声明全局变量 全局变量 即外部变量 的特征如下 1 外部变量和全局变量是对同一类变量的两种不同角度的提法 全局变量是从它的作用域提出的 外部变量是从它的存储方式提出的 表示了它的生存期 2 当一个源程序由若干个源文件组成时 在一个源文件中定义的外部变量在其他的源文件中也有效 例如有一个源程序由源文件F1 C和F2 C组成 5 4 3变量的存储方式 在F1 C和F2 C两个文件中都要使用a b c三个变量 在F1 C文件中把a b c都定义为外部变量 在F2 C文件中用extern把三个变量说明为外部变量 表示这些变量已在其他文件中定义 并把这些变量的类型和变量名进行说明 编译系统不再为它们分配内存空间 5 5编译预处理 编译预处理是C语言区别于其他高级程序设计语言的特征之一 它属于C语言编译系统的一部分 C程序中使用的编译预处理命令均以 开头 它在C编译系统对源程序进行编译之前 先对程序中的这些命令进行预处理 从而改进程序设计环境 提高编程效率的目的 C语言提供的预处理功能主要包括三种 宏定义 文件包含 条件编译 分别用宏定义命令 define 文件包含命令 include 条件编译命令 ifdef endif等 来实现 这些命令均以 开头 结尾无分号 以区别于C语言中的语句 5 5 1宏定义 宏定义是用预处理命令 define实现的预处理 它分为两种形式 带参数的宏定义与不带参数的宏定义 1 不带参数的宏定义不带参数的宏定义也叫字符串的宏定义 它用来指定一个标识符代表一个字符串常量 一般格式如下 define标识符字符串其中 标识符就是宏的名字 简称宏 字符串是宏的替换正文 通过宏定义 使得标识符等同于字符串 例如 definePI3 14其中 PI是宏名 字符串3 14是替换正文 预处理程序将程序中凡以PI作为标识符出现的地方都用3 14替换 这种替换称为宏替换或宏扩展 这种替换的优点在于 用一个有意义的标识符代替一个字符串 便于记忆 易于修改 提高了程序的可移植性 5 5 1宏定义 程序如下 defineN100main inti sum 0 for i 2 i N i i 2 sum sum i printf sum d n sum 经过编译预处理后将得到如下程序 main inti sum 0 for i 2 i 100 i i 2 sum sum i printf sum d n sum 如果要改变处理数的内容 只需要修改宏定义中N的替换字符串即可 不需修改其他地方 例5 13 求100以内所有偶数之和 5 5 1宏定义 提醒 1 宏定义在源程序中要单独占一行 通常 出现在一行的第一个字符的位置 允许 号前有若干的空格或制表符 但不允许有其他字符 2 每个宏定义以换行符作为结束的标志 这与C语言的语句不同 不以 作为结束 如果使用了分号 则会将分号作为字符串的一部分一起替换 例如 definePI3 14 area PI r r 在宏扩展后成为 area 3 14 r 号也作为字符串的一部分参与了替换 结果在编译时出现语法错误 3 宏的名字用大小写字母作为标识符都可以 为了与程序中的变量名或函数名相区别和醒目 习惯用大写字母作为宏名 宏名是一个常量的标识符 它不是变量 不能对它进行赋值 若对上面PI进行赋值操作 例如PI 3 1415926 是错误的 5 5 1宏定义 4 一个宏的作用域是从定义的地方开始到本文件结束 也可以用 undef命令终止宏定义的作用域 例如在程序中定义宏 defineINTEGERint后来又用下列宏定义撤消 undefINTEGER那么 之后程序中再出现INTEGER时就是未定义的标识符 也就是说 INTEGER的作用域是从宏定义的地方开始到 undef之前结束 从上面代码看出可以使用宏定义来表示数据类型 5 宏定义可以嵌套 例如 definePI3 14 defineTWOPI 2 0 PI 若有语句s TWOPI r r 则在编译时被替换为s 2 0 PI r r 5 5 1宏定义 2 带参数的宏定义C语言的预处理命令允许使用带参数的宏 带参数的宏在展开时 不是进行简单的字符串替换 而是进行参数替换 带参数的宏定义的一般形式如下 define标识符 参数表 字符串例如 定义一个计算圆面积的宏 defineS r PI r r 则在程序中的printf 10 4f n S 2 0 将被替换为printf 10 4f n PI 2 0 2 0 提醒 1 在宏定义中宏名和左括号之间没有空格 2 带参数的宏展开时 用实参字符串替换形参字符串 可能会发生错误 比较好的办法是将宏的各个参数用圆括号括起来 例如 有以下的宏定义 defineS r PI r r 5 5 1宏定义 若在程序中有语句area S a b 则将被替换为area PI a b a b 显然不符合程序设计的意图 最好采用下面的形式 defineS r PI r r 这样对于语句area S a b 宏展开后为area PI a b a b 这就达到了程序设计的目的 3 带参数的宏调用和函数调用非常相似 但它们毕竟不是一回事 其主要区别在于 带参数的宏替换只是简单的字符串替换 不存在函数类型 返回值及参数类型的问题 函数调用时 先计算实参表达式的值 再将它的值传给形参 在传递过程中 要检查实参和形参的数据类型是否一致 而带参数的宏替换是用实参表达式原封不动地替换形参 并不进行计算 也不检查参数类型的一致性 在第 2 点中已经说明了该特点 5 5 2文件包含 文件包含 是指把指定文件的全部内容包含到本文件中 文件包含控制行的一般形式如下 include 文件名 或 include例如 include在编译预处理时 就把stdio h头文件的内容与当前的文件连在一起进行编译 同样此命令对用户自己编写的文件也适用 使用文件包含命令的优点 在程序设计中常常把一些公用性符号常量 宏 变量和函数的说明等集中起来组成若干文件 使用时可以根据需要将相关文件包含进来 这样可以避免在多个文件中输入相同的内容 也为程序的可移植性 可修改性提供了良好的条件 5 5 2文件包含 例5 14 假设有3个源文件f1 c f2 c和f3 c 它们的内容如下所示 利用编译预处理命令实现多个文件的编译和连接 源文件f1 c main inta b c s m printf na b c scanf d d d 源文件f2 c intsum intx inty intz return x y z 源文件f3 c intmul intx inty intz return x y z 5 5 2文件包含 处理的方法是在含有主函数的源文件中使用预处理命令 include将其他源文件包含进来即可 这里需要把源文件f2 c和f3 c包含在源文件f1 c中 则修改后源文件f1 c的内容如下 include f2 c include f3 c main inta b c s m printf na b c scanf d d d 现在文件f2 c中的函数sum和文件f3 c中的函数mul都被包含到文件f1 c中 如同在文件f1 c中定义了这两个函数一样 所以说文件包含处理也是模块化程序设计的一种手段 5 5 2文件包含 重点 1 一个include命令只能指定一个被包含文件 如果要包含n个文件 则需要用n个include命令 2 文件包含控制行可出现在源文件的任何地方 但为了醒目 大多放在文件的开头部分 3 include命令的文件名 使用双引号和尖括号是有区别的 使用尖括号仅在系统指定的 标准 目录中查找文件 而不在源文件的目录中查找 使用双引号表明先在正在处理的源文件目录中搜索指定的文件 若没有 再到系统指定的 标准 目录中查找 所以使用系统提供的文件时 一般使用尖括号 以节省查找时间 如果包含用户自己编写的文件 这些文件一般在当前目录中 使用双引号比较好 4 文件包含命令可以是嵌套的 在一个被包含的文件中还可以包含有其他的文件 5 5 3条件编译 一般情况下 源程序中所有的行都参加编译 但是有时希望对其中一部分内容只在满足一定条件时才进行编译 也就是对一部分内容指定编译的条件 这就是 条件编译 有时希望当满足某条件时对一组语句进行编译 而当条件不满足时则编译另一组语句 条件编译命令有以下几种形式 1 使用 ifdef的形式 ifdef标识符程序段1 else程序段2 endif此语句的作用是当标识符已经被 define命令所定义时 条件为真 编译程序段1 否则为假 编译程序段2 它与选择结构的if语句类似 else语句可以没有 如下面的形式 ifdef标识符程序段1 endif 5 5 3条件编译 例5 15 程序调试信息的显示 defineDEBUG ifdefDEBUGprintf x d y d z d n x y z endif程序说明 pintf 被编译 程序运行时可以显示x y z 在程序调试完后 不再需要显示x y z的值 则只需要去掉DEBUG标识符的定义 提醒 虽然直接使用pintf 语句也可以显示调试信息 在程序调试完成后去掉pintf 语句 同样也达到了目的 但如果程序中有很多处需要调试观察 增删语句既麻烦又容易出错 而使用条件编译则相当清晰 方便 5 5 3条件编译 2 使用 ifndef的形式 ifndef标识符程序段1 else程序段2 endif此语句的作用是当标识符未被 define命令所定义时 条件为真 编译程序段1 否则为假 编译程序段2 与上面的条件编译类似 else语句可以没有 如下面的形式 ifndef标识符程序段1 endif提醒 以上 ifndef与 ifdef用法差不多 根据需要任选一种 视方便而定 5 5 3条件编译 例如 例5 15调试时输出信息的条件编译段也可以改为 ifndefRUNprintf x d y d z d n x y z endif如果在此之前没有对RUN定义 则输出x y z的值 调试完成后 在运行之前 增加以下命令行 defineRUN则不再输出x y z的值 5 5 3条件编译 3 使用 if的形式 if表达式程序段1 else程序段2 endif它的作用与if else语句类似 当表达式的值为非0时 条件为真 编译表达式后的程序段1 否则条件为假 转至程序段2进行编译 5 5 3条件编译 例5 16 输入一行字母字符 根据需要设置条件编译 使之能将字母全改为大写输出或全改为小写输出 defineLETTER1main charstr 20 CLanguage c inti i 0 printf Stringis s n str printf ChangeStringis while c str i 0 i ifLETTER if c a 运行结果 Stringis CLanguage ChangeStringis CLANGUAGE 5 5 3条件编译 重点 事实上条件编译可以用if语句代替 但使用if语句目标代码比较长 因为所有的语句均要参与编译 而使用条件编译 只有一部分参与编译 且编译后的目标代码比较短 所以很多地方使用条件编译 实训 实训内容 1 实训1 求素数的函数 编写一个判断素数的函数 当一个数为素数时 函数的返回值为1 否则为0 在主函数中 从键盘上输入10个整数 求其中所有的素数之和 对素数的判断调用上面的素数函数实现 2 实训2 用函数嵌套方法求x y z 3 实训3 用递归函数求解Fibonacci数列问题 在主函数中调用该函数求Fibonacci数列中任意一项的值 4 实训4 输入两个整数 求它们相除的余数 用带参数的宏实现 5 实训5 密码程序的条件编译 编写程序 用条件编译方法实现以下功能 输入一行电报文字 可以任意两种输出 一种输出为原文输出 另一种输出为将所有字母均转化为其下一个字母后输出 如a变成b b变成c z变成a 其他字符不变 用 define命令来控制是否要译成密码 例如 defineCHANGE1则译成密码 若 defineCHANGE0则不译成密码 按原文输出 项目实施 5 6高校学生成绩管理系统 学生成绩录入 统计模块设计 5 6 1技能要求1 具有分析问题 解决问题的能力和项目团队合作能力 2 掌握C语言的程序结构设计和编程思路 3 掌握函数的声明 定义和使用 4 掌握C程序设计模块化编程的思路 5 6高校学生成绩管理系统 学生成绩录入 统计模块设计 5 6 2算法设计在高校学生成绩管理系统项目中 目前我们可以完成的功能是学生成绩录入和统计模块设计 因为目前没有学数组和指针 不能实现多个同类型的数据存储 也因为此 要学生成绩录入和统计两个模块都要用到成绩录入功能 所有定义了一个辅助模块函数Insert1 用以实现1个学生成绩的录入功能 为了学生们便于理解每个模块的设计过程 在这里用流程图简单明了来提示大家 5 6 3代码编写及运行调试 程序代码 includefloatInsert1 1个学生成绩录入功能函数 floatscore scanf f 5 6 3代码编写及运行调试 do scanf d voidCount 学生成绩统计功能函数 inti num floatscore max min ave printf 请输入学生的人数num do scanf d printf 请输入 d个学生的 C语言程序设计 课程成绩 n num printf 第1个学生的成绩 score Insert1 max min ave score for i 2 iscore min score ave ave score ave ave num printf 学生成绩管理系统 n printf 学生成绩统计分析如下 n printf 学生人数是 d人 n num printf 学生成绩中最高分是 1f分 n max printf 学生成绩中最低分是 1f分 n min printf 学生成绩平均分是 1f分 n ave voidmain 输出高校学生成绩管理系统的界面信息 printf n 高校学生成绩管理系统 n printf n printf 1 学生成绩录入2 学生成绩查询 n printf 3 学生成绩统计4 学生资料删除 n printf 5 学生资料修改6 学生成绩插入 n printf 0 系统退出 n printf n printf 请您根据需要选择 0 6 n intt scanf d 如果选择学生成绩录入功能的实现 若选择学生成绩统计功能的实现 若运行时 选择的是编号2对应的学生成绩查询功能 则结果如下图 总结 C语言的源程序是由函数组成的 函数是C语言源程序的基本模块 本章首先介绍函数的基本概念 函数的分类等 接着介绍了函数的定义和声明格式以及调用的一般形式 然后通过若干例子详细讲解嵌套调用 递归调用函数的应用 最后介绍了全局变量和局部变量的相关知识和编译预处理命令的使用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值