¿Cómo convertir una cadena a entero en C?

260

Estoy tratando de averiguar si hay una forma alternativa de convertir cadenas a enteros en C.

Regularmente modelo lo siguiente en mi código.

char s[] = "45";

int num = atoi(s);

Entonces, ¿hay una mejor manera u otra?

usuario618677
fuente
21
Sus etiquetas y título dicen que quiere una solución en C, pero su pregunta dice C o C ++. ¿Cuál quieres?
En silico
1
@ Yann, Perdón por esa confusión. Prefiero C.
user618677
1
Funciona, pero no es la forma recomendada, porque no hay forma de manejar los errores. Nunca use esto en el código de producción a menos que pueda confiar en la entrada al 100%.
Uwe Geuder
1
Defina "mejor" e indique claramente por qué necesita otra forma.
Marqués de Lorne
3
@EJP Solo para mejorarme.
user618677

Respuestas:

185

Hay strtolcuál es mejor IMO. También me ha gustado mucho strtonum, así que úsalo si lo tienes (pero recuerda que no es portátil):

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

EDITAR

También puede interesarle strtoumaxystrtoimax cuáles son las funciones estándar en C99. Por ejemplo, podrías decir:

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

De todos modos, mantente alejado de atoi:

La llamada atoi (str) será equivalente a:

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

excepto que el manejo de errores puede diferir. Si el valor no se puede representar, el comportamiento es indefinido .

cnicutar
fuente
¿Qué necesito incluir strtonum? Sigo recibiendo una advertencia de declaración implícita
jsj
@ trideceth12 En los sistemas donde está disponible, debe declararse en #<stdlib.h>. Sin embargo, podría usar la strtoumaxalternativa estándar .
cnicutar
44
Esta respuesta no parece más corta que el primer código del interlocutor.
Azurespot
11
@NoniA. La concisión siempre es buena, pero no a expensas de la corrección.
cnicutar
66
No es tan malo como inseguro. atoi () funciona si la entrada es válida. Pero, ¿y si haces atoi ("gato")? strtol () tiene un comportamiento definido si el valor no se puede representar como largo, atoi () no.
Daniel B.
27

Solución robusta strtolbasada en C89

Con:

  • sin comportamiento indefinido (como se podría tener con la atoifamilia)
  • una definición más estricta de entero que strtol(por ejemplo, sin espacios en blanco iniciales ni caracteres de basura finales)
  • clasificación del caso de error (por ejemplo, para dar mensajes de error útiles a los usuarios)
  • un "traje de prueba"
#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 aguas arriba .

Basado en: https://stackoverflow.com/a/6154614/895245

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
3
Agradable robusto str2int(). Pedantic: uso isspace((unsigned char) s[0]).
chux - Restablece a Monica
@chux gracias! ¿Puedes explicar un poco más por qué el (unsigned char)elenco podría marcar la diferencia?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
El compilador IAR C advierte eso l > INT_MAXy l < INT_MINson una comparación de enteros sin sentido ya que cualquiera de los resultados es siempre falso. ¿Qué sucede si los cambio l >= INT_MAXy l <= INT_MINborro las advertencias? En ARM C, long e int son tipos de datos básicos con
ecle
@ecle cambiando el código para incurrir l >= INT_MAXen una funcionalidad incorrecta: Ejemplo que regresa STR2INT_OVERFLOWcon entrada "32767"y 16 bits int. Use una compilación condicional. Ejemplo .
chux
if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) return STR2INT_OVERFLOW;sería mejor como if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) { errno = ERANGE; return STR2INT_OVERFLOW;}para permitir llamar a código para utilizar errnoen intfuera de rango. Lo mismo para if (l < INT_MIN....
chux - Restablecer Monica
24

No uses funciones del ato...grupo. Estos están rotos y prácticamente inútiles. Sería una solución moderadamente mejor sscanf, aunque tampoco es perfecta.

Para convertir una cadena en un entero, se strto...deben usar las funciones del grupo. En su caso específico sería strtolfunción.

Hormiga
fuente
77
sscanfen realidad tiene un comportamiento indefinido si intenta convertir un número fuera del rango de su tipo (por ejemplo, sscanf("999999999999999999999", "%d", &n)).
Keith Thompson
1
@Keith Thompson: Eso es exactamente lo que quiero decir. atoino proporciona comentarios significativos de éxito / fracaso y tiene un comportamiento indefinido en caso de desbordamiento. sscanfproporciona comentarios de éxito / fracaso (el valor de retorno, que es lo que lo hace "moderadamente mejor"), pero aún tiene un comportamiento indefinido en caso de desbordamiento. Solo strtoles una solución viable.
ANT
1
Convenido; Solo quería enfatizar el problema potencialmente fatal con sscanf. (Aunque confieso que a veces uso atoi, generalmente para programas que no espero sobrevivir más de 10 minutos antes de eliminar la fuente.)
Keith Thompson
5

Puedes codificar un poco de atoi () por diversión:

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

También puede hacerlo recursivo, lo que puede envejecer en 3 líneas =)

jDourlens
fuente
Muchas gracias ... Pero, ¿podría decirme cómo funciona el siguiente código? code((* str) - '0')code
usuario618677
un personaje tiene un valor ascii. Si eres uner tipo linux: man ascii en el shell o si no, ve a: table-ascii.com . Verá que el carácter '0' = 68 (creo) para un int. Entonces, para obtener el número de '9' (es '0' + 9) para obtener 9 = '9' - '0'. ¿Usted lo consigue?
jDourlens
1
1) El código permite "----1" 2) Tiene un comportamiento indefinido con intdesbordamiento cuando el resultado debería ser INT_MIN. Consideremy_getnbr("-2147483648")
chux
Gracias por la precisión, fue solo por mostrar un pequeño ejemplo. Como se dice por diversión y aprendizaje. Definitivamente debe usar standart lib para este tipo de tareas. ¡Más rápido y más seguro!
jDourlens
2

Solo quería compartir una solución por tiempo sin firmar también.

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;
}
Jacob
fuente
1
No maneja el desbordamiento. Además, el parámetro debería ser const char *.
Roland Illig
2
Además, ¿qué 48significa eso ? ¿Asume que ese es el valor de '0'dónde se ejecutará el código? ¡Por favor, no infundas suposiciones tan amplias en el mundo!
Toby Speight
@TobySpeight Sí, supongo que 48 representan '0' en la tabla ASCII.
Jacob
3
No todo el mundo es ASCII, solo úsalo '0'como deberías.
Toby Speight
se recomienda usar la función strtoul en su lugar.
reloj rápido
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;
}
Biswajit Karmakar
fuente
-1

¡Siempre puedes rodar el tuyo!

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

Esto hará lo que quieras sin desorden.

ButchDean
fuente
2
¿Pero por qué? Esto no comprueba el desbordamiento y simplemente ignora los valores basura. No hay razón para no usar la strto...familia de funciones. Son portátiles y significativamente mejores.
chad
1
Extraño de usar en 0x2d, 0x30lugar de '-', '0'. No permite '+'firmar. ¿Por qué (int)echarlo (int)strlen(snum)? UB si la entrada es "". UB cuando el resultado se INT_MINdeba a intdesbordar conaccum += (snum[strIdx] - 0x30) * pow(10, idx);
chux - Reinstale a Monica
@chux: este código es un código de demostración. Hay soluciones fáciles a lo que describió como problemas potenciales.
ButchDean
2
@ButchDean Lo que usted describe como "código de demostración" será utilizado por otros que no tienen idea de todos los detalles. Solo el puntaje negativo y los comentarios sobre esta respuesta los protegen ahora. En mi opinión, el "código de demostración" debe tener una calidad mucho mayor.
Roland Illig
@RolandIllig En lugar de ser todo crítico, ¿no sería más útil para otros presentar su propia solución?
ButchDean
-1

Esta función te ayudará

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

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

Amith Chinthaka
fuente
1
¿Por qué hacer esto sin embargo? Uno de los mayores problemas atoiy amigos es que si hay un desbordamiento, es un comportamiento indefinido. Su función no verifica esto. strtoly amigos lo hacen.
chad
1
Sip. Como C no es Python, espero que las personas que usan lenguaje C estén al tanto de este tipo de errores de desbordamiento. Todo tiene sus propios límites.
Amith Chinthaka
-1

Ok, tuve el mismo problema. Se me ocurrió esta solución. Funcionó mejor para mí. Intenté con atoi () pero no funcionó bien para mí. Así que aquí está mi solución:

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;
}
Khaled Mohammad
fuente
-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;
}
Aditya Kumar
fuente
El código no se compila en C. Por qué nInt = (nInt *= 10) + ((int) snum[index] - 48);vs. nInt = nInt*10 + snum[index] - '0'; if(!nInt)no es necesario.
chux - Restablece a Monica
-3

En C ++, puede usar una función de este tipo:

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

Esto puede ayudarlo a convertir cualquier cadena a cualquier tipo, como float, int, double ...

neodelphi
fuente
1
Ya hay una pregunta similar que cubre C ++ , donde se explican los problemas con este enfoque.
Ben Voigt
-6

Sí, puede almacenar el número entero directamente:

int num = 45;

Si debe analizar una cadena atoio strolva a ganar el concurso de "menor cantidad de código".

Yann Ramin
fuente
Si desea hacerlo de forma segura, en strtol()realidad requiere una buena cantidad de código. Puede volver LONG_MINo LONG_MAX bien si ese es el valor convertido actual o si hay un desbordamiento o desbordamiento, y puede devolver 0 o bien si ese es el valor real o si no había un número para convertir. Debe configurar errno = 0antes de la llamada y verificar el endptr.
Keith Thompson
Las soluciones dadas para analizar, no son soluciones viables.
BananaAcid