【whale-starry-stl】01天 list学习笔记

这篇具有很好参考价值的文章主要介绍了【whale-starry-stl】01天 list学习笔记。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、知识点

1. std::bidirectional_iterator_tag

std::bidirectional_iterator_tag 是 C++ 标准库中定义的一个迭代器类型标签,用于标识支持双向遍历的迭代器类型。

在 C++ 中,迭代器是一种泛型指针,用于遍历容器中的元素。迭代器类型标签用于标识迭代器的特性,从而在算法中选择合适的迭代器类型。

std::bidirectional_iterator_tag 是迭代器类型标签中的一种,用于标识支持双向遍历的迭代器类型。双向迭代器可以向前和向后遍历容器中的元素,支持 ++-- 运算符。

标准库中的许多算法都要求迭代器支持特定的操作,例如 std::reverse 要求迭代器支持双向遍历,因此可以使用 std::bidirectional_iterator_tag 标签来限定迭代器的类型,从而保证算法的正确性。

//迭代器类型标签,用于标识支持双向遍历的迭代器类型,支持++和--运算符
typedef std::bidirectional_iterator_tag    iterator_category;

2. ptrdiff_t

ptrdiff_t 是 C++ 标准库中定义的一个整型类型,用于表示指针之间的差值(即两个指针在内存中的地址差距)。在 C++ 中,指针的加减运算结果是一个 ptrdiff_t 类型的值。

ptrdiff_t 的实现是平台相关的,通常是一个带符号的整数类型。在 32 位平台上,ptrdiff_t 通常是一个 4 字节的整型,而在 64 位平台上,ptrdiff_t 通常是一个 8 字节的整型。

使用 ptrdiff_t 类型可以保证指针操作的安全性,因为指针之间的差值可能超出普通整型的表示范围。此外,使用 ptrdiff_t 类型还可以提高代码的可移植性,因为不同平台上指针的大小可能不同。

//带符号整型,表示指针之间的差值(即两个指针在内存中的地址差距)
typedef ptrdiff_t                          difference_type;

3. #if __cplusplus >= 201103L

#if __cplusplus >= 201103L这行代码是C++中的条件编译指令,意思是如果当前编译器支持C++11标准或更高版本,则编译下面的代码。其中,__cplusplus是一个C++预定义宏,表示当前编译器所支持的C++标准版本,201103L表示C++11标准的版本号。因此,这行代码的作用是在编译时检查当前编译器是否支持C++11标准或更高版本,如果支持,则编译下面的代码,否则忽略。

4. explicit

explicit 是 C++ 中的一个关键字,用于修饰类的构造函数,表示该构造函数是显式的,不能进行隐式转换。

在 C++ 中,如果一个类的构造函数只有一个参数,那么这个构造函数可以被用于隐式转换。例如,如果有一个类 A 和一个构造函数 A(int),那么可以使用 A a = 1; 的方式创建一个 A 类型的对象,这里的 1 会被自动转换为 A 类型。

使用 explicit 关键字可以禁止这种隐式转换,从而避免一些潜在的问题。例如,如果一个类 B 有一个构造函数 B(int),但是我们希望这个构造函数只能显式调用,那么可以将其声明为 explicit,这样就不能再使用 B b = 1; 的方式创建一个 B 类型的对象,而必须显式地调用 B b(1);

另外,explicit 关键字还可以用于修饰转换函数,表示该函数也是显式的,不能进行隐式转换。

5. stl_list.h结构

【whale-starry-stl】01天 list学习笔记

6. 迭代器相关

  • 运算符重载
    重载++(分为++item与item++)
    【whale-starry-stl】01天 list学习笔记
    重载*&
    【whale-starry-stl】01天 list学习笔记

7. 迭代器 traits

  • 模板偏特化(感觉类似java泛型重载)

  • 完整的iterator_traits

二、源码

_List_node_base

主要提供了prev和next指针,以及一些方法。

struct _List_node_base
{
      _List_node_base* _M_next;
      _List_node_base* _M_prev;
      ...
};

_List_node

继承自_List_node_base,主要提供了一个存放数据的 _M_data

