本文目录
前言
第一单元的主题为表达式化简,这一核心业务贯穿在整个过程中。通过四次理论授课、两次上机实验、两次师生研讨课和三次作业的迭代训练来熟悉面向对象的思维方式,完成从过程到行为的转变,培养层次化和模块化设计的能力。
首先附上第三次作业的文法规则,前两次都是它的子集:
- 表达式 →→ 空白项 [加减 空白项] 项 空白项 | 表达式 加减 空白项 项 空白项
- 项 →→ [加减 空白项] 因子 | 项 空白项 ‘*’ 空白项 因子
- 因子 →→ 变量因子 | 常数因子 | 表达式因子|求导因子
- 变量因子 →→ 幂函数 | 三角函数 | 自定义函数调用
- 常数因子 →→ 带符号的整数
- 表达式因子 →→ ‘(’ 表达式 ‘)’ [空白项 指数]
- 幂函数 →→ 自变量 [空白项 指数]
- 自变量 →→ ‘x’ | ‘y’ | ‘z’
- 三角函数 →→ ‘sin’ 空白项 ‘(’ 空白项 因子 空白项 ‘)’ [空白项 指数] | ‘cos’ 空白项 ‘(’ 空白项 因子 空白项 ‘)’ [空白项 指数]
- 指数 →→ ‘**’ 空白项 [‘+’] 允许前导零的整数 **(注:指数一定不是负数)
- 带符号的整数 →→ [加减] 允许前导零的整数
- 允许前导零的整数 →→ (‘0’|‘1’|‘2’|…|‘9’){‘0’|‘1’|‘2’|…|‘9’}
- 空白项 →→ {空白字符}
- 空白字符 →→ (空格) |
\t
- 加减 →→ ‘+’ | ‘-’
Homework 1
这次作业主要考察对表达式的化简和去括号,表达式中可以包含加、减、乘、乘方和括号,还可能存在多个变量。涉及内容较多,强度较大,对于我这种未曾亲自动手设计过完整逻辑架构的人来说是很大的挑战。
架构分析与实现
从需求出发,我们的任务为“读取并解析表达式”,表达式由一个或多个项相加组成,每一个项又由一个或多个因子相乘组成,因子可分为变量因子 | 常数因子 | 表达式因子。可以看出这是一个相互包含的关系,Factor
可作为这三种结构的接口,提取出“getValues()-获取值”和“toString()-构建自身字符串”的行为,供实现该接口的类自行实现。
对字符串的解析采用递归下降的方法,构建Parser
和Lexer
两个类,具体方法不多阐述。
此外,我额外设置了一个Operation
类,作为“单项式类”,其内部定义系数、xyz的指数等属性(后续还会加入三角函数Map),作为最小运算单元、合并单元和输出单元,也是所有类的ArrayList
容器内的对象类型。该类型可以做到统一化处理。
UML类图
总体上看,本次作业的UML类图如下:
复杂度分析
本次作业部分方法复杂度如下图(一张图没办法截全,其余方法复杂度均为0或1):
可以看出总体复杂度还算可以,存在个别复杂度超标的方法:
Operation
类的toString()
方法复杂度较高,原因为在该方法中有大量的if
语句配合三目运算符对输出格式进行特判,再加上我对一些特殊情况如x**2=x*x
进行了优化处理,导致分支判定数量庞大。Lexer.next()
方法中存在多重if-else
语句嵌套判定连续的正负号并进行化简处理,导致复杂度比较高。Expr
类中的addTerm()
方法内也是含有双层嵌套的for
循环与if
语句,判定次数较多。
测试与bug分析
由于一些特殊原因,本次并没有进入到互测环节,但最终的代码在补交的中测强测中都未出现明显问题。
在我看来,比较容易出Bug的点有:
- 忽略了指数前可能存在的正号
- 指数前有若干个前导0时如何处理
- 输出部分需要讨论很多种情况,包括
xyz
要写成x*y*z
这种,否则就会出现格式错误 - 优化的时候稍微不小心就会出现结构上的问题,故尽可能少做改动
总结与体会
第一单元的第一次作业的确是开幕雷击,对尚未完全适应新学期的我造成了巨大的冲击,加上开学前两种有缓考等其他事情,很遗憾没能通过中测,好在课程组贴心地安排了补交,算是回了一口血。
我认为难点有二:
- 一是如何从零开始设计一个完整的体系架构,虽然假期中我把上学期先导课的作业都自己动手写了一遍,但绝大部分都是看着同学的代码学习,勉强能够理解。这次则要求完全独立设计,起初完全摸不到头绪,前几天的进度几乎为零,后期突击效果也不甚理想。所以这次作业带给我更多的是思想上的启发,教会了我如何一步步搭建起具有一定复杂度的体系结构,并不断修整完善它。我在和作业苦战的过程中也强化了面向对象的思想方法,收益颇丰。
- 二是对Java语言比较陌生,虽然我也提前解除了一些Java的语法知识,但真正到了上阵作战还是感到深深的无力感。用“书到用时方恨少”来形容再恰当不过,同时这也从一个层面反映了单纯的理论学习很难获得显著效果,需要和实践练习相结合才能将知识进一步理解和吸收。
Homework 2
第二次作业在上一次的基础上新增了三角函数与自定义函数因子,且支持嵌套多层括号,对整体架构和解析逻辑提出了更高的要求。
补充自定义函数相关部分:
- 自定义函数定义 →→ 自定义函数名 空白项 ‘(’ 空白项 自变量 空白项 [‘,’ 空白项 自变量 空白项 [‘,’ 空白项 自变量 空白项]] ‘)’ 空白项 ‘=’ 空白项 函数表达式
- 自定义函数调用 →→ 自定义函数名 空白项 ‘(’ 空白项 因子 空白项 [‘,’ 空白项 因子 空白项 [‘,’ 空白项 因子 空白项]] ‘)’
- 自定义函数名 →→ ‘f’ | ‘g’ | ‘h’
- 函数表达式 →→ 表达式 (注:本次作业中函数表达式保证不会调用自己或其他自定义函数)
迭代开发
嵌套括号
对于嵌套括号,由于我采用了递归下降的处理模式,只需要在解析ExpressionFactor
方法中稍作改动即可实现,部分代码逻辑如下:
parseFactor()
方法中:
public Factor parseFactor() {
//分情况讨论,对于不同种类的因子:变量,幂函数,常数,表达式
if (lexer.peek().equals("(")) { //如果是左括号,说明是表达式因子
lexer.next();
return parseExpressionFactor();
}
parseExpressionFactor
方法中:
public Expr parseExpressionFactor() {
Factor expr = parseExpr();
lexer.next();//跨过")"
if (lexer.peek().equals("*") && lexer.peekNext().equals("*")) { //变量后有连续的两个*,说明是乘方
lexer.next();//再读取一个*
lexer.next();//读取指数,可以直接读取有前导0的数
这样即可利用解析架构的完善性自动化处理任意层括号的嵌套,无需过多考虑实现过程。
三角函数
相较于多层括号,本次作业新增的三角函数和自定义函数明显更难处理。分析要求后发现:三角函数和自定义函数仍然作为因子出现在表达式中,且并未对原有规则做任何改动,所以上一次的架构可以继续沿用。
我新建了Sin
和Cos
两个类表示两种三角函数,两者都要实现Factor
接口。在Parser
类中新增parseSinCosFactor()
方法,如果在解析因子时遇到s
或c
,即调用此方法解析。解析的逻辑为:先读取内部的因子并解析,再判定整个三角函数有无乘方,最后返回三角函数对象。下为部分代码:
private Factor parseSinCosFactor(String flag) {
lexer.next();//读取里面的因子
Factor factor = parseExpr();//解析内部的因子
lexer.next();//读取右括号后面的一个字符
BigInteger index = BigInteger.ONE;
if (lexer.peek().equals("*") && lexer.peekNext().equals("*")) {
lexer.next();//读取第二个*
lexer.next();//读取指数
if (lexer.peek().equals("+")) { //如果指数前有正号,就再读一下
lexer.next();
}
index = new BigInteger(lexer.peek());
if (index.equals(Constant.ZERO)) { //讨论指数是否为0
lexer.next();//要先向后读一个
return new Expr();
}
lexer.next();
此处提前剧透一下我的可怜bug:起初我没有写“讨论指数是否为0”的那个if
,导致第三次作业强测出现了format error
,特此为戒!
自定义函数
首先我新建了Func
类以表示自定义函数,实现因子接口,内部设有funcName
(函数名)、newFunc
(代入实参后的函数表达式)、funcExpr
(函数表达式)等属性。
同时我设计了两个工具方法用于和函数调用,这两个方法都定义在一个单独的类FuncDefiner
中,该类作为工具类,所有属性和方法都设置为static
类型。
defineFunc
:函数定义时对函数表达式进行解析,以等号为界将函数名和表达式分开,装入HashMap
中形成键值对,并把形参和函数名也装入HashMap
中,便于替换参数。callFunc
:函数调用时使用,传入函数名和实参列表,根据散列表的映射关系将函数表达式中的形参替换为实参(注意为字符串替换)。
自定义函数的函数名只能为f,g,h
,原有的解析逻辑仍旧适用,不过需要加入parseFuncFactor()
方法,该方法在读取到函数名时被调用,依次解析函数括号内的因子(实参)并放入容器中,最后将函数名和实参容器交给Func
类的构造方法,返回函数对象。
以上只是大致流程,具体实现仍有很多步骤,不再赘述。
单项式结构调整
使用哈希表存放连续相乘的sin和cos因子,整体结构如下:
a × x b × y c × z d × hashmap sin ( ( expr ) , exponent s i n ) × hashmap cos ( ( expr ) , exponent cos ) a \times x^{b} \times y^{c} \times z^{d} \times \text { hashmap }_{\text {sin }}\left((\text { expr }), \text { exponent }_{s i n}\right) \times \text { hashmap }_{\text {cos }}\left((\text { expr }), \text { exponent }_{\text {cos }}\right) a×xb×yc×zd× hashmap sin (( expr ), exponent sin)× hashmap cos (( expr ), exponent cos )
其中Expr代表三角函数括号内的表达式,exp代表指数。
这样仍旧可以转化成统一的形式来分析,这一“单项式结构”可以使用至本单元结束。
相等的判定
另一个需要解决的问题是如何判断两个三角函数括号内的表达式是否相等,从而进行合并。
因为表达式(可以看成是多项式)是由多个单项式相加而成的,故只需要为Expr
和Operation
两个类重写equals
方法,这是本次作业的难点之一。
- 首先是多项式的判等方法,这个逻辑比较简单,首先判断表达式的长度是否相同,不同则返回
false
,随后二重循环遍历每一项,只有当两项的系数和变量完全相等时才算相等(这里需要判断系数,因为不是合并):
public boolean equals(Expr expr) {
if (this.getValues().size() == expr.getValues().size()) { //如果两个表达式长度相同,再进行比较
for (int i = 0; i < this.values.size(); ++i) {
if (this.values.get(i).getCoef().equals(new BigInteger("0"))) {
continue;
}
Boolean flag = false;
for (int j = 0; j < expr.getValues().size(); ++j) {
if (this.values.get(i).equals(expr.getValues().get(j)) && this.
values.get(i).getCoef().equals(expr.getValues().get(j).getCoef())) {
flag = true;
}
}
- 然后是单项式的判等,先判定
xyz
的指数和三角函数Map
的size
是否相等,再遍历三角函数Map
,只有当所有的三角函数内部表达式和指数都相等时才相等:
public boolean equals(Operation o) {
Expr expr0 = new Expr(BigInteger.ZERO);
if (!this.indexX.equals(o.getIndexX()) ||
!this.indexY.equals(o.getIndexY()) ||
!this.indexZ.equals(o.getIndexZ()) ||
this.sinMap.size() != o.getSinMap().size() ||
this.cosMap.size() != o.getCosMap().size()
) {
return false;
} else {
for (Expr expr1 : this.sinMap.keySet()) {
Boolean flag = false;
for (Expr expr2 : o.getSinMap().keySet()) {
if (!expr1.equals(expr0) && expr1.equals(expr2) &&
sinMap.get(expr1).equals(o.getSinMap().get(expr2))) {
flag = true;
break;
}
}
注意这里并没有对coef
进行判断,因为在合并同类项时并不需要系数相同。
这样我们的equals
方法就重写完成了。
UML类图
本次作业的UML类图如下:
复杂度分析
如下图(同上没有截全,下面的方法复杂度均为0或1):
可见加入了三角函数后,整体的复杂度更上一层台阶。
Operation
类中的toString()
方法复杂度极高,原因在于三角函数的引进迫使增加更多的if
条件语句和三目运算符进行特判,输出逻辑非常复杂。即便我从中抽离出两个方法setSin()
和setCos()
,但它们中仍旧存在大量的特判和循环语句,没能成功降低复杂度。这也直接导致了mult
和addTerm
两个运算方法的复杂度升高。- 两个
equals
方法同样含有大量条件和循环语句以遍历所有的情况,导致复杂度升高。 - 在加入了三角函数和自定义函数两种因子后,
parseFactor()
内部需要讨论的情况达到6种,每一种的内部仍包含条件或循环结构,复杂度急剧攀升。
测试与bug分析
本次作业在强测和互测中出现bug,具体如下:
- 在比较表达式是否相等时,没有优先判定两者的长度是否相同,导致当一个表达式是另一个表达式的子集时,仍会判定两者相等。原因在于equals函数一开始没有进行该判定。
- 继上一条优化后,
sin(0)
和cos(0)
的处理出现问题,无法将其化简为0和1。原因为在输出特判时没有讨论三角函数内的表达式是否为0,需加入一次判定。 - 当
cos(0)
和另外一个cos
相乘时,我的代码会认为它们相等。原因是:当三角函数括号内出现0时,0会作为Expr
被传入equals
比较,而equals存在上述问题,故得到错误结果。我的解决方案为,凡是涉及合并或比较相等,都在传入方法前判定是否为0,如果为0则直接不传入。
结合复杂度分析可知:代码的行数越多、圈复杂度越高,出现bug的可能性越大,这是合乎情理的,也是难以避免的,本次出现的bug所在方法基本都具有较高的复杂度。
总结
第二次作业相较于第一次有很大的跨度,假如第一次的代码扩展性不太好,很可能需要重构。我在前期思路整理、代码书写方面还算顺利,但因为严重缺乏debug经验,后续找bug花费了大量时间,一些基本算法如mult
、equals
都无法独立完成(大一程设留下的烂摊子…),只好依助同学的思路来写。
中途令我百思不得其解的“mult问题”在第三次作业中得到了解决,其实是浅克隆引发的对象属性被修改。(当时真的心态要爆炸了)
Homework 3
本次作业新增求导运算,且自定义函数在定义时可以调用其他已经定义的函数,复杂度进一步提升。
补充自定义函数与求导算子相关部分:
自定义函数相关(相关限制见“公测数据限制”)
- 自定义函数定义 →→ 自定义函数名 空白项 ‘(’ 空白项 自变量 空白项 [‘,’ 空白项 自变量 空白项 [‘,’ 空白项 自变量 空白项]] ‘)’ 空白项 ‘=’ 空白项 函数表达式
- 自定义函数调用 →→ 自定义函数名 空白项 ‘(’ 空白项 因子 空白项 [‘,’ 空白项 因子 空白项 [‘,’ 空白项 因子 空白项]] ‘)’
- 自定义函数名 →→ ‘f’ | ‘g’ | ‘h’
- 函数表达式 →→ 表达式 (注:本次作业函数表达式中可以调用其他自定义函数,但保证不会出现递归调用的情况)
求导算子相关(相关限制见“公测数据限制”)
- 求导因子 →→ 求导算子 空白项 ‘(’ 空白项 表达式 空白项 ‘)’
- 求导算子 →→ ‘dx’ |’dy’ |’dz’
迭代开发
1. 自定义函数的“链式调用”
本次作业允许自定义函数表达式中调用其他“已定义”的函数,我习惯将其通俗地理解为“链式调用”。
由于自定义函数的表达式在输入时就已经完成解析并加入散列表中,故即便后面定义的函数表达式中出现了刚刚定义过的函数,在解析时也会被识别并完成变量替换,和第二次作业中解析输入的待化简表达式是同样的道理。最后得到的一定是化简后的表达式。
所以,这一部分无需调整先前的代码便已经实现。求导因子也一样。
2. 导数计算
2.1 基本结构
沿用上一次的单项式定义:
a × x b × y c × z d × hashmap sin ( ( expr ) , exponent s i n ) × hashmap cos ( ( expr ) , exponent cos ) a \times x^{b} \times y^{c} \times z^{d} \times \text { hashmap }_{\text {sin }}\left((\text { expr }), \text { exponent }_{s i n}\right) \times \text { hashmap }_{\text {cos }}\left((\text { expr }), \text { exponent }_{\text {cos }}\right) a×xb×yc×zd× hashmap sin (( expr ), exponent sin)× hashmap cos (( expr ), exponent cos )
只需要考虑对“单项式”的求导法则即可。
对于多项式,可以把它看作很多个单项式的和,遍历其中的单项式,分别求导即可处理。
2.2 单项式求导
-
可以先判定一下系数,如果为0直接跳过(或许不判定也可以,不过if一下还是比较保险)。
-
对前面的“幂函数”因子求导,系数相乘,指数减一,注意减一后是否会等于0或者负数(也可以在计算前先判定一下,免得夜长梦多)。
-
对
sinMap
和cosMap
求导,注意它们都是连乘的,对其中的一个求导前需要保存除它之外的所有组分,这里很容易出问题,简单叙述一下我的处理步骤:- 一般而言,单项式求导后得到的将是一个多项式(结合乘法规则不难理解),所以在一开始需要创建一个多项式对象用以存放求导结果。
- 外层循环遍历该单项式的
sinMap
,循环体内新建一个单项式对象,将原单项式的系数、xyz
的指数、cosMap
、sinMap
中除当前遍历到的sin
因子外的所有因子全部深拷贝到新建的单项式对象中。(深拷贝十分有必要,具体原因下文会提到) - 对提取出的“
sin^exp^(Expr)
”求导,和幂函数类似,系数和指数分别处理,将处理后的sin
因子添加到单项式的sinMap
中,同时别忘了把sin
求导得到的cos
添加到cosMap
中(链式规则)。 - 随后对
sin
内的Expr
求导,调用我们已经写好的多项式求导方法即可。注意:Expr求导的结果需要和单项式相乘,利用分配律拆开。这一步从某种意义上解释了为何“单项式求导后会变成多项式”。 - 做完了上面这些,一个单项式的求导工作就完成了。
-
cosMap
的求导和sinMap
完全类似,要注意的是cos转换成sin后的负号,可以直接令单项式的系数取相反数,若采用BigInteger存储,可以表示为:o1.setCoef(o1.getCoef().negate());//o1代表单项式对象
2.3 多变量处理
求导因子后的变量有三种情况,自然可以使用统一形式来匹配(比如正则),不过我没有找到特别好的匹配模式和统一的方法接口(脑子不很灵光,想着想着就绕进去了…),于是采用了“一根筋”式做法:对xyz分别建立求导方法,调用时将自变量当作字符串传入,再通过switch-case分类调用对应的方法。虽然较为低级,但思路还是比较清晰的。
UML类图
我新建了一个处理求导的类,其中定义了对表达式、三个自变量、三角函数的求导方法。
复杂度分析
本次复杂度较高的方法和上次基本相同。
测试与bug分析
自己的bug
-
本次作业在强测中出现了较大问题,三角函数求导方法内使用了常量类定义的
SINMAP
和COSMAP
!!!再次出现了深浅克隆的老毛病!(真的没想到之前改了那么多坑,还有俩没改过来qwq)解决办法很简单,把所有要用到
HashMap
的地方都换成new HashMap<>()
即可,如下图:
在互测中中的两刀也是这个问题(无比懊恼)。
- 强测中出现的另一个Bug为:三角函数的零次方后紧跟着乘以三角函数,最后会多打印一个*。问题在于
parseSinCosFacotr()
方法中没有对指数是否为0的讨论,导致Map中多出来一个指数为0的项,而实际上指数为0的项不会被打印,但仍旧占一个位子,故而会在末尾多输出一个*。
从这里也能看出,绝对不能仅依赖中测的数据,课下还需要自行构造覆盖度更加全面的数据进行测试,保证万无一失。
hack历程
本次作业中我采用了将水群中数据和自己构造测试用例相结合的方式,成功hack了9次(虽然有6次hack中的都是同一个人hhh),对这个结果我是相对满意的。
水群中分享的数据可以为hack提供突破口和思考方向,如dy(x)
这类数据,可有效测试求导时变量的一致性问题,这类问题在公测中较难测出,作为hack用例再合适不过。
此外,我还将第二次作业互测中自己被hack的数据拿来进行了一番测试,意外地也成功命中了!有点惊喜。
但我的测试仍旧存在很大的不足:首先,我没有下载其他人的代码到本地进行测试,其次,我没有系统化的测试思路,常常遇到输入数据不合法的情况,效率低下。
坑点总结
1. 深浅克隆问题
-
一定一定要注意深浅克隆问题,我在一开始的时候所有求导方法都没有传入对象参数,直接对“当前类的
this
对象”进行操作,导致一次迭代后原本的值被改变,改变过的值又参与到下一次迭代中,得到的结果简直恐怖。 -
因此稳妥的策略为:给求导方法传入对象引用作为形参,方法内部新建对象保存中间值并返回新的对象引用。
-
复制对象属性时要使用深克隆。
2. final问题(本质仍为深浅克隆)
以下为反面教材,仅作演示参考,切勿模仿!!!
为了方便处理,我设定了一个Constant
类用以定义代码中经常用到的BigInteger.ZERO
、BigInteger.ONE
、空的sinMap
和空的cosMap
等常量,且它们都使用static final
来修饰,必要时直接通过Constant.ZERO
(举例)以快速调用而不需单独定义,具体如下:
public static final HashMap<Expr, BigInteger> SINMAP = new HashMap<>();
public static final HashMap<Expr, BigInteger> COSMAP = new HashMap<>();
//定义常量0和1
public static final BigInteger ONE = BigInteger.ONE;
public static final BigInteger ZERO = BigInteger.ZERO;
但这样的设定出现了严重的问题:使用static final
修饰的sinMap
和cosMap
仍旧可以被修改,并且下次被调用时获取到的已经是修改后的结果!细思极恐吧,这就是发生在我身上的事情。
一番查阅后得知:final关键字修饰基本类型变量和引用类型变量时存在本质区别!
当使用 final 修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变。 但对于引用类型变量而言,它保存的仅仅是一个引用,final 只保证这个引用类型变量所引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变。
因此上方代码中的ONE
和ZERO
是不可被改变的,而SINMAP
和COSMAP
只能保证该引用不会改变,无法锁定对象自身的属性。
所以还是本本分分创建新的对象吧,至少能保证不出错。
优点&缺点
优点
- 在很多层面上实现了统一处理,如比较相等和求导运算均可以只在表达式和单项式层面考虑、实现
Facotr
接口的类均只需要实现高度同质化的方法(toString()
和getValues()
)、表达式的addTerm()
和项的addFactor()
实现了形式上的统一、统一处理表达式和项开头可能出现的正负号等。 - 每当输入一个函数表达式就立即完成解析,整体逻辑保持一致,每一部分只需要完成各自的任务,其余都交给递归,即可保证结果无误,而不必为具体细节过度费心,可以有效防止递归思考的僵局。
不足
- 没有很好地处理深浅拷贝问题,当前代码中存在很多补丁,第三次作业甚至把所有涉及新建对象的地方都更改了一遍。
- 没有将
Sin
和Cos
设为Tri
的子类,并不符合面向对象编程的基本思想。 - 没有应用典型的设计模式如工厂模式,在很多细节仍旧存在面向过程的味道。
性能
这几次作业中我都没有刻意去提升性能,只是在输出时将一些方便处理的形式做了简化,如把x**2
输出为x*x
,此外在各个类的toString()
方法中我也做了一些处理,比如在Expr
类中遇到为0的项就不拼接到字符串中。
总结
这次作业是三次中完成最顺利的一次,也是唯一一次周五就通过了中测。但正是前期太过顺利导致我在通过了公测后基本没再自行测试,甚至没有再仔细看代码,导致强测中暴露了很多中测未能测出的问题,实属掉以轻心。
直到现在都没有使用过评测机,本地测试也只是白嫖或手搓一些数据,数据覆盖面不够广泛,接下来应考虑在本地利用评测机自动化测试。
心得体会
这一单元的学习可以说是自身能力的一次飞跃,从开始时的茫然无助到通宵debug终苦尽甘来,我在这个过程中收获的,不仅仅是知识。
由于先前从未接触过面向对象这种思想,假期也只是自学了一些面向对象的课程,没有经过实战训练,终究没能体会这种思想的精髓。本学期前三周给我的体验感远超以往的所有课程,若非先导课的作业练习和基本的Java语法知识,纯粹从零开始可谓“难于上青天”!对这一点,我深有感触。
现阶段我仍存在很多不足,代码中仍然存在很多不符合面向对象编程的方法和结构,假期自学的设计模式也完全没有用武之地,尚未使用评测机进行本地测试,等等。接下来的学习过程中我应进一步培养自身的代码能力和设计能力,尝试走出舒适圈,迎接全新的挑战。
一些参考
-
「BUAA-OO」第一单元:表达式展开)(第二次作业迭代思路提供)
-
「BUAA OO Unit 1 HW4」第一单元总结)(行文架构、文章名称参考)
-
「BUAA-OO-Unit1」)(UML画法启蒙)