2006年02月22日

  前面已经说过程序就是方法的描述,而方法的描述无外乎就是动作加动作的宾语,而这里的动作在C++中就是通过语句来表现的,而动作的宾语,也就是能够被操作的资源,但非常可惜地C++语言本身只支持一种资源——内存。由于电脑实际可以操作不止内存这一种资源,导致C++语言实际并不能作为底层硬件程序的编写语言(即使是C语言也不能),不过各编译器厂商都提供了自己的嵌入式汇编语句功能(也可能没提供或提供其它的附加语法以使得可以操作硬件),对于VC,通过使用__asm语句即可实现在C++代码中加入汇编代码来操作其他类型的硬件资源。对于此语句,本系列不做说明。

  语句就是动作,C++中共有两种语句:单句和复合语句。复合语句是用一对大括号括起来,以在需要的地方同时放入多条单句,如:{ long a = 10; a += 34; }。而单句都是以“;”结尾的,但也可能由于在末尾要插入单句的地方用复合语句代替了而用“}”结尾,如:if( a ) { a–; a++; }。应注意大括号后就不用再写“;”了,因为其不是单句。

  方法就是怎么做,而怎么做就是在什么样的情况下以什么样的顺序做什么样的动作。因为C++中能操作的资源只有内存,故动作也就很简单的只是关于内存内容的运算和赋值取值等,也就是前面说过的表达式。而对于“什么样的顺序”,C++强行规定只能从上朝下,从左朝右来执行单句或复合语句(不要和前面关于表达式的计算顺序搞混了,那只是在一个单句中的规则)。而最后对于“什么样的情况”,即进行条件的判断。为了不同情况下能执行不同的代码,C++定义了跳转语句来实现,其是基于CPU的运行规则来实现的,下面先来看CPU是如何执行机器代码的。

 机器代码的运行方式

  前面已经说过,C++中的所有代码到最后都要变成CPU能够认识的机器代码,而机器代码由于是方法的描述也就包含了动作和动作的宾语(也可能不带宾语),即机器指令和内存地址或其他硬件资源的标识,并且全部都是用二进制数表示的。很正常,这些代表机器代码的二进制数出于效率的考虑在执行时要放到内存中(实际也可以放在硬盘或其他存储设备中),则很正常地每个机器指令都能有一个地址和其相对应。

  CPU内带一种功能和内存一样的用于暂时记录二进制数的硬件,称作寄存器,其读取速度较内存要快很多,但大小就小许多了。为了加快读取速度,寄存器被去掉了寻址电路进而一个寄存器只能存放1个32位的二进制数(对于32位电脑)。而CPU就使用其中的一个寄存器来记录当前欲运行的机器指令的位置,在此称它为指令寄存器。

  CPU运行时,就取出指令寄存器的值,进而找到相应的内存,读取1个字节的内容,查看此8位二进制数对应的机器指令是什么,进而做相应的动作。由于不同的指令可能有不同数量的参数(即前面说的动作的宾语)需要,如乘法指令要两个参数以将它们乘起来,而取反操作只需要一个参数的参与。并且两个8位二进制数的乘法和两个16位二进制数的乘法也不相同,故不同的指令带不同的参数而形成的机器代码的长度可能不同。每次CPU执行完某条机器代码后,就将指令寄存器的内容加上此机器代码的长度以使指令寄存器指向下一条机器代码,进而重复上面的过程以实现程序的运行(这只是简单地说明,实际由于各种技术的加入,如高速缓冲等,实际的运行过程要比这复杂得多)。

 语句的分类

  在C++中,语句总共有6种:声明语句、定义语句、表达式语句、指令语句、预编译语句和注释语句。其中的声明语句下篇说明,预编译语句将在《C++从零开始(十六)》中说明,而定义语句就是前面已经见过的定义变量,后面还将说明定义函数、结构等。表达式语句则就是一个表达式直接接一个“;”,如:34;、a = 34;等,以依靠操作符的计算功能的定义而生成相应的关于内存值操作的代码。注释语句就是用于注释代码的语句,即写来给人看的,不是给编译器看的。最后的指令语句就是含有下面所述关键字的语句,即它们的用处不是操作内存,而是实现前面说的“什么样的情况”。

  这里的声明语句、预编译语句和注释语句都不会转换成机器代码,即这三种语句不是为了操作电脑,而是其他用途,以后将详述。而定义语句也不一定会生成机器代码,只有表达式语句和指令语句一定会生成代码(不考虑编译器的优化功能)。

  还应注意可以写空语句,即;或{},它们不会生成任何代码,其作用仅仅只是为了保证语法上的正确,后面将看到这一点。下面说明注释语句和指令语句——跳转语句、判断语句和循环语句(实际不止这些,由于异常和模板技术的引入而增加了一些语句,将分别在说明异常和模板时说明)。

 注释语句——//、/**/

  注释,即用于解释的标注,即一些文字信息,用以向看源代码的人解释这段代码什么意思,因为人的认知空间和电脑的完全不同,这在以后说明如何编程时会具体讨论。要书写一段话用以注释,用“/*”和“*/”将这段话括起来,如下:

    long a = 1;
    a += 1;  /* a放的是人的个数,让人的个数加一 */
    b *= a;  /* b放的是人均花费,得到总的花费 */

  上面就分别针对a += 1;和b *= a;写了两条注释语句以说明各自的语义(因为只要会C++都知道它们是一个变量的自增一和另一个变量的自乘a,但不知道意义)。上面的麻烦之处就是需要写“/*”和“*/”,有点麻烦,故C++又提供了另一种注释语句——“//”:

    long a = 1;
    a += 1;  // a放的是人的个数,让人的个数加一
    b *= a;  // b放的是人均花费,得到总的花费

  上面和前面等效,其中的“//”表示从它开始,这一行后面的所有字符均看成注释,编译器将不予理会,即:

    long a = 1; a += 1;  // a放的是人的个数,让人的个数加一 b *= a;

  其中的b *= a;将不会被编译,因为前面的“//”已经告诉编译器,从“//”开始,这一行后面的所有字符均是注释,故编译器不会编译b *= a;。但如果

    long a = 1; a += 1;  /* a放的是人的个数,让人的个数加一 */ b *= a;

  这样编译器依旧会编译b *= a;,因为“/*”和“*/”括起来的才是注释。

  应该注意注释语句并不是语句,其不以“;”结束,其只是另一种语法以提供注释功能,就好象以后将要说明的预编译语句一样,都不是语句,都不以“;”结束,既不是单句也不是复合语句,只是出于习惯的原因依旧将它们称作语句。

 跳转语句——goto

  前面已经说明,源代码(在此指用C++编写的代码)中的语句依次地转变成用长度不同的二进制数表示的机器代码,然后顺序放在内存中(这种说法不准确)。如下面这段代码:

    long a = 1;  // 假设长度为5字节,地址为3000
    a += 1;      // 则其地址为3005,假设长度为4字节
    b *= a;      // 则其地址为3009,假设长度为6字节

  上面的3000、3005和3009就表示上面3条语句在内存中的位置,而所谓的跳转语句,也就是将上面的3000、3005等语句的地址放到前面提过的指令寄存器中以使得CPU开始从给定的位置执行以表现出执行顺序的改变。因此,就必须有一种手段来表现语句的地址,C++对此给出了标号(Label)。

  写一标识符,后接“:”即建立了一映射,将此标识符和其所在位置的地址绑定了起来,如下:

    long a = 1;  // 假设长度为5字节,地址为3000

  P1:

    a += 1;      // 则其地址为3005,假设长度为4字节

  P2:

    b *= a;      // 则其地址为3009,假设长度为6字节
    goto P2;

  上面的P1和P2就是标号,其值分别为3005和3009,而最后的goto就是跳转语句,其格式为goto <标号>;。此语句非常简单,先通过“:”定义了一个标号,然后在编写goto时使用不同的标号就能跳到不同的位置。

  应该注意上面故意让P1和P2定义时独占一行,其实也可以不用,即:

    long a = 1; P1: a += 1; P2: b *= a; goto P2;

  因此看起来“P1:”和“P2:”好象是单独的一条定义语句,应该注意,准确地说它们应该是语句修饰符,作用是定义标号,并不是语句,即这样是错误的:

    long a = 1; P1: { a += 1; P2: b *= a; P3: } goto P2;

  上面的P3:将报错,因为其没有修饰任何语句。还应注意其中的P1仍然是3005,即“{}”仅仅只是其复合的作用,实际并不产生代码进而不影响语句的地址。

 判断语句——if else、switch

  if else  前面说过了,为了实现“什么样的情况”做“什么样的动作”,故C++非常正常地提供了条件判断语句以实现条件的不同而执行不同的代码。if else的格式为:

    if(<数字>)<语句1>else<语句2>  或者  if(<数字>)<语句1>
    long a = 0, b = 1;
 P1:
    a++;
    b *= a;
    if( a < 10 )
        goto P1;
    long c = b;

  上面的代码就表示只有当a的值小于10时,才跳转到P1以重复执行,最后的效果就是c的值为10的阶乘。

  上面的<数字>表示可以在“if”后的括号中放一数字,即表达式,而当此数字的值非零时,即逻辑真,程序跳转以执行<语句1>,如果为零,即逻辑假,则执行<语句2>。即也可如此:if( a – 10 ) goto P1;,其表示当a – 10不为零时才执行goto P1;。这和前面的效果一样,虽然最后c仍然是10的阶乘,但意义不同,代码的可读性下降,除非出于效率的考虑,不推荐如此书写代码。

  而<语句1>和<语句2>由于是语句,也就可以放任何是语句的东西,因此也可以这样:

    if( a ) long c;

  上面可谓吃饱了撑了,在此只是为了说明<语句1>实际可以放任何是语句的东西,但由于前面已经说过,标号的定义以及注释语句和预编译语句其实都不是语句,因此下面试图当a非零时,定义标号P2和当a为零时书写注释“错误!”的意图是错误的:

    if( a ) P2:      或者    if( !a )  // 错误!
    a++;                     a++;

  但编译器不会报错,因为前者实际是当a非零时,将a自增一;后者实际是当a为零时,将a自增一。还应注意,由于复合语句也是语句,因此:

    if( a ){ long c = 0; c++; }

  由于使用了复合语句,因此这个判断语句并不是以“;”结尾,但它依旧是一个单句,即:

    if( a )
        if( a < 10 ) { long c = 0; c++; }
    else
        b *= a;

  上面虽然看起来很复杂,但依旧是一个单句,应该注意当写了一个“else”时,编译器向上寻找最近的一个“if”以和其匹配,因此上面的“else”是和“if( a < 10 )”匹配的,而不是由于上面那样的缩进书写而和“if( a )”匹配,因此b *= a;只有在a大于等于10的时候才执行,而不是想象的a为零的时候。

  还应注意前面书写的if( a ) long c;。这里的意思并不是如果a非零,就定义变量c,这里涉及到作用域的问题,将在下篇说明。

  switch  这个语句的定义或多或少地是因为实现的原因而不是和“if else”一样由于逻辑的原因。先来看它的格式:switch(<整型数字>)<语句>。

  上面的<整型数字>和if语句一样,只要是一个数字就可以了,但不同地必须是整型数字(后面说明原因)。然后其后的<语句>与前相同,只要是语句就可以。在<语句>中,应该使用这样的形式:case <整型常数1>:。它在它所对应的位置定义了一个标号,即前面goto语句使用的东西,表示如果<整型数字>和<整型常数1>相等,程序就跳转到“case <整型常数1>:”所标识的位置,否则接着执行后续的语句。

    long a, b = 3;
    switch( a + 3 )
    case 2: case 3: a++;
    b *= a;

  上面就表示如果a + 3等于2或3,就跳到a++;的地址,进而执行a++,否则接着执行后面的语句b *= a;。这看起来很荒谬,有什么用?一条语句当然没意义,为了能够标识多条语句,必须使用复合语句,即如下:

    long a, b = 3;
    switch( a + 3 )
    {
        b = 0;
    case 2:
        a++;     // 假设地址为3003
    case 3:
        a–;     // 假设地址为3004
        break;
    case 1:
        a *= a;  // 假设地址为3006
    }
    b *= a;      // 假设地址为3010

  应该注意上面的“2:”、“3:”、“1:”在这里看着都是整型的数字,但实际应该把它们理解为标号。因此,上面检查a + 3的值,如果等于1,就跳到“1:”标识的地址,即3006;如果为2,则跳转到3003的地方执行代码;如果为3,则跳到3004的位置继续执行。而上面的break;语句是特定的,其放在switch后接的语句中表示打断,使程序跳转到switch以后,对于上面就是3010以执行b *= a;。即还可如此:

    switch( a ) if( a ) break;

  由于是跳到相应位置,因此如果a为-1,则将执行a++;,然后执行a–;,再执行break;而跳到3010地址处执行b *= a;。并且,上面的b = 0;将永远不会被执行。

  switch表示的是针对某个变量的值,其不同的取值将导致执行不同的语句,非常适合实现状态的选择。比如用1表示安全,2表示有点危险,3表示比较危险而4表示非常危险,通过书写一个switch语句就能根据某个怪物当前的状态来决定其应该做“逃跑”还是“攻击”或其他的行动以实现游戏中的人工智能。那不是很奇怪吗?上面的switch通过if语句也可以实现,为什么要专门提供一个switch语句?如果只是为了简写,那为什么不顺便提供多一些类似这种逻辑方案的简写,而仅仅只提供了一个分支选择的简写和后面将说的循环的简写?因为其是出于一种优化技术而提出的,就好象后面的循环语句一样,它们对逻辑的贡献都可以通过if语句来实现(毕竟逻辑就是判断),而它们的提出一定程度都是基于某种优化技术,不过后面的循环语句简写的成分要大一些。

  我们给出一个数组,数组的每个元素都是4个字节大小,则对于上面的switch语句,如下:

    unsigned long Addr[3]; Addr[0] = 3006; Addr[1] = 3003; Addr[2] = 3004;

  而对于switch( a + 3 ),则使用类似的语句就可以代替:goto Addr[ a + 3 – 1 ];

  上面就是switch的真面目,应注意上面的goto的写法是错误的,这也正是为什么会有switch语句。编译器为我们构建一个存储地址的数组,这个数组的每个元素都是一个地址,其表示的是某条语句的地址,这样,通过不同的偏移即可实现跳转到不同的位置以执行不同的语句进而表现出状态的选择。

  现在应该了解为什么上面必须是<整型数字>了,因为这些数字将用于数组的下标或者是偏移,因此必须是整数。而<整型常数1>必须是常数,因为其由编译时期告诉编译器它现在所在位置应放在地址数组的第几个元素中。

  了解了switch的实现后,以后在书写switch时,应尽量将各case后接的整型常数或其倍数靠拢以减小需生成的数组的大小,而无需管常数的大小。即case 1000、case1001、case 1002和case 2、case 4、case 6都只用3个元素大小的数组,而case 0、case 100、case 101就需要102个元素大小的数组。应该注意,现在的编译器都很智能,当发现如刚才的后者这种只有3个分支却要102个元素大小的数组时,编译器是有可能使用重复的if语句来代替上面数组的生成。

  switch还提供了一个关键字——default。如下:

    long a, b = 3;
    switch( a + 3 )
    {
    case 2:
        a++;
        break;
    case 3:
        a += 3;
        break;
    default:
        a–;
    }
    b *= a;

  上面的“default:”表示当a + 3不为2且不为3时,则执行a–;,即default表示缺省的状况,但也可以没有,则将直接执行switch后的语句,因此这是可以的:switch( a ){}或switch( a );,只不过毫无意义罢了。

 循环语句——for、while、do while

  刚刚已经说明,循环语句的提供主要是出于简写目的,因为循环是方法描述中用得最多的,且算法并不复杂,进而对编译器的开发难度不是增加太多。

  for  其格式为for(<数字1>;<数字2>;<数字3>)<语句>。其中的<语句>同上,即可接单句也可接复合语句。而<数字1>、<数字2>和<数字3>由于是数字,就是表达式,进而可以做表达式语句能做的所有的工作——操作符的计算。for语句的意思是先计算<数字1>,相当于初始化工作,然后计算<数字2>。如果<数字2>的值为零,表示逻辑假,则退出循环,执行for后面的语句,否则执行<语句>,然后计算<数字3>,相当于每次循环的例行公事,接着再计算<数字2>,并重复。上面的<语句>一般被称作循环体。

  上面的设计是一种面向过程的设计思想,将循环体看作是一个过程,则这个过程的初始化(<数字1>)和必定执行(<数字3>)都表现出来。一个简单的循环,如下:

    long a, b;
    for( a = 1, b = 1; a <= 10; a++ )
        b *= a;

  上面执行完后b是10的阶乘,和前面在说明if语句时举的例子相比,其要简单地多,并且可读性更好——a = 1, b = 1是初始化操作,每次循环都将a加一,这些信息是goto和if语句表现不出来的。由于前面一再强调的语句和数字的概念,因此可以如下:

    long a, b = 1;
    for( ; b < 100; )
        for( a = 1, b = 1; a; ++a, ++b )
            if( b *= a )
                switch( a = b )
                {
                case 1:
                    a++; break;
                case 2:
                    for( b = 10; b; b– )
                    {
                        a += b * b;
                        case 3: a *= a;
                    }
                    break;
                }

  上面看着很混乱,注意“case 3:”在“case 2:”后的一个for语句的循环体中,也就是说,当a = b返回1时,跳到a++;处,并由于break;的缘故而执行switch后的语句,也就是if后的语句,也就是第二个for语句的++a, ++b。当返回2时,跳到第三个for语句处开始执行,循环完后同样由break;而继续后面的执行。当返回3时,跳到a *= a;处执行,然后计算b–,接着计算b的值,检查是否非零,然后重复循环直到b的值为零,然后继续以后的执行。上面的代码并没什么意义,在这里是故意写成这么混乱以进一步说明前面提过的语句和数字的概念,如果真正执行,大致看过去也很容易知道将是一个死循环,即永远循环无法退出的循环。

  还应注意C++提出了一种特殊语法,即上面的<数字1>可以不是数字,而是一变量定义语句,即可如此:for( long a = 1, b = 1; a < 10; ++a, ++b );。其中就定义了变量a和b。但是也只能接变量定义语句,而结构定义、类定义及函数定义语句将不能写在这里。这个语法的提出是更进一步地将for语句定义为记数式循环的过程,这里的变量定义语句就是用于定义此循环中充当计数器的变量(上面的a)以实现循环固定次数。

  最后还应注意上面写的<数字1>、<数字2>和<数字3>都是可选的,即可以:for(;;);。

  while  其格式为while(<数字>)<语句>,其中的<数字>和<语句>都同上,意思很明显,当<数字>非零时,执行<语句>,否则执行while后面的语句,这里的<语句>被称作循环体。

  do while  其格式为do<语句>while(<数字>);。注意,在while后接了“;”以表示这个单句的结束。其中的<数字>和<语句>都同上,意思很明显,当<数字>非零时,执行<语句>,否则执行while后面的语句,这里的<语句>被称作循环体。

  为什么C++要提供上面的三种循环语句?简写是一重要目的,但更重要的是可以提供一定的优化。for被设计成用于固定次数的循环,而while和do while都是用于条件决定的循环。对于前者,编译器就可以将前面提过的用于记数的变量映射成寄存器以优化速度,而后者就要视编译器的智能程度来决定是否能生成优化代码了。

  while和do while的主要区别就是前者的循环体不一定会被执行,而后者的循环体一定至少会被执行一次。而出于简写的目的,C++又提出了continue和break语句。如下:

    for( long i = 0; i < 10; i++ )
    {
        if( !( i % 3 ) )
            continue;
        if( !( i % 7 ) )
            break;
        // 其他语句
    }

  上面当i的值能被3整除时,就不执行后面的“其他语句”,而是直接计算i++,再计算i < 10以决定是否继续循环。即continue就是终止当前这次循环的执行,开始下一次的循环。上面当i的值能被7整除时,就不执行后面的“其他语句”,而是跳出循环体,执行for后的语句。即break就是终止循环的运行,立即跳出循环体。如下:

    while( –i )                         do
    {                                    {
        if( i == 10 )                        if( i == 10 )
            continue;                            continue;
        if( i > 20 )                         if( i > 20 )
            break;                               break;
        // 其他语句                           // 其他语句
    }                                    }while( –i );
    a = i;                               a = i;

  上面的continue;执行时都将立即计算—i以判断是否继续循环,而break;执行时都将立即退出循环体进而执行后继的a = i;。

  还应注意嵌套问题,即前面说过的else在寻找配对的if时,总是找最近的一个if,这里依旧。

    long a = 0;
P1:
    for( long i = a; i < 10; i++ )
        for( long j = 0; j < 10; j++ )
        {
            if( !( j % 3 ) )
                continue;
            if( !( j % 7 ) )
                break;
            if( i * j )
            {
                a = i * j;
                goto P1;
            }
            // 其他语句
        }

  上面的continue;执行后,将立即计算j++,而break;执行后,将退出第二个循环(即j的循环),进而执行i++,然后继续由i < 10来决定是否继续循环。当goto P1;执行时,程序跳到上面的P1处,即执行long i = a;,进而重新开始i的循环。

  上面那样书写goto语句是不被推荐的,因为其破坏了循环,不符合人的思维习惯。在此只是要说明,for或while、do while等都不是循环,只是它们各自的用处最后表现出来好象是循环,实际只是程序执行位置的变化。应清楚语句的实现,这样才能清楚地了解各种语句的实际作用,进而明确他人写的代码的意思。而对于自己书写代码,了解语句的实现,将有助于进行一定的优化。但当你写出即精简又执行效率高的程序时,保持其良好的可读性是一个程序员的素养,应尽量培养自己书写可读性高的代码的习惯。

  上面的long j = 0在第一个循环的循环体内,被多次执行岂不是要多次定义?这属于变量的作用域的问题,下篇将说明。

  本篇的内容应该是很简单的,重点只是应该理解源代码编译成机器指令后,在执行时也放在内存中,故每条语句都对应着一个地址,而通过跳转语句即可改变程序的运行顺序。下篇将对此提出一系列的概念,并重点说明类型的意义。

  你是否梦想写一部格斗游戏但却无从着手呢?是否你只因游戏开发好玩而对之感兴趣?本文我们将分析一个通用的跨平台游戏引擎,每个游戏开发新手都可以自由地使用它。

  1. 3D游戏引擎的简短历史

  在游戏开发中,从一开始就确定正确的开发平台是很重要的。是否你的游戏支持Windows,Linux和OS X?是否你的游戏开发只使用OpenGL就足够了?OpenGL是十九世纪九十年代初期设计的,起初只运行于价值约$25,000的Unix CAD工作站上,后来移植到Windows和其它一些低端平台上。与此同时,随着游戏工业的发展,图形加速器价格从$2,000剧跌到你今天看到的价值约$150的大众市场价格。

  确实,许多人都会援引在1996年用OpenGL开发成功的革命性的游戏Quake,作为以上急速发展现象的直接的原因。然而,成功的Quake级的游戏开发标准要求更多:世界级音频支持,网络连接,用户输入设备支持,以及实时的管理能力等。既需要实现跨平台支持又能使游戏效果激动人心,要实现这样的解决方案最好建立一个体面的游戏开发站台。

  2. 用于C++,Java和其它开发语言的简单DirectMedia层

  对,历史就是这样有趣,但并不是每一部游戏都要做成Quake的克隆品。一直被业界许多人吹捧有着许多优点的选择是简单DirectMedia层(SDML)。这是一套跨平台的多媒体库,它提供对于音频,键盘,鼠标,游戏杆,OpenGL和2D视频帧缓冲的低级存取。SDML支持几乎我能想像出的每一个平台,包括Linux,Windows,所有的MacOS变异物,WinCE,Dreamcast还有另外一些操作系统。它被广泛应用于开发MPEG播放器,硬件仿真器,和许多流行的游戏,包括获奖的运行于Linux平台的Civilization:Call to Power。

  SDML用C写成,但生来就与C++一起工作,已经绑定到了另外许多语言,包括Ada,Eiffel,Java,Lua,ML,Perl,PHP,Pike,Python和Ruby。SDML的应用环境简直就没有什么限制,而且它碰巧是我最喜爱的开源飞行模拟器GL-117(见图1)的开发引擎。事实上,513游戏的当前开发已经基于SDML引擎而且被注册到了SDML的主页。



图1.GL-117中的一个视图


  3. 通道视觉效果演示程序

  研究游戏引擎的最好方法是看一些示例程序代码。简单地看一下图2中用SDML实现的2D通道类型演示图,你就能发现你仅用几行代码所能完成的工作。你可以使用该实例作为一个保护屏程序,音乐可视化动画效果,等等。篇幅所限,我已经整理了实际的绘制代码。请跟随我的注释分析下面对SDML的工作原理的描述:


#include "Tunnel.h"
// SDL 相关变量定义
SDL_Surface *screen,*bBuffer,*Image;
SDL_Rect rScreen,rBuffer;
int main (int argc, char **argv)
{
 int flag = SDL_SWSURFACE;// 请求一个软件表面.
 //软件表面处于系统内存中,
 // 一般不如硬件表面速度快
 #ifdef WIN32
 int fullscreen = MessageBox(NULL, "Show Full Screen (y/n):","Screen Setting", MB_YESNO);
 if (fullscreen==IDYES) {
  flag |= SDL_FULLSCREEN; // 如果用户需要,接管整个屏幕
 }
 #endif
 Tunnel_Timer(); // 读取起始的系统时钟值
 SDL_Init( SDL_INIT_VIDEO ); // 初始化视频子系统
 //把屏幕设置到 320×240,32位颜色
 screen = SDL_SetVideoMode( 320, 240, 32, flag);
 // 如果可用的话,为屏幕表面请求硬件缓冲
 bBuffer = SDL_CreateRGBSurface( SDL_HWSURFACE, screen->w,screen->h,screen->format->BitsPerPixel,
    screen->format->Rmask,
    screen->format->Gmask,
    screen->format->Bmask,
    screen->format->Amask);
 // 这是种子图像,一旦开始它就会盘旋起来
 Image = SDL_LoadBMP( "tunnel_map.bmp" );
 Image = SDL_ConvertSurface(Image, screen->format, SDL_HWSURFACE);
 rBuffer.x = 0;
 rBuffer.y = 0;
 rBuffer.w = bBuffer->w;
 rBuffer.h = bBuffer->h;
 // 忽视大多数事件, 包括 鼠标动作, 并取消光标
 SDL_EventState(SDL_ACTIVEEVENT, SDL_IGNORE);
 SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
 SDL_ShowCursor( SDL_DISABLE );
 Tunnel.Set( 320, 240 ); // 通道将填充整个的缓冲区
 Tunnel.Precalc( 16 ); //内部的圆圈直径
 while (SDL_PollEvent(NULL)==0) {
  float fTime = Tunnel_GetTime();
  //在修改前,必须锁定表面,特别当缓冲区处于图形硬件内存中时
  SDL_LockSurface(bBuffer);
  SDL_LockSurface(Image);
  Tunnel.Draw(bBuffer, Image, 180*sin(fTime), fTime*100);
  SDL_UnlockSurface(bBuffer); // 在更新以后你可以开锁
  SDL_UnlockSurface(Image);
  // 把缓冲区中的数据输出到屏幕绘图区域并强迫进行重画
  SDL_BlitSurface( bBuffer, NULL, screen, &rBuffer );
  SDL_UpdateRect( screen, 0, 0, 0, 0 );
 }
 Tunnel.Free();
}




图 2. 演示旋转和扭曲的2D通道


  4. 对另外一些游戏引擎的探索

  让我们看一下另外一些开源的游戏引擎。

  a) ALLEGRO(Allegro低级游戏开发例程)

  Allegro是一个开源的可移植的库,主要针对视频游戏和多媒体编程。Allegro由Shawn Hargreaves(近来称为Climax)创建,现在成长为一个能够跨越许多操作系统如Linux,Windows,MacOS,MS-DOS和许多另外的流行平台等的游戏系统。

  除了具有一个高级的2D图形库,它能容易地存取鼠标,键盘,游戏杆和高精度定时器中断。Allegro并没有包装或替换OpenGL,但是通过参观他们广阔的开发站点(http://www.allegro.cc/),你能学习怎样把OpenGL集成到Allegro游戏程序中。

  大约有700种不同的游戏工程,与Allegro一起发行,其中最为杰出的两类是街机游戏和谜题游戏。我特别地喜欢经典的街机游戏Zaxxon(见图3)的重制品。



图3.酷毙的Zaxxon的重制品


  b) Irrlicht:点燃快速实时的3D引擎

  这个Irrlicht 引擎是一个跨平台,高性能实时引擎,用C++写成。你可以选择Direct3D,OpenGL或基于软件的着色技术。高端特点包括动态阴影,粒子系统,人物动画,进门和出门技术和碰撞检测(见图4)。Irrlicht支持Windows和Linux并提供到语言Java,Perl,Ruby等的绑定。业界先驱Nikolaus Gebhardt在他的朋友的少部分帮助下完成的这个引擎工。



图4.在Irrlicht中的一个十分逼真的场景


  c) ClanLib:为多玩家游戏设计的引擎

  ClanLib提供了一个平台独立的接口来书写游戏-它们有一个共同的到低级库如DirectX和OpenGL的接口。借助于ClanLib,你只需编写少量代码即可在Windows,Linux和OSX系统上开发游戏程序。ClanLib包括一个广泛的声音库,2D碰撞检测,动画,GUI框架和网络库。图5显示了游戏XenoHammer中的一个场景。



图5.XenoHammer屏幕快照

要:本文重点讨论开源游戏开发库Allegro(Allegro低级游戏例程),同时涉及到一些深度技术并提供了一个简单的示例程序,帮你进一步确定它是否是适合你的开发平台。

  一、 一个适于多环境的引擎

  Allegro最开始被研发于八十年代后期古老的Atari ST平台上,随后被快速地移植到流行的DJGPP环境(一个在九十年代早期流行的32位的MS-DOS扩展程序)。此后,Allegro被移植到最为流行的Windows C++开发环境中,包括VS,MinGW,Cygwin和Borland C++。另外的支持它的平台包括Linux,BeOS,QNX,Mac OSX以及几乎任何其它带有X11库的Unix平台上。

  Allegro能着色到各种类型的位图和硬件加速的环境中,例如DirectX,XWindows,SVGAlib,FreeBE/AF,CGDirectDisplay,QuickDraw,等等。Allegro并不想提供它自己的3D环境或模拟器,但是OpenGL可以被容易地集成,这是通过使用AllegroGL库-它提供了一个类似于GLUT的接口(包括扩展管理)-实现的。

  二、 性能概要

  在进一步使用API开发前,让我们看一下Allegro提供的总体功能:

  ·具体到像素级的绘图函数,包括平坦阴影,gouraud阴影,纹理贴图,z缓冲的多边形和圆绘制,填充,贝塞尔样条曲线,图案填充,精灵,blitting(位图复制),位图计算缩放和旋转,半透明/光效果以及比例字体支持的文本输出

  ·FLI/FLC(在计算机生成的动画方面,这种格式比MPEG有更高的压缩性能)动画播放器

  ·播放后台MIDI音乐,可达64种同时的声音效果,并能录制样本波形和MIDI输入(声音平台支持,包括WaveOut,DirectSound,OSS,ESD,CoreAudio和QuickTime,等等)

  ·容易地存取鼠标,键盘,游戏杆等设备,还支持高分辨率定时器中断,包括一个DOS版本的垂直折回中断模拟器

  ·读/写LZSS压缩文件的例程

  ·数学函数,包括定点算术,表查找和3D矢量/矩阵/四元数操作

  ·GUI对话框管理器和文件选择器

  ·内建地支持16位和UTF-8格式的Unicode字符

  三、 使用引擎

  使用Allegro进行开发,就象在许多其它游戏场合下一样,游戏的总体结构都包括游戏开始前的初始化,游戏循环以及游戏完成后的清理。初始化意味着既包含Allegro启动代码也包含在开始的位置实现基本地装载或生成你的游戏级别。在创建你的初始化代码时,启动Allegro基本上没有什么代价付出(见图1).

  如果你需要很多屏幕相关的真实性能,建议你首先礼貌地用get_gfx_mode_list()函数查询一下最大可用方式:


#include <allegro.h> //必须放于系统头文件的引用之后
set_color_depth(32); // 缺省情况下使用8位颜色
if (set_gfx_mode(GFX_AUTODETECT, 640, 480, 0, 0) != 0) {
 abort_on_error("Couldn’t set a 32 bit color resolution");
}


  set_gfx_mode()的最后两个参数用于指定虚拟缓冲区的大小-我们的图形屏幕存储于其中。这可以使创建一个卷边游戏-其中地形是连续移动的-变得容易。例如,你可能要使虚拟缓冲区,比方说,宽出20%以留出足够的空间来平滑卷动新的精灵和地形。

 四、 一个完整的Allegro实例

  本教程将使用Kee-Yip Chan的SnookerClone演示程序,它是基于James Lohr的另一个具有相同名字的演示程序。图1显示了演示程序的基本屏幕快照。



图1.Kee-Yip Chan的"SnookerClone"演示程序


  这个工程向你展示了许多不同的Allegro技术,包括动画,键盘输入和鼠标输入,碰撞和游戏物理知识(例如重力)。它利用了三个主要的元素:一个有8个扶手的旋转的车轮,一个用箭头键来控制的大红球,还有一些从顶部往下坠落的蓝球。车轮以接触方式推动红球,而当红球碰上蓝球时,它们之间相互影响。

  下列是完整的Allegro演示程序的代码:


1 #include <allegro.h>
2 vector<Point> g_points; //aka球上点的列表
3 vector<Joint> g_joints; //物理对象列表,如车轮和缓冲器
4 kVec g_accControl;
6 int main(void)
7 {
8  allegro_init(); // 初始化allegro.
9  install_keyboard(); // 启动键盘.
10 install_mouse(); // 启动鼠标.
11 install_timer(); //过程show_mouse()所需要;
13 // 创建一个800×600的非全屏窗口.
14 set_gfx_mode(GFX_AUTODETECT_WINDOWED, 800, 600, 0, 0);
16 set_window_title("Kee-Yip Chan’s Snooker Clone");
17 text_mode(-1); // 文本将被画在透明的背景之上
19 BITMAP* buffer = create_bitmap(SCREEN_W, SCREEN_H);
  //创建一张位图用于双缓冲.
21  // 初始化数据.
22 create_joints(g_joints); //注册车轮、地板和缓冲器的硬编码的屏幕位置
25 // 创建顶点以组成aka球: 玩家所用球和三个蓝球
26 // 的位置, 速度, 大小和质量.
27 g_points.push_back(Point(kVec(100, 300),kVec(0, 0),16, 10));
// 玩家.
28 g_points.push_back(Point(kVec(50, 40), kVec(0, 0),12, 5));
// 中等的球.
29 g_points.push_back(Point(kVec(80, 40), kVec(0, 0) 12, 5));
//中等的球.
30 g_points.push_back(Point(kVec(110, 40),kVec(0, 0),6, 1));
// 小球.
32 //主循环,在按ESC键后退出
33 while(!key[KEY_ESC]) { //检查输入.
34  if(key[KEY_UP])
35   g_accControl.y = -0.07; //Jet pack.向上加速
36  if(key[KEY_LEFT])
37   g_accControl.x = -0.07; //左走.向左加速
38  if(key[KEY_RIGHT])
39   g_accControl.x = 0.07; //右走.向右加速
41   static bool leftMousePressed = false,
    rightMousePressed = false;
42  if(mouse_b & 1) { //鼠标左键按下
43   if(!leftMousePressed){
44    leftMousePressed = true; // 创建一个新球.
45    g_points.push_back(Point(kVec(mouse_x, mouse_y),kVec(0, 0), 12, 5));
46   }
47  }
48  if(!(mouse_b & 1))
49  //保证不重复鼠标按键
50  //否则,就会出现许多的新球
51  leftMousePressed = false;
52  if(mouse_b & 2) { //鼠标右键按下
53   if(!rightMousePressed){
54    rightMousePressed = true; // 创建一个新球
55    g_points.push_back(Point(kVec(mouse_x, mouse_y),kVec(0, 0), 6, 1));
56   }
57  }
58  if(!(mouse_b & 2))
59   //保证不重复鼠标按键
60   //否则,就会出现许多的新球.
61   rightMousePressed = false;
63   doPhysics();
65   // 着色:如果我们能再次使用缓冲区,则清除它;
    //否则,旧图像将滞留显示
66   //用白色进行清除.
67   clear_to_color(buffer, makecol(255, 255, 255));
68   for(unsigned i = 0; i < g_points.size(); i++) {
     //画点.
69    //画一个实心球
70    circlefill(buffer, //画向缓冲区
71    g_points[i].position.x,g_points[i].position.y,// aka 球的中心点的位置
72    g_points[i].size, // 半径.
73    (i == 0) ? makecol(255, 0, 0) : makecol(0, 0, 255)); //红色如果是玩家;否则为蓝色
75    // 画一个轮廓球.
76    circle(buffer, //画向缓冲区
77     g_points[i].position.x,g_points[i].position.y, // aka 球的中心点的位置.
78     g_points[i].size, // 半径.
79     makecol(0, 0, 0)); //红色如果是玩家;否则为蓝色.
81   }
83   // 画接合点
84   for (unsigned i = 0; i < g_joints.size(); i++)
85    line(buffer, //画向缓冲区
86     g_joints[i].p1.x, g_joints[i].p1.y, // 点 1.
87     g_joints[i].p2.x, g_joints[i].p2.y, // 点 2.
88     makecol(0, 0, 0)); // 黑颜色.
89    );
91   // 打印指令.
92   textout(buffer, font, "Left Mouse Button – new big ball Right Mouse Button – new small ball",
93     125, 1, makecol(0, 0, 0));
95   textout(buffer, font, "Arrow Keys – move red ball",
96     300, 592, makecol(0, 0, 0));
98   show_mouse(buffer); // 画鼠标光标.
100   draw_sprite(screen, buffer, 0, 0);// 把缓冲区中的数据画向屏幕.
101  } // while循环结束
103  return 0;
105 }END_OF_MAIN();


  33-101行包括了典型的游戏编程循环模式。游戏继续进行直到玩家按下ESC键退出为止。34-39行支持同时进行的键盘输入,因为你可以按下向上和向左箭头键来获取粗略的斜向运动。

  在41-61行,鼠标动作被捕获到全局变量mouse_b(用于按钮),mouse_x和mouse_y。如果你一直在使用一滚轮鼠标,你还可以使用变量mouse_z。我们对反向弹跳逻辑进行了一点硬编码以确保每次鼠标按下事件只有一个球下落。

  第63行调用doPhysics(),其目的是旋转车轮的线段,更新球位置,检测球碰撞和适当地改变它们的方向矢量。这个模块(350行的数学代码)有点深入了些,但它确实是一个一流的实现,值得你深入研究。

  余下的代码,65-101行,开始着色,在典型的示例程序中这属于常规实现部分。这里的着色用典型的双缓冲区技术,下一次屏幕变化被计算出来并进行脱屏绘制并在最的一毫秒进行缓冲交换(第100行)。这确保了视觉的连续性又减少了烦人的闪烁-对象看上去是随机地绘制的。在着色代码部分,对line()和circlefill()的调用是相当直接的:circlefill()以x,y,半径和填充颜色作为参数。

  textout_ex()函数的功能稍强于textout()(示于92-96行),允许你指定前景和背景颜色。Allegro提供例程以直接从GRX格式.fnt文件,8×8或8×16 BIOS格式.fnt文件,位图图象以及数据文件格式中装入字体。作为选择,你能导入一种大范围的Unicode字体,这可以通过写一个.txt脚本-它为每一范围的字符指定相应的不同的源文件-来实现。如果你想要支持TrueType,那么你需要AllegroTTF或相同功能的插件。
最后,在第100行的draw_sprite()实现一个覆盖性复制新生成的位图到第14行创建的屏幕对象上。覆盖性复制意指只有非透明的颜色像素被复制。在本例中,我确信它已被退化成一个"blit"(块复制)转储。

  五、 Allegro的音频

  这个snooker演示程序只涉及到了一些最基本的图形和I/O函数,但是并没有用到Allegro的音频开发包。该包中的MIDI混频,音响效果和录音API,其效果达到或超过几乎每一个我所见过的专业的声音库。Allegro音频应用软件大量存在,包括WinCab-一个MP3和OGG Vorbis音乐唱片机,还有LouTronic Rhythm Box-一个鼓声生成合成器,它具有可全面融合到一起的snare鼓,低音鼓和hi hat的效果。下面我们简单地回顾Allegro音频API的一小部分。

  每一个使用音频的程序都应该调用reserve_voices()来指定数字和MIDI声音驱动程序分别使用的声音的数目。接下去,你能控制这些音频轨道的混合.

  你可以非常容易地象下面这样插入一个音轨:


MIDI *midFile = load_midi("myfile.mid’);
play_midi(midFile, TRUE);//连续循环


  对于更复杂的需要,你可以安装三个钩子函数之一,它们可以使你拦截MIDI玩家事件。如果被设置为非NULL,这些例程将在每次MIDI消息,元事件和系统独占的数据块中被分别调用。

  Allegro的数字音频系统被设计为从最基本的配置到可高度扩展的。你能容易安装读取器和写入器来处理新的或者不同的音频文件类型, 例如:


register_sample_file_type("mp3",load_mp3,NULL);//安装MP3读取器


  当正播放数字音频时,你可以随时编辑它。下列代码改变将在播放一个样本参数时改变该样本(用于操作循环播放的声音):


void adjust_sample(const SAMPLE *spl, int vol, int pan, int freq, int loop);


  你能改变音量,平移音频数据并清除循环标志,在下次执行到循环末尾时,这将停止该样本。如果在播放相同样本的好几个副本,这会调整它遇到的第一个副本。如果该样本没有播放,对它没有任何影响。


  Irrlicht引擎是一个用C++书写的高性能实时的3D引擎,可以应用于C++程序或者.NET语言中。通过使用Direct3D(Windows平台),OpenGL 1.2或它自己的软件着色程序,可以实现该引擎的完全跨平台。尽管是开源的,该Irrlicht库提供了可以在商业级的3D引擎上具有的艺术特性,例如动态的阴影,粒子系统,角色动画,室内和室外技术以及碰撞检测等(见图1)。



图1.Irrlicht 3D引擎


  Irrlicht是一个德国神话故事中的一种动物的名字,它能够发光和飞翔,可以在大部分的沼泽地附近发现它。单词"Irrlicht"是两个德国单词("irr"意思是疯狂的;而"Licht"意思是光)的组合。在英语中,它被译为"鬼火"。

  Irrlicht十分幸运地为一个巨大的活跃的开发团队以大量的工程所支持。然而,因为Irrlicht主要由游戏名家Nikolaus Gebhardt所设计,所以该游戏在设计上十分连贯。你可以在网上到处发现有Irrlicht的增强程序,如可选用的地形生成器,入口生成器,输出器,world层生成器,相关教程和编辑器等。而且,它独立地创建了到Java,Perl,Ruby,BASIC,Python,LUA甚至更多种语言的绑定。而最为重要的是,它是完全自由的。

  二、 Irrlicht特性

  在深入分析API之前,请让我更具体地介绍一下Irrlicht提供给了3D游戏开发者哪些功能:

  ·一个可以运行于Linux以及Windows 98,ME,NT,2000和XP(MacOS在计划之中)等操作系统之上的引擎

  ·针对Direct3D 8生成器或Direct3D 9生成器(可选)提供了Anti-aliasing支持

  ·可换肤的GUI环境(包括一个很酷的具有金属质地的带阴影的皮肤),给一些老式的对话框加上漂亮的外观

  ·场景管理系统,它允许无缝的室内/室外过渡

  ·角色动画系统,带有骨骼和变形目标动画功能

  ·一个特殊的效果系统,包括粒子效果(雨,烟,火,雪,等等),告示板,灯光贴图,环境,地图,模板缓冲区阴影,雾,纹理动画,视差贴图,凹凸贴图,还有更多

  ·内建的材质支持,包括支持Pixel and Vertex Shaders版本1.1到3.0,ARB Fragment and Vertex程序以及HLSL(GLSL正在计划中)

  ·.NET语言绑定,这使得引擎可用于所有的.NET语言例如C#,Visual Basic.NET以及Delphi.NET

  ·一内建的平台独立的软件生成器,特性有:z-缓冲,Gouraud阴影,alpha混合和透明性,还有快速的2D绘图(见图2)

  ·你久已期待的2D绘图功能,例如alpha混合,基于关键色的位图复制,字体绘制,以及混合3D与2D图形

  ·能直接导入常见的建模文件格式:Maya,3DStudio Max,COLLADA,DeleD,Milkshape,Quake 3 levels,Quake2 models,DirectX,Pulsar,My3DTools,FSRad以及Cartography Shop

  ·能直接从BMP,PNG,Photoshop,JPEG,Targa和PCX导入纹理

  ·快速而易用的碰撞检测与响应

  ·为快速的3D运算和容器模板库进行了优化处理

  ·直接读取档案(可能是压缩的,如.zip文件)

  ·集成了快速的XML分析器

  ·为实现容易的本地化开发提供Unicode支持



图2:基于Irrlicht的游戏Yet Another Space Shooter(YASS),这里显示的是一个静态游戏帧中的令人吃惊的着色效果

  三、 在Irrlicht中的特殊效果

  在本文的例子中,我将向你展示怎样使用模板缓冲区影子技术,还有粒子系统,告示板,动态光以及水表面场景结点等技术。参见图3。



图3.结合动态的光和水进行的场景着色


  Irrlicht引擎自动地检查是否你的硬件支持模板缓冲;而如果不支持,则不启动阴影。在这个演示程序中,在方法createDevice()中的’shadows’标志被置位,以产生从一个动画角色投下的动态影子。如果这个实例程序在你的PC上运行太慢,可以把这个标志设置为false或者干脆再买一块更好些的图形加速卡。

  为能够使用Irrlicht.DLL文件,你需要链接到Irrlicht.lib库文件。你可以在工程设置对话框中设置这个选项;但是为了容易实现,你可以使用一个pragma预编译注释命令。方法createDevice()负责实例化根对象-它使用引擎完成一切事情。参数如下:

  ·deviceType:设备类型。当前你可选取Null设备以及软设备,如DirectX8,DirectX9或OpenGL。

  ·windowSize:要创建的窗口的大小或全屏幕模式。这个例子中使用512×384。

  ·bits:每像素位数(当在全屏幕情况时)。仅允许值为16或者32。

  ·fullscreen:指定是否你想使设备运行于全屏幕方式。

  ·stencilbuffer:指定是否你想使用模板缓冲区以用于绘制阴影。

  ·vsync:指定是否你想启动vsync(仅在全屏幕情况),可选。

  ·eventReceiver:一个接收事件的对象,可选。

  为适合于本实例环境,你将装载一个3D Studio Max文件(一幢房子)。该房子看起来并没有什么特别的,但是Irrlicht引擎能为你创建一个相当酷的纹理贴图。只需使用造型操纵器并为之创建一个planar纹理贴图即可:


#include <irrlicht.h>
#include <iostream>
using namespace irr;
#pragma comment(lib, "Irrlicht.lib")
int main()
{
 //让我们假定用户在本例中使用OpenGL
 //当然,也可以指定DirectX 8, 9, 等等.
 video::E_DRIVER_TYPE driverType = video::EDT_OPENGL;
 //创建设备,如果创建失败立即退出。
 IrrlichtDevice *device = createDevice(driverType,
 core::dimension2d(640, 480), 16, false, true);
 if (device == 0)
  return 1;
 video::IVideoDriver* driver = device->getVideoDriver();
 scene::ISceneManager* smgr = device->getSceneManager();


  我对从这个导入文件产生的发射光线颜色的效果并不满意。下列代码显示怎样实现这些步骤:


scene::IAnimatedMesh* mesh = smgr->getMesh("room.3ds");
smgr->getMeshManipulator()->makePlanarTextureMapping(
mesh->getMesh(0), 0.008f);
scene::ISceneNode* node = 0;
node = smgr->addAnimatedMeshSceneNode(mesh);
node->setMaterialTexture(0, driver->getTexture("wall.jpg"));
node->getMaterial(0).EmissiveColor.set(0,0,0,0);


  四、 水动画

  你将添加的第一个特殊的效果是水动画。为此,WaterSurfaceSceneNode导入一个造型文件并使之象水表面一样地波动。如果你让这个场景结点使用一种相当好的材质如MT_REFLECTION_2_LAYER,那么它看起来相当酷:


mesh = smgr->addHillPlaneMesh("myHill",
core::dimension2d(20,20),
core::dimension2d(40,40), 0, 0,
core::dimension2d(0,0),
core::dimension2d(10,10));
node = smgr->addWaterSurfaceSceneNode(mesh->getMesh(0),3,300,30);
node->setPosition(core::vector3df(0,7,0));
node->setMaterialTexture(0,driver->getTexture("water.jpg"));
node->setMaterialTexture(1,driver->getTexture("stones.jpg"));
node->setMaterialType(video::EMT_REFLECTION_2_LAYER);


  作为输入造型,你可以创建一个陡峭的平面造型,但是你也可以为此使用任何其它的造型。你甚至能重用room.3ds输入文件(它看上去确实很奇怪)。该实例还用一个普通的石头纹理模型来绘制所有另外的表面。

  五、透明的告示板和灯光

  第二个特殊的效果是很基本的但是非常有用:一个透明的告示板,伴之有一个动态的灯光。为产生这种效果,你只需要产生一个灯光场景结点,并让它四处飞行;而且,为了让它看起来更酷一些,可以把一个告示板场景结点依附到它上面:


//创建灯光
node = smgr->addLightSceneNode(0, core::vector3df(0,0,0),
video::SColorf(1.0f, 0.6f, 0.7f, 1.0f), 600.0f);
scene::ISceneNodeAnimator* anim = 0;
anim = smgr->createFlyCircleAnimator(core::vector3df(0,150,0),250.0f);
node->addAnimator(anim);
anim->drop();
// 把告示板依附到灯光
node = smgr->addBillboardSceneNode(node, core::dimension2d(50, 50));
node->setMaterialFlag(video::EMF_LIGHTING, false);
node->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
node->setMaterialTexture(0,driver->getTexture("particlewhite.bmp"));

 六、 粒子系统

  下面介绍的这个特别效果更有趣:一个粒子系统。在Irrlicht引擎中,粒子系统既是组件化的,也是可扩展的,但是仍然易于使用。你只需要简单地把粒子发射器放到一个粒子系统场景结点,这样以来粒子看上去没有产生源。这些发射器可以据需要进行灵活配置,并经常带有许多参数,如粒子方向,粒子数量,以及粒子颜色等。

  当然,发射器类型有区别(例如,一个点发射器能够使粒子从一个固定的点上发出粒子)。如果该引擎提供的粒子发射器还不能满足你的要求,你可以容易地创建你自己的发射器。这只需简单地从IParticleEmitter接口派生一个新类并使用setEmitter()方法把它依附到粒子系统上去即可。

  下一个实例将创建一个盒子粒子发射器。你可能已经猜出,它从一个跳跃的盒中随机生成粒子。由参数来定义盒子,粒子的方向,每秒产生粒子的最小和最大数目,颜色以及粒子的最小和最大生命周期。

  一个完全由发射器组成的粒子系统将是令人生厌的,因为缺乏真实感。因此,Irrlicht支持粒子影响器-它负责在粒子到处飞扬时予以修整。一旦添加到粒子系统上,它们就能模仿另外的更真实的效果,象重力或风。在本例中的粒子影响器只是简单地修改粒子的颜色来产生一种淡出效果。

  可能你已经猜出,粒子影响器是通过派生IParticleAffector接口实现的,然后通过使用addAffector()方法把它添加到粒子系统上去。在你为该粒子系统设置了一种好看的材质后,你就有了一个看上去相当酷的野外宿营火的效果。通过调整材质,纹理,粒子发射器,还有影响器参数,你能容易地创建烟雾,下雨,爆炸,下雪等效果:


scene::IParticleSystemSceneNode* ps = 0;
ps = smgr->addParticleSystemSceneNode(false);
ps->setPosition(core::vector3df(-70,60,40));
ps->setScale(core::vector3df(2,2,2));
ps->setParticleSize(core::dimension2d(20.0f, 10.0f));
scene::IParticleEmitter* em = ps->createBoxEmitter(
core::aabbox3d(-7,0,-7,7,1,7),
core::vector3df(0.0f,0.03f,0.0f),
80,100,
video::SColor(0,255,255,255), video::SColor(0,255,255,255),
800,2000);
ps->setEmitter(em);
em->drop();
scene::IParticleAffector* paf =ps->createFadeOutParticleAffector();
ps->addAffector(paf);
paf->drop();
ps->setMaterialFlag(video::EMF_LIGHTING, false);
ps->setMaterialTexture(0, driver->getTexture,"particle.bmp"));
ps->setMaterialType(video::EMT_TRANSPARENT_VERTEX_ALPHA);


  七、 影子投射

  最后但也不容忽视一个问题是,你需要为一个动画角色产生一个动态的影子。为此,你装载一个Quake2.md2模型文件并把它放到你的world上去。为了创建影子,你只需要调用方法addShadowVolumeSceneNode()。你可能通过调用ISceneManager::setShadowColor()来控制影子的颜色;注意,这仅是全局可调整的,并影响所有的影子。好,下面就是你的产生动态影子效果的代码:


mesh = smgr->getMesh("../../media/faerie.md2");
scene::IAnimatedMeshSceneNode* anode = 0;
anode = smgr->addAnimatedMeshSceneNode(mesh);
anode->setPosition(core::vector3df(-50,45,-60));
anode->setMD2Animation(scene::EMAT_STAND);
anode->setMaterialTexture(0, driver->getTexture("../../media/Faerie5.BMP"));
anode->addShadowVolumeSceneNode();
smgr->setShadowColor(video::SColor(220,0,0,0));


  八、 游戏循环

  最后,你能进入由device->run()方法控制的游戏循环。该循环将不断运行,直到通过获取一个关闭窗口事件(例如在Windows操作系统下的ALT-F4击键)来退出设备。你必须在一个beginScene()和endScene()命令对之间绘制每样东西。beginScene()用指定的一种颜色清屏,如果需要的话,可以同时清除深度缓冲区。然后你就可以让场景管理器和GUI环境来绘制它们的内容。随着调用endScene(),每一样东西都被绘制到屏幕上去。在本例中,你还可以动态地在标题栏上显示帧每秒(FPS)数,这对于严肃的游戏开发者是十分重要的事情:


scene::ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS();
camera->setPosition(core::vector3df(-50,50,-150));
int lastFPS = -1;
while(device->run())
{
 driver->beginScene(true, true, 0);
 smgr->drawAll();
 driver->endScene();
 int fps = driver->getFPS();
 if (lastFPS != fps)
 {
  core::stringw str = L"Campfire FX example [";
  str += driver->getName();
  str += "] FPS:";
  str += fps;
  device->setWindowCaption(str.c_str());
  lastFPS = fps;
 }
}
device->drop();


  结束循环后,你必须删除先前用createDevice()方法创建的Irrlicht设备。通过使用Irrlicht引擎,你应该删除所有你用以’create’开头的方法或函数创建的所有对象。你可以通过简单地调用device->drop()来删除该设备对象。

  九、你可能喜欢的Irrlicht插件

  正如在前面所介绍的,Irrlicht有一群勤奋的独立开发人员并为之产生了大量的插件,也用之开发了相当多的游戏。这些开发者中提出的许多的改进被再次集成到Irrlicht的随后的发行版本中。下面我列举其中的几个例子,我想这会吸引许多颇有前程的开发者感兴趣:

  ·OCTTools,是一套用于Irrlicht的工具,由Murphy McCauley所创建,用于操作OCT文件相关的:输出器,加载器,甚至更多。

  ·ICE(Irrlicht通用引擎)是一个开发框架,它提供了一个工程的轮廓实现,从而加快了新工程的开发。

  ·MIM,由Murphy McCauley所创建,是一个非常有用的基于XML的文件格式,可用于Irrlicht的加载器,转换器及其各种工具。

  ·My3D是一个开发工具包,它能够使你把来自于各种3D包(3DStudio MAX,Giles,等等)中的灯光贴图场景直接输出到Irrlicht中。

  ·Dusty引擎允许程序员创建"任务"-这些"任务"可以完成程序员想做的任何事情。之后,这些任务被添加到一棵普通的任务树上去,而每个任务可以有它们希望数目的孩子任务。任务"组"允许游戏设计者在一棵完整的树上执行普通的操作,例如暂停,继续或破坏等。

  ·Irrlicht RPG(Erring Light)是一个3D 绕行走游戏引擎,最初是针对RPG类游戏开发的。

  ·2D 图像和精灵类组成了一个很有用的库,它扩展了Irrlicht的2D能力。

  ·Zenprogramming站点,提供第一个针对Irrlicht的非正式的外部地形生成器,此处也提供很多相关的教程。

 一、 简介

  ClanLib是一个主要针对游戏开发者的跨平台C++框架。尽管API主要为游戏开发设计,你照样可以容易地使用ClanLib来开发一个科学的3D可视化工具或多媒体应用程序(例如Gecko多媒体系统)。

  ClanLib拥有各种API-2D和3D图形,声音,网络,I/O,输入,GUI以及资源管理。它还提供透明的OpenGL支持,因此你可以使用本机OpenGL命令而让ClanLib处理依赖于操作系统的窗口管理和其它一切事情。ClanLib通过DirectX或简单的Direct Media Layer(一平台独立的多媒体库)生成2D图形。ClanLib游戏主页上列举了约50多个开发非常成功的游戏,包括以2D和3D形式完成的难题、策略以及射手类游戏。例如,Asteroid Arena(见图1)使用了ClanLib和OpenGL技术,实现了胜人一筹的经典街机游戏。



图1.Asteroid Arena屏幕快照


  ClanLib可以工作在Windows,Linux和MacOS操作系统之上,并且提供源码级的zip或tar文件支持。Windows开发者可以使用微软Visual Studio,Borland C++或者MinGW(小型GNU for Windows)编译器和环境。第三方的对于Ruby和Perl语言的绑定支持也是可用的。可选的特效程序包括一个Lua插件(流行的小脚本编程语言)和FreeType(一个免费的TrueType字体库)。

  二、 ClanLib特征集

  在具体使用API之前,让我们看一下ClanLib的主要特征:

  ·基本跨平台运行时刻库(GUI,多线程,文件I/O,等等)

  ·基于模板的C++信号/槽库(类型安全的回调/代理)

  ·综合的资源管理

  ·声音混合器支持。WAV文件,Ogg Vorbis,以及由MikMod库(MOD,S3M,XM,等等)支持的任何类型文件

  ·文档对象模型(DOM)XML分析器支持

  ·高级2D图形API,支持OpenGL,DirectX和SDL作为着色目标

  ·高性能的批量着色引擎,当用OpenGL着色2D时

  ·2D碰撞检测

  ·2D精灵动画支持

  ·高度可定制的GUI框架

  ·从低级到高级的网络库接口

  三、 ClanLib基本的游戏模型

  现在,让我们仔细分析一下ClanLib API模型。我发现最好的教程是一个完全自解释的示例程序。具体地,让我们分析一下Luke Worth的盒子游戏,这是一个有两个玩家的纸和铅笔游戏(见图2)。这个盒子游戏包含一些格子点,在任意两点间玩家都可以画线。谁用最后一条线画成一个封装的矩形,谁就得一分,并进入到下一轮中。



图2.一个进行中的盒子游戏,得分情况是蓝8/红3


  我特意使程序的main函数尽可能简短,这样我们可能集中注意力于高亮处的"游戏循环":


1 #include <iostream>
2 #include <ClanLib/application.h>
3 #include <ClanLib/core.h>
4 #include <ClanLib/display.h>
5 #include <ClanLib/gl.h>
6 #include <ClanLib/sound.h>
7 #include <ClanLib/vorbis.h>
8
9 const int boardsize = 6, spacing = 50, border = 20;
10 const int numsquares = int(pow(float(boardsize – 1), 2));
11
12 enum coloursquare { off, blue, red };
13 struct cursor {
14  int x, y;
15  bool vert;
16 };
17
18 class Boxes: public CL_ClanApplication {
19  bool ver[boardsize][boardsize - 1];
20  bool hor[boardsize - 1][boardsize];
21  coloursquare squares[boardsize - 1][boardsize - 1];
22  bool redturn;
23  bool fullup;
24  cursor curs;
25
26  void inputHandler(const CL_InputEvent &i);
27  bool findsquares(void);
28  inline int numaroundsquare(int x, int y);
29  void init();
30  void drawBoard();
31  void endOfGame();
32
33 public:
34  virtual int Boxes::main(int, char **);
35 } app;
36
37 using namespace std;
40
41 int Boxes::main(int, char **)
42 {
43  int winsize = spacing * (boardsize – 1) + border * 2;
44  try {
45   Boxes::init();
46   while (!CL_Keyboard::get_keycode(CL_KEY_ESCAPE)) {
47    Boxes::drawBoard();
48    if (fullup) break;
49     CL_System::keep_alive(20);
50   }
51   Boxes::endOfGame();
52
53   CL_SetupVorbis::deinit();
54   CL_SetupSound::deinit();
55   CL_SetupGL::deinit();
56   CL_SetupDisplay::deinit();
57   CL_SetupCore::deinit();
58  }
59  catch (CL_Error err) {
60   std::cout << "Exception caught: "<< err.message.c_str() << std::endl;
61  }
62
63  return 0;
64 }


  关于这个应用程序,应注意的第一事情是main()函数(见行41)并不是一个最顶层的函数,而是嵌入到一个从CL_ClanApplication派生的对象中。该对象封装了不少难以避免的平台依赖性-这可能包含一个传统的::main()实现(例如在Win32应用程序中必须使用WinMain())。

  而且还应注意,事实上所有的可执行的代码(行43-58)被封装在一个try{}/catch{}异常处理器块中。如果需要的话,ClanLib将引发异常,你可以重启一游戏,等等。基本上,所有的游戏逻辑包含在init(),drawBoard(),endOfGame()和inputHandler()这几个方法中。如果board不再移动(fullup==true),则退出游戏循环(行48)。CL_System::keep_alive()更新所有的输入和系统事件(象关闭窗口或者移动它)。这在老式的Win16 API ::Yield()或者Linux上的sleep()中将会释放CPU周期。


66 void Boxes::init()
67 {
68  CL_SetupCore::init();
69  CL_SetupDisplay::init();
70  CL_SetupGL::init();
71  CL_SetupSound::init();
72  CL_SetupVorbis::init();
73
74  CL_DisplayWindow window("Boxes", winsize, winsize);
75  CL_SoundOutput output(44100); //选择44Khz采样
76
77  CL_Surface *cursimg = new CL_Surface("cursor.tga");
78  cursimg->set_alignment(origin_center);
79  CL_Surface *redpict = new CL_Surface("handtransp.tga");
80  redpict->set_alignment(origin_center);
81  redpict->set_scale(float(spacing)/float(redpict->get_width()),
82  float(spacing)/float(redpict->get_height()));
83  CL_Surface *bluepict = new CL_Surface("circlehandtransp.tga");
84  bluepict->set_alignment(origin_center);
85  bluepict->set_scale(float(spacing) / float(bluepict->get_width()),
86  float(spacing) / float(bluepict->get_height()));
87


  这里的init()方法完成大部分的游戏初始化工作。当然,在此需要ClanLib子系统以用于处理图形和声音(行68-72),然后构建一个窗口用于显示所有的图形(行75)。

  CL_Surface(行77-87)是一个2D位图类,用于绘制光标,用蓝色填充的方格和用红色填充的方格。

  TGA文件是一种位图文件格式。ClanLib有一个集成的PNG库,因此它可以读写最流行的位图文件格式化。

  下一步,你必须把板子初始化成一个空状态(行87-103)并执行类似的其它的清理工作以实现新的游戏计数器。


89
90 redturn = true;
91 curs.vert = false;
92 fullup = false;
93 curs.x = curs.y = 1;
94
95 srand(CL_System::get_time()); //启动随机数字生成器
96
97 for (int x = 0; x < boardsize – 1; x++) {
98  for (int y = 0; y < boardsize; y++)
99  hor[x][y] = ver[y][x] = false;
100
101  for (int y = 0; y < boardsize – 1; y++)
102   squares[x][y] = off;
103
104


  ClanLib的一个特别突出的方面是它避开传统型应用于许多框架中的回调模型,而引入了"信号和槽"模型。这种模型广泛应用于Boost C++库中,并在QT中得到实现。信号代表具有多个目标的回调函数,又在一些类似的系统中称作"出版者"或者"事件"。信号被连接到一些槽上,它们是回调函数接收器(也称作事件目标或者订户),当信号被"发出"时即被调用。信号具有类型安全的优点,它们避开了在传统型的框架中的不可避免的cast操作。

  信号和槽被统一管理。在信号和槽中(或者更准确些说是,作为槽的一部分出现的对象)跟踪所有的连接,并当任何其一被破坏时能够自动地断开信号/槽连接。这能够使用户建立信号/槽连接而不需要花费多大的代价来管理那些连接以及所有包含于其中的对象的生命周期。在行105中,你只要捕获所有的键击("down")事件并确保使用了你自己的inputHandler()(见行168-216)。


105 CL_Slot keypress =
CL_Keyboard::sig_key_down().connect(this,
&Boxes::inputHandler);


  现在,你将开始初始化程序的音乐部分。首先,你用一个.wav格式的("binary")音乐文件装载一个CL_SoundBuffer,然后准备一个会话句柄以为玩游戏之用。下一步,你应用一个淡入淡出过滤器来异步地调整音量-在五秒(行 108-112)内把音量从零变化到最大音量的百分之六十。


106 CL_SoundBuffer *music = new CL_SoundBuffer("linemusic.ogg");
107 CL_SoundBuffer_Session session = music->prepare();
108 CL_FadeFilter *fade = new CL_FadeFilter(0.0f);
109 session.add_filter(fade);
110 session.set_looping(true);
111 session.play();
112 fade->fade_to_volume(0.6f, 5000);
113 }


  drawBoard()方法绘制线段所在的点画格子图案,如,每个玩家赢得的红色的西红柿和蓝色的矢车菊框出的方格,还有模仿的光标。而最重要的代码行是第165行。CL_Display::flip()交换前后台缓冲区。后台缓冲区是在该帧中你绘制所有图形的地方,而前台缓冲区是显示在屏幕上的内容。


115 void Boxes::drawBoard()
116 {
117  CL_Display::clear(redturn ? CL_Color::red : CL_Color::blue);
118  CL_Display::fill_rect(CL_Rect(border/2, border/2,
119    winsize – border/2, winsize – border/2),CL_Color::black);
120
121  //画方框
122  for (int x = 0; x < boardsize – 1; x++)
123   for (int y = 0; y < boardsize – 1; y++) {
124    if (squares[x][y] == red) {
125     CL_Display::fill_rect(CL_Rect(x * spacing + border,y * spacing + border, x * spacing + border +
        spacing,
127       y * spacing + border + spacing),CL_Gradient(CL_Color::red,
128       CL_Color::red, CL_Color::tomato, CL_Color::tomato));
129     redpict->draw(x * spacing + border + spacing / 2,
130        y * spacing + border + spacing / 2);
131    }
132    else if (squares[x][y] == blue) {
133      CL_Display::fill_rect(CL_Rect(x * spacing + border,
134      y * spacing + border,x * spacing + border +spacing,
135        y * spacing + border +spacing),CL_Gradient(CL_Color::blue,
136        CL_Color::blue, CL_Color::cornflowerblue,CL_Color::cornflowerblue));
137      bluepict->draw(x * spacing + border + spacing / 2,y * spacing + border + spacing / 2);
139    }
140   }
141
142   //画线
143   for (int x = 0; x < boardsize; x++) {
144    for (int y = 0; y < boardsize – 1; y++) {
145     if (ver[x][y]) CL_Display::draw_line(x * spacing + border,
146       y * spacing + border,x * spacing + border,
147       y * spacing + border+ spacing,CL_Color::yellow);
148     if (hor[y][x]) CL_Display::draw_line(y * spacing + border,
149       x * spacing + border,y * spacing + border+ spacing,x * spacing + border,CL_Color::yellow);
151     }
152   }
153
154   //画格子
155   for (int x = 0; x < boardsize; x++)
156    for (int y = 0; y < boardsize; y++)
157     CL_Display::draw_rect(CL_Rect(x * spacing + border,
158       y * spacing + border,x * spacing + border + 2,159 y * spacing + border + 2),CL_Color::white);
160
161     //画光标
162     if (curs.vert) cursimg->draw((curs.x – 1) * spacing + border,int((curs.y – 0.5) * spacing + border));
163     else cursimg->draw(int((curs.x – 0.5) * spacing + border),(curs.y – 1) * spacing + border);
164
165      CL_Display::flip();
166    }


  你安装的inputHandler()函数用于观察在行105的按键信号。这个函数负责处理细节问题-把键击变成游戏运动,还有最重要的空格或者回车键-用于指示当前玩家的一个选择(行200-210)。然后,你要检查一下是否已完成了一个"方形"并把控制返回到原来的玩家。


168 void Boxes::inputHandler(const CL_InputEvent &i)
169 {
170  if (redturn) {
171   switch(i.id) {
172    case CL_KEY_LEFT:
173    case CL_KEY_G:
174     if (curs.x > 1) curs.x–;
175     break;
176    case CL_KEY_RIGHT:
177    case CL_KEY_J:
178     if (curs.x < boardsize) curs.x++;
179     break;
180    case CL_KEY_UP:
181    case CL_KEY_Y:
182     if (!curs.vert && curs.y > 1) {
183      curs.y–;
184      curs.vert = !curs.vert;
185     }
186     else if (curs.vert) curs.vert = false;
187     break;
188    case CL_KEY_DOWN:
189    case CL_KEY_H:
190     if (curs.vert && curs.y < boardsize) {
191      curs.y++;
192      curs.vert = !curs.vert;
193     }
194     else if (!curs.vert) curs.vert = true;
195     break;
196    }
197    if (curs.x == boardsize && !curs.vert) curs.x–;
198    if (curs.y == boardsize && curs.vert)
      curs.vert = false;
199
200    if (i.id == CL_KEY_SPACE || i.id == CL_KEY_ENTER) {
201     if (curs.vert) {
202      if (!ver[curs.x-1][curs.y-1]) {
203       ver[curs.x-1][curs.y-1] = true;
204       if (!findsquares()) redturn = !redturn;
205      }
206    }
207    else {
208     if (!hor[curs.x-1][curs.y-1]) {
209      hor[curs.x-1][curs.y-1] = true;
210      if (!findsquares()) redturn = !redturn;
211     }
212    }
213   }
214  }
215 }


  最后,由endOfGame()方法计算最后的得分。记住游戏还没有结束,直到板子满了为止(见行48)或者某人通过按下ESC键(见行46)退出。最后,你用大约1秒的时间把音量淡出到0。


217 void Boxes::endOfGame()
218 {
219  // 计数得分
220  int redscore, bluescore;
221  redscore = bluescore = 0;
222  for (int x = 0; x < boardsize – 1; x++)
223   for (int y = 0; y < boardsize – 1; y++) {
224    if (squares[x][y] == red) redscore++;
225    else if (squares[x][y] == blue) bluescore++;
226   }
227
228  cout << "Red: " << redscore << "\nBlue: " << bluescore << endl;
229  if (bluescore != redscore)
230   cout << (bluescore > redscore ? "Blue" : "Red") << " player wins\n";
231  else cout << "It was a tie\n";
232
233  if (fullup) {
234   fade->fade_to_volume(0.0f, 1000);
235   CL_System::sleep(1000);
236  }
237 }
2005年11月01日

1 【 C 语言吧 · 问题资料大全 】 
 注意:
1.这里会尽量搜集所有问题,定时更新,有问题请先查阅这里.
2.为方便查阅,请不要在此问问题,此类贴将被删除,不另通知.
3.希望这里成为大家学习 C 的好帮手.

2 ■■■■■■■____本吧相关____■■■■■■■ 
★本吧推荐的几个C编译器:http://post.baidu.com/f?kz=2769360
◆本吧代码格式编辑器:http://post.baidu.com/f?kz=9364381
◆本吧代码中显示?:http://post.baidu.com/f?kz=5054984


4 ■■■■■■■____学习资料____■■■■■■■ 
◆搜索答案与提问的秘诀:http://post.baidu.com/f?kz=6602487
◆C 语言学习看什么书:http://post.baidu.com/f?kz=5728013
◆如何学好c语言:http://post.baidu.com/f?kz=8642778 
◆学 C 容易出错的地方: http://post.baidu.com/f?kz=4275539 
◆C程序易犯错误:http://post.baidu.com/f?kz=12428951
◆几种排序算法:http://post.baidu.com/f?kz=5633380
◆几个经典字符串Hash函数:http://post.baidu.com/f?kz=5662514 
◆100,000素数表:http://post.baidu.com/f?kz=5663116
◆C99 的语言新特性:http://post.baidu.com/f?kz=4784366
◆深入理解C语言指针的奥秘:http://post.baidu.com/f?kz=5932198
◆ISO-c99标准英文全文(pdf格式):http://www.nirvani.net/docs/
◆C 的文件操作:http://post.baidu.com/f?kz=5877273
◆字符屏幕函数:http://post.baidu.com/f?kz=6001621
◆C的运算符:http://post.baidu.com/f?kz=8512815
◆图形函数:http://post.baidu.com/f?kz=8183509
◆C语言图形编程–图形文本: http://post.baidu.com/f?kz=8948371
◆《C++语法》–函数重载:http://post.baidu.com/f?kz=6469598
◆Lcc-win32中文使用说明:http://post.baidu.com/f?kz=7355798
◆LCC-Win32编译程序的内存泄露漏洞:http://post.baidu.com/f?kz=11166343
◆标准C中各种类型数值范围:http://post.baidu.com/f?kz=6762862
◆C/C++开发常用工具:http://post.baidu.com/f?kz=7321315
◆免费开源的C/C++编译系统:http://post.baidu.com/f?kz=6847647
◆编译器OpenWatcomC/C++: http://post.baidu.com/f?kz=10055042
◆简单的编译器:http://post.baidu.com/f?kz=7057280
◆键盘各键对应的键值:http://post.baidu.com/f?kz=8005045
◆关于农历的知识:http://post.baidu.com/f?kz=8652667
◆万年历__公历农历转换:http://post.baidu.com/f?kz=11142578
◆经典C源程序100例:http://post.baidu.com/f?kz=8618367
◆时钟的驻留程序:http://post.baidu.com/f?kz=10822377
◆数据结构暨若干经典问题和算法:http://post.baidu.com/f?kz=10922856
◆LIUXUY 磁盘系统源程序:http://post.baidu.com/f?kz=12973347 
◆RLE压缩:http://post.baidu.com/f?kz=12592570
◆快速排序:http://post.baidu.com/f?kz=12262349
◆全排列的递归算法:http://post.baidu.com/f?kz=12248706
◆KMP字符串搜索算法:http://post.baidu.com/f?kz=12143581
◆C高效编程四招:http://post.baidu.com/f?kz=13192245
◆无栈非递归二叉树遍历:http://post.baidu.com/f?kz=12394188
◆跟我学NETBSD内核源码:http://post.baidu.com/f?kz=12201581
◆Linux源码:http://post.baidu.com/f?kz=12692827 

 

5 ■■■■■■■____简单应用____■■■■■■■ 
◆怎样用c写游戏:http://post.baidu.com/f?kz=5417859
◆本吧游戏编程相关:http://post.baidu.com/f?kz=11867577
◆简单的病毒程序:http://post.baidu.com/f?kz=5558054
◆迷宫游戏:http://post.baidu.com/f?kz=5781985
◆QQ好友管理程序:http://post.baidu.com/f?kz=5071518 
◆销售监控程序:http://post.baidu.com/f?kz=6352894
◆简单的仓储管理系统:http://post.baidu.com/f?kz=7316990
◆简单的电话储存系统:http://post.baidu.com/f?kz=7257980
◆模拟电子时钟的程序:http://post.baidu.com/f?kz=7519467
◆计算任意一天是星期几:http://post.baidu.com/f?kz=8329211
◆打印任意月份日历:http://post.baidu.com/f?kz=8880741
◆分类记事本:http://post.baidu.com/f?kz=8154266 
◆TC2编程图片处理利器:http://post.baidu.com/f?kz=11163580 
◆printf写的游戏:http://post.baidu.com/f?kz=11162185
◆人机对战扑克游戏:http://post.baidu.com/f?kz=11366672
◆俄罗斯方块:http://post.baidu.com/f?kz=12145169 
◆文本加密例程:http://post.baidu.com/f?kz=11968123 
◆破解BIOS密码:http://post.baidu.com/f?kz=12230804


6 ■■■■■■■____习题作业____■■■■■■■ 
◆C++编一个成绩管理课件:http://post.baidu.com/f?kz=3889433
◆一圈人报数问题问题:http://post.baidu.com/f?kz=3833415
◆用c编一个分数计算器程序 :http://post.baidu.com/f?kz=3831746
◆命令行计算器:http://post.baidu.com/f?kz=8465088
◆学生管理系统:http://post.baidu.com/f?kz=5806073
◆打印九九表:http://post.baidu.com/f?kz=6059385 
◆水仙花数问题:http://post.baidu.com/f?kz=11510377
◆乘法表:http://post.baidu.com/f?kz=11267637
◆学生成绩管理系统:http://post.baidu.com/f?kz=5972675 
◆学生成绩管理程序(链表):http://post.baidu.com/f?kz=6598859 
◆打印2004年日历:http://post.baidu.com/f?kz=6469868
◆八皇后问题:http://post.baidu.com/f?kz=4456709
◆八皇后问题(do熊版):http://post.baidu.com/f?kz=8513222
◆汉诺塔问题:http://post.baidu.com/f?kz=7402377
◆统计不同字符个数:http://post.baidu.com/f?kz=7394163
◆统计单词数行数:http://post.baidu.com/f?kz=8211273
◆查找替换字符串并统计次数:http://post.baidu.com/f?kz=7335032 
◆杨辉三角:http://post.baidu.com/f?kz=9299150
◆矩阵转置:http://post.baidu.com/f?kz=9882271
◆输出用*组成的三角形:http://post.baidu.com/f?kz=8465901 
◆输出一个菱形:http://post.baidu.com/f?kz=11879706
◆手把手教你写猜数游戏:http://post.baidu.com/f?kz=11205156 
◆求PI:http://post.baidu.com/f?kz=14832207
◆十个学生的成绩统计:http://post.baidu.com/f?kz=14787745
◆某一天是这一年的第几天:http://post.baidu.com/f?kz=13772082 

 
7 ■■■■■■■____函数用法____■■■■■■■ 
◆abort函数简介:http://post.baidu.com/f?kz=8610596
◆getchar与putchar:http://post.baidu.com/f?kz=7820387
◆scanf 讨论:http://post.baidu.com/f?kz=5305522 
◆C语言输入输出讨论:http://post.baidu.com/f?kz=7586045 
◆时间函数clock():http://post.baidu.com/f?kz=7867779
◆在规定时段自动关机:http://post.baidu.com/f?kz=7132544
◆getch防止程序运行一闪而过:http://post.baidu.com/f?kz=7360819
◆MALLOC FREE问题:http://post.baidu.com/f?kz=4826280
◆一个连接两个字符串的函数:http://post.baidu.com/f?kz=7958354
◆同一行回显输入字符:http://post.baidu.com/f?kz=5272169
◆整点报时问题:http://post.baidu.com/f?kz=7307517 
◆c语言中随机数字应用: http://post.baidu.com/f?kz=3065339
◆怎样获取文件大小:http://post.baidu.com/f?kz=4189689
◆程序得到自已的当前名称:http://post.baidu.com/f?kz=5993842
◆怎么使用清屏:http://post.baidu.com/f?kz=6087210
◆bioskey这个函数怎么用:http://post.baidu.com/f?kz=15272841


8 ■■■■■■■____数学相关____■■■■■■■ 
◆狐狸找兔子:http://post.baidu.com/f?kz=8729392
◆猴子吃桃:http://post.baidu.com/f?kz=8448705
◆猜数游戏: http://post.baidu.com/f?kz=4215306 
◆二分法解方程:http://post.baidu.com/f?kz=7663955
◆同余在数值计算中的应用:http://post.baidu.com/f?kz=6924343
◆多项式相乘:http://post.baidu.com/f?kz=7441950
◆韩信点兵:http://post.baidu.com/f?kz=8203887
◆用C编写的四叶玫瑰曲线:http://post.baidu.com/f?kz=7853365
◆求n个数的最大公约数:http://post.baidu.com/f?kz=6848849
◆寻找完数:http://post.baidu.com/f?kz=11892209 
◆蛇形矩阵算法:http://post.baidu.com/f?kz=12433353
◆圆周率计算:http://post.baidu.com/f?kz=12394522
◆小球称重:http://post.baidu.com/f?kz=12648561
◆整数分解:http://post.baidu.com/f?kz=12582169 

9 ■■■■■■■____技术点滴____■■■■■■■ 
 ◆@ 的读音:http://post.baidu.com/f?kz=12952426
◆gcc跨平台问题:http://post.baidu.com/f?kz=5371847
◆LL型运算(64位): http://post.baidu.com/f?kz=5248192
◆正方形动画下落(不用清屏):http://post.baidu.com/f?kz=5467099 
◆大数运算的免费库libgmp:http://www.swox.com/gmp/ 
◆大数运算10000以内阶乘:http://post.baidu.com/f?kz=2780897
◆素数搜索算法程序:http://post.baidu.com/f?kz=6221885
◆bmp文件读写: http://post.baidu.com/f?kz=5236264
◆用C显示PCX文件:http://post.baidu.com/f?kz=6059067
◆汇编语言嵌入C 程序段:http://post.baidu.com/f?kz=4038342 
◆宏替换问题: http://post.baidu.com/f?kz=5327438 
◆宏定义带括号的问题:http://post.baidu.com/f?kz=10001502
◆大下标数组操作:http://post.baidu.com/f?kz=4201196
◆关于数组越界:http://post.baidu.com/f?kz=13204257
◆整数数组初始化赋值:http://post.baidu.com/f?kz=8513573
◆字符数组与字符串:http://post.baidu.com/f?kz=7608560
◆字符串数组下标问题:http://post.baidu.com/f?kz=11253296
◆数组的编译和执行的效率:http://post.baidu.com/f?kz=6243463
◆C写的隐式输入密码:http://post.baidu.com/f?kz=5438409 
◆又一个隐式密码输入:http://post.baidu.com/f?kz=11321460
◆C 的文本文件行操作:http://post.baidu.com/f?kz=4303211
◆链表的数据插入:http://post.baidu.com/f?kz=5851022
◆链表的合并:http://post.baidu.com/f?kz=5790984 
◆双向链表的插入和删除:http://post.baidu.com/f?kz=6352819 
◆谭C例题链表插入删除操作:http://post.baidu.com/f?kz=2286853
◆进制转换的几个函数:http://post.baidu.com/f?kz=5799330
◆十进制转二进制的一种方法:http://post.baidu.com/f?kz=4201639 
◆鼠标事件处理:http://post.baidu.com/f?kz=5469795
◆两个鼠标函数库:http://post.baidu.com/f?kz=6952040
◆鼠标器的程序信息:http://post.baidu.com/f?kz=2728110
◆关于鼠标程序:http://post.baidu.com/f?kz=12299687
◆中断号调用:http://post.baidu.com/f?kz=5469596 
◆几个中断的资料:http://post.baidu.com/f?kz=7462907
◆C语言接口与实现:http://post.baidu.com/f?kz=5979383 
◆C语言的串口通信:http://post.baidu.com/f?kz=8467750
◆怎样理解递归对栈的应用:http://post.baidu.com/f?kz=9882580
◆递归求组合定值:http://post.baidu.com/f?kz=5824841 
◆递归求元素和:http://post.baidu.com/f?kz=6380549
◆全排列:http://post.baidu.com/f?kz=7336872
◆擂台赛–求1000000素数:http://post.baidu.com/f?kz=6221885
◆擂台赛–大数运算:http://post.baidu.com/f?kz=6279195
◆字符串编辑时光标的控制:http://post.baidu.com/f?kz=6230397
◆在指定位置输出字符:http://post.baidu.com/f?kz=12059970
◆光盘启动菜单的问题:http://post.baidu.com/f?kz=6093143
◆NOIP复赛文件名大小写问题:http://post.baidu.com/f?kz=6270393
◆程序的带参运行 http://post.baidu.com/f?kz=3545215
◆关于main 的参数:http://post.baidu.com/f?kz=7535662
◆main 函数返回值问题:http://post.baidu.com/f?kz=11161360 
◆一个主函数参数问题:http://post.baidu.com/f?kz=9495695
◆函数参数中引用别名问题:http://post.baidu.com/f?kz=9299395
◆函数调用返回多个值:http://post.baidu.com/f?kz=5536004
◆函数中参数调用的问题:http://post.baidu.com/f?kz=6035295 
◆函数参数按址传送的问题:http://post.baidu.com/f?kz=5558840
◆assiss谈函数中参数调用的问题:http://post.baidu.com/f?kz=6035295
◆assiss谈宏定义及条件编译:http://post.baidu.com/f?kz=6193259
◆输出格式*号控制符:http://post.baidu.com/f?kz=7149608
◆输出格式的空格问题:http://post.baidu.com/f?kz=5147346
◆输出格式中#代表什么:http://post.baidu.com/f?kz=11993035 
◆程序代码的上传与下载:http://post.baidu.com/f?kz=6566895 
◆++i和i++的问题:http://post.baidu.com/f?kz=6712609 
◆ASCII字符输出问题:http://post.baidu.com/f?kz=6771317 
◆Fdisk的源程序:http://post.baidu.com/f?kz=6790551
◆正则替换:http://post.baidu.com/f?kz=6775253
◆自我保护—-“程序自杀”:http://post.baidu.com/f?kz=6682850
◆编辑框问题:http://post.baidu.com/f?kz=6953560 
◆西文环境下显示汉字:http://post.baidu.com/f?kz=2144548
◆读取汉字库中的汉字:http://post.baidu.com/f?kz=7359496 
◆三种排序:http://post.baidu.com/f?kz=6769579
◆far 远程指针简述:http://post.baidu.com/f?kz=7742757
◆指针悬空的危险错误:http://post.baidu.com/f?kz=7130531
◆指针作参数的问题:http://post.baidu.com/f?kz=6922977
◆判断字符串是否回文: http://post.baidu.com/f?kz=7624806
◆编程中遇到的小困扰:http://post.baidu.com/f?kz=8002835
◆UNIX下面怎么编译C程序:http://post.baidu.com/f?kz=8046512
◆C如何调用其他程序:http://post.baidu.com/f?kz=8355281
◆C游戏中控制键盘:http://post.baidu.com/f?kz=4902906 
◆C语言控制应用:http://post.baidu.com/f?kz=8399030
◆C项目工程(unix下)注意事项:http://post.baidu.com/f?kz=9087043
◆make file(unix下)易错地方:http://post.baidu.com/f?kz=9362741 
◆补码问题:http://post.baidu.com/f?kz=11189164
◆DOS仿多线程问题:http://post.baidu.com/f?kz=11553963
◆栈的push和pop操作:http://post.baidu.com/f?kz=10820691
◆头文件如何理解:http://post.baidu.com/f?kz=7566720
◆编程解IQ测试:http://post.baidu.com/f?kz=11359491
◆逻辑破案的编程处理:http://post.baidu.com/f?kz=11283225
◆VC++中浮点数与字符串转化:http://post.baidu.com/f?kz=12061615
◆为何VC++6.0无法运行某些C程序:http://post.baidu.com/f?kz=11123696 
◆逆序输出:http://post.baidu.com/f?kz=11484218 
◆scanf:floating points format not linked:http://post.baidu.com/f?kz=12999839
◆c输出到打印机:http://post.baidu.com/f?kz=12482547 
◆源代码-自我产生的程序:http://post.baidu.com/f?kz=14261864
◆bioskey(1)的问题:http://post.baidu.com/f?kz=14651579
 http://post.baidu.com/f?kz=14536875

 

10 ■■■■■■■____参考资源____■■■■■■■ 
◆上网资源指南:http://www.jily.net/sites/46.htm http://post.baidu.com/f?kz=6682454
◆各类源码大仓库:http://www.moon-soft.com/download/index.htm 
◆电脑视频教程:http://post.baidu.com/f?kz=12061285
◆几个C语言资源:http://post.baidu.com/f?kz=12432670 
◆资料下载站: www.crazydown.cn 
◆c数据结构资料:http://post.baidu.com/f?kz=11992292
◆严蔚敏数据结构:http://post.baidu.com/f?kz=11965339
◆visual studio 6.0下载:http://post.baidu.com/f?kz=10632625
◆GSM/GPRS的C语言代码:http://post.baidu.com/f?kz=10385426 
◆C语言的视频教程:http://post.baidu.com/f?kz=12944443
一个有关 FreeBSD 的网站:http://post.baidu.com/f?kz=14828500

11■■■■■■■____资料下载____■■■■■■■
◆本优秀开发类图书下载:http://post.baidu.com/f?kz=13689264
◆《CSDN开发高手》2004电子版下载:http://post.baidu.com/f?kz=13689380
◆《程序员》2004电子版下载:http://post.baidu.com/f?kz=13689304 
◆C/C++ 编译器评测及下载:http://post.baidu.com/f?kz=13392270 


 

12■■■■■■■____未分类资料____■■■■■■■
阶乘级数:http://post.baidu.com/f?kz=14601711
三点坐标求三角形面积:http://post.baidu.com/f?kz=14679236
局域网聊天的程序:http://post.baidu.com/f?kz=14413292
C编写简单的窗口界面:http://post.baidu.com/f?kz=3388604
哈希表实例:http://post.baidu.com/f?kz=13350908
c51系列仿真器:http://post.baidu.com/f?kz=14332652 
PC 底层系统编程:http://post.baidu.com/f?kz=14071636
自写trim函数:http://post.baidu.com/f?kz=14226973
C中怎么制作函数库:http://post.baidu.com/f?kz=14190938
谭C程序设计(第二版)下载:http://post.baidu.com/f?kz=14275061 
统计学生成绩(8pm版)http://post.baidu.com/f?kz=14787745
有关 FreeBSD 的网站:http://post.baidu.com/f?kz=14828500
求pi的怪异程式:http://post.baidu.com/f?kz=14832207
三点坐标输出三角形面积:http://post.baidu.com/f?kz=14679236
关于bioskey(1)的具体问题:http://post.baidu.com/f?kz=14651579
一个画图程序:http://post.baidu.com/f?kz=15165712
CHAR*转为LONG:http://post.baidu.com/f?kz=15054773
LONG毫秒转为CHAR*分秒的函数:http://post.baidu.com/f?kz=15140414
算法——贪心法:http://post.baidu.com/f?kz=15321632
合并排序:http://post.baidu.com/f?kz=15330626
射击游戏:http://post.baidu.com/f?kz=15417872
怎样编一个螺旋数组:http://post.baidu.com/f?kz=15384659
源代码-自我产生的程序:http://post.baidu.com/f?kz=14261864
整数的立方表为两个整数的平方差:http://post.baidu.com/f?kz=15121585
游戏的时候怎么控制键盘:http://post.baidu.com/f?kz=4902906
c语言写的直线插补程序:http://post.baidu.com/f?kz=15742793
射击10发90环有多少种可能:http://post.baidu.com/f?kz=15878635
游戏编程中一些方法:http://post.baidu.com/f?kz=15882627
根据等级来发津贴:http://post.baidu.com/f?kz=15806820
俄罗斯方块:http://post.baidu.com/f?kz=16028030
100元买100只鸡:http://post.baidu.com/f?kz=14920285
随机生成的迷宫游戏:http://post.baidu.com/f?kz=11120413
高效编程的的四大绝招:http://post.baidu.com/f?kz=13192245
模拟自由落体运动的一个程序:http://post.baidu.com/f?kz=16206926
理解递归程序http://post.baidu.com/f?kz=16158704
不可移植的指针赋值的问题http://post.baidu.com/f?kz=16054476
数据结构http://post.baidu.com/f?kz=10922856
brainf*ck 语言的解释器http://post.baidu.com/f?kz=15998145
C 编程最佳实践:http://post.baidu.com/f?kz=16444079
10000以内的阶乘(do熊版)http://post.baidu.com/f?kz=16446002
数据结构直接插入排序http://post.baidu.com/f?kz=16645029
希尔排序http://post.baidu.com/f?kz=16645114
COS X曲线http://post.baidu.com/f?kz=16633244
定义文件指针问题http://post.baidu.com/f?kz=16708924
算法:整数划分问题http://post.baidu.com/f?kz=12582169
辗转法求2个数最大公约数http://post.baidu.com/f?kz=16965299 
指针越界问题:http://post.baidu.com/f?kz=16918216
碰撞检测http://post.baidu.com/f?kz=17102077
n阶螺旋方阵的问题http://post.baidu.com/f?kz=16452171
经典冒泡程序http://post.baidu.com/f?kz=16644795
打印格式控制http://post.baidu.com/f?kz=16581988
屏幕中间输出杨辉三角http://post.baidu.com/f?kz=12054232
万年历http://post.baidu.com/f?kz=17197492
输出字符的代码值ASCII码http://post.baidu.com/f?kz=17247524
多项式相乘http://post.baidu.com/f?kz=17157736
线性表操作:http://post.baidu.com/f?kz=15110265
算24:http://post.baidu.com/f?kz=17336190
用压栈输出自己的源码http://post.baidu.com/f?kz=14831974
float二维数组漏洞http://post.baidu.com/f?kz=17268765
图标在屏幕上移动http://post.baidu.com/f?kz=7047855
判断闰年http://post.baidu.com/f?kz=16137615
简述递归和迭代http://post.baidu.com/f?kz=17476611
字符变整数http://post.baidu.com/f?kz=17455565
运动的人http://post.baidu.com/f?kz=17577824
递归的hanoi 程序段http://post.baidu.com/f?kz=17590991
六籽弹小游戏http://post.baidu.com/f?kz=17637684
无栈非递归二叉树遍历http://post.baidu.com/f?kz=12394188
“&”怎么读http://post.baidu.com/f?kz=17725981
C语言之内存使用引出的问题http://post.baidu.com/f?kz=17730316
又一报数出圈http://post.baidu.com/f?kz=17789700
字母替换式密码http://post.baidu.com/f?kz=17372067
图书管理系统http://post.baidu.com/f?kz=17682927
如何写一个c程序删除文件http://post.baidu.com/f?kz=17331973
用指针反序输出字符串http://post.baidu.com/f?kz=17749459
求最大公约数和最小公倍数http://post.baidu.com/f?kz=15965236
3个正整数的最小公倍数http://post.baidu.com/f?kz=17747110
输出菱形http://post.baidu.com/f?kz=16465168
又一输出菱形http://post.baidu.com/f?kz=11879706
用C编写的音乐程序http://post.baidu.com/f?kz=17662844 
 变化的同心圆http://post.baidu.com/f?kz=18056765 
  学生成绩系统(C++版) http://post.baidu.com/f?kz=6640660 
  给一行文字设置密码加解密:http://post.baidu.com/f?kz=20205053
飞机从下面移动到上面:http://post.baidu.com/f?kz=20221475
课程设计题目集合:http://post.baidu.com/f?kz=1751265
歌德巴赫猜想:http://post.baidu.com/f?kz=20011131
希尔排序:http://post.baidu.com/f?kz=16645114
用C语言制作按钮:http://post.baidu.com/f?kz=20168832
点阵字体的通讯录程序:http://post.baidu.com/f?kz=19904438
以邻接矩阵为存储结构的图的基本操作:http://post.baidu.com/f?kz=19526370
字符串排序:http://post.baidu.com/f?kz=18780534
贪吃蛇:http://post.baidu.com/f?kz=19624003
排列问题:http://post.baidu.com/f?kz=19326688
屏幕中间输出“杨辉三角”http://post.baidu.com/f?kz=9299150
求阶乘:http://post.baidu.com/f?kz=19314067
输出三数最大值:http://post.baidu.com/f?kz=19023855
黑白棋:http://post.baidu.com/f?kz=19624178
2叉树用C语言编写http://post.baidu.com/f?kz=19336835
交换两个变量的几种方法http://post.baidu.com/f?kz=2752640
C语言中不定参数的实现http://post.baidu.com/f?kz=19232306
四叶玫瑰曲线的旋转动画:http://post.baidu.com/f?kz=18656216
 n×n围墙方阵http://post.baidu.com/f?kz=21173057
约瑟夫环循环链表 http://post.baidu.com/f?kz=13750140
免费电子书籍下载站点大全http://post.baidu.com/f?kz=22692641
通讯录 http://post.baidu.com/f?kz=22775724
图书馆管理系统http://post.baidu.com/f?kz=22241061
万年历http://post.baidu.com/f?kz=20493202
飞机票订购系统http://post.baidu.com/f?kz=21296518
判断回文字符串:http://post.baidu.com/f?kz=21815959
教小学生算术:http://post.baidu.com/f?kz=22322599
一个级数:http://post.baidu.com/f?kz=16708781
四则运算计算器http://post.baidu.com/f?kz=22279483
公历农历转换程序:http://post.baidu.com/f?kz=11142578
二进制文件与文本文件的转换:http://post.baidu.com/f?kz=21576218
时间函数clock() http://post.baidu.com/f?kz=7867779
二叉树排序树上实现学生信息管理http://post.baidu.com/f?kz=21436251
又一个计算器:http://post.baidu.com/f?kz=24283347
杀进程程序PK.exe :http://post.baidu.com/f?kz=24460615
利用堆栈计算数学表达式:http://post.baidu.com/f?kz=24602649 

2005年10月20日

  有人开玩笑似地建议,当一个C++程序员被问及怎样实现一个给定的任务时,他/她首先应该提供一个列表-一打或更多潜在的解决方案,然后他/她就应从每一个细节角度构划每一种方案中可能存在的问题。Visual C++ 2005,通过绑定C++/CLI语言,引入了泛型的概念,提供给C++程序员最强有力的语言装备库。Brent Rector在2004年6月一篇优秀的文章中阐述了.NET泛型和C++模板之间的区别。这篇文章的主要观点之一就是,尽管泛型和模板有非常强的语法上的相似性,但它们在实现上还是非常不同的,而且它们不存在任何内嵌的匹配性。如果你没有读过Brent的文章,那么本文就值得你一读,因为本文正是基于他的那篇文章中的一些思想。

  模板和泛型之间的区别导致了问题的出现:每个想转到.NET平台的C++程序员都会无休止地问:我应该选择哪种技术?对于已用C++进行多年开发的程序员来说,特别是已熟练使用Visual C++格式的老手,可能已经知道了这个答案-两种技术都提供了优秀的特性,任何其一都不是另外一个的超集;因而,正确的技术选择应该依赖于给定的具体任务。简言之,哪一种技术都不会包罗你面临的所有情形。这与一个已困扰Visual C++程序员多年的问题一样:Win32或MFC或ATL或WTL(或控制台)到底选择谁?COM或C风格的DLL到底选择谁?使用#import还是CcomPtr更好?

  二、 平衡已有的C++技能以更利于.NET开发

  以前,Visual C++已经提供了一些技术以实现STL集合与其它技术的联合工作。同一个称为CComEnumOnSTL的模板类一同发行的活动模板库就允许一个Visual Basic 客户端使用For Each来枚举由一个C++ COM服务器维护的一个STL集合的内容。尽管这种级别的集成狭隘,但是它在某些情况下确实是有用的。基于同样的思想,STL.NET提供给C++开发者一种标准STL库的扩展。该扩展允许在一个C++/CLI装配集内部使用的STL集合可以暴露给其它的.NET装配集,如泛型集合。

  STL.NET集合提供了与标准STL集合类相同的接口,因此,对于熟悉标准STL集合类和算法的C++开发者来说,没有太陡的学习曲线。如果安装了Visual C++ 2005的话,STL.NET相应的头文件位于文件夹Cliext中-它在主文件夹Vcinclude下。因此,要使用一个STL.NET集合-例如矢量-就需要包括头文件<cliext/vector>,而不是标准的<vector>。STL.NET集合包含于cliext命名空间中,而不是包含于被标准STL集合所使用的std命名空间中。

  作者提示:在深入分析STL.NET之前,让我们来看一下如何得到它,并且了解一下微软的发行计划。STL.NET与Visual Studio.NET 2005 Beta 2的Visual Studio团队系统发行版本一同发行,并且它也会包含在Visual Studio.NET 2005的CTP(社团技术预览)发行中。该C++小组仍在努力工作力图使得STL.NET更易于使用并进一步改进它的性能,而且STL.NET很有可能不会与Visual Studio 2005.NET的厂家发行版本(RTM)一同发行。在某种RTM发行之后,STL.NET将能够通过Web进行下载。由于还会对STL.NET进行重大改进,本文不会深入分析STL.NET集合。而只是站在一个较高层次上分析一下为什么STL.NET非常有用。

  三、 桥接模板与泛型

  实现模板和泛型的桥接是相当困难的一项任务。模板仅是C++中特有的概念-它在以前的编译时代并不存在,而泛型是一个.NET概念-它被编译的装配集所维护而且能够用于所有的.NET语言中。STL.NET中使用的解决方案是以C++模板类(它也是实现泛型ICollection接口的.NET参考类型)方式来实现集合。STL和STL.NET矢量的声明就说明了这种设计决策:


//STL矢量声明
template<class _Ty,class _Ax = allocator<_Ty> > class vector;
//STL.NET矢量声明
template<typename _Value_t> ref class vector : Generic::ICollection<_Value_t>


  这两种声明存在几个关键的区别,除了泛型接口的实现(STL.NET矢量无法指定一个分配器而且在需要时它只是简单地通过调用gcnew来分配一新元素)之外。STL.NET集合用ref关键字进行声明-这意味着它们是.NET参考类型,因而可以把它们在.NET托管堆上进行分配。

  四、 使用STL.NET

  除了在声明一个STL.NET集合的方式上存在一些差别之外-该差别与C++/CLI和标准C++之间的语法区别也存在联系,使用一个STL.NET集合的方式与使用同样的STL集合的方式完全一致。下列控制台应用程序声明了一个对象矢量,然后它把几个不同类型的元素添加到该集合上:


#include "stdafx.h"
#include <cliext/vector>
using namespace System;
using namespace cliext;
int main(array<System::String ^> ^args)
{
 vector<Object^>^ v = gcnew vector<Object^>;
 v->push_back(nullptr); //第一个元素为空
 v->push_back(gcnew Object()); //第二个元素是一个普通对象
 v->push_back(1); //第三个元素是被装箱的整型
 v->push_back("Element Four"); //第四个元素是一个字符串
 return 0;
}


  该代码说明了STL.NET的一个主要优点:一套非常熟悉的集合类。STL.NET的另一个主要优点是它能够使用集合并针对它使用两种不同的编程模型。前面示例中使用了充满各种类型数据的集合,这样以来我们就可能通过使用STL find算法来判断整数1的存在:


//用作STL集合
bool containsOneSTL = find( v->begin( ), v->end( ), 1 ) != v->end();
当然,你也可以选用.NET泛型ICollection接口来实现相同的逻辑:
//用作.NET集合
bool containsOnedotNet = v->Contains(1);


  在STL和.NET两类算法中使用相同的集合类的能力-不需复制内容或提供桥接函数-允许C++/CLI程序员选择最好的函数和库以完成任何集合操作任务。

  STL.NET的最后一个主要优点在于,它能够无缝地暴露STL.NET集合给用C#或VB.NET创建的.NET装配集。由于STL.NET集合实现泛型ICollection接口,所以并没有丢失类型安全。因为STL.NET集合使用托管内存来存储集合的元素,所以在与暴露STL.NET集合的C++/CLI装配集进行交互时,不存在性能或代码安全方面的损失问题。

  结束语:C++程序员到.NET王国的门票

  STL.NET代表了Visual C++产品中的一个重要的部分。STL.NET允许C++程序员平衡他们已有的技能和经验,并使用一种具有相当威力的集合和算法库,而不需要与快速发展的.NET世界切断联系。

  非常希望STL.NET仅仅是新技术的开始-让C++在保留该它光辉的历史的同时成为第一流的.NET语言

2005年10月18日

编写高效简洁的C语言代码,是许多软件工程师追求的目标。本文就是针对编程工作中的一些体会和经验做相关的阐述。

第一招:以空间换时间

计算机程序中最大的矛盾是空间和时间的矛盾,那么,从这个角度出发逆向思维来考虑程序的效率问题,我们就有了解决问题的第1招–以空间换时间。比如说字符串的赋值:

方法A:通常的办法


#define LEN 32
char string1 [LEN];
memset (string1,0,LEN);
strcpy (string1,"This is a example!!");



方法B:


const char string2[LEN] ="This is a example!";
char * cp;
cp = string2 ;



使用的时候可以直接用指针来操作。

从上面的例子可以看出,A和B的效率是不能比的。在同样的存储空间下,B直接使用指针就可以操作了,而A需要调用两个字符函数才能完成。B的缺点在于灵活性没有A好。在需要频繁更改一个字符串内容的时候,A具有更好的灵活性;如果采用方法B,则需要预存许多字符串,虽然占用了大量的内存,但是获得了程序执行的高效率。

如果系统的实时性要求很高,内存还有一些,那我推荐你使用该招数。该招数的变招–使用宏函数而不是函数。举例如下:

方法C:


#define bwMCDR2_ADDRESS 4
#define bsMCDR2_ADDRESS 17
int BIT_MASK(int __bf)
{
 return ((1U << (bw ## __bf)) - 1)
<< (bs ## __bf);
}
void SET_BITS(int __dst,
int __bf, int __val)
{
 __dst = ((__dst) & ~(BIT_MASK(__bf))) |
\
 (((__val) << (bs ## __bf))
& (BIT_MASK(__bf))))
}

SET_BITS(MCDR2, MCDR2_ADDRESS,
RegisterNumber);



方法D:


#define bwMCDR2_ADDRESS 4
#define bsMCDR2_ADDRESS 17
#define bmMCDR2_ADDRESS BIT_MASK(MCDR2_ADDRESS)
#define BIT_MASK(__bf)
(((1U << (bw ## __bf)) - 1)
<< (bs ## __bf))
#define SET_BITS(__dst, __bf, __val)
\
((__dst) = ((__dst) & ~(BIT_MASK(__bf)))
| \
(((__val) << (bs ## __bf))
& (BIT_MASK(__bf))))

SET_BITS(MCDR2, MCDR2_ADDRESS,
RegisterNumber);



函数和宏函数的区别就在于,宏函数占用了大量的空间,而函数占用了时间。大家要知道的是,函数调用是要使用系统的栈来保存数据的,如果编译器里有栈检查选项,一般在函数的头会嵌入一些汇编语句对当前栈进行检查;同时,CPU也要在函数调用时保存和恢复当前的现场,进行压栈和弹栈操作,所以,函数调用需要一些CPU时间。

而宏函数不存在这个问题。宏函数仅仅作为预先写好的代码嵌入到当前程序,不会产生函数调用,所以仅仅是占用了空间,在频繁调用同一个宏函数的时候,该现象尤其突出。

D方法是我看到的最好的置位操作函数,是ARM公司源码的一部分,在短短的三行内实现了很多功能,几乎涵盖了所有的位操作功能。C方法是其变体,其中滋味还需大家仔细体会。
第二招:数学方法解决问题

现在我们演绎高效C语言编写的第二招–采用数学方法来解决问题。数学是计算机之母,没有数学的依据和基础,就没有计算机的发展,所以在编写程序的时候,采用一些数学方法会对程序的执行效率有数量级的提高。举例如下,求 1~100的和。

方法E:






int I , j;
for (I = 1 ;I<=100; I ++)
{
 j += I;
}



方法F


int I;
I = (100 * (1+100)) / 2



这个例子是我印象最深的一个数学用例,是我的计算机启蒙老师考我的。当时我只有小学三年级,可惜我当时不知道用公式 N×(N+1)/ 2 来解决这个问题。方法E循环了100次才解决问题,也就是说最少用了100个赋值,100个判断,200个加法(I和j);而方法F仅仅用了1个加法,1次乘法,1次除法。效果自然不言而喻。所以,现在我在编程序的时候,更多的是动脑筋找规律,最大限度地发挥数学的威力来提高程序运行的效率。

第三招:使用位操作

实现高效的C语言编写的第三招——使用位操作。减少除法和取模的运算。在计算机程序中数据的位是可以操作的最小数据单位,理论上可以用"位运算"来完成所有的运算和操作。一般的位操作是用来控制硬件的,或者做数据变换使用,但是,灵活的位操作可以有效地提高程序运行的效率。举例如下:

方法G


int I,J;
I = 257 /8;
J = 456 % 32;



方法H


int I,J;
I = 257 >>3;
J = 456 - (456 >> 4 << 4);



在字面上好像H比G麻烦了好多,但是,仔细查看产生的汇编代码就会明白,方法G调用了基本的取模函数和除法函数,既有函数调用,还有很多汇编代码和寄存器参与运算;而方法H则仅仅是几句相关的汇编,代码更简洁,效率更高。当然,由于编译器的不同,可能效率的差距不大,但是,以我目前遇到的MS C ,ARM C 来看,效率的差距还是不小。相关汇编代码就不在这里列举了。

运用这招需要注意的是,因为CPU的不同而产生的问题。比如说,在PC上用这招编写的程序,并在PC上调试通过,在移植到一个16位机平台上的时候,可能会产生代码隐患。所以只有在一定技术进阶的基础下才可以使用这招。

第四招:汇编嵌入

高效C语言编程的必杀技,第四招——嵌入汇编。"在熟悉汇编语言的人眼里,C语言编写的程序都是垃圾"。这种说法虽然偏激了一些,但是却有它的道理。汇编语言是效率最高的计算机语言,但是,不可能靠着它来写一个操作系统吧?所以,为了获得程序的高效率,我们只好采用变通的方法–嵌入汇编,混合编程。举例如下,将数组一赋值给数组二,要求每一字节都相符。


char string1[1024],string2[1024];



方法I


int I;
for (I =0 ;I<1024;I++)
 *(string2 + I) = *(string1 + I)



方法J


#ifdef _PC_
int I;
for (I =0 ;I<1024;I++)
*(string2 + I) = *(string1 + I);
#else
#ifdef _ARM_
__asm
{
 MOV R0,string1
 MOV R1,string2
 MOV R2,#0
loop:
 LDMIA R0!, [R3-R11]
 STMIA R1!, [R3-R11]
 ADD R2,R2,#8
 CMP R2, #400
 BNE loop
}
#endif



方法I是最常见的方法,使用了1024次循环;方法J则根据平台不同做了区分,在ARM平台下,用嵌入汇编仅用128次循环就完成了同样的操作。这里有朋友会说,为什么不用标准的内存拷贝函数呢?这是因为在源数据里可能含有数据为0的字节,这样的话,标准库函数会提前结束而不会完成我们要求的操作。这个例程典型应用于LCD数据的拷贝过程。根据不同的CPU,熟练使用相应的嵌入汇编,可以大大提高程序执行的效率。

虽然是必杀技,但是如果轻易使用会付出惨重的代价。这是因为,使用了嵌入汇编,便限制了程序的可移植性,使程序在不同平台移植的过程中,卧虎藏龙,险象环生!同时该招数也与现代软件工程的思想相违背,只有在迫不得已的情况下才可以采用。

2005年07月25日

 

我在上篇中“盘点”了TCPL和D&E以及入门教程、高效和健壮编程、模板和泛型编程等方面共十几本C++好书。冬去春来,让我们继续C++书籍精彩之旅J 

标准库 

当我还在研究院工作时,与同院另外两家研究所合作开发过一个大型水利枢纽调度集成项目。我们三家软件系统之间都要相互通信。在调试通讯模块时,细心的客户(一名好学的系统管理员)发现对于同一通信规约的解释代码,我的不超过30行,而对方的则超过了150行且很难看懂。这位系统管理员很纳闷,我说大家编程风格和习惯不一样,我使用了标准库,而他使用了传统C编程风格以及他所习惯的另外一些技术。 

别误会!我绝无贬低这位合作伙伴的意思。事实上,我对那些真正有着深厚的C编程功力的程序员常常怀有钦佩之心。毕竟,C++能有今天的成功在很大程度上缘于它深深地植根于C。作为一名C++程序员,倘若不熟悉C++中的C,我往往会认为他的基本功是不扎实的,他的技术底气是不足的。 

不过话又说回来,C++是一种多范型(paradigm)编程语言,具体采用哪种编程风格,专业程序员应该知道视具体情况而定。作为一名经常需要在现场做即兴开发的项目负责人,为了短平快地解决当务之急,我习惯尽量采用现有的库(和组件)。效率(以及强健性)久经验证的C++标准库已经摆在那儿了,何乐而不用呢? 

Nicolai M. Josuttis, The C++ Standard Library: A Tutorial and Reference

《C++标准程序库:自修教程与参考手册》,华中科技大学出版社

这是一本百科全书式的C++标准库著作,是一本需要一再查阅的参考大全。它在完备性、细致性以及精确性方面都是无与伦比的。本书详细介绍了每一标准库组件的规格和用法,内容涵盖包括流和本地化在内的整个标准库而不仅仅是STL。正如本书副标题所示,它首先适合作为教程阅读,尔后又可用作参考手册。

浅显易懂的写作风格使得这本书非常易读。如果你希望学习标准库的用法并尽可能地发挥其潜能,那你必须拥有这本书。正如网络上所言,这本书不仅仅应该摆在你的书橱中,更应该放到你的电脑桌上。我向每一位职业C++程序员强烈推荐。

Angelika Langer, Klaus Kreft, Standard C++ IOStreams and Locales: Advanced Programmer’s Guide and Reference

《标准C++输入输出流与本地化》,人民邮电出版社

C++标准库由STL、流和本地化三部分构成。关于STL的书市面上已经有不少,但罕见流和本地化方面的专著。本书是这两个领域中最优秀的一本,迄今为止没有任何一本书比这一本更全面详尽地讨论了流和本地化。如果你不满足于停留在“会用”流库的层面,千万不要错过它。

2001年夏天,我草草翻阅过这本书的中文版,从内容到包装都给我留下了比较深刻的印象 — 不过负面的居多一些。2003年秋天,无意中得知某网络书店正以超低价格甩卖这本书的中译本,情不自禁,一阵唏嘘。

Scott Meyers, Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library

《Effective STL(影印版)》,中国电力出版社

读完Scott 的《Effective C++》和《More Effective C++》的中译本之后,我一直期待这本书的中文版。我从潘爱民先生的个人主页上了解到,他和他的合作伙伴似乎早已完成了这本书的翻译工作,可惜至今市面上仍不得见。幸运的是,我们可以看到它的原版。

本书是使用STL的程序员必读之作。在这本书中,Scott向我们讲述STL容器和算法的工作机制以及如何以最佳方式使用它们。和Scott的其他作品一样,这本书的写作风格清晰、精确,具有极佳的可读性。看过这本书以后,我想你也许会和我以及其他C++程序员一样产生这样的想法:Scott什么时候会写出一本“More Effective STL”?

关于STL,我还提醒你留心Matthew H. Austern的《Generic Programming and the STL: Using and Extending the C++ Standard Template Library》(《泛型编程与STL》,中国电力出版社)。这本书散发着浓厚的学院气息。Andrew Koenig和Barbara Moo在《Accelerated C++: Practical Programming by Example》一书末尾郑重推荐另外两本进阶好书(除了他们自己的《Ruminations on C++》外),其中一本是TCPL,另外一本就是本书!

网络编程

在网络编程时代,C++应该扮演着怎样的角色,让ACE(Adaptive Communications Environment)来告诉你。

Douglas C. Schmidt, Stephen D. Huston, C++ Network Programming, Volume 1: Mastering Complexity with ACE and Patterns

Douglas C. Schmidt, Stephen D. Huston, C++ Network Programming, Volume 2: Systematic Reuse with ACE and Frameworks

《C++网络编程,卷1:运用ACE和模式消除复杂性》,华中科技大学出版社

《C++网络编程,卷2:基于 ACE 和框架的系统化复用》,电子工业出版社

采用C++进行企业级网络编程,目前ACE(以及这两本书)是一个值得考虑的选择。ACE是一个面向对象、跨平台、开放源码的网络编程框架,目标在于构建高性能网络应用和中间件。Douglas是ACE的创始人,Stephen则已为ACE提供了数年的技术支持和顾问服务,两位都是ACE社群(是的,ACE的影响和实际应用的程度已经形成了一个社群)的专家。

ACE并不单单被大学和研究所追捧,它已经被成功地应用于世界上成千上万个商业应用中。在电信、宇航、医药和财经领域的网络系统中,ACE已经并继续发挥着重要的作用。如果你准备开发高性能通讯系统,你应该考虑考虑这一汇集世界顶尖专家智慧的成果。

除了使用C++面向对象设计技术和模板等高级语言特性外,ACE还运用了大量的模式。《C++网络编程》卷1和卷2并不仅仅教你关于ACE的方方面面,它还会教给你模式和通用框架设计等高级技术等。所以,作为一名中、高级C++程序员,即使你很少进行正儿八经的C++网络程序设计,阅读这两本书同样可以从中受益。

是的,并非所有网络应用都要使用Web服务器(以及其他应用服务器)和重量级组件模型,换个思路,它们或许也可以从轻量级的ACE组件中获益。

杂项

以下这几本书之所以被列入“杂项”单元,只是因为我没有考虑出更合适的归类方法,它们和上面的书籍一样,值得一读。

Bruce Eckel, Thinking in C++, Volume 1: Introduction to Standard C++ (2nd Edition)

Bruce Eckel, Thinking in C++, Volume 2: Practical Programming (Second Edition)

《C++编程思想(第2版)第1卷:标准C++导引》,机械工业出版社

《C++编程思想(英文版 第2版)》,机械工业出版社

《Thinking in C++》的第1版于1996年荣获“软件研发”杂志评选的图书震撼大奖。最新推出的第2版对内容进行了大幅改写和调整,以反映C++标准化带来的影响以及近几年面向对象领域最新研究和实践成果。“输入输入流”、“多重继承”、“异常处理”和“运行时类型识别”等高级主题连同C++标准化以后增加的一些内容则被放入第二卷中。Bruce是一名经验丰富的C++讲师和顾问,其培训和写作经验都是世界一流水准,他的作品比那些“玩票”的技术人员写的东西更能吸引读者。事实上,在同类图书中,对于大多数读者而言,这本书的可读性要超过TCPL《C++ Primer》顺带一提,访问作者的站点,你可以先睹第二卷的风采。

Andrew Koenig, Barbara E. Moo, Ruminations on C++: A Decade of Programming Insight and Experience

《C++沉思录》,人民邮电出版社

Andrew是世界上屈指可数的C++专家。这是一本关于C++编程思想和程序设计技术而非语言细节的著作。如果你已经具有一定的基础,这本书将教你在进行C++编程时应该怎样思考,应该如何表达解决方案。整本书技术表达透彻,文字通俗易懂。Bjarne这样评价这本书:本书遍布“C++是什么、C++能够做什么”的真知灼见。

Stanley B. Lippman, Inside The C++ Object Model

《深度探索C++对象模型》,华中科技大学出版社

《深度探索C++对象模型(影印版)》,中国电力出版社

从编译器的角度观察C++可以使你知其然并知其所以然。本书探讨了大量的C++面向对象程序设计的底层运作机制,包括构造函数、函数、临时对象、继承、虚拟、模板的实例化、异常处理、运行期类型识别等,另外还介绍了一些在实现C++对象模型过程中做出的权衡折衷。喜欢刨根问底的C++程序员不要错过这本书。

Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, Design Patterns: Elements of Reusable Object-Oriented software

《设计模式:可复用面向对象软件的基础》,机械工业出版社

《设计模式:可复用面向对象软件的基础(英文版)》,机械工业出版社

设计可复用的面向对象的软件,你需要掌握设计模式。本书并非专为C++程序员而写,但它采用了C++(以及Smalltalk)作为主要示例语言,C++程序员尤其易于从中受益。四位作者都是国际公认的面向对象软件领域专家,他们将面向对象软件的设计经验作为设计模式详细记录下来。这本书影响是如此深远,以至于四位作者以及本书都被昵称为GoF(Gang of Four)。本书学院气息浓厚,行文风格严谨简洁,虽然它不如某些讲解模式的书籍易读,但真正要精准地理解设计模式,本书是终极权威。学习设计模式,这本书需要一而再、再而三的咀嚼。顺带一句:请将设计模式化作开拓思维的钥匙,切莫成为封闭思维的枷锁。

还有一些C++好书值得一读,恕此处无法一一列出。例如John Lakos的著作《Large-Scale C++ Software Design》(《大规模C++程序设计》,中国电力出版社)和侯捷先生的《STL 源码剖析》(华中科技大学出版社)等。

《STL 源码剖析》是一本很有特色的书,但我认为它还可以更好。我个人期待侯捷先生自第一版发行以来经过对模板技术的沉淀和再思考之后,再写一本剖析得更深入、更透彻并且更全面的“第二版”。遗憾的是,侯捷先生在完成《C++ Templates: The Complete Guide》一书的翻译后似乎决定暂时告别模板、泛型编程和STL领域。

2004年3月31日补充:我目前最常查阅的两本参考书是《C++标准程序库》和《STL源码剖析》。当然了,这与我年内的写作计划有很大的关系。

使用C++成功开发大规模软件系统,不仅需要很好地理解大多数C++书籍中讲述的逻辑设计问题,更需要掌握《大规模C++程序设计》中讲述的物理设计技术。当然,这本书的确有点过时了,不过,如果你的精力和金钱都比较宽绰,买一本看看并无坏处。

至此,我想有必要声明一下,有一些(好)书没有得到推荐,主要原因如下:

  • 以上这些书已经足够多、足够好了。

  • 我不会推荐通过正常渠道很难购买到的书籍 — 不管是中文版还是英文版。

  • 作(译)者名气大小不影响我的推荐。我们是在看书,不是看人。

  • 我不会推荐我从来没有看过的书。我至少要看过其中的某个版本(包括电子档)。这个“看”,一般指“认真阅读”,不过有一些也只能算是“浏览”。

结语

作为一名普通技术写译者,我深知技术创作和翻译的艰辛(和快乐),并多多少少了解一些有关技术书籍创作、翻译、制作、出版以及市场推介背后的细节。今天,我不会再对一本看上去差强人意的图书信口开河。罗列同一本书的各种版本的用意只在于为你多提供一些信息,让你多一种选择。

在本文成文的后期,我给Bjarne写了一封信,请教如果他来写这篇文章会怎么写。他给了我简明扼要的建议。在肯定以上列出的绝大部分图书都是世界顶尖水平的C++著作的同时,Bjarne提醒我别忘了向专家级程序员推荐《The C++ Standard : Incorporating Technical Corrigendum No. 1》。这本书是 C++标准规范的“图书版”,Bjarne亲自为之作序。

Bjarne还友好地提醒我,在我的推荐列表中没有哪一本有助于C++程序员进行Windows编程 — 这正是我的本意。在这篇文章中,我只推荐、点评平台中立的C++著作(网络编程除外) — 和操作系统无关,和集成开发环境无关,我甚至幻想它们和编译器也无关。你可以根据业务开发需要,选读自己喜爱的领域相关的C++书籍。

说到“系统无关、平台中立”,我不由得想起了“抽象层”的概念。开发实际应用的C++程序员通常工作于特定操作系统、特定开发环境和特定业务领域之中,而对标准C++和C++标准库扎实而深刻的把握,无疑是你得以在不同的操作系统、不同的开发环境以及不同的业务领域之间纵横驰骋的“抽象”本钱。


 

C++是一门广泛用于工业软件研发的大型语言。它自身的复杂性和解决现实问题的能力,使其极具学术研究价值和工业价值。和C语言一样,C++已经在许多重要的领域大获成功。

 

然而,一个不可否认的现实是,在低阶程序设计领域,C++挤压着C同时也在承受着C的强烈反弹,而在高阶程序设计领域,Java和C#正在不断蚕食着C++的地盘。也许C++与C合为一体永远都是一个梦想,也许Java和C#的狂潮终将迫使C++回归本位 — 回到它有着根本性优势的开发领域:低级系统程序设计、高级大规模高性能应用设计、嵌入式程序设计以及数值科学计算等。果真如此,我认为这未尝不是一件好事。

 

C++吸引如此之多的智力投入,以至于这个领域的优秀作品,包括重量级的软件产品、程序库以及书籍等,数不胜数。文题“C++程序设计之四书五经”一个不太严格的含义是:C++程序设计之四书 ⅹ 五经。是的,在本文(及其下篇)中,我将分门别类推荐20多本C++好书,你可以根据自己的需要选读。

 

TCPL和D&E

 

TCPL和D&E分别是《The C++ Programming Language》和《The Design and Evolution of C++》的简称,均出自Bjarne Stroustrup之手。我将它们单列出来,首先是因为Bjarne是C++语言的创建者,然后是因为比“首先”那个原因更重要的原因:这两本书是C++领域毋庸置疑的杰作。说它们是C++语言圣经,并不为过。

 

Bjarne Stroustrup, The C++ Programming Language (Special 3rd Edition)

《C++程序设计语言(特别版)》,机械工业出版社

《C++程序设计语言(特别版)(英文影印版)》,高等教育出版社

 

迄今为止,TCPL是除了C++标准文献之外最权威的C++参考手册。和大多数人的看法不大一样,我认为Bjarne的文字语言并不逊色于他所创建的程序语言,至少我喜欢这种学院气息浓厚的作品。本书对C++语言的描述轮廓鲜明、直截了当。它从C++语言创建者的角度来观察C++,这是任何别的作者和书籍做不到的 — 没有任何人比Bjarne自己更清楚该怎么来使用C++。

 

馐且槐狙纤嗟闹鳎灾小⒏呒禖++开发人员为目标读者。如果你是一名有经验的C++程序员,需要了解更加本质的C++知识,本书正是为你而写。它不是那种让你看了会不断窃喜的小书,需要用心体会,反复咀嚼。在阅读过程中,请特别留心Bjarne先生强调了什么,又对什么一语带过。我个人比较喜欢这本书的第四部分“使用C++做设计”,这样的内容在类似的程序设计语言书籍中很难看到 — 我甚至认为Bjarne应该将这部分独立出来单独写一本书。

 

Bjarne Stroustrup, The Design and Evolution of C++

《C++语言的设计和演化》,机械工业出版社

《C++语言的设计和演化(英文版)》,机械工业出版社

 

D&E是一本关于C++语言设计原理、设计决策和设计哲学的专著。它清晰地回答了C++为什么会成为今天这个样子而没有变成另外一种语言。作为C++语言的创建者,Bjarne淋漓尽致地展示了他独到而深刻的见解。除了广受赞誉的语言特性外,Bjarne没有回避那些引起争议的甚至被拒绝的C++特性,他一一给出了逻辑严密、令人信服的解释。内容涵盖C++的史前时代、带类的C、C++的设计规则、标准化、库、内存管理、多重继承、模板等,对包括异常机制、运行时类型信息和名字空间在内的重要的新特性都分别进行了深入探讨。每一名C++程序员都应该可以从Bjarne的阐释中加深对手中这门语言的认识。

 

需要再次提醒的是,这两本书知识浓缩,信息量极大,请不要错过Bjarne每一句看似漫不经意的话。

 

入门教程

 

学习任何一门语言都需要一个从入门到精通、从新手到高手循序渐进的过程。不过,对于一个所谓的新手而言,究竟是一个完完全全的新手,还是一个熟悉某种别的语言的“新手”,甚至是在某种语言程序设计领域已经颇有建树的高手,很难一概而论?不同的C++新手需要不同的入门书籍。

 

Andrew Koenig, Barbara E. Moo, Accelerated C++: Practical Programming by Example

《Accelerated C++中文版》,中国电力出版社

 

和市面上大多数C++教程不同,本书不是从“C++中的C”开始讲解,而是始于地道的C++特性。从一开始就使用标准库来写程序,随着讲述的逐渐深入,又一一解释这些标准库组件所依赖的基础概念。另外,和其他C++教材不同的是,这本书以实例拉动语言和标准库的讲解,对后两者的讲解是为了给实例程序提供支持,而不是像绝大多数C++教材那样,例子只是用作演示语言特性和标准库用法的辅助工具。

 

作者在C++领域的编程实践、教育培训以及技术写作方面都是世界一流水准。我喜欢这种大量使用标准库和C++语言原生特性的清新的写作风格。在这本教材面前,几乎迄今为止的所有C++教材都黯然失色或显得过时。尽管这本教材也许对于国内的高校教育来说有些前卫,不过我仍然极力向我的同行们推荐。顺带一提,在Bjarne和我最近的一封通信里,他这样评价本书:对于有经验的程序员学习C++而言,这本书可能是世界上最好的一本。

 

Stanley B.Lippman, Josee Lajoie, C++ Primer (3rd Edition)

《C++ Primer (3RD)中文版》,中国电力出版社

 

这本书的名字多少有点让人误解。尽管作者声称这本书是为C++新手而写,但无论是它的厚度还是讲解的深度都暴露了似乎并非如此。也许说它是一本“从入门到精通”的C++教程会更合适一些。我个人认为它并不适合完全不懂C++的初学者 — 在阅读这本书之前,你至少应该先有那么一点C或C++的背景知识,或者至少要具有一些其他语言的编程经验。

 

尽管这本书省略了一些高级C++特性的讨论,但仍然可以称得上是迄今为止最全面的C++学习教程。事实上,如果一名C++初学者能够扎扎实实地读完本书并对照《C++ Primer Answer Book》完成全部习题的话,他的水平肯定可以进入职业C++程序员的行列。我个人认为,即使你已经拥有了TCPL,这本书依然有拥有的价值,因为在许多方面它比TCPL来得更详细、更易懂。

 

Stanley B. Lippman, Essential C++

《Essential C++中文版》,华中科技大学出版社

《Essential C++(影印版)》,中国电力出版社

 

可以不太严格地认为这本书是《C++ Primer》的精简版。本书一一讲述了C++中最具代表性的主题,包括过程式编程、泛型编程、基于对象编程、面向对象编程、模板编程以及异常处理等。Stanley将门槛调低到“具有其他语言程序设计经验”的C++新手所能接受的最基本的层次,使他们能够迅速开始使用C++编程而又免于阅读《C++ Primer》那样的大部头。它以实例引导学习,力图使读者在最短的时间内把握C++的精粹。

 

也许换一个人来概述C++编程范型(paradigm)的方方面面需要好几百页才能说清楚,但这本小书不可思议地做到了这一点。我个人非常喜欢这种满是技术、简明扼要并且“有话好好说”的书。这本书同样具有一个明显的风格:所有程序例子全部采用标准库组件,让人耳目一新。

 

以上三本书都不是为了完完全全的编程新手而写。完全的C++编程新手可以阅读Francis Glassborow的新书(尚未出版):《You Can Do It!: A Beginner’s Introduction to Computer Programming》。这也是Bjarne的推荐。Francis Glassborow是ACCU主席,多年来他对几乎每一本C++经典名著评头论足,他自己的这一本自然会引起C++社群的极大兴趣。

 

高效、健壮编程

 

两年前我在负责一个省级电力调度系统项目时编写了一个网关程序,它从SCADA系统获取电力实时信息。通讯接口采用了不常用的数据库直连方式(这个网关程序一端连接SQL Server 6.5,另一端连接Oralce 8.1.6)。由于实时测点近万,每次将全部取样更新或插入一遍显然是低效的。我在网关程序里建了一个内存库,获取到的数据首先在其中进行比较,然后决定是否更新物理数据库(同时还做了别的更复杂的事情……),从而在效率和资源占用两方面达到了预期效果。

 

这个程序一直运行得很好,但在离开现场之后的某一天,系统管理员打来电话,说大概因为网络故障等原因,有时这个网关程序会崩溃掉 — 它自己崩掉也就罢了,问题是它还会把Windows 2000 Advanced Server搞成“蓝屏”!坦白地说,我还从来没看过哪个非蓄意的程序有这个“能耐”。由于当时正忙于另外一个大项目,无法去现场调试,最后只有凭经验对内存库代码小心翼翼地封装以异常处理代码(同时也做了一些别的修改……)。这样,虽然没有彻底解决问题,但程序终究不再死得那么难看了。

 

在这儿讲这么一段花絮有什么意思呢(当初为那个可怕的bug朝思暮想时我可不认为这是一个“花絮”)?我想说的是,对于任何软件而言,离开强健,效率也就无从谈起。而对于C++程序员来说,也许编写一个高效的程序并不难,但要编写一个需要7 ⅹ 24小时持续运行的服务端软件就不是那么容易了,需要考虑许多因素,有时这些因素甚至远远超出C++语言和开发工具的本身。作为一名开发实际项目软件的程序员,并非非得自己碰钉子才能积累经验,只要我们足够虚心,别人的经验往往都是我们很好的借鉴。鉴于此,我推荐以下几本书供你选读,它们可以让你从强健和效率两方面受益(当然了,它们涵盖的内容远不限于异常处理J)。

 

Scott Meyers, Effective C++: 50 Specific Ways to Improve Your Programs and Design (2nd Edition)

Scott Meyers, More Effective C++: 35 New Ways to Improve Your Programs and Designs

《Effective C++中文版》,华中科技大学出版社

《More Effective C++中文版》,中国电力出版社

《Effective C++(影印版)》,中国电力出版社

 

如果说《Effective C++》主要讨论C++中一些相对基础的概念和技巧的话,那么《More Effective C++》则着重探讨了包括异常处理在内的一系列高级技术。与前者相比,后者具有两大主要区别:其一,它包含很多时新的标准C++的内容;第二,它讨论的主题倾向于“战略化”而非“战术化”,并且讨论得更深入、更彻底。尤其是对虚析构函数、智能指针、引用计数以及代理类(proxy classe)等技术和模式论述的深入程度,让人很难想象是出现于这样的一本小书之中。

 

游刃有余的技术,高超的写作技巧,Scott无疑是世界上最优秀的C++技术作家之一。在简洁、清晰、易读等方面,这两本书都卓尔不群。总之,Scott提供的这85个可以改善编程技术和设计思维的方法,都是中、高级C++程序员必备的技能。我强烈推荐这两本书(实际上还有一本,稍后就会看到)。

 

Herb Sutter, Exceptional C++: 47 Engineering Puzzles, Programming Problems, and Solutions

Herb Sutter, More Exceptional C++: 40 New Engineering Puzzles, Programming Problems, and Solutions

《Exceptional C++中文版》,中国电力出版社

《More Exceptional C++中文版》,华中科技大学出版社

 

你自认为是一名C++语言专家吗?读一读ISO C++标准委员会秘书长的这两本书再回答。在这两本书中,Herb采用了“问答”的方式指导你学习C++语言特性。对于每一个专题,Herb首先合理地设想出你的疑问和困惑,接着又猜测出你十有八九是错误的解答,然后给你以指点并提出最佳解决方案,最后还归纳出解决类似问题的普适性原则。

 

这两本书是典型的深究C++语言细节的著作,很薄,但内容密集,远远超过Scott的那两本书,读起来很费脑筋 — 我个人认为它们要比Scott的书难懂得多。若要研习这薄薄的两本书所包含的知识,至少需要花费数月的时间!(在Scott的荐序中,他坦陈不止一次陷入GotW问题的陷阱,你应该知道这意味着什么)对于语言细节的深究有什么好处呢?尽管在大多数情况下,我们不必关心C++代码幕后的动作,然而当我们不得不关心时,这两本书可以为我们提供很好的线索,因为它们揭示了C++语言中微妙而又至关重要的东西。

 

Stephen C. Dewhurst, C++ Gotchas: Avoiding Common Problems in Coding and Design

《C++程序设计陷阱》,中国青年出版社

 

Stephen的理论素养和实践经验注定这是一本值得一读的好书。Stephen曾经是贝尔实验室中第一批C++使用者。他已经使用C++成功解决了包括编译器、证券交易、电子商务以及嵌入式系统等领域中的问题。本书汇集了作者来自开发一线的99条编程真知灼见,洞悉它们,你可以避免几乎所有常见的C++设计和编程问题。

 

我甚至认为,对于C++编程菜鸟而言,阅读这本书会比阅读Scott和Herb的书更能轻松而立竿见影地获得更大的提高。我个人很喜欢这本书的写作风格 — Stephen的许多观点看似极端却无可辩驳。当然了,这种自信(以及冷幽默)来自于作者深厚的技术素养,而非自大的偏执。

 

除了上面推荐的书籍外,Dov Bulka和 David Mayhew合著的《Efficient C++: Performance Programming Techniques》(《提高C++性能的编程技术》,清华大学出版社)也值得一看。这本超薄小书聚焦于高性能C++应用程序开发。两位作者都是IBM软件专家,都工作于对性能要求极高的系统构建领域,本书是他们的经验之谈。也有人不喜欢这本书,因为它花了不少的篇幅讲述和C++无关的东西,我却恰恰因为这一点而对这本书产生好感,正是这些东西让我开阔了眼界。

 

模板和泛型编程

 

模板和基于模板的泛型编程无疑是当今发展最活跃的C++程序设计技术。模板的第一个革命性的应用是STL,它将模板技术在泛型容器和算法领域的运用展现得淋漓尽致,而Boost、Loki等现代程序库则将模板技术的潜能不断发挥到极致。在模板和泛型编程领域,我推荐以下两本重量级著作:

 

David Vandevoorde, Nicolai M. Josuttis, C++ Templates: The Complete Guide

《C++ Templates全览(繁体版)》,台湾碁峰资讯股份有限公司

《C++ Templates全览(简体版)》,人民邮电出版社

 

有一种老套的赞美一本书的手法,大致是“没有看过这本书,你就怎么怎么地”,这里面往往夸张的成分居多。不过,倘若说“没有看过《C++ Templates: The Complete Guide》,你就不可能精通C++模板编程”,那么这个论断对于世界上绝大多数C++程序员来说是成立的。

 

这本书填补了C++模板书籍领域由来已久的空白。此前,上有《Modern C++ Design》这样的专注于模板高级编程技术和泛型模式的著作,下有《The C++ Standard Library》这样的针对特定模板框架和组件的使用指南。然而,假如对模板机制缺乏深入的理解,你就很难“上下”自如。鉴于此,我向每一位渴望透彻理解C++模板技术的朋友推荐这本书。

 

这本书在内地、台湾各有一个译本,但出自不同的译者之手。当你看到这篇文章时,两个译本应该都已经上市,对于读者来说当然也就多了一种选择。侯捷先生个人网站上开放了繁体译本大部分章节,不妨先睹为快。

 

Andrei Alexandrescu, Modern C++ Design: Generic Programming and Design Patterns Applied

《C++设计新思维:泛型编程与设计模式之应用》,华中科技大学出版社

《C++设计新思维(影印版)》,中国电力出版社

 

你自认为是C++模板编程高手吗?请看过獗臼樵倩卮餔 这是一本出自天才之手令人敬畏的杰作。泛型模式,无限延伸你的视野,足以挑战任何一名C++程序员的思维极限。

 

这本书共分为两大部分,第一部分讨论了 Loki程序库采用的基础技术以及一些高级语言特性,包括基于策略的类设计、模板局部特化、编译期断言、Typelist以及小型对象分配技术等。第二部分则着重介绍了Loki中的重要组件和泛型模式技术,包括泛化仿函数(Generalization Functor)、单件(Singleton)、智能指针、对象工厂(Object Factory)、抽象工厂(Abstract Factory)、访问者(Visitor)以及多方法(Multimethods)等。每一种技术都让人大开眼界,叹为观止。

 

在C++的学习方面,过犹不及往往成了不求甚解的借口。然而,面向对象并非C++的全部,模板和泛型编程亦占半壁江山。对于“严肃”的C++程序员而言,及时跟进这项早经例证的成功技术,不失为明智之举。

 

结语

 

这些著作是如此大名鼎鼎,也许根本不缺我一个推荐。然而,纵然C++程序员队伍的发展壮大速度不像其他更时髦的语言那样迅速,新人进总是多于旧人出。除了热忱地欢迎新人,我个人认为到了对C++书籍进行“盘点”的时候了,并且希望这样的“盘点”有益于感兴趣的读者。请保持耐心和宽厚。在下篇中,我将继续介绍标准库、网络编程以及其他方面的C++好书。有好书相伴,这个冬天不会冷。