• <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>
  • C++/CLI基本數據類型探索

    發表于:2007-05-25來源:作者:點擊數: 標簽:導讀C++CLI探索基本
    導讀 :本文向大家揭示了在將CLI類型系統和ISO-C++語義框架集成在一起的時候,微軟做了哪些調整工作,以及如何在必要的時候調整在集成過程中所出現的各個情況的優先級。同時,這也提醒大家在將一個本地類型重新構造為一個CLI類的過程中需要注意的問題。 C++/
     導讀:本文向大家揭示了在將CLI類型系統和ISO-C++語義框架集成在一起的時候,微軟做了哪些調整工作,以及如何在必要的時候調整在集成過程中所出現的各個情況的優先級。同時,這也提醒大家在將一個本地類型重新構造為一個CLI類的過程中需要注意的問題。

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

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

    clearcase/" target="_blank" >cc66" width="90%" align="center" bgcolor="#dadacf" border="1">
    int ival = 0;
    Int32 ival2 = 0;

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

    基本類型 System命名空間中對應的類 注釋/用法
    bool System::Boolean bool dirty = false;
    char System::SByte char sp = ' ';
    signed char System::SByte signed char ch = -1;
    unsigned char System::Byte unsigned char ch = '\0';
    wchar_t System::Char wchar_t wch = ch;
    short System::Int16 short s = ch;
    unsigned short System::UInt16 unsigned short s = 0xffff;
    int System::Int32 int ival = s;
    unsigned int System::UInt32 unsigned int ui = 0xffffffff;
    long System::Int32 long lval = ival;
    unsigned long System::UInt32 unsigned long ul = ui;
    long long System::Int64 long long etime = ui;
    unsigned long long System::UInt64 unsigned long long mtime = etime;
    float System::Single float f = 3.14f;
    double System::Double double d = 3.14159;
    long double System::Double long double d = 3.14159L;
                表1 基本類型和它們在System命名空間中對應的類

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

    int imaxval = int::MaxValue;
    int iminval = Int32::MinValue;

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

    String^ bonus = "$ 12,000.79";

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

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

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

    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" );
    }
      在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.從候選函數集合中選擇可用函數集合??捎煤瘮凳侵负瘮德暶鲿r的參數個數和它們的類型與調用時所指定的相匹配的那些函數。在我們上面的例子中,三個候選函數都是可用函數。如果這個階段得到的可用函數集合為空,那么調用也將失敗。

      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實際上在技術的定義上是很嚴格的??偣灿?種這樣的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中,我們仍然需要這么做。

    原文轉自:http://www.kjueaiud.com

    評論列表(網友評論僅供網友表達個人看法,并不表明本站同意其觀點或證實其描述)
    老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月

  • <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>