Bir karakter dizisini (char * olarak verilir) int olarak ayrıştırmanın C ++ yolu nedir? Sağlam ve net hata işleme bir artıdır ( sıfır döndürmek yerine ).
Bir karakter dizisini (char * olarak verilir) int olarak ayrıştırmanın C ++ yolu nedir? Sağlam ve net hata işleme bir artıdır ( sıfır döndürmek yerine ).
Yanıtlar:
Yeni C ++ 11'de bunun işlevleri vardır: stoi, stol, stoll, stoul vb.
int myNr = std::stoi(myString);
Dönüşüm hatası için bir istisna atar.
Bu yeni işlevler bile aynı sorunu yaşıyor Dan'ın belirttiği : "11x" dizesini "11" tamsayısına dönüştürürler.
Daha fazlasını görün: http://en.cppreference.com/w/cpp/string/basic_string/stol
size_t
, dizenin uzunluğuna eşit değilse, erken durdu. Bu durumda yine de 11 döndürür, ancak pos
dize uzunluğu 3 yerine 2 olur. Coliru.stacked-crooked.com/a/cabe25d64d2ffa29
İşte benim ilk tavsiyem: Bunun için stringstream kullanmayın . İlk başta kullanımı basit gibi görünse de, sağlamlık ve iyi hata işleme 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;
}
Bu büyük bir sorun var: str2int(i, "1337h4x0r")
mutlulukla dönecek true
ve i
değeri alacak 1337
. stringstream
Dönüşümden sonra başka karakter kalmadığından emin olarak bu sorunu çözebiliriz :
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 sorunu düzelttik, ancak hala birkaç sorun daha var.
Dizedeki sayı temel 10 değilse ne olur? ss << std::hex
Dönüştürmeyi denemeden önce akışı doğru moda (ör. ) Ayarlayarak diğer üsleri barındırmayı deneyebiliriz . Ancak bu, arayan kişinin bir önsezi bilmesi gerektiği anlamına gelir numarayı gereken sayıdır - arayan kişi bunu nasıl bilebilir? Arayan kişi numaranın ne olduğunu henüz bilmiyor. Bunun olduğunu bile bilmiyorlarbir sayı! Bunun nasıl bir temel olduğunu bilmesi nasıl beklenebilir? Programlarımıza girilen tüm sayıların temel 10 olması ve onaltılık veya sekizlik girdiyi geçersiz olarak reddetmesi gerektiğini söyleyebiliriz. Ancak bu çok esnek veya sağlam değildir. Bu sorunun basit bir çözümü yoktur. Her bir taban için dönüşümü bir kez deneyemezsiniz, çünkü ondalık dönüşüm her zaman sekizli sayılar için (baştaki sıfır ile) başarılı olur ve sekizlik dönüşüm bazı ondalık sayılar için başarılı olabilir. Şimdi önde sıfır olup olmadığını kontrol etmelisiniz. Fakat bekle! Onaltılık sayılar da baştaki sıfır ile başlayabilir (0x ...). İç çekmek.
Yukarıdaki sorunlarla başa çıksanız bile, daha büyük bir sorun daha var: arayanın hatalı giriş (örneğin "123foo") ile aralığının dışında bir sayı int
( 32 bit int
)? İle stringstream
, bu ayrımı yapmanın bir yolu yoktur. Yalnızca dönüşümün başarılı veya başarısız olduğunu biliyoruz. Başarısız olursa, neden başarısız olduğunu bilmenin hiçbir yolu yoktur . Gördüğünüz gibi, stringstream
sağlamlık ve net hata yönetimi istiyorsanız, arzulanan çok şey bırakıyor.
Tavsiye benim ikinci parçasına Bu potansiyel müşteriler beni: faydası yok Boost var yapmak lexical_cast
bunun için . lexical_cast
Belgelerin neler söyleyeceğini düşünün :
Dönüşümler üzerinde daha yüksek düzeyde kontrol gerektiğinde, std :: stringstream ve std :: wstringstream daha uygun bir yol sunar. Akışa dayalı olmayan dönüşümlerin gerekli olduğu durumlarda, lexical_cast iş için yanlış araçtır ve bu tür senaryolar için özel bir kasa değildir.
Ne?? Bunun stringstream
daha düşük bir kontrol seviyesine sahip olduğunu gördük ve yine de "daha yüksek bir kontrol seviyesine" ihtiyacınız olduğunda stringstream
kullanılması gerektiğini söylüyor lexical_cast
. Ayrıca, lexical_cast
sadece bir sarıcı stringstream
olduğu için, aynı problemlerden muzdariptir stringstream
: çoklu sayı tabanları için zayıf destek ve zayıf hata işleme.
Neyse ki, bir kişi yukarıdaki sorunların hepsini zaten çözdü. C standart kütüphanesi strtol
ve bu problemlerin hiçbirine sahip olmayan bir aile içerir .
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ı işleyen 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 ise (varsayılan) herhangi bir tabandan dönüştürmeye çalışacaktır. Veya arayan üçüncü argümanı sağlayabilir ve dönüşümün yalnızca belirli bir temel için denenmesi gerektiğini belirtebilir. Sağlamdır ve tüm hataları en az çaba harcayarak işler.
Tercih etmek için diğer nedenler strtol
(ve aile):
Başka bir yöntem kullanmak için kesinlikle iyi bir neden yoktur.
strtol
iş parçacığı için güvenli olması gerekir . POSIX ayrıca errno
yerel iş parçacığı depolama birimini de gerektirir . POSIX olmayan sistemlerde bile, errno
çok iş parçacıklı sistemlerin neredeyse tüm uygulamaları yerel iş parçacığı depolamasını kullanır. En son C ++ standardının errno
POSIX uyumlu olması gerekir. En yeni C standardı da errno
yerel olarak yerel depolamaya sahip olmayı gerektirir . Kesinlikle POSIX uyumlu olmayan Windows'ta bile errno
, iş parçacığı için güvenlidir ve uzantı ile de öyle strtol
.
std::stol
bunun için, sabitleri döndürmek yerine uygun şekilde istisnalar atacaktır.
std::stol
bile C ++ diline eklendi. Bununla birlikte, bunun "C ++ içinde C kodlaması" olduğunu söylemenin adil olduğunu düşünmüyorum. std::strtol
Açıkça C ++ dilinin bir parçası olduğunda C kodlaması olduğunu söylemek saçmadır . Cevabım yazıldığı zaman C ++ 'a mükemmel bir şekilde uygulandı ve hala yeni ile bile geçerli std::stol
. İstisnalar atabilecek işlevleri çağırmak her programlama durumu için her zaman en iyisi değildir.
Bu, atoi () 'den daha güvenli bir C yoludur
const char* str = "123";
int i;
if(sscanf(str, "%d", &i) == EOF )
{
/* error */
}
Standart kitaplık stringstream ile C ++ : (teşekkürler CMS )
int str2int (const string &str) {
stringstream ss(str);
int num;
if((ss >> num).fail())
{
//ERROR
}
return num;
}
İle boost kütüphanesinden: (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: Dize akışı sürümünü hataları işleyecek şekilde düzeltildi. (CMS'ler ve jk'ın orijinal gönderi hakkındaki yorumları sayesinde)
İyi 'eski C yolu hala çalışıyor. Ben strtol veya strtoul öneririm. Dönüş durumu ile 'endPtr' arasında, iyi tanı çıktısı verebilirsiniz. Aynı zamanda birden fazla üssü güzelce idare eder.
Sen kullanabilirsiniz Boost 'slexical_cast
, bu sarar daha genel bir arayüzde.
lexical_cast<Target>(Source)
atar bad_lexical_cast
başarısızlık üzerine.
C ++ standart kütüphanesinden bir dize akışını kullanabilirsiniz:
stringstream ss(str);
int x;
ss >> x;
if(ss) { // <-- error handling
// use x
} else {
// not a number
}
Bir tam sayı okunmaya çalışılırken rakam olmayan bir durumla karşılaşılırsa akış durumu başarısız olacak şekilde ayarlanır.
C ++ 'da hata işleme ve akış tuzakları için Akış tuzaklarına bakın .
Stringstream'leri kullanabilirsiniz
int str2int (const string &str) {
stringstream ss(str);
int num;
ss >> num;
return num;
}
Bence bu üç bağlantı özetliyor:
stringstream ve lexical_cast çözümleri, lexical cast'ın stringstream kullandığı ile hemen hemen aynıdır.
Sözcük dağarcığının bazı uzmanlıkları farklı yaklaşımlar kullanır , ayrıntılar için http://www.boost.org/doc/libs/release/boost/lexical_cast.hpp adresine bakın. Tamsayılar ve kayan noktalar artık tamsayıdan dizeye dönüştürme için uzmanlaşmıştır.
Lexical_cast kendi ihtiyaçları için uzmanlaşabilir ve hızlı bir şekilde yapılabilir. Bu, temiz ve basit tüm tarafları tatmin eden nihai çözüm olacaktır.
Daha önce zikredilen makaleler, tamsayılar <-> dizelerini dönüştürmenin farklı yöntemleri arasındaki karşılaştırmayı göstermektedir. Aşağıdaki yaklaşımlar mantıklıdır: eski c-yolu, ruh. Karma, fastformat, basit saf döngü.
Lexical_cast bazı durumlarda, örneğin int'ten string'e dönüştürme için uygundur.
Diziyi lexical cast kullanarak int'e dönüştürmek, kullanılan platforma / derleyiciye bağlı olarak atoi'den 10-40 kat daha yavaş olduğu için iyi bir fikir değildir.
Boost.Spirit.Karma, tamsayıyı dizeye dönüştürmek için en hızlı kütüphane gibi görünüyor.
ex.: generate(ptr_char, int_, integer_number);
ve yukarıda bahsedilen makaledeki basit basit döngü, dizeyi int'e dönüştürmenin en hızlı yoludur, açıkçası en güvenli olana değil, strtol () daha güvenli bir çözüm gibi görünüyor
int naive_char_2_int(const char *p) {
int x = 0;
bool neg = false;
if (*p == '-') {
neg = true;
++p;
}
while (*p >= '0' && *p <= '9') {
x = (x*10) + (*p - '0');
++p;
}
if (neg) {
x = -x;
}
return x;
}
C ++ Yaylı Toolkit Kütüphanesi (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 işaretsiz char *, char * veya std :: string yineleyicileri olabilir ve T'nin işaretli int, int veya uzun gibi işaretli bir int olması beklenir.
v = (10 * v) + digit;
metin değeri ile dize girişi ile gereksiz yere taşar INT_MIN
. Tablo şüpheli değer vs basitçedigit >= '0' && digit <= '9'
Eğer C ++ 11 varsa, uygun çözümler günümüzde dönüşüm işlevleri tamsayı C ++ şunlardır <string>
: stoi
, stol
, stoul
, stoll
, stoull
. Yanlış giriş yapıldığında uygun istisnalar atarlar strto*
ve kaputun altındaki hızlı ve küçük fonksiyonları kullanırlar .
C ++ 'nın daha önceki bir revizyonuna takılı kalırsanız, uygulamanızda bu işlevleri taklit etmeniz ileriye taşınabilir.
C ++ 17'den itibaren std::from_chars
, burada<charconv>
belgelendiği gibi başlıktan kullanabilirsiniz .
Ö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 ele alabilir.
Dan Moulding'in cevabını seviyorum , sadece biraz C ++ stili ekleyeceğim:
#include <cstdlib>
#include <cerrno>
#include <climits>
#include <stdexcept>
int to_int(const std::string &s, int base = 0)
{
char *end;
errno = 0;
long result = std::strtol(s.c_str(), &end, base);
if (errno == ERANGE || result > INT_MAX || result < INT_MIN)
throw std::out_of_range("toint: string is out of range");
if (s.length() == 0 || *end != '\0')
throw std::invalid_argument("toint: invalid string");
return result;
}
Örtük dönüşüm yoluyla hem std :: string hem de const char * için çalışır. Ayrıca temel dönüşüm için de faydalıdır, örneğin tüm to_int("0x7b")
ve to_int("0173")
ve to_int("01111011", 2)
ve to_int("0000007B", 16)
ve to_int("11120", 3)
veto_int("3L", 34);
123 dönecekti.
Aksine std::stoi
C ++ 11 öncesi çalışır. Ayrıca farklı std::stoi
, boost::lexical_cast
vestringstream
bu "123hohoho" gibi garip dizeleri için istisnalar atar.
Not: Bu işlev öndeki boşlukları tolere eder, ancak arkadaki boşlukları tolere etmez, yani istisna to_int(" 123")
atarken 123 döndürür to_int("123 ")
. Bunun kullanım durumunuz için kabul edilebilir olduğundan emin olun veya kodu ayarlayın.
Böyle bir fonksiyon STL'nin bir parçası olabilir ...
Int içine String dönüştürmenin üç yolu biliyorum:
Stoi (String to int) işlevini kullanın ya da tek tek dönüştürmenin üçüncü yolu olan Stringstream ile gidin, Code aşağıdadır:
1. Yöntem
std::string s1 = "4533";
std::string s2 = "3.010101";
std::string s3 = "31337 with some string";
int myint1 = std::stoi(s1);
int myint2 = std::stoi(s2);
int myint3 = std::stoi(s3);
std::cout << s1 <<"=" << myint1 << '\n';
std::cout << s2 <<"=" << myint2 << '\n';
std::cout << s3 <<"=" << myint3 << '\n';
2. Yöntem
#include <string.h>
#include <sstream>
#include <iostream>
#include <cstring>
using namespace std;
int StringToInteger(string NumberAsString)
{
int NumberAsInteger;
stringstream ss;
ss << NumberAsString;
ss >> NumberAsInteger;
return NumberAsInteger;
}
int main()
{
string NumberAsString;
cin >> NumberAsString;
cout << StringToInteger(NumberAsString) << endl;
return 0;
}
3. Yöntem - ancak tek bir dönüşüm için değil
std::string str4 = "453";
int i = 0, in=0; // 453 as on
for ( i = 0; i < str4.length(); i++)
{
in = str4[i];
cout <<in-48 ;
}
Dan'ın cevabını seviyorum , özellikle de istisnalardan kaçınmak. Gömülü sistem geliştirme ve diğer düşük düzeyli sistem geliştirme için uygun bir İstisna çerçevesi olmayabilir.
Geçerli bir dizeden sonra boşluk için bir kontrol eklendi ... bu üç satır
while (isspace(*end)) {
end++;
}
Ayrıştırma hataları için de bir kontrol eklendi.
if ((errno != 0) || (s == end)) {
return INCONVERTIBLE;
}
İşte tam fonksiyon ..
#include <cstdlib>
#include <cerrno>
#include <climits>
#include <stdexcept>
enum STR2INT_ERROR { SUCCESS, OVERFLOW, UNDERFLOW, INCONVERTIBLE };
STR2INT_ERROR str2long (long &l, char const *s, int base = 0)
{
char *end = (char *)s;
errno = 0;
l = strtol(s, &end, base);
if ((errno == ERANGE) && (l == LONG_MAX)) {
return OVERFLOW;
}
if ((errno == ERANGE) && (l == LONG_MIN)) {
return UNDERFLOW;
}
if ((errno != 0) || (s == end)) {
return INCONVERTIBLE;
}
while (isspace((unsigned char)*end)) {
end++;
}
if (*s == '\0' || *end != '\0') {
return INCONVERTIBLE;
}
return SUCCESS;
}
" "
. hiçbir dönüşüm gerçekleşmediğinde strtol()
ayarlanacak şekilde belirtilmez errno
. if (s == end) return INCONVERTIBLE;
Dönüşüm tespit etmek için kullanmak daha iyidir. Ve sonra 2'ye if (*s == '\0' || *end != '\0')
sadeleştirebilir if (*end)
) || l > LONG_MAX
ve || l < LONG_MIN
hiçbir amaca hizmet etmezler - asla doğru değildir.
Bu tanımlı yöntemi kullanabilirsiniz.
#define toInt(x) {atoi(x.c_str())};
Ve eğer String'den bir Tamsayıya dönüştüyseniz, aşağıdakileri yapardınız.
int main()
{
string test = "46", test2 = "56";
int a = toInt(test);
int b = toInt(test2);
cout<<a+b<<endl;
}
Çıktı 102 olur.
atoi
, kabul edilen diğer cevaplar ışığında "C ++ yolu" gibi görünmüyor std::stoi()
.
Bunun daha eski bir soru olduğunu biliyorum, ancak birçok kez karşılaştım ve bugüne kadar aşağıdaki özelliklere sahip güzel bir şablon bulamadık:
İşte, test kayışı ile benim. Kaputun altında strtoull / strtoll C işlevlerini kullandığı için, her zaman önce mevcut en büyük türe dönüştürür. Daha sonra, en büyük türü kullanmıyorsanız, türünüzün fazla (altında) akmadığını doğrulamak için ek aralık kontrolleri gerçekleştirir. Bunun için, strtol / strtoul'u düzgün seçmiş olmaktan biraz daha az performans gösterir. Bununla birlikte, şort / chars için de çalışır ve bildiğim kadarıyla bunu yapan standart bir kütüphane işlevi yoktur.
Zevk almak; umarım birisi faydalı bulur.
#include <cstdlib>
#include <cerrno>
#include <limits>
#include <stdexcept>
#include <sstream>
static const int DefaultBase = 10;
template<typename T>
static inline T CstrtoxllWrapper(const char *str, int base = DefaultBase)
{
while (isspace(*str)) str++; // remove leading spaces; verify there's data
if (*str == '\0') { throw std::invalid_argument("str; no data"); } // nothing to convert
// NOTE: for some reason strtoull allows a negative sign, we don't; if
// converting to an unsigned then it must always be positive!
if (!std::numeric_limits<T>::is_signed && *str == '-')
{ throw std::invalid_argument("str; negative"); }
// reset errno and call fn (either strtoll or strtoull)
errno = 0;
char *ePtr;
T tmp = std::numeric_limits<T>::is_signed ? strtoll(str, &ePtr, base)
: strtoull(str, &ePtr, base);
// check for any C errors -- note these are range errors on T, which may
// still be out of the range of the actual type we're using; the caller
// may need to perform additional range checks.
if (errno != 0)
{
if (errno == ERANGE) { throw std::range_error("str; out of range"); }
else if (errno == EINVAL) { throw std::invalid_argument("str; EINVAL"); }
else { throw std::invalid_argument("str; unknown errno"); }
}
// verify everything converted -- extraneous spaces are allowed
if (ePtr != NULL)
{
while (isspace(*ePtr)) ePtr++;
if (*ePtr != '\0') { throw std::invalid_argument("str; bad data"); }
}
return tmp;
}
template<typename T>
T StringToSigned(const char *str, int base = DefaultBase)
{
static const long long max = std::numeric_limits<T>::max();
static const long long min = std::numeric_limits<T>::min();
long long tmp = CstrtoxllWrapper<typeof(tmp)>(str, base); // use largest type
// final range check -- only needed if not long long type; a smart compiler
// should optimize this whole thing out
if (sizeof(T) == sizeof(tmp)) { return tmp; }
if (tmp < min || tmp > max)
{
std::ostringstream err;
err << "str; value " << tmp << " out of " << sizeof(T) * 8
<< "-bit signed range (";
if (sizeof(T) != 1) err << min << ".." << max;
else err << (int) min << ".." << (int) max; // don't print garbage chars
err << ")";
throw std::range_error(err.str());
}
return tmp;
}
template<typename T>
T StringToUnsigned(const char *str, int base = DefaultBase)
{
static const unsigned long long max = std::numeric_limits<T>::max();
unsigned long long tmp = CstrtoxllWrapper<typeof(tmp)>(str, base); // use largest type
// final range check -- only needed if not long long type; a smart compiler
// should optimize this whole thing out
if (sizeof(T) == sizeof(tmp)) { return tmp; }
if (tmp > max)
{
std::ostringstream err;
err << "str; value " << tmp << " out of " << sizeof(T) * 8
<< "-bit unsigned range (0..";
if (sizeof(T) != 1) err << max;
else err << (int) max; // don't print garbage chars
err << ")";
throw std::range_error(err.str());
}
return tmp;
}
template<typename T>
inline T
StringToDecimal(const char *str, int base = DefaultBase)
{
return std::numeric_limits<T>::is_signed ? StringToSigned<T>(str, base)
: StringToUnsigned<T>(str, base);
}
template<typename T>
inline T
StringToDecimal(T &out_convertedVal, const char *str, int base = DefaultBase)
{
return out_convertedVal = StringToDecimal<T>(str, base);
}
/*============================== [ Test Strap ] ==============================*/
#include <inttypes.h>
#include <iostream>
static bool _g_anyFailed = false;
template<typename T>
void TestIt(const char *tName,
const char *s, int base,
bool successExpected = false, T expectedValue = 0)
{
#define FAIL(s) { _g_anyFailed = true; std::cout << s; }
T x;
std::cout << "converting<" << tName << ">b:" << base << " [" << s << "]";
try
{
StringToDecimal<T>(x, s, base);
// get here on success only
if (!successExpected)
{
FAIL(" -- TEST FAILED; SUCCESS NOT EXPECTED!" << std::endl);
}
else
{
std::cout << " -> ";
if (sizeof(T) != 1) std::cout << x;
else std::cout << (int) x; // don't print garbage chars
if (x != expectedValue)
{
FAIL("; FAILED (expected value:" << expectedValue << ")!");
}
std::cout << std::endl;
}
}
catch (std::exception &e)
{
if (successExpected)
{
FAIL( " -- TEST FAILED; EXPECTED SUCCESS!"
<< " (got:" << e.what() << ")" << std::endl);
}
else
{
std::cout << "; expected exception encounterd: [" << e.what() << "]" << std::endl;
}
}
}
#define TEST(t, s, ...) \
TestIt<t>(#t, s, __VA_ARGS__);
int main()
{
std::cout << "============ variable base tests ============" << std::endl;
TEST(int, "-0xF", 0, true, -0xF);
TEST(int, "+0xF", 0, true, 0xF);
TEST(int, "0xF", 0, true, 0xF);
TEST(int, "-010", 0, true, -010);
TEST(int, "+010", 0, true, 010);
TEST(int, "010", 0, true, 010);
TEST(int, "-10", 0, true, -10);
TEST(int, "+10", 0, true, 10);
TEST(int, "10", 0, true, 10);
std::cout << "============ base-10 tests ============" << std::endl;
TEST(int, "-010", 10, true, -10);
TEST(int, "+010", 10, true, 10);
TEST(int, "010", 10, true, 10);
TEST(int, "-10", 10, true, -10);
TEST(int, "+10", 10, true, 10);
TEST(int, "10", 10, true, 10);
TEST(int, "00010", 10, true, 10);
std::cout << "============ base-8 tests ============" << std::endl;
TEST(int, "777", 8, true, 0777);
TEST(int, "-0111 ", 8, true, -0111);
TEST(int, "+0010 ", 8, true, 010);
std::cout << "============ base-16 tests ============" << std::endl;
TEST(int, "DEAD", 16, true, 0xDEAD);
TEST(int, "-BEEF", 16, true, -0xBEEF);
TEST(int, "+C30", 16, true, 0xC30);
std::cout << "============ base-2 tests ============" << std::endl;
TEST(int, "-10011001", 2, true, -153);
TEST(int, "10011001", 2, true, 153);
std::cout << "============ irregular base tests ============" << std::endl;
TEST(int, "Z", 36, true, 35);
TEST(int, "ZZTOP", 36, true, 60457993);
TEST(int, "G", 17, true, 16);
TEST(int, "H", 17);
std::cout << "============ space deliminated tests ============" << std::endl;
TEST(int, "1337 ", 10, true, 1337);
TEST(int, " FEAD", 16, true, 0xFEAD);
TEST(int, " 0711 ", 0, true, 0711);
std::cout << "============ bad data tests ============" << std::endl;
TEST(int, "FEAD", 10);
TEST(int, "1234 asdfklj", 10);
TEST(int, "-0xF", 10);
TEST(int, "+0xF", 10);
TEST(int, "0xF", 10);
TEST(int, "-F", 10);
TEST(int, "+F", 10);
TEST(int, "12.4", 10);
TEST(int, "ABG", 16);
TEST(int, "10011002", 2);
std::cout << "============ int8_t range tests ============" << std::endl;
TEST(int8_t, "7F", 16, true, std::numeric_limits<int8_t>::max());
TEST(int8_t, "80", 16);
TEST(int8_t, "-80", 16, true, std::numeric_limits<int8_t>::min());
TEST(int8_t, "-81", 16);
TEST(int8_t, "FF", 16);
TEST(int8_t, "100", 16);
std::cout << "============ uint8_t range tests ============" << std::endl;
TEST(uint8_t, "7F", 16, true, std::numeric_limits<int8_t>::max());
TEST(uint8_t, "80", 16, true, std::numeric_limits<int8_t>::max()+1);
TEST(uint8_t, "-80", 16);
TEST(uint8_t, "-81", 16);
TEST(uint8_t, "FF", 16, true, std::numeric_limits<uint8_t>::max());
TEST(uint8_t, "100", 16);
std::cout << "============ int16_t range tests ============" << std::endl;
TEST(int16_t, "7FFF", 16, true, std::numeric_limits<int16_t>::max());
TEST(int16_t, "8000", 16);
TEST(int16_t, "-8000", 16, true, std::numeric_limits<int16_t>::min());
TEST(int16_t, "-8001", 16);
TEST(int16_t, "FFFF", 16);
TEST(int16_t, "10000", 16);
std::cout << "============ uint16_t range tests ============" << std::endl;
TEST(uint16_t, "7FFF", 16, true, std::numeric_limits<int16_t>::max());
TEST(uint16_t, "8000", 16, true, std::numeric_limits<int16_t>::max()+1);
TEST(uint16_t, "-8000", 16);
TEST(uint16_t, "-8001", 16);
TEST(uint16_t, "FFFF", 16, true, std::numeric_limits<uint16_t>::max());
TEST(uint16_t, "10000", 16);
std::cout << "============ int32_t range tests ============" << std::endl;
TEST(int32_t, "7FFFFFFF", 16, true, std::numeric_limits<int32_t>::max());
TEST(int32_t, "80000000", 16);
TEST(int32_t, "-80000000", 16, true, std::numeric_limits<int32_t>::min());
TEST(int32_t, "-80000001", 16);
TEST(int32_t, "FFFFFFFF", 16);
TEST(int32_t, "100000000", 16);
std::cout << "============ uint32_t range tests ============" << std::endl;
TEST(uint32_t, "7FFFFFFF", 16, true, std::numeric_limits<int32_t>::max());
TEST(uint32_t, "80000000", 16, true, std::numeric_limits<int32_t>::max()+1);
TEST(uint32_t, "-80000000", 16);
TEST(uint32_t, "-80000001", 16);
TEST(uint32_t, "FFFFFFFF", 16, true, std::numeric_limits<uint32_t>::max());
TEST(uint32_t, "100000000", 16);
std::cout << "============ int64_t range tests ============" << std::endl;
TEST(int64_t, "7FFFFFFFFFFFFFFF", 16, true, std::numeric_limits<int64_t>::max());
TEST(int64_t, "8000000000000000", 16);
TEST(int64_t, "-8000000000000000", 16, true, std::numeric_limits<int64_t>::min());
TEST(int64_t, "-8000000000000001", 16);
TEST(int64_t, "FFFFFFFFFFFFFFFF", 16);
TEST(int64_t, "10000000000000000", 16);
std::cout << "============ uint64_t range tests ============" << std::endl;
TEST(uint64_t, "7FFFFFFFFFFFFFFF", 16, true, std::numeric_limits<int64_t>::max());
TEST(uint64_t, "8000000000000000", 16, true, std::numeric_limits<int64_t>::max()+1);
TEST(uint64_t, "-8000000000000000", 16);
TEST(uint64_t, "-8000000000000001", 16);
TEST(uint64_t, "FFFFFFFFFFFFFFFF", 16, true, std::numeric_limits<uint64_t>::max());
TEST(uint64_t, "10000000000000000", 16);
std::cout << std::endl << std::endl
<< (_g_anyFailed ? "!! SOME TESTS FAILED !!" : "ALL TESTS PASSED")
<< std::endl;
return _g_anyFailed;
}
StringToDecimal
kullanıcı-arazi yöntemidir; aşırı yüklenir, böylece şu şekilde çağrılabilir:
int a; a = StringToDecimal<int>("100");
veya bu:
int a; StringToDecimal(a, "100");
İnt türünü tekrarlamaktan nefret ediyorum, bu yüzden ikincisini tercih edin. Bu, 'a' tipi değişirse kötü sonuç alınmamasını sağlar. Derleyici şöyle çözebilir:
int a; a = StringToDecimal("100");
... ancak, C ++ şablon dönüş türlerini çıkarmaz, bu yüzden alabileceğim en iyisi budur.
Uygulama oldukça basit:
CstrtoxllWrapper
her ikisini de sarar strtoull
ve strtoll
şablon türünün imzalı olmasına bağlı olarak gerekli olanları çağırmak ve bazı ek garantiler sağlamak (örneğin, imzalanmamışsa negatif girdiye izin verilmez ve tüm dizgenin dönüştürülmesini sağlar).
CstrtoxllWrapper
derleyici tarafından kullanılabilen en büyük tip (uzun uzun / imzasız uzun uzun) tarafından StringToSigned
ve StringToUnsigned
ile kullanılır; bu, maksimum dönüşümün gerçekleştirilmesini sağlar. Daha sonra, gerekliyse, StringToSigned
/ StringToUnsigned
altta yatan tipte son aralık kontrollerini yapar. Son olarak, bitiş noktası yöntemi, StringToDecimal
temel türün imzalı olmasına bağlı olarak StringTo * şablon yöntemlerinden hangisinin çağrılacağına karar verir.
Önemsizlerin çoğu derleyici tarafından optimize edilebilir düşünüyorum; hemen hemen her şey derleme zamanı belirleyicisi olmalıdır. Bu konudaki herhangi bir yorum benim için ilginç olurdu!
long long
yerine intmax_t
?
if (ePtr != str)
. Ayrıca, isspace((unsigned char) *ePtr)
negatif değerlerini düzgün işlemek için kullanın *ePtr
.
C dilinde şunları kullanabilirsiniz int atoi (const char * str)
:
İnt türünde bir değer olarak döndürülen içeriğini integral bir sayı olarak yorumlayan C-string str'yi ayrıştırır.
atoi
kurduğumdan bunun farkındayım. Soru açıkça C ile ilgili değil, C ++ ile ilgili. -1