【问题标题】:What is the difference between sscanf or atoi to convert a string to an integer?sscanf 或 atoi 将字符串转换为整数有什么区别?
【发布时间】:2011-03-26 03:20:16
【问题描述】:

gcc 4.4.4 c89

将字符串转换为整数值更好。

我尝试了 2 种不同的方法 atoi 和 sscanf。两者都按预期工作。

char digits[3] = "34";
int device_num = 0;

if(sscanf(digits, "%d", &device_num) == EOF) {
    fprintf(stderr, "WARNING: Incorrect value for device\n");
    return FALSE;
}

或使用atoi

device_num = atoi(digits);

我认为 sscanf 会更好,因为您可以检查错误。但是,atoi 不做任何检查。

【问题讨论】:

标签: c scanf atoi


【解决方案1】:

您有 3 个选择:

  1. atoi

如果您在性能关键的代码中使用它,这可能是最快的,但它不会报告错误。如果字符串不以整数开头,则返回 0。如果字符串在整数后包含垃圾,则转换初始部分并忽略其余部分。如果数字太大而无法放入 int,则行为未指定。

  1. sscanf

一些错误报告,并且您可以灵活地选择要存储的类型(char/short/int/long/long long/size_t/ptrdiff_t/intmax_t 的签名/未签名版本)。

返回值是成功的转换次数,因此如果字符串不是以整数开头,则扫描"%d" 将返回0。您可以使用"%d%n" 存储在另一个变量中读取的整数之后的第一个字符的索引,从而检查整个字符串是否已转换或之后是否有垃圾。但是,与atoi 一样,整数溢出的行为是未指定的。

  1. strtol 和家人

强大的错误报告,前提是您在拨打电话之前将 errno 设置为 0。返回值在溢出时指定,errno 将被设置。您可以选择从 2 到 36 的任何数字基数,或指定 0 作为基数以将前导 0x0 分别自动解释为十六进制和八进制。要转换为的类型选择是 long/long long/intmax_t 的有符号/无符号版本。

如果您需要更小的类型,您始终可以将结果存储在一个临时的longunsigned long 变量中,然后自己检查是否溢出。

由于这些函数采用指向指针参数的指针,您还可以免费获得指向转换后的整数后面的第一个字符的指针,因此您可以判断整个字符串是否为整数或在需要时解析字符串中的后续数据.


就个人而言,我会推荐strtol 家庭用于大多数目的。如果您正在做一些快速而肮脏的事情,atoi 可能会满足您的需求。

顺便说一句,有时我发现我需要解析不应该接受前导空格、符号等的数字。在这种情况下,滚动你自己的 for 循环非常容易,例如,

for (x=0; (unsigned)*s-'0'<10; s++) 
    x=10*x+(*s-'0');

或者你可以使用(为了健壮性):

if (isdigit(*s))
    x=strtol(s, &s, 10);
else /* error */ 

【讨论】:

  • strtol 中的 errno 是 strtol(3) 手册页中所述的实现特定功能。要正确验证,您应该通过 endptr。如果 **endptr 在strtol 之后为 '\0',则字符串被作为一个整体解析并且是有效的(或者它的长度为零)。
  • @Zouppen:不知道你从哪里得到的信息,但这是错误的。 “strtol、strtoll、strtoul 和 strtoull 函数返回转换后的值(如果有)。如果无法执行转换,则返回零。如果正确的值超出可表示值的范围,LONG_MIN,LONG_MAX,LLONG_MIN,LLONG_MAX,返回 ULONG_MAX 或 ULLONG_MAX(根据返回类型和值的符号,如果有),宏 ERANGE 的值存储在 errno 中。” (C99 7.20.1.4 第 8 段)
  • 但是你是对的,你需要检查其他条件。只有溢出是“错误”。任何转换失败都应该通过endptr 检测到,如果你坚持要消耗整个字符串,你也应该检查一下。
  • 你说得对。只有 EINVAL 行为在某种程度上是特定于实现的。
【解决方案2】:

*scanf() 系列函数返回转换后的值的数量。因此,您应该检查以确保 sscanf() 在您的情况下返回 1。 “输入失败”返回EOF,这意味着ssacnf() 永远不会返回EOF

对于sscanf(),函数必须解析格式字符串,然后解码一个整数。 atoi() 没有这种开销。两者都存在超出范围的值会导致未定义行为的问题。

您应该使用strtol()strtoul() 函数,它们提供了更好的错误检测和检查。他们还会让您知道整个字符串是否已被使用。

如果你想要int,你总是可以使用strtol(),然后检查返回值是否介于INT_MININT_MAX之间。

