【问题标题】:What does left-to-right associativity mean?从左到右的关联性是什么意思?
【发布时间】:2014-08-31 05:41:04
【问题描述】:

我对从左到右和从右到左关联性的定义感到困惑。我还看到它们被称为左关联性和右关联性,并且想知道哪个对应于哪个。

我知道这与执行具有相同优先级的操作的顺序有关,例如 a = x * y * z 表示 a = x * (y * z) 还是 a = (x * y) * z .我不知道哪个是从左到右关联的,哪个是从右到左关联的。

我试过用谷歌搜索它,但我只能找到 C++ 中不同运算符的关联性的表格。看着所有的例子让我更加困惑。

让我更困惑的是:

glm::vec4 transformedVector = translationMatrix * rotationMatrix * scaleMatrix * originalVector;

首先执行缩放矩阵乘法,然后是旋转矩阵,然后是平移。在这个例子中,矩阵都是 glm::mat4 类型,向量是 glm::vec4 类型。这是从左到右还是从右到左的关联性?这和普通乘法一样还是glm类型的乘法不同?

【问题讨论】:

  • 忘记“-to-right”或“-to-left”(第二个)部分,这只是眼中的灰尘。
  • wikipedia 会帮助你

标签: c++ glm-math


【解决方案1】:

您通常从左到右阅读。你通常从左到右做数学。这是从左到右的关联性,最常见。

大多数人都会解决

x = 23 + 34 + 45

通过分组

x = (23 + 34) + 45

这是从左到右的关联性。你可以记住它,因为你从左到右阅读和做数学。

对于数学中的加法 并不重要。无论哪种方式,您总是会得到相同的结果。这是因为加法是关联的。说一个操作是关联的,意味着从左到右和从右到左的关联是一回事。对于编程中的加法 ,它仍然很重要,因为溢出和浮点运算(但不适用于任何合理语言中的正常大小的整数),所以当你有一个大数字和轻率的 2 AM 错误时使用a+bb+a,记住添加发生的顺序。

在你的例子中:

glm::vec4 transformedVector = translationMatrix * rotationMatrix * scaleMatrix * originalVector

从概念上首先从右侧切入,因为那是您正在采取行动的地方。然而在 C++ 中,* 通常是从左到右关联的并且不可能覆盖它。 glm 可以通过多种方式处理这个问题:它可以建立一个缓存的东西来乘法等待最终的向量到达然后做从右到左的乘法。它也可能(更有可能)使用矩阵乘法是完全关联的代数定理,并且只是从左到右相乘,然后在文档中向读者保证它与从右到左的想法相同。但是,您需要了解实现,因为如前所述 it matters which way the implementation chooses to multiplying floating point numbers together

为了完整起见,请考虑减法。 a - b - c 是什么?这里确实确实它是左结合还是右结合。当然,在数学中我们将它定义为 b (a - b) - c,但是一些奇怪的编程语言可能更喜欢减法来正确关联,并且将 a - b - c 始终表示 a - (b - c)。这种外星语言最好有一个文档页面指定- 是右关联的,因为它是操作规范的一部分,而不是仅仅通过查看操作符的使用就可以看出。

【讨论】:

  • +1 用于提及代数关联属性
  • 我猜 DV 是因为我假装 C++ 可以覆盖 * 的关联性,我修复了这个
  • 感谢于浩现在删除的答案,因为我知道我从中弄错了最重要的细节。
  • 您还应该指出,数学操作是关联的,不是编程语言中的实际操作(尤其是浮点数)。例如。 100 + 1e-15 + 1e-15 + ... + 1e-15 计算结果为 100 如果您从左到右对操作进行分组,但如果您从右到左对操作进行分组并且有足够的操作数(例如 8 个 1e-15加号)。顺便说一句:APL 具有从右到左作为默认关联性,如果您想要最后一段的示例。
  • 我不是反对者,但转换被 OP 解释为“以相反的顺序”看到的原因与 + 或 * 运算符的关联性或交换性无关;这是一个从内到外对向量应用变换矩阵的问题。
【解决方案2】:

从下面的话可以看出:

当我们将运算符组合成表达式时, 要应用的运算符可能并不明显。例如, a + b + c 可以解释为 ((a + b) + c) 或 (a + (b + c))。 如果操作数左分组,我们说 + 是左结合的 就像 ((a + b) + c) 一样。我们说它是右结合的,如果它 将操作数按相反方向分组,如 (a + (b + c))。

A.V. Aho & J.D. Ullman 1977 年,第47

【讨论】:

  • 什么是正确关联性的一个很好的例子,它实际使用并在数学上有所作为(不是在操作上,所以忽略浮点类型问题)?在 Haskell 中,==/=、“^、:$ 是右结合运算符,这意味着第一个隐含的括号在右侧。在下面引用@Marc:“因此,在没有显式括号的嵌套使用中,最左边的运算符将其直接邻居作为操作数,而其他实例将其左侧所有内容形成的表达式(结果)作为左操作数。”
  • [cont] 和非关联运算符的情况下,第一个隐含括号是在子表达式的最左边还是最右边的非关联运算符上并不重要表达。
【解决方案3】:

如果在没有显式括号的情况下嵌套使用此运算符(表达式类型),则将隐式括号放在左侧,则中缀运算符(或更一般地说,具有未闭合的左右子表达式的表达式类型)是左结合的。由于* 在 C++ 中是左关联的,所以a*b*c 表示(a*b)*c。如果嵌套更深,左端会出现一组隐式括号:(((a*b)*c)*d)*e

等效地,这意味着该运算符的语法产生规则是左递归的(意味着左子表达式与该规则的产生具有相同的语法类别,因此相同的规则(相同的运算符)可能是直接用于形成该子表达式;另一端的子表达式具有更严格的句法类别,并且使用相同的运算符将需要显式括号)。在 C++ 中,multiplicative-expression 的一种产生式(标准中的第 5.6 节)读取为 mutliplicative-expression * pm-expression,其中 乘法表达式在左边。

因此,在没有显式括号的嵌套使用中,最左边的运算符将其直接邻居作为操作数,而其他实例将其左侧所有内容形成的表达式(结果)作为左操作数。

我承认,我一直在推动这一点(太过分了)。我的观点是,上面没有出现“正确”这个词,也没有涉及任何运动;关联性是一种句法,因此是静态的。重要的是 在哪里 隐式括号去哪里,而不是按什么顺序写它们(事实上根本没有,否则它们会是显式的)。当然,对于右关联性,只需将上面的每个“左”替换为“右”即可。

总之,我看不出有什么正当理由应该称这种从左到右的关联性(或分组),但事实是人们会这样做(即使是标准也这样做,尽管考虑到明确的语法,它是完全多余的也给出了规则)。

混淆来自解释这一点,正如通常所做的那样,通过说(在没有显式括号的情况下)运算符是从左到右执行的(对于右关联运算符分别是从右到左)。这是误导性的,因为它混淆了语法和语义(执行),并且仅对自下而上求值的操作有效(所有操作数在运算符之前求值)。对于具有特殊评估规则的操作员来说,这是完全错误的。对于运算符&& (and) 和|| (or),语义是先计算左操作数,然后是运算符本身(即决定左操作数还是右操作数会产生结果),然后是可能 通过对右操作数的评估。这种从左到右的评估完全独立于关联性:运算符碰巧是左关联的,可能是因为所有非赋值二元运算符都是,但是(c1 && c2) && c3(在它们已经隐含的地方带有冗余括号)有一个等效于c1 && (c2 && c3) 的执行(即从左到右执行条件,直到一个返回false 并返回它,或者如果没有返回true),我无法想象一个合理的编译器为这两种情况生成不同的代码。实际上,我发现右分组更能说明表达式的评估方式,但实际上并没有什么区别; or 也是如此。

对于条件(三元)运算符? ... :,这一点更加清楚。这里适用关联性,因为两边都有开放的子表达式(参见我的开场白);中间操作数包含在 ?: 中,并且 never 需要额外的括号。事实上,这个运算符被声明为right-关联,这意味着c1 ? x : c2 ? y : z 应该读作c1 ? x : (c2 ? y : z) 而不是(c1 ? x : c2) ? y : z(隐式括号在右边)。然而,使用隐式括号,两个三元运算符从左到右执行;解释是三元运算符的语义不会首先计算所有子表达式。


回到您问题中的示例,左关联性(或从左到右分组)意味着您的矩阵向量乘积被解析为((M1*M2)*M3)*v。尽管在数学上是等价的,但实际上不可能将其作为M1*(M2*(M3*v)) 执行,即使这样更有效。原因是浮点乘法不是真正的关联(只是近似),因此浮点矩阵乘法也不是。因此,编译器无法将一个表达式转换为另一个表达式。请注意,在((M1*M2)*M3)*v 中,不能说哪个矩阵首先应用于向量,因为它们都不是:首先计算复合线性映射的矩阵,然后 矩阵被应用于向量。结果将大约等于M1*(M2*(M3*v)) 的结果,其中应用了M3,然后是M2,最后是M1。但是如果你想让事情像那样发生,你必须写那些括号。

【讨论】:

    【解决方案4】:

    我找到的最简单和最不重要的答案:

    在大多数编程语言中,加法、减法、乘法和除法运算符是左结合的,而赋值、条件和求幂运算符是右结合的

    感谢:http://www.computerhope.com/jargon/a/assooper.htm

    【讨论】:

      【解决方案5】:

      a = (x * y) * z 是从左到右,a = x * (y * z) 是从右到左。

      glm 的矩阵乘法从左到右关联,因为它重载了* 运算符。这里的问题是关于矩阵乘法在几何变换方面的含义,而不是数学关联性。

      【讨论】:

        【解决方案6】:

        运算符的从左到右的关联性表示运算符的右侧 不应该有任何更高优先级(优先级)的运算符,但它 可以具有相同的优先级。如果有更高优先级的运算符 在我们运算符的右侧,那么我们必须先解决它。示例:

        x = 2 + 3 * 3;
        

        这里,对于 + 运算符(从左到右的关联性),右侧包含 一个运算符 * ,它的优先级高于 + 运算符,所以我们有 先解决它。

        x = 2 + 9;
        x = 11;
        

        【讨论】:

          猜你喜欢
          • 2014-03-06
          • 2015-03-20
          • 2019-09-13
          • 2010-11-01
          • 1970-01-01
          • 1970-01-01
          • 2014-12-09
          • 2023-03-27
          • 2021-03-13
          相关资源
          最近更新 更多