Як розібрати рядок до int в C ++?


261

Який спосіб C ++ аналізує рядок (заданий як char *) у int? Надійна та чітка обробка помилок - це плюс (замість повернення нуля ).


21
Як щодо деяких прикладів з наступного: codeproject.com/KB/recipes/Tokenizer.aspx Вони дуже ефективні та дещо елегантні

@ Bee 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
Zharf

Майте на увазі, що другий аргумент у цих функціях може бути використаний для визначення того, чи була перетворена вся струна чи ні. Якщо отриманий результат size_tне дорівнює довжині рядка, він зупинився рано. У цьому випадку він все одно поверне 11, але posбуде 2 замість довжини рядка 3. coliru.stacked-crooked.com/a/cabe25d64d2ffa29
Zoe

204

Чого не робити

Ось моя перша порада: не використовуйте для цього рядок . Хоча спочатку це може здатися простим у використанні, ви виявите, що вам доведеться виконати багато зайвих робіт, якщо вам потрібно надійність і хороше поводження з помилками.

Ось підхід, який інтуїтивно здається, що він повинен працювати:

bool str2int (int &i, char const *s)
{
    std::stringstream ss(s);
    ss >> i;
    if (ss.fail()) {
        // not an integer
        return false;
    }
    return true;
}

У цьому є основна проблема: str2int(i, "1337h4x0r")радісно повернеться trueіi отримає цінність 1337. Ми можемо вирішити цю проблему, переконавшись, що stringstreamпісля конверсії більше символів не буде:

bool str2int (int &i, char const *s)
{
    char              c;
    std::stringstream ss(s);
    ss >> i;
    if (ss.fail() || ss.get(c)) {
        // not an integer
        return false;
    }
    return true;
}

Ми вирішили одну проблему, але є ще пару інших проблем.

Що робити, якщо число в рядку не є базовим 10? Ми можемо спробувати розмістити інші бази, встановивши потік у правильний режим (наприклад,ss << std::hex ), перш ніж спробувати перетворення. Але це означає, що абонент повинен апріорі знати, яке базове число - і як, можливо, той, хто телефонує, може це знати? Абонент ще не знає, що таке номер. Вони навіть не знають , що цечисло! Як від них можна очікувати, що це основа? Ми можемо просто призначити, що всі цифри, що вводяться в наші програми, повинні бути базовими 10 і відхиляти шістнадцятковий або восьмеричний введення як недійсні. Але це не дуже гнучко чи надійно. Просте рішення цієї проблеми не існує. Ви не можете просто спробувати перетворення один раз для кожної бази, тому що десяткове перетворення завжди буде успішним для восьмеричних чисел (з початковим нулем), а восьмеричне перетворення може досягти успіху для деяких десяткових чисел. Тому зараз вам доведеться перевірити провідний нуль. Але зачекайте! Шістнадцяткові числа можуть починатися і з провідного нуля (0x ...). Зітхнути.

Навіть якщо вам вдасться вирішити вищезазначені проблеми, існує ще одна більша проблема: що робити, якщо абонент повинен розрізняти поганий вхід (наприклад, "123foo") і число, яке знаходиться поза діапазоном int(наприклад, "4000000000" для 32-розрядні int)? З stringstreamцим способом зробити це не можна. Ми лише знаємо, вдалося чи не вдалося перейти. Якщо це не вдасться, ми не можемо знати, чому він не вдався. Як бачимо, stringstreamзалишається бажати кращого, якщо ви хочете отримати надійність та чітку обробку помилок.

Це підводить мене до моєї другої поради: не використовуйте lexical_castдля цього Boost . Розглянемо, що lexical_castмає відповідати документація:

Там, де потрібен більш високий ступінь контролю над перетвореннями, std :: stringstream і std :: wstringstream пропонують більш відповідний шлях. Там, де потрібні конверсії на основі потоку, lexical_cast є неправильним інструментом для роботи та не застосовується для таких сценаріїв.

Що?? Ми вже бачили, що він stringstreamмає низький рівень контролю, але він говорить, що його stringstreamслід використовувати замість того, lexical_castякщо вам потрібен "більш високий рівень контролю". Крім того, оскільки lexical_castце лише обгортка навколо stringstream, вона страждає від тих же проблем, що stringstreamі: погана підтримка декількох баз чисел і погана обробка помилок.

Найкраще рішення

На щастя, хтось уже вирішив усі перераховані вище проблеми. Стандартна бібліотека С містить strtolсім'ю, яка не має жодної з цих проблем.

enum STR2INT_ERROR { SUCCESS, OVERFLOW, UNDERFLOW, INCONVERTIBLE };

STR2INT_ERROR str2int (int &i, char const *s, int base = 0)
{
    char *end;
    long  l;
    errno = 0;
    l = strtol(s, &end, base);
    if ((errno == ERANGE && l == LONG_MAX) || l > INT_MAX) {
        return OVERFLOW;
    }
    if ((errno == ERANGE && l == LONG_MIN) || l < INT_MIN) {
        return UNDERFLOW;
    }
    if (*s == '\0' || *end != '\0') {
        return INCONVERTIBLE;
    }
    i = l;
    return SUCCESS;
}

Досить просто для чогось, що обробляє всі випадки помилок, а також підтримує будь-яку базу чисел від 2 до 36. Якщо base нуль (за замовчуванням), він спробує перетворити з будь-якої бази. Або абонент може надати третій аргумент і вказати, що перетворення слід намагатись лише для певної бази. Він надійний і обробляє всі помилки з мінімальною витратою зусиль.

Інші причини віддати перевагу strtol(та сімейні):

  • Він демонструє набагато краще показники виконання
  • Він пропонує менше витрат на час збирання (інші тягнуть майже 20 разів більше SLOC із заголовків)
  • Це призводить до найменшого розміру коду

Абсолютно немає вагомих причин використовувати будь-який інший метод.


22
@JamesDunne: POSIX strtolповинен бути безпечним для потоків. POSIX також вимагає errnoвикористання локального потокового сховища. Навіть у системах, що не є POSIX, майже всі реалізації errnoбагатопотокових систем використовують локальне зберігання. Останній C ++ стандарт errnoповинен відповідати POSIX. Останній стандарт С також вимагає 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

Це безпечніший спосіб C, ніж atoi ()

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

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

C ++ з використанням стандартної бібліотеки stringstream : (спасибі CMS )

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

З збільшеною бібліотекою: (спасибі jk )

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

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

Редагувати: виправлена ​​версія потокової версії, щоб вона обробляла помилки. (завдяки коментарям CMS та jk щодо оригінальної публікації)


1
будь ласка, оновив версію stringstream, щоб включити чек на stringstream :: fail () (як того вимагає запитувач "Надійна та чітка робота з помилками")
jk.

2
Ваша строкова версія прийме такі речі, як "10haha", не скаржившись
Йоханнес Шауб - litb

3
змініть його на (! (ss >> num) .fail () && (ss >> ws) .eof ()) з ((ss >> num) .fail ()), якщо ви хочете виконати таку ж обробку, як lexical_cast
Johannes Schaub - ліб

3
C ++ зі стандартним методом streamstream бібліотеки не працює для рядків, таких як "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
Підвищення 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

Ви можете використовувати рядкові потоки

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 ++ Рядок Toolkit Library (StrTk) має наступне рішення:

static const std::size_t digit_table_symbol_count = 256;
static const unsigned char digit_table[digit_table_symbol_count] = {
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xFF - 0x07
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x08 - 0x0F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x10 - 0x17
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x18 - 0x1F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x20 - 0x27
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x28 - 0x2F
   0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // 0x30 - 0x37
   0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x38 - 0x3F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x40 - 0x47
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x48 - 0x4F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x50 - 0x57
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x58 - 0x5F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x60 - 0x67
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x68 - 0x6F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x70 - 0x77
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x78 - 0x7F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x80 - 0x87
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x88 - 0x8F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x90 - 0x97
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x98 - 0x9F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xA0 - 0xA7
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xA8 - 0xAF
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xB0 - 0xB7
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xB8 - 0xBF
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xC0 - 0xC7
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xC8 - 0xCF
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xD0 - 0xD7
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xD8 - 0xDF
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xE0 - 0xE7
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xE8 - 0xEF
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xF0 - 0xF7
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF  // 0xF8 - 0xFF
 };

template<typename InputIterator, typename T>
inline bool string_to_signed_type_converter_impl_itr(InputIterator begin, InputIterator end, T& v)
{
   if (0 == std::distance(begin,end))
      return false;
   v = 0;
   InputIterator it = begin;
   bool negative = false;
   if ('+' == *it)
      ++it;
   else if ('-' == *it)
   {
      ++it;
      negative = true;
   }
   if (end == it)
      return false;
   while(end != it)
   {
      const T digit = static_cast<T>(digit_table[static_cast<unsigned int>(*it++)]);
      if (0xFF == digit)
         return false;
      v = (10 * v) + digit;
   }
   if (negative)
      v *= -1;
   return true;
}

InputIterator може бути або непідписаним char *, char * або std :: ітераторами рядків, і T, як очікується, буде підписаним int, таким як підписаний int, int або long


1
ПОПЕРЕДЖЕННЯ Ця реалізація виглядає чудово, але не обробляє переповнення, наскільки я можу сказати.
Vinnie Falco

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цього, працює у попередньому C ++ 11. Крім того, в відміну std::stoi, boost::lexical_castіstringstream він кидає винятки для дивних рядків типу "123hohoho".

Примітка: Ця функція переносить провідні пробіли, але не проміжки, тобто to_int(" 123")повертає 123 під час to_int("123 ")виключення. Переконайтесь, що це прийнятно для вашого випадку використання або відкоригуйте код.

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


2

Я знаю три способи перетворення рядка в 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 додав код, щоб вирішити проблеми, які ви згадали.
пелюцид

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 не встановлено. Змінено код залежно від вказівника "кінець", щоб виявити це.
пелюцид

0

Ви можете використовувати цей визначений метод.

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

І якби ви перетворили з String в цілий, ви просто зробите наступне.

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

Вихід буде 102.


4
ідк. Написання визначеного макросу навколо atoiне схоже на "спосіб С ++", зважаючи на інші відповіді, як прийняті 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, тому віддаю перевагу останній. Це гарантує, що якщо тип 'a' зміниться, результат не отримає поганих результатів. Я хочу, щоб компілятор міг зрозуміти це так:

int a; a = StringToDecimal("100");

... але, C ++ не виводить типи повернення шаблонів, тому це найкраще, що я можу отримати.

Реалізація досить проста:

CstrtoxllWrapperзагортає і, strtoullі strtollвикликає те, що потрібно на основі підписання типу шаблону та надає додаткові гарантії (наприклад, негативний вхід заборонено, якщо його не підписано, і це гарантує, що вся рядок була перетворена).

CstrtoxllWrapperвикористовується StringToSignedі StringToUnsignedз найбільшим типом (long long / unsigned long long), доступним компілятору; це дозволяє виконати максимальне перетворення. Потім, якщо це необхідно, StringToSigned/ StringToUnsignedвиконує остаточну перевірку діапазону для базового типу. Нарешті, кінцевий метод,StringToDecimal визначає, який із методів шаблону StringTo * викликати на основі підписаного типу.

Я думаю, що більшу частину сміття може оптимізувати компілятор; майже все повинно бути детермінованим за часом збирання. Будь-який коментар до цього аспекту був би мені цікавий!


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

Ви впевнені, що хочете if (ePtr != str). Далі, використовуйте isspace((unsigned char) *ePtr)для правильної обробки негативних значень *ePtr.
chux

-3

В C ви можете використовувати int atoi (const char * str),

Розбирає рядок C-string, інтерпретуючи її вміст як цілісне число, яке повертається як значення типу int.


2
Як я посилався на atoiзапитання, я це знаю. Питання явно не про С, а про С ++. -1
Євген Йокота
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.