一文速通stack和queue的理解与使用

news2025/1/27 3:55:03

C++STL之stack和queue

  • 1.stack
    • 1.1.stack的基本概念
    • 1.2.stack的接口
  • 2.queue
    • 2.1.queue的基本概念
    • 2.2.queue的接口
  • 3.priority_queue
    • 3.1.priority_queue的基本概念
    • 3.2.priority_queue的接口
    • 3.3.仿函数
  • 4.容器适配器
  • 5.deque
    • 5.1.deque的简单了解
    • 5.2.deque的优缺点

🌟🌟hello,各位读者大大们你们好呀🌟🌟
🚀🚀系列专栏:【C++的学习】
📝📝本篇内容:stack;stack的基本概念;stack的接口;queue;queue的基本概念;queue的接口;priority_queue;priority_queue的基本概念;priority_queue的接口;仿函数;容器适配器;deque;deque的简单了解;deque的优缺点
⬆⬆⬆⬆上一篇:一文搞懂反向迭代器之C++模拟实现list
💖💖作者简介:轩情吖,请多多指教(> •̀֊•́ ) ̖́-

1.stack

1.1.stack的基本概念

在我们C++的STL中也实现了栈,也就是stack,它的特性是“先进后出”(First In Last Out,FILO)。允许插入和删除的一端称为栈顶,另一端称为栈底。stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为底层的容器,并提供一组特定的成员函数来访问其元素,将特定的类作为底层的,元素特定容器的尾部(即栈顶)被压入和弹出。
在这里插入图片描述
在底层的容器需要有这些成员函数进行操作:empty()、back()、push_back()、pop_back()
我们STL标准库中vector、list都是符合的,但是默认没有为stack指定特定的容器,底层都是使用的deque。
在这里插入图片描述

1.2.stack的接口

stack函数说明
stack()构造一个空的栈
size_t size()const;栈有效元素的个数
value_type& top();栈顶的值
void push (const value_type& val);压入一个数据,俗称压栈
void pop();出栈,将数据弹出
bool empty()const;是否为空
#include <iostream>
#include <stack>
using namespace std;
int main()
{
	//默认构造
	stack<int> st;

	//压栈
	st.push(10);
	st.push(11);
	st.push(12);

	//查看大小
	cout <<"stack size:" << st.size() << endl;//3

	//查看栈顶元素
	cout <<"stack top:"<< st.top() << endl;//12

	//出栈
	st.pop();
	st.pop();
	st.pop();

	if (st.empty())//true
	{
		cout << "stack is empty" << endl;
	}

	return 0;
}

在这里插入图片描述

还可以指定容器,对于deque我们稍后再讲,以及对于它的底层实现有不理解的可以看我下一篇博客

#include <stack>
#include <iostream>
#include <vector>
#include <list>
using namespace std;
int main()
{
	stack<int, list<int>> st1;//底层容器使用的是list
	stack<int, vector<int>> st2;//底层容器使用的是vector

	return 0;
}

2.queue

2.1.queue的基本概念

谈到stack就必定会谈到queue,queue就是队列。它的特性是“先进先出”(First In First Out,FIFO),它与stack不同,stack只能在一端操作,但是queue不一样,它的插入操作(进队)只能在表尾,队列的删除操作(出队)只能在表头。能够进行插入元素的一端我们称为队尾,允许删除元素的一端称为队头或队首。
在这里插入图片描述

底层和stack一样,是使用了其他的容器来适配,该容器需要支持这些操作:empty()、size()、front()、back()、push_back()、pop_front()。
我们的list是符合这个条件的(vector没有头插,效率太低),但是底层默认使用的是deque
在这里插入图片描述

2.2.queue的接口

