白话C++中的引用(&)

本文目标

通过本文我将同各位看博客的朋友共同总结一下C++中的引用,从以下4个点进行总结:

  1. 简单了解引用
  2. 引用场景实例
  3. 对比指针和引用
  4. 引用实现原理

本文使用代码:github地址

1. 简单了解引用

引用的含义

简单来说,引用就是给变量取一个别名。
就像一个人可以有多种称呼,对于父母来说,应该称之为儿子,对于爷爷奶奶辈来说,应称之为孙子或者外孙,这很好理解。

引用的格式

类型& 引用变量名 = 已定义变量名

引用的特点

  1. 一个变量可以有多个别名(就好像一个人可以有多个称呼)
  2. 引用必须初始化(对比人的称呼,一个称呼当然要对应到人才有用)
  3. 引用只能在初始化时引用一次,之后不能改变所指对象(就好像你是你爷爷奶奶的孙子,不可能变成别人的孙子)
void test1()
{
    //我习惯于int和&放在一起,也有喜欢&和变量名放在一起的,这无伤大雅,都可以
    int a = 10;
    int* p = &a;
    int& b = a;

    cout << "a.value:" << a << endl;
    cout << "b.value:" << b << endl;

    cout << "a.address:" << &a << endl;
    cout << "b.address:" << &b << endl;

    a += 5;

    //输出一下a和b发现a改变,b也改变了
    cout << "a.value:" << a << endl;
    cout << "b.value:" << b << endl;
}

运行结果:
运行结果

2. 引用场景实例

const引用

const作为关键字和引用在一起表示这个变量不能被改变,只能读不能写。
如果原来的变量不是const的变量,相当于引用变量的权限缩小了,但是原变量的权限不受影响,这和const int*很类似

这里需要注意的便是引用时的权限变化(只能缩小权限不能扩大)引用常量时必须注意常量具有常属性(其实和前面一点一样)

额外了解一下double到int类型的引用,但是这个应该是没人去这么做的。

void test2()
{
    int d1 = 10;
    const int& d2 = d1;

    d1 += 5;
    //d2 += 5; 此句非法,b没有权限修改a

    const int d3 = 10;
    //int& d4 = d3; 非法,权限只能缩小不能放大

    const int& d5 = 5;//合法,因为常量也有空间,但是常量不能修改,所以必须有const

    double d6 = 1.5;
    //int& d7 = d6; 非法,由于在引用时类型不同,需要转换,d6转换为临时变量,具有常属性,不能引用
    const int& d8 = d6;//理由同d5一致
}

引用传参

普通传参

c语言中函数参数传参为非引用传值的方式,会生成局部临时变量接收实参的值。
说白了就是我传一个数组,函数执行的话在一开始就需要再弄一块空间把原数组拷贝过来,用完这个临时数组就不在了,改变的是临时数组,自然不会影响到原数组。
简单举个值传参的例子:

void swap(int left, int right)
{
    //这个函数很明显是不能完成交换数据的任务的
    //交换只是把临时变量给换了
    int temp = left;
    temp = left;
    left = right;
    right = temp;
}

引用传参

起个名再传吧,要交换的数据一个叫left一个叫right,反正是原数据的别名,改动会影响到原数据,就达到了交换的效果。

void swap(int& left, int& right)
{
    //虽然代码一样但是传参用的引用,能够交换两数
    int temp = left;
    temp = left;
    left = right;
    right = temp;
}

常引用传参

如果不想改变参数内部的值,那就用const

这里注意一下引用不能引用数组,但是可以引用vector模板

int getNumber(const vector<int> arr)
{
    return arr.back();
}

指针传参

void swap(int *left, int *right)
{
    int temp = *left;
    temp = *left;
    *left = *right;
    *right = temp;
}

引用返回值

错误的引用返回值

下面有一个例子,虽然他并不会报任何错误,但是我们要知道这种方式是错误的。

由于ret是在Add函数内部定义的,他的作用域在Add函数内,出了Add函数ret对应的空间就被释放,不再是属于ret了,他可以被分配给任何一个变量(这个和栈帧的创建与销毁有关,关于栈帧可以看我的另一篇博客),所以如果返回ret的引用,显然是错误的,如果这块空间被别的变量占有,那么这个新的变量更改值得话,将会导致不可预知的错误。

int& add(int a, int b)//引用返回值
{
    int ret = a + b;
    return ret;
}

int addNormal(int a, int b)//普通的值返回方式
{
    int ret = a + b;
    return ret;
}

错误的引用返回值方式汇编
错误返回方式
普通的值返回
普通值返回
从两张图中可以对比出来,普通的值返回返回的是一个值,而引用返回返回的是地址,也就是说这个地址如果返回,那么地址对应的变量一定需要存在那么返回才有意义,不然这个地址都不知道是谁的,返回意义何在

举个例子,好比每个班级里面都有一个班长,但是前任班长被撤职了,新班长都没选出来,叫班长是叫谁呢?

正确的返回值引用方式

只有当变量在出了作用域依旧存在时,可以使用引用返回值,例如具有static属性的变量以及传参时是引用传入的变量作为返回值返回。

int& getMaxiumNum(int& a, int& b)
{
    //返回a和b中的较大值,由于a和b是参数引用传入的,所以可以作为引用返回值返回
    return a > b ? a : b;
}

引用返回值小结

  1. 不要返回一个临时变量;
  2. 如果返回对象出了函数作用域仍存在,则最好用引用返回,因为效率更高

为啥效率更高,这里偷个懒,可以通过系统函数GetTickCount()来比较调用函数经过的时间,这里就不写了啊,有兴趣的朋友可以自己试试。

3. 对比指针和引用

引用和指针的区别与联系(重点,笔试常考)

  1. 引用在定义的时候就必须初始化,且初始化之后不能指向其他变量,
    指针可以在后续过程中指向别的变量,只要有需要随时可以变
  2. 引用必须指向有效变量,
    指针可以为空
  3. sizeof引用的变量,得到大小为变量的大小,
    sizeof指针变量,得到的的大小为指针的大小(32位下为4字节)
  4. 引用++如果为int是值++,
    指针++为跳过当前变量的大小,指向下一个相同类型变量的位置
  5. 相对而言,指针比引用更灵活,可以说是缺点也可以说是优点,
    反过来说就是引用比指针更安全

4. 引用实现原理

引用的实现我们可以通过汇编来看,执行test1()中的下面这段语句

    int a = 10;
    int* p = &a;
    int& b = a;

执行这段话时,查看汇编如下
指针和引用
可以看到引用和指针的语句在汇编实现的时候是一样的,也就是说引用的实现原理和指针一样,只不过他是用的语法不一样,引用的这个“指针”不能被改变指向的变量,只能用不能改位置。

引用按照我的理解,就类似于int* const p = &a,p的位置不能被改变,但是p指向的空间内的内容可以修改,只不过引用在使用上不需要加上*来解引用,语法上不同而已。

猜你喜欢

转载自blog.csdn.net/Boring_Wednesday/article/details/80620461