一文搞懂参数传递原理(中)

简介: 最近一年多的时间陆续接触了一些对我来说陌生的语言,主要就是 Python 和 Go,期间为了快速实现需求只是依葫芦画瓢的撸代码;并没有深究一些细节与原理。 就拿参数传递一事来说各个语言的实现细节各不相同,但又有类似之处;在许多新手入门时容易搞不清楚,导致犯一些低级错误。

所以同理,类似于这样的代码也会影响到外部原始数据:


@Test
    public void testList(){
        List<Integer> list = new ArrayList<>();
        list.add(1);
        addList(list);
        System.out.println(list);
    }
    private void addList(List<Integer> list) {
        list.add(2);
    }
    [1, 2]


那如果是这样的代码:


@Test
    public void test02(){
        Car car1 = new Car("benz");
        modifyCar(car1);
        System.out.println(String.format("最终结果 main car1==%s", car1));
    }
    private void modifyCar(Car car2) {
        System.out.println(String.format("修改之前 car2==%s", car2));
        car2 = new Car("bmw");
        System.out.println(String.format("修改之后 car2==%s", car2));
    }


假设 Java 是引用传递那最终的结果应该是打印 bmw 才对。


修改之前 car2==Car{name='benz'}
修改之后 car2==Car{name='bmw'}
最终结果 main car1==Car{name='benz'}


从结果又能佐证这里依然是值传递。


网络异常,图片无法展示
|


如果是引用传递,原本的 0x1102 应该是被直接替换为新创建的 0x1103 才对;而实际情况如上图所示,car2 直接重新引用了一个对象,两个对象之间互不干扰。


Go


相对于 Java 来说  Go 的用法又有所不同,不过我们也可以先得出结论:


Go语言的参数也是值传递。


Go 语言中数据类型主要有以下两种:


网络异常,图片无法展示
|


值类型与引用类型;


值类型


先以值类型举例:


func main() {
  a :=10
  modifyValue(a)
  fmt.Printf("最终 a=%v", a)
}
func modifyValue(a int) {
  a = 20
}
输出:最终 a=10


网络异常,图片无法展示
|


函数调用过程与之前的 Java 类似,本质上传递到函数中的值也是  a  的拷贝,所以对其的修改不会影响到原始数据。


当我们把代码稍加修改:


func main() {
  a :=10
  fmt.Printf("传递之前a的内存地址%p \n", &a)
  modifyValue(&a)
  fmt.Printf("最终 a=%v", a)
}
func modifyValue(a *int) {
  fmt.Printf("传递之后a的内存地址%p \n", &a)
  *a = 20
}
传递之前a的内存地址0xc0000b4040 
传递之后a的内存地址0xc0000ae020
最终 a=20


从结果来看最终 a 的值是被方法修改了,这点便是 GoJava 很大的不同点:


Go 中存在着指针的概念,我们可以将变量通过指针的方式传递到不同的方法中,在方法里便可通过这个指针访问甚至修改原始数据。


那这么一看不就是引用传递嘛?


其实不然,我们仔细看看刚才的输出会发现参数传递前后的内存地址并不相同。


传递之前a的内存地址0xc0000b4040 
传递之后a的内存地址0xc0000ae020


这也恰好论证了值传递,因为这里实际传递的是指针的拷贝。


也就是说 modifyValue 方法中的参数与入参的&a都是同一块内存的指针,但指针本身也是需要内存来存放的,所以在方法调用过程中新建了一个指针 a ,从而导致他们的内存地址不同。


虽然内存地址不同,但指向的数据都是同一块,所以方法内修改后原始数据也受到了影响。


引用类型


对于 map slice channel 这类引用类型又略有不同:


func main() {
  var personList = []string{"张三","李四"}
  modifySlice(personList)
  fmt.Printf("slice=%v \n", personList)
}
func modifySlice(personList []string) {
  personList[1] = "王五"
}
slice=[张三 王五]


最终我们会发现原始数据也被修改了,但我们并没有传递指针;同样的特性也适用于 map


但其实我们查看 slice 的源码会发现存放数据的 array 就是指针类型:


type slice struct {
  array unsafe.Pointer
  len   int
  cap   int
}


所以我们可以直接对数据进行修改,相当于间接的带了指针。


相关文章
|
1月前
|
开发者
简述函数和框架的区别
简述函数和框架的区别
13 1
|
19天前
|
图形学
一文搞懂:【UnityAPI】MenuItem属性
一文搞懂:【UnityAPI】MenuItem属性
|
2月前
|
算法 编译器 C++
【C++ 函数 基本教程 第二篇 】深度剖析C++:作用域与函数查找机制
【C++ 函数 基本教程 第二篇 】深度剖析C++:作用域与函数查找机制
43 0
|
10月前
|
自然语言处理 前端开发 JavaScript
前端经典面试题 | 闭包的作用和原理
前端经典面试题 | 闭包的作用和原理
|
2月前
|
SQL 关系型数据库 MySQL
搞懂connectTimeout和socketTimeout的区别
搞懂connectTimeout和socketTimeout的区别
125 0
|
2月前
|
存储 C++
面试题:C++函数调用的过程?
面试题:C++函数调用的过程?
50 0
|
2月前
|
存储 缓存 自然语言处理
【面试题】深入理解闭包的形成过程及应用!
【面试题】深入理解闭包的形成过程及应用!
|
存储 编译器 C语言
【C】函数真的难嘛?其实一点也不难,原理很简单。
# 什么是函数 程序是由多个零件组合而成的,而函数就是这种“零件”的一个较小单位。 ## main函数和库函数 C语言程序中,main函数是必不可少的。程序运行的时候,会执行main函数的主题部分。main函数中使用了printf、scanf、puts等函数。由C语言提供的这些为数众多的函数称为库函数。 ## 什么是函数 当然,我们也可以自己创建函数。而实际上,我们也必须亲自动手创建各种函数。下面我们来自己创建一个简单的函数。 创建一个函数,接收两个整数参数,返回较大整数的值。 printf函数和scanf函数等创建得比较好得函数,即使不知道其内容,只要了解使用方法,也可以轻松使用。 ## 函
|
C语言
一直没有搞懂的C语言参数传递,今天终于明白了
一直没有搞懂的C语言参数传递,今天终于明白了
83 0