STL容器-- list的模拟实现(附源码)

STL容器-- list的模拟实现(附源码)

List的实现主要考察我们对list这一容器的理解,和代码的编写能力,通过上节对list容器的使用,我们对list容器已经有了一些基本的了解·,接下来就让我们来实现一些list容器常见的功能函数来加深我们对c++工程的理解:

在这里插入图片描述

一、基本框架的搭建

首先我们需要将基本的框架搭建好, 从之前我们学数据结构时,对list的一些认识并结合c++ stl标准可以得出:

  • 定义节点的构造
  • 明确指针的指向
  • list的封装
    根据c++中的标准我们可以得知,list时一个双向迭代器, 所以对应的, 我们需要定义的双向循环链表:

1.1节点的构造

template <class T>
struct ListNode
{
    T _data;
    ListNode<T>* _next;
    ListNode<T>* _prev;
    // 节点的构造函数
    ListNode(const T& val)
    :_data(val)
    ,_next(nullptr)
    ,_prev(nullptr)
    {}
};

在定义一个节点的时候,我们先让这个节点的_next and _prev指针为空

1.2 list的封装

    
template <class T>
struct ListNode
{
    T _data;
    ListNode<T>* _next;
    ListNode<T>* _prev;
    // 节点的构造函数
    ListNode(const T& val = T())
    :_data(val)
    ,_next(nullptr)
    ,_prev(nullptr)
    {}
};

template<class T>
class list
{
public:
    typedef ListNode<T> node;
    void empty_init()
    {
        // zdl :: 在初始化一个空链表时, 先定义一个哨兵位
        _head = new node();
        _head->_next = _head;
        _head->_prev = _head;
    }
    list()
    {
    	empty_init();
    }
    // functon define:
    }

private:
        node* _head;
};

同时为了防止访问冲突,我们可以将我们自己实现的类,放在我们自己定义的命名域中:

namespace zdl
{
	template <class T>
	struct ListNode
	{
	    T _data;
	    ListNode<T>* _next;
	    ListNode<T>* _prev;
	    // 节点的构造函数
	    ListNode(const T& val)
	    :_data(val)
	    ,_next(nullptr)
	    ,_prev(nullptr)
	    {}
	};
	    
	template <class T>
	struct ListNode
	{
	    T _data;
	    ListNode<T>* _next;
	    ListNode<T>* _prev;
	    // 节点的构造函数
	    ListNode(const T& val = T())
	    :_data(val)
	    ,_next(nullptr)
	    ,_prev(nullptr)
	    {}
	};
	
	template<class T>
	class list
	{
	public:
	    typedef ListNode<T> node;
	    void empty_init()
	    {
	        // zdl :: 在初始化一个空链表时, 先定义一个哨兵位
	        _head = new node();
	        _head->_next = _head;
	        _head->_prev = _head;
	    }
	    list()
	    {
	    	empty_init();
	    }
	    // functon define:
	    }
	
	private:
	        node* _head;
	};
}

这样list的基本框架就搭建好了!!

二、节点的尾插和头插

2.1 节点的头插 (push_back)

在这里插入图片描述

通过前面数据结构的学习。我们已经清楚了链表的结构,在进行数据尾插时, 就只是在改变指针的指向罢了:

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

2.2 节点的头插(push_front)

节点的头插和尾插十分的相似,
这里我们就直接展示一下代码:

void push_front(const T& val)
    {
        node* fnode = new node(val);
        fnode->_next = _head->_next;
        fnode->_prev = _head;
        _head->_next->_prev = fnode;
        _head->_next = fnode;
    }

2.3 数据插入验证

为了方便起见, 我这里再再这个类里面定义一个打印链表的函数:

 void Print_list()
        {
            node* cur = _head->_next;
            while (cur != _head)
            {
                cout << cur->_data << " ";
                cur = cur->_next;
            }
            cout << endl;
        }

完成了list容器的头插和尾插操作接下来我们可以来验证一下,我们的函数实现是否有问题:

#include"list.h"

int main()
{
    zdl::list<int> l1;
    l1.push_back(1);
    l1.push_back(2);
    l1.push_front(3);
    l1.Print_list();
    return 0;
}

通过运行可知, 这里的函数没有问题:

在这里插入图片描述

三、list迭代器的实现

list迭代器的功能和vetor的功能有很多相似的地方,但是二者在底层实现时,使用的不同的方法:
基于,list迭代器的特殊性质,我们会采用类封装的方式来满足list_iterator的特殊性质:

