目录
引入
C++最重要的特性之一就是代码重用,为 了实现代码重用,代码必须具有通用性。 通用代码需要不受数据类型的影响,并且可以自动适应数据类型的变化。这种程序设计类型称为参数化程序设计。模板是C++支持参数化程序设计的工具,通过它可以实现参数化多态性。所谓参数化多态性,就是将程序所处理的对象的类型参数化,使得一段程序可以用于处理多种不同类型的对象。
一、函数模板
我们上一次学习的是函数重载,可以看出重载函数通常是对于不同的数据类型完成类似的操作。但如果这两个函数只有参数类型不同,功能完全一样,这时如果能写一段通用代码适用于多种不同数据类型,便会使代码的可重用性大大提高,从而提高软件的开发效率;使用函数模板就是为了这一目的。程序员只需对函数模板编写一次,然后基于调用函数时提供的参数类型,C++编译器将自动产生相应的函数来正确的处理该类型的数据。
函数模板的定义形式:
template<typename Type>
return_type fuc_name(parameter list)
{
//函数具体实现
}
<>里的内容是由用逗号分隔的模板参数构成,typename可以接受一个类型参数,也可以用class关键字替代。
一般模板函数
我们可以通过一个简单的compare函数来体会模板函数的神奇之处:
template <class Type>
int compare(const Type& x1, const Type& x2)
{
if (x1 < x2)
return -1;
if (x1 > x2)
return 1;
return 0;
}
模板函数只能在.h头文件中。定义编译器从实参的类型推导出函数模板的类型参数。例如,对于调用表达式compare(a,b),由于实参n为int类型,所以推导出模板中类型参数Type为int;对于调用表达式compare(a,b),由于实参n为double类型,所以推导出模板中类型参数Type为double。当类型参数的含义确定后,编译器将以函数模板为样板,生成一个函数,这一过程称为函数模板的实例化。
main.cpp
int main()
{
cout << compare(6, 6)<<endl;
cout << compare(6.11,6.22)<<endl;
cout << compare(6.1,6.0)<<endl;
return 0;
}
输出:
0
-1
1
特化模板函数
有时,遇到某些特定的参数类型我们又需要有另外不同的函数实现,来看一个不理想的结果:
int main()
{
const char* a = "b";
const char* b = "a";
cout << compare(a, b) << endl;
cout << compare(6, 6)<<endl;
cout << compare(6.11,6.22)<<endl;
cout << compare(6.1,6.0)<<endl;
return 0;
}
输出:
-1
0
-1
1
很明显,字符串的对比并不是正确的结果;实际上程序是将两个字符串的地址进行对比,这并不是我们想要的对比方式。这时应该使用特化模板函数来实现:
template<>
int compare(const char* const& x1, const char* const& x2);
template<>
int compare<const char*>(const char* const& x1, const char* const& x2)
{
return strcmp(x1, x2);
}
int main()
{
const char* a = "b";
const char* b = "a";
cout << compare(a, b) << endl;
cout << compare(6, 6)<<endl;
cout << compare(6.11,6.22)<<endl;
cout << compare(6.1,6.0)<<endl;
return 0;
}
输出:
1
0
-1
1
得到了我们期望的结果。
二、模板类Queue
使用类模板使用户可以为类定义-种模式,使得类中的某些数据成员、某些成员函数的参数、返回值或局部变量能取不同类型(包括系统预定义的和用户自定义的)。类是对一组对象的公共性质的抽象,而类模板则是对不同类的公共性质的抽象,因此类模板是属于更高层次的抽象。由于类模板需要一种或多种类型参数,所以类模板也常常称为参数化类。当然,模板参数的实参也不总是可以用任何类型的。
类模板的定义形式:
template<class Type>
class class_name
{
//类的具体实现
}
模板类
下面用一个Queue实例来了解一下类模板:
#ifndef QUEUE_H
#define QUEUE_H
#include <string>
#include <iostream>
using namespace std;
template<class Type> class Queue;
template<class Type>
class QueueItem
{
//构造器初始化,这种初始化效率更高
QueueItem(const Type& t) :item(t), next(0) {};
//队列里的元素
Type item;
//队列里实例块的指针
QueueItem* next;
//友元类Queue
friend class Queue<Type>;
//输出运算符重载
friend ostream& operator<<(ostream& os, const Queue<Type>& q);
public:
//++运算符重载:指针的地址++
QueueItem<Type>* operator++()
{
return next;
}
//*取值运算符重载:返回队列实例块中的元素
Type& operator*()
{
return item;
}
};
template<class Type>
class Queue
{
private:
//头指针
QueueItem<Type>* head;
//尾指针
QueueItem<Type>* tail;
//销毁
void destroy();
public:
//无参构造器且初始化
Queue() :head(0), tail(0) {};
//构造一个队列 怎么构造:拷贝已有的整段Queue
Queue(const Queue& q) :head(0), tail(0) { copy_items(q); }
//构造一个队列 怎么构造:切片拷贝
template<class It>Queue(It begin, It end) : head(0), tail(0)
{
copy_items(begin, end);
}
//拷贝整段队列,加在原有的队列后,但不能自己拷贝自己
void copy_items(const Queue&);
//切片拷贝,加在原有的队列后
template<class It> void copy_items(It begin, It end);
//切片拷贝,回删原有队列
template<class It> void assign(It begin, It end);
//重载赋值运算符
void operator=(const Queue&);
//获取头指针的元素
const Type& front() const{ return head->item; }
// 获取头指针
const QueueItem<Type>* Head() const { return head; }
// 获取尾指针
QueueItem<Type>* End() { return (tail == NULL) ? NULL : tail; }
//析构函数
~Queue() { destroy(); }
//进队
void push(const Type&);
//出队
void pop();
//判断队列是否为空
bool empty()const
{
return head == 0;
}
//重载输出运算符
friend ostream& operator<<(ostream& os, const Queue<Type>& q)
{
os << "[";
//创建Queue里实例块指针
QueueItem<Type>* p;
//循环输出Queue里的元素
for (p = q.head; p; p = p->next) {
os << p->item << " ";
}
os << "]\n";
return os;
}
};
#endif // !QUEUE_H
模板类QueueItem是队列里的实例块,Queue是队列实例。
成员模板函数
类模板以外定义其成员函数,则要采用以下形式:
template<class Type>
return_type class_name<type>::fuc_name(parameter list)
{
//函数具体实现
}
我们来看一下具体的实例:
//销毁
template<class Type>
void Queue<Type>::destroy()
{
//一直重复出队列的动作,直到队列为空
while (!empty()) {
pop();
}
}
//元素进队列
template<class Type>
void Queue<Type>::push(const Type& val)
{
//创建元素为val的实列块
QueueItem<Type>* pt = new QueueItem<Type>(val);
//若队列为空,则新进入的实例块既是头指针也是尾指针
if (empty()) {
head = tail = pt;
}
else {
//新进入的实例块放在尾指针后,尾指针指向它
tail->next = pt;
tail = pt;
}
}
//元素出队列
template<class Type>
void Queue<Type>::pop()
{
//先进先出
//找到头指针
QueueItem<Type>* p = head;
head = head->next;
//删除p
delete p;
}
//拷贝整段队列
template<class Type>
void Queue<Type>::copy_items(const Queue& orig)
{
//简单的找到头指针、next、push
for (QueueItem<Type>* pt = orig.head; pt; pt = pt->next) {
push(pt->item);
}
}
//切片拷贝,加在原有的队列后
template<class Type>
template<class It>
void Queue<Type>::copy_items(It begin, It end)
{
while (begin != end)
{
push(*begin);
++begin;
}
}
//切片拷贝,回删原有的队列
template<class Type>
template<class It>
void Queue<Type>::assign(It begin, It end)
{
//先回删
destroy();
//再切片拷贝
copy_items(begin, end);
}
template<class Type>
void Queue<Type>::operator=(const Queue& q)
{
//要先判断是不是本身,不然是本身的话会出现逻辑错误
if (this != &q) {
//删除原队列
destroy();
//再拷贝另外的整段队列
copy_items(q);
}
}
这些我都是放在头文件里实现的。
main函数:
#include"Cmb.h"
#include"Queue.h"
#include<iostream>
using namespace std;
template<>
int compare<const char*>(const char* const& x1, const char* const& x2)
{
return strcmp(x1, x2);
}
int main()
{
//const char* a = "b";
//const char* b = "a";
//cout << compare(a, b) << endl;
//cout << compare(6, 6)<<endl;
//cout << compare(6.11,6.22)<<endl;
//cout << compare(6.1,6.0)<<endl;
//这个队列q1是int类型,输入遇到其他类型的元素,会强制转换为int
Queue<int> q1;
double a = 6.66;
q1.push(1);
q1.push(a);
q1.push(3);
cout << "q1:" << q1;
//构造一个队列q2 怎么构造:拷贝已有的整段Queue q1
Queue<int> q2(q1);
cout << "q2:" << q2;
//拷贝整段队列q3,加在原有的队列q2后,但不能自己拷贝自己
Queue<int> q3;
q3.push(1);
q3.push(2);
q3.push(3);
cout << "q3:" << q3;
q2.copy_items(q3);
cout << "q2:" << q2;
//构造一个队列q4 怎么构造:切片拷贝已有数组
double num[8] = { 1.1,2.2,-3.3,4.4,-5.5,6.66 };
Queue<double> q4(num, num + 8);
cout << "q4:" << q4;
//切片拷贝,加在原有的队列q2、q4后
q2.copy_items(num, num + 6);
q4.copy_items(num+1, num + 5);
cout << "q2:" << q2;
cout << "q4:" << q4;
//切片拷贝,回删原有队列
q4.assign(num + 1, num + 6);
cout << "q4:" << q4;
//重载后的赋值运算符
Queue<double> q5(num, num + 1);
q4 = q5;
cout << "q5:" << q5;
cout << "q4:" << q4;
//QueueItem<double>* c = q5.End();
//cout << c;
return 0;
}
输出:
q1:[1 6 3 ]
q2:[1 6 3 ]
q3:[1 2 3 ]
q2:[1 6 3 1 2 3 ]
q4:[1.1 2.2 -3.3 4.4 -5.5 6.66 0 0 ]
q2:[1 6 3 1 2 3 1 2 -3 4 -5 6 ]
q4:[1.1 2.2 -3.3 4.4 -5.5 6.66 0 0 2.2 -3.3 4.4 -5.5 ]
q4:[2.2 -3.3 4.4 -5.5 6.66 ]
q5:[1.1 ]
q4:[1.1 ]
模板特化
我们来看一个不理想的结果:
mian.cpp
Queue<const char*> qst;
char str[10];
strcpy(str, "I'm");
qst.push(str);
strcpy(str, "Hanyan");
qst.push(str);
strcpy(str, "Wei");
qst.push(str);
cout << qst;
在使用strcpy()函数时会报错函数存在隐藏危险,只需要在在预处理定义中添加:_CRT_SECURE_NO_WARNINGS
输出:
[Wei Wei Wei ]
这并不是我们想要的结果,因为我们Queue里面存的是str的指针 而最后的str存的是Wei 所以qst存的也一直是Wei,对于类模板的push操作,一般的非指针类型,push方法会自动开辟一个新的空间,因此不必考虑地址重合的问题,但是,这个Queue存的是一个指针类型,就出现了问题。
模板成员函数特化
Queue里存的始终都是str最初的地址空间,因此对于这种指针类型的元素,我们需要对push函数进行模板成员函数特化处理:
template<>
void Queue<const char*>::push(const char* const& val)
{
//创建元素为val的实列块
//这里本来存的const char*指针
//QueueItem<const char*>* pt = new QueueItem<const char*>(val);
//但是我们现在想要的是指针所指向的字符串,那我们就需要为每次指针指向的字符串申请新空间
char* pData = new char[strlen(val) + 1];
//然后把每次指针所指向的字符串复制到申请新空间
strcpy(pData, val);
//存它
QueueItem<const char*>* pt = new QueueItem<const char*>(pData);
//若队列为空,则新进入的实例块既是头指针也是尾指针
if (empty()) {
head = tail = pt;
}
else {
//新进入的实例块放在尾指针后,尾指针指向它
tail->next = pt;
tail = pt;
}
}
template<>
void Queue<const char*>::pop()
{
//先进先出
//找到头指针
QueueItem<const char*>* p = head;
head = head->next;
//因为现在不仅存了指针,还另外申请空间存字符串,所以也要另外删除
delete[]p->item;
//删除p
delete p;
}
同样的,这需要在main.cpp中实现
输出:
[I'm Hanyan Wei ]
是我们想要的结果。
类模板特化
这是另外的一种方法——类模板Queue的全特化:实际储存的不是cosnt char*指针,而是string类型
template<>
class Queue<const char*>
{
public:
//进队
void push(const char* const& val)
{
//push函数会把 const char* 类型强制转换为string
real_que.push(val);
}
//出队
void pop()
{
real_que.pop();
}
//判断队列是否为空
bool empty()const
{
return real_que.empty();
}
//获取头指针的元素
const string & front() const { return real_que.front(); }
//重载输出运算符
friend ostream& operator<<(ostream& os, const Queue<const char*>& q)
{
//输出const char* 队列的成员变量real_que
os << q.real_que;
return os;
}
private:
//实际存储的数据类型是string
Queue<string> real_que;
};
同样的,这需要在main.cpp中实现
输出:
[I'm Hanyan Wei ]
是我们想要的结果。
三、智能指针(模板类AutoPtr)
我们知道除了静态内存和栈内存外,每个程序还有一个内存池,这部分内存被称为*空间或者堆。程序用堆来存储动态分配的对象即那些在程序运行时分配的对象,当动态对象不再使用时,我们的代码必须显式的销毁它们;所以,动态内存管理经常会出现两种问题:一种是忘记释放内存,会造成内存泄漏;一种是尚有指针引用内存的情况下就释放了它,就会产生引用非法内存的指针。为了更加容易(更加安全)的使用动态内存,引入了智能指针的概念。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象;它能自动判别所指向的内存是否还有常规指针指向它,如果没有,则表示该片内存的生命周期已结束,该片内存自动释放。
其中,有三种智能指针类型:
shared_ptr:允许多个指针指向同一个对象
unique_ptr:“独占”所指向的对象
weak_ptr:它是一种弱引用,指向shared_ptr所管理的对象,与shared_ptr同用,不影响对象生命周期
智能指针会有一个计数器用来代表当前“用户数”也就是判断当前地址是否有常规指针指向它,但”用户数“为0时,这块地址就要被释放了
用一个模板类AutoPtr实例来了解一下智能指针:
构造函数
//指针构造器 初始化为指向pData
template<class T>
AutoPtr<T>::AutoPtr(T* pData)
{
//new一个ptr指针初始化为指向pData
ptr = pData;
//new一个user初始化为1
user = new int(1);
}
拷贝构造函数
//指针拷贝构造器 怎么构造:拷贝已有的AutoPtr
template<class T>
AutoPtr<T>::AutoPtr(const AutoPtr<T>& handle)
{
//ptr初始化为指向handle数据的指针
ptr = handle.ptr;
//user初始化为指向handle用户数的指针
user = handle.user;
(*user)++;
}
析构函数
//析构函数 如果自己释放了,那自己指向的数据地址的当前地址的指针以及用户数减一
template<class T>
AutoPtr<T>::~AutoPtr()
{
cout << "智能指针" << this << "已销毁" << endl;
decr();
}
赋值运算符重载
//当前地址的指针以及用户数减一
template<class T>
void AutoPtr<T>::decr()
{
//用户数减一
(*user)--;
if ((*user) == 0) {
delete ptr;
//ptr地址赋为空
ptr = 0;
delete user;
//user地址赋为空
user = 0;
cout << "智能指针" << this << "原先指向的地址已经没有指针指向它了 该地址内存释放" << endl;
}
}
//重载赋值运算符 表示指针改指为handle这个数据
template<class T>
AutoPtr<T>& AutoPtr<T>:: operator=(const AutoPtr<T>& handle)
{
//如果handle是本身 则返回本身
if (this == &handle) return *this;
//ptr指向的原数据地址的指针以及用户数减一
decr();
//ptr改为指向handle数据的指针
ptr = handle.ptr;
//user改为指向handle用户数的指针
user = handle.user;
(*user)++;
return *this;
}
->、*等运算符重载
//重载-> 返回指针 代表对智能指针指向的原始数据进行操作,而不是操作智能指针本身
template<class T>
T* AutoPtr<T>::operator->()
{
return ptr;
}
//重载* 返回指针所指的对象 代表对智能指针所指的对象里的数据进行操作,而不是操作对象
template<class T>
T& AutoPtr<T>::operator*()
{
return *ptr;
}
完整AutoPtr.h
#ifndef AUTOPRT_H
#define AUTOPRT_H
#include<iostream>
using namespace std;
template<class T>
class AutoPtr
{
public:
//指针构造器 初始化为指向pData
AutoPtr(T* pData);
//指针拷贝构造器 怎么构造:拷贝已有的AutoPtr
AutoPtr(const AutoPtr<T>& handle);
//重载赋值运算符 表示指针改指为handle这个数据
AutoPtr<T>& operator=(const AutoPtr<T>& handle);
//当前地址的指针以及用户数减一
void decr();
//析构函数 如果自己释放了,那自己指向的数据地址的当前地址的指针以及用户数减一
~AutoPtr();
//重载-> 返回指针 代表对智能指针指向的对象进行操作,而不是操作智能指针本身
T* operator->();
const T* operator->() const { return ptr; }
//重载* 返回指针所指的对象 代表对智能指针所指的对象里的数据进行操作,而不是操作对象
T& operator*();
const T& operator*() const { return *ptr; }
private:
//指向储存数据的指针
T* ptr;
//用户数 带*代表这个user是统一指向存储用户数的地址,保证了用户数的统一修改
int* user;
};
//指针构造器 初始化为指向pData
template<class T>
AutoPtr<T>::AutoPtr(T* pData)
{
//new一个ptr指针初始化为指向pData
ptr = pData;
//new一个user初始化为1
user = new int(1);
}
//指针拷贝构造器 怎么构造:拷贝已有的AutoPtr
template<class T>
AutoPtr<T>::AutoPtr(const AutoPtr<T>& handle)
{
//ptr初始化为指向handle数据的指针
ptr = handle.ptr;
//user初始化为指向handle用户数的指针
user = handle.user;
(*user)++;
}
//当前地址的指针以及用户数减一
template<class T>
void AutoPtr<T>::decr()
{
//用户数减一
(*user)--;
if ((*user) == 0) {
delete ptr;
//ptr地址赋为空
ptr = 0;
delete user;
//user地址赋为空
user = 0;
cout << "智能指针" << this << "原先指向的地址已经没有指针指向它了 该地址内存释放" << endl;
}
}
//重载赋值运算符 表示指针改指为handle这个数据
template<class T>
AutoPtr<T>& AutoPtr<T>:: operator=(const AutoPtr<T>& handle)
{
//如果handle是本身 则返回本身
if (this == &handle) return *this;
//ptr指向的原数据地址的指针以及用户数减一
decr();
//ptr改为指向handle数据的指针
ptr = handle.ptr;
//user改为指向handle用户数的指针
user = handle.user;
(*user)++;
return *this;
}
//析构函数 如果自己释放了,那自己指向的数据地址的当前地址的指针以及用户数减一
template<class T>
AutoPtr<T>::~AutoPtr()
{
cout << "智能指针" << this << "已销毁" << endl;
decr();
}
//重载-> 返回指针 代表对智能指针指向的原始数据进行操作,而不是操作智能指针本身
template<class T>
T* AutoPtr<T>::operator->()
{
return ptr;
}
//重载* 返回指针所指的对象 代表对智能指针所指的对象里的数据进行操作,而不是操作对象
template<class T>
T& AutoPtr<T>::operator*()
{
return *ptr;
}
#endif // !AUTOPRT_H
main.cpp
//指针构造器 新建一个智能指针h1 指向一个CMatrix对象
AutoPtr<CMatrix> h1(new CMatrix);
AutoPtr<CMatrix> h2(new CMatrix);
AutoPtr<CMatrix> h3(new CMatrix);
cout << "h1:" << &h1 << endl;
cout << "h2:" << &h2 << endl;
cout << "h3:" << &h3 << endl;
//拷贝指针构造器 新建一个智能指针h2 指向h1指向的CMatrix对象
AutoPtr<CMatrix> h4(h1);
cout << "h4:" << &h4 << endl;
cout << "=========================" << endl;
double data1[6] = { 1,2,3,4,5,6 };
double data2[6] = { 7,8,9,10,11,12 };
double data3[2] = { 7,8 };
//h1->重载后代表h1智能指针指向的对象
h1->Create(2, 3, data1);
h2->Create(2, 3, data2);
h3->Create(1, 2, data3);
cout << "h1: " << *h1 << endl;
cout << "h2: " << *h2 << endl;
cout << "h3: " << *h3 << endl;
cout << "h4: " << *h4 << endl;
cout << "=========================" << endl;
//*h4代表h4指向的地址所存的对象的数据 因为h1和h4指向的是统一内存地址 所以h4指向的对象也是h1所指的对象
(*h4).Set(0, 1, 10);
cout << "h1: " << *h1 << endl;
cout << "h2: " << *h2 << endl;
cout << "h3: " << *h3 << endl;
cout << "h4: " << *h4 << endl;
cout << "=========================" << endl;
//把h1指向的内存地址赋给h3 h3就指向了h1的指向 h3原先指向的内存地址就没有指针指向了 所以会出现释放内存
h3 = h1;
h1 = h2;
cout << "h1: " << *h1 << endl;
cout << "h2: " << *h2 << endl;
cout << "h3: " << *h3 << endl;
cout << "h4: " << *h4 << endl;
cout << "--------------------------------" << endl;
输出:
h1:00DEFE14
h2:00DEFE04
h3:00DEFDF4
h4:00DEFDE4
=========================
h1: 2 3
1 2 3
4 5 6
h2: 2 3
7 8 9
10 11 12
h3: 1 2
7 8
h4: 2 3
1 2 3
4 5 6
=========================
h1: 2 3
1 10 3
4 5 6
h2: 2 3
7 8 9
10 11 12
h3: 1 2
7 8
h4: 2 3
1 10 3
4 5 6
=========================
智能指针00DEFDF4原先指向的地址已经没有指针指向它了 该地址内存释放
h1: 2 3
7 8 9
10 11 12
h2: 2 3
7 8 9
10 11 12
h3: 2 3
1 10 3
4 5 6
h4: 2 3
1 10 3
4 5 6
--------------------------------
智能指针00DEFDE4已销毁
智能指针00DEFDF4已销毁
智能指针00DEFDF4原先指向的地址已经没有指针指向它了 该地址内存释放
智能指针00DEFE04已销毁
智能指针00DEFE14已销毁
智能指针00DEFE14原先指向的地址已经没有指针指向它了 该地址内存释放
让我来解释一下这个输出是怎么回事:
下面一段对应输出的第一段,h1、h2、h3分别指向三片不同的内存地址,由拷贝指针构造函数构造出的h4拷贝于h1,所以h1和h4指向同一片内存地址。
//指针构造器 新建一个智能指针h1 指向一个CMatrix对象
AutoPtr<CMatrix> h1(new CMatrix);
AutoPtr<CMatrix> h2(new CMatrix);
AutoPtr<CMatrix> h3(new CMatrix);
cout << "h1:" << &h1 << endl;
cout << "h2:" << &h2 << endl;
cout << "h3:" << &h3 << endl;//拷贝指针构造器 新建一个智能指针h2 指向h1指向的CMatrix对象
AutoPtr<CMatrix> h4(h1);
cout << "h4:" << &h4 << endl;
下面一段对应输出的第二段、第三段,主要操作是,对h4指向的对象的数据进行修改,由于h1和h4指向同一片内存地址,所以对h4指向的对象的数据进行修改就是所以对h1指向的对象的数据进行修改
double data1[6] = { 1,2,3,4,5,6 };
double data2[6] = { 7,8,9,10,11,12 };
double data3[2] = { 7,8 };
//h1->重载后代表h1智能指针指向的对象
h1->Create(2, 3, data1);
h2->Create(2, 3, data2);
h3->Create(1, 2, data3);
cout << "h1: " << *h1 << endl;
cout << "h2: " << *h2 << endl;
cout << "h3: " << *h3 << endl;
cout << "h4: " << *h4 << endl;cout << "=========================" << endl;
//*h4代表h4指向的地址所存的对象的数据 因为h1和h4指向的是统一内存地址 所以h4指向的对象也是h1所指的对象
(*h4).Set(0, 1, 10);
cout << "h1: " << *h1 << endl;
cout << "h2: " << *h2 << endl;
cout << "h3: " << *h3 << endl;
cout << "h4: " << *h4 << endl;cout << "=========================" << endl;
下面一段对应输出的第四段,主要操作就是让h3指向h1和h4指向的那片内存地址,h3原先指向的内存地址就没有指针指向了,那片内存地址释放内存;再让h1指向h2指向的那片内存地址,所以最后的输出h1和h2一样,h3和h4一样:
//把h1指向的内存地址赋给h3 h3就指向了h1的指向 h3原先指向的内存地址就没有指针指向了 所以会出现释放内存
h3 = h1;
h1 = h2;
cout << "h1: " << *h1 << endl;
cout << "h2: " << *h2 << endl;
cout << "h3: " << *h3 << endl;
cout << "h4: " << *h4 << endl;
cout << "--------------------------------" << endl;
最后那段输出就是智能指针的智能之处,程序结束:h3、h4指针销毁,h3、h4原先指向的内存地址就没有指针指向了,那片内存地址释放内存;h1、h2指针销毁,h1、h2原先指向的内存地址就没有指针指向了,那片内存地址释放内存。