[基礎概念] 指標 (2):指標和陣列

指標和陣列有密切的關係。我們通常會習慣使用索引,也就是 index 去存取陣列的元素。而也正是因為陣列在記憶體內的排列特性,讓陣列元素也非常適合用指標來存取。

陣列

首先要先介紹指標加減法。當我們操作加減法時,是針對指標所指向的資料型態大小作處理。例如我宣告了一個指標變數 int *ptr,因為指向整數,因此 ptr+1 是將位址的值加上四個 bytes。

因為記憶體會以連續的記憶體空間配置給陣列,所以我們可以利用上面指標的算術特性操作。另外有一句話很重要:陣列的名稱本身是存放一個位置的指標常數。

例如我們宣告了一個陣列名稱叫做 arr,它本身是指標常數,指向陣列的位置,但無法變更名稱。這個指標常數的位置等於指標常數的值。另外,陣列第一個元素的位址就代表了陣列的位置。

好像繞口令,可以直接透過下面的實作看得更清楚:

#include <stdio.h>
#include <stdlib.h>

int main (void){
  int a[5] = {1, 100, 321, 6, 34};
  printf("a = %p\n", a);
  printf("&a = %p\n", &a);
  
  for (int i = 0; i < 5; i++){
    printf("a[%d] = %d\n", i, a[i]);
  }
  for (int i = 0; i < 5; i++){
    printf("&a[%d] = %p\n", i, &a[i]);
  }
}

output

a = 0x7fff93300ff0
&a = 0x7fff93300ff0
a[0] = 1
a[1] = 100
a[2] = 321
a[3] = 6
a[4] = 34
&a[0] = 0x7fff93300ff0
&a[1] = 0x7fff93300ff4
&a[2] = 0x7fff93300ff8
&a[3] = 0x7fff93300ffc
&a[4] = 0x7fff93301000

指標加減法操作

熟悉上面特性之後,就可以實際利用指標的加減法演練一次陣列操作:

#include <stdio.h>
#include <stdlib.h>

int main (void){
  int a[5] = {9, 8, 7, 6, 5};
  
  for (int i = 0; i < 5; i++){
    printf("a[%d] = %d\n", i, *(a+i));
  }
}

其實只是把第一個程式碼的 a[i] 改成 *(a+i) 而已。

也就是說 a+i 就代表了 a[i] 的位址,理所當然 *(a+i) 就可以取出 a[i] 的值。

然而我們有一點要注意,也是上面強調過的:陣列名稱本身是指標常數,它指向陣列的位置,但無法變更名稱。所以,a = a + 1 這種操作會失敗,就跟你說了不能改變名稱吼。

但若是宣告一個指標變數來指向 a,就可以避開這個問題:
int *ptr = a,代表我宣告了指標 ptr 指向 a
ptr = ptr + 1,代表指標 ptr 指向 a 的下一個元素位址。

二維陣列和指標

基本上上述都是一維陣列的指標操作方法。二維陣列也很常見到,要怎麼操作呢?

首先要了解二維陣列的儲存方式:

和一維陣列的概念很像,只是我們可以把二維看作是一維陣列的堆疊。

很重要的一句話:陣列名稱本身是指標常數。有了上述概念後,可能對於如何指向二維陣列內的元素有一點想法了,或許我們會需要兩個指標去指這個元素。因此,接下來要稍微跳開,講一下雙重指標。

指標除了指向任何一種資料型態的變數,還可以指向指標!宣告方法也很簡單,就是多加一個 *

資料型態 **雙重指標;
int **ptr;
int *(*ptr);

兩種寫法都可以。概念上可以看下圖:

我們一樣用實際操作來理解:

#include <stdio.h>
#include <stdlib.h>

int main (void){
  int n=999;
  int *p, **pp;
  
  p = &n;
  pp = &p;
  
  printf("n = %d; &n = %p \n", n, &n);
  printf("*p = %d; p = %p; &p = %p \n", *p, p, &p);
  printf("**pp = %d; *pp = %p; pp = %p; &pp = %p \n", **pp, *pp, pp, &pp);
}

output

n = 999; &n = 0x7ffceb269c14 
*p = 999; p = 0x7ffceb269c14; &p = 0x7ffceb269c18 
**pp = 999; *pp = 0x7ffceb269c14; pp = 0x7ffceb269c18; &pp = 0x7ffceb269c20

OK,這樣子就越來越清楚二維陣列的可能用法了。

從之前一維陣列我們知道 a+i 就代表了 a[i] 的位址,*(a+i) 就可以取出 a[i] 的值,而二維陣列還要再往更裡面取值,所以就是 *(*(a+i)+j) 這種形式,也代表著取出二維陣列第 i 列第 j 行(index 從 0 開始):

直接看一下實作:

#include <stdio.h>
#include <stdlib.h>

int main (void){
  int arr[4][3] = {{11, 55, 88},
                   {46, 89, 23},
                   {17, 30, 14},
                   {99, 27, 31}};
                   
  printf("列印各個元素:\n");               
  for (int i = 0; i < 4; i++){
    for (int j = 0; j < 3; j++){
      printf("%3d", *(*(arr+i)+j));
    }
    printf("\n");
  }
  
  printf("\n各個元素位址:\n");
  for (int i = 0; i < 4; i++){
    for (int j = 0; j < 3; j++){
      printf("arr[%d][%d]=%d; (%p)\n", i, j, *(*(arr+i)+j), *(arr+i)+j);
    }
  }
}

output

列印各個元素:
 11 55 88
 46 89 23
 17 30 14
 99 27 31

各個元素位址:
arr[0][0]=11; (0x7ffff5f5a940)
arr[0][1]=55; (0x7ffff5f5a944)
arr[0][2]=88; (0x7ffff5f5a948)
arr[1][0]=46; (0x7ffff5f5a94c)
arr[1][1]=89; (0x7ffff5f5a950)
arr[1][2]=23; (0x7ffff5f5a954)
arr[2][0]=17; (0x7ffff5f5a958)
arr[2][1]=30; (0x7ffff5f5a95c)
arr[2][2]=14; (0x7ffff5f5a960)
arr[3][0]=99; (0x7ffff5f5a964)
arr[3][1]=27; (0x7ffff5f5a968)
arr[3][2]=31; (0x7ffff5f5a96c)

關於指標和陣列的簡單介紹就先到此結束,以後若有更深的理解還會再更新!

讓我知道你在想什麼!