---
GA: G-RZYLL0RZGV
---
###### tags: `大一程設-下` `東華大學` `東華大學資管系` `基本程式概念` `資管經驗分享`
進階探討 pointer 雙重指標!與 2-d dynamic array
===
[TOC]
## 前言
在真的探討二維的動態陣列之前,有一個必須知道的基本知識就是雙重指標,而他的概念有點不直覺,我們一起來看看。
在開始閱讀這篇筆記前,請確定你閱讀過運算子的優先權了哦! -> [我是傳送門](https://hackmd.io/@ndhu-programming-2021/HJAkAf8AF)
> 第一次看請不要灰心,一定會看不懂,多看幾次吧QAQ。
> [name=Orange]
## 雙重指標變數
複習一下,一般的指標變數如下:
```
int *p;
```
那既然會說是雙重指標變數,長相如下:
```
int **p;
```
可以看到有兩顆 `*`,那該怎麼理解呢? 我們先從名詞解釋開始吧。
還記得我們稱 `int *p` 為一個指標變數,會儲存一個指標(記憶體位址)。
不失一般性,`int **p` 仍為一個指標變數,也會儲存一個指標(記憶體位址),但他儲存的指標是一個指標變數的指標(記憶體位址)。
所以就會有我們常聽到的,雙重指標變數就是「<span style="color:red">**指向指標變數的指標變數**</span>」。
我們馬上看個實際例子。
```cpp=
int a = 5;
int *p1 = &a;
int **p2 = &p1;
```
以 `int *p1` 來看,他是一個指標變數,儲存一個變數 a 的指標。
以 `int **p2` 來看,他是一個指標變數,<span style="color:red">**儲存一個指標變數的指標**</span>。
如果你覺得很饒口,看一下下面的圖幫助你理解。

核心觀念千萬不要忘記,無論是一般的指標變數,還是雙重指標變數,都是儲存記憶體位址,就算今天變成三重指標變數,四重指標變數都一樣哦!
而這邊宣告他們是 int 型態就與上一篇筆記在講一般指標變數時相同,代表該指標變數最後指向的記憶體位址所儲存的值必須要是 int 型態。
### 小提醒
今天你宣告一個指標變數是幾層指標,他就必須要指向幾次,可以參考上圖,雙重指標變數(0x300)會根據記憶體位址指兩次,一般指標變數(0x200)只有指一次。
如果你把指向次數不同的位址指派錯人,程式是會報錯的哦,像下面這樣。
```cpp=
int a = 5;
int *p1 = &a;
int **p2 = &a; // error!!
```
你會看到像下面這樣的錯誤訊息。沒有辦法把一重指標變數指派給雙重指標變數,所以請一定要明確了解這邊的運作方式哦。

### precedence 優先權與結合性
還記得請各位去看一下運算子的優先權,這邊想跟大家討論的原因是,`int **p`這邊有兩個 `*,亦稱 dereference 運算子`,請問要先看哪個星號,優先權是如何?
* <span style="color:red">dereference 的結合性是右結合</span>,會先看右邊
* 所以宣告雙重指標其實是像這樣 `int *(*p2)`
看到 `*` 號(左邊那顆)代表宣告指標變數,但因為是右結合,所以 p2 與 右邊的 `*` 優先結合,這樣由左至右就可以翻譯成,宣告一個指標變數其指向一個一重指標變數。
## 雙重指標變數的取值與取址
```cpp=
int main(){
int a = 5;
int *p1 = &a;
int **p2 = &p1;
//a 取值
cout << "[變數] a 存的值: " << a << endl;
//a 取址
cout << "[變數] a 所在的記憶體位址: " << &a << endl;
//p1 取值
cout << "[指標變數] p1 指向的位址儲存的值: " << *p1 << endl;
//p1 取址
cout << "[指標變數] p1 指向的位址: " << p1 << endl;
cout << "[指標變數] p1 所在的記憶體位址: " << &p1 << endl;
//p2 取值
cout << "[指標變數] p2 指向的位址所儲存的值(位址): " << *p2 << endl;
cout << "[指標變數] p2 指向的位址所指向的位址儲存的值: " << **p2 << endl;
//p2 取址
cout << "[指標變數] p2 指向的位址: " << p2 << endl;
cout << "[指標變數] p2 所在的記憶體位址: " << &p2 << endl;
return 0;
}
```

> 對齊工具 setw 與 left ->小題外話[(1)](https://dotblogs.com.tw/v6610688/2013/11/05/cplusplus_output_align_setw_set_field_width)、[(2)](https://www.cnblogs.com/wxxweb/archive/2011/06/01/2065671.html)
相信都是文字有點不直覺,我們再看張圖吧!

已經講到最簡單摟,希望大家多多思考,有問題鼓勵發問!
## 動態配置二維陣列
在真的配置動態二維陣列之前,有一些觀念要跟大家討論。
二維動態陣列配置的記憶體運作方式是像這樣的,請看下圖。
> <span style="color:red">**圖片來源**</span> -> [here](https://mropengate.blogspot.com/2015/12/cc-dynamic-2d-arrays-in-c.html)
> 
* 最上層,雙重指標變數 p
* 會是由外往內配,先配置最上層(圖中最左)的雙重指標,讓它往下指,而我們知道一個指標變數就存一個記憶體位址,所以它要存下一層一維陣列的陣列頭 p[0],剩下的 p[1]、p[2] 則是依靠我們之前說的位移量去遍歷它。
* 第二層的 p[0]、p[1]...,因為要能夠指到最內層的陣列,所以每一個(p[0]、p[1]...p[n])都會被視為指向一維陣列頭(記憶體位址)的指標變數
* 因此 p[0] 會衍生出一條一維陣列,p[1] 會衍生出一條一維陣列,依此類推。
* 而對於 p[0] 而言,p[0][0]、p[0][1] 則是依靠位移量來達到。
所以第二層我們稱它為<span style="color:red">**指標變數陣列**</span>,陣列中的每一格都儲存一個指標變數。我們後面來說明它。
> 就像**整數陣列**每格**存整數**,**浮點數陣列**每格**存浮點數**。
> **指標變數陣列**每格**存指標變數**,就這麼簡單~<br>
> 所以你只要搞懂什麼是指標變數你就會了。
> [name=Orange]
### 動態配置二維陣列的語法
既然已經知道上面那張圖的看法跟建立陣列的方式,我們來看 CODE。
```cpp=
int main(){
int row = 3, col = 2;
int **p = new int*[row];
for(int i = 0; i < row; i++){
p[i] = new int[col];
}
}
```
* 第 3 行
* 宣告一個雙重指標變數 p
* 因為要配置出下一層的指標變數陣列,讓每一格指標變數都能指出一條陣列(這邊假設 row = 3,所以有 3 條) -> 3 條一維陣列建構出一個二維陣列。
* 第 4 行
* 假設我們要配置出一個 3 * 2 的陣列,在第 3 行已經配置了三格儲存 3 個指標變數,接下來要為這三個指標變數分別指向一個陣列的陣列頭。
* 第 5 行
* 每條一維陣列有 2 格(col = 2)
* 因為在第 3 行已經配置一個長度為 3 的一維指標變數陣列,所以 p[i] 代表配置出的陣列的位移量,去取得該記憶體位址內的值。如果這一行混淆你,我們返璞歸真看一下下面。
* **提醒** : p[i] 跟 p[i][j] 的位移量影響的是不同的方向哦。
別忘記之前講的。
```cpp=
int a = 5;
int *p1 = &a;
int **p2 = &p1;
cout << p2 << endl;
```
`cout << p2` 代表印出指標變數 p2 所指向的位址。
那換成下面這樣。
```
int **p2 = new int*[3];
cout << p2 << endl;
```
`cout << p2` 會印出指標變數 p2 所指向的陣列頭的記憶體位址。
而 `cout << p2[0]` 會取出 p2 這個指標變數陣列第一格所儲存的值,因為每一格都是指標變數,所以會印出第一格指標變數的記憶體位址。
`cout << p2[0][0]` 會取出 p2 這個指標變數陣列第一格所儲存的值(位址)所儲存的值。
以下依此類推...
畫個圖就像這樣。

上面這張圖可以用下面這個範例玩一下,確保你知道圖上每個取值取址的不同處哦!
```cpp=
int main(){
int **p = new int*[3];
int i = 0, j = 0;
// 配置
for(i = 0; i < 3; i++){
p[i] = new int[2];
}
// 取址
for(i = 0; i < 3; i++){
for(j = 0; j < 2; j++){
cout << &p[i][j] << " ";
}
cout << endl;
}
// 附值
int k = 0;
for(i = 0; i < 3; i++){
for(j = 0; j < 2; j++){
p[i][j] = k;
k++;
}
}
// 取值
cout << p << " " << &p[0] << " " //p equal to &p[0]
<< p[0] << " " << &p[0][0] << " " //p[0] equal to &p[0][0]
<< p[0][0] << endl;
return 0;
}
```
所以其實一個 3 * 2 的動態陣列,就配置了 10 個記憶體位址,你說它真的非常有效率嗎?真的端看需求而定!
### 動態配置記憶體,用完請回收
上面的例子動態配置了 3 * 2 的陣列,但配出來的這些記憶體,必須要被收回去,語法先來。
```cpp=
//配置
int **p = new int*[3];
for(int i = 0; i < 3; i++){
p[i] = new int[2];
}
//回收
for(int i = 0; i < 3; i++){
delete[] p[i];
}
delete[] p;
```
我想在看配置的時候,應該有點感覺,我們是從外到內一直分配記憶體出來,所以在回收的時候呢,要先把最內部的記憶體刪乾淨,才能把外面的刪掉。
* 最內層
* 第 7 + 8 行,因為總共配置 3 條,每條 2 格,所以是先把 p[0 ~ 2][0 ~ 1] 共 6 格的記憶體刪掉
* > 語法沒有 p[0 ~ 2][0 ~ 1] 這種寫法,這邊只是說明!
> 這樣寫絕對大扣分<br>[name=Orange]
* 外層
* 第 10 行
* 接者再把 p[0~2] 共 3 格,的記憶體給刪掉
<span style="color:green;font-size:20px">**到這邊的內容是參考書的內容,要考過考試大家要學會,下面是補充。**</span>
---
<span style="color:green;font-size:20px">**以下為補充~**</span>
### [補充 - 自行閱讀]指標變數陣列
> 我左思右想,我雖然會,但我決定依靠大神們來講解
> [name=Orange]
* 這篇請閱讀文章內 3(a)(b)
* [簡單搞懂指標(pointer)、指標陣列(pointers of array, int *foo[]) 與指向陣列的指標 (pointer to array, int (*bar)[])](http://hackgrass.blogspot.com/2018/03/c-pointerint-foo-int-bar.html)
* > 雖然語法是 C,不是 C++,但核心概念不變,可以自己用 C++ 的語法試試看
> 裡面有一些小地方寫錯,麻煩自己判別哦
* 接著請閱讀這篇
* > 上面那篇要你們看的地方看得懂的話,這篇一定看得懂
* [C / C++ 函式傳遞二維陣列 範例與解說](https://charlottehong.blogspot.com/2017/11/c-c.html)
* 最後請閱讀這篇
* > 雖然是大陸用語,但我相信聰穎的你一定知道對應到我們常說的甚麼。
> 不會歡迎來找我討論哦~
* [C/C++编程语言中char** a和char* a[]介绍](https://blog.csdn.net/liitdar/article/details/80972088)
## [補充 - 自行閱讀] 雙重指標的運算 (double pointer arithmetic)
* [雙重指標運算與二維陣列](https://edisonshih.pixnet.net/blog/post/27344592)
給大家一些例子自己閱讀吧~
有問題歡迎來問我~
### 一重指標的二維陣列運算
```cpp=
int main(){
int array[2][3] = {1,2,3,4,5,6};
int *p1 = &array[0][0];
for(int i = 0; i < 2; i++){
for(int j = 0; j < 3; j++){
cout << *(p1+i*3+j);
}
}
}
```
### 雙重指標的二維陣列運算
如果你真的搞懂了 precedence 跟結合性,請問第 11 行怎麼解讀呢?
```cpp=
#include <iostream>
#include <cmath>
using namespace std;
void input(double (*wh)[2], int sizeOfRow){
int j = 0; double t = 0;
for(int i = 0; i < sizeOfRow; i++){
for(j = 0; j < 2; j++){
cout << "student" << i+1 << " - "<<j<<":";
cin >> t;
*(*(wh+i)+j) = t;
// 如果寫 *(*wh+i+j) 結果會一樣嗎?
}
if(*(*(wh+i)+j) <= 0) break;
}
}
string calculation(){
double wh[3][2];
input(wh, 3);
double max = 0, mini = 0, bmi = 0;
for(int i = 0; i < 3; i++){
bmi = wh[i][0]/(pow(wh[i][1], 2));
cout << bmi << endl;
if(i==0) mini = bmi;
if(max < bmi) max = bmi;
if(mini > bmi) mini = bmi;
}
string a = "max and bmi is ";
// to_string 只支援 C++11
a = a + to_string(max) + "," + to_string(mini);
return a;
}
void show(string a){
cout << a << endl;
}
int main(int argc, char** argv) {
show(calculation());
return 0;
}
```
## Reference
* [[C語言] 指標教學[七]: 多重指標](https://medium.com/@racktar7743/c%E8%AA%9E%E8%A8%80-%E6%8C%87%E6%A8%99%E6%95%99%E5%AD%B8-%E4%B8%83-%E5%A4%9A%E9%87%8D%E6%8C%87%E6%A8%99-89854e6eae0b)
* [指標與位址](https://openhome.cc/Gossip/CGossip/Pointer.html)
很累吧,看個搞笑的文章放鬆一下
* [台灣工程師常唸錯的英文單字](https://blog.privism.org/2012/06/blog-post.html)