﻿<?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++博客-duke-文章分类-c/c++</title><link>http://www.cppblog.com/duke/category/849.html</link><description /><language>zh-cn</language><lastBuildDate>Tue, 20 May 2008 17:47:24 GMT</lastBuildDate><pubDate>Tue, 20 May 2008 17:47:24 GMT</pubDate><ttl>60</ttl><item><title>转载:WDM驱动程序入门</title><link>http://www.cppblog.com/duke/articles/3358.html</link><dc:creator>暴雪狂沙</dc:creator><author>暴雪狂沙</author><pubDate>Tue, 21 Feb 2006 00:59:00 GMT</pubDate><guid>http://www.cppblog.com/duke/articles/3358.html</guid><wfw:comment>http://www.cppblog.com/duke/comments/3358.html</wfw:comment><comments>http://www.cppblog.com/duke/articles/3358.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/duke/comments/commentRss/3358.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/duke/services/trackbacks/3358.html</trackback:ping><description><![CDATA[<p>WDM驱动程序是一种很新的东西，相信很多人都跟我一样，对它很感兴趣，但是又找不到学习的切入点。究其原因，还是因为WDM是一种非常“死板板”
的程序，它一运行就是工作在系统的底层RING
0处，提供各种接口给应用程序调用。也正因为如此，它不像普通的应用程序一样，可以很快地上手——更多的时候，你是在阅读它的技术资料和各种接口信息，你
还要非常地熟悉系统底层的工作原理，否则一个不小心，就“蓝屏”了，呵呵——话说回来，写驱动程序的时候，死机是家常便饭。</p>
<p>因此很多人都对
WDM望而生畏了。回想一下，我刚开始学WDM的情形还历历在目——看书看了整整3天，但是看完之后好像跟没看也差不了多少，还是不知道怎么入门，甚至连
怎么写一个“Hello World”都不知道——后来才知道其实WDM是没有所谓的“Hello
World”程序的，唉，真是痛苦啊，这主要还是因为网络上的WDM资料太少造成的。为了不让大家重蹈我的覆辙并对WDM有个感性的认识，在此我给出一个
最简单的完整的WDM框架，并附有注释，姑且可以算是一个入门的“Hello World”吧。</p>
<p>废话少说，让我们马上开始研究，要求读者已安装DDK 2000。（在Win98中我还没有测试过，不清楚是否能正常运行）</p>
<p>/***************************************************************<br>程序名称：Hello World for WDM<br>文件名称：HelloWDM.cpp<br>作者：罗聪<br>日期：2002-8-16<br>***************************************************************/</p>
<p>//一定要的头文件，声明了函数模块和变量：<br>#include "HelloWDM.h"</p>
<p>/***************************************************************<br>函数名称：DriverEntry()<br>功能描述：WDM程序入口<br>***************************************************************/<br>//extern "C"是必须的，表示“用C链接”。如果你的文件名是HelloWDM.c的话，这句可以省略。<br>extern "C"<br>NTSTATUS DriverEntry(&nbsp;&nbsp;&nbsp; IN PDRIVER_OBJECT DriverObject,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
IN PUNICODE_STRING RegistryPath)<br>{<br>&nbsp;&nbsp;&nbsp; //指定“添加设备”消息由函数“HelloWDMAddDevice()”来处理：<br>&nbsp;&nbsp;&nbsp; DriverObject-&gt;DriverExtension-&gt;AddDevice = HelloWDMAddDevice;<br>&nbsp;&nbsp;&nbsp; //指定“即插即用”消息由函数“HelloWDMPnp()”来处理：<br>&nbsp;&nbsp;&nbsp; DriverObject-&gt;MajorFunction[IRP_MJ_PNP] = HelloWDMPnp;</p>
<p>&nbsp;&nbsp;&nbsp; //返回一个NTSTATUS值STATUS_SUCCESS。几乎所有的驱动程序例程都必须返回一个NTSTATUS值，这些值在NTSTATUS.H DDK头文件中有详细的定义。<br>&nbsp;&nbsp;&nbsp; return STATUS_SUCCESS;<br>}</p>
<p><br>/***************************************************************<br>函数名称：HelloWDMAddDevice()<br>功能描述：处理“添加设备”消息<br>***************************************************************/<br>NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
IN PDEVICE_OBJECT PhysicalDeviceObject)<br>{<br>&nbsp;&nbsp;&nbsp; //定义一个NTSTATUS类型的返回值：<br>&nbsp;&nbsp;&nbsp; NTSTATUS status;<br>&nbsp;&nbsp;&nbsp; //定义一个功能设备对象（Functional Device Object）：<br>&nbsp;&nbsp;&nbsp; PDEVICE_OBJECT fdo;</p>
<p>&nbsp;&nbsp;&nbsp; //创建我们的功能设备对象，并储存到fdo中：<br>&nbsp;&nbsp;&nbsp; status = IoCreateDevice(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
DriverObject,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
//驱动程序对象<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sizeof(DEVICE_EXTENSION),&nbsp;&nbsp;&nbsp; //要求的设备扩展的大小<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
NULL,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
//设备名称，这里为NULL<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
FILE_DEVICE_UNKNOWN,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
//设备的类型，在标准头文件WDM.H或NTDDK.H中列出的FILE_DEVICE_xxx值之一<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
0,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
//各种常量用OR组合在一起，指示可删除介质、只读等。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
FALSE,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
//如果一次只有一个线程可以访问该设备，为TRUE，否则为FALSE<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&amp;fdo);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
//返回的设备对象</p>
<p>&nbsp;&nbsp;&nbsp;
//NT_SUCCESS宏用于测试IoCreateDevice内核是否成功完成。不要忘记检查对内核的所有调用是否成功。NT_ERROR宏不等同
于!NT_SUCCESS，最好使用!NT_SUCCESS，因为除了错误外，它还截获警告信息。<br>&nbsp;&nbsp;&nbsp; if( !NT_SUCCESS(status))<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return status;</p>
<p>&nbsp;&nbsp;&nbsp; //创建一个设备扩展对象dx，用于存储指向fdo的指针：<br>&nbsp;&nbsp;&nbsp; PDEVICE_EXTENSION dx = (PDEVICE_EXTENSION)fdo-&gt;DeviceExtension;<br>&nbsp;&nbsp;&nbsp; dx-&gt;fdo = fdo;</p>
<p>&nbsp;&nbsp;&nbsp; //用IoAttachDeviceToDeviceStack函数把HelloWDM设备挂接到设备栈：<br>&nbsp;&nbsp;&nbsp; dx-&gt;NextStackDevice = IoAttachDeviceToDeviceStack(fdo, PhysicalDeviceObject);</p>
<p>&nbsp;&nbsp;&nbsp;
//
设置fdo的flags。有两个“位”是必须改变的，一个是必须清除DO_DEVICE_INITIALIZING标志，如果在DriverEntry例
程中调用IoCreateDevice()，就不需要清除这个标志位。还有一个是必须设置DO_BUFFER_IO标志位：<br>&nbsp;&nbsp;&nbsp; fdo-&gt;Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE;<br>&nbsp;&nbsp;&nbsp; fdo-&gt;Flags &amp;= ~DO_DEVICE_INITIALIZING;</p>
<p>&nbsp;&nbsp;&nbsp; //返回值：<br>&nbsp;&nbsp;&nbsp; return STATUS_SUCCESS;<br>}</p>
<p><br>/***************************************************************<br>函数名称：HelloWDMPnp()<br>功能描述：处理“即插即用”消息<br>***************************************************************/<br>NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT fdo,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
IN PIRP Irp)<br>{<br>&nbsp;&nbsp;&nbsp; //创建一个设备扩展对象dx，用于存储指向fdo的指针：<br>&nbsp;&nbsp;&nbsp; PDEVICE_EXTENSION dx=(PDEVICE_EXTENSION)fdo-&gt;DeviceExtension;</p>
<p>&nbsp;&nbsp;&nbsp; //首先要通过函数IoGetCurrentIrpStackLocation()得到当前的IRP，并由此得到Minor Function：<br>&nbsp;&nbsp;&nbsp; PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);<br>&nbsp;&nbsp;&nbsp; ULONG MinorFunction = IrpStack-&gt;MinorFunction;</p>
<p>&nbsp;&nbsp;&nbsp; //然后把这个Minor Function传递给下一个设备栈：<br>&nbsp;&nbsp;&nbsp; IoSkipCurrentIrpStackLocation(Irp);<br>&nbsp;&nbsp;&nbsp; NTSTATUS status = IoCallDriver( dx-&gt;NextStackDevice, Irp);</p>
<p>&nbsp;&nbsp;&nbsp; //处理“即插即用”次功能代码：<br>&nbsp;&nbsp;&nbsp; //当Minor Function等于IRP_MN_REMOVE_DEVICE时，说明有设备被拔出或卸下，这时要取消资源分配并删除设备：<br>&nbsp;&nbsp;&nbsp; if( MinorFunction==IRP_MN_REMOVE_DEVICE)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //取消设备接口：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IoSetDeviceInterfaceState(&amp;dx-&gt;ifSymLinkName, FALSE);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; RtlFreeUnicodeString(&amp;dx-&gt;ifSymLinkName);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //调用IoDetachDevice()把fdo从设备栈中脱开：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (dx-&gt;NextStackDevice)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IoDetachDevice(dx-&gt;NextStackDevice);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //删除fdo：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IoDeleteDevice(fdo);<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; //返回值：<br>&nbsp;&nbsp;&nbsp; return status;<br>}</p>
<p>/***************************************************************<br>程序名称：Hello World for WDM<br>文件名称：HelloWDM.h<br>作者：罗聪<br>日期：2002-8-16<br>***************************************************************/</p>
<p>//头文件，只是声明一些函数和变量，比较简单就不多说了，请读者自行研究：</p>
<p>#ifdef __cplusplus</p>
<p>extern "C"<br>{<br>#endif</p>
<p>#include "ntddk.h"</p>
<p>#ifdef __cplusplus<br>}<br>#endif</p>
<p>typedef struct _DEVICE_EXTENSION<br>{<br>&nbsp;&nbsp;&nbsp; PDEVICE_OBJECT&nbsp;&nbsp;&nbsp; fdo;<br>&nbsp;&nbsp;&nbsp; PDEVICE_OBJECT&nbsp;&nbsp;&nbsp; NextStackDevice;<br>&nbsp;&nbsp;&nbsp; UNICODE_STRING&nbsp;&nbsp;&nbsp; ifSymLinkName;</p>
<p>} DEVICE_EXTENSION, *PDEVICE_EXTENSION;</p>
<p>NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
IN PDEVICE_OBJECT PhysicalDeviceObject);</p>
<p>NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT fdo,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
IN PIRP Irp);</p>
<p>好
了，第一个WDM版的“Hello
World”就介绍到这里，虽然实际上它什么都没有做，但是由于它包含了完整的框架，所以对于初学者来说还是很有参考价值的。至于怎么编译及安装，留待下
次再讲，敬请留意。（不是我想卖关子啊，这些步骤实在是很麻烦的，要另外写一篇文章才说得清楚哦！）</p>
<img src ="http://www.cppblog.com/duke/aggbug/3358.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/duke/" target="_blank">暴雪狂沙</a> 2006-02-21 08:59 <a href="http://www.cppblog.com/duke/articles/3358.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转载：教您在C/C++中如何构造通用的对象链表</title><link>http://www.cppblog.com/duke/articles/3357.html</link><dc:creator>暴雪狂沙</dc:creator><author>暴雪狂沙</author><pubDate>Tue, 21 Feb 2006 00:33:00 GMT</pubDate><guid>http://www.cppblog.com/duke/articles/3357.html</guid><wfw:comment>http://www.cppblog.com/duke/comments/3357.html</wfw:comment><comments>http://www.cppblog.com/duke/articles/3357.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/duke/comments/commentRss/3357.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/duke/services/trackbacks/3357.html</trackback:ping><description><![CDATA[<ccid_nobr>
一个简化的问题示例<br><br>
链表的难点在于必须复制链表处理函数来处理不同的对象，即便逻辑是完全相同的。例如两个结构类似的链表:<br><br><br><br><br><br><br><br><br><br><br>
</ccid_nobr>
<center><ccid_nobr>
</ccid_nobr><table bordercolorlight="black" bordercolordark="#FFFFFF" align="center" border="1" cellpadding="2" cellspacing="0" width="400">
<tbody><tr>
    <td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6">
    <pre><ccid_code>struct Struct_Object_A<br>　　{<br>　　　　int a;<br>　　　　int b;<br>　　　　Struct_Object_A *next;<br>　　<br>　　} OBJECT_A;<br>　　<br>　　typedef struct Struct_Object_B<br>　　{<br>　　　　int a;<br>　　　　int b;<br>　　　　int c;<br>　　　　Struct_Object_B *next;<br>　　<br>　　} OBJECT_B;　</ccid_code></pre> 
   </td>
  </tr>
