2007年02月02日

刚拜读了CSDN上的一篇文章:《苛求VCL:不满意TObject的设计》,原文中说“对象指针和接口指针并不是同一个地址”,这句话没有交代明白,同一个对象实例,怎么可能不是同一个地址呢,这一点解释不通,事实上,做个简单的实验,发现对象指针和指口指针肯定是同一个地址。新建一个工程,在Form上放一个Memo控件:

type  IFoo = interface    procedure Execute;  end;

  TFoo = class(TInterfacedObject, IFoo)    procedure Execute;    procedure MyMethod;  end;

procedure Test(AFoo: IFoo);begin  AFoo.Execute;end;

procedure TForm1.FormCreate(Sender: TObject);var  intf: IFoo;  obj: TFoo;begin  obj := TFoo.Create;  obj.Execute;  intf := obj;  intf.Execute;  Test(obj);  Test(intf);end;

procedure TFoo.Execute;begin  Form1.Memo1.Lines.Add(Format('this=%d', [Integer(Self)]));end;

结果当然是地址都相同。

这段代码还说明了从对象转换到接口是很轻松的事情,连AS都不用(上面Test过程要求一个接口,由于obj这个对象实例实现了该接口,所以直接传递就可以了)。

至于原文中所说的“如何从接口转换到对象”,首先想到的是为什么要把接口转换到对象?动机是什么?通常,编程是基于抽象,用接口的目的就是为了让上层不必了解底层细节,将派生类看生基类是很自然的做法,反过来把基类向下强制转换为派生类就很费解了。

如果非要这么做,当且仅当你知道intf实现就是TFoo时,你可以用一个强制类型转换:

TFoo(intf).MyMethod;
2007年01月18日

MasteringRegEx

一、问题

将一个数值字串,从右向左,每三位用逗号分开,比如,将“123456789”替换为“123,456,789”。

二、分析

替换算法的本质是将字串从右向左,每3个字符为一组进行分组,然后在组与组之间插入逗号。

三、语法

正则表达式引入了“预测”和“回顾”这两个概念,它们不是匹配字串,而是匹配一个“位置”,其中预测是正向(自左向右)查看,回顾是反向(自右向左)查看。

(下表中圆括号及其括住的内容,只匹配位置,不匹配文本)
正则表达式 名称 说明 示例
(?=…) 预测 断言子表达式在此位置的右侧 abcd(?=\d) 匹配右侧紧跟数字的abcd
(?!…) 否定预测 断言子表达式不在此位置的右侧 abcd(?!\d) 匹配右侧紧跟的不是数字的abcd
(?<=…) 回顾 断言子表达式在此位置的左侧 (?<=\d)abcd 匹配左侧是数字的abcd
(?<!…) 否定回顾 断言子表达式不在此位置的左侧 (?<!\d)abcd 匹配左侧不是数字的abcd

预测和回顾只是匹配一个位置,其中的子表达式并不被匹配。比如有字符“ABCDEFG”,则正则表达式

(?=EFG)(?<=ABCD)(?<=ABCD)(?=EFG)

就匹配D与E之间的那个位置,而不是匹配EFG,也不匹配ABCD,或其他任何一个字串。 注意,由于只是断言一个位置,所以上述预测和回顾的先后次序并不重要,二者同样都是匹配D与E之间的那个位置。前者的含义是“匹配一个位置,它的右边是 EFG,左边是ABCD”,后者的含义是“匹配一个位置,它的左边是ABCD,右边是EFG”,两者都是同一个意思。下面的Perl在ABCD和EFG之 间插入一个逗号:

$foo = "ABCDEFG";$foo =~ s/(?<=ABCD)(?=EFG)/,/;print $foo;

假定要把某段文本中所有的Jeffs都替换为Jeff’s(匹配到单词边界),那么,就会有如下几种方案:

方案
说明
s/\bJeffs\b/Jeff’s/g 这是最直观的方法,直接把匹配到的Jeffs替换为Jeff’s
s/\b(Jeff)(s)\b/$1′$2/g 分组构造,在两个组之间插入单引号
s/\bJeff(?=s\b)/Jeff’/g 预测,仅匹配“后面紧跟s并在单词边界的Jeff”的右侧那个位置
(其它的如Jeffrey中的Jeff不被匹配)
s/(?<=\bJeff)(?=s\b)/’/g 同时使用预测和回顾,在匹配位置处插入单引号
s/(?=s\b)(?<=\bJeff)/’/g 与上面的行为完全相同,由于预测和回顾只是匹配位置而不是匹配字串,所以先后次序不重要了。

四、解决问题

利用预测和回顾,就可以解决“在数值中插入逗号”的问题了。插入算法是:从右向左把数字,每3个分为一组,在组与组之间插入逗号。也就是说,只要匹配到这些位置,就可以在这些位置上插入逗号。

$foo = "123456789";$foo =~ s/(?=\d\d\d)/,/g;print $foo;

上面的结果是“,1,2,3,4,5,6,789”,显然不是想要的。

正则表达式 结果 说明
s/(?=\d\d\d)/,/g
s/(?=(\d\d\d)+)/,/g
,1,2,3,4,5,6,789 1左边的位置被匹配,因为该位置右边有3个数字(123)
同理,1右边的位置也被匹配,该位置右边有3个数字(234)……
而7的右边位置不再被匹配,因为该位置右边不再有3个数字
s/(?=\d\d\d$)/,/g 123456,789 只有7左边的位置被匹配,因为该点符合以下两个条件:
其右边是3个数字,
且这3个数字在行尾(其右侧什么也没有了)。
s/(?=(\d\d\d)+$)/,/g ,123,456,789 1左边的位置符合条件“其右侧有N组(每组3个数字),且最后一组在行尾”;
2左边的位置不符合该条件,因为其后面是234、567和89,最后的89无法构成一组;
其它同理……
s/(?=(\d\d\d)+$)(?<=\d)/,/g 123,456,789 1左边的位置不再被匹配,因为(?<=\d)的存在,它指示该位置的左侧必须是数字,
而1左边的位置显然不符合。

这个正则表达式替换单个的数字串没问题,但如果想要将“The population of 281421906 is growing”中的281421906替换成281,421,906就无能为力了,原因是$限定了最后一组(3个)数字必须在行尾,因此需要把条件修正为“最后一组数字的右边不是数字”,这就要用到“否定预测”:

s/(?=(\d\d\d)+(?!\d))(?<=\d)/,/g

(微软VBScript.DLL提供的COM对象IRegExp2,文档里说支持“回顾”语法,但实际运用却会抛出一个异常,不知何故)

2007年01月16日

地址:http://v.blog.sina.com.cn/b/582584-1147984444.html

无论如何,英达果然是个牛人,他才是这幕戏的主角。

2007年01月13日

路上:

路上

山脚:

山脚

索道:

索道

滑雪场:

滑雪场

第一次见到雪:

第一次见到雪

高兴坏了:

高兴坏了

滑雪:

滑雪

红妆素裹:

红妆素裹

2007年01月06日

一想起回收奶事件,就觉得反胃,这次乳品业又出来个可怕的牛奶,这下更不敢喝了。

南方周末也不负责,只说是在西安有分公司的某上市公司,却不点名是哪家,害得我只好错杀一千也不放过一个,按文章原文所说“目前在陕的乳品企业有银桥、光明、东方、蒙牛、伊利5家”,只好把这5家都列在我自己的乳品黑名单里。

文章里还提到了该上市公司的一些特征:

  • 该企业在陕西的市场销量并不大,其生产的乳饮料和纯牛奶系列产品主要销往安徽、江苏、山东等省。
  • 该企业西安分公司所在地是临潼
  • 该分公司原奶源部宝鸡片区奶站质量监管员名叫王磊,任职期是2005年8月~2006年6月。
  • 该分公司2006年2月曾对为其提供奶源的“三陵奶站“进行过处罚
  • 文章中的主角,举报人蒋卫锁,自2002年底便开始长期稳定地向这家全国知名的乳制品企业供应鲜奶。3年后,他发展成为该企业在陕西省最大的奶源供应商之一。在2004年,蒋在杨凌大寨乡官村的私人奶站已经达到日收奶量30多吨的规模,年收入近2000万元,并获得该企业“2004年优秀原奶供应商”称号。同年蒋卫锁被团中央、农业部等授予中国杰出青年农民奖。

2007年01月05日

MasteringRegEx

一、Non-Capturing

这个Non-Capturing的确不知道该如何翻译(非捕获?),但意思是明白了。用圆括号括起来的部分可以被捕获到$1、$2……中,但如果起始圆括号后面紧跟一个问号和冒号,就表示本组不需要被捕获到$1、$2……中。

例如,下列正则表达式中

^([-+]?\d+(\.\d*)?)([CF])$

  • 第1个被捕获的组:从第一个左圆括号,到与该左圆括号配对的右括号,被存储到$1中(整数+小数点+小数部分)
  • 第2个被捕获的组:从第二个左圆括号,到与该左圆括号配对的右括号,被存储到$2中(小数点+小数部分)
  • 第3个被捕获的组:从第三个左圆括号,到与该左圆括号配对的右括号,被存储到$3中(C或F)

如果是下面的正则表达式:

^([-+]?\d+(?:\.\d*)?)([CF])$

注意红色的部分,有了这个就表示本组不要捕获,这样,$2中存储的就是([CF])这一组,没有$3了。

二、字符转义

  • \bTAB字符(ASCII码9)
  • \n换行符(ASCII码10)
  • \r回车符(ASCII码13)
  • \s所有能代表“空白”的字符(包括空格、制表、换行等)
  • \S所有不是\s的字符
  • \w大小写字母、数字和下划线,与[a-zA-Z0-9_]等价
  • \W所有不是\w的字符,与[^a-zA-Z0-9_]等价
  • \d数字,与[0-9]等价
  • \D所有非数字字符,与[^0-9]等价

三、文本替换

先前知道了m的作用(匹配),把m换成s就成了“替换”,另外,好友告诉我有了m,就可以用任何一对符号来括住正则表达式,不必老是用/,说不定s也一样。

$name =~ s/\bSaddam\b/Bush/;

这句表示:把$name里面存储的内容,凡是匹配到单词边界的Saddam,通通替换为Bush。

四、Perl的一些语法

while ($var = <>) {}

这个“$var=<>”看起来有点古怪,其实就是“var = getline()”,也就是说,从输入流中取下一行的内容,并赋值变量中,如果输入流已结束,则变量值未定义,同时整个赋值表达式返回布尔假。

关键字last,与C里面的continue类似。

函数defined($var)测试一个变量里是否含有值,是则返回true,否则返回false。

2007年01月03日

好友给我出了个题,绕来绕去把我给绕晕:

牌盒里有三张牌,一张是两面黑色,一张是两面红色,一张是一面红一面黑。问,当抽出一张牌,你看到牌的正面是红色时,另一面也是红色的几率是多少?

我的第一感告诉我,由于“牌的正面是红色”是已知条件,那么牌的背面非红即黑,各占50%的概率。同时,我的经验也告诉我,答案如此简单,那么是正确答案的概率就几乎为零。:)

其实这张两面是黑色的牌完全可以不存在,或者存在1000张,都不影响结果,因为我们永远不可能抽到一张某面为黑色的牌(否则就不合题意),当,且仅当抽出一张某面为红色的牌时,才来计算概率。

所以两面黑色的牌完全可以去掉,只留下两张牌:一张是两面红色,另一张是一面红、一面黑。

似乎清楚了一点点, 两张牌共有四面,其中红色占据三面,啊,看到红色的概率有四分之三这么大哦……慢着,问题并不是问“从两张牌中抽一张,看到某面红色的概率有多大”,而是“当你看到一张某面为红的牌时,另一面也是红色的概率是多少”。

嗯,当且仅当你抽到两面都是红色的那张牌时,第二面也是红色的情形才存在,那么似乎概率又是二中取一,应该是50%喽。

……啊,漏掉了一点,如果抽到的牌是一面红一面黑的那样,且黑面向上时,这种情况是不予考虑(不参予计算概率的),所以,总共可以参加计算的情况其实只有3种:

  1. 抽到一面红一面黑的那张牌,红面向上。
  2. 抽到两面红的那张牌,A面向上。
  3. 抽到两面红的那张牌,B面向上。

上述三种情况,只有2、3种时,牌的第2面是红色,所以答案是三分之二,也就是说:当你抽到一张牌发现它的这一面是红色时,另一面也是红色的概率是三分之二

推而广之:

  • 从三张牌(一张双面红、一张双面黑、一张一面红一面黑)中任意抽取一张,看到是正面是红的概率是二分之一。
  • 从三张牌中任意抽取一张,看到正面是红,且另一面也为红的概率是三分之一
  • 从三张牌中任意抽取一张,看到正面是某种颜色,且另一面也为同种颜色的概率是三分之二

我被这个好友绕晕了,所以上述结论不排除被他再次绕倒推翻的可能。

2006年12月29日

这个页面简直就是典型的色情站点首页,各种令人面红耳赤心跳加速的淫词艳语充斥其间,还以为是来到了哪个成人站点呢,其实这个是新浪读书频道的页面,截图时间是2006-12-28。

新浪耶?性浪耶?

新浪读书页面截图

2006年12月28日

这样一个简单的表,只有一个名为ID的int型字段。表中,某些ID重复出现多次,有的只出现一次,很显示,要列出所有出现次数大于1的ID,及其的次数,可以用如下SQL语句实现:

select ID, count(*)    from XXX    group by ID    having count(*) > 1

如果要知道这个结果的数据集的行数(也就是有多少个出现次数大于1的ID),应该是:

select count(*) from    ( select ID, count(*) as ct        from XXX        group by ID        having count(*) > 1    ) A

 注意最后一行,右括号后面的那个A,这个是临时数据集的别名,如果不指定这个别名,整句SQL的语法就是错误的(别名是什么不重要,重要的是必须要指定这个别名),也就是说,它不支持匿名的临时数据集。

此外,在MSSQL 2000上面,在临时数据集的count(*)这个匿名字段指定名字(as ct)的话,同样也是一个语法错误,据朋友验证,这个在MySQL上面不是错误,但同样要为临时数据集指定别名。

一上网就惊闻黎老已在其BLOG上面推出了四色问题的简单证明,拜读后发现证明方法果真是简单得惊人,原来四色问题根本就是个公理,不需要证明的。

黎老的原文在此,摘录其中重点如下:

为什么球面或平面地图面积的全相邻数不可能等于或大于5以上的自然数。这可以通过大量试错的过程来获得证明,并以此而作为归纳性的公理。

原来通过大量试错没有反例就可以得到公理,那哥德巴赫猜想也是公理,因为现在还没有哪位能举出反例。而且上面那个“不可能大于等于5”,其实就是“最多为4”的另一种说法,不大于也不等于5,不就是小于等于4吗,绕那么大的弯子是想忽悠谁?换句话来说,“不大于等于5”如果是公理,那么“小于等于4”也是公理了,那还证明个屁?

可怜的方舟子,差点与一个公理进行决斗。