分享
 
 
 

关于STL中stack的实现的讨论

王朝other·作者佚名  2006-01-10
窄屏简体版  字體: |||超大  

关于STL中stack的实现的讨论

文章讨论了 为什么大多数STL的stack的实现中,对于内部的容器默认选择deque容器;并且给出了几个自己按不同想法实现的stack;并进行了简单的性能比较测试; 关于STL中stack的实现的讨论

HouSisong@263.net 2004.11.07整理

文章来源于abp论坛中的一篇讨论帖子: http://bbs.allaboutprogram.com/viewtopic.php?t=1026这是自己开始接触泛型和STL时形成的一篇讨论;文章中借用了Elminster,papercrane,Innocentius,PolyRandom等人的部分观点

文章讨论了为什么大多数STL的stack的实现中,对于内部的容器默认选择deque容器;并且给出了自己的几个不同想法实现的stack;并进行了简单的性能比较测试;

1:为什么大多数STL的stack的实现中,对于内部的容器默认选择deque容器?而不是vector?

STL中,stack对内部使用容器的函数调用主要有:push_back,back,pop_back等,也就是顺序容器都满足要求(包括vector,deque,list)。很多人应该和我一样,在STL之前看到的stack实现都是以动态数组来(甚至静态数组)实现为主,也就是接近于使用vector方案;那为什么STL偏偏选择deque呢!?

我的分析:

a.用vector实现中(push_back动作为分期摊还常数时间),如果发生容器的大小改变时,将可能产生一个大动作(申请空间,拷贝构造,释放原来的元素和空间,该动作成线性复杂度) 而且vector的很多实现版本中,容器在任何情况下都从不缩减已经申请的空间容量(swap技巧除外);

b.用deque实现时,容器的大小改变时(数据量较大),动作比vector就小多了(常数复杂度),并且当容器的大小变小时,还可以适当减小容量;但push_back 的逻辑相对vector复杂一点;

c.用list实现时,不用考虑空间容量变化;但每次的压入弹出开销(内存时间)较大,但很平稳;那么,经过分析,在不同的应用场合,为stack选择不同的内部容器是很有必要的;如果对stack有性能上的要求,就应该考虑这一点(甚至重新写一个最适应问题要求的stack); 比如:要求有最快的平均访问速度,而且大概的容量要求也清楚(比较衡定),那么,使用vector是个不错的选择 要求每次的访问时间平稳,而不在乎平均访问时间时,那么,可以考虑使用list;所以,库默认的deque是个不错的选择,它介于vector和list之间,并且很好的综合了两者的优势;另papercrane:“oncrete policy deque相对于stack来说就像傻瓜机,乱用也不会有什么太大的问题。如你所说的平均时间和最差时间的要求,我觉得就好像hash map和tree map的性能差别一样。 ”

(提示:文章后面还有两种想进一步融合这三种方式各自优势的stack的实现,特别是最后那个实现也许推翻了这里的表面上得到的看法);

2.自己也来写一个stack;

由于看到VC6中实现的太差,所以自己简单写了一个stack模版实现,性质比较接近于stack<T,vector<T> >, 代码如下:

template<class T>

class mystack

//测试用

//没有考虑异常时的rollback语义

//另: Elminster指出“使用 count 不是一个好主意。保存一个指向“下一个位置”的指针应该会效率更高,而且也更易读”;

{

public:

typedef T value_type;

typedef unsigned int size_type;

mystack():_lenght(0),_count(0) {}

~mystack()

{

if (_lenght!=0)

{

for (int i=0;i<_count;++i)

{

((T*)(&_vData[i*sizeof(T)]))->T::~T();

}

}

}

bool empty() const

{ return (0==_count); }

size_type size() const

{ return _count; }

value_type& top()

{ return *(T*)(&_vData[(_count-1)*sizeof(T)]); }

const value_type& top() const

{ return *(T*)(&_vData[(_count-1)*sizeof(T)]); }

void push(const value_type& x)

{

if (_count>=_lenght)

{

_resize();

}

new ((T*)(&_vData[_count*sizeof(T)])) T(x) ;

++_count;

}

void pop()

{

--_count;

((T*)(&_vData[_count*sizeof(T)]))->T::~T();

}

protected:

std::vector<unsigned char> _vData;

size_type _lenght;

size_type _count;

void _resize()

{

if (0==_lenght)

{

_lenght=32;

_vData.resize(_lenght*sizeof(T));

}

else

{

_lenght*=2;

std::vector<unsigned char> new_vData;

new_vData.resize(sizeof(T)*_lenght);

for (int i=0;i<_count;++i)

{

T& x=*(T*)(_vData.begin()+i*sizeof(T));

new ((T*)(new_vData.begin()+i*sizeof(T))) T(x);

(&x)->T::~T();

}

_vData.swap(new_vData);

}

}

};

