程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> const,static,extern用法總結,constextern

const,static,extern用法總結,constextern

編輯:C++入門知識

const,static,extern用法總結,constextern


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返回值禁止作為左值調用。    // Function1 和Function2返回的是外部的臨時變量,其中Function2返回的這個臨時變量是const類型的,不能被修

//改,一旦該條語句執行完畢,臨時變量自動銷毀了

 

4.函數中指針的const傳遞和返回:    

int F1 (const char *pstr); //作為傳遞的時候使用const修飾可以保證不會通過這個指針來修改傳遞參數的初值     // 表明pstr指向的內容不能被修改,而不是單單pstr[0]不能被修改,如:


int Test( const char* pCh )
{
 //pCh[3] = 'd'; 編譯不過,被const修飾了
 return 1;
}

 

const char *F2(); //意義是函數返回的指針指向的對象是一個const對象,它必須賦給一個同樣是指向const對象的指針  // 由於返回的是const char*,即返回的是不能被修改的字符串,所以必須須賦給一個同樣是指向const對象的指針

const char * const F3(); //比上面多了一個const,這個const的意義只是在他被用作左值時有效,它表明了這個指針除了指向const對象外,它本身也不能被修改,所以就不能當作左值來處理。  // 這裡同上面的const int * const r=&x;類似

 

五、對於類     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'。

 

第一種場景 -- extern

在該博克的公告中看到一句話挺好的,也一並轉載了:如果你有一個蘋果,我有一個蘋果,我們交換以後還是一人一個蘋果,但如果你有一種思想,我有一種思想,我們交換以後,每個人便擁有了兩種思想.

extern關鍵字的作用是聲明變量和函數為外部鏈接,即該變量或函數名在其它文件中可見。用其聲明的變量或函數應該在別的文件或同一文件的其它地方定義。

例如語句:extern int a;      僅僅是一個變量的聲明,其並不是在定義變量a,並未為a分配內存空間。變量a在所有模塊中作為一種全局變量只能被定義一次,否則會出現連接錯誤。      通常,在模塊的頭文件中對本模塊提供給其它模塊引用的函數和全局變量以關鍵字extern聲明。例如,如果模塊B欲引用該模塊A中定義的全局變量和函數時 只需包含模塊A的頭文件即可。這樣,模塊B中調用模塊A中的函數時,在編譯階段,模塊B雖然找不到該函數,但是並不會報錯;它會在連接階段中從模塊A編譯 生成的目標代碼中找到此函數。 如果一個工程包含如下兩個文件: 1.cpp如下:                            2.cpp如下: int x,y;                                extern int x,y; extern void PrintHello();               void PrintHello() void Func1()                             { {                                           cout<<"hello!"<<endl;      x=123;                               } }                                        void Func2() int main()                              { {                                           y=x*10;       PrintHello();                        }           ......                               }  

    在2.cpp中使用extern int x,y;只是聲明了x,y這兩個變量,它告訴編譯器其後的變量已經在別的文件中說明,不再為它們分配內存。當兩個文件都編譯成為.obj後,連接時所有的外部變量和函數都得到統一,可以共享各自定義的全局變量和函數。//這個例子很不錯,淺顯易懂。

 

轉載地址:http://www.cnblogs.com/crhacker/archive/2006/06/09/421669.html

第二種場景 -- extern "C"

(轉)C++中extern “C”含義深層探索 1.引言

  C++語言的創建初衷是“a better C”,但是這並不意味著C++中類似C語言的全局變量和函數所采用的編譯和連接方式與C語言完全相同。作為一種欲與C兼容的語言,C++保留了一部分過程 式語言的特點(被世人稱為“不徹底地面向對象”),因而它可以定義不屬於任何類的全局變量和函數。但是,C++畢竟是一種面向對象的程序設計語言,為了支 持函數的重載,C++對全局函數的處理方式與C有明顯的不同。   2.從標准頭文件說起

  某企業曾經給出如下的一道面試題:

  面試題   為什麼標准頭文件都有類似以下的結構?

 

#ifndef __INCvxWorksh

