首先是分支循环部分,一个猜数字小游戏的记录。
对于这样一个小小游戏的实现,我们有以下思考:
1.作为一个游戏,我们在启动程序之后首先就应该显示菜单,这不需要任何的判断条件,因此我们可以用do while 循环来实现。
2.显示菜单之后,我们应该有一些选项供玩家选择,如开始游戏、退出游戏等,根据玩家的选择进行下一步操作,因此可以用switch语句来实现这一功能。
3.在游戏本体部分,既然是猜数字游戏,系统得随机生成一个数字来让玩家去猜,因此涉及到随机数的生成。
4.在生成随机数之后,将玩家输入的数字与随机数进行比较,并将比较的结果适当地反馈给玩家。
有了上面地思考,我们将代码实现如下:
运行玩一把游戏,情况如下:
至此我们就实现了这样一个简单的小游戏。
初识函数
知识点总结
1.C语言常用的库函数有:IO函数、字符串操作函数、字符操作函数、内存操作函数、时间\日期函数、数学函数和其他库函数。在使用这些库函数时,我们必须包含#include对应的头文件。以下的一些网站可以帮助我们学习库函数的使用方法,以及每个库函数对应的头文件。
在调用Swap1函数之后,x和y的值其实已经被交换了,但是形参是实参的一份临时拷贝,x和y的地址与num1和num2的地址都不一样,因此我们对形参的修改并不会影响到实参,所以在此基础上我们想到,可以对num1和num2的地址传过去,调用Swap函数远程交换num1和num2。代码如下:
函数的调用分为传值调用和传址调用。
传值调用中,函数的形参和实参分别占用不同的内存块,对形参的修改不会影响实参。
而传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式,这种传参方式可以让函数和函数外部的变量建立起真正的联系,也就是该函数内部可以直接操作函数外部的变量。
3.函数的嵌套调用:
函数和函数之间可以根据实际的需求进行组合,相互调用:
注:函数可以嵌套调用,但是不能嵌套定义。
4.函数的链式访问:
把一个函数的返回值作为另一个函数的参数:
这里我们看一个题目:
查看了MSDN后,我们知道printf的返回值是打印的字符的个数
所以上面的式子中,先执行①,打印43,然后①的返回值是2,所以②打印2,然后返回1,最后①打印1。最终我们看到的结果就是4321。
5.函数的声明和定义:
①函数声明就是告诉编译器有一个函数叫什么,参数是什么,返回类型是什么,但是具体是不是存在,函数声明决定不了。函数声明一般出现在函数的使用之前,要满足先声明后使用,并且函数声明一般放在头文件中。
②函数的定义是指函数的具体实现,交代函数的功能实现。
5.函数递归
程序调用自身的编程技巧称为递归,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,其主要思考方式在于:把大事化小。
递归的两个条件:
①存在限制条件,当满足这个限制条件的时候,递归将不再继续。
②每次递归调用之后越来越接近这个限制条件。
关于函数递归,我们看几个题目来感受递归的思想。
①接受一个整型值(无符号),按照顺序打印它的每一位,例如输入1234,输出1 2 3 4.
代码实现如下:
②编写程序不允许创建临时变量,求字符串长度。代码实现如下:
求字符串的长度还有两个方法,一个是创建临时变量进行计数,还有一个用指针计数的方式,在此不讨论。
③求n的阶乘(不考虑溢出)。代码实现如下:
在这个函数中,如果我们计算10000的阶乘(不考虑结果的正确性),程序会崩溃,并报错:stack overflow(栈溢出)的信息。因为系统分配给程序的栈空间是有限的,但是如果出现了死循环或者“死递归”,这样就有可能导致一直开辟栈空间,最终导致栈空间耗尽的情况,这样的情况我们称为栈溢出。
④求第n个斐波那契数(不考虑溢出)。代码实现如下:
在这个程序中,如果我们要计算第50个斐波那契数字,我们会发现特别消耗时间,因为该函数在调用的时候很多计算其实一直在重复,造成了大量的浪费,我们修改一下代码,看一下在求第50个斐波那契数的时候,第3个斐波那契数计算了几次:
由于第50个斐波那契数计算时间太长,我们用30代替,我们看到计算第30个斐波那契数的时候,第三个斐波那契数被计算了317811次,由此可见,计算第50个斐波那契数的时候,这个数字会成倍增长。
那么第三和第四个题目中出现的问题该怎么解决呢?
①将递归改为非递归。很多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更清晰,但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。只有当一个问题相当复杂,难以用迭代实现的时候,此时递归实现的简洁性便可以补偿它所带来的运行时的开销。
将两道题改成非递归,代码实现如下:
求n的阶乘
求第n个斐波那契数
不考虑数字正确性的前提下(数字太大),输入50瞬间就能给出结果。
②第二种解决方案就是使用static对象替代 nonstatic 局部对象。在递归函数设计中,可以使用static 对象替代nonstatic 局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放 nonstatic 对象的开销,而且 static 对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。
最后还有一个个人觉得比较复杂的问题:字符串逆序。
用递归来实现字符串逆序,将参数字符串中的字符反向排列,不是逆序打印,也不能使用C函数库中的字符串操作函数。代码实现如下: