Как правильно интерпретировать числа (hex, oct, dec)

Я пытаюсь написать программу, которая принимает входные данные - шестнадцатеричные, восьмеричные и десятичные числа, сохраняет их в целочисленных переменных и выводит их вместе с их преобразованием в десятичную форму. Например:

Пользовательский ввод: 0x43, 0123, 65

Выходы программы:

0x43 hexadecimal converts to 67 decimal
0123 octal converts to 83 decimal
65 decimal converts to 65 decimal

Поэтому, очевидно, мне нужен способ интерпретировать числа, но я не уверен, как это сделать. Я пробовал различные методы, такие как чтение их в функцию и преобразование в строку и наоборот (см. здесь для примеров кода), но интерпретация чисел всегда требует преобразования в какой-либо формат, который искажает исходный ввод.

Единственное, что я могу придумать, это перегрузить оператор >>, который считывает символ за раз, и если он видит 0x или 0 в начале ввода, он сохраняет весь ввод в строку, прежде чем он будет прочитан в int. Тогда программа каким-то образом должна была бы определять правильный манипулятор при выводе.

Не уверен, что есть более простой способ сделать это, любая помощь приветствуется.

Изменить: это было решено, но я решил опубликовать код, если кому-то интересно.

#include "std_lib_facilities.h"

void number_sys(string num, string& s)
{
  if(num[0] == '0' && (num[1] != 'x' && num[1] != 'X')) s = "octal";
  else if(num[0] == '0' && (num[1] == 'x' || num[1] == 'X')) s = "hexadecimal";
  else s = "decimal";
}

int main()
{
    cout << "Input numbers in hex, dec, or oct. Use 0xx to cancel.\n";
    string a;

    while(cin >> a){
    if(a == "0xx")break;
    string atype;
    number_sys(a, atype);

    int anum = strtol(a.c_str(), NULL, 0);

    cout << a << setw(20-a.length()) << atype << setw(20) << "converts to" << setw(10)
         << anum << setw(10) << "decimal\n";
                 }

    keep_window_open();
}

person trikker    schedule 28.07.2009    source источник
comment
Это учебное упражнение или вы просто хотите вызвать atoi()?   -  person Michael Burr    schedule 28.07.2009
comment
помечен как c, так как это простая проблема C, а не специфичная для C++.   -  person Dave DeLong    schedule 28.07.2009
comment
atoi() не работает. Он будет читать от 0x43 как строку до 43 как целое число, что неверно, я сейчас реализую решение Дейва ДеЛонга, потому что strtol учитывает базу.   -  person trikker    schedule 28.07.2009
comment
@tikker: но scanf работает, если вы используете спецификатор %i. Однако вопрос Майкла актуален: чтобы просто заставить его работать, используйте scanf, чтобы что-то узнать, сверните свой собственный.   -  person dmckee --- ex-moderator kitten    schedule 28.07.2009
comment
К сожалению, извините за путаницу с atoi() - я думал о strtol(), но мой мозг остановился на atoi(). Но мне было действительно интересно, хотите ли вы простое, готовое решение или вам нужно руководство по развертыванию собственного.   -  person Michael Burr    schedule 29.07.2009
comment
Если у меня есть инструменты для решения проблемы, я использую их. Нет смысла пытаться открутить винт молотком, когда под рукой есть отвертка. Кроме того, когда я изучаю эти новые инструменты, это просто основывается на моем знании языка. Перегрузка оператора ввода и игра с входным потоком — это просто способ создать себе головную боль. Только в случае крайней необходимости :P   -  person trikker    schedule 29.07.2009
comment
Использование правильного инструмента — это хорошо. Но понимание баз, преобразования и строковых/числовых представлений — это основные вещи, которые нужно хорошо понимать. Возможно, стоит свернуть свой собственный один раз для знаний. Я бы сделал это на простом C и пропустил бы любую перегрузку оператора, изучая этот материал.   -  person Steve Fallows    schedule 29.07.2009
comment
Я использую книгу под названием «Программирование: принципы и практика использования C++», и в конце есть глава, посвященная C, так что я доберусь до нее. Некоторое время я полностью разбирался в шестнадцатеричных, восьмеричных, десятичных и двоичных числах, а также в различиях между ними и преобразованиях между ними (я написал несколько задач преобразования, чтобы решить их самостоятельно). Не уточняйте толкование при работе с ними на входе (кроме манипуляторов). Я писал пользовательский ввод в других упражнениях, и я узнал, что ввод особенно важен.   -  person trikker    schedule 29.07.2009
comment
подвержен ошибкам, поэтому обработка после ввода, но перед выводом, как правило, предпочтительнее обработки прямо во входном потоке.   -  person trikker    schedule 29.07.2009
comment
В вашем первом выражении if ваше сравнение неверно. У вас есть оператор И, где вы хотите ИЛИ.   -  person jkeys    schedule 29.07.2009
comment
Нет... это правильно. если первый элемент равен 0, а элемент 1 не равен x и не равен X, то это восьмеричное число.   -  person trikker    schedule 29.07.2009


