yenpo_c
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
      • Invitee
      • No invitee
    • Publish Note

      Publish Note

      Everyone on the web can find and read all notes of this public team.
      Once published, notes can be searched and viewed by anyone online.
      See published notes
      Please check the box to agree to the Community Guidelines.
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Versions and GitHub Sync Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
Invitee
No invitee
Publish Note

Publish Note

Everyone on the web can find and read all notes of this public team.
Once published, notes can be searched and viewed by anyone online.
See published notes
Please check the box to agree to the Community Guidelines.
Engagement control
Commenting
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
  • Everyone
Suggest edit
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
Emoji Reply
Enable
Import from Dropbox Google Drive Gist Clipboard
   owned this note    owned this note      
Published Linked with GitHub
Subscribed
  • Any changes
    Be notified of any changes
  • Mention me
    Be notified of mention me
  • Unsubscribe
Subscribe
# 2018q3 Homework3 (review) ###### tags: `C語言` contributed by < `asd757817` > ## 1. [Week 2 測驗 5](https://hackmd.io/UKpj7o20TLuLqm8t2Erv0g?both#%E6%B8%AC%E9%A9%97-1) 以下程式是合法 C99 程式嗎? ```C #include <stdio.h> int main() { return (********puts)("Hello"); } ``` 參照 C99 規格書解釋: 6.3.2.1 - 4 :::success A function designator is an expression that has function type. Except when it is the operand of the sizeof operator or the unary & operator, a function designator with type “function returning type” is converted to an expression that has type “pointer to function returning type”. ::: 這邊指出 function designator 在進行 sizeof 運算或者是 & 運算以外都會轉換成一個 pointer to a function 的型態。 6.5.3.2 - 4 :::success The unary * operator denotes indirection. If the operand points to a function, the result is a function designator; if it points to an object, the result is an lvalue designating the object. If the operand has type ‘‘pointer to type’’, the result has type ‘‘type’’. If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined. ::: \* 的功用是間接取值,如果取值的對象指向一個 function 則得到的結果為 function designator;而 function designator 除了做 sizeof 跟 & 運算以外,正是指向 function 本身的指標,因此 dereference function designator 一樣是得到 funciton designator。 這一行程式碼雖然有許多 \*,但最終都獲得 function designator 來呼叫 puts 函式,因此程式碼合法。 繼續思考以下是否合法: ```C #include <stdio.h> int main() { return (**&puts)("Hello"); } ``` 6.5.3.2 - 4 註記 84 :::success Thus, &\*E is equivalent to E (even if E is a null pointer), and &(E1[E2]) to ((E1)+(E2)). It is always true that if E is a function designator or an lvalue that is a valid operand of the unary & operator, \*&E is a function designator or an lvalue equal to E. If \*P is an lvalue and T is the name of an object pointer type, \*(T)P is an lvalue that has a type compatible with that to which T points. ::: 從上述內容可知道 ```*&puts```的結果為 function designator(這個情況不會獲得 lvalue),接著再對 function designator 取值,結果一樣是獲得 function designator 並呼叫函式,程式碼合法。 繼續變形: ```C #include <stdio.h> int main() { return (&**&puts)("Hello"); } ``` 同 6.5.3.2 - 4 註記 84 敘述,```&*puts``` 的結果會是 ```puts``` (也就是 function designator)再進行取值,程式碼合法。 ## 2. [Week 3 測驗 4](https://hackmd.io/UKpj7o20TLuLqm8t2Erv0g?both#%E6%B8%AC%E9%A9%97-1) 考慮以下程式碼,在 little-endian 硬體上,會返回 1,反之,在 big-endian 硬體上會返回 0: ```C int main() { union { int a; char b; } c = { .a = K1 }; return c.b == K2; } ``` 補完上述程式碼。 K1 = ? * `(a)` 0 * `(b)` 1 * `(c)` -1 * `(d)` 254 K2 = ? * `(a)` 0 * `(b)` 1 * `(c)` 254 **解題流程** - union 的功用使 a、b 共用同一個記憶體區段,區段長度以長的一方為主 - int 的大小為 4-byte、char 的大小為 1-byte,在這一題共用 int 的 4-byte - little-endian: 從最低位的 byte 開始放入數值 - big-endian: 從最高位的 byte 開始放入數值 當放入 1 的時候記憶體中的狀態: ``` # little-endian 放入1 的情況 # 會在最低位的 byte 開始填入數值 高 0x00 0x00 0x00 0x01 低 # big-endian 放入 1 的情況 # 從最高位的 byte 開始填入數值 高 0x01 0x00 0x00 0x00 低 ``` char b 在記憶體中所使用的區域為黃色部分:0x00 0x00 0x00 ==0x00== 因此若 a 有填入數值但 b 的內容等於 0 就表示這個系統是 Big-endian;反之,若系統是 little-endian 則 b 的內容應為 a 填入的數值。 在 little-endian 的情況要回傳 True ,因此 K2=K1 且兩者不可等於 0 否則無法判斷; 而當 K1 = 254 的時候記憶體狀況為 0x00 0x00 0x00 ==0x11111110==, 這個時候 a 的數值為 254 但是 **b 的數值是 -2**,因為 b 的宣告型態為 char 只佔1個 byte 的空間,且是有號數,因此對於 b 而言最高位的 1 表示數值為負數,因此 K1=K2=254 時,不管系統是 little-endian 還是 big-endian 都會回傳 false。 ### 相關程式 Linux 系統輸入指令```lscpu``` 可以查看許多資訊,其中"Byte Order"就可以顯示當前系統是屬於 Big-endian 還是 Little-endian。 Github: [karelzak/util-linux](https://github.com/karelzak/util-linux/blob/master/sys-utils/lscpu.c) lscpu.c ```C #if !defined(WORDS_BIGENDIAN) add_summary_s(tb, _("Byte Order:"), "Little Endian"); #else add_summary_s(tb, _("Byte Order:"), "Big Endian"); #endif ``` 藉由 WORDS_BIGENDIAN 的內容判斷系統的 byte order 參考另一專案 [google/cityhash](https://github.com/google/cityhash/blob/master/config.h.in) 裡面的註解有對 WORDS_BIGENDIAN 進行解釋。 ```C /* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most significant byte first (like Motorola and SPARC, unlike Intel). */ #if defined AC_APPLE_UNIVERSAL_BUILD # if defined __BIG_ENDIAN__ # define WORDS_BIGENDIAN 1 # endif #else # ifndef WORDS_BIGENDIAN # undef WORDS_BIGENDIAN # endif #endif ``` ## 3. [Week 2 測驗 1](https://hackmd.io/UKpj7o20TLuLqm8t2Erv0g?both#%E6%B8%AC%E9%A9%97-1) 考慮到以下程式碼: ```C const char *p; void dont_do_this(void) { const char c_str[] = "This will change"; p = c_str; } ``` 指出存在的問題和提出修正機制,需要用 C99/C11 規格解釋。 在 dont_do_this 函式內,指標 p 獲得了 c_str 所在的記憶體位置,但因 c_str 為區域變數,生命週期只在函式內,函式結束後 c_str[] 的生命週期也結束,但 p 仍然指向原先 c_str 的位置,若對 p 取值就是一個未定義行為。 C99 規格書 6.2.4 Storage durations of objects - 2 :::success The lifetime of an object is the portion of program execution during which storage is guaranteed to be reserved for it. An object exists, has a constant address and retains its last-stored value throughout its lifetime ==If an object is referred to outside of its lifetime, the behavior is undefined.== The value of a pointer becomes indeterminate when the object it points to reaches the end of its lifetime. ::: 在規格書中雖然有明確指出這是一個未定義的行為,但值得注意的是:**這樣的程式碼在編譯的過程中是可以正常編譯的(可能有提示訊息)**,但實際上避免程式出現未定義行為的責任在於開發者本身;而對於未定義行為執行的動作會依據編譯器而有所不同,但不管編譯器做了什麼樣的動作都是合理的。 相關的 CVE : ## 4. [Week 3 測驗 3](https://hackmd.io/cCa9LSvQSey1R7XgP47Slg?both#%E6%B8%AC%E9%A9%97-3) 以下程式碼計算 parity bit: ```C= #include <stdint.h> int parity(uint32_t x) x ^= x >> 1; x ^= x >> 2; x = (x & 0x11111111U) * 0x11111111U; return (x >> P) & 1; } ``` P = 28 **原理說明:** 原 x 內各 bit 的 parity 情況為: | 32 | 31 |... |2 |1 | | -------- | -------- | -------- | -------- | -------- | | b32 | b31 | ... |b2 | b1 | bn: 表示第 n 位的 parity 情況(1=奇數;0=偶數) 執行第一次 XOR 後,x 內各 bit 的 parity 情況等同於: | 32 | 31 |... |2 |1 | | -------- | -------- | -------- | -------- | -------- | | b32 | b31+b32 | ... |b2+b3 | b1+b2 | 觀察最後一個欄位 b1+b2 ,意思是原本第1位跟第2位相加的情況,因 XOR 運算會符合奇偶相加的情況 ,如: 1^1=0(奇數+奇數=偶數)、1\^0=1(奇數+偶數=奇數)、0\^0=0(偶數+偶數=偶數) 接著執行第二次 XOR 後,x 內各 bit 的 parity 情況為: | 32 | 31 |30 |29 |... |2 |1 | | -------- | -------- |-----------|--------|-------- | --------- | --------- | | b32 | b31+b32 |b30+b31+b32|b29+b30+b31+b32|... |b2+b3+b4+b5|b1+b2+b3+b4| 正確的 parity 應為: b1+b2+...+b32,所以目標是使第1、5、9、...、29 bit 的內容相加(或做 XOR) 執行 ```x = (x & 0x11111111U) * 0x11111111U;```,觀察第29 bit ``` xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx * 00010001 00010001 00010001 00010001 ---------------------------------------- xxx[x]xxxx xxxxxxxx xxxxxxxx xxxxxxxx 第 29 bit xxxx xxx[x]xxxx xxxxxxxx xxxxxxxx xxxx 第 25 bit xxxxxxxx xxx[x]xxxx xxxxxxxx xxxxxxxx 第 21 bit ... ... ... xxxxxxxx xxxxxxx[x] 第 1 bit ``` 以第29 bit 表示 b1+b2+b3+...+b32 的情形,return 需要取得第29 bit,因此要右移28位。 延伸程式碼第5行的概念 ``` x = (x & 0x11111111U) * 0x11111111U;``` 直接獲得各 bit 並進行相加的效率會更高。 ~~SIMD 實作方式尋找中~~ 搜尋了幾份 simd 相關的程式碼,發現主要的概念是以一行指令實現 vector 各位置間的相加,設 v1、v2 都有10個元素,v1 + v2 可以直接完成 v1[0]+v2[0]、v1[1]+v2[1]...,應用 simd 的概念在 parity 檢查上就是一次對多個 bit 進行運算。 :::info :question: 已了解 SIMD 的基本概念,但不知道怎麼將記憶中各 bit 以 SIMD 方式進行運算, 若是以支援 SIMD 的 vector 來儲存各 bit 數值,在轉換過程就耗費了許多時間,沒有改善效率。 ::: ## 5. [Week 3 測驗 4](https://hackmd.io/cCa9LSvQSey1R7XgP47Slg?both#%E6%B8%AC%E9%A9%97-4) 考慮以下程式碼: ```C #include <stdlib.h> #include <string.h> typedef struct rec { char *value; struct rec *next; } record; void insert_sorted(record **r, const char *value) { record *newrec = NULL; while (*r && strcmp(value, (*r)->value) > 0) r = &((F1)->next); newrec = malloc(sizeof(record)); newrec->value = F2(value); newrec->next = F3; F4 = newrec; } ``` **正確程式碼** ```C #include <stdlib.h> #include <string.h> typedef struct rec { char *value; struct rec *next; } record; void insert_sorted(record **r, const char *value) { record *newrec = NULL; while (*r && strcmp(value, (*r)->value) > 0) r = &((*r)->next); newrec = malloc(sizeof(record)); newrec->value = strdup(value); newrec->next = *r; *r = newrec; } ``` ### 程式說明: r 為指標的指標,在一呼叫 ```insert_sorted``` 時,r 會指向**指向第一個節點的指標**,如下圖所示。 對 r 間接取值(\*r)會得到**指向第一個節點的指標** (下圖的 ptr) ```graphviz digraph linked_list{ node[shape=record] r->prt-> rec1:f0; rec1 [label="<f0> node1|<f1> value|<f2> next"]; rec2 [label="<f0> node2|<f1> value|<f2> next"]; rec3 [label="<f0> node3|<f1> value|<f2> next"]; rec4 [label="<f0> node4|<f1> value|<f2> next"]; rec1:f2 -> rec2:f0; rec2:f2 -> rec3:f0; rec3:f2 -> rec4:f0; } ``` 當 ptr 指到的對象 value 小於要插入的值時,r 必須移動到**指向 node2 的指標** 使得 **```*r = node1->next```** 也就是 r 存放的數值為 **```(*r)->next```** 的 address, 程式碼: ```r = &((*r)->next);``` ```graphviz digraph linked_list{ node[shape=record] prt -> rec1:f0; rec1 [label="<f0> node1|<f1> value|<f2> next"]; rec2 [label="<f0> node2|<f1> value|<f2> next"]; rec3 [label="<f0> node3|<f1> value|<f2> next"]; rec4 [label="<f0> node4|<f1> value|<f2> next"]; rec1:f2 -> rec2:f0; rec2:f2 -> rec3:f0; rec3:f2 -> rec4:f0; r -> rec1:f2; } ``` 假設要插入的值位於 node2 跟 node3 間表示 \*r->value 的數值大於要加入的數值,此時 r 的位置如圖, 需要將 node2->next 指向 newrec,newrec->next 指向 node3。 ```graphviz digraph linked_list{ node[shape=record] prt -> rec1:f0; rec1 [label="<f0> node1|<f1> value|<f2> next"]; rec2 [label="<f0> node2|<f1> value|<f2> next"]; rec3 [label="<f0> node3|<f1> value|<f2> next"]; rec4 [label="<f0> node4|<f1> value|<f2> next"]; newrec [label="<f0> newrec|<f1> value|<f2> next"]; rec1:f2 -> rec2:f0; rec2:f2 -> rec3:f0; rec3:f2 -> rec4:f0; r -> rec2:f2; } ``` - 先把 node3 指派給 newrec->next * 以 \*r 取得 node3 的位置並指派給 newrec->next ==(此時的 \*r 等於 node->next)== * ```newrec->next = *r``` * ==作為 rvalue 時 \*r 代表的是指標所指向的地址,也就是 node3 的地址== - 接著將 newrec 指派給 node2->next * ```*r = newrec``` * ==\*r 作為 lvalue 時代表的是要被指派數值的地址==,將 newrec 指派給 \*r 也就是 node->next **Insertion node** - 修改函式型態改為 bool,插入成功回傳 True、失敗回傳 False - 若 r 為 NULL 或 value 為 NULL 回傳 false ```C bool insert_sorted(record **r, const char *value){ /* 若 r 為 NULL 或 value 為 NULL 回傳 false */ if(!r | !value){ return false; } else{ record *newrec = NULL; /* 走訪 list 找出新節點的位置 */ while (*r && strcmp(value, (*r)->value) > 0) r = &((*r)->next); newrec = malloc(sizeof(record)); newrec->value = strdup(value); newrec->next = *r; *r = newrec; return true; } } ``` **Insertion 測試** - 建立一指標指向一個空的 list - 依序加入 c,b,a,z,y,x,g,g,依序走訪 list 觀察結果是否為 abcggxyz 新增 show() 函式,顯示 list 內容 ```C void show(record *r){ printf("List: "); while(r){ printf("%s",r->value); r = r->next; } printf("\n"); } ``` 測試程式: - 測試插入節點的 value 為 NULL 時,是否能正確回傳 false - 測試成功插入節點是否會回傳 ture - 檢查 list 結果是否有正確排序 ```C record *r = NULL; printf("======= Insert Test =======\n"); printf("Insert a NULL value tese : "); if(!insert_sorted(&r,NULL)); printf("Pass wrong parameter\n"); printf("Insert a correct case test : "); if(insert_sorted(&r,"c")) printf("insert complete\n"); insert_sorted(&r,"b"); insert_sorted(&r,"a"); insert_sorted(&r,"z"); insert_sorted(&r,"y"); insert_sorted(&r,"x"); insert_sorted(&r,"g"); insert_sorted(&r,"g"); printf("Expect order is: abcggxyz\n"); show(r); } ``` 輸出結果: ```shell $ gcc -o linked_list linked_list.c && ./linked_list ======= Insert Test ======= ======= Insert Test ======= Insert a NULL value tese : Pass wrong parameter Insert a correct case test : insert complete Expect order is: abcggxyz List: abcggxyz ``` ### Search node - 傳入 list 起點和要查詢的 value - 函式回傳的結果為一個 record - 若查詢 value 在 list 內,回傳該節點 (資料型態為 record) - 若傳入的 list 為 NULL 或要查詢的 value 是 NULL,則回傳 NULL - 若查詢的 value 不在 list 內,回傳 NULL ```C record *search_node(record **r, const char *value){ /* 如果傳入的指標為 NULL 直接回傳 NULL 並提醒*/ if(!*r | !value){ return NULL; } else{ /* 走訪節點,若查詢的 value 小於當前節點則往後尋找 */ while(*r && strcmp(value,(*r)->value)>0) r = &((*r)->next); /* 檢查走訪結果是不是符合搜尋目標,是則回傳該節點的指標 */ if(strcmp((*r)->value,value)==0){ return *r; } /* 走訪結果不符合搜尋結果,回傳 NULL 並提醒使用者 */ else{ return NULL; } } } ``` 測試程式: 延續先前測試程式,目前 list 內容為:abcggxyz - 新增一個 null list ,測試傳入 NULL list 是否得到 NULL - 測試 value 為 NULL 時是否得到 NULL - 測試成功搜尋是否可以得到該節點(顯示得到的節點的 value) - 測試搜尋目標不在 list 內是否得到 NULL ```C record *null_ptr = NULL; printf("======= Search Test =======\n"); record *search; printf("Search a NULL list tese : "); if(search = search_node(&null_ptr,"b")) printf("%s\n", search->value); else printf("You can't search node in a null list ! \n"); printf("Search a NULL value tese : "); if(search = search_node(&r,NULL)) printf("%s\n", search->value); else printf("You can't search a NULL value !\n"); printf("Correct case test :"); if(search = search_node(&r,"b")) printf("Find %s !!\n", search->value); else printf("Can't find the node \n"); printf("Search a value that is not in the list : "); if(search = search_node(&r,"q")) printf("%s\n", search->value); else printf("Can't find the node \n"); ``` 測試結果: ```shell $ gcc -o linked_list linked_list.c && ./linked_list ======= Search Test ======= Search a NULL list tese : You can't search node in a null list ! Search a NULL value tese : You can't search a NULL value ! Correct case test :Find b !! Search a value that is not in the list : Can't find the node ``` ### Remove node - 函式型態為 bool,刪除成功回傳 true、失敗回傳 false - 傳入 list 起點與要刪除的節點 value - 若 list 起點是 NULL 或 value 是 NULL 則回傳 false - 刪除節點後需要把原本節點配置的空間釋放 程式碼 ```C bool remove_node(record **r , const char *value){ /* 若 list 起點是 NULL 或 value 是 NULL 則回傳 false */ if(!*r | !value ){ return false; } else{ /* 走訪 list 尋找要刪除的節點 */ while(*r && strcmp(value,(*r)->value)>0) r = &((*r)->next); /* 檢查是否找到要刪除的節點 */ if(strcmp((*r)->value,value)==0){ record *to_be_removed = *r; *r = (*r)->next; // 修正 list 連接的情況 free(to_be_removed); // 釋放記憶體 return true; } else return false; } } ``` 測試程式 ==延續先前所有測試內容== - 測試從一個 NULL list 刪除節點 - 測試從 list 刪除一個 value 為 NULL 的節點 - 測試可以正確刪除時是否回傳 ture - 測試 value 不在 list 中是否會回傳 false - 顯示 list 內容 ```C printf("\n======= Remove Test =======\n"); printf("Remove node from a NULL list : "); if(remove_node(&null_ptr,"a")) printf("Remove node completed !\n"); else printf("Can not remove node from a NULL list !\n"); printf("Remove NULL value test :"); if(remove_node(&r,NULL)) printf("Remove node completed !\n"); else printf("Can not remove NULL value !\n"); printf("Remove a correct case : "); if(remove_node(&r,"b")) printf("Remove node completed !\n"); else printf("Remove node failed !\n"); printf("Remove a value that is not in list : "); if(remove_node(&r,"q")) printf("Can not remove a value that is not in the list !\n"); else printf("Removed node completed !\n"); show(r); printf("Remove \"a\" from list\n"); remove_node(&r,"a"); show(r); ``` 測試結果 ```shell $ gcc -o linked_list linked_list.c && ./linked_list ======= Remove Test ======= Remove node from a NULL list : Can not remove node from a NULL list ! Remove NULL value test :Can not remove NULL value ! Remove a correct case : Remove node completed ! Remove a value that is not in list : Removed node completed ! List: acggxyz Remove "a" from list List: cggxyz ``` 以 AddressSanitizer 觀察 remove 是否有正確釋放記憶體 將上述程式```remove(&r,"a")```一行註解後記憶體配置的情況的 ```shell $ gcc -o linked_list linked_list.c -fsanitize=address -g && ./linked_list ...中間省略... SUMMARY: AddressSanitizer: 128 byte(s) leaked in 15 allocation(s). ``` 上述程式呼叫 ```remove(&r,"a")``` 後的記憶體配置情況 ```shell $ gcc -o linked_list linked_list.c -fsanitize=address -g && ./linked_list ...中間省略... SUMMARY: AddressSanitizer: 112 byte(s) leaked in 14 allocation(s). ``` 可以發現配置出的空間減少了一個,表示 remove 有正確釋放記憶體。 完整測試結果 ```shell ======= Insert Test ======= Insert a NULL value tese : Pass wrong parameter Insert a correct case test : insert complete Expect order is: abcggxyz List: abcggxyz ======= Search Test ======= Search a NULL list tese : You can't search node in a null list ! Search a NULL value tese : You can't search a NULL value ! Correct case test :Find b !! Search a value that is not in the list : Can't find the node ======= Remove Test ======= Remove node from a NULL list : Can not remove node from a NULL list ! Remove NULL value test :Can not remove NULL value ! Remove a correct case : Remove node completed ! Remove a value that is not in list : Removed node completed ! List: acggxyz Remove "a" from list List: cggxyz ``` 補充: - 在程式中我規定節點的 value 不得為 NULL,不過實際上為 NULL 也可以。

