【问题标题】:Struct is not passed by reference when passed to a method传递给方法时,结构不通过引用传递
【发布时间】:2016-10-27 02:19:53
【问题描述】:
struct Data {
    public int x;
}

void change_x(Data data) {
    data.x = 123;
}

Data a = Data();
change_x(a);
print("%d", a.x); // 0

但文件说:

将结构类型实例传递给方法时,不会进行复制。而是传递对实例的引用。
- 在https://wiki.gnome.org/Projects/Vala/Manual/Types

怎么了?

【问题讨论】:

    标签: struct vala


    【解决方案1】:

    Vala 中的结构被实现为赋值复制和引用传递。因此,您可以将您的示例视为复制结构,因为它被分配给函数中的参数,然后该副本通过引用传递。这是生成的 C 代码在幕后发生的事情,但从 Vala 方面来看,这意味着结构是一种值类型。只有在与 C 库接口时,知道结构的副本是通过引用传递的才有用。手册中的引用是指结构方法,但在我们详细了解之前,让我们更多地了解值和引用类型。

    Vala 与 Java、C# 和许多其他语言一样,有两种数据类型:值类型和引用类型。

    值类型通过值传递

    当值类型作为参数传递给函数或方法时,该值作为参数传递,但它是该值的副本。如果函数或方法继续修改​​它收到的参数,这不会改变调用代码中的值。代码已封装。

    下面的例子:

    void main () {
            int a = 23;
            print ("Initial value: %i\n", a);
            modify_example (a);
            print ("Final value: %i\n", a);
    }
    
    void modify_example (int x) {
            x += 100;
    }
    

    产生:

    Initial value: 23
    Final value: 23
    

    虽然在函数中修改了值,但它不会同时修改调用代码中的值。

    值类型可以通过引用传递

    使用ref 关键字将传递对值的引用,而不是将值传递给函数或方法。这将为调用值创建一个别名。结果是同一内存位置的两个标识符。

    只需添加 ref 关键字即可:

    void main () {
            int a = 23;
            print ("Initial value: %i\n", a);
            modify_example (ref a);
            print ("Final value: %i\n", a);
    }
    
    void modify_example (ref int x) {
            x += 100;
    }
    

    现在产生:

    Initial value: 23
    Final value: 123
    

    调用modify_example () 的副作用是也改变了调用代码中的值。 ref 的使用使这一点变得明确,并且可以用作函数返回多个值的一种方式,但在此示例中,return 修改后的值会更清楚,而不是通过引用传递。

    引用类型总是通过引用传递

    对象是引用类型。这个例子没有使用显式的ref,而是在调用代码中改变了值:

    void main () {
            var a = new ExampleReferenceType (23);
            print ("Initial value: %i\n", a.value);
            modify_example (a);
            print ("Final value: %i\n", a.value);
    }
    
    class ExampleReferenceType {
            public int value;
    
            public ExampleReferenceType (int default = 0) {
                    this.value = default;
            }
    }
    
    void modify_example (ExampleReferenceType x) {
            x.value += 100;
    }
    

    这会产生:

    Initial value: 23
    Final value: 123
    

    以这种方式修改的对象在跟踪错误时可能会导致问题。这是使值对象不可变的优点。这将通过仅在构造函数中设置值、将所有字段设为私有并且仅使用属性来获取值而不设置它来完成。

    作为值类型的结构

    以下代码:

    void main () {
            ExampleStruct a = { 23 };
            print ("Initial value: %i\n", a.value);
            modify_example (a);
            print ("Final value: %i\n", a.value);
    }
    
    private struct ExampleStruct {
            public int value;
    }
    
    void modify_example (ExampleStruct x) {
            x.value += 100;
    }
    

    类似于您的代码并产生:

    Initial value: 23
    Final value: 23
    

    这与其他值类型在 Vala 中的行为相同,但如果您通过使用 --ccode 开关和 valac 查看 C 代码,您将看到结构被复制并通过引用传递。当您需要了解 Vala 如何维护 C ABI(应用程序二进制接口)时,这是相关的。

    结构方法

    您对手册的引用可能不清楚,但我认为它与结构中的方法有关。如果 modify_example 在结构定义内移动:

    void main () {
            ExampleStruct a = { 23 };
            print ("Initial value: %i\n", a.value);
            a.modify_example ();
            print ("Final value: %i\n", a.value);
    }
    
    private struct ExampleStruct {
            public int value;
    
            public void modify_example () {
                    this.value += 100;
            }
    }
    

    这现在产生:

    Initial value: 23
    Final value: 123
    

    该方法现在在实例上运行。

    [SimpleType] 结构体

    您引用的手册中的部分也在下一句中说明:

    可以通过将结构声明为简单的来更改此行为 输入。

    为了完整起见,这里是最后一个例子:

    void main () {
            ExampleStruct a = { 23 };
            print ("Initial value: %i\n", a.value);
            a.modify_example ();
            print ("Final value: %i\n", a.value);
    }
    
    [SimpleType]
    private struct ExampleStruct {
            public int value;
    
            public void modify_example () {
                    this.value += 100;
            }
    }
    

    这会产生:

    Initial value: 23
    Final value: 23
    

    虽然方法仍然在结构中定义,但实例是通过值而不是引用传递的。

    简单类型结构是在 Vala 中定义基本值类型的方式,例如 intint64。如果您想要定义一个作用于简单类型结构实例的结构方法,则必须定义该方法以返回一个包含修改后值的新实例。

    结论

    结构是 Vala 中的值类型,但了解它们是如何实现的以了解与 C ABI 的兼容性很有用。在您的示例中,结构表现为值类型,但根据 C ABI 通过引用进行复制和传递。引用似乎与结构中定义的方法最相关。

    【讨论】:

      【解决方案2】:

      我认为您所引用的引用文本要么已过时,要么一开始就有错误。

      如果你想通过引用传递它,你必须使用ref(或out)(因此得名ref)。

      struct Data {
          public int x;
      }
      
      void change_x (ref Data data) {
          data.x = 123;
      }
      
      int main () {
          Data a = Data ();
          change_x (ref a);
          print ("%d\n", a.x);
          return 0;
      }
      

      【讨论】:

      • 如果您查看 C 代码,则手册文本是正确的。输出 C 的函数签名是 void data_change_x (Data* data)。但是,主要功能是使用临时变量进行函数调用,我不确定为什么会这样。如果将change_x 设为结构的公共方法,该代码也可以工作。
      • 好点,我没有看 C 输出。 Vala 编译器通常使用许多临时变量。结论还是一样:如果你想在 Vala 中通过引用传递一些东西,总是使用ref。这就是关键字的用途。
      • 除了那些总是作为参考 AFAIK 传递的课程。
      猜你喜欢
      • 2013-06-04
      • 2013-05-12
      • 2011-02-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-13
      相关资源
      最近更新 更多