设为首页收藏本站
查看: 280|回复: 3

7.《剑指C++》之从C到C++(7)

[复制链接]
  • TA的每日心情
    擦汗
    昨天 08:01
  • 签到天数: 760 天

    [LV.10]以坛为家III

    发表于 2020-6-12 19:26:59 | 显示全部楼层 |阅读模式
    本帖最后由 御天行 于 2020-6-13 12:03 编辑

    标准开头:
    ======《剑指C++系列》:总目录======

    本讲主要介绍:C++引用(下)--深入探究篇
    5.常引用
    5.1引入
    在C++中,用关键字const修饰的变量,称为常变量。其特点是:有着变量的形式,常量的作用(一经初始化即不能更改)。用作常量,常用于取代#define定义的宏常量。如下:
    1. #include<iostream>
    2. using namespace std;

    3. #define N 200

    4. int main(void){
    5.    
    6.     const int a = 200;
    7.     int b =300;
    8.     int c = a + b;  //等价于int c = N + b;
    9.     //用const声明的变量,可以替代#define 声明的常量
    10.    
    11.     return 0;
    12. }
    复制代码

    附:从底层的角度来讲:执行int c =a +b; int c = N +b;编译器已经自动将'a''N'替换成了200,这里实际上执行的都是:int c = 200 + b;不一样的地方在于,宏常量是在编译的时候进行的替换,const是在汇编的时候进行的替换。
    5.2常引用的特性
    const 的本意,即不可修改。所以,const 对象,只能声明为 const 引用,使其语义保持一致性。而 non-const 对象,既可以声明为 const 引用,也可以声明为 no-const 引用。无论如何,只要声明为const 引用,就不可以通过 const 引用修改数据。
    //声明为const的对象,只能声明为const引用
    1. #include<iostream>
    2. using namespace std;

    3. int main(void){

    4.     const int val = 10;
    5.     int &rv = val;              //error
    6.     const int &rv2 = val;

    7.     return 0;
    8. }
    复制代码

    运行结果如下:

    //non-const 对象,既可以声明为 const 引用,也可以声明为 no-const 引用
    1. #include<iostream>
    2. using namespace std;

    3. int main(void){

    4.     int data = 10;//non-const对象
    5.     int &rd = data;//声明为no-const引用
    6.     const int &rd2 = data;//声明为const引用

    7.     rd = 15;//非const引用,可以修改数据
    8.     cout<<"rd="<<rd<<" data="<<data<<endl;

    9.     rd2 = 20;//声明为const引用,则不可以通过该引用修改数据

    10.     return 0;
    11. }
    复制代码

    运行结果如下:

    5.3临时对象的常引用
    临时对象,通常理解为不可以取地址的对象,比如函数的返回值,即 Cpu 中计算产生的中间变量通常称为右值。常见临时值有常量表达式等。
    实验1:临时对象的常引用探究
    1. #include<iostream>
    2. using namespace std;

    3. int fun(){
    4.     int a = 100;
    5.     return a;
    6. }

    7. int main(void){

    8.     //常量
    9.     int &c1 =55;
    10.     const int &c2 = 55;
    11.    
    12.    //表达式
    13.     int a = 3; int b = 5;
    14.     int &n1 = a + b;
    15.     const int &n2 = a + b;
    16.    
    17.     //函数返回值
    18.     int &ret1 = fun();
    19.     const int &ret2 = fun();
    20.    
    21.     //类型不同的变量
    22.     double d = 10.2;
    23.     int &rd1 = d;
    24.     const int &rd2 = d;
    25.    
    26.     return 0;
    27. }
    复制代码

    效果图如下:

    由实验1可知:对于这些临时变量(函数返回值、常量、不同类型的变量,表达式)的引用,在C++中是不可行的,但加上关键字const,使之成为常引用,则能顺利通过编译。这是为何呢?
    原因在于,虽然对临时变量直接引用[,在语法上是行不通的;加上关键字const之后,实际上就是C++在底层,使这些临时变量产生一个中间变量,再对这些中间值进行常引用,就能够实现对临时变量进行引用的效果。
    我们再通过对不同类型的变量进行常引用,来进行佐证:
    1. #include<iostream>
    2. using namespace std;

    3. int main(void){

    4.     //类型不同的变量
    5.     double d = 10.2;
    6.     const int &rd = d;
    7.     cout<<"d="<<d<<endl;
    8.     cout<<"rd="<<rd<<endl;

    9.     d = 15.6;
    10.     cout<<"d="<<d<<endl;
    11.     cout<<"rd="<<rd<<endl;

    12.     return 0;
    13. }
    复制代码

    运行结果如下:

    通过运行结果可知:修改变量b的值前后,b打印的结果不同,而常引用rd的值却没有随着b的变化而变化,我们可以证明, const 引用了一个不可改变的临时变量,const int tmp = data; c onst int&rd = tmp; 此时,我们改变了 data 的值,临时变量的值也并没有发生改变。如下:
    1.     double d = 10.2;   
    2.     const int tmp = d;
    3.     const int &rd = tmp;
    复制代码

    5.4易混点const type &与 type & const剖析
    const type & 与 type &const 这两个修饰方式,哪个是正确呢,背后的有什么设计的用义呢?
    此处可以参考 const 修饰指针的用法,int * const p 的话,表示 p 不可更改指向,而引用呢,本身一经声明,即不可更改指向,故也不存在这个修饰方法了。

    小结:尽可能使用const
    在任何可以使用 const 的地方,尽量使用 const,为什么呢?因为有如下的好处,可以使你的程序,更健壮。
    1.使用const可以避免无意修改数据的编程错误。
    2.使用const可以处理const和非const实参。否则将只能接受非const数据。
    3.使用const引用,可使函数能够正确的生成并使用临时变量(如果实参与引用参数不匹配,就会生成临时变量)

    6.引用的本质
    之前讲过,引用只有在生成对象的时候才会开辟空间,其大小就是生成对象类型的大小。
    1. #include<iostream>
    2. using namespace std;

    3. void fun(int &a, char &b){
    4.     cout<<"sizeof(a)="<<sizeof(a)<<" sizeof(b)="<<sizeof(b)<<endl;
    5. }

    6. int main(void){

    7.     int x;
    8.     char y;
    9.     fun(x,y);

    10.     return 0;
    11. }
    复制代码

    运行结果:

    接下来,我们通过一个实验来探究引用"类型"的大小。
    实验2:引用类型大小的探究
    1. #include<iostream>
    2. using namespace std;

    3. struct TypeP{
    4.     char *p;
    5. };

    6. struct TypeC{
    7.     char c;
    8. };

    9. struct TypeR{
    10.     char &c;
    11. };

    12. int main(void){

    13.     cout<<"sizeof(TypeP)="<<sizeof(TypeP)<<" sizeof(TypeC)="<<sizeof(TypeC)<<" sizeof(TypeR)="<<sizeof(TypeR)<<endl;

    14.     return 0;
    15. }
    复制代码


    进一步的推测:
    C++中只有 const 类型的数据,要求必须初始化。而引用也必须要初始化,指针的大小是4个字节,而引用的大小也是4个字节。所以引用应是一个指针,还应该是 const 修饰的指针。
    结论:
    综上所述,引用的本质是 const 类型的指针,即 const type * ref。其功能类似指针,看起来又不是指针,是对指针的封装。
    引申内容:通过汇编代码来验证引用的本质是指针
    1. #include<iostream>
    2. using namespace std;

    3. void mySwap1(int *p, int *q){
    4.     int t = *p;
    5.     *p = *q;
    6.     *q = t;
    7. }

    8. void mySwap2(int &a, int &b){
    9.     int t = a;
    10.     a = b;
    11.     b = t;
    12. }

    13. int main(void){

    14.     int a = 10, b = 15;
    15.     mySwap1(&a,&b);
    16.     mySwap2(a,b);
    17.    
    18.     return 0;
    19. }
    复制代码

    分别查看这两个交换函数的反汇编代码:
    1. 0x401630 <swapPtr(int*, int*)>
    2. 7 [1]{
    3. 0x401630 55 push %ebp
    4. 0x401631 <+0x0001> 89 e5 mov %esp,%ebp
    5. 0x401633 <+0x0003> 83 ec 10 sub $0x10,%esp
    6. 8 [1] int t = *p;
    7. 0x401636 <+0x0006> 8b 45 08 mov 0x8(%ebp),%eax
    8. 0x401639 <+0x0009> 8b 00 mov (%eax),%eax
    9. 0x40163b <+0x000b> 89 45 fc mov %eax,-0x4(%ebp)
    10. 9 [1] *p = *q;
    11. 0x40163e <+0x000e> 8b 45 0c mov 0xc(%ebp),%eax
    12. 0x401641 <+0x0011> 8b 10 mov (%eax),%edx
    13. 0x401643 <+0x0013> 8b 45 08 mov 0x8(%ebp),%eax
    14. 0x401646 <+0x0016> 89 10 mov %edx,(%eax)
    15. 10 [1] *q = t;
    16. 0x401648 <+0x0018> 8b 45 0c mov 0xc(%ebp),%eax
    17. 0x40164b <+0x001b> 8b 55 fc mov -0x4(%ebp),%edx
    18. 0x40164e <+0x001e> 89 10 mov %edx,(%eax)
    19. 11 [1]}
    复制代码
    1.  0x401653 <swapRef(int&, int&)>
    2. 13 [1]{
    3. 0x401653 55 push %ebp
    4. 0x401654 <+0x0001> 89 e5 mov %esp,%ebp
    5. 0x401656 <+0x0003> 83 ec 10 sub $0x10,%esp
    6. 14 [1] int t = p;
    7. 0x401659 <+0x0006> 8b 45 08 mov 0x8(%ebp),%eax
    8. 0x40165c <+0x0009> 8b 00 mov (%eax),%eax
    9. 0x40165e <+0x000b> 89 45 fc mov %eax,-0x4(%ebp)
    10. 15 [1] p = q;
    11. 0x401661 <+0x000e> 8b 45 0c mov 0xc(%ebp),%eax
    12. 0x401664 <+0x0011> 8b 10 mov (%eax),%edx
    13. 0x401666 <+0x0013> 8b 45 08 mov 0x8(%ebp),%eax
    14. 0x401669 <+0x0016> 89 10 mov %edx,(%eax)
    15. 16 [1] q = t;
    16. 0x40166b <+0x0018> 8b 45 0c mov 0xc(%ebp),%eax
    17. 0x40166e <+0x001b> 8b 55 fc mov -0x4(%ebp),%edx
    18. 0x401671 <+0x001e> 89 10 mov %edx,(%eax)
    19. 17 [1]}
    复制代码

    通过对比,可以发现,这两段汇编代码,除了地址不同,其他操作完全一致。即调用指针交换函数mySwap1()和调用引用交换函数mySwap2(),底层的实现完全一致,因此可证,引用的本质就是指针!
    上一篇:6.C++之引用(上)
    下一篇:8.堆内存操作:new/delete

    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?注册

    x

    点评

    多谢分享,希望坚持哦  发表于 2020-6-12 19:52
  • TA的每日心情
    开心
    昨天 08:32
  • 签到天数: 88 天

    [LV.6]常住居民II

    发表于 2020-6-13 06:07:23 | 显示全部楼层
    多谢分享,希望坚持哦
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    4 天前
  • 签到天数: 10 天

    [LV.3]偶尔看看II

    发表于 2020-7-1 10:23:04 来自手机 | 显示全部楼层
    学习了 感谢楼主分享
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 登录 | 注册

    本版积分规则

    关闭

    站长推荐 上一条 /3 下一条

    红盟社区--中国红客联盟 

    Processed in 0.068155 second(s), 17 queries.

    站点统计| 举报| Archiver| 手机版| 黑屋 |   

    Powered by HUC © 2001-2017 Comsenz Inc.

    手机扫我进入移动触屏客户端

    关注我们可获取更多热点资讯

    Honor accompaniments. theme macfee

    快速回复 返回顶部 返回列表