c/c++常用的幾個關鍵字總結


volatile

volatile提醒編譯器它后面所定義的變量隨時都有可能改變,因此編譯后的程序每次需要存儲或讀取這個變量的時候,都會直接從變量地址中讀取數據。如果沒有volatile關鍵字,則編譯器可能優化讀取和存儲,可能暫時使用寄存器中的值,如果這個變量由別的程序更新了的話,將出現不一致的現象。下面舉例說明。在DSP開發中,經常需要等待某個事件的觸發,所以經常會寫出這樣的程序:
short flag;
void test()
{
do1();
while(flag==0);
do2();
}

    這段程序等待內存變量flag的值變為1(懷疑此處是0,有點疑問,)之后才運行do2()。變量flag的值由別的程序更改,這個程序可能是某個硬件中斷服務程序。例如:如果某個按鈕按下的話,就會對DSP產生中斷,在按鍵中斷程序中修改flag1,這樣上面的程序就能夠得以繼續運行。但是,編譯器並不知道flag的值會被別的程序修改,因此在它進行優化的時候,可能會把flag的值先讀入某個寄存器,然后等待那個寄存器變為1。如果不幸進行了這樣的優化,那么while循環就變成了死循環,因為寄存器的內容不可能被中斷服務程序修改。為了讓程序每次都讀取真正flag變量的值,就需要定義為如下形式:
volatile short flag;
    需要注意的是,沒有volatile也可能能正常運行,但是可能修改了編譯器的優化級別之后就又不能正常運行了。因此經常會出現debug版本正常,但是release版本卻不能正常的問題。所以為了安全起見,只要是等待別的程序修改某個變量的話,就加上volatile關鍵字。

 

const

const只是一個修飾符,不管怎么樣a仍然是一個int型的變量

const int a;

int const a;

const int *a;

int * const a;

int const * a const;

本質:const在誰后面誰就不可修改,const在最前面則將其后移一位即可,二者等效

前兩個的作用是一樣,a是一個常整型數。

第三個意味着a是一個指向常整型數的指針(也就是,指向的整型數是不可修改的,但指針可以,此最常見於函數的參數,當你只引用傳進來指針所指向的值時應該加上const修飾符,程序中修改編譯就不通過,可以減少程序的bug)。 

第四個意思a是一個指向整型數的常指針(也就是說,指針指向的整型數是可以修改的,但指針是不可修改的)。最后一個意味着a是一個指向常整型數的常指針(也就是說,指針指向的整型數是不可修改的,同時指針也是不可修改的)。

 

const關鍵字至少有下列n個作用:

1)欲阻止一個變量被改變,可以使用const關鍵字。在定義該const變量時,通常需要對它進行初始化,因為以后就沒有機會再去改變它了
2)對指針來說,可以指定指針本身為const,也可以指定指針所指的數據為const,或二者同時指定為const
3)在一個函數聲明中,const可以修飾形參,表明它是一個輸入參數,在函數內部不能改變其值;
4)對於類的成員函數,若指定其為const類型,則表明其是一個常函數,不能修改類的成員變量;
5)對於類的成員函數,有時候必須指定其返回值為const類型,以使得其返回值不為左值。例如:
const classA operator*(const classA& a1,const classA& a2); 
operator*的返回結果必須是一個const對象。如果不是,這樣的變態代碼也不會編譯出錯:
classA a, b, c;
(a * b) = c; // a*b的結果賦值 
  操作(a * b) = c顯然不符合編程者的初衷,也沒有任何意義。

 

三、restrict

我們希望某個對象(內存空間)不被修改的通常做法是什么?聲明該空間的const類型,但是這樣真的可以嗎?是不是的,由於const空間對象的指針是可以付給一個非const值指針的。所以這仍然無法不讓該空間被修改。

const int a=10;

int * b=&a;

雖然,編譯器會報警告“ 警告:初始化丟棄了指針目標類型的限定”,這個意思是,b失去了對目標對象的const的限定。但是通過,並且,可以通過指針b更改它們共同指向的空間。

 

const int a=10;

int b=(int)&a;

int * c=(int *)b;

這 種寫法和上面的效果一樣,卻連警告都不報,這就可以看到C語言類型轉換的功能有多大,並且有多危險。當我們將a的地址轉換成int類型的時候,編譯器不會 認為這是一個嚴重的事情(因為int類型並不能訪問內存空間),之后再從int類型轉換到int *類型,這個時候,編譯器也很難確定這是否會出現不安全的地方。所以,這樣一做,就騙過了C編譯器。而前面的方法中,編譯器明顯看到兩個指針之間的賦值初 始化,是有不安全的地方的(丟掉了const的對象設定),所以發出警告。

 

