2017-06-16 93 views
5

誰能解釋的值將如何存儲在這個數組中:初始化二維數組像一維數組

int a[2][3] = {1,2,3,4,5} 

使用二維數組,但像一維數組分配它。

+0

查找「行大訂單」。 –

+1

我很驚訝,我找不到這個問題的重複。 –

+0

爲什麼[0] [3]和[1] [0]都指向4? – Fullmetal

回答

5

他們被分配如下:

1 2 3 
4 5 0 

零是因爲你已經分配的大小6陣列,但只有指定的5個元素。

這被稱爲「行主要訂單」。

您可能希望稍微正式確定您的代碼。您的代碼是目前:

int a[2][3] = {1,2,3,4,5}; 

如果編譯這個與gcc main.c -Wall -pedantic --std=c99,你會得到一些警告:

temp.c:2:17:警告:缺少初始化左右括號[-Wmissing -braces]

解決此使用

int a[2][3] = {{1,2,3,4,5}}; 

這會給你一個新的警告:

temp.c:2:25:警告:在數組初始化多餘元素

解決此使用:

int a[2][3] = {{1,2,3},{4,5,0}}; 

這明確地表示數據因爲每個都有兩行三個元素。

上存儲器佈局

int a[2][3]思考將產生一個 「數組的數組」。這與「陣列指針數組」類似,但與之不同。兩者都具有相似的訪問語法(例如a[1][2])。但僅限於「陣列陣列」,您可以使用a+y*WIDTH+x可靠地訪問元素。

有些代碼可能澄清:

#include <stdlib.h> 
#include <stdio.h> 

void PrintArray1D(int* a){ 
    for(int i=0;i<6;i++) 
    printf("%d ",a[i]); 
    printf("\n"); 
} 

int main(){ 
    //Construct a two dimensional array 
    int a[2][3] = {{1,2,3},{4,5,6}}; 

    //Construct an array of arrays 
    int* b[2]; 
    b[0] = calloc(3,sizeof(int)); 
    b[1] = calloc(3,sizeof(int)); 

    //Initialize the array of arrays 
    for(int y=0;y<2;y++) 
    for(int x=0;x<3;x++) 
    b[y][x] = a[y][x]; 

    PrintArray1D(a[0]); 
    PrintArray1D(b[0]); 
} 

當你運行它,你會得到:

1 2 3 4 5 6 
1 2 3 0 0 0 

印刷b給出零(我的機器上),因爲它運行到未初始化的內存。結果是使用連續的內存可以讓你做一些方便的事情,讓所有的值都可以不需要雙循環。

+0

出於好奇,int a [2] [3] = {{1,2},{3,4},{5}}'會觸發什麼警告? –

+1

「這不一定如此。」 - 請詳細說明!「包含int [3]數組的數組可能在內存中不連續。」 - 那是錯的。 'int a [2] [3]'**是**數組的數組,並保證在內存中連續不斷!它不能是別的。如果你引用類似'int * a [3]'的東西:這是一個完全不同的數據類型,而不是一個數組數組! – Olaf

+0

@Olaf:的確如此。 'int a [2] [3]'在內存中是連續的,所有訪問都是有效的。但是,在您分開分配行的情況下,陣列數組上的'a [2] [3]'訪問模式不能保證有效。 – Richard

4

在C中,一維數組存儲在所謂的「行優先」順序在存儲器中的單個線性緩衝器中。行重大意味着最後一個索引因元素而變化最快。例如,列主要意味着第一個索引變化最快,正如它在MATLAB中所做的那樣。

您聲明的數組僅僅是2D,因爲編譯器通過爲您計算元素的線性地址來幫助您。計算一維數組中元素的地址爲linear[x] = linear + x。同樣,對於你的2D陣列,a[y][x] = a + 3 * y + x。一般來說,a[y][x] = a + num_cols * y + x

您可以將數組初始化爲單個元素向量,它將首先填充第一行,然後填充第二行,依此類推。由於您有兩行三個元素,第一行變爲1, 2, 3,第二行變爲4, 5, 0

只要編譯器至少關心,對行尾進行索引是完全有效的。在您給出的示例中,a[0][3]正在訪問三元素寬的數組中第一行的第四個元素。使用環繞式,您可以看到這只是第二行的第一個元素,更明確地指出爲a[1][0]