stack函数说明
queue()构造一个空的队列
bool empty()const;判断队列是否为空
size_t size()const;返回队列有效元素的个数
value_type& front();返回队头元素的引用
value_type& back();返回队尾元素的引用
void push (const value_type& val);将元素在队尾入队列
void pop();将队头元素出队列
#include <iostream>
#include <queue>
using namespace std;
int main()
{
	//默认构造
	queue<int> q;
	
	//入队列
	q.push(10);
	q.push(11);
	q.push(12);
	q.push(13);
	
	//有效元素个数
	cout << "size:" << q.size() << endl;//4

	//队头元素
	cout << "front:" << q.front() << endl;//10

	//队尾元素
	cout << "back:" << q.back() << endl;//13

	//出队列
	q.pop();
	q.pop();
	q.pop();
	q.pop();

	//判空
	if (q.empty())//true
	{
		cout << "queue is empty" << endl;
	}

	return 0;
}

在这里插入图片描述

3.priority_queue

3.1.priority_queue的基本概念

priority_queue是优先级队列,它也是一种容器适配器,它每一次插入元素,底层就会进行调整,保证它的第一个元素是最大的(最小的)。
它类似于堆,在堆中可以随时插入元素,并且只能检索最大的堆元素(优先级队列中位于顶部的元素)
底层的容器应该是能够支持随机访问迭代器访问的,并且要支持以下操作:empty()、size()、front()、push_back()、pop_back()
在标准容器中,我们的deque和vector都是符合要求的,但是如果没有自己指定容器,那么默认为vector,需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap、push_heap和pop_heap来自动完成此操作。
在这里插入图片描述
我们可以看到其模板参数中还有一个Compare,这其实就是为了使用者能够根据自己的需求,选择大堆还是小堆
我稍微看图来理解一下,具体的实现看我模拟实现的文章,这边主要还是使用为主
在这里插入图片描述
上图假设为vector中所开辟的空间,可能看上去没有什么,那么转换为树型结构呢?如下图
在这里插入图片描述
可以根据公式来看一下:
若i>0,i位置结点的双亲序号:(i-1)/2=parent;i=0,i为根节点编号,无双亲结点
设n为数组的大小,若2i+1<n,左孩子序号:2i+1=leftchild;2i+1>=n否则无左孩子
设n为数组的大小,若2i+2<n,右孩子序号:2i+2=rightchild;2i+2>=n否则无右孩子

可以看到我们的树中,根节点的元素是最小的,其余子树的根节点的元素也是小于孩子节点元素的。
这个事我们的小根堆,大根堆就是反过来,根节点的元素是最大的,其余子树的根节点的元素也是大于孩子节点元素的
我们的priority_queue默认情况下是大堆,其底层是按照小于比较的

3.2.priority_queue的接口

priority_queue函数说明
priority_queue();构造一个空的优先级队列
priority_queue(InputIterator first, InputIterator last);使用迭代器构造
bool empty() const;判断优先级队列是否为空
size_t size()const;优先级队列有效元素的个数
const value_type& top() const;返回优先级队列中最大(最小)的元素,即堆顶的元素
void push (const value_type& val);往优先级队列中插入元素
void pop();删除优先级队列中最大(最小)的元素,即堆顶的元素
#include <iostream>
#include <queue>
using namespace std;
int main()
{
	//默认构造,大堆
	priority_queue<int> pq;

	//插入元素
	pq.push(10);
	pq.push(1);
	pq.push(32);
	pq.push(23);
	pq.push(16);

	//priority_queue的有效元素个数
	cout << "priority_queue size:" << pq.size() << endl;//5

	//最大元素
	cout<<"priority_queue top1:"<<pq.top()<<endl;//32

	//删除堆顶元素
	pq.pop();

	//现在堆顶是第二大元素
	cout << "priority_queue top2:" << pq.top() << endl;//23

	pq.pop();
	pq.pop();
	pq.pop();
	pq.pop();
	//叛空
	if (pq.empty())//true
	{
		cout << "priority_queue is empty" << endl;
	}

	return 0;
}

在这里插入图片描述

3.3.仿函数

前面我们提到过,我们的优先级队列可以通过传递模板参数改变到底是存储大堆还是小堆,接下来先演示一下
在这里插入图片描述
可以看到我们的优先级队列的模板参数传递时多了一个greater类,它是在functional头文件中的,它其实就是一个普通的类中重载了运算符(),这是用来优先级队列内部改变比较的方法,从而达到改变大小堆的切换,还是那句话,具体的实现看我的模拟实现文章,这边主要讲述使用

当优先级队列所存储的元素是自定义类型的时候,那么这个自定义类型需要重载比较运算符,这是因为优先级队列的底层为了保证堆的特性,进行比较调整
下面是演示代码:

#include <iostream>
#include <queue>
#include <functional>
using namespace std;
struct Date
{
	Date(int year=0,int month=0,int day=0)
		:_year(year),
		_month(month),
		_day(day)
	{}


	//对于自定义的类,需要重载小于<、大于>
	bool operator>(const Date& d)const
	{
		if (_year > d._year)
		{
			return true;
		}
		else if (_year == d._year && _month > d._month)
		{
			return true;
		}
		else if (_year == d._year && _month == d._month && _day > d._day)
		{
			return true;
		}
		return false;
	}

	bool operator<(const Date& d)const
	{
		if (_year < d._year)
		{
			return true;
		}
		else if (_year == d._year && _month < d._month)
		{
			return true;
		}
		else if (_year == d._year && _month == d._month && _day < d._day)
		{
			return true;
		}
		return false;
	}

	int _year;
	int _month;
	int _day;

};



namespace lnb
{
	//这里便于理解,模拟实现一下greater,和less
	template<class T>
	struct greater
	{
		bool operator()(const T& x,const T& y)const//重载运算符()
		{
			return x > y;
		}
	};

	template<class T>
	struct less
	{
		bool operator()(const T& x, const T& y)const
		{
			return x < y;
		}

	};

}
int main()
{
	priority_queue<Date, vector<Date>, lnb::less<Date>> pq;
	pq.push(Date(2024, 1, 1));
	pq.push(Date(2024, 2, 5));
	pq.push(Date(2024, 12, 2));
	pq.push(Date(2024, 7, 9));
	pq.push(Date(2024, 1, 4));
	
	const Date& d = pq.top();//注意top()的返回值是const属性的
	cout << "priority_queue top:" <<d._year<<"/"<<d._month<<"/"<<d._day<<endl;// 2024/12/ 2


	return 0;
}

上面的代码也实现了一下greater和less类,默认情况下,我们的priority_queue用来比较的类是less。
大家把一个类中重载了()运算符的类叫做仿函数,为什么呢?因为它的使用像一个函数,使用类来模拟函数行为的技术,也叫作函数对象

//一个简单的仿函数使用
#include <iostream>
using namespace std;
struct Compare
{
	bool operator()(const int& x, const int& y)const
	{
		return x > y;
	}
};
int main()
{
	//bool flag=Compare()(1, 2);//使用匿名对象来直接调用
	Compare com;
	bool flag=com(1, 2);.//像函数一样调用
	if (flag)
	{
		cout << "大于" << endl;
	}
	else
	{
		cout << "小于" << endl;
	}

	return 0;
}

4.容器适配器

在我们前面的讲述的stack、queue、priority_queue都是容器适配器。
适配器是一种设计模式,该种模式是将一个类的接口转换成客户希望的另外一个接口。
虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为容器适配器,这是因为stack和队列只是对其他容器的接口进行了包装,STL中stack和queue默认使用deque,priority_queue默认使用vector
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.deque

5.1.deque的简单了解

之前提及了那么多次的deque,现在该讲它了,它的底层是非常复杂的,我们只需要对它有个简单的了解即可,如果想要学习具体的完整源码可以去看候捷老师的STL源码剖析,如果需要电子版,可以私信我

deque是一种双向开口的连续线性空间,能够在头尾两端分别做元素的插入和删除操作,和vector相比,vector只是单向开口的连续线性空间,虽然从技术观点上来说,vector也可以在首尾两端进行操作,但是它的头部操作需要进行移动数据,效率极差。我们的deque能够在常数时间内对头部进行操作,并且我们的deque没有所谓容量的概念,因为它是动态地以分段连续空间组合而成,随时可以增加一段新的空间并链接起来。deque的迭代器也是随机迭代器,能够支持随机访问,但是其底层是经过了封装的,极其复杂。

