Soru C ++ bir int için bir dize ayrıştırmak nasıl?


Bir dize (char * olarak verilen) bir diziyi ayrıştırmanın C ++ yolu nedir? Sağlam ve net hata işleme bir artıdır (yerine sıfır döndürme).


244
2017-10-11 19:20


Menşei


Aşağıdaki örneklerden bazılarına ne dersiniz? codeproject.com/KB/recipes/Tokenizer.aspx Onlar çok verimli ve biraz zarif
@Beh Tou Cheh, int'yi ayrıştırmanın iyi bir yolu olduğunu düşünüyorsanız, lütfen yanıt olarak gönderin. - Eugene Yokota
C için de aynı: stackoverflow.com/questions/7021725/... - Ciro Santilli 新疆改造中心 六四事件 法轮功


Cevaplar:


Yeni C ++ 11'de bunun için fonksiyonlar var: stoi, stol, stoll, stoul vb.

int myNr = std::stoi(myString);

Dönüşüm hatasına bir istisna atar.

Bu yeni işlevler bile hala aynı sorun Dan tarafından belirtildiği gibi: "11x" dizesini "11" tamsayısına dönüştürürler.

Daha fazla gör: http://en.cppreference.com/w/cpp/string/basic_string/stol


145
2017-07-06 00:48



Ama onlar argümanları kabul ederler, bunlardan bir tanesi, eğer boş değilse, ilk dönüştürülmemiş karaktere ayarlanır. - Zharf
Evet, std :: stoi'nin ikinci parametresini kullanarak geçersiz girişi tespit edebilirsiniz. Yine de kendi dönüşüm fonksiyonunuzu yuvarlamak zorundasınız ... - CC.
Aynen kabul edilen cevap gibi, ama bu standart fonksiyonlarla çok daha temiz olacak, imo - Zharf


Ne yapmamak

İşte ilk tavsiyem: Bunun için stringstream kullanmayın. İlk başta kullanımı kolay görünse de, sağlamlık ve iyi hata işlemeyi istiyorsanız çok fazla iş yapmanız gerektiğini göreceksiniz.

İşte, sezgisel olarak çalışması gerektiği gibi görünen bir yaklaşım:

bool str2int (int &i, char const *s)
{
    std::stringstream ss(s);
    ss >> i;
    if (ss.fail()) {
        // not an integer
        return false;
    }
    return true;
}

Bunun büyük bir sorunu var: str2int(i, "1337h4x0r") mutlulukla dönecek true ve i değeri alacak 1337. Daha fazla karakter kalmamasını sağlayarak bu soruna geçici bir çözüm bulabiliriz. stringstream dönüşümden sonra:

bool str2int (int &i, char const *s)
{
    char              c;
    std::stringstream ss(s);
    ss >> i;
    if (ss.fail() || ss.get(c)) {
        // not an integer
        return false;
    }
    return true;
}

Bir problemi çözdük, ama hala birkaç problem daha var.

Eğer dizideki sayı baz 10 değilse? Akışı doğru moda ayarlayarak diğer tabanları yerleştirmeye çalışabiliriz (ör. ss << std::hex) dönüşümü denemeden önce. Ancak bu, arayanın bilmesi gerektiği anlamına gelir Önsel Sayı ne kadardır - ve arayan kişi bunu nereden biliyor? Arayan numaranın ne olduğunu bilmiyor. Onu bile bilmiyorlar. olduğu bir sayı! Hangi tabanın olduğunu bilmek nasıl beklenebilir? Programlarımıza giren tüm sayıların 10 bazında olması ve onaltılık veya sekizli girişi geçersiz olarak reddetmesi emrini verebiliriz. Ancak bu çok esnek veya sağlam değildir. Bu problem için basit bir çözüm yoktur. Her bir taban için dönüşümü bir kez denemezsiniz, çünkü ondalık dönüşüm her zaman sekizlik sayılar (baştaki sıfır ile) için başarılı olur ve sekizlik dönüşüm bazı ondalık sayılar için başarılı olabilir. Öyleyse şimdi baştaki bir sırayı kontrol etmelisin. Fakat bekle! Onaltılık sayılar da bir sıfır ile başlayabilir (0x ...). İç çekmek.

Yukarıdaki problemlerle başa çıkmada başarılı olsanız bile, daha büyük bir problem daha vardır: eğer arayan kişi, kötü girdi (ör. "123foo") ve aralığın dışında kalan bir sayı arasında ayrım yapmak isterse ne olur? int (ör. 32-bit için "4000000000" int)? İle stringstreamBu ayrımı yapmanın bir yolu yoktur. Yalnızca dönüşümün başarılı olup olmadığını biliyoruz. Başarısız olursa, bilmenin bir yolu yoktur. niye ya başarısız oldu. Gördüğün gibi, stringstream Sağlamlık ve net hata işlemeyi istiyorsanız çok arzu edilen bırakır.

