一關鍵字:
1. static:
第一個作用:修飾變量。變量又分為局部和全局變量,但它們都存在內存的靜態區。由於被 static修飾的變量總是存在內存的靜態區,所以即使這個函數運行結束,這個靜態變量的值還是不會被銷毀,函數下次使用時仍然能用到這個值。
static int j;
void fun1(void)
{
static int i = 0;
i++;
}
void fun2(void)
{
j=0;
j++;
}
int main()
{
for(k=0; k<10; k++)
{
fun1();
fun2();
}
return 0;
第二個作用:修飾函數。函數前加 static使得函數成為靜態函數。但此處“static”的含義不是指存儲方式,而是指對函數的作用域僅局限於本文件(所以又稱內部函數)。使用內部函數的好處是:不同的人編寫不同的函數時,不用擔心自己定義的函數,是否會與其它文件中的函數同名。
2 .sizeof
sizeof是關鍵字不是函數
3. signed、unsigned關鍵字
4. float變量與“零值”進行比較
5.if else語句
先處理正常情況,再處理異常情況。
6.循環語句風格
在多重循環中,如果有可能,應當將最長的循環放在最內層,最短的循環放
在最外層,以減少 CPU 跨切循環層的次數。
7. void
void不能代表一個真實的變量
void a; //錯誤
function(void a); //錯誤
8.const
const修飾的只讀變量必須在定義的同時初始化,編譯器通常不為普通 const只讀變量分配存儲空間,而是將它們保存在符號表中,這使得它成為一個編譯期間的值,沒有了存儲與讀內存的操作,使得它的效率也很高。
a)修飾指針
const int *p; // p 可變,p指向的對象不可變
int const *p; // p可變,p指向的對象不可變
int*const p; //p不可變,p指向的對象可變
const int *const p; //指針 p和 p指向的對象都不可變
b)修飾函數參數
const修飾符也可以修飾函數的參數,當不希望這個參數值被函數體內意外改變時使
用。例如:
void Fun(const int i);
9.extern
extern就相當於他們的這些區別於中國人的特性。extern可以置於變量或者函數前,以
標示變量或者函數的定義在別的文件中。
10.struct
空結構體的大小就定位 1個byte。struct的成員默認情況下屬性是 public的,而class成員卻是 private的。
11.union
union維護足夠的空間來置放多個數據成員中的“一種”,而不是為每一個數據成員配置空間,在union中所有的數據成員共用一個空間,同一時間只能儲存其中一個數據成員,所有的數據成員具有相同的起始地址。例子如下:
union StateMachine
{
char character;
int number;
char *str;
double exp;
};
一個 union只配置一個足夠大的空間以來容納最大長度的數據成員.
12 typedef
二預處理
1.#if
#if 的一般含義是如果#if後面的常量表達式為true,則編譯它與#endif之間的代碼,否則跳過這些代碼。命令#endif 標識一個#if塊的結束。#else命令的功能有點象 C語言中的 else ,#else建立另一選擇(在#if 失敗的情況下)。#elif命令意義與 else if 相同,它形成一個 if else-if階梯狀語句,可進行多種編譯選擇。
2.#define
#define會原樣替換,要搞定它其實很簡單,別吝啬括號就行了。
例子:
定義一個宏函數,求 x 的平方:
#define SQR (x) x * x
試試:假設 x 的值為10,SQR (x)被替換後變成 10*10。沒有問題。
再試試:假設 x 的值是個表達式10+1,SQR (x)被替換後變成10+1*10+1。問題
這並不是我想要得到的。怎麼辦?括號括起來不就完了?
#define SQR (x) ((x)*(x))
三指針和數組
注意!!!:指針的偏移值是起始地址加上指針指向對象的size。
1.數組名a作為左值和右值的區別
數組名不能作為左值,對指針進行加 1操作,得到的是下一個元素的地址,而不是原有地址值直接加 1。所以,一個類型為 T的指針的移動,以 sizeof(T) 為移動單位。 main()
{
int a[5]={1,2,3,4,5};
int *ptr=(int *)(&a+1);
printf("%d,%d",*(a+1),*(ptr-1));
}
&a + 1: 取數組 a的首地址,該地址的值加上 sizeof(a) 的值,即 &a + 5*sizeof(int),也
就是下一個數組的首地址,顯然當前指針已經越過了數組的界限。(int *)(&a+1):則是把上一步計算出來的地址,強制轉換為 int * 類型,賦值給 ptr。*(a+1): a,&a的值是一樣的,但意思不一樣,a是數組首元素的首地址,也就是 a[0]的首地址,&a是數組的首地址,a+1是數組下一元素的首地址,即a[1]的首地址,&a+1是下一個數組的首地址。所以輸出 2*(ptr-1): 因為 ptr 是指向 a[5],並且 ptr是 int * 類型,所以 *(ptr-1)是指向 a[4] ,輸出 5。
2.數組和指針的關系
文件 1中定義如下:
char a[100];
文件 2 中聲明如下:
extern char *a;
這裡,文件1 中定義了數組a,文件2中聲明它為指針。這有什麼問題嗎?平時不是總說數組與指針相似,甚至可以通用嗎?但是,很不幸,這是錯誤的。通過上面的分析我們也能明白一些,數組就是數組,指針就是指針,它們是完全不同的兩碼事!他們之間沒有任何關系,只是經常穿著相似的衣服來迷惑你罷了。下面就來分析分析這個問題:
定義和聲明之間的區別,定義分配的內存,而聲明沒有。定義只能出現一次,而聲明可以出現多次。這裡extern告訴編譯器 a這個名字已經在別的文件中被定義了,下面的代碼使用的名字 a是別的文件定義的。再回顧到前面對於左值和右值的討論,我們知道如果編譯器需要某個地址(可能還需要加上偏移量)來執行某種操作的話,它就可以直接通過開鎖動作(使用“*”這把鑰匙)來讀或者寫這個地址上的內存,並不需要先去找到儲存這個地址的地方。相反,對於指針而言,必須先去找到儲存這個地址的地方,取出這個地址值然後對這個地址進行開鎖(使用“*”這把鑰匙)。如下圖:
這就是為什麼 extern char a[]與 extern char a[100]等價的原因。因為這只是聲明,不分配空間,所以編譯器無需知道這個數組有多少個元素。這兩個聲明都告訴編譯器 a是在別的文件中被定義的一個數組,a 同時代表著數組 a的首元素的首地址,也就是這塊內存的起始地址。數組內地任何元素的的地址都只需要知道這個地址就可以計算出來。
但是,當你聲明為 extern char *a時,編譯器理所當然的認為 a是一個指針變量,在 32位系統下,占 4個byte。這 4個byte裡保存了一個地址,這個地址上存的是字符類型數據。雖然在文件 1中,編譯器知道a 是一個數組,但是在文件 2中,編譯器並不知道這點。大多數編譯器是按文件分別編譯的,編譯器只按照本文件中聲明的類型來處理。所以,雖然a實際大小為 100個byte,但是在文件 2中,編譯器認為a 只占4個byte。
我們說過,編譯器會把存在指針變量中的任何數據當作地址來處理。所以,如果需要
訪問這些字符類型數據,我們必須先從指針變量 a中取出其保存的地址。如下圖:
顯然,按照上面的分析,我們把文件 1中定義的數組在文件2 中聲明為指針會發生錯誤。同樣的,如果在文件 1中定義為指針,而在文件中聲明為數組也會發生錯誤:
文件 1
char *p = “abcdefg”;
文件 2
extern char p[];
在文件 1 中,編譯器分配4個byte空間,並命名為 p。同時p裡保存了字符串常量“abcdefg”的首字符的首地址。這個字符串常量本身保存在內存的靜態區,其內容不可更改。在文件 2中,編譯器認為 p是一個數組,其大小為 4 個byte,數組內保存的是 char類型的數據。在文件 2中使用p 的過程如下圖:
3. 數組參數與指針參數
無法向函數傳遞一個數組,C語言中,當一維數組作為函數參數的時候,編譯器總是把它解析成一個指向其首元素首地址的指針。