(初学者) 为什么在示例1中临时变量会改变,但在示例2中却不会改变?
在第一个例子中,临时变量的值发生了改变,而在第二个例子中没有发生改变。问题出现的原因是,临时变量在第一个例子中是可变的,而在第二个例子中是不可变的。解决方法是将临时变量声明为不可变的。
例子1:
temp = [1, 2, 3] temp.append(4) print(temp) # 输出 [1, 2, 3, 4]
例子2:
temp = (1, 2, 3) temp.append(4) # 报错,元组不支持添加元素的操作
在第一个例子中,临时变量是一个列表,可以通过append方法添加新的元素。所以,当调用temp.append(4)时,临时变量的值被改变了。
而在第二个例子中,临时变量是一个元组,元组是不可变的,不支持添加新的元素。所以,当调用temp.append(4)时,会报错。
为了解决这个问题,我们可以将临时变量声明为不可变的,比如使用元组代替列表。这样,就可以避免在操作临时变量时发生意外的改变。
总结起来,临时变量在例子1中发生改变,是因为它是可变的;而在例子2中没有发生改变,是因为它是不可变的。为了避免意外的改变,我们可以将临时变量声明为不可变的。
初学者为什么在示例1中临时变量会改变,而在示例2中不会改变?
这个问题的出现原因有几个方面。
首先,可变对象在没有引用标识的情况下会在奇怪的时候引起错误。例如,考虑一个具有基于值的equals方法的Person bean:
Mapmap = ... Person p = new Person(); map.put(p, "Hey, there!"); p.setName("Daniel"); map.get(p); // => null
当作为键使用时,Person实例在map中“丢失”,因为它的hashCode和相等性是基于可变值的。这些值在map之外改变,所有的哈希变得过时。理论家喜欢强调这一点,但在实践中,我发现这不是一个太大的问题。
另一个方面是代码的逻辑合理性。这是一个难以定义的术语,包括从可读性到流程的所有内容。一般来说,你应该能够查看代码片段并轻松理解它的作用。但更重要的是,你应该能够确信它正确地执行了它的任务。当对象可以在不同的代码“域”中独立更改时,有时很难跟踪哪里以及为什么发生了“幽灵般的远程操作”。这是一个更难以解释的概念,但在更大、更复杂的架构中经常面临这个问题。
最后,可变对象在并发情况下是致命的。当你从不同的线程访问可变对象时,你必须处理锁定。这会降低吞吐量,并使你的代码变得极其难以维护。一个足够复杂的系统会严重夸大这个问题,以至于几乎不可能维护(即使对于并发专家来说也是如此)。
不可变对象(尤其是不可变集合)避免了所有这些问题。一旦你理解了它们的工作原理,你的代码将变得更容易阅读、更容易维护,并且不太可能以奇怪和不可预测的方式失败。不可变对象甚至更容易进行测试,这不仅因为它们易于模拟,还因为它们倾向于强制执行的代码模式。简而言之,它们是全方位的良好实践!
话虽如此,我在这个问题上并不是一个狂热者。当一切都是不可变的时候,有些问题的建模并不好。但我认为你应该尽量将你的代码朝着这个方向推进,前提当然是你使用的语言使这成为一个可行的观点(C/C++很难做到这一点,Java也是如此)。简而言之:优势在一定程度上取决于你的问题,但我倾向于更喜欢不可变性。
很好的回答。然而,有一个小问题:C++是否对不可变性有很好的支持?const-correctness特性不足以解决吗?
.: C++实际上有一些更基本的特性,尤其是对于封装非共享状态的存储位置与封装对象标识的存储位置之间的更好区分。
在Java编程中,Joshua Bloch在他著名的书《Effective Java(第15项)》中对这个问题进行了很好的解释。