测试环境:VC6,赛扬1G,256M内存

测试代码:(不好意思,代码风格被VC的环境影响太久,想改变这种风格ing)

另: Elminster指出测试代码里面,TestStack("stack_deque_T0 : ",stack_deque_T0()) 这个做法也不太妥当。TestStack 的第二个参数是 stackT&,把一个临时对象绑在非常量引用上可能会带来问题。

typedef int Test0_T;//简单 POD 类型

class Test1_T //较复杂的类

{

public:

char _c;

double _d;

int _i;

char* _p;

Test1_T():_c(),_d(),_i(),_p(new char[20])

{}

Test1_T(const Test1_T& x)

:_c(),_d(),_i(),_p(new char[20])

{ (*this).operator =(x); }

~Test1_T(){ delete[] _p; }

Test1_T& operator =(const Test1_T& x)

{

_c=x._c; _d=x._d; _i=x._i;

for (int i=0;i<20;++i)

_p[i]=x._p[i];

return *this;

}

};

__declspec( naked ) __int64 CPUCycleCounter()//获取当前CPU周期计数(CPU周期数)

{

__asm

{

RDTSC //0F 31 //eax,edx

ret

}

}

template<class stackT>

int testProc(stackT& s,int Count)

{

typename stackT::value_type vl=stackT::value_type();

typename stackT::value_type vx;

__int64 t0=::CPUCycleCounter();//返回CPU启动以来运行的周期数

for (int c=0;c<100;++c)

{

int i;

for (i=0;i<Count;++i)

s.push(vl);

for (i=0;i<Count;++i)

vx=s.top();

for (i=0;i<Count;++i)

s.pop();

}

__int64 t1=::CPUCycleCounter();

return int((t1-t0)/100);

}

template<class stackT>

CString TestStack(PCSTR lab,stackT& s)

{

int t0=testProc(s,10);

int t1=testProc(s,1000);

int t2=testProc(s,100000);

CString str;

str.Format("%10d,%10d,%10d",t0,t1,t2);

str+=char(13);str+=char(10);

return lab+str;

}

void CSTACKTESTDlg::OnBUTTONTest()

{

// TODO: Add your control notification handler code here

using namespace std;

typedef stack<Test0_T,deque<Test0_T> > stack_deque_T0;

typedef stack<Test1_T,deque<Test1_T> > stack_deque_T1;

typedef stack<Test0_T,vector<Test0_T> > stack_vector_T0;

typedef stack<Test1_T,vector<Test1_T> > stack_vector_T1;

typedef stack<Test0_T,list<Test0_T> > stack_list_T0;

typedef stack<Test1_T,list<Test1_T> > stack_list_T1;

typedef mystack<Test0_T> mystack_T0;

typedef mystack<Test1_T> mystack_T1;

CString str;

str+=TestStack("stack_deque_T0 : ",stack_deque_T0());

str+=TestStack("stack_vector_T0: ",stack_vector_T0());

str+=TestStack("stack_list_T0 : ",stack_list_T0());

str+=TestStack("mystack_T0 : ",mystack_T0());

str+=char(13);str+=char(10);

str+=TestStack("stack_deque_T1 : ",stack_deque_T1());

str+=TestStack("stack_vector_T1: ",stack_vector_T1());

str+=TestStack("stack_list_T1 : ",stack_list_T1());

str+=TestStack("mystack_T1 : ",mystack_T1());

this->m_str=str;//::MessageBox(0,str,"",0);

UpdateData(FALSE);

}

测试结果:(不同使用环境下的测试情况可能不同,该数据仅作参考)

(另:测试中使用的STL是VC6自带的)

(计时单位:万CPU周期)

N: 10 1000 100000

stack_deque_T0 : 2695, 65621, 12191532

stack_vector_T0 : 579, 47750, 9169942

stack_list_T0 : 6028, 957515, 167660924

mystack_T0 : 230, 13766, 2550403 (效果很好嘛)

stack_deque_T1 : 10699, 1168983, 270182336

stack_vector_T1: 8043, 1247187, 250648378

stack_list_T1 : 13043, 1988924, 424801657

mystack_T1 : 6796, 1240879, 252690706(对于复杂对象,与stack_vector_T1的差不多)

Elminster给出了他的测试结果:

(计时单位:tick count) :

stack_deque_T0 : 0 156 12969

stack_vector_T0 : 0 63 7562

mystack_T0 : 0 47 6766

stack_deque_T1 : 16 500 57765

stack_vector_T1 : 0 813 91843

mystack_T1 : 0 734 85469

环境是 amd athlon 1600+, win2k sp4, 256M ddr, vs.net 2003,最大速度优化。

“结论比较有趣。对于拷贝动作比较轻量级的 T0,你的方案比 deque 和 vector 都快,但与 vector 相差不大。此时 deque 的性能落的比较后面,原因应该是 deque 的 push_back 的逻辑相对复杂(我看了看)。对于拷贝动作比较重的 T1 ,你的方案和 vector 反而要比 deque 慢。这里的原因应该是 resize 的时候拷贝的开销太重。其实我认为对于 stack 的行为模式,类似 deque 的存储结构会比较好,因为空间完全不需要连续,像vector 那样需要拷贝的 resize 是毫无必要的。你自己实现一个简洁的 deque style 数据结构,相信可以把stack 的性能再提升一个台阶。”

/////////////////////////////////////////

对上面自己写的stack做了些改进:

template<class T>

class mystack

{

public:

enum{ type_sizes=sizeof(T) };

typedef T value_type;

typedef unsigned int size_type;

mystack():_begin(0),_end(0),_last(0) {}

~mystack()

{

if (size()>0)

{

for (T* i=_begin;i<_end;++i)

i->T::~T();

}

if (_begin!=0) delete[] (unsigned char*)_begin;

}

bool empty() const

{ return (0==size()); }

size_type size() const

{ return (_end-_begin); }

value_type& top()

{ return *(_end-1); }

const value_type& top() const

{ return *(_end-1); }

void push(const value_type& x)

{

if (_end<_last)

{

new (_end) T(x) ;

++_end;

}

else

{

_resize();

new (_end) T(x);

++_end;

}

}

void pop()

{

--_end;

_end->T::~T();

}

protected:

T* _begin;

T* _end;

T* _last;

void _resize()

{

if (0==_begin)

{

const unsigned int lenght=32;

_begin=(T*)( new unsigned char[lenght*type_sizes]);

_end =_begin;

_last =_begin+lenght;

}

else

{

unsigned int old_size=size();

unsigned int lenght=(old_size<<1);

T* _pNewData=(T*)( new unsigned char[lenght*type_sizes]);

int i=0;

try

{

for (;i<(int)old_size;++i)

new (_pNewData + i) T(*(_begin+i));

}

catch(...)//rollback语义

{

for (int r=0;r<i;++r)

(_pNewData + r)->T::~T();

delete[] (unsigned char*)_pNewData;

throw;

}

for (int j=0;j<(int)old_size;++j)

(_begin+j)->T::~T();

T* old_begin=_begin;

_begin=_pNewData;

_end =_begin+old_size;

_last =_begin+lenght;

delete[] (unsigned char*)old_begin;

}

}

};

测试环境:VC6,XP,赛扬1G,256M内存

测试结果:

stack_deque_T0 : 2704, 65916, 11726143

stack_vector_T0 : 581, 45713, 9192746

stack_list_T0 : 5941, 1010118, 181184020

