当前课程知识点:基于Linux的C++ >  第九讲 类与对象 >  9.7 类与对象的成员(二) >  LinuxCPP0907

返回《基于Linux的C++》慕课在线视频课程列表

LinuxCPP0907在线视频

LinuxCPP0907

下一节:LinuxCPP0908

返回《基于Linux的C++》慕课在线视频列表

LinuxCPP0907课程教案、知识点、字幕

静态的数据成员和静态的成员函数

非常非常有用 在我们的编程中

频繁地需要设计一个很特殊的程序功能

就是什么呢

存在着某一个类的单一的共享对象

这样的一个共享对象 它是全局的

在整个程序中 它只能存在一个

不会有很多个

并被整个程序中的所有的模块所共享

这样的一个东西 我们称它为是一个单子

英语的名字叫singleton

那么我们的编程呢

就必须能够保证这样的一个单子:

(1)全局就存在一个

(2)在全局的任何一个地方都能够访问到它

也就是说 我们一定要有一个

全局的访问策略

保证在程序代码的任何地方

只要需要就能够访问到它

操纵的那唯一一个数据对象

这个数据对象就叫单子

单子模式呢 有很多种实现策略

我们来看一个实现策略

这个程序代码虽然代码量很短

但实际上相当复杂

我们定义类“classSingleton{};”

我里面定义一些公开的成员函数、

私有的成员函数和私有的数据

我们定义的这个单子模式里面

因为要求这样的数据对象

全局只能构造一个唯一的副本

这就意味着它的构造函数不能公开

你一公开构造函数

就意味着程序中任何一个地方

都可以构造它

你就没有办法保证这个单子对象的唯一性

所以我要将这个单子的构造函数

全部定义为私有的

缺省构造函数定义成私有的

拷贝构造函数要弄成私有的

赋值的这个重载的构造函数

也要弄成私有的 然后析构函数

我们也要把它定义成私有的

我也不允许你析构我们这样的一个对象

私有的构造函数和私有的析构函数

这就意味着我们没有办法

在这个类的外部构造和销毁这个对象

在这个类的实现里面

有两个地方比较特殊的就是

我们在Singleton的拷贝构造函数里面

还有Singleton的赋值操作符重载的构造里面

后面都带着分号 我们声明了它

但是我们在源代码里边并没有实现它

只声明而不实现

这就意味着这样的拷贝函数

不仅外界是不可以调用的

就是我们自己也是不会调用的

因为没有它的实现代码

你一调用编译器就报错

这实际上意味着我们通过这种手段

保证我们这个单子的模式

它是一个不可拷贝对象

这个语义是非常重要的

因为如果你允许这个对象可以被拷贝

那就意味着这个单子就不再单了

只定义而不实现

它的拷贝构造函数和赋值构造函数

就能够让这个单子对象不可拷贝

从某种程度上来讲

也保证我们的单子模式的唯一性

接下来 我们要保证

怎么真正地构造这个对象

你没有公开的构造函数

外界就不能够被调用

那我们这个对象什么时候被构造出来呢

因为单子嘛 它又不是无子 对不对

它总归还要有一个子

那么就提供一个公开的Get函数

用这个Get函数来构造这个对象

因为这个Get函数是我们类的一个成员函数

当我们调用这个公开的Get函数的时候

那么它就会执行它的内部代码

而这个内部代码又在这个类的内部

所以它可以调用这个类的私有的

构造函数去替我们构造它

那么我们在这里面

就可以用一个new操作符调用私有的构造函数

去构造我们的这个对象

然后我们返回它构造出来的这个对象

构造出来的这个对象保存在什么地方

这是一个需要特别注意的一个问题

因为我们时刻要记录构造的对象

分配的内存地址在哪里

这个指针必须要保存

所以我们要在这个类Singleton里面

定义一个私有的数据字段

是一个指向Singleton的指针

来保存我们构造出来的这个类的

单子对象到底存在哪里

因为这个类中保存的单子就一份

所以这个指针_s应该只有一份

前面要加上static关键字

保证_s是指向本类唯一的对象的

那一个唯一的指针

我们还写了一个私有的数据成员inta

来作为我们的验证数据

同时我们写了一个函数intGetData()

然后return++a

来返回我们的验证数据看看对不对

这个就是我们class Singleton的

类的全部的定义

在这里面我仅仅是

写了一个析构函数的声明

