Главная / Информатика / Учебное пособие «Pascal для школы» (9-11 класс)

Учебное пособие «Pascal для школы» (9-11 класс)

Документы в архиве:

41.5 КБ fadeev.doc
1.65 МБ fadeev_pascal.doc

Название документа fadeev.doc

  • Фадеев Игорь Юрьевич

  • Заместитель директора по ИКТ, учитель информатики и ИКТ

  • квалификациионная категория - высшая

  • Государственное образовательно учреждение средняя общеобразовательная школа «Школа здоровья» №48

  • Москва

  • fad_ig@mail.ru, igorfadeev64@yandex.ru

  • http://sch48.mosuzedu.ru



Название документа fadeev_pascal.doc

304


В

Pascal для школы

Фадеев Игорь Юрьевич,

ГОУСОШ №48, Москва

ведение

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

Это, конечно же, шутка. Но ведь в каждой шутке есть доля правды.

Никаких совсем уж принципиальных новшеств при знакомстве с алгоритмами и программами мы не встречаем. Однако, для успешного знакомства нам, как правило, не хватает самой малости - букваря. Той самой элементарной книги, которая рассказала бы основы, азы. Конечно, книг по «основам» очень много. И все они разные: и для опытных программистов и пользователей, и для начинающих. Каждая из таких книг, как правило, посвящена какой-либо одной теме: либо языку программирования, либо устройству компьютера, либо чему-то еще. А если она действительно обзорная (обо всем по порядку), то, наверняка, для маленьких детей: с цветными картинками, с птичками и бабочками. Бесспорно, такие книги нужны. Но нужны они детям...

Разумеется, охватить всё невозможно. За деревьями не увидишь леса. Приоритет темам этой книги назначался исходя из примерной тематики школьного курса программирования и подготовке к сдаче единого государственного экзамена по ИКТ.

Данная книга не содержит таких общеизвестных тем, как «Алгебра логики» или «Системы счисления». Думается, что за более чем полувековую историю существования компьютеров, в этом необходимости нет, да и заниматься программированием, не зная, что такое файл - глупость. Поэтому, если для кого-то неизвестно, как устроен компьютер или что такое папка – можно воспользоваься приложением в конце книги. И есть, конечно же, ещё одна причина: слишком быстро всё меняется. Японские разработчики заявили, что они создадут на базе оптических компьютеров электронный интеллект к 2012 году. Поживем - увидим.

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

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





























Глава I. Общие принципы.


§1.1 Системы программирования.


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

Рассмотрим вначале компилирующие системы.

Предположим, что вам совершенно четко и однозначно поставлена задача, и вы беретесь составить программу для ее решения. Вы пишете программу на листе бумаги, а затем вводите ее в компьютер с помощью текстового редактора. Таким образом вы создаете текстовый файл, содержащий исходный текст вашей программы. После этого вы запускаете в работу программу - компилятор (он же транслятор) и указываете ей на файл с исходным текстом. Компилятор (транслятор) компилирует (масло масляное) ваш файл и создает новый файл – объектный, состоящий из нулей и единиц. Этот новый файл содержит переведенную на машинный язык программу. Но это еще не та программа, которую можно запускать в работу. В ней нет определений стандартных функций, которые находятся в библиотеках. Чтобы включить эти определения в вашу программу, нужно выполнить еще один этап - компоновку. Этим занимается программа - компоновщик (он же редактор связей, он же линкер (от англ. Link)). После того, как компоновщик закончит работу с объектным файлом вашей программы, он создаст новый файл - файл в формате загрузки. Вот это и есть та программа, которую можно загружать в память и запускать на выполнение.

Разумеется, если на каком-либо этапе компилятор или компоновщик обнаружат ошибку, то ее нужно исправить и повторить соответствующий этап.

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

- текстовый редактор для ввода исходного текста программы;

- компилятор для преобразования исходного текста и создания объектного файла;

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

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

Многовато, не правда ли? Кроме того, еще пока нет гарантий что программа будет работать правильно. Для отладки программ применяют специальные программы - отладчики (или Debug-еры). Для работы с файлами библиотек применяют программы-библиотекари.

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

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

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

Вопрос 1.1.1. Чем компиляторы отличаются от интерпретаторов ?

Вопрос 1.1.2. Назовите этапы создания программ в компилирующих системах.


§1.2 Алгоритм

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

Многие думают, что первым за программу берется программист. Это неверно. Программист - один из последних, кто имеет отношение к программе. До него над ее созданием трудятся еще много разных специалистов.

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

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

Третий этап - поиск метода решения задачи, составление сценария её решения. Согласитесь, что способов решения может быть великое множество. Даже поиск корней квадратного уравнения может быть произведён, по крайней мере, шестью способами: с помощью дискриминанта, с помощью четвёртой части дискриминанта (а это разве не одно и то же?), по теореме Виета, выделением полного квадрата, графически и, наконец, методом «тыка», т.е. методом подстановки.

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

Пятый этап - собственно, само составление программы, ввод программы в компьютер и исполнение её на контрольном примере. Здесь программе предоставляют данные, для которых заранее известен результат (подсчитан вручную или получен каким-либо иным способом).

Последний этап, шестой этап - это анализ результатов и отладка программы. Результат, полученный от компьютера, сравнивается с ожидаемым. Почти со стопроцентной вероятностью можно утверждать, что они не совпадают. Стоит привести знаменитую фразу отца кибернетики Норберта Винера: «Если программа заработала сразу – ищите в ней ошибки». Здесь анализируется, на каком из этапов была допущена ошибка. Быть может, это элементарная опечатка оператора при наборе текста программы. А может быть, и сама идея несостоятельна. В любом случае программа отправляется либо в утиль, либо на доработку на тот или иной этап.

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

Хорошо, если все этапы, начиная со второго, выполняются одним человеком (или под руководством одного человека). В этом случае существенно сокращается время разработки.

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

Имея под рукой алгоритм в том или ином виде, записать его на языке программирования - не такая уж сложная задача. Это простой перевод алгоритма из одной формы представления в другую. Другое дело - уметь составлять эти алгоритмы. Это работа, достойная образованного человека. По сути дела, программируя «из головы», мы тоже составляем алгоритм, но записываем его сразу на языке программирования. Это довольно сложный процесс. Гораздо проще сначала записать то же самое на специальном языке, языке алгоритмов, а потом перевести записанное на язык программирования. И, по сути, если не был составлен верный алгоритм (а перед этим не было понято, что же, собственно, требуется при решении задачи и не была проведена её математическая постановка с последующей детализацией), будет абсолютно всё равно, каким языком программирования вы владеете – от простенького FOCAL и до Visual C.

Начнем с того, что такое алгоритм.

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


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

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

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

Алгоритм должен обладать свойствами:

1. Дискретность (прерывность). Алгоритм должен разлагаться на отдельные составляющие, команды. Каждая такая составляющая предписывает исполнителю выполнить какое-то конкретное законченное действие.

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

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

4. Массовость. Однажды составленный для решения какой-либо задачи алгоритм, должен быть пригодным для решения всех (читай: многих) задач подобного типа. Алгоритм не должен зависеть от исходных данных.

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

6. Иерархичность (подчинённость). Как бы ни был сложен алгоритм и из скольких бы узлов он не состоял, всегда существуют основные и вспомогательные узлы. Естественно, вторые «подчиняются» первым. А всё вместе подчинено основной цели – нахождению верного ответа.

7. Эргономичность (удобство). Алгоритм должен быть просто-напросто удобен. И не только для того, кто его пишет, но и для тех, кто с ним просто работает.

Теперь о том, как выглядят алгоритмы.

Провожая ребёнка в школу, родители наказывают ему: «после последнего урока зайди в детский садик, забери сестренку, потом зайди в магазин, купи хлеб, затем вернись домой». Они составляют алгоритм поведения ребёнка на какой-то промежуток времени. В данном случае он являетесь исполнителем этого алгоритма, а сообщен он ему (ребёнку) в обычном словесном описании. Можем записать его на бумаге обычными словами, пронумеровав каждый пункт (каждую команду).

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

Стандарты, определяющие изображение символов, меняются довольно часто. Некоторые из приведенных ниже символов отличаются от тех, которые определены ГОСТ. Эти отличия непринципиальны, однако облегчают запоминание и восприятие языка блок-схем.


Вот эти символы.

hello_html_71e482b9.gif

1. Процесс - вычислительное действие или

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

действий.

hello_html_4f696d8e.gifhello_html_m2a7690f7.gif

2. Условие - проверка одного или несколь-

ких условий.


hello_html_m6fa319b5.gif

3. Ввод или вывод данных.


hello_html_2405dfdf.gif4. Предопределенный процесс - вычисление

по подпрограмме.


hello_html_7f80f8d7.gif

5. Цикл – процесс явных повторений.

hello_html_m79403230.gif

6. Соединитель - разрыв линии потока.




hello_html_m557fc2f7.gif7. Пуск (останов) - начало, конец, останов,

вход и выход в подпрограммах.



hello_html_157d24c.gif

8. Документ - вывод, печать результатов

на бумаге.

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

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


hello_html_m604f2a1f.gif Чтобы исполнитель мог вычислить

длину окружности, ему должен быть

hello_html_m4edf1676.gif известен радиус. Это достигается ко-

hello_html_6bc16c34.gif мандой ввода данных. По команде вы-

полнения действия исполнитель произ-

hello_html_m60a9e078.gifhello_html_m4edf1676.gif водит вычисления. После этого испол-

нителю предписывается команда вывес-

hello_html_4d6a7c1c.gifhello_html_m1047a52.gif ти результат вычисления. На этом ал-

горитм заканчивается.

Это был пример линейного алгоритма.

hello_html_md1a98e8.gif Здесь все команды выполняются пос-

ледовательно, одна за другой. Все ко-

hello_html_m6e407b75.gif

Рис.1

hello_html_160f8d5c.gif манды выстроены в линию.




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

Кажущаяся простота этой задачи приводит к тому, что решение сводится к простому действию – сложить и разделить пополам. Дело в том, что ошибка производится на этапе математического моделирования: среднее арифметическое и средняя скорость – суть разные понятия! Итак, в общем виде дано: S – расстояние, V1 и V2 – скорости автомобиля. Средняя скорость есть общий путь разделить на общее время:

Vср = Sобщ / Tобщ

Sобщ = S + S = 2S

Tобщ = T1 + T2 = S/V1 + S/V2 = S(V1 + V2) / (V1V2)

Vср = 2SV1V2/(S(V1 + V2)) = 2V1V2 / (V1 + V2)

Только теперь получена правильная формула! А алгоритм будет выглядеть так:

hello_html_m604f2a1f.gif


hello_html_m52c92f75.gif

hello_html_m473d96dd.gif


hello_html_m1047a52.gif

hello_html_m4edf1676.gif

hello_html_m712f507c.gif

Рис.2


hello_html_m4edf1676.gifhello_html_m4edf1676.gif

hello_html_377517b7.gif




hello_html_m3348e5a5.gif


Алгоритмы бывают также разветвленные. Разветвленные алгоритмы имеют несколько (больше одной) ветвей. Разветвление производится либо на команде проверки условия, либо на команде выбора (и нигде больше!). Исполнитель должен проанализировать условие, записанное в команде и выбрать ту или иную ветвь для дальнейшего исполнения.

Пример. Составить алгоритм вычисления Y по одной из двух формул:


hello_html_m3efe5f9c.gif Y= X+2 если x<0

hello_html_44bd66e.gif Y=-X-4 если x>=0.


В этом примере для вычисления Y

hello_html_6b1c10f5.gifhello_html_m2823cef2.gifhello_html_m2fd3af.gif

нужно знать значение Х. Оно будет

hello_html_m62372fd2.gif

вводиться. Формула, по которой будет

hello_html_m6e3c78d2.gif

Нет


вычисляться Y, зависит от значения Х.

hello_html_1cbd7991.gifhello_html_75c45759.gif


Да

hello_html_44fe6fc3.gif Проверять будем условие х<0.


Если это действительно так, то будет


hello_html_m1047a52.gifhello_html_m1047a52.gif выполнено вычисление Y по формуле

hello_html_249bbe2.gifhello_html_1554f555.gif

hello_html_m3c31682c.gif Y=X+2, если нет, - то по формуле


hello_html_m43f550f3.gif Y=-X-4. После вычисления Y пути ал-


горитма вновь объединяются: в том и

hello_html_m71217f9a.gif

другом случае производится вывод ре-


зультата вычислений Y. На этом

Рис.3


алгоритм заканчивается.

Любая команда алгоритма (любой символ в его изображении) должна иметь один вход и один выход. Исключение составляют: команда проверки условия (имеет 2 выхода: «да» и «нет» или «тrue» и «false»), команда выбора (содержит ключ выбора и имеет несколько выходов в зависимости от значения ключа) и команда цикла (содержит один вход, один выход, а также вход и выход тела цикла). Команда цикла предписывает исполнителю выполнить группу команд несколько раз подряд. Алгоритмы, содержащие команды цикла, называют циклическими. Группа команд, которая повторяется, составляет тело цикла. Одно исполнение тела цикла называют проходом. В тело цикла могут быть включены любые исполняемые команды, в том числе и команды цикла. При каждом выполнении тела цикла изменяется какое-либо значение. Это значение называется переменной цикла, или его параметром. В изображении символа команды цикла указывается параметр, его начальное, конечное значение и шаг изменения от одного прохода к другому.

Пример. Составить алгоритм вычисления суммы 1+2+3+...+N-1 +N.


hello_html_m3efe5f9c.gif В этом примере в цикле выполня-


hello_html_m62372fd2.gif ется операция сложения. К полученной


hello_html_m7f21f542.gif до текущего прохода сумме добавляет-


hello_html_m62372fd2.gif ся очередное число (переменная цик-


hello_html_ma4bdac1.gif ла). Тело цикла выполняется N раз.


hello_html_m62372fd2.gif Переменная цикла i меняется от 1 до


hello_html_79fa6118.gif N с шагом 1 (если шаг не указан,

hello_html_m311f0002.gifhello_html_m5e46efb9.gif

hello_html_m7a155939.gifhello_html_3b8a6ff7.gif то он равен единице, если нужно ука-

hello_html_4742a535.gif

зать другой шаг, то его записывают

hello_html_4f6a16da.gif

после указания конечного значения,

hello_html_576cf212.gif

hello_html_m36d2df2a.gif например, I = 1, 100, 2


После завершения выполнения цик-

hello_html_57aed7ff.gifhello_html_m36d2df2a.gif

hello_html_m32485b0c.gif ла (после того, как тело цикла будет


hello_html_51d2c5d1.gif выполнено N раз) выполняется ко-


hello_html_m71217f9a.gif манда вывода результатов, и на этом


Рис.4

алгоритм заканчивается.




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

В результате исполнения алгоритма получается какой-либо результат. Этот результат должен быть сообщен (выведен). Следовательно, всякий алгоритм должен содержать команду вывода результатов в том или ином виде.

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

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

Вопрос 1.2.1. Назовите этапы решения задач с помощью компьютера.

Вопрос 1.2.2 Что такое алгоритм?

Вопрос 1.2.3. Какими свойствами должен обладать алгоритм?

Вопрос 1.2.4. Что означает свойство детерминированности алгоритма

Вопрос 1.2.5. Составьте алгоритм вычисления S по одной из трех формул:

S=X+Y если k<2

S=X-Y если k>2

S=X+X если k=2

Вопрос 1.2.6. Сколько команд проверки условия должен содержать алгоритм, если он содержит 3 ветви?

Вопрос 1.2.7. Составьте алгоритм вычисления суммы чисел 2+4+...+N-2 +N. Вопрос 1.2.8. Составьте алгоритм решения квадратного уравнения.


Глава II. Pascal


§2.1 Основы программирования на языке


Первая версия языка Pascal была разработана Н. Виртом в 1968(!) году на кафедре информатики Стэндфордского университета. Благодаря простоте общения с языком, его мощности и удобству, Pascal из учебного языка очень быстро превратился в мощное средство разработки программ. Назван язык в честь великого французского математика и физика Блеза Паскаля.

Мы уже говорили о существовании двух видов систем программирования: интерпретирующей и компилирующей. Pascal относится к компилирующим системам, хотя по самому названию - «Turbo Pascal» - всё становится ясно.

Работа на языке Pascal можно организовать как напрямую, из редактора языка, так и создавая файлы формата «exe», компилируя исходник программы.

Существует, по крайней мере, семь версий языка Pascal. Многие версии имеют непринципиальные отличия в основных операторах, поэтому возможен простой перевод с одной версии языка на другую. Однако не следует преуменьшать и роль отличий между версиями. Версия 3.0, вышедшая в 1985 году с компилятором стандартного языка, стала применяться в качестве «первого» языка программирования. Версия Turbo Pascal 7.0, созданная в 1992 году фирмой Borland International, строится на небольшом количестве базовых понятий, допускает перевод исходных кодов в машинный код простым компилятором и, конечно же, имеет простой по сравнению с остальными языками синтаксис.

К сожалению, практически полное несоответствие программ подготовки школьников к высшей школе и стандартных школьных программ обучения курсу ОИВТ - ИКТ привело к тому, что студентам, поступившим в ВУЗ, почти в ста случаях из ста приходится всё начинать заново. Дело в том, что стандартный школьный BASIC так же похож на Pascal, как «Запорожец» на иномарку класса S. Хотя набор операторов Pascal относительно мал, как и у BASIC, и легко изучаем. Но проблема – в расширении языка в приложениях. В Pascal существует огромное количество библиотек разнообразных процедур, чего, к сожалению, нельзя сказать о BASIC. К BASIC практически неприменимы понятия структурного и объектно-ориентированного программирования. То есть то, из-за чего, собственно, языки Pascal и С и являются основными языками высшей школы.

И позвольте, может быть, грубое и высокомерное замечание. Программисты, начинающие постигать это искусство с изучения BASIC, умственно оболванены. BASIC покалечил разум многих программистов и не известно чего принёс больше – вреда или пользы. Доказательством этого служит то, что Microsoft не выдержала конкуренции с производителями языков программирования. Даже такая прелесть, как Microsoft Visual C++ не стал стандартом «де-факто».


§2.2 Алфавит Pascal

Запись программ производится с помощью различных букв, знаков и служебных символов. Используются все прописные и строчные латинские буквы от А до Z и от а до z. Причем, при записи операторов прописные и соответствующие им строчные буквы считаются эквивалентными. Русские буквы (прописные и строчные) могут быть использованы только в строковых данных и в комментариях. Применение их в операторах и служебных словах недопустимо.

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

При записи программ применяют также цифры от 0 до 9 и практически все дополнительные знаки клавиатуры.

Цифра 0 перечеркивается чертой с правым наклоном, чтобы не путать с буквой О.

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

Вопрос 2.2.1. Какие буквы допустимы при записи операторов и служебных слов на языке Pascal?

Вопрос 2.2.2. Что такое кириллицы? Где их можно применять и где нельзя?

Вопрос 2.2.3. Как различают цифру 0 и букву О?


§2.3 Инструментальная оболочка.

Будем считать, что Pascal уже установлен на диск D: (извините, привычка, потому как на системный диск учили не допускать ничего, кроме «системы») в каталог ТР. Устанавливается Pascal, как правило, при помощи встроенных средств инсталляции. После установки необходимо проверить на системном диске файл autoexec.bat, который должен теперь включить в строку РАТН следующее изменение

PATH C:\WINDOWS; . . . . .; D:\TP\BIN

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

Запускается Pascal файлом turbo.exe, находящимся во вложенном каталоге BIN.

В строке меню, расположенной вверху экрана и активизирующейся при нажатии на F10, содержатся пункты меню:

Fileоперации над файлами (загрузка, сохранение, печать и т.д.);

Edit редактирование текста;

Searchпоиск и замена фрагментов текста;

Run запуск программы, находящейся в рабочей зоне, либо пошаговое её выполнение;

Compileкомпиляция программы рабочей зоны;

Debug инструменты поиска ошибок;

Tools инструменты. Как правило, используются для выполнения некоторых программ, не выходя из Pascal:

Optionsустановка параметров компилятора;

Windowоперации с окнами (открытие, перемещение и т.д.);

Helpсправочная информация.

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


§2.4 Запись программ на Pascal.

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

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



<Раздел объявлений и соглашений>

Program Заголовок программы;

{$ . . .} Глобальные директивы компилятора;

Uses Подключаемые библиотеки;

Label Объявление глобальных меток;

Const Объявление глобальных констант;

Type Объявление глобальных типов;

Var Объявление глобальных переменных;


<Раздел процедур и функций>

Procedure (Заголовок процедуры);

(Function) (Заголовок функции);

Label Объявление локальных меток;

Const Объявление локальных меток;

Type Объявление локальных типов;

Var Объявление локальных переменных;

Begin

<Тело процедуры или функции>

End;


<Раздел основного блока программы>

Begin

<Тело основного блока программы>

End.


Нетрудно заметить, что каждая строка любого раздела завершается точкой с запятой. Исключение составляет строка Begin, что абсолютно логично, и последняя строка End, завершающаяся точкой. Вообще-то, точка с запятой не ставятся после оператора, который открывает структуру, но об этом чуть позже.

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




§2.5 Данные в Pascal

Данные в Pascal бывают пяти классов: простой, структурированный, ссылочный, процедурный и объектный. Любое данное, будь то константа, переменная или что-либо еще, - относится к одному из этих типов.

В свою очередь, числовые данные подразделяются на целочисленный и вещественный тип.

Числовые данные иначе называют арифметическими, строковые - текстовыми.


Целочисленный тип:

Byte 0. . . 255 1 байт без знака

Word 0. . . 65535 2 байта без знака

ShortInt -128. . . 127 1 байт и знак

Integer -32768. . . 32767 2 байта и знак

LongInt -2147483648. . .2147483647 4 байта и знак


Вещественный тип:

Real 2.9*10-39. . . 1.7*1038 6 байт

Single 1.5*10-45. . . 3.4*1038 4 байта

Double 5*10-324. . . 1.7*10308 8 байт

Extended 3.4*10-4932. . . 1.1*104932 10 байт

Comp -9.2*1018. . . 9.2*1018 8 байт


В Pascal наложен запрет на автоматическое преобразование типов, но это не значит, что такое преобразование невозможно. Например, для преобразования типа Real в тип Integer существуют функции Round (округление до ближайшего целого) и Trunc (отсечение дробной части числа). Над этими типами возможно проведение следующих операций: сложение, вычитание, умножение (*), вещественное деление (/) и целочисленное деление (div).


Логический тип: Boolean (1 байт), ByteBool (1 байт), WordBool (2 байта) и LongBool (4 байта). Переменные этого типа могут принимать только значения False (ложь) и True (Истина). Над этими типами разрешены только две операции сравнения: «равно» (=) и «неравно» (< >)


Символьный тип: Char (длина – 1 байт). Предназначен для хранения одного из 256 символов кода ASCII. Буквы ’а’...’z’ и ’A’...’Z’, цифры ’0’...’9’, знаки препинания и специальные символы. Тип String предназначен для хранения слов, предложений, последовательностей символов.

Все эти типы данных относятся к классу простых. Ещё о двух типах – перечисляемом и интервальном – этого класса мы поговорим немного попозже.


Константы.

Объявления констант, применяемых в программе, осуществляется в разделе объявлений после слова Const. Константы бывают: целочисленные (к = 5000), вещественные (к = 5е+3 или к = 5000.00), символьные (к = ’F’ или к = ’9’), строковые (к = ’Мама мыла раму’) и типизированные, когда при описании константы сразу оговаривается её имя, тип и начальное значение.


Переменные.

Переменные в Pascal применяются для символического указания на какие-либо объекты (числа, значения). Имя переменной должно начинаться с буквы и должно содержать не более 255 символов (А зачем? Хотя...).

Типы переменных описываются после слова Var в разделе объявлений и соглашений. Есть несколько правил, которых необходимо придерживаться при использовании переменных:


1. Нельзя использовать две глобальные переменные с одинаковыми именами;

2. Нельзя использовать две локальные переменные с одинаковыми именами в одной функции или процедуре;

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

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


§2.6 Выражения.

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

Выражения бывают арифметическими, строковыми или логическими. Значение арифметического выражения есть число, строкового – строка символов.

Арифметическое выражение может быть целым или вещественным в зависимости от того, из каких компонентов оно состоит.

Примеры выражений:

3.14 - константа - тоже выражение, его значение равно 3.14

а + b + c - выражение, значение которого равно сумме a, b и c.

a1 + 4 - арифметическое выражение (целое или вещественное)

c + ’ЭВМ’ - строковое выражение,

а > b + 5 - выражение логического типа.

Под типом выражения понимают тип констант и переменных, составляющих

выражение.

Результат вычисления логического выражения - одно из двух значений: «истина» или «ложь». (Оказывается, можно вычислить а > b. Это будет «истина», если а действительно больше b и «ложь», если наоборот).

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

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


Приоритет операций в Pascal:

  1. not, @

  2. *, /, div, mod, and, sh1, shr

  3. +, -, or, xor

  4. =, < >, >, > =, <, < =, in

Об этом говорят так: «Уможение и деление имеет более высокий приоритет, чем сложение и вычитание» и т.д. Операции одинакового приоритета выполняются последовательно слева - направо.

При наличии скобок в первую очередь выполняются действия, заключенные

в скобки.


Примеры:

Выражение Значение

10+5+2 17

10+5*2 20

(10+5)*2 30

10/5*2 4

10/(5*2) 1

Операция mod (это именно операция, а не функция!) вычисляет остаток от деления первого числа на второе. Результатом операции 5 mod 2 будет 1, а 10 mod 5 будет 0. Простейшее приложение операции mod – проверка данного на четность. Если n mod 2 равно нулю, то n - четное.

Использование логических операций и операций отношения разберем позже, в параграфе «Условный оператор».

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

Забегая немного вперед, отметим, что корень квадратный записывается четырьмя символами sqrt, после которых в скобках записывается то, из чего извлекается корень. Например, sqrt(10) - корень квадратный из 10. sqrt(x - 1) - корень из x-1. А вот sqr(x) – это x в квадрате. Аналогично записываются тригонометрические функции: sin(x) - синус от х, сos(pi+a) - косинус от pi+a и т.д.

Здесь приведены в алфавитном порядке стандартные математические функции Pascal, включающие тип аргумента и результата.


Стандартные математические функции Pascal

Обращение

Название

Тип аргумента

Тип функции

Abs(x)

Модуль аргумента

Real, Integer

Real, Integer

ArcTan(x)

Арктангенс

Real

Real

Cos(x)

Косинус

Real

Real

Exp(x)

Экспонента (ех)

Real

Real

Frac(x)

Дробная часть числа

Real

Real

Int(x)

Целая часть числа

Real

Real

Ln(x)

Натуральный логарифм

Real

Real

Pi

3.1415926...

--------

Real

Random

Случайное число от 0 до 1

--------

Real

Random(x)

Случайное число от 0 до (х – 1)

Integer

Integer

Randomize

Запуск генератора случайного числа

--------

--------

Sin(x)

Синус

Real

Real

Sqr(x)

Квадрат аргумента

Real

Real

Sqrt(x)

Квадратный корень аргумента

Real

Real

Аргументы всех тригонометрических функций задаются в радианах, поэтому, если производить вычисления градусной величины, необходимо воспользоваться классической формулой: x(рад.) = x(гр.) * pi / 180. Например, тангенс 42 градусов 17 минут равен sin((42 + 17/60) * pi/180)/ cos((42 + 17/60) * pi/180)


Примеры выражений:

a + b * x + c * y * z

a * b / c + c / a / b или a * b / c + c /(a * b)

(Sin(x * x) + Cos(x * x)) / 2

Sqrt(1 + x * x)

Sqr(Cos(x* x))/(x+1)+1

(Sin(a / 2) + Cos(b / 2)) / 2






Ниже приведена структура типов данных в Pascal


Типы

Простые

Порядковые

Целые


hello_html_m311f0002.gifhello_html_m2bddf96.gifhello_html_m441d7c7e.gifhello_html_m262ea49d.gifhello_html_4cbb7abc.gifhello_html_m1ac1e794.gif



Вещественные

Символьный


hello_html_m311f0002.gifhello_html_m2823cef2.gif


Объекты

hello_html_m54e136e9.gifhello_html_1cbd7991.gif

Логические

hello_html_5951fc3b.gif

Массивы

hello_html_5951fc3b.gif

hello_html_4a499f33.gifhello_html_m311f0002.gif

Указатели

hello_html_5951fc3b.gif

Перечисляемый


Файлы

hello_html_5951fc3b.gif

Строки

hello_html_m311f0002.gif

hello_html_5951fc3b.gif

Тип - диапазон


Структурированные

Множества

hello_html_m311f0002.gif

hello_html_5951fc3b.gifhello_html_mb60b119.gifhello_html_m311f0002.gif


Процедурные


Записи

hello_html_5951fc3b.gif

Рис.5


hello_html_m311f0002.gif



Вопрос 2.6.1. Какие типы данных вы знаете?

Вопрос 2.6.2. На какие группы подразделяются числовые данные?

Вопрос 2.6.3. Приведите примеры констант всех известных вам типов.

Вопрос 2.6.4. Для чего нужны переменные?

Вопрос 2.6.5. Какими значениями ограничены данные целого типа?

Вопрос 2.6.6. Что такое выражение?

Вопрос 2.6.7. Что такое значение выражения?

Вопрос 2.6.8. Перечислите арифметические операции в порядке убывания приоритета их выполнения.

Вопрос 2.6.9. Какие значения может принимать данные логического типа?


§2.7 Команда присваивания.

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

Записывается команда так:

<переменная> := <выражение> ;

<переменная> - любая допустимая переменная в языке Pascal,

<выражение> - любое выражение того же типа, что и переменная.

Примеры:

a := 10.0 ;

b := 2 * (45/pi) ;

s := ’компьютер’ ;

Логика применения операции присвоения проста, поскольку, строго говоря, знак «равно» есть команда отношения, то есть сравнения, и говорить «Переменная а равна 100» не очень корректно.

Итак, повторим. Команда присваивания выполняет следующие действия:

1. Вычисляет значение выражения, стоящего справа от знака «:=»

2. Размещает полученное значение в ячейке памяти, отведенной для хранения переменной, стоящей слева от знака «:=»

Говорят так: команда «:=» присваивает значение переменной.

При чтении программ команду присваивания следует произносить так (см. пример выше): переменной а присваивается вещественное значение 10; переменной b присваивается значение, равное два, умноженное на дробь, числитель - 45, знаменатель - число пи; строковой переменной s присваивается значение строки символов «компьютер».

Рассмотрим еще один пример. Пусть значение переменной a было 10, а переменной b - 20. Что произойдет при выполнении команды a := b + 5?

Вычисляется выражение в правой части (20 + 5 будет 25) и записывается в ячейку a. То что было в a до выполнения команды (а там было 10), будет безвозвратно потеряно (туда будет записано 25). Следовательно, нам безразлично, что находится в ячейке a перед присваиванием. Содержимое же ячейки b так и останется равным 20. В эту ячейку мы ничего не записывали.

И еще один пример: k := k + 1. В этом примере к значению переменной k добавляется единица, и результат размещается на месте переменной k. Таким образом, после выполнения этого оператора значение переменной k увеличится на единицу. Кстати, такая запись (к := к + 1) называется инкрементом и, соответственно (к := к – 1) – декрементом. В некоторых языках программирования (С, Perl) инкремент и декремент имеют даже свою сокращённую форму записи. С инкрементом и декрементом связана одна очень интересная задача, которую мы рассмотри чуть позже.

И последнее. Разумеется, что тип переменной должен соответствовать типу выражения. Нельзя числовым переменным присвоить строку символов, а строковым переменным - число. Или переменной целочисленного типа – вещественное значение.

Примеры неправильной записи операторов присваивания.

s + k := 10 ; {слева от знака «=» не переменная}

a > b ; {нет знака «:=». Синтаксически неверная запись}

a := WriteLn(b) ; {справа от знака «:=» не выражение}


Вопрос 2.7.1. Какие действия выполняет команда присваивания? Как она записывается?

Вопрос 2.7.2. Какое значение будет иметь переменная х после выполнения операторов:

х := 10 ;

х := х + 3 ;

Вопрос 2.7.3. Записать команду присваивания, который меняет знак переменной t.

Вопрос 2.7.4. Записать команды присваивания, которые переменной d присваивают:

а) среднее арифметическое чисел x,y и z,

б) расстояние между точками с координатами (x1,y1) и (x2,y2),

в) площадь треугольника со сторонами a,b и c.

Вопрос 2.7.5. Чему равны значения переменных х и у после выполнения операторов: х :=2 ;

у :=5 ;

х := у ;

у := х ;

Вопрос 2.7.6 Поменять местами значения переменных x,y и z таким образом, чтобы в x оказалось значение переменной y, в y - значение переменной z, а в z - прежнее значение переменной х.

Вопрос 2.7.7. Какие из приведенных строк содержат ошибки:

a) a := a + 10 ; г) ’stroka’ := a ;

б) a := 10 ; д) a := ’stroka’ ;

в) a + 4 := b + 2 ;

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

В Pascal структура любого программного узла должна начинаться со слова Begin и заканчиваться словом End с символом «;». В случае завершения основного блока программы после слова End ставится точка. По сути, в разделе объявлений и соглашений заголовки разделов Const, Var, Label и Type могут располагаться в любой последовательности сколь угодно раз.

Строго говоря, операторы Begin. . . End являются составным оператором. Это, пожалуй, и есть один из самых мощных инструментов Pascal, позволяющий реализовать технологию структурного программирования. Уровень вложенности структур друг в друга не ограничен.

Begin

<серия 1> ;

Begin

<серия 2> ;

Begin

<серия 3> ;

End ; {Закрытие серии 3>

. . . . . . . .

Begin

<Серия N> ;

End ; {Закрытие серии N}

. . . . . . . .

End ; {Закрытие серии 2}

. . . . . . . .

End . {Закрытие серии 1}

Поскольку оператор End является ничем иным, как закрывающей скобкой, то перед этим оператором End ставить точку с запятой нет никакой необходимости. Т.е. после оператора, который стоит перед End, точку с запятой ставить необязательно. Естественно, количество операторов Begin и End должно совпадать.

Вопрос 2.8.1. Для чего нужен оператор End? В каких случаях он применяется?

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

Вопрос 2.8.3. Как вы думаете, какую информацию нужно отмечать в тексте комментария в первую очередь?


Оператор Read и Write.

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

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

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

Ввод данных из файла рассмотрим позже. Сейчас остановимся на вводе с клавиатуры.

Формат оператора: Read (список ввода) ;

Пример:

Read (a, b, c) ;

ReadLn (Vasia, Kusia, Musia) ;

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

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

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

Оператор Write или WriteLn производит вывод информации. По умолчанию вывод производится на экран монитора. Существует замечательная возможность выводить данные форматно, т.е. выделяя под переменные определённую часть строки. Если, например, необходимо напечатать число k = 5 в вещественном формате, то не очень приятно видеть на экране «5.00000000000000» или «5.0е+00». А вот если задать команду Writeln(’k = ’, k:7:1), то на экране появится

к = 5.0

Это означает, что на переменную к отводится всего 7 позиций (На дробную часть – 1 позиция, на целую – 5 позиций и точка – 1 позиция. Всего – 7). Если дробная часть больше указанной длины, то она просто отсекается. И самое замечательное: если число разрядов в переменной больше указанного формата, то целая часть значения будет выведена полностью, а длина дробной части останется равной указанной.

А теперь рассмотрим целиком программу сложения двух чисел.


Program P_1; {Каждая программа имеет своё имя. Имя не может содержать пробелы}

Uses Crt; {Включение библиотеки Crt}

Var x, y, z : real; {Числа вещественные}

Begin {Начало}

ClrScr; {Очистить экран. Именно для этого нужна библиотека}

Write(’Введите 2 числа’); {Пояснительный текст}

Readln(x, y); {Ввод двух чисел и переход на следующую строку}

z := x + y; {Сложение}

WriteLn(’Результат сложения равен’, z:8:2); {Форматный вывод}

Repeat Until KeyPressed {Дословно - ждать, пока не будет нажата}

End. {любая клавиша. И остановиться.}

Вопрос 2.8.4. Какие действия выполняет оператор ReadLn?

Вопрос 2.8.5. Как записывается оператор Read?

Вопрос 2.8.6. Какие значения получат переменные a, b и c после выполнения оператора Read( a, b, c), если на клавиатуре набрали 1 Doom 3 <Ввод>?

Вопрос 2.8.7. Какие функции выполняет оператор WriteLn?

Вопрос 2.8.8. Как будут выведены данные при выполнении следующих строк:

Write(a, b, c);

Write(a,b:7:2,c);

Write(a, b, c:8:7);

Вопрос 2.8.9. Какие действия выполняет WriteLn без списка вывода?


§2.9 Простейшие программы.

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

Строки программы вводятся последовательно, одна за другой. Пока не нажата клавиша Enter, строка не попадает в память компьютера. Если при вводе строки вы допустили опечатку, - не расстраивайтесь: ее можно легко исправить. Если опечатка обнаружена раньше, чем запущена программа, то исправление производится элементарно. Для этого верните курсор (клавишей «Влево») на позицию с неправильным символом и исправьте его.

Последний введенный символ стирается клавишей Delete.

Рассмотрим несколько простейших примеров программ.


Пример. Составить программу вычисления площади квадрата.

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


Program P_2;

Uses Crt;

Var a, s : Real;

Begin

ClrScr;

Write(’Введите длину стороны квадрата’);

Readln(a);

s := a * a;

Write(’Площадь квадрата со стороной ’, a:7:2,’ равна ’,s:10:4);

Repeat Until KeyPressed

End.


Пример. Вычислить силу, действующую на тело массой m, движущееся с ускорением а.

Program P_3;

Uses Crt;

Var a, m, f: Real;

Begin

ClrScr;

Write(’Введите массу тела и его ускорение’);

ReadLn(m, a);

f := m * a;

ClrScr;

WriteLn(’Сила, действующая на тело массой m=’,m:7:2);

WriteLn(’и движущееся с ускорением а=’,a:7:2);

WriteLn(’равна ’, f:7:2);

Repeat Until KeyPressed

End.


Давайте обратим внимание на, казалось бы, не нужную команду Repeat Until KeyPressed. А всё очень просто: когда будет выполнена команда End, все наши результаты с экрана «слизнутся», т.е. всё, что мы сделали, будет закрыто окном редактора. Конечно же, можно убрать окно редактора с помощью Alt + F5, но... Честно говоря, чем «ленивее» вы будете действовать, тем больше вероятность того, что всю ненужную работу вы переложите на «плечи» компьютера. Вот такая вот странная философия. Лень – двигатель прогресса! Это, конечно же, шутка, хотя... Так вот, команда Repeat Until KeyPressed всего лишь останавливает работу следующего за ней оператора. Дословно это можно понять так: ждать, пока не будет нажата любая клавиша.


Даны две переменные. Поменять их местами, не используя вспомогательную переменную. Сама по себе задача проста. Даны две переменные, допустим X и Y. А вот… Ой, а как же это сделать? Написать.

X := Y

Y := X ?

Кажется так, но… В первой строке, то, что хранила переменная X исчезнет, туда поместится содержимое переменной Y, и, в итоге, обе переменные будут содержать значение Y, и вторая строка потеряет какой-либо смысл… А если… взять, допустим, ячейку Z… и сделать так:

Z := X

X := Y

Y := Z

Да, вот теперь верно. Вообще-то, такой приём в программировании называется довольно сложным словом – свопирование (от английского слова swap – обмен). Достаточно сказать, что одним из основных инженерных понятий в работе Windows есть «своп-файл». Но мы отвлеклись. Так вот, дело в том, что в задаче чётко указано – не использовать вспомогательную переменную. Но как же так? Мы ведь выяснили, что это невозможно! А теперь волшебный инкремент – декремент (см. выше).


Program P_4;

Uses Crt;

Var x, y: Real;

Begin ClrScr;

Write(‘X=’); ReadLn(x); {Вводим x и y}

Write(‘Y=’); ReadLn(y); {Допустим, x = 3 и y = 5}

x:= x + y; {Теперь x =3 + 5 = 8…}

y:= x - y; {y = 8 – 5 = 3…}

x:= x - y; {и, наконец, x = 8 – 3 = 5}

WriteLn(‘X=’, x:7:2, ‘Y=’,y:7:2); {Волшебство…}

Repeat Until KeyPressed

End.


Вопрос 2.9.1. Составить программу расчета ускорения тела массой m, если на него действует сила f.

Вопрос 2.9.2. Составьте программу вычисления площади квадрата, вписанного в окружность радиусом r.

Вопрос 2.9.3. Тело упало с высоты h. Составьте программу расчета скорости тела в момент удара о землю.

Вопрос 2.9.4 Тело определённой массы брошено с начальной скоростью под углом к горизонту. Вычислите дальность полёта тела.


§2.10 Оператор безусловного перехода Goto и метки

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

Оператор Goto <метка> позволяет изменить нормальную последовательность выполнения строк программы. Запись Goto <метка> приводит к тому, что следующим после Goto <метка> будет выполняться строка, где стоит эта метка.

Буквально оператор Goto можно рассматривать как «перейти к строке с меткой n».

Самое интересное, что в данный момент так и подмывает сказать: «Забудьте то, что здесь написано. Никогда не применяйте этот оператор!» Но будем корректны: применение этого оператора нежелательно, т.к. он нарушает структурную целостность программы. Представьте себе, если в вашей программе тысяча строк кода, и на каждую 50-ю строку приходится оператор Goto. Стало быть, таких операторов будет 20, чего достаточно лихвой, чтобы просто запутаться.



Пример:

. . . .

Label a1, a2, Vasia ;

. . . . ;

hello_html_4641c3ba.gifBegin

. . . . ;

hello_html_7cfc76bc.gifhello_html_m5c143b4.gifGoto a1 ;

. . . . ;

hello_html_59e0311b.gifhello_html_57aed7ff.gif a2:. . . . ;

hello_html_24eb3a7c.gif

Рис.6

. . . . ;

Goto Vasia;

. . . . ;

hello_html_b1a841d.gif a1: . . . . ;

. . . . ;

hello_html_m30d23a1b.gif Goto a2;

hello_html_4641c3ba.gif Vasia: . . . . ;

. . . . ;

. . . .

End.


Ну и как вам такая схема?

В Pascal существуют 4 безусловные функции:

Halt – альтернативное завершение программы;

Exit – завершение текущего программного блока;

Break – аварийный выход из цикла;

Continue – новый шаг цикла, даже если не завершён предыдущий шаг.

Вопрос 2.10.1. В какой последовательности будут выполняться строки программы:

a := 10 ;

goto Vasia ;

Petia: a := 100 ;

goto Oleg ;

Vasia: b := 20 ;

goto Kuzma ;

Oleg: b := 200 ;

goto Kolia ;

Kuzma: c := 30 ;

goto Petia ;

Kolia: c := 300 ;

End .

Вопрос 2.10.2. Чему будут равны значения переменных a, b и с после выполнения программы из предыдущего примера?

Вопрос 2.10.3. Может ли в программе содержаться такой фрагмент?

.....

a := 0 ;

s2: b := 50 ;

Goto s1 ;

c := a + b ;

End .

s1: a := c - b ;

Goto s2 ;



§2.11 Условный оператор

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

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

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

Записывается он так:

If <условие> Then <серия 1> Else <серия 2>

<условие> - выражение логического типа;

<серия 1> и <серия 2> - любые серии операторов Pascal.

Условие - это то, что можно проверить. То, что имеет своим значением «Да» или «Нет». Условие может выполняться (в этом случае говорят, что условие истинно) или не выполняться (условие ложно).

Примеры условий: s > 0. Если к моменту выполнения оператора If переменная s действительно больше нуля, то условие истинно. Если же s меньше нуля или s равно нулю, то условие ложно.

Если условие выполняется (истинно), то будет исполняться серия операторов, следующий за ключевым словом Then, т.е. <серия 1>, если не выполняется, то будет исполнена <серия 2> (та, который стоит после слова ELSE).

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

Пример: Составить программу вычисления квадратного корня числа.


Program P_5;

Uses Crt;

Var a: Integer;

Begin

ClrScr;

Write(’Введите число’);

ReadLn(a);

WriteLn(’Вычисление квадратного корня из числа а=’, a:7:2);

If a >= 0 Then

WriteLn(’Результат равен’, sqrt(a):7:2)

Else WriteLn(’К доктору!’);

ReadKey

End .


На что обратить внимание? Во-первых, части условного оператора могут располагаться и на одной, и на нескольких строках. Во-вторых, в самом операторе Write возможны вычисления. И, в-третьих, если лень писать Repeat Until KeyPressed, пишите ReadKey. В данном случае это одно и то же.

Читая программы, условный оператор следует произносить так: если а не меньше нуля, то напечатать значение корня квадратного из а. Иначе напечатать фразу «Обращайтесь к доктору!».

Допускается сокращенная запись оператора If без конструкции Else.

Если конструкции Else нет, и условие не выполняется, то оператор If не выполняет никаких действий (эквивалентен пустому оператору).

Внимание! Следующая программа является модификацией предыдущей. Внимательно рассмотрите работу оператора If. В этой программе мы впервые вводим понятие структуры.

Пример: Составить программу вычисления квадратного корня числа.


Program P_6;

Uses Crt;

Var a, b: Integer;

Begin

ClrScr;

Write(’Введите число’);

ReadLn(a);

WriteLn(’Вычисление квадратного корня из числа а=’, a:7:2);

If a >= 0 Then

Begin

B := sqrt(a);

Writeln(’Результат равен’, b:7:2);

End

Else Writeln(’Обращайтесь к доктору!’);

ReadKey

End .


В данном примере после слова Then стоит структура Begin. . . End, которая будет выполняться в случае выполнения условия.

Рассмотрим еще один пример разветвленного алгоритма. На сей раз это будет программа решения квадратного уравнения.



Proram P_7;

Uses Crt;

Var a, b, c, d, x1, x2: Real;

Begin

ClrScr; {Очистить экран}

Repeat {Повторять ввод коэффициентов до тех пор,}

Write(’Введите коэффициенты квадратного уравнения ’);

ReadLn(a, b, c);

Until a < > 0; {пока коэффициент а не станет неравным 0.}

d := b * b – 4 * a * c; {Вычислить дискриминант.}

If d < 0 Then WriteLn(’Корней нет’) Else

Begin

{Если дискриминант меньше 0, то} x1 := (-b + sqrt(d)) / 2 / a;

{напечатать, что корней нет.} x2 := (-b - sqrt(d)) / 2 / a;

{Иначе вычислить корни и выдать} WriteLn(’Корни уравнения равны’);

{их значения.} WriteLn(x1:7:2, x2:7:2);

End;

ReadKey;

End.


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

Остановимся подробнее на том, что стоит между If и Then. Как мы уже говорили, - это условие. Однако, это слишком примитивное название. Более профессионально было бы сказать: «выражение логического типа».

Значением логического выражения может быть «false» или «true». Внутри компьютера эти значения представляются числами. Значению «ложь» соответствует ноль, а значению «истина» - любое число, отличное от нуля (как правило, единица).

Так вот. Что значит «проверка условия»? Это вычисление значения логического выражения. В результате такого вычисления получается либо 1 (true), либо 0 (false).

Теперь вспомним оператор присваивания. Он записывается так:

<Переменная> := <Выражение>

До сих пор мы пользовались только арифметическими выражениями. Теперь мы знаем, что выражения бывают и логические (например, a > b). И логическое выражение тоже вычисляется. Следовательно, его можно поставить в правой части оператора присваивания.

Например: c := a > b

В этом примере переменной с присваивается значение «true», если а больше b, и «false» - если нет.

После этого можно будет записать If c then ... Это будет все равно, что записать If a>b then ...

Задана точка с координатами x и y. Попадает ли точка в круг радиусом r?

Введем логическую переменную z, которая будет иметь смысл «Точка попадает в круг». Точка действительно попадает в круг, если x*x+y*y< r*r, или sqrt(x*x+y*y) < r. Это и будет определением нашей переменной z.


Program P_8;

Uses Crt;

Var x, y, r: Real;

z: Boolean;

Begin

ClrScr;

Write(’Введите координаты точки и радиус круга ’);

ReadLn(x, y, r);

z := sqrt(x * x + y * y) < = r;

If z Then Writeln(’Точка в круге’) Else WriteLn(’Точка вне круга’);

ReadKey;

End.


Разумеется, можно было бы поставить между If и Then само логическое выражение sqrt(x*x + y*y) <= r вместо того, чтобы присваивать его значение переменной z, а потом проверять значение этой переменной. Сократили бы одну строку программы. Однако сложные логические выражения лучше разбивать на отдельные составляющие и присваивать их значения переменным, чтобы потом использовать именно их.

Запись a := b = c тоже имеет смысл. Трактуется эта запись так: присвоить переменной а значение логического выражения b=c. В результате выполнения этой строки переменная а будет иметь значение «true», если b=c и «false», если b<>c. Здесь первый (слева направо) знак «:=» есть операция присваивания, а второй - операция отношения a и b. (Ни в коем случае эта запись не означает, что нужно уравнять все 3 переменные!).

В логических выражениях можно применять логические операции and (и), or (или) и not (не). Операции and и or производятся над двумя операндами, а not - над одним. Значением операций опять же может быть истина или ложь.

Результат операции and дает истину тогда и только тогда, когда оба операнда имеют значение «истина». Or - когда хотя бы один имеет значение «истина». Операция not инвертирует (гм… переворачивает) значение операнда.

And называется логическим умножением, or - логическим сложением, not - отрицанием.


False and False = False

False or False = False

False and True = False

False or True = True

True and False = False

True or False = True

True and True = True

True or True = True

Not False = True Not True = False


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

Вопрос 2.11.1. Для чего нужен оператор If? Как он записывается?

Вопрос 2.11.2. Можно ли в операторе If не указывать конструкцию Else? Что при этом происходит, если условие не выполняется?

Вопрос 2.11.3. Можно ли не указывать Then?

Вопрос 2.11.4. Можно ли после Then поставить Goto? А после Else?

Вопрос 2.11.5. Что может стоять после Then и Else?

Вопрос 2.11.6. Что такое условие?

Вопрос 2.11.7. Может ли в качестве условия применяться переменная? Функция?

Вопрос 2.11.8. Перечислите операции отношения.

Вопрос 2.11.9. Перечислите логические операции.

Вопрос 2.11.10. Запишите таблицы истинности всех логических операций.

Вопрос 2.11.11. Какие из приведенных строк содержат ошибки?

If a>b Then Sin(x) + 0.1;

If c Else WriteLn(a, b);

If c WriteLn(a, b);

If c then WriteLn(a, b);

If c>0 then WriteLn(a, b) and w := 10;

Вопрос 2.11.12. Составьте программу вычисления y по одной из двух формул:

y=x2 + 2x + 3 если x>0

y=x3 - 2x + 3 если x<=0

Вопрос 2.11.13. Составьте программу построения таблицы значений функции y=(2*x+1)/ sqrt(x-3) на интервале от 0 до 4 с шагом 0.1


§2.12 Массивы и циклы

До сих пор мы рассматривали примеры и задачи, с которыми вполне мог бы справиться средней успеваемости школьник. Причем, без всякого компьютера. Что, скажем, стоит найти массу тела, если известен его (тела) вес? А решить квадратное уравнение? Тоже не проблема! И неужели ради этого стоит учиться программировать?

Ну, хорошо. Одно квадратное уравнение решить можно... Два? Тоже... Сто? С трудом, но можно... А десять тысяч?.. Причем, за несколько секунд?.. И от результата решения должно зависеть что-то жизненно важное?..

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

Мы уже писали программу для решения одного квадратного уравнения. Помнится, мы просили ввести с клавиатуры 3 числа - коэффициенты a, b и c (с помощью Read), потом вычисляли дискриминант, проверяли его If-ом, искали корни и т. д.

Наверное, чтобы решать много квадратных уравнений, нужно столько же троек коэффициентов a, b и с. Для каждой тройки - свое решение.

Что может быть проще? Запустим программу решения уравнения. Зададим ей исходные данные a,b и с, получим и запишем результат. Снова запустим, зададим новые a, b и с, опять запишем результат... и так много-много раз.

А можно проще. Можно составить программу так, чтобы она выполняла одну и ту же работу много раз. В цикле. И всякий раз - над разными данными. Для этого нужно: во-первых - иметь средство выполнять программу (или какую-то ее часть) нужное количество раз. И, во-вторых – задать сразу много данных. Как это сделать? Именно об этом и будем здесь говорить.

Сначала о данных. Мы уже знаем, что каждому данному в памяти отводится место. Если мы записали a := 10, то это значит, что в памяти будет отведена ячейка. Ей будет присвоено имя а, и в эту ячейку будет записано значение 10. Можно одним именем назвать сразу много ячеек памяти, каждой ячейка дать свой порядковый номер, а потом обращаться к каждой по номеру. Такая совокупность данных называется массивом. Чтобы это сделать, нужно записать оператор:

Array [<тип индекса>] Of <тип элементов массива>.

Например:

Var x: Array[1..10] Of Integer;

Это значит, что переменным с именем х будут присвоены порядковые номера от 1 до 10 и значения этих переменных будут лежать в интервале от –32768 до 32767. следующий вариант объявления массива совершенно аналогичен предыдущему:

Type massiv = Array[1..10] Of Integer;

Var x: massiv;

Оператор Array только отводит место в памяти для хранения данных. Он ничего туда не помещает. Мы «говорим» компьютеру этим оператором, что у нас будет столько-то одинаковых по смыслу (а может быть и нет, не его это дело - принимать решения!) данных. Пусть он найдет для них место, чтобы мы могли в любой момент «взять» или «положить» в память любое данное.

После того, как мы объявим массив оператором Array, мы можем обращаться с каждым его элементом как с обычной переменной. Запись а[3] := 10 будет означать: «В третью ячейку массива а записать значение 10» (вообще-то, слово «ячейка» здесь не применяется. Говорят так: «Третьему элементу массива а присвоить значение 10»).

Разница между обычной переменной и массивом в том, что: во-первых переменная - это только одна ячейка памяти, а массив - много. И все пронумерованы. Во-вторых, для переменной сразу автоматически отводится место в памяти, как только она встречается в программе, а массив нужно объявить оператором Array.

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

Значение, заключенное в скобки, называется индексом.

Пример: f := a[12+4*k];

Если, например, к моменту выполнения этой строки переменная k имела значение 1, то переменной f будет присвоено значение 16 – го элемента массива а}

В одной программе категорически не могут одновременно существовать массив и переменная с одним и тем же именем!


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

Пример:

Var matrix: Array[1..5, 1..4] Of Real;

Такая запись означает, что матрица с именем «matrix» имеет 5 строк и 4 столбца. Обращение к элементу, стоящему в 3 строке и 4 столбце этой матрицы производится так: matrix[3, 4].

Обратите внимание, что понятие «размер» и «размерность» - вещи разные. Размер – это количество элементов массива. Размерность – количество измерений. Размер массива matrix – 20 (строго – 30, об этом чуть ниже), размерность – 2.

Во многих языках программирования возможна организация массивов с большим числом измерений (трехмерные, четырехмерные и т.д.). В базовом Fortran количество измерений массива не может быть больше семи, в Pascal - 256, в QBasic - 60. Массивы в программах на языке Clipper могут иметь 32767 измерений.

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

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

Запрещается обращение к несуществующему элементу массива (вне его границ). Например, если массив а объявлен так: Array a[20], то оператор f:=a[21] приведет к ошибке.

Строго говоря, некоторые из правил использования массивов являются необязательными. Нумерация элементов массива начинается с нуля, так что массив f[10] содержит на самом деле 11 элементов, пронумерованных с 0 до 10. В ранних версиях Pascal массивы, содержащие до 10 элементов по каждому измерению, можно было не объявлять... Однако использование этих возможностей на практике означает для начинающего программиста (да и вообще) дурной тон.

И, наконец, о расположении элементов в памяти. Разумеется, все элементы массива (в том числе и двумерного) «выстроены» в линейку. В Pascal и Basic это делается по строкам. Эта информация пригодится тем, кто будет писать многоязычные программы.

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

Предположим, что уравнений всего 1000 «штук».

Объявим три массива коэффициентов:

Var a, b, c: Array[1..1000] Of Integer;

Обратите внимание – одной командой Array можно объявить 3 однотипных массива.

Договоримся так: первые элементы массивов a[1], b[1] и c[1] будут содержать коэффициенты первого уравнения. Вторые - a[2], b[2], c[2] - второго и т.д. Теперь нужно заставить компьютер выполнять одну и ту же программу решения уравнения 1000 раз, выбирая каждый раз новую тройку коэффициентов.

Var a, b, c: Array[1..1000] Of Integer; {массивы коэффициентов уравнений}

...

Begin

...

i := 1; {Берем первое уравнение}

metka3: d := b[i] * b[i]-4 * a * c; {находим дискриминант}

If d>=0 Then metka1; {проверяем дискриминант}

WriteLn(’Уравнение номер ’, i,’ не имеет решений’); {Если d<0, то}

Goto metka2; {Сообщаем и берем следующее уравнение}

metka1: x1= (-b[i]+sqrt(d))/(2*a); {находим корни очередного уравнения}

x2= (-b[i]-sqrt(d))/(2*a) '

WriteLn(’Решения уравнения номер ’,i,’:x1=’,x1,’x2=’,x2);

metka2: i := i + 1; {берем следующее уравнение}

If i<=1000 Then metka3; {все ли решили? Если нет – продолжаем}

ReadKey; {иначе заканчиваем}

End.

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

Пусть переменная i будет номером того уравнения, которое программа в настоящий момент решает. Тогда коэффициенты этого уравнения будут расположены в массивах в элементах номер i: a[i], b[i] и c[i].

Положим для начала i=1. Это значит, что будем решать уравнение номер 1. Фрагмент программы производит собственно решение уравнения и вывод на экран результатов. Этот фрагмент мы разбирали (см. «Условный оператор»). Решив одно уравнение, увеличиваем его номер и проверяем, не все ли уравнения уже решили. Если еще нет, то решаем следующее. Так будет до тех пор, пока i не превысит 1000.

Фрагмент повторяется 1000 раз. И при каждом очередном повторении номер уравнения i увеличивается на единицу. Это позволяет нам «брать» другие коэффициенты, т.е. рассматривать другое, очередное уравнение. В подобных случаях такую переменную называют переменной цикла.

Такой прием в программировании называется организацией цикла с помощью условия. Но согласитесь, что такой вариант решения не то, что удобен. Он просто-напросто неприемлем из-за оператора Goto! Рассмотрим 2 варианта решения задачи с помощью циклов по условию. Решается 10 уравнений.

Program P_9;

Uses Crt;

Var a, b, c: Array[1..10] of Real;

d, x1,x2: Real;

i: Integer;

Begin

ClrScr;

i := 1; {Стартовый номер уравнения}

While I<=10 Do {До тех пор, пока номер уравнения меньше или равен 10}

Begin {делать...}

Write(’Введите коэффициенты ’,i,’-го уравнения’);

ReadLn(a[i], b[i], c[i]); {}

d := b[i] * b[i] – 4 * a[i] * c[i];

If d<0 Then WriteLn(i,’-е уравнение корней не имеет’)

Else

Begin {Расчёт корней}

x1 := (-b[i] + sqrt(d))/2/ a[i];

x2 := (-b[i] - sqrt(d))/2/a[i];

WriteLn(i,’-е уравнение имеет корни:’);

WriteLn(x1:7:2, x2:7:2)

End;

i := i + 1 {Следующее уравнение}

End; {Эта конструкция Begin... End относится к слову Do}

ReadKey

End.

Такой способ задания повторений называется циклом с предусловием. На самом деле, ключевая конструкция While <условие> Do располагается до начала выполнения цикла. Стало быть, если есть предусловие, то должно быть и постусловие. Совершенно верно: это конструкция Repeat... Until <условие>, с которой мы уже немного знакомы (вспомните Repeat Until KeyPressed!).

Итак:


Program P_10;

Uses Crt;

Var a, b, c: Array[1..10] of Real;

d, x1,x2: Real;

i: Integer;

Begin

ClrScr;

i := 1; {Стартовый номер уравнения}

Repeat {Повторять всё, что мы делали...}

Begin

Write(’Введите коэффициенты ’,i,’-го уравнения’);

ReadLn(a[i], b[i], c[i]); {}

d := b[i] * b[i] – 4 * a[i] * c[i];

If d<0 Then WriteLn(i,’-е уравнение корней не имеет’)

Else

Begin

x1 := (-b[i] + sqrt(d))/2/ a[i];

x2 := (-b[i] - sqrt(d))/2/a[i];

WriteLn(i,’-е уравнение имеет корни:’);

WriteLn(x1:7:2, x2:7:2)

End;

i := i + 1 {Следующее уравнение. И точки с запятой нет!}

Until i>10; {до тех пор, пока номер уравнения не станет больше 10}

ReadKey

End.

А чем отличаются эти способы задания? Всё очень просто. Цикл While... Do выполняется до тех пор, пока выполняется условие. Цикл Repeat... Until идёт, пока условие не выполняется.

Решим ещё одну задачу: Лягушка-Царевна, попав на болото, в первый день съела 10 комаров. Каждый следующий день она съедала на 5 комаров больше, чем в предыдущий. Через сколько дней общее число съеденных комаров превысит 1000?

Program P_11;

Uses Crt;

Var zavtrak, summa: Word;

i: ShortInt;

Begin

i := 1; {В первый день, попав на болото}

zavtrak := 10; {лягушка съела 10 комаров.}

summa := 10; {Общее число съеденных комаров тоже 10.}

Repeat

i := i + 1; {И каждый следующий день}

zavtrak := zavtrak + 5; {она съедала на 5 комаров больше.}

summa := summa + zavtrak {Сумма съеденного копилась до тех пор,}

Until summa > 1000; {пока не превысила 1000}

WriteLn(’Через ’, i-1); {-1 потому что «через сколько дней?»}

ReadKey

End.





Алгоритм Евклида. Определить наибольший общий делитель двух чисел.


hello_html_m3ad9d3bf.gif

Begin

Ввод x и y



hello_html_2d2985a9.gif

hello_html_m7c39c63b.gif



hello_html_56c7af11.gif




hello_html_m31748f58.gifhello_html_19079b60.gifhello_html_5a7bab4.gifhello_html_2f2eeee5.gifhello_html_m4edf1676.gif

x = y

+


hello_html_2d2985a9.gifhello_html_2d2985a9.gifhello_html_11709a13.gif

-

Вывод x, y

Stop


hello_html_m31748f58.gif

hello_html_m4edf1676.gif

+

hello_html_4c7416ad.gifhello_html_m4edf1676.gifhello_html_m4edf1676.gifhello_html_m3ad9d3bf.gif

x > y


hello_html_m5eae62bb.gifhello_html_m5eae62bb.gifhello_html_m5eae62bb.gif

x = x - y

y = y - x


-

x = x - y


hello_html_m78972a1a.gif

Рис.7



hello_html_6cfd3589.gif


Program P_12;

Uses Crt;

Var x, y: Integer;

Begin ClrScr;

Write(‘X=’); ReadLn(x);

Write(‘Y=’); ReadLn(y);

While x < > y Do

If x > y Then x:= x - y Else y:= y - x;

WriteLn(‘НОД=’, x);

ReadKey

End.


Экологическая задача. В озере можно ловить рыбу. Известны темпы добычи (тонн в год), её запасы (тонн), темпы прироста (%) и нижний экологический барьер (тонн), по достижении которого биоресурс не восстанавливается. Выяснить, сколько лет можно ловить рыбу при заданных темпах добычи. Договоримся о следующем: zap – запасы рыбы, proc – процент прироста, dob – темпы добычи, nb – экологический барьер и god – текущий год добычи. В задаче может возникнуть не очень хорошая ситуация, когда запасы рыбы по истечении года могут не уменьшаться, а увеличиваться. Кушаем плохо, стало быть, из этой ситуации надо как-то выйти.


Program P_13;

Uses Crt;

Var zap, prov, proc, dob, nb: Real; {Переменная prov введена как раз для}

god:Integer; {разрешения ситуации с плохим аппетитом}

Begin ClrScr;

Write('Запасы рыбы = '); ReadLn(zap); {Вводим исходные данные}

Write('Процент прироста = '); ReadLn(proc);

Write('Темпы добычи = '); ReadLn(dob);

Write('Экологический барьер = '); ReadLn(nb);

prov:= zap; {Запомним, сколько было рыбы}

god:=0; {Инициализируем счётчик}

Repeat

god:=god+1; {Наступил следующий год,}

zap:=zap-dob; {произведён отлов рыбы}

zap:=zap*(1+proc/100); {и её прирост.}

If zap>prov Then Begin {И если её стало больше,}

{то пишем} WriteLn('Сколько угодно!');

ReadKey;

Halt {и прекращаем работу.}

End;

Until zap < = nb; {Если запасы уменьшаются, то повторяем.}

god:= god - 1; {Цикл прерван в тот год, когда рыбу уже ловить нельзя, }

WriteLn(god); {поэтому вычитаем единицу и выводим результат.}

ReadKey

End.


А теперь... Эти способы задания циклов употребляются в тех случаях, когда количество повторений заранее не известно. А зачем же так решать задачу про 1000 уравнений? Вот именно. Если количество повторений строго указано, то в Pascal применяется структура For... Next.

Оператор For задает цикл, строго ограниченный по количеству проходов.

Формат оператора:

For <параметр>:=<начальное значение> To <конечное значение> Do <тело цикла>

или

For <параметр>:=<начальное значение> DownTo <конечное значение> Do <тело цикла>

После слова Do в программе могут быть расположены любые выполняемые операторы. Эта группа операторов называется телом цикла. Именно эта группа и будет выполняться в цикле (несколько раз). Сколько именно - указывается в операторе For. Там же указывается и переменная цикла, которая всякий раз будет изменять свое значение.

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

1. Переменной цикла присваивается начальное значение.

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

3. Выполняется тело цикла.

4. К переменной цикла прибавляется единица и вновь выполняется проверка переменной цикла (п.2)

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

...

Begin

... {ввод данных в массивы коэффициентов}

For i=1 To 1000 Do Begin {Все уравнения в цикле}

d := b[i]*b[i] - 4*a[i]*c[i]; {находим дискриминант}

. . . .

. . . .

WriteLn('Решения уравнения номер ',i:4);

WriteLn(': x1=',x1:7:2,'x2=',x2:7:2)

End;

ReadKey

End.

Обратите внимание, что слово Do относится в данном случае не к одному оператору, а к структуре Begin . . . End;

Теперь нам нет необходимости проверять, достигла ли переменная i значения 1000. Нет надобности также и увеличивать ее всякий раз на 1. Все это делается автоматически с помощью оператора For.

Одно исполнение тела цикла часто называют проходом.

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

Пример: Напечатать слово "Слон" 20 раз.


Programm P_14;

Uses Crt; {Переменная цикла i здесь не используется. Тело}

Var i: Byte; {цикла содержит один оператор. Нет ни массивов,}

Begin {ни каких бы то ни было исходных данных.}

ClrScr; {Цикл организован ради того, чтобы 20 раз выполнить}

For I:=1 to 20 Do WriteLn(’Слон’); {один и тот же оператор:}

ReadKey {печатать слово «Слон»}

End.


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

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

- Конечное значение переменной цикла может быть больше начального. В этом случае говорят об  убывающем цикле.

- Размер тела цикла ограничен только объёмом памяти.

- Запрещается передавать управление вовнутрь тела цикла (минуя оператор For) каким бы то ни было иным способом категорически!

- Из цикла вовне можно передавать управление (и очень часто это делается). Это называется преждевременным завершением цикла. Такие ситуации встречаются очень часто: в цикле что-то искали и нашли, дальше «вертеть» цикл нет смысла, поэтому «уходим» преждевременно. Бывают случаи, когда в процессе исполнения цикла его необходимо прервать, чтобы начать заново. Для этого служат операторы Break и Continue.

Пример: найти в векторе, состоящим из 10 чисел последнее отрицательное число.


Program P_15;

Uses Crt;

Var a: Array[1..10] Of Integer;

i, flag: byte;

Begin

ClrScr;

For i:= 1 To 10 Do {Ввод массива}

Begin

Write(’Введите ’, i,’ элемент массива_’);

ReadLn(a[i])

End;

For i:=10 DownTo 1 Do {Просматриваем элементы с последнего}

Begin

If a[i]>0 Then Continue; {Если элемент положительный, то продолжаем,}

WriteLn(’Последнее отрицательное число ’, a[i]); {а если нет, то выводим}

WriteLn(’Стоит в позиции ’, i); {его значение и номер.}

flag := 1; {Выставляем «1» во флаг}

Break {и прерываем цикл.}

End;

If flag < > 1 Then WriteLn(’Отрицательных чисел нет.’); {В случае, если}

ReadKey {отрицательных чисел нет, выводим сообщение.}

End.


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

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

For i:=1 To 10 Do Это внешний цикл по i (от 1 до 10)

For j:=1 To 40 Do А это вложенный цикл по j (от 1 до 40)

Begin У них разные переменные цикла и разные

. . . . границы ее изменения.

End; тело выполняется 10 * 40 = 400 раз.

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

For i:=... Здесь уровень вложения равен 4.

For j:=...

For k:=...

For l:=...

. . . .

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

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

- Количество циклов в программе не ограничено (ограничено объёмом самой программы для данного компьютера, а состоит она из циклов или еще из чего, - дело программиста).

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

Теперь рассмотрим несколько примеров задач.

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

Пример: Составить программу вычисления суммы чисел 1+2+3+...+99+100


Program P_16;

Uses Crt;

Var i: Byte;

s: Word;

Begin

ClrScr; {Строка s:=0 называется инициализацией переменной.}

s:=0; {В некоторых языках, если этого не сделать, переменная}

For i:=1 To 100 Do s:=s+i; {имеет «нехорошее» свойство накапливать}

WriteLn(’S=’,s); {результат от запуска к запуску...}

ReadKey

End.


Пример: Определить сумму элементов одномерного массива. Размер массива не более 100.


Program P_17;

Uses Crt;

Var mass: Array[1..100] Of Real;

i, n: Byte;

s: Real;

Begin

ClrScr;

Write(’Введите размер вектора ’);

ReadLn(n); {Ввод размера массива.}

s:=0; {Инициализация переменной s.}

For i:=1 To n Do {Организация цикла.}

Begin

Write(’Введите ’,i:4,’-й элемент массива:’);

ReadLn(mass[i]); {Ввод очередного элемента}

s:=s + mass[i] {и его суммирование.}

end;

WriteLn(’Сумма равна ’, s:10:2); {Вывод результата.}

ReadKey

End.


Пример: Определить минимальный элемент и его номер в одномерном массиве.


Program P_18;

Uses Crt;

Var mass: Array[1..100] Of Real;

min: Real;

n, i, dig: Byte;

Begin

ClrScr;

Write(’Введите размер вектора (не более 100) ’);

ReadLn(n);

For i:=1 To n Do

Begin

Write(’Введите ’, i:4,’-й элемент ’);

ReadLn(mass[i])

End;

min:=mass[1]; {Вводим вектор в отдельном цикле.}

dig:=1;

For i:=2 To n Do If mass[i]< min Then {И сравниваем все элементы вектора, начиная со второго} Begin

{со значением min. Если оно меньше,} min:=mass[i];

{то запоминаем в min его значение} dig:=i

{и в dig его номер.} End;

WriteLn(’Элемент вектора с номером ’, dig);

WriteLn(’имеет минимальное значение ’, min:7:2);

ReadKey

End.


Данный метод решения этой задачи имеет в программировании своё имя и называется пузырьковым методом, или «методом пузырька».


Пример: Пусть массивы x и y содержат координаты n точек плоскости (n не более 100). Сколько из этих точек попадают в круг радиусом r с центром в начале координат?


Program P_19;

Uses Crt;

Var x, y: Array[1..100] Of Real;

r , s: Real;

i, k, n: Byte;

Begin

ClrScr;

Write(’Введите количество точек (не более 100) ’);

ReadLn(n);

Write(’Введите радиус окружности ’);

ReadLn(r);

k:=0;

For i:=1 To n Do

Begin {Цикл ввода координат точки и определения}

Write(’Введите через пробел координаты ’, i, ’-й точки ’);

ReadLn(x[i], y[i]); {расстояния от неё до начала координат.}

s:=sqrt(x[i]*x[i]+y[i]*y[i]);

If s<= r Then {Если это расстояние меньше радиуса, то}

Begin

k:=k+1; {учитываем эту точку.}

WriteLn(’Точка с номером ’, i, ’ в круге’)

End

End;

WriteLn(’Точек, попавших в круг, всего ’, k);

ReadKey

End.

Пример: Определить столбец двумерного целочисленного массива с наибольшей суммой. Чему равна эта сумма?

Для удобства, в этой задаче будем вводить элементы массива не по строкам, а по столбцам для того, чтобы в одном цикле, одновременно со вводом чисел, производить вычисление сумм столбцов. Это называется «эффективность».


Program P_20;

Uses Crt;

Var a: Array[1..100,1..100] Of Integer;

s: Array[1..100] Of Integer;

Max: Integer;

i, j, m, n, nmax: Byte;

Begin

ClrScr;

Write(’Введите размеры матрицы ’);

ReadLn(m, n);

For j:=1 To n Do

Begin

s[j]:=0; {Инициализация очередной суммы}

For i:=1 To m Do {Ввод элемента и его сложение}

Begin

WriteLn(’Введите элемент ’, j, ’-го столбца’);

Write(’и ’, i, ’-й строки: ’);

ReadLn(a[i,j]);

s[j]:=s[j]+a[i,j]

End

End;

Max:=s[1];

NMax:=1;

For j:=2 to n Do {Стандартное решение минмаксной задачи}

If s[j]>Max Then {методом пузырька}

Begin

Max:=s[j];

NMax:=j

End;

WriteLn(’Максимальная сумма в столбце ’, NMax, ’ равна ’, Max);

ReadKey

End.

Пример: Задана целочисленная матрица. Во сколько раз сумма элементов, стоящих на четных строках, больше, чем на нечетных строках?


Program P_21;

Uses Crt;

Var a: Array[1..100,1..100] Of Integer;

s1, s2: Integer;

otn: Real;

i, j, m, n: Byte;

Begin

ClrScr;

Write(’Введите размеры матрицы ’);

ReadLn(m, n);

s1:=0; {Инициализация сумм элементов чётных и}

s2:=0; {нечётных строк.}

For i:=1 To m Do {После ввода элемента с помощью операции mod}

For j:=1 To n Do {выясняем – строка чётная или нет?}

Begin

WriteLn(’Введите элемент ’, i, ’-й строки’);

Write(’и ’, j, ’-го столбца: ’);

ReadLn(a[i,j]);

If i mod 2 = 0 Then s1:=s1+a[i,j] Else s2:=s2+a[i,j]

End;

If s2=0 Then {На ноль делить нельзя!}

Begin

WriteLn(’Решение задачи невозможно!!!’);

ReadKey; Halt

End;

Otn:=s1/s2;

WriteLn(’Отношение сумм элементов равно’, s1/s2:10:5);

ReadKey

End.


Пример: разложить произвольное натуральное число n на простые сомножители.


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

hello_html_4641c3ba.gif



hello_html_m1a0ceda4.gif


hello_html_57aed7ff.gif

hello_html_7488a81e.gifhello_html_m23ba87f8.gif

hello_html_1fb1d26e.gif

+

-



hello_html_m311f0002.gifhello_html_m2df47aa7.gifhello_html_m2823cef2.gifhello_html_4641c3ba.gif


hello_html_1821a9a.gifhello_html_3c0018f9.gif

hello_html_1163dd06.gif

hello_html_m7370a650.gif

+

-




hello_html_mb60b119.gifhello_html_m2df47aa7.gifhello_html_1cbd7991.gifhello_html_m2df47aa7.gif



hello_html_5b716fc9.gif

d:= d + 1



hello_html_m5ee0d1.gif

hello_html_4641c3ba.gif


n:= n div d

hello_html_1e8ab3c9.gif

Рис.8


hello_html_m5ee0d1.gif


hello_html_m2823cef2.gif


Program P_22;

Uses Crt;

Var n: Integer;

d: Byte;

Begin

ClrScr;

Repeat

Write(’Введите число:’);

ReadLn(n); {fool-protect}

Until n > 1;

WriteLn(’Число ’, n, ’ есть произведение ’);

Write(1, ’*’);

d:=2; {Самый маленький возможный делитель.}

While n >1 Do {До тех пор, пока число больше 1,}

Begin {проверяем, делится ли n на d.}

While n mod d = 0 Do {Если да, то проверяем тот же}

Begin {самый делитель ещё раз.}

Write(d, ’*’);

{Обратите внимание на} n:=n div d

{ОПЕРАЦИЮ div!} End;

d:=d+1 {Если нет, то переходим}

end; {следующему делителю.}

ReadKey

End.


И ещё немного об условиях. Дело в том, что мы рассмотрели только случаи, когда происходит выбор одного, максимум, двух условий. А что, если нужно выбрать из 5 (а то и из 15!) вариантов? Для этого в Pascal существует конструкция

Case <переменная> Of

<выбор переменной>;

Else <выбор неудачен>;

End.


Пример: определите сезон года по номеру месяца.


Program P_23;

Uses Crt;

Var Month, Flag: Byte;

Begin

ClrScr;

Repeat

Write(’Введите номер месяца: ’);

ReadLn(Month);

Case Month Of

3..5: WriteLn(’Весна’);

6..8: WriteLn(’Лето’);

9..11: WriteLn(’Осень’);

1..2, 12: WriteLn(’Зима’);

Else WriteLn(’Такого месяца нет!’)

End;

WriteLn(’Повторим? (1-да, 0-нет) ’);

ReadLn(Flag);

Until Flag< >1;

ReadKey

End.


Логично будет предположить, что данная структура может применяться и без слова Else, точно так же, как и структура If...Then.

Вопрос 2.12.1. Сформулируйте определение массива.

Вопрос 2.12.2. Какие действия выполняет оператор Array?

Вопрос 2.12.3. Чем отличается массив от обычной переменной?

Вопрос 2.12.4. Можно ли одним оператором Array объявить сразу несколько массивов?

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

Вопрос 2.12.6. Для чего нужен оператор For? Как он записывается?

Вопрос 2.12.7. Что такое шаг цикла?

Вопрос 2.12.8. Может ли шаг цикла быть отрицательным?

Вопрос 2.12.9. Может ли шаг цикла быть не целым числом?

Вопрос 2.12.10. Можно ли в операторе For не указывать переменную цикла?

Вопрос 2.12.11. Что такое тело цикла?

Вопрос 2.12.12. Можно ли передавать управление вовнутрь тела цикла извне? А наоборот?

Вопрос 2.12.13. Какие ограничения использования переменной цикла, ее начального, конечного значений и шага действуют в теле цикла?

Вопрос 2.12.14. Что такое вложенный цикл?

Вопрос 2.12.15. Что такое уровень вложения?

Вопрос 2.12.16. Какие ограничения действуют для вложенных циклов?

Вопрос 2.12.17. Является ли произвольное число n простым? Составьте программу проверки.

Вопрос 2.12.18. Среди простых чисел 2 числа называются близнецами, если они отличаются на 2 (например, 41 и 43). Составьте программу печати всех близнецов на интервале от m1 до m2.

Вопрос 2.12.19. В одномерном массиве определить минимальный, максимальный элементы и их отношение.

Вопрос 2.12.20. Определить среднее арифметическое и среднее геометрическое всех элементов одномерного массива.

Вопрос 2.12.21. В одномерном массиве определить максимальную длину неубывающего участка.

Вопрос 2.12.22. Определить арифметический центр тяжести в одномерном массиве (максимальный номер элемента k, для которого сумма элементов от 1 до k не превосходит сумму элементов от k+1 до n).

Вопрос 2.12.23. Какой из двух одномерных массивов имеет большую сумму элементов?

Вопрос 2.12.24. Вывести те элементы одномерного массива и их количество, которые по модулю больше соответствующих элементов другого массива.

Вопрос 2.12.25. Совпадают ли номера у максимальных и минимальных элементов двух одномерных массивов?


§2.13 Подпрограммы

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

Рассмотрим пример. Задан массив 10 чисел. Нужно: 1. Распечатать этот массив. 2. Сортировать его элементы в порядке возрастания. 3. Распечатать то, что получилось после сортировки.

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


Var a: Array[1..10] Of Integer; {Исходный массив.}

. . . {Здесь введем данные (как - это сейчас неважно)}

For i:=1 To 10 Do {Выводим на экран все элементы массива}

Write(a[i]:6);

WriteLn;

. . . {Сортируем (тоже неважно - как)}

For i:=1 To 10 Do {Выводим результат сортировки}

Write(a[i]:6);

WriteLn;

ReadKey

End.


Обратите внимание, что две группы строк совпадают. Оба этих фрагмента выполняют вывод на экран 10 элементов массива а.

Чтобы не повторять этот фрагмент программы дважды, напишем его один раз, в одном месте и затем вызовем его на выполнение дважды.

Такой фрагмент и будет являться подпрограммой.

В Pascal существуют два типа подпрограмм – функции и процедуры.


ОБЯЗАТЕЛЬНО подпрограмма должна быть описана до того момента, когда она будет использоваться в основной программе или другой подпрограмме.


Параметры, которые использует подпрограмма, бывают локальные и глобальные. Локальные параметры доступны только самой подпрограмме и объявляются в ней же. Глобальные параметры объявляются в основной программе и доступны и в программе, и в подпрограмме. Информация передаётся между подпрограммой и основной программой только с помощью глобальных параметров.

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

Рассмотрим следующую задачу: вычислить сумму факториалов чисел от 1 до N. Факториалом числа называется произведение всех целых чисел от 1 до этого числа включительно.

Например: 5! (читается «пять факториал») = 1*2*3*4*5 = 120. Естественно будет предположить, что здесь надо использовать подпрограмму – функцию, основная задача которой – вычисление какого-либо параметра и передача его значения в основную программу. В нашем случае – факториала числа.

Достаточно сказать, что, например, 30! – это число в 33 позиции, а поэтому ни один из целочисленных форматов не пригоден для того, чтобы описать результат. А для использования форматов Extended или Comp необходимо включать расширенные возможности компилятора (математический сопроцессор) директивой {$N+}.

Итак:


Program P_24;

{$N+} {Включение расширенных возможностей}

Uses Crt;

Var s: Extended; {Глобальные переменные}

i, n: Byte;


Function Fact(i: Byte): Extended;

Var r: Extended; {Вычисление факториала текущей переменной}

j: Byte;

Begin

r:=1;

For j:=1 To i Do r:=r*j;

Fact:=r {Это необходимо для передачи значения функции}

End;

Begin

ClrScr;

Write(’Введите число: ’);

ReadLn(n);

For i:=1 To n Do

Begin

{Обращение к} WriteLn(’Факториал ’, i,’ равен ’, Fact(i));

{подпрограмме} s:= s + Fact(i);

{для проверки} WriteLn(’Текущая сумма равна ’, s);

WriteLn(’Для продолжения нажмите любую клавишу’);

ReadKey;

WriteLn

End;

Write(’Итоговый результат равен ’);

WriteLn(s);

ReadKey;

End.


Давайте рассмотрим эту программу подробней. Узлом решения данной задачи является цикл, где происходит суммирование и печать текущего значения факториала параметра цикла. Как только программа «натыкается» на слово Fact, управление передаётся в подпрограмму – функцию с этим именем, где и происходит расчёт факториала текущего значения i. После выполнения подпрограммы значение функции Fact передаётся обратно в основную программу. Вот и всё!

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

Пример: составить программу таблицы умножения.


Program P_25;

Uses Crt;

Type f = Function(x,y: Integer): Integer;

Var a,b: Integer;


{$f+} {Функция используется для переменных процедурного типа,}

Function Mult(x,y: Integer): Integer; {поэтому должен быть включен}

Begin {режим компиляции Force Far Calls}

Mult:= x * y;

end;

{$f-} {Отключение режима}


Procedure Print(a,b: Integer; Oper: f);

Var i, j: Integer; {Процедура построения таблицы}

Begin

For i:=1 To a Do

Begin

For j:= 1 To b Do Write(Oper(i, j):5);

WriteLn

End;

WriteLn

End;


Begin

ClrScr;

Write(’Введите количество строк и столбцов ’);

ReadLn(a, b);

Print(a, b, Mult);

ReadKey

End.


Формальные параметры подпрограммы указывают, с какими параметрами надо обращаться к подпрограмме. Они задаются в заголовке подпрограммы в виде списка, разбитого на группы, которые, в свою очередь, разделяются точками с запятыми. Всё, что не относится к формальным параметрам, называется фактическими параметрами.

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

Любую подпрограмму может вызвать не только центральная программа, но и любая из других подпрограмм. Центральную программу в разговорном языке называют Main-ом.

Подпрограмма может вызвать на выполнение и сама себя (непосредственно или через посредника). Такой прием в программировании называется рекурсией.

Для каждой переменной есть так называемая область видимости. Переменные с одним и тем же именем могут существовать в различных модулях и обозначать при этом совершенно различные объекты. При этом между ними нет ничего общего. Они известны (видимы) только внутри той процедуры, в которой существуют. Например, в основном модуле (в Main-е) есть переменная j. И в модуле Amin, предположим, тоже есть переменная j, но это уже совсем другая переменная, у нее другое значение. Она не имеет к той j, которая в Main-е, никакого отношения, разве что называются они одинаково. Говорят так, что областью видимости локальной переменной является процедура.

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

