Модераторы: Poseidon, Snowy, bems, MetalFan
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Процедурный тип данных. Что это такое и с чем его едят 
:(
    Опции темы
Fedor
Дата 16.4.2005, 21:29 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Днепрянин
****


Профиль
Группа: Участник
Сообщений: 2090
Регистрация: 8.2.2003
Где: Великий

Репутация: нет
Всего: 32



Процедурный тип данных. Что это такое и с чем его едят.

Подпрограмы в языке паскаль бывют двух типов: процедуры - подпрограммы (чаще всего) не возвращающие никакого результата, и функции, возвращающие какое-то значение.
Давайте представим, что нам нужно произвести какие-то действия с каким-то объектом, например с числом. Например, нам надо вычислить корень кубический этого числа, или возвести в 5-ую степень, или посчитать сумму чисел от 1 до этого числа или ....... Как можно реализовать всё это дело? Можно написать ряд подпрограммок , например с именами SQRT3, SQR5, Symma ... а в решающий момент (когда надо результат получить) мы просто будем вызывать нужную нам из перечисленных функций. Вот наверное первое, что приходит в голову для решения вышеуказанное задачи.
Хорошо мы с вами придумали 3 дествия с числом: корень, возведение в степень и сумму. Функций не много, реализовать не сложно, примерно так:
Код

function SQRT3(x:Real):Real;
Begin
....
End;
function SQR5(X:Real):Real;
Begin
.....
End;
function Symma(X:Integer):Integer;
Begin
....
End;
....
Begin
  ....
  Case чего-то of
      1: Result:=SQRT3(X);
      2: Result:=SQR5(X);
      3: Result:=Symma(X);
  End;
  // пользуемся значением Result в нужных целях далее
  ....
End;

Как я уже сказал функций не много, реализовать не сложно. Теперь смотрите, если мы такую программку составим, сдадим в пользование и через некоторое время нам скажут: "А вот если бы с числом можно было бы производить еще ряд операций" и выдадут нам список из еще 50 наименований функций. :p 
Что будем делать, если текст общей программы большой, найти нужный кусок оччччччень затруднительно.
А вот здесь к нам напомощь и пришёл бы (просто прискакал бы) процедурный тип данных.
идея следующая: функции нам, хочешь - нехочешь, пришлось бы реализовывать в виде отдельных подпрограмм функций.  а вот на вызове нужной мы с экономим: станим вызывать не саму функцию, а ссылку на неё.
Это очень напоминает работу с указателями и динамическими переменными, где по ссылке мы можем получить само значение. На самом деле так оно и получается, в машинных кодах и подпрограммы и пееменные имеют свои адреса, к ним (подпрограммам) и происходит обращение по адресу.
Для такой реализации в паскале надо обявить прототип функции, (с появлением .Net их стали называть делегатами) т е её объявление в общем виде: тип подпрограммы (процедура или функция), список параметров, с указанием типов данных и тип возвращаемого значения, если это функция.
Пример:
Код

Type
  TFunct=Function(X:Real):Real;
  TMyProc=Procedure(A,B,C:Integer;Var S:String);

Здесь обявлены два типа: TFunct - это функция, в качестве параметра которой выступает переменная X вещественного типа, результат - тоже вещественное число; TMyProc - подпрограмма, имеющая три параметра с именами A, B и C целого типа и параметра S строкового типа, который возвращает эта подпрограма. Как видите имена подпрограмм в общем виде не задаются.
TFunct и TMyProc теперь равноправные типы данных паскаля, такие как Integer, Real, Char, MyRecord, MyBigArray .... И соответственно можно объявлять переменные этих типов:
Код

Var
   F:TFunct;
   P:TMyProc;
 

На самом деле переменные F и P - указатели. Но к этим указателям нельзя применять адресную арифметику, к ним применяются только операции присваения и сравнения. В них может содержаться "пустой" укзатель, т е Nil.
Так как переменная F имеет тип данных TFunct, который обявлен как Function(X:Real):Real, то в эту переменную может быть занесена только функция, не подпрограмма!, у каоторой только один параметр, типа Real. Но и это не все функция должна возвращать значение тоже типа Real, если у функции один пааметр типа Real, но она возвращает значение типа Char или String или Integer или ....,  то такую функцию в переменную F занести нельзя!

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

  Result:=F(X);
 Тогда как на самом деле функции F мы не объявляли, т е  у нет кода
Код

Function F(...);
.....

Но у нас есть тип данных
Код

Type
  TFunct=Function(X:Real):Real;

и Переменная
Код

Var
  F:TFunct;

Которые не смотря на отсутствие функции F позволяют выполнить строку Result:=F(X);
Если перед этой строкой в F будет ссылка на функцию, извлекающую корень, то эта строка будет эквивалентна Result:=SQRT3(X); как будто вместо F стоит идентификатор SQRT3. А сохранять в переменной процедурного типа даных ссылку на процедуру очень просто - простым присваиванием:
Код

Type
   TFunct=Function(Var X:String):Real;
Function XYZ(Var X:String):Real;
Begin
...
End;
Var
   Ft:TFunct;
Begin 
  ....
  Ft:=XYZ;  // просто идентификатор функции, без скобок и параметров!!!
  ...


Теперь нескоко слов о специфике реализации таких функций:
все процедурыи функции, имена которых присваиваются процедурным переменным, необходимо компилировать с ключом компиляции $F+. Вложенные процедуры и функции с процедурными переменными использовать нельзя. Ну и самое обидное, это то что в качестве ссылок НЕМОГУТ быть использованы СТАНДАРТНЫЕ процедуры и функции типа sincos и т п. Последнее правда можно легко обойти: просто создать функцию, которая будет в качестве своей реализации использовать стандартную. Например, так может быть реализованы ссылки на тригонометрические функции:
Код

{$F+}  // включаем специальный режим компиляции
Function MySin(X:Real):Real;
Begin
  MySin:=Sin(X);
End;
Function MyCos(X:Real):Real;
Begin
  MyCos:=Cos(X);
End;
{$F-}  // выключаем...
Type
   TTrigFunct=Function(X:Real):Real;
Var
   F:TTrigFunct;
...
  F:=MyCos;
...
  Res:=F(0.5); // эквивалентно MyCos(0.5) или еще проще просто cos(0.5)
....

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

Ну и теперь подитожу. Если в нашей первоначальной задаче, которую наверное уже все забыли, использовать процедурный тип данных, то вызов функции (это вся конструкция case of), будет заменена на строчку вида:
Result:=F(X); 
и в случае добавления еще нескольких функций, мы лишь добавляем реализацию этих функций, ну и добавляем есчо определение, какую же функцию мы хотим вызвать и все. вышеуказанная строка, остаются и все будет работать.

Да замечу, что при использовании массива, эл-ты которого яв-ся переменными процедурного типа, вызов функции, которая назначана I-ому элементу будет выглядеть следующим образом:
Proc(X,Y,z)
Запись, которая может сбить с толку на первый взгляд. Но при боле внимательном рассмотрении все становится на свои места: Proc[I] - вызов эл-та массива Proc, имеющего номер I, а то что стоит дальше - скобки и параметры - это дальнейший вызов нужной нам функции, ведь элемент массива Proc[I] можно заменить обычной, не индексированной переменной, например A, тогда запись более понятна:
A(X,Y,Z)

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

PS: а в некотрых случаях "системного" программирования без переменных процедурного типа просто не обойтись, ведь если решать задачу в общем виде, то скорее всего вы не знаете сразу как будет вычислять функция F(X,Y), а реализация этой функции будет известна только в прикладной программе. Так что если бы не было бы возможности ссылаться на другие, в том числе и внешние подпрограммы, наверное, невозможно было бы в системе windows, использование библиотек (в том числе и DLL), с доработаными или добавленными возможностями.


В качестве примера рассмотрю изобретение велосипеда. smile 
Допустим сочиняем мы контейнер для чего-то. Ну т е коллекцию, в Delphi уже есть готовые классы-коллекции, в Turbo Vision то ж есть такое счастье. ООП рассматривать здесь не буду, т к это уже другая тема.
Так вот пусть наша коллекция будет хранить любые объекты, это значит что нам по большому счету надо будет хранить указатель на объект. Объект у меня это не термин ООП, а даные, т е в качестве объекта может рассматриваться массив, целое число, строка, запись, другой тип данный. Кто говорит что для обозначения любого, что здесь перечислено, нельзя приментть слово "объект" пусть кинет в меня камень!
А сама коллекция будут организована в виде очереди (основанной на динамических переменных). И пусть мы все чего на создадим поместим в библиотеку (может даже и в DLL). И после будем использовать наши труды в других програмах, и даже другим програмистам дадим воспользоваться.
Ну так вот для реализации очереди я предлагаю использовать следующую структуру:
Код

Type
   PItem=^TItem;
   TItem=Record
     Data:Pointer;
     Next,Pred:PItem;  // пусть очередь у нас будет двунаправленная
  End;
Var
  First,Last:PItem;

Далее мы реализуем подпрограммки по работе с этой [i]бедой
, например, добавление элементов, удаление элементов, поиск элементов, упорядочивание элементов...
Насчет добавления и удаления я думаю ничего сложного нет - подпрограммки простые!
Вот как реализовать поиск? 
Ну что начинаем с простого - пусть в нашей коллекции (и точка!), будут храниться только целые числа (точнее указатели на них). Тогда мы могли бы написать что-то вроде:
Код

Function Find(Chislo:Integer):PItem;  // возвращаться будет элемент коллекции или Nil если нет такового
  Var
    CurItem:PItem;
Begin
  CurItem:=First;
  While CurItem<>Nil Do Begin
     If Integer(CurItem^.Data^)=Chislo Then Begin  
        Find:=CurItem;
        Exit
     End;
     CurItem:=CurItem^.Next
  End;
  Find:=Nil
End;

Несложно! Условие, в котором производится приведение значения,хранимого по указателю Data к целому типу я думаю понятно.
А что будет, если у нас в коллекции будут строки? Нужно изменить тип входного параметра, и приводить нужно к другому типу данных.
А если другой программист или мы в программке, использующей эти коллекции, создадим ... ну например запись, и хранить в Data будем указатель на эту запись. Тогда для сравнения, мы должны будем не только привести data^  к типу апись, но и выбрать из этой записи поле по которому будем сравнивать. Например, у нас будет сисок работников, и искать мы будем по имени, то есть по полю FIO в одном случае, а в другом - по окладу..........................
А если.... А что ..... 
типов данных может быть бесчисленное множество. И как сравнивать текущий элемент с эталоном?
Вот тута мы и воспользуемся ссылкой на подпрограмму, которую кстати сами и напишем в каждом конкретном случае, а в подпрограмму Find будем передовать как эталон, так функцию, которая будет вычислять равны ли два значения, или не равны...
Так как в коллекции может храниться все, что угодно, то тип эталона я думаю ясен - это ссылка (указатель) на эталон, т е Pointer. Значит заголовок нашей процедурки будет следующий:
Код

Function Find(Etalon:Pointer;FunctForCompare:[b]тип_функции[/b]):PItem;
...

В реализации мало, что измениться, разве что поменяется немного вид сравнения....
Давайте подумаем, что это за тип_функции нам нужен (это и будет параметр процедурного типа). Ну во-первых это будет функция, кроме того она будет возвращать логическое знаяение: True - если элементы одинаковы, и False - в противном случае. В функции будет сравниваться два значения: эталон и текущие данные. Тип их (я уже не обясняю) Pointer.
Вот и получили следующий процедурный тип:
Код

Type
   TFunctForCompare=Function(Item1,Item2:Pointer):Boolean;

ну и объявление подпрограмки (если кто еще не понял):
Код

Function Find(Etalon:Pointer;FunctForCompare:FunctForCompare):PItem;
...
begin
  ...
       If FunctForCompare(Etalon,CurItem^.Data) Then     // Элементы равны?
  ...
End;

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

Если кто не понял, то в основной программе мы с вами написали что-то типа:
Код

{$F+}
Function CompInteger(Item1,Item2:Pointer):Boolean;  // сравнение двух целых чисел
Begin
   CompInteger:=Integer(Item1^)=Integer(Item2^)
End;
Function CompString(Item1,Item2:Pointer):Boolean;  // двух строк
Begin
   CompString:=String(Item1^)=String(Item2^)
End;
Function CompMyRecord(Item1,Item2:Pointer):Boolean; // двух записей "только что придуманых"
Begin
   CompMyRecord:=MyRecord(Item1^).Pole1=MyRecord(Item2^).Pole1
End;
{$F-}

А в критический момент использовать это все так:
Код

EtalonStr^:='Строка';
If  Find(EtalonStr,CompString)<>Nil Then 
   // в коллекции есть элемент "Строка"!!!

или так:
Код

EtalonInt^:=33;
If  Find(EtalonIntr,CompInteger)<>Nil Then 
   // в коллекции есть элемент со значением 33

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


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

Procedure Sort;
  Var
    CurItem1,CurItem2:PItem;
  Procedure SWAP; // обмен двух элементов местами...
  Begin                    // реализация - задание читающему  :) 
    ....
  End;
Begin
  CurItem1:=First;
  While CurItem1^.Next<>Nil Do Begin
    CurItem2:=CurItem1^.Next;
    While CurItem2<>Nil Do Begin
        If Элемент1 < Элемент2 Then      // вот тут и будем использовать процедурный тип данных
          Swap;    // обмен                        //
        CurItem2:=CurItem2^.next
    End;
    CurItem1:=CurItem1^.Next
  End
End;

Теперь Нам надо выяснить как относятся друг к другу два элемента:
-они равны;
-первый больше вторго;
-первый меньше второго.
задача непосредственного сравнения остаётся за основной подпрограммой, и в каждом конкретном случае будет своя.
Нам остаётся определить обявление функции - какие параметры, какого типа, что ф-ция будет возвращать. Сравниваем мы опять два элемента, поэтому с входными параметрами ясно. Результатов сравнения у нас три - использовать логический тип данных в принципе можно, но не желательно, давайте будет разнообразновать свою жизнь. smile
Поэтому пусть наша ф-ция возвращает целые числа:
0 - равные элементы;
-1 - первый больше вторго;
+1 - первый меньше второго.
соответственно тип такой функции следующий:
Код

Type
  TFunctForSort=Function(Item1,Item2:Pointer):Integer;

А дальше.... а дальше так же как и с подпрограммой поиска.... 

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


--------------------
Мы - Днепряне. Мы всех сильней.
PM ICQ   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Delphi: Общие вопросы"
SnowyMetalFan
bemsPoseidon
Rrader

Запрещается!

1. Публиковать ссылки на вскрытые компоненты

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

  • Литературу по Дельфи обсуждаем здесь
  • Действия модераторов можно обсудить здесь
  • С просьбами о написании курсовой, реферата и т.п. обращаться сюда
  • Вопросы по реализации алгоритмов рассматриваются здесь
  • 90% ответов на свои вопросы можно найти в DRKB (Delphi Russian Knowledge Base) - крупнейшем в рунете сборнике материалов по Дельфи


Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Snowy, MetalFan, bems, Poseidon, Rrader.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Delphi: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.0906 ]   [ Использовано запросов: 23 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.