C语言学习笔记 (005) - 二维数组作为函数参数传递剖析
前言
很多文章不外乎告訴你下面這幾種標(biāo)準(zhǔn)的形式,你如果按照它們來(lái)用,準(zhǔn)沒(méi)錯(cuò):
//對(duì)于一個(gè)2行13列int元素的二維數(shù)組 //函數(shù)f的形參形式 f(int daytab[2][13]) {...}//以下兩種可以忽略行數(shù) f(int daytab[][13]) {...}f(int (*daytab)[13]) {...}甚至?xí)腥烁嬖V你多維數(shù)組作為參數(shù)傳遞可以省略第一維,其他維不能省略。然而你對(duì)這種形式并不滿意:如果事先限定了二維數(shù)組的大小,函數(shù)的泛用性就要大打折扣了。因?yàn)槟阏嬲枰?#xff0c;是可以處理事先未知行數(shù)和列數(shù)的二維數(shù)組的函數(shù)。當(dāng)然也有文章提到類似下面的動(dòng)態(tài)分配的方式,但作為函數(shù)參數(shù)傳遞有時(shí)不能成功,令人疑惑。
int **array;//array[M][N] array = (int **)malloc(M *sizeof(int *)); for(i=0;i<M;i++)array[i] = (int *)malloc(N *sizeof(int));本文目的是深入剖析各個(gè)形式的二維數(shù)組,以及為了進(jìn)行參數(shù)傳遞,如何寫函數(shù)的形參表。更高維的數(shù)組可以做類似的推廣。
下面先進(jìn)行分析,文中討論的地址空間是虛擬地址空間,是程序員看到的地址空間,不是實(shí)際的物理地址空間。
?
?
1.基本形式:二維數(shù)組在棧上分配,各行地址空間連續(xù),函數(shù)參數(shù)使用文首提到的3種形式
最初接觸二維數(shù)組時(shí),可能只是在main()或某個(gè)函數(shù)里進(jìn)行聲明,然后直接使用:
...int array[M][N]; //array[][N] ={{...},...,{...}}; is ok//array[][] = {{...},...,{...}}; is wrong ...
//使用array[m][n]
這種分配是在棧上進(jìn)行的,能夠保證所有元素的地址空間連續(xù)。這樣,array[i][j] 和 *(*(array +i) +j)是一樣的,程序是知道array+i的i實(shí)際上偏移了i*N個(gè)單位,這也導(dǎo)致了在二維數(shù)組array[3][3]中,使用下標(biāo)array[2][1]和array[1][4]是訪問(wèn)的同一個(gè)元素,盡管后者的下標(biāo)對(duì)于一個(gè)3*3矩陣來(lái)說(shuō)是非法的,但這并不影響訪問(wèn)。
這種形式,無(wú)論是數(shù)組定義還是函數(shù)都不夠泛用,兩個(gè)維度在編譯前就定好了,唯一可以做的就是把維度M、N聲明為宏或者枚舉類型,但這仍不能避免每次修改后都要重新編譯。
?
2.數(shù)組傳參形式:二維數(shù)組在棧上分配,各行地址空間連續(xù),函數(shù)參數(shù)使用指針形式
當(dāng)把這種二維數(shù)組的指針直接作為參數(shù)傳遞時(shí),數(shù)組名退化為指針,函數(shù)并不知道數(shù)組的列數(shù),N對(duì)它來(lái)說(shuō)是不可見(jiàn)的,即使使用*(*(array +i) +j),第一層解引用失敗。這時(shí),編譯器會(huì)報(bào)warning,運(yùn)行生成的文件會(huì)發(fā)生segment fault。那么,為了指導(dǎo)這個(gè)函數(shù)如何解引用,也就是人為地解引用,需要把這個(gè)二維數(shù)組的首元素地址傳給函數(shù),于是就變成了下面的形式:
#include <stdio.h> #include <stdlib.h> #include <assert.h> int func(int *array, int m, int n) {int i,j;for(i=0;i<m;i++) {for(j=0;j<n;j++)printf("\t%d", *(array +i*n +j));printf("\n");}return 0; }int main(int argc,char** argv) {int m=3,n=3,i;int array[][3] = {{1,2,3},{4,5,6},{7,8,9}};func(*array,m,n);return 0; }也可以寫成
int fun(int *array,int m,int n) {int i,j;for(i=0;i<m;i++)for(j=0;j<n;j++)printf("%d ", *((int*)array + n*i + j));return 0; } int main() {int array[3][3] = {{1,2,3},{4,5,6},{7,8,9}};fun((int *)array,3,3);return 0; }但是意圖沒(méi)有上一種清晰,并不推薦。
你可能會(huì)問(wèn),為什么下面的不行?原因其實(shí)和上面提到的一樣,第一次解引用時(shí),函數(shù)并不知道數(shù)組的列數(shù),從而導(dǎo)致失敗。準(zhǔn)確的說(shuō),是因?yàn)閿?shù)組實(shí)際類型是int [3][3],在作為右值時(shí)可以被轉(zhuǎn)化為int (*)[3],它們都和int **不同,自然不可用。(感謝garbageMan在回復(fù)中指出)
int func(int **array, int m, int n) {...printf("\t%d", *(*array +i*n +j));... }int main() {int array[3][3] = {{1,2,3},{4,5,6},{7,8,9}};... func(array,3,3);... }?
?
3.動(dòng)態(tài)數(shù)組形式:二維數(shù)組在堆上分配,各行地址空間不一定連續(xù),函數(shù)參數(shù)使用指針形式
第2種雖然函數(shù)參數(shù)的限定降低了,但仍需要在棧上預(yù)先分配一定大小的二維數(shù)組,程序整體并不是完全的泛用。為了進(jìn)一步提高泛用性,把二維數(shù)組空間的分配也動(dòng)態(tài)化,使用malloc()在堆上分配空間,重復(fù)一下前言中的方式如下:
int **array; array = (int **)malloc(m *sizeof(int *)); for(i=0;i<M;i++)array[i] = (int *)malloc(n *sizeof(int));這時(shí),在分配空間的作用域里,對(duì)0<=i<M,0<=j<N,array[i][j]的訪問(wèn)完全沒(méi)有問(wèn)題。那么,對(duì)應(yīng)地,函數(shù)寫作
int func(int **array,int m,int n) {...printf("%d ", *(*(array+i)+j));... }值得注意的是,雖然malloc()每次分配的空間在地址上是連續(xù)的,但是多次malloc()分配的空間之間并不一定是連續(xù)的,這與在棧上分配的二維矩陣有著根本的不同,對(duì)于二維數(shù)組array[3][3],不能再用array[1][4]來(lái)訪問(wèn)array[2][1]了,前者地址越界。
?
4.折中形式:用堆上分配的一維數(shù)組表示二維數(shù)組,函數(shù)參數(shù)使用指針形式
用一維數(shù)組來(lái)實(shí)現(xiàn)二維數(shù)組,是一種折中方案,但是很好理解,也不易出錯(cuò)。這樣分配的數(shù)組空間是連續(xù)的。使用時(shí)需要把兩維下標(biāo)轉(zhuǎn)化為一維下標(biāo)。
#include <stdio.h> #include <stdlib.h> #include <assert.h> int func(int *array, int m, int n) {int i,j;for(i=0;i<m;i++) {for(j=0;j<n;j++)printf("\t%d",*(array+i*n+j));printf("\n");}return 0; }int main(int argc,char** argv) {int m,n,i;int *array;assert(argc == 3);m = atoi(argv[1]);n = atoi(argv[2]);array = (int*)malloc(m*n*sizeof(int));for(i=0;i<m*n;i++)array[i] = i;func(array,m,n);return 0; }?
?5.較新的編譯器:用棧上分配的直到執(zhí)行時(shí)才確定大小的二維數(shù)組
C90不支持這種形式,C99支持,因此一些較新的編譯器可以對(duì)下面的代碼進(jìn)行執(zhí)行。注意print()的參數(shù)順序不能改變。
void print(int x, int y, int a[x][y]){printf("\n");int i, j;for(i = 0; i < x; i++){for(j = 0; j < y; j++)printf("%d ", a[i][j]);printf("\n");} }// Function to initialize the two-dimensional array void init_2d(int *a, int x, int y){int i, j;for(i = 0; i < x; i++){for(j = 0; j < y; j++){a[i*y + j] = i + j;}printf("\n");} }int main(){int m , n ;scanf("%d %d",&m,&n);int a[m][n]; // a two dimensional whose size has been defined using variablesinit_2d(a, m, n);print(m, n, a); }這段代碼出自http://stackoverflow.com/questions/17181577/two-dimensional-arrays-in-c。
(2013.7.28更新)
? 另外,這種分配方式仍然是在棧上,相關(guān)討論可見(jiàn)于http://bbs.csdn.net/topics/90350681。
小結(jié)
- 其實(shí)所謂的二維數(shù)組,在K&R上只是指預(yù)先分配好大小的形如int a[M][M]這樣的數(shù)組,它存在于棧上;而實(shí)際使用的在堆空間利用malloc動(dòng)態(tài)分配空間的并不是這種,只是用的人多了,把后者叫成二維數(shù)組了(我不認(rèn)為把后者也稱為二維數(shù)組是標(biāo)準(zhǔn)的說(shuō)法)。再加上我們經(jīng)常用它來(lái)處理矩陣,“標(biāo)準(zhǔn)的”二維數(shù)組、“動(dòng)態(tài)的”“二維數(shù)組”、矩陣這三個(gè)概念就混在了一起。矩陣是可以用這兩種二維數(shù)組表示的,而對(duì)于這兩種不同的二維數(shù)組,函數(shù)傳參的方式也不完全相同,不能隨意混用。
- C99對(duì)于多維數(shù)組的描述:
If E is an n -dimensional array ( n ≥ 2) with dimensions i × j ×?...?× k , then E (used as?other than an lvalue) is converted to a pointer to an ( n ? 1)-dimensional array with?dimensions j ×?...?× k . If the unary * operator is applied to this pointer explicitly, or?implicitly as a result of subscripting, the result is the pointed-to ( n ? 1)-dimensional array?which itself is converted into a pointer if used as other than an lvalue. It follows from this
that arrays are stored in row-major order (last subscript varies fastest). - 棧上分配的二維數(shù)組數(shù)組名int array[3][3]的真實(shí)類型是int [ ][ ],在作為右值時(shí)才被轉(zhuǎn)化為(int *array)[N] (感謝?garbageMan指出),和int **是不同的。把前者進(jìn)行強(qiáng)制轉(zhuǎn)換為后者,在函數(shù)中對(duì)元素操作也會(huì)導(dǎo)致段錯(cuò)誤,下面用圖來(lái)說(shuō)明二者區(qū)別:
?
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/jikexianfeng/p/7297388.html
總結(jié)
以上是生活随笔為你收集整理的C语言学习笔记 (005) - 二维数组作为函数参数传递剖析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: EUI库 - EXML
- 下一篇: 6kyu Persistent Bug