传值与传地址

栏目: IT技术 · 发布时间: 4年前

内容简介:很多语言在传参的时候都有一个传值和传地址(或者是引用)的问题,我想用 C++ 语言来简单的描述一下。因为我觉得无论是传值还是传址,C 或者 C++ 这两种语言都是能够比较直观的描述清楚的语言,原因是可以容易的去观察内存。其他语言也可能可以,只是其他语言的我不太知道如何去做。NO.1传递两个 int 型参数

很多语言在传参的时候都有一个传值和传地址(或者是引用)的问题,我想用 C++ 语言来简单的描述一下。因为我觉得无论是传值还是传址,C 或者 C++ 这两种语言都是能够比较直观的描述清楚的语言,原因是可以容易的去观察内存。其他语言也可能可以,只是其他语言的我不太知道如何去做。

NO.1

传递两个 int 型参数

先来看第一段程序,代码如下:

void swap(int a, int b)

{

cout << "a = " << a << " b = " << b << endl;


int tmp = a;

a = b;

b = tmp;


cout << "a = " << a << " b = " << b << endl;

}


int main()

{

int x = 1, y = 2;


cout << "x = " << x << " y = " << y << endl;


swap(x, y);


cout << "x = " << x << " y = " << y << endl;


return 0;

}

代码比较简短,是一个交换函数。在 main 函数中定义了两个变量,分别是 x 和 y,其值分别为 1 和 2,然后调用 swap 函数,swap 函数当中是典型的交换两个值的一段代码。然后 main 函数中,调用 swap 函数前后都会输出 x 和 y 的两个值,然后观察一下它们的输出。

传值与传地址

从图中,第一行和最后一行是 main 函数输出的结果,第二行和第三行是 swap 函数输出的结果。图中可以看出,在 swap 函数中,两个变量的值交换了,而在 main 函数中值是没有被交换的。

我们在调用 swap(x, y) 的位置处,设置一个断点,然后查看一下 变量 x 和 变量 y 的地址,如下图。

传值与传地址

从图中可以看到,x 的地址是 0x0019fefc,y 的地址是 0x0019fef0,记住这两个地址,然后去 swap 函数中查看 变量 a 和 变量 b 的地址,如下图。

传值与传地址

从图中可以看到,a 的地址是 0x0019fe18,b 的地址是 0x19fe1c。

注意看上面两张图,两个地址下面都是保存的实际的值。

这就说明,形参 a、形参 b 和 实参 x、实参 y 根本不是一个地址。因为在函数调用时,形参只是一个副本,只是把值的内容由实参拷贝给了形参。而形参进行值交换以后,实参是不会受影响的。

如果想要在 swap 中改变两个变量的值,并影响 main 函数中的两个实参,其实是可以的。但是这里不演示,我们通过后面的内容继续说。

NO.2

传递一个对象

再来看第二段代码,代码如下:

class Point

{

public:

int x;

int y;

};


void swap(Point pt)

{

cout << "pt.x " << pt.x << " pt.y " << pt.y << endl;


int tmp = pt.x;

pt.x = pt.y;

pt.y = tmp;


cout << "pt.x " << pt.x << " pt.y " << pt.y << endl;

}


int main()

{

Point pt;

pt.x = 1;

pt.y = 2;


cout << "pt.x " << pt.x << " pt.y " << pt.y << endl;

swap(pt);

cout << "pt.x " << pt.x << " pt.y " << pt.y << endl;


return 0;

}

第二段代码,我这里定义了一个类,类中由两个成员变量,然后仍然通过 swap 方法来交换两个成员变量的值,swap 函数的形参是 Point 类型,输出结果如下图。

传值与传地址

可以看到,第一行和第四行是 main 函数的输出,第二行和第三行是 swap 函数的输出。同样,swap 函数的交换没有影响到 main 函数。继续看一下它们的地址,先来看 main 函数中 pt 变量的地址,如下图。

传值与传地址

可以看到,pt 变量的地址是 0x0019fef8,记住这个地址,然后看 swap 中形参 pt 变量的地址,如下图。

传值与传地址

可以看到,在 swap 中形参 pt 的地址是 0x0019fe20。

注意看上面两张图,对象地址下面都是保存的是成员变量的值。

可以看到,实参和形参的地址同样不是相同的地址。同样,调用函数的时候,把 main 函数中的 pt 对象的值拷贝了一份给 swap 函数的形参 pt,还是一个值拷贝的过程。因此在 swap 中交换还是没有影响 main 函数中的值。

NO.3

传递一个对象的指针

再看第三段代码,代码如下。

class Point

{

public:

int x;

int y;

};


void swap(Point *pt)

{

cout << "pt->x = " << pt->x << " pt->y = " << pt->y << endl;


int tmp = pt->x;

pt->x = pt->y;

pt->y = tmp;


cout << "pt->x = " << pt->x << " pt->y = " << pt->y << endl;

}


int main()

{

Point *pt = new Point();


pt->x = 10;

pt->y = 20;


cout << "pt->x = " << pt->x << " pt->y = " << pt->y << endl;

swap(pt);

cout << "pt->x = " << pt->x << " pt->y = " << pt->y << endl;


return 0;

}

