static關鍵字
1.作用於變量:
   用static
聲明局部變量-------局部變量指在代碼塊{}內部定義的變量,只在代碼塊內部有效(作用域),其缺省的存儲方式是自動變量或說是動態存儲的,即指令執行到變量定義處時才给變量分配存儲單元,跳出代碼塊時釋放內存單元(生命期)。用static聲明局部變量時,則改變變量的存儲方式(生命期),使變量成为靜態的局部變量,即編譯時就为變量分配內存,直到程序退出才釋放存儲單元。這样,使得該局部變量有記憶功能,可以記憶上次的數據,不過由於仍是局部變量,因而只能在代碼塊內部使用(作用域不變)。

   用static聲明外部變量-------外部變量指在所有代碼塊{}之外定義的變量,它缺省为靜態變量,編譯時分配內存,程序結束時釋放內存單元。同時其作用域很廣,整個文件都有效甚至別的文件也能引用它。为了限制某些外部變量的作用域,使其只在本文件中有效,而不能被其他文件引用,可以用static關鍵字對其作出聲明。

總結:用static聲明局部變量,使其變为靜態存儲方式,作用域不變;用static聲明外部變量,其本身就是靜態變量,這只會改變其連接方式,使其只在本文件內部有效,而其他文件不可連接或引用該變量。

2.作用於函數:
使用static用於函數定義時,對函數的連接方式產生影響,使得函數只在本文件內部有效,對其他文件是不可見的。這样的函數又叫作靜態函數。使用靜態函數的好處是,不用擔心與其他文件的同名函數產生幹擾,另外也是對函數本身的一種保護機制。

如果想要其他文件可以引用本地函數,則要在函數定義時使用關鍵字extern,表示該函數是外部函數,可供其他文件調用。另外在要引用別的文件中定義的外部函數的文件中,使用extern聲明要用的外部函數即可。

参考資料:

①《C程序設計(第二版)》,譚浩強

②《Pointers on C》,Kenneth A.Reek

void和void指針

void的含義
    void即“無類型”,void *則为“無類型指針”,可以指向任何數據類型。

void指針使用規範
   
①void指針可以指向任意類型的數據,亦即可用任意數據類型的指針對void指針賦值。例如:
    int *pint;
    void *pvoid;
    pvoid = pint;   /* 不過不能 pint = pvoid; */
    如果要將pvoid賦给其他類型指針,則需要強制類型轉換如:pint = (int *)pvoid;
   
    ②在ANSI C標准中,不允許對void指針進行算術運算如pvoid++或pvoid+=1等,而在GNU中則允許,因为在缺省情況下,GNU認为void *與char *一样。sizeof( *pvoid )== sizeof( char ).
  

void的作用
   
①對函數返回的限定。
    ②對函數参數的限定。
    當函數不需要返回值時,必須使用void限定。例如: void func(int, int);
    當函數不允許接受参數時,必須使用void限定。例如: int func(void)。

    由於void指針可以指向任意類型的數據,亦即可用任意數據類型的指針對void指針賦值,因此還可以用void指針來作为函數形参,這样函數就可以接受任意數據類型的指針作为参數。例如:
    void * memcpy( void *dest, const void *src, size_t len );
    void * memset( void * buffer, int c, size_t num );


参考資料:《 C/C++語言void及void指針深層探索 》,宋寶華

函數指針

函數指針是什麼?
    先來看函數調用是怎麼回事。一個函數占用一段連續內存。當調用一個函數時,實際上是跳轉到函數入口地址,執行函數體的代碼,完成後返回。如何找到對應的入口地址?這是由函數名來標記的,實際上,函數名就是函數的入口地址。
    函數指針是一種特殊類型的指針,它指向一個函數的入口地址。

   
注意:除了void類型指針是無類型的指針外,其他所有指針都是有對應類型的,例如int *pint、struct studentdata *psdata等,只有指明了指針所指的數據類型,編譯器才能为指針分配或預計分配相應大小的存儲空間,指針的算術運算如pint++等才是有意義的。因此,定義了某種類型的指針之後,除非使用強制類型轉換,那麼它只能指向相應數據類型的變量或常量,不同類型的指針或數據之間不可混用。所以指針的類型實際上是一種身份標志的作用。

   函數指針如何表明自己的身份呢?为了避免混亂,必須也要作出相應規定,不同函數的函數指針不能混用。例如,int func1(int arg11, char arg12)與int func2(char arg)的函數指針就不能混用,要定義可以指向func1的函數指針應該這样:
    int (*pfunc1)(int, char) = func1;
定義可以指向func2的函數指針則該如下:
    int (*pfunc2)(char) = func2;
   從函數指針的定義可以看出,函數指針的類型實際上是由函數簽名决定的。函數簽名就象是函數的身份證,一個函數的函數簽名是獨一無二的,具有相同函數簽名的函數實際上就是同一函數。函數簽名包括函數名、函數形参類型的有序列表和函數返回值類型。

    一個函數指針的定義規定了它只能指向特定類型的函數。如果兩個函數的形参列表和返回值類型相同,只有函數名和函數體不同,則可以使用相同類型的函數指針。例如,如果還有一個函數int func3(char arg),則上面定義的可以指向函數func2的函數指針也可以用於指向func3,即:
    pfunc2 = func3;
    再使用pfunc2(char ARG)就可以調用函數func3,這時指令計數器(PC)指向函數入口,從此開始執行函數體代碼。
    注意:對函數指針進行算術運算也是沒有意義的。

如何使用函數指針?
    ①定義合适類型的函數指針變量;
       int (*pfunc)(int, int);
    ②给函數指針變量賦值,使它指向某個函數入口;
       int example(int, int);
       pfunc = example;          /*將函數入口地址賦给函數指針變量*/
       或者:pfunc = &example;    /*函數名總是被編譯器轉換为函數指針(入口地址)來使用,因此與上面一句等價 */
    ③使用函數指針來調用相應的函數;
       retval = pfunc(10, 16);
       或者:retval = (*pfunc)(10, 16);
       上面兩句都與retval = example(10, 16);等價。
    理解:一個指針變量p實際上也和普通的變量一样,要占存儲空間(通常與平台的虛擬地址一样寬),也有其自身的存儲地址&p;不同的是,在指針變量p的值有特殊的意義,它是另外一個變量或常量的地址值,也就是說,在地址为&p的存儲單元上存放着另外一個數據的地址。因此,*p實際上是將p看作它指向的數據的地址來使用,*操作符是引用相應地址中的數據,也就是對地址为p的存儲單元中存放的數據進行操作。
    一個函數指針變量則更为特殊。比如上面的例子,pfunc變量本身的值是函數example()的入口地址。因此pfunc可以代替其所指函數的函數名來使用。至於*pfunc,如果按照上面的理解,它實際上是地址pfunc的內容,也即函數example()的入口地址的內容,就有點含糊了。不過,從另一方面來理解,如果使用pfunc = &example來初始化pfunc,則*pfunc == *(&example) == example,又與pfunc等價。因此,就有了兩種使用函數指針來調用相應函數的形式。
    值得注意的是,不可用*pfunc來對pfunc的值初始化。即*pfunc = example的寫法是錯誤的。


为什麼要使用函數指針?
   
前面介紹了函數指針的基本知識和使用規範。下面介紹函數指針的實際用途。不過首先要對前面的知識再做一個補充,因为下面的應用很可能用到這一特性。前面指出,除函數名之外的函數簽名內容(函數返回值類型和形参列表)决定了函數指針的類型。實際上還有一種特殊的或說通用的函數指針,在定義這類函數指針時,只需要指定函數返回值類型,而留空形参列表,這样就可以指向返回值類型相同的所有函數。例如:
    int (*pfunc)();
    這样定義的pfunc就可以指向前面提到的func1和func2,因为他們都返回整型值。
   注意:int (*pfunc)()與int (*pfunc)(void)不是一回事,後者不允許接受任何参數。


   函數指針最常見的三個用途是:
    ①作为参數傳遞给其他函數。
    這样可以把多個函數用一個函數體封裝起來,得到一個具有多個函數功能的新函數,根據傳遞的函數指針變量值的不同,執行不同的函數功能。這是函數嵌套調用難以實現的。参數的傳遞可以由程序員設定,也可以由用戶輸入讀取,因此具有較大的靈活性和交互性。
    另外還可以用於回調函數。使用void配合,還可以將對不同數據類型的數據進行相同處理的多個函數封裝为一個函數,增強函數的生命力。

    ②用於散轉程序。
    這種程序首先建立一個函數表(實際上是一個函數指針數組),表中存放了各個函數的入口地址(或函數名),根據條件的設定來查表選擇執行相應的函數。這样也可以將多個函數封裝为一個函數或者程序,散轉分支條件可以由程序員設定,也可以由用戶輸入讀取,甚至是外設的某種特定狀態(這種狀態可以是不受人为控制的)。

    ③實現C的面向對象的類的封裝。
    C語言中的struct與C++中的class有很大不同,除了缺省的成員屬性外(struct的成員缺省为public的,可隨意使用,而class成員缺省为private的),
