Каким способом C++ преобразовывает строку (заданную как char *) в int? Надежная и понятная обработка ошибок - это плюс (вместо возвращение нуля).
@Beh Tou Cheh, если вы думаете, что это хороший способ разобрать int, опубликуйте его в качестве ответа.
То же самое для C: stackoverflow.com/questions/7021725/…





Старый добрый способ C все еще работает. Я рекомендую strtol или strtoul. Между статусом возврата и «endPtr» вы можете дать хороший диагностический вывод. Он также прекрасно обрабатывает несколько баз.
О, пожалуйста, не используйте этот старый C при программировании на C++. На C++ есть способы сделать это лучше / проще / чище / современнее / безопаснее!
Забавно, когда людей беспокоят «более современные» способы решения проблемы.
@Jason, IMO более строгая безопасность типов и обработка ошибок - более современная идея по сравнению с C.
Я просмотрел другие ответы, и пока что явно нет ничего лучше / проще / чище или безопаснее. На плакате было написано, что у него есть char *. Это ограничивает степень безопасности, которую вы собираетесь получить :)
Вы можете использовать струнный поток
int str2int (const string &str) {
stringstream ss(str);
int num;
ss >> num;
return num;
}
Но это не обрабатывает никаких ошибок. Вы должны проверить поток на наличие сбоев.
Правильно, вам нужно проверить поток if ((ss >> num) .fail ()) {// ERROR}
Метод строкового потока C++ не работает для таких строк, как "12-SomeString", даже с проверкой состояния потока.
Вы можете использовать lexical_cast Boost, который оборачивает это в более общем интерфейсе.
lexical_cast<Target>(Source) выбрасывает bad_lexical_cast при неудаче.
Повышение lexical_cast происходит очень медленно и мучительно неэффективно.
@Matthieu Обновления для Boost немного улучшили производительность: boost.org/doc/libs/1_49_0/doc/html/boost_lexical_cast/… (см. Также stackoverflow.com/questions/1250795/…)
Это более безопасный способ C, чем atoi ()
const char* str = "123";
int i;
if (sscanf(str, "%d", &i) == EOF )
{
/* error */
}
C++ со стандартной библиотекой струнный поток: (спасибо 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 к исходному сообщению)
пожалуйста, обновите свою версию stringstream, включив проверку на stringstream :: fail () (в соответствии с запросом "Надежная и четкая обработка ошибок")
Ваша версия stringstream будет принимать такие вещи, как "10haha", без жалоб.
измените его на (! (ss >> num) .fail () && (ss >> ws) .eof ()) с ((ss >> num) .fail ()), если вы хотите такую же обработку, как lexical_cast
Метод C++ со стандартной библиотекой stringstream не работает для таких строк, как «12-SomeString», даже с проверкой .fail ().
C++ 11 теперь включает стандартные быстрые функции для этого
Вы можете использовать строковый поток из стандартной библиотеки C++:
stringstream ss(str);
int x;
ss >> x;
if (ss) { // <-- error handling
// use x
} else {
// not a number
}
The stream state will be set to fail if a non-digit is encountered when trying to read an integer.
См. Ловушки ручья о ловушках обработки ошибок и потоков в C++.
Метод строкового потока C++ не работает для таких строк, как «12-SomeString», даже при проверке «состояния потока».
Библиотека C++ String Toolkit (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 может иметь итераторы unsigned char *, char * или std :: string, а T должен быть подписанным int, таким как signed int, int или long
ПРЕДУПРЕЖДЕНИЕ. Эта реализация выглядит неплохо, но, насколько я могу судить, не обрабатывает переполнения.
Код не обрабатывает переполнение. v = (10 * v) + digit; напрасно переполняется вводом строки с текстовым значением INT_MIN. Таблица сомнительной ценности по сравнению с просто digit >= '0' && digit <= '9'
Думаю, эти три ссылки подводят итог:
Решения stringstream и lexical_cast примерно такие же, как при лексическом приведении с использованием stringstream.
Некоторые специализации лексического приведения используют другой подход, подробности см. В http://www.boost.org/doc/libs/release/boost/lexical_cast.hpp. Целые числа и числа с плавающей запятой теперь специализируются на преобразовании целых чисел в строковые.
Можно специализировать lexical_cast под свои нужды и сделать это быстро. Это было бы идеальное решение, удовлетворяющее все стороны, чистое и простое.
Уже упомянутые статьи показывают сравнение различных методов преобразования целых чисел <-> строк. Имеют смысл следующие подходы: old 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;
}
Вот мой первый совет: не используйте для этого строковый поток. Хотя поначалу это может показаться простым в использовании, вы обнаружите, что вам нужно проделать много дополнительной работы, если вам нужна надежность и хорошая обработка ошибок.
Вот подход, который интуитивно кажется, что он должен работать:
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 оставляет желать лучшего, если вам нужна надежность и понятная обработка ошибок.
Это подводит меня ко второму совету: не используйте для этого Boost lexical_cast. Подумайте, что говорится в документации lexical_cast:
Where a higher degree of control is required over conversions, std::stringstream and std::wstringstream offer a more appropriate path. Where non-stream-based conversions are required, lexical_cast is the wrong tool for the job and is not special-cased for such scenarios.
Какие?? Мы уже видели, что stringstream имеет плохой уровень контроля, и все же он говорит, что stringstream следует использовать вместо lexical_cast, если вам нужен «более высокий уровень контроля». Кроме того, поскольку lexical_cast - это просто оболочка для stringstream, он страдает теми же проблемами, что и stringstream: плохая поддержка множественных числовых баз и плохая обработка ошибок.
К счастью, кто-то уже решил все вышеперечисленные проблемы. Стандартная библиотека C содержит 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 не является потокобезопасным из-за использования глобальных переменных для состояния ошибки.
@JamesDunne: POSIX требует, чтобы strtol был потокобезопасным. POSIX также требует, чтобы errno использовал локальное хранилище потока. Даже в системах, отличных от POSIX, почти все реализации errno в многопоточных системах используют локальное хранилище потока. Последний стандарт C++ требует, чтобы errno был совместим с POSIX. Последний стандарт C также требует, чтобы errno имел локальное хранилище потоков. Даже в Windows, которая определенно не совместима с POSIX, errno является потокобезопасным, и, соответственно, strtol.
Я действительно не могу понять ваши доводы против использования boost :: lexical_cast. Как говорится, std :: stringstream действительно предлагает большой контроль - вы делаете все, от проверки ошибок до определения собственной базы. Текущая документация формулирует это так: «Для более сложных преобразований, например, когда точность или форматирование требует более жесткого контроля, чем это предлагается по умолчанию для lexical_cast, рекомендуется стандартный подход std :: stringstream».
Обратите внимание, что идентификаторы OVERFLOW и UNDERFLOW используются для макросов gcc (и, следовательно, g++) для совместимости с System V. Чтобы отключить расширения стандарта, такие как этот, передайте -ansi в g++ в командной строке или в make-файле. sourceware.org/bugzilla/show_bug.cgi?id=5407gcc.gnu.org/onlinedocs/gcc-4.7.1/gcc/C-Dialect-Options.html
Последующие действия: отчет об ошибке по первой ссылке указывает, что -D_ISOC99_SOURCE отключит расширение, но вторая ссылка говорит, что -ansi отключит все. Я отправил слишком рано и еще не начал работать.
Я использую Mac OS X 10.6 (llvm-gcc 4.2), и мне пришлось определить _POSIX_C_SOURCE, чтобы отключить расширение. Скорее всего, в других системах в этом нет необходимости.
Это неуместное кодирование C в C++. Стандартная библиотека для этого содержит std::stol, который будет генерировать исключения, а не возвращать константы.
@fuzzyTew Я написал этот ответ еще до того, как std::stol был добавлен в язык C++. Тем не менее, я не думаю, что будет справедливо сказать, что это «кодирование C в C++». Глупо говорить, что std::strtol - это кодировка C, когда она явно является частью языка C++. Мой ответ идеально подходил к C++, когда он был написан, и до сих пор применяется даже с новым std::stol. Вызов функций, которые могут вызывать исключения, не всегда лучше для каждой ситуации программирования.
@DanMoulding, правда, я не понимал, когда писал, что stol был только на C++ 11. Однако исключения - это стандартный способ обработки исключительных условий в C++. Возврат констант ошибок - это парадигма C, которая обычно не одобряется и является неожиданной в C++, добавляя барьеры для отладки и совместного использования кода и распространяя визуально раздутые условные проверки. Это необходимо в C, потому что в C нет исключений. Возвращаемые значения часто компилируются в более быстрый код, поэтому может быть лучше, если код профилирован так, чтобы иметь узкое место вокруг многих исключительных условий.
Не уверен, насколько распространен C++ 11, но в качестве идиоматического способа C++ синтаксического анализа int я собираюсь переключить свой принятый ответ на std::stol.
@fuzzyTew: Недостаточно места на диске - исключительное условие. Файлы с неверно отформатированными данными, созданные компьютером, являются исключением. Но опечатки при вводе пользователем не являются исключением. Хорошо иметь подход к синтаксическому анализу, который может обрабатывать обычные, неисключительные сбои синтаксического анализа.
@Grault и @DanMoulding, есть ли причина не помещать квалифицирующий «тег» перед каждым из счетчиков STR2INT_ERROR (например, enum STR2INT_ERROR { S2I_SUCCESS, S2I_OVERFLOW, S2I_UNDERFLOW, S2I_INCONVERTIBLE };? Разве это не было бы более простым решением конфликта?
@rtmh Да. Если я правильно помню свое душевное состояние, я был расстроен из-за того, что Apple загрязнила пространство макросов, не сделав что-то подобное в своем расширении. Как вы предположили, беспрепятственное размещение имен макросов, вероятно, было бы наиболее практичным.
@Grault Забавно, что вы упомянули яблоко, потому что я тоже столкнулся с ним с MSVS. Действительно утомительно, ха-ха.
Если вызывающий код использует errno для распознавания ошибок, изменение кода на if ((errno == ERANGE && l == LONG_MAX) || l > INT_MAX) { errno = ERANGE; return OVERFLOW; } потребует небольшого количества дополнительного кода, чтобы приспособиться к этому.
Вы присваиваете errno значение 0, но я не вижу другого места, где бы это было присвоено.
@Yankee Большинство функций библиотеки C возвращают специальное значение, например -1, чтобы указать, что произошла ошибка. Как только вы узнаете, что произошла ошибка, все, что вам нужно сделать, это проверить errno, чтобы узнать код ошибки. Но ни одна функция библиотеки C не может установить errno в 0. И нет возвращаемых значений из strtol, которые сами по себе указывают на то, что strtol вернул ошибку. Таким образом, только errno необходимо использовать, чтобы определить, произошла ли ошибка. Поскольку strtol не может установить errno в 0 в случае успеха, errno необходимо явно установить в 0 перед вызовом strtol, а затем проверить, остается ли он по-прежнему равным 0, для обнаружения ошибок.
В новом 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, который, если не равен null, устанавливается на первый непреобразованный символ.
Да, используя второй параметр std :: stoi, вы можете обнаружить недопустимый ввод. Однако вам все равно придется свернуть свою собственную функцию преобразования ...
Как и в принятом ответе, но с этими стандартными функциями, которые были бы намного чище, imo
Имейте в виду, что второй аргумент в этих функциях может использоваться, чтобы определить, была ли преобразована вся строка или нет. Если результирующий size_t не равен длине строки, то он преждевременно остановился. В этом случае он все равно вернет 11, но pos будет равен 2 вместо длины строки 3. coliru.stacked-crooked.com/a/cabe25d64d2ffa29
Если у вас C++ 11, подходящими решениями в настоящее время являются функции преобразования целых чисел C++ в <string>: stoi, stol, stoul, stoll, stoull. Они выдают соответствующие исключения при неправильном вводе и используют быстрые и небольшие функции strto* под капотом.
Если вы застряли с более ранней версией C++, с вашей стороны было бы легко имитировать эти функции в своей реализации.
В C вы можете использовать int atoi (const char * str),
Анализирует C-строку str, интерпретируя ее содержимое как целое число, которое возвращается как значение типа int.
Поскольку я связался с atoi в вопросе, я в курсе. Вопрос явно не в C, а в C++. -1
Мне нравится Ответ Дэна Молдинга, я просто добавлю к нему немного стиля 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».
NB: эта функция допускает начальные пробелы, но не конечные пробелы, т.е. to_int(" 123") возвращает 123, а to_int("123 ") выдает исключение. Убедитесь, что это приемлемо для вашего варианта использования, или измените код.
Такая функция могла бы быть частью STL ...
Вы можете использовать этот определенный метод.
#define toInt(x) {atoi(x.c_str())};
И если бы вам нужно было преобразовать из String в Integer, вы бы просто сделали следующее.
int main()
{
string test = "46", test2 = "56";
int a = toInt(test);
int b = toInt(test2);
cout<<a+b<<endl;
}
Результат будет 102.
я не знаю. Написание макроса определения для atoi не похоже на «способ C++» в свете других ответов, таких как принятый std::stoi().
Мне интереснее использовать предопределенные методы: P
Я знаю, что это более старый вопрос, но я сталкивался с ним много раз и до сих пор не нашел хорошо шаблонного решения со следующими характеристиками:
Итак, вот мой, с тестовым ремешком. Поскольку он использует функции 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, поэтому предпочитаю последний. Это гарантирует, что при изменении типа «а» не будут получены плохие результаты. Я бы хотел, чтобы компилятор мог понять это так:
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.
Мне нравится Ответ Дэна, особенно из-за избегания исключений. Для разработки встроенных систем и других систем низкого уровня может быть недоступна надлежащая структура исключений.
Добавлена проверка пробелов после действительной строки ... эти три строки
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;
}
@chux добавил код, чтобы решить упомянутые вами проблемы.
1) По-прежнему не удается обнаружить ошибку с вводом вроде " ". strtol() не указывается для установки errno, когда преобразование не происходит. Лучше использовать if (s == end) return INCONVERTIBLE;, чтобы не обнаруживать преобразования. И тогда if (*s == '\0' || *end != '\0') можно упростить до if (*end) 2) || l > LONG_MAX и || l < LONG_MIN не служат никакой цели - они никогда не верны.
@chux На Mac errno устанавливается для ошибок синтаксического анализа, но в Linux errno не устанавливается. Изменен код, чтобы он зависел от указателя «конца» для обнаружения этого.
Я знаю три способа преобразования String в 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 ;
}
Начиная с 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;
}
}
В качестве бонуса он также может обрабатывать другие основы, например шестнадцатеричные.
Как насчет некоторых примеров из следующего: codeproject.com/KB/recipes/Tokenizer.aspx Они очень эффективны и несколько элегантны.