可随机访问的队列

本文介绍了为解决手势识别算法需求而自定义的一种可随机访问的固定大小队列。队列通过循环数组实现,维护头指针和尾指针。文章讨论了const和非const版本成员函数的调用规则,解释了为何const版本可以调用非const版本,而非const版本不能调用const版本,避免无穷递归。同时,作者提醒读者注意,仅返回类型不同不足以构成重载,关键在于const修饰的差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

需求

最近在做毕设,关于手势识别的,自己yy了一种算法,实现里面为了方便,需要一种可供我随机访问的大小固定的队列。

矛盾就是,STL里提供的队列只能访问队头,所以就自己实现了一发。

设计

众所周知,要支持随机访问,那么底层就得用数组来实现!我选择了vector(用原始数组也完全没问题,因为细节可以自己控制,不用怕client使用时会越界)。

具体方法是,自己维护一个循环数组,即设立一个头指针,一个尾指针,注意两者的含义要维持在每种操作的前后都不变:

head:表示当前队列里头最旧的一个元素的下标
tail:表示当前队列里,新被插入元素应该放的位置的下标

举个例子,空队列时,head = 0(问:难道不应该是-1?答:特殊的点,没什么,为了写代码方便而已),tail = 0。
插入第一个元素时,根据tail的指示,应该把它放在下标为tail=0的位置!插入后,tail应该变成1了,因为下一个应该放的位置是1!

代码与问题

代码很短,还有一个点可以看的,就是operator []提供了两种版本,分别返回T&const T&
为什么呢?
因为有需要啊,比如下面代码里的display函数;还有使用const对象时,如果只有非const版本,则无法调用该函数!
但是两者的代码又是一模一样的,重复不好
根据《Effective C++》的指示,可以在后者(const)里面调用前者(非const);反过来,则不行,想想为什么?(文末有答案)

代码如下,由于写成了模板,所以声明和实现放在同个文件里头了:

#ifndef __N_RANDOME_QUEUE_H__
#define __N_RANDOME_QUEUE_H__

#include <vector>
using std::vector;  // 尽量不要用using namespace std;


template<typename T>
class NRandomQueue
{
public:
    NRandomQueue(size_t capacity);
    void push(const T& obj);
    T& operator [] (size_t k);
    const T& operator [] (size_t k) const;
    void display() const;

private:
    vector<T> m_data;
    size_t m_capacity;  // 队列的容量
    size_t m_head;  // 表示当前队列里头最旧的一个元素的下标
    size_t m_tail;  // 表示当前队列里,新被插入元素应该放的位置的下标
};



template<typename T>
NRandomQueue<T>::NRandomQueue(size_t capacity)
: m_capacity(capacity), m_head(0), m_tail(0) {}


template<typename T>
void NRandomQueue<T>::push(const T& obj) {
    // “追尾”表示满了,这一次插入时需要则覆盖最旧的头部,头部需要前进一位!
    if (m_tail == m_head) {
        ++m_head;
        if (m_head >= m_capacity)
            m_head -= m_capacity;
    }
    if (m_data.size() < m_capacity) {
        m_data.push_back(obj);
        ++m_tail;
    }
    else {
        m_data[m_tail++] = obj;
    }
    if (m_tail >= m_capacity)
        m_tail -= m_capacity;
}


template<typename T>
T& NRandomQueue<T>::operator [] (size_t k) {
    k += m_head;
    k = k % m_data.size();  // 为了保证下标不越界访问
    return m_data[k];
}


template<typename T>
const T& NRandomQueue<T>::operator [] (size_t k) const {
    NRandomQueue<T>* _this = const_cast<NRandomQueue<T>*>(this);
    return _this->operator[](k);
}


template<typename T>
void NRandomQueue<T>::display() const {
    for (size_t i = 0; i < m_data.size(); ++i) {
        if (i > 0)
            cout << ", ";
        cout << this->operator[](i);
    }
    cout << endl << endl;
}


#endif

回答与新的问题

回答一下上面提到的问题:

为什么不能反过来,通过非const版本来调用const版本呢?

答:因为,在非const版本里,根本没法调用到const版本!!!
因为既然有两个函数重载了(下面会继续讨论重载的问题),那么编译器会按照它的规则来选取最合适的一个版本来调用!谁是最合适的呢?很明显是它自己,所以一旦这样子写代码,会导致无穷递归,所以没法调用到const版本。

从而,只能通过在const版本里头调用非const版本。

不信的话,请分别运行下面两份实现:

const版本调用非const版本(正常)

template<typename T>
T& NRandomQueue<T>::operator [] (size_t k) {
    cout << "T&" << endl;
    k += m_head;
    k = k % m_data.size();  // 为了保证下标不越界访问
    return m_data[k];
}


template<typename T>
const T& NRandomQueue<T>::operator [] (size_t k) const {
    cout << "const T&" << endl;
    NRandomQueue<T>* _this = const_cast<NRandomQueue<T>*>(this);
    return _this->operator[](k);
}

非const版本调用const版本(无穷递归)

注意,得显式调用非const版本的operator[]才会出现无穷递归。如果通过const成员函数display()去看的话,调用的是const版本,是没有问题的!

template<typename T>
T& NRandomQueue<T>::operator [] (size_t k) {
    cout << "T&" << endl;
    return const_cast<T&>(this->operator[](k));
}


template<typename T>
const T& NRandomQueue<T>::operator [] (size_t k) const {
    cout << "const T&" << endl;
    k += m_head;
    k = k % m_data.size();  // 为了保证下标不越界访问
    return m_data[k];
}

重载

C++对于重载的要求是:两个函数的“函数签名”(signature)要不一样,然而函数签名并不包含返回值的类型,所以对于上述的两者,如果只是返回值类型的不同(即const T&和T&),是不足以构成重载的!!!

值得注意的是,两者构成重载的根本在于成员函数的const修饰,再回顾一下两者的区别:

T& operator [] (size_t k);
const T& operator [] (size_t k) const;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值