【C++】-list的模拟实现

news2024/11/26 8:50:13

在这里插入图片描述
💖作者:小树苗渴望变成参天大树🎈
🎉作者宣言:认真写好每一篇博客💤
🎊作者gitee:gitee✨
💞作者专栏:C语言,数据结构初阶,Linux,C++ 动态规划算法🎄
如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧!

文章目录

  • 前言
  • 一、源码介绍
  • 二、模拟实现
    • 2.1list的基本框架
    • 2.2 迭代器的定义
    • 2.3迭代器函数
    • 2.4insert和erase函数
    • 2.5 头插头删和尾插尾删函数
    • 2.6构造函数
    • 2.7resize函数
    • 2.8size和clear函数
    • 2.9赋值运算符和swap函数
  • 三、代码汇总
  • 四、总结


前言

今天我们开始来介绍一下list的具体实现,他的难点就在迭代器的实现上,其余的对大家来说应该都是小菜一碟,因为我们不是使用原生指针,为什么要使用原生指针??
第一、它是一个内置类型(int*等)这个类型里面i你没有其他属性要描述的。
第二、它所在的结构地址是连续的,这样就不需要对++等其他运算符进行重载。

接下来我们一起来看看list具体是怎么实现的吧。


一、源码介绍

我们通过vector的模拟实现,已经大致了解看文档的顺序,我们先来看看这个list的成员变量
在这里插入图片描述
我们的list是一个双向带头的循环链表,所以我们将结点独立包装起来,这样方便申请结点的空间, 因为是带头的所以再构造函数里面肯定要先申请一个头结点
在这里插入图片描述
接下来我们来看看迭代器是怎么实现的:
在这里插入图片描述

按照以往我们喜欢定义一个原生指针,这是优化结构上的一些特殊性和类型本身的特性让它使用原生指针不会出错,再list中,我们的结点类型是link_type,这是一个结点的指针类型,不知道的同学可能类似于这样定义迭代器:typedef link_type iterator,这样显然是不行的,因为我们就没有办法对++等其他运算符进行重载,来重新定义其含义,所以我们要按照库里面的一样使用一个结构体将其封装起来,我们的结构体也就只有一个link_type类型的属性,这样才能对重载其他运算符,至于其他的一个模板参数啊,这个到时候再说,大致已经有了大致的了解的吧。

我们的list看这些结构大致就够了,其他的函数功能实现都是很简单的。我们来看具体模拟实现

二、模拟实现

再模拟实现之前我们要自定义一个命名空间域,防止和库里面的名字发生冲突,我们按照库里面一样,创建一个结构体类型的结点出来:

namespace xdh 
{
	template<class T>//定义一个规定节点的数据类型的模板
	struct list_Node//将节点封装起来
	{

		list_Node(const T& val = T())//对结点进行初始化。因为结点都是new出来的,还没有连接关系	
			:_next(nullptr)
			, _prev(nullptr)
			, _val(val)
		{}
		//节点的各个属性
		list_Node<T>* _prev ;//指向前一个节点
		list_Node<T>* _next;//指向后一个节点
		T _val; //此节点的值
	};
}

2.1list的基本框架

我们要定义list,里面有属性和构造方法:

	template<class T>
	class list
	{
		typedef list_Node<T> Node;//将结点的类型进行重命名,我再这里没有重命名为指针类型,那么再属性声明的时候就要至于指针
	public:
		list()//默认构造器,相当于创建了一个头结点
		{
			createHead();
		}
		private:
		void createHead()
		{
			_head = new Node();
			_head->_prev = _head;
			_head->_next = _head;
		}
	private:
		Node* _head=nullptr;//定义一个带头的双向链表,给一个缺省值就不需要初始化列表了
		size_t _size = 0;//统计list里面的结点个数,这个做了一下优化
	};
}

此时的链表结构是这样的:
在这里插入图片描述

2.2 迭代器的定义

通过上面的分析,我们需要将结点封装起来。

	template<class T>
	struct list_iterator//将结点指针封装起来,含义还是结点指针
	{
		typedef list_Node<T> Node;
		typedef list_iterator<T> Self;

		Node* _node;//结点指针,不能直接写成typedef Node* iterator;

		list_iterator(Node* node=nullptr)//给结点初始化
			:_node(node)
		{}

		T& operator*()//重载解引用运算符
		{
			return _node->_val;//访问结点里面的数据
		}
		Self& operator++()//实现了对++运算符的重载
		{
			_node= _node->_next;
			return *this;
		}

		Self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		Self operator++(int)
		{
			list_iterator<T> tmp(*this);//先将此结点的迭代器保留
			_node = _node->_next;
			return tmp;
		}
		Self operator--(int)
		{
			list_iterator<T> tmp(*this);
			_node = _node->_prev;
			return tmp;
		}

		bool operator!=(const Self&it)const
		{
			return _node != it._node;
		}
		bool operator==(const Self& it)const
		{
			return _node == it._node;
		}
	};

通过上面的代码,我们发现我们即通过了解引用获取结点里面的数据,也重载了++等其他运算符,为了可以更好找到下一个结点的指针,也可以判断结束条件,我们再list面只需要重命名为**typedef list_iterator iterator;**就可以了


const 对象的迭代器我们怎么去定义呢??
相信大部分和博主第一次一样,毫不犹豫的这样去定义
typedef const list_iterator<T,T,T*> const_iterator;
但是往往不是这样的,大家可以回想一下const int * p 和int * const p,我们家const的目的到底是为了什么,是不是就不希望结点里面的内容被修改,而我们上面的这种定义方式是将迭代器定义为const了,而不是把迭代器指向的内容变成了const,所以我们要对指向的内容加const,那我们应该怎么做呢??

在这里插入图片描述
想对指向的类型进行const修饰,只需要再返回的时候加一个const就行了,那么就不能改变其内容了。但是我们不可能使用两份相似度这么搞的代码,这样太冗余了,写实可以,但是不太好,所以我们定义了第二个模板参数,来控制这个

在这里插入图片描述


我们的结点访问数据的形式还有一种方法就是->,那我们应该怎么去重载呢??

知识回顾:
在这里插入图片描述
我们发现结构体类型只能通过点来访问内容,而结构体指针可以通过箭头来访问内容,既然这样,我们取得数据的地址将其返回不就行了??也是再迭代器里面去重载

		T* operator->()//重载箭头运算符,使用的时候会优化,本来是(it->)->_val,优化成it->_val
		{
			return &(_node->_val);
			//return &(operator*());
		}

我们来看一下:
在这里插入图片描述
可以更好的解释了上面代码里面注释的那句话了。


但是我们的箭头也是访问数据,也有const所以我们还需要再传一个模板参数过去,来控制箭头的返回值

在这里插入图片描述
迭代器的最终实现为:

template<class T,class Ref,class Ptr>//第二个模板参数是为了控制返回的数据是否是const类型的
										 //第三个模板参数是为了控制返回的数据的指针是否是const类型
	struct list_iterator//将结点指针封装起来
	{
		typedef list_Node<T> Node;
		typedef list_iterator<T,Ref,Ptr> Self;

		Node* _node;//结点指针,不能直接写成typedef Node* iterator;

		list_iterator(Node* node=nullptr)//给结点初始化
			:_node(node)
		{}

		Ref& operator*()//重载解引用运算符
		{
			return _node->_val;
		}

		Ptr operator->()//重载箭头运算符,使用的时候会优化,本来是(it->)->_val,优化成it->_val
		{
			return &(_node->_val);
		}
		Self& operator++()
		{
			_node= _node->_next;
			return *this;
		}

		Self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		Self operator++(int)
		{
			list_iterator<T> tmp(*this);
			_node = _node->_next;
			return tmp;
		}
		Self operator--(int)
		{
			list_iterator<T> tmp(*this);
			_node = _node->_prev;
			return tmp;
		}

		bool operator!=(const Self&it)const
		{
			return _node != it._node;
		}
		bool operator==(const Self& it)const
		{
			return _node == it._node;
		}
	};

上面的实现过后,其他的就简单了

2.3迭代器函数

说明一下:我们的头结点是end的位置,头结点的下一个是begin的位置

