本文最后更新于:December 3, 2021 pm
记录STL提供的有关容器的函数及其用法。此片博客内容较多,篇幅较长。
目录 一.vector 添加元素的两种函数 push_back()函数 该成员函数的功能是在 vector 容器 尾部 添加一个元素,用法也非常简单。简单举例:
#include <iostream> #include <vector> using namespace std ;int main () { vector <int > values{}; values.push_back(1 ); values.push_back(2 ); for (int i = 0 ; i < values.size(); i++) { cout << values[i] << " " ; } return 0 ; }1 2
emplace_back()函数 该函数是 C++ 11 新增加的,其功能和 push_back() 相同,都是在 vector 容器的尾部添加一个元素。简单例子:
#include <iostream> #include <vector> using namespace std ;int main () { vector <int > values{}; values.emplace_back(1 ); values.emplace_back(2 ); for (int i = 0 ; i < values.size(); i++) { cout << values[i] << " " ; } return 0 ; }1 2
以上 2 段代码,只是用 emplace_back() 替换了 push_back(),它们实现的功能是一样的。
那么 C++ 11 标准中为什么要多此一举呢? 在于他们还是有一定的区别。
区别 emplace_back() 和 push_back() 的区别,就在于底层实现的机制不同。push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。
用例子了解它们之间的区别:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <vector> #include <iostream> using namespace std ;class testDemo { public : testDemo(int num):num(num){ std ::cout << "调用构造函数" << endl ; } testDemo(const testDemo& other) :num(other.num) { std ::cout << "调用拷贝构造函数" << endl ; } testDemo(testDemo&& other) :num(other.num) { std ::cout << "调用移动构造函数" << endl ; }private : int num; };int main () { cout << "emplace_back:" << endl ; std ::vector <testDemo> demo1; demo1.emplace_back(2 ); cout << "push_back:" << endl ; std ::vector <testDemo> demo2; demo2.push_back(2 ); } emplace_back: 调用构造函数 push_back: 调用构造函数 调用移动构造函数 emplace_back: 调用构造函数 push_back: 调用构造函数 调用拷贝构造函数
可以看出,push_back() 在底层实现时,会优先选择调用移动构造函数,如果没有才会调用拷贝构造函数。显然完成同样的操作,push_back() 的底层实现过程比 emplace_back() 更繁琐,换句话说,emplace_back() 的执行效率比 push_back() 高。因此,在实际使用时,建议还是优先选用 emplace_back()。
由于 emplace_back() 是 C++ 11 标准新增加的,如果程序要兼顾之前的版本,还是应该使用 push_back()。
二.vector 插入元素的两种函数 vector容器提供了 insert() 和 emplace() 这 2 个成员函数,用来实现在容器指定位置处插入元素。
insert()函数 insert() 函数的功能是在 vector 容器的指定位置插入一个或多个元素。该函数的语法格式有多种.
语法格式
用法说明
iterator insert(pos,elem)
在迭代器 pos 指定的位置之前插入一个新元素elem,并返回表示新插入元素位置的迭代器。
iterator insert(pos,n,elem)
在迭代器 pos 指定的位置之前插入 n 个元素 elem,并返回表示第一个新插入元素位置的迭代器。
iterator insert(pos,first,last)
在迭代器 pos 指定的位置之前,插入其他容器(不仅限于vector)中位于 [first,last) 区域的所有元素,并返回表示第一个新插入元素位置的迭代器。
iterator insert(pos,initlist)
在迭代器 pos 指定的位置之前,插入初始化列表(用大括号{}括起来的多个元素,中间有逗号隔开)中所有的元素,并返回表示第一个新插入元素位置的迭代器。
演示使用 insert() 函数向 vector 容器中插入元素:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> #include <vector> #include <array> using namespace std ;int main () { std ::vector <int > demo{1 ,2 }; demo.insert(demo.begin() + 1 , 3 ); demo.insert(demo.end(), 2 , 5 ); std ::array <int ,3>test{ 7 ,8 ,9 }; demo.insert(demo.end(), test.begin(), test.end()); demo.insert(demo.end(), { 10 ,11 }); for (int i = 0 ; i < demo.size(); i++) { cout << demo[i] << " " ; } return 0 ; }1 3 2 5 5 7 8 9 10 11
emplace()函数 emplace() 是 C++ 11 标准新增加的成员函数,用于在 vector 容器指定位置之前插入一个新的元素。emplace() 每次只能插入一个元素,而不是多个。
该函数的语法格式:
iterator emplace (const_iterator pos, args...) ;
其中,pos 为指定插入位置的迭代器;args… 表示与新插入元素的构造函数相对应的多个参数;该函数会返回表示新插入元素位置的迭代器。 简单的理解 args…,即被插入元素的构造函数需要多少个参数,那么在 emplace() 的第一个参数的后面,就需要传入相应数量的参数。
例子:
#include <vector> #include <iostream> using namespace std ;int main () { std ::vector <int > demo1{1 ,2 }; demo1.emplace(demo1.begin(), 3 ); for (int i = 0 ; i < demo1.size(); i++) { cout << demo1[i] << " " ; } return 0 ; }3 1 2
既然 emplace() 和 insert() 都能完成向 vector 容器中插入新元素,那么谁的运行效率更高呢? 答案是 emplace()。
区别代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <vector> #include <iostream> using namespace std ;class testDemo { public : testDemo(int num) :num(num) { std ::cout << "调用构造函数" << endl ; } testDemo(const testDemo& other) :num(other.num) { std ::cout << "调用拷贝构造函数" << endl ; } testDemo(testDemo&& other) :num(other.num) { std ::cout << "调用移动构造函数" << endl ; } testDemo& operator =(const testDemo& other);private : int num; }; testDemo& testDemo::operator =(const testDemo& other) { this ->num = other.num; return *this ; }int main () { cout << "insert:" << endl ; std ::vector <testDemo> demo2{}; demo2.insert(demo2.begin(), testDemo(1 )); cout << "emplace:" << endl ; std ::vector <testDemo> demo1{}; demo1.emplace(demo1.begin(), 1 ); return 0 ; } insert: 调用构造函数 调用移动构造函数 emplace: 调用构造函数
当拷贝构造函数和移动构造函数同时存在时,insert() 会优先调用移动构造函数。 可以看到,通过 insert() 函数向 vector 容器中插入 testDemo 类对象,需要调用类的构造函数和移动构造函数(或拷贝构造函数);而通过 emplace() 函数实现同样的功能,只需要调用构造函数即可。 简单的理解,就是 emplace() 在插入元素时,是在容器的指定位置直接构造元素,而不是先单独生成,再将其复制(或移动)到容器中。因此,在实际使用中,推荐大家优先使用 emplace()。
三.list 添加(插入)元素 list 模板类中,与“添加或插入新元素”相关的成员方法有如下几个:
push_front():向 list 容器首个元素前添加新元素; push_back():向 list 容器最后一个元素后添加新元素; emplace_front():在容器首个元素前直接生成新的元素; emplace_back():在容器最后一个元素后直接生成新的元素; emplace():在容器的指定位置直接生成新的元素; insert():在指定位置插入新元素; splice():将其他 list 容器存储的多个元素添加到当前 list 容器的指定位置处。
以上这些成员方法中,除了 insert() 和 splice() 方法有多种语法格式外,其它成员方法都仅有 1 种语法格式。用法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> #include <list> using namespace std ;int main () { std ::list <int > values{1 ,2 ,3 }; values.push_front(0 ); values.push_back(4 ); values.emplace_front(-1 ); values.emplace_back(5 ); values.emplace(values.end(), 6 ); for (auto p = values.begin(); p != values.end(); ++p) { cout << *p << " " ; } return 0 ; }-1 ,0 ,1 ,2 ,3 ,4 ,5 ,6
insert()成员方法 insert() 成员方法的语法格式有 4 种。
语法格式
用法说明
iterator insert(pos,elem)
在迭代器 pos 指定的位置之前插入一个新元素 elem,并返回表示新插入元素位置的迭代器。
iterator insert(pos,n,elem)
在迭代器 pos 指定的位置之前插入 n 个元素 elem,并返回表示第一个新插入元素位置的迭代器。
iterator insert(pos,first,last)
在迭代器 pos 指定的位置之前,插入其他容器(例如 array、vector、deque 等)中位于 [first,last) 区域的所有元素,并返回表示第一个新插入元素位置的迭代器。
iterator insert(pos,initlist)
在迭代器 pos 指定的位置之前,插入初始化列表(用大括号 { } 括起来的多个元素,中间有逗号隔开)中所有的元素,并返回表示第一个新插入元素位置的迭代器。
演示用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> #include <list> #include <array> using namespace std ;int main () { std ::list <int > values{ 1 ,2 }; values.insert(values.begin() , 3 ); values.insert(values.end(), 2 , 5 ); std ::array <int , 3>test{ 7 ,8 ,9 }; values.insert(values.end(), test.begin(), test.end()); values.insert(values.end(), { 10 ,11 }); for (auto p = values.begin(); p != values.end(); ++p) { cout << *p << " " ; } return 0 ; }3 1 2 5 5 7 8 9 10 11
不知道有没有人发现,同样是实现插入元素的功能,无论是 push_front()、push_back() 还是 insert(),都有以 emplace 为名且功能和前者相同的成员函数。这是因为,后者是 C++ 11 标准新添加的,在大多数场景中,都可以完全替代前者实现同样的功能。更重要的是,实现同样的功能,emplace 系列方法的执行效率更高。
splice()成员方法 和 insert() 成员方法相比,splice() 成员方法的作用对象是其它 list 容器,其功能是将其它 list 容器中的元素添加到当前 list 容器中指定位置处。 splice() 成员方法的语法格式有 3 种。
语法格式
功能
void splice (iterator position, list& x);
position 为迭代器,用于指明插入位置;x 为另一个 list 容器。 此格式的 splice() 方法的功能是,将 x 容器中存储的所有元素全部移动当前 list 容器中 position 指明的位置处。
void splice (iterator position, list& x, iterator i);
position 为迭代器,用于指明插入位置;x 为另一个 list 容器;i 也是一个迭代器,用于指向 x 容器中某个元素。 此格式的 splice() 方法的功能是将 x 容器中 i 指向的元素移动到当前容器中 position 指明的位置处。
void splice (iterator position, list& x, iterator first, iterator last);
position 为迭代器,用于指明插入位置;x 为另一个 list 容器;first 和 last 都是迭代器,[fist,last) 用于指定 x 容器中的某个区域。 此格式的 splice() 方法的功能是将 x 容器 [first, last) 范围内所有的元素移动到当前容器 position 指明的位置处。
list 容器底层使用的是链表存储结构,splice() 成员方法移动元素的方式是,将存储该元素的节点从 list 容器底层的链表中摘除,然后再链接到当前 list 容器底层的链表中。这意味着,当使用 splice() 成员方法将 x 容器中的元素添加到当前容器的同时,该元素会从 x 容器中删除。
演示用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> #include <list> using namespace std ;int main () { list <int > mylist1{ 1 ,2 ,3 ,4 }, mylist2{10 ,20 ,30 }; list <int >::iterator it = ++mylist1.begin(); mylist1.splice(it, mylist2); mylist2.splice(mylist2.begin(), mylist1, it); mylist2.splice(mylist2.begin(), mylist1, mylist1.begin(), mylist1.end()); cout << "mylist1 包含 " << mylist1.size() << "个元素" << endl ; cout << "mylist2 包含 " << mylist2.size() << "个元素" << endl ; cout << "mylist2:" ; for (auto iter = mylist2.begin(); iter != mylist2.end(); ++iter) { cout << *iter << " " ; } return 0 ; } mylist1 包含 0 个元素 mylist2 包含 7 个元素 mylist2:1 10 20 30 3 4 2
四.list 删除元素
成员函数
功能
pop_front()
删除位于 list 容器头部的一个元素。
pop_back()
删除位于 list 容器尾部的一个元素。
erase()
该成员函数既可以删除 list 容器中指定位置处的元素,也可以删除容器中某个区域内的多个元素。
clear()
删除 list 容器存储的所有元素。
remove(val)
删除容器中所有等于 val 的元素。
unique()
删除容器中相邻的重复元素,只保留一份。
remove_if()
删除容器中满足条件的元素。
常见函数就只是简单记录一下用法:
list <int >values; values.pop_front(); values.pop_back(); values.clear();
erase() 成员函数有以下 2 种语法格式:
iterator erase (iterator position) ;iterator erase (iterator first, iterator last) ;
利用第一种语法格式,可实现删除 list 容器中 position 迭代器所指位置处的元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> #include <list> using namespace std ;int main () { list <int >values{ 1 ,2 ,3 ,4 ,5 }; auto del = values.begin(); ++del; values.erase(del); for (auto begin = values.begin(); begin != values.end(); ++begin) { cout << *begin << " " ; } return 0 ; }1 3 4 5
利用第二种语法格式,可实现删除 list 容器中 first 迭代器和 last 迭代器限定区域内的所有元素(包括 first 指向的元素,但不包括 last 指向的元素)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> #include <list> using namespace std ;int main () { list <int >values{ 1 ,2 ,3 ,4 ,5 }; auto first = values.begin(); ++first; auto last = values.end(); --last; values.erase(first, last); for (auto begin = values.begin(); begin != values.end(); ++begin) { cout << *begin << " " ; } return 0 ; }1 5
erase() 成员函数是按照被删除元素所在的位置来执行删除操作,如果想根据元素的值来执行删除操作,可以使用 remove() 成员函数。例如:
#include <iostream> #include <list> using namespace std ;int main () { list <char >values{'a' ,'b' ,'c' ,'d' }; values.remove('c' ); for (auto begin = values.begin(); begin != values.end(); ++begin) { cout << *begin << " " ; } return 0 ; } a b d
unique() 函数也有以下 2 种语法格式:
void unique () void unique(BinaryPredicate)//传入一个二元谓词函数
以上 2 种格式都能实现去除 list 容器中相邻重复的元素,仅保留一份。但第 2 种格式的优势在于,我们能自定义去重的规则,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> #include <list> using namespace std ;bool demo (double first, double second) { return (int (first) == int (second)); }int main () { list <double > mylist{ 1 ,1.2 ,1.2 ,3 ,4 ,4.5 ,4.6 }; mylist.unique(); for (auto it = mylist.begin(); it != mylist.end(); ++it) cout << *it << ' ' ; cout << endl ; mylist.unique(demo); for (auto it = mylist.begin(); it != mylist.end(); ++it) std ::cout << *it << ' ' ; return 0 ; }1 1.2 3 4 4.5 4.6 1 3 4
注意,除了以上一定谓词函数的方式,还可以使用 lamba表达式以及函数对象的方式定义。 通过调用无参的 unique(),仅能删除相邻重复(也就是相等)的元素,而通过我们自定义去重的规则,可以更好的满足在不同场景下去重的需求。 除此之外,通过将自定义的谓词函数(不限定参数个数)传给 remove_if() 成员函数,list 容器中能使谓词函数成立的元素都会被删除。例如:
#include <iostream> #include <list> using namespace std ;int main () { std ::list <int > mylist{ 15 , 36 , 7 , 17 , 20 , 39 , 4 , 1 }; mylist.remove_if([](int value) {return (value < 10 ); }); for (auto it = mylist.begin(); it != mylist.end(); ++it) std ::cout << ' ' << *it; return 0 ; }15 36 17 20 39
五.map 容器插入键值的两种函数 map中除了insert()方法可以插入外。C++ STL map 类模板中还提供了 emplace() 和 emplace_hint() 成员函数,也可以实现向 map 容器中插入新的键值对。 实现相同的插入操作,无论是用 emplace() 还是 emplace_hont(),都比 insert() 方法的效率高。
和 insert() 方法相比,emplace() 和 emplace_hint() 方法的使用要简单很多,因为它们各自只有一种语法格式。其中,emplace() 方法的语法格式如下:
template <class... Args> pair<iterator,bool> emplace (Args&&... args);
参数 (Args&&… args) 指的是,这里只需要将创建新键值对所需的数据作为参数直接传入即可,此方法可以自行利用这些数据构建出指定的键值对。另外,该方法的返回值也是一个 pair 对象,其中 pair.first 为一个迭代器,pair.second 为一个 bool 类型变量:
当该方法将键值对成功插入到 map 容器中时,其返回的迭代器指向该新插入的键值对,同时 bool 变量的值为 true;
当插入失败时,则表明 map 容器中存在具有相同键的键值对,此时返回的迭代器指向此具有相同键的键值对,同时 bool 变量的值为 false。
emplace() 方法的具体用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> #include <map> //map #include <string> //string using namespace std ;int main () { std ::map <string , string >mymap; pair <map <string , string >::iterator, bool > ret = mymap.emplace("STL教程" , "https://lichengloong.com/" ); cout << "1、ret.iter = <{" << ret.first->first << ", " << ret.first->second << "}, " << ret.second << ">" << endl ; ret = mymap.emplace("C语言教程" , "https://lichengloong.com/" ); cout << "2、ret.iter = <{" << ret.first->first << ", " << ret.first->second << "}, " << ret.second << ">" << endl ; ret = mymap.emplace("STL教程" , "https://lichengloong.com/" ); cout << "3、ret.iter = <{" << ret.first->first << ", " << ret.first->second << "}, " << ret.second << ">" << endl ; return 0 ; }1 、ret.iter = <{STL教程, https:2 、ret.iter = <{C语言教程, https:3 、ret.iter = <{STL教程, https:
程序中共执行了 3 次向 map 容器插入键值对的操作,其中前 2 次都成功了,第 3 次由于要插入的键值对的键和 map 容器中已存在的键值对的键相同,因此插入失败。
emplace_hint() 方法的功能和 emplace() 类似,其语法格式如下:
template <class... Args> iterator emplace_hint (const_iterator position, Args&&... args) ;
显然和 emplace() 语法格式相比,有以下 2 点不同:
1.该方法不仅要传入创建键值对所需要的数据,还需要传入一个迭代器作为第一个参数,指明要插入的位置(新键值对键会插入到该迭代器指向的键值对的前面); 2.该方法的返回值是一个迭代器,而不再是 pair 对象。当成功插入新键值对时,返回的迭代器指向新插入的键值对;反之,如果插入失败,则表明 map 容器中存有相同键的键值对,返回的迭代器就指向这个键值对。
emplace_hint() 方法的用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> #include <map> //map #include <string> //string using namespace std ;int main () { std ::map <string , string >mymap; map <string , string >::iterator iter = mymap.emplace_hint(mymap.begin(),"STL教程" , "https://lichengloong.com/" ); cout << iter->first << " " << iter->second << endl ; iter = mymap.emplace_hint(mymap.begin(), "C语言教程" , "https://lichengloong.com/" ); cout << iter->first << " " << iter->second << endl ; iter = mymap.emplace_hint(mymap.begin(), "STL教程" , "https://lichengloong.com/" ); cout << iter->first << " " << iter->second << endl ; return 0 ; } STL教程 https: C语言教程 https: STL教程 https:
和 insert() 方法一样,虽然 emplace_hint() 方法指定了插入键值对的位置,但 map 容器为了保持存储键值对的有序状态,可能会移动其位置。
六.set 容器添加元素的两种方法 set 类模板提供的所有成员方法中,能实现向指定 set 容器中添加新元素的,只有 3 个成员方法,分别为 insert()、emplace() 和 emplace_hint()。其中 insert() 成员方法的用法比较常规,这里就不再记录具体用法了。
emplace() 和 emplace_hint() 是 C++ 11 标准加入到 set 类模板中的,相比具有同样功能的 insert() 方法,完成同样的任务,emplace() 和 emplace_hint() 的效率会更高。
emplace()函数 emplace() 方法的语法格式:
template <class... Args> pair<iterator,bool> emplace (Args&&... args);
其中,参数 (Args&&… args) 指的是,只需要传入构建新元素所需的数据即可,该方法可以自行利用这些数据构建出要添加的元素。比如,若 set 容器中存储的元素类型为自定义的结构体或者类,则在使用 emplace() 方法向容器中添加新元素时,构造新结构体变量(或者类对象)需要多少个数据,就需要为该方法传入相应个数的数据。
该方法的返回值类型为 pair 类型,其包含 2 个元素,一个迭代器和一个 bool 值:
当该方法将目标元素成功添加到 set 容器中时,其返回的迭代器指向新插入的元素,同时 bool 值为 true;
当添加失败时,则表明原 set 容器中已存在相同值的元素,此时返回的迭代器指向容器中具有相同键的这个元素,同时 bool 值为 false。
演示 emplace() 方法的具体用法:
#include <iostream> #include <set> #include <string> using namespace std ;int main () { std ::set <string >myset; pair <set <string , string >::iterator, bool > ret = myset.emplace("https://lichengloong.com/" ); cout << "myset size = " << myset.size() << endl ; cout << "ret.iter = <" << *(ret.first) << ", " << ret.second << ">" << endl ; return 0 ; } myset size = 1 ret.iter = <https:
从执行结果可以看出,通过调用 emplace() 方法,成功向空 myset 容器中添加了一个元素,并且该方法的返回值中就包含指向新添加元素的迭代器。
emplace_hint()函数 emplace_hint() 方法的功能和 emplace() 类似,其语法格式如下:
template <class... Args> iterator emplace_hint (const_iterator position, Args&&... args) ;
和 emplace() 方法相比,有以下 2 点不同:
该方法需要额外传入一个迭代器,用来指明新元素添加到 set 容器的具体位置(新元素会添加到该迭代器指向元素的前面);
返回值是一个迭代器,而不再是 pair 对象。当成功添加元素时,返回的迭代器指向新添加的元素;反之,如果添加失败,则迭代器就指向 set 容器和要添加元素的值相同的元素。
演示 emplace_hint() 方法的用法:
#include <iostream> #include <set> #include <string> using namespace std ;int main () { std ::set <string >myset; set <string >::iterator iter = myset.emplace_hint(myset.begin(), "https://lichengloong.com/" ); cout << "myset size = " << myset.size() << endl ; cout << *iter << endl ; return 0 ; } myset size = 1 https:
需要注意的是,和 insert() 方法一样,虽然 emplace_hint() 方法中指定了添加新元素的位置,但 set 容器为了保持数据的有序状态,可能会移动其位置。
至于比 insert() 执行效率高的原因,可参照 map 容器 emplace() 和 emplace_hint() 比 insert() 效率高的原因,它们是完全一样的。