C++容器——list的模拟实现

news2024/9/24 9:23:12

目录

一.list的基本结构

二. 接下来就是对list类构造函数的设计了:

三.链表数据的增加:

四.接下来就是迭代器的创建了:

四.简单函数的实现:

五.构造与析构 

六.拷贝构造和赋值重载

传统写法:

现代写法:

七.迭代器模板类型


     一.list的基本结构

         想要模拟实现list类,就需要先了解它的底层架构,上篇博客讲到:list容器的底层是双向链表,那么就需要自定义一个节点类,通过节点类可以创建节点,设置节点的前后指针和数据值。之后便可以通过该类类型创建list类的成员变量。

template<class T>
struct list_node {	//该类为内部类,是list的内部类

	list_node(const T& val)
		:_next(nullptr)
		, _prev(nullptr)
		, _data(val) {
	}
	//成员变量
	list_node* _next;    //后指针
	list_node* _prev;    //前指针
	T _data;            //值
};

template<class T>
class list {
    public:
	    typedef list_node<T>  node;    //将节点类作为类类型

    private:
	    node* _head;	//指向堆区空间链表的指针
	    size_t _size;    //计数
};

node* 类型就好比是对节点类的封装。 

二. 接下来就是对list类构造函数的设计了:

template<class T>
class list {
    public:
	    typedef list_node<T>  node;    //将节点类作为类类型


    //初始化操作
    void empty_Init() {
		_head = new node(T());
		_head->_next = _head;
		_head->_prev = _head;
		_size = 0;
	}

	list()    //构造函数
	:_head(nullptr)
    ,_size(0){
		empty_Init();
	}

  private:
	    node* _head;	//指向堆区空间链表的指针
	    size_t _size;    //计数
};

        对构造函数的初始化设计就是:创建哨兵位头结点,让链表指针指向哨兵位头结点,由哨兵头节点去控制节点的增删查改,避免了由链表指针去控制,操作和形式上都方便了很多。

         注:哨兵位头结点的创建是在empty_Init()函数中进行的!

三.链表数据的增加:

template<class T>
class list{

public:
    typedef Node<T> node;
     //尾插  
     void push_back(const T& val) {
		 node* newnode = new node(val);
		 node* tail = _head->_prev;
		 tail->_next = newnode;
		 newnode->_prev = tail;
		 newnode->_next = _head;
		 _head->_prev = newnode;
		 ++_size;
	 }
    //尾删
	 void pop_back() {
		 assert(!empty());
		 node* tail = _head->_prev;
		 node* last = tail->_prev;
		 last->_next = _head;
		 _head->_prev = last;
		 delete tail;
		 --_size;
	 }
    //头插
	 void push_front(const T& val) {
		 node* newnode = new node(val);
		 node* first = _head->_next;
		 _head->_next = newnode;
		 newnode->_prev = _head->_next;
		 newnode->_next = first;
		 first->_prev = newnode;
		 ++_size;
	 }
    //头删
	 void pop_front() {
		 assert(!empty());
		 node* first = _head->_next;
		 node* second = first->_next;
		 _head->_next = second;
		 second->_prev = _head->_next;
		 delete first;
		 --_size;
	 }

    //任意位置的插入
    iterator insert(iterator pos, const T& val=T()) {
		 if (pos == this->begin()) {
			 push_front(val);    //复用代码
		 }

		 else if (pos == this->end()) {
			 push_back(val);    //复用代码
		 }

		 else {
			 node* newnode = new node(val);
			 node* cur = pos.phead;
			 node* last = cur->_prev;
			 last->_next = newnode;
			 newnode->_prev = last;
			 newnode->_next = cur;
			 cur->_prev = newnode;
			 ++_size;
		 }
		 return pos;
	 }

    //任意位置的删除
     iterator erase(iterator pos) {
		 assert(!empty());
		 if (pos == this->begin()) {
			 pop_front();
		 }
		 else if (pos == this->end()) {
			 pop_back();
		 }
		 else {
			 node* cur = pos.phead;
			 node* tail = cur->_next;
			 node* last = cur->_prev;
			 last->_next = tail;
			 tail->_prev = last;
			 delete cur;
			 --_size;
		 }
		 return pos;
	 }

  private:
	    node* _head;	//指向堆区空间链表的指针
	    size_t _size;    //计数

};

        对于数据的增加和删除,头插头删、尾插尾删简单就不说了,重点是insert和erase函数的实现,如上代码,在insert和erase中,各有三种情况,其中头尾的操作直接复用函数即可,对于中间位置的插入删除情况,我想说的是,指定的pos参数是iterator类型——自定义迭代器类,它是指针!!! 它只是指向该节点元素的位置,所以想要获取该位置的节点,就需要pos.phead才能获取到该节点,只有获取到该节点,才能使用该节点附近的前后指针! 

 

四.接下来就是迭代器的创建了:

        在对vector、String容器的模拟实现中,我并没有单独创建迭代器,这是因为这两个容器的底层都是数组,是一段连续的地址空间,对于迭代器中的成员begin、end都是可以直接让指针进行类型的字节++/--进行的,很方便的,是使用原生指针来确定位置

        而对于list容器来说,它的底层是链表,各个节点的位置是不连续的,随机的。使用原生指针并不能遍历到每一个对象的元素!所以针对list容器,需要创建一个自定义类型的迭代器进行链表的遍历。 

template<class T>
	struct list_iterator
	{
		typedef list_node<T> node;

		node* _pnode;            //成员变量

		list_iterator(node* p)    //构造函数
			:_pnode(p){}

		T& operator*(){                    //指针解引用
			return _pnode->_data;
		}
                
		list_iterator<T>& operator++(){    //指针++
			_pnode = _pnode->_next;
			return *this;
		}

		bool operator!=(const list_iterator<T>& it){   //!=运算符重载
			return _pnode != it._pnode;
		}
	};

template<class T>
	class list{
	public:
        typedef list_node<T> node;
		typedef list_iterator<T> iterator;

		iterator begin(){
			return iterator(_head->_next);
		}

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

        在自定义的迭代器类中,我根据平常练习vector、String的迭代器代码中,写了几个一定会用到的运算符重载函数:解引用、指针++,遍历所用到的!=等函数。

        写好自定义迭代器类后,需要在list类中重命名该类。

        写好迭代器后,我们就可以试验一下了:

 

 

 注:上面的迭代器只是普通迭代器的实现,还会有const迭代器、反向迭代器需要实现,意味着还得再写两个迭代器类。

四.简单函数的实现:

template<class T>
class list{
  public:
     size_t size()const {
		 return _size;
		 //方法2:利用指针遍历,每遍历一次记一次数
	 }

	 bool empty() const {
		 //方法1:
		 return _size == 0;
        //方法2:return _head->next==_head;
	 }

    void clear() {
		node* cur = _head->_next;
		 while (cur != _head) {
			 node* del = cur;
			 cur = cur->_next;
			 delete del;
		 }
		 cur->_next = _head;
		 cur->_prev = _head;
		 _size = 0;
	 }

	 T& front() {
		 return  this->begin().phead->_data;
	 }

	 T& back() {
		 return  this->end().phead->_prev->_data;
	 }

  private:
    node* _head;
    size_t _size;

    };

五.构造与析构 

        有了迭代器,我们就可以对list构造函数进行迭代器区间构造实现了:

template<class T>
class list{
public:
    //迭代器区间构造函数
    template<class Inputiterator>
	list(Inputiterator first, Inputiterator last) {		
		empty_Init();
		while (first != last) {
			push_back(*first);
			++first;
		}
	}

    void empty_Init() {
		_head = new node(T());
		_head->_next = _head;
		_head->_prev = _head;
		_size = 0;
	}

	list()    //无参构造
	{
		empty_Init();
	}

	//析构函数
	~list() {
		this->clear();
		delete _head;
		_head = nullptr;
		_size = 0;
	}
private:
    node* _head;
    size_t _size;
};

析构函数就是遍历链表中每个节点都进行遍历释放,置空指针,置零变量。

六.拷贝构造和赋值重载

 

传统写法:

//拷贝构造——传统写法
	list(const list<T>& lt) {
		empty_Init();
		for (auto& e : lt) {
				this->push_back(e);
			}
		}

//赋值重载函数——传统写法
    list<T>& operator=(const list<T>& lt) {
		if (this != &lt) {
			this->clear();
		}
		for (const auto& e : lt) {
			this->push_back(e);
		}
		return *this;
	}

        拷贝构造和赋值重载本质上都相同,都是复制已有的list对象,然后深拷贝数据给自己。深拷贝就是创建一个属于自己的头结点,剩下的数据就是浅拷贝(无脑将数据以遍历的方式让自己的头指针进行指针链接)。 

现代写法:

    //调用std库中swap函数进行成员交换
    void Swap(list<T>& lt) {
		std::swap(this->_head, lt._head);
		std::swap(this->_size, lt._size);
	}	

    //拷贝构造——现代写法
	list(const list<T>& lt) {
		empty_Init();
		list<T> tmp(lt.begin(), lt.end());		//调用迭代器区间构造函数
		this->Swap(tmp);
	}

    //赋值重载——现代写法
	list<T>& operator=(list<T> lt) {
		this->Swap(lt);    //值传递,形参的改变不影响实参
		return *this;
	}

 

七.迭代器模板类型

        上面讲迭代器的最后说了,迭代器有普通版、const版、反向版、反向const版,意味着我们需要创建四个迭代器类型,但迭代器能用到的运算符重载函数都一样,都是解引用、指针++、!=运算符。

//自定义普通迭代器类
template<class T>
	struct list_iterator{
		typedef list_node<T> node;
		node* _pnode;

		list_iterator(node* p)
			:_pnode(p){}

		T& operator*(){
			return _pnode->_data;
		}

		list_iterator<T>& operator++(){
			_pnode = _pnode->_next;
			return *this;
		}

		list_iterator<T>& operator--(){
			_pnode = _pnode->_prev;
			return *this;
		}

		bool operator!=(const list_iterator<T>& it){
			return _pnode != it._pnode;
		}
	};

    //const迭代器类
	template<class T>
	struct list_const_iterator{
		typedef list_node<T> node;
		node* _pnode;

		list_const_iterator(node* p)
			:_pnode(p){}

		const T& operator*(){
			return _pnode->_data;
		}

		list_const_iterator<T>& operator++(){
			_pnode = _pnode->_next;
			return *this;
		}

		list_const_iterator<T>& operator--(){
			_pnode = _pnode->_prev;
			return *this;
		}

		bool operator!=(const list_const_iterator<T>& it){
			return _pnode != it._pnode;
		}
	};

	template<class T>
	class list{
		typedef list_node<T> node;
	public:
		typedef list_iterator<T> iterator;
		typedef list_const_iterator<T> const_iterator;

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

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

		iterator begin(){
			return iterator(_head->_next);
		}

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

        如上,普通迭代器类和const迭代器类唯一的区别:是在遍历上,const迭代器类的解引用运算符重载函数中不能用*it修改数据,那么这俩迭代器类中其他函数的实现完全一样,这极大的造成了代码的冗余,降低了可读性!!!

        于是为了在一种迭代器类中体现不同类型的迭代器,可以这样做:

template<class T, class Ref, class Ptr>	
struct _list_iterator {
	typedef list_node<T> node;
	typedef _list_iterator<T, Ref,Ptr> Self;	//Self是T与ref,ptr 模板类型的另一种别称

	//迭代器构造函数
	_list_iterator(node* p)
		:_pnode(p) {}

	//解引用
	Ref operator*() {
		return _pnode->_data;
	}

    //箭头只有是结构体才可以用
	Ptr operator->() {
		return &_pnode->_data;		//返回的是该结点的地址
	}

	Self& operator++() {
		_pnode = _pnode->_next;
		return *this;
	}

	//后置++,使用占位符int,与前置++以示区分
	Self operator++(int) {
		Self tmp(*this);
		_pnode = _pnode->_next;
		return tmp;
	}
    //前置--
	Self& operator--() {
		_pnode = _pnode->_prev;
		return *this;
	}

	//后置--
	Self operator--(int) {
		Self tmp(*this);
		_pnode = _pnode->_prev;
		return tmp;
	}

	bool operator!=(const Self& lt) const {
		return _pnode != lt._pnode;
	}

	bool operator==(const Self& lt)  const{
		return _pnode == lt._pnode;
	}

	node* _pnode;
};

        在迭代器类中,采用了三个模板参数。这三个模板参数:T代表泛型值,Ref代表泛型引用,Ptr代表泛型指针,这三种参数主要应用于运算符重载函数的函数返回值,函数形参,相当方便,一举多得,通过不同实参的传递就可以调用不同类型的函数。

 

template<class T>
class list {
public:
    typedef list_node<T>  node;
    typedef _list_iterator<T, T&, T*> iterator;
	//typedef list_const_iterator<T> const_iterator;
	typedef _list_iterator<T, const T&, const T*> const_iterator;

完整模拟实现代码 .h文件:

#include<iostream>
#include<assert.h>

using std::cout;
using std::endl;

template<class T>
struct list_node {	//该类为内部类,是list的内部类

	list_node(const T& val)
		:_next(nullptr)
		, _prev(nullptr)
		, _data(val) {
	}

	//成员变量
	list_node* _next;
	list_node* _prev;
	T _data;
};

//typedef list_iterator<T, T&> iterator;
//typedef list_iterator<T, const T&> const_iterator;

//这种写法来源:vector<int>,vector<string>,vector<vector<int>> 
template<class T, class Ref, class Ptr>	//新增一个模板参数	,T是一种类型,ref是一种类型
struct list_iterator {
	typedef list_node<T> node;
	typedef list_iterator<T, Ref, Ptr> Self;	//Self是T与ref,ptr 模板类型的另一种别称

	//迭代器构造函数
	list_iterator(node* p)
		:_pnode(p) {}

	//在下面的运算符重载中,const版与非const版只有解引用运算符重载函数的类型不同,其他运算符重载都一样
	//所以operator* 的类型需要使用ref,ref可以理解为constT&, 而非const对象也可以调用const函数,权限够
	//const对象不可调用非const函数,权限不够,所以使用ref

	//解引用
	Ref operator*() {
		return _pnode->_data;
	}

	//箭头只有是结构体才可以用
	Ptr operator->() {
		return &_pnode->_data;		//返回的是该结点的地址
	}


	//前置++,
	//为啥要用引用? 原因:return *this ,this(迭代器对象)出了该函数体,还存在(this的生命周期在该类中是全局的)
	Self& operator++() {
		_pnode = _pnode->_next;
		return *this;
		//既然this还在,那么直接用引用返回,栈帧中不开临时空间,减少拷贝次数,提高效率
		//记住:使用引用返回的前提是,要返回的值出了函数体仍在才可以使用,否则会报错
	}

	//后置++,使用占位符int,与前置++以示区分
	Self operator++(int) {
		Self tmp(*this);
		_pnode = _pnode->_next;
		return tmp;
		//返回tmp后,tmp为临时对象,出了函数就消失了,tmp对象不在,需要拷贝,那就得用传值返回,在栈帧中
		//创建一个临时空间去接收返回的tmp对象数据。设置一个默认参数和前置++做区分,构成函数重载。
		//若使用引用返回,那么该函数结束后,返回的tmp已经不存在了,引用返回返回野指针(随机值)就会报错!!!
	}

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

	//后置--
	Self operator--(int) {
		Self tmp(*this);
		_pnode = _pnode->_prev;
		return tmp;
	}

	bool operator!=(const Self& lt) const {
		return _pnode != lt._pnode;
	}

	bool operator==(const Self& lt)  const{
		return _pnode == lt._pnode;
	}

	node* _pnode;
};

//--------------------------------------------------------------------------------

template<class T>
class list {
public:
	typedef list_node<T>  node;
	typedef list_iterator<T, T&, T*> iterator;
	typedef list_iterator<T, const T&, const T*> const_iterator;

	void empty_Init() {
		_head = new node(T());
		_head->_next = _head;
		_head->_prev = _head;
		_size = 0;
	}

	list(){
		empty_Init();
	}

	//析构
	~list() {
		this->clear();
		delete _head;
		_head = nullptr;
		_size = 0;
	}

	template<class Inputiterator>
	list(Inputiterator first, Inputiterator last) {		//拷贝构造的天选打工人
		//先初始化,给头节点,否则没法继续
		empty_Init();
		while (first != last) {
			push_back(*first);
			++first;
		}
	}

	void swap(list<T>& lt) {
		std::swap(this->_head, lt._head);
		std::swap(this->_size, lt._size);
	}

	void clear() {
		iterator it = this->begin();
		while (it != this->end()) {
			it = this->erase(it);
		}
	}

	//拷贝构造——传统写法
	/*list(const list<T>& lt) {
		empty_Init();
		for (auto& e : lt) {
				this->push_back(e);
			}
		}*/

		//拷贝构造——现代写法
	list(const list<T>& lt) {
		empty_Init();
		list<T> tmp(lt.begin(), lt.end());		//调用迭代器区间构造函数
		this->swap(tmp);
	}

	//赋值重载——传统写法
	/*list<T>& operator=(const list<T>& lt) {
		if (this != &lt) {
			this->clear();
		}
		for (const auto& e : lt) {
			this->push_back(e);
		}
		return *this;
	}*/

	//赋值重载——现代写法
	list<T>& operator=(list<T> lt) {
		this->swap(lt);
		return *this;
	}

	//迭代器
	iterator begin() {
		return iterator(_head->_next);
	}

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

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

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

	//尾插
	void push_back(const T& val) {
		node* newnode = new node(T(val));
		node* tail = _head->_prev;
		tail->_next = newnode;
		newnode->_prev = tail;
		newnode->_next = _head;
		_head->_prev = newnode;
	}

	//insert
	iterator insert(iterator pos, const T& val) {
		node* newnode = new node(T(val));
		node* cur = pos._pnode;
		node* first = cur->_prev;
		first->_next = newnode;
		newnode->_prev = first;
		newnode->_next = cur;
		cur->_prev = newnode;
		_size++;
		//insert后返回新节点的位置,那么下一次pos就会指向最近一次创建新节点的位置了
		return iterator(newnode);
	}

	iterator erase(iterator pos) {
		//pos不能指向哨兵位头节点,因为pos一旦指向哨兵位头,那么该链表一定为空,空链表是不能再删数据的
		assert(pos != end());
		node* first = pos._pnode->_prev;
		node* last = pos._pnode->_next;
		first->_next = last;
		last->_prev = first;
		delete pos._pnode;
		pos._pnode = nullptr;
		--_size;
		return iterator(last);
	}

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

	void pop_back() {
		erase(--end());
	}

	void pop_front() {
		erase(begin());
	}

	size_t size() const{
		/*size_t s = 0;
		iterator it = this->begin();
		while (it != this->end()) {
			++it;
			++s;
		}
		return s;*/

		//复用insert和erase
		//因为在链表中,一切的新增和减少都是复用的insert和erase,所以在insert和erase中size++,size--即可
		return _size;
	}

	bool empty() const {
		//return _head->_next == _head;

		//也可以复用size()
		return _size == 0;
	}

private:
	node* _head;	//头节点
	size_t _size;
};

 

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

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

相关文章

Docker—— consul的容器服务更新与发现

Docker—— consul的容器服务更新与发现 一、Consul概述1.什么是服务注册与发现2.什么是consul 二、consul 部署1.consul服务器①. 建立 Consul 服务②. 查看集群信息③. 通过 http api 获取集群信息 2.registrator服务器①. 安装 Gliderlabs/Registrator②. 测试服务发现功能是…

别再被割韭菜了,小白几块钱就能打造专属AI知识库

随着AIGC各种项目的越发成熟&#xff0c;打造自己的知识库&#xff0c;对于企业和个人来说就变的门槛越来越低&#xff0c;自己的知识库&#xff0c;有许多的好处&#xff0c;上传自己的知识文档&#xff0c;能让对话变的更加垂直专业。 但是博主看到网站很多商家动辄几千的收…

使用docker-compose搭建lnmpr环境

源码gitee compose 使用的三个步骤&#xff1a; • 使用 Dockerfile 定义应用程序的环境。 • 使用 docker-compose.yml 定义构成应用程序的服务&#xff0c;这样它们可以在隔离环境中一起运行。 • 最后&#xff0c;执行 docker-compose up -d 命令来启动并运行整个应用程序…

Carla教程一:动力学模型到LQR

Carla教程一、动力学模型到LQR 从运动学模型和动力学模型到LQR 模型就是可以描述车辆运动规律的模型。车辆建模都是基于自行车模型的设定,也就是将四个轮子抽象为自行车一样的两个轮子来建模。 1、运动学模型 运动学模型是基于几何关系分析出来的,一般适用于低俗情况下,…

【西安交通大学】:融合传统与创新的学府之旅

【西安交通大学】&#xff1a;融合传统与创新的学府之旅 引言历史与发展学校特色学科优势院系专业校园环境与设施学生生活与社团活动校友荣誉与成就未来发展展望总结&#x1f340;小结&#x1f340; &#x1f389;博客主页&#xff1a;小智_x0___0x_ &#x1f389;欢迎关注&…

【Linux】Http协议的学习

文章目录 前言一、了解HTTP协议是如何规定的总结 前言 HTTP协议&#xff08;超文本传输协议&#xff09;和我们上一篇写的网络版计算器中自己定制的协议一样&#xff0c;只不过Http协议是是一个非常好用的协议&#xff0c;所以我们可以直接用现成的不用自己再搞一套了。 一、了…

LeetCode Top100 Liked 题单(序号1~17)

01Two Sum - LeetCode 我自己写的代码【193ms】 因为不知道怎么加cmp函数&#xff0c;就只能pair的first设为值了&#xff0c;但其实这也是瞎做&#xff0c;应该也是O(n&#xff09;吧 class Solution { public:vector<int> twoSum(vector<int>& nums, int …

【观察】智能运维的“下半场”,看云智慧如何“开新局”

毫无疑问&#xff0c;随着数字化转型的加速&#xff0c;越来越多的企业正在把数字化战略提升到一个全新的高度&#xff0c;转型的进程也正从“浅层次”的数字化走向“深层次”的数字化。 也正因此&#xff0c;过去传统的人工运维方式越来越“捉襟见肘”&#xff0c;谋求运维模…

飞桨paddlespeech语音唤醒推理C定点实现

前面的文章&#xff08;飞桨paddlespeech语音唤醒推理C浮点实现&#xff09;讲了飞桨paddlespeech语音唤醒推理的C浮点实现。但是嵌入式设备通常CPU频率低和memory小&#xff0c;在嵌入式设备上要想流畅的运行语音唤醒功能&#xff0c;通常用的是定点实现。于是我就在浮点实现&…

【redis】通过配置文件简述redis的rdb和aof

redis的持久化方式有2种&#xff0c;rdb&#xff0c;即通过快照的方式将全量数据以二进制记录在磁盘中&#xff0c;aof&#xff0c;仅追加文件&#xff0c;将增量的写命令追加在aof文件中。在恢复的时候&#xff0c;rdb要更快&#xff0c;但是会丢失一部分数据。aof丢失数据极少…

HTML快速学习

目录 一、网页元素属性 1.全局属性 2.标签 2.1其他标签 2.2表单标签 2.3图像标签 2.4列表标签 2.5表格标签 2.6文本标签 二、编码 1.字符的数字表示法 2.字符的实体表示法 三、实践一下 一、网页元素属性 1.全局属性 id属性是元素在网页内的唯一标识符。 class…

207. 课程表 Python

文章目录 一、题目描述示例 1示例 2 二、代码三、解题思路 一、题目描述 你这个学期必须选修 numCourses 门课程&#xff0c;记为 0 到 numCourses - 1 。 在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出&#xff0c;其中 prerequisites[i] [ai, …

入门Linux基本指令(2)

这篇文章主要提供一些对文件操作的Linux基本指令&#xff0c;希望对大家有所帮助&#xff0c;三连支持&#xff01; 目录 cp指令(复制) mv指令(剪切) nano指令 cat指令(打印文件内容) > 输出重定向 >> 追加重定向 < 输入重定向 more指令 less指令(推荐) …

影刀下载,插件安装

1、下载 在影刀官网下载&#xff1a;www.yingdao.com 2、谷歌插件安装 参考&#xff1a; 影刀插件安装各种方式 浏览器安装插件说明 - 影刀帮助中心 安装说明&#xff1a;驱动外置 Chrome 需要安装插件&#xff0c;并且保证此插件处于开启状态 方式一&#xff1a;用户头…

利用sklearn 实现线性回归、非线性回归

代码&#xff1a; import pandas as pd import numpy as np import matplotlib import random from matplotlib import pyplot as plt from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import LinearRegression# 创建虚拟数据 x np.array(r…

nacos安装与基础配置

源码 https://github.com/alibaba/nacos https://gitee.com/mirrors/Nacos 编译 git clone https://github.com/alibaba/nacos.git cd nacos/ mvn -Prelease-nacos -Dmaven.test.skiptrue clean install -U ls -al distribution/target/// change the $version to your ac…

24考研数据结构-队列1

目录 3.2队列&#xff08;Queue&#xff09;3.2.1队列的基本概念3.2.2队列的顺序存储结构3.2.2.1 队列存储的基本操作3.2.2.2 循环队列 基本操作和判空方式 \color{Red}{基本操作和判空方式} 基本操作和判空方式3.2.2.3 知识回顾 3.2队列&#xff08;Queue&#xff09; 3.2.1队…

解读RSAC 2021丨灵魂拷问:你的网络够“皮实”吗?

美国时间5月20日&#xff0c;RSA大会落下帷幕。大会虽已结束&#xff0c;讨论还在继续。对于大会的主题“Resilience”&#xff0c;每个厂商、每个人都有自己的解读。 山石网科新技术研究院全程关注RSA大会&#xff0c;对于“Resilience”&#xff0c;他们的解读简单易懂接地气…

Java面向对象 - 常用类——Object类

什么是Object类 Java中有一个比较特殊的类&#xff0c;就是 Object类&#xff0c;它是所有类的父类&#xff0c;如果一个类没有使用extends关键字明确标识继承另外一个类&#xff0c;那么这个类就默认继承 Object类。因此&#xff0c;Object 类是 Java 类层中的最高层类&#x…

【C语言进阶篇】指针都学完了吧!那回调函数的应用我不允许还有人不会!

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《C语言初阶篇》 《C语言进阶篇》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 &#x1f4cb; 前言&#x1f4ac; 函数指针数组&#x1f4ad; 函数指针数组的定义&#x1f4ad; 函数指针数组的…