快速學習C語言一: Hello World


估計不會寫C語言的同學也都聽過C語言,從頭開始快速學一下吧,以后肯定能用的上。 如果使用過其它類C的語言,如JAVA,C#等,學C的語法應該挺快的。

先快速學習並練習一些基本的語言要素,基本類型,表達式,函數,循環結構, 基本字符串操作, 基本指針操作,動態分配內存,使用結構表示復雜數據, 使用函數指針實現靈活邏輯。

雖然C是一個規模很小的語言,但也得自己多設計一些練習練手才能學會。

基本類型

我就記得char, int, 別的都不常用吧應該,用的時候再搜索。

表達式

和JAVA, C#差不多吧,不用學基本,各種算數運算符,關系運算符,邏輯運算符,逗號, 括號等的意思應該也差不多,表達式最終的結果也有類型和值。

函數

函數是最基本的抽象,基本沒有什么語言沒有函數的概念,它封裝一系列操作, 最簡單的Hello world,如下。

static void hello_world(){
    printf("hello, world\n");
}

 

我們的練習都是隨手寫的函數,不需要被外部調用,所以前面加個static,表示只在 本文件內可見。

printf輸出一行的話,最后要加\n, 常見個格式化參數有%d,%c,%s,%p等,分別表示 輸出int, char, 字符串, 指針。

分支,循環結構

和別的語言差不多,不過i的聲明要放在函數開頭,c89就是這樣。

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程序,現在開始多練習 練習吧。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM