一、宏定義:主要是一些文法問題和技巧
例如:
#define FIND(s,e) (size_t)&(((struct s*)(0))->e)//求結構體內的變數相對於結構體的位移量
#define SECONDS_PER_YEAR (360*24*60*60)UL//求一年中的秒數
#define MIN(a,b) (((a)<=(b))?(a):(b))//求最小值
#define SWAP(A,B) {A=A^B;B=A^B;A=A^B;} //注意是{ }不是( )
說明:儘可能考慮移植性,由於代碼可能在16位機,也有可能在32位機器上運行,所以採用size_t和UL都是基於移植性的考慮。
二、const用法:定義常量,修飾指標、函數的輸入參數和傳回值,簡單說const表示唯讀意思,本質上來說它只是在全域資料段或者棧中定義的是一個唯讀常量,不是真正位於字串常量區。Const的目的是為了產生高品質的代碼,提高代碼的可讀性,同時保護好不能被任意改變的記憶體,從而降低Bug產生的機率。
const int a = 10;
const int b;//錯誤,常量必須初始化
int a = 10,b = 9;
const int *p1 = &a;//指標指向的內容唯讀,不能通過該指標去寫
*p1 = 11;//錯誤
int * const p2 = &a;//指標本身唯讀,指標初始化到一個對象後,將不能被修改
p2 = &b;//錯誤
const int *p3 const = &a;//指標本身和指向的內容都是唯讀
const char *fp1(void) //修飾傳回值,表示返回的指標指向內容唯讀
{
char *p = "dddd";
return p;
}
void fp1(const char *str)
{
*str = 4; //錯誤
const char *p = str;//p必須為const,才能接受str
}
int _tmain(int argc, _TCHAR* argv[])
{
const char *d = fp1();
printf("%s",d);
}
三、extern用法:在別的檔案中定義的變數,要想在本檔案中使用,必須先用extern聲明,例如:extern a;之後就當成在本檔案中定義的變數一樣使用。
四、static用法:
1.修飾變數,從生存域和訪問域兩個方面說明,無論static變數定義在函數內或外,該變數都位於資料區段中;定義於函數體外的static變數的訪問域僅僅是它所在檔案中定義的函數,其他檔案無法通過extern對其聲明後訪問。
2.修飾函數,使得函數的訪問域僅僅為其所定義的檔案可見。
3.類中變數用static修飾表示變數是類變數,類中函數用static修飾表示函數只能訪問類中的static變數,不接受this指標,稱為類函數。
例如:
//file1
void fstatic(void);
static void fstatic(void)
{
return;
}
//file2
void fstatic(void);
Main()
{
fstatic();//聲明仍然無法調用
}
總之,static實現了c語言的封裝性,一定程度上實現了資訊的封裝和隱藏。
五、sizeof用法:sizeof是一個運算子,編譯器在編譯時間就能確定其的運算結果,其運算對象可以是內部資料類型名、struct類型名、class類型名、陣列變數和其他類型的變數,只有計算對象是陣列變數時,能得出數組實際的長度,其他類型的變數作為運算對象時,都將退化為對該對象的資料類型的運算。另外,在計算struct和class等使用者自訂資料類型的大小時,需要考慮編譯器會對其做最佳化處理,根據一定的原則調整,即會自動對齊,sizeof的結果往往會大於各個元素長度的和。
例一:
short l;//sizeof(l)==2
char *a = "abcddd";//sizeof(a)==4
void *s = malloc(100);//sizeof(s)==4
char b[] = "abcddd";//sizeof(b)==7*1
float c[20];//sizeof(c)==20*4;
例二:
struct A
{
short a;
short b;
short c;
};//sizeof(A)==6
struct B
{
int a;
char b;
short c;
};//sizeof(A)==8
struct C
{
double a1;
long a;
int a2;
char b;
};//sizeof(B)==24
#pragma pack(1)//使用pack指令強制編譯器不做最佳化對其,這種方式一般用於這個
struct D//結構體要直接寫入檔案或者直接用於網路收發的情況下
{
double a1;
long a;
int a2;
char b;
};
#pragma pack()//sizeof(B)==17
說明:例二中示範了資料對其的情況,由於CPU訪問資料的特點是一次訪問多個位元組,故如果多位元組資料的首地址是2的整數倍的話,將可以一次記憶體訪問即可取得其所對應的所有資料,所以一個最佳化要求就是變數的地址是其資料類型長度的整數倍,例如int a的a的地址要求是4的整數倍。
針對結構體,如果結構體中所有元素類型相同,作為數組處理,入struct A;否則一般都是其最常元素的整數倍,例如struct B和C。這樣處理的目的是考慮定義結構體數組的情況,例如struct B b[10];那麼這10個結構體變數如果每個長度都是8,將能保證每個變數的第一個元素都能是4的整數倍,否則將達不到這個效果,不能實現高效的記憶體訪問,故編譯器將對不符合要求的結構體自動調整資料對齊。
最後需要交代的是,class中的static變數不被分配到棧上,所以不能計入sizeof中,空類的長度是1,有虛函數的長度為4,因為包含一個指向函數表的指標。
下面分析幾個面試題:
例一:
int fp1(char var[])//傳回值為4,因為var作為參數傳遞時,已經退化為普通指標,
{//故其大小就是指標的大小,不作為數組名處理,無法計算出數組長度。
return sizeof(var);
}
例二:
//str1是一個數組名,該數組是一個指標數組,長度為3,故sizeof(str1)為3*4
void fp1(void)
{
char *str1[] = {"hello","mico","china"};
for(int i=0;i < sizeof(str1)/sizeof(char *);i++)
{
printf("%s",str1[i]);
}
}
六、volatile的用法:該詞的意思是”易變的”,用於修飾變數的一個關鍵字,表示該變數在很多地方都能被改變,會被意象不到的改變,編譯器不能對其最佳化,往往用於多任務系統或嵌入式系統中,情況一:嵌入式系統中的很多外設寄存器的值會即時改變,如:#define PTA *(volatile unsigned char *)(0x00000001);情況二:嵌入式系統記憶體中的某些變數有可能被中斷程式修改;情況三:多任務系統中的共用變數可能隨時改變。滿足這些特點之一的變數必須要用volatile修飾,保證編譯器不能對其最佳化處理,如果被最佳化,往往程式的執行結果出錯。
例一:
int a = 10;
if(a == 10)
{
a = 11;
}
這短代碼正常被最佳化為
a = 11; 但是,如果volatile int a;則這段代碼將不被最佳化。
例二:
//原始代碼:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
//編譯器最佳化完的代碼:
int square(volatile int *ptr)
{
int a = *ptr;
int b = *ptr;
return a*b;
}
//正確代碼:由於*ptr的值隨時可能改變,故最佳化代碼中的a和b的值可能不一樣,故計
int square(volatile int *ptr) //算的結果可能有誤
{
int a = *ptr;
return a*a;
}