【C++】引用与指针

简介: 【C++】引用与指针

专栏放在【C++知识总结】,会持续更新,期待支持🌹


e05157b8c9d640b0a1b8b40ae3a8cb9d.png


引用


引用的概念


在C++中,引用的本质其实就是给一个已经存在的变量”起别名“。也就是说,引用与它所引用的对象共用一块空间。(同一块空间的多个名字)


就比如说,李逵又叫黑旋风,而黑旋风就是指李逵本人,只是名字换了而已。1.png



引用的特性


1. 引用在定义时必须初始化


2. 一个变量可以有多个引用,但一个引用只能有一个实体对象


#include<iostream>
using namespace std;
int main()
{
    int a = 0;
    //一个变量可以有多个引用,但一个引用只能有一个实体
    int& b = a;//不可以写成int& b;  引用必须在定义时初始化
    int& c = b;
    int& d = c;
    cout << &a << endl;//012FFBD0
    cout << &b << endl;//012FFBD0
    cout << &c << endl;//012FFBD0
    cout << &d << endl;//012FFBD0
    //地址相同,abcd共用同一块空间
    // 另外,引用类型与引用实体的类型必须一致,这里不能写为char & d = a(error)
    return 0;
}


扩展(函数栈帧的创建与销毁)


这里我们进行复习一下关于函数栈帧的一些知识。我们知道,在调用一个函数时, 首先会在内存占用一块空间,用来创建该函数的函数栈帧,当调用结束后,该函数栈帧会被销毁,这里需要注意的是,当 栈帧被销毁后,这里的空间实际上在内存中还是存在的,只不过空间的使用权不再归我们使用。 并且函数栈帧的销毁,可能会对原有空间进行清理。


2.png


这里可以举个例子来理解一下,就好比说我们在酒店开了一个房间,并且在退房时把我们的电脑放在了房间里,在这里,酒店就相当于内存的存在,而我们退房的那一刻,就好比 函数栈帧销毁的那一刻,但是虽然我们退房了,该房间还是实际存在的,并没有说随着我们的退房而消失,只不过不再归我们使用。并且房间里的东西也 可能会被清理(也可能依然还在),加入此时我们再进行使用该房间,用是可以用,只不过肯定是不合法的,这种行为就好比 空间的非法访问。


引用的使用场景


做参数进行引用(输出型参数)


所谓输出型参数,实际上就是可以影响实参的参数,就比如我们经常写的交换两个变量的值,在以前我们会使用指针来完成传址调用,从而实现形参的改变影响实参,但现在我们可以用引用来实现,如下:

//做参数来使用,由于共用同一块空间,所以这里的c实际上就是a,d实际就是b
void Swap(int& c, int& d)
{
    int tmp = c;
    c = d;
    d = tmp;
}
int main()
{
    int a = 1, b = 2;
    Swap(a, b);
    cout << a << " " << b << endl;//2 1
    return 0;
}

可以做返回值使用


3.png

我们先来看这样一段代码:


4.png


这里注意的是:这里的a是局部变量,生命周期会随着栈区的销毁而结束,所以这里返回的实际上并不是a,我们通过查看反汇编发现实际上是借助了一个临时变量来实现的。


那么不禁会有个疑问,假如这里的a不随着栈帧的结束而销毁,那么会不会直接返回a呢?可以试验一下:



对于这种现象,我们可以把引用作为返回值来使用,从而实现优化,写成如下格式:


//返回值
int& Test()
{
    static int a = 10;
    a++;
    return a;//也会产生临时变量,但是临时变量的类型是int& 也就是a的别名,即临时变量就是返回的a,减少了拷贝操作
}
int main()
{
    int ret = Test();
    return 0;
}


这就是引用返回,即在返回类型前面加上&,虽然也需要借助临时变量的存在,但是由于临时变量的类型为int& ,即临时变量就是a,所以就减少了临时变量的拷贝工作,会使效率得到提升。我们可以来验证一下:


传值返回 vs 传引用返回效率对比