deque采用一块连续的map作为主控,map是一小块连续的空间,其中的每一个元素都是指针,指向另一段连续线性空间,称为缓冲区。所以说deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组。
在这里插入图片描述
我们来看一下STL源码剖析中的图来更好的理解

// 见 deque_buf_size()。BufSize 默认值为 0 的唯一理由是为了闪避某些
// 编译程序在处理常数算式(constant expressions)时的臭虫。
// 默认使用alloc为配置器
template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
public: // Basic types
    typedef T value_type;
    typedef value_type* pointer;
    typedef size_t size_type;
 
public: // Iterators
    typedef deque_iterator<T, T&, T*, BufSiz> iterator;
 
protected: // Internal typedefs
    // 元素的指针的指针(pointer of pointer of T)
    typedef pointer* map_pointer;
 
protected: // Data members
    iterator start;  // 表现第一个节点。
    iterator finish; // 表现最后一个节点。
    map_pointer map; // 指向 map,map是块连续空间,
                     // 其每个元素都是个指针,指向一个节点(缓冲区)
    size_type map_size; // map内有多少指标
    ...
};

上面的代码也是摘取自STL源码剖析,其中我们可以通过图和代码有个大致的了解,map中存储了指向各个缓冲区的指针,通过map我们就可以找到任意一个元素。其中比较重要的就是还有它的迭代器。
我们依旧集合图片和代码来看
在这里插入图片描述

template <class T, class Ref, class Ptr, size_t BufSiz>
struct deque_iterator { // 未继承 std::iterator
 
    typedef deque_iterator<T, T&, T*, BufSiz> iterator;
    typedef deque_iterator<T, const T&, const T*, BufSiz> const_iterator;
    static size_t buffer_size(){return deque_buf_size(BufSiz,sizeof(T));}
 
    // 未继承 std::iterator,所以必须自行撰写五个必要的迭代器相应型别
    typedef random_access_iterator_tag iterator_category; // (1)
    typedef T value_type;                                 // (2)
    typedef Ptr pointer;                                  // (3)
    typedef Ref reference;                                // (4)
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;                    // (5)
    typedef T** map_pointer;
    typedef deque_iterator self;
 
    //保持与容器的联结
    T* cur; // 此迭代器所指之缓冲区中的现行(current)元素
    T* first; // 此迭代器所指之缓冲区的头
    T* last; // 此迭代器所指之缓冲区的尾(含备用空间)
    map_pointer node; // 指向管控中心
    ...
};

我们的迭代器内部的四个成员,能够保证找到任意一个元素。我们可以通过node来进行跳转到上一个或下一个缓冲区,通过first和last来保证因为插入或删除导致越界访问,cur指向当前访问的元素。
在我们deque中,也包含了start和finish两个迭代器,它们分别是begin()和end()的返回值,所以说要注意finish中cur的指向。因为迭代器访问会不停的往前迭代,那么最后的终止条件是遇到最后一个元素的下一个位置,所以说finish中的cur指向并不是最后一个元素。
看一下下面的这张图来理解一下,一个迭代器类型的对象的指向:
在这里插入图片描述

5.2.deque的优缺点

优点:
①相比vector来说,扩容的代价比较低,vector扩容需要搬用元素,而deque则只需要增加一段线性空间即可
②deque的头插头删、尾插尾删效率高,我们的vector在头插时可能需要扩容并且还要移动元素,因此效率极差
③支持随机访问,我们的list却没法做到
④其底层是连续空间,空间利用率比较高,不需要存储额外字段,这点list也做不到
缺点:
①deque的中间插入和删除就不是很好的了,因为这也是有可能需要移动大量的元素
这边有两种情况:每个buff数组不一样大,效率会提高,但是会影响随机访问效率;每个buff数组固定大小,牺牲中间插入删除效率,随机访问效率就变高了。我们的SGI的STL中使用是后者,为了保证随机访问
②最主要的缺点是没有vector和list的优点极致
③但是,deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构

