C++/CLI教程

C++/CLI编程入门指南

导言

  欢迎来到我的C++/CLI教程。我不确定它是否可以被称为合适的的教程,但无论如何我的意图是写一篇介绍性的文章,您可以从中学习这种语言的基础知识。在过去的2年里,我在以前的工作中一直使用C++/CLI 。并不是我决定使用这项技术,但现在我认为,我们老板为公司的项目选择C++/CLI是正确的。C++/CLI 是一种很好的语言,具有一些独特的功能,所以我认为它值得了解。这篇文章并非全面或系统。它更注重实践,基于在相当复杂的软件中使用C++/CLI的经验,所以我在这里只描述了这些我很熟悉的语言特性,以及在我代码中有用的部分,并不是语言所有的语法。
  如果你问这篇文章的难度,我会回答它是中级的。它并不高阶,而仅仅介绍了编程语言的基础知识。但另一方面我相信要理解C++/CLI ,您必须已经了解C++中的原生编程(包括头文件,指针,类等主题)以及. NET(包括有关垃圾收集器,Windows窗体,.NET标准库的知识)。

什么是C++/CLI? 

    C++/CLI是一种独立的编程语言,可以被视为C++语法的扩展,但同时它也是.NET平台的编程语言之一,就C#Visual Basic.NET一样。它是由微软设计和实现的,因此无法移植到Linux或其他系统。它需要Microsoft Visual IDE来编译和调试代码,因此我们没有可替代的编译器,如gccicc。好消息是Visual Studio 2005 20082010都支持该语言,包括免费的Visual C++ Express Edition的相应版本。

     要运行用C++/CLI编写的程序,就像其他.NET应用程序一样,用户必须在其Windows中安装适当版本的免费 Microsoft.NET Framework 。我没有成功地在Linux中运行C++/CLI程序,在Wine和Mono中也没成功过

为什么要使用C++/CLI?

如果我们有专门为.NET平台设计的语法良好的C# ,为什么还要学习和使用这种奇怪的混合语言呢C++/CLI 在其他.NET语言中有一个独特的特性:您可以在单个项目、单个源文件甚至单个函数代码中自由混合托管 (.NET)和原生(C++)代码。这使得该语言在某些应用程序中难以替换,例如:

  • 当您编写需要使用一些原生C++库和托管库的软件时。
  • 当您编写将原生C++代码与托管代码链接在一起的库时。例如,将某个原生C++库的接口暴露给.NET代码。
  • 当您编写一个程序时,它需要高效处理一些二进制数据(最好使用指针在原生C++代码中完成)并具有复杂的图形界面(最好使用Windows窗体完成)-就像我在工作中所做的那样。

C++/CLI不是什么?

  你可能会认为 C++/CLI 是某种丑陋的、专有的 C++ 拼凑物,是那个坏坏的 “M$” 引入来故意让程序员困惑的东西。这并不完全正确。微软努力让这种语言变得非常好。一些著名的计算机科学家和C++大师参与了它的开发,如Stanley B.LippmanHerb Sutter 。该语言本身被标准化为ECMA-372。

  我还必须澄清,C++ Microsoft Extensions这种旧的的语言,用奇怪和丑陋的关键字如__gc__value扩展了 C++法,该语言是C++/CLI的前身,但现在已被弃用。C++/CLI的语法完全不同,而且更加令人愉快,您将在接下来的章节中看 到。

  

Hello World示例

  现在是第一个示例代码的时候了。您可以在附件的存档中找Hello_world子目录。我在Visual C++2010 Express中编写了本教程的所有示例。通常第一个示例是输出Hello World 文本的简单应用程序。为此,我启动了Visual C++Express并创建了CLR控制台应用程序类型的新项目。所以它是C++/CLI项目,可以编译成EXE文件。它是.NET应用程序——它需要安装.NET Framework 。它显示系统控制台,没有窗口。Project包含一些自动生成的文件,包括AssemblyInfo.cpp ,您可以在其中填写有关程序的一些元数据,如产品名称和版本。但主源文件是Hello_world.cpp ,内容如下:

#include <iostream>
int main(array<System::String ^> ^args)
{
 System::Console::WriteLine(L"Managed Hello World");
 std::cout << "Native Hello World" << std::endl;
 return 0;
}

此示例显示了两条消息。第一个是使用传统的.NET方式完成的—— System.Console 类的WriteLine静态方法。第二种是使用C++推荐的方法完成的——来自标准C++库的std::cout流,这需要#include<iostream> 。如您所见,托管代码和原生C++代码在这里自由混合在一个函数代码中。这就是C++/CLI的最大力量!当然,这里还有更多奇 怪的事情可能会让您感到困惑,但不要担心-我稍后会解释它们。现在这是这个程序的输出:

Managed Hello World
Native Hello World

项目属性

  是时候深入了解语言语法的一些细节了。如果您已经知C++/或一些.NET语言和Visual Studio IDE ,我希望您知道如何进入项目属性窗口。在原生C++.NET的项目属性看起来完全不同。在C++/CLI中编码时看到的窗口类似于原生C++目。

  有一点我必须在开始时强调: 配置属性/常规 分支中的一个选项,称为Common Language Runtime Support。此选项决定编译器把你的代码当做原生C++C++/CLI来处理和编译。C++/CLI几个选项,所有选项都以/clr开头的。 您应该选择/clr ,而不是 /clr:pure  。我不确定他们具体做什么,但是按照这种方法切换后,多次帮助我修复了一些奇怪的编译器/链接器错误,例如讨厌的"Unresolved external symbol..."

命名空间

名称空间在原生C++代码和托管代码中的工作方式相似。在C++/CLI中,它们具有与C++相同的语法,并且可以自由混合。原生C++命名空间和托管命名空间之间没有区别,就像您将看到的类一样。这里有一个小例子,演示了如何定义一个命名空间(MyNamespace),如何使用命名空间来限定标识符(MyNamespace::DoEverything),以及如何通过 using namespace 指令 来导入一个命名空间。

#include <iostream>
using namespace System;
using namespace std;
namespace MyNamespace
{
  void DoEverything()
  {
    Console::WriteLine(L"Managed Hello World"); // System::Console
    cout << "Native Hello World" << endl; // std::cout, std::endl
  }
}
int main(array<System::String ^> ^args)
{
  MyNamespace::DoEverything();
  return 0;
}

类和指针

  这是您必须学习使用C++/CLI的最大知识-关于类,对象,指针,引用,析构函数,垃圾收集器等。别担心——我会一步一步地解释一切,展示一些简单的例子,并在最后总结一切。

  您需要知道的第一件事是,在C++/CLI中,原生C++类和托管类之间有严格的区别,并且某些规则适用于某些特定类型。这两种类型的指针/引用运算符也是不同的。指向原生C++对象的指针就像在原生C++中一样工作。您必须手动释放它们——它们没有垃圾回收器。另一方面,托管类的对象的行为类似于C#.NET平台的其余部分——你必须在堆上动态分配它们,并且它们会被垃圾收集器自动释放。对于这些托管对象,C++/CLI中有一个全新的语法,您很快就会看到。

原生C++类

让我们从您已经知道的东西开始——原生C++类。您可以定义并使用它们,就像没有托管部分一样,还是从前那个熟悉的原生C++ 。例如,定义如下类:

class NativeMonster
{
public:
  NativeMonster(int HP) : m_HP(HP) 
  {
   cout << "NativeMonster Constructor" << endl;
  }
  ~NativeMonster() 
  {
    cout << "NativeMonster Destructor" << endl;
  }
  void TellHitPoints() 
  {
    cout << "NativeMonster has " << m_HP << " HP" << endl;
  }
private:
  int m_HP;
};

NativeMonster是一个原生C++类。它有构造函数、析构函数、私有字段m_HP和公共方法

TellHitPoints 。您可以在某些代码中使用它,方法是在栈上按值创建此类型的本地对象,如下所示:

int main(array<System::String ^> ^args) 
{
  NativeMonster stack_monster(100);
  stack_monster.TellHitPoints();
}

此程序打印以下输出:

NativeMonster Constructor
NativeMonster has 100 HP
NativeMonster Destructor