iterator begin()
		{
			return _head->_next;//单参数的隐式类型转换
			//return iterator(_head->_next);

		}
		iterator end()
		{
			return _head;
			//return iterator(_head);

		}
		const_iterator begin()const
		{
			return _head->_next;
			//return const_iterator(_head->_next);

		}
		const_iterator end()const
		{
			return _head;
			//return const_iterator(_head);
		}

2.4insert和erase函数

iterator insert(iterator pos, const T& val)
		{
			Node* newnode = new Node(val);
			Node* tail = pos._node->_prev;
			newnode->_next = pos._node;
			pos._node->_prev = newnode;

			tail->_next = newnode;
			newnode->_prev = tail;

			++_size;
			return newnode;
			//return iterator(newnode);
		}
		iterator erase(iterator pos)
		{
			assert(pos != end());//end位置就是头节点的位置,所以不能被删除
			Node* del = pos._node;//
			Node* ret = del->_next;//保留被删除结点的下一个位置的结点,作为返回值,防止迭代器失效用的

			del->_prev->_next = del->_next;
			del->_next->_prev = del->_prev;
			delete del;

			--_size;
			return ret;
			//return iterator(ret);
		}

这都是我们链表相关的知识,我就不做过多的介绍了。

2.5 头插头删和尾插尾删函数

都是复用insert和erase:

void push_back(const T& val)//尾插
		{
			insert(end(), val);
		}
		void pop_back()//尾删
		{
			erase(--end());
		}

		void push_front(const T& val)//头插
		{
			insert(begin(), val);
		}

		void pop_front()//头删
		{
			erase(begin());
		}

2.6构造函数

list()//默认构造器
		{
			createHead();
		}

		list(size_t n, const T& val = T())
		{
			createHead();//头结点每次都要初始化的
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
		}
		list(const list<T>& lt)//拷贝构造
		{
			createHead();
			for (auto& e : lt)
			{
				push_back(e);
			}
		}

2.7resize函数

改变有效字符个数的。

		void resize(size_t n, const T& val = T())
		{
			if (n < _size)//有效字符小于本身的大效
			{
				int ret = _size-n;//记录多出来多少,方便删除
				for (int i = 0; i <ret; i++)
				{
					erase(--end());
				}
			}
			else
			{
				int ret = n - _size;//记录多出来多少,方便插入
				for (int i = 0; i < ret; i++)
				{
					push_back(val);
				}
			}
		}

2.8size和clear函数

这两个函数还是比较简单的,要是没有一开始统计个数的_size,那么计算个数就要遍历链表了。

		size_t size()const
		{
			return _size;
		}
		void clear()
		{
			int n = _size;
			for (int i = 0;i < n; i++)//就相当于把有效字符都删除
			{
				erase(begin());
			}
		}

2.9赋值运算符和swap函数

	//============swap函数==============
		void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);

		}
		//=============赋值运算符重载===========
		list<T>& operator=(list<T> lt)
		{
			swap(lt);//这个大家再vector那一块应该非常的熟悉了
			return *this;
		}

到这里我们的list的模拟实现就结束了,就是迭代器那一块比较难理解其余的都挺简单的

三、代码汇总

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
namespace xdh 
{
	template<class T>//定义一个规定节点的数据类型的模板
	struct list_Node//将节点封装起来
	{

		list_Node(const T& val = T())	
			:_next(nullptr)
			, _prev(nullptr)
			, _val(val)
		{}
		//节点的各个属性
		list_Node<T>* _prev ;//指向前一个节点
		list_Node<T>* _next;//指向后一个节点
		T _val; //此节点的值
	};
	template<class T,class Ref,class Ptr>//第二个模板参数是为了控制返回的数据是否是const类型的
										 //第三个模板参数是为了控制返回的数据的指针是否是const类型
	struct list_iterator//将结点指针封装起来
	{
		typedef list_Node<T> Node;
		typedef list_iterator<T,Ref,Ptr> Self;

		Node* _node;//结点指针,不能直接写成typedef Node* iterator;

		list_iterator(Node* node=nullptr)//给结点初始化
			:_node(node)
		{}

