| 導購 | 订阅 | 在线投稿
分享
 
 
 

C++/CLI基本數據類型探索

2008-06-01 01:58:51  編輯來源:互聯網  简体版  手機版  移動版  評論  字體: ||

C++/CLI所支持的基本類型,例如int、double、bool等,在某些方面可以說是沿襲了ISO-C++中的類型——同樣的用法會在C++/CLI中得到同樣的結果,例如加法或者賦值操作。但是C++/CLI也爲這些基本類型引入了一些新的東西。

在通用類型系統(CTS)中,每一個基本類型都在System命名空間中存在一個對應的類(見表1)。例如int實際上完全等價于System::Int32。我們可以使用二者中的任何一個來聲明一個整數:

int ival = 0;

Int32 ival2 = 0;

出于移植性的考慮,在使用這些基本類型時,我們推薦大家使用內建的要害詞,而非System命名空間中的類名。

C++/CLI基本數據類型探索

對于System命名空間中類的公有靜態成員,我們既可以通過內建的要害字,也可以通過System命名空間中的類名來訪問。例如,爲了獲取一個數值類型的取值範圍,我們可以直接使用內建的要害字來訪問其靜態屬性MaxValue和MinValue。

int imaxval = int::MaxValue;

int iminval = Int32::MinValue;

每個數值類型都支持一個名爲Parse的成員函數,用以將一個字符串轉化爲其所表示的數值。例如,給定下面的字符串:

String^ bonus = "$ 12,000.79";

C++/CLI基本數據類型探索
更多內容請看C/C++技術專題專題,或

調用Parse會將myBonus初始化爲12000.79:

double myBonus = double::Parse( bonus, ns );

其中ns表示對一些NumberStyles枚舉類型取位或(bitwise or)運算的結果。NumberStyles是位于System::Globalization命名空間中的一個枚舉類型,用于表征對空白、貨幣符號、小數點或者逗號等的處理。看下面的代碼:

using namespace System;

using namespace System::Globalization;

double bonusString( String^ bonus )

{

NumberStyles ns = NumberStyles::AllowLeadingWhite;

ns = NumberStyles::AllowCurrencySymbol;

ns = NumberStyles::AllowThousands;

ns = NumberStyles::AllowDecimalPoint;

return double::Parse( bonus, ns );

}

我們也可以使用轉型符號來在類型間進行顯式的轉換。

int ival = ( int ) myBonus;

或者使用System::Convert類的一些轉換方法,例如ToDouble(), ToInt32(), ToDateTime()等:

int ival2 = Convert::ToInt32( myBonus );

兩種轉換方法采用的策略有所不同:顯式轉型會直接對小數部分進行截斷,而Convert的成員函數則采用的是舍入算法。例如上面的例子中ival賦值後的結果爲12000,而ival2賦值後的結果爲12001。

我們還可以直接使用字面常量(literal)來調用其對應類型的成員函數,雖然這乍看起來有些怪異。例如,我們可以編寫如下代碼:

Console::Write( "{0} : ", ( 5 ).ToString() );

其中( 5 ).ToString()返回的是字面常量整數5的字符串表示。注重5外面的圓括號是必須的,因爲它會使得編譯器將後面的成員選擇操作符點號綁定到整數5上,而不是將'5.'解析爲一個double類型的字面常量——那樣的話,後面的ToString()將變得不合法。爲什麽我們有時候需要這樣做呢?一種可能的情況是將一個字符串傳遞給Console的成員函數要比傳遞實際的數值來的更加高效。

對于字符以及字符串這樣的字面常量,我們也可以像上面的整數一樣調用它們的成員函數,但是它們的行爲有一點點晦澀。例如,下面的代碼:

Console::WriteLine(( 'a' ).ToString() );

將在控制台上打印出97,而非'a'這個字符。要將字符'a'打印出來,我們需要將其首先轉型爲System::Char:

Console::WriteLine(((wchar_t)'a').ToString() );

C++/CLI對字符串字面常量采取了非凡的處理策略。從某種程度上來講,字符串字面常量在C++/CLI中的類型更接近System::String,而非C風格的字符串指針。顯然,這將對重載函數的辨析産生影響。例如:

public ref class R {

public:

void foo( System::String^ ); // (1)

void foo( std::string ); // (2)

void foo( const char* ); // (3)

};

void bar( R^ r )

{

// 調用哪一個foo呢?

r->foo( "Pooh" );

}

C++/CLI基本數據類型探索
更多內容請看C/C++技術專題專題,或

在ISO-C++中,這將被辨析爲第3個foo(),因爲字符串字面常量更接近const char*,而非ISO-C++標准庫中的string類型。但是,在C++/CLI中,上面的調用將被辨析爲第1個foo(),因爲現在字符串字面常量被認爲更接近System::String,而非字符指針。

要理解其中的緣由,讓我們往後退兩步,先來看看ISO-C++和C++/CLI如何辨析一個重載函數,然後再來看ISO-C++和C++/CLI如何辨析一個字符串字面常量。

一個重載函數的辨析過程通常包含以下三個步驟:

1.選擇候選函數集合。候選函數是指那些從詞法範疇來看與所調用函數名相匹配的函數。例如,由于我們上面是在R的一個實例上調用foo(),所以所有名稱爲foo但卻不是R或者其基類的成員的那些函數將不被認爲是候選函數。這樣看來,我們現在有三個候選函數,即R中三個名稱爲foo的成員函數。假如這個階段得到的候選函數集合爲空,那麽調用將告失敗。

2.從候選函數集合中選擇可用函數集合。可用函數是指函數聲明時的參數個數和它們的類型與調用時所指定的相匹配的那些函數。在我們上面的例子中,三個候選函數都是可用函數。假如這個階段得到的可用函數集合爲空,那麽調用也將失敗。

3.從可用函數集合中選擇最匹配的函數。這個階段將會對實際傳遞的參數和可用函數所聲明的參數之間的轉換進行一個排名。對于只含一個參數的函數來說,這個過程比較簡單。但是對于含有多個參數的函數來說,這個過程就變得相對有些複雜。假如沒有一個最佳的匹配函數勝出,那麽調用將告失敗。也就是說各個可用函數的參數類型到實際參數類型之間的轉換被認爲一樣的好,換言之多個調用之間産生了混淆。

那麽現在擺在我們面前有兩個問題:(1)我們實際傳遞的參數"Pooh"到底是什麽類型?(2)在判定類型轉換的優劣時采用的是什麽算法?

在ISO-C++中,字符串字面常量"Pooh"的類型爲const char[5]——注重,在字符串字面常量後面有一個隱含的截斷字符null。在上面的例子中顯然不存在這樣的精確匹配,因此必須應用某種形式的類型轉換。這樣,兩個ISO-C++候選函數(2)和(3)將進行競爭:

void foo( std::string ); // (2)

void foo( const char* ); // (3)

那麽編譯器如何從中判定可用函數呢?C++語言對類型轉換按照優先順序定義有一個層級結構,在這個結構中,假如一種轉換優于另一種轉換,那麽它將被排在前面。在C++/CLI中,我們將CLI類型的行爲也集成到了ISO-C++的標准類型轉換層級結構中。下面是對集成之後的層級結構的一個描述:

1)精確匹配是最好的。需要注重的是精確匹配並不意味著實際傳遞的參數類型和函數聲明的形式參數類型完全匹配。它們只需要「足夠接近」就可以了。我們下面將會看到,「足夠接近」對于ISO-C++和C++/CLI中的字符串字面常量有著一些不同的含義。

2)在標准轉換中,拓寬轉換要優于非拓寬轉換。例如,將short拓寬爲int要優于將int轉換爲double。

3)標准轉換優于裝箱(boxing)轉換。例如,將int轉換爲double優于將int裝箱爲Object。

4)裝箱轉換優于用戶自定義的隱式轉換。

5)用戶自定義的隱式轉換優于沒有任何轉換!

6)否則,用戶必須使用顯式的轉型符號來表示期望的轉換。

對于上面兩個ISO-C++下的候選函數,將字符串字面常量轉換爲一個std::string屬于上面第5條,即隱式調用string的構造器來創建一個臨時string對象。而將字符串字面常量轉換爲一個const char* 屬于上面第1條。第1條優于第5條,因此,參數爲const char*的那個函數在這場競爭中勝出。

這種歸屬在第1條「精確匹配」下的trivial conversions實際上在技術的定義上是很嚴格的。總共有4種這樣的trivial conversions可以被歸爲精確匹配。即使在這4種trivial conversions中,爲了規範語言對類型的選擇,它們也有一個優先級的排序。

大多數讀者和程序員可能對于這樣的細節沒有多大愛好,並且通常情況下我們也無須深入到這些細節的地方。但是假如我們要得到一個直觀的語言行爲、並且確保它們在不同的實現上表現相同,這些規則的存在就很有必要。這是因爲作爲一門編程語言,它的行爲一般要具有某種程度的「類型感知」能力,從而答應程序員忽略這些細節。 下面讓我們來對這4種trivial conversions做一簡單的了解。其中3種被稱爲左值轉換(lvalue transformation)。左值(lvalue)是一個可尋址的,可被執行寫操作的程序實體。第4種爲限定性轉換(qualification conversion),例如,在一個類型聲明上加一個const修飾符就屬于這種轉換。其中3種左值轉換優于限定性轉換。

在我們上面的例子中,由本地數組到指針的轉換,即由const char [5]到const char *,就是一種左值轉換。

在大多數情況下,我們甚至不將這看作一種轉換。

這種形式的左值轉換在C++/CLI中仍然適用,但是在我們將System::String類引入之後,字符串字面常量到const char*的轉換就不再是最好的匹配了。實際上,在C++/CLI中,"Pooh"這樣的字符串字面常量的類型既是const char[5](C++/CLI對本地類型系統保留後的結果),同時也是System::String(C++/CLI中的托管類型)。這樣,在C++/CLI中,字符串字面常量和System::String類型之間是一個精確的匹配,它優于由字符串字面常量到const char*的trivial conversion。

有些朋友看到這裏,可能會不興奮,「爲什麽要這樣做?難道ISO-C++對字符串字面常量的處理不能滿足C++/CLI的綁定需要嗎?」C++/CLI這樣做的理由在于字符串字面常量是我們程序中的一個基本元素,而ISO-C++的行爲在很多情況下顯得並不直觀。實際上,這些規則在我們現在看到的結果之前的一年中被來往返回改了3次之多。

這反映了ISO-C++和C++/CLI在對待各自的類型系統時存在的一個基礎性差異。在ISO-C++中,除非是顯式存在于一個類庫中,否則類型就是獨立的。因此,在字符串字面常量和std::string之間並沒有隱含的類型關系,雖然它們共享著同一個抽象域(domain)。

但是在C++/CLI中,我們支持統一的類型系統。每一個類型,包括字面常量值,都是一個Object的子類。這也是我們爲什麽可以在一個字面常量值,或者內建類型的對象上直接調用方法的原因。整數字面常量5的類型爲Int32,字符串字面常量"Pooh"的類型爲String。認爲字符串字面常量更接近C風格的字符串,或者就把它看作C風格的字符串是不合適的。

集成後的類型轉換層級結構使得一個正常運行的ISO-C++程序在使用/clr編譯器開關重新編譯後仍能展現同樣的行爲,但是使用CLI類型的新的C++/CLI程序在處理字符串字面常量時將會體現新的類型優先排序規則。這段討論的長度相對于這個主題的重要性而言可能並不合適,但是它卻向大家揭示了我們到底在將CLI類型系統和ISO-C++語義框架集成在一起的時候,做了哪些工作,以及如何在必要的時候調整在集成過程中所出現的各個情況的優先級。同時,這也提醒大家在將一個本地類重新構造爲一個CLI類的過程中需要注重的一些問題。比如有些情況下我們最好要對那些接受字符串字面常量的成員函數進行新的設計,而不是簡單地將一個參數爲String的函數添加到這些重載函數集合中了事。

另外需要注重的是,String表示的是Unicode字符集。和ASCII字符集不同,這需要兩個字節來表示一個字符。雖然在C++/CLI中字符串字面常量的類型爲String,但這並不意味著在C++/CLI中,一個字符串字面常量必然會被解析爲雙字節的字符流。在本地C++中,我們要在字符串字面常量前加一個L來告訴編譯器將其看作一個雙字節的字符流。在C++/CLI中,我們仍然需要這麽做。

C++/CLI基本數據類型探索
更多內容請看C/C++技術專題專題,或

  C++/CLI所支持的基本類型,例如int、double、bool等,在某些方面可以說是沿襲了ISO-C++中的類型——同樣的用法會在C++/CLI中得到同樣的結果,例如加法或者賦值操作。但是C++/CLI也爲這些基本類型引入了一些新的東西。   在通用類型系統(CTS)中,每一個基本類型都在System命名空間中存在一個對應的類(見表1)。例如int實際上完全等價于System::Int32。我們可以使用二者中的任何一個來聲明一個整數:   int ival = 0;   Int32 ival2 = 0;   出于移植性的考慮,在使用這些基本類型時,我們推薦大家使用內建的要害詞,而非System命名空間中的類名。 [url=/bbs/detail_1785120.html][img]http://image.wangchao.net.cn/it/1323424652087.gif[/img][/url]      對于System命名空間中類的公有靜態成員,我們既可以通過內建的要害字,也可以通過System命名空間中的類名來訪問。例如,爲了獲取一個數值類型的取值範圍,我們可以直接使用內建的要害字來訪問其靜態屬性MaxValue和MinValue。   int imaxval = int::MaxValue;   int iminval = Int32::MinValue;   每個數值類型都支持一個名爲Parse的成員函數,用以將一個字符串轉化爲其所表示的數值。例如,給定下面的字符串:   String^ bonus = "$ 12,000.79"; [url=/bbs/detail_1785120.html][img]http://image.wangchao.net.cn/it/1323424652155.gif[/img][/url] 更多內容請看C/C++技術專題專題,或   調用Parse會將myBonus初始化爲12000.79:   double myBonus = double::Parse( bonus, ns );   其中ns表示對一些NumberStyles枚舉類型取位或(bitwise or)運算的結果。NumberStyles是位于System::Globalization命名空間中的一個枚舉類型,用于表征對空白、貨幣符號、小數點或者逗號等的處理。看下面的代碼:      using namespace System;   using namespace System::Globalization;      double bonusString( String^ bonus )   {   NumberStyles ns = NumberStyles::AllowLeadingWhite;   ns  = NumberStyles::AllowCurrencySymbol;   ns  = NumberStyles::AllowThousands;   ns  = NumberStyles::AllowDecimalPoint;   return double::Parse( bonus, ns );   }   我們也可以使用轉型符號來在類型間進行顯式的轉換。      int ival = ( int ) myBonus;   或者使用System::Convert類的一些轉換方法,例如ToDouble(), ToInt32(), ToDateTime()等:      int ival2 = Convert::ToInt32( myBonus );   兩種轉換方法采用的策略有所不同:顯式轉型會直接對小數部分進行截斷,而Convert的成員函數則采用的是舍入算法。例如上面的例子中ival賦值後的結果爲12000,而ival2賦值後的結果爲12001。   我們還可以直接使用字面常量(literal)來調用其對應類型的成員函數,雖然這乍看起來有些怪異。例如,我們可以編寫如下代碼:      Console::Write( "{0} : ", ( 5 ).ToString() );   其中( 5 ).ToString()返回的是字面常量整數5的字符串表示。注重5外面的圓括號是必須的,因爲它會使得編譯器將後面的成員選擇操作符點號綁定到整數5上,而不是將'5.'解析爲一個double類型的字面常量——那樣的話,後面的ToString()將變得不合法。爲什麽我們有時候需要這樣做呢?一種可能的情況是將一個字符串傳遞給Console的成員函數要比傳遞實際的數值來的更加高效。   對于字符以及字符串這樣的字面常量,我們也可以像上面的整數一樣調用它們的成員函數,但是它們的行爲有一點點晦澀。例如,下面的代碼:      Console::WriteLine(( 'a' ).ToString() );   將在控制台上打印出97,而非'a'這個字符。要將字符'a'打印出來,我們需要將其首先轉型爲System::Char:      Console::WriteLine(((wchar_t)'a').ToString() );   C++/CLI對字符串字面常量采取了非凡的處理策略。從某種程度上來講,字符串字面常量在C++/CLI中的類型更接近System::String,而非C風格的字符串指針。顯然,這將對重載函數的辨析産生影響。例如:      public ref class R {    public:     void foo( System::String^ ); // (1)     void foo( std::string ); // (2)     void foo( const char* ); // (3)   };   void bar( R^ r )   {    // 調用哪一個foo呢?    r->foo( "Pooh" );   } [url=/bbs/detail_1785120.html][img]http://image.wangchao.net.cn/it/1323424652169.gif[/img][/url] 更多內容請看C/C++技術專題專題,或   在ISO-C++中,這將被辨析爲第3個foo(),因爲字符串字面常量更接近const char*,而非ISO-C++標准庫中的string類型。但是,在C++/CLI中,上面的調用將被辨析爲第1個foo(),因爲現在字符串字面常量被認爲更接近System::String,而非字符指針。 要理解其中的緣由,讓我們往後退兩步,先來看看ISO-C++和C++/CLI如何辨析一個重載函數,然後再來看ISO-C++和C++/CLI如何辨析一個字符串字面常量。   一個重載函數的辨析過程通常包含以下三個步驟:   1.選擇候選函數集合。候選函數是指那些從詞法範疇來看與所調用函數名相匹配的函數。例如,由于我們上面是在R的一個實例上調用foo(),所以所有名稱爲foo但卻不是R或者其基類的成員的那些函數將不被認爲是候選函數。這樣看來,我們現在有三個候選函數,即R中三個名稱爲foo的成員函數。假如這個階段得到的候選函數集合爲空,那麽調用將告失敗。   2.從候選函數集合中選擇可用函數集合。可用函數是指函數聲明時的參數個數和它們的類型與調用時所指定的相匹配的那些函數。在我們上面的例子中,三個候選函數都是可用函數。假如這個階段得到的可用函數集合爲空,那麽調用也將失敗。   3.從可用函數集合中選擇最匹配的函數。這個階段將會對實際傳遞的參數和可用函數所聲明的參數之間的轉換進行一個排名。對于只含一個參數的函數來說,這個過程比較簡單。但是對于含有多個參數的函數來說,這個過程就變得相對有些複雜。假如沒有一個最佳的匹配函數勝出,那麽調用將告失敗。也就是說各個可用函數的參數類型到實際參數類型之間的轉換被認爲一樣的好,換言之多個調用之間産生了混淆。   那麽現在擺在我們面前有兩個問題:(1)我們實際傳遞的參數"Pooh"到底是什麽類型?(2)在判定類型轉換的優劣時采用的是什麽算法?   在ISO-C++中,字符串字面常量"Pooh"的類型爲const char[5]——注重,在字符串字面常量後面有一個隱含的截斷字符null。在上面的例子中顯然不存在這樣的精確匹配,因此必須應用某種形式的類型轉換。這樣,兩個ISO-C++候選函數(2)和(3)將進行競爭:      void foo( std::string ); // (2)   void foo( const char* ); // (3)   那麽編譯器如何從中判定可用函數呢?C++語言對類型轉換按照優先順序定義有一個層級結構,在這個結構中,假如一種轉換優于另一種轉換,那麽它將被排在前面。在C++/CLI中,我們將CLI類型的行爲也集成到了ISO-C++的標准類型轉換層級結構中。下面是對集成之後的層級結構的一個描述:   1)精確匹配是最好的。需要注重的是精確匹配並不意味著實際傳遞的參數類型和函數聲明的形式參數類型完全匹配。它們只需要「足夠接近」就可以了。我們下面將會看到,「足夠接近」對于ISO-C++和C++/CLI中的字符串字面常量有著一些不同的含義。   2)在標准轉換中,拓寬轉換要優于非拓寬轉換。例如,將short拓寬爲int要優于將int轉換爲double。   3)標准轉換優于裝箱(boxing)轉換。例如,將int轉換爲double優于將int裝箱爲Object。   4)裝箱轉換優于用戶自定義的隱式轉換。   5)用戶自定義的隱式轉換優于沒有任何轉換!   6)否則,用戶必須使用顯式的轉型符號來表示期望的轉換。   對于上面兩個ISO-C++下的候選函數,將字符串字面常量轉換爲一個std::string屬于上面第5條,即隱式調用string的構造器來創建一個臨時string對象。而將字符串字面常量轉換爲一個const char* 屬于上面第1條。第1條優于第5條,因此,參數爲const char*的那個函數在這場競爭中勝出。   這種歸屬在第1條「精確匹配」下的trivial conversions實際上在技術的定義上是很嚴格的。總共有4種這樣的trivial conversions可以被歸爲精確匹配。即使在這4種trivial conversions中,爲了規範語言對類型的選擇,它們也有一個優先級的排序。   大多數讀者和程序員可能對于這樣的細節沒有多大愛好,並且通常情況下我們也無須深入到這些細節的地方。但是假如我們要得到一個直觀的語言行爲、並且確保它們在不同的實現上表現相同,這些規則的存在就很有必要。這是因爲作爲一門編程語言,它的行爲一般要具有某種程度的「類型感知」能力,從而答應程序員忽略這些細節。 下面讓我們來對這4種trivial conversions做一簡單的了解。其中3種被稱爲左值轉換(lvalue transformation)。左值(lvalue)是一個可尋址的,可被執行寫操作的程序實體。第4種爲限定性轉換(qualification conversion),例如,在一個類型聲明上加一個const修飾符就屬于這種轉換。其中3種左值轉換優于限定性轉換。   在我們上面的例子中,由本地數組到指針的轉換,即由const char [5]到const char *,就是一種左值轉換。 在大多數情況下,我們甚至不將這看作一種轉換。   這種形式的左值轉換在C++/CLI中仍然適用,但是在我們將System::String類引入之後,字符串字面常量到const char*的轉換就不再是最好的匹配了。實際上,在C++/CLI中,"Pooh"這樣的字符串字面常量的類型既是const char[5](C++/CLI對本地類型系統保留後的結果),同時也是System::String(C++/CLI中的托管類型)。這樣,在C++/CLI中,字符串字面常量和System::String類型之間是一個精確的匹配,它優于由字符串字面常量到const char*的trivial conversion。   有些朋友看到這裏,可能會不興奮,「爲什麽要這樣做?難道ISO-C++對字符串字面常量的處理不能滿足C++/CLI的綁定需要嗎?」C++/CLI這樣做的理由在于字符串字面常量是我們程序中的一個基本元素,而ISO-C++的行爲在很多情況下顯得並不直觀。實際上,這些規則在我們現在看到的結果之前的一年中被來往返回改了3次之多。   這反映了ISO-C++和C++/CLI在對待各自的類型系統時存在的一個基礎性差異。在ISO-C++中,除非是顯式存在于一個類庫中,否則類型就是獨立的。因此,在字符串字面常量和std::string之間並沒有隱含的類型關系,雖然它們共享著同一個抽象域(domain)。   但是在C++/CLI中,我們支持統一的類型系統。每一個類型,包括字面常量值,都是一個Object的子類。這也是我們爲什麽可以在一個字面常量值,或者內建類型的對象上直接調用方法的原因。整數字面常量5的類型爲Int32,字符串字面常量"Pooh"的類型爲String。認爲字符串字面常量更接近C風格的字符串,或者就把它看作C風格的字符串是不合適的。   集成後的類型轉換層級結構使得一個正常運行的ISO-C++程序在使用/clr編譯器開關重新編譯後仍能展現同樣的行爲,但是使用CLI類型的新的C++/CLI程序在處理字符串字面常量時將會體現新的類型優先排序規則。這段討論的長度相對于這個主題的重要性而言可能並不合適,但是它卻向大家揭示了我們到底在將CLI類型系統和ISO-C++語義框架集成在一起的時候,做了哪些工作,以及如何在必要的時候調整在集成過程中所出現的各個情況的優先級。同時,這也提醒大家在將一個本地類重新構造爲一個CLI類的過程中需要注重的一些問題。比如有些情況下我們最好要對那些接受字符串字面常量的成員函數進行新的設計,而不是簡單地將一個參數爲String的函數添加到這些重載函數集合中了事。   另外需要注重的是,String表示的是Unicode字符集。和ASCII字符集不同,這需要兩個字節來表示一個字符。雖然在C++/CLI中字符串字面常量的類型爲String,但這並不意味著在C++/CLI中,一個字符串字面常量必然會被解析爲雙字節的字符流。在本地C++中,我們要在字符串字面常量前加一個L來告訴編譯器將其看作一個雙字節的字符流。在C++/CLI中,我們仍然需要這麽做。 [url=/bbs/detail_1785120.html][img]http://image.wangchao.net.cn/it/1323424652200.gif[/img][/url] 更多內容請看C/C++技術專題專題,或
󰈣󰈤
王朝萬家燈火計劃
期待原創作者加盟
 
 
 
>>返回首頁<<
 
 
 
 
 熱帖排行
 
王朝網路微信公眾號
微信掃碼關註本站公眾號 wangchaonetcn
 
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有