Для меток (ох уж эти метки… Ещё раз вспомним, что безусловная передача управления – не подарок. А уж в подпрограммах…) в процедуре тоже есть область видимости. Метки в разных процедурах могут повторяться и не иметь при этом ничего общего. Метка «видна» только в пределах процедуры.

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

Вопрос 2.13.1. Что такое подпрограммы? Для чего они нужны?

Вопрос 2.13.2. Какие операторы применяются для организации подпрограмм?

Вопрос 2.13.3. Где в программе располагают тексты подпрограмм?

Вопрос 2.13.4. Чем отличаются модули Procedure и Function?

Вопрос 2.13.5. Что такое формальные и фактические параметры? Какова между ними взаимосвязь?

Вопрос 2.13.6. Как вызываются модули типа Procedure и Function?

Вопрос 2.13.7. Что понимают под областью видимости переменной?

Вопрос 2.13.8. Чем отличаются локальные и глобальные переменные?

Вопрос 2.13.9. Чем отличаются статические и динамические переменные?

Вопрос 2.13.10. Может ли в основном модуле и в подпрограмме быть использована одна и та же метка?


§2.14 Строки

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

В памяти цепочки символов (объекты строкового типа) хранятся в виде кодов (чисел в диапазоне от -128 до 127, если рассматривать их как числа со знаком и от 0 до 255, - если без знака). Каждому символу соответствует свой код. Этот код называется ASCII - кодом (американская система кодирования информации). Такой код является международным. Код одного символа занимает ровно один байт. Цепочка символов заканчивается символом-ограничителем, содержащим ноль байт. (Причем, существует 2 типа стандартов: основной - здесь коды имеют диапазон от 0 до 127 - и расширенный - коды от 0 до 255. В основном не используется старший (знаковый) бит байта. Точнее, используется, но совсем для других целей. В современных компьютерах чаще применяются расширенные коды символов).

Ниже приведена таблица кодов ASCII для наиболее широко используемых символов. Кроме символов алфавита в таблице имеются управляющие символы, предназначенные для форматирования потока информации. Они равноправные со всеми остальными символы и применяются без особенностей. Каждое устройство, будь то дисплей или принтер, «знает» что делать, когда принимаешь тот или иной символ. Буква - значит, надо ее напечатать, CR - значит надо начать с начала строки и т.д.

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

Таблица кодов ASCII

«10» с/с

«16» с/с

Символ

Мнемоника

CTRL

0

00

Пустой символ

NUL


1

01

Смайлик

SOH

^A

2

02

Инвертир. смайлик

STX

^B

3

03

Черви

ETX

^C

4

04

Бубны

EOT

^D

5

05

Треф

ENQ

^E

6

06

Пики

ACK

^F

7

07

Ядро

BEL

^G

8

08

Инвертир. ядро

BS

^H

9

09

Круг

HT

^I

10

0A

Инвертир. круг

LF

^J

11

0B

Знак Марса

VT

^K

12

0C

Знак Венеры

FF

^L

13

0D

Нота

CR

^M

14

0E

Двойная нота

SO

^N

15

0F

Знак Солнца

SI

^O

16

10

Левый треугольник

DLE

^P

17

11

Правый треугольник

DC1

^Q

18

12

Дв. верт. стрелка

DC2

^R

19

13

Дв. воскл. знак

DC3

^S

20

14

Знак абзаца

NAK

^T

21

15

Знак раздела

SYN

^U

22

16

Квадратное ядро

ETB

^V

23

17

Дв. верт. линии

CAN

^W

24

18

Стрелка вверх

EM

^X

25

19

Стрелка вниз

SUB

^Y

26

1A

Стрелка вправо

ESC

^Z

27

1B

Стрелка влево

FS

^[

28

1C

Нижн. левый угол

GS

^\

29

1D

Дв. гориз. стрелка

RS

^]

30

1E

Треугольник вверх

US

^^

31

1F

Треугольник вниз


^_

«10»

«16»

Символ

«10»

«16»

Символ

32

20

Пробел

40

28

( Левая скобка

33

21

! Воскл. знак

41

29

) Правая скобка

34

22

« Кавычка

42

2A

* Умножение

35

23

# Знак номера

43

2B

+ Плюс

36

24

$ Знак доллара

44

2C

, Запятая

37

25

% Знак процента

45

2D

- Минус

38

26

& Амперсанд

46

2E

. Точка

39

27

' Апостроф

47

2F

/ Слеш


48

30

0

53

35

5

49

31

1

54

36

6

50

32

2

55

37

7

51

33

3

56

38

8

52

34

4

57

39

9


58

3A

: Двоеточие

62

3E

> Больше

59

3B

; Точка с зап.

63

3F

? Вопрос. знак

60

3C

< Меньше

64

40

@ Коммерч. ЭТ

61

3D

= Равно



65

41

A

78

4E

N

66

42

B

79

4F

O

67

43

C

80

50

P

68

44

D

81

51

Q

69

45

E

82

52

R

70

46

F

83

53

S

71

47

G

84

54

T

72

48

H

85

55

U

73

49

I

86

56

V

74

4A

J

87

57

W

75

4B

K

88

58

X

76

4C

L

89

59

Y

77

4D

M

90

5A

Z


91

5B

[ Лев. кв. скобка

94

5E

^ «Крышка»

92

5C

\ Обратный слеш

95

5F

_ Подчерк

93

5D

] Пр. кв. скобка

96

60

Обр. апостроф


97

61

A

110

6E

n

98

62

B

111

6F

o

99

63

C

112

70

P

100

64

D

113

71

Q

101

65

E

114

72

R

102

66

F

115

73

S

103

67

G

116

74

T

104

68

H

117

75

U

105

69

I

118

76

V

106

6A

J

119

77

W

107

6B

K

120

78

X

108

6C

L

121

79

Y

109

6D

M

122

7A

Z


123

7B

{ Лев. фиг.скобка

126

7E

~ Тильда

124

7C

Верт. Черта

127

7F

«Маленький дом»

125

7D

} Пр. фиг. скобка



128

80

А

160

A0

А

129

81

Б

161

A1

Б

130

82

В

162

A2

В

131

83

Г

163

A3

Г

132

84

Д

164

A4

Д

133

85

Е

165

A5

Е

134

86

Ж

166

A6

Ж

135

87

З

167

A7

З

136

88

И

168

A8

И

137

89

Й

169

A9

Й

138

К

170

AA

К

139

Л

171

AB

Л

140

М

172

AC

М

141

8D

Н

173

AD

Н

142

8E

О

174

AE

О

143

8F

П

175

AF

П

144

90

Р

224

EO

Р

145

91

С

225

E1

С

146

92

Т

226

E2

Т

147

93

У

227

E3

У

148

94

Ф

228

E4

Ф

149

95

Х

229

E5

Х

150

96

Ц

230

E6

Ц

151

97

Ч

231

E7

Ч

152

98

Ш

232

E8

Ш

153

99

Щ

232

E8

Щ

154

9A

Ъ

234

EA

Ъ

155

9B

Ы

235

EB

Ы

156

9C

Ь

236

EC

Ь

157

9D

Э

237

ED

Э

158

9E

Ю

238

EE

Ю

159

9F

Я

239

EF

Я


240

F0

Ё

248

F8

Градус

241

F1

Ё

249

F9

Инверт. градус

242

F2

Больше или равно

250

FA

Малое ядро

243

F3

Меньше или равно

251

FB

Радикал

244

F4

Верх. интеграл

252

FC

Индексный n

245

F5

Нижн. интеграл

253

FD

Индексное «2»

246

F6

Деление со знаком

254

FE

Квадрат

247

F7

Прибл. равенство

255

FF

Пробел - призрак


Коды с номерами 176 – 223 (B0 – DF) являются кодами псевдографики.

Целым, вещественным, двойной точности данным в памяти отводится место фиксированной длины: целым - 1 слово памяти, вещественным – 2 слова и двойной точности - 4 слова. Объектам строкового типа память распределяется динамически. С точки зрения программиста строковый объект имеет начало в зоне памяти, но не имеет конца. Система сама отслеживает размеры этих данных и не позволяет им перекрываться при расширении.

Нам уже знаком один из типов для хранения символов – Char. Но вот беда – переменные такого типа могут содержать только один символ. В Pascal есть ещё один тип, который позволяет задавать последовательность символов длиной от 0 до 255 – String. Например:

Var Stroka1: String[50];

Stroka2: String;

Это означает, что переменная Stroka1 имеет максимальную длину 50 символов, а переменная Stroka2 – длину 255 по умолчанию. К любому символу в строке можно обращаться как к элементу массива. Нулевой байт строки содержит её длину. То есть, если есть строка а:=’Вася’, то а[0]:=4.

Операции, проводимые над строками, конечно отличаются от привычных арифметических операций.

  1. Конкатенация (литерное сложение).

Пример: Из строк «Мама» и «мыла раму» сформировать строку «Мама мыла раму».

Program P_26;

Uses Crt;

Var a, b, c: String;

Begin

ClrScr;

a:= ’Мама’; b:= ’мыла раму’;

c:= a + ’ ’ + b;

WriteLn(c);

ReadKey

End.


Если длина итоговой строки превышает 255, то лишние символы отбрасываются!


2. Сравнение

Сравнение строк в Pascal идёт слева направо в соответствии с ASCII-кодами символов. Например ’AA’ > ’AB’ и ’F’ < ’FК’, т.к., например, во втором случае отсутствующий символ имеет меньший код, чем код символа К.

Pascal имеет довольно мощный инструментарий для работы со строками.

1. Функция Length определяет длину строки. Например, для того, чтобы распечатать все символы строки s достаточно дать команду:

For i:=1 to Length(s) Do WriteLn(s[i]);

Значение функции – число типа Integer.

2. Функция Copy(stroka, n, k) копирует из строки stroka подстроку длиной k, начиная с позиции n.

Var Stroka:= ’Мама мыла раму’;

Pod:= Copy(Stroka, 6, 4);

Значение переменной Pod равно ’мыла’.

3. Процедура Delete(stroka, n, k) удаляет из строки stroka k символов, начиная с позиции n.

Kuzia:= ’Мама мыла раму’;

Delete(Kuzia, 10, 5);

Значение переменной Kuzia=’Мама мыла’.

4. Процедура Insert(Pod, Stroka, n) осуществляет вставку подстроки Pod в строку Stroka с позиции n.

Petia:=’Петя ушёл’;

Dom:=’домой ’;

Insert(Dom, Petia, 6);

Значение переменной Petia = ’Петя домой ушёл’.

5. Функция Pos(Pod, Stroka) осуществляет поиск первого вхождения подстроки Pod в строку Stroka. Тип функции – Integer. Если подстрока отсутствует в строке, то значение функции равно нулю.

Stroka:= ’Иванов Пётр Сергеевич’;

Pod:=’Пётр’;

k:=Pos(Pod, Stroka);

Значение переменной k=8.

6. Процедура Str преобразует число любого целочисленного и вещественного типов в строку символов.

Perem:=3003;

Str(Perem, Stroka);

Delete(Stroka, 2, 2);

Данные команды превращают переменную Perem в строку Stroka и удаляют из ней 2 средних символа. Stroka = ’33’.

7. Процедура Val превращает строку, содержащую цифры, в целое или вещественное число.

Пример: перемножить 5-ю и 6-ю цифры после запятой числа π.


Program P_27;

Uses Crt;

Var Stroka: String;

Piat, Shest: Char;

a, b, Rez, Flag: Integer; {Flag – специальная переменная,}

Begin {применяющаяся в процедуре Val для проверки,}

ClrScr; {успешно ли прошёл перевод. Если Flag=0, то успешно.}

Str(Pi, Stroka); {Превращение числа π в строку.}

Piat:= Stroka[8]; {Выделение 8 и 9 символов строки. Не забудем, что}

Shest:= Stroka[9]; {счёт начинается не с одного, а с нуля!}

Val(Piat, a, Flag); {Преобразование символов}

Val(Shest, b, Flag); {в числа.}

Rez:= a * b;

WriteLn(Pi); {И выведем всё это}

WriteLn(a, b); {на экран}

WriteLn(Rez); {для проверки...}

ReadKey

End.


Пример: дата задана в виде строки чч-мм-гг, где чч - 2 символа - число месяца, мм - 2 символа - месяц, гг - 2 последние цифры года. Найти произведение числа, месяца и года.


Program P_28;

Uses Crt;

Var Stroka, Day, Month, Year: String;

d, m, y, Rez, Code: Integer;

Begin

ClrScr;

WriteLn('Введите дату в виде чч-мм-гг');

ReadLn(Stroka);

Day:= Copy(Stroka,1,2);

Month:= Copy(Stroka,4,2);

Year:= Copy(Stroka,7,2);

Val(Day, d, Code);

Val(Month, m, Code);

Val(Year, y, Code);

Rez:= d * m * y;

WriteLn(Day,’ ’, month,’ ’, year);

WriteLn(Rez);

ReadKey;

End.

Пример: задана строка. Напечатать ее наоборот.


Program P_29;

Uses Crt;

Var Stroka: String;

i: Integer;

Begin

ClrSCr;

Write(’Введите строку’);

ReadLn(Stroka);

For i:=Length(Stroka) DownTo 1 Do Write(Stroka[i]);

WriteLn;

ReadKey

End.


Пример: удалить из строки все пробелы.


Program P_30;

Uses Crt;

Var Stroka, Rez: String;

i: Integer;

Begin

ClrSCr;

Write(’Введите строку’);

ReadLn(Stroka); {Здесь введена дополнительная переменная Rez...}

Rez:=’’; {А можно ли обойтись без неё?}

For i:=1 to Length(Stroka) Do

If Stroka[i]< > ’ ’ Then Rez:= Rez + Stroka[i];

WriteLn(Stroka);

WriteLn(Rez);

ReadKey

End.


При обработке текстов часто встречается задача разбиения текста на отдельные слова.

Существует множество таких алгоритмов. Хороших и не очень. Основные недостатки тех алгоритмов, которые «не очень»: не учитываются хвостовые и (или) начальные пробелы в строке, не учитываются особенности форматированного текста, в котором пробелов между словами может быть несколько. Приведенные ниже алгоритмы от этих недостатков свободны. Они несколько громоздки и грубоваты, зато могут быть применены с минимальными доработками на любых версиях.

Попробуем вначале разбить на слова одну строку. Алгоритм будет таков:

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

2. Ищем позицию первого пробела в строке правее Left. Если не находим, то от Left до конца строки - последнее слово. Выделяем его и заканчиваем. Если находим, то пусть номер этого символа будет Right.

3. Выделяем часть строки от Left до Right. Это будет слово.

4. Началом строки считаем позицию Right.

5. Переходим к шагу 1.


Program P_31;

Uses Crt;

Var Stroka, Slovo: String;

i, Kol, Right, Left, Dlina: Integer;

Begin

ClrScr;

Write('Введите строку ');

ReadLn(Stroka);

Kol:=0;

;

Dlina:=Length(Stroka);

While Right <= Dlina Do

Begin

While(Stroka[Right]=' ') And (Right <= Dlina) Do ;

;

While (Stroka[Right]< >' ') And (Right<=Dlina) Do ;

Kol:=Kol+1;

Slovo:=Copy(Stroka, Left, Right-Left);

WriteLn(Slovo)

End;

If Stroka[Dlina]=' ' Then Kol:=Kol-1;

WriteLn('Всего слов ', Kol);

ReadKey

End.

Достаточно будет оформить массив Var Slovo: Array[1..128] Of String для того, чтобы сохранить слова в памяти...

Вопрос 2.14.1. В каком виде символы хранятся в памяти компьютера?

Вопрос 2.14.2. Какова максимальная длина строки?

Вопрос 2.14.3. Как производится слияние двух и более строк?

Вопрос 2.14.4. Перечислите функции и процедуры, применяемые при работе со строками.

Вопрос 2.14.5. Какие действия выполняет функция Copy?

Вопрос 2.14.6. Какое действие выполняет процедура Val? Какая процедура выполняет обратное действие?

Вопрос 2.14.7. Какова минимальная и максимальная длина строки символов?

Вопрос 2.14.8. Сколько предложений в строке Stroka? (каждое предложение заканчивается точкой или одним из знаков: ? или !)

Вопрос 2.14.9. Заменить в строке Stroka все символы ";" на ",".

Вопрос 2.14.10. Сколько слов в строке Stroka?

Вопрос 2.14.11. Текст задан в виде массива строк. Сколько в нем предложений, сколько абзацев?

Вопрос 2.14.12. Сколько раз в тексте встречается буква "к" (кириллица, строчная)?

Вопрос 2.14.13. Напишите подпрограмму преобразования строки даты из формата чч-мм-гг в формат чч-ммм-гг, где

чч - два символа - число,

мм - два символа - номер месяца,

гг - две последние цифры года,

ммм- три первых символа (рус.,строчные) названия месяца.

Вопрос 2.14.14. Сколько в строке Stroka гласных, сколько согласных букв и сколько пробелов?

Вопрос 2.14.15. Текст задан в виде массива строк произвольной длины. Распечатать тот же текст строками ровно k символов (строки не имеют переносов, в выходном тексте строки также не переносить, обрывать).

Вопрос 2.14.16. При написании текста автор допускал ошибки: «жи» и «ши» писал с буквой «ы». Исправьте в тексте все ошибки этого типа (текст задан в виде массива строк).

Вопрос 2.14.17. Номер билета представляет собой шестизначное число. Напечатайте все «счастливые» номера.


§2.15 Файлы

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

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

Напомним: файлом называется именованный набор данных на внешнем носителе. С файлом можно сравнить записанную на лазерный диск песенку. Эта песенка имеет название (имя файла) и «расположена» на внешнем устройстве (на диске). Название песенки включено в каталог (директорию) диска.

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

Для начала рассмотрим общие понятия без привязки к какой-либо версии.

Над файлами можно производить следующие элементарные операции: создать, открыть, закрыть, удалить, переименовать, считать информацию из файла, записать информацию в файл.

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

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

Писать в последовательные файлы можно только в конец, добавлять записи в «хвост» файла.

Записи в последовательных файлах разделены специальными символами (как правило, это символы возврата каретки (CR) и перевода строки (LF)). Длина записи может быть различной и внутри одного файла могут быть записи разной длины.

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

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

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

Собственно, в Pascal (и не только. Например, в C, Perl) происходит обращение не к файлу, а к файловой переменной, которая соответствует этому файлу. Строго говоря, Pascal требует обязательного упоминания файлов в заголовке программы, но если не очень хочется... Итак, файловая переменная «привязывается» к файлу с помощью команды Assign.

Assign(f, File_name);

f – файловая переменная, объявленная ранее как переменная файлового типа и

File_name – текстовое выражение, содержащее имя файла, путь к нему или логическое устройство.

Var f1: text;

f2: File Of String;

Const Name = ’d:\Progi\dom.txt’;

Begin

Assign(f1, ’d:\Files\My_f\kuzia.dat’);

Assign(f2, Name);

Включается (инициируется) файловая переменная с помощью команды Reset обычно следующим образом:

Var f: File Of String;

Begin

Assign(f, ’c:\Unit\v.txt’);

{$i-} {Отключение контроля ошибок ввода-вывода}

Reset(f);

{i+} {Включение контроля ошибок ввода-вывода}

If IOResult < > 0 Then {Файла нет...} Else {Файл есть!}

End.

Встроенная функция IOResult (Input-Output Result) позволяет проконтролировать существование файла. Если её значение в данном случае не ноль, то файла нет.

Файловые процедуры и функции:

  1. Процедура Close(<файловая переменная>)

Закрывает файл.

2. Процедура Rename(<файловая переменная>, <новое имя>)

Переименовывает файл.

3. Процедура Erase(<файловая переменная>)

Уничтожает файл. Перед выполнением процедуры файл должен быть закрыт.

4. Функция EOF(<файловая переменная>)

Определяет, закончен файл или нет.

5. Процедура ChDir(<путь>)

Изменяет текущий каталог по умолчанию

6. Процедура MkDir(<каталог>)

Создаёт новый каталог

7. Процедура RmDir(<каталог>)

Удаляет каталог. Каталог должен быть пустым.

8. Знакомая уже функция IOResult. Содержит условный признак последней операции ввода – вывода.

9. Процедура GetFtime(<файловая переменная>, <время>)

Возвращает время создания (или последнего обновления) файла.

10. Процедура SetFtime(<файловая переменная>, <время>)

Устанавливает новую дату для файла.

11. Функция FSearch(<файловая переменная>, <список каталогов>)

Производит поиск файла в списке каталогов

12. Процедура Read(<файловая переменная>, <список ввода>)

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

13. Процедура Write(<файловая переменная>, <список вывода>)

Ну а здесь, естественно, вывод в текстовый файл.

Естественно, приведены не все процедуры и функции для работы с файлами, да это в данном случае и не нужно. Существует огромное количество справочной литературы, где в полном объёме приводится описание всех функции. Например, классический двухтомник В.В. Фаронова издательства «Нолидж».

Допустим, на диске D в каталоге User существует файл s.dat, в котором содержится не более 100 вещественных чисел. Наша задача – считать (прочитать) эти числа, допустим, в массив, вычислить сумму этих чисел и, собственно говоря, узнать, сколько там было чисел.

Program P_32;

Uses Crt;

Var f: Text; {Это - файловая переменная.}

Mass: Array[1..100] Of Real;

Summ: Real;

i: Integer;

Begin

ClrScr;

Assign(f,’D:\User\s.dat’); Привязываем» переменную к файлу,}

Reset(f); {становимся в его начало}

i:=1; {и рассматриваем первое число, не забывая}

Summ:= 0; {инициализировать сумму.}

While Not EOF(f) And (i < = 100) Do {Пока файл на закончен и}

Begin {количество записей меньше или равно 100,}

Read(f, Mass[i]); {считываем из файла очередное число и}

Summ:= Summ + Mass[i]; {прибавляем его к сумме.}

i:= i + 1 {Переходим к следующему числу. Кстати, здесь}

End; {уместно использовать функцию Inc(i)}

Close(f); {Закрываем файл.}

WriteLn(’Сумма чисел файла равна ’, Summ:10:2);

WriteLn(’В файле всего ’, i - 1, ’ чисел’);

ReadKey

End.


Пример: Известно, что стандарт издательского листа – 40000 символов. Определить количество листов файла.

Program P_33;

Uses Crt;

Var f: Text;

Path, s: String; {Это путь к файлу и имя записи в файле}

Summ: LongInt; {Общее количество символов}

List: Real;

Begin

ClrScr;

Write(’Введите путь к файлу’);

ReadLn(Path);

Assign(f, Path); {Включаем файловую переменную и}

Reset(f); {становимся в начало файла.}

If IOResult< >0 Then {Такой узел уже встречался. Помните?}

Begin

WriteLn(’Такого файла нет!!!’);

Close(f)

End

Else Begin

Summ:= 0; {Инициализация суммы.}

While Not EOF(f) Do

Begin {Если файл не закончен}

ReadLn(f, s); {Считываем строку и}

Inc(Summ, Length(s)) {прибавляем её длину к Summ}

End;

Close(f);

List:= Summ/40000;

WriteLn(’Количество листов в файле ’, List:7:2)

End;

ReadKey

End.


Пример: в файл Chisla.txt на диске D поместить 100 целых чисел, значения которых лежат в интервале от –1000 до 1000.


Program P_34;

Uses Crt;

Var f: Text;

a, i: Integer;

Begin

ClrScr;

Randomize;

Assign(f, ’D:\Chisla.txt’);

Rewrite(f); {Файл для записи, поэтому Rewrite, а не Reset!}

For i:=1 To 100 Do

begin

a:= Random(2001) - 1000;

WriteLn(f, a)

End;

Close(f);

ReadKey

End.

Вопрос 2.15.1. Что такое файл?

Вопрос 2.15.2. Какие операции можно производить над файлом?

Вопрос 2.15.3. Что такое запись?

Вопрос 2.15.4. Что такое обращение к файлу?

Вопрос 2.15.5. Чем различаются файлы прямого и последовательного доступа?

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

Вопрос 2.15.7. Каковы достоинства и недостатки файлов прямого доступа?

Вопрос 2.15.8. Что означает «Открыть файл»?

Вопрос 2.15.9. Что такое текущая запись?

Вопрос 2.15.10. Что такое позиционирование и для чего оно применяется?

Вопрос 2.15.11. Для чего применяется функция EOF? Какое значение она возвращает?



Глава III. Принципы программирования.


§3.1 Приемы программирования

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

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

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

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

Есть еще один уровень общения, которым часто пренебрегают, - между составителем программы и ее читателем. Как правило, составитель программы предполагает, что читателями будут он сам да вычислительная машина. Подобно любым человеческим творениям, программа несовершенна, и разрабатывая её, необходимо помнить, что её придется «сопровождать». Поэтому необходимо предпринимать все усилия, дабы логика программы была прозрачна, следовала из ее текста.

С другой стороны, современные программы становятся настолько сложными искусственными объектами, что многие профессионалы в этой области относят программное обеспечение к области инженерной деятельности.

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


§3.2 Интерфейс пользователя

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

Компьютер общается с внешним миром с помощью устройств ввода-вывода.

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

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

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

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


Program v1; Program v2;

Uses Crt; Uses Crt;

Var x: Real; Var x: Real;

Begin flag: Byte;

ClrScr; Begin

ReadLn(x); Repeat

WriteLn(sqrt(x)); Repeat

End. ClrScr;

WriteLn(’Программа вычисляет кв. корень’);

Write(’Пожалуйста, число, >=0’);

ReadLn(x);

Until x > = 0;

WriteLn(’Значение корня из числа равно ’)

Writeln(sqrt(x):10:5);

WriteLn(’Нажмите любую клавишу’);

ReadKey;

Write(’Мучаем компьютер дальше? (0-нет, 1-да)’);

ReadLn(flag);

Until flag=0

End.


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

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

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

Создавая свои программы, помните, что кому-то придется с ними работать. То, как ваша программа относятся к пользователю, характеризует вас не столько как программиста, сколько как человека. Это адресовано тем, кто считает позволительным «отпускать» в адрес пользователя пикантные шуточки, не задумываясь над тем, что не всеми они могут быть восприняты как шуточки. По-сути, основные «шутники» - вирусописатели, но это уже тема для отдельного разговора.

Еще одна сторона вопроса - это так называемая «защита от дурака». Приношу свои извинения, если вам эта фраза показалась несколько грубоватой, но это не грубость и не шутка, а реальное инженерное понятие. На самом деле, одно из основных свойств программы – реакция должным образом на некорректные действия пользователя. Отсюда и название «fool-protect», или «защита от дурака» (если дословно). Этим свойством должны обладать все модули интерфейса пользователя. Второй вариант приведенной выше программы содержит fool-protect. Здесь производится проверка корректности данных и обработка ситуации, возникающей при ошибочном вводе.

Здесь, однако, тоже нужно применять принцип разумной достаточности. (Великому программисту Кернигану - автору учебника по языку С - приписывают такую фразу: «Если с вашей программой сможет работать даже дурак, то никто, кроме дурака, с ней работать не станет»). Кое-что нужно оставить и для пользователя.

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

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

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



1. Стройте программы таким образом, чтобы они не «хамили» пользователю независимо от его действий;

2. Всякий ввод в программе должен сопровождаться подсказкой (что именно нужно ввести в настоящий момент);

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

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


Вопрос 3.2.1. Что понимают под интерфейсом пользователя?

Вопрос 3.2.2. Какими свойствами должны обладать фрагменты программ, относящиеся к интерфейсу пользователя?


§3.3 Понятие о структурном программировании

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

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

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

Такой принцип организации программ называется структурным программированием, а такие фрагменты - структурами. Процесс написания программ для вас сводится к компоновке структур в одно единое целое, организацию связей между структурами.

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

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

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

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

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

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

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

6. В тексте программы структуры рекомендуется отделять друг от друга. Структуру более низкого уровня (вложенную) рекомендуется располагать правее объемлющей. Операторы внутри одной структуры располагают на одном уровне.

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

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

9. Наиболее часто используемые подпрограммы удобно хранить в библиотеках и подключать к своим программам по мере необходимости на этапе компоновки.

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

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


Рассмотрим пример. Задана квадратная целочисленная матрица, размером не более 100 на 100. Сортировать элементы строк матрицы в порядке возрастания, а сами строки расположить в порядке убывания их сумм.

На первом этапе составим план решения задачи.

а. Ввод исходных данных,

б. Нахождение сумм строк матрицы,

в. Сортировка элементов в строках,

