Matlab支持面向对象编程,主要有两种方式,一种是利用class命令,一种是利用classdef关键字。Octave(一种开源科学计算程序,可视为Matlab的替代品)目前只支持第一种方式,对classdef暂不支持。下面对这两种编程方式做简单介绍。

1. 利用class命令创建类

创建一个@class形式的文件夹,其中class代表要实现的类的名称。假定需要创建一个名为point的类,可以创建一个名为@point的文件夹:

mkdir @point

之后,文件夹@point下定义的函数会被视为point类的成员函数。主要包括:

  1. point.m
    构造函数。这是一个与类名称同名的函数。
  2. get.m
    用于获取类point的属性。
  3. set.m
    用于设置类point的属性。
  4. display.m
    用于控制类的显示字符串。
  5. disp.m
    同display.m,但是比display更加高级,在disp.m中会调用display.m。因而display.m可以实现对显示更加精细的控制。
  6. 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。

这里需要注意两点:

  1. 只能在成员函数中对class的属性进行access,这也就是为什么要定义get和set的原因。通过point.x或者point.y的方式来访问类的属性会报错。
  2. 利用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类是所有类的基类。

此外,methodsproperties语句块还可以利用更多的描述符控制其访问级别,从而使得类能够支持公共属性,私有属性,公共方法,私有方法,静态方法等特性。关于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)
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