Bagaimana cara mengubah string menjadi integer di C?

260

Saya mencoba mencari tahu apakah ada cara alternatif untuk mengubah string menjadi integer di C.

Saya secara teratur pola berikut ini dalam kode saya.

char s[] = "45";

int num = atoi(s);

Jadi, apakah ada cara yang lebih baik atau cara lain?

pengguna618677
sumber
21
Tag dan judul Anda mengatakan Anda menginginkan solusi dalam C, tetapi pertanyaan Anda mengatakan C atau C ++. Yang mana yang kamu mau?
Dalam silico
1
@Yann, Maaf atas kebingungan itu. Saya lebih suka C.
user618677
1
Ini berfungsi, tetapi ini bukan cara yang disarankan, karena tidak ada cara untuk menangani kesalahan. Jangan pernah gunakan ini dalam kode produksi kecuali Anda dapat mempercayai input 100%.
Uwe Geuder
1
Tetapkan 'lebih baik', dan nyatakan dengan jelas mengapa Anda membutuhkan cara lain.
Marquis of Lorne
3
@ EJP Hanya untuk meningkatkan diri.
user618677

Jawaban:

185

Ada strtolyang lebih baik IMO. Juga saya telah menyukai strtonum, jadi gunakan jika Anda memilikinya (tapi ingat itu tidak portabel):

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

EDIT

Anda mungkin juga tertarik strtoumaxdanstrtoimax yang merupakan fungsi standar di C99. Misalnya Anda bisa mengatakan:

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

Bagaimanapun, tinggal jauh dari atoi:

Panggilan atoi (str) harus sama dengan:

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

kecuali bahwa penanganan kesalahan mungkin berbeda. Jika nilai tidak dapat direpresentasikan, perilaku tidak terdefinisi .

cnicutar
sumber
untuk apa saya perlu memasukkan strtonum? Saya terus mendapatkan peringatan deklarasi implisit
jsj
@ trideceth12 Pada sistem yang tersedia, harus dinyatakan dalam #<stdlib.h>. Namun, Anda bisa menggunakan strtoumaxalternatif standar .
cnicutar
4
Jawaban ini sepertinya tidak lebih pendek dari kode pertama si penanya.
Azurespot
11
@NoniA. Keringkasan selalu baik, tetapi tidak dengan mengorbankan kebenaran.
cnicutar
6
Tidak terlalu salah dengan tidak aman. atoi () berfungsi jika input valid. Tetapi bagaimana jika Anda melakukan atoi ("kucing")? strtol () telah mendefinisikan perilaku jika nilai tidak dapat direpresentasikan sebagai panjang, atoi () tidak.
Daniel B.
27

strtolSolusi berbasis C89 yang tangguh

Dengan:

  • tidak ada perilaku yang tidak jelas (seperti yang bisa terjadi dengan atoikeluarga)
  • definisi integer yang lebih ketat daripada strtol(mis. tidak ada spasi putih terkemuka atau karakter trash)
  • klasifikasi kasus kesalahan (mis. untuk memberikan pesan kesalahan yang berguna bagi pengguna)
  • sebuah "tes"
#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 hulu .

Berdasarkan: https://stackoverflow.com/a/6154614/895245

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
3
Bagus kuat str2int(). Pedantic: gunakan isspace((unsigned char) s[0]).
chux
@ chux terima kasih! Bisakah Anda jelaskan lebih banyak mengapa para (unsigned char)pemain bisa membuat perbedaan?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Kompiler IAR C memperingatkan itu l > INT_MAXdan l < INT_MINtidak ada perbandingan integer karena hasil mana pun selalu salah. Apa yang terjadi jika saya mengubahnya l >= INT_MAXdan l <= INT_MINmenghapus peringatan? Pada ARM C, panjang dan int adalah 32-bit bertanda tipe data dasar dalam ARM C dan C ++
ecle
@ hapus perubahan kode untuk l >= INT_MAXmenimbulkan fungsi yang salah: Contoh kembali STR2INT_OVERFLOWdengan input "32767"dan 16-bit int. Gunakan kompilasi bersyarat. Contoh .
chux - Reinstate Monica
if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) return STR2INT_OVERFLOW;akan lebih baik if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) { errno = ERANGE; return STR2INT_OVERFLOW;}untuk memungkinkan kode panggilan untuk digunakan errnodi intluar jangkauan. Sama untuk if (l < INT_MIN....
chux - Reinstate Monica
24

Jangan gunakan fungsi dari ato...grup. Ini rusak dan hampir tidak berguna. Solusi yang cukup baik akan digunakan sscanf, meskipun tidak sempurna juga.

Untuk mengonversi string menjadi integer, fungsi dari strto...grup harus digunakan. Dalam kasus spesifik Anda itu akan strtolberfungsi.

Semut
sumber
7
sscanfsebenarnya memiliki perilaku yang tidak terdefinisi jika mencoba mengonversi angka di luar kisaran jenisnya (misalnya, sscanf("999999999999999999999", "%d", &n)).
Keith Thompson
1
@Keith Thompson: Itulah yang saya maksud. atoitidak memberikan umpan balik keberhasilan / kegagalan yang berarti dan memiliki perilaku yang tidak jelas pada luapan. sscanfmemberikan umpan balik keberhasilan / kegagalan (nilai pengembalian, yang membuatnya "lebih baik"), tetapi masih memiliki perilaku yang tidak terdefinisi pada overflow. Hanya strtolsolusi yang layak.
AnT
1
Sepakat; Saya hanya ingin menekankan masalah yang berpotensi fatal sscanf. (Meskipun saya akui kadang-kadang saya gunakan atoi, biasanya untuk program yang saya tidak harapkan untuk bertahan lebih dari 10 menit sebelum saya menghapus sumbernya.)
Keith Thompson
5

Anda dapat mengkodekan sedikit atoi () untuk bersenang-senang:

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

Anda juga dapat membuatnya rekursif yang dapat berusia 3 baris =)

jourlens
sumber
Terima kasih banyak .. Tapi bisakah Anda memberi tahu saya bagaimana kode di bawah ini bekerja? code((* str) - '0')code
user618677
sebuah karakter memiliki nilai ascii. Jika Anda bukan tipe linux: man ascii di shell atau jika tidak pergi ke: table-ascii.com . Anda akan melihat bahwa karakter '0' = 68 (saya pikir) untuk int. Jadi untuk mendapatkan angka '9' ('0' + 9) maka Anda mendapatkan 9 = '9' - '0'. Kau mengerti?
jDourlens
1
1) Kode memungkinkan "----1" 2) Memiliki perilaku yang tidak terdefinisi dengan intmeluap ketika hasilnya seharusnya INT_MIN. Pertimbangkanmy_getnbr("-2147483648")
chux - Reinstate Monica
Terima kasih atas ketepatannya, itu hanya untuk menunjukkan sedikit contoh. Seperti yang dikatakan untuk bersenang-senang dan belajar. Anda harus menggunakan lib standar untuk tugas semacam ini. Lebih cepat dan lebih aman!
jDourlens
2

Hanya ingin berbagi solusi untuk waktu yang lama tanpa tanda tangan.

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;
}
Yakub
sumber
1
Tidak menangani overflow. Juga, parameternya harus const char *.
Roland Illig
2
Plus, apa 48artinya itu? Apakah Anda menganggap itu adalah nilai dari '0'mana kode akan dijalankan? Tolong jangan menimbulkan asumsi luas seperti itu di dunia!
Toby Speight
@TobySpeight Ya saya menganggap 48 mewakili '0' di tabel ascii.
Yakub
3
Tidak semua dunia adalah ASCII - gunakan saja '0'seperti yang seharusnya.
Toby Speight
disarankan untuk menggunakan fungsi strtoul sebagai gantinya.
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
sumber
-1

