目录
前言
本篇文章主要讲述在C++中关于引用的一些概念及其用法,还有引用与指针的些许区别,希望能够帮助大家理解~
一.引用
1.1引用概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,这里我们可以理解为取外号,就比如宋江外号叫及时雨,它们二者都是指向同一个人——引用的变量也是与原变量共享一块内存空间。
类型& 引用变量名(对象名) = 引用实体;
#include <iostream> using namespace std; int main() { int a = 1; int b = a; int& ra = a; printf("%p\n", &a); printf("%p\n", &b); printf("%p\n", &ra); return 0; }
这里我们可以发现a与ra是共用一块内存空间的,而b则是另外开辟一处。
int main() { int a = 1; int b = a; int& ra = a; ra++; b--; cout << ra << endl; cout << a << endl; cout << b << endl; cout << " " << endl; a++; cout << ra << endl; cout << a << endl; cout << b << endl; return 0; }
从这里我们也可以看出ra++会影响到a的变化,而a++同样也会影响到ra。b只是a的拷贝,是完全独立的。
注意:引用类型必须和引用实体是同种类型的
1.2引用特性
- 引用在定义时必须初始化
就像你给别人取外号的前提是那个人得存在,不存在是没办法取外号的。
- 一个变量可以有多个引用
int main()
{
int a = 1;
int& b = a;
int& c = a;
printf("%p %p %p", &a, &b, &c);
return 0;
}
- 引用一旦引用一个实体,再不能引用其他实体。
这里我们可以理解为引用——称号,而每一个人可以有很多种称号,但每一个称号都是唯一的,只能对应一个人。就比如弼马温,斗战胜佛,齐天大圣等等这些称号都是指向同一个人的,具有单一性。
1.3使用场景
- 做参数
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
int main()
{
int a = 1;
int b = 2;
Swap(a, b);
cout << a << ' ' << b << endl;
return 0;
}
当我们的形参添加引用时Swap交换函数就发生了改变。没有引用时只是传值拷贝, 并不会真正使得外面的a与b交换。而当我们用引用来作为形参时,引用与原变量都是同一个对象,引用发生改变那么传递的变量也会发生改变。
- 做返回值
注意:以下用例(包含错误用例)只为引出一个结论:引用返回值不适用于在栈帧中开辟的变量。
这里的返回值并不是真正的n,它在出作用域就被销毁了,所以ret接收的只是n的拷贝。
int& Count() { int n = 0; return n; } int main() { int ret = Count(); cout << ret << endl; return 0; }
该处返回的是n的别名(假设为temp),那么ret接收的可就是一个不确定值的n,因为在出Count函数作用域后对应的栈帧会销毁,而为n所开辟的空间我们不清楚是否也会跟着销毁,直接访问它会有风险。
这是一个有趣的例子,我们再通过ret作为n别名的别名。这样就意味着ret也指向n同处一块空间了,所以会有打印两次ret而出现3,7的情况,虽然在其中包含了空间重复利用的特点,但这种现象还是不可取的。
就比如ret目前充当的是n的通灵师,二者在进行灵魂的沟通,可就在休息一会(离开函数作用域再重新调用进入)后发现n突然性情大变(数据发生变化),那之前得到的信息又是否准确呢?
所以对处于栈帧中的变量使用引用返回本身就是不靠谱的事,要么可能被销毁返回一个随机值,要么中途多次调用导致数据变化,这些都是不稳定因素。
int& Count(int a, int b) { static int c = a + b; return c; } int main() { int& ret = Count(1, 2); cout << ret << endl; //3 Count(3, 4); cout << ret << endl; //3 return 0; }
int& Count(int a, int b) { static int c; c = a + b; return c; } int main() { int& ret = Count(1, 2); cout << ret << endl; //3 Count(3, 4); cout << ret << endl; //7 return 0; }
这里用引用返回就不会出错,因为c是在静态区所开辟的变量而非栈帧。不过为何会出现不同的值呢?因为涉及到了一个小知识点:static只会初始化一次语句(用完不会再出现)。
因此我们要想使用引用返回那就得避开栈帧,而全局,堆,静态区等都是可以引用返回的。
- 引用返回的真正价值
通常情况下我们对一个顺序表进行修改是如下表示:这样修改是比较别扭的
而且针对每个位置的值进行++也很麻烦,但在C++中用了引用返回就很简单了~
而且没有引用的话那SLat(&s,3) = 10也无法执行,因为没有引用的话是传值返回,返回的是它的拷贝,而拷贝是临时对象具有常性,是没办法修改赋值的。
1.4常引用
- 权限不能放大
int main()
{
const int a = 10;
int& b = a;
return 0;
}
b无法引用由const所修饰的变量,就好比孙悟空与齐天大圣,都是同一只猴呀,凭啥前者要戴上紧箍咒(const)而后者七十二般变化潇洒自在(变量)。
int main()
{
const int a = 10;
const int& b = a;
return 0;
}
既然我们是同为一体的,那作为我的别名你也不能越界,你也要戴上紧箍咒,这就是权限平移。
- 权限可以缩小
int main()
{
int a = 10;
const int& b = a;
return 0;
}
毕竟你只是我的别名,最大上限就是我本身,那别名稍微限制点也情有可原。
另外常引用也经常出现在类型的隐式转换中。
int main()
{
int i = 0;
double& j = i;
return 0;
}
上述语句是错误的,之所以如此是因为任何类型的隐式转换都不会去改变自身类型,而是生成一个转换好类型的临时变量,而临时变量具有常性,需要在前面加上const加以修饰才可引用。
总之隐式转换要想引用的时候,const是必不可少的。
1.5 指针与引用的区别
- 从语法上引用不需要开辟空间,而指针需要
- 引用必须要有实体初始化,没有空引用,可以有空指针
- 从概念上引用是对象的别名,而指针是存储变量地址
- 从用途上引用不能任意修改指向的对象,而指针可以随意改变指向
- 从大小上用sizeof时,引用计算的是所属类型的大小,而指针则统一是4or8字节
- 从自增运算上引用的变量是在数值基础上自增1,而指针是跳过一个类型的大小
- 有多级指针,无多级引用
- 引用相对指针更安全点,使用时也不用像指针一样用*显示解引用