Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

[ Миронченко ] Императивное и объектно-ориентированное програмирование на Turbo Pascal и Delphi

.pdf
Скачиваний:
68
Добавлен:
25.04.2014
Размер:
3.16 Mб
Скачать

121

нулей и единица – к элементу C[2]. В результате в С[1] должен быть записан ноль, а в C[2] единица.

c[1] c[2]

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1

Но если вы заглянете в результаты работы программы, то вы заметите прямо противоположную ситуацию: С[1]=1, а C[2]=0. А все дело вот в чем: в памяти байты хранятся в обратном порядке: если число 1 в двоичной системе имеет вид 00000000 00000001, то в памяти оно хранится так: 00000001 00000000. Именно этим и объясняется то, что числа 1, 2, 3 хранятся в обратном порядке.

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

GrossMas(v)[i]:=i+255;

Посмотрите, какие будут результаты и разберитесь, что там и как.

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

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

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

7.8.Процедурный и функциональный типы

Вшколе всем нам давали определение функции y = f (x) как зависимости между значениями независимой переменной x и соответствующими значениями y . В то же

время в математике часто рассматриваются зависимости более общего вида: h( y) = F ( y(x)) , где каждой функции y(x) соответствуют определенные значения h( y) .

Такие зависимости, где независимой переменной являются не числа, а функции,

обычно называют функционалами.

 

Примеры функционалов:

 

h( y(x)) = y(1) , т.е. каждой функции y(x) ставится в

соответствие ее значение в

 

точке x = 1 (предполагается, что функция y(x) определена в этой точке).

 

b

 

I ( f (x)) = f (x)dx - интегральный функционал ( f (x)

- интегрируема на сегменте

a

[a,b] ).

Замечание: если функционал F ( y) задан на множестве функций вида y(x) = c , c R , то мы фактически определяем обычную функцию, заданную на множестве R . Поэтому

122

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

Раньше мы с вами писали подпрограммы, которые принимали в качестве параметров либо числа, либо массивы чисел, - т.е. с точки зрения математики мы писали функции. Но часто требуется в подпрограммы передавать другие подпрограммы, - т.е. программировать функционалы. Например, если наша цель – отсортировать массив, то необходимо написать 2 процедуры: сортировку по возрастанию, и сортировку по убыванию. При этом обе процедуры будут отличаться лишь условиями в операторе if.

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

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

1

:

const

 

2

:

n=6;

 

3

:

type

 

4

:

Mas=array [1..n] of longint;

5

:

Funk=function(a,b:longint):boolean;

6

:

 

 

7

: function Mehr(a,b:longint):boolean; far;

8

: begin

{Mehr = больше}

9

:

Mehr:=a>b;

 

10: end;

 

11:

 

 

12:

function Weniger(a,b:longint):boolean; far;

13:

begin

{Weniger = меньше}

14:Weniger:=a<b;

15:end;

16:

17:procedure RandFulle(var A:array of longint;k:word);

18:var

19:i:integer;

20:begin

21:for i:=0 to High(A) do

22:A[i]:=random(k);

23:end;

24:

25: procedure DruckMas(var A:array of longint);

26: var {drucken - печатать (сравните: друкувати)}

27:i:integer;

28:begin

29:for i:=0 to High(A) do

30:Write(A[i],' ');

31:writeln;

32:end;

123

33:

34:procedure KahnSort(var A:array of longint;F:Funk); {Kahn - чёлн}

35:var

36:i,j:integer;

37:tmp:longint;

38:begin

39:for i:=0 to High(A)-1 do

40:if F(A[i+1],A[i])=false then {нарушен ли порядок}

41:begin

42:

tmp:=A[i];

{меняем элементы местами}

43:A[i]:=A[i+1];

44:A[i+1]:=tmp;

45:for j:=i downto 1 do {погружаем элемент}

46:

if F(A[j],A[j-1])=false then{если элемент не на месте}

47:

begin

{то меняем его с предыдущим}

48:

tmp:=A[j-1];

 

49:

A[j-1]:=A[j];

 

50:

A[j]:=tmp;

 

51:

end

 

52:else

53: break;

54:end;

55:end;

56:

57:var

58:M:Mas;

59:begin

60:randomize;

61:RandFulle(M,1000);

62:writeln('Исходный массив');

63:DruckMas(M);

64:KahnSort(M,Mehr);

65:writeln('Отсортированный массив');

66:DruckMas(M);

67:end.

В5-й строке объявлен тип Funk (функция = Funktion). Типу Funk принадлежит любая функция с формальными параметрами (a,b:longint) и типом возвращаемого значения boolean.

