static void和void區別(轉)

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了,在該文件中定義了不一樣的errno的值(錯誤類型編號)所對應的宏以及錯誤類型.
  
  基本使用:
  #include 
  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 ....
  }程序員

相關文章
相關標籤/搜索