函数参数的求值策略 Evaluation Strategy 指的是函数调用时,实参(表达式)的求值和传值方式,主要有两种求值策略,按值传递和按引用传递。
按值传递(pass by value)是指在调用函数时将实参复制一份传递到函数中,在函数中如果对参数进行修改,将不会影响到实参。
引用传递(pass by reference)是指在调用函数时将实参的地址直接传递到函数中,在函数中对参数所进行的修改,将影响到实参。
按值传递传递的是原始值的复制,或内存地址值的复制(比如 JS 中的共享传递,C/C++ 中的指针传递)。按引用传递传递的是内存地址(不是内存地址值)。
JavaScript 中参数的求值策略 Javascript 中函数参数求值策略是按值传递。无论是值类型还是引用类型,都会在栈上创建副本(拷贝、复制),不同是,对于值类型而言,这个副本就是整个原始值的复制,对于引用类型,由于引用类型的实例在堆中,在栈上只有它的一个地址引用值,其副本也只是这个引用值的复制,而不是整个原始对象的复制,这种策略也被称为按共享传递(传递的是地址值,可通过引用来修改原始对象的属性,重新赋值则会断开对原始对象的引用,不影响原始对象),类似于 C 中的指针传递。按共享传递是按值传递的特例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function change (num, obj ) { num += 10 ; obj.value = 'hello' ; } const a = 10 ;const greeting = { value : 'hello world' , }; change (a, greeting);console .log (a); console .log (greeting);
上面例子的内存模型图如下:
1 2 3 4 5 6 stack(栈) | heap(堆) --------------------------------- a 10 | greeting 0x01 ---> | 0x01 hello world num 10 | obj 0x01 ---> |
如果是按引用传递,直接传递第二格的内容即可,不需要有第四格。
其他语言中参数的求值策略 Java 中参数求值策略与 JavaScript 一样,都是按值传递(含共享传递)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class TestByValue { public static void main (String[] args) { int a = 10 ; Greeting greeting = new Greeting (); TestByValue testByValue = new TestByValue (); testByValue.change(a, greeting); System.out.println(a); System.out.println(greeting.value); } public void change (int num, Greeting greeting) { num += 10 ; greeting.value = "hello" ; } } class Greeting { String value = "hello world" ; }
1 2 3 4 # 编译 javac TestByValue.java # 运行 java TestByValue
PHP 既支持值传递又支持引用传递,通过 & 运算符(取址运算符)实现引用传递。
1 2 3 4 5 6 7 8 function change (&$num ) { $num = $num + 100 ; } $a = 1 ;echo $a ; change ($a );echo $a ;
C/C++ 支持值传递(含指针传递), 另外 C++ 还支持引用传递,通过 & 取址运算符实现引用传递。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <stdio.h> void change (int num1, int &num2, int *num3) { num1 = 11 ; num2 = 22 ; *num3 = 33 ; } int main () { int a = 1 ; int b = 2 ; int c = 3 ; change (a, b, &c); printf ("%d\n" , a); printf ("%d\n" , b); printf ("%d\n" , c); return 0 ; }
注意:上面代码需使用 gcc test.cpp -lstdc++ -o test
作为 C++ 编译,不能使用 gcc -o test test.c
作为 C 编译,C 没有按引用传递,都是按值传递,通过指针传递也可实现引用传递的效果,要想通过 C 编译,需删除引用传递。
其内存图如下。
1 2 3 4 5 a 1 b/num2 2 c [0x7ff7b2c49920] 3 num1 1 num3 [0x7ff7b2c498f8] 0x7ff7b2c49920
num1 的内容是复制于 a 的原值,num2 是 b 的别名,num3 的内容是 c 的地址值,*num3 指向 c 的内容。
指针传递本质上也是值传递的方式,它所传递的是一个地址值,与 JavaScript 中的共享传递一样。C/C++ 中通过 * 指针运算符实现指针传递。
作为指针类型数据本身,其既可使用指针传递又可使用引用传递。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> void change (int *&p) { std::cout<<&p<<'\n' ; std::cout<<p<<'\n' ; std::cout<<*p<<'\n' ; *p = 11 ; } int main () { int a = 1 ; std::cout<<&a<<'\n' ; std::cout<<a<<'\n' ; int *b = &a; std::cout<<&b<<'\n' ; std::cout<<b<<'\n' ; std::cout<<*b<<'\n' ; change (b); std::cout<<a<<'\n' ; return 0 ; }
算子的求值策略 求值策略不但规定了函数参数的求值规则,也规定了算子的求值规则,比如赋值表达式中的 =
运算符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int main () { int a = 1 ; int b = 2 ; int c = 3 ; int num1 = a; int *num2; num2 = &b; int &num3 = c; num1 = 11 ; *num2 = 22 ; num3 = 33 ; printf ("%d\n" , a); printf ("%d\n" , b); printf ("%d\n" , c); return 0 ; }
1 2 3 4 const a = 1 ;const b = {value : 2 };const num = a;const obj = b;