要学习数组,必须先了解跟踪句柄。

一、跟踪句柄

跟踪句柄类似于本地C++指针,但也有很大区别。跟踪句柄确实存储着某个对象的地址,但当CLR压缩堆过程中改变了该对象的地址,则垃圾回收器自动更新句柄所包含的地址。我们不能像本地指针那样用跟踪句柄来执行地址的算术运算,也不允许对跟踪句柄进行强制类型转换。

在CLR堆中创建的对象必须被跟踪句柄引用。所有属于引用类型的对象都存储在堆中,因此为引用这些对象所创建的变量都必须是跟踪句柄。例如,String类型是引用类型,因此引用String对象的变量必须是跟踪句柄。值类型默认分配在堆栈上,但也可以用gcnew操作符将其存储在堆上。此外必须注意,在堆上分配的变量——其中包括所有CLR引用类型——都不能在全局作用域内声明。

通过在类型名称后加”^”符号,用于声明一个该类型的句柄。下面的例子声明了一个String类型的跟踪句柄proverb。

String^ proverb;

在声明时句柄时,系统自动为其分配一个空值,该句柄不引用任何对象。也可显示地将某个句柄置为空值:

proverb = nullptr;

注意不能用0来表示空值。如果用0来初始化某个句柄,则0将自动转换为被引用类型的对象,而句柄则指向该对象。可以在声明句柄时显示的将其初始化:

String^ saying = L"I used to think I was indecisive but now I??¡ê¡èm not so sure."; 

该语句首先在堆上创建一个包含等号右边字符串的String对象,然后将该对象的地址存入saying中。注意字符串字面值的类型为const wchar_t*而非String。类String提供了这样的方法使得const wchar_t*类型的字符串可以用来创建String类型的对象。

下面这条语句创建了值类型的句柄:

int^ value = 99; 

      该语句在堆上创建一个Int32型的值类型变量,然后将该变量的地址存入句柄value中。由于value是一种指针,因此不能直接参与算术运算,可使用*运算符对地址求值(类似于本地C++指针那样):

int result = 2*(*value)+15;

      由于*value表示value所指向地址存储的数值,因此result的值为2*99+15=213。注意,当value作为运算式左值时,不需要*即可对value指向的变量赋值。

int^ result = 0;
result = 2*(*value)+15;

      首先创建了一个指向数值0的句柄result。(该语句会触发一条编译器警告,提示不能利用0来将句柄初始化为空值。)

      第2条语句=号右边为数值,而左边为句柄,编译器将自动将右值赋予句柄所指向的对象,即将其转换为如下语句

*result = 2*(*value)+15;

       注意,要采用上面的语句,result句柄必须实际定义过。如果仅仅声明了result,则会产生运行时错误

int^ result;
*result = 2*(*value)+15;

       这是因为第二句要对地址result求值,即意味着result指向的对象已经存在,但实际并非如此,因为声明该对象时系统默认赋予其空值(nullptr)。在这种情况下,采用下面的方法就可以正常工作了

int^ result;
result = 2*(*value)+15; 

二、数组

(一)数组句柄

       CLR数组是分配在可回收垃圾堆上的。必须用array<typename>指出要创建的数组,同其它CLR堆上的对象一样,需要用句柄来访问它,例子如下:

array<int>^ data;

      数组句柄data可用于存储对元素类型为int的一维数组的引用。下面的例子声明了一个句柄,并新建一个CLR数组来对此句柄初始化。

array<int>^ data = gcnew array<int>(100); 

       和本地C++数组一样,CLR数组中元素的索引值也是从0开始的,可以通过[ ]访问数组元素。数组元素都是CLR对象,在上面的例子中数组元素为Int32型对象,它们在算术表达式中就像普通的整数类型一样。

       Length属性是数组的一个重要属性,记录着数组元素的数量。保存了64位的数组长度。

for(int i=0; i<data->Length; i++)
	data[i] = 2*(i+1);

可以用for each循环遍历数组元素。

array<int>^ value = {3, 5, 6, 8, 6};
for each(int item in value)
{
	item = 2*item + 1;
	Console::WriteLine("{0, 5}", item);
}

该循环输出5字符宽度的字段,以右对齐的方式输出当前元素的计算结果,输出如下:

    7   11   13   17   13 

       数组句柄可以被重新赋值,只要保持数组元素类型和维数(等级)不变即可,在前面例子中的数组句柄data指向一个int类型的一维数组,可以重新给它赋值,使其指向另外的int类型1维数组:

data = gcnew array<int>(45);

       数组可以在创建时通过元素列表初始化,下例在CLR堆上创建了一个double类型的数组,并将引用赋值给了数组句柄:

