---
# System prepended metadata

title: Lec.2 指標

---

# Lec.2 **指標**
[TOC]

<br>

## **用法**

### **指標變數 vs. 一般變數**
* 一般變數：值代表資料
* 指標變數(pointer variable)：值代表另一個變數的記憶體位址
<br>

### **宣告**
* 宣告變數時在變數名稱前加＂\*＂，就代表這是一個指標變數。
* ＂\*＂前面的資料類別就是知個指標變數能指到的變數類別。
    * ex. 指向整數的指標變數就不能指向浮點數


<br>

> 範例一&emsp;宣告一個指向整數的指標變數
```c=
int *iptr;

//ptr常用作於當指標變數的變數名稱，iptr常用於當指向整數的指標變數名稱(int-pointer)
//此iptr不能指向除了整數以外的資料類別
```
<br>

> 範例二&emsp;指向浮點數的指標變數
```c=
float *fptr; //float-pointer
double *dfptr; // double-float-pointer
```
:::info 
* 可以利用sizeof(ptr)來看看指標所佔的位元組數
> :::spoiler <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">👉</font></font> 點開看程式碼 <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">👈</font></font>
> ```c=
> #include <stdio.h>
> 
> int main(void){
>     int *iptr; //integer pointer
>     int *fptr; //float pointer
>     int *dfptr;  //double pointer
>     printf("sizeof(iptr) = %d\n", sizeof(iptr));
>     printf("sizeof(fptr) = %d\n", sizeof(fptr));
>     printf("sizeof(dfptr) = %d\n", sizeof(dfptr));
>     return 0;
> }
> ```
> :::

> :::spoiler OUTPUT
> ```
> sizeof(iptr) = 8
> sizeof(fptr) = 8
> sizeof(dfptr) = 8
> ```
> :::
* 跟一般變數所佔的位元組數比較看看
> :::spoiler <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">👉</font></font> 點開看程式碼 <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">👈</font></font>
> ```c=
> #include <stdio.h>
> 
> int main(void){
>     int i;
>     float f;
>     double d;
>     printf("sizeof(int) = %lu\n", sizeof(i));
>     printf("sizeof(float) = %lu\n" , sizeof(f));
>     printf("sizeof(double) = %lu\n", sizeof(d));
>     return 0;
> }
> //%lu: long unsign integer
> ```

> :::spoiler OUTPUT
> ```
> sizeof(int) = 4
> sizeof(float) = 4
> sizeof(double) = 8
> ```
> :::
:::
<br>

### **指定**
（後續使用**整數指標變數**來代替**指向整數的指標變數**來說明內容）
* **iptr指向i**的意思：**一個整數指標變數iptr的值**是另一個**整數變數i的記憶體位址**時。
<br>
> 指定整數指標變數的值
```c=
int i;
int *iptr1;
int *iptr2;
iptr1 = &i; //iptr1指向i
iptr2 = iptr1; //把iptr1的值給iptr2當作值，這樣iptr2也指向i
```

<br>

### **取值**
* 在指標變數前面加上星號，代表從這個記憶體位址**取值**(dereference)
    * 當一個指標iptr指向一個變數i時，\*iptr就代表變數i的值

> 使用指標變數所指到的變數
```c=
i = *iptr; //從iptr取值，並將值賦給i
*iptr = i; //將來從iptr中取值時，會取到i （可以想成把從*iptr取到的值變成i的值）

/*
在此假設指標變數iptr已經指向一個變數，這是才能使用*iptr的語法取值
因程式在使用記憶體時，能使用的記憶體位址有一定的範圍，超出範圍就會執行錯誤
如果一個指標變數沒有經過正確的初始化，他的值極有可能不在正確範圍內，就無法從記憶體正確取值。
*/
```
:::info
* 當指標iptr變數指向一個變數i時，可以想像成：
    1. \*iptr就代表變數i
    2. \*iptr如同一個int一樣
:::
:::danger
* ⚠️ 注意！
    iptr需要先經過正確初始化，才能取值
:::
<br>

### **NULL (空)**
* 當需要指標變數**不指向**任何有效的記憶體位址時使用，讓程式判斷這個指標變數**絕對**不指向任何的有效記憶體位址。
* 跟指標變數沒有正確初始化不同。

