這是一個C語言系列文章,如果是初學者的話,建議先行閱讀之前的文章。筆者也會按照章節順序發佈。
我們介紹了表達式和語句,這一篇我們介紹C語言中的函數。
首先,我們先來看個例子:
<code>int add(int num1, int num2)
{
return num1 + num2;
}/<code>
上面這段內容就是一個完整的C語言函數。
函數的功能
函數與我們在數學中瞭解到的函數概念比較相似。粗略地講,在數學中,函數是用來完成某種功能的,例如,sin、cos等。在C語言中,與之類似,都是用於封裝某種功能的。函數不但將功能封裝成小的個體函數,還可以在需要的地方直接調用,避免通篇複製功能細節代碼。
上面的例子就是計算兩數相加的結果。當我給出具體的數值,例如1和2,那麼這個函數就會返回3,其中1和2我們稱作輸入參數,3稱作函數的輸出(或者 返回值)。
讀過碼哥前面幾篇文章的讀者可能記得,在表達式一節中,函數調用表達式的值就是函數返回值,我們將在下面進一步介紹。
函數命名規範
在C語言中,函數的命名規範與變量的命名規範是一樣的,都是以下劃線(_)或字母開頭,後續字符可以是字母、數字、下劃線。下面給出一些例子:
<code>_abc 符合命名規範
123abc 不符合規範
abc 符合規範
~abc 不符合規範/<code>
函數定義的一般形式
由於在C語言中有多種 ,因此,函數的參數類型、參數數量、返回值類型都有所不同。除去這些不同,其他部分則是有統一形式的。
<code>返回值類型 函數名 (參數1, ...)
{
各種語句
return 返回值;
}/<code>
其中:
函數參數
根據功能需要可有參數也可以沒有參數。例如:
<code>int get10(void)
{
return 10;
}
//或者
int get10()
{
return 10;
}/<code>
對於不需要參數的函數,可以在參數列表處寫void,也可以省略不寫。
返回值
如果函數不需要返回值,那麼return語句可以省略,但是函數 返回值類型 處要寫void。
如果不寫返回值類型,那麼編譯器默認為int型。
<code>void print_help(void)
{
printf("Help information\\n");
}
或
void print_help(void)
{
printf("Help information\\n");
return;
}/<code>
其中,printf是C標準IO庫中定義的終端輸出函數,初學者暫時不必糾結於此,只記住這個函數可以將裡面的字符串部分輸出到屏幕上。
可能有的讀者會發現,筆者從來沒說過基礎數據類型中有字符串類型。是的,C語言中確實沒有這個基礎類型,字符串在C中都是以字符數組出現的,關於數組相關內容,筆者將會在後面文章中講解。
函數的聲明是用來告知編譯器,函數在當前代碼文件或者其他代碼文件中有定義。
為何有這種需求呢?我們來看下面這個例子:
<code>int main(void)
{
foo();
}
void foo(void
)
{
}/<code>
這個例子的功能是,在main函數中調用foo函數。
將代碼直接寫入文件然後編譯,編譯器會報錯,大致是說main中的foo和外面的foo類型衝突。一般導致這樣的報錯有兩種原因:
- 函數名稱重複了,即定義了兩個同名函數,且返回值類型或參數類型、參數數目不同
- 函數沒重複但是缺少函數聲明
這裡,是因為第二個原因導致的。為何是第二個呢?
由於因為C語言編譯器在處理源文件(即代碼文件)時,是由上至下,由左至右的讀取和處理文件內容的,因此就會對函數定義的位置、順序較為敏感。在上面的這個例子中,當處理到main時,編譯器並不知道這個文件中定義了foo函數,因此當看到main函數中調用了foo函數時就會誤將調用當函數聲明。而後當讀取到foo函數定義時,發現和之前的聲明不一致(返回值類型)。
解決方法很容,見下例:
<code>void foo(void);
int main(void)
{
foo();
}
void foo(void)
{
}/<code>
再次編譯即可通過。
我們在main函數定義前加入的內容就是foo函數的聲明。
<code>返回值類型 函數名(參數列表);/<code>
其中,參數列表部分,若無參數可以省略不寫或者寫void,與定義一樣。但如果有參數,則可省略參數名,亦可不省略。例如:
<code>int add(int num1, int num2);
或
int add(int, int);/<code>
都是可行的,因為編譯器在讀取聲明時只關心函數參數的類型,而不關心具體名字。
函數調用
上面我們其實已經見過函數的調用了。下面先說其一般形式:
<code>函數名(輸入參數列表);/<code>
例如,
<code>add(1, 2); //調用最開始定義的add函數,函數有返回值,但是並未使用到
int result = add(1, 2); //調用add函數,並將返回值賦給變量result,result的值為3/<code>
函數參數
在C語言中,我們將函數定義中的參數稱做形式參數(形參),函數調用中的參數稱作實際參數(實參)。
上面的例子中,1和2就是實參,而定義中的num1和num2就是形參。
在一些關於C語言的文章中,有人說參數的傳遞包含兩種方式,這點筆者不敢苟同。在C語言中只有一種傳參方式——值傳遞。
其中,參數傳遞是指:調用函數時,將實際參數傳遞給被調用函數的形式參數,也可以看作是一種映射過程。
值傳遞(此處內容建議初學者在閱讀後續數組和指針文章後再來閱讀):
值傳遞最直接的理解就是傳遞數值。
在C語言中,基礎數據類型的參數傳遞比較顯而易見,例如上面add調用的例子,傳遞的是兩個整數值。但是數組的傳遞,對於一些寫慣腳本語言的開發者來說會不太習慣。看一個例子:
<code>int new_add(int *nums)
{
return nums[0] + nums[1];
}
int main(void)
{
int nums = {1, 2};
int result = new_add(nums);
return 0;
}/<code>
這裡,在main中定義了一個整型數組,其中含有兩個元素分別是1和2。我要將數組傳給new_add函數,然後讓函數返回兩個元素相加的結果。此時,我們傳遞給new_add函數的參數並非完整數組的拷貝,而是數組的首地址,即指針。而指針也是一個值(可被看作無符號長整型),而非一種結構。
main函數
main函數也被叫做主函數,每一個生成可執行程序的C語言工程都一定有一個main函數,缺失主函數,編譯器會報錯。所有C程序都會從main函數開始執行。
在UNIX系統下(Linux、OSX等),主函數的形式如下:
<code>int main(void)
{
...//具體內容
return 0;//具體返回值可自己定但一定是整數
}
或
int main(int argc, char *argv[])
{
...//具體內容
return 0;//具體返回值可自己定但一定是整數
}/<code>
其中,argc是命令行參數個數,而argv則是每一個命令行參數內容組成的字符指針數組,例如這個源文件生成的可執行程序叫a,那麼在命令行下執行:
<code>$ ./a argument1 2 3/<code>
此時,argc為4,而argv的內容為: "./a", "argument1", "2", "3"這4個字符數組,即可執行程序名和其命令行參數。關於數組和指針的話題我們後面文章中會介紹。
遞歸函數
下面我們介紹一種特殊的函數調用方式。先來看個例子:
<code>void recursive_func(int i)
{
if (i > 3)
return;
recursive_func(++i);
}
int main(void)
{
recursive_func(0);
return 0;
}/<code>
在這個例子中,main函數中調用了一個名為recursive_func的函數,而recursive_func函數就是一個遞歸函數,即在函數中調用其自身。在main中傳給recursive_func的參數i的值為0,然後在recursive_func內部每次調用自身時都將函數參數自增1,如此一層層調用下去。當i增加到3時,函數停止自身調用,然後逐層返回。
本篇涉及一些後續文章中的內容,實在無法避免,建議初學者在通讀後,先繼續閱讀後續文章,然後再返回本篇復讀一遍。
喜歡的讀者可以關注碼哥,也可以在下方留言評論,如果有不清楚的地方也可以私信碼哥,我會第一時間回覆。
閱讀更多 碼哥比特 的文章