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

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

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

28

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

Even though this is an assembly, you run it as you would any other executable. If you run it from the command line, you should get something like Figure 2-1.

Figure 2-1. Executing Hello.exe from the command line

I don’t cover namespaces until later in this chapter, but for now you can think of them as a way of combining a bunch of code into a uniquely named group. When you want to access this group, you use the using statement and provide its unique name. Basically, the next line

using namespace System;

says you are going to use the stuff in this System namespace.

Every C++/CLI program must start with a main() function, and every program can have only one main() function. When the main() function finishes executing, so does the program. In the case of Hello.cpp, it also happens to be the only function. The first line of the main() function is this:

void main(void)

There are other variations of main(), including the WinMain() function used to start Windows programs. I cover those other variations later in this chapter. In the preceding variation of main(), you are receiving no parameters, which is signified by the (void) placed after the main, and you are also expecting the function to return no value so void is placed before the function call, as well.

Tip The void parameter is optional when no parameters are used by the method. Instead, you can just use a pair of empty brackets. Therefore, you could also declare the above main method as void main().

A function is a block of code referenced by name, in this case main. It starts with an open curly bracket ({) and ends with a closed curly bracket (}). Within a function is the set of statements that it will execute. The main() function of Hello.cpp contains only one statement:

Console::WriteLine("Hello Managed World");

If more than one statement were present, the statements would be executed sequentially from beginning to end, unless a statement specifically altered the flow, either by looping back or by conditionally bypassing some of the code. You will see how this is done later in this chapter.

In C++/CLI, displaying text strings, which are enclosed in quotation marks (""), to a console window is handled using the static WriteLine() method of the class Console. Don’t panic if that doesn't mean much to you—it will shortly. You will learn about classes and static methods in Chapter 3. You will also examine text strings and namespaces in Chapter 3. For now, all you need to know about displaying your own text is to replace “Hello Managed World” with whatever you want.

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

29

Statements

C++/CLI’s most basic element is the statement. A statement is a coding construct that performs a single C++/CLI action. You will learn about different types of statements as you progress through this book, but the main thing to remember about all statements is that they end with a semicolon (;). If you forget the semicolon, your compiler will throw up all over you. Here are some statements:

using namespace System; System::Console::WriteLine("Hello Managed World"); bool IsOpen;

y = GetYCoord();

Not much to look at, are they?

C++/CLI provides a construct for compound statements. To create a compound statement, you simply enclose several simple statements within curly brackets:

{

x = x + y; PrintAnswer(x);

}

These statements execute as a group and can be placed anywhere a simple statement can be placed. You will see them in the “Flow Control Constructs” and “Looping Constructs” sections later in this chapter.

Variables and C++/CLI Data Types

One of the key differences between traditional C++ and C++/CLI, believe it or not, is found at this low level of the language. If you have worked with C++, then it may come as a little surprise that the data types int, long, float, and so on, are no more. They have been replaced with .NET fundamental types. To simplify things for traditional C++ programmers, C++/CLI allows the use of the old data types, but they are, in fact, just aliases.

Alas, I’m getting ahead of myself. I’ll start at the beginning, and that is how to create or, more accurately, declare variables.

Declaring Variables

To use a variable in C++/CLI, you must first declare it. The minimum declaration of a variable consists of a data type and a variable name:

int counter; double yCoord;

Variable declarations can go almost anywhere in the code body of a C++/CLI program. One of the few criteria for declarations is that they have to occur before the variable is used. It was once required that all declarations occur as the first statements of a function, as a result of C++’s original C background. You will still see this in practice today, because some programmers feel it makes the code cleaner to read. Personally, I prefer to place the variable closer to where it is first used—that way, I don’t have to scroll to the top of every function to see how I declared something. How you code it is up to you. Following the standards of your company is always a good rule of thumb, or if you are coding on your own, stay consistent. You will find that it will save you time down the line.

30

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

There is an assortment of more complex declarations. For example, you can string together several comma-delimited variable names at the same time:

int x, y, z;

There are two special data types called a handle and a pointer (which I’ll explain in more detail later). A handle requires a carat [^] in front of the variable name or after the data type, and a pointer requires an asterisk [*]:

String^ handlename;

String ^handlename;

String* pointername;

String *pointername;

Unsafe Code Pointers are classified as unsafe code because they cause data to be placed in the CRT heap and not the managed heap. Therefore, you need to handle all memory management yourself. The primary reason pointers are unsafe is that they allow a programmer to specify a memory location to access or reference; thus, with knowledge of the operating system, a programmer could potentially allow the executing of code unprotected by .NET.

You might think of these as saying “String handle called handlename” or “handlename handle to a String” and “String pointer called pointername” or “pointername pointer to a String.” They are equivalent. There is a complication with string handles, as shown here:

int^ isaHandle, isNOTaHandle; int* isaPointer, isNOTaPointer;

The preceding line actually declares one handle and one pointer to an int and two variables of type int. This is probably not what you are expecting. If you want two handles and two pointers to an int, you need to declare it like this:

int ^aHandle, ^anotherHandle; int *aPointer, *anotherPointer;

You have two possible ways to initialize the variable within the declaration statement. The first is by using a standard assignment:

int counter = 0; double yCoord = 300.5;

The second is by using what is known as functional notation, as it resembles the calling of a function passing the initialization value as a parameter. In C++/CLI, you should probably call this constructor initialization, as you are actually calling the data type’s constructor to create these variables:

int counter(0); double yCoord(300.5);

Again, use caution when initializing a variable within the declaration statement using standard assignment. This code may not do what you expect:

int x, y, z = 200;

Only z is initialized to 200; all the other variables take on the default value of the data type. Enter the following to code this so that all variables are initialized to 200:

int x = 200, y = 200, z = 200;

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

31

or

int x = y = z = 200;

It is always a good thing to initialize your variables before you use them. If you don’t initialize a variable, its contents can be almost anything when it is used. To help remind you of this, the compiler displays a warning about uninitialized variables while it is compiling.

Variable Name Restrictions

For those of you with a C++ background, there are no big changes here. Variable names consist of upperand lowercase letters, digits from 0 to 9, and the underscore character (_). The variable name must start with a letter or an underscore character. Also, variable names cannot be the same as C++/CLI reserved keywords, including all variable names starting with two underscores, which C++/CLI has also reserved. Table 2-1 contains a list of more commonly used C++/CLI reserved keywords. For a complete list of all reserved words, look in the documentation provided in Visual Studio 2005.

Table 2-1. Common C++/CLI Reserved Keywords

Keywords

asm

auto

bool

break

case

catch

char

class

const

const_cast

continue

default

delete

do

double

dynamic_cast

else

enum

explicit

export

extern

false

float

for

friend

gcnew

goto

if

inline

int

long

mutable

namespace

new

nullptr

operator

pin_ptr

private

protected

public

register

reinterpret_cast

restrict

return

safe_cast

short

signed

sizeof

static

static_cast

struct

switch

template

this

throw

true

try

typedef

typeid

typename

typeid

_typeof

union

unsigned

using

virtual

void

volatile

wchar_t

while

 

 

 

 

 

In addition to the single-word keywords in Table 2-1, in C++/CLI double-word keywords have been added. Any white space, including comments and new lines (but excluding XML documentation comments and new lines in macros), is permitted between the double-word keywords. Table 2-2 contains a list of all C++/CLI reserved double-word keywords.

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

Table 2-2. C++/CLI Reserved Double-Word Keywords

Double-Word Keywords

enum class

enum struct

for each

interface class

interface struct

ref class

ref struct

value class

value struct

 

 

 

 

 

 

 

To add one more wrinkle to variable name mess, C++/CLI also has added some context-sensitive keywords words, or what Microsoft calls identifiers. These words can be used as variable names unless they are placed in a specific location in the code. I will describe each of these identifiers later in this chapter or in subsequent chapters where appropriate. Table 2-3 contains a list of all the C++/CLI identifiers.

Table 2-3. C++/CLI Identifiers

Identifiers

Abstract

delegate

event

finally

Generic

in

initonly

literal

Override

property

sealed

where

 

 

 

 

Variables should probably be self-descriptive. However, there is nothing stopping you from writing a program that uses variable names starting with a0000 and continuing through z9999. If you do this, though, don’t ask me to debug it for you.

There are also people who think that you should use Hungarian notation for variable names. This notation allows other programmers to read your code and know the data type by the prefix attached to its name. I find this notation cumbersome and don’t use it myself unless, of course, company standards dictate its use.

Note You can find out the data type of a variable within Visual Studio 2005 by just placing your cursor over the variable name.

Predefined Data Types

All data types, even the simplest ones, are truly objects in C++/CLI. This differs from traditional C++, where primitive types such as int, float, and double were strictly stored values of data types themselves.

As a C++/CLI programmer, you have the luxury of programming simple data types just as you would in traditional C++, knowing that you can convert them to objects if needed.

Predefined data types fall into two different types: fundamental types and reference types. Fundamental types are the data types that default to just storing their values for efficiency on the stack but can be boxed to become full objects. Reference types, on the other hand, are always objects and are stored on the managed heap.

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

33

Fundamental Types

All the standard C++ data types are available to the C++/CLI programmer. Or, at least, so it appears. In reality, the standard data types are just an alias for the .NET Framework’s fundamental types. With .NET 2.0, there is now no difference between using the standard C++ data types and .NET Framework’s fundamental types. It’s a matter of taste (or company standards) which ones you choose. My feeling, given that this is C++/CLI, is that I’m going to use C++ data types. Plus, the Visual Studio 2005 editor defaults to color-coding the data type keywords, which make things easier.

There are five distinct groups of fundamental value types:

1.Integer

2.Floating point

3.Decimal

4.Boolean

5.Character

Programmers with a C++ background should readily recognize four of these groups. Decimal, most probably, is new to all. Let’s go over all of them so that there are no surprises.

Integer Types

Eight different integer types are provided to C++/CLI programmers. These can all be broken down into unsigned and signed numbers. (In other words, can negative numbers be represented or just positive numbers?) Table 2-4 shows the integer types.

Table 2-4. Integer Fundamental Types

C++/CLI Alias

Class Library

Description

Range

unsigned char

System::Byte

8-bit unsigned integer

0 to 255

char

System::SByte

8-bit signed integer

–128 to 127

short

System::Int16

16-bit signed integer

–32,768 to 32,767

unsigned short

System::UInt16

16-bit unsigned integer

0 to 65,535

int or long

System::Int32

32-bit signed integer

–2,147,483,648 to 2,147,483,647

unsigned int or long

System::UInt32

32-bit unsigned integer

0 to 4,294,967,295

long long or __int64

System::Int64

64-bit signed integer

–9,223,372,036,854,775,808 to

 

 

 

9,223,372,036,854,775,807

unsigned long long

System::UInt64

64-bit unsigned integer

0 to 18,446,744,073,709,551,615

or __int64

 

 

 

 

 

 

 

Byte and SByte are the smallest of the integer types, at 1 byte each, hence their names. Their C++/CLI aliases are unsigned char and char, respectively. A Byte can range from 0 to 255, and an SByte can range from –128 to 127, inclusive. In traditional C++, char usually represents ASCII characters.

34

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

Caution The C++/CLI alias char is not the same as the .NET Framework class library System::Char. A char is an 8-bit unsigned integer that frequently represents an ASCII character, whereas a System::Char is a 16-bit Unicode character.

The remainder of the integer types has fairly self-descriptive .NET Framework class library names, with their type and size merged into their name. Int16 are 16-bit integers, UInt16 are unsigned 16-bit integers, and so on. Personally, I think these names make more sense than short, int, and long. Plus, long and int are the same size (4 bytes), so you have to throw in __Int64 or long long.

Note Given that short and int are the norm to a C++ programmer, I’ll use them but, because there really isn’t a 64-bit integer standard keyword, I use the .NET Framework’s System::Int64 or the more convenient Int64.

There is nothing complex about declaring integer type variables. Whenever you declare an integer type variable in C++/CLI, it is immediately initialized to the value of zero. This differs from traditional C++ compilers, where the initialization is optional and up to the compiler. For traditional C++, it is possible that the value of a variable remains uninitialized and, thus, contains just about any numeric value.

To initialize integer types, you simply declare a variable and assign it a character: octal, decimal, or hexadecimal literal. I examine literals later in this chapter.

Listing 2-1 is a simple piece of code showing integer types in action.

Listing 2-1. Integer Types in Action

using namespace System;

// Integer Fundamental Types in Action

 

 

void main()

 

 

 

 

{

 

 

 

 

char

v = 'F';

//

Intialize using charater literal

short

w(123);

//

Intializing using Functional Notation

int

x = 456789;

//

Decimal literal assigned

long

y = 987654321l;

//

long integer

literal assigned

Int64

z = 0xFEDCBA9876543210; //

Hex literal assigned

Console::WriteLine( v );

 

// Write

out a char

Console::WriteLine( w );

 

// Write

out a short

Console::WriteLine( x );

 

// Write

out a int

Console::WriteLine( y );

 

// Write

out a long

Console::WriteLine( z );

 

// Write

out a Int64

Console::WriteLine( z.ToString("x") ); // Write out a Int64 in Hex

}

Figure 2-2 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

35

Figure 2-2. Results of IntegerTypes.exe

For those of you from traditional C++ backgrounds, the ToString() appended to the integer variables in the Console::WriteLine() method might be a little confusing. Remember, in C++/CLI, integer types are objects and have several methods attached to them, and ToString() happens to be one of them.

Floating-Point Types

C++/CLI provides only two different floating-point types. Table 2-5 describes the details of each.

Table 2-5. Floating-Point Fundamental Types

