Как разобрать строку на int в С ++?

Каким способом C++ преобразовывает строку (заданную как char *) в int? Надежная и понятная обработка ошибок - это плюс (вместо возвращение нуля).

Как насчет некоторых примеров из следующего: codeproject.com/KB/recipes/Tokenizer.aspx Они очень эффективны и несколько элегантны.

Matthieu N. 04.11.2010 04:52

@Beh Tou Cheh, если вы думаете, что это хороший способ разобрать int, опубликуйте его в качестве ответа.

Eugene Yokota 04.11.2010 17:57

То же самое для C: stackoverflow.com/questions/7021725/…

Ciro Santilli TRUMP BAN IS BAD 12.08.2014 20:29
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
269
3
225 341
17
Перейти к ответу Данный вопрос помечен как решенный

Ответы 17

Старый добрый способ C все еще работает. Я рекомендую strtol или strtoul. Между статусом возврата и «endPtr» вы можете дать хороший диагностический вывод. Он также прекрасно обрабатывает несколько баз.

О, пожалуйста, не используйте этот старый C при программировании на C++. На C++ есть способы сделать это лучше / проще / чище / современнее / безопаснее!

jk. 11.10.2008 23:47

Забавно, когда людей беспокоят «более современные» способы решения проблемы.

J Miller 11.10.2008 23:54

@Jason, IMO более строгая безопасность типов и обработка ошибок - более современная идея по сравнению с C.

Eugene Yokota 12.10.2008 00:01

Я просмотрел другие ответы, и пока что явно нет ничего лучше / проще / чище или безопаснее. На плакате было написано, что у него есть char *. Это ограничивает степень безопасности, которую вы собираетесь получить :)

Chris Arguin 12.10.2008 05:11

Вы можете использовать струнный поток

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

Но это не обрабатывает никаких ошибок. Вы должны проверить поток на наличие сбоев.

jk. 11.10.2008 23:34

Правильно, вам нужно проверить поток if ((ss >> num) .fail ()) {// ERROR}

Christian C. Salvadó 11.10.2008 23:45

Метод строкового потока C++ не работает для таких строк, как "12-SomeString", даже с проверкой состояния потока.

captonssj 12.11.2009 18:30

Вы можете использовать lexical_cast Boost, который оборачивает это в более общем интерфейсе. lexical_cast<Target>(Source) выбрасывает bad_lexical_cast при неудаче.

Повышение lexical_cast происходит очень медленно и мучительно неэффективно.

Matthieu N. 04.11.2010 04:37

@Matthieu Обновления для Boost немного улучшили производительность: boost.org/doc/libs/1_49_0/doc/html/boost_lexical_cast/… (см. Также stackoverflow.com/questions/1250795/…)

flies 10.05.2012 19:11

Это более безопасный способ 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 () (в соответствии с запросом "Надежная и четкая обработка ошибок")

jk. 11.10.2008 23:54

Ваша версия stringstream будет принимать такие вещи, как "10haha", без жалоб.

Johannes Schaub - litb 13.11.2008 16:40

измените его на (! (ss >> num) .fail () && (ss >> ws) .eof ()) с ((ss >> num) .fail ()), если вы хотите такую ​​же обработку, как lexical_cast

Johannes Schaub - litb 13.11.2008 16:42

Метод C++ со стандартной библиотекой stringstream не работает для таких строк, как «12-SomeString», даже с проверкой .fail ().

captonssj 12.11.2009 18:20

C++ 11 теперь включает стандартные быстрые функции для этого

fuzzyTew 01.08.2013 19:55

Вы можете использовать строковый поток из стандартной библиотеки 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», даже при проверке «состояния потока».

captonssj 12.11.2009 18:29

Библиотека 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

ПРЕДУПРЕЖДЕНИЕ. Эта реализация выглядит неплохо, но, насколько я могу судить, не обрабатывает переполнения.

Vinnie Falco 29.07.2013 06:01

Код не обрабатывает переполнение. v = (10 * v) + digit; напрасно переполняется вводом строки с текстовым значением INT_MIN. Таблица сомнительной ценности по сравнению с просто digit >= '0' && digit <= '9'

chux - Reinstate Monica 30.08.2016 00:54

Думаю, эти три ссылки подводят итог:

Решения 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 (и семейство):

  • Показывает намного лучше производительность во время выполнения
  • Это приводит к меньшим накладным расходам времени компиляции (другие тянут почти в 20 раз больше SLOC из заголовков)
  • Это приводит к наименьшему размеру кода

Нет абсолютно никаких причин использовать какой-либо другой метод.

strtol не является потокобезопасным из-за использования глобальных переменных для состояния ошибки.

James Dunne 19.04.2012 04:46

@JamesDunne: POSIX требует, чтобы strtol был потокобезопасным. POSIX также требует, чтобы errno использовал локальное хранилище потока. Даже в системах, отличных от POSIX, почти все реализации errno в многопоточных системах используют локальное хранилище потока. Последний стандарт C++ требует, чтобы errno был совместим с POSIX. Последний стандарт C также требует, чтобы errno имел локальное хранилище потоков. Даже в Windows, которая определенно не совместима с POSIX, errno является потокобезопасным, и, соответственно, strtol.

Dan Moulding 20.04.2012 15:34

Я действительно не могу понять ваши доводы против использования boost :: lexical_cast. Как говорится, std :: stringstream действительно предлагает большой контроль - вы делаете все, от проверки ошибок до определения собственной базы. Текущая документация формулирует это так: «Для более сложных преобразований, например, когда точность или форматирование требует более жесткого контроля, чем это предлагается по умолчанию для lexical_cast, рекомендуется стандартный подход std :: stringstream».

fhd 06.05.2012 08:21

Обратите внимание, что идентификаторы 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

Grault 10.09.2012 09:55

Последующие действия: отчет об ошибке по первой ссылке указывает, что -D_ISOC99_SOURCE отключит расширение, но вторая ссылка говорит, что -ansi отключит все. Я отправил слишком рано и еще не начал работать.

Grault 10.09.2012 10:05

Я использую Mac OS X 10.6 (llvm-gcc 4.2), и мне пришлось определить _POSIX_C_SOURCE, чтобы отключить расширение. Скорее всего, в других системах в этом нет необходимости.

Grault 10.09.2012 12:22

Это неуместное кодирование C в C++. Стандартная библиотека для этого содержит std::stol, который будет генерировать исключения, а не возвращать константы.

fuzzyTew 01.08.2013 19:40

@fuzzyTew Я написал этот ответ еще до того, как std::stol был добавлен в язык C++. Тем не менее, я не думаю, что будет справедливо сказать, что это «кодирование C в C++». Глупо говорить, что std::strtol - это кодировка C, когда она явно является частью языка C++. Мой ответ идеально подходил к C++, когда он был написан, и до сих пор применяется даже с новым std::stol. Вызов функций, которые могут вызывать исключения, не всегда лучше для каждой ситуации программирования.

Dan Moulding 03.09.2013 00:33

@DanMoulding, правда, я не понимал, когда писал, что stol был только на C++ 11. Однако исключения - это стандартный способ обработки исключительных условий в C++. Возврат констант ошибок - это парадигма C, которая обычно не одобряется и является неожиданной в C++, добавляя барьеры для отладки и совместного использования кода и распространяя визуально раздутые условные проверки. Это необходимо в C, потому что в C нет исключений. Возвращаемые значения часто компилируются в более быстрый код, поэтому может быть лучше, если код профилирован так, чтобы иметь узкое место вокруг многих исключительных условий.

fuzzyTew 18.09.2013 16:56

Не уверен, насколько распространен C++ 11, но в качестве идиоматического способа C++ синтаксического анализа int я собираюсь переключить свой принятый ответ на std::stol.

Eugene Yokota 24.10.2013 20:32

@fuzzyTew: Недостаточно места на диске - исключительное условие. Файлы с неверно отформатированными данными, созданные компьютером, являются исключением. Но опечатки при вводе пользователем не являются исключением. Хорошо иметь подход к синтаксическому анализу, который может обрабатывать обычные, неисключительные сбои синтаксического анализа.

Ben Voigt 20.02.2015 20:24

Ваша оболочка strtol() очень похожа на BSD strtonum().

cp.engr 16.03.2016 23:17

@Grault и @DanMoulding, есть ли причина не помещать квалифицирующий «тег» перед каждым из счетчиков STR2INT_ERROR (например, enum STR2INT_ERROR { S2I_SUCCESS, S2I_OVERFLOW, S2I_UNDERFLOW, S2I_INCONVERTIBLE };? Разве это не было бы более простым решением конфликта?

rtmh 05.07.2016 18:08

@rtmh Да. Если я правильно помню свое душевное состояние, я был расстроен из-за того, что Apple загрязнила пространство макросов, не сделав что-то подобное в своем расширении. Как вы предположили, беспрепятственное размещение имен макросов, вероятно, было бы наиболее практичным.

Grault 05.07.2016 18:16

@Grault Забавно, что вы упомянули яблоко, потому что я тоже столкнулся с ним с MSVS. Действительно утомительно, ха-ха.

rtmh 05.07.2016 18:34

Если вызывающий код использует errno для распознавания ошибок, изменение кода на if ((errno == ERANGE && l == LONG_MAX) || l > INT_MAX) { errno = ERANGE; return OVERFLOW; } потребует небольшого количества дополнительного кода, чтобы приспособиться к этому.

chux - Reinstate Monica 30.08.2016 00:48

Вы присваиваете errno значение 0, но я не вижу другого места, где бы это было присвоено.

Yankee 21.06.2020 02:46

@Yankee Большинство функций библиотеки C возвращают специальное значение, например -1, чтобы указать, что произошла ошибка. Как только вы узнаете, что произошла ошибка, все, что вам нужно сделать, это проверить errno, чтобы узнать код ошибки. Но ни одна функция библиотеки C не может установить errno в 0. И нет возвращаемых значений из strtol, которые сами по себе указывают на то, что strtol вернул ошибку. Таким образом, только errno необходимо использовать, чтобы определить, произошла ли ошибка. Поскольку strtol не может установить errno в 0 в случае успеха, errno необходимо явно установить в 0 перед вызовом strtol, а затем проверить, остается ли он по-прежнему равным 0, для обнаружения ошибок.

Dan Moulding 23.06.2020 00:30
Ответ принят как подходящий

В новом 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, устанавливается на первый непреобразованный символ.

Zharf 13.10.2012 14:36

Да, используя второй параметр std :: stoi, вы можете обнаружить недопустимый ввод. Однако вам все равно придется свернуть свою собственную функцию преобразования ...

CC. 14.12.2012 00:56

Как и в принятом ответе, но с этими стандартными функциями, которые были бы намного чище, imo

Zharf 14.12.2012 02:33

Имейте в виду, что второй аргумент в этих функциях может использоваться, чтобы определить, была ли преобразована вся строка или нет. Если результирующий size_t не равен длине строки, то он преждевременно остановился. В этом случае он все равно вернет 11, но pos будет равен 2 вместо длины строки 3. coliru.stacked-crooked.com/a/cabe25d64d2ffa29

Zoe 22.06.2020 15:11

Если у вас C++ 11, подходящими решениями в настоящее время являются функции преобразования целых чисел C++ в <string>: stoi, stol, stoul, stoll, stoull. Они выдают соответствующие исключения при неправильном вводе и используют быстрые и небольшие функции strto* под капотом.

Если вы застряли с более ранней версией C++, с вашей стороны было бы легко имитировать эти функции в своей реализации.

В C вы можете использовать int atoi (const char * str),

Анализирует C-строку str, интерпретируя ее содержимое как целое число, которое возвращается как значение типа int.

Поскольку я связался с atoi в вопросе, я в курсе. Вопрос явно не в C, а в C++. -1

Eugene Yokota 01.08.2013 22:27

Мне нравится Ответ Дэна Молдинга, я просто добавлю к нему немного стиля 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().

Eugene Yokota 31.12.2014 23:37

Мне интереснее использовать предопределенные методы: P

Boris 01.01.2015 01:17

Я знаю, что это более старый вопрос, но я сталкивался с ним много раз и до сих пор не нашел хорошо шаблонного решения со следующими характеристиками:

  • Может преобразовать любую базу (и определить базовый тип)
  • Обнаруживает ошибочные данные (т.е. гарантирует, что вся строка, за исключением начальных / конечных пробелов, используется преобразованием)
  • Гарантирует, что независимо от преобразованного типа диапазон значения строки является приемлемым.

Итак, вот мой, с тестовым ремешком. Поскольку он использует функции 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?

chux - Reinstate Monica 30.08.2016 01:10

Уверен, что вам нужен if (ePtr != str). Кроме того, используйте isspace((unsigned char) *ePtr) для правильной обработки отрицательных значений *ePtr.

chux - Reinstate Monica 30.08.2016 01:14

Мне нравится Ответ Дэна, особенно из-за избегания исключений. Для разработки встроенных систем и других систем низкого уровня может быть недоступна надлежащая структура исключений.

Добавлена ​​проверка пробелов после действительной строки ... эти три строки

    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 добавил код, чтобы решить упомянутые вами проблемы.

pellucide 31.08.2016 19:26

1) По-прежнему не удается обнаружить ошибку с вводом вроде " ". strtol() не указывается для установки errno, когда преобразование не происходит. Лучше использовать if (s == end) return INCONVERTIBLE;, чтобы не обнаруживать преобразования. И тогда if (*s == '\0' || *end != '\0') можно упростить до if (*end) 2) || l > LONG_MAX и || l < LONG_MIN не служат никакой цели - они никогда не верны.

chux - Reinstate Monica 31.08.2016 19:43

@chux На Mac errno устанавливается для ошибок синтаксического анализа, но в Linux errno не устанавливается. Изменен код, чтобы он зависел от указателя «конца» для обнаружения этого.

pellucide 03.09.2016 09:03

Я знаю три способа преобразования 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;
    }
}

В качестве бонуса он также может обрабатывать другие основы, например шестнадцатеричные.

Другие вопросы по теме