好,各位同学大家好。那么在讲完了 理性认识C++程序这个部分之后啊, 我们从这次课开始进入我们第三个迭代周期。 就是关于C程序中的函数。 函数这个部分啊对于C程序而言是一个非常重要的部分。 那么在这个部分呢,我们计划讲两次课。 今天呢是第一次。那么今天呢,我们先来介绍一下 关于C程序中函数的一些基本的知识。 那么我希望呢大家要重视这个基本知识的学习。那么把一些基本的概念 掌握好,那么这样呢,到了后面比较难的部分,啊我们才比较容易应付。OK,那么在 这个部分呢,我们将介绍这么几部分的内容。第一个关于函数的定义,也就是说怎么去定义一个函数。 第二个部分我们讲一下函数的执行过程。 那这个部分呢,我觉得还是比较重要的一个部分。第三个,那么 有了函数之后,我们讨论一下函数中的变量的作用范围。 第四个部分呢讨论一下数组与函数。啊其实, 重点就一个数组名作函数参数。最后呢,我们举一个 简单的例子来说明一下函数的作用。好,我们先来看第一个部分,函数的定义。 说到函数呀,一说到函数这个词儿, 啊基本上我们的脑子里头啊立刻就会浮现出来传统意义上数学里面的函数。 像y=f(x),啊这是我们脑子里面关于函数的 最普遍的一个认识了。这样的一个函数呢其实就描述了一个过程。 也就是说对于两个变量x和y,如果呢, 对于x的每一个确定的值,y呢都有唯一确定的值 与它对应,那么我们就说x和y形成了这样的一个函数关系。 而且呢在这里我们把x称作自变量,y呢称作 因变量。啊这是我们脑子里面最熟悉的函数 的那个样子。那么在C语言里头函数长成什么样子呢? 那么在这儿啊,我就列出来了一些在C语言里头非常非常 常用的函数。这些函数呢都是在我们写程序的过程中非常非常常用的。 我们先来看一下它的样子。 比方说第一个,如果已知一个数的话,我们要求它的平方根, 那么我们就可以用sqrt,啊这是我们碰到过的。 输入写到这里头,然后呢,它就会返回一个平方根的值。第二个, 如果已知底数x,幂指数y,我们要求x的y次幂。 那我们可以用pow这样的一个函数pow。 它有两个输入参数x和y,那么把x,y写在这, 那么我就可以获得x的y次幂是多少。啊这两个呢都是一个 很常用的两个数学函数。那么下面呢,还有三个,这三个呢都是关于字符串的。第一个, 如果已经有一个字符串的话,我们要求它的长度,我们就可以用strlen这个函数, 它就会返回字符串的长度。那如果你想比较两个字符串的大小,就是看它们 字母的优先级的顺序以及长度,啊来比较大小。 那么如果比较大小的话,你就可以用strcmp这样一个函数。 它呢会返回一个值代表它们的大小关系。第三个,这个也非常非常的常用。 如果你已经有一个字符串,这个字符串里面呢都是一些字符形式的数字,比方说, 啊有一个字符串1,2,3,4,5,那这是一个字符串啦。 那么如果我想把这个字符串啊转换成它对应的数字,比方说 转换成12345,啊想转换成一个这样的数。 怎么办呢?你就可以用这样一个函数,atoi,就是area to int。 非常好用的一个函数。那这样呢你输入一个字符串,你就可以获得跟这个字符串相对应的一个整数了。 这是五个非常常用的这个函数。大家在写程序的过程中如果想用, 那你就随便用,没有问题,包括在我们的作业和考试里面,啊我绝对不会给你限制。 我们来观察一下这些函数。你看这些函数啊, 基本上它都满足这样的条件,给出一个输入,有一个输入的参数, 对于这个输入呢这个函数会返回一个与这个输入相对应的输出。 这个输入就相当于这个x,这个输出呢就相当于这个y。所以说你看, 其实在C程序里面的这些函数跟我们平常意义上我们认为的函数它其实是一样的。 正如我们在图灵机那个部分曾经讲过的, 任何一个计算机程序其实都可以被看作一个函数。因为 它都会处理一些输入的数据,并且给出来一个相应的输出。 所以说,我们才能够把程序表示为函数。 所以说在C语言里头,我们可以把所有的程序都组织成 函数。这就是C程序中的函数之所以存在的道理。 OK,那么在C程序中我应该怎么去定义一个函数呢?也就是说我想定义一个 自己的函数,那么在C程序中我应该怎么去定义呢?我们来看一下函数如何去定义。先来看一个例子。 这是一个很简短的一个例子。但是在这个例子里头呢, 有些程序是我们所熟悉的,有些我们不太熟悉。比方说,啊这两行,这是我们熟悉的。 下面啊main函数的部分也是我们熟悉的。 那么除了这个熟悉的部分之外,中间的这个里这个部分其实就定义了一个 函数。这个就是定义了一个函数。这个函数的名字呢叫做absolute, 它的作用呢我可以先告诉大家,它是一个用来求一个数的绝对值。在这个函数的名字的后面, 啊有一个括号,括号里面呢会写入 输入参数,那么当你定义一个函数的时候呢, 就需要在这列出来所有参数的类型。那么同时呢你要指定一个函数的返回值, 就是说这个函数可能会返回什么类型的值。有的同学说,我为什么需要指定一个 函数的返回值的类型啊,我干嘛要指定这个东西啊。那是因为你之所以 定义这个函数,你的目的是希望别人在程序里头调用你这个函数。 那如果别人不知道你这个函数可能会 返回一个什么类型的值,他就没办法去调用了, 或者调用起来就变得非常困难。 对这个程序而言,比方说我的主函数里头,我就调用了这个函数。啊有一个变量, 叫result,用来存放结果的。result里头存放什么结果呢? absolute(m),m呢是我定义的一个变量,它是-123,把它当做一个参数传递给这个 函数。我期望从这个函数呢获得一个返回值,给这个result。 那么既然我希望从这个函数获得一个值,那我就 否则我连定义一个什么类型的变量去接纳这个返回值,我都不知道。 所以说一定要指定返回值的类型。 OK。那么在写完了这个函数的名字的部分之后呢, 我们就在底下用一个大括号写出来函数体。 把所有的函数的执行过程、执行语句全部都写在函数体里头。 那么需要注意的是呢,在这个函数体里头啊,首先你就可以用你刚才在 这个参数定义部分所定义的这些参数,你就可以引用它了。其次呢, 因为你在函数的定义部分指定了函数的返回值,所以说你一定要在函数里面 返回一个相应类型的一个值。否则的话你就不满足函数的这个定义了。 那么通过这种方式,你就定义了一个函数。那么在这个程序里头啊, 我们把函数输入参数里面这个n称作形式参数, 啊简称形参。 也就说这个参数啊是一个形式上的参数,并不是真正让函数执行的时候 传给它的那个参数。这个参数啊只是用来辅助进行函数的 定义的。这叫形参。那么在函数运行的时候实际上传给 它的这个参数,也就是这个m被称作实际参数,简称实参。 也就说实际运行的时候传给它的这个参数。 这两个概念今后就是我们非常非常常用的两个概念。 那以后啊当我们提到形参的时候,我们指的就是函数定义里头定义的这个 输入参数。它是用来辅助定义这个函数的。那么当我们提到实参呢, 就是真正要运行这个函数的时候,我们传给这个函数的参数。 那我们来总结一下,如果要定义一个函数的话,你需要指定函数的 名字,并且呢给出来一个输入参数的列表,并且指明 每一个输入参数的类型,然后呢要说明输出也就是返回值的类型, 在这个基础上给出相应的函数体,这样呢我们就完成了一个函数的定义, 当然在这定义的函数呢,它只有一个参数,当然你也可以定义具有多个参数的函数。 比方说这个函数, 那么在这个程序里头呢,我们就定义了一个包含两个输入参数的一个函数, 这个函数的名字叫max,它的作用 就是返回两个输入参数中比较大的那个数, 比较大小,然后返回那个大的数,它的两个参数都是float型的, 而且它定义了那么返回值也是float 型的, 那么函数体也很明确,如果a大于b就return a,否则的话就return b。 当然无论a和b都是float型的,所以说满足返回值的这个条件。那么要调用这个函数的时候也极其简单, 我们就把相应的变量,比方说m和n是两个定义好的变量,传递给这个函数,然后呢我们就从 这个函数获得了一个m和n里面比较大的一个值,作为一个返回值。有的同学可能发现了, 你这个程序写的太啰嗦了,你为了求3和4的值还定义了这么多的变量,其实不用定义,如果简化一下这个程序的话,我完全可以写成这个, 直接c out, c out max(3,4),有的同学说这行吗,能这样写吗? 因为max是有返回值的呀,你在这呢你直接写出来它的值你往哪返回呀, 允许这样写吗?首先说这样写完全是允许的。那么函数的调用, 不一定我们非要搞一个变量去接纳它的这个返回值。函数的调用呢,是有多种形式可以选的。 根据函数在程序中出现的位置和形式,那么函数的调用呢可以分成以下三种, 也就是说任何一个函数我都可以以以下三种方式来调用它: 第一个就是作为独立的语句,就像刚才我们所看到的, c out 直接max (3,4) 没有任何问题,这是一种调用方式。第二种调用方式, 它可以作为表达式的一个部分,也就是说它可以写在一个式子里头,参与计算,比方说max(numA,numB) 然后呢,我直接把这个函数的调用写在一个表达式里然后除以2,没问题,这也是允许的。 第三种形式, 作为实际参数出现在其他函数的调用中,这也是可以的, 比方说,我们想调用一个函数,叫min, 它有两个参数,第一个,第二个, 那它第一个参数呢,我们就可以直接写一个函数调用在它参数的这个位置, 也就相当于我们要拿这个参数函数调用返回的值, 当作min函数的这个输入参数之一,这个也是允许的, 这三种调用方式都可以。所以说刚才我们所讲到的这种调用方式是没有任何问题的。 那我们可以这样去调用。 好,这是具有两个参数的函数。 那能不能定义没有参数的函数呢?当然可以,我们看这个例子, 那么在这个例子里,我就定义了一个函数,它没有任何的输入参数,那这个函数的作用呢? 其实就是帮助程序员用来完成输入的,首先它打印出一行please input an integer,输入一个整数, 然后它接着c in 把这个数读进来,然后立刻返回读入的这个数, 把这个数当作返回值返回出去,它没有任何的输入参数,这也是没有问题的,这是可以的。 在主程序里,调用这个函数,就可以完成一个整数的输入了, 这也是没有问题的,没有任何的参数。不但是没有任何的参数是可以的, 没有返回值照样是可以的。再看这个程序,在这个程序里头我也定义了一个函数, 名字叫做delay,看到这个名字我们就可以想象这个函数的作用。 这个作用是干嘛呀? 作为一个延迟的,有的时候在程序里头特别是输出的时候, 我们希望做一些延迟,它一个一个的来慢慢的输出, 那我们就可以构造一个类似的程序。当然这种方法并不是最好的,如果你真的想使用呢, 在C程序的库里有一些专门用来做延时的函数,你可以去使用。 在这呢,我们做一个例子来看一下。 既然是做延时,所以说这个函数啊我只需要它做事情,不需要它返回任何的值, 这个函数我们这样来定义,delay 它有一个输入参数n, 这个n来指定我循环多少多少次,当然为了取得延时我们给它乘了10万 用这个for循环来实现这个延时,当然这个方法很奢侈呀, 那么执行完这个延时以后,它也应该结束了, 那么结束的方式有两种,一个呢你可以写一个return, 因为我不需要它返回任何的值,所以return后面不需要写任何的数据,第二种方式你也可以不写, 不写也是可以的,既然这个函数没有任何的返回值, 我就需要告诉别人这个函数不会返回任何的东西, 于是我在定义这个函数的时候,我就指明它的返回值是void,也就是空返回值, 它不会返回任何的东西,你需要说明这一点。那么调用这样一个函数也是, 直接调用就可以啦,比方说我们在这,在for循环里头我们就可以调用这个delay, 也就是说函数不一定非要有一个确定的值返回,不返回值也可以, 还有一些函数呢,既不需要参数也不需要返回值, 比如说这个函数show, 这个函数的作用呢,它就是为了在屏幕上打印这些字符, 这就是当系统碰到错误时我们经常用的一个输出,那么为了省事, 不用每次都写这个输出,于是我就干脆定义了一个函数,这个函数 不需要任何的输入参数,也不需要任何的返回值,那么调用的时候呢我就直接调用它就可以。 那么通过这个讨论我们就能感受到,我们可以根据自己的需要, 定义任何类型的函数,它可以有返回值,也可以没有返回值, 它可以有输入参数也可以没有输入参数,也可以有多个输入参数。anyway,根据你的需要去定义。 那么除了这些之外还有一个函数,是我们每次写程序都需要去定义的, 那就是main函数。大家还记得这个程序吗,这个程序呢是 在这个课上我展示给大家的最简单的第一个程序。 其实这个程序的目的就是为了定义这个main函数, 说到这我们终于能够掉回头来解释一下这个main函数了。 看看这个程序,在main的后面,也有一个括号, 那我们现在知道其实这个括号是用来放输入参数的, 那么我们之前写的程序里头呢,这个括号里头都是空的,其实啊, 这个括号里头是可以写东西的。main函数是可以带参数的,如果有时间的话,以后我们还会提到这个问题。 那main函数呢,还有一个返回值,约定呢是int类型的, 也正是因为main函数要求有一个int类型的返回值, 所以说无论这个程序执行什么东西,到最后啊我们都要 返回一个整数给它。如果我们没什么想要返回的, 那我们就写return 0,这个return 0就有两个作用,一个作用是结束当前的main函数, 另一个呢就是要满足函数返回值的这个要求。 那说到这我顺便来解释一个问题, 有的同学可能在一些资料上看到,说老师我看到它们的main函数是这样来定义的, void main, 然后呢,因为它是void,返回值是空, 所以说我也不需要写return 0, 那用这种方式来定义main函数可以吗? 那这两种方式又有什么区别呢?稍微回答一下,那么这种定义方式是老的C标准允许的一种定义方式。 现在我们所使用的返回值为int 类型的这种定义方式呢, 是新的C语言标准所要求的一种方式。 当然在一些编程环境里头, 仍然兼容了老的这种方式,也就是说你仍然可以用这种方式。 它们两位有什么区别呢? 老的这种方式因为返回值是空,也就是说它不会返回任何的东西, 所以呢调用这个main函数的程序体没办法获知, 这种类型的main函数它执行的结果到底如何,这就是它的一个小的弊端。 所以说新的编程环境往往都要求大家使用这种新的方式来定义。 那为什么要在程序里面一定要定义main函数呢? 因为main函数是程序执行的入口,那么有了main函数, 我们就可以在这个基础上定义任何其他的函数。 那么在一个程序里头我可以定义多少个函数啊?多少个都可以。 啊,你可以定义多个函数。有的同学说,那一个文件我得写多长啊? 其实啊,你可以在一个程序里头写多个文件。 啊,也就是说,一个程序啊,可以由多个源文件来组成。 那一个源文件里头呢,又可以包含多个函数。 啊这都是允许的。只要你有一个 main 函数,作为执行的入口,啊就可以。 比方说刚才的那个程序啊,我们就可以把它拆分到多个文件里头。 看这个例子。在这个例子里头啊,我就把一份程序, 拆分到两个文件里头。 其中的一个文件包含了 main 函数。 包含 main 函数的文件呢,我们把它命名作 compare.cpp , 这是我们平常保存文件的那种格式。啊, cpp 文件。那么除了 main 函数之外呢, 比方说我还定义了这样的一个函数。那这个函数呢,我可以把它放在 max.h 这个文件里头。 然后呢我们把这两个文件都放在同一个目录之下。那怎么才能说明它们两个连起来呢? 在包含 main 函数的这个文件里头我可以写这样一句,include max.h, 然后呢,用双引号把它引起来。通过这种方式,我就可以把这两个文件连成一个程序。 那么它的效果呢,就跟放到一个文件里头是完全一样的。在这儿呢,也做一个小的说明。 有的同学可能要问了,在这儿为什么要用双引号?为什么不能用这个间括号呢? 那么在微 C 的环境之下,这跟微 C 的变应切所默认的搜索入境是有关系的。 那么如果你使用这个间括号,那么它就会默认的去搜索那些系统函数库所在的目录。 如果你使用双引号,啊它就会优先去搜索当前的这个目录。 啊然后呢,再去搜索系统函数库。 所以说,使用双引号才能确保它能够找得着我们所定义的这个 max.h 这个文件。 Ok , 这就是关于函数的定义。那么在这儿呢,还想给大家介绍一个概念。就是说啊, 所有的函数,其实也是有类型的。 那什么叫做函数的类型呢? 其实很简单。函数的类型啊,是指函数的返回值的数据类型。 也就是说,一个函数,它会返回什么类型的数据, 那这个函数本身的类型,就是什么类型。 啊比方说 main 函数, 因为它会返回 int 类型的数,所以说我们说 main 函数的类型是 int 型。 那有的同学说,哎呀,为什么提这样一个概念,听上去怪怪的。 啊,没关系。 你呢,先记得这样一个概念。啊,有这样的一个印象。 以后啊,会用得着的。啊,也就是说,以后用得着的时候,你想起来, 啊,函数的类型指的却是它,就是它的返回值的类型就 Ok 了。 这是关于函数的定义。 那么,说完关于函数的定义啊,我相信大家就算是比着葫芦画瓢, 也能够定义自己的函数了。在一个程序里头,我可以定义很多个函数。 那这些函数我应该定义在哪呢?是不是我都必须把它 定义在像 main 函数和预定义部分之间呢? 啊,定义在这个地方呢?我能不能把函数定义在 main 函数的后面呢? 可以吗?可以。 可以。但是,当你把一个函数定义在 main 函数的 后面的话,那么你就必须得在 main 函数的前面 多写一句, 先声明一下,啊,有这样一个函数。 比方说,看右边这个程序。因为我把函数的定义挪到了 main 函数的后面, 所以说,我在 main 函数的前面多写了这样一句定义。 首先解释一下为什么要在前面写这一句。定义器啊,在解释你所写出来的这个程序的时候啊, 是按照从上往下的这样的一个顺序来解释的。 那么如果你把函数的定义写到了后面, 在前面呢,又没有进行说明。那么当定义器 读到 max 的这个东西的时候啊,它就不知道这个东西是什么。 那么就产生了一个疑问,这个东西是个什么呀?它到底是个变量呢, 还是一个标示符呢,还是一个什么东西?这就不知道了。 所以说啊,如果我们把函数体定义在 main 函数的后面,我们就需要在 main 函数的前面 写这样一句。在明白了这个原因的基础上,我们再来 讨论一下我们写下来的这个东西。 这个东西是什么呢?我们所写出来的这个东西啊,被称为函数的原型。 那什么叫做原型呢?一个函数的原型啊,首先包含了这个函数的名字。 然后呢,包含了这个函数的所有的输入参数的类型。 需要强调的是,只需要给出来类型就可以。再一个呢,需要指定函数的 返回值的类型。也就是说,所谓函数的原型,其实就是指 返回值的类型加上函数名再加上参数的类型。 对于任何一个函数而言, 只要你告诉了我,这一些东西。你看,我是不是 从形式上我就应该能够去调用这个函数。 比方对于这样一个函数, 啊,如果它的原型是 float max (float, float) , 那我就可以知道,如果想调用这个函数,它有两个参数。这两个参数的类型都是 float 的类型的。而且呢,这个函数会返回一个 float 的型档结果。 从形式上我就可以去把握它了。 所以说啊,拿到一个函数的原型,我们就可以知道该怎么去调用这个函数了。 当然,仅凭这个函数的原型, 我们并不能确定这个函数做了些什么。 啊,但是呢,它的调用的形式,我们就已经可以掌握了。 啊,这就是函数的原型的作用。 Ok, 所有这儿稍微做一个总结,啊关于函数的原型和函数的声明。 所谓函数的原型,啊是指由函数的返回类型,函数名以及参数表所构成的一个符号串。 啊,其中呢,参数可以不写名字。啊,比方说 bool checkPrime (int) , 我就可以知道,这个函数的名字,它的所有参数呢, 只有一个是 int 类型的。那它的返回值呢,是 bool 类型的。 正因为获得了一个函数的原型,我们就知道 如何去调用这个函数了。所以说我们又把函数的原型 充作这个函数的 Signature 。那么很多地方把这个 Signature 翻成函数的签名。Ok, 也行吧!啊,就是一个名字而已。 那么在软件工程里面呢,我们通常把这个东西称作函数的基调。 Ok, 那么,如果一个函数它的定义不肯被写在 main 函数的后面, 我们就需要利用函数的原型来对函数做一个 声明。啊,就是在使用函数前都要进行声明。 除非被调用的函数部分出现在主函数之前。 也就是说,在 C 语言中,啊,那么对函数进行声明, 使用的就是函数的原型。 这是刚才我们所讲过的。