对C++引用的理解
“ 在C++中,引用符号与取地址符号都是&,我们知道&用于取地址时,相当于取标识符的值的存储地址,那么如果该符号用于引用呢?”
开门见山:C++中,如果标识符a是标识符b的引用,那么对a进行任何操作,都等效于对b进行操作。比如:
int b = 10;
int &a = b; //a是b的引用
a = a + 1; //等效于b=b+1,a与b的值都变成11
也可以这么比喻理解:有一个人的原名叫b,有一天他被别人起了个外号叫a,从此以后他有a、b两个称呼。不管a身上发生什么事情,都相当于b身上发生同样的事情,因为a和b是同一个人。
01
—
引用与指针
我们知道,指针的值是某一个存储地址,经常通过指针来操作其指向地址所存储的数据:
float a = 20.0; //定义标识符a,并将a的值初始化为20.0
float *p = &a; //定义指针p,并将指针p指向a的地址
*p = (*p) + 1; //通过*p来改变a的值,等效于a = a + 1
而引用就是一个标识符的别名,对引用进行任何操作等效于对该标识符进行操作。我们也可以这么比喻理解:
比如某个城市叫“柳州市”,但是又因为该市的螺蛳粉在全国很出名,人们又称之为“螺蛳粉之乡”,“螺蛳粉之乡”是“柳州市”的别名,也相当于“柳州市”的“引用”,而“柳州市”的地址为“中华人民共和国广西壮族自治区柳州市”,沿着该地址可以到达该市,因此“中华人民共和国广西壮族自治区柳州市”相当于“柳州市”的“指针”。
那么,引用与指针的主要区别在哪里呢?请看下方:
1. 在创建引用的同时必须将其初始化,而指针可以在任何时候被初始化(const指针除外):
float a = 20.0;
float &b = a; //创建引用b的同时将b初始化,也即将b绑定到a,使其成为a的别名
float *p; //创建指针p的时候,不是必须同时将其初始化
p = &a; //可以在任何时刻将指针p初始化
//特殊情况,使用const修饰的指针也必须在定义的时候初始化
float *const p1 = &a; //初始化指针p1指向a的地址之后,p1将不能改为指向其它地址
float x = 2.0;
p1 = &x; //无效操作
2. 引用不能为NULL,其必须与某个标识符关联,然而指针却可以为NULL:
float &b = NULL; //错误用法
float *p = NULL; //把指针指向的地址设置为NULL,这是允许且很常见的做法
3. 引用被初始化之后就不能再更改引用关系,然而指针却可以在任何时候被更改为指向其它地址(const指针除外):
float a = 20.0;
float &b = a; //创建引用b的同时将b初始化,也即将b绑定到a,使其成为a的别名
float c = 5.0;
b = c; //无效操作,引用b已经与a关联,不能再关联到其它标识符
float *p; //创建指针p的时候,不是必须同时将其初始化
p = &a;
p = &c; //指针p可以任何时候更改为指向别的地址
//特殊情况,使用const修饰的指针也必须在定义的时候初始化,且初始化之后其指向的地址将不能更改
float *const p1 = &a; //初始化指针p1指向a的地址之后,p1将不能改为指向其它地址
float x = 2.0;
p1 = &x; //无效操作
03
—
引用的用途
C++中,引用最常见的用途为向函数传递输入参数,除了引用传递参数之外,常见的传递参数方式还有值传递、指针传递,下面我们分别举例来说明对比。
1. 值传递
使用值传递的方式,在函数内部不能改变输入参数的值,比如以下代码:
void func(int a)
{
a = a * 5;
}
int main(void)
{
int a = 6;
func(a);
printf("a=%d\n", a); //这里打印出来a的值还是6,因为a以值传递的方式传入函数,函数内部并不能改变a的值
return 0;
}
2. 指针传递
使用指针传递的方式,在函数内部可以改变输入参数的值,比如以下代码:
void func(int *a)
{
(*a) = (*a) * 5;
}
int main(void)
{
int a = 6;
int *p = &a; //定义指针p,并将其初始化为指向a的地址
func(p); //将指针p传入函数
printf("a=%d\n", a); //这里打印出来a的值变成30,因为a以指针传递的方式传入函数,函数内部可以改变a的值
return 0;
}
3. 引用传递
使用引用传递的方式,在函数内部也可以改变输入参数的值,比如以下代码:
void func(int &b)
{
b = b * 5;
}
int main(void)
{
int a = 6;
func(a); //将a的引用传入函数,也即引用b为函数的输入参数,将引用b与a关联,然后对b进行操作等效于对a操作
printf("a=%d\n", a); //这里打印出来a的值变成30,因为a以引用传递的方式传入函数,函数内部可以改变a的值
return 0;
}
当需要在函数内部改变输入参数的值时,指针传递和引用传递都是可以做到的。那么为什么C++相对于C还增加了引用传递的方式呢:
1. 引用传递,在函数内部可以直接对传入的变量名进行操作,而不需要考虑是否在变量名前加“*”号。而且访问结构体/类的成员时是使用“.”就好,不需要考虑“->”,减小出错的概率。比如以下代码:
typedef struct A
{
int a;
float b;
}A_obj;
//指针传递
void func(int *a)
{
(*a) = (*a) + 5; //需要在a前面加*号,这个*号也恰恰是容易忘记的地方
}
//引用传递
void func1(int &a)
{
a = a + 5; //直接对a操作即可,不需要在a前面加*号
}
//指针传递
void func2(A_obj *AA)
{
AA->a = AA->a + 5; //使用->访问成员a
AA->b = AA->b * 5.0; //使用->访问成员b
}
//引用传递
void func2(A_obj &AA)
{
AA.a = AA.a + 5; //使用.访问成员a
AA.b = AA.b * 5.0; //使用.访问成员b
}
2. 使用指针可以很自由地操作内存,其操作权限是非常大的,然而危险度、易错度也因权限的强大而增大。所以如果我们使用标识符的别名(引用)就可以完成我们想要的功能,那么就没必要犯更大的风险去使用指针了。
欢迎扫码关注本微信公众号,接下来会不定时更新更加精彩的内容,敬请期待~