不 過,一句話,const是無法保證某個對象不被更改的。所以C99提出了一個restrict關鍵字。它是修飾指針的,表達出的意思是,對該指針指向的空 間的訪問,只能從這個指針進入。這樣,如果該關鍵字在加上restrict,就會達到鎖定該空間。當然,restrict不一定只是為了建立一個完全常態 的空間,它的本意是限制該空間只能通過該指針進行訪問。

 

restrict pointer是c99新標准提出的一個很著名的概念叫着——受限指針。這種指針的聲明是為了,編譯達到更好的優化,它暗示編譯器,某個指針指向的空間,只能從該指針訪問。但是這種暗示卻是對程序員的要求,而編譯器只是在這種暗示的基礎上作一些優化。

restrict只可以用在指針身上。如果一個指針被restrict修飾,那么就在它(這個指針)和它所指向的對象之間建立了一種特殊的聯系──只能用這個指針或者這個指針的表達式來訪問這個對象的值.
一 個指針指向一個內存地址。同一塊內存可以由多個指針來訪問並在程序運行時修改它(這塊內存)。restrict告訴編譯器,如果一塊由一個被 restrict修飾的指針所指向的內存被修改了,那么沒有其它的指針可以來訪問這塊內存。編譯器可能會選擇一種方式來優化代碼中調用被restrict 修飾的指針的部分,這可能導致錯誤發生。程序員有責任來確保正確地按照他們所設想的來使用被restrict修飾的指針,否則的話,可能會發生意想不到的 結果。

 

四、static

static關鍵字是C, C++中都存在的關鍵字。static從字面理解,是“靜態的“的意思,與此相對應的,應該是“動態的“。

static的作用主要有以下3個:

1、擴展生存期;

2、限制作用域;

3、唯一性;

 

1、擴展生存期

這一點主要是針對普通局部變量和static局部變量來說的。聲明為static的局部變量的生存期不再是當前作用域,而是整個程序的生存期。

在程序中,常用內存類型主要有堆、棧和靜態存儲區。要理解static局部變量就必須首先理解這三種內存類型。

 

在C/C++中, 局部變量按照存儲形式可分為三種auto, static, register

(譚浩強, 第174-175頁)

局部變量的默認類型都是auto,從棧中分配內存。

auto的含義是由程序自動控制變量的生存周期,通常指的就是變量在進入其作用域的時候被分配,離開其作用域的時候被釋放。

而static變量,不管是局部還是全局,都存放在靜態存儲區。

表 面意思就是不auto,變量在程序初始化時被分配,直到程序退出前才被釋放;也就是static是按照程序的生命周期來分配釋放變量的,而不是變量自己的 生命周期. 如果在main前設置斷點,然后查看static變量,已經被初始化,也就是說static在執行main函數前已經被初始化。也就是在程序初始化時被分 配。

 

--------------------------------------------------------------------------------------------------------------------------

堆:由程序員自己分配釋放(用malloc和free,或new和delete),如果我們不手動釋放,那就要到程序結束才釋放。如果對分配的空間在不用的時候不釋放而一味的分配,那么可能會引起內存泄漏,其容量取決於虛擬內存,較大。

棧:由編譯器自動分配釋放,其中存放在主調函數中被調函數的下一句代碼、函數參數和局部變量,容量有限,較小。

靜態存儲區:由在編譯時由編譯器分配,由系統釋放,其中存放的是全局變量、static變量和常量.

區別:

1) 堆是由低地址向高地址擴展,棧是由高地址向低地址擴展。

2) 堆是不連續的空間,棧是連續的空間。

3) 在申請空間后,棧的分配要比堆的快。對於堆,先遍歷存放空閑存儲地址的鏈表、修改鏈表、再進行分配;對於棧,只要剩下的可用空間足夠,就可分配到,如果不夠,那么就會報告棧溢出。

4) 棧的生命期最短,到函數調用結束時;靜態存儲區的生命期最長,到程序結束時;堆中的生命期是到被我們手動釋放時(如果整個過程中都不手動釋放,那就到程序結束時)。

--------------------------------------------------------------------------------------------------------------------------

 

2、限制作用域

這一點相對於普通全局變量和static全局變量來說的。

對於全局變量而言,不論是普通全局變量還是static全局變量,其存儲區都是靜態存儲區,因此在內存分配上沒有什么區別。

區別在於:

1) 普通的全局變量和函數,其作用域為整個程序或項目,外部文件(其它cpp文件)可以通過extern關鍵字訪問該變量和函數。一般不提倡這種用法,如果要在多個cpp文件間共享數據,應該將數據聲明為extern類型。

 

 在頭文件里聲明為extern:

extern int g_value; // 注意,不要初始化值!

然后在其中任何一個包含該頭文件的cpp中初始化(一次)就好:

int g_value = 0; // 初始化一樣不要extern修飾,因為extern也是聲明性關鍵字;

然后所有包含該頭文件的cpp文件都可以用g_value這個名字訪問相同的一個變量;

 

2) static全局變量和函數,其作用域為當前cpp文件,其它的cpp文件不能訪問該變量和函數。如果有兩個cpp文件聲明了同名的全局靜態變量,那么他們實際上是獨立的兩個變量。

 

static函數的好處是不同的人編寫不同的函數時,不用擔心自己定義的函數,是否會與其它文件中的函數同名。

 

頭文件中的static變量

如果在一個頭文件中聲明:

 static int g_vaule = 0;

 那么會為每個包含該頭文件的cpp都創建一個全局變量,但他們都是獨立的;所以也不建議這樣的寫法,一樣不明確需要怎樣使用這個變量,因為只是創建了一組同名而不同作用域的變量。

 

3、數據唯一性

這是C++對static關鍵字的重用。主要指靜態數據成員/成員函數。

表 示屬於一個類而不是屬於此類的任何特定對象的變量和函數. 這是與普通成員函數的最大區別, 也是其應用所在, 比如在對某一個類的對象進行計數時, 計數生成多少個類的實例, 就可以用到靜態數據成員. 在這里面, static既不是限定作用域的, 也不是擴展生存期的作用, 而是指示變量/函數在此類中的唯一性. 這也是”屬於一個類而不是屬於此類的任何特定對象的變量和函數”的含義. 因為它是對整個類來說是唯一的, 因此不可能屬於某一個實例對象的. (針對靜態數據成員而言, 成員函數不管是否是static, 在內存中只有一個副本, 普通成員函數調用時, 需要傳入this指針, static成員函數調用時, 沒有this指針. )

static數據成員的初始化:

(1) 初始化在類體外進行,而前面不加static,以免與一般靜態變量或對象相混淆。

(2) 初始化時不加該成員的訪問權限控制符private,public等。

(3) 初始化時使用作用域運算符來標明它所屬類,因此,靜態數據成員是類的成員,而不是對象的成員。

(4) 靜態數據成員是靜態存儲的,它是靜態生存期,必須對它進行初始化。

 

Static成員函數

靜態成員函數和靜態數據成員一樣,它們都屬於類的靜態成員,它們都不是對象成員。因此,對靜態成員的引用不需要用對象名。

靜態成員函數僅能訪問靜態的數據成員,不能訪問非靜態的數據成員,也不能訪問非靜態的成員函數,這是由於靜態的成員函數沒有this指針。

五、extern

基本解釋extern可以置於變量或者函數前,以標示變量或者函數的定義在別的文件中提示編譯器遇到此變量和函數時在其他模塊中尋找其定義。此外extern也可用來進行鏈接指定。

      也就是說extern有兩個作用,第一個,當它與"C"一起連用時,如: extern "C" void fun(int a, int b);則告訴編譯器在編譯fun這個函數名時按着C的規則去翻譯相應的函數名而不是C++的,C++的規則在翻譯這個函數名時會把fun這個名字變得面目全非,可能是fun@aBc_int_int#%$也可能是別的,這要看編譯器的"脾氣"(不同的編譯器采用的方法不一樣),為什么這么做呢,因為C++支持函數的重載啊,在這里不去過多的論述這個問題,如果你有興趣可以去網上搜索,相信你可以得到滿意的解釋!
    第二,當extern不與"C"在一起修飾變量或函數時,如在頭文件中:extern int g_Int; 它的作用就是聲明函數或全局變量的作用范圍的關鍵字,其聲明的函數和變量可以在本模塊活其他模塊中使用,記住它是一個聲明不是定義!也就是說B模塊(編譯單元)要是引用模塊(編譯單元)A中定義的全局變量或函數時,它只要包含A模塊的頭文件即可,在編譯階段,模塊B雖然找不到該函數或變量,但它不會報錯,它會在連接時從模塊A生成的目標代碼中找到此函數。

2 問題:extern 變量
  在一個源文件里定義了一個數組:chara[6];
  在另外一個文件里用下列語句進行了聲明:externchar *a
  請問,這樣可以嗎? 
  答案與分析:
