此 C++ 系列的笔记,是根据黑马程序员的 C++ 视频教程所写。这部分是容器,是整个C++语言中最重要的部分之一
STL初识
STL基本概念
- STL(Standard Template Library,标准模板库)
- STL 从广义上分为: 容器(container) 算法(algorithm) 迭代器(iterator)
STL六大组件
- 容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据。
- 算法:各种常用的算法,如sort、find、copy、for_each等
- 迭代器:扮演了容器与算法之间的胶合剂。
- 仿函数:行为类似函数,可作为算法的某种策略。
- 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。
- 空间配置器:负责空间的配置与管理。
容器、算法、迭代器
- 容器:将一些广泛使用的数据结构实现出来
-
- 序列式容器:每个元素都有固定的位置(我们现在接触的大部分都是这样),怎么定义的就怎么存放(顺序存放)
- 关联式容器:二叉树结构,各元素之间没有严格的物理顺序关系(存放的顺序可能自动排序的方式存放)
- 算法:
-
- 质变算法:运算过程中会更改区间的元素内容。拷贝、替换、删除等操作
- 非质变算法:运算过程中不会更改区间的元素内容。查找、技术、遍历等操作
- 迭代器:提供一种方法,能够访问到某个容器中的各个元素
-
- 非常类似于指针
容器算法迭代器初识
vector存放内置数据类型
- 容器:
vector
- 算法:
for_each
- 迭代器:
vector<int>::iterator
#include <vector>
#include <algorithm>
//使用for_each算法时,会调用下面的函数
void myPrint(int val){
cout << val << endl;
}
void test(){
//创建vector容器对象,并通过模板参数指定容器的数据类型
vector<int> v;
//向容器存放数据
v.push_back(10);
v.push_back(20);
v.push_back(30);
//迭代器的类型vector<int>::iterator,用来定义vector迭代器
//使用了v.begin()迭代器,指向了容器中的第一个数据
//使用了v.end()迭代器,指向了容器中的最后一个数据的下一个位置
//所以说,跟指针很像
vector<int>::iterator pBegin = v.begin();
vertor<int>::iterator pEnd = v.end();
//第一种遍历方式:
while(pBegin != pEnd){
cout << *pBegin << endl; //解引用
pBegin++;
}
//第二种遍历方式:
for(vector<int>::iterator it = v.begin();it != v.end();it++){
cout << *it << endl;
}
//第三种遍历方式:使用for_each算法
//数组的开头,数组的结尾,回调的函数
for_each(v.begin(),v.end(),myPrint);
}
vector存放自定义数据类型
- vector容器可以嵌套
vector<vector<int>> v
-
- 嵌套之后,注意解引用,解引用解的第一层还是个容器,而第二层解引用才解到了数据,因此,需要双层循环才能看到数据
#include <vector>
#include <string>
//自定义数据类型
class Person {
public:
Person(string name, int age) {
mName = name;
mAge = age;
}
string mName;
int mAge;
};
//存放对象
void test01() {
vector<Person> v;
//创建数据
Person p1("aaa", 10);
Person p2("bbb", 20);
Person p3("ccc", 30);
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
//使用for循环遍历数据
for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) {
//下面的*it是解引用,看<>中是什么,解引用解出来就是什么
//如果不适用解引用,那么it可以当做指针来使用
//下面两行一样的功能
cout << "Name:" << (*it).mName << endl;
cout << "Name:" << it->mName << endl;
}
}
//存放对象指针
void test02() {
//<>中存放指针
vector<Person*> v;
//创建数据
Person p1("aaa", 10);
Person p2("bbb", 20);
Person p3("ccc", 30);
v.push_back(&p1);
v.push_back(&p2);
v.push_back(&p3);
//使用for循环遍历
for (vector<Person*>::iterator it = v.begin(); it != v.end(); it++) {
//*it解引用,看<>中是什么,这里的解引用解出来就是指针
cout << "Name:" << (*it)->mName << endl;
}
}
string容器
- string实际上是一个类,因此有很多的成员函数(后续会讲)
string构造函数
- 构造函数原型:
-
string();
创建一个空的字符串string(const char* s);
使用字符串s初始化string(const string& str);
使用一个string对象初始化另一个string对象string(int n, char c);
使用n个字符c进行初始化
#include <string>
//string构造
void test01(){
//创建空字符串,调用无参构造函数
string s1;
cout << "str1 = " << s1 << endl;
//把const char*转换成了string
const char* str = "hello world";
string s2(str);
cout << "str2 = " << s2 << endl;
//调用拷贝构造函数,用一个string对象创建另一个string对象
string s3(s2);
cout << "str3 = " << s3 << endl;
//输出10个a
string s4(10, 'a');
cout << "str3 = " << s3 << endl;
}
string赋值
- 赋值的函数模型:
-
string& operator=(const char* s);
char*类型字符串赋值给当前的字符串string& operator=(const string &s);
把字符串s赋给当前的字符串string& operator=(char c);
字符赋值给当前的字符串string& assign(const char *s);
把字符串s赋给当前的字符串string& assign(const char *s, int n);
*把字符串s的前n个字符赋给当前的字符串string& assign(const string &s);
把字符串s赋给当前字符串string& assign(int n, char c);
用n个字符c赋给当前字符串
//赋值,与上面一一对应
void test01()
{
//char*字符串赋值给当前
string str1;
str1 = "hello world";
cout << "str1 = " << str1 << endl;
//字符串赋值给另一个字符串
string str2;
str2 = str1;
cout << "str2 = " << str2 << endl;
//char字符赋值字符串
string str3;
str3 = 'a';
cout << "str3 = " << str3 << endl;
//使用assign赋值给str4字符串
string str4;
str4.assign("hello c++");
cout << "str4 = " << str4 << endl;
//给str5赋值前五个字符
string str5;
str5.assign("hello c++",5);
cout << "str5 = " << str5 << endl;
//使用assign将字符串赋值给另一个字符串
string str6;
str6.assign(str5);
cout << "str6 = " << str6 << endl;
//给str7赋值五个x字符
string str7;
str7.assign(5, 'x');
cout << "str7 = " << str7 << endl;
}
string字符串拼接
- 就是在字符串的末尾拼接其他字符串
- 函数原型
-
string& operator+=(const char* str);
重载+=操作符string& operator+=(const char c);
重载+=操作符string& operator+=(const string& str);
重载+=操作符string& append(const char *s);
字符串s连接到当前字符串结尾string& append(const char *s, int n);
把字符串s的前n个字符连接到当前字符串结尾string& append(const string &s);
也是拼接字符串string& append(const string &s, int pos, int n);
字符串s中从pos开始的n个字符连接到字符串结尾
//字符串拼接,按照如上的顺序
void test01(){
string str1 = "我";
str1 += "爱玩游戏";
cout << "str1 = " << str1 << endl;
str1 += ':';
cout << "str1 = " << str1 << endl;
string str2 = "LOL DNF";
str1 += str2;
cout << "str1 = " << str1 << endl;
string str3 = "I";
str3.append(" love ");
//把字符串的前4个字符拼接到str3
str3.append("game abcde", 4);
str3.append(str2);
// 从str2的下标4位置开始,截取3个字符,拼接到字符串末尾
str3.append(str2, 4, 3);
cout << "str3 = " << str3 << endl;
}
string查找和替换
- 查找:查找字符串是否存在,注意参数有默认值
- find是从左往右找,rfind是从右往左找,但是返回的都是下标
-
int find(const string& str, int pos = 0) const;
查找str第一次出现位置,从pos开始查找int find(const char* s, int pos = 0) const;
查找s第一次出现位置,从pos开始查找int find(const char* s, int pos, int n) const;
从pos位置查找s的前n个字符第一次位置int find(const char c, int pos = 0) const;
查找字符c第一次出现位置int rfind(const string& str, int pos = npos) const;
查找str最后一次位置,从pos开始查找int rfind(const char* s, int pos = npos) const;
查找s最后一次出现位置,从pos开始查找int rfind(const char* s, int pos, int n) const;
从pos查找s的前n个字符最后一次位置int rfind(const char c, int pos = 0) const;
查找字符c最后一次出现位置
- 替换:在指定的位置替换字符串
-
string& replace(int pos, int n, const string& str);
替换从pos开始n个字符为字符串strstring& replace(int pos, int n,const char* s);
//替换从pos开始的n个字符为字符串s
//查找和替换
void test01(){
//查找
string str1 = "abcdefgde";
//返回的下标,因此返回的3
int pos = str1.find("de");
if (pos == -1){ //没找到就返回-1
cout << "未找到" << endl;
}
else{
cout << "pos = " << pos << endl;
}
//rfind从右往左找,但是返回的是下标,因此返回7
pos = str1.rfind("de");
cout << "pos = " << pos << endl;
}
//替换
void test02(){
//从str1的第一个位置起往后的3个字符替换成1111
//注意,是1111把bcd替换了,因此结果是a1111efgde
string str1 = "abcdefgde";
str1.replace(1, 3, "1111");
cout << "str1 = " << str1 << endl;
}
string字符串比较
- 比较两个字符串是否相等,是进行ASCII值进行比较
-
- 相等,返回0
- 大于,返回1(一般只是比较两个字符串是否相同,大小无意义)
- 小于,返回-1(一般只是比较两个字符串是否相同,大小无意义)
- 函数原型:
-
int compare(const string &s) const;
与字符串s比较int compare(const char *s) const;
与字符串s比较
//字符串比较
void test01(){
//是对每个字符一个一个进行对比
string s1 = "hello";
string s2 = "aello";
int ret = s1.compare(s2);
if (ret == 0) {
cout << "s1 等于 s2" << endl;
}
else if (ret > 0){ //h的ASCII值大于a的,因此运行这
cout << "s1 大于 s2" << endl;
}
else{
cout << "s1 小于 s2" << endl;
}
}
string字符存取
- 读取或者存放字符串的方式:
-
char& operator[](int n);
通过[]方式取字符(就是普通的C语言存取字符串的方式)char& at(int n);
通过at方法获取字符
void test01(){
string str = "hello world";
for (int i = 0; i < str.size(); i++){
//使用[]方式读取
cout << str[i] << " ";
}
for (int i = 0; i < str.size(); i++){
//使用at方式读取
cout << str.at(i) << " ";
}
//字符存取(修改)
str[0] = 'x';
str.at(1) = 'x';
}
string插入和删除
- 函数原型
-
string& insert(int pos, const char* s);
在pos位置插入字符串string& insert(int pos, const string& str);
在pos位置插入字符串string& insert(int pos, int n, char c);
在pos位置插入n个字符cstring& erase(int pos, int n = npos);
删除从Pos开始的n个字符
//字符串插入和删除
void test01(){
string str = "hello";
//插入111字符串,输出的是:h111ello
str.insert(1, "111");
//从1号位置开始3个字符删除,输出hello
str.erase(1, 3);
cout << str << endl;
}
string子串
- 就是从字符串中截取一部分,就是子串
- 函数原型:
-
string substr(int pos = 0, int n = npos) const;
返回由pos开始的n个字符组成的字符串
//子串
void test01(){
//使用substr截取一部分
string str = "abcdefg";
string subStr = str.substr(1, 3);
cout << "subStr = " << subStr << endl;
//在实际应用中,一般先find下标,然后再截取
string email = "hello@sina.com";
int pos = email.find("@");
string username = email.substr(0, pos);
cout << "username: " << username << endl;
}
vector容器
- vector数据结构和数组很相似,与数组不同之处就是,数组是静态空间,而vector是动态扩展
- 动态扩展:不是在原来空间后面接新空间,而是找到一个更大的空间存放数据,释放原来空间
vector构造函数
- 创建容器的函数原型:
-
vector<T> v;
采用模板实现类实现,默认构造函数vector(v.begin(), v.end());
将v[begin(), end())区间中的元素拷贝给本身。以为end是指向最后位置的后面一位,因此这里的区间是左闭右开vector(n, elem);
构造函数将n个elem拷贝给本身。vector(const vector &vec);
拷贝构造函数。
//打印容器中的数据
void printVector(vector<int>& v) {
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
}
void test01(){
//无参构造,使用push_back向容器中赋值
vector<int> v1;
for (int i = 0; i < 10; i++){
v1.push_back(i);
}
printVector(v1);
//使用区间元素,将区间内元素拷贝
vector<int> v2(v1.begin(), v1.end());
printVector(v2);
//给v3赋值10个100
vector<int> v3(10, 100);
printVector(v3);
//拷贝构造
vector<int> v4(v3);
printVector(v4);
}
vector赋值
- 函数原型:
-
vector& operator=(const vector &vec);
重载=操作符assign(beg, end);
将[beg, end)区间中的数据拷贝赋值给本身。assign(n, elem);
将n个elem拷贝赋值给本身
//赋值操作
void test01(){
vector<int> v1; //无参构造
for (int i = 0; i < 10; i++){
v1.push_back(i);
}
//下面是赋值操作
vector<int>v2;
v2 = v1; //重载=运算符
//使用assign赋值容器区间的数据
vector<int>v3;
v3.assign(v1.begin(), v1.end());
//使用assign赋值10个100
vector<int>v4;
v4.assign(10, 100);
}
vector容量和大小
- 对vector容器的容量和大小操作,函数原型:
-
empty();
判断容器是否为空capacity();
容器的容量size();
返回容器中元素的个数resize(int num);
重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则超出容器长度的元素被删除。resize(int num, elem);
重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除
void test01(){
vector<int> v1; //无参构造
for (int i = 0; i < 10; i++){
v1.push_back(i);
}
//empty如果为空,则返回true否则返回false
if (v1.empty()){
cout << "v1为空" << endl;
}
else{
cout << "v1不为空" << endl;
//capacity是返回容量大小
cout << "v1的容量 = " << v1.capacity() << endl;
//size是返回容器内元素的数量
cout << "v1的大小 = " << v1.size() << endl;
}
//resize 重新指定大小 ,若指定的更大,默认用0填充新位置,可以利用重载版本替换默认填充
v1.resize(15,10);
//resize 重新指定大小 ,若指定的更小,超出部分元素被删除
v1.resize(5);
}
vector插入和删除
- 函数原型:
-
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 test01(){
vector<int> v1;
//尾插
v1.push_back(10);
v1.push_back(20); //10 20
//尾删
v1.pop_back(); //10
//插入
v1.insert(v1.begin(), 100); //100 10
//插入2个
v1.insert(v1.begin(), 2, 1000); //1000 1000 100 10
//删除
v1.erase(v1.begin()); //1000 100 10
//清空
v1.erase(v1.begin(), v1.end()); //空
v1.clear(); //空
}
vector数据存取
- 函数原型:
-
at(int idx);
返回索引idx所指的数据operator[];
返回索引idx所指的数据front();
返回容器中第一个数据元素back();
返回容器中最后一个数据元素- 注:之前的begin和end都是指针,指向的第一个和最后一个+1的位置,想要看具体的数据元素需要解引用;而现在的front和back是直接返回的具体的数据元素
void test01(){
vector<int>v1;
for (int i = 0; i < 10; i++){
v1.push_back(i);
}
//使用[]存取容器内数据
for (int i = 0; i < v1.size(); i++){
cout << v1[i] << " ";
}
//使用at存取容器内数据
for (int i = 0; i < v1.size(); i++){
cout << v1.at(i) << " ";
}
cout << "v1的第一个元素为: " << v1.front() << endl;
cout << "v1的最后一个元素为: " << v1.back() << endl;
}
vector互换容器
- 将两个容器中的元素进行互换,一般用于收缩内存:
-
swap(vec);
将vec与本身的元素互换
//元素互换
void test01(){
//定义两个准备交换的容器
vector<int>v1;
for (int i = 0; i < 10; i++){
v1.push_back(i);
}
vector<int>v2;
for (int i = 10; i > 0; i--){
v2.push_back(i);
}
//互换容器,两个容器内数据互换
v1.swap(v2);
}
//收缩内存
void test02(){
//定义一个存放很多数据的容量,此时,会发现size是100000而capacity更大
vector<int> v;
for (int i = 0; i < 100000; i++) {
v.push_back(i);
}
cout << "v的容量为:" << v.capacity() << endl;
cout << "v的大小为:" << v.size() << endl;
//若此时我们将这个容器resize成3,一个很小的数量
v.resize(3);
//这时会发现,size是3而capacity很大,这是很浪费资源的
cout << "v的容量为:" << v.capacity() << endl;
cout << "v的大小为:" << v.size() << endl;
//因此可以使用swap的方式收缩内存
vector<int>(v).swap(v); //匿名对象
//收缩后,size就是3,capacity也是3
cout << "v的容量为:" << v.capacity() << endl;
cout << "v的大小为:" << v.size() << endl;
}
vector<int>(v).swap(v);
解读-
vector<int>(v)
是一个匿名对象,也就是说这里创建了一个匿名的容器对象,拷贝的v的容器,这个匿名的容器size=3,capacity=3- 匿名对象经过swap交换后,v的容器内容就是匿名对象的内容,此时v的size=3,capacity=3
- 因为匿名对象的特性,这行代码运行结束后,匿名对象就直接被释放了,因此,最后就剩下了v,而v的内存也就实现了缩小
vector预留空间
- 减少vector在动态扩展容量时的扩展次数
-
- 就是说,因为vector容器的动态扩展属性,在存放大量数据的时候,容器就需要多次的扩展内存,而使用了reserve(len)后,就相当于告诉编译器我们要存放len长度的数据,编译器就只会进行一次动态扩展(就是让编译器提前预留这些空间,不需要多次的扩展了)
- 函数原型:
-
reserve(int len);
容器预留len个元素长度,预留位置不初始化,元素不可访问。
void test01(){
vector<int> v;
//预留空间
v.reserve(100000);
//下面都是为了看动态扩展了几次
//因为每次动态扩展后,都会重新寻找一块更大的空间,那么v的首地址就会发生改变
int num = 0;
int* p = NULL;
for (int i = 0; i < 100000; i++) {
v.push_back(i);
if (p != &v[0]) {
p = &v[0];
num++;
}
}
cout << "num:" << num << endl;
}
deque容器
- 这是一个双端数组,可以对头端进行插入删除操作
- deque和vector的区别
-
- vector对于头部的插入删除效率低,数据量越大,效率越低(因为对vector进行头部插入时,需要讲整个数据向后移才能在头部插入)
- deque相对而言,对头部的插入删除速度回比vector快
- vector访问元素时的速度会比deque快,这和两者内部实现有关
deque构造函数
- 函数原型(跟vector一样)
-
deque<T>
默认构造形式deque(beg, end);
构造函数将[beg, end)区间中的元素拷贝给本身。deque(n, elem);
构造函数将n个elem拷贝给本身。deque(const deque &deq);
拷贝构造函数
//在这里函数调用容器时,需要注意:
//printDeque1函数中可以任意额修改传入容器的数据
//而一般我们不想函数可以修改容器中数据,仅仅可以读取数据
void printDeque1(deque<int>& d) {
for (deque<int>::iterator it = d.begin(); it != d.end(); it++) {
*it = 100;
cout << *it << " ";
}
}
//因此,就需要const进行限制,像下面printDeque2函数
//但是形参加了const后,deque<int>::iterator将会报错
//就是这个迭代器不符合const,
//需要修改成deque<int>::const_iterator,此时就可以只读了
void printDeque2(const deque<int>& d) {
for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
cout << *it << " ";
}
}
//deque构造
void test01() {
deque<int> d1; //无参构造函数
for (int i = 0; i < 10; i++){
d1.push_back(i);
}
printDeque2(d1);
//区间的方式构造函数
deque<int> d2(d1.begin(),d1.end());
printDeque2(d2);
//给容器内构造10个100
deque<int>d3(10,100);
printDeque2(d3);
//拷贝构造函数
deque<int>d4 = d3;
printDeque2(d4);
}
deque赋值
- 函数原型(跟vector一模一样)
-
deque& operator=(const deque &deq);
重载等号操作符assign(beg, end);
将[beg, end)区间中的数据拷贝赋值给本身。assign(n, elem);
将n个elem拷贝赋值给本身。
deque大小操作
- 函数原型(跟vector一模一样)
-
deque.empty();
判断容器是否为空deque.size();
返回容器中元素的个数deque.resize(num);
重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。deque.resize(num, elem);
重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。- 注:deuqe没有capacity容量的概念
deque插入和删除
- 函数原型(大体上与vector差不多,就是insert多了一个功能)
- 两端插入操作:
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位置的数据,返回下一个数据的位置。
//两端操作
void test01(){
deque<int> d;
//尾插
d.push_back(10); //10
d.push_back(20); //10 20
//头插
d.push_front(100); //100 10 20
d.push_front(200); //200 100 10 20
//尾删
d.pop_back(); //200 100 10
//头删
d.pop_front(); //100 10
}
//插入
void test02(){
deque<int> d;
d.push_back(10);
d.push_back(20);
d.push_front(100);
d.push_front(200); //200 100 10 20
d.insert(d.begin(), 1000); //1000 200 100 10 20
//10000 10000 1000 200 100 10 20
d.insert(d.begin(), 2,10000);
deque<int>d2;
d2.push_back(1);
d2.push_back(2);
d2.push_back(3);
//1 2 3 10000 10000 1000 200 100 10 20
d.insert(d.begin(), d2.begin(), d2.end());
}
//删除
void test03(){
deque<int> d;
d.push_back(10);
d.push_back(20);
d.push_front(100);
d.push_front(200);
d.erase(d.begin()); //100 10 20
d.erase(d.begin(), d.end()); //空
d.clear(); //空
}
deque数据存取
- 函数原型(跟vector一模一样)
-
at(int idx);
返回索引idx所指的数据operator[];
返回索引idx所指的数据(就平常读取数组的方式)front();
返回容器中第一个数据元素back();
返回容器中最后一个数据元素
deque排序
- 利用算法对容器进行排序,对vector一样有效
- 算法:
-
sort(iterator beg, iterator end)
对beg和end区间内元素进行排序,从小到大- 注:后面的算法部分,会讲解更多的排序方法
stack容器(栈容器)
stack基本概念
- starck是一种先进后出的数据结构(下面的图其实倒过来看容易理解)
- 注:因为栈顶的元素才能被外界使用,因此栈不存在遍历的行为
stack常用接口
- 栈容器的常用接口:
-
stack<T> stk;
stack采用模板类实现, stack对象的默认构造形式stack(const stack &stk);
拷贝构造函数
- 赋值操作:
-
stack& operator=(const stack &stk);
重载等号操作符
- 数据存取:
push(elem);
向栈顶添加元素pop();
从栈顶移除第一个元素top();
返回栈顶元素
- 大小操作:
empty();
判断堆栈是否为空size();
返回栈的大小
//栈容器常用接口
//想象上面的图片,入栈和出栈的操作
void test01(){
//创建栈容器 栈容器必须符合先进后出
stack<int> s;
//向栈中添加元素,叫做 压栈 入栈
s.push(10);
s.push(20);
s.push(30);
while (!s.empty()) {
//输出栈顶元素
cout << "栈顶元素为: " << s.top() << endl;
//弹出栈顶元素
s.pop();
}
cout << "栈的大小为:" << s.size() << endl;
}
queue容器(队列容器)
queue基本概念
- queue是一种先进先出的数据结构,有两个口
- 从一端新增元素,从另一端移除元素
- 注:由于只有队头和队尾可以被访问,因此不存在遍历行为
queue常用接口
- 常用接口:(跟stack差不多)
-
queue<T> que;
queue采用模板类实现,queue对象的默认构造形式queue(const queue &que);
拷贝构造函数
- 赋值操作:
queue& operator=(const queue &que);
重载等号操作符
- 数据存取:
push(elem);
往队尾添加元素pop();
从队头移除第一个元素back();
返回最后一个元素front();
返回第一个元素
- 大小操作:
empty();
判断堆栈是否为空size();
返回栈的大小
class Person{
public:
Person(string name, int age){
this->m_Name = name;
this->m_Age = age;
}
string m_Name;
int m_Age;
};
//队列中存放类
void test01() {
//创建队列
queue<Person> q;
//准备数据
Person p1("唐僧", 30);
Person p2("孙悟空", 1000);
Person p3("猪八戒", 900);
Person p4("沙僧", 800);
//向队列中添加元素 入队操作
q.push(p1);
q.push(p2);
q.push(p3);
q.push(p4);
//队列不提供迭代器,更不支持随机访问
while (!q.empty()) {
//输出队头元素
cout << "队头元素-- 姓名: " << q.front().m_Name
<< " 年龄: "<< q.front().m_Age << endl;
cout << "队尾元素-- 姓名: " << q.back().m_Name
<< " 年龄: " << q.back().m_Age << endl;
//弹出队头元素
q.pop();
}
cout << "队列大小为:" << q.size() << endl;
}
list容器(链表)
list基本概念
- 链表:是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的
- 链表有一系列结点组成,结点是由存储数据元素的数据域和存储下一个结点地址的指针域组成
注:其中prev指向前一个数据的地址,next指向下一个数据的地址
- list的优点:
- 采用动态存储分配,不会造成内存浪费和溢出
- 链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素(如果是普通的数组存储,那么在插入或者删除的时候,需要移动后面的数据,而使用链表,就可以直接插入或者删除,然后修改指针域的指向地址就可以了,大大加速了插入和删除的速度)
- list的缺点:
- 链表灵活,但是空间(指针域) 和 时间(遍历)额外耗费较大(因为存储的不只有数据元素本身,还有指针,因此空间消耗大;由于每次读取都要使用指针找到下个数据元素,而不会像数组直接拿到,因此在遍历链表的时候会消耗大)
注:List有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的。(list在进行插入和删除的时候直接操作就可以,然后修改指针域的指向,就完成了操作,此时迭代器不会出现变化;而vector在进行插入时,可能会由于当前存储数据的空间不够,而发生动态扩展,此时迭代器指向的位置就发生了变化)
list构造函数
- 函数原型:(跟vector一样)
-
list<T> lst;
list采用模板类实现,对象的默认构造形式:list(beg,end);
构造函数将[beg, end)区间中的元素拷贝给本身。list(n,elem);
构造函数将n个elem拷贝给本身。list(const list &lst);
拷贝构造函数。
void test01(){
list<int>L1; //无参构造
L1.push_back(10);
L1.push_back(20);
L1.push_back(30);
//区间的方式构造
list<int>L2(L1.begin(),L1.end());
//拷贝构造
list<int>L3(L2);
//构造10个1000
list<int>L4(10, 1000);
}
list赋值和交换
- 函数原型:(与vector一样)
-
assign(beg, end);
将[beg, end)区间中的数据拷贝赋值给本身。assign(n, elem);
将n个elem拷贝赋值给本身。list& operator=(const list &lst);
重载等号操作符swap(lst);
将lst与本身的元素互换。
//赋值和交换
void test01(){
list<int>L1;
L1.push_back(10);
L1.push_back(20);
//赋值
list<int>L2;
L2 = L1;
//区间赋值
list<int>L3;
L3.assign(L2.begin(), L2.end());
//赋值10个100
list<int>L4;
L4.assign(10, 100);
}
//交换
void test02(){
list<int>L1;
L1.push_back(10);
list<int>L2;
L2.assign(10, 100);
//交换两个容器的内容
L1.swap(L2);
}
list大小操作
- 函数原型:(跟vector一样)
-
size();
返回容器中元素的个数empty();
判断容器是否为空resize(num);
重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。resize(num, elem);
重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
list插入和删除
- 函数原型:(跟vector差不多,多一个remove)
-
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)区间的数据,无返回值。clear();
移除容器的所有数据erase(beg,end);
删除[beg,end)区间数据,返回下一个数据位置。erase(pos);
删除pos位置的数据,返回下一个数据的位置。remove(elem);
删除容器中所有与elem值匹配的元素。(elem是具体的什么数据,就是说删除了指定的什么数据,而不知删除的某个位置的数据,并且,当容器中多次出现这个数据,都会被删除)
list数据存取
- 函数原型:
-
front();
返回第一个元素。back();
返回最后一个元素。
//数据存取
void test01(){
list<int>L1;
L1.push_back(10);
L1.push_back(20);
//cout << L1.at(0) << endl;//错误 不支持at访问数据
//cout << L1[0] << endl; //错误 不支持[]方式访问数据
cout << "第一个元素为: " << L1.front() << endl;
cout << "最后一个元素为: " << L1.back() << endl;
}
list反转和排序
- 将容器中的数据元素反转(以前1 2 3反转后3 2 1)
- 函数原型:
-
reverse();
反转链表,前面的预留空间是reservesort();
链表排序
//反转和排序
//下面这个是指定排序规则
//如果自定义的数据需要排序,可以将形参换成类,然后自定义排序规则
//需要返回bool的值
bool myCompare(int val1 , int val2){
//如果要降序,那么就要前一个数 > 后一个数
return val1 > val2;
}
void test01(){
list<int> L;
L.push_back(90);
L.push_back(30);
L.push_back(20);
//反转容器的元素
L.reverse();
//排序
//这里不能使用sort(L.begin(),L.end())
//因为L不能随机访问,不能时候全局的排序函数,而应该使用自身的
L.sort(); //默认的排序规则 从小到大
//需要使用myCompare自定义函数,自定义升序还是降序
L.sort(myCompare); //指定规则,从大到小,降序
}
set/multiset容器
- 所有元素都会在插入时自动被排序
- set和multiset区别:
- set不允许容器中有重复的元素,因此向set容器中插入重复数据会失效
- multiset允许容器中有重复的元素
set构造和赋值
- 函数原型:
-
set<T> st;
默认构造函数:set(const set &st);
拷贝构造函数
- 赋值:
set& operator=(const set &st);
//重载等号操作符
//构造和赋值
void test01(){
set<int> s1;
//set容器中添加数据只能insert,不能使用之前的push_back
s1.insert(10);
s1.insert(30);
s1.insert(20);
s1.insert(40); //自动排序10 20 30 40
//这时候就算插入一个10的数据,也不会出现在容器中
//拷贝构造
set<int>s2(s1);
//赋值
set<int>s3;
s3 = s2;
}
set大小和交换
- 函数原型:(跟前面一样的使用方式)
-
size();
返回容器中元素的数目empty();
判断容器是否为空swap(st);
交换两个集合容器
set插入和删除
- 函数原型:
insert(elem);
在容器中插入元素。clear();
清除所有元素erase(pos);
删除pos迭代器所指元素,返回下一个元素的迭代器。erase(beg, end);
删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。(可以使用begin和end)erase(elem);
删除容器中值为elem的元素。
//插入和删除
void test01(){
set<int> s1;
//插入
s1.insert(10);
s1.insert(30);
s1.insert(20);
s1.insert(40); //10 20 30 40
//删除
s1.erase(s1.begin()); //20 30 40
s1.erase(30); //20 40
//清空
//s1.erase(s1.begin(), s1.end());
s1.clear();
}
set查找和统计
- 函数原型:
find(key);
查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();count(key);
统计key的元素个数(对于set容器,这个值只能是0或者1,因为set容器不存放重复的数据)
//查找和统计
void test01(){
set<int> s1;
//插入
s1.insert(10);
s1.insert(30);
s1.insert(20);
s1.insert(40);
s1.insert(30);
//查找,返回的是迭代器
set<int>::iterator pos = s1.find(30);
if (pos != s1.end()){
cout << "找到了元素 : " << *pos << endl;
}
else{
cout << "未找到元素" << endl;
}
//统计,set容器,要么0要么1
int num = s1.count(30);
}
set和multiset的区别
- 区别:
- set不可以插入重复数据,而multiset可以
- set插入数据的同时会返回插入结果,表示插入是否成功,这个返回的结果是个对组pair,里面包含两个数据,可以使用first和second访问,注意,这里的first和second没有括号()
- multiset不会检测数据,因此可以插入重复数据
//set和multiset区别
void test01(){
set<int> s;
//下面就是insert返回的对组pair,由迭代器和bool数据组成,
//其中的bool代表这容器是否插入成功
pair<set<int>::iterator, bool> ret = s.insert(10);
if (ret.second) {
cout << "第一次插入成功!" << endl;
}
else {
cout << "第一次插入失败!" << endl;
}
//由于set不可插入重复的数据,因此下面再次插入就会失败
ret = s.insert(10);
if (ret.second) {
cout << "第二次插入成功!" << endl;
}
else {
cout << "第二次插入失败!" << endl;
}
//multiset
multiset<int> ms;
//multiset可以重复的插入重复的数据
ms.insert(10);
ms.insert(10);
for (multiset<int>::iterator it = ms.begin(); it != ms.end(); it++) {
cout << *it << " ";
}
}
pair对组创建
- 成对出现的数据,利用对组可以返回两个数据(一般用来存放两个有相关关系的数据)
- 两种创建方式:
pair<type, type> p ( value1, value2 );
pair<type, type> p = make_pair( value1, value2 );
//对组创建
void test01(){
pair<string, int> p(string("Tom"), 20);
cout << "姓名: " << p.first << " 年龄: " << p.second << endl;
//make_pair常用
pair<string, int> p2 = make_pair("Jerry", 10);
cout << "姓名: " << p2.first << " 年龄: " << p2.second << endl;
}
set容器排序
- 利用仿函数,改变排序规则
//set存放内置数据类型int数据,进行自定义排序
//下面是仿函数,实际上是个类,指定前一个数要大于后一个数,升序
class MyCompare {
public:
bool operator()(int v1, int v2) {
return v1 > v2; //降序
}
};
void test01() {
set<int> s1;
s1.insert(10);
s1.insert(40);
s1.insert(20);
s1.insert(30);
s1.insert(50);
//默认从小到大
for (set<int>::iterator it = s1.begin(); it != s1.end(); it++) {
cout << *it << " ";
}
//指定排序规则
//set<int>这是平常使用的set容器,因为没有写后面的参数
//所以是默认使用降序,现在我们指定使用MyCompare仿函数指定拍苏方式
set<int,MyCompare> s2;
s2.insert(10);
s2.insert(40);
s2.insert(20);
s2.insert(30);
s2.insert(50);
for (set<int, MyCompare>::iterator it = s2.begin(); it != s2.end(); it++) {
cout << *it << " ";
}
}
//set存放自定义数据类型
class Person{
public:
Person(string name, int age){
this->m_Name = name;
this->m_Age = age;
}
string m_Name;
int m_Age;
};
//下面是自定义排序规则
class comparePerson{
public:
bool operator()(const Person& p1, const Person &p2){
//按照年龄进行排序 降序
return p1.m_Age > p2.m_Age;
}
};
void test01(){
//存放自定义的数据类型,编译器并不知道该怎么排序
//因此,一般自定义的数据类型都需要自定义排序规则
set<Person, comparePerson> s;
Person p1("刘备", 23);
Person p2("关羽", 27);
Person p3("张飞", 25);
Person p4("赵云", 21);
s.insert(p1);
s.insert(p2);
s.insert(p3);
s.insert(p4);
//注意下面的迭代器也需要写出comparePerson仿函数
for (set<Person, comparePerson>::iterator it = s.begin(); it != s.end(); it++){
cout << "姓名: " << it->m_Name << " 年龄: " << it->m_Age << endl;
}
}
map/multimap容器
- map中所有元素都是pair,也就是对组
- pair中第一个元素为key(键值),起到索引作用,第二个元素为value(实值),这既是键值对
- 所有元素都会根据元素的键值自动排序。注意:是根据键值排序而不是实值排序
- map的优点:可以根据key值快速找到value值
- map和multimap区别:
- map不允许容器中有重复key值元素,注意是key不是value
- multimap允许容器中有重复key值元素,注意是key不是value
map构造和赋值
- 函数原型:
- 构造:
-
map<T1, T2> mp;
map默认构造函数:
-
map(const map &mp);
拷贝构造函数
- 赋值:
-
map& operator=(const map &mp);
重载等号操作符
//map构造和赋值
void printMap(map<int,int>&m){
for (map<int, int>::iterator it = m.begin(); it != m.end(); it++){
//下面访问map的数据时,如果使用(*it)那么就得到的是键值对
//因此,可以使用(*it).first访问数据
//也可以把迭代器it当成指针,指向数据,进行访问
cout << "key = " << it->first << " value = " << it->second << endl;
}
}
void test01(){
map<int,int>m; //默认构造
//向map中插入键值对,需要使用pair对组
m.insert(pair<int, int>(1, 10));
m.insert(pair<int, int>(2, 20));
m.insert(pair<int, int>(3, 30));
printMap(m);
map<int, int>m2(m); //拷贝构造
printMap(m2);
map<int, int>m3;
m3 = m2; //赋值
printMap(m3);
}
map大小和交换
- 函数原型:(跟之前一样,没什么区别)
-
size();
返回容器中元素的数目empty();
判断容器是否为空swap(st);
交换两个集合容器
map插入和删除
- 函数原型:
-
insert(elem);
在容器中插入元素。clear();
清除所有元素erase(pos);
删除pos迭代器所指元素,返回下一个元素的迭代器。erase(beg, end);
删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。erase(key);
删除容器中值为key的元素。
void test01(){
//插入
map<int, int> m;
//第一种插入方式
m.insert(pair<int, int>(1, 10));
//第二种插入方式,常用
m.insert(make_pair(2, 20));
//第三种插入方式
m.insert(map<int, int>::value_type(3, 30));
//第四种插入方式
//这种方式不适合赋值,但是可以读取数据
//因为此时如果写m[5]依然可以运行,尽管没有第5个key值
//但是运行m[5]会创建一个key=5,value=0的键值对
//因此不应该用这种方式进行数据插入
m[4] = 40;
//删除第一个
m.erase(m.begin());
//删除key为3的键值对
m.erase(3);
//清空
m.erase(m.begin(),m.end());
m.clear();
}
map查找和统计
- 函数原型:(跟set一模一样)
-
find(key);
查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();count(key);
统计key的元素个数,对于map容器这个数值要么是0要么是1,跟set一样
//查找和统计
void test01(){
map<int, int>m;
m.insert(pair<int, int>(1, 10));
m.insert(pair<int, int>(2, 20));
m.insert(pair<int, int>(3, 30));
//查找
map<int, int>::iterator pos = m.find(3);
if (pos != m.end()){
cout << "找到了元素 key = " << (*pos).first << " value = " << (*pos).second << endl;
}
else{
cout << "未找到元素" << endl;
}
//统计,找key=3的数据的数量
int num = m.count(3);
}
map容器排序
- map容器默认排序规则为按照key值进行从小到大排序
- 使用仿函数,自定义排序规则(跟set一样)
//自定义仿函数,排序规则
class MyCompare {
public:
bool operator()(int v1, int v2) {
return v1 > v2;
}
};
void test01() {
//默认从小到大排序
//利用仿函数实现从大到小排序
//注意仿函数写的位置
map<int, int, MyCompare> m;
m.insert(make_pair(1, 10));
m.insert(make_pair(2, 20));
m.insert(make_pair(3, 30));
m.insert(make_pair(4, 40));
m.insert(make_pair(5, 50));
//下面的迭代器也需要带有仿函数
for (map<int, int, MyCompare>::iterator it = m.begin(); it != m.end(); it++) {
cout << "key:" << it->first << " value:" << it->second << endl;
}
}