??? 《C語(yǔ)言趣味教程》?? 猛戳訂閱?。?!
- ?? 寫在前面:這是一套?C 語(yǔ)言趣味教學(xué)專欄,目前正在火熱連載中,歡迎猛戳訂閱!本專欄保證篇篇精品,繼續(xù)保持本人一貫的幽默式寫作風(fēng)格,當(dāng)然,在有趣的同時(shí)也同樣會(huì)保證文章的質(zhì)量,旨在能夠產(chǎn)出?"有趣的干貨" !
本系列教程不管是零基礎(chǔ)還是有基礎(chǔ)的讀者都可以閱讀,可以先看看目錄!?標(biāo)題前帶星號(hào) (*) 的部分不建議初學(xué)者閱讀,因?yàn)閮?nèi)容難免會(huì)超出當(dāng)前章節(jié)的知識(shí)點(diǎn),面向的是對(duì) C 語(yǔ)言有一定基礎(chǔ)或已經(jīng)學(xué)過一遍的讀者,初學(xué)者可自行選擇跳過帶星號(hào)的標(biāo)題內(nèi)容,等到后期再回過頭來(lái)學(xué)習(xí)。
值得一提的是,本專欄?強(qiáng)烈建議使用網(wǎng)頁(yè)端閱讀!?享受極度舒適的排版!你也可以展開目錄,看看有沒有你感興趣的部分!?本章是本教程的開篇之作,篇幅較長(zhǎng)且內(nèi)容極為豐富 (全文近兩萬(wàn)字),是本團(tuán)隊(duì)(只有我一個(gè)人的團(tuán)隊(duì))嘔心瀝血耗時(shí)一周打磨出來(lái)的內(nèi)容,希望需要學(xué)?C 語(yǔ)言的朋友可以耐下心來(lái)讀一讀。最后,可以訂閱一下專欄防止找不到。
" 有趣的寫作風(fēng)格,還有特制的表情包,而且還干貨滿滿!太下飯了!"
—— 沃茲基碩德
?? 本章目錄:
Ⅰ.?你好, 世界?。℉ello World)
0x00 引入:HelloWorld 的由來(lái)
0x01 代碼編輯器的選用(推薦 VS2022)
0x02 創(chuàng)建新項(xiàng)目
0x03?敲下這 “跨越歷史” 的 Hello World!
Ⅱ. 頭文件(Header)
0x00?引入:什么是頭文件?
0x01?標(biāo)準(zhǔn)輸入輸出庫(kù) stdio
0x02 引入頭文件的方式
* 0x03 養(yǎng)成聲明與定義分離的習(xí)慣
* 0x04?頭文件保護(hù)(防止頭文件重復(fù)包含)
* 0x05 條件引用技術(shù)
* 0x06?實(shí)踐錯(cuò)誤記錄:#include 展開的問題
* 0x07?技巧:學(xué)會(huì)定義 _GLOBAL_H 來(lái)管理頭文件
* 0x08 整理:stdio 定義的庫(kù)變量、庫(kù)宏和庫(kù)函數(shù)
Ⅲ.? main 函數(shù)(Main Function)
0x00 引入:繼續(xù)觀察 HelloWorld 示例
0x01 什么是 main 函數(shù)?
0x02?常規(guī)寫法: int main() 和 int main(void)
* 0x03?帶參寫法:int main(int argc, char *argv[])
* 0x04?頗有爭(zhēng)議的寫法:void main()
* 0x05?存在于 C89 的寫法:main()
* 0x06?錯(cuò)誤寫法:mian()
* 0x07?main 函數(shù)執(zhí)行前做的事
* 0x08 可以調(diào)用 main 函數(shù)嗎?
Ⅳ. 深入淺出:Hello,World!
0x00 引入:再看 Hello,World!
0x01 函數(shù)體的概念(花括號(hào)匹配原則)
0x02?簡(jiǎn)單介紹一下 printf 函數(shù)
0x03 分號(hào),語(yǔ)句結(jié)束的標(biāo)志!
0x04 返回語(yǔ)句 return?
* 0x05 關(guān)于?return 后面帶括號(hào)的寫法
0x06 深入淺出:Hello,World!
尾記:如何學(xué)習(xí)一門編程語(yǔ)言?
Ⅰ.?你好, 世界!(Hello World)
- 本章是首個(gè)章節(jié),將通過計(jì)算機(jī)最經(jīng)典的示例程序 ?Hello World 來(lái)展開我們的教程,考慮到 C 語(yǔ)言歷史大家應(yīng)該早已屢見不鮮,所以這里我們選擇介紹 Hello World 的歷史和由來(lái)。然后帶著大家創(chuàng)建項(xiàng)目并敲下這最經(jīng)典的代碼。
0x00 引入:HelloWorld 的由來(lái)
"所有的偉大,都源于一個(gè)勇敢的開始!"
?? 思考:什么是 Hello World ?它又是怎么來(lái)的?
Hello World 是一種常見的計(jì)算機(jī)程序,通常作為一個(gè)新編程語(yǔ)言或平臺(tái)的入門示例。
??它的起源可以追溯到 1974 年,由計(jì)算機(jī)科學(xué)家?布萊恩·柯林漢?創(chuàng)造。
沒錯(cuò),就是那個(gè)?80 歲還在咔咔咔寫代碼的巨佬,貝爾實(shí)驗(yàn)室的 神仙?!
??在?C 語(yǔ)言第一本教材《C程序設(shè)計(jì)語(yǔ)言》中,
他使用了 Hello World 作為一個(gè)簡(jiǎn)單的示例,來(lái)介紹 C 語(yǔ)言的基本語(yǔ)法。
他與?C 語(yǔ)言之父 ——?丹尼斯里奇?共同合作了撰寫了這本書,K&R?就是兩人名字的縮寫。
?
??同時(shí),他還是開發(fā) Unix 的主要貢獻(xiàn)者,Unix 就是由柯林漢命名的!
"全員惡人?什么惡人?全員神仙!"
?
??可以這么說,當(dāng)你把 Hello World 這幾個(gè)字成功打印到屏幕上時(shí),
你的內(nèi)心體驗(yàn)到的不僅僅是一種成功的喜悅,更重要的是,你正在親身經(jīng)歷一個(gè)跨越歷史的時(shí)刻!
" 編程生涯,由此開始!"
Hello World 究竟從何而來(lái)?
當(dāng) Forbes India 雜志采訪柯林漢時(shí),他本人對(duì)自己這段傳奇故事中一些記憶已經(jīng)有點(diǎn)兒模糊了。當(dāng)被問及為什么選擇 "Hello, World! "時(shí),他回答道:"我只記得,我好像看過一幅漫畫,講述一枚雞蛋和一只小雞的故事,在那副漫畫中,小雞說了一句 Hello World " ——
0x01 代碼編輯器的選用(推薦 VS2022)
??剛才我們了解了 Hello World 的故事,敲之前還需要做一些 "必要" 的準(zhǔn)備!正所謂:
" 工欲善其事,必先利其器!"
我們先來(lái)對(duì) "代碼編輯器" 做一個(gè)簡(jiǎn)單的了解,我們這里指的 "編輯器" 是 集成開發(fā)環(huán)境 (IDE)?。
集成開發(fā)環(huán)境(即 IDE)是一種軟件應(yīng)用程序,提供了一個(gè)集成的開發(fā)環(huán)境,包括代碼編輯器、編譯器、調(diào)試器和其他開發(fā)工具,用于簡(jiǎn)化和加速軟件開發(fā)的過程。IDE 通常用于軟件開發(fā),尤其是針對(duì)特定編程語(yǔ)言的開發(fā),例如 Java、Python、C++ 等。IDE 的主要優(yōu)點(diǎn)是提供了一個(gè)集成的工作流程,使得開發(fā)人員能夠更加高效地編寫、測(cè)試和調(diào)試代碼。IDE通常具有自動(dòng)代碼完成、語(yǔ)法高亮、代碼調(diào)試、版本控制等功能,可以大大提高開發(fā)效率和代碼質(zhì)量。常見的 IDE 包括 Visual Studio、Eclipse、和 IntelliJ IDEA 等。
??下面我先打開我的編輯器,我的代碼編輯器是:
?
??大人!時(shí)代變了!怎么還有人在用 VC6.0??。〔粫?huì)吧?
哈哈哈哈哈,怎么會(huì)!我用的可是支持腦機(jī)接口編程的?VS2077?:
?
開個(gè)玩笑,其實(shí)是 VS2022!本系列博客 Windows 系統(tǒng)下一律選用 Visual Studio 2022?。
我們也是非常推薦大家使用 VS2022 的,臣以為 VS2022 真乃 「宇宙最強(qiáng)編輯器」 也!
?
" 強(qiáng)烈建議安裝,用過都說好,下載鏈接我貼到下面了。"
?? VS 官網(wǎng):Visual Studio 2022 IDE - 適用于軟件開發(fā)人員的編程工具
?
學(xué)習(xí)階段我們使用免費(fèi)的 Community2022 版本 (社區(qū)版) 就完全夠用了,大家可以自行下載。
關(guān)于為什么選用 VS
關(guān)于 Visual Studio 的安裝,網(wǎng)上有很多很不錯(cuò)的教學(xué)視頻,簡(jiǎn)易大家跟著視頻去安裝。?安裝過程還是沒有什么難度的,喝杯咖啡的功夫就可以搞定了!和安裝 Java 環(huán)境比真的容易太多,Java 還要手動(dòng)配置環(huán)境,設(shè)置電腦環(huán)境變量等一系列雜七雜八的工作…… 而 Visual Studio?非常人性化地一條龍服務(wù),幾乎幫你包攬了這一切?。ㄟ€有個(gè)編輯器叫CLion,J家的開包即用的編輯器,但本專欄還是選用了?VS)
此外,微軟還有一個(gè)叫 Vscode 的神器,這個(gè)環(huán)境的準(zhǔn)備就復(fù)雜得多,我們也不簡(jiǎn)易新手一上來(lái)就用 Vscode(雖然我當(dāng)年系統(tǒng)性學(xué)C的時(shí)候就直接搗鼓了?Vscode 環(huán)境),安裝環(huán)境的過程是非常勸退人的,所以也不建議。值得一提的是,Vscode 屬于輕量化的編輯器,并不是?IDE!
當(dāng)然有些學(xué)??赡軙?huì)要求大家使用 Dev C++,那個(gè)也是個(gè)不錯(cuò)的編輯器,但是還是建議大家去使用?Visual Studio,因?yàn)楹罄m(xù)章節(jié)中會(huì)附帶一些基礎(chǔ)調(diào)試的教學(xué),是基于?Visual Studio 的,本專欄在 Window 下也都是采用?Visual Studio 來(lái)給大家做教學(xué)演示的,所以為了更好地貼合本專欄進(jìn)行系統(tǒng)性地學(xué)習(xí),盡量選用?Visual Studio!?(ps:再多說一句:如果你學(xué)校推薦的編譯器是 VC6.0 + 和用到包漿的 PPT,額…… )
* 當(dāng)然,為了更方便大家系統(tǒng)性地學(xué)習(xí),后期我會(huì)更新一篇 VS 的安裝教程的。
?下面,我們將認(rèn)為讀者已經(jīng)安裝好了 VS2022,并帶著大家手把手敲出 Hello,World!
?? 注意:所以為了更好地貼合本專欄進(jìn)行系統(tǒng)性地學(xué)習(xí),盡量選用?Visual Studio?!
0x02 創(chuàng)建新項(xiàng)目
①?首先,找到桌面快捷方式,雙擊打開?Visual Studio 2022:
?(簡(jiǎn)約而不失優(yōu)雅的加載頁(yè)面...)
②?進(jìn)入下方頁(yè)面后,點(diǎn)擊右下角的?創(chuàng)建新項(xiàng)目 (N) :
?
③?然后點(diǎn)擊?空項(xiàng)目:
?
④ 隨后到了配置新項(xiàng)目,這里創(chuàng)建項(xiàng)目名稱,演示階段名字自行取,之后點(diǎn)擊 創(chuàng)建 (C)?:
?
⑤ 創(chuàng)建完新項(xiàng)目后,我們找到 解決方案資源管理器,右鍵 "源文件",點(diǎn)擊 "添加新建項(xiàng)" :
?
?名稱我們就取為 test.c,然后點(diǎn) 添加(A),我們的工程就會(huì)在指定的位置很好地創(chuàng)建出來(lái):
?
??這里的 .c 文件 和 .h 以及 VS 默認(rèn)的 .cpp 后綴是什么?我們來(lái)簡(jiǎn)單介紹一下:
簡(jiǎn)而言之,.c?文件用于 C 語(yǔ)言編程,.cpp?文件用于 C++ 編程:
- .c 是 C?源代碼文件的擴(kuò)展名。
- .cpp 是 C++ 源代碼文件的擴(kuò)展名。
- .h 是頭文件 (header file) ,包含在 C 或 C++ 程序中,用于聲明函數(shù)、變量和數(shù)據(jù)結(jié)構(gòu)等,以便在程序的其他部分中使用。
(這些我們?cè)诤笃谥v解 編譯鏈接 時(shí)都會(huì)詳細(xì)講解,這里只需要 .c 是 C 語(yǔ)言源文件的后綴即可)
?創(chuàng)建完畢后,我們就可以在 test.c?下打代碼了:
?
??至此,我們要做的準(zhǔn)備工作基本就完成了,下面我們就要開始敲 Hello,World 了!
0x03?敲下這 “跨越歷史” 的 Hello World!
??如果你是一個(gè)初學(xué)者,我們很能理解你迫切的想要敲出 Hello,World 的心情!
我們現(xiàn)在就拉出本教程的第一個(gè)代碼 —— Hello,World,來(lái)讓大家 一睹芳容 !
?
" 手動(dòng)播放勁爆的新寶島 bgm,敲下這令人激動(dòng)的第一個(gè)代碼!"
?? 代碼演示:C 語(yǔ)言經(jīng)典示例程序 —— Hello,World
#include <stdio.h>
int main()
{
printf("Hello, World!\n");
return 0;
}
你可以手動(dòng)將代碼打到源文件中,也可以直接點(diǎn)擊右上角 "復(fù)制" 按鈕,將代碼粘貼到源文件中。
下面我們準(zhǔn)備運(yùn)行這段代碼,點(diǎn)擊頂部菜單欄 調(diào)試 (D) → 開始執(zhí)行(不調(diào)試)? :
?
??當(dāng)然你也可以使用快捷鍵運(yùn)行,輸入?Ctrl + F5?運(yùn)行代碼:
?
我們也推薦大家使用!因?yàn)樽鳛殒I盤俠,使用快捷鍵會(huì)很 cool !
運(yùn)行后就會(huì)彈出一個(gè)窗口 ——?Microsoft Visual Stidio?調(diào)試控制臺(tái):
?
我們可以看到,我們的 Hello, World! 在控制臺(tái)上出現(xiàn)了,這是因?yàn)槲覀冋{(diào)用了打印函數(shù) printf :
#include <stdio.h>
int main()
{
printf("Hello, World!\n"); ?? 打印 Hello,World
return 0;
}
(如果是零基礎(chǔ)讀者,看不明白這段代碼,這都沒有關(guān)系,我們會(huì)深入淺出地慢慢講解)
??只會(huì)敲 Hello World 的我瑟瑟發(fā)抖……
至此,我們的 Hello World 就敲完了,我們也成功的將這段話輸出到了我們的顯示器上。
?下面我們會(huì)慢慢地、深入淺出地講解剛才的 Hello World 代碼:
#include <stdio.h>
int main()
{
printf("Hello, World!\n");
return 0;
}
??等講解完這些必要的知識(shí)點(diǎn)后,我們?cè)倩貋?lái)看這段代碼,就會(huì)非常地清楚 ~
Ⅱ. 頭文件(Header)
- 圍繞著 HelloWorld 繼續(xù)推進(jìn),理解第一行代碼我們需要先知道什么是頭文件,以及我們引入的 stdio 庫(kù),這里我們會(huì)詳細(xì)的講解各種頭文件的知識(shí)點(diǎn),包含了頭文件保護(hù)、聲明定義分離、#include 展開等內(nèi)容。
0x00?引入:什么是頭文件?
?下面是 HelloWorld 示例程序的第一行代碼:
#include <stdio.h> // 引入頭文件 stdio 庫(kù)
* 注:兩個(gè)斜杠 (?//?) 后面的內(nèi)容屬于「注釋」,我們會(huì)在下一章進(jìn)行講解!
這一行代碼的意思是 "引入頭文件 stdio.h 庫(kù)",我們先來(lái)簡(jiǎn)單介紹一下什么是 頭文件?(header)。
?
?? 概念:頭文件,?[英] header,[繁] 標(biāo)頭檔
后綴為 .h 的文件被稱為 "頭文件" ,因?yàn)轭^文件的英文是 header,所以縮寫為 .h 。
包含了 C 語(yǔ)言函數(shù)聲明和宏定義,被多個(gè)源文件中引用共享。
頭文件分為兩種,一種是 "程序員自己編寫的頭文件",另一種是 "編譯器自帶的頭文件" 。
關(guān)于頭文件的一些細(xì)節(jié)
在 C 語(yǔ)言中,頭文件是一個(gè)文件。頭文件通常是源代碼的形式,由編譯器在處理另一個(gè)源文件時(shí)自動(dòng)包含進(jìn)來(lái)。程序員通過編譯器指令將頭文件包含進(jìn)其他源文件的開始。
一個(gè)頭文件包含類、子程序、變量和其他標(biāo)識(shí)符的前置聲明,需要在一個(gè)以上源文件中生命的標(biāo)識(shí)符可以放在一個(gè)頭文件中,并在需要的地方包含該頭文件。頭文件作為一種包含功能函數(shù)、數(shù)據(jù)接口聲明的載體文件,主要用于保存程序的聲明。
早期的編程語(yǔ)言如 Basic、Fortran 并沒有頭文件的概念。
我們使用 #include 來(lái)引用頭文件,這是一個(gè) C 預(yù)處理指令,對(duì)于預(yù)處理的知識(shí)我們后期會(huì)講解。
#include 引入頭文件
我們的 Hello World 示例代碼中,就是使用了 #include 來(lái)引用頭文件的,引用的是 stdio.h?。
??這是什么?下面,我們就來(lái)介紹一下這個(gè) stdio.h。
0x01?標(biāo)準(zhǔn)輸入輸出庫(kù) stdio
stdio.h 是編譯器自帶的頭文件,其中 .h 我們剛才說過,是擴(kuò)展名,被稱之為頭文件。
stdio 全稱 standard input & output,即標(biāo)準(zhǔn)輸入輸出,我們稱之為 C 標(biāo)準(zhǔn)庫(kù),直接看圖記憶:
?
因?yàn)槲覀兊?HelloWorld 示例程序需要用到 printf 這個(gè)函數(shù),所以我們需要引入 stdio.h !
??之所以叫做 stdio 是因?yàn)?standard input & output,而它表示了這庫(kù)中最經(jīng)典的兩個(gè)函數(shù):
- printf() :標(biāo)準(zhǔn)輸入函數(shù)(input)
- scanf() :標(biāo)準(zhǔn)輸出函數(shù)(output)
(對(duì)于這兩個(gè)函數(shù),我們后續(xù)會(huì)詳細(xì)講解)
?? 了解:stdio.h 的文件說明
/*
*stdio.h
*This file has no copyright assigned and is placed in the Public Domain.
*This file is a part of the mingw-runtime package.
*Now arranty is given;refer to the file DISCLAIMER within the package.
*
*Definitions of types and proto types of functions for
*standard inputand output.
*
*NOTE:The file manipulation functions provided by Microsoft seem to
*work with either slash(/) or backslash(\) as the directory separator.
*
*/
0x02 引入頭文件的方式
??我們前面講了,我們使用 #include 來(lái)引用頭文件,這個(gè) # 是預(yù)處理指令。
預(yù)處理指令 # 可用于宏定義、文件包含 和 條件編譯。
剛才我們介紹了 stdio.h 庫(kù),現(xiàn)在我們?cè)賮?lái)觀察?HelloWorld 示例程序的第一行代碼:
#include <stdio.h>
??我們可以看到 #include 后面使用了兩個(gè)尖括號(hào) < > 包住了 stdio.h。
尖括號(hào)中引入的頭文件基本都是 C 語(yǔ)言中各種各樣的?庫(kù)函數(shù),剛才學(xué)習(xí)的 stdio 就是其中之一。
這行代碼中,我們通過 #include <> 去引入了標(biāo)準(zhǔn)輸入輸出庫(kù) stdio。
頭文件的引入除了尖括號(hào) < > ,還有雙引號(hào) " " 形式的引入:
?
?? 兩種引用方式的區(qū)別:頭文件用 < > 引入和用 " " 引入的區(qū)別
?首先,< > 和 " " 包含頭文件的本質(zhì)區(qū)別是查找策略區(qū)別。
①?尖括號(hào) < > 的查找策略:直接去標(biāo)準(zhǔn)路徑下去查找,用來(lái)引用標(biāo)準(zhǔn)庫(kù)的頭文件。
#include <文件> // 編譯器將從標(biāo)準(zhǔn)庫(kù)目錄開始搜索
② 雙引號(hào) " " 的查找策略:先在源文件所在的目錄下查找。如果該頭文件未找到,則在庫(kù)函數(shù)的頭文件目錄下查找。如果仍然找不到,則提示編譯錯(cuò)誤。用來(lái)引用非標(biāo)準(zhǔn)庫(kù)的頭文件。
#include "文件" // 編譯器將從用戶的工作目錄開始搜索
? 思考:那可不可以用雙引號(hào) " " 包含庫(kù)文件?
#include "stdio.h"
??當(dāng)然,庫(kù)文件也是可以使用 " " 包含的,但是不建議庫(kù)文件用 " " 形式引入!
#include <stdio.h> 是庫(kù)文件,你用引號(hào)形式引入 #include "stdio.h" 也是可以的,
但是這樣查找的效率就會(huì)大打折扣,因?yàn)闀?huì)先從源文件所在目錄下去找一遍,然后再去找。
我們既然明確知道自己是要使用庫(kù)文件了,我們自然就直接使用尖括號(hào)才是正常的。
?而且這樣也不容易區(qū)分是庫(kù)文件還是本地文件。
(看到尖括號(hào)引入就知道是庫(kù)文件,看到引號(hào)引入的就知道是本地文件,還是很香的)
① Linux環(huán)境 標(biāo)準(zhǔn)頭文件的路徑:
/usr/include
② VS環(huán)境 標(biāo)準(zhǔn)頭文件的路徑:
C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include
* 0x03 養(yǎng)成聲明與定義分離的習(xí)慣
?要做到聲明與定義相分離,頭文件中應(yīng)當(dāng)只存放聲明,而不存放定義。
為了養(yǎng)成模塊化編程的好習(xí)慣,我們盡量把代碼分開寫,聲明放到頭文件中,定義放到源文件中。
在 VS 下,在解決方案資源管理器中:
- 在 "頭文件" 文件夾中右鍵取文件名以 .h 結(jié)尾即可創(chuàng)建頭文件。
- 在 "源文件" 文件夾中右鍵取文件名以 .c 結(jié)尾即可創(chuàng)建源文件。
C++ 語(yǔ)法中,類的成員函數(shù)可以在聲明的同時(shí)被定義,并自動(dòng)成為內(nèi)聯(lián)函數(shù)。這帶來(lái)了書寫上的便利,但也造成了風(fēng)格的不一致,弊大于利。建議將成員函數(shù)的定義與聲明分離開來(lái)。
* 0x04?頭文件保護(hù)(防止頭文件重復(fù)包含)
"使用頭文件保護(hù)技術(shù),能夠有效提升代碼的一致性和可維護(hù)性……"
當(dāng)需要引入多個(gè)頭文件時(shí),頭文件引用條目達(dá)到十幾條以上時(shí),難免會(huì)出現(xiàn)重復(fù)引用的失誤。
??比如下面這個(gè)代碼,仔細(xì)觀察你會(huì)發(fā)現(xiàn)不小心引入了兩次 stdio:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <cstdio>
#include <memory>
#include <cstdlib>
#include <random>
#include <cstdio>
#include <stdio.h> ← 引入兩次了
#include <utility>
#include <cstring>
#include <vector>
#include <functional>
...
這就是典型的重復(fù)引用錯(cuò)誤,此時(shí)編譯器會(huì)處理兩次頭文件的內(nèi)容,從而產(chǎn)生錯(cuò)誤。
為了防止這種情況的發(fā)生,經(jīng)典的做法是將文件的整個(gè)內(nèi)容都放到條件編譯語(yǔ)句中。
具體做法是使用預(yù)處理指令 #ifndef、#define 和 #endif 來(lái)避免重復(fù)包含:
#ifndef STDIO_H
#define STDIO_H
// stdio.h 的內(nèi)容
#endif
這里我們使用了包裝器 #ifndef,當(dāng)再次引用頭文件時(shí)條件為假,因?yàn)?STDIO_H 已經(jīng)定義。
如此一來(lái),預(yù)處理器會(huì)檢查 STDIO_H 是否以及定義,如果沒有定義就會(huì)進(jìn)入 #ifndef 塊內(nèi);
如果已經(jīng)定義,此時(shí)預(yù)處理器會(huì)跳過文件的整個(gè)類容,編譯器就會(huì)將其忽略不計(jì)。
??該技術(shù)我們稱之為 頭文件保護(hù) (header guards) ,可以有效確保頭文件只被包含一次。
? 如果嫌麻煩,還有一種非常簡(jiǎn)單的方法:
#pragma once // 讓頭文件即使被包含多次,也只包含一份
(具體的講解我們會(huì)在后面的預(yù)處理章節(jié)進(jìn)行更加深刻細(xì)致的講解)
因此,為了預(yù)防頭文件被重復(fù)引入,建議使用 ifndef / define /?endif 結(jié)構(gòu)產(chǎn)生與處理塊。
* 0x05 條件引用技術(shù)
?條件引用是一種根據(jù)特定條件來(lái)選擇性包含或排除代碼的技術(shù)。
如果需要從多個(gè)不同的頭文件選擇一個(gè)引用到程序中,此時(shí)就需要用?"有條件引用" 來(lái)達(dá)成目的。
舉個(gè)例子,現(xiàn)在我們需要指定不同的操作系統(tǒng)上使用的配置參數(shù):
#if SYSTEM_1
# include "system_1.h"
#elif SYSTEM_2
# include "system_2.h"
#elif SYSTEM_3
...
#endif
當(dāng)頭文件較多時(shí),此這么做顯然不妥,我們可以預(yù)處理器使用宏來(lái)定義頭文件的名稱。
這種方法就是 有條件引用,不用頭文件的名稱作為 #include 的直接參數(shù),
而只需要用宏名稱代替即可,因?yàn)?SYS_H 會(huì)拓展,預(yù)處理器會(huì)查找 sys_1.h?:
#define SYS_H "sys_1.h"
...
#include SYS_H
(SYSTEM_H 可通過 -D 選項(xiàng)被 Makefile 定義)
* 0x06?實(shí)踐錯(cuò)誤記錄:#include 展開的問題
??#include 的展開是有特點(diǎn)的,下面我將通過一個(gè)實(shí)踐中碰到的例子,來(lái)講解展開的特點(diǎn)。
?? 舉個(gè)例子:下面我們手動(dòng)定義了一顆二叉樹(Queue.c)
?
#define _CRT_SECURE_NO_WARNINGS 1
#include "Queue.h"
typedef char BTDataType;
typedef struct BinaryTreeNode {
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
BTDataType data;
} BTNode;
//#include "Queue.h" 解決方案?
/* 創(chuàng)建新節(jié)點(diǎn) */
BTNode* BuyNode(BTDataType x) {
BTNode* new_node = (BTNode*)malloc(sizeof(BTNode));
if (new_node == NULL) {
printf("malloc failed!\n");
exit(-1);
}
new_node->data = x;
new_node->left = new_node->right = NULL;
return new_node;
}
/* 手動(dòng)創(chuàng)建二叉樹 */
BTNode* CreateBinaryTree() {
BTNode* nodeA = BuyNode('A');
BTNode* nodeB = BuyNode('B');
BTNode* nodeC = BuyNode('C');
BTNode* nodeD = BuyNode('D');
BTNode* nodeE = BuyNode('E');
BTNode* nodeF = BuyNode('F');
nodeA->left = nodeB;
nodeA->right = nodeC;
nodeB->left = nodeD;
nodeC->left = nodeE;
nodeC->right = nodeF;
return nodeA;
}
我們可以看到,我們 #include 引入了 Queue.h 文件,由于是我們的數(shù)據(jù)類型是 BTNode,
?所以我們需要修改一下 Queue.h 中的 QueueDataType:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
typedef BTNode* QueueDataType;
typedef struct QueueNode {
struct QueueNode* next;
QueueDataType data;
} QueueNode;
typedef struct Queue {
QueueNode* pHead;
QueueNode* pTail;
} Queue;
void QueueInit(Queue* pQ); //隊(duì)列初始化
void QueueDestroy(Queue* pQ); //銷毀隊(duì)列
bool QueueIfEmpty(Queue* pQ); //判斷隊(duì)列是否為空
void QueuePush(Queue* pQ, QueueDataType x); //入隊(duì)
void QueuePop(Queue* pQ); //出隊(duì)
QueueDataType QueueFront(Queue* pQ); //返回隊(duì)頭數(shù)據(jù)
QueueDataType QueueBack(Queue* pQ); //返回隊(duì)尾數(shù)據(jù)
int QueueSize(Queue* pQ); //求隊(duì)列大小
?此時(shí)令人瞠目結(jié)舌的問題就出現(xiàn)了,我們運(yùn)行代碼就會(huì)產(chǎn)生報(bào)錯(cuò):
?
?我們來(lái)冷靜分析一下這個(gè)報(bào)錯(cuò),分析完你會(huì)發(fā)現(xiàn)分析了個(gè)寂寞……
報(bào)錯(cuò)說又缺少 " { " 又缺少 " ) "?的,這明顯是胡說八道。
我們的編譯器在這里就顯得沒有這么智能了,報(bào)的錯(cuò)都開始胡言亂語(yǔ)了。
? 思考:這里產(chǎn)生問題的原因是什么呢?(提示:想想編譯器的原則)
?? 編譯器原則:編譯器認(rèn)識(shí) int,是因?yàn)?int 是一個(gè)內(nèi)置類型。但是 BTNode* 編譯器并不認(rèn)識(shí),
就需要 "往上面" 去找這個(gè)類型。這里顯然往上找,是找不到它的定義的,所以編譯器會(huì)報(bào)錯(cuò)。
?
如果你要用這個(gè)類型,你就需要先定義這個(gè)類型。
test.c 文件中 #include "Queue.h" ,相當(dāng)于把這里的代碼拷貝過去了。
此時(shí),由于 BTNode* 會(huì)在上面展開,導(dǎo)致找不到 BTNode*? 。
? 思考:我把 #include 移到 定義類型的代碼 的后面,可以解決問題嗎?
可以!遺憾的是只能解決這里 typedef BTNode* 的問題,還有 Queue.c 里的問題……
??那我們?cè)撛趺醋觯拍軓氐捉鉀Q呢??
?? 解決方案:使用前置聲明。這樣就不會(huì)帶來(lái)問題了,滿足了先聲明后使用。
?
?? 代碼演示:使用 "前置聲明" 修改后的 Queue.h
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
// 前置聲明
struct BinaryTreeNode;
typedef struct BinaryTreeNode* QueueDataType;
typedef struct QueueNode {
struct QueueNode* next;
QueueDataType data;
} QueueNode;
typedef struct Queue {
QueueNode* pHead;
QueueNode* pTail;
} Queue;
void QueueInit(Queue* pQ); //隊(duì)列初始化
void QueueDestroy(Queue* pQ); //銷毀隊(duì)列
bool QueueIfEmpty(Queue* pQ); //判斷隊(duì)列是否為空
void QueuePush(Queue* pQ, QueueDataType x); //入隊(duì)
void QueuePop(Queue* pQ); //出隊(duì)
QueueDataType QueueFront(Queue* pQ); //返回隊(duì)頭數(shù)據(jù)
QueueDataType QueueBack(Queue* pQ); //返回隊(duì)尾數(shù)據(jù)
int QueueSize(Queue* pQ); //求隊(duì)列大小
此時(shí)問題就解決了,頭文件展開后不會(huì)引發(fā)報(bào)錯(cuò)問題。
* 0x07?技巧:學(xué)會(huì)定義 _GLOBAL_H 來(lái)管理頭文件
"有效避免頭文件過多,亂七八糟,不方便管理的問題。"
?如果一個(gè)工程中有大量的 .h 文件 和 .c 文件,此時(shí)難免會(huì)很亂。
此時(shí)我們可以選擇用一個(gè) global.h 的頭文件來(lái)把所有?.h 文件 "打包" 起來(lái),
在除 global.h 文件外的頭文件中包含此文件,這樣我們就可以有條不紊地管理好所有的頭文件了。
#ifndef _GLOBAL_H
#define _GLOBAL_H
#include <cstdio>
#include <memory>
#include <cstdlib>
#include <random>
#include <cstdio>
#include <utility>
#include <cstring>
#include <vector>
#include <iostream>
#include <functional>
...
* 0x08 整理:stdio 定義的庫(kù)變量、庫(kù)宏和庫(kù)函數(shù)
?? 專欄閱讀貼士
當(dāng)標(biāo)題前出現(xiàn) *(星號(hào))時(shí),則說明該部分知識(shí)點(diǎn)是面向?qū)?C 語(yǔ)言已經(jīng)有一定基礎(chǔ)的讀者閱讀的,講解時(shí)難免會(huì)穿插不屬于本章范圍的內(nèi)容。如果你是一名初學(xué)者,可以選擇跳過。
對(duì) stdio 有過一些了解的讀者可能會(huì)知道 stdio.h 定義了 3 個(gè)變量類型,以及一些宏和各種函數(shù)。
對(duì)于庫(kù)中的變量、宏和函數(shù),我們一般稱之為 庫(kù)變量、庫(kù)宏?和 庫(kù)函數(shù)。
① 庫(kù)變量 (Library Variable):
頭文件在 stdio.h 中定義了 3 個(gè)變量類型,分別是?FILE,size_t?和 fpos_t 類型。
FILE
size_t
fpos_t
- FILE 類型:存儲(chǔ)文件流信息的對(duì)象類型(文件章節(jié)我們會(huì)詳細(xì)講解)。
- size_t 類型:無(wú)符號(hào)整數(shù)類型,為關(guān)鍵字?sizeof 的返回結(jié)果。
- fpos_t 類型:存儲(chǔ)文件中任何位置的對(duì)象類型。
②?庫(kù)宏?(Libary Micro):以下是 stdio.h 中定義的宏,一共有 16 種,我們先簡(jiǎn)單介紹一下。
1. NULL
NULL:空指針常量的值。
2. BUFSIZ
BUFSIZ:該宏為整數(shù),代表了 setbuf 函數(shù)使用的緩沖區(qū)大小。
3. EOF
EOF:文件結(jié)束標(biāo)志 EndOfFile,表示文件結(jié)束的負(fù)整數(shù),非常常見!后續(xù)我們學(xué)習(xí) getchar 函數(shù)時(shí)會(huì)詳細(xì)講解。
4. FOPEN_MAX
FOPEN_MAX:該宏為整數(shù),代表了系統(tǒng)可以同時(shí)打開的文件數(shù)量。
5. FILENAME_MAX
FILENAME_MAX:該宏為整數(shù),代表了字符數(shù)組可以存儲(chǔ)的文件名最大長(zhǎng)度,如果事先沒有任何限制,則該值為推薦的最大值。
6. L_tmpnam
L_tmpnam:該宏為整數(shù),代表了字符數(shù)組可以存儲(chǔ)的由 tmpnam 函數(shù)創(chuàng)建的臨時(shí)文件名的最大長(zhǎng)度。
7. _IOFBF
8. _IOLBF
9. _IONBF
拓展了帶有特定值的整型常量表達(dá)式,適用于 setvbuf 的第三個(gè)參數(shù)。
10. SEEK_CUR
11. SEEK_END
12. SEEK_SET
在 fseek 函數(shù)中使用,用于在一個(gè)文件中定位不同的位置。
13. TMP_MAX
tmpnam 函數(shù)可生成的獨(dú)特文件名的最大數(shù)量。
14. stderr
15. stdin
16. stdout
FILE 類型的指針,標(biāo)準(zhǔn)錯(cuò)誤、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)輸出流。
③ 庫(kù)函數(shù) (Libary Funciton):共有 42 種函數(shù),我們會(huì)在后續(xù)章節(jié)將一些常用的函數(shù)進(jìn)行介紹。
Ⅲ.? main 函數(shù)(Main Function)
- 介紹完頭文件后,下面我們將講解 C 程序中最為重要的 main 函數(shù),掌握 main 函數(shù)的特點(diǎn)與特性,探討 main 函數(shù)的各種寫法形式,并對(duì)這些寫法進(jìn)行說明講解。
0x00 引入:繼續(xù)觀察 HelloWorld 示例
??剛才我們講解了 #include <stdio.h>,下面我們來(lái)看第三行代碼,int main()?
#include <stdio.h>
int main()
{
printf("Hello, World!\n");
return 0;
}
首先,int 表示整型,這里表示 main 函數(shù)的返回值為整型。(下一章我們會(huì)講解數(shù)據(jù)類型)
*?[注]?int 是英文單詞?integer 的縮寫,意思是 "整數(shù)"。
而 int?后面跟著的 main 就表示了 main 函數(shù),我們先了解下什么是 函數(shù) (function) :
這里的函數(shù)可不是數(shù)學(xué)中的函數(shù),在 C 語(yǔ)言中,函數(shù)是一種可重復(fù)使用的代碼塊,用于執(zhí)行特定的任務(wù)。它是程序的基本組成部分,用于模塊化和組織代碼,使程序更易于理解、調(diào)試和維護(hù)。
0x01 什么是 main 函數(shù)?
?? 概念:main 函數(shù),又稱主函數(shù),每個(gè) C 程序都必須有一個(gè) main 函數(shù)。
#include <stdio.h>
int main() ?? 程序從這里執(zhí)行
{
return 0;
}
主函數(shù)是程序執(zhí)行的起點(diǎn),你的程序會(huì)從這里開始執(zhí)行,每次執(zhí)行程序都從 main 函數(shù)開始。
一個(gè)工程中可以有多個(gè) .c 文件,但多個(gè) .c 文件中只能有一個(gè) main 函數(shù),main 函數(shù)具有唯一性。
"一個(gè)程序不能沒有主函數(shù),就像山東不能沒有曹縣!"
?
?? 鐵律:main函數(shù)是程序的入口,主函數(shù)有且僅有一個(gè)!
一個(gè) C 語(yǔ)言程序的執(zhí)行,從 main 函數(shù)開始,到 main 函數(shù)結(jié)束。 如何理解呢?
你可以把 main 函數(shù)想象成入口,一切都從這里開始執(zhí)行,最后肯定也是要從這出去的。
?main 函數(shù)的返回類型取決于 main 標(biāo)識(shí)符前是什么數(shù)據(jù)類型,比如這里是 int:
int main()
{
return 0;
}
那么該主函數(shù)就存在一個(gè)整型類型的返回值,我們需要使用 return?去返回(我們后續(xù)再說)。
在我們的演示代碼 HelloWorld 中,我們可以看到,main 后面跟了一個(gè)括號(hào),
括號(hào)中定義了函數(shù)的參數(shù)類型,而我們演示代碼中,括號(hào)中是什么都沒有的。
??我們可以在括號(hào)里面加上一個(gè) void,就像這樣:
int main(void)
void 也是數(shù)據(jù)類型,表示無(wú)類型,演示代碼中為了演示簡(jiǎn)單化,我們把括號(hào)中的 void 省略掉了。
這里 在括號(hào)中加上 void 表示函數(shù)不接受任何參數(shù), 后面接上的花括號(hào),就是 函數(shù)體 了:
#include <stdio.h>
int main(void) // 主函數(shù)的返回類型是 int,主函數(shù) void 不接受任何參數(shù)
{
printf("Hello, World!\n");
return 0; // 由于主函數(shù)要求返回整型,這里 return 0
}
C 語(yǔ)言規(guī)定,在一個(gè)源程序中,main 函數(shù)的位置可以任意。
?下面我先列舉一些大家常見的主函數(shù)形式,然后我們?cè)俾u(píng)價(jià)。
① 約定俗成型(目前主流的寫法):
int main()
int main(void)
②?什么都不返回型(頗有爭(zhēng)議):
void main()
void main(void)
③ 帶參型(帶參形式,標(biāo)準(zhǔn)的 main 函數(shù),帶兩參數(shù)):
int main(int argc, char **argv)
int main (int argc, char *argv[], char *envp[])
④?超級(jí)簡(jiǎn)單型(存在于 C89 標(biāo)準(zhǔn)中的寫法,沒有返回值,沒有入?yún)ⅲ?/p>
main()
// Brian W. Kernighan 和 Dennis M. Ritchie 的經(jīng)典巨著
// The C programming Language 用的就是 main()。
⑤ 直接報(bào)錯(cuò)型(相信 90% 的程序員都犯過這個(gè)錯(cuò)誤):
int mian()
0x02?常規(guī)寫法: int main() 和 int main(void)
" 約定俗成型,這應(yīng)該是現(xiàn)在最普遍的寫法了。"
??我們平時(shí)寫 main 函數(shù)的時(shí) main 函數(shù)參數(shù)一般都不寫,即括號(hào)里什么都不填。
或者填上一個(gè) void 表示主函數(shù)不接受任何參數(shù):
int main()
int main(void)
?? 注意:值得注意的是,int main() 和 int main(void) 這兩種寫法是存在區(qū)別的!
- int main():參數(shù)列表沒有被 void 限制死,可以傳入?yún)?shù),程序需要返回值。
- int main(void):不能傳入?yún)?shù),如果輸入?yún)?shù)就會(huì)報(bào)錯(cuò),程序必須要有返回值。
這兩種寫法中,更加建議加 void 的寫法,因?yàn)槭?C89/C99/C11 標(biāo)準(zhǔn)文檔中提供的寫法之一:
int main(void) { /* ... */ }
* 0x03?帶參寫法:int main(int argc, char *argv[])
" VS下輸入 main 然后回車后就能自動(dòng)補(bǔ)全的標(biāo)準(zhǔn)寫法 "
?該寫法也是? C89/C99/C11 標(biāo)準(zhǔn)文檔中提供的標(biāo)準(zhǔn)寫法,也是很常見的一種寫法:
int main(int argc, char *argv[]) { /* ... */ }
參數(shù)列表中,argc 是一個(gè)整數(shù)參數(shù),表示傳遞給程序的命令行參數(shù)的數(shù)量。
argv?是一個(gè)指向字符指針數(shù)組的指針,用于存儲(chǔ)命令行參數(shù)的字符串。
-
argv
[
0
]
通常是程序的名稱或路徑。 -
argv
[
1
]?
到?argv
[
argc
-
1
]?
是傳遞給程序的其他命令行參數(shù)。
?? 代碼演示:int main(int argc, char *argv[])?
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("命令行參數(shù)的數(shù)量:%d\n", argc);
printf("第一個(gè)參數(shù)(程序名稱):%s\n", argv[0]);
for (int i = 1; i < argc; i++)
{
printf("參數(shù) %d: %s\n", i, argv[i]);
}
return 0;
}
* 0x04?頗有爭(zhēng)議的寫法:void main()
??首先拋出一個(gè)事實(shí) —— C/C++ 中從來(lái)沒有定義過 void main()
C++ 之父本賈尼在他的主頁(yè)上的 FAQ 中明確地寫著 ——
The definition void main() {/*...*/} is not and never has been C++, nor has it even been C.
(void main?從來(lái)就不存在于 C++ 或者 C)
void main 這種寫法究竟規(guī)不規(guī)范?這個(gè)廣為大家所知的?void main 究竟是怎么回事?
void main()
void main(void)
?光從語(yǔ)法角度上來(lái)看,C 語(yǔ)言里是可以編譯運(yùn)行 void main 的。
因?yàn)?C 中的函數(shù)只關(guān)注函數(shù)名,而關(guān)注其返回類型,在找入口函數(shù)時(shí)僅僅在找 "main" 而已。
從匯編角度來(lái)看,返回值即函數(shù)返回時(shí)寄存器? 的值,因此 ?中總是有值的。
和變量一樣,不主動(dòng)賦值給的是隨機(jī)數(shù),因此?void main 即使返回類型是 void,也是有返回值的。
很多人說這種寫法都是 "譚C" 帶的,事實(shí)上并非如此,因?yàn)樵缭谏鲜兰o(jì)就已然出現(xiàn)了這種寫法。
"還有甩鍋給譚C的,更是無(wú)稽之談,難道微軟和 Borland 的工程師也是跟譚C學(xué)的?"
—— 電腦博物館站長(zhǎng)
???當(dāng)然,一些編譯器?void main 是可通過編譯的,比如 VC6.0?
有人說 void main 其實(shí)挺合理的,編寫單片機(jī)的話你 return 給值給誰(shuí)?
?此時(shí)出現(xiàn)了一個(gè)非常炸裂的回答:你可以不收,我不能不給。
哈哈哈哈,看來(lái)寫代碼也講人情世故!return 什么無(wú)所謂,我就是要個(gè)態(tài)度 ~?
最后,對(duì) void main 感興趣的朋友可以看此帖:
?? 鏈接:C 語(yǔ)言的「void main」是怎么一代代傳下來(lái)的? - 知乎
* 0x05?存在于 C89 的寫法:main()
?C89 標(biāo)準(zhǔn)中支持這種只丟一個(gè) main() 的寫法:
main()
C?語(yǔ)言中是調(diào)用者負(fù)責(zé)清棧任務(wù),因此 main 函數(shù)不寫返回參數(shù)依然是可以的。
你看到有人直接扔一個(gè)?main() 但是不報(bào)錯(cuò),事實(shí)上也只是編輯器支持而已。
* 0x06?錯(cuò)誤寫法:mian()
" 因?yàn)檎娴暮苡腥?,所以我不得不提一提?
?相信很多人都犯過這個(gè)錯(cuò)誤,不小心把 main 打成了?mian……
int mian(void)
相信很多程序員在職業(yè)生涯中都犯過這個(gè)錯(cuò)誤,看了半天沒看出問題到底出在哪里:
?
編譯器只會(huì)去找 main,你不小心寫成 mian 編譯器會(huì)說沒找到 main 函數(shù)的。
?? 總結(jié):我們只需老老實(shí)實(shí)地遵循 C 語(yǔ)言標(biāo)準(zhǔn)文檔的兩種寫法,就行了。
int main(void) { /* ... */ }
int main(int argc, char *argv[]) { /* ... */ }
??可以保障代碼的通用性和可移植性,沒有 bug,豈不美哉?
* 0x07?main 函數(shù)執(zhí)行前做的事
待更新……
* 0x08 可以調(diào)用 main 函數(shù)嗎?
?? 思考:用戶可不可以調(diào)用 main 函數(shù)?
這個(gè)問題乍一看會(huì)讓人覺得,不可以吧……main 函數(shù)怎么可以讓用戶調(diào)呢:?
?
很多人都沒有想過這個(gè)問題,但事實(shí)上是可以的,main 函數(shù)是可以遞歸的。
可以是可以,但是調(diào)用會(huì)一直輸出,直至棧溢出(觸發(fā) StackOverflow )。
VS 下觸發(fā)警告:warning C4717: “main”: 如遞歸所有控件路徑,函數(shù)將導(dǎo)致運(yùn)行時(shí)堆棧溢出。
?? 代碼演示:調(diào)用 main()
#include <stdio.h>
int main()
{
printf("檸檬葉子C\n");
main();
return 0;
}
?? 運(yùn)行結(jié)果如下:
?
Ⅳ. 深入淺出:Hello,World!
- 最后,我們講講?Hello World 示例程序中剩下來(lái)的部分知識(shí)點(diǎn),引出函數(shù)體的概念,簡(jiǎn)單介紹一下 printf 函數(shù),談?wù)撜Z(yǔ)句結(jié)束的標(biāo)志(分號(hào)),最后講解 return 語(yǔ)句。這些知識(shí)點(diǎn)講完后,我們串聯(lián)前面講的內(nèi)容,就能很清楚地理解 Hello World 了。
0x00 引入:再看 Hello,World!
" 通過這小小的 Hello,World 來(lái)展開我們的教學(xué),豈不美哉?"
#include <stdio.h>
int main(void)
{
printf("Hello, World!\n");
return 0;
}
??我們剛才講解了頭文件和 main 函數(shù),
現(xiàn)在大家應(yīng)該能理解 #include <stdio.h> 和 int main() 的含義了。
下面我們來(lái)細(xì)說一下 main() 后面接的花括號(hào) { },所以我們先要介紹一下函數(shù)體的概念。
再介紹一下 printf 函數(shù),以及 return 語(yǔ)句,我們的 Hello World 示例程序就可以 "淺出" 了。
0x01 函數(shù)體的概念(花括號(hào)匹配原則)
?? 概念:定義一個(gè)函數(shù)功能的所有代碼組成的整體,稱為 函數(shù)體 (Function Body) 。
函數(shù)體是用花括號(hào)括起來(lái)的若干語(yǔ)句,他們完成了一個(gè)函數(shù)的具體功能。
(函數(shù)聲明與函數(shù)體組成函數(shù)定義,我們會(huì)在函數(shù)章節(jié)詳細(xì)講解)
?? 舉個(gè)例子:比如下面的 main 函數(shù)下面跟著一對(duì)花括號(hào),花括號(hào)內(nèi)的內(nèi)容就是函數(shù)體了。
#include <stdio.h>
int main(void)
{
printf("Hello, World!\n");
return 0;
}
?一對(duì)花括號(hào)?{ } 由 左花括號(hào) {? 和 右花括號(hào) } 組成,左花括號(hào)匹配右花括號(hào)。
比如這里的 main 函數(shù),其 函數(shù)體包括左花括號(hào)和與之匹配的右花括號(hào)之間的任何內(nèi)容 :
?
0x02?簡(jiǎn)單介紹一下 printf 函數(shù)
printf("Hello, World!\n");
printf 函數(shù),該函數(shù)是?stdio.h 中的庫(kù)函數(shù),因此在使用前需要使用 #include 引入 stdio.h 。
這里我們就簡(jiǎn)單介紹一下,printf 后面跟著 () ,括號(hào)中的內(nèi)容用雙引號(hào)括出,
雙引號(hào)內(nèi)的內(nèi)容可以輸出到 控制臺(tái),所以我們的 Hello,World 示例程序運(yùn)行后控制臺(tái)會(huì)顯示:
?
所以,你可以把它叫做 "打印函數(shù)",通過 printf 函數(shù)將 Hello,World 打印到了控制臺(tái)上:
printf("需要打印到控制臺(tái)的內(nèi)容");
? 這里的 \n 為什么沒有被打印出來(lái)?
因?yàn)?\n 是轉(zhuǎn)義字符,用于換行,關(guān)于轉(zhuǎn)義字符的知識(shí)我們會(huì)放到下一章進(jìn)行系統(tǒng)性講解。
0x03 分號(hào),語(yǔ)句結(jié)束的標(biāo)志!
???在我們?nèi)粘I钪校?"句號(hào)" 表示一段話的結(jié)束。
而在 C 語(yǔ)言中,語(yǔ)句的結(jié)束用 ;?表示,分號(hào)是語(yǔ)句結(jié)束的標(biāo)志,表示一條語(yǔ)句的結(jié)束。
?可以看到,我們的 printf() 函數(shù)后面就有一個(gè) 分號(hào)?;
printf("Hello,World\n"); ?? 加分號(hào)
return 0; ?? 加分號(hào)
當(dāng)一條語(yǔ)句結(jié)束時(shí),我們需要手動(dòng)給它加上分號(hào),表明該語(yǔ)句結(jié)束。
?
不加分號(hào)會(huì)報(bào)錯(cuò),人難免是會(huì)忘的,如果忘記加了編譯器會(huì)提示:
?
隨著敲代碼越來(lái)越熟練,忘記加分號(hào)的情況會(huì)越來(lái)越少的,就像你寫作文不會(huì)忘記加句號(hào)一樣。
遺漏分號(hào)會(huì)引發(fā) C2143 號(hào)報(bào)錯(cuò):error C2143: 語(yǔ)法錯(cuò)誤: 缺少“;”?
當(dāng)然了,也不能在不該加分號(hào)的地方加分號(hào)!
如果你在 main() 后面直接加分號(hào),那就表示語(yǔ)句直接結(jié)束了,后面的 { } 就會(huì)讓編譯器一臉懵……
?
所以,分號(hào)是初學(xué)者最容易犯錯(cuò)的地方,多用了會(huì)錯(cuò)少用了也會(huì)錯(cuò)。
但是不用擔(dān)心,在后續(xù)學(xué)習(xí)過程中慢慢就會(huì)掌握了。(比如 do...while 語(yǔ)句 while 結(jié)尾要加分號(hào))
0x04 返回語(yǔ)句 return?
我們前面說了,因?yàn)槲覀兊?main 函數(shù)的返回值類型定義的是 int,所以我們需要 return 一個(gè)值:
#include <stdio.h>
int main(void)
{
printf("Hello, World!\n");
return 0;
}
?? 概念:在C 語(yǔ)言中,return 用于表示函數(shù)的返回值,用來(lái)將函數(shù)的結(jié)果返回給調(diào)用它的程序。
return 返回值;
因此,這里我們 return 一個(gè) 0,表示程序正常退出。
* 0x05 關(guān)于?return 后面帶括號(hào)的寫法
值得一提的是,return 后面 "不帶括號(hào)" 和?"帶括號(hào)" 的寫法,都是正確的。
return 表達(dá)式; ? 常規(guī)寫法
return (表達(dá)式); ? 也是可以的
但是為了簡(jiǎn)單起見,一般我們不去寫,當(dāng)然寫了也不會(huì)錯(cuò),這取決于個(gè)人的編程習(xí)慣。
比如下面這兩行實(shí)際效果是一樣的:
return 10 + 20;
return (10 + 20);
我見過一些老的微軟大佬寫的代碼里,return 是這么寫的:
return( 0); // 括號(hào)直接頂著return
* ?? 舉個(gè)例子:庫(kù)函數(shù) src/strlen.c
size_t __cdecl strlen (
const char * str
)
{
const char *eos = str;
while( *eos++ ) ;
return( eos - str - 1 ); ?? 表達(dá)式
}
0x06 深入淺出:Hello,World!
現(xiàn)在,我們已經(jīng)解釋完所有 Hello,World 的細(xì)節(jié)了,并且把其中的知識(shí)點(diǎn)都講的差不多了。
??現(xiàn)在我們?cè)倩氐阶畛醯钠瘘c(diǎn),再次感受那個(gè)跨越歷史的時(shí)刻……
#include <stdio.h>
int main(void)
{
printf("Hello, World!\n");
return 0;
}
經(jīng)過本章的學(xué)習(xí)后,你也應(yīng)該會(huì)有一個(gè)大概的認(rèn)識(shí)了。
我們通過這 Hello World 來(lái)展開了我們的教學(xué)專欄,介紹了頭文件、主函數(shù)和一些基礎(chǔ)概念。
??這樣的開篇方式我們構(gòu)思了很久很久,希望能得到大家的喜愛!
C 語(yǔ)言之父 丹尼斯里奇?的曾言:
" C 詭異離奇,缺陷重重,卻獲得了巨大的成功。"
C 語(yǔ)言無(wú)疑是改變?nèi)祟悮v史的最偉大發(fā)明之一……
在計(jì)算機(jī)蓬勃發(fā)展的今天,我們是站在了巨人的肩膀上,得以讓我們看得更高,行的更遠(yuǎn)……
學(xué)習(xí) C 語(yǔ)言的過程是漫長(zhǎng)且枯燥的,一定要有一個(gè)非常強(qiáng)大的心理狀態(tài)。
?? 翁凱老師說:在計(jì)算機(jī)里頭沒有任何黑魔法!
學(xué)計(jì)算機(jī)一定要有一個(gè)非常強(qiáng)大的心理狀態(tài),計(jì)算機(jī)的所有東西都是人做出來(lái)的,別人能想的出來(lái),我也一定能想得出來(lái),在計(jì)算機(jī)的世界里沒有任何的黑魔法,所有的東西只不過是我現(xiàn)在不知道而已,總有一天我會(huì)把所有的細(xì)節(jié)、所有的內(nèi)部的東西全搞明白的。
尾記:如何學(xué)習(xí)一門編程語(yǔ)言?
?
我的編程母語(yǔ)是易語(yǔ)言(一款中文編程語(yǔ)言),我的編程啟蒙導(dǎo)師是覓風(fēng)。
覓風(fēng)老師在他的 《易語(yǔ)言視頻教程》中說過:
" 興趣是最好的老師。"
如果你對(duì)編程感興趣,那么這條路及時(shí)很艱難,你也會(huì)有無(wú)窮無(wú)盡的力量。
其次就是多動(dòng)手,代碼眼看千遍不如手敲一遍,多動(dòng)手去實(shí)操,畢竟:
" 紙上得來(lái)終覺淺,絕知此事要躬行。"
然后就是多看源碼,看看高手是怎么寫代碼的,大有裨益。
最后,遇到問題要深入了解,要具備對(duì)編程知識(shí)的求知欲,對(duì)問題要有鉆研精神。
?文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-616739.html
|
?? [ 筆者 ]? ?王亦優(yōu) | 雷向明
?? [ 更新 ]? ?2023.6.29(初稿)| 2023.7.1(審稿結(jié)束)
? [ 勘誤 ]?? /* 暫無(wú) */
?? [ 聲明 ]? ?由于作者水平有限,本文有錯(cuò)誤和不準(zhǔn)確之處在所難免,
本人也很想知道這些錯(cuò)誤,懇望讀者批評(píng)指正!
?? 參考文獻(xiàn):文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-616739.html
|
到了這里,關(guān)于【C語(yǔ)言趣味教程】(1) 深入淺出 HelloWorld:通過 HelloWorld 展開教學(xué) | 頭文件詳解 | main 函數(shù)詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!