需求
最近在做毕设,关于手势识别的,自己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;