學C語言,一定繞不過指針這一大難關,而指針最讓人頭疼的就是各種指向關系,一階的指針還比較容易掌握,但一旦階數一高,就很容易理不清楚其中的指向關系,現在我將通過楊輝三角為例,我會用四種方法從內存的角度簡單分析動態二維數組,若有不足或錯誤之處,還請指出!
在講這之前,以一維數組為例,先重新認識一下數組:
int array[5] = {1, 2, 3, 4, 5};
首先數組名稱是該數組的首地址常量,即數組名稱就是指針,就有&array[0] == array!
那么我們可以推出*array == array[0] == 1;
這里引入一個概念“指類”(這個概念沒有在正規場合出現過,只是我為了方便分析而引入的),其表示指針所指向的空間的類型!array是一個int*類型的指針,那么它的指類就是int類型(比較容易的記憶,就是指針的類型去掉一個*就是其指類!);
其實array[0]是一個表象,其本質應該是*array ;
我們的array是局部變量,在系統堆棧中申請了sizeof(int)*5,即20字節大小的空間,用於存放5個整型數!
因為array的值是該數組首元素的地址(即首地址),那么array+1的意思就是給該數組的首地址這個值增加了一個int類型空間字節數,也就是4字節,從而array+1的值就應該是該數組的下一個int類型元素的地址,即&a[1],所以就有array+1 == &a[1];
那么array加幾加幾,加的實際就是多少個指類空間大小!
那么*array就可以理解成*(array + 0),同理,array[1] ==*(array + 1),array[2] == *(array + 2)......
上式還可以由加法交換律變形得到array[1] == *(1 + array),那么array[1]透過這一本質來看,其也可以變形成1[array];
*這樣寫編譯器不但不會報錯,而且連警告都不會有,但不建議這樣書寫!
*如果對我上文提到的系統堆棧不了解的話,強烈建議看一看下面的這個博客,后文全部涉及到內存!
*后文我不會用array[index]這種方式,而是用*(array + index)這種最本質的方式
這里我們先講一講系統堆棧和系統堆:
操作系統將內存分成:系統數據,系統功能調用(核心代碼)區域;用戶代碼和數據區域;系統堆棧區;系統堆區;
系統堆棧是由編譯器自動分配,用於存放函數的參數值,局部變量的值等;
而系統堆區是由程序員通過malloc/calloc函數自主申請的空間,系統堆的空間要遠遠大於系統堆棧的空間,但切記,一定要在使用完畢后,通過free函數釋放掉所申請的空間。
*C語言(包括C++)不像Java那樣有gc(垃圾回收)機制,gc機制大大減少了程序員的工作量,程序員在Java中通過new申請空間時,只需負責申請,gc會幫助善后(實則是Java的JVM),而C/C++需要程序員自主釋放,所以java相比C++要容易掌握!
----------------------------------------------------------------------------------------------------------------------------------
楊輝三角:
關於楊輝三角如何計算得到的問題,我就不累贅了(*^_^*)
方法一:
如圖,我們給出了一個六層的楊輝三角,通常的話,我們會給一個靜態的二維數組用來存放這個楊輝三角:
int yangHui[row][row];
但是這會生成一個row行×row列的空間,而我們實際用到的的空間要比這小,除了最后一層,其他每一層都會有浪費的空間,為了避免這樣的情況,我們就應該想到動態的數組,根據當前行數,通過malloc/calloc動態申請每一層的空間。所以,我們可以用如下的方式表示這個二維數組:
int *yangHui[row];
這種定義看起來很奇怪,其實它完全就是一個一維數組,這個一維數組的大小是row,只不過這個一維數組的每一個元素是由int *類型所組成,其本質就是一個一維的指針數組!我們可以把它定義成以下這種形式:
typedef int* type; type yangHui[row];
這樣看的話就比較好理解了,他就是一個類型為type類型的一維數組!而type就是int*,那么,這個一維數組存放的每一個元素就應該是一個int*類型的的值,那么這個值完全就可以是一個int類型的一維數組的首地址!即yangHui數組里面存放的是row個一維數組的首地址!
鋪墊工作完成,下來我們就來生成楊輝三角:
*由於楊輝三角往后的數字越來越大,故以下代碼都用long類型!先假定要生成的楊輝三角的層數num = 5;
void creatYangHuiOne(int num); void creatYangHuiOne(int num) { long *yangHui[num]; int row; int col; for (row = 0; row < num; row++) { *(yangHui + row) = (long *) calloc(sizeof(long), row + 1); for (col = 0; col <= row; col++) { *(*(yangHui + row) + col) = (row == col || col == 0) ? 1 : *(*(yangHui + row - 1) + col - 1) + *(*(yangHui + row - 1) + col); } } // 關於showYangHuiTriangle函數我會在最后給出,只是為了打印好看,不做重點! showYangHuiTriangle(yangHui, num); for (row = 0; row < num; row++) { free(*(yangHui + row)); } }
多次循環得到下列關系
方法二:
void creatYangHuiTwo(int num); void destoryYangHui(long **yangHui, int num); void creatYangHuiTwo(int num) { long **yangHui = NULL; int row; int col; yangHui = (long **) calloc(sizeof(long *), num); for (row = 0; row < num; row++) { *(yangHui + row) = (long *) calloc(sizeof(long), row + 1); for (col = 0; col <= row; col++) { *(*(yangHui + row) + col) = (row == col || col == 0) ? 1 : *(*(yangHui + row - 1) + col - 1) + *(*(yangHui + row - 1) + col); } } showYangHuiTriangle(yangHui, num); destoryYangHui(yangHui, num); } void destoryYangHui(long **yangHui, int num) { int row; for (row = 0; row < num; row++) { free(*(yangHui + row)); } free(yangHui); }
方法三:
long **creatYangHuiThree(int num); long **creatYangHuiThree(int num) { long **yangHui = NULL; int row; int col; yangHui = (long **) calloc(sizeof(long *), num); for (row = 0; row < num; row++) { *(yangHui + row) = (long *) calloc(sizeof(long), row + 1); for (col = 0; col <= row; col++) { *(*(yangHui + row) + col) = (row == col || col == 0) ? 1 : *(*(yangHui + row - 1) + col - 1) + *(*(yangHui + row - 1) + col); } } return yangHui; }
與方法二基本一樣,只不過返回值是long **類型,將creatYnaghHuiThree函數中yangHui的值即在系統堆中申請的空間的首地址addressRow作為返回值返回!該空間不會隨着子函數的調用結束而消失,需要在主函數中釋放!
方法四:
void creatYangHuiFour(long ***yangHui, int num); void creatYangHuiFour(long ***yangHui, int num) { int row; int col; *yangHui = (long **) calloc(sizeof(long *), num); for (row = 0; row < num; row++) { *((*yangHui) + row) = (long *) calloc(sizeof(long), row + 1); for (col = 0; col <= row; col++) { *(*((*yangHui) + row) + col) = (row == col || col == 0) ? 1 : *(*((*yangHui) + row - 1) + col - 1) + *(*((*yangHui) + row -1 ) + col); } } }
當creatYangHuiFour函數調用結束,棧底棧頂指針回落,系統堆棧申請的子函數的局部變量都奔釋放,但是主函數的yangHui空間的值通過指針運算已經由NULL變為ddressRow,而這個空間是在系統堆中申請的,不會隨着子函數的調用結束而消失,即該空間還未被釋放,故需要在主函數中釋放!
打印函數及主函數:
void showYangHuiTriangle(long **yangHui, int num); int getMaxNumberLength(long num); int getMaxNumberLength(long num) { int count = 1; while (num/=10) { ++count; } return count; } void showYangHuiTriangle(long **yangHui, int num) { int len = getMaxNumberLength(*(*(yangHui + num -1) + num/2)); int i; int j; int row; int col; for (row = 0; row < num; row++) { for (i = 0; i < num - row - 1; i++) { for (j = 0; j < len; j++) { printf(" "); } } for (col = 0; col < row + 1; col++) { printf("%ld", *(*(yangHui + row) + col)); if (getMaxNumberLength(*(*(yangHui + row) + col)) < len) { for (j = 0; j < len - getMaxNumberLength(*(*(yangHui + row) + col)); j++) { printf(" "); } } for (j = 0; j < len; j++) { printf(" "); } } printf("\n"); } } int main() { long **yangHui = NULL; int num; printf("請輸入行數:\n"); scanf("%d", &num); creatYangHuiOne(num); creatYangHuiTwo(num); //yangHui = creatYangHuiThree(num); creatYangHuiFour(&yangHui, num); showYangHuiTriangle(yangHui, num); destoryYangHui(yangHui, num); return 0; }
輸出如圖:
感謝您的閱讀(*^_^*)
我在CSDN放了一份以楊輝三角為例,從內存角度簡單分析C語言中的動態二維數組