摘要:C語言做爲編程的入門語言,學習者如何快速掌握其核心知識點,面對茫茫書海,彷佛有點迷茫。爲了讓各位快速地掌握C語言的知識內容,在這裏對相關的知識點進行了概括。
筆者有十餘年的C++開發經驗,相比而言,個人C經驗只有一兩年,C比較簡單,簡單到《The C Programming Language》(C程序設計語言)只有區區的200多頁,相比上千頁的C++大部頭,不得不說真的很人性化了。html
C語言精簡的語法集和標準庫,讓咱們能夠把精力集中到設計等真正重要的事情上來,而不是迷失在語法的海洋裏,這對於初學者尤爲重要。雖然C語言有抽象不足的缺點,但我更喜歡它的精巧,只須要花少許的時間,研究清楚它每個知識點,看任何C源碼就不會存在語法上的障礙,你們須要創建的知識共識足夠少,少便是多,少好於多。node
我教過6我的編程,教過HTML,教過JAVA,也教過C++。最近,我在教我小孩編程,他只有十歲,不少人建議我選擇Python,但我最終選擇了C語言,由於C語言簡單且強大,如今看來,好像是個不錯的選擇。linux
C是強類型語言,有short、long、int、char、float、double等build-in數據類型,類型是貫穿c語言整個課程的核心概念。程序員
struct、union、enum屬於c的構造類型,用於自定義類型,擴充類型系統。編程
變量用來保存數據,數據是操做的對象,變量的變字意味着它能夠在運行時被修改。數組
變量由類型名+變量名決定,定義變量須要爲變量分配內存,能夠在定義變量的同時作初始化。安全
int i;bash
float f1 = 0.5, f2= 0.8;網絡
const int i = 100;架構
const char* p = "hello world";
運行中恆定、不可變,編譯期即可肯定。
光有簡單變量顯然不夠,咱們須要數組,它模擬現實中相同類型的多個元素,這些對象是緊密相鄰的,經過數組名+位置索引便能訪問每一個元素。
二維、三維、高緯數組本質上仍是線性的,二維數組經過模擬行列給人平面的感受,實際存儲上仍是連續內存的方式。
數組是靜態的,在定義的時候,數組的長度就已經確認,運行中沒法伸縮,因此有時候咱們不得不爲應付擴充多分配一些空間。數組元素無論用多用少,它都在哪裏,有時候,咱們會用一個int n去界定數組實際被使用的元素個數。
函數封裝行爲,是模塊化的最小單元,函數使得邏輯複用變得可能。
C語言是過程式的,現實世界均可以封裝爲一個個過程(函數),經過過程串聯和編排模擬世界。
用C語言編程,行爲和數據是分離的。調用函數的時候,調用者經過參數向函數傳遞信息,函數經過返回值向調用者反饋結果。
函數最好是無反作用的,函數內應該儘可能避免修改全局變量或者靜態局部變量,更好的方式是經過參數傳遞進來,這樣的函數只是邏輯的盒子,它知足線程安全的要求。
有了變量和函數,就能夠編寫簡單的程序了。
build-in數據類型不足以描繪現實世界,或者用build-in類型描述不夠直接,結構體用來模擬複合類型,它賦予了咱們擴充類型系統的能力,咱們把類型組合到一塊兒構建更復雜的類型,而每一個被組合的成分就叫成員變量。
結構體內的成分,對象經過點(.)運算符,指針經過箭頭(->)訪問成員。
C語言的靈魂是指針,指針帶來彈性,指針的本質是地址。
須要區分指針和指針指向的對象,多個指針變量可指向同一個對象,一個指針不能同時指向多個對象。
指針相關的基本操做包括:賦值(修改指針指向),解引用(訪問指針指向的對象),取地址(&variable),指針支持加減運算。
由於指針變量要能覆蓋整個內存空間,因此指針變量的長度等於字長,32位系統下32位4字節,64位系統下64位8字節。
指針的含義遠比上述豐富,指針跟數組結合便有了指針數組(int* p[n])和數組指針(int (*p)[n]),指針跟函數結合便有了函數指針(ret_type (*pf)(param list)),指針跟const結合便有了const char*/char* const/const char* const,還有指向指針的指針(int **p)。
既能夠定義指向build-in數據類型的指針,也能夠定義指向struct的指針,void*表示通用(萬能)指針,它不能被解引用,也不能作指針算術運算。
c source code被編譯連接後,函數被轉換到可執行程序文件的text節,進程啓動的時候,會把text節的內容裝載到進程的代碼段,代碼段是c進程內存空間的一部分,因此任何c函數都會佔一塊內存空間,函數指針就是指向函數在代碼段的第一行彙編指令,函數調用就會跳轉到函數的第一個指令處執行。
函數指針常常被用來做爲回調(callback),c語言也會用包含函數指針成員的結構體模擬OOP,本質上是把C++編譯器作的事情,轉給程序員來作(C++爲包含虛函數的類構建虛函數表,爲包含虛函數的類對象附加虛函數表的指針)。
char*是一類特殊的指針,它被稱爲c風格字符串,由於它老是以‘\0’做爲結尾的標識,因此要標識一個字符串,有一個char*指針就夠了,字符串的長度被0隱式指出,跟字符串相關的STD C API大多以str打頭,好比strlen/strcpy/strcat/strcmp/strtok。
指針提供了c語言直接操做底層內存的能力,c程序區分棧內存和堆內存,棧內存是函數內的局部變量,它隨程序執行而動態伸縮,因此不要返回臨時變量的指針,棧內存容量有限(8/16M),因此咱們要避免在函數內建立過大的局部變量,要警戒遞歸爆棧。
堆內存也叫動態內存,它由一個叫動態內存配置器的標準庫組件管理,glibc的默認動態內存配置器叫ptmalloc,初始版本有性能問題,但後面用線程私有解決了競爭改善了性能。動態內存配置器是介於kernel與應用層的一個層次,從內核視角看ptmalloc是應用程序,從應用層來看ptmalloc又是系統庫。malloc跟free必須配對,這是程序員的職責,動態分配的內存丟失引用就會致使內存泄漏,指向已釋放的內存塊俗稱野(懸垂)指針。
從c source file到可執行程序須要通過預處理-編譯-彙編-連接多個階段,預處理階段作替換、消除和擴充,預處理語句以#打頭。
宏定義,#define,宏定義能夠用\作行鏈接,#用來產生字符串,##用來拼接,宏定義的時候要注意加()避免操做符優先級干擾,能夠用do while(0)來把定義做爲單獨語句,#undef是define的反操做。
#if #ifdef #ifndef #else #elif #endif用來條件編譯,爲了不頭文件重複包含,常常用#ifndef #define #endif。
#include用來作頭文件包含;#pragma用來作行爲控制;#error用來在編譯的時候輸出錯誤信息。
__FILE__、__LINE__、_DATE_、_TIME_、_STDC_等標準預約義宏能夠被用來作一些debug用途。
#typedef用來定義類型別名。好比typedef int money_t;money_t比int更有含義。
typedef也能用來爲結構體取別名,有時候會這樣寫:
typedef struct
{
int a;
int b;
} xyz_t;複製代碼
這樣在定義結構體變量的時候就能夠少敲幾下鍵盤。
typedef也能夠用來重定義函數指針類型,好比 typedef void (*PF) (int a, int b); PF是函數指針類型,而非函數指針變量。
枚舉能增長代碼可讀性和可維護性,枚舉本質上是int,只是爲了更有含義,將有限取值的幾個int值放在一組,好比定義性別:enum sex { male = 1, female };
能夠在定義的時候賦值,好比male=1,後面的值依次遞增1,若是不賦值則從0開始。
結構體和聯合體(共用體)的區別在於:結構體的各個成員會佔用不一樣的內存,互相之間沒有影響;而共用體的全部成員佔用同一段內存,修改一個成員會影響其他全部成員。
union u_data
{
int n;
char ch;
double f;
};複製代碼
其實本質上,聯合體就是對一塊內存的多種解釋,大小按最大的來。
struct SNField
{
unsigned char seq:7 ; // frame sequnce
unsigned char startbit:1 ; // indicate if it's starting frame 1 for yes. }; 複製代碼
節省空間,在面向底層的編碼,或者編寫處理網絡等程序時候用的比較多,注意這個語法特徵是跟機器架構相關的。
void simple_printf(const char* fmt, ...)
va_list、va_start、va_arg、va_end
GNU C擴展不是標準C,建議以符合標準C的方式編寫C代碼,但若是你閱讀linux kernel code,你會發現有不少有趣看不懂的語法,它來自GNU C擴展,它確實也帶來了一些便利性。
好比結構體成員能夠不按定義順序初始化:
struct test_t { int a; int b; };
struct test_t t1 = { .b = 1, .a = 2 };複製代碼
好比能夠經過指定索引初始化數組:
int a[5] = {[2] 5,[4] 9};
或 int a[5] = { [2] = 4, [4] = 9 };
至關於int a[5] = {0, 0, 4, 0, 9};
或者int a[100] = {[0 ... 9] = 1, [10 ... 98] = 2, 3};
好比0長度數組
struct foo
{
int i;
char a[0];
};
好比用變量做爲數組長度
void f(int n)
{
char a[n];
...
}複製代碼
好比case範圍,case 'A' ... 'Z' case 1 ... 10
好比表達式擴展({...}),好比三元運算符擴展...