C++/CLI可以说是标准C++语言一种新的\"方言\",它是Microsoft为充分利用CLI平台而开发出来的。
作者:谢启东编译 来源:天极开发 2007年11月14日
关键字:
与C++程序员预想的一样,除了默认的成员可访问性,一个引用结构(ref struct)与引用类基本上一模一样,在这,我们把两者都称为引用类。
每个引用类都有一个基类,如果没有显式指定,那么默认的基类即为System::Object,一个引用类有且只能有一个基类。
我们先不管Point在内部是怎么表示的,考虑到它有X与Y属性,我们在此使用了笛卡尔坐标,实现起来非常简单;如果它使用极坐标,那么就复杂多了。
作为成员的标量属性,也对实例提供了类似字段的访问性,在标记3(a)中,用int类型定义了一个X属性,property符号是一个上下文关键字,而不是一个全局保留的关键字,它的用法只限于在这个上下文中。
对于get与set存取程序,在一个属性中即可有任意一个,也可两者兼有。在标记3(b)中,get返回既定属性的值;而在标记3(c)中,set使用编程者提供的值来设置即定的属性值。这两个存取程序分别以名字get与set定义为单独的函数,必须接受或返回相应的声明类型值,在本例中,为int(注意,这两个名字不是关键字)。存取程序也能具有不同的可访问性,但可能会妨碍到语言间的互操作性(interop),因为其他CLI语言可能不支持。
在标记5(b)与5(c)代表的默认构造函数中,是使用set的简单例子--X与Y均被设置为零,注意,不能使用X=Y=0来代替,因为set为一个void返回类型,所以子表达式Y=0不能出现在另一个表达式中。
对一个引用类来说,相等性是通过函数Equals来实现的,而不是重载==操作符,如标记8(a)所示。因为Point重载了System::Object::Equals,所以Point::Equals必须被声明为virtual,再次提醒的是,override符号也是一个上下文关键字,而不是一个保留关键字。而这个函数重载了Object中的一个函数,所以需要接受一个Object作为参数,而不是一个Point。
实际上,参数带有类型Object^,其表示"Object的句柄",并指向托管堆(垃圾回收)中的一个对象。句柄在此是一个C++/CLI术语,CLI实际上把它称为"引用",但C++已经有引用了,这是两回事。
有经验的C++类设计人员可能会留意到,在这个类的定义中,缺乏了两个重要的东西:函数未const限定;且参数不是作为一个const句柄传递的。为什么会这样呢?因为引用类的成员函数不会用const来限定,CLI也没有概念上的const函数;把参数声明为一个const句柄将会使它成为另一种类型,这样它就不再能被System::Object::Equals重载了(const类型的句柄是允许的,但它们只能被用在一个C++/CLI上下文之内,而不能与任何CLI标准库函数一起使用的,因为目前CLI中还未有const这个概念,未来版本的C++/CLI有可能会全面支持const,但其他语言仍不会支持const)。
在标记8(b)中,我们把obj与nullptr作一比较。nullptr关键字表示常量空值,当使用在一个句柄上下文中时,它表示空句柄--没有指向任何对象的句柄;当使用在一个指针上下文中时,它表示空指针--没有包含任何地址的指针。
为防止自身比较,在标记8(c)中,把obj与this作一对比。在一个非引用类(指本地类)中,this是一个实例函数调用时指向对象的指针,可带有const限定符;在一个引用类中,则是实例函数调用时指向对象的句柄--此处要再次提醒大家,不允许带有const限定符。也可以通过类似以指针访问成员时的指向操作符 ->,来访问类中成员,只不过此处使用的是句柄。
Equals是为了确保其比较的两个对象有着相同的类型,所以在标记8(d)中调用了System::Object::GetType,其返回一个代表当前实例运行时类型的System::Type句柄,如果两个System::Type对象引用指向同一对象,则它们代表了同一类型。此处,我们比较的是两个句柄,而不是两个类型对象。
一旦你获知两个对象为同一类型,就可以安全地把Object句柄向上转换为一个Point句柄,进而执行数据比较,而不用担心发生错误的类型匹配这样的异常,在此,使用了static_cast。
为使哈希表(散列表)数据结构工作正常,在对象中必须有一个名为GetHashCode的函数。基本上,如果一个类型定义了Equals,它也应该同时定义GetHashCode,其是重载System::Object的版本,如标记9。
与相等性比较类似,值的格式化是通过一个重载System::Object的函数实现的,如标记10(a),而不是重载<<操作符。这个函数称为ToString,它的功能是创建并返回一个当前实例的字符串,它调用了System::String::Concat连接三个字符串及两个int,实现了所需功能。
毫无疑问,不可能对任一参数及类型的搭配,Concat都能有一个适当的重载版本,那么,Concat是怎样处理这些参数的呢?本例中使用的重载版本如下: static String^ Concat(... array<Object^>^ list);
圆括号中的参数声明(其必须有一托管的数组类型),表明可接受任意数量给定元素类型的参数,即,它是一个类型安全的varargs--参数数组,参数列表为一指向对象句柄托管数组的句柄。
那么这两个int--X与Y,是怎样转换为Object^的呢?其实,在基本数据类型对Object^的表达式中,都存在着一个隐式转换,这个过程称为"装箱",也就是包含基本数据类型值的对象,在托管堆上的分配。逆过程称为"解箱",这需要显式转换。
最后提一下命名约定。CLI指定了类、函数、属性必须以PascalCase模式来编写,也就是说,每个单词的首字母必须大写,而CLI标准库也遵循这条原则。