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


260

我试图找出是否存在将字符串转换为C中的整数的另一种方法。

我定期在代码中设置以下内容。

char s[] = "45";

int num = atoi(s);

那么,有没有更好的方法或另一种方法?


21
您的标签和标题表明您想要使用C解决方案,但您的问题却是C或C ++。你想要哪一个?
在计算机上2011年

1
@Yann,抱歉让您感到困惑。我更喜欢C。
user618677 2011年

1
它可以工作,但不是推荐的方法,因为没有办法处理错误。除非您可以100%信任输入,否则切勿在生产代码中使用此代码。
Uwe Geuder,2015年

1
定义“更好”,并清楚说明为什么需要另一种方式。
罗恩侯爵

3
@EJP只是为了提高自己。
user618677

Answers:


185

strtol更好的IMO。我也很喜欢strtonum,所以如果有的话就使用它(但要记住它不是便携式的):

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

编辑

你也可能在有意strtoumaxstrtoimax这是在C99的标准功能。例如,您可以说:

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)

除了错误处理可能有所不同。如果无法表示该值,则行为是undefined


我需要包括strtonum什么?我不断收到隐式声明警告
jsj 2013年

@ trideceth12在可用的系统上,应在中声明#<stdlib.h>。但是,您可以使用标准strtoumax替代方法。
cnicutar

4
这个答案似乎并不比提问者的第一个代码短。
Azurespot 2014年

11
@NoniA。简洁永远是好的,但不能以正确为代价。
cnicutar 2014年

6
没有太多不安全的错误。如果输入有效,atoi()将起作用。但是,如果您执行atoi(“ cat”),该怎么办?如果值不能表示为long,则strtol()具有定义的行为,而atoi()则不能。
Daniel B.

27

基于C89 strtol的强大解决方案

带有:

  • 没有不确定的行为(就像atoi家人一样)
  • strtol(例如,没有前导空格或尾随垃圾字符)更严格的整数定义
  • 错误案例的分类(例如,向用户提供有用的错误消息)
  • 一个“测试者”
#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 : //stackoverflow.com/a/6154614/895245


3
不错健壮str2int()。Pedantic:使用isspace((unsigned char) s[0])
chux-恢复莫妮卡

@chux谢谢!您能否解释一下(unsigned char)演员阵容为何有所作为的原因?
西罗桑蒂利郝海东冠状病六四事件法轮功

IAR C编译器会警告,l > INT_MAX并且l < INT_MIN是无意义的整数比较,因为任何一个结果始终为false。如果将它们更改为l >= INT_MAXl <= INT_MIN清除警告,会发生什么情况?在ARM C上,longintARM C和C ++
ecle

@ecle更改代码以l >= INT_MAX导致不正确的功能:示例返回STR2INT_OVERFLOWinput "32767"和16-bit int。使用条件编译。 实例
chux-恢复莫妮卡

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;}允许调用代码使用errnoint超出范围。相同if (l < INT_MIN...
chux-恢复莫妮卡

24

不要使用ato...组中的功能。这些都是坏的,几乎没有用。一个比较好的解决方案是使用sscanf,尽管它也不是完美的。

要将字符串转换为整数,strto...应使用group 函数。在您的特定情况下,它将strtol起作用。


7
sscanf如果尝试转换其类型范围之外的数字(例如),则实际上具有未定义的行为sscanf("999999999999999999999", "%d", &n)
基思·汤普森

1
@基思·汤普森:这正是我的意思。atoi没有提供有意义的成功/失败反馈,并且在溢出时具有未定义的行为。sscanf提供各种成功/失败反馈(返回值,这使它“适度更好”),但是在溢出时仍具有未定义的行为。唯一strtol可行的解决方案。
2011年

1
同意;我只想强调潜在的致命问题sscanf。(尽管我承认我有时会使用atoi,通常用于那些我希望在删除源之前不会存活超过10分钟的程序。)
Keith Thompson

5

您可以编写一些有趣的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行中旧=)


非常感谢..但是您能告诉我下面的代码如何工作吗?code((** str)-'0')code
user618677 2011年

字符具有ascii值。如果您使用的不是Linux,请输入:在外壳程序中输入man ascii,否则请输入:table-ascii.com。您将看到一个整数的字符“ 0” = 68(我认为)。因此,要获得数字“ 9”(它是“ 0” + 9),则得到9 =“ 9”-“ 0”。你懂了?
jDourlens 2011年

1
1)代码允许"----1" 2)int当结果应为时,具有未定义的溢出行为INT_MIN。考虑my_getnbr("-2147483648")
chux-恢复莫妮卡

感谢您的精确性,它只是为了展示一个小例子。就像它说的那样,是为了娱乐和学习。您应该明确地将standart lib用于此类任务。更快,更安全!
jDourlens

2

只是想分享一个未签名的解决方案。

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 *
罗兰·伊利格

2
另外,那48是什么意思?您是否假设这就是'0'代码将在其中运行的值?请不要对世界施加如此广泛的假设!
Toby Speight

@TobySpeight是,我假设48在ascii表中代表“ 0”。
雅各布

3
并非所有世界都是ASCII- '0'像应该那样使用。
Toby Speight

建议改用strtoul函数。
Rapidclock

1
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;
}

-1

您随时可以自己滚!

#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;
}

这将做您想要的而不会混乱。


2
但为什么?这不检查溢出,而只是忽略垃圾值。没有理由不使用strto...函数系列。它们是便携式的并且明显更好。
乍得

1
奇怪的是0x2d, 0x30代替使用'-', '0'。不允许'+'签名。为什么要(int)投身(int)strlen(snum)?UB如果输入为""。UB当结果INT_MIN由于int溢出accum += (snum[strIdx] - 0x30) * pow(10, idx);
chux –恢复莫妮卡

@chux-此代码为演示代码。您可以轻松地解决您所说的潜在问题。
ButchDean

2
@ButchDean那些不了解所有细节的人将使用您所描述的“演示代码”。现在,只有否定分数和对此答案的评论可以保护他们。我认为,“演示代码”必须具有更高的质量。
罗兰·伊利格

@RolandIllig除了严格要求之外,实际提出您自己的解决方案对其他人没有帮助吗?
ButchDean

-1

此功能将为您提供帮助

int strtoint_n(char* str, int n)
{
    int sign = 1;
    int place = 1;
    int ret = 0;

    int i;
    for (i = n-1; i >= 0; i--, place *= 10)
    {
        int c = str[i];
        switch (c)
        {
            case '-':
                if (i == 0) sign = -1;
                else return -1;
                break;
            default:
                if (c >= '0' && c <= '9')   ret += (c - '0') * place;
                else return -1;
        }
    }

    return sign * ret;
}

int strtoint(char* str)
{
    char* temp = str;
    int n = 0;
    while (*temp != '\0')
    {
        n++;
        temp++;
    }
    return strtoint_n(str, n);
}

参考:http : //amscata.blogspot.com/2013/09/strnumstr-version-2.html


1
为什么这样做呢?atoi和朋友之间最大的问题之一是,如果发生溢出,那就是未定义的行为。您的函数不会对此进行检查。strtol和朋友做。
2015年

1
对。由于C不是Python,所以我希望使用C语言的人意识到这些溢出错误。一切都有自己的局限性。
Amith Chinthaka

-1

好的,我遇到了同样的问题,我想出了这个解决方案,它对我来说是最好的,我确实尝试过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;
}

-1
//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);vs. nInt = nInt*10 + snum[index] - '0'; if(!nInt)不需要。
chux-恢复莫妮卡

-3

在C ++中,可以使用这样的函数:

template <typename T>
T to(const std::string & s)
{
    std::istringstream stm(s);
    T result;
    stm >> result;

    if(stm.tellg() != s.size())
        throw error;

    return result;
}

这可以帮助您将任何字符串转换为任何类型,例如float,int,double ...


1
关于C ++已经有一个类似的问题,其中解释了这种方法的问题。
Ben Voigt

-6

是的,您可以直接存储整数:

int num = 45;

如果您必须解析字符串,atoi否则strol将赢得“最短代码量”竞赛。


如果您想安全地进行操作,strtol()实际上需要大量的代码。它可以返回LONG_MINLONG_MAX 要么如果这是实际的转换值,或者如果有一个溢或溢出,它可以返回0,如果是这样的实际值,或者如果没有数转换。您需要errno = 0在通话前进行设置,然后检查endptr
基思·汤普森

解析的解决方案不是可行的解决方案。
BananaAcid
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.