Сохранить шестнадцатеричный ввод в переменную int без использования функции scanf () в C - PullRequest
0 голосов
/ 04 октября 2019

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

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

Цитата:

Бьюсь об заклад, вы сто долларов, вы видите эту проблему только тогда, когда вызов getchar () предшествует scanf ().

Не используйте scanf для интерактивных программ. Для этого есть две основные причины:

1) scanf не может восстановиться после некорректного ввода. Вы должны получить правильную строку формата каждый раз, иначе она просто отбрасывает любые входные данные, которые не могут соответствовать, и возвращает значение, указывающее на ошибку. Это может быть хорошо, если вы анализируете файл с фиксированным форматом, когда плохое форматирование в любом случае невозможно восстановить, но это полная противоположность тому, что вы хотите делать с пользовательским вводом. Используйте fgets () и sscanf (), fgets () и strtok () или напишите свои собственные пользовательские процедуры ввода, используя getchar () и putchar ().

1.5) Даже при правильном использовании scanf неизбежно отбрасывает ввод (пробел), который иногда может быть важен.

2) scanf имеет неприятную привычку оставлять перевод строки во входном потоке. Это хорошо, если вы никогда не используете ничего, кроме scanf, так как scanf обычно пропускает любые пробельные символы в своем стремлении найти то, что он ожидает дальше. Но если вы смешаете scanf с fgets / getchar, он быстро превращается в полный беспорядок, пытаясь выяснить, что может остаться или не остаться в потоке ввода. Особенно, если вы делаете какую-либо зацикливание - довольно часто входной поток на первой итерации отличается, что приводит к потенциально странной ошибке и даже более странным попыткам ее исправить.

tl; dr - scanfдля форматированного ввода. Пользовательский ввод не отформатирован. //

Вот ссылка на эту тему: https://bbs.archlinux.org/viewtopic.php?id=161294


scanf () с:

scanf("%x",integer_variable); 

кажется мне какновичок на сцене как единственный способ ввести шестнадцатеричное число с клавиатуры (или лучше сказать, файл stdin) и сохранить его в переменной int.

Есть ли другой способ ввести шестнадцатеричное значение из стандартного ввода и сохранить его в целочисленной переменной?

Дополнительная задача: Было бы также неплохо, если бы я мог записывать отрицательные значения (черезконечно, отрицательный шестнадцатеричный ввод) в переменную типа со знаком.


ИНФОРМАЦИЯ: Я прочитал здесь много тем для C на Stackoverflow о похожих проблемах, но ни один из них не отвечает на мой явный вопрос достаточно хорошо. Итак, я разместил этот вопрос.

Я работаю под Linux Ubuntu.

Ответы [ 3 ]

4 голосов
/ 04 октября 2019

Цитата о стодолларовой ставке точна. Смешивать scanf с getchar почти всегда плохая идея;это почти всегда приводит к неприятностям. Это не значит, что они не могут использоваться вместе. Можно использовать их вместе, но обычно это слишком сложно. Слишком много суетливых мелких деталей и ошибок, которые нужно отслеживать. Это больше проблем, чем стоит.

Сначала вы сказали, что

scanf () с ... %d ... кажется мне новичком на сцене, так какединственный возможный способ ввода шестнадцатеричного числа с клавиатуры

Там была некоторая путаница, потому что, конечно, %d предназначен для десятичного ввода. Но так как я написал этот ответ к тому времени, как вы исправили это, давайте сейчас перейдем к десятичному. (Также на данный момент я пропускаю проверку ошибок - то есть эти фрагменты кода не проверяют или не делают ничего изящного, если пользователь не набирает запрошенный номер.) В любом случае, вот несколько способов чтенияцелое число:

  1. scanf("%d", &integer_variable);
    Вы правы, это (поверхностно) самый простой способ.

  2. char buf[100];
    fgets(buf, sizeof(buf), stdin);
    integer_variable = atoi(buf);
    Это, я думаю, самый простой способ, который не использует scanf. Но большинство людей в наши дни недовольны использованием atoi, потому что оно не делает много полезной проверки ошибок.

  3. char buf[100];
    fgets(buf, sizeof(buf), stdin);
    integer_variable = strtol(buf, NULL, 10);
    Это почти то же самое, что и раньше, но избегает atoi в пользу предпочтительного strtol.

  4. char buf[100];
    fgets(buf, sizeof(buf), stdin);
    sscanf(buf, "%d", &integer_variable);
    Это читает строку и затем использует sscanf для ее анализа, еще один популярный и общий метод.