Import from clipboard

Advanced permission required

Your current role can only read. Ask the system administrator to acquire write and comment permission.

This team is disabled

Sorry, this team is disabled. You can't edit this note.

This note is locked

Sorry, only owner can edit this note.

Reach the limit

Sorry, you've reached the max length this note can be.
Please reduce the content or divide it to more notes, thank you!

Import from Gist

Import from Snippet

or

Export to Snippet

Are you sure?

Do you really want to delete this note?
All users will lose their connection.

Create a note from template

Create a note from template

Oops...
This template is not available.
Upgrade
All
  • All
  • Team
No template found.

Create custom template

Upgrade

Delete template

Do you really want to delete this template?
Turn this template into a regular note and keep its content, versions, and comments.

This page need refresh

You have an incompatible client version.
Refresh to update.
New version available!
See releases notes here
Refresh to enjoy new features.
Your user state has changed.
Refresh to load new user state.

Sign in

Forgot password

or

By clicking below, you agree to our terms of service.

Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
Wallet ( )
Connect another wallet

New to HackMD? Sign up

Help

  • English
  • 中文
  • Français
  • Deutsch
  • 日本語
  • Español
  • Català
  • Ελληνικά
  • Português
  • italiano
  • Türkçe
  • Русский
  • Nederlands
  • hrvatski jezik
  • język polski
  • Українська
  • हिन्दी
  • svenska
  • Esperanto
  • dansk

