語言只是一種工具,任何語言之間都是相通的,一通則百通,關鍵是要理解語言背後的思想,理解其思想,任何語言,拿來用就好了。語言沒有好壞之分,任何語言既然存在天然有它存在的價值。nginx
在一個處處是OOP的年代,爲什麼面向過程的C語言依然能夠如此活躍?這主要得益於C語言自己的語言特性。C語言小巧靈活,並且還有一個直接與硬件打交道的指針的存在,因此它是嵌入式開發惟有的高級語言;正由於他的小巧靈活,咱們能夠用它來開發一系列的小工具,Unix/Linux就是由這些小工具組成的操做系統;同時用C語言能夠開發高性能的應用程序。正則表達式
一、數據類型。C是一門面向過程的語言,但它依舊能夠實現大多數面向對象所能完成的工做。好比面向對象的三大特性:封裝、繼承、多態。apache
封裝:C中有一種複雜的數據結構叫作struct。struct是C裏面的結構體。編程
假如咱們要對person進行封裝,person可能包括姓名、性別、年齡、身高、體重等信息。咱們就能夠對它封裝以下:數組
struct Person{ char name[20];//姓名 char gender; //性別 int age; //年齡 int height; //身高 int weight; //體重 };
當咱們要像OOP那樣新建一個對象時,咱們就能夠:數據結構
struct Person p;
咱們就能夠直接對p進行賦值:併發
p.name = "whc"; p.gender = 'b'; //'b' = boy; 'g' = girl p.age = 25; p.height = 175; p.weight = 65;
繼承:一樣利用struct,咱們來建立一個學生結構,同時繼承結構體Person,以下:編程語言
struct Student{ struct Person p; char number[20]; //學號 int score; //成績 };
對Student進行建立對象,並賦值:函數
struct Student s; s.p.name = "whc"; s.p.gender = 'b'; s.p.age = 25; s.p.height = 175; s.p.weight = 65; s.number = "20150618"; s.score = 90;
多態:C中對於多態的實現能夠藉助函數指針來實現。爲了簡單起見,咱們假設Person這個結構體中,只有一個函數指針。工具
struct Person{ void (*print)(void *p); }; struct Student{ struct Person p; };
而Person和Student這兩個結構體的print函數實現以下:
void printPerson(void *person){ if(NULL == person) return ; struct Person *p = (struct Person *)person; printf("run in the person!!\n"); } void printStudent(void *person){ if(NULL == person) return ; struct Person *p = (struct Person *)person; printf("run in the student!!\n"); }
咱們寫一個函數來調用他們:
void print(void *person){ if(NULL == person) return ; struct Person *p = (struct Person *)person; p->print(person); } int main(){ struct Person person; struct Student student; person.print = printPerson; student.p.print = printStudent; print(&person); //實參爲Person的對象 print(&student); //實參爲Student的對象 return 0; }
他們的輸出爲:
其實這個也不難理解,不管是Person仍是Student,他們在內存中只有一個變量,就是那個函數指針,而void*表示任何類型的指針,當咱們將它強制轉換成struct Person*類型時,p->print指向的天然就是傳入實參的print地址。
二、 指針和內存管理
不管問哪個C工程獅:C語言中最容易出錯的地方在哪?咱們基本上會獲得同一個答案,那就是指針和內存溢出。那麼指針是什麼,指針其實就是一個地址,這個地址能夠是一個變量的地址,也能夠是一個函數的地址,不論是什麼,反正都是內存中的一個地址。
例若有一個變量a,咱們定義一個指針來保存變量a的地址:
int a = 0; int *p = &a;
若是是一個函數呢?咱們定義一個函數,而後用一個函數指針來保存這個函數地址:
int min(int a,int b){ return a<b?a:b; } int (*f)(int,int); f = min;
可能咱們有時候會想,難道咱們只能先定義一個變量或者函數,而後把它的地址給指針麼?不能直接使用指針,或者直接給指針賦一個常量麼?首先,咱們不知道內存中哪些是可用的地址,哪些是不可用的,每當咱們定義一個指針時,這個指針指向的是一個未定義的內存,這個就是傳說中的野指針。若是咱們給這個指針所指向的內存賦值,就有可能覆蓋了一些很重要的數據,因此每當咱們定義一個指針時,最好給它賦一個初始地址或者NULL;若是咱們給一個指針賦常量,一樣的道理。
指針的類型要與變量的類型一致(若是咱們不是故意要他們不一致),所謂類型,只是變量的一直表現形式,其實在內存中,他們不過是0101的二進制,當咱們用32bits的原碼錶示時,它就是unsigned;當咱們用32bits補碼錶示時,就是signed;當用浮點表示時就是float;當用更復雜的自定義表示時就是struct;用union能夠很好的理解這些。
如今咱們來說一下內存,這裏咱們只討論用戶內存區域:
通常分爲5個區域:
(1)程序代碼區:存放代碼指令的地方
(2)全局(靜態)變量區:包括初始化、未初始化的全局變量和靜態變量
(3)字符常量區:存放一些字符串常量,在C語言裏面,這個很容易與棧中定義的字符數組搞混,當咱們定義以下:
int main(){ char *str0 = "Hello World!"; //字符常量區 char str1[] = "Hello World!"; //棧區 return 0; }
str0所指向的字符串就是在字符常量區,可是str0自己的這個指針變量是在棧區的,這個變量存放的是字符常量區中"Hello World!"的首地址。
str1是字符數組,因此str1中所存放的字符串是在棧區,這裏利用的不過是字符數組初始化的一種形式,其實它能夠寫成以下形式:
char str1[] = {'H','e','l','l','o',' ','W','o','r','l','d','!','\0'};
(4)棧區:局部變量,形參,函數返回地址等,由系統來管理,在內存裏面是由高地址往低地址生長,因此棧空間大小是有限的,當在棧中定義一個很大的數組或者使用很深的遞歸調用時,就有可能棧溢出。
(5)堆區:由malloc、calloc、realloc函數分配的空間,由咱們本身來管理,每次用完以後,必須用free釋放內存,不然,就會產生內存泄漏,每次釋放內存後,雖然再也不佔用着這塊內存中,可是對應的指針依然指向這塊區域,這個指針就是野指針,因此釋放內存後,建議給指針賦NULL。以下:
int main(){ int *p = (int*)malloc(100*sizeof(int)); /* 執行語句 */ free(p);//這時p依然指向那塊內存,成了野指針 p = NULL; //對p賦值NULL return 0; }
三、C語言的I/O輸入輸出
C語言自己並不帶有輸入輸出的特性,因此它的全部I/O操做都是經過系統調用來實現。幸運的是C標準庫,已經給咱們封裝好了一系列的I/O操做的函數。
putchar ():把變量中的一個字符常量輸出到顯示器屏幕上;
getchar ();從鍵盤上輸入一個字符常量,此常量就是該函數的值;
printf ();把鍵盤中的各種數據,加以格式控制輸出到顯示器屏幕上;
scanf ();從鍵盤上輸入各種數據,並存放到程序變量中;
puts ():把數組變量中的一個字符串常量輸出到顯示器屏幕上
gets ():從鍵盤上輸入一個字符串常量並放到程序的數組中
一些爲對文件的操做,因爲一切皆可看做是文件,標準輸入,輸出也能夠看成文件來操做,文件描述符:標準輸入(0)、標準輸出(1)、標準錯誤(2)
fputs();輸出到文件
fgets();從文件輸入
fscanf();格式化文件輸入
fprintf();格式化文件輸出
另外兩個很重要的函數,固然還有他們的派生函數也是相似的
sscanf(); 從一個字符串中提取各種數據。
sprintf(); 把格式化的數據寫入某個字符串
這裏不對每一個函數進行詳解,主要對格式化函數進行分析:
(1)當咱們要把一個字符串轉換成一個整數或者把一個整數轉換成一個字符串時,咱們通常會想到atoi()或者itoa()(非標準函數),可是咱們能夠經過流來實現:
int main(){ int num = 10; char str[10] = {0}; sprintf(str,"%d",num); //把int轉換成char[] num = 0; sscanf(str,"%d",&num);//把字符串轉換成int printf("num:%d str:%s\n",num,str); return 0; }
輸出結果以下:
把字符串轉與其它類型之間的轉換:好比float,16進制,unsigned等均可以用流實現。
(2)格式化函數中的正則表達式
全部的格式化函數均可以定製本身的掃描集 %[abc]、%[a-z]、%[^abc]、%[^a-z],其中[]內是匹配的字符,^表示求反集。
當咱們要從標準輸入輸入一個可能帶空格的字符串時,直接用scanf("%s",str);當讀到空格時就返回,此時就可使用正則表達式:
char str[100] = {0}; scanf("%[^\n]",str);//直到遇到回車才寫入
從標準輸入中只要讀小寫字母a-z,遇到其它字符則返回:
char str[100] = {0}; scanf("%[a-z]",str);
其餘格式化函數的用法相同,不一一舉例。
四、總結
從大一開始學習C語言也有四五年了,我的認爲:C語言中最大的成功在於它的指針,可是也是最容易出錯的,想要理解C,必需要掌握指針。雖說,語言只是一門工具,可是這是基礎。或許,你能夠說,如今是JAVA的天下了,滿大街都是招聘JAVA工程師;或者你能夠說C太底層,如今都是OOP的時代了,誰還會用面向過程的......大家不要忘了操做系統是用什麼寫的?是C;C實現的nginx的併發量是C++實現的apache的幾十倍。不管是什麼編程語言,好好學,深刻學就行,不要由於它今天流行就拋棄昨天所學的。
版權全部,歡迎轉載,轉載請註明出處。