Chapter 4 Arrays, Strings, and Pointers, Section 3

Applies to: Visual Studio 2010

Provided by: Ivor Horton

Book Cover

Buy This Book from Publisher

This topic contains the following sections.

  • C++/CLI Programming
  • Summary
  • What You Learned in This Chapter
  • Copyright

C++/CLI Programming

Dynamic memory allocation works differently with the CLR, and the CLR maintains its own memory heap that is independent of the native C++ heap. The CLR automatically deletes memory that you allocate on the CLR heap when it is no longer required, so you do not need to use the delete operator in a program written for the CLR. The CLR may also compact heap memory to avoid fragmentation from time to time. Thus, at a stroke, the CLR greatly reduces the possibility of memory leaks and memory fragmentation. The management and clean-up of the heap that the CLR provides is described as garbage collection — the garbage being your discarded variables and objects. The heap that is managed by the CLR is called the garbage-collected heap. You use the gcnew operator instead of new to allocate memory in a C++/CLI, program; the ‘gc’ prefix is a cue to the fact that you are allocating memory on the garbage-collected heap, and not the native C++ heap, where all the housekeeping is down to you.

The CLR garbage collector is able to delete objects and release the memory that they occupy when they are no longer required. An obvious question arises: How does the garbage collector know when an object on the heap is no longer required? The answer is quite simple. The CLR keeps track of every variable that references each object in the heap; when there are no variables containing the address of a given object, the object can no longer be referred to in a program, and therefore can be deleted.

Because the garbage collection process can involve compacting the heap memory area to remove fragmented unused blocks of memory, the addresses of data items that you have stored in the heap can change. Consequently, you cannot use ordinary native C++ pointers with the garbage-collected heap, because if the location of the data that is pointed to changes, the pointer will no longer be valid. You need a way to access objects on the heap that enables the address to be updated when the garbage collector relocates the data item in the heap. This capability is provided in two ways: by a tracking handle (also referred to simply as a handle) that is analogous to a pointer in native C++, and by a tracking reference that provides the equivalent of a native C++ reference in a CLR program.

Tracking Handles

A tracking handle has similarities to a native C++ pointer, but there are significant differences, too. A tracking handle does store an address, which is automatically updated by the garbage collector if the object it references is moved during compaction of the heap. However, you cannot perform address arithmetic with a tracking handle as you can with a native pointer, and casting a tracking handle is not permitted.

You use tracking handles to reference objects created in the CLR heap. All objects that are reference class types are stored in the heap; therefore, the variables you create to refer to such objects must be tracking handles. For instance, the String class type is a reference class type, so variables that reference String objects must be tracking handles. The memory for value class types is allocated on the stack by default, but you can choose to store values in the heap by using the gcnew operator. This is also a good time to remind you of a point I mentioned in Chapter 2 — that variables allocated on the CLR heap, which includes all CLR reference types, cannot be declared at global scope.

Creating Tracking Handles

You specify a handle for a type by placing the ^ symbol (commonly referred to as a ‘hat') following the type name. For example, here’s how you could declare a tracking handle with the name proverb that can store the address of a String object:

String^ proverb;

This defines the variable proverb to be a tracking handle of type String^. When you declare a handle it is automatically initialized with null, so it will not refer to anything. To explicitly set a handle to null you use the keyword nullptr like this:

proverb = nullptr;                     // Set handle to null

Note that you cannot use 0 to represent null here, as you can with native pointers (even though it is now not recommended). If you initialize a tracking handle with 0, the value 0 is converted to the type of object that the handle references, and the address of this new object is stored in the handle.

The nullptr keyword in C++/CLI has a different meaning from the nullptr keyword in native C++. This doesn’t matter, as long as you are not mixing native C++ code that uses native pointers with C++/CLI code. If you are, you must use __nullptr as the null pointer value for your native C++ pointers and nullptr for the value of handles in the C++/CLI code. Although you can mix native C++ and C++/CLI code, it is best avoided as far as possible.

Of course, you can initialize a handle explicitly when you declare it. Here’s another statement that defines a handle to a String object:

String^ saying(L"I used to think I was indecisive but now I'm not so sure");

This statement creates a String object on the heap that contains the string between the parentheses; the address of the new object is stored in saying. Note that the type of the string literal is const wchar_t*, not type String. The way the String class has been defined makes it possible for such a literal to be used to create an object of type String.

Here’s how you could create a handle for a value type:

int^ value(99);

This statement creates the handle value of type int^; the value it points to on the heap is initialized to 99. Remember that you have created a kind of pointer, so value cannot participate in arithmetic operations without dereferencing it. To dereference a tracking handle, you use the * operator in the same way as you do for native pointers. For example, here is a statement that uses the value pointed to by a tracking handle in an arithmetic operation:

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

The expression *value between the parentheses accesses the integer stored at the address held in the tracking handle, so the variable result is set to 213.

Note that when you use a handle on the left of an assignment, there’s no need to explicitly dereference it to store a result; the compiler takes care of it for you. For example:

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

Here you first create the handle result, initialized to null. Because result appears on the left of an assignment in the next statement, and the right-hand side produces a value, the compiler is able to determine that result must be dereferenced to store the value. Of course, you could write it explicitly like this:

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

Here you explicitly dereference the handle on the left of the assignment.

CLR Arrays

CLR arrays are different from the native C++ arrays. Memory for a CLR array is allocated on the garbage-collected heap, but there’s more to it than that. CLR arrays have built-in functionality that you don’t get with native C++ arrays, as you’ll see shortly. You specify an array variable type using the keyword array. You must also specify the type for the array elements between angled brackets following the array keyword. The general form for specifying the type of variable to reference a one-dimensional array is array<element_type>^. Because a CLR array is created on the heap, an array variable is always a tracking handle. Here’s an example of a declaration for an array variable:

array<int>^ data;

The array variable, data, that you create here can store a reference to any one-dimensional array of elements of type int.

You can create a CLR array using the gcnew operator at the same time that you declare the array variable:

array<int>^ data = gcnew array<int>(100);  // Create an array to store 100 integers

This statement creates a one-dimensional array with the name data. Note that an array variable is a tracking handle, so you must not forget the hat following the element type specification between the angled brackets. The number of elements appears between parentheses following the array type specification, so this array contains 100 elements, each of which can store a value of type int. Of course, you can also use functional notation to initialize the variable data:

array<int>^ data(gcnew array<int>(100));   // Create an array to store 100 integers

Just like native C++ arrays, CLR array elements are indexed from zero, so you could set values for the elements in the data array like this:

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

This loop sets the values of the elements to 2, 4, 6, and so on up to 200. Elements in a CLR array are objects; here you are storing objects of type Int32 in the array. Of course, these behave like ordinary integers in arithmetic expressions, so the fact that they are objects is transparent in such situations.

The number of elements appears in the loop control expression as a literal value. It would be better to use the Length property of the array that records the number of elements, like this:

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

To access the Length property, you use the -> operator, because data is a tracking handle and works like a pointer. The Length property records the number of elements in the array as a 32-bit integer value. If you need it, you can get the array length as a 64-bit value through the LongLength property.

You can also use the for each loop to iterate over all the elements in an array:

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

The first statement demonstrates that you can initialize an array handle with an array defined by a set of values. The size of the array is determined by the number of initial values between the braces, in this case five, and the values are assigned to the elements in sequence. Thus the handle values will reference an array of 5 integers where the elements have the values 3, 5, 6, 8 and 6. Within the loop, item references each of the elements in the values array in turn. The first statement in the body of the loop stores twice the current element’s value plus 1 in item. The second statement in the loop outputs the new value, right-justified in a field width of five characters; the output produced by this code fragment is:

    7   11   13   17   13

It is easy to get the wrong idea about what is going on here. The for each loop above does not change the elements in the values array. item is a variable that accesses the value of each array element in turn; it does not reference the array elements themselves.

An array variable can store the address of any array of the same rank (the rank being the number of dimensions, which in the case of the data array is 1) and element type. For example:

data = gcnew array<int>(45);

This statement creates a new one-dimensional array of 45 elements of type int and stores its address in data. The original array referenced by the handle, data, is discarded.

Of course, the elements in an array can be of any type, so you can easily create an array of strings:

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

The elements of this array are initialized with the strings that appear between the braces, and the number of strings between the braces determines the number of elements in the array. String objects are created on the CLR heap, so each element in the array is a tracking handle of type String^.

If you declare the array variable without initializing it and then want it to reference an array you create subsequently, you must explicitly create the array in order to use a list of initial values. Here’s an example:

array<String^>^ names;                 // Declare the array variable
names = gcnew array<String^>{ "Jack", "Jane", "Joe", "Jessica", "Jim", "Joanna"};

The first statement creates the array variable names, which will be initialized with nullptr by default. The second statement creates an array of elements of type String^ and initializes it with handles to the strings between the braces. Without the explicit gcnew definition the statement will not compile.

You can use the static Clear() function that is defined in the Array class to set any sequence of numeric elements in an array to zero. You call a static function using the class name. You’ll learn more about such functions when you explore classes in detail. Here’s an example of how you could use the Clear() function to clear an array of elements of type double:

Array::Clear(samples, 0, samples->Length);            // Set all elements to zero

The first argument to Clear() is the array that is to be cleared, the second argument is the index for the first element to be cleared, and the third argument is the number of elements to be cleared. Thus, this example sets all the elements of the samples array to 0.0. If you apply the Clear() function to an array of tracking handles such as String^, the elements are set to nullptr and if you apply it to an array of bool elements they are set to false.

It’s time to let a CLR array loose in an example.

Try it Out: Using a CLR Array

In this example, you generate an array containing random values and then find the maximum value. Here’s the code:

// Ex4_13.cpp : main project file.
// Using a CLR array
#include "stdafx.h"
        
using namespace System;
        
int main(array<System::String ^> ^args)
{
  array<double>^ samples = gcnew array<double>(50);

  // Generate random element values
  Random^ generator = gcnew Random;
  for(int i = 0 ; i< samples->Length ; i++)
    samples[i] = 100.0*generator->NextDouble();

  // Output the samples
  Console::WriteLine(L"The array contains the following values:");
  for(int i = 0 ; i< samples->Length ; i++)
  {
    Console::Write(L"{0,10:F2}", samples[i]);
    if((i+1)%5 == 0)
      Console::WriteLine();
  }

  // Find the maximum value
  double max(0);
  for each(double sample in samples)
    if(max < sample)
      max = sample;

Console::WriteLine(L"The maximum value in the array is {0:F2}", max);
  return 0;
}

Typical output from this example looks like this:

The array contains the following values:
     30.38     73.93     29.82     93.00     78.14
     89.53     75.87      5.98     45.29     89.83

      5.25     53.86     11.40      3.34     83.39
     69.94     82.43     43.05     32.87     59.50
     58.89     96.69     34.67     18.81     72.99
     89.60     25.53     34.00     97.35     55.26
     52.64     90.85     10.35     46.14     82.03
     55.46     93.26     92.96     85.11     10.55
     50.50      8.10     29.32     82.98     76.48
     83.94     56.95     15.04     21.94     24.81
The maximum value in the array is 97.35

Tip

You first create an array that stores 50 values of type double:

  array<double>^ samples = gcnew array<double>(50);

The array variable, samples, must be a tracking handle, because CLR arrays are created on the garbage-collected heap.

You populate the array with pseudo-random values of type double with the following statements:

  Random^ generator = gcnew Random;
  for(int i = 0 ; i< samples->Length ; i++)
    samples[i] = 100.0*generator->NextDouble();

The first statement creates an object of type Random on the CLR heap. A Random object has functions that will generate pseudo-random values. Here you use the NextDouble() function in the loop, which returns a random value of type double that lies between 0.0 and 1.0. By multiplying this by 100.0 you get a value between 0.0 and 100.0. The for loop stores a random value in each element of the samples array.

A Random object also has a Next() function that returns a random non-negative value of type int. If you supply an integer argument when you call the Next() function, it will return a random non-negative integer which is less than the argument value. You can also supply two integer arguments that represent the minimum and maximum values for the random integer to be returned.

The next loop outputs the contents of the array, five elements to a line:

  Console::WriteLine(L"The array contains the following values:");
  for(int i = 0 ; i< samples->Length ; i++)
  {
    Console::Write(L"{0,10:F2}", samples[i]);
    if((i+1)%5 == 0)
      Console::WriteLine();
  }

Within the loop, you write the value of each element with a field width of 10 and 2 decimal places. Specifying the field width ensures the values align in columns. You also write a newline character to the output whenever the expression (i+1)%5 is zero, which is after every fifth element value, so you get five to a line in the output.

Finally, you figure out what the maximum element value is:

  double max = 0;
  for each(double sample in samples)
    if(max < sample)
      max = sample;

This uses a for each loop just to show that you can. The loop compares max with each element value in turn, and whenever the element is greater than the current value in max, max is set to that value. You end up with the maximum element value in max.

You could use a for loop here if you also wanted to record the index position of the maximum element as well as its value — for example:

  double max = 0;
  int index = 0;
  for (int i = 0 ; i < samples->Length ; i++)
    if(max < samples[i])
    {
      max = samples[i];
      index = i;
    }

Sorting One-Dimensional Arrays

The Array class in the System namespace defines a Sort() function that sorts the elements of a one-dimensional array so that they are in ascending order. To sort an array, you just pass the array handle to the Sort() function. Here’s an example:

array<int>^ samples = { 27, 3, 54, 11, 18, 2, 16};
Array::Sort(samples);                            // Sort the array elements
        
for each(int value in samples)                   // Output the array elements
  Console::Write(L"{0, 8}", value);
Console::WriteLine();

The call to the Sort() function rearranges the values of the elements in the samples array into ascending sequence. The result of executing this code fragment is:

    2    3   11   16   18   27   54

You can also sort a range of elements in an array by supplying two more arguments to the Sort() function, specifying the index for the first element of those to be sorted, and the number of elements to be sorted. For example:

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

This statement sorts the three elements in the samples array that begin at index position 2. After executing these statements, the elements in the array will have the values:

   27    3   11   18   54    2   16

There are several other versions of the Sort() function that you can find if you consult the documentation, but I’ll introduce one other that is particularly useful. This version presumes you have two arrays that are associated such that the elements in the first array represent keys to the corresponding elements in the second array. For example, you might store names of people in one array and the weights of the individuals in a second array. The Sort() function sorts the array of names in ascending sequence and also rearranges the elements of the weights array so that the weights still match the appropriate person. Let’s try it in an example.

Try it Out: Sorting Two Associated Arrays

This example creates an array of names, and stores the weights of each person in the corresponding element of a second array. It then sorts both arrays in a single operation.

// Ex4_14.cpp : main project file.
// Sorting an array of keys(the names) and an array of objects(the weights)
        
#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);                   // Sort the arrays
  for each(String^ name in names)                // Output the names
    Console::Write(L"{0, 10}", name);
  Console::WriteLine();

  for each(int weight in weights)                // Output the weights
    Console::Write(L"{0, 10}", weight);
  Console::WriteLine();
    return 0;
}

