# 2019q3 Homework2 (lab0)
contributed by < `YenHengLin` >
>作業系統:Ubuntu 18.04 LTS
>作業系統類型:64 位元
## [C Programming Lab](http://www.cs.cmu.edu/~213/labs/cprogramminglab.pdf) 作業要求
### 回想 string 儲存方式
* c 語言儲存 string的方式 是用 char 去儲存的,如果要存長度為 $n$ 的 string 就必須要用 $n+1$ 個 char 去儲存。
* 儲存的方式為前面 $n$ 個 char 為典型的 ASCII =='== 型式(例: **'A'**),而最後一個 char 則設成 `\0` (null terminator)
### 修改 queue_t structure
* 為了更好的做到 FIFO 操作,增加了 `list_ele_t *tail`
* 題目要求 $O(1)$ 時間算出 queue_t 裡的 element 個數,所以增加 `int size` 紀錄增加的 element
* code
```c
typedef struct {
list_ele_t *head; /* Linked list of elements */
/*
You will need to add more fields to this structure
to efficiently implement q_size and q_insert_tail
*/
list_ele_t *tail;
int size;
} queue_t;
```
### 修改 q_new
* 設 `q->tail = NULL`讓 q_new 在 FIFO 的情況下也能判斷 queue_t 是 empty queue
* 設 `q->size = 0`,因為 empty queue 跟 NULL queue 一樣,size 都為 0
* 多寫一行程式碼判斷 malloc 失敗的情況,避免 malloc 失敗,程式卻還要求 q->head 存在
* code
```c
queue_t *q_new()
{
queue_t *q = malloc(sizeof(queue_t));
/* What if malloc returned NULL? */
if (q == NULL) {
return NULL;
}
q->head = NULL;
q->tail = NULL;
q->size = 0;
return q;
}
```
### 完成 q_free
* 如果只 free(q) 的話,則會有好幾個物件遺失掉指著他的指標,而使得這些物件散落在記憶體空間裡,如果程式沒有完整的回收機制的話,程式會認為這些遺失的記憶是還有用處的,而不去動用他,久了造成電腦明明還有很多剩餘空間,可是記憶體卻滿了的現象
* 為了避免 memory leak,我們必須將 q->head 指向的東西一個個 free 掉,透過 head 移動來實現
* code
```c
void q_free(queue_t *q)
{
/* How about freeing the list elements and the strings? */
/* Free queue structure */
if (q == NULL) {
return;
}
list_ele_t *temp;
while (q->head != NULL) {
temp = q->head;
q->head = q->head->next;
free(temp->value);
free(temp);
}
free(q);
}
```
* 另外透過 valgrind 去檢驗是否有 memory leak
`$valgrind ./qtest`
```
cmd>new
cmd>new
q = []
cmd>it 1
cmd>it 1
q = [1]
cmd>it 2
cmd>it 2
q = [1 2]
cmd>free
cmd>free
q = NULL
cmd>quit
cmd>quit
Freeing queue
```
發現並無 memory leak,代表我們的 q_free 確實把不用的記憶體還給系統
### 完成 q_insert_head
* 如果 q 為 NULL 就代表 q 還沒被 new 出來,這時插入是錯的
* q 如果不為 NULL 就要判斷之後的 malloc 是否都成功,如果不成功就要回傳 NULL,而 value 為 NULL 的處理比較特別,由於 value 為 NULL 時,newh 這個變數已經有空間佔據了,必須要 free 掉他才能回傳 false,避免造成 memory leak
* value 的空間大小跟 s 一樣大,而每個 char 的值則使用 c 語言內建的函式 **strcpy**
* 由於是 LIFO 的形式,所以我們將新創的物件,插在 head 前面
* 插完後增加 q->size 的值,方便 q_size 的計算
* 為了讓操作順序為 ih 先在 it 會使得 it 操作不知道要將物件插在哪裡,所以寫入 q->tail,紀錄 it 操作插入的地方
* code
```c
ool q_insert_head(queue_t *q, char *s)
{
list_ele_t *newh;
/* What should you do if the q is NULL? */
if (q == NULL) {
return false;
}
newh = malloc(sizeof(list_ele_t));
if (newh == NULL) {
return false;
}
/* Don't forget to allocate space for the string and copy it */
/* What if either call to malloc returns NULL? */
newh->value = malloc((strlen(s) + 1) * sizeof(char));
if (newh->value == NULL) {
free(newh);
return false;
}
strcpy(newh->value, s);
if (q->head == NULL) {
q->tail = newh;
}
newh->next = q->head;
q->head = newh;
q->size++;
return true;
}
```
### 完成 q_insert_tail
* 前面的新增的方式跟 q_insert_head 一樣
* 插入的方式由於要實現 FIFO,所以我們要接在 head 之後,用 tail 去紀錄尾巴在哪裡,而 tail 儲存的方式隨著 queue 是否為空而給變
* 第一次插入時 queue 為空的,所以要將 head 指向第一個新建出來的物件,之後 head 指標就不動了,只改動 tail 指標
* 加完物件後,一樣要將 `q->size++` 以記錄現在有幾個 element 在 queue 裡面
* code
```c
bool q_insert_tail(queue_t *q, char *s)
{
list_ele_t *newh;
/* What should you do if the q is NULL? */
if (q == NULL) {
return false;
}
newh = malloc(sizeof(list_ele_t));
if (newh == NULL) {
return false;
}
/* Don't forget to allocate space for the string and copy it */
/* What if either call to malloc returns NULL? */
newh->value = malloc((strlen(s) + 1) * sizeof(char));
if (newh->value == NULL) {
free(newh);
return false;
}
strcpy(newh->value, s);
if (q->head == NULL) {
q->head = newh;
q->tail = newh;
} else {
q->tail->next = newh;
q->tail = newh;
}
q->tail->next = NULL;
q->size++;
return true;
/* You need to write the complete code for this function */
/* Remember: It should operate in O(1) time */
}
```
### q_remove_head
* 判斷是否為 NULL queue 或是為 empty queue,如果是,那就不能移除 head
* 判斷移除的字串是否大於 bufsize,大於時比較麻煩,由於移除的字串空間超出了 sp 所配置的空間,所以要使用 strncpy 來指定要 copy 多少個字元
* **strcpy** 從 src 複製 n 個指定的字串到 dest 字串,有可能複製的這 n 個字串都無 `\0` (null terminator),所以必須要在最後的位子,也就是 `sp[bufsize-1]` 設成 `\0`
* code
```c
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
/* You need to fix up this code. */
if (q == NULL || q->head == NULL) {
return false;
}
if (sp != NULL) {
// because sp could have string not match the sp size,so use strlen
if (bufsize < strlen(q->head->value) + 1) {
strncpy(sp, q->head->value, bufsize);
sp[bufsize - 1] = 0;
} else {
strcpy(sp, q->head->value);
}
}
list_ele_t *temp = q->head;
q->head = q->head->next;
free(temp->value);
free(temp);
--q->size;
return true;
}
```
### 修改 q_size
* 判斷 q 是否為空如果為空就回傳 0
* 如果不為空就回傳 q->size
* code
```c
int q_size(queue_t *q)
{
/* You need to write the code for this function */
/* Remember: It should operate in O(1) time */
if (q == NULL) {
return 0;
}
return q->size;
}
```
### 完成 q_reverse
* [參考資料](http://alrightchiu.github.io/SecondRound/linked-list-xin-zeng-zi-liao-shan-chu-zi-liao-fan-zhuan.html)
* 翻轉 linklist 指標要注意一點,修改指標 next 指向前面時,指向後面的鏈結就會跟著消失了,所以我們必須要使用 preceding 去記錄 next 目前指向誰,記錄下來後才能對 next 做更改,將他更改為我們用 prev 記錄下來的上一個物件
* 範例圖示
![](https://i.imgur.com/X1h9mom.png)
* 另外需要注要 q->tail 的指標要從尾巴指向開頭,所以要在還沒 reverse 前,先將 q->tail 指向 q->head
```c
void q_reverse(queue_t *q)
{
/* You need to write the code for this function */
if (q == NULL || q->head == NULL) {
return;
}
q->tail = q->head;
list_ele_t *prev = NULL;
list_ele_t *curr = q->head;
list_ele_t *preceding = q->head->next;
while (preceding != NULL) {
curr->next = prev;
prev = curr;
curr = preceding;
preceding = preceding->next;
}
curr->next = prev;
q->head = curr;
}
```