【问题标题】:Property initialization in MATLABMATLAB 中的属性初始化
【发布时间】:2019-02-19 19:49:48
【问题描述】:

在 MATLAB 中有两种方法可以初始化其属性。直接在属性中:

classdef A
    properties
       foo = 'bar';
    end
end

或通过显式定义构造函数:

classdef B
    properties
        foo;
    end

    methods this = B()
        this.foo = 'bar';
    end
end

哪种方式更可取?

(我之所以问,是因为在 C++ 中有类似的情况,首选方法是在构造函数之后用冒号初始化成员变量,而不是在构造函数中分配变量)。

【问题讨论】:

  • 我发现第一个版本更容易阅读。
  • @CrisLuengo 这不仅仅是一个意见问题 - 这两种方法的行为不同。请参阅下面的答案。
  • @SamRoberts:谢谢,这很有趣!

标签: matlab oop constructor


【解决方案1】:

在大多数情况下,这两种方法的行为相同,选择只是一种偏好,具体取决于您认为哪种方法更清晰/更易于维护等。

但它们并不相同,在某些情况下,如果您不了解这些差异,您会遇到麻烦。

本质区别在于,在构造函数中初始化的属性在每次构造对象时都会单独初始化,而在属性块中使用默认值初始化的属性会在第一次读取类定义时初始化一次。属性块中的默认值是 class 的默认值,而不是对象的默认值;并且(使用反射)即使尚未实例化该类的实例,您也可以查询该默认值。

在大多数情况下,这没有区别 - 但是当初始值是句柄对象或非确定函数的输出时,它会这样做。所以考虑以下两个类:

classdef A
    properties
        foo
    end
    methods
        function obj = A
            obj.foo = containers.Map;
        end
    end
end

classdef B
    properties
        foo = containers.Map
    end
end

注意containers.Map 是一个句柄类。

在A 类中,每次创建A 的新实例时,都会为其foo 获得一个新的/不同的containers.Map。在B 中,B 的每个实例都得到相同 containers.Map 的foo,因为该属性仅初始化一次,即第一次读取类定义。因此,如果您为 B 类的任何对象修改 foo,则该更改将传播到 B 类的所有其他实例,如您所见:

>> a1 = A; a2 = A; a1.foo('greeting') = 'hello'; a2.foo('greeting') = 'bonjour';
>> a1.foo('greeting'), a2.foo('greeting')
ans =
    'hello'
ans =
    'bonjour'
>> b1 = B; b2 = B; b1.foo('greeting') = 'hello'; b2.foo('greeting') = 'bonjour';
>> b1.foo('greeting'), b2.foo('greeting')
ans =
    'bonjour'
ans =
    'bonjour'
>> % Note that b1.foo has changed as a result of setting b2.foo

关于将类处理为默认值的这一点经常让人绊倒;但该行为并非特定于处理类。例如,考虑以下变体:

classdef A
    properties
        foo
    end
    methods
        function obj = A
            obj.foo = datetime('now');
        end
    end
end

classdef B
    properties
        foo = datetime('now')
    end
end

这里A 将存储每个对象的创建时间,而B 将存储所有对象的类首次初始化的时间,无论它们是何时创建的。

如果您发现这种行为令人困惑,请参阅 https://undocumentedmatlab.com/blog/handle-object-as-default-class-property-value,尤其是该文章下方的评论线程,以讨论该问题并解释 MATLAB 以这种方式设计的原因。


编辑:cmets 中关于clear 的行为及其与此问题的关系的很好的后续问题。

使用上述类的第二个实现(使用datetime),看看以下内容:

>> a = A; b = B; datestr(a.foo), datestr(b.foo)
ans =
    '01-Sep-2018 18:59:30'
ans =
    '01-Sep-2018 18:59:30'
>> clear variables
>> a = A; b = B; datestr(a.foo), datestr(b.foo)
ans =
    '01-Sep-2018 18:59:48'
ans =
    '01-Sep-2018 18:59:30'
>> clear classes
>> a = A; b = B; datestr(a.foo), datestr(b.foo)
ans =
    '01-Sep-2018 18:59:57'
ans =
    '01-Sep-2018 18:59:57'

所以我们首先创建一个A和一个B,并显示它们的foos,它们都同时显示。然后我们等一会儿,做clear variables,然后我们再做一次。请注意,A 中的foo 是新时间,B 中的foo 仍与以前相同。最后我们再等一会儿,我们做clear classes,然后我们再做一次。这次A和B都有新的时间了。

为什么?因为clear variables 只是从工作区中删除了对变量的引用。 B 的类定义没有被清除,所以当我们创建另一个 B 时,它仍然使用第一次读取类定义时的值。 clear classes,相比之下,也删除了类定义,所以当我们稍后构造一个新的B 时,它会得到那个时间,因为然后重新读取类定义。所有这些都与A 无关,因为foo 只是在构建时被赋予了一个值。

注意clear classes 清除所有类定义:您可以使用clear B 清除仅类B 的定义。

【讨论】:

  • 正是我想要的!在 MATLAB 中工作时编辑类定义有什么不同吗?比如说,我有类A 和一个实例obj。当我更改属性时,无论哪种情况,它都不会反映在 obj 中 - 但我可以清除 obj 并重新实例化。当我在初始化属性时更改某些内容时,clear classes(并因此破坏整个工作区)是强制性的吗?
【解决方案2】:

除某些情况外,您可以自行选择。

当我们在Constructor function inC++ 中使用冒号时让我提一下:

1- 调用基类构造函数

2- 在构造函数体执行之前初始化成员变量。

#1 也可以用在正文中,但是当成员为const 时,我们应该在#2 中使用冒号。

但是

但这里是 Matlab,有自己的语言语法和结构。

使用哪一个来完成你的工作。

但是在 Matlab 中,当您想要定义 Constant 属性时,您必须在 properties 部分定义它,而您不能在初始化程序/构造函数中进行。

classdef NamedConst
   properties (Constant)
      R = pi/180; % Can't define it in constructor body, because it is `Constant properties`
  end
  %class body
end

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多