指针中的战斗机---智能指针!!!

指针是C语言中的精髓,智能指针是C++中的王炸!

温故知新,可以为师。在开启智能指针学习篇章前,先来探望老朋友—C语言中的精髓—指针。指针的本质是变量,何为变量,变量就是用特定的数据类型做的mooncake,可大可小,例如有 char、int 、short、double等基础类型,还有就是变量在程序运行时可以被改变。
照本宣科,拿课本里的一句话,“指针变量用于保存变量的地址,通过所保存的地址操作变量。” 这看似简单的一句话,被难倒的初学者不计其数。所以有必要用标新立异的形象比喻来阐明其本质,指针和所指向的变量之间的关系类似于邮差与信箱,邮差知道了信箱的具体位置,就可以往信箱塞信或者拿信。

#include<stdio.h>

int main(int argc,char* argv[])
{
	int a = 1;
	int b = 0;
	
	int* p = &a;	// 取地址操作,指针 p 指向了变量 a

	*p = 2;		// (寄信)相当于 a = 2;运算过后 a 的值为 2;
	
	b = *p + 3;	// (取信)等价于 b = a + 3; 即 a 加上3再赋值给 b
			// 运算过后 b = 5;
	return 0;
}

信随意拿,也随意放,有没有觉得这个邮差权力好像有点大啊 !其实,我想说指针的能力真的是太强大了,但是,有无相生,难易相成,任何事物都有两面性,指针也不例外,如果滥用指针随意修改变量值,可能会导致系统崩溃!!!
为了解决一系列的问题,于是乎,C语言就生产了指针常量,常量指针,常量指针常量,初学者大概率搞不明白说的是什么。话不多说,先上代码!

int b = 1;

const int* p1 = &b;

int* const p2 = &b;

int const* p3 = &b;

const int* const p4 = &b; 

指针p1、p2、p3、p4都指向了同一个变量,坦白说,p1指向的内容不可变,p2指向的地址不可变,p3指向的内容不可变,p4指向的内容和地址都不可变。记住一句话就可以了,左数据右地址。
有时候指针很复杂,例如:

int*(*(*p)(int*))(int*)int (*(*p)(int*))[10]

乍一看还以为是乱码,这样的代码除了吓人之外,没半毛钱用。使用指针的时候,尽量避开形式过于复杂的指针。

另外,指针最大的危害在于内存泄漏,什么是内存泄漏(Memory Leakage),最为恰当的比喻,就是借钱不还。

//C语言程序

#include <stdio.h>
#include <malloc.h>

int main(int argc,char* argv[])
{
	int c = 0;

	int* p = (int* )malloc(sizeof(int));  //借钱了
	*p = 10;
	
	c = *p;
	printf("%d\n",c);

	return 0;	// 没还钱(没有free)
}
//C++程序

#include <iostream>

using namespace std;

int main(int argc,char* argv[])
{
	int* p = new int(10);	// 借钱了
	int c = 0;
	
	c = *p;
	cout << c << endl;
	
	return 0;		// 没还钱(没有delete)
}

如此简短的程序,想必读者一定洞若观火,有错是很明显的,解决也是轻而易举,分分钟的事情。但当置身于成千上万行代码的海洋中,无助地感叹,头发又少了!
其实,还钱,90%都是因为忘记了,哈哈对不对?所以花呗自动还款挺好的。高级一点的语言如Java语言,就有内存回收机制,再不担心借钱不还了。虽然C++中没有这样优秀的机制,但是,我们可以充分利用现有的资源来实现目标。很明显我们需要人工的智能,C++中有两个特殊的无返回值的函数会被编译器自动调用,那就是构造函数和析构函数,如果在构造函数(借钱)向堆空间申请内存,在析构函数中(还钱)释放申请的内存不就完美解决问题了吗?
先回顾一下,构造函数与析构函数的用法。

//C++
#include <iostream>
#include <string>

using namespace std;

