<?xml version="1.0" encoding="UTF-8" ?><rss version="2.0" xmlns:content="http://purl.org/Rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"><channel><title>Be Yourself And Beyond Yourself</title><link>http://www.i170.com/user/killercat/Rss</link><description></description><language>zh-cn</language><pubDate>Sun, 23 Nov 2008 22:04:07  +0800</pubDate><generator>i170.com</generator><image><title>Be Yourself And Beyond Yourself</title><url>http://www.i170.comattavatar_1/killercat_Src.JPG</url><link>http://www.i170.com/user/killercat/Rss</link></image> <item><link>http://www.i170.com/Article/112807</link><title><![CDATA[C++ 重载解析]]></title><author>killercat</author><category>C/C++</category><pubDate>Sun, 23 Nov 2008 15:23:18  +0800</pubDate><description><![CDATA[<p>首先我们来看几个函数：</p>
<p>int f(int) {}</p>
<p>int f(const int) {}</p>
<p>以上两个函数不能出现在同一个编译单元，编译器无法区分他们。</p>
<p>int f(int&amp;) {}</p>
<p>int f(const int&amp;) {}</p>
<p>以上两个函数可以出现在同一个编译单元，编译器可以区别他们。</p>
<p>我们需要了解一下编译器如何进行重载解析的。</p>
<p>&nbsp;</p>
<p>重载解析是一个比较复杂的过程，编译器将做如下工作：</p>
<p>1）通过被调用函数的名字和参数列表来确定一个或者一组可行函数</p>
<p>2）在可行函数中确定是否有一个最佳可行函数，如果有使用它，否则出错</p>
<p>
这里说到的可行函数包括：普通函数和函数模板，被调用的函数的实参能够隐式的向可行函数的形参转换或者函数模板能够实例化为需要的函数。</p>
<p>例如：</p>
<p>int f(int); #1</p>
<p>int f(void); #2<br>
int f(double); #3</p>
<p>void f(int, int); #4</p>
<p>void f(long); #5</p>
<p>template&lt;class T&gt; void f(T); #6</p>
<p>int f(int*); #7</p>
<p>我们这样使用：</p>
<p>short sNum = 1;</p>
<p>f(sNum);</p>
<p>经过编译器分析，将得到一个由 #1, #3, #5, #6 组成的可行函数列表。显然，这里 short 类型的对象能够向
int, double, long 进行隐式转换，而函数模板则可以实例化为 void f(short)。</p>
<p>接下来编译器将寻找一个最佳可行函数。寻找最佳可行函数的策略如下（对于现代 C++ 编译器来说）：</p>
<p>1）是否有类型完全匹配的函数，如果有则这个函数是最佳可行函数</p>
<p>2）是否有函数模板存在，如果有则这个函数模板为最佳可行函数</p>
<p>3）函数实参是否能够通过提升转换找到匹配的函数，例如 short 类型对象向 int 类型转换就是提升转换，float 向
int 也是提升转换，如果有这样的函数，则为最佳可行函数</p>
<p>4）函数实参是否能够通过标准转换找到匹配的函数，例如 int 类型对象向 short 类型转换或者 double 类型向
float 类型转换，如果有这样的函数，则为最佳可行函数</p>
<p>5）是否存在用户定义的转换（例如类声明的转换），如果转换可行，那么就是最佳函数</p>
<p>6）找不到最佳函数，失败。</p>
<p>&nbsp;</p>
<p>测试程序如下：</p>
<p>void f(int)<br>
{<br>
&nbsp;std::cout &lt;&lt; "f(int)" &lt;&lt; std::endl;<br>
}</p>
<p>void f(void)<br>
{<br>
&nbsp;std::cout &lt;&lt; "f(void)" &lt;&lt; std::endl;<br>
}</p>
<p>void f(double)<br>
{<br>
&nbsp;std::cout &lt;&lt; "f(double)" &lt;&lt; std::endl;<br>
}</p>
<p>template &lt;typename T&gt;<br>
void f(T)<br>
{<br>
&nbsp;std::cout &lt;&lt; "f(T)" &lt;&lt; std::endl;<br>
}</p>
<p>int main(int argc, char* argv[])<br>
{<br>
&nbsp;short sNum = 1;<br>
&nbsp;f(sNum);</p>
<p>&nbsp;return 0;<br>
}</p>

]]></description><guid>http://www.i170.com/Article/112807</guid><trackback:ping>http://www.i170.com/Article/112807/trackback</trackback:ping><comments>http://www.i170.com/Article/112807#comment</comments><wfw:commentRss>http://www.i170.com/Article/112807/commentRss</wfw:commentRss></item> <item><link>http://www.i170.com/Article/112475</link><title><![CDATA[MMOG 服务器开发]]></title><author>killercat</author><category>C/C++</category><pubDate>Sat, 08 Nov 2008 18:50:56  +0800</pubDate><description><![CDATA[<p>最重要的技术指标是：</p>
<p>延迟：完成给定事务所花费的时间（例如，50 ms）</p>
<p>对于一个 60 FPS 的游戏来说，意味着服务器对每个用户的任何一次请求的响应在 1000 / 60 ms
内完成，用户将感觉非常流畅。</p>
<p>&nbsp;</p>
<p>我们必须首先找到性能的瓶颈，然后克服瓶颈，克服瓶颈的一个有效的手段是为瓶颈增加资源（例如更好的主机或者网络）</p>
<p>发现和解决瓶颈：</p>
<p>首先我们需要收集信息，特别指收集用户请求到得到响应的时间以及服务器的内在消耗（例如服务器的 OnTimer）。</p>
<p>&lt;1&gt; CPU 的影响是最小的</p>
<p>现代服务器运行十分高效，通常在微妙级完成大量计算，如果因为 CPU 出现性能问题，我们可以使用更加高效的 CPU
或者优化我们的程序，例如优化对容器的操作，诸如 std::map 这样的容器，在数据量极大的情况下，插入是低效的。频繁的 new 和
delete 是低效的，如果有必要，我们可以收集需要删除的对象而不是真正的删除对象。</p>
<p>&lt;2&gt; 内存的读写对性能影响较小</p>
<p>对内存的读写一般来说影响是小的，但是应该注意 std::vector 等容器的内存分配方式，频繁的内存拷贝是较为低效的。</p>
<p>&lt;3&gt; 文件 IO 和数据库 IO 影响较大</p>
<p>文件的 IO 和数据库 IO 是比较低效的，我们通常都要尽量使用异步 IO，而一般情况下数据库 IO 都伴随则网络的
IO，因此我们应该尽量减少数据库 IO，尽量更新内存镜像而不是直接写库。</p>
<p>a. 在合理的范围内，在服务器启动时，载入数据库中的数据。</p>
<p>b.
除了非常重要的数据之外的数据，都没有必要立即写库，取而代之是修改内存的镜像，为了确保安全（例如避免服务器崩溃时玩家损失过大），可以定时将内存数据同步数据库，这种同步可以分散到秒级，例如，我们服务器拥有
1000 名玩家，每个玩家 10 分钟同步一次数据，通常我们不会在第 10 分钟同时保存 1000
名玩家的数据，而是在每秒钟（1000 / 60 * 10 = 1）或者每两秒钟保存一个玩家的数据</p>
<p>c. 尽量使用异步数据库 IO</p>
<p>d. 一般来说，玩家离线后，其相关数据就从内存中删除，如果某玩家需要读取离线玩家的数据，可以使用异步 IO。</p>
<p>e. 所有数据全部置于内存是可能的，我们可以查询相关的技术，当然高效率的同时带来了高成本。</p>
<p>&lt;4&gt; 网络 IO 影响最大</p>
<p>没有必要的服务器端和客户端的数据同步开销巨大。网络底层通常使用
IOCP（Windows）、Epoll（Linux）、Kqueue（Freebsd）或者使用 ACE
等技术，当然也可以使用一些特殊的手段（参考云风《IOCP, kqueue, epoll 有多重要》）。</p>
<p>&nbsp;</p>
<p>最终网络和计算机硬件的发展对 MMOG 服务器开发有非常重要的影响，如果辅助存储器 IO 效率能够同主存储器一样高效，如果网络
IO 效率能够达到访问辅助存储器的效率（而这些都是可能的），那么 MMOG 服务器开发将是完全另外一番景象了。</p>

]]></description><guid>http://www.i170.com/Article/112475</guid><trackback:ping>http://www.i170.com/Article/112475/trackback</trackback:ping><comments>http://www.i170.com/Article/112475#comment</comments><wfw:commentRss>http://www.i170.com/Article/112475/commentRss</wfw:commentRss></item> <item><link>http://www.i170.com/Article/112370</link><title><![CDATA[使用 MSVC 编译 ACE5.6]]></title><author>killercat</author><category>C/C++</category><pubDate>Tue, 04 Nov 2008 11:26:18  +0800</pubDate><description><![CDATA[<p>ACE5.6 包中包含了 VC7.1（VS2003）和 VC8.0（VS2005）的工程文件。</p>
<p>VC8 支持编译适用于 desktop/server Windows、Windows CE 和 Windows Mobile
平台的库。</p>
<p>ACE_wrappers 目录下的：</p>
<p>ACE.sln 适用于 VC7.1</p>
<p>ACE_vc8.sln 适用于 VC8，用于编译适用于 desktop/server Windows 平台的库</p>
<p>ACE_WinCE.sln 适用于 VC8，用于编译适用于 Windows CE/Moblie 平台的库</p>
<p>1. 按需要使用 MSVC 打开相关的 sln 文件</p>
<p>2. 在 ACE_wrappers\ace 目录下创建 config.h 文件，写入：<br>
#include "ace/config-win32.h"<br>
3. 如果你希望使用标准的 C++ 头文件（例如 iostream、cstdio 等）在 #include
"ace/config-win32.h" 前加入：<br>
#define ACE_HAS_STANDARD_CPP_LIBRARY 1</p>
<p>4. 如果你希望使用 MFC 库，那么 config.h 中加入：</p>
<p>#define ACE_HAS_MFC 1</p>
<p>如果你希望使用 MFC 静态库，那么加入：</p>
<p>#define ACE_USES_STATIC_MFC</p>
<p>5. 如果你希望编译静态版本的 ACE 库，那么在 config.h 中加入：</p>
<p>#define ACE_AS_STATIC_LIBS</p>
<p>6. 如果你希望减少静态库的大小，可以禁止使用 inline，在 config.h 的 #include
"ace/config-win32.h" 前加入：</p>
<p>#define ACE_NO_INLINE</p>
<p>&nbsp;</p>

]]></description><guid>http://www.i170.com/Article/112370</guid><trackback:ping>http://www.i170.com/Article/112370/trackback</trackback:ping><comments>http://www.i170.com/Article/112370#comment</comments><wfw:commentRss>http://www.i170.com/Article/112370/commentRss</wfw:commentRss></item> <item><link>http://www.i170.com/Article/112360</link><title><![CDATA[ACE 体系结构（Mastering Complexity with ACE and Patterns 读书笔记）]]></title><author>killercat</author><category>C/C++</category><pubDate>Mon, 03 Nov 2008 19:42:54  +0800</pubDate><description><![CDATA[<p><img height="359" width="515" src=
"http://www.i170.com/Attach/EE638160-A6F2-4276-8B80-5FD982E273A7"
alt=""></p>
<p>&nbsp;</p>
<p>ACE 的基础是：</p>
<p>1. ACE OS adaptation</p>
<p>2. C++ wrapper facades</p>
<p>它们封装了 OS 的并发网络编程机制</p>
<p>&nbsp;</p>
<p>1）ACE OS adaptation 层封装了 OS API，对上层提供 OS 平台无关的接口。</p>
<p>2）C++ wrapper facades 层位于 OS adaptation 之上，提供了与之相似的功能，这些功能使用 C++
的类封装起来，而不是 C 语言 API。每个 wrapper facade 都包含一个或者一个以上的类。我们可以有选择的继承、聚合这些
wrapper facade。</p>
<p>3）框架层（Framework layer）</p>
<p>框架层在 C++ wrapper facades 层之上，它集成和扩充了 wrapper facade 类。</p>
<p>&lt;1&gt; 事件多路分离和分发框架</p>
<p>ACE Reactor 和 ACE Proactor 实现了 Reactor 模式和 Proactor 模式。</p>
<p>&lt;2&gt; 连接建立和服务初始化框架</p>
<p>ACE Acceptor-Connector 框架实现了 Acceptor-Connector 模式。</p>
<p>&lt;3&gt; 并发框架</p>
<p>ACE 提供了 Task 框架实现了并发模式。</p>
<p>&lt;4&gt; 服务配置框架</p>
<p>ACE 的服务配置框架实现了 Component Configurator 模式。</p>
<p>&lt;5&gt; 流框架</p>
<p>ACE 的流框架实现了 Pipes and Fiters 模式。</p>
<p>4）ACE 网络组件层</p>
<p>组件（component）就是软件系统中被封装的一个部分，ACE 发行包中的组件用于提供以下功能：</p>
<p>&lt;1&gt; 演示 ACE</p>
<p>&lt;2&gt; 提供常见网络服务的可复用实现。如提供日志记录、时间同步等服务的可复用实现。</p>
<p>&nbsp;</p>

]]></description><guid>http://www.i170.com/Article/112360</guid><trackback:ping>http://www.i170.com/Article/112360/trackback</trackback:ping><comments>http://www.i170.com/Article/112360#comment</comments><wfw:commentRss>http://www.i170.com/Article/112360/commentRss</wfw:commentRss></item> <item><link>http://www.i170.com/Article/112274</link><title><![CDATA[RPC]]></title><author>killercat</author><category>Other</category><pubDate>Fri, 31 Oct 2008 21:56:10  +0800</pubDate><description><![CDATA[<p>RPC --- Remote Procedure Calls<br>
RPC 是一种用于分布式系统的技术<br>
RPC 类似于函数调用<br>
RPC 技术使得在不同地址空间的调用成为可能<br>
RPC 技术使得程序员可以避免接触网络通讯的细节<br>
RPC 技术使得程序员开发更加容易<br>
&nbsp;<br>
在一种实现下，RPC 被创建后，实参（arguments）被传递到远程的过程（remote
procedure）之后调用者等待远程过程的相应<br>
1. 请求发送（sends a request）<br>
调用者发送请求，请求调用服务器的某个过程并且传递实参<br>
2. 等待（waits）<br>
阻塞并等待服务器回应（response）或者出现超时（times out）<br>
3. 请求到达（request arrives）<br>
服务器调用例程完成客户端请求<br>
4. 相应客户端（reply to the client）<br>
服务端将结果回应客户端<br>
5. 客户端程序继续执行<br>
客户端收到服务器端回应并继续向下执行<br>
&nbsp;<br>
另外还可以使用异步的方式，这样将复杂但高效。</p>

]]></description><guid>http://www.i170.com/Article/112274</guid><trackback:ping>http://www.i170.com/Article/112274/trackback</trackback:ping><comments>http://www.i170.com/Article/112274#comment</comments><wfw:commentRss>http://www.i170.com/Article/112274/commentRss</wfw:commentRss></item> <item><link>http://www.i170.com/Article/112212</link><title><![CDATA[Ruby 中的 Proc 对象]]></title><author>killercat</author><category>Ruby</category><pubDate>Wed, 29 Oct 2008 14:06:35  +0800</pubDate><description><![CDATA[<p>创建 Proc 对象有多种方式：</p>
<p>1）通过 Proc 类创建 Proc 对象</p>
<p>2）通过 Kernel.proc 创建 Proc 对象</p>
<p>&nbsp;</p>
<p>&lt;1&gt; 通过 Proc 类创建 Proc 对象</p>
<p>a_proc = Proc.new {|a, b| a} # 后接 Block</p>
<p>&lt;2&gt; 通过 Kernel.proc 创建 Proc 对象</p>
<p>a_proc = Kernel.proc {|a, b| a} # 后接 Block</p>
<p>方法 lambda 和 proc 同于类方法 Kernel.proc：</p>
<p>a_proc = lambda {|a, b| a}</p>
<p>a_proc = proc {|a, b| a}</p>
<p>&nbsp;</p>
<p>两种创建 Proc 对象的方式稍微有区别，具体来说在调用 call 方法时有区别：</p>
<p>&lt;1&gt; 通过 Proc.new 创建 Proc 对象</p>
<p>a_proc = Proc.new {|a, b| a} # Block 带有 2 个形参</p>
<p>a_proc.call(1, 2, 3) # call 方法带有 3 个实参</p>
<p>运行正常</p>
<p>&lt;2&gt; 通过 Kernel.new 创建 Proc 对象（lambda、proc 相同）</p>
<p>a_proc = lambda {|a, b| a} # Block 带有 2 个形参</p>
<p>a_proc.call(1, 2, 3) # call 方法带有 3 个实参</p>
<p>执行异常：</p>
<p>wrong number of arguments (3 for 2) (ArgumentError)</p>
<p>&nbsp;</p>

]]></description><guid>http://www.i170.com/Article/112212</guid><trackback:ping>http://www.i170.com/Article/112212/trackback</trackback:ping><comments>http://www.i170.com/Article/112212#comment</comments><wfw:commentRss>http://www.i170.com/Article/112212/commentRss</wfw:commentRss></item> <item><link>http://www.i170.com/Article/112118</link><title><![CDATA[[转载]C/C++中禁用危险API]]></title><author>killercat</author><category>C/C++</category><pubDate>Fri, 24 Oct 2008 09:16:02  +0800</pubDate><description><![CDATA[<p><a href=
"http://blog.csdn.net/chengyun_chu/archive/2008/10/23/3127844.aspx">
http://blog.csdn.net/chengyun_chu/archive/2008/10/23/3127844.aspx</a></p>

]]></description><guid>http://www.i170.com/Article/112118</guid><trackback:ping>http://www.i170.com/Article/112118/trackback</trackback:ping><comments>http://www.i170.com/Article/112118#comment</comments><wfw:commentRss>http://www.i170.com/Article/112118/commentRss</wfw:commentRss></item> <item><link>http://www.i170.com/Article/111688</link><title><![CDATA[C++ 实践 ------ 开发总结（2008.11.15）]]></title><author>killercat</author><category>C/C++</category><pubDate>Sun, 05 Oct 2008 19:56:37  +0800</pubDate><description><![CDATA[<p>1. 慎用继承</p>
<p>继承的一个最大的缺点就是带来了高耦合，这仅仅次于友元。</p>
<p>继承的优点主要有几个：</p>
<p>1）使用通用的方式操作子类对象</p>
<p>2）仅仅需要修改基类方法的实现，使得各个子类的行为改变，而无需一个一个的改变子类的实现</p>
<p>我们必须慎用，否则无法利用到继承带来的好处，反深受其害。</p>
<p>下面给出几个标准：</p>
<p>
1）基类的方法要符合基类的身份，而不是为了使用某个方法而定义某个方法。这样设计出来的基类，才不至于在需求变更时招到致命的打击。</p>
<p>2）区分什么是抽象基类，什么是接口，如果出现以下的情况，我们都需要考虑一下：</p>
<p>&lt;1&gt; 我的接口有两个或者两个以上的类去继承它</p>
<p>&lt;2&gt; 我的抽象基类只有一个类去继承它</p>
<p>除非你和明确你这样做的用意，否则这些现象可能是某种不当设计的警示灯。</p>
<p>关于接口和抽象类，参考：<a href=
"http://www.i170.com/user/killercat/Article_109853">http://www.i170.com/user/killercat/Article_109853</a></p>
<p>&nbsp;</p>
<p>2.
要么完全使用智能指针，要么完全不用指针指针。最好是完全使用它。特别在多个容器保存同一个对象的指针时，务必使用智能指针。</p>
<p>
一般我们把智能指针保存在容器里面，这么做首先能够为我们释放内存提供方法，其次也保证了在复杂逻辑下也能完全避免野指针。一会使用智能指针，一会不使用智能指针的做法是不明智的，就算不会引起任何问题，也不是一个好的习惯。</p>
<p>&nbsp;</p>
<p>3. 小心处理递归组合的问题</p>
<p>&nbsp;</p>
<p>4. 一个函数完成多个任务是绝对错误的</p>
<p>
你当然可以这么做，把多个任务写在一个函数里面，甚至通过一个参数的值来决定应该完成什么样的功能，但是当一个函数需要完成多个任务时，它的体积变大了，这不仅仅是影响了可维护性，也影响的可读性，这样的代码在需求变更下会显得很不稳定。</p>
<p>&nbsp;</p>
<p>5. 出现重复代码是绝对不允许的</p>
<p>
重复的代码出现绝对是噩梦，继承的一个目的就是减少重复的代码，没有人希望在需求变化时，去修改几乎相同的代码，这是容易出错的，也是低质量代码的一个标志。这种代码可维护性差。</p>
<p>&nbsp;</p>
<p>6. 构造你的基础设施，使得你的容错代码不再那么恶心，把 assert 融入进去</p>
<p>单纯的容错代码是恶心的，至少它把 bug 隐藏了起来，使得不那么容易分辨。当然，开发人员不希望发生什么意外的
Crash，因此用容错代码把这个系统死死的包裹起来，不可否认单纯的容错代码的确在一定程度上保障了系统的稳定。这里不是告诫大家不要使用容错性代码，而是保证在错误发生时，错误本身是被暴露给了开发者，而不是被忽略掉了。达到这个目的需要构架一些基础设施，最好的办法是把
assert 融入。</p>
<p>&nbsp;</p>
<p>7. 对代码效率做理性的估计</p>
<p>一些极端的人在写代码时考虑非常多，最极端的人甚至害怕 if 是否会影响到效率，当然 if
语句相对于其他语句来说相对耗时，然后这种时间的损耗是几乎无法检查的。诸如此类的问题，我们必须给予理性的分析。理性的最常用手段是定量，由于笔者是开发游戏服务器的，可以参考：<a href="http://www.i170.com/user/killercat/Article_112475">http://www.i170.com/user/killercat/Article_112475</a>
来看待效率问题。</p>
<p>&nbsp;</p>
<p>8. 要知道什么东西是危险的，知道它为什么危险</p>
<p>c++ 语言或者 stl 库里面有一些很危险的东西，常常需要我们注意，例如：</p>
<p>void SetName(std::string strName)</p>
<p>{</p>
<p>&nbsp;&nbsp;&nbsp; m_strName = strName;</p>
<p>}</p>
<p>// 调用</p>
<p>void main()</p>
<p>{</p>
<p>&nbsp;&nbsp;&nbsp; char* pszName = NULL;</p>
<p>&nbsp;&nbsp;&nbsp; // 一些代码</p>
<p>&nbsp;&nbsp;&nbsp; SetName(pszName); // 危险，能否保证 pszName
是否不为空？</p>
<p>}</p>
<p>在很多书籍强调使用 string 代替 char 数组的时候，你是否想过 string 也有危险的地方，对于：</p>
<p>std::string strName = NULL;</p>
<p>strName.length(); // 行为未定义</p>
<p>除了要知道危险何在，还要清楚一些危险的东西险在哪里，例如可变长参数，参考：<a href=
"http://www.i170.com/user/killercat/Article_111683">http://www.i170.com/user/killercat/Article_111683</a>，此文指出来变长参数带来的问题，知道了变长参数那里比较危险，那么我们就可以合理的使用它，而不是完全的抛弃它。</p>
<p>需要我们注意的陷阱很多，需要大家在实践中慢慢积累。</p>
<p>&nbsp;</p>
<p>9. 小的问题在复杂的项目中将被放大</p>
<p>
一些小的问题，也许大家觉得没有什么，但是在复杂的逻辑中，在一些条件下（例如程序员精力不足，环境比较吵闹等）很可能就出现了，甚至被放大，最终导致了很严重的后果。因此我们必须处处小心。</p>

]]></description><guid>http://www.i170.com/Article/111688</guid><trackback:ping>http://www.i170.com/Article/111688/trackback</trackback:ping><comments>http://www.i170.com/Article/111688#comment</comments><wfw:commentRss>http://www.i170.com/Article/111688/commentRss</wfw:commentRss></item> <item><link>http://www.i170.com/Article/111683</link><title><![CDATA[C++ 实践 ------ 变长参数带来的问题]]></title><author>killercat</author><category>C/C++</category><pubDate>Sun, 05 Oct 2008 15:04:39  +0800</pubDate><description><![CDATA[<p>关于变长参数，可以先参考：<a href=
"http://www.i170.com/user/killercat/Article_110494">http://www.i170.com/user/killercat/Article_110494</a></p>
<p>我们先来看一个例子：</p>
<p>int main()<br>
{<br>
&nbsp;__int64 n64Num = 10;<br>
&nbsp;bool bGood = true;</p>
<p>&nbsp;// 输出结果为 10 0<br>
&nbsp;printf("%d %d", n64Num, bGood);</p>
<p>&nbsp;return 0;<br>
}</p>
<p>&nbsp;</p>
<p>对于上面这个程序，不注意，还以为输出结果是 10 1，由此程序引入本文讨论的主题 ------ 变长参数。</p>
<p>首先我们需要达成一个共识：变长参数关闭了编译器对类型的识别和隐式转换。</p>
<p>&nbsp;</p>
<p>1）共识：变长参数关闭了编译器对类型的识别和隐式转换</p>
<p>
使用带变长参数的函数，只能通过第一个参数来判断匿名参数的个数和类型，而编译器本身是无法识别的，换而言之，编译器对参数类型识别的功能被关闭了。我们来看一个函数：</p>
<p>// 接受一个 int 类型的参数</p>
<p>void Func(int nNum)<br>
{<br>
&nbsp;std::cout &lt;&lt; nNum &lt;&lt; std::endl;<br>
}</p>
<p>&nbsp;</p>
<p>// 比较使用变长参数和不使用变长参数</p>
<p>int main()<br>
{<br>
&nbsp;double dNum = 10;</p>
<p>&nbsp;//&nbsp;使用非变长参数函数处理 dNum<br>
&nbsp;Func(dNum);</p>
<p>&nbsp;// 使用变长参数的函数处理 dNum<br>
&nbsp;printf("%d\n", dNum);</p>
<p>&nbsp;</p>
<p>&nbsp;return 0;<br>
}<br>
运行上面的程序，输出结果是：</p>
<p>10</p>
<p>0</p>
<p>通过这个程序，我们可以很明显的看出带有变长参数的函数和一般函数的差别，函数 Func
进行了参数的隐式转换，这个是编译器帮助程序员完成的，而对于变长参数，编辑器没有办法识别参数的类型，无法进行类型转换，因此出现了错误的结果。</p>
<p>我们看一个真实项目的缩影：</p>
<p>__int64 GetID()<br>
{<br>
&nbsp;return 10;<br>
}</p>
<p>bool IsDie()<br>
{<br>
&nbsp;return true;<br>
}</p>
<p>int main()<br>
{</p>
<p>&nbsp;// 错误的结果<br>
&nbsp;printf("%d %d\n", GetID(), IsDie());</p>
<p>&nbsp;return 0;<br>
}<br>
我们看到了，使用函数 GetID() 时，程序员没有注意到它的返回类型，因此错误发生了。或者你有可能遇见这样的情况，最初函数
GetID() 的返回值为 int，程序能够正常运行，后来由于种种原因，GetID() 的返回值变成了
__int64，那么错误产生了，这样的错误不容易发现。</p>
<p>&nbsp;</p>
<p>2）解决之道</p>
<p>
很多书籍都告诫我们，要慎用变长参数，甚至有一些书籍鼓励程序员完全抛弃变长参数。这里，笔者认为，在一定的条件下，可以合理的使用。</p>
<p>这里，笔者建议这样使用：</p>
<p>printf("%d %d", (int)GetID(), (int)IsDie());</p>
<p>即对每一个匿名参数进行显式类型转换，或者这样用：</p>
<p>int nID = GetID();</p>
<p>int bDie = IsDie();</p>
<p>printf("%d %d", nID, bDie);</p>
<p>即：帮助编译器完成类型转换的工作。</p>

]]></description><guid>http://www.i170.com/Article/111683</guid><trackback:ping>http://www.i170.com/Article/111683/trackback</trackback:ping><comments>http://www.i170.com/Article/111683#comment</comments><wfw:commentRss>http://www.i170.com/Article/111683/commentRss</wfw:commentRss></item> <item><link>http://www.i170.com/Article/111668</link><title><![CDATA[C++ 实践 ------ 指针上的奇技淫巧]]></title><author>killercat</author><category>C/C++</category><pubDate>Sat, 04 Oct 2008 20:22:38  +0800</pubDate><description><![CDATA[<p>在这里，我使用一个程序作为本文的开始：</p>
<p>#include &lt;iostream&gt;<br>
#include &lt;conio.h&gt;<br>
<br>
class CTest<br>
{<br>
public:<br>
&nbsp;&nbsp;&nbsp; int Func(int nNum);<br>
private:<br>
&nbsp;&nbsp;&nbsp; int m_nNum;<br>
};<br>
<br>
int CTest::Func(int nNum)<br>
{<br>
&nbsp;&nbsp;&nbsp; return nNum;<br>
}<br>
<br>
int main()<br>
{<br>
&nbsp;&nbsp;&nbsp; CTest* pTest = NULL;<br>
&nbsp;&nbsp;&nbsp; CTest* pTest2;<br>
&nbsp;&nbsp;&nbsp; CTest* pTest3 = new CTest;<br>
&nbsp;&nbsp;&nbsp; delete pTest3;<br>
<br>
&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; pTest-&gt;Func(10) &lt;&lt;
std::endl;</p>
<p>&nbsp;&nbsp;&nbsp; // 在 VS2003 下，应该注释此行再运行<br>
&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; pTest2-&gt;Func(10) &lt;&lt;
std::endl;<br>
&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; pTest3-&gt;Func(10) &lt;&lt;
std::endl;<br>
<br>
&nbsp;&nbsp;&nbsp; system("PAUSE");<br>
&nbsp;&nbsp;&nbsp; return 0;<br>
}</p>
<p>&nbsp;</p>
<p>读者可以在 MSVC6，VS2003，GCC 下测试本程序，除了 VS2003 下，需要注释"std::cout
&lt;&lt; pTest2-&gt;Func(10) &lt;&lt; std::endl;"之外，程序都能正常运行。</p>
<p>我们可以得到这样一个结论，如果某成员函数没有访问成员变量，那么无论指针的值如何，对此成员函数的访问通常是有效的。</p>
<p>这种技巧被称之为奇技淫巧并不为过，不过云风有一文《<a href=
"http://blog.codingnow.com/2007/07/robust.html">更健壮的 C++
对象生命期管理</a>》谈及到它的用法（笔者对此持保留态度）。一般来说，我们是不会使用这种技巧的。我们来看看真实项目中的一个缩影：</p>
<p>// Thing<br>
class CThing<br>
{<br>
public:<br>
&nbsp;&nbsp; &nbsp;void Dosomething();<br>
};<br>
<br>
<br>
// Thing Manager<br>
class CThingMgr<br>
{<br>
public:<br>
&nbsp;&nbsp; &nbsp;// Check<br>
&nbsp;&nbsp; &nbsp;bool Check(int nID);<br>
&nbsp;&nbsp; &nbsp;// Remove<br>
&nbsp;&nbsp; &nbsp;void Remove(int nID);<br>
&nbsp;&nbsp; &nbsp;CThing* GetThing(int nID);<br>
private:<br>
&nbsp;&nbsp; &nbsp;std::map&lt;int /*ID*/, CThing*&gt;
m_mapThing;<br>
};<br>
<br>
bool CThingMgr::Check(int nID)<br>
{<br>
&nbsp;&nbsp; &nbsp;// Do something without variables<br>
&nbsp;&nbsp; &nbsp;return true;<br>
}<br>
<br>
void CThingMgr::Remove(int nID)<br>
{<br>
&nbsp;&nbsp; &nbsp;std::map&lt;int, CThing*&gt;::iterator it =
m_mapThing.begin();<br>
&nbsp;&nbsp; &nbsp;if (m_mapThing.end() == it)<br>
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; return;<br>
<br>
&nbsp;&nbsp; &nbsp;m_mapThing.erase(it);<br>
}</p>
<p>&nbsp;</p>
<p>使用：</p>
<p>CThing* pThing = mapThing-&gt;GetThing(nID);<br>
// 做了很多事情之后<br>
mapThing-&gt;Remove(nID);<br>
// 又做了很多事情<br>
pThing-&gt;Dosomething(); // 这里 pThing 已经被删除</p>
<p>&nbsp;</p>
<p>我们可以看到，如果函数 Dosomething 没有访问 CThing
的成员变量，那么运行将很正常。假如某天，某个维护此代码的人员加入了一些访问成员变量的代码，那么程序将崩溃，并且这种错误非常难以发现（特别是维护人员和开发人员不是同一个人时）。</p>
<p>我们可以考虑使用一些技术来解决这个问题，在此笔者经过分析考虑认为使用智能指针来解决是最佳方案。</p>

]]></description><guid>http://www.i170.com/Article/111668</guid><trackback:ping>http://www.i170.com/Article/111668/trackback</trackback:ping><comments>http://www.i170.com/Article/111668#comment</comments><wfw:commentRss>http://www.i170.com/Article/111668/commentRss</wfw:commentRss></item> <item><link>http://www.i170.com/Article/111665</link><title><![CDATA[VI 编辑器（2） ------ 基础（2）]]></title><author>killercat</author><category>Other</category><pubDate>Sat, 04 Oct 2008 19:01:43  +0800</pubDate><description><![CDATA[<p>如果你还不了解 VIM 那么你可以参考：<a href=
"http://www.i170.com/user/killercat/Article_111571">http://www.i170.com/user/killercat/Article_111571</a><br>

<br>
使用 VIM 做小的修改<br>
我们通常使用 x 命令来删除一个字符。实际上，我们可以选择更加好用的 d 命令来完成删除操作。<br>
<br>
d 命令<br>
d 命令是一个特殊的命令，后面需要接上动作（motion），使用实例：<br>
1. dw<br>
-----------------------------------------<br>
&nbsp;&nbsp; &nbsp;I am a boy.<br>
-----------------------------------------<br>
光标位于 b 上时，输入 dw 将删除 boy 这个词。如果需要删除多个词，例如，删除 4 个词，可以使用 d4w。<br>
这里的 w 或者 4w 就是动作（motion）<br>
2. d$ 和 d0<br>
$ 的含义是移动到句尾，那么 d$ 的含义就是删除从光标处到句尾的所有字符。<br>
0 的含义是移动到句首，那么 d0 的含义就是删除从光标处到行首的所有字符（不包括光标下的字符）。<br>
3. dnh、dnj、dnk、dnl（这里 n 为计数）<br>
d4h 表示向左删除 4 个字符<br>
d4j 表示向下删除 4 个字符<br>
d4k 表示向上删除 4 个字符<br>
d4l 表示向右删除 4 个字符<br>
4. dG 和 dgg<br>
我们用 dG 来删除从此行到文件尾的所有字符。<br>
我们用 dgg 来删除从此行到文件头的所有字符。<br>
5. dfx（这里 x 为一个字符）<br>
我们知道 fx（x 为一个字符）用于找到 x 字符，那么 dfx 表示删除从此处到 x 字符处的所有字符（包括 x 字符）<br>
<br>
c 命令<br>
c 命令和 d 命令非常相似，也是用来删除字符，只是执行完 c 命令后会进入到插入模式，等同于 d 命令执行完之后输入
a（append）。例如：<br>
cgg 用于删除从此行到文件头的所有字符，删除后进入插入模式。<br>
<br>
y 命令<br>
y 命令的含义和 c 命令以及 d 命令相差甚远，但是它们的用法相似，后面都需要加上一个动作，y 命令用于复制字符（p
命令用于粘贴字符）。例如：<br>
------------------------------------------<br>
&nbsp;&nbsp; &nbsp;I am a boy.<br>
------------------------------------------<br>
假定光标位于 b 上，输入 yw（或者 yfy）那么单词 boy 将被复制。<br>
<br>
yy dd 和 cc<br>
yy 命令用于复制一行（包括行尾的换行）<br>
dd 命令用于删除一行（包括行尾的换行）<br>
cc 命令用于删除一行后进入插入模式（不包括行尾的换行）<br>
<br>
快捷键<br>
dl ------ x ------ 删除光标下的一个字符<br>
cl ------ s ------ 删除光标下的一个字符然后进入插入模式<br>
dh ------ X ------ 删除光标左的一个字符<br>
cc ------ S ------ 删除一整行并进入插入模式<br>
d$ ------ D ------ 删除到行尾<br>
c$ ------ C ------ 删除到行尾并进入插入模式<br>
yy ------ Y ------ 复制一整行<br>
<br>
替换单个字符<br>
r 命令用于替换单个字符，在你需要替换的字符上，敲击 r 然后输入需要替换的字符。要注意的是，r 命令后面只能接上一个字符。r
命令可以有计数前缀，例如：<br>
------------------------------------------<br>
&nbsp;&nbsp; &nbsp;hello<br>
------------------------------------------<br>
假定光标在 h 上，输入 4rx 后，hello 变成了 xxxxo<br>
<br>
重复一个修改<br>
. 命令用于重复最后一次的修改操作（仅仅是修改操作）<br>
删除就是一种修改操作，例如，我们现在想删除 &lt;a&gt; 和 &lt;/a&gt; 那么我们可以在字符 &lt; 上敲入
df&gt; 删除 &lt;a&gt;，然后把光标移动到 &lt;/a&gt; 的&lt; 字符上，然后敲入 . 命令<br>
<br>
使用可视模式<br>
在普通模式下，输入 v 可以进入可视模式。<br>
可视模式可以使得我们方便的复制和删除字符串。首先，我们输入 v 进入可视模式，然后移动光标，那么移过的字符将会高亮显示，这时如果输入
y 那么被选择的高亮字符将被复制，如果输入 d（或者 c）那么被选择的高亮字符将被删除。<br>
------------------------------------------<br>
&nbsp;&nbsp; &nbsp;hello boy.<br>
------------------------------------------<br>
假定光标在 h 上，我们在普通模式下敲入 vlllllld，那么 hello 和后面的空格将被删除掉。<br>
如果使用 V
命令也可以进入可视模式，这个时候整行都被选中了，这个时候，只能进行上下移动，左右移动将无效，每次上移或者下移都会选中一行。例如 Vjj
那么你会选择 3 行。<br>
可视模式下，使用 o 命令可以移动光标到被选择的文字的另一端。<br>
<br>
关于粘贴的更多内容<br>
命令 P 和命令 p 一样用于粘贴，只是命令 P 会把粘贴的内容粘贴在光标前。<br>
删除字符后，能够使用 p 来粘贴被删除的字符，例如：<br>
------------------------------------------<br>
&nbsp;&nbsp; &nbsp;hello.<br>
------------------------------------------<br>
我们在 hello 所在行输入 dd，删除这一行，那么这个时候可以使用 p
来粘贴这一行（无需先复制），因此我们在复制的时候需要注意，如果出现了删除操作，那么复制的内容将发生变换。<br>
------------------------------------------<br>
&nbsp;&nbsp; &nbsp;hlelo.<br>
------------------------------------------<br>
如果我们输入 hello 成了 hlelo，我们需要修改 l 和 e 的位置，这个时候，我们只需要把光标移动到 l 上，然后敲入
xp。<br>
<br>
使用剪贴板<br>
我们使用命令 y 等复制的内容并不会放入剪贴板，如果需要把复制的内容放入剪贴板，那么可以在复制命令前面加上
"*，对于粘贴也一样，如果需要粘贴来自剪贴板的字符，使用 "*p</p>

]]></description><guid>http://www.i170.com/Article/111665</guid><trackback:ping>http://www.i170.com/Article/111665/trackback</trackback:ping><comments>http://www.i170.com/Article/111665#comment</comments><wfw:commentRss>http://www.i170.com/Article/111665/commentRss</wfw:commentRss></item> <item><link>http://www.i170.com/Article/111571</link><title><![CDATA[VI 编辑器（1） ------ 基础]]></title><author>killercat</author><category>Other</category><pubDate>Mon, 29 Sep 2008 17:30:02  +0800</pubDate><description><![CDATA[<p>VIM 编辑器使用手册<br>
1. VIM 中的"~"开头的行是文件中不存在的行<br>
<br>
2. VIM 是一个多模式的编辑器<br>
&lt;1&gt;
普通模式：在普通模式下，输入的字符将被解释为命令，我们输入的字符将显示在最后一行。无论在何种模式下，敲击"ESC"能够进入到普通模式。如果需要输入命令，那么应该切换到普通模式下。<br>

&lt;2&gt; 插入模式：在插入模式下，输入的字符将成为插入的文本。通常情况，在普通模式下，敲入"i"（insert
的缩写）或者"a"（append 的缩写）进入插入模式。<br>
&lt;3&gt; 可视模式：在普通模式下，输入 v（或者 V）进入可视模式，可视模式可以方便的删除一串字符。<br>
&lt;4&gt; 替换模式：在普通模式下，输入 R 进入替换模式。在插入模式下，可以按下 Insert
键也可以进入插入模式。<br>
<br>
3. VIM 中的命令<br>
前面已经说过，在普通模式下输入的字符将被解释为命令，命令是区分大小写的，VIM 中的命令显示在最后一行。<br>
&lt;1&gt; : 命令（冒号命令）<br>
冒号命令以":"开头，例如，:w 表示保存本文件。输入冒号命令后，需要使用回车表示命令的结束。<br>
&lt;2&gt; 移动光标命令<br>
在普通模式下可以使用移动光标命令:<br>
&nbsp;&nbsp; &nbsp;h&nbsp;&nbsp; &nbsp;左<br>
&nbsp;&nbsp; &nbsp;j&nbsp;&nbsp; &nbsp;下<br>
&nbsp;&nbsp; &nbsp;k&nbsp;&nbsp; &nbsp;上<br>
&nbsp;&nbsp; &nbsp;l&nbsp;&nbsp; &nbsp;右<br>
当然也可以使用方向键（如果你不介意过远的移动你的手），而实际上，移动光标命令的出现是有历史原因的，以前有一些键盘是没有方向键的。我们可以这样来记忆移动光标命令：<br>

-----------hjkl-----------<br>
-----------&nbsp; k -----------<br>
-----------h&nbsp; l-----------<br>
----------- j&nbsp; -----------<br>
记住移动光标命令的最好方式是使用它，而不是使用方向键，不过，不要忘记输入前切换到普通模式（按下"ESC"）<br>
&lt;3&gt; 删除字符命令<br>
x 为删除字符命令，在一个字符上，输入 x 可以删除它。当然，也可以有其他的方法删除字符，例如，在输入模式下，使用 Backspace
按键来删除字符等。<br>
&lt;4&gt; 删除一行的命令<br>
dd 用来删除一行（d 是一个比较复杂的命令，后面会讲到）<br>
&lt;5&gt; J 命令（大写的 J）<br>
J 命令会删除当前行的换行符，这意味着两行将连接在一起，它们之间使用空格分开（j 是移动光标命令）。<br>
&lt;6&gt; u 命令、U 命令和 Ctrl-r 命令<br>
u（undo）命令用于撤销上一个编辑操作，注意，u 命令仅仅用于撤销上一次的编辑操作而不是所有操作，例如，我们使用了 :w
命令来保存文件，无法使用 u 命令来撤销保存。<br>
U 命令用于撤销在一行上的编辑操作（而非一个编辑操作）<br>
Ctrl-r 用于恢复上一个撤销的编辑操作（redo），它与 u 命令对应，而无法恢复因为 U 命令而造成的改变。<br>
&lt;8&gt; i 命令（insert）<br>
前面已经介绍过，在普通模式下，输入 i 命令，将进入插入模式，具体来说，用户将能够在光标前的位置插入字符。<br>
&lt;9&gt; a 命令（append）<br>
和 i 命令类似，通过在普通模式下，输入 a 命令，进入到插入模式，与 i 命令不同的是，用户将能在光标后的位置插入字符。<br>
&lt;10&gt; o 命令和 O 命令<br>
o 命令用于在当前行的下面插入一个新行，O 命令用于在当前行的上面插入一个新行。<br>
&lt;11&gt; 指定计数<br>
这是一个非常有用的功能，例如，我们需要向上移动 10 行，那么我们可以在普通模式下输入 10k 或者 10
和上方向键。计数能够使用在很多的命令前面但是注意它只能用于命令的前面。我们可以通过 10k 来向上移动 10 行，也可以用 5x
来删除光标后的 5 个字符。<br>
&lt;12&gt; ZZ 命令<br>
ZZ 命令用于保存并且退出，同样的，我们可以使用冒号命令 :wq 来实现相同的功能（但是冒号命令输入较为麻烦）<br>
&lt;13&gt; 冒号命令 :wq<br>
用于保存退出，同于 ZZ。<br>
&lt;14&gt; 冒号命令 :q!<br>
用于不保存退出，这里 ! 是强制命令修饰符，表示将强制执行命令。通常来说，强制命令修饰符用在命令之后。<br>
&lt;15&gt; 冒号命令 :w<br>
用于保存文件<br>
&lt;16&gt; 冒号命令 :q<br>
用于退出，注意，如果文件修改了，但是没有保存，那么无法使用 :q 退出<br>
&lt;17&gt; 冒号命令 :e<br>
用于载入原来的文件，如果文件没有保存，那么无法载入，可以使用强制命令修饰符来载入未保存的文件并且编辑它。即是 :e! 等同于 :q!
然后再打开文件。我们还可以这样使用：":e 文件名"来打开文件进行编辑，例如 :e d:/vim.txt。<br>
&lt;18&gt; 冒号命令 :help<br>
用于获得帮助，使用 F1 也有相同的效果。注意一下，帮助系统里面存在超级链接，一些文字使用了 || 括起来了，这时候把光标移动到两个
|| 之间并按 Ctrl-] 那么就会发生跳转。VIM 中使用术语标记（tag）而不是超级链接。使用 Ctrl-] 跳转后，使用
Ctrl-O 或者 Ctrl-T 跳回原来的位置。<br>
VIM 中的 :help 命令异常强大，如果需要获得特定的主题，那么可以使用如下命令：<br>
:help {主题}<br>
这里，主题可以是一个命令的名字，例如 :help x<br>
主题可以是一个命令的关键词，例如 :help find<br>
主题可以是包含控制符的命令，例如 :help Ctrl-h<br>
注意一下，:help Ctrl-h 表示普通模式下的 Ctrl-h，如果需要了解插入模式下的 Ctrl-h 可以使用 :help
i_Ctrl-h<br>
另外，我们在未保存文件并退出的时候，VIM 会提示错误：<br>
E37: 已修改但尚未保存（E37: No write since last change）这个时候，我们可以通过 :help E37
来寻求帮助。<br>
&lt;19&gt; 冒号命令 :!<br>
:! 用于执行一个外部的程序，例如在 Windows 下，:!cmd 用于打开一个 dos 窗口，通常来说，我们可以这样用：<br>
:read !xxx 这里 xxx 表示一个外部程序的名称<br>
:write !xxx 这里 xxx 表示一个外部程序的名称<br>
例如：<br>
:read !dir --- 用于将执行 dir 程序并且将输出结果插入到当前位置<br>
:write !wc（unix 或类 unix 系统下的一个命令） --- 用于将文件内容输出到 wc 命令中<br>
我们希望在当前位置插入一个时间，那么我们可以这样做 :read !date/t（windows 系统）<br>
这里的 ! 不是强制命令修饰符，而是表示了一个外部程序名的开始。<br>
&lt;20&gt; 词移动命令<br>
w 命令用于向前移动一个词，3w 表示移动 3 个词。<br>
b 命令用于向后移动一个词，3w 表示移动 3 个词。<br>
&lt;21&gt; 移动到行首（文件首）和行尾（文件尾）的命令<br>
$ 用于移动到行尾或者使用 &lt;End&gt; 键<br>
^ 用于移动到行首也可以使用 0（零）或者使用 &lt;Home&gt; 键<br>
gg 用于移动到文件首或者使用 :0（零）<br>
G 用于移动到文件尾或者使用 :$<br>
这里我推荐不要使用冒号命令<br>
使用 0 回到行首使用 gg 回到文件首<br>
使用 $ 回到行尾使用 G 回到文件尾<br>
另外，在插入模式下，可以方便的使用 &lt;Home&gt; 回到行首并且使用 &lt;End&gt; 回到行尾。<br>
对于命令 $ 可以使用计数前缀，例如 10$ 表示向下移动 9 行并且移动到行尾。<br>
&lt;22&gt; f 命令和 F 命令<br>
f（find）命令用于移动到本行的某个字符，例如 fx 用于向右移动到本行的第一个 x 字符。<br>
F 命令一样用于移动到本行的某个字符，只是 F 命令用于向左移动，注意，不论是 f 还是 F
命令后面都只能加上一个字符（不能是多个字符）。f 和 F 命令能够使用计数前缀。<br>
&lt;23&gt; % 命令<br>
% 命令用于在匹配的 () 或者 {} 移动光标。例如：<br>
int main()<br>
{<br>
&nbsp;&nbsp; &nbsp;// something<br>
}<br>
我们在第一个 { 上敲击 %，光标即会跳到 } 上去，再次敲击又会跳到 { 上去。<br>
&lt;24&gt; 带有计数前缀的 G 命令<br>
我们知道 G 命令用于跳到文件尾（gg 用于跳到文件头），带有计数前缀的 G 命令用于跳转到某行，例如 10G 用于跳转到第 10
行。gg 命令的效果同于 1G。<br>
&lt;25&gt; 带计数前缀的 % 命令<br>
带计数前缀的 % 命令用于实现跳转，例如 50% 用于跳转到文件的中间位置，100% 表示跳转到文件的结尾。<br>
&lt;26&gt; H M L 命令<br>
这三个命令用于在当前页中跳转（而不是整个文件），H（Home）用于跳转到本页最上行，L（Last）用于跳转到本页最下行，M（Middle）用于跳转到本页中间。<br>

&lt;27&gt; Ctrl-G 命令<br>
Ctrl-G 命令用于显示如下信息：<br>
"user_03.txt" line 233 of 650 --35%-- col 45-52<br>
&lt;28&gt; :set number<br>
这是一个很有用的指令，用于在每行前面显示一个行号。如果需要取消这种显示，输入 :set nonumber<br>
number 是一个选项，除了 number 还有很多其他的选项。<br>
&lt;29&gt; :set ruler<br>
用于在最后一行显示出当前光标所在的行和列数。<br>
&lt;30&gt; Ctrl-u 和 Ctrl-d<br>
Ctrl-d（down）用于向下移动半屏<br>
Ctrl-u（up）用于向上移动半屏<br>
&lt;31&gt; Ctrl-f 和 Ctrl-b<br>
Ctrl-f（front）用于向前滚动一屏<br>
Ctrl-b（back）用于向后滚动一屏<br>
&lt;32&gt; Ctrl-e<br>
Ctrl-e 用于屏幕上滚<br>
&lt;33&gt; zz 命令 zt 命令 zb 命令<br>
zz 命令将移动文本，使得光标处于屏幕的中间（区别于 M）<br>
zt 命令将移动文本，使得光标处于屏幕的顶部（区别于 L）<br>
zb 命令将移动文本，使得光标处于屏幕的底部（区别于 H）<br>
&lt;34&gt; 一些习惯问题<br>
很多用户习惯使用 Ctrl + z 来撤销编辑操作，使用 Ctrl + y 来恢复编辑操作，使用 Ctrl + s 来保存，在
gvim 中，这些操作都是允许的并且可以使用在插入模式下。<br>
<br>
4. 查找<br>
简单的查找一个字符串使用 / 后接上一个字符串，例如 /屏幕。<br>
/ 命令和 : 命令类似，都是使用回车结束。回车后，使用 n 移到下一个匹配的位置（N 和 n 相反），n 可以接受计数前缀。<br>
? 命令和 / 类似，但是进行反方向查找。<br>
如果需要忽略大小写，使用 :set ignorecase，如果要区分大小写，使用 :set
noignorecase，不过有一个最好的设置方式是 :set ignorecase smartcase
含义是，如果出现大写，那么区分大小写，否则不作区分。<br>
输入 / 后，点击方向键可以查找搜索的记录。: 开头的命令，也有历史记录。<br>
在某个单词上点击 *，那么就会在全文中查找这个单词。<br>
查找实际上可以非常复杂，VIM 中可以使用正则表达式来查找，这里有一个简单的例子：<br>
/^the 用于查找出现在行首的 the<br>
/^the$ 用于查找某行，这样的行只有一个 the<br>
如果需要了解更多关于正则表达式的内容，请参考有关书籍（正则表达式并非 VIM 专有）。<br>
这里介绍一些很简单又实用的技巧：<br>
. 用于表示任意的字符，例如 /c.m 可以表示 cam com ctm 等等。<br>
如果我们需要查找真正的 . 字符，我们可以使用 \. 这个类似于 C 语言中的转义字符。处理 . 还有一些字符是需要加 的。<br>
<br>
5. 标记（tags）<br>
标记表示了一个可以跳转的位置。例如使用命令 G 时，VIM 会记录当前位置，这个位置成为一个标记，命令 '（单引号）和 `
用于在各个标记之间跳转。VIM 中的标记和 microsoft visual stdio 中的 bookmarks
类似但是更加强大。正如我们这里所说的，使用 G
命令将产生一个标记，注意，并不是所有的命令都会产生标记，实际的情况是发生跳转时可能产生标记，所谓的跳转是：每次光标移动到本行之外的行，就发生了跳转。<br>

Ctrl-o 用于跳转到一个较老的标记，而 Ctrl-i 用于跳转到一个较新的标记。<br>
命令 :jumps 用于输出所有的标记。<br>
命名标记：<br>
命名标记可以完成类似 visual stdio 中 bookmark 的功能（更加强大和灵活），我们使用命令 m，例如：<br>
在某次输入 ma 的含义是用 a 标记此处，然后如果我们需要跳回到这里，可以使用 `a（跳回到这个位置）或者使用
'a（跳回到这个位置所处的行的行首）<br>
同样，我们可以使用 :marks 命令来查看所有的命名标记。我们可以看到一些特殊的标记，这里就不做介绍了。<br>
<br>
6. 强大的配置文件<br>
VIM 有一个 _vimrc（windows）或者
.vimrc（linux）配置文件，我们可以使用这个配置文件做很多事情。下面是一个例子：<br>
---------------------------------------------------<br>
" 配置文件中使用 " 开始表示注释<br>
" 快速的保存，无需使用 :w! 来保存<br>
nmap w :w!&lt;cr&gt;<br>
---------------------------------------------------<br>
通过上面的配置，我们就可以通过敲入 w 来保存文件（原来的 w 是词移动命令）。VIM
允许我们使用自己喜欢的方式来进行操作，在不同的机器或者操作系统上，只需要拷贝配置文件就 ok 了。<br>
<br>
VIM 十分复杂，这种复杂使得大多数人望而却步。</p>

]]></description><guid>http://www.i170.com/Article/111571</guid><trackback:ping>http://www.i170.com/Article/111571/trackback</trackback:ping><comments>http://www.i170.com/Article/111571#comment</comments><wfw:commentRss>http://www.i170.com/Article/111571/commentRss</wfw:commentRss></item> <item><link>http://www.i170.com/Article/111097</link><title><![CDATA[C++ 内置类型（未完成）]]></title><author>killercat</author><category>C/C++</category><pubDate>Wed, 17 Sep 2008 09:51:08  +0800</pubDate><description><![CDATA[<p>1. 整形</p>
<p>C++ 中的整形有 8 种，包括 char, short, int, long 以及它们的无符号版本。C++
的整形的大小是平台依赖的，不同平台可能表现出不同的大小，但是也有一定的规定：</p>
<p>&lt;1&gt; short 至少是 16 bits</p>
<p>&lt;2&gt; int 至少和 short 一样长</p>
<p>&lt;3&gt; long 至少是 32 bits 并且至少和 int 一样长</p>
<p>目前来说（2008/09/17），在 32 bits 的操作系统平台上，short 为 16 bits，int 为 32
bits，long 为 32 bits</p>
<p>在头文件 climits（limits.h）中定义了符号常量表示了整数类型的信息，例如定义了 INT_MAX 表示了 int
类型能够保存的最大值，定义了 CHAR_MAX 表示 char 类型的最大值等。</p>
<p>C99 添加了 2 个新的类型，long long 和 unsigned long long，这两种类型至少是 64
bits，并且它们至少和 long 和 unsigned long 一样长。</p>
<p>&nbsp;</p>
<p>2. 整形的溢出问题</p>
<p>&lt;1&gt; 无符号数的溢出</p>
<p>unsigned int nNum = 0xffffffff;</p>
<p>++nNum; // nNum 为 0</p>
<p>注意，nNum 表示了一个 32 bits 的内存区域，当溢出发生时，绝对不会影响到这 32 bits
区域之外的区域，下同。</p>
<p>&lt;2&gt; 有符号数的溢出</p>
<p>int nNum = 0x7fffffff; // INT_MAX<br>
++nNum; // nNum 为最小的整数 0x80000000</p>
<p>对于有符号数的表示法，可以参考：<a href=
"http://www.i170.com/user/killercat/Article_110496">http://www.i170.com/user/killercat/Article_110496</a></p>
<p>&nbsp;</p>
<p>3. 字符常量</p>
<p>C++ 中，有三种不同的方式（十进制、八进制、十六进制）来书写整形常量，我们通过 Literals
的前两位来表示整形常量的基数（n 进制）：</p>
<p>第一位为 1 - 9，表示是一个十进制数（基数为 10）</p>
<p>例如：std::cout &lt;&lt; "Year: " &lt;&lt; 2008 &lt;&lt;
std::endl;</p>
<p>这里的字符常量（Literals）2008 将被存储为什么样的类型？答案是，如果没有其他理由（例如，使用了后缀），那么将其储存为
int 类型。字符常量的后缀决定了字符常量被存储的类型：</p>
<p>2008l 或者 2008L ---- long 类型常量</p>
<p>2008ul 或者 2008UL ---- unsigned long 类型常量</p>
<p>&nbsp;</p>

]]></description><guid>http://www.i170.com/Article/111097</guid><trackback:ping>http://www.i170.com/Article/111097/trackback</trackback:ping><comments>http://www.i170.com/Article/111097#comment</comments><wfw:commentRss>http://www.i170.com/Article/111097/commentRss</wfw:commentRss></item> <item><link>http://www.i170.com/Article/111057</link><title><![CDATA[计算机中数据的表示（未完成）]]></title><author>killercat</author><category>Architecture</category><pubDate>Tue, 16 Sep 2008 10:20:22  +0800</pubDate><description><![CDATA[<p>计算机使用位（bits）来表示值，例如：</p>
<p>1101 表示了十进制的 13（2^3 + 2^2 + 0 + 2^0 = 13）</p>
<p>二进制数不方便书写和阅读，十六进制数用一位表示了二进制的四位便于阅读和书写：</p>
<p>1101 表示十六进制的 D（A 表示 10），注意，对于十六进制的表示中的 A，B，C，D，E，F 是不区分大小写的，也就是说
a 表示 10，b 表示 11 等也是可行的。</p>
<p>&nbsp;</p>
<p>1. X 进制的数和十进制数的转换</p>
<p>对于 X 进制的数和十进制数的转换公式是：</p>
<p>对表达式 (X^(n-1))*m 求和，其中 n 表示位数，m 表示本位的值，例如：</p>
<p>十六进制数 --- 9D7A</p>
<p>对于第 4 位：(16^(4-1))*9 = 36864</p>
<p>对于第 3 位：(16^(3-1))*13 = 3328</p>
<p>对于第 2 位：(16^(2-1))*7 = 112</p>
<p>对于第 1 位：(16^(1-1))*10 = 10</p>
<p>求和 36864 + 3328 + 112 + 10 = 9D7A，即是十六进制的 9D7A 表示十进制的 40314</p>
<p>&nbsp;</p>
<p>2. 二进制和十六进制的转换</p>
<p>二进制的四位对应了十六进制的一位，因此，我们可以这样转换：</p>
<p>100 1000 1101</p>
<p>从右到左取 4 位：1101 表示 D（把这个四位二进制数先转换位十进制数，然后用十六进制数表示，下同）</p>
<p>再取 4 位：1000 表示 8</p>
<p>剩余 3 位：100 表示 4</p>
<p>因此，100 1000 1101 可以转换为 48D 这个十六进制数</p>
<p>&nbsp;</p>
<p>3. 十进制转换为 X 进制</p>
<p>做法：除 X 取余，例如：</p>
<p>1000 转化为 16 进制数：</p>
<p>1000 / 16 = 62 余 8 ---- 8 为最低位</p>
<p>62 / 16 = 3 余 14 ---- 14 为次低位</p>
<p>3 / 16 = 0 余 3 ---- 3 为最高位</p>
<p>结果是：1000 = 3E8</p>
<p>当然，最简单的办法就是用计算器了，只要一个按钮，就可以完成转换 ^_^</p>
<p>&nbsp;</p>
<p>4. 字符的表示</p>
<p>计算机只能保持 10 序列，而不能直接保持字符，因此我们通过建立字符和 10 序列的映射，通过保存 10
序列来保存字符，这种映射被称为之编码。字符和 10 序列到底以何种方式隐射，这就由编码方式决定，ASCII
就是常见的编码方式，例如它规定了用 43（1000011）表示大写的 'C' 字符：</p>
<p>// ASCII 编码方式举例<br>
int main()<br>
{<br>
&nbsp;&nbsp;&nbsp; const int SIZE = 18;</p>
<p>&nbsp;&nbsp;&nbsp; // 按 ASCII 编码方式对字符串 "Computers are fun."
进行编码<br>
&nbsp;&nbsp;&nbsp; int output[SIZE] =<br>
&nbsp;&nbsp;&nbsp; {<br>
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 0x43, 0x6f, 0x6d, 0x70,<br>
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 0x75, 0x74, 0x65, 0x72,<br>
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 0x73, 0x20, 0x61, 0x72,<br>
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 0x65, 0x20, 0x66, 0x75,<br>
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 0x6E, 0x2E<br>
&nbsp;&nbsp;&nbsp; };<br>
<br>
&nbsp;&nbsp;&nbsp; for (int i=0; i&lt;SIZE; ++i)<br>
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; printf("%c", output[i]); //
printf 函数按照 ASCII 编码方式进行解码<br>
<br>
&nbsp;&nbsp;&nbsp; return 0;<br>
}</p>
<p>&nbsp;</p>
<p>ASCII 中的规律：</p>
<p>1）小写字母和大写字母的码（Code）是连续的，例如：</p>
<p>int main(void)<br>
{</p>
<p>&nbsp;&nbsp;&nbsp; // 术语 code 即是 01 序列<br>
&nbsp;&nbsp; &nbsp;char cLetter;<br>
&nbsp;&nbsp;&nbsp; for (cLetter='a'; cLetter&lt;='z';
++cLetter)<br>
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; cLetter
&lt;&lt; " ";<br>
<br>
&nbsp;&nbsp;&nbsp; for (cLetter='A'; cLetter&lt;='Z';
++cLetter)<br>
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; cLetter
&lt;&lt; " ";<br>
<br>
&nbsp;&nbsp; &nbsp;return 0;<br>
}</p>
<p>2）大写字母和对应的小写字母的第 6 位分别是 0 和 1：</p>
<p>'A' ---- 1000001</p>
<p>'a' ---- 1100001</p>
<p>因此：'a' - 'A' = 2^5 = 32</p>
<p>'b' - 'B' = 2^5 = 32 等</p>
<p>&nbsp;</p>

]]></description><guid>http://www.i170.com/Article/111057</guid><trackback:ping>http://www.i170.com/Article/111057/trackback</trackback:ping><comments>http://www.i170.com/Article/111057#comment</comments><wfw:commentRss>http://www.i170.com/Article/111057/commentRss</wfw:commentRss></item> <item><link>http://www.i170.com/Article/111037</link><title><![CDATA[冯诺依曼体系带来的安全问题（未完成）]]></title><author>killercat</author><category>Architecture</category><pubDate>Mon, 15 Sep 2008 17:56:04  +0800</pubDate><description><![CDATA[<p>RT</p>

]]></description><guid>http://www.i170.com/Article/111037</guid><trackback:ping>http://www.i170.com/Article/111037/trackback</trackback:ping><comments>http://www.i170.com/Article/111037#comment</comments><wfw:commentRss>http://www.i170.com/Article/111037/commentRss</wfw:commentRss></item> </channel></rss>