天天看點

C語言的基礎知識1 - 木屐

C語言的基礎知識1

1、作用域

1.1代碼塊作用域:花括号之間的所有語句稱為一個代碼塊,作用域就是{}内。
    注意:如果内層代碼有一個辨別符的名字和外層代碼塊的一個辨別符同名,内層的就将隐藏外層的。外層的辨別符無法在内層代碼通過名字通路。
    ANSI C把形參的作用域設定為函數最外層的那個作用域(也就是整個函數體),函數最外層作用域的局部變量無法與形參名同名。
1.2函數作用域:goto所用的辨別符,盡量不要用
1.3檔案塊作用域:任何在所有代碼塊之外聲明的辨別符都具有檔案作用域,在檔案内有效,且包括#include包含的檔案。
1.4原型作用域:隻适用于函數原型中聲明的參數名,比如在int b(int c);
           

2、連結屬性

2.1 外部external,成為全局實體,不論聲明多少次,位于幾個源檔案内,都是同一實體。
2.2 内部internal,在同一個源檔案内指同一實體,位于不同源檔案是不同的實體。
2.3 無none,總是被當成單獨的個體,多個聲明被當作不同的實體。
注意:在預設情況下,在代碼塊之外的辨別符的連結屬性為external,其餘為none

2.4 修改連結屬性
    2.4.1 extern 一般而言,它為一個辨別符指定external連結屬性,這樣就可以通路在其他任何位置定義的這個實體。當extern用于源檔案中一個辨別符的第1次聲明時,它指定該辨別符具有external連結屬性,但是如果是第2次或者以後的聲明時,它并不會更改第1次聲明所指定的連結屬性。
    2.4.2 static 如果某個聲明在正常情況下有external連結屬性,前面加上static就改為internal連結屬性,可以防止被其他源檔案調用。隻對預設為external連結屬性的聲明才有效。
           

3、常量

不能修改的值稱為常量,用const 或者用define

4、存儲類型

有三個地方用于存儲變量:普通記憶體、運作時堆棧、硬體寄存器

4.1 凡是在任何代碼塊之外聲明的變量總是存儲在靜态記憶體中的,這類變量成為靜态(static)變量。靜态變量在程式運作之前建立,在整個執行期間始終存在。

4.2 代碼塊内部聲明的變量預設存儲類似是自動的(automatic),存儲在堆棧裡,關鍵字auto用來修飾(但很少使用,因為預設就是),調用時配置設定,調用結束銷毀。

4.3 關鍵字register可以用于自動變量的聲明,存儲在硬體寄存器中,而不是堆棧中。

在代碼塊内部聲明的變量,前面加上static,則存儲類型變成靜态,在整個程式執行過程中一直存在,而不僅僅在聲明它的代碼塊執行時存在。
注意:修改變量的存儲類型并沒有修改變量的作用域,它仍然隻能在代碼塊内部按名字通路。
      函數的形參不能聲明為靜态,因為實參總是在堆棧中傳遞給函數,用于支援遞歸。
4.4 對于函數而言,函數代碼總是存儲在記憶體中的。
           

5、初始化

5.1 靜态變量初始化:把想要初始化的值放在當程式執行時變量将會使用的位置。當可執行檔案載入記憶體時,這個已經儲存了正确初始值的位置将指派給那個變量,如果不顯示的初始化,靜态變量将被初始化為0。

5.2 自動變量初始化:要指派,不然它的值就是堆棧中上一次的垃圾值。

6、static總結

6.1 當它用于函數定義時,或用于代碼塊之外的變量聲明時,static用于修改辨別符的連結屬性,由external-->internal,但辨別符的存儲類型和作用域不受影響。

6.2 當用于代碼塊内部的變量聲明時,修改存儲類型為靜态,隻初始化一次,程式執行之前建立,運作時一直存在,代碼執行完畢銷毀。

7、操作符

7.1 算術操作符:+ - * / %,其中%隻能用于整型操作數,其他既可整型又可浮點型

7.2 移位操作符:>> <<,轉換成二進制移位。

向右移時,情況分兩種

7.2.1 邏輯移位:左邊補0

7.2.2 算術移位:左邊補符号位

說明:無符号數都采用邏輯移位,有符号數則要看編譯器的設定

7.3 位操作符 &(按位與) |(按位或) ^(按位異或)

7.4 指派 x=y+3;這個也是個表達式,指派表達式的值就是左操作數的新值,指派操作符的結合型(求值的順序)是從右向左。

7.5 複合指派符 += -= *= /= %= <<= >>= &= ^= |=

7.6 單目操作符 !邏輯取反 ++ -- -(負數) &(取位址) sizeof(操作者的類型長度,以位元組為機關) ~求補操作(1變成0 0變成1) -- +(什麼也不幹) * (間接通路)

sizeof(int)傳回整型變量的位元組數

sizeof x 傳回變量x所占據的位元組數,字元變量的長度為1個位元組。當sizeof的操作數是個數組名時,它傳回數組的長度,以位元組為機關的。

