因為做leetcode的一道算法題https://leetcode-cn.com/problems/regular-expression-matching/,需要用到二維數組,這里就詳細介紹一下數組
一維數組
char * a = (char*)malloc(8 * sizeof(char)); memset(a, 0, 8); for (int i = 0; i < 8; i++) { *(a + i) = 'a' + i; } for (int i = 0; i < 8; i++) { cout << *(a+i); } for (int i = 0; i < 8; i++) { cout << a[i]; } free(a);
輸出內容abcdefghabcdefgh
這樣申請與char a[8]是一樣的,不管是底層實現還是使用,都是一樣的,這就是一個8個字節長度的數組。我們看內存,也是一段連續的數據。
釋放內存的時候,系統可以根據內存管理把這連續的8個字節的空間回收。
二維數組
我們先看一下正常的用法
char a[8][4] = { 0 }; for (int i = 0; i < 8; i++) { for (int j = 0; j < 4; j++) { a[i][j] = 'a' + i; } } for (int i = 0; i < 8; i++) { for (int j = 0; j < 4; j++) { cout << a[i][j]; } }
輸出結果 aaaabbbbccccddddeeeeffffgggghhhh
我們看一下內存
是一段連續的內存,數組a[n][m],按照m跳度,前m個是第一個n的序列,然后是第二個n的m個序列,實際上這是一個一維數組。
看看我們第一種實現
char** a = (char**)malloc(8 * sizeof(char*)); for (int i = 0; i < 8; i++) { *(a + i) = (char*)malloc(4 * sizeof(char)); } for (int i = 0; i < 8; i++) { for (int j = 0; j < 4; j++) { a[i][j] = 'a' + i; } } for (int i = 0; i < 8; i++) { for (int j = 0; j < 4; j++) { cout << a[i][j]; } }
a是一個數組,也就是a指向了申請的一塊空間,空間是8個char*大小。然后為a的每一個數組元素,又指向了一塊空間,空間是4個char大小。內存空間如圖所示,a指向了一塊空間,有8個元素,每個元素的空間用來保存了一個char*的數據,也就是char的指針,a算是一個指向指針的指針。a是一個指針,指向了一塊數據,這塊數據保存的是一個指針,指向了一塊數據,這塊數據保存的是一個4字節的char。
我們看一下實際的內存地址
這是char*數組a的數據,8個char*的空間,標藍的是第一個a+0保存的數據,是一個指針地址,因為是32位的,所以是4個字節,翻譯過來就是0X00557600
我們看一下a的第一塊數據指向的內容,是一塊數據,保存了aaaa,四個a。
多維數組可以這樣理解,一維數組a指向了一塊數據,保存的是字符;二維數組指向了一塊數據,保存的相當於是一個一維數組指針的列表,每個二維數組元素相當於一個一維數組;三維數組就是保存了一個二維數組指針的列表,以此類推,一層層解析。
上面這種二維數組是如何訪問數據呢,先通過a+i訪問到a的元素指針,然后通過*(a+i)訪問到數據,這個數據又是個數組,通過*(a+i)+j訪問到元素指針,再通過*(*(a+i)+j)訪問到數據,最后提醒,別忘了釋放空間。
for (int i = 0; i < 8; i++) { for (int j = 0; j < 4; j++) { cout << *(*(a + i) + j); } } for (int i = 0; i < 8; i++) { free(*(a + i)); } free(a);
二維數組的第二種實現
我們看到,如果通過char a[8][4]申請一個二維數組,實際上空間是連續的,我們能不能模擬呢,這樣整塊申請內存,效率更高。代碼如下
char* a = (char*)malloc(8 * 4 * sizeof(char)); for (int i = 0; i < 8; i++) { for (int j = 0; j < 4; j++) { *(a + i * 4 + j) = 'a' + i; } } for (int i = 0; i < 8; i++) { for (int j = 0; j < 4; j++) { cout << *(a + i * 4 + j); } } free(a);
申請了一個一維數組,大小是a[n][m]中n*m個數組元素個數。訪問呢,就模仿二維數組的訪問,m個元素是一組。內存如下
這里有一個小小的一問,char a[8][4]這種可以通過 a[1][2]訪問,但是這種方式不行。char[8][4]系統知道是一個二維數組,保留了它的信息,知道如何跳,而char* a = (char*)malloc(8 * 4 * sizeof(char));系統認為是一維數組,按照二維的那種方式跳,就會運行錯誤。
插曲
除了malloc,也可以用new,更方便一些
char * a = new char[8]();
減少了初始化的步驟,這里就是創建一塊空間,大小是8,單位是char,調用()初始化。這里()的意思就是調用默認構造函數等於new char[8]{}
二維數組的第三種方法
上面的方法空間在一起了,但是無法用二維數組的方式訪問 ,很別扭,那么有沒有動態二維數組呢,上面插曲的new為我們提供了方法。
auto a = new char[8][4](); for (int i = 0; i < 8; i++) { for (int j = 0; j < 4; j++) { a[i][j] = 'a' + i; } } for (int i = 0; i < 8; i++) { for (int j = 0; j < 4; j++) { cout << a[i][j]; } } delete[](a);
auto a = new char[8][4]()到底是什么
這種方法的實質是什么呢?實際上就是第二種方法,只不過告訴了系統,這是一個二維數組。那么二維數組的指針怎么定義呢?看下面的示例
char(* a)[4] = (char(*)[4])malloc(8 * 4 * sizeof(char)); for (int i = 0; i < 8; i++) { for (int j = 0; j < 4; j++) { a[i][j] = 'a' + i; } } for (int i = 0; i < 8; i++) { for (int j = 0; j < 4; j++) { cout << a[i][j]; } } free(a);
進一步了解詳情請看數組、函數與指針