/// An actual node in the %list.
  template<typename _Tp>
    struct _List_node : public __detail::_List_node_base
    {
      ///< User's data.
      _Tp _M_data;
//检查编译器是否支持c++11及以上版本
#if __cplusplus >= 201103L
      template<typename... _Args>
        _List_node(_Args&&... __args)     //万能引用
	: __detail::_List_node_base(), _M_data(std::forward<_Args>(__args)...) //完美转发
        { }
#endif
    };

_List_iterator

迭代器

1. 必须定义的五个typedef

      //下面这五个typedef是每个iterator必须有的
      //带符号整型,表示指针之间的差值(即两个指针在内存中的地址差距)
      typedef ptrdiff_t                          difference_type;
      //迭代器类型标签,用于标识支持双向遍历的迭代器类型,支持++和--运算符
      typedef std::bidirectional_iterator_tag    iterator_category;
      typedef _Tp                                value_type;
      typedef _Tp*                               pointer;
      typedef _Tp&                               reference;

2. 两个构造器

      _List_iterator() _GLIBCXX_NOEXCEPT
      : _M_node() { }

      explicit //修饰类的构造函数,表示该构造函数是显式的,不能进行隐式转换
      _List_iterator(__detail::_List_node_base* __x) _GLIBCXX_NOEXCEPT
      : _M_node(__x) { }      //_M_node:_List_node_base

3. 运算符重载

个人理解,此处之所以能够向下造型,应该是传入参数的时候已经做过一次向上造型

// Must downcast from _List_node_base to _List_node to get to _M_data.
      reference
      operator*() const _GLIBCXX_NOEXCEPT 
      { return static_cast<_Node*>(_M_node)->_M_data; }     //_M_node:_List_node_base, _Node:_List_node<_Tp>

      pointer
      operator->() const _GLIBCXX_NOEXCEPT      //2.9版本中是return &(operator*())
      { return std::__addressof(static_cast<_Node*>(_M_node)->_M_data); }      //__addressof用于取地址

前++运算符与后++运算符

此处模仿了整数的++i与i++,所以前++运算符需要返回一个引用。

个人理解:与左值和右值有关,如++i是左值,i++是右值。

	  _Self&
      operator++() _GLIBCXX_NOEXCEPT      //++item
      {
	_M_node = _M_node->_M_next;
	return *this;	//由于只有一个成员变量_M_node,因此能返回*this
      }

      _Self
      operator++(int) _GLIBCXX_NOEXCEPT   //item++ 感觉跟左值右值有关
      {
	_Self __tmp = *this;
	_M_node = _M_node->_M_next;
	return __tmp;
      }

_List_base

1. typedef

	  //rebind是一个模板类,它接受一个模板参数T和一个新的类型U,然后定义一个新的类型
      //这个新的类型是将T中的模板参数全部替换为U后得到的结果
      typedef typename _Alloc::template rebind<_List_node<_Tp> >::other
        _Node_alloc_type;

      typedef typename _Alloc::template rebind<_Tp>::other _Tp_alloc_type;

2. _List_impl

这段代码定义了一个结构体 _List_impl,它是 list 类的一个内部实现,用于存储 list 对象中的元素和节点信息。

该结构体中使用了两个模板别名:_Node_alloc_type_Tp_alloc_type,它们分别表示节点和元素的分配器类型。这里使用了 typename 关键字来指示 _Alloc::template rebind<_List_node<_Tp> >::other_Alloc::template rebind<_Tp>::other 是类型别名,而不是静态成员变量或函数。

接下来,_List_impl 结构体中定义了一个 _M_node 成员变量,它是一个 _List_node_base 类型的对象,用于存储 list 对象中的头节点和尾节点信息。

_List_impl 结构体还有三个构造函数,分别用于构造一个空的 _List_impl 对象、使用指定的节点分配器构造 _List_impl 对象,以及使用右值引用的节点分配器构造 _List_impl 对象。

