Версия для печати


Глава 2. Основы языка Delphi. Часть II.
http://www.delphikingdom.com/asp/viewitem.asp?catalogID=1173

Кирилл Сурков
Александр Вальвачев, Дмитрий Сурков, Юрий Четырько
дата публикации 01-11-2005 05:39

урок из цикла: Учебное пособие по программированию на языке Delphi

Глава 2. Основы языка Delphi. Часть II. 2.5. Консольный ввод-вывод

2.5.1. Консольное приложение

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

Сейчас мы рассмотрим лишь средства консольного ввод-вывода данных. Консоль - это клавиатура плюс монитор. С клавиатуры данные вводятся в программу, а на монитор выводятся результаты ее работы. Консольная модель ввода-вывода, при которой данные представляются потоком символов, не позволяет использовать графических средств. Однако она очень подходит для изучения языка Delphi, так как не загромождает примеры программ излишней информацией о среде и библиотеках программирования.

Итак, давайте последовательно создадим консольное приложение:
  1. Запустите среду Delphi, выберите в главном меню команду File | Close All, а затем - команду File | New.
  2. Выберите "Console Application" и нажмите "OK" (рисунок 2.1).
    Рисунок 2.1. Окно среды Delphi для создания нового проекта
    Рисунок 2.1. Окно среды Delphi для создания нового проекта

  3. В появившемся окне между ключевыми словами BEGIN и END введите следующие строчки (рисунок 2.2):
    Writeln('Press Enter to exit...');
    ReadLn;
    


    Рисунок 2.2. Текст простейшей консольной программы в окне редактора кода

  4. Скомпилируйте и выполните эту программу, щелкнув на пункте Run | Run главного меню среды Delphi. На экране появится черное окно (рисунок 2.3), в левом верхнем углу которого будет содержаться текст "Press ENTER to exit..." ("Нажмите клавишу Enter ...").

    Рисунок 2.3. Окно работающей консольной программы

Теперь, когда есть основа для проверки изучаемого материала, рассмотрим операторы консольного ввода-вывода. К ним относятся Write, Writeln, Read, Readln.

2.5.2. Консольный вывод

Инструкции Write и Writeln служат для вывода чисел, символов, строк и булевских значений на экран. Они имеют следующий формат:

Write(Y1, Y2, ... ,Yn);
Writeln(Y1, Y2, ... ,Yn);

где Y1, Y2,..., Yn - константы, переменные и результаты выражений. Инструкция Writeln аналогична Write, но после своего выполнения переводит курсор в начало следующей строки.

Если инструкции Write и Writeln записаны без параметров:
Write;
Writeln;
то это вызывает пропуск на экране соответственно одной позиции и одной строки.

2.5.3. Консольный ввод

Инструкции ввода обеспечивают ввод числовых данных, символов, строк для последующей обработки в программе. Формат их прост:

Read(X1, X2, ... ,Xn);
Readln(X1, X2, ... ,Xn);
где X1, X2, ..., Xn - переменные, для которых осуществляется ввод значений. Пример:
Read(A);   // Вводится значение переменной A
Readln(B); // Вводится значение переменной B
Если одна инструкция вводит несколько значений:
Read(A, B);
то все эти значения надо набрать на клавиатуре, отделяя одно значение от другого пробелом, и нажать клавишу Enter.

Если вводится одно значение:
Read(C);
то его следует набрать и нажать клавишу Enter. С этого момента программа может обрабатывать введенное значение в соответствии с алгоритмом решаемой задачи.

Инструкция Readln отличается от Read только одним свойством: каждое выполнение инструкции Readln переводит курсор в начало новой строки, а после выполнения Read курсор остается в той же строке, где и был (потренеруйтесь - и вы быстро поймете разницу).

В простейшем случае в инструкциях Read и Readln параметры можно вообще не указывать:
Read;
Readln;
Оба этих оператора останавливают выполнение программы до нажатия клавиши Enter.

2.6. Структура программы

Читатель уже достаточно много знает об отдельных элементах программы, пора изучить ее общую структуру. Синтаксически программа состоит из заголовка, списка подключаемых к программе модулей и программного блока:

Заголовок программы	program <имя программы>;
Директивы компилятора	{$<директивы>}

Подключение модулей	uses <имя>, ..., <имя>;

Программный блок
    Константы		const ...;
    Типы данных		type  ...;
    Переменные		var   ...;
    Процедуры		procedure <имя>; begin ... end;
    Функции		function  <имя>; begin ... end;
    Тело программы	begin
 	  			<операторы>
			end.

Любая секция в программном блоке кроме тела программы может отсутствовать. Секции описания констант, типов данных, переменных, процедур и функций могут встречаться в программе любое количество раз и следовать в произвольном порядке. Главное, чтобы все описания были сделаны до того, как они будут использованы (иначе компилятор просто не поймет того, что вы написали).

2.6.1. Заголовок программы

Заголовок программы должен совпадать с именем программного файла. Он формируется автоматически при сохранении файла на диске и его не следует изменять вручную. Например, заголовок программы в файле Console.dpr выглядит так:

program Console;

Одним заголовком сказать можно немного, поэтому для подробного рассказа о назначении программы, нюансах алгоритма и других вещах применяют комментарий, например:

{****************************************************}
{    Демонстрационный пример                         }
{    A.Valvachev, K.Surkov, D.Surkov, Yu.Chetyrko    }
{****************************************************}

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

{$APPTYPE CONSOLE}


2.6.2. Подключение модулей

Секция подключения модулей предназначена для встраивания в программу стандартных и разработанных вами библиотек подпрограмм и классов (о подпрограммах и классах читайте ниже). Эта секция состоит из зарезервированного слова uses и списка имен подключаемых библиотечных модулей. При написании программ, эмулирующих текстовый режим, подключается по крайней мере модуль SysUtils. В нем содержатся определения часто используемых типов данных и подпрограмм:

uses
  SysUtils;

С момента подключения все ресурсы модуля (типы данных, константы, переменные, процедуры и функции) становятся доступны программисту.

2.6.3. Программный блок

Важнейшим понятием в языке Delphi является так называемый блок. По своей сути блок - это программа в целом или логически обособленная часть программы, содержащая описательную и исполнительную части. В первом случае блок называется глобальным, во втором - локальным. Глобальный блок - это основная программа, он присутствует всегда; локальные блоки - это необязательные подпрограммы (они рассмотрены ниже). Локальные блоки могут содержать в себе другие локальные блоки (т.е. одни подпрограммы могут включать в себя другие подпрограммы). Объекты программы (типы, переменные и константы) называют глобальными или локальными в зависимости от того, в каком блоке они объявлены.

С понятием блока тесно связано понятие области действия программных объектов. Область действия трактуется как допустимость использования объектов в том или ином месте программы. Правило здесь простое: объекты программы можно использовать в пределах блока, где они описаны, и во всех вложенных в него блоках. Отсюда следует вывод - с глобальными объектами можно работать в любом локальном блоке.

Тело программы является исполнительной частью глобального блока. Именно из него вызываются для выполнения описанные выше процедуры и функции. Тело программы начинается зарезервированным словом begin (начало), далее следуют операторы языка, отделенные друг от друга точкой с запятой. Завершает тело программы зарезервированное слово end (конец) с точкой. Тело простейшей консольной программы выглядит так:

begin
  Writeln('Press Enter to exit...');
  Readln;
end.

На этом мы заканчиваем рассмотрение структуры программы и переходим к содержимому тела программы - операторам.

2.7. Операторы

2.7.1. Общие положения

Основная часть программы на языке Delphi представляет собой последовательность операторов, выполняющих некоторое действие над данными, объявленными в секции описания данных. Операторы выполняются строго последовательно в том порядке, в котором они записаны в тексте программы и отделяются один от другого точкой с запятой.

Все операторы принято в зависимости от их назначения разделять на две группы: простые и структурные. Простые операторы не содержат в себе никаких других операторов. К ним относятся операторы присваивания, вызова процедуры и безусловного перехода. Структурные операторы содержат в себе простые или другие структурные операторы и подразделяются на составной оператор, условные операторы и операторы повтора.

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

2.7.2. Оператор присваивания

Оператор присваивания (:=) вычисляет выражение, заданное в его правой части, и присваивает результат переменной, идентификатор которой расположен в левой части. Например:

X := 4;
Y := 6;
Z := (X + Y) / 2;

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

В общем случае для числовых типов данных действует следующее правило: выражение с более узким диапазоном возможных значений можно присвоить переменной с более широким диапазоном значений. Например, выражение с типом данных Byte можно присвоить переменной с типом данных Integer, а выражение с типом данных Integer можно присвоить переменной с типом данных Real. В таких случаях преобразование данных из одного представления в другое выполняется автоматически:

var
  B: Byte;
  I: Integer;
  R: Real;
begin
  B := 255;
  I := B + 1;    // I = 256
  R := I + 0.1;  // R = 256.1
  I := R;        // Ошибка! Типы данных несовместимы по присваиванию
end.

Исключение составляет случай, когда выражение принадлежит 32-разрядному целочисленному типу данных (например, Integer), а переменная - 64-разрядному целочисленному типу данных Int64. Для того, чтобы на 32-разрядных процессорах семейства x86 вычисление выражения происходило правильно, необходимо выполнить явное преобразование одного из операндов выражения к типу данных Int64. Следующий пример поясняет сказанное:

var
  I: Integer;
  J: Int64;
begin
  I := MaxInt;        // I =  2147483647 (максимальное целое)
  J := I + 1;         // J = -2147483648 (неправильно: ошибка переполнения!)
  J := Int64(I) + 1;  // J =  2147483648 (правильно: вычисления в формате Int64)
end.

2.7.3. Оператор вызова процедуры

Оператор вызова процедуры представляет собой не что иное, как имя стандартной или пользовательской процедуры. О том, что это такое, вы узнаете чуть позже, а пока достаточно просто наглядного представления. Примеры вызова процедур:

Writeln('Hello!'); // Вызов стандартной процедуры вывода данных
MyProc;            // Вызов процедуры, определенной программистом

2.7.4. Составной оператор

Составной оператор представляет собой группу из произвольного числа операторов, отделенных друг от друга точкой с запятой и заключенную в так называемые операторные скобки - begin и end:

begin
  <оператор 1>;
  <оператор 2>;
  …
  <оператор N>
end
Частным случаем составного оператора является тело следующей программы:
program Console;

{$APPTYPE CONSOLE}

uses
  SysUtils;

var
  X, Y: Integer;

begin
  X := 4;
  Y := 6;
  Writeln(X + Y);
  Writeln('Press Enter to exit...');
  Readln; // Точка с запятой после этого оператора не обязательна
end.

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

Составной оператор может находиться в любом месте программы, где разрешен простой оператор. Он широко используется с условными операторами и операторами повтора.

2.7.5. Оператор ветвления if

Оператор ветвления if - одно из самых популярных средств, изменяющих естественный порядок выполнения операторов программы. Вот его общий вид:

if <условие> then
  <оператор 1>
else
  <оператор 2>;

Условие - это выражение булевского типа, оно может быть простым или сложным. Сложные условия образуются с помощью логических операций и операций отношения. Обратите внимание, что перед словом else точка с запятой не ставится.

Логика работы оператора if очевидна: выполнить оператор 1, если условие истинно, и оператор 2, если условие ложно. Поясним сказанное на примере:

program Console;

{$APPTYPE CONSOLE}

uses
  SysUtils;

var
  A, B, C: Integer;

begin
  A := 2;
  B := 8;
  if A > B then
    C := A
  else
    C := B;
  Writeln('C=', C);
  Writeln('Press Enter to exit...');
  Readln;
end.
В данном случае значение выражения А > В ложно, следовательно на экране появится сообщение C=8.

У оператора if существует и другая форма, в которой else отсутствует:
if <условие> then <оператор>;

Логика работы этого оператора if еще проще: выполнить оператор, если условие истинно, и пропустить оператор, если оно ложно. Поясним сказанное на примере:

program Console;

{$APPTYPE CONSOLE}

uses
  SysUtils;

var
  A, B, C: Integer;

begin
  A := 2;
  B := 8;
  C := 0;
  if A > B then C := A + B;
  Writeln('C=', C);
  Writeln('Press Enter to exit...');
  Readln;
end.

В результате на экране появится сообщение С=0, поскольку выражение А > В ложно и присваивание С := А + В пропускается.

Один оператор if может входить в состав другого оператора if. В таком случае говорят о вложенности операторов. При вложенности операторов каждое else соответствует тому then, которое непосредственно ему предшествует. Например:

program Console;

{$APPTYPE CONSOLE}

uses
  SysUtils;

var
  A: Integer;

begin
  Readln(A);
  if A >= 0 then
    if A <= 100 then
      Writeln('A попадает в диапазон 0 - 100.')
    else
      Writeln('A больше 100.')
  else
    Writeln('A меньше 0.');
  Writeln('Press Enter to exit...');
  Readln;
end.

Конструкций со степенью вложенности более 2-3 лучше избегать из-за сложности их анализа при отладке программ.

2.7.6. Оператор ветвления case

Оператор ветвления case является удобной альтернативой оператору if, если необходимо сделать выбор из конечного числа имеющихся вариантов. Он состоит из выражения, называемого переключателем, и альтернативных операторов, каждому из которых предшествует свой список допустимых значений переключателя:

case <переключатель> of
  <список ¹1 значений переключателя>: <оператор 1>;
  <список ¹2 значений переключателя>: <оператор 2>;
      ...
  <список ¹N значений переключателя>: <оператор N>;
  else <оператор N+1>
end;

Оператор case вычисляет значение переключателя (который может быть задан выражением), затем последовательно просматривает списки его допустимых значений в поисках вычисленного значения и, если это значение найдено, выполняет соответствующий ему оператор. Если переключатель не попадает ни в один из списков, выполняется оператор, стоящий за словом else. Если часть else отсутствует, управление передается следующему за словом end оператору.

Переключатель должен принадлежать порядковому типу данных. Использовать вещественные и строковые типы в качестве переключателя не допускается.

Список значений переключателя может состоять из произвольного количества констант и диапазонов, отделенных друг от друга запятыми. Границы диапазонов записываются двумя константами через разграничитель в виде двух точек (..). Все значения переключателя должны быть уникальными, а диапазоны не должны пересекаться, иначе компилятор сообщит об ошибке. Тип значений должен быть совместим с типом переключателя. Например:

program Console;

{$APPTYPE CONSOLE}

uses
  SysUtils;

var
  Day: 1..31;

begin
  Readln(Day);
  case Day of
    20..31: Writeln('День попадает в диапазон 20 - 31.');
    1, 5..10: Writeln('День попадает в диапазон 1, 5 - 10.');
    else Writeln('День не попадает в заданные диапазоны.');
  end;
  Writeln('Press Enter to exit...');
  Readln;
end.

Если значения переключателя записаны в возрастающем порядке, то поиск требуемого оператора выполняется значительно быстрее, так как в этом случае компилятор строит оптимизированный код. Учитывая сказанное, перепишем предыдущий пример:

program Console;

{$APPTYPE CONSOLE}

uses
  SysUtils;

var
  Day: 1..31;

begin
  Readln(Day);
  case Day of
    1, 5..10: Writeln('День попадает в диапазон 1, 5 - 10.');
    20..31: Writeln('День попадает в диапазон 20 - 31.');
    else Writeln('День не попадает в заданные диапазоны.');
  end;
  Writeln('Press Enter to exit...');
  Readln;
end.


2.7.7. Операторы повтора - циклы

Алгоритм решения многих задач требует многократного повторения одних и тех же действий. При этом суть действий остается прежней, но меняются данные. С помощью рассмотренных выше операторов трудно представить в компактном виде подобные действия в программе. Для многократного (циклического) выполнения одних и тех же действий предназначены операторы повтора (циклы). К ним относятся операторы for, while и repeat. Все они используются для организации циклов разного вида.

Любой оператор повтора состоит из условия повтора и повторяемого оператора (тела цикла). Тело цикла представляет собой простой или структурный оператор. Оно выполняется столько раз, сколько предписывает условие повтора. Различие среди операторов повтора связано с различными способами записи условия повтора.

2.7.8. Оператор повтора for

Оператор повтора for используется в том случае, если заранее известно количество повторений цикла. Приведем наиболее распространенную его форму:

for <параметр цикла> := <значение 1> to <значение 2> do
  <оператор>;
где <параметр цикла> - это переменная любого порядкового типа данных (переменные вещественных типов данных недопустимы); <значение 1> и <значение 2> - выражения, определяющие соответственно начальное и конечное значения параметра цикла (они вычисляются только один раз перед началом работы цикла); <оператор> - тело цикла.

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

program Console;

{$APPTYPE CONSOLE}

uses
  SysUtils;

var
  I: Integer;

begin
  for I := 1 to 10 do Writeln(I);
  Writeln('Press Enter to exit...');
  Readln;
end.

Заметим, что если начальное значение параметра цикла больше конечного значения, цикл не выполнится ни разу.

В качестве начального и конечного значений параметра цикла могут использоваться выражения. Они вычисляются только один раз перед началом выполнения оператора for. В этом состоит важная особенность цикла for в языке Delphi, которую следует учитывать тем, кто имеет опыт программирования на языках C/C++.

После выполнения цикла значение параметра цикла считается неопределенным, поэтому в предыдущем примере нельзя полагаться на то, что значение переменной I равно 10 при выходе из цикла.

Вторая форма записи оператора for обеспечивает перебор значений параметра цикла не по возрастанию, а по убыванию:

for <параметр цикла> := <значение 1> downto <значение 2> do
  <оператор>;

Например, в результате выполнения следующей программы на экран будут выведены значения параметра цикла в порядке убывания (от 10 до 1):

program Console;

{$APPTYPE CONSOLE}

uses
  SysUtils;

var
  I: Integer;

begin
  for I := 10 downto 1 do Writeln(I);
  Writeln('Press Enter to exit...');
  Readln;
end.

Если в такой записи оператора for начальное значение параметра цикла меньше конечного значения, цикл не выполнится ни разу.

2.7.9. Оператор повтора repeat

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

repeat
  <оператор 1>;
      ...
  <оператор N>;
until <условие завершения цикла>;

Тело цикла выполняется до тех пор, пока условие завершения цикла (выражение булевского типа) не станет истинным. Оператор repeat имеет две характерные особенности, о которых нужно всегда помнить:

В следующем примере показано, как оператор repeat применяется для суммирования вводимых с клавиатуры чисел. Суммирование прекращается, когда пользователь вводит число 0:

program Console;

{$APPTYPE CONSOLE}

uses
  SysUtils;

var
  S, X: Integer;

begin
  S := 0;
  repeat
    Readln(X);
    S := S + X;
  until X = 0;
  Writeln('S=', S);
  Writeln('Press Enter to exit...');
  Readln;
end.

Часто бывает, что условие выполнения цикла нужно проверять перед каждым повторением тела цикла. В этом случае применяется оператор while, который, в отличие от оператора repeat, содержит условие выполнения цикла, а не условие завершения.

2.7.10. Оператор повтора while

Оператор повтора while имеет следующий формат:
while <условие> do
  <оператор>;

Перед каждым выполнением тела цикла происходит проверка условия. Если оно истинно, цикл выполняется и условие вычисляется заново; если оно ложно, происходит выход из цикла, т.е. переход к следующему за циклом оператору. Если первоначально условие ложно, то тело цикла не выполняется ни разу. Следующий пример показывает использование оператора while для вычисления суммы S = 1 + 2 + .. + N, где число N задается пользователем с клавиатуры:

program Console;

{$APPTYPE CONSOLE}

uses
  SysUtils;

var
  S, N: Integer;

begin
  Readln(N);
  S := 0;
  while N > 0 do
  begin
    S := S + N;
    N := N - 1;
  end;
  Writeln('S=', S);
  Writeln('Press Enter to exit...');
  Readln;
end.


2.7.11. Прямая передача управления в операторах повтора

Для управления работой операторов повтора используются специальные процедуры-операторы Continue и Break, которые можно вызывать только в теле цикла.

Процедура-оператор Continue немедленно передает управление оператору проверки условия, пропуская оставшуюся часть цикла (рисунок 2.4):


Рисунок 2.4. Схема работы процедуры-оператора Continue


Процедура-оператор Break прерывает выполнение цикла и передает управление первому оператору, расположенному за блоком цикла (рисунок 2.5):


Рисунок 2.5. Схема работы процедуры-оператора Break


2.7.12. Оператор безусловного перехода

Среди операторов языка Delphi существует один редкий оператор, о котором авторы сперва хотели умолчать, но так и не решились. Это оператор безусловного перехода goto ("перейти к"). Он задумывался для того случая, когда после выполнения некоторого оператора надо выполнить не следующий по порядку, а какой-либо другой, отмеченный меткой, оператор.

Метка - это именованная точка в программе, в которую можно передать управление. Перед употреблением метка должна быть описана. Раздел описания меток начинается зарезервированным словом label, за которым следуют имена меток, разделенные запятыми. За последним именем ставится точка с запятой. Типичный пример описания меток:

label
  Label1, Label2;

В разделе операторов метка записывается с двоеточием. Переход на метку выполняется с помощью зарезервированного слова goto, за которым следует имя метки:

program Console;

{$APPTYPE CONSOLE}

uses
  SysUtils;

label
  M1, M2;

begin
  M1:
  Write('Желаем успеха ');
  goto M2;
  Write('А этого сообщения вы никогда не увидите!');
  M2:
  goto M1;
  Writeln('в освоении среды Delphi!');
  Writeln('Press Enter to exit...');
  Readln;
end.

Эта программа будет выполняться бесконечно, причем второй оператор Write не выполнится ни разу!

Внимание! В соответствии с правилами структурного программирования следует избегать применения оператора goto, поскольку оно усложняет понимание логики программы. Оператор goto использовался на заре программирования, когда выразительные возможности языков были скудными. В языке Delphi без него можно успешно обойтись, применяя условные операторы, операторы повтора, процедуры Break и Continue, операторы обработки исключений (последние описаны в главе 4).