array<double>^ sample = {3.4, 2.3, 6.8, 1.2, 5.5, 4.9, 7.4, 1.6};

       如果在声明数组句柄时不进行初始化,那么在给句柄赋值时不能采用上面的方法直接用元素列表用作右值,而必须采用显示创建的方式。即不能

array<double>^ sample;
sample = {3.4, 2.3, 6.8, 1.2, 5.5, 4.9, 7.4, 1.6}

       而必须采用如下方式

array<double>^ sample;
sample = gcnew array<double>{3.4, 2.3, 6.8, 1.2, 5.5, 4.9, 7.4, 1.6}

       对于字符串数组,注意每一个元素也是引用类型,这是因为每一个元素String也是在CLR堆上创建的,因此也要用String^来访问它。

array<String^>^ names = {"Jack", "John", "Joe", "Jessica", "Jim", "Joanna"};

       可以用Array类静态函数Clear()对数组中的连续数组元素清零。

Array::Clear(samples, 0, samples->Length);

       Clear()函数的第一个参数是被清零的数组,第二个参数是要清除地第一个元素的索引,第三个参数为要清除地元素数量。因此上述语句将samples数组的所有元素都置为0。如果Clear()清除的是某个跟踪句柄,则句柄所对应的元素都被应用Clear()函数。如果元素为bool型,则被置为false。

(二)数组排序

Array类还定义了一个Sort()静态函数,可用于对数组进行排序。如果以数组句柄作为参数,则对整个数组排序。如果要对数组部分排序,则还需要增加元素起始索引及数量,如下例

array<int>^ samples = {27, 3, 54, 11, 18, 2, 16};
Array::Sort(samples, 2, 3);

排序后数组元素变为{27, 3, 11, 18, 54, 2, 16}

Sort函数还有很多其它版本,下面的例子展示了如何排序两个相关的数组,即第一个数组中的元素是第二个数组对应元素的键。对第一个数组排序后,可对第二个数组进行相应的调整,使得键与值相互对应。

Sort()函数对2个数组排序时,用第一个数组参数来确定两个数组的顺序,以此保持2个数组元素对应关系不变。

- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::开始==>> [Ex4_13.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Ex4_13.cpp : main project file.
#include "stdafx.h"
using namespace System;

int main(array<System::String ^> ^args)
{
	array<String^>^ names = { "Jill", "Ted", "Mary", "Eve", "Bill", "Al" };
	array<int>^ weights = {103, 168, 128, 115, 180, 176};

	Array::Sort(names, weights);
	for each( String^ name in names )
		Console::Write(L"{0, 10}", name);
	Console::WriteLine();
	
	for each(int weight in weights)
		Console::Write(L"{0, 10}", weight);
	Console::WriteLine();
	
	return 0;
}
- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::结束==>> [Ex4_13.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
输出为: 
Al Bill Eve Jill Mary Ted 
176 180 115 103 128 168 

(三)数组搜索

Array类还提供了函数BinarySearch()以使用对分法搜索算法,对一维数组或给定范围内搜索特定元素的索引位置。使用该函数要求数组必须是顺序排列的,因此在搜索之前必须对数组进行排序。

array<int>^ value = { 23, 45, 68, 94, 123, 150, 203, 299 };
int toBeFound = 127;
int position = Array::BinarySearch(value, toBeFound);
if(position<0)
        Console::WriteLine(L"{0} was not found.", toBeFound);
else
        Console::WriteLine(L"{0} was found at index position {1}", toBeFound, position);

Array::BinarySearch()的第一个参数是被搜索数组的句柄,第二个参数是要查找的内容,返回值为int类型的数值。如果返回值小于0则说明未找到。如果要指定搜索范围,则需要传递4个参数,其中第2参数为搜索起始索引,第3参数为搜索的元素数量,第4个是要搜索的内容。下面的代码从第4个元素开始,一直搜索到结束位置。

array<int>^ value = { 23, 45, 68, 94, 123, 150, 203, 299 };
int toBeFound = 127;
int position = Array::BinarySearch(value, 3, 6, toBeFound);    
- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::开始==>> [Ex4_14.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Ex4_14.cpp : main project file.
#include "stdafx.h"
using namespace System;

int main(array<System::String ^> ^args)
{
	array<String^>^ names = { "Jill", "Ted", "Mary", "Eve", "Bill", "Al", "Ned", "Zoe", "Dan", "Jean" };
	array<int>^ weights = {103, 168, 128, 115, 180, 176, 209, 98, 190, 130};
	array<String^>^ toBeFound = {"Bill", "Eve", "Al", "Fred"};
	
	int result = 0;
	Array::Sort(names, weights);

	for each( String^ name in toBeFound )
	{
		result = Array::BinarySearch(names, name);
		if(result<0)
			Console::WriteLine(L"{0} was not found.", name);
		else
			Console::WriteLine(L"{0} weights {1} lbs.", name, weights[result]);
	}

	return 0;
}
- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::开始==>> [Ex4_14.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        当搜索不到目标时,Array::BinarySearch()函数输出的并非任意负数,而是第一个大于该目标的元素索引值的按位补码。利用该方法,可以不打乱顺序在数组中插入新值。如,我们希望插入”Fred”到names数组中

array<String^>^ names = { "Jill", "Ted", "Mary", "Eve", "Bill", "Al", "Ned", "Zoe", "Dan", "Jean" }
Array::Sort(names);
String^ name = L"Fred";
int position = Array::BinarySearch(names, name);
if(position<0)
position = ~position;

此时,position保存的是大于Fred的第一个元素的位置,该数值可用于插入新值。

array<String^>^ newNames = gcnew array<String^>(names->Length+1);
for(int i=0;i<position;i++)
	newNames[i] = names[i];
newNames[position] = name;

if(position<name->Length)
	for(int i=position; i<names->Length; i++)
		newNames[i+1] = names[i];
names = nullptr;

注意:最后一句用于删除names数组。

(四)多维数组

C++/CLI中可以创建多维数组,最大维数32维。与ISO/ANSI C++不同的是,C++/CLI中的多维数组并非数组的数组,而是真正的多维数组,创建整数多维数组方法如下:

array<int 2>^ value = gcnew array<int, 2>(4, 5);

上面的代码创建了一个二维数组,四行五列,共20个元素。访问的方法是利用多个用逗号分隔的索引值来访问每一个元素,而不能用一个索引值访问一行

int nrows = 4;
int ncols = 5;
array<int, 2>^ value = gcnew array<int, 2>(nrows, ncols);
for(int i=0; i<nrows; i++)
    for(int j=0; j<ncols; j++)
        value[i, j] = (i+1)*(j+1);

上面的代码利用循环给二维数组value赋值。这里访问二维数组元素的符号与本地C++不同:后者实际上是数组的数组,而C++/CLI是真正的二维数组,不能用一个索引值来访问二维数组,那样是没有意义的。数组的维数被称为等级,上面value数组的等级为2。而本地C++数组的等级始终为1。当然,在C++/CLI中也可以定义数组的数组,方法见下例

- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::开始==>> [Ex4_15.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Ex4_15.cpp : main project file.
#include "stdafx.h"
using namespace System;

int main(array<System::String ^> ^args)
{
	const int SIZE = 12;
	array<int, 2>^ products = gcnew array<int, 2>(SIZE, SIZE);
	
	for(int i=0; i<SIZE; i++)
		for(int j=0; j<SIZE; j++)
			products[i, j] = (i+1)*(j+1);
	Console::WriteLine(L"Here is the {0} times table:", SIZE);

	// Write horizontal divider line
	for(int i=0; i<=SIZE; i++)
		Console::Write(L"_____");
	Console::WriteLine();

	// Write top line of table
	Console::Write(L"    |");
	for(int i=1; i<=SIZE; i++)
		Console::Write("{0, 3} |", i);
	Console::WriteLine();

	// Write horizontal divider line with verticals
	for(int i=0; i<=SIZE; i++)
		Console::Write("____|", i);
	Console::WriteLine();

	// Write remaining lines
	for(int i=0; i<SIZE; i++)
	{
		Console::Write(L"{0, 3} |", i+1);
		for(int j=0; j<SIZE; j++)
			Console::Write("{0, 3} |", products[i, j]);
		Console::WriteLine();
	}

	// Write horizontal divider line
	for(int i=0; i<=SIZE; i++)
		Console::Write("_____", i);
	Console::WriteLine();
	return 0;
}
- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::结束==>> [Ex4_15.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

上面的例子创建了一个12x12的乘法表,输出如下:

Here is the 12 times table:
_________________________________________________________________
    |  1 |  2 |  3 |  4 |  5 |  6 |  7 |  8 |  9 | 10 | 11 | 12 |
____|____|____|____|____|____|____|____|____|____|____|____|____|
  1 |  1 |  2 |  3 |  4 |  5 |  6 |  7 |  8 |  9 | 10 | 11 | 12 |
  2 |  2 |  4 |  6 |  8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 |
  3 |  3 |  6 |  9 | 12 | 15 | 18 | 21 | 24 | 27 | 30 | 33 | 36 |
  4 |  4 |  8 | 12 | 16 | 20 | 24 | 28 | 32 | 36 | 40 | 44 | 48 |
  5 |  5 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 |
  6 |  6 | 12 | 18 | 24 | 30 | 36 | 42 | 48 | 54 | 60 | 66 | 72 |
  7 |  7 | 14 | 21 | 28 | 35 | 42 | 49 | 56 | 63 | 70 | 77 | 84 |
  8 |  8 | 16 | 24 | 32 | 40 | 48 | 56 | 64 | 72 | 80 | 88 | 96 |
  9 |  9 | 18 | 27 | 36 | 45 | 54 | 63 | 72 | 81 | 90 | 99 |108 |
 10 | 10 | 20 | 30 | 40 | 50 | 60 | 70 | 80 | 90 |100 |110 |120 |
 11 | 11 | 22 | 33 | 44 | 55 | 66 | 77 | 88 | 99 |110 |121 |132 |
 12 | 12 | 24 | 36 | 48 | 60 | 72 | 84 | 96 |108 |120 |132 |144 |
_________________________________________________________________

其中创建二维数组的代码如下:

const int SIZE = 12;
array<int, 2>^ products = gcnew array<int, 2>(SIZE, SIZE);

第一行定义了一个整型常量SIZE,用于指定每一维数组的元素数量,第二行代码定义了一个等级2的数组,为12x12大小,该数组用于存储12x12的乘法表乘积。然后在嵌套循环中给数组赋值,大部分代码用于格式化输出以使其更加美观,这里就不再说明。

(五)数组的数组

如果数组的元素是引用数组的跟踪句柄,那么就可以创建数组的数组。同时,每一维数组的长度可以不同,即所谓的“锯齿形数组”。例如,用ABCDE来表示学生的成绩等级,根据等级分组存储班内学生的姓名,则可以创建一个包含5个元素的数组,每个元素为一个姓名数组(即字符串数组)

array<array<String ^>^>^ grades = gcnew array<array<String^>^>(5)

利用上面创建的数组,然后可以创建5个姓名数组了

grades[0] = gcnew array<String^>{"Louise", "Jack"};
grades[1] = gcnew array<String^>{"Bill", "Mary", "Ben", "Joan"};
grades[2] = gcnew array<String^>{"Jill", "Will", "Phil"};
grades[3] = gcnew array<String^>{"Ned", "Fred", "Ted", "Jed", "Ed"};
grades[4] = gcnew array<String^>{"Dan", "Ann"};

grades[n]访问grades数组的第n个元素,而各元素为指向String^类型数组的句柄,因此上面的语句用于创建了String对象句柄的数组,并将创建数组的地址赋值给了grades数组元素。同时,这些字符串数组的长度是不同的。

上面的语句也可以用一个初始化语句来实现

array<array<String^>^>^ grades = gcnew array<array<String^>^>
{
    gcnew array<String^>{"Louise", "Jack"},
    gcnew array<String^>{"Bill", "Maray", "Ben", "Joan"},
    gcnew array<String^>{"Jill", "Will", "Phil"},
    gcnew array<String^>{"Ned", "Fred", "Ted", "Jed", "Ed"},
    gcnew array<String^>{"Dan", "Ann"},
};

注意:元素的初值必须写在花括号里。

- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::开始==>> [Ex4_16.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Ex4_16.cpp : main project file.
#include "stdafx.h"
using namespace System;

int main(array<System::String ^> ^args)
{
	array<array<String^>^>^ grades = gcnew array<array<String^>^>
		{
			gcnew array<String^>{"Louise", "Jack"},
			gcnew array<String^>{"Bill", "Maray", "Ben", "Joan"},
			gcnew array<String^>{"Jill", "Will", "Phil"},
			gcnew array<String^>{"Ned", "Fred", "Ted", "Jed", "Ed"},
			gcnew array<String^>{"Dan", "Ann"}
		};
	
	wchar_t gradeLetter = 'A';
	for each(array<String^>^ grade in grades)
	{
		Console::WriteLine(L"Students with Grade {0}:", gradeLetter++);
		for each(String^ student in grade)
			Console::Write("{0, 12}", student);
		Console::WriteLine();
	}
	return 0;
}
- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::结束==>> [Ex4_16.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

输出为

Students with Grade A:
      Louise        Jack
Students with Grade B:
        Bill       Maray         Ben        Joan
Students with Grade C:
        Jill        Will        Phil
Students with Grade D:
         Ned        Fred         Ted         Jed          Ed
Students with Grade E:
         Dan         Ann