快速學習C語言一: Hello World

估計不會寫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程序,如今開始多練習 練習吧。

相關文章
相關標籤/搜索