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

简介: 最近一年多的时间陆续接触了一些对我来说陌生的语言,主要就是 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
}


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


相关文章
|
4月前
|
存储 运维 前端开发
【面试题】吃透Promise?先实现一个再说(包含所有方法)(一)
【面试题】吃透Promise?先实现一个再说(包含所有方法)(一)
|
2月前
|
算法 编译器 C++
【C++ 函数 基本教程 第二篇 】深度剖析C++:作用域与函数查找机制
【C++ 函数 基本教程 第二篇 】深度剖析C++:作用域与函数查找机制
34 0
|
5月前
|
前端开发
【前端学习】—箭头函数和普通函数的区别(十四)
【前端学习】—箭头函数和普通函数的区别(十四)
|
6月前
一个案例搞懂解构
一个案例搞懂解构
对于async和await的使用方式、作用效果不怎么理解 ?没关系,初步看这篇就够了
对于async和await的使用方式、作用效果不怎么理解 ?没关系,初步看这篇就够了
|
11月前
|
存储 编译器 C语言
【C】函数真的难嘛?其实一点也不难,原理很简单。
# 什么是函数 程序是由多个零件组合而成的,而函数就是这种“零件”的一个较小单位。 ## main函数和库函数 C语言程序中,main函数是必不可少的。程序运行的时候,会执行main函数的主题部分。main函数中使用了printf、scanf、puts等函数。由C语言提供的这些为数众多的函数称为库函数。 ## 什么是函数 当然,我们也可以自己创建函数。而实际上,我们也必须亲自动手创建各种函数。下面我们来自己创建一个简单的函数。 创建一个函数,接收两个整数参数,返回较大整数的值。 printf函数和scanf函数等创建得比较好得函数,即使不知道其内容,只要了解使用方法,也可以轻松使用。 ## 函
|
11月前
|
存储 JavaScript 前端开发
📕 重学JavaScript:函数的入参(arguments)是什么类型?我能怎么使用它?
arguments是一个特殊的对象👏,它可以存储我们给函数传递的所有参数。只不过它的属性从0开始排,依次为0,1,2…最后还有callee和length属性。我们也把这样的对象称为类数组。
72 0
|
前端开发
前端学习案例2-闭包的形式
前端学习案例2-闭包的形式
29 0
前端学习案例2-闭包的形式
|
前端开发
前端学习案例1-闭包的形式
前端学习案例1-闭包的形式
38 0
前端学习案例1-闭包的形式
|
前端开发
前端学习案例2-闭包定义2
前端学习案例2-闭包定义2
46 0
前端学习案例2-闭包定义2