要學習數組,必須先了解跟蹤句柄。
一、跟蹤句柄
跟蹤句柄類似于本地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