緣起於語言,情深於內存。(由c語言數組引入個人學習近期總結)

最近的大學生活就像是一個穩穩的循環,它就在那一直轉來轉去,出口條件貌似已經被賦值爲1了,我有點南~~~(つまらない! )。但多多少少還是有點收穫的,就像是那個自增條件,雖食之無味,但棄之可惜,願積水成淵,有蛟龍生焉。(廢話莫多說,言歸且正傳)。

在學習完分支和循環結構之後,就接觸了比較重要的函數和數組這兩塊。同時在老師的講解後,我深刻的認識到了,編程並不只是活用語言,對計算機這一物體深入瞭解纔是應有之道。

人生若只如初見

函數

在函數部分,比較重要的有函數的傳參和函數的調用這兩塊。

函數傳參:

函數傳參有傳值傳參和傳址傳參兩種方式。**

int exchange1(int a, int b)
{
	a = a^b;
	b = a^b;
	a = a^b;
	printf("exchange1內  :a=%d b=%d\n", a, b);
}
int exchange2( int*p,int*q)
{
	*p = *p^*q;
	*q = *p^*q;
	*p = *p^*q;
	printf("exchange2內  ;a=%d b=%d\n", *p, *q);
}

void main()
{
	int a = 3, b = 6;
	exchange1(a, b);
	printf("exchange1外  :a=%d b=%d\n", a, b);
	exchange2(&a, &b);
	printf("exchange2外  :a=%d b=%d\n", a, b);
	system("pause");
}

其中exchange1()函數中傳參是a,b兩個實參。exchange2()函數中是a,b兩個實參的地址。暨分別對應兩種傳參方式。在函數調用過程中,在被調函數內的結果和在main函數中的結果我們都將其打印出來就有一下結果。
在這裏插入圖片描述
由此可見,在實現兩數交換的這種操作需求下傳址傳參可以改變實參的值,而傳值傳參不能改變實參的值。同時注意到在exchange1內 a和b是在被調函數中完成了交換的。(這一點接下來說)。
這是傳值傳參和傳址傳參兩種方法的區別。在實際操作中還是需要根據自己的操作需求來選擇到底使用哪一種傳參方式。兩種方式的優缺點如下。
傳值傳參;缺點,不能通過形參的改變來改變外部實參
優點:與傳址傳參的缺陷相對。
傳址傳參(指針)(注:在傳入數組地址時,數組會發生降維問題。小生還尚未學習到哪裏,所以這一點希望讀者能去參考其他大佬的博客學習一下。(我與其看了沒搞懂就寫,還不如不寫,免得誤導人 ))
** 優點**:可以通過形參的改變來改變外部實參。
缺陷:1.代碼的可讀性比較差。
2.代碼的安全性比較低(指針在傳參是一定要判空---->NULL)
3.可能會通過形參影響外部

函數調用

函數調用有鏈式調用和嵌套調用兩種 。其中嵌套調用中有一種特別的調用方式暨遞歸調用。

int max_(int a,int b)
{
	return a > b ? a : b;
}
void MAX(int a, int b,int c,int d)
{
	int x = max_(a, b);
	int y = max_(c, d);
	int z = max_(x, y);
	printf("MAX=%d\n", z);
}
void main()
{ 
	int a = 9, b = 6, c = 3, d = 12;
	printf("max_(a, max_(b, max_(c, d)))=%d\n", max_(a, max_(b, max_(c, d))));
	MAX(a, b, c, d);
    system("pause");
}

其中main()函數中調用了2個函數,分別是max_()和MAX()函數。
printf("max_(a, max_(b, max_(c, d)))=%d\n", max_(a, max_(b, max_(c, d))));
這一段中的被打印部分爲max_函數爲鏈式調用(把一個函數的返回值作爲另外一個函數的參數即爲鏈式調用(或許用鏈式訪問這個詞更好))。

