calc.c
#include <math.h>
#include <stdio.h>
#include <string.h>
typedef enum {
AND = '&',
OR = '|',
XOR = '^',
NONE = 0
} operator;
typedef enum {
B02 = 'b',
B10 = 'd',
B16 = 'x'
} type;
const char zero = '0';
const char _A = 'A';
const char _a = 'a';
int main(int argc, char **argv) {
int i, offs, len;
char *nend;
long result = 0, tmp, optmp;
operator op = NONE;
type ntype;
for (i = 1; i < argc; i++) {
len = strlen(argv[i]);
nend = argv[i] + len - 1;
if (len == 1) {
op = *nend;
continue;
}
ntype = *nend;
nend--;
offs = 0;
optmp = 0;
while (offs < len - 1) {
switch (ntype) {
case B02:
tmp = (*(nend - offs) - zero) * pow(2, offs);
break;
case B10:
tmp = (*(nend - offs) - zero) * pow(10, offs);
break;
case B16:
tmp = *(nend - offs);
if (tmp >= _a) tmp -= _a - 10;
else if (tmp >= _A) tmp -= _A - 10;
else tmp -= zero;
tmp *= pow(16, offs);
break;
}
optmp += tmp;
offs++;
}
//printf("%s: offs: %i tmp: %li optmp: %li len: %i result: %li\n", argv[i], offs, tmp, optmp, len, result);
switch (op) {
case AND:
result &= optmp;
break;
case OR:
result |= optmp;
break;
case XOR:
result ^= optmp;
break;
case NONE:
result += optmp;
break;
}
//printf("\n");
}
printf("%li\n", result);
return 0;
}
makecalc.sh
gcc -Wall calc.c -o calc
testcalc.py
import subprocess as subp
import time
_bin = lambda x: bin(x)[2:] + 'b'
_dec = lambda x: str(x) + 'd'
_hex = lambda x: hex(x)[2:] + 'x'
ops = ('&', '|', '^')
types = (_bin, _dec, _hex)
RANGE = 20
tmp = None
def test(op, t, x, y):
call = ["./calc", t(x), op, t(y)]
print call
proc = subp.Popen(call, stdout=subp.PIPE, stdin=subp.PIPE)
data = proc.stdout.readline().replace("\n", "")
exec("tmp = %s %c %s;" % (str(x), op, str(y)))
if tmp != int(data):
print ("fail", tmp, data)
quit()
for op in ops:
for t in types:
for x in range(RANGE):
for y in range(RANGE):
test(op, t, x, y)
#1 楼
我想您不会缩进printf
调试行以使它们更容易发现。这是一个明智的想法(尽管我以前从未见过)。但是,我将“代码审查”答案视为“就在签入之前”,因此建议在这一点上删除这些答案。您需要预先声明变量。不要这样
将它们写在尽可能接近使用点的位置。
您的
NONE
op似乎没什么用,您可以直接使用OR
。因此,将其删除。您的
const char zero = '0';
const char _A = 'A';
const char _a = 'a';
变量似乎是对“无魔法常数”的良好依从性,但实际上它们可以有效地没有有用的目的。如果要更新代码以支持其他字符集,则无论如何都要重新设计整个解析器。恕我直言,内联这些代码更简单。
现代C语言中不需要
return 0;
,而且在某些情况下通常不需要。您的整数解析器应该在基础。我建议将其提取到类似
int parse_digit(char character, int base, int place)
的函数中:int parse_digit(char character, int base, int place) {
int value;
if (character >= 'a') {
value = character - 'a' + 10;
}
else if (character >= 'A') {
value = character - 'A' + 10;
}
else {
value = character - '0';
}
return value * pow(base, place);
}
while (offs < len - 1) {
optmp += parse_digit(*(nend - offs), base, offs);
offs++;
}
base
可以通过在ntype
上进行简单查找来计算: > 尽管这使
type
看起来很傻:毕竟这只是一个整数!int type_to_base(type ntype) {
switch (ntype) {
case B02: return 2;
case B10: return 10;
case B16: return 16;
}
}
请注意,这是一种愚蠢的事情不过。首先,
enum
需要pow
s!如果可能,我宁愿避免这种情况。此外,这是需要完成的工作。相反,可以执行以下操作:int flag_to_base(char flag) {
switch (flag) {
case 'b': return 2;
case 'd': return 10;
case 'x': return 16;
}
}
这可以通过以下方式完成:
1*base^4 + 0*base^3 + 1*base^2 + 0*base^1 + 1*base^0 // how you're currently doing it
= (((1 * base + 0) * base + 1) * base + 0) * base + 1 // simpler way to do it
解析整数应该在另一个函数中,可以稍微简化一下:
int parse_digit(char character) {
if (character >= 'a') {
return character - 'a' + 10;
}
else if (character >= 'A') {
return character - 'A' + 10;
}
else {
return character - '0';
}
}
while (offs < len - 1) {
optmp *= base;
optmp += parse_digit(*(nend - offs));
offs++;
}
我还将把
double
的应用程序放到一个函数中,并停止调用optmp
(通常,大多数变量是临时的-说明这毫无意义):long parse_int(char *start, char *end) {
int base = flag_to_base(*end);
long ret = 0;
for (char *digit = start; digit < end; digit++) {
ret *= base;
ret += parse_digit(*digit);
}
return ret;
}
然后,我会(通常)将
optmp
更改为long
,将uint64_t
更改为int
(甚至uint32_t
)-在这种时代,没有必要为这些类型使用可变长度整数,并且您不需要带符号的值。当您确实想要可变长度值时,您可能宁愿坚持使用uint8_t
。 (如果发现这些问题比较笨拙,请size_t
替换为typedef
,u64
和u32
。只是不要使用u8
和int
,因为它们更漂亮。) long apply(operator op, long left, long right) {
switch (op) {
case AND: return left & right;
case OR: return left | right;
case XOR: return left ^ right;
}
}
这是因为新方法可以进行更好的案例分析-我们忘记了检查此类操作是否有效!
calc.c: In function ‘apply’:
calc.c:18:1: warning: control reaches end of non-void function [-Wreturn-type]
}
^
calc.c: In function ‘flag_to_base’:
calc.c:26:1: warning: control reaches end of non-void function [-Wreturn-type]
}
^
请注意,在将枚举转换为可能导致“无效”枚举的枚举时,我会有所警惕,但至少它是定义良好的行为。
对于Python ,您的
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
typedef enum {
AND = '&',
OR = '|',
XOR = '^',
} operator;
int apply(operator op, uint64_t *left, uint64_t right) {
switch (op) {
case AND: *left &= right; break;
case OR: *left |= right; break;
case XOR: *left ^= right; break;
default: return -1;
}
return 0;
}
int flag_to_base(char flag, uint8_t *base) {
switch (flag) {
case 'b': *base = 2; break;
case 'd': *base = 10; break;
case 'x': *base = 16; break;
default: return -1;
}
return 0;
}
int parse_digit(char digit, uint8_t *value) {
if ('a' <= digit && digit <= 'z') {
*value = digit - 'a' + 10;
}
else if ('A' <= digit && digit <= 'Z') {
*value = digit - 'A' + 10;
}
else if ('0' <= digit && digit <= '9') {
*value = digit - '0';
}
else {
return -1;
}
return 0;
}
int parse_int(uint64_t *ret, char *start, char *end) {
uint8_t base;
if (flag_to_base(*end, &base)) {
return -1;
}
*ret = 0;
for (char *digit = start; digit < end; digit++) {
uint8_t value;
if (parse_digit(*digit, &value) || value >= base) {
return -1;
}
*ret *= base;
*ret += value;
}
return 0;
}
int main(int argc, char **argv) {
uint64_t result = 0;
operator op = OR;
for (int i = 1; i < argc; i++) {
size_t len = strlen(argv[i]);
char *end = argv[i] + len - 1;
if (len == 1) {
op = *end;
continue;
}
uint64_t operand;
if (parse_int(&operand, argv[i], end)) {
printf("Can't parse argument '%s'\n", argv[i]);
return -1;
}
if (apply(op, &result, operand)) {
printf("Can't parse argument '%c'\n", op);
return -1;
}
}
printf("%li\n", result);
}
就可以了
_bin = lambda x: bin(x)[2:] + 'b'
_dec = lambda x: str(x) + 'd'
_hex = lambda x: hex(x)[2:] + 'x'
您的嵌入式循环可以简化为
_bin = "{:b}b".format
_dec = "{:d}d".format
_hex = "{:x}x".format
您不使用
long
-将其删除。我将直接导入time
和subprocess
而不是将subp
重命名为Popen
。请使用新的格式设置!另外,请使用PIPE
而不是eval
-最好不要使用exec
,尽管在这种情况下使用它是可以理解的(即使仍然令人不安)。解释器的用法。在这种情况下,我实际上建议仅quit
。您的子流程调用仅需要输出,因此删除
assert
。此外,您可以致电stdin=PIPE
:from itertools import product
for args in product(ops, types, range(RANGE), range(RANGE)):
test(*args)
#2 楼
这写得很好,很高兴阅读。我的大部分评论都将涉及可用性和可读性。
NONE
运算符typedef enum {
AND = '&',
OR = '|',
XOR = '^',
NONE = 0
} operator;
NONE
有点在那里:它不是运算符。让我想知道它的用法。
结果,
如果运算符是
NONE
,然后计算器用
+
进行加法。因此,最好将其更改为
ADD = '+'
。这样,一切都会变得很清楚,
为什么将加法默认设置为
,但这不是一个大问题。
输入验证
分配运算符时,
验证输入。例如,如果您运行以下命令:
./calc 5d x 4d 6d
在我的计算机上,我得到5作为输出,没有任何意义。
当然会发生什么,
默认的运算符是加法运算,就像我们之前看到的那样,
所以
5
被添加到q43120 79q,之后将不再进行任何操作,因为
result
的值为op
,它将与任何'x'
语句都不匹配。用户友好的操作类似地,操作数也不会得到验证,
也可能导致意外结果。
再次,崩溃将导致错误。更好。
case
语句应具有switch
大小写通常建议
default
语句应具有switch
大小写。您的两个
default
语句不具有如果没有,它会使读者猜测如果没有大小写匹配的情况,程序将会发生什么。
命名比原始图片稍长:
typedef enum {
BASE_2 = 'b',
BASE_10 = 'd',
BASE_16 = 'x'
} type;
或者:
typedef enum {
BINARY = 'b',
DECIMAL = 'd', // or DENARY
HEXADECIMAL = 'x'
} type;
一个其中的一个与众不同:
const char zero = '0';
const char _A = 'A';
const char _a = 'a';
我正在看
switch
。 您可以轻松地使它看起来像其他样式:
const char _0 = '0';
另一件事,
我跳过了阅读
zero
元素,/>,然后在代码的中间,当我看到
enum
,zero
,_a
时,并不清楚它们是什么。
这可能是个问题,
但是我实际上会更喜欢
_A
,'0'
,'a'
而不是命名的常量。 *(nend - offs)
我会在
'A'
之前给它起一个名字,然后在以下情况下重新使用它:char c = *(nend - offs);
易用性
我对此有一个小小的可用性抱怨,那就是在shell中运行程序时,我必须转义一些运算符,例如:
./calc 5d '&' 9d
虽然不确定什么是一个好的解决方案。只是让您知道。
另一件事,
我注意到在Python代码中,您将
case
和switch
表示法转换为自己的0xNUM
,0bNUM
表示法。如何重用Python的?至少您会遵循一些标准,而不是发明新的东西。
使用用户熟悉的现有东西的好处。
我做的第一件事是测试了您的代码而没有阅读,
而当
NUMx
没有提供我期望的结果时,我有点沮丧。Python 2?
为什么Python 2?为什么不使用Python 3?
顺便说一句,您的
NUMb
语句之一使用括号(因此可以用于Python 3),而另一个则不需要。同时为Python 3做好准备将是一件好事。评论
\ $ \ begingroup \ $
NONE的“运算符”可以在参数序列中放置第一个数字。感谢您的评论。
\ $ \ endgroup \ $
–motoku
2015年6月14日在18:47
\ $ \ begingroup \ $
您在这里指出的大多数问题都是我考虑过的;但没有这么详细。我的想法是我不想弄乱代码。但是您已经明确了有效性检查的必要性。我也在考虑将strcmp与运算符一起使用,例如AND或XOR(不再有转义字符)。
\ $ \ endgroup \ $
–motoku
15年6月14日在19:08
#3 楼
我肯定会将主要功能分解为几个较小的功能,并为这些功能找到最好的名称。int convert(const char* str, type in_type)
,bin2hex(...)
,hex2bin(...)
,dec2bin(...)
,int operator_apply(int a, int b, operator op)
。 这将使
main()
更加易于浏览,并且可以通过不同步骤进行自我记录(具有良好的标识符)。现在,所有参数解析,转换,操作都交织在一起,难以阅读。
评论
\ $ \ begingroup \ $
好答案!您能否详细说明不使用可变长度类型?听起来很有趣,但是我什么都没看完。
\ $ \ endgroup \ $
–杰瓦
15年6月14日在20:48
\ $ \ begingroup \ $
@jacwah int和long的大小(以及它们可以容纳的值)取决于平台。另一方面,int32_t始终为32位长。使用int只是意味着您的代码在不同平台上的工作方式不同,通常这不是您想要的。
\ $ \ endgroup \ $
–Veedrac
15年6月14日在21:05
\ $ \ begingroup \ $
“您预先声明了变量。请不要这样做...”您能否详细说明这种偏好?
\ $ \ endgroup \ $
–motoku
15年6月14日在22:45
\ $ \ begingroup \ $
是的。我在问为什么选择它是首选,因为我的教练以其他方式教了我。
\ $ \ endgroup \ $
–motoku
15年6月14日在22:57
\ $ \ begingroup \ $
@MotokoKusanagi的想法是最小化变量的范围,这也可以最大程度地减少实时变量的数量。通过减少交叉变量的交互作用,可以提高灵活性,并且通常更易于阅读。互联网对此也有一些疑问。
\ $ \ endgroup \ $
–Veedrac
15年6月15日在1:10