前言

  首先感谢 @JerryZhang 在端午节百忙之中帮忙校验。里面杂七杂八的地方错了不少。自己也看了两三遍,但难保里面还有一些错误。如果有所发现请告诉一声。感激不尽。

  写这个的原因是想回答上一阵子一名同学在C++奋斗乐园提问的一个关于虚拟继承的问题。后来导致了自己重新复习一下《深入探索C++对象模型》,就想着顺便把这本书总结一下。所以内容可能有点多。如果有时间的话还是推荐看一看《深入探索C++对象模型》这本书。

  其中内容夹杂的东西较多,一些是总结于书上的内容,一些是自己的一些想法。我会尽量标明哪些是来自书籍资料、哪些是自己的想法,以免误导他人,请大家斟酌吸纳。另外,能力不够,错误难免也会很多。希望大神不吝赐教。

  里面一些地方用上了C语言去理解C++的一些东西,所以如果看后觉得误导您的话,那么抱歉了。

  对于封装我没啥使用经验,也并未能了解其精华与本质。Jerryzhang 也跟我说过“讲封装就不应该把 C 语言拉进来,用面向过程语言去实现面向对象,为什么不直接用面向对象语言去实现面向对象呢?”。最终我还是贴出来吧(T^T请勿拍砖)。把它做为一种笔记或是心得,或许在某年某月翻过来看一看也可以看到自己的一些成长过程。另外我个人觉得面向对象是一种对具体事物的一种抽象,提供更好的程序组织逻辑,提高程序复用,减少重复编码的一种思维方法,而不用太纠结于某某编程语言。就像耗子大神也曾经用UNIX去解释设计模式一样。详情请戳:从面向对象的设计模式看软件设计

  另外原帖贴在:http://www.cppleyuan.com/forum.php?mod=viewthread&tid=11546

关于封装

  在面向对象程式设计方法中,封装(英语:Encapsulation)是指,一种将抽象性函式接口的实作细节部份包装、隐藏起来的方法。同时,它也是一种防止外界呼叫端,去存取物件内部实作细节的手段,这个手段是由编程语言本身来提供的。这两个概念有一些不同,但通常被混合使用。封装被视为是面向对象的四项原则之一。

  适当的封装,可以将物件使用接口的程式实作部份隐藏起来,不让使用者看到,同时确保使用者无法任意更改物件内部的重要资料。它可以让程式码更容易理解与维护,也加强了程式码的安全性。 (来自:wikipedia)

  简单的以下面的point class 为例,把数据与函数联合,隐蔽掉用户对数据的直接操作,提供一组对外开放的函数以供用户对数据的操作,就称为封装。(个人理解)

class point{public :point(){x = 0;y = 0;}point(int x, int y){this->x = x;this->y = y;}void set_x(int x){this->x = x;}void set_y(int y){this->y = y;}int get_x(){return x;}int get_y(){return y;}private:int x;int y;};

  在point class 里面, 使用private 隐藏了point 的x坐标属性和y坐标属性数据成员。用户只能通过point提供的对外开放函数 set_x()、set_y()、get_x()、get_y() 来对数据x和y进行访问、修改。但是对于计算机内部来说,数据是没有被分为private和public的(当然,那些如X86 CPU 提供对数据段\代码段的访问控制不属于Object Oriented Programming 的谈论范围内)。那么理论上只要取得point object在内存的地址,是可以对private保护起来的数据进行访问的。但是这样做却破坏了Object Oriented Programming 的封装性。

  像下面的例子,通过取得point object的内存地址,就可以实现对point object中被保护的数据进行操作。如果在即使编程中使用这种编程方式可能会造成不可预料的后果。

int main (void)
{point pt;int *p = (int*)&pt;p[0] = 11;p[1] = 22;std::cout << pt.get_x() << std::endl;std::cout << pt.get_y() << std::endl;return 0;
}

如果用C语言,如何理解封装  

  先举一个反面例子, 像下面的point class,虽然使用了C++ class进行对point的封装,但能说它实现了对point的封装?这个point class中并没有提供对它应该处理的x和y 进行有效的处理。反而提供两个两个与这个object毫无相关的的eat(), sleep()函数。(个人理解)

