# Ch10 指標
> 上一章: [Ch10 指標](https://hackmd.io/@sunfrancis12/BykpM0iza)
> 回目錄: [NTCU程式戰鬥營C講義](https://hackmd.io/@sunfrancis12/ByfdXdjG6)
## 指標的概念
在電腦科學中,指標(英語:Pointer),是程式語言中的一類資料類型及其物件或變數_(程式設計),用來表示或儲存一個記憶體位址,這個位址的值直接指向(points to)存在該位址的對象的值。
> [維基百科 -指標 (電腦科學)](https://zh.wikipedia.org/zh-tw/%E6%8C%87%E6%A8%99_(%E9%9B%BB%E8%85%A6%E7%A7%91%E5%AD%B8))
上述的解釋看起來很難懂對吧? 我們慢慢來看吧
大家可以把指標想像成<a style="color:red">姓名</a>跟<a style="color:red">綽號</a>之間的關係,比如`王安明`的綽號叫`小明`

如果我跟A說"我給`小明`10塊錢",那A就會知道我給`王安明`10塊錢

代表即便我沒有直接跟A說"我給`王安明`10塊",A還是可以透過<a style="color:red">綽號</a>跟<a style="color:red">姓名</a>的關係跟知道這件事,這就是**指標Pointer**的基本概念
從上述的例子來看,<a style="color:red">綽號</a>就是我們的**Pointer指標**,而且<a style="color:red">綽號</a>**指向(points to)**<a style="color:red">姓名</a>

## 指標的宣告
在宣告指標時,我們也必須告訴程式指標指向的變數其變數型態為何
```c=
int *p; //宣告一個指標P,其指向的變數其變數型態為int
{指向變數其變數型態} *{指標名稱}
```
### 指標的初始化
我們會使用`&`符號,告訴程式指標是指向哪個變數
```c=
*p = # //指標P指向變數num
*{指標名稱} = &要指向的變數名稱
```
輸出指標的指向的變數內容:
```c=
#include<stdio.h>
int main(){
int num,*p = #
printf("輸入num的值: ");
scanf("%d",&num);
printf("num的值為: %d\n",num);
printf("指向num的指標(*p)其值為: %d",*p);
}
```
輸入內容:
```c=
12
```
輸出結果:
```c=
輸入num的值: 12
num的值為: 12
指向num的指標(*p)其值為: 12
```
## 指標的現實應用
那為什麼我們要這麼麻煩搞一個指標出來呢?這就要說到指標在現實中的應用了
在現實中,我們程式裡面的每一個變數都會存在記憶體(memory)的某一個處,而記錄這一位置的數字又稱作**記憶體位址(memory address)**

我們在使用`scanf()`輸入以及初始化指標所使用的`&`符號,就是代表變數在記憶體中的**記憶體位址(memory address)**,此過程又被稱作**reference**或者**取址**
印出變數num的記憶體位址:
```c=
#include<stdio.h>
int main(){
int num,*p = #
printf("輸入num的值: ");
scanf("%d",&num);
printf("num的值為: %d\n",num);
printf("變數num的記憶體位址: %p\n",&num);
}
```
輸入內容:
```c=
12
```
輸出結果:
```c=
輸入num的值: 12
num的值為: 12
變數num的記憶體位址: 0061FF18
```
而**指標Pointer**存取的值正是該變數的記憶體位址:

印出num和p的值以及其記憶體位址:
```c=
#include<stdio.h>
int main(){
int num,*p = #
printf("輸入num的值: ");
scanf("%d",&num);
printf("num的值為: %d\n",num);
printf("變數num的記憶體位址: %p\n",&num);
printf("\np的值為: %p\n",p);
printf("變數p的記憶體位址: %p\n",&p);
printf("\n變數p的值所指向的記憶體位址的值 %d",*p);
}
```
輸入內容:
```c=
12
```
輸出結果:
```c=
輸入num的值: 12
num的值為: 12
變數num的記憶體位址: 0061FF1C
p的值為: 0061FF1C
變數p的記憶體位址: 0061FF18
變數p的值所指向的記憶體位址的值 12
```
將上述輸出結果畫成圖:

## 為什麼要用指標
**指標的用途**
* 指標直接指向記憶體中的位址
- 由於指標是直接指向記憶體中的位址,所以執行的效率會比一般呼叫變數來的**更快**
* 不會受限於區域變數或者全域變數
- 由於指標是直接指向記憶體中的位址,因此我們可以直接透過記憶體位址更改變數的內容
有了指標後,我們就可以在`function`函式內使用其他函式的區域變數了:
```c=
#include<stdio.h>
void plus_ten(int *p){
*p += 10;
}
int main(){
int num,*p = # //num為區域變數
printf("輸入num的值: ");
scanf("%d",&num);
plus_ten(p);
printf("輸入function後num的值: %d",num);
}
```
輸入內容:
```c=
12
```
輸出結果:
```c=
輸入num的值: 12
輸入function後num的值: 22
```
## 陣列與指標
其實我們熟知的陣列,就是由記憶體上面連續的位址組成:

印出陣列所有元素的指標:
```c=
#include <stdio.h>
int main(void) {
int arr[10] = {0};
int *p = arr;
for(int i = 0; i < 10; i++) {
printf("&arr[%d]: %p", i ,&arr[i]);
printf("\t\tptr + %d: %p\n", i, p + i);
}
return 0;
}
```
> [程式碼取自: 指標與陣列 -openhome.cc](https://openhome.cc/Gossip/CGossip/PointerAndArray.html)
輸出內容:
```c=
&arr[0]: 0061FEF0 ptr + 0: 0061FEF0
&arr[1]: 0061FEF4 ptr + 1: 0061FEF4
&arr[2]: 0061FEF8 ptr + 2: 0061FEF8
&arr[3]: 0061FEFC ptr + 3: 0061FEFC
&arr[4]: 0061FF00 ptr + 4: 0061FF00
&arr[5]: 0061FF04 ptr + 5: 0061FF04
&arr[6]: 0061FF08 ptr + 6: 0061FF08
&arr[7]: 0061FF0C ptr + 7: 0061FF0C
&arr[8]: 0061FF10 ptr + 8: 0061FF10
&arr[9]: 0061FF14 ptr + 9: 0061FF14
```
使用指標運算來取出陣列的元素值:
```c=
#include <stdio.h>
int main(){
int num[10] = {9,8,7,6,5,4,3,2,1,0}; //num為區域變數
int *p = num;
for(int i=0;i<10;i++){
printf("%d ",*(p + i));
}
}
```
輸出結果:
```c=
9 8 7 6 5 4 3 2 1 0
```
## 進階的陣列用法
除了標準的for(i=0;...)的方法,我們也可以使用指標的方式印出迴圈,又稱**迭代**:
透過迭代地方式印出陣列(大家可以去[來源網站](https://openhome.cc/Gossip/CGossip/PointerAndArray.html)查看程式的運作原理):
```c=
#include <stdio.h>
int main(void) {
int arr[] = {10, 20, 30, 40, 50};
int *begin = arr;
int *end = *(&arr + 1);
for(int *it = begin; it < end; it++) {
printf("%d ", *it);
}
return 0;
}
```
> [程式碼取自: 指標與陣列 -openhome.cc](https://openhome.cc/Gossip/CGossip/PointerAndArray.html)
輸出結果:
```c=
10 20 30 40 50
```
如果指標中都是非`0`的元素,可以使用以下方式印出:
```c=
#include <stdio.h>
int main(){
int num[10] = {9,8,7,6,5,4,3,2,1,1}; //num為區域變數
int *p = num;
int count = 0;
for(;*p;*p++){ //如果*p不存在或等於0,則跳出迴圈
printf("(p + %d): %d \n",count,*p);
count++;
}
}
```
輸出結果:
```c=
(p + 0): 9
(p + 1): 8
(p + 2): 7
(p + 3): 6
(p + 4): 5
(p + 5): 4
(p + 6): 3
(p + 7): 2
(p + 8): 1
(p + 9): 1
```
迭代的終極用法:
```c=
#include <stdio.h>
int main(){
int num[10] = {9,8,7,6,5,4,3,2,1,0}; //num為區域變數
for(int i : num){
printf("%d ",i);
}
}
```
輸出結果:
```c=
9 8 7 6 5 4 3 2 1 0
```
:::warning
誠心建議在沒有了解這個迭代的運作原理之前,先不要用此方法
:::
### 使用其他區域的陣列
我們也可以透過上述我們學到關於陣列的性質,透過指標的方式在使用位於其他function的陣列:
```c=
#include<stdio.h>
int* plus_ten(int arr[],int length){
for(int i=0;i<length;i++){
arr[i] +=10;
}
return arr;
}
int main(){
int num[10] = {9,8,7,6,5,4,3,2,1,0}; //num為區域變數
int *p = num;
printf("陣列num的值: \n");
for(int i=0;i<10;i++) printf("%d ",num[i]);
p = plus_ten(num,10);
printf("\n\n輸入function後陣列num的值: \n");
for(int i=0;i<10;i++) printf("%d ",num[i]);
}
```
輸出結果:
```c=
陣列num的值:
9 8 7 6 5 4 3 2 1 0
輸入function後陣列num的值:
19 18 17 16 15 14 13 12 11 10
```
---
> 上一章: [Ch10 指標](https://hackmd.io/@sunfrancis12/BykpM0iza)
> 回目錄: [NTCU程式戰鬥營C講義](https://hackmd.io/@sunfrancis12/ByfdXdjG6)