如果在没有显式括号的情况下嵌套使用此运算符(表达式类型),则将隐式括号放在左侧,则中缀运算符(或更一般地说,具有未闭合的左右子表达式的表达式类型)是左结合的。由于* 在 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。但是如果你想让事情像那样发生,你必须写那些括号。