Изображение квадрата Дюрера

ООО АВТОМАТИКА плюс

Rambler's Top100

Рейтинг@Mail.ru

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




Программирование внешних функций

Внешними функциями будем называть все подпрограммы, находящиеся вне модуля Matrix.pas. Такие функции отличаются тем, что в них среди входных параметров обязательно присутствует ссылка на рабочую область, содержащую входные массивы. Внешние функции могут быть двух видов:
1 - с собственной рабочей областью;
2 - без собственной рабочей области.

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

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

Рассмотрим правила программирования внешних функций с собственной рабочей областью. Создадим простейшую внешнюю функцию, которая складывает два массива 'A' и 'B' и умножает результат сложения на третий массив 'C'. На выходе функции формируется массив 'D'.

procedure ExternalFunc(Ws: TWorkspace; A, B, C, D: string);
begin

  with TWorkspace.Create('Внешная функция', Ws) do
  begin
    // Проверка правильности имени выходного массива
    CheckResAr(D);
    LoadArray(A, 'A'); // Передача ссылки на массив А
    LoadArray(B, 'B'); // Передача ссылки на массив В
    LoadArray(C, 'C'); // Передача ссылки на массив С
    CalcFunc2('A', 'B', 'Tmp', fncSum); // A + B = Tmp
    CalcFunc2('Tmp', 'C', 'D', fncMul); // Tmp * C = D

    // Предыдущие две строки можно свернуть в одну, 
    // как показано ниже:
    // CalcFunc2(CalcFunc2('A', 'B', 'Tmp', fncSum), 
    //                          'C', 'D', fncMul);

    // Возвращаем массив D
    ReturnArray('D', D);
    // Уничтожаем рабочую область
    Free;
  end;
end;

В первой строке находится заголовок подпрограммы. Параметры A, B и С соответствуют входным массивам, параметр D соответствует выходному массиву, Ws - ссылка на рабочую область. Именно рабочая область Ws содержит все входные массивы и в нее же необходимо передать полученный выходной массив 'D'.

Комментарии подробно раскрывают смысл каждой строки текста. В самом начале процедуры происходит создание рабочей области. Оператор with позволяет не объявлять переменную класса TWorkspace. Если в вашей подпрограмме должна вызываться другая подпрограмма, то в качестве ссылки на рабочую область достаточно передать указатель SelfWS.

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

  • В заголовке функции ссылка на рабочую область должна стоять на первом месте, после чего должны следовать входные массивы, выходные массивы и дополнительные параметры;
  • В методе Create() обязательно указывайте ссылку на родительскую рабочую область;
  • Для передачи массивов в подпрограмму используйте функцию LoadArray(). Третий параметр ByRef изменять не следует. Его изменение может понадобиться в редких случаях.
  • Результаты работы подпрограммы следует возвращать с помощью функции ReturnArray();
  • Вся обработка должна находиться внутри оператора with. В конце оператора with должен стоять вызов метода Free(), необходимого для уничтожения рабочей области;
  • Если в результате проверок обнаруживаются недопустимые входные данные, то следует (в большинстве случаев, в зависимости от задачи) прервать выполнение подпрограммы путем генерации исключения с помощью функции DoError(). В результате вызова этой функции прервется выполнение всех вложенных процедур и произойдет каскадное удаление рабочих областей;
  • Имя выходного массива следует (в зависимости от задачи) проверять с помощью функции CheckResAr(). Если задана пустая строка, то функция изменит ее на стандартное имя 'ans'. Если задано недопустимое имя массива, то функция сгенерирует исключение;
  • Если ваша подпрограмма вызывает другую внешнюю функцию, которая должна обработать ваши массивы, то в качестве ссылки на свою рабочую область (первый аргумент функции) вы должны указать переменную SelfWS, которая является типизированным указателем на вашу рабочую область (т.е. ссылка).
  • Вычислительный алгоритм должен быть продуманным. Старайтесь организовать вычисления таким образом, чтобы массивы создавались или меняли размеры как можно реже. Некоторые функции ядра Matrix проверяют, существует ли выходной массив в данной рабочей области. Если он существует и имеет такие же размеры (или число элементов), как у результирующего массива, то лишней инициализации массива не будет и код выполнится быстрее (в несколько раз быстрее, в зависимости от конкретного случая);
  • Снабжайте программу необходимыми комментариями. Если имеется довольно запутанный вычислительный алгоритм, то приводите соответствующие формулы на математическом языке;
  • Старайтесь реже обращаться к отдельным элементам массивов с помощью свойств Elem[], ElemI[]. Первое свойство самое простое в использовании, т.к. обращение к массиву происходит непосредственно по его имени. Однако приходится каждый раз искать индекс массива по его имени, а это выполняется довольно долго. Свойство ElemI[] использовать сложнее, т.к. вместо имени массива приходится указывать его индекс, но работает этот способ быстрее. Еще более быстрым способом доступа к элементам массива является использование свойства ElemFast[]. Однако, использовать это свойство еще сложнее - необходимо указывать адрес массива и число столбцов.
  • Если вы оперируете индексами массивов (например, для использования в свойстве ElemI[], то используйте названия переменных наподобие следующих: aIdx, bIdx, cIdx и т.д., где первая буква соответствует символьному названию соответствующего массива. Например, если у вас есть массив A, то индекс массива должен называться aIdx. Самое главное, чтобы было окончание Idx, указывающее на то, что переменная является индексом.
  • Старайтесь не использовать свернутые выражения, как показано в предыдущем примере. При сворачивании выражений код получается более компактным, но становится абсолютно нечитабельным. Кроме того, при сворачивании выражений могут появиться ошибки, связанные с неправильным порядком вычислений.

Рассмотрим правила программирования внешних функций без выделенной рабочей области. Будем использовать тот же пример (D=(A+B)*C). Ниже представлен листинг процедуры:

procedure ExternalFunc1(Ws: TWorkspace; A, B, C, D: string);
var
  Tmp: string;
begin
  with Ws do begin
    // Генерируем исключение при отсутствии одного из массивов
    if (not ArrayExists(A)) or (not ArrayExists(B)) or
      (not ArrayExists(C)) then DoError(matArrayNotFound);
    CheckResAr(D);  // Проверяем имя выходного массива
    Tmp := GenName(); // Генерируем имя временного массива
    CalcFunc2(CalcFunc2('A', 'B', Tmp, fncSum), 'C', Tmp, fncMul);
    RenameArray(Tmp, D); // Переименовываем временный массив
  end;
end;

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

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

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

Логинов Дмитрий © 2005-2015