class point{public :point();void eat(){std::cout << "I'm can cat";}void sleep(){std::cout << "I'm can sleep";}private:int x;int y;};

  C语言的封装,通常的做法是以一个file作为一个object。像:point.h头文件提供给用户接口,point.c源文件对头文件的声明进行实现。用户在使用的时候在意的是point.h提供的数据结构和对这个结构进行操作的函数方法。(个人理解)

  C语言方式的封装大概如下:

// point.h 头文件(定义)typedef struct {int x;int y;}point;void init_point(point *pt, int x, int y);void set_x(point *pt, int x);void set_y(point *pt, int y);int get_x(point *pt);int get_y(point *pt);void zero_point(point *pt);

//point.c 源文件(实现)void init_point(point *pt, int x, int y){pt->x = x;pt->y = y;}void set_x(point *pt, int x){pt->x = x;}void set_y(point *pt, int y){pt->y = y;}int get_x(point *pt, int x){return pt->x;}int get_y(point *pt, int y){return pt->y;}static void zero_point(point *pt){pt->x = 0;pt->y = 0;}

  虽然C语言没有提供类似private的访问控制,但若point的开发者想隐藏某些方法(函数), 或者数据(属性)。那么可以在方法或声明属性的前面加上static关键字进行对方法或属性的隐藏。这样别的文件里面就没办法使用到point文件中被加上static属性的成员了。如代码中的 static void zero_point(point *pt) 在别的实现文件里面是无法引用到它的。

  这种方式在我理解就是C语言的封装方式了。

为什么需要封装?

  先看一个例子:

// point.h 头文件
typedef struct {int x;int y;
}point;void init_point(point *pt, int x, int y);
void set_x(point *pt, int x);
void set_y(point *pt, int y);
int get_x(point *pt);
int get_y(point *pt);typedef animal{char name[256];
}animal;void init_animal(animal *al, char *name);
void eat(animal *al);
void sleep(animal *al);

//point 源文件#include "point.h"void init_animal(animal *al, cha *name){strcpy(al->name, name)}void eat(animal *al){printf ("I'm %s I can eat", al->name);}void sleep(animal *al){printf ("I'm %s I can sleep", al->name);}void init_point(point *pt, int x, int y){pt->x = x;pt->y = y;}void set_x(point *pt, int x){pt->x = x;}void set_y(point *pt, int y){pt->y = y;}int get_x(point *pt){return pt->x;}int get_y(point *pt){return pt->y;}static void zero_point(point *pt){pt->x = 0;pt->y = 0;}

  上面的代码看起来是没有什么问题,但是不好的地方就在把两个毫不相关的数据结构整合到一个文件里面。比较好的做法是将animal和point两种不同的数据结构放到不同的文件里面。实现功能的隔离。上面的例子中,只是提供了两个数据结构的处理而已,如果多写上几个数据结构,那么这个头文件简直就是乱七八糟了(当然,某些强人记忆力很强大,我无话可说)。当然,功能相近的在一起是没什么问题的,还是功能不相关的扔在一起,总是会让人蛋疼菊紧的。但又有一个问题,里面我的animal结构实在太小了,放到一个文件里头确实有点大题小做。(个人理解)

  像C++这类面向对象的语言提供了较为优雅的处理方法。

class point{
public :point(){x = 0;y = 0;}point(int x, int y){this->x = x;this->y = y;}void set_x(int x){this->x = x;}void set_y(int y){this->y = y;}int get_x(){return x;}int get_y(){return y;}private:int x;int y;
};class animal{
public:animal(char *name){strcpy(this->name, name);}void eat(){cout << "I'm " << name << "I can eat" << endl;}void sleep(){cout << "I'm " << name << "I can sleep" << endl;}private:char name[256];};

  通过class 的封装之后,在使用animal 或point的时候,是通过animal 或point的object去操作的。这样看起来程序的组织逻辑会比对上面C语言对point和animal清晰一些,条理一些。当然,就算是在C++中,不同功能的class最好还是封装在不同的文件里面。(个人理解)

C++ class的封装与C语言相比效率如何?

  在不考虑继承、多态、内存对齐的情况下,C++ object在内存上的开销就等同于这个object 的数据成员之和了。而且在无继承无多态的情况下,函数(成员函数)的调用,C语言和C++的效率应该是不相上下的,引用对象模型中的一句话 "members function虽然包含class 的声明之中,却不出现在object之内,每一个non-inline member function只会诞生一个函数实体,至于每一个“拥有0个或多个定义”的inline function则会在其每一个调用者模块身上参生一个函数实体...C++在布局以及存取时间上主要的额外负担是由virtual引起的 "。(总结于《深入探索C++对象模型》)

  MSDN  的一篇文章 “C++ under the Hood” 对C++的内存布局有这么一段的描述: "...except for hidden data members introduced to implement virtual functions and virtual inheritance, the instance size is solely determined by a class’s data members and base classes."

  既然说C++中class 的成员函数不在object之内(是成员函数而不是虚函数或虚继承)。那么在程序的看来只要取得class中的成员函数。那么即使在没有这个class的object情况下也是可以调用这个成员函数。如下面的测试:

class point {
public:void show(){cout << "point show" << endl;}
private:int a;
};int main(void)
{  point *pt = NULL;pt->show();return 0;
}

  如果拿C来比较的话,它的调用方式大概等于这样:

void show (point *this)
{printf ("point show\n");
}....//调用方式
show (NULL);

  这个例子,也从侧面说明了C++是怎么对成员函数的处理。要注意的是,如果show函数中包含了对class 中数据成员的使用,那么程序肯定崩溃。因为传入NULL的话,this所指的压根就不存在对象。

  《深入探索C++对象模型》有一段话说C++ class 在无继承,无多态(成员函数不含virtual), 无构造函数/析构函数(有构造函数/析构函数的话,object 在定义和释放的时候会调用构造函数/析构函数费去一定的时间)的情况下。效率至少要达到C语言的效率。在某种程度来说C++ 的效率不会低于C语言的效率。如果C语言想要像C++那样模拟实现封装、多态,那么会比C++付出更大的代价。

C++ class中 access level 对封装的影响

  访问控制(access level)主要的影响还是在C++的内存布局上。

  MSDN 中 C++ under the Hood有这么一段话:"There are public/protected/private access control declarations, member functions, static members, and nested type declarations. Only the non-virtual data members occupy space in each instance. Note that the standards committee working papers permit implementati ons to reorder data members separated by an access declarator, so these three members could have been laid out in any order. (In Visual C++, members are always laid out in declaration order, just as if they were members of a C struct)"

  其中应该注意的是 "Note that the standards committee working papers permit implementati ons to reorder data members separated by an access declarator, so these three members could have been laid out in any order" 这句话。这句话的大概意思是说,C++标准委员会并未对声明在不同的acess level各段之间的数据成员在实例化时的内存布局并没有进行强制的规定。因此,不同的编译器可以不同的做法。但是目前流行的编译器 VC 和 GCC都是采取按照声明的先后进行内存布局。

总结

  封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

  封装提供给开发用户一个更好的程序组织的逻辑思路,而面向对象的语言的封装方式提供了更好的封装便利。

  在《深入探索C++对象模型》中,对于上面介绍的那种简单的封装称之为抽象数据类型模型(abstract data type model, ADT)(接触过数据结构一定对ADT不陌生)。而面向对象模型,是通过一个抽象的base class(提供共同接口)被封装起来。(我个人感觉,设计模式似乎就是面向对象模型)。(对《深入探索C++对象模型》的概括)

  (总结仅是个人理解,若有误导之处,那么抱歉)

转载于:https://www.cnblogs.com/Jer-/p/3192514.html

对象模型学习总结 (一) . 关于封装相关推荐

  1. 视音频编解码学习工程:TS封装格式分析器

    ===================================================== 视音频编解码学习工程系列文章列表: 视音频编解码学习工程:H.264分析器 视音频编解码学习 ...

  2. 视音频编解码学习工程:FLV封装格式分析器

    ===================================================== 视音频编解码学习工程系列文章列表: 视音频编解码学习工程:H.264分析器 视音频编解码学习 ...

  3. C++对象模型学习——站在对象模型的尖端

    2019独角兽企业重金招聘Python工程师标准>>> 这里要讨论三个著名的C++语言扩充性质,它们都会影响C++对象.它们分别是template. exception handli ...

  4. libevent源码学习----io多路复用的封装和使用

    因为是非阻塞监听事件的发生,所以内部其实还是采用io多路复用函数实现的. 又因为可供选择的io函数很多,linux下有epoll, poll, select等,window下有ICOP, select ...

  5. java学习之面向对象和封装

    面向过程:当需要实现一个功能的时候,每一个具体的步骤都要亲力亲为,详细处理每一个细节. 面向对象:当需要实现一个功能的时候,不关系具体的步骤,而是找一个已经具有该功能的人帮忙做事情. 面向过程强调步骤 ...

  6. Java学习第十二天<封装详解><继承><super详解><方法重写><多态>

    封装详解 //类 public class Student {//名字 学号 性别 属性私有(new 以后不能赋值)private String name;private int id;private ...

  7. Python 学习笔记 类的封装 类的继承 多态继承 类方法和静态方法 单例设计模式

    一.类的封装: 1.概念: 广义的封装:函数和类的定义本身,就是封装的体现 狭义的封装:一个类的某些属性,在使用的过程 中,不希望被外界直接访问,而是把这个属性给作为私有的[只有当前类持有],然后暴露 ...

  8. PCB入门学习— CHIP类PCB封装的创建

    目录 2.12 原理图PCB封装完整性的检查 3.1 CHIP类PCB封装的创建 学习目录 2.12 原理图PCB封装完整性的检查 然后点接受变更. www.digikey.com搜索规格的网站. 3 ...

  9. Python学习:多态与封装

    多态:指的是一类事物有多种形态,也就是一个对象的类型,动物有多种形态:人,狗,猪. 多态性:是指在不考虑实例类型的情况下都可以使用实例. 封装:面向对象的思想本身就是一种封装,让特有对象能够调用类中的 ...

  10. 微信小程序学习:使用picker封装省市区三级联动模板

    目前学习小程序更多的是看看能否二次封装其它组件,利于以后能快速开发各种小程序应用.目前发现picker的selector模式只有一级下拉,那么我们是否可以通过3个picker来实现三级联动模板的形式来 ...

最新文章

  1. redis字符串匹配_Redis的数据类型和抽象概念介绍
  2. android 显示yuv数据格式,YUV数据格式的理解
  3. SAP SD 关于信用管理--信用更新
  4. [Angular 2] @ngrx/devtools demo
  5. C++ STL之map常用指令
  6. arm单片机中函数参数的传递
  7. 汇编语言--串处理指令
  8. java创建具体时间点_java单例饿汉模式对象创建时间点疑问
  9. margin 等高布局
  10. SQL迁移到ORACLE实例
  11. 计算机一级综合第九套试题及答案,2012年计算机一级B第九套选择题精选及参考答案...
  12. MSCRM4.0显示图片格式附件
  13. c语言笔试题大题带答案,c语言常见笔试题及答案
  14. 报错——xxx is not defined
  15. Windows Metro Style颜色色值表
  16. php 恶意上传,如何防止恶意文件上传到我的服务器上?(检查文件类型)?
  17. 使用deno和oak开发的短链系统2.0
  18. PS出现“不能完成命令,因为没有足够内存(RAM)”的解决方案
  19. GC是什么?为什么要有GC
  20. O2耳放 DIY 模拟放大

热门文章

  1. 计算机国内期刊sci,国内计算机类期刊 SCI收录:
  2. 计算机专业比较好的加州州立,美国计算机专业大学排名前十
  3. 戴尔服务器加无线网卡用不了网,电脑安装了无线网卡却不能用是怎么回事?
  4. 【数据结构】一张图让你读懂:树的高度、深度、层的区别
  5. 【线性代数(1)】二阶三阶不等式
  6. win误删计算机桌面快捷方式,win10系统找回桌面被误删快捷方式的图文教程
  7. 华为交换机配置syslog发送_配置华为交换机推送syslog到日志服务器
  8. Linux/Unix如何将日志发送到日志服务器
  9. npm ERR! code EINTEGRITY npm! ERR! shal-
  10. 1400——507B、1370C、1363B、1324D、1365C、1374D