C++笔记8:C++提高编程2:STL---标准模板库
0831
C++提高编程:
- 1、模板
- 2、初识STL
- 3、STL---常用容器
- (总)常用容器比较:
- 3.1 string容器
- 3.2 vector容器
- 3.3 deque容器
- 3.4 STL案例1
- 3.5 stack容器(栈)
- 3.6 queue容器(队列)
- 3.7 list容器(链表)
- 3.8 set / multiset 容器(二叉树结构---自动排序)
- ☆☆☆总结 常用容器的`排序`的区别
- 3.9 map multimap容器(二叉树结构---自动排序)
- 3.10 STL案例2
- 4、STL---函数对象
- 5、STL---常用算法
1、模板
函数模板和类模板
2、初识STL
2、初识STL
2.1 诞生
为提升代码的复用性,C++提供了面向对象编程思想
和泛型编程思想
。为了建立数据结构
和算法
的一套标准,诞生了STL
。
面向对象的编程思想:封装、继承、多态
泛型编程思想:模板Template
2.2 STL基本概念
STL(Standard Template Library,标准模板库)。
另外,STL 几乎所有的代码都采用了模板类
或者模板函数
,见函数模板的总结。
2.3 STL六大组件
六大组件为:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器。主要学习前四大组件
。
- 容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据。
- 算法:各种常用的算法,如sort、find、copy、for_each等
- 迭代器:扮演了容器与算法之间的胶合剂。
- 仿函数:行为类似函数,可作为算法的某种策略。见6.函数调用运算符()重载—仿函数 —3.7.7、3.7.8、3.8.8也有涉及到仿函数
- 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。
- 空间配置器:负责空间的配置与管理。
2.4 容器、算法、迭代器
STL 从广义上分为: 容器(container) 、算法(algorithm) 、迭代器(iterator)
。
三者之间的关系(重点记!!!):
①容器
和算法
之间通过迭代器
进行无缝连接,或者说算法
要通过迭代器
才能访问容器
中的数据。
②每个容器都有自己专属的迭代器,迭代器用来遍历容器中的元素
③ 容器-相当于-数据结构
算法-相当于-算法
迭代器-相当于-指针
2.4.1 容器—数据结构:
STL容器就是将运用最广泛的一些数据结构
实现出来。
常用的数据结构:数组, 链表,树, 栈, 队列, 集合, 映射表 等
。
这些容器分为序列式容器和关联式容器两种:
序列式容器:强调值的排序,序列式容器中的每个元素均有固定的位置
;
关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系
。
2.4.2 算法:
用有限的步骤,解决逻辑或数学上的问题,这一门学科我们叫做算法(Algorithms
)。
算法分为:质变算法和非质变算法。
质变算法:是指运算过程中会更改
区间内的元素的内容。例如拷贝,替换,删除
等等;
非质变算法:是指运算过程中不会更改
区间内的元素内容,例如查找、计数、遍历、寻找极值
等等。
2.4.3 迭代器:
容器和算法之间粘合剂,提供一种方法,使之能够依序寻访某个容器所含的各个元素
,而又无需暴露该容器的内部表示方式。
注意:每个容器都有自己专属的迭代器
。
迭代器使用非常类似于指针
,初学阶段我们可以先理解迭代器为指针
。
迭代器的种类:
迭代器名称 | 用途 | 特征 |
---|---|---|
输入迭代器 | list容器打印函数中有用到 | 只读,支持++、 == 、!= |
输出迭代器 | 只写,支持++ | |
前向迭代器 | 读写,并能向前推进迭代器,支持++、==、!= | |
双向迭代器 | list容器 | 读写,并能向前和向后操作,支持++、– |
随机访问迭代器 | vector容器deque容器 |
读写,可以以跳跃的方式访问任意数据 ,功能最强的迭代器,支持++、–、[n]、-n、<、<=、>、>=
|
p.s. 常用的容器中迭代器种类为双向迭代器 和随机访问迭代器 。
|
2.5 容器、算法、迭代器初识
STL中最常用的容器为Vector
,可以理解为数组
。
2.5.1 vector存放内置数据类型
重点看几点注释
示例:
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>//①标准算法头文件
using namespace std;//自定义打印函数
void MyPrint(int val) {cout << val << " ";
}int main() {//vector中存内置数据类型//②创建vector容器对象,并且通过模板参数来指定容器中存放的数据类型,v1为容器对象名vector<int> v1;//③用push_back()向容器中添加数据:v1.push_back(1);v1.push_back(2);v1.push_back(3);//④每个容器都有自己的迭代器,迭代器用来遍历容器中的元素vector<int>::iterator itBegin = v1.begin();//⑤v1.begin()是容器v1的起始迭代器,指向容器v1中第一个数据; itBegin是容器v1的起始迭代器的名称vector<int>::iterator itEnd = v1.end();//⑥v1.end()是容器v1的结束迭代器,指向容器v1中最后一个元素的下一个位置; itEnd是容器v1的结束迭代器的名称//遍历方式一:while (itBegin != itEnd) {cout << *itBegin << " ";itBegin++;}cout << endl;//遍历方式二:for (vector<int>::iterator itera = v1.begin(); itera != v1.end(); itera++) {cout << *itera << " ";}cout << endl;//遍历方式三:使用STL提供的标准遍历算法for_each(),头文件是#include<algorithm>for_each(v1.begin(),v1.end(),MyPrint);//MyPrint()是自定义的一个打印函数cout << endl;system("pause");return 0;
}
①容器和算法的头文件
②创建vector容器对象,并且通过模板参数来指定容器中存放的数据类型,v1为容器对象名
③用push_back()
向容器中添加数据—尾插法push_back()
④每个容器都有自己的迭代器,迭代器用来遍历
容器中的元素
⑤v1.begin()
是容器v1的起始迭代器,指向容器v1中第一个数据; itBegin
是容器v1的起始迭代器的名称
⑥v1.end()
是容器v1的结束迭代器,指向容器v1中最后一个元素的下一个位置; itEnd
是容器v1的结束迭代器的名称
补充:⑤⑥中的itBegin = v1.begin();
和itEnd = v1.end();
相当于四个指针:itBegin等价于v1.begin() 而itEnd 等价于 v1.end(); 。
⑦遍历方式一和二是一样的,遍历方式三是利用STL提供的标准遍历算法for_each()
,还需要自定义一个打印函数MyPrint()
遍历方式二比较简洁一些。
2.5.2 vector存放自定义数据类型
因为itBegin2
是指针,所以(*itBegin2)
是容器中存放的元素,即Person类,所以要输出类的成员变量,有下面两种方式:
cout << (*itBegin2).name << " " << (*itBegin2).age << endl;
//两种输出方式
cout << itBegin2->age << "," << itBegin2->name << endl;
//第二种输出方式
示例:
//自定义数据类型Person类
class Person {
public :string name;int age;public:Person(string name, int age) {this->name = name;this->age = age;}
};int main() {//vector中存放自定义数据类型Person类Person p1("Tom",23);Person p2("Jerry",22);Person p3("Reus",25);//创建容器vector<Person> v2;//为容器插入数据v2.push_back(p1);v2.push_back(p2);v2.push_back(p3);vector<Person>::iterator itBegin2 = v2.begin();vector<Person>::iterator itEnd2 = v2.end();//遍历一while (itBegin2 != itEnd2) {cout << (*itBegin2).name << " " << (*itBegin2).age << endl;//两种输出方式cout << itBegin2->age << "," << itBegin2->name << endl;itBegin2++;}cout << endl;//遍历二for (vector<Person>::iterator itera2 = v2.begin(); itera2 != v2.end(); itera2++) {cout << (*itera2).name << " " << (*itera2).age << endl;cout << itera2->age << "," << itera2->name << endl;//两种输出方式}cout << endl;//遍历三//for_each(v2.begin(), v2.end(), MyPrint2);//vector中存放自定义数据类型Person类的指针Person p4("Tom---", 23);Person p5("Jerry---", 22);Person p6("Reus---", 25);//创建容器vector<Person*> v3;//容器中存Person类的指针//为容器插入数据v3.push_back(&p4);//对象取地址,存入容器中v3.push_back(&p5);v3.push_back(&p6);//vector<Person*>::iterator itBgin3 = v3.begin();//vector<Person*>::iterator itEnd3 = v3.end();for (vector<Person*>::iterator it = v3.begin(); it != v3.end(); it++) {cout << (*(*it)).name << " " << (*(*it)).age << endl;//两种输出方式cout << (*it)->name << "," << (*it)->age << endl;}system("pause");return 0;
}
2.5.3 容器嵌套容器
容器相当于一个一维数组,
容器中再嵌套一个数组,就相当于二维数组。
示例:
int main(){//2.5.3 容器中嵌套容器vector< vector<int> > v4;//容器中的元素为vector<int>//先创建几个元素---容器性质的元素vector<int> v11;vector<int> v12;vector<int> v13;vector<int> v14;//给几个元素赋值for (int i = 0; i < 5; i++) {v11.push_back(i);//0 1 2 3 4v12.push_back(i + 10);//10 11 12 13 14v13.push_back(i + 20);//20 21 22 23 24 v14.push_back(i + 30);//30 31 32 33 34}//将容器元素插入到vector v4中v4.push_back(v11);v4.push_back(v12);v4.push_back(v13);v4.push_back(v14);//双层for循环输出二维数组:for (vector<vector<int>> ::iterator it = v4.begin(); it != v4.end(); it++) {for (vector<int>::iterator itera=(*it).begin(); itera !=(*it).end(); itera++) {cout << "\t"<< *itera ;}cout << endl;}system("pause");return 0;
}
解释:
示例中的是4*5的二维数组,
for (vector<vector> ::iterator it = v4.begin()
; it != v4.end(); it++) {
for (vector::iterator itera=(*it).begin()
; itera !=(*it).end(); itera++) {
cout << “\t”<< *itera ;
}
cout << endl;
}
第一层容器的首元素为it = v4.begin()
,各元素为(*it)
;
所以第二层容器的首元素为itera = (*it).begin()
,各元素为(*itera)
。
3、STL—常用容器
(总)常用容器比较:
容器 | 备注 | 迭代器 | 使用
-------- | ----- | -----|-----|-----| ----- | -----|-----|-----| ----- | -----|-----|-----|-----
string容器 | string类 | | 构造、赋值、拼接、查找、替换、比较、存取、插入、删除
vector容器
| 单端数组 | 随机访问迭代器 | 构造、赋值、容量capacity
、大小、插入、删除、存取、交换、预留reserve
、排序(#include<algorithm> sort();
)
deque容器 | 双端数组 | 随机访问迭代器 | 构造、赋值、大小、插入、删除、存取、排序(#include<algorithm> sort();
)
stack容器(栈) | 栈顶增删,栈底封 | 不能遍历 | 构造、赋值、存取、大小
queue容器(队列) |队尾增,队头删 | 不能遍历 | 构造、赋值、存取、大小
list容器(链表)
| 双向循环链表 | 双向迭代器 | 构造、赋值、交换、大小、插入、删除、存取、反转、排序(成员函数:L1.sort();
)
set容器 | | | 构造、赋值、大小、交换、插入、删除、查找find、统计count、排序(自动排序)
map容器
| <Key,Value> | | 构造、赋值、大小、交换、插入、删除、查找find、统计count、排序(自动排序)
排序的区别见3.8.8最后的总结!!!
打印(打印函数的参数要加引用&
):
vector容器和deque容器
:
(打印方式一:数组的输出方式)
void printDeque_i(deque<int>& d) {for (int i = 0; i < d.size(); i++) {cout << d[i] << " ";}cout << endl;
}
(打印方式二:)
void printVector(vector<int>& v) {for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {cout << *it << " ";}cout << endl;
}
stack容器和queue容器
:判空,然后挨个出队出栈,打印完容器也就清空了。
void printStack_i(stack<int>& stk) {while (!stk.empty()) {cout << stk.top() << " ";stk.pop();//出栈}cout << endl;
}
list容器
(加了const
):
void printList(const list<int>& L) {for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {cout << *it << " ";}cout << endl;
}
set multiset容器
:
void printSet_i(set<int>& s){//加引用&for(set<int>::iterator it = s.begin(); it != s.end(); it++){cout << *it << " ";}cout << endl;
}
3.1 string容器
构造: string str1;//无参构造 string str2(str1);//拷贝构造
string str3(10, ‘a’);//有参构造
赋值: str2 = str1; str.assign();
拼接: str2 += str1; str.append();
查找: int res = str1.find();//返回所找的元素下标 或 -1 str2.rfind();//倒查
替换: str.replace();
比较: str1.compare(str2);//返回1 0 -1
存取: str[i] 或者 str.at(i)
插入: str.insert()
删除: str.erase()
3.1.1 string基本概念
string是C++风格的字符串,而string本质上是一个类。
string 和 char * :
- char * 是一个指针
- string是一个类,类内部封装了char*,管理这个字符串,是一个
char*型的容器
。
特点:
string 类内部封装了很多成员方法,例如:查找find,拷贝copy,删除delete 替换replace,插入insert
等。
string管理char*所分配的内存,不用担心复制越界和取值越界等,由类内部进行负责。
3.1.2 string构造函数
string是一个类,所以string str = “Hello,world!”;
中的str
就是一个string类的对象
名。
示例:
int main() {string str1;//调用无参构造函数,创建空字符串cout << "str1 = " << str1 << endl;//空const char* str = "Hi,Reus.";string str2(str);//调用有参构造函数,使用字符串s初始化cout << "str2 = " << str2 << endl;//Hi,Reus.//string str5(const char* s = "Hello,world!");报错!!!//cout << "str5 = " << str5 << endl;//string str6("Oh!");//调用有参构造函数cout << "str6 = " << str6 << endl;//Oh!string str7 = "Sorry,";cout << "str7 = " << str7 << endl;//Sorry,string str3(str2);//调用拷贝构造函数cout << "str3 = " << str3 << endl;//Hi,Reus.string str4(6, 's');//使用6个字符s初始化cout << "str4 = " << str4 << endl;//sssssssystem("pause");return 0;
}
总结:string的多种构造方式没有可比性,灵活使用即可
3.1.3 string赋值操作
1.除了直接用赋值操作符=进行赋值外,
2.还可以利用string类的assign
方法进行复制操作。
示例:
int main(){string str1;str1 = "hello world";cout << "str1 = " << str1 << endl;//hello worldstring str2;str2 = str1;cout << "str2 = " << str2 << endl;//hello worldstring str3;str3 = 'a';cout << "str3 = " << str3 << endl;//astring str4;str4.assign("12345678");cout << "str4 = " << str4 << endl;//12345678string str5;str5.assign("12345678", 5);//把字符串s的前n=5个字符赋给当前的字符串cout << "str5 = " << str5 << endl;//12345string str6;str6.assign(str5);//把字符串s赋给当前的字符串cout << "str6 = " << str6 << endl;//12345string str7;str7.assign(5, 's');//用5个字符x赋给当前字符串cout << "str7 = " << str7 << endl;//ssssssystem("pause");return 0;
3.1.4 string字符串拼接
1.除了直接用+=操作符进行p拼接外,
2.还可以利用string类的append
方法进行拼接操作。
示例:
int main(){string str1 = "我";str1 += "爱玩游戏";cout << "str1 = " << str1 << endl;//我爱玩游戏str1 += ':';cout << "str1 = " << str1 << endl;//我爱玩游戏:string str2 = "LOL DNF";str1 += str2;//str1 = str1 + str2;cout << "str1 = " << str1 << endl;//我爱玩游戏:LOL DNFstring str3;str3.assign("123");//赋值操作str3.append(" 654321", 3);//拼接操作; 把字符串s的前n个字符连接到当前字符串结尾//str3.append(str2);str3.append(str1, 6, 5); // 从下标6位置开始 ,截取5个字符,拼接到字符串str3末尾cout << "str3 = " << str3 << endl;//123 65游戏://我 爱 玩 游 戏 :LOL DNF //01 23 45 67 89 10 冒号的下标为10//一个汉字占两个字节,所以从下标6位置开始 ,截取5个字符,拼接到字符串str3的末尾system("pause");return 0;
}
补充:
//我爱玩游戏:LOL DNF
//01 23 45 67 89 10 冒号的下标为10
//一个汉字占两个字节,所以从下标6位置开始 ,截取5个字符,所以会把游戏:
拼接到字符串str3的末尾
3.1.5 string查找和替换
查找:
str.find()
查找是从左往后,str.rfind()
从右往左
找到字符串后返回查找的第一个字符位置,找不到返回-1。
替换:
str.replace()在替换时,要指定从哪个位置起,多少个字符,替换成什么样的字符串
示例:
int main(){//查找string str1 = "abcdefg hijklmn oq rst";cout << "str1 = " << str1 << endl;//abcdefg hijklmn oq rstint position;//标记位置//正着查:position = str1.find("rst");//p q rst//倒着查:position = str1.rfind("rst");//p q rstif(position == -1) cout << "未找到" << endl;elsecout << "找到了,要查找的字符下标为 " << position << endl;//替换str1.replace(8, 7, "p654321p");//从下标8开始,替换7个字符,替换成字符串"p654321p",这里的7用处不大,字符串"p654321p"有8个字符cout << "str1 = " << str1 << endl;//abcdefg p654321p oq rstsystem("pause");return 0;
}
3.1.6 string字符串比较
字符串比较是按字符的ASCII码
进行对比:str1.compare(str2);
相等== 返回 0;
大于> 返回 1 ;
小于< 返回 -1。
另外,字符串对比主要是用于比较两个字符串是否相等
,判断谁大谁小并没有意义。
示例:
int main(){string s1 = "hello";string s2 = "aello";int ret;//记录比较结果ret = s1.compare(s2);if (ret == 0) cout << "s1 等于 s2" << endl;else if (ret > 0)cout << "s1 大于 s2" << endl;elsecout << "s1 小于 s2" << endl;system("pause");return 0;
}
3.1.7 string 字符存取
string字符串中单个字符
存取有两种方式,利用 [ ]
或 at
。
char& operator[](int n); //通过str.[i]
方式取字符
char& at(int n); //通过str.at(i)
方法获取字符
除了利用这两种方式输出
单个字符,还可以修改
单个字符,总之就是可以利用这两种方式对单个字符进行各种操作。
示例:
int main(){string str1 = "Hi, Marco Reus. I am your crazy fan.";//三种打印方式:cout << "str1 = " << str1 << endl;//Hi, Marco Reus. I am your crazy fan.for (int i = 0; i < str1.size(); i++) {cout << str1[i] << ",";//依次打印单个字符}cout << endl;for (int i = 0; i < str1.size(); i++) {cout << str1.at(i) << " ";//依次打印单个字符}cout << endl;//修改单个字符str1[0] = 'h';str1.at(1) = 'I';cout << "str1 = " << str1 << endl;//hI, Marco Reus. I am your crazy fan.system("pause");return 0;
}
3.1.8 string插入和删除
插入str.insert()
//在指定位置插入___
删除str.erase()
//从指定位置pos开始删除n个字符
示例:
int main(){string str = "hello";cout << str << endl;//hello//插入://str.insert(3,"_这是插入的字符串_");//从下标3开始插入一个字符串 结果:hel_这是插入的字符串_lostr.insert(3,5,'_');//从下标3开始插入5个下划线字符 结果:hel_____locout << str << endl;
//删除:str.erase(3,4);//从下标3开始,删除4个字符cout << str << endl;//hel_losystem("pause");return 0;
}
3.1.9 string 子串
string substr(int pos = 0, int n = npos) const;
str.substr();
//返回由从下标pos开始的n个字符
组成的字符串
示例:
int main(){//示例1string str1 = "1796675813@qq.com";int pos = str1.find('@');//pos既是@的下标位置,也是@之前的字符个数//从中提取出qq号cout << "qq: " << str1.substr(0, pos) << endl;//起始位置为0,一共提取pos个字符//示例2cout << "情侣争吵的时候是这个样子的:\n我说:";string str = "上次是我不对,我不该那么冲动,当然啦,你也有不对,你看哈,假如说...";cout << str << endl;cout << "(女朋友听到的:..." << str.substr(35, 10) << "...)" << endl;//提取10个字符=5个汉字cout << "然后女朋友说:什么?你居然说我不对!!!\n然后就没有然后了...\n\n\n" << endl;system("pause");return 0;
}
3.2 vector容器
构造: vector v1;//无参构造 vector v2(v1);//拷贝构造
vector v3(10, 3);//有参构造
赋值: v2 = v1; v.assign();
容量和大小:v1.capacity() v1.size()
v1.resize()//重置大小 v.empty()//判空
插入: v1.push_back()//尾插 v1.insert()
删除: v1.pop_back()//尾删 v1.erase() v1.clear()//清空
存取: v1[i] 或者 v1.at() v1.front()//首元素 v1.back()//最后一个元素
交换: v1.swap(v2) vector(v).swap(v)//收缩内存空间,其中vector(v)是通过拷贝构造创建的匿名对象
预留: v1.reserve(100000)//这样就只需开辟一次内存
3.2.1 vector基本概念
解释上图:
vector容器会把前面封住,只能在容器的后面进行增删操作
front()—容器中第一个元素
back()—容器中最后一个元素
push_back()—尾插法增加数据;
pop_back()—尾删法删除数据;
v.rend() v.end()—rend表示首元素的前一个位置,end表示最后一个元素的下一个位置;
v.begin() v.rbegin()—begin表示首元素的位置,rbegin表示最后一个元素的位置;
insert()—插入数据
erase()—删除数据
比较常用的是v.begin()
和v.end()
;
vector数据结构和数组非常相似,也称为单端数组
,(单端是因为容器只能从一端back端进行插数据和删数据)。
vector与普通数组区别:数组是静态空间
,而vector可以动态扩展
。
(补充:动态扩展
并不是在原空间之后续接新空间,而是找更大的内存空间,然后将原数据拷贝
至新空间,释放
原空间。)
另外,vector容器的迭代器是支持随机访问
的迭代器!!!
3.2.2 vector构造函数
(几种构造方式在2.5.1已经见过了)
vector<T> v;
//采用模板实现类实现,默认构造函数vector(v.begin(), v.end());
//将v[begin(), end())区间中的元素拷贝给本身。vector(n, elem);
//构造函数将n个elem拷贝给本身。vector(const vector &vec);
//拷贝构造函数。
示例:
//打印
void printVector_i(vector<char>& v) {for (vector<char>::iterator it = v.begin(); it != v.end(); it++) {cout << *it << " ";}cout << endl;
}int main() {//默认构造函数 vector<T> v;vector<char> v1;//尾插法为容器v1插入元素for (int i = 0; i < 5; i++) {v1.push_back('a' + i);}printVector_i(v1);//a b c d e//拷贝构造函数 vector(const vector &vec);vector<char> v2(v1);printVector_i(v2);//a b c d e//将v(begin(), end())区间中的元素拷贝给本身 vector(v.begin(), v.end());vector<char> v3(v1.begin(), v1.end());printVector_i(v3);//a b c d e//构造函数将n个elem拷贝给本身 vector(n, elem); vector<char> v4(8, 's');printVector_i(v4);//s s s s s s s ssystem("pause");return 0;
}
3.2.3 vector赋值操作
除了用赋值操作符operator=
,还可以用assign()
。
vector& operator=(const vector &vec);
//重载等号操作符assign(beg, end);
//将[beg, end)区间中的数据拷贝赋值给本身。assign(n, elem);
//将n个elem拷贝赋值给本身。
示例:
//打印
void printVector_i(vector<int>& v) {//charfor (vector<int>::iterator it = v.begin(); it != v.end(); it++) {//charcout << *it << " ";}cout << endl;
}int main() {vector<int> v1;for (int i = 0; i < 5; i++) {v1.push_back(i + 1);}printVector_i(v1);//1 2 3 4 5vector<int> v2;v2 = v1;//直接用赋值操作符=printVector_i(v2);//1 2 3 4 5vector<int> v3;v3.assign(v1.begin(), v1.end());printVector_i(v3);//1 2 3 4 5vector<int> v4;v4.assign(8, 6);printVector_i(v4);//6 6 6 6 6 6 6 6system("pause");return 0;
}
3.2.4 vector容量和大小(元素个数)
empty();
//判断容器是否为空capacity();
//容器的容量size();
//返回容器中元素的个数resize(int num);
//重新指定容器的大小size为num,若容器变长,则以默认值填充新位置
。
//如果容器变短,则末尾超出容器长度的元素被删除。resize(int num, elem);
//重新指定容器的大小size为num,若容器变长,则以elem值填充新位置
。
//如果容器变短,则末尾超出容器长度的元素被删除
(看完示例再解释resize()
)
示例:
//打印
void printVector_i(vector<int>& v) {//charfor (vector<int>::iterator it = v.begin(); it != v.end(); it++) {//charcout << *it << " ";}cout << endl;
}int main() {vector<int> v1;for (int i = 0; i < 7; i++){v1.push_back(i + 1);}printVector_i(v1);//1 2 3 4 5 6 7//判断容器是否为空if (v1.empty()) {cout << "容器v1为空!!!" << endl;}elsecout << "容器v1非空" << endl;//√//容器的容量和大小(元素个数)cout << "容器v1的容量为:" << v1.capacity() << "; 容器v1的元素个数为:" << v1.size() << endl;//9 7//重置容器的大小resize()//①如果newSize > capacity,则必有newSize > size,那么capacity变大,size变大;v1.resize(15);//使用默认值0填充新位置cout << "容器v1的容量为:" << v1.capacity() << "; 容器v1的元素个数为:" << v1.size() << endl;//15 15printVector_i(v1);//1 2 3 4 5 6 7 0 0 0 0 0 0 0 0//②如果newSize < capacity且newSize < size,那么capacity不变,size变小。v1.resize(3);//cout << "容器v1的容量为:" << v1.capacity() << "; 容器v1的元素个数为:" << v1.size() << endl;//15 3,这里容量值没有变成3printVector_i(v1);//1 2 3//③如果newSize < capacity且newSize > size,那么capacity不变,size变大;v1.resize(10, 6);//指定新位置的填充值为6cout << "容器v1的容量为:" << v1.capacity() << "; 容器v1的元素个数为:" << v1.size() << endl;//15 10printVector_i(v1);//1 2 3 6 6 6 6 6 6 6system("pause");return 0;
}
总结:
1.容量capacity >= 大小size(元素个数);
2.resize重置的是size的值
,共有以下三种情况:
①如果newSize > capacity,则必有newSize > size,那么capacity变大,size变大;
②如果newSize < capacity且newSize > size,那么capacity不变,size变大;
③如果newSize < capacity且newSize < size,那么capacity不变,size变小。
3.2.5 vector插入和删除
- 尾插 — push_back
- 尾删 — pop_back
- 插入 — insert (位置迭代器)—
参数是指针
- 删除 — erase (位置迭代器)—
参数是指针
- 清空 — clear
具体解释:
push_back(ele);
//尾部插入元素elepop_back();
//删除最后一个元素insert(const_iterator pos, ele);
//①迭代器指向位置pos插入元素eleinsert(const_iterator pos, int count,ele);
//②迭代器指向位置pos插入count个元素eleerase(const_iterator pos);
//删除迭代器指向的元素erase(const_iterator start, const_iterator end);
//删除迭代器从start到end之间的元素clear();
//删除容器中所有元素
示例:
//打印
void printVector_i(vector<int>& v) {//charfor (vector<int>::iterator it = v.begin(); it != v.end(); it++) {//charcout << *it << " ";}cout << endl;
}int main() {vector<int> v1;//尾插v1.push_back(10);v1.push_back(20);v1.push_back(30);v1.push_back(40);v1.push_back(50);printVector_i(v1);//10 20 30 40 50//尾删,删除最后一个元素v1.pop_back();v1.pop_back();printVector_i(v1);//10 20 30//插入insertv1.insert(v1.begin(), 66);//①在首元素的位置插入一个66printVector_i(v1);//66 10 20 30v1.insert(v1.begin(), 2, 55);//②在首元素的位置插入两个55printVector_i(v1);//55 55 66 10 20 30//删除erasev1.erase(v1.begin());//删除首元素printVector_i(v1);//55 66 10 20 30//两种清空方式v1.erase(v1.begin(), v1.end());//删除头和尾之间的所有元素,即清空printVector_i(v1);//空v1.clear();//清空printVector_i(v1);//空system("pause");return 0;
}
3.2.6 vector数据存取
at(int idx);
//返回索引idx所指的数据operator[idx];
//返回索引idx所指的数据front();
//返回容器中第一个数据元素back();
//返回容器中最后一个数据元素
示例:
int main(){vector<int> v1;for (int i = 0; i < 5; i++) {v1.push_back(i + 1);//尾插}for (int i = 0; i < v1.size(); i++) {cout << v1[i] << " ";//operator[]}cout << endl;for (int i = 0; i < v1.size(); i++) {cout << v1.at(i) << " ";//at()}cout << endl;cout << "容器v1的首元素为 " << v1.front() << endl;//1cout << "容器v1的最后一个元素为 " << v1.back() << endl;//5system("pause");return 0;
}
3.2.7 vector互换容器
swap(vec);
// 将vec与本身的元素互换 v1.swap(v2);
除了交换两个容器的内容,还有实际的功能:收缩内存空间
。
//打印
void printVector_i(vector<int>& v) {//charfor (vector<int>::iterator it = v.begin(); it != v.end(); it++) {//charcout << *it << " ";}cout << endl;
}int main() {vector<int> v1;for (int i = 0; i < 5; i++) {v1.push_back(i + 1);//尾插}printVector_i(v1);//1 2 3 4 5vector<int> v2;for (int i = 0; i < 5; i++) {v2.push_back(i + 10);//尾插}printVector_i(v2);//10 11 12 13 14//交换两个容器v1.swap(v2);printVector_i(v1);//10 11 12 13 14printVector_i(v2);//1 2 3 4 5//实际用途---收缩内存空间:vector<int> v3;for (int i = 0; i < 1000; i++) {v3.push_back(i + 1);//尾插}cout << "容量:" << v3.capacity() << ";大小:" << v3.size() << endl;//1066 1000//重置容器的大小sizev3.resize(5);cout << "容量:" << v3.capacity() << ";大小:" << v3.size() << endl;//1066 5//收缩内存vector<int>(v3).swap(v3);//具体解释见下图cout << "容量:" << v3.capacity() << ";大小:" << v3.size() << endl;//5 5 system("pause");return 0;
}
程序中 vector<int>(v3).swap(v3);
先利用拷贝构造函数对v3进行拷贝vector<int>(v3)
得到一个匿名对象x容器
,然后再把容器x和容器v3进行交换,达到收缩内存的效果。
3.2.8 vector预留空间
reserve(int len);
//容器预留len个元素长度,预留位置不初始化,元素不可访问。
功能:减少vector在动态扩展容量时的扩展次数
,即减少重复开辟新内存的次数。。如果数据量较大,可以一开始利用reserve()
预留空间
示例:
int main(){//3.2.8 vector预留空间vector<int> v1;//如果提前已知需要输入的数据量,利用reserve()提前预留空间,这样就只需开辟一次内存v1.reserve(100000);//有了这行代码,第193行代码的结果就变成了1 int num = 0;//统计开辟内存的次数 int* p = NULL;for (int i = 0; i < 100000; i++) {//1000-18次 10000---24次 100000---30次v1.push_back(i + 1);//尾插if (p != &v1[0]) {p = &v1[0];num++;}}cout << "共开辟内存的次数为:" << num << endl;//30次 如果加入了第180行的预留空间的代码,就只需要开辟1次内存system("pause");return 0;
}
3.3 deque容器
#include<deque>
//头文件别忘了!!!
构造:deque d1;//无参构造 deque d2(d1);//拷贝构造
deque d3(10, 3);//有参构造
赋值:d2 = d1; d.assign();
(和vector有区别
)大小:
deque没有容量的概念!!d1.size(); d1.resize()//重置大小; d1.empty()//判空
(和vector有区别
)插入:
d1.push_back()//尾插; d1.push_front()//头插; d1.insert()
删除:d1.pop_back()//尾删 d1.pop_front()// 头插 d1.erase() d1.clear()//清空
存取:d1[i] 或者 d1.at() d1.front()//首元素 d1.back()//最后一个元素
排序:(加头文件#include) sort();//注意:直接是sort(); 不要写成d1.sort();
3.3.1 deque容器基本概念
双端数组,可以对头尾两端
进行插入删除操作。
deque容器与vector容器区别:
deque对头部
的插入删除速度比vector快;
vector访问元素
时的速度会比deque快,这和两者内部实现有关。
deque内部工作原理(见下图):
deque内部有个中控器,用来维护每个缓冲区的地址,缓冲区中存放真实数据,使得使用deque时像一片连续的内存空间。
另外,eque容器的迭代器也是支持随机访问的。
3.3.2 deque构造函数
deque<T>
deqT; //默认构造形式
deque(beg, end);
//构造函数将[beg, end)区间中的元素拷贝给本身。
deque(n, elem);
//构造函数将n个elem拷贝给本身。
deque(const deque &deq);
//拷贝构造函数
示例:
//打印
void printDeque_i(deque<int>& d) {for (int i = 0; i < d.size(); i++) {cout << d[i] << " ";}cout << endl;
}
int main() {//3.3.2 deque构造函数//无参构造deque<int> d1;for (int i = 0; i < 5; i++) {d1.push_back(i + 1);//尾插}printDeque_i(d1);//1 2 3 4 5//拷贝构造deque<int> d2(d1);printDeque_i(d2);//1 2 3 4 5//有参构造deque<int> d3(8,3);printDeque_i(d3);//3 3 3 3 3 3 3 3deque<int> d4(d1.begin(), d1.end());printDeque_i(d4);//1 2 3 4 5system("pause");return 0;
}
3.3.3 deque赋值操作
deque& operator=(const deque &deq);
//重载等号操作符
assign(beg, end);
//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);
//将n个elem拷贝赋值给本身。
示例:
//打印
void printDeque_i(deque<int>& d) {for (int i = 0; i < d.size(); i++) {cout << d[i] << " ";}cout << endl;
}
int main() {//3.3.3 deque赋值操作deque<int> d1;for (int i = 0; i < 10; i++){d1.push_back(i);}printDeque_i(d1);//0 1 2 3 4 5 6 7 8 9deque<int>d2;d2 = d1;printDeque_i(d2);//0 1 2 3 4 5 6 7 8 9deque<int>d3;d3.assign(d1.begin(), d1.end());printDeque_i(d3);//0 1 2 3 4 5 6 7 8 9deque<int>d4;d4.assign(10, 100);printDeque_i(d4);//100 100 100 100 100 100 100 100 100 100system("pause");return 0;
}
3.3.4 deque大小操作
deque.empty();
//判断容器是否为空
deque.size();
//返回容器中元素的个数
deque.resize(num);
//重新指定容器的大小size为num,若容器变长,则以默认值填充新位置
。
//如果容器变短,则末尾超出容器长度的元素被删除。
deque.resize(num, elem);
//重新指定容器的大小size为num,若容器变长,则以elem值填充新位置
。
//如果容器变短,则末尾超出容器长度的元素被删除
总结:
- deque
没有容量的概念
!!! - 判断是否为空 — empty
- 返回元素个数 — size
- 重新指定个数 — resize
示例:
//打印
void printDeque_i(deque<int>& d) {for (int i = 0; i < d.size(); i++) {cout << d[i] << " ";}cout << endl;
}
int main() {//3.3.4 deque大小(元素个数)操作 deque<int> d1;for (int i = 0; i < 5; i++) {d1.push_back(i + 1);}printDeque_i(d1);//1 2 3 4 5if (d1.empty() == -1) {cout << "容器d1为空!!!" << endl;}elsecout << "容器d1不为空" << endl;//√cout << "容器d1的大小(元素个数)为: " << d1.size() << endl;//5d1.resize(10);//用默认值0填补新位置printDeque_i(d1);//1 2 3 4 5 0 0 0 0 0d1.resize(3);//当容器大小变小时,删除多余的元素printDeque_i(d1);//1 2 3 d1.resize(8, 20);//用指定值20填补新位置printDeque_i(d1);//1 2 3 20 20 20 20 20system("pause");return 0;
}
3.3.5 deque 插入和删除(看示例)
注意(!!!):d.begin()
和 d.end()
可以进行insert()操作,但d.end() 不能进行erase()操作。`
两端插入操作:
push_back(elem);
//在容器尾部添加一个数据push_front(elem);
//在容器头部插入一个数据pop_back();
//删除容器最后一个数据pop_front();
//删除容器第一个数据
指定位置操作:
insert(pos,elem);
//①在pos位置插入一个elem元素的拷贝,返回新数据的位置。insert(pos,n,elem);
//②在pos位置插入n个elem数据,无返回值。insert(pos,beg,end);
//③在pos位置插入(beg,end)区间的数据,无返回值。clear();
//清空容器的所有数据erase(beg,end);
//删除[beg,end)区间的数据,返回下一个数据的位置。erase(pos);
//删除pos位置的数据,返回下一个数据的位置。
总结:
- 插入和删除提供的位置是
迭代器(指针)
! - 尾插 — push_back
- 尾删 — pop_back
- 头插 — push_front
- 头删 — pop_front
示例:
//打印
void printDeque_i(deque<int>& d) {for (int i = 0; i < d.size(); i++) {cout << d[i] << " ";}cout << endl;
}
int main() {//3.3.5 deque 插入和删除
//两端操作deque<int> d1;//尾插d1.push_back(10);d1.push_back(20);d1.push_back(30);//头插d1.push_front(1);d1.push_front(2);d1.push_front(3);printDeque_i(d1);//3 2 1 10 20 30//尾删d1.pop_back();//头删d1.pop_front();printDeque_i(d1);//2 1 10 20//指定位置操作
//插入deque<int> d2;//尾插d2.push_back(10);d2.push_back(20);d2.push_back(30);//头插d2.push_front(1);d2.push_front(2);d2.push_front(3);printDeque_i(d2);//3 2 1 10 20 30d2.insert(d2.begin(), 66);//①在首元素的位置插入一个66printDeque_i(d2);//66 3 2 1 10 20 30d2.insert(d2.end(), 2, 55);//②在首元素的位置插入两个55printDeque_i(d2);//66 3 2 1 10 20 30 55 55deque<int> d3;d3.push_back(12345);d3.insert(d3.begin(), d2.begin(), d2.end());//③在首元素的位置插入容器d2的全部内容printDeque_i(d3);//66 3 2 1 10 20 30 55 55 12345d3.insert(d3.end(), d2.begin(), d2.end());//③在首元素的位置插入容器d2的全部内容printDeque_i(d3);//55 55 66 3 2 1 10 20 30 12345 66 3 2 1 10 20 30 55 55
//删除deque<int> d4;d4.push_back(10);d4.push_back(20);d4.push_front(100);d4.push_front(200);printDeque_i(d4);//200 100 10 20d4.erase(d4.begin());//删除首元素printDeque_i(d4);//100 10 20d4.erase(d4.begin(), d4.end());//清除d4.clear();//清除printDeque_i(d4);//空system("pause");return 0;
}
3.3.6 deque 数据存取
(和vector容器一样)
3.3.7 deque 排序
需要加头文件#include
sort(iterator beg, iterator end)
//对beg和end区间内元素进行排序
//注意:直接是sort(); 不要写成d1.sort();
案例:
//打印
void printDeque_i(deque<int>& d) {for (int i = 0; i < d.size(); i++) {cout << d[i] << " ";}cout << endl;
}
int main() {//3.3.7 deque 排序deque<int> d1;//尾插d1.push_back(10);d1.push_back(20);d1.push_back(30);//头插d1.push_front(1);d1.push_front(2);d1.push_front(3);printDeque_i(d1);//3 2 1 10 20 30sort(d1.begin(), d1.end());printDeque_i(d1);//1 2 3 10 20 30system("pause");return 0;
}
3.4 STL案例1
- 创建五名选手,放到vector中
- 遍历vector容器,取出来每一个选手,执行for循环,可以把10个评分打分存到deque容器中
- sort算法对deque容器中分数排序,去除最高和最低分
- deque容器遍历一遍,累加总分
- 获取平均分
要点:
1.vector<Person> v;
2.Person类的成员变量:string name; deque<double> d;
3.sort(d.begin(),d.end());
4.去掉一个最高分,去掉一个最低分—deque容器,头删法、尾删法(最后没用到,直接用总分减去首元素和最后一个元素:res-=d.front(); res-=d.back();
5.生成随机数:(好像没有这个srand也可以 笑哭)
—9.5更新
:这个srand不能少,有它可以确保每次重新运行程序的时候,生成的随机数和上次编译运行不一样
。
//srand((unsigned)time(NULL));//放到循环外面
for循环里面,给每个选手打十个分数:
int random = (rand() % (100 - 1 + 1) + 1);//生成[1,100]的随机数
v[i].d[j] = random;//随机数
示例:
#include<iostream>
#include<string>
#include<vector>
#include<deque>
#include<algorithm>using namespace std;//选手类
class Participants {public:string name;//选手姓名deque<double> d;//存放选手成绩的deque容器Participants(string name, deque<double>& d) {this->d = d;this->name = name;}
};//打印
void printDeque_i(deque<double>& d) {for (int i = 0; i < d.size(); i++) {cout << d[i] << " ";}cout << endl;
}int main() {//初始化一个全为0的deque容器deque<double> d_model;for (int i = 0; i < 10; i++) {d_model.push_back(0);}//创建五名选手Participants p1("A", d_model);Participants p2("B", d_model);Participants p3("C", d_model);Participants p4("D", d_model);Participants p5("E", d_model);vector<Participants> v;//容器v存放5个Person类对象v.push_back(p1);v.push_back(p2);v.push_back(p3);v.push_back(p4);v.push_back(p5);//cout << "v.size() = " << v.size() << endl;//5//cout << "v[0].d.size() = " << v[0].d.size() << endl;//10srand((unsigned)time(NULL));//rand()产生的随机数在每次运行的时候都是与上一次相同的。若要不同,用函数srand()初始化它。
// //可以利用srand((unsigned int)(time(NULL))的方法,产生不同的随机数种子,因为每一次运行程序的时间是不同的。//依次给5为选手打分,并计算平均分for (int i = 0; i < v.size(); i++) {//遍历vector容器中的Person类对象:p1,p2,p3,p4,p5 v[0]就是p1,即选手Adouble res = 0;//记总分,求平均值for (int j = 0; j < v[i].d.size(); j++) {//遍历每个Person类对象中的成员变量d: v[0].d[0]是p1也即选手A的第一个分数 int random = (rand() % (100 - 1 + 1) + 1);//生成[1,100]的随机数v[i].d[j] = random;//cout << "第" << (j + 1) << "个评委的打分为:" << v[i].d[j] << ";" << endl;res += v[i].d[j];//把10个分数累加起来}sort(v[i].d.begin(),v[i].d.end());//排序//打印排序后的分数和总分printDeque_i(v[i].d);cout << "10位评委的总分为:" << res << endl;res -= v[i].d.back();//减去最大值res -= v[i].d.front();//减去最小值//v[i].d.pop_front();//头删最小值//v[i].d.pop_back();//尾删最大值//打印掐头去尾后的总分cout << "去掉一个最高分,去掉一个最低分,总分为:" << res << endl;res /= (v[i].d.size() - 2);//最终的平均值 或者直接除以8cout << "选手" << v[i].name << "的平均分为:" << res << '\n' << endl;}system("pause");return 0;
}
结果:
3.5 stack容器(栈)
3.5.1 基本概念
stack容器最大的特点:先进后出
。
只能在栈顶
对数据进行插入(入栈
)和删除(出栈
)操作,栈底是封着的。
注意:栈中只有顶端的元素才可以被外界使用,因此栈不允许有遍历行为
。
3.5.2 stack 常用接口
构造函数:stack<T> stk;
//默认构造形式; stack(const stack &stk);
//拷贝构造
赋值操作:stack& operator=(const stack &stk);
//重载等号操作符
数据存取: push(elem);
//从栈顶添加元素; pop();
//从栈顶移除第一个元素; top();
//返回栈顶元素
大小操作:empty();
//判空 size();
//返回栈的大小
总结:
- 栈顶入栈 — push
- 栈顶出栈 — pop
- 返回栈顶元素 — top
- 判断栈是否为空 — empty
- 返回栈大小 — size
示例(stack容器中存放内置数据类型int):
(注意:看下示例中栈满和栈空之后会怎样,尤其是栈空之后!!!
)
//打印:判空+出栈 ---打出来的顺序是反的
void printStack_i(stack<int> stk) {while (!stk.empty()) {cout << stk.top() << " ";stk.pop();//出栈}cout << endl;
}
int main() {//①无参构造stack<int> stk1;for (int i = 0; i < 5; i++) {stk1.push(i + 1);//入栈}//②拷贝构造stack<int> stk2(stk1);//③赋值stack<int> stk3;stk3 = stk1;//④栈空判断+栈的大小if (stk1.empty() == true)cout << "栈空!!!" << endl;elsecout << "栈非空,且栈的大小为:" << stk1.size() << endl;//⑤存取cout << "stk1的栈顶元素为:" << stk1.top() << endl;//输出栈顶元素//⑥打印printStack_i(stk1);//5 4 3 2 1printStack_i(stk2);//5 4 3 2 1printStack_i(stk3);//5 4 3 2 1//栈空之后会怎样?//打印完之后栈都被清空,此时stk.top();和stk.pop();会报错,但依然可以输出大小(结果为0)和判空(结果为空),当然还有stk.push(26);cout << "stk3大小为:" << stk3.size() << endl;//0if (stk3.empty() == true)cout << "栈空!!!" << endl;//√elsecout << "栈非空,且栈的大小为:" << stk1.size() << endl;stk3.push(26);stk3.pop();//此时栈已清空//cout << "stk3的栈顶元素为:" << stk3.top() << ";大小为:" <<stk3.size() << endl;//报错!//stk3.pop();//报错!//栈满之后会怎样?for (int i = 0; i < 5; i++) {stk2.push(i + 1);//入栈}cout << "stk2栈的大小为:" << stk2.size() << endl;//5stk2.push(26); stk2.push(26);cout << "stk2栈的大小为:" << stk2.size() << endl;//7printStack_i(stk2);//stk2被清空system("pause");return 0;
}
3.6 queue容器(队列)
3.6.1 queue 基本概念
Queue容器最大的特点:先进先出
。
它有两个出口,只能在队尾
对数据进行插入(入队
),在队头
对数据进行删除(出队
)操作。
注意:队列中只有队头和队尾才可以被外界使用,因此队列不允许有遍历行为
。
3.6.2 queue 常用接口
构造函数:queue<T> que;
//默认构造形式; queue(const queue &que);
//拷贝构造
赋值操作:queue& operator=(const queue &que);
//重载等号操作符
数据存取: push(elem);
//从队尾添加元素; pop();
//从队头移除第一个元素; front();
//返回第一个元素 back();
//返回最后一个元素
大小操作:empty();
//判空 size();
//返回栈的大小
总结:
- 队尾入队 — push
- 队头出队 — pop
- 返回队头元素 — front
- 返回队尾元素 — back
- 判断队列是否为空 — empty
- 返回队列大小 — size
示例(queue容器中存放自定义数据类型Person类):
(注意:看下示例中队满和队空之后会怎样,尤其是队空之后!!!
)—和栈一样
//自定义数据类型Person类
class Person {public:string name;int age;//构造函数Person(string name, int age) {this->name = name;this->age = age;}
};//打印
void printQueue_i(queue<Person>& q) {while (!q.empty()) {cout << "姓名:" << q.front().name << ",年龄:" << q.front().age;q.pop();//出队}cout << endl;
}
int main() {Person p1("Tom", 25);Person p2("Jerry", 29);Person p3("Reus", 27);Person p4("Nacho", 23);Person p5("Lucas", 26);//无参构造queue<Person> q1;//入队q1.push(p1);q1.push(p2);q1.push(p3);q1.push(p4);q1.push(p5);//判空if (q1.empty() == true) cout << "队列为空!!!" << endl;elsecout << "队列非空" << endl;//√//队列大小、队头元素、队尾元素cout << "队列大小:" << q1.size() << endl;cout << "队头元素:" << q1.front().name << " " << q1.front().age << endl;cout << "队尾元素:" << q1.back().name << " " << q1.back().age << endl;//拷贝构造queue<Person> q2(q1);//赋值操作queue<Person> q3;q3 = q1;printQueue_i(q1);//姓名:Tom,年龄:25姓名:Jerry,年龄:29姓名:Reus,年龄:27姓名:Nacho,年龄:23姓名:Lucas,年龄:26printQueue_i(q2);//姓名:Tom,年龄:25姓名:Jerry,年龄:29姓名:Reus,年龄:27姓名:Nacho,年龄:23姓名:Lucas,年龄:26printQueue_i(q3);//姓名:Tom,年龄:25姓名:Jerry,年龄:29姓名:Reus,年龄:27姓名:Nacho,年龄:23姓名:Lucas,年龄:26//队空之后会怎样?cout << "队列大小:" << q2.size() << endl;//判空if (q2.empty() == true)cout << "队列为空!!!" << endl;//√elsecout << "队列非空" << endl;q2.push(p2);//入队cout << "队列大小:" << q2.size() << endl;//1//q2.pop();//报错!//cout << "队头元素:" << q2.front().name << " " << q2.front().age << endl;//报错!//cout << "队尾元素:" << q2.back().name << " " << q2.back().age << endl;//报错!//队满之后会怎样?q3.push(p1);q3.push(p2);q3.push(p3);q3.push(p4);q3.push(p5);cout << "队列大小:" << q3.size() << endl;//5q3.push(p3);cout << "队列大小:" << q3.size() << endl;//6system("pause");return 0;
}
3.7 list容器(链表)
list容器的打印
实现(有点不一样,要加const
):
void printList_i(const list<int>& L) {for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {//不能写成const list<int>::const_iterator 会报错//另外这里的const_iterator表示只读迭代器 cout << *it << " ";}cout << endl;
}
小目录:
3.7.2 list构造函数
listL1;//无参构造 listL3(L2);//拷贝构造 listL4(10, 8);//有参构造 listL2(L1.begin(),L1.end());
3.7.3 list 赋值和交换
L2 = L1; L3.assign(); L1.swap(L2);//交换
3.7.4 list 大小操作
L1.size(); L1.resize(); L1.empty();
3.7.5 list 插入和删除
L.push_back(elem);//尾插 L.pop_back();//尾删 L.push_front(elem);//头插 L.pop_front();//头删
L.insert(); L.erase(); L.remove(elem);//删除所有容器中所有等于elem的元素 L.clear();//清空
(区别于deque容器)3.7.6 list 数据存取
L.front()//首元素 L.back();//最后一个元素 (注意:list容器不支持L[i]和L.at(i)的方式)
(区别于deque容器)3.7.7 list 反转和排序
L.reverse();//反转 L.sort();//排序 (注意:不是全局函数sort(); 而是成员函数L.sort()),可以自定义排序规则。
3.7.1 list容器基本概念
功能:将数据进行链式存储
。
组成:
链表由一系列结点
组成,而结点包括(存储数据元素的)数据域
和(存储下一个结点地址的)指针域
。
STL中的链表是一个双向循环链表
,每个结点的结构如下图所示:
数据域 | 前向指针 | 后向指针 |
---|---|---|
data | prev | next |
由于链表的存储方式并不是连续的内存空间,因此链表list中的迭代器只支持前移和后移,属于双向迭代器
。
list的优点:
采用动态存储分配
,不会造成内存浪费和溢出;
链表执行插入和删除
操作十分方便,修改指针即可,不需要移动大量元素。
list的缺点:
耗空间(因为指针域
) ;
耗时间(因为遍历慢
)。
List有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的(因为vector在动态扩展时并不是在原空间之后续接新空间,而是找更大的内存空间,然后将原数据拷贝新空间,释放原空间,见3.2.1)。
总结:STL中List和vector是两个最常被使用的容器,各有优缺点。
3.7.2 list构造函数
list<T> lst;
//list采用模板类实现,对象的默认构造形式:list(beg,end);
//构造函数将(beg, end)区间中的元素拷贝给本身。list(n,elem);
//构造函数将n个elem拷贝给本身。list(const list &lst);
//拷贝构造函数。
示例:
//打印
void printList(const list<int>& L) {for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {cout << *it << " ";}cout << endl;
}int main() {//无参构造list<int>L1;L1.push_back(10);//尾插L1.push_back(20);L1.push_back(30);L1.push_back(40);printList(L1);//10 20 30 40list<int>L2(L1.begin(),L1.end());printList(L2);//10 20 30 40//拷贝构造list<int>L3(L2);printList(L3);//10 20 30 40//赋值list<int>L5;L5 = L1;printList(L5);//10 20 30 40//有参构造list<int>L4(10, 8);printList(L4);//8 8 8 8 8 8 8 8 8 8 system("pause");return 0;
}
3.7.3 list 赋值和交换
assign(beg, end);
//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);
//将n个elem拷贝赋值给本身。
list& operator=(const list &lst);
//重载等号操作符
swap(lst);
//将lst与本身的元素互换。
示例:
void printList(const list<int>& L) {for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {cout << *it << " ";}cout << endl;
}int main() {list<int>L1;L1.push_back(10);L1.push_back(20);L1.push_back(30);L1.push_back(40);printList(L1);
/*
//赋值list<int>L2;L2 = L1;printList(L2);list<int>L3;L3.assign(L2.begin(), L2.end());printList(L3);list<int>L4;L4.assign(10, 100);printList(L4);
*/
//交换list<int>L2;L2.assign(10, 3);cout << "交换前: " << endl;printList(L1);//10 20 30 40printList(L2);//3 3 3 3 3 3 3 3 3 3 cout << endl;L1.swap(L2);cout << "交换后: " << endl;printList(L1);//3 3 3 3 3 3 3 3 3 3 printList(L2);//10 20 30 40system("pause");return 0;
}
3.7.4 list 大小操作
size();
//返回容器中元素的个数
empty();
//判断容器是否为空
resize(num);
//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。
//如果容器变短,则末尾超出容器长度的元素被删除。
resize(num, elem);
//重新指定容器的长度为num,若容器变长,则以elem值填充新位置。
//如果容器变短,则末尾超出容器长度的元素被删除。
示例:
void printList(const list<int>& L) {for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {cout << *it << " ";}cout << endl;
}int main() {list<int>L1;L1.push_back(10);L1.push_back(20);L1.push_back(30);L1.push_back(40);printList(L1);//10 20 30 40//判空if (L1.empty()){cout << "L1为空" << endl;}else{cout << "L1不为空" << endl;//√cout << "L1的大小为: " << L1.size() << endl;//4}//重新指定大小L1.resize(10);printList(L1);//10 20 30 40 0 0 0 0 0 0L1.resize(2);printList(L1);//10 20system("pause");return 0;
}
3.7.5 list 插入和删除
注意(!!!):L.begin() 和 L.end() 可以进行insert()操作,但d.end() 不能进行erase()操作。
push_back(elem);//尾插
pop_back();//尾删
push_front(elem);//头插
pop_front();//头删
insert(pos,elem);//在pos位置插elem元素的拷贝,返回新数据的位置。
insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值。
insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值。
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。erase(pos);//删除pos位置的数据,返回下一个数据的位置。
remove(elem);//删除容器中所有与elem值匹配的元素。
clear();//清空
示例:
void printList(const list<int>& L) {for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {cout << *it << " ";}cout << endl;
}int main() {list<int> L;//头插L.push_front(1);L.push_front(2);L.push_front(3);//尾插L.push_back(10);L.push_back(20);L.push_back(30);printList(L);//3 2 1 10 20 30//头删L.pop_front();//尾删L.pop_back();printList(L);//2 1 10 20 //插入L.insert(L.begin(), 26);L.insert(L.end(),2, 36);printList(L);//26 2 1 10 20 36 36list<int> L1;L1.push_front(123);L1.push_back(321);L1.insert(L1.begin(),L.begin(), L.end());//在头部插入数据L1.insert(L1.end(), L.begin(), L.end());//在尾部插入数据printList(L1);//26 2 1 10 20 36 36 123 321 26 2 1 10 20 36 36//删除L.erase(L.begin());//可以在头部删除数据printList(L);//2 1 10 20 36 36//L.erase(L.end());//报错,因为L.end()位置没有数据,所以不能在尾部删除数据//删除某些元素L.remove(1); L.remove(36);printList(L);//2 10 20 //清空整个链表L.clear();cout << L.size() << endl;//0printList(L);//空system("pause");return 0;
}
3.7.6 list 数据存取
注意:list容器的迭代器是双向迭代器
,不支持随机访问
,所以不支持[]
方式访问数据,也不支持at()
访问数据。
front();
//返回第一个元素。back();
//返回最后一个元素。
示例:
int main() {list<int>L1;L1.push_back(10);L1.push_back(20);L1.push_back(30);L1.push_back(40);//cout << L1.at(0) << endl;//错误 不支持at访问数据//cout << L1[0] << endl; //错误 不支持[]方式访问数据cout << "第一个元素为: " << L1.front() << endl;//10cout << "最后一个元素为: " << L1.back() << endl;//40//list容器的迭代器是双向迭代器,不支持随机访问list<int>::iterator it = L1.begin();//it = it + 1;//错误,不可以跳跃访问,即使是+1system("pause");return 0;
}
3.7.7 list 反转和排序
①
reverse();
//反转链表
sort();
//链表排序
注意:reverse();
和sort();
都是成员函数
。
②所有不支持随机访问迭代器的容器,不可以用标准算法#include<algorithm>
(deque容器中的排序sort();是全局函数),这些容器内部会提供对应的算法,即成员函数
。
③L.sort();//默认是从小到大排序,如果要降序排列,可以自定义排序规则
。自定义排序有几点需要注意:
1.自定义排序规则的函数返回bool类型
;—不是仿函数,但和它相关
2.L.sort(MySort
);//降序 ②不要写成 MySort()
。
示例(区别:可以自定义排序规则
):
//打印
void printList(const list<int>& L) {for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {cout << *it << " ";}cout << endl;
}
//自定义排序规则
bool myCompare(int a, int b)//①返回值为bool类型
{if(a > b)return 1;
}int main() {list<int> L;L.push_back(90);L.push_back(30);L.push_back(20);L.push_back(70);printList_i(L);//90 30 20 70//反转L.reverse();printList_i(L);//70 20 30 90//排序L.sort();//升序printList_i(L);//20 30 70 90 L.sort(MySort);//降序 ②不要写成 MySort() printList_i(L);//90 70 30 20system("pause");return 0;
}
3.7.8 排序案例—list容器存放自定义数据类型
案例描述:
将自定义数据类型Person类
进行排序,Person中属性有姓名、年龄、身高。
排序规则:按照年龄进行升序
,如果年龄相同按照身高进行降序
。
总结:
对于自定义数据类型
,必须要指定排序规则
,否则编译器不知道如何进行排序;—不是仿函数,但和它相关
高级排序只是在排序规则上再进行一次逻辑规则制定,并不复杂。
示例(注意L.sort();//报错!
):
//3.7.8 自定义数据类型
class Person {public:string name;int age;int height;Person(string name, int age, int height) {this->name = name;this->age = age;this->height = height;}
};//3.7.8 打印存储自定义数据类型Person类的list容器
void printList_i(const list<Person>& L) {for (list<Person>::const_iterator it = L.begin(); it != L.end(); it++) {//cout << "姓名:" << (*it).name << "\t年龄:" << (*it).age << "\t身高:" << (*it).height << endl;cout << "姓名:" << it->name << "\t年龄:" << it->age << "\t身高:" << it->height << endl;}
}//3.7.8 自定义排序规则:先按年龄升序,年龄相等则按身高降序
bool MySort1(Person& p1,Person& p2) {if (p1.age == p2.age) {if (p1.height > p2.height)//年龄相等则按身高降序return 1;elsereturn 0;}else{if (p1.age < p2.age)//年龄升序return 1;elsereturn 0;}
}int main() {//3.7.8 排序案例Person p1("Tom", 23, 175);Person p2("Jerry", 26, 163);Person p3("Reus", 26, 185);Person p4("Lucas", 29, 177);Person p5("Messi", 26, 173);list<Person> L;L.push_back(p1);L.push_back(p2);L.push_back(p3);L.push_back(p4);L.push_back(p5);printList_i(L);//cout << endl;//开始排序//L.sort();报错 //因为对于自定义数据类型,必须要指定排序规则,否则编译器不知道如何进行排序L.sort(MySort1);printList_i(L);//system("pause");return 0;
}
结果:
3.8 set / multiset 容器(二叉树结构—自动排序)
小目录:
(有区别)3.8.2 构造和赋值:set s1;//默认构造函数 set s2(s1);//拷贝构造函数 s3 = s1;//赋值
3.8.3大小和交换:s1.size();//大小 s1.empty();//判空 s2.swap(s1);//交换
(有区别)3.8.4插入和删除:s1.insert(); s1.erase(); s1.clear();//清空
3.8.5查找和统计:s1.find(6);//返回的是迭代器 s1.count(6);//返回的是个数,找到就是1没找到就是0
(有区别)3.8.8排序:自动排序,既不是成员函数,也不是全局函数;可以利用仿函数自定义排序规则。
3.8.1 基本概念
所有元素都会在插入时自动被排序
,默认是升序
(见3.8.4 set插入与删除的插入操作部分
)。
set/multiset属于关联式容器
,底层结构是用二叉树
实现。区别是set不允许
容器中有重复的元素(见3.8.4 set插入与删除的插入操作部分
);而multiset允许
。
3.8.2 set构造和赋值 & 3.8.3 set大小和交换 & 3.8.4 set插入和删除
构造:
set<T> st;
//默认构造函数:
set(const set &st);
//拷贝构造函数
赋值:
set& operator=(const set &st);
//重载等号操作符
大小:
size();
//返回容器中元素的数目
empty();
//判断容器是否为空
交换:
swap(st);
//交换两个集合容器
插入:
insert(elem);
//在容器中插入元素。
注意:不论插入几个23(s1.insert(23);),最终只会有1个23,因为set容器不允许有重复的元素
。
删除:
erase(elem);
//删除容器中值为elem的元素。
erase(pos);
//删除pos迭代器所指的元素,返回下一个元素的迭代器。
erase(beg, end);
//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
clear();
//清除所有元素
示例:
//打印
void printSet_i(set<int> s) {for (set<int>::iterator it = s.begin(); it != s.end(); it++) {cout << *it << " ";}cout << endl;
}int main() {//默认构造函数set<int> s1;set<int> s4;for (int i = 0; i < 5; i++) {s1.insert(i + 1);s4.insert(i + 10);}printSet_i(s1);//1 2 3 4 5printSet_i(s4);//10 11 12 13 14//大小cout << "" << s1.size() << endl;//5//判空if (s1.empty()) {cout << "s1容器为空!!!" << endl;}else {cout << "s1容器非空。" << endl;//√}//交换cout << "交换后:" << endl;s1.swap(s4);printSet_i(s1);//10 11 12 13 14printSet_i(s4);//1 2 3 4 5//拷贝+赋值+插入+删除://拷贝构造函数set<int> s2(s1);printSet_i(s2);//10 11 12 13 14set<int> s3;s3 = s1;//赋值printSet_i(s3);//10 11 12 13 14//(用s2来做测试)插入---所有元素都会在插入式自动被排序(默认为升序)s2.insert(26);s2.insert(21);s2.insert(19);printSet_i(s2);//10 11 12 13 14 19 21 26//(用s3来做测试)删除---共三种方式s3.insert(23);s3.insert(23);s3.insert(23);printSet_i(s3);//10 11 12 13 14 23 这里要注意,不论插入几个23,最终只会有1个23,因为set容器不允许有重复的元素//删某个位置的元素s3.erase(s3.begin());//s3.erase(s3.end());报错!!!printSet_i(s3);//11 12 13 14 23 //删某个元素s3.erase(14);printSet_i(s3);//11 12 13 23 //删某个区间的元素,相当于清空操作s3.erase(s3.begin(), s3.end());printSet_i(s3);//空s3 = s2;//赋值printSet_i(s3);//10 11 12 13 14 19 21 26s3.clear();//清空printSet_i(s3);//空system("pause");return 0;
}
3.8.5 set查找和统计
find(key);
//查找key是否存在:若存在,返回该键的元素的迭代器
(指针,即位置);若不存在,返回set.end();
count(key);
//统计key的元素个数
示例:
//打印
void printSet_i(set<int> s) {for (set<int>::iterator it = s.begin(); it != s.end(); it++) {cout << *it << " ";}cout << endl;
}int main() {//3.8.5 set查找和统计set<int> s1;for (int i = 0; i < 5; i++) {s1.insert(1 + i);}s1.insert(10);s1.insert(10);s1.insert(12);printSet_i(s1);//1 2 3 4 5 10 12//查找---返回值是迭代器(即指针,或者说元素的位置)set<int>::iterator pos = s1.find(6);//用一个迭代器来接收查找结果,没找到就返回s.end(),找到了就返回元素的迭代器if (pos == s1.end()) {cout << "没找到" << endl;}else{cout << "找到了元素" << *pos << endl;}if (s1.find(12) == s1.end()) {cout << "没找到" << endl;}else{cout << "找到了元素" << *s1.find(12) << endl;//*s1.find(12)可以直接写12 这就相当远int a; cout<<*(&a)<<endl;}//统计---返回值为int型---因为set容器中不允许有重复的元素,所以统计的结果为0或1int res = s1.count(6);cout << "元素6的个数为:" << res << endl;//0cout << "元素12的个数为:" << s1.count(12) << endl;//1system("pause");return 0;
}
3.8.6 set和multiset区别
两个容器最大的区别就在于插入操作insert()
的区别:
set不可以插入重复数据,而multiset可以
—见示例1
。
(底层原理上的具体区别见示例2
)
示例1的三点结论:
①set容器和multiset容器的头文件都是#include< set>
,没有#include<ultiset>
;
②两个打印函数可以同名,这样就形成了重载;
③如果不允许插入重复数据可以利用set
;如果允许就利用multiset
。
示例1:
//打印set容器
void printSet_i(set<int> s) {for (set<int>::iterator it = s.begin(); it != s.end(); it++) {cout << *it << " ";}cout << endl;
}
//打印multiset容器
void printSet_i(multiset<int> s) {for (multiset<int>::iterator it = s.begin(); it != s.end(); it++) {cout << *it << " ";}cout << endl;
}int main() {//3.8.6 set和multiset区别set<int> s1;s1.insert(1);s1.insert(1);s1.insert(2);printSet_i(s1);//1 2multiset<int> s2;s2.insert(1);s2.insert(1);s2.insert(2);printSet_i(s2);//1 1 2system("pause");return 0;
}
然后来看两个容器的插入操作在底层原理上的具体区别
:
先对set容器
的插入操作insert()进行学习(区别于multiset容器
的插入操作):
set<int> s1;
s1.insert(10);
上面两行代码中insert的底层源码是:
template <bool _Multi2 = _Multi, enable_if_t<!_Multi2, int> = 0>pair<iterator, bool> insert(value_type&& _Val) {const auto _Result = _Emplace(_STD move(_Val));return {iterator(_Result.first, _Get_scary()), _Result.second};}
其中pair叫做对组
,表示一些内容(这里是iterator和bool)会成对出现,也就是说每次insert操作之后的返回值
是pair<iterator, bool>,即返回一个对组pair的内容<iterator,bool>
,分别表示把val插到了什么位置(iterator)和 是否插入成功(bool)。
想要读取这个对组pair里的内容(具体见下面的示例2),就要
先创建一个对组pair对象用来接收insert操作的返回值
:pair<set<int>::iterator,bool> pr1;
//这块要指明是什么容器的迭代器,不能只写pair<iterator,bool> pr1;
然后分别输出cout<<pr1.first<<pr1.second<<endl;
//这里直接是pr1.first,不要写成cout<<pr1.first()<<pr1.second()<<endl;
再来看multiset容器
的插入操作:
multiset<int> s2;
s2.insert(10);
//pair<multiset<int>::iterator, bool> pr2 = s2.insert(10);;//报错!!
这里insert()源码是:
template <bool _Multi2 = _Multi, enable_if_t<_Multi2, int> = 0>iterator insert(value_type&& _Val) {return iterator(_Emplace(_STD move(_Val)).first, _Get_scary());}
也就是说multiset容器
的insert操作返回值只有一个迭代器iterator
,并不是一个对组pair,所以第3行代码报错,因为不能用一个pair对象来接收insert操作的返回值。
示例2:
#include<iostream>
#include<set>
using namespace std;int main(){//set容器: set<int> s1;//第一次插入10pair<set<int>::iterator, bool> pr1 = s1.insert(10);;//①创建pair对象来接收插入操作的返回值 注意:要指明是什么容器的迭代器pair<iterator, bool> pr1;是错的if (pr1.second) {//②直接pr1.second就好,不要写成pr1.second()cout << "元素10插入成功" << endl;//√}else {cout << "元素10插入失败!!!" << endl;}//第二次插入10pr1 = s1.insert(10);if (pr1.second) {cout << "元素10插入成功" << endl;}else {cout << "元素10插入失败!!!" << endl;//√}pr1 = s1.insert(20);if (pr1.second) {cout << "元素20插入成功" << endl;//√}else {cout << "元素20插入失败!!!" << endl;}printSet_i(s1);//1 2//multiset容器: multiset<int> s2;s2.insert(10);//源码是iterator insert(value_type&& _Val){}//也就是说multiset容器的insert操作返回值只有一个迭代器,并不是一个对组pair,所以下面一行代码报错//pair<multiset<int>::iterator, bool> pr2 = s2.insert(10);;//报错!!!system("pause");return 0;
}
3.8.7 pair对组 创建
功能描述:
可以利用对组返回两个数据。
两种创建方式:
pair<type, type> p ( value1, value2 );
//p是对象名
pair<type, type> p = make_pair( value1, value2 );
示例:
int main(){//3.8.7 pair对组创建 pair<string, int> pr1("Tom", 25);cout << pr1.first << "," << pr1.second << endl;//Tom,25pair<string, int> pr2 = make_pair("Jerry", 23);cout << pr2.first << "," << pr2.second << endl;//Jerry,23system("pause");return 0;
3.8.8 set容器排序
set容器默认排序
规则为从小到大
,掌握如何改变排序规则。
主要技术点:利用仿函数
,可以改变排序规则。仿函数就是重载函数调用运算符()
,见6.函数调用运算符()重载—仿函数
示例1(//set容器存放内置数据类型):
①仿函数(重载函数调用运算符)写在一个类里,类名
写在容器的参数列表中set<int,MySort> s;
(区别于3.7.7和3.7.8的自定义排序规则的写法)
②仿函数bool operator()( int a, int b)const{}
//仿函数的返回值是bool类型,另外要在后面加上const
,否则会报错!
③打印set<int,MySort> s; 打印函数的参数是set<int,MySort> s,不是set s —另外两个打印函数形成了重载:void printSet_i()参数不一样
④创建容器的时候就要指明自定义排序规则,因为↓
set容器会对插入的数据进行自动排序,默认是从小到大排。执行一次数据插入操作insert();
的同时也已经完成了默认排序的操作,所以要想改变排序规则,就要在插入数据之前即创建容器对象的时候
就要指明自定义的排序规则
。
//3.8.8 利用仿函数自定义排序规则:降序
class MySort {public://①重载函数调用运算符()bool operator()( int a, int b)const {//②返回值是bool类型if (a > b) {return true;}else {return false;}}
};//打印set容器
void printSet_i(set<int> s) {for (set<int>::iterator it = s.begin(); it != s.end(); it++) {cout << *it << " ";}cout << endl;
}
//3.8.8 打印set<int,MySort> s;
void printSet_i(set<int,MySort> s) {//③参数是set<int,MySort> s,不是set<int> sfor (set<int,MySort>::iterator it = s.begin(); it != s.end(); it++) {//③参数是set<int,MySort> s,不是set<int> scout << *it << " ";}cout << endl;
}int main() {//set容器存放内置数据类型://set容器的默认排序规则:从小到大排set<int> s1;cout << "默认排序:" << endl;s1.insert(10);printSet_i(s1);//10s1.insert(40);printSet_i(s1);//10 40s1.insert(20);printSet_i(s1);//10 20 40s1.insert(30);printSet_i(s1);//10 20 30 40s1.insert(50);printSet_i(s1);//10 20 30 40 50s1.insert(26);printSet_i(s1);//10 20 26 30 40 50//自定义排序规则:set<int, MySort> s2;//④创建容器的时候就要指明自定义排序规则cout << "自定义排序:" << endl;s2.insert(10);s2.insert(40);printSet_i(s2);//40 10s2.insert(20);s2.insert(30);printSet_i(s2);//40 30 20 10 s2.insert(50);s2.insert(26);printSet_i(s2);//50 40 30 26 20 10 system("pause");return 0;
示例2(//set容器存放自定义数据类型):
对于自定义数据类型,set必须指定排序规则才可以插入数据。
仿函数的写法和示例1基本相同,因为set容器不允许有重复的数据,所以假入下面示例中的s3插入的数据里有年龄相同的元素就会插入失败(自定义排序规则里是按照年龄进行降序排列),例如有三个人的年龄都是26,最终就只会输出三个(不同年龄的)人的信息,所以下面又重新用multiset容器来做了一次测试。
//3.8.8 自定义数据类型
class Person {public:string name;int age;int height;Person( string name, int age, int height) {this->name = name;this->age = age;this->height = height;}
};
//3.8.8 利用仿函数自定义排序规则:按年龄从大到小降序排列
class MySort1 {public:bool operator()(const Person& p1, const Person& p2) const{//记得加constif (p1.age > p2.age) {return true;}else {return false;}}
};
//3.8.8 打印set<Person,MySort1> s;
void printSet_i(set<Person, MySort1> s) {for (set<Person, MySort1>::iterator it = s.begin(); it != s.end(); it++) {//cout << "姓名:" << it->name << "\t年龄:" << it->age << "\t身高:" << it->height << endl;cout << "姓名:" << (*it).name << "\t年龄:" << (*it).age << "\t身高:" << (*it).height << endl;}cout << endl;
}
//3.8.8 打印multiset<Person,MySort1> s;
void printSet_i(multiset<Person, MySort1> s) {for (multiset<Person, MySort1>::iterator it = s.begin(); it != s.end(); it++) {//cout << "姓名:" << it->name << "\t年龄:" << it->age << "\t身高:" << it->height << endl;cout << "姓名:" << (*it).name << "\t年龄:" << (*it).age << "\t身高:" << (*it).height << endl;}cout << endl;
}int main() {//set容器存放自定义数据类型://对于自定义数据类型,set必须指定排序规则才可以插入数据Person p1("Tom", 23, 175);Person p2("Jerry", 25, 163);//26Person p3("Reus", 28, 185);Person p4("Lucas", 29, 177);//26Person p5("Messi", 26, 173);set<Person,MySort1> s3;s3.insert(p1);s3.insert(p2);s3.insert(p3);s3.insert(p4);s3.insert(p5);printSet_i(s3);////因为set容器不允许有重复的数据,所以假入插入的数据里有年龄相同的元素就会插入失败,//上面第287和289行如果把年龄改成26,最终就只会输出三个人的信息,所以下面又重新用multiset容器来做了一次测试Person p11("Tom", 23, 175);Person p12("Jerry", 26, 163);//26Person p13("Reus", 28, 185);Person p14("Lucas", 26, 177);//26Person p15("Messi", 26, 173);multiset<Person, MySort1> s4;s4.insert(p11);s4.insert(p12);s4.insert(p13);s4.insert(p14);s4.insert(p15);printSet_i(s4);//system("pause");return 0;
}
☆☆☆总结 常用容器的排序
的区别
容器种类 | 排序函数 |
存储内置数据类型 默认是升序排列 |
存储内置数据类型 自定义排序(降序) |
存储自定义数据类型 只能自定义排序(年龄降序,年龄相同则身高升序) |
备注 |
---|---|---|---|---|---|
vector、deque |
头文件algorithm 中的sort() 函数
|
sort(v1.begin(), v1.end()); | sort(v1.begin(), v1.end(), mySort0); | sort(v2.begin(), v2.end(), mySort1); |
只需在sort()后面加上自定义的排序规则 mySort0和mySort1是全局函数的函数名 |
list |
链表容器的成员函数.sort()
|
L1.sort(); | L1.sort(mySort0); | L2.sort(mySort1); |
只需在sort()后面加上自定义的排序规则 mySort0和mySort1是全局函数的函数名 |
set、map |
没有sort() 自动排序 |
set s1; | set<int,mySort2> s2; | set<Person,mySort2> s3; |
因为是自动排序,所以在创建容器的时候就要指定排序规则 mySort2是个类名,类中实现了仿函数 |
还可以使用内建仿函数(加头文件#include<functional>
)来实现降序排列:
注意:是greater<>()
,不要只写greater<>
sort(v1.begin(), v1.end(), greater<>());
//注意:是greater<>()
,不要只写greater<>L.sort(greater<>());
//降序排列,使用内建函数对象(仿函数),就不用自己写排序规则了
代码:
#include<iostream>
#include<vector>
#include<list>
#include<string>
#include<algorithm>
#include<set>
using std::cout; using std::endl; using std::string; using std::vector; using std::iterator; using std::list; using std::set;//自定义数据类型:
class Person {public:string name;int age;int height;Person( string name, int age, int height) {this->name = name;this->age = age;this->height = height;}
};//自定义排序规则:(全局函数)
bool mySort0(int a, int b){//降序if(a > b)return 1;else return 0;
}
bool mySort1(const Person& p1, const Person& p2){//降序if(p1.age > p2.age)//按年龄降序return 1;else if(p1.age == p2.age){//如果年龄相等,就按身高升序return p1.height < p2.height;}else return 0;
}
//自定义排序规则:(仿函数)
class mySort2{public://重载函数调用运算符()bool operator()(int a, int b){return a > b;}bool operator()(const Person& p1, const Person& p2) {//constif(p1.age > p2.age)//按年龄降序return 1;else if(p1.age == p2.age){//如果年龄相等,就按身高升序return p1.height < p2.height;}else return 0;}
};//使用模板打印任何容器的所有数据:
//版本1:用auto自动推断迭代器的类型
template<class T>
void print1(const T & t){for(auto it = t.begin(); it != t.end(); it++)//auto it = begin(t); it != end(t); it++cout << *it << ", ";cout << endl;
}
//版本2:用class T::const_iterator 来表示具体类型的迭代器
template<class T>
void print2(const T & t){for(class T::const_iterator it = t.begin(); it != t.end(); it++)// cout << *it << ", ";cout << endl;
}
void printVector(vector<int> & v);
void printVector(vector<Person> & v);
void printList(list<int> & L);
void printList(list<Person> & L);void printSet(set<int> & s);
void printSet(set<int, mySort2> & s);
void printSet(set<Person, mySort2> & s);int main(){//自定义数据类型:Person p1("abc",23,176);Person p2("def",26,180);// 29Person p3("ijk",26,171);//32 Person p4("opq",26,165);Person p5("xyz",22,163);//vector容器自定义排序:vector<int> v1;v1.push_back(3);v1.push_back(9);v1.push_back(26);v1.push_back(6);v1.push_back(10);print1(v1);//3, 9, 26, 6, 10,//print2(v1);//printVector(v1);sort(v1.begin(), v1.end());//默认升序排序print1(v1);//3, 6, 9, 10, 26,//sort(v1.begin(), v1.end(), mySort0);//降序排列sort(v1.begin(), v1.end(), greater<>());//降序排列,使用内建函数对象(仿函数),就不用自己写排序规则了print1(v1);//26, 10, 9, 6, 3,vector<Person> v2;v2.push_back(p1);v2.push_back(p2);v2.push_back(p3);v2.push_back(p4);v2.push_back(p5);//print1(v2);//3, 9, 26, 6, 10,//print2(v1);printVector(v2); cout << "-----------" << endl;sort(v2.begin(), v2.end(), mySort1);//默认排序不能用,必须指明排序规则printVector(v2);//list容器自定义排序:list<int> L;L.push_back(2);L.push_back(5);L.push_back(8);L.push_front(3);L.push_front(6);L.push_front(9);print1(L);//9, 6, 3, 2, 5, 8//print2(L);//printList(L);//L.sort();//默认升序排序L.sort(greater<>()); //降序排列,使用内建函数对象(仿函数),就不用自己写排序规则了print1(L);//2, 3, 5, 6, 8, 9,L.sort(mySort0);//降序排列print1(L);//9, 8, 6, 5, 3, 2, list<Person> L2;L2.push_back(p1);L2.push_back(p2);L2.push_back(p3);L2.push_back(p4);L2.push_back(p5);printList(L2); cout << "-----------" << endl;L2.sort(mySort1);printList(L2);//set容器自定义排序:(自动排序,如果要自定义排序,就要利用仿函数,即重载函数调用运算符())set<int> s1;s1.insert(3);s1.insert(8);s1.insert(4);s1.insert(6);print1(s1);//3, 4, 6, 8, //print2(s1);////printSet(s1);//set<int,mySort2> s2;s2.insert(3);s2.insert(8);s2.insert(4);s2.insert(6);print1(s2);//8, 6, 4, 3, //print2(s2);////printSet(s2);//set<Person,mySort2> s3;//如果容器中存储的是自定义数据类型,在创建容器的时候就要指明自定义的排序规则s3.insert(p1);s3.insert(p3);s3.insert(p4);s3.insert(p2);s3.insert(p5);printSet(s3);//return 0;
}void printVector(vector<int> & v){for(vector<int>::iterator it = begin(v); it != end(v); it++)//vector<int>::iterator it = v.begin(); it != v.end(); it++cout << *it << ", ";cout << endl;
}
void printList(list<int> & L){for(list<int>::iterator it = L.begin(); it != L.end(); it++)cout << *it << ", ";cout << endl;
}
void printSet(set<int> & s){for(set<int>::iterator it = s.begin(); it != s.end(); it++)cout << *it << ", ";cout << endl;
}
void printSet(set<int, mySort2> & s){for(set<int, mySort2>::iterator it = s.begin(); it != s.end(); it++)cout << *it << ", ";cout << endl;
}
void printSet(set<Person,mySort2> & s){for(set<Person,mySort2>::iterator it = s.begin(); it != s.end(); it++){cout << "姓名:" << it->name << ";年龄:" << it->age << ";身高:" << it->height << endl;}
}
void printVector(vector<Person> & v){for(vector<Person>::iterator it = v.begin(); it != v.end(); it++){cout << "姓名:" << it->name << ";年龄:" << it->age << ";身高:" << it->height << endl;}
}
void printList(list<Person> & L){for(list<Person>::iterator it = L.begin(); it != L.end(); it++){cout << "姓名:" << it->name << ";年龄:" << it->age << ";身高:" << it->height << endl;}
}
结果:
相同点:
1.都可以自定义排序规则(利用仿函数:返回值是bool类型
,重载函数调用运算符()
);
2.如果容器中存放的是自定义数据类型
,就必须要指明排序规则;
3.也可以直接使用内建仿函数
(大于仿函数greater<>()
)直接实现降序
排列,需要包含头文件#include<functional>
。
不同点:
一、vector容器,deque容器:
(加头文件#include<algorithm>
)
①默认排序规则:升序
sort(d1.begin(),d1.end());
//注意:直接是sort(); 不要写成d1.sort();
这里的sort();
是全局函数
,
②如果要自定义排序规则,可以利用全局函数(不是仿函数)
来实现降序:
sort(v2.begin(), v2.end(), MySort);
//注意:这里是MySort,不要写MySort()
③使用内建仿函数(夹头文件#include<functional>
):降序
sort(v2.begin(), v2.end(), greater<>());
//注意:是greater<>(),不要只写greater<>。
二、list容器:
①默认排序规则:升序
L.sort();//排序
(注意:不是全局函数sort(); 而是成员函数
)
②利用仿函数自定义排序规则(降序):
L1.sort(MySort1);
//这里是MySort1,不是MySort1()
③使用内建仿函数(大于仿函数greater<>()):降序
L1.sort(greater<>());
//注意:是greater<>(),不要只写greater<>
如果容器里存放的是自定义数据类型,则必须
指明排序规则(年龄降序):L.sort(MySort1);
//这里是MySort1,不是MySort1()—和下面的set容器不一样,注意区分
(自定义数据类型:只能使用②利用仿函数自定义排序规则,①和③都不可以,L2.sort();
//报错!!!L2.sort(greater<>());
//会报错!!!)
三、set容器,map容器:
没有sort(),因为此容器会**自动排序
**,默认是升序,因此可以利用仿函数
实现自定义排序规则,并且且如果要自定义排序,在创建容器的时候就要指明排序规则,即先指明排序规则再插入数据,这个和list容器不一样。
set容器:
①使用默认排序规则:
set<int> s1; s1.insert(1); s1.insert(3); s1.insert(2);
//就可以直接输出了,结果是1 2 3,
②利用仿函数自定义排序规则(降序):结果输出3 2 1
//利用仿函数自定义排序规则(降序):
class MySort {public:bool operator()( int a, int b)const {//重载函数调用运算符,记得加const!!!if (a > b) {return true;}else {return false;}}
};int main(){//先指明排序规则set<int,MySort> s2; //注意:这里的参数只写MySort,而不是MySort()//插入数据s2.insert(1); s2.insert(3); s2.insert(2); //打印
}
③使用内建仿函数(大于仿函数greater<>()):降序
//先指明排序规则
set<int, greater<>> s2;//注意:这里的参数只写greater<>,而不是greater<>()
//插入数据
s2.insert(10); s2.insert(40); s2.insert(50);
//打印
如果set容器中存放的是自定义数据类型
,就必须在创建容器的时候指明排序规则(按照年龄降序):set<Person,MySort1> s3;
//注意:这里的参数只写MySort1,而不是MySort1()—和list容器不一样,注意区分
(自定义数据类型:只能使用②利用仿函数自定义排序规则,①和③都不可以,set<Person> s5;
//报错!!!set<Person, greater<>> s3;
//会报错!!!)
//利用仿函数自定义排序规则(按照年龄降序):
class MySort1{public:bool operator()(const Person& p1, const Person& p2)const {//重载函数调用运算符,记得加const!!!if (p1.age > p2.age) {return true;}else {return false;}}
};int main(){//先指明排序规则set<Person,MySort1> s3; //注意:这里的参数只写MySort1,而不是MySort1()//set<Person, greater<>> s3;//报错!//插入数据s3.insert(p1); s3; s3.insert(p3); s3; s3.insert(p2); //打印
}
map容器和set容器不同的地方:
1.元素不同,map容器是对组pair<Key,Value>;
2.插入的方式不同,map容器是m.insert(make_pair(3,26));
map容器的排序如下:
//利用仿函数实现自定义排序规则---按照Key值降序排列
class MySort {public:bool operator()(double a, double b)const {//加constif (a > b)return 1;elsereturn 0;}
};
//打印map<double,double> m
void printMap(map<double, double>& m) {for (map<double, double>::iterator it = m.begin(); it != m.end(); it++) {cout << "Key = " << it->first << ";\tValue = " << it->second << endl;}
}
//打印map<double,double,MySort> m
void printMap(map<double, double,MySort>& m) {for (map<double, double,MySort>::iterator it = m.begin(); it != m.end(); it++) {cout << "Key = " << it->first << ";\tValue = " << it->second << endl;}
}
//打印map<double,double,greater<>> m
void printMap(map<double, double, greater<>>& m) {for (map<double, double, greater<>>::iterator it = m.begin(); it != m.end(); it++) {cout << "Key = " << it->first << ";\tValue = " << it->second << endl;}
}int main() {//①默认排序规则---按照Key值从小到大排map<double, double> m1;m1.insert(make_pair(1.2, 2.5));m1.insert(make_pair(6, 3.8));m1.insert(make_pair(4, 3.1));printMap(m1);//结果如下://Key = 1.2 Value = 2.5//Key = 4 Value = 3.1//Key = 6 Value = 3.8cout << endl;//②自定义排序规则---在创建容器的时候就指明排序规则(按Key值降序)map<double, double,MySort> m2;m2.insert(make_pair(1.2, 2.5));m2.insert(make_pair(6, 3.8));m2.insert(make_pair(4, 3.1));printMap(m2);//结果如下://Key = 6 Value = 3.8//Key = 4 Value = 3.1//Key = 1.2 Value = 2.5cout << endl;
//③使用内建仿函数(大于仿函数greater<>()):按Key值降序map<double, double, greater<>> m5;m5.insert(make_pair(1.2, 2.5));m5.insert(make_pair(6, 3.8));m5.insert(make_pair(4, 3.1));printMap(m5);//结果如下://Key = 6 Value = 3.8//Key = 4 Value = 3.1//Key = 1.2 Value = 2.5cout << endl;
如果map容器中存放的是自定义数据类型:
//自定义数据类型:
class Person {public:string name;int age;int height;Person(string name, int age, int height) {this->name = name;this->age = age;this->height = height;}
};//打印map<double,Person> m
void printMap(map<double, Person>& m) {for (map<double, Person>::iterator it = m.begin(); it != m.end(); it++) {cout << "Key = " << it->first << ";\t姓名:" << it->second.name<< "\t年龄:" << it->second.age << "\t身高:" << it->second.height << endl;}
}
//打印map<double,Person,MySort> m
void printMap(map<double, Person,MySort>& m) {for (map<double, Person, MySort>::iterator it = m.begin(); it != m.end(); it++) {cout << "Key = " << it->first << ";\t姓名:" << it->second.name<< "\t年龄:" << it->second.age << "\t身高:" << it->second.height << endl;}
}
//打印map<double,Person,greater<>> m
void printMap(map<double, Person, greater<>>& m) {for (map<double, Person, greater<>>::iterator it = m.begin(); it != m.end(); it++) {cout << "Key = " << it->first << ";\t姓名:" << it->second.name<< "\t年龄:" << it->second.age << "\t身高:" << it->second.height << endl;}
}int main() {//④map容器中的value值是自定义数据类型Person类:Person p1("Tom", 23, 175);Person p2("Jerry", 25, 163);//26Person p3("Reus", 28, 185);Person p4("Lucas", 29, 177);//26Person p5("Messi", 26, 173);//map容器中的value值是自定义数据类型Person类:Person p1("Tom", 23, 175);Person p2("Jerry", 25, 163);//26Person p3("Reus", 28, 185);Person p4("Lucas", 29, 177);//26Person p5("Messi", 26, 173);//④默认排序规则:按Key值升序map<double, Person> m3;m3.insert(make_pair(1.1, p2));m3.insert(make_pair(6.1, p1));m3.insert(make_pair(3.1, p3));m3.insert(make_pair(9.1, p5));m3.insert(make_pair(5.1, p4));printMap(m3);//结果如下://Key = 1.1; 姓名:Jerry 年龄:25 身高:163//Key = 3.1; 姓名:Reus 年龄:28 身高:185//Key = 5.1; 姓名:Lucas 年龄:29 身高:177//Key = 6.1; 姓名:Tom 年龄:23 身高:175//Key = 9.1; 姓名:Messi 年龄:26 身高:173cout << endl;//⑤利用仿函数自定义排序规则:按Key值降序map<double, Person,MySort> m6;m6.insert(make_pair(1.1, p2));m6.insert(make_pair(6.1, p1));m6.insert(make_pair(3.1, p3));m6.insert(make_pair(9.1, p5));m6.insert(make_pair(5.1, p4));printMap(m6);//结果如下://Key = 9.1; 姓名:Messi 年龄:26 身高:173//Key = 6.1; 姓名:Tom 年龄:23 身高:175//Key = 5.1; 姓名:Lucas 年龄:29 身高:177//Key = 3.1; 姓名:Reus 年龄:28 身高:185//Key = 1.1; 姓名:Jerry 年龄:25 身高:163cout << endl;//⑥内建仿函数(大于仿函数greater<>()):按Key值降序map<double, Person, greater<>> m7;m7.insert(make_pair(1.1, p2));m7.insert(make_pair(6.1, p1));m7.insert(make_pair(3.1, p3));m7.insert(make_pair(9.1, p5));m7.insert(make_pair(5.1, p4));printMap(m7);//结果如下://Key = 9.1; 姓名:Messi 年龄:26 身高:173//Key = 6.1; 姓名:Tom 年龄:23 身高:175//Key = 5.1; 姓名:Lucas 年龄:29 身高:177//Key = 3.1; 姓名:Reus 年龄:28 身高:185//Key = 1.1; 姓名:Jerry 年龄:25 身高:163cout << endl;system("pause");return 0;
}
3.9 map multimap容器(二叉树结构—自动排序)
3.10 STL案例2
4、STL—函数对象
5、STL—常用算法
3.9、3.10、STL—函数对象&常用算法
C++笔记8:C++提高编程2:STL---标准模板库相关推荐
- C++提高编程----STL标准模板库-常用容器
STL标准模板库(Standard Template Library)-常用容器 C++的,面向对象和泛型编程,目的就是提高代码的复用性:为了建立数据结构和算法的统一标准,诞生了STL 一.STL初识 ...
- 信息学奥赛中的STL(标准模板库)--2022.09.30
1.信息学奥赛一本通 第5版 第8章 C++实用技巧与模版库(6节) 第一节 排序算法 第二节 运算符重载 第三节 字符串(string) 第四节 FIFO队列和优先队列 第五节 动态数组 第六 ...
- C++ STL 标准模板库介绍与入门
目录 1.概述 1.1.C++ 标准库 1.2.Boost库 2.STL 版本 2.1.HP 原始版本 2.2.P. J. 实现版本 2.3.RW 实现版本 2.4.SGI 实现版本 2.5.STLp ...
- 19.1 C++STL标准模板库大局观-STL总述、发展史、组成与数据结构谈
19.1 C++STL标准模板库大局观-STL总述.发展史.组成与数据结构谈 19.2 C++STL标准模板库大局观-容器分类与array.vector容器精解 19.3 C++STL标准模板库大局观 ...
- C++入门到精通 ——第七章 STL标准模板库大局观
七.STL标准模板库大局观 Author: XFFer_ 先分享一本 <C++ 标准库 第二版> ,望在STL的道路上从入门到放弃!(开玩笑的啦,愈行愈远~) 链接: https://pa ...
- STL(标准模板库)理论基础与容器
10.1 STL(标准模板库)理论基础 10.1.1基本概念 STL(Standard Template Library,标准模板库)是惠普实验室开发的一系列软件的统称.现然主要出现在C++中,但在被 ...
- 补8-5日复习内容 STL 标准模板库的容器
//有关 STL 标准模板库的函数 /* string 的 */ /* #include <iostream> #include <string> #include <w ...
- stl标准模板库_C ++标准模板库(STL)中的array :: fill()
stl标准模板库 fill() is a member function of "array container", which sets a given value to all ...
- stl标准模板库_C ++标准模板库(STL)中的数组及其常用功能
stl标准模板库 "array" is a container in C++ STL, which has fixed size, which is defined in &quo ...
- C++的STL标准模板库思维导图
STL标准模板库思维导图 C++ 语言的核心优势之一就是便于软件的重用.C++ 中有两个方面体现重用: 一是面向对象的继承和多态机制: 二是通过模板的概念实现了对泛型程序设计的支持. C++ 的标准模 ...
最新文章
- 微信小程序自定义组件Component的简单使用
- Kubernetes1.5源码分析(二) apiServer之资源注册
- linux基础 云,云计算之linux基础一
- python游戏编程入门txt-Python真好玩:教孩子学编程 PDF 完整原版
- 迎接.NET 6,《dotnet+Linux中文手册》完整PDF开放下载!
- vlc-qt编译 linux,记录一次搞vlc官方源码中Qt示例工程的过程,文件路径对话框
- 天池又上工业视觉检测算法大赛:瓶装白酒疵品质检
- 高考计算机专业最低分数线是多少,2021最低多少分可以稳上二本 高考二本分数线是多少...
- python中from的用法_Python import用法以及与from...import的区别
- 福昕PDF阅读器文本复制功能设置
- 三菱FX5U系列PLC使用MODBUS协议与仪表通信的简单说明
- 离合器膜片弹簧的优化设计matlab,基于matlab目标函数的建立优化离合器膜片弹簧的设计研究.doc...
- 软考(22)-网络存储、网络安全、网络规划与设计
- java 计算百分比 保留两位小数
- 深圳市计算机素质测试答案,广东 : 2019年深圳计算机专业素质测试真题
- php输出26个大小写英文字母
- 整数划分问题将正整数n表示成一系列正整数之和
- 为什么我会感到迷茫? 文/江湖一剑客
- linux下,代码阅读工具,understand
- linux下僵尸进程(<defunct>进程)的产生与避免
热门文章
- VMware安装Redhat虚拟机步骤
- OpenStack Days官方活动首登中国大陆,国内外云计算精英论道北京
- 使用OpenMP reduction子句求解二维数组的最小值和输出所在下标
- SQL SERVER查询生僻字问题
- components matlab,MATLAB图论工具箱—components函数
- js美化系统默认Prompt仿APP移动端弹出,可以自行修改
- 硬盘安装arch linux,ArchLinux硬盘安装
- Pytest(17)运行未提交的git(pytest-picked)
- 用Custom Element来实现UI组件
- MindFusion教程:Charting for Java Swing中的FunctionSeries