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

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

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

48

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

array<datatype, rank>^ arrayname;

The rank specifies the number of dimensions of the array and can range from 1 to 32. Any other value generates an error. The rank must also be explicit. Therefore, the rank cannot be a variable. It must be either a numeric literal or a numeric const value. When this rank is greater than 1, then the array is multidimensional. Notice, with this syntax it is possible to write single and multidimensional array declarations the same way:

using namespace stdcli::language;

 

 

array<int, 1>^ Ints_5

= gcnew

array<int>(5);

array<int,

2>^ Ints_5x3

=

gcnew

array<int>(5,

3);

array<int,

3>^ Ints_5x3x2 =

gcnew

array<int>(5,

3, 2);

Multidimensional arrays declared in the preceding fashion all have dimensions of uniform size or, in the case of a two-dimensional array, are rectangular. It is also possible to have arrays that have different sizes within a dimension. This form of declaring multidimensional arrays, usually known as jagged arrays, is made up of arrays of arrays. With the new array syntax, declaring an array in this format is a breeze:

array< array<datatype>^ >^

Notice all you do is make the data type of the outer array declaration another array declaration. Initializing the array takes a little more effort, but then again it is not complicated. Here we create a two-dimensional array, in which the first dimension is 4 and the second dimension varies from 5 to 20.

array< array<int>^ >^ jagged = gcnew array< array<int>^ >(4);

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

{

e[i] = gcnew array<int>((i+1) * 5); // each row 5 bigger

}

In the preceding example, I show how to subscript into an array, or in layman’s terms, how to access an element of an array. For those of you with prior C++ experience, this should look familiar. It’s the name of the array followed by the index to the element enclosed in square brackets:

variable_name[index];

Be careful though: Multidimensional arrays are accessed in a different syntax than traditional arrays. Instead of the name of the array followed by each dimension index in its own square bracket, the syntax is now the name of the array followed by a comma-delimitated list of dimension indexes enclosed in a single set of square brackets:

variable_name[index1,index2,index3];

Caution Just to complicate things, jagged arrays use the traditional syntax to access an element of an array.

Unlike traditional C++, subscripting is not a synonym for pointer arithmetic, and it is not commutative. Thus, the only way to access data from an array is by using subscripts with all dimensions starting at a value of zero.

Two very helpful static methods of the System::Array are Sort() and Reverse(), which provide quick ways to sort and reverse the order of the elements in an array. Reverse() is shown in the following example.

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

49

Listing 2-9 is a program showing C++/CLI arrays in action.

Listing 2-9. C++/CLI Arrays in Action

using namespace System;

// Arrays in Action void main()

{

// Single dimension

array<int>^ a = gcnew array<int>(4); array<String^>^ b = gcnew array<String^>(4);

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

{

a[i] = i;

}

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

{

b[i] = a[i].ToString();

}

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

{

Console::WriteLine(b[i]);

}

Console::WriteLine();

Array::Reverse(b);

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

{

Console::WriteLine(b[i]);

}

// Multi dimension uniform

array<int,2>^ c = gcnew array<int,2>(4,3); array<String^,2>^ d = gcnew array<String^,2>(4,3);

for (int x = 0; x < c->GetLength(0); x++)

{

for (int y = 0; y < c->GetLength(1); y++)

{

c[x,y] = (x*10)+y;

}

}

Console::WriteLine();

for (int x = 0; x < d->GetLength(0); x++)

{

for (int y = 0; y < d->GetLength(1); y++)

{

Console::Write("{0,-5:00}", c[x,y]);

}

Console::WriteLine();

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

}

// Multidimension jagged

array< array<int>^ >^ e = gcnew array<array<int>^>(4);

for (int x = 0; x < e->Length; x++)

{

e[x] = gcnew array<int>(4+(x*2)); // each row 2 bigger for(int y = 0; y < e[x]->Length; y++)

{

e[x][y] = (x*10)+y;

}

}

Console::WriteLine();

for (int x = 0; x < e->Length; x++)

{

for (int y = 0; y < e[x]->Length; y++)

{

Console::Write("{0,-5:00}", e[x][y]);

}

Console::WriteLine();

}

}

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

Figure 2-10. Results of Arrays.exe

Classes

