C++ STL学习(一)——容器

C++ STL学习笔记(一)——容器

琐记

 为了准备蓝桥杯不得不学习C++STL部分,这样能够省下来不少的时间;这篇笔记是看B站上黑马程序员的课程的时候记得:

黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难

1.STL初识

1.1 STL基本概念

  • STL(Standard Template Library,标准模板库)
  • STL从广义上分为:①容器(container) ②算法(algorithm) ③迭代器(iterator)
  • 容器和算法之间通过迭代器连接
  • STL几乎所有的代码都采用了模板类或者模板函数

1.2 STL六大组件

STL大体分为六种:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器。

  1. 容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据
  2. 算法:各种常用的算法,如sort、find、copy等
  3. 迭代器:容器和算法之间连接器
  4. 仿函数:行为类似函数,作为算法的某种策略
  5. 适配器:用来修饰容器或者仿函数或迭代器接口
  6. 空间配置器:负责空间的配置与管理

1.3 STL中容器、算法、迭代器

1.3.1 容器

容器:放置数据
 STL容器就是运用最广泛的一些数据结构实现
 常用的数据结构:数组、链表、树、队列、集合、映射表
 这些容器分为序列式容器关联式容器两种:
  序列式容器:强调值的顺序,每个元素都有固定的位置
  关联式容器:二叉树结构,没有严格上的物理上的顺序关系

1.3.2 算法

算法:有限的步骤解决逻辑/数学问题
 算法分为:质变算法,非质变算法
 质变:会更改元素内容的算法、删除拷贝
 非质变:运算不会改变元素内容,计数 查找

1.3.3 迭代器

迭代器:算法要通过迭代器才能访问容器中的数据
 每个容器都有自己的迭代器
用法类似于指针

常用迭代器种类有:双向迭代器、随机访问迭代器

1.4容器算法迭代器初识

 在了解STL容器算法迭代器概念后,利用代码来加深理解
 下面用最常用的容器:Vector,可以理解为数组来演示

1.4.1 vector存放内置数据类型

 容器:vector
 算法:for_each
 迭代器:vector< int>::iterator

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

void myPrint(int val){
    
    
	printf("%d\n",val);
}
void test01(){
    
    
	vector<int> v;//创建vector容器对象,通过模板参数指定容器中存放的数据类型 
	
	v.push_back(10); //放入数据 
	v.push_back(20);
	v.push_back(30);
	
	/* 第一种遍历
	 * v.begin()返回指向容器中第一个数据的迭代器
	 * v.end()返回指向容器中最后一个数据下一位的迭代器
	 * vector<int>::iterator 是vector<int>类型的迭代器
	 */
	vector<int>::iterator itBegin = v.begin();	//itBegin指向第一个元素 
	vector<int>::iterator itEnd = v.end();		//itEnd指向最后一个元素的下一位 
	while(itBegin != itEnd){
    
    
		cout<<*itBegin<<endl;
		itBegin++;
	}
	
	/*第二种遍历*/ 
	for(vector<int>::iterator it = v.begin();it!=v.end();it++){
    
    
		cout<<*it<<endl;
	}
	
	/*第三种遍历:利用STL提供的遍历算法*/
	for_each(v.begin(),v.end(),myPrint); 
	
}
int main(){
    
    
	test01();
	return 0;
} 

1.4.2 vector存放自定义数据类型

(*it)到最后是什么类型,可以直接看迭代器尖括号里面的东西即可
就像下面的
函数1,(*it)是Person类型
函数2,(*it)是Person * 类型
#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
using namespace std;

class Person{
    
    
	public: 
		Person(string name, int age){
    
    
			this->name = name;
			this->age = age;
		}
		string name;
		int age;
}; 

void test01(){
    
    
	vector<Person> v;
	
	Person p1("zhao",1);
	Person p2("qina",2);
	Person p3("swun",3);
	
	//存放数据 
	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);
	
	//遍历数据 
	for(vector<Person>::iterator it=v.begin();it!=v.end();it++){
    
    
		cout<<(*it).name<<" "<<(*it).age<<endl;
	}
}

void test02(){
    
    
	vector<Person*> v;
	
	Person p1("zhao",1);
	Person p2("qina",2);
	Person p3("swun",3);
	
	//存放数据 
	v.push_back(&p1);
	v.push_back(&p2);
	v.push_back(&p3);
	
	//遍历数据 
	//*在这里*it是Person类型的指针*/ 
	for(vector<Person*>::iterator it=v.begin();it!=v.end();it++){
    
    
		cout<<(*it)->name<<" "<<(*it)->age<<endl;
	}
}

int main(){
    
    
	test01();
	test02();
	return 0;
} 

1.4.3 vector容器嵌套容器

学习目标:容器中嵌套容器,将所有数据遍历输出
例:

