[讀] C和指針 (Ch11 ~ Ch14)

Chapter 11

  • mallocfree維護一個內存池數組

    • malloc老是分配一整塊內存。根據編譯器的實現,實際分配的內存也有可能比請求的稍大一些
    • 可用內存池沒法知足請求時,malloc先向OS申請新的內存,仍是不夠時返回NULL。所以malloc返回的指針必須先檢查
    • free的參數必須爲經過malloc等函數申請到的內存指針或NULL,它也老是釋放整個分配到的內存
  • calloc:返回指針前將整塊內存都初始化爲0
  • reallocbash

    • 用於(在尾部)擴大或縮小一塊已經分配到的內存,能夠被用於有效地釋放一塊內存的尾部空間
    • 原先的內存沒法修改大小時realloc可能返回一塊新內存的指針(原先的內容會被複制過去),所以realloc後必須更新指針
    • 第一個參數爲NULL時,與malloc行爲相同

Chapter 12

  • 能夠額外使用一個根結點來避免插入發生在鏈表頭部以致於須要修改根結點的值。對於雙鏈表,額外的根結點還能夠同時維護鏈表的頭部和尾部
  • 不過當一個節點自己的體積過大(好比存有大塊數據)時,這種方式會形成浪費

Chapter 13

  • ⚠️函數指針(這裏過重要了吧!!)函數

    此處重點在於函數調用符(即函數名後的括號)的優先級比間接訪問要高,所以工具

    int *foo(); // foo是一個返回int*指針的函數

    foo優先與函數調用符結合,成爲一個函數,而後對該函數的值(即返回值)進行間接訪問獲得int,可據此推論函數返回值爲int*測試

    int (*foo)(); // foo是一個返回int的函數的指針

    foo被迫先與間接訪問符結合,也就是說*foo做爲總體與函數調用符結合,即*foo爲函數,則foo爲函數指針命令行

    int (*foo[])(); // foo是一個函數指針數組

    foo先與[]結合,說明foo是一個數組名,接着foo[]*結合,說明foo中的元素爲某種指針,最後*foo[]與函數調用符()結合,並參照類型聲明爲int,說明元素爲返回int值的函數指針(暈了。。)debug

  • 函數名在表達式中的角色與數組有些相似,也是以相似指針常量的形式出現的,所以指針

    void f(int arg); // f爲接收1個int參數,無返回值的函數
    
    void (*fPtr)(int arg) = f; // 函數名能夠做爲指針被直接複製給函數指針
    void (*fPtr)(int arg) = &f; // 也能夠先取址再賦值給函數指針

    事實上,&只是顯式地執行了編譯器將隱式執行的工做code

  • 轉移表(jump table)常經過函數指針數組實現,此時若發生數組越界等問題debug將會很是艱難。。
  • 命令行參數遞歸

    • argc:參數個數(即argv數組的長度)
    • argv:字符串指針數組(命令行中空格分隔的每一截都爲一個參數),一般經過檢查字符串開頭是否爲-來識別選項參數
  • 字符串常量自己其實就是一個常量指針

    char *strPtr = "abcdefg";
    
    "abcdefg"[2] == 'c'; // true
    "abcdefg" + 2 == strPtr + 2; // true,所以它也能夠做爲一個字符串指針在printf裏直接用%s輸出

Chapter 14

  • 預約義符號(不是預處理指令!)

    • __FILE__%d源代碼名(test.c
    • __LINE__%d文件當前行號(25
    • __DATE__%s文件被編譯的日期(Jan 31 1997
    • __TIME__%s文件被編譯的時間(13:07:02
    • __STDC__%d編譯器是否遵循ANSI C標準(1 / 0
  • ⚠️帶參數宏

    #define macro(arg1, arg2) stuff // 參數列表的左括號必須緊鄰宏名,不然就會被認爲是stuff的一部分

    (之前對宏有誤解,覺得是單純的把一段文本替換成另外一段文本,而實際上宏是先被按照符號解析再轉換爲文本進行替換的)

  • 宏很容易產生歧義,要格外注意括號和分號的使用
  • 宏參數和#define定義能夠包含其餘#define定義的符號,但宏不能夠遞歸(大概是由於沒有調用棧這種東西)
  • 利用printf會將直接相鄰的字符串鏈接起來的特性,能夠實現簡單的輸出包裝:

    #define PRINT(FORMAT, VALUE) printf("This value is "FORMAT".\n", VALUE) // 注意沒有分號!
    
    PRINT("%d", x + 4); // 則FORMAT爲"%d",VALUE爲x + 4
    // 實際被轉換爲:
    printf("This value is ""%d"".\n", x + 4); // 三個字符串被自動鏈接
  • ⚠️符號鏈接符##:產生的新符號必須合法,不然就是undefined行爲

    #define ADD_TO_SUM(sum_number, value) sum##sum_number += value // 注意沒有分號!
    
    ADD_TO_SUM(5, 23); // 5將做爲sum_number的值被鏈接到sum,產生sum5這個符號,最後整個宏被替換爲sum5 += 23;
    // 宏被引用時上下文中必須存在名爲sum5的變量
  • ⚠️宏接收參數不指定類型,所以甚至能接受類型自己爲參數

    (這就是爲何sizeof不是一個函數吧,由於類型自己不能做爲參數傳遞,以及可變參數列表也是用宏來實現)

  • 帶反作用的宏參數可能致使嚴重錯誤,好比:

    • ++ / --:改變參數自己
    • getchar():消耗I/O buffer
  • 由於宏是被直接替換的,因此反覆使用同一個宏也老是生成新的代碼。而函數則每次在調用時使用同一份代碼
  • 命令行定義:

    • -D{name} / -D{name}=stuff === #define name stuff (未指定時stuff默認爲1)
    • -U{name} === #undef name
  • 條件編譯:#if / #endif / #elif / #else

    • 條件表達式將由預處理器進行求值,所以它們的值必須是在編譯時就能肯定的常量
    • 測試符號是否已被定義:

      • #ifdef symbol === #if defined(symbol)
      • #ifndef symbol === #if !defined(symbol)
    • ⚠️能夠結合命令行定義來完成條件編譯

      • 源代碼test.c

        #ifndef CHOICE // 以避免與命令行定義衝突
            #define CHOICE 5
        #endif
        
        int main() {
            printf("Choice: %d\n", CHOICE);
            return 0;
        }

        直接編譯執行:

        > gcc test.c -o test
        > ./test
        > Choice: 5

        編譯時使用命令行定義:

        > gcc -DCHOICE=2 test.c -o test # 提早定義CHOICE爲5
        > ./test
        > Choice: 2
    • 能夠在#endif後用註釋標註它結束的是哪個條件以提升程序可讀性
    • ⚠️使用條件編譯來避免因爲頭文件被重複包含致使的符號衝突

      #ifndef _HEADERNAME_H
      #define _HEADERNAME_H 1 // 若是沒有1則_HEADERNAME_H被定義爲空字符串,但仍然被定義,不會影響判斷結果
      #endif
      • 儘管有辦法防止衝突,多重包含仍然會拖慢編譯速度,應被盡力避免
      • 但也不該該爲了不多重包含而依靠頭文件本身嵌套包含,這樣會導在使用make等工具時很難判斷文件間的依賴關係
    • #progma指令是不可移植的,不一樣平臺的預處理器可能會徹底忽略它,或以不一樣的方式執行它
相關文章
相關標籤/搜索