Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Глоссарий.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
526.34 Кб
Скачать

1) Переменная типа "массив" (или просто массив) является совокупностью компонентов одного и того же типа, называемых элементами массива.

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

3) Число элементов массива определяется при его описании и в дальнейшем не меняется – массив является статическим.

Определение типа "массив" имеет вид:

type

TA = array[TIndex] of TE;

здесь TA – имя типа, TIndex – тип индекса, TE – тип элементов массива. TIndex фактически определяет число элементов массива. В качестве TIndex обычно указывается тип-диапазон. TE может быть любым простым или сложным типом.

Элемент массива обозначается как A[I], где A – переменная типа TA (то есть типа "массив"), а I – индексное выражение или индекс, тип которого – TIndex. Сам элемент A[I] – это переменная типа TE, его часто также называют индексированной переменной.

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

type

TA = array[T1, T2,, TD] of TE;

здесь D называется размерностью массива, а каждое TK задаёт тип индекса по K-му измерению массива. Элемент многомерного массива обозначается как A[I1, I2,, ID], где IK – это индексное выражение типа TK по K-му измерению. Таким образом, для матрицы A с двумя измерениями

var

A: array[K..M, L..N] of Real;

элементы A[I, J] будут переменными вещественного типа, I – это номер строки, J – это номер столбца; индексы должны удовлетворять неравенствам: K_≤_I_≤_M и L_≤_J_≤_N.

Типизированные константы-массивы позволяют задать начальные значения, например:

const

A: array[0..3] of Integer = (10, 15, 20, 25);

B: array[0..1, 1..3] of Byte = ((5, 6, 7), (2, 4, 8));

здесь заданы начальные значения для вектора A и матрицы B из четырёх и шести элементов соответственно. В матрице B – две строки по три элемента.

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

Лекция 8. Файлы

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

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

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

3) Число элементов файла может изменяться и ограничено только ёмкостью внешнего носителя. Файл может быть пуст.

В зависимости от способа описания и внутренней структуры различают три вида файлов: текстовые, типизированные, нетипизированные (бестиповые). Соответствующие переменные описываются следующим образом:

var

F: Text; {текстовой файл}

G: file of TE; {типизированный файл}

H: file; {нетипизированный файл}

здесь TE – имя типа элементов файла. Тип элементов может быть любым, но только не файловым или объектным. По внутренней структуре текстовые файлы Турбо Паскаля являются текстовыми файлами в DOS и Windows, типизированные и нетипизированные – двоичными (нетекстовыми) файлами.

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

1) Получение доступа (подсоединение) к файлу или связывание с файлом (процедура Assign).

2) Открытие файла (процедуры Reset, Rewrite, Append).

3) Закрытие файла (процедура Close).

4) Ввод из файла (процедуры Read, ReadLn, BlockRead).

5) Вывод в файл (процедуры Write, WriteLn, BlockWrite).

6) Позиционирование в файле (функции Eof, SeekEof, FileSize, FilePos, процедура Seek).

При получении доступа к файлу происходит связывание файловой переменной с конкретным файлом или устройством. Далее эта переменная будет посредником между файлом и программой – имя файла больше указываться не будет. При подсоединении надо учитывать особенности работающей операционной системы, в частности, в DOS и Windows имена con, prn, lpt1, lpt2, …, aux, com1, com2, …, nul задают логические устройства и именами файлов быть не могут.

Открытие файла означает указание для него направления передачи данных: Reset – чтение (ввод с самого начала файла), Rewrite – запись (разрушающая существующий файл), Append – дозапись (вывод в конец существующего файла, то есть его расширение). Здесь имеется тонкость: Append применима только к текстовым файлам, для дозаписи в типизированные и нетипизированные файлы их необходимо открывать с помощью Reset, которая, на самом деле, открывает и на чтение, и на запись.

В Турбо Паскале две стандартные переменные типа Text по умолчанию подсоединены к устройствам ввода/вывода и открыты: Input – для ввода с клавиатуры, Output – для вывода на экран.

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

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

Текстовой файл – это набор строк символов клавиатуры переменной длины. Его легко создавать, просматривать, корректировать и вне программы – с помощью простого редактора текста. Действие текстовых инструкций ввода (Read, ReadLn) и вывода (Write, WriteLn) аналогично вводу с клавиатуры и выводу на экран.

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

Нетипизированный файл – это набор записей (блоков) одинаковой длины, которая устанавливается при открытии файла. Для него контроль типов не производится – это делает его совместимым с другими видами файлов и позволяет организовать высокоскоростной обмен данными между переменной-буфером в программе и диском. Нетипизированные инструкции BlockRead и BlockWrite осуществляют ввод/вывод записями.

Функция Eof(F) возвращает True, если следующий за текущей позицией элемент файла уже есть конец файла. Это позволяет задать типичный цикл для обработки любого файла F:

while not Eof(F) do

begin

ReadLn(F, …); {или Read(F, …); или BlockRead(F, …);}

…{обработка введённых данных}…

end

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

Функции FileSize, FilePos и процедура Seek применяются только для типизированных и бестиповых файлов, но не для текстовых. FileSize возвращает количество элементов или записей в файле. FilePos возвращает номер элемента или записи в файле, следующей за текущей позицией (нумерация начинается с 0). Seek – устанавливает в файле новую текущую позицию, обеспечивая тем самым прямой доступ к элементам файла.

Лекция 9. Записи

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

1) Переменная типа "множество" (или просто множество) является совокупностью компонентов одного и того же типа, называемых элементами множества.

2) Повторение одного и того же элемента множества равносильно однократному вхождению этого элемента. Порядок размещения элементов отсутствует, поэтому для множества возможна только проверка принадлежности ему какого-то значения, а не доступ к элементу.

3) Число элементов (мощность) множества может изменяться в пределах от 0 до 256. Множество, не содержащее элементов, называется пустым.

Определение типа "множество" имеет вид:

type

TS = set of TE;

здесь TS – имя типа, TE – тип элементов множества. TE может быть только любым порядковым однобайтовым типом с диапазоном значений, не выходящим за пределы от 0 до 255.

Для задания множества используется так называемый конструктор множества: [E1, E2,, EN], где EK – это константа или диапазон констант типа TE. Любой элемент EK может быть даже выражением или диапазоном выражений типа TE. Конструктор [ ] обозначает пустое множество.

Типизированная константа-множество – задаётся константным конструктором, например:

const

S: set of Char = ['!', '?', '0'..'9', 'A'..'Z'];

Совместимые по типу элементов TE множества можно присваивать друг другу, но переменную-множество не разрешается вводить с клавиатуры и выводить на экран – это можно делать только поэлементно. Операции, разрешённые над множествами, аналогичны таким же математическим операциям. Их обозначения: *, +, -, =, <>, <=, >=, in. Также имеются стандартные высокоэффективные процедуры Include и Exclude.

Тип "запись" характеризуется следующими особенностями:

1) Переменная типа "запись" (или просто запись) является совокупностью компонентов, возможно, различных типов, называемых полями записи.

2) Каждое поле записи обозначается именем, и поэтому к нему имеется прямой доступ.

3) Количество полей записи определяется при её описании и в дальнейшем не меняется.

Определение типа "запись" имеет вид:

type

TZ = record

P1; P2;; PN

end;

здесь TZ – имя типа, P1; P2;; PN – описания полей записи, причём их типы могут быть любыми простыми или сложными.

Для доступа к полю записи используется составное (уточнённое) имя Z.V, где Z – имя переменной типа TZ (то есть типа "запись"), а V – имя поля. Z.V является переменной того типа, который задан при описании поля V.

Типизированная константа-запись позволяет задать начальные значения, например:

const

Z: record

D, M: Byte;

Y: Word

end = (D: 18; M: 3; Y: 1871);

здесь заданы значения для полей записи Z.

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

В записях можно применять так называемую вариантную часть (case of), которая позволяет "накладывать" друг на друга разнотипные значения.

Лекция 10. Подпрограммы-процедуры.

Локальность имён.

Параметры процедуры

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

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

procedure Имя_процедуры(S);

Разделы описаний

begin

Основной блок процедуры

end;

Здесь S обозначает список описаний формальных параметров, который может отсутствовать – тогда опускаются и скобки. Разделы описаний и основной блок аналогичны таким же разделам главной программы – нельзя указывать только раздел uses. Таким образом, как вспомогательный алгоритм, – процедура является программой в миниатюре, только заголовок процедуры является обязательным. Поэтому самая короткая процедура выглядит так:

procedure Имя_процедуры;

begin

end;

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

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

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

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

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

Общий вид списка описаний формальных параметров: R1; R2;; RL, где каждый элемент RJ – это группа однотипных формальных параметров. Каждая группа может быть представлена в одном из трёх вариантов:

V1, V2,, VK: TJ, или var V1, V2,, VK: TJ, или var V1, V2,, VK

которые задают вид передачи параметров: в первом случае – по значению, во втором и третьем случае – по ссылке. Здесь VI обозначают имена формальных параметров, а TJимя их типа. Формальные параметры являются переменными в процедуре и могут использоваться только в ней, то есть их область видимости простирается до её конца (end;). В первом случае они называются параметрами-значениями, во втором случае – параметрами-переменными, в третьем случае – бестиповыми (нетипизированными) параметрами-переменными.

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

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

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

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

Лекция 11. Подпрограммы-функции.

Побочный эффект

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

function Имя_функции(S): TF;

Разделы описаний

begin

Основной блок функции

end;

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

Имя_функции := выражение_типа_TF

Таким образом, самая короткая функция выглядит так:

function Имя_функции: TF;

begin

Имя_функции := выражение_типа_TF

end;

Обращение к такой функции состоит только из одного имени функции.

При указании имени функции в каких-либо выражениях в её основном блоке в качестве простой переменной – это воспринимается как попытка рекурсивного вызова (смотри далее Тему "Рекурсия").

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

function F(var X: Integer): Integer;

begin

X := X + 1;

F := X*X*X

end; {F}

var

A: Integer;

begin

A := 1;

WriteLn(F(A) + A)

end.

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

Лекция 12. Рекурсия.

Модули.

Процедурный (функциональный) тип – это множество однотипных либо процедур, либо функций. Определение процедурного типа имеет вид:

type

TP1 = procedure (S);

а определение функционального типа:

TF1 = function (S): TF;

здесь TP1, TF1 – имя того или иного типа, S – обычный список описаний формальных параметров, который может отсутствовать – тогда опускаются и скобки. TF – имя типа значения функции. Принадлежность реальной процедуры или функции определённому типу устанавливается по типам в списке S, а для функций – ещё и по типу TF. Имена формальных параметров могут не совпадать с именами в определении процедурного типа – обязательным является совпадение их типов.

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

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

Для этого после её заголовка ставится стандартная директива far; (точка с запятой – обязательно!). Возможен и другой способ определения дальней модели памяти уже для целой группы подпрограмм: перед определением первой подпрограммы ставится директива компилятору {$F+} (включить компиляцию дальней модели). После конца текста последней подпрограммы можно, наоборот – поставить директиву {$F-} (возобновить компиляцию ближней модели).

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

Рекурсия – это обращение к подпрограмме из тела самой этой же подпрограммы. В языке Паскаль рекурсия разрешена. Рекурсивный алгоритм выглядит изящней при решении математических задач, сама формулировка которых имеет рекурсивный характер. В этом случае задача может быть разложена на совокупность подзадач того же типа, но меньшей размерности. Классический пример – вычисление факториала: N! = N*(N - 1)! для N > 0 и 0! = 1! = 1. Здесь исходная задача размерности N разложена на произведение N и решения подзадачи размерности N - 1.

Методика создания рекурсивных алгоритмов содержит три этапа:

1) параметризация задачи;

2) поиск тривиального случая и его решение;

3) декомпозиция общего случая.

При каждом вызове подпрограммы её локальные переменные и адрес возврата из подпрограммы заносятся в специальную область памяти, называемую стеком. По окончании выполнения подпрограммы и выходе из неё эта область стека освобождается. В случае рекурсивного вызова локальная память не освобождается, а впадает в "зимнюю спячку", в стеке выделяется новая область под новые значения локальных переменных и новый адрес возврата. Доступ к стеку производится по принципу "последним пришёл – первым ушёл" (Last In, First Out – LIFO), поэтому при выходе из подпрограммы возврат производится в точку её последнего вызова. Таким образом, организация стека обеспечивает реализацию рекурсии.

Рекурсия может быть косвенной: подпрограмма A вызывает подпрограмму B, а та, в свою очередь, вызывает A. В этом случае нарушается принцип сильной типизации языка Паскаль: подпрограмма должна быть определена до обращения к ней. В такой ситуации выносят заголовок подпрограммы, определённой ниже, перед первой подпрограммой, а вместо тела, после заголовка, указывают стандартную директиву forward; (точка с запятой – обязательно!)

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

Текст модуля размещается в отдельном .pas файле, например gauss.pas. Результатом компиляции модуля является двоичный файл с расширением .tpu (Turbo Pascal Unit), например gauss.tpu. Чтобы подключить модуль к программе или к другому модулю, необходимо указать имя модуля в соответствующем разделе uses.

Общий вид модуля:

unit Имя_модуля;

interface

Интерфейсная ("общедоступная") часть

implementation

Исполняемая часть

begin

Инициализирующая часть

end.

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

unit Имя_модуля;

interface

implementation

end.

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

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

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

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

Имеются три вида компиляции многомодульной программы. Все они задаются в меню Compile интегрированной среды: Compile (Alt+F9) – компиляция программы или модуля в текущем окне редактора; Make (F9) – выборочно перекомпилируются все модули данного проекта (комплекса), в текст которых внесены изменения; Build – перекомпилируются все модули данного проекта, независимо от того вносились в них изменения или нет.

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

unit A;

interface

uses B;

implementation

end.

unit B;

interface

uses A;

implementation

end.

Это ограничение можно обойти, если "спрятать" раздел uses в исполняемую часть модуля:

unit A;

interface

implementation

uses B;

end.

unit B;

interface

implementation

uses A;

end.

Дело в том, что в Турбо Паскале разрешаются ссылки на частично откомпилированные модули; то есть компиляция проходит в два этапа: сначала компилируется интерфейсная часть, а затем – всё остальное, после слова implementation.

Некоторые стандартные модули Турбо Паскаля входят в состав библиотеки turbo.tpl (System, Dos, Crt, Printer, Overlay), а другие – хранятся в отдельных .tpu файлах (Graph, Strings и т. д.). При этом модуль System (содержит подпрограммы ввода/вывода, обработки строк, математические и другие функции) всегда подключается автоматически, а остальные, при необходимости, – указываются в разделе uses

Лекция 13. Ссылочный тип.

Динамические переменные

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

var

P: ^T; {типизированный указатель}

Q: Pointer; {нетипизированный указатель}

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

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

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

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

PA: ^Integer;

результатом разыменования будет переменная с именем PA^ типа Integer. Конечно, сама переменная-указатель PA к этому моменту должна быть определена, то есть в ней должен находиться какой-то адрес, иначе результат разыменования будет не определён.

Операции, связанные со ссылочным типом:

@ – взятие адреса переменной любого типа (например @A), тип результата – Pointer.

Операции = (равно) и <> (не равно) применимы к однотипным указателям либо к нетипизированному и любому типизированному указателю.

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

Захват и освобождение памяти производится обращениями к стандартным процедурам New(P) и Dispose(P) или процедурам GetMem(Q, Size) и FreeMem(Q, Size) соответственно. Здесь P – всегда типизированный указатель, Q может быть любым указателем, но обычно используется нетипизированный указатель. Size – это целочисленное выражение, указывающее размер захватываемой или освобождаемой памяти в байтах. Хороший стиль программирования предполагает освобождение такого же объёма динамической памяти, сколько было захвачено ранее, – когда в этой памяти отпадает необходимость. Это позволит использовать её для других переменных.

При выполнении процедуры New:

1) захватывается область динамической памяти, размер которой равен числу байтов, занимаемых переменной P^;

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

При выполнении процедуры Dispose:

1) освобождается область динамической памяти, на которую указывает P и размер которой равен числу байтов, занимаемых переменной P^;