由於鬆散索引檢查,只要提供初始化程序,就可以完全忽略任何數組中的第一個索引。計算線性地址的公式不依賴於第一個索引(因爲它是主要的行),並且元素的總數由初始化程序本身指定。一個例子是int linear[] = {1, 2, 3};

請記住,數組的名稱也指向指向其第一個元素的指針。這些是可以用同一個名字訪問的兩種不同的東西。

+0

一般性聲明「C沒有對指針算術和數組索引進行任何邊界檢查」實際上並非如此,因爲甚至數組邊界之外的指針算術都是未定義的行爲;它甚至是一個討論,如果違反子數組的邊界是未定義的行爲。 –

+0

@StephanLechner。雖然確實,編譯器明確允許你觸發這種未定義的行爲,但這還不夠公平。正如您所指出的那樣,我刪除了這些違規行,因此與討論無關。 –

+0

允許此初始化程序實際上是一項遺留問題,與內存中的數組佈局無關。 Moern編譯器會警告這些代碼。 – Olaf

2

從解釋如何訪問像解碼「a[1][2]」這樣的二維數組的定義來看,「由此得出數組以行優先順序存儲」(例如,參見此online C standard comitee draft/array subscripting)。 這意味着對於訪問a[r][c]的數組int a[ROWS][COLUMNS],按照(r*COLUMNS + c)計算int值的偏移量。

因此對於陣列int a[2][3],接入a[0][1]已偏移0*3 + 1 = 1和接入a[1][0]具有偏移1*3 + 0 = 3。也就是說,a[0][3]可能會導致抵消3,而a[1][0]肯定會導致3。我寫了「might」,因爲我認爲使用a[0][3]訪問數組int a[2][3]是未定義的行爲,因爲最後一個下標的範圍是0..2。因此根據6.5.6(8),表達式a[0][3]正在討論的範圍之外尋找子陣列a[0],例如,here

現在來解釋如何解釋int a[2][3] = {1,2,3,4,5}。這項聲明是在section 6.7.9 of this online C standard comitee draft定義的初始化,以及第(20)(26)描述了這裏所需要的東西:

(20)如果聚集或聯合包含的元素或成員是 聚集或工會,這些規則遞歸地適用於 子集或包含的聯合。如果 子集或包含的聯合的初始化程序以左大括號開始,則大括號及其匹配的右大括號 所包含的初始值設定項會初始化子集或包含聯合的 的元素或成員。否則,從清單中只有足夠的初始化程序是 是考慮到子集的要素或成員或包含工會的第一個成員的 ;任何其餘的初始化程序 都將被初始化爲當前子集聚或包含的聯合是其一部分的集合 的下一個元素或成員。

(21)如果在一個大括號內的列表更少初始化值多於 是用於初始化已知大小的陣列之外還有 是元件的元件或聚集體的成員,或更少的字符在 字符串文字在數組中,聚合的其餘部分應該是 ,隱含地初始化爲具有靜態存儲持續時間的對象。

26實施例

(3)該聲明

 int y[4][3] = { 
      { 1, 3, 5 }, 
      { 2, 4, 6 }, 
      { 3, 5, 7 }, 
     }; 

是與完全括號初始化一個定義:13,和5初始化的y第一行(陣列對象y[0]),即 y[0][0],y[0][1]y[0][2]。同樣,接下來的兩行初始化爲 y[1]y[2]。初始化程序提前結束,所以y[3]用 零初始化。正是同樣的效果可能是由

 int y[4][3] = { 
      1, 3, 5, 2, 4, 6, 3, 5, 7 
     }; 

y[0]的初始化不與左括號開始實現的,因此從列表中的三個項目中使用。同樣,接下來的三個 連續取得y[1]y[2]

+1

cppreference不是權威資源。爲什麼不參考標準,最後的草稿? – Olaf

+0

@Olaf:對,cppreference不是規範性的;但我只是在尋找示例,而我在這裏找到更簡短的參考。無論如何,你有最終的網上草稿嗎?我總是找到我用於引用的那個... –

+1

@ StephanLechner--這是我的[C11 Draft Standard]的轉到鏈接(http://port70.net/~nsz/c/c11/n1570.html#內容)。引用非常方便。 –