The output from this program is:

        Al      Bill       Eve      Jill      Mary       Ted
       176       180       115       103       128       168

Tip

The values in the weights array correspond to the weight of the person at the same index position in the names array. The Sort() function you call here sorts both arrays using the first array argument — names, in this instance — to determine the order of both arrays. You can see from the output that after sorting, everyone still has his or her correct weight recorded in the corresponding element of the weights array.

Searching One-Dimensional Arrays

The Array class provides functions that search the elements of a one-dimensional array. Versions of the BinarySearch() function use a binary search algorithm to find the index position of a given element in the entire array, or in a given range of elements. The binary search algorithm requires that the elements are ordered, if it is to work, so you need to sort the elements before you search an array.

Here’s how you could search an entire array:

array<int>^ values = { 23, 45, 68, 94, 123, 127, 150, 203, 299};
int toBeFound(127);
int position = Array::BinarySearch(values, 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);

The value to be found is stored in the toBeFound variable. The first argument to the BinarySearch() function is the handle of the array to be searched, and the second argument specifies what you are looking for. The result of the search is returned by the BinarySearch() function as a value of type int. If the second argument to the function is found in the array specified by the first argument, its index position is returned; otherwise a negative integer is returned. Thus, you must test the value returned to determine whether or not the search target was found. Because the values in the values array are already in ascending sequence, there is no need to sort the array before searching it. This code fragment would produce the output:

127 was found at index position 5.

To search a given range of elements in an array you use a version of the BinarySearch() function that accepts four arguments. The first argument is the handle of the array to be searched, the second argument is the index position of the element where the search should start, the third argument is the number of elements to be searched, and the fourth argument is what you are looking for. Here’s how you might use that:

array<int>^ values = { 23, 45, 68, 94, 123, 127, 150, 203, 299};
int toBeFound(127);
int position = Array::BinarySearch(values, 3, 6, toBeFound);

This searches the values array from the fourth array element through to the last. As with the previous version of BinarySearch(), the function returns the index position found, or a negative integer if the search fails.

Let’s try a searching example.

Try it Out: Searching Arrays

This is a variation on the previous example with a search operation added:

// Ex4_15.cpp : main project file.
// Searching an array
        
#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"};

  Array::Sort( names, weights);                  // Sort the arrays

  int result(0);                                 // Stores search result
  for each(String^ name in toBeFound)            // Search to find weights
  {
    result = Array::BinarySearch(names, name);   // Search names array

    if(result<0)                                 // Check the result
      Console::WriteLine(L"{0} was not found.", name);
    else
      Console::WriteLine(L"{0} weighs {1} lbs.", name, weights[result]);
  }
  return 0;
}

This program produces the output:

Bill weighs 180 lbs.
Eve weighs 115 lbs.
Al weighs 176 lbs.
Fred was not found.

Tip

You create two associated arrays — an array of names and an array of corresponding weights in pounds. You also create the toBeFound array that contains the names of the people whose weights you’d like to know.

You sort the names and weights arrays, using the names array to determine the order. You then search the names array for each name in the toBeFound array using a for each loop. The loop variable, name, is assigned each of the names in the toBeFound array in turn. Within the loop, you search for the current name with the statement:

    result = Array::BinarySearch(names, name);   // Search names array

This returns the index of the element from names that contains name, or a negative integer if the name is not found. You then test the result and produce the output in the if statement:

    if(result<0)                                 // Check the result
      Console::WriteLine(L"{0} was not found.", name);
    else
      Console::WriteLine(L"{0} weighs {1} lbs.", name, weights[result]);

Because the ordering of the weights array was determined by the ordering of the names array, you are able to index the weights array with result, the index position in the names array where name was found. You can see from the output that “Fred” was not found in the names array.

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, and 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 that is one greater than the length of 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 it 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);

This statement creates a two-dimensional array with four rows and five columns for 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 placed, 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 can 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 a two-dimensional C++/CLI array, because this has no meaning; the array is a two-dimensional array of elements. 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_16.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
  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 |
_________________________________________________________________

Tip