C++/CLI

Class Library

Description

Significant

Range

Alias

 

 

Digits

 

 

 

 

 

 

float

System::Single

32-bit single-precision

7

significant digits ±1.5x10-45

 

 

floating point

 

to ±3.4x1038

double

System::Double

64-bit double-precision

15

significant digits ±5.0x10-324

 

 

floating point

 

to ±1.7x10308

Note C++/CLI also supports a long double, but on the Microsoft platform long double and double are the same.

The .NET Framework class library System::Single has the smaller range of numbers it can represent of the two floating-point types available to C++/CLI. Its alias for C++ programmers is the better-known float type. A float can represent numbers from ±1.5x10-45 to ±3.4x1038, but only seven of the digits are significant.

The System::Double class library has the larger range of the two. Its alias is double. A double can represent numbers from ±5.0x10-324 to ±1.7x10308, but only 15 of the digits are significant.

Listing 2-2 is a simple piece of code showing floating-point types in action.

36

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

Listing 2-2. Floating-Point Types in Action

using namespace System;

// Floating-point Fundamental Types in Action void main()

{

float w = 123.456f; // standard decimal notation float x = 7890e3f; // exponent notation

double y = 34525425432525764765.76476476547654; // too big will truncate double z = 123456789012345e-300; // exponent will be reset

Console::WriteLine( w ); // Write out Single Console::WriteLine( x ); // Write out Single with more zeros Console::WriteLine( y ); // Write out Double truncated Console::WriteLine( z ); // Write out Double shift back decimal

}

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

Figure 2-3. Results of FloatingPoint.exe

The .NET Framework class library double is the default value used by most functions and methods that deal with floating-point numbers.

Decimal Type

C++/CLI supports only one decimal type. This type has no traditional C++ equivalent and thus has no alias. Table 2-6 describes the decimal type.

Table 2-6. Decimal Fundamental Type

Class Library

Description

Significant

Range

 

 

Digits

 

 

 

 

 

System::Decimal

128-bit high-precision

28

±7.9x10-28 to ±7.9x1028

 

decimal notation

 

 

 

 

 

 

This fundamental type was designed specifically for calculations requiring a lot of significant digits, as it provides 28 significant digits. Within those 28 digits, you can put a decimal. In other words, you can place a very big number in a System::Decimal that will have a small fractional area, or you can make a very small number with a very big fractional part.

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

37

System::Decimals are not a native C++ data type and, as such, they need a little magic to get them initialized if the number of significant digits you want to capture is larger than 15. The significance of 15 is that it is the number of significant digits provided by a double, the closest data type available to initialize a Decimal.

Here are three ways to load a number with more than 15 significant digits (there are other ways, I’m sure):

1.The first method is to load the digits into a String and convert the String to Decimal.

Decimal w = System::Convert::ToDecimal("123456789012345678901.2345678");

2.The second method is to use one of the Decimal constructors. Most of the constructors are pretty self explanatory. Basically convert a numeric type to a Decimal. Two constructors are more complex. The first takes an array of integers to represent the binary format of the Decimal. The second is this monstrosity:

public: Decimal(

 

 

 

 

int lo,

// The low 32

bits of

a 96-bit integer.

int mid,

// The middle

32 bits

of

a 96-bit integer.

int hi,

// The high 32 bits of a

96-bit integer.

bool isNegative,

// false is positive.

 

 

unsigned char scale

// A power of

10 ranging

from 0 to 28.

);

 

 

 

 

3.The third method is to add two doubles together using the combined significant digits of both to make up the Decimal.

All three of these methods are shown in Listing 2-3. Also, for grins and giggles, I decided to use the Decimal method GetBits() to break the Decimal into its parts and then use the constructor to put it back together again. Don’t fret if you don’t understand C++/CLI arrays, as that portion of the code is not essential to the understanding of the Decimal type. I cover arrays in detail later in the chapter.

Listing 2-3. Decimal Types in Action

using namespace System;

// Decimal Fundamental Type in Action void main()

{

Decimal w = System::Convert::ToDecimal("123456789012345678901.2345678"); Console::WriteLine( w );

Decimal x = (Decimal)0.1234567890123456789012345678; // will get truncated Decimal y = (Decimal)0.0000000000000000789012345678; // works fine

Console::WriteLine( x );

Console::WriteLine( y );

// Decimal constructor

Decimal z(0xeb1f0ad2, 0xab54a98c, 0, false, 0); // = 12345678901234567890 Console::WriteLine( z );

// Create a 28 significant digit number

Decimal a = (Decimal)123456789012345000000.00000000; Decimal b = (Decimal)678901.23456780;

Decimal c = -(a + b);