Documents

Help & Tutorial

How to use Book mode

How to use Slide mode

API Docs

Edit in VSCode

Install browser extension

Get in Touch

Feedback

Discord

Send us email

Resources

Releases

Pricing

Blog

Policy

Terms

Privacy

Cheatsheet

Syntax Example Reference
# Header Header 基本排版
- Unordered List
  • Unordered List
1. Ordered List
  1. Ordered List
- [ ] Todo List
  • Todo List
> Blockquote
Blockquote
**Bold font** Bold font
*Italics font* Italics font
~~Strikethrough~~ Strikethrough
19^th^ 19th
H~2~O H2O
++Inserted text++ Inserted text
==Marked text== Marked text
[link text](https:// "title") Link
![image alt](https:// "title") Image
`Code` Code 在筆記中貼入程式碼
```javascript
var i = 0;
```
var i = 0;
:smile: :smile: Emoji list
{%youtube youtube_id %} Externals
$L^aT_eX$ LaTeX
:::info
This is a alert area.
:::

This is a alert area.

Versions and GitHub Sync
Upgrade to Prime Plan

  • Edit version name
  • Delete

revision author avatar     named on  

More Less

No updates to save
Compare
    Choose a version
    No search result
    Version not found
Sign in to link this note to GitHub
Learn more
This note is not linked with GitHub
 

Feedback

Submission failed, please try again

Thanks for your support.

On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

Please give us some advice and help us improve HackMD.

 

Thanks for your feedback

Remove version name

Do you want to remove this version name and description?

Transfer ownership

Transfer to
    Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

      Link with GitHub

      Please authorize HackMD on GitHub
      • Please sign in to GitHub and install the HackMD app on your GitHub repo.
      • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
      Learn more  Sign in to GitHub

      Push the note to GitHub Push to GitHub Pull a file from GitHub

        Authorize again
       

      Choose which file to push to

      Select repo
      Refresh Authorize more repos
      Select branch
      Select file
      Select branch
      Choose version(s) to push
      • Save a new version and push
      • Choose from existing versions
      Include title and tags
      Available push count

      Upgrade

      Pull from GitHub

       
      File from GitHub
      File from HackMD

      GitHub Link Settings

      File linked

      Linked by
      File path
      Last synced branch
      Available push count

      Upgrade

      Danger Zone

      Unlink
      You will no longer receive notification when GitHub file changes after unlink.

      Syncing

      Push failed

      Push successfully