以楊輝三角為例,從內存角度簡單分析C語言中的動態二維數組


學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)這種最本質的方式

C語言中關於形參與實參關系

 

這里我們先講一講系統堆棧和系統堆:

        操作系統將內存分成:系統數據,系統功能調用(核心代碼)區域;用戶代碼和數據區域;系統堆棧區;系統堆區;

        系統堆棧是由編譯器自動分配,用於存放函數的參數值,局部變量的值等;

        而系統堆區是由程序員通過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語言中的動態二維數組


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM