您好,登錄后才能下訂單哦!
這篇文章主要介紹了C語言函數使用實例分析的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇C語言函數使用實例分析文章都會有所收獲,下面我們一起來看看吧。
告訴編譯器有一個函數叫什么,參數是什么,返回類型是什么。但是具體是不是存在,函數聲明決定不了
函數的聲明一般出現在函數的使用之前。要滿足先聲明后使用
函數的聲明一般要放在頭文件中的
函數的定義是指函數的具體實現,交待函數的功能實現
一般寫簡單的求和函數,求和功能直接寫在main( )函數了。
//簡單的求和函數 int main() { int a = 10; int b = 20; int sum = a+b; printf("%d\n", sum); return 0; }
把加法功能單獨寫成一個函數,放在主函數前面。如果將函數add放在主函數后面,則會報錯,因為程序自上而下進行的,主函數執行后,發現add函數未定義,找不到。
//之前的有函數的寫法.函數放在前面 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; }
int add(int x, int y);//函數的聲明 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)//定義放在主函數后面,需要先聲明 { return x + y; }
實際上,當函數代碼較多時,一般采用模塊化編程,每個函數實現功能盡量單一,函數間要低耦合、高內聚。因此,針對上面的加單的加法函數,用帶頭文件的寫法重寫一遍。
先定義源文件 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)//定義放在主函數后面,需要先聲明 { return x + y; } //頭文件add.h int add(int x, int y);//函數的聲明
當編程寫了一個減法的函數給別人用,但是又不想把源碼直接分享給別人,這時候就可以將代碼編譯成靜態庫(就是.lib文件)。
靜態庫的特點:將函數編譯成靜態庫,別人可以正常使用封裝好的代碼,但是又看不到源碼。
下面舉例說明,如何生成靜態庫(.lib):
新建VS工程,新建源文件 sub.c和 頭文件 sub.h,編寫一個減法函數 sub
//源文件 sub.c int sub(int x, int y)//函數定義需要先聲明 { return y - x; } //頭文件 sub.h int sub(int x, int y);
依次點擊解決方案資源管理器——項目名稱——右鍵選屬性,彈出對話框。
然后依次點擊——配置屬性——常規——項目默認值——配置類型——下拉菜單選擇靜態庫(.lib)——應用——確定。
接著點擊生成——生成解決方案。
最終會在工程文件夾下的——Debug文件夾——看到靜態庫.lib文件。
用記事本打開靜態庫,可以看到是亂碼。
接下來說明如何使用別人或者自己生成好的靜態庫文件:
(1)將函數對應的同名頭文件.h文件 和 同名靜態庫.lib拷貝至自己的工程文件中。
(2)在頭文件中添加上t頭文件 sub0119.h
(3)在源文件中添加減法頭文件引用 和靜態庫的引用,
#include "add.h"//加法頭文件 #include "sub0119.h"//減法頭文件 #pragma comment(lib,"sub0119.lib")//靜態庫必須加上
(4)程序運行時,會通過上面的引用將生成的靜態庫加載進來。在主函數直接使用 減法函數sub即可。
//帶頭文件的寫法 int main() { int a = 10; int b = 20; int sum = add(a, b);//一般的函數調用 int subnum = sub(a, b);//使用靜態庫 printf("%d\n", sum); printf("%d\n", subnum); return 0; }
運行程序見下圖:
遞歸做為一種算法在程序設計語言中廣泛應用。
一個過程或函數在其定義或說明中有直接或間接調用自身的一種方法
它通常把一個大型復雜的問題層層轉化為一個與原問題相似的規模較小的問題來求解
只需少量的程序就可描述出解題過程所需要的多次重復計算,大大地減少了程序的代碼量
遞歸的主要思考方式在于:把大事化小,將問題轉化為可以重復有限次的小問題解決
存在限制條件,當滿足這個限制條件的時候,遞歸便不再繼續
每次遞歸調用之后越來越接近這個限制條件
接受一個整型值(無符號),按照順序打印它的每一位。
輸入:1234,輸出 1 2 3 4
void print(num)//自定義打印函數 { int arr[10] = { 0 };//定義數組 for (int i = 0; i < 4; i++) {//將數字存放在數組里 arr[i] = num % 10;//取數字最后一位 num = num / 10;//取整數 } for (int i = 3; i >= 0; i--) {//倒著打印 printf("%d ", arr[i]); } } int main() { int num = 1234; print(num); return 0; }
分析:打印1234可以分解成下圖那樣拆解,分別把不同位上的數字取出,最終把數字拆解剩最后一位時,開始打印:
print(1234),數字大于9表明數字還不是個位數,于是將1234拆解成123 和 4,分別通過取余和取模操作。把取余的123再次傳給函數print
print(123),數字大于9表明數字還不是個位數,于是將123拆解成12 和 3,分別通過取余和取模操作,把取余的12再次傳給函數print
print(12),數字大于9表明數字還不是個位數,于是將12拆解成1 和 2,分別通過取余和取模操作,把取余的1再次傳給函數print
print(1),數字小于9表明數字是個位數,也就是分解到最后一步了,這就是遞歸的限制條件,于是將1取模操作,打印出來
代碼如下所示:
void print(int num) { if (num>9) { print(num/10); //取余 } printf("%d ",num % 10);//取模 } int main() { int num = 1234; print(num); return 0; }
通過調試,我們分析整個遞歸程序的運行邏輯見下圖,紅色圓圈的1 2 3 4表示程序的執行順序:
第一次調用函數print,此時 num=1234, n>9, 滿足if條件, 執行print(123), 調用函數print自身
第二次調用函數print,此時 num=123, n>9, 滿足if條件, 執行print(12), 調用函數print自身
第三次調用函數print,此時 num=12, n>9, 滿足if條件, 執行print(1), 調用函數print自身
第四次調用函數print,此時 num=1, n<9, 不滿足if條件, 執行printf(“%d”, num%10), 此時num=1, 打印1
此時函數已經拆解到最后一層了,函數執行結束返回到上一次調用的地方,綠色圓圈的1 2 3 4表示程序的返回順序:
在綠色圓圈1處,函數printf打印1后,將會返回到上一次調用print的地方,即綠色圓圈2
在綠色圓圈2處,if語句已經執行完畢,按順序執行printf(“%d”, num%10),此時num=12, 打印2。接著將會返回到上一次調用print的地方,即綠色圓圈3處
在綠色圓圈3處,if語句已經執行完畢,按順序執行printf(“%d”, num%10),此時num=123, 打印3。接著將會返回到上一次調用print的地方,即綠色圓圈4處
在綠色圓圈4處,if語句已經執行完畢,按順序執行printf(“%d”, num%10),此時num=1234, 打印4。接著將會返回到上一次調用print的地方,即主函數main中。
在函數運行過程中,每調用一次函數,在內存棧中就會開辟空間存放num的值,如下面藍色方框顯示,第一次調用函數存放的1234 在最底層, 以此類推,1是最后調用函數存放的,就在最上面。
在函數返回時,看綠色圓圈 1 2 3 4, num的值就從上向下取值,
在綠色圓圈1處,num數值為1。打印1后,函數運行結束,存放1的空間就銷毀了
此時返回到綠色圓圈2里,num數值為12,打印2后,函數運行結束,存放12的空間也銷毀了
此時返回到綠色圓圈3里,num數值為123,打印3后,函數運行結束,存放123的空間也銷毀了
此時返回到綠色圓圈2里,num數值為1234,打印4后,函數運行結束,存放1234的空間也銷毀了
運行結果見下圖:
編寫函數不允許創建臨時變量,求取字符串的長度
//編寫函數不允許創建臨時變量,求取字符串的長度 void getlen(char* arr) { int count = 0;//計數 while (*arr!='\0') { count++;//計數加1 arr++;//地址移動到下一個字符 } printf("%d", count); } int main() { char arr[] = "abcd"; getlen(arr);//數組名就是首元素地址 return 0; }
分析:自定義函數getlen,計算字符串 abcd,字符串以 '\0’結尾,這是隱藏的,實際字符串為"abcd\0"自定義函數可以分解成下圖那樣拆解,每次取出一個字符,最終把字符都取完,返回值:
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’),已經到字符串結尾了
getlen(’\0’), 判斷字符是不是’\0’, 是,于是返回值0,代表字符串結束了,這就是遞歸的限制條件
下面是實現代碼:
//遞歸方法 int getlen(char* arr) { if (*arr != 0) { arr++; return 1 + getlen(arr); } return 0;//等于0,字符串結束了,返回0 } int main() { char arr[] = "abcd"; int sz = getlen(arr); printf("%d", sz); return 0; }
程序運行結果見下圖,紅色路線是遞歸按順序調用函數,綠色的路線是遞歸達到限制條件后,返回值
求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; }
分析:自定義函數fact,求n的階乘。自定義函數可以分解成下圖那樣拆解:
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已經推到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; }
運行結果見下圖:
求第n個斐波那契數。(不考慮溢出)
//一般方法 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);//第五個斐波那契數列是5 return 0; }
分析:自定義函數fib,求第n個斐波那契數列。自定義函數可以分解成下圖那樣拆解:
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)為已知值,數列推到此結束了,直接給返回值就行, 這就是遞歸的限制條件。
//遞歸方法方法 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; }
在前面7.2.2.4 小節練習4中,發現有一個問題,舉例fib(6)說明,下面是計算fib(6)時用的遞歸方法,分析會發現fib(3)居然重復計算了3次,如果計算fib(40)時,這樣的重復計算會更多,大量的重復計算勢必會降低計算速度。
通過程序來驗證一樣,計算fib(6)時,fib(3)總共計算了幾次:
int count = 0;//全局變量 int fib(int n) { if (n == 3) count++;//計算fib(3)計算了多少次 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; }
結果如下所示:fib(6) = 8,fib(3)總共計算了3次:**
當計算fib(40)時,fib(3)總共計算了幾次?
結果見下圖,fib(40) = 102334155,fib(3)總共計算了39088169次,驚呆了居然3千多萬次。而且很耗時間,計算效率低。
而用一般的方法計算fib(40)時,fib(3)只計算了一次。
在調試 factorial 函數的時候,如果你的參數比較大,那就會報錯: stack overflow(棧溢出)這樣的信息。例如計算fib(50)時,結果為負數,就是溢出了。
系統分配給程序的棧空間是有限的,但是如果出現了死循環,或者(死遞歸),這樣有可能導致一
直開辟棧空間,最終產生棧空間耗盡的情況,這樣的現象我們稱為棧溢出。
那如何解決上述的問題:
將遞歸改寫成非遞歸。例如計算fib(40)時,就采用一般方法計算。
使用static對象替代 nonstatic 局部對象。 在遞歸函數設計中,可以使用 static 對象替代 nonstatic 局部對象(即棧對象),這不僅可以減少每次遞歸調用和返回時產生和釋放 nonstatic 對象的開銷,而且 static 對象還可以保存遞歸調用的中間狀態,并且可為各個調用層所訪問。
許多問題是以遞歸的形式進行解釋的,這只是因為它比非遞歸的形式更為清晰。
但是這些問題的迭代實現往往比遞歸實現效率更高,雖然代碼的可讀性稍微差些。
當一個問題相當復雜,難以用迭代實現時,此時遞歸實現的簡潔性便可以補償它所帶來的運行時開銷。
當發現遞歸的算法很耗時間,都沒結果出來,那可能要考慮迭代實現了。
關于“C語言函數使用實例分析”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“C語言函數使用實例分析”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。