г. Сортировка строк.

д. Вывод результатов работы

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

Обозначим исходную матрицу - а. Пусть она имеет размер n x n. Для хранения сумм строк матрицы нам потребуется еще n ячеек памяти. Пусть это будет массив s[n]. Итак, скелет программы.

Write(’Введите размер матрицы ’);

ReadLn(n);

{********** вводим исходные данные *****************}

For i:=1 To n Do

Begin

WriteLn(’Вводим ’, i, ’-ю строку’);

{...ввести n элементов i-й строки}

End;

{*********** находим суммы строк *******************}

For i:=1 To n Do

Begin

{...s[i]=сумма i-й строки матрицы}

End;

{********* сортировка элементов в строках **********}

For i:=1 To n Do

Begin

{...сортировать i-ю строку}

End;

{************* сортировка строк ********************}

metka: k:= 0;

For i:=1 to n-1 Do {И сразу подумаем: нужен ли Begin здесь? Если друг}

Begin {за другом следуют 2 структурных оператора? Do и If?}

If s[i]<=s[i+1] Then

Begin

{...поменять местами i-ю и (i+1)-ю строки}

{...поменять местами s[i] и s[i+1]}

k:= k+1

End

End;

If k>0 Then metka;

{*************** вывод результатов *****************}

For i:=1 To n Do

Begin

{...вывести n элементов i-й строки}

{...вывести для контроля сумму этой строки}

End;

...

End.


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

Вставив в этот скелет структуры, попутно пытаясь модифицировать некоторые структуры и введя в него комментарии, получим готовую программу. (Во многих случаях комментарии вводятся в программу с помощью оператора WriteLn. Тогда тексты комментариев помимо того, что просто присутствуют в программе, еще и выводятся на экран в процессе ее работы).


Program P_35;

Uses Crt;

Var a, b: Array[1..100, 1..100] Of Integer; {Матрица [b] нужна для}

s: Array[1..100] Of Integer; {резервирования матрицы [а], т.к.}

zam: Integer; {исходная матрица будет преобразована.}

i, j, n: Byte; {Да и сравнить надо...}

Begin

While (n<2) Or (n>100) Do

Begin {fool-protect}

ClrScr;

Write(’Введите, пожалуйста, размер матрицы ’);

ReadLn(n);

End;

For i:=1 To n Do

Begin

WriteLn(’Вводим ’, i, ’-ю строку...’);

s[i]:=0;

For j:=1 To n Do

Begin

Write(’a[’, i, ’,’, j, ’]=’);

ReadLn(a[i,j]);

{Создадим копию элемента} b[i,j]:=a[i,j];

{и сразу суммируем его} s[i]:=s[i]+a[i,j]

End

End;

For k:=1 To n Do {Избыточное количество проходов поможет избежать}

For i:=1 To n Do {меток. Берём пару элементов-сумм, и если они}

If s[i]<= s[i+1] Then {стоят не на месте – меняем их местами.}

Begin

zam:=s[i];

s[i]:=s[i+1];

s[i+1]:=zam;

{А теперь то же самое делаем} For j:=1 To n Do

Begin

{с каждым элементом переставляемой строки} zam:=a[i,j];

a[i,j]:=a[i+1,j];

a[i+1,j]:=zam

{Итак, строки переставлены} End

End; {в соответствии с их суммами, но внутри сами}

For i:=1 To n Do {строки ещё надо отсортировать по возрастанию.}

For k:=1 To n Do {Чем мы сейчас и занимаемся. В итоге}

For j:=1 To n-1 Do {строки внутри отсортированы по возрастанию}

If a[i,j]>=a[i,j+1] Then {элементов, а сами строки}

Begin {уже переставлены по}

zam:=a[i,j]; {убыванию}

a[i,j]:=a[i,j+1]; {их сумм.}

a[i,j+1]:=zam

End;

ClrScr;

WriteLn(’Исходная матрица’);

For i:=1 To n Do

Begin {Выведем строку, позиционируя элементы}

For j:=1 To n Do Write(b[i,j]:7);

WriteLn {и перейдём к следующей строке}

End;

WriteLn(’Конечная матрица и суммы по строкам’);

For i:=1 To n Do

Begin {Выведем матрицу результата и суммы по строкам}

For j:=1 To n Do Write(a[i,j]:7);

Write(’ s=’, s[i]);

WriteLn

End;

ReadKey {Наслаждаемся!}

End.


По мере приобретения опыта в программировании понятия о элементе программы меняются.

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


§3.4 Как создать модуль в Pascal.

Ничего сложного или страшного в создании модулей нет. Просто нужно соблюдать простые нехитрые правила. Используя модули, можно разбивать программу на отдельные части и, что немаловажно и удобно, компилировать их отдельно. Программный модуль представляет собой набор констант, типов данных, переменных, процедур, функций, которые могут использоваться одной или несколькими программами одновременно. Давайте договоримся, что программа (пока одна), использующая модуль, будет называться «главной», или, как принято Main-ом. Что представляет из себя структура программного модуля? Да почти то же самое, что и сама программа, его использующая.

Unit название модуля ; {Внимание! Название модуля должно }

{$..}; {совпадать с именем файла, хранящего модуль!}\

<Интерфейсный раздел>

Interface {Здесь описываются глобальные метки, константы,}

Uses . . .; {типы, переменные, процедуры и функции}

. . . . .

<Раздел реализации>

Implementation {Здесь описываются «местные» метки}

Uses . . .;{константы, типы, пременные, процедуры и}

. . . . . {и функции}

Begin

. . . . . {Основной блок модуля}

End.


Пример. Необходимо сложить 2 числа. Причём сложение должно производиться в отдельном модуле, а вывод результата – в самой программе. Ну захотелось нам так… Сначала пишем модуль.

Unit Primer; {Название модуля}

Interface

Var a, b, c: Real; {Объявление переменных. Видны - везде}

Implementation

Begin

Write(‘A=’); ReadLn(a); {Ввод переменных}

Write(‘B=’); ReadLn(b);

c:= a + b {и, собственно, сложение}

End.

Обратите внимание: и интерфейсный, и реализационный раздел могут быть пустыми (а это надо?). Интерфейсный раздел определяет, что является «видимым» для любой программы (или модуля), использующей данный модуль. Раздел реализации – приватная часть модуля, которая «видна» только самому модулю.

Компилируем модуль. В разделе Compile выбираем режим компиляции DestinationDisk и компилируем файл с именем Primer. (Не будем заводить разговор о целевых платформах, дабы не запутаться). Ай! Появилось окно со страшной надписью – Cannot run a unit. И что? Ну не может Pascal запустить модуль отдельно. Нажимаем ОК и далее пишем сам Main.

Program s_1;

Uses Crt, Primer; {Вот оно! Мы подключаем свой собственный модуль!}

Begin ClrScr;

WriteLn(‘C=’; c:7:2);

ReadKey

End.

Всё! Сложно? Ну, первый раз сложно и компьютер включить. А теперь программирование зависит от времени и фантазии. Зачем это надо? Причины, по-сути, две: работа с несколькими файлами одновременно и высокая скорость компиляции. Pascal включаются стандартные модули System, String, Crt, Printer, Dos, WinDos, Graph, Overlay, Graph3 и Turbo. Описание – в любом справочнике.

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

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

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

Вопрос 3.4.1. Что лежит в основе структурного программирования?

Вопрос 3.4.2. Что такое модуль?

Вопрос 3.4.3. Перечислите основные правила организации программ по принципу структурного программирования.


§3.5 Об использовании оператора Goto

С того времени, как в лексиконе программистов появился термин «структурное программирование», не затихают споры об использовании оператора Goto.

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

Некоторые языки программирования вообще не имеют в своем словаре такого оператора.

Аргументы «за» и «против» имеются как у одних, так и у других. Структурная организация программы позволяет построить ее таким образом, что взаимодействие между структурами вполне можно «завести» без применения оператора Goto, распределив структуры в теле программы в соответствии с алгоритмом.

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

Практически можно рекомендовать следующее.

Не следует прибегать к изощренным приемам написания программ без Goto ради подражания авторитетам или ради утверждения оригинальности среди своих коллег. В то же время, использование Goto в программах следует сводить к минимуму там, где это возможно. Особенно это относится к организации связей между структурами. Именно там от Goto больше всего неприятностей. Не согласны? Ну, сколько людей, столько и мнений. Кстати, ни в одной из программ этой книги оператор Goto не используется.

§3.6 Особенности программирования циклов

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

Пример. Найти сумму элементов массива а(100,10). Ниже приведены два варианта решения (опущен фрагмент ввода данных).


Var a: Array[1..100,1..10] Of Real; Var a: Array[1..100,1..10] Of Real;

. . . . . . . . . . . . . . . .

s:=0; s:=0;

For i:=1 To 100 Do For i:=1 To 10 Do

For j:=1 To 10 Do For j:=1 To 100 Do

s:= s + a[i,j]; s:= s + a[j,i];

. . . . . .


При кажущемся безразличии, на самом деле второй вариант является предпочтительным. В том и другом случае строка суммирования будет выполнена 1000 раз. Однако если учесть, что сам оператор For тоже требует времени на выполнение (подготовка вычислений, определение тела цикла, задание начального и конечного значений переменной цикла), то легко заметить, что вложенный For в первом примере выполняется 100 раз, а во втором - 10. Внешний For в том и другом случае выполняется один раз (имеется в виду не выполнение тела цикла, а подготовительные функции оператора For). Очевидно, что второй вариант будет работать быстрее. В больших программах применение этого приема программирования циклов может существенно повлиять на время выполнения программ.

Еще одна тонкость программирования циклов. Где производится проверка достижения переменной цикла его конечного значения: в начале или в конце цикла? Перед очередным выполнением тела или после него? Ответ – в начале.

Например, такой фрагмент программы не вызовет печати слова «Вася», т.к. проверка начальных и конечных значений производится перед выполнением цикла. (Или всё-таки один раз «Вася» проскочит? Почему? Проверьте…)

For i:=10 To 1 Do WriteLn('Вася');

Вопрос 3.6.1. Проверьте, где производится проверка условия выхода из цикла.

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

Вопрос 3.6.3. Есть ли в матрице элементы, равные сумме модулей оставшихся элементов этой же строки? Напечатайте такие элементы и их количество.

Вопрос 3.6.4. Есть ли в матрице элементы, модуль которых меньше суммы элементов, окружающих данный сверху, снизу и с боков? Напечатайте такие элементы и их количество.


§3.7 Вставка и удаление

Задан массив. Для начала одномерный (вектор). Максимальный размер, допустим, 100. В этот массив нужно добавить элемент, расположив его после элемента с номером К (это первая задача, - задача вставки) или удалить элемент (выбросить, уничтожить). Это вторая задача, - задача удаления.

Пойдем по порядку.

Задача первая: вставка элемента (добавление).

Пусть массив содержит N целочисленных элементов. Нужно добавить к массиву еще один элемент (предположим, что он пока «живет» в переменной С). Попробуем «поставить его в строй» после элемента номер К (, т.е. номер вставляемого элемента будет К+1).


Program P_36;

Uses Crt;

Var i, n, k, Lb, Rb, c: Integer;

a: Array[1..11] Of Integer;

Begin

Randomize;

ClrScr;

Write('Введите размер целочисленного массива, не более 10 ');

ReadLn(n);

Write('Введите диапазон значений массива ');

ReadLn(Lb, Rb);

While (k < 1) or (k > n + 1) Do {Защита от неправильного ввода номера}

Begin {вставляемого элемента}

Write('Введите номер вставляемого элемента и его значение ');

ReadLn(k,c)

End;

WriteLn('Исходный массив');

For i:=1 To n Do {Цикл генерации и печати исходного вектора}

Begin

a[i]:= Random(Rb- Lb+ 1)+ Lb;

Write(a[i]:5)

End;

WriteLn;

For i:=n DownTo k Do a[i+1]:= a[i]; {Сдвигаем «хвост» вектора и}

a[k]:=c; {ставим в «голове хвоста» заданное значение}

WriteLn('Массив результата');

For i:=1 To n+1 Do Write(a[i]:5);

WriteLn;

ReadKey

End.


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

Почему пришлось выполнять цикл назад, начиная от "хвоста" массива?

Опять вернемся к примеру с солдатами. Стоит строй. Пришел новенький, которого нужно пристроить. В строю стоит N солдат, а его нужно поставить К-ым. Значит, нужно раздвинуть строй, начиная от К-го до последнего вправо. Как мы это делаем? Последнего сдвигаем на шаг вправо. Его место становится свободным. Предпоследнего ставим на то место, где только что стоял последний. И так далее до номера К (см. цикл For i:=n DownTo k Do ...). Теперь освободилась ячейка номер К. В нее-то мы и запишем элемент С (пристроим новенького). Цель достигнута. Выводим результаты.

Задача вторая. Убрать ненужный элемент.

Пусть его номер К (нам уже сообщили). Из N элементов массива останется лишь N-1. Одна ячейка в конце концов должна оказаться пустой.

Внимание! Есть 2 типа таких задач. Первый. Когда просят только лишь «уничтожить» какой-либо элемент «в строю», не смыкая строя. И второй - уничтожить, и «замести следы». Всех, кто стоял справа, сдвинуть левее, чтобы не было прорехи. Тогда свободное место окажется в конце строя. В любом случае, прежде чем приступать к решению, внимательно ознакомьтесь с условием, выясните: а что, собственно...

В первом случае (без смыкания) задача тривиальна и решается одним оператором: a[k]=0. Здесь мы просто «стираем» (приравниваем к нулю; наверняка, интуиция подсказывает, что это не самый лучший способ уничтожения). Во втором случае нужно сомкнуть строй. Посмотрим, как это делается.

.....

For i:= k+1 To n do a[i-1]:=a[i]; {каждый из элементов, стоящих правее К}

{сдвигаем влево на единицу («подтягиваем хвост»)}

a[n]:= 0; {последнего «уничтожаем».}

.....

Всех, кто стоял правее К-го элемента, подвинули левее. При этом К-й элемент был стерт при первом же шаге (на его место встал К+1 -й). Последний встал на место предпоследнего, но при этом еще остался на своем! Их стало «двое». Последних. Мы его скопировали. Чтобы этого не было, дабы последняя ячейка была «чистой», мы ее обнуляем ( a[n]= 0 ).

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

Пример. Удалить k-ю строку матрицы a[n, m].

.....

For i:= k+1 To n Do {Все строки, ниже k, будем сдвигать на 1.}

For j:=1 To m Do a[i-1, j]:= a[i, j]; {Сдвиг строки - это цикл от 1 до m.}

{Строку i перемещаем на место строки i-1.}

{И так со всеми, кто больше k.}

For j:= 1 To m Do a[n, j]:= 0; {Теперь последнюю (n-ую) строку забьем}

{нулями. Она уже «переехала» на место n-1.}

.....

Аналогично решается задача с удалением столбца.

В условиях задач сказано, что матрица А - задана. Следовательно, заданы ее размеры и содержимое. Однако вставка элементов подразумевает расширение границ массива, а это довольно сложная задача. Поэтому, давай договоримся так, что под словом «задана» будем иметь в виду задание содержимого матрицы. Ну, а отводить памяти для нее можно столько, сколько считается нужным. Небольшая подсказка. В первой задаче нужно объявлять массивы размерами a[n+1, m], b[m], а во второй- a[n, m+1], b[n]. Не забудьте, что вводить данные надо не во все ячейки матрицы, а только в область [n, m]. Остальные ячейки (лишняя строка или лишний столбец) - вакантные. Это резерв, дабы было куда раздвигать.

Вопрос 3.7.1. Заданы матрица a[n, m] и вектор b[m]. Вставить вектор b в матрицу a, разместив его после строки с номером к (к < n).

Вопрос 3.7.2. Заданы матрица a[n, m] и вектор b[n]. Вставить вектор b в матрицу a, разместив его после столбца номер k (k < m).


§3.8 Транспонирование

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

Операцию транспонирования производят с переносом элементов из исходной (заданной) матрицы в результирующую (выходную; причем, того же размера) матрицу. Транспонируют матрицу «в пути».

Пусть имена матриц - a и b. Тогда процесс транспонирования можно объяснить словами так: i-й столбец матрицы b совпадает с i-й строкой матрицы a. А так выглядит формула транспонирования: b[j, i]:= a[i, j]. Т.е. то, что было строкой в [a], станет столбцом в [b].



Program P_37;

Uses Crt;

Var i, j, n, Lb, Rb: Integer;

a, b : Array[1..10, 1..10] Of Integer; {Максимальный размер матриц.}

Begin

Randomize;

ClrScr;

Write('Size? '); ReadLn(n); {Размер и интервал значений}

Write('Left & right borders? '); ReadLn(Lb, Rb); {элементов матрицы.}

WriteLn('Start table');

For i:=1 To n Do

Begin

For j:=1 To n Do

Begin

a[i, j]:= Random(Rb - Lb + 1) + Lb; {Генерируем элемент матрицы [а] и}

b[j, i]:= a[i, j]; {сразу транспонируем его в матрицу [b].}

Write(a[i, j]:5) {Печатаем исходную матрицу...}

End;

WriteLn

End;

WriteLn('Finish table');

For i:=1 To n Do

Begin

For j:=1 To n Do Write(b[i, j]:5); {и финишную матрицу.}

WriteLn

End;

ReadKey

End.


Вопрос 3.8.1. Задана матрица a[m, m]. После выполнения программы та же матрица должна содержать транспонированную к первоначальной. Дополнительную матрицу не использовать.


§3.9 Сдвиги и вращения

Часто на практике бывает необходимо сдвинуть элементы массива на один или несколько вперед или назад (и при этом ни одного элемента не «потерять»).

Если необходимо выполнить сдвиг, то, как правило, выталкиваемый элемент размещается во вспомогательной переменной, а освободившаяся «вакансия» - стирается (в простейшем случае обнуляется).

Рассмотрим пример сдвига массива вправо на один элемент. При этой операции последний элемент будет «вытолкнут» из массива, а первый - освободится.

..... {Объявления массивов и ввод данных.}

c:= a[n]; {Выталкиваем последний элемент в ячейку с.}

For i:= n-1 DownTo 1 Do a[i+1]:=a[i]; {Всех, которые левее последнего,}

{сдвигаем вправо на единицу,}

a[1]:= 0; {стираем освободившийся первый элемент,}

..... {выводим результаты и завершаем программу.}

Аналогично производится сдвиг влево, только там выталкиваться будет не последний, а первый элемент, а цикл сдвига нужно будет организовать от второго до последнего элемента. В общем случае направление «движения» по массиву во время операции сдвига должно быть навстречу сдвигу.

Вращение. Эта операция отличается от сдвига тем, что освободившееся место не обнуляется. На нем размещается «вытолкнутый» элемент. При вращении вправо последний элемент станет первым. Его место займет предпоследний и т.д. Программа, выполняющая операцию вращения вправо на один элемент, будет отличаться от предыдущей (см. пример со сдвигом) только последним оператором. Он будет выглядеть так:

a[1]:= c;

Рассмотрим более сложный пример. Выполнить вращение строк массива a[n,m] вверх на один шаг. Сами строки оставить без изменения.


Program P_38;

Uses Crt;

Var n, m, i, j: Integer;

a: Array[1..10, 1..10] Of Integer; {Максимально возможный размер}

b: Array[1..10] Of Integer; {Исходного массива и вектора}

{для хранения строки}

Begin

ClrScr;

Write(’Введите размер матрицы ’); ReadLn(n, m);

For i:=1 To n Do

Begin

WriteLn(’Вводим ’, i, ’-ю строку’);

For j:=1 To n Do

Begin

Write(’a[’, i, ’,’ , j,’]=’);

ReadLn(a[i, j]) {Здесь введем исходные данные.}

End

End;

For j:=1 To m Do b[j]:= a[1,j]; {Первая строка будет вытолкнута.}

{Переместим ее временно в вектор b. Она содержит m элементов.}

For i:=2 To n Do {Все остальные строки поднимем вверх}

For j:=1 To m Do a[i-1, j]:= a[i, j]; {Поднимаем m элементов}

{очередной строки. Текущую ставим на место предыдущей.}

{И так со всеми строками (кроме первой).}

For j:=1 To m Do a[n, j]:= b[j]; {Освободилась последняя строка. На ее}

{месте (а ее номер равен n) располагаем временно}

{сохраненную в векторе b первую строку.}

For i:=1 To n Do Begin {Вывод результатов и завершение программы.}

For j:=1 To m Do Write(a[i j]:5);

WriteLn

End;

ReadKey

End.


Вопрос 3.9.1. Выполнить вращение вектора a(n) на два шага влево.

Вопрос 3.9.2. Выполнить вращение столбцов матрицы на один шаг вправо. Сами столбцы оставить без изменения.


§3.10 Циклы с переменными границами

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

На практике в качестве граничных значений переменной цикла широко применяют данные типа выражения. Значение выражения перед использованием его в качестве параметра цикла вычисляется оператором For.

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

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

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

Рис.9




.






































. . .

s:= 0;

For i:=1 To n Do

For j:=n + 1 - i To n Do s:= s + a[i, j];

WriteLn(’s=’, s);

ReadKey

End.

