前言
這兩天又在首頁看見指標的文章了,隨手再來寫一篇。本來想先寫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無關。
完。