一. const
1. 定義:
變量聲明中帶有關(guān)鍵詞const,意味著不能通過賦值,增量或減量來修改該變量的值,這是顯而易見的一點。
指針使用const則要稍微復(fù)雜點,因為不得不把 讓指針本身成為const 和 指針指向的值成為cons t區(qū)別開來:
下面的聲明表示pf指向的值必須是不變的,而pf則是可變的,它可以指向另外一個const或非const值
const float *pf;
相反,下面的聲明說明pf是不能改變的,而pf所指向的值則是可以改變的:
float* const pf;
最后,當然可以有既不能改變指針的值也不能改變指針指向的值的值的聲明方式:
const float * const pf;
需要注意的是,還有第三種放置const關(guān)鍵字的方法:
float const * pf; //等價于constfloat * pf;
總結(jié)就是:一個位于*左邊任意位置的const使得數(shù)據(jù)成為常量,而一個位于*右邊的const使得指針本身成為const
const 與 define的區(qū)別:
盡量用const和inline而不用#define 這個條款最好稱為:“盡量用編譯器而不用預(yù)處理”,因為#define經(jīng)常被認為好象不是語言本身的一部分。
(1)define是預(yù)處理宏,在編譯的預(yù)處理階段起作用,進行替換,而const是在 編譯、運行的時候起作用,const修飾的只讀變量是在編譯的時候確定其值
(2)#define宏沒有類型,而const修飾的只讀變量具有特定的類型,define只是簡單的字符串替換,沒有類型檢查。而const有對應(yīng)的數(shù)據(jù)類型,是要進行判斷的
(3)存儲方式不同 define僅僅是一個宏,那里使用,就在那里展開,不占用內(nèi)存。 const 常量會在內(nèi)存中分配內(nèi)存
const 優(yōu)點:
(1)const常量可以進行類型檢查,避免一些低級的錯誤 (2)在我們調(diào)試的時候,const常量可以進行調(diào)試的,但是define是不能進行調(diào)試的,因為在預(yù)編譯階段就已經(jīng)替換掉了
2. const在全局數(shù)據(jù)中的使用:
使用全局變量被認為是一個冒險的方法,它使得數(shù)據(jù)在程序的任何部分都可以被錯誤地修改,如果數(shù)據(jù)是const,那么這種擔心就是多余的了不是嘛?因此對全局數(shù)據(jù)使用const是合理的。
然而,在文件之間共享const數(shù)據(jù)要格外小心,有兩個策略可以使用:
1)一個是遵循外部變量的慣用規(guī)則,在一個文件進行定義聲明,在其他文件進行引用聲明(使用關(guān)鍵字extern)。
/*file1.c------定義一些全局常量*/
const double PI = 3.14159;
/*file2.c-----是用在其他文件中定義的全局變量*/
extern const dounle PI;
2)另外一個方法是把全局變量放在一個include文件里,這時候需要格外注意的是必須使用靜態(tài)外部存儲類
/*constant.h----定義一些全局常量*/
static const double PI = 3.14159;
/*file1.c-----使用其他文件定義的全局變量*/
#include”constant.h”。
/*file2.c-----使用其他文件定義的全局變量*/
#include”constant.h”
如果不使用關(guān)鍵字static,在文件file1.c和file2.c中包含constant.h將導(dǎo)致每個文件都有同一標識符的定義聲明,ANSI標準不支持這樣做(有些編譯器確實支持)。通過使用static, 實際上給了每個文件一個獨立的數(shù)據(jù)拷貝,如果文件想使用該數(shù)據(jù)與另外一個文件通話,這樣做就不行了,因為每個文件只能看見他自己的拷貝,然而由于數(shù)據(jù)是不 可變的,這就不是問題了。使用頭文件的好處是不必惦記在一個文件中進行定義聲明,在另一個文件中進行引用聲明,缺點在于復(fù)制了數(shù)據(jù),如果常量很大的話,這 就是個問題了。
3. const的常規(guī)用法1)修飾局部變量
const int n=5;int const n=5;這兩種寫法是一樣的,都是表示變量n的值不能被改變了,需要注意的是,用const修飾變量時,一定要給變量初始化,否則之后就不能再進行賦值了。
接下來看看const用于修飾常量靜態(tài)字符串,例如:
const char* str="fdsafdsa";如果沒有const的修飾,我們可能會在后面有意無意的寫str[4]='x'這樣的語句,這樣會導(dǎo)致對只讀內(nèi)存區(qū)域的賦值,然后程序會立刻異常終止。有了const,這個錯誤就能在程序被編譯的時候就立即檢查出來,這就是const的好處。讓邏輯錯誤在編譯期被發(fā)現(xiàn)。
2)常量指針與指針常量
常量指針是指針指向的內(nèi)容是常量,可以有一下兩種定義方式。
const int * n;int const * n;需要注意的是一下兩點:
(1)常量指針說的是不能通過這個指針改變變量的值,但是還是可以通過其他的引用來改變變量的值的。
int a=5;const int* n=&a;a=6;(2)常量指針指向的值不能改變,但是這并不是意味著指針本身不能改變,常量指針可以指向其他的地址。
int a=5;int b=6;const int* n=&a;n=&b;指針常量是指指針本身是個常量,不能在指向其他的地址,寫法如下:
int *const n;需要注意的是,指針常量指向的地址不能改變,但是地址中保存的數(shù)值是可以改變的,可以通過其他指向改地址的指針來修改。
int a=5;int *p=&a;int* const n=&a;*p=8;區(qū)分常量指針和指針常量的關(guān)鍵就在于星號的位置,我們以星號為分界線,如果const在星號的左邊,則為常量指針,如果const在星號的右邊則為指針常量。如果我們將星號讀作‘指針',將const讀作‘常量'的話,內(nèi)容正好符合。int const * n;是常量指針,int *const n;是指針常量。
指向常量的常指針
是以上兩種的結(jié)合,指針指向的位置不能改變并且也不能通過這個指針改變變量的值,但是依然可以通過其他的普通指針改變變量的值。
const int* const p;3)修飾函數(shù)的參數(shù)
根據(jù)常量指針與指針常量,const修飾函數(shù)的參數(shù)也是分為三種情況
(1)防止修改指針指向的內(nèi)容
void StringCopy(char *strDestination, const char *strSource);其中 strSource 是輸入?yún)?shù),strDestination 是輸出參數(shù)。給 strSource 加上 const 修飾后,如果函數(shù)體內(nèi)的語句試圖改動 strSource 的內(nèi)容,編譯器將指出錯誤。
(2)防止修改指針指向的地址
void swap ( int * const p1 , int * const p2 )指針p1和指針p2指向的地址都不能修改。
(3)以上兩種的結(jié)合。
4)修飾函數(shù)的返回值
如果給以“指針傳遞”方式的函數(shù)返回值加 const 修飾,那么函數(shù)返回值(即指針)的內(nèi)容不能被修改,該返回值只能被賦給加const 修飾的同類型指針。 例如函數(shù)
const char * GetString(void);如下語句將出現(xiàn)編譯錯誤:
char *str = GetString();正確的用法是
const char *str = GetString();4)修飾全局變量
全局變量的作用域是整個文件,我們應(yīng)該盡量避免使用全局變量,以為一旦有一個函數(shù)改變了全局變量的值,它也會影響到其他引用這個變量的函數(shù),導(dǎo)致除了bug后很難發(fā)現(xiàn),如果一定要用全局變量,我們應(yīng)該盡量的使用const修飾符進行修飾,這樣方式不必要的意外修改,使用的方法與局部變量是相同的。
以上就是const關(guān)鍵字的常見用法
二 volatile限定詞volatile告訴編譯器,該變量除了可被程序改變意外還可以被其他代理改變。典型的它用于硬件地址和其他并行運行的程序共享的數(shù)據(jù)。例如,一個地址中可能保存著當前的時鐘信息。不管程序做些什么,該地址會隨時間改變。另一種情況是一個地址用來接收來自其他計算機的信息;
語法同const:
volatile int a;//a是一個易變的位置
volatile int * pf;//pf指向一個易變的位置
把volatile作為一個關(guān)鍵字的原因是它可以方便編譯器優(yōu)化。
假如有如下代碼:
va= x;
//一些不使用x的代碼
vb= x;
一個聰明的編譯器可能注意到你兩次使用了x,但是沒有改變它的值,它將把x臨時存貯在一個寄存器中,接著,當vb需要x的時候,它從寄存器而非初始的內(nèi)存位置得到x的值來節(jié)省時間。這個過程被稱為緩存。通常緩存是一個好的優(yōu)化方式,但是如果兩個語句中間的其他代理改變了x的值的話就不是這樣了。如果沒有規(guī)定volatile關(guān)鍵字,編譯器將無從得知這種改變是否可能發(fā)生,因此,為了安全起見,編譯器不使用緩存。那是在ANSI以前的情形,現(xiàn)在,如果在聲明中沒有使用volatile關(guān)鍵字,編譯器就可以假定一個值在使用過程中沒有修改,它就可以試著優(yōu)化代碼。總而言之,volatile使得每次讀取數(shù)據(jù)都是直接在內(nèi)存讀取而不是緩存。
你可能會覺得奇怪,const和volatile可以同時使用,但是確實可以。例如硬件時鐘一般不能由程序改變,這使得他成為const,但他被程序以外的代理改變,這使得他成為volatile,所以你可以同時使用它們,順序是不重要的:
const volatile time;
volatile表明某個變量的值可能在外部被改變,優(yōu)化器在用到這個變量時必須每次都小心地重新讀取這個變量的值,而不是使用保存在寄存器里的備份。它可以適用于基礎(chǔ)類 型如:int,char,long......也適用于C的結(jié)構(gòu)和C++的類。當對結(jié)構(gòu)或者類對象使用volatile修飾的時候,結(jié)構(gòu)或者類的所有成員 都會被視為volatile.
該關(guān)鍵字在多線程環(huán)境下經(jīng)常使用,因為在編寫多線程的程序時,同一個變量可能被多個線程修改,而程序通過該變量同步各個線程。
簡單示例:
復(fù)制代碼代碼如下:DWord __stdcall threadFunc(LPVOID signal){int* intSignal=reinterdivt_cast(signal);*intSignal=2;while(*intSignal!=1)sleep(1000);return 0;}該線程啟動時將intSignal 置為2,然后循環(huán)等待直到intSignal 為1 時退出。顯然intSignal的值必須在外部被改變,否則該線程不會退出。但是實際運行的時候該線程卻不會退出,即使在外部將它的值改為1,看一下對應(yīng)的偽匯編代碼就明白了:mov ax,signallabel:if(ax!=1)goto label
對于C編譯器來說,它并不知道這個值會被其他線程修改。自然就把它cache在寄存器里面。C 編譯器是沒有線程概念的,這時候就需要用到volatile。volatile 的本意是指:這個值可能會在當前線程外部被改變。也就是說,我們要在threadFunc中的intSignal前面加上volatile關(guān)鍵字,這時 候,編譯器知道該變量的值會在外部改變,因此每次訪問該變量時會重新讀取,所作的循環(huán)變?yōu)槿缦旅鎮(zhèn)未a所示:label:mov ax,signal //重新從內(nèi)存獲取變量值,而不是從緩存if(ax!=1)goto label
注意:一個參數(shù)既可以是const同時是volatile,是volatile因為它可能被意想不到地改變。它是const因為程序不應(yīng)該試圖去修改它。
三 restrict
關(guān)鍵字restrict通過允許編譯器優(yōu)化某幾種代碼增強了計算支持。記住,它只能用于指針,并且表明指針是訪問一個數(shù)據(jù)對象的唯一且初始的方式。為了清楚為何這樣做,我們需要看一些例子:
復(fù)制代碼代碼如下:intar[10];int* restrict restar = (int*)malloc(10*sizeof(int));
int* par = ar;
這里,指針restar是訪問malloc分配的內(nèi)存的唯一而且初始的方式,因此聲明為restrict。然而,par指針既不是初始的,也不是訪問數(shù)組ar中數(shù)據(jù)的唯一方式,所以不用restrict限定詞?,F(xiàn)在考慮下面這個更加復(fù)雜的例子,其中n是一個int復(fù)制代碼代碼如下:for(n= 0;n < 10;n++){
par[n]+= 5;
restar[n]+= 5;
ar[n]*= 2;
par[n]+= 3;
restar[n]+= 3;
}
知道了restar是訪問它所指向的數(shù)據(jù)的唯一初始方式,編譯器就可以用具有同樣效果的一條語句來替代包含restar的兩個語句restar[n]+= 8;/*可以替換*/
然而將兩個計算par的語句精簡為一個則會導(dǎo)致錯誤因為在par兩次訪問數(shù)據(jù)之間,ar改變了該數(shù)據(jù)的值。沒有關(guān)鍵字restrict,編譯器將不得不設(shè)想比較糟糕的那一種形式,而使用之后,編譯器可以放心大膽的尋找計算的捷徑??梢詫㈥P(guān)鍵字作為指針型函數(shù)參量的限定詞使用,這意味著編譯器可以假定在函數(shù)體內(nèi)沒有其他標志符修改指針指向的數(shù)據(jù),因而可以試著優(yōu)化代碼,反之不然。來看一下C99標準下C庫中的兩個函數(shù),他們從一個位置把字節(jié)復(fù)制到另一個位置
void*memcpy(void* restrict s1,const void* restrict s2,size_t n);
void*memmove(void* s1,const void * s2,size_t);
memcpy要求兩個指針的位置不能重疊,但memmove沒有這個要求。把s1,s2聲明為restrict意味著每個指針都是相應(yīng)數(shù)據(jù)的唯一訪問方式,因此他們不能訪問同一數(shù)據(jù)塊。這滿足了不能有重疊的要求。
關(guān)鍵字restrict有兩個讀者:編譯器,它告訴編譯器可以自由地做一些優(yōu)化的假定。另一個讀者是用戶,他告訴用戶僅使用滿足restrict要求的參數(shù)。一般,編譯器沒法檢查你是否遵循了這一限制,如果你蔑視它,也就是讓自己冒險。
參考文檔:
http://www.jb51.net/article/70831.htm
http://www.jb51.net/article/42348.htm
新聞熱點
疑難解答