:::danger
* ⚠️ 注意！
    任何使用NULL的程式都必須引入 <stdio.h>
:::
<br>

> ex. 此程式沒有經過正確的初始化，執行會導致segmentation-fault
```c=
#include <stdio.h>

int main(void)
{
    int *iptr;
    iptr = NULL;
    printf("*iptr = %d\n", *iptr);
}
```
<br>

### **小結**
> ex. input: 5
```c=
#include <stdio.h>

int main(void)
{
    int i, k;
    int *iptr1, *iptr2;
    scanf("%d", &i);
    iptr1 = &i;
    iptr2 = iptr1;
    printf("i = %d\n", i);
    printf("&i = %p\n", &i);
    printf("iptr1 = %p\n", iptr1);
    printf("&iptr1 = %p\n", &iptr1);
    printf("iptr2 = %p\n", iptr2);
    printf("&iptr2 = %p\n", &iptr2);
    *iptr1 = 8;
    printf("i = %d\n", i);
    k = *iptr2 + 3;
    printf("&k = %p\n", &k);
    printf("k = %d\n", k);
    return 0;
}
```
:::spoiler OUTPUT
```
i = 5
&i = 0x7fffb8327e78
iptr1 = 0x7fffb8327e78
&iptr1 = 0x7fffb8327e68
iptr2 = 0x7fffb8327e78
&iptr2 = 0x7fffb8327e60
i = 8
&k = 0x7fffb8327e74
k = 11
```
:::
<br>

:::spoiler 說明

Before line16:
| 位址            | 值             | 變數名稱  |
| -------------- | -------------- | -------- |
| 0x7fffb8327e60 | 0x7fffb8327e78 | iptr2    |
| 0x7fffb8327e68 | 0x7fffb8327e78 | iptr1    |
| 0x7fffb8327e78 | 0x000000000005 | i        |

After line16:

| 位址           | 值             | 變數名稱 |
| -------------- | -------------- | -------- |
| 0x7fffb8327e60 | 0x7fffb8327e78 | iptr2    |
| 0x7fffb8327e68 | 0x7fffb8327e78 | iptr1    |
| 0x7fffb8327e74 | 0x000000000011 | k        |
| 0x7fffb8327e78 | 0x000000000008 | i        |

:::

<br>

## **取址(位址)**
> ex.比較\*取值與&取址，input: 5
```c=
#include<stdio.h>

int main(void)
{
    int i;
    int *iptr = &i;
    scanf("%d", &i);
    printf("iptr = %p\n", iptr);
    printf("&iptr = %p\n", &iptr);
    printf("*iptr = %d\n", *iptr);
    printf("*(&iptr) =%p\n", *(&iptr));
    printf("&(*iptr) = %p\n", &(*iptr));
    printf("*(*(&iptr)) = %d\n", *(*(&iptr)));
    printf("*(&(*iptr)) = %d\n", *(&(*iptr)));
    printf("&(*(&iptr)) = %p\n", &(*(&iptr)));
    printf("i = %d\n", i);/* errorcase */
    printf("&i = %p\n", &i);
    /* printf("*i = %p\n", *i);     do not do this */
    printf("*(&i) = %d\n", *(&i));
    /* printf("&(*i) = %p\n", &(*i));    do not do this either */
    return 0;
}
```
:::spoiler OUTPUT
```
iptr = 0x7ffe1d785fa8
&iptr = 0x7ffe1d785fa0
*iptr = 5
*(&iptr) =0x7ffe1d785fa8
&(*iptr) = 0x7ffe1d785fa8
*(*(&iptr)) = 5
*(&(*iptr)) = 5
&(*(&iptr)) = 0x7ffe1d785fa0
i = 5
&i = 0x7ffe1d785fa8
*(&i) = 5
```
:::

<br>

:::spoiler 說明

| 位址           | 值             | 變數名稱 |
| -------------- | -------------- | -------- |
| 0x7ffe1d785fa0 | 0x7ffe1d785fa8 | iptr1    |
| 0x7ffe1d785fa8 | 0x000000000005 | i        |