Все это будет работать;все они будут обрабатывать отрицательные числа. Тем не менее, важно подумать об условиях ошибки - я расскажу об этом позже.

Если вы хотите ввести шестнадцатеричные числа, методы аналогичны:

scanf("%x", &integer_variable);

char buf[100];
fgets(buf, sizeof(buf), stdin);
integer_variable = strtol(buf, NULL, 16);

char buf[100];
fgets(buf, sizeof(buf), stdin);
sscanf(buf, "%x", &integer_variable);

Все это тоже должно работать. Я бы не ожидал, что они будут обрабатывать «отрицательный шестнадцатеричный», потому что это необычное требование. В большинстве случаев шестнадцатеричная запись используется для беззнаковых целых чисел. (На самом деле, строго говоря, %x с scanf и sscanf должны использоваться с integer_variable, который был объявлен как unsigned int, а не просто int.)

Иногда этополезно или необходимо делать подобные вещи "вручную". Вот фрагмент кода, который читает ровно две шестнадцатеричные цифры. Я начну с версии, использующей getchar:

int c1 = getchar();
if(c1 != EOF && isascii(c1) && isxdigit(c1)) {
    int c2 = getchar();
    if(c2 != EOF && isascii(c2) && isxdigit(c2)) {
        if(isdigit(c1)) integer_variable = c1 - '0';
        else if(isupper(c1)) integer_variable = 10 + c1 - 'A';
        else if(islower(c1)) integer_variable = 10 + c1 - 'a';

        integer_variable = integer_variable * 16;

        if(isdigit(c2)) integer_variable += c2 - '0';
        else if(isupper(c2)) integer_variable += 10 + c2 - 'A';
        else if(islower(c2)) integer_variable += 10 + c1 - 'a';
    }
}

Как видите, это что-то вроде челюсти. Я, хотя я почти никогда не пользуюсь членами семьи scanf, это то место, где я иногда делаю, именно потому, что делать это «вручную» - это очень много работы. Вы можете значительно упростить его, используя вспомогательную функцию или макрос для преобразования цифр:

int c1 = getchar();
if(c1 != EOF && isascii(c1) && isxdigit(c1)) {
    int c2 = getchar();
    if(c2 != EOF && isascii(c2) && isxdigit(c2)) {
        integer_variable = Xctod(c1);
        integer_variable = integer_variable * 16;
        integer_variable += Xctod(c2);
    }
}

Или вы можете свернуть эти внутренние выражения до

        integer_variable = 16 * Xctod(c1) + Xctod(c2);

Они работают в терминахвспомогательной функции:

int Xctod(int c)
{
    if(!isascii(c)) return 0;
    else if(isdigit(c)) return c - '0';
    else if(isupper(c)) return 10 + c - 'A';
    else if(islower(c)) return 10 + c - 'a';
    else return 0;
}

Или, возможно, макрос (хотя это определенно старая школа):

#define Xctod(c) (isdigit(c) ? (c) - '0' : (c) - (isupper(c) ? 'A' : 'a') + 10)

Часто я разбираю шестнадцатеричные цифры, как этоне из stdin с использованием getchar(), а из строки. Часто я использую символьный указатель (char *p) для перехода по строке, что означает, что я получаю код, похожий на этот:

char c1 = *p++;
if(isascii(c1) && isxdigit(c1)) {
    char c2 = *p++;
    if(isascii(c2) && isxdigit(c2))
        integer_variable = 16 * Xctod(c1) + Xctod(c2);
}

Соблазнительно опустить временные переменные и проверку ошибоки свести это еще дальше:

integer_variable = 16 * Xctod(*p++) + Xctod(*p++);

