在.NET框架中,值类型是数据结构的一种,它们在托管堆上分配内存。然而,在处理简单的数据结构时,如整数或坐标(x,y),直接在栈上分配这些值并使用标准的值类型语义会更加方便和高效。例如,声明一个整数时,不需要使用指针和new关键字来创建一个整数对象。相反,数值类型、布尔值、字符和日期等原始类型,以及结构体和枚举都是作为值类型声明的,这意味着它们在栈上分配,并像标准C++中的结构体或栈上变量一样声明和访问。
在托管C++中声明值类型时,使用新的__value
关键字。例如,如果想要创建一个表示复数的数据类型,可以声明如下:
__value struct Complex {
double real;
double imaginary;
};
值类型是在栈上创建的,并且可以直接访问。值类型声明如下:
Complex z;
并且使用点语法访问其成员:
z.real = 1.0;
z.imaginary = -3.1415;
一旦包含值类型的内存被释放,该值类型的实例就会被销毁。因此,不允许引用值类型。如果允许,可能会导致引用指向一个无效的内存位置。值类型总是指向该类型的变量,并且不能为null。将值类型赋值给另一个变量时,会创建该值的副本。
因为值类型是托管类型,所以在创建时它们会被初始化为0。值类型可以包含引用类型,但这些引用类型将在托管堆上创建,而引用将存储在栈上。
值类型隐式继承自System.ValueType
,鼓励用于行为类似于原始数据类型的类型、小型类型、不从其他数据类型继承或被继承的类型,以及不经常作为参数传递的类型(这会导致大量的内存分配和复制)。值类型可以继承托管接口,并可以覆盖其中定义的虚拟方法。
所有值类型的一个重要点是它们可以包含方法和字段,并且可以覆盖基接口中的虚拟方法。例如,可能希望覆盖复数类型的ToString
方法,以便可以以格式化的方式呈现它:
__value struct Complex {
double real;
double imaginary;
virtual String* ToString() {
return String::Format("{0} + {1}i", real.ToString("N2"), imaginary.ToString("N2"));
}
};
装箱和解包是.NET的一个特性,它允许值类型作为引用类型传递。例如,.NET的String
类的标准Format
方法重写接受一个格式字符串和一个对象。格式字符串指定格式,对象在该格式字符串中使用{0}
语法访问。一个整数不是从Object
派生的,因此通常不能传递给这个函数。然而,通过装箱一个整数值,创建了一个包含整数值的对象,并且可以作为对象使用。语法如下:
object obj = __box(5);
这在托管堆上创建了一个包含值'5'的托管引用。现在这个对象可以在任何需要Object*
对象的地方使用。
__box Complex* pZ = __box(z);
这给提供了一个可以在任何认为合适的地方使用的装箱对象,但在这样做的同时,保留了访问底层类型数据字段的能力:
pZ->real = 4;
Complex w = *dynamic_cast(pZ);