《C语言程序设计》课程实验报告(三)

实验题目    函数其应用实验
实验时间    第9周 —第10周
一、实验目的及要求
(一)掌握函数的定义、函数申明、函数参数、函数调用的基本概念与作用;
(二)掌握常量、变量名、数组名作为参数,实参与形参传递数据的本质;
(三)掌握函数的嵌套调用的方法;
(四)掌握常规变量、数组作为参数的函数参数传递方法;
(五)了解全局变量、局部变量的概念和使用方法;
(六)使用单步执行,观察变量的值,学会程序调试基本方法。
(七)掌握递归调用编程方法
二、实验内容、过程和结果(实验主要内容的介绍、主要的操作步骤、程序代码和测试数据及实验结果)
•    函数的定义
函数是一段可以重复使用的代码,用来独立地完成某个功能,它可以接收用户传递的数据,也可以不接收。接收用户数据的函数在定义时要指明参数,不接收用户数据的不需要指明,根据这一点可以将函数分为有参函数和无参函数。将代码段封装成函数的过程叫做函数定义。
1C语言无参函数的定义:如果函数不接收用户传递的数据,那么定义时可以不带参数
datatype functionName()
{
//body
}
dataType 是返回值类型,它可以是C语言中的任意数据类型,例如 int、float、char 等。
functionName 是函数名,它是标识符的一种,命名规则和标识符相同。函数名后面的括号( )不能少。
body 是函数体,它是函数需要执行的代码,是函数的主体部分。即使只有一个语句,函数体也要由{ }包围。
如果有返回值,在函数体中使用 return 语句返回。return 出来的数据的类型要和 dataType 一样。
//定义一个函数,计算从 1 加到 100 的结果:
  dataType不为void要有返回值
累加结果保存在变量sum中,最后通过return语句返回。sum 是 int 型,返回值也是 int 类型,它们一一对应。
return是C语言中的一个关键字,只能用在函数中,用来返回处理结果。
函数不能嵌套定义,main 也是一个函数定义,所以要将 sum 放在 main 外面。函数必须先定义后使用,所以 sum 要放在 main 前面。
2无返回值函数:有的函数不需要返回值,或者返回值类型不确定(很少见),那么可以用 void 表示
   //利用主函数调用hello()
void是C语言中的一个关键字,表示“空类型”或“无类型”,绝大部分情况下也就意味着没有 return 语句
3 C语言有参函数的定义
如果函数需要接收用户传递的数据,那么定义时就要带上参数。如下所示:
dataType functionName( dataType1 param1, dataType2 param2 ... )
{
//body
}
dataType1 param1, dataType2 param2 ...是参数列表。函数可以只有一个参数,也可以有多个,多个参数之间由”,”分隔。参数本质上也是变量,定义时要指明类型和名称。与无参函数的定义相比,有参函数的定义仅仅是多了一个参数列表。
数据通过参数传递到函数内部进行处理,处理完成以后再通过返回值告知函数外部。
   
调用 sum() 函数时,需要给它传递两份数据,一份传递给 m,一份传递给 n。你可以直接传递整数就如同上面的sum(3,5);也可以传递变量:也可以整数和变量一起传递:
   
函数定义时给出的参数称为形式参数,简称形参;函数调用时给出的参数(也就是传递的数据)称为实际参数,简称实参。函数调用时,将实参的值传递给形参,相当于一次赋值操作。
原则上讲,实参的类型和数目要与形参保持一致。如果能够进行自动类型转换,或者进行了强制类型转换,那么实参类型也可以不同于形参类型,例如将 int 类型的实参传递给 float 类型的形参就会发生自动类型转换。
4函数不能嵌套定义
强调一点,C语言不允许函数嵌套定义;也就是说,不能在一个函数中定义另外一个函数,必须在所有函数之外定义另外一个函数。main() 也是一个函数定义,也不能在 main() 函数内部定义新函数。
(在 JavaScript 中经常会使用函数的嵌套定义。)
•    C语言形参和实参的区别
C语言函数的参数会出现在两个地方,分别是函数定义处和函数调用处,这两个地方的参数是有区别的。
1形参(形式参数)
在函数定义中出现的参数可以看做是一个占位符,它没有数据,只能等到函数被调用时接收传递进来的数据,所以称为形式参数,简称形参
2实参(实际参数)
函数被调用时给出的参数包含了实实在在的数据,会被函数内部的代码使用,所以称为实际参数,简称实参。
3形参和实参的区别和联系
形参变量只有在函数被调用时才会分配内存,调用结束后,立刻释放内存,所以形参变量只有在函数内部有效,不能在函数外部使用。
实参可以是常量、变量、表达式、函数等,无论实参是何种类型的数据,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参,所以应该提前用赋值、输入等办法使实参获得确定值。
实参和形参在数量上、类型上、顺序上必须严格一致,否则会发生“类型不匹配”的错误。当然,如果能够进行自动类型转换,或者进行了强制类型转换,那么实参类型也可以不同于形参类型。
函数调用中发生的数据传递是单向的,只能把实参的值传递给形参,而不能把形参的值反向地传递给实参
在函数调用过程中,形参的值发生改变并不会影响实参。
 
形参和实参虽然可以同名,但它们之间是相互独立的,互不影响,因为实参在函数外部有效,而形参在函数内部有效。
•    C语言函数返回值 return
函数的返回值是指函数被调用之后,执行函数体中的代码所得到的结果,这个结果通过 return 语句返回。 一般形式:return 表达式;或者return (表达式);
1没有返回值的函数为空类型,用void表示。一旦函数的返回值类型被定义为 void,就不能再接收它的值了
2 return 语句可以有多个,可以出现在函数体的任意位置,但是每次调用函数只能有一个 return 语句被执行,所以只有一个返回值(少数的编程语言支持多个返回值,例如Go语言)
3函数一旦遇到 return 语句就立即返回,后面的所有语句都不会被执行到了。从这个角度看,return 语句还有强制结束函数执行的作用
 
•    C语言函数调用
所谓函数调用(Function Call),就是使用已经定义好的函数。函数调用的一般形式为:
functionName(param1, param2, param3 ...);
functionName 是函数名称,param1, param2, param3 ...是实参列表。实参可以是常数、变量、表达式等,多个实参用逗号,分隔。
1函数调用方式
作为一个单独的语句;作为表达式中的一项出现在表达式中;作为调用另一个函数时的实参。
 
2函数的嵌套调用
函数不能嵌套定义,但可以嵌套调用,也就是在一个函数的定义或调用过程中允许出现对另外一个函数的调用。
计算sum = 1! + 2! + 3! + ... + (n-1)! + n! 可以编写两个函数,一个用来计算阶乘,一个用来计算累加的和。
 
•    C语言函数声明以及函数原型
1函数声明
所谓声明(Declaration),就是告诉编译器我要使用这个函数,你现在没有找到它的定义不要紧,请不要报错,稍后我会把定义补上。
函数声明的格式非常简单,相当于去掉函数定义中的函数体,并在最后加上分号;
dataType  functionName( dataType1 param1, dataType2 param2 ... );
也可以不写形参,只写数据类型:
dataType  functionName( dataType1, dataType2 ... );
函数声明给出了函数名、返回值类型、参数列表(重点是参数类型)等与该函数有关的信息,称为函数原型(Function Prototype)。函数原型的作用是告诉编译器与该函数有关的信息,让编译器知道函数的存在,以及存在的形式,即使函数暂时没有定义,编译器也知道如何使用它。
有了函数声明,函数定义就可以出现在任何地方了,甚至是其他文件、静态链接库、动态链接库等。
 
我们在 main() 函数中调用了 sum() 函数,编译器在它前面虽然没有发现函数定义,但是发现了函数声明,这样编译器就知道函数怎么使用了,至于函数体到底是什么,暂时可以不用操心,后续再把函数体补上就行。
定义两个函数,计算1! + 2! + 3! + ... + (n-1)! + n!的和。
 
在实际开发中,往往都是几千行、上万行、百万行的代码,将这些代码都放在一个源文件中简直是灾难,不但检索麻烦,而且打开文件也很慢,所以必须将这些代码分散到多个文件中。对于多个文件的程序,通常是将函数定义放到源文件(.c文件)中,将函数的声明放到头文件(.h文件)中,使用函数时引入对应的头文件就可以,编译器会在链接阶段找到函数体。
实际开发过程中,变量定义需要放在源文件(.c文件)中,变量声明需要放在头文件(.h文件)中,在链接程序时会将它们对应起来。
•    C语言全局变量和局部变量
形参变量要等到函数被调用时才分配内存,调用结束后立即释放内存。这说明形参变量的作用域非常有限,只能在函数内部使用,离开该函数就无效了。所谓作用域(Scope),就是变量的有效范围。
不仅对于形参变量,C语言中所有的变量都有自己的作用域。决定变量作用域的是变量的定义位置。
所谓作用域(Scope),就是变量的有效范围,就是变量可以在哪个范围以内使用。有些变量可以在所有代码文件中使用,有些变量只能在当前的文件中使用,有些变量只能在函数内部使用,有些变量只能在 for 循环内部使用。
C语言规定,在同一个作用域中不能出现两个名字相同的变量,否则会产生命名冲突;但是在不同的作用域中,允许出现名字相同的变量,它们的作用范围不同,彼此之间不会产生冲突。这句话有两层含义:
不同函数内部可以出现同名的变量,不同函数是不同的局部作用域
函数内部和外部可以出现同名的变量,函数内部是局部作用域,函数外部是全局作用域。
1局部变量
定义在函数内部的变量称为局部变量(Local Variable),它的作用域仅限于函数内部, 离开该函数后就是无效的,再使用就会报错;
    
在 main 函数中定义的变量也是局部变量,只能在 main 函数中使用;同时,main 函数中也不能使用其它函数中定义的变量。main 函数也是一个函数,与其它函数地位平等。
形参变量、在函数体内定义的变量都是局部变量。实参给形参传值的过程也就是给局部变量赋值的过程。
可以在不同的函数中使用相同的变量名,它们表示不同的数据,分配不同的内存,互不干扰,也不会发生混淆。
 
在语句块中也可定义变量,它的作用域只限于当前语句块。
2全局变量
在所有函数外部定义的变量称为全局变量(Global Variable),它的作用域默认是整个程序,也就是所有的源文件,包括 .c 和 .h 文件。
 
局部变量与全局变量的综合实例
 
代码中虽然定义了多个同名变量 n,但它们的作用域不同,在内存中的位置(地址)也不同,所以是相互独立的变量,互不影响,不会产生重复定义(Redefinition)错误。
对于 func1(),输出结果为 20,显然使用的是函数内部的 n,而不是外部的 n;func2() 也是相同的情况。
当全局变量和局部变量同名时,在局部范围内全局变量被“屏蔽”,不再起作用。或者说,变量的使用遵循就近原则,如果在当前作用域中存在同名变量,就不会向更大的作用域中去寻找变量。
func3() 输出 10,使用的是全局变量,因为在 func3() 函数中不存在局部变量 n,所以编译器只能到函数外部,也就是全局作用域中去寻找变量 n。
由{ }包围的代码块也拥有独立的作用域,printf() 使用它自己内部的变量 n,输出 40。
C语言规定,只能从小的作用域向大的作用域中去寻找变量,而不能反过来,使用更小的作用域中的变量。对于 main() 函数,即使代码块中的 n 离输出语句更近,但它仍然会使用 main() 函数开头定义的 n,所以输出结果是 60。
【示例2】根据长方体的长宽高求它的体积以及三个面的面积。
              
我们希望借助一个函数得到三个值:体积 v 以及三个面的面积 s1、s2、s3。但C语言中的函数只能有一个返回值,我们只能将其中的一份数据,也就是体积 v 放到返回值中,而将面积 s1、s2、s3 设置为全局变量。全局变量的作用域是整个程序,在函数 vs() 中修改 s1、s2、s3 的值,能够影响到包括 main() 在内的其它函数。
•    C语言块级变量:在代码块内部定义的变量
所谓代码块,就是由{ }包围起来的代码。代码块在C语言中随处可见,例如函数体、选择结构、循环结构等。不包含代码块的C语言程序根本不能运行,即使最简单的C语言程序也要包含代码块。
C语言允许在代码块内部定义变量,这样的变量具有块级作用域;换句话说,在代码块内部定义的变量只能在代码块内部使用,出了代码块就无效了。
 
   
•    C语言递归函数(递归调用)
一个函数在它的函数体内调用它自身称为递归调用,这种函数称为递归函数。执行递归函数将反复调用其自身,每调用一次就进入新的一层,当最内层的函数执行完毕后,再一层一层地由里到外退出。
阶乘 n! 的计算公式如下: 
 
factorial() 就是一个典型的递归函数。调用 factorial() 后即进入函数体,只有当n==1 时函数才会执行结束,否则就一直调用它自身。
由于每次调用的实参为 n-1,即把 n-1 的值赋给形参 n,所以每次递归实参的值都减 1,直到最后 n-1 的值为 1 时再作递归调用,形参 n 的值也为1,递归就终止了,会逐层退出。
factorial() 是最简单的一种递归形式——尾递归,也就是递归调用位于函数体的结尾处。除了尾递归,还有更加烧脑的两种递归形式,分别是中间递归和多层递归:
•    中间递归:发生递归调用的位置在函数体的中间;
•    多层递归:在一个函数里面多次调用自己。
坏处:递归函数的时间开销和内存开销都非常大,极端情况下会导致程序崩溃。
•    C语言带参宏定义和函数的区别
带参数的宏和函数很相似,但有本质上的区别:宏展开仅仅是字符串的替换,不会对表达式进行计算;宏在编译之前就被处理掉了,它没有机会参与编译,也不会占用内存。而函数是一段可以重复使用的代码,会被编译,会给它分配内存,每次调用函数,就是执行这块内存中的代码。
【示例①】用函数和宏计算平方值。
      
    
宏调用只是简单的字符串替换,SQ(i++) 会被替换为 ((i++)*(i++)),这样每循环一次 i 的值增加 2,所以最终只循环 3  次。
此可见,宏和函数只是在形式上相似,本质上是完全不同的。
 
•    函数例题
1二进制转换成十进制
 
利用数学定理,输出的二进制是1还是0依据余数决定,当n=1时结束,最后逆向输出
2汉诺塔实验
 
3将字符串1的第1,3,5,7,9,......位置的字符复制到字符串2并输出。
      
(有结束标志符)
4斐波拉契数列                                     (无结束标识符)
      
(递归过程中产生很多了多余的计算,使得递归算法的时间复杂度很大,所以通常用非递归法)
三、实验总结与收获
1.    本次实验主要是对函数的章节一个的一个学习,从定义、类型、参数、调用等内容了解了函数的大体框架。
2.    了解到了形参与实参的区别,形参变量只有在函数被调用时才会分配内存,调用结束后,立刻释放内存,所以形参变量只有在函数内部有效,不能在函数外部使用。
3.    函数调用中发生的数据传递是单向的,只能把实参的值传递给形参,而不能把形参的值反向地传递给实参,在函数调用过程中,形参的值发生改变并不会影响实参。
4.    基本掌握函数调用,包括嵌套使用。
5.    一个程序有且只能有一个main函数的存在。
6.    全局变量的作用域是整个程序,而局部变量作用域为函数内部。
7.    递归法的一个运用,特别是汉诺塔、斐波拉契数。但是切记递归过程中容易产生很多了多余的计算,要学会选择方法。
8.    还有就是宏定义与函数的区别,一些函数题目的练习。总的来说每一次写报告都是一种知识的总结,收获颇多。
成绩        评阅老师    
                

  • 18
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值