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

Pro Visual C++-CLI And The .NET 2.0 Platform (2006) [eng]-1

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

68 C H A P T E R 2 C + + / C L I B A S I C S

int ^y = gcnew int(100);

// create a handle to an int

Console::WriteLine(y);

// print

out int.

*y = 110;

//

Assign new value to dereferenced int

Console::WriteLine(*y);

//

print

out dereferenced int.

}

Figure 2-15 shows the results of this little program.

Figure 2-15. Results of ReferenceIndirect.exe

Unsafe Code Directly modifying a dereferenced value is unverifiable and therefore is an unsafe operation. Thus, the preceding example cannot be compiled using the /clr:safe option.

Operator Precedence

I have shown operator precedence for each operator in its own section, but what if operators from different sections occur in the same statement? Table 2-19 shows the precedence of all the operators.

Table 2-19. Operator Precedence

Precedence

Operators

Highest

() [] ::

 

! ~ ++ -- - (unary) * (unary) % (unary) & (unary)

 

* / %

 

+ -

 

<< >>

 

< <= > >=

 

== !=

 

&

 

^

 

|

 

&&

C H A P T E R 2 C + + / C L I B A S I C S

69

Table 2-19. Operator Precedence

Precedence

Operators

 

||

 

?:

Lowest

= += -= *= /= %= >>= <<= &= ^= |=

 

 

Flow Control Constructs

Normally in a C++/CLI program, statements are executed sequentially from beginning to end. There will be times when a program is going to execute a portion of code only if certain conditions are true. To handle conditional execution of code, C++/CLI provides two flow control constructs: if and switch.

if Statement

The if statement enables the conditional execution of code based on the evaluated value of some condition. An if statement in its simplest form is as follows:

if ( condition )

{

statements;

}

The condition can be any expression, but to make more sense it should evaluate to a Boolean value of either true or false. It is perfectly valid to evaluate to a zero (false) or nonzero (true) condition, as well.

Obviously, it is possible to execute a block of code when a condition is not true, as shown here:

if ( ! condition )

{

statements;

}

What if you want a block of code to execute when a condition is true and some other block of code to execute when the condition is false? You could write two if statements, one for the true condition and one for the false condition, or you could use the if-else statement, which looks like this:

if ( condition )

{

statements;

}

else // ! condition (the comment is optional)

{

statements;

}

There is one more construct for if statements. What if you want different blocks of code to be executed based on mutually exclusive conditions? You could write a stream of if conditions, one for each condition, but then each condition would have to be checked, which would be a waste of time. Instead, you should use the if-else–if-else construct, also called the nested if construct, which exits the if construct once it matches a condition. The nested if construct looks like this:

70

C H A P T E R 2 C + + / C L I B A S I C S

if ( condition1 ) // first mutually exclusive condition

{

statements;

}

else if ( condition2 ) // second mutually exclusive condition

{

statements;

}

else // optional catch the rest condition

{

statements;

}

This example will display a different string depending on the value of the animal variable:

enum Creature : int {Dog, Cat, Eagle}; Creature animal;

// assign a value to animal animal = Cat;

if ( animal == Dog )

{

Console::WriteLine ("The animal is a dog");

}

else if ( animal == Cat )

{

Console::WriteLine ("The animal is a cat");

}

else // animal is not a dog or cat

{

Console::WriteLine ("Maybe the animal is a bird");

}

switch Statement

The switch statement is a multiple-choice flow-control construct. It functions in a very similar manner to the nested if construct, except that it only works for integer value types or expressions that evaluate to integers. The switch statement works like this: The switch expression is checked against each case constant. If a case constant matches the expression, then its associated statements are executed. If no case constant matches the expression, then the default statements are executed. Finally, the switch statement is exited.

A switch statement looks like this:

switch ( expression )

{

case constant1: statements1; break;

case constant2: statements2; break;

default:

statements3;

}

C H A P T E R 2 C + + / C L I B A S I C S

71

You can write the preceding nested if statement as a switch statement, like this:

switch ( animal )

{

case Dog:

Console::WriteLine ("The animal is a dog"); break;

case Cat:

Console::WriteLine ("The animal is a cat"); break;

default:

Console::WriteLine ("Maybe the animal is a bird");

}

The first thing you may notice is that each case ends with a break statement. This break statement tells the switch that it is finished. If you fail to place a break statement at the end of a case, then the following case will also be executed. This may sound like a mistake, but there are times when this falling through to the next case is exactly what you will want. For example, this case statement executes the same code for lowerand uppercase characters:

switch ( keypressed )

{

case 'A': case 'a':

Console::WriteLine ("Pressed the A key"); break;

case 'B': case 'b':

Console::WriteLine ("Pressed the B key"); break;

default:

Console::WriteLine ("Pressed some other key");

}

Caution A missing break statement is a very common and difficult error to debug, because often the error caused by it does not occur until later in the program.

Looping Constructs

So far, you have seen that C++/CLI programs are statements that are executed sequentially from beginning to end, except when flow control dictates otherwise. Obviously, there are scenarios in which you would like to be able to repeat a single statement or a block of statements a certain number of times, until a certain condition occurs or for all elements of a collection. C++/CLI provides four looping constructs for this: while, do while, for, and for each.

while Loop

The while loop is the simplest looping construct provided by C++/CLI. It simply repeats a statement or a block of statements while the condition is true (some people prefer to say until the condition is false). The basic format of a while loop is as follows:

72

C H A P T E R 2 C + + / C L I B A S I C S

while ( condition )

{

statements;

}

The condition is checked at the start of each iteration of the loop, including the first. Thus, if the condition evaluates at the start to false, then the statements never are executed. The while loop condition expression is the same as an if statement condition.

In its simplest form, the while loop repeats a statement or a block of statements forever:

while ( true )

{

statements;

}

I cover how to break out of this type of loop a little later.

More commonly, you will want the while loop condition to be evaluated. Here is an example of how to display all the numbers from 1 to 6 inclusive:

int i = 0; while ( i < 6)

{

i++;

Console::WriteLine(i);

}

do-while Loop

There are scenarios in which you will want or need the loop to always execute at least once. You could do this in one of two ways:

Duplicate the statement or block of statements before the while loop.

Use the do while loop.

Obviously, the do while loop is the better of the two solutions.

Like the while loop, the do while loop loops through a statement or a block of statements until a condition becomes false. Where the do while differs is that it always executes the body of the loop at least once. The basic format of a do while loop is as follows:

do {

statements;

} while ( condition );

As you can see, the condition is checked at the end of every iteration of the loop. Therefore, the body is guaranteed to execute at least once. The condition is just like the while statement and the if statement.

Like the while statement, the most basic form of the do while loop loops forever, but because this format has no benefit over the while statement, it is seldom used. Here is the same example previously used for the while statement. It displays the numbers 1 to 6 inclusive.

int i = 0; do {

i++;

Console::WriteLine(i); } while ( i < 6 );

C H A P T E R 2 C + + / C L I B A S I C S

73

Caution Do not forget the semicolon (;) after the closing bracket of the condition because if you do, the compiler will generate a few angry messages and not compile successfully.

for Loop

The for loop is the most complex construct for handling looping and can be used for almost any kind of loop. In its simplest form, the for loop, like the other two loop constructs, simply repeats a statement or a block of statements forever:

for ( ; ; )

{

statements;

}

Normally, you will want control of how your program will loop, and that’s what the for loop excels at. With the for loop, you can not only check to see if a condition is met as you do in the while loop, but you can also initialize and increment variables on which to base the condition. The basic format for a for loop is this:

for (initialization; condition; increment)

{

statements;

}

When the code starts executing a for loop (only the first time), the initialization is executed. The initialization is an expression that initializes variables that will be used in the loop. It is also possible to actually declare and initialize variables that will only exist while they are within the loop construct.

