C/C++的值传递,指针传值和引用传值的区别

时间:2021-03-13 16:37:25

一 值传递,指针传值和引用传值详解

首先让我们来看下面几个代码:

#include <iostream>
using namespace std;

// 这里插入mySwap函数声明和定义

int main()
{
int n = 15, m = 20;
cout << "before:\tn = " << n << ", \tm = " << m << endl;
// 如果是传指针请用这种方式调用:
// mySwap(&n, &m);
mySwap(n, m);
cout << "after:\tn = " << n << ", \tm = " << m << endl;

return 0;
}

下面是几个版本的mySwap函数,分别进行尝试。

1.值传递:

void mySwap(int a, int b)
{
int temp = a;
a = b;
b = temp;
}

2.指针传递之一:

void mySwap(int *a, int *b)
{
int *temp = a;
a = b;
b = temp;
}

3.指针传递之二:

void mySwap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}

4.引用传值:

void mySwap(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}

各个函数的输出情况:

使用值传递的mySwap时,输出的n和m实际上没有被交换,使用指针传递之一的mySwap时,n和m的值仍未交换,但是使用指针传递之二和引用传值时,此时的n和m的值会被交换,下面解释原因:

函数传值:传值实际上是在子函数的栈里面重新开辟一个空间存储传进来的实参参数的值,所以在子函数内对子函数的形参参数进行操作(赋值之类的)只是改变了子函数那个栈里面的内存值,原main函数的值并不会被改变,因为main函数的变量内存并未改变。

传指针之一:如上面所说,程序在运行到调用函数的时候会重新开辟一个栈,并且为相应的形参参数分配内存空间,传指针之一里面的两个指针ab实际上是子函数mySwap的内部变量,这两个变量的值分别等于main函数的nm的内存地址值,所以在mySwap内部里交换ab的值,相当于子函数mySwap的内部变量ab的值被交换了,但是这并没有改变main函数的nm的内存里面的值,所以main函数的nm的值并没有改变。

传指针之二:有了前面的基础,理解这个就简单多了,刚刚说过,mySwap里面的ab存的分别是main函数里的nm的地址值,那么对ab操作相当于直接改变了ab所指向的这一个内存空间的值,也就是改变了main函数的变量nm的值,所以输出的结果是nm的值被交换了。(ps:实际上,指针传递的本质是值传递,只是传递的是指针的值罢了)

传引用:传引用的方法和传指针的相类似,也是在被调函数内部栈里面创建一个空间,这个空间存放的是主调函数实参的地址值,被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。

二 查看地址

现在让我们来看一下值传递,指针传递和引用传递的形参变量和实参变量的地址,main函数如下:

#include <iostream>
using namespace std;

// mySwap函数的声明和定义放在这里

int main()
{
int n = 15, m = 20;
cout << "before:\t&n = " << &n << ", \t&m = " << &m << endl;
cout << endl;

cout << "mySwap function called:" << endl;
// 如果是传指针请用这种方式调用:
// mySwap(&n, &m);
mySwap(n, m);
cout << "mySwap function end:" << endl;

cout << endl;
cout << "after:\t&n = " << &n << ", \t&m = " << &m << endl;

return 0;
}

下面是几个mySwap函数:

1.传值:

void mySwap(int a, int b)
{
cout << "mySwap:\t&a = " << &a << ", \t&b = " << &b << endl;
}

2.传指针:

void mySwap(int *a, int *b)
{
cout << "mySwap:\t&a = " << &a << ", \t&b = " << &b << endl;
cout << "mySwap:\ta = " << a << ", \tb = " << b << endl;
}

3.传引用:

void mySwap(int &a, int &b)
{
cout << "mySwap:\t&a = " << &a << ", \t&b = " << &b << endl;
}

输出的结果如下:

1.传值:

输出:

before: &n =0x7fff579ae1f8, &m = 0x7fff579ae1fc


mySwap functioncalled:

mySwap: &a =0x7fff579ae1dc, &b = 0x7fff579ae1d8

mySwap function end:


after: &n =0x7fff579ae1f8, &m = 0x7fff579ae1fc

解释:

如之前所说,形参ab的地址跟实参nm的地址不一样,说明a和b所在的内存空间跟n和m的内存空间并不相同。


2.传指针:

输出:

before: &n =0x7fff6597d9e8, &m = 0x7fff6597d9ec


mySwap functioncalled:

mySwap: &a =0x7fff6597d9b8, &b = 0x7fff6597d9b0

mySwap: a =0x7fff6597d9e8, b = 0x7fff6597d9ec

mySwap function end:


after: &n =0x7fff6597d9e8, &m = 0x7fff6597d9ec

