zoukankan      html  css  js  c++  java
  • C++深度解析教程学习笔记(2)C++中的引用

    1.C++中的引用

    (1)变量名的回顾 

        ①变量是一段实际连续存储空间的别名,程序中通过变量来申请并命名存储空间

        ②通过变量的名字可以使用存储空间。(变量的名字就是变量的值,&变量名是取地址操作)

    (2)C++中新增加了引用的概念

       ①引用可以看作一个己定义变量的别名

       ②引用的语法:Type& name = var; //Type 为类型名,name 为引用的名字,var为己定义的变量名

       ③普通引用在定义时必须用同类型的变量进行初始化,函数参数引用的初始化发生在函数被调用时。

    #include <stdio.h>
    
    int main()
    {
        //引用初探
        int a = 4;
        int& b = a;//b为a的别名
    
        b = 5; //操作b就是操作a
    
        printf("a = %d
    ", a); //5
        printf("b = %d
    ", b); //5
    
        printf("&a = %p
    ", &a);
        printf("&b = %p
    ", &b);//&b等于&a
    }

    (3)引用作为变量别名而存在,因此在一些场合可以代替指针

    (4)引用相对于指针来说,具有更好的可读性和实用性

    2.特殊的引用——const引用

    (1)const Type& name = var; //让变量拥有只读属性

    (2)当使用常量对 const 引用进行初始化时,C++编译器会为这个常量值分配空间,并将引用名作为这段空间的别名。但这样用常量对 const 引用初始化将生成的是一个只读变量

    #include <stdio.h>
    
    void Example()
    {
        printf("Example:
    ");
    
        int a = 4;
        const int& b = a;//变量a的别名,const让变量b具有只读属性
        int* p = (int*)&b; //&b就是变量a的地址
    
        //b = 5;//非法,因为用const修饰的引用,其代表的变量是只读的
    
        *p = 5; //合法,修改变量a的值
    
        printf("a = %d
    ", a); //5;
        printf("b = %d
    ", b); //5
    }
    
    void Demo()
    {
        printf("Demo:
    ");
    
        //const引用
        const int& c = 1; //引用本应是变量的别名。一般,常量是不分配内存的。当编译器看
        //该行时,会为常量1分配内存,并让c作为这段空间的别名。
    
        int* p = (int*)&c;
    
        //c = 5;//非法,因为const修饰,c是只读的
    
        *p = 5;//合法,通过指针访问内存
    
        printf("c = %d
    ", c); //5
    
        printf("
    ", c); //5
    
        //const修饰变量:注意与const引用的区别
        const int a = 1;
        p = (int*)&a; //遇到&a才为常量分配内存
    
        //a = 5;//非法,因为const修饰,a是只读的
    
        *p = 5;//合法,通过指针访问内存
    
        printf("a = %d
    ", a); //1,编译器看到a直接从符号表读其值(1)
        printf("*p = %d
    ", *p); //5
    }
    
    int main()
    {
        Example();
    
        printf("
    ");
    
        Demo();
    
        return 0;
    }

    3.引用的本质

    (1)引用在 C++中的内部实现是一个常量指针,因此引用所占用的空间大小与指针相同。

    (2)从使用的角度,引用只是一个别名,C++为了实用性而隐藏了引用的存储空间这一细节。

       ①在编译过程中,编译器看到 int& a 的声明就会转换为 int* const a;

       ②看到使用引用时,会转为*a,如此隐藏了使用指针的事实。

    #include <stdio.h>
    
    struct TRef
    {
        char& r;//引用的本质是常量指针,因此会分配4字节的空间,相当于char* const r;
    };
    
    
    
    int main()
    {
        char c = 'c';
        char& rc = c;
        TRef ref = { c };
    
        printf("sizeof(char&) = %d
    ", sizeof(char&));//1,char型变量别名,大小为1
        printf("sizeof(rc) = %d
    ", sizeof(rc));      //1,变量c的别名,大小为1
    
        printf("sizeof(TRef) = %d
    ", sizeof(TRef));  //结构体内有个引用,本质为指针,占4字节
        printf("sizeof(ref.r)= %d
    ", sizeof(ref.r)); //1,char型变量的别名,大小为1
    
    
        return 0;
    }

    引用的存储空间

    #include <stdio.h>
    
    struct TRef
    {
        char* before; //4字节
        char& ref;//4字节,本质是常量指针,会分配4字节的空间,相当于char* const ref;
        char* after;//4字节
    
    };
    
    
    
    int main()
    {
        char a = 'a';
        char& b = a;
        char c = 'c';
    
        TRef r = { &a, b, &c };
    
        printf("sizeof(r) = %d
    ", sizeof(r));               //12
        printf("sizeof(r.before) = %d
    ", sizeof(r.before)); //4
        printf("sizeof(r.after)  = %d
    ", sizeof(r.after));  //4
        printf("&r.before = %p
    ", &r.before);
        printf("&r.after  = %p
    ", &r.after); //after和before相差8个字节,中间隔了个b引用所占用的空间
    
        return 0;
    }

    4.引用的意义

    (1)功能性:引用在大多数情况下代替指针,可以满足需要使用指针的场合

    (2)安全性:可以避开由于指针操作不当而带来的内存错误

    (3)操作性:简单易用,又不失功能强大

    函数返回引用

    #include <stdio.h>
    
    int& demo()
    {
        int d = 0;
    
        printf("demo: d = %d
    ", d); //输出0
    
        return d;//返回局部变量的引用,危险
    }
    
    int& func()
    {
        static int s = 0;
        printf("func: s = %d
    ", s);//输出0
    
        return s; //合法,返回的是静态局部变量(位于全局存储区中的)
    }
    
    
    
    int main()
    {
        int& rd = demo();
        int& rs = func();
    
        printf("
    ");
        printf("main: rd = %d
    ", rd);//垃圾数值
        printf("main: rs = %d
    ", rs);//0,返回全局存储区中的s
        printf("
    ");
    
        return 0;     
    }

    引用作为变量别名而存在,旨在代替指针,引用在编译器内部使用常量指针实现,其最终本质为指针,引用可以尽可能的避开内存错误。

    5.关于const的疑问

    (1)const 常量的判别准则

    ①只有用字面量初始化的 const 常量才会进入符号表,如 const int i = 1;

    ②使用其它变量初始化的 const 常量仍然是只读变量。如 const int a = b;//a 为只读变量

    ③被 volatile 修饰的 const 常量不会进入符号表,如 volatile const int i = 1;//这时会为 i 分配内存,且 i 每次都是从内存中取值。加 const 只是说明 i 不能作为左值。

    ▲在编译期间不能直接确定初始值的 const 标识符,都被作为只读变量处理。

    (2)const 引用的类型与初始化变量的类型

    ①当用变量来初始化与 const 引用时,如果两者类型相同,则初始化变量成为只读变量。

    ②当用变量来初始化与 const 引用时,如果两者类型不同,则将生成一个新的变量,即引用的是另一个新变量,而不是原来的用来初始化引用的那个变量。

    #include <stdio.h>
    
    int main()
    {
        //实验1:初始化变量的类型与引用类型相同时
        const int x = 1;
        const int& rx = x;//x与rx的类型相同,所以rx为只读变量,不能作为左值
                          //但本质上还是变量,引用x的内存值(而不是符号表中的值)
    
        int& nrx = const_cast<int&>(rx);//转为普通引用
    
        nrx = 5;  //nrx与rx都引用了x的内存值
    
        printf("x = %d
    ", x);     //1,从符号表中读取
        printf("rx = %d
    ", rx);   //5,从内存中读取
        printf("nrx = %d
    ", nrx); //5,从内存中读取
    
        printf("&x = %d
    ", &x);
        printf("&rx = %d
    ",&rx);
        printf("&nrx = %d
    ",&nrx); //x、rx、nrx地址相同
    
        //实验2:初始化变量的类型与引用类型不同时
        char c = 'c';
        char& rc =c;
        const int& trc = c;//c与trc类型不一致,则会生成一个新的变量,然后trc引用这个新的变量
        
        rc = 'a';
    
        printf("c = %c
    ", c);  //c
        printf("rc = %c
    ",rc); //c
        printf("trc = %c
    ",trc);//a
    
        printf("&c = %p
    ", &c); 
        printf("&rc = %p
    ",&rc); //rc与c的地址相同
        printf("&trc = %p
    ",&trc);//trc是一个新的地址
    
        //实验3:volatlie
        volatile const int y = 2; //volatile修饰,分为y分配内存
        int* p = const_cast<int*>(&y);//因y被const修饰,不能作为左值
    
        *p = 6; //因y不能作为左值,用来代替y = 6;这样的写法
    
        printf("y = %d
    ", y);//6,volatile指示y得从内存中读取
        printf("p = %p
    ", p);//y的地址
    
        const int z = y; //用变量初始化const常量,z不会进入符号表,z分配内存
    
        p = const_cast<int*>(&z);
        
        *p = 7;
    
        printf("z = %d
    ", z);//7,因z没进入符号表
        printf("p = %p
    ", p);//z的地址
    
        return 0;
    }

    6.关于引用的疑问

    (1)指针与引用的不同

     

    指针

    引用

    初始化

    值是一个内存地址,不需要初始化

    必须在定义时初始化,之后无法代表其它变量

    访问内存

    通过指针可以访问对应内存地址中的值

    对引用的操作(赋值,取地址等)都会传递到其代表的变量上。

    const修饰

    被const修饰成常量或只读变量。 如const int* p;//p

    const引用,表示其代表的变量具有只读属性。如,const int& a等价于const int* const a;

    (2)从使用 C++语言的角度来看,引用与指针没有任何关系。引用是变量的新名字,操作引用就是操作对应的变量。当进行 C++编程时,直接站在使用的角度看待引用,与指针毫无关系,引用就是变量的别名。

    (3)从 C++编译器的角度来看,在编译器内部,使用指针常量来实现“引用”。因此,“引用”在定义时必须初始化。当对 C++代码进行调试分析时,一些特殊情况,可以考虑站在 C++编译器的角度看待引用。

    引用典型问题分析

    #include <stdio.h>
    
    int a = 1;
    
    struct SV
    {
        int& x;
        int& y;
        int& z;
    };
    
    int main()
    {
        int b = 2;
        int* pc = new int(3);
        
        //将sv各成员初始化为变量a,b,*pc等内存的引用
        SV sv = {a, b, *pc};
        printf("&sv.x = %p
    ", &sv.x);//变量a的地址,全局区
        printf("&sv.y = %p
    ", &sv.y); //变量b的地址,栈
        printf("&sv.z = %p
    ",&sv.z);   //new出来的地址,堆
    
        //在C++中没有“引用数组”的概念,请看如下分析
        //对于数组而言,其内存是连续分布的。当进行&array[1] - &array[0]
        //表示前后两个元素的地址相差的值,应等于sizeof(元素的类型)。
        //但如果允许定义“引用数组”的话,如下面语句,&array[1]表示第1个元素
        //的地址,即元素b的地址(&b),而&array[0]表示&a,显然这两个地址是不连续的。
        //所以int& array[]={a, b, *pc};//这样的定义是错误的,C++里不支持“引用数组”
    
        return 0;
    }

    指针是一个变量,而引用是一个变量的新名字。const 引用能够生成新的只读变量,编译时不能直接确定初始值的 const 标识符都是只读变量。

  • 相关阅读:
    [BZOJ2661][BeiJing wc2012]连连看 费用流
    <meta> 标签
    CSS3 Transitions, Transforms和Animation的使用
    word-wrap和word-break的区别吗?
    css3中比较少用到的属性记录
    CSS ::Selection的使用方法
    CSS 属性
    javascript正则表达式语法
    jquery metadata 详解
    关于window.console&&console.log(123)的思考
  • 原文地址:https://www.cnblogs.com/CoderTian/p/7735195.html
Copyright © 2011-2022 走看看