所以我没有销毁在构造函数中分配的

那个指针所指向的目标数据对象的内存

当程序结束的时候

绝大多数的操作系统

都会自动地释放动态分配的内存

我们可以不实现它 大部分时候都没问题

但是偶尔 真地没有析构函数

它确实是有可能导致问题的

因为我们这里面毕竟有一个指针

指向一个动态分配的内存

因为这个静态成员

是从属于一个特定的类的

而不从属于那个类的某一个对象

所以要想解析这个静态数据成员

那么前面必须加上类名 Singleton::_s

我要把它初始化成NULL

那么我们怎么使用它呢

我们将以Singleton::Get这个函数调用

来获取我们的那个单子对象

获取过来的这个对象

就是Get替我们构造的

当然了 它是在需要的时候才构造

回过头来我们来看Get这个成员函数的实现

这两条语句说明什么呢

我写在内联函数里面了

说明当我们调用这个Get成员函数

来获取这个单子的时候

如果这个单子还不存在

那么Get就会给我们构造一个单子出来

然后返回它

而如果这个单子已经存在了

那么就直接返回那个单子

就给你 就OK了

所以我们在程序中访问这个单子的唯一方式

就是Singleton::Get

因为它是静态的成员函数

所以解析的时候仍然是用类名加“::”

我们得到这个指针之后

就可以引领这个指针

所指向的那个目标对象

就是我们的Singleton的一个对象

然后调用它的GetData

返回它对应的实际的那个验证数据的值

当然是递增以后返回

返回递增以后的那个验证数据的值

这个就是我们的单子模式的第一版

没有析构

没有析构 当然是有可能导致问题的

那么我们就给它弄上一个析构的函数

看看我们这样的一个实现

还是classSingleton的定义

然后是一个静态的

返回本类一个指针的Get函数

当这个单子对象不存在的时候

我就构造它 如果存在就返回

GetData也一样 Singleton的构造也一样

无参数的构造函数被我们实现了

拷贝构造和赋值构造我们都只声明不实现

然后我们给它实现一个析构的函数

我就按照这个方式去析构:if(Singleton::_s)

如果存在这样一个对象

那么我们就销毁它 我们delete

然后Singleton::_s赋值为NULL

你说我就按照这个方式在我的Singleton

这个析构函数里面实现它 行不行呢

跟同学们说 这个实现是错的

首先一点 你把析构函数实现为private

在外部是不能够调用的

第二个 即便将这个析构函数

改成public访问控制的 它也是不对的

delete这个操作符本身

是需要调用这个析构函数的

你不是delete Singleton::_s吗

Singleton::_s是什么呀

它是指向Singleton的一个指针哪

要销毁这个_s所指向的那个目标对象

目标对象就是Singleton

所以你就必须调用这个Singleton析构函数

才能够去销毁它嘛

所以你在这个类的析构函数里边

调用这个类的析构函数本身是不可以的

另外 非静态的函数

不能够释放我们的静态的数据成员的

否则在某些Linux系统下面

它会导致系统崩溃

所以这个实现肯定是不对的

那我们又需要这个析构

那这刚才那个实现 它又不对

那我们怎么办呢 那我们就有一个方案

我希望这个析构函数它能够正确地被调用

我不是说我不实现它

但是我需要它在某一个

恰当的时机能够被调用

那么什么样一个时机被调用呢

就是程序完全结束之前

所有的动态分配的静态量

都应该销毁它们指向的目标数据对象

那么我们就想了一个很巧妙的一个技巧

使用到了嵌套类的概念

如果我在一个类的内部还定义了另外一个类

那么这样的一个类就从属于这个类

这个叫嵌套类

你看我们在Singleton里面的private字段里面

定义了一个新的类 叫Destroyer(毁灭者)

我定义了一个毁灭者类

那么这个类就是从属于Singleton这个类的

你要解析它的时候只能Singleton::Destroyer

如果再解析Destroyer的成员函数或数据成员

就只能Singleton::Destroyer

“::”它的成员 比如说~Destroyer

你可以按照这样一个方式来解析它

这就叫嵌套类 我们这个嵌套类里面

就实现了一个函数——析构函数

if(Singleton::_s )

{ delete Singleton::_s, Singleton::_s = NULL; }

就销毁它 这个析构函数就干这一件事情

所以类 Destroyer本身

它的唯一目的就是为了释放单子

