【C語言】10.static&extern,typedef,宏,條件編譯,const

  • 全局變量:

    • 程序一啟動就會分配空間,直到程序結束。
    • 存儲位置在靜態存儲區
    • 多個同名的全局變量指向同一塊存儲空間。
    • 全局變量默認為0
    • 分類:
      • 內部變量:只能在這個文件內部訪問(加上static關鍵字)
      • 外部變量:可以在其他文件中訪問的變量,默認所有全局變量都是外部變量。(什么都不加或是有extern聲明)
  • static:

    • static對局部變量的作用:

      • 延長局部變量的生命周期,從程序啟動到程序退出,但是它并沒有改變變量的作用域。也就是說,雖然保證它一直存在,但是該不能用的時候還是不能用。
      • 定義變量的代碼在整個程序運行期間僅僅會執行一次。注意,是定義變量的代碼,也就是說只會定義一次。
    • static對全局變量的作用:

      • 由于靜態全局變量的作用域局限于一個源文件內,只能為該源文件內的函數公用,因此可以 避免在其它源文件中引起錯誤。

      ?

  • extern:

    • 如果聲明的時候沒有寫extern,那系統會自動定義這個變量,并將其初始化為0。
    • 如果聲明的時候寫extern了,那系統不會自動定義這個變量。
    • 一般時候用extern來聲明變量是其他文件的外部變量。(?)
  • static 與 extern對函數的作用:

    • C語言默認所有函數都是外部函數,可以被其他文件訪問;而內部函數只能在本文件中訪問。

    • 同樣地,加上static關鍵字可以聲明和定義一個函數變為內部函數,extern則可以聲明和定義一個外部函數。

    • 例:

      // 聲明一個內部函數
      static int sum(int num1,int num2);
      
      // 定義一個內部函數
      static int sum(int num1,int num2)
      {
          return num1 + num2;
      }
      
      // 聲明一個外部函數
      extern int sum(int num1,int num2);
      
      // 定義一個外部函數
      extern int sum(int num1,int num2)
      {
          return num1 + num2;
      }
      

      ?

  • typedef:

    • 宏定義也可以完成typedef的工作,但是宏定義是由預處理完成的,而typedef則是在編譯時完成的,后者更為靈活方便且不易出錯。

    • 例:

      typedef int INTEGER;
      
      typedef Integer MyInteger;
      
      typedef char NAME[20]; // 表示NAME是字符數組類型,數組長度為20。然后可用NAME 說明變量。
      NAME a; // 等價于 char a[20];
      
      typedef char * String;
      String myString = "hello";
      

      與結構體的聲明定義一樣,在對結構體使用typedef時,也有三種形式:

      struct Person{
          int age;
          char *name;
      };
      
      typedef struct Person PersonType; // PersonType person; person.age = ...
      
      /////////////////////////////////////////////////////////////////////
      
      typedef struct Person{
          int age;
          char *name;
      } PersonType;
      
      注意這個和結構體在定義的時候直接初始化一個值的區別,這個是由typedef關鍵字的,且是別名,而不是一個變量。
      
      /////////////////////////////////////////////////////////////////////
      
      typedef struct {
          int age;
          char *name;
      } PersonType;
      
      省略結構體名。
      
      /////////////////////////////////////////////////////////////////////
      
      // typedef和指向結構體的指針
      // 首先定義一個結構體并起別名 
        typedef struct { float x; float y; } Point;
      // 起別名
        typedef Point *PP;
      

      與枚舉的聲明定義一樣,在對枚舉使用typedef時,也有三種形式:

      enum Sex{
          SexMan,
          SexWoman,
          SexOther
      };
      typedef enum Sex SexType;
      
      /////////////////////////////////////////////////////////////////////
      
      typedef enum Sex{
          SexMan,
          SexWoman,
          SexOther
      } SexType;
      
      /////////////////////////////////////////////////////////////////////
      
      typedef enum{
          SexMan,
          SexWoman,
          SexOther
      } SexType;
      

      typedef和函數指針:(重要!)

      int add(int a, int b) {
          return a + b;
      }
      
      int main() {
          typedef int (*myFunction) (int, int); 
        //注意此時myFunction就是int (*myFunction) (int, int)的別名。表示這是一種指向函數的指針的類型。
          myFunction p = add; 
          p();
          return 0;
      }
      

      ?

      ?

    • 不帶參數的宏定義:

      • 格式: #define 標示符 字符串(“字符串”可以是常數、表達式、格式串等。)
      • 宏名的有效范圍是從定義位置到文件結束。如果需要終止宏定義的作用域,可以用#undef命令。
    • 帶參數的宏定義:

      • 對帶參數的宏,在調用中,不僅要宏展開,而且要用實參去代換形參。

      • #define 宏名(形參表) 字符串

      • #define average(a, b) (a+b)/2

      • 宏名和參數列表之間不能有空格,否則空格后面的所有字符串都作為替換的字符串。

      • 帶參數的宏在展開時,只作簡單的字符和參數的替換,不進行任何計算操作。所以在定義宏時,一般用一個小括號括住字符串的參數。并且計算結果最好也括起來防止錯誤。

        #define Pow(a) ( (a) * (a) )
        
  • 條件編譯

    • 在很多情況下,我們希望程序的其中一部分代碼只有在滿足一定條件時才進行編譯,否則不參與編譯(只有參與編譯的代碼最終才能被執行),這就是條件編譯。換句話說,在條件編譯中,不滿足條件的會直接被刪去,并不會參與編譯。
    • 條件編譯和宏定義經常一起使用。
#define SCORE 67
#if SCORE > 90
    printf("優秀\n");
#else
    printf("不及格\n");
#endif

// 條件編譯后面的條件表達式中不能識別變量,它里面只能識別常量和宏定義

#if 條件1
  ...code1...
 #elif 條件2
  ...code2...
 #else
  ...code3...
 #endif

#define SCORE 67
#if SCORE > 90
    printf("優秀\n");
#elif SCORE > 60
    printf("良好\n");
#else
    printf("不及格\n");
#endif

  • 注意,條件一般是判斷宏定義而不是判斷變量,因為條件編譯是在編譯之前做的判斷,宏定義也是編譯之前定義的,而變量是在運行時才產生的。
  • 一定不要忘記在最后加上 #endif !
  • ifndef 條件編譯指令 與 上面用法類似,不過是條件相反的。
  • 使用條件編譯指令調試 bug

    #define DEBUG1 0
    #if DEBUG1 == 0 //format是格式控制,##表示可以沒有參數,__VA_ARGS__表示變量 #define Log(format,...) printf(format,## __VA_ARGS__)
    #else
    #define Log(format,...)
    #endif
    
    void test(){
        Log("xxxxx");
    }
    int main(int argc, const char * argv[]) {
        Log("%d\n",10);
        return 0;
    }
    

    這種場景用Log代替printf,在調試的時候我們只需要改變宏定義的值,就可以靈活的控制輸出的語句。比如把DEBUG1設置為0,變為調試狀態,那么所有的printf就會替代Log輸出一些信息;把DEBUG1設置為1,則關閉調試狀態,則會將Log代替為空,則不會有輸出。

    這里,‘...’指可變參數。這類宏在被調用時,##表示成零個或多個參數,包括里面的逗號,一直到到右括弧結束為止。當被調用時,在宏體(macro body)中,那些符號序列集合將代替里面 的VA_ARGS標識符。

  • 或者用條件指令在調試階段設置一些默認值,比如賬號密碼,等結束調試只需要改變宏命令就可以關閉這個功能。

  • 在C語言中防止重復導入頭文件的問題:

    #ifndef __xxxxxx__x__
    #define __xxxxxx__x__
    
    #include <stdio.h>
    
    #endif /* defined(__xxxxxx__x__) */
    

    比如這是xxxxx里面的x.h文件,當別的文件導入這個文件的時候(#include),相當于復制這個文件里的內容。如果導入了多個文件不小心重復導入了同一個,那么像上面這樣寫是沒有問題的。因為預處理指令會首先檢查是否定義了

    __xxxxxx__x__
    

    如果沒有定義,那么就定義,并且導入stdio.h文件。

    那么如果下次遇到(這時候都是在預編譯的時候完成的),它仍然檢查是否定義了

    __xxxxxx__x__
    

    這次因為剛才我們已經定義了,那么它就會直接結束這個指令,所以不會重復導入相同的頭文件。

  • 如果出現重復導入循環(比如a.h文件里導入了b.h,而b.h文件里導入了a.h),解決方法:

    如果a.h聲明了一個add方法,b.h里面聲明了一個minus方法,但是此時重復引用循環,編譯器會報錯,那么可以在b.h里面不再導入a.h就不會出現循環導入的問題。但是我又想在b.h里面訪問a.h里面的add方法,但是此時b.h里面并沒有導入a.h,那么我們就直接將add方法的聲明復制到b.h里面即可。

  • const

    const的作用主要是兩個:

    1. 節省空間,避免不必要的內存分配。(Swift中建議使用let也是這個原因?)

      #define PI 3.14159 //常量宏
      const doulbe Pi=3.14159; //此時并未將Pi放入ROM中 ...... double i=Pi; //此時為Pi分配內存,以后不再分配!
      double I=PI; //編譯期間進行宏替換,分配內存
      double j=Pi; //沒有內存分配
      double J=PI; //再進行宏替換,又一次分配內存! const定義常量從匯編的角度來看,只是給出了對應的內存地址,而不是象#define一樣給出的是立即數,所以,const定義的常量在程序運行過程中只有一份拷貝,而#define定義的常量在內存中有若干個拷貝。
      

      編譯器通常不為普通const常量分配存儲空間,而是將它們保存在符號表中,這使得它成為一個編譯期間的常量,沒有了存儲與讀內存的操作,使得它的效率也很高。

    2. 更加安全。

    使用:

    1. 在變量名前或變量名后。

      int const x = 2;

      const int x = 2;

    2. 注意用const修飾指針:

      • const int *A; //const修飾指針,A可變,A指向的值不能被修改

      • int const *A; //const修飾指向的對象,A可變,A指向的對象不可變

      • int *const A; //const修飾指針A, A不可變,A指向的對象可變

      • const int *const A;//指針A和A指向的對象都不可變

        ?

      先看“*”的位置

      如果const 在 *的左側 表示值不能修改,但是指向可以改。

      如果const 在 *的右側 表示指向不能改,但是值可以改

      如果在“*”的兩側都有const 標識指向和值都不能改。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容