從今日開始,將前期學習《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類型。