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


261

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


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

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

Ответы:


165

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

int myNr = std::stoi(myString);

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

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

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


4
Но они принимают аргументы, кроме того, что один из них является точкой для size_t, которая, если не ноль, установлена ​​на первый не
преобразованный

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

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

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

204

Что не делать

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

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

Это приводит меня ко второму совету: не используйте Boost 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 (и семью):

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


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

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

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

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

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

67

Это более безопасный способ, чем atoi ()

const char* str = "123";
int i;

if(sscanf(str, "%d", &i)  == EOF )
{
   /* error */
}

C ++ со стандартной библиотекой stringstream : (спасибо CMS )

int str2int (const string &str) {
  stringstream ss(str);
  int num;
  if((ss >> num).fail())
  { 
      //ERROR 
  }
  return num;
}

С буст библиотеки: (спасибо JK )

#include <boost/lexical_cast.hpp>
#include <string>

try
{
    std::string str = "123";
    int number = boost::lexical_cast< int >( str );
}
catch( const boost::bad_lexical_cast & )
{
    // Error
}

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


1
пожалуйста, обновите свою версию stringstream, чтобы включить проверку для stringstream :: fail () (по запросу опрашивающего «Надежная и понятная обработка ошибок»)
jk.

2
Ваша струнная версия будет принимать такие вещи, как "10haha", не жалуясь
Йоханнес Шауб - Litb

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

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

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

21

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


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

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

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

6
Я посмотрел на другие ответы, и до сих пор нет ничего лучше, легче, чище или безопаснее. На плакате говорилось, что у него есть символ *. Это ограничивает уровень безопасности, который вы собираетесь получить :)
Крис Аргин

21

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


12
Boost lexical_cast чрезвычайно медленный и мучительно неэффективный.

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

16

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

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

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

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

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


2
Строковый метод C ++ не работает для таких строк, как «12-SomeString», даже с проверкой «состояния потока».
captonssj

10

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

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

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

1
Право, вы должны проверить поток, если ((ss >> num) .fail ()) {// ОШИБКА}
CMS

2
Строковый метод C ++ не работает для таких строк, как «12-SomeString», даже с проверкой «состояния потока»
captonssj

8

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

Решения 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

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


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

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

6

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

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


6

Начиная с 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;
    }
}

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


3

Мне нравится ответ Дана Молдинга , я просто добавлю немного стиля 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этого работает в pre-C ++ 11. Кроме того, в отличие std::stoi, boost::lexical_castиstringstream он генерирует исключения для странных строк, таких как «123hohoho».

NB. Эта функция допускает начальные пробелы, но не конечные пробелы, т.е. to_int(" 123")возвращает 123, а to_int("123 ")выбрасывает исключение. Убедитесь, что это приемлемо для вашего варианта использования или скорректируйте код.

Такая функция может быть частью STL ...


2

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

}

1

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

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

    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

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

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

0

Вы можете использовать этот определенный метод.

#define toInt(x) {atoi(x.c_str())};

И если бы вам пришлось преобразовать строку в целое число, вы бы просто сделали следующее.

int main()
{
string test = "46", test2 = "56";
int a = toInt(test);
int b = toInt(test2);
cout<<a+b<<endl;
}

Выход будет 102.


4
ИДК. Написание макроса определения atoiне похоже на «путь C ++» в свете других ответов, таких как принятый std::stoi().
Евгений Йокота

Я нахожу это более забавным, используя предопределенные методы: P
Борис

0

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

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

Итак, вот мой, с тестовым ремешком. Поскольку он использует функции 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с самым большим длиной типа (длинный / без знака долго долго) , доступного для компилятора; это позволяет выполнить максимальное преобразование. Затем, если это необходимо, StringToSigned/ StringToUnsignedвыполняет окончательную проверку диапазона базового типа. Наконец, метод конечной точки,StringToDecimal решает, какой из шаблонных методов StringTo * вызывать на основе подписи нижележащего типа.

Я думаю, что большая часть мусора может быть оптимизирована компилятором; почти все должно быть детерминированным во время компиляции. Любые комментарии по этому аспекту были бы мне интересны!


"использовать самый большой тип" -> почему long longвместо intmax_t?
chux - Восстановить Монику

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

-3

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

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


2
Как я упоминал atoiв этом вопросе, я знаю об этом. Вопрос явно не о C, а о C ++. -1
Евгений Йокота
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.