C/C++編程筆記:C語言編程需要掌握的核心要點有哪些?建議收藏
C語言作為編程的入門語言,學習者如何快速掌握其核心知識點,面對茫茫書海,似乎有點迷茫。為了讓各位快速地掌握C語言的知識內(nèi)容,在這里對相關(guān)的知識點進行了歸納。

引言
筆者有十余年的C++開發(fā)經(jīng)驗,相比而言,我的C經(jīng)驗只有一兩年,C比較簡單,簡單到《The C Programming Language》(C程序設(shè)計語言)只有區(qū)區(qū)的200多頁,相比上千頁的C++大部頭,不得不說真的很人性化了。
C語言精簡的語法集和標準庫,讓我們可以把精力集中到設(shè)計等真正重要的事情上來,而不是迷失在語法的海洋里,這對于初學者尤其重要。雖然C語言有抽象不足的缺點,但我更喜歡它的精巧,只需要花少量的時間,研究清楚它每一個知識點,看任何C源碼就不會存在語法上的障礙,大家需要建立的知識共識足夠少,少即是多,少好于多。
我教過6個人編程,教過HTML,教過JAVA,也教過C++。最近,我在教我小孩編程,他只有十歲,很多人建議我選擇Python,但我最終選擇了C語言,因為C語言簡單且強大,現(xiàn)在看來,好像是個不錯的選擇。
類型
C是強類型語言,有short、long、int、char、float、double等build-in數(shù)據(jù)類型,類型是貫穿c語言整個課程的核心概念。
struct、union、enum屬于c的構(gòu)造類型,用于自定義類型,擴充類型系統(tǒng)。
變量
變量用來保存數(shù)據(jù),數(shù)據(jù)是操作的對象,變量的變字意味著它可以在運行時被修改。
變量由類型名+變量名決定,定義變量需要為變量分配內(nèi)存,可以在定義變量的同時做初始化。
int i;
float f1 = 0.5, f2= 0.8;
常量
const int i = 100;
const char* p = "hello world";
運行中恒定、不可變,編譯期便可確定。
數(shù)組
光有簡單變量顯然不夠,我們需要數(shù)組,它模擬現(xiàn)實中相同類型的多個元素,這些對象是緊密相鄰的,通過數(shù)組名+位置索引便能訪問每個元素。
二維、三維、高緯數(shù)組本質(zhì)上還是線性的,二維數(shù)組通過模擬行列給人平面的感覺,實際存儲上還是連續(xù)內(nèi)存的方式。
數(shù)組是靜態(tài)的,在定義的時候,數(shù)組的長度就已經(jīng)確認,運行中無法伸縮,所以有時候我們不得不為應(yīng)付擴充多分配一些空間。數(shù)組元素不管用多用少,它都在哪里,有時候,我們會用一個int n去界定數(shù)組實際被使用的元素個數(shù)。

函數(shù)
函數(shù)封裝行為,是模塊化的最小單元,函數(shù)使得邏輯復(fù)用變得可能。
C語言是過程式的,現(xiàn)實世界都可以封裝為一個個過程(函數(shù)),通過過程串聯(lián)和編排模擬世界。
用C語言編程,行為和數(shù)據(jù)是分離的。調(diào)用函數(shù)的時候,調(diào)用者通過參數(shù)向函數(shù)傳遞信息,函數(shù)通過返回值向調(diào)用者反饋結(jié)果。
函數(shù)最好是無副作用的,函數(shù)內(nèi)應(yīng)該盡量避免修改全局變量或者靜態(tài)局部變量,更好的方式是通過參數(shù)傳遞進來,這樣的函數(shù)只是邏輯的盒子,它滿足線程安全的要求。
有了變量和函數(shù),就可以編寫簡單的程序了。
控制語句
分支:if 、else、else if、switch case、?:
循環(huán):while、do while、for
break、continue、goto、default
結(jié)構(gòu)體
build-in數(shù)據(jù)類型不足以描繪現(xiàn)實世界,或者用build-in類型描述不夠直接,結(jié)構(gòu)體用來模擬復(fù)合類型,它賦予了我們擴充類型系統(tǒng)的能力,我們把類型組合到一起構(gòu)建更復(fù)雜的類型,而每個被組合的成分就叫成員變量。
結(jié)構(gòu)體內(nèi)的成分,對象通過點(.)運算符,指針通過箭頭(->)訪問成員。
指針
C語言的靈魂是指針,指針帶來彈性,指針的本質(zhì)是地址。
需要區(qū)分指針和指針指向的對象,多個指針變量可指向同一個對象,一個指針不能同時指向多個對象。
指針相關(guān)的基本操作包括:賦值(修改指針指向),解引用(訪問指針指向的對象),取地址(&variable),指針支持加減運算。
因為指針變量要能覆蓋整個內(nèi)存空間,所以指針變量的長度等于字長,32位系統(tǒng)下32位4字節(jié),64位系統(tǒng)下64位8字節(jié)。
指針的含義遠比上述豐富,指針跟數(shù)組結(jié)合便有了指針數(shù)組(int* p[n])和數(shù)組指針(int (*p)[n]),指針跟函數(shù)結(jié)合便有了函數(shù)指針(ret_type (*pf)(param list)),指針跟const結(jié)合便有了const char*/char* const/const char* const,還有指向指針的指針(int **p)。
既可以定義指向build-in數(shù)據(jù)類型的指針,也可以定義指向struct的指針,void*表示通用(萬能)指針,它不能被解引用,也不能做指針算術(shù)運算。

函數(shù)指針與回調(diào)(callback)
c source code被編譯鏈接后,函數(shù)被轉(zhuǎn)換到可執(zhí)行程序文件的text節(jié),進程啟動的時候,會把text節(jié)的內(nèi)容裝載到進程的代碼段,代碼段是c進程內(nèi)存空間的一部分,所以任何c函數(shù)都會占一塊內(nèi)存空間,函數(shù)指針就是指向函數(shù)在代碼段的第一行匯編指令,函數(shù)調(diào)用就會跳轉(zhuǎn)到函數(shù)的第一個指令處執(zhí)行。
函數(shù)指針經(jīng)常被用來作為回調(diào)(callback),c語言也會用包含函數(shù)指針成員的結(jié)構(gòu)體模擬OOP,本質(zhì)上是把C++編譯器做的事情,轉(zhuǎn)給程序員來做(C++為包含虛函數(shù)的類構(gòu)建虛函數(shù)表,為包含虛函數(shù)的類對象附加虛函數(shù)表的指針)。
字符串
char*是一類特殊的指針,它被稱為c風格字符串,因為它總是以‘\0’作為結(jié)尾的標識,所以要標識一個字符串,有一個char*指針就夠了,字符串的長度被0隱式指出,跟字符串相關(guān)的STD C API大多以str打頭,比如strlen/strcpy/strcat/strcmp/strtok。
內(nèi)存和內(nèi)存管理
指針提供了c語言直接操作底層內(nèi)存的能力,c程序區(qū)分棧內(nèi)存和堆內(nèi)存,棧內(nèi)存是函數(shù)內(nèi)的局部變量,它隨程序執(zhí)行而動態(tài)伸縮,所以不要返回臨時變量的指針,棧內(nèi)存容量有限(8/16M),所以我們要避免在函數(shù)內(nèi)創(chuàng)建過大的局部變量,要警惕遞歸爆棧。
堆內(nèi)存也叫動態(tài)內(nèi)存,它由一個叫動態(tài)內(nèi)存配置器的標準庫組件管理,glibc的默認動態(tài)內(nèi)存配置器叫ptmalloc,初始版本有性能問題,但后面用線程私有解決了競爭改善了性能。動態(tài)內(nèi)存配置器是介于kernel與應(yīng)用層的一個層次,從內(nèi)核視角看ptmalloc是應(yīng)用程序,從應(yīng)用層來看ptmalloc又是系統(tǒng)庫。malloc跟free必須配對,這是程序員的職責,動態(tài)分配的內(nèi)存丟失引用就會導(dǎo)致內(nèi)存泄漏,指向已釋放的內(nèi)存塊俗稱野(懸垂)指針。

