CG@CPPBLOG

/*=========================================*/
随笔 - 76, 文章 - 39, 评论 - 137, 引用 - 0
数据加载中……

我的SICP习题答案(1.6)

会递归直到堆栈溢出。
原因是 在 new-if 还没有展开为 cond special forms 时,else-clause 子式已经陷入了无限递归。 做了以下实验,可以验证
(define (new-if pred thenc elsec)
  (cond (pred thenc)
        (else elsec)))

(define (iter x y)
  (new-if (
= x y)
          
0
          (iter (+ x 
1) y)))

(define (iter-if x y)
  (if (
= x y)
      
0
      (iter-if (+ x 
1) y)))

(define (iter-cond x y)
  (cond ((
= x y) 0)
        (else (iter-cond (+ x 
1) y))))

对于 (iter 1 10), (iter-if 1 10), (iter-cond 1 10)
其中 iter 会 导致堆栈溢出,而 iter-cond 和 iter-if 并不会。

此题同1.5
在1.5中,由于应用序的原因,在 test 表达式 还没有展开为 if 特殊形式(special forms)时, (p)已经陷入了无限递归。

posted @ 2008-03-09 23:12 cuigang 阅读(1726) | 评论 (9)编辑 收藏

浅论要协同作战

兵者,国之大事,不可不察也。——孙武

战 争是一个复杂的系统工程,需要多兵种的协同,即便是在冷兵器时代,也不能依靠单一兵种完成某场战役,现代战争更是需要不同专业士兵的配合。我们经常可以在 影视剧中看到这样的情景:两军对垒,最前面是盾牌兵,接下来是弓箭兵、长枪兵、骑兵、朴刀兵。首先弓箭兵在盾牌兵的掩护下放箭,一通乱射之后,然后盾牌兵 和长枪兵冲锋,弓箭兵掩护,快接近敌军时,盾牌兵后撤,弓箭兵停止射箭,骑兵和朴刀兵冲锋。这种配合看似教条,实际是有一定意义的,它是从实践中总结出来 的,从某种程度上反应了冷兵器时代多兵种协同作战的重要性。

软 件开发也是一个复杂的系统工程,需要各种专业的技术人员配合。通常我们简单的把这些技术人员分成需求、开发和测试,相应的也把整个过程分为需求阶段、开发 阶段、测试阶段。这种划分积极的意义是区分了不同专业方向,将整个过程流水化,负面的意义是将各个阶段和各个专业团队割裂开,各自为政,失去了划分专业的 本意,形式替代了目的。结果需求人员的目的变成了写出一份需求,然后封闭意见;开发人员按照需求写出代码,改正bug;测试人员挖掘bug,监督开发人员改正,然后写一份报告。整体的目的荡然无存。当然无法否认这种流水作业的正确性以及必要性,我这里要说的只是它的负面问题。

当 作战室只要指定一份作战计划,作战部队只要冲锋杀敌,后勤部队只要做饭埋尸体;空军只管按线路图投弹,炮兵只要向指定坐标开炮时。如果没有一个正确的、伟 大的、英明的将军,这场战争只能在一个貌似严格符合教科书的方式开始,以一个不可思议的方式失败。因为除了这位将军,所有人都没有带着脑袋去打仗,他想的 对不对,直接影响到结局。

可 惜的是,我们没有这样的将军指挥软件开发,也不存在这样的将军。我们都长着脑袋,都要去思考。从另外一个角度,我们并不严格类似军队,我们更像一个自治的 团体。那么自我协同就成了一个现实问题。需求、开发和测试人员如何协同完成一个产品呢?首先我们要把他们看做一个整体,他们所有人工作的目的只有一个:完 成一个产品。需求阶段、开发阶段和测试阶段只是这个目的的一个阶段,不是某个人工作的终极目的。我们已经认识到这点,但我们做的远远不够。

在 需求阶段,需求人员应该统领全局,向开发和测试人员介绍产品构想,解释需求,辅助开发和测试人员完成后续阶段的计划和方案;在开发阶段,开发人员要重新整 合所有资源,由开发人员冲锋,需求人员提供支持,测试人员监督开发过程,纠正开发失误;而在测试阶段,测试人员才是指挥中心,他们要检查软件,需求人员配 合诊断,开发人员按测试计划解决故障并封闭。不同的阶段只是中心成员不同,决策人员不同,而不是责任不同,目的不同。

可 能说的太隐晦了,举两个例子来说明。一个还是打仗的例子,比如现在我们需要让弓箭兵上马,去追杀敌军。弓箭兵老大可能反对并提出几个原因,第一,并非职责 范围,弓箭兵的职责是远距离造成敌军伤亡,没有追逃的责任,这是骑兵的责任。第二,技术能力不足,弓箭兵都不善于骑马,而且马上射箭难度较大,没法实现。 第三,如果敌人反扑,由于不善近战,可能会造成大量伤亡。的确有道理,可是如果我们真的需要这样做来表现我们的战略意图,怎么办?

再 举一个身边的例子来说明,前几天有这样的争执,关于需求应不应该明确界面限制导致选项不生效,然后切换界面取消限制,选项应如何的问题。需求人员的意见是 这样的,第一,选项最终是什么不重要。第二,强行限制,会使需求工作量变大,同时开发和测试的工作量也增大(因为不同的地方,自然结果必然不同,强行一致 会导致不自然的处理,同时测试需要关注测试)。第三,不利于平台化建设,这种限制会导致需求过分依赖于产品,无法在不同产品间共用。都很有道理。但是我认 为,提出这个问题是无可厚非的,需求的意见也是中肯的,解决的办法是,我们要看看我们的终极目标,以此来判断是否需要这样做。首先,如果不限制造成不一 致,对我们的产品有没有影响,其次,如果不一致,会不会造成后续工作的阻滞。对于这个问题的本身,我觉的对于无关紧要的选项,需求可以不写,开发可以顺其 自然,测试不必关注。而对于比较重要的选项,需求必须明确,开发必须处理,测试要注意关注。不可一概而论,非左即右。


posted @ 2008-01-10 23:02 cuigang 阅读(322) | 评论 (0)编辑 收藏

我的SICP习题答案(1.1~1.5)

1.1

10,12,8,3,10 6,a,b,19,#f,4,16,6,16

1.2


(/(+ 5 4 (- 2 (- 3 (+ 6(/ 4 5)))))(* 3 (- 6 2)(- 2 7)))

or

(/(+ 5 4 (- 2 (- 3 (+ 6 4/5))))(* 3 (- 6 2)(- 2 7)))

1.3

这个问题中文版的翻译是错的,参看原文是求平方和而不是“和”。

(define (square(x)(* x x)))
(define (max x y)(if (< x y) y x))
(define (func x y z)
  (+ (square (max x y))
     (square (max (min x y) z))))

1.4

a+|b| 

<=>

1 # in python
2 def a_plus_abs_b(a,b):
3     if b>0 :
4         x = a + b
5     else:
6         x = a - b
7     return x

1.5

在网上看了很多答案,都认为“应用序”的实现会导致死循环,我非常困惑。反复看了中文版和英文版,觉得大家这样认为可能是书中说lisp的实现是“应用序”,而在scheme中跑这段代码会死循环,就先入为主的认为“应用序”的实现会死循环。其实对照正文,我们可以看到“正则序”停止展开的条件是“只包含基本运算符的表达式”,而对于

(define (p) (p))

是无论如何也没法完全展开的,因为它会不断递归,所以“正则序”才会死循环。

而对于“应用序”的实现,则会这样展开


(test 0 (p))
(if (= 0 0) 0 (p))
(if #t 0 (p))

; 0

解决这个问题主要是“正则序”(Normal order)以及“应用序”(Applicative order)展开一个组合式的规则,仔细研究了MIT 6.001课程讲义,网上的各种答案,以及中英文版。我认为,正则序以类似广度优先的方式进行展开。而应用序优先计算子表达式,类似与深度优先。那么对于这个问题,
正则序会展开为
=> (if (= 0 00 (p))
=> (if #t 0 (p))
接着,由于这是一个if的special form(特殊形式),就会被展开为
0
而应用序,由于(p)一直可以递归代换,从一开始就会进入一个无限递归中去。
简言之,由于应用序的原因,在 test 表达式 还没有展开为 if 特殊形式(special forms)时, (p)已经陷入了无限递归。

posted @ 2007-12-26 00:19 cuigang 阅读(2292) | 评论 (13)编辑 收藏

读SICP有感

思想都藏在lisp程序的层层括号中吗?

1 (define (abs x)
2     (if (< x 0)
3         (- x)
4         (x)))


posted @ 2007-12-25 21:48 cuigang 阅读(741) | 评论 (0)编辑 收藏

切和剥

——关于重构方式的设想

我们重构部分代码时,往往想到的是稳定,最好是不变接口,只变实现,保持接口的稳定性。但现实往往没有这么轻松,接口不变,意味着有着一个良好的结构设计,至少在功能职责划分上没有大的问题。而我们却时常遭遇这种职责的混乱,这种Martin Fowler不愿详谈的事情,对我们来说很麻烦。我们不能横切式的改变,这将导致大规模的变化,特别是对于层次靠下的部分,范围的扩大,无论是从控制能力上,还是工作量上,包括对系统稳定性的影响方面都是巨大的,兼容旧组件也许并不亚于推倒重来。

也许是个好办法,另辟蹊径绕开原来的设计,从底向上建立一条新的结构通路,将旧的部分一片一片的剥开,合并到新的部分中来,直到完成重构。就像做一个心脏搭桥手术。

posted @ 2007-12-21 19:36 cuigang 阅读(249) | 评论 (0)编辑 收藏

再论Singleton

记得以前大家讨论过Singleton三种写法的优劣,今天我又发现了一个新问题,在这里和大家分享一下。下面是三种Singleton的写法:

 

 1 // 1st
 2 class Singleton{
 3     static Singleton inst;
 4 public:
 5     Singleton& GetInst(){
 6         return inst;
 7     }
 8 };
 9 Singleton Singleton::inst;
10 // 2nd
11 class Singleton{
12     static Singleton* _inst;
13 public:
14     Singleton& GetInst(){
15         if(_inst == NULL)
16             _inst = new Singleton;
17         return *_inst;
18     }
19 };
20 // 3rd
21 class Singleton{
22 public:
23     Singleton& GetInst(){
24         static Singleton inst;
25         return inst;
26     }
27 }

我们已经知道方法1没有多线程问题,但编译时分配内存。方法2有多线程问题,在运行时分配内存。方法3也有多线程问题,而且在编译时分配内存,但在运行时调用构造函数。(多线程问题的解决方法在此不再赘述。)

那么,我们可能就认为方法
1虽然在编译时分配内存,但我们不在乎这点内存,反正写出来是要用的,这点内存少不了,既避免了多线程,又避免了分配失败。就用它了!

不幸的是,方法1也有它自身的问题。今天我在构造一个对象工厂时,期望通过全局变量,向工厂注册派生类的生成方法时,方法1暴露了它的问题。比如我们在Singleton中有一个方法RegisterMethod(CallBack*),那么我可能这样实现。

 

1 //以下代码是全局的,文件级作用域
2 
3 namespace{
4     CBase* CreateDeriveObj()
5     {
6         return new(CDerive);
7     }
8     BOOL tmp = Singleton::GetInst().RegisterMethod(CreateDeriveObj);
9 }//end of namespace
 

 

结果发现在调用RegisterMethod()时,Singleton::inst 还没有初始化(构造函数没有被调用)。究其原因是编译器虽然在编译时对其分配内存,但是构造函数是在运行时,在Main()函数前调用。而对于全局变量,编译器是不保证初始化顺序的!而这个例子就是tmp在构造时, Singleton::inst 还没有构造。

而这个问题的解决方法只有使用方法2或者方法3,考虑到我们不能在Main函数前使用new操作符,我用了方法3。又因为我有全局变量保证,就可以不考虑多线程问题了。

要么是这个问题,要么是那个问题,你总要解决一个问题。

 

posted @ 2007-12-20 21:55 cuigang 阅读(383) | 评论 (0)编辑 收藏

在LINUX下看WINDOWS的中文文件名不是乱码的方法

mount -o codepage=936   iocharset=cp936  /dev/hda?  /mnt/???

posted @ 2007-12-17 23:41 cuigang 阅读(1879) | 评论 (2)编辑 收藏

UNICODE的区域列表(部分)

0000-00FF        ASCII字符
0370-03FF        希腊字母
2000-206F        通用符号
2070-209F        上标和下标
2150-218F        特殊数字
2190-21FF        箭头
2460-24FF        带圈的数字
2500-257F        制表符
2580-259F        阴影
2E80-2EFF        CJK基本补充(好象是偏旁)
2F00-2FDF        偏旁
3000-303F        CJK标点符号
3040-309F        日文平假名
30A0-30FF        日文片假名
3100-312F        汉字注音符号
31A0-31BF        汉字注音符号扩展
31F0-31FF        片假名语音扩展(?)
3200-32FF        带圈的CJK字符和月份符号
3300-33FF        CJK兼容
3400-34FF        CJK统一汉字扩展A
4E00-9FAF        CJK统一汉字
AC00-D7AF        韩文音节
E000-F8FF        私用保留区域
F900-FAFF        CJK兼容汉字
FE30-FE4F        CJK兼容符号

posted @ 2007-12-17 23:39 cuigang 阅读(409) | 评论 (0)编辑 收藏

灌水


 
有两种方式构建软件设计:
一种是把软件做得很简单以至于明显找不到缺陷;
另一种是把它做得很复杂以至于找不到明显的缺陷。
——C.A.R. Hoare

posted @ 2007-12-17 22:17 cuigang 阅读(333) | 评论 (2)编辑 收藏

WSH+JSP

向大家隆重推荐一个比批处理更强大的windows脚本。不知道你是不是经常写一些小工具,用批处理实现不了。只好用C来做,结果打开IDE,写段程序, 调试编译。当然这些都没问题,关键是因为是临时写的,没有考虑需求变化(呵呵),之后有时想要改改,那么就是打开工程,修改调试,编译运行。如果有人要享 用,往往你把可执行文件发给他还不够,还要把源码发给他,因为他要改。小小一个文件处理,写个exe。杀鸡焉用牛刀。今天我隆重推荐WSH+JSP, Java的语法,支持COM组件的windows脚本宿主,完全替代简陋的批处理,并且做出的东西,全是开源的。
 
这里响应靳波前日日志中的txt2bin工具,写了一段简单的例子,你把它保存为一个文本文件txt2bin.js,双击就可以运行:
 
 1 ////////////////////////////////////////////////////////
 2 //txt2bin
 3 ////////////////////////////////////////////////////////
 4 // 创建文件系统对象
 5 var FileSys = WScript.CreateObject("Scripting.FileSystemObject");
 6 // 打开文本文件
 7 var TxtFile = FileSys.OpenTextFile("test.txt");
 8 // 创建bin文件
 9 var BinFile = FileSys.CreateTextFile("test.bin"true);
10 // 读一行
11 var buf = TxtFile.ReadLine();
12 // 声明一个字符串
13 var str = new String();
14 for(var i =0 ;i<buf.length/*字符串长度*/; i++){
15     ch = buf.charAt(i); //得到一个字符,并把它转成数值
16     str+= String.fromCharCode(ch); // 追加到数组
17 }
18 // 写入bin文件
19 BinFile.Write(str);
20 // 关闭文件
21 TxtFile.Close();
22 BinFile.Close();
23 ////////////////////////////////////////////////////////

 
当然关于脚本语言还有诸如python、ruby、lua等,但要在windows下独立运行,破费周折,目标机是否有需要的dll还不一定,而这个程序只需要IE3.0以上版本,office。想必遍地都是吧。
 

posted @ 2007-12-17 21:52 cuigang 阅读(365) | 评论 (0)编辑 收藏

仅列出标题
共8页: 1 2 3 4 5 6 7 8