Использование printf() и scanf()
От: LaptevVV Россия  
Дата: 17.04.05 11:07
Оценка: 50 (10) :)
#Имя: FAQ.cpp.printf.scanf
Здравствуйте, TARGRED, Вы писали:

TAR>Привет всем.

TAR>Скажите, пожалуйста, почему, когда я использую функцию scanf() как написано ниже, то у меня, получается, использовать эту функцию один раз, а потом программа закрывается.
Потому что полную ерунду написал

Начнем с функции вывода printf(), которая выполняет форматируемый вывод в стандартный поток stdout. Это означает, что значения переменных, которые хранятся в памяти в двоичном виде, при выводе в поток (на экран) переводятся в символьный вид, причем вид преобразования задается спецификатором формата. Спецификаторы формата задаются как составная часть обязательного первого аргумента — форматной строки. После форматной строки задается список выражений, значения которых должны выдаваться на экран. В качестве выражений допускается задавать и переменные. Функция возвращает количество выведенных символов. Если произошел сбой, то возвращается отрицательное значение.

Спецификатор формата начинаются символом % (процент), вслед за которым прописывается код формата. Количество аргументов-переменных должно в точности соответствовать количеству спецификаторов формата. При отсутствии списка переменных функция просто выводит в поток (на экран) форматную строку.

Спецификация формата имеет следующий вид:
%[флаг][ширина][.точность][h|l|L]тип

Здесь квадратные скобки означают, что соответствующий элемент спецификации может отсутствовать. В простейшем виде спецификатор формата записывается как
%тип

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

Таблица 8.2. Некоторые типы спецификаторов формата

Тип Вид в потоке
c Символ
s Строка символов
d, i Целое десятичное со знаком
o Целое восьмеричное
u Целое десятичное без знака
x, X Целое шестнадцатеричное
f Дробное число в фиксированном формате
e, E Дробное число в научном формате
g, G Дробное число в научном или фиксированном формате
p Указатель (в шестнадцатеричном виде)

Ширина показывает, сколько символов будет выведено в поток. В это число входят все символы: и знак, и десятичная точка для дробных чисел. Если ширина окажется фактически меньше, чем требуется для вывода, то все равно значение выводится полностью. Точность задает количество цифр дробной части и применяется обычно только для дробных чисел.

Перед типом может стоять модификатор типа (табл. 8.3). Обычно модификаторы применяются к числовым переменным: в этом случае они указывают короткую (h) или длинную (L,l) форму типа. Например, тип %f применяется для вывода значений типа float, %lf — для вывода double, а %Lf — для вывода значений long double.

Таблица 8.3. Модификаторы типа

Модификатор Спецификатор формата Тип переменной
h i d u o x X short int
l i d u o x X long int
l e E f g G double
L e E f g G long double


Вместо ширины и точности в спецификаторе формата можно указывать символ * (звездочка). Это позволяет задавать и ширину, и точность как значения целых переменных — это позволяет вычислять ширину поля вывода во время выполнения программы. Рассмотрим простейший пример, представленный в листинге 8.2.

//Листинг 8.2. Переменные ширина и точность
float number = -12.3;
int w = 10,                     // ширина
    p = 4;                        // точность
printf("%0*.*f\n", w, p, number);
w = 15, p = 5;                    // новая ширина и точность
printf("%0*.*f\n", w, p, number);

На экране выводятся две строки
-0012.3000
-00000012.30000

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

В связи с возможностью задавать переменную ширину поля часто бывает необходимо знать количество символов, выведенных в поток после последнего освобождения буфера. И эту величину можно получить с помощью спецификатора %n. В списке аргументов после форматной строки задается адрес целой переменной, куда и заносится количество выведенных в поток символов. Следующий небольшой фрагмент демонстрирует эту возможность.

w = 15, p = 5;                                // новая ширина и точность
int kk = 0;
printf("%0*.*f%n\n", w, p, number, &kk);    // вычисление количества
printf("%d\n", kk);                            // выводит 15

В переменную kk, адрес которой задан в списке аргументов, помещается количество символов, выведенных в поток с момента последнего освобождения буфера, в данном случае 15.


А теперь про ввод

