В этом ответе я собираюсь предположить, что вы читаете и интерпретируете строки текста . Возможно, вы предлагаете пользователю, который что-то печатает и нажимает RETURN. Или, возможно, вы читаете строки структурированного текста из какого-либо файла данных.
Поскольку вы читаете строки текста, имеет смысл организовать ваш код вокруг библиотечной функции, которая читает, ну, в общем, строку текста. Стандартная функция есть fgets()
, хотя есть и другие (в том числе getline
). И затем следующий шаг - как-то интерпретировать эту строку текста.
Вот основной рецепт для вызова, fgets
чтобы прочитать строку текста:
char line[512];
printf("type something:\n");
fgets(line, 512, stdin);
printf("you typed: %s", line);
Это просто читает в одной строке текста и печатает его обратно. Как написано, у него есть пара ограничений, которые мы получим через минуту. У него также есть очень хорошая особенность: число 512, которое мы передали в качестве второго аргумента, fgets
является размером массива, в который
line
мы просим fgets
прочитать. Этот факт - то, что мы можем сказать, fgets
сколько ему разрешено читать, - означает, что мы можем быть уверены, что fgets
массив не переполнится, если читать слишком много в него.
Итак, теперь мы знаем, как читать строку текста, но что, если мы действительно хотим прочитать целое число, или число с плавающей запятой, или один символ, или одно слово? (То есть, что делать , если
scanf
вызов мы пытаемся улучшить был использованием спецификатора формата , как %d
, %f
, %c
или %s
?)
Легко переосмыслить строку текста - строку - как любую из этих вещей. Чтобы преобразовать строку в целое число, самый простой (хотя и несовершенный) способ сделать это - вызвать atoi()
. Чтобы преобразовать в число с плавающей точкой, есть atof()
. (И есть и лучшие способы, как мы увидим через минуту.) Вот очень простой пример:
printf("type an integer:\n");
fgets(line, 512, stdin);
int i = atoi(line);
printf("type a floating-point number:\n");
fgets(line, 512, stdin);
float f = atof(line);
printf("you typed %d and %f\n", i, f);
Если вы хотите, чтобы пользователь вводил один символ (возможно, y
или
n
как ответ «да / нет»), вы можете буквально просто захватить первый символ строки, например так:
printf("type a character:\n");
fgets(line, 512, stdin);
char c = line[0];
printf("you typed %c\n", c);
(Это, конечно, игнорирует возможность того, что пользователь набрал многосимвольный ответ; он тихо игнорирует любые дополнительные символы, которые были набраны.)
Наконец, если вы хотите, чтобы пользователь набрал строку, определенно не содержащую пробелов, если вы хотите обработать строку ввода
hello world!
поскольку за строкой "hello"
следует что-то еще (что и сделал бы scanf
формат %s
), ну, в этом случае, я немного выдумал, в конце концов, не так просто интерпретировать строку таким образом, так что ответ на этот вопрос Часть вопроса придется немного подождать.
Но сначала я хочу вернуться к трем вещам, которые я пропустил.
(1) мы звонили
fgets(line, 512, stdin);
читать в массив line
, и где 512 - размер массива, line
поэтому fgets
знает, что не переполнить его. Но чтобы убедиться, что 512 - это правильное число (особенно, чтобы проверить, возможно, кто-то подправил программу, чтобы изменить размер), вы должны вернуться туда, где line
было объявлено. Это неприятно, поэтому есть два гораздо лучших способа синхронизировать размеры. Вы можете, (а) использовать препроцессор, чтобы сделать имя для размера:
#define MAXLINE 512
char line[MAXLINE];
fgets(line, MAXLINE, stdin);
Или (б) используйте sizeof
оператор С:
fgets(line, sizeof(line), stdin);
(2) Вторая проблема заключается в том, что мы не проверяли ошибки. Когда вы читаете ввод, вы всегда должны проверять возможность ошибки. Если по какой-либо причине fgets
не удается прочитать строку текста, к которой вы его просили, это указывает на это, возвращая нулевой указатель. Таким образом, мы должны были делать такие вещи, как
printf("type something:\n");
if(fgets(line, 512, stdin) == NULL) {
printf("Well, never mind, then.\n");
exit(1);
}
Наконец, существует проблема, заключающаяся в том, что для того, чтобы прочитать строку текста, прочитать
fgets
символы и заполнить их в вашем массиве, пока не будет найден \n
символ, заканчивающий строку, и он также заполнит \n
символ в вашем массиве . Это можно увидеть, если немного изменить наш предыдущий пример:
printf("you typed: \"%s\"\n", line);
Если я запускаю это и набираю «Стив», когда он мне подсказывает, он печатает
you typed: "Steve
"
Это "
во второй строке, потому что строка, которую он прочитал и распечатал, была на самом деле "Steve\n"
.
Иногда этот дополнительный символ новой строки не имеет значения (например, когда мы звонили
atoi
или atof
, поскольку они оба игнорируют любой дополнительный нечисловой ввод после числа), но иногда это имеет большое значение. Так часто мы хотим убрать эту новую строку. Есть несколько способов сделать это, к которым я вернусь через минуту. (Я знаю, что много говорил. Но я вернусь ко всем этим вещам, обещаю.)
В этот момент вы можете подумать: «Я думал, что вы сказали, что scanf
это не хорошо, и этот другой способ был бы намного лучше. Но fgets
он начинает выглядеть как неприятность. Звонить scanf
было так легко ! Разве я не могу продолжать его использовать? "
Конечно, вы можете продолжать использовать scanf
, если хотите. (И для действительно
простых вещей, в некотором смысле, это проще.) Но, пожалуйста, не приходите ко мне плакать, когда он подведет вас из-за одной из 17 его причуд и слабостей, или войдет в бесконечный цикл из-за ввода вашего не ожидал, или когда вы не можете понять, как использовать это, чтобы сделать что-то более сложное. И давайте посмотрим на fgets
реальные неприятности:
Вы всегда должны указывать размер массива. Ну, конечно, это совсем не неприятность - это особенность, потому что переполнение буфера - это действительно плохо.
Вы должны проверить возвращаемое значение. На самом деле, это стирка, потому что для scanf
правильного использования , вы также должны проверить его возвращаемое значение.
Вы должны раздеться \n
. Это, я признаю, настоящая неприятность. Хотелось бы, чтобы была стандартная функция, на которую я мог бы указать, у которой не было этой маленькой проблемы. (Пожалуйста, никто не воспитывает gets
.) Но по сравнению с scanf's
17 различными неприятностями, я возьму это одно неудобство fgets
любого дня.
Так как же раздеть эту новую строку? Три способа:
(а) Очевидный способ:
char *p = strchr(line, '\n');
if(p != NULL) *p = '\0';
(б) хитрый и компактный способ:
strtok(line, "\n");
К сожалению, этот не всегда работает.
(c) Еще один компактный и слегка неясный способ:
line[strcspn(line, "\n")] = '\0';
И теперь, когда это не так, мы можем вернуться к другой вещи, которую я пропустил: недостатки atoi()
и atof()
. Проблема в том, что они не дают вам полезного указания на успех или неудачу: они тихо игнорируют завершающий нечисловой ввод и спокойно возвращают 0, если числовой ввод вообще отсутствует. Предпочтительными альтернативами, которые также имеют определенные другие преимущества, являются strtol
и strtod
.
strtol
также позволяет использовать базу, отличную от 10, то есть вы можете получить эффект (среди прочего) %o
или %x
сscanf
, Но показ того, как правильно использовать эти функции, сам по себе является историей, и это слишком отвлекает от того, что уже превращается в довольно фрагментированный рассказ, так что я не буду сейчас говорить о них больше.
Остальная часть основного повествования касается ввода, который вы, возможно, пытаетесь разобрать, это более сложный, чем просто одно число или символ. Что если вы хотите прочитать строку, содержащую два числа, или несколько слов, разделенных пробелами, или специальную пунктуацию? Вот где все становится интересным, и где, возможно, все усложняется, если вы пытаетесь сделать что-то с помощью scanf
, и где теперь гораздо больше вариантов, когда вы чисто прочитали одну строку текста, используя fgets
, хотя полная история всех этих вариантов Возможно, мы могли бы заполнить книгу, поэтому мы только сможем здесь поцарапать поверхность.
Моя любимая техника - разбить строку на «слова», разделенные пробелами, а затем сделать что-то еще с каждым «словом». Одной из основных стандартных функций для этого является
strtok
(которая также имеет свои проблемы и которая также оценивает отдельное обсуждение). Мое собственное предпочтение - выделенная функция для построения массива указателей на каждое разбитое на части «слово», функция, которую я описываю в
этих заметках к курсу . Во всяком случае, как только вы получили «слова», вы можете дополнительно обработать каждый из них, возможно , с теми же atoi
/ atof
/ strtol
/ strtod
функциями мы уже смотрели.
Как это ни парадоксально, даже несмотря на то, что мы потратили немало времени и усилий, чтобы выяснить, как отойти от этого scanf
, еще один прекрасный способ справиться с только что прочитанной строкой текста
fgets
- передать ее sscanf
. Таким образом, вы получите большинство преимуществ scanf
, но без большинства недостатков.
Если ваш входной синтаксис особенно сложен, может быть целесообразно использовать библиотеку "regexp" для его анализа.
Наконец, вы можете использовать любые специальные решения для анализа. Вы можете перемещаться по строке на символ за раз с помощью
char *
указателя, который проверяет наличие ожидаемых символов. Или вы можете искать конкретные символы, используя такие функции, как strchr
или strrchr
, или strspn
или strcspn
, или strpbrk
. Или вы можете анализировать / преобразовывать и пропускать группы цифровых символов, используя функции strtol
или,
strtod
которые мы пропустили ранее.
Очевидно, можно сказать гораздо больше, но, надеюсь, это введение поможет вам начать.
(r = sscanf("1 2 junk", "%d%d", &x, &y)) != 2
не плохо распознает конечный нечисловой текст.