需要注意的是,_List_impl 结构体中的 _Node_alloc_type_Tp_alloc_type 类型别名都是使用 _Alloc 类型的 template rebind 成员模板生成的,这是因为 list 类需要支持自定义分配器,因此需要使用 rebind 将原始分配器重新绑定到节点和元素类型上。

      struct _List_impl
      : public _Node_alloc_type
      {
	__detail::_List_node_base _M_node;

	_List_impl()
	: _Node_alloc_type(), _M_node()
	{ }

	_List_impl(const _Node_alloc_type& __a) _GLIBCXX_NOEXCEPT   //底层const
	: _Node_alloc_type(__a), _M_node()
	{ }

#if __cplusplus >= 201103L
	_List_impl(_Node_alloc_type&& __a) _GLIBCXX_NOEXCEPT  //右值引用
	: _Node_alloc_type(std::move(__a)), _M_node()   //转为右值,移动语义
	{ }
#endif
      };

3. 构造函数

这是 list 类的一个基类 _List_base,包含了 list 类的一些基本实现。这里的三个构造函数都是 _List_base 类的构造函数。

第一个构造函数 _List_base() 是默认构造函数,它使用默认的节点分配器构造了一个 _List_impl 对象 _M_impl,并调用了 _M_init() 函数来初始化 _List_base 对象。

第二个构造函数 _List_base(const _Node_alloc_type& __a) 则接受一个节点分配器 __a,用于构造一个 _List_impl 对象 _M_impl,并调用了 _M_init() 函数来初始化 _List_base 对象。

第三个构造函数 _List_base(_List_base&& __x) 是移动构造函数,它接受一个右值引用 __x,用于构造一个 _List_impl 对象 _M_impl。在构造过程中,它使用 __x._M_get_Node_allocator() 获取 __x 对象的节点分配器,并将其作为参数传递给 _M_impl 的构造函数,从而实现了节点分配器的移动。接着,它调用了 _M_init() 函数来初始化 _List_base 对象,并使用 __detail::_List_node_base::swap 函数交换了 _M_impl._M_node__x._M_impl._M_node 两个节点的信息,实现了 _List_base 对象的移动构造。需要注意的是,该构造函数只在 C++11 及以上版本中被定义。

      _List_base()
      : _M_impl()
      { _M_init(); }

      _List_base(const _Node_alloc_type& __a) _GLIBCXX_NOEXCEPT
      : _M_impl(__a)
      { _M_init(); }

#if __cplusplus >= 201103L
      _List_base(_List_base&& __x) noexcept     //右值引用
      : _M_impl(std::move(__x._M_get_Node_allocator()))     //移动构造函数
      {
	_M_init();
	__detail::_List_node_base::swap(_M_impl._M_node, __x._M_impl._M_node);
      }
#endif

4. _M_init()

	  void
      _M_init() _GLIBCXX_NOEXCEPT
      {
        //头节点的prev与next指向自身
        this->_M_impl._M_node._M_next = &this->_M_impl._M_node;
        this->_M_impl._M_node._M_prev = &this->_M_impl._M_node;
      }

list

1. typedef

      // concept requirements
      typedef typename _Alloc::value_type                _Alloc_value_type;
      __glibcxx_class_requires(_Tp, _SGIAssignableConcept)
      __glibcxx_class_requires2(_Tp, _Alloc_value_type, _SameTypeConcept)

      typedef _List_base<_Tp, _Alloc>                    _Base;
      typedef typename _Base::_Tp_alloc_type		 _Tp_alloc_type;
      typedef typename _Base::_Node_alloc_type		 _Node_alloc_type;

    public:
      typedef _Tp                                        value_type;
      typedef typename _Tp_alloc_type::pointer           pointer;
      typedef typename _Tp_alloc_type::const_pointer     const_pointer;
      typedef typename _Tp_alloc_type::reference         reference;
      typedef typename _Tp_alloc_type::const_reference   const_reference;
      typedef _List_iterator<_Tp>                        iterator;
      typedef _List_const_iterator<_Tp>                  const_iterator;
      typedef std::reverse_iterator<const_iterator>      const_reverse_iterator;
      typedef std::reverse_iterator<iterator>            reverse_iterator;
      typedef size_t                                     size_type;
      typedef ptrdiff_t                                  difference_type;
      typedef _Alloc                                     allocator_type;

    protected:
      // Note that pointers-to-_Node's can be ctor-converted to
      // iterator types.
      typedef _List_node<_Tp>				 _Node;

      using _Base::_M_impl;
      using _Base::_M_put_node;
      using _Base::_M_get_node;
      using _Base::_M_get_Tp_allocator;
      using _Base::_M_get_Node_allocator;

