4.5 求值顺序

到目前为止,我们已经看到了&&|||?:制约一些操作数的求值。这特别意味着,对于这些运算符,操作数有一个求值顺序:第一个操作数,因为它是其余操作数的条件,总是被先求值:

要点4.15 &&||?:,首先计算它们的第一个操作数。

逗号(,)是唯一一个我们还没有介绍的运算符。它按顺序计算操作数,结果是右操作数的值。例如,(f(a),f(b))首先求f(a)的值,然后求f(b)的值,结果是f(b)的值。注意,逗号在C语言中扮演其他语法角色,即它们在求值时不使用相同的约定。例如,分隔初始化的逗号与分隔函数参数的逗号不具有相同的属性。

逗号运算符在简洁的代码中没有什么用处,对初学者来说它是一个陷阱:a [i, j]不是矩阵A的二维索引,而是结果在A[j]中。

要点4.16 不要使用,运算符。

其他操作符没有求值限制。例如,在f(a)+g(b)这样的表达式中,没有预先确定的顺序来指定是先计算f(a)还是先计算g(b)。如果函数fg有副作用(例如,如果f在幕后修改b),表达式的结果将取决于所选择的顺序。

要点4.17 大多数运算符不会对它们的操作数进行排序。

这个顺序可能取决于编译器、编译器的特定版本、编译时选项,或者仅仅取决于围绕表达式的代码。不要依赖于任何特定的排序:它会刺痛你。

函数参数也是如此。例如:

053-01

我们不知道最后两个参数中哪一个先求值。

要点4.18 函数调用不会对它们的参数表达式进行排序。

不依赖算术表达式的求值顺序的唯一可靠方法是禁止副作用:

要点4.19 在表达式内部调用的函数不应该有副作用。

挑战4 Union-Find(联合-查找)

Union-Find问题涉及基本集上分区的表示。我们将使用数字01...来识别基本集的元素,并将使用森林数据结构表示分区,其中每个元素都有一个“父元素”,它是同一分区内的另一个元素。这样一个分区中的每个集合都由一个指定的元素来标识,这个元素称为集合的根。

我们想要执行两个主要操作:

  • Find操作接收基础集的一个元素,并返回相应集合的根。
  • Union[1]操作接收两个元素,并将这些元素所属的两个集合合并为一个。

能否在名为父的基础类型size_t索引表中实现森林数据结构?这里,表SIZE_MAX中的一个值代表其中一棵树的根的位置,另一个数字表示相应树的父树的位置。开始实现的重要特性之一是初始化函数,它使父分区成为单例分区:即每个元素都是其私有集的根的分区。

使用这个索引表,可以实现Find函数吗?也就是对于给定的索引,查找它的树的根。

你能否实现一个FindReplace函数,其将根路径上的所有父项改为一个特定值的根(包括根)?

你能否实现一个FindCompress函数,将所有父项改为已找到的根?

你能否实现一个Union函数,也就是,对于两个给定的元素,将它们的树合并为一个?一边使用FindCompress,另一边使用FindReplace

总结
  • 算术运算符做数学运算。它们对值进行操作。
  • 赋值运算符修改对象。
  • 比较运算符比较值并返回01
  • 函数调用和大多数运算符按非特定顺序计算其操作数。只有&&||?:对其操作数的求值强制排序。

[1]C还有一个概念叫作union,我们稍后会看到,它与我们现在讨论的操作完全不同。因为union是一个关键字,所以我们在这里使用大写字母来命名操作。