預(yù)處理
從c source file到可執(zhí)行程序需要經(jīng)過預(yù)處理-編譯-匯編-鏈接多個階段,預(yù)處理階段做替換、消除和擴充,預(yù)處理語句以#打頭。
宏定義,#define,宏定義可以用\做行連接,#用來產(chǎn)生字符串,##用來拼接,宏定義的時候要注意加()避免操作符優(yōu)先級干擾,可以用do while(0)來把定義作為單獨語句,#undef是define的反操作。
#if #ifdef #ifndef #else #elif #endif用來條件編譯,為了避免頭文件重復(fù)包含,經(jīng)常用#ifndef #define #endif。
#include用來做頭文件包含;#pragma用來做行為控制;#error用來在編譯的時候輸出錯誤信息。
__FILE__、__LINE__、_DATE_、_TIME_、_STDC_等標準預(yù)定義宏可以被用來做一些debug用途。
#typedef用來定義類型別名。比如typedef int money_t;money_t比int更有含義。
typedef也能用來為結(jié)構(gòu)體取別名,有時候會這樣寫:
typedef struct{? int a;? int b;} xyz_t;復(fù)制代碼
這樣在定義結(jié)構(gòu)體變量的時候就可以少敲幾下鍵盤。
typedef也可以用來重定義函數(shù)指針類型,比如 typedef void (*PF) (int a, int b); PF是函數(shù)指針類型,而非函數(shù)指針變量。
枚舉
枚舉能增加代碼可讀性和可維護性,枚舉本質(zhì)上是int,只是為了更有含義,將有限取值的幾個int值放在一組,比如定義性別:enum sex { male = 1, female };
可以在定義的時候賦值,比如male=1,后面的值依次遞增1,如果不賦值則從0開始。
聯(lián)合體(union)
結(jié)構(gòu)體和聯(lián)合體(共用體)的區(qū)別在于:結(jié)構(gòu)體的各個成員會占用不同的內(nèi)存,互相之間沒有影響;而共用體的所有成員占用同一段內(nèi)存,修改一個成員會影響其余所有成員。

其實本質(zhì)上,聯(lián)合體就是對一塊內(nèi)存的多種解釋,大小按最大的來。
位域(bitfield)

節(jié)省空間,在面向底層的編碼,或者編寫處理網(wǎng)絡(luò)等程序時候用的比較多,注意這個語法特征是跟機器架構(gòu)相關(guān)的。
位操作
位與 &
位或 |
位取反 ~
位異或 ^
位移 << >>
static、extern、register、volatile、sizeof
(1)static修飾全局函數(shù),表示模塊內(nèi)(編譯單元)內(nèi)可用,不需要導(dǎo)出全局符號。
(2)static修飾局部變量,意味超越函數(shù)調(diào)用的生命周期,不存儲在棧上,只會被初始化1次。
(3)extern聲明外部變量。
(4)register,寄存器變量,建議編譯器將變量放在寄存器里。
(5)volatile,告訴編譯器不要做優(yōu)化,每次從內(nèi)存讀取,不做寄存器優(yōu)化。
(6)sizeof求大小,可以作用于變量,類型,表達式
可變參數(shù)
void simple_printf(const char* fmt, ...)
va_list、va_start、va_arg、va_end
C的高級感
泛型:linux內(nèi)核鏈表,通過offset和內(nèi)嵌node,寫出泛型鏈表。
OOP:通過定義帶函數(shù)指針成員變量的結(jié)構(gòu)體,在運行中,為結(jié)構(gòu)體對象設(shè)置上函數(shù)指針,模擬運行時綁定,實現(xiàn)類似OOP多態(tài)的感覺。
GNU C擴展
GNU C擴展不是標準C,建議以符合標準C的方式編寫C代碼,但如果你閱讀linux kernel code,你會發(fā)現(xiàn)有很多有趣看不懂的語法,它來自GNU C擴展,它確實也帶來了一些便利性。
比如結(jié)構(gòu)體成員可以不按定義順序初始化:

比如可以通過指定索引初始化數(shù)組:

比如case范圍,case 'A' ... 'Z' case 1 ... 10
比如表達式擴展({...}),比如三元運算符擴展...
原文鏈接:https://juejin.im/post/6847902225226956813#heading-13
希望對大家有幫助!

學習C/C++編程知識,提升C/C++編程能力,歡迎關(guān)注UP一起來成長!
另外,UP在主頁上傳了一些學習C/C++編程的視頻教程,有興趣或者正在學習的小伙伴一定要去看一看哦!會對你有幫助的~