7_引用类型

7 引用类型

引用类型是 C++ 拓展出来的一个新的类型

1_引用就是变量的 "别名"

引用类型就是给变量起个别名, 起个另外的名字. 你操作变量的别名, 就相当于操作变量本身.

引用类型是必须要赋初始值的, 不能先 int& ref; 然后再 ref = x;, 编译器报错 : references must be initialized

  1. 基本类型

    int x = 1;
    int& p = x; //起个别名叫 p
    p = 2;      // p 就是 x
    printf("%d\n",x);
  2. Person p;
    Person& px = p; // 起个别名叫作 px
    px.x = 10;      // px 就是 p
    printf("%d\n",p.x);
  3. 指针

    int******** i = (int********)1;
    int********& r = i; // 起个别名叫 r
    r = (int********)2; // r 就是 i
    printf("%d\n",r);
  4. 数组

    int arr[] = {1, 2, 3};
    int (&p)[3] = arr;      // 现在 p 是数组 arr 的别名
    p[0] = 4;
    printf("%d\n",arr[0]);

2_引用类型的本质

int main(int argc, char* argv[])
{
    int x = 10;
    int& ref = x;
    printf("%d\n",x);
    return 0;
}

ref 存储的就是变量 x 的地址

int main(int argc, char*argv[])
{
    int x = 10;
    int* ref = &x;
    printf("%d\n",x);
    return 0;
}

我们把它改成指针, 观察反汇编发现反汇编代码和刚才一模一样.


我们现在看一下类类型

Base b;
b.x = 1;
Base& ref = b;
ref.x = 2;
printf("%d\n",b.x);

现在得出结论, 引用生成的汇编代码, 和指针的汇编代码一模一样

我们看看引用与指针的区别

3_引用类型与指针的区别

int x = 1;

// 必须初始化
int* p = &x;
int& ref = x;

// 运算
p++;
ref++;

// 赋值
p = (int*)1;
ref = 100;

当我们对指针做运算的时候, 实际上是对指针本身做运算; 实际上, 你对引用做运算, 引用本身说明不能任何问题.

p++, p 这个指针本身加了 1,int 是 4 个字节, 所以这个指针指向的地址往后面移动了 4 个字节

ref++, 这只是一个别名, x++

p = (int*)1; 这么赋值之后, p 的值就变成 1 了, 就这么简单

那么 ref 重新赋值 ref = 100;, ref 本身存的是 x 的地址, 这个永远不变, ref = 100; 改的是 x 的值


现在我们通过类类型进一步了解引用的特点

class Base
{
    public:
        int x;
        int y;
        Base(int x, int y)
        {
            this->x = x;
            this->y = y;
        }
};

int main(int argc, char* argv[])
{
    Base b(1,2);

    // 初始化
    Base* p = &b;
    Base& ref = b;

    // 运算
    p++;
    //ref++; 编译不过去
    return 0;
}

初始化的时候二者汇编代码没啥区别

运算的时候, ref++ 编译不过去了, 你直接在这写 ref++ 相当于就是在这里写 b++, b 是一个对象, 它这个 ++ 不能直接用啊, 所以编译不过去.

现在来看, 引用类型, 除了赋值的时候它看着像指针, 其它时候, 它代表的就是那个变量.

你代表的是 a, 那你就能做 a 所可以做的事情, a 能 ++, 你这个引用就可以 ++

总结

  1. 引用必须赋初始值, 且只能指向一个变量, "从一而终"

  2. 对引用赋值, 是对其指向的变量赋值, 而并不是修改引用本身的值

  3. 对引用做运算, 就是对其代表的变量做运算, 而不是对引用本身做运算

  4. 引用类型就是一个 "弱化了的指针"

为什么会出现引用这么个东西呢, 因为如果你驾驭不住指针的话, 程序就容易出错

4_引用在函数参数传递中的作用 (基本类型)

void Plus(int& i)
{
    i++;
    return;
}

int main(int argc, char* argv[])
{
    int i = 10;
    Plus(i);
    printf("%d\n",i);
    return 0;
}

我们这里参数是引用类型, 按照我们以前基本类型的方式, 这里传的应该是 10 这个值进去, 但这里是引用类型, 我们来看一下它到底干了什么

观察反汇编代码, 发现这里穿进去的是变量的地址, 传递的时候像指针. 然后 i++; 操作, 这个操作并不是对指针本身进行操作, 而是对它所代表的那个变量进行 ++ 操作, 就是把这个变量地址取出来, 然后把地址存的那个值 10 取出来加 1 然后存回去.

5_引用在函数参数传递中的作用 (构造类型)

struct Base
{
    int x;
    int y;
    Base(int x, int y)
    {
        this->x = x;
        this->y = y;
    }
}

void PrintByRef(Base& refb, Base* pb)
{
    // 通过指针读取
    printf("%d %d\n",refb.x, refb.y;

    // 通过引用读取
    printf("%d %d\n", refb.x, refb.y);

    // 指针可以重新赋值, 可以做运算
    // refb = (Base&)1;
    // refb++;
}

int main(int argc, char* argv[])
{
    Base b(1,2);

    PrintByRef(b, &b);

    return 0;
}

参数传递, 都是取地址, 看不出任何差异

通过指针读取, 很好理解

通过引用读取, 没有任何区别

所以使用的时候, 我们没有看到任何差异

6给狗起个人的名字 ?

引用是变量的别名, 比如 :

int x = 10;

int& r = x; // int 类型的别名就应该是 int&

Base b(1, 2);

Base& r = b;    // Base 类型的别名就应该是 Base&

Base& r = (Base&)x; // 虽然可以编译, 但是意义不大

给狗起个人的名字 ? 就是说我使用引用的时候, 我不按照它的规则去给它起个别名, 允许还是不允许 ?

比如上面代码, 我们给 int 类型起一个 Base 类型的别名. 编译器允许, 但是没啥意义.

我们在使用引用的时候, 人家原来是什么类型, 你就起一个什么类型的别名.

7_常引用

class Base
{
    public:
        int x;
};

void Print(const Base& ref) // 常引用
{
    // ref = 100;   // 不能修改
    // ref.x = 200; // 但能修改里面的内容
    printf("%d\n", ref.x);
}

int main(int argc, char* argv[])
{
    Base b;
    b.x = 100;
    Print(b);
    return 0;
}

如果对象很大的话, 我们不能直接传对象进去, 虽然对象可以作参数来传递, 但是内存效率太低, 不推荐.

常引用就是当我们希望对某一个对象, 进行只读操作的时候, 通过 const 关键词加上引用来达到我的目的.