Но не делай этого! Помимо отсутствия проверки ошибок, это выражение, вероятно, undefined , и оно определенно не всегда будет делать то, что вы хотите, потому что больше нет никакой гарантии относительно порядка, в котором вы читаете символы. Если вы знать p указывает на первую из двух шестнадцатеричных цифр, вы не хотите свернуть ее дальше, чем

integer_variable = Xctod(*p++);
integer_variable = 16 * integer_variable + Xctod(*p++);

, и даже в этом случае это будет работать только с версией функцииXctod, а не макрос, поскольку макрос оценивает свой аргумент несколько раз.


Наконец, давайте поговорим об обработке ошибок. Существует множество возможностей для беспокойства:

  1. Пользователь нажимает клавишу Return, ничего не печатая.
  2. Пользователь вводит пробел перед или после числа.
  3. пользователь вводит лишний мусор после числа.
  4. Пользователь вводит нецифровый ввод вместо числа.
  5. Код достигает конца файла;нет символов для чтения вообще.

И то, как вы их обработаете, зависит от того, какие методы ввода вы используете. Вот основные правила:

A. Если вы звоните scanf, fscanf или sscanf, всегда , проверьте возвращаемое значение. Если это не 1 (или, если у вас было несколько % спецификаторов, это не количество значений, которые вы ожидали прочитать), это означает, что что-то пошло не так. Обычно это решает проблемы 4 и 5 и изящно обрабатывает случай 2. Но это часто незаметно игнорирует проблемы 1 и 3. (В частности, scanf и fscanf обрабатывают дополнительный \n так же, как начальные пробелы.)

B. Если вы звоните fgets, опять же, всегда проверяйте возвращаемое значение. Вы получите NULL на EOF (проблема 5). Решение других проблем зависит от того, что вы делаете с прочитанной строкой.

C. Если вы звоните по номеру atoi, он будет изящно решать проблему 2, но проигнорирует проблему 3 и тихо превратит проблему 4 в число 0 (именно поэтому atoi обычно больше не рекомендуется).

D. Если вы вызываете strtol или любую другую функцию «strto», они будут корректно работать с проблемой 2, и если вы позволите им вернуть вам «указатель конца», вы можете проверить и решить проблемы 3 и4. (Обратите внимание, что я оставил обработку указателя конца из моих двух strtol примеров выше.)

E. Наконец, если вы делаете что-то нехорошее, как мой двузначный шестнадцатеричный преобразователь "хардвей", вам, как правило, приходится самостоятельно решать все эти проблемы. Если вы хотите пропустить начальные пробелы, вы должны это сделать (функция isspace из <ctype.h> может помочь), и если могут быть неожиданные нецифровые символы, вы должны также проверить их. (Это то, что делают вызовы isascii и isxdigit в моем "жестком" двухзначном шестнадцатеричном преобразователе.)

1 голос
/ 04 октября 2019

Как упоминалось в ссылке, которую вы разместили, использование fgets и sscanf - лучший способ справиться с этим. fgets прочитает полную строку текста, а sscanf проанализирует строку.

Например,

char line[100];
fgets(line, sizeof(line), stdin);

int x;
int rval = sscanf(line, "%x", &x);
if (rval == 1) {
    printf("read value %x\n", x);
} else {
    printf("please enter a hexadecimal integer\n");
}

Поскольку вы читаете только одно целое число, вы также можетеиспользуйте strtol вместо sscanf. Это также имеет то преимущество, что вы можете определить, были ли введены дополнительные символы:

char *ptr;
errno = 0;
long x = strtol(line, &ptr, 16);
if (errno) {
    perror("parsing failed");
} else if (*ptr != '\n' && *ptr != 0) {
    printf("extra characters entered: %s\n", ptr);
} else {
    printf("read value %lx\n", x);
}    
1 голос
/ 04 октября 2019

Для scanf справочной страницы, вы можете использовать scanf для считывания шестнадцатеричного числа из стандартного ввода в (целую беззнаковую) переменную.

unsigned int v ;
if ( scanf("%x", &v) == 1 ) {
   // do something with v.
}

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

...