		Ref& operator*()//重载解引用运算符
		{
			return _node->_val;
		}

		Ptr operator->()//重载箭头运算符,使用的时候会优化,本来是(it->)->_val,优化成it->_val
		{
			return &(_node->_val);
		}
		Self& operator++()
		{
			_node= _node->_next;
			return *this;
		}

		Self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		Self operator++(int)
		{
			list_iterator<T> tmp(*this);
			_node = _node->_next;
			return tmp;
		}
		Self operator--(int)
		{
			list_iterator<T> tmp(*this);
			_node = _node->_prev;
			return tmp;
		}

		bool operator!=(const Self&it)const
		{
			return _node != it._node;
		}
		bool operator==(const Self& it)const
		{
			return _node == it._node;
		}

		
	};

	template<class T>
	class list
	{
		typedef list_Node<T> Node;//将节点进行重命名
	public:
		typedef list_iterator<T,T,T*> iterator;//迭代器要对外暴露,所以放在public里面
		typedef list_iterator<T,const T,const T*> const_iterator;//const修饰的不是迭代器本身,而是迭代器指向的那个内容不能被修改
		list()//默认构造器
		{
			createHead();
		}

		list(size_t n, const T& val = T())
		{
			createHead();
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
		}
		list(const list<T>& lt)//拷贝构造
		{
			createHead();
			for (auto& e : lt)
			{
				push_back(e);
			}
		}
		//============swap函数==============
		void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);

		}
		//=============赋值运算符重载===========
		list<T>& operator=(list<T> lt)
		{
			swap(lt);
			return *this;
		}
		size_t size()const
		{
			return _size;
		}
		void resize(size_t n, const T& val = T())
		{
			if (n < _size)//有效字符小于本身的大效
			{
				int ret = _size-n;//记录多出来多少,方便删除
				for (int i = 0; i <ret; i++)
				{
					erase(--end());
				}
			}
			else
			{
				int ret = n - _size;//记录多出来多少,方便插入
				for (int i = 0; i < ret; i++)
				{
					push_back(val);
				}
			}
		}
		void clear()
		{
			int n = _size;
			for (int i = 0;i < n; i++)//就相当于把有效字符都删除
			{
				erase(begin());
			}
		}
		//==============迭代器==================
		iterator begin()
		{
			return _head->_next;//单参数的隐式类型转换
			//return iterator(_head->_next);

		}
		iterator end()
		{
			return _head;
			//return iterator(_head);

		}
		const_iterator begin()const
		{
			return _head->_next;
			//return const_iterator(_head->_next);

		}
		const_iterator end()const
		{
			return _head;
			//return const_iterator(_head);
		}
		void push_back(const T& val)//尾插
		{
			insert(end(), val);
		}
		void pop_back()//尾删
		{
			erase(--end());
		}

		void push_front(const T& val)//头插
		{
			insert(begin(), val);
		}

		void pop_front()//头删
		{
			erase(begin());
		}
		iterator insert(iterator pos, const T& val)
		{
			Node* newnode = new Node(val);
			Node* tail = pos._node->_prev;
			newnode->_next = pos._node;
			pos._node->_prev = newnode;

			tail->_next = newnode;
			newnode->_prev = tail;

			++_size;
			return newnode;
			//return iterator(newnode);
		}
		iterator erase(iterator pos)
		{
			assert(pos != end());//end位置就是头节点的位置,所以不能被删除
			Node* del = pos._node;//
			Node* ret = del->_next;//保留被删除结点的下一个位置的结点,作为返回值,防止迭代器失效用的

			del->_prev->_next = del->_next;
			del->_next->_prev = del->_prev;
			delete del;

			--_size;
			return ret;
			//return iterator(ret);
		}

	private:
		void createHead()
		{
			_head = new Node();
			_head->_prev = _head;
			_head->_next = _head;
		}
	private:
		Node* _head=nullptr;//定义一个带头的双向链表
		size_t _size = 0;//统计list里面的结点个数
	};
}

四、总结

