二維數組做爲函數參數傳遞剖析(轉載)

前言

  不少文章不外乎告訴你下面這幾種標準的形式,你若是按照它們來用,準沒錯:程序員

複製代碼
//對於一個2行13列int元素的二維數組
//函數f的形參形式
f(int daytab[2][13]) {...}

//如下兩種能夠忽略行數
f(int daytab[][13]) {...}

f(int (*daytab)[13]) {...}
複製代碼

  甚至會有人告訴你多維數組做爲參數傳遞能夠省略第一維,其餘維不能省略。然而你對這種形式並不滿意:若是事先限定了二維數組的大小,函數的泛用性就要大打折扣了。由於你真正須要的,是能夠處理事先未知行數和列數的二維數組的函數。固然也有文章提到相似下面的動態分配的方式,但做爲函數參數傳遞有時不能成功,使人疑惑。數組

int **array;//array[M][N]
array = (int **)malloc(M *sizeof(int *));
for(i=0;i<M;i++)
    array[i] = (int *)malloc(N *sizeof(int));

  本文目的是深刻剖析各個形式的二維數組,以及爲了進行參數傳遞,如何寫函數的形參表。更高維的數組能夠作相似的推廣。app

  下面先進行分析,文中討論的地址空間是虛擬地址空間,是程序員看到的地址空間,不是實際的物理地址空間。函數

 


 

1.基本形式:二維數組在棧上分配,各行地址空間連續,函數參數使用文首提到的3種形式post

  最初接觸二維數組時,可能只是在main()或某個函數裏進行聲明,而後直接使用:this

複製代碼
...
    int array[M][N];
  //array[][N] ={{...},...,{...}}; is ok
  //array[][] = {{...},...,{...}}; is wrong
 
  
 
  
...
//使用array[m][n]

複製代碼

  這種分配是在棧上進行的,可以保證全部元素的地址空間連續。這樣,array[i][j] 和 *(*(array +i) +j)是同樣的,程序是知道array+i的i實際上偏移了i*N個單位,這也致使了在二維數組array[3][3]中,使用下標array[2][1]和array[1][4]是訪問的同一個元素,儘管後者的下標對於一個3*3矩陣來講是非法的,但這並不影響訪問。spa

  這種形式,不管是數組定義仍是函數都不夠泛用,兩個維度在編譯前就定好了,惟一能夠作的就是把維度M、N聲明爲宏或者枚舉類型,但這仍不能避免每次修改後都要從新編譯。.net


 

2.數組傳參形式:二維數組在棧上分配,各行地址空間連續,函數參數使用指針形式指針

  當把這種二維數組的指針直接做爲參數傳遞時,數組名退化爲指針,函數並不知道數組的列數,N對它來講是不可見的,即便使用*(*(array +i) +j),第一層解引用失敗。這時,編譯器會報warning,運行生成的文件會發生segment fault。那麼,爲了指導這個函數如何解引用,也就是人爲地解引用,須要把這個二維數組的首元素地址傳給函數,因而就變成了下面的形式:code

複製代碼
#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;
}
複製代碼

  可是意圖沒有上一種清晰,並不推薦。

  你可能會問,爲何下面的不行?緣由其實和上面提到的同樣,第一次解引用時,函數並不知道數組的列數,從而致使失敗。準確的說,是由於數組實際類型是int [3][3],在做爲右值時能夠被轉化爲int (*)[3],它們都和int **不一樣,天然不可用。(感謝garbageMan在回覆中指出)

複製代碼
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.動態數組形式:二維數組在堆上分配,各行地址空間不必定連續,函數參數使用指針形式

  第2種雖然函數參數的限定下降了,但仍須要在棧上預先分配必定大小的二維數組,程序總體並非徹底的泛用。爲了進一步提升泛用性,把二維數組空間的分配也動態化,使用malloc()在堆上分配空間,重複一下前言中的方式以下:

int **array;
array = (int **)malloc(m *sizeof(int *));
for(i=0;i<M;i++)
    array[i] = (int *)malloc(n *sizeof(int));

  這時,在分配空間的做用域裏,對0<=i<M,0<=j<N,array[i][j]的訪問徹底沒有問題。那麼,對應地,函數寫做

int func(int **array,int m,int n) {
    ...
    printf("%d ", *(*(array+i)+j));
    ...
}

  值得注意的是,雖然malloc()每次分配的空間在地址上是連續的,可是屢次malloc()分配的空間之間並不必定是連續的,這與在棧上分配的二維矩陣有着根本的不一樣,對於二維數組array[3][3],不能再用array[1][4]來訪問array[2][1]了,前者地址越界。


 

4.折中形式:用堆上分配的一維數組表示二維數組,函數參數使用指針形式

  用一維數組來實現二維數組,是一種折中方案,可是很好理解,也不易出錯。這樣分配的數組空間是連續的。使用時須要把兩維下標轉化爲一維下標。

複製代碼
#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.較新的編譯器:用棧上分配的直到執行時才肯定大小的二維數組

  C90不支持這種形式,C99支持,所以一些較新的編譯器能夠對下面的代碼進行執行。注意print()的參數順序不能改變。

複製代碼
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 variables
    init_2d(a, m, n);
    print(m, n, a);
}
複製代碼

  這段代碼出自http://stackoverflow.com/questions/17181577/two-dimensional-arrays-in-c

  (2013.7.28更新)

   另外,這種分配方式仍然是在棧上,相關討論可見於http://bbs.csdn.net/topics/90350681

  

小結

  • 其實所謂的二維數組,在K&R上只是指預先分配好大小的形如int a[M][M]這樣的數組,它存在於棧上;而實際使用的在堆空間利用malloc動態分配空間的並非這種,只是用的人多了,把後者叫成二維數組了(我不認爲把後者也稱爲二維數組是標準的說法)。再加上咱們常常用它來處理矩陣,「標準的」二維數組、「動態的」「二維數組」、矩陣這三個概念就混在了一塊兒。矩陣是能夠用這兩種二維數組表示的,而對於這兩種不一樣的二維數組,函數傳參的方式也不徹底相同,不能隨意混用。
  • C99對於多維數組的描述:

    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).

  • 棧上分配的二維數組數組名int array[3][3]的真實類型是int [ ][ ],在做爲右值時才被轉化爲(int *array)[N] (感謝 garbageMan指出),和int **是不一樣的。把前者進行強制轉換爲後者,在函數中對元素操做也會致使段錯誤,下面用圖來講明兩者區別:

  

 

 


做者:五嶽 
出處:http://www.cnblogs.com/wuyuegb2312 對於標題未標註爲「轉載」的文章均爲原創,其版權歸做者全部,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。

相關文章
相關標籤/搜索