C++中引用的本质以及与指针的区别(c++数据在内存中的分配)

1、引用的意义

引用作为变量别名而存在,因此在一些场合可以替代指针,引用相对于指针来说具有更好的可读性和实用性

// swap函数的实现对比
#include 
using namespace std;

void swap1(int a, int b);
void swap2(int *p1, int *p2);
void swap3(int &r1, int &r2);

int main() {
    int num1, num2;
    cout << "Input two integers: ";
    cin >> num1 >> num2;
    swap1(num1, num2);
    cout << num1 << " " << num2 << endl;

    cout << "Input two integers: ";
    cin >> num1 >> num2;
    swap2(&num1, &num2);
    cout << num1 << " " << num2 << endl;

    cout << "Input two integers: ";
    cin >> num1 >> num2;
    swap3(num1, num2);
    cout << num1 << " " << num2 << endl;

    return 0;
}

//直接传递参数内容,无法达到交换目的,因为是形参进行了交换,而实参没有交换
void swap1(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
}

//传递指针,实现交换
void swap2(int *p1, int *p2) {
    int temp = *p1;
    *p1 = *p2;
    *p2 = temp;
}

//按引用传参,实现交换
void swap3(int &r1, int &r2) {
    int temp = r1;
    r1 = r2;
    r2 = temp;
}

注意: 函数中的引用形参不需要进行初始化,初始化是在调用的时候完成的

2、特殊的引用

在C++中可以声明const引用,具体用法如下:

const Type& name = var;

const引用让变量拥有只读属性,这个只读属性是针对当前的这个别名,变量是可以通过其它方式进行修改

int a = 4;              // a是一个变量
const int  & b = a;     // b是a的一个引用,但是b具有只读属性
int * p = (int *)&b;    // p = &a
b = 5;      // err, 引用b 被const修饰,b是一个只读变量
a = 6;      // ok
printf("a = %d\n", a);
*p = 5;     // ok
printf("a = %d\n", a);

当使用常量对const引用进行初始化时,C++编译器会为常量值分配空间,并将引用名作为这段空间的别名

#include 
void Example()
{
    printf("Example:\n");  
    int a = 4;
    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;
}

结论: 使用常量对const引用初始化后将产生一个只读变量

3.问题:引用有自己的存储空间吗?

在研究这个问题之前,我们先来看一下c++中数据在内存中的存储:
C++中引用的本质以及与指针的区别(c++数据在内存中的分配)_第1张图片
一个运行的程序在内存中主要表示为这四种空间区域。那这几种空间区域存储的是什么?

代码区:存放的是程序的执行代码(编译后的二进制代码)。

全局数据区:存放全局变量、静态变量、常量和文字量(文字量和常量有区别的)。

堆区:存放动态内存,供程序随机申请使用。C的malloc、free,C++的new和delete内存分配就是在堆中进行。

栈区:又称局部数据区,它动态反映了程序运行中的函数状态。存储函数的形参、局部变量以及函数的返回值和返回地址。

这其中栈区的存储方式较为特别,按照自下至上内存地址递增来算,栈区中按照变量定义顺序自高地址向低地址依次分配,也就是自上至下进行分配。

#include 

struct TRef
{
    char& r;        // 字符类型引用
};

int main(int argc, char *argv[])
{ 
    char c = 'c';
    char & rc = c;
    TRef ref = { c }; // 用C进行初始化, TRef.r 就是 c的别名了
    
    printf("sizeof(char&) = %d\n", sizeof(char&));   // char引用的大小,引用即变量本身,求所对应的变量本身的大小,即sizeof(char) = 1
    printf("sizeof(rc) = %d\n", sizeof(rc));        // rc是一个引用,即sizeof(c) = 1
    
    printf("sizeof(TRef) = %d\n", sizeof(TRef));    // sizeof(TRef) = 4
    printf("sizeof(ref.r) = %d\n", sizeof(ref.r));  // TRef.r是 c的别名,sizeof(c) = 1

    // sizeof(TRef) = 4
    // 指针变量本身也是占4个字节
    // 引用和指针的关系
    
    return 0;
}

4、引用的本质

引用在C++中的内部实现是一个指针常量
注意:
1、C++编译器在编译过程中用 指针常量 作为引用的内部实现,因此引用所占用的空间大小于指针相同
2、从使用的角度,引用只是一个别名,C++为了使用性而隐藏了引用的存储空间这一细节。

#include 
// 在内存中结构体内部从低地址向高地址依次存储
// 如果以局部变量来定义结构体时,其整体存放在栈区
// [参考资料](https://blog.csdn.net/u011770174/article/details/77369259)

struct TRef
{
    char* before;    // 4字节
    char& ref;      // 4字节
    char* after;    // 4字节
};

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));  // sizeof(r) = 12
    printf("sizeof(r.before) = %d\n", sizeof(r.before)); // sizeof(r.before) = 4
    printf("sizeof(r.after) = %d\n", sizeof(r.after));   // sizeof(r.after) = 4
    printf("&r.before = %p\n", &r.before);  // &r.before = 0xbuf8a300c
    printf("&r.after = %p\n", &r.after);    // &r.after  = 0xbuf8a3014

    /*
     0xbuf8a3014 - 0xbuf8a300c = 8
     before占了4个字节,所以ref也是占4个字节
    */
    return 0;
}

引用的意义: C++中的引用旨在大多数的情况下替代指针
功能性:可以满足多数需要使用指针的场合
安全性:可以避开由于指针操作不当带来的内存错误
操作性:简单易用,又不失功能强大

5. 引用的易出现的问题

在将引用作为函数返回值时应该注意一个小问题,就是不能返回局部数据(例如局部变量、局部对象、局部数组等)的引用,因为当函数调用完成后局部数据就会被销毁,有可能在下次使用时数据就不存在了,C++ 编译器检测到该行为时也会给出警告。

#include 
using namespace std;

int &plus10(int &r) {
    int m = r + 10;
    return m;  //返回局部数据的引用
}

int main() {
    int num1 = 10;
    int num2 = plus10(num1);
    cout << num2 << endl;
    int &num3 = plus10(num1);
    int &num4 = plus10(num3);
    cout << num3 << " " << num4 << endl;

    return 0;
}

// 在 Visual Studio 下的运行结果:
// 20
// -858993450 -858993450

6. 总结

  • 引用作为变量别名而存在旨在代替指针
  • const引用可以使得变量具有只读属性
  • 引用在编译器内部使用指针常量实现
  • 引用的最终本质为指针
  • 引用可以尽可能地避开内存错误

你可能感兴趣的:(C++,c++,开发语言,算法)