Bu beni ikinci tavsiyemle yönlendiriyor: Boost yok lexical_cast bunun için. Ne olduğunu düşünün lexical_cast dokümantasyon şunları söylemelidir:

Daha yüksek derecede kontrolün olduğu yer   dönüşümler için gerekli   std :: stringstream ve   std :: wstringstream daha fazlasını sunuyor   uygun yol Nerede   akışa dayalı olmayan dönüşümler   gerekli, lexical_cast yanlış   iş için araç ve değil   Bu tür senaryolar için özel kasa.

Ne?? Bunu daha önce görmüştük stringstream zayıf bir kontrol seviyesine sahip, ve yine de diyor stringstream yerine kullanılmalıdır lexical_cast "Daha yüksek bir kontrol seviyesi" ye ihtiyacınız varsa. Ayrıca, çünkü lexical_cast sadece bir sarıcı stringstream, aynı sorunlardan muzdarip stringstream yapar: çoklu sayı üsleri için zayıf destek ve hatalı hata işleme.

En iyi çözüm

Neyse ki, birisi yukarıdaki problemlerin hepsini zaten çözmüştür. C standart kütüphanesi içerir strtol ve bu sorunların hiçbirine sahip olmayan aile.

enum STR2INT_ERROR { SUCCESS, OVERFLOW, UNDERFLOW, INCONVERTIBLE };

STR2INT_ERROR str2int (int &i, char const *s, int base = 0)
{
    char *end;
    long  l;
    errno = 0;
    l = strtol(s, &end, base);
    if ((errno == ERANGE && l == LONG_MAX) || l > INT_MAX) {
        return OVERFLOW;
    }
    if ((errno == ERANGE && l == LONG_MIN) || l < INT_MIN) {
        return UNDERFLOW;
    }
    if (*s == '\0' || *end != '\0') {
        return INCONVERTIBLE;
    }
    i = l;
    return SUCCESS;
}

Tüm hata durumlarını ele alan ve ayrıca 2'den 36'ya kadar herhangi bir sayı tabanını destekleyen bir şey için oldukça basittir. base sıfır (varsayılan) herhangi bir tabandan dönüştürmeyi deneyecektir. Veya arayan üçüncü argümanı sağlayabilir ve dönüşümün sadece belirli bir baz için denenmesi gerektiğini belirleyebilir. Sağlamdır ve tüm hataları minimum çaba ile ele alır.

Tercih etmek için diğer nedenler strtol (ve aile):

  • Çok daha iyi sergiler çalışma zamanı performansı
  • Daha az derleme zamanı yükü getiriyor (diğerleri başlıklardan yaklaşık 20 kat daha fazla SLOC çekiyor)
  • En küçük kod boyutuyla sonuçlanır

Başka bir yöntem kullanmanın kesinlikle iyi bir nedeni yoktur.


190
2018-05-27 15:42



@JamesDunne: POSIX gerektirir strtol iplik güvenli olmak. POSIX ayrıca gerektirir errno iş parçacığı yerel depolama kullanmak için. POSIX olmayan sistemlerde bile, neredeyse tüm uygulamalar errno çok iş parçacıklı sistemlerde iş parçacığı yerel depolama kullanın. En yeni C ++ standardı gerektirir errno POSIX uyumlu olmak. En son C standardı da gerektirir errno iş parçacığı yerel depolama sahip olmak. Kesinlikle POSIX uyumlu olmayan Windows'ta bile, errno iş parçacığı güvenli ve uzantısı tarafından strtol. - Dan Moulding
İcraatınızı gerçekten kullanamamışım :: lexical_cast. Dedikleri gibi, std :: stringstream gerçekten de çok fazla kontrol sağlıyor - siz hata kontrolünden her şeyi kendiniz belirlersiniz. Mevcut belgeler şu şekildedir: "Hassasiyet veya biçimlendirmenin, lexical_cast'in varsayılan davranışı tarafından sunulandan daha sıkı denetime gereksinim duyduğu daha fazla ilgili dönüşümler için, geleneksel std :: stringstream yaklaşımı önerilir." - fhd
Bu C ++ içinde uygun C kodlamasıdır. Standart kütüphane içerir std::stol Bunun için, sabitleri geri döndürmek yerine istisnalar atacaklardır. - fuzzyTew
@fuzzyTew Bu cevabı daha önce yazdım std::stol C ++ diline bile eklendi. Bu, bunun "C ++ içinde kodlama" olduğunu söylemek yanlış olmaz. Bunu söylemek aptalca std::strtol C ++ dilinin bir parçası olduğunda C kodlamasıdır. Cevabım yazıldığında C ++'ya mükemmel bir şekilde uygulandı ve hala yeni olsa bile geçerli std::stol. İstisnalar atayan çağrı işlevleri her programlama durumu için her zaman en iyisi değildir. - Dan Moulding
@fuzzyTew: Disk alanı tükenmek olağanüstü bir durumdur. Bilgisayarda üretilen hatalı biçimlendirilmiş veri dosyaları olağanüstü bir durumdur. Ancak kullanıcı girdisindeki yazım hataları istisna değildir. Normal, istisnai olmayan ayrıştırma hatalarını ele alan bir ayrıştırma yaklaşımına sahip olmak iyidir. - Ben Voigt