3.1迭代器基本功能的实现和封装处理

template<class T>
    struct list_iterator
    {
        typedef ListNode<T> node;
        typedef list_iterator<T> self;
        node* _nd;
        list_iterator(node* nd)
        :_nd(nd)
        {}
        // zdl:: 实现迭代器的基本功能:
        // 解引用数值
        T& operator*()
        {
            return _nd->_data;
        }
        // zdl:: 前置++ / --
        self& operator++()
        {
            _nd = _nd->_next;
            return *this;
        }
        self& operator--()
        {
            _nd = _nd->_prev;
            return *this;
        }
        // zdl:: 后置 ++ / --
        self operator++(int)
        {
            self tmp = _nd;
            _nd = _nd->_next;
            return tmp;
        }
        self operator--(int)
        {
            self tmp = _nd;
            _nd = _nd->_prev;
            return tmp;
        }
        // zdl:: != 运算
        bool operator!=(const self& s)
        {
            return _nd != s._nd;
        }
    };

实现了list_iterator的功能后,我们就只需要将他封装道list类中就可以正常使用了:
使用typedef 封装迭器
在这里插入图片描述
基本的逻辑都是十分的简单的接下来我们来简单的验证一代码是否可行:
在这里插入图片描述

3.2 对结构体(类)的解引用

->运算符的重载

大家现在可以想象一下这样的场景,假设我现在在llist中存储的不是基础类型的元素,而是类等较为复杂的对象时,我们应当怎么正常的使用这个类对象的成员呢?
这时,我们就可以考虑对->运算符重载, 通过返回对象的地址,来访问成员变量和成员函数等
下面我们就来举个例子:
假设我现在我要在list中存储pos类:

struct pos
{
	int row;
	int col;
	//构造函数
	pos(const int& x = 0, const int& y = 0)
	:row(x)
	,col(y)
	{}
	//成员函数
	void pos_show()
	{
		printf("(%d, %d) ",row, col);
	}
};

我们就只需要重载->运算符:

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

运行后可以得到:
在这里插入图片描述
但是大家可能会觉得很奇怪,通过->重载,返回的只是_data的地址,为什么能直接访问到元素呢?不应该使用it->->解引用两次才行啊?
这个其实就只是c++便准下为我们提供的特殊语法, 通过这样的规定使我们能够直接访问到元素, 当然c++也是支持这样的写法的:
在这里插入图片描述
但是不支持这样的写法:
在这里插入图片描述

3.3const迭代器的实现与模板参数的巧用

前面我们已经实现了,可读可写的迭代器,现在我们就来实现一下只读迭代器:
const iterator,
其实从功能上看,这个迭代器与原来的迭代器十分的相似,只是不能对指向对象的值进行修改,因此我们就只需要对* 和 ->运算符重载的时候稍加修改就可以得到我们想要的结果,即再创建一个类:
在这里插入图片描述
其他的共同功能粗需要改动,只需要改动下面两个重载函数:

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

紧接着我们还需要在llist类中实现const对象专用的begin()、end()
在这里插入图片描述
在这里插入图片描述

接下来我们来验证一下效果:
在这里插入图片描述
现在consr iterator也实现好了,但是这里依然还存在问题,这样我们将相当于为迭代器实现了两个类,这两个类的攻击能还高度重合,这并没有,体现出模板函数的简洁性,因此我们还可以通过其他的方式来优化我们现在的代码。
通过对源码的分析,参照我们或许可以从中得到一些启发, 从源码中可知我们可以得知,通过对模板参数的巧用就可以的实现代码的简化:

在这里插入图片描述
接下来我们就只需要将代码稍加改动就可以实现我们的目的:
在这里插入图片描述
在这里插入图片描述
接下来,我们再次运行看看是否可以达到简化代码的效果:
在这里插入图片描述
可以发现现在的代码依然有效,代码简化成功!!

四、丰富构造函数、list增删查改的实现

现在我们已经完成了list的简单构造函数,现在我们就可以参照 c++ library完成其他的构造函数:

4.1 list(size_t n, const T& x = T())

我们要实现的这个函数和标准库中的一样,并且直接复用我峨嵋你已经实现的函数就好了:

list(size_t n, const T& x = T())
{
   for (int i = 0; i < n; i++)
   {
       push_back(x);
   }
}

直接来演示一下的效果:
在这里插入图片描述

