Skip to content

MATLAB - 用户定义类

MATLAB 中的现代面向对象编程 (OOP)

Section titled “MATLAB 中的现代面向对象编程 (OOP)”

尽管 MATLAB 擅长过程式和基于矩阵的编程,但其面向对象功能在构建复杂、模块化和可重用应用程序方面非常强大。用户定义的类允许您创建自定义数据类型,将数据(属性)和对该数据进行操作的函数(方法)捆绑到一个单一实体中。

类在与类同名的 .m 文件中定义,使用 classdef 关键字。

% 保存为 BasicCounter.m
classdef BasicCounter
properties
Count = 0; % 用于存储数据的属性
end
methods
% 构造方法:在创建对象时调用
function obj = BasicCounter(initialValue)
if nargin > 0
% 使用 validateattributes 进行健壮的输入检查
validateattributes(initialValue, {'numeric'}, {'scalar', 'integer'});
obj.Count = initialValue;
end
end
% 修改对象状态的常规方法
function obj = increment(obj)
obj.Count = obj.Count + 1;
end
% 显示对象状态的方法
function displayState(obj)
fprintf('当前计数为:%d\n', obj.Count);
end
end
end

要使用此类型:

myCounter = BasicCounter(5); % 创建一个实例
myCounter = myCounter.increment(); % 调用一个方法
myCounter.displayState();
Current count is: 6

这是 MATLAB OOP 中最重要的区别。默认情况下,类是值类。

  • 值类 (classdef MyClass):行为类似于标准的 MATLAB 数据类型(例如,数字、结构体)。当您将一个值对象传递给函数或将其赋值给另一个变量时,MATLAB 会创建一个副本。修改副本不会影响原始对象。在我们的 BasicCounter 示例中,increment 必须返回修改后的对象(function obj = increment(obj))。
  • 句柄类 (classdef MyClass < handle):行为类似于指针或引用。当您传递一个句柄对象时,只传递一个引用(句柄)。通过其句柄修改对象会影响该对象的唯一实例。这对于大型对象来说更节省内存。
% 保存为 HandleCounter.m
classdef HandleCounter < handle % 继承自 'handle' 类
properties
Count = 0;
end
methods
function obj = HandleCounter(initialValue)
if nargin > 0
obj.Count = initialValue;
end
end
% 注意:此方法无需返回 'obj'
function increment(obj)
obj.Count = obj.Count + 1;
end
end
end
% 用法:
hCounter = HandleCounter(10);
hCounter2 = hCounter; % 两个变量指向同一个对象
hCounter.increment(); % 通过第一个句柄修改对象
fprintf('hCounter count: %d\n', hCounter.Count); % 输出: 11
fprintf('hCounter2 count: %d\n', hCounter2.Count); % 输出: 11 (也改变了!)

您可以使用特性来控制对属性和方法的访问,以强制封装。

classdef BankAccount < handle
properties (SetAccess = private) % 只能由该类的方法设置
Balance = 0;
end
properties (Constant)
ACCOUNT_TYPE = 'Checking';
end
methods
function obj = BankAccount(initialDeposit)
obj.deposit(initialDeposit);
end
function deposit(obj, amount)
validateattributes(amount, {'numeric'}, {'>=', 0});
obj.Balance = obj.Balance + amount;
fprintf('Deposited %.2f. New balance: %.2f\n', amount, obj.Balance);
end
function withdraw(obj, amount)
validateattributes(amount, {'numeric'}, {'>', 0});
if amount > obj.Balance
error('Insufficient funds.');
end
obj.Balance = obj.Balance - amount;
fprintf('Withdrew %.2f. New balance: %.2f\n', amount, obj.Balance);
end
end
end
% myAccount = BankAccount(100);
% myAccount.deposit(50);
% myAccount.Balance = 5000; % 这将导致错误!很好。

继承允许一个类(子类)从另一个类(超类)继承属性和方法,从而促进代码重用。

% 保存为 Shape.m
classdef (Abstract) Shape < handle % 抽象类不能实例化
properties
Name
end
methods (Abstract) % 子类必须实现此方法
area = calculateArea(obj);
end
methods
function display(obj) % 所有形状都将具有的具体方法
fprintf('这个形状是一个 %s,面积为 %.2f\n', obj.Name, obj.calculateArea());
end
end
end
% 保存为 Circle.m
classdef Circle < Shape % Circle 继承自 Shape
properties
Radius
end
methods
function obj = Circle(radius)
obj.Name = 'Circle';
obj.Radius = radius;
end
% 实现 Shape 中的抽象方法
function area = calculateArea(obj)
area = pi * obj.Radius^2;
end
end
end
% myCircle = Circle(5);
% myCircle.display(); % 调用 Circle 和 Shape 中的方法
% 这个形状是一个 Circle,面积为 78.54
  • 值类与句柄类:当您需要类似引用的行为时(例如,用于 GUI 元素、原地修改的数据容器),请使用句柄类。当您需要按修改复制的行为时(例如,用于不可变数学对象),请使用值类。
  • 验证:在构造函数和属性 set 方法中使用 validateattributes 和 validatestring 以编写健壮的代码。
  • 封装:使用 (Access = private) 或 (SetAccess = private) 来保护内部状态并暴露一个清晰的公共 API。
  • 组合优于继承:在使用继承之前,请考虑是否可以通过一个对象包含另一个对象来解决问题(例如,一个 Car 类包含一个 Motor 对象作为属性)。
  • 命名:类名使用 PascalCase(例如,MyClassName),属性和方法使用 camelCase(例如,myProperty)。