이번에는 Queue를 보자. Queue는 Stack처럼 추상화 데이터형이지만, 후입선출(LIFO)인 Stack과 달리, 먼저 들어온 데이터가 먼저 나가는 선입선출(FIFO : First in First out)을 따른다. 다음의 코드를 보자.
Queue.h
#pragma once
class Customer
{
private:
long arrive;
int processtime;
public:
Customer() { arrive = processtime = 0; }
void Set(long when);
long when() const { return arrive; }
int pTime() const { return processtime; }
};
typedef Customer Item;
class Queue
{
struct Node
{
Item item;
struct Node* next;
};
enum { Q_SIZE = 10 };
public:
Queue(int qs = Q_SIZE);
~Queue();
bool isEmpty() const;
bool isFull() const;
bool enqueue(const Item & item);
bool dequeue(Item & item);
int queueCount() const;
private:
Queue(const Queue& q) : qSize(0) {}
Queue& operator=(const Queue& q) { return *this; }
Node* front; // 머리.
Node* rear; // 꼬리.
int items; // 현재 항목 수.
const int qSize; //최대 항목 수.
};
Queue.cpp
#include "Queue.h"
#include <iostream>
void Customer::Set(long when)
{
processtime = std::rand() % 3 + 1;
arrive = when;
}
Queue::Queue(int qs)
:front(nullptr), rear(nullptr), items(0), qSize(qs)
{
}
Queue::~Queue()
{
/*if (front == nullptr)
return;
Node* temp = front;
Node* toDelete = front;
while (temp->next)
{
toDelete = temp;
temp = temp->next;
delete toDelete;
}
delete toDelete;*/
Node* temp;
while (front != nullptr)
{
temp = front;
front = front->next;
delete temp;
}
}
bool Queue::isEmpty() const
{
/*if (items == 0)
return true;
return false;*/
return items == 0;
}
bool Queue::isFull() const
{
/*if (items == qSize)
return true;
return false;*/
return items == qSize;
}
bool Queue::enqueue(const Item& item)
{
/*if (isFull())
return false;
if (front == nullptr)
{
front = new Node();
front->item = item;
}
else
{
Node* temp = front;
while (temp->next)
{
temp = temp->next;
}
temp->next = new Node();
temp->next->item = item;
rear->item = item;
rear->next = nullptr;
}
return true;*/
if (isFull())
return false;
Node* add = new Node();
std::bad_alloc exception; // 예외를 던짐.
add->item = item;
add->next = nullptr;
items++;
if (front == nullptr)
front = add;
else
rear->next = add;
rear = add;
return true;
}
bool Queue::dequeue(Item& item)
{
/*if (isEmpty())
return false;
Node* temp = front;
while (temp->next)
{
temp = temp->next;
}
temp->next = nullptr;
delete rear;*/
if (front == nullptr)
return false;
item = front->item;
items--;
Node* temp = front;
front = front->next;
delete temp;
if (items == 0)
rear = nullptr;
return true;
}
int Queue::queueCount() const
{
return items;
}
(주석 처리 한 건 필자가 짜본 매우 겸손한 코드다.)
이 코드에서 하나씩 보자.
클래스 내의 const 변수에 주목해보자. const는 상수 특징을 가지고 있어서 생성과 동시에 초기화 시켜주어야 한다. 만약 이렇게 적었다면 제대로 작동하지 않는다.
Queue::Queue(int qs)
{
...
qSize = qs; // 오류가 난다.
}
왜? 객체를 선언할 때 클래스 내의 변수들도 똑같이 선언된다고 생각하면 편하다. 그 후 생성자 코드 블록에서 하는 작업들은 대입 작업이다. const같은 변수들을 선언과 동시에 초기화시켜주기 위해서 생성자에 특별한 문법이 추가됐다. 이를 '초기자 리스트'라고 부른다. 혹은 이니셜라이저(initializer)라고 부른다. 이니셜라이저는 이런 식으로 작성한다.
Queue::Queue(int qs)
:front(nullptr), rear(nullptr), items(0), qSize(qs)
{
}
이는 각각 괄호 왼쪽의 변수를 괄호 안의 변수로 '초기화'시켜달라는 의미이다. 오직 생성자에서만 가능하다. 클래스 내의 const뿐만 아니라 '참조 변수'들에 대해서는 초기자 리스트 문법을 반드시 사용해야 한다. 참조 변수들도 const와 마찬가지로 생성될 때만 초기화 될 수 있기 때문이다. 그리고 애초에 다른 기본 변수들도 시간 관점에서 이니셜라이저를 이용하는 게 좋다. 왜? 그렇지 않으면 디폴트 생성자를 대입 한 후에 다시 대입하는 거랑 같기 때문이다.
이니셜라이저와 같은 효과를 가지는 문구는 다음과 같다.
class Queue
{
public:
...
private:
Node* front = nullptr;
Node* rear = nullptr;
int items = 0;
const int qSize = Q_SIZE;
};
클래스 내에서 초기화 시키는 것이다. 그래서 이름도 In-Class 초기화다. 만약 이거랑 생성자랑 초기화 값이 다르면 어떻게 되는가? 생성자 값을 따른다. 그러니 효과는 동일하지만, 생성자가 약간 뒤늦게 실행 된다고 생각하면 편할 것 같다. 갠적으로 이거 궁금했는데 겨우 알았네;; 그래도 난 생성자를 사용할 것 같다. 생성자가 객체를 생성할 때 초기화를 담당하는 그런 뉘앙스가 강하기 때문이다.
복사 생성자와 대입 연산자를 private에 적음으로써 디폴트 정의들의 생성을 방지할 수 있고, 또 아예 사용하지 못하게 하는 효과가 있다.
std::bad_alloc exception; // 예외를 던짐.
동적 할당을 했을 때 아주 가끔씩 못 만드는 경우가 있다고 한다. 그런 예외적인 상황이 나오면 프로그램이 종료되는데 그런 걸 막아주는 문구인 것 같다. 찾아보니까 catch랑 쓰이는 것 같은데 정확한 쓰임새는 아직 잘 모르겠다;;