我在这个classSingleton单子类里面

添加一个新的静态成员staticDestroyer_d

这样的一个静态成员

它将在程序结束的时候

由操作系统负责替我们销毁

在销毁它的时候

操作系统就会调用这个_d

这个静态成员的析构函数

而这个析构函数

就会替我们销毁_s所指向的

那个静态的Singleton那个单子对象

这个过程是自动的

可以按照像这样的一个模式

完成我们单子的析构

但是这个析构 它也是有一些问题的

一个 这个销毁只能在程序结束的时候进行

因为它是自动进行的

什么时候_d需要被销毁了 它才能够销毁

所以它在程序结束的时候

才会销毁我们的单子

销毁的时机我们无从选择

想提前销毁不可能

第二个 有些编译器在程序结束的时候

因为优化的目的

所有的资源都会直接返还给操作系统

也就是说 这样对象

你不销毁其实问题也不大

所以它可能压根就不销毁_d

它不销毁_d

那么就不会销毁_s所指向的singleton

如果你的单子模式中

它的析构函数里面写了一些特殊的代码

那么这个类可能会有问题

假设我们这个classSingleton

它是一个实际应用的类

它会有自己其它附加的信息

专门做某些特定的事情的

那么这样一个析构函数

因为要被外界调用

所以我需要把它变成public 同时呢

你比如讲我这个类里面有一些数据

需要在这个单子对象被销毁的时候

完成它的数据持久化

那么如果我把那段代码

写在了Singleton的析构函数里

要确保这个Singleton的这个析构函数

能够被调用 刚才不讲了吗

有些编译器 它最后程序都结束了

你所有动态分配的量不管三七二十一

全返还给操作系统算了

根本就不用管 所以这个析构函数

它压根就不调用 没关系

操作系统一股脑全收回了

根本不管这个单一的一个对象

要不要销毁 要不要释放的问题

它不释放_s

Singleton这个析构函数就不会被调用

它不会被调用

你的数据持久化代码如果写在析构函数里

它就不会被执行

你的程序语义就会有问题

所以这个析构的方式是正确的

但是在某些特殊的情况下边

我们的程序可能不像

我们所期望的那么工作

基于Linux的C++课程列表:

第一讲 C/C++基本语法元素

-1.1 提纲

--LinuxCPP0101

-1.2 程序设计的基本概念

--LinuxCPP0102

-1.3 简单C/C++程序介绍

--LinuxCPP0103

-1.4 程序设计的基本流程

--LinuxCPP0104

-1.5 基本语法元素

--LinuxCPP0105

-1.6 程序设计风格

--LinuxCPP0106

-1.7 编程实践

--LinuxCPP0107

-第一讲 C/C++基本语法元素--编程实践提交入口

第二讲 程序控制结构

-2.1 提纲

--LinuxCPP0201

-2.2 结构化程序设计基础

--LinuxCPP0202

-2.3 布尔数据

--LinuxCPP0203

-2.4 分支结构

--LinuxCPP0204

-2.5 break语句

--LinuxCPP0205

-2.6 循环结构

--LinuxCPP0206

-2.7 编程实践

--LinuxCPP0207

-第二讲 程序控制结构--编程实践提交入口

第三讲 函数

-3.1 提纲

--LinuxCPP0301

-3.2 函数声明、调用与定义

--LinuxCPP0302

-3.3 函数调用栈框架

--LinuxCPP0303

-3.4 编程实践

--LinuxCPP0304

-第三讲 函数--编程实践提交入口

第四讲 算法

-4.1 提纲

--LinuxCPP0401

-4.2 算法概念与特征

--LinuxCPP0402

-4.3 算法描述

--LinuxCPP0403

-4.4 算法设计与实现

--LinuxCPP0404

-4.5 递归算法(一)

--LinuxCPP0405

-4.6 递归算法(二)

--LinuxCPP0406

-4.7 容错与计算复杂度

--LinuxCPP0407

-4.8 编程实践

--LinuxCPP0408

-第四讲 算法--编程实践提交入口

第五讲 程序组织与开发方法

-5.1 提纲

--LinuxCPP0501

-5.2 库与接口

--LinuxCPP0502

-5.3 随机数库(一)

--LinuxCPP0503

-5.4 随机数库(二)

--LinuxCPP0504

-5.5 作用域与生存期

--LinuxCPP0505

-5.6 典型软件开发流程(一)

--LinuxCPP0506

-5.7 典型软件开发流程(二)

--LinuxCPP0507

-5.8 编程实践

--LinuxCPP0508

-第五讲 程序组织与开发方法--编程实践提交入口

第六讲 复合数据类型

-6.1 提纲

--LinuxCPP0601

-6.2 字符

--LinuxCPP0602

-6.3 数组(一)

--LinuxCPP0603

-6.4 数组(二)

--LinuxCPP0604

-6.5 结构体

--LinuxCPP0605

-6.6 编程实践

--LinuxCPP0606

-第六讲 复合数据类型--编程实践提交入口

第七讲 指针与引用

-7.1 提纲

--LinuxCPP0701

-7.2 指针基本概念

--LinuxCPP0702

-7.3 指针与函数

--LinuxCPP0703

-7.4 指针与复合数据类型(一)

--LinuxCPP0704

-7.5 指针与复合数据类型(二)

--LinuxCPP0705

-7.6 字符串

--LinuxCPP0706

-7.7 动态存储管理(一)

--LinuxCPP0707

-7.8 动态存储管理(二)

--LinuxCPP0708

-7.9 引用

--LinuxCPP0709

-7.10 编程实践

--LinuxCPP0710

-第七讲 指针与引用--编程实践提交入口

第八讲 链表与程序抽象

-8.1 提纲

--LinuxCPP0801

-8.2 数据抽象(一)

--LinuxCPP0802

-8.3 数据抽象(二)

--LinuxCPP0803

-8.4 链表(一)

--LinuxCPP0804

-8.5 链表(二)

--LinuxCPP0805

-8.6 链表(三)

--LinuxCPP0806

-8.7 链表(四)

--LinuxCPP0807

-8.8 函数指针(一)

--LinuxCPP0808

-8.9 函数指针(二)

--LinuxCPP0809

-8.10 抽象链表(一)

--LinuxCPP0810

-8.11 抽象链表(二)

--LinuxCPP0811

-8.12 编程实践

--LinuxCPP0812

-第八讲 链表与程序抽象--编程实践提交入口

第九讲 类与对象

-9.1 提纲

--LinuxCPP0901

-9.2 程序抽象与面向对象

--LinuxCPP0902

-9.3 类类型

--LinuxCPP0903

-9.4 对象(一)

--LinuxCPP0904

-9.5 对象(二)

--LinuxCPP0905

-9.6 类与对象的成员(一)

--LinuxCPP0906

-9.7 类与对象的成员(二)

--LinuxCPP0907

-9.8 类与对象的成员(三)

--LinuxCPP0908

-9.9 继承(一)

--LinuxCPP0909

-9.10 继承(二)

--LinuxCPP0910

-9.11 继承(三)

--LinuxCPP0911

-9.12 多态(一)

--LinuxCPP0912

-9.13 多态(二)

--LinuxCPP0913

-9.14 编程实践

--LinuxCPP0914

-第九讲 类与对象--编程实践提交入口

第十讲 操作符重载

-10.1 提纲

--LinuxCPP1001

-10.2 四则运算符重载(一)

--LinuxCPP1002

-10.3 四则运算符重载(二)

--LinuxCPP1003

-10.4 关系与下标操作符重载

--LinuxCPP1004

-10.5 赋值操作符重载(一)

--LinuxCPP1005

-10.6 赋值操作符重载(二)

--LinuxCPP1006

-10.7 赋值操作符重载(三)

--LinuxCPP1007

-10.8 赋值操作符重载(四)

--LinuxCPP1008

-10.9 赋值操作符重载(五)

--LinuxCPP1009

-10.10 流操作符重载(一)

--LinuxCPP1010

-10.11 流操作符重载(二)

--LinuxCPP1011

-10.12 流操作符重载(三)

--LinuxCPP1012

-10.13 操作符重载总结

--LinuxCPP1013

-10.14 编程实践

--LinuxCPP1014

-第十讲 操作符重载--编程实践提交入口

第十一讲 泛型编程

-11.1 提纲

--LinuxCPP1101

-11.2 泛型编程概览

--LinuxCPP1102

-11.3 异常处理机制(一)

--LinuxCPP1103

-11.4 异常处理机制(二)

--LinuxCPP1104

-11.5 运行期型式信息(一)

--LinuxCPP1105