大家这届一定要好好理解,因为像线性结构的不多,不是都可以使用原生指针的,后面的还有树形结构,更加的复杂,所以我们要理解其中的方法,后面学起来会得心应手,下一篇我们开始进入下一个STL容器的学习,是栈和队列,相对来说比较简单,因为是复用前面的容器,所以也叫容器适配器,到下一篇再介绍吧
请添加图片描述

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

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

相关文章

linux文件锁(保证只能同时启动一个实例,不同时启动多个实例)

文章目录 C如果程序异常退出&#xff0c;会自动释放锁码&#xff1f; shell脚本python脚本 C 可以使用Linux中的进程锁来实现只能启动一个实例的功能。一种常见的方法是使用文件锁&#xff08;File Locking&#xff09;。 可以在程序启动时创建一个特定的文件&#xff0c;并尝…

Unknown column ‘str_time‘ in ‘field list‘

报这个错说明这个strTime在对象里有&#xff0c;在数据表里没有&#xff0c;数据库表里加上这个属性字段就行了。 或者可以加个注解忽略此映射字段&#xff1a; TableField(existfalse) 问题解决&#xff01;&#xff01;&#xff01;

常用语言的线程模型(Java、go、C++、python3) | 京东云技术团队

背景知识 软件是如何驱动硬件的&#xff1f; 硬件是需要相关的驱动程序才能执行&#xff0c;而驱动程序是安装在操作系统内核中。如果写了一个程序A&#xff0c;A程序想操作硬件工作&#xff0c;首先需要进行系统调用&#xff0c;由内核去找对应的驱动程序驱使硬件工作。而驱动…

Python学习笔记-WSGI接口

Web服务器网关接口&#xff08;Python Web Server Gateway Interface&#xff0c;缩写为WSGI&#xff09;是为Python语言定义的Web服务器和Web应用程序或框架之间的一种简单而通用的接口。自从WSGI被开发出来以后&#xff0c;许多其它语言中也出现了类似接口。 是CGI和FastCGI…

企业拥抱开源的同时,该如何做好风险防范?- 对话新思科技杨国梁

“软件供应链安全”相关文章合集 杨国梁 新思科技软件质量与安全部门高级安全架构师 当前&#xff0c;开源组件已成为软件应用程序中不可或缺的一部分。然而&#xff0c;随着开源软件数量的快速增长&#xff0c;应用领域的不断扩大&#xff0c;随之而来的安全问题也变得愈发严峻…

分页列表缓存,你真的会吗

看了很多关于缓存的文章&#xff0c;其中多级缓存思路&#xff0c;分页列表缓存这些知识点给了我很大的启发性。 写这篇文章&#xff0c;我们聊聊分页列表缓存&#xff0c;希望能帮助大家提升缓存技术认知。 1 直接缓存分页列表结果 显而易见&#xff0c;这是最简单易懂的方…

终点与起点,“玄魂工作室”的最后一篇推文

本文原载于 微信公众号 “VisActor”&#xff0c;https://mp.weixin.qq.com/s/tAjcPT8FjIQ6qbhSNWr2hw 做一个决绝的人 用了一天时间&#xff0c;我手工删除了500多篇原创文章&#xff0c;200多篇转载文章&#xff0c;删除了所有菜单、清空了所有自动回复、修改了公众号简介、…

JS事件及相关操作

JS如何处理事件&#xff1a;&#xff08;有三种处理方式&#xff09; this指向事件源 this指向事件源&#xff1a; this指向事件源&#xff1a; 冒泡流是从里向外&#xff0c;一层一层向父级抛&#xff0c;目前只有ie浏览器支持 捕获流是从外向里

2023年7月济南/郑州/深圳DAMA-CDGP数据治理专家认证报名

DAMA认证为数据管理专业人士提供职业目标晋升规划&#xff0c;彰显了职业发展里程碑及发展阶梯定义&#xff0c;帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力&#xff0c;促进开展工作实践应用及实际问题解决&#xff0c;形成企业所需的新数字经济下的核心职业…

C++ 南科大于仕琪课程

目录 课程信息 7.4 function pointers 7.5 递归函数(Recursive functions) 课程信息 作者&#xff1a;于仕琪(南方科技大学计算机系副教授/opencv中国团队负责人) 视频地址&#xff1a;快速学习C和C&#xff0c;基础语法和优化策略&#xff0c;学了不再怕指针(南科大计算机…

Android事件高级手势处理

原文链接 Android事件高级手势处理 GestureDetector只能帮我们处理并识别一些常用的简单的手势&#xff0c;如点击&#xff0c;双击&#xff0c;长按&#xff0c;滑动&#xff08;Scroll&#xff09;和快速滑动&#xff08;Fling&#xff09;等&#xff0c;一般情况下&#xf…

js 实现导入导出功能(FileSave.js)

点击导出按钮弹出文件框导出文件&#xff0c;点击导入选择文件获取文件数据&#xff0c;以下我保存的是json。 html&#xff1a; <div class"layui-inline"> <div class"layui-btn-group"><button type"button" class"la…

文心一言 VS 讯飞星火 VS chatgpt (61)-- 算法导论6.4 5题

文心一言 VS 讯飞星火 VS chatgpt &#xff08;61&#xff09;-- 算法导论6.4 5题 五、证明:在所有元素都不同的情况下&#xff0c;HEAPSORT 的时间复杂度是 Ω (nlgn)。 文心一言&#xff1a; 要证明在所有元素都不同的情况下&#xff0c;HEAPSORT 的时间复杂度是 Ω(n log…

音视频H265编码; Socket通信实现N对N连接代码示例

H.265编码和Socket通信是两个不同的概念&#xff0c;它们分别涉及视频编码和网络通信。在实现N对N连接时&#xff0c;您可以将它们结合起来&#xff0c;但要注意每个方面的具体实现。 H.265编码&#xff08;视频编码&#xff09;&#xff1a; H.265编码涉及将视频数据进行压缩…

速腾robosense-sdk配置和使用方法

官方的安装和配置教程https://github.com/RoboSense-LiDAR/rslidar_sdk/ 1.手动下载sdk&#xff0c;下载tar.gz&#xff0c;然后解压缩:Releases RoboSense-LiDAR/rslidar_sdk GitHub 2.个人电脑连接速腾激光雷达后&#xff0c;确保能够ping通。当连接网线后&#xff0c;电…

使用 appium 进行微信小程序的自动化测试

目录 前言&#xff1a; 微信小程序结构 自动化用例的调整 示例代码 后记 前言&#xff1a; 微信小程序是一种流行的移动应用程序&#xff0c;它在移动设备上提供了丰富的功能和用户体验。为了确保微信小程序的质量和稳定性&#xff0c;自动化测试是必不可少的一环。Appiu…

视频融合平台EasyCVR级联后上级平台播放失败的问题排查与优化

EasyCVR视频融合平台基于云边端智能协同架构&#xff0c;具有强大的数据接入、处理及分发能力&#xff0c;平台可提供视频监控直播、云端录像、云存储、录像检索与回看、智能告警、平台级联、云台控制等视频能力与服务&#xff0c;可支持多协议、多类型的海量设备接入与分发。 …

MATLAB数据类型及代码实现

本推文是MATLAB基础与统计实战课程中的S02-1数据类型及代码实现 矩阵(Matrix) MATLAB最基础的数据单位是矩阵。什么是矩阵&#xff1f; 如下图可以看作M行乘以N列的数的组。这就是矩阵最基础的显示 ■区别于其他数据分 析软件或者编程语言的最大一 个特点(如&#xff0c;转…

DOM4j及源码分析

文章目录 DOM4jXML 解析技术原理XML 解析技术介绍 DOM4J 介绍DOM4j 中&#xff0c;获得 Document 对象的方式有三种源码增删改查代码 DOM4j 文档: https://dom4j.github.io/javadoc/1.6.1/ 本地文档: dom4j-1.6.1\docs\index.html XML 解析技术原理 不管是 html 文件还是 x…

OAuth2.0详细介绍与实践(通俗易懂)

一、OAuth2.0介绍 1.1 概述 OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是OAUTH的授权不会使第三方触及到用户的帐号信息&#xff08;如用户名与密码&#xff09;&#xff0c;即第三方无需使用用户的用户名与密码就可以申请获…