Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Антонюк и др.doc
Скачиваний:
48
Добавлен:
07.11.2018
Размер:
32.99 Mб
Скачать

Глава 7. Дополнительные возможности системы matlab Объекты и классы. Переопределение операций.

MATLAB позволяет реализовать концепцию объектно-ориентированного программирования. В MATLAB можно создавать и манипулировать объектами, которые скрывают реальные данные за контролируемыми методами работы с ними, допускают переопределение операций и дают возможность наследовать их свойства производным объектам-потомкам. Однако, последний принцип объектно-ориентированного программирования, полиморфизм (возможность вызывать методы классов-потомков через интерфейсы базовых классов), в MATLAB не реализован. Конкретнее, в MATLAB нет ничего подобного механизму виртуальных функций в языке программирования C++.

Все пять основных типов MATLAB (числовые и разреженные массивы, массивы ячеек, структуры и текстовые строки) представляют собой встроенные классы, а переменные– объекты этих классов. Пользователь имеет возможность вводить свои классы, а также переопределять и доопределять методы всех существующих классов.

Для создания нового класса объектов нужно спроектировать структуру MATLAB, которая будет хранить данные, принадлежащие объекту, и определить функции-методы работы с этими объектами. Эти функции определяются в обычных М-файлах, которые должны быть помещены в специальную папку, имя которой начинается с символа '@', а в остальном должно совпадать с именем структуры (класса), причем эта папка должна входить в папку, определенную в пути MATLAB-path. Саму папку-контейнер методов добавлять в путь MATLAB не нужно.

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

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

Рассмотрим пример конструктора, создающего объекты класса «полином». Этот конструктор должен находиться в файле @polynom/polynom.m.

function p = polynom(a)

%POLYNOM Polynomial class constructor.

% p = POLYNOM(v)

if nargin == 0

p.c = [];

p = class(p, 'polynom');

elseif isa(a,'polynom')

p = a;

else

p.c = a(:).';

p = class(p,'polynom');

end

Данный конструктор создает полином из заданного вектора, содержащего коэффициенты полинома при убывающих степенях x. Если же в конструктор не передавать никакого аргумента, то будет создан «пустой» полином.

Сначала в конструкторе проверяется количество аргументов, и если оно равно нулю, то создается пустой полином. Точнее, создается структура p, содержащая единственное поле c, которое и будет содержать пустой вектор коэффициентов полинома. Затем из этой структуры конструируется сам объект при помощи встроенной функции class(). У этой функции есть два обязательных параметра. Первый – структура, которая будет представлять данные объекта, а второй – текстовая строка, содержащая имя создаваемого класса. Оно должно совпадать с именем конструктора и папки, его содержащей (с добавлением символа '@' в начале имени папки).

Если при вызове конструктора аргумент был задан, то проверяется тип этого аргумента. В том случае, когда был задан объект того же класса (так называемый copy-constructor), создается его копия при помощи оператора присваивания. Проверка класса переданного объекта осуществляется по его имени, при помощи функции isa() (является).

Наконец, если передан был обычный массив, то он копируется в поле p.c создаваемой структуры и, затем, из нее конструируется объект класса так же, как и в первом случае.

Подобная последовательность действий является обязательной для конструктора объектов. Естественно, структура, хранящая данные объекта, может быть создана сколь угодно сложной – с произвольным набором полей.

Отметим, что за пределами методов данного класса, функциями class() и isa() тоже можно активно пользоваться. В этом случае, первая из них может принять только один аргумент – объект и возвращает текстовую строку – имя класса заданного объекта. Например, для заданного вещественного вектора эта функция вернет строку 'double'.

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

function c = double(p)

% @polynom/double.m.

c = p.c;

Теперь, в сессии MATLAB мы можем преобразовывать вектора в полиномы и обратно:

» P = polynom([1 -2 7]);

» double(p)

ans =

1 -2 7

Другим частым преобразованием является преобразование к текстовому виду (метод char()) для нужд распечатки объекта. В примере с полиномом этот метод (@polynom/char.m) мог бы порождать строку 'x^3 – 2*x + 7'.

