如何在C语言中将字符串转换为整数?

336

我想知道在C语言中是否有将字符串转换为整数的替代方法。

我经常在我的代码中使用以下模式。

char s[] = "45";

int num = atoi(s);

那么,是否有更好或是另一种方法呢?


1
http://www.programmingsimplified.com/c/source-code/c-program-convert-string-to-integer-without-using-atoi-function - Kyle Bridenstine
3
虽然这个方法可以工作,但并不是推荐的方式,因为它没有处理错误的方法。除非您可以百分之百地信任输入,否则永远不要在生产代码中使用它。 - Uwe Geuder
13个回答

240

我认为更好的选择是使用strtol。此外,我还喜欢strtonum,如果你有它,可以使用它(但请记住它不可移植):

long long
     strtonum(const char *nptr, long long minval, long long maxval,
     const char **errstr);

你可能还会对C99标准中的 strtoumaxstrtoimax 函数感兴趣。例如,你可以这样说:

uintmax_t num = strtoumax(s, NULL, 10);
if (num == UINTMAX_MAX && errno == ERANGE)
    /* Could not convert. */

无论如何,远离atoi函数:

调用atoi(str)相当于:

(int) strtol(str, (char **)NULL, 10)

除此之外,错误处理可能会有所不同。 如果无法表示该值,则行为未定义


@trideceth12 在支持的系统上,应该在 #<stdlib.h> 中声明。但是,您可以使用标准的 strtoumax 替代方案。 - cnicutar
4
这个答案似乎并不比提问者的第一段代码更短。 - Azurespot
18
简洁明了固然重要,但正确性也同样重要,两者不可相妨。 - cnicutar
8
不是说错了,而是不安全。如果输入有效,atoi()函数可以正常工作。但是如果你使用atoi("cat")会怎样呢?strtol()函数对于无法表示成长整型的值有着定义好的行为,而atoi()则没有。 - Daniel B.
@cnicutar,为什么要将 strtoumax 的结果与 UINTMAX_MAX 进行比较?当无法转换时 errno 总是设置为 != 0,因此只需检查 errno 就可以告诉你它无法转换。 - abetancort
显示剩余3条评论

42

基于C89的鲁棒strtol解决方案

该方案具有以下特点:

  • 无未定义行为(与atoi系列相比)
  • 对整数有更严格的定义,不允许前导空白字符或尾随垃圾字符
  • 错误情况进行分类处理(例如向用户提供有用的错误信息)
  • 配备“测试套件”
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>

typedef enum {
    STR2INT_SUCCESS,
    STR2INT_OVERFLOW,
    STR2INT_UNDERFLOW,
    STR2INT_INCONVERTIBLE
} str2int_errno;

/* Convert string s to int out.
 *
 * @param[out] out The converted int. Cannot be NULL.
 *
 * @param[in] s Input string to be converted.
 *
 *     The format is the same as strtol,
 *     except that the following are inconvertible:
 *
 *     - empty string
 *     - leading whitespace
 *     - any trailing characters that are not part of the number
 *
 *     Cannot be NULL.
 *
 * @param[in] base Base to interpret string in. Same range as strtol (2 to 36).
 *
 * @return Indicates if the operation succeeded, or why it failed.
 */
str2int_errno str2int(int *out, char *s, int base) {
    char *end;
    if (s[0] == '\0' || isspace(s[0]))
        return STR2INT_INCONVERTIBLE;
    errno = 0;
    long l = strtol(s, &end, base);
    /* Both checks are needed because INT_MAX == LONG_MAX is possible. */
    if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX))
        return STR2INT_OVERFLOW;
    if (l < INT_MIN || (errno == ERANGE && l == LONG_MIN))
        return STR2INT_UNDERFLOW;
    if (*end != '\0')
        return STR2INT_INCONVERTIBLE;
    *out = l;
    return STR2INT_SUCCESS;
}

int main(void) {
    int i;
    /* Lazy to calculate this size properly. */
    char s[256];

    /* Simple case. */
    assert(str2int(&i, "11", 10) == STR2INT_SUCCESS);
    assert(i == 11);

    /* Negative number . */
    assert(str2int(&i, "-11", 10) == STR2INT_SUCCESS);
    assert(i == -11);

    /* Different base. */
    assert(str2int(&i, "11", 16) == STR2INT_SUCCESS);
    assert(i == 17);

    /* 0 */
    assert(str2int(&i, "0", 10) == STR2INT_SUCCESS);
    assert(i == 0);

    /* INT_MAX. */
    sprintf(s, "%d", INT_MAX);
    assert(str2int(&i, s, 10) == STR2INT_SUCCESS);
    assert(i == INT_MAX);

    /* INT_MIN. */
    sprintf(s, "%d", INT_MIN);
    assert(str2int(&i, s, 10) == STR2INT_SUCCESS);
    assert(i == INT_MIN);

    /* Leading and trailing space. */
    assert(str2int(&i, " 1", 10) == STR2INT_INCONVERTIBLE);
    assert(str2int(&i, "1 ", 10) == STR2INT_INCONVERTIBLE);

    /* Trash characters. */
    assert(str2int(&i, "a10", 10) == STR2INT_INCONVERTIBLE);
    assert(str2int(&i, "10a", 10) == STR2INT_INCONVERTIBLE);

    /* int overflow.
     *
     * `if` needed to avoid undefined behaviour
     * on `INT_MAX + 1` if INT_MAX == LONG_MAX.
     */
    if (INT_MAX < LONG_MAX) {
        sprintf(s, "%ld", (long int)INT_MAX + 1L);
        assert(str2int(&i, s, 10) == STR2INT_OVERFLOW);
    }

    /* int underflow */
    if (LONG_MIN < INT_MIN) {
        sprintf(s, "%ld", (long int)INT_MIN - 1L);
        assert(str2int(&i, s, 10) == STR2INT_UNDERFLOW);
    }

    /* long overflow */
    sprintf(s, "%ld0", LONG_MAX);
    assert(str2int(&i, s, 10) == STR2INT_OVERFLOW);

    /* long underflow */
    sprintf(s, "%ld0", LONG_MIN);
    assert(str2int(&i, s, 10) == STR2INT_UNDERFLOW);

    return EXIT_SUCCESS;
}

GitHub 上游

基于:https://dev59.com/yXVC5IYBdhLWcg3wvT_g#6154614


