如果一切都是对象,为啥我不能交换两个变量? [复制]

Posted

技术标签:

【中文标题】如果一切都是对象,为啥我不能交换两个变量? [复制]【英文标题】:If everything is an object, why can't I swap two variables? [duplicate]如果一切都是对象,为什么我不能交换两个变量? [复制] 【发布时间】:2017-06-18 13:14:11 【问题描述】:

我有一个简单的测试:

def swap(a, b)
   c = a
   a = b
   b = c
end

a = 3
b = 4
swap(a, b)
print a # 3. not change value

我知道此示例不适用于 Java 或 C# 等某些语言,因为这些变量是原语。但是在 Ruby 中,如果一切都是对象,我认为上面的示例应该可以工作。请给我解释一下。


我认为问题在于 FixNum 是不可变的。我有使用 Java 的概念验证代码来制作一个不可变的整数类:

static class CustomInteger 
        Integer value;
        public CustomInteger(int i) 
            this.value = i;
        

        public void changeValue(int i) 
            this.value = i;
        

        public int getValue() 
            return value;
        

        public CustomInteger setValueImmutable(int i) 
           return new CustomInteger(i);
        
    

    public static void swap(CustomInteger a, CustomInteger b) 
        int c = a.getValue();
        a.changeValue(b.getValue());
        b.changeValue(c);
    

    public static void main(String[] args) 
        CustomInteger i1 = new CustomInteger(3);
        CustomInteger i2 = new CustomInteger(4);
        swap(i1, i2);
        System.out.println(i1.getValue());
    

public static void notWorkSwap(Integer a, Integer b) 
        int temp_a = a;
        int temp_b = b;
        a = temp_b;
        b = temp_a;
    

【问题讨论】:

Fixnum 对象是不可变的。见this answer。 @TrầnKimDự,我 1000% 确定这个确切的代码不会在 c/c++/c#/java/whatever 中交换它们。如果您传递指针并更改这些指针的地址 - 是的。但不是这样的常规任务。 @TrầnKimDự,这不是原因。注意你是如何使用a.changeValue(b.getValue()) 而不是a = ba = b.getValue()。赋值 (=) 只是改变了局部变量所指的内容。前一个值或新值是否可变并不重要。 @TrầnKimDự,装箱/拆箱是对应类的基元和对象之间的神奇转换。这与它无关。调用changeValue 不是“为对象赋值”,它调用了一个为实例变量赋值的方法。它仍在调用方法,而不是赋值。 @TrầnKimDự,它不起作用,因为您假设取消装箱会更改值是不正确的。它不会改变任何东西,它只是为您提供底层的原始值。装箱也不会改变任何东西,它会创建一个包装该值的新对象。在右侧完成任何包装/展开之后,它仍然像正常分配一样简单地分配。 【参考方案1】:

您的 swap 方法仅交换该方法的本地变量。

在方法内部,ab 被交换。不过,这不会影响方法之外的任何内容。例如,c 没有在 swap 之外定义。

请注意,要交换 ab,您不需要任何其他变量:a, b = b, a 是有效的 Ruby 语法。

最后,变量aren't objects。它们只是引用对象。

这是实现您想做的事情的一种方法。 更新:它基本上相当于你的 Java 代码:

class MyObject
  attr_accessor :x
  def initialize(x)
    @x = x
  end
end

def swap(f,g)
  f.x, g.x = g.x, f.x
end

a = MyObject.new(1)
b = MyObject.new(2)

swap(a, b)

puts a.x
#=> 2
puts b.x
#=> 1

请注意,对象 ab 尚未交换。不过,它们的 x 值已被交换。

另一种可能性是使用实例变量:

def swap
  @a, @b = @b, @a
end

@a = 1
@b = 2

swap

p @a
#=> 2
p @b
#=> 1

【讨论】:

【参考方案2】:

这不起作用的原因是方法参数是按值传递的,而不是按引用传递的。默认情况下,只有少数语言使用传递引用语义。 Java 和 Ruby 中的行为是相同的。

但是,很多人滥用该术语就够棘手了。例如,请参阅此问题。 Is Java "pass-by-reference" or "pass-by-value"?

在许多语言中,例如 C#,可以使用特殊关键字(在 C# 中为 ref,或在 C++ 中类型后的 & 标记)进行引用传递。您可以通过传递间接指针来模拟 C 等语言中的行为(尽管由于不那么严格的类型检查,这会给您更多的权力)。

我已经有几年没有写过 C# 代码了,但是你想要达到的目标大概是:

static void SwapStrings(ref string s1, ref string s2)
        
            string temp = s1;
            s1 = s2;
            s2 = temp;
         

它将被调用SwapStrings(ref str1, ref str2);

此代码是此处内容的缩写版本:https://msdn.microsoft.com/en-us/library/s6938f28.aspx

【讨论】:

它们是本地的,因为它们是该函数范围内的值。如果你有一个 byref a,byref b 它们现在是引用,技术上仍然是局部变量,但由于调用者有引用,所以调用函数可以使用。 在这个意义上你是对的;这些天我的 C 有点生疏了,IIRC 你必须使用双重间接来支持 C 中的交换。 我认为这里的问题是因为 Ruby 中的 FixNum 是不可变的。如果它们是可变的,它会起作用。请看看我更新的问题。我有用于概念验证的邮政编码 Java。 @ndn:是的,C 仍然按值传递指针。 C 通过值传递所有内容,就像 Ruby 一样。 (事实上​​,Ruby 基本上就像总是按值传递指针,并且由于传递的所有内容始终是指针,因此没有像 C 中那样获取和取消引用指针的特殊语法。)但是在 C++ 和 C 等语言中♯ ,确实具有传递引用语义,您可以影响调用者范围内的引用;事实上,这几乎就是传递引用的定义。 @TrầnKimDự:您发布的 Java 代码和 Ruby 代码完全不同。在 Ruby 代码中您正在修改引用,在 Java 代码中您正在修改对象。这些是完全不同的事情。上面写着“Trần Kim Dự”的便利贴和你不一样。仅供参考。

以上是关于如果一切都是对象,为啥我不能交换两个变量? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

如果在 Ruby 中一切都是对象,为啥这不起作用?

为啥在这个变量赋值中引用了两个对象? [复制]

为啥使用类变量的这段代码不能像在 Java 中那样工作? [复制]

Python中复制深拷贝和浅拷贝的区别

如果'C'从'B'公开继承,B私下从'A'继承,为啥我不能在'C'内部创建'A'的对象? [复制]

如果两个变量指向同一个对象,为啥重新分配一个变量不会影响另一个?