Форматированный ввод данных в Си — функция scanf
В то время как функция printf()
осуществляет форматированный вывод данных, функция scanf()
осуществляет их форматированный ввод. Это значит, что поступающие на ввод данные преобразуются соответственно указанному формату(ам) и записываются по адресу(ам) указанной(ых) переменной(ых):
scanf(строка_формата, адреса_переменных);
Причина, по которой в scanf()
передаются адреса, а не значения переменных, очевидна. Функция scanf()
должна изменять значения переменных тех функций, из которых вызывается. Единственный способ — это получить адреса областей памяти.
Спецификации, допустимые в строке формата, для scanf()
почти идентичны тем, что были описаны для функции printf()
.
Ввод чисел, символов и строк
Пример ввода-вывода целого и вещественного чисел, символа и строки:
int a; float b; char ch, str[30]; scanf("%d%f%c%s", &a, &b, &ch, str); printf("%d %.3f %c %s\n", a, b, ch, str);
Результат:
45 34.3456y hello 45 34.346 y hello
Здесь при выполнении программы все данные были введены в одну строку. Разделителем между числами и строками является пробел, а также любой другой символ пустого пространства (например, '\n'). Однако при считывании символа, пробел учитывается как символ; чтобы этого не произошло, в примере букву записали сразу после числа. Данные можно было бы ввести, разделяя их переходом на новую строку (опять же при этом надо иметь ввиду, как считывается символ).
В строке формата функции scanf()
между спецификациями вполне допустимо поставить пробелы: %d %f %c %s
. Они никакой роли не сыграют. Понятно, что данные можно было получить и так:
scanf("%d", &a); scanf("%f", &b); scanf("%c", &ch); scanf("%s", str);
Обратите внимание, перед переменной str отсутствует знак амперсанда. В последующих уроках вы узнаете, что имя массива уже само по себе является ссылкой на массив (другими словами, str содержит адрес начала массива).
В функции scanf()
в спецификации формата вещественных чисел не указывается точность представления числа. Запись типа %.3f
или %.10lf
приведет к невозможности получить вещественное число. Чтобы получить число типа double
используют формат %lf
, для long double
‒ %Lf
.
Для целых чисел: длинное целое ‒ %ld
, короткое целое ‒ %hd
. Существуют спецификации для ввода восьмеричных и шестнадцатеричных чисел.
Функция scanf()
возвращает количество удачно считанных данных; т.е. значение, возвращаемое функцией, можно проанализировать и таким образом узнать, корректно ли были введены данные. Например:
int a; double b; char ch, str[30]; ch = scanf("%d %lf %s", &a, &b, str); if (ch == 3) printf("%d %.3lf %s\n", a, b, str); else printf("Error input\n");
Обычные символы в строке формата
В строке формата scanf()
допустимо использование обычных символов. В этом случае при вводе данных также должны вводиться и эти символы:
int a, b, c; scanf("%d + %d = %d", &a, &b, &c); printf("Your answer is %d\n", c); printf("The correct is %d\n", a+b);
В данном случае, когда программа выполняется, ввод должен выглядеть примерно так: 342+1024 = 1366. Знаки "+" и "=" обязательно должны присутствовать между числами, наличие пробелов или их отсутствие никакой роли не играют:
45 + 839=875 Your answer is 875 The correct answer is 884
Запрет присваивания
Если какие-либо данные, вводимые пользователем, следует проигнорировать, то используют запрет присваивания, ставя после знака %
, но перед буквой формата звездочку *
. В таком случае данные считываются, но никакой переменной не присваиваются. Это можно использовать, например, когда нет определенной уверенности в том, что поступит на ввод, с одной стороны, и нужды сохранять эти данные, с другой:
float arr[3]; int i; for(i = 0; i < 3; i++) scanf("%*s %f", &arr[i]); printf("Sum: %.2f\n", arr[0]+arr[1]+arr[2]);
Здесь предполагается, что перед каждым числом будет вводиться строка, которую следует проигнорировать, например:
First: 23.356 Second: 17.285 Third: 32.457 Sum: 73.098
Использование "шаблонов"
Для функции scanf()
есть пара спецификаций формата, отдаленно напоминающих шаблоны командной оболочки и др. Формат […]
позволяет получить строку, содержащую любые символы, указанные в квадратных скобках. Как только на ввод поступает символ, не входящий в указанный набор, считывание данных прекращается. Формат [^…]
, наоборот, помещает в строку символы, не входящие в указанный набор, до тех пор пока не встретит любой из указанных.
В примере ниже как только поступает не цифра, считывание ввода завершается. При этом если первый символ — не цифра, то в str вообще ничего не записывается:
char str[30]=""; scanf("%[0-9]", str); printf("%s\n", str);
А в этом случае строке будет присвоена последовательность символов до любого из указанных знаков препинания:
scanf("%[^;:,!?]", str); printf("%s\n", str);
Пример выполнения:
one two three four five! one two three four five
Обратите внимание, что в примере выше в строку были записаны как символы пробелов, так и символ перехода на новую строку. Таким образом, если надо прочитать одну строку вместе с пробелами, можно использовать такой подход:
scanf("%[^'\n']", str);
Здесь в строку считываются все символы, кроме перехода на новую строку. Как только встречается этот символ ‒ '\n'
, запись данных в переменную прекращается.
Некоторые особенности и ограничения функции scanf
Как только поступают некорректные данные, функция scanf()
завершает свою работу. В примере:
scanf("%d%f", &a, &b);
если переменной a попытаться присвоить символ или строку, что невозможно, то в переменную b потом уже не получится записать число. Можно предположить, что так будет надежнее:
scanf("%d", &a); scanf("%f", &b);
Вроде бы неудачное считывание a не должно оказывать никакого влияния на b, т.к. это уже иной вызов scanf()
. Но не все так просто: при некорректном вводе данные остаются в буфере и пытаются "навязать" себя последующим вызовам scanf()
. Поэтому при использовании scanf()
надо думать о том, как в случае некорректного ввода очистить буфер. Например, это можно сделать так, как показано ниже, или путем использования специальных функций (здесь не рассматриваются):
// если данные не удалось присвоить, if (scanf("%d", &a) != 1) // то выбросить их в виде строки scanf("%*s"); scanf("%f", &b);
- На прошлом занятии вы написали программу, содержащую функции, вычисляющие факториал числа и заданный элемент ряда Фибоначчи. Измените эту программу таким образом, чтобы она запрашивала у пользователя, что он хочет вычислить: факториал или число Фибоначчи. Затем программа запрашивала бы у пользователя либо число для вычисления факториала, либо номер элемента ряда Фибоначчи.
- Напишите программу, которая запрашивает у пользователя две даты в формате дд.мм.гггг. Дни, месяцы и года следует присвоить целочисленным переменным. Программа должна выводить на экран информацию о том, какая дата более ранняя, а какая более поздняя.
- Используя цикл, напишите код, в котором пользователю предлагается вводить данные до тех пор, пока он не сделает это корректно, т.е. пока все указанные в
scanf()
переменные не получат свои значения. Протестируйте программу.
Курс с решением задач:
pdf-версия