#define __INCvxWorksh

#ifdef __cplusplus

extern "C" { #endif /*...*/ #ifdef __cplusplus }

#endif

#endif

/* __INCvxWorksh */

  分析   顯然,頭文件中的編譯宏“#ifndef __INCvxWorksh、#define __INCvxWorksh、#endif” 的作用是防止該頭文件被重復引用。

  那麼

#ifdef __cplusplus extern "C" { #endif #ifdef __cplusplus } #endif

  的作用又是什麼呢?我們將在下文一一道來。   3.深層揭密extern "C"

  extern "C" 包含雙重含義,從字面上即可得到:首先,被它修飾的目標是“extern”的;其次,被它修飾的目標是“C”的。讓我們來詳細解讀這兩重含義。

  被extern "C"限定的函數或變量是extern類型的;

  extern是C/C++語言中表明函數和全局變量作用范圍(可見性)的關鍵字,該關鍵字告訴編譯器,其聲明的函數和變量可以在本模塊或其它模塊中使用。記住,下列語句:

  extern int a;

  僅僅是一個變量的聲明,其並不是在定義變量a,並未為a分配內存空間。變量a在所有模塊中作為一種全局變量只能被定義一次,否則會出現連接錯誤。

  通常,在模塊的頭文件中對本模塊提供給其它模塊引用的函數和全局變量以關鍵字extern聲明。例如,如果模塊B欲引用該模塊A中定義的全局變 量和函數時只需包含模塊A的頭文件即可。這樣,模塊B中調用模塊A中的函數時,在編譯階段,模塊B雖然找不到該函數,但是並不會報錯;它會在連接階段中從 模塊A編譯生成的目標代碼中找到此函數。

  與extern對應的關鍵字是static,被它修飾的全局變量和函數只能在本模塊中使用。因此,一個函數或變量只可能被本模塊使用時,其不可能被extern “C”修飾。

  被extern "C"修飾的變量和函數是按照C語言方式編譯和連接的;

  未加extern “C”聲明時的編譯方式

  首先看看C++中對類似C的函數是怎樣編譯的。

  作為一種面向對象的語言,C++支持函數重載,而過程式語言C則不支持。函數被C++編譯後在符號庫中的名字與C語言的不同。例如,假設某個函數的原型為:

void foo( int x, int y );

  該函數被C編譯器編譯後在符號庫中的名字為_foo,而C++編譯器則會產生像_foo_int_int之類的名字(不同的編譯器可能生成的名字不同,但是都采用了相同的機制,生成的新名字稱為“mangled name”)。

  _foo_int_int這樣的名字包含了函數名、函數參數數量及類型信息,C++就是靠這種機制來實現函數重載的。例如,在C++中,函數 void foo( int x, int y )與void foo( int x, float y )編譯生成的符號是不相同的,後者為_foo_int_float。   同樣地,C++中的變量除支持局部變量外,還支持類成員變量和全局變量。用戶所編寫程序的類成員變量可能與全局變量同名,我們以"."來區分。而本質 上,編譯器在進行編譯時,與函數的處理相似,也為類中的變量取了一個獨一無二的名字,這個名字與用戶程序中同名的全局變量名字不同。

  未加extern "C"聲明時的連接方式

  假設在C++中,模塊A的頭文件如下:

// 模塊A頭文件 moduleA.h

#ifndef MODULE_A_H

#define MODULE_A_H

int foo( int x, int y );

#endif

  在模塊B中引用該函數:

// 模塊B實現文件 moduleB.cpp

#include "moduleA.h"

foo(2,3);

  實際上,在連接階段,連接器會從模塊A生成的目標文件moduleA.obj中尋找_foo_int_int這樣的符號!

  加extern "C"聲明後的編譯和連接方式

  加extern "C"聲明後,模塊A的頭文件變為:

// 模塊A頭文件 moduleA.h #ifndef MODULE_A_H #define MODULE_A_H extern "C" int foo( int x, int y ); #endif

  在模塊B的實現文件中仍然調用foo( 2,3 ),其結果是:

  (1)模塊A編譯生成foo的目標代碼時,沒有對其名字進行特殊處理,采用了C語言的方式;

  (2)連接器在為模塊B的目標代碼尋找foo(2,3)調用時,尋找的是未經修改的符號名_foo。

  如果在模塊A中函數聲明了foo為extern "C"類型,而模塊B中包含的是extern int foo( int x, int y ) ,則模塊B找不到模塊A中的函數;反之亦然。

//因為不匹配,extern int foo( int x, int y )按照C++方式編譯。

 

  所以,可以用一句話概括extern “C”這個聲明的真實目的(任何語言中的任何語法特性的誕生都不是隨意而為的,來源於真實世界的需求驅動。我們在思考問題時,不能只停留在這個語言是怎麼做的,還要問一問它為什麼要這麼做,動機是什麼,這樣我們可以更深入地理解許多問題):  

 實現C++與C及其它語言的混合編程。   

明白了C++中extern "C"的設立動機,我們下面來具體分析extern "C"通常的使用技巧。   

 

4.extern "C"的慣用法

  (1)在C++中引用C語言中的函數和變量,在包含C語言頭文件(假設為cExample.h)時,需進行下列處理:

extern "C" { #include "cExample.h" }

  而在C語言的頭文件中,對其外部函數只能指定為extern類型,C語言中不支持extern "C"聲明,在.c文件中包含了extern "C"時會出現編譯語法錯誤。

  筆者編寫的C++引用C函數例子工程中包含的三個文件的源代碼如下:

/* c語言頭文件:cExample.h */

#ifndef C_EXAMPLE_H

#define C_EXAMPLE_H

extern int add(int x,int y);

#endif

 

/* c語言實現文件:cExample.c */

#include "cExample.h"

int add( int x, int y )

{ return x + y; }

 

// c++實現文件,調用add:cppFile.cpp

extern "C" { #include "cExample.h" }

int main(int argc, char* argv[])

{

add(2,3);

return 0;

}

  如果C++調用一個C語言編寫的.DLL時,當包括.DLL的頭文件或聲明接口函數時,應加extern "C" { }。

  (2)在C中引用C++語言中的函數和變量時,C++的頭文件需添加extern "C",但是在C語言中不能直接引用聲明了extern "C"的該頭文件,應該僅將C文件中將C++中定義的extern "C"函數聲明為extern類型。   筆者編寫的C引用C++函數例子工程中包含的三個文件的源代碼如下:

//C++頭文件 cppExample.h

#ifndef CPP_EXAMPLE_H

#define CPP_EXAMPLE_H

extern "C" int add( int x, int y );

#endif /

/C++實現文件 cppExample.cpp

#include "cppExample.h"

int add( int x, int y )

{

return x + y;

}

/* C實現文件 cFile.c /*

這樣會編譯出錯:#include "cExample.h" */

extern int add( int x, int y );

int main( int argc, char* argv[] )

{

add( 2, 3 );

return 0;

}

  如果深入理解了第3節中所闡述的extern "C"在編譯和連接階段發揮的作用,就能真正理解本節所闡述的從C++引用C函數和C引用C++函數的慣用法。對第4節給出的示例代碼,需要特別留意各個細節。

轉載地址:http://www.cppblog.com/Macaulish/archive/2008/06/17/53689.html

博主補充一點體會

在用“動態反射”方式調用動態鏈接庫的函數時,

eg:

typedef StubBase * (* func)();

func fun = (func)GetProcAddress(dllhandle,LPCSTR("methodName"));

為了讓這種動態反射能夠成功,最好在dll的實現代碼裡面使用extern "C"來修飾。如下:

extern "C" { __declspec( dllexport ) StubBase * methodName(){     StubBase * x = new StubBase();     return x; }}

這樣做能夠保證在動態反射函數地址時,編譯器編譯函數名的方式是一樣的,都按照C語言格式編譯。

 

注:上面內容全為轉載過來的,動態反射”方式調用動態鏈接庫的函數部分為我不懂的,其他紅色部分的內容是比較好的,也有自己的一點點小體會,呵呵!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved