嵌入式編程中關於const,static,extern,volatile的用法

一.const的用法:
爲什麼使用const?
採用符號常量寫出的代碼更容易維護;指針常常是邊讀邊移動,而不是邊寫邊移動;許多函數參數是隻讀不寫的。const最常見用途是作爲數組的界和switch分情況標號(也可以用枚舉符代替)

用法1:常量
   取代了C中的宏定義,聲明時必須進行初始化。const限制了常量的使用方式,並沒有描述常量應該如何分配。如果編譯器知道了某const的所有使用,它甚至可以不爲該const分配空間。最簡單的常見情況就是常量的值在編譯時已知,而且不需要分配存儲。―《C++ Program Language》
   用const聲明的變量雖然增加了分配空間,但是可以保證類型安全。C標準中,const定義的常量是全局的,C++中視聲明位置而定。

用法2:指針和常量
   使用指針時涉及到兩個對象:該指針本身和被它所指的對象。將一個指針的聲明用const“預先固定”將使那個對象而不是使這個指針成爲常量。要將指針本身而不是被指對象聲明爲常量,必須使用聲明運算符*const所以出現在 * 之前的const是作爲基礎類型的一部分:
char *const cp; //到char的const指針

(後兩個聲明是等同的)

char const *pc1; //到const char的指針
const char *pc2; //到const char的指針
從右向左讀的記憶方式:
cp is a const pointer to char.
pc2 is a pointer to const char.

用法3:const修飾函數傳入參數


    將函數傳入參數聲明爲const,以指明使用這種參數僅僅是爲了效率的原因,而不是想讓調用函數能夠修改對象的值。同理,將指針參數聲明爲const,函數將不修改由這個參數所指的對象。
    通常修飾指針參數和引用參數
void Fun(const A *in); //修飾指針型傳入參數
void Fun(const A &in); //修飾引用型傳入參數

//注意引用的概念,我得C學得不夠好!icon


用法4:修飾函數返回值

   可以阻止用戶修改返回值。返回值也要相應的付給一個常量或常指針。



用法5:const修飾成員函數

//這個地方也理解不好啊!icon

const對象只能訪問const成員函數,而非const對象可以訪問任意的成員函數,包括const成員函數;
const對象的成員是不能修改的,而通過指針維護的對象確實可以修改的;
const成員函數不可以修改對象的數據,不管對象是否具有const性質。編譯時以是否修改成員數據爲依據進行檢查。


      2.static的用法:

全局靜態變量怎麼用??

所謂的全局靜態變量只在當前程序中有效。比如在a.c中定義了
static int i;
則只有a.c中的函數可以訪問i,其它程序模塊的無法直接訪問這個變量,但可以通過a.c中所定義的函數間接訪問。比如在a.c中定義兩個函數:
int get_i()
{
return i;
};

int set_i( int n )
{
i=n;
}

以此獲得及重新設置i的值。這在c環境下是一種較好的模擬C++風格的實現方法。因爲你可以將a.c看作一個類,i看作a的成員,而get_i()和set_i()則看作這個類的成員函數。

靜態變量作用範圍在一個文件內,程序開始時分配空間,結束時釋放空間,默認初始化爲0,使用時可以改變其值。
靜態變量或靜態函數只有本文件內的代碼才能訪問它,它的名字在其它文件中不可見。
用法1:函數內部聲明的static變量,可作爲對象間的一種通信機制
如果一局部變量被聲明爲static,那麼將只有唯一的一個靜態分配的對象,它被用於在該函數的所有調用中表示這個變量。這個對象將只在執行線程第一次到達它的定義使初始化。
用法2:局部靜態對象
對於局部靜態對象,構造函數是在控制線程第一次通過該對象的定義時調用。在程序結束時,局部靜態對象的析構函數將按照他們被構造的相反順序逐一調用,沒有規定確切時間。
用法3:靜態成員和靜態成員函數
如果一個變量是類的一部分,但卻不是該類的各個對象的一部分,它就被成爲是一個static靜態成員。一個static成員只有唯一的一份副本,而不像常規的非static成員那樣在每個對象裏各有一份副本。同理,一個需要訪問類成員,而不需要針對特定對象去調用的函數,也被稱爲一個static成員函數。
類的靜態成員函數只能訪問類的靜態成員(變量或函數)。

       
3.extern的用法:
extern可以聲明其他文件內定義的變量。在一個程序裏,一個對象只能定義一次,它可以有多個聲明,但類型必須完全一樣。如果定義在全局作用域或者名字空間作用域裏某一個變量沒有初始化,它會被按照默認方式初始化。
將變量或函數聲明成外部鏈接,即該變量或函數名在其它函數中可見。被其修飾的變量(外部變量)是靜態分配空間的,即程序開始時分配,結束時釋放。
在C++中,還可以指定使用另一語言鏈接,需要與特定的轉換符一起使用。
extern “C” 聲明語句
extern “C” { 聲明語句塊 }

        4.volatile的用法:
     類型修正符(type-modifier),限定一個對象可被外部進程(操作系統、硬件或併發進程等)改變。volatile與變量連用,可以讓變量被不同的線程訪問和修改。聲明時語法:int volatile vInt;

    除了基本類型外,對用戶定義類型也可以用volatile類型進行修飾。
注意:可以把一個非volatile int賦給volatile int,但是不能把非volatile對象賦給一個volatile對象。
一個有volatile標識符的類只能訪問它接口的子集,一個由類的實現者控制的子集。用戶只能用const_cast來獲得對類型接口的完全訪問。此外,volatile向const一樣會從類傳遞到它的成員。

       volatile的本意是“易變的”

       由於訪問寄存器的速度要快過RAM,所以編譯器一般都會作減少存取外部RAM的優化。比如:

bit bFlag="0";

int main(void)
{
...
while (1)
{
if (bFlag) dosomething();
}
}

/* 中斷程序*/
void ISR(void)
{
bFlag=1;
}

程序的本意是希望ISR中斷產生時,在main當中調用dosomething函數,但是,由於編譯器判
斷在main函數裏面沒有修改過bFlag,因此
可能只執行一次對從bFlag到某寄存器的讀操作,然後每次if判斷都只使用這個寄存器裏面
的“bFlag副本”,導致dosomething永遠也不會被調用。如果將將變量加上volatile修飾,
則編譯器保證對此變量的讀寫操作都不會被優化(肯定執行)。此例中bFlag也應該如此說
明。

        volatile是一個限定符,也稱爲keyword或描述符,"volatile 關鍵字指示字段可由操作系統、硬件或併發執行的線程在程序中進行修改。"

當要求使用volatile 聲明的變量的值的時候,系統總是重新從它所在的內存讀取數據,即使它前面的指令剛剛從該處讀取過數據。而且讀取的數據立刻被保存。


一般說來,volatile用在如下的幾個地方:

1、中斷服務程序中修改的供其它程序檢測的變量需要加volatile;

2、多任務環境下各任務間共享的標誌應該加volatile;

3、存儲器映射的硬件寄存器通常也要加volatile說明,因爲每次對它的讀寫都可能由不同意義;


聲明方式爲  volatile declaration


備註
系統總是在 volatile 對象被請求的那一刻讀取其當前值,即使上一條指令從同一對象請求值。而且,該對象的值在賦值時立即寫入。

volatile 修飾符通常用於由多個線程訪問而不使用 lock 語句來序列化訪問的字段。使用 volatile 修飾符能夠確保一個線程檢索由另一線程寫入的最新值。

      另外,以上這幾種情況經常還要同時考慮數據的完整性(相互關聯的幾個標誌讀了一半被打斷了重寫),在1中可以通過關中斷來實現,2中可以禁止任務調度,3中則只能依靠硬件的良好設計了。

     volatile 的含義
     volatile總是與優化有關,編譯器有一種技術叫做數據流分析,分析程序中的變量在哪裏賦值、在哪裏使用、在哪裏失效,分析結果可以用於常量合併,常量傳播等優化,進一步可以死代碼消除。但有時這些優化不是程序所需要的,這時可以用volatile關鍵字禁止做這些優化,volatile的字面含義是易變的,它有下面的作用:

     1 不會在兩個***作之間把volatile變量緩存在寄存器中。在多任務、中斷、甚至setjmp環境下,變量可能被其他的程序改變,編譯器 自己無法知道,volatile就是告訴編譯器這種情況。

     2 不做常量合併、常量傳播等優化,所以像下面的代碼:
        volatile int i = 1;
        if (i > 0) ...

        if的條件不會當作無條件真。

     3 對volatile變量的讀寫不會被優化掉。如果你對一個變量賦值但後面沒用到,編譯器常常可以省略那個賦值***作,然而對Memory Mapped IO的處理是不能這樣優化的。

     一個網友說:volatile的意思是什麼?
很多時候,全局變量不一定是全局的,在多線程環境下可能產生微妙的錯誤,很有可能編譯器爲了優化,而把一個全局變量放入寄存器裏。volatile修飾符就是明確告訴編譯器,你他媽不準把這個變量優化到寄存器上,只能放內存裏。

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

const應用:
一、對於基本聲明
    const int r=100;//標準const變量聲明加初始化,編譯器經過類型檢查後直接用100在編譯時替換。
二、對於指針
    1. int x=10; const int *r=&x; //指針指向的內容是常量,r指向的內容不能夠通過r改變,但如果是非const,內容可以通過自己改變,而且r指針可以改變,可以指向其它的整形.

    //*r=*r+1;NO //x++;YES //r=&y;YES
    2. int const *r=&x; 與1完全相同
    3. int * const r=&x; //指針指向是常量,不能修改去指向其它內容,但指向的內容可以修改
     //r=&y;NO //*r=*r+1;YES //x++;YES
    4.const int * const r=&x; //綜合1、3用法,r是一個指向常量的常量型指針,指針指向不能改變,指針內容不能改變,內容可以自身改變

    //r=&y;NO //*r=*r+1;NO //x++;YES

三、對於類型檢查
    可以把非const對象賦予const指針,這樣就不能改變.但是不能把const賦給非const,除非先強制轉換
const int x=100; int *p=(int*)&x; *p++;
四、對於函數
    1.void Fuction1(const int r); //此處爲參數傳遞const值,意義是變量初值不能被函數改變
    2.const int Fuction1 (int); //此處返回const值,意思指返回的原函數裏的變量的初值不能被修改,但是函數按值返回的這個變量被製成副本,能不能被修改就沒有了意義,它可以被賦給任何的const或非const類型變量,完全不需要加上這個const關鍵字。
    3.Class CX; //內部有構造函數,聲明如CX(int r =0)
      CX Fuction1 () { return CX(); }
      const CX Fuction2 () { return CX(); }
      Fuction1() = CX(1); //沒有問題,可以作爲左值調用
      Fuction2() = CX(1); //編譯錯誤,const返回值禁止作爲左值調用。
    4.函數中指針的const傳遞和返回:
    int F1 (const char *pstr); //作爲傳遞的時候使用const修飾可以保證不會通過這個指針來修改傳遞參數的初值
    const char *F2();//意義是函數返回的指針指向的對象是一個const對象,它必須賦給一個同樣是指向const對象的指針
    const char * const F3(); //比上面多了一個const,這個const的意義只是在他被用作左值時有效,它表明了這個指針除了指向const對象外,它本身也不能被修改,所以就不能當作左值來處理。

五、對於類
    1.首先,對於const的成員變量,只能在構造函數裏使用初始化成員列表來初始化,試圖在構造函數體內進行初始化const成員變量會引起編譯錯誤。初始化成員列表形如:
   X:: X ( int ir ): r(ir) {} //假設r是類X的const成員變量
      注意:類的構造和析構函數都不能是const函數。
    2.建立了一個const成員函數,但仍然想用這個函數改變對象內部的數據。(函數不能修改類的數據成員)
//假如有一個叫做X的類,它有一個int成員變量r,我們需要通過一個const成員函數f( )來對這個r進行++r操作,代碼如下
void X::f( ) const
{ (const_cast(this)) -> ++r; } //通過this指針進行類型強制轉換實現


--------------------------------STATIC--------------------------------
對於一個完整的程序,內存中的分佈情況:
      ==========
      |      代碼區    |
      ------------------
      | 全局數據區 |
      ------------------
      |       堆區       |
      -----------------
      |        棧區      |
      ==========
    一般程序的由new產生的動態數據存放在堆區,函數內部的自動變量存放在棧區,全局變量和static變量放在全局數據區

static的作用主要有以下3個:
    1、擴展生存期;
    2、限制作用域;
    3、唯一性

STATIC:

一、面向過程設計中的static
    1、[靜態全局變量] //在全局變量前,加上關鍵字static,該變量就被定義成爲一個靜態全局變量。

     靜態全局變量有以下特點:
        1)該變量在全局數據區分配內存;
        2)未經初始化的靜態全局變量會被程序自動初始化爲0(自動變量的值是隨機的,除非它被顯式初始化);
        3)靜態全局變量在聲明它的整個文件都是可見的,而在文件之外(extern)是不可見的; 
