【C++数据结构】智能指针的构建
@TOC
一、构建智能指针的原因
众所周知,C++
是没有垃圾回收的,就会导致以下问题:
1、导致动态申请堆空间,用完后不归还
。
2、会导致程序的内存泄露
,进而影响整个程序,甚至可能是整个电脑
3、指针无法控制
所指堆空间的生命周期
我们就需要设计一个类SmartPointer
他的功能如下:
- 指针生命周期结束时
主动释放堆空间
。 - 一片堆空间最多由一个指针标识。
原因:因为我们要在析构函数中释放堆空间,如果2个智能指向同一片空间,就会导致他被释放2次,就会得到意想不到的结果!!! 杜绝
指针运算和指针比较。
二、智能指针分析
通过类模板描述指针的行为。
在之前的第一节课我们已经讲过了,可以使用类模板来加强我们的数据结构的复用,在这里我们也是使用类模板来定义我们的SmartPointer
使他能够定义不同类型的指针对象重载指针特征操作符(
->
和*
)。
利用对象模拟原生指针的行为
三、实现智能指针
数据结构使用的编译器
我使用的编译器是Qt5.9.4,操作系统环境为Ubuntu 22,如果大家使用的是vs/qt(其他版本),或是其他操作系统,我都会讲他们函数的不同地方,例如函数名称的不同!!!
创建Qt控制台项目
1、在侧边栏打开qt creator
如果在侧边栏未找到怎么办?
那就进行下列操作:
在侧边栏找到点的按钮
,点击他。
在上方搜索qt,下面的这个应用就是qt,点击打开他。
2、创建新的项目
进入之后点击New Project
之后选择第二个。
之后一路Choose
即可,注意在此步的下一步是选择路径的,路径不能有中文
进入之后就是这样的:
智能指针的构建
点击我们的项目,创建新的文件:
选择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;
}
拷贝构造函数和"="重载操作符
具体的过程如图所示
拷贝构造函数:
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)