</tbody></table>
</center>

<br>
<br>
上面定义的两个结构只有很小的一点差别。OBJECT_B 和 OBJECT_A
之间只差一个整型变量。但是，在编译器看来，它们仍然是非常不同的。必须为存储在链表中的每个对象复制用来添加、删除和搜索链表的函数。为了解决这个问
题，可以使用具有全部三个变量的一个联合或结构，其中整数 c 并不是在所有的情况下都要使用。这可能变得非常复杂，并会形成不良的编程风格。
<br>
<br>
C 代码解决方案：虚拟链表
<br>
<br>
此问题更好的解决方案之一是虚拟链表。虚拟链表是只包含链表指针的链表。对象存储在链表结构背后。这一点是这样实现的，首先为链表节点分配内存，接着为对象分配内存，然后将这块内存分配给链表节点指针，如下所示：
<br>
<br>
虚拟链表结构的一种实现
<br>
<br>
<center><ccid_nobr>
</ccid_nobr><table bordercolorlight="black" bordercolordark="#FFFFFF" align="center" border="1" cellpadding="2" cellspacing="0" width="400">
<tbody><tr>
    <td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6">
    <pre><ccid_code>typedef struct liststruct<br>　　{<br>　　　　liststruct *next;<br>　　<br>　　} LIST, *pLIST;<br>　　<br>　　pLIST Head = NULL;<br>　　<br>　　pLIST AddToList( pLIST Head,<br>void * data, size_t datasize )<br>　　{<br>　　pLIST newlist=NULL;<br>　　void *p;<br>　　<br>　　　　// 分配节点内存和数据内存<br>　　　　newlist = (pLIST) malloc<br>( datasize + sizeof( LIST ) );<br>　　<br>　　　　// 为这块数据缓冲区指定一个指针<br>　　　　p = (void *)( newlist + 1 );<br>　　<br>　　　　// 复制数据<br>　　　　memcpy( p, data, datasize );<br>　　<br>　　　　// 将这个节点指定给链表的表头<br>　　　　if( Head )<br>　　　　{<br>　　　　newlist-&gt;next = Head;<br>　　　　}<br>　　　　else<br>　　　　newlist-&gt;next = NULL;<br>　　<br>　　　　Head = newlist;<br>　　<br>　　　　return Head;<br>　　}　　</ccid_code></pre> 
   </td>
  </tr>
</tbody></table>
</center>

<br>
<br>
链表节点现在建立在数据值副本的基本之上。这个版本能很好地处理标量值，但不能处理带有用 malloc 或 new
分配的元素的对象。要处理这些对象，LIST
结构需要包含一个一般的解除函数指针，这个指针可用来在将节点从链表中删除并解除它之前释放内存（或者关闭文件，或者调用关闭方法）。
<br>
<br>
一个带有解除函数的链表
<br>
<br>
<center><ccid_nobr>
</ccid_nobr><table bordercolorlight="black" bordercolordark="#FFFFFF" align="center" border="1" cellpadding="2" cellspacing="0" width="400">
<tbody><tr>
    <td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6">
    <pre><ccid_code>typedef void (*ListNodeDestructor)( void * );<br>　　<br>　　typedef struct liststruct<br>　　{<br>　　　　ListNodeDestructor DestructFunc;<br>　　　　liststruct *next;<br>　　<br>　　} LIST, *pLIST;<br>　　<br>　　pLIST AddToList( pLIST Head, void * data, <br>size_t datasize,<br>　　ListNodeDestructor Destructor )<br>　　{<br>　　pLIST newlist=NULL;<br>　　void *p;<br>　　<br>　　<br>　　　　// 分配节点内存和数据内存<br>　　　　newlist = (pLIST) malloc<br>( datasize + sizeof( LIST ) );<br>　　<br>　　　　// 为这块数据缓冲区指定一个指针<br>　　　　p = (void *)( newlist + 1 );<br>　　<br>　　　　// 复制数据<br>　　　　memcpy( p, data, datasize );<br>　　<br>　　　　newlist-&gt;DestructFunc = Destructor;<br>　　　　<br>　　　　// 将这个节点指定给链表的表头<br>　　　　if( Head )<br>　　　　{<br>　　　　　　newlist-&gt;next = Head;<br>　　　　}<br>　　　　else<br>　　　　　　newlist-&gt;next = NULL;<br>　　<br>　　　　Head = newlist;<br>　　<br>　　　　return Head;<br>　　}<br>　　<br>　　void DeleteList( pLIST Head )<br>　　{<br>　　　　pLIST Next;<br>　　　　while( Head )<br>　　　　{<br>　　　　　　Next = Head-&gt;next;<br>　　　　　　Head-&gt;DestructFunc( <br>(void *) Head );<br>　　　　　　free( Head );<br>　　　　　　Head = Next;<br>　　　　}<br>　　}<br>　　<br>　　typedef struct ListDataStruct<br>　　{<br>　　　　LPSTR p;<br>　　<br>　　} LIST_DATA, *pLIST_DATA;<br>　　<br>　　void ListDataDestructor( void *p )<br>　　{<br>　　　　// 对节点指针进行类型转换<br>　　　　pLIST pl = (pLIST)p;<br>　　<br>　　　　// 对数据指针进行类型转换<br>　　　　pLIST_DATA pLD = (pLIST_DATA)<br>( pl + 1 );<br>　　<br>　　　　delete pLD-&gt;p;<br>　　}<br>　　pLIST Head = NULL;<br>　　<br>　　void TestList()<br>　　{<br>　　　　pLIST_DATA d = new LIST_DATA;<br>　　　　d-&gt;p = new char[24];<br>　　　　strcpy( d-&gt;p, "Hello" ); <br>　　　　Head = AddToList( Head, (void *) d,<br>sizeof( pLIST_DATA ),<br>　　　　ListDataDestructor );<br>　　　　// 该对象已被复制，现在删除原来的对象<br>　　　　delete d;<br>　　<br>　　　　d = new LIST_DATA;<br>　　　　d-&gt;p = new char[24];<br>　　　　strcpy( d-&gt;p, "World" ); <br>　　　　Head = AddToList( Head, (void *) d,<br>sizeof( pLIST_DATA ),<br>　　　　ListDataDestructor );<br>　　　　delete d;<br>　　<br>　　　　// 释放链表<br>　　　　DeleteList( Head );<br>　　}　　　</ccid_code></pre> 
   </td>
  </tr>
</tbody></table>
</center>

<br>
<br>
在每个链表节点中包含同一个解除函数的同一个指针似乎是浪费内存空间。确实如此，但只有链表始终包含相同的对象才属于这种情况。按这种方式编写链表允许您将任何对象放在链表中的任何位置。大多数链表函数要求对象总是相同的类型或类。
<br>
<br>
虚拟链表则无此要求。它所需要的只是将对象彼此区分开的一种方法。要实现这一点，您既可以检测解除函数指针的值，也可以在链表中所用的全部结构前添加一个类型值并对它进行检测。
<br>
<br>
当然，如果要将链表编写为一个 C++ 类，则对指向解除函数的指针的设置和存储只能进行一次。
<br>
<br>
C++ 解决方案：类链表
<br>
<br>
本解决方案将 CList 类定义为从 LIST 结构导出的一个类，它通过存储解除函数的单个值来处理单个存储类型。请注意添加的 GetCurrentData() 函数，该函数完成从链表节点指针到数据偏移指针的数学转换。一个虚拟链表对象
<br>
<br>
<center><ccid_nobr>
</ccid_nobr><table bordercolorlight="black" bordercolordark="#FFFFFF" align="center" border="1" cellpadding="2" cellspacing="0" width="400">
<tbody><tr>
    <td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6">
    <pre><ccid_code>// 定义解除函数指针<br>　　<br>　　typedef void (*ListNodeDestructor)<br>( void * );<br>　　<br>　　// 未添加解除函数指针的链表<br>　　<br>　　typedef struct ndliststruct<br>　　{<br>　　　　ndliststruct *next;<br>　　<br>　　} ND_LIST, *pND_LIST;<br>　　<br>　　// 定义处理一种数据类型的链表类<br>　　<br>　　class CList : public ND_LIST<br>　　{<br>　　public:<br>　　　　CList(ListNodeDestructor);<br>　　　　~CList();<br>　　　　pND_LIST AddToList<br>( void * data, size_t datasize );<br>　　　　void *GetCurrentData();<br>　　　　void DeleteList( pND_LIST Head );<br>　　<br>　　<br>　　private:<br>　　　　pND_LIST m_HeadOfList;<br>　　　　pND_LIST m_CurrentNode;<br>　　　　ListNodeDestructor<br>m_DestructFunc;<br>　　};<br>　　<br>　　// 用正确的起始值构造这个链表对象<br>　　<br>　　CList::CList(ListNodeDestructor Destructor)<br>　　　　: m_HeadOfList(NULL), <br>m_CurrentNode(NULL)<br>　　{<br>　　　　m_DestructFunc = Destructor;<br>　　}<br>　　<br>　　// 在解除对象以后删除链表<br>　　<br>　　CList::~CList()<br>　　{<br>　　　　DeleteList(m_HeadOfList);<br>　　}<br>　　<br>　　// 向链表中添加一个新节点<br>　　<br>　　pND_LIST CList::AddToList<br>( void * data, size_t datasize )<br>　　{<br>　　pND_LIST newlist=NULL;<br>　　void *p;<br>　　<br>　　<br>　　　　// 分配节点内存和数据内存<br>　　　　newlist = (pND_LIST) malloc<br>( datasize + sizeof( ND_LIST ) );<br>　　<br>　　　　// 为这块数据缓冲区指定一个指针<br>　　　　p = (void *)( newlist + 1 );<br>　　<br>　　　　// 复制数据<br>　　　　memcpy( p, data, datasize );<br>　　<br>　　　　// 将这个节点指定给链表的表头<br>　　　　if( m_HeadOfList )<br>　　　　{<br>　　　　　　newlist-&gt;next = m_HeadOfList;<br>　　　　}<br>　　　　else<br>　　　　　　newlist-&gt;next = NULL;<br>　　<br>　　　　m_HeadOfList = newlist;<br>　　<br>　　　　return m_HeadOfList;<br>　　}<br>　　<br>　　// 将当前的节点数据作为 void 类型返回，<br>以便调用函数能够将它转换为任何类型<br>　　<br>　　void * CList::GetCurrentData()<br>　　{<br>　　　　return (void *)(m_CurrentNode+1);<br>　　}<br>　　<br>　　// 删除已分配的链表<br>　　<br>　　void CList::DeleteList( pND_LIST Head )<br>　　{<br>　　　　pND_LIST Next;<br>　　　　while( Head )<br>　　　　{<br>　　　　　　Next = Head-&gt;next;<br>　　　　　　m_DestructFunc( (void *) Head );<br>　　　　　　free( Head );<br>　　　　　　Head = Next;<br>　　　　}<br>　　}<br>　　<br>　　// 创建一个要在链表中创建和存储的结构<br>　　<br>　　typedef struct ListDataStruct<br>　　{<br>　　　　LPSTR p;<br>　　<br>　　} LIST_DATA, *pND_LIST_DATA;<br>　　<br>　　// 定义标准解除函数<br>　　<br>　　void ClassListDataDestructor( void *p )<br>　　{<br>　　　　// 对节点指针进行类型转换<br>　　　　pND_LIST pl = (pND_LIST)p;<br>　　<br>　　　　// 对数据指针进行类型转换<br>　　　　pND_LIST_DATA pLD = (pND_LIST_DATA)<br>( pl + 1 );<br>　　<br>　　　　delete pLD-&gt;p;<br>　　}<br>　　<br>　　// 测试上面的代码<br>　　<br>　　void MyCListClassTest()<br>　　{<br>　　　　// 创建链表类<br>　　<br>　　　　CList* pA_List_of_Data =<br>new CList(ClassListDataDestructor);<br>　　<br>　　　　// 创建数据对象<br>　　　　<br>　　　　pND_LIST_DATA d = new LIST_DATA;<br>　　　　d-&gt;p = new char[24];<br>　　　　strcpy( d-&gt;p, "Hello" ); <br>　　<br>　　　　// 创建指向链表顶部的局部指针<br>　　<br>　　　　pND_LIST Head = NULL;<br>　　<br>　　　　//向链表中添加一些数据<br>　　<br>　　　　Head = pA_List_of_Data-&gt;AddToList<br>( (void *) d, <br>　　　　sizeof( pND_LIST_DATA ) );<br>　　　　// 该对象已被复制，现在删除原来的对象<br>　　　　delete d;<br>　　<br>　　　　// 确认它已被存储<br>　　　　char * p = ((pND_LIST_DATA) pA_List_of_Data-&gt;GetCurrentData())-&gt;p;<br>　　<br>　　　　d = new LIST_DATA;<br>　　　　d-&gt;p = new char[24];<br>　　　　strcpy( d-&gt;p, "World" ); <br>　　　　Head = pA_List_of_Data-&gt;AddToList<br>( (void *) d, sizeof( pND_LIST_DATA ) );<br>　　　　// 该对象已被复制，现在删除原来的对象<br>　　　　delete d;<br>　　<br>　　　　// 确认它已被存储<br>　　　　p = ((pND_LIST_DATA) <br>pA_List_of_Data-&gt;GetCurrentData())-&gt;p;<br>　　<br>　　　　// 删除链表类，析构函数将删除链表<br>　　　　delete pA_List_of_Data;<br>　　}</ccid_code></pre> 
   </td>
  </tr>
</tbody></table>
</center>
　　
<br>
<br>
小结
<br>
<br>
从前面的讨论来看，似乎仅编写一个简单的链表就要做大量的工作，但这只须进行一次。很容易将这段代码扩充为一个处理排序、搜索以及各种其
他任务的 C++ 类，并且这个类可以处理任何数据对象或类（在一个项目中，它处理大约二十个不同的对象）。您永远不必重新编写这段代码。 <br>
<img src ="http://www.cppblog.com/duke/aggbug/3357.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/duke/" target="_blank">暴雪狂沙</a> 2006-02-21 08:33 <a href="http://www.cppblog.com/duke/articles/3357.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>水滴石穿C语言之typedef的问题 引自：http://blog.csdn.net/i_like_cpp/</title><link>http://www.cppblog.com/duke/articles/3356.html</link><dc:creator>暴雪狂沙</dc:creator><author>暴雪狂沙</author><pubDate>Tue, 21 Feb 2006 00:29:00 GMT</pubDate><guid>http://www.cppblog.com/duke/articles/3356.html</guid><wfw:comment>http://www.cppblog.com/duke/comments/3356.html</wfw:comment><comments>http://www.cppblog.com/duke/articles/3356.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/duke/comments/commentRss/3356.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/duke/services/trackbacks/3356.html</trackback:ping><description><![CDATA[1. 基本解释
<p>　　typedef为C语言的关键字，作用是为一种数据类型定义一个新名字。这里的数据类型包括内部数据类型（int,char等）和自定义的数据类型（struct等）。</p>
<p>　　在编程中使用typedef目的一般有两个，一个是给变量一个易记且意义明确的新名字，另一个是简化一些比较复杂的类型声明。</p>
<p>　　至于typedef有什么微妙之处，请你接着看下面对几个问题的具体阐述。</p>
<p>&nbsp;<br>&nbsp;</p>
<p><br>　　2. typedef &amp; 结构的问题</p>
<p>　　当用下面的代码定义一个结构时，编译器报了一个错误，为什么呢？莫非C语言不允许在结构中包含指向它自己的指针吗？请你先猜想一下，然后看下文说明：</p>
<p>typedef struct tagNode<br>{<br>　char *pItem;<br>　pNode pNext;<br>} *pNode;&nbsp; </p>
<p>　　答案与分析：</p>
<p>　　1、typedef的最简单使用</p>
<p>typedef long byte_4; </p>
<p>　　给已知数据类型long起个新名字，叫byte_4。</p>
<p>　　2、 typedef与结构结合使用</p>
<p>typedef struct tagMyStruct<br>{ <br>　int iNum;<br>　long lLength;<br>} MyStruct; </p>
<p>　　这语句实际上完成两个操作：</p>
<p>　　1) 定义一个新的结构类型</p>
<p>struct tagMyStruct<br>{ <br>　int iNum; <br>　long lLength; <br>}; </p>
<p>　　分析：tagMyStruct称为“tag”，即“标签”，实际上是一个临时名字，struct 关键字和tagMyStruct一起，构成了这个结构类型，不论是否有typedef，这个结构都存在。</p>
<p>　　我们可以用struct tagMyStruct varName来定义变量，但要注意，使用tagMyStruct varName来定义变量是不对的，因为struct 和tagMyStruct合在一起才能表示一个结构类型。</p>
<p>　　2) typedef为这个新的结构起了一个名字，叫MyStruct。</p>
<p>typedef struct tagMyStruct MyStruct; </p>
<p>　　因此，MyStruct实际上相当于struct tagMyStruct，我们可以使用MyStruct varName来定义变量。</p>
<p>　　答案与分析</p>
<p>　　C语言当然允许在结构中包含指向它自己的指针，我们可以在建立链表等数据结构的实现上看到无数这样的例子，上述代码的根本问题在于typedef的应用。</p>
<p>　　根据我们上面的阐述可以知道：新结构建立的过程中遇到了pNext域的声明，类型是pNode，要知道pNode表示的是类型的新名字，那么在类型本身还没有建立完成的时候，这个类型的新名字也还不存在，也就是说这个时候编译器根本不认识pNode。</p>
<p>　　解决这个问题的方法有多种：</p>
<p>　　1)、</p>
<p>typedef struct tagNode <br>{<br>　char *pItem;<br>　struct tagNode *pNext;<br>} *pNode; </p>
<p>　　2)、</p>
<p>typedef struct tagNode *pNode;<br>struct tagNode <br>{<br>　char *pItem;<br>　pNode pNext;<br>}; </p>
<p>　　注意：在这个例子中，你用typedef给一个还未完全声明的类型起新名字。C语言编译器支持这种做法。</p>
<p>　　3)、规范做法：</p>
<p>struct tagNode<br>{<br>　char *pItem;<br>　struct tagNode *pNext;<br>};<br>typedef struct tagNode *pNode; </p>
<p>　　3. typedef &amp; #define的问题</p>
<p>　　有下面两种定义pStr数据类型的方法，两者有什么不同？哪一种更好一点？</p>
<p>typedef char *pStr;<br>#define pStr char *;&nbsp; </p>
<p>　　答案与分析：</p>
<p>　　通常讲，typedef要比#define要好，特别是在有指针的场合。请看例子：</p>
<p>typedef char *pStr1;<br>#define pStr2 char *;<br>pStr1 s1, s2;<br>pStr2 s3, s4; </p>
<p>　　在上述的变量定义中，s1、s2、s3都被定义为char *，而s4则定义成了char，不是我们所预期的指针变量，根本原因就在于#define只是简单的字符串替换而typedef则是为一个类型起新名字。</p>
<p>　　#define用法例子： </p>
<p>#define f(x) x*x<br>main( )<br>{<br>　int a=6，b=2，c；<br>　c=f(a) / f(b)；<br>　printf("%d \n"，c)；<br>} </p>
<p>　　以下程序的输出结果是: 36。</p>
<p>　　因为如此原因，在许多C语言编程规范中提到使用#define定义时，如果定义中包含表达式，必须使用括号，则上述定义应该如下定义才对：</p>
<p>#define f(x) (x*x) </p>
<p>　　当然，如果你使用typedef就没有这样的问题。</p>
<p>　　4. typedef &amp; #define的另一例</p>
<p>　　下面的代码中编译器会报一个错误，你知道是哪个语句错了吗？</p>
<p>typedef char * pStr;<br>char string[4] = "abc";<br>const char *p1 = string;<br>const pStr p2 = string;<br>p1++;<br>p2++; </p>
<p>　　答案与分析：</p>
<p>　
　是p2++出错了。这个问题再一次提醒我们：typedef和#define不同，它不是简单的文本替换。上述代码中const pStr
p2并不等于const char * p2。const pStr p2和const long
x本质上没有区别，都是对变量进行只读限制，只不过此处变量p2的数据类型是我们自己定义的而不是系统固有类型而已。因此，const pStr
p2的含义是：限定数据类型为char *的变量p2为只读，因此p2++错误。</p>
<p>（注：关于const的限定内容问题，在本系列第二篇有详细讲解）。</p>
<p>　　#define与typedef引申谈</p>
<p>　　1) #define宏定义有一个特别的长处：可以使用 #ifdef ,#ifndef等来进行逻辑判断，还可以使用#undef来取消定义。</p>
<p>　　2) typedef也有一个特别的长处：它符合范围规则，使用typedef定义的变量类型其作用范围限制在所定义的函数或者文件内（取决于此变量定义的位置），而宏定义则没有这种特性。</p>
<p>　　5. typedef &amp; 复杂的变量声明</p>
<p>　　在编程实践中，尤其是看别人代码的时候，常常会遇到比较复杂的变量声明,使用typedef作简化自有其价值，比如：</p>
<p>　　下面是三个变量的声明，我想使用typdef分别给它们定义一个别名，请问该如何做？</p>
<p>&gt;1：int *(*a[5])(int, char*);<br>&gt;2：void (*b[10]) (void (*)());<br>&gt;3. doube(*)() (*pa)[9]; </p>
<p>　　答案与分析：</p>
<p>　　对复杂变量建立一个类型别名的方法很简单，你只要在传统的变量声明表达式里用类型名替代变量名，然后把关键字typedef加在该语句的开头就行了。 </p>
<p>　　（注：如果你对有些变量的声明语法感到难以理解，请参阅本系列第十篇的相关内容）。</p>
<p>&gt;1：int *(*a[5])(int, char*);<br>//pFun是我们建的一个类型别名<br>typedef int *(*pFun)(int, char*); <br>//使用定义的新类型来声明对象，等价于int* (*a[5])(int, char*);<br>pFun a[5]; </p>
<p>&gt;2：void (*b[10]) (void (*)());<br>//首先为上面表达式蓝色部分声明一个新类型<br>typedef void (*pFunParam)();<br>//整体声明一个新类型<br>typedef void (*pFun)(pFunParam);<br>//使用定义的新类型来声明对象，等价于void (*b[10]) (void (*)());<br>pFun b[10];</p>
&gt;3. doube(*)() (*pa)[9]; <br>
//首先为上面表达式蓝色部分声明一个新类型<br>
typedef double(*pFun)();<br>
//整体声明一个新类型<br>
typedef pFun (*pFunParam)[9];<br>
//使用定义的新类型来声明对象，等价于doube(*)() (*pa)[9];<br>
pFunParam pa;&nbsp; <img src ="http://www.cppblog.com/duke/aggbug/3356.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/duke/" target="_blank">暴雪狂沙</a> 2006-02-21 08:29 <a href="http://www.cppblog.com/duke/articles/3356.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>大名鼎鼎的林锐：高质量C/C++编程指南</title><link>http://www.cppblog.com/duke/articles/3026.html</link><dc:creator>暴雪狂沙</dc:creator><author>暴雪狂沙</author><pubDate>Wed, 25 Jan 2006 09:13:00 GMT</pubDate><guid>http://www.cppblog.com/duke/articles/3026.html</guid><wfw:comment>http://www.cppblog.com/duke/comments/3026.html</wfw:comment><comments>http://www.cppblog.com/duke/articles/3026.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/duke/comments/commentRss/3026.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/duke/services/trackbacks/3026.html</trackback:ping><description><![CDATA[林锐 2001<br>
<br>
1.头文件能调用库功能、加强类型安全检查；<br>
2.const常量有数据类型，而宏常量没有数据类型；<br>
3.建立在整个类中都恒定的常量不能用const数据成员了，应该用类中的枚举常量来实现；<br>
4.C语言中，凡不加类型说明的函数，一律自动按整型处理；<br>
5.getchar的原型为：int getchar(void);<br>
6.正常值用输出参数获得，而错误标志用return语句返回；<br>
7.strcpy函数将strSrc拷贝至输出参数strDest中，同时函数的返回值又是strDest；<br>
8.创建一个临时对象并返回它：编译器直接把临时对象创建并初始化在外部存储单元中；<br>
9.assert是仅在Debug版本起作用的宏；<br>
10.如果指针p是函数的参数，那么在函数的入口处用assert(p!=NULL)进行检查。如果是用malloc或new来申请内存，应该用if(p==NULL) 或if(p!=NULL)进行防错处理；<br>
11.使用free或delete释放了内存后，应该将指针设置为NULL,防止产生“野指针”；<br>
12.数组要么在静态存储区被创建（如全局数组），要么在栈上被创建；<br>
13.如果函数的参数是一个指针，不要指望用该指针去申请动态内存；<br>
14.如果p不是NULL指针，那么free对p连续操作两次就会导致程序运行错误；<br>
15.inline是一种“用于实现的关键字”，而不是一种“用于声明的关键字”；<br>
16.“缺省的拷贝构造函数”和“缺省的赋值函数”均采用“位拷贝”而非“值拷贝”的方式来实现，倘若类中含有指针变量，这两个函数注定将出错；<br>
17.const只能修饰输入参数；<br>
18.函数返回值采用“引用传递”的场合并不多，这种方式一般只出现在类的赋值函数中，目的是为了实现链式表达。<img src ="http://www.cppblog.com/duke/aggbug/3026.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/duke/" target="_blank">暴雪狂沙</a> 2006-01-25 17:13 <a href="http://www.cppblog.com/duke/articles/3026.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转载：static关键字Q/A</title><link>http://www.cppblog.com/duke/articles/3025.html</link><dc:creator>暴雪狂沙</dc:creator><author>暴雪狂沙</author><pubDate>Wed, 25 Jan 2006 09:11:00 GMT</pubDate><guid>http://www.cppblog.com/duke/articles/3025.html</guid><wfw:comment>http://www.cppblog.com/duke/comments/3025.html</wfw:comment><comments>http://www.cppblog.com/duke/articles/3025.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/duke/comments/commentRss/3025.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/duke/services/trackbacks/3025.html</trackback:ping><description><![CDATA[<p><strong>Q:在C和C++中，栈的作用是什么?</strong><br>A:栈又称stack - 函数调用栈，其中的内容在函数执行期间有效，并由编译器负责分配和收回；</p>
<p><strong>Q:在C++中，堆栈的作用是什么?</strong><br>A:堆栈又称heap - 堆，由程序显式分配和收回，如果不收回就是内存泄漏。</p>
<p>扩展分析:　　<br>&nbsp; 1)、程序中有不同的内存段，包括：<br>　　.data - 已初始化全局/静态变量，在整个软件执行过程中有效；<br>　　.bss - 未初始化全局/静态变量，在整个软件执行过程中有效；<br>　　.stack - 函数调用栈，其中的内容在函数执行期间有效，并由编译器负责分配和收回；<br>　　.heap - 堆，由程序显式分配和收回，如果不收回就是内存泄漏。<br>&nbsp; 2)、自己使用的内存最好还是自己申请和释放。<br>&nbsp; <br><strong>Q:在C中，static变量的含义。</strong><br>A:static 声明的变量在C语言中有两方面的特征：<br>　　1)、变量会被放在程序的全局存储区中，这样可以在下一次调用的时候还可以保持原来的赋值。这一点是它与堆栈变量和堆变量的区别。<br>　　2)、变量用static告知编译器，自己仅仅在变量的作用范围内可见。这一点是它与全局变量的区别。<br>Tips:<br>　　A.若全局变量仅在单个C文件中访问，则可以将这个变量修改为静态全局变量，以降低模块间的耦合度；<br>　　B.若全局变量仅由单个函数访问，则可以将这个变量改为该函数的静态局部变量，以降低模块间的耦合度；<br>　　C.设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时，需要考虑重入问题；<br>&nbsp;&nbsp;D.如果我们需要一个可重入的函数，那么，我们一定要避免函数中使用static变量(这样的函数被称为：带“内部存储器”功能的的函数)<br>&nbsp;&nbsp;E.函数中必须要使用static变量情况:比如当某函数的返回值为指针类型时，则必须是static的局部变量的地址作为返回值，若为auto类型，则返回为错指针。</p>
<p><strong>Q:在C中，static函数的含义。</strong><br>A:函数前加static使得函数成为静态函数。但此处“static”的含义不是指存储方式，而是指对函数的作用域仅局限于本文件(所以又称内部函数)。使用内部函数的好处是：不同的人编写不同的函数时，不用担心自己定义的函数，是否会与其它文件中的函数同名。</p>
<p>扩
展分析:术语static有着不寻常的历史.起初，在C中引入关键字static是为了表示退出一个块后仍然存在的局部变量。随后，static在C中有
了第二种含义：用来表示不能被其它文件访问的全局变量和函数。为了避免引入新的关键字，所以仍使用static关键字来表示这第二种含义。最后，C++重
用了这个关键字，并赋予它与前面不同的第三种含义：表示属于一个类而不是属于此类的任何特定对象的变量和函数(与Java中此关键字的含义相同)。</p>
<img src ="http://www.cppblog.com/duke/aggbug/3025.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/duke/" target="_blank">暴雪狂沙</a> 2006-01-25 17:11 <a href="http://www.cppblog.com/duke/articles/3025.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>现在时</title><link>http://www.cppblog.com/duke/articles/3008.html</link><dc:creator>暴雪狂沙</dc:creator><author>暴雪狂沙</author><pubDate>Tue, 24 Jan 2006 06:42:00 GMT</pubDate><guid>http://www.cppblog.com/duke/articles/3008.html</guid><wfw:comment>http://www.cppblog.com/duke/comments/3008.html</wfw:comment><comments>http://www.cppblog.com/duke/articles/3008.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/duke/comments/commentRss/3008.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/duke/services/trackbacks/3008.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp; 最近正在使用Qt库，总体感觉还不错，进行中的项目也写的差不多了，找时间把文档写一写，得总结一下才好。<br>
&nbsp;&nbsp;&nbsp; 正在写一个小测试程序，在windows 2000/XP 下串口的收发程序，到了首尾阶段，还要细心一点，这次的开发文档还是没有先写，可能会有漏洞，尽量避免吧。<br>
&nbsp;&nbsp;&nbsp; 最近在网上看了一下，很多知识都应该补充了，像STL,早些时候只是看了点，没有深入研究，比较落伍了，有空也要了解其原理和使用。<br>
&nbsp;&nbsp;&nbsp; 快春节了，心里很散，不知道新的一年会是什么样子，我想考验会更多吧。<br>
&nbsp;&nbsp;&nbsp; <br>
<img src ="http://www.cppblog.com/duke/aggbug/3008.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/duke/" target="_blank">暴雪狂沙</a> 2006-01-24 14:42 <a href="http://www.cppblog.com/duke/articles/3008.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>