C++函数形参为引用类型时,传入的参数类型为什么用引用对象的类型和引用类型都可以?

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
void refer1(int &);
void refer2(int *);

int main()
{
int a = 5;
int &b = a;
int *c = &a;
refer1(a);
refer1(b);
return 0;
}

这里的refer1的形参是一个int类型的引用,但是我传a(int类型)进去可以,我传b(int引用类型)进去也可以,对比refer2的形参,要求传int指针类型,也就是可以传c进去,如果我传a(c指向的类型)进去则报错。

按照常理说应该是类型匹配才对,int 匹配传进来的int,int *匹配传进来的int *,为什么形参是引用int &,既可以是int &又可以是int呢?
如果说形参是引用int &,传 int 进去是为了绑定到实参上,那传一个已经绑定的int &进去是绑定什么呢?

解答:

使用指针或者引用作为形参是为了解决按值传递可能导致的问题。

所以这里再次讲一下使用指针,引用和值作为形参所导致的结果。

C++教科书都会用一个交换两个变量的值的函数来举例:

1
2
3
4
5
6
7
8
9
void swap(int a, int b); //使用指针和引用的情况下形参类型分别为int*和int&
{
int temp;
//使用指针为形参的情况下需要将以下的a和b分别替换为*a和*b
temp = a;
a = b;
b = temp;
}

结果是怎么样的题主应该清楚:按值传递无法完成这一行为,而传递指针或者引用是可行的。

那么原因是什么?

在按值传递的情况下:

1
2
3
4
5
6
7
8
9
10
int x = 4, y = 5;
swap(x, y);
//...........................main函数其它部分..........................
void swap(int a, int b)
{
int temp;
temp = a;
a = b;
b = temp;
}

第一步:编译器会在内存开辟两个能存放int型变量的区域(假设分别为0xAAAAAAAA和0xBBBBBBBB),用于保存x和y的值。第二步:swap函数接收x和y的值,编译器会另外开辟两个存放int型变量的区域(假设分别为0xCCCCCCCC和0xDDDDDDDD),将4和5分别赋给形参a和b。第三步:swap函数完成交换,此时形参a=5, b=4,但是实参x和y的值并没有发生变化。因为swap函数只交换了0xCCCCCCCC和0xDDDDDDDD两块区域储存的值,并没有影响到0xAAAAAAAA和0xBBBBBBBB。所以x和y本身没有受到swap函数的影响,交换失败。


形参为指针的情况下:

1
2
3
4
5
6
7
8
9
10
11
12
int x = 4, y = 5;
int *px = &x, *py = &y;
swap(px, py);
//...........................main函数其它部分..........................
void swap(int* a, int* b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}

第一步:编译器会在内存开辟两个能存放int型变量的区域(假设分别为0xAAAAAAAA和0xBBBBBBBB),用于保存x和y的值。

第二步:编译器会在内存开辟两个能存放int型指针的区域(假设分别为0xCCCCCCCC和0xDDDDDDDD),两块区域分别存储x和y的地址(即0xAAAAAAAA和0xBBBBBBBB)

第三步:swap函数接收px和py的值,编译器会另外开辟两个存放int型指针的区域(假设分别为0xEEEEEEEE和0xFFFFFFFF),将0xAAAAAAAA和0xBBBBBBBB分别赋给形参a和b。

第四步:swap函数创造一个int型变量temp,假设地址为0xGGGGGGGG。

-—————————————————————————————————————————————–

第五步:

1
temp = *a;

*a的值就是x的值(即4),temp获得4这个值。

-—————————————————————————————————————————————–

第六步:

1
*a = *b;

形参a的值是0xAAAAAAAA,所以这一句将0xAAAAAAAA这一块内存所存储的值由4修改为5,而0xAAAAAAAA正是x的地址。也就是说x本身的值被改成了5。

-—————————————————————————————————————————————–

第七步:

1
*b = temp;

同理,形参b的值是0xBBBBBBBB,这一句将0xBBBBBBBB这一块内存所存储的值由5修改为4,而0xBBBBBBBB正是y的地址。也就是说y本身的值被改成了4。

swap函数执行完毕后x和y的值分别为5和4,交换成功。

在这种情况下,如果传入两个int型变量而不是int型指针,则编译不会通过。因为int型变量并不是地址,在a为int型变量的情况下,a并不是符号的合法用途。

-—————————————————————————————————————————————–

形参为引用的情况下:

1
2
3
4
5
6
7
8
9
10
int x = 4, y = 5;
swap(x, y);
//...........................main函数其它部分..........................
void swap(int& a, int& b)
{
int temp;
temp = a;
a = b;
b = temp;
}

第一步:编译器会在内存开辟两个能存放int型变量的区域(假设分别为0xAAAAAAAA和0xBBBBBBBB),用于保存x和y的值。

第二步:两个int型参数传入swap函数,函数将形参a和b分别声明为x和y的引用。此时a的地址和x一样是0xAAAAAAAA,b的地址和y一样是0xBBBBBBBB。

第三步:此时swap函数交换a和b的值,由于a和b的地址分别与x和y的地址相同(即0xAAAAAAAA和0xBBBBBBBB),该函数完成了对x和y的值的交换。交换后0xAAAAAAAA存储的值为5,0xBBBBBBBB存储的值为4。

当形参类型为引用时,实参和形参共享一个地址,对形参的修改也就是对实参的修改。

可以看到,使用指针和引用分别实现交换变量值的机制是不同的。尽管两种方法都直接对x和y的地址储存的值进行了修改,但是当形参是指针时,a和b的值并未发生变化(依然分别是x和y的地址);而当形参是引用时,a和b的值发生了变化。

-—————————————————————————————————————————————–

最后回到问题本身:

为什么形参是引用int &,既可以是int &又可以是int呢?

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;

int main()
{
int a = 1, b = 2; //声明并初始化两个int型变量
int *pa = &a, *pb = &b; //pa和pb分别为指向a和b的int型指针
int &ra = a, &rb = b; //ra和rb分别为指向a和b的int型引用

cout << "a*b=" << a*b <<endl; //合法,输出为2
cout << "pa*pb=" << pa*pb << endl; //非法,编译报错
cout << "ra*rb=" << ra*rb << endl; //合法,输出同样为2

return 0;
}

在这个例子中,rarb的结果和ab完全一致,尽管ra和rb是指向int型变量的引用,但是ra和rb在被声明为引用以后也可以被当作int型变量进行处理。而pa和pb是指向int型变量的指针,它们存储的是a和b的地址而不是a和b的值,所以对pa和pb进行int型变量的运算是非法的。

-—————————————————————————————————————————————–

如果说形参是引用int &,传int进去是为了绑定到实参上,那传一个已经绑定的int &进去是绑定什么呢?

修改一下上面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;

int main()
{
int a = 1, b = 2; //声明并初始化两个int型变量
int &ra = a, &rb = b; //ra和rb分别为指向a和b的int型引用
int &rra = ra, &rrb = rb; //rra和rrb分别为指向ra和rb的int型引用

cout << "a*b=" << a*b <<endl; //合法,输出为2
cout << "ra*rb=" << ra*rb << endl; //合法,输出同样为2
cout << "rra*rrb=" << rra*rrb << endl; //合法,输出依然为2

return 0;
}

在这个例子中,rra为指向【指向int型变量的引用】的引用,国内的C++教科书在讲到引用也会提一下指向引用的引用是合法的。在这种情况下,ra被引用是会被当作普通的int型变量处理。

当一个int&参数传入swap函数的时候,同样地,该int&参数会被当作一个int型变量,然后形参就是这个变量的引用。所以在这种情况下传入int或int&的输出都是一样的。