這篇文章是 C 語言系列第三篇,之前兩篇見
下面我們來介紹一下 C 語言中一個非常重要的概念 - 函數 (function)。首先就要先給函數下一個定義,函數就是完成特定任務的獨立代碼單元,這也就是說,一個函數肯定是要為了完成某種功能的,比如一個函數它能夠執行加法運算,比如一個函數能交換兩個數的值,還有一些函數可能只是為了打印某些東西等等。
函數也可以把很多大的任務拆分成一個個小的任務,通過設計每個小的任務來完成一個大的功能。一個設計優良的函數能夠把程序中不需要了解的細節隱藏起來,從而使整個程序結構更加清晰,降低程序的修改難度。
C 語言程序由許多小的函數組成,一個程序會被保存在多個源文件中,每個文件可以單獨編譯,并可以與庫中已編譯過的函數一起加載。
下面我們通過一個例子來討論一下函數是如何創建并使用的。
函數創建以及使用
函數的創建和使用會分為三個步驟:
- 函數原型 ( function type ):這個是創建函數定義,也叫函數聲明,能夠表明一個文件中有哪些函數。
- 函數調用 ( function call ):調用函數的位置,函數被定義出來肯定是要使用它的,在哪里使用的這個函數就被稱為函數調用。
- 函數定義 ( function definition ):這個就是函數的具體要干的什么事兒,也就是函數的具體邏輯是什么。
這么一看,函數和變量簡直一模一樣了,函數需要原型、調用和定義,而變量也需要這些,只不過變量還可以把原型和定義一起表示。
#include
intnum;//變量原型
intsum(int,int);//函數原型
intmain(void){
num=12;//變量定義
intnum2=11;//函數原型+函數定義
intall=sum(num,num2);//變量使用,函數使用
printf("all=%d",all);
return0;
}
//函數定義
intsum(inta,intb){
returna+b;
}
上面這段代碼很好的列舉了變量的定義以及函數的定義。
我們首先定義了一個 num 變量,這個就是變量的原型,然后在 main 函數中使用這個變量,就是變量的定義和使用,當然變量也可以直接使用原型 + 定義的方式( 上面的 num2 ),sum 函數演示了函數的原型、定義和使用。這里注意一點,main 函數比較特殊,它是所有方法的入口,而且 main 函數無需定義原型就能直接使用。
上面這段代碼被一起保存在一個文件中,當然你也可以把它們保存在不同的文件中,只不過把它們放在同一個文件中我們在演示的時候比較方便,還有一點就是能夠一起進行編譯,這兩個函數也可以定義在不同的文件中,分別進行編譯,這樣的好處是使程序更加易于維護,代碼讀起來更加順暢,事實上項目中也是采用的單獨編譯的方式。當然你也可以把所有的功能都寫在 main 函數中,只不過這樣不易于維護,也不符合項目開發標準。
一個完整的函數定義形同如下:
返回值類型函數名(參數列表)
{
函數體(函數的具體功能)
}
注意我們上面說的只是一個完整的函數定義,而不是每個函數必須都要有返回值類型、參數列表、函數體,只有函數名是必須的(這個肯定好理解)。
當然也有函數定義出來什么都沒有做,這就相當于是一個空函數,C 語言默認是允許空函數出現的,比如下面函數就是一個空函數。
sort(){}
sort 函數不執行任何操作也不返回任何值,這種函數可以在程序開發期間用于保留位置,留待以后再填充代碼。
程序其實就是一些變量和函數的集合,函數之間的通信可以通過函數參數、返回值來進行,函數通過傳遞參數,進行一系列的邏輯計算后,把返回值返回回去,以此達到函數交流、通信的目的。
對于函數來說,我們需要了解的兩個關鍵點是參數列表和返回值。
函數參數
對于上面的 sum 函數來說,它的函數參數有兩個,分別是 int 類型的 a 和 b,像這種在函數定義的括號中的變量被稱為函數參數,這兩個變量 a 和 b 也叫做形式參數,簡稱形參。
和定義在函數中的變量一樣,形式參數也是局部變量,這些都屬于函數私有的,作用域范圍都是從進入函數開始起作用到函數執行完成后作用結束。
當函數接受參數時,函數原型用逗號分隔的列表指明參數的數量和類型,函數原型中你可以使用下面方式定義。
intsum(inta,intb);//函數原型
也可以省略具體的變量名稱,使用下面這種方式進行定義。
intsum(int,int);//函數原型
在函數原型中沒有定義變量,只是聲明了兩個 int 類型的參數。
除了形參之外,還有一個叫做實際參數 ( 實參 ) 的概念,就對應于上面代碼中的 sum(num,num2),因為在調用 sum 的時候是知道 num 和 num2 的具體值的,像這種在調用函數中對參數進行傳值的參數被稱為實參。
簡單點來說就是 形式參數是被調用函數中的變量,實際參數是調用函數賦給被調函數的具體值。實際參數可以是常量、變量,或甚至是更復雜的表達式。
被調函數不知道也不關心傳入的數值是來自常量、變量還是一般表達式。實參在把值傳遞給函數的時候,其實是把值拷貝給被調函數的形式參數,所以無論被調函數對拷貝數據進行什么操作,都不會影響主調函數中的原始數據。
如下代碼所示
#include
intnum;//變量原型
voidsum(int,int);//函數原型
intmain(void){
num=12;//變量定義
intnum2=11;//函數原型+函數定義
sum(num,num2);//變量使用,函數使用
printf("num=%d,num2=%d",num,num2);
return0;
}
//函數定義
voidsum(inta,intb){
intsumAll=a+b;
printf("sumAll=%d
",sumAll);
}
從輸出結果可以看出,只要把值傳遞給 sum 后,不論 sum 函數內部進行何種處理,都不會影響 main 函數中 num 和 num2 的值。
函數返回值
我們上面說過函數之間的通信可以通過函數參數、返回值來進行。函數參數的傳遞方向是由函數調用者 -> 被調函數,而函數返回值的方向是和參數傳遞的方向相反,也就是被調函數 -> 函數調用者。
![b0fb9196-fcc2-11ec-ba43-dac502259ad0.jpg](https://file1.elecfans.com//web2/M00/95/99/wKgZomTnCAyABokXAAA6iLeFeV8655.jpg)
當然并不是所有的函數都需要返回值,而且 return 語句后面也不一定需要表達式,當 return 語句后面沒有表達式時,函數不會向調用者返回值。返回值會通過
return表達式
進行返回,這個返回值的表達式類型和函數定義的返回值類型是一致的。
我們還用上面的 sum 函數來舉例子
intsum(inta,intb){
returna+b;
}
可以看到,sum 函數的表達式返回了 a + b,這其實就是一個表達式。而我們可以看到上面的 int main 方法,它的返回值是 0 ,這就是返回了一個常量。
return 后面可以不返回任何值,只是單獨寫一個 return 也是允許的,不過這種方式相當于沒有返回任何值,所以它的函數類型可以定義為 void ,如下代碼所示:
//函數定義
voidsum(inta,intb){
intsumAll=a+b;
printf("sumAll=%d
",sumAll);
return;
}
使用 return 語句的另外一個作用是終止函數的執行,強制把控制返回給調用函數,如下代碼所示:
//函數定義
intsum(inta,intb){
intsumAll=a+b;
printf("sumAll=%d
",sumAll);
if(a+b>0){
returnsumAll;
}else{
return0;
}
}
如果 a + b 的值大于 0 的話,會直接返回 a + b 的和,否則為 0 。這個 if 的控制流程就是強制把結果返回給函數調用者。如果在 if 控制流程后面添加代碼的話,那么這段代碼不會執行,但是編譯卻沒有給出警告。
![b1162c04-fcc2-11ec-ba43-dac502259ad0.jpg](https://file1.elecfans.com//web2/M00/95/99/wKgZomTnCAyADd1bAAA5zbqslJs972.jpg)
在 Java 編輯器中,如果最后一行代碼出現在 return 強制返回后面的話,編譯器會給出警告或者錯誤提示這行代碼不會被執行。
函數類型
這里需要再強調一下函數類型,定義函數的時候需要聲明函數的類型,帶返回值的函數類型與返回值類型相同,沒有返回值的函數應該將其定義為 void 類型。在老版本的 C 編譯器中,如果你沒有聲明函數類型,編譯器會默認把函數當做 int 類型來處理,不過這都是早期的事兒了,現在 C 標準不再支持默認函數為 int 類型這種情況。
在編寫函數的時候,你就需要考慮好函數的具體功能是什么,也就是這個函數做了哪些事情,需不需要返回值,如果需要返回值的話,它的返回類型是什么。
函數聲明
如果大家學過 Java ,可能對 C 這種先聲明再定義的方式很不習慣,為什么函數在定義前需要再單獨聲明一下呢?我直接定義函數不聲明行嗎?答案肯定是不行的。
這個先聲明再使用一直是 C 語言的標準,標準沒有為什么,這就是一個標準,但是這個標準卻是一個歷史遺留問題。
上世紀 70 年代,大部分計算機內存很小,處理速度也比較差,所以導致代碼的運行>時間很長,效率很差,這時候進行我們就需要考慮內存占用和編譯時間的問題。因為 C 語言開發的比較早,而且 C 又是和硬件直接打交道的,所以提前聲明一下函數能夠提前分配內存空間,提升效率。說白了還是效率問題。
還有為什么 C 語言不選擇采用預編譯一下呢?
參考自 https://www.zhihu.com/question/20567689
首先,C語言出現的很早,那時候編譯器也是一個很復雜的東西,當時計算機的內存、外存都很小,編譯器做的太大也是一個麻煩的事情,所以事先聲明就成為一種規范,保留下來,目的是為了讓編譯器更簡單,雖然這一切已經很過時了。
其次,預編譯的成本很高,與腳本語言、解釋語言不同,C語言項目的規模可以很大,比如操作系統一級的C語言工程,其源文件有幾萬個,涉及全局符號幾十萬個,這樣規模的項目預編譯一次的負擔是很高的,如果是整個項目完全掃描一遍,遍歷所有全局符號,再進行真正的編譯,估計很多碼農都會瘋了,等待時間會特別長。
再次,C語言是一種靜態鏈接的語言,如果一個項目被設計成只編譯,不鏈接的方式,比如有些庫就會被設計成這樣,有些合作開發的項目里,組員之間有時候也只提供obj文件,那么某些全局符號可能就不包含在現有的代碼里,那么預搜索就一定找不到某些符號,那么該怎么辦?如果不提供聲明,這個代碼就沒辦法編譯了。
基于以上幾點考慮,所以C語言才設計成這樣,對于開發者而言,不算友好,但也不算很糟糕,甚至在某些方面是有好處的。
對于一個函數來說,它的最終目的就是通過一系列的邏輯處理獲得我們想要的結果,邏輯處理離不開各種程序控制語句,比如說 While 、for、do while 等,下面我們就要來討論一下這些程序控制語句。
程序控制語句
在有些時候的某些程序可能會重復做一件事情,就應該讓計算機做這些重復性的工作,這才是我們需要計算機的意義。畢竟,需要重復計算是使用計算機的主要原因。
C 語言中有很多用于重復計算的方法,我們下面先來介紹其中的一種 --- while 循環。
while 循環
下面我們通過一段代碼來看一下 while 循環的使用。
#include
intmain()
{
inti=1;
while(i<=?10)
{
printf("%d
",i);
i++;
}
return0;
}
這段代碼首先聲明了一個 i 變量,然后使用了 while 循環來判斷 i 的值,當 i 的值 <= 10 的時候,就會執行 while 中的循環邏輯,否則即 i > 10 就會直接跳過循環,不會輸出任何結果就直接返回 0 。
如果 i 的值在 10 以內,就會循環打印出來 i 的值。這就是 while 循環的作用。
用通俗易懂的語句來描述 while 循環:當某個判斷條件為 true 的時候,循環執行 while 中的代碼塊。
流程圖如下:
![b132a6e0-fcc2-11ec-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/95/99/wKgZomTnCA2AQL8JAADGA_5zA9Q808.png)
在 while 循環中的一個關鍵點就是進入 while 循環的判斷,上面代碼就是判斷 i <= 10 ,這個表達式是關系運算符的一種。
while循環經常依賴測試表達式作比較,這樣的表達式被稱為關系表達式,出現在關系表達式中間的運算符叫做關系運算符,下表是我們經常使用到的關系運算符。
運算符 | 說明 |
---|---|
< | 小于 |
<= | 小于或等于 |
== | 等于 |
>= | 大于或等于 |
> | 大于 |
!= | 不等于 |
這些運算符會不單單會出現在 while 循環中,實際上任何邏輯控制語句都會使用到這幾種運算符。
這里需要說明一點,不能用關系運算符來比較字符串,比如 ch != '@' 。
雖然關系運算符可以用來比較浮點數,但是要注意:比較浮點數時,盡量只使用 < 和 > 。因為浮點數的舍入誤差會導致在邏輯上應該相等的兩數卻不相等。例如,3乘以1/3的積是1.0。如果用把1/3表示成小數點后面6位數字,乘積則是 .999999,不等于 1。
for 循環
for 循環一個非常明顯的特征就是把三個行為組合在一處,也就是初始化、判斷、更新,如下代碼所示。
#include
intmain()
{
for(inti=1;i<=?10;i++){
printf("%d
",i);
}
return0;
}
可以看到,上面代碼中 for 循環分別做了三件事情,每個表達式用 ;
進行分隔。
- int i = 0 相當于是對 i 進行初始化操作;
- i <= 10 相當于對 i 進行一個邏輯判斷,邏輯判斷是判斷是否進行下一次循環的關鍵。
- i++ 相當于是更新 i 的值。
for 循環的一般形式定義如下:
for(表達式1;表達式2;表達式3)
{
語句;
}
這里要注意的是,表達式 1 只在循環開始時執行一次,而表達式 3 是循環結束后再執行。表達式 2 可以省略,省略后默認值為 1,則判斷為真,for 循環就會成為一個死循環。
for 循環的流程圖如下
![b15b1bb6-fcc2-11ec-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/95/99/wKgZomTnCA2AGwJTAAD7_MrS_ME141.png)
do while 循環
一般來說,循環的方式可以分為兩種:入口循環和出口循環,什么意思呢?入口循環是先進行循環,再執行每次循環要做的事情,比如上面的 while 循環、for 循環,他們都是先進行判斷是否需要進行下一次循環,如果需要的話,才會打印出 i 的值,這就是入口循環。
而出口循環則是要先執行代碼,再判斷是否要進行下一次循環,即在循環的每次迭代之后檢查測試條件,這保證了至少執行循環體中的內容一次,典型的出口循環就是 do ... while。
我們把上面的代碼進行修改:
#include
intmain()
{
inti=1;
do{
printf("%d
",i);
i++;
}while(i<=?10);
return0;
}
從輸出結果可以看到,do while 循環在執行完循環體后才執行測試條件,所以 do ... while 循環至少執行循環體一次,而 for 循環和 while 循環在執行循環體之前先執行測試條件,do ... while 的一般形式如下
do
代碼
while(表達式);
do ... while 循環的流程圖如下
![b17c01dc-fcc2-11ec-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/95/99/wKgZomTnCA2AdPmdAADAe2jE4iw967.png)
到現在為止, C 語言中的程序控制語句我們都了解了,那么該如何進行選擇呢?
實際上上面我們已經稍微討論了一下如何選擇的問題了。
while 循環和 for 循環很類似,這兩類循環都是先進行一次循環條件的判斷,然后再執行具體的循環體操作,只要一次循環條件不滿足則一次都不會執行;而 do ... while 循環會至少先進行一次循環,然后才會執行循環判斷。
一般來說,使用 for 循環的場景比較多,因為 for 循環形式更加簡潔,而且在 for 循環中,變量和判斷以及更新的作用域都在循環體內,不會有其他外部代碼來修改這些變量,更可控,在 while 和 do ... while 循環中,變量的更新不可控,而且代碼也沒有 for 循環可讀性強。
break 和 continue
break 和 continue 相當于是循環體內領導者的這樣一個角色,有了這兩個角色存在,循環體內的代碼會根據這兩個關鍵字來判斷是中斷循環還是執行下一次循環。
C 語言中的 break 有兩種用法:
- 一種用法是用在循環體中,當 break 出現在循環體中時,會中斷這個循環。
- 一種用法是用在 switch 語句中,用作中斷這個 switch 語句的 case 條件。
break 用于中斷循環:如下代碼所示
#include
intmain(void)
{
for(inti=1;i<=?10;i++){
if(i==5){
break;
}
printf("i的值=%d
",i);
}
return0;
}
輸出的結果是 i 的值 = 1 - 4, 當 i == 5 時,會進入到 if 判斷中,if 判斷會直接觸發 break,break 用于跳出當前循環,當前是 for 循環,所以 break 會直接跳到 for 循環外面,也就是直接 return ,不會再打印 i 的值。
![b19152a8-fcc2-11ec-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/95/99/wKgZomTnCA2AeMh7AAARnNyN3Wc658.png)
continue 關鍵字用于跳過當前循環,執行下一次循環,它和 break 很相似但是有著本質的區別,break 是跳出循環,continue 是執行下一次循環,我們同樣拿這個代碼來說明,只需要把上面的 break 改成 continue 即可。
#include
intmain(void)
{
for(inti=1;i<=?10;i++){
if(i==5){
continue;
}
printf("i的值=%d
",i);
}
return0;
}
(這段代碼的輸出結果會輸出出 i = 5 以外的值)
從輸出結果可以看出,只有 i = 5 的值沒有輸出,這也就是說,當代碼執行到 i == 5 的時候,會進行 continue 繼續執行當前循環,從而跳過這次循環后面的代碼,如下圖所示。
![b1a41f28-fcc2-11ec-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/95/99/wKgZomTnCA2AbWlYAAASOMbIT8s273.png)
總結
這篇文章我主要和你聊了聊 C 語言中的函數,函數定義、函數返回值、參數以及程序控制流程中的三類循環的特點以及選型,最后又介紹了一下 break 和 continue 的作用。
如果文章對你有幫助,還請各位小伙伴們三連支持哦!
審核編輯:湯梓紅
-
C語言
+關注
關注
180文章
7615瀏覽量
137827 -
函數
+關注
關注
3文章
4346瀏覽量
63012 -
break
+關注
關注
0文章
6瀏覽量
2509
原文標題:講一篇通俗易懂的 C 函數。
文章出處:【微信號:cxuangoodjob,微信公眾號:程序員cxuan】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
C語言中指針函數和函數指針的概念及應用示例
C語言中memmove函數的使用
c語言函數指針定義,指針函數和函數指針的區別
單片機C語言中如何使用nop函數進行短延時的效果資料和程序說明
![單片機<b class='flag-5'>C</b><b class='flag-5'>語言中</b>如何使用nop<b class='flag-5'>函數</b>進行短延時的效果資料和程序說明](https://file.elecfans.com/web1/M00/86/A5/pIYBAFx953iAYmFtAAQ3NBrSV_U802.png)
評論