![Visual C++.NET 2010开发实践:基于C++/CLI](https://wfqqreader-1252317822.image.myqcloud.com/cover/289/680289/b_680289.jpg)
1.2 简单的C++/CLI程序
在本节中通过创建一个简单的CLR控制台应用程序,来简单了解C++/CLI的语法结构。
1.2.1 创建CLR控制台程序
在Visual Studio 2010集成开发环境中创建一个CLR控制台应用程序。该程序将从控制台读取用户输入的值,并根据该值来计算圆的面积,并输出到控制台上。
【例1.1】 创建CLR控制台程序。
(1)启动Visual Studio 2010集成开发环境,在菜单栏中选择“文件”→“新建”→“项目”菜单命令,弹出如图1.4所示的“新建项目”对话框。
![](https://epubservercos.yuewen.com/B3F144/3590443504715001/epubprivate/OEBPS/Images/figure_0015_0001.jpg?sign=1738966156-Hs9Di2tGYFm4Toe5kTcAIFhjBqzzAsYx-0-3e8b47c04ce71b6d89c6818cf8ec7450)
图1.4 “新建项目”对话框
(2)在对话框的左侧选择“Visual C++”节点下的“CLR”节点,并在对话框的中部选择“CLR控制台应用程序”项。在“名称”栏中输入Ex1_1,并在“位置”栏中指定该项目保存的位置,可以单击“浏览”按钮选择其他保存路径。
(3)单击“确定”按钮后,集成开发环境会自动创建Ex1_1项目,并自动打开源代码编辑窗口。此时可以在Ex1_1.cpp源文件的main函数中添加计算圆面积的代码。代码如下:
// Ex1_1.cpp : 主项目文件 #include "stdafx.h" using namespace System; int main(array<System::String ^> ^args) { Console::Clear(); Console::Write(L"输入圆的半径: "); double r = Double::Parse(Console::ReadLine()); double area = 3.14159 * r * r; Console::WriteLine(L"半径为{0}的圆面积为: {1}", r, area); return 0; }
(4)选择“生成”→“生成解决方案”菜单命令以编译项目,并选择“调试”→“开始执行(不调试)”菜单命令以运行项目。Ex1_1项目运行的结果如图1.5所示。
![](https://epubservercos.yuewen.com/B3F144/3590443504715001/epubprivate/OEBPS/Images/figure_0016_0001.jpg?sign=1738966156-VhvcyY6lY6l5hYCz4hVAkfOj0eBRNHBk-0-8ef6b48c597674fd020511c7de11ac2d)
图1.5 Ex1_1项目的运行结果
从整体上看,使用C++/CLI编写的程序的结构与标准C++编写的程序的结构相似,其中#include指令将stdafx.h头文件包含到Ex1_1.cpp源文件中,using指令声明了使用的命名空间,而main函数为应用程序的入口函数。
1.2.2 命名空间
命名空间是为了防止相同名字的不同标识符发生冲突而设计的隔离机制,同时是一个程序集内相关类型的一个分组。例如,System命名空间定义了许多的基本数据类型,System::IO命名空间中包含了有关I/O的类型,而System::Data命名空间则定义了基本的数据库类型等。
C++/CLI编写的应用程序可以使用.NET基类库中的类型。在.NET基类库中,最基本的命名空间是System,其中提供了大量核心的类型,并在程序中会经常地使用。.NET常用命名空间及其说明如表1.1所示。
表1.1 .NET常用命名空间及其说明
![](https://epubservercos.yuewen.com/B3F144/3590443504715001/epubprivate/OEBPS/Images/figure_0016_0002.jpg?sign=1738966156-UyGBjRSFCgEqMmNzsOdkCjNAv5rkZFvo-0-9ea68e24b8edf9c5fff70bdf2f42569d)
命名空间提供了一种从逻辑上理解和组织关联类型的方式。与标准C++相同,C++/CLI中同样通过using指令来将特定命名空间引入到当前程序文件中。如果需要使用.NET中不同功能的类型时,需要引入该类型所在的命名空间。例如:
using System; // 通用基类库类型 using System::Drawing; // 图形呈现类型 using System::Windows::Forms; // GUI窗口部件类型 using System::Data; // 通用以数据为中心的类型 using System::Data::SqlClient; // SQL Server数据访问类型
当引入某个命名空间后,就可以使用该命名空间中的所有包含的类型。例如,如果需要创建Bitmap类型的实例对象,那么必须引入System::Drawing命名空间。
using System; using System::Drawing; int main(array<System::String ^> ^args) { Bitmap^ bmp = gcnew Bitmap( 32, 32 ); // 创建一个大小为20×20像素的位图 … return 0; }
如果未引入System::Drawing命名空间,那么当编译器在解析Bitmap类型时会产生编译错误。然而,可以通过使用完全限定名来声明一个未引入命名空间的类型。例如:
using System; int int main(array<System::String ^> ^args) { System::Drawing::Bitmap^ bmp = gcnew System::Drawing::Bitmap( 32, 32 ); … return 0; }
虽然,使用完全限定名同样可以正确使用命名空间中的某个类型,但是使用using指令明显可以减少按键的次数。
1.2.3 应用程序入口
从前面的程序中可以看出,C++/CLI程序同样包含main函数,而该函数仅有一个形参,这就是String^类型的数组。
main函数的参数与标准C++中main函数的参数的作用相同,它们都是用于接收在命令行上执行程序时的输入参数。C++/CLI所有的数组中都包含了一个Length属性以记录该数组中包含的元素数,因此可以遍历整个数组来访问args数组中的所有参数。
【例1.2】 访问在命令行上执行程序的参数。其中,在main函数内通过for循环遍历整个数组中的参数,并输出到控制台上。代码如下:
// Ex1_2.cpp: 主项目文件 #include "stdafx.h" using namespace System; int main(array<System::String ^> ^args) { Console::WriteLine(L"命令行参数: "); for ( int i = 0; i<args->Length; i++) // 输出所有参数 Console::WriteLine(L"参数{0}: {1}", i, args[i]); return 0; }
当成功编译并连接Ex1_2项目后,选择“开始”→“所有程序”→“附件”→“命令提示符”菜单命令。然后在命令行上执行Ex1_2.exe程序,并在后面输入一些参数,其执行的结果如图1.6所示。
![](https://epubservercos.yuewen.com/B3F144/3590443504715001/epubprivate/OEBPS/Images/figure_0018_0001.jpg?sign=1738966156-nVdZxcacMlwNuPnFKg8jT01K1SnkRyRD-0-68e80dfc3f1f4941402d25623a31ec7a)
图1.6 Ex1_2项目的运行结果
1.2.4 控制台输入/输出
控制台的输入、输出是由System命名空间中的Console类实现的,该类封装了基于控制台应用程序的输入、输出和错误流操作。
1. 控制台输出
Console类提供了几个静态方法用于在控制台上输出字符串或含有变量的混合文本。其中,该类的WriteLine方法用于在控制台上输出一行文本,并自动换行;而Write方法则可以输出单个值,但不会自动换行。例如:
Console::Write(L"半径为8的圆"); Console::WriteLine(L"面积为: {0}", 3.14*8*8 );
WriteLine方法是将该方法名后面的圆括号中的所有内容都输出到命令行上,然后在这些内容的末尾添加一个换行符,并将光标移动到下一行的起始位置。字符串前的L表示字符串中的字符将按照宽字符输出,其中每个字符占两个字节。
Write方法基本上和WriteLine方法相同,其区别在于该方法并不在输出字符串的末尾自动添加换行符。因此,可以通过多次调用Write方法将多个数据输出到同一行中。
Write和WriteLine方法都可以接受0个、1个或多个参数,这些参数之间通过逗号(“,”)隔开。在这两个方法输出的字符串中,可以通过大括号(“{ }”)来分别引用所带的参数。例如:
Console::WriteLine(L"半径为{0}的圆面积为: {1}", 8, 3.14*8*8);
其中,{0}为格式化占位符,表示将在该处插入所引用的参数的值。如果需要插入更多的参数,则可以增加占位符所对应参数的编号,如{1}、{2}、{3}等,并且编号的顺序可以颠倒。例如:
Console::WriteLine(L"第{1}个半径为{2}的圆面积为: {0}", 3.14*8*8, 1, 8);
另外,还可以在格式化占位符中指定输出的格式、对齐方式及精度等。通常,可以指定格式化占位符的格式规范为{n,w:Axx},其中n为索引值,用于选择逗号后的第几个参数;A为单个字母的格式化说明符,表示将如何对变量格式化,其可选的值如表1.2所示。
表1.2 常用的格式说明符及说明
![](https://epubservercos.yuewen.com/B3F144/3590443504715001/epubprivate/OEBPS/Images/figure_0019_0001.jpg?sign=1738966156-7T9d4vtdnTFrfeI5N37PXgRdo9xr29rt-0-b18b445535ecb90aa3f3849e1302c390)
xx为1个或2个数字,指定输出参数值的精度;而w为有符号整数,用于表示可选的字段宽度范围,如果w的值为正数,则输出的字段将右对齐,而如果w的值为负数,则将左对齐。如果数值的位置数小于w指定的位置数,则多出来的空位用空格填充;如果数值的位置数大于w指定的位置数,则忽略w的限定。例如:
Console::WriteLine(L"圆的半径为: {0, 3}, 面积为: {1, 5:F6}", 8, 3.1415926f*8*8);
【例1.3】 控制台格式化输出的示例。在main函数中通过调用Console类的WriteLine方法,以不同方式格式化输出一个负整数和一个浮点数。代码如下:
// Ex1_3.cpp: 主项目文件 #include "stdafx.h" using namespace System; int main(array<System::String ^> ^args) { // 以不同方式格式化输出一个负整数和一个浮点数 Console::WriteLine("标准数字格式字符串:"); Console::WriteLine( L"(C) 货币格式: {0:C}\n" + L"(D) 十进制格式: {0:D}\n" + L"(E) 指数计数法: {1:E}\n" + "(F) 顶点格式: {1:F}\n" + "(G) 常规格式: {0:G}\n" + " (默认格式): {0} (default = 'G')\n" + "(N) 数字格式: {0:N}\n" + "(P) 百分比格式: {1:P}\n" + "(X) 十六进制格式: {0:X}\n", -123, -123.45f); return 0; }
Ex1_3项目运行后,将根据标准数字格式字符串来分别格式化输出负整数-123和浮点数-123.45f。其执行结果如图1.7所示。
![](https://epubservercos.yuewen.com/B3F144/3590443504715001/epubprivate/OEBPS/Images/figure_0020_0001.jpg?sign=1738966156-Q0OtTZIEWKl4pR63dgDZGj91G6BEWzg8-0-077bc922bc27d17edbf183acef0224c7)
图1.7 Ex1_3项目的运行结果
2. 控制台输入
.NET框架中的Console类还提供几个方法用于从控制台中读取键盘的输入字符。其中,该类的ReadLine方法可以将整行的输入作为字符串读取,而Read方法则读取单个字符。另外,还可以通过ReadKey方法读取按键的值。
ReadLine方法将整行文本存入到一个字符串中,当按下【Enter】键时,则结束文本的读取。该方法返回一个String^类型的变量,以表示一个String数据类型的引用,其中包含实际读入的字符串。例如:
String^ line = Console::ReadLine();
由于ReadLine方法将以字符串形式返回输入的内容,所以如果需要获取从控制台输入的整数和双精度浮点数时,可以通过这些基本数据类型的包装类的Parser方法将字符串解析成该类型的值。例如:
int value1=Int32::Parse( Console::ReadLine() );//将输入转换为int类型的值 double value2=Double::Parse( Console::ReadLine());//将输入转换为double类型的值
如果需要按照逐个字符的读取方式输入数据,那么可以调用Read方法读取输入的字符,并将其转换成对应的数字值。例如:
char ch = Console::Read(); // 读取一个字符,并返回该字符的ASCII码 int n = Console::Read();
Console类的ReadKey方法可以用于测试按键,并将读取的结果存储在ConsoleKeyInfo类对象中。可以通过给ReadKey方法传递一个bool类型的参数来指定按键是否在命令行中回显,值为true表示按键不显示在命令行上,而值为false则表示显示按键回显。
ConsoleKeyInfo类有3个可访问属性用于帮助确定被按下的是哪个或哪些键。其中,属性Key是ConsoleKey枚举类型,用于指定被按下的键;属性KeyChar表示按下的键的Unicode字符码,该字符码以wchar_t类型存储;属性Modifiers是ConsoleModifiers枚举类型,用于表示是否是Shift、Alt或Ctrl键的按键组合。例如:
ConsoleKeyInfo keyPressed = Console::ReadKey(true); Console::WriteLine(L"控制台按键: {0}", keyPressed . Key); Console::WriteLine(L"按键的Unicode码: {0}", keyPressed . KeyChar); Console::WriteLine(L"Shift Alt Ctrl按键: {0}", keyPressed . Modifiers);
【例1.4】 控制台输入示例。在main函数中通过循环调用Console类的ReadKey方法,获取用户的按键信息,并且当按【Esc】键时退出。代码如下:
// Ex1_4.cpp: 主项目文件 #include "stdafx.h" using namespace System; int main(array<System::String ^> ^args) { ConsoleKeyInfo cki; Console::WriteLine(L"按下一个键及任意Ctrl、Alt和Shift组合键."); Console::WriteLine(L"(按Esc键退出)"); do { cki = Console::ReadKey(true); Console::Write(L"已按下了: "); Console::Write (L"{0}键,", cki.Key); Console::Write (L"Unicode码: {0},", cki.KeyChar); Console::Write (L"组合键: {0}", cki.Modifiers); Console::WriteLine(); } while (cki.Key != ConsoleKey::Escape); return 0; }
当Ex1_4程序运行时,每当用户按下一个键或者包含【Ctrl】、【Shift】或【Alt】键的组合键时,都将输出该键的键值、Unicode码及其组合键。Ex1_4项目的运行结果如图1.8所示。
![](https://epubservercos.yuewen.com/B3F144/3590443504715001/epubprivate/OEBPS/Images/figure_0021_0001.jpg?sign=1738966156-JGXWZTqGZjefd3pUuyrbCpk6dFNfHEoP-0-d762fbf5f09bf659411600a75cfd4d8d)
图1.8 Ex1_4项目的运行结果