附加题三号
平时写程序的时候经常会碰见明知道自己不该这么写,却又无法明确自己不足在哪里的地方。经过阅读重构,我发现了我平时编程中存在的各种具体而又细微的问题。由于个人写的程序大部分都是面向过程的,所以很多地方可能会有局限性。以我写的编译器代码为例,我来简要说下存在的问题。
1.循环的最后一次需要特殊处理的案例:
for (i=0;i<n;i++)
{
a+=s[i];
a+=’@’;
a+=s[i].length();
i==n-1?cout<<s[i]<<’,’:cout<<s[i]<<’;’;
}
虽然编译器在开启优化之后可以通过循环提出将最后一条语句提出循环,但这毕竟不是C++标准里提及的,没开优化而影响效率也不该由编译器负责,况且还有很多情况编译器发现不了,因而个人认为应该养成良好习惯,尽可能把能手动重构优化的代码挑选出来。
于是成了:
for (i=0;i<n-1;i++)
{
a+=s[i];
a+=’@’;
a+=s[i].length();
cout<<s[i]<<’,’:
}
a+=s[i];
a+=’@’;
a+=s[i].length();
cout<<s[i]<<’;’;
但这里就存在了另外一个问题,那就是代码冗余,同样的代码手写了两遍。
原先我认为单独为几个句子,写一个函数(其实类似一个过程),虽然代码可读性提高,冗余度降低了,但是却把效率拖慢了,但是我在学习的过程中发现C++的标准里的内联处理其实就跟宏一样是字符串处理并不影响效率,所以改为:
for (i=0;i<n-1;i++)
{
DoSomethingWith(i);
cout<<s[i]<<’,’:
}
DoSomethingWith(i);
cout<<s[i]<<’;’;
如此即可又加强了代码效率,同时也使得代码冗余度并没有增加太多。
2.GOTO语句的使用
以下是我个人观点。诚然goto语句的使用在一定程度上破坏代码循环结构,然而goto语句并不是绝对的坏。
因为我们实际的代码过程中一个稍长的函数里也存在很多非循环的具有拟序关系的代码块。如果if语句来进行嵌套处理,那么if-else的配对会比较繁琐。
对齐上来讲,明明在概念里应该平齐的一些代码块却经常无法对齐:
If (A>40)
{
Dosomthingwith(A);
}
Else
{
if (A>20)
{
Dosomethingwith(A);
}
Else
{
Dosomethingwith(A);
}
}
所以重构为
If (A>40) goto DosomethingwithA_WhenAbgt40;
If (A>20) goto DosomethingwithA_WhenAbgt20;
goto DosomethingwithA_WhenAbgt0;
DosomethingwithA_WhenAbgt0:
DosomethingwithA_WhenAbgt0:
Goto DosomethingwithA_END;
DosomethingwithA_WhenAbgt20:
DosomethingwithA_WhenAbgt20:
Goto DosomethingwithA_END;
DosomethingwithA_WhenAbgt40:
DosomethingwithA_WhenAbgt40:
Goto DosomethingwithA_END;
DosomethingwithA_END:
ENDITALL();
如此能让代码的逻辑层次跟缩进对应的更好一些。但是GOTO确实应该慎用,我认为在特定的工程里可以用特定的含有goto的宏来限制其使用。
3.利用宏生成相似功能的函数
token tokenizer::left_paren(char start)
{
return token("(", token::token_type::lparen, line_num);
}
token tokenizer::right_paren(char start)
{
return token(")", token::token_type::rparen, line_num);
}
token tokenizer::left_bracket(char start)
{
return token("[", token::token_type::lbracket, line_num);
}
token tokenizer::right_bracket(char start)
{
return token("]", token::token_type::rbracket, line_num);
}
token tokenizer::semicolon(char start)
{
return token(";",token::token_type::semicolon,line_num);
}
token tokenizer::comma(char start)
{
return token(",",token::token_type::comma,line_num);
}
假设我需要添加一段错误处理的代码岂不是每一个都得加?
而且,写这么多虽然都是复制粘贴的,但仍然很麻烦啊,代码也很冗余。
#define _IS_FUNCTION(name, value) bool is_##name(token current)\
{\
return current.get_type() == token::value;\
}
_IS_FUNCTION(left_parenthese, LEFT_PARENTHESE)
_IS_FUNCTION(right_parenthese, RIGHT_PARENTHESE)
_IS_FUNCTION(left_bracket, LEFT_BRACKET)
_IS_FUNCTION(right_bracket, RIGHT_BRACKET)
_IS_FUNCTION(semicolon, SEMICOLON)
_IS_FUNCTION(comma, COMMA)
_IS_FUNCTION(add_operator, ADD_OPERATOR)
_IS_FUNCTION(multiply_operator, MULTIPLY_OPERATOR)
_IS_FUNCTION(relation_operator, RELATION_OPERATOR)
_IS_FUNCTION(assign_operator, ASSIGN_OPERATOR)
_IS_FUNCTION(_void, VOID)
_IS_FUNCTION(_main, MAIN)
_IS_FUNCTION(_int, INT)
_IS_FUNCTION(_float, FLOAT)
_IS_FUNCTION(_char, CHAR)
_IS_FUNCTION(_const, CONST)
_IS_FUNCTION(_for, FOR)
_IS_FUNCTION(_while, WHILE)
_IS_FUNCTION(_if, IF)
_IS_FUNCTION(_else, ELSE)
_IS_FUNCTION(_return, RETURN)
_IS_FUNCTION(_scanf, SCANF)
_IS_FUNCTION(_printf, PRINTF)
_IS_FUNCTION(unsigned_integer, UNSIGNED_INTEGER_CONST)
_IS_FUNCTION(unsigned_real, UNSIGNED_REAL_CONST)
_IS_FUNCTION(signed_integer, SIGNED_INTEGER_CONST)
_IS_FUNCTION(signed_real, SIGNED_REAL_CONST)
_IS_FUNCTION(char_const, CHAR_CONST)
_IS_FUNCTION(string_const, STRING_CONST)
_IS_FUNCTION(identifier, IDENTIFIER)
改动时统一改宏就可以了。