[关闭]
@lishuhuakai 2015-05-19T15:39:21.000000Z 字数 4351 阅读 1685

C++中的引用

c++


1. 变量名回顾

    + 变量名实质上是一段连续存储空间的别名,是一个标号
    + 程序中通过变量来申请并命名内存空间
    + 通过变量的名字可以使用存储空间

2. c++的引用

    引用是C++的概念,属于C++编译器对C的扩展。
  1. int main()
  2. {
  3. int a = 0;
  4. int &b = a; //int * const b = &a
  5. b = 11; //*b = 11;
  6. return 0;
  7. }
  8. /**
  9. 结论:请不要用C的语法考虑 b=11
  10. */
    引用概念:
    + 在C++中新增加了引用的概念
    + 引用可以看作一个已定义变量的别名
    + 引用的语法:Type& name = var;
    + 引用做函数参数呢?(引用作为函数参数声明时不进行初始化)
  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. int a = 10;
  6. int &b = a;
  7. //b是a的别名,请问c++编译器后面做了什么工作?
  8. b = 11;
  9. cout<<"b--->"<< a << endl;
  10. printf("a: %d\n", a);
  11. printf("b: %d\n", b);
  12. printf("&a: %d\n", &a);
  13. printf("&b: %d\n", &b); //对同一块内存空间可以取多个别名
  14. system("pause");
  15. return 0;
  16. }
  17. /**
  18. b--->11
  19. a: 11
  20. b: 11
  21. &a: 3798852
  22. &b: 3798852
  23. */
    结论:1. 普通引用在声明时必须用其它的变量进行初始化
    为什么必须初始化?   --> 结论:很像一个只读的常量

3. 引用的意义

    1. 引用作为其它变量的别名而存在,因此在一些场合可以代替指针
    2. 引用相对于指针来说具有更好的可读性和实用性
    比较下面实现同样功能的代码:
  1. #include <iostream>
  2. using namespace std;
  3. int swap(int &a, int &b)
  4. {
  5. int c = 0;
  6. c = a;
  7. a = b;
  8. b = c;
  9. }
  10. int swap(int *a, int *b)
  11. {
  12. int c = 0;
  13. c = *a;
  14. *a = *b;
  15. *b = c;
  16. }
  17. //引用和左值进行绑定时,将设计出高质量的程序
  18. //引用像一个常量,能起到指针的作用
  19. //引用和指针有关系吗?
  20. //引用有内存空间
  21. void main()
  22. {
  23. int a = 10;
  24. int a1 = 20;
  25. int &b = a; // type & 引用的名字 = 被引用的变量
  26. char buf[100];
  27. //在使用的时候,引用相当于变量的别名
  28. b = 11; // *b = 11;
  29. swap(a, b);
  30. swap(&a, &b);
  31. }

4. 关于引用

    普通引用有自己的内存空间吗?回答是肯定的:
  1. #include <iostream>
  2. using namespace std;
  3. struct Teacer {
  4. int &a;
  5. int &b;
  6. };
  7. int main()
  8. {
  9. printf("sizeof(Teacher) %d\n", sizeof(Teacer));
  10. system("pause");
  11. return 0;
  12. }
  13. /**
  14. 结果是:
  15. sizeof(Teacher) 8
  16. */
    我们可以得出结论:引用是一个有地址,引用是常量。
    引用的本质:
    + 引用在C++中的内部实现是一个常指针
        Type& name <--> Type* const name
    + C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同。
    + 从使用的角度,引用会让人误会其只是一个别名,没有自己的存储空间。这是C++为了实用性而做出的细节隐藏
    请仔细对比间接赋值成立的三个条件:
    1. 定义两个变量 (一个实参一个形参)
    2. 建立关联,实参取地址传给形参: p = &a;
    3. 利用*p形参去间接的修改实参的值

    结论:引用在实现上,只不过是把间接赋值成立的三个条件的后两步合二为一。当实参传给形参引用的时候,只不过是c++编译器帮我们程序员手工取了一个实参地址,传给了形参引用(常量指针)。

5. 引用的细节问题

    看下面的代码:
  1. #include <iostream>
  2. using namespace std;
  3. int myf01()
  4. {
  5. int a;
  6. a = 11;
  7. return a;
  8. }
  9. int& myf02()
  10. {
  11. int a;
  12. a = 11;
  13. return a;
  14. }
  15. int& myf03()
  16. {
  17. int b;
  18. b = 22;
  19. return b;
  20. }
  21. void main()
  22. {
  23. int b1 = myf01();
  24. printf("b1 = %d \n", b1);
  25. int b2 = myf02(); //返回一个值,然后赋给b2
  26. /**
  27. 还原现场是这样的:
  28. b2 = p(p是a的别名),因此实现了赋值操作,执行完了这一步myf02()里面的变量才被清理
  29. */
  30. printf("b2 = %d \n", b2);
  31. int &b3 = myf03(); //b3是返回时的引用
  32. /**
  33. b3只是一个引用相当于int * const b3
  34. b3并没有记录下a的值,只记住了地址,函数运行完成了之后myf03()里面的局部变量被清理了(也可能没有),有可能会出错
  35. */
  36. printf("b3 = %d \n", b3); //b3是一个引用,c++编译器会帮我们执行*p操作
  37. system("pause");
  38. }
  39. /**
  40. 结果如下:
  41. b1 = 11
  42. b2 = 11
  43. b3 = 22
  44. 其实b3大多时候应该是一个未知的数,不过本人的64位机器上运行正常,这是因为还没有清理内存的缘故。
  45. */
    如果函数里面的值生命周期比较长,结果会有所不同:
  1. #include <iostream>
  2. using namespace std;
  3. int& myj01()
  4. {
  5. static int a; //a分配在全局区,生命周期和程序一致
  6. a = 10;
  7. return a;
  8. }
  9. void main()
  10. {
  11. int b1 = myj01();
  12. printf("b1 = %d \n", b1);
  13. int b2 = myj01(); //返回一个值,然后赋给b2
  14. printf("b2 = %d \n", b2);
  15. int &b3 = myj01(); //b3是返回时的引用
  16. printf("b3 = %d \n", b3); //b3是一个引用,c++编译器会帮我们执行*p操作
  17. system("pause");
  18. }
  19. /**
  20. 结果如下:
  21. b1 = 10
  22. b2 = 10
  23. b3 = 10
  24. 原因也很简单,那就是函数myj01运行完后a并未被清理掉,因此不会出错。
  25. */
    当然,也可以这样:
  1. #include <iostream>
  2. using namespace std;
  3. int& myA(int &a)
  4. {
  5. a++;
  6. return a;
  7. }
  8. void main()
  9. {
  10. int b = 10;
  11. int b1 = myA(b);
  12. int b2 = myA(b);
  13. int b3 = myA(b);
  14. printf("b1 = %d \n", b1);
  15. printf("b2 = %d \n", b2);
  16. printf("b3 = %d \n", b3); //b3是一个引用,c++编译器会帮我们执行*p操作
  17. system("pause");
  18. }
  19. /**
  20. 结果如下:
  21. b1 = 11
  22. b2 = 12
  23. b3 = 13
  24. 解释和前面的例子是一样的。
  25. */
    我们需要特别注意下面的代码:
  1. #include <iostream>
  2. using namespace std;
  3. struct AdvTeacher
  4. {
  5. char name[32];
  6. int age;
  7. };
  8. void getTeacher01(AdvTeacher **p)
  9. {
  10. AdvTeacher *ptmp = (AdvTeacher *)malloc(sizeof(AdvTeacher));
  11. ptmp->age = 30;
  12. *p = ptmp;
  13. }
  14. //这个是结构体变量指针的引用,指针的引用
  15. //p是t2的别名
  16. void getTeacher02(AdvTeacher *&p) //和上面函数的作用一致
  17. {
  18. /**
  19. &p <--> * const p
  20. */
  21. p = (AdvTeacher *)malloc(sizeof(AdvTeacher));
  22. p->age = 30;
  23. }
  24. //如果不加引用,那么myT3会拷贝给p,修改p和myT3没有任何关系
  25. //加了引用,p是myT3的别名,修改p和修改myT3一样
  26. void getTeacher03(AdvTeacher &p) //和上面函数的作用一致
  27. {
  28. p.age = 11;
  29. }
  30. void main()
  31. {
  32. AdvTeacher *t1 = NULL;
  33. AdvTeacher *t2 = NULL;
  34. getTeacher01(&t1);
  35. getTeacher02(t2);
  36. {
  37. AdvTeacher myT3;
  38. myT3.age = 10;
  39. getTeacher03(myT3);
  40. }
  41. system("pause");
  42. }
    引用可以做左值:
  1. #include <iostream>
  2. using namespace std;
  3. //static int a = 10;将a变成一个状态变量
  4. //a初始化的时候为10,只会初始化一次
  5. int& myg()
  6. {
  7. static int a = 10; //分配在全局区
  8. printf("a = %d\n", a);
  9. return a;
  10. }
  11. void main()
  12. {
  13. myg() = 11; //引用当做左值
  14. myg();
  15. system("pause");
  16. }
  17. /**
  18. 结果如下:
  19. a = 10
  20. a = 11
  21. */
    常引用:
    + 常引用 Const int &e  相当于 const int * const e
    + 普通引用 int &e 相当于 int *const e
    有两种方法对常引用进行初始化:
    + 让变量初始化const引用如
    int a = 10;
    const int &b = a;
    + 使用字面量初始化const引用
    const int &c = 10;
    虽然这在普通引用里不行,但在常引用里面行得通。当使用常量(字面量)对const引用进行初始化时,C++编译器会为常量值分配空间,并将引用名作为这段空间的别名,使用常量对const引用初始化后将生成一个只读变量。
  1. #include <iostream>
  2. using namespace std;
  3. struct AdvTeacher
  4. {
  5. char name[32];
  6. int age;
  7. };
  8. //const &
  9. void getTeacher(const AdvTeacher &p) //和上面函数的作用一致
  10. { //只能读,不能写
  11. //p.age = 11; //常引用不能被修改
  12. printf("age = %d \n", p.age);
  13. }
  14. //1. const引用的作用,让变量所指向的内存空间只读
  15. //2. 给const引用初始化,有两种方法
  16. //让变量初始化const引用
  17. void main()
  18. {
  19. //int &a = 10; //10没法取地址,因此会出错
  20. int b = 10;
  21. const int &c = b;
  22. //c = 11; //不能通过c去间接修改b
  23. b = 12; //可以修改b的值
  24. //const引用的第二种初始化方法
  25. const int &d = 10; //对10另外分配一块内存空间
  26. printf("&d = %d\n", &d);
  27. system("pause");
  28. }
  29. /**
  30. 结果因人而异,不过肯定的是10分配了空间
  31. &d = 1506876
  32. */

6. c++引用需要注意的地方

    当函数返回值为引用时
    + 若返回栈内变量的引用,则不能用其成为其它引用的初始值,且不能将其作为左值使用。
    + 若返回静态变量或全局变量,可以成为其他引用的初始值,即可作为右值使用,也可作为左值使用。
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注