C博客作业03

第三次C博客作业


Q0.展示PTA总分


Q1.本章学习总结

1.1 学习内容总结

  • 函数的组成与使用
    • 定义好了一个函数还需要函数声明才能使用它(函数定义写在主函数后面时)
    • 下面举例的函数中,int a、int b为形参,5、3为实参,a被赋予了5的值,b被赋予了3的值
    • 函数计算完毕后的result的值返回给了main()函数中的c
    • 函数执行到c=fun(5,3)这一句时,会暂停main()函数,转入进行fun()函数
int fun(int a, int b);  //函数声明,这里有分号

int main()
{
	int c;
	c = fun(5, 3);     //走到这里调用定义的fun()函数
	printf("%d", c);
	return 0;
}

int fun(int a, int b)    //函数首部,注意这里没有分号!需要有函数类型和函数名,并不一定需要形参
{
	int result = 1;    //函数体
	for (int i = 1; i <= b; i++)
		result = result * a;
	return result;     //相当于fun(a,b)=result,在main()函数中即为c=result=fun(a,b)
}

  • 关于函数调用的注意事项
    1.形参与实参一一对应,两者数量相同,类型尽量保持一致,程序运行到函数调用时,实参的值会依次传给形参
    2.我们还可以将定义的函数写在main()函数的前面,这样可以不用进行声明,但是考虑到代码的阅读性,建议还是以函数声明-->主函数-->函数定义的顺序进行编写
    3.即使函数定义在main()前面,当程序执行时还是会先执行main()里的内容,等到有该函数的调用时才会转入进行定义的函数部分
    4.在函数定义的首部的函数类型一般与return语句中的表达式类型一致
    5.函数默认返回的是int类型,如果没有明确写出返回类型的话,就会被默认为int类型

  • 函数中的return,我该return什么?

    • 首先我们要知道,return语句只能返回一个值,如果要一次返回多个值我们要考虑全局变量等方法
    • return在函数中用于结束函数运行,或是将一个表达式的值返还给主函数(如上面例子中的result)
    • void类型的函数不返回结果,它可以不使用return语句,但如果使用则return语句不能跟有表达式
    • 一般的函数类型都必须return一个值或表达式,否则VS会报错
    • 也正因为他们对return语句的要求不同,void类型的函数常用于输出,int double等类型的函数常用于计算等
    • 总的来说,return的表达式仍应该根据题目类型判断,如计算阶乘的函数我们直接返回阶乘计算结果,判断素数的函数我们返回1/0来记录它是不是素数
/*简单例子:输入一个数,判断它是否为素数并计算它的阶乘*/
double fact(int n);
int prime(int p);

int main()
{
	int a;
	double item;
	
	scanf("%d", &a);
	item = fact(a);
	
	if (prime(a) == 1)printf("YES %d", item);
	else printf("NO %d", item);
	
	return 0;
}

double fact(int n)   //计算阶乘
{
	int i;
	double result = 1;
	for (i = 1; i <= n; i++) {
		result = result * i;
	}
	return result;
}

int prime(int p)    //判断素数
{
	int i;
	if (p <= 1)
		return 0;
	for (i = 2; i <= sqrt(p); i++)
	{
		if (p % i == 0)
			return 0;
	}
	return 1;
}
  • 全局变量、局部变量、静态局部变量
    • 使用范围
      1. 全局变量:在函数外定义,它的作用范围是从定义开始到程序结束,对范围内所有函数都起作用
      2. 局部变量:定义在函数内部,适用范围限定于所在函数内,形参也是一种局部变量
      3. 静态局部变量:同局部变量定义在函数内部,适用范围限定于所定义的函数内
    • 注意事项
      1. 静态局部变量和全局变量相比,它们的生存周期都贯穿整个程序执行过程,不同的是它们的作用范围不同
      2. 局部变量和静态局部变量相比,给静态局部变量赋初值只有在第一次调用时起作用,局部变量每次调用赋初值都会起作用。且静态局部变量的值改变之后值都会保留,具体见下面例子
      3. 局部变量与全局变量相比,如果在某个函数中有局部变量与全局变量同名,那么在该函数中全局变量不起作用
      4. 如果静态局部变量没有赋初值,那么会默认赋初值0
    • 例子说明
      • i=1时,fun()函数中静态局部变量wow被赋初值3,局部变量b,global赋初值3,1,经处理后wow=4,b=4,函数返回了值12,主函数中再加上全局变量global值为5,此时result=17,将全局变量global值自增1后进入下一次循环
      • i=2时,fun()函数中wow的赋初值语句不被执行,局部变量b与global再次被赋初值3,1,处理后wow=5,b=4,函数返回值13,主函数中再加上全局变量global值为6,此时result=36
      • i=3时与i=2同理
int global = 5;   //定义全局变量global初始值为5
int fun(int a);    //函数声明

int main()
{
	int number;
	int i;
	int result = 0;    //这三个都是局部变量
	
	scanf("%d", &number);
	for (i = 1; i <= 3; i++)
	{
		result = result + fun(number) + global;   //这里的global是全局变量中的global
		global++;
	}
	printf("%d", result);

	return 0;
}

int fun(int a)    //函数定义
{
	static int wow = 3;     //定义静态局部变量wow初始值为3
	int b = 3;             //局部变量
	int global = 1;      //定义了一个和全局变量global同名的局部变量
	wow++;
	b++;

	return wow + b + global + a;
}

1.2 本章学习体会

  • 函数部分还是较易理解的,除了return部分的内容可能需要绕一点点弯路,个人觉得还是挺容易的
  • 为了减少重复的代码,让主函数看起来更清楚,可以试着多用函数来编写程序
  • 之前自学的时候对全局变量和静态局部变量的部分只是了解个大概,在经过本章的学习之后对它们的了解更深了一些,比如在函数中需要返回多个值时使用全局变量,利用静态局部变量来储存需要的值等等
  • 之后的指针、递归、链表也经常会用到函数,所以这部分的基础要打好
  • 本次综合作业的代码量:528(含空行和单独的大括号等)

Q2.综合作业

2.1 本次作业的函数关系图

2.2 函数功能及全局变量介绍

2.2.1 全局变量

  • int grade:记录用户选择的年级
  • int count:记录用户想作答的数量
  • int right:答对的题量
  • int wrong:答错的题量
  • int finish:已经作答的题量
  • int limitedTime:根据难度不同给出的限制时间
  • int exitflag:记录是否要退出
  • double percent:记录答题正确率
  • time_t S,E:记录开始和结束答题时间

2.2.2 ListMenu()函数

该函数为输出主菜单,用户可以根据输出提示来选择年级和题量,并将这些变量保存到全局变量grade、count等变量中,也可以在这里退出程序。
在这个函数中我写的东西比较多,还写上了每个阶段遇到无效指令重新输入的情况。
考虑到可能会有输入错误的情况,我在开始答题前写上了确认题目和题量的部分,如果有误则返回重新进行输入。
这个函数会一直进行到开始答题前,在答题之前输出一些提示给用户,告诉一些必要的注意事项和中途退出的方式,并且给予10秒的准备时间。
每次结束作答后如果还想继续做题就会再次输出这个界面。
效果如下


2.2.3 随机文字输出函数

因为这四个函数功能差不多,所以我就直接放在一起写了。
MessageBefore()函数为出题之前生成的话。
MessageRight()函数为回答正确后输出的鼓励的话。
MessageWrong()函数为回答错误后输出的鼓励的话(本来想写嘲讽的话,想想还是算了)
MessageTimeout()函数为回答超时输出的话。
每次都是随机生成,本来想写更多的,但我想不出那么多话就算了。。


2.2.4 DrawSummary()函数

当用户答题途中输入-1退出或是答完所有题目就会执行这个函数。
该函数用于总结完成题量、输出答题用时、计算正确率,并让用户选择是否继续做题。
选择继续做题就会回到ListMenu()函数,重新选择年级和题量。
同样为了防止未知指令写了段语句。
效果如下


2.2.5 GetNumber()函数

生成随机数的函数。
根据GetQuestion()函数中随机出的题目类型不同,生成不同的随机数并返回。
采用了比较笨的方法,把所有情况写出来,这样出题时可以直接使用GetNumber(需要的类型)
里面生成随机数的方法也比较笨。。但是用起来还是挺顺手的。。
是一块还需优化的代码。


2.2.6 GetQuestion()和GetQuestion2()函数

生成问题并让用户作答的函数。

出题前执行MessageBefore()函数输出一句鼓励的话。
再根据全局变量grade进入到不同年级的题库,生成随机数type来决定会生成哪种题目。
如果是三年级的四则运算题就转入GetQuestion2()函数。
遇到减法的题目先判断是否后面的数比前面大,如果是就交换两个数。
如果答案有特殊需求就判断答案是否能满足条件,如果不行就重新随机生成。
根据题目类型判断输入答案中是否要有余数。
题目生成并输出后记录开始时间,输入答案后再次记录时间,并用difftime()函数计算用时。
输入答案-1结束作答,回到主函数执行DrawSummary()。
答错进行函数MessageWrong(),超时进行函数MessageTimeout(),答对进行函数MessageRight(),并相应的增加答对题量/答错题量和已完成题量。
个人平心而论,这个函数写的并不好,感觉内容有点太多了,不方便阅读。有考虑将输入答案的部分单独再写出一个函数。

