【C语言探索之旅】 第一部分第九课:函数
内容简介
1、课程大纲
2、第一部分第九课:函数
3、第一部分第十课预告: 练习题+习作
课程大纲
我们的课程分为四大部分,每一个部分结束后都会有练习题,并会公布答案。还会带大家用C语言编写三个游戏。
C语言编程基础知识
什么是编程?
工欲善其事,必先利其器
你的第一个程序
变量的世界
运算那点事
条件表达式
循环语句
实战:第一个C语言小游戏
函数
练习题
习作:完善第一个C语言小游戏
C语言高级技术
模块化编程
进击的指针,C语言王牌
数组
字符串
预处理
创建你自己的变量类型
文件读写
动态分配
实战:“悬挂小人”游戏
安全的文本输入
练习题
习作:用自己的语言解释指针
用基于C语言的SDL库开发2D游戏
安装SDL
创建窗口和画布
显示图像
事件处理
实战:“超级玛丽推箱子”游戏
掌握时间的使用
用SDL_ttf编辑文字
用FMOD控制声音
实战:可视化的声音谱线
练习题
数据结构
链表
堆,栈和队列
哈希表
练习题
第一部分第九课:函数
最近各种社交媒体和新闻都被姚贝娜和杰伦刷屏了。一个令人惋惜,一个令人高兴。
我觉得怎么也得让编程来刷一下屏吧。哈哈,开玩笑的,今天就用函数来刷屏好了。
说是刷屏,其实也是翻页,因为这一课我们将会用函数这个重中之重来结束《C语言探索之旅》的第一部分(基础部分),而第二部分将要迎接我们的就是C语言的高级技术了。刷走了一个部分,准备迎接新的挑战。
第二部分会比较难哦,不过不用担心,我们一点点学习。只要方向对,肯花时间,C语言并不可怕。
这一课里我们也会给大家讲C语言程序所基于的原则。
我们将要学习如何将程序分块管理,有点像乐高积木...
其实所有C语言的大型程序都是小程序块的集合,而这些小程序块我们称之为函数。在面向对象的语言如Java里面,函数又被称为方法,当然这里我们只讨论C语言(面向过程的语言),不讨论面向对象的语言,sorry,我又废话了... 好吧,我们只面向C语言这个对象。
函数的创建和调用
在之前的课程中我们已经学过所有的C语言程序都是由main函数开始运行的。那时候我们也展示了一个概要图,里面有一些术语:
最上面的部分我们称之为“预处理指令”,很容易辨识,因为以#号开头,而且总是放在程序的最前面。
下面的部分就是我们要学习的函数了,这里的示例是main函数。
前面说过,C语言的程度都是以main函数为入口函数的。一个C程序要运行,必须要有main函数。只不过,目前为止我们写的所有程序 包括上一课的小游戏,也只是在main函数里面捣鼓而已,我们还没跳出过main函数过。没办法,有钱任性,没钱只能…
那你要问了:这样不好吗?
答案是:并不是说这样不好,但这并不是C程序员平时所做的。几乎没有程序员会只在main函数的大括号内部写代码。到目前为止我们所写的程序都还比较短小,但是想象一下如果程序变得很大,代码几千几万甚至上百万行,难道我们还把这些代码都塞在main函数里面吗?
所以我们现在来学习如何更好地规划我们的程序。我们要学习将程序分成很多小块,就像乐高积木的每一个小块一样,这些小块搭起来却可以组成很多好玩的形状。这些程序小块我们称其为函数。
一个函数会执行某些操作,并返回一个值。程序就是一个代码序列,负责完成特定的任务。
我们说一个函数有输入和输出,如下图所示:
可以把函数想象成一台制作香肠的机器,在输入那一头你把猪装进去,输出那一头就出来香肠了。这酸爽。
当我们在程序中调用一个函数的时候,会依次发生三个步骤:
输入:给函数传入一些信息(借着给函数一些参数)
运算:因着输入里传进去的信息,函数就可以完成特定任务了
输出:做完运算后,函数会返回一个结果。我们称其为输出或者返回值
举个实际例子,比如我们有个函数叫做multipleTwo,作用是将输入乘以二,如下所示:
函数的目的是为了让源代码更加结构分明,也节省源代码数目,因为我们就不用每次都输入重复的代码片段而只需要调用函数就好了。
再设想一下:之后我们可能会想要创建一个叫showWindow(显示窗口)的函数,作用是在屏幕上显示一个窗口。一旦函数写好之后(当然写的过程是最难的),我们就只需要说:“那个谁,给我去打开一个窗口”,showWindow函数就会为我们在屏幕上显示一个窗口。我们也可以写一个displayPersonage的函数,作用是为我们在屏幕上显示一个游戏人物。
函数的构成
我们在之前的课中已经接触过函数了,就是非常重要的main函数。然而还是需要我们来介绍一下一个函数的构成到底是怎么样的。
下面是函数的语义学的结构,这是一个需要了解的模板:
类型 函数名(参数)
{
// 函数体,在这里插入指令
}
关于这个模板我们需要掌握四点:
函数类型:对应输出类型,也可以把其看做函数的类型。和变量类似,函数也有类型,这类型取决于函数返回值的类型。如果一个函数返回一个浮点数(带小数点的),那么自然我们会将函数类型定为float或者double;如果返回整数,那么我们一般会将类型定为int或long。但是我们也可以创建不返回任何值的函数。
函数名:这是你的函数的名字。你可以给你的函数起任意名字,只要遵从给变量命名的相同的规则就好。
函数的参数(对应输入):参数位于函数名之后的圆括号内。这些参数是函数要用来做操作(运算)的数据。你可以给函数传入任意数量的参数,也可以不传入任何参数。
函数体:大括号规定了函数的起始和结束范围。在大括号中你可以写入任意多的指令。对于上面的multipleTwo函数,需要写入将输入的数字乘以2的相关操作指令。
根据函数类型,函数可以分为两类:
返回一个值的函数,这样的函数,我们将其类型定为对应的值的类型(char,int,long,double等)
不返回任何值的函数,这样的函数,我们将其类型定为void(void在英语中是“空的,无效的”之意)
创建函数
我们不要再迟延了,马上给出一个实例。用的还是我们上面提过的multipleTwo这个函数:这个函数的输入是一个整型int,输出也是int类型的数。
int multipleTwo(int number)
{
int result = 0;
result = 2 * number; // 我们将提供的数乘以2
return result; // 我们将2倍的数返回
}
这就是你的第一个除了main以外的函数,自豪不?
return result;
这句话一般放在函数体的最后,用于返回一个值。这句话意味着:“函数你给我停下,然后返回这个值”。这里的result必须是int类型的,因为函数类型是int,所以返回值也必须是int类型。
result这个变量是在multipleTwo函数中声明/创建的,所以它只能在这个函数里面用,不能在另一个函数比如main中使用,所以是multipleTwo函数的私有变量。
但上面的代码是不是最简单的呢?
不是,还可以简化,如下:
int multipleTwo(int number)
{
return 2 * number;
}
上面的代码做的是一样的事情,写起来也更简单,函数体内只有一句话。
通常来说,我们写的函数都会有多个变量,以便做运算,multipleTwo这个函数算是相当简单了。
多个参数,或没有参数
多个参数
我们的multipleTwo函数只有一个参数,但是我们也可以创建有几个参数的函数,比如下面这个加法函数addition:
int addition(int a, int b)
{
return a + b;
}
可以看到,只需要用一个逗号来分隔参数就好了。
没有参数
有些函数,虽然不太常见,可能会没有参数。例如一个用来显示Hello(你好)的函数:
void hello()
{
printf("Hello");
}
如上所见,这个函数没有任何参数,此外,可以看到我们还把函数类型定为了void,就是空,所以也没有return语句用于返回一个值,所以这个函数也没有返回值。彻头彻尾的黑五类…
调用函数
现在我们来看一个程序,复习一下我们之前学的内容。
我们要用到我们的multipleTwo函数,来计算一个数的两倍的值。
我们暂时把multipleTwo函数写在main函数之前,如果放在main函数之后会出错,以后的课程我们会解释为什么。
#include <stdio.h>
int multipleTwo(int number)
{
return 2 * number;
}
int main(int argc, char *argv[])
{
int initial = 0, twice = 0;
printf("请输入一个整数... ");
scanf("%d", &initial);
twice = multipleTwo(initial);
printf("这个数的两倍是 %d\n", twice);
return 0;
}
我们的程序是从main函数开始运行的,这个大家已经知道了。
我们首先请求用户输入一个整数,将其值传递给multipleTwo函数,并且把multipleTwo函数的返回值赋给twice这个变量。
仔细看下面这一行,这是我们最关心的一行代码,因为正是这一行调用了我们的multipleTwo函数。
twice = multipleTwo(initial);
在括号里,我们将变量initial当做输入传递给函数,也正是这个变量,函数将要用于其内部的处理。
这个函数返回一个值,这个值我们赋给twice这个变量。
其实这一行中,我们就是命令电脑:“让multipleTwo函数给我计算initial的两倍的值,并且将结果储存到twice这个变量中”。
详细的分步解释
也许对于初学者,理解起来还是有些许困难。
不用担心,我相信通过下面的分步解释,大家会明白得更透彻。
这个特殊注释的代码向大家展示了程序的运行顺序:
#include <stdio.h>
int multipleTwo(int number) // 6
{
return 2 * number; // 7
}
int main(int argc, char *argv[]) // 1
{
int initial = 0, twice = 0; // 2
printf("请输入一个整数... "); // 3
scanf("%d", &initial); // 4
twice = multipleTwo(initial); // 5
printf("这个数的两倍是 %d\n", twice); // 8
return 0; // 9
}
上面的编号表示了执行的顺序:1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9
程序从main函数开始执行
在main函数中的命令一行一行地被执行
执行printf输出
执行scanf读入数据,赋值给变量initial
读入指令... 啊,调用multipleTwo函数了,因此程序跳到上面的multipleTwo函数中去执行
我们运行multipleTwo函数,并接受一个数作为输入(number)
我们对number做运算,并且结束multipleTwo函数,return意味着函数的结束,并且返回一个值
我们重新回到main函数的下一条指令,用printf输出
又一个return,这次是main函数的结束,于是整个程序运行完毕。
变量initial被传值给multipleTwo的参数number(另一个变量),称为值传递。当然其实原理是做了一份变量initial的拷贝,把拷贝赋值给了number,这个值传递的概念以后学习指针那一章会再详述。
这里如果我们把initial改名为number也是可以的,并不会与函数multipleTwo的参数number冲突。因为参数number是属于multipleTwo这个函数的专属变量。
测试程序
下面是程序运行起来的一个实例:
请输入一个整数... 10
这个数的两倍是 20
当然你不必将multipleTwo函数的返回值赋给一个变量,也可以直接将multipleTwo函数的返回值传递给另一个函数,就好像multipleTwo(intial)是一个变量一般。
仔细看下面这个程序,跟上面几乎是一样的,但是修改了最后一个printf的行为,我们也没有使用twice这个变量,因为不必要:
#include <stdio.h>
int multipleTwo(int number)
{
return 2 * number;
}
int main(int argc, char *argv[])
{
int initial = 0, twice = 0;
printf("请输入一个整数... ");
scanf("%d", &initial);
// 函数的结果(返回值)直接传递给printf函数,而没有通过第三方变量
printf("这个数的两倍是 %d\n", multipleTwo(initial));
return 0;
}
我们可以看到,这次的程序直接将multipleTwo函数的返回值传递给了printf函数。
当程序运行到这一行会发生什么呢?
很简单,电脑看到这一行是printf函数,所以调用标准输入输出库的printf函数,向printf函数传递我们给的所有参数。第一个参数是要显示的语句,第二个参数是一个整数。电脑又知道要把这个整数值传递给printf函数,必须先调用multipleTwo函数,所以它就乖乖地去调用multipleTwo函数,做两倍乘法运算,并且直接把结果传递给printf函数。
这就是函数的层叠式调用,这样做的好处是,一个函数可以按需调用另一个函数。
只要愿意,我们的multipleTwo函数也可以再调用其他的函数,只要你肯写,然后这个函数再调用其它函数,依次类推。
这就是C语言程序所基于的原则。所有的代码都是有规划地组合在一起的,类似乐高积木。
最后,最艰难的当然是编写函数了,一旦完成,你就只需要调用它就好了,不需要太担心函数内部所做的运算。使用函数可以大大降低代码的重复度,相信我,你会非常需要函数的。
一些函数的实例
如果一起学习过之前的课程,你应该会有这种印象:我就是个例子狂人。
是的,因为我很喜欢用实例来加深理解。我可不是什么superhero(超级英雄),比如Iron Man,绿巨人,蚁人,等。
因为我觉得理论虽好,但如果只有理论,那我们就不能很好地掌握知识,而且不知道怎么应用,那就很可惜了。想起了“劲酒虽好,可不要贪杯哦”那句广告词…
所以下面我们会一起看几个函数的实例,以便读者对函数有更深入的了解。我们尽量展示不同情况,使大家看到可能出现的各种函数类型。
欧元/人民币转换
最近欧元大贬值,已经降到7左右了,我们就来写一个函数,用于转换欧元到人民币。
查了一下最新的汇率:
1欧元=7.1874人民币元
#include <stdio.h>
double conversion(double euros)
{
double rmb = 0;
rmb = 7.1874 * euros;
return rmb;
}
int main(int argc, char *argv[])
{
printf("10 欧元 = %f 人民币\n", conversion(10));
printf("50 欧元 = %f 人民币\n", conversion(50));
printf("100 欧元 = %f 人民币\n", conversion(100));
printf("200 欧元 = %f 人民币\n", conversion(200));
return 0;
}
你也可以写一个人民币转换为欧元的小程序。
惩罚
接下来看一个函数,这个函数不会返回任何值,所以类型是void。这个函数会根据传入的参数在屏幕上显示一定次数的信息。这个函数只有一个参数,那就是显示惩罚语句的次数:
#include <stdio.h>
void punish(int lineNumber)
{
int i;
for (i = 0 ; i < lineNumber ; i++)
{
printf("我不应该有钱任性\n");
}
}
int main(int argc, char *argv[])
{
punish(5);
return 0;
}
显示结果如下:
我不应该有钱任性
我不应该有钱任性
我不应该有钱任性
我不应该有钱任性
我不应该有钱任性
矩形面积
矩形的面积很容易计算:长 x 宽。
我们来写一个求矩形面积的函数,它有两个参数:矩形的长和矩形的宽。返回值是矩形的面积:
#include <stdio.h>
double rectangleArea(double length, double width)
{
return length * width;
}
int main(int argc, char *argv[])
{
printf("长是10,宽是5的矩形面积是 %f\n", rectangleArea(10, 5));
printf("长是3.5,宽是2.5的矩形面积是 %f\n", rectangleArea(3.5, 2.5));
printf("长是9.7,宽是4.2的矩形面积是 %f\n", rectangleArea(9.7, 4.2));
return 0;
}
显示结果:
长是10,宽是5的矩形面积是 50.000000
长是3.5,宽是2.5的矩形面积是 8.750000
长是9.7,宽是4.2的矩形面积是 40.740000
我们可以直接在函数里显示 长,宽和计算所得的面积吗?
当然可以。这样的情况下,函数就不必返回任何值了,函数计算出矩形面积,然后直接显示在屏幕上:
#include <stdio.h>
void rectangleArea(double length, double width)
{
double area = 0;
area = length * width;
printf("长为 %f 宽为 %f 的矩形的面积是 %f\n", length, width, area);
}
int main(int argc, char *argv[])
{
rectangleArea(10, 5);
rectangleArea(3.5, 2.5);
rectangleArea(9.7, 4.2);
return 0;
}
我们可以看到,printf函数在函数体内被调用,显示的结果和之前把printf放在main函数里是一样的。只不过我们用的方法不一样罢了。
菜单
还记得之前的课程中菜单的那个例子吗?(皇上,您还记得大明湖畔的夏雨荷么?)
这次我们用自定义的函数来重写一次,会更详细和优化:
#include <stdio.h>
int menu()
{
int choice = 0;
while (choice < 1 || choice > 4)
{
printf("菜单 :\n");
printf("1 : 北京烤鸭\n");
printf("2 : 麻婆豆腐\n");
printf("3 : 鱼香肉丝\n");
printf("4 : 剁椒鱼头\n");
printf("您的选择是 ? ");
scanf("%d", &choice);
}
return choice;
}
int main(int argc, char *argv[])
{
switch (menu())
{
case 1:
printf("您点了北京烤鸭\n");
break;
case 2:
printf("您点了麻婆豆腐\n");
break;
case 3:
printf("您点了鱼香肉丝\n");
break;
case 4:
printf("您点了剁椒鱼头\n");
break;
}
return 0;
}
这个程序还可以改进:你可以在用户输入一个错误的数字时显示一个错误信息,而不是直接继续让其点单。
总结
函数之间可以互相调用,因此main函数可以调用C语言系统定义好的函数例如scanf和printf等,也可以调用我们自己定义的函数
一个函数接受一些变量作为输入,我们将其称为函数的参数(也有空(void)参数的函数)
函数会用这些参数来做一系列的操作,之后会用return返回一个值(也有无返回值的函数)
第一部分第十课预告:练习题+习作
今天的课就到这里,一起加油咯。
下一次我们学习第一部分第十课,来做一些帮助巩固知识点的练习题吧!有答案,但是希望大家先做完再对答案。
程序员联盟
微信公众号*您若觉得本文不错,请点击画面右上角《···》按钮“分享到朋友圈”或“发送给朋友”
*新朋友请关注「程序员联盟」微信搜公众号
ProgrammerLeague
小编微信号:
frogoscar
小编QQ号: 379641629
小编邮箱: enmingx@gmail.com
(微信和邮箱最常用)
PS: 有朋友反映看手机端的文章太累,其实是可以用浏览器网页来看的
方法1. 点击画面右上角的《···》按钮,然后选择“复制链接”,再把链接黏贴到你的浏览器里面或用邮件发送给自己,就可以在电脑的浏览器里打开了
方法2. 头条网www.toutiao.com,搜索我的自媒体“程序员联盟”,里面有所有文章,也可以直接进这个链接:http://www.toutiao.com/m3750422747/
新朋友如何查看所有文章:
点击“查看公众号”,再点击“查看历史消息”
“程序员联盟”公众号专为程序员,App设计师,各位喜爱编程和热爱分享的小伙伴们推送各样编程相关知识,优秀软件推荐,业界动态等。搜索
ProgrammerLeague 加关注~
持续关注 程序员联盟 微信公众号,更多有趣,有料,有亮点的内容等着你哦!
【C语言探索之旅】 第一部分第九课:函数的更多相关文章
- 【C语言探索之旅】 第三课:你的第一个程序
内容简介 1.课程大纲 2.第一部分第三课:你的第一个程序 3.第一部分第四课预告:变量的世界 课程大纲 我们的课程分为四大部分,每一个部分结束后都会有练习题,并会公布答案.还会带大家用C语言编写三个 ...
- 【C语言探索之旅】 第一部分第八课:第一个C语言小游戏
内容简介 1.课程大纲 2.第一部分第八课:第一个C语言小游戏 3.第一部分第九课预告: 函数 课程大纲 我们的课程分为四大部分,每一个部分结束后都会有练习题,并会公布答案.还会带大家用C语言编写 ...
- 【C语言探索之旅】 第三部分第二课:SDL开发游戏之创建窗口和画布
内容简介 1.第三部分第二课: SDL开发游戏之创建窗口和画布 2.第三部分第三课预告: SDL开发游戏之显示图像 第三部分第二课:SDL开发游戏之创建窗口和画布 在上一课中,我们对SDL这个开源库做 ...
- 【C语言探索之旅】 第二部分第二课:进击的指针,C语言的王牌!
内容简介 1.课程大纲 2.第二部分第二课: 进击的指针,C语言的王牌 3.第二部分第三课预告: 数组 课程大纲 我们的课程分为四大部分,每一个部分结束后都会有练习题,并会公布答案.还会带大家用C语言 ...
- 【C语言探索之旅】 第二部分第一课:模块化编程
内容简介 1.课程大纲 2.第二部分第一课: 模块化编程 3.第二部分第二课预告: 进击的指针,C语言王牌 课程大纲 我们的课程分为四大部分,每一个部分结束后都会有练习题,并会公布答案.还会带大家用C ...
- 【C语言探索之旅】 第一部分第十课:练习题+习作
内容简介 1.课程大纲 2.第一部分第十课: 练习题+习作 3.第二部分第一课预告: 模块化编程 课程大纲 我们的课程分为四大部分,每一个部分结束后都会有练习题,并会公布答案.还会带大家用C语言编写三 ...
- 【C语言探索之旅】 第二部分第九课: 实战"悬挂小人"游戏 答案
内容简介 1.课程大纲 2.第二部分第九课: 实战"悬挂小人"游戏 答案 3.第二部分第十课预告: 安全的文本输入 课程大纲 我们的课程分为四大部分,每一个部分结束后都会有练习题, ...
- 【C语言探索之旅】 开宗明义及第一课:什么是编程?
内容简介 1.课程大纲 2.第一部分第一课:什么是编程? 3.第一部分第二课预告:工欲善其事,必先利其器 课程大纲 不知道为什么,一直对C语言有一种很深厚的“情怀”(类似老罗对锤子手机的那种),说 ...
- 【C语言探索之旅】 第二部分第八课:动态分配
内容简介 1.课程大纲 2.第二部分第八课: 动态分配 3.第二部分第九课预告: 实战“悬挂小人”游戏 课程大纲 我们的课程分为四大部分,每一个部分结束后都会有练习题,并会公布答案.还会带大家用C语言 ...
随机推荐
- hdu2606(递推)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2606 题意: 用1*1,2*2,3*3,4*4的正方形填充4*n的矩形, 问有多少种不同填法. 分析 ...
- 怎样在Linux下通过ldapsearch查询活动文件夹的内容
从Win2000開始.微软抛弃NT域而採用活动文件夹来管理Windows域.而活动文件夹就是微软基于遵守LDAP协议的文件夹服务.假设用扫描器扫描的话能够发现活动文件夹的389port是打开的.并且微 ...
- C#函数参数传递解惑
C#语言函数参数的传递 就像C语言众多的后世子孙一样,C#的函数参数是非常讲究的.首先,参数必须写在函数名后面的括号里,这里我们有必要称其为形参.参数必须有一个参数名称和明确的类型声明.该参数名称 ...
- Java多线程使用场景
使用多线程就一定效率高吗? 有时候使用多线程并不是为了提高效率,而是使得CPU能够同时处理多个事件. 使用场景1 为什么了不阻塞主线程,启动其他线程来做耗时的事情. 比如app开发中耗时的操作都不在U ...
- codeforces 112APetya and Strings(字符串水题)
A. Petya and Strings 点击打开题目 time limit per test 2 seconds memory limit per test 256 megabytes input ...
- B桥接模式ridge
1.一个简短的引论 1)模式概述:将抽象部分与实现部分分离.使它们都能够独立的变化.让抽象类和派生类各自实现自己的对象.当一个系统有多维度的变化时,将各个维度分离出来让它们独立于变化(多角度地分类实现 ...
- 使用配置文件(.settings、.config)存储应用程序配置
原文:使用配置文件(.settings..config)存储应用程序配置 引言 我不知大家早先是如何保存应用程序配置,以备下次打开时使用的,反正我开始学.Net的时候就去研究序列化,以二进制或XML格 ...
- java名词,关键字
抽象类:规定一个或多个抽象方法的类别本身必须定义为abstract,抽象类只是用来派生子类,而不能用它来创建对象. final类:又称“最终类”,它只能用来创建对象,而不能被继承,与抽象类刚好相反,而 ...
- hdu1754(splay)
给n个数,有两种操作 Q a b 询问区间[a,b]的最大值, U a b 将第a个数的值改成b splay树的中序遍历是我们所维护的序列.如果要询问区间[a,b]的最大值,那么只要将第a ...
- windows phone (19) 深入了解TextBlock
原文:windows phone (19) 深入了解TextBlock TextBlock 一般用于显示文本的元素,我们最为经常用到的是该类的Text属性,其实显示文本有两种呈现方式,一个是设置内部文 ...