:::warning
**\*(&iptr1)和&(\*iptr1)的結果相同，但意義不同**
* \*(&iptr1) 是先對 iptr1 取址，會得到 iptr1 的記憶體位址，在對得到的記憶體位址取值，因此得到i的記憶體位址，因為 iptr1 裡面存的(值)是i的記憶體位址( iptr1 指向i)。
* &(\*iptr1) 是先對iptr1取值，得到i(因 iptr1 指向i，由前面可以知道 \*iptr1 和i是一樣的)，所以對 \*iptr1 取址，就等同於對i取址，也救世會得到i的記憶體位址。

> 想想看為什麼 \*(\*(&iptr)) 和 \*(&(\*iptr)) 的結果都是5?

:::danger
**Why \*(&i) 的結果是5，但 \*i 卻不合法?**
* 因為 i 並不是一個有效的記憶體位址，所以不能取值。
:::

<br>

## **參數傳遞**
透過將傳遞指標變數傳給被呼叫方，可以幫助我們透過記憶體位址改動呼叫方的變數。

### 利用指標交換變數值 ###
> ex. swap
```c=
#include<stdio.h>

void swap(int *a, int *b)
{
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}

int main(void)
{
    int i, j;
    scanf("%d", &i);
    scanf("%d", &j);
    swap(&i, &j);
    printf("i = %d, j = %d\n", i, j);
    return 0;
}

```
<br>

### 藉由指標改變變數的值 ###
> ex. 指標參數傳遞，input: 10 20
```c=
#include<stdio.h>

void pointer(int *p1, int *p2)
{
    printf("The address of p1 is %p\n", &p1);
    printf("The value of p1 is %p\n", p1);
    printf("The address of p2 is %p\n", &p2);
    printf("The value of p2 is %p\n", p2);
    *p1 += 1;
    p1 = p2;
    *p1 += 2;
}

int main(void)
{
    int i, j, *iptr = &i;
    scanf("%d%d", &i, &j);
    printf("The address of i is %p\n", &i);
    printf("The address of j is %p\n", &j);
    printf("The address of iptr is %p\n", &iptr);
    printf("i = %d, j = %d\n", i, j);
    pointer(iptr, &j);
    printf("i = %d, j = %d\n", i, j);
    *iptr += 5;
    printf("i = %d, j = %d\n", i, j);
    return 0;
}
```

:::spoiler OUPUT
```
10 20
The address of i is 0x7fffd5056b58
The address of j is 0x7fffd5056b54
The address of iptr is 0x7fffd5056b48
i = 10, j = 20
The address of p1 is 0x7fffd5056b08
The value of p1 is 0x7fffd5056b58
The address of p2 is 0x7fffd5056b00
The value of p2 is 0x7fffd5056b54
i = 11, j = 22
i = 16, j = 22
```
:::


:::spoiler 說明
p1和p2由實際參數得到初始值：
| 位址            | 值             | 變數名稱  |
| -------------- | -------------- | -------- |
| 0x7fffd5056b00 | 0x7fffd5056b54 | p2       |
| 0x7fffd5056b08 | 0x7fffd5056b58 | p1       |
| 0x7fffd5056b48 | 請找出          | iptr     |
| 0x7fffd5056b54 | 0x000000000014 | j        |
| 0x7fffd5056b58 | 0x00000000000a | i        |

<br>
\*p1 += 1:

| 位址            | 值             | 變數名稱  |
| -------------- | -------------- | -------- |
| 0x7fffd5056b00 | 0x7fffd5056b54 | p2       |
| 0x7fffd5056b08 | 0x7fffd5056b58 | p1       |
| 0x7fffd5056b48 | 請找出          | iptr     |
| 0x7fffd5056b54 | 0x000000000014 | j        |
| 0x7fffd5056b58 | 0x00000000000b | i        |

<br>

p1 = p2; \*p1 += 2:
| 位址            | 值             | 變數名稱  |
| -------------- | -------------- | -------- |
| 0x7fffd5056b00 | 0x7fffd5056b54 | p2       |
| 0x7fffd5056b08 | 0x7fffd5056b54 | p1       |
| 0x7fffd5056b48 | 0x7fffd5056b58 | iptr     |
| 0x7fffd5056b54 | 0x000000000016 | j        |
| 0x7fffd5056b58 | 0x00000000000b | i        |

