34

我用 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] < '0' || 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;
}
4

8 回答 8

41

有几种方法。都有自己的优点和缺点。

  • 让函数返回错误代码并传入指向位置的指针以返回结果。这样做的好处是没有结果过载。不好的是你不能直接在表达式中使用函数的真实结果。

    Evan Teran 提出了一个变体,让调用者传递一个指向成功变量的指针(如果调用者不关心,可以选择为 NULL)并从函数返回实际值。这样做的好处是,当调用者在错误结果中使用默认值时可以直接在表达式中使用该函数,或者知道该函数不会失败。

  • 使用特殊的“哨兵”返回值来指示错误,例如负数(如果正常返回值不能为负),INT_MAX或者INT_MIN如果好的值不能那么极端。有时为了获得更详细的错误信息,需要参考对另一个函数(例如GetLastError())或全局变量的调用(例如errno)。当您的返回值没有无效值时,这不起作用,并且通常被许多人认为是错误的形式。

    使用此技术的示例函数是 getc(),如果到达文件末尾或遇到错误,它将返回 EOF。

  • 让函数永远不会直接返回错误指示,而是要求调用者查询另一个函数或全局。这类似于 VB 的 " On Error Goto Next" 模式的工作原理——而且它几乎被普遍认为是一种糟糕的方式。

  • 另一种方法是使用“默认”值。例如,该atoi()函数与您的函数具有几乎相同的功能intval(),当它无法转换任何字符时将返回 0(它与您的函数不同,它消耗字符进行转换,直到它到达字符串的末尾或不是数字的字符)。

    这里明显的缺点是,判断实际值是否已转换或垃圾是否已传递给atoi().

    我不是这种处理错误的方式的超级粉丝。

当我想到其他选项时,我会更新...

于 2008-11-15T00:24:35.047 回答
17

好吧,.NET 在Int32.TryParse中处理此问题的方式是返回成功/失败,并使用传递引用参数将解析值传回。在 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
}
于 2008-11-15T00:08:54.500 回答
12

一种常见的方法是传递一个指向成功标志的指针,如下所示:

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");
}
于 2008-11-15T00:06:26.737 回答
6

os 风格的全局 errno 变量也很流行。使用errno.h.

如果 errno 不为零,则说明出现问题。

这是errno的手册页参考。

于 2008-11-15T00:08:59.337 回答
4

看看标准库是如何处理这个问题的:

long  strtol(const  char  * restrict str,  char **restrict endptr, int base);

在这里,调用后 endptr 指向第一个无法解析的字符。如果 endptr == str,则没有字符被转换,这是一个问题。

于 2008-11-17T17:38:51.323 回答
3

总的来说,我更喜欢 Jon Skeet 提出的方式,即。返回一个关于成功的 bool(int 或 uint)并将结果存储在传递的地址中。但是您的函数与 strtol 非常相似,因此我认为为您的函数使用相同(或相似)的 API 是个好主意。如果您给它起一个类似的名称,例如 my_strtos32,则无需阅读任何文档即可轻松理解该函数的作用。

编辑:由于您的函数明确基于 10,因此 my_strtos32_base10 是一个更好的名称。只要您的功能不是瓶颈,您就可以跳过您的实现。并简单地环绕 strtol:


s32
my_strtos32_base10(const char *nptr, char **endptr)
{
    long ret;
    ret = strtol(nptr, endptr, 10);
    return ret;
}

如果您后来意识到它是一个瓶颈,您仍然可以根据您的需要对其进行优化。

于 2008-11-15T11:44:57.357 回答
1

您可以返回一个类的实例,其中一个属性是感兴趣的值,另一个属性是某种状态标志。或者,传入结果类的一个实例..

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 绝对是我想说的最多。)

于 2008-11-15T03:43:04.803 回答
0

当我已经返回一个值时,从函数返回错误的最佳方法是什么?

对各种答案的一些额外想法。


返回一个结构

代码可以返回一个值和一个错误代码。一个问题是类型的扩散。

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 <math.h>
#include <stddef.h>

double y = foo(x);
if (isnan(y)) {
  Process_Error();
}

void *ptr = bar(x);
if (ptr == NULL) {
  Process_Error();
}

_Generic/函数重载

error_t foo(&dest, x)考虑到vs.的优缺点dest_t foo(x, &error)

通过级联使用_Generic或函数重载作为编译器扩展,选择 2 种或更多类型,根据调用的参数而不是返回值来区分调用的底层函数是有意义的。返回普通类型,错误状态。

示例:将error_t narrow(destination_t *, source_t)一种类型的值转换为更窄类型的函数,例如long longshort测试源是否在目标类型的范围内。

long long ll = ...; 
int i;
char ch; 
error = narrow(&i, ll);
...
error = narrow(&ch, i);
于 2018-07-13T21:51:47.187 回答