PS:本来是想写超过时间限制直接判错并进入下一题的,但是由于scanf()函数如果不执行完又不会执行下一步,查阅各种资料后发现可能要用多线程来写,于是直接放弃,改成了现在这个样子。
PSS:GetQuestion2()是个人感觉写的最差的函数…相当纯粹的列举法,然后再删去几种太简单的题目类型……该函数还有待修改和优化。
PSSS:后面测试时发现问题,在三年级的case3(两位数除两位数或一位数的题)中加入了除数为0时重新随机的语句……但是这个代码截图的长图已经做完了而且真的太太太长了我不想重新截图了就在这里说明一下。。


2.2.7 主函数

没有什么特别的内容,记录开始的时间和结束的时间,如果作答结束就退出。
以及程序结束时的尾巴一点点东西的输出。


Q3.运行结果截图

  • 一年级

  • 二年级

  • 三年级

PS:由于我的随机数函数写的方式,导致我题目这边一位数乘一位数、一位数乘两位数的题目比较少见(随机范围是1-999),所以就没有这两题的截图


Q4.调试碰到问题及解决办法

  1. 除数为0:一开始是用while循环重新随机,但是测试的时候发现其他题目中随机一位数经常有0,导致题目一点意思都没有,于是修改了随机生成一位数的代码 rand()%10(0-9)--> rand() % 9 + 1(0-8再加1)
  2. 快速生成表内除法:刚入手是随机一个两位数或一位数,再随机一个一位数,循环随机至它们能整除,后来发现这样写十分麻烦,改为生成两个一位数a,b并让a=a*b,这样的a/b就一定是表内除法了,写起来也很方便
  3. 时间限制:scanf()函数会导致输入前程序停止,想要同步计时需要使用多线程,改用其他方法对时间进行限制
  4. 如何生成一位数四则运算类的题目:该问题仍未解决,目前是用列举法暂时代替着,希望有人能指点一下
  5. 处理输入的未知命令:这个是我写完部分代码发给同学玩时遇到的,“要是就是有不清楚的人爱乱输命令怎么办?”,于是在所有输入命令的地方都加入了非法命令时重新输入
  6. 用户想中途停止:这个可能并不算调试时遇到的问题,最终考虑到小学生计算题不会有负数,于是选择了-1为标志
  7. 随机数的生成:我的程序目前三位数、四位数的生成的方法还是有点笨,比较难看,尚未找到解决方法,仍待改善中

Q5.大作业总结

第一次一下子写这么多代码的程序,写之前觉得很艰难,但实际写起来也并没有那么难受。
经过这次大作业,个人感觉编写这种大程序最最重要的是要有框架和思路

第一个问题:这个程序的功能或者目的是什么?
接下来,为了这些功能我可能会运用到一些函数,那么这些函数分别是用来干什么的呢?
先不要管能不能实现这个功能,首先需要有一个思路,各个函数分工合作,这个函数完成什么功能,另一个函数又完成什么功能,这个函数可能会遇到什么困难?
画出一个树状图,主函数为躯干,各个函数为分支,我刚刚思考的函数可以组成这个完整的程序吗?
再来想某一个函数只要它自己就够了吗?需要与其他函数联动吗?还是要为它再写一个小函数呢?
等有了框架,知道我该写些什么函数,这个函数它应该完成什么功能,它需要与哪些函数进行配合,再开始敲代码,有一个清晰的框架和思路,写代码的速度会完全不一样。

写代码的时候多多检查,有了前面对可能会遇到的困难的思考,犯错的概率也会有一定的下降,但还是要多测试,越快测出问题越好。等到全部写完再回来找问题会很麻烦。
等到程序功能基本实现的时候再去完善那些刚才没能写出来的部分,不要在一棵树上吊死,先把其他功能实现了再说(万一正好略过了那个测试点呢)
之后就可以开始对界面进行优化了,用户可能会在这个部分干什么?要怎么应对这种脑子不清楚的情况?把自己想象成是个奇葩的用户或者让同学当个杠精,找这种优化就特别简单。

还有一个很重要的,写注释!!真的,程序的代码量上去以后,你可能真的就会突然忘掉这个变量是什么,这一部分是用来干啥的,别说记性好,能忘了我就吃xx穿xx剁xx之类的话,因为真的忘记就在脑抽那一下子,要想起来又要花一堆时间(就像你突然忘了自己突然要说什么话的情况)

原文地址:https://www.cnblogs.com/silverash/p/11785387.html