1)、不可以,程序運行時會告訴你非法訪問。原因在於,指向類型T的指針並不等價於類型T的數組externchar *a聲明的是一個指針變量而不是字符數組,因此與實際的定義不同,從而造成運行時非法訪問。應該將聲明改為extern char a[ ]
2)、例子分析如下,如果a[] = "abcd",則外部變量a=0x61626364 (abcdASCII碼值)*a顯然沒有意義
  顯然a指向的空間(0x61626364)沒有意義,易出現非法內存訪問。
3)、這提示我們,在使用extern時候要嚴格對應聲明時的格式,在實際編程中,這樣的錯誤屢見不鮮。
4)extern用在變量聲明中常常有這樣一個作用,你在*.c文件中聲明了一個全局的變量,這個全局的變量如果要被引用,就放在*.h中並用extern來聲明

3 問題:當方面修改extern 函數原型
  當函數提供方單方面修改函數原型時,如果使用方不知情繼續沿用原來的extern申明,這樣編譯時編譯器不會報錯。但是在運行過程中,因為少了或者多了輸入參數,往往會照成系統錯誤,這種情況應該如何解決?
  答案與分析:
  目前業界針對這種情況的處理沒有一個很完美的方案,通常的做法是提供方在自己的xxx_pub.h中提供對外部接口的聲明,然后調用方include該頭文件,從而省去extern這一步。以避免這種錯誤。
  寶劍有雙鋒,對extern的應用,不同的場合應該選擇不同的做法。

4 問題:extern “C”
  在C++環境下使用C函數的時候,常常會出現編譯器無法找到obj模塊中的C函數定義,從而導致鏈接失敗的情況,應該如何解決這種情況呢?

  答案與分析:
C++語言在編譯的時候為了解決函數的多態問題,會將函數名和參數聯合起來生成一個中間的函數名稱,而C語言則不會,因此會造成鏈接時找不到對應函數的情況,此時C函數就需要用extern “C”進行鏈接指定,這告訴編譯器,請保持我的名稱,不要給我生成用於鏈接的中間函數名。
  下面是一個標准的寫法:
//.h文件的頭上
#ifdef __cplusplus
#if __cplusplus
extern "C"{
#endif
#endif /* __cplusplus*/ 


//.h文件結束的地方
#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif /* __cplusplus */ 

5 問題:extern 函數聲明
  常常見extern放在函數的前面成為函數聲明的一部分,那么,C語言的關鍵字extern在函數的聲明中起什么作用?
  答案與分析:
  如果函數的聲明中帶有關鍵字extern,僅僅是暗示這個函數可能在別的源文件里定義,沒有其它作用。即下述兩個函數聲明沒有明顯的區別:
extern int f(); intf();
  當然,這樣的用處還是有的,就是在程序中取代include“*.h”來聲明函數,在一些復雜的項目中,我比較習慣在所有的函數聲明前添加extern修飾。關於這樣做的原因和利弊可見下面的這個例子:extern修飾的全局變量

    (1) test1.h中有下列聲明:
    #ifndef TEST1H
    #define TEST1H
    extern char g_str[]; // 聲明全局變量g_str
    void fun1();
    #endif
    (2) test1.cpp
    #include "test1.h"
        char g_str[] = "123456"; // 定義全局變量g_str
        void fun1() { cout << g_str<< endl; }
    (3) 以上是test1模塊,它的編譯和連接都可以通過,如果我們還有test2模塊也想使用g_str,只需要在原文件中引用就可以了
    #include "test1.h"

     void fun2()    { cout<< g_str << endl;    }
    以上test1test2可以同時編譯連接通過,如果你感興趣的話可以用ultraEdit打開test1.obj,你可以在里面找到"123456"這個字符串,但是你卻不能在test2.obj里面找到,這是因為g_str是整個工程的全局變量,在內存中只存在一份,test2.obj這個編譯單元不需要再有一份了,不然會在連接時報告重復定義這個錯誤!
    (4) 有些人喜歡把全局變量的聲明和定義放在一起,這樣可以防止忘記了定義,如把上面test1.h改為
    extern char g_str[] = "123456"; // 這個時候相當於沒有extern
    然后把test1.cpp中的g_str的定義去掉,這個時候再編譯連接test1test2兩個模塊時,會報連接錯誤,這是因為你把全局變量g_str的定義放在了頭文件之后,test1.cpp這個模塊包含了test1.h所以定義了一次g_str,test2.cpp也包含了test1.h所以再一次定義了g_str,這個時候連接器在連接test1test2時發現兩個g_str。如果你非要把g_str的定義放在test1.h中的話,那么就把test2的代碼中#include "test1.h"去掉 換成:
    extern char g_str[];
    void fun2()   {  cout << g_str <<endl;   }
   這個時候編譯器就知道g_str是引自於外部的一個編譯模塊了,不會在本模塊中再重復定義一個出來,但是我想說這樣做非常糟糕,因為你由於無法在test2.cpp中使用#include "test1.h",那么test1.h中聲明的其他函數你也無法使用了,除非也用都用extern修飾,這樣的話你光聲明的函數就要一大串,而且頭文件的作用就是要給外部提供接口使用的,所以請記住, 只在頭文件中做聲明,真理總是這么簡單

 

六. extern static

 (1) extern 表明該變量在別的地方已經定義過了,在這里要使用那個變量.
 (2) static 表示靜態的變量,分配內存的時候, 存儲在靜態區,不存儲在棧上面.

    static 作用范圍是內部連接的關系, extern有點相反.它和對象本身是分開存儲的,extern也是分開存儲的,但是extern可以被其他的對象用extern 引用,static 不可以,只允許對象本身用它. 具體差別首先,staticextern是一對水火不容的家伙,也就是說externstatic不能同時修飾一個變量;其次,static修飾的全局變量聲明與定義同時進行,也就是說當你在頭文件中使用static聲明了全局變量后,它也同時被定義了;最后,static修飾全局變量的作用域只能是本身的編譯單元,也就是說它的全局只對本編譯單元有效,其他編譯單元則看不到它,:
    (1) test1.h:
    #ifndef TEST1H
    #define TEST1H
    static char g_str[] = "123456"; 
    void fun1();
    #endif

    (2) test1.cpp:
    #include "test1.h"
    void fun1()  {   cout << g_str <<endl;  }
    (3) test2.cpp
    #include "test1.h"
    void fun2()  {   cout << g_str <<endl;  }
    以上兩個編譯單元可以連接成功, 當你打開test1.obj時,你可以在它里面找到字符串"123456",同時你也可以在test2.obj中找到它們,它們之所以可以連接成功而沒有報重復定義的錯誤是因為雖然它們有相同的內容,但是存儲的物理地址並不一樣,就像是兩個不同變量賦了相同的值一樣,而這兩個變量分別作用於它們各自的編譯單元。也許你比較較真,自己偷偷的跟蹤調試上面的代碼,結果你發現兩個編譯單元(test1,test2)的g_str的內存地址相同,於是你下結論static修飾的變量也可以作用於其他模塊,但是我要告訴你,那是你的編譯器在欺騙你,大多數編譯器都對代碼都有優化功能,以達到生成的目標程序更節省內存,執行效率更高,當編譯器在連接各個編譯單元的時候,它會把相同內容的內存只拷貝一份,比如上面的"123456", 位於兩個編譯單元中的變量都是同樣的內容,那么在連接的時候它在內存中就只會存在一份了,如果你把上面的代碼改成下面的樣子,你馬上就可以拆穿編譯器的謊言:
    (1) test1.cpp:
    #include "test1.h"
    void fun1()
    {
        g_str[0] = ''a'';
        cout << g_str << endl;
    }

    (2) test2.cpp
    #include "test1.h"
    void fun2()  {  cout << g_str <<endl;  }
    (3) void main()     {
        fun1(); // a23456
        fun2(); // 123456
    }
    這個時候你在跟蹤代碼時,就會發現兩個編譯單元中的g_str地址並不相同,因為你在一處修改了它,所以編譯器被強行的恢復內存的原貌,在內存中存在了兩份拷貝給兩個模塊中的變量使用。正是因為static有以上的特性,所以一般定義static全局變量時,都把它放在原文件中而不是頭文件,這樣就不會給其他模塊造成不必要的信息污染,同樣記住這個原則吧!

 

七. extern const

   C++const修飾的全局常量據有跟static相同的特性,即它們只能作用於本編譯模塊中,但是const可以與extern連用來聲明該常量可以作用於其他編譯模塊中, extern const char g_str[];
    然后在原文件中別忘了定義:     const char g_str[] ="123456"; 

    所以當const單獨使用時它就與static相同,而當與extern一起合作的時候,它的特性就跟extern的一樣了!所以對const我沒有什么可以過多的描述,我只是想提醒你,const char* g_str = "123456" const char g_str[]="123465"是不同的,前面那個const 修飾的是char *而不是g_str,它的g_str並不是常量,它被看做是一個定義了的全局變量(可以被其他編譯單元使用),所以如果你像讓char*g_str遵守const的全局常量的規則,最好這么定義const char* constg_str="123456".


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM