实例解析C++/CLI之值类型

值类型是一种轻量级的C++/CLI类机制,非常适合于小型的数据结构,且从语义的角度来看,与数值(Value)类似。

与之相比,引用类型的实例--包括那些声明在堆栈上的,是由垃圾回收器管理的,而值类型的实例却不是。一般来说,一个值类较好的实现应只有一些数据成员,而不需要继承性,这样,在函数传递及返回值、或是赋值操作时,不会带来巨大的数据开销。

值类初印像

请看例1中的Point类,可以通过替换ref为value,来把一个引用类变为值类;与引用类(ref)相似,值类(value)也是一个包含了空格的关键字。与大家想像的一样,值类(value)与值结构(value struct)之间唯一的区别就是,前者默认的可访问性为private,而后者则为public。

例1:

using namespace System;
public value class Point
{
  int x;
  int y;
  public:
   //定义属性X与 Y的读写实例
   property int X
   {
    int get() { return x; }
    void set(int val) { x = val; }
   }
   property int Y
   {
    int get() { return y; }
    void set(int val) { y = val; }
   }
   //定义实例构造函数
 
   Point(int xor, int yor)
   {
    X = xor;
    Y = yor;
   }
   void Move(int xor, int yor)
   {
    X = xor;
    Y = yor;
  }
  virtual bool Equals(Object^ obj) override
  {
   if (obj == nullptr)
   {
    return false;
   }
   if (GetType() == obj->GetType())
   {
    Point^ p = static_cast<Point^>(obj);
    return (X == p->X) && (Y == p->Y);
   }
   return false;
  }
  static bool operator==(Point p1, Point p2)
  {
   return (p1.X == p2.X) && (p1.Y == p2.Y);
  }
  // static bool operator==(Point% p1, Point% p2)
  // {
  // return (p1.X == p2.X) && (p1.Y == p2.Y);
  // }
  // static bool operator==(Point& p1, Point& p2)
  // {
  // return (p1.X == p2.X) && (p1.Y == p2.Y);
  // }
  virtual int GetHashCode() override
  {
   return X ^ (Y << 1);
  }
  virtual String^ ToString() override
  {
   return String::Concat("(", X, ",", Y, ")");
  }
};


值类自动继承自System::ValueType,而System::ValueType则继承自System::Object,但是,这却不能显式地声明。值类隐式表明了为"sealed",也就是说,它不能被作为一个基类,另外,为其类成员指定一个protected是没有任何意义,并且也是不允许的。如果想显式声明一个值类(或引用类),可像如下所示:

value class X sealed {/*...*/};

请注意,此处没有默认的构造函数。对一个值类来说,CLI本身把类实例中所有字段的位都设置为零,所以,不能提供自己的默认构造函数;然而,零、false、nullptr对其他类型来说,也许并不是合适的默认值,因此,对某些特定类型来说,就要用引用类型来取代值类型了。(遵从C++/CLI的实现会将false与nullptr表示为位全部为零。)

值类的另一个限制是它们带有一个默认的拷贝构造函数和一个赋值操作符,两者都会进行逐位复制,并不可被重载。

如果要实现Point类中的Equals函数,相比引用类中的而言要简单一些。请记住,我们正在重载定义System::Object中的这个版本,而其接受一个Object^,因为这种类型的参数很可能有一个nullptr值,在此,先可以省去检查是否为自身比较这一步,而对引用类的Equals实现来说,这一步是必需的,因为可有多个句柄引用同一对象。但是话说回来,在目前的这个值类中,没有两个值的实例可表示同一个实例,两个相同的值实例,只代表两个Point有相同的坐标,但修改其中一者的x坐标,不会影响到另一者的相同值。

当一个Point的实例传递到Equals时,作为值类型(其最终也都继承自System::Object)而言,装箱就发生了--也就是说,在垃圾回收堆上分配了一个Object的实例,而其包含了传递进来Point的一份副本。因为是创建了一个新的对象,所以只有一个句柄,也不会有相同的其他Point。

之前接受Point句柄的 == 操作符函数,现在已经精简到一行,并且由接受句柄改为接受Point值,且用于选择成员的指向操作符 -> 也被替换为点操作符。因为给定的值类型为sealed,所以与值类型参数Point唯一匹配的则为同类型的值了。同样地,既无需检查nullptr来确认是否为自身比较,也无需检查传递进来的对象是否类型完全一致。

而之前用于追踪引用的 == 操作符函数基本上无需太多改动,但删除了检测同一类型这一部分。然而,这两个== 操作符函数,最好只保留一个,以免在point1 == point2调用时引发歧义。(在声明函数参数时,也可使用标准C++引用符&,而不是%,因为两者可在本地类型与值类型之间互换。但由于这种类型的实例不存在于垃圾回收堆中,所以在垃圾回收期间不会改变它们的位置,因此也不需要对它们的位置进行追踪。)

例2使用了值类中的大多数成员,最主要的是它包含了静态Point类的实例,而这在引用类中是不可能完成的。事实上,不只是不能有一个引用类的静态实例,甚至也不能有一个此类型的静态句柄。

例2:

using namespace System;
Point p1;
static Point p2(3,4);
int main()
{
  static Point p3(4,7);
  Console::WriteLine("p2 is {0}", p2);
  Point% p4 = p3;
  Point p5 = p2;
  p5 = p2;
  Console::WriteLine("p1 == p2 is {0}", p1 == p2);
  Console::WriteLine("p1.Equals(p2) is {0}", p1.Equals(p2));
}
p2 is (3,4)
p1 == p2 is False
p1.Equals(p2) is False


在第一次调用Console::WriteLine时,用传值的方式传递进一个Point,但是,这个函数却指望着接受一个对象引用,在此,Point值被自动装箱,并把装箱后的对象引用传递给函数。

