1 C++ 值传递、指针传递、引用传递详解
值传递:
形参是实参的拷贝,改变形参的值并不会影响外部实参的值。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出。当函数内部需要修改参数,并且不希望这个改变影响调用者时,采用值传递。
指针传递:
形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作
引用传递:
- 形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作。
- 在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。
- 被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。
- 正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
下面的代码对此作出细致解释(从实参,形参在内存中存放地址的角度 说明了问题的本质,容易理解 )
#include <iostream>
#include <vector>
using namespace std;
#include<iostream>
using namespace std;
//值传递
void change1(int n){
cout<<"值传递--函数操作地址"<<&n<<endl; //显示的是拷贝的地址而不是源地址
n++;
}
//引用传递
void change2(int & n){
cout<<"引用传递--函数操作地址"<<&n<<endl;
n++;
}
//指针传递
void change3(int *n){
cout<<"指针传递--函数操作地址 "<<n<<endl;
*n=*n+1;
}
int main(){
int n=10;
cout<<"实参的地址"<<&n<<endl;
change1(n);
cout<<"after change1() n="<<n<<endl;
change2(n);
cout<<"after change2() n="<<n<<endl;
change3(&n);
cout<<"after change3() n="<<n<<endl;
return true;
}
引用的规则:
(1)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。
(2)不能有NULL引用,引用必须与合法的存储单元关联(指针则可以是NULL)。
(3)一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。
参考链接:https://www.cnblogs.com/yanlingyin/archive/2011/12/07/2278961.html
2 数组作为函数的形参
2.1 一维数组传递
然而,由于在C和C++中数组不能直接复制,传递时只传递一个首地址,在函数中需要使用的时候再跟据首地址和下标去寻找对应的值。
至于为何数组不能直接复制,有一种解释的说法:
- 是为了避免不必要的复制开销,因为数组的复制将导致连续的内存读与内存写,其时间开销取决于数组长度,有可能会变得非常大。为了避免复制数组的开销,才用指针代替数组。因此C语言使得当数组作为实参传递给函数的时候,将退化为同类型的指针,再传递指针的值。
因此,在函数中修改数组值时,修改的是真的值。
#include <iostream>
#include <vector>
using namespace std;
#include<iostream>
using namespace std;
//值传递
void printdz(int a[])
{
cout<<"子函数里a0的地址为"<<&a[0]<<endl;
cout<<"子函数里a的地址为"<<a<<endl; //a本身即为指针 无需取地址符
cout<<"子函数里a1的首地址为"<<&a[1]<<endl;
a[1]=2;
}
int main()
{
int a[10]={1};
cout<<"主函数里a的首地址为"<<&a[0]<<endl;
cout<<"开始的a1为 "<<a[1]<<endl;
printdz(a);
cout<<"函数里改过的a1 为"<<a[1]<<endl;
return 0;
}
- 在这里我们可以看到子函数里的首地址a[0]与主函数的首地址是相同的。
- a已经是一个指针了。由于是通过指针传递,因此无法得到数组的长度。
除了退化为指针传递,还可以直接通过指针传递和通过引用传递。
指针传递:
void printdzyy(int *aa) //传入指针 or void printdzyy(int aa[])
{
cout<<"函数里参数的地址为"<<&aa[0]<<endl;
}
int main()
{
int a[10]={1};
int b[5]={1};
cout<<"主函数里a[0]的首地址为"<<&a[0]<<endl;
printdzyy(a);
printdzyy(b);//传入指针时 编译器无法知道数组长度 因此可以随便传
return 0;
}
通过引用传递::
void printdzyy(int (&aa)[10]) //引用就可以传递数组长度 因此需要写出数组大小
{
cout<<"函数里aa的地址为"<<aa<<endl;
}
int main()
{
int a[10]={1};
int b[5]={1};
cout<<"主函数里a[0]的首地址为"<<&a[0]<<endl;
printdzyy(a);
//printdzyy(b); incorrect //如果传入b则编译不通过
return 0;
}
- 从上述程序可以知道,传入指针时,编译器无法知道数组长度,因此可以传递进去不同长度的数组,也可以不写出数组长度。
- 但是传递引用时,同时将数组长度也传递进去了,所以传入时必须为相应长度的数组。并且,在传递引用时,需要注意用()将&aa括起来,否则编译器报错。
- 对于引用,也叫“别名”,某种意义上可作为无地址的指针,相比指针,引用不存在地址,必须被初始化,不存在NULL,不可改变对象,引用无法被多重引用。因此更加安全。
2.2 二维数组传递
可以理解为数组的定义等同于指针的定义,即*a等同于a[],因此可以作如下变换
#include <iostream>
#include <vector>
using namespace std;
//1.5
void func1(int iArray[][10]) //等同于 void func1(int (*iArray)[10])
{
} //等同于void func1(int iArray[10][10])
int main()
{
int array[10][10];
func1(array);
}
注意(iAarray)可通过,去掉括号会编译错误*
由于二维数组在栈内分配的内存是连续的,需要告诉编译器偏移的点,所以第二维度不可省,必须符合,而第一维度如一维数组一样随意。因此在上述代码1.5 中void func1(int iArray[9][10])不会报错,而void func1(int iArray[10][9])会报错。
同一维数组,这边推荐使用引用,使用引用时需要保证两个维度都要ok:
//1.6
void func3(int (&pArray)[10][10])
{ }
int main()
{
int array[10][10];
func3(array);
}
也可以指针的指针来表示二维数组,动态分配内存的形式,此时,严格来说,并不是二维数组。
#include <iostream>
#include <stdio.h>
void out(double **a,int m, int n)
{
int i, j;
double b=0.0;
for(i=0; i<m; i++)
{for (j=0; j<n; j++)
{
a[i][j] = b;
b += 1.2;
printf("%5.1f",a[i][j]);
}
std::cout << std::endl; }
}
int main(int argc, char * agrv)
{
int i, j, m=2, n=3;
double **a;
a = new double*[m];
for (i=0; i<m; i++)
a[i] = new double[n];
out(a,m,n);
return 1;
}
总结
在传递数组参数时,首要推荐通过引用来传递参数,精准传入数组大小值,函数中参数定义为
vtype (&name)[size][size],引用时传入名字即可。
在通过指针传递时,需要另外传入参数来传递数组的大小。
参考:
https://blog.csdn.net/qq_30600259/article/details/101551220
https://blog.csdn.net/desilting/article/details/7983530