指標與陣列關係

寫過C的人一定知道陣列和指標的關係密不可分,而對於初學者的話,這個部份常常是非常容易搞混的,因為指標的觀念不熟,或是陣列的觀念不熟,所以在這個部份,對於指標陣列分析一下,讓初學者可以清楚的明白,到底是存在什麼樣的關係。

※ 陣列的意義

array是同一型態的資料集合,記憶體內容是連續的

同樣的也可以是指向int型別的指標array

※ 二維陣列

深入的array就是二維的array了,其實在線性記憶體中並沒有類似向距陣那樣的儲存結構,故我們須要以一維的array來模擬二維陣列。

※ 幾個重點

* 從上圖中我們可以發現x[0] = &x[0][0]的位址,而x[1] = &x[1][0]的位址。
* 在二維中單取一列的話x[0],會取得第1列的起始位址,x[1]會取得第2列的起始位址。
* 當傳遞二維陣列的時候須要傳遞下標,除了第一個維度外,這是因為函式只知道線性記憶體,因此須要告訴函式,多少個col。

Example:

void print(int data[][3]); (3個col分成一個row)
void print(int (*data)[3]); (和上面完全相同)

* 一維陣列使用指標記憶體位址,即可指向、操做、解參考(dereference),那二維陣列該如何使用指標來指向呢?

Example:
一維做法

int x[10];
int* p = x; (指向)
p[2] = 100; (解參考)
++p; (操做)

二維做法

int x[3][2];
int (*p)[2] = x; (p是個指標,指向有2個int元素一維的陣列)
p[1][1] = 10;
p[2][0] = 100;

* int (*p)[2]其中的括號是必須要存在的,否則int* p[2]意思可是p是個陣列有兩個元素,每個元素都是一個指向int型別的指標。
* 只有使用二維的指標在函式中才知道線性記憶體是分成幾組,才可以讓你方便的使用x[1][1]來存取。

※ 使用指標來分析

若將int x[3][2]使用指標的方式來看的話,以上的圖我們可以很清楚的了解到x和x[0]和x[0][0]的關系,所以由以下的等式,更可以了解二維陣列到底意義在那

* x這個二維陣列名稱,表示著這是個位址,而且等於是x[0]的位址,也是x[0][0]的位址。
* x = &x[0] = &x[0][0]
* x[0]的內容值是個指標,所存放的是x[0][0]的位址。
* x[0] = &x[0][0]
* &x = x = &x[0] = x[0] = &x[0][0] 印出來的址相同,都是代表同一個位址
* *vip 上式不同的地方,在於型別
* &x 型別為 int (*)[3][2]
* x 型別為 int (*)[2]
* &x[0] 型別為 int (*)[2]
* x[0] 型別為 int*
* &x[0][0] 型別為 int*
* x[0][0] 型別為 int

Example:
你可以做個實驗,印出下面的值,看看位址多少

int x[3][2];
printf("%p\n", &x);
printf("%p\n", x);
printf("%p\n", x[0]);
printf("%p\n", &x[0]);
printf("%p\n", &x[0][0]);

* 由上面輸出的資料我們可以了解x[0]與&x[0]輸出是一樣的,就是x[0]的位址和它的內容值都是同一個位址。
* &x[0] = x[0] 差別在於「型別不同」(非常重要) 。
* 反正&x[0] = &*(x + 0) = x + 0 = x所以&x[0] = x就簡寫成x就好。
* 將一個指標或是記憶體位址加1,所增加的數值將會和所指向的物件型別有關。

Example:

x + 1 = &x[1] ; (row move列移動)
x[0] + 1 = &x[0][1]; (col move行移動)

「將x[0], x[1], x[2]當成一維陣列的起始位址」,則x[0][0], x[0][1]就是此一維陣列的第一個第二個元素

* **x = *(*(x + 0) + 0) = x[0][0] 第一列第一個元素資料
* 現在要取第二列第二個元素資料,有以下幾種取法

Example:

x[1][1]; (使用下標運算子給索引直接取值)
*(*(x + 1) + 1); (使用指標方式利用指標的offset取值)
*(x[1] + 1); (合併使用)

* 為什麼x + 1和x[0] + 1所指的結果是不同的呢?

Example:

x + 1 = &x[1]
x[0] + 1 = &x[0][1]

先來看看我們宣告是怎麼宣告的int x[3][2]英文解釋為x is an array of 3 arrays of 2 ints,拆解後x有3個arrays,因此將x + 1會指到第二個arrays,而x[0]是第一個arrays,而x[0] + 1則表示指向第二個元素。

※ 自我功力測驗

看了這麼久的指標和陣列,有一維的有二維的,來試試以下幾個題目,看是否都能以定義和觀念完全解意義所在

Example:

int n = 5;
double x;
int* pi;
double* pd;
pi = &n;
pd = &x;
pd = pi; (是錯誤的喔!因為型別不同不能相互指定)int* pi;
int (*pa)[3];
int ar1[2][3];
int ar2[3][2];
int **p2;
pi = &ar1[0][0]; (ok 解析:*(ar1 + 0) + 0)
pi = ar1[0]; (ok 解析:*(ar1 + 0))
pi = ar1; (是錯誤的喔!ar1是指向一個以3-int型態資料為單位的陣列)
pa = ar1; (ok)
pa = ar2; (是錯誤的喔!ar2是指向一個以2-int型態資料為單位的陣列)
p2 = π (ok 解析:指向指標的指標)

※ 拆解二維陣列以一維陣列仿二維

當我們傳遞一個二維陣列到一個函數的時候,我們都是傳一個地址,但是在函式那邊它看到的是什麼呢?就單單一個地址的話,那麼我們該如何存取第二個維度呢?所以傳遞陣列的時候,不止是傳遞一個位址,還需要傳遞它的「結構型別」和大小,才可以讓我們的函式來操做這個二維陣列。

今天我們宣告了int x[2][3],當以指標傳遞給函式的時候,有二種方法可以參考到正確資料。

Example:
方法1

print(x); (call)
void print(int (*p)[3]); (get)

方法2

print(x[0]); (call)
void printf(int* p); (get)
(兩者一樣)
print(&x[0][0]); (call)
void print(int* p); (get)

但是兩者看到的結構完全不同

故資料在存取的時候在(法1)中可像在main()中使用二維的下標運算子來存取資料,如要取得資料直接用p[0][1]列可存取第一排的第二個元素,但使用在(法2)可是完全錯誤的。

在(法2)中就像存取一維陣列一樣,要取得資料的話,直接*(p+0) 或是p[0],於於要印出資料,線性的找尋資料非常方便。

在呼叫print(x)和print(&x[0][0])或print(x[0]),意思果然不同,x表示此陣列結構和位址,而&x[0][0],只取出第二排第一個元素,純址,沒有結構。

0 意見: