笨鸟先飞学编程系列浅析C++的封装性文档格式.docx

上传人:b****6 文档编号:20692417 上传时间:2023-01-25 格式:DOCX 页数:16 大小:58.03KB
下载 相关 举报
笨鸟先飞学编程系列浅析C++的封装性文档格式.docx_第1页
第1页 / 共16页
笨鸟先飞学编程系列浅析C++的封装性文档格式.docx_第2页
第2页 / 共16页
笨鸟先飞学编程系列浅析C++的封装性文档格式.docx_第3页
第3页 / 共16页
笨鸟先飞学编程系列浅析C++的封装性文档格式.docx_第4页
第4页 / 共16页
笨鸟先飞学编程系列浅析C++的封装性文档格式.docx_第5页
第5页 / 共16页
点击查看更多>>
下载资源
资源描述

笨鸟先飞学编程系列浅析C++的封装性文档格式.docx

《笨鸟先飞学编程系列浅析C++的封装性文档格式.docx》由会员分享,可在线阅读,更多相关《笨鸟先飞学编程系列浅析C++的封装性文档格式.docx(16页珍藏版)》请在冰豆网上搜索。

笨鸟先飞学编程系列浅析C++的封装性文档格式.docx

m_nSecNum=0;

}//构造函数

~CExample(){}//空析构

};

当然,上面这个类的定义是不是很像定义一个结构体?

只不过多了个private和public还有一些函数。

是的,C++里面,将结构体升级了,结构体里面可以有函数成员了,为了兼容,换了个关键字,当然,上面的这个class完全可以改成struct,一点问题都没有。

好奇的朋友会问:

如果函数体的语句太多,逻辑复杂了,函数很大,那这个类岂不是很难看,太臃肿了吧。

是的,为了方便类的组织,也为了协调项目工程中文件的组织。

上面的类还可以写成如下的形式:

//.h文件中写如下的声明部分

//权限控制,防止外面直接操作这些变量,相关内容在下面的小节中详细讲述

intGetSum()const;

//成员函数

boolSetNum(intnFirst,intnSec);

CExample();

//构造函数

~CExample();

//空析构

//.cpp文件中写如下的定义及实现部分

CExample:

:

CExample()

~CExample()

intCExample:

GetFirstNum()const

returnm_nFirstNum;

GetSecNum()const

returnm_nSecNum;

boolCExample:

SetNum(intnFirst,intnSec)

GetSum()const

returnm_nFirstNum+m_nSecNum;

上面两种写法也是有区别的,第一种方法写的函数具有Inline函数的特性。

后一种则没有。

2、属性和方法的使用

C++中定义一个对象跟定义一个函数没有什么区别。

#include<

stdio.h>

#include"

Example.h"

intmain(intargc,char*argv[])

CExampleobj_Exp;

//定义一个对象

obj_Exp.SetNum(10,20);

//调用一个方法

printf("

%d+%d=%d\r\n"

obj_Exp.GetFirstNum(),

obj_Exp.GetSecNum(),

obj_Exp.GetSum());

return0;

由此,我们就可以通过一个函数间接的来操作我们的变量,用户在给我们的变量赋值时,我们可以通过Set函数来对输入的内容作检测,在获取一个变量的内容时,我们可以通过一个函数来取得,这样都提高了程序安全性。

也许,有的朋友会说,如果我绕过你提供的函数,直接对_nFirstNum;

和m_nSecNum;

进行操作不是一样不安全么,是的,这就是为什么我在程序中加上了private的原因。

下面我们详细说明下这些关键字的含义。

private、public、protected三个关键字,是C++提供给并实现类封装的关键,它们用来说明类外的代码可以直接访问类内的什么哪些成员,哪些成员不可能被外面访问。

private:

说明,它后面所有的变量和函数,都不可能被类外访问,只能在类内被使用。

public:

说明,它后面的所有变量和函数可以被类外的代码所访问,没有任何限制。

protected:

说明,它后面的所有变量和函数,只能被自己或自己派生的类所使用。

不能被类外的代码使用。

这个我们将在C++继承性中详细讨论。

从程序设计的角度来讲,如果我们以类为单位编码的话,每个模块都是独立的我们只要关注与本类相关操作,比如人这个类,它一般情况下有两个眼睛、一个嘴巴等之类的属性,人可以使用工具,可以行走,可以跳跃等方法。

我们编写的所有的函数都是针对这些展开的。

而不用去关心谁要使用这个类。

因此,类/对象概念的加入,不单单是给编码方式做了改变,主要是设计思路的改变,程序模块化的改变等等。

3、关于常成员函数

相信我们讲过的const与inline相关的知识,大家一定都没有忘记,是的,你猜对了,现在我们要说的就是const的一个扩展用法。

当然,不用担心,这只是一个小小的扩展,不用担心混淆杂乱:

当我们的成员函数不允许修改我们类中的成员内容时,可以在函数的参数列表后加上一个const关键字。

以免以后不小心更改了我们的类中成员属性。

二、解析对象的内存结构

现在,我相信,如果习惯了我这种学习方式的朋友一定会很好奇,类定义对象的内存格式是怎样的,它是不是像一个普通变量那样,或者是不是像一个结构体变量那样在内存里连续的将各个成员组织到一起,它又是怎样将各个成员变量还有函数绑定到一起的?

变量和函数在一起它是怎么组织的?

本小节让我们来解决这些问题。

为节省篇幅,我们仍旧使用上面的代码。

我们用VC的调试器,调试这个代码:

注意看我们的变量监视区,我们定义的对象的内容跟结构体成员的内容格式差不多,(是按照定义的顺序连续存放的,这点跟普通的局部变量不一样,普通的局部变量在内存中的顺序与定义顺序相反)内存中只存放了成员变量,它并没有标出SetNum的位置,那它是怎么找到SetNum这个函数的呢?

根据我们先前调试C函数的经验,我们知道,函数的代码是被放在可执行文件的代码区段中的。

在这个代码中,也有调用SetNum的代码,我们详细的跟一下它的汇编代码:

10:

004011EDleaecx,[ebp-14h]

004011F0call@ILT+15(CExample:

CExample)(00401014)

004011F5movdwordptr[ebp-4],0

11:

004011FCpush14h

004011FEpush0Ah

00401200leaecx,[ebp-14h]

00401203call@ILT+0(CExample:

SetNum)(00401005)

这段代码又给我们带来了新的问题,我们只用类定义了一个对象(变量),它自动的调用了一个函数,根据注释我们知道它调用的是构造函数。

我们跟进去看下:

CExample:

12:

{

00401050pushebp

00401051movebp,esp

00401053subesp,44h

00401056pushebx

00401057pushesi

00401058pushedi

00401059pushecx;

保存寄存器环境

0040105Aleaedi,[ebp-44h]

0040105Dmovecx,11h

00401062moveax,0CCCCCCCCh

00401067repstosdwordptr[edi];

将栈空间清为CC(Release编译就没有这部分代码了。

00401069popecx

0040106Amovdwordptr[ebp-4],ecx;

将ECX中的内容给局部变量

13:

}

0040106Dmoveax,dwordptr[ebp-4];

将ECX的内容返回

00401070popedi

00401071popesi

00401072popebx

00401073movesp,ebp

00401075popebp

00401076ret

这段代码,首次看还真看不出个所以然来,源码的构造函数中,我们什么都没写,是个空函数,而这里做的是返回ECX的值,可是这个函数也没有对ECX做什么特别的操作,而是直接使用进函数时ECX的值。

那只能说明在调用这个函数前,ECX发生了变化。

我们再回头看下调用构造函数的代码:

哈哈,它是把我们obj_Exp对象的地址给了ECX,然后调用构造返回的,也就是说,构造的返回值是我们对象的首地址。

哎,迷糊了,真搞不懂这是在干什么。

先不管他,我们继续看怎么调用的SetNum这个函数吧:

004011FCpush14h;

传递参数

00401200leaecx,[ebp-14h];

也有这句,还是把我们的对象首地址给ECX

29:

boolCExample:

30:

00401130pushebp

00401131movebp,esp

00401133subesp,44h

00401136pushebx

00401137pushesi

00401138pushedi

00401139pushecx

0040113Aleaedi,[ebp-44h]

0040113Dmovecx,11h

00401142moveax,0CCCCCCCCh

00401147repstosdwordptr[edi]

00401149popecx

0040114Amovdwordptr[ebp-4],ecx;

备份一下我们的对象首地址

31:

m_nFirstNum=nFirst;

0040114Dmoveax,dwordptr[ebp-4];

取出对象首地址

00401150movecx,dwordptr[ebp+8];

取出nFirst参数

00401153movdwordptr[eax],ecx;

给对象首地址指向的内容赋值为nFirst的内容

32:

m_nSecNum=nSec;

00401155moveax,dwordptr[ebp-4];

00401158movecx,dwordptr[ebp+0Ch];

取出nSec参数

0040115Bmovdwordptr[eax+4],ecx;

给对象首地址+4指向的你内容赋值

returntrue;

0040115Emoval,1;

返回1

34:

00401160popedi

00401161popesi

00401162popebx

00401163movesp,ebp

00401165popebp

00401166ret8

我简要的注释下来一下上面的代码。

通过分析上面的代码,我们可以得出这样的结论:

A、函数通过ecx传递了我们对象的首地址。

B、函数通过ecx传递的对象首地址定位对象的每个成员变量。

这样,很明显,ECX起到了传递参数的作用,这时ecx中的地址有个专业术语,叫做this指针。

OK,这就是一个新的知识点,我们成员函数的调用方式。

1、成员函数的调用方式:

__thiscall

记得在前面章节介绍函数时,讲过一些调用方式,但是没有提到过这种调用方式。

下面我做一个简要的总结:

A、参数也通过栈传递。

B、它用一个寄存器来传递this指针。

C、本条特性摘自《加密与解密》(第三版)非原文:

a)对于VC++中传参规则:

i.最左边两个不大于4字节的参数分别用ECX和EDX传参数.

ii.对于浮点数、远指针、__int64类型总是通过栈来传递的。

b)对于BC++|DELPHI中的传递规则:

i.最左边三个不大于DWORD的参数,依次使用EAX,ECX,EDX传递,其它多的参数依次通过PASCAL方式传递。

这样,函数的地址还是在代码区域,对象的内存中只存放数据成员,当我们要调用成员函数时,就通过一个寄存器将函数操作的对象的首地址(也就是this指针)传递过去就可以了,传递不同的对象指针,就操作不同的数据。

哈哈,太巧妙了。

2、浅谈构造与析构函数

OK,继续调试代码:

obj_Exp.GetFirstNum(),

14:

obj_Exp.GetSecNum(),

15:

00401208leaecx,[ebp-14h]

0040120Bcall@ILT+30(CExample:

GetSum)(00401023);

调用GetSum函数

00401210pusheax

00401211leaecx,[ebp-14h]

00401214call@ILT+5(CExample:

GetSecNum)(0040100a)

00401219pusheax

0040121Aleaecx,[ebp-14h]

0040121Dcall@ILT+10(CExample:

GetFirstNum)(0040100f)

00401222pusheax

00401223pushoffsetstring"

(0042501c)

00401228callprintf(00401290)

0040122Daddesp,10h

16:

17:

00401230movdwordptr[ebp-18h],0

00401237movdwordptr[ebp-4],0FFFFFFFFh

0040123Eleaecx,[ebp-14h]

00401241call@ILT+20(CExample:

~CExample)(00401019);

调用析构函数

00401246moveax,dwordptr[ebp-18h]

我们至始至终都没有调用过构造和析构函数。

但是,通过这次调我们知道,在创建一个对象(变量)的时候,我们的程序会自动的调用我们的构造函数,在要出对象作用域的时候,会自动的调用析构函数。

这样,我们很容易就能想象出,构造和析构的用途:

构造就做初始化对象的各个成员,申请空间等初始化工作。

析构就做一些释放申请的空间啊之类的清理工作。

就这样,C++将数据跟函数封装到了一起,这样我们每个类产生的对象都是一个独立的个体,它有一个自己的运作方式,几乎完全独立。

在我们使用它的时候,根本不需要它是怎么实现了,只要知道怎么使用即可。

三、浅谈类的静态成员

通过前面几节的学习,我们大概的能理解类的封装性及其运作过程,但是,如果我们继续深入的学习C++,我们很快就能发现一个问题:

我们上面说的所有的成员都是属于对象的,也就是说,我们必须先通过类来定义一个对象才可以操作。

但是有的时候,我们需要一些属于类的成员,比如:

人都有一个脑袋,这一个脑袋属于人类共有的特性。

不需要具体到哪一个人,我们都可以确定人只有一个脑袋。

放到类中也一样,比如我们需要知道当前这个类创建了几个对象的时候,我们不必在创建一个新的对象只需要使用类的相关函数或者直接访问类的某些属性就可以了,而这些函数或者变量,它肯定不可能属于某个对象,它应该属于这个类本身。

OK,下面就来体验一下静态带给我们的一些好处。

同样,我们将前面的代码添加点儿东西(见Exp02):

//.h文件中

staticintm_nCount;

//统计产生对象的

staticintprint(constchar*szFormat,...);

//让我们的类有自己的输出函数

//.cpp文件中

m_nCount=0;

//初始化静态成员变量

m_nCount++;

//当创建一个对象的时候,这个变量加1

if(m_nCount>

0)

m_nCount--;

//当对象销毁时,这个变量减1

/************************************************************************/

/*让我们的CExample可以打印自己的信息

/*支持多参,同printf用法相同

print(constchar*szFormat,...)

if(!

szFormat)

va_listpArgs;

charszBuffer[256*15]={0};

va_start(pArgs,szFormat);

vsprintf(szBuffer,szFormat,pArgs);

va_end(pArgs);

printf(szBuffer);

returnstrlen(szFormat);

好,有了这些,我们可以编写如下的测试代码:

stdafx.h"

CExampleobj_Exp1;

print("

当前对象的数量为:

%d\r\n"

CExample:

m_nCount);

if

(1)

CExampleobj_Exp2;

//该对象属于if作用域,出了if,对象自动销毁

我想大家应该能想象出来运行的结果:

好,我们调试一下这段程序:

004012ECmoveax,[CExample:

m_nCount(0042ae6c)];

这明显告诉我们,静态就是全局

004012F1pusheax

004012F2pushoffsetstring"

%d\r\n"

004012F7call@ILT+30(CExample:

print)(00401023);

调用该静态函数没有传递this指针

004012FCaddesp,8

多了不用看了,通过这段代码,我们很明显就可以清楚,静态变量,不属于类对象,它存放于全局数据区,同全局变量在一个地方(更多关于静态变量的相关说明见我发的《static学习笔记》一文)。

静态函数,跟全局函数一样,它虽然在源码中书写与类内,但是它其实就是一个全局函数,不传递this指针,因此,在使用静态函数时需要知道,静态函数中不能调用其它普通的成员函数也不能引用普通的成员变量。

但是反过来,在其它的成员函数中可以调用静态函数也可以使用静态变量。

四、说一下初始化列表

现在让我们考虑一个问题:

倘若我们的类中有其它类成员、有引用成员、有常量成员时,应该怎么初始化它们呢?

是啊,如果我们在头文件中声明它们的时候,直接给它们初始化很明显是要报错的。

这时,我们的主角,初始化列表就上场了。

(见Exp03):

假如,我们有如下一个类:

classCBaseExp

CBaseExp(char*pszStr)

intnLength=strlen(pszStr)+1;

if(1>

=nLength)

return;

m_pszBuff=newchar[nLength];

strcpy(m_pszBuff,pszStr);

~CBaseExp(){};

char*m_pszBuff;

//需要临时分配堆空间

现在,我们在我们的CExample中增加如下几个成员:

//为了描述方便,我直接使用public方式。

CBaseExpm_ObjBE;

//这个对象需要初始化(用到初始化列表)

int&

m_nFirst;

//需要初始化(用到初始化列表)

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 经管营销 > 经济市场

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1