当前课程知识点:C++语言程序设计基础 > 第3章 函数 > 实验三(上)函数的应用 > 实验三(上)函数的应用
大家好
欢迎来到实验3的上半部分
本章的学习目标
是掌握将一段功能独立的程序
写成一个函数
为今后学习类和对象
打下一个必要的基础
本部分的实验内容
是通过实验练习
来进一地理解和掌握
函数的定义
和调用的方法
并且掌握
debug中的step into方法
来跟踪到函数的内部
说到函数
大家首先想到的
就是数学中的函数吧
那么本章的第一个例子
就来练习一下
如何用C++中的函数模块
来实现一个数学中的简单函数
其实掌握函数的定义
和基本的语法比较简单
它的主要难点在于
如何去理解和跟踪
函数的调用过程
以及它的参数传递
在本章中
郑莉老师给大家介绍的例子
明显比第二章复杂
其中包含大量有用的算法
并附有详细的注释
建议大家仔细地阅读
并亲自上机调试
还记得我们之前给大家说过的
学习编程从修改例子程序开始吗
在调试程序的时候
将利用本章介绍的step into功能
跟踪到函数的内部
能够帮助我们理解
整个函数执行的过程
和参数的传递
郑老师还给大家介绍了递归算法
一种用途广泛
简捷高效的算法
这种算法理解起来
有一定的难度
本部分的第二个例子
就将以一个简单的递归程序为例
介绍step into
这个debug功能
跟踪递归算法的过程
首先我们来看例题一
编写一个函数
函数的名称叫Convert
它的参数和返回值都为float类型
这个Convert函数
需要实现一个算法
如图上所说的这个公式
这个算法呢
它是把一个华氏温度
转化成一个摄氏温度的一个公式
接下来我们来看一下
已经编写好的这个代码
在这里
我们函数名称叫Convert
这里有一个函数
那么Convert函数呢
大家看看它函数体非常简单
只有三行
这三行代码就实现了
用C++语言来把相应的算法
翻译成C++代码
来实现函数相应的功能
数学函数呢
是用最直接的翻译方式
大家看到
我们直接把公式列在这里
F减去32 乘以5 除以9
然后复制给C
在这里呢F是形参
它的类型是float
然后C呢是函数内部的局部变量
它的类型也是float
当C的值计算完毕之后
通过return语句
作为函数的返回值
进行函数参数的返回
那在这个例子里
我们可以看到
我们把这个函数Convert
写在main函数之前
完成了函数的定义
这一部分的工作
那么在定义完了
Convert这个函数之后呢
我们在main函数中
通过几个简单的步骤
来获得用户
对于华氏摄氏度输入
然后通过调用
我们刚才定义的这个Conver函数
来验证一下
这个函数的调用
是否如我们预想的那样
是正确进行的
那么在调试之前呢
想给大家介绍一个新的调试功能
就叫step into
也可以通过f11来启动它
那么和我们之前给大家介绍的
逐过程 step over
或者是f10对应的这个
单步调试的功能所不同呢
f10是直接会
越过整个函数的执行
直接给出函数执行的结果
而f11逐语句step into呢
它遇到常数调用的时候
会进入函数的内部
我们在这一章学习了函数
为了更好地跟踪和了解
函数调用之后
进入到函数体中
一步一步地执行过程
所以我们选择f11
step into的这种方式来跟踪
会比较清楚
我们从f11开始
我们可以看到
从这个窗口里头看到
我们这里声明了
在main函数中声明了这个
一个float类型的变量叫F
那么在main函数的第一条语句
就是光标所指的位置
这个时候呢
只是把变量名和变量类型
已经声明了
但是F并没有进行初始化
我们在调用堆栈这里也可以看到
在这里我们运行到
main函数的第19行
接着我们再用逐语句的方式
来进行运行
这个时候就进行到了
main函数的第23行
这个时候屏幕会输出
让用户输入一个华氏度的
这样一个温度值
我们来看一下
屏幕上就显示出了这样一行文字
接下来呢
我们把光标放到这里
放到调用Convert函数的这一行
27行
然后点击右键
选择运行到光标
或者按下Ctrl f10
然后让编译器调试的过程呢
直接运行到Convert这一步
然后再停下来
这个时候
我们可以输入一个
你想要的华氏的温度
比如说100度吧
那可以看到
这个时候呢
光标就停在了刚才我们设立
执行位置的地方
main函数中的27行
同时呢
刚才我们用户的键入100
也正确地通过cin
读入到了变量F中去
这个时候F的值变成了100
接下来我们
不想通过以前的f10
step over的方式
直接看到Convert函数
执行的结果
而是我们想跟踪一下
当调用Convert的时候
整个调用执行的过程
那这个时候我们就选择F11
step into
这个时候光标挪到了
Convert这个函数的第一行
也就是
现在我们跟踪到了
Convert调用的内部
那我们在调用堆栈里头
也可以看到这个时候
我们运行的位置是
Convert这个函数的
它的运行的函数是第11行
在Convert函数内部呢
我们有两个局部的量
一个F 一个是C
F的值通过刚才函数的调用
已经进行了初始化
所以F的值是100
那变量C呢
只是进行了名称和类型的声明
还并没有初始化
所以我们在这里看到的是一个
随机的一个数
那我们接下来再一步一步地进行
那么这个时候
光标指向了第14行
也就是这个数学公式计算之前
那么再往下走一步
这个时候我们看到
通过刚才的计算
局部变量C的值已经被改变了
变成了一个37.77777786
这样的一个浮点数
接着我们到了
整个Convert函数的最后一句
return C
那么它就是将把C当前所用的值
作为Convert整个函数的返回值
返回到main函数中去
返回以后呢
我们可以看到
光标又回到了刚才
main函数中的27行
就是返回到了main函数中
刚才调用Convert函数的位置
这个时候返回值
通过cout这个输出流对象
进行输出
我们再往下执行一步
就可以看到
在这里显示出了
通过调用Convert函数以后
返回到main函数中去
通过cout输出的这个摄氏的温度
通过这个例子呢
又复习一下函数的定义
和调用
了解了如何用一个C++语言函数
来翻译数学函数
并且更重要的是
我们在这里给大家介绍了
用step into
还有run to cursor这两种手段
来跟踪整个函数的调用过程
察看函数调用时
不同参数的值
以及运行到调用堆栈的位置
以及行数
这对于我们来跟踪调试
和理解函数的执行 调用
参数的传递
和参数的返回
是非常有帮助的
接下来我们来看例题二
例题二是要求大家
编写一个递归函数
就是著名的Fibonacci数列
大家可以看到
它的参数只有一个
是整型的int类型
返回值也是int类型
要求在主程序中
通过键盘来输入n的值
然后调用Fibonacci函数
来计算Fibonacci级数
这里我们来看一下
Fibonacci级数的计算公式
它是一个
非常典型的分段函数的形式
当参数值为1和2
也就是第一个
和第二个Fiboacci级数
它的值都为1
那么当n大于2开始呢
Fibonacci级数的第n个
等于它在第n-1个
和第n-2个之和
接下来我们来看一下
我们准备好的这个代码
那么在这个例子中
我们跟例一有所不同
我们把Fibonacci
这个函数的实现呢
放到了main函数调用的之后
可以看到
把它main函数之后
所以我们在这里
也需要在main函数之前
也就是调用之前
进行一个函数原型的声明
来告诉编译器
存在这样一个函数的声明
和定义
并且我们接下来马上就要使用它
那么我们言归正传
我们来看一下main函数
那么按照题目的意思
我们需要由两个int类型参数
一个是n
给出我要
求Fibonacci级数的第几个
另外一个是answer
计算出来的Fibonacci数列的
第n个级数到底应该是多少
接下来我们用cout
提示用户输入一个数
这个数是级数的第几个
并且把这个数
通过cin存入到变量n中去
我们在这里的cout
是打印两个换行符
是为了把
Fibonacci级数的计算结果
和刚才的输入进行分隔
那大家可以根据自己的喜好
进行不同的输入输出语句的
这个格式调整
目的首先是清楚
然后如果能美观那就更好了
接下来我们就
调用了Fibonacci函数
fib这个函数
那把刚才从界面上读的n
作为参数的值传入进去
它的结果赋值给answer
这样一个变量
接下来通过cout
把计算出来的第n个
Fibonacci级数的值打印输出
这就是main函数的功能
非常简单
接下来我们来看一下
Fibonacci函数的对应实现
fib这个函数
fib函数有一个形式参数
int类型的变量n
那么需要注意的是
尽管在这里有一个n
然后在main函数中有一个n
同时它们的类型都是int类型
但实际上这是
两个完全不相干的两个参数
那么它们的生存区
和作用域
也是各不相同的
在调用的这一时刻
会把主函数中的n的值
传递到fib这个函数的形参中去
那传输完毕以后呢
这两个n就完全没有关系了
对这个n的任何操作和改变
都不会影响main函数的这个n
那么在fib这个函数的内部呢
也是为了便于观测
整个函数的执行过程
我们在这里打印输出
Processing fib
也就是告诉我们的观察者
或程序人员
我们已经进入fib这个函数了
同时把这个n也输出
就是说
我接下来要计算的
是级数的第n个
这个n的值是多少
接下来要做一个判断
因为我们刚才说到
Fibonacci函数
是一个典型的分段函数
所以显然
我们应该用if else分支
来进行分情况的计算和讨论
那么n等于1和2的时候呢
Fibonacci级数的值都是1
所以在这里if n小于3
那么直接把整数1作为返回值
进行返回
并且打印输出返回1
那当n的值是大于或等于3的时候
我们就需要
进行一个级数的递归运算
我们在这里打印输出
按照Fibonacci计算的公式
当n大于等于3的时候
那么它的级数应该是把
第n-2个Fibonacci级数
和第n-1个Fibonacci级数相加
然后作为第n个级数的返回值
进行返回
那么在这里
因为我们在fib这个函数内部
又调用了fib这个函数自己
只是参数的规模进行了缩减
所以这是一个
非常非常典型的函数
自己调用自己的例子
也就是我们说的递归调用
那么接下来我们就要
仍然会应用
刚才给大家介绍的step into功能
和run to cursor功能
来观测一下
这个递归的调用过程
和参数传递的情况
我们还是用f11
好 我们调试开始了
那么首先f11呢
最后进入main函数
并且在main函数
并且在main函数的第一行停住
在main函数中
我们这里声明了两个
int类型的变量
一个是answer 一个是n
这个时候呢
它们都没有进行初始化
因为它们是一个内存中任意的值
我们在调用栈里头可以看到
这个时候光标是停在了
main函数的里面
而且行数是第13行
接下来呢
我们把光标放到
answer等于fib(n)这一行
通过右键选择运行到光标处
这个时候要我们去输入一下
这个级数n的值
在这里我们先输入一个2
来试一下
也就是没有发生递归
或者是递归的最简单情形
那这个时候n的值是2
通过cin来读入了
并且整个调试过程停到了
这个黄色箭头所指的光标处
在调用栈里我们也可以看到
现在执行到了main函数的第18行
但是n的值呢
也正常地被刷新为2
接下来我们想跟踪一下
它进入
fib这个函数之后的执行过程
这个时候你可以按一下f11
大家可以看到
现在光标停在的位置
这是正常的进入了fib这个函数
并且停在了它的第一行
在调用栈我们可以看到
这个时候我们运行到的是
在main函数之内
我们又进入了fib这个函数
并且停在了第24行
这个位置
我们再来看一下局部变量
在fib这个函数中
只有一个局部变量
它是n
这个时候显示n的值是2
也就是在这次调用中
main函数中的n值
已经正常地传递到了fib函数中
使它的n值也为2
那接下来呢
因为n值为2
所以它肯定会选择if分支中的
哪一个分支呢
对 显然是第一个分支
就是小于3的情况
这个时候不做进一步的递归
而是直接返回整数1
我们来单步跟踪一下
果然
因为n的值是2
2小于3
使得if后面的这个条件判断成立
就进入了if分支
这个时候我们来看一下
都输出了什么
这个时候输出的是
我们正在处理的是
Fibonacci函数中的第二个级数
我们再来看一下
这个时候打印出来返回1
说明进入了if的这个第一个分支
那现在光标就跳到了
整个fib函数的最后
末尾部分的这个第36行
也就是它的结束之处
意味着这个函数的执行完成了
并没有去执行else分支
后面的这半部分
那我们再看一下
它如何返回的
这个时候光标又退回到了
main函数中的第18行
就是在这里
可以看到这个时候呢
answer的值还没有被赋值
而fib函数结束了
返回的值呢是1
我们再来进行一步
这个时候可以看到
返回值
那个临时的无名变量的1的值
复制给了answer这个变量
使得answer的值为1
完成了我们的计算
也就是n等于2的时候
Fibonacci级数的值是1
那最后就是打印输出我们的结果
您可以看到
输出Fibonacci数列的
第二个级数的值是1
那么这个最简单情形的调试
我们就结束了
接下来我们希望观测什么呢
观测n大于等于3的时候
Fibonacci数列这个fib函数
会自己调用自己
这个时候才进行了
一个真正的递归的调用的过程
我们再来跟踪一次
首先还是通过f11
进入到main函数的第一行
接着我们还是把光标
设在answer等于fib(n)这里
运行到光标处
那这次呢
我们输入4来试一试
因为4是大于等于3的
所以它必然会进行一个
真正的递归调用
可以看到这个时候呢
光标停在这个地方
然后n的值呢通过键盘读入
变成为4
接着我们通过f11
来运行到fib函数的内部
它停在了fib函数的第1行
也就是对应的24行这里
这个时候的n值变成了4
局部变量n值变成了4
显然它是要执行else分支的
我们再次把光标放到return
递归调用的这个地方
运行到光标处
这个时候我们可以看到
它确实进行了
进入到了else分支里
并且正常地打印出了
这个时候它要调用
Fibonacci级数的第二个级数
和Fibonacci级数的第三个级数
这个时候自己调用了自己一次
这时候是进行了
递归调用的第一层
但是事情还没有结束
因为即使是
Fibonacci的第二个级数
和第三个级数
它也是需要进一步进行递归的
那我们可以看到
因为我们刚才用了f10
step over就完成了
整个递归调用的过程
接下来的分解
应该是先去调用
从Fibonacci第二个级数进行深入
然后返回呢 值是1
然后再从
Fibonacci数列第三个级数
开始深入
它进入了
Fibonacci的第三个级数之后呢
再去进一步地分解调用
Fibonacci的第一个级数
和第二个级数
那遇到Fibonacci的
第一个级数的时候
返回 为1
遇到第二个级数的时候
返回 也为1
这样共同返回就到了
Fibonacci的第三个级数这里
那么整个递归调用的过程
结束以后呢
它又会回到main函数的这一行
那这个时候呢
我们的这个fib函数呢
返回值是3
这时候有一个临时无名的变量
我们还没有完成复制这一步
那我们再来完成这一步
这个时候把返回值3
赋值给了answer这个变量
所以最后的结果应该是输出
Fibonacci的第四个级数的值
应该是3
我们就会看到这个结果
那么我们选这个n为4的情况呢
实际上是真正的递归调用中
最简单的一种情形
那么 对 还有3
3和4是最简单的两种情形
因为时间的关系呢
在这里不可能把n的值
设得特别大
然后一层一层地来跟踪
所以接下来的扩展类型呢
是大家可以尝试把n的值
设得大一点
比如说8 9 或者是10
并且跟踪的时候呢
用一张大白纸来记录下
每一层递归的调用的过程
以及每次传递的参数
那么这样一次深度地
跟踪下来之后呢
大家一定会对递归的理解
和利用会更加地熟悉
并且对于step into
和run to cursor
这样两个debug功能的用法
也会更加地熟悉
掌握函数的定义和调用
其语法并不难
难的是理解其执行过程
和参数传递
debug中的step into功能
可以简单有效地帮助我们
本章中的递归函数
理解起来有难度
自己亲自编写
更不是一件容易的事情
作为初学者不要着急
本章中只是介绍了C++中
支持递归函数的调用
如果想深入地理解
和熟练地编写
建议大家去扩展阅读一下
数据结构中的相应章节
最后在调试递归函数的时候
建议大家准备一张大纸
记录下step into跟踪时
每一次函数调用
和参数传递的值
这样也是一个简单有效的方法
实验3的上半部分就到这里
我们下次再见
-导学
--第1章导学
-计算机系统简介
--计算机系统简介
--计算机系统简介 测试题
-计算机语言和程序设计方法的发展
--计算机语言和程序设计方法的发展 测试题
-面向对象的基本概念
--面向对象的基本概念 测试题
-程序的开发过程
--程序的开发过程
--程序的开发过程 测试题
-信息的表示和储存
--计算机的数字系统
--数据的编码表示
--信息的表示和储存 测试题
-实验指导
-导学
--第二章导学
-C++语言概述
--C++语言概述 测试题
-基本数据类型、常量、变量
--程序举例
--基本数据类型、常量、变量 测试题
-运算与表达式
--运算与表达式 测试题
-实验二:简单程序设计(上)
-数据的输入和输出
--数据的输入和输出
--数据的输入和输出 测试题
-选择结构
--if语句
--switch语句
--选择结构 测试题
-循环结构
--for语句
--循环结构 测试题
-自定义类型
--自定义类型
--自定义类型
-第2章小结
--第二章小结
-实验二:C++简单程序设计(下)
-导学
--导学
-函数定义
--函数定义
--函数定义 测试题
-函数调用
--例3-2
--例3-3
--例3-4
--例3-5
--例3-6
--函数调用 测试题
-嵌套与递归
--例3-9
--例3-10
--嵌套与递归 测试题
-函数的参数传递
--函数的参数传递
--函数的参数传递 测试题
-引用类型
--引用类型 测试题
-含有可变参数的函数
--含有可变参数的函数 测试题
-内联函数
--内联函数 测试题
-constexpr函数
--CONSTEXPR函数课后习题
-带默认参数值的函数
--带默认参数值的函数 测试题
-函数重载
--函数重载 测试题
-C++系统函数
--C++系统函数习题
-第3章小结
--第三章小结
-实验三(上)函数的应用
-实验三(下)函数的应用
-导学
--导学
-面向对象程序的基本特点
--面向对象程序的基本特点 测试题
-类和对象
--类和对象的定义
--类和对象 测试题
-构造函数
--构造函数基本概念
--委托构造函数
--复制构造函数
--构造函数 测试题
-析构函数
--析构函数
--析构函数 测试题
-类的组合
--类的组合
--类的组合程序举例
--前向引用声明
--类的组合 测试题
-UML简介
--UML简介
--UML简介课后习题
-结构体与联合体
--结构体与联合体 测试题
-枚举类
--枚举类
--枚举类 测试题
-第4章小结
--第四章小结
-实验四(上)
--实验四(上)
-实验四(下)
--实验四(下)
-导学
--导学
-标识符的作用域与可见性
--标识符的作用域与可见性 测试题
-对象的生存期
--对象的生存期
--对象的生存期 测试题
-类的静态成员
--类的静态成员 测试题
-类的友元
--类的友元 测试题
-共享数据的保护
--共享数据的保护 测试题
-多文件结构和预编译命令
--多文件结构和预编译命令 测试题
-第5章小结
--小结
-实验五
--实验五
-导学
--导学
-数组的定义与初始化
--数组的定义与使用
--一维数组应用举例
--数组的定义与初始化 测试题
-数组作为函数的参数
--数组作为函数的参数 测试题
-对象数组
--对象数组
--对象数组 测试题
-基于范围的for循环
-指针的定义和运算
--指针的定义和运算 测试题
-综合实例
--综合实例
-实验六(上)
--实验六上
-指针与数组
--指针数组
--指针与数组 测试题
-指针与函数
--指针类型的函数
--指向函数的指针
--指针与函数 测试题
-对象指针
--对象指针
--对象指针 测试题
-动态内存分配
--动态内存分配 测试题
-智能指针
--智能指针
-vector对象
--vector对象
--vector对象 测试题
-对象复制与移动
--移动构造
--对象复制与移动 测试题
-字符串
--C风格字符串
--string类
--字符串 测试题
-第6章小结
--第六章小结
-综合实例
--综合实例
-实验六(下)
--实验六(下)