【讨论】:

  • 作为strtol 等的额外奖励,如果您将base 设置为0,您可以自动选择八进制、十进制或十六进制输入的转换。跨度>
  • 使用基数 0 的一个潜在问题是,以 0 开头的字符串将被解释为基数 8(八进制)。知识渊博的用户会出现这种行为,但太多人不知道八进制,并且惊讶地发现012 变为 10 和 019 变为 1,因为转换由于非八进制数字而停止9.
【解决方案3】:

致@R.. 我认为在strtol 调用中检查errno 的错误检测是不够的。

long strtol (const char *String, char **EndPointer, int Base)

您还需要检查 EndPointer 是否有错误。

【讨论】:

    【解决方案4】:

    为了简洁,结合 R.. 和 PickBoy 的答案

    long strtol (const char *String, char **EndPointer, int Base)
    
    // examples
    strtol(s, NULL, 10);
    strtol(s, &s, 10);
    

    【讨论】:

      【解决方案5】:

      当不关心无效字符串输入或范围问题时,使用最简单的:atoi()

      否则,具有最佳错误/范围检测的方法既不是atoi(),也不是sscanf()This good answer all ready 详细说明了使用 atoi()some 使用 sscanf() 进行错误检查时缺少错误检查。

      strtol() 是将字符串转换为int 的最严格的函数。然而,这只是一个开始。下面是详细的示例,以显示正确的用法以及accepted one 之后此答案的原因。

      // Over-simplified use
      int strtoi(const char *nptr) {
        int i = (int) strtol(nptr, (char **)NULL, 10);
        return i; 
      }
      

      这和atoi()类似,忽略了strtol()的错误检测功能。

      要充分利用strtol(),需要考虑多种功能:

      1. 检测到没有转化:示例:"xyz""""--0"?在这些情况下,endptr 将匹配 nptr

        char *endptr;
        int i = (int)strtol(nptr, &endptr, 10);
        if (nptr == endptr) return FAIL_NO_CONVERT;
        
      2. 应该转换整个字符串还是只转换前导部分:"123xyz" 可以吗?

        char *endptr;
        int i = (int)strtol(nptr, &endptr, 10);
        if (*endptr != '\0') return FAIL_EXTRA_JUNK;
        
      3. 检测值是否太大,结果不能像"999999999999999999999999999999"那样表示为long

        errno = 0;
        long L = strtol(nptr, &endptr, 10);
        if (errno == ERANGE) return FAIL_OVERFLOW;
        
      4. 检测值是否超出int 的范围,而不是long。如果intlong 的范围相同,则不需要进行此测试。

        long L = strtol(nptr, &endptr, 10);
        if (L < INT_MIN || L > INT_MAX) return FAIL_INT_OVERFLOW;
        
      5. 某些实现超出了 C 标准,并出于其他原因设置 errno,例如 errno to EINVAL in case no conversion was performedEINVAL The value of the Base parameter is not valid.。测试这些errno 值的最佳时间取决于实现。

      把这一切放在一起:(根据您的需要调整)

      #include <errno.h>
      #include <stdlib.h>
      
      int strtoi(const char *nptr, int *error_code) {
        char *endptr;
        errno = 0;
        long i = strtol(nptr, &endptr, 10);
      
        #if LONG_MIN < INT_MIN || LONG_MAX > INT_MAX
        if (errno == ERANGE || i > INT_MAX || i < INT_MIN) {
          errno = ERANGE;
          i = i > 0 : INT_MAX : INT_MIN;
          *error_code = FAIL_INT_OVERFLOW;
        }
        #else
        if (errno == ERANGE) {
          *error_code = FAIL_OVERFLOW;
        }
        #endif
      
        else if (endptr == nptr) {
          *error_code = FAIL_NO_CONVERT;
        } else if (*endptr != '\0') {
          *error_code = FAIL_EXTRA_JUNK;
        } else if (errno) {
          *error_code = FAIL_IMPLEMENTATION_REASON;
        }
        return (int) i;
      }
      

      注意:所有提到的函数都允许前导空格、可选的前导 sign 字符,并受 locale 更改的影响。更严格的转换需要额外的代码。


      注意:非 OP 标题更改偏重。此答案更适用于原始标题“将字符串转换为整数 sscanf 或 atoi”

      【讨论】:

        【解决方案6】:

        如果用户输入 34abc 并将它们传递给 atoi,它将返回 34。 如果要验证输入的值,则必须迭代地对输入的字符串使用 isdigit

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-09-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-04-09
          • 1970-01-01
          • 2022-01-21
          相关资源
          最近更新 更多