stack和queue选择deque作为底层默认的容器是因为deque完美的避开了缺点,展现出了优点。stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据),内存利用率比list高;queue中的元素增长时,deque不仅效率高;出队时,头删效率也高,比vector强,而且内存使用率高,比list强。

🌸🌸C++STL之stack和queue的知识大概就讲到这里啦,博主后续会继续更新更多C++的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2283181.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于CAN(FD)转以太网详细介绍

一、功能描述 CANFD 完全向下兼容 CAN &#xff0c;以下统称 CAN(FD) 。 SG-CAN(FD)NET-210 是一款用来把 CANFD 总线数据转为网口数据的设备。 网口支持 TCP Sever 、 TCP Client 、 UDP Sever 、 UDP Client 四种模式。 可以通过软件配置和 Web 网页配置。 两路…

orbbec 奥比中光相机单目及多目调用方式python代码

这篇文章会介绍使用orbbec相机的一些常用代码梯子 orbbec 奥比中光Astra相机单目及多目调用方式编译sdk调用单相机调用多相机orbbec 奥比中光Astra相机单目及多目调用方式 Orbbec相机是一个专注于深度感知和计算机视觉应用的设备,通常用于3D扫描、手势识别、增强现实(AR)以及…

力扣hot100-->滑动窗口、贪心

你好呀&#xff0c;欢迎来到 Dong雨 的技术小栈 &#x1f331; 在这里&#xff0c;我们一同探索代码的奥秘&#xff0c;感受技术的魅力 ✨。 &#x1f449; 我的小世界&#xff1a;Dong雨 &#x1f4cc; 分享我的学习旅程 &#x1f6e0;️ 提供贴心的实用工具 &#x1f4a1; 记…

PBFT算法

在我的博客中对于RAFT算法也有详细的介绍&#xff0c;raft算法包含三种角色&#xff0c;分别是&#xff1a;跟随者&#xff08; follower &#xff09;&#xff0c;候选人&#xff08;candidate &#xff09;和领导者&#xff08; leader &#xff09;。集群中的一个节点在某一…

跨境电商代购系统独立站深度分享

在全球化日益加深的今天&#xff0c;跨境电商代购系统独立站作为一种新兴的电子商务模式&#xff0c;正逐渐成为连接全球消费者与优质商品的重要桥梁。本文将详细介绍跨境电商代购系统独立站的基本功能以及技术实现的重难点&#xff0c;以期为相关从业者提供一些有价值的参考和…

携程旅行 登录分析

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 逆向分析 所有加密流程基本一样就说…

西藏酥油茶:高原上的醇香温暖

西藏酥油茶:高原上的醇香温暖 在西藏高原,有一种饮品,它不仅滋养了一代又一代的藏民,还承载着丰富的文化与历史,它就是西藏酥油茶。酥油茶,藏语称为“恰苏玛”,意为搅动的茶,是藏族人民日常生活中不可或缺的一部分,更是待客、祭祀等活动中的重要礼仪物品。 历史与文化渊源 酥…

刷题总结 回溯算法

为了方便复习并且在把算法忘掉的时候能尽量快速的捡起来 刷完回溯算法这里需要做个总结 回溯算法的适用范围 回溯算法是深度优先搜索&#xff08;DFS&#xff09;的一种特定应用&#xff0c;在DFS的基础上引入了约束检查和回退机制。 相比于普通的DFS&#xff0c;回溯法的优…

Antd React Form使用Radio嵌套多个Select和Input的处理

使用Antd React Form使用Radio会遇到嵌套多个Select和Input的处理&#xff0c;需要多层嵌套和处理默认事件和冒泡&#xff0c;具体实现过程直接上代码。 实现效果布局如下图 代码 <Formname"basic"form{form}labelWrap{...formItemLayoutSpan(5, 19)}onFinish{on…

Linux(Centos、Ubuntu) 系统安装jenkins服务

该文章手把手演示在Linux系统下如何安装jenkins服务、并自定义jenkins数据文件位置、以及jenkins如何设置国内镜像源加速&#xff0c;解决插件下载失败问题 安装方式&#xff1a;war包安装 阿里云提供的war下载源地址&#xff1a;https://mirrors.aliyun.com/jenkins/war/?s…

【基于无线电的数据通信链】Link 11 仿真测试

〇、废话 Link 11 仿真测试 涉及多个方面&#xff0c;包括信号仿真、协议模拟、数据链路层的仿真以及网络性能评估等。Link 11 是一种基于 HF&#xff08;高频&#xff09; 或 UHF&#xff08;超高频&#xff09; 波段的无线通信协议&#xff0c;主要用于军事通信系统中。为了…

VScode 开发 Springboot 程序

1. 通过maven创建springboot程序 输入 mvn archetype:generate 选择模板&#xff0c;一般默认选择为第 7 种方式&#xff1b; 选择之后&#xff0c;一般要你填写如下内容&#xff1a; groupId: 组织名称&#xff1b;artifactId: 项目名称&#xff1b;version: 版本&#xff0…

深入MapReduce——引入

引入 前面我们已经深入了HDFS的设计与实现&#xff0c;对于分布式系统也有了不错的理解。 但HDFS仅仅解决了海量数据存储和读写的问题。要想让数据产生价值&#xff0c;一定是需要从数据中挖掘出价值才行&#xff0c;这就需要我们拥有海量数据的计算处理能力。 下面我们还是…

springfox-swagger-ui 3.0.0 配置

在3.0中&#xff0c;访问地址URL变了。 http://地址:端口/项目名/swagger-ui/ SpringBoot maven项目引入 <dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>3.0.0</version> </…

如何解压7z文件?8种方法(Win/Mac/手机/网页端)

7z 文件是一种高效的压缩文件格式&#xff0c;由 7 - Zip 软件开发者所采用。它运用独特的压缩算法&#xff0c;能显著缩小文件体积&#xff0c;便于存储与传输各类数据&#xff0c;像软件安装包、大型资料集等。但要使用其中内容&#xff0c;就必须解压&#xff0c;因为处于压…

Spring Boot 邂逅Netty:构建高性能网络应用的奇妙之旅

一、引言 在当今数字化时代&#xff0c;构建高效、可靠的网络应用是开发者面临的重要挑战。Spring Boot 作为一款强大的 Java 开发框架&#xff0c;以其快速开发、简洁配置和丰富的生态支持&#xff0c;深受广大开发者喜爱。而 Netty 作为高性能、异步的网络通信框架&#xff…

[STM32 - 野火] - - - 固件库学习笔记 - - -十一.电源管理系统

一、电源管理系统简介 电源管理系统是STM32硬件设计和系统运行的基础&#xff0c;它不仅为芯片本身提供稳定的电源&#xff0c;还通过多种电源管理功能优化功耗、延长电池寿命&#xff0c;并确保系统的可靠性和稳定性。 二、电源监控器 作用&#xff1a;保证STM32芯片工作在…

从 Web2 到 Web3:技术演进中的关键变革

随着互联网的快速发展&#xff0c;Web 技术经历了从 Web1 到 Web2&#xff0c;再到当前热议的 Web3 的演变。每一次技术迭代不仅仅是技术本身的升级&#xff0c;更代表着对社会、经济和文化的深刻影响。本文将带你走过 Web2 到 Web3 的技术演进&#xff0c;探讨其中的关键变革&…

Android实战经验篇-玩转Selinux(详解版)

列文章转如下链接&#xff1a; Android Display Graphics系列文章-汇总 Android实战经验篇-系列文章汇总 本文主要包括部分&#xff1a; 一、Selinux概述 1.1 SELinux是什么&#xff1f; 1.2 自主访问控制&#xff08;DAC&#xff09; 1.3 强制访问控制&#xff08;MAC&…

CLOUDFLARE代理请求重定向你太多次

现象 使用CLOUDFLARE代理前请求正常&#xff0c;使用CLOUDFLARE代理请求后出现 原因分析 以下是我的猜测&#xff0c;在默认情况下 CLOUDFLARE代理&#xff0c;可能是直接请求我们服务器的IP&#xff0c;比如&#xff1a;http://1.1.1.1 而不是通过域名的方式&#xff08;如…