在C++中,函数的形参有三种传递方式,分别为值传递,指针传递和引用传递,这三种传递方式各有不同的地方。
值传递:在函数调用时,将实参的值复制一份传递给形参。在这个过程中,形参和实参是两个独立的变量,它们在内存中占据不同的位置。即使形参的值在函数内部被修改,也不会影响到原始的实参值。
void Swap(int x ,int y)
指针传递:把变量的地址给函数形参的指针,使指针指向真实变量的地址,这种方式允许函数直接访问和修改调用者作用域中的变量。
void Swap(int* x,int* y)
引用传递:把形参当作是实参的“别名”,对形参的操作其实就是对实参的操作,可以直接访问和修改变量的值。
void Swap(int& x,int& y)
以Swap函数为例
值传递
#include<iostream>
using namespace std;
void Swap(int x,int y)
{
int tmp = x;
x = y;
y = tmp;
cout<<"x = "<<x<<" "<<"y = "<<y<<endl;
}
int main()
{
int a = 10,b = 20;
Swap(a,b);
cout<<"a = "<<a<<" "<<"b = "<<b<<endl;
}
大部分人可能认为运行结果是 x和y,a和b都会交换值吧,但真正的运行结果是
那么为什么a和b没有交换值呢?我们接着来看这样一个代码
#include<iostream>
using namespace std;
int main()
{
int a = 10;
int x = a;
x += 10;
cout << "a = " << a << "," << "x = " << x << endl;
}
这里的a虽然赋值给了x,但是a变量并不就是x变量,二者只是值相同,对x进行如何操作都不会影响到a的值。值传递和其原理相同。
int x = a; //
int y = b; //前两行就是值传递的操作
int tmp = x;
x = y;
y = tmp;
cout<<"x = "<<x<<" "<<"y = "<<y<<endl;
这里将a的值赋值给x,把b的值赋值给y,后面在对x和y的操作时并不会影响a和b,也就是说无论形参的值是多少都不会改变实参。
特点
-
传递副本:当通过值传递方式调用函数时,实际上是传递了实参的一个副本。这个副本在内存中有自己的存储位置。
-
不改变原始数据:由于传递的是副本,函数内部对形参的任何修改都不会影响到原始的实参。
-
独立性:形参和实参是两个独立的变量,它们在内存中占据不同的位置,需要额外开辟空间。
-
类型匹配:形参的类型必须与实参的类型相匹配,或者能够从实参类型隐式转换到形参类型。
-
性能开销:对于基本数据类型(如int、float等),值传递的开销通常很小。但对于大型数据结构,如数组或对象,值传递可能会导致较大的性能开销,因为它需要复制整个数据结构。
指针传递
#include<iostream>
using namespace std;
void Swap(int* px, int* py)
{
int tmp = *px;
*px = *py;
*py = tmp;
cout << "*px = " << *px << "," << "*py = " << *py << endl;
}
int main()
{
int a = 10, b = 20;
Swap(&a, &b);
cout << "a = " << a << "," << "b = " << b << endl;
}
它的运行结果是
这里将a和b的地址赋值给px和py,对px和py进行解引用的操作会直接影响到a和b的值
px = &a;
py = &b; //前两行就是指针传递的操作
int tmp = *px;
*px = *py;
*py = tmp;
cout << "*px = " << *px << "," << "*py = " << *py << endl;
所以在引用传递时函数内部对形参的任何操作都会影响到原始的实参。
特点
-
传递地址:当通过引用传递方式调用函数时,实际上是传递了实参的内存地址。函数内部通过这个地址直接访问和修改原始数据。
-
改变原始数据:由于传递的是地址,函数内部对形参的任何修改都会直接反映到原始的实参上。
-
数据共享:形参和实参指向内存中的同一个位置,因此它们共享数据。
-
类型匹配:形参通常是一个指针或引用类型,它需要与实参的类型兼容。
-
性能开销小:引用传递不需要复制整个数据结构,只需要传递一个地址,因此对于大型数据结构来说,性能开销较小。
注:地址传递本身不会为数据开辟额外的空间,但会为指针(即地址)开辟一个固定大小的空间。这是为了存储传递给函数的内存地址。
引用传递
#include<iostream>
using namespace std;
void Swap(int& rx, int& ry)
{
int tmp = rx;
rx = ry;
ry = tmp;
cout << "rx = " << rx << "," << "ry = " << ry << endl;
}
int main()
{
int a = 10, b = 20;
Swap(a, b);
cout << "a = " << a << "," << "b = " << b << endl;
}
它的运行结果为
这里的rx和ry分别是a和b的引用,也就是它们的别名,对rx和ry修改会直接影响到a和b的值。
特点
-
效率:引用传递避免了数据的复制,因此在传递大型数据结构时,引用传递比值传递更高效。
-
直接访问:通过引用传递,函数可以直接访问和修改调用函数中的原始变量,而不是变量的副本。
-
修改外部变量:引用传递允许函数内部修改外部变量的值,这在值传递中是无法实现的。
-
没有额外的内存开销:由于不需要为参数分配额外的内存来存储副本,引用传递不会增加额外的内存开销。
-
安全性:引用传递通常不允许引用被重新绑定到另一个对象,这意味着引用将始终指向同一个对象,这可以防止意外的修改。
-
生命周期:引用传递不会影响原始变量的生命周期,变量将在其原始作用域内正常销毁。
注意
不可空性:在许多编程语言中,引用必须引用一个有效的对象,不能是null或未初始化的。这意味着在使用引用之前,引用所指向的对象必须已经被创建。
左值引用:在C++中,引用分为左值引用和右值引用。左值引用通常用于常规的引用传递。
const引用:可以通过const引用传递来防止函数内部修改原始数据,这提供了一种只读的参数传递方式。
多态性:引用传递支持多态,即引用可以指向基类对象,但在函数内部可以按照派生类的类型来操作。