函数传参的原理和对象初始化的原理完全一致,用实参去初始化形参
函数传参的两种形式
1.按值传参
按值传参时,实参的拷贝用来初始化形参。
int main(int argc, char const *argv[])
{
int i=0;
paratest(i);
cout<<i<<endl;
return 0;
}
void paratest(int a)
{
++a;
cout<<__func__<<a<<endl;
}
对paratest函数的形参a做的所有操作都不会影响i的值,上述过程相当于如下过程
int a=i; ++a;
a和i是两个不同的对象,a的变化并不会导致i的变化
传指针参数也是按值拷贝的一种,只不过会改变指针指向的对象,不会改变指针本身
int main(int argc, char const *argv[])
{
int i=0;
int *pi=&i;
paratest(pi);
cout<<i<<endl;
return 0;
}
void paratest(int *pa)
{
*pa=10;
}
输出结果显示i的值为10,这是因为将i的指针的副本传入paratest,导致pa也指向i,所以可以通过pa改变i的值,上述过程相当于如下过程
int *pi=&i; int *pa=pi;
第二行代码导致pa和pi都指向i的地址,所以都可以操作i的数据,但是传指针参数实质还是按值传参
2.按引用传参
将函数的形参声明为引用,此时向函数中传参时,传递的是实参本身,而不是实参的拷贝,此时形参与实参绑定,形参只不过是实参的别名
int main(int argc, char const *argv[])
{
int i=0;
paratest(i);
cout<<i<<endl;
return 0;
}
void paratest(int &ra)
{
ra=10;
}
此时对引用形参操作,就是对实参操作
因为传递的是实参本身,并没有发生拷贝,省略了传值过程中的拷贝过程,所以对于大的对象来说,效率更高
而且有些对象不支持拷贝操作,所以传参时,必须使用引用形参
const与函数参数
当非引用或者指针的函数形参有const修饰时,传递const对象和非const对象都是OK的
int main(int argc, char const *argv[])
{
int i=0;
paratest(i);
const int ci=10;
paratest(ci);
return 0;
}
void paratest(const int a)
{
cout<<__func__<<a<<endl;
}
上述代码可以编译通过,但是不能在函数中修改a
函数const变量的指针或者引用的形参的初始化和const变量的指针或者引用的初始化规则完全一致,见知识点2,3
在声明一个函数时,尽量使用常量引用来声明形参
int main(int argc, char const *argv[])
{
string str="asd";
constreferencepara(str);
//constreferencepara("asd");
return 0;
}
void constreferencepara(string &str)
{
}
上述代码的第5行必须注释掉,否则会报错
因为一般的引用无法绑定字面值,但是const变量的引用可以,而且const变量的引用也可以绑定一般的变量,所以如果将函数constreferencepara的参数声明为const string &str,那么就不会报错
int main(int argc, char const *argv[])
{
string str="asd";
constreferencepara(str);
constreferencepara("asd");
return 0;
}
void constreferencepara(const string &str)
{
}
此时函数可以传入参数的范围变大,扩展性提高,而且传递引用不需要拷贝对象,效率提高,所以,建议使用const变量的引用作为函数形参
数组形参
当使用值传递的方式传递数组参数时,数组参数会转化为一个指针,指向数组首元素地址,下列三种函数声明都是等价的
void func(int *a);
void func(int a[]);
void func(int a[10]);
第三中声明方式只是期望传入的数组有10个元素,但是本质和前两种完全一样
当分别实现这三个函数时,编译器会报重定义的错误
void func(int *a)
{
cout<<"pointer func"<<endl;
}
void func(int a[])
{
cout<<"array func"<<endl;
}
void func(int a[10])
{
cout<<"array func that include 10 ele"<<endl;
}
由于向函数中传入数组时,函数接收到的只是数组首元素的指针,如果在函数中需要遍历数组,需要人为指定数组长度
int main(int argc, char const *argv[])
{
int a[10]={1,2,3,4,5};
int len=sizeof(a)/sizeof(a[0]);//计算数组长度
func(a,len);
return 0;
}
void func(int *a,int len)
{
for (int i=0;i<len;++i) {
cout<<a[i]<<endl;
}
}
这样做不如使用vector简洁,建议使用vector代替数组
传递数组的引用和指针
如果一个函数的形参是数组的引用,那么必须指定数组长度,因为引用要绑定某个具体的数组,不能绑定一个长度不确定的数组
void arrayreferpara(int (&arr)[]);
int main(int argc, char const *argv[])
{
int a[10]={1,2,3,4,5};
arrayreferpara(a);
return 0;
}
void arrayreferpara(int (&arr)[10])
{
for (int i=0;i<10;++i) {
cout<<arr[i]<<endl;
}
}
因为使用数组的引用作为形参时,传入的数组参数的个数必须和形参数组的个数一致,所以这种接口的可扩展性不强
函数的形参如果是数组的指针也是同理,数组的长度必须指定,因为数组的指针必须指向一个长度固定的数组
void arrayreferpara(int (*arr)[]);
通过数组的指针遍历数组
int main(int argc, char const *argv[])
{
int a[10]={1,2,3,4,5};
arrayreferpara(&a);
return 0;
}
void arrayreferpara(int (*arr)[10])
{
for (int *p=arr[0];p!=arr[0]+10;p++) {
cout<<*p<<endl;
}
}
因为形参是一个数组的指针,所以可以传入一个数组的数组,也就是二维数组,道理和指针形参可以传对应数组相同,下面三个的函数定义完全是一个意思
void arraypointerpara(int (*arr)[10])
{
}
void arraypointerpara(int arr[10][10])//第一个维度只是个期望值,可以填任意数
{
}
void arraypointerpara(int arr[][10])
{
}
所以会报重定义的错,道理同一维数组
这三种形式中,数组形参的第二个唯独必须确定,因为二维数组依然是一个一维数组,只不过每个元素都是一个一维数组,每个元素必须确定,所以每个元素的维度必须确定
当传入一个二维数组后,数组的指针指向二维数组的第一个一维数组
int main(int argc, char const *argv[])
{
int a[2][10]={1,2,3,4,5};
int dim=sizeof(a)/sizeof(int)/10;
arrayreferpara(a, dim);
return 0;
}
void arrayreferpara(int arr[][10], int length)
{
/*int num=sizeof(arr)/sizeof(int)/10;
cout<<num<<endl;*/
for (int (*ap)[10]=&arr[0];ap!=&arr[0]+length;ap++) {
for (int *p=ap[0];p!=ap[0]+10;++p) {
cout<<*p<<endl;
}
}
}
因为函数的数组形参接收的是数组名,是一个地址,所以不要在函数内对数组形参做sizeof,否则会出警告
提示sizeof将返回数组指针的大小而不是数组的大小
参考:
《C++ Primer》
欢迎大家评论交流,作者水平有限,如有错误,欢迎指出