Bir dize C tamsayı dönüştürmek nasıl?


260

C tamsayı dize dönüştürme alternatif bir yol olup olmadığını bulmaya çalışıyorum.

Kodumda düzenli olarak aşağıdakileri düzenlerim.

char s[] = "45";

int num = atoi(s);

Peki, daha iyi ya da başka bir yol var mı?


21
Etiketleriniz ve başlığınız C'de bir çözüm istediğinizi söylüyor, ancak sorunuz C veya C ++ diyor. Hangisini istersin?
Silico'da

1
@Yann, bu karışıklık için özür dilerim. C'yi tercih edeceğim.
user618677

1
Çalışır, ancak önerilen yol değildir, çünkü hataları ele almanın bir yolu yoktur. Girişe% 100 güvenmediğiniz sürece bunu asla üretim kodunda kullanmayın.
Uwe Geuder

1
'Daha iyi' tanımlayın ve neden başka bir yola ihtiyacınız olduğunu açıkça belirtin .
Lorne Marquis

3
@EJP Sadece kendimi geliştirmek için.
user618677

Yanıtlar:


185

Orada strtoldaha iyi IMO olan. Ayrıca ben bir sevme aldı strtonum, bu yüzden varsa (ama taşınabilir olmadığını unutmayın) kullanın:

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

DÜZENLE

Ayrıca ilginizi çekebilir strtoumaxvestrtoimax bunlar C99'da standart işlevlerdir. Örneğin şöyle diyebilirsiniz:

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

Her neyse, uzak durun atoi:

Çağrı atoi (str) aşağıdakilere eşit olacaktır:

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

hataların ele alınması farklı olabilir. Değer temsil edilemiyorsa, davranış tanımsızdır .


ne için eklemem gerekir strtonum? Örtülü bir bildirim uyarısı alıyorum
JSJ

@ trideceth12 Kullanılabilir olduğu sistemlerde bildirilmesi gerekir #<stdlib.h>. Ancak, standart strtoumaxalternatifi kullanabilirsiniz .
cnicutar

4
Bu cevap, sorucının ilk kodundan daha kısa görünmüyor.
Azurespot

11
@NoniA. Özlülük her zaman iyidir, ancak doğruluk pahasına değildir.
cnicutar

6
Güvensiz olduğu kadar yanlış değil. atoi () giriş geçerliyse çalışır. Ama ya atoi ("kedi") yaparsanız? strtol (), değer uzun olarak temsil edilemezse davranış tanımlamıştır, atoi () göstermez.
Daniel

27

Sağlam C89 strtoltabanlı çözüm

İle:

  • tanımlanmamış davranış yok ( atoiailede olduğu gibi)
  • tamsayıdan daha katı bir tanım strtol(ör. önde gelen boşluk veya sondaki çöp karakteri yok)
  • hata durumunun sınıflandırılması (örn. kullanıcılara yararlı hata mesajları vermek için)
  • bir "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 akış yukarı .

Dayalı: https://stackoverflow.com/a/6154614/895245


3
Güzel sağlam str2int(). Bilgiçlik taslayan: kullanın isspace((unsigned char) s[0]).
chux - Monica'yı yeniden başlatın

@chux teşekkürler! Oyuncunun neden (unsigned char)fark yaratabileceğini biraz daha açıklayabilir misiniz ?
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功

IAR C derleyicisi bunu uyarır l > INT_MAXve l < INT_MINher iki sonuç da her zaman yanlış olduğundan anlamsız tamsayı karşılaştırmasıdır. Uyarıları değiştirir l >= INT_MAXve l <= INT_MINuyarıları temizlersem ne olur ? ARM C'de, uzun ve int 32-bit imzalı ARM C ve C ++ 'da temel veri türleri
ecle

@ecle l >= INT_MAXyanlış işlevsellik için kod değiştirme : STR2INT_OVERFLOWGiriş "32767"ve 16 bit ile dönen örnek int. Koşullu bir derleme kullanın. Örnek .
chux - Monica'yı

if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) return STR2INT_OVERFLOW;olarak daha iyi olurdu if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) { errno = ERANGE; return STR2INT_OVERFLOW;}kullanmak için kod çağıran izin errnoüzerine intdışarı aralık. Aynı if (l < INT_MIN....
chux - Monica'yı geri döndür

24

ato...Gruptaki işlevleri kullanmayın . Bunlar kırık ve neredeyse işe yaramaz. Mükemmel sscanfolmasa da, nispeten daha iyi bir çözüm kullanmak olacaktır .

Dizeyi tamsayıya dönüştürmek için strto...gruptan işlevler kullanılmalıdır. Özel durumunuzda bu strtolişlev olacaktır .


7
sscanfaslında bir sayıyı kendi türünün aralığının dışına dönüştürmeye çalışırsa tanımlanmamış bir davranışı vardır (örneğin, sscanf("999999999999999999999", "%d", &n)).
Keith Thompson

1
@Teith Thompson: Demek istediğim bu. atoianlamlı bir başarı / başarısızlık geri bildirimi sağlamaz ve taşma konusunda tanımlanmamış bir davranışa sahiptir. sscanftürlerin başarı / başarısızlık geri bildirimlerini sağlar (dönüş değeri, "orta derecede daha iyi" yapan şeydir), ancak taşma konusunda hala tanımlanmamış bir davranışa sahiptir. Sadece strtoluygulanabilir bir çözümdür.
AnT

1
Kabul; Sadece potansiyel ölümcül sorunu vurgulamak istedim sscanf. (Her ne kadar itiraf etsem de atoi, genellikle kaynağı silmeden önce 10 dakikadan fazla hayatta kalmayı beklemediğim programlar için kullanıyorum .)
Keith Thompson

5

Eğlenmek için biraz atoi () kodlayabilirsiniz:

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

Ayrıca 3 satırda eski olabilir özyinelemeli yapabilirsiniz =)