mystack_T0 : 172, 12210, 2590757

stack_deque_T1 : 10587, 1962722, 255087740

stack_vector_T1 : 8145, 1237245, 243123956

stack_list_T1 : 13128, 2005212, 398256062

mystack_T1 : 7127, 1173165, 251515265

测试环境:VS.net,XP,赛扬1G,256M内存

stack_deque_T0 : 525, 33407, 6468531

stack_vector_T0 : 574, 34710, 5236028

stack_list_T0 : 4753, 1060449, 165353094

mystack_T0 : 160, 10200, 2290310

stack_deque_T1 : 7470, 963957, 266757848

stack_vector_T1 : 8412, 1275307, 278246615

stack_list_T1 : 12001, 1993198, 475480319

mystack_T1 : 7317, 1209379, 262822216

对比VC6, vs.net的 deque的性能好像提高了不少,但和vector一样还有优化的空间

3.利用x86的虚拟空间地址原理来实现stack

deque的实现中一般内部维护一个动态指针数组,这些指针指向数据块(每块保存多个元素),这些块在内存中是不连续的,然而deque利用软件的方式提供了一个对外的线性访问的假象;

看一下现在的x86CPU,也有一个机制和这很像,即:保护的虚拟地址内存模型; 它把不连续的物理内存映射为一维线性内存模型,由于是硬件支持的映射,所以这种机制不会损失任何性能(deque、stack、vector等的实现也可以利用这一点)

实现stack时,可以先开辟很大一块内存空间(足够),但不一次全部提交,开始只提交很少 的一部分物理页面,当需要增大容器容量时,只需要再提交部分物理页面给它,当需要减少容器容量时 可以收回部分页面;这个方案很像是deque的实现,但它可以获得vector式的线性内存访问能力和性能;

当然这会占用较大量的虚拟内存地址空间,这个方案也可能和具体平台相关,但不会浪费真正的物理内存空间(如果是64位CPU,那么虚拟内存地址空间的浪费就可以不用考虑了:))

新的容器ExStack,它同时具有vector的线性访问能力,和deque的内存管理方式,所以我期待它具有这两者的性质:

class TAlloc//移植时 在不同的平台下需要改变的部分

{

public:

static void* reserve(unsigned int size) //申请保留虚拟空间地址

{

return ::VirtualAlloc(0,size,MEM_RESERVE,PAGE_READWRITE);

}

static bool commit(void* pbase,unsigned int offset,unsigned int size) //提交物理空间

{

return 0!=::VirtualAlloc(((BYTE*)pbase)+offset,size,MEM_COMMIT,PAGE_READWRITE);

}

static bool free(void* pbase) //解除提交的物理空间,并释放申请的虚拟空间地址

{

return 0!=::VirtualFree(pbase,0,MEM_RELEASE);

}

};

template<class T>

class ExStack

{

public:

enum{ type_sizes=sizeof(T) };

typedef T value_type;

typedef unsigned int size_type;

ExStack():_begin(0),_end(0),_last(0) {}

~ExStack()

{

if (size()>0)

{

for (T* i=_begin;i<_end;++i)

i->T::~T();

}

if (_begin!=0) TAlloc::free(_begin);

}

bool empty() const

{ return (0==size()); }

size_type size() const

{ return (_end-_begin); }

value_type& top()

{ return *(_end-1); }

const value_type& top() const

{ return *(_end-1); }

void push(const value_type& x)

{

if (_end<_last)

{

//std::_Construct(_end,x) ;

new (_end) T(x) ;

++_end;

}

else

{

_resize();

//std::_Construct(_end,x);

new (_end) T(x) ;

++_end;

}

}

void pop()

{

//没有实现收回物理内存的语义

--_end;

_end->T::~T();

}

protected:

T* _begin;

T* _end;

T* _last;

void _resize()

{

if (0==_begin)

{

const unsigned int lenght=4*1024;//4KB 边界对齐

_begin=(T*)TAlloc::reserve(256*1024*1024);//预留地址空间 256 MB

TAlloc::commit((void*)_begin,0,lenght*type_sizes);

_end =_begin;

_last =_begin+lenght;

}

else

{

unsigned int old_size=size();

unsigned int lenght=(old_size<<1);

TAlloc::commit(_begin,old_size*type_sizes,(lenght-old_size)

*type_sizes);

_last =_begin+lenght;

}

}

};

//测试条件更加接近于一般使用环境, 测试也更合理

int testProc(stackT& s,int Count)//注意测试条件变了很多!!!

{

typename stackT::value_type vl=stackT::value_type();

typename stackT::value_type vx;

__int64 t0=::CPUCycleCounter();//返回CPU启动以来运行的周期数

for (int c=0;c<100;++c)

{

stackT temps;//

int i;

for (i=0;i<Count+1;++i)//+1

temps.push(vl);

for (i=0;i<Count;++i)

vx=temps.top();

for (i=0;i<Count-1;++i)//-1

temps.pop();

for (i=0;i<(Count>>1);++i)//!

temps.push(vl);

}

__int64 t1=::CPUCycleCounter();

return int((t1-t0)/100);

}

测试环境:XPvc.net赛扬1GHz256MB

测试结果:

N = 10 1000 100000

stack_deque_T0 : 2942, 205538, 54123660

stack_vector_T0: 4903, 66692, 21136702

stack_list_T0 : 8003, 1232967, 239859174

mystack_T0 : 1120, 20431, 12025250

ExStack_T0 : 40710, 75998, 5908666

stack_deque_T1 : 18444, 2383833, 490387023

stack_vector_T1: 35764, 3281555,1130011712

stack_list_T1 : 19624, 2842182, 588527582

mystack_T1 : 11764, 2285440, 749906486

ExStack_T1 : 56646, 1680889, 372481178

mystack: 在大多数情况下都很优秀,但当数据量较大并且数据类型较复杂时,性能迅速下降std::stack<T,vector<T> >: 性质与mystack一致,但VC6版实现得太差了;(这种库看了就让人生气)std::stack<T,list<T> >: 绝对速度(平均速度)没法和其他实现比,但他的优点不在这;ExStack: 初始化和销毁开销太大了些(否则ExStack在很多测试中都将领先),数据量较大时,不出意料的在简单和复杂数据类型中都领先于对手(这时才显示出综合了vector与deque的优势);但是ExStack的适用范围实在太小了,比我预期的适用范围差;

(PolyRandom:我觉得这个ExStack不错,而且可以用的地方应该不少。)

4. 极速stack的诞生myfast_stack

一次尝试(前奏): 还是忍不住自己写了一个deque内存管理方式的_myfast_stack;性质接近于std::static<T,deque<T> >; 其实比我想象中简单多了,很快就实现出来了(因为不需要实现一个完整的deque),测试时各项性能也很优秀;只是在“简单数据类型、元素个数N很小”时才输给了vector内存管理方式实现的mystack! 这一点好像在意料之中。

是否这就是极限了呢?我准备把源码和测试结果发布出来的时候,却突然有了新的想法...

一般deque内存管理需要用一个动态数组来保存指向数据块的指针,因为deque要求随机访问能力;但stack访问时明显没有随机访问特性的要求,所以 保存这些指针的数据结构最低需求也是满足stack接口就足够了;进一步的改进方案出来了,先用一个list来管理这些数据块,再把list的自己的数据成员与需要管理的数据空间合成放在一起(放在同一个数据块上);

哈哈,综合性能新的明星myfast_stack诞生了; 让人不敢相信的测试结果!!!

测试环境:Win2000,VC6,赛杨466,128M ( 自己的老古董电脑 )

N = 20 1000 50000

stack_deque_T0 : 434, 9355, 494481

stack_vector_T0: 509, 11770, 583668

stack_list_T0 : 1330, 82371, 7071753

mystack_T0 : 103, 3248, 323565

ExStack_T0 : 3258, 5580, 163168

myfast_stack_T0: 97, 2682, 190075

stack_deque_T1 : 2464, 135898, 11981644

stack_vector_T1: 4042, 228555, 22766841

stack_list_T1 : 3281, 207232, 16723651

mystack_T1 : 1916, 206413, 21782916

ExStack_T1 : 5228, 129881, 10933717

myfast_stack_T1: 1983, 124296, 10399781

实现的实质还是deque方式的,不管从那方面来看我认为它都可以将vector和list的实现淘汰掉!

!!!myfast_stack太恐怖了,几乎没有缺陷!!!

///////源代码//////////

//管理内存的list

template<unsigned int byte_size>

class Tdata_list//管理myfast_stack的内存

{

struct TNode//节点类型

{

TNode* pPrev;

TNode* pNext;

unsigned char Data[byte_size];//数据空间

};

public:

Tdata_list():pNodeBegin(0),pNodeCur(0),_size(0){}

unsigned int size() const { return _size; }

void push()//配置空间

{

if (0==pNodeBegin)

{

pNodeBegin=new TNode;

pNodeBegin->pPrev=0;

pNodeBegin->pNext=0;

pNodeCur=pNodeBegin;

}

else if (0!=pNodeCur->pNext)//还有一个空余的Node

{

pNodeCur=pNodeCur->pNext;

}

else

{

TNode* pNodeEnd=new TNode;

pNodeEnd->pPrev=pNodeCur;

pNodeEnd->pNext=0;

pNodeCur->pNext=pNodeEnd;

pNodeCur=pNodeEnd;

}

++_size;

}

~Tdata_list()

{

for (TNode* i=pNodeBegin;i!=0; )

{

TNode* pNext=i->pNext;

delete i;

i=pNext;

}

}

unsigned char* top()

{

return pNodeCur->Data;

}

void pop()

{

TNode*& pTmp=pNodeCur->pNext;

if (pTmp!=0)

{

delete pTmp;//留一个空余Node,多余的释放

pTmp=0;

}

pNodeCur=pNodeCur->pPrev;

--_size;

}

private:

TNode* pNodeCur;

TNode* pNodeBegin;

unsigned int _size;//用来追踪list的使用大小

};

//myfast_stack

//注意它的实现并没有以降低stack的通用能力来提高性能

//改进可能:1.提供专署的内存分配器,而不是默认的new/delete; (就可以和SGI中的stack对比测试了)

template<class T,bool IsPOD=false>

class myfast_stack

{

public:

enum{ type_sizes=sizeof(T),//!

node_width=(1020/type_sizes)+1//使用这种策略,stack<T,list<T> >也没有存在必要了

};

typedef T value_type;

typedef unsigned int size_type;

typedef Tdata_list<type_sizes*node_width> Tbase_alloc;

myfast_stack():_node_begin(0),_node_cur(0),_node_last(0) {}

~myfast_stack()

{

if ((!IsPOD)&&(_NodeList.size()>0))

{

for (T* i=_node_begin;i<_node_cur;++i)

i->T::~T();

int nsize=(int)_NodeList.size();

for (int j=0;j<(nsize-1);++j)

{

_NodeList.pop();

_node_cur=(T*)_NodeList.top();

for (int i=0;i<node_width;++i)

{

_node_cur->T::~T();

++_node_cur;

}

}

}

}

bool empty() const

{ return (0==size()); }

size_type size() const

{ return (_node_last-_node_begin)+node_width*(min((int)_NodeList.size()-1,0)); }

value_type& top()

{ return *(_node_cur-1); }

const value_type& top() const

{ return *(_node_cur-1); }

void push(const value_type& x)

{

if (_node_cur==_node_last)

{

_ToNextNode();

}

//std::_Construct(_node_cur,x);

new (_node_cur) T(x) ;

++_node_cur;

}

void pop()

{

if (_node_cur==_node_begin)

{

_ToPrevNode();

}

--_node_cur;

_node_cur->T::~T();

}

protected:

T* _node_begin;

T* _node_cur;

T* _node_last;

Tbase_alloc _NodeList;

void _ToNextNode()

{

_NodeList.push();

_node_begin=(T*)(_NodeList.top());

_node_last=_node_begin+node_width;

_node_cur=_node_begin;

}

void _ToPrevNode()

{

_NodeList.pop();

_node_begin=(T*)(_NodeList.top());

_node_last=_node_begin+node_width;

_node_cur=_node_last;

}

};

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有