# 2020q1 Homework1 (lab0)
contributed by < ``yceugene`` >
### Outline
* 環境設定
* 作業要求
* 實作流程
* 新增指令
* TODO
### 環境設定
```
$ uname -a
Linux LAPTOP-K7JUAVPA 4.19.128-microsoft-standard #1 SMP Tue Jun 23 12:58:10 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
$ gcc --version
gcc-10 (Homebrew GCC 10.2.0) 10.2.0
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
```
### 作業要求
* 在 ``queue.[c/h]`` 中完成以下實作:
* ``q_new``: 建立新的「空」佇列;
* ``q_free``: 釋放佇列所佔用的記憶體;
* ``q_insert_head``: 在佇列開頭 (head) 加入 (insert) 給定的新元素 (以 LIFO 準則);
* ``q_insert_tail``: 在佇列尾端 (tail) 加入 (insert) 給定的新元素 (以 FIFO 準則);
* ``q_remove_head``: 在佇列開頭 (head) 移除 (remove) 給定的元素。
* ``q_size``: 計算佇列中的元素數量。
* ``q_reverse``: 以反向順序重新排列鏈結串列,該函式不該配置或釋放任何鏈結串列元素,換言之,它只能重排已經存在的元素;
* ``q_sort``: 以遞增順序來排序鏈結串列的元素
### 實作流程
#### queue.h
根據題目要求為了讓 q_size 和 q_insert_tail 都可在 Q(1) 內完成,我們需要在 queue_t 結構中新增一些成員。
* 新的 Queue 結構定義如下:
```
typedef struct {
list_ele_t *head;
list_ele_t *tail;
int size;
} queue_t;
```
#### queue.h
##### q_new
* 新增一個空 queue 時,需要先以 malloc() 配置的一塊夠大的記憶體空間存放此 queue ,需要預防 malloc 配置失敗的情況,因次我們的程式在對 queue 初始化結構成員時,要先判斷 malloc() 回傳的是否為有效記憶體位址(即回傳位址非 NULL)。
* 修改 q_new() 如下:
```
queue_t *q_new()
{
queue_t *q = malloc(sizeof(queue_t));
if (!q)
return NULL;
q->head = NULL;
q->tail = NULL;
q->size = 0;
return q;
}
```
##### q_free
* 在釋出 queue 占用的記憶體空間前須先判斷 q 是否為 NULL,若否才會進一步釋放記憶體。
* 由於各個 queue 節點中都包含一個指向 string value 的指標,因此在我們依序釋放各節點前要先釋放該節點指向的 string,接著才是移除該節點。
* 在釋放 queue 之前我們先宣告一個 temp_ptr 指標,其功能是逐一指向 head 的下一個節點,這樣的好處是當我們用 head 釋放前一個節點後,仍然能保留下一個開始節點的資訊。
* 修改 q_free 如下:
```
void q_free(queue_t *q)
{
/* TODO: How about freeing the list elements and the strings? */
/* Free queue structure */
if (!q)
return NULL;
list_ele_t *temp_ptr;
while (q->head) {
temp_ptr = q->head->next;
free(q->head->value);
free(q->head);
q->head=temp_ptr;
}
free(q);
}
```
##### q_insert_head
* 根據提示,當函式被呼叫時先檢查 q 是否為 NULL,是則 return false。
* 針對要 insert 的字串配置一塊有效的記憶體,加上字串結束字元一共需準備 (strlen(s) + 1) 個 char 大小的空間。
* insert_head 過程中會做兩次的 malloc ,分別配置記憶體空間給新節點和新節點所指向的字串(存放要插入的字串),若 malloc 失敗則 return false。
* 如果成功配置記憶體給新節點,但字串空間配置失敗則必須一併釋放新節點的記憶體,再 return false。
* 配置好新節點後,須將新節點的記憶體位置 assign 給 q->head,並將 q->size 加一。
* 最後考慮新節點是否為第一個建立的節點,是的話則必須再以 q->tail 指向新節點(這時只有一個節點,此節點同時為 head 也是 tail)。
```
bool q_insert_head(queue_t *q, char *s)
{
if (!q)
return false;
list_ele_t *newh;
newh = malloc(sizeof(list_ele_t));
if (!newh)
return false;
/* To allocate space for the string and copy it */
newh->value = malloc(sizeof(char) * (strlen(s) + 1));
if (!newh->value)
{
free(newh);
return false;
}
memset (newh->value,'\0', strlen(s) + 1);
strncpy(newh->value, s, strlen(s));
/* Adjust head and size */
newh->next = q->head;
q->head = newh;
q->size += 1;
/* Adjust tail */
if (q->tail == NULL)
{
q->tail = newh;
}
return true;
}
```
##### q_insert_tail
* 做法和 q_insert_head 類似,只是方向不同
```
bool q_insert_tail(queue_t *q, char *s)
{
if (!q)
return false;
list_ele_t *newt;
newt = malloc(sizeof(list_ele_t));
if(!newt)
return false;
newt->value = malloc(sizeof(char) * srlen(s + 1));
if(!newt)
{
free(newt);
return false;
}
memset(newt->value, 0, sizeof(char) * srlen(s + 1));
newt->value = strncpy(newt->value, s, strlen(s));
newt->next = q->head;
q->tail->next = newt;
q->tail = newt;
q->size += 1;
if(!q->head)
q->head = newt;
return false;
}
```
##### q_remove_head
* 發生下列兩種情況時 return false
* 修改 q_remove_head 如下:
```
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
if (!q || !sp)
return false;
if (strlen(q->head->value) * sizeof(char) > bufsize - 1)
return NULL;
memset(sp, '\0', sizeof(char) * bufsize);
strncpy(sp, q->head->value, bufsize);
list_ele_t *temp_ptr = q->head->next;
free(q->head->value);
free(q->head);
q->head = temp_ptr;
q->size -= 1;
return true;
}
```
##### q_reverse
* 這裡我參考 http://alrightchiu.github.io/SecondRound/linked-list-xin-zeng-zi-liao-shan-chu-zi-liao-fan-zhuan.html 中對 single linkded list 做 reverse 的方式:
1. 先判斷 queue 中有幾個有效節點:
* 若 ``q->size < 2`` 直接 return
* 若 ``q->size == 2`` 也很好處理
2. 當 ``q->size >= 3`` 時,我們先新增三個 pointer: prev_ptr、curr_ptr、post_ptr,依序從原本 head 位址依序向 tail 方向移動,每次移動時改變 curr_ptr 指向節點的 next 的方向反轉。
* 改寫 q_reverse 如下:
```
void q_reverse(queue_t *q)
{
if (!q)
return;
if (q->size<2)
return;
if (q->size == 2)
{
q->head->next = NULL;
q->tail->next = q->head;
q->head = q->tail;
q->tail = q->tail->next;
return;
}
list_ele_t *prev_ptr = NULL;
list_ele_t *curr_ptr = NULL;
list_ele_t *post_ptr = NULL;
prev_ptr = q->head;
curr_ptr = prev_ptr->next;
post_ptr = curr_ptr->next;
q->head = q->tail;
q->tail = prev_ptr;
prev_ptr->next = NULL;
while (post_ptr->next)
{
curr_ptr->next = prev_ptr;
}
post_ptr->next = curr_ptr;
return;
}
```
##### q_size
* 若 q 不為 NULL,直接回傳 q->size 即可。
* 修改 q_size 如下:
```
int q_size(queue_t *q)
{
if(!q)
return false;
return q->size;
}
```
### 嘗試新增 hello 指令
1. 做法:
* 修改 qtest.c
* 在 console_init() 中新增如下:
```
add_cmd("hello", do_hello, " | Print hello message");
```
* 在 Forward declarations 中新增如下:
```
static bool do_hello(int argc, char *argv[]);
```
* 新增 do_hello()
```
static bool do_hello(int argc, char *argv[])
{
return (bool) printf("Hello, World\n");
}
```
2. 編譯後執行結果:
* 輸入 help 可看到新增的 hello 指令
```
Commands:
# ... | Display comment
free | Delete queue
hello | Print hello message
help | Show documentation
```
* 輸入 hello 可看到執行效果
```
cmd> hello
Hello, World
```
## 其他:
* lab0 作業說明中:
* "qtest 命令直譯器的實作" 內容中 cmd_init 應改為 init_cmd 。
* "以下示範在console.c 檔案新增名為 hello 的命令。先準備 do_hello 函式" 內文中 console.c 應改為 qtest.c。
### TODO
* Cppcheck: 靜態程式碼分析工具
* 新增指令 hello
* Git Hooks 進行自動程式碼排版檢查
* Valgrind 動態程式分析工具
* 研究自動測試機制
* Linux Programming Interface