-11.6 运行期型式信息(二)

--LinuxCPP1106

-11.7 模板与型式参数化

--LinuxCPP1107

-11.8 题外话:术语翻译

--LinuxCPP1108

-11.9 泛型编程实践(一)

--LinuxCPP1109

-11.10 泛型编程实践(二)

--LinuxCPP1110

-11.11 泛型编程实践(三)

--LinuxCPP1111

-11.12 泛型编程实践(四)

--LinuxCPP1112

-11.13 泛型编程实践(五)

--LinuxCPP1113

-11.14 泛型编程实践(六)

--LinuxCPP1114

-11.15 泛型编程实践(七)

--LinuxCPP1115

-11.16 泛型编程实践(八)

--LinuxCPP1116

-11.17 泛型编程实践(九)

--LinuxCPP1117

-11.18 泛型编程实践(十)

--LinuxCPP1118

-11.19 编程实践

--LinuxCPP1119

-第十一讲 泛型编程--编程实践提交入口

第十二讲 Linux系统编程基础

-12.1 提纲

--LinuxCPP1201

-12.2 程序执行环境(一)

--LinuxCPP1202

-12.3 程序执行环境(二)

--LinuxCPP1203

-12.4 程序执行环境(三)

--LinuxCPP1204

-12.5 程序执行环境(四)

--LinuxCPP1205

-12.6 输入输出(一)

--LinuxCPP1206

-12.7 输入输出(二)

--LinuxCPP1207

-12.8 文件系统

--LinuxCPP1208

-12.9 设备

--LinuxCPP1209

-12.10 库(一)

--LinuxCPP1210

-12.11 库(二)

--LinuxCPP1211

-12.12 makefile文件(一)

--LinuxCPP1212

-12.13 makefile文件(二)

--LinuxCPP1213

-12.14 makefile文件(三)

--LinuxCPP1214

-12.15 编程实践

--LinuxCPP1215

-第十二讲 Linux系统编程基础--编程实践提交入口

第十三讲 进程编程

-13.01 提纲

--LinuxCPP1301

-13.02 进程基本概念

--LinuxCPP1302

-13.03 信号

--LinuxCPP1303

-13.04 进程管理(一)

--LinuxCPP1304

-13.05 进程管理(二)

--LinuxCPP1305

-13.06 进程管理(三)

--LinuxCPP1306

-13.07 进程间通信(一)

--LinuxCPP1307

-13.08 进程间通信(二)

--LinuxCPP1308

-13.09 进程间通信(三)

--LinuxCPP1309

-13.10 进程间通信(四)

--LinuxCPP1310

-13.11 进程池

--LinuxCPP1311

-13.12 编程实践

--LinuxCPP1312

-第十三讲 进程编程--编程实践提交入口

第十四讲 线程编程

-14.1 提纲

--LinuxCPP1401

-14.2 线程基本概念

--LinuxCPP1402

-14.3 线程管理(一)

--LinuxCPP1403

-14.4 线程管理(二)

--LinuxCPP1404

-14.5 线程管理(三)

--LinuxCPP1405

-14.6 线程管理(四)

--LinuxCPP1406

-14.7 线程同步机制(一)

--LinuxCPP1407

-14.8 线程同步机制(二)

--LinuxCPP1408

-14.9 C++11线程库(一)

--LinuxCPP1409

-14.10 C++11线程库(二)

--LinuxCPP1410

-14.11 C++11线程库(三)

--LinuxCPP1411

-14.12 C++11线程库(四)

--LinuxCPP1412

-14.13 C++11线程库(五)

--LinuxCPP1413

-14.14 编程实践

--LinuxCPP1414

-第十四讲 线程编程--编程实践提交入口

第十五讲 网络编程

-15.1 提纲

--LinuxCPP1501

-15.2 Internet网络协议

--LinuxCPP1502

-15.3 套接字(一)

--LinuxCPP1503

-15.4 套接字(二)

--LinuxCPP1504

-15.5 编程实践

--LinuxCPP1505

-第十五讲 网络编程--编程实践提交入口

课程文档

-课程PDF文件

LinuxCPP0907笔记与讨论

也许你还感兴趣的课程:

© 柠檬大学-慕课导航 课程版权归原始院校所有,
本网站仅通过互联网进行慕课课程索引,不提供在线课程学习和视频,请同学们点击报名到课程提供网站进行学习。