A class is a fundamental building block of most C++/CLI programs. Classes are made up of data members, properties, and methods. Classes are designed to provide the object-oriented nature of the C++/CLI programming language. In other words, they provide the ability to implement encapsulation, inheritance, and polymorphism.

Chapter 3 covers classes in detail.

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

51

Interfaces

An interface is a collection of methods and properties, without actual definitions, placed into a single unit. In other words, an interface has no implementations for its own methods and properties. You might want to think of an interface as a binding contract of all the methods and properties that an inheriting class must provide.

Chapter 3 covers interfaces.

Delegates and Events

A delegate is a reference type that acts as a “function pointer” that can be bound to either an instance or a static method within a C++/CLI class. Delegates can be used whenever a method needs to be called in a dynamic nature, and they are usually used as callback functions or for handling events within .NET Framework applications. I examine delegates in Chapter 4.

An event is a specialized implementation of a delegate. An event allows one class to trigger the execution of methods found in other classes without knowing anything about these classes or even from which classes it is invoking the method. I examine events in Chapter 4, and they are implemented quite extensively in Chapters 9 and 10.

Boxing and Unboxing

In previous versions of C++/CLI (Managed Extensions for C++ versions 1.1 and prior) boxing was a big deal, but now with .NET version 2.0, you can almost not worry about it at all. Boxing is the CLR technique for converting value types into reference types. And, conversely, unboxing is the technique for converting reference types into value types.

The default form of storage for the .NET Framework value types is on the stack, in its machine native form. In this form, a data type cannot access its methods, such as ToString(), because the value type needs to be in an object (reference) format. To remedy this, the value type implicitly (automatically) is boxed whenever the ToString() method is called.

Note In prior versions of C++/CLI (Managed Extensions for C++), implicit boxing only occurred with fundamental value types. In .NET version 2.0, all value types are implicitly boxed.

For example, to box the following simple POINT value type:

value class POINT

{

public:

int x, y;

POINT(int x, int y) : x(x) , y(y) {}

};

POINT p1(1,2);

would take either of the following lines of code:

Object ^o = p1; // -or-

POINT ^hp = p1;

52

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

Caution The created boxed object is a copy of the value type. Therefore, any modifications made to the boxed object will not be reflected in the contents of the originating value type.

Unboxing a reference type back into its value type simply requires a type cast. You will probably find that unboxing comes in handy when you have stored your boxed value types in a collection (which store reference types) and want to convert them back to their value types. I cover type casting in the “Type Conversions” section later in this chapter and collections in Chapter 7.

Here’s how you would unbox the preceding two boxed value types.

POINT p2 = (POINT)o;

POINT p3 = (POINT)hp;

Type Modifiers and Qualifiers

Three modifiers and one data type qualifier are provided to C++/CLI programmers. They provide a little information to help define the variables they precede.

auto

The auto modifier tells the compiler that it should create the variable when entering a block and destroy it when exiting the block. If this sounds like most variables to you, you would be right, as it is the default modifier for all variables. Placing the auto keyword in front of variables is optional. In fact, I have never seen it used myself, but if you like typing, here is how you would use it in a program:

auto int normalInteger;

const

The const qualifier tells the compiler that the variable it is associated with cannot change during execution. It also means that objects referenced to by a const handle or pointed to by a const pointer cannot be changed. Constants are the opposite of variables. The syntax to create a const data type is simply this:

const Int32 integerConstant = 42;

Note that you need to initialize a const at the time of declaration.

Caution C++/CLI does not support const member methods on managed data types. For example, bool GetFlag() const {return true;} is not allowed within a value struct or ref class, nor is it supported by an interface.

extern

The extern modifier tells the compiler that the variable is defined elsewhere, usually in a different file, and will be added in when the final executable or library is linked together. It tells the compiler how to define a variable without actually allocating any storage for it. You will see this variable modifier usually when a global variable is used in more than one source file. (I discuss multifile source file assemblies in the Chapter 4.)

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

53

Note An error will occur during the linking of the application if an external variable is not defined in some other source file.

Using the extern modifier looks like this:

extern int externVariable;

static

The static modifier has four meanings based on where it is used.

When the static modifier is applied to a global variable, the variable’s global nature is restricted to the source file in which it is declared. In other words, the variable is accessible to all functions, classes, and so on, declared within the file, but an extern variable or class in another source file will not have access to it.

When the static modifier is applied to a variable within a function (see the "Functions" section), then the variable will not go out of scope or be deleted when the function exits. This means that the next time the function is called, the static variable will retain the same value it had when the function was left the previous time.

When the static modifier is applied to a variable within a class (I discuss classes in Chapter 3), then only one copy of the variable is created, and it is shared by all instances of the class.

When the static modifier is applied to a method within a class, then the method is accessible without the need to instantiate the class.

Here are some basic examples of the static modifier in use:

static int staticVariable;

static void staticFunction ( int arg) { }

Type Conversions

Any time the data type on the left side of an assignment statement has a different data type than the evaluated result of the right side, a type conversion will take place. When the only data types used in the statement are fundamental types, then the conversion will happen automatically. Unfortunately, converting automatically may not always be a good thing, especially if the left side data type is smaller, because the resulting number may lose significant digits. For example, when assigning a UInt16 to a Byte, the following problem may occur:

UInt16

a

= 43690;

Byte b

=

a;

// b now equals 170 not 43690.

Here is what happened. UInt16 is a 16-bit number, so 43690 decimal represented as a 16-bit number is 1010 1010 1010 1010 in binary. Byte is an 8-bit number, so only the last 8 bits of the UInt16 can be placed into the Byte. Thus, the Byte now contains 1010 1010 in binary, which happens to equal only 170 decimal.

The C++/CLI compiler will notify you when this type of error may occur. Being warned, the compiler and, subsequently, the program it generates go merrily on their way.

If you don’t want the warning, but you still want to do this type of conversion, then you can do something called an explicit cast. It’s the programmer’s way of saying, “Yes, I know, but I don’t care.” To code an explicit cast, you use one of the following syntaxes:

safe_cast<data-type-to-convert-to>(expression) // --or--

(data-type-to-convert-to) expression

54

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

Here’s an actual example of both syntaxes:

char b = safe_cast<char>(a); // --or--

char b = (char) a;

Note Unlike prior versions of C++/CLI (Managed Extensions for C++) the use of the old type conversion syntax: (datatype) variable will first try to do a safe_cast, which, in most cases, will make the two syntaxes the same.

In C++/CLI, when resolving an expression, all data types that make up the expression must be the same. If the expression is made up of more than one type, then type conversion occurs to make all the data types the same. If all the data types are integer types, then the data types are converted to an int or Int64 data type. If a data type is a floating-point type, then the all data types in the expression are converted to a float or double.

All these types of conversions happen automatically. There are cases, though, where you may want all data types to be converted to a data type of your choosing. Here again, you use explicit casting, as shown here:

double

realA

= 23.67;

double

realB

= 877.12;

int intTotal

= safe_cast<int>(realA) + safe_cast<int>(realB);

// -or-

 

int intTotal

= (int) realA + (int) realB;

Variable Scope

There are two different scopes: global and local. They have subtleties that might bend these scopes a bit, but that’s something most programmers don’t care about.

Global scope for a variable means that it is declared outside of all functions, classes, and structures that make up a program, even the main() functions. They are created when the program is started and exist for the entire lifetime of the program. All functions, classes, and structures can access global variables. The static modifier has the capability to restrict a global variable to only the source file in which it is declared.

Local variables are local to the block of code in which they are declared. This means that local variables exist within the opening and closing curly brackets within which they were declared. Most commonly, local variables are declared within a function call, but it is perfectly acceptable to declare them within flow control and looping constructs, which you will learn about in the “Flow Control Constructs” and “Looping Constructs” sections. It is also valid to create a block of code only to reduce the scope of a variable.

The following code shows some global and local variable declarations:

int globalVariable; int main()

{

int localFunctionVariable; { int localToOwnBlock; }

}

Namespaces

Some programmers work in an isolated world where their code is the only code. Others use code from many sources. A problem with using code from many sources is that there is a very real possibility that the same names for classes, functions, and so on, can be used by more than one source.

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

55

To allow the same names to be used by multiple sources, namespaces were created. Namespaces create a local scope declarative region for variables, functions, classes, and structures. In other words, namespaces allow programmers to group their code under a unique name.

Creating a namespace simply requires combining all of the code within a named region, such as

namespace MyNamespace

{

// classes, structs, functions, namespace-global variables

}

It is possible to use the same namespace across multiple source code files. The compiler will combine them into one namespace.

