Wie konvertiere ich eine Zeichenfolge in eine Ganzzahl in C?

260

Ich versuche herauszufinden, ob es eine alternative Möglichkeit gibt, einen String in eine Ganzzahl in C umzuwandeln.

Ich strukturiere regelmäßig Folgendes in meinem Code.

char s[] = "45";

int num = atoi(s);

Gibt es einen besseren oder einen anderen Weg?

user618677
quelle
21
Ihre Tags und Titel sagen, dass Sie eine Lösung in C wollen, aber Ihre Frage sagt C oder C ++. Welches willst du?
In silico
1
@ Yann, Entschuldigung für diese Verwirrung. Ich werde C bevorzugen.
user618677
1
Es funktioniert, ist aber nicht die empfohlene Methode, da es keine Möglichkeit gibt, mit Fehlern umzugehen. Verwenden Sie dies niemals im Produktionscode, es sei denn, Sie können der Eingabe zu 100% vertrauen.
Uwe Geuder
1
Definieren Sie "besser" und geben Sie klar an, warum Sie einen anderen Weg benötigen.
Marquis von Lorne
3
@EJP Nur um mich zu verbessern.
user618677

Antworten:

185

Es gibt strtolwelche IMO besser ist. Auch ich habe eine Vorliebe dafür strtonum, also benutze es, wenn du es hast (aber denk daran, es ist nicht tragbar):

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

BEARBEITEN

Das könnte Sie auch interessieren strtoumaxundstrtoimax welche Standardfunktionen in C99 sind. Zum Beispiel könnte man sagen:

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

Wie auch immer, halte dich fern von atoi:

Der Aufruf atoi (str) entspricht:

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

mit der Ausnahme, dass die Behandlung von Fehlern unterschiedlich sein kann. Wenn der Wert nicht dargestellt werden kann, ist das Verhalten undefiniert .

cnicutar
quelle
Wofür muss ich einschließen strtonum? Ich
bekomme
@ trideceth12 Auf Systemen, auf denen es verfügbar ist, sollte es in deklariert werden #<stdlib.h>. Sie können jedoch die Standardalternative verwenden strtoumax.
Cnicutar
4
Diese Antwort scheint nicht kürzer zu sein als der erste Code des Fragestellers.
Azurespot
11
@NoniA. Prägnanz ist immer gut, aber nicht auf Kosten der Korrektheit.
Cnicutar
6
Nicht so sehr falsch als unsicher. atoi () funktioniert, wenn die Eingabe gültig ist. Aber was ist, wenn Sie atoi ("Katze") tun? strtol () hat ein definiertes Verhalten, wenn der Wert nicht als long dargestellt werden kann, atoi () nicht.
Daniel B.
27

Robuste C89- strtolbasierte Lösung

Mit:

  • kein undefiniertes Verhalten (wie bei der atoiFamilie)
  • eine strengere Definition von Ganzzahl als strtol(z. B. kein führendes Leerzeichen oder nachfolgende Papierkorbzeichen)
  • Klassifizierung des Fehlerfalls (z. B. um Benutzern nützliche Fehlermeldungen zu geben)
  • eine "Testsuite"
#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 stromaufwärts .

Basierend auf: https://stackoverflow.com/a/6154614/895245

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
3
Schön robust str2int(). Pedantisch: verwenden isspace((unsigned char) s[0]).
chux
@chux danke! Können Sie etwas näher erläutern, warum die (unsigned char)Besetzung einen Unterschied machen könnte?
Ciro Santilli 30 冠状 病 六四 事件 30
Der IAR C-Compiler warnt davor l > INT_MAXund l < INT_MINist ein sinnloser Ganzzahlvergleich, da jedes Ergebnis immer falsch ist. Was passiert, wenn ich sie ändere l >= INT_MAXund l <= INT_MINdie Warnungen lösche? Auf ARM C sind long und int 32-Bit-signierte Basisdatentypen in ARM C und C ++
ecle
@ecle Ändern des Codes, um l >= INT_MAXfalsche Funktionen zu erhalten: Beispiel für die Rückgabe STR2INT_OVERFLOWmit Eingabe "32767"und 16-Bit int. Verwenden Sie eine bedingte Kompilierung. Beispiel .
chux
if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) return STR2INT_OVERFLOW;wäre besser , als if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) { errno = ERANGE; return STR2INT_OVERFLOW;}zu verwenden , damit Code Aufruf errnoauf intOut-of-Bereich. Gleiches gilt für if (l < INT_MIN....
chux
24

Verwenden Sie keine Funktionen aus der ato...Gruppe. Diese sind kaputt und praktisch nutzlos. Eine mäßig bessere Lösung wäre die Verwendung sscanf, obwohl sie auch nicht perfekt ist.

Um eine Zeichenfolge in eine Ganzzahl umzuwandeln, sollten Funktionen aus der strto...Gruppe verwendet werden. In Ihrem speziellen Fall wäre es strtolFunktion.

Ameise
quelle
7
sscanfhat tatsächlich ein undefiniertes Verhalten, wenn versucht wird, eine Zahl außerhalb des Bereichs seines Typs zu konvertieren (z. B. sscanf("999999999999999999999", "%d", &n)).
Keith Thompson
1
@ Keith Thompson: Genau das meine ich. atoibietet kein aussagekräftiges Erfolgs- / Fehler-Feedback und weist ein undefiniertes Verhalten beim Überlauf auf. sscanfBietet eine Art Erfolgs- / Fehler-Feedback (der Rückgabewert, wodurch es "mäßig besser" wird), weist jedoch immer noch ein undefiniertes Verhalten beim Überlauf auf. Nur strtolist eine tragfähige Lösung.
Am
1
Einverstanden; Ich wollte nur das potenziell tödliche Problem mit betonen sscanf. (Obwohl ich gestehe, dass ich manchmal benutze atoi, normalerweise für Programme, von denen ich nicht erwarte, dass sie länger als 10 Minuten überleben, bevor ich die Quelle lösche.)
Keith Thompson
5

Sie können ein wenig atoi () zum Spaß codieren:

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

Sie können es auch rekursiv machen, was in 3 Zeilen alt sein kann =)

jDourlens
quelle
Vielen Dank. Aber können Sie mir sagen, wie der folgende Code funktioniert? code((* str) - '0')code
user618677
Ein Zeichen hat einen ASCII-Wert. Wenn Sie nicht Linux sind, geben Sie: man ascii in der Shell ein oder gehen Sie zu: table-ascii.com . Sie werden sehen, dass das Zeichen '0' = 68 (glaube ich) für ein int. Um also die Zahl '9' zu erhalten (es ist '0' + 9), erhalten Sie 9 = '9' - '0'. Du verstehst es?
JDourlens
1
1) Der Code erlaubt "----1" 2) Hat undefiniertes Verhalten mit intÜberlauf, wenn das Ergebnis sein sollte INT_MIN. Betrachten Siemy_getnbr("-2147483648")
chux
Vielen Dank für die Präzision, es war nur ein kleines Beispiel. Wie es heißt zum Spaß und Lernen. Sie sollten auf jeden Fall standart lib für diese Art von Aufgaben verwenden. Schneller und sicherer!
jDourlens
2

Ich wollte nur eine Lösung für unsigned long teilen.

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
quelle
1
Behandelt keinen Überlauf. Auch der Parameter sollte sein const char *.
Roland Illig
2
Und was 48bedeutet das? Gehen Sie davon aus, dass dies der Wert ist, unter '0'dem der Code ausgeführt wird? Bitte geben Sie der Welt keine so breiten Annahmen!
Toby Speight
@TobySpeight Ja, ich gehe davon aus, dass 48 '0' in der ASCII-Tabelle darstellen.
Jacob
3
Nicht die ganze Welt ist ASCII - verwenden '0'Sie es einfach so, wie Sie es sollten.
Toby Speight
Es wird empfohlen, stattdessen die Funktion strtoul zu verwenden.
Schnelluhr
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
quelle
-1

