第二桶 基于对象的编程 第二碗 老C初谈统一建模 小P开练建模语言(之二)

pastpast     “呵呵,国庆过得如何啊?”小P笑着问老C,显得很轻松。
     “哦,还行。四处拜访了一番,找到以前的同学聊了聊……你呢?”老C问。
     “行程比较丰富,逛了朱雀森林公园……在城里用脚丈量了东西南北四条大街……给百盛和开元进行了慈善捐款,换回若干不等价女性物品……”小P扳着手指头唾 沫横飞,“在battle net上和朋友PK了几次魔兽……到朋友家听了听他最近练习的新曲子——他以前可是某非著名band的吉他手转行鼓手——顺便和他操练了几盘实况足球…… 哈哈,最重要的是翻看了一些UML的资料,顺便自己总结了一下……”
     “喔!那还真实做了不少事情啊……”不想看着小正太唾沫横飞,老C插嘴道。
     “呵呵,还行还行。”小P忍不住要展示一下自己的学习成果,顺便让老C评论一番,“我依照约定补全了那个sequence diagram,顺便自己总结了一些目前学到的东西,我给你看看……”说着他打开了自己的电脑。

     “喔,还是不错的。”老C评价道,“其实模型并不能完全代表编程的细节,只要可以说明问题就OK了,我们在编程的时候还是要对模型进行某些修改以避免掉某些罗嗦的地方……嗯,不错。”老C点点头。
     “来看看这幅图,”小P说道,“这是我总结的需要学习的UML图,着重黑色的部分是我已经接触到的。”
     “是么?我来看看。”老C道。

     “呵呵,你总结的很不错啊。”老C称赞道,“这样你就知道自己的road map了,学习起来就会更有一些提纲挈领的感觉。”
     “那是那是,我的学习那个还是不错的……”小P审慎的谦虚道。
     “嗯……通过这幅图你应当可以明白UML中动态模型和静态模型的概念上的区分啦。”老C补充道,“嗯……你的领悟能力出乎我的意料啊……”
     “……”小P囧,不知道这是表扬还是批评,“呵呵,我还总结了我接触过的UML概念,分为形而上和形而下的对偶。”说着小P又打开了一个文档。

形而上
形而下
Class
Object
Association
Link
Operation
Method


     “哦……”老C有些惊讶了,“你总结的有些道理。虽然现在你可能无法分辨Operation 和 Method 的具体区别,但是在脑海中保持这种概念上的区别还是很重要的。”他称赞道,“照这样的速度发展下去,过不了一两年,你就可以教我了……”
     “呵呵呵呵,你太谦虚了……”小P有些得意,“我还有很多东西需要你多和我聊聊呢。”
     “哈哈,那你可要有所表示哩……”老C打趣。
     “……好!为了迎接新的学习生活,我决定……在南门请你再吃一回刀削面……大碗也可以噢。”小P痛下决心。
     “……寥胜于无……”老C郁闷道,“也行……不过还要再加一瓶冰峰……”
     “呵呵,好的好的。”小P很痛快的答应。
     “嗯……请5天,时间我定。”老C得寸进尺,“包括中午和晚上。”
     “……”小P扳着指头算了算,“可以!不过不能在一个星期内连续吃5次……”
     “……谁会连续5天全部吃刀削面啊!”老C囧。
     “呵呵,呵呵,我只是提醒……提醒一下而已。”小P笑道。
     “嗯,趁着我心情大好,再给你讲讲线性链表的一些习惯用法和常用的设计吧。”老C道,“反正离吃饭还有一段时间。”
     “噢!好啊。”小P道,然后很自觉的从角落中拉出白板。
     “嗯……”老C觉得这个孩子还是很有前途的,“一个基本问题,就你所知,什么是线性表?”
     “哦,叫我想想。”小P眨眨眼睛,“好像和遍历这些数据结构花费的时间有关系,如果我遍历一遍这些数据结构的所有元素所花费的时间是元素个数的线性函数, 那么这个数据结构就是线性表,哦……时间复杂度就是n啦。”小P又想想,“好像就是这样,我所接触的线性表包括array, linked list, stack, queues,而queues可能有各种奇怪的queue,比如循环的,优先级的什么的……”
     “你的记忆力不错啊。”老C称赞道,“嗯,基本上线性表就是这么回事啦。”他点点头,“我再来问一个貌似题外话的问题,你知道在C语言中,有哪4类指针吗?”
     “槑……”小P摇头,飞快。
     “在C语言中,有4种指针,分别是一般的指针,空指针、0指针和past the last one指针。”老C道。
     “槑……”小P道,“一般的指针就是指向数据和函数的指针吧,空指针应当就是void*,0指针应当就是无法dereference的那种指针,那么什么是past the last one指针?”他不解的问。
     “很简单,我们举例说明吧。”老C说着在白板上写下几行代码。

