Выбор правильных средств вывода информации
Когда вы планируете способ представления информации в вашей программе, обдумайте, какие именно функции наилучшим образом соответствуют вашим целям.
Чтобы вывести на экран обычный текст или символы, можно использовать функции puts() или putchar(). Так как эти функции не имеют никаких возможностей форматирования данных, они работают быстрее, и их коды занимают меньший объем на диске, чем коды функции printf(). Имея дело с функцией puts(), прежде всего проверьте, добавляет ли компилятор код «новая строка» автоматически. Если он не делает этого, а вы не проверите сразу, потом вам придется потратить довольно много времени на редактирование программы.
Рис. 4.18. Правила использования функции printf()
Кстати, при работе с функцией printf(), пропуск кода «новая строка» тоже является распространенной оплошностью среди начинающих программистов.
Функция printf() работает медленнее и требует большего объема памяти, но она идеально подходит в тех случаях, когда вам требуется выводить числовые данные, форматировать строки или комбинировать текст и числовые переменные в одной строке. Работая с функцией printf(), следует тщательно следить за тем, чтобы указатели формата соответствовали литералам, константам и переменным в списке данных. Рис.4.18 иллюстрирует наиболее важные моменты, необходимые, чтобы правильно написать инструкцию, использующую функцию printf().
Вывод в Си++
Все обсуждавшиеся ранее приемы программирования относятся к выводу данных как в языке Си, так и Си++. Однако язык Си++ имеет дополнительный способ вывода данных всех типов.
В Си++ существует стандартный поток вывода cout, позволяющий в сочетании с двумя символами «меньше»
(<<), которые называются оператором вставки*, отображать литералы или значения констант и переменных без использования указателей формата.
Если у вас есть компилятор Си++, посмотрите документацию к нему. Не исключено, что необходим специальный файл заголовков для того, чтобы иметь возможность использовать преимущества стандартного потока вывода cout и стандартного потока ввода cin (о нем вы прочтете в следующей главе). Для некоторых компиляторов, например, необходимо включить файл STREAM.H с помощью директивы #include в начале программы (директива #include и использование файлов заголовков рассматривались в главе 2).
______________________________________
* В оригинале insertion operator. Также часто используется термин «оператор вывода». (Прим.перев.)
Рис. 4.19. Стандартный поток вывода cout
Структура инструкции, использующей cout, показана на рис.4.19. После cout надо поставить два знака
<
. Они указывают cout отобразить помещенную после них информацию. Информация может быть представлена в виде литерала (тогда ее заключают в кавычки) либо имени константы или переменной.
Рассмотрим инструкцию cout << "Привет, меня зовут Сэм. Мы с вами уже встречались";
При ее выполнении произойдет вывод на дисплей строки, заключенной в кавычки. Инструкция
int count;count = 4509;cout << countотобразит значение переменной с именем count — число 4509.
Рис. 4.20. Вывод нескольких аргументов с помощью cout
Используя один стандартный поток вывода cout, можно отобразить несколько аргументов. Между собой аргументы разделяются операторами вставки, как это продемонстрировано на рис.4.20. Например, инструкция
int age;age = 43;cout << "Вам исполнилось " << age << " года.";отображает текст
Вам исполнилось 43 года.Стандартный поток вывода cout отображает каждый пункт, указанный с помощью оператора вставки, в том порядке, в каком они записаны в инструкции.
Так же, как и функция printf(), cout не добавляет никаких команд новой строки после отображения данных. Чтобы перейти к новой строке, там, где вы хотите ее начать, надо добавить управляющий код \n, как это показано на рис.4.20.
Стандартный поток вывода cout не требует обязательного использования указателей формата, но позволяет их ввести. С помощью указателей формата можно определять ширину поля, количество пробелов и число знаков после точки в вещественных числах. Описание форматирования вывода при использовании cout не входит в задачу этой книги. Если у вас есть компилятор Си++, вы можете подробно прочитать об этом в его описании. Не забудьте посмотреть в документации, надо ли включать специальные файлы заголовков при использовании cout.
|
Проектирование программы
Вывод, то есть отображение информации на экране, принтере или каком-либо другом устройстве, является важнейшей составляющей любой программы, так что планировать вывод необходимо самым тщательным образом.
Начните программу с вывода инструкций, которые объясняют цель ее создания:
puts("Добро пожаловать\n");puts("Эта программа рассчитывает платежи по закладным.\n");puts("Введите сумму займа,\n");puts("проценты по закладным и срок выплаты в годах.\n");В следующей главе рассказывается о вводе информации в программу. Прежде чем предложить ввести данные, убедитесь, что вы точно объяснили пользователю (даже если этот пользователь — вы сами), что именно он должен ввести. Используйте функцию, выводящую строку с комментарием, для каждого ввода информации:
puts("Пожалуйста, введите сумму полученного займа:");Когда вам нужно вывести результат, делайте информацию максимально доступной для чтения и понимания:
printf("Основные месячные выплаты: %7.2f\n", princ);printf("Проценты: %7.2f\n", interest);printf("Общие месячные выплаты: %7.2f\n", total);Дополнительные пробелы между двоеточиями и указателями формата, выравнивание указателей и указатели сами по себе приводят к тому, что числовые значения выравниваются следующим образом:
Основные месячные выплаты: 256.25Проценты: 92.12Общие месячные выплаты: 34.37Такой способ представления информации кажется более удобным, чем, например, следующий:
printf("%f %f %f", prins, inter, total);в результате чего имеем:
256.25 92.12 34.37Старайтесь все время поступать в соответствии с предлагаемыми правилами. Процесс создания программы отнимет несколько больше времени, но результат того стоит. Программа будет выглядеть более профессиональной и создаст максимум удобства для всех, кто будет ею пользоваться.
В табл. 4.2 подведен краткий итог всем способам вывода данных, которые обсуждались в этой главе.
Таблица 4.2. Средства вывода информации в языке Си/Си++.
<> | Вопросы |
|
<> | Упражнения |
|
ГЛАВА 5. ВВОД В СИ/СИ++
Вводом называется процесс предоставления компьютеру информации, необходимой для работы программы. Информация вводится в переменные, это означает, что данные, которые пользователь вводит в ответ на соответствующую подсказку, определяются как значение переменной, хранящейся в памяти. Затем переменная используется в выполняемых программой операциях. Из рисунка 5.1 видно, что ввод информации может происходить из различных источников.
Ввод данных — это процесс, который определяет всю дальнейшую работу программы. Достоверность получаемых от программы результатов не может быть больше, чем достоверность вводимой в нее информации. Как говорится, чистый вход — чистый выход. Если введенные в программу данные были ошибочными, все полученные результаты тоже окажутся неверными.
В этой главе мы подробно рассмотрим ввод данных с клавиатуры. В главе 12 вы узнаете, как программа берет данные из дискового файла.
Рис. 5.1. Откуда бы ни поступали данные,компьютер хранит их в памяти как переменные
|
|
Данные вводятся только как значения переменных, но не констант. Константы всегда сохраняют присвоенные им начальные значения. Когда вы вводите в переменную некоторые данные, они помещаются в отведенную для этой переменной область памяти. Если переменной уже присвоено какое-то значение, новая информация заместит прежнюю, уничтожив ее (рис.5.2).
Рис. 5.2. При вводе данных прежнее значение переменной теряется
Функция gets()
Функция gets() вводит строку в переменную. Параметром функции является имя переменной. Рассмотрим такую программу:
main() { char name[15]; gets(name); puts(name); }Функция gets() будет рассматривать первые 14 символов, введенные с клавиатуры, как значение строковой переменной с именем name. Вы помните, что Си отводит строковой переменной столько элементов памяти, сколько указано в максимальном значении при определении переменной, а так как один элемент необходим для нулевого символа (\0), реально можно ввести на один символ меньше. Если вы хотите ввести в переменную name строку, состоящую из 15 символов, то укажите в квадратных скобках максимальное значение 16:
char name[16];На время работы функции gets() выполнение программы приостанавливается. Она ждет, пока пользователь что-то напечатает. Для того чтобы напечатанные данные стали значением переменной, после ввода информации надо нажать клавишу Enter. Как только это сделано, строка, введенная пользователем, присваивается переменной в качестве значения, а курсор переходит на следующую строку на экране. При нажатии Enter Си добавляет в строку нулевой символ.
|
Рассмотрим более подробный пример:
main() { char name[25]; printf("Пожалуйста, введите Ваше имя: "); gets(name); printf("Подтвердите, Ваше имя: %s", name); }Когда программа будет выполняться, вы увидите на экране подсказку:
Пожалуйста, введите Ваше имя:
|
Поскольку функция printf() в данном случае не содержит кода «новая строка», курсор останавливается сразу за подсказкой, на расстоянии в один пробел от двоеточия, так как в строку формата функции printf() мы ввели пробел между двоеточием и закрывающими кавычками. Итак, пока вы сидите перед компьютером и смотрите на экран, ничего не произойдет. Программа ждет, что вы введете некую информацию, в данном случае напечатаете свое имя.
Пока вы печатаете имя, символы отображаются на экран в режиме эха. Если вы допустите ошибку, ее можно исправить до того, как нажата клавиша Enter, уничтожив неправильные символы клавишей Backspace и напечатав новые. В некоторых системах можно использовать клавишу Esc для того, чтобы удалить все введенные символы и начать процедуру заново.
После нажатия клавиши Enter Си присваивает введенные символы переменной name и вставляет нулевой символ в конце строки. Затем программа переходит к выполнению второй функции printf() и на экране появляется вторая надпись (предположим, что вас зовут Петр Иванов):
Пожалуйста, введите Ваше имя: Петр ИвановПодтвердите, Ваше имя: Петр ИвановПомните, символы, составляющие ваше имя, отображались на экране после первой подсказки только в режиме эха, они не были введены в программу до того, как вы нажали Enter. После второй подсказки имя появляется на экране в качестве значения переменной name в результате выполнения второй функции printf().
Таким образом, функция gets() прекрасно подходит для ввода в программу строк.
Функция getchar()
Функция getchar() вводит с клавиатуры единичный символ. Для большинства компиляторов безразлично, к какому типу (char или int) вы отнесете вводимый символ, что обусловлено способом определения символьной переменной в K&R-стандарте языка Си (двойственность символьных данных подробно обсуждалась в главе 4).
Для ввода символа можно использовать оба этих формата:
int letter; char letter; letter = getchar(); letter = getchar();Обратите внимание на то, что вызов функции getchar() осуществляется не так, как вызов функций, которые мы рассматривали раньше. Вместо того чтобы поставить имя функции в начало строки инструкции, мы относим его к переменной с помощью знака «равно». Эта запись означает: «Присвоить переменной letter значение, полученное в результате выполнения функции getchar()». Практически, функция getchar() рассматривается программой как значение переменной (рис.5.3). При выполнении этой инструкции вызывается функция getchar() и осуществляется ввод символа, который присваивается переменной. Функция getchar() не имеет аргумента. Поэтому круглые скобки после имени функции остаются пустыми.
Когда пользователь нажимает клавишу, getchar() отображает введенный символ на экране. В данном случае нажимать Enter нет необходимости, так как getchar()
Рис. 5.3. Функция getchar()
вводит только один символ, после чего программа немедленно продолжает работу. Символ присваивается переменной, как только вы нажали какую-либо клавишу.
|
Для чего может понадобиться ввод единичного символа? Вероятно, вам приходилось видеть программы, в которых необходимо ответить «Да» или «Нет» в ответ на запрос, или выбрать один из пунктов предложенного меню. Функция getchar() идеально подходит в этих случаях, ведь при работе с ней нет необходимости нажимать Enter, ввод одного из предложенных на выбор символов позволяет немедленно продолжить выполнение программы.
Если значение переменной введено с помощью функции getchar(), оно, независимо от того, определена переменная как int или как char, может быть отображено с помощью функции putchar() или printf(). В последнем случае используется указатель формата %c:
/*getchar.c*/main() { int initial; puts("Пожалуйста, введите следующий инициал."); initial=getchar(); putchar('\n'); putchar(initial); putchar('\n'); printf("%c", initial); }Данная программа вводит символ в переменную initial, а затем отображает значение переменной на дисплее, используя функции putchar() и printf(). Если в качестве инициала вы ввели букву 'А', то на экране увидите эту букву, повторенную трижды на отдельных строках. Первый раз она появляется при вводе ее с клавиатуры, еще два раза — в результате выполнения инструкций программы. Как правило, после ввода символа большинство компиляторов не выполняет автоматический перевод на новую строку.
Используя указатель формата %d, можно отобразить на экране ASCII-код символа:
/*ascii.c*/main() { int letter; letter=getchar(); printf("ASCII-кодом символа \ %c является %d\n", letter, letter); }Здесь мы присваиваем значение переменной letter, используя функцию getchar(), а затем, используя функцию printf(), выводим на экран значение переменной, записанное в двух форматах. Указатель формата %c обеспечивает отображение символа в таком виде, как он был введен с клавиатуры, а указатель формата %d преобразует значение переменной в целое число, ASCII-код данного символа.
Значения, введенные с помощью функции getchar(), всегда рассматриваются Си как символ, а не как числовое значение, так что, даже если определить переменную как int и вводить цифру, ее нельзя будет использовать в математических операциях.
«Для продолжения нажмите Enter»
Функцию getchar() можно использовать для приостановки выполнения программы, что может оказаться весьма полезно в некоторых ситуациях. Приведем простой пример. На экран можно одновременно вывести ограниченное количество строк (обычно 25). Если в программе используется длинный ряд инструкций puts(), при выполнении которых надо вывести больше строк, чем может поместиться на экране, первые появившиеся строки информации быстро уйдут за верхний край, и пользователь просто физически не успеет ознакомиться с их содержанием до того, как на этом месте появятся другие.
Справиться с подобной проблемой можно, если разделить длинную последовательность инструкций на блоки, включающие какое-то количество функций puts(), так, чтобы сообщения, появившиеся в результате их выполнения, заняли только часть экрана, и в конце каждого блока добавить:
printf("Для продолжения нажмите Enter");getchar();В этом примере функция getchar() приостанавливает выполнение программы до тех пор, пока пользователь не нажмет клавишу (рис.5.4). Совершенно необязательно этой клавишей должна быть именно Enter, но нажатие любой другой приведет к появлению на экране соответствующего символа, а это может запутать пользователя. Функция getchar() в данном случае не связана ни с какой переменной. Если для продолжения выполнения программы будет нажата, например, клавиша Y, соответствующий символ появится на экране, но не будет присвоен в качестве значения ни одной переменной.
Рис. 5.4. Использование функции getchar() для создания паузы в программе
Оператор получения адреса &
Вы уже знаете, что для каждой переменной, используемой в программе, отводится определенный объем памяти, который зависит от типа переменной (см.главу3). Каждый элемент памяти пронумерован от 0 и далее. Если компьютер имеет 2Мегабайта оперативной памяти, элементы будут пронумерованы от 0 до 2097151, как показано на рис.5.5. Эти номера и называют адресами элементов памяти.
Рис. 5.5. Адреса элементов памяти
Когда вы определяете переменные, Си резервирует память для хранения их значений. Например, для целочисленной переменной с именем count, Си отведет два элемента (байта) памяти. Первый из них определяет адрес переменной.
На рис. 5.6 показана переменная count, которая имеет значение 2341. Это значение хранится в элементах, имеющих номера 21560 и 21561, так что адрес переменной count (не значение!) — это число 21560. В качестве адреса достаточно указать номер первого элемента, поскольку Си знает, сколько памяти отводится для каждого типа данных. Если count относится к типу int, для нее требуется два элемента памяти, и если первый из них имеет номер 21560, то номер последнего — 21561.
|
Рис. 5.6. Переменная и ее адрес
При ссылке на переменную (например, при выводе ее значения на экран) используется имя переменной. Можно также сослаться и на ее адрес, если поставить символ амперсанда (&) перед именем переменной, скажем, так: &count. Символ & называется оператором получения адреса. Он указывает Си, что вас в настоящий момент интересует адрес элемента памяти, где зарезервировано место для переменной, а не значение переменной, хранящееся в этом элементе. Поэтому, хотя переменная count имеет значение 2341, &count имеет значение 21560.
Оператор получения адреса не используется со строковыми переменными. Строки определяются с помощью специального класса переменных, называемого массив, о котором мы будем говорить в главе 10.
Оператор получения адреса используется при вводе данных с помощью функции scanf().
Функция scanf()
Функция scanf() является многоцелевой функцией, дающей возможность вводить в компьютер данные любых типов. Название функции отражает ее назначение — функция сканирует (просматривает) клавиатуру, определяет, какие клавиши нажаты, и затем интерпретирует ввод, основываясь на указателях формата (SCAN Formatted characters). Так же, как и функция printf(), scanf() может иметь несколько аргументов, позволяя тем самым вводить значения числовых, символьных и строковых переменных в одно и то же время.
|
Так же, как список параметров printf(), список параметров функции scanf() состоит из двух частей: строки формата и списка данных (рис.5.7). Строка формата содержит указатели формата, здесь они носят название преобразователей символов*, которые определяют то, каким образом должны быть интерпретированы вводимые данные. Список данных содержит переменные, в которые должны быть занесены вводимые значения.
Рис. 5.7. Список параметров функции scanf() делится на две части
___________________________
.* В оригинале conversion characters. (Прим.перев.)
Указатели формата аналогичны тем, которые используются функцией printf():
%d | целые числа |
%u | беззнаковые целые числа |
%f | вещественные числа, float |
%e | вещественные числа в экспоненциальной форме |
%g | вещественные числа в наиболее коротком из форматов %f или %e |
%c | символы |
%s | строки |
%o | целые числа в восьмеричной системе счисления |
%x | целые числа в шестнадцатеричной системе счисления |
Вводя числовые или символьные данные, следует указывать в списке данных функции scanf() адрес переменной, а не просто ее имя:
main() { float amount; scanf("%f", &amount); }В этом примере функция scanf() вводит число с плавающей точкой и вносит его в область памяти, зарезервированную для переменной amount. Как только число помещается в эту область памяти, оно автоматически становится значением переменной.
На время работы функции scanf(), выполнение программы приостанавливается, и программа ожидает ввода данных. Ввод заканчивается нажатием клавиши Enter.
Принцип работы с данными функции scanf() в корне отличается от работы функций gets() и getchar(). Для того чтобы понять, что именно происходит при вводе с помощью scanf(), необходимо детально рассмотреть эти отличия.
Входной поток
Когда данные вводятся при помощи функции gets(), все символы, которые были набраны на клавиатуре до нажатия Enter, становятся значением переменной. Когда символ вводится с помощью функции getchar(), нажатие клавиши автоматически приводит к присвоению соответствующего символа переменной.
Функция scanf() работает по-другому. Вместо того чтобы просто взять данные и присвоить их переменной, scanf() прежде всего с помощью указателей формата определяет, каким образом следует трактовать введенные символы.
Принято говорить, что scanf() получает данные из входного потока. Входным потоком называется последовательность символов, поступающих из некоторого источника. В случае функции scanf() источником служит клавиатура. После нажатия клавиши Enter все данные, которые были введены к этому времени, передаются функции scanf() в виде пока еще бессмысленного набора символов, в том же порядке, в каком их набирали. Затем scanf() определяет, какие символы соответствуют типу, заданному указателем формата, а какие следует игнорировать. Указатели формата называют преобразователями символов, так как они берут исходные символы из входного потока и преобразуют их в данные, относящиеся к определенному типу. Рис.5.8 иллюстрирует этот процесс.
Функция scanf() игнорирует не содержащие информации знаки: пробелы, символы табуляции, символы новой строки, кроме тех случаев, когда текущий тип данных определен как char. Рассмотрим программу:
main() { int count; puts("Пожалуйста, введите число: "); scanf("%d", &count); printf("Число равно %d", count); }
Рис. 5.8. Функция scanf() читает входной поток и определяет,какие данные следует ввести, а какие — игнорировать
Перед тем как ввести число, вы можете нажимать на клавишу пробела столько, сколько хотите — Си будет игнорировать пробелы, в ожидании первого значимого символа. Затем Си попытается преобразовать символы в соответствии с указателями формата в строке формата функции scanf(). Если эти символы соответствуют формату (в данном случае — если это цифры), они будут внесены в переменную. Ввод данных прекратится, если встретится символ, формат которого не соответствует ожидаемому, то есть он не является цифрой. Например, если набрать на клавиатуре последовательность «123abc», то число 123 в нашем примере будет присвоено переменной count, а буквы «abc» — проигнорированы, как это показано на рис.5.9. Остановку ввода данных может вызвать пробел. Например, если напечатать «123», то переменной будет присвоено значение 12, а число 3 — проигнорировано.
Рис. 5.9. Функция scanf() прекращает ввод данных, встретив первый не цифровой символ
Первый значимый символ должен соответствовать указанному в аргументе функции scanf() формату. Так, если напечатать последовательность «ABC123», программа проигнорирует ее всю целиком, и вы останетесь в неведении относительно значения переменной.
Какие символы программа расценивает как «подходящие», зависит от указателей формата. Если стоит указатель %d, то «подходящими» являются только цифры и знак «минус». Если поставить указатель %x, то соответствующими формату окажутся символы 0123456789ABCDE, так как все они используются при записи чисел в шестнадцатеричной системе счисления. Если же стоит указатель %c, принимаются любые символы, даже пробел внутри входного потока функция scanf() в этом случае не игнорирует. Если написать инструкцию:
char letter;scanf("%c", &letter);и нажать клавишу пробела в начале последовательности значимых символов, scanf() присвоит переменной значение пробел, игнорируя последующие символы. Поэтому, имея дело с типом char, нельзя помещать пробелы перед другими символами.
Вводя строку, функция scanf() начнет присваивание значения с первого значимого символа, игнорируя пробелы впереди, и остановит присваивание, встретив первый пробел среди значимых символов. Взгляните на программу:
main() { char name[25]; puts("Пожалуйста, введите Ваше имя: "); scanf("%s", name); puts(name); }Обратите внимание, оператор получения адреса не используется с именем строковой переменной. Если вы наберете на клавиатуре «Нэнси» и нажмете Enter, эти символы будут присвоены переменной name. Даже если набрать «Нэнси Чезин», scanf() начнет присвоение символов с первого значимого символа и остановит, встретив первый пробел, так что значением переменной все равно станет только имя Нэнси, а остальное программа проигнорирует (рис.5.10).
Рис. 5.10. Функция scanf() прекращает чтение символов в строкепри появлении первого пробела
Из-за этой особенности функции scanf(), она является не слишком удобной для ввода строк. В этих случаях лучше использовать функцию gets().