估計不會寫C語言的同窗也都聽過C語言,從頭開始快速學一下吧,之後確定能用的上。 若是使用過其它類C的語言,如JAVA,C#等,學C的語法應該挺快的。git
先快速學習並練習一些基本的語言要素,基本類型,表達式,函數,循環結構, 基本字符串操做, 基本指針操做,動態分配內存,使用結構表示複雜數據, 使用函數指針實現靈活邏輯。github
雖然C是一個規模很小的語言,但也得本身多設計一些練習練手才能學會。算法
我就記得char, int, 別的都不經常使用吧應該,用的時候再搜索。數組
和JAVA, C#差很少吧,不用學基本,各類算數運算符,關係運算符,邏輯運算符,逗號, 括號等的意思應該也差很少,表達式最終的結果也有類型和值。函數
函數是最基本的抽象,基本沒有什麼語言沒有函數的概念,它封裝一系列操做, 最簡單的Hello world,以下。
學習
static void hello_world(){ printf("hello, world\n"); }
咱們的練習都是隨手寫的函數,不須要被外部調用,因此前面加個static,表示只在 本文件內可見。測試
printf
輸出一行的話,最後要加\n
, 常見個格式化參數有%d,%c,%s,%p等,分別表示 輸出int, char, 字符串, 指針。spa
和別的語言差很少,不過i的聲明要放在函數開頭,c89就是這樣。debug
static void n_hello_world(int n){ int i = 0; for (i = 0; i < n; i++) { printf("hello, world\n"); } }
庫函數strlen
就是幹這個的,不過咱們本身能夠寫一個練手,c沒有字符串類型, 用'\0'結尾的字符數組表示字符串,因此for循環從頭滾到'\0'位置就行了。設計
// 字符串練習, 計算字符串長度 static int w_strlen(const char* str){ int i; // 向後滾動指針,同時遞增i,直到找到字符串結尾 for (i = 0; *str != '\0'; str++, i++) { ; } return i; }
const 修飾符表示這個參數不能在函數裏進行更改,防止意外改動。char *就是傳說中 字符串了。 寫C程序得用好for語句,有各類慣用法,用好了能夠寫出很緊湊的程序,好比上面for語句 的第2個分號後的逗號表達式能夠遞增兩個變量。
第一種方式是在編譯時分配的內存,是字符串常量,指針s1指向的內存不能更改。 第二種方式應該是在棧上分配的內存(不肯定),能夠經過指針修改其中的字符。
static void change_str_test(){ // 常量不能修改 // char* s1 = "hello"; // will core dump char s1[10] = "hello"; *s1 = 'p'; printf("%s\n", s1); }
指針能夠進行加減的操做,每加一次就滾動過它指向的類型長度, 好比char指針就是 滾動1個字節。
// 指針練習, 反轉字符串 static char* reverse(char* str){ char* ret = str; // 滾到字符數組末尾的\0以前 char* p = str + w_strlen(str) - 1; char c; // 兩個指針,一個從前日後滾,一個從後往前滾,直到就要交錯以前 // 滾動的過程當中交換兩個指針指向的字符 for ( ; p > str; --p, ++str) { printf("debug[reverse]: %p %p %c %c\n", p, str, *p, *str); c = *p; *p = *str; *str = c; } return ret; }
c = *p
表示取出指針p指向的字符,賦值給變量c,*表示取值。
*p = *str
至關於p[i] = str[i]
,右邊的取出來的是值,左邊的取出來的也是值, 值賦值給值,看起來有些詭異,但就是這樣寫的。反正p = *str
確定不對,由於p是 指針類型,*str是計算結果是字符類型。
我記得TCPL前幾章都沒講malloc,free等內存分配的函數,好多任務只須要在編譯階段 分配內存就夠了,但比較大型複雜的程序應該都須要動態管理一些內存的。
C語言沒有GC,要手工釋放動態分配的內存,不然就會形成內存泄漏,因此必定要配平 資源,有malloc的地方,必定要想好它應該在哪裏free。
目前我瞭解到的原則就有兩種:
對了, malloc出來的內存要強轉成你須要的指針類型,而後free時指針要滾到你動態 分配內存的起始點。
// 內存申請相關,鏈接兩個字符串 static void concat_test(){ char* a = "hello"; char* b = "world"; //結果字符串長度爲兩個字符竄長度加\0的位置 int len = w_strlen(a) + w_strlen(b) + 1; // 動態分配內存 char* p = (char *)malloc(sizeof(char) * len); char* result; // 必須判斷是否分配到內存 if (p != NULL){ // 保存動態分配內存的開始指針,free時必須從這裏free result = p; //滾動p和a,直到a的末尾 while (*a != '\0') { printf("debug[concat_test]:while a %p %c\n", a, *a); *p++ = *a++; } //滾動p和b,直到b的末尾 while (*b != '\0') { printf("debug[concat_test]:while b %p %c\n", a, *a); *p++ = *b++; } // 末尾整個0 *p= '\0'; printf("concat_test: %s\n", result); //釋放動態分配的內存 free(result); }else{ printf("malloc error"); } }
C沒有類,要表達複雜的數據,就得用結構了, 結構也能夠用指針來指,若是是結構變量 的話,引用成員用.
,若是是指向結構的指針,引用成員用->
別的好像沒啥特別的,注意動態分配結構數組後,指針滾動的邊界,別使用了界外的 內存。若是結構的成員指向的內存是動態分配的花,也記得free。
沒有結構,估計寫不出大程序,結構應該會用的不少。
//結構練習,人員統計系統 struct customer { char* name; int age; }; static void customer_manager() { // 直接在棧上分配結構體 struct customer wawa; struct customer* p_wawa; struct customer* p_customers; int n = 2; char name[] = "wawa"; // char* name = "wawa"; //splint warning char name2[] = "tiancai"; // 直接用結構名訪問成員 wawa.name = name; wawa.age = 30; printf("%s is %d years old\n", wawa.name, wawa.age); // 用指針訪問結構成員 p_wawa = &wawa; p_wawa->age = 31; printf("%s is %d years old\n", wawa.name, wawa.age); // 爲員工數組動態分配內存 p_customers = (struct customer*)malloc(sizeof(struct customer) * n); if (p_customers != NULL) { // 設置數組第一項 p_customers->name = name; p_customers->age = 10; // 設置數組第二項 p_customers++; p_customers->name = name2; p_customers->age = 30; // 滾動數組外面,而後反向循環到數組開始 p_customers++; while(n-- > 0){ p_customers--; printf("%s is %d years old\n", p_customers->name, p_customers->age); } // 釋放動態分配的內存,這時候p_customers已經位於起始位置了 // 結構體裏的name1, name2是在棧上分配的,不用釋放 free(p_customers); } }
好多語言都有高階函數的特性,好比函數的參數或返回值還能夠是個函數, C裏也有函數指針能夠達到相似的效果,用來作回調函數等。
但C的函數指針寫起來比較詭異,很差記憶,不行就用typedef來從新命個名,寫起來 簡單一些。
下面用一個比較經典的冒泡排序來演示函數指針的使用,傳遞不一樣的比較函數能夠 改變排序函數的行爲,這是寫複雜靈活邏輯的一種很方便的方式。
// 函數指針練習, 排序 // 正序排序的比較函數 static int cmp_default(int a, int b){ return a - b; } // 反序排序的比較函數 static int cmp_reverse(int a, int b){ return b - a; } // int類型的冒泡排序算法,可傳入一個比較函數指針 // 相似回調函數,該函數須要兩個int參數且返回int static void sort(int* arr, int n, int (*cmp)(int, int)){ int i, j, t; int *p, *q; p = arr; for (i = 0; i < n; i++, p++) { q = p; for (j = i; j < n; j++, q++) { // 調用函數指針指向的函數和使用函數同樣,貌似是簡單寫法 if (cmp(*p, *q) > 0) { t = *p; *p = *q; *q = t; } } } } // 測試排序函數 static void sort_test(){ int arr[] = {4, 5, 3, 1, 2}; int i, n = 5; // 正向排序, 傳入cmp_default函數的地址,貌似不須要&取地址 sort(arr, 5, cmp_default); for (i = 0; i < n; i ++) { printf("%d%s", arr[i], i == n - 1 ? "" : ", "); } printf("\n"); //反向排序,同上 sort(arr, 5, cmp_reverse); for (i = 0; i < n; i ++) { printf("%d%s", arr[i], i == n - 1 ? "" : ", "); } printf("\n"); }
這幾年斷斷續續看了四五遍K&R的《TCPL》了,可一直都沒寫過C程序,如今開始多練習 練習吧。