引用

引用的特性:

  1. 引用在定义时就要初始化

  2. 一个变量可以被多个引用

  3. 一个引用只能由一个实体变量,不能引用多个

  4. 引用不能降低实体的可执行权限,只能提升或者不变

    比如在引用常量的时候,常量只有可读权限,没有写权限,因此在定义引用的时候必须要用const来定义出常量引用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const int a = 10;
    int &b = a;//错误
    const int &c = a;//正确

    int d;
    int &e = d;//正确
    const int &f = d;//正确,不能通过引用f来修改d的值

    double g;
    int &h = g;//错误,类型不同
    const double &i = g;//正确

引用的两种常用方法:

  • 作为参数
  • 作为函数返回值
引用作为参数时属于输出型参数

现实编程中有一个函数要返回多个值很普遍

但是一个函数只有一个返回值(return),返回值其实只是用来表示函数的执行结果

通常,将引用作为参数时,在函数内的更改直接就影响到了引用的实体,实现了函数向外的输出,实现了”多返回值”

如果一个参数只是输入到函数,在内部的改变不会影响外部变量的改变的就是输入型参数;一个参数在函数内发生的改变影响函数外变量的改变的就是输出型参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void Add(int a, int b, int &c)
{
a += a;
b += b;
c = a + b;
}

int main()
{
int a, b;
a = 1;
b = 2;
int x = 0;
Add(x);
cout << a <<" "<< b << " "<< x << endl;
}

//结果是 1 2 6
引用作为函数返回值

此处应复习一下鹏哥讲的栈帧

传值返回

1
2
3
4
5
6
int A ( int b)
{
int n;
return n;
}
//A的返回值tem是n的拷贝,是一个临时变量
img
  1. main函数里调用A函数,将实参x拷贝给b,传入到A中

  2. n时局部变量,在A中,且n是返回值return。return作为函数结束标志,此时会产生一个临时变量tem作为n的拷贝,然后将tem传向main

    为何要产生一个临时变量,因为在离开函数A作用域之后n的内存会被释放,无法被访问,因此在此之前要生成临时变量。临时变量存放在在调用函数A之前就开辟好的一块空间里。

    如何证明确实返回值是一个临时变量

    临时变量具有常性

    所以如果main中使用一个引用来接收A的返回值,则会报错,如果用const 引用来接收,不会报错

    1
    2
    3
    4
    5
    6
    int main()
    {
    int x = 0;
    int &a = A(x); //报错
    const int &a = A(x); //不报错
    }

    说明A的返回值是一个具有常性

    在引用的时候权限不能放大,只能缩小或者不变,所以必须声明被a引用时的对象是个常量才可以

    为什么使用引用来接收

    引用只是取别名,a就是A的返回值n本身

    如果使用值来接收,int a = A(b),是将A的返回值拷贝赋值给a,不是同一个变量了

传引用返回

1
2
3
4
5
6
int &A (int b)
{
int n;
return n;
}
//A的返回值是tem,是n的引用
img

返回值类型为引用的时候则不是拷贝一个临时变量,返回的是A中局部变量n的引用tem,引用只是别名,不会占空间

1
2
3
4
5
int main()
{
int &a = A(x); //不会报错,因为A的返回值不是临时变量
}
//返回值tem是n的引用,a就也是n的引用

由于离开函数作用域之后局部变量内存会被释放,所以如果变量n是A内部一个局部变量,A的返回值tem是n的引用,则通过返回值访问n的时候就是非法访问

所以C++的规定是:

离开函数作用域之后,如果返回值的内存还没有被释放(比如返回值类型是静态变量全局变量之类),则可以使用引用返回,否则必须使用传值返回。否则会造成非法访问

总结:

传值返回:会有一个拷贝

传引用返回:没有拷贝,只是返回别名

因此传引用返回的效率要比传值返回高,因为不用拷贝返回值

引用和指针的区别:

  1. 语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。

    底层实现上实际是有空间的,因为引用是按照指针方式来实现的。

  2. 引用在定义时必须初始化,指针没有要求

  3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型 实体

  4. 没有NULL引用,但有NULL指针

  5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占 4个字节)

  6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

  7. 有多级指针,但是没有多级引用

  8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理

  9. 引用比指针使用起来相对更安全