Sie können immer Ihre eigenen rollen!

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

Dies wird tun, was Sie wollen, ohne Unordnung.

ButchDean
quelle
2
Aber wieso? Dies prüft nicht auf Überlauf und ignoriert einfach Müllwerte. Es gibt keinen Grund, die strto...Funktionsfamilie nicht zu verwenden . Sie sind tragbar und deutlich besser.
Chad
1
Seltsam zu verwenden 0x2d, 0x30statt '-', '0'. Erlaubt kein '+'Zeichen. Warum (int)einwerfen (int)strlen(snum)? UB wenn Eingang ist "". UB, wenn das Ergebnis INT_MINauf einen intÜberlauf mitaccum += (snum[strIdx] - 0x30) * pow(10, idx);
chux - Reinstate Monica zurückzuführen ist
@chux - Dieser Code ist ein Demonstrationscode. Es gibt einfache Lösungen für das, was Sie als potenzielle Probleme beschrieben haben.
ButchDean
2
@ButchDean Was Sie als "Demonstrationscode" beschreiben, wird von anderen verwendet, die keine Ahnung von allen Details haben. Nur die negative Punktzahl und die Kommentare zu dieser Antwort schützen sie jetzt. Meiner Meinung nach muss "Demonstrationscode" eine viel höhere Qualität haben.
Roland Illig
@RolandIllig Wäre es für andere nicht hilfreicher, eine eigene Lösung zu entwickeln, anstatt nur kritisch zu sein?
ButchDean
-1

Diese Funktion wird Ihnen helfen

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
quelle
1
Warum aber? Eines der größten Probleme mit atoiund bei Freunden ist, dass es bei Überlauf undefiniertes Verhalten gibt. Ihre Funktion prüft dies nicht. strtolund Freunde tun.
Chad
1
Jep. Da C nicht Python ist, hoffe ich, dass die Leute, die C-Sprache verwenden, diese Art von Überlauffehlern kennen. Alles hat seine eigenen Grenzen.
Amith Chinthaka
-1

Ok, ich hatte das gleiche Problem. Ich habe diese Lösung gefunden. Sie hat bei mir am besten funktioniert. Ich habe atoi () ausprobiert, aber bei mir nicht gut funktioniert. Also hier ist meine Lösung:

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
quelle
-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
quelle
Code wird in C nicht kompiliert. Warum nInt = (nInt *= 10) + ((int) snum[index] - 48);vs. nInt = nInt*10 + snum[index] - '0'; if(!nInt)nicht benötigt.
chux
-3

In C ++ können Sie eine solche Funktion verwenden:

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

Dies kann Ihnen helfen, eine beliebige Zeichenfolge in einen beliebigen Typ wie float, int, double ... zu konvertieren.

Neodelphi
quelle
1
Es gibt bereits eine ähnliche Frage zu C ++ , in der die Probleme mit diesem Ansatz erläutert werden.
Ben Voigt
-6

Ja, Sie können die Ganzzahl direkt speichern:

int num = 45;

Wenn Sie eine Zeichenfolge analysieren müssen atoioder strolden Wettbewerb "Kürzeste Menge an Code" gewinnen möchten.

Yann Ramin
quelle
Wenn Sie es sicher machen wollen, benötigen Sie strtol()tatsächlich eine ganze Menge Code. Es kann zurückgegeben werden LONG_MINoder LONG_MAX entweder, wenn dies der tatsächlich konvertierte Wert ist oder wenn ein Unter- oder Überlauf vorliegt, und es kann 0 zurückgeben, entweder wenn dies der tatsächliche Wert ist oder wenn keine zu konvertierende Zahl vorhanden war. Sie müssen errno = 0vor dem Anruf einstellen und die überprüfen endptr.
Keith Thompson
Die zu analysierenden Lösungen sind keine praktikablen Lösungen.
BananaAcid