当前课程知识点:基于Linux的C++ > 第九讲 类与对象 > 9.8 类与对象的成员(三) > LinuxCPP0908
那么还有一版 我们再想一个方案
我们仍然定义一个staticSingleton的指针_s
指向一个单一的静态Singleton对象
然后不实现它的析构函数了
就是说不用管析构函数的
我真释放 怎么办呢
我写一个public的函数
我自己写一个释放函数
因为要操纵的是静态对象
所以我们写静态函数static void Release()
释放那个静态的对象
很有趣的代码:if(_s) { free(_s), _s = NULL; }
销毁_s所指向的目标单子的内存
你注意到了 它奇怪在什么地方
奇怪在这片内存 我们是new出来的
销毁的时候没有delete
我free 我们讲内存管理的时候
我们说什么呀
我们说new和delete要配对
malloc和free要配对
我们要求你必须按照这个方式来
也就是C的内存分配格式是C的内存分配格式
C++的内存分配格式是C++内存分配格式
现在呢 我们new一个数据对象
以C++格式为它分配这个内存 然后free它
以C的格式销毁它的内存
发生了什么事
当我们调用free的时候
它简单地清空那一片存储区
而不调用那个类的析构函数
如果你delete 它要做的事情就是
调用那个目标对象的析构函数
然后清空那段内存
把那段内存还给操作系统
如果你free 它就不调用那个类的析构函数
直接返还那段内存给操作系统
就跳过了这个类的析构函数的调用 ~Singleton
所以不用管 因为我们不调用它
我们直接free它
这样我就能够保证这一段内存区域
能够完整地正确地返还给操作系统
即使它是new出来的
也就是说 在某些特殊的时候
new和free一样是可以配对的
所以如果你要对它做数据持久化操作
简单 在free这个函数前面
完成它的持久化就行了
这就意味着有了Release这个函数
那么你在任何时候想析构这个单子对象
就可以析构这个单子对象 调用它
Singleton::Release 它就会销毁那个单子对象
当你再次调用Singleton::Get的时候
它就会再次创建一个单子对象
这个时候你要注意
销毁的那个单子对象和新创建的那个单子对象
是两个不同的对象
它们存续的时间是不同的
虽然这两个单子对象在程序中
都保证在它们存在的那段时间里
它们都是唯一的
但它们仍然是两个不同的单子对象
当然也有可能放在内存的不同的地方
所以要特别去注意这个问题
这个实现也会有一个小问题 就是什么呢
如果程序员忘了调用Release 怎么办
那就没有被释放嘛
那这个问题的症状和你只调用new没有delete
只malloc没有free有什么差别呢
没有什么差别 性质不是一模一样的吗
所以它也仍然有一些令人不满意的地方
所以如果你要保证在程序结束的时候
要销毁整个程序中出现的所有单子对象
(可能每一个类一个单子对象好多个单子类呢)
那么你必须在程序结束之前
最终要写一个 清除的一个函数
来销毁所有的单子 你如果忘了写它
那么这些对象就都没有被清除
就有可能导致问题
所以 我们使用一个新的结构来实现它
这个单子类和原来那个单子类
实现有很大的差别了
其实呢它的实现比原来的代码还短
但是它就能够解决我们原来的那些问题
一个classSingleton花括号对里面
同样有一个GetData函数
同样有无参数的构造函数Singleton
单参数的一个拷贝构造函数
和一个赋值构造函数operate=
(像这样一个存在的赋值操作符
它是一个赋值构造函数)
同样有一个析构函数
但是你看它的私有字段
我们只有验证数据a
没有指向单子类静态单子的一个指针
因为以前所有的构造和析构的问题
都是因为分配了一个动态内存区域所导致的
所以我们不要指针了
那我们这个单子在哪里实现呢
那么我们就要在Get那个函数里边
定义和实现它
所以我将在这个函数内部
定义一个静态的单子对象_s
使用它的无参数版本对它进行初始化
return _s 返回在这个Get函数内部
定义的静态局部变量_s
不管这个静态局部变量是定义在普通函数里
还是定义在成员函数里
所有的静态变量都将像全局变量一样
分配存储空间
在整个程序运行期间一直维持着不变
我们返还的就是_s
不管你调用多少次Get 返回的都是它
这不就是单子了吗
尤其你考虑到 我们的构造函数全私有的
还好几个没实现
那不就是一定是这个样子吗
你只能通过Get得到那个单子
Get得到的就是唯一的那个静态局部量_s吗
那不就是单子吗
所以我们Get这个函数返回的就是
staticSingleton的一个引用
所以当你按照这样的模式
定义我们的单子类的时候
使用的方式就可以按照下面这个结构:Singleton::Get()
得到那个单子对象的一个引用
然后“.GetData” 得到了那个对象
然后访问它的公开的成员函数 GetData
因为它是一个引用
所以实际上我们还可以定义一个引用的别名
叫Singleton&sing
我把sing定义成Singleton的一个引用
然后我把它初始化成Singleton::Get()
这个没有问题
因为这是一个引用的初始化动作
sing只是Singleton::Get所得到的
那个静态局部量的一个别名
我们并没有创建一个新的数据对象
所以这个初始化动作
既不会发生值的拷贝 也不会发生赋值
所有的动作都不会发生
就是原来这个staticSingleton _s
所以不会发生值的拷贝动作
既然不会发生这个动作
那么我们引用的就是那个唯一的单子量
所以以后就可以在这个sing引用的上面
调用我们的成员函数
可以在sing上面调用成员函数Getdata
这样的方式显然比你一开始写的
Singleton::Get().GetData() 这个方式要简单
因为如果你程序中频繁地需要
使用到这条语句的话
输出的代码量太多了
如果你可以在这一段里面
定义好这个sing这个引用
你可以一直在sing上面去访问它 去操作它
这样节省你编程的代码量
可以按照这个模式进行工作
虽说这是一个不太好的方式
如果真是一个单子
其实我建议同学们不要定义这样的量
即使是引用量 也不要定义它
每次都是Singleton::Get去得到它
这样的话 我们保证访问的就是
那个唯一的静态局部量
而且在源代码级别
能够看到它是完全唯一的
接下来就是静态常数据成员
它混合了常数据成员
和静态数据成员的两个特征
它的值不仅是在成员运行期间是不可以改变的
且每一个类只有唯一的一份
所以在定义的时候
要有static和const两个关键字
这样的量只能在类的外部进行初始化
我们看这个例子:
“classA { private: staticconstintcount; };”
我们要初始化它的时候
就constintA::count = 10
注意看 这里面有两个const
不仅在classA的里面
这个count数据成员的这个地方要写const
在这个数据成员的初始化的前面
也要写const 两个都不能省略
而static只需要在类的数据成员地方
写上就行了
并不需要在初始化的前边写上static
特别注意这一条
如果同学们在编程的时候会遗忘
觉得自己可能遗忘这个count是个static量的话
那么在定义并初始化这个count的时候
可以在const前边写上static 同时注释掉
这样的话一看就知道这是一个static量
如果你能记得 不写没关系
最后一个问题就是友元函数和友元类
友元是什么东西呢 就是朋友
有些特定的类在外界
有些函数或者另外一个类想使用它
不是想使用它的公开数据和公开成员
是想使用它的隐私数据和隐私成员
就说他是我好朋友 我对他呢 没秘密
我的秘密都会跟他说
一个类里面所有private量和private的函数
都是自己私有的 都是它秘密的东西
外界是看不见的 不能用
但是我的朋友呢 就想让它用
因为它需要频繁操作这些东西
(我这个类中的数据)
如果不给它用 太麻烦 不方便
所以我就想让它用
就意味着我对它没秘密
那么那些使用这个类的
私有数据成员或成员函数的
函数或类就是这个类的朋友
我们实现的机制就是friend这个关键字
你在一个函数的原型前面加上一个friend
你在一个类的声明的前面加上一个friend
就表示这个函数和这个类是这个类的友元类
注意这样的声明只能写在classCircle
这个类的定义里
你要在这个类的定义里写上frienddoubleGetRadius()
friendclassGlobe
就说明这个函数GetRadius和这个类Globe
就是Circle类的友元
一个是它的友元类 一个是它的友元函数
这个时候GetRadius和Globe
就可以访问Circle类的私有数据
radius就可以给它看了 它就能够访问了
这是非常非常重要的一个地方
注意 友元类和友元函数是要慎用的
为什么呢 因为它打破了
我们的类的数据封装以及信息隐藏的规则
因为外界的某个函数和某个类
可以自由地使用我们类中的
私有的数据或私有的函数
当然这个数据封装和信息隐藏
就不存在了嘛
所以用的时候要特别特别地小心
可是很多时候 在某些特定的场合下边
你不使用这个东西
编程实际上是极其不方便的
所以呢 我们其实又离不开它
注意 这个友元关系是不可逆的
Globe是Circle的朋友
Circle可不是Globe的朋友
同学们要特别注意这一点
Globe可以自由地访问Circle类的私有成员
并不意味着Circle可以自由地访问
Globe的私有的成员
除非它的那边也把我设成它的友元
双方互设友元 才能互相公开
友元是单向的 不可逆
特别注意这一条
还有友元这个写法不需要访问控制
它跟访问控制没关系
它只是一个声明 它不是类的成员
所以访问控制作用不到它的上面
不管你是写在private下边
还是public下面 还是protected下边
它都一样
-1.1 提纲
-1.2 程序设计的基本概念
-1.3 简单C/C++程序介绍
-1.4 程序设计的基本流程
-1.5 基本语法元素
-1.6 程序设计风格
-1.7 编程实践
-第一讲 C/C++基本语法元素--编程实践提交入口
-2.1 提纲
-2.2 结构化程序设计基础
-2.3 布尔数据
-2.4 分支结构
-2.5 break语句
-2.6 循环结构
-2.7 编程实践
-第二讲 程序控制结构--编程实践提交入口
-3.1 提纲
-3.2 函数声明、调用与定义
-3.3 函数调用栈框架
-3.4 编程实践
-第三讲 函数--编程实践提交入口
-4.1 提纲
-4.2 算法概念与特征
-4.3 算法描述
-4.4 算法设计与实现
-4.5 递归算法(一)
-4.6 递归算法(二)
-4.7 容错与计算复杂度
-4.8 编程实践
-第四讲 算法--编程实践提交入口
-5.1 提纲
-5.2 库与接口
-5.3 随机数库(一)
-5.4 随机数库(二)
-5.5 作用域与生存期
-5.6 典型软件开发流程(一)
-5.7 典型软件开发流程(二)
-5.8 编程实践
-第五讲 程序组织与开发方法--编程实践提交入口
-6.1 提纲
-6.2 字符
-6.3 数组(一)
-6.4 数组(二)
-6.5 结构体
-6.6 编程实践
-第六讲 复合数据类型--编程实践提交入口
-7.1 提纲
-7.2 指针基本概念
-7.3 指针与函数
-7.4 指针与复合数据类型(一)
-7.5 指针与复合数据类型(二)
-7.6 字符串
-7.7 动态存储管理(一)
-7.8 动态存储管理(二)
-7.9 引用
-7.10 编程实践
-第七讲 指针与引用--编程实践提交入口
-8.1 提纲
-8.2 数据抽象(一)
-8.3 数据抽象(二)
-8.4 链表(一)
-8.5 链表(二)
-8.6 链表(三)
-8.7 链表(四)
-8.8 函数指针(一)
-8.9 函数指针(二)
-8.10 抽象链表(一)
-8.11 抽象链表(二)
-8.12 编程实践
-第八讲 链表与程序抽象--编程实践提交入口
-9.1 提纲
-9.2 程序抽象与面向对象
-9.3 类类型
-9.4 对象(一)
-9.5 对象(二)
-9.6 类与对象的成员(一)
-9.7 类与对象的成员(二)
-9.8 类与对象的成员(三)
-9.9 继承(一)
-9.10 继承(二)
-9.11 继承(三)
-9.12 多态(一)
-9.13 多态(二)
-9.14 编程实践
-第九讲 类与对象--编程实践提交入口
-10.1 提纲
-10.2 四则运算符重载(一)
-10.3 四则运算符重载(二)
-10.4 关系与下标操作符重载
-10.5 赋值操作符重载(一)
-10.6 赋值操作符重载(二)
-10.7 赋值操作符重载(三)
-10.8 赋值操作符重载(四)
-10.9 赋值操作符重载(五)
-10.10 流操作符重载(一)
-10.11 流操作符重载(二)
-10.12 流操作符重载(三)
-10.13 操作符重载总结
-10.14 编程实践
-第十讲 操作符重载--编程实践提交入口
-11.1 提纲
-11.2 泛型编程概览
-11.3 异常处理机制(一)
-11.4 异常处理机制(二)
-11.5 运行期型式信息(一)
-11.6 运行期型式信息(二)
-11.7 模板与型式参数化
-11.8 题外话:术语翻译
-11.9 泛型编程实践(一)
-11.10 泛型编程实践(二)
-11.11 泛型编程实践(三)
-11.12 泛型编程实践(四)
-11.13 泛型编程实践(五)
-11.14 泛型编程实践(六)
-11.15 泛型编程实践(七)
-11.16 泛型编程实践(八)
-11.17 泛型编程实践(九)
-11.18 泛型编程实践(十)
-11.19 编程实践
-第十一讲 泛型编程--编程实践提交入口
-12.1 提纲
-12.2 程序执行环境(一)
-12.3 程序执行环境(二)
-12.4 程序执行环境(三)
-12.5 程序执行环境(四)
-12.6 输入输出(一)
-12.7 输入输出(二)
-12.8 文件系统
-12.9 设备
-12.10 库(一)
-12.11 库(二)
-12.12 makefile文件(一)
-12.13 makefile文件(二)
-12.14 makefile文件(三)
-12.15 编程实践
-第十二讲 Linux系统编程基础--编程实践提交入口
-13.01 提纲
-13.02 进程基本概念
-13.03 信号
-13.04 进程管理(一)
-13.05 进程管理(二)
-13.06 进程管理(三)
-13.07 进程间通信(一)
-13.08 进程间通信(二)
-13.09 进程间通信(三)
-13.10 进程间通信(四)
-13.11 进程池
-13.12 编程实践
-第十三讲 进程编程--编程实践提交入口
-14.1 提纲
-14.2 线程基本概念
-14.3 线程管理(一)
-14.4 线程管理(二)
-14.5 线程管理(三)
-14.6 线程管理(四)
-14.7 线程同步机制(一)
-14.8 线程同步机制(二)
-14.9 C++11线程库(一)
-14.10 C++11线程库(二)
-14.11 C++11线程库(三)
-14.12 C++11线程库(四)
-14.13 C++11线程库(五)
-14.14 编程实践
-第十四讲 线程编程--编程实践提交入口
-15.1 提纲
-15.2 Internet网络协议
-15.3 套接字(一)
-15.4 套接字(二)
-15.5 编程实践
-第十五讲 网络编程--编程实践提交入口