【原創】淺談指針(九)二維數組和多級指針相關

本文僅在博客園發佈,其他網站均為盜取,請自覺支持正版://www.cnblogs.com/jisuanjizhishizatan/p/15732439.html

前言

最近在學校裏面看見有人寫的代碼出錯了,如下:

void dfs(int graph[][],int used[][],int x,int y);

這樣的代碼必然會出錯(從語法的角度),有感而發,寫下這點內容。
最近也是好久不寫乾貨了,這次來多寫點乾貨。

1.指針做函數參數

1.1.一維數組(複習)

#include<bits/stdc++.h>
using namespace std;
void f(int a[]){
	cout<<sizeof(a)<<endl;
}
int main(){
	int a[]={1,2,3};
	cout<<sizeof(a)<<endl;
	f(a);
}

在64位機器輸出12 8,在32位機器輸出12 4.
通過這個實驗,我們得出的結論是:*當一維數組當做函數的參數傳遞時,它會被當做指向數組第一個元素的指針。

1.2.指向數組的指針

上一節中我們的試驗,只是指向數組的第一個元素的指針,並非指向數組的指針。
真正的指向數組的指針是這個東西:

#include<bits/stdc++.h>
using namespace std;
int main(){
	int a[]={1,2,3};
	int (*p)[3]=&a;
	cout<<sizeof(*p)<<endl;
}

輸出是12,也就是3個sizeof(int)。
可以看到,*p就是這個數組a,如果使用內存圖示的畫法,應該是如下的:

那麼,有人會問,這樣和指向數組首個元素的指針有什麼區別呢?當然,區別很大。
我們假設指向數組首個元素的指針為q,那麼,當執行p++;q++;時候,

數組會變成上面的樣子,也就是說,q增加的是sizeof(int),而p增加的是sizeof(a),也就是3個int的位子。

1.3.二維數組的尋址方式

在我們人類的理解中,數組a[2][3]是這樣排列的:

但是在機器的理解中,實際上是這樣排列的:

我們做一個實驗:

#include<bits/stdc++.h>
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++){
			printf("%p ",&a[i][j]);
		}
		printf("\n");
	}
}

結果如下:

00000000006ffe00 00000000006ffe04 00000000006ffe08
00000000006ffe0c 00000000006ffe10 00000000006ffe14

沒錯,是按照線性排列的。
而我們通常所熟知的a[i][j],它的尋址方式,實際是如下的:

*(*(a+i)+j)

我們看到下面的圖:

此時,a[0]是指向數組的指針,指向的是數組{1,2,3},a[1]指向的則是數組{4,5,6}。
那麼,a[0]+1,就是指向元素2的指針,再進行解引用*,得到的就是數值2.
即:*(a[0]+1)=a[0][1]
推廣開來就是上面的二維數組的展開式:a[i][j]=*(*(a+i)+j)

1.4.二維數組做函數參數

仿照1.1節中的實驗,我們在做一個類似的。

#include<bits/stdc++.h>
using namespace std;
int f(int a[2][3]){
	cout<<sizeof(a)<<endl;
}
int main(){
	int a[2][3]={1,2,3,4,5,6};
	cout<<sizeof(a)<<endl;
	f(a);
}

輸出:24 8(64位)或24 4(32位)
由此,我們發現,二維數組做函數參數時,同樣是傳遞了指針。
這裡值得注意的是,同一機型,我們一般要同時測試32位和64位的輸出結果,才能更好判斷。如果某個輸出值,在32位和64位下有兩倍的關係,一般就是用到了指針。
某些調試環境(例如DEV C++),可以選擇32位和64位的調試模式,

我們得出的結論是:二維數組作為函數的參數傳遞,傳遞的是指向數組的指針
那麼也就是說,這個函數和下面的函數同義。

int f(int (*a)[3]){
	for(int i=0;i<2;i++){
		for(int j=0;j<3;j++){
			cout<<a[i][j]<<" ";
		}
		cout<<endl;
	}
}

回到章節開頭的問題,為什麼函數參數的聲明中不能寫used[][]?
答案非常簡單,a[i][j]等同於*(*(a+i)+j),其中,
根據指針運算的原則,*(a+i),實際加上的不是i而是i*sizeof(a[0])
這樣一來,因為在函數參數中,a[0]大小未知,導致必須手動指定數組的寬度

否則由於寬度不同,導致無法解釋數組
如果把聲明寫成(*a)[4],二維數組的解讀也會完全不同。

會被解釋做這樣。
如果指定了數組的寬度,a[0]的大小也隨之確定,也能順利的尋址。

2.二級指針

2.1.概念

#include<bits/stdc++.h>
using namespace std;
int main(){
	int a=10;
	int *p=&a;
	int **pp=&p;
	printf("%d %d %d",a,*p,**pp);
}

輸出:10 10 10

其中,帶有兩個星號的**pp就是二級指針。
類型對照一覽表:

2.2.應用

在函數參數中,如果想要修改數值,必須使用指針(或引用)。
那麼,在函數參數中,如果想要修改指針自己的值,就需要指針的指針,也就是多級指針。

#include<bits/stdc++.h>
using namespace std;
void f1(char *s){
	s="abcd";
}
void f2(char **s){
	*s="abcd";
}
int main(){
	char *s="first";
	f1(s);puts(s);
	f2(&s);puts(s);
}

注意,二級指針和二維數組,指向數組的指針是三個完全不同的東西。
完。