从今日开始,将前期学习《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£º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控制台程序的格式化输出
- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::开始==>> [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类型。