STL-related

This commit is contained in:
e2hang
2025-08-12 23:00:31 +08:00
parent a2e3b28ac0
commit 629c499a1b
9 changed files with 1363 additions and 0 deletions

View File

@@ -204,3 +204,301 @@ if (auto [it, ok] = s.insert(10); ok) {
* 🧩 `unordered_set` 的比较与选择策略 * 🧩 `unordered_set` 的比较与选择策略
是否要我继续讲 `multiset` 和 `unordered_set` 是否要我继续讲 `multiset` 和 `unordered_set`
<h1>C++ STL set容器完全攻略超级详细</h1>
<div class="pre-next-page clearfix">&nbsp;</div>
<div id="arc-body">前面章节讲解了 map 容器和 multimap 容器的用法,类似地,<a href='/cplus/' target='_blank'>C++</a> <a href='/stl/' target='_blank'>STL</a> 标准库中还提供有 set 和 multiset 这 2 个容器,它们也属于关联式容器。不过,本节先讲解 set 容器,后续章节再讲解 multiset 容器。<br />
<br />
和 map、multimap 容器不同,使用 set 容器存储的各个键值对,要求键 key 和值 value 必须相等。<br />
<br />
举个例子,如下有 2 组键值对数据:
<p class="info-box">
{&lt;&#39;a&#39;, 1&gt;, &lt;&#39;b&#39;, 2&gt;, &lt;&#39;c&#39;, 3&gt;}<br />
{&lt;&#39;a&#39;, &#39;a&#39;&gt;, &lt;&#39;b&#39;, &#39;b&#39;&gt;, &lt;&#39;c&#39;, &#39;c&#39;&gt;}</p>
显然,第一组数据中各键值对的键和值不相等,而第二组中各键值对的键和值对应相等。对于 set 容器来说,只能存储第 2 组键值对,而无法存储第一组键值对。<br />
<br />
基于 set 容器的这种特性,当使用 set 容器存储键值对时,只需要为其提供各键值对中的 value 值(也就是 key 的值)即可。仍以存储上面第 2 组键值对为例,只需要为 set 容器提供 {&#39;a&#39;,&#39;b&#39;,&#39;c&#39;} ,该容器即可成功将它们存储起来。<br />
<br />
通过前面的学习我们知道map、multimap 容器都会自行根据键的大小对存储的键值对进行排序set 容器也会如此,只不过 set 容器中各键值对的键 key 和值 value 是相等的,根据 key 排序,也就等价为根据 value 排序。<br />
<br />
另外,使用 set 容器存储的各个元素的值必须各不相同。更重要的是,从语法上讲 set 容器并没有强制对存储元素的类型做 const 修饰,即 set 容器中存储的元素的值是可以修改的。但是C++ 标准为了防止用户修改容器中元素的值,对所有可能会实现此操作的行为做了限制,使得在正常情况下,用户是无法做到修改 set 容器中元素的值的。<br />
<blockquote>
<p>
对于初学者来说,切勿尝试直接修改 set 容器中已存储元素的值,这很有可能破坏 set 容器中元素的有序性,最正确的修改 set 容器中元素值的做法是:先删除该元素,然后再添加一个修改后的元素。</p>
</blockquote>
值得一提的是set 容器定义于<code>&lt;set&gt;</code>头文件,并位于 std 命名空间中。因此如果想在程序中使用 set 容器,该程序代码应先包含如下语句:
<pre class="cpp">
#include &lt;set&gt;
using namespace std;</pre>
注意,第二行代码不是必需的,如果不用,则后续程序中在使用 set 容器时,需手动注明 std 命名空间(强烈建议初学者使用)。<br />
<br />
set 容器的类模板定义如下:
<pre class="cpp">
template &lt; class T, // 键 key 和值 value 的类型
class Compare = less&lt;T&gt;, // 指定 set 容器内部的排序规则
class Alloc = allocator&lt;T&gt; // 指定分配器对象的类型
&gt; class set;</pre>
注意,由于 set 容器存储的各个键值对,其键和值完全相同,也就意味着它们的类型相同,因此 set 容器类模板的定义中,仅有第 1 个参数用于设定存储数据的类型。<br />
<blockquote>
<p>
对于 set 类模板中的 3 个参数,后 2 个参数自带默认值,且几乎所有场景中只需使用前 2 个参数,第 3 个参数不会用到。</p>
</blockquote>
<h2>
创建C++ set容器的几种方法</h2>
常见的创建 set 容器的方法,大致有以下 5 种。<br />
<br />
1) 调用默认构造函数,创建空的 set 容器。比如:
<pre class="cpp">
std::set&lt;std::string&gt; myset;</pre>
<blockquote>
<p>
如果程序中已经默认指定了 std 命令空间,这里可以省略 std::。</p>
</blockquote>
由此就创建好了一个 set 容器,该容器采用默认的<code>std::less&lt;T&gt;</code>规则,会对存储的 string 类型元素做升序排序。注意,由于 set 容器支持随时向内部添加新的元素,因此创建空 set 容器的方法是经常使用的。<br />
<br />
2) 除此之外set 类模板还支持在创建 set 容器的同时,对其进行初始化。例如:
<pre class="cpp">
std::set&lt;std::string&gt; myset{&quot;http://c.biancheng.net/java/&quot;,
&quot;http://c.biancheng.net/stl/&quot;,
&quot;http://c.biancheng.net/python/&quot;};</pre>
由此即创建好了包含 3 个 string 元素的 myset 容器。由于其采用默认的 std::less&lt;T&gt; 规则,因此其内部存储 string 元素的顺序如下所示:
<p class="info-box">
&quot;http://c.biancheng.net/java/&quot;<br />
&quot;http://c.biancheng.net/python/&quot;<br />
&quot;http://c.biancheng.net/stl/&quot;</p>
<br />
3) set 类模板中还提供了拷贝(复制)构造函数,可以实现在创建新 set 容器的同时,将已有 set 容器中存储的所有元素全部复制到新 set 容器中。<br />
<br />
例如,在第 2 种方式创建的 myset 容器的基础上,执行如下代码:
<pre class="cpp">
std::set&lt;std::string&gt; copyset(myset);
//等同于
//std::set&lt;std::string&gt; copyset = myset</pre>
该行代码在创建 copyset 容器的基础上,还会将 myset 容器中存储的所有元素,全部复制给 copyset 容器一份。<br />
<br />
另外C++ 11 标准还为 set 类模板新增了移动构造函数,其功能是实现创建新 set 容器的同时,利用临时的 set 容器为其初始化。比如:
<pre class="cpp">
set&lt;string&gt; retSet() {
std::set&lt;std::string&gt; myset{ &quot;http://c.biancheng.net/java/&quot;,
&quot;http://c.biancheng.net/stl/&quot;,
&quot;http://c.biancheng.net/python/&quot; };
return myset;
}
std::set&lt;std::string&gt; copyset(retSet());
//或者
//std::set&lt;std::string&gt; copyset = retSet();</pre>
<p>
注意,由于 retSet() 函数的返回值是一个临时 set 容器,因此在初始化 copyset 容器时,其内部调用的是 set 类模板中的移动构造函数,而非拷贝构造函数。</p>
<blockquote>
<p>
显然,无论是调用复制构造函数还是调用拷贝构造函数,都必须保证这 2 个容器的类型完全一致。</p>
</blockquote>
<br />
4) 在第 3 种方式的基础上set 类模板还支持取已有 set 容器中的部分元素,来初始化新 set 容器。例如:
<pre class="cpp">
std::set&lt;std::string&gt; myset{ &quot;http://c.biancheng.net/java/&quot;,
&quot;http://c.biancheng.net/stl/&quot;,
&quot;http://c.biancheng.net/python/&quot; };
std::set&lt;std::string&gt; copyset(++myset.begin(), myset.end());</pre>
由此初始化的 copyset 容器,其内部仅存有如下 2 个 string 字符串:
<p class="info-box">
&quot;http://c.biancheng.net/python/&quot;<br />
&quot;http://c.biancheng.net/stl/&quot;</p>
<br />
5) 以上几种方式创建的 set 容器,都采用了默认的<code>std::less&lt;T&gt;</code>规则。其实,借助 set 类模板定义中第 2 个参数,我们完全可以手动修改 set 容器中的排序规则。比如:
<pre class="cpp">
std::set&lt;std::string,std::greater&lt;string&gt; &gt; myset{
&quot;http://c.biancheng.net/java/&quot;,
&quot;http://c.biancheng.net/stl/&quot;,
&quot;http://c.biancheng.net/python/&quot;};</pre>
通过选用 std::greater&lt;string&gt; 降序规则myset 容器中元素的存储顺序为:
<p class="info-box">
&quot;http://c.biancheng.net/stl/&quot;<br />
&quot;http://c.biancheng.net/python/&quot;<br />
&quot;http://c.biancheng.net/java/&quot;</p>
<h2>
C++ STL set容器包含的成员方法</h2>
表 1 列出了 set 容器提供的常用成员方法以及各自的功能。<br />
<br />
<table>
<caption>
表 1 C++ set 容器常用成员方法</caption>
<tbody>
<tr>
<th>
成员方法</th>
<th>
功能</th>
</tr>
<tr>
<td>
begin()</td>
<td>
返回指向容器中第一个(注意,是已排好序的第一个)元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。</td>
</tr>
<tr>
<td>
end()</td>
<td>
返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。</td>
</tr>
<tr>
<td>
rbegin()</td>
<td>
返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。</td>
</tr>
<tr>
<td>
rend()</td>
<td>
返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。</td>
</tr>
<tr>
<td>
cbegin()</td>
<td>
和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。</td>
</tr>
<tr>
<td>
cend()</td>
<td>
和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。</td>
</tr>
<tr>
<td>
crbegin()</td>
<td>
和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。</td>
</tr>
<tr>
<td>
crend()</td>
<td>
和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。</td>
</tr>
<tr>
<td>
find(val)</td>
<td>
在 set 容器中查找值为 val 的元素,如果成功找到,则返回指向该元素的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。</td>
</tr>
<tr>
<td>
lower_bound(val)</td>
<td>
返回一个指向当前 set 容器中第一个大于或等于 val 的元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。</td>
</tr>
<tr>
<td>
upper_bound(val)</td>
<td>
返回一个指向当前 set 容器中第一个大于 val 的元素的迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。</td>
</tr>
<tr>
<td>
equal_range(val)</td>
<td>
该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的值为 val 的元素set 容器中各个元素是唯一的,因此该范围最多包含一个元素)。</td>
</tr>
<tr>
<td>
empty()</td>
<td>
若容器为空,则返回 true否则 false。</td>
</tr>
<tr>
<td>
size()</td>
<td>
返回当前 set 容器中存有元素的个数。</td>
</tr>
<tr>
<td>
max_size()</td>
<td>
返回 set 容器所能容纳元素的最大个数,不同的操作系统,其返回值亦不相同。</td>
</tr>
<tr>
<td>
insert()</td>
<td>
向 set 容器中插入元素。</td>
</tr>
<tr>
<td>
erase()</td>
<td>
删除 set 容器中存储的元素。</td>
</tr>
<tr>
<td>
swap()</td>
<td>
交换 2 个 set 容器中存储的所有元素。这意味着,操作的 2 个 set 容器的类型必须相同。</td>
</tr>
<tr>
<td>
clear()</td>
<td>
清空 set 容器中所有的元素,即令 set 容器的 size() 为 0。</td>
</tr>
<tr>
<td>
emplace()</td>
<td>
在当前 set 容器中的指定位置直接构造新元素。其效果和 insert() 一样,但效率更高。</td>
</tr>
<tr>
<td>
emplace_hint()</td>
<td>
在本质上和 emplace() 在 set 容器中构造新元素的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示新元素生成位置的迭代器,并作为该方法的第一个参数。</td>
</tr>
<tr>
<td>
count(val)</td>
<td>
在当前 set 容器中,查找值为 val 的元素的个数,并返回。注意,由于 set 容器中各元素的值是唯一的,因此该函数的返回值最大为 1。</td>
</tr>
</tbody>
</table>
<br />
下面程序演示了表 1 中部分成员函数的用法:
<pre class="cpp">
#include &lt;iostream&gt;
#include &lt;set&gt;
#include &lt;string&gt;
using namespace std;
int main()
{
//创建空set容器
std::set&lt;std::string&gt; myset;
//空set容器不存储任何元素
cout &lt;&lt; &quot;1、myset size = &quot; &lt;&lt; myset.size() &lt;&lt; endl;
//向myset容器中插入新元素
myset.insert(&quot;http://c.biancheng.net/java/&quot;);
myset.insert(&quot;http://c.biancheng.net/stl/&quot;);
myset.insert(&quot;http://c.biancheng.net/python/&quot;);
cout &lt;&lt; &quot;2、myset size = &quot; &lt;&lt; myset.size() &lt;&lt; endl;
//利用双向迭代器遍历myset
<a href='/view/1811.html' target='_blank'>for</a> (auto iter = myset.begin(); iter != myset.end(); ++iter) {
cout &lt;&lt; *iter &lt;&lt; endl;
}
return 0;
}</pre>
程序执行结果为:
<p class="info-box">
1、myset size = 0<br />
2、myset size = 3<br />
http://c.biancheng.net/java/<br />
http://c.biancheng.net/python/<br />
http://c.biancheng.net/stl/</p>
<blockquote>
<p>
有关表 1 中其它成员方法的用法,后续章节会做详细讲解。</p>
</blockquote>
</div>

45
STL/STL-Set/set.cpp Normal file
View File

@@ -0,0 +1,45 @@
#include <iostream>
#include <string>
#include <set>
using namespace std;
int main(){
auto cmp = [](auto a, auto b){
return a > b;
};
set<int> a;
set<int> b = {1, 5, 4, 3, 3, 2};
set<int, decltype(cmp)> c(cmp);
a.insert(1);a.insert(5);a.insert(3);a.insert(2);
c.insert(1);c.insert(5);c.insert(3);c.insert(2);
a.emplace(6);
for(auto x : a){
cout << x << " ";
}
cout << endl;
for(auto x : c){
cout << x << " ";
}
cout << endl;
cout << a.count(3) << endl;
a.erase(6);
for(auto it = a.begin(); it != a.end(); it++){
cout << *it << " ";
}
//From "Smaller" To "Bigger", To compare big, we need "func:cmp"
cout << endl;
cout << *(c.find(5))<< endl;
//lower_bound : >= val ; upper_bound : > val ;
cout << *(c.lower_bound(2)) << " " << *(c.upper_bound(2)) << endl;
cout << *(a.lower_bound(2)) << " " <<*(a.upper_bound(2)) << endl;
return 0;
}
/*
COUT
1 2 3 5 6
5 3 2 1
1
1 2 3 5
5
2 1
2 3
*/

BIN
STL/STL-Set/set.exe Normal file

Binary file not shown.

303
STL/STL-map/README.MD Normal file
View File

@@ -0,0 +1,303 @@
<h1>C++ STL map容器详解</h1>
<div class="pre-next-page clearfix">&nbsp;</div>
<div id="arc-body">作为关联式容器的一种map 容器存储的都是 pair 对象,也就是用 pair 类模板创建的键值对。其中,各个键值对的键和值可以是任意数据类型,包括 <a href='/cplus/' target='_blank'>C++</a> 基本数据类型int、double 等)、使用结构体或类自定义的类型。<br />
<blockquote>
<p>
通常情况下map 容器中存储的各个键值对都选用 string 字符串作为键的类型。</p>
</blockquote>
与此同时,在使用 map 容器存储多个键值对时该容器会自动根据各键值对的键的大小按照既定的规则进行排序。默认情况下map 容器选用<code>std::less&lt;T&gt;</code>排序规则(其中 T 表示键的数据类型),其会根据键的大小对所有键值对做升序排序。当然,根据实际情况的需要,我们可以手动指定 map 容器的排序规则,既可以选用 <a href='/stl/' target='_blank'>STL</a> 标准库中提供的其它排序规则(比如<code>std::greater&lt;T&gt;</code>),也可以自定义排序规则。<br />
<blockquote>
<p>
关于如何自定义 map 容器的排序规则,后续章节会做详细讲解。</p>
</blockquote>
另外需要注意的是,<span style="color:#b22222;">使用 map 容器存储的各个键值对,键的值既不能重复也不能被修改。</span>换句话说map 容器中存储的各个键值对不仅键的值独一无二,键的类型也会用 const 修饰,这意味着只要键值对被存储到 map 容器中,其键的值将不能再做任何修改。
<blockquote>
<p>
前面提到map 容器存储的都是 pair 类型的键值对元素,更确切的说,该容器存储的都是 pair&lt;const K, T&gt; 类型(其中 K 和 T 分别表示键和值的数据类型)的键值对元素。</p>
</blockquote>
map 容器定义在 &lt;map&gt; 头文件中,并位于 std 命名空间中。因此,如果想使用 map 容器,代码中应包含如下语句:
<pre class="cpp">
#include &lt;map&gt;
using namespace std;</pre>
<blockquote>
<p>
注意,第二行代码不是必需的,如果不用,则后续程序中在使用 map 容器时,需手动注明 std 命名空间(强烈建议初学者使用)。</p>
</blockquote>
map 容器的模板定义如下:
<pre class="cpp">
template &lt; class Key, // 指定键key的类型
class T, // 指定值value的类型
class Compare = less&lt;Key&gt;, // 指定排序规则
class Alloc = allocator&lt;pair&lt;const Key,T&gt; &gt; // 指定分配器对象的类型
&gt; class map;</pre>
可以看到map 容器模板有 4 个参数,其中后 2 个参数都设有默认值。大多数场景中,我们只需要设定前 2 个参数的值,有些场景可能会用到第 3 个参数,但最后一个参数几乎不会用到。<br />
<h2>
创建C++ map容器的几种方法</h2>
map 容器的模板类中包含多种构造函数,因此创建 map 容器的方式也有多种,下面就几种常用的创建 map 容器的方法,做一一讲解。<br />
<br />
1) 通过调用 map 容器类的默认构造函数,可以创建出一个空的 map 容器,比如:
<pre class="cpp">
std::map&lt;std::string, int&gt;myMap;</pre>
<blockquote>
<p>
如果程序中已经默认指定了 std 命令空间,这里可以省略 <code>std::</code>。</p>
</blockquote>
通过此方式创建出的 myMap 容器,初始状态下是空的,即没有存储任何键值对。鉴于空 map 容器可以根据需要随时添加新的键值对,因此创建空 map 容器是比较常用的。<br />
<br />
2) 当然在创建 map 容器的同时,也可以进行初始化,比如:
<pre class="cpp">
std::map&lt;std::string, int&gt;myMap{ {&quot;C语言教程&quot;,10},{&quot;STL教程&quot;,20} };</pre>
由此myMap 容器在初始状态下,就包含有 2 个键值对。<br />
<br />
再次强调map 容器中存储的键值对,其本质都是 pair 类模板创建的 pair 对象。因此,下面程序也可以创建出一模一样的 myMap 容器:<br />
<pre class="cpp">
std::map&lt;std::string, int&gt;myMap{std::make_pair(&quot;C语言教程&quot;,10),std::make_pair(&quot;STL教程&quot;,20)};</pre>
<br />
3) 除此之外,在某些场景中,可以利用先前已创建好的 map 容器,再创建一个新的 map 容器。例如:
<pre class="cpp">
std::map&lt;std::string, int&gt;newMap(myMap);</pre>
由此,通过调用 map 容器的拷贝(复制)构造函数,即可成功创建一个和 myMap 完全一样的 newMap 容器。<br />
<br />
C++ 11 标准中,还为 map 容器增添了移动构造函数。当有临时的 map 对象作为参数,传递给要初始化的 map 容器时,此时就会调用移动构造函数。举个例子:
<pre class="cpp">
#创建一个会返回临时 map 对象的函数
std::map&lt;std::string,int&gt; disMap() {
std::map&lt;std::string, int&gt;tempMap{ {&quot;C语言教程&quot;,10},{&quot;STL教程&quot;,20} };
return tempMap;
}
//调用 map 类模板的移动构造函数创建 newMap 容器
std::map&lt;std::string, int&gt;newMap(disMap());</pre>
<blockquote>
<p>
注意,无论是调用复制构造函数还是调用拷贝构造函数,都必须保证这 2 个容器的类型完全一致。</p>
</blockquote>
<br />
4) map 类模板还支持取已建 map 容器中指定区域内的键值对,创建并初始化新的 map 容器。例如:
<pre class="cpp">
std::map&lt;std::string, int&gt;myMap{ {&quot;C语言教程&quot;,10},{&quot;STL教程&quot;,20} };
std::map&lt;std::string, int&gt;newMap(++myMap.begin(), myMap.end());</pre>
这里,通过调用 map 容器的双向迭代器,实现了在创建 newMap 容器的同时,将其初始化为包含一个 {&quot;STL教程&quot;,20} 键值对的容器。<br />
<blockquote>
<p>
有关 map 容器迭代器,后续章节会做详细讲解。</p>
</blockquote>
<br />
5) 当然,在以上几种创建 map 容器的基础上,我们都可以手动修改 map 容器的排序规则。默认情况下map 容器调用 std::less&lt;T&gt; 规则,根据容器内各键值对的键的大小,对所有键值对做升序排序。<br />
<br />
因此,如下 2 行创建 map 容器的方式,其实是等价的:
<pre class="cpp">
std::map&lt;std::string, int&gt;myMap{ {&quot;C语言教程&quot;,10},{&quot;STL教程&quot;,20} };
std::map&lt;std::string, int, std::less&lt;std::string&gt; &gt;myMap{ {&quot;C语言教程&quot;,10},{&quot;STL教程&quot;,20} };</pre>
以上 2 中创建方式生成的 myMap 容器,其内部键值对排列的顺序为:
<p class="info-box">
&lt;&quot;C语言教程&quot;, 10&gt;<br />
&lt;&quot;STL教程&quot;, 20&gt;</p>
<br />
下面程序手动修改了 myMap 容器的排序规则,令其作降序排序:
<pre class="cpp">
std::map&lt;std::string, int, std::greater&lt;std::string&gt; &gt;myMap{ {&quot;C语言教程&quot;,10},{&quot;STL教程&quot;,20} };</pre>
此时myMap 容器内部键值对排列的顺序为:
<p class="info-box">
&lt;&quot;STL教程&quot;, 20&gt;<br />
&lt;&quot;C语言教程&quot;, 10&gt;</p>
<blockquote>
<p>
在某些特定场景中,我们还需要为 map 容器自定义排序规则,此部分知识后续将利用整整一节做重点讲解。</p>
</blockquote>
<h2>
C++ map容器包含的成员方法</h2>
表 1 列出了 map 容器提供的常用成员方法以及各自的功能。<br />
<br />
<table>
<caption>
表 1 C++ map容器常用成员方法</caption>
<tbody>
<tr>
<th>
成员方法</th>
<th>
功能</th>
</tr>
<tr>
<td>
begin()</td>
<td>
返回指向容器中第一个(注意,是已排好序的第一个)键值对的双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。</td>
</tr>
<tr>
<td>
end()</td>
<td>
返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。</td>
</tr>
<tr>
<td>
rbegin()</td>
<td>
返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。</td>
</tr>
<tr>
<td>
rend()</td>
<td>
返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。</td>
</tr>
<tr>
<td>
cbegin()</td>
<td>
和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。</td>
</tr>
<tr>
<td>
cend()</td>
<td>
和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。</td>
</tr>
<tr>
<td>
crbegin()</td>
<td>
和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。</td>
</tr>
<tr>
<td>
crend()</td>
<td>
和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。</td>
</tr>
<tr>
<td>
find(key)</td>
<td>
在 map 容器中查找键为 key 的键值对,如果成功找到,则返回指向该键值对的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。</td>
</tr>
<tr>
<td>
lower_bound(key)</td>
<td>
返回一个指向当前 map 容器中第一个大于或等于 key 的键值对的双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。</td>
</tr>
<tr>
<td>
upper_bound(key)</td>
<td>
返回一个指向当前 map 容器中第一个大于 key 的键值对的迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。</td>
</tr>
<tr>
<td>
equal_range(key)</td>
<td>
该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的键为 key 的键值对map 容器键值对唯一,因此该范围最多包含一个键值对)。</td>
</tr>
<tr>
<td>
empty()&nbsp;</td>
<td>
若容器为空,则返回 true否则 false。</td>
</tr>
<tr>
<td>
size()</td>
<td>
返回当前 map 容器中存有键值对的个数。</td>
</tr>
<tr>
<td>
max_size()</td>
<td>
返回 map 容器所能容纳键值对的最大个数,不同的操作系统,其返回值亦不相同。</td>
</tr>
<tr>
<td>
operator[]</td>
<td>
map容器重载了 [] 运算符,只要知道 map 容器中某个键值对的键的值,就可以向获取数组中元素那样,通过键直接获取对应的值。</td>
</tr>
<tr>
<td>
at(key)</td>
<td>
找到 map 容器中 key 键对应的值,如果找不到,该函数会引发 out_of_range 异常。</td>
</tr>
<tr>
<td>
insert()</td>
<td>
向 map 容器中插入键值对。</td>
</tr>
<tr>
<td>
erase()</td>
<td>
删除 map 容器指定位置、指定键key值或者指定区域内的键值对。后续章节还会对该方法做重点讲解。</td>
</tr>
<tr>
<td>
swap()</td>
<td>
交换 2 个 map 容器中存储的键值对,这意味着,操作的 2 个键值对的类型必须相同。</td>
</tr>
<tr>
<td>
clear()</td>
<td>
清空 map 容器中所有的键值对,即使 map 容器的 size() 为 0。</td>
</tr>
<tr>
<td>
emplace()</td>
<td>
在当前 map 容器中的指定位置处构造新键值对。其效果和插入键值对一样,但效率更高。</td>
</tr>
<tr>
<td>
emplace_hint()</td>
<td>
在本质上和 emplace() 在 map 容器中构造新键值对的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示键值对生成位置的迭代器,并作为该方法的第一个参数。</td>
</tr>
<tr>
<td>
count(key)</td>
<td>
在当前 map 容器中,查找键为 key 的键值对的个数并返回。注意,由于 map 容器中各键值对的键的值是唯一的,因此该函数的返回值最大为 1。</td>
</tr>
</tbody>
</table>
<br />
下面的样例演示了表 1 中部分成员方法的用法:<br />
<pre class="cpp">
#include &lt;iostream&gt;
#include &lt;map&gt; // map
#include &lt;string&gt; // string
using namespace std;
int main() {
//创建空 map 容器,默认根据个键值对中键的值,对键值对做降序排序
std::map&lt;std::string, std::string, std::greater&lt;std::string&gt;&gt;myMap;
//调用 emplace() 方法,直接向 myMap 容器中指定位置构造新键值对
myMap.emplace(&quot;C语言教程&quot;,&quot;http://c.biancheng.net/c/&quot;);
myMap.emplace(&quot;<a href='/python/' target='_blank'>Python</a>教程&quot;, &quot;http://c.biancheng.net/python/&quot;);
myMap.emplace(&quot;STL教程&quot;, &quot;http://c.biancheng.net/stl/&quot;);
//输出当前 myMap 容器存储键值对的个数
cout &lt;&lt; &quot;myMap size==&quot; &lt;&lt; myMap.size() &lt;&lt; endl;
//判断当前 myMap 容器是否为空
if (!myMap.empty()) {
//借助 myMap 容器迭代器,将该容器的键值对逐个输出
<a href='/view/1811.html' target='_blank'>for</a> (auto i = myMap.begin(); i != myMap.end(); ++i) {
cout &lt;&lt; i-&gt;first &lt;&lt; &quot; &quot; &lt;&lt; i-&gt;second &lt;&lt; endl;
}
}
return 0;
}</pre>
程序执行结果为:
<p class="info-box">
myMap size==3<br />
STL教程 http://c.biancheng.net/stl/<br />
Python教程 http://c.biancheng.net/python/<br />
C语言教程 http://c.biancheng.net/c/</p>
<blockquote>
<p>
有关表 1 中其它成员函数的用法,后续章节会做详细详解。</p>
</blockquote>
</div>

7
STL/STL-map/map.cpp Normal file
View File

@@ -0,0 +1,7 @@
#include <iostream>
#include <map>
using namespace std;
int main(int argc, char* argv[]){
return 0;
}

View File

@@ -0,0 +1,421 @@
好的!我来给你详细介绍一下 C++ STL 里的 `priority_queue` 容器适配器的用法和常见技巧,帮你全面掌握它。
---
# 一、什么是 `priority_queue`
`priority_queue` 是 STL 提供的一个容器适配器底层默认使用堆heap数据结构实现保证访问时总能快速取得最大默认或最小元素。
它类似于一个“优先级队列”,总是先弹出优先级最高(优先级依据元素大小)的元素。
---
# 二、包含头文件
```cpp
#include <queue>
```
`priority_queue` 位于 `<queue>` 头文件中。
---
# 三、定义和基本用法
```cpp
#include <iostream>
#include <queue>
int main() {
std::priority_queue<int> pq; // 默认大顶堆元素类型为int
pq.push(5);
pq.push(1);
pq.push(10);
pq.push(7);
// 访问优先级最高的元素(最大值)
std::cout << "Top: " << pq.top() << "\n"; // 10
// 弹出元素,优先级最高的先出
pq.pop();
std::cout << "Top after pop: " << pq.top() << "\n"; // 7
return 0;
}
```
---
# 四、构造函数(常见形式)
```cpp
priority_queue();
priority_queue(const Compare& comp); // 指定比较函数
priority_queue(const Compare& comp, const Container& cont);
priority_queue(const priority_queue& other);
```
---
# 五、成员函数
| 函数 | 说明 |
| ----------------------- | ------------- |
| `push(const T& val)` | 插入元素 |
| `pop()` | 删除堆顶元素 |
| `top()` | 返回堆顶元素(最大或最小) |
| `empty()` | 判断是否为空 |
| `size()` | 返回元素个数 |
| `emplace(args...)` | 原地构造并插入元素 |
| `swap(priority_queue&)` | 交换两个优先队列 |
---
# 六、默认是大顶堆,怎么做小顶堆?
默认情况下,`priority_queue` 采用 `std::less<T>`,即大顶堆。
**方法1用 `std::greater<T>`**
```cpp
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;
minHeap.push(3);
minHeap.push(10);
minHeap.push(1);
std::cout << minHeap.top() << "\n"; // 1
```
**说明:**
* 第二个模板参数是底层容器,默认是 `std::vector<T>`
* 第三个模板参数是比较器,默认是 `std::less<T>`(大顶堆)
* 改成 `std::greater<T>` 就是小顶堆
---
# 七、自定义比较器
1. **函数对象**
```cpp
struct Compare {
bool operator()(int a, int b) {
return a > b; // 这里定义小顶堆
}
};
std::priority_queue<int, std::vector<int>, Compare> pq;
```
2. **Lambda 表达式C++11 及以上)**
```cpp
auto cmp = [](int left, int right) { return left > right; };
std::priority_queue<int, std::vector<int>, decltype(cmp)> pq(cmp);
pq.push(4);
pq.push(2);
pq.push(8);
std::cout << pq.top() << "\n"; // 2
```
---
# 八、优先队列存储自定义类型
假设你有一个结构体:
```cpp
struct Person {
std::string name;
int age;
};
// 按年龄从大到小排序(大顶堆)
struct ComparePerson {
bool operator()(const Person& a, const Person& b) {
return a.age < b.age; // 年龄大的优先级高
}
};
std::priority_queue<Person, std::vector<Person>, ComparePerson> pq;
```
使用:
```cpp
pq.push({"Alice", 30});
pq.push({"Bob", 25});
pq.push({"Cindy", 35});
auto top = pq.top();
std::cout << top.name << " " << top.age << "\n"; // Cindy 35
```
---
# 九、注意事项和技巧
* `priority_queue` 不能遍历和访问除堆顶以外的元素。
* 如果需要修改某个元素的优先级,通常做法是重新插入修改后的元素,旧元素保留或标记过期。
* `emplace()` 比 `push()` 直接传入构造参数,效率更高。
---
# 十、示例综合
```cpp
#include <iostream>
#include <queue>
#include <vector>
#include <string>
struct Task {
std::string name;
int priority;
};
struct TaskCmp {
bool operator()(const Task& a, const Task& b) {
return a.priority < b.priority; // 大顶堆
}
};
int main() {
std::priority_queue<Task, std::vector<Task>, TaskCmp> tasks;
tasks.emplace(Task{"Task1", 5});
tasks.push({"Task2", 1});
tasks.push({"Task3", 10});
while (!tasks.empty()) {
Task t = tasks.top();
std::cout << t.name << ": " << t.priority << "\n";
tasks.pop();
}
return 0;
}
```
输出:
```
Task3: 10
Task1: 5
Task2: 1
```
---
好的!这里给你整理一个**C++ STL `priority_queue` 的全面使用方法大全**,包含它的构造函数、所有成员函数(含继承自底层容器的限制),以及使用示例和细节说明。
---
# STL `priority_queue` 完整介绍
```cpp
template<
class T,
class Container = std::vector<T>,
class Compare = std::less<typename Container::value_type>
> class priority_queue;
```
* `T`:元素类型
* `Container`:底层容器类型,必须满足随机访问迭代器,默认是 `std::vector<T>`
* `Compare`:排序规则,默认是 `std::less<T>`(大顶堆)
---
# 一、构造函数
| 函数原型 | 说明 |
| ------------------------------------------------------------- | --------------- |
| `priority_queue()` | 默认构造一个空优先队列 |
| `explicit priority_queue(const Compare& comp);` | 使用指定比较器构造空优先队列 |
| `priority_queue(const Compare& comp, const Container& cont);` | 指定比较器和底层容器,拷贝构造 |
| `priority_queue(const priority_queue& other);` | 拷贝构造 |
| `priority_queue(priority_queue&& other) noexcept;` | 移动构造C++11 |
| `priority_queue& operator=(const priority_queue& other);` | 拷贝赋值 |
| `priority_queue& operator=(priority_queue&& other) noexcept;` | 移动赋值C++11 |
---
# 二、成员函数
| 函数原型 | 说明 |
| ------------------------------------------------------- | ------------------ |
| `bool empty() const;` | 判断是否为空 |
| `size_type size() const;` | 返回元素个数 |
| `const_reference top() const;` | 返回堆顶元素(优先级最高元素) |
| `void push(const value_type& val);` | 插入元素 |
| `void push(value_type&& val);` | 插入右值元素移动语义C++11 |
| `template<class... Args> void emplace(Args&&... args);` | 原地构造插入元素C++11 |
| `void pop();` | 弹出堆顶元素 |
| `void swap(priority_queue& other) noexcept;` | 交换两个优先队列 |
| `~priority_queue();` | 析构函数 |
---
# 三、底层容器访问protected
* `priority_queue` 不提供直接访问底层容器的接口,但它有一个受保护成员:
```cpp
protected:
Container c;
```
* 如果你继承 `priority_queue`,可以访问它。
---
# 四、成员变量说明
* `priority_queue` 内部维护了一个满足堆性质的容器 `c`,默认是 `vector<T>`
* 通过调用 `std::push_heap` 和 `std::pop_heap` 保持堆的有序结构。
---
# 五、示例代码
```cpp
#include <iostream>
#include <queue>
#include <vector>
#include <functional> // std::greater
int main() {
// 默认大顶堆
std::priority_queue<int> pq1;
pq1.push(5);
pq1.push(2);
pq1.push(8);
std::cout << "Max element: " << pq1.top() << "\n"; // 8
// 小顶堆,使用 greater 作为比较器
std::priority_queue<int, std::vector<int>, std::greater<int>> pq2;
pq2.push(5);
pq2.push(2);
pq2.push(8);
std::cout << "Min element: " << pq2.top() << "\n"; // 2
// emplace 直接构造
pq1.emplace(10);
std::cout << "Max element after emplace: " << pq1.top() << "\n"; // 10
// pop
pq1.pop();
std::cout << "Max element after pop: " << pq1.top() << "\n"; // 8
return 0;
}
```
---
# 六、使用自定义类型
```cpp
#include <iostream>
#include <queue>
#include <vector>
struct Person {
std::string name;
int age;
};
struct ComparePerson {
bool operator()(const Person& a, const Person& b) {
return a.age < b.age; // 按年龄降序(大顶堆)
}
};
int main() {
std::priority_queue<Person, std::vector<Person>, ComparePerson> pq;
pq.push({"Alice", 30});
pq.push({"Bob", 25});
pq.push({"Cindy", 35});
while (!pq.empty()) {
auto p = pq.top();
std::cout << p.name << " (" << p.age << ")\n";
pq.pop();
}
}
```
---
# 七、底层容器可以替换
默认是 `std::vector<T>`,也可以用 `std::deque<T>`,但必须支持随机访问迭代器。
```cpp
std::priority_queue<int, std::deque<int>> pq;
```
---
# 八、内部实现原理(简述)
* 底层容器是一个随机访问序列,使用 `std::push_heap``std::pop_heap` 来维护堆性质。
* `push()`:先插入底层容器尾部,再调用 `push_heap`。
* `pop()`:调用 `pop_heap` 将最大元素交换到尾部,然后移除尾部元素。
* `top()`:返回底层容器的第一个元素,即堆顶元素。
---
# 九、完整成员函数接口C++标准)
```cpp
// 构造与析构
priority_queue();
explicit priority_queue(const Compare&);
priority_queue(const Compare&, const Container&);
priority_queue(const priority_queue&);
priority_queue(priority_queue&&) noexcept;
~priority_queue();
// 赋值
priority_queue& operator=(const priority_queue&);
priority_queue& operator=(priority_queue&&) noexcept;
// 访问
bool empty() const;
size_type size() const;
const_reference top() const;
// 修改
void push(const value_type&);
void push(value_type&&);
template <class... Args>
void emplace(Args&&...);
void pop();
void swap(priority_queue&) noexcept;
```
---
# 十、注意点
* 只能访问堆顶元素,不能遍历优先队列中的所有元素。
* 如果需要修改队列中间元素的优先级,通常要重新插入,不能直接修改。
* `emplace` 可以避免不必要的复制,提高效率。
* 默认是大顶堆,如果要小顶堆,改比较器为 `std::greater` 或自定义。
---
如果你需要,我可以帮你写详细代码示例,或者帮你写自定义比较器的示例!

View File

@@ -0,0 +1,43 @@
#include <queue>
#include <functional>
#include <iostream>
using namespace std;
bool cmp(const int& a, const int& b) {
return a < b;
}
class compare {
public:
template <typename T>
bool operator()(const T& a, const T& b) {
//返回大的,泛型不能直接返回指针,没有初始化
//用struct或者class套一下在class初始化的过程中就顺带着初始化了
return a < b;//false是向上swap
}
};
int main() {
auto cmpl = []<class T>(const T& a, const T& b) {
return a < b;
};
//priority_queue<int, deque<int>, decltype(cmpl)> pq(cmpl);合法
//priority_queue<int, deque<int>, bool(*)(const int&, const int&)> pq(cmp);合法
priority_queue<int, deque<int>, compare> pq;
//上面三种写法非常重要
pq.push(1);
pq.push(2);
pq.push(6);
pq.push(3);
pq.push(7);
pq.push(4);
while (!pq.empty()) {
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
return 0;
}
/*
7 6 4 3 2 1
*/

108
特性/auto自动推导.MD Normal file
View File

@@ -0,0 +1,108 @@
好,那我就帮你系统性地整理一份 **C++ 中 `auto` 与 lambda 函数的知识大全**,把语法细节、坑点、进阶用法都讲全,让你能用到开发级别。
我会先分两部分:
1. `auto` 关键字大全
2. Lambda 函数大全
最后会给出它们的 **联合应用技巧**。
---
## 1. `auto` 关键字大全
### 1.1 基础概念
* **作用**:让编译器自动推导变量、函数返回值、模板参数的类型。
* **推导时间**:编译期完成(不影响运行时性能)。
* **核心限制**
* 必须有初始化值(除非是函数返回类型或形参声明中)。
* 不能推导为 **数组类型**(会退化为指针)和 **函数类型**(会退化为函数指针)。
---
### 1.2 基本用法
```cpp
auto x = 10; // int
auto y = 3.14; // double
auto ptr = &x; // int*
auto str = "Hello"; // const char*
```
---
### 1.3 `auto` 推导规则
假设初始化表达式类型为 `T`
1. **普通变量**:直接推导为 `T`。
2. **有引用**
```cpp
int a = 5;
int& r = a;
auto x = r; // int引用被忽略
```
3. **有 const**
```cpp
const int a = 5;
auto x = a; // int顶层 const 被忽略)
```
4. **保留引用和 const**:需要 `auto&` / `const auto&`
```cpp
const int a = 5;
auto& r = a; // const int&
const auto& cr = a; // const int&
```
---
### 1.4 特殊形式
* **`auto*`**:明确推导为指针类型
* **`decltype(auto)`**保留完整类型信息包括引用、const
```cpp
int x = 1;
int& r = x;
auto a = r; // int
decltype(auto) b = r; // int&
```
---
### 1.5 在函数中
* **返回类型推导**
```cpp
auto add(int a, int b) { return a + b; } // 返回 int
```
* **尾返回类型**
```cpp
template<class T, class U>
auto add(T a, U b) -> decltype(a+b) { return a+b; }
```
---
### 1.6 常见坑
* **不同返回类型不能推导**
```cpp
auto test(bool b) {
if (b) return 1; // int
else return 1.5; // double
} // ❌ 编译错误
```
* **列表初始化可能推导成 `std::initializer_list`**
```cpp
auto x = {1, 2, 3}; // std::initializer_list<int>
```
---

138
特性/lambda函数.MD Normal file
View File

@@ -0,0 +1,138 @@
## 2. Lambda 函数大全
### 2.1 基础概念
* **定义**一种匿名函数对象closure object
* **语法结构**
```cpp
[捕获列表] (参数列表) -> 返回类型 { 函数体 }
```
* **返回类型可省略**:由 `return` 推导。
---
### 2.2 捕获方式
假设外部有 `int a; int b;`
| 捕获方式 | 含义 |
| ----------- | ------------------- |
| `[=]` | 按值捕获所有外部变量(只读) |
| `[&]` | 按引用捕获所有外部变量 |
| `[a]` | 按值捕获变量 `a` |
| `[&a]` | 按引用捕获变量 `a` |
| `[=, &b]` | 默认值捕获,单独指定引用捕获某些变量 |
| `[&, a]` | 默认引用捕获,单独指定按值捕获某些变量 |
| `[this]` | 捕获当前对象指针(隐式捕获成员变量) |
| `[=, this]` | 按值捕获外部变量,同时捕获 this |
---
### 2.3 可变 Lambda (`mutable`)
* 按值捕获默认是 **不可修改捕获变量副本**
* 加 `mutable` 可以修改副本
```cpp
int x = 5;
auto f = [=]() mutable { x += 1; }; // 修改的是副本
```
---
### 2.4 泛型 LambdaC++14+
```cpp
auto add = [](auto a, auto b) { return a + b; };
```
```cpp
C++20
auto add = []<class T>(const T& a, const T& b) { return a + b; };
```
相当于模板函数。
---
### 2.5 返回类型
* **推导**C++14+ 支持多返回类型推导一致性检查)
* **显式声明**
```cpp
auto div = [](double a, double b) -> double { return a/b; };
```
---
### 2.6 存储与调用
* **立即调用**
```cpp
[]{ std::cout << "Hi\n"; }();
```
* **存储到变量**
```cpp
auto f = [](int x){ return x*x; };
std::cout << f(5);
```
---
### 2.7 转换为函数指针
* 仅无捕获 Lambda 可转换:
```cpp
void (*fp)(int) = [](int x){ std::cout << x; };
```
---
### 2.8 在 STL 中用作比较器
```cpp
auto cmp = [](int a, int b) { return a > b; };
std::priority_queue<int, std::vector<int>, decltype(cmp)> pq(cmp);
```
---
## 3. `auto` + Lambda 联合技巧
1. **类型保存**(比较器、回调等):
```cpp
auto cmp = [](int a, int b){ return a < b; };
std::set<int, decltype(cmp)> s(cmp);
```
2. **推导返回类型**(泛型 + `auto`
```cpp
auto combine = [](auto a, auto b) { return a + b; };
```
3. **模板中传 Lambda**
```cpp
template<class F>
void run(F f) { f(); }
run([]{ std::cout << "run!\n"; });
```
4. **`decltype(auto)` 保持引用**
```cpp
auto getter = [](auto& c, size_t i) -> decltype(auto) { return c[i]; };
```
---
如果你愿意,我可以给你画一个 **`auto` 与 Lambda 的知识地图**,把所有用法和推导规则可视化,这样你看一次就能记住。这样对理解捕获机制和类型推导特别直观。
你要我画吗?这样你可以一眼就看全流程。