解释:

形参ab有自己的内存空间,该空间存的值就是nm的地址值。


3.传引用:

输出:

before: &n =0x7fff14d803e8, &m = 0x7fff14d803ec


mySwap functioncalled:

mySwap: &a =0x7fff14d803e8, &b = 0x7fff14d803ec

mySwap function end:


after: &n =0x7fff14d803e8, &m = 0x7fff14d803ec

解释:

形参ab的地址分别和实参nm的一样,只是说,an的别名,bm的别名而已。


三 对比值传递,指针传递和引用传递的效率


让我们写一个主函数,分别调用三种方式的传递方式,被调函数什么也不做直接返回,那么调用的时间基本就是三种方式的效率对比值。

程序里面的clock函数获取的是CPU运行的周期计数,用它除以CLOCK_PER_SEC就可以得到系统的运行时间(以秒为单位),在这里我就暂时把clock返回的值看成是以毫秒为单位进行比较,想看更精确的时间,就把(double)(finish - start)替换成(double)(finish - start)/CLOCK_PER_SEC即可。

#include <iostream>
#include <ctime> // clock()函数查看时间
using namespace std;

struct Node
{
int num[1000];
};

void testByValue(Node a) { }
void testByReference(Node &a) { }
void testByPointer(Node *a) { }

int main()
{
time_t startTime, endTime;
Node n;
n.num[0] = 2222;
int maxNum = 1000000;
int i = maxNum;

// testByValue starts
startTime = clock();

while (i--)
testByValue(n);

endTime = clock();
cout << "testByValue: " << (double)(endTime-startTime) << "ms" << endl;
// testByValue ends

i = maxNum;
// testByReference starts
startTime = clock();

while (i--)
testByReference(n);

endTime = clock();
cout << "testByReference: " << (double)(endTime-startTime) << "ms" << endl;
// testByReference ends

i = maxNum;
// testByPointer starts
startTime = clock();

while (i--)
testByPointer(&n);

endTime = clock();
cout << "testByPointer: " << (double)(endTime-startTime) << "ms" << endl;
// testByPointer ends

return 0;
}

运行程序后的结果如下:

testByValue: 210031ms

testByReference: 2332ms

testByPointer: 2937ms

很明显,传值的耗用时间比传指针和引用的时间有着很大的数量级差别,相反,传引用和传指针的耗用时间却相差不是很多。原因是什么?因为传值是需要拷贝和复制值,也就是说每次调用传值的函数,那么都会在子函数栈内开辟那么大的内存,然后再全部初始化赋值形参,在调用次数很大的情况下当然需要很多的时间。但是传指针是怎样的?因为指针每次只需要创建一个指针大小的内存空间,然后把指针的值赋值为指向main函数的结构体的内存地址空间,只需要一步操作即可完成。同样的对引用变量的操作也是如此,至于引用和指针的时间差是因为引用传值,在操作的时候都需要间接寻址,这在操作次数很大的时候就会消耗一些时间,导致引用传值比指针传值需要的时间更多一些。但是其实二者时间差并不会相差很大,这个在待会继续实验就可以看出来,而且引用相对于指针来说也是有优点的,比如更安全一些。

现在让我们继续实验,分别对maxNum放大10倍100倍,或者对Node的num数组放大10倍或者100倍,查看运行结果:

maxNum放大10倍后的输出结果:

testByValue: 2.02018e+06ms

testByReference: 23150ms

testByPointer: 29761ms

maxNum放大100倍的输出结果:

testByValue: 2.00821e+07ms

testByReference: 230585ms

testByPointer: 294500ms

num放大10倍的结果:

testByValue: 1.66835e+06ms

testByReference: 2296ms

testByPointer: 2999ms

num放大100的结果:

testByValue: 1.68543e+07ms

testByReference: 2298ms

testByPointer: 2920ms

从上面的结果我们可以看出只放大10倍循环次数,传值,传指针或者传引用所需要的时间都是增长到10倍左右;但是只放大10倍结构体变量的大小,传值的时间仍然是增长到10倍左右,但传指针和引用的时间却基本没什么变化。其中的原因, 个人认为是赋值语句操作需要消耗时间,因为CC++的简单赋值语句好像消耗的时间大概是1us左右;另外,增加循环次数的时间变多的原因一方面是创建一个栈需要时间,删除一个栈也需要时间,调用次数多了,加上本身也会增加赋值语句的次数,时间积累起来就明显了。


到这里基本讲完了传值,传指针和传引用的原理和效率差别,希望对各位有所帮助吧。

PS:本人是在linux下面使用g++进行的测试。

参考链接:

http://xinklabi.iteye.com/blog/653643