Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Visual C++ 2005 (2006) [eng]

.pdf
Скачиваний:
126
Добавлен:
16.08.2013
Размер:
18.66 Mб
Скачать

Arrays, Strings, and Pointers

When the binary search operation fails, the value returned is not just any old negative value. It is in fact the bitwise complement of the index position of the first element that is greater than the object you are searching for, or the bitwise complement of the Length property of the array if no element is greater than the object sought. Knowing this you can use the BinarySearch() function to work out where you should insert a new object in an array and still maintain the order of the elements. Suppose you wanted to insert “Fred” in the names array. You can find the index position where it should be inserted with these statements:

array<String^>^ names = { “Jill”, “Ted”, “Mary”, “Eve”, “Bill”,

“Al”,

“Ned”, “Zoe”, “Dan”, “Jean”};

Array::Sort(names);

// Sort the array

String^ name = L”Fred”;

 

int position = Array::BinarySearch(names, name);

if(position<0)

// If it is negative

position = ~position;

// flip the bits to get the insert index

If the result of the search is negative, flipping all the bits gives you the index position of where the new name should be inserted. If the result is positive, the new name is identical to the name at this position, so you can use the result as the new position directly.

You can now copy the names array into a new array that has one more element and use the position value to insert name at the appropriate place:

array<String^>^ newNames = gcnew array<String^>(names->Length+1);

// Copy elements from names to newNames for(int i = 0 ; i<position ; i++)

newNames[i] = names[i];

newNames[position] = name;

// Copy the new element

if(position<names->Length)

// If any elements remain in names

for(int i = position ; i<names->Length ; i++)

 

newNames[i+1] = names[i];

// copy them to newNames

This creates a new array with a length one greater than the old array. You then copy all the elements from the old to the new up to index position position-1. You then copy the new name followed by the remaining elements from the old array. To discard the old array, you would just write:

names = nullptr;

Multidimensional Arrays

You can create arrays that have two or more dimensions; the maximum number of dimensions an array can have is 32, which should accommodate most situations. You specify the number of dimensions that your array has between the angled brackets immediately following the element type and separated from it by a comma. The dimension of an array is 1 by default, which is why you did not need to specify up to now. Here’s how you can create a two-dimensional array of integer elements:

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

209

Chapter 4

This statement creates a two-dimensional array with four rows and five columns so it has a total of 20 elements. To access an element of a multidimensional array you specify a set of index values, one for each dimension; these are place between square brackets separated by commas following the array name. Here’s how you could set values for the elements of a two-dimensional array of integers:

int nrows = 4; int ncols = 5;

array<int, 2>^ values = gcnew array<int, 2>(nrows, ncols);

for(int i = 0 ; i<nrows ; i++) for(int j = 0 ; j<ncols ; j++)

values[i,j] = (i+1)*(j+1);

The nested loop iterates over all the elements of the array. The outer loop iterates over the rows and the inner loop iterates over every element in the current row. As you see, each element is set to a value that is given by the expression (i+1)*(j+1) so elements in the first row will be set to 1,2,3,4,5, elements in the second row will be 2,4,6,8,10, and so on through to the last row which will be 4,6,12,16,20.

I’m sure you will have noticed that the notation for accessing an element of a two-dimensional array here is different from the notation used for native C++ arrays. This is no accident. A C++/CLI array is not an array of arrays like a native C++ array it is a true two-dimensional array. You cannot use a single index with two-dimensional C++/CLI array, because this has no meaning; the array is a two-dimensional array of elements, not an array of arrays. As I said earlier, the dimensionality of an array is referred to as its rank, so the rank of the values array in the previous fragment is 2. Of course you can also define C++/CLI arrays of rank 3 or more, up to an array of rank 32. In contrast, native C++ arrays are actually always of rank 1 because native C++ arrays of two or more dimensions are really arrays of arrays. As you’ll see later, you can also define arrays of arrays in C++/CLI.

Let’s put a multidimensional array to use in an example.

Try It Out

Using a Multidimensional Array

This CLR console example creates a 12x12 multiplication table in a two-dimensional array:

//Ex4_15.cpp : main project file.

//Using a two-dimensional array

#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

210

 

Arrays, Strings, and Pointers

 

 

for(int i = 0 ; i <= SIZE ; i++)

Console::Write(L”_____”);

Console::WriteLine();

// Write newline

// Write top line of table

Console::Write(L”

|”);

for(int i = 1 ; i <= SIZE ; i++)

Console::Write(L”{0,3} |”, i);

Console::WriteLine();

// Write newline

// Write horizontal divider line with verticals