2. 一些函数

_M_create_node

      template<typename... _Args>
        _Node*
        _M_create_node(_Args&&... __args)	//万能引用
	{
	  _Node* __p = this->_M_get_node();       //获取新的节点指针
	  __try
	    {
	      _M_get_Node_allocator().construct(__p,
						std::forward<_Args>(__args)...); //使用完美转发构造节点中的数据
	    }
	  __catch(...)
	    {
	      _M_put_node(__p);
	      __throw_exception_again;
	    }
	  return __p;	//返回指针
	}

assign

assign 函数是 list 类的成员函数,用于将新的元素赋值给当前的 list 对象。该函数有多个重载版本,其中一个版本接受一个初始化列表作为参数,用于将初始化列表中的元素赋值给当前的 list 对象。

具体地,该函数接受一个初始化列表 __l,它将 __l 中的元素替换当前 list 对象中的元素。为此,它调用另一个 assign 函数,该函数接受两个迭代器参数,分别指向初始化列表的第一个元素和最后一个元素。assign 函数用这些元素来替换当前 list 对象中的元素。

需要注意的是,该函数只在 C++11 及以上版本中被定义。

#if __cplusplus >= 201103L
      /**
       *  @brief  Assigns an initializer_list to a %list.
       *  @param  __l  An initializer_list of value_type.
       *
       *  Replace the contents of the %list with copies of the elements
       *  in the initializer_list @a __l.  This is linear in __l.size().
       */
      void
      assign(initializer_list<value_type> __l)
      { this->assign(__l.begin(), __l.end()); }
#endif

3. 构造函数

第一个构造函数 list() 是默认构造函数,它创建一个空的 list 对象,使用默认的节点分配器。

第二个构造函数 list(const allocator_type& __a) 接受一个节点分配器 __a,用于创建一个空的 list 对象。

第三个构造函数 list(size_type __n) 接受一个整数参数 __n,用于创建一个包含 __n 个默认构造的元素的 list 对象。

第四个构造函数 list(size_type __n, const value_type& __value, const allocator_type& __a = allocator_type()) 接受一个整数参数 __n 和一个元素值 __value,用于创建一个包含 __n 个值为 __value 的元素的 list 对象。

第五个构造函数 list(const list& __x) 是拷贝构造函数,用于创建一个与 __x 相同的 list 对象。

第六个构造函数 list(list&& __x) noexcept 是移动构造函数,用于创建一个与 __x 相同的 list 对象,并将 __x 中的元素移动到新的 list 对象中。

第七个构造函数 list(initializer_list<value_type> __l, const allocator_type& __a = allocator_type()) 接受一个初始化列表 __l,用于创建一个包含 __l 中元素的 list 对象。

第八个构造函数 list(_InputIterator __first, _InputIterator __last, const allocator_type& __a = allocator_type()) 接受两个迭代器参数 __first__last,用于创建一个包含从 __first__last 的所有元素的 list 对象。需要注意的是,该构造函数只在 C++11 及以上版本中被定义。

list()
#if __cplusplus >= 201103L
      noexcept(is_nothrow_default_constructible<_Node_alloc_type>::value) //判断是否支持默认构造函数
#endif
      : _Base() { }

      /**
       *  @brief  Creates a %list with no elements.
       *  @param  __a  An allocator object.
       */
      explicit
      list(const allocator_type& __a) _GLIBCXX_NOEXCEPT
      : _Base(_Node_alloc_type(__a)) { }

#if __cplusplus >= 201103L
      /**
       *  @brief  Creates a %list with default constructed elements.
       *  @param  __n  The number of elements to initially create.
       *
       *  This constructor fills the %list with @a __n default
       *  constructed elements.
       */
      explicit
      list(size_type __n)
      : _Base()
      { _M_default_initialize(__n); }

      /**
       *  @brief  Creates a %list with copies of an exemplar element.
       *  @param  __n  The number of elements to initially create.
       *  @param  __value  An element to copy.
       *  @param  __a  An allocator object.
       *
       *  This constructor fills the %list with @a __n copies of @a __value.
       */
      list(size_type __n, const value_type& __value,
	   const allocator_type& __a = allocator_type())
      : _Base(_Node_alloc_type(__a))
      { _M_fill_initialize(__n, __value); }