Çok teşekkürler .. Ama aşağıdaki kodun nasıl çalıştığını söyleyebilir misiniz? code((* str) - '0')code
user618677

bir karakterin bir ascii değeri vardır. Eğer uner linux tipindeyseniz : man ascii kabukta ya da değilse: table-ascii.com adresine gidin . Bir int için '0' = 68 (sanırım) karakterini göreceksiniz. '9' sayısını elde etmek için ('0' + 9'dur) böylece 9 = '9' - '0' elde edersiniz. Anladın mı?
jDourlens

1
1) Kod izin verir "----1" 2) intSonuç olması gerektiğinde taşma ile tanımlanmamış bir davranışa sahiptir INT_MIN. Düşününmy_getnbr("-2147483648")
chux - Monica'yı

Hassasiyet için teşekkürler, sadece küçük bir örnek göstermek içindi. Dediği gibi eğlence ve öğrenme için. Bu tür görevler için kesinlikle standart lib kullanmalısınız. Daha hızlı ve daha güvenli!
jDourlens

2

Sadece imzasız uzun bir süre için bir çözüm paylaşmak istedim.

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
Taşmayı idare etmez. Ayrıca, parametre olmalıdır const char *.
Roland Illig

2
Artı, bu ne 48anlama geliyor? '0'Kodun çalışacağı yerin değeri olduğunu mu düşünüyorsunuz? Lütfen dünyaya bu kadar geniş varsayımlar getirmeyin!
Toby Speight

@TobySpeight Evet 48 sanırım ascii tablosunda '0'ı temsil ediyor.
Jacob

3
Tüm dünya ASCII değildir - sadece '0'gerektiği gibi kullanın .
Toby Speight

bunun yerine strtoul işlevinin kullanılması önerilir .
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

Sen her zaman kendi rulo!

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

Bu, dağınıklık olmadan istediğinizi yapar.


2
Ama neden? Bu taşmayı kontrol etmez ve sadece çöp değerlerini göz ardı eder. strto...Fonksiyon ailesini kullanmamak için hiçbir sebep yok . Taşınabilir ve çok daha iyi.
Çad

1
0x2d, 0x30Bunun yerine kullanmak garip '-', '0'. İşarete izin vermiyor '+'. Neden (int)girmelisin (int)strlen(snum)? Giriş ise UB "". Sonucudur UB INT_MINnedeniyle inttaşmaaccum += (snum[strIdx] - 0x30) * pow(10, idx);
chux - Eski Monica

@chux - Bu kod tanıtım kodudur. Potansiyel sorunlar olarak tanımladığınız şeylerde kolay düzeltmeler vardır.
ButchDean

2
@ButchDean "Gösteri kodu" olarak tanımladığınız şey, tüm ayrıntılar hakkında hiçbir fikri olmayan başkaları tarafından kullanılacaktır. Sadece olumsuz skor ve bu cevabın yorumları şimdi onları koruyor. Kanımca, "gösteri kodu" çok daha yüksek kalitede olmalıdır.
Roland Illig

@RolandIllig Hepsi kritik olmaktan ziyade, kendi çözümünüzü ortaya koymak başkalarına daha fazla yardımcı olmaz mı?
ButchDean

-1

Bu işlev size yardımcı olacaktır

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


1
Bunu neden yapıyoruz? atoiVe arkadaşları ile en büyük sorunlarından biri taşma varsa, tanımsız davranış olmasıdır. İşleviniz bunu kontrol etmez. strtolve arkadaşlar.
Çad

1
Evet. C Python olmadığından, C dilini kullanan kişilerin bu tür taşma hatalarının farkında olduklarını umuyorum. Her şeyin kendi sınırları vardır.
Amith Chinthaka

-1

Tamam, ben de aynı sorunu yaşadım. Bu çözümü buldum. Benim için en iyisi çalıştı.

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

Kod C'de derlenmez. Neden nInt = (nInt *= 10) + ((int) snum[index] - 48);vs. nInt = nInt*10 + snum[index] - '0'; if(!nInt)gerekli değildir.
chux - Monica'yı yeniden

-3

C ++ 'da böyle bir işlev kullanabilirsiniz:

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

Bu, herhangi bir dizeyi float, int, double gibi herhangi bir türe dönüştürmenize yardımcı olabilir ...


1
Bu yaklaşımla ilgili sorunların açıklandığı C ++ ile ilgili benzer bir soru zaten var .
Ben Voigt

-6

Evet, tamsayıyı doğrudan saklayabilirsiniz:

int num = 45;

Bir dizeyi ayrıştırmanız gerekiyorsa atoiveya strol"en kısa kod miktarı" yarışmasını kazanacaksanız.


Güvenli bir şekilde yapmak istiyorsanız, strtol()aslında makul miktarda kod gerektirir. Dönebilir LONG_MINya LONG_MAX da gerçek dönüştürülmüş değerse veya bir taşma veya taşma varsa ve gerçek değerse veya dönüştürülecek sayı yoksa 0 döndürebilir. errno = 0Aramadan önce ayarlamanız ve öğesini kontrol etmeniz gerekir endptr.
Keith Thompson

Ayrıştırmaya verilen çözeltiler, uygulanabilir çözümler değildir.
BananaAcid
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.