﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-专注于c++</title><link>http://www.cppblog.com/bellgrade/</link><description /><language>zh-cn</language><lastBuildDate>Thu, 09 Apr 2026 01:51:01 GMT</lastBuildDate><pubDate>Thu, 09 Apr 2026 01:51:01 GMT</pubDate><ttl>60</ttl><item><title>C中的预编译宏详解</title><link>http://www.cppblog.com/bellgrade/archive/2010/03/18/110030.html</link><dc:creator>bellgrade</dc:creator><author>bellgrade</author><pubDate>Thu, 18 Mar 2010 09:48:00 GMT</pubDate><guid>http://www.cppblog.com/bellgrade/archive/2010/03/18/110030.html</guid><wfw:comment>http://www.cppblog.com/bellgrade/comments/110030.html</wfw:comment><comments>http://www.cppblog.com/bellgrade/archive/2010/03/18/110030.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/bellgrade/comments/commentRss/110030.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/bellgrade/services/trackbacks/110030.html</trackback:ping><description><![CDATA[<span class=Apple-style-span style="WORD-SPACING: 0px; FONT: medium Simsun; TEXT-TRANSFORM: none; COLOR: rgb(0,0,0); TEXT-INDENT: 0px; WHITE-SPACE: normal; LETTER-SPACING: normal; BORDER-COLLAPSE: separate; orphans: 2; widows: 2; -webkit-border-horizontal-spacing: 0px; -webkit-border-vertical-spacing: 0px; -webkit-text-decorations-in-effect: none; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px"><span class=Apple-style-span style="FONT-SIZE: 12px; COLOR: rgb(253,131,201); LINE-HEIGHT: 20px; FONT-FAMILY: Arial; -webkit-border-horizontal-spacing: 2px; -webkit-border-vertical-spacing: 2px">
<p style="LINE-HEIGHT: normal"><font style="LINE-HEIGHT: normal" size=2><span style="FONT-WEIGHT: bold; COLOR: rgb(255,1,2); LINE-HEIGHT: normal">(一) 预处理命令简介</span><br style="LINE-HEIGHT: normal"></font>
<table style="BORDER-RIGHT: rgb(153,153,153) 1px solid; TABLE-LAYOUT: auto; BORDER-TOP: rgb(153,153,153) 1px solid; FONT-SIZE: 12px; BORDER-LEFT: rgb(153,153,153) 1px solid; WIDTH: 732px; LINE-HEIGHT: normal; BORDER-BOTTOM: rgb(153,153,153) 1px solid" align=center>
    <tbody style="LINE-HEIGHT: normal">
        <tr style="LINE-HEIGHT: normal">
            <td style="FONT-SIZE: 12px; FILTER: none; VISIBILITY: visible! important; WORD-BREAK: break-all; LINE-HEIGHT: normal; ZOOM: 1! important; FONT-FAMILY: Arial; WORD-WRAP: break-word"><font style="LINE-HEIGHT: normal" size=2>注意: 函数宏对参数类型是不敏感的, 你不必考虑将何种数据类型传递给宏. 那么, 如何构建对参数类型敏感的宏呢? 参考本章的第九部分, 关于"##"的介绍.<br style="LINE-HEIGHT: normal"></font></td>
        </tr>
    </tbody>
</table>
<br style="LINE-HEIGHT: normal"><font style="LINE-HEIGHT: normal" size=2><font style="LINE-HEIGHT: normal" color=#000000><span style="FONT-WEIGHT: bold; COLOR: rgb(0,1,255); LINE-HEIGHT: normal">关于定义宏的另外一些问题</span><br style="LINE-HEIGHT: normal">(1) 宏可以被多次定义, 前提是这些定义必须是相同的. 这里的"相同"要求先后定义中空白符出现的位置相同, 但具体的空白符类型或数量可不同, 比如原先的空格可替换为多个其他类型的空白符: 可为tab, 注释...<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">e.g.</span><br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#define NULL 0</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#define NULL　/* null pointer */&nbsp;&nbsp;&nbsp;&nbsp; 0</span><br style="LINE-HEIGHT: normal">上面的重定义是相同的, 但下面的重定义不同:<br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#define fun(x) x+1</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#define fun(x) x + 1 或: #define fun(y) y+1</span><br style="LINE-HEIGHT: normal">如果多次定义时, 再次定义的宏内容是不同的, gcc会给出"NAME redefined"警告信息.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">应该避免重新定义函数宏, 不管是在预处理命令中还是C语句中, 最好对某个对象只有单一的定义. 在gcc中, 若宏出现了重定义, gcc会给出警告.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">(2) 在gcc中, 可在命令行中指定对象宏的定义:<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">e.g.</span><br style="LINE-HEIGHT: normal">$<span class=Apple-converted-space>&nbsp;</span><span style="COLOR: rgb(0,1,255); LINE-HEIGHT: normal">gcc -Wall -DMAX=100 -o tmp tmp.c</span><br style="LINE-HEIGHT: normal">相当于在tmp.c中添加" #define MAX 100".<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">那么, 如果原先tmp.c中含有MAX宏的定义, 那么再在gcc调用命令中使用-DMAX, 会出现什么情况呢?<br style="LINE-HEIGHT: normal">---若-DMAX=1, 则正确编译.<br style="LINE-HEIGHT: normal">---若-DMAX的值被指定为不为1的值, 那么gcc会给出MAX宏被重定义的警告, MAX的值仍为1.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">注意: 若在调用gcc的命令行中不显示地给出对象宏的值, 那么gcc赋予该宏默认值(1), 如: -DVAL == -DVAL=1<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">(3) #define所定义的宏的作用域<br style="LINE-HEIGHT: normal">宏在定义之后才生效, 若宏定义被#undef取消, 则#undef之后该宏无效. 并且字符串中的宏不会被识别<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">e.g.</span><br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#define ONE 1</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">sum = ONE + TWO&nbsp;&nbsp;&nbsp; /* sum = 1 + TWO */</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#define TWO 2</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">sum = ONE + TWO&nbsp;&nbsp;&nbsp; /* sum = 1 + 2&nbsp;&nbsp;&nbsp; */<span class=Apple-converted-space>&nbsp;</span></span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#undef ONE</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">sum = ONE + TWO&nbsp;&nbsp;&nbsp; /* sum = ONE + 2 */</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">char c[] = "TWO"&nbsp;&nbsp; /* c[] = "TWO", NOT "2"! */</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">(4) 宏的替换可以是递归的, 所以可以嵌套定义宏.<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">e.g.</span><br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"># define ONE NUMBER_1</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"># define NUMBER_1 1</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">int a = ONE /* a = 1 */</span><br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; COLOR: rgb(0,1,255); LINE-HEIGHT: normal">2, #undef</span><br style="LINE-HEIGHT: normal">#undef用来取消宏定义, 它与#define对立:<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#undef name</span><br style="LINE-HEIGHT: normal">如够被取消的宏实际上没有被#define所定义, 针对它的#undef并不会产生错误.<br style="LINE-HEIGHT: normal">当一个宏定义被取消后, 可以再度定义它.<span class=Apple-converted-space>&nbsp;</span><br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; COLOR: rgb(0,1,255); LINE-HEIGHT: normal">3, #if, #elif, #else, #endif</span><br style="LINE-HEIGHT: normal">#if, #elif, #else, #endif用于条件编译:<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#if 常量表达式1</span><br style="FONT-WEIGHT: bold; LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">&nbsp;&nbsp; 语句...</span><br style="FONT-WEIGHT: bold; LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#elif 常量表达式2</span><br style="FONT-WEIGHT: bold; LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">&nbsp;&nbsp; 语句...</span><br style="FONT-WEIGHT: bold; LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#elif 常量表达式3</span><br style="FONT-WEIGHT: bold; LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">&nbsp;&nbsp; 语句...</span><br style="FONT-WEIGHT: bold; LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">...</span><br style="FONT-WEIGHT: bold; LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#else</span><br style="FONT-WEIGHT: bold; LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">&nbsp;&nbsp; 语句...</span><br style="FONT-WEIGHT: bold; LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#endif</span><br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">#if和#else分别相当于C语句中的if, else. 它们根据常量表达式的值来判别是否执行后面的语句. #elif相当于C中的else-if. 使用这些条件编译命令可以方便地实现对源代码内容的控制.<br style="LINE-HEIGHT: normal">else之后不带常量表达式, 但若包含了常量表达式, gcc只是给出警告信息.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">使用它们可以提升代码的可移植性---针对不同的平台使用执行不同的语句. 也经常用于大段代码注释.<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">e.g.</span><br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#if 0</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">{</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">&nbsp;&nbsp; 一大段代码;</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">}</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#endif</span><br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">常量表达式可以是包含宏, 算术运算, 逻辑运算等等的合法C常量表达式, 如果常量表达式为一个未定义的宏, 那么它的值被视为0.<br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#if MACRO_NON_DEFINED</span><span class=Apple-converted-space>&nbsp;</span>== #if 0<br style="LINE-HEIGHT: normal">在判断某个宏是否被定义时, 应当避免使用#if, 因为该宏的值可能就是被定义为0. 而应当使用下面介绍的#ifdef或#ifndef.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">注意: #if, #elif, #else之后的宏只能是对象宏. 如果name为名的宏未定义, 或者该宏是函数宏. 那么在gcc中使用"-Wundef"选项会显示宏未定义的警告信息.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; COLOR: rgb(0,1,255); LINE-HEIGHT: normal">4, #ifdef, #ifndef, defined.</span><br style="LINE-HEIGHT: normal">#ifdef, #ifndef, defined用来测试某个宏是否被定义<br style="LINE-HEIGHT: normal">#ifdef name 或 #ifndef name<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">它们经常用于避免头文件的重复引用:<br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#ifndef __FILE_H__</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#define __FILE_H__</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#include "file.h"</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#endif</span><br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">defined(name): 若宏被定义,则返回1, 否则返回0.<br style="LINE-HEIGHT: normal">它与#if, #elif, #else结合使用来判断宏是否被定义, 乍一看好像它显得多余, 因为已经有了#ifdef和#ifndef. defined用于在一条判断语句中声明多个判别条件:<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#if defined(VAX) &amp;&amp; defined(UNIX) &amp;&amp; !defined(DEBUG)<span class=Apple-converted-space>&nbsp;</span></span><br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">和#if, #elif, #else不同, #indef, #ifndef, defined测试的宏可以是对象宏, 也可以是函数宏. 在gcc中使用"-Wundef"选项不会显示宏未定义的警告信息.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; COLOR: rgb(0,1,255); LINE-HEIGHT: normal">5, #include , #include_next</span><br style="LINE-HEIGHT: normal">#include用于文件包含. 在#include 命令所在的行不能含有除注释和空白符之外的其他任何内容.<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#include "headfile"</span><br style="FONT-WEIGHT: bold; LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#include &lt;headfile&gt;</span><br style="FONT-WEIGHT: bold; LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#include 预处理标记</span><br style="LINE-HEIGHT: normal">前面两种形式大家都很熟悉, "#include 预处理标记"中, 预处理标记会被预处理器进行替换, 替换的结果必须符合前两种形式中的某一种.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">实际上, 真正被添加的头文件并不一定就是#include中所指定的文件. #include"headfile"包含的头文件当然是同一个文件, 但#include &lt;headfile&gt;包包含的"系统头文件"可能是另外的文件. 但这不值得被注意. 感兴趣的话可以查看宏扩展后到底引入了哪些系统头文件.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">关于#include "headfile"和#include &lt;headfile&gt;的区别以及如何在gcc中包含头文件的详细信息, 参考本blog的GCC笔记.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">相对于#include, 我们对#include_next不太熟悉. #include_next仅用于特殊的场合. 它被用于头文件中(#include既可用于头文件中, 又可用于.c文件中)来包含其他的头文件. 而且包含头文件的路径比较特殊: 从当前头文件所在目录之后的目录来搜索头文件.<br style="LINE-HEIGHT: normal">比如: 头文件的搜索路径一次为A,B,C,D,E. #include_next所在的当前头文件位于B目录, 那么#include_next使得预处理器从C,D,E目录来搜索#include_next所指定的头文件.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">可参考</font></font><a style="FONT-SIZE: 12px; COLOR: rgb(253,131,201); LINE-HEIGHT: 20px; TEXT-DECORATION: none" href="http://gcc.gnu.org/onlinedocs/" target=_blank><font style="LINE-HEIGHT: normal" color=#000000 size=2>cpp手册</font></a><font style="LINE-HEIGHT: normal" size=2>进一步了解#include_next<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; COLOR: rgb(0,1,255); LINE-HEIGHT: normal">6, 预定义宏</span><br style="LINE-HEIGHT: normal">标准C中定义了一些对象宏, 这些宏的名称以"__"开头和结尾, 并且都是大写字符. 这些预定义宏可以被#undef, 也可以被重定义.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">下面列出一些标准C中常见的预定义对象宏(其中也包含gcc自己定义的一些预定义宏:<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">__LINE__<span class=Apple-converted-space>&nbsp;</span></span>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 当前语句所在的行号, 以10进制整数标注.<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">__FILE__<span class=Apple-converted-space>&nbsp;</span></span>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 当前源文件的文件名, 以字符串常量标注.<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">__DATE__&nbsp;&nbsp;</span><span class=Apple-converted-space>&nbsp;</span>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; 程序被编译的日期, 以"Mmm dd yyyy"格式的字符串标注.<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">__TIME__&nbsp;&nbsp;</span><span class=Apple-converted-space>&nbsp;</span>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 程序被编译的时间, 以"hh:mm:ss"格式的字符串标注, 该时间由asctime返回.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">__STDC__</span>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 如果当前编译器符合ISO标准, 那么该宏的值为1<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">__STDC_VERSION__</span>&nbsp;&nbsp; 如果当前编译器符合C89, 那么它被定义为199409L, 如果符合C99, 那么被定义为199901L.<span class=Apple-converted-space>&nbsp;</span><br style="LINE-HEIGHT: normal">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 我用gcc, 如果不指定-std=c99, 其他情况都给出__STDC_VERSION__未定义的错误信息, 咋回事呢?<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">__STDC_HOSTED__<span class=Apple-converted-space>&nbsp;</span></span>&nbsp;&nbsp;&nbsp; 如果当前系统是"本地系统(hosted)", 那么它被定义为1. 本地系统表示当前系统拥有完整的标准C库.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">gcc定义的预定义宏:<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">__OPTMIZE__<span class=Apple-converted-space>&nbsp;</span></span>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 如果编译过程中使用了优化, 那么该宏被定义为1.<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">__OPTMIZE_SIZE__</span>&nbsp;&nbsp; 同上, 但仅在优化是针对代码大小而非速度时才被定义为1.<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">__VERSION__<span class=Apple-converted-space>&nbsp;</span></span>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 显示所用gcc的版本号.<br style="LINE-HEIGHT: normal">可参考"GCC the complete reference".<br style="LINE-HEIGHT: normal">要想看到gcc所定义的所有预定义宏, 可以运行: $ cpp -dM /dev/null<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; COLOR: rgb(0,1,255); LINE-HEIGHT: normal">7, #line</span><br style="LINE-HEIGHT: normal">#line用来修改__LINE__和__FILE__.<span class=Apple-converted-space>&nbsp;</span><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">e.g.</span><br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(0,1,2); LINE-HEIGHT: normal">printf("line: %d, file: %s\n", __LINE__, __FILE__);</span><br style="COLOR: rgb(0,1,2); LINE-HEIGHT: normal"><span style="COLOR: rgb(0,1,2); LINE-HEIGHT: normal">#line 100 "haha"</span><br style="COLOR: rgb(0,1,2); LINE-HEIGHT: normal"><span style="COLOR: rgb(0,1,2); LINE-HEIGHT: normal">printf("line: %d, file: %s\n", __LINE__, __FILE__);</span><br style="COLOR: rgb(0,1,2); LINE-HEIGHT: normal"><span style="COLOR: rgb(0,1,2); LINE-HEIGHT: normal">printf("line: %d, file: %s\n", __LINE__, __FILE__);</span><br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">显示:<br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">line: 34, file: 1.c</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">line: 100, file: haha</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">line: 101, file: haha</span><span class=Apple-converted-space>&nbsp;</span><br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; COLOR: rgb(0,1,255); LINE-HEIGHT: normal">8, #pragma, _Pragma</span><br style="LINE-HEIGHT: normal">#pragma用编译器用来添加新的预处理功能或者显示一些编译信息. #pragma的格式是各编译器特定的, gcc的如下:<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#pragma GCC name token(s)</span><br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">#pragma之后有两个部分: GCC和特定的pragma name. 下面分别介绍gcc中常用的.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; COLOR: rgb(0,1,2); LINE-HEIGHT: normal">(1) #pragma GCC dependency</span><br style="LINE-HEIGHT: normal">dependency测试当前文件(既该语句所在的程序代码)与指定文件(既#pragma语句最后列出的文件)的时间戳. 如果指定文件比当前文件新, 则给出警告信息.<span class=Apple-converted-space>&nbsp;</span><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">e.g.</span><br style="LINE-HEIGHT: normal">在demo.c中给出这样一句:<br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(0,1,2); LINE-HEIGHT: normal">#pragma GCC dependency "temp-file"</span><br style="LINE-HEIGHT: normal">然后在demo.c所在的目录新建一个更新的文件: $<span class=Apple-converted-space>&nbsp;</span><span style="COLOR: rgb(0,1,255); LINE-HEIGHT: normal">touch temp-file</span>, 编译: $<span class=Apple-converted-space>&nbsp;</span><span style="COLOR: rgb(0,1,255); LINE-HEIGHT: normal">gcc demo.c</span><span class=Apple-converted-space>&nbsp;</span>会给出这样的警告信息:<span class=Apple-converted-space>&nbsp;</span><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">warning: current file is older than temp-file</span><br style="LINE-HEIGHT: normal">如果当前文件比指定的文件新, 则不给出任何警告信息.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">还可以在在#pragma中给添加自定义的警告信息.<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">e.g.</span><br style="LINE-HEIGHT: normal"></font><font style="LINE-HEIGHT: normal" size=2><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(0,1,2); LINE-HEIGHT: normal">#pragma GCC dependency "temp-file" "demo.c needs to be updated!"</span><br style="LINE-HEIGHT: normal">1.c:27:38: warning: extra tokens at end of #pragma directive</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">1.c:27:38: warning: current file is older than temp-file</span><br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(0,1,2); LINE-HEIGHT: normal">注意: 后面新增的警告信息要用""引用起来, 否则gcc将给出警告信息.</span><br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">(2) #pragma GCC poison token(s)</span><br style="LINE-HEIGHT: normal">若源代码中出现了#pragma中给出的token(s), 则编译时显示警告信息. 它一般用于在调用你不想使用的函数时候给出<span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">出错信息</span>.<br style="LINE-HEIGHT: normal">e.g.<br style="LINE-HEIGHT: normal">#pragma GCC poison scanf<br style="LINE-HEIGHT: normal">scanf("%d", &amp;a);<span class=Apple-converted-space>&nbsp;</span><br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">warning: extra tokens at end of #pragma directive</span><br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">error: attempt to use poisoned "scanf"</span><br style="LINE-HEIGHT: normal">注意, 如果调用了poison中给出的标记, 那么编译器会给出的是出错信息. 关于第一条警告, 我还不知道怎么避免, 用""将token(s)引用起来也不行.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">(3) #pragma GCC system_header</span><br style="LINE-HEIGHT: normal">从#pragma GCC system_header直到文件结束之间的代码会被编译器视为系统头文件之中的代码. 系统头文件中的代码往往不能完全遵循C标准, 所以头文件之中的警告信息往往不显示. (除非用 #warning显式指明).<span class=Apple-converted-space>&nbsp;</span><br style="LINE-HEIGHT: normal">(这条#pragma语句还没发现用什么大的用处<img style="LINE-HEIGHT: normal" src="http://www.cublog.cn/images/face/033.gif">)<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">由于#pragma不能用于宏扩展, 所以gcc还提供了<span style="FONT-WEIGHT: bold; COLOR: rgb(0,1,255); LINE-HEIGHT: normal">_Pragma</span>:<br style="LINE-HEIGHT: normal">e.g.<br style="LINE-HEIGHT: normal">#define PRAGMA_DEP #pragma GCC dependency "temp-file"<br style="LINE-HEIGHT: normal">由于预处理之进行一次宏扩展, 采用上面的方法会在编译时引发错误, 要将#pragma语句定义成一个宏扩展, 应该使用下面的_Pragma语句:<br style="LINE-HEIGHT: normal">#define PRAGMA_DEP _Pragma("GCC dependency \"temp-file\"")<br style="LINE-HEIGHT: normal">注意, ()中包含的""引用之前引该加上\转义字符.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; COLOR: rgb(0,1,255); LINE-HEIGHT: normal">9, #, ##</span><br style="LINE-HEIGHT: normal">#和##用于对字符串的预处理操作, 所以他们也经常用于printf, puts之类的字符串显示函数中.<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#</span>用于在宏扩展之后将tokens转换为以tokens为内容的字符串常量.<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">e.g.</span><br style="LINE-HEIGHT: normal">#define TEST(a,b) printf( #a "&lt;" #b "=%d\n", (a)&lt;(b));<br style="LINE-HEIGHT: normal">注意: #只针对紧随其后的token有效!<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">##</span>用于将它前后的两个token组合在一起转换成以这两个token为内容的字符串常量. 注意##前后必须要有token.<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">e.g.</span><br style="LINE-HEIGHT: normal">#define TYPE(type, n) type n<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">之后调用:<span class=Apple-converted-space>&nbsp;</span><br style="LINE-HEIGHT: normal">TYPE(int, a) = 1;<br style="LINE-HEIGHT: normal">TYPE(long, b) = 1999;<br style="LINE-HEIGHT: normal">将被替换为:<br style="LINE-HEIGHT: normal">int a = 1;<br style="LINE-HEIGHT: normal">long b = 1999;<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; COLOR: rgb(0,1,255); LINE-HEIGHT: normal">(10) #warning, #error</span><br style="LINE-HEIGHT: normal">#warning, #error分别用于在编译时显示警告和错误信息, 格式如下:<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#warning tokens</span><br style="FONT-WEIGHT: bold; LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#error tokens</span><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">e.g.</span><br style="LINE-HEIGHT: normal">#warning "some warning"<br style="LINE-HEIGHT: normal">注意, #error和#warning后的token要用""引用起来!<br style="LINE-HEIGHT: normal">(在gcc中, 如果给出了warning, 编译继续进行, 但若给出了error, 则编译停止. 若在命令行中指定了 -Werror, 即使只有警告信息, 也不编译.<br style="LINE-HEIGHT: normal"></font></p>
<p style="LINE-HEIGHT: normal"></p>
<hr style="WIDTH: 918px; LINE-HEIGHT: normal; HEIGHT: 2px">
<p style="LINE-HEIGHT: normal"></p>
<p style="LINE-HEIGHT: normal">预处理命令由#(hash字符)开头, 它独占一行, #之前只能是空白符. 以#开头的语句就是预处理命令, 不以#开头的语句为C中的代码行. 常用的预处理命令如下:<br style="LINE-HEIGHT: normal"><br style="FONT-WEIGHT: bold; LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#define</span>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; 定义一个预处理宏<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#undef<span class=Apple-converted-space>&nbsp;</span></span>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 取消宏的定义<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#include<span class=Apple-converted-space>&nbsp;</span></span>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 包含文件命令<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#include_next</span>&nbsp;&nbsp; 与#include相似, 但它有着特殊的用途<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#if</span><span class=Apple-converted-space>&nbsp;</span>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 编译预处理中的条件命令, 相当于C语法中的if语句<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#ifdef</span><span class=Apple-converted-space>&nbsp;</span>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 判断某个宏是否被定义, 若已定义, 执行随后的语句<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#ifndef</span><span class=Apple-converted-space>&nbsp;</span>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 与#ifdef相反, 判断某个宏是否未被定义<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#elif</span><span class=Apple-converted-space>&nbsp;</span>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 若#if, #ifdef, #ifndef或前面的#elif条件不满足, 则执行#elif之后的语句, 相当于C语法中的else-if<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#else</span><span class=Apple-converted-space>&nbsp;</span>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 与#if, #ifdef, #ifndef对应, 若这些条件不满足, 则执行#else之后的语句, 相当于C语法中的else<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#endif<span class=Apple-converted-space>&nbsp;</span></span>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #if, #ifdef, #ifndef这些条件命令的结束标志.<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">defined&nbsp;&nbsp;</span><span class=Apple-converted-space>&nbsp;</span>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; 与#if, #elif配合使用, 判断某个宏是否被定义<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#line</span>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 标志该语句所在的行号<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#</span>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 将宏参数替代为以参数值为内容的字符窜常量<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">##</span>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 将两个相邻的标记(token)连接为一个单独的标记<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#pragma</span>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 说明编译器信息<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#warning</span>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 显示编译警告信息<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#error<span class=Apple-converted-space>&nbsp;</span></span>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; 显示编译错误信息<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; COLOR: rgb(255,1,2); LINE-HEIGHT: normal">(二) 预处理的文法</span></p>
<p style="LINE-HEIGHT: normal"></p>
<hr style="WIDTH: 918px; LINE-HEIGHT: normal; HEIGHT: 2px">
<p style="LINE-HEIGHT: normal"></p>
<p style="LINE-HEIGHT: normal">预处理并不分析整个源代码文件, 它只是将源代码分割成一些标记(token), 识别语句中哪些是C语句, 哪些是预处理语句. 预处理器能够识别C标记, 文件名, 空白符, 文件结尾标志.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">预处理语句格式:&nbsp;&nbsp;&nbsp;<span class=Apple-converted-space>&nbsp;</span><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#command name(...) token(s)</span><br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">1, command预处理命令的名称, 它之前以#开头, #之后紧随预处理命令, 标准C允许#两边可以有空白符, 但比较老的编译器可能不允许这样. 若某行中只包含#(以及空白符), 那么在标准C中该行被理解为空白. 整个预处理语句之后只能有空白符或者注释, 不能有其它内容.<br style="LINE-HEIGHT: normal">2, name代表宏名称, 它可带参数. 参数可以是可变参数列表(C99).<br style="LINE-HEIGHT: normal">3, 语句中可以利用"\"来换行.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; COLOR: rgb(0,1,2); LINE-HEIGHT: normal">e.g.</span><br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"># define ONE 1 /* ONE == 1 */</span><br style="LINE-HEIGHT: normal">等价于:<span class=Apple-converted-space>&nbsp;</span><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#define ONE　１</span><br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#define err(flag, msg) if(flag) \</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">&nbsp;&nbsp; printf(msg)</span><br style="LINE-HEIGHT: normal">等价于:<span class=Apple-converted-space>&nbsp;</span><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#define err(flag, msg) if(flag) printf(msg)</span><br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; COLOR: rgb(255,1,2); LINE-HEIGHT: normal">(三) 预处理命令详述</span></p>
<p style="LINE-HEIGHT: normal"></p>
<hr style="WIDTH: 918px; LINE-HEIGHT: normal; HEIGHT: 2px">
<p style="LINE-HEIGHT: normal"></p>
<p style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; COLOR: rgb(0,1,255); LINE-HEIGHT: normal">1, #define</span><br style="LINE-HEIGHT: normal">#define命令定义一个宏:<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#define MACRO_NAME(args) tokens(opt)</span><br style="LINE-HEIGHT: normal">之后出现的MACRO_NAME将被替代为所定义的标记(tokens). 宏可带参数, 而后面的标记也是可选的.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(0,1,255); LINE-HEIGHT: normal">对象宏</span><br style="LINE-HEIGHT: normal">不带参数的宏被称为"对象宏(objectlike macro)"<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">#define经常用来定义常量, 此时的宏名称一般为大写的字符串. 这样利于修改这些常量.<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">e.g.</span><br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#define MAX 100</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">int a[MAX];</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#ifndef __FILE_H__</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#define __FILE_H__</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#include "file.h"</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#endif</span><br style="LINE-HEIGHT: normal">#define __FILE_H__ 中的宏就不带任何参数, 也不扩展为任何标记. 这经常用于包含头文件.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">要调用该宏, 只需在代码中指定宏名称, 该宏将被替代为它被定义的内容.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(0,1,255); LINE-HEIGHT: normal">函数宏</span><br style="LINE-HEIGHT: normal">带参数的宏也被称为"函数宏". 利用宏可以提高代码的运行效率: 子程序的调用需要压栈出栈, 这一过程如果过于频繁会耗费掉大量的CPU运算资源. 所以一些代码量小但运行频繁的代码如果采用带参数宏来实现会提高代码的运行效率.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="LINE-HEIGHT: normal; TEXT-DECORATION: underline">函数宏的参数是固定的情况</span><br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">函数宏的定义采用这样的方式: #define name( args ) tokens<br style="LINE-HEIGHT: normal">其中的args和tokens都是可选的. 它和对象宏定义上的区别在于宏名称之后不带括号.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">注意, name之后的左括号(必须紧跟name, 之间不能有空格, 否则这就定义了一个对象宏, 它将被替换为 以(开始的字符串. 但在调用函数宏时, name与(之间可以有空格.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">e.g.</span><br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#define mul(x,y) ((x)*(y))</span><br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">注意, 函数宏之后的参数要用括号括起来, 看看这个例子:<br style="LINE-HEIGHT: normal">e.g.<br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#define mul(x,y) x*y</span><br style="LINE-HEIGHT: normal">"mul(1, 2+2);" 将被扩展为: 1*2 + 2<br style="LINE-HEIGHT: normal">同样, 整个标记串也应该用括号引用起来:<br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">e.g.</span><br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#define mul(x,y) (x)*(y)</span><br style="LINE-HEIGHT: normal">sizeof mul(1,2.0) 将被扩展为 sizeof 1 * 2.0<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">调用函数宏时候, 传递给它的参数可以是函数的返回值, 也可以是任何有意义的语句:<br style="LINE-HEIGHT: normal">e.g.<br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">mul (f(a,b), g(c,d));</span><br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">e.g.<br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#define insert(stmt) stmt</span><br style="LINE-HEIGHT: normal">insert ( a=1; b=2;) 相当于在代码中加入 a=1; b=2 .<br style="LINE-HEIGHT: normal">insert ( a=1, b=2;) 就有问题了: 预处理器会提示出错: 函数宏的参数个数不匹配. 预处理器把","视为参数间的分隔符.<span class=Apple-converted-space>&nbsp;</span><br style="LINE-HEIGHT: normal">insert ((a=1, b=2;)) 可解决上述问题.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">在定义和调用函数宏时候, 要注意一些问题:<br style="LINE-HEIGHT: normal">1, 我们经常用{}来引用函数宏被定义的内容, 这就要注意调用这个函数宏时的";"问题.<br style="LINE-HEIGHT: normal">example_3.7:<br style="LINE-HEIGHT: normal">#define swap(x,y) { unsigned long _temp=x; x=y; y=_tmp}<br style="LINE-HEIGHT: normal">如果这样调用它: "swap(1,2);" 将被扩展为: { unsigned long _temp=1; 1=2; 2=_tmp};<span class=Apple-converted-space>&nbsp;</span><br style="LINE-HEIGHT: normal">明显后面的;是多余的, 我们应该这样调用: swap(1,2)<br style="LINE-HEIGHT: normal">虽然这样的调用是正确的, 但它和C语法相悖, 可采用下面的方法来处理被{}括起来的内容:<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">#define swap(x,y) \<br style="LINE-HEIGHT: normal">&nbsp;&nbsp; do { unsigned long _temp=x; x=y; y=_tmp} while (0)<br style="LINE-HEIGHT: normal">swap(1,2); 将被替换为:<br style="LINE-HEIGHT: normal">do { unsigned long _temp=1; 1=2; 2=_tmp} while (0);<br style="LINE-HEIGHT: normal">在Linux内核源代码中对这种do-while(0)语句有这广泛的应用.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">2, 有的函数宏是无法用do-while(0)来实现的, 所以在调用时不能带上";", 最好在调用后添加注释说明.<br style="LINE-HEIGHT: normal">eg_3.8:<br style="LINE-HEIGHT: normal">#define incr(v, low, high) \<br style="LINE-HEIGHT: normal">&nbsp;&nbsp; for ((v) = (low),; (v) &lt;= (high); (v)++)<br style="LINE-HEIGHT: normal">只能以这样的形式被调用: incr(a, 1, 10) /* increase a form 1 to 10 */<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="LINE-HEIGHT: normal; TEXT-DECORATION: underline">函数宏中的参数包括可变参数列表的情况</span><br style="LINE-HEIGHT: normal">C99标准中新增了可变参数列表的内容. 不光是函数, 函数宏中也可以使用可变参数列表.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#define name(args, ...) tokens</span><br style="LINE-HEIGHT: normal"><span style="FONT-WEIGHT: bold; LINE-HEIGHT: normal">#define name(...) tokens</span><br style="LINE-HEIGHT: normal">"..."代表可变参数列表, 如果它不是仅有的参数, 那么它只能出现在参数列表的最后. 调用这样的函数宏时, 传递给它的参数个数要不少于参数列表中参数的个数(多余的参数被丢弃).<span class=Apple-converted-space>&nbsp;</span><br style="LINE-HEIGHT: normal">通过__VA_ARGS__来替换函数宏中的可变参数列表. 注意__VA_ARGS__只能用于函数宏中参数中包含有"..."的情况.<br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">e.g.<br style="LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#ifdef DEBUG</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#define my_printf(...) fprintf(stderr, __VA_ARGS__)</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#else</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#define my_printf(...) printf(__VA_ARGS__)</span><br style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal"><span style="COLOR: rgb(73,73,73); LINE-HEIGHT: normal">#endif</span><br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">tokens中的__VA_ARGS__被替换为函数宏定义中的"..."可变参数列表.<span class=Apple-converted-space>&nbsp;</span><br style="LINE-HEIGHT: normal"><br style="LINE-HEIGHT: normal">注意在使用#define时候的一些常见错误:<br style="LINE-HEIGHT: normal">#define MAX = 100<br style="LINE-HEIGHT: normal">#define MAX 100;<br style="LINE-HEIGHT: normal">=, ; 的使用要值得注意. 再就是调用函数宏是要注意, 不要多给出";".</p>
</span></span>
<img src ="http://www.cppblog.com/bellgrade/aggbug/110030.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/bellgrade/" target="_blank">bellgrade</a> 2010-03-18 17:48 <a href="http://www.cppblog.com/bellgrade/archive/2010/03/18/110030.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>VC 线程同步</title><link>http://www.cppblog.com/bellgrade/archive/2009/10/17/98838.html</link><dc:creator>bellgrade</dc:creator><author>bellgrade</author><pubDate>Sat, 17 Oct 2009 10:01:00 GMT</pubDate><guid>http://www.cppblog.com/bellgrade/archive/2009/10/17/98838.html</guid><wfw:comment>http://www.cppblog.com/bellgrade/comments/98838.html</wfw:comment><comments>http://www.cppblog.com/bellgrade/archive/2009/10/17/98838.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/bellgrade/comments/commentRss/98838.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/bellgrade/services/trackbacks/98838.html</trackback:ping><description><![CDATA[<strong>线程同步<br><br></strong>　　在程序中使用多线程时，一般很少有多个线程能在其生命期内进行完全独立的操作。更多的情况是一些线程进行某些处理操作，而其他的线程必须对其处理结果进行了解。正常情况下对这种处理结果的了解应当在其处理任务完成后进行。<br><br>　　如果不采取适当的措施，其他线程往往会在线程处理任务结束前就去访问处理结果，这就很有可能得到有关处理结果的错误了解。例如，多个线程同时访问同一个全局变量，如果都是读取操作，则不会出现问题。如果一个线程负责改变此变量的值，而其他线程负责同时读取变量内容，则不能保证读取到的数据是经过写线程修改后的。<br><br>　　为了确保读线程读取到的是经过修改的变量，就必须在向变量写入数据时禁止其他线程对其的任何访问，直至赋值过程结束后再解除对其他线程的访问限制。象这种保证线程能了解其他线程任务处理结束后的处理结果而采取的保护措施即为线程同步。<br><br>　　线程同步是一个非常大的话题，包括方方面面的内容。从大的方面讲，线程的同步可分用户模式的线程同步和内核对象的线程同步两大类。用户模式中线程的同步方法主要有原子访问和临界区等方法。其特点是同步速度特别快，适合于对线程运行速度有严格要求的场合。<br><br>　　内核对象的线程同步则主要由事件、等待定时器、信号量以及信号灯等内核对象构成。由于这种同步机制使用了内核对象，使用时必须将线程从用户模式切换到内核模式，而这种转换一般要耗费近千个CPU周期，因此同步速度较慢，但在适用性上却要远优于用户模式的线程同步方式。
<p><font size=3><strong>临界区<br><br></strong>　　临界区（Critical Section）是一段独占对某些共享资源访问的代码，在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区，那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起，并一直持续到进入临界区的线程离开。临界区在被释放后，其他线程可以继续抢占，并以此达到用原子方式操作共享资源的目的。<br><br>　　临界区在使用时以CRITICAL_SECTION结构对象保护共享资源，并分别用 EnterCriticalSection（）和LeaveCriticalSection（）函数去标识和释放一个临界区。所用到的 CRITICAL_SECTION结构对象必须经过InitializeCriticalSection（）的初始化后才能使用，而且必须确保所有线程中的任何试图访问此共享资源的代码都处在此临界区的保护之下。否则临界区将不会起到应有的作用，共享资源依然有被破坏的可能。<br><br><img onerror=this.src=&#8217;http://www.yesky.com/image20010518/110093.jpg&#8217;; hspace=3 src="http://www.yesky.com/image20010518/110093.jpg" align=middle vspace=1 border=1><br>图1 使用临界区保持线程同步<br><br>　　下面通过一段代码展示了临界区在保护多线程访问的共享资源中的作用。通过两个线程来分别对全局变量g_cArray[10]进行写入操作，用临界区结构对象g_cs来保持线程的同步，并在开启线程前对其进行初始化。为了使实验效果更加明显，体现出临界区的作用，在线程函数对共享资源g_cArray [10]的写入时，以Sleep（）函数延迟1毫秒，使其他线程同其抢占CPU的可能性增大。如果不使用临界区对其进行保护，则共享资源数据将被破坏（参见图1（a）所示计算结果），而使用临界区对线程保持同步后则可以得到正确的结果（参见图1（b）所示计算结果）。代码实现清单附下：<br><br></font></p>
<p>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>// 临界区结构对象<br>CRITICAL_SECTION g_cs;<br>// 共享资源 <br>char g_cArray[10];<br>UINT ThreadProc10(LPVOID pParam)<br>{<br>　// 进入临界区<br>　EnterCriticalSection(&amp;g_cs);<br>　// 对共享资源进行写入操作<br>　for (int i = 0; i &lt; 10; i++)<br>　{<br>　　g_cArray[i] = &#8217;a&#8217;;<br>　　Sleep(1);<br>　}<br>　// 离开临界区<br>　LeaveCriticalSection(&amp;g_cs);<br>　return 0;<br>}<br>UINT ThreadProc11(LPVOID pParam)<br>{<br>　// 进入临界区<br>　EnterCriticalSection(&amp;g_cs);<br>　// 对共享资源进行写入操作<br>　for (int i = 0; i &lt; 10; i++)<br>　{<br>　　g_cArray[10 - i - 1] = &#8217;b&#8217;;<br>　　Sleep(1);<br>　}<br>　// 离开临界区<br>　LeaveCriticalSection(&amp;g_cs);<br>　return 0;<br>}<br>&#8230;&#8230;<br>void CSample08View::OnCriticalSection() <br>{<br>　// 初始化临界区<br>　InitializeCriticalSection(&amp;g_cs);<br>　// 启动线程<br>　AfxBeginThread(ThreadProc10, NULL);<br>　AfxBeginThread(ThreadProc11, NULL);<br>　// 等待计算完毕<br>　Sleep(300);<br>　// 报告计算结果<br>　CString sResult = CString(g_cArray);<br>　AfxMessageBox(sResult);<br>}</td>
        </tr>
    </tbody>
</table>
</p>
<p><br>　　在使用临界区时，一般不允许其运行时间过长，只要进入临界区的线程还没有离开，其他所有试图进入此临界区的线程都会被挂起而进入到等待状态，并会在一定程度上影响。程序的运行性能。尤其需要注意的是不要将等待用户输入或是其他一些外界干预的操作包含到临界区。如果进入了临界区却一直没有释放，同样也会引起其他线程的长时间等待。换句话说，在执行了EnterCriticalSection（）语句进入临界区后无论发生什么，必须确保与之匹配的LeaveCriticalSection（）都能够被执行到。可以通过添加结构化异常处理代码来确保LeaveCriticalSection （）语句的执行。虽然临界区同步速度很快，但却只能用来同步本进程内的线程，而不可用来同步多个进程中的线程。<br><br>　　MFC为临界区提供有一个CCriticalSection类，使用该类进行线程同步处理是非常简单的，只需在线程函数中用CCriticalSection类成员函数 Lock（）和UnLock（）标定出被保护代码片段即可。对于上述代码，可通过CCriticalSection类将其改写如下：<br><br></p>
<p>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>// MFC临界区类对象<br>CCriticalSection g_clsCriticalSection;<br>// 共享资源 <br>char g_cArray[10];<br>UINT ThreadProc20(LPVOID pParam)<br>{<br>　// 进入临界区<br>　g_clsCriticalSection.Lock();<br>　// 对共享资源进行写入操作<br>　for (int i = 0; i &lt; 10; i++)<br>　{<br>　　g_cArray[i] = &#8217;a&#8217;;<br>　　Sleep(1);<br>　}<br>　// 离开临界区<br>　g_clsCriticalSection.Unlock();<br>　return 0;<br>}<br>UINT ThreadProc21(LPVOID pParam)<br>{<br>　// 进入临界区<br>　g_clsCriticalSection.Lock();<br>　// 对共享资源进行写入操作<br>　for (int i = 0; i &lt; 10; i++)<br>　{<br>　　g_cArray[10 - i - 1] = &#8217;b&#8217;;<br>　　Sleep(1);<br>　}<br>　// 离开临界区<br>　g_clsCriticalSection.Unlock();<br>　return 0;<br>}<br>&#8230;&#8230;<br>void CSample08View::OnCriticalSectionMfc() <br>{<br>　// 启动线程<br>　AfxBeginThread(ThreadProc20, NULL);<br>　AfxBeginThread(ThreadProc21, NULL);<br>　// 等待计算完毕<br>　Sleep(300);<br>　// 报告计算结果<br>　CString sResult = CString(g_cArray);<br>　AfxMessageBox(sResult);<br>}</td>
        </tr>
    </tbody>
</table>
</p>
<p><font size=3><strong>管理事件内核对象<br><br></strong>　　在前面讲述线程通信时曾使用过事件内核对象来进行线程间的通信，除此之外，事件内核对象也可以通过通知操作的方式来保持线程的同步。对于前面那段使用临界区保持线程同步的代码可用事件对象的线程同步方法改写如下：<br><br></font>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>// 事件句柄<br>HANDLE hEvent = NULL;<br>// 共享资源 <br>char g_cArray[10];<br>&#8230;&#8230;<br>UINT ThreadProc12(LPVOID pParam)<br>{<br>　// 等待事件置位<br>　WaitForSingleObject(hEvent, INFINITE);<br>　// 对共享资源进行写入操作<br>　for (int i = 0; i &lt; 10; i++)<br>　{<br>　　g_cArray[i] = &#8217;a&#8217;;<br>　　Sleep(1);<br>　}<br>　// 处理完成后即将事件对象置位<br>　SetEvent(hEvent);<br>　return 0;<br>}<br>UINT ThreadProc13(LPVOID pParam)<br>{<br>　// 等待事件置位<br>　WaitForSingleObject(hEvent, INFINITE);<br>　// 对共享资源进行写入操作<br>　for (int i = 0; i &lt; 10; i++)<br>　{<br>　　g_cArray[10 - i - 1] = &#8217;b&#8217;;<br>　　Sleep(1);<br>　}<br>　// 处理完成后即将事件对象置位<br>　SetEvent(hEvent);<br>　return 0;<br>}<br>&#8230;&#8230;<br>void CSample08View::OnEvent() <br>{<br>　// 创建事件<br>　hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);<br>　// 事件置位<br>　SetEvent(hEvent);<br>　// 启动线程<br>　AfxBeginThread(ThreadProc12, NULL);<br>　AfxBeginThread(ThreadProc13, NULL);<br>　// 等待计算完毕<br>　Sleep(300);<br>　// 报告计算结果<br>　CString sResult = CString(g_cArray);<br>　AfxMessageBox(sResult);<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　在创建线程前，首先创建一个可以自动复位的事件内核对象hEvent，而线程函数则通过WaitForSingleObject（）等待函数无限等待 hEvent的置位，只有在事件置位时WaitForSingleObject（）才会返回，被保护的代码将得以执行。对于以自动复位方式创建的事件对象，在其置位后一被WaitForSingleObject（）等待到就会立即复位，也就是说在执行ThreadProc12（）中的受保护代码时，事件对象已经是复位状态的，这时即使有ThreadProc13（）对CPU的抢占，也会由于WaitForSingleObject（）没有hEvent的置位而不能继续执行，也就没有可能破坏受保护的共享资源。在ThreadProc12（）中的处理完成后可以通过SetEvent（）对hEvent的置位而允许ThreadProc13（）对共享资源g_cArray的处理。这里SetEvent（）所起的作用可以看作是对某项特定任务完成的通知。<br><br>　　使用临界区只能同步同一进程中的线程，而使用事件内核对象则可以对进程外的线程进行同步，其前提是得到对此事件对象的访问权。可以通过OpenEvent（）函数获取得到，其函数原型为：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>HANDLE OpenEvent(<br>　DWORD dwDesiredAccess, // 访问标志<br>　BOOL bInheritHandle, // 继承标志<br>　LPCTSTR lpName // 指向事件对象名的指针<br>);</td>
        </tr>
    </tbody>
</table>
<br>　　如果事件对象已创建（在创建事件时需要指定事件名），函数将返回指定事件的句柄。对于那些在创建事件时没有指定事件名的事件内核对象，可以通过使用内核对象的继承性或是调用DuplicateHandle（）函数来调用CreateEvent（）以获得对指定事件对象的访问权。在获取到访问权后所进行的同步操作与在同一个进程中所进行的线程同步操作是一样的。<br><br>　　如果需要在一个线程中等待多个事件，则用 WaitForMultipleObjects（）来等待。WaitForMultipleObjects（）与WaitForSingleObject （）类似，同时监视位于句柄数组中的所有句柄。这些被监视对象的句柄享有平等的优先权，任何一个句柄都不可能比其他句柄具有更高的优先权。 WaitForMultipleObjects（）的函数原型为：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>DWORD WaitForMultipleObjects(<br>　DWORD nCount, // 等待句柄数<br>　CONST HANDLE *lpHandles, // 句柄数组首地址<br>　BOOL fWaitAll, // 等待标志<br>　DWORD dwMilliseconds // 等待时间间隔<br>);</td>
        </tr>
    </tbody>
</table>
<br>　　参数nCount指定了要等待的内核对象的数目，存放这些内核对象的数组由lpHandles来指向。fWaitAll对指定的这nCount个内核对象的两种等待方式进行了指定，为TRUE时当所有对象都被通知时函数才会返回，为FALSE则只要其中任何一个得到通知就可以返回。 dwMilliseconds在这里的作用与在WaitForSingleObject（）中的作用是完全一致的。如果等待超时，函数将返回 WAIT_TIMEOUT。如果返回WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1中的某个值，则说明所有指定对象的状态均为已通知状态（当fWaitAll为TRUE时）或是用以减去WAIT_OBJECT_0而得到发生通知的对象的索引（当fWaitAll为FALSE 时）。如果返回值在WAIT_ABANDONED_0与WAIT_ABANDONED_0+nCount-1之间，则表示所有指定对象的状态均为已通知，且其中至少有一个对象是被丢弃的互斥对象（当fWaitAll为TRUE时），或是用以减去WAIT_OBJECT_0表示一个等待正常结束的互斥对象的索引（当fWaitAll为FALSE时）。下面给出的代码主要展示了对WaitForMultipleObjects（）函数的使用。通过对两个事件内核对象的等待来控制线程任务的执行与中途退出：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>// 存放事件句柄的数组<br>HANDLE hEvents[2];<br>UINT ThreadProc14(LPVOID pParam)<br>{ <br>　// 等待开启事件<br>　DWORD dwRet1 = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE);<br>　// 如果开启事件到达则线程开始执行任务<br>　if (dwRet1 == WAIT_OBJECT_0)<br>　{<br>　　AfxMessageBox("线程开始工作!");<br>　　while (true)<br>　　{<br>　　　for (int i = 0; i &lt; 10000; i++);<br>　　　// 在任务处理过程中等待结束事件 <br>　　　DWORD dwRet2 = WaitForMultipleObjects(2, hEvents, FALSE, 0);<br>　　　// 如果结束事件置位则立即终止任务的执行<br>　　　if (dwRet2 == WAIT_OBJECT_0 + 1)<br>　　　　break;<br>　　}<br>　}<br>　AfxMessageBox("线程退出!");<br>　return 0;<br>}<br>&#8230;&#8230;<br>void CSample08View::OnStartEvent() <br>{<br>　// 创建线程<br>　for (int i = 0; i &lt; 2; i++)<br>　　hEvents[i] = CreateEvent(NULL, FALSE, FALSE, NULL);<br>　　// 开启线程<br>　　AfxBeginThread(ThreadProc14, NULL);<br>　　// 设置事件0(开启事件)<br>　　SetEvent(hEvents[0]);<br>}<br>void CSample08View::OnEndevent() <br>{<br>　// 设置事件1(结束事件)<br>　SetEvent(hEvents[1]);<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　MFC为事件相关处理也提供了一个CEvent类，共包含有除构造函数外的4个成员函数PulseEvent（）、ResetEvent（）、 SetEvent（）和UnLock（）。在功能上分别相当与Win32 API的PulseEvent（）、ResetEvent（）、SetEvent（）和CloseHandle（）等函数。而构造函数则履行了原 CreateEvent（）函数创建事件对象的职责，其函数原型为：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>CEvent(BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );</td>
        </tr>
    </tbody>
</table>
<br>　　按照此缺省设置将创建一个自动复位、初始状态为复位状态的没有名字的事件对象。封装后的CEvent类使用起来更加方便，图2即展示了CEvent类对A、B两线程的同步过程：<br><br><img onerror=this.src=&#8217;http://www.yesky.com/image20010518/110094.jpg&#8217;; hspace=3 src="http://www.yesky.com/image20010518/110094.jpg" align=middle vspace=1 border=1><br>图2 CEvent类对线程的同步过程示意<br><br>　　B线程在执行到CEvent类成员函数Lock（）时将会发生阻塞，而A线程此时则可以在没有B线程干扰的情况下对共享资源进行处理，并在处理完成后通过成员函数SetEvent（）向B发出事件，使其被释放，得以对A先前已处理完毕的共享资源进行操作。可见，使用CEvent类对线程的同步方法与通过 API函数进行线程同步的处理方法是基本一致的。前面的API处理代码可用CEvent类将其改写为：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>// MFC事件类对象<br>CEvent g_clsEvent;<br>UINT ThreadProc22(LPVOID pParam)<br>{<br>　// 对共享资源进行写入操作<br>　for (int i = 0; i &lt; 10; i++)<br>　{<br>　　g_cArray[i] = &#8217;a&#8217;;<br>　　Sleep(1);<br>　}<br>　// 事件置位<br>　g_clsEvent.SetEvent();<br>　return 0;<br>}<br>UINT ThreadProc23(LPVOID pParam)<br>{<br>　// 等待事件<br>　g_clsEvent.Lock();<br>　// 对共享资源进行写入操作<br>　for (int i = 0; i &lt; 10; i++)<br>　{<br>　　g_cArray[10 - i - 1] = &#8217;b&#8217;;<br>　　Sleep(1);<br>　}<br>　return 0;<br>}<br>&#8230;&#8230;<br>void CSample08View::OnEventMfc() <br>{<br>　// 启动线程<br>　AfxBeginThread(ThreadProc22, NULL);<br>　AfxBeginThread(ThreadProc23, NULL);<br>　// 等待计算完毕<br>　Sleep(300);<br>　// 报告计算结果<br>　CString sResult = CString(g_cArray);<br>　AfxMessageBox(sResult);<br>}</td>
        </tr>
    </tbody>
</table>
</p>
<font size=3><strong>信号量内核对象<br><br></strong>　　信号量（Semaphore）内核对象对线程的同步方式与前面几种方法不同，它允许多个线程在同一时刻访问同一资源，但是需要限制在同一时刻访问此资源的最大线程数目。在用 CreateSemaphore（）创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数，每增加一个线程对共享资源的访问，当前可用资源计数就会减1，只要当前可用资源计数是大于0的，就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目，不能在允许其他线程的进入，此时的信号量信号将无法发出。线程在处理完共享资源后，应在离开的同时通过 ReleaseSemaphore（）函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。<br><br><img onerror=this.src=&#8217;http://www.yesky.com/image20010518/110095.jpg&#8217;; hspace=3 src="http://www.yesky.com/image20010518/110095.jpg" align=middle vspace=1 border=1><br>图3 使用信号量对象控制资源<br><br>　　下面结合图例3来演示信号量对象对资源的控制。在图3中，以箭头和白色箭头表示共享资源所允许的最大资源计数和当前可用资源计数。初始如图（a）所示，最大资源计数和当前可用资源计数均为4，此后每增加一个对资源进行访问的线程（用黑色箭头表示）当前资源计数就会相应减1，图（b）即表示的在3个线程对共享资源进行访问时的状态。当进入线程数达到4个时，将如图（c）所示，此时已达到最大资源计数，而当前可用资源计数也已减到0，其他线程无法对共享资源进行访问。在当前占有资源的线程处理完毕而退出后，将会释放出空间，图（d）已有两个线程退出对资源的占有，当前可用计数为2，可以再允许2个线程进入到对资源的处理。可以看出，信号量是通过计数来对线程访问资源进行控制的，而实际上信号量确实也被称作Dijkstra计数器。<br><br>　　使用信号量内核对象进行线程同步主要会用到CreateSemaphore（）、OpenSemaphore（）、ReleaseSemaphore（）、 WaitForSingleObject（）和WaitForMultipleObjects（）等函数。其中，CreateSemaphore（）用来创建一个信号量内核对象，其函数原型为：<br><br></font>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>HANDLE CreateSemaphore(<br>　LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全属性指针<br>　LONG lInitialCount, // 初始计数<br>　LONG lMaximumCount, // 最大计数<br>　LPCTSTR lpName // 对象名指针<br>); </td>
        </tr>
    </tbody>
</table>
<br>　　参数lMaximumCount是一个有符号32位值，定义了允许的最大资源计数，最大取值不能超过4294967295。lpName参数可以为创建的信号量定义一个名字，由于其创建的是一个内核对象，因此在其他进程中可以通过该名字而得到此信号量。OpenSemaphore（）函数即可用来根据信号量名打开在其他进程中创建的信号量，函数原型如下：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>HANDLE OpenSemaphore(<br>　DWORD dwDesiredAccess, // 访问标志<br>　BOOL bInheritHandle, // 继承标志<br>　LPCTSTR lpName // 信号量名<br>);</td>
        </tr>
    </tbody>
</table>
<br>　　在线程离开对共享资源的处理时，必须通过ReleaseSemaphore（）来增加当前可用资源计数。否则将会出现当前正在处理共享资源的实际线程数并没有达到要限制的数值，而其他线程却因为当前可用资源计数为0而仍无法进入的情况。ReleaseSemaphore（）的函数原型为：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>BOOL ReleaseSemaphore(<br>　HANDLE hSemaphore, // 信号量句柄<br>　LONG lReleaseCount, // 计数递增数量<br>　LPLONG lpPreviousCount // 先前计数<br>);</td>
        </tr>
    </tbody>
</table>
<br>　　该函数将lReleaseCount中的值添加给信号量的当前资源计数，一般将lReleaseCount设置为1，如果需要也可以设置其他的值。 WaitForSingleObject（）和WaitForMultipleObjects（）主要用在试图进入共享资源的线程函数入口处，主要用来判断信号量的当前可用资源计数是否允许本线程的进入。只有在当前可用资源计数值大于0时，被监视的信号量内核对象才会得到通知。<br><br>　　信号量的使用特点使其更适用于对Socket（套接字）程序中线程的同步。例如，网络上的HTTP服务器要对同一时间内访问同一页面的用户数加以限制，这时可以为没一个用户对服务器的页面请求设置一个线程，而页面则是待保护的共享资源，通过使用信号量对线程的同步作用可以确保在任一时刻无论有多少用户对某一页面进行访问，只有不大于设定的最大用户数目的线程能够进行访问，而其他的访问企图则被挂起，只有在有用户退出对此页面的访问后才有可能进入。下面给出的示例代码即展示了类似的处理过程：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>// 信号量对象句柄<br>HANDLE hSemaphore;<br>UINT ThreadProc15(LPVOID pParam)<br>{ <br>　// 试图进入信号量关口<br>　WaitForSingleObject(hSemaphore, INFINITE);<br>　// 线程任务处理<br>　AfxMessageBox("线程一正在执行!");<br>　// 释放信号量计数<br>　ReleaseSemaphore(hSemaphore, 1, NULL);<br>　return 0;<br>}<br>UINT ThreadProc16(LPVOID pParam)<br>{ <br>　// 试图进入信号量关口<br>　WaitForSingleObject(hSemaphore, INFINITE);<br>　// 线程任务处理<br>　AfxMessageBox("线程二正在执行!");<br>　// 释放信号量计数<br>　ReleaseSemaphore(hSemaphore, 1, NULL);<br>　return 0;<br>}<br>UINT ThreadProc17(LPVOID pParam)<br>{ <br>　// 试图进入信号量关口<br>　WaitForSingleObject(hSemaphore, INFINITE);<br>　// 线程任务处理<br>　AfxMessageBox("线程三正在执行!");<br>　// 释放信号量计数<br>　ReleaseSemaphore(hSemaphore, 1, NULL);<br>　return 0;<br>}<br>&#8230;&#8230;<br>void CSample08View::OnSemaphore() <br>{<br>　// 创建信号量对象<br>　hSemaphore = CreateSemaphore(NULL, 2, 2, NULL);<br>　// 开启线程<br>　AfxBeginThread(ThreadProc15, NULL);<br>　AfxBeginThread(ThreadProc16, NULL);<br>　AfxBeginThread(ThreadProc17, NULL);<br>}</td>
        </tr>
    </tbody>
</table>
<br><img onerror=this.src=&#8217;http://www.yesky.com/image20010518/110096.jpg&#8217;; hspace=3 src="http://www.yesky.com/image20010518/110096.jpg" align=middle vspace=1 border=1><br>图4 开始进入的两个线程<br><br><img onerror=this.src=&#8217;http://www.yesky.com/image20010518/110097.jpg&#8217;; hspace=3 src="http://www.yesky.com/image20010518/110097.jpg" align=middle vspace=1 border=1><br>图5 线程二退出后线程三才得以进入<br><br>　　上述代码在开启线程前首先创建了一个初始计数和最大资源计数均为2的信号量对象hSemaphore。即在同一时刻只允许2个线程进入由 hSemaphore保护的共享资源。随后开启的三个线程均试图访问此共享资源，在前两个线程试图访问共享资源时，由于hSemaphore的当前可用资源计数分别为2和1，此时的hSemaphore是可以得到通知的，也就是说位于线程入口处的WaitForSingleObject（）将立即返回，而在前两个线程进入到保护区域后，hSemaphore的当前资源计数减少到0，hSemaphore将不再得到通知， WaitForSingleObject（）将线程挂起。直到此前进入到保护区的线程退出后才能得以进入。图4和图5为上述代脉的运行结果。从实验结果可以看出，信号量始终保持了同一时刻不超过2个线程的进入。<br><br>　　在MFC中，通过CSemaphore类对信号量作了表述。该类只具有一个构造函数，可以构造一个信号量对象，并对初始资源计数、最大资源计数、对象名和安全属性等进行初始化，其原型如下：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );</td>
        </tr>
    </tbody>
</table>
<br>　　在构造了CSemaphore类对象后，任何一个访问受保护共享资源的线程都必须通过CSemaphore从父类CSyncObject类继承得到的 Lock（）和UnLock（）成员函数来访问或释放CSemaphore对象。与前面介绍的几种通过MFC类保持线程同步的方法类似，通过 CSemaphore类也可以将前面的线程同步代码进行改写，这两种使用信号量的线程同步方法无论是在实现原理上还是从实现结果上都是完全一致的。下面给出经MFC改写后的信号量线程同步代码：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>// MFC信号量类对象<br>CSemaphore g_clsSemaphore(2, 2);<br>UINT ThreadProc24(LPVOID pParam)<br>{ <br>　// 试图进入信号量关口<br>　g_clsSemaphore.Lock();<br>　// 线程任务处理<br>　AfxMessageBox("线程一正在执行!");<br>　// 释放信号量计数<br>　g_clsSemaphore.Unlock();<br>　return 0;<br>}<br>UINT ThreadProc25(LPVOID pParam)<br>{<br>　// 试图进入信号量关口<br>　g_clsSemaphore.Lock();<br>　// 线程任务处理<br>　AfxMessageBox("线程二正在执行!");<br>　// 释放信号量计数<br>　g_clsSemaphore.Unlock();<br>　return 0;<br>}<br>UINT ThreadProc26(LPVOID pParam)<br>{<br>　// 试图进入信号量关口<br>　g_clsSemaphore.Lock();<br>　// 线程任务处理<br>　AfxMessageBox("线程三正在执行!");<br>　// 释放信号量计数<br>　g_clsSemaphore.Unlock();<br>　return 0;<br>}<br>&#8230;&#8230;<br>void CSample08View::OnSemaphoreMfc() <br>{<br>　// 开启线程<br>　AfxBeginThread(ThreadProc24, NULL);<br>　AfxBeginThread(ThreadProc25, NULL);<br>　AfxBeginThread(ThreadProc26, NULL);<br>}</td>
        </tr>
    </tbody>
</table>
<font size=3><strong>互斥内核对象<br><br></strong>　　互斥（Mutex）是一种用途非常广泛的内核对象。能够保证多个线程对同一共享资源的互斥访问。同临界区有些类似，只有拥有互斥对象的线程才具有访问资源的权限，由于互斥对象只有一个，因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出，以便其他线程在获得后得以访问资源。与其他几种内核对象不同，互斥对象在操作系统中拥有特殊代码，并由操作系统来管理，操作系统甚至还允许其进行一些其他内核对象所不能进行的非常规操作。为便于理解，可参照图6给出的互斥内核对象的工作模型：<br><br><img onerror=this.src=&#8217;http://www.yesky.com/image20010518/110098.jpg&#8217;; hspace=3 src="http://www.yesky.com/image20010518/110098.jpg" align=middle vspace=1 border=1><br>图6 使用互斥内核对象对共享资源的保护<br><br>　　图（a）中的箭头为要访问资源（矩形框）的线程，但只有第二个线程拥有互斥对象（黑点）并得以进入到共享资源，而其他线程则会被排斥在外（如图（b）所示）。当此线程处理完共享资源并准备离开此区域时将把其所拥有的互斥对象交出（如图（c）所示），其他任何一个试图访问此资源的线程都有机会得到此互斥对象。<br><br>　　以互斥内核对象来保持线程同步可能用到的函数主要有CreateMutex（）、OpenMutex（）、 ReleaseMutex（）、WaitForSingleObject（）和WaitForMultipleObjects（）等。在使用互斥对象前，首先要通过CreateMutex（）或OpenMutex（）创建或打开一个互斥对象。CreateMutex（）函数原型为：<br><br></font>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>HANDLE CreateMutex(<br>　LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性指针<br>　BOOL bInitialOwner, // 初始拥有者<br>　LPCTSTR lpName // 互斥对象名<br>);</td>
        </tr>
    </tbody>
</table>
<br>　　参数bInitialOwner主要用来控制互斥对象的初始状态。一般多将其设置为FALSE，以表明互斥对象在创建时并没有为任何线程所占有。如果在创建互斥对象时指定了对象名，那么可以在本进程其他地方或是在其他进程通过OpenMutex（）函数得到此互斥对象的句柄。OpenMutex（）函数原型为：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>HANDLE OpenMutex(<br>　DWORD dwDesiredAccess, // 访问标志<br>　BOOL bInheritHandle, // 继承标志<br>　LPCTSTR lpName // 互斥对象名<br>); </td>
        </tr>
    </tbody>
</table>
<br>　　当目前对资源具有访问权的线程不再需要访问此资源而要离开时，必须通过ReleaseMutex（）函数来释放其拥有的互斥对象，其函数原型为：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>BOOL ReleaseMutex(HANDLE hMutex);</td>
        </tr>
    </tbody>
</table>
<br>　　其唯一的参数hMutex为待释放的互斥对象句柄。至于WaitForSingleObject（）和WaitForMultipleObjects（）等待函数在互斥对象保持线程同步中所起的作用与在其他内核对象中的作用是基本一致的，也是等待互斥内核对象的通知。但是这里需要特别指出的是：在互斥对象通知引起调用等待函数返回时，等待函数的返回值不再是通常的WAIT_OBJECT_0（对于WaitForSingleObject（）函数）或是在 WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1之间的一个值（对于WaitForMultipleObjects（）函数），而是将返回一个WAIT_ABANDONED_0（对于WaitForSingleObject（）函数）或是在WAIT_ABANDONED_0 到WAIT_ABANDONED_0+nCount-1之间的一个值（对于WaitForMultipleObjects（）函数）。以此来表明线程正在等待的互斥对象由另外一个线程所拥有，而此线程却在使用完共享资源前就已经终止。除此之外，使用互斥对象的方法在等待线程的可调度性上同使用其他几种内核对象的方法也有所不同，其他内核对象在没有得到通知时，受调用等待函数的作用，线程将会挂起，同时失去可调度性，而使用互斥的方法却可以在等待的同时仍具有可调度性，这也正是互斥对象所能完成的非常规操作之一。<br><br>　　在编写程序时，互斥对象多用在对那些为多个线程所访问的内存块的保护上，可以确保任何线程在处理此内存块时都对其拥有可靠的独占访问权。下面给出的示例代码即通过互斥内核对象hMutex对共享内存快g_cArray[]进行线程的独占访问保护。下面给出实现代码清单：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>// 互斥对象<br>HANDLE hMutex = NULL;<br>char g_cArray[10];<br>UINT ThreadProc18(LPVOID pParam)<br>{<br>　// 等待互斥对象通知<br>　WaitForSingleObject(hMutex, INFINITE);<br>　// 对共享资源进行写入操作<br>　for (int i = 0; i &lt; 10; i++)<br>　{<br>　　g_cArray[i] = &#8217;a&#8217;;<br>　　Sleep(1);<br>　}<br>　// 释放互斥对象<br>　ReleaseMutex(hMutex);<br>　return 0;<br>}<br>UINT ThreadProc19(LPVOID pParam)<br>{<br>　// 等待互斥对象通知<br>　WaitForSingleObject(hMutex, INFINITE);<br>　// 对共享资源进行写入操作<br>　for (int i = 0; i &lt; 10; i++)<br>　{<br>　　g_cArray[10 - i - 1] = &#8217;b&#8217;;<br>　　Sleep(1);<br>　}<br>　// 释放互斥对象<br>　ReleaseMutex(hMutex);<br>　return 0;<br>}<br>&#8230;&#8230;<br>void CSample08View::OnMutex() <br>{<br>　// 创建互斥对象<br>　hMutex = CreateMutex(NULL, FALSE, NULL);<br>　// 启动线程<br>　AfxBeginThread(ThreadProc18, NULL);<br>　AfxBeginThread(ThreadProc19, NULL);<br>　// 等待计算完毕<br>　Sleep(300);<br>　// 报告计算结果<br>　CString sResult = CString(g_cArray);<br>　AfxMessageBox(sResult);<br>} </td>
        </tr>
    </tbody>
</table>
<br>　　互斥对象在MFC中通过CMutex类进行表述。使用CMutex类的方法非常简单，在构造CMutex类对象的同时可以指明待查询的互斥对象的名字，在构造函数返回后即可访问此互斥变量。CMutex类也是只含有构造函数这唯一的成员函数，当完成对互斥对象保护资源的访问后，可通过调用从父类 CSyncObject继承的UnLock（）函数完成对互斥对象的释放。CMutex类构造函数原型为：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );</td>
        </tr>
    </tbody>
</table>
<br>　　该类的适用范围和实现原理与API方式创建的互斥内核对象是完全类似的，但要简洁的多，下面给出就是对前面的示例代码经CMutex类改写后的程序实现清单：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>// MFC互斥类对象<br>CMutex g_clsMutex(FALSE, NULL);<br>UINT ThreadProc27(LPVOID pParam)<br>{<br>　// 等待互斥对象通知<br>　g_clsMutex.Lock();<br>　// 对共享资源进行写入操作<br>　for (int i = 0; i &lt; 10; i++)<br>　{<br>　　g_cArray[i] = &#8217;a&#8217;;<br>　　Sleep(1);<br>　}<br>　// 释放互斥对象<br>　g_clsMutex.Unlock();<br>　return 0;<br>}<br>UINT ThreadProc28(LPVOID pParam)<br>{<br>　// 等待互斥对象通知<br>　g_clsMutex.Lock();<br>　// 对共享资源进行写入操作<br>　for (int i = 0; i &lt; 10; i++)<br>　{<br>　　g_cArray[10 - i - 1] = &#8217;b&#8217;;<br>　　Sleep(1);<br>　}<br>　// 释放互斥对象<br>　g_clsMutex.Unlock();<br>　return 0;<br>}<br>&#8230;&#8230;<br>void CSample08View::OnMutexMfc() <br>{<br>　// 启动线程<br>　AfxBeginThread(ThreadProc27, NULL);<br>　AfxBeginThread(ThreadProc28, NULL);<br>　// 等待计算完毕<br>　Sleep(300);<br>　// 报告计算结果<br>　CString sResult = CString(g_cArray);<br>　AfxMessageBox(sResult);<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　<strong>小结</strong><br><br>　　线程的使用使程序处理更够更加灵活，而这种灵活同样也会带来各种不确定性的可能。尤其是在多个线程对同一公共变量进行访问时。虽然未使用线程同步的程序代码在逻辑上或许没有什么问题，但为了确保程序的正确、可靠运行，必须在适当的场合采取线程同步措施。 
<img src ="http://www.cppblog.com/bellgrade/aggbug/98838.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/bellgrade/" target="_blank">bellgrade</a> 2009-10-17 18:01 <a href="http://www.cppblog.com/bellgrade/archive/2009/10/17/98838.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++对象的内存布局</title><link>http://www.cppblog.com/bellgrade/archive/2009/10/16/98787.html</link><dc:creator>bellgrade</dc:creator><author>bellgrade</author><pubDate>Fri, 16 Oct 2009 12:11:00 GMT</pubDate><guid>http://www.cppblog.com/bellgrade/archive/2009/10/16/98787.html</guid><wfw:comment>http://www.cppblog.com/bellgrade/comments/98787.html</wfw:comment><comments>http://www.cppblog.com/bellgrade/archive/2009/10/16/98787.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/bellgrade/comments/commentRss/98787.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/bellgrade/services/trackbacks/98787.html</trackback:ping><description><![CDATA[写这个文章完全是因为想要搞清楚 vc 怎么布局每个 c++ 对象,以及怎样完成指针的转换的过程.<br>　　先问一个问题,两个不同类型的指针相互转换以后,他们在数值上是一样的吗?比如:<br><br>　　　　<font color=#000066>int nValue = 10;<br>　　　　int *pInt = &amp;nValue;<br>　　　　void *pVoid = pInt;<br>　　　　char *pChar = (char*)pInt;</font><br><br>　　这些指针的值(不是说指针指向的内存的内容)是一样的吗? 如果你的回答是 yes,那如果是一个类的继承体系呢?在继承类向基类转换的过程中,指针的数值还是不变化的么?如果你的回答是"不一定会变化,要看类的体系是怎么设计的"的话,那恭喜你,不用看下去了.如果你还不确定究竟变还是不变,究竟哪些变,哪些不变,究竟为什么要变为什么不变的话,接着看下来.<br><br>　　c++ 标准不规定 c++ 实现的时候的对象的具体的内存布局,除了在某些方面有小的限制以外,c++ 对象在内存里面的布局完全是由编译器自行决定,这里我也只是讨论 vc++ .net 2003 build 7.1.3091 的实现方式,我并没有在 vc5 vc6 vc.net 2002 以及其他的 2003 build 上面做过测试,结论也许不适合那些编译平台.这些属于编译器具体实现,ms 保留有在不通知你我的情况下作出更改的权利.废话这么多,马上开始.<br><br>　　对于 c 的内建指针的转换,结果是不用多讨论的,我们只是讨论 c++ 的对象.从最简单的开始.<br><br>　　　　<font color=#000066>class CBase<br>　　　　{<br>　　　　public:<br>　　　　　　int m_nBaseValue;<br>　　　　};</font><br><br>　　这样的一个类在内存里放置是非常简单的,他占有4个 bytes 的空间,不用多说,我们从他派生一个类出来.<br><br>　　　　<font color=#000066>class CDerive1 : public CBase<br>　　　　{<br>　　　　public:<br>　　　　　　int m_nDerive1Value;<br>　　　　};</font><br><br>　　CDerive1 的对象在内存里面是怎么放的呢? 也很简单,占有8个 bytes 的空间,前4个 bytes 属于 CBase 类,后四个 bytes 属于自己.一个CDerive1 的指针转换成一个 CBase 的指针,结果是一样的.下面我们加上多重继承看看.<br><br>　　　　<font color=#000066>class CFinal : public CDerive,public CBase <font color=#ff0000>// 这里的 CDerive 是一个和 CBase 差不多的基类</font><br>　　　　{<br>　　　　public:<br>　　　　　　int m_nFinalValue;<br>　　　　}; </font><br><br>　　CFinal 的对象在内存里面的布局稍微复杂一点,但是也很容易想象,他占有 12 个 bytes 的空间,前4个属于 CDerive,中间4个属于 CBase,后面4个才是自己的.那一个 CFinal 的指针转换成一个 CDerive 指针,数值会变么? 转换成一个 CBase 指针呢?又会变化么?答案是,前一个不变,后一个要变化,道理非常的明显,CFinal 对象的开头刚好是一个 CDerive 对象,而 CBase 对象却在 CFinal 对象的中间,自然是要变化的了,具体怎么变化呢? 加 4 就 ok(自然要检查是否是空指针).<br><br>　　　　<font color=#ff0000>CBase *pBase = pFinal ? (CBase*)((char*)pFinal + sizeof(CDerive)) : 0;// 当你写下 pBase = pFinal 的时候,其实是这样的</font><br><br>　　这种不带 virtual 的继承就这么简单,只是加上一个 offset 而已.下面我们看看如果加上 virtual function 的时候是什么样子的呢?<br>还是从简单类开始.<br><br>　　　　<font color=#000066>class CBase<br>　　　　{<br>　　　　public:<br>　　　　　　virtual void VirtualBaseFunction(){}<br>　　　　　　int m_nBaseValue;<br>　　　　}; </font><br><br>　　这里刻意没有使用 virtual destructor,因为这个函数稍微有些不同.还是同样的问题,CBase 类在内存上占多大的空间?还是 4 bytes 么? 答案是 no, 在我的编译器上面是 8 bytes,多出来的 4 bytes 是 __vfptr(watch 窗口看见的名字),他是一个指针,指向了类的 vtable,那什么是 vtable 呢,他是用来干什么的呢? vtable 是用来支援 virtual function 机制的,他其实是一个函数指针数组(并不等同于c/c++语言里面的指针数组,因为他们的类型并不一定是一样的.)他的每一个元素都指向了一个你定义的 virtual function,这样通过一个中间层来到达动态连编的效果,这些指针是在程序运行的时候准备妥当的,而不是在编译的时候准备妥当的,这个就是动态联编的目的,具体是由谁来设置这些指针的呢?constructor/destructor/copy constructor/assignment operator他们完成的,不用奇怪,编译器会在你写的这些函数里面安插些必要的代码用来设置 vtable 的值,如果你没有写这些函数,编译器会在适当的时候帮你生成这些函数.明白一点, vtable 是用来支持 virtual function 机制的,而需要 virtual 机制的类基本上都会由一个 __vfptr 指向他自己的 vtable.在调用 virtual function的时候,编译器这样完成:<br><br>　　　<font color=#000099>pBase-&gt;VirtualBaseFunction(); =&gt; pBase-&gt;__vfptr[0]();</font><font color=#ff0000>// 0 是你的virtual function 在 vtable 中的 slot number,编译器决定</font><br><br>　　现在应该很想象 CBase 的大小了吧,那这个 __vfptr 是放到什么位置的呢? 在 m_nBaseValue 之前还是之后呢? 在我的编译器上看来,是在之前,为什么要放到之前,是因为在通过 指向类成员函数的指针调用 virtual function 的时候能少些代码(指汇编代码),这个原因这里就不深入讨论了,有兴趣的同学可以看看 inside the c++ object model 一书.<br>　　接下来,我们加上继承来看看.<br><br>　　　　<font color=#000099>class CDerive1 : public CBase<br>　　　　{<br>　　　　public:<br>　　　　　　virtual void VirtualDerive1Function();<br>　　　　};</font><br><br>　　这个时候你也许要说,内存布局跟没有 virtual 是一样的,只不过每个类多了一个 __vfptr 而已,呃...这个是不对的,在我的编译器上面 两个类共享同一个 __vfptr, vtable 里面放有两个指针,一个是两个类共享的,一个只属于 CDerive1 类,调用的时候如何呢?<br><br>　　　<font color=#000099>pDerive1-&gt;VirtualDerive1Function() =&gt; pDerive1-&gt;__vfptr[1]();<br>　　　pDerive1-&gt;VirtualBaseFunction() =&gt; pDerive1-&gt;__vfptr[0]();</font><br><br>　　至于指针的相互转换,数值还是没有变化的(也正是追求这种效果,所以把 __vfptr 放到类的开头,因为调整 this 指针也是要占有运行时的时间的).<br><br>　　现在加上多重继承瞧瞧,代码我不写上来了,就跟上面的 CFinal, CDerive, CBase 体系一样,只是每个类多一个VirtualxxxFunction出来,这个时候的指针调整还是没有什么变化,所以我们只是看看 vtable 的情况,你会说 CDerive 和 CFinal 共享一个 __vfptr,而 CBase 有一个自己的 __vfptr,而 CFinal 的 __vfptr 有 2 个slot,这个结论是正确的. 同时你也会说 通过 CFinal 类调用 CBase 的函数是要进行指针调整的,yes you'r right,不仅仅是 this 指针调整(呃,this 指针会成为 function 的一个参数),还要调整 vtable 的值:<br><br>　　　<font color=#0000cc>pFinal-&gt;VirtualBaseFunction() =&gt; (CBase*)((char*)pFinal + sizeof(CDerive))-&gt;__vfptr[0]();</font><br><br>　　　转换成 asm 的代码大约是这样的:<br><br><font color=#000099>　　　mov eax,[pFinal] <font color=#ff00ff>; pFinal is a local object,pFinal will be epb - xx</font><br>　　　add eax,8 <font color=#ff00ff>; 8 = sizeof(CDerive)</font><br>　　　mov ecx,eax <font color=#ff00ff>; ecx is this pointer</font><br>　　　mov edx,[eax] <font color=#ff00ff>; edx = vtable address</font><br>　　　call [edx] <font color=#ff00ff>; call vtable[0]</font></font><br><br>　　写到这里也就明白this指针是怎么调整的.带 virtual function 的继承也不复杂,this指针调整也是很简单的,下面看最复杂的部分 virtual inheritance.<br><br>　　我的编译器支持虚拟继承的方式和虚函数的方式差不多,都是通过一个 table 完成,只是这个就看不到 vc 赋予的名字了,我们叫他 vbtable 吧,编译器同样在类里面加入一个指向 vbtable 的指针,我们叫他 __vbptr 吧,这个指针指向了 vbtable ,而 vbtable 里面的每一项对应了一个基类,vbtable 记录了每个基类的某一个偏移量,通过这个偏移量就能计算出具体类的指针的位置.看个简单的例子:<br><br>　　　<font color=#000099>class CBase<br>　　　{<br>　　　public:<br>　　　　　virtual ~CBase(){}<br>　　　}; <br><br>　　　class CMid1 : public virtual CBase<br>　　　{<br>　　　public:<br>　　　　　virtual ~CMid1(){}<br>　　　　　int m_nMid1;<br>　　　}; <br><br>　　　class CMid2 : public virtual CBase<br>　　　{<br>　　　public:<br>　　　　　virtual ~CMid2(){}<br>　　　　　int m_nMid2;<br>　　　}; <br><br>　　　class CFinal : public CMid1,public CMid2<br>　　　{<br>　　　public:<br>　　　　　virtual ~CFinal(){}<br>　　　　　int m_nFinal;<br>　　　}; <br><br>　　　CFinal final;<br>　　　CFinal *pFinal = &amp;final;&nbsp;&nbsp;&nbsp; <font color=#ff00ff>// pFinal = 0x0012feb4;</font><br>　　　CBase *pBase = pFinal; <font color=#ff00ff>// pBase = 0x0012fec8 = pFinal + 0x14;</font><br>　　　CMid1 *pMid1 = pFinal; <font color=#ff00ff>// pMid1 = 0x0012feb4 = pFinal;</font><br>　　　CMid2 *pMid2 = pFinal; <font color=#ff00ff>// pMid2 = 0x004210b4 = pFinal;</font></font><br><br>　　结果让你吃惊吗? 最奇怪的地方居然是 CMid2 和 CMid1 的地址居然是一样的,这个是因为 vc 把 vbtable 放到了 CFinal 类的开头的原因,而CMid1 和 CMid2 也同样要使用这个 vbtable, 所以 这个三个的地址也就必须相同了.那 CBase 的地址是怎么出来的呢? 呃...刚刚我们说了 vbtable 放到了CFinal 的开头(vc 一定会放在开头吗?答案是不一定,这个稍后解释).在我的机器上面 final 对应内存的第一个 dword 是 0x00426030,查看这个地址,第一个dword 是 0 ,第二个就是 0x14,刚好和 pBase 的偏移相同,这个只是巧合,也许你换个类的继承体系就完全不同了,但是我只是想说明一点,基类的偏移计算是和 vbtable 的值相关联的.下面我们就来看看 vc 是怎么计算这些偏移的.<br>　　vc 在分析我们的代码的时候,生成了一份类的继承体系信息,其中有一个叫 thisDisplacement 的_PMD结构:<br><br>　　　　<font color=#000099>struct _PMD <font color=#ff00ff>// total undocumented</font><br>　　　　{<br>　　　　　　int mdisp; <font color=#ff00ff>// i think the meaning is Multiinheritance DISPlacement</font><br>　　　　　　int pdisp; <font color=#ff00ff>// Pointer to vbtable DISPlacement</font><br>　　　　　　int vdisp; <font color=#ff00ff>// Vbtable DISPlacement</font><br>　　　　}; </font><br><br>　　结构的名字和成员变量的名字确确实实是 vc 的名字(在 watch 窗口输入 (_PMD*)0 就能看到这个结构的详细信息),每个字段的含义却是我自己猜测出来的.mdisp 大概用来表示多重继承(包括单一继承)的时候的偏移量,pdisp 表示 vbtable 的偏移量,而 vdisp 表示类在 vbtable 里面的下标.那么有了这个结构怎样才能完成指针的转换呢?假如我们有一个派生类指针 pFinal,要转换成一个特定的基础类,我们首先要知道和这个基类对应的 _PMD 结构的信息(这个信息的获取,我暂时没有找到一个非常方便的方法,现在我使用的方法下面会有描述),有了这个信息以后,转换就方便了.首先找到 vbtabel 的地址 *(pFinal + pdisp),然后找到基类的偏移 *(*(pFinal + pdisp) + vdisp) 这个偏移值是相对vbtable的,所以还要加上 vbtable的偏移,最后加上 mdisp的偏移,如下:<br><br>　　<font color=#000099>char *pFinal = xxx; <font color=#ff00ff>// need a init value</font><br>　　char *pBase; <font color=#ff00ff>// we must calc</font><br>　　pBase = pFinal + mdisp + *(int *)(*(int *)(pFinal + pdisp) + vdisp) + pdisp;</font><br><br>　　<font color=#ff0000>注意: 当 pdisp &lt; 0 的时候就表示这个类没有 vbtable 直接使用 pFinal + mdisp 就得到结果了.<br>　　<font color=#00ff00>所以这个结构是一个通用的结构,专门用作类型转换,不管是有无虚继承都能使用这个结构进行类型转换.</font></font><br>　　通过这个结构,我们也能看到 vc 是怎样布局这个 object 的.<br><br>　　看到这里,也许你要大呼一口气,妈妈呀,一个类型转换要这么的麻烦吗?我直接写 pBase = pFinal 不就可以了吗? 恭喜你还没有被我忽悠得晕头转向,哈哈.其实你写下那行语句的时候,编译器在帮你做这个转换,大约生成下面的代码<br><br>　　　　<font color=#000099>mov eax,[pFinal] <font color=#ff00ff>;final address</font><br>　　　　mov ecx,[eax] <font color=#ff00ff>; vbtable address *(int *)(pFinal + pdisp)</font><br>　　　　mov edx,eax <font color=#ff00ff>; save to edx</font><br>　　　　add edx,[ecx + 4] <font color=#ff00ff>; ecx + 4 is (*(int *)(pFinal + pdisp) + vdisp)</font><br>　　　　mov [pBase],edx <font color=#ff00ff>; edx = pFinal + mdisp + *(int *)(*(int *)(pFinal + pdisp) + vdisp) + pdisp;</font><br>　　　　<font color=#ff00ff>; here mdisp = 0, pdisp = 0, vdisp = 4</font></font><br><br>　　也许你要说了,我要这些东西来干什么?要转换的时候直接转换就好了,编译器会帮做,的确,大多数的时候确实是这样,但是,在某些时候却并不如此,现在你要实现一个功能,输入一个指针,输入一个 _PMD 结构,你要实现一个AdjustPointer 的函数来生成另一个指针.这个时候你也只能这样完成了,因为我没有给你两个指针的名字,就算给了你字符串形式的名字也没有用,呃....你也许会说,办法是有的,的确是有,模板就能实现这种功能,呵..这个我们暂时不讨论具体的实现细节.也许你要问了,究竟什么时候会去实现这种听都没有听过的功能,其实这个函数是真正存在的,只不过不是由你来实现的,而是 ms 的人实现的,你只用写一个 带有 c++ 异常的程序,使用 ida 反汇编,然后查找函数,就能找到这个函数了,他用来在异常处理时创建 catch 所需要的 object.至于这个详细的信息,请期待.我会最快速度写出关于 vc 是怎样实现 c++ 异常的文章来.<br><br>　　最后了,说说那个 _PMD 结构的获取方式.看的时候不要吃惊,方法比较的麻烦,比如我想知道和 CFinal 类相关的 _PMD 信息,先新建工作,写下 throw pFinal 这样的语句,编译,在这个语句的地方设置断点,运行,转到反汇编,进入 __CxxThrowException@8 函数,这个时候不出意外你能看到一个叫 pThrowInfo 的东西(如果看不到,请打开"显示符号名"选项),在 watch 窗口里面输入pThrowInfo,展开他,看到一个pCatchableTypeArray,记录下他的 nCacthableTypes的值,然后在 watch 里面输入<br>pThrowInfo-&gt;pCatchableTypeArray-&gt;arrayOfCatchableTypes[0] 到 pThrowInfo-&gt;pCatchableTypeArray-&gt;arrayOfCatchableTypes[n], n 就是你刚刚记录的值减1,再展开他们,你就能看到一个 thisDisplacement 的数据,继续展开就是 mdisp 等等了,很是麻烦吧.哈..你已经猜到了,这个是和异常有关系的.<br>
<img src ="http://www.cppblog.com/bellgrade/aggbug/98787.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/bellgrade/" target="_blank">bellgrade</a> 2009-10-16 20:11 <a href="http://www.cppblog.com/bellgrade/archive/2009/10/16/98787.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>笔试常见智力题 附答案</title><link>http://www.cppblog.com/bellgrade/archive/2009/10/16/98786.html</link><dc:creator>bellgrade</dc:creator><author>bellgrade</author><pubDate>Fri, 16 Oct 2009 11:56:00 GMT</pubDate><guid>http://www.cppblog.com/bellgrade/archive/2009/10/16/98786.html</guid><wfw:comment>http://www.cppblog.com/bellgrade/comments/98786.html</wfw:comment><comments>http://www.cppblog.com/bellgrade/archive/2009/10/16/98786.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/bellgrade/comments/commentRss/98786.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/bellgrade/services/trackbacks/98786.html</trackback:ping><description><![CDATA[&nbsp;
<p><span>A.</span><span>逻辑推理</span><span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1</span><span>、你让工人为你工作</span><span>7</span><span>天，给工人的回报是一根金条。金条平分成相连的</span><span>7</span><span>段</span><span> <br></span><span>，你必须在每天结束时给他们一段金条，如果只许你两次把金条弄断，你如何给你</span><span> <br></span><span>的工人付费？</span><span> <br></span><span>　　</span><span>2</span><span>、请把一盒蛋糕切成</span><span>8</span><span>份，分给</span><span>8</span><span>个人，但蛋糕盒里还必须留有一份。</span><span><br></span><span>　　</span><span>3</span><span>、小明一家过一座桥，过桥时是黑夜，所以必须有灯。现在小明过桥要</span><span>1</span><span>秒，</span><span> <br></span><span>小明的弟弟要</span><span>3</span><span>秒，小明的爸爸要</span><span>6</span><span>秒，小明的妈妈要</span><span>8</span><span>秒，小明的爷爷要</span><span>12</span><span>秒。每</span><span> <br></span><span>次此桥最多可过两人，而过桥的速度依过桥最慢者而定，而且灯在点燃后</span><span>30</span><span>秒就会</span><span> <br></span><span>熄灭。问：小明一家如何过桥？</span><span><br></span><span>　　</span><span>4</span><span>、一群人开舞会，每人头上都戴着一顶帽子。帽子只有黑白两种，黑的至少</span><span> <br></span><span>有一顶。每个人都能看到其他人帽子的颜色，却看不到自己的。主持人先让大家看</span><span> <br></span><span>看别人头上戴的是什么帽子，然后关灯，如果有人认为自己戴的是黑帽子，就打自</span><span> <br></span><span>己一个耳光。第一次关灯，没有声音。于是再开灯，大家再看一遍，关灯时仍然鸦</span><span> <br></span><span>雀无声。一直到第三次关灯，才有劈劈啪啪打耳光的声音响起。问有多少人戴着黑</span><span> </span><span>帽子？</span><span> <br></span><span>　　</span><span>5</span><span>、请估算一下ＣＮ　ＴＯＷＥＲ电视塔的质量。</span><span> <br></span><span>　　</span><span>6</span><span>、一楼到十楼的每层电梯门口都放着一颗钻石，钻石大小不一。你乘坐电梯</span><span> </span><span>从一楼到十楼，每层楼电梯门都会打开一次，只能拿一次钻石，问怎样才能拿到最大的一颗？</span><span><br></span><span>　　</span><span>7</span><span>、</span><span>U2</span><span>合唱团在</span><span>17</span><span>分钟内得赶到演唱会场，途中必需跨过一座桥，四个人从桥</span><span> </span><span>的同一端出发，你得帮助他们到达另一端，天色很暗，而他们只有一只手电筒。一次同时最多可以有两人一起过桥，而过桥的时候必须持有手电筒，所以就得有人把</span><span> </span><span>手电筒带来带去，来回桥两端。手电筒是不能用丢的方式来传递的。四个人的步行</span><span> <br></span><span>速度各不同，若两人同行则以较慢者的速度为准。</span><span>Bono</span><span>需花</span><span>1</span><span>分钟过桥，</span><span>Edge</span><span>需花</span><span> 2</span><span>分钟过桥，</span><span>Adam</span><span>需花</span><span>5</span><span>分钟过桥，</span><span>Larry</span><span>需花</span><span>10</span><span>分钟过桥。他们要如何在</span><span>17</span><span>分钟内</span><span> </span><span>过桥呢？</span><span> <br></span><span>　　</span><span>8</span><span>、烧一根不均匀的绳要用一个小时，如何用它来判断半个小时</span><span> ?<br></span><span>　　</span><span>9</span><span>、为什么下水道的盖子是圆的？</span><span> </span></p>
<p><span>10</span><span>、美国有多少辆加油站（汽车）？</span><span> <br></span><span>　　</span><span>11</span><span>、有</span><span>7</span><span>克、</span><span>2</span><span>克砝码各一个，天平一只，如何只用这些物品三次将</span><span>140</span><span>克的盐</span><span> </span><span>分成</span><span>50</span><span>、</span><span>90</span><span>克各一份？</span><span> <br></span><span>　　</span><span>12</span><span>、有一辆火车以每小时</span><span>15</span><span>公里的速度离开洛杉矶直奔纽约，另一辆火车以第小时</span><span>20</span><span>公里的速度从纽约开往洛杉矶。如果有一只鸟，以外</span><span>30</span><span>公里每小时的速度和</span><span> </span><span>两辆火车现时启动，从洛杉矶出发，碰到另辆车后返回，依次在两辆火车来回的飞行，直道两面辆火车相遇，请问，这只小鸟飞行了多长距离？</span><span> <br></span><span>　　</span><span>13</span><span>、你有两个罐子，</span><span>50</span><span>个红色弹球，</span><span>50</span><span>个蓝色弹球，随机选出一个罐子，随机</span><span> </span><span>选取出一个弹球放入罐子，怎么给红色弹球最大的选中机会？在你的计划中，得到</span><span> </span><span>红球的准确几率是多少？</span><span> <br></span><span>　　</span><span>14</span><span>、想象你在镜子前，请问，为什么镜子中的影像可以颠倒左右，却不能颠倒</span><span> </span><span>上下？</span><span> <br></span><span>　　</span><span>15</span><span>、你有四人装药丸的罐子，每个药丸都有一定的重量，被污染的药丸是没被</span><span> </span><span>污染的重量</span><span>+1.</span><span>只称量一次，如何判断哪个罐子的药被污染了？</span><span> <br></span><span>　　</span><span>16</span><span>、如果你有无穷多的水，一个</span><span>3</span><span>夸脱的和一个</span><span>5</span><span>夸脱的提桶，你如何准确称出</span><span> 4</span><span>夸脱的水？</span><span> <br></span><span>　　</span><span>17</span><span>、你有一桶果冻，其中有黄色，绿色，红色三种，，闭上眼睛选出同样颜色</span><span> </span><span>的两个，抓取同种颜色的两个。抓取多少个就可以确定你肯定有两个同一颜色的果冻？</span><span> <br></span><span>　　</span><span>18</span><span>、将汽车钥匙插入车门，向哪个方向旋转就可以打开车锁？</span><span><br></span><span>　　</span><span>19</span><span>、如果要你能去掉</span><span>50</span><span>个州的任何一个，那你去掉哪一个，为什么？</span><span> <br></span><span>　　</span><span>20</span><span>、对一批编号为</span><span>1~100 </span><span>全部开关朝上开的灯进行以下操作</span><span> <br></span><span>凡是</span><span>1 </span><span>的倍数反方向拨一次开关</span><span>2 </span><span>的倍数反方向又拨一次开关</span><span>3 </span><span>的倍数反方向</span><span> </span><span>又拨一次开关。</span><span> <br></span><span>　　问最后为关熄状态的灯的编号。</span><span> <br></span><span>　　</span><span>21</span><span>、假设一张圆盘像唱机上的唱盘那样转动。这张盘一半是黑色，一半是白色</span><span> </span><span>。假设你有数量不限的一些颜色传感器。要想确定圆盘转动的方向，你需要在它周围摆多少个颜色传感器？它们应该被摆放在什么位置？</span><span> <br></span><span>　　</span><span>22</span><span>、假设时钟到了</span><span>12</span><span>点。注意时针和分针重叠在一起。在一天之中，时针和分针共重叠多少次？你知道它们重叠时的具体时间吗？</span><span> <br></span><span>　　</span><span>23</span><span>、中间只隔一个数字的两个奇数被称为奇数对，比如</span><span>17</span><span>和</span><span>19</span><span>。证明奇数对之</span><span> </span><span>间的数字总能被</span><span>6</span><span>整除（假设这两个奇数都大于</span><span>6</span><span>）。现在证明没有由三个奇数组成</span><span> </span><span>的奇数对。</span><span><br></span><span>　　</span><span>24</span><span>、一个屋子有一个门（门是关闭的）和</span><span>3</span><span>盏电灯。屋外有</span><span>3</span><span>个开关，分别与这</span><span> 3</span><span>盏灯相连。你可以随意操纵这些开关，可一旦你将门打开，就不能变换开关了。确定每个开关具体管哪盏灯。</span><span> <br></span><span>　　</span><span>25</span><span>、假设你有</span><span>8</span><span>个球，其中一个略微重一些，但是找出这个球的惟一方法是将两个球放在天平上对比。最少要称多少次才能找出这个较重的球？</span><span><br></span><span>　　</span><span>26</span><span>、下面玩一个拆字游戏，所有字母的顺序都被打乱。你要判断这个字是什么</span><span> </span><span>。假设这个被拆开的字由</span><span>5</span><span>个字母组成：</span><span> <br></span><span>　　　　</span><span>1.</span><span>共有多少种可能的组合方式？</span><span> <br></span><span>　　　　</span><span>2.</span><span>如果我们知道是哪</span><span>5</span><span>个字母，那会怎么样？</span><span> <br></span><span>　　　　</span><span>3.</span><span>找出一种解决这个问题的方法。</span><span> <br></span><span>　　</span><span>27</span><span>、有</span><span>4</span><span>个女人要过一座桥。她们都站在桥的某一边，要让她们在</span><span>17</span><span>分钟内全</span><span> </span><span>部通过这座桥。这时是晚上。她们只有一个手电筒。最多只能让两个人同时过桥。不管是谁过桥，不管是一个人还是两个人，必须要带着手电筒。手电筒必须要传来传去，不能扔过去。每个女人过桥的速度不同，两个人的速度必须以较慢的那个人</span><span> </span><span>的速度过桥。</span><span> <br></span><span>　　第一个女人：过桥需要</span><span>1</span><span>分钟；</span><span> <br></span><span>　　第二个女人：过桥需要</span><span>2</span><span>分钟；</span><span> <br></span><span>　　第三个女人：过桥需要</span><span>5</span><span>分钟；</span><span> <br></span><span>　　第四个女人：过桥需要</span><span>10</span><span>分钟。</span><span> <br></span><span>　　比如，如果第一个女人与第</span><span>4</span><span>个女人首先过桥，等她们过去时，已经过去了</span><span>10 </span><span>分钟。如果让第</span><span>4</span><span>个女人将手电筒送回去，那么等她到达桥的另一端时，总共用去了</span><span>20</span><span>分钟，行动也就失败了。怎样让这</span><span>4</span><span>个女人在</span><span>17</span><span>分钟内过桥？还有别的什么方</span><span> </span><span>法？</span><span> <br></span><span>　　</span><span>28</span><span>、如果你有两个桶，一个装的是红色的颜料，另一个装的是蓝色的颜料。你</span><span> </span><span>从蓝色颜料桶里舀一杯，倒入红色颜料桶，再从红色颜料桶里舀一杯倒入蓝颜料桶。两个桶中红蓝颜料的比例哪个更高？通过算术的方式来证明这一点。</span><span> <br>B</span><span>：疯狂计算</span><span> <br></span><span>　　</span><span>29</span><span>、已知两个</span><span>1~30</span><span>之间的数字，甲知道两数之和，乙知道两数之积。</span><span> <br></span><span>　　甲问乙：</span><span>"</span><span>你知道是哪两个数吗？</span><span>"</span><span>乙说：</span><span>"</span><span>不知道</span><span>"</span><span>；</span><span> <br></span><span>　　乙问甲：</span><span>"</span><span>你知道是哪两个数吗？</span><span>"</span><span>甲说：</span><span>"</span><span>也不知道</span><span>"</span><span>；</span><span> <br></span><span>　　于是，乙说：</span><span>"</span><span>那我知道了</span><span>"</span><span>；</span><span> <br></span><span>　　随后甲也说：</span><span>"</span><span>那我也知道了</span><span>"</span><span>；</span><span> <br></span><span>　　这两个数是什么？</span><span><br></span><span>　　</span><span>30</span><span>、</span><span>4</span><span>，</span><span>4</span><span>，</span><span>10</span><span>，</span><span>10</span><span>，加减乘除，怎么出</span><span>24</span><span>点？</span><span> <br></span><span>　　</span><span>31</span><span>、</span><span>1000!</span><span>有几位数，为什么？</span><span> <br></span><span>　　</span><span>32</span><span>、</span><span>F(n)=1 n&gt;8 n&lt;12 <br></span><span>　　</span><span>F(n)=2 n&lt;2 <br></span><span>　　</span><span>F(n)=3 n=6 <br></span><span>　　</span><span>F(n)=4 n=other <br></span><span>　　使用</span><span>+ - * /</span><span>和</span><span>sign(n)</span><span>函数组合出</span><span>F(n)</span><span>函数</span><span> <br></span><span>　　</span><span>sign(n)=0 n=0 <br></span><span>　　</span><span>sign(n)=-1 n&lt;0 <br></span><span>　　</span><span>sign(n)=1 n&gt;0 <br></span><span>　　</span><span>33</span><span>、编一个程序求质数的和例如</span><span>F(7)=1+3+5+7+11+13+17=58 <br></span><span>　　</span><span>34</span><span>、。。。</span><span> <br></span><span>　　请仅用一支笔画四根直线将上图</span><span>9 </span><span>各点全部连接</span><span> <br></span><span>　　</span><span>35</span><span>、三层四层二叉树有多少种</span><span> <br></span><span>　　</span><span>36</span><span>、</span><span>1--100000 </span><span>数列按一定顺序排列，有一个数字排错，如何纠错？写出最好方法。两个数字呢？</span><span> <br></span><span><strong>　　参考答案</strong></span><span><br>&nbsp; 1</span><span>、</span><span>day1 </span><span>给</span><span>1 </span><span>段，</span><span> <br></span><span>　　</span><span>day2 </span><span>让工人把</span><span>1 </span><span>段归还给</span><span>2 </span><span>段，</span><span> <br></span><span>　　</span><span>day3 </span><span>给</span><span>1 </span><span>段，</span><span> <br></span><span>　　</span><span>day4 </span><span>归还</span><span>1 2 </span><span>段，给</span><span>4 </span><span>段。</span><span> <br></span><span>　　</span><span>day5 </span><span>依次类推</span><span>&#8230;&#8230; <br></span><span>　　</span><span>2</span><span>、面对这样的怪题，有些应聘者绞尽脑汁也无法分成；而有些应聘者却感到</span><span> <br></span><span>此题实际很简单，把切成的</span><span>8</span><span>份蛋糕先拿出</span><span>7</span><span>份分给</span><span>7</span><span>人，剩下的</span><span>1</span><span>份连蛋糕盒一起分</span><span> <br></span><span>给第</span><span>8</span><span>个人。</span><span><br></span><span>　　</span><span>4</span><span>、假如只有一个人戴黑帽子，那他看到所有人都戴白帽，在第一次关灯时就</span><span> <br></span><span>应自打耳光，所以应该不止一个人戴黑帽子；如果有两顶黑帽子，第一次两人都只</span><span> <br></span><span>看到对方头上的黑帽子，不敢确定自己的颜色，但到第二次关灯，这两人应该明白</span><span> <br></span><span>，如果自己戴着白帽，那对方早在上一次就应打耳光了，因此自己戴的也是黑帽子</span><span> <br></span><span>，于是也会有耳光声响起；可事实是第三次才响起了耳光声，说明全场不止两顶黑</span><span> <br></span><span>帽，依此类推，应该是关了几次灯，有几顶黑帽。</span><span> <br></span><span>　　</span><span>5</span><span>、比如你怎样快速估算支架和柱子的高度、球的半径，算出各部分的体积等</span><span> <br></span><span>等。招聘官的说法：</span><span>"</span><span>就</span><span>CNTOWER</span><span>这道题来说，它和一般的谜语或智力题还是有区别</span><span> <br></span><span>的。我们称这类题为</span><span>&#8217;</span><span>快速估算题</span><span>&#8217;</span><span>，主要考的是快速估算的能力，这是开发软件</span><span> <br></span><span>必备的能力之一。当然，题目只是手段，不是目的，最终得到一个结果固然是需要</span><span> <br></span><span>的，但更重要的是对考生得出这个结果的过程也就是方法的考察。</span><span>"Mr Miller</span><span>为记</span><span> <br></span><span>者举例说明了一种比较合理的答法，他首先在纸上画出了</span><span>CN TOWER</span><span>的草图，然后快</span><span> <br></span><span>速估算支架和各柱的高度，以及球的半径，算出各部分体积，然后和各部分密度运</span><span> <br></span><span>算，最后相加得出一个结果。</span><span> <br></span><span>　　这一类的题目其实很多，如：</span><span>"</span><span>估算一下密西西比河里的水的质量。</span><span>""</span><span>如果你</span><span> <br></span><span>是田纳西州州长，请估算一下治理好康柏兰河的污染需要多长时间。</span><span>" <br></span><span>　　</span><span>"</span><span>估算一下一个行进在小雨中的人</span><span>5</span><span>分钟内身上淋到的雨的质量。</span><span>" <br></span><span>　　</span><span>Mr Miller</span><span>接着解释道：</span><span>"</span><span>像这样的题目，包括一些推理题，考的都是人的</span><span> <span><br>ProblemSolving(</span></span><span>解决问题的能力</span><span>)</span><span>，不是哪道题你记住了答案就可以了的。</span><span>" <br></span><span>　　对于公司招聘的宗旨，</span><span>Mr Miller</span><span>强调了四点，这些是有创造性的公司普遍注</span><span> <br></span><span>重的员工素质，是想要到知名企业实现自己的事业梦想的人都要具备的素质和能力</span><span> </span><span>。</span><span> <br></span><span>　　要求一：</span><span>RawSmart</span><span>（纯粹智慧），与知识无关。</span><span> <br></span><span>　　要求二：</span><span>Long-termPotential(</span><span>长远学习能力</span><span>)</span><span>。</span><span> <br></span><span>　　要求三：</span><span>TechnicSkills(</span><span>技能</span><span>)</span><span>。</span><span> <br></span><span>　　要求四：</span><span>Professionalism(</span><span>职业态度</span><span>)</span><span>。</span><span> <br></span><span>　　</span><span>6</span><span>、她的回答是：选择前五层楼都不拿，观察各层钻石的大小，做到心中有数</span><span> <br></span><span>。后五层楼再选择，选择大小接近前五层楼出现过最大钻石大小的钻石。她至今也</span><span> <br></span><span>不知道这道题的准确答案，</span><span>"</span><span>也许就没有准确答案，就是考一下你的思路，</span><span>"</span><span>她如是</span><span> <br></span><span>说。</span><span> <br></span><span>　　</span><span>7</span><span>、分析：有个康奈尔的学生写文章说他当时在微软面试时就是碰到了这道题</span><span> <br></span><span>，最短只能做出在</span><span>19</span><span>分钟内过桥。</span><span> <br></span><span>　　</span><span>8</span><span>、两边一起烧。</span><span> <br></span><span>　　</span><span>9</span><span>、答案之一：从麻省理工大学一位计算机系教授那里听来的答案，首先在同</span><span> <br></span><span>等用材的情况下他的面积最大。第二因为如果是方的、长方的或椭圆的，那无聊之</span><span> <br></span><span>徒拎起来它就可以直接扔进地下道啦！但圆形的盖子嘛，就可以避免这种情况了</span><span> <br></span><span>　　</span><span>10</span><span>、这个乍看让人有些摸不着头脑的问题时，你可能要从问这个国家有多少小</span><span> <br></span><span>汽车入手。面试者也许会告诉你这个数字，但也有可能说：</span><span>"</span><span>我不知道，你来告诉</span><span> <br></span><span>我。</span><span>"</span><span>那么，你对自己说，美国的人口是</span><span>2.75</span><span>亿。你可以猜测，如果平均每个家庭</span><span> <br></span><span>（包括单身）的规模是</span><span>2.5</span><span>人，你的计算机会告诉你，共有</span><span>1.1</span><span>亿个家庭。你回忆起</span><span> <br></span><span>在什么地方听说过，平均每个家庭拥有</span><span>1.8</span><span>辆小汽车，那么美国大约会有</span><span>1.98</span><span>亿辆</span><span> <br></span><span>小汽车。接着，只要你算出替</span><span>1.98</span><span>亿辆小汽车服务需要多少加油站，你就把问题解</span><span> <br></span><span>决了。重要的不是加油站的数字，而是你得出这个数字的方法。</span><span> <br></span><span>　　</span><span>12</span><span>、答案很容易计算的：</span><span> <br></span><span>　　假设洛杉矶到纽约的距离为</span><span>s <br></span><span>　　那小鸟飞行的距离就是</span><span>(s/(15+20))*30</span><span>。</span><span> <br></span><span>　　</span><span>13</span><span>、无答案，看你有没有魄力坚持自己的意见。</span><span> <br></span><span>　　</span><span>14</span><span>、因为人的两眼在水平方向上对称。</span><span> <br></span><span>　　</span><span>15</span><span>、从第一盒中取出一颗，第二盒中取出</span><span>2 </span><span>颗，第三盒中取出三颗。</span><span> <br></span><span>　　依次类推，称其总量。</span><span> <br></span><span>　　</span><span>16</span><span>、比较复杂：</span><span> <br></span><span>　　Ａ、先用</span><span>3 </span><span>夸脱的桶装满，倒入</span><span>5 </span><span>夸脱。以下简称</span><span>3-&gt;5) <br></span><span>　　在</span><span>5 </span><span>夸脱桶中做好标记</span><span>b1</span><span>，简称</span><span>b1)</span><span>。</span><span> <br></span><span>　　</span><span>B</span><span>、用</span><span>3 </span><span>继续装水倒满</span><span>5 </span><span>空</span><span>3 </span><span>将</span><span>5 </span><span>中水倒入</span><span>3 </span><span>直到</span><span>b1 </span><span>在</span><span>3 </span><span>中做标记</span><span>b2 <br></span><span>　　Ｃ、用</span><span>5 </span><span>继续装水倒满</span><span>3 </span><span>空</span><span>5 </span><span>将</span><span>3 </span><span>中水倒入</span><span>5 </span><span>直到</span><span>b2 <br></span><span>　　Ｄ、空</span><span>3 </span><span>将</span><span>5 </span><span>中水倒入</span><span>3 </span><span>标记为</span><span>b3 <br></span><span>　　Ｅ、装满</span><span>5 </span><span>空</span><span>3 </span><span>将</span><span>5 </span><span>中水倒入</span><span>3 </span><span>直到</span><span>3 </span><span>中水到</span><span>b3 <br></span><span>　　结束了，现在</span><span>5 </span><span>中水为标准的</span><span>4 </span><span>夸脱水。</span><span> <br></span><span>　　</span><span>20</span><span>、素数是关，其余是开。</span><span> <br></span><span>　　</span><span>29</span><span>、允许两数重复的情况下</span><span> <br></span><span>　　答案为</span><span>x=1</span><span>，</span><span>y=4</span><span>；甲知道和</span><span>A=x+y=5</span><span>，乙知道积</span><span>B=x*y=4 <br></span><span>　　不允许两数重复的情况下有两种答案</span><span> <br></span><span>　　答案</span><span>1</span><span>：为</span><span>x=1</span><span>，</span><span>y=6</span><span>；甲知道和</span><span>A=x+y=7</span><span>，乙知道积</span><span>B=x*y=6 <br></span><span>　　答案</span><span>2</span><span>：为</span><span>x=1</span><span>，</span><span>y=8</span><span>；甲知道和</span><span>A=x+y=9</span><span>，乙知道积</span><span>B=x*y=8 <br></span><span>　　解：</span><span> <br></span><span>　　设这两个数为</span><span>x</span><span>，</span><span>y. <br></span><span>　　甲知道两数之和</span><span> A=x+y</span><span>；</span><span> <br></span><span>　　乙知道两数之积</span><span> B=x*y</span><span>；</span><span> <br></span><span>　　该题分两种情况</span><span> </span><span>：</span><span> <br></span><span>　　允许重复，</span><span> </span><span>有</span><span>(1 &lt;= x &lt;= y &lt;= 30)</span><span>；</span><span> <br></span><span>　　不允许重复，有</span><span>(1 &lt;= x &lt; y &lt;= 30)</span><span>；</span><span> <br></span><span>　　当不允许重复，即</span><span>(1 &lt;= x &lt; y &lt;= 30)</span><span>；</span><span> <br></span><span>　　</span><span>1)</span><span>由题设条件：乙不知道答案</span><span> <br></span><span>　　</span><span>&lt;=&gt; B=x*y </span><span>解不唯一</span><span> <br></span><span>　　</span><span>=&gt; B=x*y </span><span>为非质数</span><span> <br></span><span>　　又</span><span>∵</span><span> x &#8800; y <br></span><span>　　</span><span>&#8756;</span><span> B &#8800; k*k (</span><span>其中</span><span>k</span><span>&#8712;</span><span>N) <br></span><span>　　结论</span><span>(</span><span>推论</span><span>1)</span><span>：</span><span> <br></span><span>　　</span><span>B=x*y </span><span>非质数且</span><span> B &#8800; k*k (</span><span>其中</span><span>k</span><span>&#8712;</span><span>N) <br></span><span>　　即：</span><span>B </span><span>&#8712;</span><span>(6</span><span>，</span><span>8</span><span>，</span><span>10</span><span>，</span><span>12</span><span>，</span><span>14</span><span>，</span><span>15</span><span>，</span><span>18</span><span>，</span><span>20...) <br></span><span>　　证明过程略。</span><span> <br></span><span>　　</span><span>2)</span><span>由题设条件：甲不知道答案</span><span> <br></span><span>　　</span><span>&lt;=&gt; A=x+y </span><span>解不唯一</span><span> <br></span><span>　　</span><span>=&gt; A &gt;= 5</span><span>；</span><span> <br></span><span>　　分两种情况：</span><span> <br></span><span>　　</span><span>A=5</span><span>，</span><span>A=6</span><span>时</span><span>x</span><span>，</span><span>y</span><span>有双解</span><span> <br></span><span>　　</span><span>A&gt;=7 </span><span>时</span><span>x</span><span>，</span><span>y</span><span>有三重及三重以上解</span><span> <br></span><span>　　假设</span><span> A=x+y=5 <br></span><span>　　则有双解</span><span> <br></span><span>　　</span><span>x1=1</span><span>，</span><span>y1=4</span><span>；</span><span> <br></span><span>　　</span><span>x2=2</span><span>，</span><span>y2=3 <br></span><span>　　代入公式</span><span>B=x*y</span><span>：</span><span> <br></span><span>　　</span><span>B1=x1*y1=1*4=4</span><span>；</span><span>(</span><span>不满足推论</span><span>1</span><span>，舍去</span><span>) <br></span><span>　　</span><span>B2=x2*y2=2*3=6</span><span>；</span><span> <br></span><span>　　得到唯一解</span><span>x=2</span><span>，</span><span>y=3</span><span>即甲知道答案。</span><span> <br></span><span>　　与题设条件：</span><span>"</span><span>甲不知道答案</span><span>"</span><span>相矛盾，</span><span> <br></span><span>　　故假设不成立，</span><span>A=x+y&#8800;5 <br></span><span>　　假设</span><span> A=x+y=6 <br></span><span>　　则有双解。</span><span> <br></span><span>　　</span><span>x1=1</span><span>，</span><span>y1=5</span><span>；</span><span> <br></span><span>　　</span><span>x2=2</span><span>，</span><span>y2=4 <br></span><span>　　代入公式</span><span>B=x*y</span><span>：</span><span> <br></span><span>　　</span><span>B1=x1*y1=1*5=5</span><span>；</span><span>(</span><span>不满足推论</span><span>1</span><span>，舍去</span><span>) <br></span><span>　　</span><span>B2=x2*y2=2*4=8</span><span>；</span><span> <br></span><span>　　得到唯一解</span><span>x=2</span><span>，</span><span>y=4 <br></span><span>　　即甲知道答案</span><span> <br></span><span>　　与题设条件：</span><span>"</span><span>甲不知道答案</span><span>"</span><span>相矛盾</span><span> <br></span><span>　　故假设不成立，</span><span>A=x+y&#8800;6 <br></span><span>　　当</span><span>A&gt;=7</span><span>时</span><span> <br></span><span>　　</span><span>∵</span><span> x</span><span>，</span><span>y</span><span>的解至少存在两种满足推论</span><span>1</span><span>的解</span><span> <br></span><span>　　</span><span>B1=x1*y1=2*(A-2) <br></span><span>　　</span><span>B2=x2*y2=3*(A-3) <br></span><span>　　</span><span>&#8756;</span><span> </span><span>符合条件</span><span> <br></span><span>　　结论</span><span>(</span><span>推论</span><span>2)</span><span>：</span><span>A &gt;= 7 <br></span><span>　　</span><span>3)</span><span>由题设条件：乙说</span><span>"</span><span>那我知道了</span><span>" <br></span><span>　　</span><span>=&gt;</span><span>乙通过已知条件</span><span>B=x*y</span><span>及推论</span><span>(1)(2)</span><span>可以得出唯一解</span><span> <br></span><span>　　即：</span><span> <br></span><span>　　</span><span>A=x+y</span><span>，</span><span> A &gt;= 7 <br></span><span>　　</span><span>B=x*y</span><span>，</span><span> B </span><span>&#8712;</span><span>(6</span><span>，</span><span>8</span><span>，</span><span>10</span><span>，</span><span>12</span><span>，</span><span>14</span><span>，</span><span>15</span><span>，</span><span>16</span><span>，</span><span>18</span><span>，</span><span>20...) <br></span><span>　　</span><span>1 &lt;= x &lt; y &lt;= 30 <br></span><span>　　</span><span>x</span><span>，</span><span>y</span><span>存在唯一解</span><span> <br></span><span>　　当</span><span> B=6 </span><span>时：有两组解</span><span> <br></span><span>　　</span><span>x1=1</span><span>，</span><span>y1=6 <br></span><span>　　</span><span>x2=2</span><span>，</span><span>y2=3 (</span><span>∵</span><span> x2+y2=2+3=5 &lt; 7</span><span>&#8756;</span><span>不合题意，舍去</span><span>) <br></span><span>　　得到唯一解</span><span> x=1</span><span>，</span><span>y=6 <br></span><span>　　当</span><span> B=8 </span><span>时：有两组解</span><span> <br></span><span>　　</span><span>x1=1</span><span>，</span><span>y1=8 <br></span><span>　　</span><span>x2=2</span><span>，</span><span>y2=4 (</span><span>∵</span><span> x2+y2=2+4=6 &lt; 7</span><span>&#8756;</span><span>不合题意，舍去</span><span>) <br></span><span>　　得到唯一解</span><span> x=1</span><span>，</span><span>y=8 <br></span><span>　　当</span><span> B&gt;8 </span><span>时：容易证明均为多重解</span><span> <br></span><span>　　结论：</span><span> <br></span><span>　　当</span><span>B=6</span><span>时有唯一解</span><span> x=1</span><span>，</span><span>y=6</span><span>当</span><span>B=8</span><span>时有唯一解</span><span> x=1</span><span>，</span><span>y=8 <br></span><span>　　</span><span>4)</span><span>由题设条件：甲说</span><span>"</span><span>那我也知道了</span><span>" <br></span><span>　　</span><span>=&gt;</span><span>　甲通过已知条件</span><span>A=x+y</span><span>及推论</span><span>(3)</span><span>可以得出唯一解</span><span> <br></span><span>　　综上所述，原题所求有两组解：</span><span> <br></span><span>　　</span><span>x1=1</span><span>，</span><span>y1=6 <br></span><span>　　</span><span>x2=1</span><span>，</span><span>y2=8 <br></span><span>　　当</span><span>x&lt;=y</span><span>时，有</span><span>(1 &lt;= x &lt;= y &lt;= 30)</span><span>；</span><span> <br></span><span>　　同理可得唯一解</span><span> x=1</span><span>，</span><span>y=4 <br></span><span>　　</span><span>31</span><span>、</span><span> </span><span>　　解：</span><span>1000 <br></span><span>　　</span><span>Lg(1000!)=sum(Lg(n)) <br></span><span>　　</span><span>n=1 <br></span><span>　　用</span><span>3 </span><span>段折线代替曲线可以得到</span><span> <br></span><span>　　</span><span>10(0+1)/2+90(1+2)/2+900(2+3)/2=2390 <br></span><span>　　作为近似结果，好象</span><span>1500~3000 </span><span>都算对</span><span> <br></span><span>　　</span><span>32</span><span>、</span><span>F(n)=1 n&gt;8 n&lt;12 <br></span><span>　　</span><span>F(n)=2 n&lt;2 <br></span><span>　　</span><span>F(n)=3 n=6 <br></span><span>　　</span><span>F(n)=4 n=other <br></span><span>　　使用</span><span>+ - * /</span><span>和</span><span>sign(n)</span><span>函数组合出</span><span>F(n)</span><span>函数</span><span> <br></span><span>　　</span><span>sign(n)=0 n=0 <br></span><span>　　</span><span>sign(n)=-1 n&lt;0 <br></span><span>　　：</span><span>sign(n)=1 n&gt;0 <br></span><span>　　解</span><span>:</span><span>只要注意</span><span>[sign(n-m)*sign(m-n)+1]</span><span>在</span><span>n=m </span><span>处取</span><span>1 </span><span>其他点取</span><span>0 </span><span>就可以了</span><span> <br></span><span>　　</span><span>34</span><span>、米字形的画就行了</span></p>
<img src ="http://www.cppblog.com/bellgrade/aggbug/98786.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/bellgrade/" target="_blank">bellgrade</a> 2009-10-16 19:56 <a href="http://www.cppblog.com/bellgrade/archive/2009/10/16/98786.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>平衡二叉树</title><link>http://www.cppblog.com/bellgrade/archive/2009/10/12/98402.html</link><dc:creator>bellgrade</dc:creator><author>bellgrade</author><pubDate>Mon, 12 Oct 2009 10:50:00 GMT</pubDate><guid>http://www.cppblog.com/bellgrade/archive/2009/10/12/98402.html</guid><wfw:comment>http://www.cppblog.com/bellgrade/comments/98402.html</wfw:comment><comments>http://www.cppblog.com/bellgrade/archive/2009/10/12/98402.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.cppblog.com/bellgrade/comments/commentRss/98402.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/bellgrade/services/trackbacks/98402.html</trackback:ping><description><![CDATA[形态匀称的二叉树称为平衡二叉树 (Balanced binary tree) ，其严格定义是：<br>　　一棵空树是平衡二叉树；若 T 是一棵非空二叉树，其左、右子树为 TL 和 TR ，令 hl 和 hr 分别为左、右子树的深度。当且仅当<br>　　　①TL 、 TR 都是平衡二叉树； <br>　　　② ｜ hl － hr ｜&#8804; 1；<br>时，则 T 是平衡二叉树。<br>【例】如图 8.4 所示。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img height=205 src="http://sjjg.js.zwu.edu.cn/SFXX/chazhao/tupian/8.4.gif" width=328><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;（a）平衡二叉树&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;（b）非平衡二叉树<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;图8.3 平衡二叉树与非平衡二叉树<br>相应地定义 hl － hr 为二叉平衡树的平衡因子 (balance factor) 。因此，平衡二叉树上所有结点的平衡因子可能是 -1 ， 0 ， 1 。换言之，若一棵二叉树上任一结点的平衡因子的绝对值都不大于 1 ，则该树是就平衡二叉树。<br><strong><font size=4>动态平衡技术</font></strong> <br><strong>1.动态平衡技术</strong><br>Adelson-Velskii 和 Landis 提出了一个动态地保持二叉排序树平衡的方法，其基本思想是：<br>　　在构造二叉排序树的过程中，每当插入一个结点时，首先检查是否因插入而破坏了树的平衡性，如果是因插入结点而破坏了树的平衡性，则找出其中<strong>最小不平衡子树</strong>，在保持排序树特性的前提下，调整最小不平衡子树中各结点之间的连接关系，以达到新的平衡。通常将这样得到的平衡二叉排序树简称为<strong> AVL 树</strong>。<br><strong>2.最小不平衡子树</strong><br>　　以离插入结点最近、且平衡因子绝对值大于 1 的结点作根结点的子树。为了简化讨论，不妨假设二叉排序树的最小不平衡子树的根结点为 A ，则调整该子树的规律可归纳为下列四种情况：<br><font color=#0000ff>（1） LL 型：</font><br>　　新结点 X 插在 A 的左孩子的左子树里。调整方法见图 8.5(a) 。图中以 B 为轴心，将 A 结点从 B 的右上方转到 B 的右下侧，使 A 成为 B 的右孩子。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img height=383 src="http://sjjg.js.zwu.edu.cn/SFXX/chazhao/tupian/8.5.gif" width=408><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;图8.5 平衡调整的4种基本类型（结点旁的数字是平衡因子）<br><font color=#0000ff>（2）RR 型：</font><br>　　新结点 X 插在 A 的右孩子的右子树里。调整方法见图 8.5(b) 。图中以 B 为轴心，将 A 结点从 B 的左上方转到 B 的左下侧，使 A 成为 B 的左孩子。<br><font color=#0000ff>（3）LR 型：</font><br>　　新结点 X 插在 A 的左孩子的右子树里。调整方法见图 8.5(c) 。分为两步进行：第一步以 X 为轴心，将 B 从 X 的左上方转到 X 的左下侧，使 B 成为 X 的左孩子， X 成为 A 的左孩子。第二步跟 LL 型一样处理 ( 应以 X 为轴心 ) 。 <br><font color=#0000ff>（4）RL 型：</font><br>　　新结点 X 插在 A 的右孩子的左子树里。调整方法见图 8.5(d) 。分为两步进行：第一步以 X 为轴心，将 B 从 X 的右上方转到 X 的右下侧，使 B 成为 X 的右孩子， X 成为 A 的右孩子。第二步跟 RR 型一样处理 ( 应以 X 为轴心 ) 。<br>【例】<br>实际的插入情况，可能比图 8.5 要复杂。因为 A 、 B 结点可能还会有子树。现举一例说明，设一组记录的关键字按以下次序进行插入： 4 、 5 、 7 ， 2 、 1 、 3 、 6 ，其生成及调整成二叉平衡树的过程示于图 8.6 。 <br>　　在图 8.6 中，当插入关键字为 3 的结点后，由于离结点 3 最近的平衡因子为 2 的祖先是根结点 5 。所以，第一次旋转应以结点 4 为轴心，把结点 2 从结点 4 的左上方转到左下侧，从而结点 5 的左孩子是结点 4 ，结点 4 的左孩子是结点 2 ，原结点 4 的左孩子变成了结点 2 的右孩子。第二步再以结点 4 为轴心，按 LL 类型进行转换。这种插入与调整平衡的方法可以编成算法和程序，这里就不再讨论了。 <br><img height=673 src="http://sjjg.js.zwu.edu.cn/SFXX/chazhao/tupian/8.6.gif" width=530><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;图 8.6 二叉平衡树插入结点 ( 结点旁的数字为其平衡因子 )<br>
<img src ="http://www.cppblog.com/bellgrade/aggbug/98402.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/bellgrade/" target="_blank">bellgrade</a> 2009-10-12 18:50 <a href="http://www.cppblog.com/bellgrade/archive/2009/10/12/98402.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>VC++，掀起你的盖头来 ——谈VC++对象模型</title><link>http://www.cppblog.com/bellgrade/archive/2009/10/12/98371.html</link><dc:creator>bellgrade</dc:creator><author>bellgrade</author><pubDate>Mon, 12 Oct 2009 05:44:00 GMT</pubDate><guid>http://www.cppblog.com/bellgrade/archive/2009/10/12/98371.html</guid><wfw:comment>http://www.cppblog.com/bellgrade/comments/98371.html</wfw:comment><comments>http://www.cppblog.com/bellgrade/archive/2009/10/12/98371.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/bellgrade/comments/commentRss/98371.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/bellgrade/services/trackbacks/98371.html</trackback:ping><description><![CDATA[<p>一个C++程序员，想要进一步提升技术水平的话，应该多了解一些语言的语意细节。对于使用VC++的程序员来说，还应该了解一些VC++对于C++的诠释。Inside the C++ Object Model虽然是一本好书，然而，书的篇幅多一些，又和具体的VC++关系小一些。因此，从篇幅和内容来看，译者认为本文是深入理解C++对象模型比较好的一个出发点。<br>这篇文章以前看到时就觉得很好，旧文重读，感觉理解得更多一些了，于是产生了翻译出来，与大家共享的想法。虽然文章不长，但时间有限，又若干次在翻译时打盹睡着，拖拖拉拉用了小一个月。<br>一方面因本人水平所限，另一方面因翻译时经常打盹，错误之处恐怕不少，欢迎大家批评指正。</p>
<p>1 前言</p>
<p>了解你所使用的编程语言究竟是如何实现的，对于C++程序员可能特别有意义。首先，它可以去除我们对于所使用语言的神秘感，使我们不至于对于编译器干的活感到完全不可思议；尤其重要的是，它使我们在Debug和使用语言高级特性的时候，有更多的把握。当需要提高代码效率的时候，这些知识也能够很好地帮助我们。</p>
<p>本文着重回答这样一些问题：<br> * 类如何布局？<br> * 成员变量如何访问？<br> * 成员函数如何访问？<br> * 所谓的&#8220;调整块&#8221;（adjuster thunk）是怎么回事？<br> * 使用如下机制时，开销如何：<br> * 单继承、多重继承、虚继承<br> * 虚函数调用<br> * 强制转换到基类，或者强制转换到虚基类<br> * 异常处理<br>首先，我们顺次考察C兼容的结构（struct）的布局，单继承，多重继承，以及虚继承；<br>接着，我们讲成员变量和成员函数的访问，当然，这里面包含虚函数的情况；<br>再接下来，我们考察构造函数，析构函数，以及特殊的赋值操作符成员函数是如何工作的，数组是如何动态构造和销毁的；<br>最后，简单地介绍对异常处理的支持。</p>
<p>对每个语言特性，我们将简要介绍该特性背后的动机，该特性自身的语意（当然，本文决不是&#8220;C++入门&#8221;，大家对此要有充分认识），以及该特性在微软的VC++中是如何实现的。这里要注意区分抽象的C++语言语意与其特定实现。微软之外的其他C++厂商可能提供一个完全不同的实现，我们偶尔也会将VC++的实现与其他实现进行比较。</p>
<p>2 类布局</p>
<p>本节讨论不同的继承方式造成的不同内存布局。</p>
<p>2.1 C结构（struct）</p>
<p>由于C++基于C，所以C++也&#8220;基本上&#8221;兼容C。特别地，C++规范在&#8220;结构&#8221;上使用了和C相同的，简单的内存布局原则：成员变量按其被声明的顺序排列，按具体实现所规定的对齐原则在内存地址上对齐。所有的C/C++厂商都保证他们的C/C++编译器对于有效的C结构采用完全相同的布局。这里，A是一个简单的C结构，其成员布局和对齐方式都一目了然。</p>
<p><br>struct A {<br>&nbsp;&nbsp; char c;<br>&nbsp;&nbsp; int i;<br>};</p>
<p>译者注：从上图可见，A在内存中占有8个字节，按照声明成员的顺序，前4个字节包含一个字符（实际占用1个字节，3个字节空着，补对齐），后4个字节包含一个整数。A的指针就指向字符开始字节处。</p>
<p><br>2.2 有C++特征的C结构</p>
<p><br>当然了，C++不是复杂的C，C++本质上是面向对象的语言：包含继承、封装，以及多态。原始的C结构经过改造，成了面向对象世界的基石——类。除了成员变量外，C++类还可以封装成员函数和其他东西。然而，有趣的是，除非为了实现虚函数和虚继承引入的隐藏成员变量外，C++类实例的大小完全取决于一个类及其基类的成员变量！成员函数基本上不影响类实例的大小。</p>
<p>这里提供的B是一个C结构，然而，该结构有一些C++特征：控制成员可见性的&#8220;public/protected/private&#8221;关键字、成员函数、静态成员，以及嵌套的类型声明。虽然看着琳琅满目，实际上只有成员变量才占用类实例的空间。要注意的是，C++标准委员会不限制由&#8220;public/protected/private&#8221;关键字分开的各段在实现时的先后顺序，因此，不同的编译器实现的内存布局可能并不相同。（在VC++中，成员变量总是按照声明时的顺序排列）。</p>
<p>&nbsp;</p>
<p>struct B {<br>public:<br>&nbsp;&nbsp; int bm1;<br>protected:<br>&nbsp;&nbsp; int bm2;<br>private:<br>&nbsp;&nbsp; int bm3;<br>&nbsp;&nbsp; static int bsm;<br>&nbsp;&nbsp; void bf();<br>&nbsp;&nbsp; static void bsf();<br>&nbsp;&nbsp; typedef void* bpv;<br>&nbsp;&nbsp; struct N { };<br>};</p>
<p>译者注：B中，为何static int bsm不占用内存空间？因为它是静态成员，该数据存放在程序的数据段中，不在类实例中。</p>
<p><br>2.3 单继承</p>
<p><br>C++提供继承的目的是在不同的类型之间提取共性。比如，科学家对物种进行分类，从而有种、属、纲等说法。有了这种层次结构，我们才可能将某些具备特定性质的东西归入到最合适的分类层次上，如&#8220;怀孩子的是哺乳动物&#8221;。由于这些属性可以被子类继承，所以，我们只要知道&#8220;鲸鱼、人&#8221;是哺乳动物，就可以方便地指出&#8220;鲸鱼、人都可以怀孩子&#8221;。那些特例，如鸭嘴兽（生蛋的哺乳动物），则要求我们对缺省的属性或行为进行覆盖。<br>C++中的继承语法很简单，在子类后加上&#8220;:base&#8221;就可以了。下面的D继承自基类C。</p>
<p>&nbsp;</p>
<p>struct C {<br>&nbsp;&nbsp; int c1;<br>&nbsp;&nbsp; void cf();<br>};</p>
<p>&nbsp;</p>
<p>struct D : C {<br>&nbsp;&nbsp; int d1;<br>&nbsp;&nbsp; void df();<br>};</p>
<p>既然派生类要保留基类的所有属性和行为，自然地，每个派生类的实例都包含了一份完整的基类实例数据。在D中，并不是说基类C的数据一定要放在D的数据之前，只不过这样放的话，能够保证D中的C对象地址，恰好是D对象地址的第一个字节。这种安排之下，有了派生类D的指针，要获得基类C的指针，就不必要计算偏移量了。几乎所有知名的C++厂商都采用这种内存安排。在单继承类层次下，每一个新的派生类都简单地把自己的成员变量添加到基类的成员变量之后。看看上图，C对象指针和D对象指针指向同一地址。</p>
<p>2.4 多重继承</p>
<p><br>大多数情况下，其实单继承就足够了。但是，C++为了我们的方便，还提供了多重继承。</p>
<p>比如，我们有一个组织模型，其中有经理类（分任务），工人类（干活）。那么，对于一线经理类，即既要从上级经理那里领取任务干活，又要向下级工人分任务的角色来说，如何在类层次中表达呢？单继承在此就有点力不胜任。我们可以安排经理类先继承工人类，一线经理类再继承经理类，但这种层次结构错误地让经理类继承了工人类的属性和行为。反之亦然。当然，一线经理类也可以仅仅从一个类（经理类或工人类）继承，或者一个都不继承，重新声明一个或两个接口，但这样的实现弊处太多：多态不可能了；未能重用现有的接口；最严重的是，当接口变化时，必须多处维护。最合理的情况似乎是一线经理从两个地方继承属性和行为——经理类、工人类。</p>
<p>C++就允许用多重继承来解决这样的问题：</p>
<p>struct Manager ... { ... };<br>struct Worker ... { ... };<br>struct MiddleManager : Manager, Worker { ... };</p>
<p>这样的继承将造成怎样的类布局呢？下面我们还是用&#8220;字母类&#8221;来举例：</p>
<p>&nbsp;</p>
<p>struct E {<br>&nbsp;&nbsp; int e1;<br>&nbsp;&nbsp; void ef();<br>};</p>
<p>&nbsp;</p>
<p>struct F : C, E {<br>&nbsp;&nbsp; int f1;<br>&nbsp;&nbsp; void ff();<br>&nbsp; <br>&nbsp; <br>};<br>&nbsp; <br>结构F从C和E多重继承得来。与单继承相同的是，F实例拷贝了每个基类的所有数据。与单继承不同的是，在多重继承下，内嵌的两个基类的对象指针不可能全都与派生类对象指针相同：<br>&nbsp; <br>F f;<br>// (void*)&amp;f == (void*)(C*)&amp;f;<br>// (void*)&amp;f &lt;&nbsp; (void*)(E*)&amp;f;<br>译者注：上面那行说明C对象指针与F对象指针相同，下面那行说明E对象指针与F对象指针不同。</p>
<p>观察类布局，可以看到F中内嵌的E对象，其指针与F指针并不相同。正如后文讨论强制转化和成员函数时指出的，这个偏移量会造成少量的调用开销。</p>
<p>具体的编译器实现可以自由地选择内嵌基类和派生类的布局。VC++按照基类的声明顺序先排列基类实例数据，最后才排列派生类数据。当然，派生类数据本身也是按照声明顺序布局的（本规则并非一成不变，我们会看到，当一些基类有虚函数而另一些基类没有时，内存布局并非如此）。</p>
<p>2.5 虚继承</p>
<p><br>回到我们讨论的一线经理类例子。让我们考虑这种情况：如果经理类和工人类都继承自&#8220;雇员类&#8221;，将会发生什么？<br>&nbsp; <br>struct Employee { ... };<br>struct Manager : Employee { ... };<br>struct Worker : Employee { ... };<br>struct MiddleManager : Manager, Worker { ... };<br>&nbsp; <br>如果经理类和工人类都继承自雇员类，很自然地，它们每个类都会从雇员类获得一份数据拷贝。如果不作特殊处理，一线经理类的实例将含有两个雇员类实例，它们分别来自两个雇员基类。如果雇员类成员变量不多，问题不严重；如果成员变量众多，则那份多余的拷贝将造成实例生成时的严重开销。更糟的是，这两份不同的雇员实例可能分别被修改，造成数据的不一致。因此，我们需要让经理类和工人类进行特殊的声明，说明它们愿意共享一份雇员基类实例数据。</p>
<p>很不幸，在C++中，这种&#8220;共享继承&#8221;被称为&#8220;虚继承&#8221;，把问题搞得似乎很抽象。虚继承的语法很简单，在指定基类时加上virtual关键字即可。<br>&nbsp; <br>struct Employee { ... };<br>struct Manager : virtual Employee { ... };<br>struct Worker : virtual Employee { ... };<br>struct MiddleManager : Manager, Worker { ... };<br>&nbsp; <br>使用虚继承，比起单继承和多重继承有更大的实现开销、调用开销。回忆一下，在单继承和多重继承的情况下，内嵌的基类实例地址比起派生类实例地址来，要么地址相同（单继承，以及多重继承的最靠左基类），要么地址相差一个固定偏移量（多重继承的非最靠左基类）。然而，当虚继承时，一般说来，派生类地址和其虚基类地址之间的偏移量是不固定的，因为如果这个派生类又被进一步继承的话，最终派生类会把共享的虚基类实例数据放到一个与上一层派生类不同的偏移量处。请看下例：<br>&nbsp; <br>struct G : virtual C {<br>&nbsp;&nbsp; int g1;<br>&nbsp;&nbsp; void gf();<br>};<br>&nbsp; <br>译者注：GdGvbptrG（In G, the displacement of G&#8217;s virtual base pointer to G）意思是：在G中，G对象的指针与G的虚基类表指针之间的偏移量，在此可见为0，因为G对象内存布局第一项就是虚基类表指针； GdGvbptrC（In G, the displacement of G&#8217;s virtual base pointer to C）意思是：在G中，C对象的指针与G的虚基类表指针之间的偏移量，在此可见为4。</p>
<p>&nbsp;</p>
<p>struct H : virtual C {<br>&nbsp;&nbsp; int h1;<br>&nbsp;&nbsp; void hf();<br>};<br>&nbsp; <br>struct I : G, H {<br>&nbsp;&nbsp; int i1;<br>&nbsp;&nbsp; void _if();<br>};<br>&nbsp; <br>暂时不追究vbptr成员变量从何而来。从上面这些图可以直观地看到，在G对象中，内嵌的C基类对象的数据紧跟在G的数据之后，在H对象中，内嵌的C基类对象的数据也紧跟在H的数据之后。但是，在I对象中，内存布局就并非如此了。VC++实现的内存布局中，G对象实例中G对象和C对象之间的偏移，不同于I对象实例中G对象和C对象之间的偏移。当使用指针访问虚基类成员变量时，由于指针可以是指向派生类实例的基类指针，所以，编译器不能根据声明的指针类型计算偏移，而必须找到另一种间接的方法，从派生类指针计算虚基类的位置。</p>
<p>在VC++中，对每个继承自虚基类的类实例，将增加一个隐藏的&#8220;虚基类表指针&#8221;（vbptr）成员变量，从而达到间接计算虚基类位置的目的。该变量指向一个全类共享的偏移量表，表中项目记录了对于该类而言，&#8220;虚基类表指针&#8221;与虚基类之间的偏移量。</p>
<p>其它的实现方式中，有一种是在派生类中使用指针成员变量。这些指针成员变量指向派生类的虚基类，每个虚基类一个指针。这种方式的优点是：获取虚基类地址时，所用代码比较少。然而，编译器优化代码时通常都可以采取措施避免重复计算虚基类地址。况且，这种实现方式还有一个大弊端：从多个虚基类派生时，类实例将占用更多的内存空间；获取虚基类的虚基类的地址时，需要多次使用指针，从而效率较低等等。</p>
<p>在VC++中，G拥有一个隐藏的&#8220;虚基类表指针&#8221;成员，指向一个虚基类表，该表的第二项是GdGvbptrC。（在G中，虚基类对象C的地址与G的&#8220;虚基类表指针&#8221;之间的偏移量（当对于所有的派生类来说偏移量不变时，省略&#8220;d&#8221;前的前缀））。比如，在32位平台上，GdGvptrC是8个字节。同样，在I实例中的G对象实例也有&#8220;虚基类表指针&#8221;，不过该指针指向一个适用于&#8220;G处于I之中&#8221;的虚基类表，表中一项为IdGvbptrC，值为20。</p>
<p>观察前面的G、H和I，我们可以得到如下关于VC++虚继承下内存布局的结论：<br> 首先排列非虚继承的基类实例；<br> 有虚基类时，为每个基类增加一个隐藏的vbptr，除非已经从非虚继承的类那里继承了一个vbptr；<br> 排列派生类的新数据成员；<br> 在实例最后，排列每个虚基类的一个实例。</p>
<p>该布局安排使得虚基类的位置随着派生类的不同而&#8220;浮动不定&#8221;，但是，非虚基类因此也就凑在一起，彼此的偏移量固定不变。</p>
<p>3 成员变量</p>
<p>介绍了类布局之后，我们接着考虑对不同的继承方式，访问成员变量的开销究竟如何。</p>
<p>没有继承。没有任何继承关系时，访问成员变量和C语言的情况完全一样：从指向对象的指针，考虑一定的偏移量即可。<br>&nbsp; <br>C* pc;<br>pc-&gt;c1; // *(pc + dCc1);<br>译者注：pc是指向C的指针。<br> 访问C的成员变量c1，只需要在pc上加上固定的偏移量dCc1（在C中，C指针地址与其c1成员变量之间的偏移量值），再获取该指针的内容即可。</p>
<p>单继承。由于派生类实例与其基类实例之间的偏移量是常数0，所以，可以直接利用基类指针和基类成员之间的偏移量关系，如此计算得以简化。<br>&nbsp; <br>D* pd;<br>pd-&gt;c1; // *(pd + dDC + dCc1); // *(pd + dDc1);<br>pd-&gt;d1; // *(pd + dDd1);<br>译者注：D从C单继承，pd为指向D的指针。<br> 当访问基类成员c1时，计算步骤本来应该为&#8220;pd+dDC+dCc1&#8221;，即为先计算D对象和C对象之间的偏移，再在此基础上加上C对象指针与成员变量c1之间的偏移量。然而，由于dDC恒定为0，所以直接计算C对象地址与c1之间的偏移就可以了。<br> 当访问派生类成员d1时，直接计算偏移量。</p>
<p>多重继承。虽然派生类与某个基类之间的偏移量可能不为0，然而，该偏移量总是一个常数。只要是个常数，访问成员变量，计算成员变量偏移时的计算就可以被简化。可见即使对于多重继承来说，访问成员变量开销仍然不大。<br>&nbsp; <br>F* pf;<br>pf-&gt;c1; // *(pf + dFC + dCc1); // *(pf + dFc1);<br>pf-&gt;e1; // *(pf + dFE + dEe1); // *(pf + dFe1);<br>pf-&gt;f1; // *(pf + dFf1);<br>译者注：F继承自C和E，pf是指向F对象的指针。<br> 访问C类成员c1时，F对象与内嵌C对象的相对偏移为0，可以直接计算F和c1的偏移；<br> 访问E类成员e1时，F对象与内嵌E对象的相对偏移是一个常数，F和e1之间的偏移计算也可以被简化；<br> 访问F自己的成员f1时，直接计算偏移量。</p>
<p>虚继承。当类有虚基类时，访问非虚基类的成员仍然是计算固定偏移量的问题。然而，访问虚基类的成员变量，开销就增大了，因为必须经过如下步骤才能获得成员变量的地址：获取&#8220;虚基类表指针&#8221;；获取虚基类表中某一表项的内容；把内容中指出的偏移量加到&#8220;虚基类表指针&#8221;的地址上。然而，事情并非永远如此。正如下面访问I对象的c1成员那样，如果不是通过指针访问，而是直接通过对象实例，则派生类的布局可以在编译期间静态获得，偏移量也可以在编译时计算，因此也就不必要根据虚基类表的表项来间接计算了。</p>
<p>I* pi;<br>pi-&gt;c1; // *(pi + dIGvbptr + (*(pi+dIGvbptr))[1] + dCc1);<br>pi-&gt;g1; // *(pi + dIG + dGg1); // *(pi + dIg1);<br>pi-&gt;h1; // *(pi + dIH + dHh1); // *(pi + dIh1);<br>pi-&gt;i1; // *(pi + dIi1);<br>I i;<br>i.c1; // *(&amp;i + IdIC + dCc1); // *(&amp;i + IdIc1);<br>译者注：I继承自G和H，G和H的虚基类是C，pi是指向I对象的指针。<br> 访问虚基类C的成员c1时，dIGvbptr是&#8220;在I中，I对象指针与G的&#8220;虚基类表指针&#8221;之间的偏移&#8221;，*(pi + dIGvbptr)是虚基类表的开始地址，*(pi + dIGvbptr)[1]是虚基类表的第二项的内容（在I对象中，G对象的&#8220;虚基类表指针&#8221;与虚基类之间的偏移），dCc1是C对象指针与成员变量c1之间的偏移；<br> 访问非虚基类G的成员g1时，直接计算偏移量；<br> 访问非虚基类H的成员h1时，直接计算偏移量；<br> 访问自身成员i1时，直接使用偏移量；<br> 当声明了一个对象实例，用点&#8220;.&#8221;操作符访问虚基类成员c1时，由于编译时就完全知道对象的布局情况，所以可以直接计算偏移量。</p>
<p>当访问类继承层次中，多层虚基类的成员变量时，情况又如何呢？比如，访问虚基类的虚基类的成员变量时？一些实现方式为：保存一个指向直接虚基类的指针，然后就可以从直接虚基类找到它的虚基类，逐级上推。VC++优化了这个过程。VC++在虚基类表中增加了一些额外的项，这些项保存了从派生类到其各层虚基类的偏移量。</p>
<p>4 强制转化</p>
<p>如果没有虚基类的问题，将一个指针强制转化为另一个类型的指针代价并不高昂。如果在要求转化的两个指针之间有&#8220;基类-派生类&#8221;关系，编译器只需要简单地在两者之间加上或者减去一个偏移量即可（并且该量还往往为0）。</p>
<p>F* pf;<br>(C*)pf; // (C*)(pf ? pf + dFC : 0); // (C*)pf;<br>(E*)pf; // (E*)(pf ? pf + dFE : 0);</p>
<p>C和E是F的基类，将F的指针pf转化为C*或E*，只需要将pf加上一个相应的偏移量。转化为C类型指针C*时，不需要计算，因为F和C之间的偏移量为0。转化为E类型指针E*时，必须在指针上加一个非0的偏移常量dFE。C++规范要求NULL指针在强制转化后依然为NULL，因此在做强制转化需要的运算之前，VC++会检查指针是否为NULL。当然，这个检查只有当指针被显示或者隐式转化为相关类型指针时才进行；当在派生类对象中调用基类的方法，从而派生类指针被在后台转化为一个基类的Const &#8220;this&#8221; 指针时，这个检查就不需要进行了，因为在此时，该指针一定不为NULL。</p>
<p>正如你猜想的，当继承关系中存在虚基类时，强制转化的开销会比较大。具体说来，和访问虚基类成员变量的开销相当。</p>
<p>I* pi;<br>(G*)pi; // (G*)pi;<br>(H*)pi; // (H*)(pi ? pi + dIH : 0);<br>(C*)pi; // (C*)(pi ? (pi+dIGvbptr + (*(pi+dIGvbptr))[1]) : 0);<br>译者注：pi是指向I对象的指针，G,H是I的基类，C是G,H的虚基类。<br> 强制转化pi为G*时，由于G*和I*的地址相同，不需要计算；<br> 强制转化pi为H*时，只需要考虑一个常量偏移；<br> 强制转化pi为C*时，所作的计算和访问虚基类成员变量的开销相同，首先得到G的虚基类表指针，再从虚基类表的第二项中取出G到虚基类C的偏移量，最后根据pi、虚基类表偏移和虚基类C与虚基类表指针之间的偏移计算出C*。</p>
<p>一般说来，当从派生类中访问虚基类成员时，应该先强制转化派生类指针为虚基类指针，然后一直使用虚基类指针来访问虚基类成员变量。这样做，可以避免每次都要计算虚基类地址的开销。见下例。</p>
<p>/* before: */&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ... pi-&gt;c1 ... pi-&gt;c1 ...<br>/* faster: */ C* pc = pi; ... pc-&gt;c1 ... pc-&gt;c1 ...<br>译者注：前者一直使用派生类指针pi，故每次访问c1都有计算虚基类地址的较大开销；后者先将pi转化为虚基类指针pc，故后续调用可以省去计算虚基类地址的开销。</p>
<p>5 成员函数</p>
<p>一个C++成员函数只是类范围内的又一个成员。X类每一个非静态的成员函数都会接受一个特殊的隐藏参数——this指针，类型为X* const。该指针在后台初始化为指向成员函数工作于其上的对象。同样，在成员函数体内，成员变量的访问是通过在后台计算与this指针的偏移来进行。<br>&nbsp;<br>struct P {<br>&nbsp;&nbsp; int p1;<br>&nbsp;&nbsp; void pf(); // new<br>&nbsp;&nbsp; virtual void pvf(); // new<br>&nbsp; <br>&nbsp; <br>};</p>
<p>P有一个非虚成员函数pf()，以及一个虚成员函数pvf()。很明显，虚成员函数造成对象实例占用更多内存空间，因为虚成员函数需要虚函数表指针。这一点以后还会谈到。这里要特别指出的是，声明非虚成员函数不会造成任何对象实例的内存开销。现在，考虑P::pf()的定义。<br>&nbsp; <br>void P::pf() { // void P::pf([P *const this])<br>&nbsp;&nbsp; ++p1;&nbsp;&nbsp; // ++(this-&gt;p1);<br>}</p>
<p>这里P:pf()接受了一个隐藏的this指针参数，对于每个成员函数调用，编译器都会自动加上这个参数。同时，注意成员变量访问也许比看起来要代价高昂一些，因为成员变量访问通过this指针进行，在有的继承层次下，this指针需要调整，所以访问的开销可能会比较大。然而，从另一方面来说，编译器通常会把this指针缓存到寄存器中，所以，成员变量访问的代价不会比访问局部变量的效率更差。<br>译者注：访问局部变量，需要到SP寄存器中得到栈指针，再加上局部变量与栈顶的偏移。在没有虚基类的情况下，如果编译器把this指针缓存到了寄存器中，访问成员变量的过程将与访问局部变量的开销相似。</p>
<p>5.1 覆盖成员函数</p>
<p><br>和成员变量一样，成员函数也会被继承。与成员变量不同的是，通过在派生类中重新定义基类函数，一个派生类可以覆盖，或者说替换掉基类的函数定义。覆盖是静态（根据成员函数的静态类型在编译时决定）还是动态（通过对象指针在运行时动态决定），依赖于成员函数是否被声明为&#8220;虚函数&#8221;。</p>
<p>Q从P继承了成员变量和成员函数。Q声明了pf()，覆盖了P::pf()。Q还声明了pvf()，覆盖了P::pvf()虚函数。Q还声明了新的非虚成员函数qf()，以及新的虚成员函数qvf()。</p>
<p>&nbsp;</p>
<p>struct Q : P {<br>&nbsp;&nbsp; int q1;<br>&nbsp;&nbsp; void pf();&nbsp; // overrides P::pf<br>&nbsp;&nbsp; void qf();&nbsp; // new<br>&nbsp;&nbsp; void pvf(); // overrides P::pvf<br>&nbsp;&nbsp; virtual void qvf(); // new<br>};<br>&nbsp; <br>对于非虚的成员函数来说，调用哪个成员函数是在编译时，根据&#8220;-&gt;&#8221;操作符左边指针表达式的类型静态决定的。特别地，即使ppq指向Q的实例，ppq-&gt;pf()仍然调用的是P::pf()，因为ppq被声明为&#8220;P*&#8221;。（注意，&#8220;-&gt;&#8221;操作符左边的指针类型决定隐藏的this参数的类型。）<br>&nbsp; <br>P p; P* pp = &amp;p; Q q; P* ppq = &amp;q; Q* pq = &amp;q;<br>pp-&gt;pf();&nbsp; // pp-&gt;P::pf();&nbsp; // P::pf(pp);<br>ppq-&gt;pf(); // ppq-&gt;P::pf(); // P::pf(ppq);<br>pq-&gt;pf();&nbsp; // pq-&gt;Q::pf();&nbsp; // Q::pf((P*)pq); （错误!）<br>pq-&gt;qf();&nbsp; // pq-&gt;Q::qf();&nbsp; // Q::qf(pq);<br>译者注：标记&#8220;错误&#8221;处，P*似应为Q*。因为pf非虚函数，而pq的类型为Q*，故应该调用到Q的pf函数上，从而该函数应该要求一个Q* const类型的this指针。</p>
<p>对于虚函数调用来说，调用哪个成员函数在运行时决定。不管&#8220;-&gt;&#8221;操作符左边的指针表达式的类型如何，调用的虚函数都是由指针实际指向的实例类型所决定。比如，尽管ppq的类型是P*，当ppq指向Q的实例时，调用的仍然是Q::pvf()。<br>&nbsp; <br>pp-&gt;pvf();&nbsp; // pp-&gt;P::pvf();&nbsp; // P::pvf(pp);<br>ppq-&gt;pvf(); // ppq-&gt;Q::pvf(); // Q::pvf((Q*)ppq);<br>pq-&gt;pvf();&nbsp; // pq-&gt;Q::pvf();&nbsp; // Q::pvf((P*)pq); （错误！）<br>译者注：标记&#8220;错误&#8221;处，P*似应为Q*。因为pvf是虚函数，pq本来就是Q*，又指向Q的实例，从哪个方面来看都不应该是P*。</p>
<p>为了实现这种机制，引入了隐藏的vfptr成员变量。一个vfptr被加入到类中（如果类中没有的话），该vfptr指向类的虚函数表（vftable）。类中每个虚函数在该类的虚函数表中都占据一项。每项保存一个对于该类适用的虚函数的地址。因此，调用虚函数的过程如下：取得实例的vfptr；通过vfptr得到虚函数表的一项；通过虚函数表该项的函数地址间接调用虚函数。也就是说，在普通函数调用的参数传递、调用、返回指令开销外，虚函数调用还需要额外的开销。</p>
<p>回头再看看P和Q的内存布局，可以发现，VC++编译器把隐藏的vfptr成员变量放在P和Q实例的开始处。这就使虚函数的调用能够尽量快一些。实际上，VC++的实现方式是，保证任何有虚函数的类的第一项永远是vfptr。这就可能要求在实例布局时，在基类前插入新的vfptr，或者要求在多重继承时，虽然在右边，然而有vfptr的基类放到左边没有vfptr的基类的前面。</p>
<p>许多C++的实现会共享或者重用从基类继承来的vfptr。比如，Q并不会有一个额外的vfptr，指向一个专门存放新的虚函数qvf()的虚函数表。Qvf项只是简单地追加到P的虚函数表的末尾。如此一来，单继承的代价就不算高昂。一旦一个实例有vfptr了，它就不需要更多的vfptr。新的派生类可以引入更多的虚函数，这些新的虚函数只是简单地在已存在的，&#8220;每类一个&#8221;的虚函数表的末尾追加新项。</p>
<p>5.2 多重继承下的虚函数</p>
<p>如果从多个有虚函数的基类继承，一个实例就有可能包含多个vfptr。考虑如下的R和S类：<br>&nbsp; <br>struct R {<br>&nbsp;&nbsp; int r1;<br>&nbsp;&nbsp; virtual void pvf(); // new<br>&nbsp;&nbsp; virtual void rvf(); // new<br>};<br>&nbsp; </p>
<p>struct S : P, R {<br>&nbsp;&nbsp; int s1;<br>&nbsp;&nbsp; void pvf(); // overrides P::pvf and R::pvf<br>&nbsp;&nbsp; void rvf(); // overrides R::rvf<br>&nbsp;&nbsp; void svf(); // new<br>};</p>
<p>这里R是另一个包含虚函数的类。因为S从P和R多重继承，S的实例内嵌P和R的实例，以及S自身的数据成员S::s1。注意，在多重继承下，靠右的基类R，其实例的地址和P与S不同。S::pvf覆盖了P::pvf()和R::pvf()，S::rvf()覆盖了R::rvf()。<br>&nbsp; <br>S s; S* ps = &amp;s;<br>((P*)ps)-&gt;pvf(); // (*(P*)ps)-&gt;P::vfptr[0])((S*)(P*)ps)<br>((R*)ps)-&gt;pvf(); // (*(R*)ps)-&gt;R::vfptr[0])((S*)(R*)ps)<br>ps-&gt;pvf();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // one of the above; calls S::pvf()<br>译者注：<br> 调用((P*)ps)-&gt;pvf()时，先到P的虚函数表中取出第一项，然后把ps转化为S*作为this指针传递进去；<br> 调用((R*)ps)-&gt;pvf()时，先到R的虚函数表中取出第一项，然后把ps转化为S*作为this指针传递进去；</p>
<p>因为S::pvf()覆盖了P::pvf()和R::pvf()，在S的虚函数表中，相应的项也应该被覆盖。然而，我们很快注意到，不光可以用P*，还可以用R*来调用pvf()。问题出现了：R的地址与P和S的地址不同。表达式(R*)ps与表达式(P*)ps指向类布局中不同的位置。因为函数S::pvf希望获得一个S*作为隐藏的this指针参数，虚函数必须把R*转化为S*。因此，在S对R虚函数表的拷贝中，pvf函数对应的项，指向的是一个&#8220;调整块&#8221;的地址，该调整块使用必要的计算，把R*转换为需要的S*。<br>译者注：这就是&#8220;thunk1: this-= sdPR; goto S::pvf&#8221;干的事。先根据P和R在S中的偏移，调整this为P*，也就是S*，然后跳转到相应的虚函数处执行。</p>
<p>在微软VC++实现中，对于有虚函数的多重继承，只有当派生类虚函数覆盖了多个基类的虚函数时，才使用调整块。</p>
<p>5.3 地址点与&#8220;逻辑this调整&#8221;</p>
<p>考虑下一个虚函数S::rvf()，该函数覆盖了R::rvf()。我们都知道S::rvf()必须有一个隐藏的S*类型的this参数。但是，因为也可以用R*来调用rvf()，也就是说，R的rvf虚函数槽可能以如下方式被用到：<br>&nbsp; <br>((R*)ps)-&gt;rvf(); // (*((R*)ps)-&gt;R::vfptr[1])((R*)ps)<br>&nbsp; <br>所以，大多数实现用另一个调整块将传递给rvf的R*转换为S*。还有一些实现在S的虚函数表末尾添加一个特别的虚函数项，该虚函数项提供方法，从而可以直接调用ps-&gt;rvf()，而不用先转换R*。MSC++的实现不是这样，MSC++有意将S::rvf编译为接受一个指向S中嵌套的R实例，而非指向S实例的指针（我们称这种行为是&#8220;给派生类的指针类型与该虚函数第一次被引入时接受的指针类型相同&#8221;）。所有这些在后台透明发生，对成员变量的存取，成员函数的this指针，都进行&#8220;逻辑this调整&#8221;。</p>
<p>当然，在debugger中，必须对这种this调整进行补偿。<br>&nbsp; <br>ps-&gt;rvf(); // ((R*)ps)-&gt;rvf(); // S::rvf((R*)ps)<br>译者注：调用rvf虚函数时，直接给入R*作为this指针。</p>
<p>所以，当覆盖非最左边的基类的虚函数时，MSC++一般不创建调整块，也不增加额外的虚函数项。</p>
<p>5.4 调整块</p>
<p>正如已经描述的，有时需要调整块来调整this指针的值（this指针通常位于栈上返回地址之下，或者在寄存器中），在this指针上加或减去一个常量偏移，再调用虚函数。某些实现（尤其是基于cfront的）并不使用调整块机制。它们在每个虚函数表项中增加额外的偏移数据。每当虚函数被调用时，该偏移数据（通常为0）,被加到对象的地址上，然后对象的地址再作为this指针传入。</p>
<p>ps-&gt;rvf();<br>// struct { void (*pfn)(void*); size_t disp; };<br>// (*ps-&gt;vfptr[i].pfn)(ps + ps-&gt;vfptr[i].disp);<br>译者注：当调用rvf虚函数时，前一句表示虚函数表每一项是一个结构，结构中包含偏移量；后一句表示调用第i个虚函数时，this指针使用保存在虚函数表中第i项的偏移量来进行调整。</p>
<p>这种方法的缺点是虚函数表增大了，虚函数的调用也更加复杂。</p>
<p>现代基于PC的实现一般采用&#8220;调整—跳转&#8221;技术：<br>&nbsp; <br>S::pvf-adjust: // MSC++<br>this -= SdPR;<br>goto S::pvf()<br>&nbsp; <br>当然，下面的代码序列更好（然而，当前没有任何实现采用该方法）：<br>&nbsp;<br>S::pvf-adjust:<br>this -= SdPR; // fall into S::pvf()<br>S::pvf() { ... }<br>译者注：IBM的C++编译器使用该方法。</p>
<p><br>5.5 虚继承下的虚函数</p>
<p>T虚继承P，覆盖P的虚成员函数，声明了新的虚函数。如果采用在基类虚函数表末尾添加新项的方式，则访问虚函数总要求访问虚基类。在VC++中，为了避免获取虚函数表时，转换到虚基类P的高昂代价，T中的新虚函数通过一个新的虚函数表获取，从而带来了一个新的虚函数表指针。该指针放在T实例的顶端。<br>&nbsp; <br>struct T : virtual P {<br>&nbsp;&nbsp; int t1;<br>&nbsp;&nbsp; void pvf();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // overrides P::pvf<br>&nbsp;&nbsp; virtual void tvf(); // new<br>};<br>&nbsp;<br>void T::pvf() {<br>&nbsp;&nbsp; ++p1; // ((P*)this)-&gt;p1++; // vbtable lookup!<br>&nbsp;&nbsp; ++t1; // this-&gt;t1++;<br>}<br>&nbsp; <br>如上所示，即使是在虚函数中，访问虚基类的成员变量也要通过获取虚基类表的偏移，实行计算来进行。这样做之所以必要，是因为虚函数可能被进一步继承的类所覆盖，而进一步继承的类的布局中，虚基类的位置变化了。下面就是这样的一个类：<br>&nbsp; <br>struct U : T {<br>&nbsp;&nbsp; int u1;<br>};</p>
<p>在此U增加了一个成员变量，从而改变了P的偏移。因为VC++实现中，T::pvf()接受的是嵌套在T中的P的指针，所以，需要提供一个调整块，把this指针调整到T::t1之后（该处即是P在T中的位置）。</p>
<p>5.6 特殊成员函数</p>
<p>本节讨论编译器合成到特殊成员函数中的隐藏代码。</p>
<p>5.6.1 构造函数和析构函数</p>
<p>正如我们所见，在构造和析构过程中，有时需要初始化一些隐藏的成员变量。最坏的情况下，一个构造函数要执行如下操作：</p>
<p>&nbsp; * 如果是&#8220;最终派生类&#8221;，初始化vbptr成员变量，调用虚基类的构造函数；<br>&nbsp; * 调用非虚基类的构造函数<br>&nbsp; * 调用成员变量的构造函数<br>&nbsp; * 初始化虚函数表成员变量<br>&nbsp; * 执行构造函数体中，程序所定义的其他初始化代码</p>
<p>（注意：一个&#8220;最终派生类&#8221;的实例，一定不是嵌套在其他派生类实例中的基类实例）</p>
<p>所以，如果你有一个包含虚函数的很深的继承层次，即使该继承层次由单继承构成，对象的构造可能也需要很多针对虚函数表的初始化。<br>反之，析构函数必须按照与构造时严格相反的顺序来&#8220;肢解&#8221;一个对象。</p>
<p> * 合成并初始化虚函数表成员变量<br> * 执行析构函数体中，程序定义的其他析构代码<br> * 调用成员变量的析构函数（按照相反的顺序）<br> * 调用直接非虚基类的析构函数（按照相反的顺序）<br> * 如果是&#8220;最终派生类&#8221;，调用虚基类的析构函数（按照相反顺序）</p>
<p>在VC++中，有虚基类的类的构造函数接受一个隐藏的&#8220;最终派生类标志&#8221;，标示虚基类是否需要初始化。对于析构函数，VC++采用&#8220;分层析构模型&#8221;，代码中加入一个隐藏的析构函数，该函数被用于析构包含虚基类的类（对于&#8220;最终派生类&#8221;实例而言）；代码中再加入另一个析构函数，析构不包含虚基类的类。前一个析构函数调用后一个。</p>
<p>5.6.2 虚析构函数与delete操作符</p>
<p>考虑结构V和W。<br>&nbsp; <br>struct V {<br>&nbsp;&nbsp; virtual ~V();<br>};<br>&nbsp; <br>&nbsp; struct W : V {<br>&nbsp;&nbsp; operator delete();<br>};<br>&nbsp; <br>析构函数可以为虚。一个类如果有虚析构函数的话，将会象有其他虚函数一样，拥有一个虚函数表指针，虚函数表中包含一项，其内容为指向对该类适用的虚析构函数的地址。这些机制和普通虚函数相同。虚析构函数的特别之处在于：当类实例被销毁时，虚析构函数被隐含地调用。调用地（delete发生的地方）虽然不知道销毁的动态类型，然而，要保证调用对该类型合适的delete操作符。例如，当pv指向W的实例时，当W::~W被调用之后，W实例将由W类的delete操作符来销毁。<br>&nbsp; <br>V* pv = new V;<br>delete pv;&nbsp;&nbsp; // pv-&gt;~V::V(); // use ::operator delete()<br>pv = new W;<br>delete pv;&nbsp;&nbsp; // pv-&gt;~W::W(); // use W::operator delete()<br>pv = new W;<br>::delete pv; // pv-&gt;~W::W(); // use ::operator delete()<br>译者注：<br> V没有定义delete操作符，delete时使用函数库的delete操作符；<br> W定义了delete操作符，delete时使用自己的delete操作符；<br> 可以用全局范围标示符显示地调用函数库的delete操作符。</p>
<p>为了实现上述语意，VC++扩展了其&#8220;分层析构模型&#8221;，从而自动创建另一个隐藏的析构帮助函数——&#8220;deleting析构函数&#8221;，然后，用该函数的地址来替换虚函数表中&#8220;实际&#8221;虚析构函数的地址。析构帮助函数调用对该类合适的析构函数，然后为该类有选择性地调用合适的delete操作符。</p>
<p>6 数组</p>
<p>堆上分配空间的数组使虚析构函数进一步复杂化。问题变复杂的原因有两个：<br>1、 堆上分配空间的数组，由于数组可大可小，所以，数组大小值应该和数组一起保存。因此，堆上分配空间的数组会分配额外的空间来存储数组元素的个数；<br>2、 当数组被删除时，数组中每个元素都要被正确地释放，即使当数组大小不确定时也必须成功完成该操作。然而，派生类可能比基类占用更多的内存空间，从而使正确释放比较困难。<br>&nbsp; <br>struct WW : W { int w1; };<br>pv = new W[m];<br>delete [] pv; // delete m W's&nbsp; (sizeof(W)&nbsp; == sizeof(V))<br>pv = new WW[n];<br>delete [] pv; // delete n WW's (sizeof(WW) &gt;&nbsp; sizeof(V))<br>译者注：WW从W继承，增加了一个成员变量，因此，WW占用的内存空间比W大。然而，不管指针pv指向W的数组还是WW的数组，delete[]都必须正确地释放WW或W对象占用的内存空间。</p>
<p>虽然从严格意义上来说，数组delete的多态行为C++标准并未定义，然而，微软有一些客户要求实现该行为。因此，在MSC++中，该行为是用另一个编译器生成的虚析构帮助函数来完成。该函数被称为&#8220;向量delete析构函数&#8221;（因其针对特定的类定制，比如WW，所以，它能够遍历数组的每个元素，调用对每个元素适用的析构函数）。</p>
<p>7 异常处理</p>
<p>简单说来，异常处理是C++标准委员会工作文件提供的一种机制，通过该机制，一个函数可以通知其调用者&#8220;异常&#8221;情况的发生，调用者则能据此选择合适的代码来处理异常。该机制在传统的&#8220;函数调用返回，检查错误状态代码&#8221;方法之外，给程序提供了另一种处理错误的手段。</p>
<p>因为C++是面向对象的语言，很自然地，C++中用对象来表达异常状态。并且，使用何种异常处理也是基于&#8220;抛出的&#8221;异常对象的静态或动态类型来决定的。不光如此，既然C++总是保证超出范围的对象能够被正确地销毁，异常实现也必须保证当控制从异常抛出点转换到异常&#8220;捕获&#8221;点时（栈展开），超出范围的对象能够被自动、正确地销毁。<br>考虑如下例子：<br>&nbsp; <br>struct X { X(); }; // exception object class<br>struct Z { Z(); ~Z(); }; // class with a destructor<br>extern void recover(const X&amp;);<br>void f(int), g(int);</p>
<p>int main() {<br>&nbsp;&nbsp; try {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f(0);<br>&nbsp;&nbsp; } catch (const X&amp; rx) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; recover(rx);<br>&nbsp;&nbsp; }<br>&nbsp;&nbsp; return 0;<br>}</p>
<p>void f(int i) {<br>&nbsp;&nbsp; Z z1;<br>&nbsp;&nbsp; g(i);<br>&nbsp;&nbsp; Z z2;<br>&nbsp;&nbsp; g(i-1);<br>}</p>
<p>void g(int j) {<br>&nbsp;&nbsp; if (j &lt; 0)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw X();<br>}<br>译者注：X是异常类，Z是带析构函数的工作类，recover是错误处理函数，f和g一起产生异常条件，g实际抛出异常。</p>
<p>这段程序会抛出异常。在main中，加入了处理异常的try &amp; catch框架，当调用f(0)时，f构造z1，调用g(0)后，再构造z2，再调用g(-1)，此时g发现参数为负，抛出X异常对象。我们希望在某个调用层次上，该异常能够得到处理。既然g和f都没有建立处理异常的框架，我们就只能希望main函数建立的异常处理框架能够处理X异常对象。实际上，确实如此。当控制被转移到main中异常捕获点时，从g中的异常抛出点到main中的异常捕获点之间，该范围内的对象都必须被销毁。在本例中，z2和z1应该被销毁。</p>
<p>谈到异常处理的具体实现方式，一般情况下，在抛出点和捕获点都使用&#8220;表&#8221;来表述能够捕获异常对象的类型；并且，实现要保证能够在特定的捕获点真正捕获特定的异常对象；一般地，还要运用抛出的对象来初始化捕获语句的&#8220;实参&#8221;。通过合理地选择编码方案，可以保证这些表格不会占用过多的内存空间。</p>
<p>异常处理的开销到底如何？让我们再考虑一下函数f。看起来f没有做异常处理。f确实没有包含try，catch，或者是throw关键字，因此，我们会猜异常处理应该对f没有什么影响。错！编译器必须保证一旦z1被构造，而后续调用的任何函数向f抛回了异常，异常又出了f的范围时，z1对象能被正确地销毁。同样，一旦z2被构造，编译器也必须保证后续抛出异常时，能够正确地销毁z2和z1。</p>
<p>要实现这些&#8220;展开&#8221;语意，编译器必须在后台提供一种机制，该机制在调用者函数中，针对调用的函数抛出的异常动态决定异常环境（处理点）。这可能包括在每个函数的准备工作和善后工作中增加额外的代码，在最糟糕的情况下，要针对每一套对象初始化的情况更新状态变量。例如，上述例子中，z1应被销毁的异常环境当然与z2和z1都应该被销毁的异常环境不同，因此，不管是在构造z1后，还是继而在构造z2后，VC++都要分别在状态变量中更新（存储）新的值。</p>
<p>所有这些表，函数调用的准备和善后工作，状态变量的更新，都会使异常处理功能造成可观的内存空间和运行速度开销。正如我们所见，即使在没有使用异常处理的函数中，该开销也会发生。</p>
<p>幸运的是，一些编译器可以提供编译选项，关闭异常处理机制。那些不需要异常处理机制的代码，就可以避免这些额外的开销了。</p>
<p>8 小结</p>
<p>好了，现在你可以写C++编译器了（开个玩笑）。<br>在本文中，我们讨论了许多重要的C++运行实现问题。我们发现，很多美妙的C++语言特性的开销很低，同时，其他一些美妙的特性（译者注：主要是和&#8220;虚&#8221;字相关的东西）将造成较大的开销。C++很多实现机制都是在后台默默地为你工作。一般说来，单独看一段代码时，很难衡量这段代码造成的运行时开销，必须把这段代码放到一个更大的环境中来考察，运行时开销问题才能得到比较明确的答案。</p>
<img src ="http://www.cppblog.com/bellgrade/aggbug/98371.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/bellgrade/" target="_blank">bellgrade</a> 2009-10-12 13:44 <a href="http://www.cppblog.com/bellgrade/archive/2009/10/12/98371.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>字典树(trie tree)</title><link>http://www.cppblog.com/bellgrade/archive/2009/10/08/98097.html</link><dc:creator>bellgrade</dc:creator><author>bellgrade</author><pubDate>Thu, 08 Oct 2009 10:29:00 GMT</pubDate><guid>http://www.cppblog.com/bellgrade/archive/2009/10/08/98097.html</guid><wfw:comment>http://www.cppblog.com/bellgrade/comments/98097.html</wfw:comment><comments>http://www.cppblog.com/bellgrade/archive/2009/10/08/98097.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/bellgrade/comments/commentRss/98097.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/bellgrade/services/trackbacks/98097.html</trackback:ping><description><![CDATA[今天AC了两题trie tree的题目,感觉trie的性质真的是相当的好，而且实现比较简单。它使在字符串集合中查找某个字符串的操作的复杂度降到最大只需O(n),其中n为字符串的长度。trie是典型的将时间置换为空间的算法，好在ACM中一般对空间的要求很宽松。
<div>&nbsp;&nbsp;&nbsp;&nbsp; trie的原理是利用字符串集合中字符串的公共前缀来降低时间开销以达到提高效率的目的。</div>
<div>它具有以下性质:1,根结点不包含任何字符信息;2,如果字符的种数为n,则每个结点的出度为n(这样必然会导致浪费很多空间,这也是trie的缺点,我还没有想到好点的办法避免);3,查找，插入复杂度为O(n),n为字符串长度。</div>
<div>&nbsp;&nbsp;&nbsp; 举一个例子,给50000个由小写字母构成的长度不超过10的单词,然后问某个公共前缀是否出现过。如果我们直接从字符串集中从头往后搜，看给定的字符串是否为字符串集中某个字符串的前缀，那样复杂度为O(50000^2)，这样显然会TLE。又或是我们对于字符串集中的每个字符串，我们用MAP存下它所有的前缀。然后询问时可以直接给出结果。这样复杂度为O(50000*len),最坏情况下len为字符串最长字符串的长度。而且这没有算建立MAP存储的时间，也没有算用MAP查询的时间，实际效率会更低。但如果我们用trie的话，当查询如字符串abcd是否为某字符串的前缀时，显然以b,c,d....等不是以a开头的字符串就不用查找了。实际查询复杂度只有O(len)，建立trie的复杂度为O(50000).这是完全可以接受的。</div>
<div>&nbsp;&nbsp;&nbsp; 如给定字符串集合abcd,abd,cdd,efg,hij,hi六个字符串建立的trie tree如下图所示:</div>
<div>&nbsp;&nbsp; <img src="http://blog.chinaunix.net/photo/26924_080809204058.gif"></div>
<div>&nbsp;&nbsp;&nbsp; 查找一个字符串时，我们只需从根结点按字符串中字符出现顺序依次往下走。如果到最后字符串结束时，对应的结点标记为红色，则该字符串存在;否则不存在。</div>
<div>&nbsp;&nbsp;&nbsp; 插入时也只需从根结点往下遍历，碰到已存在的字符结点就往下遍历，否则，建立新结点;最后标记最后一个字符的结点为红色即可。</div>
<div>&nbsp;&nbsp;&nbsp; 同时我们看到,如果字符的种类为n，则需要结点的个数为n级数。(谁有好办法降低空间开销,请告诉我)</div>
<div>&nbsp;</div>
<div>－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－</div>
<div>题目:<a href="http://acm.hdu.edu.cn/showproblem.php?pid=1251" target=_blank><u><font color=#810081>http://acm.hdu.edu.cn/showproblem.php?pid=1251</font></u></a></div>
<div>题目和我上面举的例子差不多，是说给定一个字符串集合，然后每次询问时给出一个字符串，问以该字符串为前缀的字符串在集合中有多少个。先给个用MAP版本的，限时2000MS的题目，用MAP，1750MS，险过。</div>
<div>my code1:</div>
<div>&nbsp;</div>
<table style="BORDER-COLLAPSE: collapse" borderColor=#999999 cellSpacing=0 cellPadding=0 width="95%" bgColor=#f1f1f1 border=1>
    <tbody>
        <tr>
            <td>
            <p style="MARGIN: 5px; LINE-HEIGHT: 150%"><code><span style="COLOR: #000000"><span style="COLOR: #0000cc">#</span><span style="COLOR: #ff0000">include</span><span style="COLOR: #0000cc">&lt;</span><span style="COLOR: #ff0000">iostream</span><span style="COLOR: #0000cc">&gt;</span><br><span style="COLOR: #0000cc">#</span><span style="COLOR: #ff0000">include</span><span style="COLOR: #0000cc">&lt;</span><span style="COLOR: #ff0000">map</span><span style="COLOR: #0000cc">&gt;</span><br><span style="COLOR: #0000cc">#</span><span style="COLOR: #ff0000">include</span><span style="COLOR: #0000cc">&lt;</span><span style="COLOR: #ff0000">string</span><span style="COLOR: #0000cc">&gt;</span><br><span style="COLOR: #0000ff">using</span> <span style="COLOR: #0000ff">namespace</span> <span style="COLOR: #ff0000">std</span><span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000ff">int</span> main<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><br><span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">int</span> i<span style="COLOR: #0000cc">,</span>j<span style="COLOR: #0000cc">,</span>k<span style="COLOR: #0000cc">,</span>len<span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff0000">string</span> str<span style="COLOR: #0000cc">;</span><span style="COLOR: #0000ff">char</span> temp<span style="COLOR: #0000cc">[</span>15<span style="COLOR: #0000cc">]</span><span style="COLOR: #0000cc">,</span>temp1<span style="COLOR: #0000cc">[</span>15<span style="COLOR: #0000cc">]</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff0000">map</span> <span style="COLOR: #0000cc">&lt;</span><span style="COLOR: #ff0000">string</span><span style="COLOR: #0000cc">,</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #0000cc">&gt;</span> mymap<span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">while</span><span style="COLOR: #0000cc">(</span><span style="COLOR: #ff0000">gets</span><span style="COLOR: #0000cc">(</span>temp<span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">)</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">if</span><span style="COLOR: #0000cc">(</span>temp<span style="COLOR: #0000cc">[</span>0<span style="COLOR: #0000cc">]</span><span style="COLOR: #0000cc">=</span><span style="COLOR: #0000cc">=</span><span style="COLOR: #ff00ff">'\n'</span><span style="COLOR: #0000cc">)</span> <span style="COLOR: #0000ff">break</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;len<span style="COLOR: #0000cc">=</span><span style="COLOR: #ff0000">strlen</span><span style="COLOR: #0000cc">(</span>temp<span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">if</span><span style="COLOR: #0000cc">(</span>len<span style="COLOR: #0000cc">=</span><span style="COLOR: #0000cc">=</span>0<span style="COLOR: #0000cc">)</span> <span style="COLOR: #0000ff">break</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">for</span><span style="COLOR: #0000cc">(</span>i<span style="COLOR: #0000cc">=</span>0<span style="COLOR: #0000cc">;</span>i<span style="COLOR: #0000cc">&lt;</span>len<span style="COLOR: #0000cc">;</span>i<span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">)//求出某个字符串的所有前缀,并用MAP存起来</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">for</span><span style="COLOR: #0000cc">(</span>j<span style="COLOR: #0000cc">=</span>0<span style="COLOR: #0000cc">;</span>j<span style="COLOR: #0000cc">&lt;</span><span style="COLOR: #0000cc">=</span>i<span style="COLOR: #0000cc">;</span>j<span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">)</span> temp1<span style="COLOR: #0000cc">[</span>j<span style="COLOR: #0000cc">]</span><span style="COLOR: #0000cc">=</span>temp<span style="COLOR: #0000cc">[</span>j<span style="COLOR: #0000cc">]</span><span style="COLOR: #0000cc">;</span>temp1<span style="COLOR: #0000cc">[</span>j<span style="COLOR: #0000cc">]</span><span style="COLOR: #0000cc">=</span><span style="COLOR: #ff00ff">'\0'</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;str<span style="COLOR: #0000cc">.</span>assign<span style="COLOR: #0000cc">(</span>temp1<span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mymap<span style="COLOR: #0000cc">[</span>str<span style="COLOR: #0000cc">]</span><span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000cc">}</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000cc">}</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">while</span><span style="COLOR: #0000cc">(</span><span style="COLOR: #ff0000">scanf</span><span style="COLOR: #0000cc">(</span><span style="COLOR: #ff00ff">"%s"</span><span style="COLOR: #0000cc">,</span><span style="COLOR: #0000cc">&amp;</span>temp<span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">!</span><span style="COLOR: #0000cc">=</span><span style="COLOR: #ff0000">EOF</span><span style="COLOR: #0000cc">)</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff0000">cout</span><span style="COLOR: #0000cc">&lt;</span><span style="COLOR: #0000cc">&lt;</span>mymap<span style="COLOR: #0000cc">[</span>temp<span style="COLOR: #0000cc">]</span><span style="COLOR: #0000cc">&lt;</span><span style="COLOR: #0000cc">&lt;</span><span style="COLOR: #ff0000">endl</span><span style="COLOR: #0000cc">;//此时直接输出结果即可</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">return</span> 0<span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000cc">}</span></span></code></p>
            </td>
        </tr>
    </tbody>
</table>
<p>用MAP的特点是代码短，思路简单，很容易实现，但耗时大。下面给出trie版本的。</p>
<p>my code2:</p>
<table style="BORDER-COLLAPSE: collapse" borderColor=#999999 cellSpacing=0 cellPadding=0 width="95%" bgColor=#f1f1f1 border=1>
    <tbody>
        <tr>
            <td>
            <p style="MARGIN: 5px; LINE-HEIGHT: 150%"><code><span style="COLOR: #000000"><span style="COLOR: #0000cc">#</span><span style="COLOR: #ff0000">include</span><span style="COLOR: #0000cc">&lt;</span><span style="COLOR: #ff0000">iostream</span><span style="COLOR: #0000cc">&gt;</span><br><span style="COLOR: #0000ff">using</span> <span style="COLOR: #0000ff">namespace</span> <span style="COLOR: #ff0000">std</span><span style="COLOR: #0000cc">;</span><br><br><span style="COLOR: #0000ff">const</span> <span style="COLOR: #0000ff">int</span> kind<span style="COLOR: #0000cc">=</span>26<span style="COLOR: #0000cc">;//字母种类</span><br><br><span style="COLOR: #0000ff">struct</span> Treenode//树的结点结构<br><span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">int</span> <span style="COLOR: #ff0000">count</span><span style="COLOR: #0000cc">;//这个附加变量在本题中记录遍历到该结点形成的字符串出现的次数，在不同题中可记录不同的内容。</span><br>&nbsp;&nbsp;&nbsp;&nbsp;Treenode <span style="COLOR: #0000cc">*</span>next<span style="COLOR: #0000cc">[</span>kind<span style="COLOR: #0000cc">]</span><span style="COLOR: #0000cc">;//指向儿子结点</span><br>&nbsp;&nbsp;&nbsp;&nbsp;Treenode<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)//每个结点的初始化</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff0000">count</span><span style="COLOR: #0000cc">=</span>1<span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">for</span><span style="COLOR: #0000cc">(</span><span style="COLOR: #0000ff">int</span> i<span style="COLOR: #0000cc">=</span>0<span style="COLOR: #0000cc">;</span>i<span style="COLOR: #0000cc">&lt;</span>kind<span style="COLOR: #0000cc">;</span>i<span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">)</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;next<span style="COLOR: #0000cc">[</span>i<span style="COLOR: #0000cc">]</span><span style="COLOR: #0000cc">=</span><span style="COLOR: #ff0000">NULL</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000cc">}</span><br><span style="COLOR: #0000cc">}</span><span style="COLOR: #0000cc">;</span><br><br><span style="COLOR: #0000ff">void</span> insert<span style="COLOR: #0000cc">(</span>Treenode <span style="COLOR: #0000cc">*</span><span style="COLOR: #0000cc">&amp;</span>root<span style="COLOR: #0000cc">,</span><span style="COLOR: #0000ff">char</span> <span style="COLOR: #0000cc">*</span>word<span style="COLOR: #0000cc">)//向以root为根结点的树中插入串word</span><br><span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;&nbsp;&nbsp;Treenode <span style="COLOR: #0000cc">*</span>location<span style="COLOR: #0000cc">=</span>root<span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">int</span> i<span style="COLOR: #0000cc">=</span>0<span style="COLOR: #0000cc">,</span>branch<span style="COLOR: #0000cc">=</span>0<span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">if</span><span style="COLOR: #0000cc">(</span>location<span style="COLOR: #0000cc">=</span><span style="COLOR: #0000cc">=</span><span style="COLOR: #ff0000">NULL</span><span style="COLOR: #0000cc">)</span> <span style="COLOR: #0000cc">{</span>location<span style="COLOR: #0000cc">=</span><span style="COLOR: #0000ff">new</span> Treenode<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span>root<span style="COLOR: #0000cc">=</span>location<span style="COLOR: #0000cc">;</span><span style="COLOR: #0000cc">}</span><br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">while</span><span style="COLOR: #0000cc">(</span>word<font color=#0000cc>[</font>i<font color=#0000cc>]</font><span style="COLOR: #0000cc">)</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;branch<span style="COLOR: #0000cc">=</span>word<span style="COLOR: #0000cc">[</span>i<span style="COLOR: #0000cc">]</span><span style="COLOR: #0000cc">-</span><span style="COLOR: #ff00ff">'a'</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">if</span><span style="COLOR: #0000cc">(</span>location<span style="COLOR: #0000cc">-</span><span style="COLOR: #0000cc">&gt;</span>next<span style="COLOR: #0000cc">[</span>branch<span style="COLOR: #0000cc">]</span><span style="COLOR: #0000cc">)</span> location<span style="COLOR: #0000cc">-</span><span style="COLOR: #0000cc">&gt;</span>next<span style="COLOR: #0000cc">[</span>branch<span style="COLOR: #0000cc">]</span><span style="COLOR: #0000cc">-</span><span style="COLOR: #0000cc">&gt;</span><span style="COLOR: #ff0000">count</span><span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">;//如果该字符存在，串数量加1</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">else</span> location<span style="COLOR: #0000cc">-</span><span style="COLOR: #0000cc">&gt;</span>next<span style="COLOR: #0000cc">[</span>branch<span style="COLOR: #0000cc">]</span><span style="COLOR: #0000cc">=</span><span style="COLOR: #0000ff">new</span> Treenode<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;//如果不存在，建新结点</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i<span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;location<span style="COLOR: #0000cc">=</span>location<span style="COLOR: #0000cc">-</span><span style="COLOR: #0000cc">&gt;</span>next<span style="COLOR: #0000cc">[</span>branch<span style="COLOR: #0000cc">]</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000cc">}</span><br><span style="COLOR: #0000cc">}</span><br><br><span style="COLOR: #0000ff">int</span> <span style="COLOR: #ff0000">search</span><span style="COLOR: #0000cc">(</span>Treenode <span style="COLOR: #0000cc">*</span>root<span style="COLOR: #0000cc">,</span><span style="COLOR: #0000ff">char</span> <span style="COLOR: #0000cc">*</span>word<span style="COLOR: #0000cc">)//查找，与插入类似</span><br><span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;&nbsp;&nbsp;Treenode <span style="COLOR: #0000cc">*</span>location<span style="COLOR: #0000cc">=</span>root<span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">int</span> i<span style="COLOR: #0000cc">=</span>0<span style="COLOR: #0000cc">,</span>branch<span style="COLOR: #0000cc">=</span>0<span style="COLOR: #0000cc">,</span>ans<span style="COLOR: #0000cc">;</span><br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">if</span><span style="COLOR: #0000cc">(</span>location<span style="COLOR: #0000cc">=</span><span style="COLOR: #0000cc">=</span><span style="COLOR: #ff0000">NULL</span><span style="COLOR: #0000cc">)</span> <span style="COLOR: #0000ff">return</span> 0<span style="COLOR: #0000cc">;</span><br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">while</span><span style="COLOR: #0000cc">(</span>word<font color=#0000cc>[</font>i<font color=#0000cc>]</font><span style="COLOR: #0000cc">)</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;branch<span style="COLOR: #0000cc">=</span>word<span style="COLOR: #0000cc">[</span>i<span style="COLOR: #0000cc">]</span><span style="COLOR: #0000cc">-</span><span style="COLOR: #ff00ff">'a'</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">if</span><span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">!</span>location<span style="COLOR: #0000cc">-</span><span style="COLOR: #0000cc">&gt;</span>next<span style="COLOR: #0000cc">[</span>branch<span style="COLOR: #0000cc">]</span><span style="COLOR: #0000cc">)</span> <span style="COLOR: #0000ff">return</span> 0<span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i<span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;location<span style="COLOR: #0000cc">=</span>location<span style="COLOR: #0000cc">-</span><span style="COLOR: #0000cc">&gt;</span>next<span style="COLOR: #0000cc">[</span>branch<span style="COLOR: #0000cc">]</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ans<span style="COLOR: #0000cc">=</span>location<span style="COLOR: #0000cc">-</span><span style="COLOR: #0000cc">&gt;</span><span style="COLOR: #ff0000">count</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000cc">}</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">return</span> ans<span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000cc">}</span><br><span style="COLOR: #0000ff">int</span> main<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><br><span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">char</span> word<span style="COLOR: #0000cc">[</span>10<span style="COLOR: #0000cc">]</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">char</span> ask<span style="COLOR: #0000cc">[</span>10<span style="COLOR: #0000cc">]</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;Treenode <span style="COLOR: #0000cc">*</span>root<span style="COLOR: #0000cc">=</span><span style="COLOR: #ff0000">NULL</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">while</span><span style="COLOR: #0000cc">(</span><span style="COLOR: #ff0000">gets</span><span style="COLOR: #0000cc">(</span>word<span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">)</span> <br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">if</span><span style="COLOR: #0000cc">(</span>word<span style="COLOR: #0000cc">[</span>0<span style="COLOR: #0000cc">]</span><span style="COLOR: #0000cc">=</span><span style="COLOR: #0000cc">=</span><span style="COLOR: #ff00ff">'\0'</span><span style="COLOR: #0000cc">)</span> <span style="COLOR: #0000ff">break</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;insert<span style="COLOR: #0000cc">(</span>root<span style="COLOR: #0000cc">,</span>word<span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000cc">}</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">while</span><span style="COLOR: #0000cc">(</span><span style="COLOR: #ff0000">gets</span><span style="COLOR: #0000cc">(</span>ask<span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">)</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff0000">cout</span><span style="COLOR: #0000cc">&lt;</span><span style="COLOR: #0000cc">&lt;</span><span style="COLOR: #ff0000">search</span><span style="COLOR: #0000cc">(</span>root<span style="COLOR: #0000cc">,</span>ask<span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">&lt;</span><span style="COLOR: #0000cc">&lt;</span><span style="COLOR: #ff0000">endl</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">return</span> 0<span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000cc">}</span></span></code></p>
            </td>
        </tr>
    </tbody>
</table>
<p>上述代码中插入和查找可当模板来用了。。。</p>
<img src ="http://www.cppblog.com/bellgrade/aggbug/98097.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/bellgrade/" target="_blank">bellgrade</a> 2009-10-08 18:29 <a href="http://www.cppblog.com/bellgrade/archive/2009/10/08/98097.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Hash函数设计优化</title><link>http://www.cppblog.com/bellgrade/archive/2009/10/06/97926.html</link><dc:creator>bellgrade</dc:creator><author>bellgrade</author><pubDate>Tue, 06 Oct 2009 03:24:00 GMT</pubDate><guid>http://www.cppblog.com/bellgrade/archive/2009/10/06/97926.html</guid><wfw:comment>http://www.cppblog.com/bellgrade/comments/97926.html</wfw:comment><comments>http://www.cppblog.com/bellgrade/archive/2009/10/06/97926.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/bellgrade/comments/commentRss/97926.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/bellgrade/services/trackbacks/97926.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: &nbsp;【摘要】Hash是一种在信息学竞赛中经常用到的数据结构。一个好的Hash函数可以很大程度上提高程序的整体时间效率和空间效率。本文对面向各种不同标本（关键值）的Hash函数进行讨论，并对多种常用的Hash函数进行了分析和总结。&nbsp;【关键字】Hash 函数，字符串，整数，实数，排列组合&nbsp;【正文】对于一个Hash函数，评价其优劣的标准应为随机...&nbsp;&nbsp;<a href='http://www.cppblog.com/bellgrade/archive/2009/10/06/97926.html'>阅读全文</a><img src ="http://www.cppblog.com/bellgrade/aggbug/97926.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/bellgrade/" target="_blank">bellgrade</a> 2009-10-06 11:24 <a href="http://www.cppblog.com/bellgrade/archive/2009/10/06/97926.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>动态规划算法解析</title><link>http://www.cppblog.com/bellgrade/archive/2009/10/04/97839.html</link><dc:creator>bellgrade</dc:creator><author>bellgrade</author><pubDate>Sun, 04 Oct 2009 05:13:00 GMT</pubDate><guid>http://www.cppblog.com/bellgrade/archive/2009/10/04/97839.html</guid><wfw:comment>http://www.cppblog.com/bellgrade/comments/97839.html</wfw:comment><comments>http://www.cppblog.com/bellgrade/archive/2009/10/04/97839.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/bellgrade/comments/commentRss/97839.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/bellgrade/services/trackbacks/97839.html</trackback:ping><description><![CDATA[<p>最优化原理<br>&nbsp;&nbsp; 1951年美国数学家R．Bellman等人，根据一类多阶段问题的特点，把多阶段决策问题变换为一系列互相联系的单阶段问题，然后逐个加以解决。一些静态模型，只要人为地引进&#8220;时间&#8221;因素，分成时段，就可以转化成多阶段的动态模型，用动态规划方法去处理。与此同时，他提出了解决这类问题的&#8220;最优化原理&#8221;(Principle of optimality)：<br>&nbsp;&nbsp;&nbsp; &#8220;一个过程的最优决策具有这样的性质：即无论其初始状态和初始决策如何，其今后诸策略对以第一个决策所形成的状态作为初始状态的过程而言，必须构成最优策略&#8221;。简言之，一个最优策略的子策略，对于它的初态和终态而言也必是最优的。<br>&nbsp;&nbsp;&nbsp; 这个&#8220;最优化原理&#8221;如果用数学化一点的语言来描述的话，就是：假设为了解决某一优化问题，需要依次作出n个决策D1，D2，&#8230;，Dn，如若这个决策序列是最优的，对于任何一个整数k，1 &lt; k &lt; n，不论前面k个决策是怎样的，以后的最优决策只取决于由前面决策所确定的当前状态，即以后的决策Dk+1，Dk+2，&#8230;，Dn也是最优的。<br>&nbsp;&nbsp;&nbsp; 最优化原理是动态规划的基础。任何一个问题，如果失去了这个最优化原理的支持，就不可能用动态规划方法计算。能采用动态规划求解的问题都需要满足一定的条件： <br>&nbsp;&nbsp;&nbsp; (1) 问题中的状态必须满足最优化原理；<br>&nbsp;&nbsp;&nbsp; (2) 问题中的状态必须满足无后效性。<br>&nbsp;&nbsp;&nbsp; 所谓的无后效性是指：&#8220;下一时刻的状态只与当前状态有关，而和当前状态之前的状态无关，当前的状态是对以往决策的总结&#8221;。 </p>
<p>问题求解模式 <br>&nbsp;&nbsp;&nbsp; 动态规划所处理的问题是一个多阶段决策问题，一般由初始状态开始，通过对中间阶段决策的选择，达到结束状态。这些决策形成了一个决策序列，同时确定了完成整个过程的一条活动路线(通常是求最优的活动路线)。如图所示。动态规划的设计都有着一定的模式，一般要经历以下几个步骤。<br><br>&nbsp;&nbsp; 初始状态&#8594;│决策１│&#8594;│决策２│&#8594;&#8230;&#8594;│决策ｎ│&#8594;结束状态<br>&nbsp;&nbsp;&nbsp;&nbsp; 图1 动态规划决策过程示意图</p>
<p>&nbsp;&nbsp;&nbsp; (1)划分阶段：按照问题的时间或空间特征，把问题分为若干个阶段。在划分阶段时，注意划分后的阶段一定要是有序的或者是可排序的，否则问题就无法求解。<br>&nbsp;&nbsp;&nbsp; (2)确定状态和状态变量：将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然，状态的选择要满足无后效性。<br>&nbsp;&nbsp;&nbsp; (3)确定决策并写出状态转移方程：因为决策和状态转移有着天然的联系，状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以如果确定了决策，状态转移方程也就可写出。但事实上常常是反过来做，根据相邻两段各状态之间的关系来确定决策。<br>&nbsp;&nbsp;&nbsp; (4)寻找边界条件：给出的状态转移方程是一个递推式，需要一个递推的终止条件或边界条件。</p>
<p>算法实现<br>&nbsp;&nbsp;&nbsp; 动态规划的主要难点在于理论上的设计，也就是上面4个步骤的确定，一旦设计完成，实现部分就会非常简单。使用动态规划求解问题，最重要的就是确定动态规划三要素：问题的阶段,每个阶段的状态以及从前一个阶段转化到后一个阶段之间的递推关系。递推关系必须是从次小的问题开始到较大的问题之间的转化，从这个角度来说，动态规划往往可以用递归程序来实现，不过因为递推可以充分利用前面保存的子问题的解来减少重复计算，所以对于大规模问题来说，有递归不可比拟的优势，这也是动态规划算法的核心之处。确定了动态规划的这三要素，整个求解过程就可以用一个最优决策表来描述，最优决策表是一个二维表，其中行表示决策的阶段，列表示问题状态，表格需要填写的数据一般对应此问题的在某个阶段某个状态下的最优值（如最短路径，最长公共子序列，最大价值等），填表的过程就是根据递推关系，从1行1列开始，以行或者列优先的顺序，依次填写表格，最后根据整个表格的数据通过简单的取舍或者运算求得问题的最优解。下面分别以求解最大化投资回报问题和最长公共子序列问题为例阐述用动态规划算法求解问题的一般思路。</p>
<p>1. 最大化投资回报问题：某人有一定的资金用来购买不同面额的债卷，不同面额债卷的年收益是不同的，求给定资金，年限以及债卷面额、收益的情况下怎样购买才能使此人获得最大投资回报。<br>&nbsp;&nbsp;&nbsp; 程序输入约定：第一行第一列表示资金（1000的倍数）总量，第二列表示投资年限；第二行表示债卷面额总数；从第三行开始每行表示一种债卷，占用两列，前一列表示债卷面额，后一列表示其年收益，如下输入实例，<br>10000 1<br>2<br>4000 400<br>3000 250<br>程序实现如下，注释几乎说明了一切，所以不再另外分析。<br>/// 此数组是算法的关键存储结构，用来存储不同阶段各种债卷<br>/// 组合下对应可获取的最大利息。<br>int saifa[80005]; </p>
<p>/// 此函数用于计算当前债卷在不同购买额下的最优利息情况，<br>/// 注意此时的利息情况是基于上一次债卷的情况下计算得到的，<br>/// 也就是说当前利息最优是基于上一次利息最优的基础上计算出来的，<br>/// 这也正好体现了动态规划中&#8220;最优化原则&#8221;：不管前面的策略如何，<br>/// 此后的决策必须是基于当前状态（由上一次决策产生）的最优决策。<br>/*<br>&nbsp;&nbsp;&nbsp; 动态规划的求解过程一般都可以用一个最优决策表来描述，<br>&nbsp;&nbsp;&nbsp; 对于本程序，以示例输入为例，对于第一年，其最优决策表如下：<br>&nbsp;&nbsp;&nbsp; 0 1 2 3&nbsp;&nbsp; 4&nbsp;&nbsp; 5&nbsp;&nbsp; 6&nbsp;&nbsp; 7&nbsp;&nbsp; 8&nbsp;&nbsp; 9&nbsp;&nbsp; 10(*1000)&nbsp; -- (1)<br>&nbsp;&nbsp;&nbsp; 0 0 0 0&nbsp;&nbsp; 400 400 400 400 800 800 800&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -- (2)<br>&nbsp;&nbsp;&nbsp; 0 0 0 250 400 400 500 650 800 900 900&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -- (3)<br>&nbsp;&nbsp;&nbsp; (1) -- 表示首先选利息为400的债卷在对应资金下的最优利息。<br>&nbsp;&nbsp;&nbsp; (2) -- 表示可用来购买债卷的资金。<br>&nbsp;&nbsp;&nbsp; (3) -- 表示在已有状态下再选择利息为300的债卷在对应资金下的最优利息。<br>&nbsp;&nbsp;&nbsp; 注意上面表格，在求购买利息为300的债卷获得的最优收益的时候，<br>&nbsp;&nbsp;&nbsp; 参考了以前的最优状态，以3行8列的650为例，7(*1000)可以<br>&nbsp;&nbsp;&nbsp; 在以前购买了0张4000的债卷的基础上再2张3000的，也可以在以前购<br>&nbsp;&nbsp;&nbsp; 买了1张4000的基础上再买1张3000，经比较取其收益大的，这就是典<br>&nbsp;&nbsp;&nbsp; 型的动态规划中的当前最优状态计算。<br>&nbsp;&nbsp;&nbsp; 本程序中把上面的最优决策二维表用一个一维数组表示，值得借鉴。 <br>*/<br>void add(int a,int b)<br>{ cout &lt;&lt; a &lt;&lt; " " &lt;&lt; b &lt;&lt; endl; // for debug<br>&nbsp;for(int i=0;i&lt;=80000;i++)<br>&nbsp;{<br>&nbsp; if(i+a &gt; 80000)<br>&nbsp; {<br>&nbsp;&nbsp; break;<br>&nbsp; }</p>
<p>&nbsp; if(saifa[i]+b &gt; saifa[i+a]) // 累计同时购买多种债卷时的利息<br>&nbsp; {<br>&nbsp;&nbsp; saifa[i+a] = saifa[i] + b;<br>&nbsp; }</p>
<p>&nbsp; if(i&lt;200) // for debug<br>&nbsp;&nbsp; cout &lt;&lt; i &lt;&lt; "-" &lt;&lt; saifa[i] &lt;&lt; " ";<br>&nbsp;}<br>&nbsp;cout &lt;&lt; endl; // for debug<br>}</p>
<p>int main(void)<br>{<br>&nbsp;int n,d,money,year,pay,bond;<br>&nbsp;int ii,i;</p>
<p>&nbsp;scanf("%d",&amp;n);<br>&nbsp;for(ii=0;ii&lt;n;ii++)<br>&nbsp;{<br>&nbsp; memset(saifa,0,sizeof(saifa));<br>&nbsp; scanf("%d%d",&amp;money,&amp;year);<br>&nbsp; scanf("%d",&amp;d);</p>
<p>&nbsp; for(i=0;i&lt;d;i++)<br>&nbsp; {<br>&nbsp;&nbsp; scanf("%d%d",&amp;pay,&amp;bond);<br>&nbsp;&nbsp; add(pay/1000,bond);<br>&nbsp; }</p>
<p>&nbsp; // 计算指定年限内最优组合的本金利息总额<br>&nbsp; for(i=0;i&lt;year;i++)<br>&nbsp; { cout &lt;&lt; saifa[money/1000] &lt;&lt; " "; // for debug<br>&nbsp;&nbsp; money += saifa[money/1000]; <br>&nbsp; }<br>&nbsp; cout &lt;&lt; endl; // for debug</p>
<p>&nbsp; printf("%d\n",money);<br>&nbsp;}</p>
<p>&nbsp;return 0;<br>}<br>上述程序实现方法同样适合于背包问题，最优库存问题等，只是针对具体情况，最优决策表的表示和生成会有所不同。</p>
<p>2. 最长公共子串问题：一个给定序列的子序列是在该序列中删去若干元素后得到的序列。给定两个序列X和Y，当另一序列Z既是X的子序列又是Y的子序列时，称Z是序列X和Y的公共子序列。最长公共子串就是求给定两个序列的一个最长公共子序列。例如，X=&#8220;ABCBDAB&#8221;，Y=&#8220;BCDB&#8221;是X的一个子序列。<br>问题分析：<br>&nbsp;&nbsp;&nbsp; 给定两个序列A和B，称序列Z是A和B的公共子序列，是指Z同是A和B的子序列。问题要求已知两序列A和B的最长公共子序列。如采用列举A的所有子序列，并一一检查其是否又是B的子序列，并随时记录所发现的子序列，最终求出最长公共子序列。这种方法因耗时太多而不可取。<br>&nbsp;&nbsp;&nbsp; 考虑最长公共子序列问题如何分解成子问题，设A=&#8220;a0，a1，&#8230;，am-1&#8221;，B=&#8220;b0，b1，&#8230;，bm-1&#8221;，并Z=&#8220;z0，z1，&#8230;，zk-1&#8221;为它们的最长公共子序列。不难证明有以下性质： <br>（1） 如果am-1=bn-1，则zk-1=am-1=bn-1，且&#8220;z0，z1，&#8230;，zk-2&#8221;是&#8220;a0，a1，&#8230;，am-2&#8221;和&#8220;b0，b1，&#8230;，bn-2&#8221;的一个最长公共子序列； <br>（2） 如果am-1!=bn-1，则若zk-1!=am-1，蕴涵&#8220;z0，z1，&#8230;，zk-1&#8221;是&#8220;a0，a1，&#8230;，am-2&#8221;和&#8220;b0，b1，&#8230;，bn-1&#8221;的一个最长公共子序列； <br>（3） 如果am-1!=bn-1，则若zk-1!=bn-1，蕴涵&#8220;z0，z1，&#8230;，zk-1&#8221;是&#8220;a0，a1，&#8230;，am-1&#8221;和&#8220;b0，b1，&#8230;，bn-2&#8221;的一个最长公共子序列。 <br>这样，在找A和B的公共子序列时，如有am-1=bn-1，则进一步解决一个子问题，找&#8220;a0，a1，&#8230;，am-2&#8221;和&#8220;b0，b1，&#8230;，bm-2&#8221;的一个 最长公共子序列；如果am-1!=bn-1，则要解决两个子问题，找出&#8220;a0，a1，&#8230;，am-2&#8221;和&#8220;b0，b1，&#8230;，bn-1&#8221;的一个最长公共子序列 和找出&#8220;a0，a1，&#8230;，am-1&#8221;和&#8220;b0，b1，&#8230;，bn-2&#8221;的一个最长公共子序列，再取两者中较长者作为A和B的最长公共子序列。<br>&nbsp;&nbsp;&nbsp; 为了节约重复求相同子问题的时间，引入一个数组，不管它们是否对最终解有用，把所有子问题的解存于该数组中，这就是动态规划法所采用的基本方法，具体说明如下。 <br>定义c[i][j]为序列&#8220;a0，a1，&#8230;，ai-2&#8221;和&#8220;b0，b1，&#8230;，bj-1&#8221;的最长公共子序列的长度，计算c[i][j]可递归地表述如下： <br>（1）c[i][j] = 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果i=0或j=0； <br>（2）c[i][j] = c[i-1][j-1]+1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果i,j&gt;0，且a[i-1] = b[j-1]； <br>（3）c[i][j] = max{c[i][j-1], c[i-1][j]} 如果i,j&gt;0，且a[i-1] != b[j-1]。 <br>按此算式可写出计算两个序列的最长公共子序列的长度函数。由于c[i][j]的产生仅依赖于c[i-1][j-1]、c[i-1][j]和c[i][j-1]，故可以从c[m][n]开始，跟踪c[i][j]的产生过程，逆向构造出最长公共子序列。细节见程序。 </p>
<p><br>#include &lt;stdio.h&gt;<br>#include &lt;string.h&gt;</p>
<p>#define N 100</p>
<p>char a[N], b[N], str[N];<br>int c[N][N];</p>
<p>int lcs_len(char* a, char* b, int c[][N])<br>{<br>&nbsp;&nbsp;&nbsp; int m = strlen(a), n = strlen(b), i, j;</p>
<p>&nbsp;&nbsp;&nbsp; for( i=0; i&lt;=m; i++ )&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c[i][0]=0;<br>&nbsp;&nbsp;&nbsp; for( i=0; i&lt;=n; i++ )&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c[0][i]=0;</p>
<p>&nbsp;&nbsp;&nbsp; for( i=1; i&lt;=m; i++ )&nbsp; <br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for( j=1; j&lt;=n; j++ )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (a[i-1]==b[j-1])<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c[i][j]=c[i-1][j-1]+1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else if (c[i-1][j]&gt;=c[i][j-1])<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c[i][j]=c[i-1][j];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c[i][j]=c[i][j-1];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; return c[m][n];<br>}</p>
<p>char* build_lcs(char s[], char* a, char* b)<br>{<br>&nbsp;&nbsp;&nbsp; int i = strlen(a), j = strlen(b);<br>&nbsp;&nbsp;&nbsp; int k = lcs_len(a,b,c);<br>&nbsp;&nbsp;&nbsp; s[k] = '\0';<br>&nbsp;&nbsp;&nbsp; while( k&gt;0 )<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (c[i][j]==c[i-1][j])&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i--;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else if (c[i][j]==c[i][j-1])&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; j--;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s[--k]=a[i-1];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i--; j--;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; return s;<br>}</p>
<p>void main()<br>{&nbsp; <br>&nbsp;&nbsp;&nbsp; printf("Enter two string (length &lt; %d) :\n",N);<br>&nbsp;&nbsp;&nbsp; scanf("%s%s",a,b);<br>&nbsp;&nbsp;&nbsp; printf("LCS=%s\n",build_lcs(str,a,b)); <br>}</p>
<img src ="http://www.cppblog.com/bellgrade/aggbug/97839.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/bellgrade/" target="_blank">bellgrade</a> 2009-10-04 13:13 <a href="http://www.cppblog.com/bellgrade/archive/2009/10/04/97839.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++拾遗之基础</title><link>http://www.cppblog.com/bellgrade/archive/2009/09/30/97662.html</link><dc:creator>bellgrade</dc:creator><author>bellgrade</author><pubDate>Wed, 30 Sep 2009 08:21:00 GMT</pubDate><guid>http://www.cppblog.com/bellgrade/archive/2009/09/30/97662.html</guid><wfw:comment>http://www.cppblog.com/bellgrade/comments/97662.html</wfw:comment><comments>http://www.cppblog.com/bellgrade/archive/2009/09/30/97662.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/bellgrade/comments/commentRss/97662.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/bellgrade/services/trackbacks/97662.html</trackback:ping><description><![CDATA[&nbsp;
<p><span>1</span><span>、数组与指针</span></p>
<p><span>假如</span><span>int a[3][4];</span></p>
<p><span>则</span><span> a[i][j] == *(a[i]+j) == *(*(a+i)+j)</span></p>
<p><span>数组名就是数组内存的首地址</span></p>
<p><span><span>1、&nbsp;</span></span><span>函数的指针</span></p>
<p><span>一个函数被编译和连接后，要占用一定的内存区，它的首地址就是该函数的指针，函数的指针也是该函数的入口地址。</span></p>
<p><span>假设</span><span>p=max()</span><span>函数。</span><span>p</span><span>是函数指针，且</span><span>max</span><span>有返回值，则下列</span></p>
<p><span>c=max(a,b);</span></p>
<p><span>c=(*p)(a,b)</span></p>
<p><span>c=p(a,b)</span></p>
<p><span>都是等价的，对</span><span>p</span><span>不能做增减运算</span></p>
<p><span><span>2、&nbsp;</span></span><span>内联函数</span></p>
<p><span>*</span><span>不能有多分支语句和循环语句，否则作普通函数</span></p>
<p><span>*</span><span>不能是递归子函数</span></p>
<p><span>*</span><span>所含语句行数应该为</span><span>1~5</span><span>行</span></p>
<p><span>4</span><span>、带缺省参数的函数</span></p>
<p><span>从友到左被定义，右边不能含有缺省参数</span></p>
<p><span>5</span><span>、</span> <span>void Sort(int *&amp;val)<span>&nbsp;&nbsp; </span>val</span><span>是个</span><span>int*</span><span>型的引用型变量</span></p>
<p><span>6</span><span>、</span><span>Q: </span><span>带符号数赋予不带符号数的变量</span></p>
<p><span><span>&nbsp;&nbsp; </span>A: short&nbsp;x=-35;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>unsigned short y;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>y=x;</span></p>
<p><span><span>&nbsp;&nbsp; </span></span></p>
<p><span><span>&nbsp;&nbsp; </span>x</span><span>原</span><span>=-35=1000 0000 0010 0011</span></p>
<p><span><span>&nbsp;&nbsp; </span>x</span><span>补</span><span>=<span>&nbsp;&nbsp;&nbsp; </span>1111 1111 1101 1101</span></p>
<p><span><span>&nbsp;&nbsp; </span></span><span>将它看作不带符号的二进制数</span></p>
<p><span>所以</span><span> y=x</span><span>补</span><span>=1111 1111 1101 1101=65501</span></p>
<p><span>&nbsp;</span></p>
<p><span>&nbsp;Q:</span><span>无符号数赋予有符号数</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span>unsigned short x=65530;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span>short y;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span>y=x;</span></p>
<p><span>&nbsp;A:&nbsp;x=65530=1111 1111 1111 1010</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>y</span><span>补</span><span>=x;</span></p>
<p><span>所以</span><span>y</span><span>原</span><span>=1000 0000 0000 0110</span></p>
<p><span>所以</span><span>y= -000 0000 0000 0110=-6</span></p>
<p>&nbsp;</p>
<p><span>7</span><span>、</span><span>int a=b=6;</span><span>是不允许的，但是</span><span>int a,b=a=6;</span><span>却是可以的。</span></p>
<p><span>8</span><span>、</span><span>++</span><span>与</span><span>+</span><span>等的优先级</span></p>
<p><span>优先级</span><span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>高——</span><span>&gt; </span><span>低</span></p>
<p><span>+,-,++,--<span>&nbsp;&nbsp;&nbsp;&nbsp; </span>*,/,%<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>+,-<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>&lt;&lt;,&gt;&gt;</span></p>
<p><span>__</span><span>同级</span><span>_<span>&nbsp;&nbsp;&nbsp;&nbsp; </span>__</span><span>同级</span><span>_<span>&nbsp;&nbsp; </span>__</span><span>同级</span><span>_<span>&nbsp;&nbsp;&nbsp; </span>__</span><span>同级</span><span>_</span></p>
<p>&nbsp;</p>
<p><span>Q</span><span>：</span><span> -a++ </span><span>由于</span><span>++,-</span><span>同级，所欲按结合性从右向左，故等价于</span> <span>&#8211;(a++)</span></p>
<p><span>-++a&nbsp;EQU&nbsp;-(++a)</span></p>
<p><span>但是</span><span> ++(-a)</span><span>是个错误的表达式，因为</span><span>-a</span><span>不是变量，而是表达式，不能对表达式进行自增。</span><span>(-a)++</span><span>同理也是错误的。</span></p>
<p><span>a+++b </span><span>未定义。不该使用。</span></p>
<p><span>a+--b EQU a+(--b)</span></p>
<p>&nbsp;</p>
<p><span>9</span><span>、</span><span>Q:</span><span>指向不同类型指针</span></p>
<p><span>int *p;</span></p>
<p><span>float f;</span></p>
<p><span>p=&amp;f;&nbsp;//ERROR,p</span><span>指向的类型是</span><span>int</span><span>，类型不匹配</span></p>
<p><span>10</span><span>、假设</span><span>p=&amp;a</span><span>则</span></p>
<p><span>*p&nbsp;EQU&nbsp;*(&amp;a)<span>&nbsp;&nbsp; </span>EQU<span>&nbsp;&nbsp;&nbsp;&nbsp; </span>*&amp;a<span>&nbsp;&nbsp; </span>EQU<span>&nbsp;&nbsp; </span>a</span></p>
<p><span>&amp;a&nbsp;EQU&nbsp;&amp;(*a)&nbsp;EQU<span>&nbsp;&nbsp; </span>&amp;*p&nbsp;EQU&nbsp;p</span></p>
<p>&nbsp;</p>
<p><span><span>11、<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span></span><span>y=*p++ </span><span>等价于</span><span>&nbsp;y=*(p++)&nbsp;</span><span>注：</span><span> y</span><span>的值等于</span><span>*p</span><span>的值，</span><span>p</span><span>的新值等于</span><span>(p</span><span>原值</span><span>+1) X </span><span>相应数据类型长度。</span></p>
<p><span>y=*++p&nbsp;</span><span>等价于</span><span> y=*(++p)&nbsp;</span><span>注：</span><span> y</span><span>的值为</span><span>(p+1)X</span><span>数据类型长度。</span></p>
<p><span>y=(*p)++<span>&nbsp;&nbsp; </span>y</span><span>值等于</span><span>*p</span><span>，</span><span>*p</span><span>的值为</span><span>*p + 1</span></p>
<p><span>y=++(*p)<span>&nbsp;&nbsp; </span>y</span><span>的值等于</span><span>(*p)+1&nbsp;*p</span><span>新值等于</span><span> (*p)+1<br>12、%取余运算。<br>(-3)%2&nbsp; &nbsp;EQU&nbsp; -(3%2)=-1<br>5%-1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EQU&nbsp;&nbsp; 5%3=2<br>19%10%5 EQU (19%10)%5=4</span></p>
<img src ="http://www.cppblog.com/bellgrade/aggbug/97662.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/bellgrade/" target="_blank">bellgrade</a> 2009-09-30 16:21 <a href="http://www.cppblog.com/bellgrade/archive/2009/09/30/97662.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++拾遗之类</title><link>http://www.cppblog.com/bellgrade/archive/2009/09/30/97661.html</link><dc:creator>bellgrade</dc:creator><author>bellgrade</author><pubDate>Wed, 30 Sep 2009 08:19:00 GMT</pubDate><guid>http://www.cppblog.com/bellgrade/archive/2009/09/30/97661.html</guid><wfw:comment>http://www.cppblog.com/bellgrade/comments/97661.html</wfw:comment><comments>http://www.cppblog.com/bellgrade/archive/2009/09/30/97661.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/bellgrade/comments/commentRss/97661.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/bellgrade/services/trackbacks/97661.html</trackback:ping><description><![CDATA[&nbsp;
<p>&nbsp;</p>
<p><span><span>1、&nbsp;</span></span><span>如何使用无参构造函数初始化？</span></p>
<p><span>aClass obj();<span>&nbsp;&nbsp; </span>//error</span><span>，不能加括号</span></p>
<p><span>aClass obj;</span></p>
<p><span>2</span><span>、声明为引用的数据成员和</span><span>const</span><span>数据成员必须采用成员初始化列表进行初始化。</span></p>
<p><span>3</span><span>、类与</span><span>const</span></p>
<p><span>①</span><span>const</span><span>成员函数定义：在该函数中不改变调用他的对象的值。</span></p>
<p><span>eg: void Func() const;</span></p>
<p><span>②</span><span>const</span><span>成员函数可重载，根据对象是否是</span><span>const</span><span>来决定调用。</span></p>
<p><span>③</span><span>构造函数和析构函数不能声明为</span><span>const</span></p>
<p><span>④</span><span>const</span><span>数据成员</span> <span>必须在构造函数初始化列表中被初始化。</span></p>
<p><span>eg: class test</span></p>
<p><span>{</span></p>
<p><span>test(int i):data(i) {}</span></p>
<p><span>&#8230;&#8230;</span></p>
<p><span>private:</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>const int data;</span></p>
<p><span>}</span></p>
<p><span>注：</span><span>const</span><span>对象的&#8220;常量性质&#8221;在从构造函数完成对象初始化</span> <span>到</span> <span>对象析构函数被调用结束这个过程之间一直保持。</span></p>
<p>&nbsp;</p>
<p><span>⑤</span><span>static</span><span>成员函数不能声明为</span><span>const</span><span>。</span><span>const</span><span>指示函数不能修改它操作的对象的内容，但</span><span>static</span><span>成员函数独立于类的任何对象。</span></p>
<p>&nbsp;</p>
<p><span>⑥</span><span>不一致使用</span><span>const</span></p>
<p>&nbsp;</p>
<p align=left><span>void Func2(int a[])</span></p>
<p align=left><span>{</span></p>
<p align=left><span>&#8230;&#8230;</span></p>
<p align=left><span>}</span><span> </span></p>
<p align=left><span>void Func1(const int a[])</span></p>
<p align=left><span>{</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span>Func2(a); //Error</span><span>，不能将<span>const</span>修改为非<span>const</span>。<span>Func2</span>参数也该声明为<span>const</span></span></p>
<p align=left><span>&#8230;&#8230;</span></p>
<p align=left><span>}</span><span> void Func2()vo</span></p>
<p>&nbsp;</p>
<p><span>4</span><span>、友元</span><span>friend</span><span>，原型在类定义内出现，但友元不是成员函数，</span><span>private</span><span>、</span><span>protected</span><span>等与友元无关，因此可以放在类的任何地方。</span></p>
<p><span>5</span><span>、在派生类的构造函数的初始化列表中初始化成员、调用基类的构造函数，可以防止重复初始化。</span></p>
<p><span>6</span><span>、若一个类中含</span><span>virtual</span><span>函数，则该类的析构函数应该为</span><span>virtual</span></p>
<p>&nbsp;</p>
<p><span>7</span><span>、重载复数类的</span><span>+</span><span>运算符。</span></p>
<p>&nbsp;</p>
<p><span>Complex <span>operator</span> + (<span>const</span> Complex&amp; x,<span>const</span> Complex&amp; y)</span></p>
<p><span>{</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span>return</span> (a,b);<span>&nbsp;&nbsp; </span>//</span><span>此语句调用下面的构造函数</span></p>
<p><span>}</span></p>
<p>&nbsp;</p>
<p><span>Complex::Complex(<span>const</span> Complex&amp; x,<span>const</span> Comolex&amp; y)</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>:real(x.real+y.real),imag(x.img+y.img)</span></p>
<p><span>{</span></p>
<p><span>}</span></p>
<p>&nbsp;</p>
<p><span>7</span><span>、临时对象</span></p>
<p><span>class X</span></p>
<p><span>{</span><span>&#8230;&#8230;</span><span>};</span></p>
<p>&nbsp;</p>
<p><span>X x1(100);<span>&nbsp;&nbsp; </span>//</span><span>只有它不会产生临时对象，只有</span><span>1</span><span>个构造函数被调用</span></p>
<p><span>X x2=X(100);</span></p>
<p><span>X x3=100;</span></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><span>8</span><span>、多重继承</span></p>
<p><span>继承关系如下图</span></p>
<p><span>
<table cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td>
            <div>
            <p><span>Employee</span></p>
            </div>
            </td>
        </tr>
    </tbody>
</table>
<table cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td>
            <div>
            <p><span>Employer</span></p>
            </div>
            </td>
        </tr>
    </tbody>
</table>
<table cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td>
            <div>
            <p><span>Worker</span></p>
            </div>
            </td>
        </tr>
    </tbody>
</table>
<table cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td>
            <div>
            <p><span>Manager</span></p>
            </div>
            </td>
        </tr>
    </tbody>
</table>
<table cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td>
            <div>
            <p><span>Engineer</span></p>
            </div>
            </td>
        </tr>
    </tbody>
</table>
</span></p>
<p><span>产生二义性：如</span></p>
<p><span>&nbsp;Engineer<span>&nbsp;&nbsp; </span>eng;</span></p>
<p><span>&nbsp;eng.Worker::Sex=0;</span></p>
<p><span>&nbsp;eng.Manager::Sex=1;</span></p>
<p><span>解决方法：使用虚基类</span></p>
<p><span><span>&nbsp;&nbsp; </span>class Worker:public virtual Employee</span></p>
<p><span>{</span></p>
<p><span>};</span></p>
<p>&nbsp;</p>
<p><span>class Manager:public virtual Employer</span></p>
<p><span>{</span></p>
<p><span>};</span></p>
<p><span>类之间的关系如下图所示：</span></p>
<p><span>
<table cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td>
            <div>
            <p><span>Employer</span></p>
            </div>
            </td>
        </tr>
    </tbody>
</table>
<table cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td>
            <div>
            <p><span>Worker</span></p>
            </div>
            </td>
        </tr>
    </tbody>
</table>
<table cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td>
            <div>
            <p><span>Manager</span></p>
            </div>
            </td>
        </tr>
    </tbody>
</table>
<table cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td>
            <div>
            <p><span>Engineer</span></p>
            </div>
            </td>
        </tr>
    </tbody>
</table>
</span></p>
<p><span>不过这样还是存在问题的，这是</span><span>c++</span><span>自身所决定的。</span></p>
<img src ="http://www.cppblog.com/bellgrade/aggbug/97661.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/bellgrade/" target="_blank">bellgrade</a> 2009-09-30 16:19 <a href="http://www.cppblog.com/bellgrade/archive/2009/09/30/97661.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>内存对齐</title><link>http://www.cppblog.com/bellgrade/archive/2009/09/30/97660.html</link><dc:creator>bellgrade</dc:creator><author>bellgrade</author><pubDate>Wed, 30 Sep 2009 08:17:00 GMT</pubDate><guid>http://www.cppblog.com/bellgrade/archive/2009/09/30/97660.html</guid><wfw:comment>http://www.cppblog.com/bellgrade/comments/97660.html</wfw:comment><comments>http://www.cppblog.com/bellgrade/archive/2009/09/30/97660.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/bellgrade/comments/commentRss/97660.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/bellgrade/services/trackbacks/97660.html</trackback:ping><description><![CDATA[&nbsp;
<p><span>1</span><span>、为什么会产生数据对齐问题</span></p>
<p><span>&nbsp;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>8</span><span>位</span><span>CPU </span><span>当然不会产生数据对齐问题，当</span><span>CPU</span><span>发展到</span><span>16</span><span>，</span><span>32</span><span>位时，因<u>为</u></span><u><span>CPU</span></u><u><span>的一次内存访问就能取回</span><span>4</span></u><u><span>个</span><span>byte</span></u><span>（且用</span><span>32</span><span>位举例，这个数据理所当然的缓存在相应的</span><span>32</span><span>位寄存器中）——里面可能存储了</span><span>4</span><span>个</span><span>1byte</span><span>的数据，也可能存储了</span><span>2</span><span>个</span><span>2byte</span><span>的数据&#8230;&#8230;，所以</span><span>CPU</span><span>在逻辑上将内存单元寻址地址边界设置为</span><span>4</span><span>的倍数（如：</span><span>0</span><span>，</span><span>4</span><span>，</span><span>8</span><span>，</span><span>12</span><span>&#8230;&#8230;），这是数据对齐产生的必要条件之一；另一个原因是程序中使用的数据类型并非都是</span><span>4</span><span>的倍数，如：</span><span>char</span><span>（</span><span>1byte</span><span>），</span><span>short</span><span>（</span><span>2byte</span><span>），</span><span>int</span><span>（</span><span>4byte</span><span>）等等。让我们考虑一下一个</span><span>2byte</span><span>的的变量在内存单元中排布吧：如果这个变量地址为</span><span>0</span><span>或</span><span>1</span><span>或</span><span>2</span><span>，那么</span><span>CPU</span><span>一次内存访问就能够取得你的变量；但如果是</span><span>3</span><span>的话，很不幸，</span><span>CPU</span><span>还得访问一次内存以取得全部数据。</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>2</span><span>、举例说明</span></p>
<p align=left><span><span>struct</span><span>&nbsp;A</span><span> </span><span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>char</span><span>&nbsp;m_ch;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>//1&nbsp;Byte<br></span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>char</span><span>&nbsp;*m_pStr;&nbsp;&nbsp;</span><span>//4&nbsp;Byte<br></span><span>};&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>//sizeof(A)=8,</span></span><span>按<span>4</span>对齐<span><br></span></span><span><br></span><span>struct</span><span>&nbsp;B</span><span> </span><span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>char</span><span>&nbsp;m_ch;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>//1&nbsp;Byte<br></span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>int</span><span>&nbsp;m_count;&nbsp;&nbsp;&nbsp;</span><span>//4&nbsp;Byte&nbsp;&nbsp;<br></span><span>};&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>//sizeof(A)=8,</span><span>按<span>4</span>对齐<span><br></span></span><span><br></span><span>struct</span><span>&nbsp;C</span><span> </span><span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>bool</span><span>&nbsp;m_ok;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>//1&nbsp;Byte<br></span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>char</span><span>&nbsp;m_name[6];</span><span>//char&nbsp;</span><span>也是<span>&nbsp;1&nbsp;Byte,6</span>个<span>char</span>罢了<span><br></span></span><span>};</span></p>
<p align=left><span>________________________________________</span><span>结构体成员<span>________________________________&nbsp;</span></span></p>
<p align=left><span><span>struct</span><span>&nbsp;X<br></span><span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>double</span><span>&nbsp;m_width;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>//8&nbsp;Byte<br></span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>char</span><span>&nbsp;&nbsp;&nbsp;m_name[6];&nbsp;&nbsp;&nbsp;&nbsp;</span><span>//1&nbsp;Byte&nbsp;per<br></span><span>};&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>//sizeof(X)=16<br></span><span><br></span><span>struct</span><span>&nbsp;Y</span><span> </span><span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>int</span><span>&nbsp;m_no;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>//4&nbsp;Byte<br></span><span>&nbsp;&nbsp;&nbsp;&nbsp;X&nbsp;&nbsp;&nbsp;m_x;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>//X</span></span><span>按<span>8</span>对齐<span>,</span>故按<span>8</span>对齐<span>,<br></span></span><span>};&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span>//sizeof(Y)=8x3=24</span><span>，提示可将<span>X&nbsp;m_x</span>扩展成<span>struct&nbsp;X</span></span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>struct</span><span>&nbsp;X2<br></span><span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>double</span><span>&nbsp;m_width;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>//8&nbsp;Byte<br></span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>char</span><span>&nbsp;&nbsp;&nbsp;m_name[9];&nbsp;&nbsp;&nbsp;&nbsp;</span><span>//1&nbsp;Byte&nbsp;per,</span></span><span>前<span>8</span>字节<span>+8</span>字节中的一个字节<span><br></span></span><span>};&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>//sizeof(X2)=8+8+8=24</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>struct</span><span>&nbsp;X2<br></span><span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>double</span><span>&nbsp;m_width;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>//8&nbsp;Byte<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>8x1<br></span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>char</span><span>&nbsp;&nbsp;&nbsp;m_name[9];&nbsp;&nbsp;&nbsp;&nbsp;</span><span>//</span></span><span>占用<span>8+8 <span>&nbsp;&nbsp;&nbsp;&nbsp;</span>8x2 </span>和<span> 8x3<br></span></span><span>};&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span><br></span><span><br></span><span>struct</span><span>&nbsp;Y2</span><span> </span><span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>int</span><span>&nbsp;m_no;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>//4&nbsp;Byte,</span><span>占用另一个<span>8</span>字节<span> 8x4<br></span></span><span>&nbsp;&nbsp;&nbsp;&nbsp;X2&nbsp;&nbsp;&nbsp;m_x;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>};&nbsp;&nbsp;&nbsp;&nbsp;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>//sizeof(Y2)=8+8+8+8=32</span></p>
<p align=left><span><span>enum</span><span>&nbsp;DataType</span><span> </span><span>{IntData,CharData,VcharData};<br></span><span>struct</span><span>&nbsp;Item&nbsp;&nbsp;&nbsp;&nbsp;<br></span><span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>char</span><span>&nbsp;ItemNAme[30];&nbsp;&nbsp;</span><span>//</span></span><span>按<span>4</span>对齐，故是<span>32<br></span></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DataType&nbsp;ItemType;&nbsp;&nbsp;</span><span>//enum</span><span>站<span>4</span>个字节<span><br></span></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>char</span><span>&nbsp;ItemDecr[50];&nbsp;&nbsp;</span><span>//</span><span>按<span>4</span>对齐，所以是<span>52<br></span></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>int</span><span>&nbsp;ItemLength;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>//4</span><span>字节<span><br></span></span><span>};<br><br></span><span>sizeof</span><span>(Item)&nbsp;=&nbsp;32+4+52+4=92</span></p>
<p>&nbsp;</p>
<p align=left><span><span>#pragma&nbsp;pack(2)&nbsp;</span><span>//</span></span><span>指定对齐方式是<span>2<br></span></span><span>enum</span><span>&nbsp;DataType</span><span> </span><span>{IntData,CharData,VcharData};<br><br></span><span>struct</span><span>&nbsp;Item&nbsp;&nbsp;&nbsp;&nbsp;<br></span><span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>char</span><span>&nbsp;ItemNAme[30];&nbsp;&nbsp;</span><span>//30<br></span><span>&nbsp;&nbsp;&nbsp;&nbsp;DataType&nbsp;ItemType;&nbsp;&nbsp;</span><span>//4<br></span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>char</span><span>&nbsp;ItemDecr[50];&nbsp;&nbsp;</span><span>//50<br></span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>int</span><span>&nbsp;ItemLength;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>//4<br></span><span>};<br><br></span><span>sizeof</span><span>(Item)&nbsp;=&nbsp;30+4+50+4=88</span></p>
<img src ="http://www.cppblog.com/bellgrade/aggbug/97660.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/bellgrade/" target="_blank">bellgrade</a> 2009-09-30 16:17 <a href="http://www.cppblog.com/bellgrade/archive/2009/09/30/97660.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>各种Hash函数和代码</title><link>http://www.cppblog.com/bellgrade/archive/2009/09/29/97565.html</link><dc:creator>bellgrade</dc:creator><author>bellgrade</author><pubDate>Tue, 29 Sep 2009 10:20:00 GMT</pubDate><guid>http://www.cppblog.com/bellgrade/archive/2009/09/29/97565.html</guid><wfw:comment>http://www.cppblog.com/bellgrade/comments/97565.html</wfw:comment><comments>http://www.cppblog.com/bellgrade/archive/2009/09/29/97565.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/bellgrade/comments/commentRss/97565.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/bellgrade/services/trackbacks/97565.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 常用的字符串Hash函数还有ELFHash，APHash等等，都是十分简单有效的方法。这些函数使用位运算使得每一个字符都对最后的函数值产生影响。另外还有以MD5和SHA1为代表的杂凑函数，这些函数几乎不可能找到碰撞。常用字符串哈希函数有BKDRHash，APHash，DJBHash，JSHash，RSHash，SDBMHash，PJWHash，ELFHash等等。对于以上几种哈希函...&nbsp;&nbsp;<a href='http://www.cppblog.com/bellgrade/archive/2009/09/29/97565.html'>阅读全文</a><img src ="http://www.cppblog.com/bellgrade/aggbug/97565.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/bellgrade/" target="_blank">bellgrade</a> 2009-09-29 18:20 <a href="http://www.cppblog.com/bellgrade/archive/2009/09/29/97565.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>页面置换算法 计算</title><link>http://www.cppblog.com/bellgrade/archive/2009/09/28/97463.html</link><dc:creator>bellgrade</dc:creator><author>bellgrade</author><pubDate>Mon, 28 Sep 2009 06:33:00 GMT</pubDate><guid>http://www.cppblog.com/bellgrade/archive/2009/09/28/97463.html</guid><wfw:comment>http://www.cppblog.com/bellgrade/comments/97463.html</wfw:comment><comments>http://www.cppblog.com/bellgrade/archive/2009/09/28/97463.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/bellgrade/comments/commentRss/97463.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/bellgrade/services/trackbacks/97463.html</trackback:ping><description><![CDATA[<span  style="color: rgb(85, 85, 85); font-family: Georgia; font-size: 12px; letter-spacing: 1px; line-height: 25px; -webkit-border-horizontal-spacing: 2px; -webkit-border-vertical-spacing: 2px; "><p style="line-height: normal; letter-spacing: 0.1em; ">评价一个算法的优劣,可通过在一个特定的<strong style="line-height: normal; letter-spacing: 0.1em; ">存储访问序列</strong>（<font color="#ff0000" style="line-height: normal; letter-spacing: 0.1em; ">页面走向</font>）上运行它，并计算缺页数量来实现。</p><p style="line-height: normal; letter-spacing: 0.1em; "><strong style="line-height: normal; letter-spacing: 0.1em; ">1 先入先出法（FIFO）</strong></p><p style="line-height: normal; letter-spacing: 0.1em; "><font color="#ff0000" style="line-height: normal; letter-spacing: 0.1em; ">最简单</font>的页面置换算法是先入先出（FIFO）法。这种算法的<font color="#ff0000" style="line-height: normal; letter-spacing: 0.1em; ">实质</font>是，总是选择在主存中停留时间最长（即最老）的一页置换，即<font color="#ff0000" style="line-height: normal; letter-spacing: 0.1em; "><strong style="line-height: normal; letter-spacing: 0.1em; ">先进入内存的页，先退出内存</strong></font>。理由是：最早调入内存的页，其不再被使用的可能性比刚调入内存的可能性大。建立一个FIFO队列，收容所有在内存中的页。被置换页面总是在队列头上进行。当一个页面被放入内存时，就把它插在队尾上。<br style="line-height: normal; letter-spacing: 0.1em; ">这种算法只是<font color="#ff0000" style="line-height: normal; letter-spacing: 0.1em; ">在按线性顺序访问地址空间时才是理想的</font>，否则效率不高。因为那些常被访问的页，往往在主存中也停留得最久，结果它们因变&#8220;老&#8221;而不得不被置换出去。<br style="line-height: normal; letter-spacing: 0.1em; ">FIFO的另一个缺点是，它有一种异常现象，即在增加存储块的情况下，反而使缺页中断率增加了。当然，导致这种异常现象的页面走向实际上是很少见的。</p><h2 style="line-height: normal; letter-spacing: 0.1em; "></h2><div class="t_msgfont" style="font-family: Arial; word-wrap: break-word; word-break: break-all; visibility: visible !important; zoom: 1 !important; filter: none; font-size: 12px; line-height: normal; letter-spacing: 0.1em; "><br style="line-height: normal; letter-spacing: 0.1em; ">现在来看下4块的情况：&nbsp;<br style="line-height: normal; letter-spacing: 0.1em; ">0 1 2 3 2 1 3 2 5 2 3 6 2 1 4 2&nbsp;<br style="line-height: normal; letter-spacing: 0.1em; "><br style="line-height: normal; letter-spacing: 0.1em; ">【解答】<br style="line-height: normal; letter-spacing: 0.1em; ">刚开始内存并没有这个作业，所以发生缺页中断一次。作业的0号页进入内存。(1次缺页中断)&nbsp;<br style="line-height: normal; letter-spacing: 0.1em; ">而页1又不在内存，又发生缺页中断一次。作业页1进入内存。(2次缺页中断)&nbsp;<br style="line-height: normal; letter-spacing: 0.1em; ">页2不在内存，发生缺页中断。页2进入内存。 (3次缺页中断)&nbsp;<br style="line-height: normal; letter-spacing: 0.1em; ">页3不在内存，发生缺页中断。页3进入内存。 (4次缺页中断)&nbsp;<br style="line-height: normal; letter-spacing: 0.1em; ">接下来调入页2，页1，页3，页2。由于都在内存中，并不发生缺页中断。&nbsp;<br style="line-height: normal; letter-spacing: 0.1em; ">页5不在内存，发生缺页中断。页5进入内存，页5置换页0。 (5次缺页中断)&nbsp;<br style="line-height: normal; letter-spacing: 0.1em; ">接下来调入页2，页3。由于都在内存中，并不发生缺页中断。&nbsp;<br style="line-height: normal; letter-spacing: 0.1em; ">页6不在内存，发生缺页中断。页6进入内存。页6置换页1。 (6次缺页中断)&nbsp;<br style="line-height: normal; letter-spacing: 0.1em; ">页2在内存，不发生缺页中断。&nbsp;<br style="line-height: normal; letter-spacing: 0.1em; ">页1不在内存(在发生第6次缺页中断时被置换了)，发生缺页中断。&nbsp;<br style="line-height: normal; letter-spacing: 0.1em; ">页1进入内存，页2被置换。 (7次缺页中断)&nbsp;<br style="line-height: normal; letter-spacing: 0.1em; ">页4置换页3，页4进入内存。 (8次缺页中断)&nbsp;<br style="line-height: normal; letter-spacing: 0.1em; ">现在调入页2，但页2在发生第7次缺页中断时被置换掉了。&nbsp;<br style="line-height: normal; letter-spacing: 0.1em; ">现在页2进入内存，其置换页5。(因为这个时候是页5最先进入内存。)(9次缺页中断)</div><p style="line-height: normal; letter-spacing: 0.1em; "><strong style="line-height: normal; letter-spacing: 0.1em; ">2 最优置换算法（OPT）</strong></p><p style="line-height: normal; letter-spacing: 0.1em; ">最优置换（Optimal Replacement）是在<font color="#ff0000" style="line-height: normal; letter-spacing: 0.1em; "><strong style="line-height: normal; letter-spacing: 0.1em; ">理论上</strong></font>提出的一种算法。其<font color="#ff0000" style="line-height: normal; letter-spacing: 0.1em; ">实质</font>是：当调入新的一页而必须预先置换某个老页时，所选择的老页应是将来不再被使用，或者是在最远的将来才被访问。采用这种页面置换算法，保证有最少的缺页率。<br style="line-height: normal; letter-spacing: 0.1em; ">但是最优页面置换算法的实现是困难的，因为它需要人们预先就知道一个进程整个运行过程中页面走向的全部情况。不过，这个算法<font color="#ff0000" style="line-height: normal; letter-spacing: 0.1em; ">可用来衡量</font>（如通过模拟实验分析或理论分析）<font color="#ff0000" style="line-height: normal; letter-spacing: 0.1em; ">其他算法的优劣</font>。</p><p style="line-height: normal; letter-spacing: 0.1em; "><font size="2" style="line-height: normal; letter-spacing: 0.1em; ">用最佳页面置换法计算缺页次数<br style="line-height: normal; letter-spacing: 0.1em; "><br style="line-height: normal; letter-spacing: 0.1em; ">6 5 4 3 5 4 3 6 5 4 5&nbsp;<br style="line-height: normal; letter-spacing: 0.1em; "><br style="line-height: normal; letter-spacing: 0.1em; ">－－－－－－－－－－－<br style="line-height: normal; letter-spacing: 0.1em; "><br style="line-height: normal; letter-spacing: 0.1em; ">6 6 6 3 3 3 3 6 6 6 6&nbsp;<br style="line-height: normal; letter-spacing: 0.1em; "><br style="line-height: normal; letter-spacing: 0.1em; ">5 5 5 5 5 5 5 5 5 5&nbsp;<br style="line-height: normal; letter-spacing: 0.1em; "><br style="line-height: normal; letter-spacing: 0.1em; ">4 4 4 4 4 4 4 4 4&nbsp;<br style="line-height: normal; letter-spacing: 0.1em; "><br style="line-height: normal; letter-spacing: 0.1em; "><br style="line-height: normal; letter-spacing: 0.1em; ">仅仅第四列3和第八列6处,缺页.<br style="line-height: normal; letter-spacing: 0.1em; "><br style="line-height: normal; letter-spacing: 0.1em; ">第四列处:<br style="line-height: normal; letter-spacing: 0.1em; "><br style="line-height: normal; letter-spacing: 0.1em; ">opt算法中，页面发生冲突时，被替换的页面是未来访问最靠后的页面。<br style="line-height: normal; letter-spacing: 0.1em; "><br style="line-height: normal; letter-spacing: 0.1em; ">例子中，第4列处，6的再次访问最靠后，因而6被替换。<br style="line-height: normal; letter-spacing: 0.1em; "><br style="line-height: normal; letter-spacing: 0.1em; ">之后，第8列处，3被替换是因为3，4，5中未来被访问的页是4，5。<br style="line-height: normal; letter-spacing: 0.1em; "><br style="line-height: normal; letter-spacing: 0.1em; ">所以，3被替换。</font></p><p style="line-height: normal; letter-spacing: 0.1em; "><strong style="line-height: normal; letter-spacing: 0.1em; ">3 最久未使用算法（LRU）</strong></p><p style="line-height: normal; letter-spacing: 0.1em; ">FIFO算法和OPT算法之间的主要<font color="#ff0000" style="line-height: normal; letter-spacing: 0.1em; ">差别</font>是，FIFO算法利用页面进入内存后的时间长短作为置换依据，而OPT算法的依据是将来使用页面的时间。如果以最近的过去作为不久将来的近似，那么就可以把过去最长一段时间里不曾被使用的页面置换掉。它的<font color="#ff0000" style="line-height: normal; letter-spacing: 0.1em; ">实质</font>是，当需要置换一页时，<strong style="line-height: normal; letter-spacing: 0.1em; "><font color="#ff0000" style="line-height: normal; letter-spacing: 0.1em; ">选择在最近一段时间里最久没有使用过的页面予以置换</font></strong>。这种算法就称为<strong style="line-height: normal; letter-spacing: 0.1em; ">最久未使用算法</strong>（Least Recently Used，LRU）。<br style="line-height: normal; letter-spacing: 0.1em; ">LRU算法是与每个页面最后使用的时间有关的。当必须置换一个页面时，LRU算法选择过去一段时间里最久未被使用的页面。<br style="line-height: normal; letter-spacing: 0.1em; ">LRU算法是<font color="#ff0000" style="line-height: normal; letter-spacing: 0.1em; ">经常采用的页面置换算法</font>，并被认为是相当好的，但是存在如何实现它的问题。LRU算法需要实际硬件的支持。其问题是怎么确定最后使用时间的顺序，对此有两种可行的办法：<br style="line-height: normal; letter-spacing: 0.1em; ">（1）计数器。最简单的情况是使每个页表项对应一个使用时间字段，并给CPU增加一个逻辑时钟或计数器。每次存储访问，该时钟都加1。每当访问一个页面时，时钟寄存器的内容就被复制到相应页表项的使用时间字段中。这样我们就可以始终保留着每个页面最后访问的&#8220;时间&#8221;。在置换页面时，选择该时间值最小的页面。这样做，不仅要查页表，而且当页表改变时（因CPU调度）要维护这个页表中的时间，还要考虑到时钟值溢出的问题。<br style="line-height: normal; letter-spacing: 0.1em; ">（2）栈。用一个栈保留页号。每当访问一个页面时，就把它从栈中取出放在栈顶上。这样一来，栈顶总是放有目前使用最多的页，而栈底放着目前最少使用的页。由于要从栈的中间移走一项，所以要用具有头尾指针的双向链连起来。在最坏的情况下，移走一页并把它放在栈顶上需要改动6个指针。每次修改都要有开销，但需要置换哪个页面却可直接得到，用不着查找，因为尾指针指向栈底，其中有被置换页。<br style="line-height: normal; letter-spacing: 0.1em; ">因实现LRU算法必须有大量硬件支持，还需要一定的软件开销。所以实际实现的都是一种简单有效的LRU近似算法。<br style="line-height: normal; letter-spacing: 0.1em; ">一种<font color="#ff0000" style="line-height: normal; letter-spacing: 0.1em; ">LRU近似算法</font>是<strong style="line-height: normal; letter-spacing: 0.1em; ">最近未使用算法</strong>（Not Recently Used，NUR）。它在存储分块表的每一表项中增加一个引用位，操作系统定期地将它们置为0。当某一页被访问时，由硬件将该位置1。过一段时间后，通过检查这些位可以确定哪些页使用过，哪些页自上次置0后还未使用过。就可把该位是0的页淘汰出去，因为在最近一段时间里它未被访问过。</p><p style="line-height: normal; letter-spacing: 0.1em; "><strong style="line-height: normal; letter-spacing: 0.1em; ">4 第二次机会算法（SCR）</strong></p><p style="line-height: normal; letter-spacing: 0.1em; ">第二次机会算法的基本思想是与FIFO相同的，但是有所改进，<font color="#ff0000" style="line-height: normal; letter-spacing: 0.1em; ">避免把经常使用的页面置换出去</font>。当选择置换页面时，检查它的访问位。如果是0，就淘汰这页；如果访问位是1，就给它第二次机会，并选择下一个FIFO页面。当一个页面得到第二次机会时，它的访问位就清为0，它的到达时间就置为当前时间。如果该页在此期间被访问过，则访问位置1。这样给了第二次机会的页面将不被淘汰，直至所有其他页面被淘汰过（或者也给了第二次机会）。因此，如果一个页面经常使用，它的访问位总保持为1，它就从来不会被淘汰出去。<br style="line-height: normal; letter-spacing: 0.1em; ">第二次机会算法可视为一个环形队列。用一个指针指示哪一页是下面要淘汰的。当需要一个存储块时，指针就前进，直至找到访问位是0的页。随着指针的前进，把访问位就清为0。在最坏的情况下，所有的访问位都是1，指针要通过整个队列一周，每个页都给第二次机会。这时就退化成FIFO算法了。</p><p style="line-height: normal; letter-spacing: 0.1em; ">页面置换算法还有很多变种，如考虑到被置换页是否修改过、按FIFO算法选中的页正在使用等情况，都需要硬件、软件协同实现。</p></span>
<img src ="http://www.cppblog.com/bellgrade/aggbug/97463.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/bellgrade/" target="_blank">bellgrade</a> 2009-09-28 14:33 <a href="http://www.cppblog.com/bellgrade/archive/2009/09/28/97463.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>存储管理——中科大课程</title><link>http://www.cppblog.com/bellgrade/archive/2009/09/28/97460.html</link><dc:creator>bellgrade</dc:creator><author>bellgrade</author><pubDate>Mon, 28 Sep 2009 06:08:00 GMT</pubDate><guid>http://www.cppblog.com/bellgrade/archive/2009/09/28/97460.html</guid><wfw:comment>http://www.cppblog.com/bellgrade/comments/97460.html</wfw:comment><comments>http://www.cppblog.com/bellgrade/archive/2009/09/28/97460.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/bellgrade/comments/commentRss/97460.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/bellgrade/services/trackbacks/97460.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 第一节&nbsp; 存储管理概述 l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 信息的二级存储 &nbsp;&nbsp;&nbsp; 为了提高处理器的利用率和系统的工作效率，主存储器中经常存放了多个程序和数据。现今许多计算机系统都采用二级存储的办法，利用辅助存储器（磁盘、磁带等）提供的大容量存储空间，存放准备运行的程序和...&nbsp;&nbsp;<a href='http://www.cppblog.com/bellgrade/archive/2009/09/28/97460.html'>阅读全文</a><img src ="http://www.cppblog.com/bellgrade/aggbug/97460.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/bellgrade/" target="_blank">bellgrade</a> 2009-09-28 14:08 <a href="http://www.cppblog.com/bellgrade/archive/2009/09/28/97460.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>不使用中间变量交换两个数值型变量的值</title><link>http://www.cppblog.com/bellgrade/archive/2009/09/27/97354.html</link><dc:creator>bellgrade</dc:creator><author>bellgrade</author><pubDate>Sun, 27 Sep 2009 05:39:00 GMT</pubDate><guid>http://www.cppblog.com/bellgrade/archive/2009/09/27/97354.html</guid><wfw:comment>http://www.cppblog.com/bellgrade/comments/97354.html</wfw:comment><comments>http://www.cppblog.com/bellgrade/archive/2009/09/27/97354.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/bellgrade/comments/commentRss/97354.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/bellgrade/services/trackbacks/97354.html</trackback:ping><description><![CDATA[<p>&nbsp;第一类方法也是常用的方法，通过多次的数值计算来完成交换，到现在知道的有下面三种：</p>
<p>&nbsp; （1）加减法。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; a = a + b;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; b = a - b;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; a = a - b;</p>
<p>&nbsp; 该方法可以交换整型和浮点型数值的变量，但在处理浮点型的时候有可能出现精度的损失，例如对数据：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; a = 3.123456</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; b = 1234567.000000</p>
<p>&nbsp; 交换后各变量值变为：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; a = 1234567.000000</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; b = 3.125000</p>
<p>&nbsp; 很明显，原来a的值在交换给b的过程中发生了精度损失。</p>
<p>&nbsp; （2）乘除法。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; a = a * b;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; b = a / b;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; a = a / b;</p>
<p>&nbsp; 乘除法更像是加减法向乘除运算的映射，它与加减法类似：可以处理整型和浮点型变量，但在处理浮点型变量时也存在精度损失问题。而且乘除法比加减法要多一条约束：b必不为0。</p>
<p>&nbsp; 可能经验上的某种直觉告诉我们：加减法和乘除法可能会溢出，而且乘除的溢出会特别严重。其实不然，采用这两种方法都不会溢出。以加减法为例，第一步的加运算可能会造成溢出，但它所造成的溢出会在后边的减运算中被溢出回来。</p>
<p>&nbsp; （3）异或法。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; a ^= b;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; b ^= a;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; a ^= b;</p>
<p>&nbsp; 异或法可以完成对整型变量的交换，对于浮点型变量它无法完成交换。</p>
<p>&nbsp; 第二类方法更像是玩了一个文字游戏，此种方法采用了在代码中嵌入汇编代码的方法避免了临时变量的引入，但究其本质还是会使用额外的存储空间。此种方法可以有很多种，下边列出几种：</p>
<p>&nbsp; </p>
<p>&nbsp; （1）使用xchg指令，这也是比较直观、容易想到的方法，因为xchg指令的功能就是交换源操作数和目的操作数的值，这里要使用额外寄存器来暂存变量。内嵌汇编代码如下：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; _asm</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; {</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mov eax,a</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; xchg b,eax</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mov a,eax</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp; （2）使用额外的栈。这里使用反向的出栈顺序来完成交换。内嵌代码有如下两种形式：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; _asm</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; {</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push a</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push b</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pop a</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pop b</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp; 另一种形式：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; _asm push a</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; a = b;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; _asm pop a</p>
<p>&nbsp; （3）使用mov指令。这种方法使用额外寄存器来暂存一个变量的值。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; _asm mov eax,a</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; a = b;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; _asm mov b,eax</p>
<p>&nbsp; 其实第二类方法并不合格，它虽然没有显式的使用临时变量，但还是会用到额外的存贮空间。不过也不能说没有必要掌握，从实用的角度看还是很&#8220;有用&#8221;的。不是有公司出过这样的面试题吗？&#8220;不使用加减法和异或法完成不使用中间变量交换两个数值型变量的值&#8221;。此时或许只好使用这种方法了。</p>
<img src ="http://www.cppblog.com/bellgrade/aggbug/97354.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/bellgrade/" target="_blank">bellgrade</a> 2009-09-27 13:39 <a href="http://www.cppblog.com/bellgrade/archive/2009/09/27/97354.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>鼠标键盘API</title><link>http://www.cppblog.com/bellgrade/archive/2009/09/27/97349.html</link><dc:creator>bellgrade</dc:creator><author>bellgrade</author><pubDate>Sun, 27 Sep 2009 05:11:00 GMT</pubDate><guid>http://www.cppblog.com/bellgrade/archive/2009/09/27/97349.html</guid><wfw:comment>http://www.cppblog.com/bellgrade/comments/97349.html</wfw:comment><comments>http://www.cppblog.com/bellgrade/archive/2009/09/27/97349.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/bellgrade/comments/commentRss/97349.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/bellgrade/services/trackbacks/97349.html</trackback:ping><description><![CDATA[1、动作式，所谓动作式，就是指用API发命令给窗口或API控制鼠标、键盘等，使游戏里的人物进行流动或者攻击，最早以前的&#8220;石器&#8221;外挂就是这种方式。<br>2、本地修改式，这种外挂跟传统上的一些游戏修改器没有两样，做这种外挂在编程只需要对内存地址有一点认识并且掌握API就可以实现，&#8220;精灵&#8221;的外挂这是这种方式写成的，它的难点在于找到那些地址码，找地址一般地要借助于别人的工具，有的游戏还有双码校验，正正找起来会比较困难。<br>3、木马式，这种外挂的目的是帮外挂制作者偷到用户的密码，做这种外挂有一定的难度，需要HOOK或键盘监视技术做底子，才可以完成，它的原理是先首截了用户的帐号或密码，然后发到指定邮箱。<br>4、加速式，这种外挂可以加快游戏的速度。原本我一直以为加速外挂是针对某个游戏而写的，后来发现我这种概念是不对的，所谓加速外挂其实是修改时钟频率达到加速的目的。<br>5、封包式，这种外挂是高难度外挂，需要有很强的编程功力才可以写得出来。它的原理是先截取封包，后修改，再转发。这种外挂适用于大多数网络游戏，像WPE及一些网络游戏外挂都是用这种方式写成的，编写这种外挂需要apihook技术，winsock2技术&#8230;&#8230;&#8230;&#8230;<br>　 以下就用Delphi实现网络游戏外挂。 <br><br>--------------------------------------------------------------------------------<br><br>上回对五种类型的外挂做了一个大体的概括，大家对这几种外挂都有了一定的了解，现在就依次（制作难度）由浅到深谈谈我对外挂制作的一些认识吧~~~~<br>首先，先来谈一下动作式的外挂，这也是我第一次写外挂时做的最简单的一种。<br>记得还在&#8220;石器&#8221;时代的时候，我看到别人挂着一种软件（外挂）人物就可以四外游走（当时我还不知道外挂怎么回事^_^），于是找了这种软件过来研究（拿来后才听别人说这叫外挂），发现这种东东其实实现起来并不难，仔佃看其实人物的行走无非就是鼠标在不同的地方点来点去而已，看后就有实现这功能的冲动，随后跑到MSDN上看了一些资料，发现这种实现这几个功能，只需要几个简单的API函数就可以搞定：<br>1、首先我们要知道现在鼠标的位置（为了好还原现在鼠标的位置）所以我们就要用到API函数GetCursorPos，它的使用方法如下：<br>BOOL GetCursorPos(<br>LPPOINT lpPoint // address of structure for cursor position <br>);<br>2、我们把鼠标的位置移到要到人物走到的地方，我们就要用到SetCursorPos函数来移动鼠标位置，它的使用方法如下：<br>BOOL SetCursorPos(<br>int X, // horizontal position <br>int Y // vertical position<br>);<br>3、模拟鼠标发出按下和放开的动作，我们要用到mouse_event函数来实现，具休使用方法用下：<br>VOID mouse_event(<br>DWORD dwFlags, // flags specifying various motion/click variants<br>DWORD dx, // horizontal mouse position or position change<br>DWORD dy, // vertical mouse position or position change<br>DWORD dwData, // amount of wheel movement<br>DWORD dwExtraInfo // 32 bits of application-defined information<br>);<br>在它的dwFlags处，可用的事件很多如移动MOUSEEVENTF_MOVE，左键按下MOUSEEVENTF_LEFTDOWN，左键放开MOUSEEVENTF_LEFTUP，具体的东东还是查一下MSDN吧~~~~~<br>好了，有了以前的知识，我们就可以来看看人物移走是怎么实现的了：<br>getcursorpos(point);<br>setcursorpos(ranpoint(80,windowX),ranpoint(80,windowY));//ranpoint是个自制的随机坐标函数<br>mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);<br>mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);<br>setcursorpos(point.x,point.y);<br>看了以上的代码，是不是觉得人物的游走很简单啦~~，举一仿三，还有好多好东东可以用这个技巧实现（我早就说过，TMD，这是垃圾外挂的做法，相信了吧~~~），接下来，再看看游戏里面自动攻击的做法吧（必需游戏中攻击支持快捷键的），道理还是一样的，只是用的API不同罢了~~~，这回我们要用到的是keybd_event函数，其用法如下：<br>VOID keybd_event(<br>BYTE bVk, // virtual-key code<br>BYTE bScan, // hardware scan code<br>DWORD dwFlags, // flags specifying various function options<br>DWORD dwExtraInfo // additional data associated with keystroke<br>);<br>我们还要知道扫描码不可以直接使用，要用函数MapVirtualKey把键值转成扫描码，MapVirtualKey的具体使用方法如下：<br>UINT MapVirtualKey(<br>UINT uCode, // virtual-key code or scan code<br>UINT uMapType // translation to perform<br>);<br>好了，比说此快接键是CTRL+A，接下来让我们看看实际代码是怎么写的：<br>keybd_event(VK_CONTROL,mapvirtualkey(VK_CONTROL,0),0,0);<br>keybd_event(65,mapvirtualkey(65,0),0,0);<br>keybd_event(65,mapvirtualkey(65,0),keyeventf_keyup,0);<br>keybd_event(VK_CONTROL,mapvirtualkey(VK_CONTROL,0),keyeventf_keyup,0);<br>首先模拟按下了CTRL键，再模拟按下A键，再模拟放开A键，最后放开CTRL键，这就是一个模拟按快捷键的周期。<br>（看到这里，差不多对简易外挂有了一定的了解了吧~~~~做一个试试？如果你举一仿三还能有更好的东东出来，这就要看你的领悟能力了~~，不过不要高兴太早这只是才开始，以后还有更复杂的东东等着你呢~~）<br><br>--------------------------------------------------------------------------------<br>上回我们对动作式外挂做了一个解析，动作式是最简单的外挂，现在我们带来看看，比动作式外挂更进一步的外挂——本地修改式外挂的整个制作过程进行一个详细的分解。<br>具我所知，本地修改式外挂最典型的应用就是在&#8220;精灵&#8221;游戏上面，因为我在近一年前（&#8220;精灵&#8221;还在测试阶段），我所在的公司里有很多同事玩&#8220;精灵&#8221;，于是我看了一下游戏的数据处理方式，发现它所发送到服务器上的信息是存在于内存当中（我看后第一个感受是：修改这种游戏和修改单机版的游戏没有多大分别，换句话说就是在他向服务器提交信息之前修改了内存地址就可以了），当时我找到了地址于是修改了内存地址，果然，按我的想法修改了地址，让系统自动提交后，果然成功了~~~~~，后来&#8220;精灵&#8221;又改成了双地址校检，内存校检等等，在这里我就不废话了~~~~，OK，我们就来看看这类外挂是如何制作的：<br>在做外挂之前我们要对Windows的内存有个具体的认识，而在这里我们所指的内存是指系统的内存偏移量，也就是相对内存，而我们所要对其进行修改，那么我们要对几个Windows API进行了解，OK，跟着例子让我们看清楚这种外挂的制作和API的应用（为了保证网络游戏的正常运行，我就不把找内存地址的方法详细解说了）：<br>1、首先我们要用FindWindow,知道游戏窗口的句柄，因为我们要通过它来得知游戏的运行后所在进程的ID，下面就是FindWindow的用法：<br>HWND FindWindow(<br>LPCTSTR lpClassName, // pointer to class name<br>LPCTSTR lpWindowName // pointer to window name<br>);<br>2、我们GetWindowThreadProcessId来得到游戏窗口相对应进程的进程ID，函数用法如下：<br>DWORD GetWindowThreadProcessId(<br>HWND hWnd, // handle of window<br>LPDWORD lpdwProcessId // address of variable for process identifier<br>);<br>3、得到游戏进程ID后，接下来的事是要以最高权限打开进程，所用到的函数OpenProcess的具体使用方法如下：<br>HANDLE OpenProcess(<br>DWORD dwDesiredAccess, // access flag <br>BOOL bInheritHandle, // handle inheritance flag <br>DWORD dwProcessId // process identifier <br>);<br>在dwDesiredAccess之处就是设存取方式的地方，它可设的权限很多，我们在这里使用只要使用PROCESS_ALL_ACCESS 来打开进程就可以，其他的方式我们可以查一下MSDN。<br>4、打开进程后，我们就可以用函数对存内进行操作，在这里我们只要用到WriteProcessMemory来对内存地址写入数据即可（其他的操作方式比如说：ReadProcessMemory等，我在这里就不一一介绍了），我们看一下WriteProcessMemory的用法：<br>BOOL WriteProcessMemory(<br>HANDLE hProcess, // handle to process whose memory is written to <br>LPVOID lpBaseAddress, // address to start writing to <br>LPVOID lpBuffer, // pointer to buffer to write data to<br>DWORD nSize, // number of bytes to write<br>LPDWORD lpNumberOfBytesWritten // actual number of bytes written <br>);<br>5、下面用CloseHandle关闭进程句柄就完成了。<br>这就是这类游戏外挂的程序实现部份的方法，好了，有了此方法，我们就有了理性的认识，我们看看实际例子，提升一下我们的感性认识吧，下面就是XX游戏的外挂代码，我们照上面的方法对应去研究一下吧：<br>const<br>ResourceOffset: dword = $004219F4;<br>resource: dword = 3113226621;<br>ResourceOffset1: dword = $004219F8;<br>resource1: dword = 1940000000;<br>ResourceOffset2: dword = $0043FA50;<br>resource2: dword = 1280185;<br>ResourceOffset3: dword = $0043FA54;<br>resource3: dword = 3163064576;<br>ResourceOffset4: dword = $0043FA58;<br>resource4: dword = 2298478592;<br>var<br>hw: HWND;<br>pid: dword;<br>h: THandle;<br>tt: Cardinal;<br>begin<br>hw := FindWindow('XX', nil);<br>if hw = 0 then<br>Exit;<br>GetWindowThreadProcessId(hw, @pid);<br>h := OpenProcess(PROCESS_ALL_ACCESS, false, pid);<br>if h = 0 then<br>Exit;<br>if flatcheckbox1.Checked=true then<br>begin<br>WriteProcessMemory(h, Pointer(ResourceOffset), @Resource, sizeof(Resource), tt);<br>WriteProcessMemory(h, Pointer(ResourceOffset1), @Resource1, sizeof(Resource1), tt);<br>end;<br>if flatcheckbox2.Checked=true then<br>begin<br>WriteProcessMemory(h, Pointer(ResourceOffset2), @Resource2, sizeof(Resource2), tt);<br>WriteProcessMemory(h, Pointer(ResourceOffset3), @Resource3, sizeof(Resource3), tt);<br>WriteProcessMemory(h, Pointer(ResourceOffset4), @Resource4, sizeof(Resource4), tt);<br>end;<br>MessageBeep(0);<br>CloseHandle(h);<br>close;<br>这个游戏是用了多地址对所要提交的数据进行了校验，所以说这类游戏外挂制作并不是很难，最难的是要找到这些地址。 <br><br>--------------------------------------------------------------------------------<br><br>我一直没有搞懂制作加速外挂是怎么一回事，直到前不久又翻出来了2001年下半期的《程序员合订本》中《&#8220;变速齿轮&#8221;研究手记》重新回味了一遍，才有了一点点开悟，随后用Delphi重写了一遍，下面我就把我的心得说给大家听听，并且在此感谢《&#8220;变速齿轮&#8221;研究手记》作者褚瑞大虲给了提示。废话我就不多说了，那就开始神奇的加速型外挂体验之旅吧！<br>原本我一直以为加速外挂是针对某个游戏而写的，后来发现我这种概念是不对的，所谓加速外挂其实是修改时钟频率达到加速的目的。<br>以前DOS时代玩过编程的人就会马上想到，这很简单嘛不就是直接修改一下8253寄存器嘛，这在以前DOS时代可能可以行得通，但是windows则不然。windows是一个32位的操作系统，并不是你想改哪就改哪的（微软的东东就是如此霸气，说不给你改就不给你改^_^），但要改也不是不可能，我们可以通过两种方法来实现：第一是写一个硬件驱动来完成，第二是用Ring0来实现（这种方法是CIH的作者陈盈豪首用的，它的原理是修改一下IDT表-&gt;创建一个中断门-&gt;进入Ring0-&gt;调用中断修改向量，但是没有办法只能用ASM汇编来实现这一切*_*，做为高级语言使用者惨啦！），用第一种方法用点麻烦，所以我们在这里就用第二种方法实现吧~~~<br>在实现之前我们来理一下思路吧：<br>1、我们首先要写一个过程在这个过程里嵌入汇编语言来实现修改IDE表、创建中断门，修改向量等工作<br>2、调用这个过程来实现加速功能<br>好了，现在思路有了，我们就边看代码边讲解吧：<br>首先我们建立一个过程，这个过程就是本程序的核心部份：<br>procedure SetRing(value:word); stdcall; <br>const ZDH = $03; ／／ 设一个中断号<br>var<br>IDT : array [0..5] of byte; ／／ 保存IDT表<br>OG : dword; ／／存放旧向量<br>begin<br>asm<br>push ebx<br>sidt IDT ／／读入中断描述符表<br>mov ebx, dword ptr [IDT+2] ／／IDT表基地址<br>add ebx, 8*ZDH ／／计算中断在中断描述符表中的位置<br>cli ／／关中断<br>mov dx, word ptr [ebx+6] <br>shl edx, 16d <br>mov dx, word ptr [ebx] <br>mov [OG], edx <br>mov eax, offset @@Ring0 ／／指向Ring0级代码段<br>mov word ptr [ebx], ax ／／低16位,保存在1,2位<br>shr eax, 16d<br>mov word ptr [ebx+6], ax ／／高16位，保存在6,7位<br>int ZDH ／／中断<br>mov ebx, dword ptr [IDT+2] ／／重新定位<br>add ebx, 8*ZDH<br>mov edx, [OG]<br>mov word ptr [ebx], dx<br>shr edx, 16d<br>mov word ptr [ebx+6], dx ／／恢复被改了的向量<br>pop ebx<br>jmp @@exitasm ／／到exitasm处<br>@@Ring0: ／／Ring0,这个也是最最最核心的东东<br>mov al,$34 ／／写入8253控制寄存器<br>out $43,al<br>mov ax,value　／／写入定时值<br>out $40,al ／／写定时值低位<br>mov al,ah<br>out $40,al ／／写定时值高位<br>iretd ／／返回<br>@@exitasm:<br>end;<br>end;<br>最核心的东西已经写完了，大部份读者是知其然不知其所以然吧，呵呵，不过不知其所以然也然。下面我们就试着用一下这个过程来做一个类似于&#8220;变速齿轮&#8221;的一个东东吧！<br>先加一个窗口，在窗口上放上一个trackbar控件把其Max设为20，Min设为1，把Position设为10，在这个控件的Change事件里写上：<br>SetRing(strtoint('$'+inttostr(1742+(10-trackbar1.Position)*160)));<br>因为windows默认的值为$1742，所以我们把1742做为基数，又因为值越小越快，反之越慢的原理，所以写了这样一个公式，好了，这就是&#8220;变速齿轮&#8221;的一个Delphi＋ASM版了（只适用于win9X），呵呵，试一下吧，这对你帮助会很大的，呵呵。<br>在win2000里，我们不可能实现在直接对端口进行操作，Ring0也失了效，有的人就会想到，我们可以写驱动程序来完成呀，但在这里我告诉你，windows2000的驱动不是一个VxD就能实现的，像我这样的低手是写不出windows所用的驱动WDM的，没办法，我只有借助外力实现了，ProtTalk就是一个很好的设备驱动，他很方便的来实现对低层端口的操作，从而实现加速外挂。<br>1、我们首先要下一个PortTalk驱动，他的官方网站是<a id=url_1 onclick="return checkUrl(this)" href="http://www.beyondlogic.org/" target=_blank><font color=#0070af><u>http://www.beyondlogic.org</u></font></a><br>2、我们要把里面的prottalk.sys拷贝出来。<br>3、建立一个Protalk.sys的接口（我想省略了，大家可以上<a id=url_2 onclick="return checkUrl(this)" href="http://www.freewebs.com/liuyue/porttalk.pas" target=_blank><font color=#0070af><u>http://www.freewebs.com/liuyue/porttalk.pas</u></font></a>下个pas文件自己看吧）<br>4、实现加速外挂。<br>本来就篇就是补充篇原理我也不想讲太多了，下面就讲一下这程序的实现方法吧，如果说用ProtTalk来操作端口就容易多了，比win98下用ring权限操作方便。<br>1、新建一个工程，把刚刚下的接口文件和Protalk.sys一起拷到工程文件保存的文件夹下。<br>2、我们在我们新建的工程加入我们的接口文件<br>uses<br>windows,ProtTalk&#8230;&#8230;<br>3、我们建立一个过程<br>procedure SetRing(value:word); <br>begin<br>if not OpenPortTalk then exit;<br>outportb($43,$34);<br>outportb($40,lo(Value));<br>outprotb($40,hi(value));<br>ClosePortTalk;<br>end;<br>4、先加一个窗口，在窗口上放上一个trackbar控件把其Max设为20，Min设为1，把Position设为10，在这个控件的Change事件里写上：<br>SetRing(strtoint('$'+inttostr(1742+(10-trackbar1.Position)*160))); <br><br>--------------------------------------------------------------------------------<br><br>网络游戏的封包技术是大多数编程爱好者都比较关注的关注的问题之一，在这一篇里就让我们一起研究一下这一个问题吧。<br>别看这是封包这一问题，但是涉及的技术范围很广范，实现的方式也很多（比如说APIHOOK,VXD,Winsock2都可以实现），在这里我们不可能每种技术和方法都涉及，所以我在这里以Winsock2技术作详细讲解，就算作抛砖引玉。<br>由于大多数读者对封包类编程不是很了解，我在这里就简单介绍一下相关知识：<br>APIHooK：<br>由于Windows的把内核提供的功能都封装到API里面，所以大家要实现功能就必须通过API，换句话说就是我们要想捕获数据封包，就必须先要得知道并且捕获这个API，从API里面得到封包信息。<br>VXD：<br>直接通过控制VXD驱动程序来实现封包信息的捕获，不过VXD只能用于win9X。<br>winsock2：<br>winsock是Windows网络编程接口，winsock工作在应用层，它提供与底层传输协议无关的高层数据传输编程接口，winsock2是winsock2.0提供的服务提供者接口，但只能在win2000下用。<br>好了，我们开始进入winsock2封包式编程吧。<br>在封包编程里面我准备分两个步骤对大家进行讲解：1、封包的捕获，2、封包的发送。<br>首先我们要实现的是封包的捕获：<br>Delphi的封装的winsock是1.0版的，很自然winsock2就用不成。如果要使用winsock2我们要对winsock2在Delphi里面做一个接口，才可以使用winsock2。<br>1、如何做winsock2的接口？<br>1）我们要先定义winsock2.0所用得到的类型，在这里我们以WSA_DATA类型做示范，大家可以举一仿三的来实现winsock2其他类型的封装。<br>我们要知道WSA_DATA类型会被用于WSAStartup(wVersionRequired: word; var WSData: TWSAData): Integer;，大家会发现WSData是引用参数，在传入参数时传的是变量的地址，所以我们对WSA_DATA做以下封装：<br>const <br>WSADESCRIPTION_LEN = 256; <br>WSASYS_STATUS_LEN = 128; <br>type <br>PWSA_DATA = ^TWSA_DATA; <br>WSA_DATA = record <br>wVersion: Word; <br>wHighVersion: Word; <br>szDescription: array[0..WSADESCRIPTION_LEN] of Char; <br>szSystemStatus: array[0..WSASYS_STATUS_LEN] of Char; <br>iMaxSockets: Word; <br>iMaxUdpDg: Word; <br>lpVendorInfo: PChar; <br>end; <br>TWSA_DATA = WSA_DATA; <br>2）我们要从WS2_32.DLL引入winsock2的函数，在此我们也是以WSAStartup为例做函数引入：<br>function WSAStartup(wVersionRequired: word; var WSData: TWSAData): Integer; stdcall;<br>implementation <br>const WinSocket2 = 'WS2_32.DLL'; <br>function WSAStartup; external winsocket name 'WSAStartup'; <br>通过以上方法，我们便可以对winsock2做接口，下面我们就可以用winsock2做封包捕获了，不过首先要有一块网卡。因为涉及到正在运作的网络游戏安全问题，所以我们在这里以IP数据包为例做封包捕获，如果下面的某些数据类型您不是很清楚，请您查阅MSDN：<br>1）我们要起动WSA，这时个要用到的WSAStartup函数，用法如下：<br>INTEGER WSAStartup(<br>wVersionRequired: word，<br>WSData: TWSA_DATA<br>)；<br>2）使用socket函数得到socket句柄，m_hSocket:=Socket(AF_INET, SOCK_RAW, IPPROTO_IP); 用法如下：<br>INTEGER socket(af: Integer, <br>Struct: Integer, <br>protocol: Integer<br>); <br>m_hSocket:=Socket(AF_INET, SOCK_RAW, IPPROTO_IP);在程序里m_hSocket为socket句柄，AF_INET，SOCK_RAW，IPPROTO_IP均为常量。<br>3)定义SOCK_ADDR类型，跟据我们的网卡IP给Sock_ADDR类型附值，然后我们使用bind函数来绑定我们的网卡，Bind函数用法如下：<br>Type <br>IN_ADDR = record <br>S_addr : PChar;<br>End;<br>Type <br>TSOCK_ADDR = record <br>sin_family: Word;<br>sin_port: Word;<br>sin_addr : IN_ADDR<br>sin_zero: array[0..7] of Char; <br>End;<br>var<br>LocalAddr:TSOCK_ADDR;<br>LocalAddr.sin_family: = AF_INET;<br>LocalAddr.sin_port: = 0;<br>LocalAddr.sin_addr.S_addr: = inet_addr('192.168.1.1'); ／／这里你自己的网卡的IP地址,而inet_addr这个函数是winsock2的函数。<br>bind(m_hSocket, LocalAddr, sizeof(LocalAddr))；<br>4)用WSAIoctl来注册WSA的输入输出组件，其用法如下：<br>INTEGER WSAIoctl(s:INTEGER, <br>dwIoControlCode : INTEGER, <br>lpvInBuffer :INTEGER,<br>cbInBuffer : INTEGER, <br>lpvOutBuffer : INTEGER,<br>cbOutBuffer: INTEGER, <br>lpcbBytesReturned : INTEGER, <br>lpOverlapped : INTEGER, <br>lpCompletionRoutine : INTEGER<br>);<br>5)下面做死循环，在死循环块里，来实现数据的接收。但是徇环中间要用Sleep()做延时，不然程序会出错。<br>6)在循环块里，用recv函数来接收数据，recv函数用法如下：<br>INTEGER recv (s : INTEGER, <br>buffer:Array[0..4095] of byte, <br>length : INTEGER,<br>flags : INTEGER,<br>)；<br>7)在buffer里就是我们接收回来的数据了，如果我们想要知道数据是什么地方发来的，那么，我们要定义一定IP包结构，用CopyMemory()把IP信息从buffer里面读出来就可以了，不过读出来的是十六进制的数据需要转换一下。<br>看了封包捕获的全过程序，对你是不是有点起发，然而在这里要告诉大家的是封包的获得是很容易的，但是许多游戏的封包都是加密的，如果你想搞清楚所得到的是什么内容还需要自己进行封包解密。 <br><br>--------------------------------------------------------------------------------<br><br>在本章中，我们主要来研究一下封包的制作和发送，同样，我们所采用的方法是Delphi+winsock2来制作。在以前说过在Delphi中只封装了winsock1，winsock2需要自已封装一下，我在此就不多介绍如何封装了。<br>下面就一步步实现我们的封包封装与发送吧：<br>首先，我们应该知道，封包是分两段的，一段是IP，一段是协议（TCP，UDP，其他协议），IP就像邮政编码一样，标识着你的这个封包是从哪里到哪里，而协议里记录着目标所要用到的包的格式及校验等，在网络游戏中的协议一般都是自已定义的，要破解网络游戏最重要的是学会破解网络游戏的协议网络游戏协议破解，为了不影响现运行的网络游戏的安全，我在此会以UDP协议为例，介绍一下网络协议的封包与发送的全过程。<br>接下来，我们就可以开始看看整个封包全过程了：<br>1）我们要起动sock2，这时个要用到的WSAStartup函数，用法如下：<br>INTEGER WSAStartup(<br>wVersionRequired: word，<br>WSData: TWSA_DATA<br>)；<br>在程序中wVersionRequired我们传入的值为$0002,WSData为TWSA_DATA的结构。<br>2）使用socket函数创建并得到socket句柄; 用法如下：<br>INTEGER socket(af: Integer, <br>Struct: Integer, <br>protocol: Integer<br>); <br>注意的是在我们的程序封包中饱含了IP包头，所以我们的Struct参数这里要传入的参数值为2，表示包含了包头。该函数返回值为刚刚创建的winsocket的句柄。<br>3）使用setsockopt函数设置sock的选项; 用法如下：<br>INTEGER setsockopt(s: Integer,<br>level: Integer, <br>optname: Integer,<br>optval: PChar,<br>optlen: Integer<br>); <br>在S处传入的是Socket句柄，在本程序里level输入的值为0表示IP（如果是6表示TCP，17表示UDP等~），OptName里写入2，而optval的初始值填入1，optlen为optval的大小。<br>4）接下来我们要分几个步骤来实现构建封包：<br>1、把IP转换成sock地址，用inet_addr来转换。<br>Longint inet_addr(<br>cp: PChar<br>);<br>2、定义包的总大小、IP的版本信息为IP结构：<br>总包大小=IP头的大小+UDP头的大小+UDP消息的大小，<br>IP的版本，在此程序里定义为4，<br>3、填写IP包头的结构：<br>ip.ipverlen := IP的版本 shl 4; <br>ip.iptos := 0; // IP服务类型<br>ip.iptotallength := ; // 总包大小 <br>ip.ipid := 0; // 唯一标识，一般设置为0<br>ip.ipoffset := 0; // 偏移字段 <br>ip.ipttl := 128; // 超时时间<br>ip.ipprotocol := $11; // 定义协议 <br>ip.ipchecksum := 0 ; // 检验总数<br>ip.ipsrcaddr := ; // 源地址<br>ip.ipdestaddr := ; // 目标地址<br>4、填写UDP包头的结构：<br>udp.srcportno := ; //源端口号<br>udp.dstportno := ; //目标端口号<br>udp.udplength := ; //UDP包的大小<br>udp.udpchecksum := ; //检验总数<br>5、把IP包头，UDP包头及消息，放入缓存。<br>6、定义远程信息：<br>remote.family := 2; <br>remote.port :=; //远程端口<br>remote.addr.addr :=; //远程地址<br>5）我们用SendTo发送封包，用法如下： <br>INTEGER sendto(s: Integer,<br>var Buf: Integer,<br>var len: Integer,<br>var flags: Integer, <br>var addrto: TSock_Addr; <br>tolen: Integer<br>); <br>在S处传入的是Socket句柄,Buf是刚刚建好的封包，len传入封包的总长度刚刚计算过了，flag是传入标记在这里我们设为0，addto发送到的目标地址，在这里我们就传入remote就可以了，tolen写入的是remote的大小。<br>6）到了最后别忘记了用CloseSocket(sh)关了socket和用WSACleanup关了winsock。<br>最后要说的是这种发送方式，只能发送完全被破解的网络协议，如果要在别人的程序中间发送数据就只有用APIHOOK或在winsock2做中间层了。 
<img src ="http://www.cppblog.com/bellgrade/aggbug/97349.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/bellgrade/" target="_blank">bellgrade</a> 2009-09-27 13:11 <a href="http://www.cppblog.com/bellgrade/archive/2009/09/27/97349.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>俄罗斯方块源码（转，不错）</title><link>http://www.cppblog.com/bellgrade/archive/2009/09/26/97326.html</link><dc:creator>bellgrade</dc:creator><author>bellgrade</author><pubDate>Sat, 26 Sep 2009 15:39:00 GMT</pubDate><guid>http://www.cppblog.com/bellgrade/archive/2009/09/26/97326.html</guid><wfw:comment>http://www.cppblog.com/bellgrade/comments/97326.html</wfw:comment><comments>http://www.cppblog.com/bellgrade/archive/2009/09/26/97326.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/bellgrade/comments/commentRss/97326.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/bellgrade/services/trackbacks/97326.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: /**//***************************************************Filename:ELSFK.hAnthor:shlyE-mail:xlq1989@vip.qq.comLastUpdate:2009.8.7***************************************************/#pragma&nbsp;once#inc...&nbsp;&nbsp;<a href='http://www.cppblog.com/bellgrade/archive/2009/09/26/97326.html'>阅读全文</a><img src ="http://www.cppblog.com/bellgrade/aggbug/97326.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/bellgrade/" target="_blank">bellgrade</a> 2009-09-26 23:39 <a href="http://www.cppblog.com/bellgrade/archive/2009/09/26/97326.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>GDB 全面使用技巧</title><link>http://www.cppblog.com/bellgrade/archive/2009/09/26/97323.html</link><dc:creator>bellgrade</dc:creator><author>bellgrade</author><pubDate>Sat, 26 Sep 2009 15:11:00 GMT</pubDate><guid>http://www.cppblog.com/bellgrade/archive/2009/09/26/97323.html</guid><wfw:comment>http://www.cppblog.com/bellgrade/comments/97323.html</wfw:comment><comments>http://www.cppblog.com/bellgrade/archive/2009/09/26/97323.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/bellgrade/comments/commentRss/97323.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/bellgrade/services/trackbacks/97323.html</trackback:ping><description><![CDATA[用GDB调试程序 <br>GDB是一个强大的命令行调试工具。大家知道命令行的强大就是在于，其可以形成执行序<br>列，形成脚本。UNIX下的软件全是命令行的，这给程序开发提代供了极大的便利，命令行<br>软件的优势在于，它们可以非常容易的集成在一起，使用几个简单的已有工具的命令，就可<br>以做出一个非常强大的功能。 <br>于是UNIX下的软件比Windows下的软件更能有机地结合，各自发挥各自的长处，组合成<br>更为强劲的功能。而Windows下的图形软件基本上是各自为营，互相不能调用，很不利于<br>各种软件的相互集成。在这里并不是要和Windows做个什么比较，所谓&#8220;寸有所长，尺有<br>所短&#8221;，图形化工具还是有不如命令行的地方。 <br>用GDB调试程序 <br>GDB概述 <br>———— <br>GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。或许，各位比较喜欢那<br>种图形界面方式的，像VC、BCB等IDE的调试，但如果你是在UNIX平台下做软件，你<br>会发现GDB这个调试工具有比VC、BCB的图形化调试器更强大的功能。所谓&#8220;寸有所长，<br>尺有所短&#8221;就是这个道理。 <br>一般来说，GDB主要帮忙你完成下面四个方面的功能： <br>1、启动你的程序，可以按照你的自定义的要求随心所欲的运行程序。 <br>2、可让被调试的程序在你所指定的调置的断点处停住。（断点可以是条件表达式） <br>3、当程序被停住时，可以检查此时你的程序中所发生的事。 <br>4、动态的改变你程序的执行环境。 <br>从上面看来，GDB和一般的调试工具没有什么两样，基本上也是完成这些功能，不过在细<br>节上，你会发现GDB这个调试工具的强大，大家可能比较习惯了图形化的调试工具，但有<br>时候，命令行的调试工具却有着图形化工具所不能完成的功能。让我们一一看来。 <br>一个调试示例 <br>—————— <br>源程序：tst.c <br>1 #include <br>2 <br>3 int func(int n) <br>4 { <br>5 int sum=0,i; <br>6 for(i=0; i&lt;7; i++) {
<p>8 sum+=i; <br>9 } <br>10 return sum; <br>11 } <br>12 <br>13 <br>14 main() <br>15 { <br>16 int i; <br>17 long result = 0; <br>18 for(i=1; i&lt;=100; i++) <br>19 { <br>20 result += i; <br>21 } <br>22 <br>23 printf("result[1-100] = %d \n", result ); <br>24 printf("result[1-250] = %d \n", func(250) ); <br>25 } <br>编译生成执行文件：（Linux下） <br>hchen/test&gt; cc -g tst.c -o tst <br>使用GDB调试： <br>hchen/test&gt; gdb tst &lt;---------- 启动GDB <br>GNU gdb 5.1.1 <br>Copyright 2002 Free Software Foundation, Inc. <br>GDB is free software, covered by the GNU General Public License, and you are <br>welcome to change it and/or distribute copies of it under certain conditions. <br>Type "show copying" to see the conditions. <br>There is absolutely no warranty for GDB. Type "show warranty" for details. <br>This GDB was configured as "i386-suse-linux"... <br>(gdb) l &lt;-------------------- l命令相当于list，从第一行开始例出原码。 <br>1 #include <br>2 <br>3 int func(int n) <br>4 { <br>5 int sum=0,i; <br>6 for(i=0; i 7 { <br>8 sum+=i; <br>9 } <br>10 return sum; <br>(gdb) &lt;-------------------- 直接回车表示，重复上一次命令 <br>11 } </p>
<p>12 <br>13 <br>14 main() <br>15 { <br>16 int i; <br>17 long result = 0; <br>18 for(i=1; i&lt;=100; i++) <br>19 { <br>20 result += i; <br>(gdb) break 16 &lt;-------------------- 设置断点，在源程序第16行处。 <br>Breakpoint 1 at 0x8048496: file tst.c, line 16. <br>(gdb) break func &lt;-------------------- 设置断点，在函数func()入口处。 <br>Breakpoint 2 at 0x8048456: file tst.c, line 5. <br>(gdb) info break &lt;-------------------- 查看断点信息。 <br>Num Type Disp Enb Address What <br>1 breakpoint keep y 0x08048496 in main at tst.c:16 <br>2 breakpoint keep y 0x08048456 in func at tst.c:5 <br>(gdb) r &lt;--------------------- 运行程序，run命令简写 <br>Starting program: /home/hchen/test/tst <br>Breakpoint 1, main () at tst.c:17 &lt;---------- 在断点处停住。 <br>17 long result = 0; <br>(gdb) n &lt;--------------------- 单条语句执行，next命令简写。 <br>18 for(i=1; i&lt;=100; i++) <br>(gdb) n <br>20 result += i; <br>(gdb) n <br>18 for(i=1; i&lt;=100; i++) <br>(gdb) n <br>20 result += i; <br>(gdb) c &lt;--------------------- 继续运行程序，continue命令简写。 <br>Continuing. <br>result[1-100] = 5050 &lt;----------程序输出。 <br>Breakpoint 2, func (n=250) at tst.c:5 <br>5 int sum=0,i; <br>(gdb) n <br>6 for(i=1; i&lt;=n; i++) <br>(gdb) p i &lt;--------------------- 打印变量i的值，print命令简写。 <br>$1 = 134513808 <br>(gdb) n <br>8 sum+=i; <br>(gdb) n <br>6 for(i=1; i&lt;=n; i++) </p>
<p>(gdb) p sum <br>$2 = 1 <br>(gdb) n <br>8 sum+=i; <br>(gdb) p i <br>$3 = 2 <br>(gdb) n <br>6 for(i=1; i&lt;=n; i++) <br>(gdb) p sum <br>$4 = 3 <br>(gdb) bt &lt;--------------------- 查看函数堆栈。 <br>#0 func (n=250) at tst.c:5 <br>#1 0x080484e4 in main () at tst.c:24 <br>#2 0x400409ed in __libc_start_main () from /lib/libc.so.6 <br>(gdb) finish &lt;--------------------- 退出函数。 <br>Run till exit from #0 func (n=250) at tst.c:5 <br>0x080484e4 in main () at tst.c:24 <br>24 printf("result[1-250] = %d \n", func(250) ); <br>Value returned is $6 = 31375 <br>(gdb) c &lt;--------------------- 继续运行。 <br>Continuing. <br>result[1-250] = 31375 &lt;----------程序输出。 <br>Program exited with code 027. &lt;--------程序退出，调试结束。 <br>(gdb) q &lt;--------------------- 退出gdb。 <br>hchen/test&gt; <br>好了，有了以上的感性认识，还是让我们来系统地认识一下gdb吧。 <br>使用GDB <br>———— <br>一般来说GDB主要调试的是C/C++的程序。要调试C/C++的程序，首先在编译时，我们必<br>须要把调试信息加到可执行文件中。使用编译器（cc/gcc/g++）的 -g 参数可以做到这一点。<br>如： <br>&gt; cc -g hello.c -o hello <br>&gt; g++ -g hello.cpp -o hello <br>如果没有-g，你将看不见程序的函数名、变量名，所代替的全是运行时的内存地址。当你用<br>-g把调试信息加入之后，并成功编译目标代码以后，让我们来看看如何用gdb来调试他。 <br>启动GDB的方法有以下几种： </p>
<p>1、gdb <br>program也就是你的执行文件，一般在当然目录下。 <br>2、gdb core <br>用gdb同时调试一个运行程序和core文件，core是程序非法执行后core dump后产生的文件。 <br>3、gdb <br>如果你的程序是一个服务程序，那么你可以指定这个服务程序运行时的进程ID。gdb会自<br>动attach上去，并调试他。program应该在PATH环境变量中搜索得到。 <br>GDB启动时，可以加上一些GDB的启动开关，详细的开关可以用gdb -help查看。我在下<br>面只例举一些比较常用的参数： <br>-symbols <br>-s <br>从指定文件中读取符号表。 <br>-se file <br>从指定文件中读取符号表信息，并把他用在可执行文件中。 <br>-core <br>-c <br>调试时core dump的core文件。 <br>-directory <br>-d <br>加入一个源文件的搜索路径。默认搜索路径是环境变量中PATH所定义的路径。 <br>GDB的命令概貌 <br>——————— <br>启动gdb后，就你被带入gdb的调试环境中，就可以使用gdb的命令开始调试程序了，gdb<br>的命令可以使用help命令来查看，如下所示： <br>/home/hchen&gt; gdb <br>GNU gdb 5.1.1 <br>Copyright 2002 Free Software Foundation, Inc. <br>GDB is free software, covered by the GNU General Public License, and you are <br>welcome to change it and/or distribute copies of it under certain conditions. <br>Type "show copying" to see the conditions. <br>There is absolutely no warranty for GDB. Type "show warranty" for details. <br>This GDB was configured as "i386-suse-linux". <br>(gdb) help </p>
<p>List of classes of commands: <br>aliases -- Aliases of other commands <br>breakpoints -- Making program stop at certain points <br>data -- Examining data <br>files -- Specifying and examining files <br>internals -- Maintenance commands <br>obscure -- Obscure features <br>running -- Running the program <br>stack -- Examining the stack <br>status -- Status inquiries <br>support -- Support facilities <br>tracepoints -- Tracing of program execution without stopping the program <br>user-defined -- User-defined commands <br>Type "help" followed by a class name for a list of commands in that class. <br>Type "help" followed by command name for full documentation. <br>Command name abbreviations are allowed if unambiguous. <br>(gdb) <br>gdb的命令很多，gdb把之分成许多个种类。help命令只是例出gdb的命令种类，如果要看<br>种类中的命令，可以使用help 命令，如：help breakpoints，查看设置断点的所有命令。也<br>可以直接help 来查看命令的帮助。 <br>gdb中，输入命令时，可以不用打全命令，只用打命令的前几个字符就可以了，当然，命令<br>的前几个字符应该要标志着一个唯一的命令，在Linux下，你可以敲击两次TAB键来补齐<br>命令的全称，如果有重复的，那么gdb会把其例出来。 <br>示例一：在进入函数func时，设置一个断点。可以敲入break func，或是直接就是b func <br>(gdb) b func <br>Breakpoint 1 at 0x8048458: file hello.c, line 10. <br>示例二：敲入b按两次TAB键，你会看到所有b打头的命令： <br>(gdb) b <br>backtrace break bt <br>(gdb) <br>示例三：只记得函数的前缀，可以这样： <br>(gdb) b make_ &lt;按TAB键&gt; <br>（再按下一次TAB键，你会看到:） <br>make_a_section_from_file make_environ <br>make_abs_section make_function_type <br>make_blockvector make_pointer_type </p>
<p>make_cleanup make_reference_type <br>make_command make_symbol_completion_list <br>(gdb) b make_ <br>GDB把所有make开头的函数全部例出来给你查看。 <br>示例四：调试C++的程序时，有可以函数名一样。如： <br>(gdb) b 'bubble( M-? <br>bubble(double,double) bubble(int,int) <br>(gdb) b 'bubble( <br>你可以查看到C++中的所有的重载函数及参数。（注：M-?和&#8220;按两次TAB键&#8221;是一个意思） <br>要退出gdb时，只用发quit或命令简称q就行了。 <br>GDB中运行UNIX的shell程序 <br>———————————— <br>在gdb环境中，你可以执行UNIX的shell的命令，使用gdb的shell命令来完成： <br>shell <br>调用UNIX的shell来执行，环境变量SHELL中定义的UNIX的shell将会被用来执行，如<br>果SHELL没有定义，那就使用UNIX的标准shell：/bin/sh。（在Windows中使用Command.com<br>或cmd.exe） <br>还有一个gdb命令是make： <br>make <br>可以在gdb中执行make命令来重新build自己的程序。这个命令等价于&#8220;shell make &#8221;。 <br>在GDB中运行程序 <br>———————— <br>当以gdb 方式启动gdb后，gdb会在PATH路径和当前目录中搜索的源文件。如要确认gdb<br>是否读到源文件，可使用l或list命令，看看gdb是否能列出源代码。 <br>在gdb中，运行程序使用r或是run命令。程序的运行，你有可能需要设置下面四方面的事。 <br>1、程序运行参数。 <br>set args 可指定运行时参数。（如：set args 10 20 30 40 50） <br>show args 命令可以查看设置好的运行参数。 <br>2、运行环境。 <br>path <br>可设定程序的运行路径。 <br>show paths 查看程序的运行路径。 </p>
<p>set environment varname [=value] 设置环境变量。如：set env USER=hchen <br>show environment [varname] 查看环境变量。 <br>3、工作目录。 <br>cd <br>相当于shell的cd命令。 <br>pwd 显示当前的所在目录。 <br>4、程序的输入输出。 <br>info terminal 显示你程序用到的终端的模式。 <br>使用重定向控制程序输出。如：run &gt; outfile <br>tty命令可以指写输入输出的终端设备。如：tty /dev/ttyb <br>调试已运行的程序 <br>———————— <br>两种方法： <br>1、在UNIX下用ps查看正在运行的程序的PID（进程ID），然后用gdb PID格式挂接正在<br>运行的程序。 <br>2、先用gdb 关联上源代码，并进行gdb，在gdb中用attach命令来挂接进程的PID。并用<br>detach来取消挂接的进程。 <br>暂停 / 恢复程序运行 <br>————————— <br>调试程序中，暂停程序运行是必须的，GDB可以方便地暂停程序的运行。你可以设置程序<br>的在哪行停住，在什么条件下停住，在收到什么信号时停往等等。以便于你查看运行时的变<br>量，以及运行时的流程。 <br>当进程被gdb停住时，你可以使用info program 来查看程序的是否在运行，进程号，被暂停<br>的原因。 <br>在gdb中，我们可以有以下几种暂停方式：断点（BreakPoint）、观察点（WatchPoint）、捕<br>捉点（CatchPoint）、信号（Signals）、线程停止（Thread Stops）。如果要恢复程序运行，可<br>以使用c或是continue命令。 <br>一、设置断点（BreakPoint） <br>我们用break命令来设置断点。正面有几点设置断点的方法： <br>break <br>在进入指定函数时停住。C++中可以使用class::function或function(type,type)格式来指定函</p>
<p>数名。 <br>break <br>在指定行号停住。 <br>break +offset <br>break -offset <br>在当前行号的前面或后面的offset行停住。offiset为自然数。 <br>break filename:linenum <br>在源文件filename的linenum行处停住。 <br>break filename:function <br>在源文件filename的function函数的入口处停住。 <br>break *address <br>在程序运行的内存地址处停住。 <br>break <br>break命令没有参数时，表示在下一条指令处停住。 <br>break ... if <br>...可以是上述的参数，condition表示条件，在条件成立时停住。比如在循环境体中，可以设<br>置break if i=100，表示当i为100时停住程序。 <br>查看断点时，可使用info命令，如下所示：（注：n表示断点号） <br>info breakpoints [n] <br>info break [n] <br>二、设置观察点（WatchPoint） <br>观察点一般来观察某个表达式（变量也是一种表达式）的值是否有变化了，如果有变化，马<br>上停住程序。我们有下面的几种方法来设置观察点： <br>watch <br>为表达式（变量）expr设置一个观察点。一量表达式值有变化时，马上停住程序。 <br>rwatch <br>当表达式（变量）expr被读时，停住程序。 <br>awatch <br>当表达式（变量）的值被读或被写时，停住程序。 </p>
<p>info watchpoints <br>列出当前所设置了的所有观察点。 <br>三、设置捕捉点（CatchPoint） <br>你可设置捕捉点来补捉程序运行时的一些事件。如：载入共享库（动态链接库）或是C++<br>的异常。设置捕捉点的格式为： <br>catch <br>当event发生时，停住程序。event可以是下面的内容： <br>1、throw 一个C++抛出的异常。（throw为关键字） <br>2、catch 一个C++捕捉到的异常。（catch为关键字） <br>3、exec 调用系统调用exec时。（exec为关键字，目前此功能只在HP-UX下有用） <br>4、fork 调用系统调用fork时。（fork为关键字，目前此功能只在HP-UX下有用） <br>5、vfork 调用系统调用vfork时。（vfork为关键字，目前此功能只在HP-UX下有用） <br>6、load 或 load 载入共享库（动态链接库）时。（load为关键字，目前此功能只在HP-UX<br>下有用） <br>7、unload 或 unload 卸载共享库（动态链接库）时。（unload为关键字，目前此功能只在<br>HP-UX下有用） <br>tcatch <br>只设置一次捕捉点，当程序停住以后，应点被自动删除。 <br>四、维护停止点 <br>上面说了如何设置程序的停止点，GDB中的停止点也就是上述的三类。在GDB中，如果你<br>觉得已定义好的停止点没有用了，你可以使用delete、clear、disable、enable这几个命令来<br>进行维护。 <br>clear <br>清除所有的已定义的停止点。 <br>clear <br>clear <br>清除所有设置在函数上的停止点。 <br>clear <br>clear <br>清除所有设置在指定行上的停止点。 <br>delete [breakpoints] [range...] <br>删除指定的断点，breakpoints为断点号。如果不指定断点号，则表示删除所有的断点。range <br>表示断点号的范围（如：3-7）。其简写命令为d。 </p>
<p>比删除更好的一种方法是disable停止点，disable了的停止点，GDB不会删除，当你还需要<br>时，enable即可，就好像回收站一样。 <br>disable [breakpoints] [range...] <br>disable所指定的停止点，breakpoints为停止点号。如果什么都不指定，表示disable所有的<br>停止点。简写命令是dis. <br>enable [breakpoints] [range...] <br>enable所指定的停止点，breakpoints为停止点号。 <br>enable [breakpoints] once range... <br>enable所指定的停止点一次，当程序停止后，该停止点马上被GDB自动disable。 <br>enable [breakpoints] delete range... <br>enable所指定的停止点一次，当程序停止后，该停止点马上被GDB自动删除。 <br>五、停止条件维护 <br>前面在说到设置断点时，我们提到过可以设置一个条件，当条件成立时，程序自动停止，这<br>是一个非常强大的功能，这里，我想专门说说这个条件的相关维护命令。一般来说，为断点<br>设置一个条件，我们使用if关键词，后面跟其断点条件。并且，条件设置好后，我们可以<br>用condition命令来修改断点的条件。（只有break和watch命令支持if，catch目前暂不支持<br>if） <br>condition <br>修改断点号为bnum的停止条件为expression。 <br>condition <br>清除断点号为bnum的停止条件。 <br>还有一个比较特殊的维护命令ignore，你可以指定程序运行时，忽略停止条件几次。 <br>ignore <br>表示忽略断点号为bnum的停止条件count次。 <br>六、为停止点设定运行命令 <br>我们可以使用GDB提供的command命令来设置停止点的运行命令。也就是说，当运行的<br>程序在被停止住时，我们可以让其自动运行一些别的命令，这很有利行自动化调试。对基于<br>GDB的自动化调试是一个强大的支持。 <br>commands [bnum] </p>
<p>... command-list ... <br>end <br>为断点号bnum指写一个命令列表。当程序被该断点停住时，gdb会依次运行命令列表中的<br>命令。 <br>例如： <br>break foo if x&gt;0 <br>commands <br>printf "x is %d\n",x <br>continue <br>end <br>断点设置在函数foo中，断点条件是x&gt;0，如果程序被断住后，也就是，一旦x的值在foo<br>函数中大于0，GDB会自动打印出x的值，并继续运行程序。 <br>如果你要清除断点上的命令序列，那么只要简单的执行一下commands命令，并直接在打个<br>end就行了。 <br>七、断点菜单 <br>在C++中，可能会重复出现同一个名字的函数若干次（函数重载），在这种情况下，break 不<br>能告诉GDB要停在哪个函数的入口。当然，你可以使用break 也就是把函数的参数类型告<br>诉GDB，以指定一个函数。否则的话，GDB会给你列出一个断点菜单供你选择你所需要的<br>断点。你只要输入你菜单列表中的编号就可以了。如： <br>(gdb) b String::after <br>[0] cancel <br>[1] all <br>[2] file:String.cc; line number:867 <br>[3] file:String.cc; line number:860 <br>[4] file:String.cc; line number:875 <br>[5] file:String.cc; line number:853 <br>[6] file:String.cc; line number:846 <br>[7] file:String.cc; line number:735 <br>&gt; 2 4 6 <br>Breakpoint 1 at 0xb26c: file String.cc, line 867. <br>Breakpoint 2 at 0xb344: file String.cc, line 875. <br>Breakpoint 3 at 0xafcc: file String.cc, line 846. <br>Multiple breakpoints were set. <br>Use the "delete" command to delete unwanted <br>breakpoints. <br>(gdb) </p>
<p>可见，GDB列出了所有after的重载函数，你可以选一下列表编号就行了。0表示放弃设置<br>断点，1表示所有函数都设置断点。 <br>八、恢复程序运行和单步调试 <br>当程序被停住了，你可以用continue命令恢复程序的运行直到程序结束，或下一个断点到来。<br>也可以使用step或next命令单步跟踪程序。 <br>continue [ignore-count] <br>c [ignore-count] <br>fg [ignore-count] <br>恢复程序运行，直到程序结束，或是下一个断点到来。ignore-count表示忽略其后的断点次<br>数。continue，c，fg三个命令都是一样的意思。 <br>step <br>单步跟踪，如果有函数调用，他会进入该函数。进入函数的前提是，此函数被编译有debug<br>信息。很像VC等工具中的step in。后面可以加count也可以不加，不加表示一条条地执行，<br>加表示执行后面的count条指令，然后再停住。 <br>next <br>同样单步跟踪，如果有函数调用，他不会进入该函数。很像VC等工具中的step over。后面<br>可以加count也可以不加，不加表示一条条地执行，加表示执行后面的count条指令，然后<br>再停住。 <br>set step-mode <br>set step-mode on <br>打开step-mode模式，于是，在进行单步跟踪时，程序不会因为没有debug信息而不停住。<br>这个参数有很利于查看机器码。 <br>set step-mod off <br>关闭step-mode模式。 <br>finish <br>运行程序，直到当前函数完成返回。并打印函数返回时的堆栈地址和返回值及参数值等信息。 <br>until 或 u <br>当你厌倦了在一个循环体内单步跟踪时，这个命令可以运行程序直到退出循环体。 <br>stepi 或 si <br>nexti 或 ni <br>单步跟踪一条机器指令！一条程序代码有可能由数条机器指令完成，stepi和nexti可以单步<br>执行机器指令。与之一样有相同功能的命令是&#8220;display/i $pc&#8221; ，当运行完这个命令后，单<br>步跟踪会在打出程序代码的同时打出机器指令（也就是汇编代码） </p>
<p>九、信号（Signals） <br>信号是一种软中断，是一种处理异步事件的方法。一般来说，操作系统都支持许多信号。尤<br>其是UNIX，比较重要应用程序一般都会处理信号。UNIX定义了许多信号，比如SIGINT<br>表示中断字符信号，也就是Ctrl+C的信号，SIGBUS表示硬件故障的信号；SIGCHLD表示<br>子进程状态改变信号；SIGKILL表示终止程序运行的信号，等等。信号量编程是UNIX下<br>非常重要的一种技术。 <br>GDB有能力在你调试程序的时候处理任何一种信号，你可以告诉GDB需要处理哪一种信<br>号。你可以要求GDB收到你所指定的信号时，马上停住正在运行的程序，以供你进行调试。<br>你可以用GDB的handle命令来完成这一功能。 <br>handle <br>在GDB中定义一个信号处理。信号可以以SIG开头或不以SIG开头，可以用定义一个要处<br>理信号的范围（如：SIGIO-SIGKILL，表示处理从SIGIO信号到SIGKILL的信号，其中包<br>括SIGIO，SIGIOT，SIGKILL三个信号），也可以使用关键字all来标明要处理所有的信号。<br>一旦被调试的程序接收到信号，运行程序马上会被GDB停住，以供调试。其可以是以下几<br>种关键字的一个或多个。 <br>nostop <br>当被调试的程序收到信号时，GDB不会停住程序的运行，但会打出消息告诉你收到这种信<br>号。 <br>stop <br>当被调试的程序收到信号时，GDB会停住你的程序。 <br>print <br>当被调试的程序收到信号时，GDB会显示出一条信息。 <br>noprint <br>当被调试的程序收到信号时，GDB不会告诉你收到信号的信息。 <br>pass <br>noignore <br>当被调试的程序收到信号时，GDB不处理信号。这表示，GDB会把这个信号交给被调试程<br>序会处理。 <br>nopass <br>ignore <br>当被调试的程序收到信号时，GDB不会让被调试程序来处理这个信号。 <br>info signals <br>info handle <br>查看有哪些信号在被GDB检测中。 <br>十、线程（Thread Stops） </p>
<p>如果你程序是多线程的话，你可以定义你的断点是否在所有的线程上，或是在某个特定的线<br>程。GDB很容易帮你完成这一工作。 <br>break thread <br>break thread if ... <br>linespec指定了断点设置在的源程序的行号。threadno指定了线程的ID，注意，这个ID是<br>GDB分配的，你可以通过&#8220;info threads&#8221;命令来查看正在运行程序中的线程信息。如果你<br>不指定thread 则表示你的断点设在所有线程上面。你还可以为某线程指定断点条件。如： <br>(gdb) break frik.c:13 thread 28 if bartab &gt; lim <br>当你的程序被GDB停住时，所有的运行线程都会被停住。这方便你你查看运行程序的总体<br>情况。而在你恢复程序运行时，所有的线程也会被恢复运行。那怕是主进程在被单步调试时。 <br>查看栈信息 <br>————— <br>当程序被停住了，你需要做的第一件事就是查看程序是在哪里停住的。当你的程序调用了一<br>个函数，函数的地址，函数参数，函数内的局部变量都会被压入&#8220;栈&#8221;（Stack）中。你可以<br>用GDB命令来查看当前的栈中的信息。 <br>下面是一些查看函数调用栈信息的GDB命令： <br>backtrace <br>bt <br>打印当前的函数调用栈的所有信息。如： <br>(gdb) bt <br>#0 func (n=250) at tst.c:6 <br>#1 0x08048524 in main (argc=1, argv=0xbffff674) at tst.c:30 <br>#2 0x400409ed in __libc_start_main () from /lib/libc.so.6 <br>从上可以看出函数的调用栈信息：__libc_start_main --&gt; main() --&gt; func() <br>backtrace <br>bt <br>n是一个正整数，表示只打印栈顶上n层的栈信息。 <br>backtrace &lt;-n&gt; <br>bt &lt;-n&gt; <br>-n表一个负整数，表示只打印栈底下n层的栈信息。 <br>如果你要查看某一层的信息，你需要在切换当前的栈，一般来说，程序停止时，最顶层的栈</p>
<p>就是当前栈，如果你要查看栈下面层的详细信息，首先要做的是切换当前栈。 <br>frame <br>f <br>n是一个从0开始的整数，是栈中的层编号。比如：frame 0，表示栈顶，frame 1，表示栈的<br>第二层。 <br>up <br>表示向栈的上面移动n层，可以不打n，表示向上移动一层。 <br>down <br>表示向栈的下面移动n层，可以不打n，表示向下移动一层。 <br>上面的命令，都会打印出移动到的栈层的信息。如果你不想让其打出信息。你可以使用这三<br>个命令： <br>select-frame 对应于 frame 命令。 <br>up-silently 对应于 up 命令。 <br>down-silently 对应于 down 命令。 <br>查看当前栈层的信息，你可以用以下GDB命令： <br>frame 或 f <br>会打印出这些信息：栈的层编号，当前的函数名，函数参数值，函数所在文件及行号，函数<br>执行到的语句。 <br>info frame <br>info f <br>这个命令会打印出更为详细的当前栈层的信息，只不过，大多数都是运行时的内内地址。比<br>如：函数地址，调用函数的地址，被调用函数的地址，目前的函数是由什么样的程序语言写<br>成的、函数参数地址及值、局部变量的地址等等。如： <br>(gdb) info f <br>Stack level 0, frame at 0xbffff5d4: <br>eip = 0x804845d in func (tst.c:6); saved eip 0x8048524 <br>called by frame at 0xbffff60c <br>source language c. <br>Arglist at 0xbffff5d4, args: n=250 <br>Locals at 0xbffff5d4, Previous frame's sp is 0x0 <br>Saved registers: <br>ebp at 0xbffff5d4, eip at 0xbffff5d8 <br>info args </p>
<p>打印出当前函数的参数名及其值。 <br>info locals <br>打印出当前函数中所有局部变量及其值。 <br>info catch <br>打印出当前的函数中的异常处理信息。 <br>查看源程序 <br>————— <br>一、显示源代码 <br>GDB 可以打印出所调试程序的源代码，当然，在程序编译时一定要加上-g的参数，把源程<br>序信息编译到执行文件中。不然就看不到源程序了。当程序停下来以后，GDB会报告程序<br>停在了那个文件的第几行上。你可以用list命令来打印程序的源代码。还是来看一看查看源<br>代码的GDB命令吧。 <br>list <br>显示程序第linenum行的周围的源程序。 <br>list <br>显示函数名为function的函数的源程序。 <br>list <br>显示当前行后面的源程序。 <br>list - <br>显示当前行前面的源程序。 <br>一般是打印当前行的上5行和下5行，如果显示函数是是上2行下8行，默认是10行，当<br>然，你也可以定制显示的范围，使用下面命令可以设置一次显示源程序的行数。 <br>set listsize <br>设置一次显示源代码的行数。 <br>show listsize <br>查看当前listsize的设置。 <br>list命令还有下面的用法： <br>list , </p>
<p>显示从first行到last行之间的源代码。 <br>list , <br>显示从当前行到last行之间的源代码。 <br>list + <br>往后显示源代码。 <br>一般来说在list后面可以跟以下这们的参数： <br>行号。 <br>&lt;+offset&gt; 当前行号的正偏移量。 <br>&lt;-offset&gt; 当前行号的负偏移量。 <br>哪个文件的哪一行。 <br>函数名。 <br>哪个文件中的哪个函数。 <br>&lt;*address&gt; 程序运行时的语句在内存中的地址。 <br>二、搜索源代码 <br>不仅如此，GDB还提供了源代码搜索的命令： <br>forward-search <br>search <br>向前面搜索。 <br>reverse-search <br>全部搜索。 <br>其中，就是正则表达式，也主一个字符串的匹配模式，关于正则表达式，我就不在这里讲了，<br>还请各位查看相关资料。 <br>三、指定源文件的路径 <br>某些时候，用-g编译过后的执行程序中只是包括了源文件的名字，没有路径名。GDB提供<br>了可以让你指定源文件的路径的命令，以便GDB进行搜索。 <br>directory <br>dir <br>加一个源文件路径到当前路径的前面。如果你要指定多个路径，UNIX下你可以使用&#8220;:&#8221;，<br>Windows下你可以使用&#8220;;&#8221;。 </p>
<p>directory <br>清除所有的自定义的源文件搜索路径信息。 <br>show directories <br>显示定义了的源文件搜索路径。 <br>四、源代码的内存 <br>你可以使用info line命令来查看源代码在内存中的地址。info line后面可以跟&#8220;行号&#8221;，&#8220;函<br>数名&#8221;，&#8220;文件名:行号&#8221;，&#8220;文件名:函数名&#8221;，这个命令会打印出所指定的源码在运行时的内<br>存地址，如： <br>(gdb) info line tst.c:func <br>Line 5 of "tst.c" starts at address 0x8048456 and ends at 0x804845d . <br>还有一个命令（disassemble）你可以查看源程序的当前执行时的机器码，这个命令会把目前<br>内存中的指令dump出来。如下面的示例表示查看函数func的汇编代码。 <br>(gdb) disassemble func <br>Dump of assembler code for function func: <br>0x8048450 : push %ebp <br>0x8048451 : mov %esp,%ebp <br>0x8048453 : sub $0x18,%esp <br>0x8048456 : movl $0x0,0xfffffffc(%ebp) <br>0x804845d : movl $0x1,0xfffffff8(%ebp) <br>0x8048464 : mov 0xfffffff8(%ebp),%eax <br>0x8048467 : cmp 0x8(%ebp),%eax <br>0x804846a : jle 0x8048470 <br>0x804846c : jmp 0x8048480 <br>0x804846e : mov %esi,%esi <br>0x8048470 : mov 0xfffffff8(%ebp),%eax <br>0x8048473 : add %eax,0xfffffffc(%ebp) <br>0x8048476 : incl 0xfffffff8(%ebp) <br>0x8048479 : jmp 0x8048464 <br>0x804847b : nop <br>0x804847c : lea 0x0(%esi,1),%esi <br>0x8048480 : mov 0xfffffffc(%ebp),%edx <br>0x8048483 : mov %edx,%eax <br>0x8048485 : jmp 0x8048487 <br>0x8048487 : mov %ebp,%esp <br>0x8048489 : pop %ebp <br>0x804848a : ret <br>End of assembler dump. </p>
<p>查看运行时数据 <br>——————— <br>在你调试程序时，当程序被停住时，你可以使用print命令（简写命令为p），或是同义命令<br>inspect来查看当前程序的运行数据。print命令的格式是： <br>print <br>print / <br>是表达式，是你所调试的程序的语言的表达式（GDB可以调试多种编程语言），是输出的格<br>式，比如，如果要把表达式按16进制的格式输出，那么就是/x。 <br>一、表达式 <br>print和许多GDB的命令一样，可以接受一个表达式，GDB会根据当前的程序运行的数据<br>来计算这个表达式，既然是表达式，那么就可以是当前程序运行中的const常量、变量、函<br>数等内容。可惜的是GDB不能使用你在程序中所定义的宏。 <br>表达式的语法应该是当前所调试的语言的语法，由于C/C++是一种大众型的语言，所以，本<br>文中的例子都是关于C/C++的。（而关于用GDB调试其它语言的章节，我将在后面介绍） <br>在表达式中，有几种GDB所支持的操作符，它们可以用在任何一种语言中。 <br>@ <br>是一个和数组有关的操作符，在后面会有更详细的说明。 <br>:: <br>指定一个在文件或是一个函数中的变量。 <br>{} <br>表示一个指向内存地址的类型为type的一个对象。 <br>二、程序变量 <br>在GDB中，你可以随时查看以下三种变量的值： <br>1、全局变量（所有文件可见的） <br>2、静态全局变量（当前文件可见的） <br>3、局部变量（当前Scope可见的） <br>如果你的局部变量和全局变量发生冲突（也就是重名），一般情况下是局部变量会隐藏全局<br>变量，也就是说，如果一个全局变量和一个函数中的局部变量同名时，如果当前停止点在函</p>
<p>数中，用print显示出的变量的值会是函数中的局部变量的值。如果此时你想查看全局变量<br>的值时，你可以使用&#8220;::&#8221;操作符： <br>file::variable <br>function::variable <br>可以通过这种形式指定你所想查看的变量，是哪个文件中的或是哪个函数中的。例如，查看<br>文件f2.c中的全局变量x的值： <br>gdb) p 'f2.c'::x <br>当然，&#8220;::&#8221;操作符会和C++中的发生冲突，GDB能自动识别&#8220;::&#8221; 是否C++的操作符，所<br>以你不必担心在调试C++程序时会出现异常。 <br>另外，需要注意的是，如果你的程序编译时开启了优化选项，那么在用GDB调试被优化过<br>的程序时，可能会发生某些变量不能访问，或是取值错误码的情况。这个是很正常的，因为<br>优化程序会删改你的程序，整理你程序的语句顺序，剔除一些无意义的变量等，所以在GDB<br>调试这种程序时，运行时的指令和你所编写指令就有不一样，也就会出现你所想象不到的结<br>果。对付这种情况时，需要在编译程序时关闭编译优化。一般来说，几乎所有的编译器都支<br>持编译优化的开关，例如，GNU的C/C++编译器GCC，你可以使用&#8220;-gstabs&#8221;选项来解决<br>这个问题。关于编译器的参数，还请查看编译器的使用说明文档。 <br>三、数组 <br>有时候，你需要查看一段连续的内存空间的值。比如数组的一段，或是动态分配的数据的大<br>小。你可以使用GDB的&#8220;@&#8221;操作符，&#8220;@&#8221;的左边是第一个内存的地址的值，&#8220;@&#8221;的右<br>边则你你想查看内存的长度。例如，你的程序中有这样的语句： <br>int *array = (int *) malloc (len * sizeof (int)); <br>于是，在GDB调试过程中，你可以以如下命令显示出这个动态数组的取值： <br>p <a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#42;&#97;&#114;&#114;&#97;&#121;&#64;&#108;&#101;&#110;"><font color=#336699>*array@len</font></a> <br>@的左边是数组的首地址的值，也就是变量array所指向的内容，右边则是数据的长度，其<br>保存在变量len中，其输出结果，大约是下面这个样子的： <br>(gdb) p <a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#42;&#97;&#114;&#114;&#97;&#121;&#64;&#108;&#101;&#110;"><font color=#336699>*array@len</font></a> <br>$1 = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40} <br>如果是静态数组的话，可以直接用print数组名，就可以显示数组中所有数据的内容了。 <br>四、输出格式 </p>
<p>一般来说，GDB会根据变量的类型输出变量的值。但你也可以自定义GDB的输出的格式。<br>例如，你想输出一个整数的十六进制，或是二进制来查看这个整型变量的中的位的情况。要<br>做到这样，你可以使用GDB的数据显示格式： <br>x 按十六进制格式显示变量。 <br>d 按十进制格式显示变量。 <br>u 按十六进制格式显示无符号整型。 <br>o 按八进制格式显示变量。 <br>t 按二进制格式显示变量。 <br>a 按十六进制格式显示变量。 <br>c 按字符格式显示变量。 <br>f 按浮点数格式显示变量。 <br>(gdb) p i <br>$21 = 101 <br>(gdb) p/a i <br>$22 = 0x65 <br>(gdb) p/c i <br>$23 = 101 'e' <br>(gdb) p/f i <br>$24 = 1.41531145e-43 <br>(gdb) p/x i <br>$25 = 0x65 <br>(gdb) p/t i <br>$26 = 1100101 <br>五、查看内存 <br>你可以使用examine命令（简写是x）来查看内存地址中的值。x命令的语法如下所示： <br>x/ <br>n、f、u是可选的参数。 <br>n 是一个正整数，表示显示内存的长度，也就是说从当前地址向后显示几个地址的内容。 <br>f 表示显示的格式，参见上面。如果地址所指的是字符串，那么格式可以是s，如果地十是<br>指令地址，那么格式可以是i。 </p>
<p>u 表示从当前地址往后请求的字节数，如果不指定的话，GDB默认是4个bytes。u参数可<br>以用下面的字符来代替，b表示单字节，h表示双字节，w表示四字节，g表示八字节。当<br>我们指定了字节长度后，GDB会从指内存定的内存地址开始，读写指定字节，并把其当作<br>一个值取出来。 <br>表示一个内存地址。 <br>n/f/u三个参数可以一起使用。例如： <br>命令：x/3uh 0x54320 表示，从内存地址0x54320读取内容，h表示以双字节为一个单位，3<br>表示三个单位，u表示按十六进制显示。 <br>六、自动显示 <br>你可以设置一些自动显示的变量，当程序停住时，或是在你单步跟踪时，这些变量会自动显<br>示。相关的GDB命令是display。 <br>display <br>display/ <br>display/ <br>expr是一个表达式，fmt表示显示的格式，addr表示内存地址，当你用display设定好了一<br>个或多个表达式后，只要你的程序被停下来，GDB会自动显示你所设置的这些表达式的值。 <br>格式i和s同样被display支持，一个非常有用的命令是： <br>display/i $pc <br>$pc是GDB的环境变量，表示着指令的地址，/i则表示输出格式为机器指令码，也就是汇<br>编。于是当程序停下后，就会出现源代码和机器指令码相对应的情形，这是一个很有意思的<br>功能。 <br>下面是一些和display相关的GDB命令： <br>undisplay <br>delete display <br>删除自动显示，dnums意为所设置好了的自动显式的编号。如果要同时删除几个，编号可以<br>用空格分隔，如果要删除一个范围内的编号，可以用减号表示（如：2-5） <br>disable display <br>enable display <br>disable和enalbe不删除自动显示的设置，而只是让其失效和恢复。 </p>
<p>info display <br>查看display设置的自动显示的信息。GDB会打出一张表格，向你报告当然调试中设置了多<br>少个自动显示设置，其中包括，设置的编号，表达式，是否enable。 <br>七、设置显示选项 <br>GDB中关于显示的选项比较多，这里我只例举大多数常用的选项。 <br>set print address <br>set print address on <br>打开地址输出，当程序显示函数信息时，GDB会显出函数的参数地址。系统默认为打开的，<br>如： <br>(gdb) f <br>#0 set_quotes (lq=0x34c78 "&lt;&lt;", rq=0x34c88 "&gt;&gt;") <br>at input.c:530 <br>530 if (lquote != def_lquote) <br>set print address off <br>关闭函数的参数地址显示，如： <br>(gdb) set print addr off <br>(gdb) f <br>#0 set_quotes (lq="&lt;&lt;", rq="&gt;&gt;") at input.c:530 <br>530 if (lquote != def_lquote) <br>show print address <br>查看当前地址显示选项是否打开。 <br>set print array <br>set print array on <br>打开数组显示，打开后当数组显示时，每个元素占一行，如果不打开的话，每个元素则以逗<br>号分隔。这个选项默认是关闭的。与之相关的两个命令如下，我就不再多说了。 <br>set print array off <br>show print array <br>set print elements <br>这个选项主要是设置数组的，如果你的数组太大了，那么就可以指定一个来指定数据显示的<br>最大长度，当到达这个长度时，GDB就不再往下显示了。如果设置为0，则表示不限制。 <br>show print elements </p>
<p>查看print elements的选项信息。 <br>set print null-stop <br>如果打开了这个选项，那么当显示字符串时，遇到结束符则停止显示。这个选项默认为off。 <br>set print pretty on <br>如果打开printf pretty这个选项，那么当GDB显示结构体时会比较漂亮。如： <br>$1 = { <br>next = 0x0, <br>flags = { <br>sweet = 1, <br>sour = 1 <br>}, <br>meat = 0x54 "Pork" <br>} <br>set print pretty off <br>关闭printf pretty这个选项，GDB显示结构体时会如下显示： <br>$1 = {next = 0x0, flags = {sweet = 1, sour = 1}, meat = 0x54 "Pork"} <br>show print pretty <br>查看GDB是如何显示结构体的。 <br>set print sevenbit-strings <br>设置字符显示，是否按&#8220;\nnn&#8221;的格式显示，如果打开，则字符串或字符数据按\nnn显示，<br>如&#8220;\065&#8221;。 <br>show print sevenbit-strings <br>查看字符显示开关是否打开。 <br>set print union <br>设置显示结构体时，是否显式其内的联合体数据。例如有以下数据结构： <br>typedef enum {Tree, Bug} Species; <br>typedef enum {Big_tree, Acorn, Seedling} Tree_forms; <br>typedef enum {Caterpillar, Cocoon, Butterfly} <br>Bug_forms; <br>struct thing { <br>Species it; <br>union { </p>
<p>Tree_forms tree; <br>Bug_forms bug; <br>} form; <br>}; <br>struct thing foo = {Tree, {Acorn}}; <br>当打开这个开关时，执行 p foo 命令后，会如下显示： <br>$1 = {it = Tree, form = {tree = Acorn, bug = Cocoon}} <br>当关闭这个开关时，执行 p foo 命令后，会如下显示： <br>$1 = {it = Tree, form = {...}} <br>show print union <br>查看联合体数据的显示方式 <br>set print object <br>在C++中，如果一个对象指针指向其派生类，如果打开这个选项，GDB会自动按照虚方法<br>调用的规则显示输出，如果关闭这个选项的话，GDB就不管虚函数表了。这个选项默认是<br>off。 <br>show print object <br>查看对象选项的设置。 <br>set print static-members <br>这个选项表示，当显示一个C++对象中的内容是，是否显示其中的静态数据成员。默认是<br>on。 <br>show print static-members <br>查看静态数据成员选项设置。 <br>set print vtbl <br>当此选项打开时，GDB将用比较规整的格式来显示虚函数表时。其默认是关闭的。 <br>show print vtbl <br>查看虚函数显示格式的选项。 <br>八、历史记录 <br>当你用GDB的print查看程序运行时的数据时，你每一个print都会被GDB记录下来。GDB<br>会以$1, $2, $3 .....这样的方式为你每一个print命令编上号。于是，你可以使用这个编号访问<br>以前的表达式，如$1。这个功能所带来的好处是，如果你先前输入了一个比较长的表达式，<br>如果你还想查看这个表达式的值，你可以使用历史记录来访问，省去了重复输入。 </p>
<p>九、GDB环境变量 <br>你可以在GDB的调试环境中定义自己的变量，用来保存一些调试程序中的运行数据。要定<br>义一个GDB的变量很简单只需。使用GDB的set命令。GDB的环境变量和UNIX一样，<br>也是以$起头。如： <br>set $foo = *object_ptr <br>使用环境变量时，GDB会在你第一次使用时创建这个变量，而在以后的使用中，则直接对<br>其賦值。环境变量没有类型，你可以给环境变量定义任一的类型。包括结构体和数组。 <br>show convenience <br>该命令查看当前所设置的所有的环境变量。 <br>这是一个比较强大的功能，环境变量和程序变量的交互使用，将使得程序调试更为灵活便捷。<br>例如： <br>set $i = 0 <br>print bar[$i++]-&gt;contents <br>于是，当你就不必，print bar[0]-&gt;contents, print bar[1]-&gt;contents地输入命令了。输入这样的<br>命令后，只用敲回车，重复执行上一条语句，环境变量会自动累加，从而完成逐个输出的功<br>能。 <br>十、查看寄存器 <br>要查看寄存器的值，很简单，可以使用如下命令： <br>info registers <br>查看寄存器的情况。（除了浮点寄存器） <br>info all-registers <br>查看所有寄存器的情况。（包括浮点寄存器） <br>info registers <br>查看所指定的寄存器的情况。 <br>寄存器中放置了程序运行时的数据，比如程序当前运行的指令地址（ip），程序的当前堆栈<br>地址（sp）等等。你同样可以使用print命令来访问寄存器的情况，只需要在寄存器名字前<br>加一个$符号就可以了。如：p $eip。 </p>
<p>改变程序的执行 <br>——————— <br>一旦使用GDB挂上被调试程序，当程序运行起来后，你可以根据自己的调试思路来动态地<br>在GDB中更改当前被调试程序的运行线路或是其变量的值，这个强大的功能能够让你更好<br>的调试你的程序，比如，你可以在程序的一次运行中走遍程序的所有分支。 <br>一、修改变量值 <br>修改被调试程序运行时的变量值，在GDB中很容易实现，使用GDB的print命令即可完成。<br>如： <br>(gdb) print x=4 <br>x=4这个表达式是C/C++的语法，意为把变量x的值修改为4，如果你当前调试的语言是<br>Pascal，那么你可以使用Pascal的语法：x:=4。 <br>在某些时候，很有可能你的变量和GDB中的参数冲突，如： <br>(gdb) whatis width <br>type = double <br>(gdb) p width <br>$4 = 13 <br>(gdb) set width=47 <br>Invalid syntax in expression. <br>因为，set width是GDB的命令，所以，出现了&#8220;Invalid syntax in expression&#8221;的设置错误，<br>此时，你可以使用set var命令来告诉GDB，width不是你GDB的参数，而是程序的变量名，<br>如： <br>(gdb) set var width=47 <br>另外，还可能有些情况，GDB并不报告这种错误，所以保险起见，在你改变程序变量取值<br>时，最好都使用set var格式的GDB命令。 <br>二、跳转执行 <br>一般来说，被调试程序会按照程序代码的运行顺序依次执行。GDB提供了乱序执行的功能，<br>也就是说，GDB可以修改程序的执行顺序，可以让程序执行随意跳跃。这个功能可以由GDB<br>的jump命令来完： <br>jump </p>
<p>指定下一条语句的运行点。可以是文件的行号，可以是file:line格式，可以是+num这种偏<br>移量格式。表式着下一条运行语句从哪里开始。 <br>jump <br>这里的 <br>是代码行的内存地址。 <br>注意，jump命令不会改变当前的程序栈中的内容，所以，当你从一个函数跳到另一个函数<br>时，当函数运行完返回时进行弹栈操作时必然会发生错误，可能结果还是非常奇怪的，甚至<br>于产生程序Core Dump。所以最好是同一个函数中进行跳转。 <br>熟悉汇编的人都知道，程序运行时，有一个寄存器用于保存当前代码所在的内存地址。所以，<br>jump命令也就是改变了这个寄存器中的值。于是，你可以使用&#8220;set $pc&#8221;来更改跳转执行<br>的地址。如： <br>set $pc = 0x485 <br>三、产生信号量 <br>使用singal命令，可以产生一个信号量给被调试的程序。如：中断信号Ctrl+C。这非常方便<br>于程序的调试，可以在程序运行的任意位置设置断点，并在该断点用GDB产生一个信号量，<br>这种精确地在某处产生信号非常有利程序的调试。 <br>语法是：signal ，UNIX的系统信号量通常从1到15。所以取值也在这个范围。 <br>single命令和shell的kill命令不同，系统的kill命令发信号给被调试程序时，是由GDB截<br>获的，而single命令所发出一信号则是直接发给被调试程序的。 <br>四、强制函数返回 <br>如果你的调试断点在某个函数中，并还有语句没有执行完。你可以使用return命令强制函数<br>忽略还没有执行的语句并返回。 <br>return <br>return <br>使用return命令取消当前函数的执行，并立即返回，如果指定了，那么该表达式的值会被认<br>作函数的返回值。 <br>五、强制调用函数 </p>
<p>call <br>表达式中可以一是函数，以此达到强制调用函数的目的。并显示函数的返回值，如果函数返<br>回值是void，那么就不显示。 <br>另一个相似的命令也可以完成这一功能——print，print后面可以跟表达式，所以也可以用他<br>来调用函数，print和call的不同是，如果函数返回void，call则不显示，print则显示函数返<br>回值，并把该值存入历史数据中。 <br>在不同语言中使用GDB <br>—————————— <br>GDB支持下列语言：C, C++, Fortran, PASCAL, Java, Chill, assembly, 和 Modula-2。一般说<br>来，GDB会根据你所调试的程序来确定当然的调试语言，比如：发现文件名后缀为&#8220;.c&#8221;<br>的，GDB会认为是C程序。文件名后缀为&#8220;.C, .cc, .cp, .cpp, .cxx, .c++&#8221;的，GDB会认为是<br>C++程序。而后缀是&#8220;.f, .F&#8221;的，GDB会认为是Fortran程序，还有，后缀为如果是&#8220;.s, .S&#8221;<br>的会认为是汇编语言。 <br>也就是说，GDB会根据你所调试的程序的语言，来设置自己的语言环境，并让GDB的命令<br>跟着语言环境的改变而改变。比如一些GDB命令需要用到表达式或变量时，这些表达式或<br>变量的语法，完全是根据当前的语言环境而改变的。例如C/C++中对指针的语法是*p，而在<br>Modula-2中则是p^。并且，如果你当前的程序是由几种不同语言一同编译成的，那到在调<br>试过程中，GDB也能根据不同的语言自动地切换语言环境。这种跟着语言环境而改变的功<br>能，真是体贴开发人员的一种设计。 <br>下面是几个相关于GDB语言环境的命令： <br>show language <br>查看当前的语言环境。如果GDB不能识为你所调试的编程语言，那么，C语言被认为是默<br>认的环境。 <br>info frame <br>查看当前函数的程序语言。 <br>info source <br>查看当前文件的程序语言。 <br>如果GDB没有检测出当前的程序语言，那么你也可以手动设置当前的程序语言。使用set <br>language命令即可做到。 <br>当set language命令后什么也不跟的话，你可以查看GDB所支持的语言种类： </p>
<p>(gdb) set language <br>The currently understood settings are: <br>local or auto Automatic setting based on source file <br>c Use the C language <br>c++ Use the C++ language <br>asm Use the Asm language <br>chill Use the Chill language <br>fortran Use the Fortran language <br>java Use the Java language <br>modula-2 Use the Modula-2 language <br>pascal Use the Pascal language <br>scheme Use the Scheme language <br>于是你可以在set language后跟上被列出来的程序语言名，来设置当前的语言环境。 <br>后记 <br>—— <br>GDB是一个强大的命令行调试工具。大家知道命令行的强大就是在于，其可以形成执行序<br>列，形成脚本。UNIX下的软件全是命令行的，这给程序开发提代供了极大的便利，命令行<br>软件的优势在于，它们可以非常容易的集成在一起，使用几个简单的已有工具的命令，就可<br>以做出一个非常强大的功能。 <br>于是UNIX下的软件比Windows下的软件更能有机地结合，各自发挥各自的长处，组合成<br>更为强劲的功能。而Windows下的图形软件基本上是各自为营，互相不能调用，很不利于<br>各种软件的相互集成。在这里并不是要和Windows做个什么比较，所谓&#8220;寸有所长，尺有<br>所短&#8221;，图形化工具还是有不如命令行的地方。（看到这句话时，希望各位千万再也不要认为<br>我就是&#8220;鄙视图形界面&#8221;，和我抬杠了 ） <br>我是根据版本为5.1.1的GDB所写的这篇文章，所以可能有些功能已被修改，或是又有更<br>为强劲的功能。而且，我写得非常仓促，写得比较简略，并且，其中我已经看到有许多错别<br>字了（我用五笔，所以错字让你看不懂），所以，我在这里对我文中的差错表示万分的歉意。 <br>文中所罗列的GDB的功能时，我只是罗列了一些带用的GDB的命令和使用方法，其实，<br>我这里只讲述的功能大约只占GDB所有功能的60%吧，详细的文档，还是请查看GDB的<br>帮助和使用手册吧，或许，过段时间，如果我有空，我再写一篇GDB的高级使用。 <br>我个人非常喜欢GDB的自动调试的功能，这个功能真的很强大，试想，我在UNIX下写个<br>脚本，让脚本自动编译我的程序，被自动调试，并把结果报告出来，调试成功，自动checkin<br>源码库。一个命令，编译带着调试带着checkin，多爽啊。只是GDB对自动化调试目前支持<br>还不是很成熟，只能实现半自动化，真心期望着GDB的自动化调试功能的成熟。</p>
<img src ="http://www.cppblog.com/bellgrade/aggbug/97323.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/bellgrade/" target="_blank">bellgrade</a> 2009-09-26 23:11 <a href="http://www.cppblog.com/bellgrade/archive/2009/09/26/97323.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>String类的实现</title><link>http://www.cppblog.com/bellgrade/archive/2009/09/26/97321.html</link><dc:creator>bellgrade</dc:creator><author>bellgrade</author><pubDate>Sat, 26 Sep 2009 14:44:00 GMT</pubDate><guid>http://www.cppblog.com/bellgrade/archive/2009/09/26/97321.html</guid><wfw:comment>http://www.cppblog.com/bellgrade/comments/97321.html</wfw:comment><comments>http://www.cppblog.com/bellgrade/archive/2009/09/26/97321.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/bellgrade/comments/commentRss/97321.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/bellgrade/services/trackbacks/97321.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 实现stl string类——转自C++ how to program。//&nbsp;String&nbsp;class&nbsp;definition&nbsp;with&nbsp;operator&nbsp;overloading.#ifndef&nbsp;STRING_H#define&nbsp;STRING_H#include&nbsp;&lt;iostream&gt;using&n...&nbsp;&nbsp;<a href='http://www.cppblog.com/bellgrade/archive/2009/09/26/97321.html'>阅读全文</a><img src ="http://www.cppblog.com/bellgrade/aggbug/97321.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/bellgrade/" target="_blank">bellgrade</a> 2009-09-26 22:44 <a href="http://www.cppblog.com/bellgrade/archive/2009/09/26/97321.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>