在开篇之前,补充一个之前遗留的问题。
在写单链表的时候,有一个成员函数是如下形式:
struct snode* getPtrByIndex(int pos);///通过传入的索引,返回节点的指针,如果不存在该索引,则返回空指针
同样的还有:
struct snode* getPtrByVal(const T val);///返回值满足传入的第一个节点的指针,不存在则返回空指针
但是这两个函数在编译的时候始终无法通过。而且,之前曾经提到过,模板类如果不调用函数的话,在编译的时候,即使有语法错误都不会提示,那么这个函数在没有调用的情况下反而在编译的时候出现了问题,是什么原因呢。
原因我还是不知道!!!!
但是现在知道怎么解决了!!!!
方法很简单,就是将前面的struct删除即可。
template<class T>
snode<T>* mychain<T>::getPtrByVal(const T val)
{
struct snode<T>* p = m_pHead;
while (NULL != p)
{
if (val == p->val)
{
return p;
}
p = p->next;
}
return p;
}
之所以我会在前面加上struct,其实是一个以前在C语言中遗留下来的习惯。在以前上课的时候强调了C语言中对于结构体指针,必须要带上struct,不然会出错。一直这样的习惯导致到了在C++中也在坚持,但是可能是C++放松了这样的要求,以及类class的出现和两者的相似性,取消了这样的限制。反而是在使用中,添加struct的方法可能会产生混淆。
在以后,尽量全部使用class,毕竟即使是一个坐标结构体,也可以携程class的形式,添加上成员变量和成员函数,管理起来方便,使用起来也很方便。
其实队列的部分我是真的不想写来的,因为从其定功能上来说,都只能算得上“穷人版的线性表”。队列在外表上和线性表具有太多的相似性了, 以至于几乎可以将线性表部分拿过来,再删除掉几个中间操作的函数,就成为了队列的数据结构。
但是最后决定还写一写的原因在于:
1、写队列,我决定链表采用双链表,以补充之前在写线性表的时候采用的是单链表;
2、数组表示的队列决定采用循环队列的方式,一方面能够很好的利用空间,另一方面也是补充之前的普通数组线性表。
其实循环队列的方式更加适用于队列的数据结构,因为随着队伍的进进出出,整个数据块在数组申请空间的位置逐渐后移,如果不采用循环队列的方式,那么空间的利用率就会大打折扣。
下面贴上队列的链表描述源程序代码:其中等于符号进行操作符重载的时候偷懒了,利用了拷贝构造函数
#ifndef _H_MYQUEUECHAIN_H
#define _H_MYQUEUECHAIN_H
#include <iostream>
template<class T>
class myQueueChain;
template<class T>
struct snode
{
snode<T>* next;
T val;
};
template<class T>
class myQueueChain
{
public:
myQueueChain(void);
myQueueChain(const myQueueChain<T>& mqc);
int push_back(T val);
void pop_front(void);
bool Empty(void);
T top(void);
int size(void);
myQueueChain<T>& operator=(const myQueueChain<T>& mqc);
protected:
private:
snode<T>* m_pHead;
};
template<class T>
myQueueChain<T>::myQueueChain(void)
{
std::cout<<"默认构造函数,仅将头指针置为NULL"<<std::endl;
m_pHead = NULL;
}
template<class T>
myQueueChain<T>::myQueueChain(const myQueueChain<T>& mqc)
{
std::cout<<"拷贝构造函数"<<std::endl;
snode<T>* p = mqc.m_pHead;
m_pHead = NULL;
snode<T>* p_last = m_pHead;
while (NULL != p)
{
if (NULL == m_pHead)
{
m_pHead = new struct snode<T>;
m_pHead->val = p->val;
p_last = m_pHead;
p_last->next = NULL;
}
else
{
p_last->next = new struct snode<T>;
p_last = p_last->next;
p_last->val = p->val;
p_last->next = NULL;
}
p = p->next;
}
}
//template<class T>
//myQueueChain<T> myQueueChain<T>::operator=(const myQueueChain<T>& mqc)
//{
// myQueueChain<T> m(mqc);
// return m;
// 之前上面的这种方法是不正确的:
// 1、没有考虑到原来的队列中存在元素的情况,
// 2、正常应该返回引用
// 3、即使考虑了存在元素的情况也应该考虑两边元素的多少
//
//}
template<class T>
myQueueChain<T>& myQueueChain<T>::operator=(const myQueueChain<T>& mqc)
{
std::cout<<"重载操作符 = "<<std::endl;
if (this != &mqc)
{
struct snode<T>* p_leftPre = NULL;
struct snode<T>* p_left = m_pHead;
struct snode<T>* p_right = mqc.m_pHead;
while (NULL != p_left && NULL != p_right)
{
p_leftPre = p_left;
p_left->val = p_right->val;
p_left = p_left->next;
p_right = p_right->next;
}
if (NULL == p_left)旧空间使用完了
{
while (NULL != p_right)
{
if (NULL == p_leftPre)///这种情况是左边上来就是个空的
{
p_leftPre = new struct snode<T>;
p_leftPre->val = p_right->val;
p_leftPre->next = p_left;
}
else
{
p_leftPre->next = new struct snode<T>;
p_leftPre = p_leftPre->next;
p_leftPre->val = p_right->val;
p_leftPre->next = NULL;
}
p_right = p_right->next;
}
}
if (NULL == p_right)/ 旧空间还有剩余
{
p_leftPre->next = NULL;
while (NULL != p_left)
{
p_leftPre = p_left->next;
delete p_left;
p_left = p_leftPre;
}
}
}
return (*this);
}
template<class T>
int myQueueChain<T>::push_back(T val)
{
struct snode<T>* p = m_pHead;
int cnt = 0;
if (NULL == p)
{
m_pHead = new struct snode<T>;
m_pHead->val = val;
m_pHead->next = NULL;
return cnt;
}
while (NULL != p)
{
cnt = cnt + 1;
if (NULL == p->next)
{
break;
}
p = p->next;
}
p->next = new struct snode<T>;
p = p->next;
p->next = NULL;
p->val = val;
return cnt;
}
template<class T>
void myQueueChain<T>::pop_front(void)
{
if (NULL != m_pHead)
{
struct snode<T>* p = m_pHead;
m_pHead = m_pHead->next;
delete p;
}
else
{
throw("NoElementToPop");
}
}
template<class T>
T myQueueChain<T>::top(void)
{
if (NULL == m_pHead)
{
throw("NoElement");
}
return m_pHead->val;
}
template<class T>
bool myQueueChain<T>::Empty(void)
{
return (NULL == m_pHead);
}
template<class T>
int myQueueChain<T>::size(void)
{
struct snode<T>* p = m_pHead;
int cnt = 0;
while (NULL != p)
{
cnt = cnt + 1;
p = p->next;
}
return cnt;
}
#endif
下面是链表描述的队列数据结构的测试程序:
#include "myQueueChain.cpp"
#include <iostream>
void main(void)
{
myQueueChain<int> mqc1;
std::cout<<"**********************************"<<std::endl;
for (int ii = 0; ii < 6; ii++)
{
mqc1.push_back(ii);
}
myQueueChain<int> mqc2(mqc1);
std::cout<<"**********************************"<<std::endl;
std::cout<<mqc1.size()<<std::endl;
while (!mqc1.Empty())
{
std::cout<<mqc1.top()<<"\t";
mqc1.pop_front();
}
std::cout<<std::endl;
myQueueChain<int> mqc3 = mqc2;
std::cout<<"*上面的并没有进入到默认构造然后重载操作符,而是直接进入拷贝构造函数"<<std::endl;
myQueueChain<int> mqc4;
mqc4 = mqc2;
std::cout<<"**********************************"<<std::endl;
while (!mqc2.Empty())
{
std::cout<<mqc2.top()<<"\t";
mqc2.pop_front();
}
std::cout<<std::endl<<mqc2.size()<<std::endl;
while (!mqc3.Empty())
{
std::cout<<mqc3.top()<<"\t";
mqc3.pop_front();
}
std::cout<<std::endl<<mqc3.size()<<std::endl;
std::system("pause");
}
下面是链表表示的队列的代码,暂时还没有测试所有的成员函数。
#ifndef _H_MYQUEUEARRAY_H
#define _H_MYQUEUEARRAY_H
template<class T>
class myQueueArray;
template<class T>
class myQueueArray
{
public:
myQueueArray(int len = 64);
myQueueArray(const myQueueArray<T>& mqa);
myQueueArray(T arr[], int len);
~myQueueArray();
T pop_front(void);
void push_back(T val);
int size(void);
bool full(void);
bool empty(void);
T top();
myQueueArray<T>& operator=(myQueueArray<T>& mqa);
private:
T* m_ptr;
int m_front;
int m_back;
int m_len;
};
template<class T>
myQueueArray<T>::myQueueArray(int len = 64)
{
m_front = 0;
m_back = 0;
if (len <= 0)
{
len = 16;
}
m_len = len;
m_ptr = new T[m_len];
}
template<class T>
myQueueArray<T>::myQueueArray(const myQueueArray<T>& mqa)
{
m_front = mqa.m_front;
m_back = mqa.m_back;
m_len = mqa.m_len;
m_ptr = new T[m_len];
memcpy(m_ptr, mqa.m_ptr, sizeof(T) * m_len);
}
template<class T>
myQueueArray<T>::myQueueArray(T arr[], int len)
{
m_front = 0;
m_back = len;
m_len = len * 1.2 + 1;
m_ptr = new T[m_len];
memcpy(m_ptr, arr, sizeof(T) * len);
}
template<class T>
myQueueArray<T>::~myQueueArray()
{
if (NULL != m_ptr)
{
delete[] m_ptr;
}
}
template<class T>
T myQueueArray<T>::pop_front(void)
{
if(m_back == m_front)
{
throw("NoElmentToPop");
}
else
{
T val = m_ptr[m_front];
m_front = (m_front + 1)%m_len;
return(val);
}
}
template<class T>
void myQueueArray<T>::push_back(T val)
{
if (0 == (m_back - m_front + 1 + m_len) % m_len)将要满了,扩容
{
T* p = new T[m_len * 1.2 + 1];
if (m_back >= m_front)/正常模式,正常扩容,仅修改m_len即可
{
memcpy(p, m_ptr, sizeof(T) * m_len);
}
else
{
memcpy(p, &(m_ptr[m_front]), sizeof(T) * (m_len - m_front));
memcpy(&(p[m_len - m_front]), m_ptr, m_back);
m_front = 0;
m_back = (m_back - m_front) % m_len;
}
delete[] m_ptr;
m_ptr = p;
m_len = m_len * 1.2 + 1;
}
m_ptr[m_back] = val;
m_back = m_back + 1;
}
template<class T>
int myQueueArray<T>::size(void)
{
return((m_back - m_front + 1 + m_len) % m_len);
}
template<class T>
bool myQueueArray<T>::full(void)
{
return(0 == (m_back - m_front + 1 + m_len) % m_len);
}
template<class T>
bool myQueueArray<T>::empty(void)
{
return(m_back == m_front);
}
template<class T>
T myQueueArray<T>::top()
{
if (m_front != m_back)
{
return(m_ptr[m_front]);
}
else
{
throw("NoElement");
}
}
template<class T>
myQueueArray<T>& myQueueArray<T>::operator=(myQueueArray<T>& mqa)
{
if (this != &mqa)
{
m_len = mqa.m_len;
m_front = mqa.m_front;
m_back = mqa.m_back;
delete[] m_ptr;
m_ptr = new T[m_len];
memcpy(m_ptr, mqa.m_ptr, sizeof(T) * m_len);
}
return(*this);
}
#endif
下面是测试数组表示的队列的代码
myQueueArray<int> mqa;
int arr[9];
for (int ii = 0; ii < 9; ii++)
{
mqa.push_back(ii);
arr[ii] = ii;
}
myQueueArray<int> mqa1;
mqa1 = mqa;
myQueueArray<int> mqa2(mqa);
while (!mqa2.empty())
{
std::cout<<mqa2.pop_front()<<"\t";
}
std::cout<<std::endl;
myQueueArray<int> mqa3(arr, 9);
while (!mqa3.empty())
{
std::cout<<mqa3.pop_front()<<"\t";
}
std::cout<<std::endl;
昨天还看到一个有意思的事情。在两个模板类几个构造函数和重载操作符的函数中,都添加了输出语句,就是为了验证这个现象。
myQueueChain<int> mqc3 = mqc2;
上面这个句子,正常的期待是首先进入到默认的构造函数中,然后再执行操作符重载函数中的内容。但是实际上的情况却是两个都没有执行,而是直接进入到了拷贝构造函数中。
那么如果没有拷贝构造函数的情况下面又该是什么样呢?
应该就是期待的先进入默认构造函数,然后执行操作符重载函数。