const int MAX_SIZE = 10;

int main()
{
    int array[MAX_SIZE];

    for (int* p = &array[0]; p != &array[MAX_SIZE]; ++p)
    {
        *p = 0;
    }
   
    return 0;
}

     “看,”老C指着代码,“在这里我们将数组中所有的元素初始化为0,但是在这里我们使用了一种看似更间接的方法,我们没有使用类似array[i]这种下 标的形式,而是使用了指针。”他用笔在代码下面标出着重符号,“请注意这句, p != &array[MAX_SIZE],在这里就表示那个past the last one的指针。”看到小P还是没有反应,老C接着解释,“其实&array[MAX_SIZE]指针是我们之前没有定义的,因为其实 array[MAX_SIZE]已经越过了array数组的界限。对于一个越过界限的元素,取它的地址,按道理来说应当是没有意义的,但是为了我们可以方 便的表示一个数组的结束,C语言特地为我们提供了一定的方便——C语言规定这个对越过数组界限一个元素取地址是合法的,而且它就是数组最后一个元素地址的 下一个地址。当然,对这个指针dereference和对其指向的内容赋值,都是没有意义的行为——它是且仅仅是被用来作为一个结束的标识符而已。”他擦 了擦头上的汗,“总之你就认为past the last one指针就是表示一个数组结束用的,对其赋值和解引用是没有意义的。这个指针的出现完全是为了人们使用的方便而已。”
     “唔,就是说C语言帮我们一个忙,就是了?”小P问,“那么和使用下标的方法比,这样有什么好处啊?”
     “嗯,使得我们减少一些记忆力上的负担,如果你按照某种格式书写代码,那么就不用担心 ‘过一’ 问题。”老C说道。“比如在幼儿园,一个老师问,孩子们,我们现在有几个小板凳啊?一个孩子数道,0,1,2,3,4,他回答,我们有5个小板凳!那么他 就是天才并且使用了C编程界在线性表中采用的习惯用法——使用past the last one指针来表示线性表的结束!”
     “哈哈,”小P笑道,“我还没有见过哪个孩子这样数的。”
     “总之就是一种很好的方法帮我们解决过一问题的。”老C说道,“因为C语言的数组采用以0开始的计数方式,这样我们在进行指针运算的时候会产生某种便利 性,比如让你有某两个指向array元素的指针,比如p1和p2,且p1 <= p2,那么计算p1和p2之间的元素个数,很简单的p2-p1就可以了。这只是一个例子,还有很多其它类似的指针运算,也会显得很方便。但是这样又会造成 过一问题,因为一般人在计数的时候很难适应从0开始计数——在简化了指针运算的同时,又引入了过一问题——反正世界上没有完美的东东。”
     “哦?那么引入这儿past the last one指针会给我们带来什么便利呢?”小P问。
     “呵呵,这样我们可以以统一的方式来表示数组的结束。”老C说,“这样大家在编码时在计算数组是否应当已经遍历结束的时候,智力负担会小很多。”他想想,在白板上写下一个数学表达式。

