Основные конструкции и операторы

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

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

q      язык программирования описывает только действия;

q      не любые действия, а только простейшие;

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

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

 

 Пример. Если рабочий день закончился, то можно идти домой, иначе необходимо продолжать работать.

 

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

 

Пример.

Для каждого числа от 1 до 100 делаем:

q      находим сумму его делителей;

q      печатаем найденную сумму.

 

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

 

·         Записываем алгоритм на русском языке.

·         Выделяем в полученной записи команды.

·         Записываем выделенные команды командами языка программирования.

 

Пример 1. Требуется ввести два числа, найти их среднее арифметическое и вывести полученное значение на экран монитора.

Требуемый алгоритм выглядит следующим образом:

1.        Ввести число А.

2.        Ввести число В.

3.        Вычислить С = (А + В) / 2.

4.        Вывести значение С.

Ясно, что для записи программы по этому алгоритму нужны команды ввода значений переменных величин, вывода полученных значений и вычисления значений. В языке Паскаль команда ввода записывается словом read, команда вывода словом write. Команда вычисления записывается с помощью знака := (но называется эта команда не вычислением, а присваиванием). Теперь мы наш фрагмент программы можем записать так:

   read(A);

   read(B);

   C:=(A+B)/2;

   write(C);

 

Примечание

Буквы A, B, C обозначают переменные величины, то есть величины, чье значение можно изменять. Записывать имена переменных большими буквами совершенно необязательно. Каждая команда заканчивается точкой с запятой. Команды в программе на языке Паскаль не нумеруются и их можно располагать совершенно произвольным образом, компьютер же будет их читать слева направо и сверху вниз.

 

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

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

Оператор присваивания в языке Паскаль имеет следующую форму:

Переменная : = Арифметическое или логическое выражение

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

q      Тип выражения, вычисляемого справа от знака оператора, должен совпадать с типом переменной, указанной слева. Целое число должно равнятся целому. Действительное число может равняться любому.

q      Необходимо следить за числовым интервалом, в который попадает вычисляемое значение. Это следует из того, что каждый тип имеет вполне определенный допустимый интервал значений. Если ваше выражение даст значение за пределами этого интервала, то в ходе работы программы возникнет ошибка, которую компилятор не сможет обнаружить. Например, целые числа определены в интервале от –32 768 до 32 767. Более подробно обо всех эти проблемах можно почитать в разделе, посвященном типам данных.

Оператор цикла

Пример 2. Найти сумму N-последовательных чисел. То есть найти сумму следующего ряда: 1 + 2 + 3 + .... + N.

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

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

Ввести N

S=0

Для всех i от 1 до N делать

  Начало цикла

   S=S+i

  Конец цикла

Вывести S

Фрагмент Паскаль-программы будет выглядеть так:

read(N);

s:=0;

for i:=1 to n do

   s:=s+i;

write(s);

Примечание

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

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

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

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

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

for i:=1 to 10 do

   i:=i*i;

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

Цикл по параметру (for to do) не единственный в языке Паскаль. Во-первых, возможна запись цикла с параметром, в которой параметр изменяется от большего к меньшему, уменьшаясь на каждом шаге на единицу. Наша программа с таким оператором цикла будет выглядеть так:

read(N);

s:=0;

for i:=n downto 1 do

   s:=s+i;

write(s);

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

read(N);

s:=0;i:=1;

while i<=n do

  begin

   s:=s+i;i:=i+1;

  end;

write(s);

read(N);

s:=0;i:=1;

repeat

   s:=s+i;i:=i+1;

until i>n;

write(s);

 

Цикл while do

После слова while записывается условие, а после слова do записывается выполняемый оператор, который, конечно, может быть сложным. Что такое сложный оператор видно на примере цикла в форме while. А именно, сложным оператором мы будем называть группу операторов записанных между ключевыми словами begin end.

Данный цикл исполняет оператор, записанный после слова do, до тех пор, пока истинно условие. Здесь сначала проверяется условие, а затем выполняется оператор, поэтому такая конструкция называется циклом с предусловием.

Цикл repeat ... until

После слова until записывается условие. Операторы, записанные между словом repeat и словом until, исполняются до тех пор, пока условие не станет истинным. В данном типе цикла сначала выполняются действия, а затем проверяется условие, поэтому такая конструкция называется циклом с постусловием.

Отличия циклов по условию от цикла с параметром

В циклах по условию нет понятия параметра, и поэтому все переменные, используемые внутри цикла, могут меняться произвольным образом.

Параметр "Цикла по параметру" изменяется только на 1. Прибавить (или отнять) 1 к числу проще, чем произвольное число, поэтому операция +1 обрабатывается компилятором быстрее, чем произвольное сложение, и, следовательно, цикл по параметру работает быстрее, чем цикл по условию.

Условный оператор (оператор выбора между несколькими действиями)

Пример 3. Ввести множество чисел и определить, сколько среди них положительных.

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

Алгоритм будет выглядеть так:

Введем N

s=0

Для всех i от 1 до N делать

  Начало

   Ввести Число

   Если Число > 0 То s=s+1

  Конец

Вывести s

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

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

read(n);

s:=0;

for i:=1 to n do

  begin

   read(a);

   if a>0 then s:=s+1;

  end;

write(s);

Английское слово "if" переводится как "если", а оператор:

if a>0 then s:=s+1;

называется условным оператором. Его устройство таково: после слова if записывается условие, а после слова then записывается один оператор (можно сложный), который исполняется, если условие оказывается истинным. Условный оператор имеет еще одну форму:

if условие then оператор else оператор;

В этой форме, если условие истинно, то исполняется оператор, записанный после слова then, а если условие ложно, выполняется оператор, записанный после слова else.

Примечание

Те, кто только начал практиковаться в программировании на языке Паскаль, часто допускают грубую ошибку в условном операторе. Рассмотрим пример:

if t=5 then t:=6; else t:=8;


В записанном операторе после t:=6 стоит точка с запятой. Ставят ее из тех соображений, что любой оператор в языке Паскаль завершается точкой с запятой. Но завершается так логически законченная конструкция. В данном случае на t:=6 конструкция условного оператора еще не завершена, и точка с запятой здесь не к месту. Ставить ее нужно только после оператора, следующего за словом else. Следовательно, правильная конструкция такова:
if t=5 then t:=6 else t:=8;

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

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

У программы должно быть имя. Имя программы — это набор латинских букв и цифр. Имя может быть почти произвольным, но оно не должно совпадать с ключевыми словами, то есть такие слова, как "read", "write", "for", не могут быть именем программы. Имя программы также не может начинаться с цифры, хотя цифры могут в нем присутствовать. Имя программы не может совпадать с именами используемых в программе переменных. Оно пишется после ключевого слова program. После имени программы обязательно ставится точка с запятой, например:

Program proba;

Далеко не все команды, которые мы можем записать в программе, непосредственно обрабатываются компилятором. Беда в том, что возможных команд слишком много, чтобы обязывать компилятор знать их все. Поэтому в современных языках программирования используется такое понятие, как библиотека готовых команд. И, если мы используем что-то неизвестное компилятору, мы должны указать имя библиотеки, где записан код этой команды. Приведем пример, как это сделать.

Предположим, что у нас есть желание использовать процедуру clrscr, она очищает экран. Если мы просто вставим эту команду в текст программы, то компилятор сообщит нам, что такой идентификатор ему не известен. Поэтому необходимо обратится к справочнику языка, найти в нем описание clrscr, а в описании найти слово unit. И то, что будет записано после этого слова, есть имя библиотеки. Конкретно в нашем случае имя библиотеки будет crt. А указать это имя в программе можно так:

Uses crt;

Теперь наша программа будет выглядеть следующим образом:

Program proba;

  Uses crt;

Далее в программе следует описание типов переменных:

Var

  список переменных : описание типа;

  список переменных : описание типа;

  ...

  список переменных : описание типа;

В языке Паскаль много различных типов, но нам сейчас достаточно одного. Договоримся, что вводимые нами числа будут обязательно целыми. Тип целых чисел в языке Паскаль называется integer. Теперь наша программа будет выглядеть так:

Program proba;

  Uses crt;

  Var

    n,s,i,a:integer;

Далее записывается текст программы, заключенный между словами begin (начало) и end (конец).

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

Окончательный вариант программы будет выглядеть  так:

Program proba;

  Uses crt;

  Var

    n,s,i,a:integer;

begin

  read(n);

  s:=0;

  for i:=1 to n do

    begin

      read(a);

      if a>0 then s:=s+1;

    end;

  write(s);

end.

Примечание

Блок описания переменных необязательно содержит описания только одного типа. После слова var можно создать сколько угодно описаний.

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

Конструкция case

Рассмотрим следующую задачу: пусть две величины L и U могут принимать только три значения, причем эти величины взаимосвязаны, то есть величина U принимает свои значения в зависимости от того, какое значение принимает величина L. Предположим, что взаимосвязь устанавливается следующим образом: при L=1 U=4; при L=4 U=–5; при L=0 U=11. Предположим далее, что величина L вводится с клавиатуры, а величина U вычисляется в зависимости от введенного L. Запишем каким образом это можно реализовать на языке Паскаль:

Program example;

  Var

    U,L:integer;

begin

  read(L);

  case L of

    1: U:=4;

    4: U:=-5;

    0: U:=11;

  end; { Этот end завершает выполнение case }

end.

Рассмотрим еще одну интересную ситуацию. Предположим, что взаимосвязь L с U немного усложнилась. Старые значения связаны так же, как и раньше, но теперь L может принимать любые значения, и, если L принимает значение, отличное от уже описанных, то U=20. Опишем эту ситуацию с помощью конструкции case (или оператора выбора).

Program example;

  Var

    U,L:integer;

begin

  read(L);

  case L of

    1: U:=4;

    4: U:=-5;

    0: U:=11;

    else U:=20;

  end; { Этот end завершает выполнение case }

end.

При исполнении этой конструкции, если L примет значение, отличное от 1, 4, 0, то U примет значение 20. Возможна и такая структура оператора, при которой U будет принимать одно и то же значение при различных L. Приведем пример:

Program example;

  Var

    U,L:integer;

begin

  read(L);

  case L of

    1,5,8: U:=4;

    4,7: U:=-5;

    0,3: U:=11;

    else U:=20;

  end; { Этот end завершает выполнение CASE }

end.

Заключение

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

 

if Then else for to do
если То иначе для до делать

 

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

И последнее, обратите внимание на то, каким образом записываются операторы. Каждый оператор, если он связан с вышестоящим, смещается немного вправо. Например, вот так:

for i:=1 to 10 do

  s:=s+1;

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

 

Операции языка Паскаль

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

Арифметические операции

Для начала можно, конечно, дать определение арифметического выражения, но будем надеяться, что все понимают что это такое. В табл. 1.1 приводится список арифметических операций.

Таблица 1.1. Арифметические операции

Знак Операция
- Вычитание
+ Сложение
* Умножение
/ Деление
Div Целочисленное деление
Mod Остаток от деления

 

Высший приоритет в арифметическом выражении имеют следующие операции: div, mod, *, /. Низший приоритет имеют операции +, -. Если две операции одинакового приоритета стоят рядом, то первой выполняется та, которая стоит первой от левого конца выражения. И, конечно, выражение в скобках, так же, как и в математике, имеет более высокий приоритет, чем выражение за скобкой. Приведем несколько примеров правильных арифметических выражений:

x*col-7*(g-u+5)

s/t/y-y+8*(u/7-5-g)*(u-8.78)

5.89+6*(y+7*u*(t+6))

5 mod g (В этом выражении ищется остаток от деления 5 на g.)

g div 2 (В этом выражении вычисляется результат от деления g на 2.)

Примечание

Дробная часть числа отделяется от целой не запятой, как это принято в математике, а точкой.

Некоторые полезные арифметические функции:

q      sin — вычисление синуса. Аргумент задается в радианах;

q      cos — вычисление косинуса. Аргумент задается в радианах;

q      exp — вычисление экспоненты;

q      sqr — вычисление квадрата выражения;

