kyle shanks
  • NEW!
    NEW!  Connect Ideas Across Notes
    Save time and share insights. With Paragraph Citation, you can quote others’ work with source info built in. If someone cites your note, you’ll see a card showing where it’s used—bringing notes closer together.
    Got it
      • 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
      • Invite by email
        Invitee

        This note has no invitees

      • Publish Note

        Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

        Your note will be visible on your profile and discoverable by anyone.
        Your note is now live.
        This note is visible on your profile and discoverable online.
        Everyone on the web can find and read all notes of this public team.

        Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

        Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

        Explore these features while you wait
        Complete general settings
        Bookmark and like published notes
        Write a few more notes
        Complete general settings
        Write a few more notes
        See published notes
        Unpublish note
        Please check the box to agree to the Community Guidelines.
        View profile
      • 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
      • Note Insights New
      • Engagement control
      • Make a copy
      • 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 Note Insights Versions and GitHub Sync Sharing URL Create Help
    Create Create new note Create a note from template
    Menu
    Options
    Engagement control Make a copy 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
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    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
    2
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    --- title: '指標 & Array & typedef' disqus: kyleAlien --- 指標 & Array & typedef === ## OverView of Content 指標對於底層系統開發來說相當重要,而驅動又是透過控制 Register 來控制硬體,在這操控中就常常使用到指標 :::success * 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/) 本篇文章對應的是 [**指標 & Array & typedef | 指標應用的關鍵 9 點 | 指標應用、細節**](https://devtechascendancy.com/pointers-arrays-const-typedef-sizeof-null/) ::: [TOC] ## 指標概念 * **普通變數**:首先我們要知道一般的讀寫都不會涉及 **強制轉換**,哪種類型的變數,就會以相對應的格式存在 RAM 中 ```c= int a = 10; long b = (long) a; ``` > ![](https://i.imgur.com/Od8sces.png) * **指標變數**: 1. 可以把指標當成是另一種類型的宣告 2. 指標的內容:跟普通變數一樣,是儲存某一個數值,只是 **指標是儲存一個地址** (使用關鍵字 `&` 來取得某個變數的地址) ```c= int a = 10; // 儲存 10 int* p = &a; // 儲存 a 的地址, p 本身也有地址 ``` :::info 指標本身也有地址 ::: > ![](https://i.imgur.com/jpg9Pmi.png) ### 指標特性 - 首地址 * 內存的大小:這取決於 **尋址總線數量**,如果尋址總現有 32 條,那地址最大可到 2^32^;相對來說,如果有 64 條,那地址最大可到 2^64^ * 由於 **在電腦中,硬體是以 1 Byte 作為單元切割**,所以當一個指標拿到一個類型的首地址,就會自動順延到符合該類型的長度內容 ```c= int a = 10; // 取得 a 的第一個 Byte 的首地址,自動往後推 3 Byte // 最終以 4 Byte 作為該變量的空間 int* p = &a; // -------------------------------------------------------- struct hello_t { int a; int b; int c; }; struct hello_t hello_world = {0}; // 取得 hello_world 的第一個 Byte 的首地址,自動往後推 11 Byte (符合類型) // 最終以 12 Byte 作為該變量空間的地址 struct hello_t* hw = &hello_world; ``` :::info * 指標可以透過類型推導出接下來需要幾個 Byte 的數據 * 一個地址能儲存的大小就為 1 個 Byte (跟硬體有關) > ![](https://i.imgur.com/18R4g8Z.png) ::: ### 指標級量 * 指標可以指向另外一個指標,層層疊加,這就是指標的級量 ```c= int a = 10; // 一級指標 int* b = &a; // 二級指標 int** c = &b; // 三級指標 int*** d = &c; ``` > (n + 1 級) = &(n 級) :::danger 建議指標不要超過 3 級,不僅降低了可讀性,效率也會不好 ::: ### 指標其他作用 - 作用域 * 一般我們在規範函數不讓其它文件訪問時會使用 static,但是只要透過指標就可以取得該函數並執行 `static` 關鍵字在 C 語言中相當於「存取限制符號」,使用 `static` 描述的函數、屬性,只能在該檔案中被訪問,其他檔案不可訪問! ```c= // 只有該檔案內部可訪問 static void hello() { } // 只要有 include 的檔案都可以使用 void world() { } ``` ## 指標使用 ### 指標符號 * **`*` 符號**: 1. **宣告指標變量使用 ,前後都可** ```c= int *p; // 同上 int* p; // ------------------------------------------------ int *p1, *p2; // 兩個指標變量 int *p1, p2; // 一個指標、一個整數變量 ``` 2. **解引用** ```c= int a = 10; int *p = &a; // 解引用,並賦予值 *a = 20; ``` * **`&` 符號**: 取變量的第一個 Byte 的地址 ```c= int a = 10; int *p = &a; ``` ### 野指標 * 只要指標可能出現未知性錯誤,它就是一個野指標;可能產生野指標的操作如下 :::warning * `Segmentation Fault`:程式為了防止雪崩性錯誤,會使用 `Segmentation Fault` 停止該應用程式;而錯誤又分為兩種 * 大段錯誤:地址不存在 * 小段錯誤:地址存在,但訪問受限 ::: 1. 尚未初始化就直接使用 ```c= void wild_ptr() { char* p; *p += 1; // Segment Error } ``` 2. 不清楚空間權限,試圖訪問、修改資料 ```c= void wild_ptr_2() { char *p = "hello"; // "hello" 放置在常量區,常量區不許須改 *(p + 1) = 'w'; // Segment Error } ``` 3. 越界訪問 ```c= void wild_ptr_3() { int buf[4] = {0}; *(buf + 4) = 10; // + 4 已越界 } ``` :::info * `*(buf + 4)` 與 `*buf + 4` 是不同的,這有關於符號的優先度 1. `*(buf + 4)` 是 buf 這個地址加上 4 個 Byte 2. `*buf + 4` 是數組第一個數 buf[0] 再加 4 ::: ### What is Null * Null 在 C/C++ 中是不同定義的存在,C++ 中被定義為 0,但是在 C 中定義為 `(void*) 0`,會被嚴格檢查 ```c= #ifdef _cplusplus #define NULL 0 #else #define NULL (void*) 0 #endif ``` ## const 修飾符 const 也就是 constant 代表不變,用來修飾變量,**希望變量轉為常量** ### const 修飾普通變數 * 修飾變數,不管 const 是在前還是在後都可以,只要保證在變數之前即可 1. 類型之前 ```c= #include <stdio.h> int main() { const int a = 10; a = 30; // const 不可修改,所以編譯會錯 printf("Hello: %d\n", a); return 0; } ``` > ![](https://i.imgur.com/2uttY4B.png) 2. 類型之後 ```c= #include <stdio.h> int main() { // 不同之處 int const a = 10; a = 30; // const 不可修改,所以編譯會錯 printf("Hello: %d\n", a); return 0; } ``` ### const 修飾指標 * **const 修飾指標有三種表現方式**,個代表了不同的限制(注意 `const` 放置的位置) 1. **const 修飾指標指向的空間**:及說明 **該空間的內容物為常量**;修飾指標指向的空間常量有 2 種表達方式,代表的意思是相同 ```c= void const_ptr_1() { int tmp = 20; int const *a = &tmp; // const int *a = &tmp; // 同上 *a = 100; // Read-only 編譯器檢查錯誤,不可修改 ! printf("Hello: %d\n", *a); } ``` > ![](https://i.imgur.com/KdR7AHX.png) 2. **const 修飾指標**:使用 const 修飾指標說明 **該指標指向不可在修改**,也就是 **指標無法再指去其他地方**,但其 **值能是可被修改** ```c= void const_ptr_2() { int tmp = 20; int * const a = &tmp; *a = 100; // OK int tmp2 = 30; a = &tmp2; // Read-only 編譯器檢查錯誤,不可修改 ! printf("Hello: %d\n", *a); } ``` > ![](https://i.imgur.com/BYRFhmT.png) 3. **const 修飾指標 & 修飾指向空間**:代表指標 & 其指向的內容物都不可以修改 ```c= void const_ptr_3() { int tmp = 20; int const * const a = &tmp; // const int * const a = &tmp; // 同上 *a = 100; // Read-only 編譯器檢查錯誤,不可修改 ! int tmp2 = 30; a = &tmp2; // Read-only 編譯器檢查錯誤,不可修改 ! printf("Hello: %d\n", *a); } ``` > ![](https://i.imgur.com/773OIQw.png) ### 指標修改 const * **const 機制是通過編譯器檢查實現**,實際上真正運行的過程中並不關心變數是否被 const 修飾,只要保證編譯通過,程式仍可跳過 const 檢查 ```c= void const_ptr_4() { const int a = 100; int *p = NULL; p = &a; // 會有警告而已 *p = 300; printf("Hello: %d\n", a); // 可修改 a 的值 } ``` :::success * 既然可以被修改,那為何要使用 const 修飾? 讓程式更加健壯,提醒使用者某修地方不能被修改,或是保證不會被修改 ::: ### const & 變量 & 常量 * 在程式中我們常會使用 `1`、`2`、`3`、`"HELLO"`... 等等數值,而這些數值在編譯器的處理下會以兩種方式存在 RAM 中 1. **變量** 經過編譯後,會將 **變量放置在 `.data`、`.bss` 中,常出現在 `堆`、`棧` 中**,這些變量都是 **可讀可寫**;經過編譯檢查 const 關鍵字,可將這些數值看做 **偽常量** > 真正的常量不可修改,而偽常量 其實仍可修改 :::info Linux 可使用 `readelf` 來查看編譯出來的執行檔中的 `.data`、`.bss` 區塊 ::: 2. **常量** 經過編譯後,會將 **變量放置在 `.ro.data` 中,訪問權限為 可讀 (不可改)** ```c= // p 儲存首字 `H` 的地址,而 "Hello const" 則是放置在常量區 char *p = "Hello const"; // 如果透過指標修改這個變量,則會失敗 (Segmention Fault) ``` ## Array 深刻了解一維數組,是了解二維(甚至多維)數組的關鍵 ```c= // 格式如下 <類型> <變量名>[<數量>] int a[100]; // 0 ~ 100 long b[1] // 0 ~ 1 ``` :::info 以內存(記憶體)的角度來看,Array 的物理記憶體是連續的,並不會斷開,所以訪問速度也快 ::: ### Array 訪問 1. **變量名訪問**:最基礎的訪問方式就是透過變量名稱來訪問 ```c= void accessByName() { int a[10] = {0}; a[0] = 100; printf("a[0]: %d\n", a[0]); a[10] = 9; // 越界,但仍可正常設定值 printf("a[10]: %d\n", a[10]); } ``` > ![](https://i.imgur.com/Y5O4WQ0.png) 2. **指標訪問**:**不受到編譯器的 作用域檢查 規範** ```c= void accessByPtr() { int a[10] = {0}; int *p = a; // a 本身就是一個地址,加上了 `[]` 才能解析其內容 *p = 100; printf("a[0]: %d\n", a[0]); *(p + 10) = 9; printf("a[10]: %d\n", a[10]); } ``` > ![](https://i.imgur.com/CjakUPF.png) ### 一維數組 & 符號 * 數組的符號有 4 種不同的意思(而部分其中還有細分,說明如下):^1^ `a`、^2^ `a[0]`、^3^ `&a[0]`、^4^ `&a` ```c= int a[10] = {0}; ``` 1. **Array 符號 `a`**:有兩種含意 * Array 名稱:`sizeof(a)` 時,可以計算出該數組占用幾個 byte 大小 * Array 的第一個地址:等同於 `&a[0]`,是 **數組的首元素的首個字節**,是一個常量值 :::info * 如果是地址,那代表了是常量不可修改,所以永遠不會是左值 ```c= int a[10] = {0}; a = 1000; // a 是常量,不可為左值 (被賦予) ``` ::: 2. **Array 符號 `a[0]`**:取第一個元素的空間,並可以對其讀寫操作 ```c= int a[10] = {0}; printf("a[0]: %d\n", a[0]); // read a[0] = 1000; // write ``` 3. **Array 符號 `&a[0]`**:取締一個元素的首位元空間地址,就等同於符號 `a` 4. **Array 符號 `&a`**:數組首地址,代表一個地址常量,同樣不可為左值 :::info * **符號 `&a`、`a` 的差異 ?** 兩者的數值皆是 Array 的首地址,但是 **==意義完全不同==**;`&a` 代表該空間的全體,而 `a` 只代表了該空間的 1 個元素 ```c= void symbleTest2() { int a[5] = {0}; printf("a: %p, &a: %p\n\n", a, &a); printf("a+1: %p, &a+1: %p\n", a+1, &a+1); } ``` * `&a+1` 代表地址前進 `int a[5]` * `a+1` 代表地址前進 1 個 `int` > ![](https://i.imgur.com/yzw6Uhm.png) ::: ### 指標 & Array * 一般訪問 Array 是透過 index 來指定要訪問 Array 的第幾個元素 ```c= void iterate_array() { int array[10] = {0}; for(int i = 0; i < sizeof(array)/sizeof(array[0]); i++) { // array[i] 使用 index 取元素 printf("array[%d]: %d\n", i, array[i]); } } ``` > ![](https://i.imgur.com/ur5JGek.png) * **Array 的 ==變數名本身就有指標的意義==**,所以可以透過指標來訪問,這指標也分為兩中 ^1.^ 常量指標 (不可修改)、^2.^ 變量指標 (可修改) 1. **常量指標**:常量指標其實就是代表 Array 宣告的變數名 (Symble),該變數不可再修改,它是一個常量值 ! ```c= void ptr_with_array_1() { int array[10] = {0}; for(int i = 0; i < sizeof(array)/sizeof(array[0]); i++) { // 使用 `array + i` 改變常量指標 array printf("array[%d]: %d\n", i, *(array + i)); } } ``` :::danger * **可否修改成 `*(array++)` ?** **不行!因為 Array 宣告出的變數名是一個常量,既然是常量就不可以修改 !** > ![](https://i.imgur.com/2wIcJzW.png) ::: 2. **變量指標**: ```c= void ptr_with_array_2() { int array[10] = {0}; int *p = array; for(int i = 0; i < sizeof(array)/sizeof(array[0]); i++) { // 如果是變數,就可以修改為 *(p++),以下還有幾種方案,都可以達到相同的效果 // *(p++) 可以寫作 // 1. p[i] // 2. *(p + 1) // 3. *(p + 1 * sizeof(int)) printf("array[%d]: %d\n", i, *(p++)); } } ``` :::danger * 以下寫法是錯誤的 ```c= int array[10] = {0}; // 錯誤! array 原本就是首地址的第一個 Byte,語意變成了 // array 首地址的首地址 p = &array; ``` ::: * 使用指標位置 + 1,編譯器會依照指標的大小,新增一個單元 ```c= int array[10] = {0} int *p = array; int a = *(p + 1); // (p + 1) 相等於 (p + 1 *sizeof(int)) ``` ## 指標類型 & 強制轉換 對於編譯器來說,數據類型就是告訴編譯器該變數,要已什麼樣的格式儲存 (數據結構),儲存的空間又是多大 ? 1. **儲存空間**:依照類型、硬體裝置,編譯器會給予不同變量,不同空間大小 ```c= sizeof(char); // 1 Byte sizeof(short); // 2 Byte sizeof(int); // 4 Byte sizeof(float); // 4 Byte sizeof(double); // 8 Byte ``` 2. **儲存結構**:即便是相同大小的 **`int`、`float` 儲存格式也不同** > float 是用科學計數法形式儲存 (其中就包括:小數、指數、符號... 等等) ```c= int a = 10; printf("%d", a); // 正確 printf("%f", a); // 亂碼 // -------------------------------------------- float b = 10; printf("%d", b); // 亂碼 printf("%f", b); // 正確 ``` ### 一般類型強制轉換 - 顯示 * 強制轉換一般類型,需要注意幾個點:**`空間`、`結構`** * **空間大小改變** 1. **小轉大**:沒啥問題,資料可以正常轉換 ```c= void small_to_big() { short a = 10; int b = (int)a; printf("b value: %d, size: %d", b, sizeof b); } ``` > ![](https://i.imgur.com/hLd5r4j.png) 2. **大轉小**:**小心數據丟失、改變**!(並非一定會丟失,但是很大機率會將數據解釋錯) ```c= void big_to_small() { int a = 0x0000ffff; printf("a value: %d, size: %d\n", a, sizeof a); short b = (short)a; printf("b value: %d, size: %d", b, sizeof b); } ``` > ![](https://i.imgur.com/ZTJGt9g.png) * **結構改變**:將原儲存資料方式就不同的結構強制轉型,像是浮點數(浮點數的儲存結構很不同)與整數的轉換 1. 整數轉浮點數,**數據不丟失** ```c= void change_struct_1() { int a = 100; // 轉換儲存結構 float b = (float) a; printf("b value: %f, size: %d", b, sizeof b); } ``` > ![](https://i.imgur.com/Z5oiMu8.png) 2. 浮點數轉整數,**數據丟失** ```c= void change_struct_2() { float a = 3.1415926; // 丟失小數部分數據 int b = (int)a; printf("b value: %d, size: %d", b, sizeof b); } ``` > ![](https://i.imgur.com/kFmEkhJ.png) ### 一般類型強制轉換 - 隱示 * 上面使用 `(<類型>)` 來做強制轉換,而隱式轉換則如下 1. **使用 `=` 符號的隱示轉換**: ```c= void implict_change_1() { char c = 0x11223344; printf("c value: %d, size: %d", c, sizeof c); } ``` > ![](https://i.imgur.com/KVaKN3j.png) :::info * 這種隱示轉換不安全,通常會有編譯器發出警告(會警告,但並不代表有問題) > ![](https://i.imgur.com/e1j6IPu.png) ::: 2. **當使用 `return` 關鍵字,在返回之前隱示轉換**: ```c= int implict_change_2() { char c = 0x11223344; return c; } ``` :::info * 這種 `return` 隱示轉換不安全,通常會有編譯器發出警告(會警告,但並不代表有問題) > ![](https://i.imgur.com/R8OBcx9.png) ::: ### 指標強制轉換 * 指標轉換「最好使用」顯示轉換,盡量不要使用隱式轉換;指標轉換也涉及兩個層面:^1.^ 指標類型轉換、^2.^ 指標指向類型 1. **指標類型轉換**:改變數據的解析方式 ```c= void ptr_value_change_1() { int a = 100; int *pa = &a; printf("a ptr value: %d, size: %d\n", *pa, sizeof(*pa)); float *pb = NULL; // 指標類型轉換 pb = (float*) pa; printf("b ptr value: %f, size: %d", *pb, sizeof(*pb)); } ``` > ![](https://i.imgur.com/TWaa9aY.png) :::warning * **結構的解析方式不同,導致數據解析異常** 從這邊可以看出,改變指標的類型,使用 `*` 也會改變對於該結構的解析方式;上面改變指標為 `float*` 導致解析時不以 `int` 的結構來解析數據內容 ::: 2. **指標指向類型**(不改變指標,改變解指標後的數據) ```c= void ptr_value_change_2() { int a = 0; float b = 3.1415926; int *pa = &a; float *pb = &b; *pa = (int)*pb; printf("a ptr value: %d, size: %d", *pa, sizeof(*pa)); } ``` > ![](https://i.imgur.com/ryhoQRy.png) ## sizeof `sizeof` 看似 Function,但其實它是 C 語言中的 **運算符號 !** ### sizeof vs. array ```c= char array[] = "Hello"; ``` | sizeof 計算 | 結果 | 說明 | 注意 | | -------- | -------- | -------- | - | | sizeof(array) | 6 | 前面有說過 array 符號用在 **`sizeof` 會計算該 Array 所有的空間** | 包括 `\0` (字符算的結尾) | | sizeof(array[0]) | 1 | 一個 array 元素大小 | | | strlen(array) | 5 | 使用 C 標準函式庫 | 會解析到 `\0` 為止 (不包含),所以結果會少 1 | * 測試 1:測試 Array 首指標、Array 元素、C 標準庫的 `strlen` 測試 ```c= #include <stdio.h> #include <stdlib.h> void sizeof_vs_array() { char array[] = "Hello"; printf("sizeof(array): %d\n", sizeof(array)); printf("sizeof(array[0]): %d\n", sizeof(array[0])); printf("strlen: %d\n", strlen(array)); } ``` > ![](https://i.imgur.com/VRviSfv.png) * 測試 2:`sizeof` 配合 Array 首元素、首地址、C 標準庫的 `strlen` 測試 ```c= #include <stdio.h> #include <stdlib.h> void test_ptr_strlen() { char str[] = "Hello"; char* p = str; printf("sizeof(*p): %d, sizeof(p): %d, strlen(p): %d\n", sizeof(*p), sizeof(p), strlen(p)); // 會計算 *p 到 '\0' 之間的位元數 } ``` > ![](https://i.imgur.com/IeHCn4Z.png) ### sizeof vs. 指標 ```c= char array[] = "Hello"; ``` | sizeof 計算 | 結果 | 說明 | 注意 | | -------- | -------- | -------- | - | | sizeof\(p) | 4 | **指標 p 的大小** | sizeof 會根據類型判別大小,array 才會計算整體 | | sizeof(\*p) | 1 | 一個 char 大小 | | | strlen(p\) | 5 | 使用 C 標準函式庫 | 會解析到 `\0` 為止 (不包含) | * 測試:指標、指標取得的元素、C 標準庫的 `strlen` 測試 ```c= void sizeof_vs_ptr() { char array[] = "Hello"; char *p = array; printf("sizeof(p): %d\n", sizeof(p)); printf("sizeof(*p): %d\n", sizeof(*p)); printf("strlen(p): %d\n", strlen(p)); } ``` > ![](https://i.imgur.com/Insfmgb.png) ### array 作為參數傳遞 * C 語言由於 **考慮到 `Stack` 大小的關係** (入參會導致棧溢出),設計在 **傳遞 Array 時,傳入的入參是 `Array address` 而不是整體** ```c= void transfer_array(int array[20]) { printf("inner sizeof(array): %d\n", sizeof(array)); } void template_array() { int array[20] = {0}; printf("outsize sizeof(array): %d\n", sizeof(array)); transfer_array(array); } ``` > ![](https://i.imgur.com/y5iZXJ5.png) * 知道 Function 傳入的是 `Array address` 後,其實可以修改如下(將接收的類型改為 `pointers`),也會有一樣的功能 ```c= void transfer_array_2(int *array) { for(int i = 0; i < 20; i++) { printf("Value: %d\n", *(array + i)); } } void template_array() { int array[20] = {0}; printf("outsize sizeof(array): %d\n", sizeof(array)); transfer_array_2(array); } ``` > ![](https://i.imgur.com/mbJ2KJc.png) :::success * 那要決定用哪一種,要選宣告接收 Pointer 還是 Array? 對於程式的「**可讀性**」來說,我們還是會 **選擇使用 `a[10]` 這種寫法**,因為 **這能明確標示請使用者傳入的是一個數組,而不是一個普通數值的 Ptr** ::: * 使用 Array 類型的另類寫法:使用 Array 前,宣告大小變數,這個變數就可以讓 Array 變數使用 ```c= void transfer_array_3(int size, int array[size]) { for(int i = 0; i < size; i++) { printf("Value: %d\n", *(array + i)); } } void template_array() { int array[20] = {0}; printf("outsize sizeof(array): %d\n", sizeof(array)); transfer_array_3(5, array); } ``` :::warning * **`size` 參數必須定義在 array 之前**! 否則編譯檢查不能通過 > ![](https://i.imgur.com/URN3eX7.png) ::: > ![](https://i.imgur.com/b2OSUwY.png) ## 高級指標 其實沒啥高級指標,應該說是指標比較高級的用法 ### 指標數組 & 數組指標 * C 語言語法的重點是:**++前面是修飾詞++,++後面才是主語++** (使用中文來看時) * 變數 - **指標數組**:代表該變數,主語是 Array,並且修飾(類型)是 Ptr * 變數 - **數組指標**:代表該變數,主語是 Ptr,並且修飾(類型)是 Array | 類型 | 主語 (本質) | 修飾 | 舉例 | | -------- | -------- | -------- | - | | 指標 數組 | 數組 (Array) | 指標 (Ptr) | `int *p[5];` (本質是 Array,每個元素都為 `(int*)`,代表 5 個指標) | | 數組 指標 | 指標 (Ptr) | 數組 (Array) | `int (*p)[5]` (本質是 1 指標,指向 `int[5]` 的空間) | :::info * 有趣的是,英文會把變數名稱的修飾語放置在後面,前面是名稱(主語),跟這裡的概念相反過來 像是 `revenueAverage`:在英文中可以拆解為 `revenue` 是名稱、`Average` 修飾 ::: * **`*`、`[]` 在符號的前後,指明了該變量是指標還是數組 !(這還有關於到優先級)** 定義一個符號時思考的步驟如下 1. **找到定義的符號,誰是「核心」** ```c= // `p` 是核心 // `int`、`*`、`[]` 都是為了定義 p int *p[6]; ``` 2. **看誰跟核心最近,誰的結合優先度 (優先級) 高,就先與之結合**;以下嘗試讓核心與不同的符號結合 ```c= // 以下分號 `;` 不結合 // 核心 a,跟 [] 結合,所以是數組 int a[5]; // 核心 p,跟 * 結合,所以是數組 int *p; // 核心 function,跟 `()` 結合,所以是函數 int function(); ``` * 以下列出幾個常見的優先度,從上到下代表優先度的高到低 | 運算符號 | 描述 | 結合性 | | -------- | -------- | -------- | | () | 函數呼叫 | | | [] | Array 引用 | 先於 `*` 符號結合 | | -> | 指標指向成員 | 左到右 | | . | Struct 成員引用 | | | -/+ | 負號、正號 | | | ++/-- | 遞增、遞減 | | | ! | 邏輯否 | | | ~ | 1 的補數 | 右到左 | | * | 指標引用 | | | & | 記憶體位置 | | | sizeof | 計算物件 Byte 大小 | | | (type) | 強制轉型 | | | * | 乘法 | | | / | 除法 | 左到右 | | % | 取模 | | | +/- | 加、減 | 左到右 | :::success 符號的建立、分析,都先從基礎去分析,沒有無緣無故的規則 ! ::: * 以下範例是「指標函數」、「函數指標」正確的使用方式 ```c= // 指標數組 void major_array() { int *p[5]; // 主體是 Array(5 個指標) // int array[5] = {0}; // p = &array; // Error *(p + 0) = 1; // 可用指標的方式訪問 Array *(p + 1) = 2; *(p + 2) = 3; *(p + 3) = 4; *(p + 4) = 5; printf("p: %p, p+1: %p\n", p, (p+1)); } // 數組指標 (主要用在二維數組) void major_ptr() { int (*p)[5]; // 主體是指標(1 個指標) int array[5] = {0}; p = &array; (*p)[0] = 1; // 可用 Array 的方式訪問訪問指標指向的元素 (*p)[1] = 2; (*p)[2] = 3; (*p)[3] = 4; (*p)[4] = 5; printf("p: %p, p+1: %p\n", p, (p+1)); } ``` > ![](https://i.imgur.com/2QlR1tP.png) ### 函數指標 - function pointer * 首先要知道,**函數指標也是一個 ==指標==**,與其它指標並無不同; :::info * **函數(`Function`)到底是什麼**? **函數本值一段程式**,在經過編譯器編譯成匯編碼後,**載入到記憶體 (RAM) 中,是一段連續記憶體**,**而 ==函數指標就是該記憶體的第一個地址==**! ```c= // 偽程式,以下記憶體地址都是假的 // 下面這段程式載入 RAM 中佔用了 0x11221100 ~ 0x11221140 的連續空間 // 函數指標則是 0x11221100 int function_hello() { // 0x11221100 char str[] = "Hello"; // 0x11221110 char* p = str; // 0x11221120 printf("sizeof(*p): %d, sizeof(p): %d, strlen(p): %d\n", // 0x11221130 sizeof(*p), sizeof(p), strlen(p)); } // 0x11221140 ``` ::: * 我們可以看到 `數組指標` 的類型是 `<數組類型> (*)[]`;而 `函數指標` 也是,主要是指標所以它的類型是 `<回傳類>(*)(接收參數)` ```c= #include <stdio.h> void test_hello(void) { printf("Hello Function\n"); } int main(void) { // pFunc 是一個指標 void (*pFunc)(void); // pFunc = &test_hello; // 同上 pFunc = test_hello; pFunc(); // 加上 `()` 代表調用函數 return 0; } ``` > ![](https://i.imgur.com/j5xRmnw.png) ### typedef & 函數指標 * **typedef 這個關鍵字是用來定義新類型**,其實我們上面看到的都是自定義類型 * `數組指標`:像是 `int (*p)[5]` ```c= #include <stdio.h> typedef int (*IntArrayPointer)[5]; int main() { int arr[5] = {1, 2, 3, 4, 5}; IntArrayPointer p = &arr; for (int i = 0; i < 5; ++i) { printf("%d ", (*p)[i]); } return 0; } ``` * `指標數組`:像是 `int *p[5]` ```c= #include <stdio.h> typedef int* IntPointerArray[5]; int main() { int arr[5] = {1, 2, 3, 4, 5}; IntPointerArray p; for (int i = 0; i < 5; ++i) { p[i] = &arr[i]; printf("%d ", *p[i]); } return 0; } ``` * `函數指標`:像是 `void (*p)(int)` ```c= #include <stdio.h> typedef void (*FunctionPointer)(int); void printNumber(int num) { printf("Number: %d\n", num); } int main() { FunctionPointer p = printNumber; p(42); // 調用函數指標 return 0; } ``` :::info * typedef 定義出的新類型並不占用 RAM ::: ### 二重指標 * `二重指標` 可以存放 `一重指標` 的地址 ```c= #include <stdio.h> int main() { int num = 42; int *ptr = &num; // 一重指標指向變數 num int **doublePtr = &ptr; // 二重指標指向一重指標 ptr printf("Value of num: %d\n", num); printf("Value through ptr: %d\n", *ptr); printf("Value through doublePtr: %d\n", **doublePtr); return 0; } ``` * `二重指標` 可以指向 指標數組 (`*p[]`):二重指標也就是 **用來儲存 `指標數組` 的第一個元素的指標變量** | 指標 | 儲存 | | -------- | -------- | | 一重 int* | `int` 的 addr | | 二重 int** | `int*` 的 addr | ```c= #include <stdio.h> int main() { int num1 = 1, num2 = 2, num3 = 3; int *arr[] = {&num1, &num2, &num3}; // 指標數組 int **doublePtr = arr; // 二重指標指向指標數組的第一個元素 for (int i = 0; i < 3; ++i) { printf("Value through doublePtr[%d]: %d\n", i, *doublePtr[i]); } return 0; } ``` > ![image](https://hackmd.io/_uploads/SyWC5xWjp.png) ## typedef 1. **typedef 用來定義新類型,形式越複雜 typedef 的優勢則越明顯** 2. **typedef 的另一個優點是 ==方便移植==** :::danger * **typedef 是一個儲存類的關鍵字**,而 **變量只能被一種儲存類的關鍵字修飾** > 其他儲存類的關鍵字:`auto`、`extern`、`static`、`register` ```c= typedef static int ClzNum[10]; // 編譯錯誤 ``` 錯誤如下 > ![](https://i.imgur.com/rVv18Y5.png) ::: ### typedef 看法解析 1. typedef 是給類型取別名,所以 typedef 定義的東西都是類型;所以 **typedef 定義中一定會有一個 `類型`** 2. typedef 定義出的類型:**移除定義中 typedef 關鍵字**,再將 **類型看作變量**,就能知道它的類型 :::info * 從這裡可以發現將 typedef 關鍵字 移除後,它就只是一個普通的變量語句 ::: * 數組類型 ```c= // MyClass 就是類型 typedef int MyClass[5]; // 去除 typedef 關鍵字 int MyClass[5]; // 再將 MyClass 看成變量 int <變量>[]; // 數組類型 ``` * 函數指標類型 ```c= // MyFunc 就是類型 typedef int* (MyFunc*)(int); // 去除 typedef 關鍵字 int* (MyFunc*)(int); // 再將 MyFunc 看成變量 // MyFunc 變量是一個指標 // MyFunc 是函數指標(因為後面是 `()`) // MyFunc 該函數指標回傳一個 int* int* (<變量>*)(int); ``` ### define & typedef | 使用 | 功能 | 編譯時機 | | -------- | -------- | -------- | | define | 簡單 **宏替換** | **==預處理==** | | typedef | **重新定義類型** | **==編譯期==** | * **`define` & `typedef` 的區別**: 1. **typedef 不是簡單替換,而是 typedef 對類型重新定義** ```c= #define dpInt int* // 不可加 `;` 號 typedef int* tpInt; void typedef_define_diff_1() { // int* dp1, dp2; // 同下 dpInt dp1, dp2; // int* tp1, *tp2; // 同下 tpInt tp1, tp2; int a = 20; tp1 = tp2 = dp1 = &a; dp2 = a; printf("tp1: %d\n", *tp1); printf("tp2: %d\n", *tp2); printf("dp1: %d\n", *dp1); printf("dp2: %d\n", dp2); // 這是一個 int 類型 } ``` 2. **#define 可以實現類型組合,而 typedef 不行**:define 過的仍可再使用其他關鍵字修飾,而 typedef 不行 ```c= #define dInt int typedef int tInt; void typedef_define_diff_2() { unsigned dInt c1; // unsigned tInt c2; // 不可再組合 } ``` > ![](https://i.imgur.com/OqYQ8vk.png) 3. **define 無法創建新類型** ```c= // 定義新類型 (新類型為 Class) typedef char Class[10]; void typdef_create_new_definition() { Class clz; for(int i = 0; i < sizeof(clz)/sizeof(clz[0]); i++) { *(clz + i) = i * 3; printf("index: %d, value: %d\n", i, *(clz + i)); } } ``` ### typedef & struct * struct 結構最簡單的定義如下 ```c= struct Node {}; ``` * `struct` 配合使用上 `typedef` 有以下幾種情況 1. 串上 `typedef` 可省去 `struct` 關鍵字 ```c= typedef struct MyNode {} Node_T; void my_node() { Node_T node; } ``` 2. **定義兩個類型**:一個結構類型,另一個結構指標類型 ```c= // 定義等同於 // typedef <類型> Node_T2; // typedef <類型> *pNode_T2; typedef struct MyNode_2 {} Node_T2, *pNode_T2; void my_node_2() { Node_T2 node; pNode_T2 pNode; } ``` ### typedef & const * 我們知道 `const` 是如何修飾指標的,有分為 3 個種類 1. `const int* p`、`const int* p`:修飾指標指向內容不可改 2. `int* const p`:修飾指標指向不可改 3. `const int* const p`:內容、指向都不可改 * 如果以上功能要配合 `typedef` 使用 1. **`const` 修飾新類型變量**:指向不可修改 ```c= typedef int* pInt; void const_typedef() { int apply = 10; int bannana = 5; const pInt p = &apply; // 等同於 `int* const p` // pInt const p = &apply; // 同上 // p = &bannana; // read-only, Error 編譯錯誤 printf("p value: %d\n", *p); } ``` > ![](https://i.imgur.com/vTpMgpA.png) 2. **`const` 修飾新類型宣告**:修飾內容不可修改 ```c= void const_typedef_2() { short apply = 10; pShort p = &apply; printf("initialize p value: %d\n", *p); short bannana = 5; p = &bannana; // *p = bannana; // read-only, Error 編譯錯誤 printf("After change p value: %d\n", *p); } ``` > ![](https://i.imgur.com/gETpQqR.png) 3. **`const` 修飾內容、指向皆不可改** ```c= typedef const long* pLong; void const_typedef_3() { long apply = 200; const pLong p = &apply; printf("initialize p value: %d\n", *p); short bannana = 103; p = &bannana; p = bannana; printf("After change p value: %d\n", *p); } ``` > ![](https://i.imgur.com/VRCoRqH.png) ### typedef & 函數指標 * 我們就分析一個比較複雜的函數指標,以下兩個是相等意思 1. **函數指標原型** ```c= void printTest(int count) { for(int i = 0; i < count; i++) { printf("Hello: %d\n", i); } } // --------------------------------------------------------- // 1. 首先知道 a[10] 是一個指標數組 (主體是數組 // 2. 之後接上 `()` 代表是一個函數,得知外層是一個函數指標 // 3. 該函數指標返回 void、接收 `void(*)(int)` 函數指標 void (*a[10]) (void(*)(int)); void func_ptr_1() { a[0] = printTest; // 指定函數指標 a[0](5); // 呼叫函數 } ``` 2. **`typedef` 改寫上面的範例** ```c= // 功能完全同上 void printTest(int count) { for(int i = 0; i < count; i++) { printf("Hello: %d\n", i); } } // 宣告一個新類型 pFunc (函數指標) typedef void (*pFunc)(void(*)(int)); // 定義一個 Array 的 pFunc pFunc pFuncArray[10]; void func_ptr_2() { pFuncArray[0] = printTest; pFuncArray[0](10); } ``` > ![](https://i.imgur.com/lwbAIOr.png) ### typedef & sizeof * `typedef` 在使用 `sizeof` 要注意**一定要括號**,否則會報錯誤 1. 正常可以測量出 size 是 8 byte 2. 兩者配合使用沒有括號,會拋出錯誤 `error: expected expression` ```c= typedef struct { char a; short b; int c; } Test_T; int main() { //"1. " printf("%d\n", sizeof (Test_T) ); //"2. " printf("%d\n", sizeof Test_T); return 0; } ``` ## 二維 Array ```c= // 二維 Array int a[2][5]; // [2] 代表一維,[5] 代表二維,可解釋成 2 個 [5] 的空間 ``` > ![](https://i.imgur.com/iez7j5q.png) ### 二維 Array 的首地址 * 在一維 Array 中我們可以知道,一維 Array 的符號,等價於 `&a[0]` 的地址 ```c= void One_dimen_array_head() { int a[6]; if(a == &a[0]) { printf("Same"); } else { printf("Different"); } } ``` > ![](https://i.imgur.com/XDB06ec.png) * 推斷可得知二維的符號,等價於 `&(&a[0])[0]` 的地址 ```c= void Two_dimen_array_head() { int a[6][6]; if(a == &(&a[0])[0]) { printf("Same on a == &&a[0][0]"); } else { printf("Different"); } } ``` > ![](https://i.imgur.com/gh1HCIT.png) ### 訪問 二維 Array * 使用普通 Pointer 訪問 ```c= void visit_by_ptr() { int array[6][6] = {0}; array[0][0] = -1; array[0][1] = 10; array[0][2] = 7; array[1][0] = -3; array[1][1] = 100; array[1][2] = 97; int *p1 = array[0]; // 指向第一行的第一個元素 int *p2 = array[1]; // 指向第二行的第一個元素 printf("array[0][0]: %d\n", *p1); printf("array[0][1]: %d\n", *(p1 + 1)); printf("array[0][2]: %d\n", *(p1 + 2)); // 證明 Array 是連續空間,其實可以用一個 ptr 訪問全部二維 Array printf("array[1][0]: %d\n", *(p1 + 6)); printf("array[1][1]: %d\n", *(p2 + 1)); printf("array[1][2]: %d\n", *(p2 + 2)); } ``` > ![](https://i.imgur.com/An97YyK.png) * 使用 **陣列指標** 訪問 ```c= void visit_by_ptr_array() { int array[6][6] = {0}; array[0][0] = -1; array[0][1] = 10; array[0][2] = 7; array[1][1] = 100; array[1][2] = 97; int (*p)[6] = array; // 指向第一個元素 printf("array[0][0]: %d\n", *(*p)); printf("array[0][1]: %d\n", *(*p + 1)); // *p 是第一個地址 printf("array[0][2]: %d\n", *(*p + 2)); printf("array[1][1]: %d\n", *(*(p + 1) + 1)); printf("array[1][2]: %d\n", *(*(p + 1) + 2)); } ``` > ![](https://i.imgur.com/SWVm3hP.png) :::info 1. `a[i][j]` 對於陣列指標來說,等同於 `*( *(p + i) + j)` 2. 上面宣告的陣列指標 `int (*p)[6]`,其中的 **`[6]` 並不能亂定義,必須要與二維數組的數量相同才可以** ! ::: ## 更多的 C 語言相關文章 關於 C 語言的應用、研究其實涉及的層面也很廣闊,但主要是有關於到系統層面的應用(所以 C 語言又稱之為系統語言),為了避免文章過長導致混淆重點,所以將文章係分成如下章節來幫助讀者更好地從不同的層面去學習 C 語言 ### C 語言基礎 * **C 語言基礎**:有關於到 C 語言的「語言基礎、細節」 :::info * [**理解C語言中的位元操作:位元運算基礎與宏定義**](https://devtechascendancy.com/bitwise-operations-and-macros-in-c/) * [**C 語言解析:void 意義、NULL 意義 | main 函數調用、函數返回值意義 | 臨時變量的產生**](https://devtechascendancy.com/meaning_void_null_return-value_temp-vars/) * [**C 語言中的 Struct 定義、初始化 | 對齊、大小端 | Union、Enum**](https://devtechascendancy.com/c-struct_alignment_endianness_union_enum/) * [**C 語言儲存類別、作用域 | 修飾語、生命週期 | 連結屬性**](https://devtechascendancy.com/c-storage-scope-modifiers-lifecycle-linkage/) * [**指標 & Array & typedef | 指標應用的關鍵 9 點 | 指標應用、細節**](https://devtechascendancy.com/pointers-arrays-const-typedef-sizeof-null/) ::: ### 編譯器、系統開念 * **編譯器、系統開念**:是學習完 C 語言的基礎(或是有一定的程度)之後,從編譯器以及系統的角度重新檢視 C 語言的一些細節 :::warning * [**理解電腦記憶體管理 | 深入瞭解記憶體 | C 語言程式與記憶體**](https://devtechascendancy.com/computer-memory_manager-c-explained/) * [**C 語言記憶體區塊規劃 | Segment 段 | 字符串特性**](https://devtechascendancy.com/c-memory-segmentation-string-properties/) * [**編譯器的角度看程式 | 低階與高階、作業系統、編譯器、直譯器、預處理 | C語言函數探討**](https://devtechascendancy.com/compiler-programming-os-c-functions/) ::: ### C 語言與系統開發 * **C 語言與系統開發**:在這裡會說明 C 語言的實際應用,以及系統為 C 語言所提供的一些函數、庫... 等等工具,看它們是如何實現、應用 :::danger * [**了解 C 語言函式庫 | 靜態、動態函式庫 | 使用與編譯 | Library 庫知識**](https://devtechascendancy.com/understanding-c-library-static-dynamic/) * [**Linux 宏拓展 | offsetof、container_of 宏、鏈表 | 使用與分析**](https://devtechascendancy.com/linux-macro_offsetof_containerof_list/) ::: ## Appendix & FAQ :::info ::: ###### tags: `C`

    Import from clipboard

    Paste your markdown or webpage here...

    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 has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a 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
    Sign in via Google Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    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

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    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
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    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

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

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

        Syncing

        Push failed

        Push successfully