[first, last)


     ‘“看,这样我们就可以用一个左闭右开的区间表示我们的数组啦。”老C说道,“对于这个区间,你可以很容易的用last - first来计算出区间中元素的个数;如果你知道了区间中元素的个数——比如说SIZE——那么可以很简单的用 first + SIZE = last 的公式来计算last数据的地址,从而知道了如果在遍历这个数组的时候,应当在那里结束。”他想了想,又说道,“这样我们遍历数据结构的代码可以统一成为 如下的形式。”说罢又在白板上写下如下代码。

for (iter = first; iter != last; ++iter)
{
}

     “如果我们在代码中坚持使用这种遍历方式和左闭右开的区间表示方法,那么数组的过一问题就会远远的离开我们,而且不会再回来……”老C说道,“而且一般我 们在声明的时候总是知道数组的大小的,比如MAX_SIZE,这样我就很方便的知道了array[MAX_SIZE]就是我定义的数组区间的结束标识。”
     “嗯……”小P道,“好像真的有些用处……但是为什么for()循环中要用 != 来表示数组没有结束而不用 < 来表示呢?”他问道。
     “因为统一……”老C解释,“线性表的表示方法不只有使用数组一种,我们还可以使用linked list,虽然在linked list中,每个节点表示的逻辑意义是线性的,但是它们的地址并不是线性的,不能说前面的节点的地址恰好比后一个节点的小。我们无法比较两个指针的大小, 只能比较它们是否相等。”说罢他又在白板上写下另一行代码。

for (Node* iter = firstNodeAddr; iter != lastNodeAddr; iter = iter->next)
{
}

     “看,这样在如何表示线性表遍历结束方面,两者保持了适度的统一。”老C说道,“而统一是我们编程人员最喜欢的,这样即有利于减少编码者的智力负担,又有利于维护者对代码的阅读。”他又补充,“总之是一种行话,约定俗成的习惯。”
     “哦,我突然想起来了,我们在实现linked list的时候,也是采用[first, last)的方法的。”小P道,“这样说的话我倒是马上明白为什么要将list的head作为表示这个双向循环链表的结束了。”
     “没错!”老C说道,“你可以再回头看看我们对linked list的实现,体会一下[first, last)区间表示线性表的好处。”老C接着说道,“因为我们有了基本统一的表示方法,通过运算符重载这一利器,可以将对节点的操作从数据结构中提取出来 ——你不是在写linked list的时候嫌index这个东东比较烦人吗——那么我们就可以将这种index操作从线性表中提取出来,从而简化我们对数据结构的操作。”
     “哦?怎么运用运算符的重载来简化这种操作呢?”小P问道。
     “一个简单的例子,如果我们认为iter是一个类的,那么我们可以重载iter类的++运算符,从而使得对数组的遍历与对linked list的遍历统一起来。”老C说道,然后在白板上写下一个示例代码。

for (Iterator iter = first; iter != last; ++iter)
{
    ...
}

++iter其实实现 iter = iter->next的动作

     “看,这样我们就可以保持代码形式上的统一。”老C说道,“如果代码在形式上统一了,那么我们就很容易会想到用一个基类来表示统一的操作,而用子类和虚函 数对不同的操作进行区分。”他揉揉手,“这就是有名的iterator模式,我想我们可以从一个最简单的对数组遍历的例子引申开……”
     “是么是么?”小P好奇的问,“那么应当怎么做呢?”
     “嗯……那要看今天的面香不香……”老C打岔。
     “囧……那我们去看看吧,如果你觉得勉强还可以,那就回来接着说吧。”小P道。
     “是啊是啊,吃饭不积极,思想有问题。我们的思想可没有什么问题,走啦走啊啦。”老C到隔壁叫了其它人,大家一起浩浩荡荡杀出门外。

(老C会怎么样解释iterator模式呢)

posted on 2009-03-09 17:41 Anderson 阅读(1350) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


<2009年1月>
28293031123
45678910
11121314151617
18192021222324
25262728293031
1234567

导航

统计

常用链接

留言簿(6)

随笔档案(21)

文章档案(1)

搜索

最新评论

阅读排行榜

评论排行榜