Преобразование к тексту используется в другом методе, которой желательно реализовать в каждом создаваемом новом классе. Это метод display() – он вызывается всякий раз, когда MATLAB нужно распечатать содержимое объекта. Главным образом это происходит, когда в среде MATLAB введено выражение не завершенное точкой с запятой.

function display(p)

% @polynom/display.m

disp(' ');

disp([inputname(1),' = '])

disp(' ');

disp([' ' char(p)])

disp(' ');

Одним из важнейших способов управления поведением объекта служит переопределение для него основных математических операторов. Делается это также, как и в предыдущем случае, при определении конструкторов и конверторов. А именно, нужно завести в папке, содержащей методы класса M-файл с именем, соответствующем имени переопределяемого оператора и в этом файле определить функцию с этим же именем, например:

function r = plus(p,q)

% @POLYNOM/PLUS.M

p = polynom(p);

q = polynom(q);

k = length(q.c) – length(p.c);

r = polynom([zeros(1,k) p.c] + [zeros(1,–k) q.c]);

Такая функция, определенная в файле @polynom/plus.m позволит складывать полиномы, как обычные объекты MATLAB – вещественные матрицы, вектора или числа. При этом, принудительное преобразование формальных аргументов к типу полинома в первых строках этой функции гарантирует, что будут правильно вычисляться смешанные выражения:

» p = polynom([1 2 3]);

» q = polynom([3 2 1]);

» pq = p + q;

» p1 = p + 1;

» q1 = 1 + q;

Вызовы функции zeros() в методе сложения полиномов дополняют нулями коэффициенты более короткого полинома из двух.

В таблице приводится полный список имен методов для переопределения всех операторов MATLAB:

Оператор

Метод

Описание

a + b

plus(a,b)

Сложение

a – b

minus(a,b)

Вычитание

a

uminus(a)

Унарный минус

+a

uplus(a)

Унарный плюс

a.*b

times(a,b)

Поэлементное умножение

a*b

mtimes(a,b)

Матричное умножение

a./b

rdivide(a,b)

Правое поэлементное деление

a.\b

ldivide(a,b)

Левое поэлементное деление

a/b

mrdivide(a,b)

Правое матричное деление

a\b

mldivide(a,b)

Левое матричное деление

a.^b

power(a,b)

Поэлементное возведение в степень

a^b

mpower(a,b)

Матричное возведение в степень

a < b

lt(a,b)

Меньше

a > b

gt(a,b)

Больше

a <= b

le(a,b)

Меньше или равно

a >= b

ge(a,b)

Больше или равно

a ~= b

ne(a,b)

Неравно

a == b

eq(a,b)

Равно

a & b

and(a,b)

Логическое И

a | b

or(a,b)

Логическое ИЛИ

~a

not(a,b)

Логическое НЕ

a:d:b или

a:b

colon(a,d,b)

colon(a,b)

Оператор «двоеточие» - генерация

a.'

transpose(a)

Транспонирование

a'

ctranspose(a)

Комплексно-сопряженное транспонирование

[a b]

horzcat(a,b,...)

Горизонтальная конкатенация

[a; b]

vertcat(a,b,...)

Вертикальная конкатенация

a(s1,s2,...sn)

subsref(a,s)

Ссылка по индексу

a(s1,...,sn) = b

subsasgn(a,s,b)

Присваивание по индексу

b(a)

subsindex(a,b)

Индексирование

Вывод в командной строке

display(a)

Распечатка значения

MATLAB всегда начинает просмотр функций, оперирующих над объектом с папки, содержащей методы класса – перед любой другой папкой, указанной в пути просмотра MATLAB (MATLAB-path). Это означает, что при создании нового класса всегда есть возможность переопределить любую функцию для данного класса. Например, для полиномов можно создать специализированную функцию roots(), вычисляющую корни полинома каким-либо методом, отличным от стандартного, реализуемого встроенной функцией roots().

Обычно, MATLAB считает объекты всех классов имеющими одинаковый приоритет и вызывает бинарный метод у левого операнда выражения. Существует возможность управлять иерархией приоритетов для разных классов, так, что в ситуациях a + b и b + a будет гарантированно вызываться метод операнда b.

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

Аналогично, можно понизить приоритет создаваемого класса по сравнению с некоторым другим. Для этого в конструкторе надо вызвать функцию iferiorto().

Нужно не забывать, что объекты базируются на структурах, которые в MATLAB на самом деле всегда являются массивами структур. Этим фактом можно воспользоваться, для конструирования объектов-массивов. При этом во всех методах такого класса нужно будет пользоваться циклом по элементам базового массива структур – p(i).c, например.

К весьма нетривиальным последствиям может привести переопределение взятия значения по индексу (массива). В самом деле, для определенных выше полиномов операция p(c) может означать следующее:

  • значение полинома при x = 3;

  • третью производную;

  • третий коэффициент полинома;

  • коэффициент при x3;

  • третий полином в последовательности, например, полиномов Чебышева.

Разработчик класса полиномов может выбрать любой из этих путей, в зависимости от своих потребностей. Для того, чтобы зафиксировать выбранный способ интерпретации выражения p(c) ему придется переопределить метод ссылки по индексу – subsref(A,S) (см. таблицу).

Есть три возможных причины вызова этого метода:

  1. A(i) – обычно, индекс в вещественном массиве

  2. A{i} – обычно, индекс в массиве ячеек

  3. A.val – обращение к полю структуры.

Во всех случаях будет произведен вызов subsref(A,S), где S – структура вида:

S.type – будет иметь значение текстовой строки '()', '{}' или '.' в зависимости от того, какой из приведенных выше случаев имеет место.

S.subs – массив ячеек, содержащий сами индексы. Если в выражении встретилось A(1:3,2,:), то S.subs будет равен {1:3, 2, ':'}.

Более сложный пример, A(1,2).name(3:4), породит один вызов subsref(A,S) с S, представляющей собой массив 3х1:

S(1).type = '()' S(2).type = '.' S(3).type = '()'

S(1).subs = '{1,2}' S(2).subs = 'name' S(3).subs = '{3:4}'

Аналогично, в случае присваивания значения по индексу A(I) = B, A{I} = B или A.I = B, генерируется вызов A = subsasgn(A,S,B), где S – та же структура, что и в предыдущем случае.

Наконец, последний переопределяемый метод индексирования – subsindex() вызывается независимо для каждого указанного индекса в выражении X(A), где A является объектом. Этот метод обязательно должен возвращать целочисленный массив реальных индексов, которые и будут использоваться для извлечения значений из массива X. Этот метод вызывается встроенными методами subsref(), subasgn(), и может понадобиться при переопределении этих методов.

MATLAB поддерживает одиночное и множественное наследование для классов. А именно, класс может унаследовать все поля и методы родительского класса, добавить свои поля и методы и переопределить часть методов родительского класса на свои. Наследование осуществляется вызовом функции class() в конструкторе класса с указанием имен всех родительских классов:

function p = circle(x,y,R)

% @circle\circle.m

center = figure(x,y);

p.r = R;

p = class(p,'circle',center);

end

Здесь мы предположили, что уже имеется класс «геометрическая фигура» с единственным атрибутом – координатами центра. Данный пример реализует конструктор класса «окружность», который наследует классу «геометрическая фигура» и добавляет свой атрибут – радиус. Отметим, что созданный объект будет генерировать логическое значение «истина» при передаче его в функцию isa() как при проверке, является ли он кругом, так и при проверке, является ли он геометрической фигурой.

Для реализации множественного наследования нужно при вызове функции class() в конструкторе указать список всех родителей через запятую:

p = class(p,'name', parent1, parent2, …);

Создаваемый класс унаследует все данные и методы всех родителей. Если у нескольких родителей совпадут имена каких-либо методов, то приоритет при вызове у потомка получат методы классов, идущих первыми в списке параметров функции class().

Как уже отмечалось, все данные у объекта являются защищенными – они доступны только для методов данного класса и его потомков. Методы класса при этом можно вызывать откуда угодно. MATLAB позволяет создавать также методы, полностью недоступные для внешнего использования. Для этого в папке класса нужно завести подпапку с именем private и в ней разместить все M-файлы методов, которые необходимо скрыть от внешнего использования – они будут доступны только из других методов данного класса и его потомков.