【C++数据结构】智能指针的构建

简介: 【C++数据结构】智能指针的构建

一、构建智能指针的原因


众所周知,C++是没有垃圾回收的,就会导致以下问题:

1、导致动态申请堆空间,用完后不归还

2、会导致程序的内存泄露,进而影响整个程序,甚至可能是整个电脑

3、指针无法控制所指堆空间的生命周期

我们就需要设计一个类SmartPointer他的功能如下:

  • 指针生命周期结束时主动释放堆空间
  • 一片堆空间最多由一个指针标识
    原因:因为我们要在析构函数中释放堆空间,如果2个智能指向同一片空间,就会导致他被释放2次,就会得到意想不到的结果!!!
  • 杜绝指针运算和指针比较。


二、智能指针分析


  1. 通过类模板描述指针的行为。
    在之前的第一节课我们已经讲过了,可以使用类模板来加强我们的数据结构的复用,在这里我们也是使用类模板来定义我们的SmartPointer使他能够定义不同类型的指针对象
  2. 重载指针特征操作符(->*)。
    利用对象模拟原生指针的行为


三、实现智能指针


数据结构使用的编译器

我使用的编译器是Qt5.9.4,操作系统环境为Ubuntu 22,如果大家使用的是vs/qt(其他版本),或是其他操作系统,我都会讲他们函数的不同地方,例如函数名称的不同!!!


创建Qt控制台项目

1、在侧边栏打开qt creator

5b65084c21b64711832cba980d28bd7c.png

如果在侧边栏未找到怎么办?

那就进行下列操作:

6cef5886619541aba116b71c8bf2d1e5.png

在侧边栏找到点的按钮,点击他。

c3494790169d4a11984295207773384d.png

在上方搜索qt,下面的这个应用就是qt,点击打开他。

2、创建新的项目

3eebb96a077742abbd9ff7ee71e12404.png

进入之后点击New Project

50190fca24b443089f549d0a540c3120.png

之后选择第二个。

之后一路Choose即可,注意在此步的下一步是选择路径的,路径不能有中文

进入之后就是这样的:

31e4e61f31284035819f6c630cbbeb3e.png

智能指针的构建

点击我们的项目,创建新的文件:

d3c817013e39495f9736572a2355eb28.png

38c532352fa9496aa74077955550159b.png

选择C++ Header File,因为我们要使用模板技术,所以只需要头文件即可。具体的原因如下:

使用C++模板技术时,只需要创建头文件的原因是因为模板是一种在**编译时生成代码的机制**。在C++中,模板可以用来定义通用的数据结构和算法,以适应不同类型的数据

当我们编写一个模板类或者模板函数时,我们只需要把模板的声明和定义写在头文件中即可。这是因为模板的实例化是在编译时完成的,而不是链接或运行时。当程序在编译时遇到模板的使用,编译器会根据实际使用的类型生成对应的代码。

由于模板的代码是泛化的,可以适用于不同的类型,在编译时需要进行模板的实例化。如果把模板定义分离到实现文件中,编译器无法在编译时生成相应的实例化代码,导致链接时出现编译错误。

因此,为了正确使用C++模板技术,我们通常将模板的声明和定义都写在头文件中。这样,在包含头文件时,编译器可以看到模板的定义,并根据需要生成相应的实例化代码,保证程序的正确性。

需要注意的是,模板的成员函数的定义通常也需要放在头文件中,以便在实例化时能够正确地内联函数。如果将模板的定义和实现都放在头文件中,会使代码更易于 维护和理解,并且避免了编译和链接时的错误。


在我们本数据结构,我们都会使用一个叫做命名空间来包含我们的数据结构类

他的语法格式如下:

namespace MYLib
{
  //someclass
  class Myclass
  {
  };
  //someFunction
  void func()
  {
  }
}


我们在使用他的时候就需要uising namespace MYLib和添加对应的头文件,才可以使用类,要不然需要像这样MYLib::func()来使用

新文件结构

#ifndef SMARTPOINTER_H
#define SMARTPOINTER_H
namespace MyStruct
{
    //your class
}
#endif // SMARTPOINTER_H


创建指针成员

我们在类中需要使用原生指针。并且我们还需要使用泛指类型

  template<typename T>
    class SmartPointer
    {
    protected:
        T *m_pointer;
    public:
    };


实现析构函数

在我们智能指针中,最重要的就是自动释放指针,所以我们要使用析构函数来帮我们释放指针。

~SmartPointer()
{
    delete m_pointer;
}


构造函数

只需要把m_pointer指向参数即可,参数需要默认参数提法灵活度

SmartPointer(T*point = nullptr)
{
    m_pointer = point;
}


操作符重载

*操作符分析

int *a = new int(10);
cout << *a << endl;


通过运行上面的程序,我们可以发现*他是一个取值操作符,所以我们重载他的时候需要返回一个具体的数,而不是指针

具体实现如下:

T& operator *()
{
     return *m_pointer;
}


->操作符分析:

T*operator ->()
{
    return m_pointer;
}


成员函数的实现

1、判断是否为空

bool isNull()
{
    return m_pointer == nullptr;
}


2、得到指针

T *get()
{
    return m_pointer;
}


拷贝构造函数和"="重载操作符

f64d81d539254b66bf6f5e082a83243d.png

具体的过程如图所示

拷贝构造函数:

SmartPointer(const SmartPointer&obj)
{
    m_pointer = obj.m_pointer;//本类的指针指向obj里面的指针指向的东西
  /*使用const_cast消除const属性*/
    const_cast<SmartPointer<T>>(obj).m_pointer = nullptr;//把参数的m_pointer指向null
}


"="重载:

此处实现和上面差不多,无非是需要判断是否为自赋值

自赋值是什么:

int a = 10;
a = a;


像上面这种a =a;就属于自赋值,此时我们如果去做一遍赋值操作就会大大降低效率所以需要避免!!!

判断自赋值的方法如下:

通过比较地址即可知道是否为同一个类的自赋值

SmartPointer<T> &operator =(const SmartPointer&obj)
{
    if(&obj!=this)
    {
        m_pointer = obj.m_pointer;
        const_cast<SmartPointer<T>>(obj).m_pointer = nullptr;
    }
    return *this;//放回自身,加强连续赋值
}


四、代码一览


#ifndef SMARTPOINTER_H
#define SMARTPOINTER_H
namespace MyStruct
{
    template<typename T>
    class SmartPointer
    {
    protected:
        T *m_pointer;
    public:
        SmartPointer(T*point = nullptr)
        {
            m_pointer = point;
        }
        SmartPointer(const SmartPointer&obj)
        {
            m_pointer = obj.m_pointer;
            const_cast<SmartPointer<T>>(obj).m_pointer = nullptr;
        }
        SmartPointer<T> &operator =(const SmartPointer&obj)
        {
            if(&obj!=this)
            {
                m_pointer = obj.m_pointer;
                const_cast<SmartPointer<T>>(obj).m_pointer = nullptr;
            }
            return *this;
        }
        T& operator *()
        {
            return *m_pointer;
        }
        T*operator ->()
        {
            return m_pointer;
        }
        bool isNull()
        {
            return m_pointer == nullptr;
        }
        T *get()
        {
            return m_pointer;
        }
        ~SmartPointer()
        {
            delete m_pointer;
        }
    };
}
#endif // SMARTPOINTER_H


总结


SmartPointer中,最重要的就是要实现析构函数的自动释放功能

目录
打赏
0
0
0
0
61
分享
相关文章
|
3月前
|
【C++数据结构——查找】二分查找(头歌实践教学平台习题)【合集】
二分查找的基本思想是:每次比较中间元素与目标元素的大小,如果中间元素等于目标元素,则查找成功;顺序表是线性表的一种存储方式,它用一组地址连续的存储单元依次存储线性表中的数据元素,使得逻辑上相邻的元素在物理存储位置上也相邻。第1次比较:查找范围R[0...10],比较元素R[5]:25。第1次比较:查找范围R[0...10],比较元素R[5]:25。第2次比较:查找范围R[0..4],比较元素R[2]:10。第3次比较:查找范围R[3...4],比较元素R[3]:15。,其中是顺序表中元素的个数。
163 68
【C++数据结构——查找】二分查找(头歌实践教学平台习题)【合集】
|
3月前
|
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
192 77
|
3月前
|
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
【数据结构——树】哈夫曼树(头歌实践教学平台习题)【合集】目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果:任务描述 本关任务:编写一个程序构建哈夫曼树和生成哈夫曼编码。 相关知识 为了完成本关任务,你需要掌握: 1.如何构建哈夫曼树, 2.如何生成哈夫曼编码。 测试说明 平台会对你编写的代码进行测试: 测试输入: 1192677541518462450242195190181174157138124123 (用户分别输入所列单词的频度) 预
98 14
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
|
3月前
|
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
110 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
3月前
|
【C++数据结构——查找】二叉排序树(头歌实践教学平台习题)【合集】
【数据结构——查找】二叉排序树(头歌实践教学平台习题)【合集】 目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果: 任务描述 本关任务:实现二叉排序树的基本算法。 相关知识 为了完成本关任务,你需要掌握:二叉树的创建、查找和删除算法。具体如下: (1)由关键字序列(4,9,0,1,8,6,3,5,2,7)创建一棵二叉排序树bt并以括号表示法输出。 (2)判断bt是否为一棵二叉排序树。 (3)采用递归方法查找关键字为6的结点,并输出其查找路径。 (4)分别删除bt中关键
84 11
【C++数据结构——查找】二叉排序树(头歌实践教学平台习题)【合集】
【C++数据结构——图】最短路径(头歌教学实验平台习题) 【合集】
任务描述 本关任务:编写一个程序,利用Dijkstra算法,实现带权有向图的最短路径。 相关知识 为了完成本关任务,你需要掌握:Dijkst本关任务:编写一个程序,利用Dijkstra算法,实现带权有向图的最短路径。为了完成本关任务,你需要掌握:Dijkstra算法。带权有向图:该图对应的二维数组如下所示:Dijkstra算法:Dijkstra算法是指给定一个带权有向图G与源点v,求从v到G中其他顶点的最短路径。Dijkstra算法的具体步骤如下:(1)初始时,S只包含源点,即S={v},v的距离为0。
77 15
|
3月前
|
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
86 12
|
3月前
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
77 10
|
3月前
|
【C++数据结构——图】最小生成树(头歌实践教学平台习题) 【合集】
【数据结构——图】最小生成树(头歌实践教学平台习题)目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果:【合集】任务描述 本关任务:编写一个程序求图的最小生成树。相关知识 为了完成本关任务,你需要掌握:1.建立邻接矩阵,2.Prim算法。建立邻接矩阵 上述带权无向图对应的二维数组,根据它建立邻接矩阵,如图1建立下列邻接矩阵。注意:INF表示无穷大,表示整数:32767 intA[MAXV][MAXV];Prim算法 普里姆(Prim)算法是一种构造性算法,从候选边中挑
53 10
|
3月前
|
【C++数据结构——图】图的邻接矩阵和邻接表的存储(头歌实践教学平台习题)【合集】
本任务要求编写程序实现图的邻接矩阵和邻接表的存储。需掌握带权有向图、图的邻接矩阵及邻接表的概念。邻接矩阵用于表示顶点间的连接关系,邻接表则通过链表结构存储图信息。测试输入为图的顶点数、边数及邻接矩阵,预期输出为Prim算法求解结果。通关代码提供了完整的C++实现,包括输入、构建和打印邻接矩阵与邻接表的功能。
71 10
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等