Anda selalu dapat memutar sendiri!

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

Ini akan melakukan apa yang Anda inginkan tanpa kekacauan.

ButchDean
sumber
2
Tapi kenapa? Ini tidak memeriksa luapan dan hanya mengabaikan nilai sampah. Tidak ada alasan untuk tidak menggunakan strto...keluarga fungsi. Mereka portabel dan secara signifikan lebih baik.
chad
1
Aneh untuk digunakan, 0x2d, 0x30bukan '-', '0'. Tidak mengizinkan '+'masuk. Mengapa (int)dilemparkan (int)strlen(snum)? UB jika input "". UB ketika hasilnya INT_MINkarena intmelimpah denganaccum += (snum[strIdx] - 0x30) * pow(10, idx);
chux - Reinstate Monica
@ chux - Kode ini adalah kode demonstrasi. Ada perbaikan mudah untuk apa yang Anda gambarkan sebagai masalah potensial.
ButchDean
2
@ButchDean Apa yang Anda gambarkan sebagai "kode demonstrasi" akan digunakan oleh orang lain yang tidak memiliki petunjuk tentang semua detail. Hanya skor negatif dan komentar pada jawaban ini yang melindungi mereka sekarang. Menurut saya, "kode demonstrasi" harus memiliki kualitas yang jauh lebih tinggi.
Roland Illig
@RolandIllig Daripada bersikap kritis, bukankah akan lebih bermanfaat bagi orang lain untuk benar-benar memberikan solusi Anda sendiri?
ButchDean
-1

Fungsi ini akan membantu Anda

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
sumber
1
Mengapa melakukan ini? Salah satu masalah terbesar dengan atoidan teman adalah bahwa jika ada luapan, itu adalah perilaku yang tidak terdefinisi. Fungsi Anda tidak memeriksa ini. strtoldan teman-teman melakukannya.
chad
1
Ya. Karena C bukan Python, saya harap orang-orang yang menggunakan bahasa C menyadari kesalahan jenis ini. Semuanya memiliki batasnya sendiri.
Amith Chinthaka
-1

Ok, saya punya masalah yang sama. Saya datang dengan solusi ini. Ini bekerja untuk saya yang terbaik. Saya memang mencoba atoi () tetapi tidak bekerja dengan baik untuk saya. Jadi di sini adalah solusi saya:

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
sumber
-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
sumber
Kode tidak dikompilasi dalam C. Mengapa nInt = (nInt *= 10) + ((int) snum[index] - 48);vs. nInt = nInt*10 + snum[index] - '0'; if(!nInt)tidak diperlukan.
chux - Reinstate Monica
-3

Di C ++, Anda dapat menggunakan fungsi seperti itu:

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

Ini dapat membantu Anda mengonversi string apa pun ke jenis apa pun seperti float, int, double ...

neodelphi
sumber
1
Sudah ada pertanyaan serupa yang mencakup C ++ , di mana masalah dengan pendekatan ini dijelaskan.
Ben Voigt
-6

Ya, Anda dapat menyimpan integer secara langsung:

int num = 45;

Jika Anda harus menguraikan string, atoiatau strolakan memenangkan kontes "kode terpendek".

Yann Ramin
sumber
Jika Anda ingin melakukannya dengan aman, strtol()sebenarnya membutuhkan jumlah kode yang adil. Hal ini dapat kembali LONG_MINatau LONG_MAX baik jika itu nilai yang dikonversi sebenarnya atau jika ada sebuah underflow atau overflow, dan dapat kembali 0 baik jika itu nilai yang sebenarnya atau jika tidak ada nomor untuk mengkonversi. Anda perlu mengatur errno = 0sebelum panggilan, dan memeriksa endptr.
Keith Thompson
Solusi yang diberikan kepada parse, bukanlah solusi yang layak.
BananaAcid