![]() |
Модераторы: Poseidon, Snowy, bems, MetalFan |
![]() ![]() ![]() |
|
Fedor |
|
||||||||||||||||||||||||||||||||||||||
![]() Днепрянин ![]() ![]() ![]() ![]() Профиль Группа: Участник Сообщений: 2090 Регистрация: 8.2.2003 Где: Великий Репутация: нет Всего: 32 |
Процедурный тип данных. Что это такое и с чем его едят. Подпрограмы в языке паскаль бывют двух типов: процедуры - подпрограммы (чаще всего) не возвращающие никакого результата, и функции, возвращающие какое-то значение. Давайте представим, что нам нужно произвести какие-то действия с каким-то объектом, например с числом. Например, нам надо вычислить корень кубический этого числа, или возвести в 5-ую степень, или посчитать сумму чисел от 1 до этого числа или ....... Как можно реализовать всё это дело? Можно написать ряд подпрограммок , например с именами SQRT3, SQR5, Symma ... а в решающий момент (когда надо результат получить) мы просто будем вызывать нужную нам из перечисленных функций. Вот наверное первое, что приходит в голову для решения вышеуказанное задачи. Хорошо мы с вами придумали 3 дествия с числом: корень, возведение в степень и сумму. Функций не много, реализовать не сложно, примерно так:
Как я уже сказал функций не много, реализовать не сложно. Теперь смотрите, если мы такую программку составим, сдадим в пользование и через некоторое время нам скажут: "А вот если бы с числом можно было бы производить еще ряд операций" и выдадут нам список из еще 50 наименований функций. :p Что будем делать, если текст общей программы большой, найти нужный кусок оччччччень затруднительно. А вот здесь к нам напомощь и пришёл бы (просто прискакал бы) процедурный тип данных. идея следующая: функции нам, хочешь - нехочешь, пришлось бы реализовывать в виде отдельных подпрограмм функций. а вот на вызове нужной мы с экономим: станим вызывать не саму функцию, а ссылку на неё. Это очень напоминает работу с указателями и динамическими переменными, где по ссылке мы можем получить само значение. На самом деле так оно и получается, в машинных кодах и подпрограммы и пееменные имеют свои адреса, к ним (подпрограммам) и происходит обращение по адресу. Для такой реализации в паскале надо обявить прототип функции, (с появлением .Net их стали называть делегатами) т е её объявление в общем виде: тип подпрограммы (процедура или функция), список параметров, с указанием типов данных и тип возвращаемого значения, если это функция. Пример:
Здесь обявлены два типа: TFunct - это функция, в качестве параметра которой выступает переменная X вещественного типа, результат - тоже вещественное число; TMyProc - подпрограмма, имеющая три параметра с именами A, B и C целого типа и параметра S строкового типа, который возвращает эта подпрограма. Как видите имена подпрограмм в общем виде не задаются. TFunct и TMyProc теперь равноправные типы данных паскаля, такие как Integer, Real, Char, MyRecord, MyBigArray .... И соответственно можно объявлять переменные этих типов:
На самом деле переменные F и P - указатели. Но к этим указателям нельзя применять адресную арифметику, к ним применяются только операции присваения и сравнения. В них может содержаться "пустой" укзатель, т е Nil. Так как переменная F имеет тип данных TFunct, который обявлен как Function(X:Real):Real, то в эту переменную может быть занесена только функция, не подпрограмма!, у каоторой только один параметр, типа Real. Но и это не все функция должна возвращать значение тоже типа Real, если у функции один пааметр типа Real, но она возвращает значение типа Char или String или Integer или ...., то такую функцию в переменную F занести нельзя! Что нам это дает? Неужели нельзя было обойтись простым case ... of (из первого примера)? Впринципе можно было в случае добавления списка из новых функций, расширить код case of, и перечислить еще тридцать значений.... Но использование процедурных переменных, позволило бы нам занести в переменную, допустим F имя нужной функции, а затем использовать выражение следующего вида:
Но у нас есть тип данных
и Переменная
Которые не смотря на отсутствие функции F позволяют выполнить строку Result:=F(X); Если перед этой строкой в F будет ссылка на функцию, извлекающую корень, то эта строка будет эквивалентна Result:=SQRT3(X); как будто вместо F стоит идентификатор SQRT3. А сохранять в переменной процедурного типа даных ссылку на процедуру очень просто - простым присваиванием:
Теперь нескоко слов о специфике реализации таких функций: все процедурыи функции, имена которых присваиваются процедурным переменным, необходимо компилировать с ключом компиляции $F+. Вложенные процедуры и функции с процедурными переменными использовать нельзя. Ну и самое обидное, это то что в качестве ссылок НЕМОГУТ быть использованы СТАНДАРТНЫЕ процедуры и функции типа sin, cos и т п. Последнее правда можно легко обойти: просто создать функцию, которая будет в качестве своей реализации использовать стандартную. Например, так может быть реализованы ссылки на тригонометрические функции:
Ну есчо раз повторю, что процедурный тип данных ничем не отличается от других типов данных. После обявления соответствующего типа, можно использовать кроме переменных еще и константы процедурного типа (естно сама процедура должна быть описана "выше" чем объявлена константа). Ну и теперь подитожу. Если в нашей первоначальной задаче, которую наверное уже все забыли, использовать процедурный тип данных, то вызов функции (это вся конструкция 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), с доработаными или добавленными возможностями. В качестве примера рассмотрю изобретение велосипеда. ![]() Допустим сочиняем мы контейнер для чего-то. Ну т е коллекцию, в Delphi уже есть готовые классы-коллекции, в Turbo Vision то ж есть такое счастье. ООП рассматривать здесь не буду, т к это уже другая тема. Так вот пусть наша коллекция будет хранить любые объекты, это значит что нам по большому счету надо будет хранить указатель на объект. Объект у меня это не термин ООП, а даные, т е в качестве объекта может рассматриваться массив, целое число, строка, запись, другой тип данный. Кто говорит что для обозначения любого, что здесь перечислено, нельзя приментть слово "объект" пусть кинет в меня камень! А сама коллекция будут организована в виде очереди (основанной на динамических переменных). И пусть мы все чего на создадим поместим в библиотеку (может даже и в DLL). И после будем использовать наши труды в других програмах, и даже другим програмистам дадим воспользоваться. Ну так вот для реализации очереди я предлагаю использовать следующую структуру:
Далее мы реализуем подпрограммки по работе с этой [i]бедой, например, добавление элементов, удаление элементов, поиск элементов, упорядочивание элементов... Насчет добавления и удаления я думаю ничего сложного нет - подпрограммки простые! Вот как реализовать поиск? Ну что начинаем с простого - пусть в нашей коллекции (и точка!), будут храниться только целые числа (точнее указатели на них). Тогда мы могли бы написать что-то вроде:
Несложно! Условие, в котором производится приведение значения,хранимого по указателю Data к целому типу я думаю понятно. А что будет, если у нас в коллекции будут строки? Нужно изменить тип входного параметра, и приводить нужно к другому типу данных. А если другой программист или мы в программке, использующей эти коллекции, создадим ... ну например запись, и хранить в Data будем указатель на эту запись. Тогда для сравнения, мы должны будем не только привести data^ к типу апись, но и выбрать из этой записи поле по которому будем сравнивать. Например, у нас будет сисок работников, и искать мы будем по имени, то есть по полю FIO в одном случае, а в другом - по окладу.......................... А если.... А что ..... типов данных может быть бесчисленное множество. И как сравнивать текущий элемент с эталоном? Вот тута мы и воспользуемся ссылкой на подпрограмму, которую кстати сами и напишем в каждом конкретном случае, а в подпрограмму Find будем передовать как эталон, так функцию, которая будет вычислять равны ли два значения, или не равны... Так как в коллекции может храниться все, что угодно, то тип эталона я думаю ясен - это ссылка (указатель) на эталон, т е Pointer. Значит заголовок нашей процедурки будет следующий:
В реализации мало, что измениться, разве что поменяется немного вид сравнения.... Давайте подумаем, что это за тип_функции нам нужен (это и будет параметр процедурного типа). Ну во-первых это будет функция, кроме того она будет возвращать логическое знаяение: True - если элементы одинаковы, и False - в противном случае. В функции будет сравниваться два значения: эталон и текущие данные. Тип их (я уже не обясняю) Pointer. Вот и получили следующий процедурный тип:
ну и объявление подпрограмки (если кто еще не понял):
Как видите в этом случае мы вместо конкретно сравнения (например двух целых чисел), перешли к более общему: теперь процедуру сравнения мы оставляем на совести основной программы, которая будет использовать нашу коллекцию. Согласитесь, что в каждой программе реализовывать свою коллекцию, с особыми подпрограммами сравнения, в первом случае для целых чисел, в другом для записи, в третьем ... трудоёмко. Проще реализовать общий подход, а в главной программе только ссылаться на то что уже есть чуть-чуть "доработав" этот общий подход к конкретному случаю, например, для сравнения строк. Если кто не понял, то в основной программе мы с вами написали что-то типа:
А в критический момент использовать это все так:
или так:
Оказалось наверное не так сложно как казалось с первого взгляда. Мы реализовали решение задачи в общем виде, а часть специфического решения, оставили на совести основной программы. Теперь подпрограмма упорядочивания. Собсно ничего сложного опять нет, составляем общий алгоритм (пусть будет пузырёк):
Теперь Нам надо выяснить как относятся друг к другу два элемента: -они равны; -первый больше вторго; -первый меньше второго. задача непосредственного сравнения остаётся за основной подпрограммой, и в каждом конкретном случае будет своя. Нам остаётся определить обявление функции - какие параметры, какого типа, что ф-ция будет возвращать. Сравниваем мы опять два элемента, поэтому с входными параметрами ясно. Результатов сравнения у нас три - использовать логический тип данных в принципе можно, но не желательно, давайте будет разнообразновать свою жизнь. ![]() Поэтому пусть наша ф-ция возвращает целые числа: 0 - равные элементы; -1 - первый больше вторго; +1 - первый меньше второго. соответственно тип такой функции следующий:
А дальше.... а дальше так же как и с подпрограммой поиска.... Таким образом можно придумать есчо кучу подпрограмм для работы с коллекцией, и со ссылками на другие подпрограммы. -------------------- Мы - Днепряне. Мы всех сильней. |
||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||
![]() ![]() ![]() |
Правила форума "Delphi: Общие вопросы" | |
|
Запрещается! 1. Публиковать ссылки на вскрытые компоненты 2. Обсуждать взлом компонентов и делиться вскрытыми компонентами
Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Snowy, MetalFan, bems, Poseidon, Rrader. |
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей) | |
0 Пользователей: | |
« Предыдущая тема | Delphi: Общие вопросы | Следующая тема » |
|
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности Powered by Invision Power Board(R) 1.3 © 2003 IPS, Inc. |