Bu atoi'den daha güvenli bir C ()

const char* str = "123";
int i;

if(sscanf(str, "%d", &i)  == EOF )
{
   /* error */
}

C ++ standart kütüphane ile stringstream: (Teşekkürler CMS )

int str2int (const string &str) {
  stringstream ss(str);
  int num;
  if((ss >> num).fail())
  { 
      //ERROR 
  }
  return num;
}

İle artırmak kütüphane: (teşekkürler jk)

#include <boost/lexical_cast.hpp>
#include <string>

try
{
    std::string str = "123";
    int number = boost::lexical_cast< int >( str );
}
catch( const boost::bad_lexical_cast & )
{
    // Error
}

Düzenleme: Stringstream versiyonunu düzeltip hataları ele alır. (CMS'nin ve jk'in orjinal yazıya yaptığı yorum sayesinde)


65
2017-10-11 19:47



lütfen stringstream :: fail () için bir kontrol eklemek için stringstream sürümünüzü güncellendi ("Sağlam ve anlaşılır hata işleme" sorusu tarafından talep edildi) - jk.
Stringstream versiyonunuz şikayet etmeden "10haha" gibi içerikleri kabul edecektir - Johannes Schaub - litb
Eğer lexical_cast gibi aynı işlem istiyorsanız ((ss >> num) .fail () && (ss >> ws) .eof ()) ((ss >> num) .fail ()) olarak değiştirin - Johannes Schaub - litb
Standart kitaplık stringstream yöntemiyle C ++, .fail () denetimi ile bile "12-SomeString" gibi dizeler için çalışmaz. - captonssj
Bunların hepsi oldukça kötü performanslıdır. - Dmitri Nesteruk


Kullanabilirsiniz Boost en lexical_cast, hangi bunu sarar daha genel bir arayüzde. lexical_cast<Target>(Source) atar bad_lexical_cast başarısızlık durumunda.


22
2017-10-11 19:31



Boost lexical_cast son derece yavaş ve acı vericidir.
@Matthieu Güncellemeleri Yükseltme performansı biraz arttı: boost.org/doc/libs/1_49_0/doc/html/boost_lexical_cast/...  (Ayrıca bakınız stackoverflow.com/questions/1250795/... ) - flies


İyi 'eski C yolu hala çalışıyor. Strtol veya strtoul'u tavsiye ederim. Dönüş durumu ile 'endPtr' arasında, iyi bir teşhis çıkışı sağlayabilirsiniz. Aynı zamanda çok sayıda üsleri de ele alır.


20
2017-10-11 19:22



Lütfen C ++ programlama yaparken bu eski C şeylerini kullanmayın. Bunu C ++'da daha iyi / daha kolay / daha temiz / daha modern / daha güvenli yollar var! - jk.
İnsanlar bir problemi çözmek için “daha ​​modern” yollar hakkında endişe duyduklarında komiktir. - Jeff Miller
@ Jason, IMO daha güçlü tip güvenlik ve hata işleme C ile karşılaştırıldığında daha modern bir fikirdir. - Eugene Yokota
Diğer cevaplara baktım ve şu ana kadar hiçbir şey açıkça daha iyi / daha kolay / daha temiz ya da daha güvenli. Poster bir karısı olduğunu söyledi. Bu, alacağınız güvenlik miktarını sınırlar :) - Chris Arguin


C ++ standart kütüphanesinden bir stringstream kullanabilirsiniz:

stringstream ss(str);
int x;
ss >> x;

if(ss) { // <-- error handling
  // use x
} else {
  // not a number
}

Akış durumu başarısız olacak şekilde ayarlanacak   ne zaman bir non-digit ile karşılaşılırsa   bir tamsayı okumaya çalışıyorum.