而MAX函數中調用了max_函數。這種類似於A函數中使用B函數的方法叫做嵌套調用。與嵌套循環一個道理。
至於遞歸。則是函數A繼續調用函數A。只到抵達出口條件。
使用遞歸調用有兩大前提
1.問題規模不斷縮小化,縮小後還是相同的解決方法。2.能找到出口條件。缺一不可。如果問題規模不能縮小,或者縮小後解決方法改變,那麼遞歸的使用就是毫無意義的。如果沒有明確的出口條件,則函數可能會一直遞歸下去,造成死遞歸的現象,(卡的爆炸,電腦上天 !)。
遞歸不是萬能的,沒有遞歸是萬萬不能的
爲什麼呢?首先,遞歸不是萬能的,遞歸方法實際上體現了“以此類推”、“用同樣的步驟重複”這樣的思想,它可以用簡單的程序來解決某些複雜的計算問題,但是運算量較大。在非此類問題的解決方法上,一般都是使用迭代方法,迭代方法速度快,且內存佔用較少,所需時間少(爲什麼?)。但是,沒有遞歸是萬萬不能的。在如漢諾塔這種重複性高,解決邏輯相同的問題上,使用遞歸可以有效地解決程序員的思考時間,減少代碼書寫的時間。(能寫幾行的我絕對不寫幾百行233 )。

消耗空間和時間的原因。

每一次函數調用,都需要在內存空間的棧上開闢空間,以執行被調函數和存放臨時變量。背調函數一旦執行完,纔會釋放該處空間,同時處於該空間內的臨時變量等臨時數據都會被釋放(前文中max_()函數中ab交換成功而函數執行完畢後ab並沒有完成交換的原因所在)。
同時在每一次開闢空間和釋放空間時,都會有時間的消耗。所以函數調用是具有時間成本和空間成本的。

在非遞歸調用的其他函數調用中。背調函數一旦執行完,釋放空間,完成調用。哪怕是多次的嵌套調用也不會消耗太多的時空成本。但是遞歸調用一旦問題規模較大,或者規模縮小的較小時,就需要通過增加遞歸次數來靠近出口條件。
次數一多,時空成本一旦累計起來。就會很顯眼,造成明顯的延遲和卡頓。

先入後出,棧區原理(才疏學淺,個人現階段看法)

上文提到,在函數調用是會在內存空間的棧上開闢空間。那麼遞歸在棧上是如何運作的呢?
在這裏插入圖片描述
因爲函數是先調用,運行完畢後再釋放的。而上圖這個遞歸在return語句中 return的後半句有調用,所以,在第一次調用時,運行return時會先執行text(a-1)這個函數,從而實現函數調用。但是這個時候text(a)函數並沒有運行完畢,他的空間還存在,同理一次類推,只有當最後一個函數既text(1)執行完畢時,返回return1後再一次進入到text(2)這時,text(2)纔算執行完畢,釋放空間後再次向上返回一個值,完成後text(3)纔算是運行完畢,重複以往的網上實行這個過程,只有當所有的調用全部完畢後,最初的調用text(a)才能完成。既遞歸的第一次調用是最早產生的,也同時是最遲消失的。我們將這兩個過程稱爲入棧和出棧。從時間角度上來說,遞歸的從棧上來看就是——先入棧者後出棧,後入棧者先出棧。
(本人學習尚且不是很深入,可能會有錯誤,若有用處,自然最好,若無用處,願博君一笑)

數組

這裏住要是從一維數組入手推廣到多維數組。
衆所周知,數組是具有相同數據類型的元素組合。數組的定義和初始化,是現在內從空間上開闢出一段連續的內存空間,然後載按照數據類型將其劃分。由此可見一維數組的內存地址時連續的 。

在這裏插入圖片描述
如圖所示。
那麼二維數組的地址時什麼情況呢。
在這裏插入圖片描述
那麼三維數組呢
在這裏插入圖片描述
以此類推,多維數組在內存空間上也是連續的。
其次,再仔細觀察,會發現,所有的地址也都是遞增的。
所以得出結論,所有的數組,無論是幾維的,在內存空間上他的每一個元素的地址都是連續且遞增的(**連續並不是完完全全的連續,而是相差一個數據類型大小的字節 **)
再次推導,二維數組可以看成是元素爲一維數組的一個數組,三維數組爲元素爲二維數組的一個數組,四位數組爲元素是三維數組的一維數組,既n維數組可以看成元素是n-1維數組的一維數組。
他們都具備上述結論的特徵,既數組元素在內存空間上是連續且遞增的。

學習進度差不多就到這裏了,週末抽點時間總結了一下,可能有不對的地方,希望能被大佬們指正。個人感覺最大的收穫就是,學習計算機,還是要深一步,進一步的思考,要多想爲什麼。語言知識工具,搞懂原理,弄清本質。纔會在知識的更新換代後能更快的適應。畢竟,計算機他只是一個計算機,但是,計算機他畢竟還是一個計算機。
願人生不只如初見

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章