diff --git a/STL/STL-Set/README.MD b/STL/STL-Set/README.MD index e413887..da5e2e9 100644 --- a/STL/STL-Set/README.MD +++ b/STL/STL-Set/README.MD @@ -204,3 +204,301 @@ if (auto [it, ok] = s.insert(10); ok) { * 🧩 `unordered_set` 的比较与选择策略 是否要我继续讲 `multiset` 和 `unordered_set`? + + +

C++ STL set容器完全攻略(超级详细)

+
 
+
前面章节讲解了 map 容器和 multimap 容器的用法,类似地,C++ STL 标准库中还提供有 set 和 multiset 这 2 个容器,它们也属于关联式容器。不过,本节先讲解 set 容器,后续章节再讲解 multiset 容器。
+
+和 map、multimap 容器不同,使用 set 容器存储的各个键值对,要求键 key 和值 value 必须相等。
+
+举个例子,如下有 2 组键值对数据: +

+ {<'a', 1>, <'b', 2>, <'c', 3>}
+ {<'a', 'a'>, <'b', 'b'>, <'c', 'c'>}

+显然,第一组数据中各键值对的键和值不相等,而第二组中各键值对的键和值对应相等。对于 set 容器来说,只能存储第 2 组键值对,而无法存储第一组键值对。
+
+基于 set 容器的这种特性,当使用 set 容器存储键值对时,只需要为其提供各键值对中的 value 值(也就是 key 的值)即可。仍以存储上面第 2 组键值对为例,只需要为 set 容器提供 {'a','b','c'} ,该容器即可成功将它们存储起来。
+
+通过前面的学习我们知道,map、multimap 容器都会自行根据键的大小对存储的键值对进行排序,set 容器也会如此,只不过 set 容器中各键值对的键 key 和值 value 是相等的,根据 key 排序,也就等价为根据 value 排序。
+
+另外,使用 set 容器存储的各个元素的值必须各不相同。更重要的是,从语法上讲 set 容器并没有强制对存储元素的类型做 const 修饰,即 set 容器中存储的元素的值是可以修改的。但是,C++ 标准为了防止用户修改容器中元素的值,对所有可能会实现此操作的行为做了限制,使得在正常情况下,用户是无法做到修改 set 容器中元素的值的。
+
+

+ 对于初学者来说,切勿尝试直接修改 set 容器中已存储元素的值,这很有可能破坏 set 容器中元素的有序性,最正确的修改 set 容器中元素值的做法是:先删除该元素,然后再添加一个修改后的元素。

+
+值得一提的是,set 容器定义于<set>头文件,并位于 std 命名空间中。因此如果想在程序中使用 set 容器,该程序代码应先包含如下语句: +
+#include <set>
+using namespace std;
+注意,第二行代码不是必需的,如果不用,则后续程序中在使用 set 容器时,需手动注明 std 命名空间(强烈建议初学者使用)。
+
+set 容器的类模板定义如下: +
+template < class T,                        // 键 key 和值 value 的类型
+           class Compare = less<T>,        // 指定 set 容器内部的排序规则
+           class Alloc = allocator<T>      // 指定分配器对象的类型
+           > class set;
+注意,由于 set 容器存储的各个键值对,其键和值完全相同,也就意味着它们的类型相同,因此 set 容器类模板的定义中,仅有第 1 个参数用于设定存储数据的类型。
+
+

+ 对于 set 类模板中的 3 个参数,后 2 个参数自带默认值,且几乎所有场景中只需使用前 2 个参数,第 3 个参数不会用到。

+
+

+ 创建C++ set容器的几种方法

+常见的创建 set 容器的方法,大致有以下 5 种。
+
+1) 调用默认构造函数,创建空的 set 容器。比如: +
+std::set<std::string> myset;
+
+

+ 如果程序中已经默认指定了 std 命令空间,这里可以省略 std::。

+
+由此就创建好了一个 set 容器,该容器采用默认的std::less<T>规则,会对存储的 string 类型元素做升序排序。注意,由于 set 容器支持随时向内部添加新的元素,因此创建空 set 容器的方法是经常使用的。
+
+2) 除此之外,set 类模板还支持在创建 set 容器的同时,对其进行初始化。例如: +
+std::set<std::string> myset{"http://c.biancheng.net/java/",
+                            "http://c.biancheng.net/stl/",
+                            "http://c.biancheng.net/python/"};
+由此即创建好了包含 3 个 string 元素的 myset 容器。由于其采用默认的 std::less<T> 规则,因此其内部存储 string 元素的顺序如下所示: +

+ "http://c.biancheng.net/java/"
+ "http://c.biancheng.net/python/"
+ "http://c.biancheng.net/stl/"

+
+3) set 类模板中还提供了拷贝(复制)构造函数,可以实现在创建新 set 容器的同时,将已有 set 容器中存储的所有元素全部复制到新 set 容器中。
+
+例如,在第 2 种方式创建的 myset 容器的基础上,执行如下代码: +
+std::set<std::string> copyset(myset);
+//等同于
+//std::set<std::string> copyset = myset
+该行代码在创建 copyset 容器的基础上,还会将 myset 容器中存储的所有元素,全部复制给 copyset 容器一份。
+
+另外,C++ 11 标准还为 set 类模板新增了移动构造函数,其功能是实现创建新 set 容器的同时,利用临时的 set 容器为其初始化。比如: +
+set<string> retSet() {
+    std::set<std::string> myset{ "http://c.biancheng.net/java/",
+                            "http://c.biancheng.net/stl/",
+                            "http://c.biancheng.net/python/" };
+    return myset;
+}
+std::set<std::string> copyset(retSet());
+//或者
+//std::set<std::string> copyset = retSet();
+

+ 注意,由于 retSet() 函数的返回值是一个临时 set 容器,因此在初始化 copyset 容器时,其内部调用的是 set 类模板中的移动构造函数,而非拷贝构造函数。

+
+

+ 显然,无论是调用复制构造函数还是调用拷贝构造函数,都必须保证这 2 个容器的类型完全一致。

+
+
+4) 在第 3 种方式的基础上,set 类模板还支持取已有 set 容器中的部分元素,来初始化新 set 容器。例如: +
+std::set<std::string> myset{ "http://c.biancheng.net/java/",
+                    "http://c.biancheng.net/stl/",
+                    "http://c.biancheng.net/python/" };
+std::set<std::string> copyset(++myset.begin(), myset.end());
+由此初始化的 copyset 容器,其内部仅存有如下 2 个 string 字符串: +

+ "http://c.biancheng.net/python/"
+ "http://c.biancheng.net/stl/"

+
+5) 以上几种方式创建的 set 容器,都采用了默认的std::less<T>规则。其实,借助 set 类模板定义中第 2 个参数,我们完全可以手动修改 set 容器中的排序规则。比如: +
+std::set<std::string,std::greater<string> > myset{
+    "http://c.biancheng.net/java/",
+    "http://c.biancheng.net/stl/",
+    "http://c.biancheng.net/python/"};
+通过选用 std::greater<string> 降序规则,myset 容器中元素的存储顺序为: +

+ "http://c.biancheng.net/stl/"
+ "http://c.biancheng.net/python/"
+ "http://c.biancheng.net/java/"

+

+ C++ STL set容器包含的成员方法

