# C Language / Pointer **指標也算是一種變數,只是裡面存的不是一般的「數字」,而是記憶體位址。** ## 序章 / 我們都活在指標的世界裡呀 大家有聽過「指標暴政」這一名詞嗎,這是一個來自社會學的觀點,例如:考試成績、學歷換算年收入、IG追蹤 + 貼文按讚數、體重...等等,甚至連天生的臉部都可以被打上一個名為顏質的分數。我們恨不得將一天發揮得彷彿擁有 25 個小時,努力向前奔跑追求所有用工具理性定義出的度量衡;用分數替代思考後的結果就是把自己搞得遍體鱗傷,無法區分哪些指標早已腐朽失效、哪些社會價值觀早已出了 bug。雖然這並不意味著我想叫大家直接荒廢學業、不去追求在金錢上更加安逸的生活,但我既然有這個機會,我想提醒大家比學會 C Pointer 更加重要的事情:「這個世界一定會不斷地用各種指標來把你變成一部機器,但請不要忘了我們本都是人類;追求目標的路上,要記得偷偷地雕刻自我、雕刻理想與信仰,相信那些無法測量的價值與熱愛,真正擁有一個指標,指向快樂、指向內心,指向一個永遠喜歡自己的理由。」 ## Ch01 / 指標的宣告 & 記憶體位址 ### 1. 指標的 **「資料型態」**,是在一般資料型態後加 **" * "** **e.g.** ```c= int num; //16bits整數 int *ptr; //指標 ``` ### 2. 記憶體位址符: &address 當在存記憶體位址時,電腦都是用16進位(hexadecimal, hex)去存取ㄉ ```c= int num = 100; int *ptr = NULL; ptr = &num; printf("numㄉ記憶體位置: %x\n", &num); printf("ptr包含ㄉ記憶體位置: %x\n", ptr); printf("numㄉ值: %d\n", num); printf("ptr指向ㄉ值: %d", *ptr); ``` **Output:** ::: success **num ㄉ記憶體位置: 7fbff6d4 ptr 包含ㄉ記憶體位置: 7fbff6d4 num ㄉ值: 100 ptr 指向ㄉ值: 100** ::: <kbd>![ptr](https://hackmd.io/_uploads/HkSUEJcmR.jpg)</kbd> * **「&」 記憶體位址 (取址)** * **「*」取出內容 (取值),也稱作間接運算子** ### 3. 指標ㄉ變數運算 ```c= int num1 = 5; int num2; int *ptr = NULL; ptr = &num1; // 將指標指向num1記憶體位址 num2 = *ptr + 2; // num2 變成 7 (5+2) num2 += *ptr; // num2 變成 12(7+5) *ptr = num2; //*ptr 從5變成12 (*ptr)++; //*ptr變成13(12++) ``` --- ## Ch02 / 指標陣列 ### 1. 陣列也是一個指標,指向陣列中第一個數,ptr = &arr[0] ```c= int arr[5] = {10, 20, 30, 40, 50}; int *ptr = NULL; ptr = arr; // ptr = &arr[0] for (int i = 0; i < 5; ++i) { printf("%d ", *(ptr + i)); } ``` **Output:** ::: success **10 20 30 40 50** ::: ### 2. 使用算術運算子,改變陣列的記憶體位址。 ```c= int arr[5] = {10, 20, 30, 40, 50}; int *ptr = NULL; ptr = arr; //陣列也是一個指標,指向陣列中第一個數,ptr = &arr[0] printf("%d %x\n", *ptr, ptr); // 10, ptr++; printf("%d %x\n", *ptr, ptr); // 20 ptr += 3; printf("%d %x\n", *ptr, ptr); // 50 ptr--; printf("%d %x\n", *ptr, ptr); // 40 ptr -= 2; printf("%d %x\n", *ptr, ptr); // 20 ``` **Output:** :::success **10 b05ffa10 20 b05ffa14 50 b05ffa20 40 b05ffa1c 20 b05ffa14** ::: > **note: 可以注意到記憶體位址的變化,是4個4個的跳,原因是int的佔4bytes記憶體位址。當變成double時,記憶體位址會8bytes的跳。** ## Ch03 / 利用指標呼叫函式 ### 1. 兩數交換 ```c= void swap(int *num1, int *num2); //先告訴電腦說我有一個函式 //將傳入參數宣告為指標型態int* int main() { int a = 10; int b = 25; printf("a is %d, b is %d\n", a, b); swap(&a, &b); // 傳入記憶體位址,讓指標有地方指 printf("a is %d, b is %d\n", a, b); return 0; } void swap(int *num1, int *num2) { int temp; temp = *num1; *num1 = *num2; *num2 = temp; //這個交換法,是直接將記憶體位址交換! } ``` **Output:** :::success **a is 10, b is 25 a is 25, b is 10** ::: ### 2. 利用指標,將陣列傳入函數 #### 前情提要: 陣列在C語言中構成的方式 ```c int arr[5] = {10, 20, 30, 40, 50}; //arr = &a[0]; ``` **在C語言中,陣列本身就是一種指標,而陣列中的下一個element會接續上一個element的記憶體位址。這就是為什麼Ch02的最後一個範例記憶體位址會遵循固定的increment,This is so fxxking important,拜託一定要記得。** ### 範例一: 加總陣列中的值 ```c= int total = 0; int sum(int *arr, int element); // 先告訴電腦說我有一個函式 int main() { int arr[5] = {10, 20, 30, 40, 50}; total = sum(arr, 5); // 將arr(指標),也就是&arr[0]傳入函式 printf("Total is %d", total); return 0; } int sum(int *arr, int element) // *arr代表傳入陣列的第一個數值 { for (int i = 0; i < element; ++i) { total += arr[i]; } return (total); } ``` **Output:** :::success **Total is 150** ::: ### 範例二: 令陣列的回傳值是指標 ```c= #include <stdio.h> int *generate_even_arr(); // 先告訴電腦說我有一個函式 int main() { int *arr; //宣告arr指標 arr = generate_even_arr(); //arr收到資料型態為指標的回傳值 for (int i = 0; i < 5; ++i) { printf("%d ", arr[i]); } return 0; } int *generate_even_arr() //回傳值是指標 { static int num[5]; //宣告num陣列為全域靜態,分配記憶體 int even = 0; for (int i = 0; i < 5; i++) { num[i] = even; //陣列num[i]等同於*(num+i),賦值 even += 2; } //產生 arr[5] = {0, 2, 4, 6, 8} return (num); //&num[0] //回傳陣列的第一項回main(),反正記憶體位址已經被分配 } ``` **Output:** :::success **0 2 4 6 8** ::: ## Ch04 / Keyword const 在C語言中,const被用來宣告 **「變數的值在初始化後即不能被修改」**。在多線程環境 or 處理需要保持不變的數據時,const對於保證數據的完整性和安全性非常有用。下面就來介紹一下幾種常見用法ㄅ: By the way,請特別注意一下「固定」這個用詞的位置。 ### 1. 固定變數 ```c= const int x = 10; ``` 在這個例子中,x 被宣告為一個整數變數,其值為10。一旦初始化後,x 的值就不能被改變ㄌ。如果嘗試修改 x,編譯器會報錯。 **e.g.** ```c= int main() { const int x = 10; x = 20; printf("%d\n", x); return 0; } ``` **Output:** :::danger ```c ch01.c: In function 'main': ch01.c:6:7: error: assignment of read-only variable 'x' 6 | x = 20; | ^ ``` ::: ### 2. 固定指向變數的指標 ```c= const int *ptr; int val = 10; ptr = &val; //可以改變指標指向 *ptr = 20; //編譯報錯:無法通過指標修改值 ``` 在這個例子中,**ptr 是一個指向 const int 的指標。** 這意味著我們無法修改它所指向的值ㄌ,但是可以修改 ptr 指向的位址。 **e.g.** ```c= int main() { const int *ptr; int val = 10; ptr = &val; // 可以改變指針指向 *ptr = 20; // 編譯報錯:無法通過指標修改值 printf("%d\n", *ptr); return 0; } ``` **Output:** :::danger ```c ch01.c: In function 'main': ch01.c:8:10: error: assignment of read-only location '*ptr' 8 | *ptr = 20; // 編譯報錯:無法通過指標修改值 | ^ ``` ::: ### 3.固定指標 ```c= int val = 10; int *const ptr; ptr = &val; //編譯錯誤:無法改變指標指向 *ptr = 30; //可以修改指向的值 ``` 在這個例子中,**ptr 是一個常量指標。** 這意味著我們無法修改它所指向的地址ㄌ,但是可以修改 *ptr。 **e.g._1** ```c= int main() { int val = 10; int *const ptr = &val; printf("Before modification: %d\n", *ptr); *ptr = 30; //可以修改指向的值 printf("After modification: %d\n", *ptr); return 0; } ``` **Output:** :::success **Before modification: 10 After modification: 30** ::: **e.g._2** ```c= int main() { int val_1 = 10; int val_2 = 20; int *const ptr = &val_1; printf("Before modification: %d\n", *ptr); ptr = &val_2; //編譯錯誤:無法改變指標指向 printf("After modification: %d\n", *ptr); return 0; } ``` **Output:** :::danger ``` ch01.c: In function 'main': ch01.c:11:9: error: assignment of read-only variable 'ptr' 11 | ptr = &val_2; | ^ ``` ::: ### 4. 指向固定變數的常量指標 在這個例子中,ptr 是一個指向固定變數的常量指標。這意味著既不能改變指針指向的地址,也不能通過指針修改它所指向的值。 **e.g.** ```c= int main() { int temp = 20; int val_1 = 10; int *val_2 = &temp; const int *const ptr = &val_1; ptr = &val_2; //編譯錯誤:無法改變指標指向 *ptr = val_2; //編譯錯誤:無法通過指標修改值 return 0; } ``` **Output:** :::danger ``` ch02.c: In function 'main': ch02.c:11:9: error: assignment of read-only variable 'ptr' 11 | ptr = &val_2; //編譯錯誤:無法改變指標指向 | ^ ch02.c:12:10: error: assignment of read-only location '*(const int *)ptr' 12 | *ptr = val_2; //編譯錯誤:無法通過指標修改值 | ^ ``` ::: ### 5. 在函數參數中的使用const 在這個例子中,const被用於修飾函數建構子,以保證函數內不會修改傳入的陣列。 ```c= void printArray(const int *array, int size) { for (int i = 0; i < size; i++) { printf("%d ", array[i]); } } ``` ## Ch05 / 小魔王觀念 - 二級指標 二級指標(也稱為指向指標的指標),這種結構通常被用在**動態多維陣列** 或 **指向多個陣列的指標**。 ### 1. 宣告 ```c int **hand; ``` 這行宣告了一個指向 int 型別指標的指標 hand。 ### 2. 分配記憶體 假設我們要分配記憶體來存儲**四副手牌,每副手牌有五張牌**,那麼我們會需要分配記憶體給 hand 本身,以及它指向的每個指標。 ```c= hand = (int **)malloc(4 * sizeof(int *)); //memories for 4 int * for (int i = 0; i < 4; i++) { hand[i] = (int *)malloc(5 * sizeof(int)); } // for every int * pointer, there're 5 memories awaiting int ``` line1: 為hand 分配可以指向4個 int * 指標的記憶體 line2~4: 迴圈為每個 int * 指標分配了可以容納 5 個 int 的記憶體 #### Keyword malloc / Memory_Allocate - malloc 函數被用來為 hands[i] 分配記憶體。 - 在上述範例中,malloc 會分配一塊可以容納 5 個 int 型別大小的連續記憶體塊 - sizeof(int) 是用來計算一個 int 型別的大小,5 * sizeof(int) 則是總共需要分配的位元組數。 - malloc 返回的是一個 void* 型別的指標,這裡使用 (int *) 將它轉換為 int* 型別,以便將其賦值給 hands[i]。 ### 3. 使用 困難的部分結束ㄌ,接下來就像使用二維陣列一樣使用 hand 吧~ ```c= hand[0][0] = 1; hand[0][1] = 2; // ... hand[3][4] = 20; ``` ### 4. 釋放記憶體 使用完記憶體後,要記得釋放分配的記憶體以防止記憶體洩漏唷,就像是上完廁所要記得沖馬桶一樣(? ```c= for (int i = 0; i < 4; i++) { free(hand[i]); } //迴圈釋放每個 int * 指標指向的記憶體 free(hand); //最後釋放 hand 自己指向的記憶體 ``` 總結來說,int **hand 是一個二級指標,用來管理動態分配的多維陣列或多個指標。 ## Ch06 / 在應用層中的指標範例 ### 1. 資工系的共同惡夢 - 發手牌(一人五張,共四人) ```c= #include <stdio.h> #include <stdlib.h> #include <time.h> // Function prototypes void shuffle(int deck[][13]); void deal(const int deck[][13], int **hand, int numPlayers); void printHand(const int *hand, const char *suit[], const char *face[], int numCards); int main() { const char *suit[] = {"Hearts", "Diamonds", "Clubs", "Spades"}; const char *face[] = {"Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King"}; int deck[4][13] = {{0}}; srand(time(NULL)); shuffle(deck); // Allocate memory for hands int *hands[4]; for (int i = 0; i < 4; i++) { hands[i] = (int *)malloc(5 * sizeof(int)); } // Deal cards to players deal(deck, hands, 4); // Print for (int i = 0; i < 4; i++) { printf("Player %d's hand:\n", i + 1); printHand(hands[i], suit, face, 5); printf("_\n"); } return 0; } // Shuffle the deck void shuffle(int deck[][13]) { for (int card = 1; card <= 52; card++) { int row, col; do { row = rand() % 4; col = rand() % 13; } while (deck[row][col] != 0); deck[row][col] = card; } } // Deal cards to players void deal(const int deck[][13], int **hand, int numPlayers) { int card = 0; for (int i = 0; i < numPlayers * 5; i++) { for (int row = 0; row < 4; row++) { for (int col = 0; col < 13; col++) { if (deck[row][col] == i + 1) { hand[i / 5][i % 5] = row * 13 + col; card++; break; } } } } } // Print a player's hand void printHand(const int *hand, const char *suit[], const char *face[], int numCards) { for (int i = 0; i < numCards; i++) { int card = hand[i]; int row = card / 13; int col = card % 13; printf("%s of %s\n", face[col], suit[row]); } } ``` ### 2. 檢測手牌(是否包含一對、兩對、三條、鐵支) ```c= #include <stdio.h> #include <stdlib.h> #include <time.h> // Function prototypes void shuffle(int deck[][13]); void deal(const int deck[][13], int **hand, int numPlayers); void printHand(const int *hand, const char *suit[], const char *face[], int numCards); int containsPair(const int *hand, int numCards); int containsThreeOfAKind(const int *hand, int numCards); int containsFourOfAKind(const int *hand, int numCards); int main() { const char *suit[] = {"Hearts", "Diamonds", "Clubs", "Spades"}; const char *face[] = {"Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King"}; int deck[4][13] = {{0}}; srand(time(NULL)); shuffle(deck); // Allocate memory for hands int *hands[4]; for (int i = 0; i < 4; i++) { hands[i] = (int *)malloc(5 * sizeof(int)); } deal(deck, hands, 4); // Print hands and analyze for (int i = 0; i < 1; i++) { printf("Player's hand:\n"); printHand(hands[i], suit, face, 5); printf("\n"); int hasPairs = containsPair(hands[i], 5); int hasThreeOfAKind = containsThreeOfAKind(hands[i], 5); int hasFourOfAKind = containsFourOfAKind(hands[i], 5); printf("Hand analysis:\n"); printf("Contains pair: %s\n", hasPairs ? "Yes" : "No"); printf("Contains two pairs: %s\n", (hasPairs == 2) ? "Yes" : "No"); printf("Contains three of a kind: %s\n", hasThreeOfAKind ? "Yes" : "No"); printf("Contains four of a kind: %s\n", hasFourOfAKind ? "Yes" : "No"); printf("\n"); } for (int i = 0; i < 4; i++) { free(hands[i]); } return 0; } void shuffle(int deck[][13]) { for (int card = 1; card <= 52; card++) { int row, col; do { row = rand() % 4; col = rand() % 13; } while (deck[row][col] != 0); deck[row][col] = card; } } void deal(const int deck[][13], int **hand, int numPlayers) { int card = 0; for (int i = 0; i < numPlayers * 5; i++) { for (int row = 0; row < 4; row++) { for (int col = 0; col < 13; col++) { if (deck[row][col] == i + 1) { hand[i / 5][i % 5] = row * 13 + col; card++; break; } } } } } void printHand(const int *hand, const char *suit[], const char *face[], int numCards) { for (int i = 0; i < numCards; i++) { int card = hand[i]; int row = card / 13; int col = card % 13; printf("%s of %s\n", face[col], suit[row]); } } int containsPair(const int *hand, int numCards) { int counts[13] = {0}; int numPairs = 0; int pairs_count = 0; for (int i = 0; i < numCards; i++) { counts[hand[i] % 13]++; } for (int i = 0; i < 13; i++) { if (counts[i] == 2) { pairs_count++; } } return pairs_count; } int containsThreeOfAKind(const int *hand, int numCards) { int counts[13] = {0}; for (int i = 0; i < numCards; i++) { counts[hand[i] % 13]++; } for (int i = 0; i < 13; i++) { if (counts[i] == 3) { return 1; } } return 0; } int containsFourOfAKind(const int *hand, int numCards) { int counts[13] = {0}; for (int i = 0; i < numCards; i++) { counts[hand[i] % 13]++; } for (int i = 0; i < 13; i++) { if (counts[i] == 4) { return 1; } } return 0; } ``` ### 3. 看呱呱心情隨時新增範例⛏️ --- *註: 部分內容截自於網路,此筆記非完全原創。* ***Latest Updated On:2024.05.29, published with the of LICENSE of WTFPL Author:Qaron(呱呱)***