定義全局變量就可以實現變量在文件中的共享,但定義靜態全局變量還有以下好處:
       1)靜態全局變量不能被其它文件所用;
       2)其它文件中可以定義相同名字的變量,不會發生衝突;

    2、[靜態局部變量] 在局部變量前,加上關鍵字static,該變量就被定義成爲一個靜態局部變量。

    通常,在函數體內定義了一個變量,每當程序運行到該語句時都會給該局部變量分配棧內存。但隨着程序退出函數體,系統就會收回棧內存,局部變量也相應失效。但有時候我們需要在兩次調用之間對變量的值進行保存。通常的想法是定義一個全局變量來實現。但這樣一來,變量已經不再屬於函數本身了,不再僅受函數的控制,給程序的維護帶來不便。
靜態局部變量正好可以解決這個問題。靜態局部變量保存在全局數據區,而不是保存在棧中,每次的值保持到下一次調用,直到下次賦新值。
    靜態局部變量有以下特點:
      1)該變量在全局數據區分配內存;
      2)靜態局部變量在程序執行到該對象的聲明處時被首次初始化,即以後的函數調用不再進行初始化;
      3)靜態局部變量一般在聲明處初始化,如果沒有顯式初始化,會被程序自動初始化爲0;
      4)它始終駐留在全局數據區,直到程序運行結束。但其作用域爲局部作用域,當定義它的函數或語句塊結束時,其作      用域隨之結束;

    3、靜態函數
    在函數的返回類型前加上static關鍵字,函數即被定義爲靜態函數。靜態函數與普通函數不同,它只能在聲明它的文件當中可見,不能被其它文件使用。
   定義靜態函數的好處:
      1)靜態函數不能被其它文件所用;
      2)其它文件中可以定義相同名字的函數,不會發生衝突;

二、面向對象的static關鍵字(類中的static關鍵字)
1、靜態數據成員
    在類內數據成員的聲明前加上關鍵字static,該數據成員就是類內的靜態數據成員。
   靜態數據成員有以下特點:
      1)而靜態數據成員被當作是類的成員。無論這個類的對象被定義了多少個,靜態數 據成員在程序中也只有一份拷貝,由該類型的所有對象共享訪問。
      2)靜態數據成員存儲在全局數據區,屬於本類的所有對象共享,所以,它不屬於特定的類對象,在沒有產生類對象時其作用域就可見,即在沒有產生類的實例時,我們就可以操作它;
同全局變量相比,使用靜態數據成員有兩個優勢:
     1)靜態數據成員沒有進入程序的全局名字空間,因此不存在與程序中其它全局名字衝突的可能性;
     2)可以實現[信息隱藏]。靜態數據成員可以是private成員,而全局變量不能;

2、靜態成員函數
    它爲類的全部服務而不是爲某一個類的具體對象服務。與普通函數相比,靜態成員函數由於不是與任何的 對象相聯繫,因此它不具有this指針。從這個意義上講,它無法訪問屬於類對象的非靜態數據成員,也無法訪問非靜態成員函數,它只能調用其餘的靜態成員函數。
關於靜態成員函數,可以總結爲以下幾點:
    1)出現在類體外的函數定義不能指定關鍵字static;
    2)靜態成員之間可以相互訪問,包括靜態成員函數訪問靜態數據成員和訪問靜態成員函數;
    3)非靜態成員函數可以任意地訪問靜態成員函數和靜態數據成員;
    4)靜態成員函數不能訪問非靜態成員函數和非靜態數據成員

-----------------------------------EXTERN----------------------------
EXTERN
1 基本解釋
    extern可以置於變量或者函數前,以標示變量或者函數的定義在別的文件中,提示編譯器遇到此變量和函數時在其他模塊中尋找其定義。通過這種行爲它告訴編譯器:該變量/函數的定義已經存在在某個地方了,讓編譯器到其他的模塊去尋找它的定義。
    另外,extern也可用來進行鏈接指定。

2. extern   “C”  
使用extern“C”主要是因爲C++語言在編譯的時候爲了實現多態,會將函數名和函數結合起來形成另外一種函數名(總之就是說編譯後的函數名與你之前自己聲明時的函數名會不一樣),而C語言中無多態的概念當然也就不會有這種奇異的名字變化問題。這是問題就出現了,當你要在C++中調用C函數時,由於名字的不同,所以它會找不到所調用的這個函數的定義,因而會出錯。  
爲了解決這一C與C++的矛盾衝突,就有了extern "C'。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章