爲何使用const?採用符號常量寫出的代碼更容易維護;指針經常是邊讀邊移動,而不是邊寫邊移動;許多函數參數是隻讀不寫的。const最多見用途是做爲數組的界和switch分狀況標號(也能夠用枚舉符代替),分類以下:html
常變量: const 類型說明符 變量名ios
常引用: const 類型說明符 &引用名c++
常對象: 類名 const 對象名數組
常成員函數: 類名::fun(形參) const安全
常數組: 類型說明符 const 數組名[大小] 函數
常指針: const 類型說明符* 指針名 ,類型說明符* const 指針名學習
首先提示的是:在常變量(const 類型說明符 變量名)、常引用(const 類型說明符 &引用名)、常對象(類名 const 對象名)、 常數組(類型說明符 const 數組名[大小]), const」 與 「類型說明符」或「類名」(其實類名是一種自定義的類型說明符) 的位置能夠互換。如:this
const int a=5; 與 int const a=5; 等同spa
類名 const 對象名 與 const 類名 對象名 等同指針
用法1:常量
取代了C中的宏定義,聲明時必須進行初始化(!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. 故pc不能指向別的字符串,但能夠修改其指向的字符串的內容
pc2 is a pointer to const char. 故*pc2的內容不能夠改變,但pc2能夠指向別的字符串
且注意:容許把非 const 對象的地址賦給指向 const 對象的指針,不容許把一個 const 對象的地址賦給一個普通的、非 const 對象的指針。
用法3:const修飾函數傳入參數
將函數傳入參數聲明爲const,以指明使用這種參數僅僅是爲了效率的緣由,而不是想讓調用函數可以修改對象的值。同理,將指針參數聲明爲const,函數將不修改由這個參數所指的對象。
一般修飾指針參數和引用參數:
void Fun( const A *in); //修飾指針型傳入參數
void Fun(const A &in); //修飾引用型傳入參數
用法4:修飾函數返回值
能夠阻止用戶修改返回值。返回值也要相應的付給一個常量或常指針。
用法5:const修飾成員函數(c++特性)
const對象只能訪問const成員函數,而非const對象能夠訪問任意的成員函數,包括const成員函數;
const對象的成員是不能修改的,而經過指針維護的對象確實能夠修改的;
const成員函數不能夠修改對象的數據,無論對象是否具備const性質。編譯時以是否修改爲員數據爲依據進行檢查。
具體展開來說:
(一). 常量與指針
常量與指針放在一塊兒很容易讓人迷糊。對於常量指針和指針常量也不是全部的學習C/C++的人都能說清除。例如:
const int *m1 = new int(10);
int* const m2 = new int(20);
在上面的兩個表達式中,最容易讓人迷惑的是const究竟是修飾指針仍是指針指向的內存區域?其實,只要知道:const只對它左邊的東西起做用,惟一的例外就是const自己就是最左邊的修飾符,那麼它纔會對右邊的東西起做用。根據這個規則來判斷,m1應該是常量指針(即,不能經過m1來修改它所指向的內容。);而m2應該是指針常量(即,不能讓m2指向其餘的內存模塊)。因而可知:
1. 對於常量指針,不能經過該指針來改變所指的內容。即,下面的操做是錯誤的:
int i = 10;
const int *pi = &i;
*pi = 100;
由於你在試圖經過pi改變它所指向的內容。可是,並非說該內存塊中的內容不能被修改。咱們仍然能夠經過其餘方式去修改其中的值。例如:
// 1: 經過i直接修改。
i = 100;
// 2: 使用另一個指針來修改。
int *p = (int*)pi;
*p = 100;
實際上,在將程序載入內存的時候,會有專門的一塊內存區域來存放常量。可是,上面的i自己不是常量,是存放在棧或者堆中的。咱們仍然能夠修改它的值。而pi不能修改指向的值應該說是編譯器的一個限制。
2. 根據上面const的規則,const int *m1 = new int(10);咱們也可寫做:
int const *m1 = new int(10);
這是,理由就不須做過多說明了。
3. 在函數參數中指針常量時表示不容許將該指針指向其餘內容。
void func_02(int* const p)
{
int *pi = new int(100);
//錯誤!P是指針常量。不能對它賦值。
p = pi;
}
int main()
{
int* p = new int(10);
func_02(p);
delete p;
return 0;
}
4. 在函數參數中使用常量指針時表示在函數中不能改變指針所指向的內容。
void func(const int *pi)
{
//錯誤!不能經過pi去改變pi所指向的內容!
*pi = 100;
}
int main()
{
int* p = new int(10);
func(p);
delete p;
return 0;
}
咱們可使用這樣的方法來防止函數調用者改變參數的值。可是,這樣的限制是有限的,做爲參數調用者,咱們也不要試圖去改變參數中的值。所以,下面的操做是在語法上是正確的,可是可能破還參數的值:
#include <iostream>
#include <string>
void func(const int *pi)
{
//這裏至關於從新構建了一個指針,指向相同的內存區域。固然就能夠經過該指針修改內存中的值了。
int* pp = (int*)pi;
*pp = 100;
}
int main()
{
using namespace std;
int* p = new int(10);
cout << "*p = " << *p << endl;
func(p);
cout << "*p = " << *p << endl;
delete p;
return 0;
}
(二):常量與引用
常量與引用的關係稍微簡單一點。由於引用就是另外一個變量的別名,它自己就是一個常量。也就是說不能再讓一個引用成爲另一個變量的別名, 那麼他們只剩下表明的內存區域是否可變。即:
int i = 10;
// 正確:表示不能經過該引用去修改對應的內存的內容。
const int& ri = i;
// 錯誤!不能這樣寫。
int& const rci = i;
因而可知,若是咱們不但願函數的調用者改變參數的值。最可靠的方法應該是使用引用。下面的操做會存在編譯錯誤:
void func(const int& i)
{
// 錯誤!不能經過i去改變它所表明的內存區域。
i = 100;
}
int main()
{
int i = 10;
func(i);
return 0;
}
這裏已經明白了常量與指針以及常量與引用的關係。可是,有必要深刻的說明如下。在系統加載程序的時候,系統會將內存分爲4個區域:堆區 棧區全局區(靜態)和代碼區。從這裏能夠看出,對於常量來講,系統沒有劃定專門的區域來保護其中的數據不能被更改。也就是說,使用常量的方式對數據進行保護是經過編譯器做語法限制來實現的。咱們仍然能夠繞過編譯器的限制去修改被定義爲「常量」的內存區域。看下面的代碼:
const int i = 10;
// 這裏i已經被定義爲常量,可是咱們仍然能夠經過另外的方式去修改它的值。
// 這說明把i定義爲常量,其實是防止經過i去修改所表明的內存。
int *pi = (int*) &i;
(三):常量函數
常量函數是C++對常量的一個擴展,它很好的確保了C++中類的封裝性。在C++中,爲了防止類的數據成員被非法訪問,將類的成員函數分紅了兩類,一類是常量成員函數(也被稱爲觀察着);另外一類是很是量成員函數(也被成爲變異者)。在一個函數的簽名後面加上關鍵字const後該函數就成了常量函數。對於常量函數,最關鍵的不一樣是編譯器不容許其修改類的數據成員。例如:
class Test
{
public:
void func() const;
private:
int intValue;
};
void Test::func() const
{
intValue = 100;
}
上面的代碼中,常量函數func函數內試圖去改變數據成員intValue的值,所以將在編譯的時候引起異常。
固然,對於很是量的成員函數,咱們能夠根據須要讀取或修改數據成員的值。可是,這要依賴調用函數的對象是不是常量。一般,若是咱們把一個類定義爲常量,咱們的本意是但願他的狀態(數據成員)不會被改變。那麼,若是一個常量的對象調用它的很是量函數會產生什麼後果呢?看下面的代碼:
class Fred{
public:
void inspect() const;
void mutate();
};
void UserCode(Fred& changeable, const Fred& unChangeable)
{
changeable.inspect(); // 正確,很是量對象能夠調用常量函數。
changeable.mutate(); // 正確,很是量對象也容許修改調用很是量成員函數修改數據成員。
unChangeable.inspect(); // 正確,常量對象只能調用常理函數。由於不但願修改對象狀態。
unChangeable.mutate(); // 錯誤!常量對象的狀態不能被修改,而很是量函數存在修改對象狀態的可能
}
從上面的代碼能夠看出,因爲常量對象的狀態不容許被修改,所以,經過常量對象調用很是量函數時將會產生語法錯誤。實際上,咱們知道每一個成員函數都有一個隱含的指向對象自己的this指針。而常量函數則包含一個this的常量指針。以下:
void inspect(const Fred* this) const;
void mutate(Fred* this);
也就是說對於常量函數,咱們不能經過this指針去修改對象對應的內存塊。可是,在上面咱們已經知道,這僅僅是編譯器的限制,咱們仍然能夠繞過編譯器的限制,去改變對象的狀態。看下面的代碼:
class Fred{
public:
void inspect() const;
private:
int intValue;
};
void Fred::inspect() const
{
cout << "At the beginning. intValue = "<< intValue << endl;
// 這裏,咱們根據this指針從新定義了一個指向同一塊內存地址的指針。
// 經過這個新定義的指針,咱們仍然能夠修改對象的狀態。
Fred* pFred = (Fred*)this;
pFred->intValue = 50;
cout << "Fred::inspect() called. intValue = "<< intValue << endl;
}
int main()
{
Fred fred;
fred.inspect();
return 0;
}
上面的代碼說明,只要咱們願意,咱們仍是能夠經過常量函數修改對象的狀態。同理,對於常量對象,咱們也能夠構造另一個指向同一塊內存的指針去修改它的狀態。這裏就不做過多描述了。
另外,也有這樣的狀況,雖然咱們能夠繞過編譯器的錯誤去修改類的數據成員。可是C++也容許咱們在數據成員的定義前面加上mutable,以容許該成員能夠在常量函數中被修改。例如:
class Fred{
public:
void inspect() const;
private:
mutable int intValue;
};
void Fred::inspect() const
{
intValue = 100;
}
可是,並非全部的編譯器都支持mutable關鍵字。這個時候咱們上面的歪門邪道就有用了。
關於常量函數,還有一個問題是重載。
#include <iostream>
#include <string>
using namespace std;
class Fred{
public:
void func() const;
void func();
};
void Fred::func() const
{
cout << "const function is called."<< endl;
}
void Fred::func()
{
cout << "non-const function is called."<< endl;
}
void UserCode(Fred& fred, const Fred& cFred)
{
cout << "fred is non-const object, and the result of fred.func() is:" << endl;
fred.func();
cout << "cFred is const object, and the result of cFred.func() is:" << endl;
cFred.func();
}
int main()
{
Fred fred;
UserCode(fred, fred);
return 0;
}
輸出結果爲:
fred is non-const object, and the result of fred.func() is:
non-const function is called.
cFred is const object, and the result of cFred.func() is:
const function is called.
從上面的輸出結果,咱們能夠看出。當存在同名同參數和返回值的常量函數和很是量函數時,具體調用哪一個函數是根據調用對象是常量對像仍是很是量對象來決定的。常量對象調用常量成員;很是量對象調用很是量的成員。
總之,咱們須要明白常量函數是爲了最大程度的保證對象的安全。經過使用常量函數,咱們能夠只容許必要的操做去改變對象的狀態,從而防止誤操做對對象狀態的破壞。可是,就像上面看見的同樣,這樣的保護實際上是有限的。關鍵仍是在於咱們開發人員要嚴格的遵照使用規則。另外須要注意的是常量對象不容許調用很是量的函數。這樣的規定雖然很武斷,但若是咱們都根據原則去編寫或使用類的話這樣的規定也就徹底能夠理解了。
(四):常量返回值
不少時候,咱們的函數中會返回一個地址或者引用。調用這獲得這個返回的地址或者引用後就能夠修改所指向或者表明的對象。這個時候若是咱們不但願這個函數的調用這修改這個返回的內容,就應該返回一個常量。這應該很好理解,你們能夠去試試。
+++++++++++++++++++++++++++++++++++++++
c++ 中const
+++++++++++++++++++++++++++++++++++++++
1. const常量,如const int max = 100;
優勢:const常量有數據類型,而宏常量沒有數據類型。編譯器能夠對前者進行類型安全檢查,而對後者只進行字符替換,沒有類型安全檢查,而且在字符替換時可能會產生意料不到的錯誤(邊際效應)
2. const 修飾類的數據成員。如:
class A
{
const int size;
…
}
const數據成員只在某個對象生存期內是常量,而對於整個類而言倒是可變的。由於類能夠建立多個對象,不一樣的對象其const數據成員的值能夠不一樣。因此不能在類聲明中初始化const數據成員,由於類的對象未被建立時,編譯器不知道const 數據成員的值是什麼。如
class A
{
const int size = 100; //錯誤
int array[size]; //錯誤,未知的size
}
const數據成員的初始化只能在類的構造函數的初始化表中進行。要想創建在整個類中都恆定的常量,應該用類中的枚舉常量來實現。如
class A
{…
enum {size1=100, size2 = 200 };
int array1[size1];
int array2[size2];
}
枚舉常量不會佔用對象的存儲空間,他們在編譯時被所有求值。可是枚舉常量的隱含數據類型是整數,其最大值有限,且不能表示浮點數。
3. const修飾指針的狀況,見下式:
int b = 500;
const int* a = & [1]
int const *a = & [2]
int* const a = & [3]
const int* const a = & [4]
若是你能區分出上述四種狀況,那麼,恭喜你,你已經邁出了可喜的一步。不知道,也不要緊,咱們能夠參考《Effectivec++》Item21上的作法,若是const位於星號的左側,則const就是用來修飾指針所指向的變量,即指針指向爲常量;若是const位於星號的右側,const就是修飾指針自己,即指針自己是常量。所以,[1]和[2]的狀況相同,都是指針所指向的內容爲常量(const放在變量聲明符的位置無關),這種狀況下不容許對內容進行更改操做,如不能*a = 3;[3]爲指針自己是常量,而指針所指向的內容不是常量,這種狀況下不能對指針自己進行更改操做,如a++是錯誤的;[4]爲指針自己和指向的內容均爲常量。
4. const的初始化
先看一下const變量初始化的狀況
1) 非指針const常量初始化的狀況:A b;
const A a = b;
2) 指針const常量初始化的狀況:
A* d = new A();
const A* c = d;
或者:const A* c = new A();
3)引用const常量初始化的狀況:
A f;
const A& e = f; // 這樣做e只能訪問聲明爲const的函數,而不能訪問通常的成員函數;
[思考1]: 如下的這種賦值方法正確嗎?
const A* c=new A();
A* e = c;
[思考2]: 如下的這種賦值方法正確嗎?
A* const c = new A();
A* b = c;
5. 另外const 的一些強大的功能在於它在函數聲明中的應用。在一個函數聲明中,const能夠修飾函數的返回值,或某個參數;對於成員函數,還能夠修飾是整個函數。有以下幾種狀況,如下會逐漸的說明用法:A&operator=(const A& a);
void fun0(const A* a );
void fun1( ) const; // fun1( ) 爲類成員函數
const A fun2( );
1) 修飾參數的const,如 void fun0(const A* a ); void fun1(const A& a);
調用函數的時候,用相應的變量初始化const常量,則在函數體中,按照const所修飾的部分進行常量化,如形參爲const A*a,則不能對傳遞進來的指針的內容進行改變,保護了原指針所指向的內容;如形參爲const A&a,則不能對傳遞進來的引用對象進行改變,保護了原對象的屬性。
[注意]:參數const一般用於參數爲指針或引用的狀況,且只能修飾輸入參數;若輸入參數採用「值傳遞」方式,因爲函數將自動產生臨時變量用於複製該參數,該參數本就不須要保護,因此不用const修飾。
[總結]對於非內部數據類型的輸入參數,因該將「值傳遞」的方式改成「const引用傳遞」,目的是爲了提升效率。例如,將void Func(A a)改成void Func(const A &a)
對於內部數據類型的輸入參數,不要將「值傳遞」的方式改成「const引用傳遞」。不然既達不到提升效率的目的,又下降了函數的可理解性。例如void Func(int x)不該該改成void Func(const int &x)
2) 修飾返回值的const,如const A fun2( ); const A* fun3( );
這樣聲明瞭返回值後,const按照"修飾原則"進行修飾,起到相應的保護做用。const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(),
lhs.denominator() * rhs.denominator());
}
返回值用const修飾能夠防止容許這樣的操做發生:Rational a,b;
Radional c;
(a*b) = c;
通常用const修飾返回值爲對象自己(非引用和指針)的狀況多用於二目操做符重載函數併產生新對象的時候。
[總結]
1. 通常狀況下,函數的返回值爲某個對象時,若是將其聲明爲const時,多用於操做符的重載。一般,不建議用const修飾函數的返回值類型爲某個對象或對某個對象引用的狀況。緣由以下:若是返回值爲某個對象爲const(const A test = A實例)或某個對象的引用爲const(const A& test = A實例),則返回值具備const屬性,則返回實例只能訪問類A中的公有(保護)數據成員和const成員函數,而且不容許對其進行賦值操做,這在通常狀況下不多用到。
2. 若是給採用「指針傳遞」方式的函數返回值加const修飾,那麼函數返回值(即指針)的內容不能被修改,該返回值只能被賦給加const 修飾的同類型指針。如:
const char * GetString(void);
以下語句將出現編譯錯誤:
char *str=GetString();
正確的用法是:
const char *str=GetString();
3. 函數返回值採用「引用傳遞」的場合很少,這種方式通常只出如今類的賻值函數中,目的是爲了實現鏈式表達。如:
class A
{…
A &operate = (const A &other); //負值函數
}
A a,b,c; //a,b,c爲A的對象
…
a=b=c; //正常
(a=b)=c; //不正常,可是合法
若負值函數的返回值加const修飾,那麼該返回值的內容不容許修改,上例中a=b=c依然正確。(a=b)=c就不正確了。
[思考3]: 這樣定義賦值操做符重載函數能夠嗎?
const A& operator=(const A& a);
6. 類成員函數中const的使用
通常放在函數體後,形如:void fun() const;
任何不會修改數據成員的函數都因該聲明爲const類型。若是在編寫const成員函數時,不慎修改了數據成員,或者調用了其餘非const成員函數,編譯器將報錯,這大大提升了程序的健壯性。如:
class Stack
{
public:
void Push(int elem);
int Pop(void);
int GetCount(void) const; //const 成員函數
private:
int m_num;
int m_data[100];
};
int Stack::GetCount(void) const
{
++m_num; //編譯錯誤,企圖修改數據成員m_num
Pop(); //編譯錯誤,企圖調用非const函數
Return m_num;
}
7. 使用const的一些建議
1) 要大膽的使用const,這將給你帶來無盡的益處,但前提是你必須搞清楚原委;
2) 要避免最通常的賦值操做錯誤,如將const變量賦值,具體可見思考題;
3) 在參數中使用const應該使用引用或指針,而不是通常的對象實例,緣由同上;
4) const在成員函數中的三種用法(參數、返回值、函數)要很好的使用;
5) 不要輕易的將函數的返回值類型定爲const;
6) 除了重載操做符外通常不要將返回值類型定爲對某個對象的const引用;
[思考題答案]
1) 這種方法不正確,由於聲明指針的目的是爲了對其指向的內容進行改變,而聲明的指針e指向的是一個常量,因此不正確;
2) 這種方法正確,由於聲明指針所指向的內容可變;
3) 這種作法不正確;
在const A::operator=(const A& a)中,參數列表中的const的用法正確,而當這樣連續賦值的時侯,問題就出現了:
A a,b,c:
(a=b)=c;
由於a.operator=(b)的返回值是對a的const引用,不能再將c賦值給const常量。
++++++++++++++++++++++++++++++++++++++++
const 在c和c++中的區別 http://tech.e800.com.cn/articles/2009/722/1248229886744_1.html
++++++++++++++++++++++++++++++++++++++++
1. C++中的const正常狀況下是當作編譯期的常量,編譯器並不爲const分配空間,只是在編譯的時候將期值保存在名字表中,並在適當的時候摺合在代碼中.因此,如下代碼:
using namespace std;
int main()
{
const int a = 1;
const int b = 2;
int array[ a + b ] = {0};
for (int i = 0; i < sizeof array / sizeof *array; i++)
{
cout << array << endl;
}
}
在能夠經過編譯,而且正常運行.但稍加修改後,放在C編譯器中,便會出現錯誤:
int main()
{
int i;
const int a = 1;
const int b = 2;
int array[ a + b ] = {0};
for (i = 0; i < sizeof array / sizeof *array; i++)
{
printf("%d",array);
}
}
錯誤消息:
c:\test1\te.c(8): error C2057: 應輸入常數表達式
c:\test1\te.c(8): error C2466: 不能分配常數大小爲 0 的數組
出現這種狀況的緣由是:在C中,const是一個不能被改變的普通變量,既然是變量,就要佔用存儲空間,因此編譯器不知道編譯時的值.並且,數組定義時的下標必須爲常量.
2. 在C語言中: const int size; 這個語句是正確的,由於它被C編譯器看做一個聲明,指明在別的地方分配存儲空間.但在C++中這樣寫是不正確的.C++中const默認是內部鏈接,若是想在C++中達到以上的效果,必需要用extern關鍵字.即C++中,const默認使用內部鏈接.而C中使用外部鏈接.
(1) 內鏈接:編譯器只對正被編譯的文件建立存儲空間,別的文件可使用相同的表示符或全局變量.C/C++中內鏈接使用static關鍵字指定.
(2) 外鏈接:全部被編譯過的文件建立一片單獨存儲空間.一旦空間被建立,鏈接器必須解決對這片存儲空間的引用.全局變量和函數使用外部鏈接.經過extern關鍵字聲明,能夠從其餘文件訪問相應的變量和函數.
/* C++代碼 header.h */
const int test = 1;
/* C++代碼 test1.cpp */
#include "header.h"
using namespace std;
int main() { cout << "in test1 :" << test << endl; }
/* C++代碼 test2.cpp */
#include "header.h"
using namespace std;
void print() { cout << "in test2:" << test << endl;}
以上代碼編譯鏈接徹底不會出問題,但若是把header.h改成:
extern const int test = 1;
在鏈接的時候,便會出現如下錯誤信息:
test2 error LNK2005: "int const test" (?test@@3HB) 已經在 test1.obj 中定義
由於extern關鍵字告訴C++編譯器test會在其餘地方引用,因此,C++編譯器就會爲test建立存儲空間,再也不是簡單的存儲在名字表裏面.因此,當兩個文件同時包含header.h的時候,會發生名字上的衝突.
此種狀況和C中const含義類似:
/* C代碼 header.h */
const int test = 1;
/* C代碼 test1.c */
#include "header.h"
int main() { printf("in test1:%d\n",test); }
/* C代碼 test2.c */
#include "header.h"
void print() { printf("in test2:%d\n",test); }
錯誤消息:
test3 fatal error LNK1169: 找到一個或多個多重定義的符號
test3 error LNK2005: _test 已經在 test1.obj 中定義
也就是說:在c++ 中const 對象默認爲文件的局部變量。與其餘變量不一樣,除非特別說明,在全局做用域聲明的 const 變量是定義該對象的文件的局部變量。此變量只存在於那個文件中,不能被其餘文件訪問。經過指定 const 變動爲 extern,就能夠在整個程序中訪問 const 對象:
// file_1.cc
// defines and initializes a const that is accessible to other files
extern const int bufSize = fcn();
// file_2.cc
extern const int bufSize; // uses bufSize from file_1
// uses bufSize defined in file_1
for (int index = 0; index != bufSize; ++index)
// ...
3. C++中,是否爲const分配空間要看具體狀況.若是加上關鍵字extern或者取const變量地址,則編譯器就要爲const分配存儲空間.
4. C++中定義常量的時候再也不採用define,由於define只作簡單的宏替換,並不提供類型檢查.
很是感謝博主的分享http://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777416.html