# 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 = #
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></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(呱呱)***