索引对象点表示法方法提供标量属性。

14
我在尝试使用点符号应用方法后引用对象属性时遇到了问题。只有当我尝试索引初始对象时才会出现这种情况。
classdef myclassexample

properties
    data
end    

methods   
    function obj = procData(obj)            
        if numel(obj)>1
            for i = 1:numel(obj)
                obj(i) = obj(i).procData;
            end
            return
        end
        %do some processing
        obj.data = abs(obj.data);
    end
end
end

然后分配以下内容

A = myclassexample;
A(1).data= - -1;
A(2).data =  -2;

在调用整个数组并收集属性数据时,它可以正常工作。

[A.procData.data]

如果我尝试索引 A,那么我只会得到一个标量

[A([1 2]).procData.data]

尽管没有调用该属性,似乎也能正常运行。

B  = A([1 2]).procData;
[B.data]

有什么想法吗?


@dylan2106,为什么你的类定义得这么奇怪,没有构造函数? - Marcin
这里的@Marcin所说,"MATLAB提供了一个不带参数的构造函数..." - dylan2106
2
2013b 有相同的问题。一个有趣的观察是 A.procData.('data') 也返回一个标量。 - Mohsen Nosratinia
3
哇,这是Matlab一个非常有缺陷的角落。它也会影响到句柄类。更有趣的是:[A.procData().data]访问了非法的内存并可能导致段错误(Mac R2013a)。我认为返回值可能是缓冲区溢出的垃圾数据;有时返回[1,1],但有时却是[1,4e-309]或其他无意义的东西。简化情况下,也会在for循环内部处理中发生这种情况。这肯定是Mathworks的一个严重缺陷。 - mbauman
2
这里唯一真正的解决方案是向Mathworks提交错误报告。除此之外,问题本身中提到的解决方法就是解决方案。 - mbauman
显示剩余2条评论
1个回答

14
我一定会将这称为解析器中的错误;它是个错误,因为它没有一开始就报错,反而允许你写:obj.method.prop
在某些语法变体中 MATLAB 崩溃的事实是一个严重的错误,应该向 MathWorks 报告
现在在 MATLAB 中的一般规则是不直接“索引结果”。相反,您应该首先将结果保存到一个变量中,然后再对该变量进行索引。
如果您使用表达式 func(obj) 而非 obj.func() 来调用对象的成员方法(点符号表示法与函数符号表示法),这个事实就很明确了。
>> A = MyClass;
>> A.procData.data       % or A.procData().data
ans =
     []
>> procData(A).data
Undefined variable "procData" or class "procData". 

相反,正如您所指出的,您应该使用:

>> B = procData(A):    % or: B = A.pocData;
>> [B.data]

就算不使用面向对象的对象和成员函数,而是使用普通结构体和函数,也同样会出现这种情况。因为你无法对函数调用的结果进行索引。例如:

% a function that works on structure scalar/arrays
function s = procStruct(s)
    if numel(s) > 1
        for i=1:numel(s)
            s(i) = procStruct(s(i));
        end
    else
        s.data = abs(s.data);
    end
end

接下来的所有调用都会抛出错误(正如它们应该做的那样):

% 1x2 struct array
>> s = struct('data',{1 -2});

>> procStruct(s).data
Undefined variable "procStruct" or class "procStruct". 

>> procStruct(s([1 2])).data
Undefined variable "procStruct" or class "procStruct". 

>> feval('procStruct',s).data
Undefined variable "feval" or class "feval". 

>> f=@procStruct; f(s([1 2])).data
Improper index matrix reference. 

您可能会问自己为什么他们决定不允许这样的语法。事实证明,MATLAB不允许索引函数调用(无需引入临时变量)的原因是有充分的理由的,无论是点索引还是下标索引。
以以下函数为例:
function x = f(n)
    if nargin == 0, n=3; end
    x = magic(n);
end

如果我们允许在函数调用中进行索引,那么将会存在一个歧义性,即如何解释以下的调用f(4)
  • 它应该被解释为:f()(4)(即先调用该函数不带参数,然后使用线性索引进入结果矩阵以获得第四个元素)
  • 还是应该解释为:f(4)(将n=4作为一个参数来调用该函数,并返回矩阵magic(4)
这种混淆是由 MATLAB 语法中的几个因素引起的:
  • 它允许通过函数名而不需要括号来调用没有参数的函数。如果有一个函数f.m,你可以像ff()一样调用它。这使得解析M代码更加困难,因为无法确定标记是变量还是函数。

  • 括号既用于矩阵索引,也用于函数调用。所以如果一个标记x表示一个变量,我们使用语法x(1,2)作为矩阵索引。同时,如果x是一个函数的名称,则使用x(1,2)来调用具有两个参数的函数。

另一个令人困惑的问题是逗号分隔列表和返回多个输出的函数。例如:

>> [mx,idx] = max(magic(3))
mx =
     8     9     7
idx =
     1     3     2

>> [mx,idx] = max(magic(3))(4)     % now what?

我们应该从MAX的每个输出变量中返回第四个元素,还是只从第一个输出参数中返回第四个元素以及完整的第二个输出?当函数返回不同大小的输出时怎么办?
所有这些都适用于其他类型的索引:f()(3)/f(3), f().x/f.x, f(){3}/f{3}。
因此,MathWorks决定避免上述混淆,简单地不允许直接索引结果。不幸的是,在这个过程中他们限制了语法。例如,Octave没有这样的限制(你可以写magic(4)(1,2)),但是新的OOP系统仍在开发过程中,所以我不知道Octave如何处理这种情况。

对于那些感兴趣的人,这让我想起了另一个关于软件包和类以及直接索引获取属性的相似错误。无论你是从命令提示符,脚本还是M文件函数中调用它,结果都是不同的...


1
我着迷的是Mathworks费心为这些情况实现了制表符自动完成。当你按下tab键时,他们实际上会运行该方法多次以确定输出类型。如果你让MyClass扩展handle,你实际上可以在不按回车键的情况下处理数据——只需按tab键!(你也可以通过设置断点或打印一些输出并尝试制表符自动完成来查看这一点。)这很疯狂,但我认为这可能与他们的依赖获取器实现有关。 - mbauman
我想你是对的,就好像他们通过在不应该实现制表符完成的地方来鼓励这种语法一样。如果您决定为您的类(同时处理标量和对象数组)重载 subsref 函数,情况会变得更糟,我认为这才是混乱的真正根源 :) - Amro
这里有一个相关问题,其中提问者要求利用这种语法来启用BDD风格的单元测试:expects(1+1).toBe(2)。其中一种建议的解决方案是为该类实现subsref,但正如所警告的那样,这肯定会破坏其他类型的下标索引。在使用对象数组时重载subsrefsubsasgn几乎不可能做到完美,而同时保持默认提供的所有其他功能(例如:obj(2).x{3}.y(4:6))。 - Amro
1
非常好的回答,@Amro。我只想补充一点,虽然变量必须在索引之前被捕获,但您可以使用函数式方法来获取内联索引:in = @(x,varargin) x(varargin{:}); in(magic(3), 1, 2); - Gordon Bean
@GordonBean:谢谢。是的,有很多方法可以获得这种内联索引,查看此问题以获取更多信息。 - Amro
@Amro 感谢您提供全面的答案,绝对值得获得奖励 :-) - Mohsen Nosratinia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接