Цитата о стодолларовой ставке точна. Смешивать scanf
с getchar
почти всегда плохая идея;это почти всегда приводит к неприятностям. Это не значит, что они не могут использоваться вместе. Можно использовать их вместе, но обычно это слишком сложно. Слишком много суетливых мелких деталей и ошибок, которые нужно отслеживать. Это больше проблем, чем стоит.
Сначала вы сказали, что
scanf () с ... %d
... кажется мне новичком на сцене, так какединственный возможный способ ввода шестнадцатеричного числа с клавиатуры
Там была некоторая путаница, потому что, конечно, %d
предназначен для десятичного ввода. Но так как я написал этот ответ к тому времени, как вы исправили это, давайте сейчас перейдем к десятичному. (Также на данный момент я пропускаю проверку ошибок - то есть эти фрагменты кода не проверяют или не делают ничего изящного, если пользователь не набирает запрошенный номер.) В любом случае, вот несколько способов чтенияцелое число:
scanf("%d", &integer_variable);
Вы правы, это (поверхностно) самый простой способ.
char buf[100];
fgets(buf, sizeof(buf), stdin);
integer_variable = atoi(buf);
Это, я думаю, самый простой способ, который не использует scanf
. Но большинство людей в наши дни недовольны использованием atoi
, потому что оно не делает много полезной проверки ошибок.
char buf[100];
fgets(buf, sizeof(buf), stdin);
integer_variable = strtol(buf, NULL, 10);
Это почти то же самое, что и раньше, но избегает atoi
в пользу предпочтительного strtol
.
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
, а не макрос, поскольку макрос оценивает свой аргумент несколько раз.
Наконец, давайте поговорим об обработке ошибок. Существует множество возможностей для беспокойства:
- Пользователь нажимает клавишу Return, ничего не печатая.
- Пользователь вводит пробел перед или после числа.
- пользователь вводит лишний мусор после числа.
- Пользователь вводит нецифровый ввод вместо числа.
- Код достигает конца файла;нет символов для чтения вообще.
И то, как вы их обработаете, зависит от того, какие методы ввода вы используете. Вот основные правила:
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
в моем "жестком" двухзначном шестнадцатеричном преобразователе.)