第一桶 从C到C++ 第十碗 老C初论对象模型 小P学习基于对象(之一)

第一桶 从C到C++ 第十碗 老C初论对象模型 小P学习基于对象(之一)


      又是一个阳光明媚的下午,九月的太阳照在人身上暖洋洋的。小P整理完《数字信号处理》的课件、笔记和作业,长长的伸了一个懒腰。“看来我还要复习复习《信 号与系统》。”他自言自语的说道。扭头看看老C,好像刚刚和《数理统计》斗争完毕。于是他把转椅转向老C,“呵呵,刚才在做作业吗?”
     “嗯,我刚刚和正态分布缠斗了一番,最后终于将它打倒在地……”老C开始吹嘘。
     “嘻嘻,”小P窃笑,心想自己不过20分钟搞定的事情这个家伙居然用了快一个小时,看来还是有些老啊,“我说,上次我们的代码已经到2.0版本了,你说下来要使用C++了,我们就开始这个活动如何?”
     “……”老C看看表,离吃饭还有快2个小时,于是说道,“你今天不去踢球吗?好,那么我们就来进行C++的部分。”于是他凑到小P电脑旁边,又把白板从角落中拉了出来。“你这几天看了我们写出的代码了吗?”
     “嗯,花了一些时间看了看,还试着自己重新实现了一下,但是我自己在编写程序的时候发现一些问题……”小P应道。
     “哦?说来听听?”老C追问。
     “首先是初始化,如果我们采用function(DATA*)的形式来编程,必不可少需要类似于initXXX(DATA*)的函数来初始化数据的状态, 但是我经常忘记在程序的开头调用initXXX()函数来初始化数据的状态,很容易造成数据败坏。”小P眨眨眼,“还有内存清理的问题,比如在使用 linked list的相关函数时,如果我忘记在程序最后调用ListClear()函数,那么就很容易出现内存泄漏,就算我调用了ListClear()函数,如果 在下次使用这个linked list时,忘记调用了ListInitList()函数,也会造成数据败坏而没有任何提示……”
     “是啊是啊,这个是经常遇到的问题,还有吗?”老C追问。
     “嗯,因为经常要使用DATA的指针类型,开始时有些不习惯……”小P想了想。
     “呵呵,你说的这些都是事实,而且在很长一段时间都困扰者开发人员,因此才有了C++的发明啊。”老C说道,“人们觉得用这种方式开发程序是很好的事情, 使得代码模块更容易构造,但是由于C语言没有在语言层面对此种方法进行直接支持,所以在采用以数据为中心的思想进行开发时,总会有这样或者那样的不方便。 于是人们想,可以在C语言中加入一些特性,使得基于对象的开发更方便一些,这样才有了C++语言的前身……”
     “是吗?那么是怎么一会事情呢?”小P问。
     “哦,我们来举个例子。”说着老C在白板上勾画了两段代码。

C:
typedef struct tagDATA
{
    int a_;
}DATA;

void func(DATA* data)
{
    data->a_ = 1;
}

int main()
{
    DATA data;
   
    func(&data);

    return 0;
}

C++:
class DATA
{
public:
    void func(){ a_ = 1; }
private:
    int a_;
}

int main()
{
    DATA data;

    data.func();
}

     “看看,两段代码虽然语法差别很大,但是实现的功能是一样的,都是对数据DATA中的a_变量赋值1。”老C解释到,“其实这里的C++代码可以翻译成C代码的。”他又在白板的空白处写下如下代码。

C++被翻译为C后:
class DATA
{
public:
    void func(DATA* this) { this->a_ = 1; } 
private:
    int a_;
}

