




已阅读5页,还剩54页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1 第2章函数和作用域 2 1函数定义和调用2 2C 函数特性2 3作用域和存储类型2 4名称空间 2 2 1函数定义和调用 2 1 1函数定义C 的任何一个程序都可由一个主函数和若干个子函数组合而成 主函数可以调用子函数 子函数还可以调用其他子函数 C 规定主函数名必须是main 而其他函数可以是库函数或自定义函数 1 主函数main不仅是程序的入口函数 而且与其他函数相比较还有许多使用上的限制 例如 它不能被其他函数调用 不能用inline和static来说明等 2 库函数 又称标准函数 是ANSI ISOC 编译系统已经预先定义好的函数 程序设计时可根据实际需要 直接使用这类函数 而不必重新定义 调用时 必须在程序中包含相应的头文件 并指明使用名称空间std 3 2 1函数定义和调用 3 自定义函数是用户根据程序的需要 将某一个功能相对独立的程序定义成的一个函数 或将解决某个问题的算法用一个函数来组织 与变量的使用规则相同 在C 程序中一定要先说明和定义函数 然后才能调用函数 C 中每一个函数的定义都是由4个部分组成的 即函数名 函数类型 形式参数表和函数体 其定义的格式如下 4 2 1 1函数定义 其中 函数名应是一个合法有效的C 标识符 函数头的形式参数又简称为形参 参数表中的每一个形参都是由形参的数据类型和形参名来构成 根据上述定义格式 可以编写一个函数sum 如图2 1所示 注意它们的书写规范 图2 1定义一个函数sum 5 2 1 1函数定义 需要说明的是 1 C C 不允许在一个函数体中再定义函数 即禁止嵌套定义 但允许嵌套调用 2 函数体也可不含有任何语句 这样的函数称为空函数 它仅为程序结构而设定 本身没有任何操作 3 函数类型决定了函数所需要的返回值类型 它可以是除数组类型之外的任何有效的C 数据类型 包括引用 指针等 6 2 1 1函数定义 4 若函数类型为void时 则表示该函数没有返回值 但仍然可以在函数体中使用return语句 return 此时可将 return 语句理解为是函数体花括号 的作用 当流程遇到函数体的 时 函数调用结束 控制权返回给主调函数 例如 voidf1 inta if a 10 return return 一旦执行 后面的语句不再被执行 当a 10条件满足时 return 语句将控制权返回给主调函数 7 2 1 2函数的调用和声明 1 函数的实参和形参定义一个函数就是为了以后的调用 调用函数时 先写函数名 然后紧跟括号 括号里是实际调用该函数时所给定的参数 称为实际参数 简称实参 并与形参相对应 要注意形参和实参的区别 1 从模块概念来说 形参是函数的接口 是存在于函数内部的变量 而实参是存在于函数外部的变量 它们不是同一个实体 也就是说 形参变量和实参变量所对应的内存空间不是同一个内存空间 2 按函数定义时所指定的形参类型 实参除变量外还可以是数值或表达式等 而形参只能是变量 3 形参在函数调用之前是不存在的 只有在发生函数调用时 函数中的形参才会被分配内存空间 然后执行函数体中的语句 而当调用结束后 形参所占的内存空间又会被释放 8 2 1 2函数的调用和声明 2 函数的调用函数调用的一般格式为 调用函数时要注意 实参与形参的个数应相等 类型应一致 且按顺序对应 一一传递数据 例如 下面的示例用来输出一个三角形的图案 9 2 1 2函数的调用和声明 例Ex Call 函数的调用 includeusingnamespacestd voidprintline charch intn for inti 0 i n i cout ch cout endl intmain introw 5 for inti 0 i row i printline i 1 Areturn0 10 2 1 2函数的调用和声明 程序运行的结果如下 代码中 main函数的for循环语句共调用了5次printline函数 A句 每次调用时因实参i 1值不断改变 从而使函数printline打印出来的星号个数也随之改变 11 2 1 2函数的调用和声明 3 函数的声明由于前面函数printline的定义代码是放在main函数中调用语句A之前 因而A语句执行不会有问题 但若将函数printline的定义代码放在调用语句A之后 即函数定义在后 而调用在前 就会产生 printline标识符未定义 的编译错误 此时必须在调用前进行函数声明 12 2 1 2函数的声明 声明一个函数按下列格式进行 可见 函数声明的格式是在函数头的后面加上分号 但要注意 函数声明的内容应和函数的定义应相同 例如 对于前面sum函数和最后一个printline函数可有如下声明 intsum intx inty voidprintline charch intn 13 2 1 3值传递 函数的调用实质上就是参数传递 在C 中 函数的参数传递有两种方式 一是按值传递 二是地址传递或引用传递 这里先来说明按值传递的参数传递方法 地址传递或引用传递在以后来讨论 当函数的形参定义成一般变量时 如前面printline和sum函数的形参都是一般变量 此时函数的参数传递就是按值传递方式 简称值传递 是指当一个函数被调用时 C 根据实参和形参的对应关系将实际参数的值一一传递给形参 供函数执行时使用 14 2 1 3值传递 值传递的特点是 1 若实参指定是一般变量 则传递的是实参变量的值而不是实参变量的地址 2 在执行函数代码时 由于对实参数据的操作最终是在形参的内存空间中进行 因此形参值的改变只是改变了形参的内存空间存储的值 而不会改变实参变量所对应的内存空间的值 也就是说 即使形参的值在函数中发生了变化 函数调用结束后 实参的值不会受到影响 例如 15 2 1 3值传递 例Ex SwapValue 交换函数两个参数的值 includeusingnamespacestd voidswap floatx floaty 函数原型说明intmain floata 20 b 40 cout a a b b n swap a b 函数调用cout a a b b n return0 voidswap floatx floaty 函数定义 floattemp temp x x y y temp cout x x y y n 16 2 1 3值传递 程序的运行结果为 可以看出 虽然函数swap中交换了两个形参x和y的值 但交换的结果并不能改变实参的值 所以调用该函数后 变量a和b的值仍然为原来的值 17 2 1 4函数的默认形参值 在C 中 允许在函数的声明或定义时给一个或多个参数指定默认值 这样在调用时 可以不给出实际参数 而按指定的默认值进行工作 例如 voiddelay intloops 1000 函数定义 1000为形参loops的默认值 if 0 loops return for inti 0 i loops i 空循环 起延时作用 18 2 1 4函数的默认形参值 这样 当有调用delay 和delay 1000 等效程序就会自动将loops当作成1000的默认值来进行处理 当然 也可在函数调用时指定相应的实际的参数值 例如 delay 2000 形参loops的值为2000 19 2 1 4函数的默认形参值 在设置函数的默认形参值时要注意 1 当函数既有原型声明又有定义时 默认参数只能在原型声明中指定 而不能在函数定义中指定 例如 voiddelay intloops 函数原型声明 voiddelay intloops 1000 错误 此时不能函数定义中指定默认参数 20 2 1 4函数的默认形参值 2 当一个函数中需要有多个默认参数时 则形参分布中 默认参数应严格从右到左逐次定义和指定 中间不能跳开 例如 voiddisplay inta intb intc 3 合法voiddisplay inta intb 2 intc 3 合法voiddisplay inta 1 intb 2 intc 3 合法 可以对所有的参数设置默认值voiddisplay inta intb 2 intc 错误 默认参数应从最右边开始voiddisplay inta 1 intb 2 intc 错误 默认参数应从最右边开始voiddisplay inta 1 intb intc 3 错误 多个默认参数中间不能有非默认参数 21 2 1 4函数的默认形参值 3 当带有默认参数的函数调用时 系统按从左到右的顺序将实参与形参结合 当实参的数目不足时 系统将按同样的顺序用声明或定义中的默认值来补齐所缺少的参数 例如 22 2 1 4函数的默认形参值 例Ex Default 在函数定义中设置多个默认参数 includeusingnamespacestd voiddisplay inta intb 2 intc 3 在函数定义中设置默认参数 cout a a b b c c n intmain display 1 display 1 5 display 1 7 9 return0 程序的运行结果为 23 2 2C 函数特性 在C 中 函数还有 嵌套调用 重载 内联调用以及递归调用等特性 相应的函数被称为嵌套函数 重载函数 内联函数和递归调用等 24 2 2C 函数特性 函数重载 overloaded 是C 对C的扩展 它允许多个同名的函数存在 但同名的各个函数的形参必须有区别 要么形参的个数不同 要么形参的个数相同 但参数类型有所不同 优点 代码中使用函数的重载 不仅方便函数名的记忆 而且更主要的是完善了同一个函数的代码功能 给调用带来了许多方便 下例程序中即是各种形式的sum函数都称为sum的重载函数 25 2 2C 函数特性 例Ex OverLoad 编程求两个或三个操作数之和 includeintsum intx inty intsum intx inty intz doublesum doublex doubley doublesum doublex doubley doublez 声明4个同名的函数intmain cout sum 2 5 endl 结果为7cout sum 2 5 7 endl 结果为14cout sum 1 2 5 0 7 5 endl 结果为13 7return0 程序的运行结果为 26 2 2C 函数特性 intsum intx inty returnx y intsum intx inty intz returnx y z doublesum doublex doubley returnx y doublesum doublex doubley doublez returnx y z 程序运行结果如下 27 2 2C 函数特性 需要说明的是 1 重载函数必须具有不同的参数个数或不同的参数类型 若只有返回值的类型不同是不行的 例如 voidfun inta intb intfun inta intb 是错误的 因为如果有函数调用fun 2 3 时 编译器无法准确地确定应调用哪一个函数 2 当函数的重载带有默认参数时 也要应该注意避免上述的二义性情况 例如 intfun inta intb 0 intfun inta 是错误的 因为如果有函数调用fun 2 时 编译器也是无法准确地确定应调用哪一个函数 28 2 2 2函数嵌套调用 C 允许在函数中再调用其他函数 这种调用称为函数的嵌套调用 例Ex Root 函数嵌套调用 求解一元二次方程的根 见书35页 本例main函数中调用了root函数 root函数中又调用了sdelta和print函数 而sdelta函数还调用了cmath头文件定义的库函数sqrt 求平方根 和fabs 求浮点数的绝对值 它们的调用关系如图2 2所示 图2 2例Ex Root中的各函数调用的关系 29 2 2 3递归函数 C 允许在调用一个函数的过程中出现直接地或间接地调用函数本身 这种情况称为函数的递归调用 递归 Recursion 是一种常用的程序方法 算法 相应的函数称为递归函数例如 用递归函数编程求n的阶乘n n n n 1 n 2 2 1 它也可用下式表示 由于n 和 n 1 都是同一个问题的求解 因此可将n 用递归函数longfactorial intn 来描述 程序代码如下 30 2 2 3递归函数 例Ex Factorial 编程求n的阶乘n includeusingnamespacestd longfactorial intn intmain cout factorial 4 endl 结果为24return0 longfactorial intn longresult 0 if 0 n result 1 elseresult n factorial n 1 进行自身调用returnresult 31 1 5 4函数的递归调用 程序运行结果如下 24下面来分析main函数中 factorial 4 语句的执行过程 这一过程用图1 8来表示 见书 p36 32 2 2 4内联函数 在程序的执行过程中 调用函数时首先需要保存主调函数的现场和返回地址 然后程序转移到被调函数的起始地址继续执行 被调函数执行结束后 先恢复主调函数的现场 取出返回地址 并将返回值赋给函数调用本身 最后在返回地址处开始继续执行 当函数体比较小且执行的功能比较简单时 这种函数调用方式的系统开销相对较大 33 2 2 4内联函数 为了解决这一问题 C 引入了内联函数的概念 它把函数体的代码直接插入到调用处 将调用函数的方式改为顺序执行 直接插入的程序代码 这样可以减少程序的执行时间 但同时增加了代码的实际长度 内联函数的使用方法与一般函数相同 只是在内联函数定义时 需在函数的类型前面加上inline关键字 例如 34 2 2 4内联函数 例Ex Inline 用内联函数实现求两个实数的最大值 includeusingnamespacestd inlinefloatfmax floatx floaty returnx y x y intmain floata a fmax 5 10 Acout10 5 10 35 2 2 4内联函数 在使用内联函数时 还需要注意的是 1 内联函数也要遵循定义在前 调用在后的原则 形参与实参之间的关系与一般函数相同 2 关键字inline必须放在函数定义体前才是内联函数 仅在函数声明时使用inline 不能定义内联函数 3 在C 中 需要定义成的内联函数不能含有循环 switch和复杂嵌套的if语句 4 递归函数不能被用来做内联函数 总之 内联函数一般是比较小的 经常被调用的 大多可在一行写完的函数 36 2 3作用域和存储类型 2 3 1作用域作用域又称作用范围 是指程序中标识符 变量名 函数名 数组名 类名 对象名等 的有效范围 一个标识符是否可以被引用 称之为标识符的可见性 在一个C 程序项目中 一个标识符只能在声明或定义它的范围内可见 在此之外是不可见的 根据标识符的作用范围 可将其作用域分为5种 函数原型作用域 函数作用域 块作用域 类作用域和文件作用域 其中 类作用域将在以后介绍 这里介绍其他几种 37 2 3作用域和存储类型 1 函数原型作用域函数原型作用域指的是在声明函数原型所指定的参数标识符的作用范围 这个作用范围是在函数原型声明中的左 右圆括号之间 正因为如此 在函数原型中声明的标识符可以与函数定义中说明的标识符名称不同 由于所声明的标识符与该函数的定义及调用无关 所以可以在函数原型声明中只作参数的类型声明 而省略参数名 例如 doublemax doublex doubley 和doublemax double double 是等价的 38 2 3 1作用域 2块作用域块语句就是由 和 组成的 复合语句 在块中声明的标识符 其作用域从声明处开始 一直到结束块的花括号为止 块作用域也称作局部作用域 具有块作用域的变量是局部变量 例如 voidfun void a的作用域b的作用域inta a的作用域起始处cin a if a 0 a a intb b的作用域起始处 b的作用域终止处 a的作用域终止处 39 2 3 1作用域 需要说明的是 当标识符的作用域完全相同时 不允许出现相同的标识符名 而当标识符具有不同的作用域时 却允许标识符同名 例如 voidfun void 块A块B 块A开始inti 块B开始inti i 100 块B结束 块A结束代码中 在A和B块中都声明了变量i 这是允许的 因为块A和块B不是同一个作用域 40 2 3 1作用域 但同时出现另外一个问题 语句 i 100 中的i是使用A块中的变量i还是使用B中的变量i C 规定在这种作用域嵌套的情况下 如果内层 块B 和外层 块A 作用域声明了同名的标识符 那么在外层作用域中声明的标识符对于该内层作用域是不可见的 也就是说 在块B声明的变量i与块A声明的变量i无关 当块B中的i 100时 不会影响块A中变量i的值 41 2 3 1作用域 4 文件作用域在所有函数外定义的标识符称为全局标识符 全局标识符的作用域是文件作用域 它从声明之处开始 直到文件结束一直是可见的 具有文件作用域的变量和常量称为全局变量和全局常量 例如 constfloatPI 3 14 全局常量PI 其作用域从此开始inta 全局变量a 其作用域从此开始voidmain voidfunA intx 42 2 3 1作用域 5函数作用域若函数定义在后 调用在前 必须进行函数原型声明 若函数定义在前 调用在后 函数定义包含了函数的原型声明 一旦声明了函数原型 函数标识符的作用域是从声明或定义之处开始到源程序文件结束 例如 voidfunA intx 函数funA的作用域从此开始到文件结束voidfunB 函数funB的作用域从此开始到文件结束 voidmain voidfunA intx 43 2 3 3存储类型 存储类型是针对变量而言的 它规定了变量的生存期 无论是全局变量还是局部变量 编译系统往往根据其存储方式定义 分配和释放相应的内存空间 变量的存储类型反映了变量在哪开辟内存空间以及占用内存空间的有效期限 在C 中 变量有4种存储类型 自动类型 静态类型 寄存器类型和外部类型 这些存储类型是在变量定义时来指定的 其一般格式如下 44 2 3 3存储类型 1 自动存储类型一般说来 用自动存储类型声明的变量都是限制在某个程序范围内使用 即为局部变量 从系统角度来说 自动存储类型变量是采用动态分配方式在栈区中来分配内存空间 因此 当程序执行到超出该变量的作用域时 就释放它所占用的内存空间 其值也随之消失了 在C 语言中 声明一个自动存储类型的变量是在变量类型前加上关键字auto 例如 autointi 45 2 3 3存储类型 若自动存储类型的变量是在函数内或语句块中声明的 则可省略关键字auto 例如 voidfun inti 省略auto 46 2 3 3存储类型 2寄存器类型使用关键字register声明寄存器类型的变量的目的是将所声明的变量放入寄存器内 从而加快程序的运行速度 例如 registerinti 声明寄存器类型变量但有时 在使用register声明时 若系统寄存器已经被其他数据占据时 寄存器类型的变量就会自动当作auto变量 47 2 3 3存储类型 2 静态类型从变量的生存期来说 一个变量的存储空间可以是永久的 即在程序运行期间该变量一直存在 如全局变量 也可以是临时的 如局部变量 当流程执行到它的说明语句时 系统为其在栈区中动态分配一个临时的内存空间 并在它的作用域中有效 一旦流程超出该变量的作用域时 就释放它所占用的内存空间 其值也随之消失 48 2 3 3存储类型 但是 若在声明局部变量类型前面加上关键字static 则将其定义成了一个静态类型的变量 这样的变量虽具有局部变量的作用域 但由于它是用静态分配方式在静态数据区中来分配内存空间 因此 在这种方式下 只要程序还在继续执行 静态类型变量的值就一直有效 不会随它所在的函数或语句块的结束而消失 简单地说 静态类型的局部变量虽具有局部变量的作用域 但却有全局变量的生存期 49 2 3 3存储类型 例Ex Static 使用静态类型的局部变量 includeusingnamespacestd voidcount inti 0 staticintj 0 静态类型i j cout i i j j n intmain count count return0 50 2 3 3存储类型 程序中 当第1次调用函数count时 由于变量j是静态类型 因此其初值设为0后不再进行初始化 执行j 后 j值为1 并一直有效 第2次调用函数count时 由于j已分配内存且进行过初始化 因此语句 staticintj 0 被跳过 执行j 后 j值为2 故程序运行结果为 51 2 3 3存储类型 需要说明的是 静态类型的局部变量只在第一次执行时进行初始化 正因为如此 在声明静态类型变量时一定要指定其初值 若没有指定 编译器还会将其初值置为0 52 2 4名称空间 随着程序代码的增多 名称相互冲突的可能性也将增加 尤其是在程序中使用多家厂商提供的类库时 标示符名称的冲突可能性更高 例如 两个类库中可能都定义了名为List Tree和Node的类 但定义的含义和方式不兼容 或是两个类库定义基个程序本相同 但两个类库同时包含在一个程序中 会出现标示符重复定义的错误 这些问题统称为名称空间问题 解决这些名称空间问题的方法是使用C 新的名称空间特性 通过名称空间的作用域机制来解决 53 2 4名称空间 2 4 1名称空间的定义在C 中 定义一个名称空间的格式如下 其中 namespace是C 关键字 标识符用作名称空间的名称 属于该名称空间体中的变量 函数 结构 枚举 联合以及以后要讨论的类等都可以认为是该名称空间的成员 namespace 标识符 成员 54 2 4 1名称空间的定义 需要说明的是 同一个文件中 可以允许定义多个名称空间 一旦定义名称空间后 标识符就标识名称空间体的那个区域 例如 usingnamespacestd namespaceDING1 charname thisisinDI
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 星座起源课件
- 大疆T系无人机培训
- 2026届福建省泉州市永春一中学英语九上期末统考试题含解析
- 农村发展专业解读课件
- 公共卫生体系规则解读
- 湖南省长沙市望城区2026届九年级化学第一学期期中考试试题含解析
- Android基础培训:炫彩商务应用开发与总结
- 2026届安徽省合肥市行知学校化学九年级第一学期期中考试模拟试题含解析
- 2026届贵州省毕节市九上化学期中考试模拟试题含解析
- 2026届四川省绵阳地区化学九年级第一学期期中联考试题含解析
- 软件和信息技术服务定制化开发解决方案
- 医学实验室安全培训
- 水井清污协议书
- 水利工程施工防火措施
- 2025年煤炭生产经营单位(一通三防安全管理人员)考试笔试试题(400题)附答案
- 音乐基础-乐理篇
- 塑胶地板施工合同
- 布鲁克纳操作手册
- 全案设计落地合同标准文本
- 2025年企业信用报告-上海仪恩埃半导体设备有限公司
- 企业员工打架斗殴教育
评论
0/150
提交评论