Ответы (5)


Взгляните на функцию strtol.

char * args[3] = {"0x43", "0123", "65"};
for (int i = 0; i < 3; ++i) {
  long int value = strtol(args[i], NULL, 0);
  printf("%s converts to %d decimal\n", args[i], value);
}

Выходы:

0x43 converts to 67 decimal
0123 converts to 83 decimal
65 converts to 65 decimal
person Dave DeLong    schedule 28.07.2009
comment
@Welbog пожимает плечами это то, что можно легко найти с помощью Google. Я должен был найти эту точную функцию пару недель назад для домашнего задания. - person Dave DeLong; 28.07.2009
comment
Это не домашнее задание. Если вы читаете мой профиль, я сам учусь по книге. - person trikker; 28.07.2009
comment
Ой. Ну, тебе следует удалить тег домашнего задания. Это действительно искажает то, как многие из нас отвечают на эти вопросы. - person Welbog; 28.07.2009
comment
Это не значит, что вы не можете избавиться от него. - person Welbog; 28.07.2009
comment
Мне трудно реализовать это, так как я еще не узнал об указателях. Кроме того, я использую строковые переменные, а не ввожу фактические строки в char * args[3] - person trikker; 28.07.2009
comment
Это сработало, реализовал это немного по-другому, но это сработало! Спасибо! (Используя c_str(), null, 0 в качестве аргументов) - person trikker; 28.07.2009
comment
@Джэни, это круто. Это упражнение для программирования, я мог бы написать Hello World на листе бумаги намного проще, чем я мог бы его запрограммировать (или я мог бы: P) - person trikker; 29.07.2009

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

std::string num;
std::cin >> num;

if (num[0] == '0' && num[1] == 'x')
{ 
  //handle
}
person jkeys    schedule 28.07.2009
comment
Функции стандартной библиотеки C (например, strtol) являются лучшим подходом. - person Quinn Taylor; 28.07.2009
comment
@Quinn: Поскольку strtol не сообщает вам, в какой базе было число, а OP хочет напечатать восьмеричное, шестнадцатеричное, десятичное число на выходе, некоторое ручное определение базы такого рода необходимо, даже если вы используете strtol для сделать конвертацию. Таким образом, handle должен означать «вывести шестнадцатеричное число, а затем вызвать strtol». - person Steve Jessop; 28.07.2009
comment
@onebyone, функция печати для базового типа уже обработана. Мне просто нужна была помощь с конвертацией. - person trikker; 28.07.2009
comment
@hooked, это все еще не имеет отношения к преобразованию строки в int, оно просто читает строку, которую я уже сделал. - person trikker; 28.07.2009

Я не уверен, есть ли способ сделать это на C++, но если вы не возражаете против небольшого C-ishness, вы можете прочитать это в массив char и использовать что-то вроде sscanf(buffer, "%i", &output). %i интерпретирует ввод как шестнадцатеричный, восьмеричный или десятичный в зависимости от его формата, как вы описываете.

Редактировать: Ах, не знал, что strtol тоже может это делать. Игнорируй меня.

person Thomas    schedule 28.07.2009
comment
Функции стандартной библиотеки C (например, strtol) являются лучшим подходом. - person Quinn Taylor; 28.07.2009
comment
Я согласен, что strtol лучше, чем sscanf, но sscanf также является функцией стандартной библиотеки C. Спецификатор формата %i тоже вполне стандартный. - person Thomas; 28.07.2009

