---
# System prepended metadata

title: C Language / Pointer
tags: [C Language]

---

# 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（呱呱）***