Matlab支持面向对象编程,主要有两种方式,一种是利用class命令,一种是利用classdef关键字。Octave(一种开源科学计算程序,可视为Matlab的替代品)目前只支持第一种方式,对classdef暂不支持。下面对这两种编程方式做简单介绍。
1. 利用class命令创建类
创建一个@class形式的文件夹,其中class代表要实现的类的名称。假定需要创建一个名为point的类,可以创建一个名为@point的文件夹:
mkdir @point
之后,文件夹@point下定义的函数会被视为point类的成员函数。主要包括:
- point.m
构造函数。这是一个与类名称同名的函数。 - get.m
用于获取类point的属性。 - set.m
用于设置类point的属性。 - display.m
用于控制类的显示字符串。 - disp.m
同display.m,但是比display更加高级,在disp.m中会调用display.m。因而display.m可以实现对显示更加精细的控制。 - move.m
可以是任何用户自定义函数。
以上列出的几类函数中,只有构造函数是必需的。但是,由于一般面向对象编程中都会涉及对属性的访问(读取和设置),所以大多数情况下也会实现get.m和set.m。用户自定义函数根据不同的应用场景,可以有也可以没有,而且可以有多个用户自定义成员函数。
以下给出一份示例代码:
% point.m
% 构造函数
function obj = point(x, y)
persistent id_
if (isempty(id_)); id_ = 0; end
d.id = id_;
d.x = x;
d.y = y;
obj = class(d, 'point');
id_ = id_ + 1;
end
% get.m
% 属性获取函数
function val = get(obj, name)
switch name
case 'x'
val = obj.x;
case 'y'
val = obj.y;
case 'id'
val = obj.id;
otherwise
error(['Invalid property: ' name]);
end
end
% set.m
% 属性设置函数
function obj = set(obj, varargin)
n = numel(varargin);
assert(rem(n, 2) == 0)
i = 1;
while i < n
name = varargin{i};
val = varargin{i+1};
switch name
case 'x'
obj.x = val;
case 'y'
obj.y = val;
case 'id'
error('set private field "id" is not allowed');
otherwise
error(['Invalid property: ' name]);
end
i = i + 2;
end
end
% display.m
% 显示函数
function display(obj)
fprintf(1, 'point class (id = %d):\n', obj.id);
fprintf(1, 'x = %d\n', obj.x);
fprintf(1, 'y = %d\n', obj.y);
end
% move.m
% 用户自定义函数
function obj = move(obj, varargin)
assert(numel(varargin) >= 1);
if (isstruct(varargin{1}))
% move(obj, s)
s = varargin{1};
assert(all(isfield(s, {'x', 'y'})));
obj.x = s.x;
obj.y = s.y;
else
% move(obj, x, y)
assert(numel(varargin) >= 2);
x = varargin{1};
y = varargin{2};
obj.x = x;
obj.y = y;
end
end
以上的代码具有一定的代表性。point函数中利用class命令创建了一个point类。它包含两个public属性x和y,还有一个只读属性id(从get.m和set.m可以看出来:在get.m中可以获取id的值,而在set.m中无法设置id的值)。此外,我们还定义了一个用户自定义方法move。
这里需要注意两点:
- 只能在成员函数中对class的属性进行access,这也就是为什么要定义get和set的原因。通过point.x或者point.y的方式来访问类的属性会报错。
- 利用class方式生成的类只能通过函数方式调用其成员函数,而无法通过
.
操作符对成员函数进行调用。例如,调用move函数:
move(point, 2, 3)
而
point.move(2, 3)
则是非法的。
2. 利用classdef关键字创建类
classdef是Matlab中用于创建类的关键字。其基本结构为
classdef classname
properties
PropName
end
methods
methodName
end
events
EventName
end
end
其中properties用于定义类的属性,methods定义类的成员函数,events块定义类的事件。
classdef支持类的继承,通过<
操作符进行说明,多个父类中间用&
分隔。其基本语法为:
classdef classname [< [superclass1] & [superclass2]]
...
end
在Matlab OOP中,handle类是所有类的基类。
此外,methods
和properties
语句块还可以利用更多的描述符控制其访问级别,从而使得类能够支持公共属性,私有属性,公共方法,私有方法,静态方法等特性。关于classdef的更多细节请参考Matlab文档或者网上资料。
这里给出一个利用classdef实现第1节中point类的例子:
classdef point
properties
x
y
end
properties (SetAccess = private)
id
end
methods
function self = point(x, y)
persistent id_
if (isempty(id_)); id_ = 0; end
self.id = id_;
self.x = x;
self.y = y;
id_ = id_ + 1;
end
function display(self)
fprintf(1, 'point class:\n');
fprintf(1, 'x = %d\n', self.x);
fprintf(1, 'y = %d\n', self.y);
end
function move(self, varargin)
assert(numel(varargin) >= 1);
if (isstruct(varargin{1}))
% move(obj, s)
s = varargin{1};
assert(all(isfield(s, {'x', 'y'})));
self.x = s.x;
self.y = s.y;
else
% move(obj, x, y)
assert(numel(varargin) >= 2);
x = varargin{1};
y = varargin{2};
self.x = x;
self.y = y;
end
end
end
end
上面的代码中,x和y为point类的public属性,id为私有属性。classdef创建的类支持.
操作符。可以直接通过.
访问类的属性和调用类的成员函数。所以不需要额外编写get函数和set函数,直接通过point.x即可获取类的属性,通过point.y=1即可完成对属性的设置。可见这种方式使用起来略微方便。
目前Matlab对classdef方式的支持还不是很完善,有些应用场景下使用Matlab面向对象编程在性能方面可能会有些损失;但是,面向对象编程方式会使代码结构变得比较清晰,程序内部逻辑更容易让人理解。所以需要根据实际应用场景合理做出选择(参考Matlab面向对象编程是否值得大量使用?)。由于Octave目前还不支持classdef关键字,为了保证代码的可移植性,不建议采用这种方式。
最后,贴出测试两种编程方式的测试代码:
clear all; clc;
%% Test case 1: class
clear all;
% test constructor
point = point(1, 2);
% test getter
x = get(point, 'x')
y = get(point, 'y')
id = get(point, 'id')
% get(point, 'xx') % this should issue an error
% test setter
% set(point, 'x') % this should issue an error
set(point, 'x', 2)
set(point, 'x', 2, 'y', 3)
set(point, 'y', 4, 'x', 5)
% set(point, 'id', 1) % this should issue an error
% test display
point
display(point)
disp(point)
% test user-defined function
move(point, 4, 5)
display(point)
%% Test case 2: classdef
clear all;
% test constructor
point = point.point(1, 2);
% test getter
x = point.x
y = point.y
id = point.id
% point.xx % this should issue an error
% test setter
point.x = 2
point.y = 3
% point.id = 1 % this should issue an error
% test display
point
display(point)
disp(point)
% test user-defined function
point.move(4, 5)
display(point)