<br>
\*iptr += 5:

| 位址            | 值             | 變數名稱  |
| -------------- | -------------- | -------- |
| 0x7fffd5056b00 | 0x7fffd5056b54 | p2       |
| 0x7fffd5056b08 | 0x7fffd5056b54 | p1       |
| 0x7fffd5056b48 | 0x7fffd5056b58 | iptr     |
| 0x7fffd5056b54 | 0x000000000016 | j        |
| 0x7fffd5056b58 | 0x000000000010 | i        |

<br>

:::

<br>

## **指標與陣列的關係**
:::spoiler 陣列名稱的值是什麼?
> 陣列的起始位置
:::
:::spoiler 陣列名稱+常數n是什麼?
> 第n個元素的位址
:::
--->指標變數可以拿來當陣列名稱使用：指標變數+1就是指向下一個的意思
```c
*iptr = a;
*(iptr + i) 即為 a[i]
```
>ex. 利用指標修改陣列，input: 1 2 3 4 5
```c=
#include <stdio.h>
#define ARRAYSIZE 5

int main(void)
{
    int a[ARRAYSIZE];
    for (int i = 0; i < ARRAYSIZE; i++)
        scanf("%d", &(a[i]));
    
    int *ptr = a;
    for (int i = 0; i < ARRAYSIZE; i++)
        printf("a[%d] = %d\n", i, a[i]);
    printf("\n");
    
    ptr = &(a[2]);
    for (int i = 0; i < 2; i++)
        ptr[i] += 3;
    for (int i = 0; i < ARRAYSIZE; i++)
        printf("a[%d] = %d\n", i, a[i]);
    return 0;
}
```
:::spoiler OUTPUT
```
a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
a[4] = 5

a[0] = 1
a[1] = 2
a[2] = 6
a[3] = 7
a[4] = 5
```
:::
:::spoiler 說明
:::warning
**指標變數使用 \[] 和陣列使用 \[] 時有一些不同**
* 陣列 a 使用 \[] 時，a\[1]，永遠是同一個陣列元素
* 指標 ptr 使用 \[] 時，ptr\[1]，是ptr目前所指道位置的下一個陣列元素
* ex. 
  ptr 目前指到 a\[0]，則 ptr\[1] 指到 a\[1]；如果 ptr 目前只 到a\[2]，則 ptr\[1] 就是指到 a\[3]。
    | 陣列            | 指標            |
    | -------------- | -------------- |
    | a\[0]          | ptr\[0]        |
    | a\[1]          | ptr\[1]        |
    | a\[2]          |                |
    | a\[3]          |                |
    | a\[4]          |                |
    
    --->
    
    | 陣列            | 指標            |
    | -------------- | -------------- |
    | a\[0]          |                |
    | a\[1]          |                |
    | a\[2]          | ptr\[0]        |
    | a\[3]          | ptr\[1]        |
    | a\[4]          |                |
:::danger
一般宣告的陣列像是絕對座標，第幾個元素的記憶體位址是固定的。
指標變數的陣列香是相對座標，第幾個元素的記憶體位址是會隨著指標變數的時而改變的。
:::

<br>

### 指標算數(pointer arithmetic) ###
將一個指標加減一個常數 --->得到一個指標
> 常數以指標所指到的元素大小為單位
> ex. 如果是整數指標，常數的單位就是sizeof(int)，也就是4個位元組
```c=
#include <stdio.h>
#define ARRAYSIZE 10

int main(void)
{
    int array[ARRAYSIZE];
    int *iptr1 = &(array[3]);
    int *iptr2 = iptr1 + 4;
    printf("iptr1 = %p\n", iptr1);
    printf("iptr2 = %p\n", iptr2);
    printf("iptr2 - iptr1 = %ld\n", iptr2-iptr1);
    return 0;
}
```
:::spoiler OUTPUT
```
ptr1 = 0x7ffedbcef81c
iptr2 = 0x7ffedbcef82c
iptr2 - iptr1 = 4
```
:::

<br>

