今夜栈帧皆似雪,明朝队列俱如鸿

不知道大家之前是否学习过数据结构中的栈及其相关概念,我们今天要学习的容器就是与数据结构中栈的特点非常相像的stack容器,它是一种先进后出的数据结构,并且只有一个出口。类比生活中的弹匣可以帮助你更好的理解这一种数据结构

stack容器——基本概念

基本概念:

  • 栈中只有顶端元素可以被外界使用,因此栈不允许有遍历的行为
  • 栈中进入数据称为——入栈
  • 栈中弹出数据称为——出栈

stack容器——常用接口

stack容器中的各种接口操作与之前所学的容器非常相似,可进行类比学习

示例代码:

void test01()
{
	stack<int>s;

	//入栈
	s.push(10);
	s.push(20);
	s.push(30);
	s.push(40);

	cout << "栈的大小:" << s.size() << endl;
	//输出:栈的大小:4

	//只要栈不为空,查看栈顶,并执行出栈操作
	while (!s.empty())
	{
		//查看栈顶元素
		cout << "栈顶元素为:" << s.top() << endl;

		//出栈
		s.pop();
	}
	
	cout << "栈的大小:" << s.size() << endl;
    //输出:栈的大小:0
    
}

//输出:
栈顶元素为:40
栈顶元素为:30
栈顶元素为:20
栈顶元素为:10

总结:入栈push出栈pop,返顶top空empty,点头yes摇头no,来是come去是go,不好意思走错片场了~…总而言之,通过以上的输出结果,结合之前的入栈顺序,就可以很明显地发现stack容器是一种先进后出的数据结构

queue容器——基本概念

queue容器和stack容器有个最明显的区别就是——queue是一种先进先出的数据结构,同时它有两个出口

基本概念:

  • 队列容器允许从一端新增元素,一端移除元素
  • 队列中只有队头和队尾才可以被外界使用,因此队列不允许有遍历行为
  • 队列中进数据称为——入队
  • 队列中出数据称为——出队

queue容器——常用接口

都大差不差,上代码

示例代码:

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;

}

//输出:
队头元素———姓名:唐僧 年龄:30
队尾元素———姓名:沙僧 年龄:800

队头元素———姓名:孙悟空 年龄:1000
队尾元素———姓名:沙僧 年龄:800

队头元素———姓名:猪八戒 年龄:900
队尾元素———姓名:沙僧 年龄:800

队头元素———姓名:沙僧 年龄:800
队尾元素———姓名:沙僧 年龄:800

队列大小:0

总结:各种接口与之前学习的其它容器基本上无异,并且通过输出结果可以很明显发现queue是一种先进先出的数据结构

list容器——基本概念

基本概念:

  • 定义:链表(list)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的,因此在链表list中的迭代器只支持前移和后移,属于双向迭代器。并且在STL中,链表是一个双向循环链表
  • 功能:将数据以链式的方式进行存储
  • 组成:链表由一系列结点组成,而结点又由一个存储数据元素的数据域和一个存储下一个结点地址的指针域组成

list的优点:

  • 采用动态存储分配,不会造成内存的浪费和溢出
  • 链表执行插入和删除的操作十分方便,修改指针即可,不需要移动大量元素
  • 插入和删除都不会造成原有list的迭代器失效,这在vector中是不成立的

那么,代价是?

list的缺点:

  • 空间(指针域)和时间(遍历)的额外消耗较大

总结:STL中List和vector是两个最常用被使用的容器,且相互优劣互补

list容器——构造函数

和之前所学容器的构造方式一样,但在学前面几个容器的时候没演示,而list容器又比较重要,怕大家忘记,在此还是进行一下演示吧~

示例代码:

void printList(const list<int>&L)
{
	for (list<int>::const_iterator it = L.begin(); it != L.end(); it++)
	{
		cout << (*it) << " ";
	}
	cout << endl;
}

void test01()
{
	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

	1、区间方式构造
	list<int>L2(L1.begin(), L1.end());
	printList(L2);
	//输出:10 20 30 40

	2、拷贝构造
	list<int>L3(L2);
	printList(L3);
    //输出:10 20 30 40
    
	3、n个elem构造
	list<int>L4(10,250);
	printList(L4);
	//输出:250 250 250 250 250 250 250 250 250 250
}

list容器——赋值和交换

别无二致

void test01()
{
	//创建list容器
	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

	1operator= 赋值
	list<int>L2;
	L2 = L1;
	printList(L2);
	//输出:10 20 30 40

	2、区间赋值
	list<int>L3;
	L3.assign(L2.begin(), L2.end());
	printList(L3);
    //输出:10 20 30 40
    
	3、n个elem赋值
	list<int>L4;
	L4.assign(10, 100);
	printList(L4);
	//输出:100 100 100 100 100 100 100 100 100 100
}

void test02()
{
	list<int>L1;
	L1.push_back(10);
	L1.push_back(20);
	L1.push_back(30);
	L1.push_back(40);

	list<int>L2;
	L2.assign(10, 100);

	cout << "交换前:" << endl;
	printList(L1);
	printList(L2);
	//输出:10 20 30 40
    //输出:100 100 100 100 100 100 100 100 100 100

    1、swap函数进行交换
	L1.swap(L2);
	cout << "交换后:" << endl;
	printList(L1);
	printList(L2);
	//输出:100 100 100 100 100 100 100 100 100 100
    //输出:10 20 30 40
}

list容器——大小操作

依旧如故

void test01()
{
	//创建list容器
	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;
		//输出:L1元素个数为:4
	}

	1、重新指定大小
	L1.resize(10, 100);
	printList(L1);
	//输出:10 20 30 40 100 100 100 100 100 100

	L1.resize(2);
	printList(L1);
    //输出:10 20
}

list容器——插入和删除

一点点不一样

void test01()
{
	//创建list容器
	list<int>L1;

	//头插
	L1.push_back(10);
	L1.push_back(20);
	L1.push_back(30);
	
	//尾插
	L1.push_front(100);
	L1.push_front(200);
	L1.push_front(300);

	printList(L1);
	//输出:300 200 100 10 20 30

	//尾删
	L1.pop_back();
	printList(L1);
    //输出:300 200 100 10 20
    
	//头删
	L1.pop_front();
	printList(L1);
    //输出:200 100 10 20
    
	//insert插入
	L1.insert(L1.begin(), 1000);
	printList(L1);
	//输出:1000 200 100 10 20

	//insert迭代器偏移插入
	list<int>::iterator it = L1.begin();
	L1.insert(++it,500);
	printList(L1);
	//输出:1000 500 200 100 10 20

	//迭代器偏移删除
	it = L1.begin();
	L1.erase(++it);
	printList(L1);
	//输出:1000 200 100 10 20

	//先插入,后移除
	L1.push_back(10000);
	L1.push_back(10000);
	L1.push_back(10000);
	printList(L1);
	//输出:1000 200 100 10 20 10000 10000 10000
	
	//移除所有与其匹配的数据
	L1.remove(10000);   
	printList(L1);
	//输出:1000 200 100 10 20

	//clear清空
	L1.clear();
	printList(L1);
    //输出:
}

总结:稍许不一样的地方就是remove函数会移除所有与其匹配的数据,别的操作把单词记住并明确其释义就能掌握

list容器——数据存取

list容器中的数据不可用[]和at()的方式进行随机访问,原因是list的本质是链表,不是用连续线性空间存储的,并且迭代器也是不支持随机访问的

示例代码:

void test01()
{
	list<int>L1;
	L1.push_back(10);
	L1.push_back(20);
	L1.push_back(30);
	L1.push_back(40);

	1、list容器中提供front()来访问第一个元素
	cout << "第一个元素为:" << L1.front() << endl;
	//输出:10

    2、list容器中提供back()来访问最后一个元素
	cout << "最后一个元素为:" << L1.back() << endl;
	//输出:40

那么如何验证迭代器是否是支持随机访问的呢?其实不用去死记硬背,在此提供一个小技巧来判断~

示例代码:

list<int>::iterator it = L1.begin();

//迭代器若支持以下写法,编译器不报错,则说明迭代器支持随机访问
it = it + 1;

//迭代器对于以下两种写法若都支持,则说明迭代器支持双向
//迭代器若只支持以下两种写法其中之一的,说明仅支持单向
it++;
it--

总结:明确List容器的数据访问方式和之前所学容器不同的底层逻辑即可轻松记忆,同时对于迭代器是否支持随机访问这个知识点也不用死记硬背,用以上的示例代码即可轻松验证~

list容器——反转和排序

list容器中,反转操作与之前的容器一样,但排序操作则完全不一样,这是由于List容器的迭代器不支持随机访问而造成的

反转示例代码:

void test01()
{
	//创建list容器
	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);
	//输出:20 10 50 40 30

    //反转
	L1.reverse();
	cout << "反转后:" << endl;
	printList(L1);
	//输出:30 40 50 10 20
}

排序示例代码:

//降序排序回调函数
//注意:传入数据类型应与容器数据类型保持一致
bool myCompare(int val1, int val2)
{
	return val1 > val2;
}

void test02()
{
	//创建list容器
	list<int>L2;

	//头插
	L2.push_back(20);
	L2.push_back(10);
	L2.push_back(50);
	L2.push_back(40);
	L2.push_back(30);

	//排序
	cout << "排序前:" << endl;
	printList(L2);
	//输出:20 10 50 40 30

    1、升序排序(默认排序规则)
	L2.sort();   
	cout << "升序排序后:" << endl;
	printList(L2);
	//输出:10 20 30 40 50

    2、降序排序
	L2.sort(myCompare);   
	cout << "降序排序后:" << endl;
	printList(L2);
	//输出:50 40 30 20 10
}

总结:无他,记忆即可

栈是沉入深海的锚,压着未解的逻辑层层堆叠;队列是候鸟迁徙的轨迹,数据循着队尾的羽翼向北方迁徙;链表是深秋的爬山虎,每个节点都攥着下一寸生长的脉络…不知道接下来的容器学习,又会带给我怎样的感受呢?

你可能感兴趣的:(c++,stl,容器)