C語(yǔ)言函數(shù)超詳細(xì)講解下篇
前言
緊接上文,繼續(xù)學(xué)習(xí)函數(shù)相關(guān)內(nèi)容。
函數(shù)的聲明和定義
函數(shù)聲明
- 告訴編譯器有一個(gè)函數(shù)叫什么,參數(shù)是什么,返回類型是什么。但是具體是不是存在,函數(shù)聲明決定不了
- 函數(shù)的聲明一般出現(xiàn)在函數(shù)的使用之前。要滿足先聲明后使用
- 函數(shù)的聲明一般要放在頭文件中的
函數(shù)定義
函數(shù)的定義是指函數(shù)的具體實(shí)現(xiàn),交待函數(shù)的功能實(shí)現(xiàn)
舉例
簡(jiǎn)單的求和函數(shù)
一般寫(xiě)簡(jiǎn)單的求和函數(shù),求和功能直接寫(xiě)在main( )函數(shù)了。
//簡(jiǎn)單的求和函數(shù) int main() { int a = 10; int b = 20; int sum = a+b; printf("%d\n", sum); return 0; }
把加法單獨(dú)改寫(xiě)成函數(shù)
把加法功能單獨(dú)寫(xiě)成一個(gè)函數(shù),放在主函數(shù)前面。如果將函數(shù)add放在主函數(shù)后面,則會(huì)報(bào)錯(cuò),因?yàn)槌绦蜃陨隙逻M(jìn)行的,主函數(shù)執(zhí)行后,發(fā)現(xiàn)add函數(shù)未定義,找不到。
//之前的有函數(shù)的寫(xiě)法.函數(shù)放在前面 int add(int x, int y) { return x + y; } int main() { int a = 10; int b = 20; int sum = add(a, b); printf("%d\n", sum); return 0; }
添加函數(shù)聲明
int add(int x, int y);//函數(shù)的聲明 int main() { int a = 10; int b = 20; int sum = add(a, b); printf("%d\n", sum); return 0; } int add(int x, int y)//定義放在主函數(shù)后面,需要先聲明 { return x + y; }
帶頭文件和函數(shù)聲明
實(shí)際上,當(dāng)函數(shù)代碼較多時(shí),一般采用模塊化編程,每個(gè)函數(shù)實(shí)現(xiàn)功能盡量單一,函數(shù)間要低耦合、高內(nèi)聚。因此,針對(duì)上面的加單的加法函數(shù),用帶頭文件的寫(xiě)法重寫(xiě)一遍。
先定義源文件 test.c 、源文件 add.c和頭文件 add.h
//源文件test. c #include "add.h" int main() { int a = 10; int b = 20; int sum = add(a, b); printf("%d\n", sum); return 0; } //源文件add.c int add(int x, int y)//定義放在主函數(shù)后面,需要先聲明 { return x + y; } //頭文件add.h int add(int x, int y);//函數(shù)的聲明
靜態(tài)庫(kù)(.lib)的生成
當(dāng)編程寫(xiě)了一個(gè)減法的函數(shù)給別人用,但是又不想把源碼直接分享給別人,這時(shí)候就可以將代碼編譯成靜態(tài)庫(kù)(就是.lib文件)。
靜態(tài)庫(kù)的特點(diǎn):將函數(shù)編譯成靜態(tài)庫(kù),別人可以正常使用封裝好的代碼,但是又看不到源碼。
下面舉例說(shuō)明,如何生成靜態(tài)庫(kù)(.lib):
新建VS工程,新建源文件 sub.c和 頭文件 sub.h,編寫(xiě)一個(gè)減法函數(shù) sub
//源文件 sub.c int sub(int x, int y)//函數(shù)定義需要先聲明 { return y - x; } //頭文件 sub.h int sub(int x, int y);
依次點(diǎn)擊解決方案資源管理器——項(xiàng)目名稱——右鍵選屬性,彈出對(duì)話框。
然后依次點(diǎn)擊——配置屬性——常規(guī)——項(xiàng)目默認(rèn)值——配置類型——下拉菜單選擇靜態(tài)庫(kù)(.lib)——應(yīng)用——確定。
接著點(diǎn)擊生成——生成解決方案。
最終會(huì)在工程文件夾下的——Debug文件夾——看到靜態(tài)庫(kù).lib文件。
用記事本打開(kāi)靜態(tài)庫(kù),可以看到是亂碼。
靜態(tài)庫(kù)文件的使用方法
接下來(lái)說(shuō)明如何使用別人或者自己生成好的靜態(tài)庫(kù)文件:
(1)將函數(shù)對(duì)應(yīng)的同名頭文件.h文件 和 同名靜態(tài)庫(kù).lib拷貝至自己的工程文件中。
(2)在頭文件中添加上t頭文件 sub0119.h
(3)在源文件中添加減法頭文件引用 和靜態(tài)庫(kù)的引用,
#include "add.h"http://加法頭文件 #include "sub0119.h"http://減法頭文件 #pragma comment(lib,"sub0119.lib")//靜態(tài)庫(kù)必須加上
(4)程序運(yùn)行時(shí),會(huì)通過(guò)上面的引用將生成的靜態(tài)庫(kù)加載進(jìn)來(lái)。在主函數(shù)直接使用 減法函數(shù)sub即可。
//帶頭文件的寫(xiě)法 int main() { int a = 10; int b = 20; int sum = add(a, b);//一般的函數(shù)調(diào)用 int subnum = sub(a, b);//使用靜態(tài)庫(kù) printf("%d\n", sum); printf("%d\n", subnum); return 0; }
運(yùn)行程序見(jiàn)下圖:
函數(shù)遞歸
什么是遞歸?
- 遞歸做為一種算法在程序設(shè)計(jì)語(yǔ)言中廣泛應(yīng)用。
- 一個(gè)過(guò)程或函數(shù)在其定義或說(shuō)明中有直接或間接調(diào)用自身的一種方法
- 它通常把一個(gè)大型復(fù)雜的問(wèn)題層層轉(zhuǎn)化為一個(gè)與原問(wèn)題相似的規(guī)模較小的問(wèn)題來(lái)求解
- 只需少量的程序就可描述出解題過(guò)程所需要的多次重復(fù)計(jì)算,大大地減少了程序的代碼量
- 遞歸的主要思考方式在于:把大事化小,將問(wèn)題轉(zhuǎn)化為可以重復(fù)有限次的小問(wèn)題解決
遞歸的兩個(gè)必要條件
- 存在限制條件,當(dāng)滿足這個(gè)限制條件的時(shí)候,遞歸便不再繼續(xù)
- 每次遞歸調(diào)用之后越來(lái)越接近這個(gè)限制條件
練習(xí)1
接受一個(gè)整型值(無(wú)符號(hào)),按照順序打印它的每一位。
輸入:1234,輸出 1 2 3 4
一般方法
void print(num)//自定義打印函數(shù) { int arr[10] = { 0 };//定義數(shù)組 for (int i = 0; i < 4; i++) {//將數(shù)字存放在數(shù)組里 arr[i] = num % 10;//取數(shù)字最后一位 num = num / 10;//取整數(shù) } for (int i = 3; i >= 0; i--) {//倒著打印 printf("%d ", arr[i]); } } int main() { int num = 1234; print(num); return 0; }
遞歸的方法
分析:打印1234可以分解成下圖那樣拆解,分別把不同位上的數(shù)字取出,最終把數(shù)字拆解剩最后一位時(shí),開(kāi)始打?。?/p>
- print(1234),數(shù)字大于9表明數(shù)字還不是個(gè)位數(shù),于是將1234拆解成123 和 4,分別通過(guò)取余和取模操作。把取余的123再次傳給函數(shù)print
- print(123),數(shù)字大于9表明數(shù)字還不是個(gè)位數(shù),于是將123拆解成12 和 3,分別通過(guò)取余和取模操作,把取余的12再次傳給函數(shù)print
- print(12),數(shù)字大于9表明數(shù)字還不是個(gè)位數(shù),于是將12拆解成1 和 2,分別通過(guò)取余和取模操作,把取余的1再次傳給函數(shù)print
- print(1),數(shù)字小于9表明數(shù)字是個(gè)位數(shù),也就是分解到最后一步了,這就是遞歸的限制條件,于是將1取模操作,打印出來(lái)
代碼如下所示:
void print(int num) { if (num>9) { print(num/10); //取余 } printf("%d ",num % 10);//取模 } int main() { int num = 1234; print(num); return 0; }
通過(guò)調(diào)試,我們分析整個(gè)遞歸程序的運(yùn)行邏輯見(jiàn)下圖,紅色圓圈的1 2 3 4表示程序的執(zhí)行順序:
- 第一次調(diào)用函數(shù)print,此時(shí) num=1234, n>9, 滿足if條件, 執(zhí)行print(123), 調(diào)用函數(shù)print自身
- 第二次調(diào)用函數(shù)print,此時(shí) num=123, n>9, 滿足if條件, 執(zhí)行print(12), 調(diào)用函數(shù)print自身
- 第三次調(diào)用函數(shù)print,此時(shí) num=12, n>9, 滿足if條件, 執(zhí)行print(1), 調(diào)用函數(shù)print自身
- 第四次調(diào)用函數(shù)print,此時(shí) num=1, n<9, 不滿足if條件, 執(zhí)行printf(“%d”, num%10), 此時(shí)num=1, 打印1
此時(shí)函數(shù)已經(jīng)拆解到最后一層了,函數(shù)執(zhí)行結(jié)束返回到上一次調(diào)用的地方,綠色圓圈的1 2 3 4表示程序的返回順序:
- 在綠色圓圈1處,函數(shù)printf打印1后,將會(huì)返回到上一次調(diào)用print的地方,即綠色圓圈2
- 在綠色圓圈2處,if語(yǔ)句已經(jīng)執(zhí)行完畢,按順序執(zhí)行printf(“%d”, num%10),此時(shí)num=12, 打印2。接著將會(huì)返回到上一次調(diào)用print的地方,即綠色圓圈3處
- 在綠色圓圈3處,if語(yǔ)句已經(jīng)執(zhí)行完畢,按順序執(zhí)行printf(“%d”, num%10),此時(shí)num=123, 打印3。接著將會(huì)返回到上一次調(diào)用print的地方,即綠色圓圈4處
- 在綠色圓圈4處,if語(yǔ)句已經(jīng)執(zhí)行完畢,按順序執(zhí)行printf(“%d”, num%10),此時(shí)num=1234, 打印4。接著將會(huì)返回到上一次調(diào)用print的地方,即主函數(shù)main中。
在函數(shù)運(yùn)行過(guò)程中,每調(diào)用一次函數(shù),在內(nèi)存棧中就會(huì)開(kāi)辟空間存放num的值,如下面藍(lán)色方框顯示,第一次調(diào)用函數(shù)存放的1234 在最底層, 以此類推,1是最后調(diào)用函數(shù)存放的,就在最上面。
在函數(shù)返回時(shí),看綠色圓圈 1 2 3 4, num的值就從上向下取值,
- 在綠色圓圈1處,num數(shù)值為1。打印1后,函數(shù)運(yùn)行結(jié)束,存放1的空間就銷毀了
- 此時(shí)返回到綠色圓圈2里,num數(shù)值為12,打印2后,函數(shù)運(yùn)行結(jié)束,存放12的空間也銷毀了
- 此時(shí)返回到綠色圓圈3里,num數(shù)值為123,打印3后,函數(shù)運(yùn)行結(jié)束,存放123的空間也銷毀了
- 此時(shí)返回到綠色圓圈2里,num數(shù)值為1234,打印4后,函數(shù)運(yùn)行結(jié)束,存放1234的空間也銷毀了
運(yùn)行結(jié)果見(jiàn)下圖:
練習(xí)2
編寫(xiě)函數(shù)不允許創(chuàng)建臨時(shí)變量,求取字符串的長(zhǎng)度
一般方法
//編寫(xiě)函數(shù)不允許創(chuàng)建臨時(shí)變量,求取字符串的長(zhǎng)度 void getlen(char* arr) { int count = 0;//計(jì)數(shù) while (*arr!='\0') { count++;//計(jì)數(shù)加1 arr++;//地址移動(dòng)到下一個(gè)字符 } printf("%d", count); } int main() { char arr[] = "abcd"; getlen(arr);//數(shù)組名就是首元素地址 return 0; }
遞歸方法
分析:自定義函數(shù)getlen,計(jì)算字符串 abcd,字符串以 '\0’結(jié)尾,這是隱藏的,實(shí)際字符串為"abcd\0"自定義函數(shù)可以分解成下圖那樣拆解,每次取出一個(gè)字符,最終把字符都取完,返回值:
- getlen(abcd),判斷字符是不是’\0’, 不是,于是將getlen(abcd) 拆解成 1+ getlen(bcd)
- getlen(bcd), 判斷字符是不是’\0’, 不是,于是將getlen(bcd) 拆解成 1+ getlen(cd)
- getlen(cd), 判斷字符是不是’\0’, 不是,于是將getlen(cd) 拆解成 1+ getlen(d)
- getlen(d), 判斷字符是不是’\0’, 不是,于是將getlen(d) 拆解成 1+ getlen(’\0’),已經(jīng)到字符串結(jié)尾了
- getlen(’\0’), 判斷字符是不是’\0’, 是,于是返回值0,代表字符串結(jié)束了,這就是遞歸的限制條件
下面是實(shí)現(xiàn)代碼:
//遞歸方法 int getlen(char* arr) { if (*arr != 0) { arr++; return 1 + getlen(arr); } return 0;//等于0,字符串結(jié)束了,返回0 } int main() { char arr[] = "abcd"; int sz = getlen(arr); printf("%d", sz); return 0; }
程序運(yùn)行結(jié)果見(jiàn)下圖,紅色路線是遞歸按順序調(diào)用函數(shù),綠色的路線是遞歸達(dá)到限制條件后,返回值
練習(xí)3
求n的階乘。(不考慮溢出)
一般方法
//求n的階乘 void fact(int n) { int num = 1; for (int i = 1; i <= n; i++) { num = num * i; } printf("%d", num); } int main() { fact(3);//階乘 return 0; }
遞歸方法
分析:自定義函數(shù)fact,求n的階乘。自定義函數(shù)可以分解成下圖那樣拆解:
- fact(n)=n!=n*(n-1)…1=nfact(n-1),
- fact(n-1)=(n-1)!=(n-1)…*1=(n-1)*fact(n-2),
- 依次類推, fact(2)=2!=2*fact(1),
- 判斷n已經(jīng)推到1了,返回fact(1) =1,這就是遞歸的限制條件。
//遞歸方法 int fact(int n) { if (n >= 1) { return n*fact(n - 1); n--; } else { return 1; } } int main() { int num=fact(3);//階乘 printf("%d", num); return 0; }
運(yùn)行結(jié)果見(jiàn)下圖:
練習(xí)4
求第n個(gè)斐波那契數(shù)。(不考慮溢出)
一般方法
//一般方法 void fib(int n) { int num1 = 1; int num2 = 1; int num3 = 0; for (int i = 1; i <=(n-2); i++) { num3 = num1 + num2; num1 = num2; num2 = num3; } printf("%d", num3);//輸出5 } int main() { fib(5);//第五個(gè)斐波那契數(shù)列是5 return 0; }
遞歸方法
分析:自定義函數(shù)fib,求第n個(gè)斐波那契數(shù)列。自定義函數(shù)可以分解成下圖那樣拆解:
- fib(1)=1,fib(2)=1
- fib(3)=fib(2)+fib(1),fib(4)=fib(3)+fib(2),
- 依次類推, fib(n)=fib(n-1)+fib(n-2),
- 求fib(6),就往前遞推,fib(6)=fib(5)+fib(4)
- 一直推到 fib(3)=fib(2)+fib(1), fib(1),fib(2)為已知值,數(shù)列推到此結(jié)束了,直接給返回值就行, 這就是遞歸的限制條件。
//遞歸方法方法 int fib(int n) { if (n <= 2) { return 1; } return fib(n - 1) + fib(n - 2); } int main() { int num=fib(5); printf("%d", num); return 0; }
遞歸與迭代
遞歸隱藏的問(wèn)題
在前面小節(jié)練習(xí)4中,發(fā)現(xiàn)有一個(gè)問(wèn)題,舉例fib(6)說(shuō)明,下面是計(jì)算fib(6)時(shí)用的遞歸方法,分析會(huì)發(fā)現(xiàn)fib(3)居然重復(fù)計(jì)算了3次,如果計(jì)算fib(40)時(shí),這樣的重復(fù)計(jì)算會(huì)更多,大量的重復(fù)計(jì)算勢(shì)必會(huì)降低計(jì)算速度。
通過(guò)程序來(lái)驗(yàn)證一樣,計(jì)算fib(6)時(shí),fib(3)總共計(jì)算了幾次:
int count = 0;//全局變量 int fib(int n) { if (n == 3) count++;//計(jì)算fib(3)計(jì)算了多少次 if (n <= 2) return 1; return fib(n - 1) + fib(n - 2); } int main() { int num=fib(6); printf("%d\n", num); printf("%d\n", count); return 0; }
結(jié)果如下所示:fib(6) = 8,fib(3)總共計(jì)算了3次:**
當(dāng)計(jì)算fib(40)時(shí),fib(3)總共計(jì)算了幾次?
結(jié)果見(jiàn)下圖,fib(40) = 102334155,fib(3)總共計(jì)算了39088169次,驚呆了居然3千多萬(wàn)次。而且很耗時(shí)間,計(jì)算效率低。
而用一般的方法計(jì)算fib(40)時(shí),fib(3)只計(jì)算了一次。
如何改進(jìn)
- 在調(diào)試 factorial 函數(shù)的時(shí)候,如果你的參數(shù)比較大,那就會(huì)報(bào)錯(cuò): stack overflow(棧溢出)這樣的信息。例如計(jì)算fib(50)時(shí),結(jié)果為負(fù)數(shù),就是溢出了。
- 系統(tǒng)分配給程序的??臻g是有限的,但是如果出現(xiàn)了死循環(huán),或者(死遞歸),這樣有可能導(dǎo)致一直開(kāi)辟??臻g,最終產(chǎn)生??臻g耗盡的情況,這樣的現(xiàn)象我們稱為棧溢出。
那如何解決上述的問(wèn)題:
- 將遞歸改寫(xiě)成非遞歸。例如計(jì)算fib(40)時(shí),就采用一般方法計(jì)算。
- 使用static對(duì)象替代 nonstatic 局部對(duì)象。 在遞歸函數(shù)設(shè)計(jì)中,可以使用 static 對(duì)象替代 nonstatic 局部對(duì)象(即棧對(duì)象),這不僅可以減少每次遞歸調(diào)用和返回時(shí)產(chǎn)生和釋放 nonstatic 對(duì)象的開(kāi)銷,而且 static 對(duì)象還可以保存遞歸調(diào)用的中間狀態(tài),并且可為各個(gè)調(diào)用層所訪問(wèn)。
選遞歸還是迭代
- 許多問(wèn)題是以遞歸的形式進(jìn)行解釋的,這只是因?yàn)樗确沁f歸的形式更為清晰。
- 但是這些問(wèn)題的迭代實(shí)現(xiàn)往往比遞歸實(shí)現(xiàn)效率更高,雖然代碼的可讀性稍微差些。
- 當(dāng)一個(gè)問(wèn)題相當(dāng)復(fù)雜,難以用迭代實(shí)現(xiàn)時(shí),此時(shí)遞歸實(shí)現(xiàn)的簡(jiǎn)潔性便可以補(bǔ)償它所帶來(lái)的運(yùn)行時(shí)開(kāi)銷。
- 當(dāng)發(fā)現(xiàn)遞歸的算法很耗時(shí)間,都沒(méi)結(jié)果出來(lái),那可能要考慮迭代實(shí)現(xiàn)了。
總結(jié)
函數(shù)的學(xué)習(xí)暫時(shí)告一段落,涉及到的遞歸函數(shù)的實(shí)現(xiàn)不太好理解,這塊要加大練習(xí),才能逐漸掌握,重要的就是抽絲剝繭,找到限制條件,然后返回確定的值。
下一篇博文開(kāi)始數(shù)組的學(xué)習(xí)了。
到此這篇關(guān)于C語(yǔ)言函數(shù)超詳細(xì)講解下篇的文章就介紹到這了,更多相關(guān)C語(yǔ)言 函數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Linux中使用C語(yǔ)言的fork()函數(shù)創(chuàng)建子進(jìn)程的實(shí)例教程
fork是一個(gè)在Linux系統(tǒng)環(huán)境下專有的函數(shù),現(xiàn)有的進(jìn)程調(diào)用fork后將會(huì)創(chuàng)建一個(gè)新的進(jìn)程,這里我們就來(lái)看一下Linux中使用C語(yǔ)言的fork()函數(shù)創(chuàng)建子進(jìn)程的實(shí)例教程2016-06-06C++利用PCL點(diǎn)云庫(kù)操作txt文件詳解
這篇文章主要為大家詳細(xì)介紹了C++如何利用PCL點(diǎn)云庫(kù)操作txt文件,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解一下2024-01-01C++封裝遠(yuǎn)程注入類CreateRemoteThreadEx實(shí)例
這篇文章主要介紹了C++封裝遠(yuǎn)程注入類CreateRemoteThreadEx實(shí)例,詳細(xì)講述了注入DLL到指定的地址空間以及從指定的地址空間卸載DLL的方法,需要的朋友可以參考下2014-10-10C語(yǔ)言數(shù)據(jù)結(jié)構(gòu) link 鏈表反轉(zhuǎn)的實(shí)現(xiàn)
這篇文章主要介紹了C語(yǔ)言數(shù)據(jù)結(jié)構(gòu) link 鏈表反轉(zhuǎn)的實(shí)現(xiàn)的相關(guān)資料,希望通過(guò)本文能幫助到大家,需要的朋友可以參考下2017-09-09Qt信號(hào)與槽知識(shí)點(diǎn)總結(jié)歸納
信號(hào)和槽是一種高級(jí)接口,應(yīng)用于對(duì)象之間的通信,它是QT的核心特性,下面這篇文章主要給大家介紹了關(guān)于Qt信號(hào)與槽知識(shí)點(diǎn)總結(jié)歸納的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-12-12C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單推箱子游戲
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單推箱子游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-02-02C++數(shù)據(jù)結(jié)構(gòu)之鏈表詳解
這篇文章主要介紹了C++數(shù)據(jù)結(jié)構(gòu)之鏈表的創(chuàng)建的相關(guān)資料,希望通過(guò)本文幫助到大家,讓大家理解掌握這部分內(nèi)容,需要的朋友可以參考下2021-08-08C++運(yùn)行時(shí)類型識(shí)別與轉(zhuǎn)換實(shí)現(xiàn)方法
運(yùn)行時(shí)類型識(shí)別可能被認(rèn)為是C++中一個(gè)”次要“的特征,當(dāng)程序員在編程過(guò)程中陷入非常困難的境地時(shí),實(shí)用主義將會(huì)幫助他走出困境2022-10-10C++超詳細(xì)講解隱藏私有屬性和方法的兩種實(shí)現(xiàn)方式
為了避免因?yàn)閷㈩悗?kù)中的私有成員開(kāi)放給類的使用方而導(dǎo)致的軟件邏輯外泄,因此需要將對(duì)外代碼中的私有成員隱藏起來(lái),下面我們來(lái)了解一下隱藏私有屬性和方法的兩種實(shí)現(xiàn)方式2022-05-05C語(yǔ)言中l(wèi)seek()函數(shù)和fseek()函數(shù)的使用詳解
這篇文章主要介紹了C語(yǔ)言中l(wèi)seek()函數(shù)和fseek()函數(shù)的使用詳解,是C語(yǔ)言入門(mén)學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-08-08