当我已经返回一个值时,从函数返回错误的最佳方法是什么?
我在C中编写了一个函数,它将字符串转换为整数并返回整数。 当我调用该函数时,我也希望它告诉我字符串是不是有效数字。 在过去,当发生此错误时,我返回-1,因为我不需要将字符串转换为负数。 但现在我希望它将字符串转换为负数,那么报告错误的最佳方法是什么?
如果我不清楚这个:我不希望这个函数向用户报告错误,我希望它将错误报告给调用该函数的代码。 (“报告”可能是错误的用词…)
这是代码:
s32 intval(const char *string) { bool negative = false; u32 current_char = 0; if (string[0] == '-') { negative = true; current_char = 1; } s32 num = 0; while (string[current_char]) { if (string[current_char] '9') { // Return an error here.. but how? } num *= 10; num += string[current_char] - '0'; current_char++; } if (negative) { num = -num; } return num; }
有几种方法。 所有人都有自己的优点和缺点。
-
让函数返回错误代码并传入指向某个位置的指针以返回结果。 关于这一点的好处是没有结果的重载。 不好的是你不能直接在表达式中使用函数的实际结果。
Evan Teran提出了一个变体,它使调用者传递一个指向成功变量的指针(如果调用者不关心,可以选择为NULL)并返回函数的实际值。 这样做的好处是,当调用者在错误结果中使用默认值或者知道函数不能失败时,允许函数直接在表达式中使用。
-
使用特殊的“sentinel”返回值来指示错误,例如负数(如果正常返回值不能为负数)或
INT_MAX
或INT_MIN
如果良好值不能达到极值)。 有时为了获得更详细的错误信息,需要查询另一个函数(例如GetLastError()
)或全局变量(例如errno
)。 当您的返回值没有无效值时,这不能很好地工作,并且被许多人认为通常被认为是不良forms。使用此技术的示例函数是getc(),如果到达文件末尾或遇到错误,则返回EOF。
-
函数永远不会直接返回错误指示,但要求调用者查询另一个函数或全局函数。 这类似于VB的“
On Error Goto Next
”模式的工作方式 – 而且它几乎普遍被认为是一种糟糕的方式。 -
另一种方法是拥有“默认”值。 例如,
atoi()
函数与intval()
函数具有几乎相同的function,当它无法转换任何字符时将返回0(它与您的函数不同,因为它消耗字符进行转换,直到达到字符串的结尾或不是数字的字符)。这里明显的缺点是,判断实际值是否已转换或者是否已将垃圾传递给
atoi()
可能会很棘手。我不是这种处理错误的忠实粉丝。
我会更新,因为其他选项在我脑海中浮现……
那么,.NET在Int32.TryParse中处理它的方式是返回成功/失败,并使用pass-by-reference参数传回解析后的值。 同样可以在C中应用:
int intval(const char *string, s32 *parsed) { *parsed = 0; // So that if we return an error, the value is well-defined // Normal code, returning error codes if necessary // ... *parsed = num; return SUCCESS; // Or whatever }
一种常见的方法是将指针传递给成功标志,如下所示:
int my_function(int *ok) { /* whatever */ if(ok) { *ok = success; } return ret_val; }
这样叫:
int ok; int ret = my_function(&ok); if(ok) { /* use ret safely here */ }
编辑:这里的示例实现:
s32 intval(const char *string, int *ok) { bool negative = false; u32 current_char = 0; if (string[0] == '-') { negative = true; current_char = 1; } s32 num = 0; while (string[current_char]) { if (string[current_char] < '0' || string[current_char] > '9') { // Return an error here.. but how? if(ok) { *ok = 0; } } num *= 10; num += string[current_char] - '0'; current_char++; } if (negative) { num = -num; } if(ok) { *ok = 1; } return num; } int ok; s32 val = intval("123a", &ok); if(ok) { printf("conversion successful\n"); }
os风格的全局errno变量也很受欢迎。 使用errno.h
。
如果errno不为零,则出现问题。
这是errno的手册页参考。
看看标准库如何处理这个问题:
long strtol(const char * restrict str, char **restrict endptr, int base);
这里,在调用之后,endptr指向第一个无法解析的字符。 如果endptr == str,则没有转换任何字符,这是一个问题。
总的来说,我更喜欢Jon Skeet提出的方式,即。 返回bool(int或uint)关于成功并将结果存储在传递的地址中。 但是你的函数与strtol非常相似,所以我认为为你的函数使用相同(或类似)的API是个好主意。 如果你给它一个类似my_strtos32这样的名字,这样就可以很容易地理解这个函数在没有任何文档阅读的情况下的作用。
编辑:由于您的函数显式基于10,my_strtos32_base10是一个更好的名称。 只要您的function不是瓶颈,您就可以跳过实施。 只需环绕strtol:
s32 my_strtos32_base10(const char *nptr, char **endptr) { long ret; ret = strtol(nptr, endptr, 10); return ret; }
如果您以后将其视为瓶颈,您仍然可以根据需要对其进行优化。
您可以返回属性为感兴趣的值的类的实例,另一个属性将是某种类型的状态标志。 或者,传递结果类的实例..
Pseudo code MyErrStatEnum = (myUndefined, myOK, myNegativeVal, myWhatever) ResultClass Value:Integer; ErrorStatus:MyErrStatEnum
例1:
result := yourMethod(inputString) if Result.ErrorStatus = myOK then use Result.Value else do something with Result.ErrorStatus free result
例2
create result yourMethod(inputString, result) if Result.ErrorStatus = myOK then use Result.Value else do something with Result.ErrorStatus free result
这种方法的好处是您可以通过向Result类添加其他属性来随时扩展回来的信息。
为了进一步扩展这个概念,它也适用于具有多个输入参数的方法调用。 例如,代替CallYourMethod(val1,val2,val3,bool1,bool2,string1)而不是具有与val1,val2,val3,bool1,bool2,string1匹配的属性的类,并将其用作单个输入参数。 它可以清除方法调用,并使代码在将来更容易修改。 我相信你已经看到使用多个参数的方法调用更难以使用/调试。 (7是绝对最多的我会说。)
当我已经返回一个值时,从函数返回错误的最佳方法是什么?
对各种答案的一些额外想法。
返回一个结构
代码可以返回值和错误代码。 一个问题是类型的扩散。
typedef struct { int value; int error; } int_error; int_error intval(const char *string); ... int_error = intval(some_string); if (int_error.error) { Process_Error(); } int only_care_about_value = intval(some_string).value; int only_care_about_error = intval(some_string).error;
非数字和NULL
函数返回类型提供时使用特殊值。
C不需要非数字,但无处不在。
#include #include double y = foo(x); if (isnan(y)) { Process_Error(); } void *ptr = bar(x); if (ptr == NULL) { Process_Error(); }
_Generic
/ Function重载
考虑到error_t foo(&dest, x)
与dest_t foo(x, &error)
利弊,
通过级联使用_Generic
或函数重载作为编译器扩展,选择2种或更多类型,区分被调用的基础函数是基于调用的参数而不是返回值。 返回常见类型,错误状态。
示例:函数error_t narrow(destination_t *, source_t)
将一种类型的值转换为较窄的类型,例如long long
to short
并测试源值是否在目标类型的范围内。
long long ll = ...; int i; char ch; error = narrow(&i, ll); ... error = narrow(&ch, i);