Как разобрать строку в int в C ++? - PullRequest
253 голосов
/ 11 октября 2008

Что такое C ++ способ парсинга строки (заданной как char *) в int? Надежная и понятная обработка ошибок является плюсом (вместо возвращается ноль ).

Ответы [ 17 ]

197 голосов
/ 27 мая 2011

Чего не делать

Вот мой первый совет: не используйте stringstream для этого . Хотя на первый взгляд это может показаться простым в использовании, вы обнаружите, что вам нужно проделать большую дополнительную работу, если вы хотите надежной работы и хорошей обработки ошибок.

Вот подход, который интуитивно кажется, что он должен работать:

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) перед попыткой преобразования. Но это означает, что вызывающий абонент должен знать a priori , на каком основании находится номер - и как вызывающий абонент может это знать? Звонящий еще не знает, что это за номер. Они даже не знают, что это это число! Откуда они могут знать, что это за база? Мы могли бы просто указать, что все числа, вводимые в наши программы, должны быть основанием 10 и отклонять шестнадцатеричный или восьмеричный ввод как недействительный. Но это не очень гибко или надежно. Простого решения этой проблемы не существует. Вы не можете просто попробовать преобразование один раз для каждой базы, потому что десятичное преобразование всегда будет успешным для восьмеричных чисел (с начальным нулем), и восьмеричное преобразование может быть успешным для некоторых десятичных чисел. Так что теперь вы должны проверить на ведущий ноль. Но ждать! Шестнадцатеричные числа также могут начинаться с начального нуля (0x ...). Вздох.

Даже если вам удастся справиться с вышеуказанными проблемами, есть еще одна большая проблема: что, если вызывающему абоненту необходимо различать неверный ввод (например, «123foo») и число, которое выходит за пределы int (например, «4000000000» для 32-разрядных int)? С stringstream нет никакого способа сделать это различие. Мы только знаем, было ли преобразование успешным или неудачным. Если это не удается, мы не можем знать , почему это не удалось. Как видите, stringstream оставляет желать лучшего, если вам нужна надежность и четкая обработка ошибок.

Это приводит меня ко второму совету: не используйте Boost's lexical_cast для этого . Подумайте, что говорит документация lexical_cast:

Где более высокая степень контроля требуется более конверсии, std :: stringstream и std :: wstringstream предлагает больше соответствующий путь. куда не основанные на потоке преобразования требуется, lexical_cast неправильный инструмент для работы и не специально для таких сценариев.

Что ?? Мы уже видели, что 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)
  • Получается наименьший размер кода

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

154 голосов
/ 06 июля 2012

В новом C ++ 11 есть функции для этого: Stoi, Stol, Stoll, Stoul и т. Д.

int myNr = std::stoi(myString);

Будет выдано исключение при ошибке преобразования.

Даже у этих новых функций все еще есть та же проблема , как отметил Дэн: они с радостью преобразуют строку «11x» в целое число «11».

Подробнее: http://en.cppreference.com/w/cpp/string/basic_string/stol

67 голосов
/ 11 октября 2008

Это более безопасный способ 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;
}

С boost library: (спасибо 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
}

Редактировать: Исправлена ​​версия stringstream, так что она обрабатывает ошибки. (благодаря комментариям CMS и jk к исходному сообщению)

22 голосов
/ 11 октября 2008

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

21 голосов
/ 11 октября 2008

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

16 голосов
/ 11 октября 2008

Вы можете использовать поток строк из стандартного библиотеки C ++:

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

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

Состояние потока будет установлено как сбойное если встречается не цифра, когда пытается прочитать целое число.

См. Потоковые ловушки для ловушек обработки ошибок и потоков в C ++.

10 голосов
/ 11 октября 2008

Вы можете использовать stringstream's

int str2int (const string &str) {
  stringstream ss(str);
  int num;
  ss >> num;
  return num;
}
7 голосов
/ 01 декабря 2010

Я думаю, что эти три ссылки суммируют:

Решения 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;
}
7 голосов
/ 29 августа 2009

Библиотека 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 может быть итераторами без знака char *, char * или std :: string, и ожидается, что T будет со знаком int, например, со знаком int, int или long

6 голосов
/ 01 августа 2013

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...