It looks like a lot of code, but most of it is concerned with making the output pretty. You create the two-dimensional 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 12 3 12 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
  }

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>^; 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 array are also handles to String objects, so you must replace type in the last expression with 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 of 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.

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_17.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(L"Students with Grade {0}:", gradeLetter++);

    for each( String^ student in grade)
      Console::Write(L"{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

Tip

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: the first time around the loop references the array of grade A student names, the 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(L"Students with Grade {0}:", gradeLetter++);
        
    for each( String^ student in grade)
      Console::Write(L"{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 to 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() function 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(L"{0,12}",grade[i]);         // Output the current name

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

You could 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(L"Students with Grade {0}:", gradeLetter+j);
    for (int i  = 0 ; i < grades[j]->Length ; i++)
      Console::Write(L"{0,12}",grades[j][i]);         // Output the current name
    Console::WriteLine();
  }

Now grades[j] references the jth array of names; 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, making 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 initialized with the string that appears between the parentheses. 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; 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(L"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(L"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 you get to investigate C++/CLI classes in detail.

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 L"Value: 2.5"
  String^ str2(str + 25);              // Result is new string L"Value: 25"
  String^ str3(str + true);            // Result is new string L"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(L'Z');
String^ str4(str + ch);                // Result is new string L"Value: 90"
String^ str5(str + wch);               // Result is new string L"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 = { L"Jill", L"Ted", L"Mary", L"Eve", L"Bill"};
String^ separator(L", ");
String^ joined = String::Join(separator, names);

After executing these statements, joined references the string L“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 L“ and ”, for example, which results in the string L“Jill and Ted and Mary and Eve and Bill”.

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

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_18.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(L"{0,");           // 1st half of format string
  String^ formatStr2(L"}");             // 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 = L"" + 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

Tip

The objective of this program is to create a format string to align the output of integers from the values array in columns, with a width sufficient to accommodate the maximum length string representation of the integers. You create the format string initially in two parts:

  String^ formatStr1(L"{0,");           // 1st half of format string
  String^ formatStr2(L"}");             // 2nd half of format string

These two strings are the beginning and end of the format string you ultimately require. You need to work out the length of the maximum-length number string, and sandwich that value between formatStr1 and formatStr2, to form the complete format string.

You find the length you require with the following code:

  int maxLength(0);                    // Holds the maximum length found
  for each(int value in values)

  {
    number = L"" + value;               // Create string from value
    if(maxLength<number->Length)
      maxLength = number->Length;
  }

Within the loop you convert each number from the array to its String representation by joining it to an empty string. You compare the Length property of each string to maxLength, and if it’s greater than the current value of maxLength, it becomes the new maximum length.

The statement that creates a string from value shows how an integer is automatically converted to a string when you combine it with a string using the addition operator. You could also obtain value as a string with the following statement:

    number = value.ToString();

This uses the ToString() function that is defined in the System::Int32 value class that converts an integer value to a string.

Creating the format string is simple:

  String^ format(formatStr1 + (maxLength+1) + formatStr2);

You need to add 1 to maxLength to allow one additional space in the field when the maximum length string is displayed. Placing the expression maxLength+1 between parentheses ensures that it is evaluated as an arithmetic operation before the string-joining operations are executed.

Finally, you use the format string in the code to output values from the array:

  int numberPerLine(3);
  for(int i = 0 ; i< values->Length ; i++)
  {
    Console::Write(format, values[i]);
    if((i+1)%numberPerLine == 0)
      Console::WriteLine();
  }

The output statement in the loop uses format as the string for output. With the maxLength plugged into the format string, the output is in columns that are one greater than the maximum length output value. The numberPerLine variable determines how many values appear on a line, so the loop is quite general in that you can vary the number of columns by changing the value of numberPerLine.

Modifying Strings

The most common requirement for trimming a string is to trim spaces from both the beginning and the end. The Trim() function for a string object does that:

String^ str = {L" Handsome is as handsome does... "};
String^ newStr(str->Trim());

The Trim() function in the second statement removes any spaces from the beginning and end of str and returns the result as a new String object stored in newStr. Of course, if you did not want to retain the original string, you could store the result back in str.

There’s another version of the Trim() function that allows you to specify the characters that are to be removed from the start and end of the string. This function is very flexible because you have more than one way of specifying the characters to be removed. You can specify the characters in an array and pass the array handle as the argument to the function:

String^ toBeTrimmed(L"wool wool sheep sheep wool wool wool");
array<wchar_t>^ notWanted = {L'w',L'o',L'l',L' '};
Console::WriteLine(toBeTrimmed->Trim(notWanted));

Here you have a string, toBeTrimmed, that consists of sheep covered in wool. The array of characters to be trimmed from the string is defined by the notWanted array; passing that to the Trim() function for the string removes any of the characters in the array from both ends of the string. Remember, String objects are immutable, so the original string is not being changed in any way — a new string is created and returned by the Trim() operation. Executing this code fragment produces the output:

sheep sheep

If you happen to specify the character literals without the L prefix, they will be of type char (which corresponds to the SByte value class type); however, the compiler arranges that they are converted to type wchar_t.

You can also specify the characters that the Trim() function is to remove explicitly as arguments, so you could write the last line of the previous fragment as:

Console::WriteLine(toBeTrimmed->Trim(L'w', L'o', L'l', L' '));

This produces the same output as the previous version of the statement. You can have as many arguments of type wchar_t as you like, but if there are a lot of characters to be specified, an array is the best approach.

If you want to trim only one end of a string, you can use the TrimEnd() or TrimStart() functions. These come in the same variety of versions as the Trim() function. So: without arguments you trim spaces, with an array argument you trim the characters in the array, and with explicit wchar_t arguments those characters are removed.

The inverse of trimming a string is padding it at either end with spaces or other characters. You have PadLeft() and PadRight() functions that pad a string at the left or right end, respectively. The primary use for these functions is in formatting output where you want to place strings either left- or right-justified in a fixed width field. The simpler versions of the PadLeft() and PadRight() functions accept a single argument specifying the length of the string that is to result from the operation. For example:

String^ value(L"3.142");
String^ leftPadded(value->PadLeft(10));        // Result is L"     3.142"
String^ rightPadded(value->PadRight(10));      // Result is L"3.142     "

If the length you specify as the argument is less than or equal to the length of the original string, either function returns a new String object that is identical to the original.

To pad a string with a character other than a space, you specify the padding character as the second argument to the PadLeft() or PadRight() functions. Here are a couple of examples of this:

String^ value(L"3.142");
String^ leftPadded(value->PadLeft(10, L'*'));    // Result is L"*****3.142"
String^ rightPadded(value->PadRight(10, L'#'));  // Result is L"3.142#####"

Of course, with all these examples, you could store the result back in the handle referencing the original string, which would discard the original string.

The String class also has the ToUpper() and ToLower() functions to convert an entire string to upper- or lowercase. Here’s how that works:

String^ proverb(L"Many hands make light work.");
String^ upper(proverb->ToUpper());     // Result L"MANY HANDS MAKE LIGHT WORK."

The ToUpper() function returns a new string that is the original string converted to uppercase.

You use the Insert() function to insert a string at a given position in an existing string. Here’s an example of doing that:

String^ proverb(L"Many hands make light work.");
String^ newProverb(proverb->Insert(5, L"deck "));

The function inserts the string specified by the second argument, starting at the index position in the old string, which is specified by the first argument. The result of this operation is a new string containing:

Many deck hands make light work.

You can also replace all occurrences of a given character in a string with another character, or all occurrences of a given substring with another substring. Here’s a fragment that shows both possibilities:

String^ proverb(L"Many hands make light work.");
Console::WriteLine(proverb->Replace(L' ', L'*'));
Console::WriteLine(proverb->Replace(L"Many hands", L"Pressing switch"));

Executing this code fragment produces the output:

Many*hands*make*light*work.
Pressing switch make light work.

The first argument to the Replace() function specifies the character or substring to be replaced, and the second argument specifies the replacement.

Comparing Strings

You can compare two String objects using the Compare() function in the String class. The function returns an integer that is less than zero, equal to zero, or greater than zero, depending on whether the first argument is less than, equal to, or greater than the second argument. Here’s an example:

String^ him(L"Jacko");
String^ her(L"Jillo");
int result(String::Compare(him, her));
if(result < 0)
  Console::WriteLine(L"{0} is less than {1}.", him, her);
else if(result > 0)
  Console::WriteLine(L"{0} is greater than {1}.", him, her);
else
  Console::WriteLine(L"{0} is equal to {1}.", him, her);

You store the integer that the Compare() function returns in result, and use that in the if statement to decide the appropriate output. Executing this fragment produces the output:

Jacko is less than Jillo.

There’s another version of Compare() that requires a third argument of type bool. If the third argument is true, then the strings referenced by the first two arguments are compared, ignoring case; if the third argument is false, then the behavior is the same as the previous version of Compare().

Searching Strings

Perhaps the simplest search operation is to test whether a string starts or ends with a given substring. The StartsWith() and EndsWith() functions do that. You supply a handle to the substring you are looking for as the argument to either function, and the function returns a bool value that indicates whether or not the substring is present. Here’s a fragment showing how you might use the StartsWith() function:

String^ sentence(L"Hide, the cow's outside.");
if(sentence->StartsWith(L"Hide"))
  Console::WriteLine(L"The sentence starts with 'Hide'.");

Executing this fragment results in the output:

The sentence starts with 'Hide'.

Of course, you could also apply the EndsWith() function to the sentence string:

Console::WriteLine(L"The sentence does{0} end with 'outside'.",
                                sentence->EndsWith(L"outside") ? L"" : L" not");

The result of the conditional operator expression is inserted into the output string. This is an empty string if EndsWith() returns true, and L“not” if it returns false. In this instance the function returns false (because of the period at the end of the sentence string).

The IndexOf() function searches a string for the first occurrence of a specified character or substring, and returns the index if it is present, or -1 if it is not found. You specify the character or the substring you are looking for as the argument to the function. For example:

String^ sentence(L"Hide, the cow's outside.");
int ePosition(sentence->IndexOf(L'e'));          // Returns 3
int thePosition(sentence->IndexOf(L"the"));      // Returns 6

The first search is for the letter ‘e’ and the second is for the word “the”. The values returned by the IndexOf() function are indicated in the comments.

More typically, you will want to find all occurrences of a given character or substring. Another version of the IndexOf() function is designed to be used repeatedly, to enable you to do that. In this case, you supply a second argument specifying the index position where the search is to start. Here’s an example of how you might use the function in this way:

String^ words(L"wool wool sheep sheep wool wool wool");
String^ word(L"wool");
int index(0);
int count(0);
while((index = words->IndexOf(word,index)) >= 0)
{
  index += word->Length;
  ++count;
}
Console::WriteLine(L"'{0}' was found {1} times in:\n{2}", word, count, words);

This fragment counts the number of occurrences of “wool” in the words string. The search operation appears in the while loop condition, and the result is stored in index. The loop continues as long as index is non-negative; when IndexOf() returns -1 the loop ends. Within the loop body, the value of index is incremented by the length of word, which moves the index position to the character following the instance of word that was found, ready for the search on the next iteration. The count variable is incremented within the loop, so when the loop ends it has accumulated the total number of occurrences of word in words. Executing the fragment results in the following output:

'wool' was found 5 times in:
wool wool sheep sheep wool wool wool

The LastIndexOf() function is similar to the IndexOf() function except that it searches backwards through the string from the end or from a specified index position. Here’s how the operation performed by the previous fragment could be performed using the LastIndexOf() function:

int index(words->Length - 1);
int count(0);
while(index >= 0 && (index = words->LastIndexOf(word,index)) >= 0)
{
  --index;
  ++count;
}

With the word and words strings the same as before, this fragment produces the same output. Because LastIndexOf() searches backwards, the starting index is the last character in the string, which is words->Length-1. When an occurrence of word is found, you must now decrement index by 1, so that the next backward search starts at the character preceding the current occurrence of word. If word occurs right at the beginning of words — at index position 0 — decrementing index results in –1, which is not a legal argument to the LastIndexOf() function because the search starting position must always be within the string. The additional check for a negative value of index in the loop condition prevents this from happening; if the left operand of the && operator is false, the right operand is not evaluated.

The last search function I want to mention is IndexOfAny(), which searches a string for the first occurrence of any character in the array of type array<wchar_t> that you supply as the argument. Similar to the IndexOf() function, the IndexOfAny() function comes in versions that search from the beginning of a string or from a specified index position. Let’s try a full working example of using the IndexOfAny() function.

Try it Out: Searching for Any of a Set of Characters

This example searches a string for punctuation characters:

// Ex4_19.cpp : main project file.
// Searching for punctuation
        
#include "stdafx.h"
        
using namespace System;
        
int main(array<System::String ^> ^args)
{
  array<wchar_t>^ punctuation = {L'"', L'\'', L'.', L',', L':', L';', L'!', L'?'};
  String^ sentence(L"\"It's chilly in here\", the boy's mother said coldly.");

  // Create array of space characters same length as sentence
  array<wchar_t>^ indicators(gcnew array<wchar_t>(sentence->Length){L' '});

  int index(0);                        // Index of character found
  int count(0);                        // Count of punctuation characters
  while((index = sentence->IndexOfAny(punctuation, index)) >= 0)
  {
    indicators[index] = L'^';          // Set marker
    ++index;                           // Increment to next character
    ++count;                           // Increase the count
  }
  Console::WriteLine(L"There are {0} punctuation characters in the string:",
                                                                          count);
  Console::WriteLine(L"\n{0}\n{1}", sentence, gcnew String(indicators));
  return 0;
  }

This example should produce the following output:

There are 6 punctuation characters in the string:
        
"It's chilly in here", the boy's mother said coldly.
^  ^                ^^        ^                    ^

Tip

You first create an array containing the characters to be found and the string to be searched:

  array<wchar_t>^ punctuation = {L'"', L'\'', L'.', L',', L':', L';', L'!', L'?'};
  String^ sentence(L"\"It's chilly in here\", the boy's mother said coldly.");

Note that you must specify a single quote character using an escape sequence because a single quote is a delimiter in a character literal. You can use a double quote explicitly in a character literal, as there’s no risk of it being interpreted as a delimiter in this context.

Next you define an array of characters with the elements initialized to a space character:

  array<wchar_t>^ indicators(gcnew array<wchar_t>(sentence->Length){L' '});

This array has as many elements as the sentence string has characters. You’ll be using this array in the output to mark where punctuation characters occur in the sentence string. You’ll just change the appropriate array element to ‘^’ whenever a punctuation character is found. Note how a single initializer between the braces following the array specification can be used to initialize all the elements in the array.

The search takes place in the while loop:

  while((index = sentence->IndexOfAny(punctuation, index)) >= 0)
  {
    indicators[index] = L'^';          // Set marker
    ++index;                           // Increment to next character
    ++count;                           // Increase the count
  }

The loop condition is essentially the same as you have seen in earlier code fragments. Within the loop body, you update the indicators array element at position index to be a ‘^’ character, before incrementing index, ready for the next iteration. When the loop ends, count will contain the number of punctuation characters that were found, and indicators will contain ‘^’ characters at the positions in the sentence where such characters were found.

The output is produced by the following statements:

  Console::WriteLine(L"There are {0} punctuation characters in the string:",
                                                                          count);
  Console::WriteLine(L"\n{0}\n{1}" sentence, gcnew String(indicators));

The second statement creates a new String object on the heap from the indicators array by passing the array to the String class constructor. A class constructor is a function that will create a class object when it is called. You’ll learn more about constructors when you get into defining your own classes.

Tracking References

A tracking reference provides a similar capability to a native C++ reference in that it represents an alias for something on the CLR heap. You can create tracking references to value types on the stack and to handles in the garbage-collected heap; the tracking references themselves are always created on the stack. A tracking reference is automatically updated if the object referenced is moved by the garbage collector.

You define a tracking reference using the % operator. For example, here’s how you could create a tracking reference to a value type:

int value(10);
int% trackValue(value);

The second statement defines trackValue to be a tracking reference to the variable value, which has been created on the stack. You can now modify value using trackValue:

trackValue *= 5;
Console::WriteLine(value);

Because trackValue is an alias for value, the second statement outputs 50.

Interior Pointers

Although you cannot perform arithmetic on the address in a tracking handle, C++/CLI does provide a form of pointer with which it is possible to apply arithmetic operations; it’s called an interior pointer, and it is defined using the keyword interior_ptr. The address stored in an interior pointer can be updated automatically by the CLR garbage collection when necessary. An interior pointer is always an automatic variable that is local to a function.

Here’s how you could define an interior point containing the address of the first element in an array:

  array<double>^ data = {1.5, 3.5, 6.7, 4.2, 2.1};
  interior_ptr<double> pstart(&data[0]);

You specify the type of object pointed to by the interior pointer between angled brackets following the interior_ptr keyword. In the second statement here you initialize the pointer with the address of the first element in the array using the & operator, just as you would with a native C++ pointer. If you do not provide an initial value for an interior pointer, it is initialized with nullptr by default. An array is always allocated on the CLR heap, so here’s a situation where the garbage collector may adjust the address contained in an interior pointer.

There are constraints on the type specification for an interior pointer. An interior pointer can contain the address of a value class object on the stack, or the address of a handle to an object on the CLR heap; it cannot contain the address of a whole object on the CLR heap. An interior pointer can also point to a native class object or a native pointer.

You can also use an interior pointer to hold the address of a value class object that is part of an object on the heap, such as an element of a CLR array. This way, you can create an interior pointer that can store the address of a tracking handle to a System::String object, but you cannot create an interior pointer to store the address of the String object itself. For example:

interior_ptr<String^> pstr1;      // OK - pointer to a handle
interior_ptr<String> pstr2;       // Will not compile - pointer to a String object

All the arithmetic operations that you can apply to a native C++ pointer you can also apply to an interior pointer. You can increment and decrement an interior pointer to change the address it contains, to refer to the following or preceding data item. You can also add or subtract integer values and compare interior pointers. Let’s put together an example that does some of that.

Try it Out: Creating and Using Interior Pointers

This example exercises interior pointers with numerical values and strings:

// Ex4_20.cpp : main project file.
// Creating and using interior pointers
        
#include "stdafx.h"
        
using namespace System;
        
int main(array<System::String ^> ^args)
{
  // Access array elements through a pointer
  array<double>^ data = {1.5, 3.5, 6.7, 4.2, 2.1};
  interior_ptr<double> pstart(&data[0]);
  interior_ptr<double> pend(&data[data->Length - 1]);
  double sum(0.0);
  while(pstart <= pend)
    sum += *pstart++;

  Console::WriteLine(L"Total of data array elements = {0}\n", sum);

  // Just to show we can - access strings through an interior pointer
  array<String^>^ strings = { L"Land ahoy!",
                              L"Splice the mainbrace!",
                              L"Shiver me timbers!",
                              L"Never throw into the wind!"
                            };

  for(interior_ptr<String^> pstrings = &strings[0] ;
             pstrings-&strings[0] < strings->Length ; ++pstrings)
   Console::WriteLine(*pstrings);
  return 0;
}

The output from this example is:

Total of data array elements = 18
Land ahoy!
Splice the mainbrace!
Shiver me timbers!
Never throw into the wind!

Tip

After creating the data array of elements of type double, you define two interior pointers:

  interior_ptr<double> pstart(&data[0]);
  interior_ptr<double> pend(&data[data->Length - 1]);

The first statement creates pstart as a pointer to type double and initializes it with the address of the first element in the array, data[0]. The interior pointer, pend, is initialized with the address of the last element in the array, data[data->Length - 1]. Because data->Length is the number of elements in the array, subtracting 1 from this value produces the index for the last element.

The while loop accumulates the sum of the elements in the array:

  while(pstart <= pend)
    sum += *pstart++;

The loop continues as long as the interior pointer, pstart, contains an address that is not greater than the address in pend. You could equally well have expressed the loop condition as !(pstart > pend).

Within the loop, pstart starts out containing the address of the first array element. The value of the first element is obtained by dereferencing the pointer with the expression *pstart; the result of this is added to sum. The address in the pointer is then incremented using the ++ operator. On the last loop iteration, pstart contains the address of the last element, which is the same as the address value that pend contains So, incrementing pstart makes the loop condition false because pstart is then greater than pend. After the loop ends the value of sum is written out, so you can confirm that the while loop is working as it should.

Next you create an array of four strings:

  array<String^>^ strings = { L"Land ahoy!",
                              L"Splice the mainbrace!",
                              L"Shiver me timbers!",
                              L"Never throw into the wind!"
                           };

The for loop then outputs each string to the command line:

  for(interior_ptr<String^> pstrings = &strings[0] ;
             pstrings-&strings[0] < strings->Length ; ++pstrings)
    Console::WriteLine(*pstrings);

The first expression in the for loop condition declares the interior pointer, pstrings, and initializes it with the address of the first element in the strings array. The second expression determines whether the for loop continues:

pstrings-&strings[0] < strings->Length

As long as pstrings contains the address of a valid array element, the difference between the address in pstrings and the address of the first element in the array is less than the number of elements in the array, given by the expression strings->Length. Thus, when this difference equals the length of the array, the loop ends. You can see from the output that everything works as expected.

The most frequent use of an interior pointer is referencing objects that are part of a CLR heap object, and you’ll see more about this later in the book.

Summary

You are now familiar with all of the basic types of values in C++, how to create and use arrays of those types, and how to create and use pointers. You have also been introduced to the idea of a reference. However, we have not exhausted all of these topics. I’ll come back to the topics of arrays, pointers, and references later in the book.

The pointer mechanism is sometimes a bit confusing because it can operate at different levels within the same program. Sometimes it is operating as an address, and at other times it can be operating with the value stored at an address. It’s very important that you feel at ease with the way pointers are used, so if you find that they are in any way unclear, try them out with a few examples of your own until you feel confident about applying them.

Exercises

You can download the source code for the examples in the book and the solutions to the following exercises from www.wrox.com.

  1. Write a native C++ program that allows an unlimited number of values to be entered and stored in an array allocated in the free store. The program should then output the values, five to a line, followed by the average of the values entered. The initial array size should be five elements. The program should create a new array with five additional elements, when necessary, and copy values from the old array to the new.

  2. Repeat the previous exercise but use pointer notation throughout instead of arrays.

  3. Declare a character array, and initialize it to a suitable string. Use a loop to change every other character to uppercase.

Hint: In the ASCII character set, values for uppercase characters are 32 less than their lowercase counterparts.

  1. Write a C++/CLI program that creates an array with a random number of elements of type int. The array should have from 10 to 20 elements. Set the array elements to random values between 100 and 1000. Output the elements, five to a line, in ascending sequence without sorting the array; for example, find the smallest element and output that, then the next smallest, and so on.

  2. Write a C++/CLI program that will generate a random integer greater than 10,000. Output the integer and then output the digits in the integer in words. For example, if the integer generated were 345678, then the output should be:

The value is 345678
three four five six seven eight
  1. Write a C++/CLI program that creates an array containing the following strings:
"Madam I'm Adam."
"Don't cry for me, Marge and Tina."
"Lid off a daffodil."
"Red lost soldier."
"Cigar? Toss it in a can. It is so tragic."

The program should examine each string in turn, output the string, and indicate whether it is or is not a palindrome (that is, whether it is the same sequence of letters reading backward or forward, ignoring spaces and punctuation).

What You Learned in This Chapter

Topic

Concept

Native C++ arrays

An array allows you to manage a number of variables of the same type using a single name. Each dimension of an array is defined between square brackets, following the array name in the declaration of the array.

Array dimensions

Each dimension of an array is indexed starting from zero. Thus, the fifth element of a one-dimensional array has the index value 4.

Initializing arrays

Arrays can be initialized by placing the initializing values between curly braces in the declaration.

Pointers

A pointer is a variable that contains the address of another variable. A pointer is declared as a ‘pointer to type’ and may only be assigned addresses of variables of the given type.

Pointers to const and const pointers

A pointer can point to a constant object. Such a pointer can be reassigned to another object. A pointer may also be defined as const, in which case it can’t be reassigned.

References

A reference is an alias for another variable, and can be used in the same places as the variable it references. A reference must be initialized in its declaration. A reference can’t be reassigned to another variable.

The sizeof operator

The operator sizeof returns the number of bytes occupied by the object specified as its argument. Its argument may be a variable or a type name between parentheses.

The new operator

The operator new allocates memory dynamically in the free store in a native C++ application. When memory has been assigned as requested, it returns a pointer to the beginning of the memory area provided. If memory cannot be assigned for any reason, an exception is thrown that by default causes the program to terminate.

The gcnew operator

In a CLR program, you allocate memory in the garbage-collected heap using the gcnew operator.

Reference class objects

Reference class objects in general, and String objects in particular, are always allocated on the CLR heap.

String class objects

You use String objects when working with strings in a CLR program.

CLR arrays

The CLR has its own array types with more functionality that native array types. CLR arrays are created on the CLR heap.

Tracking handles

A tracking handle is a form of pointer used to reference variables defined on the CLR heap. A tracking handle is automatically updated if what it refers to is relocated in the heap by the garbage collector. Variables that reference objects and arrays on the heap are always tracking handles.

Tracking references

A tracking reference is similar to a native reference, except that the address it contains is automatically updated if the object referenced is moved by the garbage collector.

Interior pointers

An interior pointer is a C++/CLI pointer type to which you can apply the same operation as a native pointer.

Modifying interior pointers

The address contained in an interior pointer can be modified using arithmetic operations and still maintain an address correctly, even when referring to something stored in the CLR heap.

Ivor Horton’s Beginning Visual C++® 2010, Copyright © 2010 by Ivor Horton, ISBN: 978-0-470-50088-0, Published by Wiley Publishing, Inc., All Rights Reserved. Wrox, the Wrox logo, Wrox Programmer to Programmer, and related trade dress are trademarks or registered trademarks of John Wiley & Sons, Inc and/or its affiliates.