C++/CLI学习入门(一):基础
从今日开始,将前期学习《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  | 
 1  | 
 System::Boolean  | 
默认情况下,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函数后的参数为命令行参数。然后按如下方式改写代码
// 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; 
}
编译后执行得到如下输出:
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£ºF2} 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控制台程序的格式化输出
// 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;
}
三、控制台输入
.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};
例子:使用枚举类型
// 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;
}
该例子的输出为
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类型。
 
                    
                
                
            
        
浙公网安备 33010602011771号