传值还是传引用?

堆与栈

在数据结构中:堆(heap)是一个可以被看作一棵完全二叉树的数组对象;栈(堆栈/stack)是先进后出的线性表。

堆:动态分配内存的一块区域,一般由程序员手动分配,比如Java中的new、C/C++中的malloc等,都是将创建的对象或内存块放置在堆中。

栈:由编译器自动分配释放,用于存放函数的参数值,局部变量。

二者关系:

  • 对于Java,基本类型直接存储在栈上,引用类型则是值(对象)存储在堆上,对对象的引用存储在栈上。
  • 修改引用的值,即让引用指向其它对象;而访问及修改堆上的对象:
    • 在C/C++中,通过指针运算符*p访问指针p指向的堆上的对象,然后修改;
    • 在Java中,通过点操作符,即先访问某个引用指向的对象,再让该对象调用相关的方法,从而修改引用指向的堆上的对象。

基本类型与引用类型

基本类型:

  • 值直接保存在变量中;
  • 赋值运算符会直接改变变量的值,原来的值被覆盖掉。

引用类型:

  • 变量中保存的是实际对象的地址;
  • 赋值运算符会改变引用中保存的地址,原来的地址被覆盖掉,但原来的对象不会被改变(没有被任何引用所指向的对象是垃圾,会被GC)

pass by value 与 pass by reference

传递是在函数调用时才会提到的概念,用于表明实参与形参的关系。

值传递:是指在调用函数时将实际参数复制一份传递到参数中,因此在函数中对参数的修改,不会影响到实际参数。(函数无法改变原始对象)

引用传递:是指在调用函数时将实际参数的地址直接传递到函数中,因此在函数中对参数的修改,将影响到实际参数。(函数可以改变原始对象)

求值策略

call by value:

  • 实际参数被求值,其值被绑定到函数中对应的变量上(通常是把值复制到新内存区域)
  • 在函数返回后调用者作用域里的曾传给函数的任何东西都不会变。

call by reference:

  • 传递给函数的是它的实际参数的隐式引用而不是实参的拷贝。通常函数能够修改这些参数(比如赋值),而且改变对于调用者是可见的。
  • 在使用传值调用又不支持传引用调用的语言里,可以用引用(引用其他对象的对象),比如指针(表示其他对象的内存地址的对象)来模拟。

call by sharing:

  • 在调用函数时,传递给参数的是实参地址的拷贝(如果实参在栈中,则直接拷贝该值);被Java、Python、JavaScript、OCaml等使用。

Go的相关知识

  • Go 的引用类型有 slice、map 和 chan等(变量存放的是内存地址);非引用类型有int、string、float、array、struct等(变量存的是值)
  • Go是pass by value的:In a function call, the function value and arguments are evaluated in the usual order. After they are evaluated, the parameters of the call are passed by value to the function and the called function begins execution. The return parameters of the function are passed by value back to the calling function when the function returns.
  • GO的引用类型,默认都是浅拷贝 (可以使用copy()进行深拷贝);值类型,默认都是深拷贝

python的深拷贝与浅拷贝

  • 赋值是将一个对象的地址赋值给变量,让变量指向该地址。(python的特点:所有变量本身只是一个引用)
  • 修改可变对象(list等)不需要开辟新的空间;而修改不可变对象(str/tuple)则需要开辟新的空间。
  • 浅拷贝(直接=)是对引用的拷贝,即复制容器中元素的地址
  • 深拷贝(copy.deepcopy)是对对象资源的拷贝,即完全拷贝了一个副本(容器内部元素原则上地址改变)

例子

在python中(pass-by-sharing)传递的是对象的内存地址,调用reassign()函数,传递list对象[0]的内存地址。在函数中,list变量指向了新的list对象[0,1](一个新的内存地址),而原始的list对象[0]的内存地址和对象的内容并没有改变。传递给append()函数的参数是原lList对象[0]的内存地址,然后修改了list对象的内容,list对象是可修改的,因此对象的内存地址没有变化。输出的是list变量所指向的对象,该对象被append()函数修改了,reassign()函数没有修改该对象,所以输出为[0,1]。


def reassign(list): 
 
  list = [0, 1]
 
def change_head(list): 
 
  list[0] = 1

 
list = [0]
 
reassign(list)
 
change_head(list)
 
print(list)

# result: [1]

在go中(总是pass-by-value,没有引用传递)传递的总是参数的拷贝,比如传递一个指针类型的参数,其实传递的是这个指针的一份副本。而之所以引用类型的传递可以修改原内容数据,是因为引用类型的传递在底层默认使用了该引用类型的指针进行传递,但是也是使用的指针的副本,所以依然是值传递。

func reassign(list []int){
	list = []int{0,1}

}

func change_head(list []int){
	list[0] =1
}


list := []int{0}
reassign(list)
change_head(list)
fmt.Println(list)

// result: [1]

Reference

求值策略

go语言参数传递

谈谈python中的深拷贝和浅拷贝

猜你喜欢

转载自blog.csdn.net/qq_34276652/article/details/116518326