#include<iostream>
#include<vector>
using namespace std;

//容器嵌套容器 
void test01(){
    
    
	vector< vector<int> >v;	

	vector<int> v1;
	vector<int> v2;
	vector<int> v3;

	for(int i = 0;i<3;i++){
    
    
		v1.push_back(i);
		v2.push_back(i+1);
		v3.push_back(i+2);		
	}
	
	//将小容器插入到大容器中 
	v.push_back(v1); 
	v.push_back(v2); 
	v.push_back(v3); 
	
	//通过大容器,将数据遍历
	for(vector<vector<int> >::iterator it=v.begin();it!=v.end();it++){
    
    
		//(*it) ----------容器 vector<int> 
		for(vector<int>::iterator vit=(*it).begin();vit != (*it).end();vit++){
    
    
			cout<< *vit <<" "; 
		}
		cout<<endl;
	} 
}
int main(){
    
    
	test01();
	return 0;
}


2. STL常用容器

2.1 string容器

2.1.1 string基本概念

本质:

 - string是C++风格的字符串,而string本质上是一个类
string和char *的区别:
 - char *是一个指针
 - string是一个类,类内部封装了char*,是char*型的容器

特点:

 - string类内部封装了许多成员方法(例如:find,copy,delete)
 - string管理char*所分配的空间,不用担心下标越界

2.1.2 string构造函数

构造函数原型:

 - string();				//创建空的字符串
 - string(const char* s);	//使用字符串s初始化
 - string(const string& str);//使用字符串对象来初始化另一个string对象
 - string(int n,char c);	//使用n个字符c初始化
/*example*/
#include<iostream>
#include<vector>
using namespace std;

//string初始化 
void test01(){
    
    
	string str1;
	string str2("字符串2");
	string str3(str2);
	string str4(6,'a');

	cout << str1 << endl; 
	cout << str2 << endl;
	cout << str3 << endl;
	cout << str4 << endl;
}
int main(){
    
    
	test01();
	return 0;
}

2.1.3 string赋值操作

功能描述:

 - 给string字符串赋值(= 或者assign)

赋值操作原型函数

赋值操作一共有两种:一种是直接用等号=,原型如下
 - string& operator=(const char* s);//char* 类型字符串,赋值给当前字符串
 - string& operator=(const string &s);//把字符串赋值给当前的字符串
 - string& operator=(char c);//字符赋值给当前的字符串

另一种是assign()
 - 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赋给当前字符
/*example*/
#include<iostream>
using namespace std;

//string初始化 
void test01(){
    
    
	string str1;
	string str2;
	string str3;
	string str4;
	string str5;
	string str6;
	string str7;
	
	/*用等号来赋值*/
	str1 = "hello world"; 
	str2 = str1;
	str3 = "a";
	
	str4.assign("helloworld");
	str5.assign("string5",3);
	str6.assign(str1);
	str7.assign(5,'a');
	
	cout<<str1<<endl;
	cout<<str2<<endl;
	cout<<str3<<endl;
	cout<<str4<<endl;
	cout<<str5<<endl;
	cout<<str6<<endl;
	cout<<str7<<endl;
}
int main(){
    
    
	test01();
	return 0;
}

总结:
 string赋值方式很多,但是用 operator= 号是较为实用的方法

2.1.4 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); //同operator+=(const string& str)
 - string& append(const string &s, int pos, int n);//字符串s中从pos开始的n个字符连接到字符串结尾
/*exanmple*/
	string str1 = "I";
	string str2 = "you,li ming";
	string str3 = "Life";
	
	str1 += "love";	//追加字符串
	str1 += " ";	//追加单个字符
	str1 += str2;	//拼接字符串对象
	cout<<str1<<endl;//Ilove you
	
	str3.append(" is ");
	str3.append("movieasdfasdf",5);
	str3.append(str2,0,3);
	cout<<str3<<endl;//Life is movieyou

总结:

如果使用append(const string &s, int pos, int n)函数,
应当弄明白字符的位置是从0开始的

2.1.5 string查找与替换

功能描述:

查找:查找指定字符串是否存在
替换:在指定的位置替换字符串

函数原型:

 - int find(const string &str,int pos=0) const; 	从pos位置开始查找str第一次出现的位置
 - int find(const char *s,int pos=0) const;			从pos位置开始查找s第一次出现位置
 - int find(const char *s,int pos=0,int n) const; 	从pos位置查找s的前n个字符第一次位置
 - int find(const char c,int pos=0)  const; 		从pos位置查找字符c的第一次出现位置
 - int rfind(const string &str,int pos=npos) const; 从pos开始查找str最后一次位置,
 - int rfind(const char *s,int pos=npos) const; 	从pos开始查找s最后一次出现位置,
 - 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个字符为str
 - string &replace(int pos,int n,const char *s);	替换从pos开始的n个字符为字符串s

rfind和find的区别:find默认从左往右查找,而rfind则代表从右往左查;
find找到字符串后返回查找到的字符串的第一个字符的下标,找不到则返回-1;
replace替换时要指定从哪儿个位置开始起,其后有多少个字符,替换成什么样的字符串。

  tips:C++在函数声明时,后面跟个const是限定函数类型为常成员函数, 常成员函数是指不能改变成员变量值的函数。例如“double d() const;”,其中的其中的“const”限定了d()函数中不能有任何改变其所属对象成员变量值的功能,如果有则会在编译阶段就报错。它的主要作用就是能使成员函数的意义更加清楚,我们可在不改变对象的成员函数的函数原型中加上const说明。在需要增加可读性和减少逻辑出错的情况下,就可以用这种形式。

/*查找举例*/
void test01(){
    
    
	string str1 = "zainashandenaoyouyiqunljl";
	int pos = str1.find("i");
	if (pos == -1)
		cout << "未找到" << endl;
	else
		cout << pos << endl;
	pos = str1.rfind("i");
	cout<<pos<<endl;
}
void test02(){
    
    
	string str1 = "zainashandenaoyouyiqunljl";
	/*从一号字符起3个字符用1111替换 */
	str1.replace(1,3,"1111") ;
	cout<<str1<<endl;
}

2.1.6 string字符串的比较

比较方式:

比较字符的ASCII码值
	= return 0
	> return 1
	< return -1

函数原型:

在string类内部有这两个操作:
int compare(const string &s) const; //与string对象字符串s比较
int compare(const char *s) const; //与字符串s比较
/*example*/
string str1 = "hello!";
string str2 = "hillo!";
if (str1.compare(str2) == 0){
    
    
	cout << "str1和str2相同!" << endl;
}else if(str1.compare(str2) ==-1){
    
    
	cout << "str1小于str2!" << endl;
}else
	cout<<"str1大于str2"<<endl;

总结:

一般用来比较字符串是否相等

2.1.7 string字符存取

string中单个字符存取的方式有两种

在string类内部有这两个操作:
    char& operator[](int n); //通过[]方式取字符
    char& at(int n); //通过string类内部at方法获取字符
/*example*/
string str = "hello world";
cout<<str[1]<<endl;
for(int i=0;i<str.size();i++)
	cout<<str.at(i)<<endl;
/*修改*/
str[0] = 'x';
str.at(1) = 'x';
cout<<str<<endl;

2.1.8 string插入和删除

函数原型:

在string类内部有这两个操作:
	string& insert(int pos, const char* s);//通过指向字符常量区域的指针对调用该成员函数的对象来插入字符
	string &insert(int pos,const string &str); //通过string类型的对象插入字符
	string &insert(int pos,int n,char c); //在指定位置插入n个字符c
	string &erase(int pos,int n=npos); //删除从pos开始的n个字符 
str.insert(1,"ag")

总结

	通常情况下直接str.insert(1,"ag")就行;

2.1.8 string子串获取

函数原型:

string substr(int pos = 0;int n=npos) const;
/*useful exanmple:输入邮箱,自动获取邮箱账号*/
void test02(){
    
    
	string str;
	cin >> str;
	string userName = str.substr(0,str.find("@"));
	cout<<userName<<endl;
}

下面是第二种容器:vector

2.2 vector容器

2.2.1 vector基本概念

功能:
  vector数据结构和数组非常相似,也称为单端数组
vector与普通数组区别
  不同之处在于数组是静态空间,而vector可以动态扩展
动态扩展:
  ①并不是在原空间之后续接新空间,而是找更大的内存空间,然后将原数据拷贝新空间,释放原空间。
  ②vector容器的迭代器是支持随机访问的迭代器

2.2.2 vector构造函数

函数原型:

vector<T> v; //采用模板实现类实现,默认构造函数
vector(v.begin(), v.end()); //将v[begin(), end()]区间中的元素拷贝给本身
vector(n, elem); //构造函数将n个elem拷贝给本身
vector(const vector &vec); //拷贝构造函数
vector<int>v1;  					 // 1.默认构造
vector<int> v2(v1.begin(), v1.end());//2.区间方式进行构造
vector<int> v3(10, 100);			 //3.n个elem方式构造
vector<int> v4(v3);					 //拷贝构造

2.2.3 vector赋值操作

函数原型:

vector &operator = (const vector &vec); //重载等号操作符
assign(begin,end);//将[ begin, end ]区间的数据拷贝赋值给本身
assign(n,elem); //将n个elem拷贝赋值给本身 

示例:

vector<int>v1 = v;
vector<int>v2;
v2.assign(v.begin(),v.end());
vector v3;
v3.assgin(12,1001);

2.2.4 vector容量和大小

函数原型:

empty(); //判断容器是否为空
capacity(); //容器的容量
size(); //返回容器中元素的个数
resize(int num); //重新指定容器的长度为num,若容器变长,则以默认值0填充新位置。
​ 				 //如果容器变短,则末尾超出容器长度的元素被删除。
resize(int num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。
​ 					   //如果容器变短,则末尾超出容器长度的元素被删除

总结:
 - 判断是否为空--- empty()
 - 返回元素个数--- size()
 - 返回容量个数--- capacity()
 - 重新指定大小--- resize()

示例:

#include<iostream>
#include<vector>
using namespace std;

void printVector(vector<int>& v)
{
    
    
	for (vector<int>::iterator it = v.begin(); it != v.end(); ++it)
	{
    
    
		cout << *it << " ";
	}
	cout << endl;
}
/*exanmple*/
void test02(){
    
    
	vector<int>v;
	for(int i=0;i<10;i++){
    
    
		v.push_back(i);
	}
	cout<<v.empty()<<endl;
	cout<<v.capacity()<<endl;
	cout<<v.size()<<endl;
	v.resize(6);
	printVector(v);
	v.resize(126,9);
	printVector(v);
}
int main(){
    
    
	test02();
	return 0;
}

2.2.5 vector插入和删除

函数原型:

push_back(ele) //尾部插入元素
pop_back() 	   //删除尾部元素
insert(const_iterator pos,ele) //迭代器指向位置pos插入ele
insert(const_iterator pos,int count,ele) //迭代器指向pos插入count个元素ele
erase(const_iterator start,const_iterator end) //删除迭代器从start到end之间的元素
clear() //删除容器中所有元素
总结: 插入有尾部插入、通过迭代器插入某个元素、通过迭代器插入区间的元素; 删除有尾部删除,通过迭代器删除某个元素、通过迭代器删除区间的元素; 清空元素。

示例:

v.push_back(12);
v.pop_back();
v.insert(v.begin(),100);
v.insert(v.begin(),2,1000);
v.erase(v.begin());
v.erase(v.begin(),v.begin()++);
v.clear();

2.2.6 vector容器数据存取

函数原型:

at(int idx); //返回索引idx所指的数据
operator[]; //返回索引idx所指的数据
front(); //返回容器中第一个数据元素
back(); //返回容器中最后一个数据元素

示例:

for(int i = 0;i < v.size();i++){
	cout << v[i] << endl;
	cout << v.at(i) <<endl;
}
v.front();
v.back();

2.2.6 vector互换容器

函数原型:

swap(vec): //将vec与本身的元素互换

示例:

v1.swap(v2);

妙用:

有一说一,这个里面的那个利用匿名对象来收缩空间的方法胎牛皮了!!!!!!
#include<iostream>
#include<vector>
using namespace std;
 
void printvector(vector<int> &v)
{
    
    
	for(vector<int>::iterator it=v.begin();it!=v.end();it++)
		cout<<*it<<" ";
	cout<<endl;
}
 
 
void test1()
{
    
    
	cout<<"交换之前的两个容器:"<<endl;
	vector<int> v1;
	for(int i=0;i<10;i++)
		v1.push_back(i);
	printvector(v1);
 
	vector<int> v2;
	for(int i=9;i>=0;i--)
		v2.push_back(i);
	printvector(v2);
	
	cout<<"交换之后的两个容器:"<<endl;
	v1.swap(v2);
	printvector(v1);
	printvector(v2);
 } 
 
 
 void test2() //容器互换应用实例
 {
    
    
 	vector<int> v;
	for(int i=0;i<10000;i++)
		v.push_back(i); 
	cout<<"v的容量为:"<<v.capacity()<<" v的大小为: "<<v.size()<<endl;
	
	v.resize(5); //重新指定大小之后,容量不变,大小改变 
	cout<<"v的容量为:"<<v.capacity()<<" v的大小为: "<<v.size()<<endl;
	
	//利用swap收缩内存
	vector<int>(v).swap(v); //vector<int>(v): 匿名对象 
	cout<<"v的容量为:"<<v.capacity()<<" v的大小为: "<<v.size()<<endl;
 } 
 
int main()
{
    
    
	test1();
	test2();
	return 0;
}
 
/*
打印结果:
交换之前的两个容器:
0 1 2 3 4 5 6 7 8 9
9 8 7 6 5 4 3 2 1 0
交换之后的两个容器:
9 8 7 6 5 4 3 2 1 0
0 1 2 3 4 5 6 7 8 9
v的容量为:16384 v的大小为: 10000
v的容量为:16384 v的大小为: 5
v的容量为:5 v的大小为: 5
*/

2.2.6 vector预留空间

功能描述:

减少vector在动态扩展容量时的扩展次数

函数原型:

reserve(int len);  
容器预留len个元素长度,预留位置不初始化,元素不可访问

示例:

v.reserve(100000);


下面是deque

2.3 deque容器

2.3.1 deque容器基本概念

特性:

双端数组,可以对头端进行插入删除操作
deque容器的迭代器也是支持随机访问的
使用的时候需要包含头文件:#include<deque>

与vector区别:

vector对于头部的插入删除效率低,数据量越大,效率越低
deque相对而言,对头部的插入删除速度回比vector快
vector访问元素时的速度会比deque快,这和两者内部实现有关

内部deque内部工作原理:

deque内部有个中控器,维护每段缓冲区中的内容,缓冲区中存放真实数据
中控器维护的是每个缓冲区的地址,使得使用deque时像一片连续的内存空间

中控器



2.3.2 deque构造函数与迭代器

函数原型:

deque<T> deqT;            默认构造形式
deque(beg,end);           构造函数将[beg,end)区间中元素拷贝给本身
deque(n,elem);            构造函数将n个elem拷贝给本身
deque(const deque &deq);  拷贝构造函数

示例1:

deque<int>dl;
for(int i=0;i<10;i++)
	dl.push_back(i);
deque<int>d2(dl.begin(),dl.end());
deque<int>d3(10,100);
deque<int>d4=d3;

迭代器:

当仅仅是为了遍历,而不需修改时候,应当加上const关键字
const类型的容器需要const_iterator 迭代器才不会报错

示例2:

void printDeque(const deque<int>&d){
    
    
	for(deque<int>::const_iterator it = d.begin();it!=d.end();it++){
    
    
		cout<<*it<<endl;
	}
}

2.3.3 deque赋值操作

函数原型:

deque& operator=(const deque &deq);      重载等号操作符
assign(beg,end);                         将[beg,end)区间中的数据拷贝赋值给本身
assign(n,elem);                          将n个elem拷贝赋值给本身

示例:

deque<int>d2;
d2=dl;

//assign赋值
deque<int>d3;
d3.assign(dl.begin(),dl.end());

deque<int>d4;
d4.assign(10,100);

2.3.4 deque大小操作

需要注意的点:

deque容器是没有容量这个概念的,
所以不像vector中有capacity函数

函数原型:

deque.empty();            判断容器是否为空
deque.size();             返回容器中元素的个数
deque.resize(num);        重新指定容器的长度为num,若容器变长,则默认值填充新位置,
                          若容器变短,则末尾值超出容器长度的元素被删除
deque.resize(num,elem);   重新指定容器的长度为num,若容器边长,则以elem值填充新位置,
                          如果容器变短,则末尾超出容器长度的元素被删除

总结:

判断是否为空 --- empty
返回元素个数 --- size
重新指定个数 --- resize

2.3.5 deque插入和删除

函数原型:

 两端插入操作: 
 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位置的数据,返回下一个数据的位置

示例:

d.push_back(10);
d.pop_front();

2.3.6 deque数据存取

函数原型:

at(int idx);        返回索引idx所指的数据
operator[];         返回索引idx所指的数据
front();            返回容器中第一个数据
back();             返回容器中最后一个数据

示例:

for(i=0; i<d.size() ;i++);{
    
    
	cout<<d[i]<<" ";
	cout<<d.at(i)<<" ";
}

2.3.7 deque排序

注意:

注意包含头文件<algorithm>;
sort算法默认升序;
对于支持随机访问的迭代器的容器,都可以利用sort算法直接对其进行排序;

算法:

sort(iterator beg, iterator end);

示例:

sort(d1.begin(), d1.end());


下面是stack

2.4 stack容器

2.4.1 stack基本概念

概念: stack是一种先进后出(first in first out)的数据结构,
  它只有一个出口;
 栈中只有栈顶的元素才能被访问到。
 stack不提供遍历功能,也不提供迭代器。
 入栈-push
 出栈-pop
Alt

2.4.2 stack常用接口

函数原型:

stack构造函数
	stack<T> stkT;//stack采用模板类实现, stack对象的默认构造形式:
	stack(const stack &stk);//拷贝构造函数

stack赋值操作
	stack& operator=(const stack &stk);//重载等号操作符

stack数据存取操作
	push(elem);//向栈顶添加元素
	pop();//从栈顶移除第一个元素
	top();//返回栈顶元素

stack大小操作
	empty();//判断堆栈是否为空
	size();//返回堆栈的大小

示例:

s.top();//查看栈顶元素
s.pop();//出栈


下面是queque

2.5 queque容器

2.5.1 queque基本概念

概念:

Queue是一种先进先出(First In First Out,FIFO)的数据结构,它有两个出口
队列容器允许从一端新增元素,从另一端移除元素
队列中只有队头 front()队尾 back()才可以被外界使用,因此队列不允许有遍历行为
队列中进数据称为 — 入队 push
队列中出数据称为 — 出队 pop
Alt

2.5.2 queque常用接口

构造函数:
queue<T> que; //queue采用模板类实现,queue对象的默认构造形式
queue(const queue &que); //拷贝构造函数

赋值操作:
queue& operator=(const queue &que); //重载等号操作符

数据存取:
push(elem); //往队尾添加元素
pop(); //从队头移除第一个元素
back(); //返回最后一个元素
front(); //返回第一个元素

大小操作:
empty(); //判断堆栈是否为空
size(); //返回栈的大小

总结:

入队:push
出队:pop
返回队头元素
返回队尾元素:back
判断队是否为空:empty
返回队列大小:size

2.5.3 queque示例

#include <queue>
#include <string>
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;
        
		cout << endl;
		//弹出队头元素
		q.pop();
	}
	cout << "队列大小为:" << q.size() << endl;
}

int main() {
    
    
	test01();
	system("pause");
	return 0;
}


下面是list

2.6 list

2.6.1 list基本概念

优缺点:

优点:
 可以对任意位置快速插入或者删除元素;
缺点:
 容器的遍历速度,没有数组快;
 占用空间比数组大;

STL中的链表

STL中的链表是一个双向循环链表,迭代器只支持前移和后移,属于双向迭代器
Alt

总结:在STL中list和vector属于两个被最常使用的容器,各有优缺点。

2.6.2 list构造函数

函数原型

list<T> lst; //list采用采用模板类实现,对象的默认构造形式;
list(beg,end); //构造函数将[beg, end)区间中的元素拷贝给本身。
list(n,elem); //构造函数将n个elem拷贝给本身。
list(const list &lst); //拷贝构造函数。

示例:

list<int> l1; //默认构造
list<int> l2(l1.begin(), l1.end()); //区间方式构造(也属于拷贝)
list<int> l3(l2); //拷贝构造
list<int> l4(10, 999); //10个999(也属于拷贝)

3.7.3 list赋值和交换

函数原型

  • assign(begin, end); //将[beg, end)区间中的数据拷贝赋值给本身.
  • assign(n, elem);//将n个elem拷贝赋值给本身
  • list& operator=(const list &lst); //重载等号操作符
  • swap(lst); //将lst与本身的元素互换。

