重庆分公司,新征程启航

为企业提供网站建设、域名注册、服务器等服务

引用的本质分析(四)

        我们上节讲了 C++ 中的引用,那么我们就来看下引用的本质。引用作为变量别名而存在,因此在一些场合可以代替指针。引用相对于指针来说具有更好的可读性和实用性。注意:函数中的引用参数不需要进行初始化!

创新互联建站2013年至今,是专业互联网技术服务公司,拥有项目成都网站设计、成都做网站网站策划,项目实施与项目整合能力。我们以让每一个梦想脱颖而出为使命,1280元资兴做网站,已为上家服务,为资兴各地企业和个人服务,联系电话:18980820575

        下来我们来看看 swap 函数的实现对比,如下

void swap(int* a, int* b)    // 指针形式的
{
    int t = *a;
    *a = *b;
    *b = t;
}

void swap(int& a, int& b)    // 引用形式的
{
    int t = a;
    a = b;
    b = t;
}

        那么这块就有个特殊的引用,便是 const 引用了。在 C++ 中可以声明 const 引用,它的格式为 const Type& name = var;const 引用让变量拥有只读属性。当使用常量对 const 引用进行初始化时,C++ 编译器会为常量值分配空间并将引用作为这段空间的别名。使用常量对 const 引用初始化后将生成一个只读变量!

        下来我们以代码为例进行分析,看看引用的特殊意义,代码如下

#include 

void Example()
{
    printf("Example:\n");
    
    int a = 3;
    const int& b = a;
    int* p = (int*)&b;
    
    // b = 5;
    
    *p = 5;
    
    printf("a = %d\n", a);
    printf("b = %d\n", b);
}

void Demo()
{
    printf("Demo:\n");
    
    const int& c = 1;
    int* p = (int*)&c;
    
    // c = 5;
    
    *p = 5;
    
    printf("c = %d\n", c);
}

int main(int argc, char *argv[])
{
    Example();
    
    printf("\n");
    
    Demo();
    
    return 0;
}

        我们在 Example 函数中定义了变量 a,用 b const 引用 a,然后用指针 p 指向 b。然后通过指针 p 改变 b 的值,但是这块 b 是 const 引用,所以不能直接改变 b。我们看看 a 和 b 会是多少。在 Demo 函数中,我们通过 const 引用 c 为 1,并且定义指针 p 指向它。同样不能直接改变 c,但是可以通过指针 p 来改变它的值。我们先来看看通过指针 p 改变后的值是否为 5 呢?看看编译结果

引用的本质分析(四)

        我们看到值已经都改变了,我们再来去掉第 11 和 26 行的注释,看看直接改变 const  引用会怎样?引用的本质分析(四)

        我们看到报的都是它们是只读变量。那么我们思考下:引用有自己的存储空间吗?我们通过程序来看看

#include 

struct test
{
    char& c;
};

int main(int argc, char *argv[])
{
    char c = 'c';
    char& rc = c;
    test r = { c };
    
    printf("sizeof(char&) = %d\n", sizeof(char&));
    printf("sizeof(rc) = %d\n", sizeof(rc));
    
    printf("sizeof(test) = %d\n", sizeof(test));
    printf("sizeof(r.c) = %d\n", sizeof(r.c));
    
    return 0;
}

        我们在第 3 行定义了一个结构体变量 test,但它里面只有一个 char 类型的引用 c。我们来看看这个结构体占用内存吗?编译如下

引用的本质分析(四)

        我们看到引用本身只占用了一个字节,但是结构体 test 占用了 4 个字节的内存。我们猜想它是不是跟指针有某种联系呢?其实引用在 C++ 中的内部实现是一个指针常量。关系如下

引用的本质分析(四)

        注意:a> C++ 编译器在编译过程中用 指针常量作为引用的内部实现,因此引用所占的空间大小与指针相同;b> 从使用的角度,引用只是一个别名,C++ 为了实用性而隐藏了引用的存储空间这一细节。下来我们通过一个示例代码进行说明

#include 

struct TRef
{
    char* before;
    char& ref;
    char* after;
};

int main(int argc, char* argv[])
{
    char a = 'a';
    char& b = a;
    char c = 'c';

    TRef r = {&a, b, &c};

    printf("sizeof(r) = %d\n", sizeof(r));
    printf("sizeof(r.before) = %d\n", sizeof(r.before));
    printf("sizeof(r.after) = %d\n", sizeof(r.after));
    printf("&r.before = %p\n", &r.before);
    printf("&r.after = %p\n", &r.after);

    return 0;
}

        我们看到在结构体 TRef 内部只有 3 个成员,两个指针,一个引用。我们通过打印结构体的大小和它的 before 指针和 after 指针的大小和地址来分别看看中间的引用究竟是什么

引用的本质分析(四)

        我们看到结构体总共占 12 个字节的内存,指针 before 和 after 各占 4 个字节,并且他们的地址相差 8,从而双重说明了中间的引用占 4 个字节的内存空间,引用便是指向一个地址的。那么它的本质便是指针了。

        那么为什么还要弄个引用来代替指针呢?我们知道在 C 语言中,凡是涉及到指针的操作都是容易出 bug 的地方,因此 C++ 设计了引用来在大部分情况下代替指针。从功能性来说,可以满足大多数的需要使用指针的场合;从安全性来说,可以避免由于操作指针不当而带来的内存错误;从操作性来说,简单易用,又不失功能强大。下面我们来看看函数返回引用的一个示例

#include 

int& demo()
{
    int d = 0;
    
    printf("demo: d = %d\n", d);
    
    return d;
}

int& func()
{
    static int s = 0;
    
    printf("func: s = %d\n", s);
    
    return s;
}

int main(int argc, char* argv[])
{
    int& rd = demo();
    int& rs = func();
    
    printf("\n");
    printf("main: rd = %d\n", rd);
    printf("main: rs = %d\n", rs);
    printf("\n");
    
    rd = 10;
    rs = 11;
    
    demo();
    func();
    
    printf("\n");
    printf("main: rd = %d\n", rd);
    printf("main: rs = %d\n", rs);
    printf("\n");

    return 0;
}

        我们在 demo 函数里返回了局部变量 d,因此这个肯定会出问题。在 func 函数里返回的加 static 修饰的变量,因此它是会放在全局数据区,不会出错。我们在第 23 和 24 行用 demo 和 func 函数进行初始化,因此这会打印出 d = 0 和 s = 0;在第 27 和 28 行打印 rd 和 rs 的值,因为 demo 函数返回之后 d 会丢失,这时 rd 便是一个野指针了。所以 rd 指向的是一个随机数,但是 rs 还是为 0;第 31 和 32 行分别对 rd 和 rs 进行重新赋值,再次调用 demo 和 func 函数时,d 还是为 0,s 就为 11 了;最后第 38 和 39 行会打印出 rd 为随机数,rs 为 11。我们来看看编译结果和我们分析的是否一致

引用的本质分析(四)

        我们看到它在编译的时候都已经报警告了,打印的结果和我们所分析的是一致的。通过对引用本质的学习,总结如下:1、引用作为变量别名而存在旨在代替指针;2、const 引用可以使得变量具有只读属性;3、引用在编译器内部使用指针常量实现,它的最终本质为指针;4、引用可以尽可能的避开内存错误。

        欢迎大家一起来学习 C++ 语言,可以加我QQ:243343083。


名称栏目:引用的本质分析(四)
文章分享:http://cqcxhl.cn/article/ggiohi.html

其他资讯

在线咨询
服务热线
服务热线:028-86922220
TOP