2) значение переменной P после этого будет не определено, повторное освобождение этой же области памяти запрещено и считается ошибкой.

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

var

PA, PB: ^Real;

begin

New(PA); New(PB);

PA^ := 4; PB^ := 8;

PA := PB; {1}

Dispose(PB); {2}

Dispose(PA); {3}

В этом фрагменте до строки {1} ошибок нет. В строке {1} безвозвратно теряется связь с динамической переменной, адрес которой находился в PA. Другая динамическая переменная, кроме имени PB^, приобретает ещё одно – PA^. В строке {2} эта переменная удаляется из памяти, но как раз в PA и остаётся "висячая" ссылка. Попытка освободить уже освобождённую динамическую память в строке {3} является ошибочной.

Для определения размера свободного пространства в куче используются две стандартные функции – MaxAvail и MemAvail. Для определения размера в байтах внутримашинного представления произвольной переменной можно применять универсальную функцию SizeOf(X), где X – имя переменной или даже имя типа.

Лекция 14. Алгоритмы поиска и выборки.

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

Объективным критерием, позволяющим оценить эффективность того или иного алгоритма, является, так называемый, порядок алгоритма. Порядком алгоритма называется функция O(N), позволяющая оценить зависимость времени выполнения алгоритма от объема перерабатываемых данных (N - количество элементов в массиве или таблице). Эффективность алгоритма тем выше, чем меньше время его выполнения зависит от объема данных. Большинство алгоритмов с точки зрения порядка сводятся к трем основным типам:

  • степенные - O(Na);

  • линейные - O(N);

  • логарифмические - O(loga(N)).

Эффективность степенных алгоритмов обычно считается плохой, линейных - удовлетворительной, логарифмических - хорошей.

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

В последующем изложении все описания алгоритмов даны для работы с таблицей, состоящей из записей R[1], R[2], ..., R[N] с ключами K[1], K[2], ..., K[N]. Во всех случаях N – количество элементов таблицы. Программные примеры для сокращения их объема работают с массивами целых чисел. Такой массив можно рассматривать как вырожденный случай таблицы, каждая запись которой состоит из единственного поля, которое является также и ключом. Во всех программных примерах следует считать уже определенными:

  1. константу N- целое положительное число, число элементов в массиве;

  2. константу EMPTY - целое число, признак "пусто" (EMPTY=-1);

  3. тип - type SEQ = array[1..N] of integer; сортируемые последовательности.

Последовательный или линейный поиск

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

Для последовательного поиска в среднем требуется (N+1)/2 сравнений. Таким образом, порядок алгоритма - линейный - O(N).

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

{===== Программный пример 1 =====}

Function LinSearch( a : SEQ; key : integer) : integer;

var i : integer;

for i:=1 to N do { перебор эл-тов массива }

if a[i]=key then begin {ключ найден - возврат индекса }

LinSearch:=i; Exit; end;

LinSearch:=EMPTY; {просмотрен весь массив, но ключ не найден }

end;

Бинарный поиск

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

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

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

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

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

{===== Программный пример 2 =====}

Function BinSearch(a : SEQ; key : integer) : integer;

Var b, e, i : integer;

begin

b:=1; e:=N; { начальные значения границ }

while b<=e do { цикл, пока интервал поиска не сузится до 0 }

begin i:=(b+e) div 2; {середина интервала }

if a[i]=key then

begin BinSearch:=i; Exit; {ключ найден - возврат индекса }

end else

if a[i]<key then b:=i+1 { поиск в правом подинтервале }

else e:=i-1; { поиск в левом подинтервале }

end; BinSearch:=EMPTY; { ключ не найден }

end;

Трассировка бинарного поиска ключа 275 в исходной последовательности:

75, 151, 203, 275, 318, 489, 524, 519, 647, 777 представлена в таблице 1.

Таблица 1

Итерация

b

e

i

K[i]

1

2

3

4

1

1

3

4

10

4

4

4

5

2

3

4

318

151

203

275

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

Рекурсивная процедура бинарного поиска представлена в программном примере 3.6. Для выполнения поиска необходимо при вызове процедуры задать значения ее формальных параметров b и е - 1 и N соответственно, где b, e - граничные индексы области поиска.

{===== Программный пример 2 =====}

Function BinSearch( a: SEQ; key, b, e : integer) : integer;

Var i : integer;

begin

if b>e then BinSearch:=EMPTY { проверка ширины интервала }

else begin

i:=(b+e) div 2; { середина интервала }

if a[i]=key then BinSearch:=I {ключ найден, возврат индекса }

else if a[i]<key then { поиск в правом подинтервале }

BinSearch:=BinSearch(a,key,i+1,e)

else { поиск в левом подинтервале }

BinSearch:=BinSearch(a,key,b,i-1);

end; end;

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

Лекция 15. Сортировка

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

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

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

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

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

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

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

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

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

3). Стратегия распределения. Входное множество разбивается на ряд подмножеств (возможно, меньшего объема) и сортировка ведется внутри каждого такого подмножества.

4). Стратегия слияния. Выходное множество получается путем слияния маленьких упорядоченных подмножеств.

Далее приводится обзор (далеко не полный) методов сортировки, сгруппированных по стратегиям, применяемым в их алгоритмах.

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

Сортировки выборкой

Данный метод реализует практически "дословно" сформулированную выше стратегию выборки. Порядок алгоритма простой выборки - O(N2). Количество пересылок - N.

Алгоритм сортировки простой выборкой иллюстрируется программным примером 3.

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

{===== Программный пример 3 =====}

Procedure Sort( a : SEQ; var b : SEQ);

Var i, j, m : integer;

c: array[1..N] of boolean; {состояние эл-тов вх.множества}

begin

for i:=1 to N do c[i]:=true; { сброс отметок }

for i:=1 to N do {поиск 1-го невыбранного эл. во вх.множестве}

begin j:=1;

while not c[j] do j:=j+1;

m:=j; { поиск минимального элемента }

for j:=2 to N do

if c[j] and (a[j]<a[m]) then m:=j;

b[i]:=a[m]; { запись в выходное множество }

c[m]:=false; { во входное множество - "пусто"}

end; end;

Обменная сортировка простой выборкой

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

Обменная сортировка простой выборкой показана в программном примере 4. Процедура имеет только один параметр - сортируемый массив.

{===== Программный пример 4 =====}

Procedure Sort(var a : SEQ);

Var x, i, j, m : integer;

begin

for i:=1 to N-1 do { перебор элементов выходного множества}

{ входное множество - [i:N]; выходное - [1:i-1] }

begin m:=i;

for j:=i+1 to N do { поиск минимума во входном множестве }

if (a[j]<a[m]) then m:=j;

{ обмен 1-го элемента вх. множества с минимальным }

if i<>m then begin

x:=a[i]; a[i]:=a[m]; a[m]:=x;

end;end; end;

Результаты трассировки программного примера 4 представлены в таблице 2. Двоеточием показана граница между входным и выходным множествами.

Очевидно, что обменный вариант обеспечивает экономию памяти. Очевидно также, что здесь не возникает проблемы "пустого" значения. Общее число сравнений уменьшается вдвое - N*(N-1)/2, но порядок алгоритма остается степенным - O(N2). Количество перестановок N-1, но перестановка, по-видимому, вдвое более времяемкая операция, чем пересылка в предыдущем алгоритме.

Таблица 2

Шаг

Содержимое массива а

исходный

1

2

3

4

5

6

7

8

9

результат

: 242 447 286 708 24 11 192 860 937 561

11:447 286 708 24 242 192 860 937 561

11 24:286 708 447 242 192 860 937 561

11 24 192:708 447 242 286 860 937 561

11 24 192 242:447 708 286 860 937 561

11 24 192 242 286:708 447 860 937 561

11 24 192 242 286 447:708 860 937 561

11 24 192 242 286 447 561:860 937 708

11 24 192 242 286 447 561 708:937 860

11 24 192 242 286 447 561 708 860:937

11 24 192 242 286 447 561 708 860 937

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

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

Министерство образования и науки Республики Казахстан

Евразийский национальный университет им. Л.Н. Гумилева

Кафедра Информационных систем

(наименование кафедры)