+表 1 列出了 set 容器提供的常用成员方法以及各自的功能。
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ 表 1 C++ set 容器常用成员方法
+ 成员方法 + 功能
+ begin() + 返回指向容器中第一个(注意,是已排好序的第一个)元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
+ end() + 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
+ rbegin() + 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
+ rend() + 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
+ cbegin() + 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
+ cend() + 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
+ crbegin() + 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
+ crend() + 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
+ find(val) + 在 set 容器中查找值为 val 的元素,如果成功找到,则返回指向该元素的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
+ lower_bound(val) + 返回一个指向当前 set 容器中第一个大于或等于 val 的元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
+ upper_bound(val) + 返回一个指向当前 set 容器中第一个大于 val 的元素的迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
+ equal_range(val) + 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的值为 val 的元素(set 容器中各个元素是唯一的,因此该范围最多包含一个元素)。
+ empty() + 若容器为空,则返回 true;否则 false。
+ size() + 返回当前 set 容器中存有元素的个数。
+ max_size() + 返回 set 容器所能容纳元素的最大个数,不同的操作系统,其返回值亦不相同。
+ insert() + 向 set 容器中插入元素。
+ erase() + 删除 set 容器中存储的元素。
+ swap() + 交换 2 个 set 容器中存储的所有元素。这意味着,操作的 2 个 set 容器的类型必须相同。
+ clear() + 清空 set 容器中所有的元素,即令 set 容器的 size() 为 0。
+ emplace() + 在当前 set 容器中的指定位置直接构造新元素。其效果和 insert() 一样,但效率更高。
+ emplace_hint() + 在本质上和 emplace() 在 set 容器中构造新元素的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示新元素生成位置的迭代器,并作为该方法的第一个参数。
+ count(val) + 在当前 set 容器中,查找值为 val 的元素的个数,并返回。注意,由于 set 容器中各元素的值是唯一的,因此该函数的返回值最大为 1。
+
+下面程序演示了表 1 中部分成员函数的用法: +
+#include <iostream>
+#include <set>
+#include <string>
+using namespace std;
+
+int main()
+{
+    //创建空set容器
+    std::set<std::string> myset;
+    //空set容器不存储任何元素
+    cout << "1、myset size = " << myset.size() << endl;
+    //向myset容器中插入新元素
+    myset.insert("http://c.biancheng.net/java/");
+    myset.insert("http://c.biancheng.net/stl/");
+    myset.insert("http://c.biancheng.net/python/");
+    cout << "2、myset size = " << myset.size() << endl;
+    //利用双向迭代器,遍历myset
+    for (auto iter = myset.begin(); iter != myset.end(); ++iter) {
+        cout << *iter << endl;
+    }
+    return 0;
+}
+程序执行结果为: +

+ 1、myset size = 0
+ 2、myset size = 3
+ http://c.biancheng.net/java/
+ http://c.biancheng.net/python/
+ http://c.biancheng.net/stl/

+
+

+ 有关表 1 中其它成员方法的用法,后续章节会做详细讲解。

+
+
\ No newline at end of file diff --git a/STL/STL-Set/set.cpp b/STL/STL-Set/set.cpp new file mode 100644 index 0000000..ebf0c33 --- /dev/null +++ b/STL/STL-Set/set.cpp @@ -0,0 +1,45 @@ +#include +#include +#include +using namespace std; +int main(){ + auto cmp = [](auto a, auto b){ + return a > b; + }; + set a; + set b = {1, 5, 4, 3, 3, 2}; + set 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 +*/ diff --git a/STL/STL-Set/set.exe b/STL/STL-Set/set.exe new file mode 100644 index 0000000..21ae0b7 Binary files /dev/null and b/STL/STL-Set/set.exe differ diff --git a/STL/STL-map/README.MD b/STL/STL-map/README.MD new file mode 100644 index 0000000..0b01f1f --- /dev/null +++ b/STL/STL-map/README.MD @@ -0,0 +1,303 @@ +

C++ STL map容器详解

+
 
+
作为关联式容器的一种,map 容器存储的都是 pair 对象,也就是用 pair 类模板创建的键值对。其中,各个键值对的键和值可以是任意数据类型,包括 C++ 基本数据类型(int、double 等)、使用结构体或类自定义的类型。
+
+

+ 通常情况下,map 容器中存储的各个键值对都选用 string 字符串作为键的类型。

+
+与此同时,在使用 map 容器存储多个键值对时,该容器会自动根据各键值对的键的大小,按照既定的规则进行排序。默认情况下,map 容器选用std::less<T>排序规则(其中 T 表示键的数据类型),其会根据键的大小对所有键值对做升序排序。当然,根据实际情况的需要,我们可以手动指定 map 容器的排序规则,既可以选用 STL 标准库中提供的其它排序规则(比如std::greater<T>),也可以自定义排序规则。
+
+

+ 关于如何自定义 map 容器的排序规则,后续章节会做详细讲解。

+
+另外需要注意的是,使用 map 容器存储的各个键值对,键的值既不能重复也不能被修改。换句话说,map 容器中存储的各个键值对不仅键的值独一无二,键的类型也会用 const 修饰,这意味着只要键值对被存储到 map 容器中,其键的值将不能再做任何修改。 +
+

+ 前面提到,map 容器存储的都是 pair 类型的键值对元素,更确切的说,该容器存储的都是 pair<const K, T> 类型(其中 K 和 T 分别表示键和值的数据类型)的键值对元素。

+
+map 容器定义在 <map> 头文件中,并位于 std 命名空间中。因此,如果想使用 map 容器,代码中应包含如下语句: +
+#include <map>
+using namespace std;
+
+

+ 注意,第二行代码不是必需的,如果不用,则后续程序中在使用 map 容器时,需手动注明 std 命名空间(强烈建议初学者使用)。

+
+map 容器的模板定义如下: +
+template < class Key,  // 指定键(key)的类型
+           class T,   // 指定值(value)的类型
+           class Compare = less<Key>, // 指定排序规则
+           class Alloc = allocator<pair<const Key,T> >   // 指定分配器对象的类型
+           > class map;
+可以看到,map 容器模板有 4 个参数,其中后 2 个参数都设有默认值。大多数场景中,我们只需要设定前 2 个参数的值,有些场景可能会用到第 3 个参数,但最后一个参数几乎不会用到。
+

+ 创建C++ map容器的几种方法

+map 容器的模板类中包含多种构造函数,因此创建 map 容器的方式也有多种,下面就几种常用的创建 map 容器的方法,做一一讲解。
+
+1) 通过调用 map 容器类的默认构造函数,可以创建出一个空的 map 容器,比如: +
+std::map<std::string, int>myMap;
+
+

+ 如果程序中已经默认指定了 std 命令空间,这里可以省略 std::

+
+通过此方式创建出的 myMap 容器,初始状态下是空的,即没有存储任何键值对。鉴于空 map 容器可以根据需要随时添加新的键值对,因此创建空 map 容器是比较常用的。
+
+2) 当然在创建 map 容器的同时,也可以进行初始化,比如: +
+std::map<std::string, int>myMap{ {"C语言教程",10},{"STL教程",20} };
+由此,myMap 容器在初始状态下,就包含有 2 个键值对。
+
+再次强调,map 容器中存储的键值对,其本质都是 pair 类模板创建的 pair 对象。因此,下面程序也可以创建出一模一样的 myMap 容器:
+
+std::map<std::string, int>myMap{std::make_pair("C语言教程",10),std::make_pair("STL教程",20)};
+
+3) 除此之外,在某些场景中,可以利用先前已创建好的 map 容器,再创建一个新的 map 容器。例如: +
+std::map<std::string, int>newMap(myMap);
+由此,通过调用 map 容器的拷贝(复制)构造函数,即可成功创建一个和 myMap 完全一样的 newMap 容器。
+
+C++ 11 标准中,还为 map 容器增添了移动构造函数。当有临时的 map 对象作为参数,传递给要初始化的 map 容器时,此时就会调用移动构造函数。举个例子: +
+#创建一个会返回临时 map 对象的函数
+std::map<std::string,int> disMap() {
+    std::map<std::string, int>tempMap{ {"C语言教程",10},{"STL教程",20} };
+    return tempMap;
+}
+//调用 map 类模板的移动构造函数创建 newMap 容器
+std::map<std::string, int>newMap(disMap());
+
+

+ 注意,无论是调用复制构造函数还是调用拷贝构造函数,都必须保证这 2 个容器的类型完全一致。

+
+
+4) map 类模板还支持取已建 map 容器中指定区域内的键值对,创建并初始化新的 map 容器。例如: +
+std::map<std::string, int>myMap{ {"C语言教程",10},{"STL教程",20} };
+std::map<std::string, int>newMap(++myMap.begin(), myMap.end());
+这里,通过调用 map 容器的双向迭代器,实现了在创建 newMap 容器的同时,将其初始化为包含一个 {"STL教程",20} 键值对的容器。
+
+

+ 有关 map 容器迭代器,后续章节会做详细讲解。

+
+
+5) 当然,在以上几种创建 map 容器的基础上,我们都可以手动修改 map 容器的排序规则。默认情况下,map 容器调用 std::less<T> 规则,根据容器内各键值对的键的大小,对所有键值对做升序排序。
+
+因此,如下 2 行创建 map 容器的方式,其实是等价的: +
+std::map<std::string, int>myMap{ {"C语言教程",10},{"STL教程",20} };
+std::map<std::string, int, std::less<std::string> >myMap{ {"C语言教程",10},{"STL教程",20} };
+以上 2 中创建方式生成的 myMap 容器,其内部键值对排列的顺序为: +

+ <"C语言教程", 10>
+ <"STL教程", 20>

+
+下面程序手动修改了 myMap 容器的排序规则,令其作降序排序: +
+std::map<std::string, int, std::greater<std::string> >myMap{ {"C语言教程",10},{"STL教程",20} };
+此时,myMap 容器内部键值对排列的顺序为: +

+ <"STL教程", 20>
+ <"C语言教程", 10>

+
+

+ 在某些特定场景中,我们还需要为 map 容器自定义排序规则,此部分知识后续将利用整整一节做重点讲解。

+
+

+ C++ map容器包含的成员方法

+表 1 列出了 map 容器提供的常用成员方法以及各自的功能。
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ 表 1 C++ map容器常用成员方法
+ 成员方法 + 功能
+ begin() + 返回指向容器中第一个(注意,是已排好序的第一个)键值对的双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
+ end() + 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
+ rbegin() + 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
+ rend() + 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
+ cbegin() + 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
+ cend() + 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
+ crbegin() + 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
+ crend() + 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
+ find(key) + 在 map 容器中查找键为 key 的键值对,如果成功找到,则返回指向该键值对的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
+ lower_bound(key) + 返回一个指向当前 map 容器中第一个大于或等于 key 的键值对的双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
+ upper_bound(key) + 返回一个指向当前 map 容器中第一个大于 key 的键值对的迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
+ equal_range(key) + 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的键为 key 的键值对(map 容器键值对唯一,因此该范围最多包含一个键值对)。
+ empty()  + 若容器为空,则返回 true;否则 false。
+ size() + 返回当前 map 容器中存有键值对的个数。
+ max_size() + 返回 map 容器所能容纳键值对的最大个数,不同的操作系统,其返回值亦不相同。
+ operator[] + map容器重载了 [] 运算符,只要知道 map 容器中某个键值对的键的值,就可以向获取数组中元素那样,通过键直接获取对应的值。
+ at(key) + 找到 map 容器中 key 键对应的值,如果找不到,该函数会引发 out_of_range 异常。
+ insert() + 向 map 容器中插入键值对。
+ erase() + 删除 map 容器指定位置、指定键(key)值或者指定区域内的键值对。后续章节还会对该方法做重点讲解。
+ swap() + 交换 2 个 map 容器中存储的键值对,这意味着,操作的 2 个键值对的类型必须相同。
+ clear() + 清空 map 容器中所有的键值对,即使 map 容器的 size() 为 0。
+ emplace() + 在当前 map 容器中的指定位置处构造新键值对。其效果和插入键值对一样,但效率更高。
+ emplace_hint() + 在本质上和 emplace() 在 map 容器中构造新键值对的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示键值对生成位置的迭代器,并作为该方法的第一个参数。
+ count(key) + 在当前 map 容器中,查找键为 key 的键值对的个数并返回。注意,由于 map 容器中各键值对的键的值是唯一的,因此该函数的返回值最大为 1。
+
+下面的样例演示了表 1 中部分成员方法的用法:
+
+#include <iostream>
+#include <map>      // map
+#include <string>       // string
+using namespace std;
+
+int main() {
+    //创建空 map 容器,默认根据个键值对中键的值,对键值对做降序排序
+    std::map<std::string, std::string, std::greater<std::string>>myMap;
+    //调用 emplace() 方法,直接向 myMap 容器中指定位置构造新键值对
+    myMap.emplace("C语言教程","http://c.biancheng.net/c/");
+    myMap.emplace("Python教程", "http://c.biancheng.net/python/");
+    myMap.emplace("STL教程", "http://c.biancheng.net/stl/");
+    //输出当前 myMap 容器存储键值对的个数
+    cout << "myMap size==" << myMap.size() << endl;
+    //判断当前 myMap 容器是否为空
+    if (!myMap.empty()) {
+        //借助 myMap 容器迭代器,将该容器的键值对逐个输出
+        for (auto i = myMap.begin(); i != myMap.end(); ++i) {
+            cout << i->first << " " << i->second << endl;
+        }
+    }  
+    return 0;
+}
+程序执行结果为: +

+ myMap size==3
+ STL教程 http://c.biancheng.net/stl/
+ Python教程 http://c.biancheng.net/python/
+ C语言教程 http://c.biancheng.net/c/

+
+

+ 有关表 1 中其它成员函数的用法,后续章节会做详细详解。

+
+
\ No newline at end of file diff --git a/STL/STL-map/map.cpp b/STL/STL-map/map.cpp new file mode 100644 index 0000000..9c272c4 --- /dev/null +++ b/STL/STL-map/map.cpp @@ -0,0 +1,7 @@ +#include +#include +using namespace std; +int main(int argc, char* argv[]){ + + return 0; +} diff --git a/STL/STL-priority_queue/README.MD b/STL/STL-priority_queue/README.MD new file mode 100644 index 0000000..8992bae --- /dev/null +++ b/STL/STL-priority_queue/README.MD @@ -0,0 +1,421 @@ +好的!我来给你详细介绍一下 C++ STL 里的 `priority_queue` 容器适配器的用法和常见技巧,帮你全面掌握它。 + +--- + +# 一、什么是 `priority_queue`? + +`priority_queue` 是 STL 提供的一个容器适配器,底层默认使用堆(heap)数据结构实现,保证访问时总能快速取得最大(默认)或最小元素。 + +它类似于一个“优先级队列”,总是先弹出优先级最高(优先级依据元素大小)的元素。 + +--- + +# 二、包含头文件 + +```cpp +#include +``` + +`priority_queue` 位于 `` 头文件中。 + +--- + +# 三、定义和基本用法 + +```cpp +#include +#include + +int main() { + std::priority_queue 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`,即大顶堆。 + +**方法1:用 `std::greater`** + +```cpp +std::priority_queue, std::greater> minHeap; + +minHeap.push(3); +minHeap.push(10); +minHeap.push(1); + +std::cout << minHeap.top() << "\n"; // 1 +``` + +**说明:** + +* 第二个模板参数是底层容器,默认是 `std::vector` +* 第三个模板参数是比较器,默认是 `std::less`(大顶堆) +* 改成 `std::greater` 就是小顶堆 + +--- + +# 七、自定义比较器 + +1. **函数对象** + +```cpp +struct Compare { + bool operator()(int a, int b) { + return a > b; // 这里定义小顶堆 + } +}; + +std::priority_queue, Compare> pq; +``` + +2. **Lambda 表达式(C++11 及以上)** + +```cpp +auto cmp = [](int left, int right) { return left > right; }; +std::priority_queue, 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, 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 +#include +#include +#include + +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, 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, + class Compare = std::less +> class priority_queue; +``` + +* `T`:元素类型 +* `Container`:底层容器类型,必须满足随机访问迭代器,默认是 `std::vector` +* `Compare`:排序规则,默认是 `std::less`(大顶堆) + +--- + +# 一、构造函数 + +| 函数原型 | 说明 | +| ------------------------------------------------------------- | --------------- | +| `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 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` +* 通过调用 `std::push_heap` 和 `std::pop_heap` 保持堆的有序结构。 + +--- + +# 五、示例代码 + +```cpp +#include +#include +#include +#include // std::greater + +int main() { + // 默认大顶堆 + std::priority_queue pq1; + pq1.push(5); + pq1.push(2); + pq1.push(8); + std::cout << "Max element: " << pq1.top() << "\n"; // 8 + + // 小顶堆,使用 greater 作为比较器 + std::priority_queue, std::greater> 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 +#include +#include + +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, 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`,也可以用 `std::deque`,但必须支持随机访问迭代器。 + +```cpp +std::priority_queue> 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 +void emplace(Args&&...); +void pop(); +void swap(priority_queue&) noexcept; +``` + +--- + +# 十、注意点 + +* 只能访问堆顶元素,不能遍历优先队列中的所有元素。 +* 如果需要修改队列中间元素的优先级,通常要重新插入,不能直接修改。 +* `emplace` 可以避免不必要的复制,提高效率。 +* 默认是大顶堆,如果要小顶堆,改比较器为 `std::greater` 或自定义。 + +--- + +如果你需要,我可以帮你写详细代码示例,或者帮你写自定义比较器的示例! + diff --git a/STL/STL-priority_queue/main.cpp b/STL/STL-priority_queue/main.cpp new file mode 100644 index 0000000..fbaa38d --- /dev/null +++ b/STL/STL-priority_queue/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include +using namespace std; + +bool cmp(const int& a, const int& b) { + return a < b; +} + +class compare { +public: + template + bool operator()(const T& a, const T& b) { + //返回大的,泛型不能直接返回指针,没有初始化 + //用struct或者class套一下,在class初始化的过程中就顺带着初始化了 + return a < b;//false是向上swap + } +}; + +int main() { + auto cmpl = [](const T& a, const T& b) { + return a < b; + }; + //priority_queue, decltype(cmpl)> pq(cmpl);合法 + //priority_queue, bool(*)(const int&, const int&)> pq(cmp);合法 + priority_queue, 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 + */ \ No newline at end of file diff --git a/特性/auto自动推导.MD b/特性/auto自动推导.MD new file mode 100644 index 0000000..383bd67 --- /dev/null +++ b/特性/auto自动推导.MD @@ -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 + 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 + ``` + +--- diff --git a/特性/lambda函数.MD b/特性/lambda函数.MD new file mode 100644 index 0000000..c491031 --- /dev/null +++ b/特性/lambda函数.MD @@ -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 泛型 Lambda(C++14+) + +```cpp +auto add = [](auto a, auto b) { return a + b; }; +``` + +```cpp +C++20 +auto add = [](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, decltype(cmp)> pq(cmp); +``` + +--- + +## 3. `auto` + Lambda 联合技巧 + +1. **类型保存**(比较器、回调等): + + ```cpp + auto cmp = [](int a, int b){ return a < b; }; + std::set s(cmp); + ``` + +2. **推导返回类型**(泛型 + `auto`): + + ```cpp + auto combine = [](auto a, auto b) { return a + b; }; + ``` + +3. **模板中传 Lambda**: + + ```cpp + template + 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 的知识地图**,把所有用法和推导规则可视化,这样你看一次就能记住。这样对理解捕获机制和类型推导特别直观。 +你要我画吗?这样你可以一眼就看全流程。