class Employee
{
private:
	string m_name;
public:
	Employee(const string& name)	// 构造函数
	{
		m_name = name;
		cout << m_name << endl;
	}
	~Employee()			// 析构函数
	{
		cout << m_name << endl;
	}
};

int main(int argc,char* argv[])
{
	Employee ep1("xiaoming");  // 创建了一个对象,构造函数自动被调用

	return 0;		   // 程序结束时,析构函数被调用 
}

要实现智能指针,还有一个关键要素,那就是操作符重载,简单来说就是把系统中预定义操作符如+ - * / = 等进行重载,当然啦,在C++中还有另外一个重要的重载概念,函数重载,函数重载有待下回分解。指针操作符就两个,* , -> 。激动人心的时刻来了,待我疾速实现一个智能指针。

#include <iostream>
#include <string>

using namespace std;

class Test	// 实际要操作的指针对象
{
private:
	string m_name;
public:
	Test(const string s)	// 构造函数里输出调试信息
	{
		m_name = s;
		cout << "My name is " << m_name << endl;
	}
	
	Test(const Test& obj)
	{
		m_name = obj.m_name;
	}
	
	Test& operator = (const Test& obj) // 赋值符重载,返回对象的引用,目的是不改变赋值符原义
	{
		if( this != &obj )	// 判断是否为同一个对象
		{
			m_name = obj.m_name;
		}
	}
	
	void print_name()
	{
		cout << m_name << endl;
	}
	
	~Test()
	{	
		cout << "Goodbye " << m_name << endl;
	}	// 析构函数也输出调式信息
};

class Smart_pointer	// 真正的智能指针
{
private:
	Test* pt;	// 把要使用对象作为私有成员
public:
	Smart_pointer(Test* p = NULL)// 使用时在堆空间创建一个对象作为参数
	{
		pt = p; 
	}
	
	Smart_pointer(const Smart_pointer& obj)
	{
		this->pt = obj.pt;	// 堆空间使用权力的转移
		const_cast<Smart_pointer&>(obj).pt = NULL; // 真正的转移,把之前的置空
	}
	
	Smart_pointer& operator = (const Smart_pointer& obj)
	{
		if( this != &obj )
		{
			this->pt = obj.pt;
			const_cast<Smart_pointer&>(obj).pt = NULL;
		}
	}
	
	Test* operator -> ()	// 指针操作符重载,返回对象的地址
	{
		return pt;
	}
	
	Test& operator * ()	// 指针操作符重载,返回要使用的对象,并且不改变其原义
	{
		return *pt;
	}
	
	bool isNULL()	// 判断指针是否为空
	{
		return ( this == NULL );
	}
	
	~Smart_pointer()
	{
		delete pt;	// 析构函数中释放堆空间的内容
	}			// 自动内存管理的重要手段
	
};

int main(int argc,char* argv[])
{
	Smart_pointer spt(new Test("Sophire"));

	spt->print_name();	// 使用方式与指针相同
	
	Test t(*spt);

	return 0;
}

天马行空,非常巧妙地利用了C++中的优秀特性进而实现了智能指针。智能指针是大型C++程序中自动内存管理的重要手段,例如Android的framework层就大量使用智能指针,还有Qt平台也提供了QPointer类,STL中也有丰富的智能指针类提供,比如auto_ptr。

上面的智能指针太过专一,问题很大。要是每一个对象都创建一个智能指针类对象来封装,那得多少的代码量,而且绝大部分都是用CTRL+C和CTRL+V,没什么技术含量啊。Well,是时候引入新技能了-------大名鼎鼎的模板(template),将模板一分为二,各自占山为王,一个叫函数模板,另一个叫类模板,很明显智能指针类毫无疑问是用类模板技术,函数模板下回讲解,现在就对类模板一探究竟吧。。。等等等,等,猛攻不如巧夺,先速来回顾一下有异曲同工之妙的宏定义。


#include <iostream>
			// 使用宏定义并不需要考虑参数类型
#define Compare(a,b)		\
{				\
	a > b ? a : b;		\
}				

using namespace std;

int main(int argc,char* argv[])
{
	int a = 1;
	int b = 2;
	double c = 3;
	double d = 4;
	
	int x = Compare(a,b);	    // 可以处理int类型
	double y = Compare(c,d);    // 可以处理double类型

	cout << "x = " << x << endl;
	cout << "y = " << y << endl;
	
	return 0;
}

宏定义天生强大,很多Hackers都倾向于使用宏定义,因为其速度极快,不需要函数调用栈的开销,但是宏的缺点也是致命的,宏定义是由预处理器处理的单元,编译器并不知道宏的存在,所以并不会对其进行语法检查。我们需要的是更安全的方法------模板。直接上代码!

//SmartPointer.h

#ifndef _SMARTPOINTER_H_
#define _SMARTPOINTER_H_

template	// 模板技术,代码复用的重要手段
<typename T>	// 泛型编程,使用类模板
class SmartPointer
{
private:	
	T* mp;	// T代表了使用时指定的类型
public:
	SmartPointer(T* p = NULL)
	{
  		mp = p;
 	}
	SmartPointer(const SmartPointer<T>& obj)
	{
  		mp = obj.mp;
  		const_cast<SmartPointer<T>&>(obj).mp = NULL;
 	}
  	SmartPointer& operator = (const SmartPointer<T>& obj)
 	{
  		if (this != &obj)
  		{
    			delete mp;
   			mp = obj.mp;
   			const_cast<SmartPointer<T>&>(obj).mp = NULL;
  		}
  		
    		return *this;
	}
	
 	T* operator -> ()
 	{
  		return mp;
 	}
 	
  	T& operator * ()
 	{
 		 return *mp;
 	}
 	
 	T* get()
 	{
  		return mp;
 	}
 	
 	bool isNULL()
 	{
  		return (mp == NULL);
 	}
 	
 	~SmartPointer()
 	{
  		delete mp;
 	}
};

#endif

类模板中的函数也可以在类外实现,不过同样需要加上模板的声明。此处不做详解。
现在可以来使用类模板了,实践出真知,动手吧!

//main.cpp

#include <iostream>
#include <string>
#include "SmartPointer.h"	// 包含对应的头文件

using namespace std;

class Test			// 示例类,可以有多个
{
private:
 	string m_name;
public:
	Test(const string name)
 	{
  		m_name = name;
  		cout << "Hello " << m_name << endl;
 	}
  	Test(const Test& obj)
 	{
  		cout << "Test(const Test& obj)" << endl;
  		this->m_name = obj.m_name;
 	}
 	void print()
 	{
  		cout << "I am " << m_name << endl;
 	}
  	~Test()
 	{
  		cout << "Goodbye " << m_name << endl;
 	}
};


int main(int argc, char const *argv[])
{	
 	SmartPointer<Test> pt(new Test("Sophire"));  // 在堆空间上创建Test类对象
 	
 	cout << "pt = " << pt.get() << endl;  // 调用pt对象中的函数,获取其所占用堆空间的地址
 	
	pt->print();	// 可以像类指针一样操作
	
	cout << endl;
	
	SmartPointer<Test> ptt(pt);	// 用刚才的对象初始化新创建的智能指针类对象,发生堆空间使用权力的转移,之前的对象将被置空
	
	cout << "pt = " << pt.get() << endl;	// 值为 NULL
 	cout << "ptt = " << ptt.get() << endl;
 	
	ptt->print();
	
	return 0;
}

 

学无止境,模板有太多的细节值得去深究,例如,模板有一种特殊的实现,模板的特化,这里面的内容相对深奥,知识点较多,读者有兴趣可以自行查阅资料。

1]: http://meta.math.stackexchange.com/questions/5020/mathjax-basic-tutorial-and-quick-reference
[2]: https://mermaidjs.github.io/
[3]: https://mermaidjs.github.io/
[4]: http://adrai.github.io/flowchart.js/

猜你喜欢

转载自blog.csdn.net/Dream136/article/details/104886583