C语言程序设计(三)ppt.ppt_第1页
C语言程序设计(三)ppt.ppt_第2页
C语言程序设计(三)ppt.ppt_第3页
C语言程序设计(三)ppt.ppt_第4页
C语言程序设计(三)ppt.ppt_第5页
已阅读5页,还剩176页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

C语言程序设计 三 8 1概述8 2函数调用和返回值8 3函数调用时的参数传递8 4函数的嵌套调用和递归调用8 5函数与指针8 6变量的作用域和存储属性8 7编译预处理 第8章函数 本章导读 在C语言中 函数的含义不是数学中的函数关系或表达式 而是一个程序模块 本章介绍C语言中函数的定义与调用 函数的参数和返回值 函数间的数据传递方法 函数的递归调用 函数与指针的关系 变量的作用域和存储属性 编译预处理等 通过本章的学习 要求读者理解函数的概念 掌握函数定义和函数调用的方法 理解函数调用的实质 掌握有参函数的数据传递方法 区分 值传递 与 地址传递 的概念 理解函数递归调用的概念 掌握递归函数设计的一般方法 熟悉函数与指针的关系 理解变量存储类别以及作用域和生存期的概念 会用一般预编译命令 掌握模块化程序设计的一般方法与技巧 8 1概述 人们在求解一个问题时 通常采用的是逐步分解 分而治之的方法 即把一个大问题分解成若干个比较易解的小问题 然后分别求解 一个模块化程序就是用高级语言表示的模块化算法 这种程序便于编写 阅读 修改和维护 提高了程序的可靠性 保证了程序的质量 在C语言中 程序的基本单位是函数 函数就是程序的基本模块 8 1 1C语言程序的结构 一个较大的程序通常分为若干个子程序模块 每个子程序模块实现一个特定的功能 在C语言中 这些子程序模块是由函数来完成的 一个C程序可由一个主函数和若干个函数组成 程序执行时 从主函数开始 通过主函数调用其他函数 其他函数也可以相互调用 同一个函数可以被一个或多个函数调用 图8 1是程序中函数调用的示意图 图8 1C程序函数调用示意图 例8 1函数调用的例子 includemain 主函数main printa 调用printa函数 printb 调用printb函数 printc 调用printc函数 printa 定义printa函数 printf n printb 定义printb函数 printf Cprogram n printa 调用printa函数 printc 定义printc函数 printf n 1 一个源程序文件由一个或多个函数构成 每个函数完成一个相对独立的任务 2 一个C程序是由一个或多个源程序文件组成 一个C程序是一个编译单位 即C语言是以源程序为单位进行编译 而不是以函数为单位进行编译 3 C程序的执行从main函数开始 main函数调用其他函数后流程返回到main函数 在main函数中结束整个程序的运行 4 所有函数在定义时都是相互独立的 一个函数并不从属于另一个函数 也就是说 在一个函数的函数体内 不能再定义另一个函数 即函数不能嵌套定义 函数间可以相互调用 但不能调用main函数 说明 C语言程序结构示意图 1 从函数定义的角度 函数分为两种 1 标准函数 即库函数 由系统提供 用户无须定义和说明 只需在程序前包含有该函数原型的头文件 就可在程序中直接使用 如scanf printf getchar putchar sqrt等都是标准函数 应该说明 不同的C系统提供的库函数的数量和功能不同 当然有一些基本的函数是共同的 2 用户自定义函数 用户根据需要 自己定义用以完成某种功能的函数 如例8 1中printa printb和printc等都是用户定义的函数 8 1 2C语言函数的分类 2 从函数参数的角度 函数分为两种 1 无参函数 函数定义 函数说明 函数调用均不带参数 主调函数和被调函数之间不进行参数传递 如例8 1中printa printb和printc三个都是无参函数 此类函数通常用来完成一组指定的功能 可以返回或不返回函数值 一般不返回函数值居多 2 有参函数 函数定义 函数说明时都有参数 称为形式参数 简称形参 函数调用时必须给出参数 称为实际参数 简称实参 进行调用时 主调函数单向地把实参的值传递给形参 供被调函数使用 被调函数可以通过返回函数值将结果带回供主调函数使用 8 1 2C语言函数的分类 3 函数分类的其他角度 1 根据函数是否返回值分成有返回值和无返回值两种 2 根据返回值的类型分成整型函数 实型函数 字符型函数 指针函数等等 8 1 3函数的定义 1 无参函数定义的一般形式类型说明符函数名 说明部分语句执行部分语句 其中类型说明符和函数名 称为函数头 类型说明符是指该函数值的类型 即函数返回值的类型 函数名是用户自己定义的标识符 函数名后面必须有一对空括号 花括号 中的内容称为函数体 由说明部分和执行部分语句序列组成 无参函数一般不需要有返回值 因此没有函数值的类型 此时应将函数值的类型定义为void 即空类型 以确定函数返回时不带回任何值 说明 例8 2 包含无参函数的C程序例 EX8 2 C voidmax 自定义一个无参函数 inta b c scanf d d 函数调用 例8 3 包含无参但有返回值函数的C程序例 EX8 3 C intmax1 自定义一个有返回值的无参函数 inta b c scanf d d 2 有参函数定义的一般形式 类型函数名 类型形参1 类型形参2 说明部分语句执行部分语句 8 1 3函数的定义 与无参函数的定义相比 有参函数的定义在函数头部分多了形式参数和形式参数类型说明的内容 形式参数通常简称为形参 形参可以是任何类型的变量 参数间用逗号分隔 在函数调用时 主调函数将通过实际参数赋予这些形式参数以实际值 说明 例8 4 包含有参函数的C程序示例 EX8 4 C intmax2 intx inty 自定义一个有参函数 intz z x y x y return z 函数返回值 main 主函数 inta b m scanf d d 例8 5 输入一个数n 打印n个 EX8 5 C voidprintstars intn 定义一个有参但无返回值的函数 inti for i 1 i n i printf printf n main 主函数 intm scanf d 函数调用 注意 1 如果在定义函数时没有指定函数类型 系统会隐含指定函数类型为int型 因此 int型函数定义时函数名左边的int可以省略不写 因此 例8 3 中函数max1和 例8 4 中函数max2定义时左边的int都可以省略不写 2 系统容许有返回值函数不返回函数值 空函数 定义的一般形式类型说明符函数名 例如 khs 主函数中也有调用语句 khs 但没有任何作用 空函数 没有执行作用 仅仅是取一个名字 一般是为今后程序中增加函数预留位置 3 空函数 在老版本C语言中 对形参类型的声明是放在函数定义的第2行 即不在第1行括号内指定形参的类型 而在括号外单独指定 其一般形式 类型说明符函数名 形参1 形参2 形式参数类型说明 说明部分语句执行部分语句 4 对形参声明的传统方式 例8 4 中max2函数定义可以写成 intmax2 x y 函数有两个形参x y intx y 说明形参x y的类型 intz z x y x y return z 通常把这种方法称为传统的对形参的声明方式 而把 例8 4 中定义函数的方法称为现代的方式 这两种用法是等价的 ANSI新标准推荐使用现代的方式 因为它便于编译系统检查形参的类型 现代方式与PASCAL语言中所用的方法类似 说明 8 2函数调用和返回值 1 函数调用的一般形式及调用流程 1 函数调用的一般形式为 函数名 实际参数表 说明 a 实际参数表中有多个实参用逗号分隔 b 实参与形参的个数应相同 类型应一一对应 c 如果调用无参函数 则实参表为空 但函数名后的一对园括号 不能省略 2 函数调用的流程为 a 为被调函数的局部变量 包括定义的变量和形参 开辟存储单元 b 将实际参数的值复制给形式参数 c 流程从主调函数的调用处转移到被调函数 执行被调函数体中的语句序列 d 当执行到被调函数的 return 表达式 语句时 将表达式的值返回到主调函数的调用处 即以该表达式的值作为函数值替换 函数名 实际参数表 结束被调函数的执行 如无return语句 则遇函数体的右花括号 也结束被调函数的执行 e 释放局部变量的存储单元 流程从被调函数转移到主调函数的调用处 f 继续从主调函数的调用处向下执行 函数调用流程如图所示 按函数调用出现的位置不同 可分为三种形式 1 形成单独的函数调用语句 形式为 函数名 实参表达式1 实参表达式2 这是无返回值函数被调用的情况 如 printstars m 2 出现在表达式中这是有返回值函数被调用的情况 以函数的返回值参与表达式的运算 例如 m 5 max2 a b 3 函数调用作为另一个函数调用的实际参数这种情况是把该函数的返回值作为实参进行传递 因此要求该函数必须带值返回 如 printf max d n max2 a b 中的max2 a b 2 函数调用的应用形式 函数调用实参一般是表达式 在有多个实参的情况下 C语言中实参的求值顺序是从右到左 例8 6 考察函数调用时实参求值的顺序 includemain intk 1 j j f k k 函数调用 printf d n j intf inta intb intc 1 if a b c 1 elseif a b c 0 return c 3 函数调用时实参求值的顺序 为什么运行结果不是 1呢 这是因为系统 TC和VC相同 对实参求值顺序是从右到左 即相当于f 2 2 所以程序运行结果为0 这种情况在printf函数中也同样存在 如 printf d d n k k 若k的原值为1 则在TC系统对实参求值顺序是从右到左 运行结果为2 1 但在VC系统对实参求值顺序是从左到右 运行结果为1 1 请读者务必注意 编程时应避免采用这种容易混淆的用法 分析 8 2 2函数值的返回函数值的返回是通过被调函数中的return语句实现的 return语句是C语言控制流程转向的语句 它可用在被调函数中 return语句的一般形式为 return 表达式 或return表达式 功能 计算表达式的值 并作为函数值返回给主调函数 在一个函数中允许有多个return语句 例如if语句的每个分支都有一个return语句 但每次调用只执行一个return语句 因此只能返回一个函数值 说明 1 return语句也可以不带表达式部分 如 return 它仅实现程序控制转移而不返回任何值 2 如果不需要从被调函数返回确定的函数值 被调函数可以没有return语句 这时当程序执行到函数体的右括号 时自动返回到主调函数中 3 函数返回值的类型和函数定义的类型应保持一致 如果两者不一致 以函数定义的类型为准 系统将自动进行类型的转换 4 如果函数的返回值是整型 则在函数定义时可省略类型说明 5 为了明确表示 不带回值 可将函数定义为 空类型 类型说明符为 void 这样 系统就保证函数不带回任何值 8 2 3对被调函数的声明 一个函数能被另一个函数调用需要以下几个条件 1 该函数必须已经存在 无论是库函数还是用户自定义函数 先定义后调用 2 当被调函数在主调函数之后定义时 一般应在主调函数的开始部分对被调函数作声明 3 调用库函数时 不需要对函数作声明 只需在源程序的开头用 include命令将相关的头文件包含进来 8 2 3对被调函数的声明 对被调函数的声明有两种形式 1 一种为现代形式 其一般形式为 类型说明符函数名 类型形参1 类型形参2 即是函数首部加 形成函数说明语句 也可以只给出形参类型 而不出现形参变量名 如 intmax int int 2 另一种为传统形式 其一般形式为 类型说明符函数名 解释 函数定义是指对函数功能的确立 包括指定函数名 函数值的类型 形参及其类型 函数体等 它是一个完整的 独立的函数单位 函数声明是指对已定义函数的返回值和形参进行类型说明 它只包括函数名 函数类型 形参类型等 不包括函数体 其作用是告诉编译系统 在本函数中将要调用的函数是何种类型 有何种类型的形参 以便系统作相应的检查和处理 有几种情况可以省略对被调函数的声明 1 如果被调函数的定义出现在主调函数之前 则可以省略声明 2 如果在所有函数定义之前 在函数的外部已作了某函数的声明 则在以后的各主调函数中 可以不必对该被调函数作声明 3 如果函数类型为整型 则在主调函数中可以不要声明 但使用这种方法时 系统无法对参数的类型做检查 因此在调用时若参数使用不当 编译时也不报错 为了程序的安全 建议都加以声明为好 说明 例8 7 对被调函数的声明 includemain inta b floataver intx inty 对被调用函数的声明 floatave scanf d d 程序运行时 若输入50 60 运行结果如图所示 8 3函数调用时的参数传递 函数的参数有形参和实参两种 形参出现在函数定义中的函数首部 是被调函数中的局部变量 只在被调函数内部起作用 离开该函数则无意义 实参出现在主调函数的调用语句中 其作用是把实参的值传递给被调函数的形参 从而实现主调函数向被调函数传递数据的功能 由于是传递的数据 实参可以是常量 有确定值的变量或表达式 就参数传递而言 函数调用有传值调用和传地址调用两种 在函数调用的参数传递中应注意以下两点 1 形参和实参在个数 类型及顺序上必须保持一致 否则会发生 类型不匹配 错误 2 在函数调用时 只能把实参的值 无论是数值还是地址值 传递给形参 而不能把形参的值传递给实参 因此 在函数调用的过程中 被调函数形参值的改变不会影响主调函数中实参的值 8 3 1函数的传值调用函数的传值调用是指在函数调用时将实参的数值传递给形参变量 整个调用过程直至调用结束 形参的值都不回传给实参 在实参也是变量的情况下 形参和实参各自在不同的函数中占用存储空间 在被调函数内部对形参的任何操作 其结果只能影响形参变量的值 而不会影响到实参变量的值 例8 8 分析下面程序的运行结果 intswap inta intb intt printf a d b d n a b t a a b b t printf a d b d n a b includemain intx 3 y 5 swap x y printf x d y d n x y 程序运行结果如图所示 swap函数的功能是交换两个参数的值 从上面运行结果可以看出 交换了两个形参变量a和b的值 而main函数中实参x和y的值没有交换 这是因为实参向形参的数据传递是单向的 因而形参值的改变不会影响实参的值 如图所示 分析 数组元素作实参的情况 数组元素的实质与普通变量相同 因此用数组元素作为函数的实参与普通变量作为函数实参一样 都是把值传递给形参 即实现单向的值传递 例8 9 分析下面程序的运行结果main inta 7 1 2 3 4 5 6 7 i for i 0 i 7 i printf 3d a i printf n for i 0 i 7 i fun a i 用数组元素实参 printf n for i 0 i 7 i printf 3d a i printf n fun inty 函数定义 printf 3d y 2 程序运行结果如图所示 地址传递是将数据的存储地址作为实参传递给形参 按这种方式传递时 形参的类型必须是指针变量或数组 实质也是指针变量 实参也只能是变量的地址 数组名 数组的首地址 或已存放地址值的指针变量 下面分别就指针 一维数组 二维数组和字符串指针作函数参数的情况进行讨论 8 3 2函数的传地址调用 1 指针作函数参数指针作函数参数情况如下 被调函数中的形参 指针变量主调函数中的实参 地址表达式 一般为变量的地址或取得变量地址的指针变量 这里假定为取得变量地址的指针变量 在这种情况下 传址调用以后 形参存放地址的改变不会使得实参所存地址改变 地址传递是单向的 1 传址调用地址传递的单向性分析 例8 10 传址调用地址传递的单向性 voidswap int p1 int p2 int p printf p1 d p2 d n p1 p2 p p1 p1 p2 p2 p 交换指针变量p1 p2的值 printf p1 d p2 d n p1 p2 main inti1 i2 pt1 程序运行结果如图所示 分析 本例swap函数的作用是交换形参指针变量p1和p2的值 先让主调函数指针变量pt1和pt2分别指向整型变量i1和i2 然后当i1 i2时让pt1和pt2作实参调用swap函数 试图通过swap函数交换pt1和pt2存放的地址值 最后按它们的指向输出 但是 由于传址调用地址传递的单向性 pt1和pt2存放的地址值并没有改变 传址调用的过程如下图所示 2 指针作函数参数的作用由于形参指针变量的指向操作可以引起它所指向的主调函数变量值的变化 若有多个指针变量形参 它们分别指向主调函数中作为存放运算结果的变量 则可以将被调函数中的多个计算结果数据传回主调函数 注意以前被调函数只能通过函数值传回一个运算结果 例8 11 分析以下程序的运行结果 intast intx inty int cp int dp cp x y dp x y main inta 4 b 3 c d ast a b 程序的运行结果如图所示 2 数组作为函数参数表8 1数组指针作为函数参数的形式 数组指针作为函数参数的作用 形参指针变量接受主调函数数组的首地址 就可以通过指针变量的指向操作改变主调函数数组元素的值 例8 12 编一函数sort 其功能是将一维整型数组按从小到大的顺序排序 主函数输入10个整数 调用sort函数对输入的数据进行排序 并在主函数中输出 分析 要编一个函数对主函数中的数组进行排序 还要将排序后的数据传递到主函数中 因此形参不能用普通变量 而要使用指针变量 实参用数组名 形参指针变量得到主函数中数组的首地址 通过指向操作就可以改变数组所有元素的值 从而实现对主函数中数组的排序 程序如下 EX8 12 C includemain voidsort int x intn 函数声明 inti a 10 for i 0 i x j x i 即a i x i 也可写成x i t x i x i x j x j t 程序运行时输入 2 8 3 5 14 22 19 7 10 1 运行结果如图所示 可见用形参指针的指向操作 实现了主函数中数组的排序 说明 1 形参也可以用数组 即sort函数的首部可写成 voidsort intx intn 2 由于形参x数组和主函数的a数组都是局部数组 只在各自所在的函数起作用 因此 形参数组也可以与主函数中的数组同名 sort函数的首部可写成 voidsort inta intn 增加了可读性 其实 这两个数组的类型相同 首地址相同 从存储的角度说 实质是同一个数组 共同拥有一段存储空间 3 x数组的元素个数没有写 当然可以写 因为形参数组的实质是指针变量 目的是接受实参数组名首地址 与元素个数无关 所以一般不写元素个数 而由形参n接受实参的整数 此例为10 作为元素个数 另外一种传送元素个数的办法是将元素个数定义成符号常量 请看 例8 13 例8 13 输入N 设为10 个整数 将其中的全部奇数输出 要求输入 输出均调用函数进行 程序如下 defineN10voidinput inta inti printf 输入10个数 n for i 0 i N i scanf d a i voidoutputodd int a inti for i 0 i N i a if a 2 printf 3d a printf n main intarr N input arr outputodd arr 本例程序有三个函数 操作的都是同一个数组 即main函数的arr数组 用户定义的两个函数分别完成数组的输入和寻找奇数元素输出的任务 main函数调用这两个函数 元素个数由符号常量N定义 运行程序 若输入1 2 3 4 5 6 7 7 9 10 程序运行结果如图所示 讨论 在数组作为函数参数的情况下 参数传递的单向性是否被破坏 例8 14 分析下面程序的运行结果 voidfun inta 2 intc c a 0 a 0 a 1 a 1 c 交换了a 0 与a 1 的值 main intx 2 5 10 printf x 0 d x 1 d n x 0 x 1 fun x 函数调用 printf x 0 d x 1 d n x 0 x 1 程序运行结果如图所示 分析 函数fun的功能是交换数组中两个元素的值 程序运行结果实现了此功能 实参x数组发生了改变 参数传递的单向性是否被破坏 对这个问题首先要问 形参和实参分别是什么 在函数调用以后形参和实参是否发生了变化 下面的列表回答这个问题 那么本例中什么发生了改变呢 其实发生改变的是 以形参和实参为首地址的两个整型存储单元存放的值 而不是形参和实参本身 由于形参是指针变量 其存放的值是可以改变的 例如 例8 13 中函数outputodd的形参a通过a 运算存放的地址值发生了改变 实参数组名arr仍然是常量没有改变 因此 在数组作为函数参数的情况下 参数传递仍然是单向的 形参的变化不能引起实参的改变 改变的是 从函数调用时形参和实参共同拥有的数组首地址开始的数组元素值 这正是使用数组作为函数参数的目的 3 二维数组函数参数前面讨论数组作为函数参数 讲的都是一维数组 这样的讨论可以很自然地推广到多维数组的情况 多维数组作函数的参数 表8 1仍然适用 函数形参既可以用数组定义也可以用指针变量 实质都是指针变量 可以用与数组维数一致的高级指针变量 例如二维数组用指向一维数组的指针变量作形参 也可以用一级指针变量作形参 这就是将二维数组当成一维数组访问 例8 15 利用函数求5 5二维数组的最小值以及该最小值的位置 结果在main函数中输出 分析 必须用数组作为函数参数 二维数组的最小值可以用函数值返回 最小值的位置用什么返回呢 可以用指针变量形参 数组的行数和列数可以用数值形参 也可以用符号常量传递 本例采用后者 程序编写如下 defineM5main inta M M 2 3 1 4 6 9 0 2 7 7 4 1 0 4 6 3 4 7 9 0 6 5 3 7 4 intamin mrow mcol intmin int p int pi int pj 函数声明 amin min a 程序运行结果如图所示 讨论 二维数组形参也可用数组定义形式 本例函数首部可写作 intmin inta 5 5 int pi int pj 或intmin inta 5 int pi int pj 两种定义都是合法的 但列数不能省略 如 intmin inta int pi int pj 是不合法的 原因是编译器无法计算元素的下标值 同样 函数首部写成 intmin int a int pi int pj 也是错误的 因为它把a定义成普通的整型二级指针变量 而不是指向整型一维数组的指针变量 二维数组问题也可用一维数组形参 即将二维数组当成一维数组访问 对应的实参用一级指针首地址a 0 本例的程序可改写如下 EX8 15 1 C defineM5main inta M M 2 3 1 4 6 9 0 2 7 7 4 1 0 4 6 3 4 7 9 0 6 5 3 7 4 intamin mrow mcol intmin int p int pi int pj 函数声明 amin min a 0 intmin int p int pi int pj 函数定义 inti j mn mn p p a 0 p a 0 0 for i 0 i M i for j 0 j M j if p i M j mn mn p i M j pi i pj j return mn 程序运行的结果相同 4 字符串指针作函数参数表8 2字符串指针作为函数参数的形式 例8 16 编写函数cpystr 用指针方法将字符串2复制到字符串1 主函数调用cpystr实现复制 includevoidcpystr char s1 char s2 while s1 s2 0 main charstr1 20 str2 20 printf 输入字符串2 n gets str2 cpystr str1 str2 printf 字符串1是 s n str1 程序运行结果如图所示 注意 如果要求将一个确定的字符串 例如 CLanguage 复制到字符数组1中 上面的主函数可直接调用cpystr函数 实参就用该字符串 main charstr1 20 cpystr str1 CLanguage puts str1 8 4 1函数的嵌套调用C语言中函数的定义都是相互平行 相互独立的 也就是说在函数定义时 函数体内不能包含另一个函数的定义 即函数不能嵌套定义 但可以嵌套调用 例8 17 定义一个求阶乘的函数mul实现1 2 n 定义一个求和函数sum实现a1 a2 am 调用这两个函数 计算s 1 2 3 10 程序如下 8 4函数的嵌套与递归调用 main doubles sum 此处sum 为sum函数声明 s sum 10 调用sum函数 printf s 0f n s doublesum intm time函数定义 doubles 0 mul 此处mul 为mul函数声明 inti for i 1 i m i s s mul i mul函数调用 returns doublemul intn mul函数定义 doublet 1 j for j 1 j n j t t j returnt 该程序中 主函数调用了sum函数 而在sum函数中又调用了mul函数 在一个函数被调用的过程中又调用另一个函数 这就是函数的嵌套调用 本例函数嵌套调用的执行过程如图所示 8 4 2函数的递归调用在函数的调用过程中直接或间接地调用自己 这就是函数的递归调用 直接递归 函数不断直接调用自己 间接递归 函数循环间接调用自己 由于主函数不被别的函数调用 自己调用自己的函数一定是用户自定义函数 因此 用递归算法设计的程序至少有两个函数 那就是主函数加一个递归函数 在递归调用中 主调函数又是被调函数 整个递归过程就是递归函数不断自我调用的过程 但是 递归过程不能无限制进行下去 必须有一个结束递归过程的条件 总结以上 要编写递归函数必须有以下两个条件 1 必须有递归调用的形式 即形式上要能自己调用自己 2 必须有终止递归调用的条件 利用函数的递归调用解决问题的优点 1 递归调用的算法自然 容易理解 2 采用递归算法的程序简单 3 可以解决用其他方法无法解决的问题 如汉洛塔问题 例8 18 用递归算法求n 1 2 3 n 分析 n 1 2 3 n 1 2 3 n 1 n n n 1 也就是说 要求n 可先求 n 1 同样 要求 n 1 先求 n 2 要求2 先求1 而1 1 0 1 总结起来可以写出如下计算公式 第一行是递归的终止条件 第二行是递归调用的形式 据此可编写程序如下 longfact intn 定义递归函数 if n 0 n 1 return1 递归终止条件 elsereturn n fact n 1 实现递归调用 main intm longf do printf 输入m m 0 scanf d 程序运行结果如图所示 该程序中 主函数调用了fact函数 而在fact函数中又调用了自身fact函数 程序执行流程如图所示 从图中可以看出 fact函数每自我调用一次就进入新的一层 递归函数和变量都在内存新的空间开辟 例8 19 用递归算法求两个自然数的最大公约数和最小公倍数 分析 利用辗转相除法求自然数的最大公约数是迭代算法 显然可以用递归算法求解 其递归结束的条件是 被除数除以除数的余数为0 最小公倍数等于两个自然数的乘积除以最大公约数 程序编写如下 main intgcd inta intb intx y g lcd printf 请输入两个数 scanf d d 实现递归调用 程序运行结果如图所示 例8 20 根据下式用递归算法求 的近似值 设n 5000 分析 设级数和为s n 根据上式有 这就包含了递归结束条件和递归调用的形式 程序编写如下 main intm doubles intn printf 请输入n scanf d 程序运行结果如图所示 8 5函数与指针 函数与指针的关系有以下三个方面的问题 1 指针作为函数参数 传址调用 已在8 3 2节介绍 2 指向函数的指针 本节介绍 3 返回指针的函数 本节介绍 8 5 1指向函数的指针函数作为程序实体 在程序执行以前 其代码也要进入内存 占据内存的一段连续存储区域 因此也有内存地址 函数在内存一段连续的存储区域的首字节编号叫函数的入口地址 又叫函数指针 在C语言中 函数指针用函数名表示 它是一个指针常量 C语言可以通过定义指向函数的指针变量接受函数指针 然后通过指向函数的指针变量访问该函数 间接访问 1 用指向函数的指针变量调用函数指向函数的指针变量的定义形式为 函数返回值的类型 指针变量名 注意 上面的定义中第一个园括号不能少 否则就会变成8 5 2节将要介绍的返回指针函数的定义 指向函数的指针变量在接受某一函数的入口地址以后 即可用来调用该函数 无其他运算 用指向函数的指针变量调用函数的方法是 1 定义指向函数的指针变量 2 给指针变量赋函数入口地址 函数名 3 用指向函数的指针变量调用该函数 形式为 指针变量 实参列表 例8 21 用指向函数的指针变量调用求两个数中最大值的函数 程序如下 intmaxnum inta intb return a b a b main intx y max int funp 定义指向函数的指针变量 函数返回整型值 funp maxnum 指向函数的指针变量得到函数的入口地址 printf 输入两个数 n scanf d d 程序运行结果如图所示 指向函数的指针变量的主要用途 1 有选择地执行函数 例如 利用switch语句结构 让指向函数的指针变量根据输入的开关表达式的值 如1 2 5之一 得到不同函数 如f1 f2 f5之一 的入口地址 去调用相应的函数 实现人机对话 这就是菜单功能 读者可按此思路编写菜单程序 此处从略 2 多次调用同类型的一些函数做相同的工作 例如求若干个一元函数的定积分 那就需要用指向函数的指针变量作函数参数 请看后面的例子 2 指向函数的指针变量作函数参数以用梯形法求定积分为例 对不同的被积函数来说 求定积分的算法都是一样的 如果设计一个函数可以求任意函数的定积分 这就是一个工作函数 实现了代码的重复使用 提高了编程效率 调用工作函数计算不同的被积函数的定积分 就要在工作函数中设置一个指向函数的指针变量形参 调用工作函数时对应的实参为要积分的被积函数名 再加上相应的积分上下限参数即可 在对多个被积函数积分时 工作函数的首部形式为 floatintegral double funp floata floatb 其中funp为指向函数的指针变量形参 形参a和b为积分的下限和上限 用梯形法计算定积分的算法如图8 26所示 其中梯形高h b a n n为等份数 n越大积分越准确 积分近似值 曲边梯形面积和为 s f a f a h f a h f a 2h f a n 1 h f a nh h 2 f a f b 2 f a h f a 2h f a n 1 h h 例8 22 利用梯形法计算定积分sinxdx cosxdx include math h floatintegral double funp floata floatb floats h y 定义工作函数 intn i s funp a funp b 2 0 f a f b 2作为求和的初值 n 100 h b a n for i 1 i n i s s funp a i h y s h return y doublef doublex 自定义被积函数 return sqrt 4 0 x x main floats1 s2 s3 s1 integral sin 0 0 3 1415926 2 sin为系统库函数sin x 的入口地址 s2 integral cos 0 0 3 1415926 2 s3 integral f 0 0 2 0 printf s1 f s2 f s3 f n s1 s2 s3 8 5 2返回指针的函数有时希望通过函数返回一个地址值 这时可定义一个返回指针的函数 其形式为 类型 函数名 类型形参1 类型形参2 以下为函数体 函数名前面的 表示该函数是返回指针的函数 类型 是函数返回地址值的基类型 即返回指针所指向的数据类型 注意此处的返回指针的函数与8 3 1节定义的指向函数的指针变量不同 指向函数的指针变量的定义形式为 类型 p 在没有形参的情况下 返回指针的函数定义为 类型 p 函数体 区别 1 形式不同 2 实质不同 前者 p 是指针变量名 后者 p 是函数名 3 后者除了函数首部外还有函数体 注意 在调用返回指针的函数时必须将返回值给指针变量赋值 该指针变量的基类型必须与该函数返回地址值的基类型相同 例8 23 输入一个1 7之间的整数 输出对应的星期名 函数确定整数与星期名的对应关系 主函数输入和输出 分析 由函数确定整数与星期名的对应关系 应返回星期名单词 由于单词是字符串 由首地址确定 因此函数返回地址值 程序编写如下 includechar day name intn char name Illegalday Monday Tuesday Wednesday Thursday Friday Saturday Sunday return n7 name 0 name n 将指针数组元素存放的地址值返回 main inti printf InputDayNo n scanf d day name的返回值决定输出字符串 运行结果如图所示 8 5 3带参数的主函数main函数不被其他函数调用 不需要从其他函数接受数据 因此main函数不需要形式参数 C程序编译连接形成可执行程序 EXE文件 后 要在DOS提示符的状态 即命令行 下运行 此时可以接受命令行传来的数据 因此 C语言规定main函数也可以有形式参数 而实参就是命令行的字符串 包含形式参数的main函数的一般形式为 main intargc char argv 以下为函数体 说明 1 字符型指针数组argv的元素指向命令行输入的若干字符串 这些字符串以空格隔开 形如 C 可执行文件名参数1参数2 参数n 可执行文件名字符串由argv 0 指向 后面若干字符串 称为命令行参数 分别由argv 1 argv 2 指向 通过这些指针 main函数可以引用这些字符串 2 整型变量argc记载命令行字符串的个数 也就是指针数组argv的元素个数 3 注意 与一般指针数组名不同 由于指针数组argv 是形参 其名是二级指针变量而不是常量 可以有argv argv 等运算 4 参数名可以不用 argc 和 argv 但是它们的类型和作用不变 位置也不能颠倒 例8 24 编程显示命令行输入的 除可执行文件名外的全部字符串 每行一个字符串 程序如下 EX8 24 C includemain intargc char argv while argc 1 printf s n argv 设程序在VC 6 0下编译连接后形成可执行文件EX8 24 EXE TurboC下编译连接后形成可执行文件名为EX8 24 EXE 然后在命令行 EX8 24 EXE的当前目录下 输入 EX8 24CLanguageProgram 程序运行结果如图所示 分析 命令行共输入四个字符串 因此argc 4 第一次执行while循环体时argc 3 argv argv 0 argv使得argv argv 1 argv即argv 1 它指向第二个字符串 故输出 C 以后每次循环argc减少1 argv向高端移动1 由argc 1的条件共循环3次 输出3个字符串 注意可执行文件名不能用DOS的内部命令名 否则DOS执行的是内部命令 而不是可执行文件 例如不能取echo exe 因为如果命令行输入 echoCLanguageProgram 屏幕将显示一行 CLanguageProgram而不是每个字符串一行 原因是上面的命令行执行的是DOS的内部命令echo 它的作用就是在下一行显示命令行输入的echo之后的全部字符 8 6变量的作用域和存储属性 8 6 1变量的作用域变量的作用域是指变量的有效范围 在该范围里 变量是可用的 例如函数的形参变量只能在该函数体内有效 离开该函数就不能再用了 C语言中 变量的说明方式不同 其作用域也不同 从作用范围分为局部变量和全局变量两类 在函数内部定义的变量 形参和复合语句内定义的变量称为局部变量 其作用域仅限于函数内或复合语句内 离开该函数或该复合语句再使用这些变量是非法的 因此在不同的函数内及复合语句内可以定义同名的局部变量 这些变量之间不会发生冲突 编译系统开始并不给局部变量分配内存 只在程序运行过程中 当局部变量所在的函数被调用时 才临时分配内存 调用结束立即释放 1 局部变量 例8 25 局部变量的作用范围 main voidfun 函数声明 intx 5 printf main 1 x d n x fun 函数调用 printf main 2 x d n x voidfun 函数定义 intx x 10 printf fun x d n x 程序运行结果如图所示 分析 函数fun内定义的变量x只在fun里有效 主函数内定义的变量x同样只在主函数里有效 他们之间并不相互干扰 因此在调用fun函数前主函数内输出的x值是5 而调用fun函数并进入fun函数时 fun函数里的x起作用 所以在fun函数体里输出的x值是10 而调用结束返回主函数时 fun函数里的变量x无效 主函数里的x起作用 因此输出的x值是5 例8 26 复合语句中的局部变量 EX8 26 C main intt 10 复合语句开始 intt 20 printf in d n t 复合语句结束 printf out d n t 程序运行结果如图所示 在所有函数 包括main函数 之外定义的变量称为全局变量 又叫外部变量 它不属于任何函数 而属于所在的源程序文件 其作用域是从定义的位置开始到本源程序文件结束 并且默认初值为0 如果要在定义之前使用该全局变量 要用extern加以说明 则可扩展全局变量的作用域 视说明语句的位置而定 2 全局变量 注意 外部变量的定义和说明 声明 不同 1 外部变量的定义必须在所有函数之外 且一个程序只能定义一次 其一般形式为 类型变量名1 初值 变量名n 初值 外部变量的说明出现在要使用该外部变量的地方 外部变量的作用域达不到的地方 可以是函数内部 对一个外部变量可多处说明 外部变量说明的一般形式为 extern类型变量名1 变量名n 2 外部变量在编译时就分配内存空间 在定义的同时可以赋初值 不赋初值系统自动赋给0值 而外部变量的说明只是表明在该外部变量作用域达不到的地方要使用外部变量 不能在说明语句中赋初值 说明 在一个源程序中 全局变量和局部变量可以同名 在局部变量有效的范围内 全局变量不起作用 被屏蔽 例8 27 写出下面程序运行结果inta 5 外部变量定义 main voidfunn 函数声明 inta 10 局部变量定义 printf 1 a d n a 复合语句开始 externinta 外部变量的声明 printf 2 a d n a 此时局部变量a无效 引用外部变量a 复合语句结束 funn 函数调用 voidfunn 函数定义 printf 3 a d n a 引用外部变量a 思考 1 如果删除复合语句中的外部变量说明语句externinta 则输出结果有什么改变 2 如果将复合语句中的外部变量说明语句externinta 改成inta 20 则输出结果又会怎样改变 8 6 2变量的存储属性 变量的存储属性是由它的存储方式决定的 变量的存储方式是指变量使用内存空间的方式 通常可分为 动态存储 和 静态存储 两种 内存中供用户使用的存储空间分为 程序区 静态存储区和动态存储区 动态存储是指在程序执行过程中 使用到变量时才分配存储单元 使用完毕立即释放 函数所有的局部变量都属于这种存储方式 静态存储是指在变量编译时就分配存储单元 程序开始直至整个程序结束一直保持 例如全局变量就属于这种存储方式 生存期表示了变量存在的时间 静态存储变量是一直存在 与程序共存亡 的 而动态存储变量则在函数被调用的过程中存在 调用结束返回时即消失 因此他们的生存期不同 生存期和作用域是从时间和空间两个不同的方面描述变量的特性 两者既有关联 又有区别 变量的存储类型决定了变量的生存期和作用域 8 6 2变量的存储属性 C语言中 变量的存储类型有4种 1 自动类型auto 2 寄存器类型register 3 静态类型static 4 外部类型extern不同的存储类型 存放的位置不同 auto类型存储在内存的堆栈区中 register类型存储在CPU的通用寄存器中 static extern类型存储在内存数据区中 extern类型用于多个源文件之间数据的传递 8 6 2变量的存储属性 表8 3变量的存储类及作用域表 考虑存储类 变量定义的形式为 存储类型数据类型变量名 初值 变量名 初值 8 6 2 1动态存储变量函数 含main函数 以及复合语句中定义的变量 函数的形参属于动态存储的变量 其特性是 仅当定义动态存储变量的函数被调用时 系统才给他们分配存储单元 不赋初值其初值是随机的 调用结束 空间释放 自动变量消失 动态存储变量为局部变量 只在定义的函数或复合语句中起作用 动态存储变量包括自动变量和寄存器变量两种 1 自动类型自动变量存放在内存的动态区 其定义形式为 auto 类型变量名 初值 变量名 初值 其中的auto可以省略 如定义自动变量 autointa 2 也可以写成 inta 2 以前所用的变量 包括函数和复合语句中定义的变量以及函数的形参都是自动变量 2 寄存器类型寄存器类型变量定义的一般形式为 register数据类型变量名 初值 变量名 初值 寄存器类型也属于动态存储方式 其生存期和作用域与自动类型变量相同 只不过系统把这类变量直接分配在CPU的通用寄存器中 因而无地址 不能用取地址运算符 作用 当需要使用这些变量时 无须访问内存 直接从寄存器中读写 提高了效率 一般寄存器变量只能是int char或指针型 当CPU无法分配寄存器时 编译系统会自动地将寄存器变量变为自动变量 一般把使用频繁的变量 如循环变量 设置成寄存器变量 例8 28 寄存器变量的使用 EX8 28 C main registerintk s 0 for k 1 k 10 k s k printf s d n s 程序运行结果如图所示 3 动态存储变量有如下共同的特性 1 生存期 仅当定义动态存储变量的函数被调用时 系统才给动态存储变量分配内存或寄

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论