The condition is checked at every iteration through the loop, even the first. This makes it similar to the while loop. In fact, if you don’t include the initialization and increment, the for loop acts in an identical fashion to the while loop. You can use almost any type of condition statement, so long as it evaluates to false or zero when you want to exit the loop.

The increment executes at the end of each iteration of the for loop and just before the condition is checked. Usually the code increments (or decrements) the variables that were initialized in the initialization, but this is not a requirement.

Let’s look at a simple for loop in action. This for loop creates a counter i, which will iterate so long as it remains less than 6 or, in other words, because you start iterating at zero, this for loop will repeat six times.

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

{

Console::WriteLine ( i );

}

The output of this for loop is as follows:

0

1

2

3

4

5

74

C H A P T E R 2 C + + / C L I B A S I C S

One thing to note is that the initialization variable is accessible within the for loop, so it is possible to alter it while the loop is executing. For example, this for loop, though identical to the previous example, will only iterate three times:

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

{

i++;

Console::WriteLine ( i );

}

The output of this for loop is as follows:

1

3

5

for loops are not restricted to integer type. It is possible to use floating-point or even more advanced constructs. Though this might not mean much to some of you, for loops are a handy way of iterating through link lists. (I know it is a little advanced at this point in the book, but I am throwing it in here to show how powerful the for loop can be.) For those of you who want to know what this does, it loops through the elements of a link list to the maximum of ten link list elements:

for (int i=0, list *cur=headptr; i<10 && cur->next != 0; i++, cur=cur->next)

{

statements;

}

for each Loop

Although the for each construct has been in Visual Basic for a long time and in C# since its inception, the for each loop is just now making its appearance in C++/CLI. For now, the for each is strictly

a C++/CLI construct, as it allows the iteration through all items in a collection deriving from the IEnumerable interface. I will cover in detail collections and the IEnumerable interface in Chapter 7, so at present I will stick to the collection that we already have covered, the array.

You might think that because of the specific nature of this construct it won’t be very helpful. Well, you would be wrong—the .NET Framework is filled with collections, and most developers use many different types of collections (not just arrays) within their code. Thanks to the for each construct your code will be considerably simplified.

The basic syntax of the for each loop is

for each ( <data declaration> in collection)

{

}

Therefore, if you have an array named numbers, this is how you iterate through it:

array<int>^ numbers = gcnew array<int> { 1, 2, 3, 4 }; for each ( int i in numbers )

{

Console::WriteLine(i);

}

C H A P T E R 2 C + + / C L I B A S I C S

75

There is one gotcha, however. With the for each loop, you can’t modify the collection itself while iterating through. This doesn’t mean you can’t change the contents of the elements of the collection. It means you can’t add or remove elements to or from the collection. This is not an issue for arrays, given that this is not allowed anyway, but for many other collection types it may be a problem. The worst thing is if the compiler doesn't catch it. It is the CLR that lets you know about it by throwing an exception. I’ll cover exceptions in Chapter 4; I show you this gotcha in action when I cover collections in Chapter 7.

Skipping Loop Iterations

Even though you have set up a loop to iterate through multiple iterations of a block of code, there may be times that some of the iteration doesn’t need to be executed. In C++/CLI, you can do this with a continue statement.

You usually find the continue statement in some type of condition statement. When the continue statement is executed, the program jumps immediately to the next iteration. In the case of the while and do while loops, the condition is checked, and the loop continues or exits depending on the result of the condition. For a for each loop the next element in the collection is retrieved and then continues, unless there are no more elements, and then the loop exits. If continue is used in a for loop, the increment executes first, and then the condition executes.

Here is a simple and quite contrived example that will print out all the prime numbers under 30:

for (Int32 i = 1; i < 30; i++)

{

if ( i % 2 == 0 && i / 2 > 1) continue;

else if ( i % 3 == 0 && i / 3 > 1) continue;

else if ( i % 5 == 0 && i / 5 > 1) continue;

else if ( i % 7 == 0 && i / 7 > 1) continue;

Console::WriteLine(i);

}