int main()
{
    DATA data;
   
    // data.func();
    DATA::func(&data);
}

     “不过这些都是编译器私下里进行的活动,你就认为上面的代码是编译过程的中间代码就行了——不过最初的C++代码的确被编译为C代码后再编译的。”老C解释,“同时这也解释了this关键字在C++中是什么意思。”老C接着说道。
     “哦?这样有什么好处呢?”小P问道。
     “最大的好处是在语言层面对基于对象的编程方法给予了更多的支持,这样在开发的时候开发人员的智力负担会小很多……”老C停了一下,加重了语气,“我们做 事情的目的是简化问题,任何新工具和新方法被发明的目的都是为了使问题看起来更简单一些,而不是使问题看起来更复杂。”老C想了想,“如果你了解了某种语 言特性所针对的需求,你就可以更准确的使用这种语言特性而不会出现误解,同时也会说,啊,这样多好,不得不如此。”
     “是吗?”小P有被喷晕了。
     “是啊是啊,比如我们使用struct封装数据的属性,并将此数据与对它的操作放在同一个编译单元中,将内部的操作声明为static函数……一切的一切 都是业内流行的做法而已,语言并没有强制你这样做,而且也没有提供给你更好的工具以支持这种编码风格。但在C++语言下,语言可以给我们提供工具使得我们 可以更方便的实现自己的编程思想——虽然在C下面也可以实现,但没有在C++下方便啊。”老C忍不住喝了一大口水,“比如我们的数据,可以放在class 的private部分;而对外的接口,可以放在public部分供其它模块调用;模块内部的操作可以放在private部分加以隐藏;而且还提供构造和析 构函数用于我们方便的初始化数据和销毁数据。”
     “嗯,这样说来语言的确提供了便利性……”小P回答,“我以前觉得C++难以学习,可能是没有了解各种语言特性所应对的实际需求吧。”
     “是啊是啊,”老C感叹,“我们就对比对比C语言基于对象风格的编程和C++语言的对象模型,一探语言特性的究竟。”说着他又在白板上修改了一下C代码。

C:
typedef struct tagDATA
{
    int a_;
    int b_;   
}DATA;

void DataFunc(DATA* data)
{
    data->a_ = 1;
}

int main()
{
    DATA data;
   
    DataFunc(&data);

    return 0;
}

     “当我们写下这样的main()代码的时候,到底会发生什么事情呢?”老C问,看到小P一副囧囧的样子,决定自己回答这个问题,“首先我们在使用 typedef的时候,一些类型信息会被记录到内存中;当我们写下DataFunc()函数时,这个函数出现在我们代码的全局中,也就是说它是全局可见 的;当我们写下Data data语句的时候,一块新的内存在栈上被分配,其内部的信息根据以前的记录,被分配为两个int类型,其值是随机的。”说着他在白板上画了几个框框。

     “看,为了避免在项目中出现同名函数的问题,我们不得不在对数据DATA的操作Func前加上前缀Data以表征DataFunc()函数作用于DATA 数据,这样可以在很大程度上避免命名冲突。”老C解释道,“经过DataFunc(&data)函数调用,内存中&data指针所指向的 地址开始,a_的内容就变为1了。”
     “那么在C++中的情况呢?”小P问。
     “与在C中是一模一样的。”老C肯定的回答,“不要被语法欺骗了眼睛,在实质上它们两者是完全相同的。”说着他修改了C++部分的代码。

C++:
class Data
{
public:
    void func();
private:
    int a_;
    int b_;
}

void Data::func()
{
    a_ = 1;
}

int main()
{
    Data data;

    data.func();

    return 0;
}

     “虽然我们将func()函数声明在class Data内部,但是这个与Data所分配的内存大小完全无关。在这只是表现出func()名字空间的关系,Data类型占内存的大小还是根据其内部数据所 占的内存大小决定的。”老C解释道,“我们来用C语言翻译一下C++代码的意思。”

C++被翻译为C后:
struct Data
{
private:
    int a_;
    int b_;
}

void Data::func(Data* this);

void Data::func(Data* this)
{
    this->a_ = 1;
}

int main()
{
    Data data;
   
    Data::func(&data);

    return 0;
}

     “看,其实意思是一样的,不过就是C++的语法更特殊一些而已。”老C道,“由于func()函数被声明在Data数据内部,因此其名字空间就在Data内部,这样函数就不会和其它模块的函数发生命名冲突。”老C说着又画了一个框框。


     “看吧,其实没有什么不同,而且我们还省去了使用命名规范避免函数命名冲突的麻烦……”老C道,“一般业内的习俗发展到一定程度也会变为语言特性的,这在很多语言发展的历史上都可以证明。”
     “那么public和private呢?”小P问。
     “public与private只能说明某操作或者数据属性在名字空间内部的可见性,而不影响其在内存和名字空间的位置。”老C解释,“以后我们解释可见 性的时候会再次提及的。现在你只要记住凡是public域的函数或者数据,均可以通过.运算符和->运算符操作,而在private域内的,只能由 此命名空间内的函数操作。ok?”
     “好的,我记住了。”小P回答。
     “好了,了解了C++中基于对象部分的特性,我们再使用C++改写我们的apple game。”老C道。他删除了项目中除mydebug.h外的所有文件,然后又新建了 main.cpp,applegame.h,applegame.cpp和childlist.h文件,并将这些文件添加到工程中。
     “同样,根据我们的开发方法,我们先写一个大概的框架出来。先在main.cpp中添这样的代码。”老C一边说,一边敲下如下代码。

main.cpp:

#include "applegame.h"

int main()
{
    AppleGame theGame;

    theGame.play();

    return 0;
}

------------------------------------------------------(朴实的分割线)

     “看来我们需要AppleGame有一个play()的接口。”老C一边自言自语,一边写下applegame.h文件的内容。

applegame.h:

#if !defined(APPLE_GAME_H_)
#define APPLE_GAME_H_

#include "childlist.h"

class AppleGame
{
public:
    void play();

private:
    bool isGameOver() const;
    void doPlay();
    int lastChildSeatNum() const;

private:
    ChildList childList_;
};

#endif // APPLE_GAME_H_

------------------------------------------------------(朴实的分割线)


     “嗯,看来我们还需要一个ChildList模块。”他又在childlist.h中写下如下内容。

childlist.h:

#if !defined(CHILD_LIST_H_)
#define CHILD_LIST_H_

class ChildList
{
public:
};

#endif // CHILD_LIST_H_

------------------------------------------------------(朴实的分割线)

     “嗯,下来我们来实现apple game中的具体内容。”老C自言自语道。

applegame.cpp:

#include "applegame.h"
#include "mydebug.h"

#include <iostream>

//////////////////////////////////////////////////////////////////////////
// Public

void AppleGame::play()
{
    using namespace std;

    MY_DEBUG("Start playing game...\n");

    while (!isGameOver())
    {
        doPlay();
    }

    cout << "The last child's seat number is: " << lastChildSeatNum() << endl;
}


//////////////////////////////////////////////////////////////////////////
// Private

bool AppleGame::isGameOver() const
{
    static int i = -1;

    return 1 == i++;
}

void AppleGame::doPlay()
{
    MY_DEBUG("Playing game.\n");
}

int AppleGame::lastChildSeatNum() const
{
    return 10;
}

------------------------------------------------------(朴实的分割线)

     “编译……运行……ok!”老C打了一个响指,“我们的V3.0版本成了!”然后他依法建立了一个AppleGame_V3.0的目录,将所有文件拷贝到 这个目录下。“看,由于有了语言的支持,我们很容易的将模块分开,每个部分都由清晰的接口和归属,模块之间的关系也更加明显了。”老C总结道。
     “嗯,好像是的。下来我们是要对childlist模块进行细化吗?”小P问。
     “是的是的,这些工作都由你来完成吧,我们现在去吃晚饭,等回来后我们再继续。”老C回答。
     “呵呵,是啊是啊,早去早回,要不一会儿人就多了。”小P回答。
     两个人也没有关电脑,飞快的向食堂冲去占领有利地形……

(接着看小P的实现啊)


你可能感兴趣的:(第一桶 从C到C++ 第十碗 老C初论对象模型 小P学习基于对象(之一))