Парная для printf() функция scanf() выполняет форматируемый ввод из стандартного потока stdin в переменные программы. Так как stdin "привязан" к клавиатуре, то при вводе, очевидно, выполняется преобразование из символьного вида во внутренний двоичный формат. Первым параметром тоже является форматная строка, за которой следует список адресов переменных, куда требуется поместить задаваемые значения. Функция возвращает количество успешно введенных значений. В случае ошибки функция scanf() возвращает системную константу EOF. Обычно эта константа определена как –1.
Символы в форматной строке делятся на три вида: спецификаторы формата, разделители и прочие. К разделителям относятся пробел, табуляция ('\t') и символ конца строки ('\n'). Спецификаторы формата — такие же, как и для функции printf(), что и показано в листинге 8.3.

//Листинг 8.3. Спецификаторы формата для scanf()

#include <cstdio>
int main()
{    char c1;
    short s1;
    int i1;
    unsigned int u1;
    long L1;
    float f1;
    double d1;
    printf("input char >"); scanf("%c",  &c1); printf(" %c\n",  c1);
    printf("input short>"); scanf("%hd", &s1); printf(" %hd\n", s1);
    printf("input int  >"); scanf("%d",  &i1); printf(" %d\n",  i1);
        printf("input uint >"); scanf("%u",  &u1); printf(" %u\n",  u1);
    printf("input octal>"); scanf("%o",  &i1); printf(" %o\n",  i1);
    printf("input hexa >"); scanf("%x",  &i1); printf(" %x\n",  i1);
    printf("input long >"); scanf("%ld", &L1); printf(" %ld\n", L1);
    printf("input float>"); scanf("%f",  &f1); printf(" %f\n",  f1);
    printf("input double>"); scanf("%lf", &d1);printf(" %lf\n", d1);
    return 0;
}

В этом примере ввод каждой переменной нужно завершать клавишей <Enter>, даже ввод одного символа.

В общем виде спецификатор формата имеет следующую форму:
%[ширина][h/l/L]тип

Как обычно, квадратные скобки означают, что этот элемент спецификатора может отсутствовать. По сравнению со спецификатором формата для вывода только символ *(звездочка) имеет другой смысл: если она указана, то запись значения в соответствующую переменную не происходит. Например, можно определить ввод двух значений в две числовых переменных, разделяя их при вводе любым символом

double a,b;
scanf("%lf%*c%lf",&a,&b);

В этом случае мы можем задавать ввод и так
1б2
и так
1,2
и так
1-2
Во всех случаях в переменную a попадет значение 1, а в переменную b — значение 2.

Любая из следующих последовательностей символов рассматривается как входное поле:
 все символы до следующего символа-разделителя (исключая его самого);
 все символы до первого, который не может быть преобразован текущим спецификатором формата (например, 8 или 9 при восьмеричном формате);
 не более n символов, где n — указанная в строке формата ширина поля.

К сожалению, при использовании этой функции программист очень плохо защищен от ошибок, как своих, так и пользователя. "Подводных камней" при вводе гораздо больше, чем при выводе. В документации к системе Borland С++ можно встретить такое примечание:
scanf() часто приводит к неожиданным результатам, если входное поле расходится с ожидаемым шаблоном.

Во-первых, программисты постоянно забывают, что в функции scanf() нужно указывать адреса, прописывая знак & (амперсант) перед именем. Последствия забывчивости — непредсказуемы, хорошо, если программа сразу "вылетает" по аварии!

Во-вторых, ввод в текущую переменную прекращается, как только во входном потоке встретится символ, недопустимый для данного спецификатора формата. Например, в приведенном примере сначала выполняется ввод восьмеричного целого, а затем шестнадцатеричного. Если мы наберем на клавиатуре символы
23458f1
то в качестве восьмеричного числа будет воспринято число 2345. Оставшиеся три символа 8f1 попадут в следующую переменную! Такое поведение объясняется буферизацией: последовательность символов сохраняется в буфере, и используется при следующем вводе. Так как в нашем примере следующим выполняется ввод в шестнадцатеричном виде, то эта последовательность и составляет вводимое шестнадцатеричное число.

В-третьих, как и для функции printf(), не выполняются проверки соответствия типа переменной и спецификатора формата, поэтому ошибку совершить очень легко. Например, в последней строке приведенной программы (листинг 8.3) выполняется ввод переменной d1 типа double, спецификатор формата прописан как %lf. Стоит пропустить модификатор l,

printf("input double>"); scanf("%f", &d1);printf(" %f\n", d1);

как результат
-92559604281615349000000000000000000000000000000000000000000000.000000
оказывается совершенно невероятным!

И, наконец, ввод строк выполняется до первого символа-разделителя (в частности, до первого пробела).

ПРИМЕЧАНИЕ
Ввод строк выполняется обычно другими, более подходящими функциями, например, fgets(), которую мы далее рассмотрим.
При этом, естественно, речь идет о символьных массивах, а не о переменных типа string.

char ss [80] = {0};            // объявление символьного массива
scanf("%s",ss);                // имя ss пишется без амперсанта!

Если мы попробуем ввести последовательность символов "1234 567", то в массив ss попадет только "1234". Кроме того, обратите внимание, что имя массива нужно прописывать без амперсанта — это единственное исключение из общего правила!
При вводе данных посредством scanf() надо совершенно жестко придерживаться формата ввода. Например, при вводе нескольких значений можно разделять их символами пунктуации (запятая, точка с запятой, двоеточие). Эти символы должны быть прописаны и в форматной строке, например

char c1,c2,c3;
double d1, d2, d3;
scanf("%c,%c,%c",&c1,&c2, &c3);            // ввод трех символов
scanf("%lf,%lf,%lf",&d1,&d2, &d3);        // ввод трех чисел

При вводе надо так и набирать на клавиатуре
а,б,в<Enter> // ввод трех символов
1,2,3<Enter> // ввод трех чисел

Если вместо запятых вы наберете что-нибудь другое (например, пробелы) — пеняйте на себя! И конечно, разделение значений запятыми (и любыми другими символами пунктуации) не проходит при вводе строк.
Как видите, программист должен обращаться с функцией scanf() как сапер с неразорвавшейся миной! Приведем еще несколько тонкостей, связанных со вводом строк и символов:
1. Спецификатор %c читает очередной символ, в том числе и символ-разделитель. Чтобы пропустить один символ-разделитель и прочитать следующий основной символ, используйте %1s.
2. Аргументом для спецификатора %Wc (W — ширина поля) должен быть массив символов, состоящий из W элементов (char arg[W]).
3. Множество символов, заключенное в квадратные скобки [], может быть подставлено вместо символа типа s.

На последнем остановимся подробней. Фактически в форматной строке разрешается задавать ограниченную форму регулярного выражения. Квадратные скобки [] ограничивают множество допустимых символов при вводе, составляющих строку (входное поле). Если первым символом в квадратных скобках является "крыша" (^), в множество включаются все символы ASCII, кроме тех, которые содержатся внутри скобок. Входное поле — строка, не ограниченная символом-разделителем; scanf() читает входное поле, пока не встретит символ, отсутствующий в множестве заданных символов. Например, %[abcd] задает во входном поле только символы a, b, c, d, причем не в единственном числе — можно вводить строки
aaaa
abbbabb
bdcbabdbcbcbd
и тому подобные — главное, чтобы эти строки поместились в массив. Ограничить количество вводимых символов можно, прописав, как обычно, ширину поля, например %10[abcd] — тогда в массив поместится не более 10 символов a, b, c или d. Спецификатор %[^abcd] задает во входном поле любые символы, отличные от a, b, c, d.

Разрешается задавать и диапазоны символов (цифр или букв). Например, все десятичные цифры можно определить как %[012345679], однако диапазон позволяет написать это значительно короче %[0-9]. Чтобы указать алфавитно-цифровые символы, используйте следующие сокращения:
 %[A-Z] — все заглавные английские буквы;
 %[0-9A-Za-z] — все десятичные цифры и все буквы английского алфавита;
 %[A-FT-Z] — все заглавные буквы от A до F и от T до Z.

Правила использования таких сокращений несложны:
 символ, стоящий перед дефисом ('-'), должен предшествовать символу, стоящему после дефиса, в алфавитном порядке;
 дефис не может быть ни первым, ни последним символом множества (если он является первым или последним, он интерпретируется просто как символ '-', но не как элемент, определяющий диапазон);
 символы по обе стороны от дефиса должны быть концами диапазона и не входить в другие диапазоны.

Несколько простых примеров демонстрируют эти правила:
%[-+*/] четыре арифметических операции
%[z-a] символы 'z', '-' (минус) и 'a'
%[+0-9-A-Z] символы '+', '- 'и диапазоны 0-9 и A-Z
%[+0-9A-Z-] то же самое
%[^-0-9+A-Z] все символы, кроме указанных

К сожалению, русские буквы в таких выражениях хотя и транслируются, но не работают.

Хочешь быть счастливым — будь им!
Без булдырабыз!!!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.