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

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

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

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

Console::WriteLine( c ); // display prebroken Decimal

//Break it up into 4 parts array<int>^ d = Decimal::GetBits(c);

//Reassemble using Decimal constructor

Decimal e(d[0], d[1], d[2],

// digits

((d[3] & 0x80000000) == 0x80000000), // sign

((d[3] >>

16) & 0xff) );

// decimal location

Console::WriteLine(

d[0] );

// display part 1

Console::WriteLine(

d[1] );

// display part 2

Console::WriteLine(

d[2] );

// display part 3

Console::WriteLine(

d[3].ToString("X") ); // display part 4

Console::WriteLine(

e );

// display reassembled Decimal

}

Figure 2-4 shows the results of this program.

Figure 2-4. Results of Decimal.exe

Boolean Type

C++/CLI provides only one Boolean type. Table 2-7 describes the details of it.

Table 2-7. Boolean Fundamental Type

C++/CLI Alias

Class Library

Values

bool

System::Boolean

true | not 0 or false | 0

 

 

 

The System::Boolean fundamental type has the C++/CLI alias of bool. A bool can only have a value of true or false.

C++/CLI is a little lenient when it comes to initializing bools, as it allows them to be assigned with the value of zero for false and any number other than zero for true. The compiler does give a warning if the value assigned is not one of the following: true, false, 1, or 0.

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

39

Listing 2-4 is a simple piece of code showing the Boolean type in action.

Listing 2-4. Boolean Type in Action

using namespace System;

// Boolean Fundamental Type in Action void main()

{

bool a = 18757;

// will give

a warning but set to true

bool b = 0;

// false

 

bool c

=

true;

//

obviously

true

bool d

=

false;

//

obviously

false

Console::WriteLine( a ); Console::WriteLine( b ); Console::WriteLine( c ); Console::WriteLine( d );

}

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

Figure 2-5. Results of Boolean.exe

Character Type

C++/CLI provides only one character type. Table 2-8 describes the details of this character type.

Table 2-8. Character Fundamental Type

C++/CLI Alias

Class Library

Value

wchar_t

System::Char

A single 16-bit Unicode character

 

 

 

The .NET Framework class library System::Char is a 16-bit Unicode character, which has a C++/CLI alias of __wchar_t (or wchar_t, if the Zc:wchar_t flag is set on the compiler).

Listing 2-5 is a simple piece of code showing the Char type in action.

40

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

Listing 2-5. Char Type in Action

using namespace System;

// Character Fundamental Type in Action

void main()

 

 

 

{

 

 

 

 

Char

a

= L'A';

//

character literal 'A'

Char

b

= L'\x0041';

//

hex notation for hex 41 which happens to be 'A'

Console::WriteLine ( a );

Console::WriteLine ( b ); //Even though I put hex 41 in b, the ASCII 'A' //is printed due to b being a Char

}

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

Figure 2-6. Results of Chars.exe

Not long ago, all Windows programs used ASCII, an 8-bit, English-only character set. Unfortunately, this was not very helpful for languages such as Chinese, which requires more than the 256-character limit imposed by ASCII. To try to solve this obvious problem, a new encoding protocol was developed called Unicode, within which many character sets could be defined. Unicode uses 16 bits to represent each character instead of ASCII’s 8. ASCII is a subset of Unicode.

Caution Traditional C++ programmers must be wary of the C++/CLI alias char, as it is not the same as the

.NET Framework’s class library Char. A char is an 8-bit ASCII character, whereas a Char is a 16-bit Unicode character.

Reference Types

As a C++/CLI programmer, you can think of reference types as a handle to data in the managed heap that you don’t have to worry about deleting. Handles and pointers have many similarities; they both reference data in a heap (Managed and CRT). Where handles differ considerably from pointers is that a handle’s address can’t be manipulated, or in other words, you can’t add or subtract offsets to a handle as you can with a pointer.

There are many reference types in .NET; the .NET Framework is full of them, but all C++/CLI developers almost always use only two. One is the Object type, the root of all classes in the .NET Framework class library. The other is the String type.

I deal with these two reference types now, but throughout the book I’ll cover many more.

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

41

Object Type

The System::Object is the root type of the entire .NET Framework class library hierarchy. In other words, every object found in the .NET Framework ultimately has, as a parent, the Object type.

Because all objects in the .NET Framework derive from System::Object, all objects inherit several general-purpose methods, such as

Object(): Creates a new instance of an object

Equals(): Compares two object instances to see if they are equal

GetHashCode(): Returns a hash code for the object

GetType(): Returns the data type of the object

ReferenceEquals(): Checks if two instances of an object are the same

ToString(): Returns a string representation of the object

A developer can replace a few of these methods. For example, replacing ToString() allows an object to represent itself in a way that is readable to the user.

String Type

As a C++/CLI programmer, you will probably become very intimate with System::String. Many of your programs will involve character strings. The String type was built to handle them. Traditional C++ programmers can now forget character arrays, CString or STL’s string class—you now have a powerful, predefined .NET Framework reference type to manipulate strings with. As an added bonus, it is completely garbage collected!

Being a reference type, Strings are allocated to the managed heap and referenced using a handle. String types are also immutable, meaning their value cannot be modified once they have been created. This combination allows for the optimized capability of multiple handles representing the same character string managed heap location. When a String object changes, a completely new character string is allocated in the managed heap and, if no other handle references the original String object, then the original String object is garbage collected.

Listing 2-6 is a little program showing the String type in action.

Listing 2-6. String Type in Action

using namespace System;

// String Type in Action void main()

{

//Create some strings String^ s1 = "This will "; String^ s2 = "be a "; String^ s3 = "String";

Console::WriteLine(String::Concat(s1, s2, s3));

//Create a copy, then concatenate new text String^ s4 = s2;

s4 = String::Concat(s4, "new "); Console::WriteLine(String::Concat(s1, s4, s3));

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

// Replace stuff in a concatenated string

String^ s5 = String::Concat(s1, s2, s3)->Replace("i", "*"); Console::WriteLine(s5);

// Insert into a string

String^ s6 = s3->Insert(3, "ange Str"); Console::WriteLine(String::Concat(s1, s2, s6));

// Remove text from strings

s1 = s1->Remove(4, 5); // remove ' will' from middle s2 = s2->Remove(0, 3); // remove 'be ' from start Console::WriteLine(String::Concat(s1, "is ", s2, s3));

}

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

Figure 2-7. Results of StringFun.exe

User-Defined Data Types

With C++/CLI, you can create your own data types. User-defined data types fall into two groups: value types or reference types. As I pointed out earlier, value types are placed directly on the stack, whereas reference types are placed on the managed heap and referenced via the stack.

Value Types

Only three kinds of user-defined value types can be created using C++/CLI:

enum class or enum struct (equivalent)

value struct

value class

The enum class and enum struct types are simply named constants. The value struct and value class types are identical, except that the default access value struct members are public, whereas value class members are private.

Native enums, enum classes, and enum structs

Conceptually, enums and consts share a lot of similarities. Both enable better readability of code. They also allow for the actual value being represented to be maintained at one location in your code, thus enabling the value to be changed at one location and have that change reflected throughout the code. Where enums and consts differ is that enums allow for grouping of common values within a single construct, creating a new data type. Then you can use this new data type to enforce that only

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

43

specific enum values be placed in a specified variable. A const, on the other hand, is just a representation of a value and cannot be used to define a new data type.

Like the fundamental types already discussed, enums default to being placed on the stack but can beused automatically as objects when required.

There are two different syntaxes for declaring enums in C++/CLI. Ultimately, both syntax generate the same metadata and inherit from System::enums.

The first syntax is the pre-C++/CLI style, better known as a native enum. C++/CLI has augmented the native enum with the addition of an optional ability to declare the underlying data type. The data type of a native enum can be explicitly declared as one of the following data types: bool, char, unsigned char, signed char, short, unsigned short, int, unsigned int, long long, unsigned long long, float, or double. Here is an example of a native enum with and without the optional declaration of the data type:

enum Creature { Dog, Cat, Eagle };

enum Vehicle : char { Car, Boat, Plane };

The second syntax, know as CLI enums, is the preferred one for managed code (according to Microsoft) and mirrors more the syntax of the other value type declarations:

enum class Creature { Dog, Cat, Eagle }; enum struct Creature { Dog, Cat, Eagle };

CLI enums are different from native enums in that the names of the CLI enums’ values, better known as enumerators, can only be found through the scope of the enums’ name, and the declaring of the enums’ data type has no meaning with a CLI enum. What this means to you is that to code a native enum like this:

Creature animal; animal = Cat;

you code a CLI enum like this:

Creature animal;

animal = Creature::Cat;

The following example creates a CLI enum of all the primary colors. Then the function prints the string equivalent of the primary color enum using a switch statement. I describe the switch statement later in this chapter.

The System::Enum from which enums originate provides a simpler way of doing this exact same thing. The ToString() method for enums prints out the enum name as a character string.

Listing 2-7 is a little program showing enums in action.

Listing 2-7. Enums in Action

using namespace System;

enum class PrimeColors { Red, Blue, Yellow };

// Enum Type in Action void main()

{

PrimeColors color;

color = PrimeColors::Blue;

44

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

switch (color)

{

case PrimeColors::Red : Console::WriteLine("Red"); break;

case PrimeColors::Blue : Console::WriteLine("Blue"); break;

case PrimeColors::Yellow : Console::WriteLine("Yellow"); break;

}

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

}

Figure 2-8 shows the results of this program.

Figure 2-8. Results of Enums.exe

value struct and value class

The value struct and value class data types are basically C++/CLI’s equivalent to traditional C++’s class and struct data types but with an added bonus. Both are unmanaged (not garbage collected) constructs used to combine multiple data types and methods (or functions) into a single data type. Then when a new instance of the data type is created, it is allocated either to the stack or CRT heap.

The added bonus is that a copy of value struct or value class can be assigned to a variable on the managed heap. Notice I said “a copy,” because the original value struct and value class remains unmanaged. For those of you new to the C++ world, I cover the class and struct in detail in Chapter 21.

Unsafe Code A struct and class without the prefix value or ref are unsafe code, as they are referenced using pointers and not handles. Thus, a struct and a class are placed on the CRT heap, which you have to maintain yourself.

The only difference between a value struct and a value class is the default access of their members; value struct members are public, whereas value class members are private. I cover public and private access in Chapter 3.

The value struct and value class are C++/CLI’s way of providing programmers with a method of creating their own value types, thus allowing for expansion beyond the basic fundamental types.

All value structs and value classes are derived from the .NET Framework class library’s System::ValueType, which allows for the value struct’s and value class’s ability to be placed on

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

45

the stack. A value struct and value class can inherit from only interfaces. Trying to inherit from a value struct or value class results in a compile time error.

Listing 2-8 is a simple example of a value class called Coord3D. It is made up of three doubles, a constructor, and a Write() method. I cover constructors and overriding in Chapter 3. The main() function creates the two copies of Coord3D on the stack, with one using the default constructor, and the other using the one user-defined constructor. Notice that to assign a value class to another, you simply use the equal sign (=).

Listing 2-8. A value class in Action

using namespace System;

// Value class in Action value class Coord3D

{

public: double x; double y; double z;

Coord3D (double x, double y, double z)

{

this->x = x; this->y = y; this->z = z;

}

String^ Write()

{

return String::Format("{0},{1},{2}", x, y, z);

}

};

void main()

{

Coord3D coordA; Coord3D coordB(1,2,3);

coordA = coordB; // Assign is simply an =

coordA.x += 5.5; // Operations work just as usual coordA.y *= 2.7;

coordA.z /= 1.3;

Console::WriteLine(coordB.Write());

Console::WriteLine(coordA.x);

Console::WriteLine(coordA.y);

Console::WriteLine(coordA.z);

}

Figure 2-9 shows the results of this program.

46

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

Figure 2-9. Results of ValueClass.exe

Reference Types

