02.01 零基礎學C語言——函數

這是一個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類型衝突。一般導致這樣的報錯有兩種原因:

  1. 函數名稱重複了,即定義了兩個同名函數,且返回值類型或參數類型、參數數目不同
  2. 函數沒重複但是缺少函數聲明

這裡,是因為第二個原因導致的。為何是第二個呢?

由於因為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時,函數停止自身調用,然後逐層返回。



本篇涉及一些後續文章中的內容,實在無法避免,建議初學者在通讀後,先繼續閱讀後續文章,然後再返回本篇復讀一遍。


喜歡的讀者可以關注碼哥,也可以在下方留言評論,如果有不清楚的地方也可以私信碼哥,我會第一時間回覆。


分享到:


相關文章: