Comment convertir une chaîne en entier en C?

260

J'essaie de savoir s'il existe un autre moyen de convertir une chaîne en entier en C.

Je modèle régulièrement les éléments suivants dans mon code.

char s[] = "45";

int num = atoi(s);

Alors, y a-t-il une meilleure façon ou une autre façon?

user618677
la source
21
Vos balises et votre titre indiquent que vous voulez une solution en C, mais votre question dit C ou C ++. Lequel veut-tu?
In silico
1
@Yann, Désolé pour cette confusion. Je préférerai C.
user618677
1
Cela fonctionne, mais ce n'est pas la méthode recommandée, car il n'y a aucun moyen de gérer les erreurs. N'utilisez jamais cela dans le code de production, sauf si vous pouvez faire confiance à 100% à l'entrée.
Uwe Geuder
1
Définissez «mieux» et expliquez clairement pourquoi vous avez besoin d'une autre méthode.
Marquis de Lorne
3
@EJP Juste pour m'améliorer.
user618677

Réponses:

185

Il y a strtolquel est le meilleur IMO. J'ai également pris goût strtonum, alors utilisez-le si vous l'avez (mais rappelez-vous qu'il n'est pas portable):

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

ÉDITER

Vous pourriez également être intéressé par strtoumaxetstrtoimax qui sont des fonctions standard dans C99. Par exemple, vous pourriez dire:

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

Quoi qu'il en soit, restez à l'écart de atoi:

L'appel atoi (str) doit être équivalent à:

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

sauf que la gestion des erreurs peut différer. Si la valeur ne peut pas être représentée, le comportement n'est pas défini .

cnicutar
la source
que dois-je inclure strtonum? Je reçois toujours un avertissement de déclaration implicite
jsj
@ trideceth12 Sur les systèmes où il est disponible, il doit être déclaré dans #<stdlib.h>. Cependant, vous pouvez utiliser l' strtoumaxalternative standard .
cnicutar
4
Cette réponse ne semble pas plus courte que le premier code de l'interrogateur.
Azurespot
11
@NoniA. La concision est toujours bonne, mais pas au détriment de l'exactitude.
cnicutar
6
Pas tant de mal que de danger. atoi () fonctionne si l'entrée est valide. Mais que faire si vous faites de l'atoi ("chat")? strtol () a un comportement défini si la valeur ne peut pas être représentée comme un long, atoi () ne le fait pas.
Daniel B.
27

strtolSolution robuste basée sur C89

Avec:

  • pas de comportement indéfini (comme cela pourrait être le cas avec la atoifamille)
  • une définition plus stricte de l'entier que strtol(par exemple, aucun espace de tête ni caractère de fin de corbeille)
  • classification du cas d'erreur (par exemple pour donner des messages d'erreur utiles aux utilisateurs)
  • une "suite de tests"
#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 en amont .

Basé sur: https://stackoverflow.com/a/6154614/895245

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
3
Belle robustesse str2int(). Pedantic: utilisation isspace((unsigned char) s[0]).
chux
@chux merci! Pouvez-vous expliquer un peu plus pourquoi le (unsigned char)casting peut faire la différence?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Le compilateur IAR C l'avertit l > INT_MAXet l < INT_MINconstitue une comparaison d'entier inutile car l'un ou l'autre résultat est toujours faux. Que se passe-t-il si je les modifie l >= INT_MAXet l <= INT_MINefface les avertissements? Sur ARM C, long et int sont des types de données de base
ecle
@ecle change le code pour l >= INT_MAXentraîner une fonctionnalité incorrecte: Exemple de retour STR2INT_OVERFLOWavec entrée "32767"et 16 bits int. Utilisez une compilation conditionnelle. Exemple .
chux
if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) return STR2INT_OVERFLOW;serait mieux que if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) { errno = ERANGE; return STR2INT_OVERFLOW;}de permettre à un code appelant à utiliser errnosur inthors de portée. Pareil pour if (l < INT_MIN....
chux
24

N'utilisez pas les fonctions du ato...groupe. Ceux-ci sont cassés et pratiquement inutiles. Une solution modérément meilleure serait d'utiliser sscanf, bien qu'elle ne soit pas parfaite non plus.

Pour convertir une chaîne en entier, les fonctions du strto...groupe doivent être utilisées. Dans votre cas spécifique, ce serait la strtolfonction.

Fourmi
la source
7
sscanfa en fait un comportement indéfini s'il essaie de convertir un nombre en dehors de la plage de son type (par exemple, sscanf("999999999999999999999", "%d", &n)).
Keith Thompson
1
@Keith Thompson: C'est exactement ce que je veux dire. atoine fournit aucun retour significatif de réussite / échec et a un comportement indéfini en cas de débordement. sscanffournit une sorte de rétroaction de réussite / échec (la valeur de retour, ce qui la rend "modérément meilleure"), mais a toujours un comportement indéfini en cas de débordement. Seule strtolest une solution viable.
AnT
1
D'accord; Je voulais juste souligner le problème potentiellement fatal avec sscanf. (Bien que j'avoue que j'utilise parfois atoi, généralement pour des programmes auxquels je ne m'attends pas à survivre plus de 10 minutes avant de supprimer la source.)
Keith Thompson
5

Vous pouvez coder un peu atoi () pour le plaisir:

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

Vous pouvez également le rendre récursif qui peut vieillir en 3 lignes =)

jDourlens
la source
Merci beaucoup .. Mais pourriez-vous me dire comment fonctionne le code ci-dessous? code((* str) - '0')code
user618677
un personnage a une valeur ascii. Si vous n'êtes pas de type linux: man ascii dans le shell ou sinon allez sur: table-ascii.com . Vous verrez que le caractère '0' = 68 (je pense) pour un int. Donc, pour obtenir le nombre de «9» (c'est «0» + 9), vous obtenez 9 = «9» - «0». Vous comprenez?
jDourlens
1
1) Le code autorise "----1" 2) A un comportement indéfini avec intdébordement lorsque le résultat devrait être INT_MIN. Considérezmy_getnbr("-2147483648")
chux - Rétablissez Monica
Merci pour la précision, c'était juste pour montrer un petit exemple. Comme il est dit pour le plaisir et l'apprentissage. Vous devriez certainement utiliser la bibliothèque standard pour ce type de tâches. Plus rapide et plus sûr!
jDourlens
2

Je voulais juste partager une solution pour longtemps non signé.

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
la source
1
Ne gère pas le débordement. En outre, le paramètre devrait être const char *.
Roland Illig
2
De plus, qu'est-ce que cela 48signifie? Supposez-vous que c'est la valeur de l' '0'emplacement d'exécution du code? Veuillez ne pas infliger des hypothèses aussi larges au monde!
Toby Speight
@TobySpeight Oui, je suppose que 48 représentent «0» dans le tableau ascii.
Jacob
3
Tout le monde n'est pas ASCII - utilisez-le '0'comme vous le devriez.
Toby Speight
il est recommandé d'utiliser à la place la fonction 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;
}
Biswajit Karmakar
la source
-1

Vous pouvez toujours rouler le vôtre!

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

Cela fera ce que vous voulez sans encombrement.

ButchDean
la source
2
Mais pourquoi? Cela ne vérifie pas le débordement et ignore simplement les valeurs de déchets. Il n'y a aucune raison de ne pas utiliser la strto...famille de fonctions. Ils sont portables et nettement meilleurs.
tchad
1
Étrange à utiliser 0x2d, 0x30au lieu de '-', '0'. Ne permet pas de '+'signe. Pourquoi (int)participer (int)strlen(snum)? UB si l'entrée est "". UB lorsque le résultat est INT_MINdû à un intdébordement avecaccum += (snum[strIdx] - 0x30) * pow(10, idx);
chux - Rétablir Monica
@chux - Ce code est un code de démonstration. Il existe des solutions faciles à ce que vous avez décrit comme des problèmes potentiels.
ButchDean
2
@ButchDean Ce que vous décrivez comme "code de démonstration" sera utilisé par d'autres qui n'ont aucune idée de tous les détails. Seuls le score négatif et les commentaires sur cette réponse les protègent maintenant. À mon avis, le "code de démonstration" doit avoir une qualité beaucoup plus élevée.
Roland Illig
@RolandIllig Plutôt que d'être critique, ne serait-il pas plus utile pour les autres de mettre en place votre propre solution?
ButchDean
-1

Cette fonction vous aidera

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

Réf: http://amscata.blogspot.com/2013/09/strnumstr-version-2.html

Amith Chinthaka
la source
1
Mais pourquoi faire ça? L'un des plus gros problèmes avec atoiet amis est que s'il y a débordement, c'est un comportement indéfini. Votre fonction ne vérifie pas cela. strtolet les amis le font.
tchad
1
Ouaip. Puisque C n'est pas Python, j'espère que les gens qui utilisent le langage C sont conscients de ce genre d'erreurs de débordement. Tout a ses propres limites.
Amith Chinthaka
-1

Ok, j'ai eu le même problème.J'ai trouvé cette solution.Elle a fonctionné le mieux pour moi.J'ai essayé atoi () mais n'a pas bien fonctionné pour moi.Voici donc ma solution:

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
la source
-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
la source
Le code ne se compile pas en C. Pourquoi nInt = (nInt *= 10) + ((int) snum[index] - 48);vs nInt = nInt*10 + snum[index] - '0'; if(!nInt)non nécessaire.
chux
-3

En C ++, vous pouvez utiliser une telle fonction:

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

Cela peut vous aider à convertir n'importe quelle chaîne en n'importe quel type tel que float, int, double ...

neodelphi
la source
1
Il y a déjà une question similaire concernant C ++ , où les problèmes avec cette approche sont expliqués.
Ben Voigt
-6

Oui, vous pouvez stocker directement l'entier:

int num = 45;

Si vous devez analyser une chaîne, atoiou si vous strolallez gagner le concours "la plus petite quantité de code".

Yann Ramin
la source
Si vous voulez le faire en toute sécurité, strtol()nécessite en fait une bonne quantité de code. Il peut revenir LONG_MINou LONG_MAX non si c'est la valeur convertie réelle ou s'il y a un trop - plein ou soupassement, et il peut retourner 0 soit si c'est la valeur réelle ou s'il n'y avait pas de numéro à convertir. Vous devez régler errno = 0avant l'appel et vérifier endptr.
Keith Thompson
Les solutions données pour analyser, ne sont pas des solutions viables.
BananaAcid