﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-井泉-随笔分类-c软件工程</title><link>http://www.cppblog.com/zjj2816/category/2048.html</link><description /><language>zh-cn</language><lastBuildDate>Wed, 21 May 2008 21:26:59 GMT</lastBuildDate><pubDate>Wed, 21 May 2008 21:26:59 GMT</pubDate><ttl>60</ttl><item><title>xt uml</title><link>http://www.cppblog.com/zjj2816/archive/2007/11/20/36991.html</link><dc:creator>井泉</dc:creator><author>井泉</author><pubDate>Tue, 20 Nov 2007 01:49:00 GMT</pubDate><guid>http://www.cppblog.com/zjj2816/archive/2007/11/20/36991.html</guid><wfw:comment>http://www.cppblog.com/zjj2816/comments/36991.html</wfw:comment><comments>http://www.cppblog.com/zjj2816/archive/2007/11/20/36991.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zjj2816/comments/commentRss/36991.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zjj2816/services/trackbacks/36991.html</trackback:ping><description><![CDATA[分析一下UML类图中关联、聚合、组合三者的定义与关系。 @author:JZhang 06-11-27 E-mail:zhangjunhd@gmail.com Blog: <a href="http://blog.csdn.net/zhangjunhd/">http://blog.csdn.net/zhangjunhd/</a>&nbsp;&nbsp; 1．关联（Association）类之间的关联大多用来表示变量实例持有着对其他对象的引用。&nbsp;&nbsp; Phone拥有一个对Button的引用。&nbsp;&nbsp; 2．聚合（Aggregation）聚合是关联的一种特殊形式，它意味着一种整体/部分（whole/part）的关系。&nbsp;&nbsp; 一个整体不能是它自己的一部分。 因此 ，实例不能形成聚合回路，一个单独的对象不能够成为它自己的聚合，两个对象不能互相聚合，三个对象不能形成一个聚合环。下图为实例间的非法聚合循环： 3．组合（Composition）组合是一种特殊的聚合形式。&nbsp; UML对组合的定义： ①如同聚合，实例不能有循环。 ②一个被所有者实例不能同时有两个所有者。 ③所有者负责被组合的对象的生命周期的管理。如果所有者被销毁，被所有者也必须跟着一起被销毁，如果所有者被复制，被所有者也必须跟着一起被复制。<br><br><br><a href="http://blog.csdn.net/dylgsy/archive/2006/08/16/1076044.aspx">http://blog.csdn.net/dylgsy/archive/2006/08/16/1076044.aspx</a>&nbsp;&nbsp; UML类图关系全面剖析<br><br><a href="http://www.ibm.com/developerworks/cn/rational/r-shenzj/">http://www.ibm.com/developerworks/cn/rational/r-shenzj/</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 利用Rational Rose进行C++代码和数据库结构分析<br><br><br><br>vs2008 支持 c++ 类图了<br><br>
<img src ="http://www.cppblog.com/zjj2816/aggbug/36991.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zjj2816/" target="_blank">井泉</a> 2007-11-20 09:49 <a href="http://www.cppblog.com/zjj2816/archive/2007/11/20/36991.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>使用和生成库</title><link>http://www.cppblog.com/zjj2816/archive/2006/07/24/10403.html</link><dc:creator>井泉</dc:creator><author>井泉</author><pubDate>Mon, 24 Jul 2006 07:04:00 GMT</pubDate><guid>http://www.cppblog.com/zjj2816/archive/2006/07/24/10403.html</guid><wfw:comment>http://www.cppblog.com/zjj2816/comments/10403.html</wfw:comment><comments>http://www.cppblog.com/zjj2816/archive/2006/07/24/10403.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zjj2816/comments/commentRss/10403.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zjj2816/services/trackbacks/10403.html</trackback:ping><description><![CDATA[基本概念 <br /><br />库有动态与静态两种，动态通常用.so为后缀，静态用.a为后缀。例如：libhello.so libhello.a <br /><br />为了在同一系统中使用不同版本的库，可以在库文件名后加上版本号为后缀,例如： libhello.so.1.0,由于程序连接默认以.so为文件后缀名。所以为了使用这些库，通常使用建立符号连接的方式。 <br />ln -s libhello.so.1.0 libhello.so.1 <br />ln -s libhello.so.1 libhello.so <br /><br />使用库 <br /><br />当要使用静态的程序库时，连接器会找出程序所需的函数，然后将它们拷贝到执行文件，由于这种拷贝是完整的，所以一旦连接成功，静态程序库也就不再需要了。然而，对动态库而言，就不是这样。动态库会在执行程序内留下一个标记‘指明当程序执行时，首先必须载入这个库。由于动态库节省空间，linux下进行连接的缺省操作是首先连接动态库，也就是说，如果同时存在静态和动态库，不特别指定的话，将与动态库相连接。 <br />现在假设有一个叫hello的程序开发包，它提供一个静态库libhello.a 一个动态库libhello.so,一个头文件hello.h,头文件中提供sayhello()这个函数 <br />/* hello.h */ <br />void sayhello(); <br />另外还有一些说明文档。这一个典型的程序开发包结构 <br />1.与动态库连接 <br />linux默认的就是与动态库连接，下面这段程序testlib.c使用hello库中的sayhello()函数 <br /><br />/*testlib.c*/ <br />#include &lt;hello.h&gt; <br />#include &lt;stdio.h&gt; <br /><br />int main() <br />{ <br />sayhello(); <br />return 0; <br />} <br /><br />使用如下命令进行编译 <br />$gcc -c testlib.c -o testlib.o <br />用如下命令连接： <br />$gcc testlib.o -lhello -o testlib <br />在连接时要注意，假设libhello.o 和libhello.a都在缺省的库搜索路径下/usr/lib下，如果在其它位置要加上-L参数 <br />与与静态库连接麻烦一些，主要是参数问题。还是上面的例子： <br />$gcc testlib.o -o testlib -WI,-Bstatic -lhello <br />注：这个特别的"-WI，-Bstatic"参数，实际上是传给了连接器ld. <br />指示它与静态库连接，如果系统中只有静态库当然就不需要这个参数了。 <br />如果要和多个库相连接，而每个库的连接方式不一样，比如上面的程序既要和libhello进行静态连接，又要和libbye进行动态连接，其命令应为： <br />$gcc testlib.o -o testlib -WI,-Bstatic -lhello -WI,-Bdynamic -lbye <br />3.动态库的路径问题 <br />为了让执行程序顺利找到动态库，有三种方法： <br />(1)把库拷贝到/usr/lib和/lib目录下。 <br />(2)在LD_LIBRARY_PATH环境变量中加上库所在路径。例如动态库libhello.so在/home/ting/lib目录下，以bash为例，使用命令： <br />$export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ting/lib <br />(3) 修改/etc/ld.so.conf文件，把库所在的路径加到文件末尾，并执行ldconfig刷新。这样，加入的目录下的所有库文件都可见、 <br />4.查看库中的符号 <br />有时候可能需要查看一个库中到底有哪些函数，nm命令可以打印出库中的涉及到的所有符号。库既可以是静态的也可以是动态的。nm列出的符号有很多，常见的有三种，一种是在库中被调用，但并没有在库中定义(表明需要其他库支持)，用U表示；一种是库中定义的函数，用T表示，这是最常见的；另外一种是所谓的“弱态”符号，它们虽然在库中被定义，但是可能被其他库中的同名符号覆盖，用W表示。例如，假设开发者希望知道上央提到的hello库中是否定义了printf(): <br />$nm libhello.so |grep printf <br />U printf <br />U表示符号printf被引用，但是并没有在函数内定义，由此可以推断，要正常使用hello库，必须有其它库支持，再使用ldd命令查看hello依赖于哪些库： <br />$ldd hello <br />libc.so.6=&gt;/lib/libc.so.6(0x400la000) <br />/lib/ld-linux.so.2=&gt;/lib/ld-linux.so.2 (0x40000000) <br />从上面的结果可以继续查看printf最终在哪里被定义，有兴趣可以go on <br /><br /><br />生成库 <br /><br />第一步要把源代码编绎成目标代码。以下面的代码为例，生成上面用到的hello库： <br />/* hello.c */ <br />#include &lt;stdio.h&gt; <br />void sayhello() <br />{ <br />printf("hello,world\n"); <br />} <br />用gcc编绎该文件，在编绎时可以使用任何全法的编绎参数，例如-g加入调试代码等： <br />gcc -c hello.c -o hello.o <br /><br />1.连接成静态库 <br />连接成静态库使用ar命令，其实ar是archive的意思 <br />$ar cqs libhello.a hello.o <br />2.连接成动态库 <br />生成动态库用gcc来完成，由于可能存在多个版本，因此通常指定版本号： <br />$gcc -shared -Wl,-soname,libhello.so.1 -o libhello.so.1.0 hello.o <br />另外再建立两个符号连接： <br />$ln -s libhello.so.1.0 libhello.so.1 <br />$ln -s libhello.so.1 libhello.so <br />这样一个libhello的动态连接库就生成了。最重要的是传gcc -shared 参数使其生成是动态库而不是普通执行程序。 <br />-Wl 表示后面的参数也就是-soname,libhello.so.1直接传给连接器ld进行处理。实际上，每一个库都有一个soname，当连接器发现它正在查找的程序库中有这样一个名称，连接器便会将soname嵌入连结中的二进制文件内，而不是它正在运行的实际文件名，在程序执行期间，程序会查找拥有soname名字的文件，而不是库的文件名，换句话说，soname是库的区分标志。 <br />这样做的目的主要是允许系统中多个版本的库文件共存，习惯上在命名库文件的时候通常与soname相同 <br />libxxxx.so.major.minor <br />其中，xxxx是库的名字，major是主版本号，minor 是次版本号<img src ="http://www.cppblog.com/zjj2816/aggbug/10403.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zjj2816/" target="_blank">井泉</a> 2006-07-24 15:04 <a href="http://www.cppblog.com/zjj2816/archive/2006/07/24/10403.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>新时代的门前 32位世界中的64位编程</title><link>http://www.cppblog.com/zjj2816/archive/2006/07/07/9539.html</link><dc:creator>井泉</dc:creator><author>井泉</author><pubDate>Fri, 07 Jul 2006 05:51:00 GMT</pubDate><guid>http://www.cppblog.com/zjj2816/archive/2006/07/07/9539.html</guid><wfw:comment>http://www.cppblog.com/zjj2816/comments/9539.html</wfw:comment><comments>http://www.cppblog.com/zjj2816/archive/2006/07/07/9539.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zjj2816/comments/commentRss/9539.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zjj2816/services/trackbacks/9539.html</trackback:ping><description><![CDATA[
		<strong>为16、32、64位架构编写可移植代码</strong>
		<br />
		<br />　　与16位相比，32位意味着程序更快、可直接寻址访问更多的内存和更好的处理器架构。鉴于此，越来越多的程序员已经开始考虑利用64位处理器所带来的巨大优势了。<br /><br />　　克雷研究(Cray Research 应为品牌名)计算机已经开始使用64位字，并可访问更大的内存地址。然而，作为正在向开发标准软件和与其他操作系统相互努力的一部分，我们已经停止了移植那些原本基于32位处理器的代码。事实上，我们不断遇到我们称之为“32位主义”的代码，这些代码都是在假定机器字长为32位的情况下编写的，因而很难移植这种代码，所以必须确立一些简单的指导方针，以便助于编写跨16、32、64位处理器平台的代码。<br /><br />　　由于有一些遗留问题，<a class="bluekey" href="http://www.yesky.com/key/4077/169077.html" target="_blank">C语</a>言在数据类型和数据构造方面，显得有点过剩了。可以使用的不仅仅是char、short、int和long类型，还有相应的unsigned（无符号）类型，当然你可在结构(structure)和联合(union)中混合使用，可以在联合中包含结构，再在结构中包含联合，如果还嫌数据类型不够复杂，还可以转换成比特位，当然也可以把一种数据<a class="bluekey" href="http://www.yesky.com/key/2166/12166.html" target="_blank">类型转换</a>成另一种你想要的数据类型。正是因为这些工具太强大，如果不安全地使用它们，就有可能会伤到自己了。<br /><br />　　<b>高级代码的高级结构</b><br /><br />　　在Kernighan和Plauger经典的《The Elements of Programming Style》一书中，他们的建议是“选择使程序看上去简单的数据表示法”。对个人而言，这意味着为高级编程使用高级<a class="bluekey" href="http://www.yesky.com/key/837/837.html" target="_blank">数据结构</a>，而低级编程使用低级数据结构。<br /><br />　　在我们移植的程序中，有一个常见的32位主义bug，不如拿它来做个例子，科内尔大学编写的用于网间互访的路由协议引擎，在伯克利网络环境下（指TCP/IP），自然是使用inet_addr( )来把表示Internet地址的<a class="bluekey" href="http://www.yesky.com/key/4578/14578.html" target="_blank">字符串</a>转换成二进制形式。Internet地址碰巧也是32位的，就如运行伯克利网络系统的其他计算机一样，都是有着同样的字宽。 <br /><br />　　但也存在着有关Internet地址的高级定义：in_ addr结构。这个结构定义包括了子域s_ addr，它是一个包含了Internet地址的unsigned long标量。inet_addr()接受一个指向字符的指针，并且返回一个unsigned long，在转换地址字符串过程中如果发生错误，inet_addr将返回-1。<br /><br />　　程序Gated读取以文本格式存放Internet地址的配置文件，并把它们放入sockaddr_in（这是一个包含了结构in_addr的高级结构）。例1(a)中的代码可以在32位电脑上正常运行，但移植到克雷研究计算机上，却无法运行，为什么呢？<br /><br />　　例1：高级代码的高级结构<br /><br />　　(a)<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>struct sockaddr_in saddrin<br />char *str;<br /><br />if ((saddrin.sin_addr.s_addr = inet_addr(str)) == (unsigned long)-1) {<br />　do_some_error_handling;<br />}</td></tr></tbody></table><br />　　(b)<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>struct sockaddr_in saddrin<br />char *str;<br /><br />if (inet_aton(str, &amp;saddrin.sin_addr) ! = OK) {<br />　do_some_error_handling;<br />}</td></tr></tbody></table><br />　　因为只要inet_addr能够正确地解析字符串，那么一切OK。当inet_addr在64位计算机上返回一个错误时，这段代码却未能捕捉到。你必须要考虑比较语句中的数据位宽，来确定到底是哪出了错。<br /><br />　　首先，inet_addr返回错误值——unsigned long -1，在64位中表示为比特位全为1，这个值被存储在结构in_addr下的子域s_addr中，而in_addr必须是32位来匹配Internet地址，所以它是一个32比特位的unsigned int（在我们的编译器上，int是64位）。现在我们存储进32个1，存储进的值将与unsigned long -1比较。当我们存储32个1于unsigned int时，编译器自动把32位提升为64位；这样，数值0x00000000 ffffffff与0xffffffff ffffffff的比较当然就失败了。这是一个很难发现的bug，特别是在这种因为32位到64位的隐式提升上。<br /><br />　　那我们对这个bug怎么办呢？一个解决方法是在语句中比较0xffffffff，而不是-1，但这又会使代码更加依赖于特定大小的对象。另一个方法是，使用一个中间的unsigned long变量，从而在把结果存入sockaddr_in前，执行比较，但这会让程序代码更复杂。<br /><br />　　真正的问题所在是，我们期望一个unsigned long值与一个32位量（如Internet地址）相等。Internet地址必须以32位形式进行存储，但有些时候用一个标量，来访问这个地址的一部分，是非常方便的。在32位字长的电脑中，用一个long数值（常被当作32位）来访问这个地址，看上去没什么问题。让我们暂时不想一个低级的数据项（32位Internet地址）是否与一个机器字相等，那么高级数据类型结构in_addr就应该被一直使用。因为in_addr中没有无效值，那么应有一个单独的状态用作返回值。<br /><br />　　解决方案是定义一个新的函数，就像inet_addr那样，但返回一个状态值，而且接受一个结构in_addr作为参数，参见例1(b)。因为高级的数据元素可以一直使用，而返回的值是没有溢出的，所以这个代码是可以跨架构移植的，而不管字长是多少。虽然伯克利发布了NET2，其中的确定义了一个新的函数inet_aton()，但如果试着改变inet_addr()中的代码，将会损坏许多程序。<br /><br />　　<b>低级代码的低级结构</b><br /><br />　　低级编程意味着直接操纵物理设备或者特定协议的通讯格式，例如，设备驱动程序经常使用非常精确的位模式来操纵控制寄存器。此外，<a class="bluekey" href="http://www.yesky.com/key/4093/4093.html" target="_blank">网络协议</a>通过特定的比特位模式传输数据项时，也必须适当地转译。<br /><br />　　为了操纵物理数据项，此处的数据结构必须准确地反映被操纵的内容。比特位是一个不错的选择，因为它们正好指定了比特的位数及排列。事实上，正是这种精确度，使比特位相对于short、int、long，更好地映像了物理结构（short、int、long会因为电脑的不同而改变，而比特位不会）。<br /><br />　　当映像一个物理结构时，是通过定义格式来使比特位达到这种精度的，这就使你必须一直使用一种编码风格来访问结构。此时的每个位域都是命名的，你写出的代码可直接访问这些位域。当访问物理结构时，有件事可能你并不想去做，那就是使用标量数组(short、int、or long)，访问这些数组的代码都假定存在一个特定的比特位宽，当移植这些代码到一台使用不同字宽的电脑上时，就可能不正确了。<br /><br />　　在我们移植PEX图像库时遇到的一个问题，就涉及到其映像的协议消息结构。在某台电脑上，如果int整型的长度与消息中的元素一样，那例2(a)中的代码就会工作得很正常。32位的数据元素在32字长的电脑上没有问题，拿到64位的克雷计算机上，它就出错了。对例2(b)而言，不单要改变结构定义，还要改变所有涉及到coord数组的代码。这样，摆在我们面前就有两个选择，要么重写涉及此消息的所有代码，要么定义一个低级的结构和一个高级的结构，然后用一段特殊的代码把数据从一个拷贝到另一个当中，不过我也不期望可以找出每个对zcoord = draw_ msg.coord[2]的引用，而且，当现在需要移植到一个新架构上时，把所有的代码都改写成如例2(c)所示无疑是一项艰苦的工作。这个特殊的问题是由于忽视字长的不同而带来的，所以不能假设在可移植的代码中机器字长、short、int、long都是具有同样的大小。<br /><br />　　例2：低级代码的低级结构<br /><br />　　(a)<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>struct draw_msg {<br />　int objectid;<br />　int coord[3];<br />}</td></tr></tbody></table><br />　　(b)<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>struct draw_msg {<br />　int objectid:32;<br />　int coord1:32;<br />　int coord2:32;<br />　int coord3:32;<br />}</td></tr></tbody></table><br />　　(c)<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>int *iptr, *optr, *limit;<br />int xyz[3];<br /><br />iptr = draw_msg.coord;<br />limit = draw_msg.coord + sizeof(draw_msg.coord);<br /><br />optr = xyz;<br />while (iptr &lt; limit)<br />*optr++ = *iptr++;</td></tr></tbody></table><strong>结构打包和字对齐<br /><br /></strong>　　 正是因为编译器会对结构进行打包，所以不同计算机上字长的变化，还导致了另一个问题。C编译器在字(word)的边界上对齐字长，当具有一个字长的数据后面紧接着一个较小的数据时，这种方法会产生内存空缺（不过也有例外，比如说当有足够多的小数据刚好可以填充一个字时）。<br /><br />　　 一些聪明的程序员在声明联合时，往往在其中会带有两个或更多的结构，其中一个结构刚好填充联合，另一个则可以用来从不同的角度来看待这个联合，参见例3(a)。假设这段代码是为16位字长的计算机所写，int为16位，long为32位，那么存取这个结构的代码将会得到正常的映射关系（如图1），而例3(b)中的代码也会按预期的那样工作。可是，如果这段代码一旦移植到另一台具有32位字长的计算机上时，映射关系就改变了。如果新计算机上的编译器允许你使用16位的int，那么字的对齐就会像图2所示了，或者如果编译器遵循K&amp;R约定，那么int将会和一个字（32比特）一样长，对齐就如图3所示，在任一情况下，这都将导致问题。<br /><br /><table width="90%" align="center" border="0"><tbody><tr><td><div align="center"><img src="http://dev.yesky.com/imagelist/05/11/s1w5hkl49xdt.GIF" border="0" /><br /><img src="http://dev.yesky.com/imagelist/05/11/50e62q83lvi5.GIF" border="0" /><br /><img src="http://dev.yesky.com/imagelist/05/11/zn99pw345649.GIF" border="0" /></div></td></tr></tbody></table><br />　　例3：结构打包和字对齐<br /><br />　　(a)<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>union parse_hdr {<br />　struct hdr {<br />　　char data1;<br />　　char data2;<br />　　int data3;<br />　　int data4;<br />} hdr;<br />struct tkn {<br />　int class;<br />　long tag;<br />} tkn;<br />} parse_item;</td></tr></tbody></table><br />　　(b)<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>char *ptr = msgbuf;<br /><br />parse_item.hdr.data1 = *ptr++;<br />parse_item.hdr.data2 = *ptr++;<br />parse_item.hdr.data3 = (*ptr++ &lt;&lt; 8 | *ptr++);<br />parse_item.hdr.data4 = (*ptr++ &lt;&lt; 8 | *ptr++);<br /><br />if (parse.tkn.class &gt;= MIN_TOKEN_CLASS &amp;&amp; parse.tkn.class &lt;= MAX_TOKEN_CLASS) {<br />　interpret_tag(parse.tkn.tag);<br />}</td></tr></tbody></table><br />　　在第一个情况中（图2），tag域不是像期望的那样线性拉长，而被填充了一些垃圾。而在第二个情况中（图3），无论是class还是tag域，都不再有意义，两个char值因为被打包成一个int，所以也都不再正确。再次强调，首先不要假设标准数据类型大小一样，其次还要了解它们是怎样被映射成其他数据类型的，这才是书写可移植代码的最好方法。<br /><br />　　<b>机器寻址特性</b><br /><br />　　几乎所有的处理器都在字边界上以字为单位进行寻址，而且通常都为此作了一些优化。另有一些的处理器允许其他类型的寻址，如以字节为单位寻址、或在半个字边界上以半字为单位寻址，甚至还有一些处理器有辅助硬件允许在奇数边界上同时以字和半字进行寻址。<br /><br />　　寻址机制在不同计算机上会有所变化，最快的寻址模式是在字边界上以字为单位进行寻址。其他方式的寻址需要辅助硬件，通常都会对内存访问增加了一些时钟周期。而这些过多的模式和特殊硬件的支持，是与RISC处理器的设计初衷背道而驰的，就拿克雷计算机来说，就只支持在字边界上以字为单位进行寻址。<br /><br />　　在那些不提供多种数据类型寻址方式的计算机上，编译器可以提供一些模拟。例如：编译器可以生成一些指令，当读取一个字时，通过移位和屏蔽，来找到所想要的位置，以此来模拟在字中的半字寻址，但这会需要额外的时钟周期，并且代码体积会更大。<br /><br />　　从这点上来说，位域的效率是非常低的，在以位域来取出一个字时，它们产生的代码体积最大。当你存取同一个字中的其他位域时，又需要对包含这个位域字的内存，重新进行一遍寻址，这就是典型的以空间换时间。 <br /><br />　　当在设计数据结构时，我们总是想用可以保存数据的最小数据类型，来达到节省空间的目的。我们有时小气得经常使用char和short，来存取位特域，这就像是为了节省一角钱，而花了一元钱。储存空间上的高效，会付出在程序速度和体积上隐藏的代价。<br /><br />　　试想你只为一个紧凑结构分配了一小点的空间，但却产生了大量的代码来存取结构中的域，而且这段代码还是经常执行的，那么，会因为非字寻址，而导致代码运行缓慢，而且充斥了大量用于提取域的代码，程序体积也因此增大。这些额外代码所占的空间，会让你前面为节省空间所做的努力付之东流。<br /><br />　　在高级数据结构中，特定的比特定位已不是必须的了，应在所有的域中都使用字(word)，而不要操心它们所占用的空间。特别是在程序某些依赖于机器的部分，应该为字准备一个typedef，如下：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>/*在这台计算机上，int是一个字长*/<br />typedef word int;</td></tr></tbody></table><br />　　在高级结构中，对所有域都使用字有如下好处：<br /><br />　　a.. 对其他计算机架构的可移植性 <br /><br />　　b.. 编译器可能生成最快的代码 <br /><br />　　c.. 处理器可能最快地访问到所需内存 <br /><br />　　d.. 绝对没有结构对齐的意外发生<br /><br />　　必须也承认，在某些时候，是不能做到全部使用字的。例如，有一个很大的结构，但不会被经常存取，如果使用了数千个字的话，体积将会增大25%，但使用字通常会节省空间、提高执行速度，而且更具移植性。<br /><br />　　<b>以下是我们的结论：</b><br /><br />　　书写跨平台移植的代码，其实是件简单的事情。最基本的规则是，尽可能地隐藏机器字长的细节，用非常精确的数据元素位大小来映射物理数据结构。或者像前面所建议的，为高级编程使用高级数据结构，而低级编程使用低级数据结构，当阐明高级数据结构时，对标准C的标量类型，不要作任何假设<img src ="http://www.cppblog.com/zjj2816/aggbug/9539.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zjj2816/" target="_blank">井泉</a> 2006-07-07 13:51 <a href="http://www.cppblog.com/zjj2816/archive/2006/07/07/9539.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C语言编程---性能优化</title><link>http://www.cppblog.com/zjj2816/archive/2006/07/07/9535.html</link><dc:creator>井泉</dc:creator><author>井泉</author><pubDate>Fri, 07 Jul 2006 05:16:00 GMT</pubDate><guid>http://www.cppblog.com/zjj2816/archive/2006/07/07/9535.html</guid><wfw:comment>http://www.cppblog.com/zjj2816/comments/9535.html</wfw:comment><comments>http://www.cppblog.com/zjj2816/archive/2006/07/07/9535.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zjj2816/comments/commentRss/9535.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zjj2816/services/trackbacks/9535.html</trackback:ping><description><![CDATA[
		<strong>使用宏定义<br /><br /></strong>　　在C语言中，宏是产生内嵌代码的唯一方法。对于嵌入式系统而言，为了能达到性能要求，宏是一种很好的代替函数的方法。<br /><br />　　写一个"标准"宏MIN ，这个宏输入两个参数并返回较小的一个：<br /><br />　　错误做法：<br /><br /><table style="WIDTH: 490px; HEIGHT: 29px" bordercolor="#ffcc66" width="490" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>#define MIN(A,B) 　( A &lt;= B ? A : B )</td></tr></tbody></table><br />　　正确做法：<br /><br /><table style="WIDTH: 490px; HEIGHT: 29px" bordercolor="#ffcc66" width="490" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>#define MIN(A,B) （（A）&lt;= (B) ? (A) : (B) )</td></tr></tbody></table><br />　　对于宏，我们需要知道三点：<br /><br />　　(1)宏定义"像"函数；<br /><br />　　(2)宏定义不是函数，因而需要括上所有"参数"；<br /><br />　　(3)宏定义可能产生副作用。<br /><br />　　下面的代码：<br /><br /><table style="WIDTH: 506px; HEIGHT: 29px" bordercolor="#ffcc66" width="506" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>least = MIN(*p++, b);</td></tr></tbody></table><br />　　将被替换为：<br /><br /><table style="WIDTH: 478px; HEIGHT: 29px" bordercolor="#ffcc66" width="478" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>( (*p++) &lt;= (b) ?(*p++):(b) )</td></tr></tbody></table><br />　　发生的事情无法预料。 <br /><br />　　因而不要给宏定义传入有副作用的"参数"。<br /><br />　　<b>使用寄存器变量</b><br /><br />　　当对一个变量频繁被读写时，需要反复访问内存，从而花费大量的存取时间。为此，C语言提供了一种变量，即寄存器变量。这种变量存放在CPU的寄存器中，使用时，不需要访问内存，而直接从寄存器中读写，从而提高效率。寄存器变量的说明符是register。对于循环次数较多的循环控制变量及循环体内反复使用的变量均可定义为寄存器变量，而循环计数是应用寄存器变量的最好候选者。<br /><br />　　(1) 只有局部自动变量和形参才可以定义为寄存器变量。因为寄存器变量属于动态存储方式，凡需要采用静态存储方式的量都不能定义为寄存器变量，包括：模块间全局变量、模块内全局变量、局部static变量；<br /><br />　　(2) register是一个"建议"型关键字，意指程序建议该变量放在寄存器中，但最终该变量可能因为条件不满足并未成为寄存器变量，而是被放在了存储器中，但编译器中并不报错（在C++语言中有另一个"建议"型关键字：inline）。<br /><br />　　下面是一个采用寄存器变量的例子：<br /><br /><table style="WIDTH: 504px; HEIGHT: 200px" bordercolor="#ffcc66" width="504" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>/* 求1+2+3+….+n的值 */<br />WORD Addition(BYTE n)<br />{<br />　register i,s=0;<br />　for(i=1;i&lt;=n;i++)<br />　{<br />　　s=s+i;<br />　}<br />　return s;<br />}</td></tr></tbody></table><br />　　本程序循环n次，i和s都被频繁使用，因此可定义为寄存器变量。<br /><br />　　<b>内嵌汇编</b><br /><br />　　程序中对时间要求苛刻的部分可以用内嵌汇编来重写，以带来速度上的显著提高。但是，开发和测试汇编代码是一件辛苦的工作，它将花费更长的时间，因而要慎重选择要用汇编的部分。<br /><br />　　在程序中，存在一个80-20原则，即20%的程序消耗了80%的运行时间，因而我们要改进效率，最主要是考虑改进那20%的代码。<br /><br />　　嵌入式C程序中主要使用在线汇编，即在C程序中直接插入_asm{ }内嵌汇编语句：<br /><br /><table style="WIDTH: 500px; HEIGHT: 238px" bordercolor="#ffcc66" width="500" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>/* 把两个输入参数的值相加，结果存放到另外一个全局变量中 */<br />int result; <br />void Add(long a, long *b) <br />{ <br />　_asm <br />　{ <br />　　MOV AX, a <br />　　MOV BX, b <br />　　ADD AX, [BX]<br />　　MOV result, AX<br />　} <br />} </td></tr></tbody></table><br />　　<b>利用硬件特性</b><br /><br />　　首先要明白CPU对各种存储器的访问速度，基本上是：<br /><br />CPU内部RAM　&gt;　外部同步RAM　&gt;　外部异步RAM　&gt;　FLASH/ROM<br /><br />　　对于程序代码，已经被烧录在FLASH或ROM中，我们可以让CPU直接从其中读取代码执行，但通常这不是一个好办法，我们最好在系统启动后将FLASH或ROM中的目标代码拷贝入RAM中后再执行以提高取指令速度；<br /><br />　　对于UART等设备，其内部有一定容量的接收BUFFER，我们应尽量在BUFFER被占满后再向CPU提出中断。例如计算机终端在向目标机通过RS-232传递数据时，不宜设置UART只接收到一个BYTE就向CPU提中断，从而无谓浪费中断处理时间；<br /><br />　　如果对某设备能采取DMA方式读取，就采用DMA读取，DMA读取方式在读取目标中包含的存储信息较大时效率较高，其数据传输的基本单位是块，而所传输的数据是从设备直接送入内存的（或者相反）。DMA方式较之中断驱动方式，减少了CPU 对外设的干预，进一步提高了CPU与外设的并行操作程度。<br /><br />　　<b>活用位操作</b><br /><br />　　使用C语言的位操作可以减少除法和取模的运算。在计算机程序中数据的位是可以操作的最小数据单位，理论上可以用"位运算"来完成所有的运算和操作，因而，灵活的位操作可以有效地提高程序运行的效率。举例如下： <br /><br /><table style="WIDTH: 438px; HEIGHT: 162px" bordercolor="#ffcc66" width="438" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>/* 方法1 */<br />int i,j;<br />i = 879 / 16;<br />j = 562 % 32; <br />/* 方法2 */<br />int i,j;<br />i = 879 &gt;&gt; 4;<br />j = 562 - (562 &gt;&gt; 5 &lt;&lt; 5);</td></tr></tbody></table><br />　　对于以2的指数次方为"*"、"/"或"%"因子的数学运算，转化为移位运算"&lt;&lt; &gt;&gt;"通常可以提高算法效率。因为乘除运算指令周期通常比移位运算大。<br /><br />　　C语言位运算除了可以提高运算效率外，在嵌入式系统的编程中，它的另一个最典型的应用，而且十分广泛地正在被使用着的是位间的与（&amp;）、或（|）、非（~）操作，这跟嵌入式系统的编程特点有很大关系。我们通常要对硬件寄存器进行位设置，譬如，我们通过将AM186ER型80186处理器的中断屏蔽控制寄存器的第低6位设置为0（开中断2），最通用的做法是：<br /><br /><table style="WIDTH: 444px; HEIGHT: 67px" bordercolor="#ffcc66" width="444" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>#define INT_I2_MASK 0x0040 <br />wTemp = inword(INT_MASK);<br />outword(INT_MASK, wTemp &amp;~INT_I2_MASK);</td></tr></tbody></table><br />　　而将该位设置为1的做法是：<br /><br /><table style="WIDTH: 504px; HEIGHT: 67px" bordercolor="#ffcc66" width="504" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>#define INT_I2_MASK 0x0040 <br />wTemp = inword(INT_MASK);<br />outword(INT_MASK, wTemp | INT_I2_MASK);</td></tr></tbody></table><br />　　判断该位是否为1的做法是：<br /><br /><table style="WIDTH: 492px; HEIGHT: 124px" bordercolor="#ffcc66" width="492" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>#define INT_I2_MASK 0x0040 <br />wTemp = inword(INT_MASK);<br />if(wTemp &amp; INT_I2_MASK)<br />{<br />… /* 该位为1 */<br />}</td></tr></tbody></table><br />　　上述方法在嵌入式系统的编程中是非常常见的，我们需要牢固掌握。 <br /><br />　　<b>总结</b><br /><br />　　在性能优化方面永远注意80-20准备，不要优化程序中开销不大的那80%，这是劳而无功的。<br /><br />　　宏定义是C语言中实现类似函数功能而又不具函数调用和返回开销的较好方法，但宏在本质上不是函数，因而要防止宏展开后出现不可预料的结果，对宏的定义和使用要慎而处之。很遗憾，标准C至今没有包括C++中inline函数的功能，inline函数兼具无调用开销和安全的优点。<br /><br />　　使用寄存器变量、内嵌汇编和活用位操作也是提高程序效率的有效方法。<br /><br />　　除了编程上的技巧外，为提高系统的运行效率，我们通常也需要最大可能地利用各种硬件设备自身的特点来减小其运转开销，例如减小中断次数、利用DMA传输方式等。 <img src ="http://www.cppblog.com/zjj2816/aggbug/9535.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zjj2816/" target="_blank">井泉</a> 2006-07-07 13:16 <a href="http://www.cppblog.com/zjj2816/archive/2006/07/07/9535.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>c# 到 c</title><link>http://www.cppblog.com/zjj2816/archive/2006/06/26/9025.html</link><dc:creator>井泉</dc:creator><author>井泉</author><pubDate>Mon, 26 Jun 2006 10:41:00 GMT</pubDate><guid>http://www.cppblog.com/zjj2816/archive/2006/06/26/9025.html</guid><wfw:comment>http://www.cppblog.com/zjj2816/comments/9025.html</wfw:comment><comments>http://www.cppblog.com/zjj2816/archive/2006/06/26/9025.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zjj2816/comments/commentRss/9025.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zjj2816/services/trackbacks/9025.html</trackback:ping><description><![CDATA[
		<p>1.可以简单的认为 一个.h 文件对应一层<br />2.用static变量，加全局函数 替代 全局变量，模仿属性概念。<br />3.宏的作用 实现默认参数，常量，与“##”连接字符串操作符实现动态名称<br />4.强大的位操作<br />5.动态内存分配<br />6.函数指针，回调机制，事件机制，消息机制</p>
<img src ="http://www.cppblog.com/zjj2816/aggbug/9025.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zjj2816/" target="_blank">井泉</a> 2006-06-26 18:41 <a href="http://www.cppblog.com/zjj2816/archive/2006/06/26/9025.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C语言中的面向对象</title><link>http://www.cppblog.com/zjj2816/archive/2006/06/26/9024.html</link><dc:creator>井泉</dc:creator><author>井泉</author><pubDate>Mon, 26 Jun 2006 10:31:00 GMT</pubDate><guid>http://www.cppblog.com/zjj2816/archive/2006/06/26/9024.html</guid><wfw:comment>http://www.cppblog.com/zjj2816/comments/9024.html</wfw:comment><comments>http://www.cppblog.com/zjj2816/archive/2006/06/26/9024.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zjj2816/comments/commentRss/9024.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zjj2816/services/trackbacks/9024.html</trackback:ping><description><![CDATA[一 、面向对象思想<br /><br /><br /><br />一、 面向对象思想的目的是框架化，手段是抽象<br /><br />相信很多人都明白面向对象讲了什么：类，抽象类，继承，多态。但是是什么原因促使这些概念的产生呢？<br /><br />打个比方说：你去买显示器，然而显示器的品牌样式是多种多样的，你在买的过程中发生的事情也是不可预测的。对于这样的事情，我们在程序语言中如何去描述呢。面向对象的思想就是为了解决这样的问题。编写一个程序（甚至说是一个工程），从无到用是困难的，从有到丰富是更加困难的。面向对象将程序的各个行为化为对象，而又用抽象的办法将这些对象归类（抽象），从而将错综复杂的事情简化为几个主要的有机组合（框架化）。<br /><br />其实我们的身边很多东西都是这样组成的：比如说电脑：电脑是由主板，CPU加上各种卡组成的。这就是一个框架化。而忽略不同的CPU，不同的主板，不同的声卡，网卡，显卡的区别，这就是抽象。再比如说现在的教育网：是由主核心节点：清华，北大，北邮等几个，然后是各个子节点，依次组成了整个教育网网络。<br /><br />所以我觉得面向对象的编程思想就是：一个大型工程是分层次结构的，每层又由抽象的结构连接为整体（框架化），各个抽象结构之间是彼此独立的，可以独立进化（继承，多态）。层次之间，结构之间各有统一的通讯方式（通常是消息，事件机制）。<br /><br />二、 以前 C 语言编程中常用的“面向对象”方法<br /><br />其实C语言诞生以来，人们就想了很多办法来体现“面向对象”的思想。下面就来说说我所知道的方法。先说一些大家熟悉的东东，慢慢再讲诡异的。呵呵<br /><br />1． 宏定义：<br /><br />有的人不禁要问，宏定义怎么扯到这里来了，我们可以先看一个简单的例子：<br /><br />＃define MacroFunction Afunction<br /><br />然后在程序里面你调用了大量的AFunction，但是有一天，你突然发现你要用BFunction了，（不过AFunction又不能不要，很有可能你以后还要调用），这个时候，你就可以＃define MacroFunction Bfunction来达到这样的目的。<br /><br />当然，不得不说这样的办法是too simple，sometime naïve的，因为一个很滑稽的问题是如果我一般要改为BFunction，一半不变怎么办？ 那就只好查找替换了。<br /><br />2． 静态的入口函数，保证函数名相同，利用标志位调用子函数：<br /><br />这样的典型应用很多，比如说网卡驱动里面有一个入口函数Nilan（int FunctionCode，Para*）。具体的参数是什么记不清楚了。不过NiLan的主体是这样的：<br /><br />Long Nilan（int FunctionCode，Para*）{<br /><br />Switch(FunctionCode){<br /><br />Case SendPacket: send(….)<br /><br />Case ReceivePacket: receive(…)<br /><br />…..<br /><br />}<br /><br />写到这里大家明白什么意思了吧。保证相同的函数名就是说：网卡驱动是和pNA+协议栈互连的，那么如何保证pNA+协议栈和不同的驱动都兼容呢，一个简单的办法就是仅仅使用一个入口函数。通过改变如果函数的参数值，来调用内部的各个函数。这样的做法是可以进化的：如果以后想调用新的函数，增加相应的函数参数值就好了。如果我们将网卡驱动和pNA+协议栈看作两个层的话，我们可以发现：<br /><br />层与层之间的互连接口是很小的（这里是一个入口函数），一般是采用名字解析的办法而不是具体的函数调用（利用FunctionCode调用函数，Nilan仅仅实现名字解析的功能） ――！接口限制和名字解析<br /><br />接口限制：层与层之间仅仅知道有限的函数<br /><br />名字解析：层与层之间建立共同的名字与函数的对应关系，之间利用名字调用功能。<br /><br />3．CALLBACK函数。<br /><br />我觉得这是C语言的一个创举，虽然它很简单，就象如何把鸡蛋竖起来一样，但是你如果没想到的话，嘿嘿。如果说静态入口函数实现了一个可管理的宏观的话，CallBack就是实现了一个可进化的微观：它使得一个函数可以在不重新编译的情况下实现功能的添加！但是在最最早期的时候，也有蛮多人持反对态度，因为它用了函数指针。函数指针虽然灵活，但是由于它要访问内存两次才可以调用到函数，第一次访问函数指针，第二次才是真正的函数调用。它的效率是不如普通函数的。但是在一个不太苛刻的环境下，函数调用本身就不怎么耗时，函数指针的性能又不是特别糟糕，使用函数指针其实是一个最好的选择。但是函数指针除了性能，最麻烦的地方就是会导致程序的“支离破碎”。试想：在程序中，你读到一个函数指针的时候，如果你愣是不知道这个函数指针指向的是哪个函数，那个感觉真的很糟糕。（可以看后面的文章，要使用先进的程序框架，避免这样的情况）<br /><br />三、 Event 和 Message<br /><br />看了上面的描述，相信大家多少有些明白为什么要使用Event和Message了。具体的函数调用会带来很多的问题（虽然从效率上讲，这样做是很好的）。为了提高程序的灵活性，Event和Message的办法产生了。用名字解析的办法代替通常的函数调用，这样，如果双方对这样的解析是一致的话，就可以达到一个统一。不过Event和Message的作用还不仅仅是如此。<br /><br />Event和Message还有建立进程间通信的功能。进程将自己的消息发给“控制中心”（简单的就是一个消息队列，和一个while循环不断的取消息队列的内容并执行），控制程序得到消息，分发给相应的进程，这样其他进程就可以得到这个消息并进行响应。<br /><br />Event和Message是很灵活的，因为你可以随时添加或者关闭一个进程，（仅仅需要添加分发消息的列表就可以了）Event和Message 从程序实现上将我觉得是一样的，只不过概念不同。Event多用于指一个动作，比如硬件发生了什么事情，需要调用一个什么函数等等。Message多用于指一个指示，比如什么程序发生了什么操作命令等等。<br /><br />四、 小结<br /><br />其实编程序和写文章一样，都是先有一个提纲，然后慢慢的丰富。先抽象化得到程序的骨架，然后再考虑各个方面的其他内容：程序极端的时候会发生什么问题？程序的这个地方的功能现在还不完善，以后再完善会有什么问题？程序是不是可以扩展的？<br /><br />二、类模拟的性能分析<br /><br />  类模拟中使用了大量的函数指针，结构体等等，有必须对此进行性能分析，以便观察这样的结构对程序的整体性能有什么程度的影响。<br /><br />　　1．函数调用的开销<br /><br />#define COUNTER XX<br />void testfunc()<br />{<br />  int i,k=0;<br />  for(i=0;i&lt;YY;i++)<br />}<br /><br />　　在测试程序里面，我们使用的是一个测试函数，函数体内部可以通过改变YY的值来改变函数的耗时。测试对比是 循环调用XX次函数，和循环XX次函数内部的YY循环。<br /><br />　　结果发现，在YY足够小，X足够大的情况下，函数调用耗时成为了主要原因。所以当一个“简单”功能需要“反复”调用的时候，将它编写为函数将会对性能有影响。这个时候可以使用宏，或者inline关键字。<br /><br />　　但是，实际上我设置XX＝10000000（1千万）的时候，才出现ms级别的耗时，对于非实时操作（UI等等），即使是很慢的cpu（嵌入式10M级别的），也只会在XX＝10万的时候出现短暂的函数调用耗时，所以实际上这个是可以忽略的。<br /><br />　　2．普通函数调用和函数指针调用的开销<br /><br />void (*tf)();<br />tf=testfunc;<br /><br />　　测试程序修改为一个使用函数调用，一个使用函数指针调用。测试发现对时间基本没有什么影响。（在第一次编写的时候，发现在函数调用出现耗时的情况下（XX＝1亿），函数指针的调用要慢（release版本），调用耗时350：500。后来才发现这个影响是由于将变量申请为全局的原因，全局变量的访问要比局部变量慢很多）。<br /><br />　　3．函数指针和指针结构访问的开销<br /><br />struct a {<br />  void (*tf)();<br />}<br /><br />　　测试程序修改为使用结构的函数指针，测试发现对时间基本没有什么影响。其实使用结构并不会产生影响，因为结构的访问是固定偏移量的。所以结构变量的访问和普通变量的访问对于机器码来说是一样的。<br /><br />　　测试结论：使用类模拟的办法对性能不会产生太大的影响。<br /><br />三、C语言的多态实现<br /><br />　　相信很多人都看过设计模式方面的书，大家有什么体会呢？Bridge，Proxy，Factory这些设计模式都是基于抽象类的。使用抽象对象是这里的一个核心。<br />    <br />　　其实我觉得框架化编程的一个核心问题是抽象，用抽象的对象构建程序的主体框架，这是面向对象编程的普遍思想。用抽象构建骨架，再加上多态就形成了一个完整的程序。由于C＋＋语言本身实现了继承和多态，使用这样的编程理念（理念啥意思？跟个风，嘿嘿）在C＋＋中是十分普遍的现象，可以说Virtual（多态）是VC的灵魂。<br /><br />　　但是，使用C语言的我们都快把这个多态忘光光了。我常听见前辈说，类？多态？我们用的是C，把这些忘了吧。很不幸的是，我是一个固执的人。这么好的东西，为啥不用呢。很高兴的，在最近的一些纯C代码中，我看见了C中的多态！下面且听我慢慢道来。<br /><br />　　1. VC中的Interface是什么<br /><br />　　Interface：中文解释是接口，其实它表示的是一个纯虚类。不过我所要说的是，在VC中的Interface其实就是struct，查找Interface的定义，你可以发现有这样的宏定义：<br /><br />    #Ifndef Interface<br />    #define Interface struct<br />    #endif<br /><br />　　而且，实际上在VC中，如果一个类有Virtual的函数，则类里面会有vtable，它实际上是一个虚函数列表。实际上C＋＋是从C发展而来的，它不过是在语言级别上支持了很多新功能，在C语言中，我们也可以使用这样的功能，前提是我们不得不自己实现。<br /><br />　　2．C中如何实现纯虚类（我称它为纯虚结构）<br /><br />　　比较前面，相信大家已经豁然开朗了。使用struct组合函数指针就可以实现纯虚类。<br /><br />　　例子：<br /><br />  typedef struct {<br />    void (*Foo1)();<br />    char (*Foo2)();<br />    char* (*Foo3)(char* st);<br />  }<br />  MyVirtualInterface;<br />    <br />　　这样假设我们在主体框架中要使用桥模式。（我们的主类是DoMyAct，接口具体实现类是Act1，Act2）下面我将依次介绍这些“类”。（C中的“类”在前面有说明，这里换了一个，是使用早期的数组的办法）<br /><br />　　主类DoMyAct： 主类中含有MyVirtualInterface* m_pInterface; 主类有下函数：<br /><br />  DoMyAct_SetInterface(MyVirtualInterface* pInterface)<br />  {<br />    m_pInterface= pInterface;<br />  }<br />  DoMyAct_Do()<br />  {<br />    if(m_pInterface==NULL) return;<br />    m_pInterface-&gt;Foo1();<br />    c=m_pInterface-&gt;Foo2();<br />  }<br /><br />　　子类Act1：实现虚结构，含有MyVirtualInterface st[MAX]; 有以下函数：<br /><br />  MyVirtualInterface* Act1_CreatInterface()<br />  {<br />    index=FindValid() //对象池或者使用Malloc ！应该留在外面申请，实例化<br />    if(index==-1) return NULL;<br />    St[index].Foo1=Act1_Foo1; // Act1_Foo1要在下面具体实现<br />    St[index].Foo2=Act1_Foo2;<br />    St[index].Foo3=Act1_Foo3;<br />    Return &amp;st [index];<br />  }<br /><br />　　子类Act2同上。<br /><br />　　在main中，假设有一个对象List。List中存贮的是MyVirtualInterface指针，则有：<br /><br />  if( (p= Act1_CreatInterface()) != NULL)<br />  List_AddObject(&amp;List, p); //Add Al<br /><br />  While(p=List_GetObject()){<br />    DoMyAct_SetInterface(p);//使用Interface代替了原来大篇幅的Switch Case<br />    DoMyAct_Do();//不要理会具体的什么样的动作，just do it<br />  }<br /><br />  FREE ALL<br /><br />四、类模拟和多态,继承<br /><br />　　在面向对象的语言里面，出现了类的概念。这是编程思想的一种进化。所谓类：是对特定数据的特定操作的集合体。所以说类包含了两个范畴：数据和操作。而C语言中的struct仅仅是数据的集合。（<a href="mailto:liyuming1978@163.com"><font color="#5c5a5b">liyuming1978@163.com</font></a>)<br /><br />　　1．实例：下面先从一个小例子看起<br /><br />#ifndef C_Class<br />    #define C_Class struct<br />#endif<br /><br />C_Class A {<br />    C_Class A *A_this;<br />    void (*Foo)(C_Class A *A_this);<br />    int a;<br />    int b;<br />};<br /><br />C_Class B{           //B继承了A<br />    C_Class B *B_this; //顺序很重要<br />    void (*Foo)(C_Class B *Bthis);         //虚函数<br />    int a;<br />    int b;<br /><br />    int c;<br />};<br /><br /><br />void B_F2(C_Class B *Bthis)<br />{<br />    printf("It is B_Fun\n");<br />}<br /><br />void A_Foo(C_Class A *Athis)<br />{<br />    printf("It is A.a=%d\n",Athis-&gt;a);//或者这里<br />//   exit(1);<br />//   printf("纯虚 不允许执行\n");//或者这里<br />}<br /><br />void B_Foo(C_Class B *Bthis)<br />{<br />    printf("It is B.c=%d\n",Bthis-&gt;c);<br />}<br /><br />void A_Creat(struct A* p)<br />{<br />    p-&gt;Foo=A_Foo;<br />    p-&gt;a=1;<br />    p-&gt;b=2; <br />    p-&gt;A_this=p;<br />}<br /><br /><br />void B_Creat(struct B* p)<br />{<br />    p-&gt;Foo=B_Foo;<br />    p-&gt;a=11;<br />    p-&gt;b=12;     <br />    p-&gt;c=13;<br />    p-&gt;B_this=p;<br />}<br /><br /><br />int main(int argc, char* argv[])<br />{<br />    C_Class A *ma,a;<br />    C_Class B *mb,b;<br /><br />    A_Creat(&amp;a);//实例化<br />    B_Creat(&amp;b);<br /><br />    mb=&amp;b;<br />    ma=&amp;a;<br /><br />    ma=(C_Class A*)mb;//引入多态指针<br />    printf("%d\n",ma-&gt;a);//可惜的就是 函数变量没有private<br />    ma-&gt;Foo(ma);//多态<br />    a.Foo(&amp;a);//不是多态了<br />    B_F2(&amp;b);//成员函数，因为效率问题不使用函数指针<br />    return 0;<br />}<br /><br /><br />　　输出结果：<br /><br />11<br />It is B.c=13<br />It is A.a=1<br />It is B_Fun&lt;/P&lt; p&gt; <img src ="http://www.cppblog.com/zjj2816/aggbug/9024.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zjj2816/" target="_blank">井泉</a> 2006-06-26 18:31 <a href="http://www.cppblog.com/zjj2816/archive/2006/06/26/9024.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>