小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

【零基礎(chǔ)學(xué)習(xí)iOS開(kāi)發(fā)】【02-C語(yǔ)言】11-函數(shù)的聲明和定義

 印度阿三17 2019-09-08
原文鏈接:https://my.oschina.net/kmodel/blog/619368

在上一講中,簡(jiǎn)單介紹了函數(shù)的定義和使用,只要你想完成一個(gè)新功能,首先想到的應(yīng)該是定義一個(gè)新的函數(shù)來(lái)完成這個(gè)功能。這講繼續(xù)介紹函數(shù)的其他用法和注意事項(xiàng)。

一、函數(shù)的聲明

1.在C語(yǔ)言中,函數(shù)的定義順序是有講究的:默認(rèn)情況下,只有后面定義的函數(shù)才可以調(diào)用前面定義過(guò)的函數(shù)

1 int sum(int a, int b) {
2     return a   b;
3 }
4 
5 int main()
6 {
7     int c = sum(1, 4);
8     return 0;
9 }

第5行定義的main函數(shù)調(diào)用了第1行的sum函數(shù),這是合法的。如果調(diào)換sum函數(shù)和main函數(shù)的順序,在標(biāo)準(zhǔn)的C編譯器環(huán)境下是不合法的(不過(guò)在GCC編譯器環(huán)境下只是一個(gè)警告)

?

2.如果想把函數(shù)的定義寫(xiě)在main函數(shù)后面,而且main函數(shù)能正常調(diào)用這些函數(shù),那就必須在main函數(shù)的前面進(jìn)行函數(shù)的聲明

 1 // 只是做個(gè)函數(shù)聲明,并不用實(shí)現(xiàn)
 2 int sum(int a, int b);
 3 
 4 int main()
 5 {
 6     int c = sum(1, 4);
 7     return 0;
 8 }
 9 
10 // 函數(shù)的定義(實(shí)現(xiàn))
11 int sum(int a, int b) {
12     return a   b;
13 }

在第11行定義了sum函數(shù),在第2行對(duì)sum函數(shù)進(jìn)行了聲明,然后在第6行(main函數(shù)中)就可以正常調(diào)用sum函數(shù)了。

?

3.函數(shù)的聲明格式

1> 格式

返回值類型  函數(shù)名 (參數(shù)1, 參數(shù)2, ...)

只要你在main函數(shù)前面聲明過(guò)一個(gè)函數(shù),main函數(shù)就知道這個(gè)函數(shù)的存在,就可以調(diào)用這個(gè)函數(shù)。而且只要知道函數(shù)名、函數(shù)的返回值、函數(shù)接收多少個(gè)參數(shù)、每個(gè)參數(shù)是什么類型的,就能夠調(diào)用這個(gè)函數(shù)了,因此,聲明函數(shù)的時(shí)候可以省略參數(shù)名稱。比如上面的sum函數(shù)聲明可以寫(xiě)成這樣:

int sum(int, int);

究竟這個(gè)函數(shù)是做什么用的,還要看函數(shù)的定義。

?

2> 如果只有函數(shù)的聲明,而沒(méi)有函數(shù)的定義,那么程序?qū)?huì)在鏈接時(shí)出錯(cuò)

下面的寫(xiě)法是錯(cuò)誤的:

1 int sum(int a, int b);
2 
3 int main()
4 {
5     
6     sum(10, 11);
7 
8     return 0;
9 }
  • 在第1行聲明了一個(gè)sum函數(shù),但是并沒(méi)有對(duì)sum函數(shù)進(jìn)行定義,接著在第6行調(diào)用sum函數(shù)
  • 這個(gè)程序是可以編譯成功的,因?yàn)槲覀冊(cè)趍ain函數(shù)前面聲明了sum函數(shù)(函數(shù)的聲明和定義是兩碼事),這個(gè)函數(shù)聲明可以理解為:在語(yǔ)法上,騙一下main函數(shù),告訴它sum函數(shù)是存在的,所以從語(yǔ)法的角度上main函數(shù)是可以調(diào)用sum函數(shù)的。究竟這個(gè)sum函數(shù)存不存在呢,有沒(méi)有被定義呢?編譯器是不管的。在編譯階段,編譯器并不檢測(cè)函數(shù)有沒(méi)有定義,只有在鏈接的時(shí)候才會(huì)檢測(cè)這個(gè)函數(shù)存不存在,也就是檢測(cè)函數(shù)有沒(méi)有被定義。
  • 因此,這個(gè)程序會(huì)在鏈接的時(shí)候報(bào)錯(cuò),錯(cuò)誤信息如下:

  • 我這里的源文件是main.c文件,所以編譯成功后生成一個(gè)main.o文件。鏈接的時(shí)候,鏈接器會(huì)檢測(cè)main.o中的函數(shù)有沒(méi)有被定義。
  • 上面的錯(cuò)誤信息大致意思是:在main.o文件中找不到sum這個(gè)標(biāo)識(shí)符。
  • 錯(cuò)誤信息中的linker是鏈接器的意思,下次看到這個(gè)linker,說(shuō)明是鏈接階段出錯(cuò)了。鏈接出錯(cuò)了,就不能生成可執(zhí)行文件,程序就不能運(yùn)行。
  • 這個(gè)錯(cuò)誤的解決方案就是加上sum函數(shù)的定義。

