Основы программирования — первый семестр 08-09

Материал из Вики ИТ мехмата ЮФУ
Перейти к: навигация, поиск

К основной странице курса

Содержание

Алгоритм

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

С алгоритмом всегда связан исполнитель алгоритма - устройство, имеющее систему команд.

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

Свойства алгоритма

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

Пример алгоритма.

Дано: x, y, z.
Найти max

Алгоритм 1. (словесное описание)

Если x>y и x>z, то максимум — это x
Если y>x и y>z, то максимум — это y
Если z>x и z>y, то максимум — это z

Алгоритм 2. (псевдокод)

max := x
Если y<max то max := y
Если z<max то max := z

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


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

Способы описания алгоритмов

1. Словесный

2. Псевдокод

3. Блок-схемы

Пример. А.2., представленный блок-схемой.

Max flowchart.png

4. Язык программирования (ЯП)

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

Основные характеристики алгоритма

  • правильность работы
  • скорость выполнения
  • объем занимаемой памяти
  • сложность написания
  • возможность распараллеливания

Основные характеристики программы

Те же, что и у алгоритма, а также:

  • понятность при чтении
  • модифицируемость (легкость изменения кода)
  • масштабируемость (возможность изменения кода для решения родственной или более общей задачи)
  • безопасность

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

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

(самостоятельно на форуме)

Язык программирования PascalABC.NET

Правила записи программ на PascalABC.NET

Шпаргалка

Пример программы, вычисляющей длину и площадь круга

program First; // заголовок программы – необязательная строка
{ Программа вычисления длины окружности и площади круга 
  Автор: Михалкович С.С. Дата написания: 2.09.08 }
const Pi = 3.14;
var
  r: real;   // входные данные - радиус круга
  S,C: real; (* выходные данные - площадь круга и длина окружности *)
begin
  write('Введите радиус окружности: ');
  readln(r);
  S := Pi*r*r;
  C := 2*Pi*r;
  writeln('Длина окружности равна ',С);
  writeln('Площадь круга равна ',S);
end.

Структура программы на языке Паскаль

Заголовок программы
Раздел описаний (описываются все используемые в программе переменные и определяются их типы)
begin
  операторы (разделяются ;)
end.

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

Система программирования PascalABC.NET

Как скачать, инсталлировать. (сайт системы программирования PascalABC.NET)

Общая характеристика PascalABC.NET.

Отличия языка PascalABC.NET от обычного языка Паскаль.

Компиляторы и интерпретаторы

Схема компиляции в машинный код

Схема компиляции в промежуточный код. JIT-компиляция

Ошибки времени компиляции (синтаксические и семантические) и ошибки времени выполнения

Синтаксис - правила записи конструкций, семантика - смысл конструкций.

Синтаксис и семантика ЯП

// В программе 2013-14 гг. этот пункт будет сокращен и размыт по следующим темам когда будут вводиться основные операторы языка.

Определения

Синтаксис — формальные правила описания конструкций ЯП.

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

Способы описания синтаксиса

  • БНФ (Бэкуса-Наура формы, 1960, Алгол-60).

Примеры.

<цифра> ::= 0|1|2|3|4|5|6|7|8|9
<идентификатор> ::= <буква> | <идентификатор><буква> | <идентификатор><цифра>
<список идентификаторов> ::= <идентификатор> | <идентификатор> , <список идентификаторов>

0, 1, ... 9

называют терминалами (лексемами) — это "конечные символы", т.е. по умолчанию известные в ЯП.

<цифра>

так называемый нетерминал (нетерминальный символ).
Он определяется через терминалы, другие нетерминалы и самого себя. Причем в последнем случае правило задания нетерминала называется рекурсивным (как определение нетерминала <идентификатор>)
  • РБНФ (Расширенные БНФ)
[] — 0 или 1 повторение.
{} — 0 и более повторений

Пример.

<идентификатор> ::= <буква> {<буква> | <цифра>}
  • Синтаксические диаграммы (Вирт, Паскаль)

Грамматика языка — совокупность всех синтаксических правил данного ЯП, обычно заданных в форме БНФ.

Грамматика не учитывает все виды ошибок, в ЯП формулируются дополнительные семантические правила.

Замечание 1. Способы 1-3 эквивалентны.

Замечание 2. Синтаксис определяет лексемы языка.

Лексемы Паскаля

  1. спецсимволы: := += *
  2. ключевые слова (begin, end, if, for)
  3. идентификаторы (a, b1)
  4. константы (2, 'ABC', #5)
  5. комментарии (3 вида)
{...}
(*...*)
//...

Переменные и их описание

Основные сведения

Переменная — это ячейка памяти компьютера, имеющая имя и тип.

Тип определяет размер переменной и множество принимаемых ею значений.

В языке Pascal любая переменная перед использованием должна быть описана.
Обычно переменные описываются в разделе описаний.

Синтаксис в виде РБНФ

<программа> ::= [program <имя>;] 
               <раздел описаний> 
               begin
                 <операторы>
               end.
<операторы> ::= <оператор>{; <оператор>}
<раздел описаний> ::= {<секция раздела описаний>}
<секция раздела описаний> ::= <секция описания переменных> | <с.о. констант> | <с.о. типов> | <с.о. подпрограмм>...

Пример секции описания переменных.

var
  a,b: real;
  i: integer;
<секция описания переменных> ::= var<подсекция>{< подсекция>}
<подсекция> ::= <список имен>: <тип>;
<список имен> ::= <имя>{,<имя>}
<тип> ::= <имя>

Внутриблочные переменные

В PascalABC.NET возможно внутриблочное описание переменных:

begin
  var i,j: integer;
  var r: real := 5.2;
  var Pi := 3.14;

В последнем случае происходит автоопределение типов.

Основные типы

  • Целые
integer (4 байта)
int64 (8)
byte (1)
  • Вещественные
real (8)
  • Символьные
char (2 байта — Unicode)
  • Строковые
string
string[200]
shortstring = string[255]
  • Логический
boolean (1) [True False]К основной странице курса

Основные операторы

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

Синтаксис

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

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

a := (3 + 5) * 8; 
b := a + 2;

Семанитика

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

Ограничение. Тип выражения должен быть совместим по присваиванию с переменной.
Например:

  • одинаковые типы совместимы.
  • выражение типа integer можно присвоить переменной типа real. Обратное неверно.

Операторы присваивания += и *=

Пример.

d += 1; //прибавить 1 к d
d *= 2; //умножить d на 2

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

Пример 1. Перемена местами двух значений. Дано: x, y;

var x, y: integer;
begin
  read(x,y);
  var v := x;
  x := y;
  y := v;
  writeln(x, ' ', y);
end.

Это стандартное решение. В PascalABC.NET на основе этого алгоритма определена стандартная процедура Swap(x, y).

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

var x, y: integer;
begin
  read(x, y);
  x := x + y;
  y := x - y;
  x := x - y;
  writeln (x, ' ', y);
end.

Пример 2. Использование промежуточных переменных в вычислениях Дано: x: real; Найти: x15;

Решение 1.

y := x * x;
z := y * y;
t := z * z;
p := t * z;
q := p * x * y;

Решение 2.

y := x * x;
z := y * x;
t := z * y;
p := t * t * t;

Решение 3.

y := x * x;
x := x * y * y;
t := x * x * x;

Заметим, что в первом решении используется 6 операций умножения, в во 2м и 3м — 5. Возникает задача: найти xn за минимальное число умножений.
Об этом читай тему.

Оператор ввода

Синтаксис

read (<список переменных>) | readln (<список переменных>)

Семантика

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

Имеются также стандартные функции ReadInteger, ReadReal, ReadlnInteger, ReadlnReal:

var n := ReadInteger;
var r := ReadlnReal;

С процедурой ввода связан ряд ошибок времени выполнения (например, если переменная используется в качестве делителя, и вводится 0, или, если должно быть получено целое число, а вводится 'ABC'). Эти ошибки нужно уметь обрабатывать.

Оператор try/except и обработка ошибок ввода

Операторы, которые могут получать ошибку, заключаются специальный охранный блок - оператор try.

Синтаксис

try
  ...
  readln(a);
  ...
except
  <обработка ошибки>
end;
<продолжение работы>

Семантика

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

Если ошибки не происходит, то выполняются все операторы в блоке try, блок except не выполняется, и программа продолжает работу.

Оператор вывода

Синтаксис

write(<список выражений>) | writeln(<список выражений>)

Семантика

Выражения в списке вычисляются, и их значения выводятся на экран.
В случае writeln после вывода осуществляется переход на новую строку.

Форматы вывода

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

Вывод с помощью write[ln]Format

writelnFormat('<форматная строка>', <список выражений>)

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

writelnFormat('{0} * {1} = {2}', a, b, a * b)

Будет выведено:

a * b = a * b

В форматной строке тоже можно использовать формат вывода.
{0, 10}: 10 — это ширина поля вывода
{0, 10:f3}: 3 — это количество знаков в дробной части для вещественного числа (показывает это спецификатор f).
{0, 10:e3} — экспоненциальный формат.

Арифметические выражения

Основные сведения

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

В арифметических выражениях если a и b — одного типа, то и a op b принадлежит к тому же типу. Исключением является операция "/":

a / b — вещественное.

Если a и b принадлежат к различным типам, то выражение принадлежит к "старшему" типу.
Например:

byte < integer < int64
integer < real

Стандартные функции

В арифметические выражения могут входить стандартные функции:

exp(x)
ln(x)
abs(x)  // модуль x
sin(x)
cos(x)
sqr(x)  // квадрат x
sqrt(x) // корень из x 
round(x) - целое, полученное в результате округления вещественного x
trunc(x) - целое, полученное в результате отбрасывания дробной части у вещественного x
min(x,y) 
max(x,y) 
power(x,y)// x в степени y
random(n)// псевдослучайное целое число от 0 до n-1
random(a,b)// псевдослучайное целое число от a до b

Порядок выполнения операций в арифметических выражениях

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

Операции div и mod для целых

x div y = x / y, округленное до ближайшего целого по направлению к нулю. Это результат от целочисленного деления.
x mod y = x - (x div y) * y. Это остаток от целочисленного деления.

Пример использования
Целочисленные операции часто применяются для определения четности числа:

x mod 2 = 0    <->   x — четное
x mod 2 <> 0   <->   x — нечетное

Логические выражения

Основные сведения

Выражение назывется логическим, если оно имеет тип boolean.
Пример.

x < 0
a >= b
a <> 3

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

 (бинарные)     (унарные)
  a and b         not a
  a or b
  a xor b

Таблицы истинности логических операций

a | b | a and b | a or b | a xor b 

T | T |    T    |   T    |    F
T | F |    F    |   T    |    T
F | T |    F    |   T    |    T
F | F |    F    |   F    |    F

Сокращение вычислений логических выражений

Большинство современных компиляторов, в т.ч. PascalABC.NET производит сокращенное вычисление логических выражений.
Это означает, что в выражении

a and b

если a — ложно, то b не вычисляется, а в

a or b

если a — истинно, b не вычисляется.

Это очень полезно при вычислении таких выражений, как, например,

(y <> 0) and (x / y > 0)

Логически здесь все верно, однако, если бы не использовалось сокращенное вычисление, в случае равенства нулю y'а возникала бы ошибка деления на ноль.

Логические переменные

Можно описывать логические переменные (тип boolean). Им можно присваивать логические выражения.
Эти переменные принимают одно из двух возможных значений:

true (истина)
false (ложь)

Пример использования логических переменных
Дано: прямоугольник со сторонами, параллельными осям координат, задан координатами абсцисс вертикальных сторон (x1, x2) и ординатами горизонтальных (y1, y2); точка M( x, y );
Найти: находится ли точка внутри прямоугольника, снаружи, или лежит на границе;

var inside, outside, bound: boolean;
begin
  inside := (x > x1) and (x < x2) and (y > y1) and (y < y2);
  outside := (x < x1) or (x > x2) or (y < y1) or (y > y2);
  bound := not inside and not outside;
end.

Побитовые операции

Побитовые операции and, or, xor, not

Замечание. Работают только с целыми.

Соответствующая операция применяется к каждому биту двоичного представления числа.
Пример.

5 and 7

510 = 1012
710 = 1112

101
   ( and )
111
———
1012 = 510

Операции shl и shr

Побитовый сдвиг влево и сдвиг вправо соответственно.

shl

x shl n = x * 2n

Сдвигает двоичное представление x на n позиций влево.

shr

x shr n = x div 2n

Сдвигает двоичное представление x на n позиций вправо.

Примеры

x = 510 = 1012

x shl 2 = <—(2)101
             101002 = 2010

x shr 2 = 101—>(2)
          0012 = 110

Таблица приоритетов операций языка Object Pascal

  1. унарные + - not
  2. имеющие смысл умножения * / div mod and shl shr
  3. имеющие смысл сложения + - or xor
  4. операции отношения <> <= >= < > in

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

Синтаксис

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

Семантика

If.jpg

Примеры использования для решения задач

Пример 1. Нахождение минимума
Дано: x, y
Найти: min

if x > y then
  min := y
else
  min := x;

Пример 2. Упорядочение a, b по возрастанию.
Ясно, что если a > b, — нужно поменять их местами.
Но тут одним оператором не обойтись. Для этого можно использовать составной оператор — один или больше операторов, заключенных в операторные скобки begin - end;:

if a > b then
begin
  var v := b;
  b := a;
  a := v;
end;

Пример 3. Вычисление функции по взаимоисключающим веткам
y = \begin{cases} x, & x < 2 \\ x^2, & 2 < x < 3 \\ 1-x, & x \ge\; 3 \end{cases}

if x < 2 then
  y := x
else if x < 3 then
  y := x * x
else y := 1 - x;

Замечание. Если по ветви else располагается другой оператор if, то говорят, что возникает цепочка вложенных операторов if.

Координаты средней точки

Пример 4. Даны координаты трех точек на прямой a, b, c (a, b, c попарно не равны). Найти m - координаты средней точки

Решение 1.

Достаточно рассмотреть случай, когда координаты точек равны 1,2 и 3

Рассмотрим все возможные ситуации распределения этих значений между a,b,c:

a  b  c
1  2  3  a<b
1  3  2  a<b
2  1  3
2  3  1  a<b
3  1  2
3  2  1

При a<b возможны три варианта, в двух из которых a<c. При a>b возможны три варианта, в двух из которых a>c.

Решение представляется вложенными операторами if с уровнем вложенности 3.

if a < b then
  if a < c then
    if b < c then
      m := b
    else
      m := c
  else
    m := a
else
  if a > c then
    if b < c then
      m := c
    else
      m := b
  else m := a;

По количеству операций (3 сравнения, одно присваивание) это - самое лучшее решение

То же решение, записанное с помощью функции min:

if a < b then
  if a < c then
    m := min(b,c)
  else m := a
else
  if a > c then
    m := min(b,c)
  else m := a;

Решение 2.

var m1 := min(a,b);
var m2 := max(a,b);
a  b  c  
1  2  3  c>m2
1  3  2  
2  1  3  c>m2
2  3  1  c<m1
3  1  2  
3  2  1  c<m1
var m1 := min(a,b);
var m2 := max(a,b);
if c<m1 then
  m := m1
else if c>m2 then
  m := m2
else m := c;

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

Самостоятельные задания

  • Даны координаты вершин треугольника и точка M, не лежащая на границе треугольника. Принадлежит ли M треугольнику.
  • Является ли 4-угольник ABCD корректно заданным.

Оператор case выбора варианта

Синтакстис

case <переключатель> of
  {<список выбора>: <оператор>;}
  [else <оператор>[;]]
end

Семантика

Вначале вычисляется выражение-<переключатель>, после чего его значение ищется в одном из <списков выбора>.
Если значение попадает в какой-то <список выбора>, то выполняется соответствующий ему оператор, иначе, если есть ветвь else, то выполняется оператор по ветке else.

Ограничения

  • выражение-переключатель должно иметь так называемый порядковый тип:
целый
символьный
перечислимый

НО НЕ строковый или вещественный.

  • значения в <списках выбора> не должны пересекаться.

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

Пример 1. День недели

case DayOfWeek of
  1..5: writeln('Будний');
  6, 7: writeln('Выходный');
  else  writeln('Ошибка');
end;

Пример 2. Цифра или буква

var c: char;
read(c);
case c of
  '0'..'9': writeln('Цифра');
  'A'..'Z', 'a'..'z', 'а'..'я', 'А'..'Я', 'ё', 'Ё': writeln('Буква');
end;
К основной странице курса

Циклы

Циклы с предусловием (while) и постусловием (repeat)

Синтаксис цикла while

while <условие> do   <— заголовок цикла
  <оператор>   <— тело цикла

<условие>::= <логическое выражение>

Семантика цикла while

Цикл while м.png

Синтаксис цикла repeat

repeat
  <операторы>
until <условие>

Семантика цикла repeat

Цикл repeat м.png

Зацикливание

Зацикливание происходит, если:

  • условие цикла с предусловием всегда истинно

Пример.

while true do
  <оператор>
  • условие цикла с постусловием всегда ложно

Пример.

repeat
  <операторы>
until false

Итерация — однократное повторение тела цикла.

Отличия между циклами while и repeat

while

тело может не выполниться ни разу

repeat

тело выполнится хотя бы один раз

Примеры

Пример 1. Сумма нечетных двузначных чисел

С использованием while

s := 0; 
x := 11;
while x < 100 do
begin
  s += x;
  x += 2;
end;

С использованием repeat

s := 0; x := 11;
repeat
  s += x;
  x += 2;
until x = 99;

Пример 2. Факториал

С использованием repeat

var n: integer;
read(n);
var x := n;
var p := 1;
 
repeat
  p *= x;
  x -= 1;
until x = 1;

С использованием while

var n: integer;
read(n);
var x := n;
var p := 1;
 
while x > 1 do
begin
  p *= x;
  x -= 1;
end;

Моделирование repeat с помощью while

repeat             Op
  Op       ——>     while not A do
until A;           begin
                     Op
                   end;

Моделирование while с помощью repeat

while A do         if A then
  Op         ——>     repeat
                       Op
                     until not A

Оператор цикла с параметром (for)

Синтаксис

<заголовок цикла>
  <тело цикла>
<заголовок цикла> ::= for <переменная>:=<выражение1> <направление> <выражение2> do
<тело цикла> ::= <оператор> 
<направление> ::= to | downto

Семантика

var b := <выражение1>;
var e := <выражение2>;
  <переменная> := b;
 
while <переменная> <> e do
begin
  <оператор>
  <получить следующее значение переменной>
end;
<получить следующее значение переменной> ::= <переменная> += 1; | <переменная> -= 1;

Ограничения:

  • выражения 1 и 2 должны быть совместимы по присваиванию с переменной
  • переменная должна иметь порядковый тип (такой же, как и в case — целый, символьный или перечислимый)
  • переменная цикла for не должна меняться внутри цикла for
  • переменная цикла for должна быть описана в той же п/п, где используется цикл

Дополнительные возможности PascalABC.NET

Возможно описание переменной цикла в его заголовке:

for [var] i: integer := 1 to 5 do
  <оператор>

Возможно автоопределение типа при описании:

for var i := 1 to 5 do
  <оператор>

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

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

Табулирование функции

Дана f(x) на [a; b], разбитая на N частей.

Выдать таблицу значений в точках разбиения.

var a, b: real;
var N: integer;
read(a, b, N);
 
assert(N <> 0);
assert(b > a);
var h := (b - a) / N;
var x:real:=h;

Дальнейшее решение с помощью for:

for var i := 0 to N do
begin
  writelnFormat('{0,6:f2} {1,9:f4}', x, f(x));
  x += h;
end;

Дальнейшее решение с помощью while:

var eps := h / 2;
while x < (b + eps) do
begin
  writelnFormat('{0,6:f2} {1,9:f4}', x, f(x));
  x += h;
end;

Погрешность при работе с вещественными числами

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

Вещественные хранятся в памяти компьютера в виде M*10p, где M называется мантиссой, p - порядком. Вещественное типа real занимает 64 бита (8 байт), из которых первый бит отводится под знак порядка, следующие 11 бит хранят значение порядка, затем идет бит для знака мантиссы и оставшиеся 51 бит отводятся для хранения мантиссы.

Минимальное положительное вещественное число называется машинным эпсилон и задается константой real.Epsilon

begin

 writeln(real.Epsilon);

end.


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

Рекуррентные соотношения

Говорят, что последовательность данных

x1, x2, x3,..., xn

является рекуррентной, если

xk + 1 = f( xk ), k = 1, 2, 3...

Вывод степеней двойки

var x := 1;
for var i := 1 to 10 do
begin
  writeln(x);
  x *= 2;
end;

Последовательность Фибоначчи

\begin{cases} x_1 = 1, x_2 = 1 \\ x_{k+1} = x_k + x_{k-1}\end{cases}

var a := 1;
var b := 1;
write(a, ' ', b, ' ');
for var i := 3 to 20 do
begin
  c := a + b;
  write(c, ' ');
  a := b;
  b := c;
end;

Вычисление НОД (алгоритм Евклида)

\begin{cases} x_1 = a \\ x_2 = b \\ x_{k+1} = x_{k-1} mod  x_k\end{cases}

var a, b: integer;
read(a, b);
assert((a > 0) and (b > 0));
repeat
  c := a mod b;
  a := b;
  b := c;
until c = 0;
writeln(a);

Суммирование рядов (конечных и бесконечных)

  • \sum_{i=1}^n  \frac{a^i}{i!}

Найдем рекуррентную связь между ai:

x1 = a
xi = xi-1 * a / i, i = 2, 3..
read(a, n);
x := a;
s := x;
for var i := 2 to n do
begin
  x *= a / i;
  s += x;
end;
  • \sum_{i=1}^\infty  (-1)^i\frac{a^i}{i}

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

задается некоторый малый eps и сумма \sum_{i=1}^\infty  x_i вычисляется, пока |x_i| >\ eps
assert((a > 0) and (a < 1));
i := 1;
s := 0;
y := -a;
repeat
  s += y / i;
  i += 1;
  y *= -a;
until abs(y / i) < eps;

Нахождение max в последовательности чисел

max := - real.MaxValue; // или max := real.MinValue;
for var i := 1 to n do
begin
  read(x);
  if x > max then
    max := x;
end;

Разложение целого числа на простые множители

Будем делить x на p, начиная с p = 2. Если делится нацело, то p — множитель, если не делится, то увеличиваем p на 1, пока x <> 1.

read(x);
p := 2;
repeat
  if x mod p = 0 then
  begin
    write(p, ' ');
    x := x div p;
  end
  else
    p += 1;
until x = 1;

Операторы break и continue

break — оператор досрочного завершения цикла.

Break м.png

continue — оператор досрочного завершения текущей итерации цикла.

Continue м.png

Примеры использования циклов (продолжение)

Поиск заданного значения среди введенных

Решение 1. С использованием оператора break

var exists: boolean := false;
for var i:=1 to n do
begin
  read(x);
  if x = k then
  begin
    exists := true;
    break;
  end;
end;

Решение 2.

var i := 1;
var exists: boolean:= false;
repeat
  read(x);
  if x = k then
    exists := true;
  i += 1;
until (i > n) or exists;

Обработка последовательности, завершающейся нулем

Вводятся числа. Конец ввода — ноль.
Найти сумму и произведение положительных чисел.

s := 0;
p := 1;
while True do
begin
  read(x);
  if x = 0 then
    break; 
  if x < 0 then
    continue;  //фильтр
  s += x;
  p *= x;
end;

Вычисление значения многочлена. Схема Горнера

Необходимо вычислить \ a_0x^n + a_1x^{n-1} + ... + a_{n-1}x + a_n если

a0...an известны
x дано

Решение 1.

var p := 1.0;
var s := 0.0;
for var i := 0 to n do
begin
  read(a);
  s += p * a;
  p *= x;
end;

Это решение использует 2(n + 1) умножений.

Однако есть и другое решение — схема Горнера. Оно основана на том, что
\ a_0x^2 + a_1x + a_2 = ((a_0)x + a_1)x + a_2

Решение 2.

read(a);
var res: real := a;
for var i:=1 to n do
begin
  read(a);
  res *= x;
  res += a;
end;

Итого — всего n умножений.

Поиск нуля функции на отрезке

Требуется найти корень уравнения f( x ) = 0

  • на отрезке [a; b]
  • с заданной точностью eps
  • f(x) непрерывна на этом отрезке
  • имеет на нем ровно 1 корень, т.е. f(a ) * f( b ) < 0

Эта задача решается методом половинного деления.

assert(b > a);
var fa := f(a);
var fb := f(b);
assert(fa * fb < 0);
 
while b - a > eps do
begib
  var x := (b + a) / 2;
  var fx := f(x);
  if fx * fa > 0 then
  begin
    a := x;
    fa := fx;
  end
  else
    b := x;
end;

Вложенные циклы

Метод последовательной детализации

Задача. Вывести все простые числа <= n

writeln(2);
x := 3;
while x <= n do
begin
  Если число x — простое, то
    writeln(x);
  x += 2;
end;

Метод окаймления

Задача. Вывести Ak, A = 2..10

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

Итак, пусть A — фиксировано( "заморожено" ).

var p := 1.0;
for var i:=1 to k do
  p *= A;
write(p);

Теперь размораживаем A:

for A:=2 to 10 do
begin
  ...
end;

Переборные задачи

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

Задача. Дано равенство: a2 + b2 = c2, a,b,c — целые
Вывести все такие тройки (a, b, c), что: a<=1000, b<=1000, c<=1000;

Решение.

for var a:=1 to 1000 do
for var b:=1 to 1000 do
for var c:=1 to 1000 do
  if a*a + b*b = c*c then
    writeln(a, b, c);

Однако, ясно, что

a2 + b2 = c2 <=> b2 + a2 = c2

Кроме того, c можно вычислять.

Оптимизация.

for var a:=1 to 1000 do
  for var b:=1 to a-1 do
  begin
    var c := round(sqrt(a*a + b*b));
    if a*a + b*b = c*c then
    begin
      writeln(a, b, c);
      writeln(b, a, c);
    end;
  end;

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

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

Вспомогательные алгоритмы

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

Вспомогательные алгоритмы имеют:

  • имя
  • список параметров
некоторые параметры являются входными, а некоторые — выходными

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

В программировании вспомогательные алгоритмы называются подпрограммами и делятся на:

  • процедуры
  • функции

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

Процедуры

Пример.
Даны 3 пары положительных чисел.
Найти их среднее арифметическое и среднее геометрическое.

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

procedure CalcSrASrG(a, b: real; var SA, SG: real); 
begin
  SA := (a + b) / 2;
  SG := sqrt(a * b);
end;

Строка

procedure CalcSrASrG(a, b: real; var SA, SG: real);

называется заголовком процедуры.

Теперь можем многократно использовать описанную процедуру в основной программе:

begin   //основной программы
  for var i := 1 to 3 do
  begin
    var a, b: real;
    readln(a, b);
    var SA, SG: real;
    CalcSrASrG(a, b, SA, SG);
    writeln(SA, '|', SG);
  end;
end.

Оператор

CalcSrASrG(a, b, SA, SG);

называется оператором вызова процедуры.

Синтаксис описания процедуры

procedure <имя> [( <список формальных параметров> )];
<раздел описаний>
begin
  <операторы и внутриблочные переменные>
end;
<список формальных пар-ов> ::= <секция ф.п.> | <секция ф.п.>; <список ф.п.>
<секция ф.п.> ::= [var] <список имен>: <тип>

Замечание. Pascal допускает внутренне описание подпрограмм (вложенность подпрограмм)

Синтаксис вызова процедуры

<имя процедуры> [( [<список фактических параметров>] )]
<список фактических пар-ов> ::= <список выражений>
(<список выражений> ::= <выражение>{, <выражение>})

Семантика вызова процедуры

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

Входно-выходные параметры

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

Пример 1. Увеличение значения переменной на заданное число.

procedure Inc(var x: integer; n: integer);
begin
  x += n;
end;

Вызов процедуры:

var x: integer;   //неинициализированная переменная
x := 1;           //x инициализировано
Inc(x, 2);

Пример 2. Перемена местами значений двух переменных.

procedure Swap(var x, y: integer);
begin
  var v := x;
  x := y;
  y := v;
end;
 
begin
  var a := 5;
  var b := 3;
  Swap(a ,b);
end.

Функции

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

Пример. Функция sgn(x) — знак числа

function Sign(x: real): integer;
begin
  if x > 0 then
    Result := 1
  else if x < 0 then
    Result := -1
  else
    Result := 0;
end;
Строка
function Sign(x: real): integer;

называется заголовком функции, а integer — это тип возвращаемого значения.

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

Пример неправильной(!!!) функции.

function f(x: integer): integer;
begin
  if x > 0 then
    Result := 777;
end;
 
begin
  writeln(f(0));
end.

Выведется непредсказуемое значение, находящееся в этот момент в памяти, т.к. по ветке

x = 0 

переменная Result не инициализируется.

Пример. Считывание значения с клавиатуры.

function ReadInteger: integer;
begin
  read(Result);
end;

Сравнение использования процедуры и функции

процедура           функция
var x: integer;     var x := ReadInteger();
read(x);

Пример. Сумма первых N натуральных чисел.

function SumN(N: integer): integer;
begin
  Result := 0;
  for var i := 1 to N do
    Result += i;
end;

Синтаксис описания функции

function <имя>[( [<список формальных параметров>] )]: <тип возвращаемого значения>;
<раздел описаний>
begin
  <операторы и внутриблочные описания>
end;

Синтаксис вызова функции

<имя>[( [<список формальных параметров>] )];

Семантика

Замечание. Формальные параметры функции не рекомендуется делать параметрами-переменными.

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

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

Оператор exit

Оператор exit — оператор досрочного завершения подпрограммы.

Пример. Поиск числа k среди n введенных.

function Find(n: integer; k: integer): boolean;
begin
  for var i := 1 to n do
  begin
    var x := ReadInteger;
    if x = k then
    begin
      Result := true;
      exit;
    end;
  end;
  Result := false;
end;

Замечание. Оператор exit, в отличие от break, позволяет завершить подпрограмму и выйти сразу из нескольких вложенных циклов. При вызове его в теле основной программы программа завершается.

Локальные и глобальные переменные

var
  n: integer;
  m: integer;
 
procedure p(x: integer);
var n: integer;
begin
  writeln(n);
  writeln(m);
  n := 5;
end;

Замечание. Локальная переменная, описанная в процедуре или функции, может иметь то же имя, что и глобальная переменная.
Если глобальная описана ранее, то её имя внутри процедуры становится недоступным.

Глобальные переменные по умолчанию инициализируются 0, а локальные по умолчанию не инициализируются.
Локальная переменная Result в функции по умолчанию также не инициализируется.

В данном примере:

writeln(n) выведет непредсказуемый результат;
writeln(m) выведет 0;

Пространство имен

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

Различают:

  • глобальное пространство имен
  • пространство имен каждой подпрограммы

Формальные параметры подпрограммы являются локальными по отношению к этой подпрограмме.

Примеры допустимых и недопустимых описаний:

procedure p(p: integer);        //Можно

function Result: integer;       //Можно

procedure p(i: integer);        //НЕЛЬЗЯ
var i: integer;

function f(Result: real): real; //НЕЛЬЗЯ

Область видимости и время жизни объекта

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

  • для глобальных переменных это время работы программы
  • для локальных — с момента описания до конца работы блока, где они описаны

Область видимости переменной — область в программе, в которой можно обратиться к переменной по имени.

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

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

Пример.

var n: integer;
procedure p;
begin
  n += 1;
end;
 
begin
  n := 5;
  p;
  write(p);
end.

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

Принцип локальности

Рассмотрим пример:

var n: integer;
 
procedure p;
begin
  <много кода; n не используется>
end;
 
procedure p1;
begin
  <еще больше кода; n не используется>
end;
 
begin
  <10 операторов>
  n := 1;
  <еще операторы>
end.

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

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

Пример. Вычислить произведениe целых чисел от a до b.
Решение 1.

var 
  p := 1;
  a, b: integer;
  i: integer;
 
begin
  read(a, b);
  for i := a to b do
    p *= i;
  writeln(p);
  writeln(i); //от компилятора зависит, будет i = b или (b + 1)
end.

Внесем изменения:

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

Решение 2.

var 
  p: integer;
  a, b: integer;
begin
  read(a, b);
  p := 1;
  for var i := a to b do
    p *= i;
  writeln(p);
end.

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

Подпрограммы (продолжение)

Внутренний механизм передачи параметра-переменной

procedure MyInc(var a: real; n: integer);
begin
  a += n;
end;
 
var
  b: real;
  i: integer;
begin
  b := ReadReal;
  i := ReadInteger;
  MyInc(b, i);
  writeln(b);
end.

Здесь

b, i: фактические параметры при вызове MyInc
a, n: формальные параметры (a — параметр-переменная; n — параметр-значение)

При передаче параметра-значения i в соответствующий формальный параметр копируется значение i.
А вот механизм передачи параметра-переменной называется передачей по ссылке.

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

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

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

MyInc(var a: real; i: integer);
          |
         @b (адрес b)

Замечание 1. В 32-битных ОС размер адреса — 4 байта, это позволяет адресовать примерно 4 Гб операционной памяти.

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

Перегрузка имен подпрограмм

Предположим, у нас описана процедура:

procedure Swap(a, b: integer);
begin
  var v := a;
  a := b;
  b := v;
end;

Возможно ли описать процедуру с таким же именем, параметры которой были бы вещественными? Да, возможно:

procedure Swap(a, b: integer);
begin
  var v := a;
  a := b;
  b := v;
end;
 
procedure Swap(a, b: real); 
begin
  var v := a;
  a := b;
  b := v;
end;

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

Замечание 1. Тип возвращаемого значения функции не участвует в операции разрешения перегрузки.

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

Замечание 2. Разрешить перегрузку можно не всегда.
Пример.

procedure p(i: integer; r: real);
...
 
procedure p(r: real; c: char);
...
 
procedure p(r: real; i: integer);
...
 
begin
  p(1, 2); //неоднозначность
end.

Замечание 3. В процессе разрешения перегрузки происходит неявное преобразование типов.

Параметры по умолчанию

procedure DoOp(a, b: integer; var res: integer; op: char := '+');
begin
  case op of
    '+': res := a + b;
    '-': res := a - b;
    '*': res := a * b;
  end;
end;
...
DoOp(1,2, res, '-'); // res = -1
DoOp(3,4, res1);     // res1 = 7

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

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

Предварительное объявление подпрограмм

procedure p;
begin
  q;
end;
 
procedure q;
begin
  ...
end;

Такое описание вызовет ошибку компиляции, т.к. в p используется еще не известная процедура. Чтобы таких ошибок не возникало, нужно использовать предварительное объявление подпрограммы:

procedure q; forward;
 
procedure p;
begin
  q;
end;
 
procedure q;
  ...

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

Процедурные переменные

Процедурный тип и переменные

var pr: procedure(a, b: real; var res: real);

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

type
  BinOpType = procedure(a, b: real; var res: real);
 
var
  pr: BinOpType;
 
procedure AddOp(a, b: real; var res: real);
begin
  res := a + b;
end;
procedure MultOp(a, b: real; var res: real);
begin
  res := a * b;
end;
 
begin
  pr := AddOp;
  var r: real;
  pr(1,2, r);   //вызов процедуры через процедурную переменную
 
  pr := MultOp;
  pr(3,4, r);
end.

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

<имя процедурной переменной>( <список параметров> );

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

Замечание. До первого присваивания процедурная переменная имеет тип nil. В этот момент вызывать процедуру через переменную нельзя — будет ошибка выполнения.

Функции обратного вызова (callback)

type ActionType = procedure;
 
procedure DoActions(Action1, Action2, Action3: ActionType);
begin
  Action1;
  Action2;
  Action3;
end;
 
procedure Eat;
begin
  writeln('Студент ест');
end;
procedure Think;
begin
  writeln('Студент думает');
end;
procedure Sleep;
begin
  writeln('Студент спит');
end;
 
begin
  DoActions(Sleep, Eat, Sleep);   // Первый алгоритм подготовки к сессии
  DoActions(Think, Eat, Sleep);   // Второй алгоритм подготовки к сессии
  DoActions(Sleep, Sleep, Think); // Третий алгоритм подготовки к сессии
end.

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

Такой вызов называется обратным вызовом (callback).
Примечание. Прямой вызов — это передача процедуры в качестве параметра.

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

type func = function(x: real): real;
function Integral(a, b: real; N: integer; f: func);
begin
  assert(a < b);
  var h := (b - a) / N;
  result := 0;
  var x := a;
  for var i := 0 to N-1 do
  begin
    Result += f(x);
    x += h;
  end;
  Result *= h;
end;

Вызов нескольких процедур через процедурную переменную

type ActionType = procedure;
 
procedure Eat;
begin
  writeln('Студент ест');
end;
procedure Think;
begin
  writeln('Студент думает');
end;
procedure Sleep;
begin
  writeln('Студент спит');
end;
 
var pr: ActionType;
begin
  pr += Sleep;
  pr += Eat;
  pr += Think;
  pr;
  pr -= Eat;
  pr;
end.

Замечание 1. Такое возможно только в .NET;

Замечание 2. Подобный механизм не имеет смысла использовать для функций;

Методы разработки подпрограмм

Пример. Вывести таблицу простых чисел <= 1000.

Метод разработки сверху вниз

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

Решение.

writeln(2);
var x := 3;
while x <= 1000 do
begin
  if IsPrime(x) then
    writeln(x);
  x += 2;
end;

После этого реализуем функцию IsPrime;

Этот метод используется, когда:

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

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

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

Метод разработки снизу вверх

Вначале составляется «нижняя» подпрограмма, она всесторонне тестируется, и потом
пишется основная программа или подпрограмма.

Метод используется, когда:

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

Замечание 1. Написание самых «нижних» подпрограмм позволяет лучше войти в предметную область;

Замечание 2. С какого-то момента разработка хотя бы упрощенного главного алгоритма становится необходимой;

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

Оператор goto

Синтаксис

goto <метка>
...
<метка>: <оператор>

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

label <метка1>, <метка2>...;

Семантические ограничения

  • метка должна использоваться в той же подпрограмме, в которой описана;
  • нельзя, используя goto, перейти внутрь какой-нибудь конструкции (цикла или условного оператора);

Пример плохого кода

label 1, 2, 3;
begin
   goto 1;
   <100 операторов>
2: write(2);
   goto 3;
   <100 операторов>
1: write(1);
   goto 2;
   <100 операторов>
3: write(3);
end.

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

Пример. Найти a, b, c;
a2 + b2 = c2, 50 <= a,b,c <= 100

label fin;
var aa, bb, cc: integer;
var flag := false;
 
for var a := 50 to 100 do
for var b := 50 to 100 do
for var c := 50 to 100 do
  if a*a + b*b = c*c then
  begin
    flag := true;
    aa := a;
    bb := b;
    cc := c;
    goto fin;
  end;
 
fin: if flag then
       writeln(aa, bb, cc);

Вызов подпрограммы на этапе выполнения

Введение

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

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

Пример.
Работа стека.gif

Устройство ЗА подпрограммы

ЗА.png
Штриховкой отмечены поля, заполняемые до запуска п/п.

Алгоритм входа в подпрограмму

  1. Увеличить top стека на размер ЗА п/п, вычесленного на этапе компиляции: top += rЗА
  2. Вычислить адрес возврата (АВ) и адрес конца ЗА вызывающей п/п
  3. Вычислить значения всех фактических параметров и подставить их на место формальных.
  4. Перейти в коде программы на начало выполнения п/п

Алгоритм выхода из подпрограммы

  1. Уменьшитьtop стека, переместив его на адрес конца ЗА вызывающей подпрограммы
  2. В коде программы перейти по АВ

Пример

Программный стек во время выполнения алгоритма

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

При перемещении top на программном стеке накапливается мусор, потом в этот мусор опять записывается ЗА.
Коды top и current хранятся в специальных регистрах.

Вывод 1. Если подпрограмма — маленькая, то накладные расходы на её вызов могут существенно превысить время работы самой подпрограммы.

Вывод 2. Если локальные переменные или формальные параметры, передаваемые по значению имеют большой размер, то ЗА соответствующей подпрограммы будет большой. В результате при нескольких вложенных вызовах таких подпрограмм может произойти переполнение программного стека (Stack overflow), т.к. программный стек не увеличивается в размерах, а память под него выделяется один раз.

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

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

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

Переменное число параметров подпрограмм

Шаблоны подпрограмм

К основной странице курса

Модули

Введение

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

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

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

Пример.
Модуль MyLib

unit MyLib;
 
interface       // раздел интерфейса
const
  MyPi = 3.14;
var
  size: integer := 10;
 
function AddSquares(a, b: real): real;
procedure InvertPrint(a, b: integer);
 
implementation  // раздел реализации
var i: integer;
 
function MySqr(a: real): real;
begin
  Result := a * a;
end;
 
function AddSquares(a, b: real): real;
begin
  Result := MySqr(a) + MySqr(b);
end;
 
procedure InvertPrint(a, b: integer);
begin
  write(b, ' ', a);
end;
 
end.            // конец модуля

Программа, подключающая модуль MyLib

uses MyLib;
 
var
  n: integer;
 
begin
  writeln(size);
  writeln(AddSquares(3, 4));
  InvertPrint(3,4);
end.

Модуль в языке Object Pascal состоит из двух разделов:

  • раздел интерфейса
  • раздел реализации

В разделе интерфейса описываются все

  • переменные
  • константы
  • типы
  • заголовки подпрограмм,

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

В разделе реализации содержится

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

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

Данный принцип получил название принципа сокрытия информации.
Его преимущества:

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

Синтаксис модуля

unit <имя модуля>;

interface
[uses <список модулей>;]
<раздел описаний модуля: для п/п — только заголовки>

implementation
[uses <список модулей>;]
<раздел описаний, где даны реализации п/п из раздела interface>


[initialization
  <операторы>]                 begin
                       |         <операторы>
[finalization                  end.
  <операторы>]

Семантические ограничения

  • Модули, перечисленные в секции uses в разделах интерфейса и реализации не должны пересекаться
  • циклическая зависимость между модулями в разделе интерфейса запрещена:
unit A;        unit B;
interface      interface
uses B;        uses A;
тем не менее, если A ссылается на B в разделе интерфейса, а B ссылается на A в разделе реализации, то это допустимо:
unit A;        unit B;
interface      interface
uses B;        implementation
               uses A;
потому что модули компилируются в два этапа:
  • интерфейс
  • реализация

Разделы инициализации и финализации модуля

unit A;                       основная программа
...
initialization                uses A;
  <операторы1>                
finalization                  begin
  <операторы2>                  <операторы3>
end.                          end.

Операторы в разделе initialization модуля выполняются раньше тела основной программы,
а операторы в разделе finalization выполняются после основной программы.

Т.е. последовательность выполнения будет такой:

<операторы1> 
<операторы3>
<операторы2> 

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

Пример.

unit A;
 
interface
var
  RandomNumber: integer;
 
implementation
...
initialization
  RandomNumber := Random(100);
end.

Схема компиляции программы с модулями

Схема компиляции программы с модулями

1. Компилируется файл основной программы.
2. Если в нем встречена секция uses, то компилируются модули из этой секции слева направо.
3. Каждый модуль компилируется так же, как и основная программа по пунктам 1-2:

Если в модуле подключаются другие модули, то компилируются они,
и так происходит до тех пор, пока компилятор не дойдет до модулей, не содержащих подключения других модулей.

4. По окончании компиляции модуля его откомпилированный вариант (.pcu в PascalABC.NET) записывается на диск.
5. После записи откомпилированного модуля на диск компилятор возвращается к основному модулю (вызывающему) или программе, и докомпилирует его до конца. Основная программа после компиляции хранится в оперативной памяти.
6. Первый этап компиляции закончен. Начинается заключительный этап компиляции — линковка (компоновка). Специальная программа — линковщик — собирает из откомпилированных модулей единый исполняемый файл (.exe в Windows).

Замечание 1. Ошибки могут происходить как на этапе компиляции, так и на этапе, собственно, линковки.
Замечание 2. Наличие откомпилированного файла модуля существенно ускоряет процесс компиляции (сводя его к процессу линковки).
Замечание 3. Если есть откомпилированные файлы модулей, то исходные тексты модулей можно удалить. При этом компилятор проанализирует, что данный модуль откомпилирован, и продолжит компиляцию дальше.
Замечание 4. Если файл модуля .pas создан после откомпилированного файла модуля (.pcu), то произойдет его перекомпиляция.
Замечание 5. При работе в интегрированной среде компилятор в первую очередь берет тексты модулей из открытых файлов и только затем смотрит файлы на диске.

Примечание 1. Файлы модулей могут храниться в другом каталоге относительно файла исходной программы. Тогда надо вместе с именем модуля указывать, в каком каталоге он хранится (тогда имя модуля и имя файла модуля могут не совпадать):

— Main ——————————— MyPr.pas
    |
    |— Modules | — a.pas
               | — b. pas
MyPr.pas
program MyPr;
uses
  A in 'Modules\a.pas';
  B in 'Modules\b.pas';

Примечание 2. Откомпилированные файлы модулей по умолчанию создаются:

  • в каталоге основной программы
  • если установлен специальный путь в среде программирования для выходных файлов, то по этому пути

Кроме этого, стандартные откомпилированные модули хранятся в специальной папке, например, в PascalABC.NET — в папке \LIB. Если модуль претендует на звание стандартного, его можно туда поместить.

Упрощенная структура модуля

В PascalABC.NET можно использовать упрощенную структуру модуля:

unit <имя модуля>;
<раздел описаний>
[begin
  <операторы>]
end.

В <разделе описаний> описываются типы, константы, переменные и подпрограммы.
Раздел begin + <операторы> соответствует разделу инициализации.

Алгоритм поиска имен в модулях

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

Правило 1. Имя вначале ищется в текущем модуле. Если не найдено, то в подключенных модулях секции uses справа налево.
Правило 2. Если нужно явно указать переменную из какого-то модуля, то перед ней ставится <имя модуля>.

Пример.

unit A;
uses B;
var n, m: integer;
...
end.
unit B;
var n, k, m: integer;
...
end.
program MyPr;
uses A, B;
 
var n: integer;
begin
  n := 5;    // из основной программы
  k := 6;    // из B
  m := 7;    // из B
  A.n := 8;  // из A
  B.n := 9;  // из B
  A.m := 10; // из A
end.

Стандартный системный модуль PABCSystem

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

Он содержит:

  • стандартные константы
  • типы
  • подпрограммы

В Delphi — это System,
в PascalABC.NET — PABCSystem.

Пример.

program MyProgram;
[uses PABCSystem;] // подключается неявно
 
begin
  writeln(sin(0));
end.
program MyProgram;
uses A; // —> uses PABCSystem, A;
 
var sin: integer;
begin
  writeln(PABCSystem.sin(0));
end.

Библиотеки

Сходства с модулем

Библиотеки, как и модули:

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

В Windows получили распространение библиотеки .dll (dynamically linked libraries). Они находятся либо в текущем каталоге приложения (локальные), либо в системном каталоге (глобальные библиотеки).

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

Отличия библиотек от модулей

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

    Линковка модуля и библиотеки

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

Библиотеки в PascalABC.NET

Синтаксис

library <имя>;
interface
...
implementation
...
end.

Замечание. В библиотеках отсутствуют секции инициализации и финализации.

Для подключения библиотеки используются конструкции:

#reference 'MyLib.dll'
{$reference MyLib.dll}

Примечание. reference — директива компилятора. Указывает компилятору, что программа использует внешние библиотеки, расположенные в файле MyLib.dll.

Документирующие комментарии ///

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

Документирующие комментарии в PascalABC.NET

Отображаются в виде всплывающего окна при наведении указателя на имя подпрограммы.

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

///  <текст комментария>

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

#gendoc trueК основной странице курса

Перечислимый тип

Определение и примеры

Тип, задаваемый перечислением значений, называется перечислимым.

Пример 1.

type
  /// Дни
  Days = (Mon, Tue, Wen, Thi, Fri, Sat, Sun);
  /// Месяцы
  Months = (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec);
 
var
  d: Days;
  m: Months;
begin
  d := Tue;
  m := Months.May;
end.

Переменные перечислимого типа хранятся в памяти, как целые числа. Первому значению соответствует 0, второму — 1, и т.д.

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

Переменная перечислимого типа может служить параметром цикла for, а также переключателем в операторе case.

Пример 2.

for m: Months := Jan to Dec do
  case m of
    Jan: writeln('Январь: 31');
    Feb: writeln('Февраль: 28 или 29');
    else writeln('Забыл');
  end;

Стандартные подпрограммы для работы с перечислимым типом

Succ(<переменная>)
  Возвращает следующее значение перечислимого типа
Pred(<переменная>)
  Возвращает предыдущее значение перечислимого типа
Ord(<переменная>)
  Возвращает порядковый номер значения перечислимого типа

Замечание. Тип boolean = (False, True)

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

Диапазонный тип

Диапазонный тип строится на базе

  • целого
  • символьного
  • или перечислимого типов

Пример.

type
  Marks = 2..5;
  Digits = '0'..'9';
  Letters = 'a'..'z';
  Summer = Jun..Aug;
К основной странице курса

Массивы

Введение

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

Пример.

var 
  a: array[1..10] of real;
  b: array[2..5, 'a'..'z'] of integer;

Как и диапазонный тип, индексы могут иметь типы:

  • целый
  • символьный
  • перечислимый

Кроме того, в качестве индекса может выступать порядковый тип, например:

var c: array[Marks, Letters] of integer;

Обращение к элементам массивов

Чтобы обратиться к элементу массива, нужно использовать конструкцию

<массив>[<индекс>]
var 
  a: array[1..10] of integer;
  b: array[2..5, Mon..Sun] of string;
 
begin
  a[3] := 666;
  b['x', Tue] := 'zzz';
 
  a[11] := 0; // Ошибка выполнения — выход за границы изменения индекса
end.

В некоторых компиляторах можно специальными директивами отключить проверку выхода за границы диапазона, это увеличивает скорость выполнения.
В PascalABC.NET проверка выполняется всегда. А вот в Delphi и Free Pascal существует соответствующая директива

{$R-}
{$R+}

Динамические массивы

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

var a: array of integer;

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

Выделение памяти

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

a := new integer[n];

Процедурный стиль:

SetLength(a,n);

Здесь n может быть не только константой, но и переменной.

В обоих случаях элементы массивов заполняются нулями.

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

a := nil;

При попытке использования неинициализированного динамического массива выдается исключение времени выполнения "System.NullReferenceException: Ссылка на объект не указывает на экземпляр объекта."

Перевыделение памяти

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

var a : array of integer;
SetLength(a,3);
a[0] := 1;
a[1] := 3;
a[2] := 2;
SetLength(a,4);
writeln(a[0]); // выведется 1

При этом старое содержимое массива будет сохранено.

Длина массива

Чтобы узнать количество элементов в динамическом массиве, следует воспользоваться функцией Length:

Length(a)

или свойством Length:

a.Length

Таким образом, элементы динамического массива:

a[0],...,a[a.Length-1]

Циклы по массиву

for var i:=0 to a.Length-1 do
  write(a[i],' ');

или с помощью цикла foreach (для каждого)

foreach x: integer in a do
  write(x,' ');

В цикле foreach доступ к элементам массива осуществляется только на чтение.

Инициализация массивов

Элементы массива можно инициализировать при описании:

var a: array of string:= ('Иванов','Петров','Сидорова');

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

Хранение массивов в памяти

Присваивание и сравнение массивов

Именная и структурная эквивалентность типов

Передача массивов в качестве параметров подпрограмм

При передаче статического массива должна соблюдаться именная эквивалентность

  procedure xxx(a: array [1..100] of integer); // неверно
 
  type IArr = array [1..100] of integer;
  procedure yyy(a: IArr);

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

  procedure zzz(const a: IArr);
  procedure sss(var a: IArr);

Динамический массив можно передавать в виде array of integer, т.к. для него определена структурная эквивалентность типов.

  procedure ttt(a: array of integer);

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

Массив как возвращаемое значение функции

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

Переменное число параметров подпрограмм

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

Обобщенные подпрограммы

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

Выход - обобщенные подпрограммы.

Пример:

 procedure Print<T> (array of T);

В момент вызова п/п происходит:

  • выведение типов шаблона по фактическим параметрам
  • инстанцирование - подстановка конкретных типов шаблонов и генерация кода подпрограммы с этими конкретными типами.

Если для такого типа код уже инстанцировался, то повторно действие не выполняется.

Массивы (продолжение)

Задачи на одномерные массивы

  • инвертирование массива (шаблон)
  • поиск элемента в массиве - найти и вернуть индекс (шаблон)

можно с for и break,

можно и другим способом - с while

  • поиск с барьером (шаблон). Его преимущества
  • минимальный элемент массива и его индекс
  • слияние двух упорядоченных массивов в один упорядоченный (барьер)
  • сдвиг элементов массива влево (шаблон)

знакомство со значением default(T), Т - тип

Задачи на одномерные массивы

pas-файл с задачами

doc-файл

используется тип IArr = array [1..size] of integer

  • сдвиг элементов массива вправо (ShiftRight)

[ два варианта определения граничных значений цикла (от n-1 до 1; от n до 2) ]

  • циклический сдвиг элементов массива вправо (CycleShiftRight)
  • удаление элемента по заданному индексу (Delete)
  • вставка элемента по заданному индексу (Insert)

Задачи с процедурными переменными в качестве параметров

Определяется тип IUnPred = function (x: integer): boolean

  • поиск элемента по заданному условию (Find)
  • количество элементов, удовлетворяющих условию (Count)
  • условный минимум (MinElem)
  • удаление всех элементов, удовлетворяющих условию (DeleteAll)

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

Сортировки массивов

Сортировка выбором

Пузырьковая сортировка

Сортировка вставками

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

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

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

Замечание. Рассмотренные виды сортировки не являются самыми эффективными.

Асимптотическая оценка количества шагов алгоритма

Определение. Число шагов алгоритма асимптотически равно Θ(f(n)) если, существуют такие N, с1, с2>0 (c1<c2), что для любых n ≥ N выполняется:

   c1*f(n) ≤ число шагов ≤ c2*f(n)

Замечание 1. Для рассмотренных алгоритмов сортировки асимптотическая сложность = Θ(n2).

Замечание 2. Существуют алгоритмы сортировки с асимптотической сложностью = Θ(n*logn) (они будут рассмотрены в следующем семестре).

Бинарный поиск в отсортированном массиве

Задачи на двумерные массивы (матрицы)

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

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

  • Квадратные матрицы и элементы выше-ниже главной (побочной) диагонали.
  • Поиск элемента в матрице

если для поиска используется функция, то при нахождении элемента выходить из циклов с помощью exit,

иначе удобно использовать goto КОНЕЦ_ВНЕШНЕГО_ЦИКЛА К основной странице курса

Записи

Введение

 type <имя записи>= record
  <поля записи>
 end;

Поля записей

Инициализация записей при описании (на примере Student)

Доступ к полям записей. Точечная нотация.

Замечание. Для записей имеет место именная эквивалентность типов.

Передача записей в подпрограммы. Записи как возвращаемые значения функции

Передача записей в п/п (Print(s)). Запись как возвращаемое значение функции (s := ReadStudent;).

Передавать записи по значению неэффективно, т.к. копируются все поля. Поэтому следуют передавать их по ссылке (с ключевым словом const - если значения полей не изменяются, и var - если внутри п/п их требуется изменить.

Оператор with.

 with <переменная типа запись> do
 begin
   <поле>:=<значение>
 end;

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

Методы в записях

На примере Student.Print.

Запись как пространство имен

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

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

Т.о. возникает понятие инкапсуляции:

Инкапсуляция — хранение в записи одновременно данных и методов (запись представляет собой «капсулу»).
Инкапсуляция тесно связана с защитой данных: лишь некоторые члены в этой «капсуле» являются открытыми.

Создается окружение при описании переменной типа запись.

  • Сравнение s.Print и Print(s)

s.Print - объектно-ориентированный подход, доминирует объект, который сам выполняет над собой действие.

Print(s) - процедурно-ориентированный подход, главным является действие, выполняющееся над переменной.

  • Метод Init - инициализатор полей записи.

Лекция 23

Создание простейших графических объектов

 RPoint = record x, y: integer end;
 RRectangle = record p1,p2: RPoint end;

Для каждого типа определяются методы Print, Draw, MoveOn

Записи как средство упаковки параметров

Пример. Используем определенные типы записей для создания перегруженных версий процедур Rectangle и Line:

 Rectangle(x1,y1, x2,y2: integer)
 Rectangle(p1,p2: RPoint)
 Rectangle(r: RRectangle)
 
 Line(x1,y1, x2,y2: integer)
 Line(p1,p2: RPoint)
 Line(r: RRectangle)

Переменная типа запись как объект

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

Поля записи как состояние объекта.

Методы записи как действия, воздействующие на состояние объекта.

Методы делятся на две категории

  • меняющие состояние объекта (MoveOn для RPoint)
  • осуществляющие доступ к состоянию объекта на чтение (Draw для RPoint)

Сортировка массива записей.

Лекция 24

Индексная сортировка

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

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


Пример.

Дан массив целых (с реальными индексами):

 5[1]  15[2]  3[3]  1[4]  9[5]  8[6]  20[7]  12[8]

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

 1[1]  3[2]  5[3]  8[4]  9[5]  12[6]  15[7]  20[8]

Т.е.

 index[1] = 4
 index[2] = 3
 index[3] = 1
 index[4] = 6
 index[5] = 5
 index[6] = 8
 index[7] = 2
 index[8] = 7

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

Вначале удобно положить элементы index соответствующими тождественной подстановке (т.е. a[i] = i).

type CmpFunc = function (const s1,s2: Student): boolean;
 
procedure BubbleIndexStudentSort(const a: StArr; var Index: IArr; n: integer; Cmp: CmpFunc);
begin
  for var i:=1 to n do
    Index[i] := i;
  for var i:=n downto 2 do
  for var j:=1 to i-1 do
    if Cmp(a[Index[j+1]],a[Index[j]]) then
      Swap(Index[j+1],Index[j]);
end;
 
procedure WriteArrByIndex(const a: StArr; const Index: IArr; n: integer);
begin
  for var i:=1 to n do
    writeln(a[Index[i]].name,' ',a[Index[i]].course,' ',a[Index[i]].group);
end;

Статические методы записей

type RRectangle = record
  class function EqualSquare(const r1,r2: RRectangle): boolean;
  class function CreateRandom: RRectangle;
end;

К основной странице курса

Множества

Описание множеств. Множества-константы. Пустое множество.

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

s1+s2 - объединение
s1*s2 - пересечение
s1-s2 - разность
s += s1
s -= s1
s *= s1
s1<s2 - строгое вложение
s1<=s2 - нестрогое вложене
s1>s2 - строгое вложение
s1<=s2 - нестрогое вложене
i in s - принадлежность

Include(s,i);
Exclude(s,i); 

Вывод множеств.

Цикл foreach по множеству.

Пример использования множеств: решето Эратосфена. Алгоритм.

Лекция 25

Алгоритм Эратосфена

Код алгоритма Эратосфена

const n = 10000;
var primes: set of integer;
begin
   primes := [2..n];
   for var i:=2 to round(sqrt(n)) do
   begin
     if not (i in primes) then
       continue;
     var x := i*i;
     while x<=n do
     begin
       Exclude(primes,x);
       x += i;
     end;
   end;
   writeln(primes);
end.

К основной странице курса

Символы

Коды символов. Однобайтовые и двухбайтовые кодировки. ASCII - стандарт на символы с кодами 0..127. Unicode. Однобайтовые кодировки: Windows, DOS (CP866), Koi8

Литеральные константы-символы:

#13 - символ возврата "каретки"
#10 - символ перехода на следующую строку 
#9  - символ табуляции

Стандартные подпрограммы работы с символами

OrdUnicode(c)
ChrUnicode(i)
Succ(c)
Pred(c) 
Inc(c,5)
Dec(c,5)
Ord(c)
Chr(i)
UpperCase(c)
LowerCase(c)

Для символов с кодами 0..127

OrdUnicode(c)=Ord(c)
ChrUnicode(i)=Chr(i)

char - тип, содержащий ряд статических методов:

char.IsDigit(c)
char.IsLetter(c)
char.IsLetterOrDigit(c)
char.IsLower(c)
char.IsUpper(c)
char.ToLower(c)
char.ToUpper(c)

Лекция 26

Строки

Тип string

Отводится память (до 2 Гб), равная длине строки, плюс некоторая дополнительная информация

Строки - массивы символов, индексируемые с 1: s[i] - i - тый символ строки. var s := 'IT'; // s[1]='I', s[2]='T'

Операции со строками:

s1+s2  
s1 += s2  
s1<s2  
...

Основные подпрограммы работы со строками

Length(s) - функция, возвращающая длину строки s

SetLength(s,n) - процедура, устанавливающая длину строки s равной n

Copy(s,from,len) - функция, возвращающая подстроку s с позиции from длины len

Insert(subs,s,form) - процедура, вставляющая подстроку subs в строку s с позиции from

Delete(s,from,len) - процедура, удаляющая из строки s len символов с позиции from

Pos(subs,s) - функция, возвращающая позицию первого вхождения подстроки subs в строку s или 0, если подстрока не найдена

PosEx(subs,s,from=1) - функция, возвращающая позицию первого вхождения подстроки subs в строку s, начиная с позиции from, или 0, если подстрока не найдена

TrimLeft(s) - функция, возвращающая строку s, в которой удалены все начальные пробелы

TrimRight(s) - функция, возвращающая строку s, в которой удалены все заключительные пробелы

Trim(s) - функция, возвращающая строку s, в которой удалены все удаляет все начальные и заключительные пробелы

Преобразование строка ↔ число

Str(x,s) - процедура, преобразующая целое или вещественное выражение x к строковому представлению и записывающая результат в строку s

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

IntToStr(i) - функция, преобразующая целое x в строку

StrToInt(s) - функция, преобразующая строку s к целому; может генерировать исключение

FloatToStr(i) - функция, преобразующая вещественное x в строку

StrToFloat(s) - функция, преобразующая строку s к вещественному; может генерировать исключение

Некоторые задачи на строки

Посимвольная обработка

  • Строка 'ABCD...XYZ'
  • Сумма всех цифр в строке s

Использование стандартных подпрограмм

  • Вывести индексы всех вхождений подстроки s1 в s (на Delete, PosEx)

См. также

Ссылки