Apr 21, 2009
Mar 9, 2009
【转】09年3月编程语言排行榜:VB江河日下,JavaScript稳步上升
2009年3月榜单
前十名长期趋势
以下是排名21到50的语言
Mar 8, 2009
【《程序员》2008年4月刊】做人、做事,做架构师——架构师能力模型解析
文 / 周爱民
引子
究竟是什么让你在同一个位置上——例如程序员或技术负责人——工作了三年、五年或者更久,而仍然得不到任何的发展空间?你觉得自己已成为技术圈中 的大牛,并信心满满地去拿明天就要颁发的某某大奖,然而却仍然停留在同样的技术职位上,去年到今年涨的薪水甚至填不平物价升幅?于是,你开始对老板不满, 对员工不满,对昨天升职的那个同事不满……你开始计划明天就要跑单,或者准备考虑提出加薪却又心怀忐忑。
如果技术人员有发展的轨迹,那么他要么“看透工具的本质,把关注点转移到‘团队’的圈子里去”,要么“顺着代码铺就的道路,亦步亦趋地成为良匠大 师”。仅以技术方向而言,你大概可以做到架构师、总架构师甚至首席架构师;但问题是:你现在还只是一个程序员。那要如何才能踏上通往架构师之路呢?本文为 你解析一个架构师的能力模型。
你能不能做一个好的架构师?
架构师不是界定一个技术高下的职位名称,而是一个职务。所谓职务,包括职——职位,务——工作。前者决定了你具备哪些资源,可以影响到怎样的范围,以及面向的机构,后者则简单地是你需要完成的工作列表。
所以我说“架构师”不是指“一个能做架构的人”。前者是把架构师当职能,后者是当工人。能做一份工作列表中的事,并不等于就成为相应职位上的人。 在管理体系里面,你的个人特性决定了你在哪个位置,而技术技能只是做事实施的必需。架构师这个职务,同时要求较高的个人素质和技术能力,因此它的进取之路 总结起来就是:做人、做事,做架构师。
因此“模型”由“个人特性”和“技术技能”两个方面构成,在第一张图中,我特别说明“个人特性”既包括人际关系的能力,也包括(具体)业务能力;“技术技能”也是如此。所以个人特性主要与“做人”有关,部分地也包含“做事”的要素。

图1 架构师能力模型
“有效沟通”以及“学会谈判”与做具体的事无关,是个人能力特性的公共方面。前者是过程,后者是知道如何定目标与求结果。而“风险与防备”是做事 过程控制的关键,与前面两项正好构成了一个做事基本能力的完整体系。基本上,这三项个人特性都是一个“普通程序员”所不具备的,甚至在大多数情况下,普通 程序员并不愿意去具备这样的个人特性,因为在许多陷于技术泥淖的开发人员看来:沟通总是会使事情变得更加麻烦,谈判则徒耗时间而无济于事。然而事实上,在 整个的架构决策过程中,架构师需要不停地沟通与谈判。将“架构”变成“决策”的过程,其实就是对各个技术角色(及其思想)兼容并包的过程,你需要不断地协 调需求、实现之间的各种问题,也需要面对各种投资者(时间、资金、人才等方面的决策者)进行谈判,以确定项目的规模——没有规模也就没有范围,没有范围如 何展开设计呢?
一部分开发人员会认为上述过程是“项目经理”的事情,但真的如此吗?当你作为一个更高级别的架构师,以至于要影响到多个项目的决策时,你就全然不会有这种感受了。因为这种情况下,你的决策将先于项目的启动,或者说你已经不单单是一个技术角色了。
设计是架构能力的一部分,但架构师不是设计师——看清楚二者之间的不同,你才真正迈出了架构师职业生涯的第一步。
抽象是思维能力、模型化是表达能力
个人特性中另一个非常重要的方面是“抽象思维”,而这是与架构师角色直接相关的一种能力。这种能力既有职业技能特征,又是普遍性的能力。
所谓普遍性的能力,是指“抽象”在我们——作为人这种个体的——生活中无处不在。例如我们说花、草,说桌、椅……我们用语言去指称任何一个既已存 在的(可以脱离我们的语言而自然存在的)事物时,就用到了抽象。说“桌子”的时候,既没有描述桌子的具体形式,也没有说明它的规格,但我们用这个名词时, 所有人都知道“桌子是什么”。所以,名词概念是整个抽象逻辑系统中的主体。如果失去了这些名词定义,我们基本上不能说话,也不能描述任何东西——那便到了 “只可意会不可言传”的境地。
用现有的成熟语汇去描述你的系统时,大多数人会理解你所表达的含义,例如我们说“这个系统设计为一个三层结构”。然而架构师面临的系统在许多细节 上并不见得能够用成熟的语汇去描述,因此必须自已构建一个抽象系统,这就需要概念抽象能力、概念表达能力和基于概念的逻辑表达能力。
概念抽象能力是一种思维能力。简单地说,就是“把目标分解或概括清楚”:你要么概而言之“它是什么”,要么详细地说明“它包括什么”。必须使用大 量的语汇来陈述这个“什么”,这不单单是表达为文字,也表达为你在思想过程中的一个完整系统。通常用的方法是“映射系统”。例如你可以用数学中的“数轴” 来映射“实数域”。将目标系统形式化为一个概念化的、可讨论的结构系统后,你的抽象过程就基本结束了。

图2 能力模型中的个人特性
然而这个“抽象系统”可能只构建在你的思维意识里,还必须把它描绘出来。因为不能只是你自己思考清楚,系统就能设计完成。这个“描绘”就依赖于后面两种表达能力,一种是描绘概念实体,一种是描绘实体上的逻辑——有趣的是,这似乎又回到了“程序=结构+算法”。
现在大家回过头来看看UML,或者更多种类的ML(建模语言),他们就用于表达这两个方面的东西:要么是概念实体(例如用一个框表明系统边界),要么是实体上的逻辑(例如用箭头表明逻辑时序)。
所以大家应该清楚,我们再如何称赞UML,它也只是一种对模型化系统的“表达能力”,你只能把它当一种辅助表达的工具去使用,它本身既不能帮助思考,也不见得能作为抽象过程中的或抽象思维环境中的参考。
任何一个优秀的架构师都有自己独特的思考方式,这决定了他如何抽象系统,以及如何“创造性地”设计与构画这个系统。这种“独特的思考方式”贯彻他 从孩童开始的整个成长过程,直至他形成独立的社会观、人生观与世界观。他认识世界的方式和接受世界的能力决定于他如何思考,也反映了他这种思考方式的“独 特性”。但这并不表明他有特立独行的行为特性(我们这里只说他的思考方式),我们不应介意他是否用某种语言(例如UML或者形式化编程语言)来表达他的思 考结果。
推动:设计做大,实施做小
架构师首先是把问题的真正目标确定下来,然后变成系统设计、平台设计或架构设计。而在此之后设计输出将会有两个方向的发展,一是被忠实地贯彻下 来,二是被变形地发展下去。两个方向都存在致命的危险:架构最终能否被完整实现。对前者来说,可能是架构设计过度,或设计本身出现了错误;后者则是对架构 直接的伤害。
所以架构师必须参与实施的全程——尤其是在架构被映射为目标系统的前期。在这个阶段中,架构师的任务就是推动架构实施,以保证在项目全程的设计/ 架构/体系的一致性。除了直接跟设计师或设计团队沟通,以保证他们的设计在你可以控制的范围之内以外,架构师还必须有阶段化设计的能力。这种能力用于将一 个原本规模宏大的架构设计,变成较小的、易于实施的、对开发团队来说可控的关键点。例如在体系层次的规划上,设计可能是独立、异质的、可迁移的存储框架来 实现数据层,但在(前期的)实施上,这里可能被表达为本地数据库,并要求前端开发人员必须通过一个清晰的数据交互层来访问——例如一组数据存取接口,或一 个独立数据服务组件。开发人员可能在这里遇到障碍:因为要通过这些中间层来访问本地数据库,纯粹是多余的。然而,正是这“多余的工作”提供了系统弹性,为 并行团队开发公共存储服务争取了周期,也为将来的灵活部署与数据迁移提供了可能。
这里的关键就在于,无论原始系统设定有多大,实施时总是在“做小”。每一个局部的实施块都是可控的,并为它在整个体系空间中留下了位置和接口,这 样才可能由“小的部分”做大。一个大系统的架构师可能同时在考虑许多个项目中的、不同位置的架构,并且清楚这些项目最终的总体规模。而这,就是平台架构师 和体系架构师所涉的领域。

图3 架构师模型图中的“实现能力”
架构真的是“好不好”的问题吗?如同我对工程的理解一样,架构问题的根本,也并不在于它是否完美或漂亮,而是在于是否合用。因此架构师必须对实施 架构的团队以及实施过程有充分了解,知道他们的能力缺陷,知道实现过程要消耗的资源,清楚每个环节可能的故障以及先兆。只有这样,架构师才能设计一个让这 个团队能实现,而且在实现过程中能受控的架构。
要知道,你作为架构师被请来,不是画几张图纸交给项目经理,说:你们去做吧,做不出来是你们不会做。即使你可以身体力行,在这个团队中教大家、培 养大家,那么公司的开销呢?风险呢?这些东西难道就不考虑了?项目的周期因为实现的复杂程度而无法控制时,项目就死掉了。那么,追根究底来说,是不是架构 师的问题?是啊,你为什么会做了一份“不合用”的架构呢?——你都不知道项目如何开发、由谁实施、如何管理等等,又如何能面对这些实际环境去设计架构呢?
所以这一部分能力,是要在你的开发经验、团队经验以及用人识人的经验中去找的。参考模型图的“实现能力”下的“设计能力→了解你的主要沟通对象”和“架构推行”等分支,对你或有一些可用的提示。
局部与全局
架构是一个从全局到局部的过程,而实施正好反过来,是从局部到全局。这也正是“设计做大,实施做小”的另一个层面的含义。设计大才可以见到全局,才知道此全局对彼全局的影响;实施小才可能关注细节,才谈得上品质与控制。
事实上,大多数情况下架构是在为“当前项目之外”去考虑,这可以看成全局关注的一个组成部分。因此我们需要界定所谓“全局”的范围:超出公司或整个产品系列、产品线或规划的范围才是多余的。
所以当架构决策谈及“全局”时,其目标并不见得是“保障当前项目”,而又必须由当前项目去完成。
一个经常被用到的例子是:如果仅为当前项目考虑,那么只需要做成DLL模块;如果为产品线考虑,可能会是“管道+插件”的结构形式。而“管道+插件”的形式显然比做成DLL模块更费时,这个时间成本(以及其它成本)就变成了当前项目的无谓开销。
这种全局策略对局部计划的影响是大多数公司不能忍受的,也被很多团队所垢病。然而这却是架构师角色对体系的“近乎必然”的影响——如果你试图在体 系中引用架构师这个角色的话。一些情况下,体系能够容纳这种影响,例如“技术架构师”试图推动某种插件框架,而正好开发人员对这项技术感兴趣,那就顺其自 然地花点工夫去实现了。但如果不是这样,实施者或实施团队看不到“多余的部分”对他们的价值时,来自局部的抵触就产生了。
这种情况下,平衡这些抵触就成了架构推行的实务之一。在我看来,“平衡”是全局的艺术和局部的技术。也就是说,一方面架构师要学会游说,另一方面 也要寻求更为简洁的、成本更小的实现技术。只有当整个体系都意识到(你所推行的)架构的重要性,而且实施成本在他们可以接受的范围之内时,他们才会积极行 动起来。
所以所谓平衡,其实也是折衷的过程。构架师只有眼中见大,才知道哪些折衷可以做,而哪些不能。所谓设计评估(模型图中的实现能力->设计能 力->设计评估分支)并不是去分析一个设计结果好或不好,而是从中看到原始的需求,看到体系全局的意图,然后知道在将设计变得更为“适当”时可以做 哪些折衷。同样的原因,架构师也必须知道自己的决策会产生的影响,才能控制它们,以防它们变成团队的灾难。有些时候,架构师甚至需要抛弃一些特性,以使得 项目能够持续下去。因为产品的阶段性产出只是整个战略中的一个环节,而不是全部。
其它
“怎么做一个架构师”这个问题得分成两个部分来看,一个是“做到”,一个是“做好”。由于架构师本身不过是一个技术职位,所以时机成熟了自然会做得到。但问题是,真有一天你被放在这个位置上了,你能做得好吗?
我浏览过几套所谓培训机构的有关架构师的教程,也翻阅过一些讲架构的书。我发现他们普遍地是将架构作为一种“职业技术”来讲,就像培养程序员或者 缝纫工一样来教育。但就我的经验来说,架构并不是一件纯粹表现技术能力的工作,所以并不是翻几本书学几种方法就可以投入“实战”的。更深层的问题是,架构 师其实不是“战”出来的。昨天跟同事讨论这个话题,他把我们这几年来的一些思考用了三句话来概括,非常精彩:从无到有的,是架构;从表到里的,是抽象;从 粗到细的,是设计。
那么到底什么是架构呢?从上面的概括中你是看不到答案的。到底如何做架构呢?从本文中你也是看不到答案的。然而我说,“你看不到答案”的根源其实 是在于你的眼光与心性——后面这个词换成现代白话,就是“思想”。真正阻碍了你成为优秀架构师的,也许正是你既有的知识与思想方法,扔掉它们,接受一些全 然有别的信息,也许正是良好的开端。
或许你现在正愤愤然:这篇文章怎么空洞无物?——我甚至能想象到一些读者的表情。然而请在问题面前停下来,不要急于给出答案。正如你将“?”稍微变下形,它就成为了“!”一样,问题的本身,就是答案。
作者简介
周爱民(aimingoo),具有十余年的软件开发、项目管理和团队建设的经验,现担任盛大网络的平台架构师,著有《大道至简》、《Delphi源代码分析》等。
Mar 7, 2009
【转】Delphi中常用VCL函数说明
BCB/Delphi中常用的VCL函数说明
说明,BCB,Delphi,C++Builder,内存分配,文件操作,磁盘目录管理,字符串操作,时间日期管理,类型转换
--------------------
内存分配
--------------------
函数名称:AllocMem
函数说明:在队中分配指定字节的内存块,并将分配的每一个字节初始化为 0.函数原型如下:
void * __fastcall AllocMem(Cardinal Size);
函数名称:SysFreeMem
函数说明:释放所指定的内存块.函数原型如下:
int __fastcall SysFreeMem(void * P);
函数名称:SysReallocMem
函数说明:要求重新分配参数Size所指定的内存.函数原型如下:
void * __fastcall SysReallocMem(void * P , int Size);
--------------------
文件操作
--------------------
函数名称:ChangeFileExt
函数说明:更改指定文件的扩展名,函数原型如下:
AnsiString __fastcall ChangeFileExt(const AnsiString FileName,const AnsiString Extension);
函数名称:DeleteFile
函数说明:在计算机磁盘中删除指定的文件,如果操作成功,则函数返回真,函数原型如下:
bool __fastcall DeleteFile(const AnsiString FileName);
函数名称:ExtractFileDir
函数说明:返回指定文件的工作目录,函数原型如下:
AnsiString __fastcall ExtractFileDir(const AnsiString FileName);
函数名称:ExtractFileDrive
函数说明:返回指定文件的驱动器,函数原型如下:
AnsiString __fastcall ExtractFileDrive(const AnsiString FileName);
函数名称:ExtractFileExt
函数说明:返回指定文件的扩展名,函数原型如下:
AnsiString __fastcall ExtractFileExt(const AnsiString FileName);
函数名称:ExtractFileName
函数说明:返回指定文件的文件名及扩展名,函数原型如下:
AnsiString __fastcall ExtractFileName(const AnsiString FileName);
函数名称:ExtractFilePath
函数说明:返回指定文件的工作路径,函数原型如下:
AnsiString __fastcall ExtractFilePath(const AnsiString FileName);
函数名称:FileAge
函数说明:返回指定文件的时间标签,如果操作失败,则返回-1,函数原型如下:
int __fastcall FileAge(const System::AnsiString FileName);
函数名称:FileClose
函数说明:关闭指定的文件,函数原型如下:
void __fastcall FileClose(int Handle);
函数名称:FileCreate
函数说明:以指定的文件名称创建一个新的文件,如果返回为正数,表示操作成功,返回值为文件句柄,如果返回值为-1,表示操作失败.函数原型如下:
int __fastcall FileCreate(const System ::AnsiString FileName);
函数名称:FileExists
函数说明:用于测试指定的文件是否存在,如果存在返回真,否则返回假,函数原型如下:
bool __fastcall FileExists(const System::AnsiString FileName);
函数名称:FileGetAttr
函数说明:返回指定文件的属性,如果操作失败,则函数返回-1,函数原型如下;
int __fastcall FileGetAttr(const System::AnsiString FileName);
函数名称:FileGetDate
函数说明:返回指定文件的DOS时间标签,如果操作失败,则近回-1,函数原型如下:
int __fastcall FileGetDate(int Handle);
函数名称:FileOpen
函数说明:打开指定的文件,如果返回为正数,表示操作成功,返回值为文件句柄:如果返回值为-1,表示操作失败,函数原型如下:
int __fastcall FileOpen(const System::AnsiString FileName,int Mode);
函数名称:FileRead
函数说明:从文件中读取指定字节的数据到缓冲区中,函数返回实际读取的字节数,函数原型如下;
int __fastcall FileRead(int Handle,void *Buffer,int Count);
函数名称:FileSeek
函数说明:调整文件指针到新的位置,如果操作成功,则返回新的文件位置,如果操作失败,则函数返回-1,函数原型如下:
int __fastcall FileSeek(int Handle,int Offset,int Origin);
函数名称:FileSetAttr
函数说明:更改指定文件的属性参数,如果操作成功,则返回0,函数原型如下;
int __fastcall FileSetAttr(const System::AnsiString FileName,int Attr);
函数名称:FileSetDate
函数说明:更改指定文件的DOS时间标签,如果操作成功,返回0,否则返回错误代码,函数原型如下:
int __fastcall FileSetDate(int Handle,int Age);
函数名称:FileWrite
函数说明:将缓冲区的数据写入到指定的文件的当前位置中去如果操作成功,函数返回实际写入的字节数,如果返回为-1,则表示操作产生错误,函数原型如下:
int __fastcall FileWrite(int Handle,const void *Buffer,int Count);
函数名称:FindClose
函数说明:释放FindFirst操作所申请的内存资源,函数原型如下:
void __fastcall FindClose(TSearchRec &F);
函数名称:FindFirst
函数说明:在指定的文件目录内,搜寻符合特定属性参数的文件,如果成功地查找到符合条件的文件,则函数返回0,否则函数返回一个错误代码,函数原型如下:
int __fastcall FindFirst(const System::AnsiString Path,int Attr,TSearchRec &F);
函数名称:FindNext
函数说明:继续搜寻FindFirst所指定属性参数的文件,如果成功地查找到符合条件的文件,则函数返回0,否则函数返回一个错误代码,函数原型如下:
int __fastcall FindNext(TSearchRec &F);
// 本文转自 C++Builder 研究 - http://www.ccrun.com/article.asp?i=983&d=22l01d
函数名称:RenameFile
函数说明:更改指定文件的名称,如果操作成功,则函数返回真,函数原型如下:
bool __fastcall RenameFile(const AnsiString OldName, const AnsiString NewName);
--------------------
磁盘目录管理
--------------------
函数名称:CreateDir
函数说明:创建新的目录,如果操作成功,返回真,否则返回假,函数原型如下:
bool __fastcall CreateDir(const AnsiString Dir);
函数名称:DiskFree
函数说明:返回指定磁盘的剩余空间,如果操作成功,返回剩余磁盘空间,如果操作失败,则返回-1,函数原型如下:
int __fastcall DiskFree(Byte Drive);
函数名称:DiskSize
函数说明:返回指定磁盘的空间,如果操作成功,返回磁盘空间,如果操作失败,则返回-1,函数原型如下:
int __fastcall DiskSize(Byte Drive);
函数名称:GetCurrentDir
函数说明:返回当前工作目录,函数原型如下:
AnsiString __fastcall GetCurrentDir();
函数名称:RemoveDir
函数说明:删除指定的目录,如果操作成功,返回真,否则返回假,函数原型如下:
bool __fastcall RemoveDir(const AnsiString Dir);
函数名称:SetCurrentDir
函数说明:设置当前工作目录,如果操作成功,则返回真,函数原型如下:
bool __fastcall SetCurrentDir(const AnsiString Dir);
--------------------
字符串操作
--------------------
函数名称:CompareStr
函数说明:比较两个AnsiString字符串,函数原型如下:
int __fastcall CompareStr(const AnsiString S1, const AnsiString S2);
函数名称:CompareText
函数说明:比较两个AnsiString字符串,函数原型如下:
int __fastcall CompareText(const AnsiString S1, const AnsiString S2);
函数名称:LowerCase
函数说明:将指定的AnsiString字符串转换为小写形式,函数原型如下:
AnsiString __fastcall LowerCase(const AnsiString S);
函数名称:StrAlloc
函数说明:为字符串分配指定字节的内存,并返回内存指针,函数原型如下:
char * __fastcall StrAlloc(Cardinal Size);
函数名称:StrBufSize
函数说明:返回*Str所指向内存的大小,函数原型如下:
Cardinal __fastcall StrBufSize(const char * Str);
函数名称:StrCat
函数说明:连接两个字符串,并返回目的字符串指针,函数原型如下:
char * __fastcall StrCat(char * Dest, const char * Source);
函数名称:StrComp
函数说明:两个字符串相到比较,返回比较的结果,函数原型如下:
int __fastcall StrComp(const char * Str1, const char * Str2);
函数名称:StrCopy
函数说明:将源字符串拷贝到目的字符串中,函数原型如下:
char * __fastcall StrCopy(char * Dest, const char * Source);
函数名称:StrECopy
函数说明:将源字符串拷贝到目的字符串中,并返回目的字符串结尾指针,函数原型如下:
char * __fastcall StrECopy(char * Dest, const char * Source);
函数名称:StrEnd
函数说明:返回字符串结尾指针,函数原型如下:
char * __fastcall StrEnd(const char * Str);
函数名称:StrIComp
函数说明:两个字符串相互比较(不论大小写),返回比较的结果,函数原型如下:
int __fastcall StrIComp(const char * Str1, const char * Str2);
函数名称:StrLCat
函数说明:将指定数目的源字符串连接到目的字符串,并返回目的字符串指针,函数原型如下:
char * __fastcall StrLCat(char * Dest, const char * Source, Cardinal MaxLen);
函数名称:StrLComp
函数说明:对两个字符串指定数目的字符进行比较操作,函数原型如下:
int __fastcall StrLComp(const char * Str1, const char * Str2, Cardinal MaxLen);
函数名称:StrLCopy
函数说明:将源字符串指定数目的字符拷贝到目的字符串中,并返回目的字符串指针,函数原型如下:
char * __fastcall StrLCopy(char * Dest, const char * Source, Cardinal MaxLen);
函数名称:StrLen
函数说明:返回字符串的长度,函数原型如下:
Cardinal __fastcall StrLen(const char * Str);
函数名称:StrLower
函数说明:将字符串转换为小写形式,函数原型如下:
char * __fastcall StrLower(char * Str);
函数名称:StrMove
函数说明:从源字符串向目的字符串拷贝指定数目的字符,函数原型如下:
char * __fastcall StrMove(char * Dest, const char * Source, Cardinal Count);
函数名称:StrNew
函数说明:在堆中为指定字符串分配空间,并将字符串拷贝到此空间中,函数原型如下:
char * __fastcall StrNew(const char * Str);
函数名称:StrPas
函数说明:将指定的字符串转换为AnsiString类型字符串对象,函数原型如下:
AnsiString __fastcall StrPas(const char * Str);
函数名称:StrPCopy
函数说明:将AnsiString类型的源字符串拷贝到目的字符串中,并返回目的字符串指针,函数原型如下:
char * __fastcall StrPCopy(char * Dest, const AnsiString Source);
函数名称:StrPLCopy
函数说明:将源字符串(AnsiString类型)指定数目的字符拷贝到目的字符串中,并返回目的字符串指针,函数原型如下:
char * __fastcall StrPLCopy(char * Dest, const AnsiString Source, Cardinal MaxLen);
函数名称:StrPos
函数说明:在Strl所指定的字符串中寻找Str2所指定的子字符串,并返回Str2在Str2中第一个子字符的指针,函数原型如下:
char * __fastcall StrPos(const char * Str1, const char * Str2);
函数名称:StrRScan
函数说明:在指定的字符串中寻找特定的字符,并返回字符串中最后一个特定字符的指针,函数原型如下:
char * __fastcall StrRScan(const char * Str, char Chr);
函数名称:StrScan
函数说明:在指定的字符串中寻找特定的字符,并返回字符串中第一个特定字符的指针,函数原型如下:
char * __fastcall StrScan(const char * Str, char Chr);
函数名称:StrUpper
函数说明:将字符串转换为大写形式,函数原型如下:
char * __fastcall StrUpper(char * Str);
函数名称:UpperCase
函数说明:将指定的AnsiString字符串转换为大写形式,函数原型如下:
AnsiString __fastcall UpperCase(const AnsiString S);
--------------------
类型转换
--------------------
函数名称:FloatToStr
函数说明:将浮点数转换为AnsiString字符串,函数原型如下:
AnsiString __fastcall FloatToStr(Extended Value);
函数名称:FloatToStrF
函数说明:将浮点数转换为指定格式的AnsiString字符串,函数原型如下:
AnsiString __fastcall FloatToStrF(Extended Value, TFloatFormat Format,int Precision, int Digits);
函数名称:IntToHex
函数说明:将整数转换为十六进制字符串,函数原型如下:
AnsiString __fastcall IntToHex(int Value, int Digits);
函数名称:IntToStr
函数说明:将整数转换为AnsiString字符串,函数原型如下:
AnsiString __fastcall IntToStr(int Value);
函数名称:StrToFloat
函数说明:将AnsiString字符串转换为一个浮点数值,函数原型如下:
Extended __fastcall StrToFloat(const AnsiString S);
函数名称:StrToInt
函数说明:将AnsiString字符串转换为整数值,如果不能进行转换,则产生EConvertError异常,函数原型如下:
int __fastcall StrToInt(const AnsiString S);
函数名称:StrToIntDef
函数说明:将AnsiString字符串转换为一个数值,函数原型如下:
int __fastcall StrToIntDef(const System::AnsiString S,int Default);
--------------------
时间日期管理
--------------------
函数名称:Date
函数说明:返回TDateTime对象,包含当前的年月日信息,函数原型如下:
System::TDateTime __fastcall Date(void);
函数名称:DateTimetoStr
函数说明:将TDateTime对象转换为字符串对象,函数原型如下:
AnsiString __fastcall DateTimeToStr(System::TDateTime DateTime);
函数名称:DatetimeToString
函数说明:将TDateTime对象转换为指定格式的字符串对象,函数原型如下:
void __fastcall DateTimeToString(AnsiString &Result, const AnsiString Format,System::TDateTime DateTime);
函数名称:DateTimeToSystemTime
函数说明:将TDateTime对象转换为操作系统时间,函数原型如下:
void __fastcall DateTimeToSystemTime( System::TDateTime DateTime, _SYSTEMTIME &SystemTime);
函数名称:DateToStr
函数说明:将TDateTime对象(包含当前年月日信息)转换为字符串对象,函数原型如下:
AnsiString __fastcall DateToStr(System::TDateTime Date);
函数名称:Now
函数说明:返回TDateTime对象,获取当前的日期和时间信息,函数原型如下:
System::TDateTime __fastcall Now(void);
函数名称:StrToDate
函数说明:将字符串对象转换为年月日对象,函数原型如下:
System::TDateTime __fastcall StrToDate(const AnsiString S);
函数名称:StrToDateTime
函数说明:将字符串对象转换为年月日时间对象,函数原型如下:
System::TDateTime __fastcall StrToDateTime(const AnsiString S);
函数名称:StrToTime
函数说明:将字符串对象转换时间对象,函数原型如下:
System::TDateTime __fastcall StrToTime(const AnsiString S);
函数名称:SystemTimeToDateTime
函数说明:将操作系统时间转换为TDateTime对象,函数原型如下:
System::TDateTime __fastcall SystemTimeToDateTime(const _SYSTEMTIME &SystemTime);
函数名称:Time
函数说明:返回TDateTime对象,包含当前的时间信息,函数原型如下:
System::TDateTime __fastcall Time(void);
函数名称:TimeToStr
函数说明:将当前日期转换为字符串对象,函数原型如下:
AnsiString __fastcall TimeToStr(System::TDateTime Time);
本文档是ccrun(老妖)根据网上资料整理而成。
Delphi中属性的使用(unit2)
interface
uses
Windows, Messages, SysUtils, Variants, Classes;
type
// 基类employee
employee=class
protected
name:string; // 姓名
empno:string; // 雇员号
accumpay:real;// 月薪
public
constructor create();// 构造函数
Procedure pay(); Virtual; // 计算月薪函数(虚拟方法)
Procedure displaystatus(); Virtual;// 显示信息函数(虚拟方法)
end;
//技术员类technician
technician=class(employee)
private
hrRate:string;//每小时酬金
hr:string; //当月工作时数
function GetHourlyRate():string;
procedure SetHourlyRate(Value: string);
function GetWorkHours():string;
procedure SetWorkHours(Value: string);
public
property MyHourlyRate: string read GetHourlyRate write SetHourlyRate;
property MyWorkHours: string read GetWorkHours write SetWorkHours;
Procedure pay(); override; //计算月薪函数(覆盖基类中同名方法)
Procedure displaystatus();override; //显示信息函数(覆盖基类中同名方法)
end;
//销售员类(派生于employee)
salesman=class(employee)
private
comm:string;//提取销售额的百分比
sale:string;//销售额
function GetCommrateRate():string;
procedure SetCommrateRate(Value: string);
function GetSales():string;
procedure SetSales(Value: string);
public
property MyCommRate: string read GetCommrateRate write SetCommrateRate;
property MySales: string read GetSales write SetSales;
Procedure pay(); override; //计算月薪函数(覆盖基类中同名方法)
Procedure displaystatus(); override; //显示信息函数(覆盖基类中同名方法)
end;
var
tech: technician;
sales: salesman;
name1,number,result1: string;
implementation
//employee类的函数实现
constructor employee.create();
begin
name:=name1; // name1为存放雇员姓名的全局变量
empno:=number; // number为存放雇员号的全局变量
accumpay:=0.0;
end;
Procedure employee.pay();
begin
end;
Procedure employee.displaystatus();
begin
end;
//技术员类的函数实现
Procedure technician.pay();
begin
accumpay:=strtofloat(GetHourlyRate)*strtofloat(GetWorkHours);
end;
Procedure technician.displaystatus();
begin
result1:=empno+'号技术员'+name+'的本月工资为 '+floattostr(accumpay);
end;
//技术员工资结算
function technician.GetHourlyRate(): string;
begin
GetHourlyRate:=hrRate;
end;
procedure technician.SetHourlyRate(Value: string);
begin
hrRate:=Value;
end;
function technician.GetWorkHours(): string;
begin
GetWorkHours:=hr;
end;
procedure technician.SetWorkHours(Value: string);
begin
hr:=value;
end;
//销售员类的函数实现
Procedure salesman.pay();
begin
accumpay:=strtofloat(GetCommrateRate)*strtofloat(GetSales);
end;
Procedure salesman.displaystatus();
begin
result1:=empno+'号销售员'+name+'的本月工资为 '+floattostr(accumpay);
end;
//销售人员工资结算
function salesman.GetCommrateRate(): string;
begin
GetCommrateRate:=comm;
end;
procedure salesman.SetCommrateRate(Value: string);
begin
comm:=value;
end;
function salesman.GetSales(): string;
begin
GetSales:=sale;
end;
procedure salesman.SetSales(Value: string);
begin
sale:=value;
end;
end.
Delphi中属性的使用(unit1)
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons;
type
TForm1 = class(TForm)
Label1: TLabel;
RadioButton1: TRadioButton;
RadioButton2: TRadioButton;
tch: TGroupBox;
sls: TGroupBox;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
jsyno: TEdit;
jsyxm: TEdit;
hours: TEdit;
Label5: TLabel;
xsyno: TEdit;
Label6: TLabel;
xsyxm: TEdit;
xsl: TEdit;
Label7: TLabel;
Label8: TLabel;
result: TEdit;
BitBtn1: TBitBtn;
BitBtn2: TBitBtn;
lblHrRate: TLabel;
edtHrRate: TEdit;
lblComm: TLabel;
edtComm: TEdit;
GroupBox3: TGroupBox;
procedure BitBtn1Click(Sender: TObject);
procedure BitBtn2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure ResetTech(flag: boolean); //设置只读属性
procedure ResetSale(flag: boolean);
procedure RadioButton1Click(Sender: TObject);
procedure RadioButton2Click(Sender: TObject); //flag标志着只读属性,true则只读,false则可修改
procedure SetEmptyTech();
procedure SetEmptySale();
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses Unit2;
{$R *.dfm}
procedure TForm1.BitBtn1Click(Sender: TObject);
var
t1: technician;
s1: salesman;
begin
if RadioButton1.Checked =true then
begin
// 计算技术员的月薪
number:=jsyno.Text ;
name1:= jsyxm.Text ;
t1:=technician.create ();
t1.MyHourlyRate := edtHrRate.Text;
t1.MyWorkHours := hours.Text;
t1.pay ();
t1.displaystatus ();
end
else begin
// 计算销售员的月薪
number:= xsyno.Text ;
name1:= xsyxm.Text ;
s1:=salesman.create ();
s1.MySales := xsl.Text;
s1.MyCommRate := edtComm.Text;
s1.pay ();
s1.displaystatus ();
end;
// 将信息显示在result文本框中
SetEmptyTech();
SetEmptySale();
result.Text :=result1;
end;
procedure TForm1.BitBtn2Click(Sender: TObject);
begin
form1.Close;
end;
procedure TForm1.ResetTech(flag: boolean); //根据radio button选择情况设置各输入组的只读情况
begin
if flag = true then
begin
jsyno.ReadOnly := true;
jsyxm.ReadOnly := true;
hours.ReadOnly := true;
edtHrRate.ReadOnly := true;
jsyno.Enabled := false;
jsyxm.Enabled := false;
hours.Enabled := false;
edtHrRate.Enabled := false;
end
else begin
jsyno.ReadOnly := false;
jsyxm.ReadOnly := false;
hours.ReadOnly := false;
edtHrRate.ReadOnly := false;
jsyno.Enabled := true;
jsyxm.Enabled := true;
hours.Enabled := true;
edtHrRate.Enabled := true;
end;
end;
procedure TForm1.ResetSale(flag: boolean);
begin
if flag = true then
begin
xsyno.ReadOnly := true;
xsyxm.ReadOnly := true;
xsl.ReadOnly := true;
edtComm.ReadOnly := true;
xsyno.Enabled := false;
xsyxm.Enabled := false;
xsl.Enabled := false;
edtComm.Enabled := false;
end
else begin
xsyno.ReadOnly := false;
xsyxm.ReadOnly := false;
xsl.ReadOnly := false;
edtComm.ReadOnly := false;
xsyno.Enabled := true;
xsyxm.Enabled := true;
xsl.Enabled := true;
edtComm.Enabled := true;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
if RadioButton1.Checked =true then//选中了技术员
begin
ResetTech(false); //将技术员的只读属性设置为false
ResetSale(true);//将销售员的只读属性设置为true
end
else begin
ResetTech(true); //将技术员的只读属性设置为true
ResetSale(false);//将销售员的只读属性设置为false
end;
end;
procedure TForm1.RadioButton1Click(Sender: TObject);
begin
ResetTech(false); //将技术员的只读属性设置为false
ResetSale(true);//将销售员的只读属性设置为true
end;
procedure TForm1.RadioButton2Click(Sender: TObject);
begin
ResetTech(true); //将技术员的只读属性设置为true
ResetSale(false);//将销售员的只读属性设置为false
end;
procedure TForm1.SetEmptySale;
begin
xsyno.Text := '';
xsyxm.Text := '';
xsl.Text := '';
edtComm.Text := '';
end;
procedure TForm1.SetEmptyTech;
begin
jsyno.Text := '';
jsyxm.Text := '';
hours.Text := '';
edtHrRate.Text := '';
end;
end.
【CSDN新闻】Websense 2009六大安全预测 黑客也玩“云安全”
“云安全”被恶意利用的频率将越来越高
以云计算技术为基础的服务如亚马逊的Web服务(AWS)、微软的Azure 以及GoGrid等为企业和用户提供了简单易用的基础运算、可按需租用的存储空间和大幅度的成本节省。但这些服务也引起了网络犯罪者和黑客们的高度关注, 他们将利用这些技术和服务来达成其恶意目的。Websense预测:2009年,云计算技术被滥用的现象将会越来越多。该技术既可被用于简单的垃圾邮件发 送,也可用来发起针对下载、数据上传统计、恶意代码监测更为高级的恶意程序攻击。
富互联网应用系统(RIAs)如Flash、Google Gears等被恶意利用的几率增大
利用基于浏览器的Web应用如基于Web的CRM系统、Google文档和其他基于Web的办公工具等越来 越多的取代了传统的桌面应用程序。运用基于浏览器的Web应用来丰富互联网使用体验的程序是富互联网应用系统(RIAs)。伴随这些应用需求的爆炸式增 长,使用RIA技术的Google Gears、Air、Flash和Silverlight构建了一个大型的Web2.0网络应用体系,而安全问题却是最后被考虑的,这就如同敞开大门让网 络犯罪分子肆意攻击。由于RIA的迅速流行,我们可以预见在2009年将会有大规模的利用RIA核心组件和用户创建的服务来发起的攻击,这些攻击将使黑客 们能够远程操控用户的电脑。
黑客们将充分利用可编写Web发动攻击
Web2.0应用是一个开放了网络应用程序接口、mashups、小工具的开放世界,允许共享、使用来自其 他网站的功能。搜索引擎Web API服务的发行以破纪录的速度发布而几乎没有什么时间来进行测试,而这种应用需要用户间达到一定的信赖水平方可。因此Websense认为在2009 年,利用Web API服务来获得信任、窃取用户的资格证书或机密信息的恶意攻击将会增加。
Web垃圾和向blog、论坛和社交性网站发送的恶意内容将会大幅度增加
那些允许用户进行内容编辑的网站越来越流行,数量也将出现飞跃增长,而这些网站很有可能导致Web垃圾和和 向blog、论坛和社交性网站发送的恶意内容的大幅度增加,这些内容包括搜索引擎麻痹、恶意引诱传播和网络欺诈等。此外,这些威胁和攻击将被数种新的 Web攻击工具所整合,使黑客们可以发现那些存在漏洞或者允许发布恶意代码的网站。越来越多的基础设施特许承包商会在其提供的功能中增加HTTP发布功 能。
黑客们将转向分布式僵尸网络控制和恶意代码托管模式
2008年Websense发现两家总部位于加利福尼亚州的托管公司McCololo和Intercage/Atrivo就由于发布僵尸网络和恶意代码被上游供应商关闭。Mcolor倒闭的当天垃圾邮件减少了50%,而Intercage/Atrivo倒闭的当天大大减轻了“风暴”僵尸网络的蔓延。Websense预测:由于这些僵尸集团仅拥有少数几家供应商来托管其C&C服务,他们会将其服务器分布到各处,同时转移至国外服务器供应商,使其更难被上游供应商、网络监管部门和执法部门所发现和关闭。
对于具有“良好声誉”网站的攻击将会持续
2009年,我们将看到有80%以上的恶意内容会以那些拥有“良好声誉”的网站为载体。会有更多的著名网站 和Alexa排名前10万的网站遭受恶意攻击。这些攻击既包括对于当地著名网站某些特定频道的攻击,也包括对流行的体育网站、新闻网的攻击以及向这些网站 中持续植入IFRAME、恶意重定向代码。
Feb 17, 2009
正则表达式(Regular Expression)测试程序【续】
* group
*/
/*
Pattern p = Pattern.compile("(\\d{3,5})([a-z]{2})");//分组,使用(),此处把数字和字母分组。一共有3组,正则表达式本身就是一组
String s = "123aa-34345bb-234cc-00";
Matcher m = p.matcher(s);
while(m.find()) {
p(m.group(1));//group,内容为正则表达式所匹配的字符串,组号即第n个左小括号,输入第几组就打印选中的那个组的内容
//m.group()则直接打印所有组内容
}
*/
/** Reluctant quantifiers限定词 勉强限定词
* ?? 一个或没有
*? 零个或更多
* +? 一个或更多
* {n}? 正好n个
* {n,}? 至少n个
* {n,m}? 至少n最多m
*/
/**Possesive quantifiers独占性限定词
* ?+ 意义同上
*+ Greedy,Reluctant,Possessive这三种quantifiers区别:它们都是匹配到了就停止
* ++ Greedy,看到{n,m}就直接吞入m个字符,再与正则表达式匹配,匹配不上则往外吐一个再匹配
* {n}+ Reluctant,看到{n,m}就吞入n个字符,再匹配,匹配不上则再吞一个字符
* {n,}+ Possesive,独占的,与Greedy类似,一次吞m个,只是不往外吐
* {n,m}+
*/
/*
Pattern p = Pattern.compile(".{3,10}+[0-9]");
String s = "aaaa5bbbb68";
Matcher m = p.matcher(s);
if(m.find())
p(m.start() + "-" + m.end());
else
p("not match!");
*/
正则表达式(Regular Expression)测试程序
import java.util.regex.Pattern;
public class RegExp {
public static void main(String[] args) {
/* p("abc".matches("..."));//可以匹配,一个点代表一个字符
p("a8729a".replaceAll("\\d", "-"));//把数字全替换为横线,\\d代表一位数字
Pattern p = Pattern.compile("[a-z]{3}");//匹配三个字符,每个都是a-z
Matcher m = p.matcher("fgh");
p(m.matches());*/
// p("fgh".matches("[a-z]{3}"));//上边三句可合并为这一句
/**Greedy quantifiers 贪婪限定符,默认
* 初步认识 . * + ?即meta characters
* . 一个字符
* ? 一个或零个
* * 零个或多个
* + 一个或多个
* [n] 正好n个
* [n,]最少n个
* [n,m]至少n个最多m个
*/
/* p("a".matches("."));
p("aaaa".matches("a*"));
p("aaaa".matches("a+"));
p("aaaa".matches("a?"));
p("".matches("a*"));//零宽度匹配 zero length matches。空串匹配
p("".matches("a?"));
p("".matches("a+"));
p("1231231425346234".matches("\\d{3,100}"));//至少3个之多100个,且都是数字
p("192.168.0.aaa".matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"));
p("192".matches("[0-2][0-9][0-9]"));//一个中括号内是匹配一个字符
*/
/**
* ^取反
* -代表范围
* |代表或者
* &&代表并且
*/
/* p("a".matches("[abc]"));
p("a".matches("[^abc]"));
p("A".matches("[a-zA-Z]"));
p("A".matches("[a-z]|[A-Z]"));
p("A".matches("[a-z[A-Z]]"));//或的另一个写法
p("R".matches("[A-Z&&[RFG]]"));//交集,取A-Z与RFG集合交集的部分,其实就是RFG
*/
/**
*\d digit
*\D non-digit
*\s a whitespace character:如[ \t\n\x0B\f\r] 不可见的符号
*\S a non-whitespace character
*\w a word character: [a-zA-Z_0-9]包括下划线
*\W a non-word character
*/
/*p(" \n\r\t".matches("\\s{4}"));
p(" ".matches("\\S"));
p("a_8".matches("\\w{3}"));
p("abc888&^%".matches("[a-z]{1,3}\\d+[&^#%]+"));
p("\\".matches("\\\\"));//匹配一个\必须用两个\\
*/
/**
* POSIX Style
* POSIX (Portable Operating System Interface based on uniX)
* 以 unix 操作系统为基础的可携带操作系统界面
* 以 unix 操作系统为基础的操作系统标准
*/
/*p("a".matches("\\p{Lower}"));//很少这样写,不推荐
*/
/**
* boundary 边界匹配
* ^位于中括号里代表取反,在中括号外代表一行的起始位置
* $结尾
* \b word boundary
* \B non-word boundary
*/
/*p("hello sir".matches("^h.*"));
p("hello sir".matches(".*ir$"));
p("hello sir".matches("^h[a-z]{1,3}o\\b.*"));
p("hellosir".matches("^h[a-z]{1,3}o\\b.*"));
/**whilte lines 空白行
* 此处空白行代表有空格的行。行开头就是换行符的不算作本例的空白行
*/
/*p(" \n".matches("^[\\s【开头是空白字符】&&[^\\n]]【并且不是换行符】*\\n$【末尾跟着换行符】"));
// " \n"即一个空白行
*/
}
public static void p(Object o) {
System.out.println(o);
}
}
Oct 25, 2008
【典藏】RAID技术
在项目开发的过程中,随着频繁的磁盘失败,通过磁盘的冗余来避免磁盘数据的丢失已经是必须的了。这样一来,该项目的研究对于将来的RAID变得至关重要。
RAID的标准
许多RAID级别在技术上是可以实现的,但是不经常使用。以下完整的列出了RAID级别:
RAID 0:条带或串联
RAID 1:镜像
RAID 0+1:条带加镜像
RAID 1+0:镜像加条带
RAID 2:加重平衡编码修正(Hamming code correction)
RAID 3:使用专用奇偶校验的条带化
RAID 4:独立的读取和写入
RAID 5:带有分布式奇偶校验的条带化
串联---RAID 0 串联可以复合多个物理磁盘成为一个单独的虚拟磁盘,并按如下方式组织:地址空间是相邻的、没有数据冗余 、块(chunks)可以认为是物理磁盘或相邻区域的磁盘空间
串联----RAID 0
推动这种技术发展的主要原因是创建一个大于物理磁盘容量的虚拟磁盘设备。通过在逻辑上结合两个或更多物理磁盘来获得更大存储空间。串联也能使你扩展一个虚拟磁盘通过给它串联另外的物理磁盘。这项技术不限制磁盘大小,既成员磁盘的容量可以不同,而且不会损失磁盘空间。
阵列管理软件就是负责将3个物理磁盘复合成一个虚拟磁盘设备,对于应用来说,它只是个邻近的存储空间。
通过使用RAID 0的串联结构可以获得以下优点:
当数据遍布在多个磁盘上时,串联可以提高随机的I/O性能。
写性能也是相同的;如果是随机读取的话,也可以提高读性能。
磁盘的全部容量都可以为用户存储数据。
局限性主要包括:
只使用串联将没有冗余,串联的卷可以通过镜像达到冗余。
串联的可靠性较低,一个磁盘数据的丢失将导致所有磁盘数据的丢失
当磁盘满,数据会通过所有成员磁盘扩展,但是,当磁盘未满时,最后的磁盘将不被使用,降低了磁盘的利用率。
条带----RAID 0
条带可以复合多个物理磁盘成为一个单独的虚拟磁盘,并按如下方式组织:地址空间是分段的 、I/O流在磁盘与磁盘间交换、没有数据冗余 、对性能的增加有意义
条带----RAID 0
推动这种技术发展的主要原因是为了提高每秒I/O(IOPS)的性能。通过并行访问设备来增强性能。在并行访问中,虚拟设备中的所有磁盘大部分时间都用来服务I/O请求,所以提高了I/O的吞吐量。
阵列管理软件就是负责把整个阵列看做一个单独的虚拟磁盘。它使用多个物理磁盘并将它们复合为一个虚拟磁盘给应用。
I/O流被划分为称为条带(stripe)的段,从一个逻辑存储单元映射到两个或更多的物理磁盘。条带单元是隔行扫描的所以每个片上的复合空间也是交替的。
在这种结构下,没有数据保护,实际上,执行条带化后,丢失一个磁盘上的数据会导致所有条带磁盘的数据丢失。条带化增强了性能,但是降低了可靠性。
通过使用RAID 0条带化结构可以获得的优势是:
对于大量的连续的I/O请求和随机的I/O请求增强了性能。条带单元的大小可以根据顺序或随机存取而进行优化。
磁盘的全部容量都可以为用户存储数据。
局限性主要包括:
没有冗余
条带化的可靠性较低,丢失一个磁盘的数据将导致所有条带磁盘的数据丢失。
条带单元大小的策略:优化一个条带化RAID 0结构条带单元大小的策略依赖于卷访问的类型。
顺序访问环境
在一个顺序的环境里,当请求涉及到条带宽度范围内的所有磁盘成员时,条带化能够提高性能。举例来说,一个条带包含4个磁盘,一个I/O请求为128Kbytes,那么,配置条带单元的大小为32Kbytes。
随机访问环境
在一个随机环境里,条带化可以提高性能。随机访问受控于磁盘的搜索和寻道时间,随机的I/O要比顺序的I/O小很多,通常是从2Kbytes到8Kbytes。
当条带单元的大小配置为比请求的大小大很多的时候,性能得到优化。举例来说,对于一个8Kbytes的请求,至少配置条带单元的大小为16Kbytes。
镜像----RAID 1
镜像提供了数据的最大可用性,并有以下功能:所有数据的完全冗余拷贝、提高了读性能、透明化了磁盘失败
这项技术发展的主要原因是它能够提供高级别的可用性及可靠性。
镜像(RAID 1)通过数据在独立spindes上的多次记录来提供冗余。对于应用来讲,镜像磁盘显示为一个虚拟磁盘。一旦一个物理磁盘失败,失败磁盘上的镜像也就无效了,但系统继续通过未受影响的磁盘进行操作。
通过使用RAID 1的镜像结构可以获得如下的优点:
在一个或多个磁盘上可以获得一个完全冗余的数据拷贝
如果一个阵列中的镜像连接到不同的接口板,就可以达到非常高的数据可靠性级别。
所有驱动器都可以用来读取,提高了性能
在一个多用户或多任务环境下,当多个磁盘成员要获得满意的读效果时,镜像提高了读性能。相反,如果只有单一的卷读取进程,则将不能提高性能。
你可以设置3路镜像,但性能会受很大影响。在3路镜像中,写性能最大为44%。
局限性:
镜像需要使用两倍的磁盘,本质上占用了2倍的存储空间
镜像降低了大约15%的写性能,这实际上小于典型的RAID 5的写损失。(RAID 5的写性能最大到70%)。
镜像—条带(RAID 0+1)
镜像—条带(RAID 0+1) 复合条带化的镜像可以提供如下功能:极大的提高了性能、完全的数据冗余、透明化了磁盘失败。
使用复合条带和镜像的主要原因是获得RAID 0的性能和RAID 1的可用性。
安装它需要较高的花销,但许多用户认为它是值得投资的。
两个磁盘可以先进行条带化,然后进行镜像。可以获得镜像的高可靠性。因为同时使用了条带技术,它的性能要比单独使用镜像的性能要好得多。
它的一个优点就是有利于一个磁盘上分布数据的访问(提高了I/O per second),并增强了数据冗余。
局限性:RAID 0+1是高花销的镜像系统,需要2倍的独立磁盘空间。
条带----镜像(RAID 1+0)
条带----镜像(RAID 1+0) 复合镜像化的条带可以提供以下功能:极大的提高了性能 、完全的数据冗余、透明化了磁盘失败、比RAID 0+1提供了更高的磁盘失败容许
RAID 1+0拥有所有RAID 0+1性能及可靠性的优点,但它允许了更高级别的磁盘失败而不丢失数据。
RAID 1+0通常在磁盘失败后拥有更快的恢复时间,因为它只需要代替单独的条带来恢复而不是整个镜像。
RAID 1+0推荐在大型卷中使用,因为,失败的恢复时间是个关键。
RAID 1+0的概念根本不同于RAID 0+1,在RAID 1+0的配置中,每个条带是单独镜像的。
优点:
因为每个条带被独立镜像,可以容许大量的磁盘失败而无需禁止卷。
这个配置的性能优于RAID 0+1。
局限性:
RAID 1+0是高花销的镜像系统,需要2倍的独立磁盘空间。
带有分布式奇偶校验的条带化----RAID 5
一个RAID 5的卷结构可以进行镜像因为:
条带化结构拥有较好的读性能,可靠性得到提高,而且没有镜像花费高
附加功能包括:
对于单个的磁盘来说,单独访问是有效的。
数据和奇偶较验值都进行了条带化。
全部的随机的I/O性能依赖于写的百分率。如果写超过20%,那么就可以考虑选择RAID 0+1了。
优点
奇偶校验保护了单一磁盘的失败
RAID 5只需要一个附加的磁盘,其他的都可以用来存储数据。
局限性
在Veritas VM中,最少需要3块磁盘来完成RAID 5
RAID 5不可以被镜像,通过奇偶校验信息来提供冗余
写增强(Write-intersive)性能是很差劲的
如果写超过了20%,那么就可以考虑选择RAID 0+1了
在一个写增强的环境里,如果一个磁盘失败,将可能对性能产生严重的影响
性能因素
一个磁盘失败,数据仍然可以访问,但性能会受影响
从一个继续存在的磁盘读取数据 ----- 没有改变
从一个失败的磁盘读取数据 ----在条带中读取相对应的条带单元并于原始数据异或后(XOR)相连接
写一个继续存在的磁盘 ---- 如果失败的磁盘保存有奇偶校验的数据,写数据通常不计算奇偶校验。如果失败的磁盘保存有数据,那么将请求一个:读—修改—写的顺序
写入一个失败磁盘 ---- 继续存在的所有磁盘上的所有数据将与新数据使用异或(XOR)后进行连接,结果将写入到奇偶校验驱动器
从一个单一磁盘失败中恢复 ---- 在条带中余下的条带单元的数据被读取,使用异或(XOR)后进行连接,将结果写入替代的磁盘中,假定在配置中有一个有效的交换(spare)空间。
复合RAID模式
单一的RAID模式肯定不能使用当前各种应用的需要,为了得到更多的性能,人们将各种RAID模式联合起来使用。那么将其中两种模式用在会有什么好处呢?可以得到功能更多、性能更好的RAID模式。一般符合RAID模式需要硬件控制器。因为对于如此复杂的应用软件RAID控制器显然是不现实的。RAID 0在各个单一模式中是速度最快的,所以在符合RAID模式中它也是最常用的。最长常用的符合RAID模式是0+1和1+0。 0+1和1+0是有细微区别的,不过有的公司对这个名词是不加以区分的——其实它们的主要区别在于容错能力。这两种复合RAID模式都至少需要4块硬盘。
首先让我们看看RAID 0+1模式。复合使用RAID 0是为了提高磁盘性能,使用RAID 1为了提高容错性能。假设你有8块硬盘,将它们4个一组分成两个阵列——我将其称为基阵列,每个基阵列用RAID 0模式连接。然后你就有了两个延展模式的基阵列。然后你将这两个基阵列用RAID 1模式连接——也就是让其中一个基阵列作为另一个的镜像。如果一个延展模式的基阵列中的硬盘出现故障了,那么这个延展阵列也将全部瘫痪。不过另一个延展阵列仍然可以维持系统工作,并且可以利用其来恢复数据。
RAID 1+0是先组合RAID 1阵列,然后把它们组成RAID 0模式。仍然使用刚才的例子:将8块硬盘分成4组,每组2块硬盘组成一个基阵列,然后将每个基阵列用RAID 1模式连接,也就是让其中一个硬盘作为另一个的镜像。然后把这4个RAID 1模式的基阵列用RAID 0模式连接。这个模式比RAID 0+1有更好的容错能力。任意的一个硬盘驱动器出现故障,因为有镜像驱动器的存在,所以整个阵列将能继续正常工作。
从理论上讲RAID 1+0模式即使每个基阵列都坏一块硬盘,系统仍然能正常工作,只有当第五块出现故障的时候才有出现不可挽回的损失。而RAID 0+1只要两个基阵列都有一块硬盘故障,那么就无法挽回了。:~(
目前流行的RAID 0+1和1+0模式使用了相对简单的复合技术就提高了性能和增强了数据冗余性。随着硬盘驱动器价格的不断下降,4块硬盘的价格也能为个人用户接受了。不过,如果你需要镜像功能的话,你所能使用的硬盘空间将只有两块硬盘空间大小——为了数据的稳定性,你不得不以牺牲50%的空间为代价。企业级应用以及服务器一般会不惜牺牲磁盘空间换取更高的容错性。另外还有一些其它的复合RAID模式比如:RAID 0+3、3+0、0+5、5+0、1+5、5+1。这些模式的实现往往需要昂贵的硬件支持。
RAID模式的选择
现在你已经熟悉了不同的RAID组合模式及其配置,还有什么困扰你?对了,就是如果选择适合的RAID模式。一般用RAID无非就是为了这几个目的:数据冗余、容错性、提高容量、增进性能。数据冗余可以用于保护关键的数据不丢失,一般用于大型公司、企业,当然如果你有钱,也可以为了保护你的以GB为计数单位MP3使用它。容错能力可以带给存储系统更好的稳定。
没RAID通过连接多个硬盘可以提供给计算机系统更大的存储空间。当然根据选择RAID模式的不同,你得到的最大存储空间是不同的,比如如果采用镜像技术,你所需要的空间是要存储数据的两倍。
更多的,特别是个人用户使用RAID技术的目的是为了提高磁盘性能。当然你选择的RAID模式不同,性能的提高也会各不相同。
硬件RAID的实现
首先看一下硬件实现RAID模式的方式:一般使用SCSI或者IDE/ATA作为硬盘同系统的接口。SCSI一般应用于高端服务器上,虽然性能优良、功能强大可是对于普通用户来讲价格太高。IDE/ATA RAID控制器成本低廉,(不少主板已经集成)虽然不能太复杂的应用,性能也比不上SCSI设备,但是对于家用市场来说已经能够满足要求了,所以渐渐成了RAID的主流。
硬件RAID实现分为两种:一种是内置(或集成)RAID控制器,一种是外置RAID控制器。内置RAID控制器通常是常用的卡件的形式插接在计算机主板上,集成RAID控制器则是由主板厂商直接把控制芯片集成在主板上,近来高端主板集成RAID控制器几乎成了标准配置。根据RAID控制器以及连接模式不同,RAID控制器都配有不同数量的缓存,当然缓存越多,控制器的性能就越好了,价格也就越贵了。
外部RAID控制器包括从控制器到硬盘等一套设备。在高端服务器上,你常常可以看到由独立的机箱容纳RAID控制器和硬盘。控制器包括了所有的RAID功能——在系统中看到的只是所有的逻辑驱动器。外部RAID控制器相比内置RAID控制器结构更加复杂,缓存容量更大。这是因为它常常要连接并且组织大量的硬盘来组成复杂的复合RAID来工作。它一般使用SCSI接口——可以进行热插拔,来更换有故障的硬盘,避免中置计算机系统停机所带来的损失。
很明显内置控制器外置的便宜的多。不过外置控制器功能强大、可扩展性强,但是价格不是个人所能承受的。虽然IDE/ATA RAID控制器日渐增多,可是SCSI因其特殊的性能仍然是高端计算机系统的首选。当然你可以选择软件RAID——这是一个相当经济的选择,但是需要占用CPU时间,所以RAID模式越复杂,对于计算机系统影响就越大。所以如果你的预算足够还是选择硬件RAID模式吧
Sep 17, 2008
【典藏】C++ Type traits
by John Maddock and Steve Cleary
泛型编程编出来的代码,适用於任何「吻合某种条件限制」的资料型别。这已成为撰写可复用代码时的一个重要选择。然而,总有一些时候,泛型不够好 ─ 有时候是因为不同的型别差距过大,难以产生一致的泛化实作版本。这个时候 traits 技术就变得相当重要。这种技术可以将那些需要被纳入考量的型别性质以一种 type by type 的原则,封装於一个 traits class 内,於是可以将「由於型别之间的差异,必须撰写出来以备用」的代码体积降至最低,并使泛用代码的体积提升到最高。
考虑一个例子:当我们处理字元字串(character strings)时,常见的一个操作行为就是决定「以 null 为结束符号」的字串的长度。很明显我们不可能写出一份泛型代码取代众所周知原本就存在的极有效率的解法:是的,C 标准函式 strlen 和 wcslen 通常是以组合语言完成,如果再加上适量的硬体支援,就能够比 C++ 泛型版本有明显的速度优势。C++ 标准程式库的作者了解这一点,所以他们抽取出 char 和 wchar_t 的性质,置於 class char_traits 内。於是,泛型代码一旦处理字元字串,便可以简单地使用 char_traits<>::length 来决定一个「以 null 为结束符号」的字串的长度,并且很安心地确知 char_traits 的特化版本将采用最适当的方法来完成任务。
Type traits(型别特性)
Class char_traits 是「把一系列与型别相关的性质包裹於单一 class 之内」的典型例子,那正是 Nathan Myers 所谓的 baggage class [叁考资料1]。在 Boost type-traits library 中,我们 [叁考资料2] 完成了一组非常特别的 traits classes,其中每一个 classes 都封装了 C++ 型别系统中的一个(仅仅一个)特性。所谓特性(trait)指的是,举个例子,某型别是否为一个 pointer,或是一个 reference?某型别是否拥有一个 trivial constructor,或是拥有一个 const 修饰词? 这些 type-traits classes 共同享有一致性的设计:每一个 class 都有一个 member value,那是一个编译期常数,如果某型别拥有某种特性,此一常数的值就是 true,否则就是 false。稍後我将为你展示,这些 classes 可以被使用於泛型编程之中,用来决定某个型别的特性,并导入对应的最佳化措施。
Boost type-traits library 也内含一组 classes,可以针对某个型别执行专属的特定的转换。例如它们可以从某个型别身上移除一个上层的 const 或 volatile。每一个用来执行转换的 class 都定义有一个 typedef-member type,那便是转换结果。所有这些 type-traits classes 都被定义於 namespace boost 之中。为求简化,本文的范例代码大多省略命名空间的设定。
实作(Implementation)
要在这里显示 type-traits library 的所有实作内容,是不可能的,那真是太多太多了。如果你有这个需求,请看 Boost library 的源码。大部份实作方法都是重复的,所以这里我只给你一种风貌,为你示范这些 classes 如何实作出来。让我们从程式库中最简单的一个 class 开始。is_void
template
struct is_void
{ static const bool value = false; };
template <>
struct is_void
{ static const bool value = true; };
在这里,我们定义了 template class is_void 的一个主版本,并针对「T 是 void」的情况提供了一个全特化( full-specialisation)版。虽然 template class 的全特化是一项重要技术,但有时候我们需要的解决方案介於「完全泛化」和「完全特化」之间。这正是标准委员会之所以定义出偏特化(partial template-class specialisation)的原因。举个例子,让我们考虑 class boost::is_pointer
template
struct is_pointer
{ static const bool value = false; };
template
struct is_pointer
{ static const bool value = true; };
偏特化的语法带了点不可思议的味道,而且一谈到它很容易就耗掉一整篇文章。就像全特化的情形一样,为了针对某个 class 写出一个偏特化版本,你首先必须宣告 template 主版本。偏特化版本在 class 名称之後多出一个 <┅> ,其中内含偏特化叁数;这些叁数定义出「将被系结於偏特化版」的某些型别。究竟什麽叁数会(或说能够)出现於偏特化版本之中,规则颇为曲折,以下是一个简略的规则。如果你能够以此型式合法写出两个多载化函式:
void foo(T);
void foo(U);
那麽你就能够以此型式写出一个偏特化版本:
template
class c{ /*details*/ };
template
class c{ /*details*/ };
这个简则并非绝对成立,但它非常简单,足以让你牢牢记住并足够接近精确的规则。
至於比较复杂的偏特化例子,让我们考虑 class remove_bounds
template
struct remove_bounds
{ typedef T type; };
template
struct remove_bounds
{ typedef T type; };
remove_bounds 的目的是:想像一个泛型演算法,接受一个 array 型别做为 template 叁数,remove_bounds 会提供一个方法,让你有办法得知底部(underlying)的 array 型别。例如,remove_bounds
copy 最佳化
现在我要举一个例子,说明我们可以如何运用 type traits classes。考虑标准程式库所提供的 copy 演算法:
template
Iter2 copy(Iter1 first, Iter1 last, Iter2 out);
很明显,写一个泛型版本的 copy 绝无问题,它可以处理任何型别的迭代器 Iter1和 Iter2。然而在某种情况下,copy 动作可以透过 memcpy 完成。为了能够以 memcpy 完成 copy,以下条件必须成立:
两个迭代器 Iter1 和 Iter2 的型别都必须是指标。
Iter1 和 Iter2 都必须指向相同的型别 - 但允许有不同的 const 和volatile 修饰词。
Iter1 所指的型别必须有一个 trivial assignment operator。
所谓 trivial assignment operator,我的意思是这个型别如果不是一个纯量型别(scalar types)[叁考资料3],就是:
这个型别没有使用者自定的 assignment operator。
这个型别没有任何 data members 采用 reference 型式。
所有的 base classes,以及所有的 data member objects 都有 trivial assignment operators。
如果上述所有条件都获得满足,那麽这个型别就可以被 memcpy 直接拷贝,而不使用一个由编译器产生的 assignment operator。type-traits library 提供了一个 class has_trivial_assign,使得当 T 有一个 trivial assignment operator 时,has_trivial_assign
列表一显示一个最佳化(使用 memcpy)的 copy 代码。代码之中首先定义一个 template class copier,接受唯一一个 template 叁数 Boolean,然後是一个 static template member function do_copy,执行 copy 的泛型版本(也就是比较慢但比较安全的版本)。接下来是一个 copier
为了完成整份实作代码,现在我们需要一个 copy 版本;如果可以安全使用 memcpy,就让它呼叫 copier
首先,两个迭代器必须指向相同型别 - 验证方法是透过 type-traits class is_same。
其次,两个迭代器都必须是真正的指标 - 验证方法是透过先前描述过的 class is_pointer。
最後,被迭代器所指的型别必须有一个 trivial assignment operator - 验证方法是透过 has_trivial_assign。
最後,我们可以使用 can_opt 的值做为 template 引数,传给 copier。这里所呈现的 copy 版本会根据它所获得的叁数而调整,如果有可能使用 memcpy,它就会那麽做,否则就使用一个泛型的 copy。
值得如此吗?
许多文章都会引用这句话:「贸然实施最佳化,是各种伤害的根源」("premature optimization is the root of all evil") [叁考资料4]。所以你一定会问这样的问题:我们的最佳化是否太过贸然?是否太过唐突?为了透视这一点,我把我们的 copy 版本拿来和一个传统的泛型版本做比较 [叁考资料5],结果显示於表一。
很明显,最佳化与否,造成两个截然不同的结果。但我也要持平地说,时间的量测并不含括「快取装置误击效应」(cache miss effects),因此这份结果并未能在两个演算法之间展现精确的比较。然而,或许我们可以加上一些警告,放到「贸然最佳化」的规则里头:
如果你一开始就使用正确的演算法,那麽最佳化就不再有必要。某些情况下,memcpy 是正确的演算法。
如果某个组件即将在许多地方被许多人使用,那麽最佳化是值得的 - 即使对少数使用者而言,最佳化可能是小题大作。
表一:以 copy
版本
型别 T
时间(微秒)
最佳化的 copy char 0.99
传统的 copy char 8.07
最佳化的 copy int 2.52
传统的 copy int 8.02
Pair of References
「copy 行为最佳化」这个实例告诉我们,type traits 如何被用来在编译时期执行最佳化策略。type traits 的另一个重要用途是允许某些「除非运用极端的偏特化,否则无法通过编译」的代码得以被顺利编译。只要将偏特化行为授权(delegating)给type traits classes,便有可能做到。关於这种用法,我举的例子是一个可以持有 references 的 pair [叁考资料6]。
首先让我们检验 "std::pair" 的定义,为了简化,我略去其中的 comparision operators, default constructor, 和 template copy constructor:
template
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair(const T1 & nfirst, const T2 & nsecond)
:first(nfirst), second(nsecond) { }
};
此刻这个 "pair" 无法持有 references,因为如此一来其 constructor 将被迫接受一个 reference to reference,而这种语法目前并不存在 [叁考资料7]。让我们想想,为了允许 "pair" 得以持有 non-reference 型别、references 型别、constant references 型别,constructor 的叁数必须是什麽样子:
"T1" 的型别 constructor 的叁数型别
T
const T &
T &
T &
const T &
const T &
一个和 type traits classes 类似的技术,允许我们建构单一的对应关系,使我们得以根据 contained class 的型别决定叁数型别。type traits classes 提供了一个 "add_reference" 转换,可以为自身型别加上一个 reference,除非它本身已经是一个 reference。
"T1" 的型别 "const T1" 的型别 "add_reference
T
const T
const T &
T &
T & [注8]
T &
const T &
const T &
const T &
这使我们得以建立一个 template 主版本,定义一个可内含 non-reference 型别、 reference 型别、constant reference 型别的 "pair" :
template
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair(boost::add_reference
boost::add_reference
:first(nfirst), second(nsecond) { }
};
为它回添标准的 comparision operators, default constructor 和 template copy constructor 之後(它们都和原先版本相同),你就有了一个可以内含 reference 型别的 std::pair。
当然我们也可以使用偏特化技巧完成同样的扩充,但果真如此,我们需要三个 "pair" 偏特化版本和一个主版本。Type traits 允许我们仅仅定义一个主版本,就可以自动而神奇地将自己调整为任何偏特化版,取代一一偏特化的所谓「暴力法」。以此方式使用 type traits,可允许程式员将偏特化授权(delegate)给 type traits classes,使得代码比较容易维护,也比较容易被理解。
结论
希望这篇文章能够给你一些想法,让你大略知道 type-traits 是什麽。boost 说明文件中有更完整的 classes 列表,以及更进一步的使用范例。Templates 使 C++ 有能力实现泛型编程所带来的复用性;这篇文章还告诉你,templates 可以和 generic 一样地美好。这都有赖 type traits 带来的价值。
致谢
感谢 Beman Dawes 和 Howard Hinnant 对本文所提的意见。
叁考资料
Nathan C. Myers, C++ Report, June 1995.
这个 type traits library 的完成,要感谢 Steve Cleary, Beman Dawes, Howard Hinnant 和 John Maddock。你可以在 www.boost.org 找到它。
所谓纯量型别(scalar type)就是算术型别(例如内建的整数或浮点数)、列举型别(enumeration)、指标、函式指标、或以上任何型别再加上 const- 或 volatile- 修饰词。
此句引自 Donald Knuth, ACM Computing Surveys, December 1974, pg 268.
这一份测试代码是 boost utility library 的一部份(见 algo_opt_examples.cpp),以 gcc 2.95 编译完成,所有最佳化选项都打开。我的测试结果是在 400MHz Pentium II + Microsoft Windows 98 上获得。
John Maddock 和 Howard Hinnant 已经送出一个 "compressed_pair" library 给 Boost,其中使用的一个技术,和此处所描述的技术类似,也是用来持有 references。他们的 pair 也使用 type traits 来决定是否有任何型别是空的,并且采用 "derive" 而非 "contain" 的方式,用以保存空间 -- 这正是 "compressed" 的命名由来。
这其实是 C++ 核心语言工作小组的一个议题,由 Bjarne Stroustrup 提出。暂时的解决办法是,允许 "a reference to a reference to T" 的意义等同於 "a reference to T",但是只能存在於 template 具现实体中,或是存在於一个「具备多个 const-volatile 修饰词」的 method 中。
为什麽这里不该有 const 修饰词呢?对此感到惊讶的人,我要提醒你,请记住, references 永远是个隐晦常数(举个例子,你不能够重新对一个 reference 赋值)。同时也请你记住,"const T &" 是完全不同的东西。因为这些理由,template 型别引数如果本身是个 references 的话,其「const-volatile 修饰词」都被忽略。
列表一
namespace detail{
template
struct copier
{
template
static I2 do_copy(I1 first,
I1 last, I2 out);
};
template
template
I2 copier::do_copy(I1 first,
I1 last,
I2 out)
{
while(first != last)
{
*out = *first;
++out;
++first;
}
return out;
}
template <>
struct copier
{
template
static I2* do_copy(I1* first, I1* last, I2* out)
{
memcpy(out, first, (last-first)*sizeof(I2));
return out+(last-first); // 译注:因为是 RandomAccessIterator
}
};
}
template
inline I2 copy(I1 first, I1 last, I2 out)
{
typedef typename
boost::remove_cv<
typename std::iterator_traits
::value_type>::type v1_t;
typedef typename
boost::remove_cv<
typename std::iterator_traits
::value_type>::type v2_t;
enum{ can_opt =
boost::is_same
&& boost::is_pointer
&& boost::is_pointer
&& boost::has_trivial_assign
};
return detail::copier
}
Sep 12, 2008
我的实习之旅(暑期实习论文)
2008年的暑假是我过得最充实的一个暑假,转眼间将要大四,此时的我在经过一段时间的考虑后选择了毕业就业这条道路。为了使自己尽快的适应以后的工作环境,我决定慎重对待自己的暑期实习。于是,我来到了成都盛景栋力信息技术有限公司进行我为期两个月的暑期实习计划。
我一直相信,放弃了考研,转而打算从事计算机程序类工作的我想在三年内比我的研究生同学们学得更多,变得更强,我只有几种选择:第一是进一个大型公司,如百度,华为等,在强手如云的环境中迎难而上,慢慢的充实自己历练自己;第二种是进入一家中小型的公司,这里有比大型公司更多的机会留给你,这里你能够把自己的才能尽可能的发挥,同时还有机会学到一些管理方面的技巧;最后就是自己创业,可是我和一些同学讨论过,如果对某一行业理解不够深入,贸然创业很容易失败的,虽然有很多成功的案例,可是我们没有看到那些没有被报道出来的失败案例的。
是的,很长一段时间这三条以后我可以走的道路都一直在我脑海中萦绕,我很像找到答案,究竟什么是最适合我的,究竟我想要什么,究竟我该怎么做。如何得到答案,当然只有到实践中去。这次暑期实习,针对自己以后想要从事的行业,我找到了成都盛景栋力信息技术有限公司公司的负责人,经过一段时间的商谈之后,我很幸运的得到了实习的机会。这是一家规模不大的公司,但是员工很团结踏实,其中不乏高手,是一个比较好的工作环境。在实习的过程中我也不断的在思考,从而有了新的感悟。现在,就让我细细道来。
学习不仅仅是咬文嚼字的过程,至少对于编程一类的工作,实践往往更显得重要。我首先要声明以下言论不是攻击现有的教学制度或教材,纯属个人感受。我自己学习技术已经有两年多了,学习的过程中给我的最大体会就是你的学习如果只是根据教材上的例子做那你一般不会出错,因为谁都不会把错误教给别人,教材上的例子都是经过一次次调试确定可以通过没有错误后才放上去的,最多只是一些印刷错误。同时,由于教材教的都是一些基本的知识而很少深入(至少对于大部分书籍来讲是这样),教材注重人人都可以理解,太多人咀嚼过的东西就显得淡而无味;另外,教材上的知识一般是注重教学的,也就是说,大部分的实际意义并不大,也许很多年前老掉牙的问题教材上照样讨论得津津有味。所以,作为一个学生相当忌讳的一件事是学完教材上的东西,就自以为学富五车,才高八斗了,而实际扔给你一个题目让你实现一个功能,你就会发现原来很多问题是你不曾发现的。是的,真正到了要你完全自己动手的时候面对突如其来的许多问题,你很快就会觉得自己很无助,进而感到有那么一点绝望,有那么一点受挫,如此这般之后,你会想重新审视一下自己的所学所想。现在社会上有一些声音是批评我们大学生的,说我们是商品教育的产物,实际动手能力差得可以。刚开始听到这种言论的时候我感到及其不舒服,但是又想不到反驳的理由,总不能把责任推卸给学校吧。然而做了一些实际的项目之后我发觉其实那些觉得大学生大而无用的人其言论固然偏激,可是不无道理。于是我学会了谦卑,老老实实的检讨了自己的能力不足这一问题,同时在项目中慢慢补足,这样的过程对大学生是有益的。我最后还是要声明以上言论不是攻击现有的教学制度或教材,纯属个人感受。
这里我想引入一句话:“工作中所学的东西和在学校里学的是不一样的”——不只一位前辈这样告诉我。在实习过程中,我也确实发现了这一点。
在实际做项目的时候,我经常的被一些大大小小的问题所困扰,而这些问题的出现根本是毫无征兆。比如用VC6.0做一个登陆对话框,想来挺简单,一个对话框,几个控件,然后写些代码就行了,其实不然。当然,这其中有一部分原因也许是因为我用的开发平台比较旧,如果是用VS.NET做的话,也许不会存在这么多问题。回到登陆对话框这里,我要做的那个登陆表单上面要求填入用户帐号,姓名,服务器IP,然后提供登陆和注册两个功能,点击注册则销毁当前对话框跳出一个注册对话框,注册信息填写无误后,直接连上服务器并进入主界面。仅仅这个功能我就被卡住好几次。比如,要在两个对话框间切换,而我要在哪里设置一个可以直接结束整个软件系统的进程的关闭功能(考虑到用户可能没有账号,想注册一个,后来又取消这个念头想关闭这个软件,而关闭的功能分成关闭整个程序进程;关闭登陆对话框进入注册对话框;关闭注册对话框返回登陆对话框;关闭登陆或注册对话框进入主程序,而关闭的函数很有限因此要仔细考虑用法);如何验证两个对话框的有效性(用户账号密码不能为空,是否为数据库中存储的已注册的账号,是否可以通过该IP连上服务器);如何保证用户登陆或注册成功后进入主界面(主要是对两个对话框的操作和验证问题),如何在MDI的环境下实现不同视之间的切换等等问题。仔细考虑后就是会有这么多的疑问,编写期间当然也会有疑问跳出来,总之,问题多多。
是的,这就是实际的项目,面向真正的客户的项目。这时的需求是客户提出的,也许会出现千奇百怪的要求,反正绝对不会像书本上教的那样,书本上教的全是作者假设出来的客户,假设出来的需求;而实际情况中,即要满足客户的需求,又要考虑到用户实际操作中的各种可能性(因为客户一般不是专门的程序员,他们最希望的他们付钱买的软件是完全傻瓜化的操作,越简单功能越丰富越好),所以实际做项目时,要注意很多问题。下面我就把先我亲身经历的一些开发过程写出来。
首先,设计系统时要全面的分析。需求说明书是开发人员和客户联系的第一座也是重要的一座桥梁。在接到项目以后一般会有一次会议,开发人员代表和客户代表一起坐下来谈项目,客户代表首先提出自己的需求以及对软件的一些设想;开发人员认真听取需求并从专业的角度来向客户阐述开发的流程和工作,并给出一定的意见。然后开发人员代表根据讨论的结果起草一份用户需求说明书,提交给客户代表,审阅通过后,用户需求说明书就成为后期软件开发的重要依据。用户需求说明书不仅仅是开发人员开发过程中的参考文档,也是项目验收期间用户的参考文档,同时,如果对于开发结果不满而产生一些纠纷,用户需求说明书也是一个可以参考的依据。软件工程中各种文档都已经形成标准,可以从网上下载到很多标准文档格式进行参考。
其次,由于我参与开发的是一个相对较小的项目,所以没有去遵循一些权威书籍诸如《代码大全 2》中所提出的大型软件工程项目所必须遵循的一些流程,而是简化了一些步骤。通过了需求说明书后,项目的负责人,兼首席软件架构师就开始写软件开发流程说明书,软件架构分析说明书。在其中会给出软件的功能需求,模块划分,开发周期和工作制度等等内容。团队里的所有程序员都必须认真阅读需求说明书和开发流程说明书,软件架构分析说明书。接下来一般会有一次团队内部的会议,会议内容再清楚不过,就是分配任务,把划分好的模块分配到具体的一个开发人员。当然,如果对这些说明文档里的内容有疑问,也可以当面提出来,团队内部讨论后决定是否更正部分内容。会议结束后,每个开发人员要干什么活一般都清楚了,接下来就是自行安排时间,能赶得上进度就好了。
再次,就是编写阶段了。相信很多人现在还认为编程就是写代码吧,刚开始学的时候我也这么认为,学了一阵子之后我就发现不是这么简单。编程不仅包括写代码,那些前期的准备工作,与客户的讨论和会议,编写过程中的纠正,模块间的联调,程序员间的交流等,这些都是编程。所以这个编写阶段,不是每个分配到任务的人把自己关进小黑屋子,对着电脑狂敲键盘的过程,这样写的代码根本就没用,很孤立。真正的编写是大家在一间窗明几净的工作室里完成的,所有人在一起工作,做各自的工作。实际编写过程中经常会有一些问题出现,这时就会有两三个人聚在一起讨论;或者是模块接口方面出现了一些问题,模块的编写人员在一起讨论解决方案;或者是某一功能模块不明确或者划分不当,向小头目反应后可能会有另外一个小会来解决这个问题。使得,真正的编程是一个很快乐的过程,很多人一起编写,一起讨论,互相学习,共同进步,真的是这样的,并不是套话而已。而必须要指出的是,编写阶段一般都不会总是和当初说明文档里面的设想相一致,可能的原因很多(架构师的经验问题,考虑的不全面等等),但是偏差也不会很大(否则那些说明文档就没有存在的意义了)。在实际的编写过程中你往往会对自己所用的某一门语言,某一个IDE有了新的体会,这是你一味的学习书本上的知识所没有的体会。
编写好大部分代码后其实调试工作就已经要开展了。调试是一个很折磨人的过程,对于新手来说,更是“感觉像在地狱走了一回”。是的,调试就是DEBUG,BUG就是软件中的不稳定因素,也可以概称为漏洞。BUG在刚写好的代码中肯定存在的(这间接证明了一个观点,那就是人肯定都会犯错误的),而BUG越早被发现越好。软件工程学中有一个观点,那就是问题在系统中存在得越久最后解决问题的成本也就越高,这是众人总结出来的观点,我认为是正确的。设想一下,一个BUG是在刚开始的几十行代码中容易被找到,还是在后来的几千行代码中容易被找到呢?答案不言而喻。而且,代码越多,就越可能出现一些编译器上的错误,还有其他的一些例如内存泄漏,访问越界等错误。这些错误如果叠加到一起,相信我,你肯定会觉得很郁闷的,郁闷到不能再郁闷,到后期BUG的出现往往令你措手不及,反复的调试过程极大的考验了你的耐性。如果能在联合调试前就尽量保证代码的质量,那就可以给后期的DEBUG过程减轻很多负担。这是让我感受很深的一个教训。我所经历的调试期虽然碰到了问题,可是问题都能很快被找出来,主要归功于各个开发人员的编程水平都经过了考验和平时多交流多探讨及时解决问题这些良好的习惯。这又给我一个启示,作为一名开发人员,提高自己的专业水平同时与他人共同进步,养成良好的开发习惯,这样对人对己都是有利无害的。
程序员的调试期结束后,就把编译后的软件交给客户试用调试。这就类似我们经常听到的一些字眼,“内测”和“公测”,还有软件发布中的“α版”和“β版”等,意思差不多,就是要说明一个道理,那就是软件是要经过一系列的测试的。如果程序员的调试做得好的话,用户一般是碰不到什么错误的。用户觉得满意了,项目验收就结束了,接下来就是一些结算的问题了,客户付钱,程序员收钱,这样,代码就换成银子了。这是以后我的工作,写代码的一个目的——维持生计。很实际的一件事,在以后我们工作了的时候都会这样。
这里我想对用户测试期说一些话。程序员把写好的软件程序交给客户使用,是一定会附上一份“用户手册”的。为什么呢,因为用户不是程序员,而且他们也没有参与编写这款软件,软件中的功能,菜单的级别,内部机制,快捷键的使用,程序员自然清楚,可是用户还不清楚啊。如果不把软件的使用方法告诉一个外行人,你认为他会用得好好的吗?肯定不会,他肯定要摸索上好一阵子才渐渐熟悉。日常生活中我们经常可以看到一些说明书,用户手册就是软件的说明书。买家电什么的都有一本用户说明书,买双耐克鞋,说明书就那么长了,何况一款软件。用户手册要怎么写呢?这就是说明文了,我们在日常生活中经常用到的文体,只要遵循一定的格式,写的时候困难不在于格式而在于你自己。我当初写用户手册时遭受了很大的困扰,总是有很多地方我认为没有必要写出来,是人都知道的事;很多地方我认为不需要特别标注,傻瓜才会犯错呢;很多地方我认为一笔带过,小处无关痛痒……现在想来那是纯粹的跟着感觉走,是一种危险的举动。我将我写的第一份用户手册交给小头目审阅,马上被打回来了,曰:“写给谁看呢,具体点”我这才把原先自以为是的地方给补上了。说明文就是这样,白菜内容,该具体的地方具体,不该具体的地方也不要简单得漏掉什么。
我感觉实际做项目有很多好处,首先实际的项目带给你的是更多的责任感。责任感可能来源于很多地方。因为你在做一个实际项目,你要对付你酬劳的客户负责。另外,你要对自己负责,不能再因为自己是学生而轻易的原谅自己。我想这样对个人而言是很好的,很利于成长;其次,实际做项目可以碰到很多原来没有机会碰到的问题,这些问题解决了之后你会对自己有了一种新的想法。这些问题也许会让你很受挫,但是正视这些问题,勇敢的解决它们,你会发现自己的技术有了新的提高;再次,实际做了些项目之后你会对某一行业的一些内部运行机制有了更清楚的了解,没有什么比亲身经历更让人难忘的了;最后,实际做了项目之后,很自然的你就会对这个社会有了新的理解,这和你在大学这个象牙塔里所看到的社会将会有所不同。而且随着你理解的深入,你会发现他们越来越不同。最终你会完成从一个大学生到一个职场人员的转变。
然而在实习中我也发现,某些人在工作之后就失去了学习的热情,变得很实际而没有太多的上进心。这对我而言是很可怕的。我总认为,不学习你就无法进步,更不能因为工作或者生活的重担,压力等等因素让你失去了学习的热情。我一直很认同一句老话“活到老,学到老”。不错,学习是一个不轻松的过程,身为学生我对此也颇有感触,但是学习带给你的感觉是任何感觉都无法比拟的。也许在学习和钻研的过程中你会感到痛苦,但是,不“吃得苦中苦”,怎能“方为人上人”?我也发现自己一个比较好的优点就是始终都想学习,以前是,现在是,将来也是,而且越长大,了解的东西越多,想要学习的也越多。学习使人丰富。我想我自己不会在工作中失去学习的热情的。
在实习过程中我另一个比较深的体会是团队合作的重要性。编写一个软件系统,不仅要保证所有主要的想法主要的方向统一,编写过程中也需要所有成员的交流。这就是说,一个好的团队应该首先所有的想法出自同一个大脑,其次它的四肢应该在服从大脑指挥的同时很好的协同工作。否则,我们做一个系统就好像找来一群互不认同的工匠盖房子,大家都各自盖自己的,因为彼此之间没有共同语言所以创造的过程是很枯燥的,大家都感觉自己在遵循总的原则,朝着一个总的方向前进,结果到了最后要把各自盖的部分组成一个完整的房子时才发现原来各个部分之间根本不能很好的组合在一起,排水系统,通风系统和屋子主体结构没法联合在一起,导致房子最终没法完成,只留下一堆烂摊子。是的,团队成员间的交流是重要的,特别现在的面向对象的编程过程中,都把一个系统分成几个模块,模块内再细分,然后将某部分的功能指派给一个程序员完成,模块间的通信都是通过接口实现的,如果不及时,合理的交流,两个类之间可能根本就无法通信,或者干脆就没有留有商议好的接口导致无法通信,工作当然也就完成不了了。我记得曾经有个神话传说,古巴比伦时代人们很聪明很团结,曾经他们想建一座通天塔去探索天上的秘密。这个举动惊动了天神,为了防止人类的团结危及他的统治,天神让那批建造古巴比伦通天塔的工人们彼此有不同的语言而无法向当初那样交流,最终让通天塔没法完成,人类探索天上秘密的脚步因此停住了。这样看来,天神真的很聪明,看出了人类的交流是多么的重要,交流的力量是多么的强大,让人类无法交流,人类就无法合作。而实际工作中,交流也一直占据着一个不可动摇的重要地位。
在实际中我也碰到了由于交流不够而产生的问题。比如在一个视上要接收用户的输入数据,然后要判定有效性和合法性(这几乎是交互时必须要做的事情了),先前这两个互留接口的模块编写人员没有好好交流,后来联调的时候会出现这个验证功能,或者两人都没写,或者两人都写了这样的情况。都写了那不是太大的问题,都没写的话问题可能就大了,可能会导致一些意料之外的错误,而调试人员要费好大的劲,绕了很多弯路才发现问题的出处。因为没有经过验证的数据会有潜在的危险,把没有通过验证的数据放行了,就好比给系统引入了一个没有钟表的定时炸弹,不知道它什么时候会爆炸,更不知道它会给系统带来什么样的破坏。所以下边的对话是大家都不愿意碰到的:
A:“哎,这怎么会溢出呢”
B:“我看看···”
过了好长一阵子,这是一段辛苦的摸索过程。
B:“你看,问题在这。这里接收的输入结构体在链表中的存储不对,你看,出现了部分非法数据,这怎么会是个这么大的负数嘛”
A:“怎么会这样,我填充了结构体后就传给你了。你接收我传过去的数据时没有验证吗”
B:“啊,我以为你写了,我就直接收了push到链表里了”
A:“……,知道了,原来是个谁都可以做的验证过程,只是我们都没有写而已”
B:“就这么个小问题浪费我们半天时间,哎,你写还是我写……改吧,早点调完早点解脱”
……
请不要觉得这段对话很搞笑,因为我做的是一个中小型的软件系统,所以出现这样的情况还是相对比较好解决的。如果是做一个大型系统,那么,如果这样的错误出现,很可能的情况是开发周期要往后推迟几星期,不幸的话,一两年都有可能,因为系统越大,问题越多,问题藏得也就越深,找到问题的所在所要花的时间和精力都将是巨大的,同时这一大段时间里大家都在为找出错误而忙的焦头烂额,同事之间的争吵和不和也油然而生,影响了团队的团结。长时间的延期也会给公司的形象造成相当一部分的损失,可能一些现有的或者潜在的客户听说了公司的某些延期项目之后会重新考虑和公司的合作,这样直接导致客户流失,公司付出的代价将是巨大的。所以,身为一个开发人员,在做项目做开发的时候一定要有一种严谨的态度,要和自己的工作伙伴合理的交流,防止这种缺少交流而出现的错误发生。
还有一点是必须要说明的,那就是:在实际的工作中是肯定会碰到问题的,碰到问题该怎么解决呢?以我这段时间的体会来看,我认为首先要自行求解,实在不弄不清楚再去请教高手,这是一种比较好的习惯。就像当初刚刚学习编程时候一位师兄的提醒一样,首先要自己试着去寻找答案,因为在这个寻找的过程中你往往能学习到很多知识,不管是与你的问题有关的或者是无关的知识,总之你又多学了一点。就算最后没找到答案,你已经比当初的你更丰富了;自己找不到答案,然后才拿去问别人,这是一种对待问题的比较好的方法。在实际工作中我发现网络是个很好的资源,充分利用的话足够你解决几乎所有的问题。因为网上有很多人提出了跟你或许相似的问题,也有很多人针对这些问题作了解答,这些解答有很多参考价值。回顾我的三年大学生涯(虽然还没结束),现在看来对于网络我一直没有把它用在该用的地方,虽然我不是堕落的人,但是大多数上网的时间我都是用来上浩方打DOTA(还好不是那种很浪费时间的网游例如魔兽世界之类的),聊QQ,看电影,看漫画什么的。在这次实习中我仿佛认识了一个新的网络一样。原来网络上有那么多我想要解决的问题的答案,还有那么多引导我解决问题思路的线索,有那么多牛人写的博客……原来我一直只是用了庞大的网络资源中小得可怜的一部分而已。我感觉自己是一条小鱼儿,一直一味网络就是我所游过的一条条小溪小河,如今突然闯进了汪洋大海,这才对自己之前的浅薄感到羞愧,这才有了一种相见恨晚的感觉,这才急切的想要去弥补自己的不足。
总的来说,这个暑假确实给我好好上了一课,让我对于这个社会,对于职业对于人生有了更多的思考和总结。写这份报告的时候,实习已经结束了,而我的心久久不能平静。大学眼看就要结束了,我是否准备好工作了;就要踏入社会了,我是否成为一个合格的社会人了;前方是哪里?我的未来将会是怎样的?一大串的新问题又出现了,但是我不会想当初那样的手足无措,我会一路前行,同时认真思考自己的前途。
现在我想自以为是的为自己解答几个在实习之初我问我自己的那些问题。
其一:究竟什么是最适合我的?
答曰:“这个地球不是以你为中心才会转的,很多时候你没法改变大环境。要寻找‘最’适合你的东西,那你是要付出很大代价而且风险也相当之大。所以,还是不要去寻找‘最’。前边提到的三条路,其实每条都可以走,但是可以先考虑前两条,有了社会经验和对某行业的谙熟这两个条件后可以再考虑创业。做事情要一步一步来。不过,目前看来有个相当适合你的存在,叫做‘学无止境’”
其二:究竟我想要什么?
答曰:“你不是那种甘心到老了还是个工程师的人,你想学很多东西,其中有你最喜欢的技术;同时你还想涉猎其他区域,如管理和人际,心理学等。‘池中之物’不是你最终的归宿,但是现在是重要的积累期,你要的就是一个更好的自己”
其三:究竟我该怎么做?
答曰:“对技术:求知的心和严谨的治学态度。
对人:坦诚交流和互相帮助。
对工作:认真和敢于承担起责任。
对生活:毫无根据的热爱。”
最后,我衷心的祝福我的兄弟姐妹们。希望所有出国的同学们签证顺利,在国外能够过上安稳的生活;希望所有保研的同学们在这段时间辛苦过后能够保到一个好的导师名下,三年后能很好的成长;希望所有考研的同学们在残卷枯灯的考研征途上一路走好;希望所有即将找工作的同学们找到理想的好工作,同时也希望在自己以后的职业生涯中一路走好。
Sep 6, 2008
【转】UML之父Ivar:软件开发新趋势 可执行代码比"宏观"架构更重要
CSDN9月5日现场报道】由CSDN和《程序员》杂志联合主办的第三届中国软件技术英雄会(上海站)正在上海华亭宾馆大宴会厅进行中。当前正在台上演讲的是当前软件工业开发模式的几位世界级大师之一、被业界誉为UML之父的Ivar博士。
Ivar博士诙谐的开场白立即引来了现场观众的共鸣和兴奋,之后通过现场调查听众中正在使用CMMI的人数,正在使用UML,正在使用敏捷编程,极限编程。通过现场的小调查和软件开发者现状,分析了未来软件开发的趋势。他强调,现在很多公司,尤其是大型公司,认为过程跟工具要比人更重要,甚至可以用这些东西来取代人,但这其实是不可能的。
即使是很小的系统,虽然它小,但是它可以执行,可以执行代码,可以慢慢给它加上更多的功能,让它变成一个完整的、成熟的系统。应该不要在接触具体的细节之前过多考虑它,先把早期的决定和轻量级的需求做出来,在这个基础上先做,很多客户需要做出很多详细的事情,可以为了让他们高兴先做一做,事实上它是一堆垃圾。有多少人认为架构是很重要的,还有些人就认为应该具有那种非常恢弘的,很壮观的企业的架构,类似象牙塔这样的架构,这两种情况都不明智。如果机构没有可执行的代码的话,它就只是一个空幻的东西。
Aug 7, 2008
编译原理基础
编译原理基础
编译程序是现代计算机系统的基本组成部分。从功能上看,一个编译程序就是一个语言翻译程序,把一种语言书写的程序(源程序)翻译成另一种语言(目标程序)的等价的程序
软件,计算机系统中的程序及其文档
系统软件,居于计算机系统中最靠近硬件的一层,如编译系统和操作系统等
语言处理系统,把软件语言书写的各种程序处理成可在计算机上执行的程序。
软件语言:用于书写软件的语言,主要包括需求定义语言,功能性语言,设计性语言,程序
设计语言以及文档语言
编译逻辑过程:
词法分析,语法分析,语义分析,中间代码生成,代码优化,目标代码生成
编译阶段的组合
分析,综合:源程序分析(线性分析,层次分析,语义分析),目标程序的综合
编译的前端(front end)
编译的后端(back end)
遍历一遍(pass):从头到尾扫描源程序
什么是词法分析程序:逐个读入源程序字符并按照构词规则分成一系列单词。单词是语言中具有独立意义的最小单位,包括保留字,标识符,运算符,标点符号和常量
词法分析是编译过程中的一个阶段,在语法分析前进行,页可以和语法分析结合在一起作为一遍。由语法分析程序调用词法分析程序来获得当前单词供语法分析使用
词法分析程序的主要任务:读源程序,产生单词符号
词法分析程序的其他任务:滤掉空格,跳过注释,换行符;追踪换行表示,复制出错源程序;宏展开
符号串的运算:
符号串的头,尾,固有头和固有尾:z=xy,x是z的头,y是z的尾。若x非空,那么y是固有尾;同样y非空的话x是固有头 z=abc,那z的头是a,ab,abc,除abc外都是固有透;z的尾是c,bc,abc,除abc外均是固有尾。当对符号串z = xy头感兴趣对其余部分不感兴趣,可以简写z = x…;如果只是强调x在符号串z中某处出现,可以表示为z=…x…;符号t是符号串z的第一个符号,则表示为z=t…
符号串的连接:假设x,y是符号串,他们的连接xy是吧y的符号卸载x的符号后面得到的符号串
符号串的方幂:
符号串集合:集合A中所有元素都是某字母表∑上的符号串,则A为字母表上的符号串集合
两个符号串集合A和B的乘积定义为AB={xy|x∈A且y∈B}
正规表达式与正规集
程序设计语言中的单词是基本语法成分,单词符号的语法可以用有效的工具加以描述,并且基于这类描述,实现词法分析程序的自动构造
一些基本术语和概念:
符号:一个抽象实体,字母和数字都是符号
字母表;是元素的非空有穷集合,不同的语言可以有不同的字母表
符号串;由字母表中的符号组成的任何有穷序列陈伟符号串
符号串的长度:符号串中有多少个符号就是多长
空符号串;不包含任何符号的符号串
正规式:也称正则表达式,是定义正规集的数学工具,我们用以描述单词符号。
状态转换图:是设计词法分析器的一种好途径。转换图是一张有限方向图,结点代表状态,用圆圈表示;状态之间用箭弧连接。箭弧上标记代表在射出结点状态下可能出现的输入字符或者字符类
确定的有穷自动机DFA:一个确定的有穷自动机M是一个五元组M=(K,f,S,Z)其中K是一个有穷集,它的每个元素称为一个状态,∑是一个有穷字母表,它的每一个元素称为一个输入符号;f是转换函数,是在k×∑->K的映射,S∈K是唯一的一个初态;Z包含于K是一个终态集,终态页可称为接受状态或结束状态
一个DFA可以表示成一个状态图(或称状态转换图),整个图有唯一一个初态结点和若干个终态结点。
不确定的有穷自动机NFA
例题:写出一定条件的文法和正则表达式
文法和语言概述:当我们表述一种语言时,无非是说明这种语言的句子,如果语言只含有有穷多个句子,则只需列出句子的有穷集就行了;但对于含有无穷句子的语言来讲,存在着如何给出它的有穷表示的问题。但是人们可以给出一些规则用来说明(或者定义)句子的组成结构
如果语言是无穷的,找出语言的有穷表示,语言的有穷表示有两个途径
生成方法(文法):语言中的每个句子可以用严格定义的规则来构造
识别方式(自动机):用一个过程,当输入的一任意串属于语言时,该过程经有限次计算后就会停止并回答“是“,若不属于,要么能停止并回答”不是“,要么永远继续下去

