[讀] C和指針 (Ch4 ~ Ch7)

Chapter 4

  • if嵌套出現時,else屬於離它最近的不完整的if
  • switch的最後一個case也加上break是個好習慣,以避免在後面追加新的case時忘記添加break
  • 使用goto通常不是好事,惟一的例外多是須要從多重循環中直接退出時比較方便,可是這也能夠經過把整個循環包裝成一個函數,而後在須要退出的時候直接return來解決
  • C自己不具有任何I/O函數及異常處理能力,這些都要靠調用庫函數來實現

Chapter 5

  • 取模運算符%不能用於浮點數
  • 對負數作整數除法的結果取決於編譯器實現,可能向0取整,也可能向負無窮取整
  • 移位⚠️C只規定對於unsigned值使用邏輯移位,對於signed值使用的移位方式則由編譯器決定!數組

    • 邏輯移位:不要問就是移!無論左右,空位全補0
    • 算數移位:ide

      • 左移時與邏輯移位效果相同,結果可能與原值不一樣正負,但這被視爲溢出,不予處理
      • 右移時只能一位一位移,符號位爲0則補0,爲1則補1,以保證與原值同號函數

        • 正數的算數右移等價於邏輯右移,但負數不等價!
        • 負數不等價
    • 循環移位:彙編及verilog中用的桶移位(bucket shift)是循環移位,只有它確保原值中的0和1個數不變
  • ⚠️C中的賦值符實際上會將左值做爲運算結果返回,因此下面這種連續賦值實際上是合法的:測試

    int x = 3;
    int y = x = x + 2; // 合法

    可是實際上不管x是否爲intx + 2都會返回一個int結果並賦值給x,而若x實際上是一個char,這樣的賦值就會致使結果的高位被截去,於是獲得的x的值的完整性是沒法保障的,進而y的值也不必定符合指望。lua

  • ⚠️sizeof操做符判斷它的操做數的類型長度(sizeof不是函數!!),而並不須要知道它的操做數(也能夠是表達式)的實際值指針

    // 括號
    sizeof(x);
    sizeof x; // 二者徹底等價,以字節爲單位返回變量x的長度
    // 數組
    sizeof(arr); // 返回數組的字節數(不是數組長度!!)
    // 表達式
    sizeof(a = b + 3); // 判斷長度不須要知道表達式的實際值,所以sizeof不會執行接收到的表達式,a也並不會被賦任何值
    // 給sizeof一個有反作用的表達式將會獲得以下警告:
    // Expression with side effects has no effect in an unevaluated context
  • ⚠️自增 & 自減code

    ++和--真是C永恆的話題。。
    • 只能用於左值
    • 結果爲值的拷貝,並非變量自己。所以表達式中出現自增自減時,實際參與表達式運算的是一個un-assignable的單純的值
    int a = 10;
    int b = a++;        // 合法:a加一的結果先被做爲一個值拷貝後賦值給b,而後a被加一
    int c = (++a)++;    // 非法:++a實際是a值的拷貝,不是一個左值,所以不可自增自減
  • 逗號操做符分隔多個表達式,表達式們從左到右逐個求值,一整條逗號表達式的值等於最後一個小表達式的值。內存

    惟一的用處多是用來簡化同時出如今while循環前面和內部的語句(能夠用逗號鏈接起來放進while的條件裏,而後把原來的條件做爲最後一個表達式)。。由於while每一輪開始前都會執行一遍條件
  • 對於一個任意的整形值,顯式地測試它的具體值比把它看成布爾值直接判斷真假要更好
  • ⚠️隱式類型轉換ci

    C中的整型算數運算至少都會以缺省整型類型的精度來進行,即使全部操做數都是更小的類型(居然是這樣!):get

    char a, b, c;
    a = b + c; // 雖然a, b, c全都是較小的char,b和c也會先被提高爲int,而後執行加法獲得int結果,再將結果截短賦值給a

    可是,若是直接參與運算的值已經達到了不用提高的標準而運算結果將會產生溢出,那麼即便左邊變量足夠接收完整的結果,獲得的也是先溢出再被加長的值:

    int a, b;
    long c = a * b; // 就算a和b相乘產生溢出,右邊表達式的計算結果也只是溢出後的int,不會由於c夠大就提高a和b的長度
  • 對於左右都有表達式的運算符,先計算左邊表達式仍是先計算右邊表達式是由編譯器決定的,所以以下表達式的結果實際沒法預測:

    a = ++b + --b; // C只規定自增自減運算要優先加法運算執行,可是先求加法的左邊(++b)仍是右邊(--b)則由編譯器決定

    有更好的例子以下:

    f() + g() - h();
    // 同級運算符從左到右執行的規則只約束例子中的加法比減法先執行,而不約束執行加法時必定要先算f()再算g()

    甚至編譯器也能夠先把每個小的表達式求出來再作運算:

    f() + f() * f();
    // 先無視運算優先級,直接求出3個f()再進行總體運算也是能夠的,此時3次f()的調用順序徹底取決於編譯器實現

Chapter 6

  • 訪問一個指針所指向的地址的過程稱爲間接訪問(indirection)或解引用指針(dereferencing the pointer)
  • 注意運算符優先級,尤爲是對被解引用的同時自增自減的指針(到底爲何要寫這種代碼!!)
  • 指針與整值的加減法老是以它所指向的類型的大小爲單位:對int型指針+1會使它指向下一個int
  • ⚠️指向同數組兩個指針相減的結果也是以它所指向的類型大小爲單位的,這個結果的類型爲ptrdiff_t
  • 指向不一樣數組的指針相減是undefined的,由於沒有意義
  • ⚠️標準容許將指向數組元素的指針和指向該數組最後一個元素後面那個位置的指針相比較,但不容許將它和指向數組第一個元素以前的內存位置的指針相比較

    這是什麼神仙規定。。不過大部分狀況下查得不嚴,仍是能夠比較的,只是移植性會稍微降低

Chapter 7

  • 可變參數列表

    • stdarg.h

      • 類型:va_list
      • 宏:va_startva_argva_end
      • #include <stdarg.h>
        int sum(int argNum, ...) { // 對未知個數的參數求和
            va_list args;
            va_start(args, argNum); // 使用va_start(可變參數列表 + 最後一個命名參數)將args指向可變參數列表的第一個元素
        
            int result = 0;
            for (int i = 0; i < argNum; i ++) {
                result += va_arg[args, int]; // 使用va_arg得到可變參數列表中的下一個參數併爲它指定類型
            }
            va_end(args); // 使用va_end結束訪問可變參數列表
        
            return 0;
        }
      • 這些宏沒法知道實際參數的數量和類型,而且可變參數實際上會先進行缺省參數類型提高再傳入函數,要格外當心
相關文章
相關標籤/搜索