﻿<?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++博客-&lt;b&gt;&lt;font color=#ff0000&gt;自愚@_@自乐&lt;/font&gt;&lt;/b&gt;</title><link>http://www.cppblog.com/yuqilin1228/</link><description>There is a way...</description><language>zh-cn</language><lastBuildDate>Tue, 07 Apr 2026 00:03:17 GMT</lastBuildDate><pubDate>Tue, 07 Apr 2026 00:03:17 GMT</pubDate><ttl>60</ttl><item><title>Windows Live Writer 发布博客(cppBlog,cnblogs)</title><link>http://www.cppblog.com/yuqilin1228/archive/2011/08/23/154095.html</link><dc:creator>LynnRaymond</dc:creator><author>LynnRaymond</author><pubDate>Mon, 22 Aug 2011 18:06:00 GMT</pubDate><guid>http://www.cppblog.com/yuqilin1228/archive/2011/08/23/154095.html</guid><wfw:comment>http://www.cppblog.com/yuqilin1228/comments/154095.html</wfw:comment><comments>http://www.cppblog.com/yuqilin1228/archive/2011/08/23/154095.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yuqilin1228/comments/commentRss/154095.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yuqilin1228/services/trackbacks/154095.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 第一次使用Live Writer发布博客做个测试。^_^1. MetaWeblog API地址：&nbsp;&nbsp;&nbsp; cnblogs格式为：http://www.cnblogs.com/你的博客名/services/metaweblog.aspx&nbsp;&nbsp;&nbsp; cppblog格式为:http://www.cppblog.com/你的博客名/servic...&nbsp;&nbsp;<a href='http://www.cppblog.com/yuqilin1228/archive/2011/08/23/154095.html'>阅读全文</a><img src ="http://www.cppblog.com/yuqilin1228/aggbug/154095.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yuqilin1228/" target="_blank">LynnRaymond</a> 2011-08-23 02:06 <a href="http://www.cppblog.com/yuqilin1228/archive/2011/08/23/154095.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【设计实践】The Practice of Programming 01</title><link>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110628.html</link><dc:creator>LynnRaymond</dc:creator><author>LynnRaymond</author><pubDate>Fri, 26 Mar 2010 15:39:00 GMT</pubDate><guid>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110628.html</guid><wfw:comment>http://www.cppblog.com/yuqilin1228/comments/110628.html</wfw:comment><comments>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110628.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yuqilin1228/comments/commentRss/110628.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yuqilin1228/services/trackbacks/110628.html</trackback:ping><description><![CDATA[<p><span style="font-size: 12pt;"><span style="color: blue;"><em>The Practice of Programming</em></span> - Chapter 1 Style</span>
</p>
<p><span style="font-size: 12pt;">1.1&nbsp; Names</span>
</p>
<p><span style="font-size: 12pt;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A name should be informative, concise, memorable, and pronounceable if possible. </span>
</p>
<p>&nbsp;
&nbsp;</p>
<p><span style="font-size: 12pt;">* Use descriptive names for globals, short names for locals.</span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">Global variables need names long enough and descriptive enough to remind the reader of their meaning. </span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">It's also helpful to include a brief comment with the declaration of each global. </span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">The longer the program, the more important is the choice of good, descriptive, systematic names.</span>
</p>
<p><span style="font-size: 12pt;">* Be consistent.</span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">Give related things related names that show their relationship and high-light their difference.</span>
</p>
<p><span style="font-size: 12pt;">* Use active names for functions. </span>
</p>
<p><span style="font-size: 12pt;">* Be accurate.</span>
</p>
<p>&nbsp;
&nbsp;</p>
<p><span style="font-size: 12pt;">1.2 Expressions and Statements</span>
</p>
<p>&nbsp;
&nbsp;</p>
<p><span style="font-size: 12pt;">* Indent to show structure.</span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">A consistent indentation style is the lowest-energy way to make a program's structure self-evident.</span>
</p>
<p><span style="font-size: 12pt;">* Use the natural form of expressions. </span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">Write expressions as you might speak them aloud.</span>
</p>
<p><span style="font-size: 12pt;">* Parenthesize to resolve ambiguity.</span>
</p>
<p><span style="font-size: 12pt;">* Break up complex expressions.</span>
</p>
<p><span style="font-size: 12pt;">* Be clear. </span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">the goal is to write clear code, not clever code.</span>
</p>
<p><span style="font-size: 12pt;">* Be careful with side effects.</span>
</p>
<p>&nbsp;
&nbsp;</p>
<p><span style="font-size: 12pt;">1.3 Consistency and Idioms</span>
</p>
<p><span style="font-size: 12pt;">* Use a consistent indentation and brace style. </span>
</p>
<p><span style="font-size: 12pt;">* Use idioms for consistency.</span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">A central part of learning any language is developing a familiarity with its idioms.</span>
</p>
<p><span style="font-size: 12pt;">* Use else-ifs for multi-way decisions.</span>
</p>
<p>&nbsp;
&nbsp;</p>
<p><span style="font-size: 12pt;">1.4 Function Macros</span>
</p>
<p><span style="font-size: 12pt;">* Avoid function macros.</span>
</p>
<p><span style="font-size: 12pt;">* Parenthesize the macro body and arguments.</span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">In C++, inline functions avoid the syntactic trouble while offering whatever performance advantage macros might provide.</span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">They are appropriate for short functions that set or retrieve a single value.</span>
</p>
<p>&nbsp;
&nbsp;</p>
<p><span style="font-size: 12pt;">1.5 Magic Numbers</span>
</p>
<p><span style="font-size: 12pt;">Magic numbers are the constants, array sizes, character positions, conversion factors, and other literal numric values that appear in programs.</span>
</p>
<p>&nbsp;
&nbsp;</p>
<p><span style="font-size: 12pt;">* Given names to magic numbers.</span>
</p>
<p><span style="font-size: 12pt;">* Define numbers as constants, not macros.</span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">The C preprocessor is a powerful but blunt tool, however, and macros are a dangerous way to program because they change the lexical structure of the program underfoot. Let the language proper do the work.</span>
</p>
<p><span style="font-size: 12pt;">* Use character constants, not integers.</span>
</p>
<p><span style="font-size: 12pt;">* Use the language to calculate the size of an object.</span>
</p>
<p>&nbsp;
&nbsp;</p>
<p><span style="font-size: 12pt;">1.6 Comments</span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">* Don't belabor the obvious.</span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">* Comment functions and global data.</span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">* Don't comment bad code, rewrite it.</span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">* Don't contradict the code.</span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">* Clarify,don't confuse.</span>
</p>
<p>&nbsp;
&nbsp;</p>
<p><span style="font-size: 12pt;">1.7 Why bother?</span>
</p>
<p><span style="font-size: 12pt;">The main concerns of programming style: descriptive names, clearity in expressions, straightforward control flow, readability of code and comments, and the importance of consistence use of conventions and idioms in achieving all of these.</span>
</p>
<p><span style="font-size: 12pt;">Well-written code is easier to read and to understand, almost surely has fewer errors, and is likely to be smaller than code that has been carelessly tossed together and never polished.</span>
</p>
<p><span style="font-size: 12pt;">The key observation is that good style should be a matter of habit.</span>
</p>
<p><span style="font-size: 12pt;">Once they become automatic, your subconscious will take care of many of the details for you, and even the code you produce under pressure will be better.</span>
</p>
<p>&nbsp;
&nbsp;</p>
<p>&nbsp;
&nbsp;</p>
<p><span style="font-family: 黑体; font-size: 13pt;">程序设计实践 - 第1章</span>
</p>
<p><span style="font-size: 12pt;">1. <span style="font-family: 宋体;">风格</span></span>
</p>
<p><span style="font-size: 12pt;">1.1 <span style="font-family: 宋体;">名字</span></span>
</p>
<p style="margin-left: 21pt;"><span style="font-family: 宋体; font-size: 12pt;">一个名字应该是非形式的、简练的、容易记忆的，如果可能的话，最好是能够拼读的。</span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">* <span style="font-family: 宋体;">全局变量使用具有说明性的名字，局部变量用短名字。</span></span>
</p>
<p style="margin-left: 42pt;"><span style="font-family: 宋体; font-size: 12pt;">全局变量的名字应该足够长，具有足够的说明性。给每个全局变量声明附一个简短注释也非常有帮助。</span>
</p>
<p style="margin-left: 42pt;"><span style="font-family: 宋体; font-size: 12pt;">对于长的程序，选择那些好的、具有说明性的、系统化的名字就更加重要。</span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">* <span style="font-family: 宋体;">保持一致性。</span></span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">* <span style="font-family: 宋体;">函数采用动作性名字。</span></span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">* <span style="font-family: 宋体;">要准确。名字与其实现保持准确的对应。</span></span>
</p>
<p style="margin-left: 21pt;">&nbsp;
&nbsp;</p>
<p><span style="font-size: 12pt;">1.2 <span style="font-family: 宋体;">表达式和语句</span></span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">* <span style="font-family: 宋体;">用缩行显示程序的结构。</span></span>
</p>
<p style="margin-left: 42pt;"><span style="font-family: 宋体; font-size: 12pt;">采用一种一致的缩行风格，是使程序呈现出结构清晰的最省力的方法。</span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">* <span style="font-family: 宋体;">使用表达式的自然形式。</span></span>
</p>
<p style="margin-left: 42pt;"><span style="font-family: 宋体; font-size: 12pt;">表达式应该写得你能大声念出来。</span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">* <span style="font-family: 宋体;">用加括号的方式排除二义性。</span></span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">* <span style="font-family: 宋体;">分解复杂的表达式。</span></span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">* <span style="font-family: 宋体;">要清晰。</span></span>
</p>
<p style="margin-left: 42pt;"><span style="font-family: 宋体; font-size: 12pt;">目标应该是写出最清晰的代码，而不是最巧妙的代码。</span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">* <span style="font-family: 宋体;">小心副作用。</span></span>
</p>
<p style="margin-left: 42pt;"><span style="font-size: 12pt;"><span style="font-family: 宋体;">像</span>++<span style="font-family: 宋体;">这一类运算符具有副作用，它们除了返回一个值外，还将隐含地改变变量的值。</span></span>
</p>
<p>&nbsp;
&nbsp;</p>
<p><span style="font-size: 12pt;">1.3 <span style="font-family: 宋体;">一致性和习惯用法</span></span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">* <span style="font-family: 宋体;">使用一致的缩排和加括号风格。</span></span>
</p>
<p style="margin-left: 42pt;"><span style="font-family: 宋体; font-size: 12pt;">如果你工作在一个不是自己写的程序上，请注意保留程序原有的风格。</span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">* <span style="font-family: 宋体;">为了一致性，使用习惯用法。</span></span>
</p>
<p style="margin-left: 42pt;"><span style="font-family: 宋体; font-size: 12pt;">在学习一个语言的过程中，一个中心问题就是逐渐熟悉它的习惯用法。</span>
</p>
<p style="margin-left: 31pt;">&nbsp;
&nbsp;</p>
<p style="margin-left: 31pt;"><span style="color: #c0504d; font-size: 12pt;">-----------------------------------------------</span>
</p>
<p style="margin-left: 52pt;"><span style="font-family: 宋体; font-size: 12pt;">常见习惯用法之一是循环的形式。</span>
</p>
<p style="margin-left: 52pt;"><span style="font-family: 宋体; font-size: 12pt;">例如：</span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;">for(i = 0; i &lt; n; i++)</span>
</p>
<p style="margin-left: 73pt;"><span style="font-size: 12pt;">array[i] = 1.0;</span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;">C++<span style="font-family: 宋体;">或</span>Java<span style="font-family: 宋体;">里常见的另一种形式是把循环变量的声明也包括在内：</span></span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;">for(int i = 0; i &lt; n; i++)</span>
</p>
<p style="margin-left: 73pt;"><span style="font-size: 12pt;">array[i] = 1.0;</span>
</p>
<p style="margin-left: 52pt;"><span style="color: #c0504d; font-size: 12pt;">-----------------------------------------------</span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;"><span style="font-family: 宋体;">下面是</span>C<span style="font-family: 宋体;">语言扫描一个链表的标准循环：</span></span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;">for(p = list; p != NULL; p = p-&gt;next)</span>
</p>
<p style="margin-left: 73pt;"><span style="font-size: 12pt;">...</span>
</p>
<p style="margin-left: 52pt;"><span style="color: #c0504d; font-size: 12pt;">-----------------------------------------------</span>
</p>
<p style="margin-left: 52pt;"><span style="font-family: 宋体; font-size: 12pt;">无穷循环：</span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;">for( ;; )</span>
</p>
<p style="margin-left: 73pt;"><span style="font-size: 12pt;">...</span>
</p>
<p style="margin-left: 52pt;"><span style="font-family: 宋体; font-size: 12pt;">或</span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;">while(1)</span>
</p>
<p style="margin-left: 73pt;"><span style="font-size: 12pt;">...</span>
</p>
<p style="margin-left: 52pt;"><span style="color: #c0504d; font-size: 12pt;">-----------------------------------------------</span>
</p>
<p style="margin-left: 52pt;"><span style="font-family: 宋体; font-size: 12pt;">常见的另一个惯用法是把一个赋值放进循环条件里：</span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;">while( (c = getchar()) != EOF)</span>
</p>
<p style="margin-left: 73pt;"><span style="font-size: 12pt;">putchar(c);</span>
</p>
<p style="margin-left: 52pt;"><span style="color: #c0504d; font-size: 12pt;">-----------------------------------------------</span>
</p>
<p style="margin-left: 52pt;"><span style="font-family: 宋体; font-size: 12pt;">一个不好的例子：</span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;">char *p, buf[256];</span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;">gets(buf);</span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;">p = malloc(strlen(buf));</span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;">strcpy(p, buf);</span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;"><span style="font-family: 宋体;">问题</span>1. </span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;"><span style="font-family: 宋体;">绝不要使用函数</span>gets<span style="font-family: 宋体;">，因为你没办法限制它由输入那儿读入内容的数量。这常常会导致一个安全性问题。</span></span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;"><span style="font-family: 宋体;">问题</span>2. </span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;"><span style="font-family: 宋体;">还有另一个问题：</span>strlen<span style="font-family: 宋体;">求出的值没有计入串结尾的</span>'\0'<span style="font-family: 宋体;">字符，而</span>strcpy<span style="font-family: 宋体;">却将复制它。</span></span>
</p>
<p style="margin-left: 52pt;"><span style="font-family: 宋体; font-size: 12pt;">习惯写法是：</span>
</p>
<p style="margin-left: 73pt;"><span style="font-size: 12pt;">p = malloc(strlen(buf)+1);</span>
</p>
<p style="margin-left: 63pt;"><span style="font-size: 12pt;">strcpy(p, buf);</span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;"><span style="font-family: 宋体;">或在</span>C++<span style="font-family: 宋体;">里：</span></span>
</p>
<p style="margin-left: 73pt;"><span style="font-size: 12pt;">p = new char[strlen(buf)+1];</span>
</p>
<p style="margin-left: 63pt;"><span style="font-size: 12pt;">strcpy(p , buf);</span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;"><span style="font-family: 宋体;">如果这里没有</span>+1<span style="font-family: 宋体;">，就要当心。</span></span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;">strdup<span style="font-family: 宋体;">可以使避免上述错误变得更简单。可惜</span>strdup<span style="font-family: 宋体;">不是</span>ANSI C<span style="font-family: 宋体;">标准中的内容。</span></span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;"><span style="font-family: 宋体;">问题</span>3. </span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;"><span style="font-family: 宋体;">上面两个版本都没有检查</span>malloc<span style="font-family: 宋体;">的返回值。</span></span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;"><span style="font-family: 宋体;">在实际程序中，对于</span>malloc<span style="font-family: 宋体;">、</span>realloc<span style="font-family: 宋体;">、</span>strdup<span style="font-family: 宋体;">及任何牵涉到存储分配的函数，它们的返回值都必须做检查。</span></span>
</p>
<p style="margin-left: 52pt;"><span style="color: #c0504d; font-size: 12pt;">-----------------------------------------------</span>
</p>
<p style="margin-left: 21pt;">&nbsp;
&nbsp;</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">* <span style="font-family: 宋体;">用</span>else-if<span style="font-family: 宋体;">表达多路选择。</span></span>
</p>
<p style="margin-left: 42pt;"><span style="font-family: 宋体; font-size: 12pt;">多路选择的习惯表示法形式如下：</span>
</p>
<p style="margin-left: 42pt;"><span style="font-size: 12pt;">if (condition<sub>1</sub>)</span>
</p>
<p style="margin-left: 63pt;"><span style="font-size: 12pt;">statement<sub>1</sub></span>
</p>
<p style="margin-left: 42pt;"><span style="font-size: 12pt;">else if (condition<sub>2</sub>)</span>
</p>
<p style="margin-left: 63pt;"><span style="font-size: 12pt;">statement<sub>2</sub></span>
</p>
<p style="margin-left: 42pt;"><span style="font-size: 12pt;">...</span>
</p>
<p style="margin-left: 42pt;"><span style="font-size: 12pt;">else if (condition<sub>n</sub>)</span>
</p>
<p style="margin-left: 63pt;"><span style="font-size: 12pt;">statement<sub>n</sub></span>
</p>
<p style="margin-left: 42pt;"><span style="font-size: 12pt;">else</span>
</p>
<p style="margin-left: 63pt;"><span style="font-size: 12pt;">default-statement</span>
</p>
<p>&nbsp;
&nbsp;</p>
<p><span style="font-size: 12pt;">1.4 <span style="font-family: 宋体;">函数宏</span></span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">* <span style="font-family: 宋体;">避免使用函数宏。</span></span>
</p>
<p style="margin-left: 42pt;"><span style="font-family: 宋体; font-size: 12pt;">函数宏最常见的一个严重问题是：如果一个参数在定义中出现多次，它就可能被多次求值。</span>
</p>
<p style="margin-left: 42pt;"><span style="font-family: 宋体; font-size: 12pt;">如果调用时的实际参数带有副作用，结果就可能产生一个难以捉摸的错误。</span>
</p>
<p style="margin-left: 42pt;"><span style="font-family: 宋体; font-size: 12pt;">例子：</span>
</p>
<p style="margin-left: 42pt;"><span style="font-size: 12pt;"><span style="font-family: 宋体;">某</span>&lt;ctype.h&gt;<span style="font-family: 宋体;">中：</span></span>
</p>
<p style="margin-left: 63pt;"><span style="font-size: 12pt;">#define isupper(c) ((c) &gt;= 'A' &amp;&amp; (c) &lt;= 'Z')</span>
</p>
<p style="margin-left: 42pt;"><span style="font-family: 宋体; font-size: 12pt;">如果这样调用</span>
</p>
<p style="margin-left: 63pt;"><span style="font-size: 12pt;">while (isupper(c = getchar())), </span>
</p>
<p style="margin-left: 42pt;"><span style="font-size: 12pt;"><span style="font-family: 宋体;">两次输入的字符</span>c<span style="font-family: 宋体;">被分别与</span>'A'<span style="font-family: 宋体;">和</span>'Z'<span style="font-family: 宋体;">比较了。</span></span>
</p>
<p style="margin-left: 42pt;">&nbsp;
&nbsp;</p>
<p style="margin-left: 42pt;"><span style="font-size: 12pt;">C<span style="font-family: 宋体;">语言标准允许将</span>isupper<span style="font-family: 宋体;">及类似函数定义为宏，但要求保证它们的参数只求值一次。</span></span>
</p>
<p style="margin-left: 42pt;"><span style="font-size: 12pt;"><span style="font-family: 宋体;">如果希望更安全些，那么就一定不要嵌套地使用像</span>getchar<span style="font-family: 宋体;">这种带有副作用的函数。</span></span>
</p>
<p style="margin-left: 42pt;"><span style="font-family: 宋体; font-size: 12pt;">改写如下：</span>
</p>
<p style="margin-left: 63pt;"><span style="font-size: 12pt;">while ((c = getchar()) != EOF &amp;&amp; isupper(c))</span>
</p>
<p style="margin-left: 42pt;"><span style="font-family: 宋体; font-size: 12pt;">有时多次求值带来的是执行效率问题，而不是真正的错误。</span>
</p>
<p>&nbsp;
&nbsp;</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">* <span style="font-family: 宋体;">给宏的体和参数都加上括号。</span></span>
</p>
<p style="margin-left: 42pt;"><span style="font-family: 宋体; font-size: 12pt;">如果一个操作比较复杂，或者它很具一般性，值得包装起来，那么还是应该使用函数。</span>
</p>
<p style="margin-left: 42pt;"><span style="font-size: 12pt;">C++<span style="font-family: 宋体;">提供的</span>inline<span style="font-family: 宋体;">函数既避免了语法方面的麻烦，而且又可得到宏能够提供的执行效率，很适合来定义那些设置或者提取一个值的短信函数。</span></span>
</p>
<p>&nbsp;
&nbsp;</p>
<p><span style="font-size: 12pt;">1.5 <span style="font-family: 宋体;">神秘的数</span></span>
<span style="font-size: 12pt;"><span style="font-family: 宋体;">（翻译的有点</span>...<span style="font-family: 宋体;">）</span></span>
</p>
<p style="margin-left: 21pt;"><span style="font-family: 宋体; font-size: 12pt;">神秘的数包括各种常数、数组的大小、字符位置、变换因子以及程序中出现的其他以文字形式写出的数值。</span>
</p>
<p style="margin-left: 21pt;">&nbsp;
&nbsp;</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">* <span style="font-family: 宋体;">给神秘的数起个名字。</span></span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">* <span style="font-family: 宋体;">把数定义为常数，不要定义为宏。</span></span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">C<span style="font-family: 宋体;">语言预处理程序是一个强有力的工具，但是它又有些鲁莽。</span></span>
</p>
<p style="margin-left: 21pt;"><span style="font-family: 宋体; font-size: 12pt;">使用宏进行编程是一种很危险的方式，因为宏会在背地里改变程序的词法结构。我们应该让语法去做正确的工作。</span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;"><span style="font-family: 宋体;">（译者注：预处理命令不是</span>C<span style="font-family: 宋体;">语言本身的组成部分，而是一组辅助成分。这里说</span>"<span style="font-family: 宋体;">让语言</span>..."<span style="font-family: 宋体;">，也就是说不要用预处理命令做。）</span></span>
</p>
<p><span style="font-size: 12pt;">* <span style="font-family: 宋体;">使用字符形式的常量，不要用整数。</span></span>
</p>
<p style="margin-left: 21pt;"><span style="font-family: 宋体; font-size: 12pt;">例子：</span>
</p>
<p style="margin-left: 31pt;"><span style="font-size: 12pt;">if (c &gt;= 65 &amp;&amp; c &lt;= 90)</span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;">...</span>
</p>
<p style="margin-left: 31pt;"><span style="font-family: 宋体; font-size: 12pt;">这种写法完全依赖于特殊的字符表示方式。</span>
</p>
<p style="margin-left: 31pt;"><span style="font-family: 宋体; font-size: 12pt;">这样写更好些：</span>
</p>
<p style="margin-left: 31pt;"><span style="font-size: 12pt;">if (c &gt;= 'A' &amp;&amp; c &lt;= 'Z')</span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;">...</span>
</p>
<p style="margin-left: 31pt;"><span style="font-family: 宋体; font-size: 12pt;">但是，如果在某个编码字符集里的字母编码不是连续的，或夹有其他字母，那么这种描述就是错的。</span>
</p>
<p style="margin-left: 31pt;"><span style="font-family: 宋体; font-size: 12pt;">最好是直接使用库函数。</span>
</p>
<p style="margin-left: 31pt;"><span style="font-size: 12pt;">if(isupper(c))</span>
</p>
<p style="margin-left: 52pt;"><span style="font-size: 12pt;">...</span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">-----------------------------------------------</span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;"><span style="font-family: 宋体;">程序里许多上下文中经常出现的</span>0<span style="font-family: 宋体;">。如果我们把每个</span>0<span style="font-family: 宋体;">的类型写得更明确更清楚，对读程序的人理解其作用是很有帮助的。</span></span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;"><span style="font-family: 宋体;">例如，用</span> (void*)0 <span style="font-family: 宋体;">或</span> NULL <span style="font-family: 宋体;">表示</span>C<span style="font-family: 宋体;">里的空指针值，用</span>'\0'<span style="font-family: 宋体;">而不是</span>0<span style="font-family: 宋体;">表示字符串结尾的空字节。</span></span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;"><span style="font-family: 宋体;">然而在</span>C++<span style="font-family: 宋体;">里人们都已经接受了用</span>0<span style="font-family: 宋体;">（而不是</span>NULL<span style="font-family: 宋体;">）表示空指针。</span>Java<span style="font-family: 宋体;">里则定义了关键字</span>null<span style="font-family: 宋体;">。</span></span>
</p>
<p>&nbsp;
&nbsp;</p>
<p><span style="font-size: 12pt;">* <span style="font-family: 宋体;">利用语言去计算对象的大小。</span></span>
</p>
<p style="margin-left: 21pt;"><span style="font-family: 宋体; font-size: 12pt;">不要对任何数据类型使用显式写出来的大小。</span>
</p>
<p style="margin-left: 21pt;"><span style="font-family: 宋体; font-size: 12pt;">例：</span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">&nbsp;char buf[1024];</span>
</p>
<p style="margin-left: 21pt;"><span style="font-size: 12pt;">&nbsp;fgets(buf, sizeof(buf), stdin);</span>
</p>
<p style="margin-left: 21pt;"><span style="font-family: 宋体; font-size: 12pt;">对于那些可以看清楚的数组（不是指针），下面的宏定义能计算出数组的元素个数：</span>
</p>
<p style="margin-left: 21pt;"><span style="font-family: 宋体; font-size: 12pt;">#define NELEMS(array) (sizeof(array) / sizeof(array[0]))</span><p>&nbsp;
&nbsp;</p>
<p><span style="font-size: 12pt;">1.6 <span style="font-family: 宋体;">注释</span></span>
</p>
<p><span style="font-size: 12pt;">* <span style="font-family: 宋体;">不要大谈明显的东西。</span></span>
</p>
<p style="margin-left: 21pt;"><span style="font-family: 宋体; font-size: 12pt;">注释应该提供那些不能一下子从代码中看到的东西，或者把那些散布在许多代码里的信息收集到一起。</span>
</p>
<p><span style="font-size: 12pt;">* <span style="font-family: 宋体;">给函数和全局数据加注释。</span></span>
</p>
<p><span style="font-size: 12pt;">* <span style="font-family: 宋体;">不要注释差的代码，重写它。</span></span>
</p>
<p><span style="font-size: 12pt;">* <span style="font-family: 宋体;">不要与代码矛盾。</span></span>
</p>
<p style="margin-left: 21pt;"><span style="font-family: 宋体; font-size: 12pt;">当你改变代码时，一定要注意保证其中的注释是准确的。</span>
</p>
<p><span style="font-size: 12pt;">* <span style="font-family: 宋体;">澄清情况，不要添乱。</span></span>
</p>
<p style="margin-left: 21pt;"><span style="font-family: 宋体; font-size: 12pt;">注释应该在困难的地方尽量帮助读者，而不是给他们设置障碍。</span>
</p>
<p style="margin-left: 21pt;">&nbsp;
&nbsp;</p>
<p style="margin-left: 21pt;"><span style="font-family: 宋体; font-size: 12pt;">注释很重要，但是也不必盲目注释。</span>
</p>
<p style="margin-left: 21pt;"><span style="font-family: 宋体; font-size: 12pt;">注释是一种工具，它的作用就是帮助读者理解程序中的某些部分，而这些部分的意义不容易通过代码本身直接看到。</span>
</p>
<p style="margin-left: 21pt;">&nbsp;
&nbsp;</p>
<p><span style="font-size: 12pt;">1.7 <span style="font-family: 宋体;">为何对此费心</span></span>
</p>
<p><span style="font-family: 宋体; font-size: 12pt;">注重程序设计的风格：具有说明性的名字、清晰的表达式、直截了当的控制流、可读的代码和注释，以及在追求这些内容时一致地使用某些规则和惯用法的重要性。</span>
</p>
<p>&nbsp;
&nbsp;</p>
<p><span style="font-family: 宋体; font-size: 12pt;">书写良好的代码更容易阅读和理解，几乎可以保证其中的错误更少。</span>
</p>
<p><span style="font-family: 宋体; font-size: 12pt;">好风格应该成为一种习惯。</span>
</p>
<p><span style="font-family: 宋体; font-size: 12pt;">一旦这种习惯变成自动的东西，你的潜意识就会帮助你照料许多细节问题，甚至你在工作压力下写出的代码也会更好。</span>
</p><img src ="http://www.cppblog.com/yuqilin1228/aggbug/110628.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yuqilin1228/" target="_blank">LynnRaymond</a> 2010-03-26 23:39 <a href="http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110628.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【C++常识】C++的iostream标准库介绍</title><link>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110620.html</link><dc:creator>LynnRaymond</dc:creator><author>LynnRaymond</author><pubDate>Fri, 26 Mar 2010 14:03:00 GMT</pubDate><guid>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110620.html</guid><wfw:comment>http://www.cppblog.com/yuqilin1228/comments/110620.html</wfw:comment><comments>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110620.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yuqilin1228/comments/commentRss/110620.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yuqilin1228/services/trackbacks/110620.html</trackback:ping><description><![CDATA[<strong> 为什么需要iostream
我们从一开始就一直在利用C++的输入输出在做着各种练习，输入输出是由iostream库提供的，所以讨论此标准库是有必要的，它与C语言的
stdio库不同，它从一开始就是用多重继承与虚拟继承实现的面向对象的层次结构，作为一个c++的标准库组件提供给程序员使用。 </strong>
<p><strong> 　　iostream为内置类型类型对象提供了输入输出支持，同时也支持文件的输入输出，类的设计者可以通过对iostream库
的扩展，来支持自定义类型的输入输出操作。 </strong> </p>
<p><strong> 　　为什么说要扩展才能提供支持呢？我们来一个示例。 %CODE{"cpp"}% #include
&lt;stdio.h&gt; #include <iostream></iostream>using namespace std; </strong> </p>
<p><strong> class Test { public: Test(int a=0,int b=0) { Test::a=a;
Test::b=b; } int a; int b; }; int main() { Test t(100,50);
printf("%???",t);//不明确的输出格式 scanf("%???",t);//不明确的输入格式
cout&lt;&lt;t&lt;&lt;endl;//同样不够明确 cin&gt;&gt;t;//同样不够明确
system("pause"); } %ENDCODE%
由于自定义类的特殊性，在上面的代码中，无论你使用c风格的输入输出，或者是c++的输入输出都不是不明确的一个表示，由于c语言没有运算符重载机
制，导致stdio库的不可扩充性，让我们无法让printf()和scanf()支持对自定义类对象的扩充识别，而c++是可以通过运算符重载机制扩充
iostream库的，使系统能能够识别自定义类型，从而让输入输出明确的知道他们该干什么，格式是什么。 </strong> </p>
<p><strong> 　　在上例中我们之所以用printf与cout进行对比目的是为了告诉大家，C与C++处理输入输出的根本不同，我们从c远的输
入输出可以很明显看出是函数调用方式，而c++的则是对象模式，cout和cin是ostream类和istream类的对象。 </strong> </p>
<h3><a name="2 fstream: ifstream 和 ofstream"></a>1
iostream: istream 和 ostream </h3>
<strong> 　　C++中的iostream库主要包含下图所示的几个头文件: </strong>
<table class="twikiTable" border="0" cellpadding="1" cellspacing="1">
    <tbody>
        <tr class="twikiTableEven">
            <th class="twikiFirstCol" colspan="2" maxcols="0" bgcolor="#dadada">IOSstream 库 </th>
        </tr>
        <tr class="twikiTableOdd">
            <td class="twikiFirstCol" bgcolor="#eaeaea"><strong> fstream </strong> </td>
            <td bgcolor="#eaeaea"><strong> iomainip </strong> </td>
        </tr>
        <tr class="twikiTableEven">
            <td class="twikiFirstCol" bgcolor="#ffffff"><strong> ios </strong> </td>
            <td bgcolor="#ffffff"><strong> iosfwd </strong> </td>
        </tr>
        <tr class="twikiTableOdd">
            <td class="twikiFirstCol" bgcolor="#eaeaea"><strong> iostream </strong> </td>
            <td bgcolor="#eaeaea"><strong> istream </strong> </td>
        </tr>
        <tr class="twikiTableEven">
            <td class="twikiFirstCol" bgcolor="#ffffff"><strong> ostream </strong> </td>
            <td bgcolor="#ffffff"><strong> sstream </strong> </td>
        </tr>
        <tr class="twikiTableOdd">
            <td class="twikiFirstCol twikilast" bgcolor="#eaeaea"><strong> streambuf </strong> </td>
            <td class="twikiLast" bgcolor="#eaeaea"><strong> strstream </strong> </td>
        </tr>
    </tbody>
</table>
<p><strong> 　　我们所熟悉的输入输出操作分别是由istream(输入流)和ostream(输出流)这两个类提供的，为了允许双向的输入／
输出，由istream和ostream派生出了iostream类。 </strong> </p>
<p><strong> 　　类的继承关系见下图：<br><img alt="" src="http://www.pconline.com.cn/pcedu/empolder/gj/c/0504/pic/05cppios02.gif">
</strong> </p>
<p><strong> iostream库定义了以下三个标准流对象： </strong> </p>
<ol>
    <li><strong> cin，表示标准输入(standard input)的istream类对象。cin使我们可以从设备读如数据。 </strong>
    </li>
    <li><strong> cout，表示标准输出(standard
    output)的ostream类对象。cout使我们可以向设备输出或者写数据。 </strong>
    </li>
    <li><strong> cerr，表示标准错误(standard
    error)的osttream类对象。cerr是导出程序错误消息的地方，它只能允许向屏幕设备写数据。 </strong> </li>
</ol>
<p><strong> 　　输出主要由重载的左移操作符（&lt;&lt;）来完成，输入主要由重载的右移操作符(&gt;&gt;)完成: </strong> </p>
<ol>
    <li><strong> &gt;&gt;a表示将数据放入a对象中。 </strong>
    </li>
    <li><strong> &lt;&lt;a表示将a对象中存储的数据拿出。 </strong> </li>
</ol>
<p><strong> 　　这些标准的流对象都有默认的所对应的设备，见下表：<br></strong>
<table class="twikiTable" border="0" cellpadding="1" cellspacing="1">
    <tbody>
        <tr class="twikiTableEven">
            <th class="twikiFirstCol" maxcols="0" bgcolor="#dadada"><a  href="http://www.stlchina.org/twiki/bin/view.pl/Main/STLIOStreamIntro?sortcol=0;table=2;up=0#sorted_table" title="Sort by
            this column" style="color: #000000;" rel="nofollow"><u>C++对象名</u></a> </th>
            <th maxcols="0" bgcolor="#dadada"><a title="Sort by this column" style="color: #000000;" href="http://www.stlchina.org/twiki/bin/view.pl/Main/STLIOStreamIntro?sortcol=1;table=2;up=0#sorted_table" rel="nofollow"><u>设备名称</u></a> </th>
            <th maxcols="0" bgcolor="#dadada"><a title="Sort by this column" style="color: #000000;" href="http://www.stlchina.org/twiki/bin/view.pl/Main/STLIOStreamIntro?sortcol=2;table=2;up=0#sorted_table" rel="nofollow"><u>C中标准设备名</u></a> </th>
            <th maxcols="0" bgcolor="#dadada"><a title="Sort by this column" style="color: #000000;" href="http://www.stlchina.org/twiki/bin/view.pl/Main/STLIOStreamIntro?sortcol=3;table=2;up=0#sorted_table" rel="nofollow"><u>默认含义</u></a> </th>
        </tr>
        <tr class="twikiTableOdd">
            <td class="twikiFirstCol" align="middle" bgcolor="#eaeaea"><strong> cin </strong> </td>
            <td align="middle" bgcolor="#eaeaea"><strong> 键盘 </strong> </td>
            <td align="middle" bgcolor="#eaeaea"><strong> stdin </strong> </td>
            <td align="middle" bgcolor="#eaeaea"><strong> 标准输入 </strong> </td>
        </tr>
        <tr class="twikiTableEven">
            <td class="twikiFirstCol" align="middle" bgcolor="#ffffff"><strong> cout </strong> </td>
            <td align="middle" bgcolor="#ffffff"><strong> 显示器屏幕 </strong> </td>
            <td align="middle" bgcolor="#ffffff"><strong> stdout </strong> </td>
            <td align="middle" bgcolor="#ffffff"><strong> 标准输出 </strong> </td>
        </tr>
        <tr class="twikiTableOdd">
            <td class="twikiFirstCol twikilast" align="middle" bgcolor="#eaeaea"><strong> cerr
            </strong> </td>
            <td class="twikiLast" align="middle" bgcolor="#eaeaea"><strong> 显示器屏幕 </strong> </td>
            <td class="twikiLast" align="middle" bgcolor="#eaeaea"><strong> stderr </strong> </td>
            <td class="twikiLast" align="middle" bgcolor="#eaeaea"><strong> 标准错误输出 </strong> </td>
        </tr>
    </tbody>
</table>
<strong> 　　上表中的意思表明cin对象的默认输入设备是键盘，cout对象的默认输出设备是显示器屏幕。 </strong> </p>
<p><strong> 　　那么原理上Ｃ++有是如何利用cin／cout对象与左移和右移运算符重载来实现输入输出的呢？ </strong> </p>
<p><strong> 　　下面我们以输出为例，说明其实现原理： </strong> </p>
<ol>
    <li><strong> cout是ostream类的对象，因为它所指向的是标准设备（显示器屏幕），所以它在iostream头文件中作为全
    局对象进行定义。 </strong>
    </li>
    <li><strong> ostream cout(stdout);//其默认指向的C中的标准设备名，作为其构造函数的参数使用。
    </strong>
    </li>
    <li><strong> 在iostream.h头文件中，ostream类对应每个基本数据类型都有其友元函数对左移操作符进行了友
    元函数的重载。 </strong>
    <ul>
        <li><strong> ostream&amp; operator&lt;&lt;(ostream &amp;temp,int
        source); </strong>
        </li>
        <li><strong> ostream&amp; operator&lt;&lt;(ostream
        &amp;temp,char *ps); </strong>
        </li>
        <li><strong> ... 等等 </strong> </li>
    </ul>
    </li>
</ol>
<p><strong> 　　一句输出语句：cout&lt;&lt;"www.cndev-lab.com"；，事实上调用的就是
ostream&amp; operator&lt;&lt;(ostream &amp;temp,char
*ps);这个运算符重载函数，由于返回的是流对象的引用，引用可以作为左值使用，所以当程序中有类似cout&lt;&lt;"www.cndev-
lab.com"&lt;&lt;"中国软件开发实验室";这样的语句出现的时候，就能够构成连续输出。 </strong> </p>
<p><strong> 　　由于iostream库不光支持对象的输入输出，同时也支持文件流的输入输出，所以在详细讲解左移与右移运算符重载只前，我们
有必要先对文件的输入输出以及输入输出的控制符有所了解。 </strong> </p>
<h3><a name="2 fstream: ifstream 和 ofstream"></a>2 fstream: ifstream 和
ofstream </h3>
<strong> 　　和文件有关系的输入输出类主要在fstream.h这个头文件中被定义，在这个头文件中主要被定义了三个类，由这三个类控制对文件的
各种输入输出操作，他们分别是ifstream、ofstream、fstream，其中fstream类是由iostream类派生而来，他们之间的继
承关系见下图所示。<br><img alt="" src="http://www.pconline.com.cn/pcedu/empolder/gj/c/0504/pic/05cppios04.gif">
</strong>
<p><strong> 由于文件设备并不像显示器屏幕与键盘那样是标准默认设备，所以它在fstream.h头文件中是没有像cout那样预先定义的全局
对象，所以我们必须自己定义一个该类的对象，我们要以文件作为设备向文件输出信息(也就是向文件写数据)，那么就应该使用ofstream类。 </strong> </p>
<p><strong> 　　ofstream类的默认构造函数原形为： %CODE{"cpp"}%
ofstream::ofstream(const char *filename,int mode = ios::out,int
openprot = filebuf::openprot); %ENDCODE% </strong> </p>
<ul>
    <li><strong> filename：　　要打开的文件名 </strong>
    </li>
    <li><strong> mode：　　　　要打开文件的方式 </strong>
    </li>
    <li><strong> prot：　　　　打开文件的属性 </strong> </li>
</ul>
<p><strong> 　　其中mode和openprot这两个参数的可选项表见下表： </strong>
<table class="twikiTable" border="0" cellpadding="1" cellspacing="1">
    <tbody>
        <tr class="twikiTableEven">
            <td class="twikiFirstCol" colspan="2" align="middle" bgcolor="#eaeaea"><strong> mode
            属性表 </strong> </td>
        </tr>
        <tr class="twikiTableOdd">
            <td class="twikiFirstCol" bgcolor="#ffffff"><strong> ios::app </strong> </td>
            <td bgcolor="#ffffff"><strong> 以追加的方式打开文件 </strong> </td>
        </tr>
        <tr class="twikiTableEven">
            <td class="twikiFirstCol" bgcolor="#eaeaea"><strong> ios::ate </strong> </td>
            <td bgcolor="#eaeaea"><strong> 文件打开后定位到文件尾，ios:app就包含有此属性 </strong> </td>
        </tr>
        <tr class="twikiTableOdd">
            <td class="twikiFirstCol" bgcolor="#ffffff"><strong> ios::binary </strong> </td>
            <td bgcolor="#ffffff"><strong> 以二进制方式打开文件，缺省的方式是文本方式。两种方式的区别见前文 </strong> </td>
        </tr>
        <tr class="twikiTableEven">
            <td class="twikiFirstCol" bgcolor="#eaeaea"><strong> ios::in </strong> </td>
            <td bgcolor="#eaeaea"><strong> 文件以输入方式打开 </strong> </td>
        </tr>
        <tr class="twikiTableOdd">
            <td class="twikiFirstCol" bgcolor="#ffffff"><strong> ios::out </strong> </td>
            <td bgcolor="#ffffff"><strong> 文件以输出方式打开 </strong> </td>
        </tr>
        <tr class="twikiTableEven">
            <td class="twikiFirstCol twikilast" bgcolor="#eaeaea"><strong> ios::trunc </strong> </td>
            <td class="twikiLast" bgcolor="#eaeaea"><strong> 如果文件存在，把文件长度设为0 </strong> </td>
        </tr>
    </tbody>
</table>
<strong> 　　可以用&#8220;或&#8221;把以上属性连接起来，如ios::out|ios::binary。 </strong>
<table class="twikiTable" border="0" cellpadding="1" cellspacing="1">
    <tbody>
        <tr class="twikiTableEven">
            <td class="twikiFirstCol" colspan="2" align="middle" bgcolor="#eaeaea"><strong> openprot
            属性表 </strong> </td>
        </tr>
        <tr class="twikiTableOdd">
            <td class="twikiFirstCol" bgcolor="#ffffff"><strong> 属性 </strong> </td>
            <td align="middle" bgcolor="#ffffff"><strong> 含义 </strong> </td>
        </tr>
        <tr class="twikiTableEven">
            <td class="twikiFirstCol" bgcolor="#eaeaea"><strong> 0 </strong> </td>
            <td bgcolor="#eaeaea"><strong> 普通文件，打开访问 </strong> </td>
        </tr>
        <tr class="twikiTableOdd">
            <td class="twikiFirstCol" bgcolor="#ffffff"><strong> 1 </strong> </td>
            <td bgcolor="#ffffff"><strong> 只读文件 </strong> </td>
        </tr>
        <tr class="twikiTableEven">
            <td class="twikiFirstCol" bgcolor="#eaeaea"><strong> 2 </strong> </td>
            <td bgcolor="#eaeaea"><strong> 隐含文件 </strong> </td>
        </tr>
        <tr class="twikiTableOdd">
            <td class="twikiFirstCol twikilast" bgcolor="#ffffff"><strong> 4 </strong> </td>
            <td class="twikiLast" bgcolor="#ffffff"><strong> 系统文件 </strong> </td>
        </tr>
    </tbody>
</table>
<strong> 　　可以用&#8220;或&#8221;或者&#8220;+&#8221;把以上属性连接起来 ，如3或1|2就是以只读和隐含属性打开文件。 </strong> </p>
<p><strong> 实例代码如下： %CODE{"cpp"}% #include <fstream></fstream>using
namespace std; </strong> </p>
<p><strong> int main() { ofstream
myfile("c:\\1.txt",ios::out|ios::trunc,0);
myfile&lt;&lt;"中国软件开发实验室"&lt;&lt;endl&lt;&lt;"网址："&lt;&lt;"www.cndev-
lab.com"; myfile.close() system("pause"); } %ENDCODE%
文件使用完后可以使用close成员函数关闭文件。 </strong> </p>
<p><strong> 　　ios::app为追加模式，在使用追加模式的时候同时进行文件状态的判断是一个比较好的习惯。 </strong> </p>
<p><strong> 　　示例如下： </strong> </p>
<p><strong> %CODE{"cpp"}% #include <iostream></iostream>#include <fstream></fstream>using
namespace std; int main() { ofstream myfile("c:\\1.txt",ios::app,0);
if(myfile)//或者写成myfile.fail() { cout&lt;&lt;"文件打开失败，目标文件状态可能为只读！";
system("pause"); exit(1); }
myfile&lt;&lt;"中国软件开发实验室"&lt;&lt;endl&lt;&lt;"网址："&lt;&lt;"www.cndev-
lab.com"&lt;&lt;endl; myfile.close(); } %ENDCODE% </strong> </p>
<p><strong> 　　在定义ifstream和ofstream类对象的时候，我们也可以不指定文件。以后可以通过成员函数open()显式的把一
个文件连接到一个类对象上。 </strong> </p>
<p><strong> 　　例如： </strong> </p>
<p><strong> %CODE{"cpp"}% #include <iostream></iostream>#include <fstream></fstream>using
namespace std; int main() { ofstream myfile;
myfile.open("c:\\1.txt",ios::out|ios::app,0);
if(myfile)//或者写成myfile.fail() { cout&lt;&lt;"文件创建失败,磁盘不可写或者文件为只读!";
system("pause"); exit(1); }
myfile&lt;&lt;"中国软件开发实验室"&lt;&lt;endl&lt;&lt;"网址："&lt;&lt;"www.cndev-
lab.com"&lt;&lt;endl; myfile.close(); } %ENDCODE%
下面我们来看一下是如何利用ifstream类对象，将文件中的数据读取出来，然后再输出到标准设备中的例子。 </strong> </p>
<p><strong> 　　代码如下： %CODE{"cpp"}% #include <iostream></iostream>#include <fstream></fstream>#include
<string></string>using namespace std; int main() { ifstream myfile;
myfile.open("c:\\1.txt",ios::in,0); if(myfile) { cout&lt;&lt;"文件读错误";
system("pause"); exit(1); } char ch; string content;
while(myfile.get(ch)) { content+=ch;
cout.put(ch);//cout&lt;&lt;ch;这么写也是可以的 } myfile.close();
cout&lt;&lt;content; system("pause"); } %ENDCODE%
上例中，我们利用成员函数get()，逐一的读取文件中的有效字符，再利用put()成员函数，将文件中的数据通过循环逐一输出到标准设备(屏幕)
上，
get()成员函数会在文件读到默尾的时候返回假值，所以我们可以利用它的这个特性作为while循环的终止条件，我们同时也在上例中引入了C++风格的
字符串类型string，在循环读取的时候逐一保存到content中，要使用string类型，必须包含string.h的头文件。 </strong> </p>
<p><strong> &nbsp;</strong> </p>
<p><strong> 我们在简单介绍过ofstream类和ifstream类后，我们再来看一下fstream类，fstream类是由
iostream派生而来，fstream类对象可以同对文件进行读写操作。 </strong> </p>
<p><strong> 　　示例代码如下： %CODE{"cpp"}% #include <iostream></iostream>#include
<fstream></fstream>using namespace std; int main() { fstream myfile;
myfile.open("c:\\1.txt",ios::out|ios::app,0); if(myfile) {
cout&lt;&lt;"文件写错误,文件属性可能为只读!"&lt;&lt;endl; system("pause"); exit(1); }
myfile&lt;&lt;"中国软件开发实验室"&lt;&lt;endl&lt;&lt;"网址："&lt;&lt;"www.cndev-
lab.com"&lt;&lt;endl; myfile.close(); </strong> </p>
<p><strong> myfile.open("c:\\1.txt",ios::in,0); if(myfile) {
cout&lt;&lt;"文件读错误,文件可能丢失!"&lt;&lt;endl; system("pause"); exit(1); }
char ch; while(myfile.get(ch)) { cout.put(ch); } myfile.close();
system("pause"); } %ENDCODE%
由于fstream类可以对文件同时进行读写操作，所以对它的对象进行初始话的时候一定要显式的指定mode和openprot参数。 </strong> </p>
<p><strong> 　　接下来我们来学习一下串流类的基础知识，什么叫串流类？ </strong> </p>
<h3><a name="3 strstream: ostrstream 和 istrs"></a>3 strstream:
ostrstream 和 istrstream </h3>
<strong> 　　简单的理解就是能够控制字符串类型对象进行输入输出的类，C++不光可以支持C++风格的字符串流控制，还可以支持C风格的字符串流
控制。 </strong>
<p><strong> 　　我们先看看看C++是如何对C风格的字符串流进行控制的，C中的字符串其实也就是字符数组，字符数组内的数据在内存中的位置的
排列是连续的，我们通常用 char str[size]或者char
*str的方式声明创建C风格字符数组，为了能让字符数组作为设备并提供输入输出操作，C++引入了ostrstream、istrstream、
strstream这三个类，要使用他们创建对象就必须包含strstream.h头文件。 </strong> </p>
<ul>
    <li><strong> istrstream类用于执行C风格的串流的输入操作，也就是以字符串数组作为输入设备。 </strong>
    </li>
    <li><strong> ostrstream类用于执行C风格的串流的输出操作，也就是一字符串数组作为输出设备。 </strong>
    </li>
    <li><strong> strstream类同时可以支持C风格的串流的输入输出操作。 </strong> </li>
</ul>
<p><strong> 　　istrstream类是从istream（输入流类）和strstreambase（字符串流基类）派生而
来，ostrstream是从
ostream（输出流类）和strstreambase（字符串流基类）派生而来，strstream则是从iostream(输入输出流类)和和
strstreambase（字符串流基类）派生而来。 </strong> </p>
<p><strong> 　　他们的继承关系如下图所示:<br><img alt="" src="http://www.pconline.com.cn/pcedu/empolder/gj/c/0504/pic/05cppios05.gif">
</strong> </p>
<p><strong> 　　串流同样不是标准设备，不会有预先定义好的全局对象，所以不能直接操作，需要通过构造函数创建对象。 </strong> </p>
<p><strong> 类istrstream的构造函数原形如下： %CODE{"cpp"}%
istrstream::istrstream(const char *str,int size); %ENDCODE%
参数1表示字符串数组,而参数2表示数组大小，当size为0时，表示istrstream类对象直接连接到由str所指向的内存空间并以\0结尾的
字符串。 </strong> </p>
<p><strong> 　　下面的示例代码就是利用istrstream类创建类对象，制定流输入设备为字符串数组，通过它向一个字符型对象输入数据。代
码如下： %CODE{"cpp"}% #include <iostream></iostream>#include <strstream></strstream>using
namespace std; int main() { char *name = "www.cndev-lab.com"; int
arraysize = strlen(name)+1; istrstream is(name,arraysize); char temp;
is&gt;&gt;temp; cout&lt;&lt;temp; system("pause"); } %ENDCODE%
类ostrstream用于执行串流的输出，它的构造函数如下所示： %CODE{"cpp"}%
ostrstream::ostrstream(char *_Ptr,int streamsize,int Mode = ios::out);
%ENDCODE% 　　第一个参数是字符数组，第二个是说明数组的大小，第三个参数是指打开方式。 </strong> </p>
<p><strong> 　　我们来一个示例代码： %CODE{"cpp"}% #include <iostream></iostream>#include
<strstream></strstream>using namespace std; int main() { int
arraysize=1; char *pbuffer=new char[arraysize]; ostrstream
ostr(pbuffer,arraysize,ios::out);
ostr&lt;&lt;arraysize&lt;&lt;ends;//使用ostrstream输出到流对象的时候,要用ends结束字符串
cout&lt;&lt;pbuffer; delete[] pbuffer; system("pause"); } %ENDCODE%
上面的代码中，我们创建一个c风格的串流输出对象ostr，我们将arraysize内的数据成功的以字符串的形式输出到了ostr对象所指向的
pbuffer指针的堆空间中，pbuffer也正是我们要输出的字符串数组，在结尾要使用ends结束字符串，如果不这么做就有溢出的危险。 </strong> </p>
<h3><a name="4 stringstream"></a>4 stringstream </h3>
<strong> 对于stringstream了来说，不用我多说，大家也已经知道它是用于C++风格的字符串的输入输出的。
stringstream的构造函数原形如下： %CODE{"cpp"}% 　　stringstream::stringstream(string
str); %ENDCODE% 　　示例代码如下: %CODE{"cpp"}% #include <iostream></iostream>#include
<sstream></sstream>#include <string></string>using namespace std; </strong>
<p><strong> int main() { stringstream ostr("ccc"); ostr.put('d');
ostr.put('e'); ostr&lt;&lt;"fg"; string gstr = ostr.str();
cout&lt;&lt;gstr&lt;&lt;endl; </strong> </p>
<p><strong> char a; ostr&gt;&gt;a; cout&lt;&lt;a </strong> </p>
<p><strong> system("pause"); } %ENDCODE%
除此而外，stringstream类的对象我们还常用它进行string与各种内置类型数据之间的转换。示例代码如下：
%CODE{"cpp"}% #include <iostream></iostream>#include <sstream></sstream>#include
<string></string>using namespace std; </strong> </p>
<p><strong> int main() { stringstream sstr;
//--------int转string----------- int a=100; string str; sstr&lt;&lt;a;
sstr&gt;&gt;str; cout&lt;&lt;str&lt;&lt;endl;
//--------string转char[]--------
sstr.clear();//如果你想通过使用同一stringstream对象实现多种类型的转换，请注意在每一次转换之后都必须调用clear()
成员函数。 string name = "colinguan"; char cname[200]; sstr&lt;&lt;name;
sstr&gt;&gt;cname; cout&lt;&lt;cname; system("pause"); } %ENDCODE%
接下来我们来学习一下输入/输出的状态标志的相关知识. </strong> </p>
<h3><a name="5 io_state 输入/输出的状态标志"></a>5 io_state 输入/输出的状态标志 </h3>
<strong> C++中负责的输入/输出的系统包括了关于每一个输入/输出操作的结果的记录信息。这些当前的状态信息被包含在io_state类型的对
象中。io_state是一个枚举类型（就像open_mode一样），以下便是它包含的值。 </strong>
<ul>
    <li><strong> goodbit 无错误 </strong>
    </li>
    <li><strong> Eofbit 已到达文件尾 </strong>
    </li>
    <li><strong> failbit 非致命的输入/输出错误，可挽回 </strong>
    </li>
    <li><strong> badbit　致命的输入/输出错误,无法挽回 </strong> </li>
</ul>
<p><strong> 有两种方法可以获得输入/输出的状态信息。一种方法是通过调用rdstate()函数，它将返回当前状态的错误标记。例如，假如没
有任何错误，则rdstate()会返回goodbit.下例示例，表示出了rdstate()的用法： %CODE{"cpp"}% #include
<iostream></iostream>using namespace std; </strong> </p>
<p><strong> int main() { int a; cin&gt;&gt;a;
cout&lt;&lt;cin.rdstate()&lt;&lt;endl; if(cin.rdstate() == ios::goodbit)
{ cout&lt;&lt;"输入数据的类型正确，无错误！"&lt;&lt;endl; } if(cin.rdstate() ==
ios_base::failbit) {
cout&lt;&lt;"输入数据类型错误，非致命错误，可清除输入缓冲区挽回！"&lt;&lt;endl; } system("pause");
} %ENDCODE% 　　另一种方法则是使用下面任何一个函数来检测相应的输入/输出状态： %CODE{"cpp"}% bool bad();
bool eof(); bool fail(); bool good(); %ENDCODE% </strong> </p>
<p><strong> 　　下例示例，表示出了上面各成员函数的用法： %CODE{"cpp"}% #include <iostream></iostream>using
namespace std; </strong> </p>
<p><strong> int main() { int a; cin&gt;&gt;a;
cout&lt;&lt;cin.rdstate()&lt;&lt;endl; if(cin.good()) {
cout&lt;&lt;"输入数据的类型正确，无错误！"&lt;&lt;endl; } if(cin.fail()) {
cout&lt;&lt;"输入数据类型错误，非致命错误，可清除输入缓冲区挽回！"&lt;&lt;endl; } system("pause");
} %ENDCODE%
如果错误发生，那么流状态既被标记为错误，你必须清除这些错误状态，以使你的程序能正确适当地继续运行。要清除错误状态，需使用clear()函数。
此函数带一个参数，它是你将要设为当前状态的标志值。，只要将ios::goodbit作为实参。 </strong> </p>
<p><strong> 　　示例代码如下： %CODE{"cpp"}% #include <iostream></iostream>using
namespace std; </strong> </p>
<p><strong> int main() { int a; cin&gt;&gt;a;
cout&lt;&lt;cin.rdstate()&lt;&lt;endl; cin.clear(ios::goodbit);
cout&lt;&lt;cin.rdstate()&lt;&lt;endl; system("pause"); } %ENDCODE%
通常当我们发现输入有错又需要改正的时候，使用clear()更改标记为正确后，同时也需要使用get()成员函数清除输入缓冲区，以达到重复输入的目
的。 </strong> </p>
<p><strong> 　　示例代码如下： %CODE{"cpp"}% #include <iostream></iostream>using
namespace std; </strong> </p>
<p><strong> int main() { int a; while(1) { cin&gt;&gt;a;
if(cin)//条件可改写为cin.fail() { cout&lt;&lt;"输入有错!请重新输入"&lt;&lt;endl;
cin.clear(); cin.get(); } else { cout&lt;&lt;a; break; } }
system("pause"); } %ENDCODE% 　　最后再给出一个对文件流错误标记处理的例子，巩固学习，代码如下：
%CODE{"cpp"}% #include <iostream></iostream>#include <fstream></fstream>using
namespace std; </strong> </p>
<p><strong> int main() { ifstream myfile("c:\\1.txt",ios_base::in,0);
if(myfile.fail()) { cout&lt;&lt;"文件读取失败或指定文件不存在!"&lt;&lt;endl; } else {
char ch; while(myfile.get(ch)) { cout&lt;&lt;ch; } if(myfile.eof()) {
cout&lt;&lt;"文件内容已经全部读完"&lt;&lt;endl; } while(myfile.get(ch)) {
cout&lt;&lt;ch; } } system("pause"); } %ENDCODE%&nbsp;&nbsp;</strong> </p>
<br> <img src ="http://www.cppblog.com/yuqilin1228/aggbug/110620.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yuqilin1228/" target="_blank">LynnRaymond</a> 2010-03-26 22:03 <a href="http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110620.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【C++常识】C++输入输出流</title><link>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110619.html</link><dc:creator>LynnRaymond</dc:creator><author>LynnRaymond</author><pubDate>Fri, 26 Mar 2010 14:01:00 GMT</pubDate><guid>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110619.html</guid><wfw:comment>http://www.cppblog.com/yuqilin1228/comments/110619.html</wfw:comment><comments>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110619.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yuqilin1228/comments/commentRss/110619.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yuqilin1228/services/trackbacks/110619.html</trackback:ping><description><![CDATA[<p>C++ 通过以下几个类支持文件的输入输出：</p>
<ul>
    <li>ofstream: 写操作（输出）的文件类 (由ostream引申而来)
    </li>
    <li>ifstream: 读操作（输入）的文件类(由istream引申而来)
    </li>
    <li>fstream: 可同时读写操作的文件类 (由iostream引申而来) </li>
</ul>
<p>&nbsp;</p>
<h3>打开文件(Open a file)</h3>
<p>对这些类的一个对象所做的第一个操作通常就是将它和一个真正的文件联系起来，也就是说打开一个文件。被打开的文件在程序中由一个流对象
(stream object)来表示 (这些类的一个实例) ，而对这个流对象所做的任何输入输出操作实际就是对该文件所做的操作。</p>
<p>要通过一个流对象打开一个文件，我们使用它的成员函数open()：</p>
<p><code><font face="NSimsun">void open (const char * filename, openmode
mode);</font></code>这里filename 是一个字符串，代表要打开的文件名，mode 是以下标志符的一个组合：</p>
<table border="1" cellpadding="3" cellspacing="0">
    <tbody>
        <tr>
            <td>ios::in</td>
            <td>为输入(读)而打开
            文件</td>
        </tr>
        <tr>
            <td>ios::out</td>
            <td>为输出(写)而打开
            文件</td>
        </tr>
        <tr>
            <td>ios::ate</td>
            <td>初始位置：文件尾</td>
        </tr>
        <tr>
            <td>ios::app</td>
            <td>所有输出附加在文件
            末尾</td>
        </tr>
        <tr>
            <td>ios::trunc</td>
            <td>如果文件已存在则先
            删除该文件</td>
        </tr>
        <tr>
            <td>ios::binary</td>
            <td>二进制方式</td>
        </tr>
    </tbody>
</table>
<p>这些标识符可以被组合使用，中间以&#8221;或&#8221;操作符(|)间隔。例如，如果我们想要以二进制方式打开文件&#8221;example.bin&#8221;
来写入一些数据，我们可以通过以下方式调用成员函数open（）来实现：</p>
<p><code><font face="NSimsun">ofstream file;<br>file.open
("example.bin", ios::out | ios::app | ios::binary); </font></code>ofstream,
ifstream 和 fstream所有这些类的成员函数open 都包含了一个默认打开文件的方式，这三个类的默认方式各不相同：</p>
<table border="1" cellpadding="3" cellspacing="0">
    <tbody>
        <tr>
            <th>类</th>
            <th>参数的默认方式</th>
        </tr>
        <tr>
            <td>ofstream</td>
            <td>ios::out |
            ios::trunc</td>
        </tr>
        <tr>
            <td>ifstream</td>
            <td>ios::in</td>
        </tr>
        <tr>
            <td>fstream</td>
            <td>ios::in |
            ios::out</td>
        </tr>
    </tbody>
</table>
<p>只有当函数被调用时没有声明方式参数的情况下，默认值才会被采用。如果函数被调用时声明了任何参数，默认值将被完全改写，而不会与调用参数组合。</p>
<p>由于对类ofstream, ifstream 和 fstream
的对象所进行的第一个操作通常都是打开文件，这些类都有一个构造函数可以直接调用open
函数，并拥有同样的参数。这样，我们就可以通过以下方式进行与上面同样的定义对象和打开文件的操作：</p>
<p><code><font face="NSimsun">ofstream file ("example.bin", ios::out |
ios::app | ios::binary);</font></code>两种打开文件的方式都是正确的。</p>
<p>你可以通过调用成员函数is_open()来检查一个文件是否已经被顺利的打开了：</p>
<p><code><font face="NSimsun">bool is_open();</font></code>它返回一个布尔(bool)
值，为真（true）代表文件已经被顺利打开，假( false )则相反。</p>
<h3>关闭文件(Closing a file)</h3>
<p>当文件读写操作完成之后，我们必须将文件关闭以使文件重新变为可访问的。关闭文件需要调用成员函数close()，它负责将缓存中的数据排放出来并
关闭文件。它的格式很简单：</p>
<p><code><font face="NSimsun">void close ();</font></code>这个函数一旦被调用，原先的流
对象(stream object)就可以被用来打开其它的文件了，这个文件也就可以重新被其它的进程(process)所有访问了。</p>
<p>为防止流对象被销毁时还联系着打开的文件，析构函数(destructor)将会自动调用关闭函数close。</p>
<h3>文本文件(Text mode files)</h3>
<p>类ofstream, ifstream 和fstream 是分别从ostream, istream 和iostream
中引申而来的。这就是为什么 fstream 的对象可以使用其父类的成员来访问数据。</p>
<p>一般来说，我们将使用这些类与同控制台(console)交互同样的成员函数(cin 和
cout)来进行输入输出。如下面的例题所示，我们使用重载的插入操作符&lt;&lt;：</p>
<table border="2" cellpadding="1" cellspacing="1" width="400">
    <tbody>
        <tr>
            <td><font color="#008000">// writing on a text file</font><br>#include
            &lt;fiostream.h&gt;int main () {<br>ofstream examplefile
            (&#8221;example.txt&#8221;);<br>if (examplefile.is_open()) {<br>examplefile &lt;&lt;
            &#8220;This is a line.\n&#8221;;<br>examplefile &lt;&lt; &#8220;This is another line.\n&#8221;;<br>examplefile.close();<br>}<br>return
            0;<br>}</td>
            <td><font color="#008000">file example.txt<br></font>This is a line.<br>This is
            another line.</td>
        </tr>
    </tbody>
</table>
<p>从文件中读入数据也可以用与 cin的使用同样的方法：</p>
<table border="2" cellpadding="1" cellspacing="1" width="400">
    <tbody>
        <tr>
            <td><font color="#008000">// reading a text file</font><br>#include
            &lt;iostream.h&gt;<br>#include &lt;fstream.h&gt;<br>#include
            &lt;stdlib.h&gt;int main () {<br>char buffer[256];<br>ifstream
            examplefile (&#8221;example.txt&#8221;);<br>if (! examplefile.is_open())<br>{ cout
            &lt;&lt; &#8220;Error opening file&#8221;; exit (1); }<br>while (! examplefile.eof()
            ) {<br>examplefile.getline (buffer,100);<br>cout &lt;&lt; buffer
            &lt;&lt; endl;<br>}<br>return 0;<br>}</td>
            <td>This is a
            line.<br>This is another line.</td>
        </tr>
    </tbody>
</table>
<p>上面的例子读入一个文本文件的内容，然后将它打印到屏幕上。注意我们使用了一个新的成员函数叫做eof ，它是ifstream 从类 ios
中继承过来的，当到达文件末尾时返回true 。</p>
<h3>状态标志符的验证(Verification of state flags)</h3>
<p>除了eof()以外，还有一些验证流的状态的成员函数（所有都返回bool型返回值）：</p>
<ul>
    <li><strong> bad()</strong> 如果在读写过程中出错，返回 true
    。例如：当我们要对一个不是打开为写状态的文件进行写入时，或者我们要写入的设备没有剩余空间的时候。
    </li>
    <li><strong> fail()</strong> 除了与bad() 同样的情况下会返回 true
    以外，加上格式错误时也返回true ，例如当想要读入一个整数，而获得了一个字母的时候。
    </li>
    <li><strong> eof()</strong> 如果读文件到达文件末尾，返回true。
    </li>
    <li><strong> good()</strong> 这是最通用的：如果调用以上任何一个函数返回true 的话，此函数返回
    false 。 </li>
</ul>
<p>要想重置以上成员函数所检查的状态标志，你可以使用成员函数clear()，没有参数。</p>
<h3>获得和设置流指针(get and put stream pointers)</h3>
<p>所有输入/输出流对象(i/o streams objects)都有至少一个流指针：</p>
<ul>
    <li>ifstream， 类似istream, 有一个被称为get pointer的指针，指向下一个将被读取的元素。
    </li>
    <li>ofstream, 类似 ostream, 有一个指针 put pointer ，指向写入下一个元素的位置。
    </li>
    <li>fstream, 类似 iostream, 同时继承了get 和 put </li>
</ul>
<p>我们可以通过使用以下成员函数来读出或配置这些指向流中读写位置的流指针：</p>
<ul>
    <li><strong> tellg() 和 tellp()</strong> 这两个成员函数不用传入参数，返回pos_type
    类型的值(根据ANSI-C++ 标准) ，就是一个整数，代表当前get 流指针的位置 (用tellg) 或 put
    流指针的位置(用tellp).
    </li>
    <li><strong> seekg() 和seekp()</strong> 这对函数分别用来改变流指针get
    和put的位置。两个函数都被重载为两种不同的原型：
    <p>seekg ( pos_type position );<br>seekp ( pos_type
    position );</p>
    <p>使用这个原型，流指针被改变为指向从文件开始计算的一个绝对位置。要求传入的参数类型与函数 tellg 和tellp
    的返回值类型相同。</p>
    <p>seekg ( off_type offset, seekdir direction );<br>seekp
    ( off_type offset, seekdir direction );</p>
    <p>使用这个原型可以指定由参数direction决定的一个具体的指针开始计算的一个位移(offset)。它可以是：</p>
    <table border="1" cellpadding="3" cellspacing="0">
        <tbody>
            <tr>
                <td>ios::beg</td>
                <td>从流开始位
                置计算的位移</td>
            </tr>
            <tr>
                <td>ios::cur</td>
                <td>从流指针当
                前位置开始计算的位移</td>
            </tr>
            <tr>
                <td>ios::end</td>
                <td>从流末尾处
                开始计算的位移</td>
            </tr>
        </tbody>
    </table>
    </li>
</ul>
<p>流 指针 get 和 put 的值对文本文件(text file)和二进制文件(binary
file)的计算方法都是不同的，因为文本模式的文件中某些特殊字符可能被修改。由于这个原因，建议对以文本文件模式打开的文件总是使用seekg 和
seekp的第一种原型，而且不要对tellg 或 tellp
的返回值进行修改。对二进制文件，你可以任意使用这些函数，应该不会有任何意外的行为产生。</p>
<p>以下例子使用这些函数来获得一个二进制文件的大小：</p>
<table border="2" cellpadding="1" cellspacing="1" width="400">
    <tbody>
        <tr>
            <td><font color="#008000">// obtaining file size<br><font color="#000000">#include
            &lt;iostream.h&gt;<br>#include &lt;fstream.h&gt;</font></font><font color="#008000"><font color="#000000">const char * filename =
            &#8220;example.txt&#8221;;</font></font><font color="#008000"><font color="#000000">int
            main () {<br>long l,m;<br>ifstream file (filename,
            ios::in|ios::binary);<br>l = file.tellg();<br>file.seekg (0, ios::end);<br>m
            = file.tellg();<br>file.close();<br>cout &lt;&lt; &#8220;size of &#8221; &lt;&lt;
            filename;<br>cout &lt;&lt; &#8221; is &#8221; &lt;&lt; (m-l) &lt;&lt; &#8221; bytes.\n&#8221;;<br>return
            0;<br>}</font></font></td>
            <td>size of
            example.txt<br>is 40 bytes.</td>
        </tr>
    </tbody>
</table>
<h3>二进制文件(Binary files)</h3>
<p>在二进制文件中，使用&lt;&lt;
和&gt;&gt;，以及函数（如getline）来操作符输入和输出数据，没有什么实际意义，虽然它们是符合语法的。</p>
<p>文 件流包括两个为顺序读写数据特殊设计的成员函数：write 和 read。第一个函数 (write) 是ostream
的一个成员函数，都是被ofstream所继承。而read 是istream 的一个成员函数，被ifstream 所继承。类 fstream
的对象同时拥有这两个函数。它们的原型是：</p>
<p>write ( char * buffer, streamsize size );<br>read (
char * buffer, streamsize size );</p>
<p>这里 buffer 是一块内存的地址，用来存储或读出数据。参数size 是一个整数值，表示要从缓存（buffer）中读出或写入的字符数。</p>
<table border="2" cellpadding="1" cellspacing="1" width="400">
    <tbody>
        <tr>
            <td><font color="#008000"><font color="#008000">// reading binary file</font><br><font color="#000000">#include &lt;iostream&gt;<br>#include &lt;fstream.h&gt;</font></font><font color="#008000"><font color="#000000">const char * filename =
            &#8220;example.txt&#8221;;</font></font><font color="#008000"><font color="#000000">int
            main () {<br>char * buffer;<br>long size;<br>ifstream file (filename,
            ios::in|ios::binary|ios::ate);<br>size = file.tellg();<br>file.seekg (0,
            ios::beg);<br>buffer = new char [size];<br>file.read (buffer, size);<br>file.close();</font></font><font color="#008000"><font color="#000000">cout &lt;&lt; &#8220;the complete file
            is in a buffer&#8221;;</font></font><font color="#008000"><font color="#000000">delete[] buffer;<br>return 0;<br>}</font></font></td>
            <td>The
            complete file<br>is in a buffer</td>
        </tr>
    </tbody>
</table>
<h3>缓存和同步(Buffers and Synchronization)</h3>
<p>当 我们对文件流进行操作的时候，它们与一个streambuf
类型的缓存(buffer)联系在一起。这个缓存（buffer）实际是一块内存空间，作为流(stream)和物理文件的媒介。例如，对于一个输出流，
每次成员函数put
(写一个单个字符)被调用，这个字符不是直接被写入该输出流所对应的物理文件中的，而是首先被插入到该流的缓存（buffer）中。</p>
<p>当缓存被排放出来(flush)时，它里面的所有数据或者被写入物理媒质中（如果是一个输出流的话），或者简单的被抹掉(如果是一个输入流的话)。
这个过程称为同步(synchronization)，它会在以下任一情况下发生：</p>
<ul>
    <li><strong> 当文件被关闭时:</strong>  在文件被关闭之前，所有还没有被完全写出或读取的缓存都将被同步。
    </li>
    <li><strong> 当缓存buffer 满时:</strong> 缓存Buffers
    有一定的空间限制。当缓存满时，它会被自动同步。
    </li>
    <li><strong> 控制符明确指明:</strong> 当遇到流中某些特定的控制符时，同步会发生。这些控制符包括：flush
    和endl。
    </li>
    <li><strong> 明确调用函数sync():</strong>  调用成员函数sync()
    (无参数)可以引发立即同步。这个函数返回一个int 值，等于-1 表示流没有联系的缓存或操作失败。 </li>
</ul>
<br><img src ="http://www.cppblog.com/yuqilin1228/aggbug/110619.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yuqilin1228/" target="_blank">LynnRaymond</a> 2010-03-26 22:01 <a href="http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110619.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载】并查集 (Union-Find Sets)</title><link>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110618.html</link><dc:creator>LynnRaymond</dc:creator><author>LynnRaymond</author><pubDate>Fri, 26 Mar 2010 13:59:00 GMT</pubDate><guid>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110618.html</guid><wfw:comment>http://www.cppblog.com/yuqilin1228/comments/110618.html</wfw:comment><comments>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110618.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yuqilin1228/comments/commentRss/110618.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yuqilin1228/services/trackbacks/110618.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 由于时间太久忘记出处了，不好意思...找到后补上.并查集 (Union-Find Sets)并查集： (union-find sets)是一种简单的用途广泛的集合. 并查集是若干个不相交集合，能够实现较快的合并和判断元素所在集合的操作，应用很多。一般采取树形结构来存储并查集，并利用一个rank数组来存储集合的深度下界，在查找操作时进行路径压缩使后续的查找操作加速。这样优化实现的...&nbsp;&nbsp;<a href='http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110618.html'>阅读全文</a><img src ="http://www.cppblog.com/yuqilin1228/aggbug/110618.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yuqilin1228/" target="_blank">LynnRaymond</a> 2010-03-26 21:59 <a href="http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110618.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载】从零开始学算法：十种排序算法介绍</title><link>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110617.html</link><dc:creator>LynnRaymond</dc:creator><author>LynnRaymond</author><pubDate>Fri, 26 Mar 2010 13:54:00 GMT</pubDate><guid>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110617.html</guid><wfw:comment>http://www.cppblog.com/yuqilin1228/comments/110617.html</wfw:comment><comments>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110617.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yuqilin1228/comments/commentRss/110617.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yuqilin1228/services/trackbacks/110617.html</trackback:ping><description><![CDATA[<a title="Permanent link to 从零开始学算法：十种排序算法介绍（上）" href="http://www.matrix67.com/blog/archives/166" rel="bookmark"><u><font color="#800080">从零开始学算法：十种排序算法介绍（上）</font></u></a>&nbsp;&nbsp;
From:Martix67【牛啊！】<br>今天我正式开始按照<a href="http://www.matrix67.com/blog/article.asp?id=188" target="_blank"><u><font color="#0000ff">我的目录</font></u></a>写我的OI心得了。我要把
我所有学到的OI知识传给以后千千万万的OIer。以前写过的一些东西不重复写了，但我最后将会重新整理，使之成为一个完整的教程。<br>&nbsp;&nbsp;&nbsp;&nbsp;按照
我的目录，讲任何东西之前我都会先介绍时间复杂度的相关知识，以后动不动就会扯到这个东西。这个已经写过了，你可以在<a href="http://www.matrix67.com/blog/article.asp?id=134" target="_blank"><u><font color="#0000ff">这里</font></u></a>看到那篇又臭又长的文章。在讲
排序算法的过程中，我们将始终围绕时间复杂度的内容进行说明。<br>&nbsp;&nbsp;&nbsp;&nbsp;我把这篇文章称之为&#8220;从零开始学算法&#8221;，因为排序算法是最基础的算法，介绍
算法时从各种排序算法入手是最好不过的了。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;给出n个数，怎样将它们从小到大排序？下面一口气讲三种常用的算法，它们是最简单的、
最显然的、最容易想到的。选择排序(Selection
Sort)是说，每次从数列中找出一个最小的数放到最前面来，再从剩下的n-1个数中选择一个最小的，不断做下去。插入排序(Insertion
Sort)是，每次从数列中取一个还没有取出过的数，并按照大小关系插入到已经取出的数中使得已经取出的数仍然有序。冒泡排序(Bubble
Sort)分为若干趟进行，每一趟排序从前往后比较每两个相邻的元素的大小（因此一趟排序要比较n-1对位置相邻的数）并在每次发现前面的那个数比紧接它
后的数大时交换位置；进行足够多趟直到某一趟跑完后发现这一趟没有进行任何交换操作（最坏情况下要跑n-1趟，这种情况在最小的数位于给定数列的最后面时
发生）。事实上，在第一趟冒泡结束后，最后面那个数肯定是最大的了，于是第二次只需要对前面n-1个数排序，这又将把这n-1个数中最小的数放到整个数列
的倒数第二个位置。这样下去，冒泡排序第i趟结束后后面i个数都已经到位了，第i+1趟实际上只考虑前n-i个数（需要的比较次数比前面所说的n-1要
小）。这相当于用数学归纳法证明了冒泡排序的正确性：实质与选择排序相同。上面的三个算法描述可能有点模糊了，没明白的话网上找资料，代码和动画演示遍地
都是。<br><img alt="" src="http://www.matrix67.com/blogimage/200703311.gif" border="0"><br>&nbsp;&nbsp;&nbsp;&nbsp;这
三种算法非常容易理解，因为我们生活当中经常在用。比如，班上的MM搞选美活动，有人叫我给所有MM排个名。我们通常会用选择排序，即先找出自己认为最漂
亮的，然后找第二漂亮的，然后找第三漂亮的，不断找剩下的人中最满意的。打扑克牌时我们希望抓完牌后手上的牌是有序的，三个8挨在一起，后面紧接着两个
9。这时，我们会使用插入排序，每次拿到一张牌后把它插入到手上的牌中适当的位置。什么时候我们会用冒泡排序呢？比如，体育课上从矮到高排队时，站队完毕
后总会有人出来，比较挨着的两个人的身高，指挥到：你们俩调换一下，你们俩换一下。<br>&nbsp;&nbsp;&nbsp;&nbsp;这是很有启发性的。这告诉我们，什么时候用什么排序最
好。当人们渴望先知道排在前面的是谁时，我们用选择排序；<strong style="color: #0000ff;">当我们不断拿
到新的数并想保持已有的数始终有序时，我们用插入排序；当给出的数列已经比较有序，只需要小幅度的调整一下时，我们用冒泡排序。 <br><br>&nbsp;&nbsp;&nbsp;&nbsp;我
们来算一下最坏情况下三种算法各需要多少次比较和赋值操作。<br>&nbsp;&nbsp;&nbsp;&nbsp;选择排序在第i次选择时赋值和比较都需要n-i次（在n-i+1个数中选一个
出来作为当前最小值，其余n-i个数与当前最小值比较并不断更新当前最小值），然后需要一次赋值操作。总共需要n(n-1)/2次比较与n(n-1)
/2+n次赋值。<br>&nbsp;&nbsp;&nbsp;&nbsp;插入排序在第i次寻找插入位置时需要最多i-1次比较（从后往前找到第一个比待插入的数小的数，最坏情况发生在这个数是
所有已经取出的数中最小的一个的时候），在已有数列中给新的数腾出位置需要i-1次赋值操作来实现，还需要两次赋值借助临时变量把新取出的数搬进搬出。也
就是说，最坏情况下比较需要n(n-1)/2次，赋值需要n(n-1)/2+2n次。我这么写有点误导人，大家不要以为程序的实现用了两个数组哦，其实一
个数组就够了，看看上面的演示就知道了。我只说算法，一般不写如何实现。学算法的都是强人，知道算法了都能写出一个漂亮的代码来。<br>&nbsp;&nbsp;&nbsp;&nbsp;冒泡排
序第i趟排序需要比较n-i次，n-1趟排序总共n(n-1)/2次。给出的序列逆序排列是最坏的情况，这时每一次比较都要进行交换操作。一次交换操作需
要3次赋值实现，因此冒泡排序最坏情况下需要赋值3n(n-1)/2次。<br>&nbsp;&nbsp;&nbsp;&nbsp;按照渐进复杂度理论，忽略所有的常数，三种排序的最坏情况下复杂
度都是一样的：O(n^2)。但实际应用中三种排序的效率并不相同。实践证明（政治考试时每道大题都要用这四个字），插入排序是最快的（虽然最坏情况下与
选择排序相当甚至更糟），因为每一次插入时寻找插入的位置多数情况只需要与已有数的一部分进行比较（你可能知道这还能二分）。你或许会说冒泡排序也可以在
半路上完成，还没有跑到第n-1趟就已经有序。但冒泡排序的交换操作更费时，而插入排序中找到了插入的位置后移动操作只需要用赋值就能完成（你可能知道这
还能用move）。本文后面将介绍的一种算法就利用插入排序的这些优势。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;我们证明了，三种排序方法在最坏情况下时间复杂度都是
O(n^2)。但大家想过吗，这只是最坏情况下的。在很多时候，复杂度没有这么大，因为插入和冒泡在数列已经比较有序的情况下需要的操作远远低于n^2次
（最好情况下甚至是线性的）。抛开选择排序不说（因为它的复杂度是&#8220;死&#8221;的，对于选择排序没有什么&#8220;好&#8221;的情况），我们下面探讨插入排序和冒泡排序在特定
数据和平均情况下的复杂度。<br>&nbsp;&nbsp;&nbsp;&nbsp;你会发现，如果把插入排序中的移动赋值操作看作是把当前取出的元素与前面取出的且比它大的数逐一交换，那插入
排序和冒泡排序对数据的变动其实都是相邻元素的交换操作。下面我们说明，若只能对数列中相邻的数进行交换操作，如何计算使得n个数变得有序最少需要的交换
次数。<br>&nbsp;&nbsp;&nbsp;&nbsp;我们定义逆序对的概念。假设我们要把数列从小到大排序，一个逆序对是指的在原数列中，左边的某个数比右边的大。也就是说，如果找到
了某个i和j使得i&lt;j且Ai&gt;Aj，我们就说我们找到了一个逆序对。比如说，数列3,1,4,2中有三个逆序对，而一个已经有序的数列逆序
对个数为0。我们发现，交换两个相邻的数最多消除一个逆序对，且冒泡排序（或插入排序）中的一次交换恰好能消除一个逆序对。那么显然，原数列中有多少个逆
序对冒泡排序（或插入排序）就需要多少次交换操作，这个操作次数不可能再少。<br>&nbsp;&nbsp;&nbsp;&nbsp;若给出的n个数中有m个逆序对，插入排序的时间复杂度可以说
是O(m+n)的，而冒泡排序不能这么说，因为冒泡排序有很多&#8220;无用&#8221;的比较（比较后没有交换），这些无用的比较超过了O(m+n)个。从这个意义上说，
插入排序仍然更为优秀，因为冒泡排序的复杂度要受到它跑的趟数的制约。一个典型的例子是这样的数列：8, 2, 3, 4, 5, 6, 7,
1。在这样的输入数据下插入排序的优势非常明显，冒泡排序只能哭着喊上天不公。<br>&nbsp;&nbsp;&nbsp;&nbsp;然而，我们并不想计算排序算法对于某个特定数据的效率。我
们真正关心的是，对于所有可能出现的数据，算法的平均复杂度是多少。不用激动了，平均复杂度并不会低于平方。下面证明，两种算法的平均复杂度仍然是
O(n^2)的。<br>&nbsp;&nbsp;&nbsp;&nbsp;我们仅仅证明算法需要的交换次数平均为O(n^2)就足够了。前面已经说过，它们需要的交换次数与逆序对的个数相同。我
们将证明，n个数的数列中逆序对个数平均O(n^2)个。<br>&nbsp;&nbsp;&nbsp;&nbsp;计算的方法是十分巧妙的。如果把给出的数列反过来（从后往前倒过来写），你会发
现原来的逆序对现在变成顺序的了，而原来所有的非逆序对现在都成逆序了。正反两个数列的逆序对个数加起来正好就是数列所有数对的个数，它等于n(n-
1)/2。于是，平均每个数列有n(n-1)/4个逆序对。忽略常数，逆序对平均个数O(n^2)。<br>&nbsp;&nbsp;&nbsp;&nbsp;上面的讨论启示我们，要想搞出一个复
杂度低于平方级别的排序算法，我们需要想办法能把离得老远的两个数进行操作。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;人们想啊想啊想啊，怎么都想不出怎样才能搞出复杂度
低于平方的算法。后来，英雄出现了，Donald Shell发明了一种新的算法，我们将证明它的复杂度最坏情况下也没有O(n^2)
（似乎有人不喜欢研究正确性和复杂度的证明，我会用实例告诉大家，这些证明是非常有意思的）。他把这种算法叫做Shell增量排序算法（大家常说的希尔排
序）。<br>&nbsp;&nbsp;&nbsp;&nbsp;Shell排序算法依赖一种称之为&#8220;排序增量&#8221;的数列，不同的增量将导致不同的效率。假如我们对20个数进行排序，使用的增量为
1,3,7。那么，我们首先对这20个数进行&#8220;7-排序&#8221;(7-sortedness)。所谓7-排序，就是按照位置除以7的余数分组进行排序。具体地
说，我们将把在1、8、15三个位置上的数进行排序，将第2、9、16个数进行排序，依此类推。这样，对于任意一个数字k，单看A(k),
A(k+7), A(k+14),
&#8230;这些数是有序的。7-排序后，我们接着又进行一趟3-排序（别忘了我们使用的排序增量为1,3,7）。最后进行1-排序（即普通的排序）后整个
Shell算法完成。看看我们的例子：<br><br>&nbsp;&nbsp;3 7 9 0 5 1 6
8 4 2 0 6 1 5 7 3 4 9 8 2&nbsp;&nbsp;&lt;-- 原数列<br>&nbsp;&nbsp;3 3 2 0 5 1 5 7 4 4 0 6 1 6 8
7 9 9 8 2&nbsp;&nbsp;&lt;-- 7-排序后<br>&nbsp;&nbsp;0 0 1 1 2 2 3 3 4 4 5 6 5 6 8 7 7 9 8
9&nbsp;&nbsp;&lt;-- 3-排序后<br>&nbsp;&nbsp;0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9&nbsp;&nbsp;&lt;--
1-排序后（完成）<br><br>&nbsp;&nbsp;&nbsp;&nbsp;在每一趟、每一组的排序中我们总是使用插入排序。仔细观察上面的例子你会发现是什么导致了
Shell排序的高效。对，每一趟排序将使得数列部分有序，从而使得以后的插入排序很快找到插入位置。我们下面将紧紧围绕这一点来证明Shell排序算法
的时间复杂度上界。<br>&nbsp;&nbsp;&nbsp;&nbsp;只要排序增量的第一个数是1，Shell排序算法就是正确的。但是不同的增量将导致不同的时间复杂度。我们上面例子中
的增量(1, 3, 7, 15, 31, &#8230;,
2^k-1)是使用最广泛的增量序列之一，可以证明使用这个增量的时间复杂度为O(n&#8730;n)。这个证明很简单，大家可以参看一些其它的资料，我们今天不证
明它。今天我们证明，使用增量1, 2, 3, 4, 6, 8, 9, 12, 16, &#8230;, 2^p*3^q，时间复杂度为O(n*(log
n)^2)。<br>&nbsp;&nbsp;&nbsp;&nbsp;很显然，任何一个大于1的正整数都可以表示为2x+3y，其中x和y是非负整数。于是，如果一个数列已经是2-排序的且是
3-排序的，那么对于此时数列中的每一个数A(i)，它的左边比它大的只有可能是A(i-1)。A2绝对不可能比A12大，因为10可以表示为两个2和两
个3的和，则A2&lt;A4&lt;A6&lt;A9&lt;A12。那么，在这个增量中的1-排序时每个数找插入位置只需要比较一次。一共有n个数，
所以1-排序是O(n)的。事实上，这个增量中的2-排序也是O(n)，因为在2-排序之前，这个数列已经是4-排序且6-排序过的，只看数列的奇数项或
者偶数项（即单看每一组）的话就又成了刚才的样子。这个增量序列巧妙就巧妙在，如果我们要进行h-排序，那么它一定是2h-排序过且3h-排序过，于是处
理每个数A(i)的插入时就只需要和A(i-h)进行比较。这个结论对于最开始几次（h值较大时）的h-排序同样成立，当2h、3h大于n时，按照定义，
我们也可以认为数列是2h-排序和3h-排序的，这并不影响上述结论的正确性（你也可以认为h太大以致于排序时每一组里的数字不超过3个，属于常数级）。
现在，这个增量中的每一趟排序都是O(n)的，我们只需要数一下一共跑了多少趟。也就是说，我们现在只需要知道小于n的数中有多少个数具有2^p*3^q
的形式。要想2^p*3^q不超过n，p的取值最多O(log n)个，q的取值最多也是O(log
n)个，两两组合的话共有O(logn*logn)种情况。于是，这样的增量排序需要跑O((log
n)^2)趟，每一趟的复杂度O(n)，总的复杂度为O(n*(log n)^2)。早就说过了，证明时间复杂度其实很有意思。<br>&nbsp;&nbsp;&nbsp;&nbsp;我们自然
会想，有没有能使复杂度降到O(nlogn)甚至更低的增量序列。很遗憾，现在没有任何迹象表明存在O(nlogn)的增量排序。但事实上，很多时候
Shell排序的实际效率超过了O(nlogn)的排序算法。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;后面我们将介绍三种O(nlogn)的排序算法和三种线性时间的排
序算法。最后我们将以外部排序和排序网络结束这一章节。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;很多人问到我关于转贴的问题。我欢迎除商业目的外任何形式的转贴（论坛、
Blog、Wiki、个人网站、PodCast，甚至做成ppt、pdf），但一定要注明出处，最好保留原始链接。我的网站需要点反向链接才能在网络中生
存下去，大家也都可以关注并且推广这个Blog。我一直支持cc版权协议，因此发现了文章中的问题或者想要补充什么东西尽管提出来，好让更多的人学习到好
东西。我昨天看Blog上原来写的一些东西，居然连着发现了几个错误式子和错别字，好奇大家居然没有提出来。发现了问题真的要告诉我，即使格式有点问题也
要说一下，决不能让它那么错着。另外有什么建议或想法也请说一下，我希望听到不同的声音不同的见解，好让我决定这类文章以后的发展方向。<br><br>Matrix67
原创<br>转贴请注明出处<br><br><a  href="http://www.matrix67.com/blog/archives/166" title="Permanent link to
从零开始学算法：十种排序算法介绍（上）" rel="bookmark"><u><font color="#800080">从零开始学算法：十种排序算法介绍（中）</font></u></a><br>&nbsp;本文被华丽的分割线分为了四段。对
于O(nlogn)的排序算法，我们详细介绍归并排序并证明归并排序的时间复杂度，然后简单介绍堆排序，之后给出快速排序的基本思想和复杂度证明。最后我
们将证明，O(nlogn)在理论上已经达到了最优。学过OI的人一般都学过这些很基础的东西，大多数OIer们不必看了。为了保持系列文章的完整性，我
还是花时间写了一下。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;首先考虑一个简单的问题：如何在线性的时间内将两个有序队列合并为一个有序队列（并输出）？<br><br>A
队列：1 3 5 7 9<br>B队列：1 2 7 8 9<br><br>&nbsp;&nbsp;&nbsp;&nbsp;看上面的例子，AB两个序列都是已经有序的了。在给出数据已经有序的情况下，我
们会发现很多神奇的事，比如，我们将要输出的第一个数一定来自于这两个序列各自最前面的那个数。两个数都是1，那么我们随便取出一个（比如A队列的那个
1）并输出：<br><br>A队列：<s>1</s> 3 5 7 9<br>B队列：1
2 7 8 9<br>输出：1<br><br>&nbsp;&nbsp;&nbsp;&nbsp;注意，我们取出了一个数，在原数列中删除这个数。删除操作是通过移动队首指针实
现的，否则复杂度就高了。<br>&nbsp;&nbsp;&nbsp;&nbsp;现在，A队列打头的数变成3了，B队列的队首仍然是1。此时，我们再比较3和1哪个大并输出小的那个数：<br><br>A队列：<s>1</s> 3
5 7 9<br>B队列：<s>1</s> 2 7 8 9<br>输出：1 1<br><br>&nbsp;&nbsp;&nbsp;&nbsp;接
下来的几步如下：<br><br>A队列：<s>1</s> 3 5 7
9&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A队列：<s>1 3</s> 5 7 9&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A队列：<s>1 3 5</s> 7
9&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;A队列：<s>1 3 5 7</s> 9<br>B队列：<s>1 2</s> 7 8 9&nbsp;&nbsp; ==&gt;&nbsp;&nbsp; B队列：<s>1
2</s> 7 8 9&nbsp;&nbsp; ==&gt;&nbsp;&nbsp; B队列：<s>1 2</s> 7 8 9&nbsp;&nbsp;&nbsp;&nbsp;==&gt;&nbsp;&nbsp; B队列：<s>1 2</s> 7
8 9&nbsp;&nbsp;&nbsp;&nbsp; &#8230;&#8230;<br>输出：1 1 2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;输出：1 1 2 3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;输出：1 1 2 3
5&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 输出：1 1 2 3 5 7<br><br>&nbsp;&nbsp;&nbsp;&nbsp;我希望你明白了这是怎么做的。这个做法显然是正确的，复杂
度显然是线性。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;归并排序(Merge
Sort)将会用到上面所说的合并操作。给出一个数列，归并排序利用合并操作在O(nlogn)的时间内将数列从小到大排序。归并排序用的是分治
(Divide and
Conquer)的思想。首先我们把给出的数列平分为左右两段，然后对两段数列分别进行排序，最后用刚才的合并算法把这两段（已经排过序的）数列合并为一
个数列。有人会问&#8220;对左右两段数列分别排序时用的什么排序&#8221;么？答案是：用归并排序。也就是说，我们递归地把每一段数列又分成两段进行上述操作。你不需要
关心实际上是怎么操作的，我们的程序代码将递归调用该过程直到数列不能再分（只有一个数）为止。<br>&nbsp;&nbsp;&nbsp;&nbsp;初看这个算法时有人会误以为时间复杂度相
当高。我们下面给出的一个图将用非递归的眼光来看归并排序的实际操作过程，供大家参考。我们可以借助这个图证明，归并排序算法的时间复杂度为
O(nlogn)。<br><br>[3] [1] [4] [1] [5] [9]
[2] [7]<br>&nbsp;&nbsp;\ /&nbsp;&nbsp;&nbsp;&nbsp; \ /&nbsp;&nbsp;&nbsp;&nbsp; \ /&nbsp;&nbsp;&nbsp;&nbsp; \ /<br>[1 3]&nbsp;&nbsp; [1 4]&nbsp;&nbsp; [5 9]&nbsp;&nbsp; [2
7]<br>&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp; /&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp; /<br>&nbsp;&nbsp; [1 1 3 4]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [2 5 7 9]<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
\&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [1 1 2 3 4 5 7 9]<br><br>&nbsp;&nbsp;&nbsp;&nbsp;上图中的每一个&#8220; \ /
&#8221;表示的是上文所述的线性时间合并操作。上图用了4行来图解归并排序。如果有n个数，表示成上图显然需要O(logn)行。每一行的合并操作复杂度总和都
是O(n)，那么logn行的总复杂度为O(nlogn)。这相当于用递归树的方法对归并排序的复杂度进行了分析。假设，归并排序的复杂度为
T(n)，T(n)由两个T(n/2)和一个关于n的线性时间组成，那么T(n)=2*T(n/2)+O(n)。不断展开这个式子我们可以同样可以得到
T(n)=O(nlogn)的结论，你可以自己试试。如果你能在线性的时间里把分别计算出的两组不同数据的结果合并在一起，根据
T(n)=2*T(n/2)+O(n)=O(nlogn)，那么我们就可以构造O(nlogn)的分治算法。这个结论后面经常用。我们将在计算几何部分举
一大堆类似的例子。<br>&nbsp;&nbsp;&nbsp;&nbsp;如果你第一次见到这么诡异的算法，你可能会对<a href="http://www.matrix67.com/blog/article.asp?id=29" target="_blank"><strong> <font color="#618898">这个</font></strong> </a>感兴趣。
分治是递归的一种应用。这是我们第一次接触递归运算。下面说的快速排序也是用的递归的思想。递归程序的复杂度分析通常和上面一样，主定理(Master
Theory)可以简化这个分析过程。主定理和本文内容离得太远，我们以后也不会用它，因此我们不介绍它，大家可以自己去查。有个名词在这里的话找学习资
料将变得非常容易，我最怕的就是一个东西不知道叫什么名字，半天找不到资料。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;归并排序有一个有趣的副产品。利用归并排序能够在
O(nlogn)的时间里计算出给定序列里逆序对的个数。你可以用任何一种平衡二叉树来完成这个操作，但用归并排序统计逆序对更方便。我们讨论逆序对一般
是说的一个排列中的逆序对，因此这里我们假设所有数不相同。假如我们想要数1, 6, 3, 2, 5,
4中有多少个逆序对，我们首先把这个数列分为左右两段。那么一个逆序对只可能有三种情况：两个数都在左边，两个数都在右边，一个在左一个在右。在左右两段
分别处理完后，线性合并的过程中我们可以顺便算出所有第三种情况的逆序对有多少个。换句话说，我们能在线性的时间里统计出A队列的某个数比B队列的某个数
大有多少种情况。<br><br>A队列：1 3 6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A队列：<s>1</s>
3 6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A队列：<s>1</s> 3 6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A队列：<s>1 3</s> 6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A队列：<s>1 3</s>
6<br>B队列：2 4 5&nbsp;&nbsp; ==&gt;&nbsp;&nbsp; B队列：2 4 5&nbsp;&nbsp; ==&gt;&nbsp;&nbsp; B队列：<s>2</s> 4 5&nbsp;&nbsp;
==&gt;&nbsp;&nbsp; B队列：<s>2</s> 4 5&nbsp;&nbsp; ==&gt;&nbsp;&nbsp; B队列：<s>2 4</s> 5&nbsp;&nbsp; &#8230;&#8230;<br>输
出：&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 输出：1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;输出：1 2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;输出：1 2
3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;输出：1 2 3 4<br><br>&nbsp;&nbsp;&nbsp;&nbsp;每一次从B队列取出一个数时，我们就知道了在A队列中有多少个数比B
队列的这个数大，它等于A队列现在还剩的数的个数。比如，当我们从B队列中取出2时，我们同时知道了A队列的3和6两个数比2大。在合并操作中我们不断更
新A队列中还剩几个数，在每次从B队列中取出一个数时把当前A队列剩的数目加进最终答案里。这样我们算出了所有&#8220;大的数在前一半，小的数在后一半&#8221;的情
况，其余情况下的逆序对在这之前已经被递归地算过了。<br><br>============================华丽的分割
线============================<br><br>&nbsp;&nbsp;&nbsp;&nbsp;堆排序(Heap Sort)利用了堆(Heap)这种数据结构（<a href="http://www.matrix67.com/blog/article.asp?id=115" target="_blank"><strong> <font color="#618898">什么是堆？</font></strong> </a>）。
堆的插入操作是平均常数的，而删除一个根节点需要花费O(log
n)的时间。因此，完成堆排序需要线性时间建立堆（把所有元素依次插入一个堆），然后用总共O(nlogn)的时间不断取出最小的那个数。只要堆会搞，堆
排序就会搞。堆在那篇日志里有详细的说明，因此这里不重复说了。<br><br>============================华丽的分割
线============================<br><br>&nbsp;&nbsp;&nbsp;&nbsp;快速排序(Quick
Sort)也应用了递归的思想。我们想要把给定序列分成两段，并对这两段分别进行排序。一种不错的想法是，选取一个数作为&#8220;关键字&#8221;，并把其它数分割为两
部分，把所有小于关键字的数都放在关键字的左边，大于关键字的都放在右边，然后递归地对左边和右边进行排序。把该区间内的所有数依次与关键字比较，我们就
可以在线性的时间里完成分割的操作。完成分割操作有很多有技巧性的实现方法，比如最常用的一种是定义两个指针，一个从前往后找找到比关键字大的，一个从后
往前找到比关键字小的，然后两个指针对应的元素交换位置并继续移动指针重复刚才的过程。这只是大致的方法，具体的实现还有很多细节问题。快速排序是我们最
常用的代码之一，网上的快速排序代码五花八门，各种语言，各种风格的都有。大家可以随便找一个来看看，我说过了我们讲算法但不讲如何实现。NOIp很简
单，很多人NOIp前就背了一个快速排序代码就上战场了。当时我把快速排序背完了，抓紧时间还顺便背了一下历史，免得晚上听写又不及格。<br>&nbsp;&nbsp;&nbsp;&nbsp;不
像归并排序，快速排序的时间复杂度很难计算。我们可以看到，归并排序的复杂度最坏情况下也是O(nlogn)的，而快速排序的最坏情况是O(n^2)的。
如果每一次选的关键字都是当前区间里最大（或最小）的数，那么这样将使得每一次的规模只减小一个数，这和插入排序、选择排序等平方级排序没有区别。这种情
况不是不可能发生。如果你每次选择关键字都是选择的该区间的第一个数，而给你的数据恰好又是已经有序的，那你的快速排序就完蛋了。显然，最好情况是每一次
选的数正好就是中位数，这将把该区间平分为两段，复杂度和前面讨论的归并排序一模一样。根据这一点，快速排序有一些常用的优化。比如，我们经常从数列中随
机取一个数当作是关键字（而不是每次总是取固定位置上的数），从而尽可能避免某些特殊的数据所导致的低效。更好的做法是随机取三个数并选择这三个数的中位
数作为关键字。而对三个数的随机取值反而将花费更多的时间，因此我们的这三个数可以分别取数列的头一个数、末一个数和正中间那个数。另外，当递归到了一定
深度发现当前区间里的数只有几个或十几个时，继续递归下去反而费时，不如返回插入排序后的结果。这种方法同时避免了当数字太少时递归操作出错的可能。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;下
面我们证明，快速排序算法的平均复杂度为O(nlogn)。不同的书上有不同的解释方法，这里我选用算法导论上的讲法。它更有技巧性一些，更有趣一些，需
要转几个弯才能想明白。<br>&nbsp;&nbsp;&nbsp;&nbsp;看一看快速排序的代码。正如我们提到过的那种分割方法，程序在经过若干次与关键字的比较后才进行一次交换，因此比
较的次数比交换次数更多。我们通过证明一次快速排序中元素之间的比较次数平均为O(nlogn)来说明快速排序算法的平均复杂度。证明的关键在于，我们需
要算出某两个元素在整个算法过程中进行过比较的概率。<br>&nbsp;&nbsp;&nbsp;&nbsp;我们举一个例子。假如给出了1到10这10个数，第一次选择关键字7将它们分成了
{1,2,3,4,5,6}和{8,9,10}两部分，递归左边时我们选择了3作为关键字，使得左部分又被分割为{1,2}和{4,5,6}。我们看到，
数字7与其它所有数都比较过一次，这样才能实现分割操作。同样地，1到6这6个数都需要与3进行一次比较（除了它本身之外）。然而，3和9决不可能相互比
较过，2和6也不可能进行过比较，因为第一次出现在3和9，2和6之间的关键字把它们分割开了。也就是说，两个数A(i)和A(j)比较过，当且仅当第一
个满足A(i)&lt;=x&lt;=A(j)的关键字x恰好就是A(i)或A(j)
（假设A(i)比A(j)小）。我们称排序后第i小的数为Z(i)，假设i&lt;j，那么第一次出现在Z(i)和Z(j)之间的关键字恰好就是Z(i)
或Z(j)的概率为2/(j-i+1)，这是因为当Z(i)和Z(j)之间还不曾有过关键字时，Z(i)和Z(j)处于同一个待分割的区间，不管这个区间
有多大，不管递归到哪里了，关键字的选择总是随机的。我们得到，Z(i)和Z(j)在一次快速排序中曾经比较过的概率为2/(j-i+1)。<br>&nbsp;&nbsp;&nbsp;&nbsp;现
在有四个数，2,3,5,7。排序时，相邻的两个数肯定都被比较过，2和5、3和7都有2/3的概率被比较过，2和7之间被比较过有2/4的可能。也就是
说，如果对这四个数做12次快速排序，那么2和3、3和5、5和7之间一共比较了12*3=36次，2和5、3和7之间总共比较了8*2=16次，2和7
之间平均比较了6次。那么，12次排序中总的比较次数期望值为36+16+6=58。我们可以计算出单次的快速排序平均比较了多少次：58/12=29
/6。其实，它就等于6项概率之和，1+1+1+2/3+2/3+2/4=29/6。这其实是与期望值相关的一个公式。<br>&nbsp;&nbsp;&nbsp;&nbsp;同样地，如果有n
个数，那么快速排序平均需要的比较次数可以写成下面的式子。令k=j-i，我们能够最终得到比较次数的期望值为O(nlogn)。<br>&nbsp;&nbsp; <img alt="" src="http://www.matrix67.com/blogimage/200704061.gif" border="0"><br>&nbsp;&nbsp;&nbsp;&nbsp;这
里用到了一个知识：1+1/2+1/3+&#8230;+1/n与log n增长速度相同，即&#931;(1/n)=&#920;(log n)。它的证明放在本文的最后。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;在
三种O(nlogn)的排序算法中，快速排序的理论复杂度最不理想，除了它以外今天说的另外两种算法都是以最坏情况O(nlogn)的复杂度进行排序。但
实践上看快速排序效率最高（不然为啥叫快速排序呢），原因在于快速排序的代码比其它同复杂度的算法更简洁，常数时间更小。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;快速排
序也有一个有趣的副产品：快速选择给出的一些数中第k小的数。一种简单的方法是使用上述任一种O(nlogn)的算法对这些数进行排序并返回排序后数组的
第k个元素。快速选择(Quick
Select)算法可以在平均O(n)的时间完成这一操作。它的最坏情况同快速排序一样，也是O(n^2)。在每一次分割后，我们都可以知道比关键字小的
数有多少个，从而确定了关键字在所有数中是第几小的。我们假设关键字是第m小。如果k=m，那么我们就找到了答案——第k小元素即该关键字。否则，我们递
归地计算左边或者右边：当k&lt;m时，我们递归地寻找左边的元素中第k小的；当k&gt;m时，我们递归地寻找右边的元素中第k-m小的数。由于我们
不考虑所有的数的顺序，只需要递归其中的一边，因此复杂度大大降低。复杂度平均线性，我们不再具体证了。<br>&nbsp;&nbsp;&nbsp;&nbsp;还有一种算法可以在最坏O(n)
的时间里找出第k小元素。那是我见过的所有算法中最没有实用价值的算法。那个O(n)只有理论价值。<br><br>============================
华丽的分割线============================<br><br>&nbsp;&nbsp;&nbsp;&nbsp;我们前面证明过，仅仅依靠交换相邻元素的操作，复杂度只
能达到O(n^2)。于是，人们尝试交换距离更远的元素。当人们发现O(nlogn)的排序算法似乎已经是极限的时候，又是什么制约了复杂度的下界呢？我
们将要讨论的是更底层的东西。我们仍然假设所有的数都不相等。<br>&nbsp;&nbsp;&nbsp;&nbsp;我们总是不断在数与数之间进行比较。你可以试试，只用4次比较绝对不可能给
4个数排出顺序。每多进行一次比较我们就又多知道了一个大小关系，从4次比较中一共可以获知4个大小关系。4个大小关系共有2^4=16种组合方式，而4
个数的顺序一共有4!=24种。也就是说，4次比较可能出现的结果数目不足以区分24种可能的顺序。更一般地，给你n个数叫你排序，可能的答案共有n!
个，k次比较只能区分2^k种可能，于是只有2^k&gt;=n!时才有可能排出顺序。等号两边取对数，于是，给n个数排序至少需要log2(n!)次。
注意，我们并没有说明一定能通过log2(n!)次比较排出顺序。虽然2^5=32超过了4!，但这不足以说明5次比较一定足够。如何用5次比较确定4个
数的大小关系还需要进一步研究。第一次例外发生在n=12的时候，虽然2^29&gt;12!，但现已证明给12个数排序最少需要30次比较。我们可以证
明log(n!)的增长速度与nlogn相同，即log(n!)=&#920;(nlogn)。这是排序所需要的最少的比较次数，它给出了排序复杂度的一个下界。
log(n!)=&#920;(nlogn)的证明也附在本文最后。<br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="http://www.matrix67.com/blog/article.asp?id=191" target="_blank"><strong> <font color="#618898">这篇日志</font></strong> </a>的第
三题中证明log2(N)是最优时用到了几乎相同的方法。那种&#8220;用天平称出重量不同的那个球至少要称几次&#8221;一类题目也可以用这种方法来解决。事实上，这里
有一整套的理论，它叫做信息论。信息论是由香农(Shannon)提出的。他用对数来表示信息量，用熵来表示可能的情况的随机性，通过运算可以知道你目前
得到的信息能够怎样影响最终结果的确定。如果我们的信息量是以2为底的，那信息论就变成信息学了。从根本上说，计算机的一切信息就是以2为底的信息量
(bits=<u>bi</u>nary digi<u>ts</u>)，因此我们常说香农是数字通信之父。信息论和热力学关系密切，比如熵的概念是直接
从热力学的熵定义引申过来的。和这个有关的东西已经严重偏题了，这里不说了，有兴趣可以去看《信息论与编码理论》。我对这个也很有兴趣，半懂不懂的，很想
了解更多的东西，有兴趣的同志不妨加入讨论。物理学真的很神奇，利用物理学可以解决很多纯数学问题，我有时间的话可以举一些例子。我他妈的为啥要选文科
呢。<br>&nbsp;&nbsp;&nbsp;&nbsp;后面将介绍的三种排序是线性时间复杂度，因为，它们排序时根本不是通过互相比较来确定大小关系的。<br><br><br>附
1：&#931;(1/n)=&#920;(log n)的证明<br>&nbsp;&nbsp;&nbsp;&nbsp;首先我们证明，&#931;(1/n)=O(log
n)。在式子1+1/2+1/3+1/4+1/5+&#8230;中，我们把1/3变成1/2，使得两个1/2加起来凑成一个1；再把1/5,1/6和1/7全部变成
1/4，这样四个1/4加起来又是一个1。我们把所有1/2^k的后面2^k-1项全部扩大为1/2^k，使得这2^k个分式加起来是一个1。现
在，1+1/2+&#8230;+1/n里面产生了几个1呢？我们只需要看小于n的数有多少个2的幂即可。显然，经过数的扩大后原式各项总和为log
n。O(logn)是&#931;(1/n)的复杂度上界。<br>&nbsp;&nbsp;&nbsp;&nbsp;然后我们证明，&#931;(1/n)=&#937;(log
n)。在式子1+1/2+1/3+1/4+1/5+&#8230;中，我们把1/3变成1/4，使得两个1/4加起来凑成一个1/2；再把1/5,1/6和1/7全部
变成1/8，这样四个1/8加起来又是一个1/2。我们把所有1/2^k的前面2^k-1项全部缩小为1/2^k，使得这2^k个分式加起来是一个1
/2。现在，1+1/2+&#8230;+1/n里面产生了几个1/2呢？我们只需要看小于n的数有多少个2的幂即可。显然，经过数的缩小后原式各项总和为1
/2*logn。&#937;(logn)是&#931;(1/n)的复杂度下界。<br><br><br>附2：log(n!)=&#920;(nlogn)的证明<br>&nbsp;&nbsp;&nbsp;&nbsp;首
先我们证明，log(n!)=O(nlogn)。显然n!&lt;n^n，两边取对数我们得到log(n!)&lt;log(n^n)，而
log(n^n)就等于nlogn。因此，O(nlogn)是log(n!)的复杂度上界。<br>&nbsp;&nbsp;&nbsp;&nbsp;然后我们证
明，log(n!)=&#937;(nlogn)。n!=n(n-1)(n-2)(n-3)&#8230;.1，把前面一半的因子全部缩小到n/2，后面一半因子全部舍去，显然
有n!&gt;(n/2)^(n/2)。两边取对数，log(n!)&gt;(n/2)log(n/2)，后者即&#937;(nlogn)。因
此，&#937;(nlogn)是log(n!)的复杂度下界。<br><br>今天写到这里了，大家帮忙校对哦<br>Matrix67原创<br>转贴请注明出
处<br><a  href="http://www.matrix67.com/blog/archives/166" title="Permanent link to
从零开始学算法：十种排序算法介绍（上）" rel="bookmark"><u><font color="#800080">从零开始学算法：十种排序算法介绍（下）</font></u></a><br>那么，有什么方法可以不用比较就能排
出顺序呢？借助Hash表的思想，多数人都能想出这样一种排序算法来。<br>&nbsp;&nbsp;&nbsp;&nbsp;我们假设给出的数字都在一定范围中，那么我们就可以开一个范围相同
的数组，记录这个数字是否出现过。由于数字有可能有重复，因此Hash表的概念需要扩展，我们需要把数组类型改成整型，用来表示每个数出现的次数。<br>&nbsp;&nbsp;&nbsp;&nbsp;看
这样一个例子，假如我们要对数列3 1 4 1 5 9 2 6 5 3 5
9进行排序。由于给定数字每一个都小于10，因此我们开一个0到9的整型数组T[i]，记录每一个数出现了几次。读到一个数字x，就把对应的T[x]加
一。<br><br>&nbsp;&nbsp;A[]= 3, 1, 4, 1, 5, 9, 2, 6,
5, 3, 5, 9<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +---+---+---+---+---+---+---+---+---+---+<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;数
字 i： | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+---+---+---+---+---+---+---+---+---+---+<br>出现次数T[i]： | 0 | 2 | 1 | 2 |
1 | 3 | 1 | 0 | 0 | 2 |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+---+---+---+---+---+---+---+---+---+---+<br><br>&nbsp;&nbsp;&nbsp;&nbsp;最后，我们用一个指针从前
往后扫描一遍，按照次序输出0到9，每个数出现了几次就输出几个。假如给定的数是n个大小不超过m的自然数，显然这个算法的复杂度是O(m+n)的。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;我
曾经以为，这就是线性时间排序了。后来我发现我错了。再后来，我发现我曾犯的错误是一个普遍的错误。很多人都以为上面的这个算法就是传说中的计数排序。问
题出在哪里了？为什么它不是线性时间的排序算法？原因是，这个算法根本不是排序算法，它根本没有对原数据进行排序。<br><br><br><strong> 问
题一：为什么说上述算法没有对数据进行排序？</strong> <br>STOP! You
should think for a while.<br><br>&nbsp;&nbsp;&nbsp;&nbsp;我们班有很多MM。和身高相差太远的MM在一起肯定很别扭，
接个吻都要弯腰才行（<a href="http://fayecatshome.spaces.live.com/" target="_blank"><strong> <font color="#618898">小猫</font></strong> </a>矮死了）。为此，我希望给我们班的MM的身高排序。我们班MM的身高，
再离谱也没有超过2米的，这很适合用我们刚才的算法。我们在黑板上画一个100到200的数组，MM依次自曝身高，我负责画&#8220;正&#8221;字统计人数。统计出来
了，从小到大依次为141, 143, 143, 147, 152, 153,
&#8230;。这算哪门子排序？就一排数字对我有什么用，我要知道的是哪个MM有多高。我们仅仅把元素的属性值从小到大列了出来，但我们没有对元素本身进行排序。也
就是说，我们需要知道输出结果的每个数值对应原数据的哪一个元素。下文提到的&#8220;排序算法的稳定性&#8221;也和属性值与实际元素的区别有关。<br><br><br><strong> 问
题二：怎样将线性时间排序后的输出结果还原为原数据中的元素？</strong> <br>STOP!
You should think for a while.<br><br>&nbsp;&nbsp;&nbsp;&nbsp;同样借助Hash表的思想，我们立即想到了类似于
开散列的方法。我们用链表把属性值相同的元素串起来，挂在对应的T[i]上。每次读到一个数，在增加T[i]的同时我们把这个元素放进T[i]延伸出去的
链表里。这样，输出结果时我们可以方便地获得原数据中的所有属性值为i的元素。<br><br>&nbsp;&nbsp;A[]= 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 9<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+---+---+---+---+---+---+---+---+---+---+<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;数字 i： | 0 | 1 | 2 | 3
| 4 | 5 | 6 | 7 | 8 | 9 |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+---+---+---+---+---+---+---+---+---+---+<br>出现次数T[i]： | 0 | 2 | 1 | 2 |
1 | 3 | 1 | 0 | 0 | 2 |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+---+o--+-o-+-o-+-o-+-o-+--o+---+---+-o-+<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;
|&nbsp;&nbsp; |&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<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;+---+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp; A[1]&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;
A[6]<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A[2]&nbsp;&nbsp;A[7]&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;A[3]&nbsp;&nbsp;A[5]&nbsp;&nbsp; A[8]&nbsp;&nbsp;&nbsp;&nbsp;|<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; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;A[12]<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
A[4]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;A[10]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;A[9]<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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;A[11]<br><br>&nbsp;&nbsp;&nbsp;&nbsp;形
象地说，我们在地上摆10个桶，每个桶编一个号，然后把数据分门别类放在自己所属的桶里。这种排序算法叫做桶式排序(Bucket
Sort)。本文最后你将看到桶式排序的另一个用途。<br>&nbsp;&nbsp;&nbsp;&nbsp;链表写起来比较麻烦，一般我们不使用它。我们有更简单的方法。<br><br><br><strong> 问
题三：同样是输出元素本身，你能想出不用链表的其它算法么？</strong> <br>STOP!
You should think for a while.<br><br>&nbsp;&nbsp;A[]= 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 9<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+---+---+---+---+---+---+---+---+---+---+<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;数字 i： | 0 | 1 | 2 | 3
| 4 | 5 | 6 | 7 | 8 | 9 |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+---+---+---+---+---+---+---+---+---+---+<br>出现次数T[i]： | 0 | 2 | 1 | 2 |
1 | 3 | 1 | 0 | 0 | 2 |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+---+---+---+---+---+---+---+---+---+---+<br>修改后的T[i]： | 0 | 2 | 3 | 5 |
6 | 9 | 10| 10| 10| 12|<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+---+---+---+---+---+---+---+---+---+---+<br><br>&nbsp;&nbsp;&nbsp;&nbsp;所有数都读入后，我们修改
T[i]数组的值，使得T[i]表示数字i可能的排名的最大值。比如，1最差排名第二，3最远可以排到第五。T数组的最后一个数应该等于输入数据的数字个
数。修改T数组的操作可以用一次线性的扫描累加完成。<br>&nbsp;&nbsp;&nbsp;&nbsp;我们还需要准备一个输出数组。然后，我们从后往前扫描A数组，依照T数组的指示依次
把原数据的元素直接放到输出数组中，同时T[i]的值减一。之所以从后往前扫描A数组，是因为这样输出结果才是稳定的。我们说一个排序算法是稳定的
(Stable)，当算法满足这样的性质：属性值相同的元素，排序后前后位置不变，本来在前面的现在仍然在前面。不要觉得排序算法是否具有稳定性似乎关系
不大，排序的稳定性在下文的某个问题中将变得非常重要。你可以倒回去看看前面说的七种排序算法哪些是稳定的。<br>&nbsp;&nbsp;&nbsp;&nbsp;例子中，A数组最后一个数9
所对应的T[9]=12，我们直接把9放在待输出序列中的第12个位置，然后T[9]变成11（这样下一次再出现9时就应该放在第11位）。<br><br>A[]= 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 9 &lt;--<br>T[i]=
0, 2, 3, 5, 6, 9, 10, 10, 10, 11<br>Ans
= _ _ _ _ _ _ _ _ _ _ _ 9<br><br>&nbsp;&nbsp;&nbsp;&nbsp;接下来的几步如下：<br><br>A[]= 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5 &lt;--<br>T[i]=
0, 2, 3, 5, 6, 8, 10, 10, 10, 11<br>Ans
= _ _ _ _ _ _ _ _ 5 _ _ 9<br><br>A[]= 3, 1, 4, 1, 5, 9, 2, 6, 5, 3
&lt;--<br>T[i]= 0, 2, 3, 4, 6, 8, 10,
10, 10, 11<br>Ans = _ _ _ _ 3 _ _ _ 5 _ _ 9<br><br>A[]= 3, 1, 4, 1, 5,
9, 2, 6, 5 &lt;--<br>T[i]= 0, 2, 3, 4, 6, 7,
10, 10, 10, 11<br>Ans = _ _ _ _ 3 _ _ 5 5 _ _ 9<br><br>&nbsp;&nbsp;&nbsp;&nbsp;这种算法叫
做计数排序(Counting Sort)。正确性和复杂度都是显然的。<br><br><br><strong> 问题四：给定数的数据范围大了该怎么
办？</strong> <br>STOP! You should think for a
while.<br><br>&nbsp;&nbsp;&nbsp;&nbsp;前面的算法只有在数据的范围不大时才可行，如果给定的数在长整范围内的话，这个算法是不可行的，因为
你开不下这么大的数组。Radix排序(Radix Sort)解决了这个难题。<br>&nbsp;&nbsp;&nbsp;&nbsp;昨天我没事翻了一下初中（9班）时的同学录，回忆了一下
过去。我把比较感兴趣的MM的生日列在下面（绝对真实）。如果列表中的哪个MM有幸看到了这篇日志（几乎不可能），左边的Support栏有我的电子联系
方式，我想知道你们怎么样了。排名不分先后。<br>
</strong>
<ul>
    <li><strong style="color: #0000ff;">19880818<br>
    </strong></li>
    <li><strong style="color: #0000ff;">19880816<br>
    </strong></li>
    <li><strong style="color: #0000ff;">19890426<br>
    </strong></li>
    <li><strong style="color: #0000ff;">19880405<br>
    </strong></li>
    <li><strong style="color: #0000ff;">19890125<br>
    </strong></li>
    <li><strong style="color: #0000ff;">19881004<br>
    </strong></li>
    <li><strong style="color: #0000ff;">19881209<br>
    </strong></li>
    <li><strong style="color: #0000ff;">19890126<br>
    </strong></li>
    <li><strong style="color: #0000ff;">19890228 </strong></li>
</ul>
<p><strong style="color: #0000ff;"><br>&nbsp;&nbsp;&nbsp;&nbsp;这就是我的数据了。现在，我要给这些数排序。假如我的电脑只能开出0..99的数组，那计数排序算法最多对两位数进行排序。我就把
每个八位数两位两位地分成四段（图1），分别进行四次计数排序。地球人都知道月份相同时应该看哪一日，因此我们看月份的大小时应该事先保证日已经有序。换
句话说，我们先对&#8220;最不重要&#8221;的部分进行排序。我们先对所有数的最后两位进行一次计数排序（图2）。注意观察1月26号的MM和4月26号的MM，本次排
序中它们的属性值相同，由于计数排序是稳定的，因此4月份那个排完后依然在1月份那个的前头。接下来我们对百位和千位进行排序（图3）。你可以看到两个
26日的MM在这一次排序中分出了大小，而月份相同的MM依然保持日数有序（因为计数排序是稳定的）。最后我们对年份排序（图4），完成整个算法。大家都
是跨世纪的好儿童，因此没有图5了。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <img alt="" src="http://www.matrix67.com/blogimage/200704131.gif" border="0"><br><br>&nbsp;&nbsp;&nbsp;&nbsp;这
种算法显然是正确的。它的复杂度一般写成O(d*(n+m))，其中n表示n个数，m是我开的数组大小（本例中m=100），d是一个常数因子（本例中
d=4）。我们认为它也是线性的。<br><br><br><strong> 问题五：这样的排序方法还有什么致命的缺陷？</strong> <br>STOP! You should think for a while.<br><br>&nbsp;&nbsp;&nbsp;&nbsp;即
使数据有30位，我们也可以用d=5或6的Radix算法进行排序。但，要是给定的数据有无穷多位怎么办？有人说，这可能么。这是可能的，比如给定的数据
是小数（更准确地说，实数）。基于比较的排序可以区分355/113和&#960;哪
个大，但你不知道Radix排序需要精确到哪一位。这下惨了，实数的出现把貌似高科技的线性时间排序打回了农业时代。这时，桶排序再度出山，挽救了线性时
间排序悲惨的命运。<br><br><br><strong> 问题六：如何对实数进行线性时间排序？</strong> <br>STOP! You should think for a while.<br><br>&nbsp;&nbsp;&nbsp;&nbsp;我
们把问题简化一下，给出的所有数都是0到1之间的小数。如果不是，也可以把所有数同时除以一个大整数从而转化为这种形式。我们依然设立若干个桶，比如，以
小数点后面一位数为依据对所有数进行划分。我们仍然用链表把同一类的数串在一起，不同的是，每一个链表都是有序的。也就是说，每一次读到一个新的数都要进
行一次插入排序。看我们的例子：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;A[]=
0.12345, 0.111, 0.618, 0.9, 0.99999<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+---+---+---+---+---+---+---+---+---+---+<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;十分位： | 0 | 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+---+-o-+---+---+---+---+-o-+---+---+-o-+<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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A[2]=0.111&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;A[3]=0.618&nbsp;&nbsp; A[4]=0.9<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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A[1]=0.12345&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A[5]=0.99999<br><br>&nbsp;&nbsp;&nbsp;&nbsp;假如再下一个读入的数是
0.122222，这个数需要插入到十分位为1的那个链表里适当的位置。我们需要遍历该链表直到找到第一个比0.122222大的数，在例子中则应该插入
到链表中A[2]和A[1]之间。最后，我们按顺序遍历所有链表，依次输出每个链表中的每个数。<br>&nbsp;&nbsp;&nbsp;&nbsp;这个算法显然是正确的，但复杂度显然不是
线性。事实上，这种算法最坏情况下是O(n^2)的，因为当所有数的十分位都相同时算法就是一个插入排序。和原来一样，我们下面要计算算法的平均时间复杂
度，我们希望这种算法的平均复杂度是线性的。<br>&nbsp;&nbsp;&nbsp;&nbsp;这次算平均复杂度我们用最笨的办法。我们将算出所有可能出现的情况的总时间复杂度，除以总的
情况数，得到平均的复杂度是多少。<br>&nbsp;&nbsp;&nbsp;&nbsp;每个数都可能属于10个桶中的一个，n个数总的情况有10^n种。这个值是我们庞大的算式的分母部分。
如果一个桶里有K个元素，那么只与这个桶有关的操作有O(K^2)次，它就是一次插入排序的操作次数。下面计算，在10^n种情况中，K0=1有多少种情
况。K0=1表示，n个数中只有一个数在0号桶，其余n-1个数的十分位就只能在1到9中选择。那么K0=1的情况有C(n,1)*9^(n-1)，而每
个K0=1的情况在0号桶中将产生1^2的复杂度。类似地，Ki=p的情况数为C(n,p)*9^(n-p)，复杂度总计为C(n,p)*9^(n-
p)*p^2。枚举所有K的下标和p值，累加起来，这个算式大家应该能写出来了，但是这个&#8230;&#8230;怎么算啊。别怕，我们是搞计算机的，拿出点和MO不一样的东
西来。于是，Mathematica 5.0隆重登场，我做数学作业全靠它。它将帮我们化简这个复杂的式子。<br><img alt="" src="http://www.matrix67.com/blogimage/200704132.gif" border="0"><br><br>&nbsp;&nbsp;&nbsp;&nbsp;我
们遗憾地发现，虽然常数因子很小（只有0.1），但算法的平均复杂度仍然是平方的。等一下，1/10的那个10是我们桶的个数吗？那么我们为什么不把桶的
个数弄大点？我们干脆用m来表示桶的个数，重新计算一次：<br><img alt="" src="http://www.matrix67.com/blogimage/200704133.gif" border="0"><br><br>&nbsp;&nbsp;&nbsp;&nbsp;化
简出来，操作次数为O(n+n^2/m)。发现了么，如果m=&#920;(n)的话，平均复杂度就变成了O(n)。也就是说，当桶的个数等于输入数据的个数时，算
法是平均线性的。<br>&nbsp;&nbsp;&nbsp;&nbsp;我们将在Hash表开散列的介绍中重新提到这个结论。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;且慢，还有一个问题。10个桶以十分位的
数字归类，那么n个桶用什么方法来分类呢？注意，分类的方法需要满足，一，一个数分到每个桶里的概率相同（这样才有我们上面的结论）；二，所有桶里容纳元
素的范围必须是连续的。根据这两个条件，我们有办法把所有数恰好分为n类。我们的输入数据不是都在0到1之间么？只需要看这些数乘以n的整数部分是多少就
行了，读到一个数后乘以n取整得几就插入到几号桶里。这本质上相当于把区间[0,1)平均分成n份。<br><br><br><strong> 问题七：有
没有复杂度低于线性的排序算法</strong> <br>STOP! You should
think for a while.<br><br>&nbsp;&nbsp;&nbsp;&nbsp;我们从O(n^2)走向O(nlogn)，又从O(nlogn)走向线性，
每一次我们都讨论了复杂度下限的问题，根据讨论的结果提出了更优的算法。这次总算不行了，不可能有比线性还快的算法了，因为——你读入、输出数据至少就需
要线性的时间。排序算法之旅在线性时间复杂度这一站终止了，所有十种排序算法到这里介绍完毕了。<br><br><br><br>&nbsp;&nbsp;&nbsp;&nbsp;文章有越写越长
的趋势了，我检查起来也越来越累了。我又看了三遍，应该没问题了。群众的眼睛是雪亮的，恳请大家帮我找错。<br></strong></p>
<strong style="color: #0000ff;">
</strong><br> <img src ="http://www.cppblog.com/yuqilin1228/aggbug/110617.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yuqilin1228/" target="_blank">LynnRaymond</a> 2010-03-26 21:54 <a href="http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110617.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载】位运算简介及实用技巧</title><link>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110616.html</link><dc:creator>LynnRaymond</dc:creator><author>LynnRaymond</author><pubDate>Fri, 26 Mar 2010 13:48:00 GMT</pubDate><guid>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110616.html</guid><wfw:comment>http://www.cppblog.com/yuqilin1228/comments/110616.html</wfw:comment><comments>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110616.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/yuqilin1228/comments/commentRss/110616.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yuqilin1228/services/trackbacks/110616.html</trackback:ping><description><![CDATA[<a title="Permanent link to 位运算简介及实用技巧（一）：基础篇" href="http://www.matrix67.com/blog/archives/263" rel="bookmark">位
运算简介及实用技巧（一）：基础篇</a>&nbsp;&nbsp; FROM:Matrix67大牛
<br>去年年底写的关于<a href="http://www.matrix67.com/blog/article.asp?id=153" target="_blank">位运算</a>的日志是这个Blog里少数大受欢迎的文章之一，很多人都希望我能不断完善那篇文章。后来我看到了不少其
它的资料，学习到了更多关于位运算的知识，有了重新整理位运算技巧的想法。从今天起我就开始写这一系列位运算讲解文章，与其说是原来那篇文章的
follow-up，不如说是一个remake。当然首先我还是从最基础的东西说起。<br><br>什么是位运算？<br>&nbsp;&nbsp;&nbsp;&nbsp;程序中的所有数在计
算机内存中都是以二进制的形式储存的。位运算说穿了，就是直接对整数在内存中的二进制位进行操作。比如，and运算本来是一个逻辑运算符，但整数与整数之
间也可以进行and运算。举个例子，6的二进制是110，11的二进制是1011，那么6 and
11的结果就是2，它是二进制对应位进行逻辑运算的结果（0表示False，1表示True，空位都当0处理）：<br>&nbsp;&nbsp;&nbsp;&nbsp; 110<br>AND
1011<br>----------<br>&nbsp;&nbsp;&nbsp;&nbsp;0010&nbsp;&nbsp;--&gt;&nbsp;&nbsp;2<br>&nbsp;&nbsp;&nbsp;&nbsp;由于位运算直接对内存数据进行操作，不需要转成
十进制，因此处理速度非常快。当然有人会说，这个快了有什么用，计算6 and
11没有什么实际意义啊。这一系列的文章就将告诉你，位运算到底可以干什么，有些什么经典应用，以及如何用位运算优化你的程序。<br><br><br>Pascal
和C中的位运算符号<br>&nbsp;&nbsp;&nbsp;&nbsp;下面的a和b都是整数类型，则：<br>C语言&nbsp;&nbsp;|&nbsp;&nbsp;Pascal语言<br>-------+-------------<br>a
&amp; b&nbsp;&nbsp;|&nbsp;&nbsp;a and b<br>a | b&nbsp;&nbsp;|&nbsp;&nbsp;a or b<br>a ^ b&nbsp;&nbsp;|&nbsp;&nbsp;a xor b<br>&nbsp;&nbsp;~a&nbsp;&nbsp;
|&nbsp;&nbsp; not a<br>a &lt;&lt; b |&nbsp;&nbsp;a shl b<br>a &gt;&gt; b |&nbsp;&nbsp;a shr b<br>&nbsp;&nbsp;&nbsp;&nbsp;注
意C中的逻辑运算和位运算符号是不同的。520|1314=1834，但520||1314=1，因为逻辑运算时520和1314都相当于True。同样
的，!a和~a也是有区别的。<br><br><br>各种位运算的使用<br>&nbsp;&nbsp;&nbsp;&nbsp;=== 1. and运算 ===<br>&nbsp;&nbsp;&nbsp;&nbsp;and运算通
常用于二进制取位操作，例如一个数 and
1的结果就是取二进制的最末位。这可以用来判断一个整数的奇偶，二进制的最末位为0表示该数为偶数，最末位为1表示该数为奇数.<br><br>&nbsp;&nbsp;&nbsp;&nbsp;===
2. or运算 ===<br>&nbsp;&nbsp;&nbsp;&nbsp;or运算通常用于二进制特定位上的无条件赋值，例如一个数or
1的结果就是把二进制最末位强行变成1。如果需要把二进制最末位变成0，对这个数or
1之后再减一就可以了，其实际意义就是把这个数强行变成最接近的偶数。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;=== 3. xor运算 ===<br>&nbsp;&nbsp;&nbsp;&nbsp;xor
运算通常用于对二进制的特定一位进行取反操作，因为异或可以这样定义：0和1异或0都不变，异或1则取反。<br>&nbsp;&nbsp;&nbsp;&nbsp;xor运算的逆运算是它本身，
也就是说两次异或同一个数最后结果不变，即(a xor b) xor b =
a。xor运算可以用于简单的加密，比如我想对我MM说1314520，但怕别人知道，于是双方约定拿我的生日19880516作为密钥。1314520
xor 19880516 = 20665500，我就把20665500告诉MM。MM再次计算20665500 xor
19880516的值，得到1314520，于是她就明白了我的企图。<br>&nbsp;&nbsp;&nbsp;&nbsp;下面我们看另外一个东西。定义两个符号#和@（我怎么找不到那个圈
里有个叉的字符），这两个符号互为逆运算，也就是说(x # y) @ y = x。现在依次执行下面三条命令，结果是什么？<br><code>x
&lt;- x # y<br>y &lt;- x @ y<br>x &lt;- x @ y</code><br>&nbsp;&nbsp;&nbsp;&nbsp;执行了第一句后x变成了x
# y。那么第二句实质就是y &lt;- x # y @ y，由于#和@互为逆运算，那么此时的y变成了原来的x。第三句中x实际上被赋值为(x #
y) @ x，如果#运算具有交换律，那么赋值后x就变成最初的y了。这三句话的结果是，x和y的位置互换了。<br>&nbsp;&nbsp;&nbsp;&nbsp;加法和减法互为逆运算，
并且加法满足交换律。把#换成+，把@换成-，我们可以写出一个不需要临时变量的swap过程(Pascal)。<br><code>procedure
swap(var a,b:longint);<br>begin<br>&nbsp;&nbsp; a:=a + b;<br>&nbsp;&nbsp; b:=a - b;<br>&nbsp;&nbsp;
a:=a - b;<br>end;</code><br>&nbsp;&nbsp;&nbsp;&nbsp;好了，刚才不是说xor的逆运算是它本身吗？于是我们就有了一个看起来非常诡异的
swap过程：<br><code>procedure swap(var a,b:longint);<br>begin<br>&nbsp;&nbsp; a:=a
xor b;<br>&nbsp;&nbsp; b:=a xor b;<br>&nbsp;&nbsp; a:=a xor b;<br>end;</code><br><br>&nbsp;&nbsp;&nbsp;&nbsp;===
4. not运算 ===<br>&nbsp;&nbsp;&nbsp;&nbsp;not运算的定义是把内存中的0和1全部取反。使用not运算时要格外小心，你需要注意整数类型有没有符号。
如果not的对象是无符号整数（不能表示负数），那么得到的值就是它与该类型上界的差，因为无符号类型的数是用$0000到$FFFF依次表示的。下面的
两个程序（仅语言不同）均返回65435。<br><code>var<br>&nbsp;&nbsp; a:word;<br>begin<br>&nbsp;&nbsp; a:=100;<br>&nbsp;&nbsp;
a:=not a;<br>&nbsp;&nbsp; writeln(a);<br>end.</code><br><code>#include
&lt;stdio.h&gt;<br>int main()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;unsigned short a=100;<br>&nbsp;&nbsp;&nbsp;&nbsp;a
= ~a;<br>&nbsp;&nbsp;&nbsp;&nbsp;printf( "%d\n", a );&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;return 0;<br>}</code><br>&nbsp;&nbsp;&nbsp;&nbsp;如
果not的对象是有符号的整数，情况就不一样了，稍后我们会在&#8220;整数类型的储存&#8221;小节中提到。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;=== 5. shl运算 ===<br>&nbsp;&nbsp;&nbsp;&nbsp;a
shl
b就表示把a转为二进制后左移b位（在后面添b个0）。例如100的二进制为1100100，而110010000转成十进制是400，那么100
shl 2 = 400。可以看出，a shl b的值实际上就是a乘以2的b次方，因为在二进制数后添一个0就相当于该数乘以2。<br>&nbsp;&nbsp;&nbsp;&nbsp;通常
认为a shl 1比a * 2更快，因为前者是更底层一些的操作。因此程序中乘以2的操作请尽量用左移一位来代替。<br>&nbsp;&nbsp;&nbsp;&nbsp;定义一些常量可能会
用到shl运算。你可以方便地用1 shl 16 -
1来表示65535。很多算法和数据结构要求数据规模必须是2的幂，此时可以用shl来定义Max_N等常量。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;=== 6.
shr运算 ===<br>&nbsp;&nbsp;&nbsp;&nbsp;和shl相似，a shr
b表示二进制右移b位（去掉末b位），相当于a除以2的b次方（取整）。我们也经常用shr 1来代替div
2，比如二分查找、堆的插入操作等等。想办法用shr代替除法运算可以使程序效率大大提高。最大公约数的二进制算法用除以2操作来代替慢得出奇的mod运
算，效率可以提高60%。<br><br><br>位运算的简单应用<br>&nbsp;&nbsp;&nbsp;&nbsp;有时我们的程序需要一个规模不大的Hash表来记录状态。比如，做数
独时我们需要27个Hash表来统计每一行、每一列和每一个小九宫格里已经有哪些数了。此时，我们可以用27个小于2^9的整数进行记录。例如，一个只填
了2和5的小九宫格就用数字18表示（二进制为000010010），而某一行的状态为511则表示这一行已经填满。需要改变状态时我们不需要把这个数转
成二进制修改后再转回去，而是直接进行位操作。在搜索时，把状态表示成整数可以更好地进行判重等操作。<a href="http://www.vijos.cn/Problem_Show.asp?id=1197" target="_blank">这道题</a>是在搜索中使用位运算加速的经典例子。以后我们会看到更多的例子。<br>&nbsp;&nbsp;&nbsp;&nbsp;下面列举了一些常见的
二进制位的变换操作。<br><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;&nbsp;&nbsp; 示例&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;位运算<br>----------------------+---------------------------+--------------------<br>去
掉最后一位&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| (101101-&gt;10110)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | x shr 1<br>在最后加一个
0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | (101101-&gt;1011010)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | x shl 1<br>在最后加一个1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |
(101101-&gt;1011011)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | x shl 1+1<br>把最后一位变成1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |
(101100-&gt;101101)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| x or 1<br>把最后一位变成0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |
(101101-&gt;101100)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| x or 1-1<br>最后一位取反&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|
(101101-&gt;101100)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| x xor 1<br>把右数第k位变成1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|
(101001-&gt;101101,k=3)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| x or (1 shl (k-1))<br>把右数第k位变成0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|
(101101-&gt;101001,k=3)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| x and not (1 shl (k-1))<br>右数第k位取
反&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | (101001-&gt;101101,k=3)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| x xor (1 shl (k-1))<br>取末三
位&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| (1101101-&gt;101)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| x and 7<br>取末k
位&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | (1101101-&gt;1101,k=5)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | x and (1 shl k-1)<br>取右
数第k位&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | (1101101-&gt;1,k=4)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| x shr (k-1) and 1<br>把末k
位变成1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| (101001-&gt;101111,k=4)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| x or (1 shl k-1)<br>末k位取
反&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | (101001-&gt;100110,k=4)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| x xor (1 shl k-1)<br>把右边连
续的1变成0&nbsp;&nbsp;&nbsp;&nbsp;| (100101111-&gt;100100000)&nbsp;&nbsp;&nbsp;&nbsp;| x and (x+1)<br>把右起第一个0变成
1&nbsp;&nbsp;&nbsp;&nbsp;| (100101111-&gt;100111111)&nbsp;&nbsp;&nbsp;&nbsp;| x or (x+1)<br>把右边连续的0变成1&nbsp;&nbsp;&nbsp;&nbsp;|
(11011000-&gt;11011111)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| x or (x-1)<br>取右边连续的1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |
(100101111-&gt;1111)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | (x xor (x+1)) shr 1<br>去掉右起第一个1的左边 |
(100101000-&gt;1000)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | x and (x xor (x-1))<br><br>&nbsp;&nbsp;&nbsp;&nbsp;最后这一个在树状数组
中会用到。<br><br><br>Pascal和C中的16进制表示<br>&nbsp;&nbsp;&nbsp;&nbsp;Pascal中需要在16进制数前加$符号表示，C中需要在前面加
0x来表示。这个以后我们会经常用到。<br><br>整数类型的储存<br>&nbsp;&nbsp;&nbsp;&nbsp;我们前面所说的位运算都没有涉及负数，都假设这些运算是在
unsigned/word类型（只能表示正数的整型）上进行操作。但计算机如何处理有正负符号的整数类型呢？下面两个程序都是考察16位整数的储存方式
（只是语言不同）。<br><code>var<br>&nbsp;&nbsp; a,b:integer;<br>begin<br>&nbsp;&nbsp; a:=$0000;<br>&nbsp;&nbsp;
b:=$0001;<br>&nbsp;&nbsp; write(a,' ',b,' ');<br>&nbsp;&nbsp; a:=$FFFE;<br>&nbsp;&nbsp; b:=$FFFF;<br>&nbsp;&nbsp;
write(a,' ',b,' ');<br>&nbsp;&nbsp; a:=$7FFF;<br>&nbsp;&nbsp; b:=$8000;<br>&nbsp;&nbsp; writeln(a,'
',b);<br>end.</code><br><code>#include &lt;stdio.h&gt;<br>int main()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;short
int a, b;<br>&nbsp;&nbsp;&nbsp;&nbsp;a = 0&#215;0000;<br>&nbsp;&nbsp;&nbsp;&nbsp;b = 0&#215;0001;<br>&nbsp;&nbsp;&nbsp;&nbsp;printf( "%d %d
", a, b );<br>&nbsp;&nbsp;&nbsp;&nbsp;a = 0xFFFE;<br>&nbsp;&nbsp;&nbsp;&nbsp;b = 0xFFFF;<br>&nbsp;&nbsp;&nbsp;&nbsp;printf( "%d %d
", a, b );<br>&nbsp;&nbsp;&nbsp;&nbsp;a = 0&#215;7FFF;<br>&nbsp;&nbsp;&nbsp;&nbsp;b = 0&#215;8000;<br>&nbsp;&nbsp;&nbsp;&nbsp;printf( "%d
%d\n", a, b );<br>&nbsp;&nbsp;&nbsp;&nbsp;return 0;<br>}</code><br>&nbsp;&nbsp;&nbsp;&nbsp;两个程序的输出均为0 1 -2 -1
32767
-32768。其中前两个数是内存值最小的时候，中间两个数则是内存值最大的时候，最后输出的两个数是正数与负数的分界处。由此你可以清楚地看到计算机是
如何储存一个整数的：计算机用$0000到$7FFF依次表示0到32767的数，剩下的$8000到$FFFF依次表示-32768到-1的数。32位
有符号整数的储存方式也是类似的。稍加注意你会发现，二进制的第一位是用来表示正负号的，0表示正，1表示负。这里有一个问题：0本来既不是正数，也不是
负数，但它占用了$0000的位置，因此有符号的整数类型范围中正数个数比负数少一个。对一个有符号的数进行not运算后，最高位的变化将导致正负颠倒，
并且数的绝对值会差1。也就是说，not a实际上等于-a-1。这种整数储存方式叫做&#8220;补码&#8221;。<br><br>最后还有两句话<br>&nbsp;&nbsp;&nbsp;&nbsp;Matrix67
原创<br>&nbsp;&nbsp;&nbsp;&nbsp;转贴请注明出处<br><br><a href="http://www.matrix67.com/blog/archives/264" title="Permanent link to
位运算简介及实用技巧（二）：进阶篇(1)" rel="bookmark">位
运算简介及实用技巧（二）：进阶篇(1)</a> <br>
<p>=====&nbsp;&nbsp; 真正强的东西来了！&nbsp;&nbsp; =====<br><br>二进制中的1有奇数个还是偶数个<br>&nbsp;&nbsp;&nbsp;&nbsp;我们可以用下面的代码来计算
一个32位整数的二进制中1的个数的奇偶性，当输入数据的二进制表示里有偶数个数字1时程序输出0，有奇数个则输出1。例如，1314520的二进制
101000000111011011000中有9个1，则x=1314520时程序输出1。<br><code>var<br>&nbsp;&nbsp;
i,x,c:longint;<br>begin<br>&nbsp;&nbsp; readln(x);<br>&nbsp;&nbsp; c:=0;<br>&nbsp;&nbsp; for i:=1 to
32 do<br>&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;c:=c + x and 1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x:=x shr 1;<br>&nbsp;&nbsp;
end;<br>&nbsp;&nbsp; writeln( c and 1 );<br>end.</code><br>&nbsp;&nbsp;&nbsp;&nbsp;但这样的效率并不高，位运算的神奇之处还
没有体现出来。<br>&nbsp;&nbsp;&nbsp;&nbsp;同样是判断二进制中1的个数的奇偶性，下面这段代码就强了。你能看出这个代码的原理吗？<br><code>var<br>&nbsp;&nbsp;
x:longint;<br>begin<br>&nbsp;&nbsp; readln(x);<br>&nbsp;&nbsp; x:=x xor (x shr 1);<br>&nbsp;&nbsp;
x:=x xor (x shr 2);<br>&nbsp;&nbsp; x:=x xor (x shr 4);<br>&nbsp;&nbsp; x:=x xor (x shr 8);<br>&nbsp;&nbsp;
x:=x xor (x shr 16);<br>&nbsp;&nbsp; writeln(x and 1);<br>end.</code><br>&nbsp;&nbsp;&nbsp;&nbsp;为了说明
上面这段代码的原理，我们还是拿1314520出来说事。1314520的二进制为101000000111011011000，第一次异或操作的结果如
下：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;00000000000101000000111011011000<br>XOR&nbsp;&nbsp;0000000000010100000011101101100<br>---------------------------------------<br>&nbsp;&nbsp;&nbsp;&nbsp;00000000000111100000100110110100<br><br>&nbsp;&nbsp;&nbsp;&nbsp;得
到的结果是一个新的二进制数，其中右起第i位上的数表示原数中第i和i+1位上有奇数个1还是偶数个1。比如，最右边那个0表示原数末两位有偶数个1，右
起第3位上的1就表示原数的这个位置和前一个位置中有奇数个1。对这个数进行第二次异或的结果如下：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;00000000000111100000100110110100<br>XOR&nbsp;&nbsp;
000000000001111000001001101101<br>---------------------------------------<br>&nbsp;&nbsp;&nbsp;&nbsp;00000000000110011000101111011001<br><br>&nbsp;&nbsp;&nbsp;&nbsp;结
果里的每个1表示原数的该位置及其前面三个位置中共有奇数个1，每个0就表示原数对应的四个位置上共偶数个1。一直做到第五次异或结束后，得到的二进制数
的最末位就表示整个32位数里有多少个1，这就是我们最终想要的答案。<br><br><br>计算二进制中的1的个数<br>&nbsp;&nbsp;&nbsp;&nbsp;同样假设x是一个
32位整数。经过下面五次赋值后，x的值就是原数的二进制表示中数字1的个数。比如，初始时x为1314520（网友抓狂：能不能换一个数啊），那么最后
x就变成了9，它表示1314520的二进制中有9个1。<br><code>x := (x and $55555555) + ((x shr 1)
and $55555555); <br>x := (x and $33333333) + ((x shr 2) and $33333333);
<br>x := (x and $0F0F0F0F) + ((x shr 4) and $0F0F0F0F); <br>x := (x and
$00FF00FF) + ((x shr 8) and $00FF00FF); <br>x := (x and $0000FFFF) +
((x shr 16) and $0000FFFF); </code><br>&nbsp;&nbsp;&nbsp;&nbsp;为了便于解说，我们下面仅说明这个程序是如何对一个8位整数进
行处理的。我们拿数字211（我们班某MM的生日）来开刀。211的二进制为11010011。<br><br>+---+---+---+---+---+---+---+---+<br>|
1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 |&nbsp;&nbsp; &lt;---原数<br>+---+---+---+---+---+---+---+---+<br>|&nbsp;&nbsp;1
0&nbsp;&nbsp;|&nbsp;&nbsp;0 1&nbsp;&nbsp;|&nbsp;&nbsp;0 0&nbsp;&nbsp;|&nbsp;&nbsp;1 0&nbsp;&nbsp;|&nbsp;&nbsp; &lt;---第一次运算后<br>+-------+-------+-------+-------+<br>|&nbsp;&nbsp;&nbsp;&nbsp;0
0 1 1&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;0 0 1 0&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; &lt;---第二次运算后<br>+---------------+---------------+<br>|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0
0 0 0 0 1 0 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; &lt;---第三次运算后，得数为5<br>+-------------------------------+<br><br>&nbsp;&nbsp;&nbsp;&nbsp;整
个程序是一个分治的思想。第一次我们把每相邻的两位加起来，得到每两位里1的个数，比如前两位10就表示原数的前两位有2个1。第二次我们继续两两相
加，10+01=11，00+10=10，得到的结果是00110010，它表示原数前4位有3个1，末4位有2个1。最后一次我们把0011和0010
加起来，得到的就是整个二进制中1的个数。程序中巧妙地使用取位和右移，比如第二行中$33333333的二进制为00110011001100&#8230;.，用
它和x做and运算就相当于以2为单位间隔取数。shr的作用就是让加法运算的相同数位对齐。<br><br><br>二分查找32位整数的前导0个数<br>&nbsp;&nbsp;&nbsp;&nbsp;这
里用的C语言，我直接Copy的Hacker's
Delight上的代码。这段代码写成C要好看些，写成Pascal的话会出现很多begin和end，搞得代码很难看。程序思想是二分查找，应该很简
单，我就不细说了。<br><code>int nlz(unsigned x)<br>{<br>&nbsp;&nbsp; int n;<br><br>&nbsp;&nbsp; if (x
== 0) return(32);<br>&nbsp;&nbsp; n = 1;<br>&nbsp;&nbsp; if ((x &gt;&gt; 16) == 0) {n = n
+16; x = x &lt;&lt;16;}<br>&nbsp;&nbsp; if ((x &gt;&gt; 24) == 0) {n = n + 8; x = x
&lt;&lt; 8;}<br>&nbsp;&nbsp; if ((x &gt;&gt; 28) == 0) {n = n + 4; x = x &lt;&lt;
4;}<br>&nbsp;&nbsp; if ((x &gt;&gt; 30) == 0) {n = n + 2; x = x &lt;&lt; 2;}<br>&nbsp;&nbsp;
n = n - (x &gt;&gt; 31);<br>&nbsp;&nbsp; return n;<br>}</code><br><br><br>只用位运算来取
绝对值<br>&nbsp;&nbsp;&nbsp;&nbsp;这是一个非常有趣的问题。大家先自己想想吧，Ctrl+A显示答案。<br>&nbsp;&nbsp;&nbsp;&nbsp;答案：假设x为32位整数，则x xor
(not (x shr 31) + 1) + x shr 31的结果是x的绝对值<br>&nbsp;&nbsp;&nbsp;&nbsp;x shr
31是二进制的最高位，它用来表示x的符号。如果它为0（x为正），则not (x shr 31) +
1等于$00000000，异或任何数结果都不变；如果最高位为1（x为负），则not (x shr 31) +
1等于$FFFFFFFF，x异或它相当于所有数位取反，异或完后再加一。<br><br><br>高低位交换<br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="http://www.vijos.cn/Problem_Show.asp?id=1201" target="_blank">这个题</a>实际上是我出的，做为学校内部NOIp模拟赛的第一题。题目是这样：<br><br>
</p>
<blockquote>&nbsp;&nbsp;&nbsp;&nbsp;给出一个小于2^32的正整数。这个数可以用一个32位的二进制数表示（不足32位用0补足）。我们称这个二进
制数的前16位为&#8220;高位&#8221;，后16位为&#8220;低位&#8221;。将它的高低位交换，我们可以得到一个新的数。试问这个新的数是多少（用十进制表示）。<br>　　例如，
数1314520用二进制表示为0000 0000 0001 0100 0000 1110 1101
1000（添加了11个前导0补足为32位），其中前16位为高位，即0000 0000 0001 0100；后16位为低位，即0000 1110
1101 1000。将它的高低位进行交换，我们得到了一个新的二进制数0000 1110 1101 1000 0000 0000 0001
0100。它即是十进制的249036820。
<p>&nbsp;</p>
</blockquote>
<p><br>&nbsp;&nbsp;&nbsp;&nbsp;当时几乎没有人想到用一句位操作来代替冗长的程序。使用位运算的话两句话就完了。<br><code>var<br>&nbsp;&nbsp;
n:dword;<br>begin<br>&nbsp;&nbsp; readln( n );<br>&nbsp;&nbsp; writeln( (n shr 16) or
(n&nbsp;&nbsp;shl 16) );<br>end.</code><br>&nbsp;&nbsp;&nbsp;&nbsp;而事实上，Pascal有一个系统函数swap直接就可以用。<br><br><br>二
进制逆序<br>&nbsp;&nbsp;&nbsp;&nbsp;下面的程序读入一个32位整数并输出它的二进制倒序后所表示的数。<br>&nbsp;&nbsp;&nbsp;&nbsp;输入：
1314520&nbsp;&nbsp;&nbsp;&nbsp;（二进制为00000000000101000000111011011000）<br>&nbsp;&nbsp;&nbsp;&nbsp;输出：
460335104&nbsp;&nbsp;（二进制为00011011011100000010100000000000）<br><code>var<br>&nbsp;&nbsp;
x:dword;<br>begin<br>&nbsp;&nbsp; readln(x);<br>&nbsp;&nbsp; x := (x and $55555555) shl&nbsp;&nbsp;1
or (x and $AAAAAAAA) shr&nbsp;&nbsp;1;<br>&nbsp;&nbsp; x := (x and $33333333) shl&nbsp;&nbsp;2 or (x
and $CCCCCCCC) shr&nbsp;&nbsp;2;<br>&nbsp;&nbsp; x := (x and $0F0F0F0F) shl&nbsp;&nbsp;4 or (x and
$F0F0F0F0) shr&nbsp;&nbsp;4;<br>&nbsp;&nbsp; x := (x and $00FF00FF) shl&nbsp;&nbsp;8 or (x and
$FF00FF00) shr&nbsp;&nbsp;8;<br>&nbsp;&nbsp; x := (x and $0000FFFF) shl 16 or (x and
$FFFF0000) shr 16;<br>&nbsp;&nbsp; writeln(x);<br>end.</code><br>&nbsp;&nbsp;&nbsp;&nbsp;它的原理和刚才求二进制中1
的个数那个例题是大致相同的。程序首先交换每相邻两位上的数，以后把互相交换过的数看成一个整体，继续进行以2位为单位、以4位为单位的左右对换操作。我
们再次用8位整数211来演示程序执行过程：<br>+---+---+---+---+---+---+---+---+<br>| 1 | 1 | 0
| 1 | 0 | 0 | 1 | 1 |&nbsp;&nbsp; &lt;---原数<br>+---+---+---+---+---+---+---+---+<br>|&nbsp;&nbsp;1
1&nbsp;&nbsp;|&nbsp;&nbsp;1 0&nbsp;&nbsp;|&nbsp;&nbsp;0 0&nbsp;&nbsp;|&nbsp;&nbsp;1 1&nbsp;&nbsp;|&nbsp;&nbsp; &lt;---第一次运算后<br>+-------+-------+-------+-------+<br>|&nbsp;&nbsp;&nbsp;&nbsp;1
0 1 1&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;1 1 0 0&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; &lt;---第二次运算后<br>+---------------+---------------+<br>|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1
1 0 0 1 0 1 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; &lt;---第三次运算后<br>+-------------------------------+<br><br><br>Copyright
也很强<br><code>writeln('Matrix' , 42 XOR 105 , '原创，转贴请注明出处');</code></p>
<a title="Permanent link to 位运算简介及实用技巧（三）：进阶篇(2)" href="http://www.matrix67.com/blog/archives/266" rel="bookmark">位
运算简介及实用技巧（三）：进阶篇(2)</a> <br>
<p>今天我们来看两个稍微复杂一点的例子。<br><br>n皇后问题位运算版<br>&nbsp;&nbsp;&nbsp;&nbsp;n皇后问题是啥我就不说了吧，学编程的肯定都见过。下面
的十多行代码是n皇后问题的一个高效位运算程序，看到过的人都夸它牛。初始时，upperlim:=(1 shl
n)-1。主程序调用test(0,0,0)后sum的值就是n皇后总的解数。拿这个去交USACO，0.3s，暴爽。<br><code>procedure
test(row,ld,rd:longint);<br>var<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pos,p:longint;<br>begin<br><br>{
1}&nbsp;&nbsp;if row&lt;&gt;upperlim then<br>{ 2}&nbsp;&nbsp;begin<br>{ 3}&nbsp;&nbsp;&nbsp;&nbsp;
pos:=upperlim and not (row or ld or rd);<br>{ 4}&nbsp;&nbsp;&nbsp;&nbsp; while pos&lt;&gt;0
do<br>{ 5}&nbsp;&nbsp;&nbsp;&nbsp; begin<br>{ 6}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;p:=pos and -pos;<br>{
7}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pos:=pos-p;<br>{ 8}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;test(row+p,(ld+p)shl 1,(rd+p)shr
1);<br>{ 9}&nbsp;&nbsp;&nbsp;&nbsp; end;<br>{10}&nbsp;&nbsp;end<br>{11}&nbsp;&nbsp;else inc(sum);<br><br>end;</code><br>&nbsp;&nbsp;&nbsp;&nbsp;乍
一看似乎完全摸不着头脑，实际上整个程序是非常容易理解的。这里还是建议大家自己单步运行一探究竟，实在没研究出来再看下面的解说。<br><br><img alt="" src="http://www.matrix67.com/blogimage/200707261.gif" border="0">&nbsp;&nbsp;<img alt="" src="http://www.matrix67.com/blogimage/200707262.gif" border="0"><br>&nbsp;&nbsp;&nbsp;&nbsp;和
普通算法一样，这是一个递归过程，程序一行一行地寻找可以放皇后的地方。过程带三个参数，row、ld和rd，分别表示在纵列和两个对角线方向的限制条件
下这一行的哪些地方不能放。我们以6&#215;6的棋盘为例，看看程序是怎么工作的。假设现在已经递归到第四层，前三层放的子已经标在左图上了。红色、蓝色和绿色
的线分别表示三个方向上有冲突的位置，位于该行上的冲突位置就用row、ld和rd中的1来表示。把它们三个并起来，得到该行所有的禁位，取反后就得到所
有可以放的位置（用pos来表示）。前面说过-a相当于not a + 1，这里的代码第6行就相当于pos and (not pos +
1)，其结果是取出最右边的那个1。这样，p就表示该行的某个可以放子的位置，把它从pos中移除并递归调用test过程。注意递归调用时三个参数的变
化，每个参数都加上了一个禁位，但两个对角线方向的禁位对下一行的影响需要平移一位。最后，如果递归到某个时候发现row=111111了，说明六个皇后
全放进去了，此时程序从第1行跳到第11行，找到的解的个数加一。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;~~~~====~~~~=====&nbsp;&nbsp; 华丽的分割线&nbsp;&nbsp;
=====~~~~====~~~~<br><br>Gray码<br>&nbsp;&nbsp;&nbsp;&nbsp;假如我有4个潜在的GF，我需要决定最终到底和谁在一起。一个简单的办法
就是，依次和每个MM交往一段时间，最后选择给我带来的&#8220;满意度&#8221;最大的MM。但看了<a href="http://tianyi.yo2.cn/go/89403.html" target="_blank">dd牛的理论</a>后，
事情开始变得复杂了：我可以选择和多个MM在一起。这样，需要考核的状态变成了2^4=16种（当然包括0000这一状态，因为我有可能是玻璃）。现在的
问题就是，我应该用什么顺序来遍历这16种状态呢？<br>&nbsp;&nbsp;&nbsp;&nbsp;传统的做法是，用二进制数的顺序来遍历所有可能的组合。也就是说，我需要以
0000-&gt;0001-&gt;0010-&gt;0011-&gt;0100-&gt;&#8230;-&gt;1111这样的顺序对每种状态进行测试。这个
顺序很不科学，很多时候状态的转移都很耗时。比如从0111到1000时我需要暂时甩掉当前所有的3个MM，然后去把第4个MM。同时改变所有MM与我的
关系是一件何等巨大的工程啊。因此，我希望知道，是否有一种方法可以使得，从没有MM这一状态出发，每次只改变我和一个MM的关系（追或者甩），15次操
作后恰好遍历完所有可能的组合（最终状态不一定是1111）。大家自己先试一试看行不行。<br>&nbsp;&nbsp;&nbsp;&nbsp;解决这个问题的方法很巧妙。我们来说明，假如我
们已经知道了n=2时的合法遍历顺序，我们如何得到n=3的遍历顺序。显然，n=2的遍历顺序如下：<br><br>00<br>01<br>11<br>10<br><br>&nbsp;&nbsp;&nbsp;&nbsp;你
可能已经想到了如何把上面的遍历顺序扩展到n=3的情况。n=3时一共有8种状态，其中前面4个把n=2的遍历顺序照搬下来，然后把它们对称翻折下去并在
最前面加上1作为后面4个状态：<br><br>000<br>001<br>011<br>010&nbsp;&nbsp;&#8593;<br>--------<br>110&nbsp;&nbsp;&#8595;<br>111<br>101<br>100<br><br>&nbsp;&nbsp;&nbsp;&nbsp;用
这种方法得到的遍历顺序显然符合要求。首先，上面8个状态恰好是n=3时的所有8种组合，因为它们是在n=2的全部四种组合的基础上考虑选不选第3个元素
所得到的。然后我们看到，后面一半的状态应该和前面一半一样满足&#8220;相邻状态间仅一位不同&#8221;的限制，而&#8220;镜面&#8221;处则是最前面那一位数不同。再次翻折三阶遍历
顺序，我们就得到了刚才的问题的答案：<br><br>0000<br>0001<br>0011<br>0010<br>0110<br>0111<br>0101<br>0100<br>1100<br>1101<br>1111<br>1110<br>1010<br>1011<br>1001<br>1000<br><br>&nbsp;&nbsp;&nbsp;&nbsp;这
种遍历顺序作为一种编码方式存在，叫做Gray码（写个中文让蜘蛛来抓：格雷码）。它的应用范围很广。比如，n阶的Gray码相当于在n维立方体上的
Hamilton回路，因为沿着立方体上的边走一步，n维坐标中只会有一个值改变。再比如，Gray码和Hanoi塔问题等价。Gray码改变的是第几个
数，Hanoi塔就该移动哪个盘子。比如，3阶的Gray码每次改变的元素所在位置依次为1-2-1-3-1-2-1，这正好是3阶Hanoi塔每次移动
盘子编号。如果我们可以快速求出Gray码的第n个数是多少，我们就可以输出任意步数后Hanoi塔的移动步骤。现在我告诉你，Gray码的第n个数（从
0算起）是n xor (n shr 1)，你能想出来这是为什么吗？先自己想想吧。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;下面我们把二进制数和Gray码都写在下
面，可以看到左边的数异或自身右移的结果就等于右边的数。<br><br>二进制数&nbsp;&nbsp; Gray码<br>&nbsp;&nbsp; 000&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 000<br>&nbsp;&nbsp;
001&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 001<br>&nbsp;&nbsp; 010&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 011<br>&nbsp;&nbsp; 011&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 010<br>&nbsp;&nbsp; 100&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
110<br>&nbsp;&nbsp; 101&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 111<br>&nbsp;&nbsp; 110&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 101<br>&nbsp;&nbsp; 111&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 100<br><br>&nbsp;&nbsp;&nbsp;&nbsp;从
二进制数的角度看，&#8220;镜像&#8221;位置上的数即是对原数进行not运算后的结果。比如，第3个数010和倒数第3个数101的每一位都正好相反。假设这两个数分
别为x和y，那么x xor (x shr 1)和y xor (y shr
1)的结果只有一点不同：后者的首位是1，前者的首位是0。而这正好是Gray码的生成方法。这就说明了，Gray码的第n个数确实是n xor (n
shr 1)。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;今年四月份<a href="http://failedshuo.spaces.live.com/" target="_blank">mashuo</a>给
我看了<a href="http://acm.sgu.ru/problem.php?contest=0&amp;problem=249" target="_blank">这道题</a>，是二维意义上的Gray码。题目大意是说，把0到2^(n+m)-1的数写成2^n *
2^m的矩阵，使得位置相邻两数的二进制表示只有一位之差。答案其实很简单，所有数都是由m位的Gray码和n位Gray码拼接而成，需要用左移操作和
or运算完成。完整的代码如下：<br><code>var<br>&nbsp;&nbsp; x,y,m,n,u:longint;<br>begin<br>&nbsp;&nbsp;
readln(m,n);<br>&nbsp;&nbsp; for x:=0 to 1 shl m-1 do begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;u:=(x xor (x
shr 1)) shl n; //输出数的左边是一个m位的Gray码<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for y:=0 to 1 shl n-1 do<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
write(u or (y xor (y shr 1)),' '); //并上一个n位Gray码<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;writeln;<br>&nbsp;&nbsp;
end;<br>end.</code><br><br>Matrix67原创<br>转贴请注明出处</p>
<a title="Permanent link to 位运算简介及实用技巧（四）：实战篇" href="http://www.matrix67.com/blog/archives/268" rel="bookmark"><strong> 位运算简介及实用技巧（四）：实战篇</strong> </a><br>下面
分享的是我自己写的三个代码，里面有些题目也是我自己出的。这些代码都是在我的Pascal时代写的，恕不提供C语言了。代码写得并不好，我只是想告诉大
家位运算在实战中的应用，包括了搜索和状态压缩DP方面的题目。其实大家可以在网上找到更多用位运算优化的题目，这里整理出一些自己写的代码，只是为了原
创系列文章的完整性。这一系列文章到这里就结束了，希望大家能有所收获。<br>&nbsp;&nbsp;&nbsp;&nbsp;Matrix67原创，转贴请注明出处。<br><br><br>
<blockquote>Problem : 费解的开关<br><br><a href="http://www.vijos.cn/Problem_Show.asp?id=1197" target="_blank"><strong> <font color="#618898">题目来源</font></strong> </a><br>&nbsp;&nbsp;&nbsp;&nbsp;06
年NOIp模拟赛（一） by Matrix67 第四题<br><br>问题描述<br>&nbsp;&nbsp;&nbsp;&nbsp;你玩过&#8220;拉灯&#8221;游戏吗？25盏灯排成一个5&#215;5的方
形。每一个灯都有一个开关，游戏者可以改变它的状态。每一步，游戏者可以改变某一个灯的状态。游戏者改变一个灯的状态会产生连锁反应：和这个灯上下左右相
邻的灯也要相应地改变其状态。<br>&nbsp;&nbsp;&nbsp;&nbsp;我们用数字&#8220;1&#8221;表示一盏开着的灯，用数字&#8220;0&#8221;表示关着的灯。下面这种状态<br><br>10111<br>01101<br>10111<br>10000<br>11011<br><br>&nbsp;&nbsp;&nbsp;&nbsp;在
改变了最左上角的灯的状态后将变成：<br><br>01111<br>11101<br>10111<br>10000<br>11011<br><br>&nbsp;&nbsp;&nbsp;&nbsp;再
改变它正中间的灯后状态将变成：<br><br>01111<br>11001<br>11001<br>10100<br>11011<br><br>&nbsp;&nbsp;&nbsp;&nbsp;给
定一些游戏的初始状态，编写程序判断游戏者是否可能在6步以内使所有的灯都变亮。<br><br>输入格式<br>&nbsp;&nbsp;&nbsp;&nbsp;第一行有一个正整数n，代表数
据中共有n个待解决的游戏初始状态。<br>&nbsp;&nbsp;&nbsp;&nbsp;以下若干行数据分为n组，每组数据有5行，每行5个字符。每组数据描述了一个游戏的初始状态。各组数
据间用一个空行分隔。<br>&nbsp;&nbsp;&nbsp;&nbsp;对于30%的数据，n&lt;=5；<br>&nbsp;&nbsp;&nbsp;&nbsp;对于100%的数据，n&lt;=500。<br><br>输
出格式<br>&nbsp;&nbsp;&nbsp;&nbsp;输出数据一共有n行，每行有一个小于等于6的整数，它表示对于输入数据中对应的游戏状态最少需要几步才能使所有灯变亮。<br>&nbsp;&nbsp;&nbsp;&nbsp;对
于某一个游戏初始状态，若6步以内无法使所有灯变亮，请输出&#8220;-1&#8221;。<br><br>样例输入<br>3<br>00111<br>01011<br>10001<br>11010<br>11100<br><br>11101<br>11101<br>11110<br>11111<br>11111<br><br>01111<br>11111<br>11111<br>11111<br>11111<br><br>样
例输出<br>3<br>2<br>-1
<p>&nbsp;</p>
</blockquote>
<p><br><br>程序代码<br><code>const<br>&nbsp;&nbsp; BigPrime=3214567;<br>&nbsp;&nbsp; MaxStep=6;<br>type<br>&nbsp;&nbsp;
pointer=^rec;<br>&nbsp;&nbsp; rec=record<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; v:longint;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
step:integer;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; next:pointer;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end;<br><br>var<br>&nbsp;&nbsp;
total:longint;<br>&nbsp;&nbsp; hash:array[0..BigPrime-1]of pointer;<br>&nbsp;&nbsp;
q:array[1..400000]of rec;<br><br>function
update(a:longint;p:integer):longint;<br>begin<br>&nbsp;&nbsp; a:=a xor (1 shl p);<br>&nbsp;&nbsp;
if p mod 5&lt;&gt;0 then a:=a xor (1 shl (p-1));<br>&nbsp;&nbsp; if (p+1) mod
5&lt;&gt;0 then a:=a xor (1 shl (p+1));<br>&nbsp;&nbsp; if p&lt;20 then a:=a xor
(1 shl (p+5));<br>&nbsp;&nbsp; if p&gt;4 then a:=a xor (1 shl (p-5));<br>&nbsp;&nbsp;
exit(a);<br>end;<br><br>function find(a:longint;step:integer):boolean;<br>var<br>&nbsp;&nbsp;
now:pointer;<br>begin<br>&nbsp;&nbsp; now:=hash[a mod BigPrime];<br>&nbsp;&nbsp; while
now&lt;&gt;nil do<br>&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if now^.v=a then exit(true);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;now:=now^.next;<br>&nbsp;&nbsp;
end;<br><br>&nbsp;&nbsp; new(now);<br>&nbsp;&nbsp; now^.v:=a;<br>&nbsp;&nbsp; now^.step:=step;<br>&nbsp;&nbsp;
now^.next:=hash[a mod BigPrime];<br>&nbsp;&nbsp; hash[a mod BigPrime]:=now;<br>&nbsp;&nbsp;
total:=total+1;<br>&nbsp;&nbsp; exit(false);<br>end;<br><br>procedure solve;<br>var<br>&nbsp;&nbsp;
p:integer;<br>&nbsp;&nbsp; close:longint=0;<br>&nbsp;&nbsp; open:longint=1;<br>begin<br>&nbsp;&nbsp;
find(1 shl 25-1,0);<br>&nbsp;&nbsp; q[1].v:=1 shl 25-1;<br>&nbsp;&nbsp; q[1].step:=0;<br>&nbsp;&nbsp;
repeat<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;inc(close);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for p:=0 to 24 do<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if
not find(update(q[close].v,p),q[close].step+1) and
(q[close].step+1&lt;MaxStep) then<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;open:=open+1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;q[open].v:=update(q[close].v,p);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;q[open].step:=q[close].step+1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
end;<br>&nbsp;&nbsp; until close&gt;=open;<br>end;<br><br>procedure
print(a:longint);<br>var<br>&nbsp;&nbsp; now:pointer;<br>begin<br>&nbsp;&nbsp; now:=hash[a
mod BigPrime];<br>&nbsp;&nbsp; while now&lt;&gt;nil do<br>&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if
now^.v=a then<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; writeln(now^.step);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
exit;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;now:=now^.next;<br>&nbsp;&nbsp; end;<br>&nbsp;&nbsp;
writeln(-1);<br>end;<br><br>procedure main;<br>var<br>&nbsp;&nbsp; ch:char;<br>&nbsp;&nbsp;
i,j,n:integer;<br>&nbsp;&nbsp; t:longint;<br>begin<br>&nbsp;&nbsp; readln(n);<br>&nbsp;&nbsp; for i:=1
to n do<br>&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;t:=0;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for j:=1 to 25 do<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
read(ch);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t:=t*2+ord(ch)-48;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if j mod 5=0 then
readln;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print(t);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if i&lt;n then readln;<br>&nbsp;&nbsp;
end;<br>end;<br><br>begin<br>&nbsp;&nbsp; solve;<br>&nbsp;&nbsp; main;<br>end.</code><br><br><strong> =======================&nbsp;&nbsp;性
感的分割线&nbsp;&nbsp;=======================</strong> <br><br><br>
</p>
<blockquote>Problem : garden / 和MM逛花园<br><br>题目来源<br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="http://www.matrix67.com/blog/article.asp?id=241" target="_blank"><strong> <font color="#618898">07年Matrix67生日邀请赛</font></strong> </a>第
四题<br><br>问题描述<br>&nbsp;&nbsp;&nbsp;&nbsp;花园设计强调，简单就是美。Matrix67常去的花园有着非常简单的布局：花园的所有景点的位置都是&#8220;对
齐&#8221;了的，这些景点可以看作是平面坐标上的格点。相邻的景点之间有小路相连，这些小路全部平行于坐标轴。景点和小路组成了一个&#8220;不完整的网格&#8221;。<br>&nbsp;&nbsp;&nbsp;&nbsp;一
个典型的花园布局如左图所示。花园布局在6行4列的网格上，花园的16个景点的位置用红色标注在了图中。黑色线条表示景点间的小路，其余灰色部分实际并不
存在。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img alt="" src="http://www.matrix67.com/data/prob4.gif" border="0"><br><br>&nbsp;&nbsp;&nbsp;&nbsp;Matrix67
的生日那天，他要带着他的MM在花园里游玩。Matrix67不会带MM两次经过同一个景点，因此每个景点最多被游览一次。他和他的MM边走边聊，他们是
如此的投入以致于他们从不会&#8220;主动地拐弯&#8221;。也就是说，除非前方已没有景点或是前方的景点已经访问过，否则他们会一直往前走下去。当前方景点不存在或已游
览过时，Matrix67会带MM另选一个方向继续前进。由于景点个数有限，访问过的景点将越来越多，迟早会出现不能再走的情况（即四个方向上的相邻景点
都访问过了），此时他们将结束花园的游览。Matrix67希望知道以这种方式游览花园是否有可能遍历所有的景点。Matrix67可以选择从任意一个景
点开始游览，以任意一个景点结束。<br>&nbsp;&nbsp;&nbsp;&nbsp;在上图所示的花园布局中，一种可能的游览方式如右图所示。这种浏览方式从(1,2)出发，以(2,4)
结束，经过每个景点恰好一次。<br><br>输入格式<br>&nbsp;&nbsp;&nbsp;&nbsp;第一行输入两个用空格隔开的正整数m和n，表示花园被布局在m行n列的网格上。<br>&nbsp;&nbsp;&nbsp;&nbsp;以
下m行每行n个字符，字符&#8220;0&#8221;表示该位置没有景点，字符&#8220;1&#8221;表示对应位置有景点。这些数字之间没有空格。<br><br>输出格式<br>&nbsp;&nbsp;&nbsp;&nbsp;你
的程序需要寻找满足&#8220;不主动拐弯&#8221;性质且遍历所有景点的游览路线。<br>&nbsp;&nbsp;&nbsp;&nbsp;如果没有这样的游览路线，请输出一行&#8220;Impossible&#8221;（不带引
号，注意大小写）。<br>&nbsp;&nbsp;&nbsp;&nbsp;如果存在游览路线，请依次输出你的方案中访问的景点的坐标，每行输出一个。坐标的表示格式为&#8220;(x,y)&#8221;，代表第x
行第y列。<br>&nbsp;&nbsp;&nbsp;&nbsp;如果有多种方案，你只需要输出其中一种即可。评测系统可以判断你的方案的正确性。<br><br>样例输入<br>6 4<br>1100<br>1001<br>1111<br>1100<br>1110<br>1110<br><br>样
例输出<br>(1,2)<br>(1,1)<br>(2,1)<br>(3,1)<br>(4,1)<br>(5,1)<br>(6,1)<br>(6,2)<br>(6,3)<br>(5,3)<br>(5,2)<br>(4,2)<br>(3,2)<br>(3,3)<br>(3,4)<br>(2,4)<br><br>数
据规模<br>&nbsp;&nbsp;&nbsp;&nbsp;对于30%的数据，n,m&lt;=5；<br>&nbsp;&nbsp;&nbsp;&nbsp;对于100%的数据，n,m&lt;=10。
<p>&nbsp;</p>
</blockquote>
<p><br><br>程序代码：<br><code>program garden;<br><br>const<br>&nbsp;&nbsp;
dir:array[1..4,1..2]of integer=<br>&nbsp;&nbsp;&nbsp;&nbsp; ((1,0),(0,1),(-1,0),(0,-1));<br><br>type<br>&nbsp;&nbsp;
arr=array[1..10]of integer;<br>&nbsp;&nbsp; rec=record x,y:integer;end;<br><br>var<br>&nbsp;&nbsp;
map:array[0..11,0..11]of boolean;<br>&nbsp;&nbsp; ans:array[1..100]of rec;<br>&nbsp;&nbsp;
n,m,max:integer;<br>&nbsp;&nbsp; step:integer=1;<br>&nbsp;&nbsp; state:arr;<br><br>procedure
readp;<br>var<br>&nbsp;&nbsp; i,j:integer;<br>&nbsp;&nbsp; ch:char;<br>begin<br>&nbsp;&nbsp;
readln(m,n);<br>&nbsp;&nbsp; for i:=1 to n do<br>&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for j:=1 to m
do<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; read(ch);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; map[i,j]:=(ch='1');<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
inc(max,ord( map[i,j] ))<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end;<br>&nbsp;&nbsp; readln;<br>&nbsp;&nbsp; end;<br>end;<br><br>procedure
writep;<br>var<br>&nbsp;&nbsp; i:integer;<br>begin<br>&nbsp;&nbsp; for i:=1 to step do<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;writeln(
'(' , ans[i].x , ',' , ans[i].y , ')' );<br>end;<br><br>procedure
solve(x,y:integer);<br>var<br>&nbsp;&nbsp; tx,ty,d:integer;<br>&nbsp;&nbsp;
step_cache:integer;<br>&nbsp;&nbsp; state_cache:arr;<br>begin<br>&nbsp;&nbsp;
step_cache:=step;<br>&nbsp;&nbsp; state_cache:=state;<br>&nbsp;&nbsp; if step=max then<br>&nbsp;&nbsp;
begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;writep;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;exit;<br>&nbsp;&nbsp; end;<br><br>&nbsp;&nbsp; for d:=1 to 4
do<br>&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tx:=x+dir[d,1];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ty:=y+dir[d,2];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while
map[tx,ty] and ( not state[tx] and(1 shl (ty-1) )&gt;0) do<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
inc(step);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ans[step].x:=tx;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ans[step].y:=ty;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
state[tx]:=state[tx] or ( 1 shl (ty-1) );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tx:=tx+dir[d,1];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
ty:=ty+dir[d,2];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tx:=tx-dir[d,1];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ty:=ty-dir[d,2];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if
(tx&lt;&gt;x) or (ty&lt;&gt;y) then solve(tx,ty);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;state:=state_cache;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;step:=step_cache;<br>&nbsp;&nbsp;
end;<br>end;<br><br>{====main====}<br>var<br>&nbsp;&nbsp; i,j:integer;<br>begin<br>&nbsp;&nbsp;
assign(input,'garden.in');<br>&nbsp;&nbsp; reset(input);<br>&nbsp;&nbsp;
assign(output,'garden.out');<br>&nbsp;&nbsp; rewrite(output);<br><br>&nbsp;&nbsp; readp;<br>&nbsp;&nbsp;
for i:=1 to n do<br>&nbsp;&nbsp; for j:=1 to m do<br>&nbsp;&nbsp;&nbsp;&nbsp; if map[i,j] then<br>&nbsp;&nbsp;&nbsp;&nbsp;
begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ans[1].x:=i;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ans[1].y:=j;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;state[i]:=1
shl (j-1);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;solve(i,j);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;state[i]:=0;<br>&nbsp;&nbsp;&nbsp;&nbsp; end;<br><br>&nbsp;&nbsp;
close(input);<br>&nbsp;&nbsp; close(output);<br>end.</code><br><br><strong> =======================&nbsp;&nbsp;性
感的分割线&nbsp;&nbsp;=======================</strong> <br><br><br>
</p>
<blockquote>Problem : cowfood / 玉米地<br><br>题目来源<br>&nbsp;&nbsp;&nbsp;&nbsp;USACO月赛<br><br>问
题描述<br>&nbsp;&nbsp;&nbsp;&nbsp;农夫约翰购买了一处肥沃的矩形牧场，分成M*N(1&lt;=M&lt;=12;
1&lt;=N&lt;=12)个格子。他想在那里的一些格子中种植美味的玉米。遗憾的是，有些格子区域的土地是贫瘠的，不能耕种。<br>&nbsp;&nbsp;&nbsp;&nbsp;精明
的约翰知道奶牛们进食时不喜欢和别的牛相邻，所以一旦在一个格子中种植玉米，那么他就不会在相邻的格子中种植，即没有两个被选中的格子拥有公共边。他还没
有最终确定哪些格子要选择种植玉米。<br>&nbsp;&nbsp;&nbsp;&nbsp;作为一个思想开明的人，农夫约翰希望考虑所有可行的选择格子种植方案。由于太开明，他还考虑一个格子
都不选择的种植方案！请帮助农夫约翰确定种植方案总数。<br><br>输入格式:<br>&nbsp;&nbsp;&nbsp;&nbsp;第一行：两个用空格分隔的整数M和N<br>&nbsp;&nbsp;&nbsp;&nbsp;第
二行到第M+1行：第i+1行描述牧场第i行每个格子的情况，N个用空格分隔的整数，表示这个格子是否可以种植（1表示肥沃的、适合种植，0表示贫瘠的、
不可种植）<br><br>输出格式<br>&nbsp;&nbsp;&nbsp;&nbsp;一个整数，农夫约翰可选择的方案总数除以 100,000,000 的余数<br><br>样例输入<br>2
3<br>1 1 1<br>0 1 0<br><br>样例输出<br>9<br><br>样例说明<br><br>&nbsp;&nbsp;&nbsp;&nbsp;给可以种植玉米的格子编
号：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1 2 3<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;4<br><br>&nbsp;&nbsp;&nbsp;&nbsp;只
种一个格子的方案有四种(1,2,3或4)，种植两个格子的方案有三种(13,14或34)，种植三个格子的方案有一种(134)，还有一种什么格子都不
种。<br>&nbsp;&nbsp;&nbsp;&nbsp;4+3+1+1=9。<br><br>数据规模<br>&nbsp;&nbsp;&nbsp;&nbsp;对于30%的数据，N,M&lt;=4；<br>&nbsp;&nbsp;&nbsp;&nbsp;对于
100%的数据，N,M&lt;=12。
<p>&nbsp;</p>
</blockquote>
<p><br><br>程序代码：<br><code>program cowfood;<br><br>const<br>&nbsp;&nbsp;
d=100000000;<br>&nbsp;&nbsp; MaxN=12;<br><br>var<br>&nbsp;&nbsp; f:array[0..MaxN,1..2000]of
longint;<br>&nbsp;&nbsp; w:array[1..2000,1..2000]of boolean;<br>&nbsp;&nbsp;
st:array[0..2000]of integer;<br>&nbsp;&nbsp; map:array[0..MaxN]of integer;<br>&nbsp;&nbsp;
m,n:integer;<br><br>function Impossible(a:integer):boolean;<br>var<br>&nbsp;&nbsp;
i:integer;<br>&nbsp;&nbsp; flag:boolean=false;<br>begin<br>&nbsp;&nbsp; for i:=1 to MaxN do<br>&nbsp;&nbsp;
begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if flag and (a and 1=1) then exit(true);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;flag:=(a
and 1=1);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;a:=a shr 1;<br>&nbsp;&nbsp; end;<br>&nbsp;&nbsp; exit(false);<br>end;<br><br>function
Conflict(a,b:integer):boolean;<br>var<br>&nbsp;&nbsp; i:integer;<br>begin<br>&nbsp;&nbsp;
for i:=1 to MaxN do<br>&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (a and 1=1) and (b and 1=1)
then exit(true);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;a:=a shr 1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;b:=b shr 1;<br>&nbsp;&nbsp; end;<br>&nbsp;&nbsp;
exit(false);<br>end;<br><br>function CanPlace(a,b:integer):boolean;<br>begin<br>&nbsp;&nbsp;
exit(a or b=b);<br>end;<br><br>procedure FindSt;<br>var<br>&nbsp;&nbsp;
i:integer;<br>begin<br>&nbsp;&nbsp; for i:=0 to 1 shl MaxN-1 do<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if not
Impossible(i) then<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; inc(st[0]);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
st[st[0]]:=i;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end;<br>end;<br><br>procedure Init;<br>var<br>&nbsp;&nbsp;
i,j:integer; <br>begin<br>&nbsp;&nbsp; for i:=1 to st[0] do<br>&nbsp;&nbsp; for j:=i to
st[0] do<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if not Conflict(st[i],st[j]) then<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
w[i,j]:=true;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; w[j,i]:=true;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end;<br>end;<br><br>procedure
Readp;<br>var<br>&nbsp;&nbsp; i,j,t,v:integer;<br>begin<br>&nbsp;&nbsp; readln(m,n);<br>&nbsp;&nbsp;
for i:=1 to m do<br>&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;v:=0;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for j:=1 to n do<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
read(t);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; v:=v*2+t;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map[i]:=v;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;readln;<br>&nbsp;&nbsp;
end;<br>end;<br><br>procedure Solve;<br>var<br>&nbsp;&nbsp; i,j,k:integer;<br>begin<br>&nbsp;&nbsp;
f[0,1]:=1;<br>&nbsp;&nbsp; map[0]:=1 shl n-1;<br>&nbsp;&nbsp; for i:=1 to m do<br>&nbsp;&nbsp; for
j:=1 to st[0] do<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if not CanPlace(st[j],map[i]) then f[i,j]:=-1
else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for k:=1 to st[0] do if (f[i-1,k]&lt;&gt;-1) and w[j,k]
then<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f[i,j]:=(f[i,j]+f[i-1,k]) mod d;<br>end;<br><br>procedure
Writep;<br>var<br>&nbsp;&nbsp; j:integer;<br>&nbsp;&nbsp; ans:longint=0;<br>begin<br>&nbsp;&nbsp; for
j:=1 to st[0] do<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if f[m,j]&lt;&gt;-1 then ans:=(ans+f[m,j])
mod d;<br>&nbsp;&nbsp; writeln(ans);<br>end;<br><br>begin<br>&nbsp;&nbsp;
assign(input,'cowfood.in');<br>&nbsp;&nbsp; reset(input);<br>&nbsp;&nbsp;
assign(output,'cowfood.out');<br>&nbsp;&nbsp; rewrite(output);<br><br>&nbsp;&nbsp; FindSt;<br>&nbsp;&nbsp;
Init;<br>&nbsp;&nbsp; Readp;<br>&nbsp;&nbsp; Solve;<br>&nbsp;&nbsp; Writep;<br><br>&nbsp;&nbsp; close(input);<br>&nbsp;&nbsp;
close(output);<br>end.</code></p>
<br>  <img src ="http://www.cppblog.com/yuqilin1228/aggbug/110616.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yuqilin1228/" target="_blank">LynnRaymond</a> 2010-03-26 21:48 <a href="http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110616.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>&lt;b&gt;【转载之VC++】利用Visual C++制作应用程序启动画面&lt;/b&gt;</title><link>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110615.html</link><dc:creator>LynnRaymond</dc:creator><author>LynnRaymond</author><pubDate>Fri, 26 Mar 2010 13:44:00 GMT</pubDate><guid>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110615.html</guid><wfw:comment>http://www.cppblog.com/yuqilin1228/comments/110615.html</wfw:comment><comments>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110615.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yuqilin1228/comments/commentRss/110615.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yuqilin1228/services/trackbacks/110615.html</trackback:ping><description><![CDATA[<strong> <a href="http://edu.itbulo.com/">http://edu.itbulo.com</a><br><br>摘要</strong> ：
本文提供了四种启动画面制作方法。<br><br>　　使用启动画面一是可以减少等待程序加载过程中的枯燥感（尤其是一些大型程序）；二是可以用来显示软
件名称和版权等提示信息。怎样使用VC++制作应用程序的启动画面呢？本文提供四种方法，前三种适用于基于文档的应用程序，第四种适用于基于对话框的应用
程序。<br><br>　　<strong> 1.利用组件库中的Splash Screen组件实现</strong> <br><br>　　(1)用
Photoshop等制作启动画面图像，保存为bmp格式。<br><br>　　(2)用Appwizard建一个基于单文档的工程Splash。<br><br>
(3)在资源中插入位图资源<br><br>　　打开VC++的资源编辑器，用鼠标右键单击Resources文件夹，选择Import命令，插入所制
作的位图。如果位图超过256色，VC会弹出一个对话框，提示位图已经插入但不能在位图编辑器中显示，确定即可。将位图ID改为IDB_SPLASH。 <br><br>
(4)添加Splash Screen控件<br><br>　　①选择菜单&#8220;project&#8221;/&#8220;Add To
Project&#8221;/&#8220;Conponents and Controls&#8221;打开对话框，在列表框中双击&#8220;Visual C++
Conponents&#8221;选项，选择&#8220;Splash Screen&#8221;控件，然后单击&#8220;Insert&#8221;。<br><br>　　②确认或修改类名和位图资源
ID，单击OK确认。<br><br>　　③编译、连接，漂亮的启动画面就显示出来了。<br><br>　　(5)如果需要改变启动画面的停留时间，就修
改SetTimer()函数的第二个参数，默认是750 毫秒。该函数所在位置： <br>
<div style="border: 1px solid #cccccc; padding: 4px 5px 4px 4px; font-size: 13px; width: 98%; background-color: #eeeeee;"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align="top">int&nbsp;CSplashWnd::OnCreate(LPCREATESTRUCT&nbsp;lpCreateStruct)<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" id="Codehighlighter1_56_164_Open_Image" onclick="this.style.display='none';
Codehighlighter1_56_164_Open_Text.style.display='none';
Codehighlighter1_56_164_Closed_Image.style.display='inline';
Codehighlighter1_56_164_Closed_Text.style.display='inline';" align="top"><img src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif" id="Codehighlighter1_56_164_Closed_Image" style="display: none;" onclick="this.style.display='none';
Codehighlighter1_56_164_Closed_Text.style.display='none';
Codehighlighter1_56_164_Open_Image.style.display='inline';
Codehighlighter1_56_164_Open_Text.style.display='inline';" align="top"><img src="http://www.cppblog.com/Images/dot.gif">{<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　<img src="http://www.cppblog.com/Images/dot.gif"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　//&nbsp;Set&nbsp;a&nbsp;timer&nbsp;to&nbsp;destroy&nbsp;the&nbsp;splash&nbsp;screen.<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　SetTimer(1,&nbsp;750,&nbsp;NULL);&nbsp;//修改第二个参数以调整画面停留时间<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　return&nbsp;0;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align="top">}</div>
<strong> 2.利用无模式对话框显示启动画面<br><br></strong> 　　(1)用Appwizard建一个基于单文档的工程
Splash。<br><br>　　(2)导入用作启动画面的图片，更改ID为IDB_SPLASH。<br><br>　　(3)新建一个对话框，在其中
添加启动画面。<br><br>　　在资源中新建一个对话框，创建对话框类CSplashDlg。在对话框中添加一个Picture控件，打开其
&#8220;Properties&#8221;对话框，选
General，在Type下拉列表中选择Bitmap，在Image下拉列表中选前面导入的位图资源ID值：IDB_SPLASH。<br><br>
(4)修改对话框的显示效果<br><br>　　①调整对话框大小，去掉两个自动生成的按钮，并在&#8220;Properties&#8221;的&#8220;Styles&#8221;页中去掉
对Title bar的选取；<br><br>　　②选中图像，调整大小使之适应对话框的可编辑区，修改其&#8220;Properties&#8221;的&#8220;Styles&#8221;使
之居中。<br><br>　　(5)在CMainFrame类的OnCreate()函数中添加创建、显示并销毁无模式对话框的代码。<br>
<div style="border: 1px solid #cccccc; padding: 4px 5px 4px 4px; font-size: 13px; width: 98%; background-color: #eeeeee;"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align="top">#include&nbsp;&#8220;SplashDlg.h&#8221;&nbsp;//加到MainFrm.cpp文件的头文件调用部位<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align="top">int&nbsp;CMainFrame::OnCreate(LPCREATESTRUCT&nbsp;lpCreateStruct)<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" id="Codehighlighter1_105_325_Open_Image" onclick="this.style.display='none';
Codehighlighter1_105_325_Open_Text.style.display='none';
Codehighlighter1_105_325_Closed_Image.style.display='inline';
Codehighlighter1_105_325_Closed_Text.style.display='inline';" align="top"><img src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif" id="Codehighlighter1_105_325_Closed_Image" style="display: none;" onclick="this.style.display='none';
Codehighlighter1_105_325_Closed_Text.style.display='none';
Codehighlighter1_105_325_Open_Image.style.display='inline';
Codehighlighter1_105_325_Open_Text.style.display='inline';" align="top"><img src="http://www.cppblog.com/Images/dot.gif">{<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　CSplashDlg&nbsp;*dlg&nbsp;=&nbsp;new&nbsp;CSplashDlg(this);<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　dlg-&gt;Create(CSplashDlg::IDD,this);&nbsp;//创建对话框<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　dlg-&gt;ShowWindow(SW_SHOW);&nbsp;//显示对话框<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　dlg-&gt;UpdateWindow();<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　Sleep(2000);&nbsp;//画面显示停留时间，单位为毫秒<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　&#8230;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　dlg-&gt;DestroyWindow();&nbsp;//销毁对话框<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　return&nbsp;0;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align="top">}&nbsp;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align="top"></div>
<strong> 3.通过发送消息显示和销毁启动画面<br><br></strong> 　　①重复方法二的步骤1至步骤4。<br><br>　　②使用
Class Wizard为CMainFrame类添加消息响应函数WM_TIMER。<br><br>　　③)修改代码，通过发送WM_TIMER消息
启动和销毁启动画面<br><br>　　1）定义对话框类的变量<br><br>　　在MainFrm.h文件头部添加#include
"SplashDlg.h"，并在CMainFram类的定义中加上公用变量CSplashDlg *Splash。<br><br>　　2）添加计时器
消息相应函数代码<br>
<div style="border: 1px solid #cccccc; padding: 4px 5px 4px 4px; font-size: 13px; width: 98%; background-color: #eeeeee;"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align="top">void&nbsp;CMainFrame::OnTimer(UINT&nbsp;nIDEvent)&nbsp;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" id="Codehighlighter1_41_285_Open_Image" onclick="this.style.display='none';
Codehighlighter1_41_285_Open_Text.style.display='none';
Codehighlighter1_41_285_Closed_Image.style.display='inline';
Codehighlighter1_41_285_Closed_Text.style.display='inline';" align="top"><img src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif" id="Codehighlighter1_41_285_Closed_Image" style="display: none;" onclick="this.style.display='none';
Codehighlighter1_41_285_Closed_Text.style.display='none';
Codehighlighter1_41_285_Open_Image.style.display='inline';
Codehighlighter1_41_285_Open_Text.style.display='inline';" align="top"><img src="http://www.cppblog.com/Images/dot.gif">{<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" id="Codehighlighter1_73_220_Open_Image" onclick="this.style.display='none';
Codehighlighter1_73_220_Open_Text.style.display='none';
Codehighlighter1_73_220_Closed_Image.style.display='inline';
Codehighlighter1_73_220_Closed_Text.style.display='inline';" align="top"><img src="http://www.cppblog.com/Images/OutliningIndicators/ContractedSubBlock.gif" id="Codehighlighter1_73_220_Closed_Image" style="display: none;" onclick="this.style.display='none';
Codehighlighter1_73_220_Closed_Text.style.display='none';
Codehighlighter1_73_220_Open_Image.style.display='inline';
Codehighlighter1_73_220_Open_Text.style.display='inline';" align="top">　if(Splash-&gt;IsWindowVisible())<img src="http://www.cppblog.com/Images/dot.gif">{<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　　Splash-&gt;SetActiveWindow();&nbsp;//把启动画面设置为当前活动窗口<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　　Splash-&gt;UpdateWindow();<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　　Sleep(2000);&nbsp;//修改此处可更改画面显示时间<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　　Splash-&gt;SendMessage(WM_CLOSE);&nbsp;//关闭对话框<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top">　}<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" id="Codehighlighter1_227_283_Open_Image" onclick="this.style.display='none';
Codehighlighter1_227_283_Open_Text.style.display='none';
Codehighlighter1_227_283_Closed_Image.style.display='inline';
Codehighlighter1_227_283_Closed_Text.style.display='inline';" align="top"><img src="http://www.cppblog.com/Images/OutliningIndicators/ContractedSubBlock.gif" id="Codehighlighter1_227_283_Closed_Image" style="display: none;" onclick="this.style.display='none';
Codehighlighter1_227_283_Closed_Text.style.display='none';
Codehighlighter1_227_283_Open_Image.style.display='inline';
Codehighlighter1_227_283_Open_Text.style.display='inline';" align="top">　else<img src="http://www.cppblog.com/Images/dot.gif">{<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　　SetActiveWindow();<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　　KillTimer(1)&nbsp;;&nbsp;//清除WM_TIMER事件<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top">　}<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align="top">}</div>
<br>　3）修改框架生成函数OnCreate()<br>
<div style="border: 1px solid #cccccc; padding: 4px 5px 4px 4px; font-size: 13px; width: 98%; background-color: #eeeeee;"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align="top">int&nbsp;CMainFrame::OnCreate(LPCREATESTRUCT&nbsp;lpCreateStruct)<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" id="Codehighlighter1_56_188_Open_Image" onclick="this.style.display='none';
Codehighlighter1_56_188_Open_Text.style.display='none';
Codehighlighter1_56_188_Closed_Image.style.display='inline';
Codehighlighter1_56_188_Closed_Text.style.display='inline';" align="top"><img src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif" id="Codehighlighter1_56_188_Closed_Image" style="display: none;" onclick="this.style.display='none';
Codehighlighter1_56_188_Closed_Text.style.display='none';
Codehighlighter1_56_188_Open_Image.style.display='inline';
Codehighlighter1_56_188_Open_Text.style.display='inline';" align="top"><img src="http://www.cppblog.com/Images/dot.gif">{<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　SetTimer(1,0,NULL);&nbsp;//添加ID为1的WM_TIMER事
件<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　Splash=new&nbsp;CSplashDlg();<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　Splash-&gt;Create(IDD_DIALOG1);<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　Splash-&gt;ShowWindow(SW_SHOW);<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　&#8230;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align="top">}&nbsp;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align="top"></div>
<strong> 4.制作基于对话框的应用程序启动画面<br><br></strong> 　　以上几种方法都不能给基于对话框的应用程序做启动画面，下
面介绍一种方法给基于对话框的应用程序做启动画面。基于对话框的应用程序没有主框架，因此不能采用前面几种方法制作启动画面。不过我们可以把方法一建立起
的启动画面文件移植过来，然后，对程序进行一些修改。 <br><br>　　(1)参照方法一建立基于单文档的工程Splash。<br><br>
(2)建立基于对话框的工程Cover。<br><br>　　(3)文件移植<br><br>　　①将Splash1.cpp 和Splash1.h
两个文件从方法一建立的Splash工程拷贝到Cover工程中，并且分别加入到Source Files和Header Files中；<br><br>
②导入位图文件到工程的资源中，改ID为IDB_SPLASH。<br><br>　　(4)修改代码，实现启动画面的调用<br><br>　　①添加
CCoverApp 的InitInstance() 函数代码<br>
<div style="border: 1px solid #cccccc; padding: 4px 5px 4px 4px; font-size: 13px; width: 98%; background-color: #eeeeee;"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align="top">#include&nbsp;"Splash1.h"&nbsp;//加在Cover.cpp文件的头文件调用部位<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align="top">BOOL&nbsp;CCoverApp::InitInstance()<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" id="Codehighlighter1_76_194_Open_Image" onclick="this.style.display='none';
Codehighlighter1_76_194_Open_Text.style.display='none';
Codehighlighter1_76_194_Closed_Image.style.display='inline';
Codehighlighter1_76_194_Closed_Text.style.display='inline';" align="top"><img src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif" id="Codehighlighter1_76_194_Closed_Image" style="display: none;" onclick="this.style.display='none';
Codehighlighter1_76_194_Closed_Text.style.display='none';
Codehighlighter1_76_194_Open_Image.style.display='inline';
Codehighlighter1_76_194_Open_Text.style.display='inline';" align="top"><img src="http://www.cppblog.com/Images/dot.gif">{<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　CCommandLineInfo&nbsp;cmdInfo;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　ParseCommandLine(cmdInfo);<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　CSplashWnd::EnableSplashScreen(cmdInfo.m_bShowSplash);<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　<img src="http://www.cppblog.com/Images/dot.gif"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align="top">}&nbsp;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align="top"></div>
<br>②使用ClassWizard 添加OnCreate() 函数到对话框类CCoverDlg中，并修改代码<br>
<div style="border: 1px solid #cccccc; padding: 4px 5px 4px 4px; font-size: 13px; width: 98%; background-color: #eeeeee;"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align="top">#include&nbsp;"Splash1.h"&nbsp;//加在CoverDlg.cpp文件的头文件调用部位<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align="top">int&nbsp;CCoverDlg::OnCreate(LPCREATESTRUCT&nbsp;lpCreateStruct)<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" id="Codehighlighter1_103_161_Open_Image" onclick="this.style.display='none';
Codehighlighter1_103_161_Open_Text.style.display='none';
Codehighlighter1_103_161_Closed_Image.style.display='inline';
Codehighlighter1_103_161_Closed_Text.style.display='inline';" align="top"><img src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif" id="Codehighlighter1_103_161_Closed_Image" style="display: none;" onclick="this.style.display='none';
Codehighlighter1_103_161_Closed_Text.style.display='none';
Codehighlighter1_103_161_Open_Image.style.display='inline';
Codehighlighter1_103_161_Open_Text.style.display='inline';" align="top"><img src="http://www.cppblog.com/Images/dot.gif">{<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　<img src="http://www.cppblog.com/Images/dot.gif"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　CSplashWnd::ShowSplashScreen(this);&nbsp;//显示启动画面<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align="top">　<img src="http://www.cppblog.com/Images/dot.gif"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align="top">}</div>
<br>说明：启动画面停留时间的修改同方法一。<br><br>　　<strong> 5.结束语</strong> <br><br>　　正如前面提过
的，运用好启动画面可以给使用者留下一个强烈的印象，起到很好的宣传作用，以上程序均在Visual C++ 6.0、Windows2000调试通过。
<br>  <img src ="http://www.cppblog.com/yuqilin1228/aggbug/110615.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yuqilin1228/" target="_blank">LynnRaymond</a> 2010-03-26 21:44 <a href="http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110615.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>&lt;b&gt;【转载之VC++】Visual C++ 入门精解&lt;/b&gt;</title><link>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110614.html</link><dc:creator>LynnRaymond</dc:creator><author>LynnRaymond</author><pubDate>Fri, 26 Mar 2010 13:41:00 GMT</pubDate><guid>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110614.html</guid><wfw:comment>http://www.cppblog.com/yuqilin1228/comments/110614.html</wfw:comment><comments>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110614.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yuqilin1228/comments/commentRss/110614.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yuqilin1228/services/trackbacks/110614.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: From: http://www.pconline.com.cn程序作者：管宁　个人网站：www.cndev-lab.com&nbsp; 　　作者保留作品的所有权利，如需转载，请务必注明出处和作者。 被过滤广告　　VC作为一个主流的开发平台一直深受编程爱好者的喜爱，但是很多人却对它的入门感到难于上青天，究其原因主要是大家对他错误的认识造成的，严格的来说VC++不是门语言，虽然它和C...&nbsp;&nbsp;<a href='http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110614.html'>阅读全文</a><img src ="http://www.cppblog.com/yuqilin1228/aggbug/110614.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yuqilin1228/" target="_blank">LynnRaymond</a> 2010-03-26 21:41 <a href="http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110614.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【C++常识】C/C++头文件一览</title><link>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110613.html</link><dc:creator>LynnRaymond</dc:creator><author>LynnRaymond</author><pubDate>Fri, 26 Mar 2010 13:39:00 GMT</pubDate><guid>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110613.html</guid><wfw:comment>http://www.cppblog.com/yuqilin1228/comments/110613.html</wfw:comment><comments>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110613.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yuqilin1228/comments/commentRss/110613.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yuqilin1228/services/trackbacks/110613.html</trackback:ping><description><![CDATA[<strong> <font size="2">C、传统 C++<br><br>#include
&lt;assert.h&gt;　　　　//设定插入点<br>#include &lt;ctype.h&gt;　　　　 //字符处理<br>#include
&lt;errno.h&gt;　　　　 //定义错误码<br>#include &lt;float.h&gt;　　　　 //浮点数处理<br>#include
&lt;fstream.h&gt;　　　 //文件输入／输出<br>#include &lt;iomanip.h&gt;
//参数化输入／输出<br>#include &lt;iostream.h&gt;　　　//数据流输入／输出<br>#include
&lt;limits.h&gt;　　　　//定义各种数据类型最值常量<br>#include
&lt;locale.h&gt;　　　　//定义本地化函数<br>#include &lt;math.h&gt;　　　　　//定义数学函数<br>#include
&lt;stdio.h&gt;　　　　 //定义输入／输出函数<br>#include
&lt;stdlib.h&gt;　　　　//定义杂项函数及内存分配函数<br>#include
&lt;string.h&gt;　　　　//字符串处理<br>#include
&lt;strstrea.h&gt;　　　//基于数组的输入／输出<br>#include
&lt;time.h&gt;　　　　　//定义关于时间的函数<br>#include &lt;wchar.h&gt;
//宽字符处理及输入／输出<br>#include &lt;wctype.h&gt;　　　　//宽字符分类<br><br>//////////////////////////////////////////////////////////////////////////<br><br>标
准 C++　（同上的不再注释）<br><br>#include &lt;algorithm&gt;　　　 //STL 通用算法<br>#include
&lt;bitset&gt;　　　　　//STL 位集容器<br>#include &lt;cctype&gt;<br>#include
&lt;cerrno&gt;<br>#include &lt;clocale&gt;<br>#include &lt;cmath&gt;<br>#include
&lt;complex&gt;　　　　 //复数类<br>#include &lt;cstdio&gt;<br>#include
&lt;cstdlib&gt;<br>#include &lt;cstring&gt;<br>#include &lt;ctime&gt;<br>#include
&lt;deque&gt;　　　　　 //STL 双端队列容器<br>#include &lt;exception&gt;
//异常处理类<br>#include &lt;fstream&gt;<br>#include
&lt;functional&gt;　　　//STL 定义运算函数（代替运算符）<br>#include &lt;limits&gt;<br>#include
&lt;list&gt;　　　　　　//STL 线性列表容器<br>#include &lt;map&gt;　　　　　　 //STL 映射容器<br>#include
&lt;iomanip&gt;<br>#include &lt;ios&gt;　　　　　　 //基本输入／输出支持<br>#include
&lt;iosfwd&gt;　　　　　//输入／输出系统使用的前置声明<br>#include &lt;iostream&gt;<br>#include
&lt;istream&gt;　　　　 //基本输入流<br>#include &lt;ostream&gt;　　　　 //基本输出流<br>#include
&lt;queue&gt;　　　　　 //STL 队列容器<br>#include &lt;set&gt;　　　　　　 //STL 集合容器<br>#include
&lt;sstream&gt;　　　　 //基于字符串的流<br>#include &lt;stack&gt;　　　　　 //STL
堆栈容器　　　　<br>#include &lt;stdexcept&gt;　　　 //标准异常类<br>#include
&lt;streambuf&gt;　　　 //底层输入／输出支持<br>#include &lt;string&gt;　　　　　//字符串类<br>#include
&lt;utility&gt;　　　　 //STL 通用模板类<br>#include &lt;vector&gt;　　　　　//STL
动态数组容器<br>#include &lt;cwchar&gt;<br>#include &lt;cwctype&gt;<br><br>using
namespace std;<br><br>//////////////////////////////////////////////////////////////////////////<br><br>C99
增加<br><br>#include &lt;complex.h&gt;　　 //复数处理<br>#include
&lt;fenv.h&gt;　　　　//浮点环境<br>#include &lt;inttypes.h&gt;　　//整数格式转换<br>#include
&lt;stdbool.h&gt;　　 //布尔环境<br>#include &lt;stdint.h&gt;　　　//整型环境<br>#include
&lt;tgmath.h&gt;　　　//通用类型数学宏</font>C/C++
头文件一览<br><!----><br></strong> <font size="2"><strong> C、传统 C++<br><br>#include &lt;assert.h&gt;　　　　//设定插入点<br>#include
&lt;ctype.h&gt;　　　　 //字符处理<br>#include &lt;errno.h&gt;　　　　 //定义错误码<br>#include
&lt;float.h&gt;　　　　 //浮点数处理<br>#include &lt;fstream.h&gt;　　　 //文件输入／输出<br>#include
&lt;iomanip.h&gt;　　　 //参数化输入／输出<br>#include
&lt;iostream.h&gt;　　　//数据流输入／输出<br>#include
&lt;limits.h&gt;　　　　//定义各种数据类型最值常量<br>#include
&lt;locale.h&gt;　　　　//定义本地化函数<br>#include &lt;math.h&gt;　　　　　//定义数学函数<br>#include
&lt;stdio.h&gt;　　　　 //定义输入／输出函数<br>#include
&lt;stdlib.h&gt;　　　　//定义杂项函数及内存分配函数<br>#include
&lt;string.h&gt;　　　　//字符串处理<br>#include
&lt;strstrea.h&gt;　　　//基于数组的输入／输出<br>#include
&lt;time.h&gt;　　　　　//定义关于时间的函数<br>#include &lt;wchar.h&gt;
//宽字符处理及输入／输出<br>#include &lt;wctype.h&gt;　　　　//宽字符分类<br><br>//////////////////////////////////////////////////////////////////////////<br><br>标
准 C++　（同上的不再注释）<br><br>#include &lt;algorithm&gt;　　　 //STL 通用算法<br>#include
&lt;bitset&gt;　　　　　//STL 位集容器<br>#include &lt;cctype&gt;<br>#include
&lt;cerrno&gt;<br>#include &lt;clocale&gt;<br>#include &lt;cmath&gt;<br>#include
&lt;complex&gt;　　　　 //复数类<br>#include &lt;cstdio&gt;<br>#include
&lt;cstdlib&gt;<br>#include &lt;cstring&gt;<br>#include &lt;ctime&gt;<br>#include
&lt;deque&gt;　　　　　 //STL 双端队列容器<br>#include &lt;exception&gt;
//异常处理类<br>#include &lt;fstream&gt;<br>#include
&lt;functional&gt;　　　//STL 定义运算函数（代替运算符）<br>#include &lt;limits&gt;<br>#include
&lt;list&gt;　　　　　　//STL 线性列表容器<br>#include &lt;map&gt;　　　　　　 //STL 映射容器<br>#include
&lt;iomanip&gt;<br>#include &lt;ios&gt;　　　　　　 //基本输入／输出支持<br>#include
&lt;iosfwd&gt;　　　　　//输入／输出系统使用的前置声明<br>#include &lt;iostream&gt;<br>#include
&lt;istream&gt;　　　　 //基本输入流<br>#include &lt;ostream&gt;　　　　 //基本输出流<br>#include
&lt;queue&gt;　　　　　 //STL 队列容器<br>#include &lt;set&gt;　　　　　　 //STL 集合容器<br>#include
&lt;sstream&gt;　　　　 //基于字符串的流<br>#include &lt;stack&gt;　　　　　 //STL
堆栈容器　　　　<br>#include &lt;stdexcept&gt;　　　 //标准异常类<br>#include
&lt;streambuf&gt;　　　 //底层输入／输出支持<br>#include &lt;string&gt;　　　　　//字符串类<br>#include
&lt;utility&gt;　　　　 //STL 通用模板类<br>#include &lt;vector&gt;　　　　　//STL
动态数组容器<br>#include &lt;cwchar&gt;<br>#include &lt;cwctype&gt;<br><br>using
namespace std;<br><br>//////////////////////////////////////////////////////////////////////////<br><br>C99
增加<br><br>#include &lt;complex.h&gt;　　 //复数处理<br>#include
&lt;fenv.h&gt;　　　　//浮点环境<br>#include &lt;inttypes.h&gt;　　//整数格式转换<br>#include
&lt;stdbool.h&gt;　　 //布尔环境<br>#include &lt;stdint.h&gt;　　　//整型环境<br>#include
&lt;tgmath.h&gt;　　　//通用类型数学宏</strong> </font><br>
<br>  <img src ="http://www.cppblog.com/yuqilin1228/aggbug/110613.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yuqilin1228/" target="_blank">LynnRaymond</a> 2010-03-26 21:39 <a href="http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110613.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>