q      sqrt — вычисление квадратного корня выражения;

q      abs — вычисление модуля выражения;

q      arctan — вычисление арктангенса выражения;

q      frac — вычисление дробной части выражения;

q      int — вычисление целой части выражения;

q      round — преобразование к целому типу;

q      random — вычисление случайного числа в указанном интервале.

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

Логические операции

В табл. 1.2 представлен список логических операций.

Таблица 1.2. Логические операции

Операция Пояснение
and Логическое умножение
or Логическое сложение
not Логическое отрицание
xor Логическое деление

 

Приведем определения логических операций.

q      Отрицание. Если логическая величина С является отрицанием логического выражения А, то С истинно, если А ложно, и ложно, если А истинно.

q      Логическое умножение. Если А и В истинны, то С также истинно. Если же хотя бы одно из них ложно, то С также ложно.

q      Логическое сложение. Если А и В ложны, то С также ложно. Если же хотя бы одно из логических выражений А и В истинно, то С также истинно.

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

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

Таблица 1.3. Таблица истинности для всех логических операций

А В not A A and B A or B A xor B
true true false true true false
true false false false true true
false true true false true true
false false true false false false

 

Типы данных

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

Что такое описание типов переменных?

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

Для чего нужно описывать переменные?

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

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

Какие типы переменных существуют в языке Паскаль?

q      integer — целый тип. Данный тип представляет собой целые числа в интервале от –32 768 до 32 767. Отсюда следует, что вам необходимо очень внимательно следить за границами изменения всех целых величин. Особенно следует помнить о параметрах цикла for, так как все они целого типа. И, если вы опишите цикл, параметр которого будет изменяться, например, до 32 788, то ваша программа не будет работать хорошо. Для хранения значения используется 2 байта.

q      longint — это тоже целый тип, но в отличии от типа integer позволяет задавать числа в значительно большем интервале. Интервал изменения для этого типа от –2 147 483 648 до 2 147 483 647. Данный тип позволяет использовать большие целые числа, но он требует памяти в два раза больше, чем тип integer, то есть 4 байта.

q      real — представляет вещественные числа. Используя этот тип данных, можно записать число длиной 11—12 знаков. Для хранения значения отводится 6 байт.

q      byte — является разновидностью целого типа. Интервал изменения от 0 до 255. Для хранения значения используется 1 байт.

q      word — также является разновидностью целого. Интервал изменения от 0 до 65 535. Для хранения значения используется 2 байта.

q      char — символьный тип. Величина этого типа есть ни что иное, как просто любой символ. Для хранения значения используется 1 байт.

q      string — строковый тип данных. Переменная данного типа представляет собой строку символов. Примеры описания:

f:string;

g:string[20];

В первом примере объявлена строка символов максимально возможной длины, то есть 255 символов. Во втором примере объявлена строка символов с максимальной длиной 20 символов. Для хранения каждого символа строки требуется 1 байт.

q      boolean — позволяет представлять логические переменные, которые могут иметь только два значения: истина (единица) или ложь (ноль). Такой узкий интервал представления данных означает, что для данного типа требуется очень мало памяти. Тип boolean — это очень экономный тип данных. Присвоить значение логической переменной можно так: h:=false; (h присваивается значение ложь) или h:=true; (h присваивается значение истина). Логическим переменным можно также присваивать значение логических выражений, например: h:=g<10;. Над логическими переменными можно выполнять логические операции. (Что такое логические операции и как ими пользоваться смотрите в описании условного оператора if). Для хранения значения используется 1 байт.

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

Массивы

Массив — это упорядоченное множество данных одинаковой природы. Например:

F: array[1..10] of integer; (Десять целых чисел.)

H: array[1..100] of string; (Сто строковых величин.)

В качестве примера приведем программу, в которой вычисляются квадраты первых ста целых чисел.

Program example;

  Var

    i:integer;

    d: array[1..100] of integer;

begin

  for i:=1 to 100 do

   begin

     d[i]:=i*i;

     write(d[i]);

   end;

end.

Массивы могут быть многомерными, например, двумерными:

f: array[1..10,1..100] of integer;

Здесь объявлен массив в 10 раз по 100 целых чисел.

Записи

Запись — это сложное данное, содержащее в себе простые данные различной природы. Например:

d: record

    i:integer;

    t:string;

    y:array[1..10] of integer;

   end;

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

d.i:=67;

Имя записи отделяется от имени компонента точкой.

Файловые переменные

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

Задача. Ввести N чисел, записать их в файл, затем прочитать из файла и вывести на экран.

Program example;

  Uses crt;

  Var

    i,a,n:integer;

    f:file of integer; {Обьявляется файловая переменная,

                      далее с ней будет связан файл целых чисел}

begin

  read(n);

  assign(f,'file'); {С файловой переменной связывается

                     файл целых чисел по имени file}

  rewrite(f); {Создается файл, связанный с файловой переменной f,

               если такой файл уже есть,

               то он уничтожается и создается заново}

  for i:=1 to n do

    begin

      read(a); {Запрашивается с клавиатуры очередное число}

      write(f,a); {Введенное выше число записывается в файл}

    end;

  close(f); {Файл закрывается}

  reset(f); {Файл открывается. Данной процедурой можно открыть

             только уже существующий файл.

             После выполнения данной процедуры

             над файлом можно выполнять любые операции}

  for i:=1 to n do

    begin

      read(f,a); {Очередное число читается из файла}

      writeln(a); {Прочитанное выше число выводится на экран монитора}

    end;

end.

 

Массивы, записи и файлы — это сложные структуры данных, им дальше посвящены специальные главы. А сейчас рассмотрим еще одну интересную возможность. Любой язык профессионального программирования разрешает программисту создавать собственные типы данных. Есть такая возможность и в языке Паскаль.

Типы данных, определенные пользователем

Что такое типы, определенные пользователем?

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

Type

  shablon=array[1..100] of real;

Var

  t,y,u: shablon;

Свойства, определенные для shablon, переходят на переменные, указанные в описании var, а shablon называется новым типом данных. Это означает, что с помощью shablon мы можем определять тип переменных, но shablon не является переменной и его нельзя использовать как переменную. Конечно, в таком простом примере не обязательно вводить новую структуру. Мы могли бы решить эту же проблему следующим описанием:

Var

  t,y,u: array[1..100] of real;

Но достаточно часто встречаются ситуации, в которых собственные типы существенно экономят место в описаниях. Именно такая ситуация представлена в следующем примере. Здесь описываются переменная и массив типа запись с одинаковой структурой.

Program example;

  Var

    a: record

        i:integer;

        f:string;

        g:real;

       end;

    b:array[1..100] of record

    i:integer;

    f:string;

    g:real;

end;

Это же самое можно сделать несколько короче:

Program example;

  Type 

    d=record

       i:integer;

       f:string;

       g:real;

      end;

  Var

    a: d;

    b: array[1..100] of d;

Здесь переменная a объявлена типом d, это означает, что все описания, сделанные для d, автоматически переходят на a. Так же происходит и с объявлением массива b. Чем длиннее описание переменной, тем выгоднее применить собственные типы данных.

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

Константы

Константа — это нечто постоянное, неизменяемое. И конечно, это совершенно не обязательно числовая величина.

Объявление константы

Объявляется константа в блоке объявлений следующим образом:

const

  a=79;

  d=4.5;

  s='hh';

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

const

  a='gfgfgf';

  s=a+'dsd';

  w=5.6;

  q=w + 7;

Использование константы

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

Пример использования:

Program example;

  Uses crt;

  Const

    g=9.8;

  Var

    max,s,t:real;

begin

  read(max);

  t:=0;

  repeat

    s:=g*t*t/2;

    writeln('s=',s,' t=',t);

    t:=t+0.1;

  until t>=max;

end.

 

Строковые величины

Строковый тип данных — это тип данных похожий на массив символьного типа. Если имеется данное, определенное как:

f: array[1..100] of char;

то это же данное возможно определить так:

f: string[100];

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

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

q      copy — копирует из строки подстроку;

q      delete — уничтожает в строке подстроку;

q      insert — вставляет строку в строку;

q      length — вычисляет длину строки;

q      pos — вычисляет номер символа, начиная с которого подстрока входит в строку.

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

А сейчас приведем несколько примеров, на которых можно посмотреть, как работать с данными этого нового типа.

 

Пример 1. Вычеркнуть заданный символ из строки везде, где он встречается.

Искомое решение состоит из двух циклически выполняемых операций:

q      поиск вхождения символа в строке;

q      если вхождение найдено, то символ уничтожается.

Program example;

  Uses crt;

  Var

    s:string;

    c:char;

    n:integer;

begin

  clrscr;

  readln(s);readln(c);

  repeat

    n:=pos(c,s);

    if n>0 then delete(s,n,1);

  until n=0;

  write(s);

end.

 

 

Массивы

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

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

Кроме простых типов данных (integer, boolean, real и т. д) в языке Паскаль предусмотрена такая структура данных как массив. Массив — это упорядоченное множество элементов какой-либо природы. Приведем несколько примеров описания массивов:

a: array[1..10] of integer;

d,c: array[1..100] of boolean;

f: array[1..10,1..100] of real;

В первом примере описан массив из десяти целых чисел с именем а. Во втором примере описано два массива d и с, состоящих из 100 логических значений. И в третьем примере описан двумерный массив f вещественных чисел с границами изменения индексов от 1 до 10 и от 1 до 100.

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

a[1]=2;

b[1,3]=x+5;

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

 

Пример 1. Дан массив чисел. Найти наибольший элемент массива.

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

Program example;

  Uses crt;

  Var

    a:array[1..1000] of real;

    i,n:integer;

    max:real;

begin

  clrscr;

  write('Сколько чисел - ');

  readln(n);

  for i:=1 to n do

    begin

      write(i,') ');

      readln(a[i]);

    end;

  max:=a[1];

  for i:=2 to n do

    if a[i]>max then max:=a[i];

  write('Наибольшее =',max);

end.

 

Множества

Рассмотренные нами ранее массивы можно определить как упорядоченное множество. В языке Паскаль есть еще один тип данных, который представляет собой обычное множество, то есть неупорядоченное. Данное понятие чем-то похоже на понятие массива. Массив — это набор элементов. Множество — также набор элементов, но, в отличии от массива, множество — неупорядоченный набор. То есть, если мы не можем ни для одного элемента указать его порядковый номер, то это множество, а если можем, то это массив. Множество — очень своеобразный тип данных. Так как его элементы никак не упорядочены, то мы не можем обратиться к его элементам и вынуждены работать с ним, как с единым целым. Однако, есть ситуации, когда этого достаточно. Например, мы вводим строку символов и желаем узнать, состоит ли она из одних цифр или там есть и другие символы. С  использованием структуры множества задача решается легко и просто, и ее решение мы приведем далее. А сейчас обсудим, как множество определить, задать и какие операции над ним можно выполнять.

Тип "множество" определяется через базовые простые типы. Но так как множество может содержать не более чем 255 элементов, то типы integer, real, string не подходят для создания множества. Базовые типы для множеств — это типы char, boolean, byte. Также множество можно определить через типы-перечисления. С помощью базовых типов множество можно задать так:

a: set of byte;

b: set of boolean;

c: set of char;

Элементами этих множеств будут все значения, допустимые для данных типов.

С помощью типа-перечисления и типа-диапазона множество можно определить так:

Type

  y=(p1,p2,p3,p4,p5,p6,p7,p8);

  d=p4..p7;

Var

  a: set of y;

  s: set of d;

В этом примере мы создаем два собственных типа. Идентификатор y —перечисляемый тип,  идентификатор d — тип-диапазон. Соответственно идентификаторы a и s — множества типа-перечисления и типа-диапазона.

Еще одно важное понятие, относящиеся к множествам, это операция определения содержимого множества. Такая операция в языке Паскаль называется конструктор.

Пример:

a:=['w','r'];

В этом операторе создано множество с именем а, состоящее из двух литер 'w' и 'r'.

А теперь перечислим операции над множествами — табл. 1.4.

Таблица 1.4. Операции над множествами

Знак Наименование операции Получаемый результат
+ Объединение множеств Множество, содержащее все элементы объединяемых множеств
- Вычитание множеств Множество, содержащее все элементы уменьшаемого множества, за исключением тех элементов, которые содержатся в обоих множествах
* Пересечение множеств Множество, содержащее только те элементы, которые одновременно содержатся во всех множествах, чье пересечение находится
= Проверка эквивалентности двух множеств Логическая величина, которая принимает значение истина, если множества эквиваленты, и ложь, если множества неэквивалентны
<> Проверка неэквивалентности двух множеств Логическая величина, которая принимает значение ложь, если множества эквиваленты, и истина, если множества неэквивалентны
>= Проверка, является ли правое множество подмножеством левого Логическая величина, которая принимает значение истина, если правое множество есть подмножество левого, и ложь, если это не так
in Проверка, входит ли элемент, указанный слева, в множество, указанное справа Логическая величина, которая принимает значение истина, если элемент содержится во множестве, и ложь, если это не так
<= Проверка, является ли левое множество подмножеством правого Логическая величина, которая принимает значение истина, если левое множество есть подмножество правого, и ложь, если это не так

 

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

Program example;

  Uses crt;

  Var

    c,a,b:set of char;

    q:boolean;

begin

  clrscr;

  a:=['a','s','d','f','g'];

  b:=['s','f','x'];

  c:=a+b;

  { Множество c='a','s','d','f','g','s','f','x'}

  c:=a*b;

  { Множество c='s','f'}

  c:=a-b;

  { Множество c='a','d','g'}

  q:=a=b;

  { Логическая переменная принимает значение ложь }

  q:=a<>b;

  { Логическая переменная принимает значение ложь }

  q:=b<=a;

  { Логическая переменная принимает значение ложь }

  q:=b>=a;

  { Логическая переменная принимает значение ложь }

  q:='x' in b;

  { Логическая переменная принимает значение истина }

end.

Примечание

Конструктор можно использовать не только для первоначального задания множества. Конструктор может участвовать и в операциях над множествами. Можно записать следующие операторы: f:=f+['h'];f:=f+['g']-['f','g'];. Пустое множество задается так: f:=[];. Вполне возможно и вычисление сложных выражений, содержащих различные операции над множествами и скобки. Например:
f:=['g','f','d']-(g*h+['w']);

Записи

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

Var

  book_cost,book_volum:integer;

  book_family:string;

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

Решением проблемы будет создание структуры данных, которая заключала бы в себе данные самой разной природы. Таковой структурой и являются записи языка Паскаль. Для пояснения этого термина возьмем тот же пример и опишем запись book.

Var

  book: record

         cost,volum: integer;

         family: string;

        end;

Слово end завершает описание записи. Теперь наши три переменные связаны между собой общим названием объекта. Это означает, что мы можем передать данные на объект куда угодно, используя только имя объекта, а если нам потребуется работать с файлами данных, то мы просто создадим файл, содержащий данные типа запись. А сейчас, чтобы понять, как работать с этой сложной структурой, рассмотрим пример.

 

Пример. Сохранение в файле анкетных данных сотрудников.

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

Опишем структуру записи:

Program example;

  Uses crt;

  Type

    shablon=record

             d: integer;

             z: integer;

             f: string;

             e: string;

             t: string;

            end;

  Var

    i,n: integer;

    anketa: shablon;

    h: file of shablon;

begin

  clrscr;

  assign(h,'anketa.dat');reset(h);

  write(' Сколько данных вводить'); readln(n);

   for i:=1 to n do

     begin

       write(' Введите фамилию ');readln(anketa.f);

       write(' Введите имя '); readln(anketa.e);

       write(' Введите отчество '); readln(anketa.t);

       write(' Введите стаж работы '); readln(anketa.d);

       write(' Введите величину месячного заработка '); readln(anketa.z);

       write(h,anketa);

     end;

   close(h);

end.

Структура записи еще интересна тем, что ее компонентом может быть массив. Конечно, это ясно уже из предыдущего примера, так как тип string — это не что иное, как массив типа char. Но чтобы лучше уяснить эту возможность, рассмотрим еще одну задачу. Возьмем тот же пример с анкетой, но добавим в анкету еще один пункт: предположим, есть необходимость запоминать все суммы, выплаченные человеку. Для учета нового пункта необходимо завести массив типа real. Покажем, как это можно сделать, но для краткости не будем записывать данные в файл.

Program example;

  Uses crt;

  Var

    j,i,n,m: integer;

    anketa:array[1..10] of  record

           d: integer;

           z: integer;

           f: string[20];

           e: string[20];

           t: string[20];

           g: array[1..100] of real;

          end;

begin

  clrscr;

  write(' Сколько данных вводить'); readln(n);

  for i:=1 to n do

    begin

      with anketa[i] do

        begin

          write(' Введите фамилию ');readln(f);

          write(' Введите имя '); readln(e);

          write(' Введите отчество '); readln(t);

          write(' Введите стаж работы '); readln(d);

          write(' Введите величину месячного заработка '); readln(z);

          write(' Сколько выплат было произведено');readln(m);

          for j:=1 to m do

            read(g[j]);

        end;

    end;

end.

Обратите внимание на оператор with. Этот чрезвычайно полезный оператор позволяет не упоминать имя записи, а записывать только имена компонентов записи. Действует он, от слова begin до соответствующего слова end. Использование with накладывает определенные ограничения на имена переменных. Например, в приведенной ранее программе вполне можно объявить переменную с именем t, несмотря на то, что есть компонент записи с таким именем. Однако использовать эту переменную в пределах действия with уже нельзя, так как этот оператор не сможет отличить компонент записи с именем t от переменной с именем t, что вызовет сообщение об ошибке от компилятора.

Графика языка Паскаль

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

q      line — процедура рисования отрезка. Аргументы процедуры: координаты точек концов отрезка. Отрезок рисуется от начальной точки до последней. Пример: line(x1,y1,x2,y2);. Отрезок рисуется цветом, установленным последней процедурой setcolor.

q      circle — процедура рисования окружности. Аргументы процедуры: координаты центра и радиус. Пример: circle(x,y,r);. Окружность рисуется цветом, установленным последней процедурой setcolor.

q      putpixel — процедура установки точки. Аргументы процедуры: координаты точки и цвет точки. Пример: putpixel(x,y,15);.

q      initgraph — процедура инициализации графического экрана.  Данная процедура должна выполняться перед первым вызовом графической процедуры или функции. Пример: initgraph(driver,mode,'');. Внутри апострофов должно стоять имя драйвера и указан путь к нему, но это не обязательно. Если имя пропущено, то будет загружен драйвер egavga.bgi из текущего каталога.

q      getpixel — функция, определяющая цвет заданной точки. Аргументы процедуры: координаты точки. Пример: c:=getpixel(x,y);.

q      setfillstyle — процедура, определяющая способ закраски контура. Аргументы процедуры: шаблон закраски и цвет закраски. Пример: setfillstyle(1,8);.

q      floodfill — процедура, осуществляющая закраску. Аргументы процедуры: координаты точки, в которую льется краска, и номер цвета контура, до которого льется краска. Краска начинает литься в указанную точку и растекаться во всех направлениях до тех пор, пока не встретит контур указанного цвета. Если контур не встретится, то краска зальет весь экран. Пример: floodfill(x,y,6);.

q      setcolor — процедура установки цвета для рисующих процедур. Аргумент процедуры: номер устанавливаемого цвета. Пример: setcolor(9);.

q      setbkcolor — процедура установки цвета фона. Аргумент процедуры: номер устанавливаемого цвета. Пример: setbkcolor(9);.

q      outtextxy — процедура вывода строки символов в графическом режиме экрана. Аргументы процедуры: координаты точки, с которой начинается вывод строки, и выводимая строка. Выводимая строка может быть как типа string, так и типа char. Пример: outtextxy(12,45,’Пример выводимой строки’);.

q      closegraph — процедура закрытия графического экрана.

q      cleardevice — процедура очистки экрана.

Примечание

Все аргументы всех графических процедур и функций могут быть только целого типа.

 

Пример 1. Простейшая графическая задача.

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

Program example;

  Uses crt,graph; {библиотека graph содержит графические процедуры}

  Var

    i,driver,mode:integer;

begin

  driver:=detect;

  initgraph(driver,mode,'');

  for i:=1 to 8 do

    begin

      setfillstyle(1,i);

      setcolor(i);

      circle(300,180-i*23,150-i*18);

      floodfill(300,180-i*23,i);

    end;

  closegraph;

end.

 

Пример 2. Построение динамических объектов — изображение летящего отрезка. Идея решения следующая: изобразим некоторый отрезок с помощью процедуры line. Затем будем циклически выполнять два действия:

q      пририсовывать одну точку цвета отрезка к правому концу отрезка;

q      закрашивать крайнюю левую точку отрезка точкой цвета фона.

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

Program example;

  Uses crt,graph;

  Var

    driver,mode,i:integer;

begin

  driver:=detect;

  initgraph(driver,mode,'');

  setbkcolor(0);setcolor(6);

  line(5,150,25,150);

  for i:=5 to 600 do

    begin

      putpixel(i,150,0);

      putpixel(i+21,150,6);

      delay(10);

    end;

end.

 

Процедуры и функции

Большинство современных программных средств представляют собой огромные тексты в тысячи, десятки тысяч и даже сотни тысяч команд. Разобраться в такой программе исключительно сложно, но и написать такую программу не менее трудно. Чем больше объем информации, тем труднее держать его в голове. Эта сложная проблема решается очень просто. Если вы не можете обработать задачу целиком, разбейте ее на части, решите по частям и потом сложите полученные небольшие программки вместе.

 

Пример 1. Вычисление суммы факториалов.

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

сумма=0

цикл по i от 1 до N

  начало

    сумма=сумма+факториал(i)

  конец

конец алгоритма

Мы написали команду факториал(i) с таким расчетом, что исполнитель данного алгоритма, дойдя до команды, вычислит факториал от числа i и использует его для дальнейших расчетов. Вообще говоря, в своей вычислительной практике мы часто так поступаем. Например, в выражении 5*sin(x) предполагается, что метод вычисления синуса известен. Если все же это не так, и исполнитель не знает, как вычислить факториал, то нужно описать метод вычисления, причем не обязательно в главном алгоритме.

Сейчас запишем алгоритм функции для расчета факториала:

факториал=1

цикл по j от 1 до i делать

  начало

    факториал=факториал*j

  конец

конец алгоритма

Итак, исполнитель, отрабатывая главный алгоритм, дойдет до команды факториал(i), после чего он найдет описание алгоритма расчета факториала, вычислит значение факториала, и вернется в главный алгоритм. Мы, таким образом, смогли разбить алгоритм на две независимые части, одна из которых называется главным алгоритмом, а вторая — функцией.

Программа будет выглядеть так:

Program example;

  Var

    n,i,s:integer;

    { Начало описания функции }

    function factorial(m:integer):integer;

    var

     j,k:integer;

    begin

      k:=1;

      for j:=1 to m do k:=k*j;

      factorial:=k;

    end;

    { Конец описания функции }

begin

  readln(n);s:=0;

  for i:=1 to n do s:=s+factorial(i);

    writeln(s);

end.

Как это работает? Работа программы начинается не в теле функции, а после слова begin, которым начинается главная программа. В тот момент, когда выполнение программы подходит к вызову функции factorial, управление передается на список операторов, из которых состоит тело функции. Значение параметра i передается параметру m, описанному в заголовке функции, который затем используется в тексте функции (параметр i называется фактическим, а параметр m формальным). Оператор factorial:=k; определяет, какое значение будет являться результатом вычисления функции.

А сейчас необходимо сделать важнейшее пояснение относительно применяемых в этой программе понятий формального параметра и локальной переменной.

Имена параметров, перечисляемые в заголовке функции (в данном случае мы имеем только один параметр m), называются формальными параметрами. Формальность их заключается в том, что они как бы не имеют собственного значения. Значения им передаются в момент вызова функции. Чтобы понять, как это делается, приведем пример. Предположим, что в описании функции Summa дан такой  список формальных параметров: f, g, h, j. И пусть в вызове этой функции дан следующий список фактических параметров: 3, y, 0, u+1. Тогда передача значений фактических параметров формальным параметром произойдет так: f=3; g=y; h=0; j=u+1.

Локальная переменная — это такая переменная, значение которой доступно только в данной конкретной процедуре (что такое процедура будет сказано чуть позже) или функции. Локальными переменными являются любые переменные, описанные внутри процедур и функций. В нашем примере в функции factorial локальными переменными являются переменные m, j, k. Полезность локальных переменных заключается в том, что, именно благодаря им, нам удается изолировать вычислительный процесс, проходящий внутри процедуры или функции, от вычислительных процессов, проходящих в других частях программы. Причем, даже если локальные переменные будут иметь те же самые имена, что и переменные, используемые в главной программе или в других программных блоках, все равно это будут разные, совершенно несвязанные между собой переменные.

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

Program example;

  Var

    n,i,s:integer;

    function factorial(m:integer):integer;

    var

      j:integer;

    begin

      s:=1;

      for j:=1 to m do s:=s*j;

      factorial:=s;

    end;

begin

  readln(n);s:=0;

    for i:=1 to n do s:=s+factorial(i);

  writeln(s);

end.

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

А теперь, что такое процедура?

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

 

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

Напишем программу, выводящую одно такое сообщение:

Program example;

  Uses crt;

  Var

    x,y,i:integer;

begin

  clrscr;

  x:=10;y:=10;

  for i:=1 to 20 do

    begin

      gotoxy(x-3+i,y);write('*');

      gotoxy(x-3+i,y+2);write('*');

    end;

  gotoxy(x-2,y+1);write('*');

  gotoxy(x+17,y+1);write('*');

  gotoxy(x-1,y+1);write('first');

end.

Фрагмент, выделенный жирным шрифтом, рисует рамку. Заметим сразу, что этой части программы достаточно безразлично, чему равны переменные x и y. Это означает, что выделенный фрагмент программы можно записать в виде отдельной процедуры.

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

Program example;

  Uses crt;

    procedure ramka(x,y:integer;s:string);

    var

      i:integer;

    begin

      for i:=1 to 20 do

        begin

          gotoxy(x-3+i,y);write('*');

          gotoxy(x-3+i,y+2);write('*');

        end;

      gotoxy(x-2,y+1);write('*');

      gotoxy(x+17,y+1);write('*');

      gotoxy(x-1,y+1);write(s);

    end;

begin

  clrscr;

  ramka(10,10,'first');

  ramka(15,15,'second');

  ramka(20,20,'third');

end.

 

О передаваемых параметрах

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

Program example;

  Uses crt;

  Type

     c=array[1..100] of integer;

  Var

    a:c;

    i:integer;

    procedure print(g:c);

    var

      i:integer;

    begin

      clrscr;

      for i:=1 to 50 do

        write(' ',g[i]);

    end;

begin

  for i:=1 to 50 do

    a[i]:=i;

  print(a);

end.

В этом примере в главной программе формируется массив, затем он передается как фактический параметр в процедуру print и в ней распечатывается.

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

Program example;

  Uses crt;

  Var

    u:integer;

    procedure goga(var g:integer);

    begin

      g:=20;

    end;

begin

  clrscr;

  goga(u);

  write(u);

end.

Эта программа интересна тем, что она распечатает значение переменной u, равное 20. Однако эта переменная в программе нигде не определяется. Можно предположить, что процедура использует область памяти, в которой хранится переменная u для обработки переменной, объявленной в качестве формального параметра. Это и приводит к эффекту возврата значения.

Вызов процедуры/функции

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

Program example;

  procedure proba1;

  begin { Начало процедуры proba1}

  end; { Конец процедуры proba1}

  procedure proba2;

  begin { Начало процедуры proba2}

  end; { Конец процедуры proba2}

begin { Начало главной программы}

end. { Конец главной программы }

В этом примере в главной программе можно вызывать обе процедуры, и в процедуре proba2 можно вызывать процедуру proba1. Процедуру proba2 внутри процедуры proba1 вызывать нельзя, вследствие общего запрета языка Паскаль использовать имена до их описания.

Program example;

  procedure proba1;

  procedure proba2;

  begin { Начало процедуры proba2}

  end; { Конец процедуры proba2}

  begin { Начало процедуры proba1}

  end; { Конец процедуры proba1}

begin { Начало главной программы}

end. { Конец главной программы }

В этом примере в главной программе доступна только процедура proba1. Процедура proba2 доступна только внутри процедуры proba1.

Из приведенных ранее примеров можно вывести общее правило:

q      модуль можно использовать только внутри того модуля, в котором он описан;

q      модуль может использовать только те модули, которые описаны ранее него.

В табл. 1.5 приведены вызовы нескольких процедур.

Таблица 1.5. Примеры процедур

Правильно Неправильно
Program example;

  procedure proba1;

  procedure proba2;

  begin {Начало процедуры proba2}

  end; {Конец процедуры proba2}

  begin {Начало процедуры proba1}

    proba2;

  end; {Конец процедуры proba2}

begin {Начало главной программы}

  proba1;

end. {Конец главной программы}

Program example;

  procedure proba1;

  procedure proba2;

  begin {Начало процедуры proba2}

  end; {Конец процедуры proba2}

  begin {Начало процедуры proba1}

    proba2;

  end; {Конец процедуры proba2}

begin {Начало главной программы}

  proba2; {Неправильный вызов процедуры}

end. {Конец главной программы}

 

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

Program example;

  forward proba2;

  procedure proba1;

  begin { Начало процедуры proba1}

    proba2;

  end; { Конец процедуры proba1}

  procedure proba2;

  begin { Начало процедуры proba2}

  end; { Конец процедуры proba2}

begin { Начало главной программы}

end. { Конец главной программы }

Ключевое слово forward сообщает компилятору, что описание процедуры proba2 встретится позже.

Файлы данных

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

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

a: file of integer; (Файл целых чисел.)

f: file of string; (Файл строк.)

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

Алгоритм работы с файлом:

1.        Файл специальной процедурой связывается с файловой переменной.

2.        Открывается либо создается файловая переменная (физически будет открыт файл, но программист в этом процессе не участвует).

3.        Выполняются необходимые операции чтения и записи данных.

4.        Файловая переменная закрывается.

 

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

1.        Открывается файл.

2.        Записывается 100 последовательных целых чисел.

3.        Файл закрывается.

4.        Файл открывается.

5.        Считываются числа, содержащиеся в файле, и распечатываются на экран.

Program example;

  Var

    i,u: integer;

  { Определяется переменная, которую затем можно привязать к файлу,

    содержащему целые числа }

    f: file of integer;

begin

{ Файловая переменная привязывается к файлу,

  имя которого указано в апострофах }

  assign(f,’file’);

{ Так как такой файл еще не существует, то он создается и открывается}

  rewrite(f);

{ Числа записываются в файл }

  for i:=1 to 100 do write(f,i);

{ Файл закрывается}

  close(f);

{ Так как файл уже существует, то он просто открывается }

  reset(f);

{ Числа читаются из файла и выводятся на экран дисплея}

  for i:=1 to 100 do

    begin

      read(f,u);

      write(‘ ‘,u);

    end;

end.

 

Файловая переменная не может быть совершенно обычной переменной. Поэтому для файловых переменных предусмотрены специальные процедуры и функции. Краткий список процедур и функций, работающих с файлами в Borland Pascal, приведен далее.

q      assign — процедура, связывающая файл с файловой переменной.

q      reset — открывает существующий файл и устанавливает указатель позиции файла на нулевой элемент.

q      rewrite — создает файл.

q      truncate — обрезает файл, начиная с текущей позиции.

q      seek — устанавливает указатель файла в указанную позицию.

q      eof — функция, возвращающая истину, если был достигнут конец файла, и ложь в противном случае.

q      filesize — вычисляет размер файла в количестве записей того типа, который указан в объявлении файла.

Примечание

Для файла существует такое понятие как указатель на текущую позицию. Это величина целого типа (для Borland Pascal — это величина типа longint), в которой хранится номер текущей позиции файла. При каждой операции чтения/записи указатель смещается на следующую запись. Под записью понимается длина типа, указанного в объявлении файла. Нумерация записей в файле начинается с нуля.

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

Program example;

  Uses crt;

  Var

    i,k:integer;

    f:file of integer;

begin

  clrscr;

  {Открываем файл целых чисел и записываем в него

   100 последовательных чисел}

  assign(f,'proba.dat');rewrite(f);

  for i:=1 to 100 do  write(f,i);

  reset(f);

  { Необходимо убедится, что числа были записаны правильно.

    Для этого читаем файл до тех пор, пока не будет достигнут

    признак конца файла, на каждом шаге читаем очередное значение

    и распечатываем его. }

  while not eof(f) do

    begin

      read(f,k);write(k,'  ');

    end;

  {Обрезаем 10 последних элементов файла.}

  seek(f,filesize(f)-10);  truncate(f);  reset(f);

  writeln;  writeln;

  {Повторяем процедуру чтение и распечатки значений элементов файла}

  while not eof(f) do

    begin

      read(f,k);write(k,'  ');

    end;

end.

 

В дополнение приведём ряд примеров показывающих важные особенности.

Program example;

  Uses crt;

  Var

    a:record

      s:string;

      i:integer;

     end;

     f: file of record

                s:string;

                i:integer;

               end;

begin

  assign(f,'file.dat');rewrite(f);

  a.s:='fsfsfsf';

  a.i:=8;

  write(f,a);

end.

Структуры данных в приведенном примере определены вполне корректно, но в операторе write(f,a); компилятор выдаст сообщение об ошибке. А именно: компилятор сообщит, что имеет место несоответствие типов. Кажется, что типы переменных a и f одинаковы. Однако это не так с точки зрения компилятора. Мы описали две разные структуры, и компилятор справедливо полагает, что они могут быть различны и не берет на себя заботу по проверке их одинаковости.  

Указанная проблема решается следующим образом:

Program example;

  Uses crt;

  Type

    r=record

      s:string;

      i:integer;

     end;

  Var

    a:r;

    f: file of r;

begin

  assign(f,'file.dat');rewrite(f);

  a.s:='fsfsfsf';

  a.i:=8;

  write(f,a);

end.

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

Файл может быть компонентом сложной структуры. Например, вполне допустим массив файлов:

f:array[1..10] file of integer;

Файл вполне может оказаться компонентом записи:

Program example;

  Var

    a:record

      s:string;

      f: file of integer;

     end;

begin

  assign(a.f,'file.dat');rewrite(a.f);

end.

Еще один интересный пример:

Program example;

  Uses crt;

  Type

    d=array[1..10] of integer;

  Var

    f:file of d;

    i:integer;

    a:d;

begin

  clrscr;

  for i:=1 to 5 do a[i]:=i;

  assign(f,'file.dat');rewrite(f);

  write(f,a,a);

  reset(f);

  while not eof(f) do

    begin

      read(f,a);

      for i:=1 to 10 do write(a[i],'  ');

      writeln;

    end;

end.

В этом примере открывается файл массивов. То есть каждая запись файла — это массив длиной в 10 целых чисел. Причем мы можем не определять значения всех десяти элементов, в файле все равно будет записано их десять, как дано в определении массива.

Один и тот же физический файл можно открыть как файл одного типа, а затем его же как файл другого типа:

Program example;

  Uses crt;

  Var

    s:string;

     i:integer;

    f:file of string;

    d:file of integer;

begin

  clrscr;

  assign(f,'file.dat');rewrite(f);

  s:='gdgdgjagdjasg';

  for i:=1 to 10 do write(f,s);

  close(f);

  assign(d,'file.dat');reset(d);

  while not eof(d) do

    begin

      read(d,i);write(i,' ');

    end;

end.

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

Hosted by uCoz