User-defined reference types are data types a programmer develops that are accessed using handles, and where the actual data object is located on the managed heap. All reference types in C++/CLI are garbage collected.

C++/CLI provides four kinds of user-defined reference types: arrays, classes, interfaces, and delegates. All four of these types share one thing: to create an instance of them required the gcnew operator.

new vs. gcnew

The gcnew operator is new to all C++/CLI developers. It appears for the first time with .NET version 2.0 and replaces the well-known new operator, which was used in all prior versions of C++/CLI (Managed Extensions for C++). Its purpose is to create an instance of a reference type object on the managed heap and return a handle to this instance. This differs from the new operator, which creates an instance of a native class on the CRT heap and returns a pointer to this instance.

Unsafe Code The new operator is used to create an instance of an object on the CRT heap and create a pointer to this object, and thus you lose the benefits of the .NET version 2.0 CLR.

Why a new operator? The gcnew and new operators do different things. When you think of it, it sort of makes sense. Why confuse things by using the same operator? Why not improve readability of the code and make it obvious which types of object you are creating? Hence, the new operator gcnew and the new handle with the caret [^] symbol were created.

Arrays

Arrays, like all other data types in C++/CLI, are objects, unlike their traditional C++ counterparts, which are simply pointers into CRT heap memory. In fact, the only resemblance between a C++/CLI and a traditional C++ array is its single-dimension syntax when being referenced.

All C++/CLI arrays are garbage collected. Also, they can be made up of any data type that derives from System::Object. If you recall, that is every data type in the .NET Framework class library.

C++/CLI arrays have specific dimensions that, when violated, generate an exception. All arrays are derived from a System::Array object, which provides them with many helpful methods and properties, in particular, the Length property for single-dimension arrays and the GetLength() method for singleor multidimensional arrays. Both of these provide the dimensions of the array.

There are no stack base declarations of C++/CLI arrays using subscripts, as in traditional C++. All C++/CLI arrays are references and created on the managed heap.

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

47

Unsafe Code For the experienced C++ programmer it is still possible to create stack-based declarations of unsafe C++ arrays, just as you would in traditional C++, because that syntax is still available to you. But arrays declared in this fashion lose the benefits of .NET’s CLR given that they compile to unmanaged data.

Unlike what you have seen so far when declaring data types, arrays are declared with syntax very similar to C++/CLI templates or .NET 2.0 generic classes. Also, to declare an array requires the namespace stdcli::language:

using namespace stdcli::language;

For those coders who had to struggle with the declaration syntax of an array in the previous version of .NET (1.0 and prior), the new syntax should seem like a breath of fresh air, as I believe is a little easier to work with due to three aspects of the declaration:

The declaration points out that it is derived from that array class.

The declaration is more or less consistent with other reference type declarations.

The declaration of arrays made of value types is the same as one made up reference types.

To declare an array requires a handle to the keyword array followed by the data type enclosed in angle brackets:

array<datatype>^ arrayname;

To create an instance of the array, use the constructor initialization format. Also, because you are allocating the array to the managed heap, the gcnew operator is required. Therefore, to create an array of five ints and an array of seven Strings would require the following statements:

using namespace stdcli::language;

array<int>^ fiveInts = gcnew array<int>(5); array<String^>^ sevenStrings = gcnew array<String^>(7);

Unsafe Code It is possible to create arrays of unmanaged data types, as well, so long as the data type is of type pointer. Because the data type is a pointer and thus allocated to the CRT heap, you have to make sure that you handle the memory management yourself. In other words, you need to call delete on all allocated data.

class CLASS {};

array<CLASS*>^ pClass = gcnew array<CLASS*>(5); for (int i = 0; i < pClass->Length; i++)

pClass[i] = new CLASS();

...

for (int i = 0; i < pClass->Length; i++) delete pClass[i];

It is also possible to directly initialize an array at the time of declaration with the following syntax:

array<String^>^ name = gcnew array<String^> {"Stephen", "R", "G", "Fraser"};

Multidimensional arrays also have a template-like syntax. All you have to do is add a rank after the data type: