【原創】淺談指標(十三)指向陣列的指標

計算機知識雜談發表於2022-05-10

前言

這兩天又在首頁看見指標的文章了,隨手再來寫一篇。本來想先寫static的下集的,後來發現似乎寫的有些問題,草稿已經在寫了,預計後面不久再發布。
指標其實是C++或是C語言中必不可少的一部分。即使說,很多情況下我們並不會直接使用到指標,但是指標的一些知識同樣在其它的地方(哪怕看似和指標無關的地方)奏效。

1.預備知識的複習

1.1.陣列

陣列在表示式中,其最高一維會被轉化為指標。對於一維陣列,其呈現的形態就是在表示式中轉化為指標。函式的引數也是表示式。

#include<iostream>
using namespace std;
void func(char s[]){
  cout<<sizeof(s)<<endl;
}
int main(){
  char s[]="hello";
  cout<<sizeof(s)<<endl;
  func(s);
}

在64位環境下的輸出:

6
8

1.2.VLA

在C99中,對於非static修飾的區域性變數,可以在定義中,陣列的元素寫成變數,這一功能叫做可變長陣列(Variable Length Array,簡稱VLA)。
下面是一段倒序輸出陣列的程式碼:

#include<iostream>
using namespace std;

int main(){
    int n;
    cin>>n;
    int s1[n];
    for(int i=0;i<n;i++)cin>>s1[i];
    for(int i=n-1;i>=0;i--)cout<<s1[i]<<" ";
}

其中,"int s1[n]"一句就是運用到了可變長陣列。
但在C11中,VLA降為了可選功能,而且VLA只能使用在非static的區域性變數,用途相比起來也比較有限。當然,很多情況下還是使用這一功能比較方便。

2.指向陣列的指標

指標既然可以指向單個元素,因此也可以指向其他的內容,例如陣列。

2.1.指向二維陣列

二維陣列的型別是int [][],放入表示式中,最高一維會被轉化為指標,也就是int (*)[]。我們就可以使用這樣型別的指標來指向這個二維陣列。

#include<iostream>
using namespace std;

int main(){
    int a[2][3]={1,2,3,4,5,6};
    for(int i=0;i<2;i++){
        for(int j=0;j<3;j++){
            cout<<a[i][j]<<" ";
        }
        cout<<endl;
    }
    
    int (*p)[3]=a;
    for(;p!=&a[2];p++){
        for(int j=0;j<3;j++){
            cout<<(*p)[j]<<" ";
        }
        cout<<endl;
    }
}

在程式碼的第13行中,宣告瞭int (*p)[3],它表示“指向元素個數為3的陣列的指標”。
之前的文章中提到過網頁連結,二維陣列在記憶體中是連續排列的:

如果使用普通的int*指標指向這個陣列,每次前進的是sizeof(int)個位元組:

而使用“指向陣列的指標”,每次前進的是這個陣列的大小(即int[3]的大小,3*sizeof(int))
如圖所示,一次就前進了0x0c。

2.2.注意事項

指向陣列的指標,型別表示:int (*a)[2];
而在表示式中,二維陣列int a[2][2];的最高一維轉化為了指標,因此就變為了“指向陣列的指標”。

另外,“指向陣列的指標”和“指向陣列首個元素的指標”是截然不同的。在表示式中,陣列的最高維會被轉化為指標,此時的指標,指的是“指向陣列初始元素的指標”。

int (*array_p)[3];

可以用來宣告一個指向“長度為3的陣列”的指標。

在陣列前,加上&取地址,返回的就是指向陣列的指標:

int array[3];
int (*array_p)[3];

array_p=&array;

原本是int[3]型別,加上一層*,結果就是int (*)[3]
對於scanf在輸入字串的時候,很多人還是這樣寫的:

scanf("%s",&s);

這樣寫看似沒有問題(實際上,由於後面的s在可變長引數中,沒有原型宣告,也不會出問題),但是是錯誤的寫法。因為s是陣列,因此加上&後變為了“指向陣列的指標”,而%s只需要傳入一個指向char的指標。
對於指向陣列的指標,+1之後,真正加上的是它指向的陣列的長度。(由於指標前進1,前進的是它所指向的值的大小)參考下圖:

2.3.陣列與指標之間的轉化

  • 規則:在表示式中,陣列的最高一維會被轉化為指標。
    • 特例1:對於sizeof(表示式)的形態,這是一個特例。(否則陣列的長度是無法輸出的)
    • 特例2:在初始化char陣列的時候,編譯器會自動把它解釋為初始化的列表。
char s[]="abc";

本質上是:

char s[]={'a','b','c','\0'};

的簡便寫法。這種解讀只有在初始化列表的時候可行。

  • 規則2:當陣列解讀為指標時,這個指標不可作為左值。
    • 左值在英語中稱為"lvalue",但是l並不代表left,而是locator(表示位置的事物)的意思。本質上,左值就是指可以出現在表示式的左邊,確切的說,就是有自己的記憶體空間,可以被賦值的東西、
    • 例如a=3中,a就是左值。而在a+1=3賦值語句中,由於a+1沒有自己的記憶體空間,無法被賦值,因此它不是左值。

例如:

char s[10];
s="abc";

這段程式碼是錯誤的。

2.4.應用

在函式的引數中,二維陣列會被解讀為指向陣列的指標。

int f(int a[10][10]);

與下面等價:

int f(int a[][10]);
int f(int (*a)[10]);

但是注意,不能這麼寫:

int f(int a[][]);

因為指向陣列的指標需要知道它的長度,不然在a++的時候,就不知道前進多少位置了。
當第二維的數字是2的時候,每次的前進是這樣的:

而當第二維的數字為5,每次的前進是這樣的:

而最高一維可以省略,因為無論最高維是多少,都和前進的位元組數無關。
對於陣列a[m][n],二維陣列的公式是a[i][j]=*(*(a+i)+j)。本質上說,二維陣列可以看做“陣列的陣列”。
其中(a+i)每一次增加的長度也就是sizeof(a[i]),而sizeof(a[i])取決於它指向的內容(a可看做指向陣列的指標,它的指向是j相關的一維),因此長度就是isizeof(int)n。
(a+i)+j中,加上的j前進的長度是sizeof(int)。

由此,我們可以看出,二維陣列的定址和j無關。

完。

相關文章