[ Миронченко ] Императивное и объектно-ориентированное програмирование на Turbo Pascal и Delphi
.pdf311
Как вы уже догадались, у интерфейсов тоже есть понятие наследования. Базовый интерфейс, от которого всегда наследует любой другой интерфейс – это IInterface.
Функции, которые описаны в интерфейсе, не определяются, как и абстрактные функции. Фактически, интерфейс – это аналог абстрактного класса, только в нем все без исключения функции являются абстрактными.
Интерфейсы используются для создания на их основе новых классов: Класс может наследовать методы от интерфейса (при этом говорится, что класс удовлетворяет интерфейсу). Тогда в классе должны быть написаны реализации для всех методов, которые перечислены в объявлении интерфейса.
Пример объявления класса:
Interfklasse = class (MeinInterf)
{объявления полей, методов и свойств класса}
public
function ZurZeile:string;
function MeineFunk(x,y:real):real;
end
Пример 18: Построение класса с использованием интерфейсов. program Interrf1;
{$APPTYPE CONSOLE}
uses SysUtils;
type
ISchreib = interface(IInterface) function ZurZeile:string;
end;
SchrKl = class(TInterfacedObject,ISchreib) private
x:integer; public
constructor Bilde(y:integer); function ZurZeile:string;
end;
constructor SchrKl.Bilde(y:integer); begin
x:=y;
end;
function SchrKl.ZurZeile:string; begin
result:=IntToStr(x);
end;
var S:SchrKl;
312
begin S:=SchrKl.Bilde(23); writeln(S.ZurZeile); readln;
end.
Наследовать класс может только от одного класса, но он может удовлетворять нескольким интерфейсам (они должны перечисляться через запятую в скобках после слова class).
С помощью интерфейсов можно вводить родство между классами по функциональному наполнению. Это бывает полезно, т.к. некоторые действия различных классов могут быть очень похожими – следовательно, можно сделать интерфейс, которому удовлетворяли бы оба класса. Например, глаза у осьминога и человека очень похожи по строению, хотя ветви дерева видов, которым принадлежат человек и осьминог, разошлись очень давно.
В Delphi можно динамически проверять принадлежность класса интерфейсу с помощью оператора as. Но для этого интерфейс должен удовлетворять следующим условиям:
1.Интерфейс должен удовлетворять интерфейсу IInterface (т.е. этот интерфейс должен обязательно стоять в списке интерфейсов в скобках после ключевого слова interface).
2.У интерфейса должен быть специальный номер – GUID. Он следует после списка интерфейсов, от которых наследует интерфейс.
Этот номер нужен для того, чтобы каждый интерфейс (т.е. набор функций) можно было бы идентифицировать независимо от того, какое у него название. Чтобы создать GUID в Delphi можно просто нажать комбинацию клавиш Ctrl+Shift+G. Тогда будет вызвана специальная функция, генерирующая номер.
В следующем примере создается массив ссылок на объекты типа TInterfacedObject. Этот массив мы будем заполнять случайным образом ссылками на объекты 3-х разных классов, два из которых удовлетворяют интерфейсу IEtw, а один - нет. Потом мы должны будем пройтись по элементам массива и для каждого элемента проверить, удовлетворяет ли он интерфейсу IEtw, и если да, то вызвать метод, который описан в интерфейсе.
Для динамической проверки, удовлетворяет ли объект интерфейсу, используется оператор as (см. строку 58):
V:=M[i] as Ietw;
Если M[i] удовлетворяет интерфейсу IEtw, то в V будет записана ссылка на интерфейс. В противном случае возникнет исключительная ситуация. Подробнее мы рассмотрим обработку исключительных ситуаций в следующей главе, пока же остановимся на главном: если возникает исключительная ситуация во время работы программы (т.е. выполняется некоторое недопустимое действие), то можно с помощью специального оператора продолжить выполнение программы. Для этого служит оператор try, после которого идут операторы, которые могут быть потенциально опасны. После этих операторов можно поместить секцию except, которая предписывает действия, которые надо выполнить в случае, если при выполнении этих операторов возникла ошибка.
313
Пример 19: Проверка, удовлетворяет ли класс интерфейсу.
1 : program InterfAs;
2 :
3 : {$APPTYPE CONSOLE}
4 :
5 : uses
6 : SysUtils;
7 :
8 : const
9 : n=10;
10:type
11:IEtw = interface(IInterface)['{D3835685-687C-44EF-A6D6- FCE01886C091}']
12:function Neues:integer;
13:end;
14:
15:Kl = class(TInterfacedObject,IEtw)
16:function Neues:integer;
17:end;
18:
19:Kl2 = class(TInterfacedObject,IEtw)
20:function Neues:integer;
21:end;
22:
23: Kl3 = class(TInterfacedObject) //не поддерживает интерфейс 24: end;
25:
26: Mas = array [1..n] of TInterfacedObject; 27:
28:function Kl.Neues:integer;
29:begin
30:result:=5;
31:end;
32:
33:function Kl2.Neues:integer;
34:begin
35:result:=6;
36:end;
37:
38:var
39:V:IEtw;
40:M:Mas;
41:i,x:integer;
42:begin
43:randomize;
44:for i:=1 to n do
45:begin
46:x:=random(3);
47:case x of
48:0: M[i]:=Kl.Create;
49:1: M[i]:=Kl2.Create;
50:2: M[i]:=Kl3.Create;
314
51:end
52:end;
54:for i:=1 to n do
55:begin
56:try
57:write(M[i].ClassName,' '); //печатаем название класса
58:V:=M[i] as Ietw;
59:writeln(V.Neues);
60:except
61:writeln; //ничего не делаем
62:end;
63:end;
64:
65:readln;
66:end.
Окончательный анализ концепций ООП мы дадим в заключительной части этой книги. Сейчас мы ответим на один из вопросов, который был сформулирован в части «Выведение», а именно: каким образом можно избежать применения бестиповых указателей и при этом достичь общности программ? Решение этой проблемы достигается с помощью следующих нововведений:
1.Используются только ссылки на объекты.
2.Все объекты имеют общего родителя – TObject
3.Контроль типов во время выполнения (механизм RTTI)
Кроме того, можно использовать метаклассы, что дает дополнительные возможности для написания абстрактных программ.
16.20. Новое в Delphi 2005
Все, о чем шла речь в этой главе, было еще в 6-й версии Delphi. Сейчас мы очень кратко рассмотрим новинки, которые появились в Delphi 2005. Сразу скажу, что с точки зрения языка все нововведения не являются фундаментальными. Большинство из них – лишь желание подстроиться под платформу .NET. Некоторые из новинок можно использовать только работая на .NET, а при работе в приложениях под Win32 их нет. Из-за этого программисту нужно запоминать много лишней информации, да и красота языка страдает.
Определители видимости элементов класса
ВDelphi 2005 появилось 2 новых определителя видимости элементов класса – strict private и strict protected. Смысл в них такой: если private-элементы видны не только внутри класса, но и внутри модуля, в котором объявлен класс, то strict privateэлементы видны только внутри класса. Аналогично, strict protected-элементы класса видны лишь внутри класса и его потомков.
Впринципе, введение подобных дескрипторов правильно, ибо неясно, зачем разрешать доступ к членам класса его соседям по модулю. Другое дело, зачем вообще было вводить обычные private и protected?
315
Запечатанные и абстрактные классы
ВDelphi 2005 появились 2 новых вида классов: sealed (запечатанные), abstract (абстрактные). У запечатанных классов не может быть наследников, но можно создавать экземпляры класса, а у абстрактных классов наоборот: нельзя создавать экземпляры классов, но можно создавать потомков. У абстрактного класса не обязательно должны быть абстрактные функции, а класс с абстрактными методами не обязан быть абстрактным (абстрактность методов и абстрактность классов – независимые понятия)16.
Запечатанность классов действует и при программировании на .NET и при использовании Win32. А абстрактность классов имеет смысл лишь в .NET (при программировании приложений под Win32 слово abstract писать можно, но оно не будет ничего означать).
Вследующем примере (под Win32) если раскомментировать определение класса R, то будет выдана ошибка, т.к. от класса, помеченного sealed наследовать нельзя, но при этом вполне можно создавать экземпляры абстрактных классов.
Пример 20: Запечатанные и абстрактные классы. program SealAbstr;
{$APPTYPE CONSOLE} uses
SysUtils; type
Sld = class sealed x:integer;
end;
Abst = class abstract x:integer;
end;
{ R = class (Sld) //Ошибка - нельзЯ наследовать end;}
var A:Abst;
begin
A:=Abst.Create; {Ошибки нет!!!}
A.x:=6;
writeln(A.x);
readln;
end.
Вложенные классы
В Delphi 2005 разрешили внутри класса объявлять вложенные подклассы. Тогда они не видны вне этого класса (хотя мы в примере посмотрим, что на самом деле можно добраться и до них). Рассмотрим на примере, как определяются вложенные классы.
16 Классы, которые мы раньше называли абстрактными, в Delphi 2005 логичнее называть, чтобы не возникало путаницы, абстрактнофункциональными.
316
Пример 21: Использование вложенных классов.
1 : program InnKlassen;
2 :
3 : {$APPTYPE CONSOLE}
4 :
5 : uses
6 : SysUtils,
7 : Graphics;
8 :
9 : type
10:Pixel = class
11:type
12: |
Punkt = class |
{вложенный класс} |
13:strict private
14:x,y:real;
15:public
16:constructor Bilde(xx,yy:real);
17:function GibEukNorm:real;
18:end;
19:
20:strict private
21:P:Punkt;
22:F:TColor;
23:public
24:constructor Bilde(x,y:real;frb:TColor);
25:end;
26:
27: constructor Pixel.Punkt.Bilde(xx,yy:real); //просто Punkt.Bilde
писать нельзЯ
28:begin
29:x:=xx;
30:y:=yy;
31:end;
32:
33: function Pixel.Punkt.GibEukNorm:real; //просто Punkt.GibEukNorm
писать нельзЯ
34:begin
35:result:=sqrt(x*x+y*y);
36:end;
37:
38:constructor Pixel.Bilde(x,y:real;frb:TColor);
39:begin
40:if x<=0 then
41:x:=0;
42:if y<=0 then
43:y:=0;
44:P:=Punkt.Bilde(x,y);
45:F:=frb;
46:end;
47:
48: var
317
49:Pix:Pixel;
50:P:Pixel.Punkt; {Написать просто P:Punkt нельзЯ}
51:begin
52:Pix:=Pixel.Bilde(10,20,$00ff0000);
53:P:=Pixel.Punkt.Bilde(10,-10);
54:writeln(P.GibEukNorm:8:6);
55:readln;
56:end.
Внутри класса Pixel объявляется вложенный класс Punkt. Сам же класс Pixel содержит объект класса Punkt в качестве строго закрытого поля. Чтобы написать реализации для методов вложенных классов, надо (см. строки 27, 33), надо указать имя внешнего класса, затем – имя внутреннего и потом – название метода.
Если попытаться в разделе var указать P:Punkt (строка 50), то компилятор выдаст сообщение об ошибке (класс-то вложенный), но тем не менее разработчики Delphi дали возможность программисту все же объявить экземпляры вложенный классов вне класса, в котором он объявлен (см. стр. 50). Быть может, разработчики Delphi решили ввести более громоздкую запись для обозначения вложенных классов из-за того, что в двух разных классах могут быть вложенные классы с одинаковым названием (но разной реализацией). Мне кажется, что логичнее было бы сделать вложенные классы вообще недоступными вне класса-оболочки.
Пространства имен
В ТР и Delphi программный код разбивался на модули, а каждый модуль был своеобразной оболочкой для класса. В Delphi 2005 в дополнение к модулям добавили понятие пространства имен. С точки зрения Delphi пространства имен – это надмодульные образования, которые позволяют строить иерархии. Пространства имен ввели из-за того, что количество классов, которые используются в программах, а также количество модулей, стало огромным (количество классов стандартных библиотек для студий программирования уже исчисляется тысячами). Поэтому хотелось каким-то образом навести порядок, тем более что иногда встречаются одинаковые имена классов и надо как-то их различать. Мы напишем один элементарный пример, на котором мы рассмотрим, как объявляются пространства имен. Более детальную информацию вы можете получить в справочной системе.
Пример 22: Использование пространств имен.
--------------Модуль Unit1 пространства имен MeineNamespace-----------
1 : unit MeineNamespace.Unit1;
2 :
3 : interface
4 : type
5 : Klasse = class
6 : end;
7 :
8 : implementation 9: end.
--------------Модуль Unit2 пространства имен MeineNamespace-----------
1 : unit MeineNamespace.Unit2;
318
2 :
3 : interface
4 : type
5 : Klasse = class
6 : private
7 : x:integer;
8 : end;
9 :
10:implementation
11:end.
--------------Основной файл проекта-----------
1 : program NameSp;
2 : {$APPTYPE CONSOLE}
3 :
4 : uses
5 : MeineNamespace.Unit1,
6 : MeineNamespace.Unit2;
7 : var
8 : Kl:MeineNamespace.Unit1.Klasse;
9 : Kl2:Klasse;
10:begin
11:Kl:=MeineNamespace.Unit1.Klasse.Create;
12:Kl2:=Klasse.Create;
13:end.
Чтобы вложить модуль в пространство имен, надо перед названием модуля поставить название соответствующего пространства имен и поставить точку. Файл с кодом модуля (для MeineNamespace.Unit1) будет называться при этом
(MeineNamespace.Unit1.pas).
В примере 22 в обоих модулях определен класс Klasse. Для того чтобы объявить экземпляр нужного класса Klasse, надо указать модуль, в котором он находится (см. строку 8). В строке 9, несмотря на то, что мы пишем просто
Kl2:Klasse;
имеется в виду
Kl2: MeineNamespace.Unit1.Klasse;
Еще кое-что
В Delphi 2005, если программировать на платформе .NET, можно использовать т.н. перегрузку операторов. Перегрузка операторов для классов – это возможность использования операций и операторов для экземпляров класса. Например, можно сделать класс «длинное целое», для которого перегрузить все стандартные операции (арифметические и логические операции). Перегрузка операторов (и операций) действительно выглядит симпатично, когда классы – это некоторые математические объекты, для которых ясно, какой смысл вкладывается в операции. Но мне кажется, что этих преимуществ недостаточно, чтобы оправдать введение перегрузки операторов.
Быть может, оригинальнее было бы функции для двух аргументов записывать не «Funk(a,b)», а «а Funk b». В таком случае можно было бы интерпретировать операции как частный случай функций.
319
Есть в Delphi 2005 понятие помощников классов. С их помощью можно расширять классы, не создавая наследников классов. Часто пишут, что helper’ы очень полезны тогда, когда требуется расширить классы, которые помечены как sealed. Но в таком случае, зачем было вообще вводить sealed-классы, чтобы потом давать окольные пути для их расширения.
В общем, все нововведения Delphi 2005 выглядят очень спорными: такое ощущение, что они созданы скорее для того, чтобы сделать язык более громоздким, чем чтобы сделать его красивее и мощнее.
Задачи
1.Создайте класс Mensch (человек), в котором бы хранились сведения о человеке: фамилия, имя, отчество, телефон, адрес, e-mail. Вы должны написать базовые методы класса: возможность установки/чтения полей, преобразование в строку и т.д.
2.Создайте наследника класса Mensch – Student, в котором добавились бы следующие поля:
•ВУЗ, в котором учится студент
•Стипендия
3.Объявите интерфейс KannVergleichen (“могу сравнивать”), в котором был бы метод, позволяющий сравнить 2 элемента типа Mensch.
4.Напишите класс, который представлял бы собой список ссылок на объекты класса Mensch. Этот класс должен удовлетворять интерфейсу KannVergeichen. Реализуйте основные операции над списком (вставка, элементов в начало и конец списка, вставка элемента в произвольную позицию и удаление элемента), а также сортировку с помощью метода, который объявлен в интерфейсе.
5.Добавьте в класс, который вы написали в предыдущем примере метод поиска заданного человека.
6.Напишите класс Arithmetik, в котором были бы следующие методы класса:
•Возведение числа в целую степень (количество операций должно быть порядка логарифма от показателя степени)
•Возведение числа в степень по заданному модулю (т.е. вычисление ab mod n )
•Нахождение НОД двух чисел
•Вычисление функции Эйлера ϕ(n) (ϕ(n) = количеству простых делителей числа n ).
7.Создайте класс-обертку Menge динамического массива, который имитировал бы множество (т.е. фактически мы будем иметь дело с битовым массивом).
8.Добавьте в класс Arithmetik (см. упражнение 6) функцию, которая вычисляет количество целых чисел от 0 до n включительно с помощью перебора делителей для каждого числа.
9.Добавьте в класс Arithmetik (см. упражнение 6) функцию, которая вычисляет количество целых чисел от 0 до n включительно с помощью решета Эратосфена (используйте класс Menge из упражнения 7).
10.Подумайте, каким образом могут быть реализованы наследование и полиморфизм с помощью чудо-записей.
320
Проект 5: Криптография
Основные определения:
•Криптография – это наука, изучающая шифры, методы шифрования и расшифровки информации.
•Шифрование – преобразование текста по некоторому алгоритму.
•Ключ – параметр алгоритма шифрования.
Нашей целью будет написать программу, которая будет зашифровывать сообщения, закодированные простыми методами и взламывать их. Это – основная цель. Но вы помните, что большие проекты должны удовлетворять и другим качествам, а именно: возможность модернизации программы, а также абстрактность (т.е. возможность использования кусков программы для написания ПО, имеющего достаточно отдаленное отношение к исходной программе. Поэтому вы должны писать классы, которые можно будет использовать не только для криптографии, но и для задач, связанных с лингвистикой.
Шифры применялись еще в древности: многие переписки государственных деятелей зашифровывались, чтобы обеспечить сохранность информации.
Рассмотрим 2 простых шифра.
|
|
|
|
|
|
1. |
Шифр Юлия Цезаря |
||||||||||||||||||||
|
|
Пусть буквы пронумерованы от 0 до n-1. Тогда согласно правилу Цезаря буква с |
|||||||||||||||||||||||||
номером x переходит в букву с номером (x + a) mod n . |
|
|
|
|
|
|
|
|
|
|
|||||||||||||||||
Сдвиг на 4 единицы можно представить следующей подстановкой: (A[1,j] ->A[2,j]). |
|||||||||||||||||||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
a |
b |
c |
d |
e |
f |
g |
h |
i |
j |
k |
|
l |
m |
n |
o |
p |
q |
r |
s |
t |
u |
v |
w |
x |
y |
z |
|
e |
f |
g |
h |
i |
j |
k |
l |
m |
n |
o |
|
p |
q |
r |
s |
t |
u |
v |
w |
x |
y |
z |
a |
b |
c |
d |
|
С помощью этого шифра сообщение |
|
|
|
|
|
|
|
|
|
|
|||||||||||||||||
I am programmer. |
|
|
превратится в |
M eq tvskveqqiv. |
|||||||||||||||||||||||
2. Подстановочный шифр
Подстановочный шифр – обобщение шифра Цезаря: в подстановке нижняя строка – произвольная перестановка букв латинского алфавита.
Расшифровка текстов
Если ключ известен, то расшифровать текст легко. Если же вы ключ не знаете, то сложностей гораздо больше.
Для расшифровки шифра Цезаря достаточно знать одну букву, - после этого находим смещение, и легко восстанавливаем весь недостающий алфавит.
Поэтому наиболее простой метод расшифровки этого шифра – просто перебрать все 26 возможных ключей, и посмотреть, какой из них подходит к тексту (т.е. слова, получившиеся после подстановки шифра получились правильными).
Ясно, что для этого надо запастись хорошим словарем.
Ксожалению, этот метод не подходит для расшифровки подстановочного шифра
–в нем количество возможных вариантов = 26!, и перебор, естественно, невозможен.
