• C++引用的知识补充


    接上文《C++引用的基本用法及拓展用法》

    (25条消息) C++引用的基本用法及拓展用法_洛里斯不学习的博客-CSDN博客

    若对全局变量,局部变量,栈区有不了解的地方,请回看此文:

    《C++面向对象核心编程必备知识点之----堆区》

    (25条消息) C++面向对象核心编程必备知识点之----堆区_洛里斯不学习的博客-CSDN博客

    目录

    一、引用的注意事项

    1.引用必须初始化

    2.一旦引用,不可二次修改

    二、引用做函数参数

    1.(回顾)值传递

    ​编辑

    2.(回顾)指针传递

    3.引用传递(引用做函数参数)

    4.总结

     三、引用做函数返回值

    1.不能返回局部变量

    2.函数可以作为左值

    四、引用的本质

    五、常量引用

    1.在函数参数列表中用const修饰形参可以防止形参改变实参

    2.const引用(常量引用)可以指向常量,而普通引用不可以指向常量


    一、引用的注意事项

    1.引用必须初始化

    怎么理解呢?

    f在作为引用的时候,并没有明确地告知编译器它是谁的引用(也就是原名来自于哪个变量),所以f所在语句报错

    可以在VS里面输出验证一下

    引用a的b可以正常输出,而f不可以,因为其没有初始化(指向)

    不修正&f的错误,是无法输出的

    1. #include
    2. using namespace std;
    3. int main()
    4. {
    5. int a = 10;
    6. int &b = a;
    7. int e = 20;
    8. int &f;
    9. cout << "&b=" << b << endl;
    10. cout << "a=" << a << endl;
    11. cout << "e=" << e << endl;
    12. cout << "&f=" << f << endl;
    13. system("pause");
    14. return 0;
    15. }

    2.一旦引用,不可二次修改

    这么来说吧,小红是李红的别名(引用),小明是马明的别名(引用),如果愣是要把小红当做马明的别名,人家铁定不乐意啊,或者是让小红作为另外一个(第三个人的别名)也肯定是不行的

    用代码来解释:

    1. #include
    2. using namespace std;
    3. int main()
    4. {
    5. int a = 10;
    6. int &b = a;
    7. int c = 30;
    8. b = c;//注意这不是更改引用,是赋值
    9. system("pause");
    10. return 0;
    11. }

    再次强调,我们把b作为了a的引用,后面想把b改作为c的引用,b已经引用并且初始化指向了a,那么是不可以做二次更改了

    应该会有人想,那么int &b=c?这样可以么

    看编译器怎么说

    1. #include
    2. using namespace std;
    3. int main()
    4. {
    5. int a = 10;
    6. int &b = a;
    7. int c = 30;
    8. int &b = c;
    9. b = c;//注意这不是更改引用,是赋值
    10. cout << "a=" << a << endl;
    11. cout << "&b=" << b << endl;
    12. cout << "c=" << c << endl;
    13. system("pause");
    14. return 0;
    15. }

    b重定义了,相当于有了两个一模一样的引用,只不过指向不同,一个指向a,一个指向c

    第二个错误是cout<<"&b"<

    二、引用做函数参数

    1.(回顾)值传递

    定义一个swap01的函数,交换两个数的值,不用指针*也不用引用&定义一个a和一个b作为其形参

    1. #include
    2. using namespace std;
    3. void swap01(int a, int b)
    4. {
    5. int temp = a;
    6. a = b;
    7. b = temp;
    8. }
    9. int main()
    10. {
    11. int x = 10;
    12. int y = 20;
    13. swap01(x, y);
    14. cout << "x="<
    15. cout << "y=" << y << endl;
    16. system("pause");
    17. return 0;
    18. }

    在main的实参中调用完函数,实参并不会发生变换,x=10,y=10未发生交换

    形参和实参的概念在之前函数的相关文章里面提过

     如果在函数内输出的话,swap01里的形参可执行交换

    1. #include
    2. using namespace std;
    3. void swap01(int a, int b)
    4. {
    5. int temp = a;
    6. a = b;
    7. b = temp;
    8. cout << "swap01 a=" << a << endl;
    9. cout << "swap02 b=" << b << endl;
    10. }
    11. int main()
    12. {
    13. int x = 10;
    14. int y = 20;
    15. swap01(x, y);
    16. cout << "x="<
    17. cout << "y=" << y << endl;
    18. system("pause");
    19. return 0;
    20. }

    2.(回顾)指针传递

    函数swap02的形参用指针的形式(接收main中传入的x,y的地址)

    1. void swap02(int *a, int *b)
    2. {
    3. int temp = *a;
    4. *a = *b;
    5. *b = temp;
    6. }

    main函数中的实参也用地址来作为函数swap02的参数传入,执行swap02的函数体语句

    1. int main()
    2. {
    3. int x = 10;
    4. int y = 20;
    5. //swap01(x, y);
    6. swap02(&x, &y);
    7. cout << "x="<
    8. cout << "y=" << y << endl;
    9. system("pause");
    10. return 0;
    11. }

    输出结果显示,main中的实参也发生了变化

    3.引用传递(引用做函数参数)

    swap03函数的形参用int &a,int &b,两个引用作为参数(两个引用可以视作main函数中两个x,y的别名)

    1. void swap03(int &a, int &b)
    2. {
    3. int temp = a;
    4. a = b;
    5. b = temp;
    6. }

    main中的原名x,y作为参数传入swap03函数中

    1. int main()
    2. {
    3. int x = 10;
    4. int y = 20;
    5. //swap01(x, y);
    6. //swap02(&x, &y);
    7. swap03(x, y);
    8. cout << "x="<
    9. cout << "y=" << y << endl;
    10. system("pause");
    11. return 0;
    12. }

    输出结果表明,实参也发生了变化(数值交换)

    4.总结

    能让实参跟形参一起发生变化的方式就是指针传递和引用传递(即把指针或引用作为函数形参)而值传递不能改变实参,只对形参起作用

     三、引用做函数返回值

    1.不能返回局部变量

    局部变量存放于内存四区中的栈区,在代码执行完后就会被编译器自动释放,第一次输出之所以可以正常输出是因为编译器给我们做了一次保留,第二次输出就会出现乱码,因为在程序执行完之后,局部变量就已经被释放了,我们再对它进行操作就是非法的

    1. #include
    2. using namespace std;
    3. int & test01()
    4. {
    5. int a = 10;
    6. return a;
    7. }
    8. int main()
    9. {
    10. int & ref = test01();
    11. cout << "ref=" << ref << endl;//第一次可以输出a=10
    12. cout << "ref=" << ref << endl;//第二次输出是乱码
    13. system("pause");
    14. return 0;
    15. }

    那么test01里面的局部变量a改成全局变量呢?
    (2)全局区用来存放全局变量、静态变量、常量、字符串常量、counst修饰的全局变量(全局常量)
    (3)栈区用来存放局部变量,函数参数等,栈区数据的注意事项---不要返回局部变量的地址,栈区的数据由编译器管理和释放

    所以把int a=10变成static int a=10;(静态变量)或者const int a=10(常量)就可以改变它在栈区只有短暂存活周期的悲惨命运,可以一直生存下去,也就可以一直输出啦

    1. #include
    2. using namespace std;
    3. int & test01()
    4. {
    5. static int a = 10;//静态变量全局区,不在栈区(栈区内容生命短)
    6. return a;
    7. }
    8. int main()
    9. {
    10. int & ref = test01();
    11. cout << "ref=" << ref << endl;//第一次可以输出a=10
    12. cout << "ref=" << ref << endl;//正常
    13. cout << "ref=" << ref << endl;//正常
    14. cout << "ref=" << ref << endl;//正常
    15. system("pause");
    16. return 0;
    17. }

    2.函数可以作为左值

    左值就是赋值运算符 = 左边的那部分

    沿用1里面把局部变量变成静态变量存放到全局区的例子

    把函数作为左值,并且在右侧给它赋值1000,观察输出结果

    1. #include
    2. using namespace std;
    3. int & test01()
    4. {
    5. static int a = 10;//静态变量全局区,不在栈区(栈区内容生命短)
    6. return a;
    7. }
    8. int main()
    9. {
    10. test01() = 1000;//把test01函数作为左值,并赋值1000
    11. cout << "test01=" << test01() << endl;//第一次输出结果是1000
    12. cout << "test01=" << test01() << endl;//第二次输出结果是1000
    13. cout << "test01=" << test01() << endl;//第三次输出结果是1000
    14. system("pause");
    15. return 0;
    16. }

    四、引用的本质

    引用的本质在C++中内部实现是一个指针常量

     对于指针常量而言,指针的指向是不可以修改的,指针指向的值可以修改

    也就是说:

    int a=10;

    int & ref=a;//编译器会把这句话转为指针常量int *const ref=&a;常量指针ref指向a的地址

    我们如果修改引用ref的值,则编译器内部发生的是这种变化

    ref=20;

    //编译器内部解释这段话为 *ref=20  把常量指针的值更改为20(解指针后赋值)

    接下来我们在VS2017里敲一遍

    1. #include
    2. using namespace std;
    3. int main()
    4. {
    5. //引用
    6. int a = 10;
    7. int &ref = a;//int * const ref=&a
    8. ref = 20;//*ref=20;
    9. cout << "ref=" << ref << endl;//指针的指向是a,指针的值发生了改变
    10. cout << "a=" << a << endl;//指针的指向的a也发生了改变
    11. //指针常量,指向不可以改,值可以改
    12. int b = 300;
    13. int * c = &b;
    14. *c = 100;
    15. cout << "*c=" << c << endl;
    16. cout << "b=" << b << endl;
    17. system("pause");
    18. return 0;
    19. }

    指针常量是int const*ref=a;//指向不可以改,值可以改

    常量指针是const int * ref=a;//指向可以改,值不可以改

    性质来比,引用和指针常量其实是一样的

    引用和指针的区别:

    引用和指针的区别在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间

    五、常量引用

    顾名思义一看到常量,我们就会想到const,用const来修饰引用

    int a = 10;
    //开辟堆区之new的用法复习
    int b = new int(20);//new开辟堆区,但是堆区存放的是开辟出的空间的地址,所以要用指针来接收地址
    int *b1 = new int(20);//此时正确,用指针来接收new出来的地址
    int new b2= 20;//此时没有输出标识符new int(),而且结果要用指针int * b2来接收

    1.在函数参数列表中用const修饰形参可以防止形参改变实参

    先来看下不加const的普通引用作为函数参数,会对实参有什么影响(单纯的引用传递)

    1. #include
    2. using namespace std;
    3. void showValue(int & val)
    4. {
    5. val = 100;
    6. cout << "val=" << val;
    7. }
    8. int main()
    9. {
    10. int a = 2;
    11. showValue(a);//此时的输出结果,main中的实参a也跟随形参一起被修改成了100,不再是2
    12. system("pause");
    13. return 0;
    14. }

    实参a也跟随形参val一起改变了,当我们在函数中操纵别名val对原名a进行了相同的改动

    cout<<"main中的实参a="<

     那么如果我不想要实参a跟随形参val一起改变呢?

    有的情况是我们只需要打印main中的实参,而不希望它发生任何的改动,就可以使用常量引用,保证我们main中的数据的真实性和安全性

    那么在int &val前加const把它变成常量引用即可

    当然,此时在函数体内也不可以对val进行任何赋值操作,它已变成了常量不可修改的左值

     在函数体内是不可以修改这个数据了

    那么我们在函数参数是常量引用的情况下就可以打印我们的真实的形参数据了

    1. #include
    2. using namespace std;
    3. void showValue(const int &val)
    4. {
    5. //val=100;
    6. cout << "val=" << val << endl;
    7. }
    8. int main()
    9. {
    10. int a = 2;
    11. showValue(a);//此时打印的是实参a的值2
    12. cout << "a=" << a << endl;//实参没有被修改,还是2
    13. system("pause");
    14. return 0;
    15. }

    2.const引用(常量引用)可以指向常量,而普通引用不可以指向常量

    1. int main()
    2. {
    3. int a = 10;
    4. //开辟堆区之new的用法复习
    5. int b = new int(20);//new开辟堆区,但是堆区存放的是开辟出的空间的地址,所以要用指针来接收地址
    6. int *b1 = new int(20);//此时正确,用指针来接收new出来的地址
    7. int new b2= 20;//此时没有输出标识符new int(),而且结果要用指针int * b2来接收
    8. // const 引用可以指向常量而普通引用不能
    9. const int cs = 100;//常量
    10. int &rcs = cs;//普通引用不可以指向常量
    11. //用const修饰引用则不会出错
    12. const int &rcs01 = cs;//此时不会报错
    13. int &ref = 10;//只能引用栈区,堆区全局区的内容,有指向,而不是只是字面的(无指向)的10
    14. const int&ref01 = 10;//不会报错
    15. int &ref02 = a;
    16. int &ref03 = b;//正确
    17. int &ref02 = a;
    18. int &ref03 = b;//正确
    19. system("pause");
    20. return 0;
    21. }

    const的魅力咋这么大?把特立独行的字面数字10都能收服

    其实

    const int cs=100;

    int &rcs01 = cs;//错误

    const int &rcs01 = cs;//正确,此时的引用所引用的是一块临时空间

    //编译器给我们做了一个修改,int temp=cs;(const)int &trecs01=temp;//加上const也是合法的

    //创建了一个临时变量来接收常量cs,再引用临时变量temp

    int a=10;

    int &ref=10;//错误

    const int &ref01=10;//正确

    //编译器同样执行了以下操作,int temp=10;(const)int &ref01=temp;

    那既然可以用const引用常量了,那么可以修改常量指针的值么?

    答案是不可以的,加上const限制了ref01是只读状态,是不可以修改的

    提示的错误是表达式必须是可修改的左值(左值test01被我们设置成了不可修改的常量)

    1. int a = 10;
    2. int &ref = 10;//只能引用栈区,堆区全局区的内容,有指向,而不是只是字面的(无指向)的10
    3. const int&ref01 = 10;//不会报错
    4. ref01 = 300;//报错,常量引用的值不可以修改,修改的是值不是改变引用(前面提到过的)

    引用的补充呢总算是学完了,下一次就要来个高级点的函数了 

  • 相关阅读:
    10月26日,起立&LG新品首发第五代OLED透明显示屏
    每日一题:leetcode 2594 修车的最少时间
    设备发现:通向全面网络可见性的途径
    驱动程序开发:LCD屏显示驱动
    SQL的事务隔离属性
    pandas read_json时ValueError: Expected object or value的解决方案
    Spring的Factories机制介绍
    TensorRT
    学数学,要“直觉”还是要“严谨”?
    论文阅读 | RAFT: Recurrent All-Pairs Field Transforms for Optical Flow
  • 原文地址:https://blog.csdn.net/qq_58619891/article/details/127811206