Если вы хотите сохранить базовую информацию (hex/oct/dec), вам нужно будет хранить эту информацию отдельно от самого целочисленного значения, и вам потребуется проанализировать хотя бы первую пару символов входной строки (sscanf (), strtol() и т. д. не сохранят эту информацию для вас).

Вы можете свернуть свой собственный мини-парсер, который сохраняет входную базу и выполняет преобразование (код, который я придумал, не тестировался):

char inputStr[MAX_INPUT_LENGTH+1];
char *p;
int result = 0;
char values[128];

/**
 * This enumeration serves double duty; it keeps track of what
 * base the input was entered in, and it controls the state machine 
 * used to parse the input; from a didactic POV, this is probably bad form
 */
enum {
  eStart, 
  eHexOrOctal, 
  eOctal, 
  eDecimal, 
  eHexadecimal, 
  eError
} eBase = eStart;


/**
 * Use the values array as a table to map character constants to their corresponding 
 * integer values.  This is safer than using an expression like *p - '0', in 
 * that it can work with character encodings where digits are not consecutive.
 * Yes, this wastes a little space, but the convenience makes
 * up for it IMO.  There are probably better ways to do this.
 */
values['0'] = 0; values['1'] = 1; values['2'] = 2; values['3'] = 3; 
values['4'] = 4; values['5'] = 5; values['6'] = 6; values['7'] = 7; 
values['8'] = 8; values['9'] = 9; values['a'] = 10; values['b'] = 11; 
values['c'] = 12; values['d'] = 13; values['e'] = 14; values['f'] = 15;

/**  
 * Insert code to get input string here 
 */

for (p = inputStr; *p != 0; p++)
{
  /**
   * Cycle through each character in the input string, adjusting the state
   * of the parser as necessary.  Parser starts in the eStart state.
   */
  switch(eBase)
  {
    /**
     * Start state -- we haven't parsed any characters yet
     */
    case eStart:
      if (*p == '0') eBase = eHexOrOctal; // leading 0 means either hex or octal
      else if (isdigit(*p))
      {
        eBase = eDecimal;    // leading non-0 digit means decimal
        result = values[*p];  
      }                    
      else eBase = eError;    // no other character may start an integer constant
      break;
    /**
     * HexOrOctal -- we've read a leading 0, which could start either a hex or
     * octal constant; we need to read the second character to make a determination
     */
    case eHexOrOctal:      
      if (tolower(*p) == 'x')  base = eHexadecimal;
      else if (isdigit(*p) && *p != '8' && *p != '9')
      {
        base = eOctal;   
        result = values[*p];
      }
      else eBase = eError;
      break;
    /**
     * Octal -- we are parsing an octal constant
     */
    case eOctal:
      if (isdigit(*p) && *p != '8' && *p != '9')
      {
        result *= 8;
        result += values[*p];
      }
      else eBase = eError;
      break;
    /**
     * Decimal -- we are parsing a decimal constant
     */
    case eDecimal:
      if (isdigit(*p))
      {
        result *= 10;
        result += values[*p];
      }
      else eBase = eError;
      break;
    /**
     * Hexadecimal -- we are parsing a hex constant
     */
    case eHexadecimal:
      if (isxdigit(*p))
      {
        result *= 16;
        result += values[tolower(*p)];
      }
      else eBase = eError;
      break;
    /**
     * String is not a properly formatted integer constant in 
     * any base; once we fall into the error state, we stay there.
     */
    case eError:
    default:
      break;
  }
}
if (eBase != eError)
{
  printf("input: %s ", inputStr); fflush(stdout);
  switch(eBase)
  {
    case eOctal: printf("octal "); break;
    case eHexadecimal: printf("hexadecimal "); break
    default: break;
  }
  fflush(stdout);
  printf("converts to %d decimal\n", result);
}
else
{
  /** Print a suitable error message here */
}
person John Bode    schedule 28.07.2009

Если вам буквально нужно использовать одну целочисленную переменную для хранения всей информации, необходимой для отображения окончательного вывода, то вам придется использовать часть целочисленной переменной для хранения исходной базы, которая ввод был. В противном случае его невозможно восстановить.

person chaos    schedule 28.07.2009