Breaking Out of a Loop

Sometimes you need to leave a loop early, maybe because there is an error condition and there is no point in continuing, or in the case of the loops that will loop indefinitely, you simply need a way to exit the loop. In C++/CLI, you do this with a break statement. The break statement in a loop works the same way as the switch statement you saw earlier.

There is not much to the break statement. When it is executed, the loop is terminated, and the flow of the program continues after the loop.

Though this is not really a very good example, the following sample shows how you could implement do while type flow in a for loop. This loop breaks when it gets to 10:

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

{

Console::WriteLine(i);

if (i >= 10) break;

}

76

C H A P T E R 2 C + + / C L I B A S I C S

Functions

At the core of all C++/CLI programs is the function. It is the source of all activity within a program. Functions also enable programmers to break their programs into manageable chunks. You have already been using a function called main(). Now let’s see how you can go about creating a few of your own.

The general format of a function looks like this:

return-type function-name ( parameter-list )

{

statements-of-the-function;

}

The return-type of the function is the value type, handle, pointer, or reference that is returned by the function when it finishes. The return type can be any value type, reference, handle, or pointer, even ones that are user defined. If no return type is specified for the function, then C++/CLI defaults the return value to int. If the function does not return a value, then the return value should be set to the keyword void.

The function-name is obviously the name of the function. The rules of naming a function are the same as those for naming a variable.

The parameter-list is a comma-separated list of variable declarations that define the variable, which will be passed to the function when it starts executing. Parameter variables can be any value types, references, handles, or pointers, even ones that are user defined.

Passing Arguments to a Function

There are two different ways of passing arguments to a function: by value and by reference. Syntactically, there is little difference between the two. In fact, the only difference is that passing by reference has an additional reference operator (percent [%]) placed before the value name:

void example ( int ByValue, int %ByReference )

{

}

The big difference is in how the actual values are passed. When passing by value, a copy of the variable is passed to the function. Because the argument is a copy, the function can’t change the original passed argument value. For example, this function takes the value of parameter a and adds 5 to it:

void example ( int a )

{

a = a + 5;

}

When the function is called,

int a = 5; example(a);

the value of a will still be 5.

What if you want to actually update the value of the parameter passed so that it reflects any changes made to it within the function? You have two ways to handle this. The first is to pass a handle by value. Because you are passing a handle to the value, and not the actual value, any changes that you make to the value within the function will be reflected outside the function. The problem of passing by handle is that now the syntax of the function is more complicated because you have to worry about dereferencing the handles.

Returning Values from a Function
Returning a value from a function is a two-step process. First, specify the type of value the function will return, and second, using the return statement, pass a return value of that type:
double example()
{
double a = 8.05; // do some stuff return a;
}
the value of
a will still be 5.
int b = a + 5;
}
When the function is called,
int a = 5; example(a);
a = a + 5;
// This line will cause a compiler error because // we are trying to change the const a
void example ( const int %a )
{
//
void example ( int ^a )
{
*a = *a + 5;
}
When the function is called,
int ^a = 5; example(a);
the value of a will be 10.
The second approach is to pass the arguments by reference. When passing arguments by reference, the argument value is not copied; instead, the function is accessing an alias of the argument or, in other words, the function is accessing the argument directly.
void example ( int %a )
{
a = a + 5;
}
When the function is called,
int a = 5;
int b = example(a);
the value of a will be 10.
There is a pro and a con to using references. The pro is that it is faster to pass arguments by reference, as there is no copy step involved. The con is that, unlike using handlers, other than %, there is no difference between passing by value or reference. There is a very real possibility that changes can happen to argument variables within a function without the programmer knowing.
The speed benefit is something some programmers don’t want to give up, but they still want to feel secure that calling a function will not change argument values. To solve this problem, it is possible to pass const reference values. When these are implemented, the compiler makes sure that nothing within the function will cause the value of the argument to change:

C H A P T E R 2 C + + / C L I B A S I C S

77