for(int i = 0 ; i <= SIZE ; i++)

Console::Write(L”____|”);

Console::WriteLine();

// Write newline

// 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(L”{0,3} |”, products[i,j]);

Console::WriteLine();

// Write newline

}

 

// Write horizontal divider line

 

for(int i = 0 ; i <= SIZE ; i++)

 

Console::Write(L”_____”);

 

Console::WriteLine();

// Write newline

return 0;

 

}

This example should produce the following output:

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 |

_________________________________________________________________

Press any key to continue . . .

211

Chapter 4

How It Works

It looks like a lot of code, but most of it is concerned with making the output pretty. You create the twodimensional array with the following statements:

const int SIZE = 12;

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

The first line defines a constant integer value that specifies the number of elements in each array dimension. The second line defines an array of rank 2 that has 12 rows of 12 elements. This array stores the products in the 12x12 table.

You set the values of the elements in the products array in a nested loop:

for (int i = 0 ; i < SIZE ; i++) for(int j = 0 ; j < SIZE ; j++) products[i,j] = (i+1)*(j+1);

The outer loop iterates over the rows, and the inner loop iterates over the columns. The value of each element is the product of the row and column index values after they are incremented by 1. The rest of the code in main() is concerned solely with generating output.

After writing the initial table heading, you create a row of bars to mark the top of the table like this:

for(int i = 0 ; i <= SIZE ; i++) Console::Write(L”_____”);

Console::WriteLine(); // Write newline

Each iteration of the loop writes five horizontal bar characters. Note that the upper limit for the loop is inclusive, so you write 13 sets of five bars to allow for the row labels in the table plus the 12 columns.

Next you write the row of column labels for the table with another loop:

// Write top line of table Console::Write(L” |”);

for(int i = 1 ; i <= SIZE ; i++)

 

Console::Write(L”{0,3} |”, i);

 

Console::WriteLine();

// Write newline

You have to write the space over the row label position separately because that is a special case with no output value. Each of the column labels is written in the loop. You then write a newline character ready for the row outputs that follow.

The row outputs are written in a nested loop:

for(int i = 0 ; i<SIZE ; i++)

{

Console::Write(L”{0,3} |”, i+1); for(int j = 0 ; j<SIZE ; j++)

Console::Write(L”{0,3} |”, products[i,j]);

Console::WriteLine();

// Write newline

}

212

Arrays, Strings, and Pointers

The outer loop iterates over the rows and the code inside the outer loop writes a complete row, including the row label on the left. The inner loop writes the values from the products array that correspond to the ith row, with the values separated by vertical bars.

The remaining code writes more horizontal bars to finish off the bottom of the table.

Arrays of Arrays

Array elements can be of any type so you can create arrays where the elements are tracking handles that reference arrays. This gives you the possibility of creating so-called jagged arrays because each handle referencing an array can have a different number of elements. This is most easily understood by looking at an example. Suppose you want to store the names of children in a class grouped by the grade they scored, where there are five classifications corresponding to grades A, B, C, D, and E. You could first create an array of five elements where each element stores an array of names. Here’s the statement that will do that:

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

Don’t let all the hats confuse you(it’s simpler than it looks. The array variable, grades, is a handle of type array<type>^. Each element in the array is also a handle to an array, so the type of the array elements is of the same form(array<type>^ so this has to go between the angled brackets in the original array type specification, which results in array< array<type>^ >^. The elements stored in the arrays are also handles to String objects so you must replace type in the last expression by String^; thus you end up with the array type being array< array< String^ >^ >^.

With the array of arrays worked out, you can now create the arrays of names. Here’s an example of what that might look like:

grades[0] = gcnew array<String^>{“Louise”, “Jack”};

// Grade A

grades[1] = gcnew array<String^>{“Bill”, “Mary”, “Ben”, “Joan”};

// Grade B

grades[2] = gcnew array<String^>{“Jill”, “Will”, “Phil”};

// Grade C

grades[3]

= gcnew array<String^>{“Ned”, “Fred”, “Ted”, “Jed”, “Ed”};

// Grade D

grades[4]

= gcnew array<String^>{“Dan”, “Ann”};

// Grade E

 

 

 

The expression grades[n] accesses the nth element is the grades array and of course this is a handle to an array of String^ handles in each case. Thus each of the five statements creates an array of String object handles and stores the address in one of the elements of the grades array. As you see, the arrays of strings vary in length, so clearly you can manage a set of arrays with arbitrary lengths in this way.

You could create and initialize the whole array of arrays in a single statement:

array< array< String^ >^ >^ grades = gcnew array< array< String^ >^ >

{

gcnew array<String^>{“Louise”, “Jack”},

// Grade A

gcnew array<String^>{“Bill”, “Mary”, “Ben”, “Joan”},

// Grade B

gcnew array<String^>{“Jill”, “Will”, “Phil”},

// Grade C

gcnew array<String^>{“Ned”, “Fred”, “Ted”, “Jed”, “Ed”},

// Grade D

gcnew array<String^>{“Dan”, “Ann”}

// Grade E

};

 

The initial values for the elements are between the braces.

 

213

Chapter 4

Let’s put this in a working example that demonstrates how you can process arrays of arrays.

Try It Out

Using an Array of Arrays

Create a CLR console program project and modify it as follows:

//Ex4_16.cpp : main project file.

//Using an array of arrays

#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”},

// Grade A

gcnew array<String^>{“Bill”, “Mary”, “Ben”, “Joan”},

// Grade B

gcnew array<String^>{“Jill”, “Will”, “Phil”},

// Grade C

gcnew array<String^>{“Ned”, “Fred”, “Ted”, “Jed”, “Ed”},

// Grade D

gcnew array<String^>{“Dan”, “Ann”}

// Grade E

};

wchar_t gradeLetter = ‘A’;

for each(array< String^ >^ grade in grades)

{

Console::WriteLine(“Students with Grade {0}:”, gradeLetter++);

for each( String^ student in grade)

 

 

Console::Write(“{0,12}”,student);

 

// Output the current name

Console::WriteLine();

 

 

// Write a newline

}

 

 

 

 

return 0;

 

 

 

 

}

 

 

 

 

This example produces the following output:

 

 

Students with Grade A:

 

 

 

Louise

Jack

 

 

 

Students with Grade B:

 

 

 

Bill

Mary

Ben

Joan

 

Students with Grade C:

 

 

 

Jill

Will

Phil

 

 

Students with Grade D:

 

 

 

Ned

Fred

Ted

Jed

Ed

Students with Grade E:

 

 

 

Dan

Ann

 

 

 

Press any key to continue . . .

 

 

 

214

Arrays, Strings, and Pointers

How It Works

The array definition is exactly as you saw in the previous section. Next you define the gradeLetter variable as type wchar_t with the initial value ‘A’. This is to be used to present the grade classification in the output.

The students and their grades are listed by the nested loops. The outer for each loop iterates over the elements in the grades array:

for each(array< String^ >^ grade in grades)

{

// Process students in the current grade...

}

The loop variable, grade, is of type array< String^ >^ because that’s the element type in the grades array. The variable grade references each of the arrays of String^ handles in turn, so first time around the loop it references the array of grade A student names, second time around it references grade B student names, and so on until the last loop iteration when it references the grade E student names.

On each iteration of the outer loop, you execute the following code:

Console::WriteLine(“Students with Grade {0}:”, gradeLetter++);

for each( String^ student in grade)

 

 

Console::Write(“{0,12}”,student);

//

Output the current name

Console::WriteLine();

//

Write a newline

The first statement writes a line that includes the current value of gradeLetter, which starts out as ‘A’. The statement also increments gradeLetter so it will be, ‘B’, ‘C’, ‘D’, and ‘E’ successively on subsequent iterations of the outer loop.

Next you have the inner for each loop that iterates over each of the names in the current grade array in turn. The output statement uses the Console::Write() funbction so all the names appear on the same line. The names are presented right-justified in the output in a field width of 12, so the names in the lines of output are aligned. After the loop, the WriteLine() just writes a newline to the output so the next grade output starts on a new line.

You could have used a for loop for the inner loop:

for (int i = 0 ; i < grade->Length ; i++)

Console::Write(“{0,12}”,grade[i]); // Output the current name

The loop is constrained by the Length property of the current array of names that is referenced by the grade variable.

215

Chapter 4

You could also have used a for loop for the outer loop as well, in which case the inner loop needs to be changed further and the nested loop looks like this:

for (int j = 0 ; j < grades->Length ; j++)

{

Console::WriteLine(“Students with Grade {0}:”, gradeLetter+j); for (int i = 0 ; i < grades[j]->Length ; i++)

Console::Write(“{0,12}”,grades[j][i]); // Output the current name Console::WriteLine();

}

Now grades[j] references the jth array of names so the expression grades[j][i] references the ith name in the jth array of names.

Strings

You have already seen that the String class type that is defined in the System namespace represents a string in C++/CLI(in fact a string consists of Unicode characters. To be more precise it represents a string consisting of a sequence of characters of type System::Char. You get a huge amount of powerful functionality with String class objects so it makes string processing very easy. Let’s start at the beginning with string creation

You can create a String object like this:

System::String^ saying = L”Many hands make light work.”;

The variable, saying, is a tracking handle that references the String object that is initialized with the string that appears on the right of the =. You must always use a tracking handle to store a reference to a String object. The string literal here is a wide character string because it has the prefix L. If you omit the L prefix, you have a string literal containing 8-bit characters, but the compiler ensures it is converted to a wide-character string.

You can access individual characters in a string by using a subscript just like an array, and the first character in the string has an index value of 0. Here’s how you could output the third character in the string saying:

Console::WriteLine(“The third character in the string is {0}”, saying[2]);

Note that you can only retrieve a character from a string using an index value; you cannot update the string in this way. String objects are immutable and therefore cannot be modified.

You can obtain the number of characters in a string by accessing its Length property. You could output the length of saying with this statement:

Console::WriteLine(“The string has {0} characters.”, saying->Length);

Because saying is a tracking handle(which as you know is a kind of pointer(you must use the -> operator to access the Length property (or any other member of the object). You’ll learn more about properties when we get to investigate C++/CLI classes in detail.

216

Arrays, Strings, and Pointers

Joining Strings

You can use the + operator to join strings to form a new String object. Here’s an example:

String^ name1 = L”Beth”;

String^ name2 = L”Betty”;

String^ name3 = name1 + L” and “ + name2;

After executing these statements, name3 contains the string “Beth and Betty”. Note how you can use the + operator to join String objects with string literals. You can also join String objects with numerical values or bool values and have the values converted automatically to a string before the join operation. The following statements illustrate this:

String^ str = L”Value: “;

 

String^ str1 = str + 2.5;

// Result is new string “Value: 2.5”

String^ str2 = str + 25;

// Result is new string “Value: 25”

String^ str3 = str + true;

// Result is new string “Value: True”

 

 

You can also join a String and a character, but the result depends on the type of character:

char ch = ‘Z’; wchar_t wch = ‘Z’

String^

str4

=

str

+

ch;

//

Result

is

new

string

“Value:

90”

String^

str5

=

str

+

wch;

//

Result

is

new

string

“Value:

Z”

The comments show the results of the operations. A character of type char is treated as a numerical value so you get the character code value joined to the string. The wchar_t character is of the same type as the characters in the String object (type Char) so the character is appended to the string.

Don’t forget that String objects are immutable; once created, they cannot be changed. This means that all operations that apparently modify String objects always result in new String objects being created.

The String class also defines a Join() function that you use when you want to join a series of strings stored in an array into a single string with separators between the original strings. Here’s how you could join names together in a single string with the names separated by commas:

array<String^>^ names = { “Jill”, “Ted”, “Mary”, “Eve”, “Bill”}; String^ separator = “, “;

String^ joined = String::Join(separator, names);

After executing these statements, joined references the string “Jill, Ted, Mary, Eve, Bill”. The separator string has been inserted between each of the original strings in the names array. Of course, the separator string can be anything you like(it could be “ and “, for example, which results in the string “Jill and Ted and Mary and Eve and Bill”.

Let’s try a full example of working with String objects.

217

Chapter 4

Try It Out

Working with Strings

Suppose you have an array of integer values that you want to output aligned in columns. You want the values aligned but you want the columns to be just sufficiently wide to accommodate the largest value in the array with a space between columns. This program does that.

//Ex4_17.cpp : main project file.

//Creating a custom format string

#include “stdafx.h”

using namespace System;

int main(array<System::String ^> ^args)

{

array<int>^ values

= { 2, 456, 23, -46,

34211, 456, 5609, 112098,

234, -76504, 341, 6788, -909121, 99, 10};

String^ formatStr1 = “{0,”;

// 1st half of format string

String^ formatStr2 = “}”;

// 2nd half of format string

String^ number;

 

// Stores a number as a string

// Find the length of the maximum length value string

int maxLength = 0;

 

// Holds the maximum length found

for each(int value in values)

 

 

{

 

 

 

number = “” + value;

// Create string from value

if(maxLength<number->Length)

 

 

maxLength = number->Length;

}

//Create the format string to be used for output String^ format = formatStr1 + (maxLength+1) + formatStr2;

//Output the values

int numberPerLine = 3;

for(int i = 0 ; i< values->Length ; i++)

{

Console::Write(format, values[i]); if((i+1)%numberPerLine == 0)

Console::WriteLine();

}

return 0;

}

The output from this program is:

2

456

23

-46

34211

456

5609

112098

234

-76504

341

6788

-909121

99

10

Press any key to continue . . .

218