#include <time.h>
struct A { int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a; }
// 引用返回
A& TestFunc2() { return a; }
void TestReturnByRefOrValue()
{
    // 以值作为函数的返回值类型
    size_t begin1 = clock();
    for (size_t i = 0; i < 1000000; ++i)
        TestFunc1();
    size_t end1 = clock();
    // 以引用作为函数的返回值类型
    size_t begin2 = clock();
    for (size_t i = 0; i < 1000000; ++i)
        TestFunc2();
    size_t end2 = clock();
    // 计算两个函数运算完成之后的时间
    cout << "TestFunc1 time:" << end1 - begin1 << endl;
    cout << "TestFunc2 time:" << end2 - begin2 << endl;
}
int main()
{
    TestReturnByRefOrValue();
}


运行结果如下:


5.png


我们发现传引用返回对比传值返回,效率会有显著提高(作为参数使用时,传引用参数的效率也会高于传值作为参数的效率)


当然,传引用作为返回值的使用是有一定的限制的,我们发现上面的代码能使用传引用返回的原因在于,返回的变量不会随着作用域的销毁而销毁。假如说返回的对象出了作用域后已经销毁,则必须使用传值返回,否则返回的结果是不确定的!


6.png


并且引用用作返回时,还可以修改返回对象(后面的学习会用到很多,这里简单介绍)


如下:


#define N 10
typedef struct ARR
{
    int arr[N];
}ARR;
int& PosARR(ARR& arr,int i)
{
    return arr.arr[i];//这里的arr就是main函数里的arr,不会随着PosARR函数的结束而销毁,所以可以用引用返回
}
int main()
{
    ARR arr;
    for (int i = 0; i < N; i++)
    {
        PosARR(arr, i) = i * 10;//引用返回可以修改返回对象,这里的返回对象为arr.arr[i],对此进行修改
    }
    for (int i = 0; i < N; i++)
    {
        cout << arr.arr[i] << " ";//0 10 20 30 40 50 60 70 80 90
    }
    return 0;
}


总结


引用可以用作参数来使用(输出型参数),也可以用作返回使用,用作返回使用时返回的对象必须是出了所在函数作用域后不会销毁的(比如static修饰的变量,全局变量,malloc......),并且引用返回时,返回的对象可以被修改。同时还可以减少拷贝提高效率。


常引用


我们要记住这样一句话:指针和引用在赋值或者初始化时,权限可以被缩小或者保持,但不可进行修改。


这是什么意思呢?通过以下代码进行了解:


   // 权限放大(error)

   //const int c = 2;//const 修饰的常量不可以进行修改,可以理解只具有读的属性,不具有写的属性,而d可以修改,所以权限被放大

   //int& d = c;//这里正确写法应为const int& d=c;

   //const int* p1 = NULL;

   //int* p2 = p1;//同上,前面加个const即可,const int* p2=p1; (√)

   // 权限保持

   const int c = 2;

   const int& d = c;

   const int* p1 =NULL;

   const int* p2 = p1;

   // 权限缩小

   int x = 1;//x可以进行修改,可以理解为具有读和写的属性,而x是const修饰的,只具有读的属性,权限缩小了

   const int& y = x;

   int* p3 = NULL;

   const int* p4 = p3;//同上


不仅如此,由于所谓临时变量具有常性(即不可被修改)的原因,也会出现以下的情况:


   int i = 0;

   //double& p = i;//error

   //由于int到double类型发生类型转换,而类型转换会产生临时变量,临时变量又具有常性(只可读)

   //因此在前面加上const即可

   const double& p=i;//(√)

这也就解释了上文说到的引用类型与引用实体的类型必须一致,同样,在函数中也一样适用:


int add(int x, int y)
{
    int c = x + y;
    return c;//实际上是借助临时变量,将c拷贝给临时变量,再将临时变量拷贝给p
}
int main()
{
    int a = 1, b = 2;
    //int& p = add(a, b);//由于临时变量具有常性的特点,所以不可以这样写
    //前面加上const 即可
    const int& p = add(a, b);
    return 0;
}


引用与指针



&是一个很熟悉的符号,与指针有关,用在变量前面就是取地址符号,用在类型后面则为引用符号,那么指针与引用之间是否有着什么关系呢?


int a=0;

int* p=&a;//&:取地址符

int& b=a;//&:引用


指针与引用的相同点


实际上,引用与指针,两者之间在底层实现上其实是一样的,我们可以来进行验证


7.png


当然,两者之间也存在着很大的区别。


指针与引用的不同点


首先就是在语法概念上的区别, 引用只是同一个实体的不同名称, 不会单独开辟空间,但是指针会在内存开辟一块4/8byte大小的空间。

引用在定义时 必须初始化,指针没有要求

引用在初始化时引用一个实体后,就 不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体(这一点也就意味着 引用并不能实现完全替代指针,就比如在链表这里,用来指向下一个节点的变量类型,只能是指针)

有多级指针,但是没有多级引用

引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

引用比指针使用起来相对更安全(野指针)

...


看法:


因此对于指针与引用,我们只能是说引用相较于指针来说,更加容易理解使用,并且也不会存在空引用的问题,但是在一些场景下,引用自身的特点(不能改变指向)也存在着使用限制,此时就得用指针来实现


相关文章
|
13天前
|
存储 程序员 C++
深入解析C++中的函数指针与`typedef`的妙用
本文深入解析了C++中的函数指针及其与`typedef`的结合使用。通过图示和代码示例,详细介绍了函数指针的基本概念、声明和使用方法,并展示了如何利用`typedef`简化复杂的函数指针声明,提升代码的可读性和可维护性。
46 0
|
1月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
130 4
|
2月前
|
存储 安全 编译器
在 C++中,引用和指针的区别
在C++中,引用和指针都是用于间接访问对象的工具,但它们有显著区别。引用是对象的别名,必须在定义时初始化且不可重新绑定;指针是一个变量,可以指向不同对象,也可为空。引用更安全,指针更灵活。
|
2月前
|
存储 C++
c++的指针完整教程
本文提供了一个全面的C++指针教程,包括指针的声明与初始化、访问指针指向的值、指针运算、指针与函数的关系、动态内存分配,以及不同类型指针(如一级指针、二级指针、整型指针、字符指针、数组指针、函数指针、成员指针、void指针)的介绍,还提到了不同位数机器上指针大小的差异。
68 1
|
2月前
|
存储 编译器 C语言
C++入门2——类与对象1(类的定义和this指针)
C++入门2——类与对象1(类的定义和this指针)
54 2
|
2月前
|
存储 安全 编译器
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(一)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
2月前
|
存储 C++ 索引
C++函数指针详解
【10月更文挑战第3天】本文介绍了C++中的函数指针概念、定义与应用。函数指针是一种指向函数的特殊指针,其类型取决于函数的返回值与参数类型。定义函数指针需指定返回类型和参数列表,如 `int (*funcPtr)(int, int);`。通过赋值函数名给指针,即可调用该函数,支持两种调用格式:`(*funcPtr)(参数)` 和 `funcPtr(参数)`。函数指针还可作为参数传递给其他函数,增强程序灵活性。此外,也可创建函数指针数组,存储多个函数指针。
|
3月前
|
编译器 C++
【C++核心】指针和引用案例详解
这篇文章详细讲解了C++中指针和引用的概念、使用场景和操作技巧,包括指针的定义、指针与数组、指针与函数的关系,以及引用的基本使用、注意事项和作为函数参数和返回值的用法。
58 3
|
2月前
|
存储 编译器 程序员
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(二)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
3月前
|
C++
C++(十八)Smart Pointer 智能指针简介
智能指针是C++中用于管理动态分配内存的一种机制,通过自动释放不再使用的内存来防止内存泄漏。`auto_ptr`是早期的一种实现,但已被`shared_ptr`和`weak_ptr`取代。这些智能指针基于RAII(Resource Acquisition Is Initialization)原则,即资源获取即初始化。RAII确保对象在其生命周期结束时自动释放资源。通过重载`*`和`-&gt;`运算符,可以方便地访问和操作智能指针所指向的对象。