指針和多維數組

數組名是特殊的指針

數組是一個特殊的指針,多維數組也是更為複雜的數組,它們的關係是什麼樣的呢?

我們通過一個簡單的例子來比較形象的瞭解指針和多維數組:

int a[2][3];

這是一個2*3的二維數組,首先我們清楚數組名就是指向數組首元素的常量指針(它不可以指向其他部分,可以對指向的元素進行任意修改);其次C語言中所謂的多維數組,即是數組的數組,2*3的二維數組,本質上為2個有包含3個int的數據的數組。所以現在我們就可以解釋a的含義:

a == &a[0]

那麼對於a[0]也有

a[0] == &a[0][0]

這時候我們就也可以得到另一個特殊的結論:a == a[0],字面上看起來很難理解,這是因為對於一個指針它指向一個元素有兩個要素:第一為這個元素的首地址,第二為這個元素的類型(這也是我們在使用指針所必要要求的必須指向與它類型相同的元素),所以a是一個指向包含3個int元素的數組指針,a[0]是指向int元素的指針。在比較兩者時即比較首地址,無疑都是這個二維數組的首地址。

#include <stdio.h>
 
 int main (void) {
  int a[2][3];
  if (a == &a[0])
      printf("yes!\n");
  if (a[0] == &a[0][0])
      printf("yes!\n");
  if (a == a[0])
      printf("yes!");
  return 0;
 }

但是兩者仍然有差別,這種差別是在類型上的,也可謂是根本上的,通過指針的增加運算我們可以看到他們的不同:

#include <stdio.h>
 
 int main (void) {
  int a[2][3];
  int *p = a;
  int *pp = a[0];
  if (a == a[0])
      printf("yes!\n");
  if (a+1 == a[0]+1)
      printf("yes!\n");
  else {
      printf("no!\n");
      printf("a = %p\n", a);
      printf("p = %p\n", a+1);
      printf("pp = %p", a[0]+1);
  }
 
  return 0;
 }
 
 /**
  yes!
  no!
  a = 0061FF00
  p = 0061FF0C
  pp = 0061FF04


  因為a為int (*)[3]類型,所以a+1移動了3*4=12個儲存單元,末位為C;a[0]為int *類型,所以a[0]+1移動了4個單元,這時候它們不再相等。
 **/

通過指針指向多維數組

因為C語言要求指針在指向一個元素時類型必須同元素一致,所以我們想使用指針指向數組時不能簡單使用指向int類型的指針。例如對於二維數組

a[2][3]。
 int main (void) {
  int a[2][3];
  int (*p)[3] = a;
  // int (*p1)[2] = a;這樣是不對的,因為C語言二維數組的聲明意義為2個長度為3的整型數組而不是相反的
  return 0;
 }

我們使用int (*p)[3]而不是int *p[3],因為前者表示指向含有三個整數數組的指針,後者為含有3個整型指針的數組。

我們也能通過解引用這樣的指針對於數組內容進行訪問。

我們通過討論數組名這個特殊的指針來了解指向多維數組的指針,a == &a[0],所以*a = a[0]為第一個指向長度為3數組的指針;a[0] == &a[0][0],所以*a[0] = a[0][0]這樣我們就訪問了二維數組的第一個元素,所以**a == a[0][0],我們可以通過多次解引用對多維數組元素進行訪問,對於二維數組a[1][2]等價於*(*(a+1)+2)

但是通過這樣對一個數組進行訪問往往會造成費解,而且有時候會出現不安全的現象:

#include <stdio.h>
 
 int main (void) {
  int a = 20;
  const int b = 30;
  int *p1 = &a;
  const int *p2 = &b;// 指向常量的指針指向常量
  const int **pp2;// 指向 指向常量的指針 的指針
  // p1 = p2;// 使用普通指針指向指向常量的指針這樣的行為並不可行!
  p2 = p1;// 把普通指針賦給指向常量的指針時時沒有問題的
 
  // 下面是一種值得警惕的情況
  printf("b = %d\n", b);
  pp2 = &p1;// 這樣的行為也是不安全的,稱為為嵌套指針類型賦值,這樣會使得const修飾符失效
  *pp2 = &b;
  *p1 = 10;
  printf("b = %d", b);
  return 0;
 }
 
 /**
  b = 30
  b = 10
 **/

我們驚奇發現b竟然也被修改了!因為我們讓pp2指向指針p1。然後因為pp2是指向常量的指針,所以*pp2也可以指向常量b,但是其實*pp2是p1!這時候我們就能通過p1修改常量b的值,這樣行為是很危險的!所以不要使用嵌套指針。