您还可以使用标准C++运算符new在堆上分配原生C++对象。当不再需要时,请始终记住使用运算符delete释放,因为本机对象不会被垃圾回收器收集——忘记释放它们会导致内存泄漏!下面的程序打印相同的输出,但是这NativeMonster类型的对象是在堆上动态分配的,并通过指针而不是值使用。

int main(array<System::String ^> ^args) 
{
  NativeMonster *monster_pointer = new NativeMonster(100);
  monster_pointer->TellHitPoints();
  delete monster_pointer;
}

如你所见,原生C++类可以在C++/CLI中以与原生C++相同的方式进行编码。您可以使用class关键字,实现构造函数、析构函数、字段和方法(或者不实现,取决于您是否需要它们),然后您可以在栈或堆上创建该类的对象,用new分配它们,用delete释放它们,并用指针引用它们。您使用*运算符声明指针和解引用,&运算符声明引用和获取对象地址(示例中未显示) 以及->运算符从指针访问对象成员——所有这些都与 C++中完全相同。

原生C++结构看起来很相似,所以我不会在这里展示它们。就像在C++中一样,您可以使用struct关键字定义结构,然后按值或原生C++的*指针使用它们。

托管类

现在是时候看看托管类在C++/CLI中的样子了。下面是一个例子:

ref class ManagedMonster
{
public:
  ManagedMonster(int HP) : m_HP(HP) 
  {
    cout << "ManagedMonster Constructor" << endl;
  }
  void TellHitPoints()  
  {
    cout << "ManagedMonster has " << m_HP << " HP" << endl;
  }
private:
  int m_HP;
};

   这里首先引人注目的是使用了一个特殊的新关键字ref class 。它告诉编译器我们定义了一个托管类。Rest是相同的——您可以编写构造函数并在其中使用C++初始化列表,您可以定义字段和方法,您可以使private protectedpublic关键字。无论您是在本机类还是托管类方法的主体中,可以调用原生C++函数(如使用此处所用的std::cout)或托管函数(如使用System::Console::WriteLine打印到控制台

   托管类的对象从不在栈上创建,而只在堆上创建。一种新型的指针”——对托管对象的引用,在C++/CLI使用^字符。这次它是一个托管堆,非常不同的规则对对象的分配和回收。我们使用gcnew关键字分配它们。没有delete运算符,因为托管对象由垃圾收集器自动释放——在未来某个不确定的时刻,可能在另一个后台线程上,但总是在此类对象不再被任何指针引用之后。例如:

ManagedMonster ^monster_ref = gcnew ManagedMonster(120);
monster_ref->TellHitPoints();

此示例打印以下输出:

ManagedMonster Constructor
ManagedMonster has 120 HP

如果你想清除指针,只需给它赋值null 。但这不是普通的null ,您不能在此处使用NULL宏0 。您必须使 用特殊的nullptr关键字来完成此操作,该关键字是与托管指针兼容类型的空值。

monster_ref = nullptr;

C++/CLI中编码时,nullptr是在C++/CLI中作为关键字引入的。您也可以将其用作原生C++指针的空值。但是相同的关键字也被引入到新的原生C++11标准(以前称为C++0x)中,作为指针的0或NULL的替换,因此您也可以在原生C++中使用它。

托管结构体

  原生C++中的类和结构之间的差异非常小——默认情况下,结构具有公共成员和公共继承,除非您另有明确说明,而类默认为私有。另一方面,在C#中,结构非常不同——它们的功能有限,并且通过值而不是引用传递。

  C++/CLI通过classstruct关键字支持前者(原生类型),通过引入refvalue关键字支持后者(托管类型)。因此,要在C++/CLI中定义将按值存储的托管结构,如System::DateTime System::Drawing::Color 类型,请使用value classvalue struct 。例如:

value class Position 
{
  public: float x, y, z;
};
int main(array<System::String ^> ^args) 
{
  Position pos;
  pos.x = 0.0f;
  pos.y = 1.0f;
  pos.z = 7.0f;
  Console::WriteLine(L"The position is ({0},{1},{2})", pos.x, pos.y, pos.z);
}

因此,为了总结

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值