函数 / 参数 / 编程语言 · 2023年 10月 15日 0

值传递和引用传递

45 次浏览

值传递和引用传递是函数参数传递的两种类型,一般而言,基本数据类型都是值传递,数组和对象采用引用传递减少对象复制开销,但也有特例。

值和引用传递本质一样

  • 值传递是拷贝值到函数参数,引用传递是拷贝引用(或者对象的指针)到函数参数,把引用当成值,二者无差异。基本数据类型值传递,函数参数已经包含了值的全部信息,引用传递传地址。
  • 传递引用不止可读数据,也可改写数据,同时开销还小,这是引用传递最大的作用。联想一下CPU RIP指针或RBP/RSP指针,指哪打哪,简单直接。

不同编程语言的参数传递

对于整型、字符、枚举、浮点数等基本数据类型,都按照值传递,如下主要比较复杂类型。

  • C/ObjC/C++数组按引用(指针)传递,一般函数会带额外的数组大小参数。C语言struct默认按值传递,C++保持对C语言的兼容,struct和类对象默认按值传递,可以改成指针或引用。
  • C#数组全部是引用传递,结构体是按值传递,可以用ref或out指示按引用传递,类对象全部是引用传递。ref和out表示引用功能基本完全相同,除了编译器对out修饰的变量会检查所有路径都有赋值。
  • Java数组和类对象都是引用传递,没有结构体。
  • Ada在参数加上out修饰表明是引用传递。
  • PHP 形参和实参前面加上&代表引用传递,即对应变量修改会影响外面。
  • Python 对于不可变对象(比如基本整数、字符串或者元组等),是值传递,对于可变对象,比如列表、字典,采用引用传递。
    • Python 也支持对于可变对象传递副本的方法,例如列表的切片:list_obj[:].
  • Pascal 用"变量传递"(var修饰的变量)指代引用传递。

值传递示例

以C语言为例,我们来研究调用一个简单的加法函数堆栈的层次。如下调用add(1, 2):

int add(int a, int b)
{
int sum;
sum = a + b; return sum;
}

  • 如下面图示,下划线标注的0x006FFA80/0x006FFA84是调用add函数的参数地址,分别是1和2, 方框开头是0x006FFA74, 是add函数内部形参的首地址,也对应1和2, 两个地址是独立的。

注意:在Debug版本可能不易观察,因编译器可能加入一些Debug参数,在VS2019上,有/JMC, /GS, /RTC, 它们插入了不少防范的资讯,导致堆栈信息不清晰。建议用Release版本,并关掉优化(/Od).

更自然的传出参数

  • C# 7.0 支持放弃out参数,也支持内嵌声明out变量(不需要在外面单独声明)。
    • 例如 test(out int a).