const關鍵字,很多人想到的可能是const常量,其實關鍵字const並不能把變量變成常量!在一個符號前加上const限定符只是表示這個符號 不能被賦值。也就是它的值對於這個符號來說是只讀的,但它並不能防止通過程序的內部(甚至是外部)的方法來修改這個值(C專家編程.p21)。也就是說 const變量是只讀變量,既然是變量那麼就可以取得其地址,然後修改其值。看來const也是防君子不防小人啊!:)
const 使用情況分類詳析
1、const的普通用法
const int n = 10;
意思很明顯,n是一個只讀變量,程序不可以直接修改其值。這裡還有一個問題需要注意,即如下使用:int a[n];在ANSI C中,這種寫法是錯誤的,因為數組的大小應該是個常量,而n只是一個變量。
2、const用於指針
const int *p;
int const *p;
int * const p;
在最後的一種情況下,指針是只讀的(即p只讀),而在另外兩種情況下,指針所指向的對象是只讀的(即*p只讀)。const 是一個左結合的類型修飾符,它與其左側的類型修飾符一起為一個類型修飾符,所以,int const 限定 *p,不限定p。int *const 限定p,不限定*p。這裡有一個簡便的區分方法:
沿著*號劃一條線,如果const位於*的左側,則const就是用來修飾指針所指向的變量,即指針指向為常量;如果const位於*的右側,const就是修飾指針本身,即指針本身是常量。
3、const用於函數的地址傳遞參數
void foo(const int *p)
這種形式通常用於在數組形式的參數中模擬傳值調用。也就是相當於函數調用者聲稱:"我給你一個指向它的指針,但你不能去修改它。"如果函數編寫者遵循了這個約定,那麼就相當於模擬了值傳遞。這也是const最有用之處了:用來限定函數的形參,這樣該函數將不會修改實參指針所指的數據。這裡注意了,是函數不應該去修改而不是不能修改,也就是說const不能阻止參數的修改(原因見上)。
4、const用於限定函數的返回值
const int foo();
const struct mytype foo();
上述寫法限定函數的返回值不可被更新,當函數返回內部的類型時,已經是一個數值,當然不可被賦值更新,所以,此時const無意義,最好去掉,以免困惑。當函數返回自定義的類型時,這個類型仍然包含可以被賦值的變量成員,所以,此時有意義。
一、宏定義:主要是一些語法問題和技巧
例如:
#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))//求最小值
說明:盡可能考慮移植性,由於代碼可能在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作為參數傳遞時,已經退化為普通指針,
{//故其大小就是指針的大小,不作為數組名處理,無法計算出數組長度。
returnsizeof(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;
static有兩種用途:一是修飾變量,二是修飾函數
第一:修飾變量
例子:
static int a;
void Func()
{
static int b;
}
全局變量默認的存儲類型是extern,若不加static修飾,在不同頭文件中定義名字相同的全局變量會發生沖突。static修飾符是一個能夠減少這類命名沖突的有用工具。例如,以下聲明語句
static int a;
其含義與下面的語句相同:
int a;
只不過,a的作用域限制在一個源文件內,對於其他源文件,a是不可見的。因此,如果若干個函數需要共享一組外部對象,可以將這些函數放到一個源文件中,把它們需要用到的對象也都在同一個源文件中以static修飾符聲明。
變量b是個局部變量,在程序退出函數Func後,b就不能被使用。但是當程序再次進入函數Func時,變量b保持上次運行後的值。
第二:修飾函數
static修飾符不僅適用於變量,也適用於函數。一個函數,其默認存儲狀態也是extern。如果函數f需要調用另一個函數g,而且只有函數f需要調用函數g,我們可以把函數f與函數g都放到同一個源文件中,並且聲明函數g為static:
static int
g(int x)
{
// g函數體
}
void f()
{
// 其他內容
b=g (a);
}
我們可以在多個源文件中定義同名的函數g,只要所有的函數g都被定義為static,或者僅僅只有其中一個函數g不是static。因此,為了避免可能出現的命名沖突,如果一個函數僅僅被同一個源文件中的其他函數調用,我們就應該聲明該函數為static。
如果說得規范一點,C/C++沒有全局變量,只有外部和自動。C/C++裡的全局變量是靠你自己做出來的。
所有在某函數裡定義的變量全是自動變量。默認是auto。在所有函數外定義的是外部變量。
如果你的程序放在單個源文件裡,那它就是全局變量。如果有多個源文件,你在其中一個文件裡定義那個變量,再在所有其它文件裡用extern說明它。它也成了全局變量。
注意:這裡很仔細地使用了“定義”和“說明”。前者引起存儲分配,是真正產生那個變量。後者只是讓變量在那個文件裡也可見(可以訪問)。
如果在定義外部變量時,加上指示符static,則生成的是靜態外部變量。這種變量的作用域僅限於那個文件。也就是說,不能再在其它文件裡用extern說明它。
如果在定義自動變量時,加上指示符static,則生成的是靜態自動變量。作用域沒變,仍然是所在的語句塊。但是生命期卻變成永久。