To reference something out of a namespace requires the use of the :: operator. For example:

MyNamespace::NSfunc();

Typing the namespace repeatedly can get tiring, so C++/CLI allows the programmer to bring a namespace into the local scope using

using namespace MyNamespace;

Now, with the namespace brought into local scope, the function NSfunc from the previous example can be accessed just like any other function of local scope:

NSfunc();

Caution Bringing multiple namespaces into the local scope could cause duplicate function, class, and struct names to occur.

Literals

Other than Decimals, each of the preceding data types has literals that can be used for things such as initializing variables or as constants. In the preceding programs, I have shown many different literals. In this section, I go over them in more detail.

Numeric Literals

Numeric literals come in five flavors:

Octal numbers

Integer numbers

Hexadecimal numbers

Decimal numbers

Exponential numbers

Octal numbers are hardly ever used anymore. They are mainly still in use just for backward compatibility with some ancient programs. They are base-8 numbers and thus made up of the numbers 0 through 7. All octal numbers start with a 0. Some examples are as follows:

0123 (an integer value of 83)

01010 (an integer value of 520)

56

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

You need to be aware of octal numbers because if you mistakenly start an integer number with a 0, then the compiler will happily treat it as an octal number. For example, if you type in 0246, the compiler will think its value is equivalent to the integer value 166.

Integer numbers are straightforward. They are simply whole numbers. Some examples are as follows:

1234

–1234

+1234

The symbols – and + are not actually part of the number but, in fact, are unary operators that convert the whole number into a negative or positive number. The + unary operator is assumed, so 1234 and +1234 mean the same thing.

Hexadecimal numbers are the most complex of the numeric constants. They are base-16 numbers and are made up of the numbers 0 through 9 and the letters A through F (or a through f, as case does not matter). The letters represent the numbers 10 through 15. A hexadecimal literal always starts with 0x. Some examples of hexadecimal numbers are as follows:

0x1234 (an integer value of 4660)0xabcd (an integer value of 43981)

Decimal numbers are the same as integer numbers, except they also contain a decimal and a fractional portion. They are used to represent real numbers. Some examples are as follows:

1.0

3.1415

–1.23

Just as in integer numbers, the minus symbol (–) is a unary operator and not part of the decimal number.

The last numeric literals are the exponential numbers. They are similar to decimal numbers except that along with the decimal—or more accurately, the mantissa—is the exponent, which tells the compiler how many times to multiply or divide the mantissa by 10. When the exponent is positive, the mantissa is multiplied by 10 exponent times. If the exponent is negative, the mantissa is divided by 10 exponent times. Some examples are as follows:

1.23e4 (a decimal value of 12300.0)

1.23e-4 (a decimal value of 0.000123)

An interesting feature that comes along with C++/CLI is that numeric literals are also objects. This means that they also have the ToString() method. Listing 2-10 shows a numeric literal object in action. Note that you need to surround the numeric literal with brackets.

Listing 2-10. Numeric Literals in Action

using namespace System;

// Integer Literals in Action void main()

{

Console::WriteLine ( 010 ); // An Octal 10 is a base-10 8 Console::WriteLine ( -010 ); // Negative Octal 10 is a base-10 -8

Console::WriteLine ( 0x10 ); // A Hex 10 is a base-10 16

Console::WriteLine ( -0x10 ); // Negative Hex 10 is a base-10 -16

// This is kind of neat. Number literals are objects, too! Console::WriteLine ( (1234567890).ToString() ); Console::WriteLine ( (0xABCDEF).ToString("X") );

}

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

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

57

Figure 2-11. Results of IntegerLiteral.exe

Boolean Literals

There are only two Boolean literals: the values true and false.

Like numeric literals, Boolean literals are objects in C++/CLI. Thus, they too provide the ToString() method. Listing 2-11 shows a Boolean literal object in action.

Listing 2-11. Boolean Literals in Action

using namespace System;

// Boolean Literals in Action void main()

{

bool isTrue = true; bool isFalse = false;

Console::WriteLine ( isTrue );

Console::WriteLine ( isFalse );

// This is kind of neat. Boolean literals are objects, too! Console::WriteLine ( true.ToString () );

Console::WriteLine ( false.ToString () );

}

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

Figure 2-12. Results of BooleanLiteral.exe