?

二、多源文件開(kāi)發(fā)

1.為什么要有多個(gè)源文件

1> 在編寫(xiě)第一個(gè)C程序的時(shí)候已經(jīng)提到:我們編寫(xiě)的所有C語(yǔ)言代碼都保存在拓展名為.c的源文件中,編寫(xiě)完畢后就進(jìn)行編譯、鏈接,最后運(yùn)行程序。

2> 在前面的學(xué)習(xí)過(guò)程中,由于代碼比較少,因此所有的代碼都保存在一個(gè).c源文件中。但是,在實(shí)際開(kāi)發(fā)過(guò)程中,項(xiàng)目做大了,源代碼肯定非常多,很容易就上萬(wàn)行代碼了,甚至上十萬(wàn)、百萬(wàn)都有可能。這個(gè)時(shí)候如果把所有的代碼都寫(xiě)到一個(gè).c源文件中,那么這個(gè)文件將會(huì)非常龐大,也非常惡心,你可以想象一下,一個(gè)文件有十幾萬(wàn)行文字,不要說(shuō)調(diào)試程序了,連閱讀代碼都非常困難。

3> 而且,公司里面都是以團(tuán)隊(duì)開(kāi)發(fā)為主,如果多個(gè)開(kāi)發(fā)人員同時(shí)修改一個(gè)源文件,那就會(huì)帶來(lái)很多麻煩的問(wèn)題,比如張三修改的代碼很有可能會(huì)抹掉李四之前添加的代碼。

4> 因此,為了模塊化開(kāi)發(fā),一般會(huì)將不同的功能寫(xiě)到不同的.c源文件中,這樣的話,每個(gè)開(kāi)發(fā)人員都負(fù)責(zé)修改不同的源文件,達(dá)到分工合作的目的,能夠大大提高開(kāi)發(fā)效率。也就是說(shuō),一個(gè)正常的C語(yǔ)言項(xiàng)目是由多個(gè).c源文件構(gòu)成。

?

2.將sum函數(shù)寫(xiě)到其他源文件中

接下來(lái)就演示一下多個(gè)源文件的開(kāi)發(fā),我將前面定義的sum函數(shù)寫(xiě)在另一個(gè)源文件(命名為sum.c)中。這時(shí)候就有兩個(gè)源文件:

1> main.c文件

1 int main()
2 {
3 
4     return 0;
5 }

2> sum.c文件

1 int sum(int a, int b)
2 {
3     return a   b;
4 }

?

3.在main函數(shù)中調(diào)用sum函數(shù)

1> 現(xiàn)在想在main函數(shù)中調(diào)用sum函數(shù),那么你可能會(huì)直接這樣寫(xiě):

1 int main()
2 {
3     int c = sum(10, 11);
4 
5     return 0;
6 }

這種寫(xiě)法在標(biāo)準(zhǔn)C語(yǔ)言編譯器中是直接報(bào)錯(cuò)的,因?yàn)閙ain函數(shù)都不知道sum函數(shù)的存在,怎么可以調(diào)用它呢?。?!

2> 我們應(yīng)該騙一下main函數(shù),sum函數(shù)是存在的,告訴它sum函數(shù)的返回值和參數(shù)類型即可。也就是說(shuō),應(yīng)該在main函數(shù)前面,對(duì)sum函數(shù)進(jìn)行聲明。

main.c文件應(yīng)該寫(xiě)成下面這樣

 1 #include <stdio.h>
 2 
 3 int sum(int, int);
 4 
 5 int main()
 6 {
 7     int c = sum(10, 11);
 8     
 9     printf("c is %d\n", c);
10     
11     return 0;
12 }

注意第3行,加了一個(gè)sum函數(shù)的聲明。為了檢驗(yàn)sum函數(shù)的調(diào)用結(jié)果,在第9行用prinf函數(shù)將結(jié)果輸出。

?

4.編譯所有的源文件

sum.c和main.c都編寫(xiě)完畢后,就可以使用gcc指令進(jìn)行編譯了。同時(shí)編譯兩個(gè)文件的指令是:cc -c main.c sum.c

編譯成功后,生成了2個(gè).o目標(biāo)文件

也可以單獨(dú)編譯:

cc -c main.c

cc -c sum.c

?

5.鏈接所有的目標(biāo)文件

前面已經(jīng)編譯成功,生成了main.o和sum.o文件?,F(xiàn)在應(yīng)該把這2個(gè).o文件進(jìn)行鏈接,生成可執(zhí)行文件。

1> 注意,一定要同時(shí)鏈接兩個(gè)文件。如果你只是單獨(dú)鏈接main.o或者sum.o都是不可能鏈接成功的。原因如下:

  • 如果只是鏈接main.o文件:cc main.o,錯(cuò)誤信息是:在main.o中找到不到sum這個(gè)標(biāo)識(shí)符,其實(shí)就是找不到sum函數(shù)的定義。因?yàn)閟um函數(shù)的定義在sum.o文件中,main.o中只有sum函數(shù)的聲明

  • 如果只是鏈接sum.o文件:cc sum.o,錯(cuò)誤信息是:找不到main函數(shù)。一個(gè)C程序的入口點(diǎn)就是main函數(shù),main函數(shù)定義在main.o中,sum.o中并沒(méi)有定義main函數(shù),連入口都沒(méi)有,怎么能鏈接成功、生成可執(zhí)行文件呢?

可以看出,main.o和sum.o有密不可分的關(guān)系,其實(shí)鏈接的目的就是將所有相關(guān)聯(lián)的目標(biāo)文件和C語(yǔ)言函數(shù)庫(kù)組合在一起,生成可執(zhí)行文件。

2> 鏈接main.o和sum.o文件:cc main.o sum.o,生成了可執(zhí)行文件a.out

3> 運(yùn)行a.out文件:./a.out,運(yùn)行結(jié)果是在屏幕上輸出了:

c is 21

說(shuō)明函數(shù)調(diào)用成功,我們已經(jīng)成功在main.c文件的main函數(shù)中調(diào)用了sum.c文件中的sum函數(shù)

4> 從中也可以得出一個(gè)結(jié)論:只要知道某個(gè)函數(shù)的聲明,就可以調(diào)用這個(gè)函數(shù),編譯就能成功。不過(guò)想要這個(gè)程序能夠運(yùn)行成功,必須保證在鏈接的時(shí)候能找到函數(shù)的定義。

?

三、#include

理解完前面的知識(shí)后,接下來(lái)就可以搞懂一個(gè)很久以前的問(wèn)題:每次寫(xiě)在最前面的#include是干啥用的?

1.#include的作用

先來(lái)看一個(gè)最簡(jiǎn)單的C程序:

1 #include <stdio.h>
2 
3 int main()
4 {
5     printf("Hello, World!\n");
6     return 0;
7 }

這個(gè)程序的作用是在屏幕上輸出Hello,World!這一串內(nèi)容,我們主要關(guān)注第一行代碼。

  • #include 是C語(yǔ)言的預(yù)處理指令之一,所謂預(yù)處理,就是在編譯之前做的處理,預(yù)處理指令一般以 # 開(kāi)頭
  • #include 指令后面會(huì)跟著一個(gè)文件名,預(yù)處理器發(fā)現(xiàn) #include 指令后,就會(huì)根據(jù)文件名去查找文件,并把這個(gè)文件的內(nèi)容包含到當(dāng)前文件中。被包含文件中的文本將替換源文件中的 #include 指令,就像你把被包含文件中的全部?jī)?nèi)容拷貝到這個(gè) #include 指令所在的位置一樣。所以第一行指令的作用是將stdio.h文件里面的所有內(nèi)容拷貝到第一行中。
  • 如果被包含的文件拓展名為.h,我們稱之為"頭文件"(Header File),頭文件可以用來(lái)聲明函數(shù),要想使用這些函數(shù),就必須先用 #include 指令包含函數(shù)所在的頭文件
  • #include 指令不僅僅限于.h頭文件,可以包含任何編譯器能識(shí)別的C/C 代碼文件,包括.c、.hpp、.cpp等,甚至.txt、.abc等等都可以

也就是說(shuō)你完全可以將第3行~第7行的代碼放到其他文件中,然后用 #include 指令包含進(jìn)來(lái),比如:

1> 將第3行~第7行的代碼放到my.txt中

2> 在main.c源文件中包含my.txt文件

  • 編譯鏈接后,程序還是可以照常運(yùn)行的,因?yàn)?#include 的功能就是將文件內(nèi)容完全拷貝到 #include 指令所在的位置
  • 說(shuō)明:這里用txt文件純屬演示,平時(shí)做項(xiàng)目不會(huì)這樣做,除非吃飽了撐著,才會(huì)把代碼都寫(xiě)到txt中去

?

2.#include可以使用絕對(duì)路徑

上面的#include "my.txt"使用的是相對(duì)路徑,其實(shí)也可以使用絕對(duì)路徑。比如#include "/Users/apple/Desktop/my.txt"

?

3.#include <>和#include ""的區(qū)別

二者的區(qū)別在于:當(dāng)被include的文件路徑不是絕對(duì)路徑的時(shí)候,有不同的搜索順序。

1> 對(duì)于使用雙引號(hào)""來(lái)include文件,搜索的時(shí)候按以下順序:

  • 先在這條include指令的父文件所在文件夾內(nèi)搜索,所謂的父文件,就是這條include指令所在的文件
  • 如果上一步找不到,則在父文件的父文件所在文件夾內(nèi)搜索;
  • 如果上一步找不到,則在編譯器設(shè)置的include路徑內(nèi)搜索;
  • 如果上一步找不到,則在系統(tǒng)的INCLUDE環(huán)境變量?jī)?nèi)搜索

2> 對(duì)于使用尖括號(hào)<>來(lái)include文件,搜索的時(shí)候按以下順序:

  • 在編譯器設(shè)置的include路徑內(nèi)搜索;
  • 如果上一步找不到,則在系統(tǒng)的INCLUDE環(huán)境變量?jī)?nèi)搜索

我這里使用的是clang編譯器,clang設(shè)置include路徑是(4.2是編譯器版本):/usr/lib/clang/4.2/include

Mac系統(tǒng)的include路徑有:

  • /usr/include
  • /usr/local/include

?

4.stdio.h

我們已經(jīng)知道#include指令的作用了,可是為什么要在第一行代碼包含stdio.h呢?

  • stdio.h 是C語(yǔ)言函數(shù)庫(kù)中的一個(gè)頭文件,里面聲明了一些常用的輸入輸出函數(shù),比如往屏幕上輸出內(nèi)容的printf函數(shù)
  • 這里之所以包含 stdio.h 文件,是因?yàn)樵诘?行中用到了在 stdio.h 內(nèi)部聲明的printf函數(shù),這個(gè)函數(shù)可以向屏幕輸出數(shù)據(jù),第7行代碼輸出的內(nèi)容是:Hello, World!
  • 注意:stdio.h里面只有printf函數(shù)的聲明。前面已經(jīng)提到:只要知道函數(shù)的聲明,就可以調(diào)用這個(gè)函數(shù),就能編譯成功。不過(guò)想要這個(gè)程序能夠運(yùn)行成功,必須保證在鏈接的時(shí)候能找到函數(shù)的定義。其實(shí)鏈接除了會(huì)將所有的目標(biāo)文件組合在一起,還會(huì)關(guān)聯(lián)C語(yǔ)言的函數(shù)庫(kù),函數(shù)庫(kù)中就有printf函數(shù)的定義。因此前面的程序是可以鏈接成功的。

?

5.頭文件.h和源文件.c的分工

跟printf函數(shù)一樣,我們?cè)陂_(kāi)發(fā)中會(huì)經(jīng)常將函數(shù)的聲明和定義寫(xiě)在不同的文件中,函數(shù)聲明放在.h頭文件中,函數(shù)定義放在.c源文件中。

下面我們將sum函數(shù)的聲明和定義分別放在sum.h和sum.c中

這是sum.h文件

這是sum.c文件

然后在main.c中包含sum.h即可使用sum函數(shù)

其實(shí)sum.h和sum.c的文件名不一樣要相同,可以隨便寫(xiě),只要文件名是合法的。但還是建議寫(xiě)成一樣,因?yàn)橐豢次募椭纒um.h和sum.c是有聯(lián)系的。

運(yùn)行步驟分析:

1> 在編譯之前,預(yù)編譯器會(huì)將sum.h文件中的內(nèi)容拷貝到main.c中

2> 接著編譯main.c和sum.c兩個(gè)源文件,生成目標(biāo)文件main.o和sum.o,這2個(gè)文件是不能被單獨(dú)執(zhí)行的,原因很簡(jiǎn)單:

* sum.o中不存在main函數(shù),肯定不可以被執(zhí)行

* main.o中雖然有main函數(shù),但是它在main函數(shù)中調(diào)用了一個(gè)sum函數(shù),而sum函數(shù)的定義卻存在于sum.o中,因此main.o依賴于sum.o

3> 把main.o、sum.o鏈接在一起,生成可執(zhí)行文件

4> 運(yùn)行程序

?

說(shuō)到這里,有人可能有疑惑:可不可以在main.c中包含sum.c文件,不要sum.h文件了?

大家都知道#include的功能是拷貝內(nèi)容,因此上面的代碼等效于:

這么一看,語(yǔ)法上是絕對(duì)沒(méi)有問(wèn)題的,main.c、sum.c都能編譯成功,分別生成sum.o、main.o文件。但是當(dāng)我們同時(shí)鏈接main.o和sum.o時(shí)會(huì)出錯(cuò)。原因:當(dāng)鏈接這兩個(gè)文件時(shí)鏈接器會(huì)發(fā)現(xiàn)sum.o和main.o里面都有sum函數(shù)的定義,于是報(bào)"標(biāo)識(shí)符重復(fù)"的錯(cuò)誤,也就是說(shuō)sum函數(shù)被重復(fù)定義了。默認(rèn)情況下,C語(yǔ)言不允許兩個(gè)函數(shù)的名字相同。因此,不要嘗試去#include那些.c源文件。

?

有人可能覺(jué)得分出sum.h和sum.c文件的這種做法好傻B,好端端多出2個(gè)文件,你把所有的東西都寫(xiě)到main.c不就可以了么?

  • 沒(méi)錯(cuò),整個(gè)C程序的代碼是可以都寫(xiě)在main.c中。但是,如果項(xiàng)目做得很大,你可以想象得到,main.c這個(gè)文件會(huì)有多么龐大,會(huì)嚴(yán)重降低開(kāi)發(fā)和調(diào)試效率。
  • 要想出色地完成一個(gè)大項(xiàng)目,需要一個(gè)團(tuán)隊(duì)的合作,不是一個(gè)人就可以搞的定的。如果把所有的代碼都寫(xiě)在main.c中,那就導(dǎo)致代碼沖突,因?yàn)檎麄€(gè)團(tuán)隊(duì)的開(kāi)發(fā)人員都在修改main.c文件,張三修改的代碼很有可能會(huì)抹掉李四之前添加的代碼。
  • 正常的模式應(yīng)該是這樣:假設(shè)張三負(fù)責(zé)編寫(xiě) main函數(shù),李四負(fù)責(zé)編寫(xiě)其他自定義函數(shù),張三需要用到李四編寫(xiě)的某個(gè)函數(shù),怎么辦呢?李四可以將所有自定義函數(shù)的聲明寫(xiě)在一個(gè).h文件中,比如 lisi.h,然后張三在他自己的代碼中用#include包含lisi.h文件,接著就可以調(diào)用lisi.h中聲明的函數(shù)了,而李四呢,可以獨(dú)立地在另外一個(gè)文件中(比如lisi.c)編寫(xiě)函數(shù)的定義,實(shí)現(xiàn)那些在lisi.h中聲明的函數(shù)。這樣子,張三和李四就可以相互協(xié)作、不會(huì)沖突。
?

轉(zhuǎn)載于:https://my.oschina.net/kmodel/blog/619368

來(lái)源:https://www./content-4-443101.html

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多