指针是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;   // 值为 NULLcout << "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/

指针中的战斗机---智能指针!!!相关推荐

  1. 在你的代码中使用Boost智能指针

    在你的代码中使用Boost智能指针 Smart Pointers to boost your code(By peterchen)  翻译 masterlee Download source file ...

  2. 【C++ 语言】智能指针 引入 ( 内存泄漏 | 智能指针简介 | 简单示例 )

    文章目录 I . 智能指针 引入 II . 智能指针 简介 III . 智能指针 简单示例 I . 智能指针 引入 1 . 示例前提 : 定义一个 Student 类 , 之后将该类对象作为智能指针指 ...

  3. C++智能指针使用指南 part2:智能指针本身的方法以及使用建议

    目录 往期文章 智能指针本身的方法 对于unique_ptr 对于shared_ptr 对于weak_ptr 使用建议 1.使用工厂函数而非new构造对象 2.在类内部调用其他类的方法 3.在某类内部 ...

  4. C++智能指针(一)智能指针的简单介绍

    https://blog.csdn.net/nou_camp/article/details/70176949 C++智能指针  在正式了解智能指针前先看一下下面的一段代码 #include<i ...

  5. c语言智能指针是什么,C++ 智能指针深入解析

    1. 为什么需要智能指针?简单的说,智能指针是为了实现类似于Java中的垃圾回收机制.Java的垃圾回收机制使程序员从繁杂的内存管理任务中彻底的解脱出来,在申请使用一块内存区域之后,无需去关注应该何时 ...

  6. 智能指针(一)—— 智能指针的底层原理(RAII特性)

    我们使用 new关键字 或者 malloc函数 开辟一块空间时,因为这块空间是在堆上开辟的,如果不手动释放,即便出了作用域,这块空间也依然存在,这个时候就会造成内存泄漏. 为了保证资源的释放,我们可以 ...

  7. Android 智能指针 视频,Android系统智能指针中轻量级指针

    lp.sp.wp在Android Native层中被大量使用,所以非常有必要学习它们的实现原理.lp是Light Pointer的缩写,表示轻量级指针,sp是Strong Pointer的缩写,表示强 ...

  8. android wp指针使用方法,Android智能指针RefBase、sp、wp解析

    [TOC] 在Android系统中,Native层的代码基本都是C++写的,C++跟Java不一样,C++没有垃圾回收机制,C++代码中难于管理new出来对象的释放,稍有不慎就造成内存泄漏.针对此问题 ...

  9. C语言中结构体指针出现重定义,C语言结构体指针中包含结构体指针

    码农公社  210.net.cn  210是何含义?10月24日是程序员节,1024 =210.210既 210 之意. 将C++代码转化为C的过程中,一些C++的特性无法使用 类内私有变量在类内公共 ...

最新文章

  1. jpa分页查询_spring data jpa 居然提供了这么多查询方式!
  2. 线段树 ---- CF1004F Sonya and Bitwise OR(线段树上分治合并区间信息 + or 前缀和的log性质)
  3. docker容器-实战(分享十六)
  4. 改变客户端访问时的方法名
  5. access开发精要(2)-参照完整性
  6. springboot map数据类型注入_SpringBoot结合策略模式实战套路
  7. 网络学习(十二)文本模式安装Red Hat Linux 9
  8. hibernate 数据源配置文件
  9. C++中类的拷贝控制
  10. 每次请求刷新token的时间
  11. 别怀疑,孩子在家里也能学编程!
  12. Android开发学习之仿手机QQ消息列表侧滑删除效果
  13. 全面了解CCD摄像机
  14. sqluldr2导出过blob字段_sqluldr2导出数据没有文件也没有报错
  15. Ubuntu源码方式安装lua luarocks
  16. 随机变量概率分布函数汇总-离散型分布+连续型分布
  17. win10如何显示和隐藏文件夹
  18. Java面试题及答案2019版(下),mysql事务隔离级别原理
  19. Linux下服务器开发的必要准备
  20. 常青科技冲刺A股上市:研发费用率较低,关联方曾拆出资金达1亿元

热门文章

  1. Tip: only show tooltip when hovering directly on point
  2. 手把手教学生成听小说
  3. elasticsearch 笔记三 之聚合查询之分组计数、平均数、missing值使用、最大值、使用script计算、最小值、总和
  4. opencv-python打开摄像头定位车牌
  5. iPhone隐藏指令
  6. 计算几何——点到线的投影点的实现
  7. 【计算机视觉】TIoU文本检测评价指标
  8. 关于ElementUI的CheckBox多选框数据回显
  9. 数据库、MySql知识点
  10. 把房子当年货卖,碧桂园出圈再提速