### 多維陣列指標 ###
:::spoiler 如何讓指標指向一個多維陣列?
> 在指標變數後面加上維度
:::
```c
int a[2][3][4];
int (*matrixPtr)[3][4] = a;
```
:::danger
:::spoiler 如果 (\*matrixPtr) 沒有括號會怎麼樣?
會變成宣告多維指標陣列，而不是指向多維陣列的指標。
:::

>ex. intPtr 指向 int，arrayPtr 指向 int\[4]，matrixPtr 指向 int\[3]\[4] : 
```c=
#include <stdio.h>
#define X 2
#define Y 3
#define Z 4

int main()
{
    int a[X][Y][Z];
    int *intPtr = a[0][0];
    int (*arrayPtr)[Z] = a[0];
    int (*matrixPtr)[Y][Z] = a;
    
    printf("sizeof(intPtr) = %ld\n", sizeof(intPtr));
    printf("sizeof(arrayPtr) = %ld\n", sizeof (arrayPtr));
    printf("sizeof(matrixPtr) = %ld\n\n", sizeof(matrixPtr));
        
    printf("intPtr + 1 = %p\n", intPtr + 1);
    
    printf("a[0][0] + 1 = %p\n", a[0][0] + 1);
    printf("&(a[0][0][0])+1 = %p\n", &(a[0][0][0]) + 1);

    printf("&(a[0][0][1]) %p\n\n", &(a[0][0][1]));
    
    printf("arrayPtr + 1 = %p\n", arrayPtr + 1);
    printf("a[0] + 1 = %p\n", a[0] + 1);
    printf("a[0][1] = %p\n", a[0][1]); 
    printf("a[0][0] + 4 = %p\n", a[0][0] + 4);
    printf("&(a[0][0][0]) + 4 = %p\n", & (a[0][0][0]) + 4);
    printf("&(a[0][1][0]) = %p\n\n", & (a[0][1][0]));
    
    printf("matrixPtr + 1 = %p\n", matrixPtr + 1);
    printf("a + 1 = %p\n", a + 1);
    printf("a[1] = %p\n", a[1]);
    printf("a[0] + 3 = %p\n", a[0] + 3);
    printf("a[0][0] + 12= %p\n", a[0][0] + 12);
    printf("&(a[0][0][0])+ 12 = %p\n", &(a[0][0][0]) + 12);
    printf("&(a[1][0][0]) = %p\n", & (a[1][0][0]));
    
    return 0;
}
```
:::spoiler OUTPUT
```
sizeof(intPtr) = 8
sizeof(arrayPtr) = 8
sizeof(matrixPtr) = 8

intPtr + 1 = 0x7ffd5e556f44
a[0][0] + 1 = 0x7ffd5e556f44
&(a[0][0][0])+1 = 0x7ffd5e556f44
&(a[0][0][1]) 0x7ffd5e556f44

arrayPtr + 1 = 0x7ffd5e556f50
a[0] + 1 = 0x7ffd5e556f50
a[0][1] = 0x7ffd5e556f50
a[0][0] + 4 = 0x7ffd5e556f50
&(a[0][0][0]) + 4 = 0x7ffd5e556f50
&(a[0][1][0]) = 0x7ffd5e556f50

matrixPtr + 1 = 0x7ffd5e556f70
a + 1 = 0x7ffd5e556f70
a[1] = 0x7ffd5e556f70
a[0] + 3 = 0x7ffd5e556f70
a[0][0] + 12= 0x7ffd5e556f70
&(a[0][0][0])+ 12 = 0x7ffd5e556f70
&(a[1][0][0]) = 0x7ffd5e556f70
```
:::

<br>

> ex. 如果 (\*arrayPtr)\[Z] 和 (\*matrixPtr)\[Y]\[Z] 沒有括號 :
```c=
#include <stdio.h>
#define X 2
#define Y 3
#define Z 4

int main()
{
    int a[X][Y][Z];
    int *intPtr;
    int *arrayPtr[Z];
    int *matrixPtr[Y][Z];
    
    printf("sizeof(intPtr) = %ld\n", sizeof(intPtr));
    printf("sizeof(arrayPtr) = %ld\n", sizeof (arrayPtr));
    printf("sizeof(matrixPtr) = %ld\n\n", sizeof(matrixPtr));
    
    return 0;
}
```
:::spoiler OUTPUT
```
sizeof(intPtr) = 8
sizeof(arrayPtr) = 32
sizeof(matrixPtr) = 96
```
:::

<br>


## 回傳值 ##
指標也可以當作回傳值，方法為在函式名稱前面加一個星號
```c
int *func1(int *iptr); // 如同一個整數
```
> ex. 傳回iptr後第一個正整數記憶體位址，input: 0 0 0 5 9 0 0 6 0 2
```c=
#include <stdio.h>

int *firstPositive (int *ptr)
{
    while (*ptr <= 0)
        ptr++;
    return ptr;
}

#define ARRAYSIZE 10

int main(void)
{
    int array[ARRAYSIZE];
    for (int i = 0; i < ARRAYSIZE; i++)
        scanf("%d", &( array[i]));
    int *iptr = firstPositive (array);
    printf("*iptr = %d\n", *iptr);
    printf("iptr = -array = %ld\n", iptr-array);
    iptr = firstPositive(&(array[5]));
    printf("*iptr = %d\n", *iptr);
    printf("iptr - array = %ld\n", iptr-array);
    return 0;
}
```
:::spoiler OUTPUT
```
*iptr = 5
iptr = -array = 3
*iptr = 6
iptr - array = 7
```
:::
:::spoiler 說明
iptr = firstPositive (array):
| 陣列   | 值 |       |
| ----- | -- | ----- |
| a\[0] | 0  | array |
| a\[1] | 0  |       |
| a\[2] | 0  |       |
| a\[3] | 5  | ptr   |
| a\[4] | 9  |       |
| a\[5] | 0  |       |
| a\[6] | 0  |       |
| a\[7] | 6  |       |
| a\[8] | 2  |       |
| a\[9] | 2  |       |

<br>

iptr = firstPositive(&(array\[5]));

| 陣列   | 值 |       |
| ----- | -- | ----- |
| a\[0] | 0  |       |
| a\[1] | 0  |       |
| a\[2] | 0  |       |
| a\[3] | 5  | array |
| a\[4] | 9  |       |
| a\[5] | 0  |       |
| a\[6] | 0  |       |
| a\[7] | 6  | ptr   |
| a\[8] | 2  |       |
| a\[9] | 2  |       |
     
:::

<br>

## **使用限制**
### 用途 ###
:::spoiler 由以上的討論我們發現,大部分的指標功能可以用陣列的語法取代，那麼為何我們仍需要指標呢?
> 在程式中經常需要將一個記憶體位址保存下來，以備之後使用。這時我們就必須使用指標變數。
:::

<br>

* ex1. 在動態配置記憶體時，我們可以直接向作業系統要求一塊記憶體使用。那麼作業系統要如何告訴我們記憶體在何處?顯然固定的陳列無法滿足這個需求，所以我們需要一個機制，讓程式有辦法能記住一個記憶體位址，這個機制就是指標變數。我們可以用指標變數指向這塊作業系統給我們的記憶體，之後指標變數就可透過簡便的陣列語法存取這塊記憶體。
* ex2. 在處理動態資料結構時，我們會將資料串連起來形成結構。此時結構的大小與形狀都是依動態要求而調整，無法存在固定大小的陣列中，此時我們就需要指標來描述資料之間的連結關係。
* ex3. 在處理字串時，程式常常需要以記憶體位址來溝通。此時溝通的雙方未必能夠使用陣列的 \[] 語法，因為其中一方未必能夠知道這個陣列的起始記憶體位址。此時使用指標直接指到記憶體是最有效的 方法。

### 限制 ###

:::warning
**指標注意事項**
* 除非是上述的動態配置記憶體，動態資料結構及字串處理，否則盡量避免使用指標。
* C程式語言中對指標的使用沒有安全機制,初學很容易弄錯，而且雖以除錯。
* 很多指標的使用是可以用陣列語法代替的。這樣不但容易閱讀，也容易除錯。
* 有人認為使用指標可增加效能，但是現代編譯器已經能產生非常好的執行檔。為了效能犧牲可讀性也許並不值得。
:::

:::danger
**不到真正需要，例如動態配置記憶體、動態資料結構及字串處理，否則請儘量避免使用指標。**
:::
