每次调用函数时,都会重建它的形参,并且传入的实参会对形参进行初始化。
一般来说,形参的类型决定了形参和实参的交互方式。如果形参是引用类型,它将绑定到对应的实参上;否则,将实参的值拷贝后赋给形参。由此我们会说,函数传参包括两种:引用传递和值传递。
值传递
值形参
初始化一个变量时,初始值被拷贝给变量,该变量的变化并不影响初始值的变化。如:
int i =1024; //定义i为1024
int j=i; //定义j为1024
j=2048; //修改j为2048,但是i的值依然是1024
在函数中定义
//交换函数,实参将i的值拷贝给a,j的值拷贝给b,所以i,j的拷贝值a,b 可以实现交换,但i,j本身并不交换
void swap(int a,int b)
{
int temp=0;
temp=a;
a=b;
b=temp;
}
int main()
{
int i=1024;
int j=2014;
swap(i,j); //不能实现交换,值传递不改变a,b 的值
return 0;
}
指针形参
当执行指针拷贝操作时,拷贝的是指针的值。拷贝后,两个指针是不同的指针。因为指针使我们可以间接地访问它所指的对象,所以通过指针可以修改它所指对象的值。如:
int n=0,i=42;
int *p=&n,*q=&i; //p指向n,q指向i
*p=42;//n 的值发生变化;p没有变化
p=q;//p现在指向i;但是i和n的值都不变
void reset(int *ip)
{
*ip=0; //改变ip所指对象的值
ip=0;只改变ip的局部指向,实参并未改变
}
所以传地址也是一种值拷贝。
引用传递
通过对复合类型之引用的学习,我们知道对于引用的操作实际上是作用在引用所引的对象上。
int n=0,i=42;
int &r=n; //r绑定n(即r是n的另一个名字)
r=42; //r的值为42
r=i; //r的值和i的值相同
i=r;//i的值和n相同
使用引用有一个好处就是避免传参时的拷贝,所以当某种类型不支持拷贝操作时,函数只能通过引用形参访问该类型的对象。
const 形参和实参
有时候在传参过程中,为了保证参数的值不发生变化,需要加const 关键字进行限定。同样,const 关键字可以加在基本类型上,复合类型上,同时也可以加在自定义类型上面。然而,针对各种不同的类型,const 所表现出来的行为也不同。
在《C++Primer》中,有看到关于const 的理解,其中以顶层const 和底层const 来讨论,感觉自己难以理解。所以自己就总结了一些基础知识。
基本类型
在函数传参中,基本类型使用值传递,则涉及到的就是值的拷贝,即就是基本类型的对象赋值操作。
如果利用一个对象去初始化另外一个对象,则他们是不是const都没有关系。
int i=42;
const int ci=i; //正确,i的值被拷贝给了ci
int j=ci; //正确,ci的值被拷贝给了j
ci 的常量特征仅仅在执行改变ci 的操作才会发挥作用。
复合类型(指针和引用)
指针:
一般来说,指针的类型和它所指向的对象需要严格匹配;但是在使用const 的过程中,可以令一个指向常量的指针指向一个非常量对象,具体示例如下:
const double *ptr=nullptr;
double dval=3.14;
ptr=&dval;//合法,但是不能通过ptr修改dval的值,但是dval的值还是可变的。
引用:
(1)不能将一个常量引用赋值给非常量引用,代码如下:
const int i=3; //常量对象
const int &r1=i; //引用合法
int &r2=i; //错误,i是常量引用,不能赋值给r2非常量引用
(2)在初始化常量引用时允许用任意表达式作为初始值,只要该表达式能转化成引用的类型即可。尤其是,允许一个常量引用绑定非常量的对象,字面值,甚至是一个表达式。
int i=42;
const int &r1=i; //一个常量引用绑定非常量的对象
const int &r2=42; //一个常量引用绑定字面值
const int &r3=r1*2; //一个常量引用绑定表达式
int &r4=r1*2; //r4是一个普通的非常量引用,两者类型不一致
所以针对const引用总结如下:允许一个常量引用绑定非常量对象;不允许一个非常量引用绑定常量对象;常量引用可以绑定多种类型。
数组传参
非const 数组
虽然在C++语言中,数组也是一种基本类型,但是由于其传参的特殊性,故将其取出来单独分析。
我们都知道,数组有两个性质:
(1)不允许拷贝数组;
(2)使用数组会将其转换成指针。
所以在函数传参的过程中不能简单的理解为数组值传递的方式进行。相反,由于数组会被转换成指针,所以当我们传递数组参数时,实际上传递的是指向数组首元素的指针。
和其他使用数组的代码一样,以数组作为形参的函数也必须确保使用数组时不会越界。
由于数组时以指针的形式传递给函数的,所以一开始子函数并不知道数组的尺寸大小。因此,调用者应该为此提供一些额外的信息。通常有三种常用的方法:
(1)使用标记确定数组的长度
数组中用一个特殊标记来表明数组的结束,比如C风格中的字符串
(2)使用标准库规范
在子函数中,传递指向数组首元素和尾后元素的指针(需要实践)
(3)直接传递一个表示数组大小的形参(比较常用)
通常在使用数组作为形参时,会传递一个size 表示数组的大小
const 和数组形参
一维数组
当子函数不需要对数组元素进行写操作的时候,数组形参可以指向const的指针。只有当函数确实要改变元素值的时候,才把形参定义成指向非常量的指针。如
//形参是数组的引用,维度是类型的一部分
void print(int (&arr)[10])
{
for(auto elem:arr)
{
cout<<elem<<endl;
}
}
多维数组
多维数组其实就是数组的数组。
传递多维数组的过程中,首选需要将数组转换成指针形式,再将指针形式转换成引用,传递参数即可。
int *matrix[10] //10个指针构成的数组
int (*matrix)[10] //指向含有10个整数的数组的指针