在定义中可看到,p5是由默认的拷贝构造函数初始化,而接下来的一行代码,默认的赋值操作符把p2逐位复制给p5。

时间: 2024-08-31 09:21:21

实例解析C++/CLI之值类型的相关文章

实例解析C++/CLI中的继承与枚举

本文中,将要介绍与继承相关的C++/CLI主题,并以现实生活中银行交易的三种形式:存款.取款.转账,来说明类的继承体系,且以一种新的枚举形式来实现. 枚举器 请看例1中声明的类型,它存在于其自身的源文件中,并编译为一个只包含此类型的程序集: 例1: public enum class TransactionType : unsigned char {Deposit, Withdrawal, Transfer}; 与想像的一样,枚举器中的Deposit.Withdrawal.Transfer分别代

C++/CLI的“值类型的强类型装箱实例”

近来接到几个朋友问Visual C++ 2005 (C++/CLI) Webcast中讲的"值类型的强类型装箱实例"是什么? 讲座比较匆忙,因此对这个技术点只是点了一下,没有详细展开.这里借blog把它展开说一下. 首先来看下面的C#代码: using System; using System.Collections; struct MyClass { public int data; } class Test { public static void Main() { MyClass

实例解析C++/CLI之代理与事件

在C++/CLI中,代理是对函数进行包装的对象:而事件是一种为客户程序提供通知的类机制. 在前几篇文章中,已经多次演示了如果让一个句柄在不同的时间,被引用至不同的对象,从而以更抽象的方法来解决程序中的问题,但是,也能使用代理通过函数来达到同样的效果:代理是包装了函数的一个对象,且对实例函数而言,也能通过特定的实例,与这些函数发生联系.一旦一个代理包装了一个或多个函数,你就能通过代理来调用这些函数,而无须事先了解包装了哪些函数. 请看例1中的代码,在标号1中,定义一个代理类型Del,由于使用了上下

实例解析C++/CLI之开卷有益

C++/CLI可以说是标准C++语言一种新的"方言",它是Microsoft为充分利用CLI(Common Language Infrastructure)平台而开发出来的.那么,它在语言方面有何新颖独到之处呢,下面,就让我们一起开始奇妙的C++/CLI语言之旅(文中所有示例代码,均以Visual Studio.NET 2005 Professional编译通过,所有的讲解内容,也均以Visual Studio.NET 2005环境为基础). 程序集与元数据 传统的C++编译模式包括把

实例解析C++/CLI之静态构造函数

就某些类而言,当在程序中第一次使用时,最好能有一个初始化过程:当程序不再需要时,也最好能做一些收尾工作,这些都是非常好的类设计习惯. 引出问题 如果有这样一种情况,某种类型的每个实例都必须有其唯一的ID,比如说某种交易类型,这些ID可用于在处理过程中追踪每笔交易,或之后用于审计员查看数据文件:为讨论方便,此处的ID为从0起始的有符号整型数. 如果把一个nextID值保存在内存中,并在每个新实例构造时,把它递增1,这无疑是一个不错的想法,但是,为使在程序连续的执行过程中保持ID值的唯一,就需要在每

实例解析C++/CLI线程之多任务

简介 从处理器的角度来看,线程是一个单独的执行流程,每个线程都有各自的寄存器及堆栈上下文.通常来说,在系统中只有一个处理器或处理器只有一个核心时,运行时环境在一个时间片内只能执行一个线程,当线程未能获取所需的资源时,线程的执行就会被中断,且会一直等到相关操作的完成,如I/O:或者在线程用完它的处理器时间片时,也会被中断下来等待.而处理器把执行流程从一个线程切换到另一个线程时,这称为"上下文切换":当某个线程变为"阻塞"状态,从而执行另一个线程时,系统有效地减少了处理

实例解析Ruby中的数值类型以及常量_ruby专题

数值类型(Number)整型(Integer) 整型分两种,如果在31位以内(四字节),那为Fixnum实例.如果超过,即为Bignum实例. 整数范围从 -230 到 230-1 或 -262 到 262-1.在这个范围内的整数是类 Fixnum 的对象,在这个范围外的整数存储在类 Bignum 的对象中. 您可以在整数前使用一个可选的前导符号,一个可选的基础指标(0 对应 octal,0x 对应 hex,0b 对应 binary),后跟一串数字.下划线字符在数字字符串中被忽略. 您可以获取一

实例解析C++/CLI中的接口与泛型

接口 某些时候,让不相关的类分享一组公有成员,以便产生相同的行为,是非常有用的.一个最基本的方法可能是通过一个公共的基类来定义它们,但这种方法太受局限,因为它要求这些类通过继承而互相关联,另外,它们也许还有着各自的基类,且CLI类型只支持单一类继承. C++/CLI提供了一种方法,可利用多个类实现一组通用的功能,这就是我们通称的"接口",而一个接口则是一组成员函数的声明.要注意,这些函数只是声明,没有定义,也就是说,一个接口定义了一个由抽象函数组成的类型--这些函数实际上是纯虚函数,且

实例解析C++/CLI的“克隆”

C++/CLI不但支持基于堆栈的对象,同时也支持基于堆的对象:然而,如果想与其他基于CLI的语言(如C#.J#.Visual Basic)进行互操作的话,必须要清楚地知道,这些语言只支持基于堆的对象:当处于基于堆的对象环境中时,你与对象之间,永远只有"一臂之遥",比方说,两个给定的句柄h1与h2,只有在为这种句柄类型定义了相应的赋值操作符时,*h1 = *h2才会工作正常,而对C++/CLI之外的其他语言中的类型来说,情况可能就不是这样了.同样地,一个遵从CLS的机制需要创建对象的一份