Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1759597
  • 博文数量: 290
  • 博客积分: 10653
  • 博客等级: 上将
  • 技术积分: 3178
  • 用 户 组: 普通用户
  • 注册时间: 2007-10-24 23:08
文章存档

2013年(6)

2012年(15)

2011年(25)

2010年(86)

2009年(52)

2008年(66)

2007年(40)

分类: C/C++

2010-11-14 22:47:40


【文章标题】: 逆向C++之封装性 - 类和对象
【文章作者】: kanghtta
【作者邮箱】: kanghtta@hotmail.com
【作者主页】: kanghtta.cublog.cn
【下载地址】: 自己搜索下载
--------------------------------------------------------------------------------
【详细过程】
     大家好,由于前段时间写了一部分逆向C++的内容,后来又因为考研和放假耽误了一段时间,希望大家谅解;好了,开始正题;
  我们知道,面向对象的设计思想,近来成长的非常快;而c++是从C语言发展而来的;
  
  这一节我们开始接触面向对象的第一个特性,封装性;首先,我们来回顾一下基本的概念;
  
  学过java或者是任何一门面向对象语言的人对类和对象也许都不算陌生,而面向对象中其实应用最广泛的,莫过于继承
  和多态性,而封装性,是学习后面的基础,我从高中开始,在学习上的座右铭: 基劳楼自高,水到渠自成;
  好了废话太多,我们开始:
  
  1:类和对象:
     我们知道,类是对数据类型高度抽象的具体表现,它是一种类型,这种类型的特点就是将所描述的客观事物的不同类型
  的数据和对它们的操作封装成为一个集合体;在数据结构种,类也称为数据类型;
     
     数据类型:在高级语言编写的程序中,每个变量,常量和表达式都必须有一个它所属的的确定的数据类型,类型明显
  或隐含地规定了在程序执行期间变量或表达式所有可能取值的范围,以及在这些值上允许的操作。因此,数据类型,是
  一个值的集合和定义在这个集合上一组操作的总称。
  
    类的分类: 类在语言中有原子类型也称为基本类型,如 int ,float,double,等常用的数据类型,这些类型在任何一门
  语言中都是存在的,从内存的角度来看,它们的长度不同,而且表示方式也不同,int一般是整数,在计算机中一般用补码
  表示,而float,double是浮点数表示,关于定点数和浮点数的的数制表述,大家请参考计算机组成原理;
      而我们在这一章中主要做的就是用上面的基本数据类型来描述我们现实生活中需要处理的对象; 这在以前被称为
  结构类型;由struct定义,有一定c++基础的人都知道,struct和class的作用是一样的,它们都可以作为定义类的关键字,
  不同的是,它们定义的类,默认的访问权限不同; struct 默认为public,而class默认为private;至于什么是访问权限,
  我会在下面解释;
  
  什么是对象?
      对象是类的实例化,比如人类就是一个抽象的类,而你或者我就是人类的一个实例化;类和对象的关系,
  是抽象和具体的关系; 
  
  2;类的定义格式
    类的定义格式,分为两部分;--- 说明部分和实现部分;
  
  a:)说明部分用类说明该类中的若干成员,包括被说明的数据成员和成员函数;成员函数也称为方法,用来对数据成员进行
  操作,数据成员的说明包含数据成员的名字和类型;成员函数可以在类体内只有说明,也可以在类体内对它定义(内联和外联);
  
  b:)实现部分,主要用来对类体类只有说明而没有定义的函数进行定义; 而如果一个类的所有成员函数都定义在类体内,
  该类就没有实现部分了;
  
  简单点来说: 程序员在创建类的时候就创建了一种类型,提供了具有所定义的类的属性空间;对于复杂的对象,类能很方便
  地在一个容器中把所需要用来描述对象的细节包装起来,这就是数据封装的意义;
  
  下面我们来看一个例子:
  
  我们一般将类定义在头文件中如下:
  
代码:
#include
using namespace std;

class  CDate
{
public:
  void  SetDate(int y,int m,int d);
  bool  IsLeapYear();
  void  Print();
private:

  int    year,
      month,
      day;
};

void  CDate::SetDate(int y,int m,int d)
{
  year = y;
  month = m;
  day   = d;
}

bool  CDate::IsLeapYear()
{
  return(year%4 == 0 && year % 100 !=0)||(year%400 == 0);
}

void  CDate::Print()
{
  cout<}
  

代码:
#include
#include "CDate.h"
using  namespace  std;

int  main()
{
  CDate  d1,
      d2,
      &rd = d2;
  d1.SetDate(2009,1,25);
  d2.SetDate(2008,1,25);
  d1.Print();
  d2.Print();
  rd.Print();
  return  0;
}
  访问权限:类体内的成员有3种不同的访问权限
  
  1:public  公有成员不仅能被类体内的成员函数访问,还可被类体外的程序访问;公有成员提供了类与外界的接口;
  通常将类中的成员全部和部分的定义为公有成员;
     
  2:private 私有成员只有该类的成员函数或者是友员函数才可访问,类体外的程序不可访问,因此,私有成员是封装体
  被隐藏的那一部分,通常将全部或者部分的数据成员定义为私有成员;私有成员在类体外不可见;
  
  3:protected 保护成员在不同的条件下具有公有成员和私有成员的特性;对单个类来将,保护成员和私有成员没有什么
  区别,该类的对象都不访问;如在程序中加入:
           &rd = d2;
    d1.day = 2;
  编译程序: error:
   E:\c++ 语言学习\date.cpp(10) : error C2248: 'day' : cannot access private member declared in class 'CDate'
  
  单在继承的情况下,保护成员又不同于私有成员,基类中的保护成员可被派生类中的成员函数访问,而基类中私有成员不可被派生类访问;
  保护成员又与公有成员不同,基类中公有成员在公有继承的情况下可被派生类的对象访问,而基类中的保护成员在任何继承的方式下也不能被
  派生类的对象访问;
  
  下面我们类看c++中比较重要的一个基础: 构造函数,和虚构函数;
  
  创建对象时,系统将自动调用构造函数对所创建的对象进行初始化,也就是让对象的数据成员获取初值。对象的初值是通过
  自动调用构造函数获取值。对象数据成员的值可以通过赋值,或者通过调用已有的成员函数来改变;
  
  对象数据成员的值也就是对象的值,所以改变对象的值其实是改变对象中某一数据成员的值;我们知道,对象是类的一个实例化
  ,在给对象分配内存的时候,只分配该对象的数据成员所占用的空间,而成员函数是公用的,在每个对象中不包含成员函数
  的空间;所以,在给对象初始化和赋值时,只考虑对对象的数据成员进行初始化和赋值就可以了;
  
  我们通过OD来看一下:
   在上面类的定义中,我们定义了三个数据成员和三个成员函数,
  
  OD载日debug文件夹中的date.exe文件; 首先定位到main()函数入口,关于方法前面我已经介绍过了;
  
  00421EFB  |.  E8 709E0000   call    0042BD70
  00421F00  |.  FF15 9CF14700 call    dword ptr [<&KERNEL32.GetCommand>; [GetCommandLineA
  00421F06  |.  A3 24EF4700   mov     dword ptr [47EF24], eax
  00421F0B  |.  E8 409C0000   call    0042BB50
  00421F10  |.  A3 84D34700   mov     dword ptr [47D384], eax
  00421F15  |.  E8 26970000   call    0042B640
  00421F1A  |.  E8 D1950000   call    0042B4F0
  00421F1F  |.  E8 DC400000   call    00426000
  00421F24  |.  8B0D DCD34700 mov     ecx, dword ptr [47D3DC]
  00421F2A  |.  890D E0D34700 mov     dword ptr [47D3E0], ecx
  00421F30  |.  8B15 DCD34700 mov     edx, dword ptr [47D3DC]
  00421F36  |.  52            push    edx
  00421F37  |.  A1 D4D34700   mov     eax, dword ptr [47D3D4]
  00421F3C  |.  50            push    eax
  00421F3D  |.  8B0D D0D34700 mov     ecx, dword ptr [47D3D0]
  00421F43  |.  51            push    ecx
  00421F44  |.  E8 ECF2FDFF   call    00401235                      //main() 函数入口
  
  F7跟进,
  
  00401580  /> \55            push    ebp
  00401581  |.  8BEC          mov     ebp, esp
  00401583  |.  83EC 5C       sub     esp, 5C
  00401586  |.  53            push    ebx
  00401587  |.  56            push    esi
  00401588  |.  57            push    edi                       ;执行环境保存
  00401589  |.  8D7D A4       lea     edi, dword ptr [ebp-5C]
  0040158C  |.  B9 17000000   mov     ecx, 17
  00401591  |.  B8 CCCCCCCC   mov     eax, CCCCCCCC
  00401596  |.  F3:AB         rep     stos dword ptr es:[edi]
  00401598  |.  8D45 E8       lea     eax, dword ptr [ebp-18]     ;
  0040159B  |.  8945 E4       mov     dword ptr [ebp-1C], eax     
  0040159E  |.  6A 19         push    19
  004015A0  |.  6A 01         push    1
  004015A2  |.  68 D9070000   push    7D9
  004015A7  |.  8D4D F4       lea     ecx, dword ptr [ebp-C]
  004015AA  |.  E8 96FBFFFF   call    00401145                        ;d1.SetDate(2009,1,25);
  004015AF  |.  6A 19         push    19
  004015B1  |.  6A 01         push    1
  004015B3  |.  68 D8070000   push    7D8
  004015B8  |.  8D4D E8       lea     ecx, dword ptr [ebp-18]
  004015BB  |.  E8 85FBFFFF   call    00401145                        ;d2.SetDate(2008,1,25);
  004015C0  |.  8D4D F4       lea     ecx, dword ptr [ebp-C]
  004015C3  |.  E8 A1FAFFFF   call    00401069                        ;  d1.Print();
  004015C8  |.  8D4D E8       lea     ecx, dword ptr [ebp-18]
  004015CB  |.  E8 99FAFFFF   call    00401069                        ;  d2.Print();
  004015D0  |.  8B4D E4       mov     ecx, dword ptr [ebp-1C]
  004015D3  |.  E8 91FAFFFF   call    00401069                        ;  rd.Print();
  004015D8  |.  33C0          xor     eax, eax
  004015DA  |.  5F            pop     edi
  004015DB  |.  5E            pop     esi
  004015DC  |.  5B            pop     ebx
  004015DD  |.  83C4 5C       add     esp, 5C
  004015E0  |.  3BEC          cmp     ebp, esp
  004015E2  |.  E8 89F40100   call    00420A70
  004015E7  |.  8BE5          mov     esp, ebp
  004015E9  |.  5D            pop     ebp
  004015EA  \.  C3            retn
  
  
  我们看下堆栈结构
  0012FEE0   00000000
  0012FEE4   00000000
  0012FEE8   7FFDC000
  0012FEEC   CCCCCCCC      ;rep stos dword ptr es:[edi] 
  0012FEF0   CCCCCCCC
  0012FEF4   CCCCCCCC
  0012FEF8   CCCCCCCC
  0012FEFC   CCCCCCCC
  0012FF00   CCCCCCCC
  0012FF04   CCCCCCCC
  0012FF08   CCCCCCCC
  0012FF0C   CCCCCCCC
  0012FF10   CCCCCCCC
  0012FF14   CCCCCCCC
  0012FF18   CCCCCCCC
  0012FF1C   CCCCCCCC
  0012FF20   CCCCCCCC
  0012FF24   CCCCCCCC
  0012FF28   CCCCCCCC
  0012FF2C   CCCCCCCC                    ;rd引用
  0012FF30   CCCCCCCC       ;ebp-18     ;d1对象
  0012FF34   CCCCCCCC       ;ebp-14
  0012FF38   CCCCCCCC       ;ebp-10
  0012FF3C   CCCCCCCC       ;ebp-c     ;d1对象
  0012FF40   CCCCCCCC
  0012FF44   CCCCCCCC       ;ebp-4
  0012FF48  /0012FF88       ; ebp 的值
  0012FF4C  |00421F49  返回到 date.<模块入口点>+0E9 来自 date.00401235
  
  
  我们一直F8,到4015D8时,我们来看看堆栈中的情况:
  EBP-20   > CCCCCCCC
  EBP-1C   > 0012FF30       ;&rd
  EBP-18   > 000007D8       ;d2对象
  EBP-14   > 00000001
  EBP-10   > 00000019
  
  EBP-C    > 000007D9       ; d1对象
  EBP-8    > 00000001
  EBP-4    > 00000019
  
  EBP ==>  >/0012FF88       ; 栈帧
  
  
  我们先来看看对象的初始化:
  
     由于对象的声明是在main函数的函数体内,所以对它的初始化是在栈中,我们说过,在对象的初始化时,只分配
  数据成员的地址空间,而成员函数是公用的,我们来看下,在CDate类中,数据成员是3个int型的数据;占12个字节;
  数据成员声明的语句和对应的反汇编语句如下:
  
  CDate  d1,
          d2,
    &rd = d2;
  
  我们从上面一个的栈帧中可以看出,在初始化对象时,只是为对象分配了空间,如下:
  
  0012FF2C   CCCCCCCC                    ;rd引用
  0012FF30   CCCCCCCC       ;ebp-18     ;d1对象
  0012FF34   CCCCCCCC       ;ebp-14
  0012FF38   CCCCCCCC       ;ebp-10
  0012FF3C   CCCCCCCC       ;ebp-c     ;d1对象
  0012FF40   CCCCCCCC
  0012FF44   CCCCCCCC       ;ebp-4
  
  00401598  |.  8D45 E8       lea     eax, dword ptr [ebp-18]   ;d1
  0040159B  |.  8945 E4       mov     dword ptr [ebp-1C], eax   ;&rd
  
  
  004015A7  |.  8D4D F4       lea     ecx, dword ptr [ebp-C]    ;d2
  
  从上面可以看出,在c++产生对象时,上面的只是在栈中生成对象的方法;而在C++ 中
  内存有,堆区,栈区,静态区等区分,所以这也只是一种方法;
  
  我们在看看成员函数的调用: 
  
  004015A7  |.  8D4D F4       lea     ecx, dword ptr [ebp-C]
  004015AA  |.  E8 96FBFFFF   call    00401145                        ;d1.SetDate(2009,1,25);
  004015AF  |.  6A 19         push    19
  004015B1  |.  6A 01         push    1
  004015B3  |.  68 D8070000   push    7D8
  004015B8  |.  8D4D E8       lea     ecx, dword ptr [ebp-18]
  004015BB  |.  E8 85FBFFFF   call    00401145                        ;d2.SetDate(2008,1,25);
  004015C0  |.  8D4D F4       lea     ecx, dword ptr [ebp-C]
  004015C3  |.  E8 A1FAFFFF   call    00401069                        ;  d1.Print();
  004015C8  |.  8D4D E8       lea     ecx, dword ptr [ebp-18]
  004015CB  |.  E8 99FAFFFF   call    00401069                        ;  d2.Print();
  004015D0  |.  8B4D E4       mov     ecx, dword ptr [ebp-1C]
  004015D3  |.  E8 91FAFFFF   call    00401069                        ;  rd.Print();
  
  这就证明了我们上面那段话,在为对象分配内存时,只分配数据成员的空间,而成员函数公用;因为对类的相同成员函数调用
  的地址都是相同的; 
  void  SetDate(int y,int m,int d);    的地址是0040115;
    void  Print();               的地址是00401069;
  
  不论是产生什么类型的对象,(这里从存储角度来考察),c++都会产生构造函数的调用操作,在堆中或者是在栈中
  生成对象时,C++都会在配置内存后立即产生一个隐含的构造函数调用,在程序代码中我们是看不出来的,而如果是产生
  一个全局对象,由于对象是全局的,它的作用域在任何函数活动范围之外,显然更没有地方来安置这样一个构造函数了;
  
  我们下面修改一下源程序来看下:
  #include
  using namespace std;
  
  class  CDate
  {
  public:
    void  SetDate(int y,int m,int d);
    bool  IsLeapYear();
    void  Print();
    CDate();
  private:
    int    year,
        month,
        day;
  };
  
  void  CDate::SetDate(int y,int m,int d)
  {
    year = y;
    month = m;
    day   = d;
  }
  
  bool  CDate::IsLeapYear()
  {
    return(year%4 == 0 && year % 100 !=0)||(year%400 == 0);
  }
  
  void  CDate::Print()
  {
    cout<
  #include "CDate.h"
  using  namespace  std;
  
  CDate    GlobalObject;
  int  main()
  {
    CDate  d1;               //栈对象 
    CDate  *d2 = new CDate(); //堆对象 
    CDate  &rd = d1;          //引用对象
    static  CDate  StaticObject; //静态全局对象
  
    d1.SetDate(2009,1,25);
    d2->SetDate(2008,1,25);
    StaticObject.SetDate(2000,1,1);
    GlobalObject.SetDate(1987,1,1);
    
  
    GlobalObject.Print();
    StaticObject.Print();
    d1.Print();
    d2->Print();
    rd.Print();
    return  0;
  }
  
  我们将这个程序编译执行后,用Od载入,你会发现得到的结果大同小异,但又有很多不同之处,重点你应该放在不同类型的对象
  初始化上,这一点,由于时间关系,下一次在写; 大家又时间的自己揣摩下;
   
阅读(1825) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~