#else
      /**
       *  @brief  Creates a %list with copies of an exemplar element.
       *  @param  __n  The number of elements to initially create.
       *  @param  __value  An element to copy.
       *  @param  __a  An allocator object.
       *
       *  This constructor fills the %list with @a __n copies of @a __value.
       */
      explicit
      list(size_type __n, const value_type& __value = value_type(),
	   const allocator_type& __a = allocator_type())
      : _Base(_Node_alloc_type(__a))
      { _M_fill_initialize(__n, __value); }
#endif

      /**
       *  @brief  %List copy constructor.
       *  @param  __x  A %list of identical element and allocator types.
       *
       *  The newly-created %list uses a copy of the allocation object used
       *  by @a __x.
       */
      list(const list& __x)
      : _Base(__x._M_get_Node_allocator())
      { _M_initialize_dispatch(__x.begin(), __x.end(), __false_type()); }

#if __cplusplus >= 201103L
      /**
       *  @brief  %List move constructor.
       *  @param  __x  A %list of identical element and allocator types.
       *
       *  The newly-created %list contains the exact contents of @a __x.
       *  The contents of @a __x are a valid, but unspecified %list.
       */
      list(list&& __x) noexcept
      : _Base(std::move(__x)) { }

      /**
       *  @brief  Builds a %list from an initializer_list
       *  @param  __l  An initializer_list of value_type.
       *  @param  __a  An allocator object.
       *
       *  Create a %list consisting of copies of the elements in the
       *  initializer_list @a __l.  This is linear in __l.size().
       */
      list(initializer_list<value_type> __l,
           const allocator_type& __a = allocator_type())
      : _Base(_Node_alloc_type(__a))
      { _M_initialize_dispatch(__l.begin(), __l.end(), __false_type()); }
#endif

      /**
       *  @brief  Builds a %list from a range.
       *  @param  __first  An input iterator.
       *  @param  __last  An input iterator.
       *  @param  __a  An allocator object.
       *
       *  Create a %list consisting of copies of the elements from
       *  [@a __first,@a __last).  This is linear in N (where N is
       *  distance(@a __first,@a __last)).
       */
#if __cplusplus >= 201103L
      template<typename _InputIterator,
	       typename = std::_RequireInputIter<_InputIterator>>
        list(_InputIterator __first, _InputIterator __last,
	     const allocator_type& __a = allocator_type())
	: _Base(_Node_alloc_type(__a))
        { _M_initialize_dispatch(__first, __last, __false_type()); }
#else

4. 运算符重载

移动赋值运算符

      list&
      operator=(list&& __x)   //移动赋值运算符
      {
        // NB: DR 1204.
        // NB: DR 675.
        this->clear();
        this->swap(__x);
        return *this;
      }

初始化列表

这是 list 类的赋值运算符重载,它允许使用初始化列表来为 list 对象赋值。

该运算符重载接受一个初始化列表 __l,它将 __l 中的元素赋值给当前的 list 对象。具体地,它调用 assign 函数,该函数接受两个迭代器参数,分别指向初始化列表的第一个元素和最后一个元素。assign 函数用这些元素来替换当前 list 对象中的元素。

该运算符重载返回一个 list 的引用,以支持链式赋值。文章来源地址https://www.toymoban.com/news/detail-492241.html

	 list&
      operator=(initializer_list<value_type> __l)
      {
	this->assign(__l.begin(), __l.end());
	return *this;
      }