Первый цикл (по i) задает строки (от 1 до n. В первой строке (i=1) нужно складывать элементы с n-го по n-й (всего один), во второй - с (n-1)-го по n-й, в третьей - с (n-2)-го по n-й и т.д. Нетрудно вывести зависимость для i-й строки: с (n + 1 - i) по n-й (подставь вместо i 1,2,3 ... и убедитесь, что это действительно так). Именно эти границы имеет второй цикл.

В данном примере необходимо найти сумму элементов, расположенных на второстепенной диагонали. Диагональ представляет собой одномерный массив, для обработки которого достаточно одного цикла. Итак, в первой строке берем n-й элемент, во второй - (n-1)-й, в третьей - (n-2)-ой, в i-й (n-i+1)-й.

For i:=1 To n Do s:= s + a[i, n + 1 – i];

WriteLn(’s=’, s);

ReadKey

End.

. . . . .


Пример: найдём сумму элементов квадратной матрицы, стоящих на и над главной диагональю. Здесь область обработки двумерная, поэтому задача будет решаться в 2 цикла. Пусть первый цикл задает строки от 1 до n. В первой строке будем рассматривать элементы с 1-го по n-й, во второй строке - со 2-го по n-й, в третьей строке - с 3-го по n-й, в i-й - с i- го по n-й. Это и будут границы второго (вложенного) цикла.

. . . . .

s=0;

For i:=1 To n Do

For j:=i To n Do s:= s +a[i, j];

WriteLn(’s=’, s);

ReadKey

End.

. . . . .


Вопрос 3.10.1. Найти отношение сумм элементов, расположенных на диагоналях матрицы.

Вопрос 3.10.2. Найти сумму квадратов элементов матрицы, для которых сумма индексов есть четное число.

Вопрос 3.10.3. Для каких i в произвольной матрице сумма строки меньше суммы столбца?

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


§3.11 Сочетания и размещения

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

Чтобы постановка задачи стала более «прозрачной», рассмотрим простой пример.

Встретились n человек. Сколько было всего рукопожатий?

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

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

Давайте рассуждать так: первый человек поздоровался со вторым, третьим, четвертым, n-ым. Второй - с третьим, четвертым... n-ым, (n-1)-ый - только с n-ым (его рукопожатия с предыдущими номерами были учтены ранее). Попробуем составить программу. Очевидно, здесь будет 2 цикла. Первый цикл будет рассматривать всех n участников встречи, второй - подсчитывать количество рукопожатий i-го участника. Первый цикл будет иметь границы от 1 до n-1 (последний участник уже нис кем не здоровается), второй - от i+1 до n (i-ый участник здоровается со всеми «впереди» себя, т.е. номера которых больше его самого).


Program P_39;

Uses Crt;

Var I, j, k, n: Integer;

Begin

ClrScr;

Write(’Введите количество участников встречи ’);

ReadLn(n);

k:=0; {Количество рукопожатий.}

For i:=1 To n - 1 Do {Цикл по участникам встречи.}

For j:=i + 1 To n Do k:= k + 1; {Рукопожатия i-го участника.}

WriteLn('количество рукопожатий =', k:7);

ReadKey

End.


Здесь мы традиционным способом решили задачу выборки всех возможных сочетаний из n по 2. Мы рассмотрели все возможные пары участников. Для каждой пары мы выполнили операцию k:= k + 1, подсчитывая таким образом количество рукопожатий.

Сочетание - это термин комбинаторики. Под сочетанием понимают подгруппу k элементов, входящих в состав некоторой группы из n элементов, без учета их взаимного расположения внутри подгруппы (пара элементов, например 2 и 4 или 4 и 2 рассматривается как одно и то же).

В приведенном только что примере мы не производили над сочетаниями никаких действий, а лишь просто подсчитывали их количество. Тем не менее, элементы сочетаний были доступны нам в программе через их индексы. Объявив в программе массив из n элементов, мы могли бы иметь доступ ко всем неповторяющимся парам элементов этого массива и производить с этими парами какие-либо действия.

Произвести выборку всех возможных сочетаний из n элементов по k можно, организовав в программе k циклов. В предыдущем примере мы рассматривали пары, поэтому k=2, и программа содержала 2 цикла.

Первый цикл (пусть это будет цикл по i) должен иметь границы от 1 до n – k + 1 (в предыдущем примере - от 1 до n – 2 + 1), второй - (пусть будет по j) от i + 1 до n – k + 2, третий - от j + 1 до n – k + 3 и т.д. Последний - от <предпоследний> + 1 до n.

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

Выборка размещений из n элементов по k также производится с помощью k циклов. При этом все циклы имеют границы от 1 до n. Чтобы предотвратить попадание в размещение одного и того же данного более одного раза, необходимо «удалить» из обработки такие комбинации.

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

И небольшая улыбка: если участников встречи N, то количество рукопожатий будет равно S = N * (N - 1). Почему?!...

Задача. Сколько троек элементов одномерного массива можно рассматривать как длины сторон треугольника? (Сколько можно построить треугольников, если положить, что элементы массива есть длины сторон?)


Program P_40;

Uses Crt;

Var i, j, k, n, s, Lb, Rb: Integer;

a: Array[1..100] Of Integer;

Begin

ClrScr;

Randomize;

Write('Размер массива? '); ReadLn(n);

Write('Границы чисел [от 1 и далее]? '); ReadLn(Lb, Rb);

WriteLn('Элементы массива');

For i:=1 To n Do Begin

a[i]:= Random(Rb – Lb + 1) + Lb;

Write(a[i]:5)

End;

Writeln;

For i:=1 To n-2 Do {Счетчик треугольников}

For j:= i + 1 To n - 1 Do {рассматриваем все возможные}

For k:= j + 1 To n Do {тройки элементов. Это делается тремя циклами.}

If (a[i]

Begin

WriteLn('Элементы на позициях ':30, i:5, j:5, k:5);

WriteLn('Со значениями ':30, a[i]:5, a[j]:5, a[k]:5;

s:= s + 1;

WriteLn('Нажмите любую клавишу для продолжения');

ReadKey {Эта команда нужна для того, чтобы затормозить}

End; {выполнение циклов. Насладиться ведь надо?}

WriteLn('Всего таких треугольников может быть ':30, s:7);

ReadKey

End.


Задача. Пусть массивы x(n) и y(n) содержат координаты n точек плоскости. Есть ли 3 такие точки, которые лежат на одной прямой? Напечатать координаты этих точек и их количество.


Program P_41;

Uses Crt;

Var i, j, r, n, s: Integer;

k, b,Lb, Rb: Real;

x, y: Array[1..100] Of Real;

Begin

ClrScr;

Randomize;

Write('Количество точек? '); ReadLn(n);

Write('Границы координат? '); ReadLn(Lb, Rb);

WriteLn('Получены точки с координатами');

For i:=1 To n Do Begin

x[i]:= Random(Rb – Lb + 1) + Lb;

y[i]:= Random(Rb – Lb + 1) + Lb;

WriteLn(i:5, ’-я точка с координатами’,x[i]:7:2, y[i]:7:2)

End;

s:= 0; {Количество точек.}

For i:= 1 To n – 2 Do {Рассматриваем все пары точек}

For j:= i + 1 To n – 1 Do {двумя For-ами.}

If Abs(x[i] – x[j]) > 0.0001 Then

Begin {Исключаем вертикальные прямые.}

k:= (y[j] – y[i])/(x[j] – x[i]); {Выводим уравнение прямой для}

b:= y[i] + x[i] * (y[i] - y[j])/(x[j] - x[i]); {в виде k*x + b.}

For r:= j + 1 To n Do {Ищем третью точку на прямой,}

If Abs(y[r] - (k*x[r] + b)) < 0.0001 Then

Begin

WriteLn('На одной прямой лежат точки:');

WriteLn(i:5, x[i]:7:2, y[i]:7:2);

WriteLn(j:5, x[j]:7:2, y[j]:7:2);

WriteLn(r:5, x[r]:7:2, y[r]:7:2);

s: = s + 1 {подставляя в полученное уравнение}

End {координаты третьей точки.}

End; {Должно получиться тождество.}

WriteLn('Всего групп точек, лежащих на одной прямой –', s:5);

ReadKey

End.


Вопрос 3.11.1. Что такое сочетание? Размещение? Чем они отличаются?

Вопрос 3.11.2. Какие элементы встречаются в одномерном массиве только один раз? Сколько таких элементов?

Вопрос 3.11.3. Какое значение в одномерном массиве повторяется чаще других? Сколько раз оно повторяется?

Вопрос 3.11.4. Есть ли в одномерном массиве такой элемент, который по модулю больше суммы всех оставшихся? Сколько в массиве таких элементов?

Вопрос 3.11.5. Есть ли в матрице элементы, для которых найдется еще 2 элемента, сумма модулей которых не превышает данный элемент?

Вопрос 3.11.6. Пусть массивы x(n) и y(n) содержат координаты n точек плоскости. Можно ли построить окружность, проходящую через 2 точки с центром в третьей точке. Распечатать такие тройки точек и их количество.

Вопрос 3.11.7. Есть ли в векторе 2 пары элементов с одинаковой суммой?


§3.12 Вспомогательные массивы

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

Пример. Какая строка массива а[n, n] имеет наибольшую сумму?

Можно было бы организовать программу так: объявить вспомогательный вектор длиной n, найти суммы всех строк и разместить их в этом массиве. Затем найти максимальное значение среди элементов этого массива и его индекс (номер).

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

. . . . .

Var a: Array[1..100,1.100] Of Integer; {Объявляем массивы: основн. и вспом.}

b: Array[1..100] Of Integer; {максимально возможного размера.}

. . . . . {Опущен фрагмент загрузки данных.}

For i:=1 To n Do {Просматриваем все строки.}

Begin

b[i]:= 0; {Для каждой строки находим сумму}

For j:= 1 To n Do b[i]:= b[i] + a[i, j] {и размещаем ее в массиве b.}

End;

Max:= b[1]; {Затем находим максимум в массиве b}

k:= 1; {и номер максимального элемента k.}

For i:= 2 To n Do

If b[i] > = Max Then Begin {Решаем минмаксную задачу.}

Max:= b[I]; k:= i

End;

WriteLn(’Наибольшую сумму имеет строка номер', k:5); ReadKey

End.

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

Здесь есть одна проблема. С чем сравнивать первую найденную сумму? Ответ очень прост. Сравним ее с каким-либо заранее заданным числом, заведомо меньшим, чем сумма любой строки. Для этой цели подойдет минус бесконечность - самое маленькое из чисел, которые может обрабатывать данный компьютер. Ну, например -1028.

Var a: Array[1..100,1.100] Of Integer; {Вспомогательный массив не нужен}

. . . . {Ввод данных}

Max:= -1e+28; {Пока это максимальная сумма.}

For i:= 1 To n Do {Цикл по строкам массива а.}

Begin

s:=0; {Сумма i-й строки пока равна нулю, находим сумму i-й}

For j:= 1 To n Do s:= s + a[i,j]; {строки, размещаем ее в переменной s.}

If s > = Max Then {Найдена сумма очередной строки,}

Begin

Max:= s; {сравниваем ее с максимумом.}

k:= i

End

End;

WriteLn(’Наибольшую сумму имеет строка номер ’, k:5);

ReadKey

End.

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

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



§3.13 Страничная организация данных

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

Рассмотрим простой пример. Пусть программа должна проанализировать успеваемость учащихся школы. Для этого ей, вероятно, потребуются данные об оценках. Пусть в школе всего 20 классов. В каждом классе максимум по 40 человек. Всего изучаются 15 предметов. Если ставить задачу максимально быстрого доступа к любому данному, то можно поступить так. Организовать трехмерный массив. Одно измерение будет соответствовать классу, второе - предмету и третье - номеру учащегося. Тогда для выборки любого данного нужно будет указать имя массива и 3 индекса, соответсвующие искомой величине. Пусть имя такого массива Dat, тогда Dat[9, 3, 8] будет содержать оценку ученика №8 9-го класса по предмету номер 3 (списки наименований предметов, фамилии учащихся... хранятся, как правило, в отдельных массивах. Такие данные называются нормативно-справочной информацией).

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

В нашем примере: пусть одна страница содержит данные о классе. Тогда всего будет 20 страниц. Каждая страница будет представлять собой матрицу 40 на 15. Каждая строка такой матрицы будет соответствовать одному учащемуся, а столбец - предмету. Все страницы размещаем в одном массиве Sdat. Теперь он будет уже двухмерный. Количество строк массива будет равно 40 * 20 = 800. Строки с первой по 40 будут соответствовать классу номер 1, с 41 по 80 - классу номер 2 и т.д.

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

Теперь наша задача - получить доступ к любому данному, зная номера класса, ученика и предмета. Пусть имена этих переменных соответственно: Klass, Man и Work. Тогда данные, относящиеся к классу номер Klass, будут расположены в массиве в строках с (Klass - 1) * 40 + 1 по (Klass - 1) * 40 + 40 (проверьте, подставив вместо Klass 1,2,3 и т.д.). Если задан номер учащегося (обычно используется номер по журналу с 1 по 40), то данные по этому ученику можно найти в строке номер (Klass - 1) * 40 + 1 + Man - 1 (начальный адрес страницы + смещение - 1; будь нумерация с нуля, не нужно было бы вычитать единицу) или (Klass - 1) * 40 + Man. Это будет номер строки (первый индекс массива). Номер столбца задает предмет (второй индекс).

Итак, для доступа к данному ученика номер Man класса номер Klass по предмету номер Work необходимо записать: Sdat[(Klass - 1) * 40 + Man, Work].

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

  1. Среднюю успеваемость по каждому из 15 предметов среди всех учащихся школы. Данные разместить в массиве Swork[15].

2. Среднюю успеваемость каждого класса с учетом данных по всем предметам. Данные разместить в Sklass[20].

3. Среднюю успеваемость всех учащихся школы по всем предметам (Sh).


Var Sdat: Array[1..800,1..15] Of Integer;

Sklass: Array[1..20] Of Integer;

Swork: Array[1..15] Of Integer; {Объявляем рабочие массивы.}

. . . . {Загружаем данные (из файла).}

Sh:= 0; {Средняя успеваемость школы.}

For Klass:= 1 To 20 Do {Анализируем все классы.}

Begin

Sklass[Klass]:= 0; {Очередное среднее по классу.}

For Work:= 1 To 15 Do {Все 15 предметов.}

For Man:=1 To 40 Do {Все 40 учащихся класса.}

Begin

f:= Sdat[(Klass - 1) * 40 + Man, Work]; {Извлекаем оценку(так короче)}

Sklass[Klass]:= Sklass[Klass] + f; {Данные по классу.}

Swork[Work]:= Swork[Work] + f; {И по предмету.}

Sh:= Sh + f {А это по школе.}

End;

Sklass[Klass]:= Sklass[Klass] Div 40 Div 15 {Класс обработан. Среднее.}

End;

For i:= 1 To 15 Do

Swork[i]:= Swork[i] Div 40 Div 20; {Среднее по предметам.}

Sh:= Sh / 40 / 20 / 15; {Среднее по школе.}

WriteLn ... {Все данные получены.}


Разумеется, в этом фрагменте много допущений. Во-первых, предполагается, что каждый класс состоит ровно из 40 учащихся. Во-вторых, каждый из учащихся (и стар и млад) изучает все 15 предметов... Такого, естественно, не бывает. Но... все эти каверзы обойти очень просто, имея дополнительные данные (нормативно-справочную информацию). А чего стоит функция Div? Или все данные хранятся только в целочисленном формате?

В общем случае, при страничной организации данных: если на одной странице n данных (внутри страницы данные пронумерованы от 1 до n), то данное номер i, расположенное на k-й странице, будет иметь абсолютный адрес Adr = (k - 1) * n + i. Здесь мы имеем 2 адреса данного: внутри страницы - i (такой адрес называется виртуальным) и внутри массива (абсолютный). Только что мы записали соответствие между этими адресами.

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

k = (Adr - 1) Div n + 1 - номер страницы, на которой расположено данное с абсолютным адресом Adr, если на каждой страницы по n данных.

i = ((Adr - 1) Mod n) + 1 - виртуальный адрес данного внутри страницы.

i = Adr - (k - 1) * n - его можно найти и так (зная номер страницы).

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

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

Вопрос 3.13.1. Что такое страница?

Вопрос 3.13.2. Объясните понятия абсолютного и виртуального адреса. В чем их различие?

Вопрос 3.13.3. Запишите формулы перехода от абсолютного адреса к виртуальным и обратно.




§3.14 Преобразование структуры данных

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

Каждую строку двумерного массива можно представить как страницу, содержащую j элементов (если матрица содержит j столбцов). Тогда такой массив можно обработать одним циклом. При этом будет осуществляться доступ ко всем элементам по очереди.

Задача. Пусть матрица имеет размеры N х M. Нужно найти максимальный элемент матрицы.


Program P_42;

Uses Crt;

Var n, m, Lb, Rb, Max, Adr, i, j: Integer;

a: Array[1..100, 1..100] Of Integer;

Begin

ClrScr;

Randomize;

Write('Размеры массива? '); ReadLn(n, m);

Write('Границы чисел? '); ReadLn(Lb, Rb);

WriteLn('Элементы массива');

For i:= 1 To n Do

Begin

For j:=1 To m Do

Begin

a[i, j]:= Random(Rb – Lb + 1) + Lb;

Write(a[i, j]:5)

End;

Writeln

End;

Max:= a[1,1];

For Adr:= 1 To n * m Do {Один цикл обработки.}

Begin

i:= (Adr - 1) div m + 1; {Находим виртуальные адреса.}

j:= Adr - (i - 1) * m; {Номер строки i и столбца j.}

If a[i,j] > Max Then Max:= a[i, j] {Дальше как обычно.}

End;

WriteLn(’Max=’, Max:5);

ReadKey

End.


Полезно бывает оформить фрагменты пересчета адреса в виде функций. Например i(Adr) и j(Adr). Тогда строки преобразования адреса не «путаются под ногами». Обращение к фрагменту осуществляется через эти функции: a[i(Adr), j(Adr)]. В теле функций должны быть видимыми (доступными) границы матрицы n и m.

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

Обратное преобразование (из одномерного массива в двумерный) производится аналогично. Рассмотрим алгоритм на примере задачи: разместить элементы двумерного массива а(n, m) в одномерном b(n * m).


Program P_43;

Uses Crt;

Var n, m, Lb, Rb, Adr, i, j: Integer;

a: Array[1..10, 1..10] Of Integer;

b: Array[1..100] Of Integer;

Begin

ClrScr;

Randomize;

Write('Размеры массива? '); ReadLn(n, m);

Write('Границы чисел? '); ReadLn(Lb, Rb);

WriteLn('Элементы массива');

For i:= 1 To n Do

Begin

For j:=1 To m Do

Begin

a[i, j]:= Random(Rb – Lb + 1) + Lb;

Write(a[i, j]:5)

End;

WriteLn

End;

WriteLn;

For i:= 1 To n Do {Одномерный массив обрабатываем двумя циклами.}

For j:= 1 to m Do

Begin

Adr:= (i - 1) * m + j; {Абсолютный адрес (в одномерном массиве).}

b[Adr]:= a[i, j] {Перемещаем данное.}

End;

For i:=1 To n * m Do Write(b[i]:5);

WriteLn;

ReadKey

End.


Вопрос 3.14.1 Что такое логическое преобразование структуры данных? В каких случаях его применяют?

Вопрос 3.14.2. Составьте программу размещения элементов вектора а[n * m] в матрице b[n, m] по строкам.

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




































Глава IV. Численные методы.


§4.1 Степень и факториал


Для начала два примера. Первый пример связан с одной из особенностей Pascal. Причины неизвестны, но господин Вирт, создавая язык, очевидно решил, что люди, которые будут работать в этой системе, знают математику на «ять». Комментарии, как говорится, излишни, но наверняка уже всплывал вопрос: «Люди, а степень?!». Не научная степень, а просто «степень». То есть в смысле «а в степени b». Ведь отсутствует! Ну нет её в математике Pascal! Ничего не поделаешь, если математическое высказывание

b

a b = e ln(a ) = e b * ln(a)


доступно только учащимся последнего года обучения средней школы. В лучшем случае – предпоследнего года. А информатику начинают изучать сейчас, пожалуй, класса с 7-го, а то и с 5-го.

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

Но мы отвлеклись. Если мы хотим работать со степенью, то надо просто запомнить формулу (см. выше). В Pascal «а в степени b» будет выглядеть

a b := Exp(b * Ln(a))

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


Program P_44;

Uses Crt;

Var a, b, c: Real;

Begin

ClrScr;

Write('Основание '); ReadLn(a);

Write('Показатель степени '); ReadLn(b);

c:= Exp(b * Ln(a));

WriteLn(a:5:2, ' в степени ', b:5:2, '= ', c:7:2);

ReadKey

End.


И вторая задача. Хорошо нам знакомый n!. Давайте вспомним, что самый популярный целочисленный формат Pascal обрабатывает числа до 32767, в крайнем случае – формат LongInt работает с максимальным числом 2147483647. А, например, 30! – число длиной в 33 знака. Задача не решаема? Решаема. Как у братьев Стругацких: «Я знаю, что эта задача не решается, и поэтому я хочу знать, как её решать!». Давайте внимательно рассмотрим следующий пример.


Program P_45;

Uses Crt;

Var m, s: Real;

n, p, i: LongInt;

Begin

ClrScr;

While n < 1 Do Begin {Это то число, факториал которого считаем}

Write('Число? '); {Fool – protect. Вспомнили?}

ReadLn(n)

End;

s:= 0 {Считаем не произведение, а его логарифм! Логарифм произведения -}

For i:= 1 To n Do s:= s + Ln(i); {это сумма логарифмов. Превращаем}

s:= s / Ln(10); {натуральный логарифм в десятичный. Так привычней.}

p:= Trunc(s); {Выделяем порядок числа. Это не что иное, как целая часть}

m:= s - p; {результата. И то, что осталось...}

m:= Exp(m * Ln(10)); {Осталось только получить мантиссу!}

WriteLn(n:5, '!= ', m:15:14, ' * 10 в степени ', p);

ReadKey

End.

Результатом работы программы будет два числа, которые есть не что иное, как мантисса результата и его порядок, т.е. самого результат нет, а есть его составные части. Но... Разве число не может быть представлено в показательной форме? Ну, естественно, точность вычислений пострадала, но задача-то решена! По крайней мере, AMD Athlon 2 ГГц после 3-секундного размышления выдал 1000000 (миллион!) факториал. «8.26008579270 умножить на 10 в степени 5565708». То есть число длиной 5565709 знаков. Ну, а теперь...


§4.2 Метод половинного деления.

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

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

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

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

Один шаг вычислений называют итерацией. От этого слова численные методы еще называют итерационными.

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

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

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

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

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

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

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

К решению этого вопроса есть два подхода.

Первый. Заранее задается количество итераций (шагов вычисления). Обычно оно выбирается достаточно большим. Точность в процессе вычислений не оценивается (что получится, то получится). По окончании вычислений точность результата зависит от того, насколько «близки» к реальным были начальные условия.

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

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

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

Рассмотрим в качестве примера простую задачу. Решить уравнение y = x3 для y=27. Очевидно, результат равен трем. Попробуем составить программу решения этой задачи для иллюстрации численного метода.

Преобразуем уравнение к виду: x3 – 27 = 0. Для решения воспользуемся алгоритмом половинного деления. Суть этого алгоритма в следующем.

Решением уравнения вида f(x) = 0 будет являться та точка на оси х, в которой функция y = f(x) пересечет ось х. Именно это значение (для которого x3-27 будет равно нулю) нам и нужно найти.

Найдем первое приближение (для метода половинного деления задается интервал поиска результата, т.е. нижняя и верхняя границы). Пусть решение лежит где-то между x1 = -100 и x2 = 100. Проверим, действительно ли это так. Для x1=-100 функция примет значение f(-100) = (-100)3 – 27 = < отрицательное число>. Для x2=100 функция примет значение f(x2) = 1003 – 27 = <положительное число>. На границах интервала функция принимает значения разного знака, значит, где-то внутри интервала (-100,100) есть хотя бы одна точка, в которой функция равна нулю (точка пересечения функции с осью x). Эта точка (обозначим ее х0) и будет являться решением нашего уравнения. Причем, заметим, что в нашем случае функция монотонна, следовательно, она имеет одну точку пересечения с осью иксов.

Разделим отрезок (-100,100) пополам. Серединой этого отрезка будет являться точка x0 = (x1 + x2) / 2 или x0 = (-100 + 100) / 2 = 0. Оценим значение функции в этой точке. y(x0) = 03 – 27 = -27 (отрицательное число). Вспомним, что в точке 100 функция положительна. Следовательно - новый интервал для поиска решения лежит между точками 0 и 100. Т.е. мы как бы передвинули нижнюю границу интервала от х1 = -100 к новому значению х1 = 0. Это запишется так: х1:= х0.

Далее вновь делим интервал пополам. Середина интервала - точка x0 = (x1+x2) / 2 или x0 = (0+100) / 2 = 50. Вновь оцениваем значение функции в точке х0. Теперь y(x0) = 503 - 27 есть положительное число. Следовательно, решение находится слева от х0. Теперь перемещаем правую границу интервала (х2 = х0) в точку х0. При этом получаем новый интервал для поиска. Теперь он стал равным (0,50). Отныне 50 - верхняя граница интервала.

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

Остановим вычисления, когда значение функции в очередной точке будет отличаться от нуля не более чем на 10 в минус пятой степени. Т.е. когда модуль значения функции будет меньше или равен 10-5. Это означает, что мы ищем решение с точностью до пятого знака после десятичной точки.


Program P_46;

Uses Crt;

Var x0, x1, x2, e, y: Real;

Begin

ClrScr;

x1:= -100; x2:= 100; e:= 1e-5; {Границы интервала и точность.}

Repeat {Повторять до тех пор, пока не будет достигнута точность.}

x0:= (x1 + x2) / 2; {Вычисляем аргумент}

y:= x0 * x0 * x0 - 27; {и саму функцию. В зависимости от её значения}

If y > 0 Then x2:= x0 Else x1:= x0; {меняем границы.}

Until Abs(y) < e;

WriteLn(x0); {Выводим результат}

ReadKey

End.


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


n

x1

x2

x0

Y

0

-100

100

0

-27

1

0

100

50

124973

2

0

50

25

15598

3

0

25

12.5

1926.125

4

0

12.5

6.25

217.1406

. . .

. . .

. . .

. . .

. . .

10

2.929688

3.125

3.027344

.7450308

11

2.929688

3.027344

2.978516

-.5759338

12

2.978516

3.027344

3.00293

-.2497145

13

2.978516

3.00293

2.990723

-.2497145

14

2.990723

3.00293

2.996826

-8.560273e-02

. . .

. . .

. . .

. . .

. . .

24

2.999997

3.000009

3.000003

8.368501E-05

25

2.999997

3.000003

3

1.734723E-18


Вопрос 4.2.1. Что лежит в основе численных методов?

Вопрос 4.2.2. Как называют один шаг вычислений численными методами?

Вопрос 4.2.3. Что означает сходящийся и расходящийся метод? Что такое область сходимости?

Вопрос 4.2.4. Назовите критерии оценки точности результата при применении численных методов.

Вопрос 4.2.5. В чем состоит метод половинного деления?

Вопрос 4.2.6. Составить программу решения уравнения x2.5=0.41 (x>0).

Вопрос 4.2.7. В какой точке график функции f(x)=6x5 - 5x3 + 2x - 10 пересечет ось х? Составьте программу вычисления.


§4.3 Решение уравнений вида f(x) = 0 и f(x) = x

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

Метод Ньютона. Иначе его называют методом касательных. Он применим для решения уравнений вида f(x)=0.

Чтобы найти точку пересечения функции y=f(x) с осью х поступаем следующим образом.

1. Задаем начальное приближение (некоторое начальное значение корня х0)

2. В точке х = х0 находим касательную к функции y=f(x).

3. Находим точку пересечения касательной с осью х. Это будет новое (уточненное) значение корня х0.

4. Переходим к шагу 2, если требуемая точность не достигнута.

Таким образом, итерационный процесс схождения к корню реализуется формулой xn+1 = xn - f(xn) / f1(xn), где f1(xn) - производная функции в точке x(n). Метод сходится (результат приближается к корню, а не удаляется от него), если выполняется условие f(x0)*f2(x0)>0, где f2(x0) - вторая производная функции в точке начального приближения х0.

При выборе х0 должно соблюдаться условие: f(x0) и f2(x0) имеют одинаковый знак.

Метод Ньютона обеспечивает быструю (квадратичную) сходимость.

Ниже приведена программа решения уравнения x - sin(x) - 0.25 = 0.


Program P_47;

Uses Crt;

Var x, e, f: Real;

Begin

ClrScr;

Write('Начальное приближение? '); ReadLn(x);

Write('Точность? '); ReadLn(e);

Repeat

f:= (x - Sin(x) - 0.25) / (1 - Cos(x));

x:= x - f

Until Abs(f) < e;

WriteLn('Корень уравнения равен ', x:7:2);

ReadKey

End.


Решением уравнения f(x) = x будет являться точка пересечения функций y = f(x) и y = x. Для нахождения корня такого уравнения применяют итерационную формулу x(n+1) = f(x(n)) (метод простых итераций). Процесс сходится, если на интервале поиска корня уравнения производная функции f(x) положительна.

Program P_48;

Uses Crt;

Var x, e, f: Real;

Begin

ClrScr;

Write('Начальное приближение? '); ReadLn(x);

Write('Точность? '); ReadLn(e);

Repeat

x:= f;

f:= Sin(x) - 0.25

Until Abs(f - x) < e;

WriteLn('Корень уравнения равен ', x:7:2);

ReadKey

End.


Задача – вычислить число π. Вообще-то, решение данной задачи – тема для отдельной большой книги. Достаточно вспомнить, что, начиная от Архимеда, это число было заботой и Лейбница, и Валлиса, и Эйлера… Воспользуемся численным методом, предложенным индийским учёным Мадхава, который определил число π как сумму ряда 4(1 - 1/3 + 1/5 - 1/7…). Явно, что вычисления нужно организовывать до достижения необходимой точности.


Program P_49;

Uses Crt;

Var sum, ep: Extended; {Сумма ряда и точность}

zn, k: Longint; {Знаменатель дроби и количество шагов}

Begin ClrScr;

Write('Точность='); ReadLn(ep);

sum:= 0;

zn:= 1;

k:= 1;

While 1 / a – 1 / (a+2) > ep Do {Пока не достигнута точность, }

Begin {выясним: складывать или вычитать?}

If k mod 2 = 0 Then sum:= sum – 1 / a Else sum:= s + 1/a;

k:= k + 1; {Переходя к следующему шагу,}

zn:= zn + 2 {увеличиваем знаменатель}

End;

s:= s * 4; {Умножим сумму ряда на 4 и посмотрим}

WriteLn(sum,' - ', k); {количество шагов для данной точности}

ReadKey

End.

Для того, чтобы достигнуть точности в 10-10 понадобилось 70711 шагов, для точности 10-15 – 22360680 шагов, для точности 10-20 - …

Вопрос 4.3.1. Какие типы уравнений решаются с помощью метода Ньютона?

Вопрос 4.3.2. Составьте блок-схему алгоритма решения уравнений методом Ньютона.

Вопрос 4.3.3. Запишите итерационную формулу метода простых итераций.

Вопрос 4.3.4 Даны 2 числа. 1 и 1. Каждое следующее число в ряду есть сумма предыдущих: 1, 1, 2, 3, 5, 8… Для n элементов ряда постройте таблицу отношения суммы ряда от первого до n-го элемента к сумме ряда от первого до n-1-го элемента.



§4.4 Метод Монте-Карло

В основе метода Монте-Карло лежит принцип исследования реакции системы при воздействии на нее возмущения, носящего случайный характер.

Метод Монте-Карло применим в таких ситуациях, когда все остальные методы оказываются бессильными. Как правило, метод Монте-Карло применяется для анализа систем, не поддающихся аналитическому описанию. В простейшем случае, для исследования третьего закона Ньютона (действие равно противодействию) можно применить систему «человек - стена». Всё очень просто: с разбега прыгнуть на стену и посмотреть, что получится, и какова будет реакция системы на внесение в неё возмущения. Хотите проверить?

В качестве примера рассмотрим задачу, вычисляющую число π.

Рассмотрим квадрат стороной 2 с центром в начале координат. В этот квадрат вписан круг. Очевидно, площадь квадрата равна четырем. Площадь

кhello_html_56374817.gif

1

y

Рис.10

руга с единичным радиусом в идеальном случае равна s = π r2 = π. Найдем площадь круга с помощью метода Монте-Карло.

Алгоритм вычисления площади круга

бhello_html_m2a7690f7.gifhello_html_m5bca0b98.gifhello_html_3c4b6c2c.gifhello_html_m80b1b71.gifhello_html_mecca5c0.gifhello_html_m80b1b71.gifудет таков. Генерируем n пар слу-

чайных чисел в диапазоне от -1 до 1.

Пhello_html_m3efb526b.gifhello_html_m262ea49d.gifусть эти пары представляют собой

к

A

оординаты х и у точек плоскости хОу.

Т

x

очка с координатами, каждая из

которых не превышает по модулю единицу,

пhello_html_7b40ea0.gifhello_html_476db034.gifhello_html_mecca5c0.gif

0

hello_html_m5afbc919.gifhello_html_40862967.gifопадает в описанный выше квадрат.

«

B

Обстреляем» квадрат N пулями –

точками. Каждая точка попадает в квадрат,

но не каждая попадает в круг, вписанный

в квадрат. Подсчитаем количество точек,

п

-1

опавших в круг (это точки, для которых

Sqrt(x*x+y*y)<=r). По этому количеству

тhello_html_mecca5c0.gifочек можно судить о площади круга.


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

На рисунке показано, что точка, лежащая в I четверти, попала в круг. А точка IV четверти в это число не входит. Положим, в круг попало k точек из n. Тогда площадь круга будет равна s = k / n * 4, где 4 - площадь квадрата. Естественно, что чем больше испытаний n, тем ближе значение s будет к искомому π. Не забудем, что применять переменную с именем Pi нельзя! Это зарезервированная в Pascal математическая функция.

Ниже приведена программа, реализующая описанный алгоритм. Это есть примитивное трактование метода Монте-Карло.

Program P_50;

Uses Crt;

Var x, y, oa, s: Real;

k, n, i: LongInt;

Begin

ClrScr;

Randomize; {«Испортим» генератор случайных чисел}

Write('Введите количество испытаний '); ReadLn(n);

k:=0;

For i:= 1 To n Do

Begin

x:= Random * 2 - 1;

y:= Random * 2 - 1;

oa:= Sqrt(x * x + y * y);

If oa <= 1 Then k:= k + 1

End;

s:= k / n * 4;

WriteLn('При ', n, ' испытаний Pi=', s:10:9);

ReadKey

End.


И небольшая таблица...

Число испытаний n

Значение «пи»

1

0.000000000

2

4.000000000

4

3.000000000

8

2.500000000

16

3.250000000

32

3.125000000

64

3.500000000

128

2.937500000

256

3.109375000

512

3.148437500

1024

3.066406250

123456

3.143954121

654321

3.141558960


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

Чтобы максимально приблизить генерируемую последовательность чисел к случайной, можно периодически (лучше, если с каждой генерацией) изменять значение аргумента функции. На практике в качестве аргумента часто применяют некоторое число, связанное с текущим временем (например, текущее количество секунд). Для этого перед очередным вызовом генератора случайного числа следует получить доступными средствами текущее время, выделить количество секунд и использовать это значение в качестве аргумента функции Random. При этом последовательности чисел будут «переключаться» раз в секунду. Это определенно положительно отражается на результатах работы программы.

Вопрос 4.4.1. На чем основан метод Монте-Карло?


§4.5 Численное интегрирование

Для интегрирования функций применяют методы прямоугольноков, трапеций, Симпсона и других. Простейшим из них является метод прямоугольников. Метод применим для вычисления определенного интеграла функции f(x) на отрезке от х1 до х2. Суть метода состоит в следующем.

Интервал интегрирования разбивается на n частей (элементарных отрезков). Для каждого такого отрезка вычисляется приближенная площадь над ним по формуле s=h * f(x). При этом элементарный участок приближенно считается прямоугольником (отсюда и название метода). Здесь h - величина шага интегрирования (длина элементарного отрезка), f(х) – значение функции в точке х (высота элементарного участка).

hello_html_m44fe1eaa.gifhello_html_mb60b119.gifhello_html_438e1b6b.gifhello_html_m579b343e.gifhello_html_d971bf.gifhello_html_m4bebdffe.gif

A

hello_html_331789a2.gif

Рис.11


f(x)


hello_html_mb60b119.gifhello_html_m5ee0d1.gifhello_html_18e8f33a.gifhello_html_m1433b96a.gifhello_html_5a63466a.gif

Пhello_html_mb60b119.gifhello_html_m5ee0d1.gifhello_html_m6c4676dc.gifhello_html_787e9c9a.gifhello_html_m144fe10a.gifлощадь элементарного участка вычис-

hello_html_40862967.gifhello_html_mb60b119.gifляется как площадь прямоугольника, а

hello_html_72035bbd.gifhello_html_463f4a12.gifзначение интеграла получается при

суммировании всех элементарных пло-

hello_html_438e1b6b.gifщадей.

B


hello_html_m7e7d56c6.gif


Точность вычислений зависит от шага интегрирования (или от количества шагов, что при неизменном интервале одно и то же). Чем меньше шаг, тем выше точность.

В качестве примера рассмотрим вычисление интеграла f(x) = 3x2 + 2x + 3 на участке от 2 до 5. Аналитически интеграл этой функции равен (x3 + x2 + 3x) = (125 + 25 + 15) – (8 + 4 +6) = 165 – 18 = 147 кв. ед.


Program P_51;

Uses Crt;

Var y, st, s, x: Real;

Begin

ClrScr;

Write('Введите шаг интегрирования '); ReadLn(st);

x:=2;

While x - st < 5 Do Begin

y:= 3 * x * x + 2 * x + 3;

s:= s + y * st;

x:= x + st

End;

WriteLn('Интеграл равен ',s:8:4);

ReadKey

End.


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

Программа была тестирована для различных значений шага интегрирования.




Результаты работы программы сведены в таблицу.

По данным, приведенным в таблице,

отчетливо видна зависимость между ша-

Шаг Значение интеграла гом интегрирования и точностью по-

0.1 161.4880 лученного результата. С уменьшением

0.01 148.4184 шага интегрирования растет точность

0.0001 147.0142 вычислений, и одновременно увеличи-

0.000001 147.0000 вается время работы программы. При

значении шага 0.0001 время работы составляет около 5 секунд на AMD Athlon 2 ГГц.

Повысить точность вычислений при прежнем количестве шагов можно, применив метод трапеций. При вычислении интеграла методом прямоугольников на очередном шаге для значения х вычисляется значение y = f(x). Затем вычисляется площадь прямоугольника и добавляется полученной до этого момента площади. Однако, при этом площадь погрешности оказывается неучтенной. Таким образом накапливается ошибка.

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

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

Попробуем реализовать алгоритм интегрирования по методу трапеций с учетом приведенных выше рассуждений. Для текущего значения х вычисляем y1 = f(x), далее вычисляем y2=f(x+h). Площадь трапеции находим по формуле s1=(y1+y2)/2*h. Сложив все полученные площади элементарных трапеций на интервале от х1 до х2, получим значение интеграла.

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


Program P_52;

Uses Crt;

Var y, y1, y2, st, s, x: Real;

Begin

ClrScr;

Write('Введите шаг интегрирования '); ReadLn(st);

x:=2;

While x - st < 5 Do

Begin

y1:= 3 * x * x + 2 * x + 3;

y2:= 3 * (x + st) * (x + st) + 2 * (x + st) + 3;

y:=(y1 + y2) / 2 * st;

s:=s + y;

x:= x + st

End;

WriteLn('Интеграл равен ',s:8:4);

ReadKey

End.


В приведенной ниже таблице дан сравнительный анализ характеристик программ. Функция y = x2 проинтегрирована двумя методами: прямоугольников и трапеций. Результат, получен методом трапеций примерно на порядок (в 10 раз) точнее, а время работы программ соизмеримо. Очевидно, что предпочтительным является метод трапеций.






Шаг

Метод прямоугольников

Метод трапеций

Результат

погрешн., %

результат

погрешн., %

0.1

2.47000

7.3740

2.60000

2.50000

0.01

2.68670

0.7515

2.70690

1.50900

0.001

2.66471

0.0731

2.66631

0.01310

0.0001

2.66612

0.0203

2.66671

0.00187


Вопрос 4.5.1. Чем отличаются метод трапеций и прямоугольников? Какой из них точнее при заданном количестве интервалов?

Вопрос 4.5.2. Запишите в дискретном виде выражение интеграла функции по методу трапеций.



§4.6 Операции с матрицами

Двумерные массивы (матрицы) широко применяются на практике для решения различного типа задач. Понятие матрицы является основополагающим для огромного раздела математики - линейной алгебры.

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

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

По возможности, будем придерживаться в программах следующих соглашений: n - количество строк матрицы, m - количество столбцов, i, j, k - переменные цикла, причем i - по строкам, j - по столбцам. Если в программе одна исходная матрица и одна выходная, то их имена, соответственно, а и b. Если исходных матриц две, а выходная одна, то их имена - а, b и с.


Сложение матриц: a[n, m] + b[n, m] -> c[n, m]

For i:= 1 To n Do {Сложение матриц производится путем сло-}

For j:= 1 To m Do {жения их отдельных элементов. Складывать}

c[i, j]:= a[i, j] + b[i, j]; {можно только те матрицы, у которых оди-}

{наковое количество строк и}

{одинаковое количество столбцов.}


Транспонирование матрицы: T(a[n, m]) -> b[m, n]

For i:= 1 To n Do {Транспонирование-это замена строк столб-}

For j:= 1 To m Do {цами. У транспонированной матрицы столь-}

b[j, i]:= a[i, j]; {ко строк, сколько столбцов у исходной}

{(и наоборот).}


Копирование матрицы: а[n, m] -> b[n, m]

For i:= 1 To n Do {Копируются все элементы матрицы а в со-}

For j:= 1 To m Do {ответствующие элементы матрицы b.}

b[i, j]:= a[i, j];


Умножение матрицы на скаляр: а[n, m] * c -> a[n, m]

For i:= 1 To n Do {При умножении матрицы на число с, на это}

For j:= 1 To m Do {число умножаются все элементы матрицы.}

a[i, j]:= a[i, j] * c;



Умножение строки матрицы на скаляр

For j:= 1 To m Do {На c умножается k-я строка матрицы. Эта}

a[k, j]:= a[k, j] * c; {операция часто встречается в задачах ре-}

{шения систем линейных уравнений.}


Сложение двух строк матрицы

For j:= 1 To m Do {Складываются строки k и l матрицы а[n, m]}

a[k, j]:= a[k, j] + a[l, j]; {Результат размещается на месте строки k.}



Сложение двух столбцов матрицы

For i:= 1 To n Do {Складываются столбцы k и l матрицы.}

a[i, k]:= a[i, k] + a[i, l]; {Результат размещается на месте столбца k}


Замена строк местами

For j:= 1 To m Do

Begin {Строки k и l меняются местами}

d:= a[k, j];

a[k, j]:= a[l, j];

a[l, j]:= d

End; {Используется "посредник" d.}


Замена строки матрицы вектором

For j:= 1 To m Do {Строка номер k матрицы заменяется вектором e[m].}

a[k, j]:= e[j];


Замена столбца матрицы вектором

For i:= 1 To n Do {Столбец номер k матрицы заменяется вектором e[n].}

a[i, k]:= e[i];


Умножение матриц: a[n, m] * b[m, l] -> c[n, l]

For i:= 1 To n Do {Умножение матриц производится не поэле-}

For j:= 1 To l Do {ментно, а по специальному алгоритму.}

Begin

c[i, j]:= 0; {Элемент (i, j) матрицы-результата есть}

For k:= 1 To m Do {сумма произведений элементов i-й строки}

c[i, j]:= c[i, j] + a[i, k] * b[k, j];

End; {первой матрицы на j-й столбец второй.}

{Произведение матриц не коммутативно!}

{т.е. [а] * [b] не равно [b] * [a].}

{Умножать можно такие матрицы, у которых число столбцов первой}

{равно числу строк второй.}


Объединение матриц с одинаковым числом строк

For i:= 1 To n Do

Begin {Матрица b присоединяется к матрице а}

For j:= 1 To m Do c[i, j]:= a[i, j]; {справа. Результат размещается}

{в матрице с. Число строк у всех матриц равны n. А}

{число столбцов в матрице с равно сумме}

For j:= 1 To k Do c[i, n + j]:= b[j]; {числа столбцов матриц а и b.}

End;


Сортировка строк матрицы

For i:= 1 To n Do

Repeat {Сортируем все строки от 1 до n}

l:= 0; {пузырьковым методом (l- число перестановок)}

For j:= 2 To m Do {цикл сортировки i-й строки}

If a[i, j] < = a[i, j-1] Then

Begin

d:= a[i, j]; {Сортируем по возрастанию. Чтобы сортировать}

a[i, j]:= a[i, j-1]; {по убыванию, нужно изменить знак}

a[i, j-1]:= d; {< = на > = в операторе If.}

l:=l+1

End;

Until l=0;






























Глава V. Сортировка.


§5.1 Методы сортировки

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

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

Чтобы сортировать данные, необходимо иметь однозначное трактование критерия сравнения данных. Иными словами, для каждой пары данных не должно возникать сомнения, какое из них будет предыдущим, а какое последующим. Если речь идет, например, о сортировке чисел, то здесь таких вопросов не возникает. Однако бывают случаи, когда стоит задуматься, какое из данных должно стоять впереди. Например, сортируются наименования автомобилей? Какой из них должен стоять раньше – Renault Logan или Renault__Logan?

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

Алгоритмы сортировок разберем для начала на примере сортировки чисел.


§5.2 Сортировка методом выборки

Рассмотрим метод на примере сортировки одномерного массива длиной n по возрастанию.

Этот метод состоит в следующем: отводим в памяти место для выходных данных (куда будем пересылать сортированные данные). В исходном (сортируемом) массиве находим минимальное число. Переписываем его на первое место во втором массиве, а в первом – «стираем» («Стираем» – означает присвоение элементу значения, чтобы программа точно уж не приняла его за минимальный. В целочисленном формате 32767). Затем вновь ищем минимальное в первом массиве и переписываем его во второй массив на (уже) второе место. В первом – «стираем». Так продолжаем до тех пор, пока все элементы из первого массива не будут переписаны во второй. Очевидно, что в конце работы программы элементы в выходном (втором) массиве будут расположены в порядке возрастания.


Program P_53;

Uses Crt;

Var a, b: Array[1..100] Of Integer;

c, d, i, j, k, n, Min: Integer;

Begin

Randomize;

ClrScr;

Write('Введите размер вектора, не более 100 '); ReadLn(n);

Write('Введите границы значений элементов '); ReadLn(c, d);

WriteLn('Исходный массив ');

For i:= 1 To n Do

Begin {Цикл формирования исходного массива}

a[i]:= Random(d - c) + c;

Write(a[i]:5)

End;

WriteLn;

For i:= 1 To n Do

Begin

Min:=a[1]; {Каждый раз берем 1-ый элемент и}

For j:= 1 To n Do {решаем стандартную}

If a[j]<= min Then {минмаксную задачу.}

Begin

Min:=a[j];

k:= j

End;

b[i]:= a[k];

a[k]:= 32767; {«Уничтожаем» очередной максимум.}

End;

WriteLn('Отсортированный массив');

For i:= 1 To n Do Write(b[i]:5);

WriteLn;

ReadKey

End.


Элементы исходного массива а переписываются в массив b. При этом всякий раз переписывается минимальный из оставшихся. Цикл по i задает индекс (адрес) куда переписывать очередной элемент. Далее осуществляется поиск минимального элемента в массиве а и его номера k. Далее найденный минимум (он находится в ячейке номер k массива а) переписывается на очередное (i-ое) место в массив b, а в массиве а - стирается.

Сортировка продолжается до тех пор, пока все n элементов не будут переписаны в выходной массив.

Ну что... разобрались? Понятно? Теперь забудьте этот алгоритм. Он не применяется на практике. Воспринимайте его как учебный. (Впрочем, если на экзаменах отсортируете массив по этому алгоритму, то будете правы - он все-таки работает... и «довольно исправно» - ну и что, что медленно?). Не применяется потому, что, во-первых, съедает памяти вдвое больше, чем занимают сортируемые данные, во-вторых, он очень медлителен: количество проходов по массиву всегда равно n (числу элементов массива). Ведь чтобы найти очередной минимум в исходном массиве, нужно просмотреть все его элементы. А такую операцию нужно выполнять n раз.

С точки зрения расхода памяти приемлемым является метод перестановок.


§5.3 Метод перестановок (пузырьковый метод)

Суть этого метода состоит в следующем. Просматриваем все элементы от первого до предпоследнего. Если очередной элемент больше последующего, то что-то неправильное есть в их расположении (ведь сортируем по возрастанию, а впереди - меньший). Не знаем, что там потом будет, но эти два давайте поменяем местами. Так будет «справедливей».

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

Так доходим до конца строя (массива). Вспоминаем, была ли хотя бы одна перестановка. Если нет - все o'key: у любого элемента впереди тот, который больше. Значит, все в порядке, массив сортирован.

Ну а если нет? Что-то там значит не так. Есть еще, значит, в массиве «несправедливости». Возвращаемся опять. К началу. И все заново.

И так до тех пор, пока однажды, в конце прохода припоминая, были ли перестановки, мы обнаружим: нет, все в порядке. Ни одной перестановки не было. Все «по росту».

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

Условие завершения сортировки - по окончании прохода не было ни одной перестановки. Расход памяти: массив сортируется «сам в себе», без привлечения дополнительных ресурсов памяти.


Program P_54;

Uses Crt;

Var a: Array[1..100] Of Integer;

c, d, i, p, k, n: Integer;

Begin

Randomize;

ClrScr;

Write('Введите размер вектора, не более 100 '); ReadLn(n);

Write('Введите границы значений элементов '); ReadLn(c, d);

WriteLn('Исходный массив ');

For i:= 1 To n Do

Begin

a[i]:= Random(d - c) + c;

Write(a[i]:5)

End;

WriteLn;

Repeat {До тех пор, пока число перестановок не станет равно нулю,}

k:= 0; {начинаем новый проход по массиву.}

For i:= 1 To n-1 Do {Рассматриваем n-1 пару элементов.}

If a[i] > a[i + 1] Then {Если пара стоит не на месте,}

Begin

p:= a[i]; {меняем элементы местами и}

a[i]:= a[i + 1];

a[i + 1]:= p; { указываем, что}

k:= k + 1 {перестановка произошла.}

End;

Until k = 0;

WriteLn('Отсортированный массив');

For i:= 1 To n Do Write(a[i]:5);

WriteLn;

ReadKey

End.


Сортировка осуществляется в несколько (минимум - 1, максимум - n) проходов по массиву. Количество проходов заранее не известно. Оно зависит от расположения данных в массиве до сортировки.

При каждом проходе будем подсчитывать количество перестановок. Счетчик перестановок обнуляем перед каждым проходом. Просматриваем элементы с первого до предпоследнего (смотрим «вперед», а для последнего «впереди» никого нет!). В цикле: если очередной элемент a[i] больше a[i + 1] (именно «больше»! Поставить здесь знак «больше или равен» - значит заставлять машину менять равные элементы и подсчитывать при этом перестановки. Так программа зациклится!), то их взаимное расположение отвечает требованиям сортировки (сортируем ведь по возрастанию!), и менять их местами не нужно.

В противном случае (а что делать?!! Неправильно стоят!) – меняем местами элементы a[i] и a[i + 1]. При этом увеличилось число перестановок.

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

К достоинствам метода следует отнести простоту, малый расход памяти (массив сортируется «в себе»), наглядность. Количество проходов по массиву лежит в пределах от 1 до n.

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

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

От многих этих недостатков свободны методы возврата.


§5.4 Сортировка методами возврата

Забудем пока вообще о методах, об алгоритмах и задумаемся над одним философским вопросом: «быть или не быть?» Быть или не быть сортировкам? А зачем вообще нужна сортировка как таковая? Есть данные (числа, номера ...). Лежат себе в массиве и лежат. Зачем нужно, чтобы они были расположены по возрастанию или убыванию? Давайте представим телефонный справочник Москвы в несортированном виде. Нужен нам Иванов Иван Иванович - открываем «кирпич» и ищем по страничкам. К утру (хорошо, если к утру)...

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

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

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

Существует множество алгоритмов такого перемещения. Что может быть проще, как «пойти» назад потихоньку, шаг за шагом перебирая элементы в поисках места для нашего «новенького». При этом делаем так: «вынимаем» этот элемент из массива (запоминаем его значение), чтобы «понести» его к началу (его место где-то в той стороне). Предыдущий продвигаем на его место (освободившееся). Оцениваем ситуацию: здесь ли он должен стоять, или еще левее? Если левее, то продвигаем следующий. Так в конце концов находим место для нашего «вынутого» и там его размещаем (ну, а не найдем, значит, он первый). Потом вспоминаем, где остановились и продолжаем проход с прерванной точки.

Program P_55;

Uses Crt;

Var a: Array[1..100] Of Integer;

c, d, i, j, k, n: Integer;

Begin

Randomize;

ClrScr;

Write('Введите размер вектора, не более 100 '); ReadLn(n);

Write('Введите границы значений элементов '); ReadLn(c, d);

WriteLn('Исходный массив ');

For i:= 1 To n Do

Begin

a[i]:= Random(d - c) + c;

Write(a[i]:5)

End;

WriteLn;

For i:= 2 To n Do

If a[i] < a[I - 1] Then

Begin

k:= a[i];

For j:= i DownTo 3 Do

Begin

a[j]:= a[j - 1];

If k > = a[j - 2] Then a[j - 1]:= k

End;

a[2]:= a[1];

a[1]:= k

End;

For i:= 1 To n Do Write(a[i]:5);

WriteLn;

ReadKey

End.


В цикле: найдя элемент, стоящий не на своем месте, «вынимаем» его из массива (переменная k) и осуществляем возврат к началу в поисках места для этого элемента. Продвинув предыдущий на единицу вперед, оцениваем место для элемента. Очевидно, оно найдено, если элемент в этой точке оказался больше, чем предыдущий; если это не так, то поиск продолжаем.

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

Программа не будет работать с массивом меньше 3-х элементов (а зачем?).

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

Ну, посудите сами. Слева - сортированный участок массива. А мы (программа) ищем место в нем обычным перебором всех элементов по порядку. Это все равно, что искать Иванова в справочнике (сортированном!), просматривая все страницы подряд. Ведь для того он и сортирован...

Давайте еще раз «поищем Иванова». Как будем это делать? Нормальный человек, наверное, сделал бы так: открыл бы справочник посередине. Посмотрел бы, что за «буква» попалась. Если больше, чем «И», значит Иванов где-то сзади (и наоборот). Пусть больше. Открываем середину левой части справочника и опять смотрим на «букву» и т.д. Так, разделяя оставшиеся страницы пополам, в конце концов найдем букву «И», потом «ИВ», потом «ИВА» и... Ну, сами знаете.

Теперь попробуем то же самое, но с массивами. Итак, «вынули» элемент, который должен стоять где-то левее (т.е. между первым и текущим, i-ым). Делим этот отрезок пополам. Это будет номер k: = (i+1) div 2) («начало» + «конец» разделить на 2, причем, целая часть этого выражения; не может же быть номер элемента дробным). Оцениваем новое место для элемента.

Если оно подходит (элемент больше, чем a[k] и не больше чем a[k + 1]), то тогда поступаем так: все элементы между k + 1 и i - 1 сдвигаем на шаг вперед (вспоминайте: цикл сдвига должен иметь обратное направление – от i - 1 до k + 1 c шагом -1), элемент записываем на место k + 1 и продолжаем сортировку с точки i + 1 (со следующей).

Если же выбранное делением отрезка пополам место оказалось для элемента неподходящим, то будем искать дальше (как и в справочнике). Для этого нужно выяснить, где расположено нужное место: слева или справа от только что выбранного (от точки k). Очевидно, это определяется условиями: если a[i] > a[k + 1], то справа; в этом случае новое место k = (k + i) div 2, если же a[i] < a[k], то слева, и тогда новое k = (1 + k) Div 2. Здесь полезно вспомнить метод половинного деления.

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

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

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

Попробуйте сами написать программу по приведённому сценарию.


§5.5 Сортировка рекурсией.

Этот метод был разработан в 1962 г. английским программистом К.Хоаром.

Суть его в следующем:

1. Выбираем центральный элемент исходного массива Х и записываем его в переменную - посредник А. Потом элементы исходного массива просматриваются в левой части слева – направо, а в правой части справа – налево. В левой части находим элемент x[i], который больше или равен А и запоминаем его позицию. В правой части ищем такой элемент и его позицию, который меньше или равен А. Найденные элементы меняем местами и продолжаем встречный поиск с теми же самыми условиями. На этом первый этап закончен, причём массив окажется разделённым таким образом, что элементы со значениями меньше или равно А будут в левой части массива, все остальные – в правой.

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

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


Program P_56;

Uses Crt;

Var x: Array[1..20] Of Integer;

i: Word;

Procedure Sort(l, r: Word);

Var a, tmp: Integer;

i, j: Word;

Begin

a:= x[(l + r) div 2];

i:= l;

j:= r;

While i < = j Do

Begin

While x[i] < a do i:= i + 1;

While x[i] > a do j:= j – 1;

If i < j Then

Begin

tmp:= x[i];

x[i]:= x[j];

x[j]:= tmp;

i:= i + 1;

j:= j – 1

End

End;

If l < j Then Sort(l, j);

If l < r Then Sort(i, r)

End;


Begin

ClrScr;

WriteLn(’Введите элементы массива:’);

For i:=1 to 20 Do Read(x[i]);

ReadLn;

Sort(1, 20);

WriteLn(’Отсортированный массив:’);

For i:=1 To 20 Do Write(x[i]:8);

WriteLn;

ReadKey

End.


Сравнительный анализ времени работы различных методов сортировки приведен в таблице. Здесь приведены опытные данные, полученные при внешней сортировке файла прямого доступа, содержащего 100000 записей по 128 байт. Программы выполнялись на компьютере с процессором AMD AthlonXP-2000 и RAM 1 Gb.

Сортировка

Время выполнения

Методом выборки

День? Два? Терпения, увы, не хватило...

Методом перестановок

Программа была остановлена после того, как за 1,5 часа выполнила около 80 % работы

Методом возврата с алгоритмом поиска номера элемента простым перебором.

4 минуты

Методом рекурсии

2 минуты




§5.6 Двухпроходные методы сортировки

Двухпроходные методы сортировки применяются в тех случаях, когда сортируемые объекты можно представить в виде совокупности конечного числа групп, например, алфавитная сортировка по первой букве. Все сортируемые слова (сколько бы их ни было) можно разбить на 30 групп в зависимости от того, с какой буквы начинается слово (в русском алфавите 31 буква и 2 знака, слов, начинающихся с твердого или мягкого знака, или с буквы Ы, по-моему, нет). Пусть первая группа представляет собой слова, начинающиеся на букву А, вторая - на Б и т.д.

Пусть а[m] - сортируемый массив, b[m] - выходной массив (в массив b будем переписывать сортированные данные). Пусть массив а содержит m элементов, которые нужно сортировать. Каждый элемент относится к одной из n групп (в примере с алфавитной сортировкой n = 31).

Сортировка двухпроходным методом производится в три этапа.

Первый этап. Осуществляем проход по сортируемому массиву а. При этом подсчитываем количество объектов (элементов) в каждой группе. Заполняем предварительно «очищенный» вспомогательный массив С. С будет содержать количество элементов каждой группы в исходном массиве. Длинна массива С равна количеству групп (в нашем примере - 30 (букы е и ё рассматриваем как одно и то же)). После окончания прохода с[1] будет содержать количество слов в массиве, которые начинаются на А, с[2] - количество слов на Б и т.д.

Ниже приведен фрагмент программы для первого прохода.

For i:= 1 To m Do {цикл первого прохода по массиву а[m]}

Begin {выясняем, к какой группе относится элемент а[i]}

..... {пусть это будет группа номер k}

.....

c[k]:= c[k] + 1 {найден еще один элемент группы номер k}

End;

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

Пример: после первого прохода выяснилось, что количество слов на букву А равно 10, на Б - 12, на В - 20, на Г - 40... Следовательно, слова на А должны быть расположены в выходном массиве начиная с начала (с первого элемента), слова на Б - с одиннадцатого (перед ними 10 слов на А), слова на В - с 23 элемента (10 на А + 12 на Б, всего 22), слова на Г - с 43 элемента и т.д., т.е. массив должен быть преобразован примерно так:

10 12 20 40 ... (массив содержал количество элементов по группам)

1 11 23 43 ... (массив содержит начальные номера элементов в группах)

Такое преобразование массива можно произвести следующим образом.

k:= 1; {если не задаваться целью максимальной экономии}

For i:= 1 To m Do {памяти, массив С можно преобразовать во второй}

Begin

k1:=c[i]; {массив c1 следующим образом}

c[i]:= k; {c1[1]= 1}

k:= k + k1 {c1[i]= c1[i - 1] + c[i - 1] для 2 < = i < = m}

End; {такое преобразование более наглядно.}

Третий этап сортировки. Осуществляем второй проход по массиву а. Взяв очередной элемент, выясняем номер группы, к которой он принадлежит (пусть это будет номер k). Элементы группы номер k в выходном массиве должны быть расположены начиная с номера, который хранится в с[k]. Переписываем элемент в выходной массив на место номер c[k]. После этого увеличиваем на единицу содержимое ячейки c[k], т.к. следующий элемент этой группы (если найдем) будем переписывать в следующую ячейку.

Здесь элементы массива с выступают в роли указателей. Они указывают (содержат адрес) на номер элемента массива, куда следует записать очередной элемент k-й группы.

Фрагмент программы для третьего этапа сортировки.

For i:= 1 To m Do {цикл второго прохода}

Begin

.....

..... {определить k - номер группы элемента a[i]}

.....

b[c[k]]:= a[i]; {переписать элемент в массив b}

c[k]:= c[k] + 1 {инкремент указателя}

End;

Напоминание: инкрементом (инкрементированием) называется увеличение на определённую величину.

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

Однако такое написание программы существенно ухудшит ее читаемость. В виде подпрограммы следует оформить фрагмент определения k – номера группы элемента a[i].

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

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

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

Это именно тот случай, когда за скорость выполнения приходится «платить» лишней памятью.


§5.7 Общие принципы алфавитных сортировок

Различают два способа алфавитных сортировок: прямая сортировка и сортировка последовательным приближением.

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

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

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

Шаблон может выглядеть, например, так:

АБВГДЕЖЗИКЛМНОПРСТУФ...ЭЮЯABCDEF...XYZ$*#()...

или так:

АAБBВWГGДDЕE... и т.д.

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

Вопрос 5.1. Есть ли в матрице столбцы, сортированные по возрастанию?

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

Вопрос 5.3. Сортировать одномерный массив в параболическом порядке (по краям должны быть расположены максимальные значения, которые должны убывать по мере приближения к середине).

Вопрос 5.4. Сортировать главную диагональ квадратной матрицы в порядке возрастания из левого нижнего угла

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

Вопрос 5.6. Задан двумерный массив. Разместить его элементы в одномерном массиве в порядке возрастания модулей.

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

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











































Глава VI. Отладка программ.

Эта маленькая глава полностью будет посвящена отладке программ.

Итак, вы написали программу и ввели ее в компьютер. Программа, конечно же, содержит ошибки (иначе вам место в музее, но не среди посетителей). Такие ошибки подразделяют на 2 большие группы: ошибки этапа трансляции и ошибки этапа выполнения. Ошибки первой группы «всплывают» сразу же после попытки запуска программы. Вторые - это «подводные камни».

Поговорим сначала о первых. В подавляющем большинстве случаев – это синтаксические ошибки.

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

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

Еще раз: о таких ошибках компилятор сообщает сам. Причем, до того, как позволить программе стартовать. Он же сообщит и номер ошибки.

Обнаружение и исправление таких ошибок, как правило, не составляет труда даже начинающим.

2. Ошибки этапа выполнения. Эти ошибки возникают уже в процессе работы программы, после того, как программа «допущена» компилятором на выполнение. Эти ошибки, опять-таки подразделяются на 2 группы: к первой относятся те ошибки, которые приводят к аварийному завершению программы, ко второй - которые «молчат».

Сначала о первой группе.

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

Ко второй группе ошибок этапа выполнения относятся логические ошибки. При наличии таких ошибок программа не дает сбоя при выполнении. Это ошибки в алгоритме. Сложность их выявления прежде всего состоит в том, что программа ведет себя внешне как вполне «здоровая»: запускается в работу, производит расчеты, выдает результаты, но они не верны (или могут однажды оказаться неверными).

О наличии логических ошибок в программе можно даже не подозревать. И только однажды, когда программе предложили провести расчеты с данными, с которыми она до сих пор не сталкивалась, - она выдала неверный результат. Хорошо, если по результату хотя бы примерно можно определить, насколько он верен, если его «не верность» бросается в глаза. А если нет?

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

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

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

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

Это все было об ошибках, какие они бывают. Теперь о том, как с ними бороться.

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

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

Для выявления ошибок выполнения рекомендуется следующая схема.

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

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

2. Проверить, допустимо ли выполнение тех действий, которые выполняются в этой строке над такими данными. Например, строка содержит оператор a:= b / sqrt(b - c). Предположим, что в этой строке произошла ошибка. Необходимо в непосредственном режиме вывести (посмотреть) значение переменных b и c. Если b < c, то выражение под корнем отрицательно, и вероятнее всего, ошибка произошла по этой причине. Если b = c, то знаменатель равен нулю, и ошибка произошла при попытке деления на ноль. Так можно выявить ошибку, даже не обращаясь к таблице.

В большинстве случаев таким способом удается выяснить характер ошибки и затем приступить к выяснению причины ее возникновения.

Если же и это не помогает, то:

3. Попробуйте выполнить отдельные составляющие ошибочной строки с реальными данными.

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

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

Если же логика задачи не допускает таких значений (например, площадь фигуры оказалась отрицательной), значит, ошибка не в том, что «закапризничал» корень квадратный, а в том, что это значение вообще получилось отрицательным. Ошибка произошла где-то раньше, когда вычисляли это злополучное отрицательное значение. Это логическая ошибка (результат получен, но он не верен).

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

Метод трассировки применяют для определения последовательности выполнения операторов программы. Этот метод не имеет смысла для линейных (неразветвленных) программ. Там все операторы выполняются последовательно, один за другим.

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

Метод контрольных точек применяют для отладки программ в тех случаях, когда необходимо определить участок программы, содержащий ошибку. Контрольная точка - это команда Breakpoints меню Debug компилятора. И вообще – это меню крайне удобная «штучка».

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

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

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

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

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

Контрольных тестов, как правило, бывает несколько. Чтобы проверить все возможные ситуации, каждый из путей алгоритма. Только после «прогонки» комплекса тестов программу приближенно можно считать отлаженной.




Некоторые часто встречаемые ошибки

Ошибка

Что произошло

85

«;» expected

Ожидается «;». Самая часто встречающаяся ошибка, особенно у начинающих.

20

Variable identifier expected

Ожидается переменная. Забыли в разделе описаний назвать переменную!

26

Type mismatch

Несовпадение типов. В процессе работы программа пытается поменять тип переменной.

31

Constant expected

Нужна константа

36

Begin expected

Без комментариев

37

End expected

----------------

41

Operand types do not match operator

Тип операнда не соответствует оператору

42

Error in expression

Ошибка в выражении

45

Object file too large

Увлеклись, господа, увлеклись! Pascal не может компоновать файлы таких больших размеров. Используйте модули или библиотеки.

113

Error in statement

Ошибка в операторе.

108

Overflow in arithmetic operator

Результат операции вне диапазона LongInt

116

Must be in 8087 mode tp compile this

Программа может компилироваться только в режиме {$N+}

Учтите, что даже указание места в исходном коде, где произошла ошибка, может не помочь, например, в случае, если неправильно подготовлены данные или некорректно используются функции. В таких случаях используют клавиши F4, F7 и F8. Чаще всего – F7. Эта клавиши позволяет трассировать (выполнять построчно) программу, выделяя цветом строки на экране редактора. Ещё одна очень удобная «штучка» - клавишный аккорд Ctrl+F4. Например, мы захотели во время трассировки узнать, чему равно значение какой-либо переменной вот именно сейчас. Без проблем.

  1. Ставим курсор на строку (на переменную);

  2. Нажимаем Ctrl+F4;

3. Открылось диалоговое окно, состоящее из трёх полей. Если в верхнем поле нет имени переменной (ну промахнулись, бывает), то в это поле записываем имя интересущей нас переменной и нажимаем на Enter. Кстати, то же самое применимо не только к переменным, но и к целым выражениям!

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

Вопрос 6.1. Чем различаются ошибки этапа трансляции и ошибки этапа выполнения?

Вопрос 6.2. На какие группы подразделяются ошибки этапа выполнения?

Вопрос 6.3. Какие методы применяют для обнаружения ошибок?

Вопрос 6.4. Что такое контрольная точка?

Вопрос 6.5. В чем состоит особенность отладки больших программ?

Вопрос 6.6. Каковы особенности отладки многомодульных программ?

Вопрос 6.7. Что такое контрольный пример?







Приложение 1

Устройство и работа компьютера

Современная жизнь общества диктует нам свои законы. Нельзя в современном мире обойтись без минимального знания компьютерных технологий, что уж поделать – таковы требования нашего времени. И вполне обоснованно, если, желая получить хорошую работу, мы постоянно натыкаемся на следующую фразу в объявлениях по найму: «ПК, приложения Windows обязательно…»

Постичь же эти азы (вернее, начать их постигать) порой мешает чисто субъективная причина. Между человеком и компьютером (точнее, представлениями о нем) встает психологический барьер. Он-то как раз и не позволяет взглянуть на проблему с достойной оценкой своих собственных возможностей. По-сути, компьютер – это инструмент, как и, допустим, топор. И как вы этот инструмент будете использовать – вопрос, скорее, философский. Согласитесь, что топором можно дом срубить, а можно… Вот именно. А теперь добавим к инструменту – нет, не точило – а специальный раздел математики под названием «Алгера логики». Вуаля! Вот он – компьютер. Возвращаясь к аналогии с топором: надо хотя бы минимально представлять, что рабочая часть – это лезвие, а не обух (хотя кому как). Улыбнулись? Вот так и с компьютером: в подавляющем большинстве случаев простые пользователи даже не подозревают, что находится там внутри и как это работает. А мы попробуем совсем немного поговорить об этом.

«Что может этот компьютер?»

Традиционный вопрос. Не будем придираться к такому применению слов. Может оно здесь звучит как-то неестественно, но придает некоторую одушевленность этой, хоть и электронной, но все-таки «железяке».

Компьютер может все. Компьютер не может ничего... Оба этих высказывания, как ни парадоксально, справедливы. Все возможности компьютера определяются, в первую очередь, программой, по которой этот компьютер работает. Составляет программу человек (сотни и тысячи человек. Согласитесь, что Windows Vista – результат работы явно не фаната-одиночки, если даже в занимаемый ею объём могут поместиться все библиотеки не самой маленькой страны), записывает её в память компьютера и «предлагает» ему эту самую программу выполнить. А тот уже её выполняет. Ту же самую работу мог бы выполнить и сам человек, но ему потребовалось бы для этого гораздо больше времени (раз, эдак, в несколько миллиардов больше, чем компьютеру). К тому же, человек иногда ошибается, иногда хочет есть, пить, спать, он иногда устает, да и вообще человек - не вполне надежное «устройство».

Итак, компьютер без программы не может ничего. С программой... Стоп. Нет, не все. Для начала, компьютеры бывают разные. Одни с трудом выполняют простенькие арифметические программы, другие в мгновение ока решают сложнейшие. У одних памяти - о-го-го! У других - кот наплакал. У всех свои способности. Но надо понимать одну простую истину - они не умеют думать, мыслить. У них нет интеллекта. Они выполняют (беспрекословно, последовательно, шаг за шагом) волю человека. И эту самую волю человек сообщает компьютеру с помощью программы. Поэтому, слово «может» старайтесь не произносить применительно к компьютерам, а уж если обронили, то относите его (слово) к программе.

«Ну и глупый же этот компьютер!»

Да. Глупый. А что вы хотели? Например, у него и пальцев-то, т.е. инструментов счёта, всего два (ноль и единица, включено – выключено), а не 10, как у человека. Только говорить так о машине - неправильно. Разве может быть глупым телевизор или, например, холодильник? А отличаются они от компьютера тем, что делают только то, чему их научили. А функции компьютера программирует человек. Он сообщает компьютеру программу действий и заставляет его по этой программе работать. Впечатление создается такое, будто бы компьютер выполняет работу сам, без участия человека, без его воли. Это неверно.

Скорее, программа глупая (коли уж кто-то глупый!). Компьютеру что «сказали», то он и делает. Он не умеет думать (пока!). Еще раз: у него нет интеллекта. Он раб программы. Научитесь создавать умные программы и машина с ними станет умной.

Мы тут обронили слово «Память». Да, в компьютере есть память. Выглядит она в виде маленьких пластмассовых кирпичиков - микросхем. Если память, то, наверное, должна что-то запоминать? Да. Именно так. Она запоминает напряжения. В простейшем случае: подключили к выводу микросхемы напряжение, потом отключили, а на ней напряжение осталось (вспомнили школьные конденсаторы?). Запомнила. На практике, конечно, немного сложнее, но смысл тот же.

А напряжения эти микросхема не измеряет. Для нее напряжение либо есть (обычно это плюс 5 вольт), либо его нет (чаще - около 1 вольта). Если напряжение есть, то говорят, что это логическая единица. Если нет (или почти нет) - то логический ноль.

Одна такая микросхема памяти может запомнить миллиард и больше таких напряжений (нулей или единиц). Естественно, каждое напряжение запоминается и хранится отдельно, в своей ячейке (там, внутри микросхемы). Такая ячейка называется битом. Восемь таких ячеек (обычно смежных) называются байтом. Количество таких восьмерок бит (количество байт) характеризует объём памяти компьютера.


Битом называется объём информации, уменьшающий неопределённость вдвое.


Бит хранит одно из двух значений: 0 или 1. Поэтому, говоря об устройстве компьютера, удобно пользоваться двоичной системой счисления, у которой «в арсенале» только 2 цифры: 0 и 1. Используются также системы счисления с основаниями 8 (восьмеричная) и 16 (шестнадцатеричная). Для тех, кто собирается далее «разбираться» с любой технической специальностью, знание и умение работать в этих системах – жесткая необходимость, поскольку единый государственный экзамен по информатике придётся сдавать, а там…

Продолжаем. Байт - очень маленькая единица информации. На практике применяют другие, более крупные единицы: килобайт (обозначается буквой К). Содержит не 1000, как подсказывает интуиция, а 1024 байта (210 байт). Мегабайт содержит 1024 килобайта (обозначается буквой М). Гигабайт (G) содержит 1024 мегабайта. Конечно же, существуют и другие единицы, как, например терабайт и т.д.

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

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

Биты (внутри байта) тоже пронумерованы от 0 до 7. При этом нулевой бит называется младшим, а седьмой - старшим.

Часто рассматривают два рядом байта как одно целое и называет это словом. Слово - это 2 байта (16 смежных бит памяти). Двойное слово - это 32 смежных бита (или 4 байта). Логично было бы предположить, что «слово» - это такая единица информации, которую компьютер «съедает за один присест», а по-умному – «обрабатывет за один такт машинного времени».

Назначение памяти - хранение информации. От того, как велик объём памяти компьютера, зависят его возможности выполнять большие программы, обрабатывать большое количество данных. Программа, выполняемая компьютером, всегда находится в его памяти. Бывает, что вся программа целиком не помещается в памяти, тогда ее выполняют по частям.

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

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

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

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

Теперь несколько слов о программах. Программа представляет собой последовательность машинных команд, состоящих из нулей и единиц. Вся эта последовательность называется машинным кодом программы (или объектным кодом). Каждая команда «приказывает» процессору выполнить какие-то действия.

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

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

Писать программы на языке Ассемблера - почти одно и то же, что писать их в машинных кодах (нулями и единицами). Одному «слову» на языке ассемблера соответствует одна машинная команда. Программы на ассемблере пишут только профессионалы. И то лишь такие программы, которые невозможно написать на других языках. Здесь можно реализовать все возможности компьютера. Естественно, уровень такого языка программирования считается низким (обратите внимание – «низкий» не значит плохой! Разделение языков на низкий и высокий уровень нужно только для того, чтобы определить, как он «относится» к процессору).

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

Программа, написанная на другом языке (более высокого уровня), тоже может быть транслирована (компилирована) в объектный код. И делают это другие программы - компиляторы. Таких языков много, и для каждого языка есть своя программа-компилятор, которая переводит программу, написанную на этом языке, в программу на языке, понятном процессору, т.е. в нули и единицы (в объектный код).

Каждому «слову» из такого языка соответствует несколько машинных команд (до нескольких тысяч). И компилятор занимается именно тем, что переводит эти программы на язык машинных команд.

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

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

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

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

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



Hardware и Software

Подобно тому, как все живое не земле являет собой неделимое единство материального и нематериального, духовного, - компьютеры «существуют» в виде двух неделимых составляющих: «железа» (hardware) и программного обеспечения (software).

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

То же можно сказать и о «железе». Без программы компьютер - это ящик с красивыми яркими детальками. Куча интересных безделушек для детей младшего возраста.

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


«Железо»

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

Итак, что же внутри? Посмотрим, приоткрыв крышку современного компьютера. Одна плата сравнительно больших размеров, называемая материнской платой (Motherboard), привинчина к боковой стенке компьютера (если, конечно, корпус вертикального типа). На ней расположены большие микросхемы и несколько разъемов, в которые вставлены другие платы, размером поменьше. Это интерфейсные платы. В последнее время выпускаются так называемые интегрированные платы, на которые устанавливать что-то ещё не имеет смысла. На креплениях установлены устройства, позволяющие считывать и записывать информацию на внешние носители: CD/DVD и, теперь уже как экзотика, привод для дискет. Одно из устройств размещено целиком в корпусе компьютера и доступ к нему возможен только после открытия корпуса системного блока. Это винчестер. Рядом - блок питания. Все устройства соединены между собой проводами в виде лент. Так устроено большинство из современных IBM PC - наиболее популярных и распространенных в настоящее время компьютеров.

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

Все эти споры представляются абсолютно бессмысленными, т.к. сама по себе структурная схема - это ...условное изображение... Еще раз подчеркнем - условное. Теперь о подробности схемы. Для чего же мы тогда здесь так много об этом говорим? А для того, чтобы вы знали что ответить, если вас однажды упрекнут в том, будто бы ваша схема слишком примитивна.


hello_html_m59492c59.gifhello_html_m60fca5dc.gifhello_html_m60fca5dc.gifhello_html_m763e7452.gifhello_html_m763e7452.gifhello_html_m6f97c758.gifhello_html_m6f97c758.gifhello_html_m311f0002.gifhello_html_m311f0002.gif

НГМД

(флоппи)

Монитор


ПЗУ

(ROM)

НЖМД

(винчестер)

hello_html_3b8a6ff7.gifhello_html_5daa6e72.gifhello_html_3b8a6ff7.gif

hello_html_4641c3ba.gifhello_html_5daa6e72.gif

Принтер

hello_html_5daa6e72.gifhello_html_5daa6e72.gif

НМЛ или

стример


ЦП

(CPU)

hello_html_98608df.gifhello_html_41d1c1fc.gifhello_html_3b8a6ff7.gif

Клавиатура

hello_html_3b8a6ff7.gif

hello_html_5789e2f7.gifhello_html_41d1c1fc.gifhello_html_98608df.gif

CD/DVD – ROM/RW

CD/DVD – ROM/RW

Flash-накопитель


hello_html_4641c3ba.gifhello_html_5daa6e72.gif

«Мышь»

hello_html_5daa6e72.gif

ОЗУ

(RAM)

hello_html_3b8a6ff7.gifhello_html_3b8a6ff7.gifhello_html_3b8a6ff7.gif

Сканер

hello_html_5daa6e72.gif

ВЗУ

hello_html_3b8a6ff7.gifhello_html_m311f0002.gif

hello_html_m59492c59.gifhello_html_m311f0002.gif

УВВ

Рис. 12


Итак, что есть что:

ЦП - центральный процессор (CPU - Central Processor Unit).Важнейший элемент любого компьютера. Управляет работой всех остальных узлов и блоков. Производит арифметические и логические операции над данными различного типа. Имеет достаточно сложную структуру, рассмотрение которой выходит за рамки нашего приложения. Несколько слов о том, почему именно центральный. Во многих машинах (правильнее сказать - в большинстве машин) процессоров несколько. У них строгое разделение труда. Один занимается обслуживанием памяти, другой ответственный за ввод-вывод и т.д. Но всегда есть один центральный, который руководит, координирует деятельность всех остальных подобно тому, как директор предприятия руководит заместителями, отделами, службами, не выполняя их непосредственной работы.

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

Выглядит процессор как обыкновенная интегральная микросхема (ЧИП), размером со спичечный коробок или чуть больше. Во времена царства транзисторов процессор являл собой монстра в несколько метров (а то и десятков метров) в длину, ширину и высоту. Развитие технологии позволило «упрятать» этого монстра всего в одну микросхему. Тогда-то и появилось название микропроцессор. В настоящее же время приставка микро- утратила свое первоначальное значение и используется скорее по привычке. Иные (не микро-) остались лишь в воспоминаниях.

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

Характеризуются процессоры в первую очередь следующими параметрами:

- быстродействие. Количество выполняемых операций в секунду (от сотен тысяч до миллиардов);

- разрядность. Количество бит, обрабатываемых процессором за один прием (от 4 до 1024 и более);

- система команд. Набор элементарных машинных команд, которые «понимает» данный процессор. Язык, понятный процессору. Каждая команда предписывает процессору выполнить ту или иную операцию (сложить, переместить данные и т.д.). Всего таких команд у процессора - от нескольких десятков до нескольких тысяч. О том, как выглядят эти команды, мы уже говорили. Система команд определяет прежде всего программную совместимость компьютера;

- максимальный объем адресуемой памяти, - объем памяти, который можно установить в компьютере с данным процессором (от единиц байт до десятков гигабайт).

Мы не будем говорить об идентификаторах (названиях) процессоров, поскольку в современном мире можно запутаться, если начать просто-напросто перечислять марки и семейства процессоров и те компьютеры, на которые они устанавливались. Единственный комментарий по поводу «ядерности». Вообще-то, если не изменяет память, во всех компьютерах линейки Pentium (начиная от самого первого) процессор выполнен в виде набора так называемых процессорных секций. А компания AMD выпускает многоядерные процессоры лет 10, если не 15. Технология...

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

ОЗУ (RAM – Random Access Memory) - оперативное запоминающее устройство. Предназначено для временного хранения программ и данных, промежуточных результатов. Содержимое ОЗУ стирается при выключении питания компьютера.

ПЗУ (ROM – Read Only Memory) - постоянное запоминающее устройство. Не стирается при отключении питания. Предназначено для хранения программы загрузки, а также некоторых параметров, индивидуальных для данного ПК. В большинстве компьютеров ПЗУ содержит специальную программу BIOS (базовая системы ввода-вывода Basic Input/Output System).

УВВ (I/O-U) - устройства ввода - вывода (In/Out Unit). Это все то, что может быть подключено и обычно подключается к компьютеру: начиная от клавиатуры, монитора и сканера и заканчивая сложнейшей аппаратурой управления технологическими линиями. У них у всех есть одна общая характеристика: они либо передают информацию в компьютер (клавиатура) и называются при этом устройствами ввода, либо получают и как-то преобразуют информацию (монитор получает информацию и преобразует в светящиеся буквы и рисунки) или же делают то и другое (но не одновременно).

СШ - системная шина (System Bus) - это стандартизованный набор сигналов (проводов), с помощью которого соединены воедино отдельные узлы и блоки компьютера. Во многих компьютерах системная шина представляет собой совокупность трех видов информации (трех шин): шина данных, шина адреса и шина управления. Подробнее лучше расспросить об этом у специалистов-электронщиков (если интересно).

Теперь о ВЗУ – внешних запоминающих устройствах.

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

Информация на внешних носителях расположена не непрерывным потоком, а порциями, блоками (или иначе - кластерами).

Винчестер (Hard - disk). Эти накопители представляют собой несколько жестких дисков диаметром, как правило, около 3 дюймов, вращающихся на одной оси внутри герметично закрытого корпуса, из которого, как правило, выкачан воздух. Такая стерильность в сочетании со сверхточной механикой позволяет создавать накопители емкостью с типичными значениями: от 40 Гбайт и выше. Основным эксплуатируемым на всю катушку каждый день дисководом является винчестер. Гибкие диски применяются. Редко. И только для обмена программами, хранения в виде архивов всякого хлама и мусора, который выбросить жалко, ну и конечно, для организации так называемого ключевого доступа, когда на дискете хранится пароль в специальной форме для доступа, допусти, к банковским счетам. Только вот магнитная поверхность «флопиков» имеет нехорошее свойство быстро портиться, осыпаться. Накопитель на магнитной ленте (НМЛ, стример) – одно, если так можно выразиться, из ретро – устройств. Является устройством не произвольного, а последовательного доступа к данным. Применяется (до сих пор!) на больших вычислительных комплексах или для хранения большого объёма «хлама».

CD/DVD-ROM/RWустройства, соответственно, считывания (только!) и считывания – записи информации с лазерных дисков с помощью, естественно, источника лазерного излучения. Принцип записи очень похож на флоппи, но с помощью другого инструмента, со скоростью выше в десятки и сотни раз и с плотность, превосходящей в тысячи. Стандартный размер таких дисков – соответственно 650 Мбайт и 4,7 Гбайт.

Устройства ввода и вывода информации (УВВ).

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

- Способом печати (бывают ударные, барабанные, матричные, мозаичные, струйные, термопечатающие, лазерные...)

- Скоростью печати (измеряется в знаках за секунду). Самые шустрые - лазерные и барабанные.

- Возможностью переключения шрифтов.

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

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

- Мониторы. Эти устройства как правило путают с другими: дисплеями, терминалами, консолями и т.д. Внесем ясность, что есть что. Монитор - это устройство для отображения информации (не более!). Внешне он очень похож на обыкновенный телевизор. Клавиатура никакого отношения к монитору не имеет, разве что располагается обычно рядом. Словом «монитор» еще иногда называют специальную программу – супервизор (BIOS), размещаемую обычно в ПЗУ компьютера. Эта программа предназначена для элементарного взаимодействия с человеком до загрузки операционной системы.

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

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

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

- Клавиатура современного ПК имеет 101 (или 102) клавиши. Это достаточно сложное устройство, имеющее встроенный процессор.

- «Мышь» представляет собой коробочку округлой формы с проводом (либо без него) – хвостиком, подключенным к компьютеру. Сверху на корпусе расположены три (две, четыре) кнопки. При перемещении «мыши» по столу (лучше по специальному коврику), курсор на экране монитора перемещается в том же направлении и с аналогичной скоростью. Нажатие левой кнопки, как правило, аналогично нажатию ENTER (ВВОД) на Клавиатуре. С таким манипулятором очень удобно работать с системами меню. Перемещаем «мышку» по коврику, - курсор двигается по экрану от одного пункта меню к другому. Выбрав нужный, жмем на левую кнопку (обычно это называют кликом). Назначение остальных кнопок неоднозначно и зависит от конкретной программы.

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

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

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

- Модемы (от слов модулятор - демодулятор). Эти устройства предназначены для связи с удаленными объектами: компьютерами, FAX-ами, TELEX-ами и т.д. Типов модемов очень много (в зависимости от каналов связи, устройства и т.д.)

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

- Манипулятор джойстик внешне похож на рычаг управления самолетом. На рычаге расположена кнопка - гашетка. Джойстик применяется в игровых программах для управления чем-нибудь движущимся по экрану.


Файлы и директории

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

Одно из таких понятий - файл (File).

Студенты обычно записывают такое определение: файлом называется именованный набор данных, расположенный на внешнем носителе. Итак, набор данных на внешнем носителе. Что такое внешний носитель? Это все то, куда можно записать информацию: магнитный диск, лента, флешка, в конце концов - бумага. Да, да... Бумага - это тоже внешний носитель! Ведь на нее можно что-либо записать (ручкой ли, принтером ...) и она «запомнит». Так что, уважаемый читатель, вы в настоящее время занимаетесь считыванием файла и обработкой его содержимого, если говорить на языке компьютерных терминов.

Почему носитель внешний? Опять же условности (см. структурной схему). Наверное, потому, что он вне компьютера, вне его «мозга». Теперь о том, почему именованный? Потому, что он имеет имя. Имя – или, попросту, название файла можно сравнить с названием мелодии на пластинке или кассете. Правда, для имен файлов есть свои стандарты. Оно должно быть не очень длинным (как правило, не длиннее 6-9 символов, хотя, современные операционные системы таких ограничений не вводят), потом должна стоять точка, а потом еще, как правило, 3 символа. Последние 3 символа называют расширением имени. Расширение имени призвано хоть чуть-чуть дать представление о том, что расположено в файле, о его содержимом. Да и само имя файла должно вызывать хоть какие-то ассоциации.

Согласитесь, если у вас есть на флешке 2 песни с названиями, например otryv.rok и symphony.muz, то вы можете примерно предположить, что первая - это «убойный хит» в стиле рока, а вторая - нечто симфоническое (названия записаны опять-таки по компьютерным стандартам). Если вы сами создаете файл (набрали с помощью программы-редактора какой-нибудь текст и записываете на диск), старайтесь давать ему такое имя, чтобы потом по названию можно было примерно определить, что у него «внутри». Есть среди программистов и юзеров (так называют всех непрограммистов, работающих с компьютером, от англ. user – пользователь. И ничего обидного.) соглашения о расширении имени файла (иногда эти три буквы называют типом файла). Правда, таких стандартов много. В зависимости от того, с каким типом компьютеров работают эти самые программисты или юзеры. Приведём некоторые общие для всех стандартов расширения:

.txt - файл, содержащий какой-либо текст.

.doc - документация, описание той или иной программы, инструкция.

.sys - системный файл.

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

.bas - текст программы на языке программирования BASIC.

.pas – текст программы на языке программирования PASCAL.

.asm - текст программы на Ассемблере (.MAC в других системах).

.obj - файл, содержащий машинные коды программы (продукт работы компилятора).

.jpg (или .jpeg) - картинка, рисунок.

.bmp – растровое изображение

.bak - старый файл

- и много других. Как правило, каждой программе в операционной системе соответствует свой тип файла. Как, например, типу файла .psd соответствует пакет Adobe PhotoShop. Что касается непосредственно имени, то здесь полная свобода выбора и никаких соглашений. Правда, если с какой-либо программой рядышком пристроился файл с именем readme.txt или readme.me то можно предположить, что в нем какая-то важная информация о этой программе, которую нужно прочитать перед тем, как запускать программу. Обратите внимание, что во втором варианте имени расширение состоит только из двух символов. Но как правило - три.

Еще одно важное понятие – каталог, или директория, или папка. Представьте себе книгу без содержания или пластинку без наклеенного ярлыка. Крайне неудобно, не правда ли? Еще более печальное зрелище - диск без каталога. Если книжку без содержания можно быстренько пролистать, то «пробежаться» по диску быстренько вряд ли получится. Да и вряд ли там что-то можно будет понять. Разумеется, после этого все, что было на диске, остается лишь в воспоминаниях. Есть, правда, программы-доктора, которые пытаются «вылечить» диск. Когда «поломка» в файле - это еще ничего, а вот если в каталоге - это гораздо хуже. Вылечить бывает крайне сложно.

Надо ли упражняться в определениях и выдумывать, как бы это правильнее сказать, что такое каталог? Если вам это необходимо - тренируйтесь. А пока мы договоримся, что каталог - это каталог (она же директория, она же папка). Лучше поговорим о том, как он «устроен» и что в нем записано. При этом будем проводить аналогию с каталогом (содержанием) книги.

Итак, откроем директорию книги (привыкайте, привыкайте...). Жирным шрифтом выделены названия частей (разделов). Назовем их подкаталогами. (Каталогами второго уровня. В каждой части есть главы (каталоги третьего уровня). В каждой главе - параграфы (следующий уровень) и т.д. Такую же структуру имеют и компьютерные каталоги, например, каталоги диска. (Из устройств, имеющих каталоги, что-то ничего, кроме дисков, в голову не приходит. Да, наверное, и не к чему...).