这段代码就稍有不同,这段代码中出现了指针,而且 Point 的对象 pt 是通过 new 实例化出来的。看一下输出结果。

传值与传地址

可以看出,swap 函数中的交换影响了 main 函数中的值。同样的,在 main 函数中下断点,看 pt 对象的地址。

传值与传地址

这次注意一下这次图中和上面图中的变化,这次的 pt 的地址是 0x0019fefc,但是它的下面是一个地址,而不是成员变量的值了,而这个地址是 0x004ae718,在这个地址下面是成员变量 X 和 y 的值。

然后,我们去 swap 中,看一下它形参的情况,如下图。

传值与传地址

可以看到,形参 pt 的地址是 0x0019fe18,和 main 函数中的地址还是不同,但是注意下面值,还是一个地址,而这个地址和 main 函数中的地址是一样的,也是 0x004ae718,这个地址下面,就是成员变量 x 和 y 的值了。

这样在 swap 函数中进行交换以后,就影响了 main 函数中的值。

注意:

1、这里实参和形参保存了相同的堆地址,但是实参和形参的地址是不一样的,实参在栈中的地址是 0x0019fefc,形参在栈中的地址是 0x0019fe18。

2、栈地址的由 0x0019 开头的,而堆地址是由 0x004a 开头的,两块内存的跨度很大。

3、如果你在你的机器上进行调试的时候,你的栈地址、堆地址跟我可能会不一样,因为这些地址受操作系统版本号、补丁号、编译器版本、甚至编译器参数的影响。

NO.4

传递对象和对象指针的内存示意图

画个图说明一下。

先来画一下第二段代码的内存示意图,如下图。

传值与传地址

在内存中大体有几个部分,分别是代码区、栈区、堆区、全局数据区、只读数据区……对于 main 函数,在栈中有它对应的一块栈帧,当 main 函数调用 swap 函数时,CPU 会给 swap 开辟一块栈帧。(注意:栈的维护是由 CPU 的两个寄存器管理的,在 64 位 CPU 下分别是 RBP 和 RSP 两个寄存器来维护的,且栈地址的方向是由高地址向低地址延申的。这个栈不是 JVM 的栈)然后,main 函数会把 pt 中的内容拷贝一个副本给 swap 的形参 pt,因为形参 pt 的数据在自己的栈帧中,因此,它的值交换是不会影响到 main 函数对应的栈帧中的。

再来画一下第三段代码的内存示意图,如下图。

传值与传地址

第三段代码中的 pt 是一个 Point* 类型,就是一个指针。然后 Point 的实例化是由 new 进行的,使用 new 实例化的对象在堆中,new 完成以后会把在堆中开辟的内存空间的首地址赋值给 pt 变量,也就是说 pt 中保存的是 Point 实例在堆空间中的首地址。

当 main 函数调用 swap 的时候,传递的同样是自身的一个副本,不过这个副本是 Point 实例在堆空间的首地址,那么 swap 的形参 pt 中也保存了 Point 实例在堆空间的首地址。此时,实参 pt 和 形参 pt 都指向了同一块内存,那么当 swap 中交换两个成员变量的后,当然会影响 main 中实参两个成员变量的值。

NO.5

传递 int 型指针

对于,第一段代码来说,如果想要在 swap 中的交换影响 main 中的值,可以把 实参的地址传递给 swap 的形参,代码如下。

void swap(int *a, int *b)

{

cout << "*a " << *a << " *b " << *b << endl;


int tmp = *a;

*a = *b;

*b = tmp;


cout << "*a " << *a << " *b " << *b << endl;

}


int main()

{

int x = 1, y = 2;


cout << "x " << x << " y " << y << endl;


swap(&x, &y);


cout << "x " << x << " y " << y << endl;


return 0;

}

输出图下图所示。

传值与传地址

对于第一段和第二段代码在 C++ 中称为传值,对于第三段和第四段代码在 C++ 中称为传地址。 地址和值在内存中本身都是一个值,只是具体分类是做了区别而已。

NO.6

Java 传参

Java 中说的传参全部是传值,但是当形参是一个对象的时候,其实相当于传的是一个地址。因为变量中本身就保存的是一个地址,而不是一个值。具体方式和 C++ 的是类似的(和第三段代码类)。Java 的对象是 new 出来的,也在堆空间中,而 new 赋值的那个变量是在栈中,栈中同样保存的是堆空间的首地址。传参时,也是把堆空间的地址传给了形参。Java 的堆和栈,是由 JVM 管理和维护。

传值与传地址


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

敏捷教练

敏捷教练

[英] Rachel Davies、[英] Liz Sedley / 徐毅、袁店明 / 清华大学出版社 / 2013-7 / 49.00元

《敏捷教练:如何打造优秀的敏捷团队》取材于国际知名敏捷教练的真实经历,展示了他们在辅导团队进行敏捷实践过程中所积累的辅导技巧,凝聚着他们在对敏捷辅导的真知灼见,每章还针对特定主题总结了在转型过程中教练和团队可能面对的障碍及其应对方案。 《敏捷教练:如何打造优秀的敏捷团队》具有较强的实用性和指导性,适合项目经理、技术总监和敏捷团队的所有成员阅读与参考。一起来看看 《敏捷教练》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具