struct還很難實現類成員函數的封裝。struct的成員一般都是數據成員,而非函數成員。因此,为了在C語言中,为某個struct定義一套自己的函數對結構數據成員進行操作,可以在struct結構體中增加函數指針變量成員,在初始化時使它指向特定函數即可。

    應用舉例:
    ①假設定義了四個函數:add(int, int)、sub(int, int)、mul(int, int)、div(int, int),可以將其封裝为一個四則運算計算器函數:
    double calculator(int x, int y, int (*pfunc)(int, int)) {
       double result;
       result = pfunc(x, y);
       return result;
    }
    又例如,在一個鏈表查詢程序中,要通過比較節點的特征值來查詢節點,不同類型的數據的比較方式不一样,整型等可以直接比較,字符串卻要用專門的字符串操作函數,为了使代碼可重用性更高,可以使用一個比較函數來代替各種不同數據類型的直接比較代碼,同時,比較函數也必然是數據類型相關的,因此要使用void指針和函數指針來轉換为類型無關的比較函數,根據相應的數據類型,調用相應的函數(傳遞相應的函數指針)。一個實例是:
    int (*compare)(void const *, void const *);
    這個函數指針可以接受任意類型的數據的指針参數,同時返回int值作为比較結果標志。一個比較整型數據的比較函數是:
    int compare_ints(void const *a, void const *b) {
       if( *(int *)a == *(int *)b )
          return 0;
       else
          return 1;
    }

    ②散轉程序。通過一個轉移表(函數指針數組)來實現。還是上面定義的四個四則運算函數,可以建立這样一個轉移表(注意初始化該轉移表的語句前面應有add等相應函數原型聲明或定義):
    double (*calculator[])(int, int) = {
       add, sub, mul, div
    };
    這样,calculator[0] == add, calculator[1] == sub, ...
    使用result = calculator[oper](x, y);就可以代替下面整個switch語句:
    switch( oper ) {
       case 0: result = add(x, y); break;
       case 1: result = sub(x, y); break;
       ...
    }

    ③C的面向對象化。一個對象包括數據和對數據的操作。C語言中的struct只有數據成員,因此要增加一些“偽數據成員”即函數指針來實現對數據的操作。例如:
    #ifndef C_Class
    #define
C_Class struct
    #endif
   
C_Class student{
       C_Class student *student_this
     char name;
       int height;
       int gender;
       int classnum;
       ...
       void (*Oper)( C_Class student *student_this );
     ...
    }

参考資料:
①《 Pointers on C 》,Kenneth A.Reek
②《 C程序設計(第二版)》,譚浩強
③《 C語言嵌入式系統編程修煉之道 》

errno與錯誤處理

errno是什麼?
在/usr/include/errno.h中,include了<sys/errno.h>,在該文件中定義了不同的errno的值(錯誤類型編號)所對應的宏以及錯誤類型.

基本使用:
#include <errno.h>
extern int errno;

1.使用perror( const char *msg )函數來將錯誤類型所對應的錯誤信息以字符串形式打印到終端.
首先輸出用戶自定義的字符串msg(可以为空,即""),然後打印錯誤信息.

2.使用stderr( int errnum )將錯誤信息轉換为字符串.

3.注意,必須在函數表明操作失敗後立刻對errno的值進行檢查以找出對應錯誤.在使用它之前必須總是先將其值copy到另外一個變量保存起來,因为很多函數(象fprintf之類)自身就可能會改變errno的值.
func( );
errortype = errno;
printf( "%d\n", errortype );
或者:
if( errortype == ... ) {
   do ...
}
else {
   do ....
}

       
From:OSChina  

arrow
arrow
    全站熱搜

    戮克 發表在 痞客邦 留言(0) 人氣()