3
不错的强健的 str2int()。苛刻:使用 isspace((unsigned char) s[0]) - chux - Reinstate Monica
@chux 谢谢!您能详细解释一下为什么(unsigned char)强制转换会有所不同吗? - Ciro Santilli OurBigBook.com
IAR C编译器警告l > INT_MAXl < INT_MIN是无意义的整数比较,因为任何一个结果都是永远为假。如果我将它们改为l >= INT_MAXl <= INT_MIN以消除警告,会发生什么?在ARM C中,longint是32位有符号ARM C和C++中的基本数据类型 - ecle
如果将 if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) return STR2INT_OVERFLOW; 改为 if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) { errno = ERANGE; return STR2INT_OVERFLOW;},则可以让调用代码在 int 超出范围时使用 errno。对于 if (l < INT_MIN... 同理。 - chux - Reinstate Monica
我发现在错误返回时不设置*out比像strtol()那样设置它要少用。然而,这是一个设计批评,而不是这个实现的问题。 - chux - Reinstate Monica
显示剩余2条评论

32
不要使用 ato... 组中的函数。这些函数已经损坏并且几乎无用。一个更好的解决方案是使用 sscanf,但它也不完美。
要将字符串转换为整数,请使用 strto... 组中的函数。在您的特定情况下,应该使用 strtol 函数。

8
如果 sscanf 试图将一个超出其类型范围的数字进行转换(例如 sscanf("999999999999999999999", "%d", &n)),则实际上它具有未定义的行为。 - Keith Thompson
1
@Keith Thompson:这正是我的意思。atoi没有提供有意义的成功/失败反馈,并且在溢出时具有未定义的行为。sscanf提供了某种程度的成功/失败反馈(返回值使其“适度更好”),但仍然在溢出时具有未定义的行为。只有strtol才是可行的解决方案。 - AnT stands with Russia
1
同意;我只是想强调sscanf可能存在的致命问题。(虽然我承认有时会使用atoi,通常是为了那些我不指望源代码存活超过10分钟的程序。) - Keith Thompson

10
你可以自己编写atoi()函数来练习:
int my_getnbr(char *str)
{
  int result;
  int puiss;

  result = 0;
  puiss = 1;
  while (('-' == (*str)) || ((*str) == '+'))
  {
      if (*str == '-')
        puiss = puiss * -1;
      str++;
  }
  while ((*str >= '0') && (*str <= '9'))
  {
      result = (result * 10) + ((*str) - '0');
      str++;
  }
  return (result * puiss);
}

你也可以使它递归,这可以将它折叠为3行。

1
非常感谢..但是你能告诉我下面的代码如何工作吗? code ((*str) - '0') code - user618677
1
一个字符有一个 ASCII 值。如果你使用的是 Linux 操作系统,请在 shell 中键入“man ascii”;否则请前往 http://www.table-ascii.com/。你会看到字符 '0' 的 int 值为 68(我想是这样)。所以要得到数字 '9'(它是 '0' + 9),你可以这样得到:9 = '9' - '0'。明白了吗? - GrandMarquis
2
  1. 该代码允许 "----1"
  2. 当结果应为 INT_MIN 时,使用 int 溢出会导致未定义的行为。考虑使用 my_getnbr("-2147483648")
- chux - Reinstate Monica
1
感谢您的精确解释,这只是为了展示一个小例子。正如所说,这是为了娱乐和学习。您应该在这种任务中使用标准库。更快,更安全! - GrandMarquis

6
int atoi(const char* str){
    int num = 0;
    int i = 0;
    bool isNegetive = false;
    if(str[i] == '-'){
        isNegetive = true;
        i++;
    }
    while (str[i] && (str[i] >= '0' && str[i] <= '9')){
        num = num * 10 + (str[i] - '0');
        i++;
    }
    if(isNegetive) num = -1 * num;
    return num;
}

5

如前所述,在任何C程序中都不应使用atoi函数族,因为它们没有任何错误处理。

strtol函数族是完全等效的,但具有扩展功能:它具有错误处理,还支持十六进制或二进制等其他进制。因此,正确的答案是:使用strtol函数族。

如果你出于某些原因坚持手动编写此函数,应尝试在除可选符号和数字之外存在其他符号的情况下执行类似strtol的操作。例如,我们经常需要转换作为字符串一部分的数字。

带有错误处理支持的简单版本可能看起来像下面的示例。此代码仅适用于十进制基数10的数字,但在指向第一个无效字符(如果有)的可选指针设置方面与strtol表现相似。还请注意,此代码不处理溢出。

#include <ctype.h>

long my_strtol (char* restrict src, char** endptr)
{
  long result=0;
  long sign=1;

  if(endptr != NULL) 
  {
    /* if input is ok and endptr is provided, 
       it will point at the beginning of the string */
    *endptr = src;
  }

  if(*src=='-')
  {
    sign = -1;
    src++;
  }

  for(; *src!='\0'; src++)
  {
    if(!isdigit(*src)) // error handling
    {
      if(endptr != NULL)
      {
        *endptr = src;
      }
      break;
    }
    result = result*10 + *src - '0';
  }

  return result * sign;
}

为了处理溢出,可以例如添加计算字符数的代码并检查它们是否超过10个。假设使用32位long类型,最大值为2147483647,即10个数字。

虽然来晚了,但是这里发布的糟糕代码太多了。如果你手动展开它,请至少不要比 atoi 更糟糕。 - Lundin

3

我想分享一下关于 unsigned long 的解决方案。

unsigned long ToUInt(char* str)
{
    unsigned long mult = 1;
    unsigned long re = 0;
    int len = strlen(str);
    for(int i = len -1 ; i >= 0 ; i--)
    {
        re = re + ((int)str[i] -48)*mult;
        mult = mult*10;
    }
    return re;
}

1
不处理溢出。并且参数应该是 const char * - Roland Illig
3
另外,“48”是什么意思?你是在假设代码运行时“0”的值为48吗?请不要对全世界做出如此宽泛的假设! - Toby Speight
@TobySpeight 是的,我认为48在ASCII表中代表'0'。 - Jacob
3
并非全世界都是ASCII码 - 只需像应该做的那样使用 '0' 即可。 - Toby Speight
建议使用strtoul函数。 - rapidclock

0

好的,我也遇到了同样的问题。我想出了这个解决方案,对我来说效果最好。我尝试过使用atoi(),但效果不佳。所以这是我的解决方案:

void splitInput(int arr[], int sizeArr, char num[])
{
    for(int i = 0; i < sizeArr; i++)
        // We are subtracting 48 because the numbers in ASCII starts at 48.
        arr[i] = (int)num[i] - 48;
}

这将转换为数字数组,而不是整数,因此它不能回答问题。此外,在源代码中从不需要键入某些神奇数字48。键入“'0'”即可。 - Lundin

-3

你总是可以自己动手!

#include <stdio.h>
#include <string.h>
#include <math.h>

int my_atoi(const char* snum)
{
    int idx, strIdx = 0, accum = 0, numIsNeg = 0;
    const unsigned int NUMLEN = (int)strlen(snum);

    /* Check if negative number and flag it. */
    if(snum[0] == 0x2d)
        numIsNeg = 1;

    for(idx = NUMLEN - 1; idx >= 0; idx--)
    {
        /* Only process numbers from 0 through 9. */
        if(snum[strIdx] >= 0x30 && snum[strIdx] <= 0x39)
            accum += (snum[strIdx] - 0x30) * pow(10, idx);

        strIdx++;
    }

    /* Check flag to see if originally passed -ve number and convert result if so. */
    if(!numIsNeg)
        return accum;
    else
        return accum * -1;
}

int main()
{
    /* Tests... */
    printf("Returned number is: %d\n", my_atoi("34574"));
    printf("Returned number is: %d\n", my_atoi("-23"));

    return 0;
}

这将实现你想要的功能,而不会混乱。


3
但是为什么?这并未检查溢出情况,只是忽略垃圾值。没有理由不使用strto...函数家族。它们具有可移植性并且更好。 - chad
1
使用0x2d,0x30而不是'-','0'很奇怪。不允许使用'+'符号。为什么在(int)strlen(snum)中进行(int)转换?如果输入是""则会出现未定义行为。由于int溢出而导致accum += (snum[strIdx] - 0x30) * pow(10, idx);时结果为INT_MIN时也会出现未定义行为。 - chux - Reinstate Monica
2
@ButchDean,您所描述的“演示代码”将被其他不了解所有细节的人使用。现在只有负分和对此答案的评论可以保护他们。在我看来,“演示代码”必须具有更高的质量。 - Roland Illig
@RolandIllig - 不用了,如果你愿意,可以继续给我点踩。我并不完全同意chux的建议,原因如下。使用十六进制代码可以使意图清晰明了,我相信你也遇到过一些编辑器中的'O'(字母)与'0'(数字)引起混淆的情况,这种方式可以避免这种情况的发生。对strlen()返回值进行强制类型转换是为了避免警告,并再次明确意图。最后,什么溢出?我们在这里处理的是一个非常有限的值范围:0-9,'-'。也许请提供一个可能会发生溢出的场景? - ButchDean
我希望用户知道它是 Alpha-to-Int,而不是 Alpha-to-Long-Long!这就是为什么它被称为'my_atoi'而不是'my_atoll',我的意思是这很清楚,对吧?你真的在寻找错误吗? - ButchDean
显示剩余6条评论

-3
//I think this way we could go :
int my_atoi(const char* snum)
{
 int nInt(0);
 int index(0);
 while(snum[index])
 {
    if(!nInt)
        nInt= ( (int) snum[index]) - 48;
    else
    {
        nInt = (nInt *= 10) + ((int) snum[index] - 48);
    }
    index++;
 }
 return(nInt);
}

int main()
{
    printf("Returned number is: %d\n", my_atoi("676987"));
    return 0;
}

代码在C中无法编译。为什么要使用nInt = (nInt *= 10) + ((int) snum[index] - 48);而不是nInt = nInt*10 + snum[index] - '0';if(!nInt)不需要。 - chux - Reinstate Monica
投票删除,因为它是一个 C++ 的答案,而且不相关。 - Lundin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接