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六大组件
六大组件为:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器。主要学习前四大组件

  1. 容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据。
  2. 算法:各种常用的算法,如sort、find、copy、for_each等
  3. 迭代器:扮演了容器与算法之间的胶合剂。
  4. 仿函数:行为类似函数,可作为算法的某种策略。见6.函数调用运算符()重载—仿函数 —3.7.7、3.7.8、3.8.8也有涉及到仿函数
  5. 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。
  6. 空间配置器:负责空间的配置与管理。

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); //尾部插入元素ele
  • pop_back(); //删除最后一个元素
  • insert(const_iterator pos, ele); //①迭代器指向位置pos插入元素ele
  • insert(const_iterator pos, int count,ele);
    //②迭代器指向位置pos插入count个元素ele
  • erase(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

  1. 创建五名选手,放到vector中
  2. 遍历vector容器,取出来每一个选手,执行for循环,可以把10个评分打分存到deque容器中
  3. sort算法对deque容器中分数排序,去除最高和最低分
  4. deque容器遍历一遍,累加总分
  5. 获取平均分

要点:
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---标准模板库相关推荐

  1. C++提高编程----STL标准模板库-常用容器

    STL标准模板库(Standard Template Library)-常用容器 C++的,面向对象和泛型编程,目的就是提高代码的复用性:为了建立数据结构和算法的统一标准,诞生了STL 一.STL初识 ...

  2. 信息学奥赛中的STL(标准模板库)--2022.09.30

    1.信息学奥赛一本通 第5版 第8章 C++实用技巧与模版库(6节) 第一节  排序算法 第二节 运算符重载 第三节  字符串(string) 第四节 FIFO队列和优先队列 第五节  动态数组 第六 ...

  3. 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 ...

  4. 19.1 C++STL标准模板库大局观-STL总述、发展史、组成与数据结构谈

    19.1 C++STL标准模板库大局观-STL总述.发展史.组成与数据结构谈 19.2 C++STL标准模板库大局观-容器分类与array.vector容器精解 19.3 C++STL标准模板库大局观 ...

  5. C++入门到精通 ——第七章 STL标准模板库大局观

    七.STL标准模板库大局观 Author: XFFer_ 先分享一本 <C++ 标准库 第二版> ,望在STL的道路上从入门到放弃!(开玩笑的啦,愈行愈远~) 链接: https://pa ...

  6. STL(标准模板库)理论基础与容器

    10.1 STL(标准模板库)理论基础 10.1.1基本概念 STL(Standard Template Library,标准模板库)是惠普实验室开发的一系列软件的统称.现然主要出现在C++中,但在被 ...

  7. 补8-5日复习内容 STL 标准模板库的容器

    //有关 STL 标准模板库的函数 /* string 的 */ /* #include <iostream> #include <string> #include <w ...

  8. stl标准模板库_C ++标准模板库(STL)中的array :: fill()

    stl标准模板库 fill() is a member function of "array container", which sets a given value to all ...

  9. stl标准模板库_C ++标准模板库(STL)中的数组及其常用功能

    stl标准模板库 "array" is a container in C++ STL, which has fixed size, which is defined in &quo ...

  10. C++的STL标准模板库思维导图

    STL标准模板库思维导图 C++ 语言的核心优势之一就是便于软件的重用.C++ 中有两个方面体现重用: 一是面向对象的继承和多态机制: 二是通过模板的概念实现了对泛型程序设计的支持. C++ 的标准模 ...

最新文章

  1. 微信小程序自定义组件Component的简单使用
  2. Kubernetes1.5源码分析(二) apiServer之资源注册
  3. linux基础 云,云计算之linux基础一
  4. python游戏编程入门txt-Python真好玩:教孩子学编程 PDF 完整原版
  5. 迎接.NET 6,《dotnet+Linux中文手册》完整PDF开放下载!
  6. vlc-qt编译 linux,记录一次搞vlc官方源码中Qt示例工程的过程,文件路径对话框
  7. 天池又上工业视觉检测算法大赛:瓶装白酒疵品质检
  8. 高考计算机专业最低分数线是多少,2021最低多少分可以稳上二本 高考二本分数线是多少...
  9. python中from的用法_Python import用法以及与from...import的区别
  10. 福昕PDF阅读器文本复制功能设置
  11. 三菱FX5U系列PLC使用MODBUS协议与仪表通信的简单说明
  12. 离合器膜片弹簧的优化设计matlab,基于matlab目标函数的建立优化离合器膜片弹簧的设计研究.doc...
  13. 软考(22)-网络存储、网络安全、网络规划与设计
  14. java 计算百分比 保留两位小数
  15. 深圳市计算机素质测试答案,广东 : 2019年深圳计算机专业素质测试真题
  16. php输出26个大小写英文字母
  17. 整数划分问题将正整数n表示成一系列正整数之和
  18. 为什么我会感到迷茫? 文/江湖一剑客
  19. linux下,代码阅读工具,understand
  20. linux下僵尸进程(<defunct>进程)的产生与避免

热门文章

  1. VMware安装Redhat虚拟机步骤
  2. OpenStack Days官方活动首登中国大陆,国内外云计算精英论道北京
  3. 使用OpenMP reduction子句求解二维数组的最小值和输出所在下标
  4. SQL SERVER查询生僻字问题
  5. components matlab,MATLAB图论工具箱—components函数
  6. js美化系统默认Prompt仿APP移动端弹出,可以自行修改
  7. 硬盘安装arch linux,ArchLinux硬盘安装
  8. Pytest(17)运行未提交的git(pytest-picked)
  9. 用Custom Element来实现UI组件
  10. MindFusion教程:Charting for Java Swing中的FunctionSeries