-
全局變量:
- 程序一啟動就會分配空間,直到程序結束。
- 存儲位置在靜態存儲區。
- 多個同名的全局變量指向同一塊存儲空間。
- 全局變量默認為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的作用主要是兩個:
-
節省空間,避免不必要的內存分配。(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常量分配存儲空間,而是將它們保存在符號表中,這使得它成為一個編譯期間的常量,沒有了存儲與讀內存的操作,使得它的效率也很高。
更加安全。
使用:
-
在變量名前或變量名后。
int const x = 2;
const int x = 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 標識指向和值都不能改。
-