目录
- 1.展示PTA总分
- 函数:
- 2.本章学习总结
- 2.1. 学习内容总结
- 1.函数的定义
- 2.函数的调用
- 3.不返回结果的函数
- 4.局部变量和全局变量
- 5.静态变量
- 6.函数应用:于龙又又遇见日期,叕哭了!
- 2.2 本章学习体会
- 学习感受
- 代码量统计
- 2.1. 学习内容总结
- 3.综合作业:小学生口算表达式自动生成系统
- 1.本次作业的函数关系图
- 2.函数功能及全局变量介绍
- 2.1.全局变量
- 2.2.Cut( )函数
- 2.2.Start( )函数
- 2.3.Number( )函数
- 2.4.myRand( )函数
- 2.5.correctMessage( )函数
- 2.6.incorrectMessage( )函数
- 2.7.Questions( )函数
- 2.8.Game( )函数
- 2.9.judgment( )函数
- 2.10.Timer( )函数
- 2.11.outTime( )函数
- 2.12.Prepare( )函数
- 2.13.Ending( )函数
- 2.14.Addition( )函数
- 2.15.Subtraction( )函数
- 2.16.Multiplication( )函数
- 2.17.Division( )函数
- 2.18.Remainder( )函数
- 2.19.fourOperations( )函数
- 2.20.主函数
- 3.运行结果截图,测试用例
- 3.1开始页面
- 3.2一年级题目
- 3.3二年级题目
- 3.4三年级题目
- 3.5非法输入
- 3.6随机给出习题量
- 3.7超时作答
- 3.8结算并开启下一轮练习
- 4.调试碰到问题及解决办法
- 5.大作业总结
1.展示PTA总分
函数:
2.本章学习总结
2.1. 学习内容总结
1.函数的定义
函数是一个完成特定工作的独立程序模块,包括库函数和自定义函数两种。例如,scanf()、printf()等为库函数,由C语言系统提供定义,编程时只要直接调用即可;而有时,我们需要一些能实现特定功能的函数,这时就需要用户自己定义,属于自定义函数。从函数实现计算功能角度来看,C语言的函数与数学上的函数概念十分接近。在C程序中必然为某一种数据类型,称其为函数类型。
函数定义的一般形式为:
函数类型 函数名(形式参数表) /*函数首部*/
{
函数实现过程 /*函数体*/
}
1.函数首部
函数首部由函数类型、函数名和形式参数表(以下简称形参表)组成,位于函数义的第一行。函数首部中,函数名是函数整体的称谓,需用一个合法的标识符表示。函数类型指函数结果返回的类型,一般与return语句中表达式的类型一致。形参表中给出函数计算所要用到的相关已知条件,以类似变量定义的形式给出,其格式为:
类型形参1,类型2 形参2,…,类型n形参n
形参表中各个形参之间用逗号分隔,每个形参前面的类型必须分别写明。函数的 参的数量可以是一个,也可以是多个,或者没有形参。
- 函数首部后面不能加分号,它和函数体一起构成完整的函数定义。
2.函数体
函数体体现函数的实现过程,由一对大括号内的若干条语句组成,用于计算,或完成特定的工作,并用return语句返回运算的结果。
2.函数的调用
定义一个函数后,就可以在程序中调用这个函数。在C语言中,调用标准库函数时.只需要在程序的最前面用include命令包含相应的头文件;调用自定义函数时,程序中必须有与调用函数相对应的函数定义。作为初学者的我们,充分理解函数调用与返回的实现过程,对学好函数程序设计是至关重要的。
1.函数调用过程
任何C程序执行,首先从主函数main()开始,如果遇到某个函数调用,主函数将被暂停执行,转而执行相应的函数,该函数执行完后将返回主函数,然后再从原先暂停的位置继续执行。
2.函数调用的形式
函数调用的一般格式为:
函数名(实际参数表)
实际参数(简称实参)可以是常量、变量和表达式作为实参,对于实现计算功能的函数,函数调用通常出现在赋值语句或输出函数的实参。
3.参数传递
函数定义时,位于其首部的参数被称为形参,主调函数的参数被称为实参。形参除了能接受实参的值外,使用方法与普通变量类似。形参和实参必须一一对应,两者数量相同,类型尽量一致。程序运行遇到函数调用时,实参的值依次传给形参,这就是参数传递。
函数的形参必须是变量,用于接受实参传递过来的值;而实参或表达式其作用是把常量变量或表达式的值传递给形参。如果实参是变量,它与所对应的形参是两个不同的变量。实参是主调函数的,形参是自定义函数的,这两者可以同名,也可不同名。
按照C语言的规定,在参数传递过程中,将实参的值复制给形参。这种参数传递是单向的,只允许实参把值复制给形参,形参的值即使在函数中改变了,也不会反过来影响实参。
- 实参和形参一一对应,数量应相同,顺序应一致,初学时建议类型也保持一致。
4.函数结果返回
函数结果返回的形式如下:
return表达式;
先求解表达式的值,再返回其值。一般情况下表达式的类型与函数类型应一致,如果两者不一致,以函数类型为准。return语句的作用有两个:一是结束函数的运行;二是带着运算结果(表达式的值)返回主调函数。
在函数体中,return语句中的表达式反映了函数运算的结果,通过return语句将该结果回送给主调函数。但return语句只能返回一个值,如果函数产生了多个运算结果,将无法通过return返回。例如求一元二次方程的函数,就不能用return返回两个根。
在接下来的学习中,我们将会学习使用全局变量或指针实现函数多个结果返回。
- return语句只能返回一个值。
5.函数原型声明
C语言要求函数先定义后调用,就像变量先定义后使用一样。如果自定义函数被放在主调函数的后面,就需要在函数调用前,加上函数原型声明(或称为函数声明)。函数声明的目的主要是说明函数的类型和参数的情况,以保证程序编译时能判断对该函数的调用是否正确。函数声明的一般格式为:
函数类型 函数名(参数表);
即与函数定义中的第一行函数首部相同,并以分号结束。
- 函数声明是一条C语句,而函数定义时的函数首部不是语句,后面不能跟分号。
虽然可以将主调函数放在被调函数的后面,从而不需做声明。但考虑到函数的执行顺序,在编程时一般都把主函数写在最前面,使整个程序的结构和功能开门见山地呈现在读者面前,然后通过函数声明解决函数先调用后定义的矛盾。
- 如果在调用函数前,既不定义,也不声明,程序编译时会出错。
3.不返回结果的函数
前面我们谈的的函数主要是是起计算或判断作用,最终有一个函数结果返回。在很多程序设计中,调用函数不是为了得到某个运算结果,而是要让它产生某些作用,具有类似作用的函数在有些语言中也称为过程。不返回结果的函数定义:
void函数名 (形参表) /*函数首部*/
{
函数实现过程 /*函数体*/
}
函数类型为void,表示不返回结果,函数体中可以使用没有表达式的return语句,可以省略间return。void类型的函数虽然不直接返回一个值,但它的作用通常以屏幕输出等方式体现。
- 在不返回结果的函数定义中,void不能省略;否则,函数类型被默认定义为int。
省略了return语句,并不意味着函数不能返回。对于void类型的函数,如果省略了return语句,当函数中所有语句都执行完后,遇到最后的大括号即自动返回主调函数。
由于函数没有返回结果,函数调用不可能出现在表达式中,通常以独立的调用语句。
不返回结果的函数在定义、调用、参数传递、函数声明上,思路完全与以前相同,只是函数类型变为void。它适用的场合主要是把一些确定的、相对独立的程序功能封装成函数。主函数通过调用不同的函数,体现算法步骤,而各步骤的实现由相应函数完成,从而简化主函数结构,以体现结构化程序设计思想。
4.局部变量和全局变量
1.局部变量
迄今为止,在程序中使用的变量都定义在函数内部,它们的有效使用范围被局限于所在的函数内。因此主调函数只有通过参数传递,才能把实参数据传递给函数使;同样,形参的改变也不会影响到实参变量。这种变量的有效使用范围,最大程度保了各函数之间的独立性,避免函数之间相互干扰。
C语言中把定义在函数内部的变量称为局部变量,局部变量的有效作用范围局限于所在的函数内部。形参是局部变量。
使用局部变量可以避免各个函数之间的变量相互干扰。当函数使用了同名的形参时,甚至于主函数的实参变量也同名时,由于分属不同函数,它们有各自不同的变量实体和使用范围,不会相互千扰。C语言的这个特性在结构化程序设计中非常有用。
除了作用于函数的局部变量外,C语言还允许定义作用于复合语句中的局部变量,其有效使用范围被局限于复合语句内,一般用作小范围内的临时变量。
- 局部变量一般定义在函数或复合语句的开始处,标准C规定其不能定义在中间位置。
2.全局变量
局部变量虽然保证了函数的独立性,但程序设计有时还要考虑不同函数之间的数据交流,及各函数的某些统一设置。当一些变量需要被多个函数共同使用时,参数传递虽然是一个办法,但必须通过函数调用才能实现,并且函数只能返回一个结果,这会使程序设计受到很大的限制。为了解决多个函数间的变量共用,C语言允许定义全局变量。
定义在函数外而不属于任何函数的变量称为全局变量。全局变量的作用范围是从定义开始到程序所在文件的结束,它对作用范围内所有的函数都起作用。
全局变量的定义格式与局部变量完全一致,只是定义位置不在函数内,它可以定义在程序的头部,也可以定义在两个函数的中间或程序尾部,只要在函数外部即可。
- 一般情况下把全局变量定义在程序的最前面,即第一个函数的前面。
由于全局变量和局部变量的作用范围不同,允许它们同名。当某函数的局部变量与全局变量同名时,在该函数中全局变量不起作用,而由局部变量起作用。对于其他不存在同名变量的函数,全局变量仍然有效。同样,当函数局部变量与复合语句的局部变量同名时,以复合语句为准。
- 全局变量可以帮助解决函数多结果返回的问题,但全局变量更多地用于多函数间的全局数据表示。
思考:
我们可能认为使用全局变挺比使用局部变量自由度大,更方便。一旦定义,所有函数都可直接使用,连函数参数都可省略,甚至函数返回结果个数也不受限制,不需要使用return语句,可以直接通过全局变量回送结果。从表面上看,全局变量确实能实现这些要求,但对于规模较大的程序,过多使用全局变量会带来副作用,导致各函数间相互干扰。如果整个程序是由多人合作开发的,各人都按自己的想法使用全局变量,相互的干扰可能更严重。因此在变量使用中,应尽量使用局部变量,从某个角度看使用似乎受到了限制;但从另一个角度看,它避免了不同函数间的相互干扰,提高了程序质量。
- 全局变量虽然可以用于多个函数之间的数据交流,但一般情况下,应尽量使用局部变量和函数参数。
5.静态变量
1.变量生存周期
变量是保存变化数据的工作单元,计算机用内存单元来对应实现。一旦在程序中定义变量,计算机在执行过程中就会根据变量类型分配相应的内存单元供变量保存数据。
就一般程序而言,计算机都是从主函数开始运行的,使得main函数中所有的局部变量,一开始就在内存数据区中分配了存储单元。而其他函数在被调用之前,其局部变量并未分配存储单元,只有当函数被调用时,其形参和局部变量才被分配相应存储单元;一旦函数调用结束返回主调函数,在函数中定义的所有形参和局部变量将不复存在,相应的存储单元由系统收回。根据这种特性,把局部变量称为自动变量,即函数被调用时,系统自动为其局部变量分配存储单元;一旦该函数结束(不一定是整个程序运行结束),所有分配给局部变量的单元由系统自动回收。变量从定义开始分配存储单元,到运行结束存储单元被回收,整个过程称为变量生存周期。
2.静态变量
在静态存储区中,除了全局变量外,还有一种特殊的局部变量一一静态局部变量。它存放在静态存储区,不会像普通局部变量那样因为函数调用结束而被系统回收,它的生存周期会持续到程序结束。由于存储单元被保留,一旦含有静态局部变量的函数被再次调用,则静态局部变量会被重新激活,上一次函数调用后的值仍然保存卷,可供本次调用继续使用。静态变量定义格式:
static 类型名 变量表
自动变量如果没有赋初值,其存储单元中将是随机值。就静态变量而言,如果定义时没有赋初值,系统将自动赋0。并且赋初值只在函数第一次调用时起作用,以后调用都按前一次调用保留的值使用。这是因为静态局部变量的生存周期始于函数的第一次调用,贯穿于整个程序。当函数第一次调用时,静态局部变量的内存单元得以分配,赋以初值,而函数被再次调用时,此静态局部变量单元已经存在,计算机不会再次为它分配单元,赋初值也不再发生。但静态局部变量受变量作用范围限制,不能作用于其他函数(包括主函数)。
- 静态变量赋初值只在函数第一次调用时起作用,若没有赋初值,系统将自动赋值0。
静态变量与全局变量均位于静态存储区,它们的共同点是生存周期贯穿整个程序执行过程。区别在于作用范围不同,全局变量可作用于所有函数,静态变量只能用于所定义函数,而不能用于其他函数。静态变量和全局变量一样,属于变量的特殊用法,若没有静态保存的要求,不建议使用静态变量。
- 除了静态局部变量外,C语言也宥静态全局变量,它的作用与程序文件结构有关。
6.函数应用:于龙又又遇见日期,叕哭了!
大家还记得我们的老朋友于龙吗?还记得那是一个风和日丽的下午,我们和于龙又见面了。记得当时,我的代码是这样的:
你看懂这段代码了吗?由于这一次我们是将输入的三个数组合成年月日,然后来判断合法性,根据排列组合,一共有六种可能需要我们考虑。因此我使用了穷举法,把六种可能依次搞了一遍。
但是,我们看到这六种情况的代码除了变量的位置以外完全相同,直接这么写等于把一件事重复做了6遍,感觉这么写效率好低啊!我们有没有更好地方法去实现这个功能呢?
你看懂这段代码了吗?我使用了我们刚学习的函数把这段代码封装起来了,然后只需要每次改变传入的参数的顺序,即可实现功能,是不是一下子就高大上了许多?
2.2 本章学习体会
学习感受
首先,在这两周的PTA中,我出现了数次思路不清楚的情况,在这里我必须批判自己:您老人家下次请务必理清思路在打代码,就好比上一次上机考试的最后一题,用long int来写多简单啊,非要用字符串,写了一百多行功能还是不能实现,后来改用long int 来写很快就写出来了,就是因为题目看不清楚,就是因为不好好把思路想清楚,急躁冒进,直接开始敲代码,已经吃了不止一次亏了,所以请您老人家一定要好好把问题想清楚,不然再这样下去一定会完蛋!
其次,10月这段时间确实比较忙,国庆放假回来,我马上就去参加了朗诵,朗诵结束之后又匆匆去参加了军体拳,每天都十点半多回到宿舍,这些活动都是有班级指标的,不去参加还真不行,再加上其他的杂事,时间和精力就这样一点一点被打没了,所以这段时间一直很被动,学习时间几乎都用在了完成作业,没有像上个月那样还有一定的预习和复习。但是在这段时间,在这么被动的情况下,我还是勉勉强强能跟上进度,这点还是值得肯定的,我会用白昼的时间是有限的,夜晚的时间却是无限的!这样一句话来激励自己,不要因为时间紧迫,你的代码就可以不打,PTA就可以不写,作业就可以不交,在这个时候更需要拼了老年来挤出时间完成任务,而且我认为虽然我的时间比较少,但是PTA完成得还是比较早的,博客的质量不敢说有之前两篇那么高,但应该也不会太差,因此我想和作业打折的同学说:我们不要自我设限!至少是现在的作业量,在时间缩水的情况下还是可以完成的,因此对待作业我们一定要尽可能确确实实写写好!只要好好写作业,都是可以完成的。我也知道对于一些暑假没有打代码的同学来说,课程的难度已经被大幅度地提升了,确实比较难,但是我们遇到什么困难,也不要怕,微笑着面对它,消除恐惧的最好办法就是面对恐惧,坚持才是胜利!我们才刚刚开始学习专业知识,与编程语言才在初恋之中,还没了解得那么深,现在就打消热情未免也太早了一点吧。所以在接下来的时间里,我能和大家一起努力,继续肝代码。忙完了校运会,我又要到下一个地方去忙了,但是在这段时间我也对接下来的目标有比较明确的认识了,希望我接下来也能继续保持热情,学习更多的知识。
代码量统计
3.综合作业:小学生口算表达式自动生成系统
1.本次作业的函数关系图
2.函数功能及全局变量介绍
- 代码中已有详细注释的地方不再进行解释。
2.1.全局变量
2.2.Cut( )函数
- 此函数用于分割界面,使界面更为美观。
2.2.Start( )函数
- 此函数集成了开始菜单,年级的输入,为了营造一个快乐而刺激的刷题体验,程序与用户有很多互动。
2.3.Number( )函数
- 由于出题时要非常注重题目的难度,因此控制随机数的位数极其重要,因此单独封装成一个函数处理。
2.4.myRand( )函数
- 同上,很多时候生成的随机数需要控制范围,为了提高准确性,简化判断机制,单独封装函数来达成生成随机数的目的。
2.5.correctMessage( )函数
2.6.incorrectMessage( )函数
2.7.Questions( )函数
- 此函数是出题的中枢函数,集成了我设置的所有题型,并且出口对接着其他函数共同完成出题。
出题的时候有一些问题需要格外注意:
- 所有的题目中不能出现答案的得数是负数;
- 三年级之前的小学生还没有学习交换律、结合律和分配律,命题的时候不能考查这些,及运算过程不能存在负数中间量;
- 涉及除法运算时,除数不能为0;
- 除法运算的得数不能出现小数,三年级前的普通学生并不能很好地进行这种运算;
- 出题时需要注意控制得数的位数,不能将题目搞得太难;
- 出含有乘法、除法运算的题目时,必须显示数学符号“×”和“÷”,否则小学生看不懂。
2.8.Game( )函数
- 此函数集成了题目量控制、判断答案正误及超时作答、中途退出的功能。
2.9.judgment( )函数
- 此函数用于分析用户的作答情况,并给出一些提示和建议,并确认是否开启下一轮练习。
2.10.Timer( )函数
- 此函数用于获取现在的时间。
2.11.outTime( )函数
2.12.Prepare( )函数
- 此函数用于确认用户的习题量,并给出温馨提示,做好答题准备。
2.13.Ending( )函数
2.14.Addition( )函数
2.15.Subtraction( )函数
2.16.Multiplication( )函数
2.17.Division( )函数
2.18.Remainder( )函数
2.19.fourOperations( )函数
2.20.主函数
- 集成了菜单函数、准备函数、题量控制及裁判函数、结算函数和结束函数。
3.运行结果截图,测试用例
3.1开始页面
3.2一年级题目
- 出题时由系统随机出题,我并没有完全按照作业要求出题,例如整十数减整十数的减法这类题,本质上是一位数加一位数,因此我对一些题型做了归并处理。
一年级的题型有:
- 1/2位数 + 1位数;
- 1/2位数 - 1位数;
- 3个1位数的加减法运算。
3.3二年级题目
- 出题时由系统随机出题,我并没有完全按照作业要求出题,例如尾数是0三位数加法(和在一千以内的)这类题,本质上是二位数加二位数,因此我对一些题型做了归并处理。
二年级的题型有:
- 九九乘法表及其逆运算;
- 2位数与2位数的加减运算;
- 求余运算;
- 简易的四则运算。
3.4三年级题目
- 出题时由系统随机出题,我并没有完全按照作业要求出题,例如一位数乘一位这类题,本质上是九九乘法表,因此我对一些题型做了归并处理。
三年级的题型有:
- 3位数与两位数加减法运算(含连续进/退位);
- 两位数乘一位数;
- 被除数是三位数求余运算;
- 考虑优先级的四则运算。
3.5非法输入
3.6随机给出习题量
- 生成习题量的方式是生成一个两位的随机数。
3.7超时作答
- 超时作答的判定方式为单题答题结束时间减去开始时间超过15秒。
3.8结算并开启下一轮练习
- 系统将会根据正确率100%、80%、60%、60%以下四种情况进行判定,同时对超时率30%以上也有判定。
4.调试碰到问题及解决办法
Q1:有时候出题时会同时打出两道题目。
A1:出题时,主要使用了switch多分支结构,由于该结构的特性,在缺少break语句时会继续执行下一个case,就会出现同时出了两道题的情况,补上break即可。
Q2:四则运算题时,会出现题目的中间量为负数的情况。
A2:利用循环生成题目,如果遇到会出现这种情况的数据,则重新生成。
Q3:出除法题时,会出现得数含有小数的情况,然而三年级之前的学生较难处理这类题。
A3:所有的除法题改为用乘法的逆运算出题。
Q4:出除法题时,会出出除零运算的题。
A4:利用我自己写的随机数函数,就不会出现数字0。
Q5:出题时,最后一种题型的题目不出现。
A5:我忘记了我自己的随机数函数的生成范围是开区间,修改上即可。
Q6:非法数据作答时,系统会直接跑完所有循环。
A6:由于这种情况系统不会崩溃,并且也可以实现强行退出,再加上实力问题,所以暂时没有处理。
Q7:如何实现计时功能?
A7:原计划是判断作答时间如果超过15秒就直接判错,但是由于实力问题暂时不能实现,只能做到用户作答之后判断是否超时。
Q8:如何做到重开一轮练习?
A8:原计划是想使用“goto”语句,但是后来想一想,只需要在主函数套个循环即可,因此重构了菜单函数,重新定义一个准备函数将原本的题量输入的部分放进去。
Q9:在不利用bug的情况下如何实现强退?
A9:原计划是想使用“goto”语句,但是后来想一想,只需要操作习题量,如果输入负数,就直接让题号加到习题量的上限,让系统误以为题目出完了即可。
Q10:为了避免猝不及防的开始,想要在开始练习之前设置倒计时。
A10:与舍友讨论之后,学习了Sleep函数实现这个功能。
Q11:出题函数被老师指出代码重复率很高。
A11:将不同的题型分别用函数封装,通过控制传入的参数达到控制难度的效果。
5.大作业总结
这次的大作业总体上还是比较顺利的,没有出现没有思路的情况,所出现的bug都是可以用肉眼看见的,修改也比较简单,因为不能用指针,所以我的代码几乎都是依靠全局变量实现功能,函数之间的磨合也没有出现问题,这也得益于之前一段时间有进行了一定量的训练吧,虽然这段代码也写了一段时间,但是大部分的时间都花在了润色、增加功能和调整难度上,并没有为程序无法完成功能弥留太久。我觉得,这样的大作业和平时打PTA是完全不同的状态,在打PTA的时候,我更多地是想该怎么解决问题,用最快的速度,通过了就行,然后再来想怎么优化或者一题多解,但是这样也有一些不好的地方,第一是PTA考查的毕竟还是一小段代码,比较不能体现不同区块函数的磨合问题,第二是写PTA的时候更强调结果,过了所有测试点再说,第三是PTA对答案的要求很苛刻,但是这些问题往往不一定与现实需求相挂钩。但是大作业就不同了,大作业更注重过程,更注重应用,在完成一个比较大的功能时,我们需要充分考虑该怎么准确地实现功能,更恰当地满足用户的需求,这时就不仅仅是达成目的就行了,如果出的题连我自己都难以完成,怎么能完成小学生的需求呢?如果只是打印题目让用户回答,没有与用户进行互动,这样的体验一定是极差的,这个时候需要考虑的事情就多了。我认为,作为一个IT人,项目关是非过不可的,因为就业之后,我们将不会在工作的时候打PTA,而是利用代码实现甲方的需求,这时这种大作业的训练就显得太重要了。因此接下来,我希望能继续完善这个程序,因为它还能装上更多的功能,还有很多bug可以被优化,也希望能够接触更多这样的大作业,现在的我认为,PTA的分数终究只是分数,它不能与代码能力画上等号,也不能代表是否能完成项目,因此我希望通过PTA来巩固基础,接触更多的大作业来锻炼综合能力,只有这样才能使自己的能力变得更强!
我现在要收回“完成得比较顺利这句话”,写完大作业之后,我重构了两次代码,第一次是我想要加入重新开局的功能,于是另外封装了准备函数,把大部分内容修改了。第二次是在老师给出建议,经学长和学姐的指点之后,我将出题函数分割成了一个中枢函数和6个子函数,虽然完成的工作量很大,但是代码的重复率被有效降低,思路显得更加明了,代码量也被明显地下降了。经历两次大改之后,我认识到完成功能的代码可能性是无限的,只有特定条件下的最优解。每一次的修改中,又能有新的思路,不同的方式来优化代码,使代码的效率被提高,我认为如果我们在未来选择了开发方向,优化修改的关口非过不可,只有通过不断地训练才能提升能力。