Вэтом примере таких функций две: Mehr и Weniger. Заметьте, что после заголовка этих функций стоит слово far. Оно означает то, что функция будет компилироваться с расчетом на дальнюю модель памяти. Есть 2 модели памяти – ближняя и дальняя. При ближней модели памяти вызов подпрограммы выполняется быстрее, а при дальней – медленнее. Но ближние вызовы можно делать только в рамках того модуля данных, в котором описана подпрограмма, а на дальние вызовы такого ограничения нет.

Для того чтобы при вызове подпрограммы использовалась дальняя модель памяти, можно писать после названия подпрограммы директиву far (см. строки 7, 12). Если надо использовать ближнюю модель, то можно ставить директиву near (но

124

ближняя модель памяти используется по умолчанию, поэтому слово near писать не обязательно).

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

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

Для нас главное следующее: в процедуре KahnSort, кроме самого массива есть еще один параметр: функция типа Funk, цель которой задавать порядок расположения элементов: в строках 40 и 46 программа проверяет, нарушен ли порядок следования элементов. Если в качестве функции F будет передана функция Mehr, то функция F будет проверять, не является ли первый аргумент больше второго, если же Weniger, - то, наоборот, будет ли первый меньше второго.

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

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

Задачи

1.Написать функции tg(x), ctg(x), a x , chx, shx, loga b , arcsin(x), arccos(x), arcctg(x).

2.Написать функции {x}, [x] (только правильно работающие!)

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

4.Написать функцию нахождения индекса минимального элемента в массиве.

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

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

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

8.Даны все стороны треугольника. Найти его площадь и периметр. (Вычисление площади и периметра должны быть отдельными функциями).

125

9.Функция, которая во заданному числу возвращает его “перевертыш”. (Например: 1235 – 5321).

10.Последовательность Пупкина образуется следующим образом: первое число последовательности – единица, далее к уже записанным числам дописываются они же, но все нули заменяются на единицы и наоборот (начало последовательности: 10010110…). Напишите функцию, выдающую n-ю цифру последовательности Пупкина.

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

12.Теперь решите предыдущую задачу, если одна купюра достоинством в 3 тугрика а вторая -любое число k, которое больше 3, и не делится на 3.

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

14.У вас есть монеты достоинством в 1, 2, 5, 10, 25, 50 копеек. Напишите программу, которая любую введенную сумму выдавала бы минимальным количеством монет. Алгоритм должен быть оптимален.

15.Обобщите предыдущую задачу: пусть у вас в распоряжении есть монеты достоинством в a1, a2 ,..., an . Можно ли просто перенести результаты задачи 14 на общий случай?

16.Челноку на 7-м километре заказали партию в N пар носков. В Турции, где он закупает товар, ему предложили скидки в 5% за каждую полную связку (12 пар носков) и за каждую коробку (12 связок) 15%. Челноку пришло в голову, что хотя лицензии на розничную торговлю у него нет и не предвидится, а заказчик бОльшую партию носков купить откажется, но ему может оказаться выгоднее купить больше носков и часть выкинуть, чем покупать в точности заказанную партию носков. Помогите челноку подсчитать, сколько носков ему надо купить, чтобы и заказ выполнить и заплатить как можно меньшую сумму: напишите функцию, которая по введенному числу N возвращает кол-во носков, которые челнок должен купить.

17.Пусть n N .

Функция

Эйлера

ϕ(n)

равна количеству значений из

множества

{0,1, 2,..., n −1} ,

взаимно

простых

с

n . Например, ϕ(1) = 1, ϕ(2) = 1,

ϕ(3) = 2 , …

Покажите, что если p - простое число, то ϕ( p) = p −1, и вычислите ϕ ( pe ), где e > 0 .

18.Функция f (n) называется мультипликативной, если f (rs ) = f (r ) f (s ) . Докажите, что

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

19.Докажите, что функция Эйлера (см. упражнение 17) мультипликативна. На основе этого предложите простой способ вычисления ϕ (n) , если n разложено на простые множители.

b

20.(!) Напишите функцию, которая бы вычисляла приближенное значение f (x)dx .

a

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

126

Проект 1: Длинная Арифметика

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

Представлять числа будем в виде массива, причем: const

MaxZiff=300;{Максимальное количество разрядов (Radix-ичных)} Radix=1000;{основание системы счисления}

type

LangeZ = array [0..MaxZiff] Of integer; {lange Zahl - длинное число}

В А[0] будет находиться количество Radix-цифр в числе, а дальше «цифры» числа в обратном порядке, например:

12345678910=12 10003 +345 10002 +678 10001 +910 10000

Значит, представление чисел будет такое:

4

910

678

345

12

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

Вашей целью будет написать такие подпрограммы:

1.сложение чисел

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

3.умножение чисел

4.вычисление остатка от деления (аналог mod)

5.вычисление неполного частного (аналог div)

6.вычисление НОД 2-х чисел.

Т.к. числа большие и в стандартные типы данных не влезают, а пользователю неудобно вводить число в виде массива, то мы будем считывать с клавиатуры число в виде строки (тип string), и затем переводить его из строки в LongI.

Тип string – это строковый тип. Мы его не проходили, поэтому я написал для вас подпрограммы перевода числа в строку и обратно, а также несколько других вспомогательных подпрограмм. Вводить строки можно с помощью read и readln, а печатать с помощью write и writeln.

Процедура AddLangeZ складывает 2 длинных натуральных числа, поэтому пункт 1 уже выполнен.

Пример 1: Вспомогательные функции для «длинной арифметики».

1 : uses

2 : Crt;

3 : const

 

 

 

127

4

:

MaxZiff=300;{Максимальное количество разрядов (Radix-ичных)}

5

:

Radix=1000;{основание системы счисления}

 

6

: type

 

7

:

LangeZ = array [0..MaxZiff] Of integer;

{Длинное число}

8

:

 

 

9

: procedure Nullieren(var A:LangeZ); {Обнуление числа}

10:var

11:i:integer;

12:begin

13:for i:=1 to MaxZiff do

14:A[i]:=0;

15:A[0]:=1;

16:end;

17:

18:{Записывает в А число, хранящееся в строке Int}

19:procedure StellLangeZ(var Int:string;var A:LangeZ);

20:var

21:i,j:byte;

22:begin

23:Nullieren(A);

24:for i:=1 to length(Int) do

25:begin

26:for j:=A[0] downto 1 do

27:begin

28:A[j+1]:=A[j+1]+ ((A[j]*10) div Radix);

29:A[j]:=(A[j]*10) mod Radix;

30:end;

31:A[1]:=A[1]+ord(Int[i])-ord('0');

32:if (A[A[0]+1]>0) then

33:inc(A[0]);

34:end;

35:end;

36:

37:{Переводит длинное число А в строковое представление}

38:function LangeZZurZeile(Var A:LangeZ):string;

39:var

40:s,s1,tmp:string;

41:i,k:byte;

42:Begin

43:{В tmp кол-во символов = кол-ву десятичных цифр в Radix div 10}

44:Str(Radix div 10,tmp);

45:Str(A[A[0]],s);

46:for i:=A[0]-1 downto 1 do

47:begin

48:str(A[i],s1);

49:for k:=1 to (length(tmp)-length(s1)) do

50:s:=s+'0';

51:s:=s+s1;

52:end;

53:LangeZZurZeile:=s;

54:end;

55:

128

56:{KopiereLangeZ(А,В) это просто B:=A}

57:procedure KopiereLangeZ(const A:LangeZ;var B:LangeZ);

58:var

59:i:integer;

60:begin

61:Nullieren(B);

62:for i:=0 to A[0] do

63:B[i]:=A[i];

64:end;

65:

66:{Ввод числа А с клавиатуры (заканчивается нажатием Enter}

67:procedure LeseLangeZ(var A:LangeZ);

68:var

69:s:string;

70:begin

71:Nullieren(A);

72:readln(s);

73:StellLangeZ(s,A);

74:end;

75:

76:{C:=A+B}

77:procedure AddLangeZ(const A,B:LangeZ;var C:LangeZ);

78:var i:byte;

79:k:longint;

80:begin

81:Nullieren(C);

82:if A[0]>B[0] then

83:k:=A[0]

84:else

85:k:=B[0];

86:for i:=1 to k do {Складываем столбиком разряды}

87:

begin

{Помните: разряды хранятся в обратном порядке}

88:C[i]:=A[i]+B[i]+C[i];

89:if C[i]>=Radix then {Если перешли на старший разряд}

90:begin

91:inc(C[i+1]);

92:C[i]:=C[i]-Radix;

93:end;

94:end;

95:if C[k+1]>0 then {Устанавливаем номер старшего разряда}

96:C[0]:=k+1

97:else

98:C[0]:=k;

99:end;

100:

101:var

102:A,B,C:LangeZ;

103:s1,s2:string;

104:BEGIN

105:ClrScr;

106:s1:='123334';

107:s2:='9432441';

129

108:

109:StellLangeZ(s1,A);

110:writeln(LangeZZurZeile(A));

111:StellLangeZ(s2,B);

112:writeln(LangeZZurZeile(B));

113:AddLangeZ(A,B,C);

114:writeln(LangeZZurZeile(C));

115:readln;

116:End.

130

Глава 8: Рекурсия

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

8.1. Рекурсия в биологии

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

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

Рекурсивный характер размножения характерен не только для живых организмов

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

А теперь вспомните, как размножаются гидры – новые особи «отпочковываются» от тела матери. В результате получается оригинальный организм: на одной гидре находится много гидр меньших размеров, а на них уже начинают появляться крохотные гидрочки. Чем не рекурсия?!

8.2. Рекурсия в литературе

Структура предложения

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

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