自学内容网 自学内容网

C++相关概念和易错语法(17)(适配器模式、仿函数)

1.stack和queue

stack和queue的相关接口如下:

stack

queue

我们发现不管是stack还是queue,它们都有push和pop,不区分push_back和push_front,这是由它们的入栈特定顺序特性决定的,并且它们都没有迭代器,stack只有top,queue只有front和back这使得我们只能以规定的方式去访问这个stack或者queue

emplace相当于入栈,一般我们用push就可以了。

2.stack和queue的模拟实现

我们已经知道stack、queue和vector、list本质上并没有区别,所以我们可以将复用的思想带到它们的实现中,并且我们可以利用模板对于同一个stack模板,使用不同的底层来实现。这体现了继迭代器模式后的第二种设计模式:适配器模式,即转换。

下面是它们的实现

template<class T, class Container = vector<T>>
class stack
{
public:
stack(const Container& con = Container())
:_con(con)
{}

void push(const T& val)
{
_con.push_back(val);
}

void pop()
{
_con.pop_back();
}

T& top()
{
return _con.back();
}

size_t size()
{
return _con.size();
}

bool empty()
{
return _con.empty();
}

void swap(stack& st)
{
std::swap(_con, st._con);
}


private:
Container _con;
};

template<class T, class Container = list<T>>
class queue
{
public:
queue(const Container& con = Container())
:_con(con)
{}

void push(const T& val)
{
_con.push_back(val);
}

void pop()
{
_con.pop_front();
}

T& back()
{
return _con.back();
}

T& front()
{
return _con.front();
}

size_t size()
{
return _con.size();
}

bool empty()
{
return _con.empty();
}

void swap(queue& q)
{
std::swap(_con, q._con);
}

我们可以看出,stack和queue的实现几乎不需要自己实现具体功能,我们只需要关注上层功能而不是底层的细节,这极大地体现了封装的特性。

代码中仍有一些细节点需要注意:

(1)为什么只写了构造但没有写析构?为什么构造有缺省值

构造时我们可能空构造,需要缺省值,也有可能带参构造,带参构造需要我们形参接收,这里展示的是拷贝构造的情况,析构的时候成员_con作为自定义类型会去调用自己的析构函数,不会存在内存泄露的情况,所以不需要我们显式的去写析构

(2)swap为什么使用std::swap?

事实上,当我们包含了<vector><list>后,std域里的swap函数就多了几个重载,这几个重载是专门针对vector和list类型的,_con和q._con是Container类型,也就是vector<int>或list<int>类型,会去调用重载的效率较高的swap函数。

之所以std里重载swap函数,就是为了让我们在不知vector类里有swap成员函数的情况下也能高效地交换(只交换指针的值而不是交换所有指向的值)。因此除了这种写法,我们还可以显式调用vector类的成员函数

(3)stack只能以vector作为容器吗?

vector是连续的物理结构,下标访问快,但头插效率极低。因此vector不支持头插头删。在queue中pop使用了pop.front(),在vector里没有这个接口,所以不能使用vector实现queue,但是我们如果用erase来实现pop也可以,这样就可以用vector实现queue了,但并不建议这么做,因为vector实现queue本就是效率极低的做法。

list是分散的物理结构,任意位置插入删除效果都不错,但下标访问效率低。因此list不支持下标访问。但是stack和queue都没用到下标访问,因此我们可以用list实现queue和stack。

事实上,stack和queue在STL中都是以deque作为默认的容器。我们发现,vector和list本就是两个极端的存储结构。所以deque是结合vector、list,能同时进行下标访问和push_back、push_front等操作的容器。

deque用buff小数组+中控数组来实现它的功能,是vector和list存储的结合体,虽然支持下标访问,但它的没vector快,deque[]偶尔用用还行,头尾删除不错。我们适当了解一下deque即可,不用深究,因为deque本不算一个效率高的容器。

3.优先级队列和仿函数

priority_queue本质上是堆,我们只要掌握了堆,优先级队列的核心就能很快完成了。

但是priority_queue用到了仿函数的知识点,在大堆小堆的处理和我们C语言有所不同

(1)仿函数:

仿函数,即模仿函数的一个类,我们看下面的代码:


comp(1,2)是我们常见的函数的写法,但它的本质是是一个类重载了operator()后表现出来的类似于函数的特性。仿函数实现起来也相当简单,只要理解了它的实质就能很快上手

但是仿函数和构造函数容易搞混,一定要注意仿函数是要针对已创建的对象调用而不是针对类来调用,那种写法是构造函数而不是仿函数

(2)优先级队列

优先级队列的功能很简单,就是结合vector、仿函数实现堆,核心代码就是AdjustUp和AdjustDown。

建议将AdjustUp和AdjustDown放进private,对外只提供push、pop这样的接口,体现封装的特性。

注意当仿函数是less时默认是大堆,greater默认是小堆,在我们进行数值比较时要注意谁在前谁在后,因为顺序不同,会影响返回值,从而影响我们生成的堆的类型。

由于堆相关的知识前面我已经详细讲过,这里就不多阐述了。

完整代码如下:


namespace my_priority_queue
{
template<typename T>
class less
{
public:
bool operator()(const T& t1, const T& t2)
{
return t1 < t2;
}
};


template<typename T>
class greater
{
public:
bool operator()(const T& t1, const T& t2)
{
return t1 > t2;
}
};


template<typename T, class Container = vector<T>, class Compare = less<T>>
class priority_queue
{
public:

priority_queue() = default;

template<typename iterator>
priority_queue(iterator begin, iterator end)
{
while (begin != end)
{
_con.push_back(*begin);
begin++;
}

for (int parent = (int)(size() - 2) / 2; parent >= 0; parent--)
{
AdjustDown(parent);
}

}

size_t size()
{
return _con.size();
}

void push(const T& val)
{
_con.push_back(val);
AdjustUp(size() - 1);
}

void pop()
{
std::swap(_con[0], _con[size() - 1]);
_con.pop_back();
AdjustDown(0);
}

const T& top()
{
return _con[0];
}

bool empty()
{
return _con.empty();
}

void swap(priority_queue& p)
{
std::swap(_con, p._con);
}



private:
void AdjustUp(size_t child)
{
Compare comp;

size_t parent = (child - 1) / 2;

while (child > 0)
{
if (comp(_con[parent], _con[child]))
std::swap(_con[parent], _con[child]);

child = parent;
parent = (child - 1) / 2;
}
}

void AdjustDown(size_t parent)
{
Compare comp;

size_t child = parent * 2 + 1;

while (child < size())
{
if (child + 1 < size() && comp(_con[child], _con[child + 1]))
child++;

if(comp(_con[parent], _con[child]))
std::swap(_con[parent], _con[child]);

parent = child;
child = parent * 2 + 1;
}
}


private:
Container _con;
};
}


原文地址:https://blog.csdn.net/SGlow0708/article/details/140285238

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!