到了这里,关于【whale-starry-stl】01天 list学习笔记的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 面试之快速学习STL-deuqe和list

    deque 容器用数组(数组名假设为 map)存储着各个连续空间的首地址。也就是说, map 数组中存储的都是指针 如果 map 数组满了怎么办?很简单, 再申请一块更大的连续空间供 map 数组使用,将原有数据(很多指针)拷贝到新的 map 数组中,然后释放旧的空间 。 deque容器迭代器

    2024年02月12日
    浏览(42)
  • CSP初赛知识点 学习笔记

    对于咱们信奥选手来说,会做的题要坚决不丢分,不会做的题要学会尽量多拿分,这样你的竞赛之路才能一路亨通! 文件(文件夹)操作 点击查看代码 G++/Gcc 基础指令 访问速度:寄存器 () 高速缓存 () 内存(ROM + RAM) () 外存,断电仅保留 ROM 和外存中的数据。 (texttt

    2024年02月09日
    浏览(49)
  • FPGA学习笔记-知识点3-Verilog语法1

    按其功能可分为以下几类: 1) 算术运算符(+,-,×,/,%) 2) 赋值运算符(=,=) 3) 关系运算符(,,=,=) 4) 逻辑运算符(,||,!) 5) 条件运算符( ? :) 6) 位运算符(,|,^,,^) 7) 移位运算符(,) 8) 拼接运算符({ }) 9) 其它 按其所带操作数的个数运算符可分为三种: 1) 单目运算符(unary operator):可以带一个

    2024年02月06日
    浏览(57)
  • Vue.js知识点学习的一点笔记

    目录 一、虚拟DOM  二、MVVM 三、数据代理 四、事件修饰 五、键盘事件 六、插值语法{{}}、方法methods、计算属性computed 七、 监视、深度监视、另一种写法、简写 八、computed计算属性和watch侦听 九、什么时候用箭头函数 十、Vue侦听和watch侦听原理 十一、从Vue侦听原理得出,往对

    2024年02月11日
    浏览(44)
  • 面试指南:C++之STL知识点

    相关系列文章 面试指南:C++之STL知识点 C++内存分配策略 深入理解STL空间分配器(一): new_allocator 深入理解STL空间分配器(二):mt_allocator 深入理解STL空间分配器(三):pool_allocator 深入理解STL空间分配器(四):bitmap_allocator 目录 1.讲讲STL的六大组件 2.vector 2.1.简单说说vector 2.2.vecto

    2024年02月21日
    浏览(47)
  • C#学习笔记--数据结构、泛型、委托事件等进阶知识点

    ArrayList 元素类型以Object类型存储,支持增删查改的数组容器。 因而存在装箱拆箱操作,谨慎使用。 ArrayList和数组区别? ArrayList使用不用说明固定长度,数组则需要 数组存储的是指定类型的,ArrayList是Object ArrayList存在装箱拆箱,数组不存在 ArrayList数组长度用Count获取 而数组

    2024年02月08日
    浏览(50)
  • 区块链学习笔记(6(1),深入理解Linux运维的核心知识点

    (3)检查创世块文件 (4)  检查通道文件(fabric2.2及以前会用到) 创建节点的方式有两种: (1)在创建任何节点之前,必须在本机上自定义其配置文件。对于peer节点,该文件称为 core.yaml ,而orderer节点的配置文件称为 orderer.yaml; (2)使用一个docker容器,将docker节点跑在一个

    2024年04月29日
    浏览(50)
  • UE5学习笔记(一)——界面功能梳理&第一天知识点记录

    学习UE5的第一步,是软件安装。 默认是安装好的,由于安装没有太多技术含量,所以就没有专门做记录。 这里有个注意点,虚幻引擎是整合在Epic games launcher中的,也就是说开发引擎内嵌在游戏平台上,打个比方,就是如果你要下unity你必须先下一个steam的感觉。 当然,在完

    2024年02月04日
    浏览(53)
  • c++学习笔记-STL案例-机房预约系统6-老师模块

    前言 衔接上一篇“c++学习笔记-STL案例-机房预约系统5-学生模块”,本文主要设计老师模块,从,老师登录和注销、查看所有预约、审核预约三个方面进行分析和实现。 目录 9 教师模块 9.1 教师登录和注销 9.1.1 构造函数 9.1.2 教师子菜单 ​编辑 9.1.3 菜单功能实现 9.1.4 接口对接

    2024年01月23日
    浏览(41)
  • c++学习笔记-STL案例-机房预约系统1-准备工作

    前言 准备工作包括:需求分析、项目创建、主菜单实现、退出功能实现 目录 1 机房预约系统需求 1.1 简单介绍 1.2 身份介绍 1.3 机房介绍 1.4 申请介绍 1.5 系统具体要求 1.6 预约系统-主界面思维导图  2 创建项目 2.1 创建项目 2.2 添加文件 ​编辑 3 创建主菜单 3.1 菜单实现 3.2 搭

    2024年01月24日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包