「新手入门」关于参数引用


#1

请问Julia语言中有没有像C/C++中的函数参数地址引用


#2

#3

Julia的变量是对某个值的绑定,是“引用语义”的,这和C/C++有显著不同。

int a=1; //a表示一块在内存中的大小为sizeof(int)的区域,是值语义的
int b=a; //b复制了a的值

而在Julia当中

a=MyType() #a是对一个MyType类型的值的引用
b=a #现在b和a绑定到了同一个值上

因此Julia的变量之间赋值天生就是“传引用”的,函数调用同理。但是,对于isbitstype类型来说(类似C/C++的基本类型),它们是不可变的,并且它们里面也没有字段,所以你没有办法把这样的类型传给函数里面然后修改它。(事实上,由于isbits变量不可变,两个Int类型的值11,只要数字是一样的,它们之间毫无区别,因此引用不引用的已经无关紧要了,编译器给你编译出来的可能就是立即数操作的机器码,根本不涉及堆内的对象。)

a=1 #Int(1)是isbits且不可变的
b=a
a=2 #这只是让a指向了另一个值,b不会受到任何影响

但是你可以把不可变的值放在可变的容器里,比如数组:

julia> a=[1,2,3]
3-element Array{Int64,1}:
 1
 2
 3

julia> function mutate!(x)
           push!(x,4)
       end
mutate! (generic function with 1 method)

julia> mutate!(a);

julia> a
4-element Array{Int64,1}:
 1
 2
 3
 4

通过在函数内部直接修改a指向的那个数组,你就可以在函数内部对外部造成副作用。
在和C/C++代码交互的时候,可以把值放在Ref里面,它代表对某一个值的引用,且它位于一块稳定的内存里,你可以拿到它的指针,然后传给C/C++的函数:

julia> x=Ref(0) #x指向一个Int(0)的Ref,你可以拿到这块内存的地址
Base.RefValue{Int64}(0)

julia> using Cxx

C++ > void mutate(int64_t* p){
          *p=42;
      }
true

julia> ptr=Base.unsafe_convert(Ptr{Int64},x)
Ptr{Int64} @0x000000000ff32fe0

julia> @cxx mutate(ptr)

julia> x[] #解引用,获得Ref的内容
42

即使一个类型是不可变的,但它拥有可变的成员,你也可以通过引用修改它的内容:

julia> struct ImmutableType
           x
       end

julia> u=ImmutableType(Ref(1))
ImmutableType(Base.RefValue{Int64}(1))

julia> v=u
ImmutableType(Base.RefValue{Int64}(1))

julia> v.x[]=2
2

julia> u.x[]
2

这里的不可变指的是,你无法修改ImmutableType里的字段x,使x指向另一个对象(例如v.x=Ref(3)),并不代表整个对象所在的内存是只读的。

题外话:
可变对象有点类似C/C++的左值概念,你可以拿到可变对象的稳定的内存地址,这同时也会阻止一些优化措施,因此条件允许的情况下应该优先使用不可变对象。可变类型之间的相等判定===是比较内存地址,而不可变类型之间的相等默认则是比较它们的值。如果一个类型是不可变的,又没有成员,那么它就是个单例,即

julia> struct Singleton end

julia> Singleton()===Singleton()
true