判斷表達式的長度并不需要對表達式求值,是以sizeof(a=b+1)并沒有向a賦任何值

7.7 關系操作符 > >= < <= != ==

7.8 邏輯操作符 && ||

7.9 條件操作符 expression1?expression2:expression3

7.10 逗号操作符 expression1,expression2,expression3,...,expressionN 自左向右求值,整個表達式的值就是最後那個值

7.11 左值和右值 a=b+25;
    a是個左值,辨別了一個可以存儲結果值的地點
    b是個右值,指定了一個值
    不可以互換,左右兩邊不能互換,因為b+25不是一個右值,當計算機計算b+25時,它的結果必然儲存在計算機的某個地方,但是程式員沒有辦法預測這個結果會存儲在什麼地方,也無法保證這個表達式的值下次還存儲在那個地方,其結果是這個表達式不是一個左值。同理,字面值常量也都不是左值。
    C/C++語言中可以放在指派符号左邊的變量,即具有對應的可以由使用者通路的存儲單元,并且能夠由使用者去改變其值的量。左值表示存儲在計算機記憶體的對象,而不是常量或計算的結果。或者說左值是代表一個記憶體位址值,并且通過這個記憶體位址,就可以對記憶體進行讀并且寫(主要是能寫)操作;這也就是為什麼左值可以被指派的原因了。相對應的還有右值:當一個符号或者常量放在操作符右邊的時候,計算機就讀取他們的“右值”,也就是其代表的真實值。簡單來說就是,左值相當于位址值,右值相當于資料值。右值指的是引用了一個存儲在某個記憶體位址裡的資料。
7.12操作符的屬性
複雜表達式的求值順序絕對因素:操作符的優先級、操作符的結合性、操作符是否控制執行的順序。
           
C語言的基礎知識1 - 木屐
C語言的基礎知識1 - 木屐

8、指針

8.1 指針運算

8.1.1 算術運算 p是一個指向int類型的指針,p+2則是移動2個int類型的位置,也就是8個位元組

8.2.2 指針-指針,隻有當兩個指針都指向同一數組中的元素時,才允許一個指針減去另一個指針,結果類型是ptrdiff_t,有符号整型類型,是兩個指針在記憶體中的距離(以數組元素的長度為機關,而不是以位元組為機關)

如果兩個指針指向的不是同一個數組的元素,那麼他們相減的結果就是未定義的。

9、函數

9.1 函數的參數 傳遞給函數的标量參數都是傳值調用的,傳遞給函數的數組參數在行為上就像的他們通過傳址調用那樣(因為傳值傳的是一個位址,再通過間接通路,實際通路的仍然是那個地方)

9.2 可變參數清單 是通過宏來實作的,這些宏在stdarg.h中。這個頭檔案聲明了一個類型va_list和三個宏va_start、va_arg、va_end。我們可以聲明一個va_list的變量,與這幾個宏配合使用,通路參數的值。

C函數要在程式中用到以下這些宏:
void va_start( va_list arg_ptr, prev_param ); 
type va_arg( va_list arg_ptr, type ); 
void va_end( va_list arg_ptr );

va_list:用來儲存宏va_start、va_arg和va_end所需資訊的一種類型。為了通路變長參數清單中的參數,必須聲明
         va_list類型的一個對象       定義: typedef char *  va_list;
va_start:通路變長參數清單中的參數之前使用的宏,它初始化用va_list聲明的對象,初始化結果供宏va_arg和
           va_end使用;
va_arg: 展開成一個表達式的宏,該表達式具有變長參數清單中下一個參數的值和類型。每次調用va_arg都會修改
          用va_list聲明的對象,進而使該對象指向參數清單中的下一個參數;
va_end:該宏使程式能夠從變長參數清單用宏va_start引用的函數中正常傳回。
va在這裡是variable-argument(可變參數)的意思. 
這些宏定義在stdarg.h中,是以用到可變參數的程式應該包含這個頭檔案.下面我們寫一個簡單的可變參數的函數,改函數至少有一個整數參數,第二個參數也是整數,是可選的.函數隻是列印這兩個參數的值.
           
C語言的基礎知識1 - 木屐
從這個函數的實作可以看到,我們使用可變參數應該有以下步驟: 
    1)首先在函數裡定義一個va_list型的變量,這裡是arg_ptr,這個變量是指向參數的指針. 
    2)然後用va_start宏初始化變量arg_ptr,這個宏的第二個參數是第 一個可變參數的前一個參數,是一個固定的參數. 
    3)然後用va_arg傳回可變的參數,并指派給整數j. va_arg的第二個參數是你要傳回的參數的類型,這裡是int型. 
    4)最後用va_end宏結束可變參數的擷取.然後你就可以在函數裡使用第二個參數了.如果函數有多個可變參數的,依次調用va_arg擷取各個參數. 
二、可變參類型陷阱
    下面的代碼是錯誤的,運作時得不到預期的結果:
    view plaincopy to clipboardprint?
    va_start(pArg, plotNo);   
    fValue = va_arg(pArg, float);  // 類型應改為double,不支援float   
    va_end(pArg);  
    va_start(pArg, plotNo);
    fValue = va_arg(pArg, float);  // 類型應改為double,不支援float
    va_end(pArg);
    下面列出va_arg(argp, type)宏中不支援的type:
    —— char、signed char、unsigned char
    —— short、unsigned short
    —— signed short、short int、signed short int、unsigned short int
    —— float
    在C語言中,調用一個不帶原型聲明的函數時,調用者會對每個參數執行“預設實際參數提升(default argument promotions)”。該規則同樣适用于可變參數函數——對可變長參數清單超出最後一個有類型聲明的形式參數之後的每一個實際參數,也将執行上述提升工作。
    提升工作如下:
    ——float類型的實際參數将提升到double
    ——char、short和相應的signed、unsigned類型的實際參數提升到int
    ——如果int不能存儲原值,則提升到unsigned int
    然後,調用者将提升後的參數傳遞給被調用者。
           

是以,可變參函數内是絕對無法接收到上述類型的實際參數的。

關于該陷井,C/C++著作中有以下描述:

    在《C語言程式設計》對可變長參數清單的相關章節中,并沒有提到這個陷阱。但是有提到預設實際參數提升的規則:
    在沒有函數原型的情況下,char與short類型都将被轉換為int類型,float類型将被轉換為double類型。
            ——《C語言程式設計》第2版  2.7 類型轉換 p36
    在其他一些書籍中,也有提到這個規則:
    事情很清楚,如果一個參數沒有聲明,編譯器就沒有資訊去對它執行标準的類型檢查和轉換。
    在這種情況下,一個char或short将作為int傳遞,float将作為double傳遞。
    這些做未必是程式員所期望的。
    腳注:這些都是由C語言繼承來的标準提升。
    對于由省略号表示的參數,其實際參數在傳遞之前總執行這些提升(如果它們屬于需要提升的類型),将提升後的值傳遞給有關的函數。——譯者注
            ——《C++程式設計語言》第3版-特别版 7.6 p138
    …… float類型的參數會自動轉換為double類型,short或char類型的參數會自動轉換為int類型 ……
            ——《C陷阱與缺陷》 4.4 形參、實參與傳回值 p73
    這裡有一個陷阱需要避免:
    va_arg宏的第2個參數不能被指定為char、short或者float類型。
    因為char和short類型的參數會被轉換為int類型,而float類型的參數會被轉換為double類型 ……
    例如,這樣寫肯定是不對的:
    c = va_arg(ap,char);
    因為我們無法傳遞一個char類型參數,如果傳遞了,它将會被自動轉化為int類型。上面的式子應該寫成:
    c = va_arg(ap,int);
           

10、指針

10.1指針與效率

10.1.1 當你根據某個固定數目的增量在一個數組中移動時,使用指針變量将比使用下标産生效率更高的代碼。當這個增量是1并且機器具有位址自動增量模型時,這點表現的更為突出。

10.1.2 聲明為寄存器變量的指針通常比位于靜态記憶體和堆棧中的指針效率更高(具體提高的幅度取決于你所使用的機器)。

10.1.3 如果你可以通過測試一些已經初始化并經過調整的内容來判斷循環是否終止,那麼你就不需要使用一個單獨的計數器。

10.1.4 那些必須在運作時求值的表達式較之諸如&array[SIZE]或array+SIZE這樣的常量表達式往往代價更高。

10.2數組和指針

指針和數組并不是相等的,int a[5]; int b;

聲明一個數組時,編譯器将根據聲明所指定的元素數量為數組保留記憶體空間,然後再建立數組名,它的值是一個指針常量,指向這段空間的起始位置。聲明一個指針變量時,編譯器隻為指針本身保留記憶體空間,它并不為任何整型值配置設定記憶體空間。是以a是完全合法的,但是*b确是非法。另一方面表達式b++可以通過編譯,a++卻編譯不了,因為a的值是個常量

11、數組

11.1 字元數組的初始化

char message1[] = "Hello";

char *message2 = "Hello";

C語言的基礎知識1 - 木屐
11.2 多元數組 matrix[1][5]=*(*(matrix+1)+5)
11.3 指向數組的指針
    int matrix[3][10];
    int (*p)[10]=matrix;    p指向matrix數組的第1行,指向擁有10個整型元素的數組的指針
    int *pi=&matrix[0][0];
    int *pi=matrix[0];這樣就可以逐個對數組進行移動求值了
11.4 作為函數參數的多元數組,編譯器需要知道它的維數,以便為函數形參的下标表達式進行求值。
    int matrix[3][10]; void func2(int (*mat)[10]);或者 void func2(int mat[][10])
11.5 指針數組 int *api[10];指向整型的指針數組,一個次元為10的數組,每一個元素都是指向整型的指針