码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 【c++智能指针】模拟实现my_shared_ptr


    文章目录

    • 拷贝构造和移动构造的区别:
    • 移动赋值和普通赋值的区别:
    • 总结:
    • unique_ptr和share_ptr的区别:
    • sp2(new Object(10));和std::make_shared(20);的区别
      • 区别:
    • 模拟my_share_ptr
      • 删除单个对象
      • 删除一组对象template
      • 引用计数类
      • 拷贝构造:
      • 移动构造
      • 普通赋值(重点)
      • 两个共享性指针指向同一个对象。
      • 移动赋值(重点)
      • reset()函数
      • 析构函数
    • 观察器
      • 重载解引用和指向符
      • 获取当前引用计数的个数
      • 交换资源
    • 指向一组对象的my_share_ptr
      • 提供索引访问:
    • 完整代码

    • 在模拟my_share_ptr之前,我们深入区分一下,拷贝构造和移动构造,普通赋值和移动赋值的区别。

      拷贝构造和移动构造的区别:

      拷贝构造采用的方式是:将一个对象中指针所指的所有属性复制给被拷贝的对象。为了防止出现内存泄漏,需要被拷贝对象,重新申请一片堆空来存放所指之物。
      移动构造是一个对象中的指针所指转移给被移动对象。因此需要将前者对他所拥有的数据的拥有权释放。

      移动赋值和普通赋值的区别:

      普通赋值:如 obja = base,是是将base中的指针所指赋值一份给obja。
      移动就是直接转移给obja,base对资源的拥有权释放。

      总结:

      其实c11新引入的移动赋值和移动构造都是"右值引用"的概念的产生而产生,实际针对的是对对资源和系统内部资源进行转移引入的引入的一个新的概念。(实现的是资源的转移)

      unique_ptr和share_ptr的区别:

      unique_ptr(唯一性)智能指针,一次只能指向一个对象。
      share_ptr(共享性)智能指针,多个指针可以指向同一个对象。 但是必须记录有多少个指针指向了同一个对象。

      int main(void)
      {
      	std::shared_ptr<Object> sp1(new Object(10));
      
      	return 0;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      请添加图片描述当两个指针同时指向同一个对象时,那么指向的引用计数就会加一。

      int main(void)
      {
      	std::shared_ptr<Object> sp1(new Object(10));
      
      	std::shared_ptr<Object> sp2(sp1);
      
      	return 0;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

      请添加图片描述
      shared_ptr提供了两个函数来检查其共享的引用计数值,分别是unique()和use_count()。
      use_count()函数,该函数返回当前指针的引用计数值。值得注意的是use_count()函数可能效率很低,应该只把它用于测试或调试。
      unique()函数用来测试该shared_ptr是否是原始指针唯一拥有者,也就是use_count()的返回值为1时返回true,否则返回false。

      std::shared_ptr<Object> sp1 = std::make_shared<Object>(10);
      		std::shared_ptr<Object> sp2(new Object(10));
      
      		cout << sp1.use_count() << endl;
      		cout << sp2.unique() << endl;
      
      • 1
      • 2
      • 3
      • 4
      • 5

      请添加图片描述

      sp2(new Object(10));和std::make_shared(20);的区别

       std::shared_ptr<Object> sp2(new Object(10));
                               //需要创建两个对象,一个是引用计数对象,一个是object对象
       std::shared_ptr<Object> sp1 = std::make_shared<Object>(20);
       //只创建了一次对象。
      
      • 1
      • 2
      • 3
      • 4

      区别:

      第一种,需要构建两次对象,所以就要析构两次
      第二种, 只创建一次对象,只析构一次。Object完成初始化,op2直接指向既可以。
      请添加图片描述

      模拟my_share_ptr

      删除单个对象

      template<class _Ty>
      class MyDeletor
      {
      public:
      	MyDeletor() = default;
      	void operator()(_Ty* ptr) const
      	{
      		if (ptr != nullptr)
      		{
      			delete ptr;
      		}
      	}
      };
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

      删除一组对象template

      class MyDeletor<_Ty[]>
      {
      public:
      	MyDeletor() = default;
      	void operator()(_Ty* ptr) const
      	{
      		if (ptr != nullptr)
      		{
      			delete[]ptr;
      		}
      	}
      };
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

      引用计数类

      template<typename _Ty>
      class RefCnt
      {
      private:
      	_Ty* mptr; // 指向对象的指针
      	int ref;   // 引用计数 
      public:
      	RefCnt(_Ty* p = nullptr) : mptr(p), ref(mptr != nullptr)
      	{
      	}
      	~RefCnt() {}
      };
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

      指向一个对象的my_share_ptr

      template<class _Ty, class _Dx = MyDeletor<_Ty> >
      class my_shared_ptr
      {
      public:
      	my_shared_ptr(_Ty* p = nullptr) :ptr(nullptr)
      	{
      		if (p != nullptr)
      		{
      			ptr = new RefCnt(p);
      		}
      	}
      private:
      	RefCnt<_Ty>* ptr;  //指向计数器的指针
      	_Dx mDeletor; //删除器
      };
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

      拷贝构造:

      由于是共享型智能指针,所以会让多个指针指向同一个对象,因此判断智能指针对象中ptr指针是否为空,如果不为空,对ptr->ref+1

      my_share_ptr(const my_share_ptr& _Y):ptr(_Y.ptr)   //让sp2中的指针指向sp1中指针所指之物。
      	{                                                  //将sp1的资源复制给sp2
      		if (ptr != NULL)                              
      		{
      			ptr->ref += 1;
      		}
      	}
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      移动构造

      1. 移动构造相当于直接操作sp1的值(sp1被做右值),直接将sp1的值给ptr
      2. 将sp1的中指针的指向置为空。(就是将sp1的资源转移给sp2)
      my_share_ptr(my_share_ptr&& _Y):ptr(_Y.ptr)  //移动构造相当于直接操作sp1的值(sp1被做右值),直接将sp1的值给ptr
      {
      	_Y.ptr = NULL;                        //将sp1的中指针的指向置为空。(就是将sp1的资源转移给sp2)
      }
      
      • 1
      • 2
      • 3
      • 4

      普通赋值(重点)

      完成赋值需要满足的条件:

      1. if (this == &_Y || this->ptr == _Y.ptr) return *this;
      my_share_ptr<Object>op1(new Object(5));
      my_share_ptr<Object>op2(new Object(5));
      op1 = op1;
      op1 = op2;
      
      • 1
      • 2
      • 3
      • 4

      2.if (ptr != NULL && --ptr->ref == 0)

      my_share_ptr<Object>op1(new Object(5));
      my_share_ptr<Object>op2(new Object(10));
      op2 = op1;
      
      • 1
      • 2
      • 3

      请添加图片描述

      1. op1 = op2 完成,需要对ptr->ref+1,因为有两个指针同时指向op2中的指针所指的对象。
      my_share_ptr& operator=(const my_share_ptr& _Y)   //将sp1中的值复制给sp2
      	{
      		if (this == &_Y || this->ptr == _Y.ptr) return *this;
      		if (ptr != NULL && --ptr->ref == 0)
      		{
      			mDeletor(ptr);
      		}
      		ptr = _Y.ptr;
      		if (ptr != nullptr)
      		{
      			ptr->ref += 1;
      		}
      		return *this;
      	}
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      int main()
      { 
        my_share_ptr<Object>op1;
        my_share_ptr<Obbject>op2(new Object(10));
        op2 = op1;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      请添加图片描述

      int main()
      {
         my_unique_ptr<Object>sp1(new Object(5));
         my_unique_ptr<Object>sp2;
         my_unique_ptr<Object>sp3;
         my_unique_ptr<Object>sp1(new Object(10));
         sp2 = sp3; //NULL = NULL
         sp2 = sp1; //NULL = 非 NULL
         sp1 = sp3; //非 NULL =  NULL
         sp2 = sp4;  //非 NULL = 非 NULL
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

      两个共享性指针指向同一个对象。

      请添加图片描述

      移动赋值(重点)

      my_share_ptr& operator =(my_share_ptr&& _Y)  //将sp1的资源转移给sp2
      	{
      		 //判断是否是同一个对象?
      		if (this == &_Y) return *this;
      		//判断各自的指针是否指向同一个对象,如果相等,在判断两个指向是否为空
      		if (ptr == _Y.ptr && ptr != NULL && _Y.ptr != NULL)
      		{
      			ptr->ref -= 1;
      			_Y.ptr = NULL;
      			return *this;
      		}
      		//若没有指向同一个对象,判断ptr是否为空,如果不为空,并且ptr指向了一个对象
      		if (ptr != NULL && --ptr->ref == 0)
      		{
      			MDeletor(ptr); //断开ptr对该对象的拥有权
      		}
      		ptr = _Y.ptr; 
      		_Y.ptr = NULL;
      		return *this;
      	}
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      int main()
      {
          my_unique_ptr<Object>sp1(new Object(5));
          my_unique_ptr<Object>sp3(sp1);
          my_unique_ptr<Object>sp2(sp1);
          sp1 = std::move(sp2); //采用移动构造,会将sp2移除
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

      请添加图片描述

      reset()函数

      1.判断当前指针是否为空,并且指向一个对象。如果符合,调用删除器,解绑。
      2.当前指针重新向堆区引用计数的结构,该结构的指着指向参数类的对象。

      void reset(_Ty* p = nullptr)
      {
      	if (this->ptr != nullptr && --this->ptr->ref == 0)
      	{
      		mDeletor(ptr);
      	}
      	ptr = new RefCnt<_Ty>(p);//先调用RefCnt中的构造函数创建不具名对象,并给对mptr进行初始化,返回RefCnt的对象地址给ptr
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

      析构函数

      1.判断当前ptr是否为空,以及是否指向一个对象。满足条件,调用删除器,删除ptr指向的引用计数对象的mptr指针指向的对象。在删除ptr。
      2.将ptr置为空。

      ~my_shared_ptr()
      {
      	if (this->ptr != nullptr && --this->ptr->ref == 0)
      	{
      		mDeletor(ptr->mptr);
      		delete ptr;
      	}
      	ptr = nullptr;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

      观察器

      重载解引用和指向符

      _Ty* get() const { return ptr->mptr; }
      	_Ty& operator*() const
      	{
      		return *get();
      	}
      	_Ty* operator->() const
      	{
      		return get();
      	}
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

      获取当前引用计数的个数

      size_t use_count() const
      {
      	if (this->ptr == nullptr) return 0;
      	return this->ptr->ref;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5

      交换资源

      void swap(my_shared_ptr& r)
      {
      	std::swap(this->ptr, r.ptr);
      }
      
      • 1
      • 2
      • 3
      • 4

      指向一组对象的my_share_ptr

      特点:
      1.删除一组对象是,对于删除操作,我们都要先删除,当前指针所指的引用计数对象中的指针mptr所指。
      2.在删除ptr指针。多个指针指向一组对象。
      改下如下:

      if (ptr != nullptr && --ptr->ref == 0)
      {
      	mDeletor(ptr->mptr);
      	delete ptr;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5

      提供索引访问:

      _Ty& operator[](const int idx) const
      {
      	return ptr->mptr[idx];
      }
      
      • 1
      • 2
      • 3
      • 4

      完整代码

      template<class _Ty>
      class MyDeletor
      {
      public:
      	//MyDeletor() = default;
      	MyDeletor() {}
      	void operator()(_Ty* ptr) const
      	{
      		if (ptr != nullptr)
      		{
      			delete ptr;
      		}
      	}
      };
      template<class _Ty>
      class MyDeletor<_Ty[]>
      {
      public:
      	MyDeletor() = default;
      	void operator()(_Ty* ptr) const
      	{
      		if (ptr != nullptr)
      		{
      			delete[]ptr;
      		}
      	}
      };
      template<class _Ty>
      class RefCnt
      {
      public:
      	_Ty* mptr;
      	int ref;
      	//std::atomic_int ref;
      
      public:
      	RefCnt(_Ty* p = nullptr) :mptr(p), ref(mptr != nullptr) {}
      	~RefCnt() {}
      };
      template<class _Ty, class _Dx = MyDeletor<_Ty> >
      class my_shared_ptr // thread;
      {
      public:
      	my_shared_ptr(_Ty* p = nullptr) :ptr(nullptr)
      	{
      		if (p != nullptr)
      		{
      			ptr = new RefCnt(p);
      		}
      	}
      	my_shared_ptr(const my_shared_ptr& _Y) :ptr(_Y.ptr)
      	{
      		if (ptr != nullptr)
      		{
      			ptr->ref += 1;
      		}
      	}// my_shared_ptr op2(op1);
      	my_shared_ptr(my_shared_ptr&& _Y) :ptr(_Y.ptr)
      	{
      		_Y.ptr = nullptr;
      	}// my_shared_ptr op2(std::move(op1));
      	operator bool() const { return ptr != nullptr; }
      
      	my_shared_ptr& operator=(const my_shared_ptr& _Y) // 
      	{
      		if (this == &_Y || this->ptr == _Y.ptr) return *this;
      		if (ptr != NULL && --ptr->ref == 0)
      		{
      			mDeletor(ptr);
      		}
      		ptr = _Y.ptr;
      		if (ptr != nullptr)
      		{
      			ptr->ref += 1;
      		}
      		return *this;
      	}
      	my_shared_ptr& operator=(my_shared_ptr&& _Y) // move operator =
      	{
      		if (this == &_Y) return *this;
      		if (this->ptr == _Y.ptr && this->ptr != nullptr && _Y.ptr != nullptr)
      		{
      			this->ptr->ref -= 1;
      			_Y.ptr = nullptr;
      			return *this;
      		}
      		if (this->ptr != nullptr && --ptr->ref == 0)
      		{
      			mDeletor(ptr);
      		}
      		ptr = _Y.ptr;
      		_Y.ptr = nullptr;
      		return *this;
      	}
      	void reset(_Ty* p = nullptr)
      	{
      		if (this->ptr != nullptr && --this->ptr->ref == 0)
      		{
      			mDeletor(ptr);
      		}
      		ptr = new RefCnt<_Ty>(p);
      	}
      	~my_shared_ptr()
      	{
      		if (this->ptr != nullptr && --this->ptr->ref == 0)
      		{
      			mDeletor(ptr->mptr);
      			delete ptr;
      		}
      		ptr = nullptr;
      	}
      	_Ty* get() const { return ptr->mptr; }
      	_Ty& operator*() const
      	{
      		return *get();
      	}
      	_Ty* operator->() const
      	{
      		return get();
      	}
      
      	size_t use_count() const
      	{
      		if (this->ptr == nullptr) return 0;
      		return this->ptr->ref;
      	}
      	void swap(my_shared_ptr& r)
      	{
      		std::swap(this->ptr, r.ptr);
      	}
      
      private:
      	RefCnt<_Ty>* ptr;
      	_Dx mDeletor;
      };
      template<class _Ty, class _Dx >
      class my_shared_ptr<_Ty[], _Dx>
      {
      public:
      	my_shared_ptr(_Ty* p = nullptr) :ptr(nullptr)
      	{
      		if (p != nullptr)
      		{
      			ptr = new RefCnt(p);
      		}
      	}
      	my_shared_ptr(const my_shared_ptr& _Y) :ptr(_Y.ptr)
      	{
      		if (ptr != nullptr)
      		{
      			ptr->ref += 1;
      		}
      	}// my_shared_ptr op2(op1);
      	my_shared_ptr(my_shared_ptr&& _Y) :ptr(_Y.ptr)
      	{
      		_Y.ptr = nullptr;
      	}// my_shared_ptr op2(std::move(op1));
      	operator bool() const { return ptr != nullptr; }
      
      	my_shared_ptr& operator=(const my_shared_ptr& _Y) // 
      	{
      		if (this == &_Y || this->ptr == _Y.ptr) return *this;
      		if (ptr != NULL && --ptr->ref == 0)
      		{
      			mDeletor(ptr->mptr);
      			delete ptr;
      		}
      		ptr = _Y.ptr;
      		if (ptr != nullptr)
      		{
      			ptr->ref += 1;
      		}
      		return *this;
      	}
      	my_shared_ptr& operator=(my_shared_ptr&& _Y) // move operator =
      	{
      		if (this == &_Y) return *this;
      		if (this->ptr == _Y.ptr && this->ptr != nullptr && _Y.ptr != nullptr)
      		{
      			this->ptr->ref -= 1;
      			_Y.ptr = nullptr;
      			return *this;
      		}
      		if (this->ptr != nullptr && --ptr->ref == 0)
      		{
      			mDeletor(ptr->mptr);
      			delete ptr;
      		}
      		ptr = _Y.ptr;
      		_Y.ptr = nullptr;
      		return *this;
      	}
      	void reset(_Ty* p = nullptr)
      	{
      		if (this->ptr != nullptr && --this->ptr->ref == 0)
      		{
      			mDeletor(ptr->mptr);
      			delete ptr;
      		}
      		ptr = new RefCnt<_Ty>(p);
      	}
      	~my_shared_ptr()
      	{
      		if (this->ptr != nullptr && --this->ptr->ref == 0)
      		{
      			mDeletor(ptr->mptr);
      			delete ptr;
      		}
      		ptr = nullptr;
      	}
      	_Ty* get() const { return ptr->mptr; }
      	_Ty& operator*() const
      	{
      		return *get();
      	}
      	_Ty* operator->() const
      	{
      		return get();
      	}
      
      	size_t use_count() const
      	{
      		if (this->ptr == nullptr) return 0;
      		return this->ptr->ref;
      	}
      	void swap(my_shared_ptr& r)
      	{
      		std::swap(this->ptr, r.ptr);
      	}
      
      	_Ty& operator[](const int idx) const
      	{
      		return ptr->mptr[idx];
      	}
      
      private:
      	RefCnt<_Ty>* ptr;
      	_Dx mDeletor;
      };
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87
      • 88
      • 89
      • 90
      • 91
      • 92
      • 93
      • 94
      • 95
      • 96
      • 97
      • 98
      • 99
      • 100
      • 101
      • 102
      • 103
      • 104
      • 105
      • 106
      • 107
      • 108
      • 109
      • 110
      • 111
      • 112
      • 113
      • 114
      • 115
      • 116
      • 117
      • 118
      • 119
      • 120
      • 121
      • 122
      • 123
      • 124
      • 125
      • 126
      • 127
      • 128
      • 129
      • 130
      • 131
      • 132
      • 133
      • 134
      • 135
      • 136
      • 137
      • 138
      • 139
      • 140
      • 141
      • 142
      • 143
      • 144
      • 145
      • 146
      • 147
      • 148
      • 149
      • 150
      • 151
      • 152
      • 153
      • 154
      • 155
      • 156
      • 157
      • 158
      • 159
      • 160
      • 161
      • 162
      • 163
      • 164
      • 165
      • 166
      • 167
      • 168
      • 169
      • 170
      • 171
      • 172
      • 173
      • 174
      • 175
      • 176
      • 177
      • 178
      • 179
      • 180
      • 181
      • 182
      • 183
      • 184
      • 185
      • 186
      • 187
      • 188
      • 189
      • 190
      • 191
      • 192
      • 193
      • 194
      • 195
      • 196
      • 197
      • 198
      • 199
      • 200
      • 201
      • 202
      • 203
      • 204
      • 205
      • 206
      • 207
      • 208
      • 209
      • 210
      • 211
      • 212
      • 213
      • 214
      • 215
      • 216
      • 217
      • 218
      • 219
      • 220
      • 221
      • 222
      • 223
      • 224
      • 225
      • 226
      • 227
      • 228
      • 229
      • 230
      • 231
      • 232
      • 233
      • 234
      • 235
      • 236
      • 237
      • 238
      • 239
    • 相关阅读:
      有趣的数学 积分符号∫ (integration)简述
      第三次作业
      Git常用指令(基础)
      专业图标制作软件 Image2icon 最新中文 for mac
      An工具介绍之摄像头
      5款让人惊艳的黑科技软件,只要用过一次就会爱上
      动捕设备如何推动线下活动以虚拟主持人创新升级互动形式
      JVM关键指标监控(调优)
      LeetCode 11. 盛最多水的容器
      2023.05.28 学习周报
    • 原文地址:https://blog.csdn.net/weixin_52958292/article/details/127652902
      • 最新文章
      • 攻防演习之三天拿下官网站群
        数据安全治理学习——前期安全规划和安全管理体系建设
        企业安全 | 企业内一次钓鱼演练准备过程
        内网渗透测试 | Kerberos协议及其部分攻击手法
        0day的产生 | 不懂代码的"代码审计"
        安装scrcpy-client模块av模块异常,环境问题解决方案
        leetcode hot100【LeetCode 279. 完全平方数】java实现
        OpenWrt下安装Mosquitto
        AnatoMask论文汇总
        【AI日记】24.11.01 LangChain、openai api和github copilot
      • 热门文章
      • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
        奉劝各位学弟学妹们,该打造你的技术影响力了!
        五年了,我在 CSDN 的两个一百万。
        Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
        面试官都震惊,你这网络基础可以啊!
        你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
        心情不好的时候,用 Python 画棵樱花树送给自己吧
        通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
        13 万字 C 语言从入门到精通保姆级教程2021 年版
        10行代码集2000张美女图,Python爬虫120例,再上征途
      Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
      正则表达式工具 cron表达式工具 密码生成工具

      京公网安备 11010502049817号