從今日開始,將前期學習《Visual C++ 2005入門經典》(Ivor Horton著 清華大學出版社出版)的相關筆記整理到隨筆中,希望能和C++/CLI愛好者分享學習過程中的心得。文中主要內容和例子摘自原書相關章節,如有侵權,請留言或來信告知。
相比于ISO/ANSI C++而言,C++/CLI進行了大量的擴充,并且提供了大量的附加功能。主要包括:
-
在C++/CLI程序中,所有ISO/ANSI基本數據類型都可以使用,但在一些特殊的上下文環境中,它們具有一些額外屬性;
-
在控制臺程序中,C++/CLI對鍵盤和命令行輸出提供了自己的機制;
-
C++/CLI中引入了safe_cast運算符,確保強制類型轉換操作能夠生成可檢驗的代碼;
-
C++/CLI提供了另外一種基于類的枚舉功能,其靈活性超過了ISO/ANSI C++中的enum聲明。
一、基本數據類型
C++/CLI中包括了所有ISO/ASNI C++中的基本數據類型,算術運算也和本地C++完全一樣。除此之外,C++/CLI中還定義了2種整數類型,如表1所示:
表1:C++/CLI新增基本數據類型
類型 | 字節 | 值域 |
long long | 8 | 從-9223372036854775808到9223372036854775807 |
Unsigned long long | 8 | 從0到18446744073709551615 |
指定long long數據類型時,需要在整數數值后面加LL或小寫字母ll,如
longlong big = 123456789LL;
指定unsinged long long類型時,需要在整數數值后面加ULL或小寫字母ull,如
unsigned long long huge = 123456789LL;
在C++/CLI中,每一個ISO/ANSI C++基本類型名稱都映射到System命名空間中定義的值類類型。在C++/CLI程序中,ISO/ANSI C++基本類型名稱都是CLI中對應值類類型的簡略形式。表2給出了基本類型、占用內存以及對應的值類類型。
表2:基本類型與CLI值類型
基本類型 |
字節 |
CLI值類類型 |
bool char singed char unsigned char short unsigned short int unsigned int long unsigned long long long unsigned long long float double long double wchar_t |
1 1 1 1 2 2 4 4 4 4 8 8 4 8 8 2 |
System::Boolean System::SByte System::SByte System::Byte System::Int16 System::UInt16 System::Int32 System::UInt32 System::Int32 System::UInt32 System::Int64 System::UInt64 System::Single System::Double System::Double System::Char |
默認情況下,char類型被視為singed char,因此其關聯的值類類型為System::SByte。如果編譯選項/J,則char 默認為unsigned char,此時關聯為System::Byte。System為根命名空間名,C++/CLI的值類類型在這個空間中定義。此外System空間中還定義了許多其他類型,如表示字符串的String類型、精確存儲的十進制小數類型Decimal等等。
在C++/CLI中,關聯的值類類型為基本類型添加了重要的附加功能。編譯器在需要時,將安排原值與關聯類型之間的自動轉換,其中從原值轉換為關聯類型成為裝箱(boxing),反之稱為拆箱(unboxing)。根據上下文環境,這些變量將表現為簡單的值或者對象。
由于ISO/ANSI C++基本類型的名稱是C++/CLI程序中值類類型名稱的別名,所以原則上C++/CLI代碼中可用任何一種名稱。
int count = 10;
double value = 2.5;
與下面的代碼是等價的
System::Int32 count = 10;
System::Double value = 2.5;
上面2種代碼是完全合法的,但應盡量使用基本類型名稱,如int和double,而不是System::Int32和System::Double。這是因為上面描述的這種映射關系僅適用于Visual C++ 2005及以上版本的編譯器,其他版本編譯器未必實現這種映射關系。
將基本類型轉換為值類類型是C++/CLI的一個重要特征。在ISO/ANSI C++中基本類型與類類型完全不同,而在C++/CLI中,所有數據都以類類型的形式存儲,包括值類型(存儲在堆棧上)和引用類型(存儲在堆上)2種。
例子:Fruit CLR控制臺項目
在Visual Studio 2005中創建CLR Console Application項目,輸入名稱Ex2_12,將生成如下文件
// Ex2_12.cpp : main project file.
#include "stdafx.h"
using namespace System;
int main(array<System::String ^> ^args)
{
Console::WriteLine(L"Hello World");
return 0;
}
main函數后的參數為命令行參數。然后按如下方式改寫代碼
- - - - - - - - - - - - - - - - <<== 華麗的分割線 ::開始==>> [Ex_12.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Ex2_12.cpp : main project file.
#include "stdafx.h"
using namespace System;
int main(array<System::String ^> ^args)
{
int apples, oranges;
int fruit;
apples = 5;
oranges = 6;
fruit = apples + oranges;
Console::WriteLine(L"\nOranges are not the only fruit ...");
Console::Write(L"- and we have ");
Console::Write(fruit);
Console::Write(L" fruit in all.\n");
return 0;
}
- - - - - - - - - - - - - - - - <<== 華麗的分割線 ::結束==>> [Ex_12.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
編譯后執行得到如下輸出:
Oranges are not the only fruit?-
- and we have 11 fuit in all.
與ISO/ANSI C++版本比較,變量的類型int將成為C++/CLI類型System::Int32。如果用System::Int32替換代碼中的int,然后重新編譯運行,結果將沒有變化。
WriteLine()函數為C++/CLI函數,定義在System命名空間的Console類中。Console表示標準輸入輸出流。Write()函數為該類的另一個輸出函數,不會自動換行。下面專門討論C++/CLI的控制臺輸入輸出。
二、控制臺輸出
C++/CLI中特有控制臺格式化輸出功能,例如可用下面的代碼來輸出字符串與變量混合文本。
Console::WriteLine(L"There are {0} fruit.", fruit);
Console::WriteLine()的第一個參數是L”There are {0} fruit.”,其中{0}為格式化占位符,表示在此處插入第二個參數的值,如果有更多需要插入的參數,則該參數對應的占位符編號繼續增加:{1}、{2}、{3}…。在第一個字符串參數中,編號的順序可以顛倒,如
Console::WriteLine(L"There are {1} packages weighting {0} pounds", packageWeight, packageCount);
格式化占位符還可以控制顯示的格式,如{1:F2}表示第2個參數顯示成有2位小數的浮點數,冒號后的為格式規范
Console::WriteLine(L"There are {0} packages weighting {1:F2} pounds ", packageCount, packageWeight);
輸出為
There are 25 packages weighting 7.50 pounds.
一般說來,可以編寫格式為{n,w:Axx}的格式規范,其中n為索引值,用于選擇逗號后的第幾個參數; A為單個字母,表示如何對變量格式化;xx為1個或2個數字,指定參數的精度;w為有符號整數,表示可選的字段寬度范圍,如果w為+,則字段右對齊,如果w為-,則左對齊。如果數值的位置數小于w指定的位置數,則多出來的用空格填充,如果數值的位置數大于w指定的位置數,則忽略w的限定:
Console::WriteLine(L"Packages: {0,3} Weight: {1,5£oF2} pounds ", packageCount, packageWeight);
輸出為(下劃線表示空格填充位):
Packages: _25 Weight: __7.50 pounds
可選的格式說明符如表3所示
表3:格式說明符
格式說明符 |
說明 |
C或c |
把值作為貨幣量輸出 |
D或d |
把整數作為十進制值輸出。如果指定的精度大于位數,則在數值的坐標填充0 |
E或e |
按照科學技術法輸出浮點值 |
F或f |
把浮點數作為±####.##的定點數輸出 |
G或g |
以最緊湊的形式輸出,取決于是否指定了精度值,如果沒有則適用默認精度 |
N或n |
把值作為定點十進制值輸出,必要時以3位一組用逗號分隔 |
X或x |
把整數作為十六進制值輸出。根據X或x,以大寫或小寫輸出。 |
例子:計算地毯價格,演示CLR控制臺程序的格式化輸出
- - - - - - - - - - - - - - - - <<== 華麗的分割線 ::開始==>> [Ex_13.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Ex2_13.cpp : main project file.
#include "stdafx.h"
using namespace System;
int main(array<System::String ^> ^args)
{
double carpetPriceSqYd = 27.95;
double roomWidth = 13.5; // in feet
double roomLength = 24.75; // in feet
const int feetPerYard = 3;
double roomWidthYard = roomWidth/feetPerYard;
double roomLengthYard = roomLength/feetPerYard;
double carpetPrice = roomWidthYard*roomLengthYard*carpetPriceSqYd;
Console::WriteLine(L"Room is {0:F2} yards by {1:F2} yards",
roomWidthYard, roomLengthYard);
Console::WriteLine(L"Room area is {0:F2} square yards",
roomWidthYard*roomLengthYard);
Console::WriteLine(L"Carpet price is ${0:F2}", carpetPrice);
return 0;
}
- - - - - - - - - - - - - - - - <<== 華麗的分割線 ::結束==>> [Ex_13.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
三、控制臺輸入
.Net Framework的控制臺鍵盤輸入功能有限,可以適用Console::ReadLine()函數把整行輸入作為字符串讀取,或者使用Console::Read()讀取單個字符,還可以適用Console::ReadKey()讀取按鍵。ReadLine()的例子如下:
ReadLine()用于將整行文本存入字符串中,按下Enter鍵時,文本結束。變量line為String^型,表示String數據類型的引用,line為Console::ReadLine()函數讀入字符串的引用。
String^ line = Console::ReadLine();
Read()用于逐字符的讀入輸入數據,并將其轉換成對應的數字值。ReadKey的例子如下:
char ch = Console::Read();
ReadKey()用于讀取按鍵值,并返回ConsoleKeyInfo對象,該為對象定義在System命名空間中的值類型。參數true表示按鍵不在命令行上顯示出來,false則表示顯示按鍵回顯。按鍵對應的字符可用ConsoleKeyInfo對象的KeyChar得到。
ConsoleKeyInfo keyPressed = Console::ReadKey(true);
Console::WriteLine(L"The key press corresponds to the character: {0}", keyPress.KeyChar);
盡管C++/CLI控制臺程序中不能格式化輸入,但輸入一般都通過窗口組件得到,因此這僅僅是一個小缺陷。
四、強制類型轉換safe_cast
在CLR環境中safe_cast用于顯示的強制類型轉換。safe_cast用于將一種類型轉換為另一種類型,在不成功時能夠拋出異常,因此在C++/CLI中使用safe_cast是比較好的選擇。其用法和static_cast一樣:
double value1 = 10.5;
double value2 = 15.5;
int whole_number = safe_cast<int>(value1) + safe_cast<int>(value2);
五、枚舉
C++/CLI的枚舉與ISO/ANSI C++有較大的區別。下例為C++/CLI中的一個枚舉類型:該語句定義了一個枚舉類型Suit,該類型的變量只能被賦值枚舉定義中的值,且必須用枚舉類型名稱限定枚舉常數。
enum class Suit {Clubs, Diamonds, Hearts, Spades};
Suit suit = Suit::Diamonds;
注意class關鍵字跟在enum之后。說明該枚舉類型為C++/CLI,該關鍵字還表明在定義中規定的常量: Clubs\Diamonds\Hearts\Spades都是類對象而非ISO/ANSI C++中的基本類型(整型)值。實際上,默認情況下這些都是Int32類型的對象。
由于C++/CLI枚舉定義中的變量都是類對象,因此不能在函數內部定義。
(一)指定枚舉常量的類型
枚舉中常量的類型可以是下表中任一基本類型:
short |
int |
long |
long long |
signed char |
char |
unsigned short |
unsigned int |
unsigned long |
unsigned long long |
unsigned char |
bool |
要指定一個枚舉常量的類型,可以在枚舉類型名稱之后寫入常量類型名稱(要用冒號隔開),下例枚舉類型中的常量為Char類型,對應的基本類型為char。其中第一個常量默認情況下對應于代碼值0,后面的依次遞增。
enum class Face:char { Ace,Two,Three,Four,Five,Six,Seven,Eight,Nine,Ten,Jack,Queen,King};
(二)指定枚舉常量的值
可以賦予枚舉類型定義中的一個或全部常數對應的值,下例使得Ace獲得1,Two獲得2,其余依此類推,直到King=13。
enum class Face:char { Ace=1,Two,Three,Four,Five,Six,Seven,Eight,Nine,Ten,Jack,Queen,King};
如果想讓Ace獲得最大值,則可以如下定義:
enum class Face:Char { Ace=14,Two=2,Three,Four,Five,Six,Seven,Eight,Nine,Ten,Jack,Queen,King};
例子:使用枚舉類型
- - - - - - - - - - - - - - - - <<== 華麗的分割線 ::開始==>> [Ex_14.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Ex2_14.cpp : main project file.
#include "stdafx.h"
using namespace System;
enum class Suit {Clubs, Diamonds, Hearts, Spades};
int main(array<System::String ^> ^args)
{
Suit suit = Suit::Clubs;
int value = safe_cast<int>(suit);
Console::WriteLine(L"Suit is {0} and the value is {1}", suit, value);
suit = Suit::Diamonds;
value = safe_cast<int>(suit);
Console::WriteLine(L"Suit is {0} and the value is {1}", suit, value);
suit = Suit::Hearts;
value = safe_cast<int>(suit);
Console::WriteLine(L"Suit is {0} and the value is {1}", suit, value);
suit = Suit::Spades;
value = safe_cast<int>(suit);
Console::WriteLine(L"Suit is {0} and the value is {1}", suit, value);
return 0;
}
- - - - - - - - - - - - - - - - <<== 華麗的分割線 ::結束==>> [Ex_14.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
該例子的輸出為
Suit is Clubs and the value is 0
Suit is Diamonds and the value is 1
Suit is Hearts and the value is 2
Suit is Spades and the value is 3
例子說明
- Suit為枚舉類型,不能在函數main()內部定義,因此只能定義為全局作用域內。
- 必須用類型名稱Suit限定枚舉常量,如Suit::Clubs,否則編譯器將無法識別。
- 變量suit的值為類對象,要獲取其值必須顯示的將其轉換成int類型。