C++面向对象特性实现机制的初步分析 Part2

原创 2002年03月25日 08:49:00

本人beta版的毕业论文,请各位指正!

 

Chapter 1 准备知识

 

C++是一种面向对象的高级语言,要了解它的一些内部机制,我们有必要先熟悉其二进制代码的编译过程,并且要了解运行这些二进制代码时内存中各个区域的变化情况。

 

1.程序对内存的使用方法

代码区

全局数据区

堆区

栈区

code area

data area

stack area

heap area

任何需要CPU执行的程序都必须以二进制代码的形式存储在内存中,因此,程序代码在内存中的存储方式和结构是我们首先需要了解的。当程序得到操作系统分配给它的内存区域之后,它将这个区域分为四个部分,见右图:

1-1

这四个区域存储含有不同逻辑意义的二进制信息,请看下面的代码:

 

/////////////////////////////////////////////

//Source Code 1.1

#include <iostream.h>

 

static int a=5;

static int b=1;

 

class Test

{

public:   

       Test(){cout<<"Create!"<<endl;}

       ~Test(){cout<<"Destroy!"<<endl;}

private:

       int count;

};

 

int foo(int x, int y)

{

       return x+y;

}

 

void main(void)

{

       int i=0;

       i=foo(a,b);

       cout<<"i= "<<i<<endl;

 

       Test* pTest;

       pTest= new Test;

}

 

 

 

 

//静态全局变量,存储在data area

//静态全局变量,存储在data area

 

// Test, 用来测试对heap区域的使用

 

 

//构造函数

//析构函数

 

//私有数据

 

 

//函数,编译器生成代码后,将其存储在code

//area中,函数运行时的中间变量,如形参x,y

//以及x+y产生的临时变量,都保存在stack

//area中,在函数返回时,系统自动清空函数

//压入stack area内的变量

 

 

// main()函数为程序的入口,程序开始运行时,// CPU中的指令寄存器被设置为code area

// main()函数的位置

 

//声明指向Test类的指针

//new操作符在heap中构造Test类的实例

//并将指针pTest指向Test实例的入口地址

 

将上面的程序编译,系统将各个函数的代码存放到代码区,将静态变量,常量,全局变量保存在全局数据区。运行时,CPU首先执行main()函数中的代码,系统向栈区内压入main()函数的局部变量i,程序运行到对foo()函数的调用时,系统将CPU指令寄存器压栈,并将其内容设定为foo()的地址,控制转到foo()函数。foo()函数在栈区中创建其局部变量x,y, 完成x+y的运行后将结果以临时变量的方式返回给调用者。return之后,系统清空foo()在栈区中存放的变量,并将先前压栈的指令寄存器内容出栈,转而执行main()中接下来的代码。

 

声明Test* pTest后,main在栈区中创建一个指针变量,然后调用new在堆区中创建一个Test的实例,并将pTest指向它。运行该程序,我们可以发现,Test类的构造函数被执行,但析构函数没有执行。原因是这样的,栈区内的变量值在创建这个变量的函数以内有效,例如,函数foo 返回后,形参x, y从栈中清除,main返回后,指针变量pTest也被从栈区中清除;但指针指向的堆区中的Test实例,仍然存在,类的析构函数没有被调用。由此我们可以看出栈区和堆区得区别:

 

栈区(stack area):  存放程序的局部数据,即各个函数中的局部变量

堆区(heap area):  存放程序运行中的动态数据

 

对栈区的使用,在编译的时候就已经确定,而堆区的使用是在程序运行中动态进行的。堆区中的数据的创建和删除要由程序员来自己控制,系统不会对它像栈区那样进行自动的清除,使用堆区要防止产生例子程序中那样的错误,用new创建对象时候,使用完毕后一定要把它delete, 不然会出现内存泄漏(memory leak)

 

栈区和堆区的区别,后文还会有进一步的分析。

 

以上是对程序执行过程中对各个内存区使用情况的一个简单介绍。实际情况可能比这里描述的要复杂一些,比如传递函数返回值的那个临时变量的处理,函数如何对栈进行清除(涉及到调用规范__cdecl, __fastcall __stdcall)。本节主要是了解程序对内存的使用情况,对这样的细节就不做深入地分析了。

 

2. C++ Class内存格局

 

C++引入了Class这个概念,Class封装了一组相关的数据和对这些数据的操作,这是实现面向对象编程的基础。在下面一节中,我将针对Class的内存布局,做一些分析。

 

我们知道,Class中有成员函数和成员变量,其中,成员函数分为普通成员函数,静态成员函数和虚函数,成员变量分为普通成员变量和静态成员变量。我们从最简单的情况开始分析。请看下面的代码:

 

/////////////////////////////////////////////

//Source Code 1.2

#include<iostream.h>

 

class Test1

{

public:

       int foo(){return 0;}

private:

       int     member_1;

       float   member_2;

       double* p;

};

 

class Test2

{

public:

       int foo(){return 0;}

       virtual int v_foo(){return 0;}

private:

       int     member_1;

       float   member_2;

       double* p;

};

 

class Test3

{

public:

       int foo(){return 0;}

       int static s_member;

private:

       int     member_1;

       float   member_2;

       double* p;

};

 

class Test4

{

public:

       int foo(){return 0;}

       static int s_foo(){return 0;}

private:

       int     member_1;

       float   member_2;

       double* p;

};

 

void main(void)

{

int a=sizeof Test1;

int b=sizeof Test2;

int c=sizeof Test3;

int d=sizeof Test4;

 

cout<<"Size of class Test1 is:"<<a<<endl

       <<"Size of class Test2 is:"<<b<<endl

       <<"Size of class Test3 is:"<<c<<endl

       <<"Size of class Test4 is:"<<d<<endl;

}

 

 

 

 

//Test1类,其中封装了三个普通的成员变量

//一个普通成员函数

 

//返回int型值的普通成员函数

//32位系统中,各个变量的尺寸如下

//sizeof (int)      -->4

//sizeof (float)     -->4

//sizeof (double*)  -->4

 

 

//Test2类在Test1的基础上增加了虚函数

 

 

 

//虚函数

 

 

 

 

 

 

//Test3类在Test1的基础上增加了静态成员//变量

 

 

//静态成员变量

 

 

 

 

 

 

//Class4类在Test1的基础上增加了静态成

//员函数

 

 

//返回int型值的静态成员函数

 

 

 

 

 

 

//main中测试每一个类的尺寸

 

 

 

 

 

 

//输出:Size of class Test1 is: 12

//输出:Size of class Test2 is: 16

//输出:Size of class Test3 is: 12

//输出:Size of class Test4 is: 12

 

 

现在我们来分析一下程序输出的结果:

 

Test1类的尺寸为12,恰好为三个普通成员变量尺寸之和,那成员函数foo()哪里去了?留下这个疑问,继续往下看。Test2Test1多了一个虚函数,结果尺寸比Test1多了4个字节,一个函数怎么会只有4个字节?留下第二个疑问,继续。Test3类和Test4类分别比Test1类多了一个静态成员变量和静态成员函数,但这个增加却没有在类的尺寸上反映出来,不解!

 

通过归纳,我们可以总结出这样的事实

a. 类中包括普通成员变量,但不包括普通成员函数

b. 虚函数以某种形式包含在类中,但函数体本身肯定不在类中

c. 类的静态成员变量和静态成员函数不包括在类中

 

进一步思考,我们可以想到:

 

为了提高性能,减少对内存的需要,我们没有必要把类的函数包含在类的实体中,创建10Test1的实例,这10个实例中包含10份相同的的foo()代码是非常愚蠢的。类中包含类本身数据(普通成员变量),当要对数据进行操作时,通过一定的方式调用成员函数即可。类实际上是调用Test1::foo()来访问成员函数的(这其中的一些细节会在“封装”一章中进一步阐述)。

 

再看虚函数的问题,增加虚函数后,类的体积增加了4个字节,这恰好是一个指针的尺寸,我们有理由认为这4个字节是指向虚函数入口的函数指针,这样我们第二个问题也可以得到很好地解释。可是我们为什么不用Test2::v_foo()的方式来访问类的虚函数呢?这个问题会在“多态”一章中,给出完美的答案。(其实类中的这个指针指向的是虚拟函数表而非虚函数的实际入口地址,具体请参考“多态”一章)

 

现在再来看静态成员函数和静态成员变量,根据上面的思路,一切都已经很清楚了。静态成员为所有类的实例所共享,没有必要把它们都放到实例中去,可以像寻址成员函数那样对它们进行操作:Test3::s_member Test4::s_foo()

 

通过下面这张图,可以更清楚地了解C++ Class的内存格局

 

 

Test::V_foo()

int  member_1

float member_2

double* p

指向虚函数的指针

Test

实际上此处为虚拟函数表

 

Test::foo()

Test::s_foo()

Test::s_member

普通成员函数

静态成员函数

静态成员变量

文本框: ….. …..

1-2

 

3. 编译期和运行期的区别

 

编译期是源代码向二进制指令转化的过程,而运行期则是CPU执行这些指令的过程。

C++面向对象特性实现机制的初步分析 Part3

Chapter 2 封装 2.1 封装的目的和意义 广义的说,封装是为了使这个世界以更加简单的方式呈现在我们的面前。买一台电冰箱,我不必要知道里面压缩机的运作过程,不必了解详细的制冷过程,我所要所的是...
  • yongyu2000
  • yongyu2000
  • 2002年03月25日 08:49
  • 586

C++面向对象特性实现机制的初步分析 Part1

本人的毕业论文,正在写作中。贴出半成品,请大家指导! 联系 jerryyu@21cn.com摘要… …Abstract… …目录前言1准备知识 1.1 程序对内存的使用方法1.2 C++ Class内...
  • yongyu2000
  • yongyu2000
  • 2002年03月26日 11:08
  • 715

C语言实现C++面向对象语言多态特性

我们知道,C++语言演化过程中,加入了不少新特性,使其成为一门现代高级OO语言。当初C++在上个世纪七十年代末,80年代初时,C++发展刚刚起步,那时C++可以看作C with class,C++编写...
  • sunjunior
  • sunjunior
  • 2016年03月09日 21:02
  • 1314

面向对象三大特性,五大原则

以前一直认为程序中的类有使用到封装继承多态就是面向对象设计,其实不然 封装,继承,多态只是面向对象的三大特性,但是在设计程序的时候并不是说类的结构使用到了(或是体现出了)这三个特性就是面向对象, ...
  • xtzmm1215
  • xtzmm1215
  • 2015年07月18日 22:28
  • 6553

C++面向对象编程的基本概念及四种机制及类和对象

①面向对象编程的基本概念 1.对象——实体 组成部分:{属性,行为}。 eg.一个学生可以是一个对象,那么这个学生的属性有学号,年级,班级等;行为有选课,考试,体侧等。 2.类——抽象 组成部分:{属...
  • qq_37385726
  • qq_37385726
  • 2017年04月28日 16:36
  • 188

C++编程之面向对象的三个基本特征

面向对象的三个基本特征是:封装、继承、多态。 封装 封装最好理解了。封装是面向对象的特征之一,是对象和类概念的主要特性。 封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只...
  • zhanyimao
  • zhanyimao
  • 2011年09月14日 21:41
  • 4885

C++:面向对象的基本特征

面向对象方法首先对需求进行合理分层,然后构建相对独立的业务模块,最后通过整合各模块,达到高内聚、低耦合的效果,从而满足客户要求。具体而言,有三个基本特征:封装、继承和多态。封装:将客观事物抽象成类,每...
  • oMengLiShuiXiang1234
  • oMengLiShuiXiang1234
  • 2016年05月10日 17:19
  • 999

面向对象三大特性---封装性

思路:1,封装是什么?-->为什么要封装(即封装的好处)--
  • u014167212
  • u014167212
  • 2014年04月22日 16:36
  • 2328

深入理解C++面向对象机制(三)构造与析构

深入理解C++面向对象机制(三)构造与析构 零.声明 1.《深入理解C++面向对象机制》系列的博文是博主阅读《深度探索C++对象模型》之后的自我总结性质的文章。当然也希望这些文章能...
  • Stan1989
  • Stan1989
  • 2014年08月16日 15:03
  • 1260

OOP的三大特性是什么? 面向对象编程的三大特性是什么?

OOP的三大特性是什么? 面向对象编程的三大特性是什么? 封装 :就是将一个类的使用和实现分开,只保留部分接口和方法与外部联系 继承:子类自动继承其父级类中的属性和方法,并可以添加新的属...
  • chending_
  • chending_
  • 2017年02月18日 13:18
  • 1870
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:C++面向对象特性实现机制的初步分析 Part2
举报原因:
原因补充:

(最多只允许输入30个字)