list<int>l1;
l2 = l1;
l3.assign(l2.begin(),l2.end());
l4.assign(10,100);
l4.swap(l3);

3.7.4 list大小操作

函数原型

size(); //返回容器中元素的个数
empty(); //判断容器是否为空
resize(num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。

#include<iostream>
#include<string>
#include<list>
#include<algorithm>
using namespace std;

class Person//容器中要存放的数据类型
{
    
    
public:
	Person() {
    
    }
	Person(string name, int age)
	{
    
    
		m_name = name;
		m_age = age;
	}

	string m_name;
	int m_age;
};


void PrintList(const list<Person> &l)//打印容器中的数据
{
    
    
	for (list<Person>::const_iterator it = l.begin(); it != l.end(); it++)
	{
    
    
		cout << it->m_name << " " << it->m_age << endl;//用*it访问也可以,用指针也可以
	}
	cout << endl;
}

/*
size();                     //返回容器中元素的个数

empty();                    //判断容器是否为空

resize(num);                //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。

​							//如果容器变短,则末尾超出容器长度的元素被删除。

resize(num, elem);			//重新指定容器的长度为num,若容器变长,则以elem值填充新位置。

							//如果容器变短,则末尾超出容器长度的元素被删除。

*/

void test01(){
    
    //list大小操作
	list<Person> l;

	string Name = "ABC";
	for (int i = 0; i < 3; i++){
    
    //往容器里边插入三个数据
		string name = "李";
		name += Name[i];
		Person p(name, i + 20);
		l.push_back(p);
	}

	if (!l.empty()){
    
    
		cout << "容器不为空,大小为:"<<l.size() << endl;
	}
	else
		cout << "容器为空" << endl;	

	Person p("默认", 100);
	l.resize(10,p);
	l.resize(1);
	PrintList(l);

}

int main(){
    
    
	test01();
	return 0;
}

2.7.5 list插入和删除

函数原型:

* 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值匹配的元素。

2.7.6 list 数据存取

函数原型:

l.front();返回容器第一个元素
l.back();返回容器最后一个元素
注意:

不能用[]访问list容器中的元素
不能用at()方式访问list容器的元素
原因是:list本质链表,不适用连续线性空间存储数据,迭代器不支持随机访问(it = it+1错误),支持双向访问(it–)

l1.front();
l2.back();

2.7.7 list 反转和排序

函数原型:

reverse();翻转链表
sort()排序(成员函数, 默认升序, 可用仿函数来实现降序操作)

重要提示:

所有不支持随机访问迭代器的容器,不可以用标准算法
不支持随机访问迭代器的容器,内部会提供一些算法
list自带的排序算法默认是从小到大升序,如果要从大到小,利用回调函数或仿函数实现降序(此处为回调函数)

/*example*/
#include <iostream>
#include <list>
using namespace std;

template<class T>
void printList(const list<T>& L)
{
    
    
	for (list<T>::const_iterator it = L.begin(); it != L.end(); it++)
	{
    
    
		cout << *it << '\t';
	}
	cout << endl;
}

bool myCompare(int v1, int v2)
{
    
    
	//降序 让 第一个数 > 第二个数
	return v1 > v2;
}

//排序
void test2()
{
    
    
	list<int>L1;
	L1.push_back(20);
	L1.push_back(10);
	L1.push_back(50);
	L1.push_back(40);
	L1.push_back(30);

	cout << "排序前:" << endl;
	printList(L1);

	L1.sort();  //默认升序
	cout << "排序后:" << endl;
	printList(L1);

	L1.sort(myCompare);
	printList(L1);
}

int main()
{
    
    
	test2();
}

2.7.8 list 排序案例

#include<iostream>
#include<string>
#include<sstream>
#include<list>
using namespace std;

/***/
class Person{
    
    
public:
	Person(string name,int age,int height){
    
    
		this->m_Name = name; 
		this->m_Age = age;
		this->m_Hight = height;
	}
	
	string printInfo()
    {
    
    
        stringstream temp;
        temp << "姓名:" << this->m_Name << "\t年龄:" << this->m_Age << "\t身高:" << this->m_Hight;
        return temp.str();
    }
	
	string m_Name;
	int m_Age;
	int m_Hight;
	
}; 
//指定排序规则
bool comparePerson(Person &p1, Person &p2){
    
    
	if(p1.m_Age == p2.m_Age){
    
    
		return p1.m_Hight < p2.m_Hight;
	}
	return p1.m_Age < p2.m_Age;
} 

void test01(){
    
    
	list<Person>L;
	
	Person p1("刘备", 35, 175);
    Person p2("曹操", 45, 180);
    Person p3("孙权", 40, 170);
    Person p4("赵云", 25, 190);
    Person p5("张飞", 35, 160);
    Person p6("关羽", 35, 200);
    
    L.push_back(p1);
    L.push_back(p2);
    L.push_back(p3);
    L.push_back(p4);
    L.push_back(p5);
    L.push_back(p6);
    
    for(list<Person>::iterator it = L.begin();it != L.end();it++){
    
    
		cout<< it->printInfo()<<endl;
    } 
    cout<<"----------------------------"<<endl; 
    L.sort(comparePerson);
    for(list<Person>::iterator it = L.begin();it != L.end();it++){
    
    
		cout<< it->printInfo()<<endl;
    }
    
}

int main(){
    
    
	test01(); 
	return 0;
}

 对于自定义数据类型,必须指定排序规则,否则不知道如何进行排序
 高级排序只是在排序规则上再进行一次逻辑规则的指定,并不复杂

2.8 set/multiset容器

2.8.1 set基本概念

简介:所有元素都会在插入的时候自动排序
本质:set/multiset属于关联式容器,底层结构是用二叉树实现.
区别:set中不允许有重复的元素, multiset中允许有重复的元素

2.8.2 set构造和赋值

构造函数:
set< T> st; //默认构造函数:
set(const set &st); //拷贝构造函数

赋值:
set& operator=(const set &st); //重载等号操作符
插入数据:
insert();

set<int> s2(s1);
set<int> s3;
s3 = s2;

2.8.3 set容器大小和交换

函数原型:
size(); //返回容器中元素的数目
empty(); //判断容器是否为空
swap(st); //交换两个集合容器

2.8.4 set容器插入和删除

函数原型:
insert(elem); //在容器中插入元素。
clear(); //清除所有元素
erase(pos); //删除pos迭代器所指的元素,返回下一个元素的迭代器。
erase(beg, end); //删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(elem); //删除容器中值为elem的元素。

s1.erase(s1.begin());
s1.erase(30);

2.8.5 set查找和统计

函数原型:
find(key); //查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();
count(key); //统计key的元素个数,对于set来说只有0或1; 对于multiset能够大于1;

set<int>::iterator pos = s1.find(12);
if(pos != s1.end())
	cout << "找到元素" << endl;
else
	cout << "未找到元素" << endl;

2.8.6 set和multiset区别

区别:

 set不可以插入重复数据,而multiset可以
 set插入数据的同时会返回插入结果,表示插入是否成功
 multiset不会检测数据,因此可以插入重复数据

2.8.7 pair对组的创建

功能简介:

成对出现的数据,利用对组可以返回两个数据,其中第一个数据是first,第二个数据是second;

函数原型:

pair<type, type> p ( value1, value2 );
pair<type, type> p = make_pair( value1, value2 );

示例:

 //test func of pair
void test4() {
    
    
    pair<string, int> p(string("tom"), 20);
    cout << "name: " << p.first << " " << " age: " << p.second << endl;

    pair<string, int> p2 = make_pair("jerry", 10);
    cout << "name: " << p2.first << " " <<  " age: " << p2.second << endl;
}

2.8.8 set容器排序

 set容器默认排序规则是从小到大,利用仿函数可以改变排序规则
 不能在放入数据之后才指定排序方式, 应当在创建的时候指定排序方式

示例:创建了一个MyCompare类,重载函数调用运算符,并且在创建set容器时用类名作为第二个参数。

class myCompare{
    
    
public:
	bool operator()(int v1, int v2){
    
    
		return v1 > v2;
	}
};

set<int, myCompare>s1;
下面插入的时候就会按照从大到小排列


  
   

2.9 map/multimap容器

C++STL之map容器:发现的一篇挺好的文章

(1) map基本概念

简介:

 map中的所有元素都是pair
 pair中第一个元素为key(键值)起到索引作用,第二个元素为value(值)
 所有的元素都会根据元素的键值自动排序

本质:

 map/multimap属于关联式容器,底层结构是用二叉树实现

优点:

可以根据key值快速找到value值

map和multi的区别:

map不允许容器中有重复key值元素
multimap允许容器中有重复key值得元素

其他

对于自定义数据类型, map必须指定排序规则,和set容器相同;

(2) map常用函数

构造函数

map<T1, T2> mp;		//map默认构造函数: 
map(const map &mp);	//拷贝构造函数

赋值和交换

map& operator=(const map &mp);//重载等号操作符
swap(mp);			//交换两个集合容器

map大小操作

size();//返回容器中元素的数目
empty();//判断容器是否为空

map插入操作

为什么不建议用第四种方式?
 假如用第四种方式查找一个不存在的key,会自动新建一个键值对,值为零;因此用此种方式输出的时候应当确定此键值对已经存在了

map.insert(...); 
	//往容器插入元素,返回pair<iterator,bool>
//1 map的四种插入方法
void MapInsert(map<int, int> &m) {
    
    
	//1 通过模板pair对组.返回一个对组对象.其中:对组对象的模板参1为:被插入容器的(此处为map)迭代器类型;模板参2为:布尔类型,用于判断是否插入成功.
	std::pair<map<int,int>::iterator,bool> pairIt = m.insert(std::pair<int,int>(1,1));
	if(pairIt.second == false){
    
    
		std::cout << "1 insert map failed" << std::endl;
		return;
	}

	//2 通过make_pair()函数.同样返回一个对组对象
	pairIt = m.insert(std::make_pair(2, 2));
	if (pairIt.second == false) {
    
    
		std::cout << "2 insert map failed" << std::endl;
		return;
	}

	//3 通过map模板类中的value_type()函数.同样返回一个对组对象
	pairIt = m.insert(map<int, int>::value_type(3, 3));
	if (pairIt.second == false) {
    
    
		std::cout << "3 insert map failed" << std::endl;
		return;
	}

	//4 下标法插入.注:map中,key不存在,以默认值0插入map;key存在,修改该key的值.
	m[4] = 4;
	m[5];//默认为0.
	std::cout << m[6] << std::endl;//下标打印时不存在的key会默认被初始化为0.且会被插入到map中

	//所以此时map中共有6个元素
}

删除

clear();//删除所有元素
erase(pos);//删除pos迭代器所指的元素,返回下一个元素的迭代器。
erase(beg,end);//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(keyElem);//删除容器中key为keyElem的对组。

查找统计

find(key);//查找键key是否存在,若存在,返回该键的元素的迭代器;
		  //若不存在,返回map.end();
count(keyElem);
		 返回容器中key为keyElem的对组个数。
		 对map来说,要么是0,要么是1。
		 对multimap来说,值可能大于1lower_bound(keyElem);//返回第一个key>=keyElem元素的迭代器。
upper_bound(keyElem);//返回第一个key>keyElem元素的迭代器。
equal_range(keyElem);//返回容器中key与keyElem相等的上下限的两个迭代器。

排序

map容器默认是升序排列, 利用仿函数来改变map容器的排序方式

class myCompare{
    
    
public:
	bool operator()(int v1, int v2){
    
    
			//降序:
			return v1 > v2;
			//升序
			//return v1 < v2;
	}
};
map<int, int, myCompare>m;//降序排列

猜你喜欢

转载自blog.csdn.net/Stanford_sun/article/details/122673045
今日推荐