4.2 拷贝构造

我们就只需要将被拷贝链表的元素一个一个的拷贝进链表就行了:

list(const list<T>& l1)
{
    empty_init();
    for (auto& e : l1) push_back(e);
}

我们来测试一下效果:
在这里插入图片描述

4.3 插入函数的实现(insert)

想要在这个双向链表中插入节点,我们就需要

  • 待插入的值

  • 待插入位置

所以,insert函数定义为: void insert(iterator pos, const T& val = T())

iterator insert(iterator pos, const T& val = T())
{
     node* cur = pos._nd;
     node* prev = pos._nd->_prev;
     node* insrt = new node(val);
     prev->_next = insrt;
     insrt->_prev = prev;
     insrt->_next = cur;
     cur->_prev = insrt;
     return iterator(cur->_prev);
 }

这个函数也十分的简单如果,有的uu还没有接触过链表,或者是已经忘了链表的增删查改,可以移步去看看我之前的博客:链表的介绍

4.4链表的删除(earse)

关于erase和函数的定义,我们就只需要拿到需要删除的位置就可以了, 所以定义为:void erase(iterator pos)

iterator erase(iterator pos)
{
     assert(pos != end()); //! 注意不能够将哨兵位删除!!
     node* cur = pos._nd;
     node* prev = cur->_prev;
     prev->_next = cur->_next;
     cur->_next->_prev = prev;
     delete cur;
     return iterator(prev->_next);
 }

现在我们就直接来测试一下这个函数是否可以满足我们的要求:
在这里插入图片描述
由此可知我们实现的都没有问题。

4.5链表元素的查找(find)

定义find函数时,我们就只需要给函数一个特定的需要查找到的值,然后使这个函数返回这个元素的位置(迭代器

iterator find(const T& val)
{
     auto it = begin();
     while (it != end() && *it != val)
     {
         it++;
     }
     if (*it == val) return it;
     return nullptr;
 }

在这里插入图片描述

4.6 头删和尾删操作

前面我们已经实现了earse 现在我们就只需要对这个和拿书尽心你给复用就好了:

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

接下来我们就直接来演示这个函数是否有用:

在这里插入图片描述

五、析构函数与clear函数

最后我们就来实现一下list的析构函数,我们还是继续函数的复用:

// zdl:: 析构类函数的实现:
        ~list()
        {
            // ! 不仅要将所有的数值删除还需要将哨兵位也清除!
            clear();
            delete _head;
            _head = nullptr;            
        }
        void clear()
        {
            // !! 不能删除哨兵位,就只是将所有的数值清空。
            auto it = begin();
            while (it != end()) it = erase(it);
        }

现在我们就将已有的链表清空试试:
在这里插入图片描述

六、代码展示

list.h

#pragma once 

#include<iostream>
#include<cassert>
using  namespace std;
namespace zdl
{
    
    template <class T>
    struct ListNode
    {
        T _data;
        ListNode<T>* _next;
        ListNode<T>* _prev;
        // 节点的构造函数
        ListNode(const T& val = T())
        :_data(val)
        ,_next(nullptr)
        ,_prev(nullptr)
        {}
    };
    template<class T, class Ref, class Ptr>
    struct list_iterator
    {
        typedef ListNode<T> node;
        typedef list_iterator<T, Ref, Ptr> self;
        node* _nd;
        list_iterator(node* nd)
        :_nd(nd)
        {}
        // zdl:: 实现迭代器的基本功能:
        // 解引用数值
        Ref operator*()
        {
            return _nd->_data;
        }
        Ptr operator->()
        {
            return &_nd->_data;
        }
        // zdl:: 前置++ / --
        self& operator++()
        {
            _nd = _nd->_next;
            return *this;
        }
        self& operator--()
        {
            _nd = _nd->_prev;
            return *this;
        }
        self& operator+(size_t n)
        {
            while (n--)
            {
                _nd = _nd->_next;
            }
            return *this;
        }
        self& operator-(size_t n)
        {
            while (n--)
            {
                _nd = _nd->_prev;
            }
            return *this;
        }
        // zdl:: 后置 ++ / --
        self operator++(int)
        {
            self tmp = _nd;
            _nd = _nd->_next;
            return tmp;
        }
        self operator--(int)
        {
            self tmp = _nd;
            _nd = _nd->_prev;
            return tmp;
        }
        // zdl:: != 运算
        bool operator!=(const self& s)
        {
            return _nd != s._nd;
        }
    
    };
    
    template<class T>
    class list
    {
    public:
        typedef ListNode<T> node;
        typedef list_iterator<T, T&, T*> iterator;
        typedef list_iterator<T, const T&, const T*> const_iterator;
        void empty_init()
        {
            // zdl :: 在初始化一个空链表时, 先定义一个哨兵位
            _head = new node();
            _head->_next = _head;
            _head->_prev = _head;
        }
        list()
        {
            empty_init();
        }
        list(const list<T>& l1)
        {
            empty_init();
            for (auto& e : l1) push_back(e);
        }
        list(size_t n, const T& x = T())
        {
            empty_init();
            for (size_t i = 0; i < n; i++)
            {
                push_back(x);
            }
        }
        void push_back(const T& val)
        {
            node* creat = new node(val);
            node* tail = _head->_prev;
            tail->_next = creat;
            creat->_prev = tail;
            creat->_next = _head;
            _head->_prev = creat; 
        }
        void push_front(const T& val)
        {
            node* fnode = new node(val);
            fnode->_next = _head->_next;
            fnode->_prev = _head;
            _head->_next->_prev = fnode;
            _head->_next = fnode;
        }
        void Print_list()
        {
            node* cur = _head->_next;
            while (cur != _head)
            {
                cout << cur->_data << " ";
                cur = cur->_next;
            }
            cout << endl;
        }
        // zdl:: 增删查改的实现
        iterator insert(iterator pos, const T& val = T())
        {
            node* cur = pos._nd;
            node* prev = pos._nd->_prev;
            node* insrt = new node(val);
            prev->_next = insrt;
            insrt->_prev = prev;
            insrt->_next = cur;
            cur->_prev = insrt;
            return iterator(cur->_prev);
        }
        iterator erase(iterator pos)
        {
            assert(pos != end()); //! 注意不能够将哨兵位删除!!
            node* cur = pos._nd;
            node* prev = cur->_prev;
            prev->_next = cur->_next;
            cur->_next->_prev = prev;
            delete cur;
            return iterator(prev->_next);
        }
        iterator find(const T& val)
        {
            auto it = begin();
            while (it != end() && *it != val)
            {
                it++;
            }
            if (*it == val) return it;
            return nullptr;
        }
        void pop_front()
        {
            erase(begin());
        }
        void pop_back()
        {
            erase(--end());
        }
        // zdl:: 析构类函数的实现:
        ~list()
        {
            // ! 不仅要将所有的数值删除还需要将哨兵位也清除!
            clear();
            delete _head;
            _head = nullptr;            
        }
        void clear()
        {
            // !! 不能删除哨兵位,就只是将所有的数值清空。
            auto it = begin();
            while (it != end()) it = erase(it);
        }
// zdl:: 使用类来模拟迭代器的行为
    iterator begin()
    {
        return iterator(_head->_next);
    }
    iterator end()
    {
        return iterator(_head);
    }
    const_iterator begin() const
    {
        return const_iterator(_head->_next);
    }
    const_iterator end() const
    {
        return const_iterator(_head);
    }
    private:
            node* _head;
    };
}

test.cpp


#include"list.h"
#include<list>
struct pos
{
	int row;
	int col;
	//构造函数
	pos(const int& x = 0, const int& y = 0)
	:row(x)
	,col(y)
	{}
	//成员函数
	void pos_show()
	{
		printf("(%d, %d) ",row, col);
	}
};
int main()
{
    // zdl::list<pos> l1;
    // pos p[] = {{1, 2}, {3, 4}, {5, 6}};
    // for (int i = 0; i < 3; i++) l1.push_back(p[i]);
    // auto it = l1.begin();
    // while (it != l1.end())
    // {
    //     it->pos_show();
    //     it++;
    //     cout << endl;
    // }
    // cout << endl;

    // zdl::list<int> l2(5, 4);
    // for (auto&i : l2) cout << i << " ";
    // cout << endl;

    // zdl::list<int> l4(3, 3);
    // l4.Print_list();
    // l4.insert(l4.begin() + 2, 2);
    // l4.Print_list();
    // l4.erase(l4.begin() + 2);
    // l4.Print_list();

    // zdl::list<int>l5;
    // for (int i = 1; i <= 10; i++) l5.push_back(i);
    // cout << "原数组:" << endl;
    // l5.Print_list();
    // // zdl:: 现在我们要借助 find + erase函数来删除元素:5
    // l5.erase(l5.find(5));
    // cout << "删除后:" << endl;
    // l5.Print_list();
    //  zdl:: 进行头删和尾删
    // cout << "进行尾删和头删后:" << endl;
    // l5.pop_back();
    // l5.pop_front();
    // l5.Print_list();
    // cout << "现在将所有的元素都删除!" << endl;
    // l5.clear();
    // l5.Print_list();
    zdl::list<int> l1(10, 10);
    cout <<"l1:" << endl;
    l1.Print_list();
    zdl::list<int> l2(l1);
    cout << "l2:" << endl;
    l2.Print_list();
    return 0;
}

好,常用的接口我们就实现了,list学习到此告一段落,再见!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PageHelper快速使用

依赖 <!--分页插件PageHelper--> <dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.4.7</version> </dependency>示例 /** * 封装分页结果…

大华Java开发面试题及参考答案 (上)

TCP 的三次握手和四次挥手过程中各个状态的细节是怎样的&#xff1f; TCP&#xff08;Transmission Control Protocol&#xff09;是一种面向连接的、可靠的传输层协议&#xff0c;其三次握手和四次挥手过程涉及多个状态&#xff0c;以下是详细的状态细节&#xff1a; 三次握手…

ACL基础理论

ACL ——访问控制列表 ACL属于策略的一种 ACL访问控制列表的作用&#xff1a; 访问控制&#xff1a;在路由器流量流入或流出的接口上&#xff0c;匹配流量&#xff0c;然后执行设定好的动作&#xff1a;permit&#xff08;允许&#xff09;、deny&#xff08;拒绝&#xff…

【陕西省乡镇界】面图层shp格式arcgis数据乡镇名称和编码2020年wgs84坐标无偏移内容测评

标题中的“陕西省乡镇界面图层shp格式arcgis数据乡镇名称和编码2020年wgs84坐标无偏移.zip”表明这是一个地理信息系统&#xff08;GIS&#xff09;的数据集&#xff0c;专为陕西省的乡镇区域设计。该数据集以Shapefile&#xff08;shp&#xff09;格式提供&#xff0c;是GIS领…

简述mysql 主从复制原理及其工作过程,配置一主两从并验证

第一种基于binlog的主从同步 首先对主库进行配置&#xff1a; [rootopenEuler-1 ~]# vim /etc/my.cnf 启动服务 [rootopenEuler-1 ~]# systemctl enable --now mysqld 主库的配置 从库的配置 第一个从库 [rootopenEuler-1 ~]# vim /etc/my.cnf [rootopenEuler-1 ~]# sys…

(一)相机标定——四大坐标系的介绍、对应转换、畸变原理以及OpenCV完整代码实战(C++版)

一、四大坐标系介绍 1&#xff0c;世界坐标系 从这个世界&#xff08;world&#xff09;的视角来看物体 世界坐标系是3D空间坐标&#xff0c;每个点的位置用 ( X w , Y w , Z w ) (X_w,Y_w,Z_w) (Xw​,Yw​,Zw​)表示 2&#xff0c;相机坐标系 相机本身具有一个坐标系&…

Hugging Face功能介绍,及在线体验文生图模型Flux

Hugging Face简介 对于非机器学习或深度学习领域的人士来说&#xff0c;Hugging Face这个名字可能并不耳熟。然而&#xff0c;随着近年来大模型的迅速崛起&#xff0c;相信大家或多或少都有所接触。如果你对这一领域感兴趣&#xff0c;并在GitHub上查阅过一些开源资料&#xf…

华为ENSP:STP和链路聚合的管理与配置

这里将不再过度阐述STP和链路聚合的理论知识&#xff0c;不清楚的同学可以去观看Cisco文章中的理论知识 理论知识https://blog.csdn.net/2301_76341691/article/details/145166547?fromshareblogdetail&sharetypeblogdetail&sharerId145166547&sharereferPC&…

Asp.Net Core 8.0 使用 Serilog 按日志级别写入日志文件的两种方式

1、所需的Nuget包 本文项目的版本是.NET 8.0&#xff0c;如果使用其它版本安装适配版本即可。 Serilog.AspNetCore(8.0.2) Serilog.Sinks.File(5.0.0) Serilog.Expressions(5.0.0) 2、两种配置方式 2.1 代码形式&#xff08;Program.cs&#xff09; 在Program.cs文件中&am…

基于ESP32+VUE+JAVA+Ngnix的一个小型固件编译系统

一、前提 开发ESP32固件时&#xff0c;使用本地环境输出固件时&#xff0c;存在多个开发多种开发平台的问题。会导致最终输出的固件不统一。更可能因为本地的开发环境差异导致固件无法追溯。 基于上述原因&#xff0c;开发了一个小型的固件编译系统。将该系统部署在一台ubutn…

自然语言处理——自注意力机制

一、文字表示方法 在自然语言处理中&#xff0c;如何用数据表示文字是基础问题。独热编码&#xff08;One-hot Encoding &#xff09;是一种简单的方法&#xff0c;例如对于 “我”“你”“他”“猫”“狗” 等字&#xff0c;会将其编码为如 “我 [1 0 0 0 0 ……]”“你 [0 …

[JavaScript] 运算符详解

文章目录 算术运算符&#xff08;Arithmetic Operators&#xff09;注意事项&#xff1a; 比较运算符&#xff08;Comparison Operators&#xff09;注意事项&#xff1a; 逻辑运算符&#xff08;Logical Operators&#xff09;短路运算&#xff1a;逻辑运算符的返回值&#xf…

《汽车维护与修理》是什么级别的期刊?是正规期刊吗?能评职称吗?

​问题解答&#xff1a; 问&#xff1a;《汽车维护与修理》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的正规学术期刊。 问&#xff1a;《汽车维护与修理》级别&#xff1f; 答&#xff1a;国家级。主管单位&#xff1a;中国汽车维修行业协会 …

VUE学习笔记(入门)17__小黑记事本综合案例

目录结构 记得引入css样式 css代码 /* 清除浏览器的默认内外间距 */ html, body {margin: 0;padding: 0; } body {background: #fff; } button {margin: 0;padding: 0;border: 0;background: none;font-size: 100%;vertical-align: baseline;font-family: inherit;font-weig…

React的应用级框架推荐——Next、Modern、Blitz等,快速搭建React项目

在 React 企业级应用开发中&#xff0c;Next.js、Modern.js 和 Blitz 是三个常见的框架&#xff0c;它们提供了不同的特性和功能&#xff0c;旨在简化开发流程并提高应用的性能和扩展性。以下是它们的详解与比较&#xff1a; Next、Modern、Blitz 1. Next.js Next.js 是由 Ve…

【React】插槽渲染机制

目录 通过 children 属性结合条件渲染通过 children 和 slot 属性实现具名插槽通过 props 实现具名插槽 在 React 中&#xff0c;并没有直接类似于 Vue 中的“插槽”机制&#xff08;slot&#xff09;。但是&#xff0c;React 可以通过 props和 children 来实现类似插槽的功能…

常见IQ测试题:把下面的六个图形分为两类,使每一类图形都有各自的共同特征或规律,分类正确的一项是( )。​​​​​​​

[单选题] 把下面的六个图形分为两类&#xff0c;使每一类图形都有各自的共同特征或规律&#xff0c;分类正确的一项是&#xff08; &#xff09;。 A A ①②③&#xff0c;④⑤⑥ B B ①⑤⑥&#xff0c;②③④ C C ①②④&#xff0c;③⑤⑥ &#xff08;正确答案&#xff0…

项目开发实践——基于SpringBoot+Vue3实现的在线考试系统(七)

文章目录 一、题库管理模块实现1、新增题目功能实现1.1 页面设计1.2 前端功能实现1.3 后端功能实现1.4 效果展示2、题目列表功能实现2.1 页面设计2.2 前端功能实现2.3 后端功能实现2.3.1 后端查询题目列表接口实现2.3.2 后端编辑试题接口实现2.4 效果展示二、代码下载一、题库管…

opentelemetry-collector docker安装

一、编写配置 nano /root/otelcol-config.yamlreceivers:otlp:protocols:grpc:endpoint: 0.0.0.0:4317http:endpoint: 0.0.0.0:4318 exporters:debug:verbosity: detailed service:pipelines:traces:receivers: [otlp]exporters: [debug]metrics:receivers: [otlp]exporters: …

xiao esp32 S3播放SD卡wav音频

本文旨在使用xiao esp32 S3 播放SD卡上的音频文件 1 硬件准备 SD卡 2 代码实现 2.1 依赖库 ESP32-audioI2S-master 2.2 代码 #include "Arduino.h" #include "Audio.h" #include "SD.h"// Digital I/O used #define I2S_DOUT 6 #defi…