C语言函数详解
目錄
一、函數的定義與分類
1.定義
2.分類
二、庫函數
1.庫函數存在的意義
2.庫函數的學習和使用
三、自定義函數
1.自定義函數的組成
2.示例
(1)寫一個函數找出兩個整數的最大值
(2)寫一個函數交換兩個整型變量的內容
四、函數的參數
1. 實際參數(實參)
2.形式參數(形參)
五、函數的調用
1.傳值調用
2.傳址調用
3.錯誤講解
4.練習
(1)寫一個函數可以判斷一個數是不是素數
(2)寫一個函數判斷一年是不是閏年
(3)寫一個函數,實現一個整形有序數組的二分查找
(4)寫一個函數,每調用一次這個函數,就會將 num 的值增加1
六、函數的嵌套調用和鏈式訪問
1.函數的嵌套調用
2.函數的鏈式訪問
3.鏈式訪問的經典例題
七、函數的定義和聲明
1.函數的定義
2.函數的聲明
3.程序的分塊化編寫
4.函數的聲明和定義為什么不寫在同一個.c文件內
八、函數遞歸與迭代
1.函數遞歸的定義與條件
2.講解練習
3.函數的遞歸與迭代
函數是C語言的基本單位,在C語言程序中發揮著極其重要的作用
一、函數的定義與分類
1.定義
在維基百科中,函數的定義叫做子程序。
(1)一個大型程序中的某部分代碼, 由一個或多個語句塊組成。它負責完成某項特定任務,而且相較于其他代 碼,具備相對的獨立性。
(2)一般會有輸入參數并有返回值,提供對過程的封裝和細節的隱藏。這些代碼通常被集成為軟
件庫。
2.分類
(1)庫函數:C語言內部提供的函數。
(2)自定義函數:自我發揮寫出的函數。
二、庫函數
1.庫函數存在的意義
我們在編寫C語言代碼的時候,總會頻繁地使用一些功能:
比如:將信息按照一定的格式打印到屏幕上(printf)、在編程的過程中我們會頻繁的做一些字符串的拷貝工作(strcpy)、在編程是我們也計算,總是會計算n的k次方這樣的運算(pow)......
像上面的這些基本的功能,在編寫程序時經常會用到。所以C語言的基礎庫中提供了一系列類似的庫函數,方便程序員進行軟件開發。
2.庫函數的學習和使用
庫函數的使用不需要專門去記,我們可以通過查找了解它們的使用方式。
這里推薦一個網站和一個應用程序
(1)www.cplusplus.com
(2)msdn
通過這些方式,我們可以查找到它們的信息,例如:函數名、形式參數、需要的頭文件和返回值等必要的信息。
這些工具的語言都是英文,在學習編程的工程中我們需要學習英文,保證以后在第一時間可以了解計算機的最新技術。
三、自定義函數
1.自定義函數的組成
自定義函數由程序員自主設計,和普通的函數一樣有函數名、返回類型、形式參數等。
基本結構如下:
ret_type fun_name(para1, * ) {statement;//語句項 } ret_type 返回類型 fun_name 函數名 para1 ? ?函數參數2.示例
(1)寫一個函數找出兩個整數的最大值
#include<stdio.h> int islarge(int a, int b) {if (a>=b){return a;}else{return b;} } //上述為實現程序的函數int main() {int a = 0;int b = 0;scanf("%d %d", &a, &b);int c = islarge(a, b);printf("%d", c);return 0; } //輸入:10 20 //輸出:20(2)寫一個函數交換兩個整型變量的內容
錯誤示范:
#include<stdio.h> void swap(int a,int b) {int temp = 0;temp = a;a = b;b = temp; } int main() {int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交換前:a=%d,b=%d\n", a, b);swap(a, b);printf("交換前:a=%d,b=%d\n", a, b);return 0; } //輸入:10 20 //輸出: //交換前:a=10,b=20 //交換后:a=10,b=20 //正確程序:
#include<stdio.h> void swap(int* pa, int* pb) {int temp = 0;temp = *pa;*pa = *pb;*pb = temp; } int main() {int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交換前:a=%d,b=%d\n", a, b);swap(&a, &b);printf("交換前:a=%d,b=%d\n", a, b);return 0; } //輸入:10 20 //輸出: //交換前:a=10,b=20 //交換后:a=20,b=10 //這個程序我先不講錯在哪里,到后面形參的部分再詳細解釋。
四、函數的參數
1. 實際參數(實參)
真實傳給函數的參數,叫實參。
實參可以是:常量、變量、表達式、函數等。
在調用函數時,它們都必須有確定的值,以便把這些值傳送給形參。
2.形式參數(形參)
形式參數是指函數名后括號中的變量。
形式參數只有在函數被調用的過程中才實例化(分配內存單元),所以叫形式參數。因此形式參數只在函數中才有效。
下面是函數在處理數據時的處理思路:
#include<stdio.h> int islarge(int a, int b) //int是返回類型,括號里的int a和int b {if (a>=b){return a;}else{return b;} } //上述為實現程序的函數int main() {int a = 0;int b = 0;scanf("%d %d", &a, &b);//輸入a,b的值int c = islarge(a, b);//islarge有兩個實參a和b,定義變量c接收islarge函數的返回值printf("%d", c);return 0; }形參實例化之后其實相當于實參的一份臨時拷貝。
五、函數的調用
1.傳值調用
函數的形參和實參分別占有不同內存塊,對形參的修改不會影響實參。
所以,我們在不改變函數實參的時候可以使用傳值調用。
比如,我們寫一個程序計算兩個整數的和:
#include<stdio.h> int add(int x,int y) {return x+y; } int main() {int a = 0;int b = 0;scanf("%d %d", &a, &b);int c= add(a,b);printf("%d\n",c);return 0; }在這個程序中,我們只是使用a和b進行操作,而沒有改變a和b的數值等屬性,這時我們就可以使用傳值調用,再將操作得到的值返回。
2.傳址調用
傳址調用是把函數外部創建變量的內存地址傳遞給函數參數的一種調用函數的方式。這種傳參方式可以讓函數和函數外邊的變量建立起真正的聯系,也就是函數內部可以直接操作函數外部的變量。
#include<stdio.h> void swap(int* pa, int* pb) {int temp = 0;temp = *pa;*pa = *pb;*pb = temp; } int main() {int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交換前:a=%d,b=%d\n", a, b);swap(&a, &b);printf("交換前:a=%d,b=%d\n", a, b);return 0; }在這個程序中,我們改變了a和b的數值,這時我們就需要使用傳址調用,因為在傳值調用中形參的改變是不會影響實參的。
3.錯誤講解
講到這里,我們講一講上面使用傳值調用交換數值的程序錯在哪里:
#include<stdio.h> void swap(int a,int b)//返回類型為void表示不返回,此處的int a與int b表示形式參數和它們的類型 {int temp = 0;//定義一個臨時變量temp = a;//把a的值賦給tempa = b;//把b的值賦給ab = temp;//把temp的值賦給b,完成交換操作//注意,因為形參只是實參的一份臨時拷貝,在整個函數中我們改變的只是實參,出函數后形參被銷毀無法改變實參 } int main() {int a = 0;//創建變量aint b = 0;//創建變量bscanf("%d %d", &a, &b);//輸入數值printf("交換前:a=%d,b=%d\n", a, b);//展示swap(a, b);//交換函數,將a,b傳進去printf("交換前:a=%d,b=%d\n", a, b);//實參依舊是a和b的原始值,沒有達到我們的目的return 0; }打個比方:就好像老師在練習冊上留作業,你確實是寫了,就是寫在了你同學的練習冊上。雖然確實做了正確的事,但是做完了也沒什么用,你的作業本依舊是空的。(PS:偷把別人作業寫了,阻止他學習,內卷的高級境界)
傳址調用的程序傳遞的是實參的地址,這是實參的本質屬性。
#include<stdio.h> void swap(int* pa, int* pb)//返回類型為void表示不返回,此處的int* pa與int* pb表示形式參數和它們的類型 {int temp = 0;//定義臨時變量temp = *pa;//用地址找到實參a并賦給temp*pa = *pb;//把用地址找到的實參b賦給用地址找到的實參a*pb = temp;//用地址找到實參b并賦給temp//跳出函數時,被銷毀的形參只是兩個指針變量,此時實參的交換已經完成 } int main() {int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交換前:a=%d,b=%d\n", a, b);swap(&a, &b);//傳入地址printf("交換前:a=%d,b=%d\n", a, b);return 0; }這次,我們也干同樣的事情,比如寫作業。但這次我們定位到了你自己的作業本上,就可以實現寫作業的任務。
4.練習
(1)寫一個函數可以判斷一個數是不是素數
函數1:
int isprime(int x)//這個形參用于接收需要判斷的數字 {int i = 2;for (i=2; i<x; i++)//從2到這個數字減一逐一試除{if (x%i == 0)//如果有能除開的就表明它不是素數,返回0{return 0;}}return 1;//在除完所有的數字均除不開時,為素數返回1 }這個程序是可以改進的
比如說,4×4=16,而2×8=16或8×2=16也成立,16×1=16或1×16=16依舊成立。
我們不難看出,被乘數和乘數一定會有一個大于等于這個積開根號,一個小于等于這個積開根號,那么我們只需要試除到根號下x就完全可以判斷一個數字的是否為素數。
函數2:
#include<math.h> int isprime(int x) {int i = 2;for (i=2; i<=sqrt(x); i++)//sqrt表示對參數開平方{if (x%i == 0){return 0;}}return 1; }(2)寫一個函數判斷一年是不是閏年
判定條件: 對于整百的年份,閏年必定是400的倍數 ;對于不是整百的閏年,閏年是4的倍數
函數1:
int isleap(int year) {if (year % 400 == 0){return 1;}if (year%4==0){if (year % 100 != 0){return 1;}}return 0; }我們把這兩個條件集成一下,得到函數2
函數2:
#include<stdio.h> isleap(int year) {if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))//((year是4的倍數)并且(year不是100的倍數))或者(year是400的倍數){return 1;}else{return 0;} }(3)寫一個函數,實現一個整形有序數組的二分查找
int search(int arr[], int a, int sz)//形參為數組、需要查找的整數、數組的元素個數 {int left = 0;int right = sz - 1;int mid = 0;while (left <= right){mid = left + (right - left) / 2;//找中間的元素if (arr[mid] > a)//中間元素大于查找值,就從右縮小一半的范圍{right = mid-1;//可以使用--mid,不推薦}else if (arr[mid] < a)//中間元素小于查找值,就從左縮小一半的范圍{left = mid+1;//可以使用++mid,不推薦}else{return mid;//找到了,返回下標}}if (left>right) //正常情況下不會出現{return -1;//找不到,返回-1} }(4)寫一個函數,每調用一次這個函數,就會將 num 的值增加1
#include<stdio.h> void test(int* p)//在主程序內定義一個變量儲存調用的次數,因為需要改變變量的值,所以進行傳址調用 {printf("hehe\n");(*p)++;//解引用找到變量再加1,注意這個括號不能忘//否則,*p++就表示每次這個指針先向后移動4個字節,然后解引用 }六、函數的嵌套調用和鏈式訪問
1.函數的嵌套調用
函數可以根據需要進行相互調用。
#include<stdio.h> int main() {printf("Hello world\n");return 0; }這是每一個初學者都會寫的代碼,我們先調用了main函數,然后在main函數的內部又調用了printf函數,這就是嵌套調用。
2.函數的鏈式訪問
我們為了減少不必要變量的定義,可以直接把一個函數的返回值作為另一個函數的參數。
#include<string.h> #include<stdio.h> int main() {char arr[20] = "abcdef";printf("%d", strlen(arr));return 0; }strlen函數的返回值變成了printf函數的參數,這就把這兩個函數像鎖鏈一樣串聯起來,也就是鏈式訪問。
3.鏈式訪問的經典例題
這個程序的輸出是什么?
#include<stdio.h> int main() {printf("%d", printf("%d", printf("%d", 43)));return 0; }答案:4321
printf這個函數的返回值是它打印字符的個數,首先進入最外層的printf函數
這層函數需要第二層函數printf("%d", printf("%d", 43))的返回值
而第二層的printf函數又需要第三層函數printf("%d", 43)的返回值
在執行完第三層的printf("%d", 43)函數后,返回打印字符的個數2
printf("%d", printf("%d", 2))
第二層得到返回值2,打印2,而此時第二層函數也返回它打出的字符的個數1
printf("%d", 1)
最后打印1,也就形成了4321的輸出結果
七、函數的定義和聲明
1.函數的定義
(1)函數的定義是指函數的具體實現,交待函數的功能實現。相當于我們平常創建自定義函數的步驟。
(2)函數不能嵌套定義
錯誤的定義方法:
int add(int x,int y)//加法函數 {return x + y;int sub(int x, int y)//這個減法函數被嵌套定義在了加法函數內部,這種寫法是錯的{return x - y;} }正確的定義方法:
int add(int x,int y)//加法函數 {return x + y; } int sub(int x, int y)//減法函數 {return x - y; }對于函數來講,數數平等,不能搞特權。
2.函數的聲明
函數的聲明
- 函數的聲明主要的目的在于告訴編譯器有一個函數叫什么,參數是什么,返回類型是什么。
- 但是這個函數具體存在不存在,函數聲明決定不了。
- 函數的聲明一般出現在函數的使用之前。要滿足先聲明后使用。
- 函數的聲明一般要放在頭文件中的。
3.程序的分塊化編寫
我們在寫代碼的時候可能會想:我把所有的代碼寫在一個源文件中,這樣找起來不就方便了嗎。
其實,這樣的習慣對日后程序的開發是不利的。
我們的社會是有各自的分工的,當我們在開發一個程序的時候,我們往往只需要負責一個大的工程中的部分內容,比如一個人去寫主程序,一個人寫函數等等,而我們將工程的各個部分分開就可以更快地快找到bug并對應修復。
這樣,當我們寫一個函數時,就需要這樣的文件分配:
- 函數聲明——頭文件.h
- 函數定義——函數實現的源文件.c
每一個函數都可以分成這兩個文件編寫,也可以幾個函數寫在兩個文件中。
4.函數的聲明和定義為什么不寫在同一個.c文件內
這里涉及到一個代碼加密的問題,我會補充。
八、函數遞歸與迭代
1.函數遞歸的定義與條件
(1)遞歸的定義
程序調用自身的編程技巧稱為遞歸( recursion)。表示一個過程或函數在其定義或說明中有直接或間接調用自身的一種方法,它通常把一個大型復雜的問題層層轉化為一個與原問題相似的規模較小的問題來求解,只需少量的程序就可描述出解題過程所需要的多次重復計算,大大地減少了程序的代碼量。
遞歸的主要思考方式在于:把大事化小
(2)遞歸的兩個必要條件
- 存在限制條件,當滿足這個限制條件的時候,遞歸便不再繼續。
- 每次遞歸調用之后越來越接近這個限制條件。
2.講解練習
接受一個整型值(無符號),按照順序打印它的每一位
例如:輸入:1234,輸出 :1 2 3 4
#include <stdio.h> void print(int n) {if(n>9){print(n/10);}printf("%d ", n%10); } int main() {int num = 1234;print(num);return 0; }(1)思想層面:大事化小
我們如果想要得到一個數字的每一位,就需要我們先%10得到最后一位,后/10除去最后一位,因為/10最后一位為余數,可以繼續向前查找,直到這個數字成為一個一位數停止程序(因為如果這里是個一位數,a/10的值就是0,我們并不想打印0的每一位),所以在這里我們定義一個函數print(),它可以按順序打印每一個值。
分步解決就是這樣:
print(1234)
print(123) 4
print(12) 3 4
(2)實踐講解
print(1234);//這個函數從上到下,先遞進后回歸 //1234大于9,進入if語句,第一層 print(1234) {if(n>9)//n=1234,滿足條件,進入if{print(123);}printf("%d ", n%10);//第一層,a%10=4 } //print(123)展開,n=123滿足條件,繼續進入下一層 print(123) {if(n>9)//a/10=123,滿足條件,進入if{print(12);}printf("%d ", n%10);//第二層,a%10=3 } //print(12)展開,a/10=1此時不滿足條件,不會繼續進入下一層的if語句 print(12) {if(n>9)//n=12,不滿足條件,不進入if{print(1);}printf("%d ", n%10);//第三層,a%10=2 } print(1) {if(n>9)//n=1,不滿足條件,不進入if{print(0);}printf("%d ", n%10);//第三層,a%10=1 } 遞歸的“遞”此時已經完成,我們將這個代碼整理一下,查看它時如何“歸”的 print(1234) {{{{printf("%d ",n%10);//第四層,a%10=1}printf("%d ", n%10);//第三層,a%10=2}printf("%d ", n%10);//第二層,a%10=3}printf("%d ", n%10);//第一層,a%10=4 } //代碼從第四層開始向外執行,故可以實現數字的按位打印 //輸出:1 2 3 43.函數的遞歸與迭代
(1)什么是迭代
迭代實際上就是重復,如果只討論我們比較熟悉的程序設計操作,迭代在程序中就表示循環。
(2)函數遞歸和迭代的優缺點
函數遞歸中我們一層一層調用函數,它的優點是所需代碼量少,簡潔。但缺點主要有兩個,一方面,大量重復的計算拖慢了程序的運行速度;另一方面,函數每一次被調用的時候都需要在棧區開辟相應的空間,當遞歸過深時可能會出現棧溢出。(棧區的空間已經被用完了,程序無法繼續進行了)
當我們使用迭代時,循環不需要大量調用函數,重復的計算會少很多,這個程序的運行速度會加快不少,只是這個程序的代碼量會大很多。(下面這個程序不是很明顯,但也確實更短)
程序:應用遞歸求斐波那契數列的第n項
斐波那契數列:1 1 2 3 5 8 13 ...(規律:第一二項為1,后一項等于前兩項的和)
遞歸程序:
#include<stdio.h> int fib(int m) {int ret = 0;if (m<=2){ret = 1;//第一二項為1}else {ret = fib(m - 1) + fib(m - 2);//三項及三項以后,后一項等于前兩項的和}return ret; } int main() {int n = 0;scanf("%d", &n);printf("%d",fib(n));return 0; }迭代程序:
#include<stdio.h> int fib(int m) { if (m < 2)//前兩項為1{return 1;}else//后兩項為前兩項之和{ int i = 0;int a = 1;int b = 1;int c = 0;for (i=m; i>2; i--){c = a + b;a = b;//把原來的第二個數變成新計算中的第一個數b = c;//把算出的結果變為新計算的第二個數}return c;} } int main() {int n = 0;scanf("%d", &n);printf("%d",fib(n));return 0; }總結
- 上一篇: onvif概念及应用?
- 下一篇: UE破解及注册机下载