vector的模拟实现
上一篇,我们已经相识了关于vector的常见使用接口了,如今,为了对vector的熟悉更加深入,我们如今参考STL库内里来模拟实现vector。ps:由于只是为了深入熟悉vector的知识,我们这里并不会完完全全按照STL的实现情势,而是根据我们作为新手初始学习的底子来模拟实现的。比及我们对C++的熟悉更加深入时,我们再去看看相干的册本层层剖析vector更加详细的实现方式。
起首,让我们来看看库内里是怎么界说它的成员变量的:
ps:侯捷老师的《STL源码分析》
https://i-blog.csdnimg.cn/direct/d56c9a760fab470ebaea1ea8cc1feafa.png
大概刚开始一看的时间,发现这些变量是什么意思啊?它为什么要用一个迭代器来作返回值范例呢?先别急,让我们一层一层看下去。
实在,对于刚开始看不懂时,我们可以再来借助它的其他成员函数的实现一起来资助我们分析为何?
https://i-blog.csdnimg.cn/direct/2a803dba2653400b8a4cec0a894ae555.png
https://i-blog.csdnimg.cn/direct/42b495ed2be94ca480cc4f13a8c97020.png https://i-blog.csdnimg.cn/direct/a1e5414b9965420aa1b96fa27f8606b9.png
如今,我们只需看方框内里的就行,其他看不懂的先不管它,比及我们的功底变动锋利再去明确。实在,对应我画的方框,和联合我们之前(string的模拟实现的履历),它应该是一个指向开始,末端的迭代器之类的变量。
它的根本框架应该是这么一个样子的:
https://i-blog.csdnimg.cn/direct/6d276027eac14cbc89eae81e14351691.png
如今我们展示出侯捷老师的书的一张图:
https://i-blog.csdnimg.cn/direct/591243ee1bd64ba881add4259c23fd19.png
但是,我们会发出如许的一个疑问:
1.为啥要用迭代器呢?
2.为啥它不像我们之前写string模拟时,用T* a;int size,int _capacity?
解答:
起首,我们复习一下
迭代器作用:
ps:之前我们有讲过迭代器的实现是类似指针的一种东西。
1.迭代器按次序访问向量中的每一个元素。如许不敷你的范例是什么eg:list,vector,int以及以后学到的map。都是可以使用的。
2.迭代器提供了一种同一的访问方式。差别容器(如次序容器和关联容器)的数据结构差别,但是迭代器提供了通用的接口来访问元素,使得算法可以或许以一种同一的方式操纵差别范例的容器。比如,尺度库中的 std::for_each 算法可以通过迭代器作用于各种容器,而不必要为每种容器单独写一个遍历函数。
3.迭代器还可以用来指示容器中元素的位置,方便在容器特定位置举行插入或删除操纵。不外必要注意的是,对于某些容器(如 std::list )迭代器在插入或删除元素后大概会失效,以是在这些操纵后必要审慎使用。
4.代码的复用,镌汰了代码的冗余。
5.封装性,不消去思量底层数据存储的细节
这里有解说我们迭代器失效的几种环境:
vector的先容与代码演示-CSDN博客
以是,我们的成员变量可以得出:
为什么要使用迭代器:
start 指向已使用内存空间的起始位置,用于标记数据存储的开头,通过它可以访问容器中存储的实际元素。finish 指向已使用内存空间的末端,它资助我们确定容器中有用元素的范围。比方说,当你使用 push_back 添加元素时, finish 会相应更新,以始终指向末了一个有用元素之后的位置。可以使用 finish - start 来盘算当前容器中有用元素的数量。 end_of_storage 指向整个分配内存空间的末端。这用于管理内存分配。当向 vector 中添加元素导致 finish 便是 end_of_storage (即已使用的空间已满)时, vector 会触发重新分配内存的操纵,以获取充足的空间来存储新元素。作用这三个迭代器协同工作,可以或许高效地管理内存和元素访问,使 vector 可以机动地动态增长和紧缩。由此,也可以知道了为什么我们不消之前的size,capacity的情势了吧?
由于,我们要实现的vector并不但范围于满足只存储一种固定的元素,我们在实际当中会创建差别范例的元素的vector。eg:int,string,double,char范例等等。
因此,
界说成员变量:
//为了防止与库里面的命名冲突
namespace bai
{
//模板
template <class T>
class vector
{
typedef T* iterator;
public:
private:
iterator _start=nullptr;
iterator _finish=nullptr;
iterator _endofstorage=nullptr;
};
}
构造函数:
https://i-blog.csdnimg.cn/direct/1c8fb448fd264b2197bedd6dde94aafd.png
为了更好初始学习,我们并不会像库那样千篇一律地实现(只是简朴):
一:无参的构造函数
这里我们直接使用初始化列表,把全部的迭代器都指向nullptr
vector()
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{} 固然,也可以写成如许子(但是两个不能同时存在,由于都是无参的构造函数,编译器不能辨认)
为什么呢?由于我们界说的变量的内置成员,这里我们默认不写的话编译器就会自动天生,内置函数不做处理处罚(固然有些编译器会处理处罚,但也只是个性化活动,不是全部的都是),自界说函数会自动去调用默认函数。我们在成员变量已经初始化了,以是也可以如许子(看界说成员变量那里)
https://i-blog.csdnimg.cn/direct/cabe04a4ad54478ebee775349533423a.png
vector()
{} 二:拷贝构造函数
ps:就是根据传入的参数vector来重新拷贝构造一个新的vector。
但是,要注意的是,我们要防止浅拷贝的标题(包罗vector本身和vector中的对象),否则,造成浅拷贝会公用同一块空间,析构时开释两次,而造成步调瓦解。
C++深拷贝与浅拷贝的区别-总结-CSDN博客
vector(const vector<T>& v)
{
_start = new T;
//memcpy(_start, v._start, sizeof(T)*v.size());
for (size_t i = 0; i < v.size(); i++)
{
_start = v._start;
}
_finish = _start + v.size();
_endofstorage = _start + v.capacity();
}
三: n个val的构造函数
ps: 表明为什么是要val=T() ?
缘故原由:
在函数中,T()是一个默认参数。T是一个模板,代表的是vector的范例。那么T()实际就是在调用T范例的默认构造函数。但我们调用vector这个默认的构造函数时,如果只传进来一个参数n,没有传进val,那么val就会被初始化为T的默认值。
但看这个大概有点抽象,那我们举个例子:
我们在主函数调用了这么一个:
vector<int> v(6,6).
那么T就是int(内置范例),而val=6,这里的T(),就相当于int(6,6)了。以是就到达了创建一个6个6的vector。
如果 T 是一个自界说的类范例,比如 class MyClass ,而且 MyClass 有一个默认构造函数,那么 T() 就会调用 MyClass 的默认构造函数来天生一个默认对象,用于添补 vector
https://i-blog.csdnimg.cn/direct/fa83f708ee6c48eb85740257f07a0f5c.png
同样这里是由于在成员变量里写了初始值,以是可以不写初始化列表,反之要写
别的,这里的本质就是将一个空的vector,,扩凑数据(初始化)到长度为n的过程。
这个过程就跟resize的接口功能一样了,以是我们可以直接复用resize函数(这个在下面又讲)
vector(int n, const T& val = T())
{
resize(n, val);
}
四:迭代器区间初始化:
上风:
1.机动性:
2.划一性:(上面又讲)
3.封装性:只要提供对迭代器的区间即可,无论数据来自于那里,都可以通过迭代器同一的逻辑举行元素的添加和初始化,提供代码的复用和可维护性。
解说:
由于函数吸取到的迭代器的范例是不确定的,以是我们在这里界说了一个函数模板,然后再将迭代器区间的数据一个个尾插到容器中。即可完成了初始化。
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
} 库中实现了多个构造函数:
不知道有没有人会有这么一个迷惑,为什么要写那么多个构造函数?我就有这个一个疑问:末了得出告终论:
重要有以下几个缘故原由:
方便差别的初始化场景
-指定巨细和初始值初始化像 vector(size_t n, const T& val = T()) 和 vector(int n, const T& val = T()) 这种构造函数,允许用户方便地创建一个具有指定命目元素,且元素具有类似初始值的 vector 。比方,创建一个包罗10个初始值为0的 int 范例 vector : vector<int> v(10, 0) 。- 范围初始化:模板构造函数 template<class InputIterator> vector(InputIterator first, InputIterator last) 可以使用一个范围(如其他容器中的部门元素范围)来初始化 vector 。假设已经有一个 list<int> myList ,可以用它的元素范围来初始化 vector : vector<int> v(myList.begin(), myList.end()) 。
机动性和兼容性- 范例兼容:有 size_t 和 int 范例参数的构造函数( vector(size_t n, const T& val = T()) 和 vector(int n, const T& val = T()) )提供了机动性。在一些场景下,用户大概会自然地使用 int 范例来表现元素数量,通过提供 int 参数的构造函数可以更好地兼容这种风俗。- 接口通用性:多种构造函数使得 vector 的接口更符合C++ 尺度库中其他容器的接口风格,如许用户在使用差别容器时,可以或许以相似的方式来初始化它们,方便影象和使用。 析构函数:
开释之前开辟的空间,再把成员变量置空。
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
}
operator=赋值函数:
对于赋值运算符重载,我们在模拟string的时间写过了,在vector中也是可以直接使用的。
我们来重新捋一下思绪:
创建一个临时变量,把s的数据拷贝到一个局部变量temp中,再将temp内容与this内容交换,如许就做到了赋值。而它的上风是不消别的去开释,由于它是局部变量,局部变量出了函数作用域自动烧毁。
https://i-blog.csdnimg.cn/direct/b692ce55ce7248dfbabdd756e1a39286.png
同时,我们也有别的一种优化方案:
就是直接通过形参取代了创建临时拷贝的temp,(节流了一下拷贝构造)
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
// v1 = v2
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
迭代器的begin()函数
//可读可写
iterator begin()
{
return _start;
}
//只读
const_iterator begin()const
{
return _start;
}
end()函数
//可读可写
iterator end()
{
return _finish;
}
//只读
const_iterator end()const
{
return _finish;
} size巨细
size_t size() const
{
return _finish - _start;
} capacity容量巨细
size_t capacity() const
{
return _endofstorage - _start;
} reserve()预留空间
步调解说:
1.判定n是否大于当前的容量,来决定是扩容还是缩容。只不外如今的电脑的内存空间根本完全支持你在这里多开出来的空间,可以忽略掉,以是我们一样寻常只必要思量扩容的环境。
再加上,vector扩容涉及到重新分配到一块更大的空间,再开释旧空间,那么,如果大量的调用扩容和缩容,会导致大量的元素复制和移动操纵,这会有比力高的时间本钱。尚有就是,我们来想想vector的reserve的功能:重要是用来预先分配充足的空间,以免后续插入元素的时间频仍地举行内存重新分配。很多场景下,一旦空间分配的充足大,很少必要再缩容的(由于轻易误用),(插入时不警惕调用到缩容的话,背面再插入就又要重新扩容操纵)。
过程:
1.动态开辟一段n的容量空间
2.原数据复制到新的空间
3.开释旧空间
4.更新数据(_start,_finish,_endofstorage)
两大易错点:!!!!!
1.使用memcpy,造成了浅拷贝。(同上面的拷贝构造不消的缘故原由)
在 vector 的 reserve 操纵中使用 memcpy 大概导致浅拷贝重要是由于:
memcpy 是按字节举行数据复制的低级操纵。对于像包罗指针成员的自界说类对象(大概是存的是像指针这种东西)(eg:string)的 vector , memcpy 只会复制指针的值(字节层面),而不是复制指针所指向的内容。
比方,假设有一个 vector 存放自界说的 MyClass 对象, MyClass 有一个成员是指针指向动态分配的资源(如 char* buffer )。
当用 memcpy 复制 MyClass 对象时,只是简朴地复制了 buffer 这个指针变量的值,而不是复制 buffer 所指向的实际字符数组。
这就导致两个差别的 MyClass 对象中的指针成员指向同一块内存地区,如果此中一个对象开释了这块内存,另一个对象再访问就会出现标题。
因此!不能使用memcpy,而要通过赋值运算符重载来调用它本身的深拷贝。
2.没有提前记载好size()的个数!
_start = tmp;
_finish = _start + size();
_endofstorage = _start + n; 像上面:
1.若我们调用的是无参构造函数。
https://i-blog.csdnimg.cn/direct/42ee5efbaa004bfbb277d460b4b8b696.png
那么,此时我们的三个成员变量都为nullptr。
然后,到了_finish那一步,我们再来看看我们的size()是怎样实现的?
https://i-blog.csdnimg.cn/direct/5570dc93298644d881ad8ef538f31f47.png
那么,我们带进去,相当于_finish=_start+(_finish-_start)=_start+(nullptr-_start)=nullptr.
你看,这是不是相当于没有更新,那么我们后续使用到_finish的时间,这是不是就会出现错误而造成步调瓦解的呢?
因此,我们该怎么办理这种环境呢?我们先从标题出现的源头出发,标题就出如今size()的过程。因此,我们不妨在此之前老师存下来size()的个数,防止直接调用size()过程中出现的错误。
void reserve(size_t n)
{
if (n > capacity())
{
//先计算保存size的个数
size_t sz = size();
T* tmp = new T;
if (_start)
{
//memcpy(tmp, _start, sizeof(T) * sz);--->error
for (size_t i = 0; i < sz; i++)
{
tmp = _start;
}
delete[] _start;
}
_start = tmp;
//_finish = _start + size();--->error
_finish = _start + sz;
_endofstorage = _start + n;
}
} resize()函数
这里跟我们写过的模拟string的resize的思绪差不多的。
https://i-blog.csdnimg.cn/direct/703f68f6f07247ca889821858ef01122.png
同样,vector中,我们的团体思绪是稳定的,唯一必要注意的是,我们由于使用的是迭代器,以是我们在遍历的时间也是用迭代器的方法,更改(初始化)为某个T范例的val
注意:迭代器!!!赋值时记得*
void resize(size_t n, const T& val = T())
{
if (n < size())
{
_finish = _start + n;
}
else
{
reserve(n);
while (_finish != _start + n)
{
*_finish = val;
++_finish;
}
}
}
Insert()插入函数
vector的先容与代码演示-CSDN博客
这里又写到insert和erase造成迭代器失效的缘故原由分析!必要的自行去看噢~
https://i-blog.csdnimg.cn/direct/c2abb25312c44c9ebf6c8e2c84361283.png
以是我们该怎么办理这个标题?
我们接纳:先提前记载下pos的位置,然后如果扩容了,在重新找pos的位置就很轻易了。
举个形象的例子:
你在一个旧的课堂里的位置是固定的5号位,那么,当有一天,由于上课人数超出了课堂容量的的最大值,以是我们要重新找一间更多座位的课堂,在此过程中,肯定是要基于原来的人数探求课堂的,(这就相当于拷贝到新的空间),那么,由于在旧的课堂里你的座位号是5,你记下来了,那么到了新课堂,你是不是就只要探求新课堂的5号位的座位就行了。这就很轻易找到你的座位了。
注意:由于内部迭代器失效办理后,并不能也就此办理了外部,因此,我们在insert后,只管不要再使用之前的形参迭代器,若非要使用,由于返回值是pos位置的迭代器,以是在调用时要注意吸取这个返回值来更新pos值!
注意:这是迭代器!!!!!别忘了加*
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start && pos <= _finish);
if (_finish == _endofstorage)
{
size_t len = pos - _start;
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
// 解决pos迭代器失效问题
pos = _start + len;
}
//挪动数据
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
//插入数据
*pos = x;
++_finish;
return pos;
} ps:为什么要返回pos的一个迭代器?
当你插入元素后,这个返回的迭代器指向新插入的元素。这在一些一连操纵场景下很有用。比方,如果你想在插入一个元素后,立刻基于这个新插入的元素位置再举行其他操纵,就可以直接使用这个返回的迭代器。
假设你有一个 vector<int> ,插入一个元素后,可以方便地用返回的位置来在新插入元素的背面再插入元素大概举行查找等操纵
erase()删除函数
vector的先容与代码演示-CSDN博客
同样,这篇文章里有写关于erase造成迭代器失效的缘故原由。
以是我们得在实际使用它的接口时,重新赋值,更新迭代器。
iterator erase(iterator pos)
{
assert(pos >= _start && pos < _finish);
iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *it;
++it;
}
--_finish;
return pos;
}
erase为啥要返回pos迭代器?
当你使用 erase 删除元素后,它返回的迭代器指向被删除元素之后的元素。这在遍历容器并删除某些元素的场景下很有用。
比方,在循环中删除满足肯定条件的元素时,就可以使用返回的迭代器来更新当前位置,确保遍历过程精确。
operator[]()函数
//可读可写
T& operator[](size_t pos)
{
assert(pos < size());
return _start;
}//只可读
const T& operator[](size_t pos) const
{
assert(pos < size());
return _start;
}
push_back()尾插函数
void push_back(const T& x)
{
if (_finish == _endofstorage)
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
*_finish = x;
++_finish;
}同时,我们尚有一种写法:复用insert()
void push_back(const T& x)
{
insert(end(), x);
}
pop_back()尾删函数
//复用erase函数
void pop_back()
{
erase(--end());
}
好了,关于vector的模拟实现就分享到这里了,盼望我们都共同进步!
全部代码展示:
ps:实在这里本意给出全部代码也没太大意义。但是,这里想要展示的是声明和界说不分离的熟悉
只管不要分离声明和界说,否则会出现很多不须要的贫苦(详细的以后讲到模板的进阶再详细聊聊)
vector.h
#pragma once
#include<assert.h>
namespace bai
{
template <class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
iterator begin()
{
return _start;
}
const_iterator begin()const
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator end()const
{
return _finish;
}
vector(size_t n, const T& val = T())
{
resize(n, val);
}
vector(int n, const T& val = T())
{
resize(n, val);
}
vector()
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{ }
template<class InputIterator>
vector(InputIterator frist, InputIterator last)
{
while (frist != last)
{
push_back(*frist);
frist++;
}
}
vector(const vector<T>& v)
{
_start = new T;
for (size_t i = 0; i < v.size(); i++)
{
_start = v._start;
}
_finish = _start + v.size();
_endofstorage = _start + v._capacity();
}
/*vector(const vector<T>& v)
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{
reserve(v._capacity());
for (auto e : v)
{
push_back(e);
}
}*/
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage,v._endofstorage)
}
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
}
void reserve(size_t n)
{
if (n > _capacity())
{
int sz = _size();
T* temp = new T;
// memcpy(temp, _start, sizeof(T) * sz);
if (_start)
{
for (size_t i = 0; i < sz; i++)
{
temp = _start;
}
delete[] _start;
}
_start = temp;
_finish = _start + sz;
_endofstorage = _start+n;
}
}
void resize(size_t n, const T& val = T())
{
if (n < _size())
{
_finish = _start + n;
}
else
{
reserve(n);
while (_finish != _start + n)
{
*_finish = val;
_finish++;
}
}
}
void push_back(const T& x)
{
/*if (_finish == _endofstorage)
{
size_t _newcapacity = _capacity() == 0 ? 4 : _capacity()*2;
reserve(_newcapacity);
}
*_finish = x;
_finish++;*/
insert(end(), x);
}
size_t _size()
{
return _finish-_start;
}
size_t _capacity()
{
return _endofstorage - _start;
}
T& operator[](size_t pos)
{
assert(pos<_size());
return _start;
}
const T& operator[](size_t pos)const
{
assert(pos <_size());
return _start;
}
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start && pos <= _finish);
if (_finish == _endofstorage)
{
size_t len = pos - _start;
size_tnewcapacity = _capacity() == 0 ? 4 : _capacity() * 2;
reserve(newcapacity);
pos = _start + len;
}
//挪动数据
iterator end = _finish - 1;
while (pos <= end)
{
*(end + 1) = *end;
end--;
}
//迭代器本质上是一个指针或者是类似指针的东西
*pos = x;
_finish++;
return pos;
}
iterator erase(iterator pos)
{
assert(pos >= _start && pos <= _finish);
iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *it;
it++;
}
_finish--;
return pos;
}
private:
iterator _start=nullptr;
iterator _finish=nullptr;
iterator _endofstorage=nullptr;
};
void vector_test1()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
void vector_test2()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
for (auto &e : v1)
{
e++;
cout << e << " ";
}
cout << endl;
for (size_t i = 0; i < v1._size(); i++)
{
v1++;
cout << v1 << " ";
}
}
void vector_test3()
{
vector<string> s;
s.push_back("aaaa");
s.push_back("bbbb");
s.push_back("cccc");
s.push_back("dddd");
s.push_back("eeee");
s.push_back("ffff");
vector<string>::iterator it = s.begin();
s.insert(it, "300");
/*while (it != s.end())
{
cout << (*it) << "";
it++;
}*/
//*it += 30;
for (auto e : s)
{
cout << e << " ";
}
}
void vector_test4()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
v1.push_back(5);
v1.push_back(5);
v1.push_back(5);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
v1.insert(v1.begin(), 100);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
/*vector<int>::iterator p = v1.begin() + 3;
v1.insert(p, 300);*/
vector<int>::iterator p = v1.begin() + 3;
//v1.insert(p+3, 300);
// insert以后迭代器可能会失效(扩容)
// 记住,insert以后就不要使用这个形参迭代器了,因为他可能失效了
v1.insert(p, 300);
// 高危行为
// *p += 10;
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
}
void vector_test5()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
vector<int>::iterator it = v.begin();
//v.erase(it);
v.resize(5, 8);
for (auto e : v)
{
cout << e << " ";
}
}
void vector_test6()
{
vector<string> s;
s.push_back("aaaa");
s.push_back("bbbb");
s.push_back("cccc");
s.push_back("dddd");
s.push_back("eeee");
s.push_back("ffff");
//vector<string>::iterator it = s.begin();
//s.insert(it, "300");
/*while (it != s.end())
{
cout << (*it) << "";
it++;
}*/
//*it += 30;
s.resize(9, "555555");
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
vector<int> v(6, 0);
for (auto e : v)
{
cout << e << " ";
}
}
void vector_test7()
{
int a[]{ 1,2,3,4,5,6 };
vector<int> v(a, a + 3);
}
} test.cpp
#include<iostream>
using namespace std;
#include "vector.h"
int main()
{
bai::vector_test6();
return 0;
}
末了,鸡汤环节:
对峙住!
https://i-blog.csdnimg.cn/direct/45a446ed43304cb9921844d1f3677c20.jpeg
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]