Який спосіб C ++ аналізує рядок (заданий як char *) у int? Надійна та чітка обробка помилок - це плюс (замість повернення нуля ).
Який спосіб C ++ аналізує рядок (заданий як char *) у int? Надійна та чітка обробка помилок - це плюс (замість повернення нуля ).
Відповіді:
У новому C ++ 11 для цього є функції: stoi, stol, stoll, stoul тощо.
int myNr = std::stoi(myString);
Це викине виняток на помилку конверсії.
Навіть у цих нових функцій все ще є та сама проблема , що і Ден: вони з радістю перетворять рядок "11x" в ціле число "11".
Дивіться більше: http://en.cppreference.com/w/cpp/string/basic_string/stol
size_t
не дорівнює довжині рядка, він зупинився рано. У цьому випадку він все одно поверне 11, але pos
буде 2 замість довжини рядка 3. coliru.stacked-crooked.com/a/cabe25d64d2ffa29
Ось моя перша порада: не використовуйте для цього рядок . Хоча спочатку це може здатися простим у використанні, ви виявите, що вам доведеться виконати багато зайвих робіт, якщо вам потрібно надійність і хороше поводження з помилками.
Ось підхід, який інтуїтивно здається, що він повинен працювати:
bool str2int (int &i, char const *s)
{
std::stringstream ss(s);
ss >> i;
if (ss.fail()) {
// not an integer
return false;
}
return true;
}
У цьому є основна проблема: str2int(i, "1337h4x0r")
радісно повернеться true
іi
отримає цінність 1337
. Ми можемо вирішити цю проблему, переконавшись, що stringstream
після конверсії більше символів не буде:
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;
}
Ми вирішили одну проблему, але є ще пару інших проблем.
Що робити, якщо число в рядку не є базовим 10? Ми можемо спробувати розмістити інші бази, встановивши потік у правильний режим (наприклад,ss << std::hex
), перш ніж спробувати перетворення. Але це означає, що абонент повинен апріорі знати, яке базове число - і як, можливо, той, хто телефонує, може це знати? Абонент ще не знає, що таке номер. Вони навіть не знають , що цечисло! Як від них можна очікувати, що це основа? Ми можемо просто призначити, що всі цифри, що вводяться в наші програми, повинні бути базовими 10 і відхиляти шістнадцятковий або восьмеричний введення як недійсні. Але це не дуже гнучко чи надійно. Просте рішення цієї проблеми не існує. Ви не можете просто спробувати перетворення один раз для кожної бази, тому що десяткове перетворення завжди буде успішним для восьмеричних чисел (з початковим нулем), а восьмеричне перетворення може досягти успіху для деяких десяткових чисел. Тому зараз вам доведеться перевірити провідний нуль. Але зачекайте! Шістнадцяткові числа можуть починатися і з провідного нуля (0x ...). Зітхнути.
Навіть якщо вам вдасться вирішити вищезазначені проблеми, існує ще одна більша проблема: що робити, якщо абонент повинен розрізняти поганий вхід (наприклад, "123foo") і число, яке знаходиться поза діапазоном int
(наприклад, "4000000000" для 32-розрядні int
)? З stringstream
цим способом зробити це не можна. Ми лише знаємо, вдалося чи не вдалося перейти. Якщо це не вдасться, ми не можемо знати, чому він не вдався. Як бачимо, stringstream
залишається бажати кращого, якщо ви хочете отримати надійність та чітку обробку помилок.
Це підводить мене до моєї другої поради: не використовуйте lexical_cast
для цього Boost . Розглянемо, що lexical_cast
має відповідати документація:
Там, де потрібен більш високий ступінь контролю над перетвореннями, std :: stringstream і std :: wstringstream пропонують більш відповідний шлях. Там, де потрібні конверсії на основі потоку, lexical_cast є неправильним інструментом для роботи та не застосовується для таких сценаріїв.
Що?? Ми вже бачили, що він stringstream
має низький рівень контролю, але він говорить, що його stringstream
слід використовувати замість того, lexical_cast
якщо вам потрібен "більш високий рівень контролю". Крім того, оскільки lexical_cast
це лише обгортка навколо stringstream
, вона страждає від тих же проблем, що stringstream
і: погана підтримка декількох баз чисел і погана обробка помилок.
На щастя, хтось уже вирішив усі перераховані вище проблеми. Стандартна бібліотека С містить strtol
сім'ю, яка не має жодної з цих проблем.
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;
}
Досить просто для чогось, що обробляє всі випадки помилок, а також підтримує будь-яку базу чисел від 2 до 36. Якщо base
нуль (за замовчуванням), він спробує перетворити з будь-якої бази. Або абонент може надати третій аргумент і вказати, що перетворення слід намагатись лише для певної бази. Він надійний і обробляє всі помилки з мінімальною витратою зусиль.
Інші причини віддати перевагу strtol
(та сімейні):
Абсолютно немає вагомих причин використовувати будь-який інший метод.
strtol
повинен бути безпечним для потоків. POSIX також вимагає errno
використання локального потокового сховища. Навіть у системах, що не є POSIX, майже всі реалізації errno
багатопотокових систем використовують локальне зберігання. Останній C ++ стандарт errno
повинен відповідати POSIX. Останній стандарт С також вимагає errno
локального зберігання потоків. Навіть у Windows, яка, безумовно, не підтримує POSIX, errno
є безпечною для потоків і, таким чином, так strtol
.
std::stol
для цього, що буде відповідним чином викидати винятки, а не повертати константи.
std::stol
навіть був доданий до мови C ++. При цьому, я не думаю, що це справедливо сказати, що це "C кодування в C ++". Нерозумно говорити, що std::strtol
це кодування C, коли воно явно є частиною мови C ++. Моя відповідь відмінно застосовувалася до C ++, коли вона була написана, і вона все ще застосовується навіть до нового std::stol
. Виклик функцій, які можуть кидати винятки, не завжди найкращий для кожної ситуації програмування.
Це безпечніший спосіб C, ніж atoi ()
const char* str = "123";
int i;
if(sscanf(str, "%d", &i) == EOF )
{
/* error */
}
C ++ з використанням стандартної бібліотеки stringstream : (спасибі CMS )
int str2int (const string &str) {
stringstream ss(str);
int num;
if((ss >> num).fail())
{
//ERROR
}
return num;
}
З збільшеною бібліотекою: (спасибі 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
}
Редагувати: виправлена версія потокової версії, щоб вона обробляла помилки. (завдяки коментарям CMS та jk щодо оригінальної публікації)
Хороший 'старий шлях С все ще працює. Я рекомендую strtol або strtoul. Між статусом повернення та 'endPtr', ви можете дати хороший діагностичний результат. Він також добре обробляє декілька баз.
Ви можете використовувати Boost'slexical_cast
, який перетворює це на більш загальний інтерфейс.
lexical_cast<Target>(Source)
кидає bad_lexical_cast
на невдачу.
Ви можете використовувати потоковий рядок із стандартної бібліотеки C ++:
stringstream ss(str);
int x;
ss >> x;
if(ss) { // <-- error handling
// use x
} else {
// not a number
}
Стан потоку буде встановлено з ладу, якщо під час спроби зчитування цілого числа виникає нецифрова цифра.
Дивіться підводні камені Потоки для підводних каменів керування помилками та потоків у C ++.
Ви можете використовувати рядкові потоки
int str2int (const string &str) {
stringstream ss(str);
int num;
ss >> num;
return num;
}
Я думаю, що ці три посилання підсумовують це:
Рішення stringstream і lexical_cast приблизно такі ж, як і лексичний ролик, що використовує stringstream.
Деякі спеціалізації лексичної ролі використовують різний підхід, див. Http://www.boost.org/doc/libs/release/boost/lexical_cast.hpp для деталей. Цілі чи плавці тепер спеціалізуються для перетворення цілих рядків.
Можна спеціалізувати lexical_cast на власні потреби та зробити це швидко. Це було б найкращим рішенням, яке задовольняє всі сторони, чисто і просто.
У вже згаданих статтях показано порівняння між різними методами перетворення цілих чисел <-> рядків. Наступні підходи мають сенс: старий c-way, spirit.karma, fastformat, простий наївний цикл.
У деяких випадках Lexical_cast є нормальним, наприклад, для перетворення int у рядок.
Перетворення рядка в int за допомогою лексичного виступу не є хорошою ідеєю, оскільки це в 10-40 разів повільніше, ніж atoi, залежно від використовуваної платформи / компілятора.
Boost.Spirit.Karma, здається, є найшвидшою бібліотекою для перетворення цілого числа в рядок.
ex.: generate(ptr_char, int_, integer_number);
а базовий простий цикл із зазначеної вище статті - це найшвидший спосіб перетворення рядка в int, очевидно, не найбезпечніший, strtol () здається більш безпечним рішенням
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 ++ Рядок Toolkit Library (StrTk) має наступне рішення:
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 може бути або непідписаним char *, char * або std :: ітераторами рядків, і T, як очікується, буде підписаним int, таким як підписаний int, int або long
v = (10 * v) + digit;
непотрібно переповнює рядок із текстовим значенням INT_MIN
. Таблиця має сумнівне значення порівняно з простоdigit >= '0' && digit <= '9'
Якщо у вас є C ++ 11, то в даний час відповідними рішеннями є функції перетворення цілих чисел C ++ в <string>
: stoi
, stol
, stoul
, stoll
, stoull
. Вони викидають відповідні винятки при неправильному введенні та використовують швидкі та малі strto*
функції під кришкою.
Якщо ви затрималися з попередньою версією C ++, вам було б зручно переносити ці функції у вашій реалізації.
Від C ++ 17 далі ви можете використовувати std::from_chars
з <charconv>
заголовка, як тут задокументовано .
Наприклад:
#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;
}
}
Як бонус, він також може обробляти інші бази, наприклад, шістнадцяткову.
Мені подобається відповідь Дена Маулдінга , я просто додам до неї трохи стилю C ++:
#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;
}
Він працює як для std :: string, так і для const char * через неявне перетворення. Він також корисний для базової конверсії, наприклад, всі to_int("0x7b")
та to_int("0173")
і to_int("01111011", 2)
та to_int("0000007B", 16)
і to_int("11120", 3)
і to_int("3L", 34);
повертають 123.
На відміну від std::stoi
цього, працює у попередньому C ++ 11. Крім того, в відміну std::stoi
, boost::lexical_cast
іstringstream
він кидає винятки для дивних рядків типу "123hohoho".
Примітка: Ця функція переносить провідні пробіли, але не проміжки, тобто to_int(" 123")
повертає 123 під час to_int("123 ")
виключення. Переконайтесь, що це прийнятно для вашого випадку використання або відкоригуйте код.
Така функція може бути частиною STL ...
Я знаю три способи перетворення рядка в int:
Або скористайтеся функцією stoi (String to int) або просто перейдіть за допомогою Stringstream, третій спосіб перейти до індивідуального перетворення. Код наведено нижче:
1-й метод
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-й метод
#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-й метод - але не для індивідуального перетворення
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 ;
}
Мені подобається відповідь Дана , особливо через те, що уникнути винятків. Для розробки вбудованих систем та інших системних систем низького рівня може бути відсутнім належної системи винятків.
Додано чек на пробіл після дійсного рядка ... ці три рядки
while (isspace(*end)) {
end++;
}
Також додано чек на синтаксичний аналіз помилок.
if ((errno != 0) || (s == end)) {
return INCONVERTIBLE;
}
Ось повна функція ..
#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;
}
" "
. strtol()
не вказано для встановлення, errno
коли не відбувається перетворення. Краще використовувати if (s == end) return INCONVERTIBLE;
для виявлення конверсії. А потім if (*s == '\0' || *end != '\0')
можна спростити до if (*end)
2) || l > LONG_MAX
і || l < LONG_MIN
не служити ніякій меті - вони ніколи не бувають правдою.
Ви можете використовувати цей визначений метод.
#define toInt(x) {atoi(x.c_str())};
І якби ви перетворили з String в цілий, ви просто зробите наступне.
int main()
{
string test = "46", test2 = "56";
int a = toInt(test);
int b = toInt(test2);
cout<<a+b<<endl;
}
Вихід буде 102.
atoi
не схоже на "спосіб С ++", зважаючи на інші відповіді, як прийняті std::stoi()
.
Я знаю, що це питання старіше, але я натрапляв на нього стільки разів і на сьогоднішній день досі не знайшов гарного шаблонного рішення, що має такі характеристики:
Отже, ось моя, з тест-ремінцем. Оскільки він використовує функції C strtoull / strtoll під кришкою, він завжди перетворюється спочатку в найбільший доступний тип. Тоді, якщо ви не використовуєте найбільший тип, він здійснить додаткову перевірку діапазону, щоб переконатися, що ваш тип не був надто (під) потоком. Для цього він трохи менш ефективний, ніж якщо б правильно вибрав strtol / strtoul. Однак він також працює для шорт / символів, і, наскільки мені відомо, не існує стандартної бібліотечної функції, яка б це робила.
Насолоджуйтесь; сподіваємось, хтось вважає це корисним.
#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
метод користувача-земля; він перевантажений, тому його можна назвати так:
int a; a = StringToDecimal<int>("100");
або це:
int a; StringToDecimal(a, "100");
Я ненавиджу повторювати тип int, тому віддаю перевагу останній. Це гарантує, що якщо тип 'a' зміниться, результат не отримає поганих результатів. Я хочу, щоб компілятор міг зрозуміти це так:
int a; a = StringToDecimal("100");
... але, C ++ не виводить типи повернення шаблонів, тому це найкраще, що я можу отримати.
Реалізація досить проста:
CstrtoxllWrapper
загортає і, strtoull
і strtoll
викликає те, що потрібно на основі підписання типу шаблону та надає додаткові гарантії (наприклад, негативний вхід заборонено, якщо його не підписано, і це гарантує, що вся рядок була перетворена).
CstrtoxllWrapper
використовується StringToSigned
і StringToUnsigned
з найбільшим типом (long long / unsigned long long), доступним компілятору; це дозволяє виконати максимальне перетворення. Потім, якщо це необхідно, StringToSigned
/ StringToUnsigned
виконує остаточну перевірку діапазону для базового типу. Нарешті, кінцевий метод,StringToDecimal
визначає, який із методів шаблону StringTo * викликати на основі підписаного типу.
Я думаю, що більшу частину сміття може оптимізувати компілятор; майже все повинно бути детермінованим за часом збирання. Будь-який коментар до цього аспекту був би мені цікавий!
long long
замість intmax_t
?
if (ePtr != str)
. Далі, використовуйте isspace((unsigned char) *ePtr)
для правильної обробки негативних значень *ePtr
.
В C ви можете використовувати int atoi (const char * str)
,
Розбирає рядок C-string, інтерпретуючи її вміст як цілісне число, яке повертається як значення типу int.
atoi
запитання, я це знаю. Питання явно не про С, а про С ++. -1