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

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

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

Arrays, Strings, and Pointers

How It Works

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

=

“{0,”;

//

1st

half

of

format

string

String^

formatStr2

=

“}”;

//

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 = “” + 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.

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 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.

219

Chapter 4

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 = {“ 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 so 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 be 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 are 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 leftor right-justified

220

Arrays, Strings, and Pointers

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 “

3.142”

String^ rightPadded = value->PadRight(10);

// Result is “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 “*****3.142” String^ rightPadded = value->PadRight(10, L’#’); // Result is “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 upperor lowercase. Here’s how that works:

String^

proverb

= L”Many hands make

light

work.”;

String^

upper =

proverb->ToUpper();

//

Result “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 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.

221

Chapter 4

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(“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(“The sentence does{0] end with ‘outside’.”,

sentence->EndsWith(L”outside”) ? L”” : L” not”);

The result of the conditional operator expression is inserted in the output string. This is an empty string if EndsWith() returns true and “ 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 a 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 and 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:

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” on 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

222

Arrays, Strings, and Pointers

index is non-negative so 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() functions except that is 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 string 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 addition check for a negative value of index in the loop condition prevents this from happening; if the right operand of the && operator is false, the left operand is not evaluated.

The last search function I want to mention is IndexOfAny() that 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 searches 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_18.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

223

Chapter 4

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.

^ ^ ^^ ^ ^

Press any key to continue . . .

How It Works

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 because 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

}

224

Arrays, Strings, and Pointers

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 contains the number of punctuation characters that were found, and indicators will contain ‘^’ characters at the positions in sentence where such characters were found.

The output is produced by the 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 stackValue to be a tracking reference to the variable value, which has been created on the stack. You can now modify value using stackValue:

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 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 point is always an automatic variable that is local to a function.

225

Chapter 4

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 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 use the increment and decrement an interior pointer to the 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 points. 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_19.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)

226

Arrays, Strings, and Pointers

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!

Press any key to continue . . .

How It Works

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 and 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

227

Chapter 4

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 that determines whether the for loop continues is:

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 to reference objects that are part of a CLR heap object and you’ll see more about this later in the book.

Summar y

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 important points discussed in this chapter relating to native C++ programming are:

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.

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

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

228