Files
scuc-qt-course/tools/wiki_data/bodies/58954488.json
T
张宗平 13b3ebbe14 课程代码仓库初始化:Part1 (144) + Part3 (29) 全量生成与验证
把川大 wiki「02.Qt方向」课程代码整理为 CMake 管理的可编译运行项目。

Part1 C/C++ 强化 (p01/, 144 目标, host gcc/g++ 构建, 全绿):
- 修复全部编译失败:补 <cstring>/<fstream>/<iomanip>/<vector> 等缺失头
  (common_fix + infer_stl_includes 自动检测注入)、修正 pause() 壳注入
  (改检测已转换后的 pause 调用)、namespace 提升出函数体、const 成员函数
  正确性、dedupe 不再破坏函数重载集、K&R 隐式 int / 动态异常规范等
  C++17 移除特性的可编译等价改写。
- 目录采用深缩写 p01/ch04/s03/ (原 part1_cpp/ch04_class_object/03_ctor_dtor)。

Part3 Qt 桌面 (p03/, 29 目标, 隔离 Qt 5.14.2 构建, 全绿):
- gen_part3.py: Qt 外壳模板 (main+QApplication+show, QTimer 自动退出便于
  offscreen 验证)、AUTOMOC + .moc include、rewrite_* 处理散文混入/重载/
  资源依赖、生成占位 png/gif + .qrc + rundata 样例数据。
- 修正 QLable 笔误、全角分号、.→->、Windows 路径、QApplication 阻塞事件
  循环改为 QCoreApplication 等。

任务1 概念/版本验证:
- tools/std_probe.py: 跨 -std= 编译探针 (--pedantic-errors 让已废除特性硬失败)。
- tools/demo_versions/: K&R 隐式 int、三目左值、void* 转换、enum 赋整、
  const 经指针、动态异常规范 共 6 个跨版本演示。
- docs/VERSION_NOTES.md: 每条「原文说法→C17/C++17 是否成立→何版本成立→已验证」。

文档: 顶层 README、docs/SOURCE_PAGES.md (页面→目录映射)、
docs/ERRATA.md (Part1 修正)、docs/ERRATA_part3.md (Part3 修正)。
另修复 tools/run.sh / run_qt.sh 的 ${1:?...} 引号语法 bug。

隔离 Qt 验证: ldd 0 系统 Qt 引用; 173/173 目标全绿; p03 offscreen 运行通过。
2026-06-30 14:16:55 +08:00

1 line
56 KiB
JSON
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"id":"58954488","type":"page","status":"current","title":"03.常用容器","version":{"by":{"type":"known","username":"ethanliu","userKey":"2c90931b6cfb654f016d5c69becc0001","profilePicture":{"path":"/images/icons/profilepics/default.svg","width":48,"height":48,"isDefault":true},"displayName":"刘浩","_links":{"self":"https://wiki.suncaper.net/rest/api/user?key=2c90931b6cfb654f016d5c69becc0001"},"_expandable":{"status":""}},"when":"2022-07-09T01:51:24.000Z","message":"","number":1,"minorEdit":false,"hidden":false,"_links":{"self":"https://wiki.suncaper.net/rest/experimental/content/58954488/version/1"},"_expandable":{"content":"/rest/api/content/58954488"}},"body":{"storage":{"value":"<ac:layout><ac:layout-section ac:type=\"two_right_sidebar\"><ac:layout-cell><h3><span>3.1.1 string</span>容器基本概念</h3><p>C风格字符串<span>(</span>以空字符结尾的字符数组<span>)</span>太过复杂难于掌握,不适合大程序的开发,所以<span>C++</span>标准库定义了一种<span>string</span>类,定义在头文件<span>&lt;string&gt;</span>。</p><p>String和<span>c</span>风格字符串对比:</p><ul><li>Char*是一个指针,<span>String</span>是一个类</li></ul><p>string封装了<span>char*</span>,管理这个字符串,是一个<span>char*</span>型的容器。</p><ul><li>String封装了很多实用的成员方法</li></ul><p>查找<span>find</span>,拷贝<span>copy</span>,删除<span>delete </span>替换<span>replace</span>,插入<span>insert</span></p><ul><li>不用考虑内存释放和越界</li></ul><p> string管理<span>char*</span>所分配的内存。每一次string的复制,取值都由string类负责维护,不用担心复制越界和取值越界等。</p><h3><span>3.1.2 string</span>容器常用操作</h3><h4><span>3.1.2.1 string </span>构造函数</h4><table><tbody><tr><td><p>string();//创建一个空的字符串 例如<span>: string str;      </span></p><p>string(const string&amp; str);//使用一个<span>string</span>对象初始化另一个<span>string</span>对象</p><p>string(const char* s);//使用字符串<span>s</span>初始化</p><p>string(int n, char c);//使用<span>n</span>个字符<span>c</span>初始化</p></td></tr></tbody></table><p><span> </span></p><h4><span>3.1.2.2 string</span>基本赋值操作</h4><table><tbody><tr><td><p>string&amp; operator=(const char* s);//char*类型字符串 赋值给当前的字符串</p><p>string&amp; operator=(const string &amp;s);//把字符串<span>s</span>赋给当前的字符串</p><p>string&amp; operator=(char c);//字符赋值给当前的字符串</p><p>string&amp; assign(const char *s);//把字符串<span>s</span>赋给当前的字符串</p><p>string&amp; assign(const char *s, int n);//把字符串<span>s</span>的前<span>n</span>个字符赋给当前的字符串</p><p>string&amp; assign(const string &amp;s);//把字符串<span>s</span>赋给当前字符串</p><p>string&amp; assign(int n, char c);//用<span>n</span>个字符<span>c</span>赋给当前字符串</p><p>string&amp; assign(const string &amp;s, int start, int n);//将<span>s</span>从<span>start</span>开始<span>n</span>个字符赋值给字符串</p></td></tr></tbody></table><p><span> </span></p><h4><span>3.1.2.3 string</span>存取字符操作</h4><table><tbody><tr><td><p>char&amp; operator[](int n);//通过<span>[]</span>方式取字符</p><p>char&amp; at(int n);//通过<span>at</span>方法获取字符</p></td></tr></tbody></table><p><span> </span></p><h4><span>3.1.2.4 string</span>拼接操作</h4><table><tbody><tr><td><p>string&amp; operator+=(const string&amp; str);//重载<span>+=</span>操作符</p><p>string&amp; operator+=(const char* str);//重载<span>+=</span>操作符</p><p>string&amp; operator+=(const char c);//重载<span>+=</span>操作符</p><p>string&amp; append(const char *s);//把字符串<span>s</span>连接到当前字符串结尾</p><p>string&amp; append(const char *s, int n);//把字符串<span>s</span>的前<span>n</span>个字符连接到当前字符串结尾</p><p>string&amp; append(const string &amp;s);//同<span>operator+=()</span></p><p>string&amp; append(const string &amp;s, int pos, int n);//把字符串<span>s</span>中从<span>pos</span>开始的<span>n</span>个字符连接到当前字符串结尾</p><p>string&amp; append(int n, char c);//在当前字符串结尾添加<span>n</span>个字符<span>c</span></p></td></tr></tbody></table><p><span> </span></p><h4><span>3.1.2.5 string</span>查找和替换</h4><table><tbody><tr><td><p>int find(const string&amp; str, int pos = 0) const; //查找<span>str</span>第一次出现位置<span>,</span>从<span>pos</span>开始查找</p><p>int find(const char* s, int pos = 0) const;  //查找<span>s</span>第一次出现位置<span>,</span>从<span>pos</span>开始查找</p><p>int find(const char* s, int pos, int n) const;  //从<span>pos</span>位置查找<span>s</span>的前<span>n</span>个字符第一次位置</p><p>int find(const char c, int pos = 0) const;  //查找字符<span>c</span>第一次出现位置</p><p>int rfind(const string&amp; str, int pos = npos) const;//查找<span>str</span>最后一次位置<span>,</span>从<span>pos</span>开始查找</p><p>int rfind(const char* s, int pos = npos) const;//查找<span>s</span>最后一次出现位置<span>,</span>从<span>pos</span>开始查找</p><p>int rfind(const char* s, int pos, int n) const;//从<span>pos</span>查找<span>s</span>的前<span>n</span>个字符最后一次位置</p><p>int rfind(const char c, int pos = 0) const; //查找字符<span>c</span>最后一次出现位置</p><p>string&amp; replace(int pos, int n, const string&amp; str); //替换从<span>pos</span>开始<span>n</span>个字符为字符串<span>str</span></p><p>string&amp; replace(int pos, int n, const char* s); //替换从<span>pos</span>开始的<span>n</span>个字符为字符串<span>s</span></p></td></tr></tbody></table><p><span> </span></p><h4><span>3.1.2.6 string</span>比较操作</h4><table><tbody><tr><td><p>/*</p><p>compare函数在<span>&gt;</span>时返回<span> 1</span><span>&lt;</span>时返回<span> -1</span><span>==</span>时返回<span> 0</span>。</p><p>比较区分大小写,比较时参考字典顺序,排越前面的越小。</p><p>大写的<span>A</span>比小写的<span>a</span>小。</p><p>*/</p><p>int compare(const string &amp;s) const;//与字符串<span>s</span>比较</p><p>int compare(const char *s) const;//与字符串<span>s</span>比较</p></td></tr></tbody></table><h4><span>3.1.2.7 string</span>子串</h4><table><tbody><tr><td><p>string substr(int pos = 0, int n = npos) const;//返回由<span>pos</span>开始的<span>n</span>个字符组成的字符串</p></td></tr></tbody></table><p><span> </span></p><h4><span>3.1.2.8 string</span>插入和删除操作</h4><table><tbody><tr><td><p>string&amp; insert(int pos, const char* s); //插入字符串</p><p>string&amp; insert(int pos, const string&amp; str); //插入字符串</p><p>string&amp; insert(int pos, int n, char c);//在指定位置插入<span>n</span>个字符<span>c</span></p><p>string&amp; erase(int pos, int n = npos);//删除从<span>Pos</span>开始的<span>n</span>个字符</p></td></tr></tbody></table><p><span> </span></p><p><span> </span></p><h4><span>3.1.2.9 string</span>和<span>c-style</span>字符串转换</h4><table><tbody><tr><td><p>//string 转<span> char*</span></p><p>string str = &quot;itcast&quot;;</p><p>const char* cstr = str.c_str();</p><p>//char* 转<span> string </span></p><p>char* s = &quot;itcast&quot;;</p><p>string str(s);</p></td></tr></tbody></table><p><span> </span></p><p><strong>提示<span>:</span></strong></p><table><colgroup><col /></colgroup><tbody><tr><td><p>  在<span>c++</span>中存在一个从<span>const char*</span>到<span>string</span>的隐式类型转换,却不存在从一个<span>string</span>对象到<span>C_string</span>的自动类型转换。对于<span>string</span>类型的字符串,可以通过<span>c_str()</span>函数返回<span>string</span>对象对应的<span>C_string.</span></p><p>  通常,程序员在整个程序中应坚持使用<span>string</span>类对象,直到必须将内容转化为<span>char*</span>时才将其转换为<span>C_string.</span></p></td></tr></tbody></table><p><span> </span></p><p><strong>提示<span>:</span></strong></p><table><colgroup><col /></colgroup><tbody><tr><td><p>   为了修改<span>string</span>字符串的内容,下标操作符<span>[]</span>和<span>at</span>都会返回字符的引用。但当字符串的内存被重新分配之后,可能发生错误<span>.</span></p></td></tr></tbody></table><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"6d50af1b-b385-44ee-928b-e9044093c75e\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[\tstring s = \"abcdefg\";\n\tchar& a = s[2];\n\tchar& b = s[3];\n\n\ta = '1';\n\tb = '2';\n\n\tcout << s << endl;\n\tcout << (int*)s.c_str() << endl;\n\n\ts = \"pppppppppppppppppppppppp\";\n\n\t//a = '1';\n\t//b = '2';\n\n\tcout << s << endl;\n\tcout << (int*)s.c_str() << endl;]]></ac:plain-text-body></ac:structured-macro><h3><span>3.1.3 </span>小练习</h3><p>写一个函数,函数内部将<span>string</span>字符串中的所有小写字母都变为大写字母。</p><h2><span>3.2 vector</span>容器</h2><h3><span>3.2.1 vector</span>容器基本概念</h3><p>vector的数据安排以及操作方式,与<span>array</span>非常相似,两者的唯一差别在于空间的运用的灵活性。<span>Array</span>是静态空间,一旦配置了就不能改变,要换大一点或者小一点的空间,可以,一切琐碎得由自己来,首先配置一块新的空间,然后将旧空间的数据搬往新空间,再释放原来的空间。<span>Vector</span>是动态空间,随着元素的加入,它的内部机制会自动扩充空间以容纳新元素。因此<span>vector</span>的运用对于内存的合理利用与运用的灵活性有很大的帮助,我们再也不必害怕空间不足而一开始就要求一个大块头的<span>array</span>了。</p><p>Vector的实现技术,关键在于其对大小的控制以及重新配置时的数据移动效率,一旦<span>vector</span>旧空间满了,如果客户每新增一个元素,<span>vector</span>内部只是扩充一个元素的空间,实为不智,因为所谓的扩充空间<span>(</span>不论多大<span>)</span>,一如刚所说,是”配置新空间<span>-</span>数据移动<span>-</span>释放旧空间”的大工程<span>,</span>时间成本很高,应该加入某种未雨绸缪的考虑,稍后我们便可以看到<span>vector</span>的空间配置策略。</p><p><ac:image ac:height=\"250\"><ri:attachment ri:filename=\"image2021-5-24_9-2-20.png\" /></ac:image></p><h3><span>3.2.2 vector</span>迭代器</h3><p>Vector维护一个线性空间,所以不论元素的型别如何,普通指针都可以作为<span>vector</span>的迭代器,因为<span>vector</span>迭代器所需要的操作行为,如<span>operaroe*, operator-&gt;, operator++, operator--, operator+, operator-, operator+=, operator-=, </span>普通指针天生具备。<span>Vector</span>支持随机存取,而普通指针正有着这样的能力。所以<span>vector</span>提供的是随机访问迭代器<span>(Random Access Iterators).</span></p><p>根据上述描述,如果我们写如下的代码:</p><p>Vector&lt;int&gt;::iterator it1;</p><p>Vector&lt;Teacher&gt;::iterator it2;</p><p>it1的型别其实就是<span>Int*,it2</span>的型别其实就是<span>Teacher*.</span></p><p><br /></p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"06776723-2ca4-4695-9476-1fd45a606073\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[#define _CRT_SECURE_NO_WARNINGS\n#include<iostream>\n#include<vector>\nusing namespace std;\n\nint main(){\n\n\tvector<int> v;\n\tfor (int i = 0; i < 10;i ++){\n\t\tv.push_back(i);\n\t\tcout << v.capacity() << endl; // v.capacity()容器的容量\n\t}\n\n\n\tsystem(\"pause\");\n\treturn EXIT_SUCCESS;\n}]]></ac:plain-text-body></ac:structured-macro><h3><span>3.2.3 vector</span>的数据结构</h3><p>Vector所采用的数据结构非常简单,线性连续空间,它以两个迭代器_Myfirst和<span>_Mylast</span>分别指向配置得来的连续空间中目前已被使用的范围,并以迭代器<span>_Myend</span>指向整块连续内存空间的尾端。</p><p>为了降低空间配置时的速度成本,<span>vector</span>实际配置的大小可能比客户端需求大一些,以备将来可能的扩充,这边是<strong>容量</strong>的概念。换句话说,<strong>一个<span>vector</span>的容量永远大于或等于其大小,一旦容量等于大小,便是满载,下次再有新增元素,整个<span>vector</span>容器就得另觅居所。</strong></p><p><br /></p><p><strong>注意:</strong></p><table><tbody><tr><td><p>   所谓动态增加大小,并不是在原空间之后续接新空间<span>(</span>因为无法保证原空间之后尚有可配置的空间<span>)</span>,而是一块更大的内存空间,然后将原数据拷贝新空间,并释放原空间。因此,对<span>vector</span>的任何操作,一旦引起空间的重新配置,指向原<span>vector</span>的所有迭代器就都失效了。这是程序员容易犯的一个错误,务必小心。</p></td></tr></tbody></table><p><br /></p><h3><span>3.2.4 vector</span>常用<span>API</span>操作</h3><h4><span>3.2.4.1 vector</span>构造函数</h4><table><tbody><tr><td><p>vector&lt;T&gt; v; //采用模板实现类实现,默认构造函数</p><p>vector(v.begin(), v.end());//将<span>v[begin(), end())</span>区间中的元素拷贝给本身。</p><p>vector(n, elem);//构造函数将<span>n</span>个<span>elem</span>拷贝给本身。</p><p>vector(const vector &amp;vec);//拷贝构造函数。</p><p><br /></p><p>//例子 使用第二个构造函数 我们可以<span>...</span></p><p>int arr[] = {2,3,4,1,9};</p><p>vector&lt;int&gt; v1(arr, arr + sizeof(arr) / sizeof(int));</p></td></tr></tbody></table><p><span> </span></p><h4><span>3.2.4.2 vector</span>常用赋值操作</h4><table><tbody><tr><td><p>assign(beg, end);//将<span>[beg, end)</span>区间中的数据拷贝赋值给本身。</p><p>assign(n, elem);//将<span>n</span>个<span>elem</span>拷贝赋值给本身。</p><p>vector&amp; operator=(const vector  &amp;vec);//重载等号操作符</p><p>swap(vec);// 将<span>vec</span>与本身的元素互换。</p></td></tr></tbody></table><p><span> </span></p><h4><span>3.2.4.3 vector</span>大小操作</h4><table><colgroup><col /></colgroup><tbody><tr><td><p>size();//返回容器中元素的个数</p><p>empty();//判断容器是否为空</p><p>resize(int num);//重新指定容器的长度为<span>num</span>,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。</p><p>resize(int num, elem);//重新指定容器的长度为<span>num</span>,若容器变长,则以<span>elem</span>值填充新位置。如果容器变短,则末尾超出容器长<span>&gt;</span>度的元素被删除。</p><p>capacity();//容器的容量</p><p>reserve(int len);//容器预留<span>len</span>个元素长度,预留位置不初始化,元素不可访问。</p></td></tr></tbody></table><p><span> </span></p><h4><span>3.2.4.4 vector</span>数据存取操作</h4><table><tbody><tr><td><p>at(int idx); //返回索引<span>idx</span>所指的数据,如果<span>idx</span>越界,抛出<span>out_of_range</span>异常。</p><p>operator[];//返回索引<span>idx</span>所指的数据,越界时,运行直接报错</p><p>front();//返回容器中第一个数据元素</p><p>back();//返回容器中最后一个数据元素</p></td></tr></tbody></table><p><span> </span></p><h4><span>3.2.4.5 vector</span>插入和删除操作</h4><table><colgroup><col /></colgroup><tbody><tr><td><p>insert(const_iterator pos, int count,ele);//迭代器指向位置<span>pos</span>插入<span>count</span>个元素<span>ele.</span></p><p>push_back(ele); //尾部插入元素<span>ele</span></p><p>pop_back();//删除最后一个元素</p><p>erase(const_iterator start, const_iterator end);//删除迭代器从<span>start</span>到<span>end</span>之间的元素</p><p>erase(const_iterator pos);//删除迭代器指向的元素</p><p>clear();//删除容器中所有元素</p></td></tr></tbody></table><p><span> </span></p><p><span> </span></p><h3><span>3.2.5 vector</span>小案例</h3><h4>3.2.5.1巧用swap,收缩内存空间</h4><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"abf962c1-2919-4466-bfe3-ef0adcdabe20\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[#define _CRT_SECURE_NO_WARNINGS\n#include<iostream>\n#include<vector>\nusing namespace std;\n\nint main(){\n\n\tvector<int> v;\n\tfor (int i = 0; i < 100000;i ++){\n\t\tv.push_back(i);\n\t}\n\n\tcout << \"capacity:\" << v.capacity() << endl;\n\tcout << \"size:\" << v.size() << endl;\n\n\t//此时 通过resize改变容器大小\n\tv.resize(10);\n\n\tcout << \"capacity:\" << v.capacity() << endl;\n\tcout << \"size:\" << v.size() << endl;\n\n\t//容量没有改变\n\tvector<int>(v).swap(v);\n\n\tcout << \"capacity:\" << v.capacity() << endl;\n\tcout << \"size:\" << v.size() << endl;\n\n\n\tsystem(\"pause\");\n\treturn EXIT_SUCCESS;\n}]]></ac:plain-text-body></ac:structured-macro><h4><span>3.2.5.2 reserve</span>预留空间</h4><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"7910c806-51aa-4659-bb56-45aaa9b6bbfc\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[#define _CRT_SECURE_NO_WARNINGS\n#include<iostream>\n#include<vector>\nusing namespace std;\n\nint main(){\n\n\tvector<int> v;\n\n\t//预先开辟空间\n\tv.reserve(100000);\n\n\tint* pStart = NULL;\n\tint count = 0;\n\tfor (int i = 0; i < 100000;i ++){\n\t\tv.push_back(i);\n\t\tif (pStart != &v[0]){\n\t\t\tpStart = &v[0];\n\t\t\tcount++;\n\t\t}\n\t}\n\n\tcout << \"count:\" << count << endl;\n\n\tsystem(\"pause\");\n\treturn EXIT_SUCCESS;\n}]]></ac:plain-text-body></ac:structured-macro><h2><span>3.3 deque</span>容器</h2><h3><span>3.3.1 deque</span>容器基本概念</h3><p>Vector容器是单向开口的连续内存空间,<span>deque</span>则是一种双向开口的连续线性空间。所谓的双向开口,意思是可以在头尾两端分别做元素的插入和删除操作,当然,<span>vector</span>容器也可以在头尾两端插入元素,但是在其头部操作效率奇差,无法被接受。</p><p><ac:image ac:height=\"250\"><ri:attachment ri:filename=\"image2021-5-24_9-4-45.png\" /></ac:image></p><p>Deque容器和<span>vector</span>容器最大的差异,一在于<span>deque</span>允许使用常数项时间对头端进行元素的插入和删除操作。二在于<span>deque</span>没有容量的概念,因为它是动态的以分段连续空间组合而成,随时可以增加一段新的空间并链接起来,换句话说,像<span>vector</span>那样,”旧空间不足而重新配置一块更大空间,然后复制元素,再释放旧空间”这样的事情在<span>deque</span>身上是不会发生的。也因此,<span>deque</span>没有必须要提供所谓的空间保留<span>(reserve)</span>功能<span>.</span></p><p>虽然<span>deque</span>容器也提供了<span>Random Access Iterator,</span>但是它的迭代器并不是普通的指针,其复杂度和<span>vector</span>不是一个量级,这当然影响各个运算的层面。因此,除非有必要,我们应该尽可能的使用<span>vector</span>,而不是<span>deque</span>。对<span>deque</span>进行的排序操作,为了最高效率,可将<span>deque</span>先完整的复制到一个<span>vector</span>中,对<span>vector</span>容器进行排序,再复制回<span>deque.</span></p><h3><span>3.3.2 deque</span>容器实现原理</h3><p>Deque容器是连续的空间,至少逻辑上看来如此,连续现行空间总是令我们联想到<span>array</span>和<span>vector,array</span>无法成长,<span>vector</span>虽可成长,却只能向尾端成长,而且其成长其实是一个假象,事实上<span>(1) </span>申请更大空间<span> (2)</span>原数据复制新空间<span> (3)</span>释放原空间 三步骤,如果不是<span>vector</span>每次配置新的空间时都留有余裕,其成长假象所带来的代价是非常昂贵的。</p><p>Deque是由一段一段的定量的连续空间构成。一旦有必要在<span>deque</span>前端或者尾端增加新的空间,便配置一段连续定量的空间,串接在<span>deque</span>的头端或者尾端。<span>Deque</span>最大的工作就是维护这些分段连续的内存空间的整体性的假象,并提供随机存取的接口,避开了重新配置空间,复制,释放的轮回,代价就是复杂的迭代器架构。</p><p>既然<span>deque</span>是分段连续内存空间,那么就必须有中央控制,维持整体连续的假象,数据结构的设计及迭代器的前进后退操作颇为繁琐。<span>Deque</span>代码的实现远比<span>vector</span>或<span>list</span>都多得多。</p><p>Deque采取一块所谓的<span>map(</span>注意,不是<span>STL</span>的<span>map</span>容器<span>)</span>作为主控,这里所谓的<span>map</span>是一小块连续的内存空间,其中每一个元素<span>(</span>此处成为一个结点<span>)</span>都是一个指针,指向另一段连续性内存空间,称作缓冲区。缓冲区才是<span>deque</span>的存储空间的主体。</p><p><ac:image ac:height=\"250\"><ri:attachment ri:filename=\"image2021-5-24_9-5-23.png\" /></ac:image></p><h3><span>3.3.3 deque</span>常用<span>API</span></h3><h4><span>3.3.3.1 deque</span>构造函数</h4><table><tbody><tr><td><p>deque&lt;T&gt; deqT;//默认构造形式</p><p>deque(beg, end);//构造函数将<span>[beg, end)</span>区间中的元素拷贝给本身。</p><p>deque(n, elem);//构造函数将<span>n</span>个<span>elem</span>拷贝给本身。</p><p>deque(const deque &amp;deq);//拷贝构造函数。</p></td></tr></tbody></table><p><span> </span></p><h4><span>3.3.3.2 deque</span>赋值操作</h4><table><tbody><tr><td><p>assign(beg, end);//将<span>[beg, end)</span>区间中的数据拷贝赋值给本身。</p><p>assign(n, elem);//将<span>n</span>个<span>elem</span>拷贝赋值给本身。</p><p>deque&amp; operator=(const deque &amp;deq); //重载等号操作符</p><p>swap(deq);// 将<span>deq</span>与本身的元素互换</p></td></tr></tbody></table><p><span> </span></p><h4><span>3.3.3.3 deque</span>大小操作</h4><table><tbody><tr><td><p>deque.size();//返回容器中元素的个数</p><p>deque.empty();//判断容器是否为空</p><p>deque.resize(num);//重新指定容器的长度为<span>num,</span>若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。</p><p>deque.resize(num, elem); //重新指定容器的长度为<span>num,</span>若容器变长,则以<span>elem</span>值填充新位置<span>,</span>如果容器变短,则末尾超出容器长度的元素被删除。</p></td></tr></tbody></table><p><span> </span></p><h4><span>3.3.3.4 deque</span>双端插入和删除操作</h4><table><tbody><tr><td><p>push_back(elem);//在容器尾部添加一个数据</p><p>push_front(elem);//在容器头部插入一个数据</p><p>pop_back();//删除容器最后一个数据</p><p>pop_front();//删除容器第一个数据</p></td></tr></tbody></table><p><span> </span></p><h4><span>3.3.3.5 deque</span>数据存取</h4><table><tbody><tr><td><p>at(idx);//返回索引<span>idx</span>所指的数据,如果<span>idx</span>越界,抛出<span>out_of_range</span>。</p><p>operator[];//返回索引<span>idx</span>所指的数据,如果<span>idx</span>越界,不抛出异常,直接出错。</p><p>front();//返回第一个数据。</p><p>back();//返回最后一个数据</p></td></tr></tbody></table><h4><span>3.3.3.6 deque</span>插入操作</h4><table><tbody><tr><td><p>insert(pos,elem);//在<span>pos</span>位置插入一个<span>elem</span>元素的拷贝,返回新数据的位置。</p><p>insert(pos,n,elem);//在<span>pos</span>位置插入<span>n</span>个<span>elem</span>数据,无返回值。</p><p>insert(pos,beg,end);//在<span>pos</span>位置插入<span>[beg,end)</span>区间的数据,无返回值。</p></td></tr></tbody></table><h4><span>3.3.3.7 deque</span>删除操作</h4><table><tbody><tr><td><p>clear();//移除容器的所有数据</p><p>erase(beg,end);//删除<span>[beg,end)</span>区间的数据,返回下一个数据的位置。</p><p>erase(pos);//删除<span>pos</span>位置的数据,返回下一个数据的位置。</p></td></tr></tbody></table><p><span> </span></p><h4><span>3.3.3.8 </span>案例(作业)</h4><h4> </h4><table><tbody><tr><td><p>/*</p><p>    有5名选手:选手ABCDE,10个评委分别对每一名选手打分,去除最高分,去除评委中最低分,取平均分。</p><p>    //1. 创建五名选手,放到vector中</p><p>    //2. 遍历vector容器,取出来每一个选手,执行for循环,可以把10个评分打分存到deque容器中</p><p>    //3. sort算法对deque容器中分数排序,pop_back pop_front去除最高和最低分</p><p>    //4. deque容器遍历一遍,累加分数,累加分数/d.size()</p><p>    //5. person.score = 平均分</p><p>*/</p><p><br /></p></td></tr></tbody></table><p><span> </span></p><h2><span>3.4 stack</span>容器</h2><h3><span>3.4.1 stack</span>容器基本概念</h3><p>stack是一种先进后出<span>(First In Last Out,FILO)</span>的数据结构,它只有一个出口,形式如图所示。<span>stack</span>容器允许新增元素,移除元素,取得栈顶元素,但是除了最顶端外,没有任何其他方法可以存取<span>stack</span>的其他元素。换言之,<span>stack</span>不允许有遍历行为。</p><p>有元素推入栈的操作称为<span>:push,</span>将元素推出<span>stack</span>的操作称为<span>pop.</span></p><p><span><ac:image ac:height=\"400\"><ri:attachment ri:filename=\"image2021-5-24_9-6-25.png\" /></ac:image></span></p><h3><span>3.4.2 stack</span>没有迭代器</h3><p>Stack所有元素的进出都必须符合”先进后出”的条件,只有<span>stack</span>顶端的元素,才有机会被外界取用。<span>Stack</span>不提供遍历功能,也不提供迭代器。</p><h3><span>3.4.3 stack</span>常用<span>API</span></h3><h4><span>3.4.3.1 stack</span>构造函数</h4><table><tbody><tr><td><p>stack&lt;T&gt; stkT;//stack采用模板类实现,<span> stack</span>对象的默认构造形式:</p><p>stack(const stack &amp;stk);//拷贝构造函数</p></td></tr></tbody></table><h4><span>3.4.3.2 stack</span>赋值操作</h4><table><tbody><tr><td><p>stack&amp; operator=(const stack &amp;stk);//重载等号操作符</p></td></tr></tbody></table><h4><span>3.4.3.3 </span>stack数据存取操作</h4><table><tbody><tr><td><p>push(elem);//向栈顶添加元素</p><p>pop();//从栈顶移除第一个元素</p><p>top();//返回栈顶元素</p></td></tr></tbody></table><h4><span>3.4.3.4 </span>stack大小操作</h4><table><tbody><tr><td><p>empty();//判断堆栈是否为空</p><p>size();//返回堆栈的大小</p></td></tr></tbody></table><p><span> </span></p><h2><span>3.5 queue</span>容器</h2><h3><span>3.5.1 queue</span>容器基本概念</h3><p>Queue是一种先进先出<span>(First In First Out,FIFO)</span>的数据结构,它有两个出口,<span>queue</span>容器允许从一端新增元素,从另一端移除元素。</p><p><ac:image ac:height=\"250\"><ri:attachment ri:filename=\"image2021-5-24_9-10-4.png\" /></ac:image></p><h3><span>3.5.2 queue</span>没有迭代器</h3><p>Queue所有元素的进出都必须符合”先进先出”的条件,只有<span>queue</span>的顶端元素,才有机会被外界取用。<span>Queue</span>不提供遍历功能,也不提供迭代器。</p><p><br /></p><h3><span>3.5.3 queue</span>常用<span>API</span></h3><h4><span>3.5.3.1 queue</span>构造函数</h4><table><tbody><tr><td><p>queue&lt;T&gt; queT;//queue采用模板类实现,<span>queue</span>对象的默认构造形式:</p><p>queue(const queue &amp;que);//拷贝构造函数</p></td></tr></tbody></table><h4><span>3.5.3.2 queue</span>存取、插入和删除操作</h4><table><tbody><tr><td><p>push(elem);//往队尾添加元素</p><p>pop();//从队头移除第一个元素</p><p>back();//返回最后一个元素</p><p>front();//返回第一个元素</p></td></tr></tbody></table><p><span> </span></p><h4><span>3.5.3.3 queue</span>赋值操作</h4><table><tbody><tr><td><p>queue&amp; operator=(const queue &amp;que);//重载等号操作符</p></td></tr></tbody></table><h4><span>3.5.3.4 queue</span>大小操作</h4><table><tbody><tr><td><p>empty();//判断队列是否为空</p><p>size();//返回队列的大小</p></td></tr></tbody></table><p><span> </span></p><p><span> </span></p><h2><span>3.6 list</span>容器</h2><h3><span>3.6.1 list</span>容器基本概念</h3><p>链表是一种物理<span><a href=\"http://baike.baidu.com/view/1223079.htm\">存储单元</a></span>上非连续、非顺序的<span><a href=\"http://baike.baidu.com/view/2820182.htm\">存储结构</a></span><span><a href=\"http://baike.baidu.com/view/38785.htm\">数据元素</a></span>的逻辑顺序是通过链表中的<span><a href=\"http://baike.baidu.com/view/159417.htm\">指针</a></span>链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储<span><a href=\"http://baike.baidu.com/view/38785.htm\">数据元素</a></span>的数据域,另一个是存储下一个结点地址的<span><a href=\"http://baike.baidu.com/view/159417.htm\">指针</a></span>域。</p><p>相较于<span>vector</span>的连续线性空间,<span>list</span>就显得负责许多,它的好处是每次插入或者删除一个元素,就是配置或者释放一个元素的空间。因此,<span>list</span>对于空间的运用有绝对的精准,一点也不浪费。而且,对于任何位置的元素插入或元素的移除,<span>list</span>永远是常数时间。</p><p>List和<span>vector</span>是两个最常被使用的容器。</p><p>List容器是一个双向链表。</p><p><ac:image ac:height=\"400\"><ri:attachment ri:filename=\"image2021-5-24_9-10-44.png\" /></ac:image></p><ul><li>采用动态存储分配,不会造成内存浪费和溢出</li></ul><ul><li>链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素</li><li>链表灵活,但是空间和时间额外耗费较大</li></ul><h3><span>3.6.2 list</span>容器的迭代器</h3><p>List容器不能像<span>vector</span>一样以普通指针作为迭代器,因为其节点不能保证在同一块连续的内存空间上。<span>List</span>迭代器必须有能力指向<span>list</span>的节点,并有能力进行正确的递增、递减、取值、成员存取操作。所谓”<span>list</span>正确的递增,递减、取值、成员取用”是指,递增时指向下一个节点,递减时指向上一个节点,取值时取的是节点的数据值,成员取用时取的是节点的成员。</p><p>由于<span>list</span>是一个双向链表,迭代器必须能够具备前移、后移的能力,所以<span>list</span>容器提供的是<span>Bidirectional Iterators.</span></p><p>List有一个重要的性质,插入操作和删除操作都不会造成原有<span>list</span>迭代器的失效。这在<span>vector</span>是不成立的,因为<span>vector</span>的插入操作可能造成记忆体重新配置,导致原有的迭代器全部失效,甚至<span>List</span>元素的删除,也只有被删除的那个元素的迭代器失效,其他迭代器不受任何影响。</p><h3><span>3.6.3 list</span>容器的数据结构</h3><p><span>list</span>容器不仅是一个双向链表,而且还是一个循环的双向链表。</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"dd7d57ac-831a-4c8d-ad99-2c1bb818b434\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[#define _CRT_SECURE_NO_WARNINGS\n#include<iostream>\n#include<list>\nusing namespace std;\n\nint main(){\n\n\tlist<int> myList;\n\tfor (int i = 0; i < 10; i ++){\n\t\tmyList.push_back(i);\n\t}\n\n\tlist<int>::_Nodeptr node = myList._Myhead->_Next;\n\n\tfor (int i = 0; i < myList._Mysize * 2;i++){\n\t\tcout << \"Node:\" << node->_Myval << endl;\n\t\tnode = node->_Next;\n\t\tif (node == myList._Myhead){\n\t\t\tnode = node->_Next;\n\t\t}\n\t}\n\n\tsystem(\"pause\");\n\treturn EXIT_SUCCESS;\n}]]></ac:plain-text-body></ac:structured-macro><h3><span>3.6.4 list</span>常用<span>API</span></h3><h4><span>3.6.4.1 list</span>构造函数</h4><table><tbody><tr><td><p>list&lt;T&gt; lstT;//list采用采用模板类实现<span>,</span>对象的默认构造形式:</p><p>list(beg,end);//构造函数将<span>[beg, end)</span>区间中的元素拷贝给本身。</p><p>list(n,elem);//构造函数将<span>n</span>个<span>elem</span>拷贝给本身。</p><p>list(const list &amp;lst);//拷贝构造函数。</p></td></tr></tbody></table><h4><span>3.6.4.2 list</span>数据元素插入和删除操作</h4><table><tbody><tr><td><p>push_back(elem);//在容器尾部加入一个元素</p><p>pop_back();//删除容器中最后一个元素</p><p>push_front(elem);//在容器开头插入一个元素</p><p>pop_front();//从容器开头移除第一个元素</p><p>insert(pos,elem);//在<span>pos</span>位置插<span>elem</span>元素的拷贝,返回新数据的位置。</p><p>insert(pos,n,elem);//在<span>pos</span>位置插入<span>n</span>个<span>elem</span>数据,无返回值。</p><p>insert(pos,beg,end);//在<span>pos</span>位置插入<span>[beg,end)</span>区间的数据,无返回值。</p><p>clear();//移除容器的所有数据</p><p>erase(beg,end);//删除<span>[beg,end)</span>区间的数据,返回下一个数据的位置。</p><p>erase(pos);//删除<span>pos</span>位置的数据,返回下一个数据的位置。</p><p>remove(elem);//删除容器中所有与<span>elem</span>值匹配的元素。</p></td></tr></tbody></table><p><span> </span></p><p><span> </span></p><h4><span>3.6.4.3 list</span>大小操作</h4><table><tbody><tr><td><p>size();//返回容器中元素的个数</p><p>empty();//判断容器是否为空</p><p>resize(num);//重新指定容器的长度为<span>num</span></p><p>若容器变长,则以默认值填充新位置。</p><p>如果容器变短,则末尾超出容器长度的元素被删除。</p><p>resize(num, elem);//重新指定容器的长度为<span>num</span></p><p>若容器变长,则以<span>elem</span>值填充新位置。</p><p>如果容器变短,则末尾超出容器长度的元素被删除。</p></td></tr></tbody></table><p><span> </span></p><h4><span>3.6.4.4 list</span>赋值操作</h4><table><tbody><tr><td><p>assign(beg, end);//将<span>[beg, end)</span>区间中的数据拷贝赋值给本身。</p><p>assign(n, elem);//将<span>n</span>个<span>elem</span>拷贝赋值给本身。</p><p>list&amp; operator=(const list &amp;lst);//重载等号操作符</p><p>swap(lst);//将<span>lst</span>与本身的元素互换。</p></td></tr></tbody></table><h4><span>3.6.4.5 list</span>数据的存取</h4><table><tbody><tr><td><p>front();//返回第一个元素。</p><p>back();//返回最后一个元素。</p></td></tr></tbody></table><h4><span>3.6.4.6 list</span>反转排序</h4><table><tbody><tr><td><p>reverse();//反转链表,比如<span>lst</span>包含<span>1,3,5</span>元素,运行此方法后,<span>lst</span>就包含<span>5,3,1</span>元素。</p><p>sort(); //list排序</p></td></tr></tbody></table><p><span> </span></p><h2><span>3.7 set/multiset</span>容器</h2><h3><span>3.7.1 set/multiset</span>容器基本概念</h3><h4><span>3.7.1.1 set</span>容器基本概念</h4><p>Set的特性是。所有元素都会根据元素的键值自动被排序。<span>Set</span>的元素不像<span>map</span>那样可以同时拥有实值和键值,<span>set</span>的元素即是键值又是实值。<span>Set</span>不允许两个元素有相同的键值。</p><p>我们可以通过<span>set</span>的迭代器改变<span>set</span>元素的值吗?不行,因为<span>set</span>元素值就是其键值,关系到<span>set</span>元素的排序规则。如果任意改变<span>set</span>元素值,会严重破坏<span>set</span>组织。换句话说,<span>set</span>的<span>iterator</span>是一种<span>const_iterator.</span></p><p>set拥有和<span>list</span>某些相同的性质,当对容器中的元素进行插入操作或者删除操作的时候,操作之前所有的迭代器,在操作完成之后依然有效,被删除的那个元素的迭代器必然是一个例外。</p><h4><span>3.7.1.2 multiset</span>容器基本概念</h4><p>multiset特性及用法和<span>set</span>完全相同,唯一的差别在于它允许键值重复。<span>set</span>和<span>multiset</span>的底层实现是红黑树,红黑树为平衡二叉树的一种。</p><p>树的简单知识:</p><p>二叉树就是任何节点最多只允许有两个字节点。分别是左子结点和右子节点。</p><p><ac:image ac:height=\"400\"><ri:attachment ri:filename=\"image2021-5-24_9-12-1.png\" /></ac:image></p><p>二叉搜索树,是指二叉树中的节点按照一定的规则进行排序,使得对二叉树中元素访问更加高效。二叉搜索树的放置规则是:任何节点的元素值一定大于其左子树中的每一个节点的元素值,并且小于其右子树的值。因此从根节点一直向左走,一直到无路可走,即得到最小值,一直向右走,直至无路可走,可得到最大值。那么在儿茶搜索树中找到最大元素和最小元素是非常简单的事情。下图为二叉搜索树:</p><p><ac:image ac:height=\"400\"><ri:attachment ri:filename=\"image2021-5-24_9-12-32.png\" /></ac:image></p><p>上面我们介绍了二叉搜索树,那么当一个二叉搜索树的左子树和右子树不平衡的时候,那么搜索依据上图表示,搜索<span>9</span>所花费的时间要比搜索<span>17</span>所花费的时间要多,由于我们的输入或者经过我们插入或者删除操作,二叉树失去平衡,造成搜索效率降低。</p><p>所以我们有了一个平衡二叉树的概念,所谓的平衡不是指的完全平衡。</p><p><ac:image ac:height=\"400\"><ri:attachment ri:filename=\"image2021-5-24_9-12-59.png\" /></ac:image></p><p>RB-tree(红黑树<span>)</span>为二叉树的一种。</p><p><br /></p><h3><span>3.7.2 set</span>常用<span>API</span></h3><h4><span>3.7.2.1 set</span>构造函数</h4><table><tbody><tr><td><p>set&lt;T&gt; st;//set默认构造函数:</p><p>mulitset&lt;T&gt; mst; //multiset默认构造函数<span>: </span></p><p>set(const set &amp;st);//拷贝构造函数</p></td></tr></tbody></table><h4><span>3.7.2.2 set</span>赋值操作</h4><table><tbody><tr><td><p>set&amp; operator=(const set &amp;st);//重载等号操作符</p><p>swap(st);//交换两个集合容器</p></td></tr></tbody></table><h4><span>3.7.2.3 set</span>大小操作</h4><table><colgroup><col /></colgroup><tbody><tr><td><p>size();//返回容器中元素的数目</p><p>empty();//判断容器是否为空</p></td></tr></tbody></table><p><span> </span></p><h4><span>3.7.2.4 set</span>插入和删除操作</h4><table><colgroup><col /></colgroup><tbody><tr><td><p>insert(elem);//在容器中插入元素。</p><p>clear();//清除所有元素</p><p>erase(pos);//删除<span>pos</span>迭代器所指的元素,返回下一个元素的迭代器。</p><p>erase(beg, end);//删除区间<span>[beg,end)</span>的所有元素 ,返回下一个元素的迭代器。</p><p>erase(elem);//删除容器中值为<span>elem</span>的元素。</p></td></tr></tbody></table><p><span> </span></p><h4><span>3.7.2.5 </span>set查找操作</h4><table><colgroup><col /></colgroup><tbody><tr><td><p>find(key);//查找键<span>key</span>是否存在<span>,</span>若存在,返回该键的元素的迭代器;若不存在,返回<span>set.end();</span></p><p>count(key);//查找键<span>key</span>的元素个数</p><p>lower_bound(keyElem);//返回第一个<span>key&gt;=keyElem</span>元素的迭代器。</p><p>upper_bound(keyElem);//返回第一个<span>key&gt;keyElem</span>元素的迭代器。</p><p>equal_range(keyElem);//返回容器中<span>key</span>与<span>keyElem</span>相等的上下限的两个迭代器。</p></td></tr></tbody></table><p><strong>set</strong><strong>的返回值<span>    </span>指定<span>set</span>排序规则<span>:</span></strong></p><p><br /></p><p><strong><br /></strong></p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"76230b72-5372-46a2-bf13-ffdf9ca91c1e\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[//插入操作返回值\nvoid test01(){\n\n\tset<int> s;\n\tpair<set<int>::iterator,bool> ret = s.insert(10);\n\tif (ret.second){\n\t\tcout << \"插入成功:\" << *ret.first << endl;\n\t}\n\telse{\n\t\tcout << \"插入失败:\" << *ret.first << endl;\n\t}\n\t\n\tret = s.insert(10);\n\tif(ret.second){\n\t\tcout << \"插入成功:\" << *ret.first << endl;\n\t}\n\telse{\n\t\tcout << \"插入失败:\" << *ret.first << endl;\n\t}\n\n}\n\nstruct MyCompare02{\n\tbool operator()(int v1,int v2){\n\t\treturn v1 > v2;\n\t}\n};\n\n//set从大到小\nvoid test02(){\n\n\tsrand((unsigned int)time(NULL));\n\t//我们发现set容器的第二个模板参数可以设置排序规则,默认规则是less<_Kty>\n\tset<int, MyCompare02> s;\n\tfor (int i = 0; i < 10;i++){\n\t\ts.insert(rand() % 100);\n\t}\n\t\n\tfor (set<int, MyCompare02>::iterator it = s.begin(); it != s.end(); it ++){\n\t\tcout << *it << \" \";\n\t}\n\tcout << endl;\n}\n\n//set容器中存放对象\nclass Person{\npublic:\n\tPerson(string name,int age){\n\t\tthis->mName = name;\n\t\tthis->mAge = age;\n\t}\npublic:\n\tstring mName;\n\tint mAge;\n};\n\n\nstruct MyCompare03{\n\tbool operator()(const Person& p1,const Person& p2){\n\t\treturn p1.mAge > p2.mAge;\n\t}\n};\n\nvoid test03(){\n\n\tset<Person, MyCompare03> s;\n\n\tPerson p1(\"aaa\", 20);\n\tPerson p2(\"bbb\", 30);\n\tPerson p3(\"ccc\", 40);\n\tPerson p4(\"ddd\", 50);\n\n\ts.insert(p1);\n\ts.insert(p2);\n\ts.insert(p3);\n\ts.insert(p4);\n\n\tfor (set<Person, MyCompare03>::iterator it = s.begin(); it != s.end(); it++){\n\t\tcout << \"Name:\" << it->mName << \" Age:\" << it->mAge << endl;\n\t}\n\n}]]></ac:plain-text-body></ac:structured-macro><h3><span>3.7.3 </span>对组<span>(pair)</span></h3><p>对组<span>(pair)</span>将一对值组合成一个值,这一对值可以具有不同的数据类型,两个值可以分别用<span>pair</span>的两个公有属性<span>first</span>和<span>second</span>访问。</p><p>类模板:<span>template &lt;class T1, class T2&gt; struct pair.</span></p><p>如何创建对组<span>?</span></p><table><tbody><tr><td><p>//第一种方法创建一个对组</p><p>pair&lt;string, int&gt; pair1(string(&quot;name&quot;), 20);</p><p>cout &lt;&lt; pair1.first &lt;&lt; endl; //访问<span>pair</span>第一个值</p><p>cout &lt;&lt; pair1.second &lt;&lt; endl;//访问<span>pair</span>第二个值</p><p>//第二种</p><p>pair&lt;string, int&gt; pair2 = make_pair(&quot;name&quot;, 30);</p><p>cout &lt;&lt; pair2.first &lt;&lt; endl;</p><p>cout &lt;&lt; pair2.second &lt;&lt; endl;</p><p>//pair=赋值</p><p>pair&lt;string, int&gt; pair3 = pair2;</p><p>cout &lt;&lt; pair3.first &lt;&lt; endl;</p><p>cout &lt;&lt; pair3.second &lt;&lt; endl;</p></td></tr></tbody></table><p><br /></p><h2><span>3.8 map/multimap</span>容器</h2><h3><span>3.8.1 map/multimap</span>基本概念</h3><p>Map的特性是,所有元素都会根据元素的键值自动排序。<span>Map</span>所有的元素都是<span>pair,</span>同时拥有实值和键值,<span>pair</span>的第一元素被视为键值,第二元素被视为实值,<span>map</span>不允许两个元素有相同的键值。</p><p>我们可以通过<span>map</span>的迭代器改变<span>map</span>的键值吗?答案是不行,因为<span>map</span>的键值关系到<span>map</span>元素的排列规则,任意改变<span>map</span>键值将会严重破坏<span>map</span>组织。如果想要修改元素的实值,那么是可以的。</p><p>Map和<span>list</span>拥有相同的某些性质,当对它的容器元素进行新增操作或者删除操作时,操作之前的所有迭代器,在操作完成之后依然有效,当然被删除的那个元素的迭代器必然是个例外。</p><p>Multimap和<span>map</span>的操作类似,唯一区别<span>multimap</span>键值可重复。</p><p>Map和<span>multimap</span>都是以红黑树为底层实现机制。</p><h3><span>3.8.2 map/multimap</span>常用<span>API</span></h3><h4><span>3.8.2.1 map</span>构造函数</h4><table><tbody><tr><td><p>map&lt;T1, T2&gt; mapTT;//map默认构造函数<span>: </span></p><p>map(const map &amp;mp);//拷贝构造函数</p></td></tr></tbody></table><p><span> </span></p><p><span> </span></p><h4><span>3.8.2.2 map</span>赋值操作</h4><table><tbody><tr><td><p>map&amp; operator=(const map &amp;mp);//重载等号操作符</p><p>swap(mp);//交换两个集合容器</p></td></tr></tbody></table><p><span> </span></p><h4><span>3.8.2.3 map</span>大小操作</h4><table><tbody><tr><td><p>size();//返回容器中元素的数目</p><p>empty();//判断容器是否为空</p></td></tr></tbody></table><h4>3.8.2.4 map插入数据元素操作</h4><table><tbody><tr><td><p>map.insert(...); //往容器插入元素,返回<span>pair&lt;iterator,bool&gt;</span></p><p>map&lt;int, string&gt; mapStu;</p><p>// 第一种 通过<span>pair</span>的方式插入对象</p><p>mapStu.insert(pair&lt;int, string&gt;(3, &quot;小张<span>&quot;</span>));</p><p>// 第二种 通过<span>pair</span>的方式插入对象</p><p>mapStu.inset(make_pair(-1, &quot;校长<span>&quot;</span>));</p><p>// 第三种 通过<span>value_type</span>的方式插入对象</p><p>mapStu.insert(map&lt;int, string&gt;::value_type(1, &quot;小李<span>&quot;</span>));</p><p>// 第四种 通过数组的方式插入值</p><p>mapStu[3] = &quot;小刘<span>&quot;</span>;</p><p>mapStu[5] = &quot;小王<span>&quot;</span>;</p></td></tr></tbody></table><h4><span>3.8.2.5 map</span>删除操作</h4><table><tbody><tr><td><p>clear();//删除所有元素</p><p>erase(pos);//删除<span>pos</span>迭代器所指的元素,返回下一个元素的迭代器。</p><p>erase(beg,end);//删除区间<span>[beg,end)</span>的所有元素 ,返回下一个元素的迭代器。</p><p>erase(keyElem);//删除容器中<span>key</span>为<span>keyElem</span>的对组。</p></td></tr></tbody></table><h4><span>3.8.2.6 map</span>查找操作</h4><table><colgroup><col /></colgroup><tbody><tr><td><p>find(key);//查找键<span>key</span>是否存在<span>,</span>若存在,返回该键的元素的迭代器;<span>/</span>若不存在,返回<span>map.end();</span></p><p>count(keyElem);//返回容器中<span>key</span>为<span>keyElem</span>的对组个数。对<span>map</span>来说,要么是<span>0</span>,要么是<span>1</span>。对<span>multimap</span>来说,值可能大于<span>1</span>。</p><p>lower_bound(keyElem);//返回第一个<span>key&gt;=keyElem</span>元素的迭代器。</p><p>upper_bound(keyElem);//返回第一个<span>key&gt;keyElem</span>元素的迭代器。</p><p>equal_range(keyElem);//返回容器中<span>key</span>与<span>keyElem</span>相等的上下限的两个迭代器。</p></td></tr></tbody></table><h3><span>3.8.3 multimap</span>案例</h3><p>//公司今天招聘了<span>5</span>个员工,<span>5</span>名员工进入公司之后,需要指派员工在那个部门工作</p><p>//人员信息有<span>: </span>姓名 年龄 电话 工资等组成</p><p>//通过<span>Multimap</span>进行信息的插入 保存 显示</p><p>//分部门显示员工信息 显示全部员工信息</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"a4fe0588-bdfc-4384-abbf-2e55aa5d99a3\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[#define _CRT_SECURE_NO_WARNINGS\n\n#include<iostream>\n#include<map>\n#include<string>\n#include<vector>\nusing namespace std;\n\n//multimap 案例\n//公司今天招聘了 5 个员工,5 名员工进入公司之后,需要指派员工在那个部门工作\n//人员信息有: 姓名 年龄 电话 工资等组成\n//通过 Multimap 进行信息的插入 保存 显示\n//分部门显示员工信息 显示全部员工信息\n\n\n#define SALE_DEPATMENT 1 //销售部门\n#define DEVELOP_DEPATMENT 2 //研发部门\n#define FINACIAL_DEPATMENT 3 //财务部门\n#define ALL_DEPATMENT 4 //所有部门\n\n//员工类\nclass person{\npublic:\n\tstring name; //员工姓名\n\tint age; //员工年龄\n\tdouble salary; //员工工资\n\tstring tele; //员工电话\n};\n\n//创建5个员工\nvoid CreatePerson(vector<person>& vlist){\n\n\tstring seed = \"ABCDE\";\n\tfor (int i = 0; i < 5; i++){\n\t\tperson p;\n\t\tp.name = \"员工\";\n\t\tp.name += seed[i];\n\t\tp.age = rand() % 30 + 20;\n\t\tp.salary = rand() % 20000 + 10000;\n\t\tp.tele = \"010-8888888\";\n\t\tvlist.push_back(p);\n\t}\n\n}\n\n//5名员工分配到不同的部门\nvoid PersonByGroup(vector<person>& vlist, multimap<int, person>& plist){\n\n\n\tint operate = -1; //用户的操作\n\n\tfor (vector<person>::iterator it = vlist.begin(); it != vlist.end(); it++){\n\n\t\tcout << \"当前员工信息:\" << endl;\n\t\tcout << \"姓名:\" << it->name << \" 年龄:\" << it->age << \" 工资:\" << it->salary << \" 电话:\" << it->tele << endl;\n\t\tcout << \"请对该员工进行部门分配(1 销售部门, 2 研发部门, 3 财务部门):\" << endl;\n\t\tscanf(\"%d\", &operate);\n\n\t\twhile (true){\n\n\t\t\tif (operate == SALE_DEPATMENT){ //将该员工加入到销售部门\n\t\t\t\tplist.insert(make_pair(SALE_DEPATMENT, *it));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\telse if (operate == DEVELOP_DEPATMENT){\n\t\t\t\tplist.insert(make_pair(DEVELOP_DEPATMENT, *it));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\telse if (operate == FINACIAL_DEPATMENT){\n\t\t\t\tplist.insert(make_pair(FINACIAL_DEPATMENT, *it));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\telse{\n\t\t\t\tcout << \"您的输入有误,请重新输入(1 销售部门, 2 研发部门, 3 财务部门):\" << endl;\n\t\t\t\tscanf(\"%d\", &operate);\n\t\t\t}\n\n\t\t}\n\n\t}\n\tcout << \"员工部门分配完毕!\" << endl;\n\tcout << \"***********************************************************\" << endl;\n\n}\n\n//打印员工信息\nvoid printList(multimap<int, person>& plist, int myoperate){\n\n\tif (myoperate == ALL_DEPATMENT){\n\t\tfor (multimap<int, person>::iterator it = plist.begin(); it != plist.end(); it++){\n\t\t\tcout << \"姓名:\" << it->second.name << \" 年龄:\" << it->second.age << \" 工资:\" << it->second.salary << \" 电话:\" << it->second.tele << endl;\n\t\t}\n\t\treturn;\n\t}\n\n\tmultimap<int, person>::iterator it = plist.find(myoperate);\n\tint depatCount = plist.count(myoperate);\n\tint num = 0;\n\tif (it != plist.end()){\n\t\twhile (it != plist.end() && num < depatCount){\n\t\t\tcout << \"姓名:\" << it->second.name << \" 年龄:\" << it->second.age << \" 工资:\" << it->second.salary << \" 电话:\" << it->second.tele << endl;\n\t\t\tit++;\n\t\t\tnum++;\n\t\t}\n\t}\n}\n\n//根据用户操作显示不同部门的人员列表\nvoid ShowPersonList(multimap<int, person>& plist, int myoperate){\n\n\tswitch (myoperate)\n\t{\n\tcase SALE_DEPATMENT:\n\t\tprintList(plist, SALE_DEPATMENT);\n\t\tbreak;\n\tcase DEVELOP_DEPATMENT:\n\t\tprintList(plist, DEVELOP_DEPATMENT);\n\t\tbreak;\n\tcase FINACIAL_DEPATMENT:\n\t\tprintList(plist, FINACIAL_DEPATMENT);\n\t\tbreak;\n\tcase ALL_DEPATMENT:\n\t\tprintList(plist, ALL_DEPATMENT);\n\t\tbreak;\n\t}\n}\n\n//用户操作菜单\nvoid PersonMenue(multimap<int, person>& plist){\n\n\tint flag = -1;\n\tint isexit = 0;\n\twhile (true){\n\t\tcout << \"请输入您的操作((1 销售部门, 2 研发部门, 3 财务部门, 4 所有部门, 0退出)\" << endl;\n\t\tscanf(\"%d\", &flag);\n\n\t\tswitch (flag)\n\t\t{\n\t\tcase SALE_DEPATMENT:\n\t\t\tShowPersonList(plist, SALE_DEPATMENT);\n\t\t\tbreak;\n\t\tcase DEVELOP_DEPATMENT:\n\t\t\tShowPersonList(plist, DEVELOP_DEPATMENT);\n\t\t\tbreak;\n\t\tcase FINACIAL_DEPATMENT:\n\t\t\tShowPersonList(plist, FINACIAL_DEPATMENT);\n\t\t\tbreak;\n\t\tcase ALL_DEPATMENT:\n\t\t\tShowPersonList(plist, ALL_DEPATMENT);\n\t\t\tbreak;\n\t\tcase 0:\n\t\t\tisexit = 1;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tcout << \"您的输入有误,请重新输入!\" << endl;\n\t\t\tbreak;\n\t\t}\n\n\t\tif (isexit == 1){\n\t\t\tbreak;\n\t\t}\n\t}\n\n}\n\nint main(){\n\n\tvector<person> vlist; //创建的5个员工 未分组\n\tmultimap<int, person> plist; //保存分组后员工信息\n\n\t//创建5个员工\n\tCreatePerson(vlist);\n\t//5名员工分配到不同的部门\n\tPersonByGroup(vlist, plist);\n\t//根据用户输入显示不同部门员工信息列表 或者 显示全部员工的信息列表\n\tPersonMenue(plist);\n\n\tsystem(\"pause\");\n\treturn EXIT_SUCCESS;\n}]]></ac:plain-text-body></ac:structured-macro><h2><span>3.</span>9<span> STL</span>容器使用时机</h2><table><thead><tr><td><p><span> </span></p></td><td><p>vector</p></td><td><p>deque</p></td><td><p>list</p></td><td><p>set</p></td><td><p>multiset</p></td><td><p>map</p></td><td><p>multimap</p></td></tr></thead><tbody><tr><td><p>典型内存结构</p></td><td><p>单端数组</p></td><td><p>双端数组</p></td><td><p>双向链表</p></td><td><p>二叉树</p></td><td><p>二叉树</p></td><td><p>二叉树</p></td><td><p>二叉树</p></td></tr><tr><td><p>可随机存取</p></td><td><p>是</p></td><td><p>是</p></td><td><p>否</p></td><td><p>否</p></td><td><p>否</p></td><td><p>对<span>key</span>而言:不是</p></td><td><p>否</p></td></tr><tr><td><p>元素搜寻速度</p></td><td><p>慢</p></td><td><p>慢</p></td><td><p>非常慢</p></td><td><p>快</p></td><td><p>快</p></td><td><p>对<span>key</span>而言:快</p></td><td><p>对<span>key</span>而言:快</p></td></tr><tr><td><p>元素安插移除</p></td><td><p>尾端</p></td><td><p>头尾两端</p></td><td><p>任何位置</p></td><td><p>-</p></td><td><p>-</p></td><td><p>-</p></td><td><p>-</p></td></tr></tbody></table><p><span> </span></p><p><br /></p><ul><li>vector的使用场景:比如软件历史操作记录的存储,我们经常要查看历史记录,比如上一次的记录,上上次的记录,但却不会去删除记录,因为记录是事实的描述。</li><li>deque的使用场景:比如排队购票系统,对排队者的存储可以采用deque,支持头端的快速移除,尾端的快速添加。如果采用vector,则头端移除时,会移动大量的数据,速度慢。</li></ul><p>     vector与deque的比较:</p><p>      一:<span><a href=\"http://vector.at\">vector.at</a>()</span>比<span><a href=\"http://deque.at\">deque.at</a>()</span>效率高,比如<a href=\"http://vector.at\">vector.at</a>(0)是固定的,deque的开始位置 却是不固定的。</p><p>    二:如果有大量释放操作的话,vector花的时间更少,这跟二者的内部实现有关。</p><p>    三:<span>deque</span>支持头部的快速插入与快速移除,这是deque的优点。</p><ul><li>list的使用场景:比如公交车乘客的存储,随时可能有乘客下车,支持频繁的不确实位置元素的移除插入。</li><li>set的使用场景:比如对手机游戏的个人得分记录的存储,存储要求从高分到低分的顺序排列。</li><li>map的使用场景:比如按ID号存储十万个用户,想要快速要通过ID查找对应的用户。二叉树的查找效率,这时就体现出来了。如果是vector容器,最坏的情况下可能要遍历完整个容器才能找到该用户。</li></ul><p><br /></p><p><br /></p></ac:layout-cell><ac:layout-cell><p><br /></p></ac:layout-cell></ac:layout-section></ac:layout>","representation":"storage","_expandable":{"content":"/rest/api/content/58954488"}},"_expandable":{"editor":"","view":"","export_view":"","styled_view":"","anonymous_export_view":""}},"extensions":{"position":"none"},"_links":{"webui":"/pages/viewpage.action?pageId=58954488","edit":"/pages/resumedraft.action?draftId=58954488","tinyui":"/x/_JKDAw","collection":"/rest/api/content","base":"https://wiki.suncaper.net","context":"","self":"https://wiki.suncaper.net/rest/api/content/58954488"},"_expandable":{"container":"/rest/api/space/2CT","metadata":"","operations":"","children":"/rest/api/content/58954488/child","restrictions":"/rest/api/content/58954488/restriction/byOperation","history":"/rest/api/content/58954488/history","ancestors":"","descendants":"/rest/api/content/58954488/descendant","space":"/rest/api/space/2CT"}}