Görmek Akış tuzakları C ++ 'da hata bulma ve akışların tuzakları için.


15
2017-10-11 19:49



C ++ stringstream yöntemi, "stream state" denetimi ile bile "12-SomeString" gibi dizeler için çalışmaz. - captonssj


Kullanabilirsiniz stringstream en

int str2int (const string &str) {
  stringstream ss(str);
  int num;
  ss >> num;
  return num;
}

9
2017-10-11 19:26



Ancak bu herhangi bir hatayla başa çıkmaz. Akışı arızalar için kontrol etmelisiniz. - jk.
Doğru eğer akışı kontrol etmeniz gerekiyorsa ((ss >> num) .fail ()) {// ERROR} - CMS
C ++ stringstream yöntemi, "akış durumu" denetimi ile bile "12-SomeString" gibi dizeler için çalışmaz - captonssj


Bu üç bağlantıyı özetledim:


7
2017-12-01 20:10





C ++ Dize Toolkit Kitaplığı (StrTk) aşağıdaki çözüme sahiptir:

static const std::size_t digit_table_symbol_count = 256;
static const unsigned char digit_table[digit_table_symbol_count] = {
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xFF - 0x07
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x08 - 0x0F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x10 - 0x17
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x18 - 0x1F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x20 - 0x27
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x28 - 0x2F
   0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // 0x30 - 0x37
   0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x38 - 0x3F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x40 - 0x47
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x48 - 0x4F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x50 - 0x57
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x58 - 0x5F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x60 - 0x67
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x68 - 0x6F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x70 - 0x77
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x78 - 0x7F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x80 - 0x87
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x88 - 0x8F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x90 - 0x97
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x98 - 0x9F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xA0 - 0xA7
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xA8 - 0xAF
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xB0 - 0xB7
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xB8 - 0xBF
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xC0 - 0xC7
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xC8 - 0xCF
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xD0 - 0xD7
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xD8 - 0xDF
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xE0 - 0xE7
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xE8 - 0xEF
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xF0 - 0xF7
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF  // 0xF8 - 0xFF
 };

template<typename InputIterator, typename T>
inline bool string_to_signed_type_converter_impl_itr(InputIterator begin, InputIterator end, T& v)
{
   if (0 == std::distance(begin,end))
      return false;
   v = 0;
   InputIterator it = begin;
   bool negative = false;
   if ('+' == *it)
      ++it;
   else if ('-' == *it)
   {
      ++it;
      negative = true;
   }
   if (end == it)
      return false;
   while(end != it)
   {
      const T digit = static_cast<T>(digit_table[static_cast<unsigned int>(*it++)]);
      if (0xFF == digit)
         return false;
      v = (10 * v) + digit;
   }
   if (negative)
      v *= -1;
   return true;
}

InputIterator, imzasız char *, char * veya std :: string yineleyicileri olabilir ve T'nin imzalı int, int veya long gibi imzalı bir int olması beklenir.


7
2017-08-29 05:59



Bu korkunç..
UYARI Bu uygulama hoş görünüyor, ancak anlatabildiğim kadarıyla taşmaları ele almıyor. - Vinnie Falco
Kod taşma işlemez. v = (10 * v) + digit; gereksiz yere metin girişi ile dize girişi ile taşar INT_MIN. Tablo şüphe götürmez bir değere eşittir. digit >= '0' && digit <= '9' - chux


C ++ 11'iniz varsa, uygun çözümler şu anda C ++ tamsayı dönüşüm işlevleridir. <string>: stoi, stol, stoul, stoll, stoull. Yanlış giriş verildiğinde uygun istisnalar atar ve hızlı ve küçük kullanır strto* başlık altındaki fonksiyonlar.

C ++ 'nın daha önceki bir revizyonuna takılırsanız, bu fonksiyonları uygulamanızda taklit etmeniz sizin için ileriye taşınabilir olacaktır.


6
2017-08-01 15:52





C ++ 17'den itibaren kullanabilirsiniz std::from_chars itibaren <charconv> başlıklı belge İşte.

Örneğin:

#include <iostream>
#include <charconv>
#include <array>

int main()
{
    char const * str = "42";
    int value = 0;

    std::from_chars_result result = std::from_chars(std::begin(str), std::end(str), value);

    if(result.error == std::errc::invalid_argument)
    {
      std::cout << "Error, invalid format";
    }
    else if(result.error == std::errc::result_out_of_range)
    {
      std::cout << "Error, value too big for int range";
    }
    else
    {
      std::cout << "Success: " << result;
    }
}

Bir bonus olarak, onaltılık gibi diğer üsleri de işleyebilir.


3
2018-02-21 23:30