﻿<?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++博客-吴秦（Saylor）</title><link>http://www.cppblog.com/09212744/</link><description /><language>zh-cn</language><lastBuildDate>Wed, 22 Apr 2026 13:42:32 GMT</lastBuildDate><pubDate>Wed, 22 Apr 2026 13:42:32 GMT</pubDate><ttl>60</ttl><item><title>【日常小记】linux中强大且常用命令：find、grep</title><link>http://www.cppblog.com/09212744/archive/2010/12/25/137463.html</link><dc:creator>吴秦（Saylor）</dc:creator><author>吴秦（Saylor）</author><pubDate>Sat, 25 Dec 2010 12:29:00 GMT</pubDate><guid>http://www.cppblog.com/09212744/archive/2010/12/25/137463.html</guid><wfw:comment>http://www.cppblog.com/09212744/comments/137463.html</wfw:comment><comments>http://www.cppblog.com/09212744/archive/2010/12/25/137463.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/09212744/comments/commentRss/137463.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/09212744/services/trackbacks/137463.html</trackback:ping><description><![CDATA[<p>在linux下面工作，有些命令能够大大提高效率。本文就向大家介绍find、grep命令，他哥俩可以算是必会的linux命令，我几乎每天都要用到他们。本文结构如下：</p> <ul> <li>find命令  <ul> <li>find命令的一般形式  <li>find命令的常用选项及实例  <li>find与xargs </li></ul> <li>grep命令  <ul> <li>grep命令的一般形式  <li>grep正则表达式元字符集(基本集)  <li>grep命令的常用选项及实例 </li></ul></li></ul> <h1>1、find命令</h1> <p>find命令是一个无处不在命令，是linux中最有用的命令之一。find命令用于：在一个目录（及子目录）中搜索文件，你可以指定一些匹配条件，如按文件名、文件类型、用户甚至是时间戳查找文件。下面就通过实例来体验下find命令的强大。</p> <h2>1.1、find命令的一般形式</h2> <p>man文档中给出的find命令的一般形式为：</p> <p>find [-H] [-L] [-P] [-D debugopts] [-Olevel] [path...] [expression]</p> <p>其实[-H] [-L] [-P] [-D debugopts] [-Olevel]这几个选项并不常用（至少在我的日常工作中，没有用到过），上面的find命令的常用形式可以简化为：</p> <p><font size="3">find [path...] [expression]</font></p> <li>path：find命令所查找的目录路径。例如用.来表示当前目录，用/来表示系统根目录  <li>expression：expression可以分为——“-options [-print -exec -ok ...]”  <li>-options，指定find命令的常用选项，下节详细介绍  <li>-print，find命令将匹配的文件输出到标准输出  <li>-exec，find命令对匹配的文件执行该参数所给出的shell命令。相应命令的形式为'command' {&nbsp; } \;，注意{&nbsp;&nbsp; }和\；之间的空格 <br>find ./ -size 0 -exec rm {} \; 删除文件大小为零的文件 （还可以以这样做：rm -i `find ./ -size 0`&nbsp; 或 find ./ -size 0 | xargs rm -f &amp;） <br>为了用ls -l命令列出所匹配到的文件，可以把ls -l命令放在find命令的-exec选项中：find . -type f -exec ls -l {&nbsp; } \; <br>在/logs目录中查找更改时间在5日以前的文件并删除它们：find /logs -type f -mtime +5 -exec rm {&nbsp; } \;  <li>-ok，和-exec的作用相同，只不过以一种更为安全的模式来执行该参数所给出的shell命令，在执行每一个命令之前，都会给出提示，让用户来确定是否执行。 <br>find . -name "*.conf"&nbsp; -mtime +5 -ok rm {&nbsp; } \; 在当前目录中查找所有文件名以.LOG结尾、更改时间在5日以上的文件，并删除它们，只不过在删除之前先给出提示  <p>也有人这样总结find命令的结构：</p><pre><font size="3">find <font color="#d16349">start_directory</font> test <br>     <font color="#d16349">options</font> <br>     <font color="#d16349">criteria_to_match</font> <br>     <font color="#d16349">action_to_perform_on_results</font>
</font></pre>
<h2>1.2、find命令的常用选项及实例</h2>
<li>-name <br>按照文件名查找文件。 <br>find /dir -name filename&nbsp; 在/dir目录及其子目录下面查找名字为filename的文件 <br>find . -name "*.c" 在当前目录及其子目录（用“.”表示）中查找任何扩展名为“c”的文件 
<li>-perm <br>按照文件权限来查找文件。 <br>find . -perm 755 –print 在当前目录下查找文件权限位为755的文件，即文件属主可以读、写、执行，其他用户可以读、执行的文件 
<li>-prune <br>使用这一选项可以使find命令不在当前指定的目录中查找，如果同时使用-depth选项，那么-prune将被find命令忽略。 <br><font color="#d16349">find /apps -path "/apps/bin" -prune -o –print</font> 在/apps目录下查找文件，但不希望在/apps/bin目录下查找 <br><font color="#d16349">find /usr/sam -path "/usr/sam/dir1" -prune -o –print</font> 在/usr/sam目录下查找不在dir1子目录之内的所有文件 
<li>-user <br>按照文件属主来查找文件。 <br><font color="#d16349">find ~ -user sam –print</font> 在$HOME目录中查找文件属主为sam的文件 
<li>-group <br>按照文件所属的组来查找文件。 <br><font color="#d16349">find /apps -group gem –print</font> 在/apps目录下查找属于gem用户组的文件&nbsp; <li>-mtime -n +n <br>按照文件的更改时间来查找文件， - n表示文件更改时间距现在n天以内，+ n表示文件更改时间距现在n天以前。 <br><font color="#d16349">find / -mtime -5 –print</font> 在系统根目录下查找更改时间在5日以内的文件 <br><font color="#d16349">find /var/adm -mtime +3 –print</font> 在/var/adm目录下查找更改时间在3日以前的文件 
<li>-nogroup <br>查找无有效所属组的文件，即该文件所属的组在/etc/groups中不存在。 <br><font color="#d16349">find / –nogroup -print </font>
<li>-nouser <br>查找无有效属主的文件，即该文件的属主在/etc/passwd中不存在。 <br><font color="#d16349">find /home -nouser –print </font>
<li>-newer file1 ! file2 <br>查找更改时间比文件file1新但比文件file2旧的文件。 
<li>-type <br>查找某一类型的文件，诸如： <br>b - 块设备文件。 <br>d - 目录。 <br>c - 字符设备文件。 <br>p - 管道文件。 <br>l - 符号链接文件。 <br>f - 普通文件。 <br><font color="#d16349">find /etc -type d –print</font> 在/etc目录下查找所有的目录 <br><font color="#d16349">find . ! -type d –print</font> 在当前目录下查找除目录以外的所有类型的文件 <br><font color="#d16349">find /etc -type l –print</font> 在/etc目录下查找所有的符号链接文件 
<li>-size n：[c] 查找文件长度为n块的文件，带有c时表示文件长度以字节计。 <br><font color="#d16349">find . -size +1000000c –print</font> 在当前目录下查找文件长度大于1 M字节的文件 <br><font color="#d16349">find /home/apache -size 100c –print</font> 在/home/apache目录下查找文件长度恰好为100字节的文件 <br><font color="#d16349">find . -size +10 –print</font> 在当前目录下查找长度超过10块的文件（一块等于512字节） 
<li>-depth：在查找文件时，首先查找当前目录中的文件，然后再在其子目录中查找。 <br><font color="#d16349">find / -name "CON.FILE" -depth –print</font> 它将首先匹配所有的文件然后再进入子目录中查找&nbsp; <li>-mount：在查找文件时不跨越文件系统mount点。&nbsp; <br><font color="#d16349">find . -name "*.XC" -mount –print</font> 从当前目录开始查找位于本文件系统中文件名以XC结尾的文件（不进入其他文件系统） 
<li>-follow：如果find命令遇到符号链接文件，就跟踪至链接所指向的文件。 
<h2>1.3、find与xargs</h2>
<p>在使用find命令的-exec选项处理匹配到的文件时， find命令将所有匹配到的文件一起传递给exec执行。但有些系统对能够传递给exec的命令长度有限制，这样在find命令运行几分钟之后，就会出现溢出错误。错误信息通常是“参数列太长”或“参数列溢出”。这就是xargs命令的用处所在，特别是与find命令一起使用。</p>
<p>find命令把匹配到的文件传递给xargs命令，而xargs命令每次只获取一部分文件而不是全部，不像-exec选项那样。这样它可以先处理最先获取的一部分文件，然后是下一批，并如此继续下去。</p>
<p>在有些系统中，使用-exec选项会为处理每一个匹配到的文件而发起一个相应的进程，并非将匹配到的文件全部作为参数一次执行；这样在有些情况下就会出现进程过多，系统性能下降的问题，因而效率不高；</p>
<p>而使用xargs命令则只有一个进程。另外，<font size="3">在使用xargs命令时，究竟是一次获取所有的参数，还是分批取得参数，以及每一次获取参数的数目都会根据该命令的选项及系统内核中相应的可调参数来确定。</font></p>
<p>来看看xargs命令是如何同find命令一起使用的，并给出一些例子。</p>
<p><font color="#d16349">find . -type f -print | xargs file</font> 查找系统中的每一个普通文件，然后使用xargs命令来测试它们分别属于哪类文件</p>
<p><font color="#d16349">find / -name "core" -print | xargs echo "" &gt;/tmp/core.log</font> 在整个系统中查找内存信息转储文件(core dump) ，然后把结果保存到/tmp/core.log 文件中：</p>
<p><font color="#d16349">find . -type f -print | xargs grep "hostname"</font> 用grep命令在所有的普通文件中搜索hostname这个词</p>
<p><font color="#d16349">find ./ -mtime +3 -print|xargs rm -f –r</font> 删除3天以前的所有东西 （find . -ctime +3 -exec rm -rf {} \;）</p>
<p><font color="#d16349">find ./ -size 0 | xargs rm -f &amp;</font> 删除文件大小为零的文件</p>
<p>find命令配合使用exec和xargs可以使用户对所匹配到的文件执行几乎所有的命令。</p>
<h1>2、grep命令</h1>
<p>grep (global search regular expression(RE) and print out the line,全面搜索正则表达式并把行打印出来)是一种强大的文本搜索工具，它能使用正则表达式搜索文本，并把匹配的行打印出来。</p>
<h2>2.1、grep命令的一般选项及实例</h2>
<p><font size="3">grep [OPTIONS] PATTERN [FILE...] <br>grep [OPTIONS] [-e PATTERN | -f FILE] [FILE...]</font></p>
<p>grep命令用于搜索由Pattern参数指定的模式，并将每个匹配的行写入标准输出中。这些模式是具有限定的正则表达式，它们使用ed或egrep命令样式。如果在File参数中指定了多个名称，grep命令将显示包含匹配行的文件的名称。对 shell 有特殊含义的字符 ($, *, [, |, ^, (, ), \ ) 出现在 Pattern参数中时必须带双引号。如果 Pattern参数不是简单字符串，通常必须用单引号将整个模式括起来。在诸如 [a-z], 之类的表达式中，-（减号）cml 可根据当前正在整理的序列来指定一个范围。整理序列可以定义等价的类以供在字符范围中使用。如果未指定任何文件，grep会假定为标准输入。</p>
<h2>2.2、grep正则表达式元字符集(基本集)</h2>
<p>^&nbsp; 锚定行的开始 如：'^grep'匹配所有以grep开头的行。</p>
<p>$&nbsp; 锚定行的结束 如：'grep$'匹配所有以grep结尾的行。</p>
<p>.&nbsp;&nbsp; 匹配一个非换行符的字符 如：'gr.p'匹配gr后接一个任意字符，然后是p。</p>
<p>*&nbsp; 匹配零个或多个先前字符 如：'*grep'匹配所有一个或多个空格后紧跟grep的行。<font size="3"> .*一起用代表任意字符</font>。</p>
<p>[] 匹配一个指定范围内的字符，如'[Gg]rep'匹配Grep和grep。</p>
<p>[^]&nbsp; 匹配一个不在指定范围内的字符，如：'[^A-FH-Z]rep'匹配不包含A-R和T-Z的一个字母开头，紧跟rep的行。</p>
<p>\(..\)&nbsp; 标记匹配字符，如：'\(love\)'，love被标记为1。</p>
<p>\&lt;&nbsp; 锚定单词的开始，如：'\&lt;grep'匹配包含以grep开头的单词的行。</p>
<p>\&gt;&nbsp; 锚定单词的结束，如'grep\&gt;'匹配包含以grep结尾的单词的行。</p>
<p>x\{m\} 连续重复字符x，m次，如：'o\{5\}'匹配包含连续5个o的行。</p>
<p>x\{m,\} 连续重复字符x,至少m次，如：'o\{5,\}'匹配至少连续有5个o的行。</p>
<p>x\{m,n\} 连续重复字符x，至少m次，不多于n次，如：'o\{5,10\}'匹配连续5--10个o的行。</p>
<p>\w&nbsp; 匹配一个文字和数字字符，也就是[A-Za-z0-9]，如：'G\w*p'匹配以G后跟零个或多个文字或数字字符，然后是p。</p>
<p>\W&nbsp; w的反置形式，匹配一个非单词字符，如点号句号等。\W*则可匹配多个。</p>
<p>\b&nbsp; 单词锁定符，如: '\bgrep\b'只匹配grep，即只能是grep这个单词，两边均为空格。</p>
<h2>2.3、grep命令的常用选项及实例</h2>
<p>-?</p>
<p>同时显示匹配行上下的？行，如：grep -2 pattern filename同时显示匹配行的上下2行。</p>
<p>-b，--byte-offset</p>
<p>打印匹配行前面打印该行所在的块号码。</p>
<p>-c,--count</p>
<p>只打印匹配的行数，不显示匹配的内容。</p>
<p>-f File，--file=File</p>
<p>从文件中提取模板。空文件中包含0个模板，所以什么都不匹配。</p>
<p>-h，--no-filename</p>
<p>当搜索多个文件时，不显示匹配文件名前缀。</p>
<p>-i，--ignore-case</p>
<p>忽略大小写差别。</p>
<p>-q，--quiet</p>
<p>取消显示，只返回退出状态。0则表示找到了匹配的行。</p>
<p>-l，--files-with-matches</p>
<p>打印匹配模板的文件清单。</p>
<p>-L，--files-without-match</p>
<p>打印不匹配模板的文件清单。</p>
<p>-n，--line-number</p>
<p>在匹配的行前面打印行号。</p>
<p>-s，--silent</p>
<p>不显示关于不存在或者无法读取文件的错误信息。</p>
<p>-v，--revert-match</p>
<p>反检索，只显示不匹配的行。</p>
<p>-w，--word-regexp</p>
<p>如果被\&lt;和\&gt;引用，就把表达式做为一个单词搜索。</p>
<p>-V，--version</p>
<p>显示软件版本信息。</p>
<p>=====</p>
<p><font color="#d16349">ls -l | grep '^a'</font> 通过管道过滤ls -l输出的内容，只显示以a开头的行。</p>
<p><font color="#d16349">grep 'test' d*</font> 显示所有以d开头的文件中包含test的行。</p>
<p><font color="#d16349">grep 'test' aa bb cc</font> 显示在aa，bb，cc文件中匹配test的行。</p>
<p><font color="#d16349">grep '[a-z]' aa</font> 显示所有包含每个字符串至少有5个连续小写字符的字符串的行。</p>
<p><font color="#d16349">grep 'w(es)t.*' aa</font> 如果west被匹配，则es就被存储到内存中，并标记为1，然后搜索任意个字符(.*)，这些字符后面紧跟着另外一个es()，找到就显示该行。如果用egrep或grep -E，就不用""号进行转义，直接写成'w(es)t.*'就可以了。</p>
<p><font color="#d16349">grep -i pattern files</font> ：不区分大小写地搜索。默认情况区分大小写</p>
<p><font color="#d16349">grep -l pattern files</font> ：只列出匹配的文件名，</p>
<p><font color="#d16349">grep -L pattern files</font> ：列出不匹配的文件名，</p>
<p><font color="#d16349">grep -w pattern files</font> ：只匹配整个单词，而不是字符串的一部分(如匹配‘magic’，而不是‘magical’)，</p>
<p><font color="#d16349">grep -C number pattern files</font> ：匹配的上下文分别显示[number]行，</p>
<p><font color="#d16349">grep pattern1 | pattern2 files</font> ：显示匹配 pattern1 或 pattern2 的行，</p>
<p><font color="#d16349">grep pattern1 files | grep pattern2</font> ：显示既匹配 pattern1 又匹配 pattern2 的行。</p>
<p>&nbsp;</p>
<p>参考文献：</p>
<ul>
<li>关于Linux Grep命令使用的详细介绍，<a href="http://fanqiang.chinaunix.net/system/linux/2007-03-15/5110.shtml">http://fanqiang.chinaunix.net/system/linux/2007-03-15/5110.shtml</a> 
<li>Linux文件查找命令find,xargs详述，<a href="http://www.linuxsir.org/main/?q=node/137#1.1">http://www.linuxsir.org/main/?q=node/137#1.1</a> 
<li>man文档（man find、man grep） </li></ul>
<p align="right">——借此感谢在实习公司同事们给与的帮助，</p>
<p align="right">特别是Jay、Jeff。</p>
</li><img src ="http://www.cppblog.com/09212744/aggbug/137463.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/09212744/" target="_blank">吴秦（Saylor）</a> 2010-12-25 20:29 <a href="http://www.cppblog.com/09212744/archive/2010/12/25/137463.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++ Internals: STL之Map</title><link>http://www.cppblog.com/09212744/archive/2010/06/19/118235.html</link><dc:creator>吴秦（Saylor）</dc:creator><author>吴秦（Saylor）</author><pubDate>Sat, 19 Jun 2010 04:25:00 GMT</pubDate><guid>http://www.cppblog.com/09212744/archive/2010/06/19/118235.html</guid><wfw:comment>http://www.cppblog.com/09212744/comments/118235.html</wfw:comment><comments>http://www.cppblog.com/09212744/archive/2010/06/19/118235.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.cppblog.com/09212744/comments/commentRss/118235.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/09212744/services/trackbacks/118235.html</trackback:ping><description><![CDATA[<h4><strong>概述</strong></h4>
<p>Map是标准<strong>关联式容器</strong>（<em>associative</em> <em>container</em>）之一，一个map是一个键值对序列，即（key ,value）对。它提供基于key的<strong>快速</strong>检索能力，在一个map中key值是唯一的。map提供双向迭代器，即有从前往后的（iterator），也有从后往前的（reverse_iterator）。</p>
<p><strong>map要求能对key进行&lt;操作，且保持按key值递增有序</strong>，因此map上的迭代器也是递增有序的。如果对于元素并不需要保持有序，可以使用<strong>hash_map</strong>。</p>
<p>map中key值是唯一的，如果马匹中已存在一个键值对(昵称,密码):("skynet",407574364)，而我们还想插入一个键值对("skynet",472687789)<span style="text-decoration: line-through;">则会报错</span>（<span style="color: #ff0000;">不是报错，准确的说是，返回插入不成功！</span>）。而我们又的确想这样做，即一个键对应多个值，幸运的是multimap可是实现这个功能。</p>
<p>下面我们用实例来深入介绍<em>map</em>、<em>multimap</em>，主要内容如下：</p>
<ul>
    <li>1、例子引入  </li>
    <li>2、map中的类型定义  </li>
    <li>3、map中的迭代器和键值对  </li>
    <li>4、map中的构造函数与析构函数  </li>
    <li>5、map中的操作方法  </li>
    <li>6、再议map的插入操作  </li>
    <li>7、[]不仅插入  </li>
    <li>8、multimap  </li>
    <li>9、总结 </li>
</ul>
<h4><strong>1、例子引入</strong></h4>
<p>有一个服务器manager维护着接入服务器的client信息，包括clinetId、scanRate、socketAddr等等。我们定义一个结构体保存scanRate、socketAddr信息。如下：</p>
<pre class="brush: cpp;">typedef    int    clientId;
typedef struct{
int scanRate;
string socketAddr;
}clientInfo;</pre>
<p>我们用map保存这些信息：clientId为键key，clientInfo为值。这样我们可以通过clientId快速检索到client的相关信息，我们可以这样定义：</p>
<pre class="brush: cpp;">map&lt;clientId,clientInfo&gt; clientMap;</pre>
<p>这样我们定义了一个clientMap，如果我们要定义多个这样的map，需要多次写map&lt;clientId,clientInfo&gt; 变量名。为了避免这样情况，我们通常为map&lt;clientId,clientInfo&gt;定义个别名，如：</p>
<pre class="brush: cpp;">typedef map&lt;clientId,clientInfo&gt; clientEdp;
clientEdp clientMap;</pre>
<p>之后我们就可以像定义clientMap一样定义map&lt;clientId,clientInfo&gt;对象，这样的好处还有：如果我们需要修改map的定义，只需要在一处修改即可，避免修改不彻底造成的不一致现象。</p>
<p>我们这就完成了需要的map的定义，如果不定义或没有在它上面的操作的话，就像定义类而没有方法一样，意义不大或毫无意义。幸运的是，STL提供了这些常用操作：排序（注：map是不能也不要排序的，因为map本身已经排好序了）、打印、提取子部分、移除元素、添加元素、查找对象，就像数据库的增删改查操作！现在我们详细介绍这些操作，并逐步引入<em>hash_map</em>、<em>multimap</em>。</p>
<h4><strong>2、map中的类型定义</strong></h4>
<p>关联数组（<em>associative array</em>）是最有用的用户定义类型之一，经常内置在语言中用于文本处理等。一个关联数组通常也称为map，有时也称字典（<em>dictionary</em>），保存一对值。第一个值称为key、第二个称为映射值mapped-value。</p>
<p>标准map是定义在std命名空间中的一个模板，并表示为&lt;map&gt;。它首先定义了一组标准类型名字：</p>
<pre class="brush: cpp;">template&lt;class Key,class T,class Cmp=less&lt;key&gt;,
class A=allocator&lt;pair&lt;const Key,T&gt;&gt;
class std::map
{
public:
//types
typedef Key    key_type;
typedef T    mapped_type;
typedef pair&lt;const Key,T&gt;    value_type;
typedef    Cmp    key_compare;
typedef A    allocator_type;
typedef    typename    A::reference    reference;
typedef    typename    A::const_reference    const_reference;
typedef    implementation_define1    iterator;
typedef implementation_define2    const_iterator;
typedef    typename    A::size_type    size_type;
typedef    typename    A::difference_type    difference_type;
typedef    std::reverse_iterator&lt;iterator&gt;    reverse_iterator;
typedef    std::reverse_iterator&lt;const_iterator&gt;    const_reverse_iterator;
//...
}</pre>
<p>注意：map的value_type是一个(key,value)对，映射值的被认为是mapped_type。因此，一个map是一个pair&lt;const Key,mapped_type&gt;元素的序列。从const Key可以看出，map中键key是不可修改的。</p>
<p>不得不提的是map定义中Cmp和A都是可选项。Cmp是定义在元素之间的比较方法，默认是&lt;操作；A即allocator用来<strong>分配</strong>和<strong>释放</strong>map总键值对所需使用的内存，没有指定的话即默认使用的是STL提供的，也可以自定义allocator来管理内存的使用。多数情况，我们不指定这两个选项而使用默认值，这样我们定义map就像下面这样：</p>
<pre class="brush: cpp;">map&lt;int,clientInfo&gt; clientMap;</pre>
<p>Cmp和A都缺省。 通常，实际的迭代器是实现定义的，因为map很像使用了树的形式，这些迭代器通常提供树遍历的某种形式。逆向迭代器是使用标准的reverse_iterator模板构造的。</p>
<h4><strong>3、map中的迭代器和键值对</strong></h4>
<p>map提供惯常的返回迭代器的一组函数，如下所示：</p>
<pre class="brush: cpp;">template&lt;class Key,class T,class Cmp=less&lt;key&gt;,
class A=allocator&lt;pair&lt;const Key,T&gt;&gt;
class std::map
{
public:
//...
//iterators
iterator    begin();
const_iterator    begin()    const;
iterator    end();
const_iterator    end()    const;
reverse_iterator    rbegin();
const_reverse_iterator    rbegin()    const;
reverse_iterator    rend();
const_reverse_iterator    rend()    const;
//...
}</pre>
<p>map上的迭代器是pair&lt;const Key,mapped_type&gt;元素序列上简单的迭代。例如，我们可能需要打印出所有的客户端信息，像下面的程序这样。为了实现这个，我们首先向《例子引入》中定义的clientEdp中插入数据，然后打印出来：</p>
<pre class="brush: cpp;">#include&lt;iostream&gt;
#include&lt;map&gt;
#include&lt;string&gt;
using namespace std;
typedef    int    clientId;
typedef struct{
int scanRate;
string socketAddr;
}clientInfo;
int main(int argc,char** argv)
{
typedef map&lt;clientId,clientInfo&gt; clientEdp;
typedef map&lt;clientId,clientInfo&gt;::const_iterator iterator;
clientEdp clients;
clientInfo client[100];
char str[10];
string strAddr("socket addr client ");
for(int i=0;i&lt;100;i++)
{
client[i].scanRate=i+1;
//convert int to char*
itoa(i+1,str,10);
//concatenate strAddr and str
client[i].socketAddr=strAddr+str;
cout&lt;&lt;client[i].socketAddr&lt;&lt;endl;
clients.insert(
make_pair(i+1,client[i]));
}
delete str;
for(iterator i=clients.begin();i!=clients.end();i++)
{
cout&lt;&lt;"clientId:"&lt;&lt;i-&gt;first&lt;&lt;endl;
cout&lt;&lt;"scanRate:"&lt;&lt;i-&gt;second.scanRate&lt;&lt;endl;
cout&lt;&lt;"socketAddr:"&lt;&lt;i-&gt;second.socketAddr&lt;&lt;endl;
cout&lt;&lt;endl;
}
}</pre>
<p>一个map迭代器以key升序方式表示元素，因此客户端信息以cliendId升序的方式输出。运行结果可以证明这一点，运行结果如下所示：</p>
<p><a href="http://www.cppblog.com/images/cppblog_com/09212744/WindowsLiveWriter/CInternalsSTLMap_132F1/image_2.png"><img style="border-width: 0px; display: block; float: none; margin-left: auto; margin-right: auto;" title="image" alt="image" src="http://www.cppblog.com/images/cppblog_com/09212744/WindowsLiveWriter/CInternalsSTLMap_132F1/image_thumb.png" width="313" border="0" height="433"></a> </p>
<p align="center">图1、程序运行结果</p>
<p>我们以first引用键值对的key，以second引用mapped value，且不用管key和mapped value是什么类型。其实pair在std的模板中是这样定义的：</p>
<pre class="brush: cpp;">template &lt;class    T1,class T2&gt;struct std::pair{
typedef    T1    first_type;
typedef    T2    second_type;
T1    first;
T2    second;
pair():first(T1()),second(T2()){}
pair(const T1&amp; x,const T2&amp; y):first(x),second(y){}
template&lt;class U,class V&gt;
pair(const pair&lt;U,V&gt;&amp; p):first(p.first),second(p.second){}
}</pre>
<p>即map中，key是键值对的第一个元素且mapped value是第二个元素。pair的定义可以在&lt;utility&gt;中找到，pair提供了一个方法方便创建键值对：</p>
<pre class="brush: cpp;">template &lt;class T1,class T2&gt;pair&lt;T1,T2&gt;
std::make_pair(const T1&amp; t1,const T2&amp; t2)
{
return pair&lt;T1,T2&gt;(t1,t2);
}</pre>
<p>上面的例子中我们就用到了这个方法来创建(clientId,clientInfo)对，并作为Insert()方法的参数。每个pair默认初始化每个元素的值为对应类型的默认值。</p>
<h4><strong>4、map中的构造函数与析构函数</strong></h4>
<p>map类惯常提供了构造函数和析构函数，如下所示：</p>
<pre class="brush: cpp;">template&lt;class Key,class T,class Cmp=less&lt;key&gt;,
class A=allocator&lt;pair&lt;const Key,T&gt;&gt;
class std::map
{
//...
//construct/copy/destroy
explicit map(const Cmp&amp;=Cmp(),const A&amp;=A());
template&lt;class In&gt;map(In first,In last,
const Com&amp;=Cmp(),const A&amp;=A());
map(const map&amp;);
~map();
map&amp; operator=(const map&amp;);
//...
}</pre>
<p>复制一个容器意味着为它的每个元素分配空间，并拷贝每个元素值。这样做是性能开销是很大的，应该仅当需要的时候才这样做。<strong>因此，map传的是引用</strong>。</p>
<h4><strong>5、map中的操作方法</strong></h4>
<p>前面我们已经说过，如果map中仅定义了一些key、mapped value类型的信息而没有操作方法，就如定义个仅有字段的类意义不大甚至毫无意义。由此可见map中定义操作方法非常重要！前面的例子我们就用到了不少方法，如返回迭代器的方法begin()、end()，键值对插入方法insert()。下面我们对map中的操作方法做个全面的介绍：</p>
<pre class="brush: cpp;">template&lt;class Key,class T,class Cmp=less&lt;key&gt;,
class A=allocator&lt;pair&lt;const Key,T&gt;&gt;
class std::map
{
//...
//map operations
//find element with key k
iterator find(const key_type&amp; k);
const_iterator find(const key_type&amp; k) const;
//find number of elements with key k
size_type count() const;
//find first element with key k
iterator lower_bound(const key_type&amp; k);
const_iterator lower_bound(const key_type&amp; k) const;
//find first element with key greater than k
iterator upper_bound(const key_type&amp; k);
const_iterator upper_bound(const key_type&amp; k) const;
//insert pair(key,value)
pair&lt;iterator,bool&gt;insert(const value_type&amp; val);
iterator insert(iterator pos,const value_type&amp; val);
template&lt;class In&gt;void insert(In first,In last);
//erase element
void erase(iterator pos);
size_type erase(const key_type&amp; k);
void erase(iterator first,iterator last);
void clear();
//number os elements
size_type size() const;
//size of largest possible map
size_type max_size() const;
bool empty() const{return size()==0;}
void swap(map&amp;);
//...
}</pre>
<p>上面这些方法基本都能顾名思义（PS.由此可见，命名有多重要，我们平时要养成好的命名习惯，当然注释也必不可少！）。虽然已经非常清楚了了，但我还是想讲解一下以消除不惜要的误解和更好地应用这些方法。</p>
<ul>
    <li>find(k)方法简单地返回键值为k的元素的迭代器；如果没有元素的键值为k，则返回map的end()迭代器。由于map是按键key升序排列，所有查找的复杂度只有O(logN)。因此，我们通常会这样用这个方法： <br>
    <pre class="brush: cpp;">#include&lt;iostream&gt;
    #include&lt;map&gt;
    #include&lt;string&gt;
    using namespace std;
    typedef    int    clientId;
    typedef struct{
    int scanRate;
    string socketAddr;
    }clientInfo;
    int main(int argc,char** argv)
    {
    typedef map&lt;clientId,clientInfo&gt; clientEdp;
    typedef map&lt;clientId,clientInfo&gt;::const_iterator iterator;
    clientEdp clients;
    clientInfo client[100];
    char* str=new char[10];
    string strAddr("socket addr client ");
    for(int i=0;i&lt;100;i++)
    {
    client[i].scanRate=i+1;
    //convert int to char*
    itoa(i+1,str,10);
    //concatenate strAddr and str
    client[i].socketAddr=strAddr+str;
    clients.insert(
    make_pair(i+1,client[i]));
    }
    delete str;
    <span style="color: #ff0000;">    </span><strong><span style="color: #ff0000;">clientId id=10;
    iterator i=clients.find(id);
    if(i!=clients.end()){
    cout&lt;&lt;"clientId: "&lt;&lt;id
    &lt;&lt;" exists in clients"&lt;&lt;endl;
    }
    else{
    cout&lt;&lt;"clientId: "&lt;&lt;id
    &lt;&lt;" doesn't exist in clients"&lt;&lt;endl;
    }</span></strong>
    }</pre>
    </li>
    <li>insert()方法 试图将一个（Key,T）键值对加入map。因为键时唯一的，所以仅当map中不存在键值为k的键值对时插入才成功。该方法的返回值为pair&lt;iterator,bool&gt;，如果插入成功bool值为TRUE，iterator指向插入map中后的键值对。如下代码： <br>
    <pre class="brush: cpp;">#include&lt;iostream&gt;
    #include&lt;map&gt;
    #include&lt;string&gt;
    using namespace std;
    typedef    int    clientId;
    typedef struct{
    int scanRate;
    string socketAddr;
    }clientInfo;
    int main(int argc,char** argv)
    {
    typedef map&lt;clientId,clientInfo&gt; clientEdp;
    typedef map&lt;clientId,clientInfo&gt;::const_iterator iterator;
    clientEdp clients;
    clientId id=110;
    clientInfo cltInfo;
    cltInfo.scanRate=10;
    cltInfo.socketAddr="110";
    pair&lt;clientId,clientInfo&gt; p110(id,cltInfo);
    pair&lt;iterator,bool&gt; p=clients.insert(p110);
    if(p.second){
    cout&lt;&lt;"insert success!"&lt;&lt;endl;
    }
    else{
    cout&lt;&lt;"insert failed!"&lt;&lt;endl;
    }
    //i points to clients[110];
    iterator i=p.first;
    cout&lt;&lt;i-&gt;first&lt;&lt;endl;
    cout&lt;&lt;i-&gt;second.scanRate&lt;&lt;endl;
    cout&lt;&lt;i-&gt;second.socketAddr&lt;&lt;endl;
    }</pre>
    </li>
</ul>
<p>上面我们看出，这里我们插入键值对是首先声明一个键值对pair&lt;clientId,clientInfo&gt; p110(id,cltInfo); 然后再插入，这个我们之前make_pair方法不一样，make_pair方法用的比较多。</p>
<ul>
    <li>erase()方法用法比较简单，比如像清除clientId为110的键值对，我们只需要对clients调用erase方法：<span style="color: #ff8040;">clients.erase(clients.find(110));</span>或者我们想清除clientId从1到10的键值对，我们可以这样调用erase()方法：<span style="color: #ff8040;">clients.erase(clients.finds(1),clients.find(10));</span>简单吧！别得意，你还需要注意，如果find(k)返回的是end()，这样调用erase()方法则是一个严重的错误，会对map造成破坏操作。 </li>
</ul>
<h4><strong>6、再议map的插入操作</strong></h4>
<p>前面我们介绍了利用map的插入方法insert()，声明键值对pair或make_pair生成键值对然后我们可以轻松的将键值对插入map中。其实map还提供了更方便的插入操作利用下标（subscripting，[]）操作，如下：</p>
<pre class="brush: cpp;">clientInfo cltInfo;
cltInfo.scanRate=10;
cltInfo.socketAddr="110";
<strong>clients[110]=cltInfo;</strong></pre>
<p>这样我们就可以简单地将键值对插入到map中了。下标操作在map中式这样定义的：</p>
<pre class="brush: cpp;">template&lt;class Key,class T,class Cmp=less&lt;key&gt;,
class A=allocator&lt;pair&lt;const Key,T&gt;&gt;
class std::map
{
//...
//access element with key k
mapped_type&amp; operator[](const key_type&amp; k);
//...
}</pre>
<p>我们来分析一下应用[]操作，插入键值对的过程：<strong>检查键k是否已经在map里。如果不，就添加上，以v作为它的对应值。如果k已经在map里，它的关联值被更新成v。这里首先，查找110不在map中则创建一个键为110的键值对，并将映射值设为默认值，这里scanRate为0，socketAddr为空；然后将映射值赋为cltInfo。 </strong>如果110在map中已经存在的话，则只是更新以110为键的映射值。</p>
<p>从上面的分析可知：如果大量这样插入数据，会严重影响效率！如果你考虑效率问题，请使用insert操作。insert方法，节省了三次函数调用：一个建立临时的默认映射值的对象，一个销毁那个临时的对象和一个对映射值的赋值操作。</p>
<p><strong>Note1：</strong>如果k已经存在map中，[]效率反而比insert的效率高，而且更美观！如果能够兼顾这两者那岂不是很美妙！其实我们重写map中的[]操作：首先判断k是否已经在map中，如果没有则调用insert操作，否则调用内置的[]操作。如下列代码：</p>
<pre class="brush: cpp;">//////////////////////////////////////////////
///@param MapType-map的类型参数
///@param KeyArgType-键的类型参数
///@param ValueArgtype-映射值的类型参数
///@return 迭代器，指向键为k的键值对
//////////////////////////////////////////////
template&lt;typename MapType,
typename KeyArgType,
typename ValueArgtype&gt;
typename MapType::iterator
efficientAddOrUpdate(MapType&amp; m,
const KeyArgType&amp; k,
const ValueArgtype&amp; v)
{
typename MapType::iterator Ib =    m.lower_bound(k);
if(Ib != m.end()&amp;&amp;!(m.key_comp()(k,Ib-&gt;first))) {
//key已经存在于map中做更新操作
Ib-&gt;second = v;
return Ib;
}
else{
//key不存在map中做插入操作
typedef typename MapType::value_type MVT;
return m.insert(Ib, MVT(k, v));
}
}</pre>
<p><strong>Note2：</strong>我们视乎还忽略了一点，如果映射值mapped value的类型没有默认值，怎么办？这种情况请勿使用[]操作插入。</p>
<h4><strong>7、[]不仅插入</strong></h4>
<p>通过[]操作不仅仅是插入键值对，我们也可以通过键key检索出映射值mapped value。而且我们利用[]操作可以轻松地统计信息，如有这样这样一些键值对（book-name，count）对：</p>
<p>(book1,1)、(book2,2)、(book1,2)、(book3,1)、(book3,5)</p>
<p>我们计算每种book的数量总和。我们可以这样做：将它们读入一个map&lt;string,int&gt;：</p>
<pre class="brush: cpp;">#include&lt;iostream&gt;
#include&lt;map&gt;
#include&lt;string&gt;
using namespace std;
int main(int argc,char** argv)
{
map&lt;string,int&gt; bookMap;
string book;
int count;
int total=0;
while(cin&gt;&gt;book&gt;&gt;count)
bookMap[book]+=count;
map&lt;string,int&gt;::iterator i;
for(i=bookMap.begin();i!=bookMap.end();i++)
{
total+=i-&gt;second;
cout&lt;&lt;i-&gt;first&lt;&lt;'\t'&lt;&lt;i-&gt;second&lt;&lt;endl;
}
cout&lt;&lt;"total count:"&lt;&lt;total&lt;&lt;endl;
}</pre>
<p>结果如下所示：（注意按住ctrl+z键结束输入）</p>
<p><a href="http://www.cppblog.com/images/cppblog_com/09212744/WindowsLiveWriter/CInternalsSTLMap_132F1/image_4.png"><img style="border-width: 0px; display: block; float: none; margin-left: auto; margin-right: auto;" title="image" alt="image" src="http://www.cppblog.com/images/cppblog_com/09212744/WindowsLiveWriter/CInternalsSTLMap_132F1/image_thumb_1.png" width="254" border="0" height="218"></a> </p>
<p align="center">图2、程序运行结果</p>
<h4><strong>8、multimap</strong></h4>
<p>前面介绍了map，可以说已经非常清晰了。如果允许clientId重复的话，map就无能为力了，这时候就得multimap上场了！<strong>multimap允许键key重复，即一个键对应多个映射值。</strong>其实除此之外，multimap跟map是很像的，我们接下来在map的基础上介绍multimap。</p>
<p>multimap在std中的定义跟map一样只是类名为multimap，multimap几乎有map的所有方法和类型定义。</p>
<ul>
    <li>multimap不支持[]操作；但map支持
    </li>
    <li>multimap的insert方法返回的是一个迭代器iterator，没有bool值；而map值（iterator，bool）的元素对
    </li>
    <li>对应equal_range()、方法：
    <div>
    <pre>pair&lt;iterator,iterator&gt; equal_range(<span style="color: #0000ff;">const</span> key_type&amp; k);
    pair&lt;const_iterator,const_iterator&gt;
    equal_range(<span style="color: #0000ff;">const</span> key_type&amp; k) <span style="color: #0000ff;">const</span>;
    <span style="color: #008000;">//find first element with key k</span>
    iterator lower_bound(<span style="color: #0000ff;">const</span> key_type&amp; k);
    const_iterator lower_bound(<span style="color: #0000ff;">const</span> key_type&amp; k) <span style="color: #0000ff;">const</span>;
    <span style="color: #008000;">//find first element with key greater than k</span>
    iterator upper_bound(<span style="color: #0000ff;">const</span> key_type&amp; k);
    const_iterator upper_bound(<span style="color: #0000ff;">const</span> key_type&amp; k) <span style="color: #0000ff;">const</span>;</pre>
    </div>
    虽然在map和multimap都有，显然对multimap有更多的意义！equal_range()方法返回一个键key对应的多个映射值的上界和下界的键值对的迭代器、lower_bound()方法返回键multimap中第一个箭为key的键值对迭代器、upper_bound()方法返回比key大的第一个键值对迭代器。 </li>
</ul>
<p>假设我们想取出键为key的所有映射值，我们可以这样做：</p>
<pre class="brush: cpp;">#include&lt;iostream&gt;
#include&lt;map&gt;
#include&lt;string&gt;
using namespace std;
typedef int clientId;
typedef struct{
int scanRate;
string socketAddr;
}clientInfo;
int main(int argc,char** argv)
{
typedef multimap&lt;clientId,clientInfo&gt; clientEdp;
typedef multimap&lt;clientId,clientInfo&gt;::const_iterator iterator;
clientEdp clients;
clientInfo client[20];
char* str=new char[10];
string strAddr("socket addr client ");
for(int i=0;i&lt;10;i++)
{
client[i].scanRate=i+1;
//convert int to char*
itoa(i+1,str,10);
//concatenate strAddr and str
client[i].socketAddr=strAddr+str;
clients.insert(
make_pair(10,client[i]));
}
for(int i=10;i&lt;20;i++)
{
client[i].scanRate=i+1;
//convert int to char*
itoa(i+1,str,10);
//concatenate strAddr and str
client[i].socketAddr=strAddr+str;
clients.insert(
make_pair(i+1,client[i]));
}
delete str,strAddr;
<strong>    //find elements with key 10
iterator lb=clients.lower_bound(10);
iterator ub=clients.upper_bound(10);</strong>
for(iterator i=lb;i!=ub;i++)
{
cout&lt;&lt;"clientId:"&lt;&lt;i-&gt;first&lt;&lt;endl;
cout&lt;&lt;"scanRate:"&lt;&lt;i-&gt;second.scanRate&lt;&lt;endl;
cout&lt;&lt;"socketAddr:"&lt;&lt;i-&gt;second.socketAddr&lt;&lt;endl;
cout&lt;&lt;endl;
}
}</pre>
<p>（说明：实际上，一般是不允许clientId重复的，这里只是为了举例。）这样是不是感觉很丑呢！事实上，我们可以更简单的这样：</p>
<pre class="brush: cpp;">//find elements with key 10
<strong>pair&lt;iterator,iterator&gt; p=clients.equal_range(10);</strong>
for(iterator i=p.first;i!=p.second;i++)
{
cout&lt;&lt;"clientId:"&lt;&lt;i-&gt;first&lt;&lt;endl;
cout&lt;&lt;"scanRate:"&lt;&lt;i-&gt;second.scanRate&lt;&lt;endl;
cout&lt;&lt;"socketAddr:"&lt;&lt;i-&gt;second.socketAddr&lt;&lt;endl;
cout&lt;&lt;endl;
}</pre>
<h4><strong>总结</strong></h4>
<p>map是一类关联式容器。它的特点是增加和删除节点对迭代器的影响很小，除了那个操作节点，对其他的节点都没有什么影响。对于迭代器来说，可以修改实值，而不能修改key。 </p>
<p>map的功能：</p>
<ul>
    <li>自动建立Key －value的对应。key 和value可以是任意你需要的类型。
    </li>
    <li>根据key值快速查找记录，查找的复杂度基本是Log(N)。
    </li>
    <li>快速插入Key - Value 记录。
    </li>
    <li>快速删除记录
    </li>
    <li>根据Key 修改value记录。
    </li>
    <li>遍历所有记录。 </li>
</ul>
<p>展望：本文不知不觉写了不少字了，但仍未深入涉及到map定义的第3个和第4个参数，使用的都是默认值。</p>
<p>template&lt;class Key,class T,class Cmp=less&lt;key&gt;, <br>&nbsp;&nbsp;&nbsp; class A=allocator&lt;pair&lt;const Key,T&gt;&gt;</p>
<p>感兴趣者，请查找相关资料or下面留言希望看到单独开篇介绍map第3个和第4个参数。您的支持，我的动力！PS：在此文的原因，在与公司做项目用到了map特此总结出来与大家共享，不过在进行个人总结过程中，难免会有疏漏或不当之处，请不吝指出。</p>
<h4><strong>参考文献：</strong></h4>
<p>【1】《The C++ Programming Language (Special Edition)》</p>
<p>【2】《Effective STL》</p><img src ="http://www.cppblog.com/09212744/aggbug/118235.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/09212744/" target="_blank">吴秦（Saylor）</a> 2010-06-19 12:25 <a href="http://www.cppblog.com/09212744/archive/2010/06/19/118235.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>