В этом ответе я собираюсь предположить, что вы читаете и интерпретируете строки текста . Возможно, вы предлагаете пользователю, который что-то печатает и нажимает 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's17 различными неприятностями, я возьму это одно неудобство 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не плохо распознает конечный нечисловой текст.