副标题#e#
在托管堆上分派工具实例,好像是利用托管扩展C++、C#、J#、VB.NET措施员的独一要领,而利用当地C++的措施员,不单可以在堆上分派内存,甚至更惯于利用基于仓库的工具实例。
此刻回首一下以前界说的Point引用类,再来看一下以下变量界说:
Point p1, p2(3,4);
从当地C++的角度来说,p1与p2应为基于仓库的引用类Point实例,哪怕是从一般性的角度来看,它们也是。P1由默认的结构函数初始化,而p2由接管x与y坐标的结构函数初始化。从实现上来看,Point是自包括范例的(也就是说,它不包括任何指针或句柄),然而,作为一个引用类的实例,它仍处于CLI运行时的掌控之下,且在须要时,会被垃圾接纳–正因为此,所以不能界说一个引用类的静态或全局实例。
同时,也不能将sizeof应用于指明是引用类实例的表达式,因为sizeof是在编译时举办计较的,而Point工具的巨细要直到运行时才气确定;可是,可将sizeof应用于句柄,因为它的巨细在编译时就已经确定了。
别的,还不能界说一个基于仓库的CLI数组实例。
跟踪引用
当地C++可通过&来界说一个工具的别名,譬喻,对任意当地类N,可编写如下代码:
N n1;
N& n2 = n1;
引用必需在界说时举办初始化,且在整个生命期中,它们都锁定于引用同一工具,也就是说,它的值不会改变。引用一个引用类的实例与引用一个当地类根基一致,只不外语法差异罢了。
在措施执行期间,引用类的实例会在内存中"移动",所以,需要对它们举办跟踪,而当地指针与引用却不可以或许胜任这项事情(尤其指不能对一个引用类的实例利用取地点符&),因此,C++/CLI对应地提供了句柄及用于跟踪的引用–在此简称为跟踪引用(Tracking References),譬喻,你可以界说一个跟踪引用p3,以追踪工具p2:
#p#副标题#e#
GC-Lvalues
在C++尺度中界说及利用了lvalue术语,而C++/CLI尺度则添加了gc-lvalue术语,其指"一个引用CLI堆中工具、或包括此工具的数值成员的表达式"。假如有一个指向gc-lvalue的句柄,可对其利用一元 * 操纵符来发生一个gc-lvalue;而跟踪引用也是一个gc-lvalue,当%h中h是一个句柄时,它也可以发生一个gc-lvalue。(因为有从lvalue至gc-lvalue的尺度转换,所以一个跟踪引用可绑定至任意的gc-lvalue或lvalue。)
拷贝结构函数
在下面的例子中,p6由给定的坐标结构而成,而p7则初始化为p6的一个副本,这就需要Point有一个拷贝结构函数;然而,在默认环境下,编译器不会为这些引用类发生一个拷贝结构函数。那么,在这种环境下,就必需本身编写一个。
Point p6(3,4), p7 = p6;
以下,是Point的拷贝结构函数:
Point(Point% p)
{
X = p.X;
Y = p.Y;
}
而对一个当地类N的拷贝结构函数,一般声明成如下形式:
N(const N& n);
可是,对引用类来说,因为%代替了&,所以在CLI的世界中,const显得有点扞格难入。
赋值操纵符
以下表达式:
p7 = p6;
就需要一个赋值操纵符,但再次提醒,这不是自动提供的。以下就是一个自界说的操纵符例子:
Point% operator=(Point% p)
{
X = p.X;
Y = p.Y;
return *this;
}
之所以没有提供默认的拷贝结构函数或赋值操纵符,是因为所有的引用类(除了System::Object),都有一个基类:System::Object,而这个类并没有提供一个拷贝结构函数或赋值操纵符。根基上,这两者默认城市挪用它们基类中相应的实现版本,但基类中却一个对应的界说也没有。
相等性操纵符
通过为Point界说一个拷贝结构函数和一个赋值操纵符,就可以处理惩罚那些数值范例的实例了,你可以初始化它们、把它们传给函数、或把它们从函数中返回;但实际上,大概还再需要一个操纵符–相等性较量操纵符,它能像如下界说:
static bool operator==(Point% p1, Point% p2)
{
if (p1.GetType() == p2.GetType())
{
return (p1.X == p2.X) && (p1.Y == p2.Y);
}
return false;
}
由于一个跟踪引用不行能为数值nullptr,所以就不必对此值举办查抄了,又由于p1与p2是两个Point的别名,所以可利用点操纵符挪用GetType和属性X与Y的get措施。
能同时满意两方面需求吗?
以前说过,对一个引用类而言,相等性的鉴别是通过一个Equals函数而不是重载 == 操纵符来实现的,而且重载了一个接管句柄的 == 操纵符,指出了利用上的问题。那让我们再往返首一下这个话题。
#p#分页标题#e#
当在C++/CLI中设计并实现一个引用类时,就要想到"这个类的利用者,会利用C++/CLI语言举办编程,照旧会利用如C#、J#、VB.NET之类的其他语言呢,可能两者都利用呢?"
C++措施员习惯于把类实例看成数值来看待,所以,他们等候类中有一个拷贝结构函数及一个赋值操纵符,且对某些类来说,还会等候实现相等或不相等操纵符;另一方面,C#、J#、VB.NET措施员只能通过句柄来哄骗类实例,所以他们只想要克隆或Equals函数,至于拷贝结构函数与赋值操纵符,他们无须知道,也无须体贴。
即便C++措施员更倾向于利用 == 操纵符,但一个带有Equals函数的引用类可被任意语言所挪用,所以在设计引用类时应只管实现此函数,不外话说返来,假如对一个不包括Equals函数的类实例挪用此函数,将会发生无法预料的效果。
假如在一个引用类中,提供了可接管两个跟踪引用的 == 操纵符函数,一般上也可满意C++/CLI措施员的需要。固然也能提供一个接管两个句柄的 == 操纵符函数,但好像不行能被这两组措施员利用。
简而言之,既可为C++/CLI措施员,也可为其他.NET语言措施员、或同时为两者实现一个引用类,那么,是不是可把它们简朴地分为C++/CLI与"其他语言"两个阵营呢,但工作好像总不是这么简朴的,举例来说,固然System::String是一个引用类,它提供了可接管两个句柄的 == 操纵符与 != 操纵符函数,可是,较量的是字符串的值,而不是它们的句柄。一般来说,在引用类中利用值这个说法,是有点让人感受怪怪的,但对一个string类来说,却又是合情公道的。
在此很是清楚的一点是,万能的要领是不存在的。为对引用类的利用者,提供最适当的接口,就必需在基于他们所利用语言的基本上,多思量一下他们的期望。但无论如何,C++/CLI措施员想要利用其他语言建设的引用类,就不得不要适应没有拷贝结构函数与赋值操纵符这些环境。
其他话题
以下的话题很是简短、但却十分有辅佐:
1、前面也提到,const不是很适合CLI,且在引用类中,C++/CLI也不答允用const(或volatile)来限定成员函数;但个要害字可用在某些类中实例结构函数或成员函数上。那么在此环境下,它的范例到底是什么呢?对当地范例N来说,它是N* const;然而,对一个引用类R来说,它只是一个R^。固然句柄不是const限定的,但它的值却不能被修改。
2、Point::ToString的实现利用了如下形式:
return String::Concat("(", X, ",", Y, ")");
别的也可以像下面这样写:
return String::Format("({0},{1})", X, Y);
正如它的名字,Format函数答允对文本举办名目化(如前导的空格或零、可能一些脱离符等等),而不是简朴地对字符串举办毗连。
3、假如想要知道编译器是否支持C++/CLI扩展,可测试__cplusplus_cli宏是否已经预界说。假如已界说,它的值将为200406L。
4、CLI库包括了一个称为System::Decimal的范例,其至少可暗示28位数字的值,这个范例是专为那些需要在没有四舍五入环境下举办大数额金融计较而筹备的。与浮点范例差异,Decimal的小数部门能暗示得越发准确,凡是,当某数以浮点范例暗示时,其常常会有一个无穷的小数,但却更容易导致舍入上的错误;而Decimal有一个称为"scale(数值范畴)"的属性,其代表了十进制下所需的位数。举例来说,2.340的scale为3,其末了的0很是重要,当两个十进制数相加或相减时,功效的数值范畴是两者数值范畴中较大的一者,如:1.0 + 2.000为3.000,而5.0-2.00为 3.00;当两个十进制数相乘时,功效的数值范畴是两者数值范畴之和,如:1.0*2.000为2.0000;当两个十进制相除时,功效的数值范畴是除数的数值范畴比被除数多出的值,如:4.00000/2.000为2.00。然而,数值范畴不行能小于为暗示正确的值所需的范畴,譬喻,3.000/2.000、3.00/2.000、3.0/2.000和3/2都是1.5,下面是Decimal的利用典型:
Decimal x = Decimal::Parse("23.00");
Decimal y = Decimal::Parse("2.000");
Decimal result = x * y + Decimal::Parse("2.5");
Console::WriteLine(result);
输出为48.50000。请留意,C++/CLI没有字面意义上的Decimal范例,所以需要利用Parse函数。
5、假如有这样一种环境,一个类利用了C++/CLI之外的其他语言编写,且有一个名字为C++/CLI要害字的public成员,那就可通过__identifier(x)这种形式的内部函数来会见它,此处的x可以是一个标识符、一个要害字、或一个字面上的字符串,譬喻,为挪用一个类X中名为delete、且没有参数的静态函数,就可以像X::__identifier(delete)()这样利用。
#p#分页标题#e#
6、一个literal域是一个界说在类中的定名编译期常量,同样,它也必需有接管一个常量值的初始化措施。尽量一个literal域利用上像一个静态数据成员,但它并不能声明为static,且编译器会把每个literal域都替换为域值。一个literal域能有任意标量范例,可是,能用作初始化句柄的独一常量数值,只能为字面上的字符串和nullptr。
literal double PI = 3.1415926;
literal int MinValue = -10, MaxValue = 10;
literal int Range = MaxValue - MinValue + 1;
enum Direction {North, South, East, West};
literal Direction Home = North;
literal System::String^ Title = "Annual Report";