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

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

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

More about Program Structure

while((*index < str->Length) && Char::IsDigit(str, *index))

{

value = 10.0*value + Char::GetNumericValue(str[(*index)]); ++(*index);

}

 

 

// Not a digit when we get to here

 

 

if((*index == str->Length) || str[*index] != ‘.’)

// so check for decimal point

return value;

 

// and if not, return value

double factor = 1.0;

// Factor for decimal places

++(*index);

// Move to digit

// Loop as long as we have digits

while((*index < str->Length) && Char::IsDigit(str, *index))

{

 

factor *= 0.1;

// Decrease factor by factor of 10

value = value + Char::GetNumericValue(str[*index])*factor; // Add decimal place ++(*index);

}

 

return value;

// On loop exit we are done

}

As in the native C++ version, the extract() function is used to extract a parenthesized expression and the substring that results is passed to the expr() function to be evaluated. If there’s no parenthesized expression(indicated by the absence of an opening parenthesis, the input is scanned for a number, which is a sequence of zero or more digits followed by an optional decimal point plus fractional digits. The IsDigit() function in the Char class returns true if a character is a digit and false otherwise. The character here is in the string passed as the first argument to the function at the index position specified by the second argument. There’s another version if the IsDigit() function that accepts a single argument of type wchar_t so you could use this with the argument str[*index]. The GetNumericValue() function in the Char class returns the value of a Unicode digit character that you pass as the argument as a value of type double. There’s another version of this function to which you can pass a string handle and an index position to specify the character.

Extracting a Parenthesized Substring

You can implement the extract() function that returns a parenthesized substring from the input like this:

// Function to extract a substring between parentheses String^ extract(String^ str, int^ index)

{

// Temporary space for substring

array<wchar_t>^ buffer = gcnew array<wchar_t>(str->Length);

String^ substr;

// Substring to return

int numL = 0;

// Count of left parentheses found

int bufindex = *index;

// Save starting value for index

while(*index < str->Length)

{

buffer[*index - bufindex] = str[*index]; switch(str[*index])

319

Chapter 6

{

case ‘)’: if(numL == 0)

{

array<wchar_t>^ substrChars = gcnew array<wchar_t>(*index - bufindex); str->CopyTo(bufindex, substrChars, 0, substrChars->Length);

substr = gcnew String(substrChars); ++(*index);

return substr;

// Return substring in new memory

}

 

else

 

numL--;

// Reduce count of ‘(‘ to be matched

break;

 

case ‘(‘:

 

numL++;

// Increase count of ‘(‘ to be

 

// matched

break;

 

}

 

++(*index);

 

}

Console::WriteLine(L”Ran off the end of the expression, must be bad input.”); exit(1);

return substr;

}

Again, the strategy is the same as the native C++ version, but the differences are in the detail. To find the complementary right parenthesis the function keeps track of how many new left parentheses are found using the variable numL. The substring is extracted when a right parenthesis is found and the left parenthesis count in numL is zero. The substring is copied into the substrChars array using the CopyTo() function for the String object, str. The function copies characters beginning at the string index position specified by the first argument into the array specified by the second argument; the third argument defined the starting element in the array to receive characters and the fourth argument is the number of characters to be copied. You create the string that is returned as the result of the extraction by using the String class constructor that creates an object from all the elements in the array, substrChars, that is passed as the argument.

If you assemble all the functions in a CLR console project you’ll have a C++/CLI implementation of the calculator running with the CLR. The output should be much the same as the native C++ version.

Summar y

You now have a reasonably comprehensive knowledge of writing and using functions. You’ve used a pointer to a function in a practical context for handling out-of-memory conditions in the free store, and you have used overloading to implement a set of functions providing the same operation with different types of parameters. You’ll see more about overloading functions in the following chapters.

320

More about Program Structure

The important bits that you learned in this chapter include:

A pointer to a function stores the address of a function, plus information about the number and types of parameters and return type for a function.

You can use a pointer to a function to store the address of any function with the appropriate return type, and number and types of parameters.

You can use a pointer to a function to call the function at the address it contains. You can also pass a pointer to a function as a function argument.

An exception is a way of signaling an error in a program so that the error handling code can be separated from the code for normal operations.

You throw an exception with a statement that uses the keyword throw.

Code that may throw exceptions should be placed in a try block, and the code to handle a particular type of exception is placed in a catch block immediately following the try block. There can be several catch blocks following a try block, each catching a different type of exception.

Overloaded functions are functions with the same name, but with different parameter lists.

When you call an overloaded function, the function to be called is selected by the compiler based on the number and types of the arguments that you specify.

A function template is a recipe for generating overloaded functions automatically.

A function template has one or more arguments that are type variables. An instance of the function template — that is, a function definition — is created by the compiler for each function call that corresponds to a unique set of type arguments for the template.

You can force the compiler to create a particular instance from a function template by specifying the function you want in a prototype declaration.

You also got some experience of using several functions in a program by working through the calculator example. But remember that all the uses of functions up to now have been in the context of a traditional procedural approach to programming. When we come to look at object-oriented programming, you will still use functions extensively, but with a very different approach to program structure and to the design of a solution to a problem.

Exercises

You can download the source code for the examples in the book and the solutions to the following exercises from http://www.wrox.com.

1.Consider the following function:

int ascVal(size_t i, const char* p)

{

// print the ASCII value of the char if (!p || i > strlen(p))

return -1; else

return p[i];

}

321

Chapter 6

Write a program that will call this function through a pointer and verify that it works. You’ll need an #include directive for the <cstring> header in your program to use the strlen() function.

2.Write a family of overloaded functions called equal(), which take two arguments of the same type, returning 1 if the arguments are equal, and 0 otherwise. Provide versions having char, int, double, and char* arguments. (Use the strcmp() function from the runtime library to test for equality of strings. If you don’t know how to use strcmp(), search for it in the online help. You’ll need an #include directive for the <cstring> header file in your program.) Write test code to verify that the correct versions are called.

3.At present, when the calculator hits an invalid input character, it prints an error message, but doesn’t show you where the error was in the line. Write an error routine that prints out the input string, putting a caret (^) below the offending character, like this:

12 + 4,2*3

^

4.Add an exponentiation operator, ^, to the calculator, fitting it in alongside * and /. What are the limitations of implementing it in this way, and how can you overcome them?

5.(Advanced) Extend the calculator so it can handle trig and other math functions, allowing you to input expressions such as:

2 * sin(0.6)

The math library functions all work in radians; provide versions of the trigonometric functions so that the user can use degrees, for example:

2 * sind(30)

322

7

Defining Your Own

Data Types

This chapter is about creating your own data types to suit your particular problem. It’s also about creating objects, the building blocks of object-oriented programming. An object can seem a bit mysterious to the uninitiated but, as you will see in this chapter, an object can be just an instance of one of your own data types.

In this chapter, you will learn about:

Structures and how they are used

Classes and how they are used

The basic components of a class and how you define class types

Creating and using objects of a class

Controlling access to members of a class

Constructors and how to create them

The default constructor

References in the context of classes

The copy constructor and how it is implemented

How C++/CLI classes differ from native C++ classes

Properties in a C++/CLI class and how you define and use them

Literal fields and how you define and use them

initonly fields and how you define and use them

What a static constructor is

Chapter 7

The struct in C++

A structure is a user-defined type that you define using the keyword struct so it is often referred as a struct. The struct originated back in the C language, and C++ incorporates and expands on the C struct. A struct in C++ is functionally replaceable by a class insofar as anything you can do with a struct you can also achieve by using a class; however, because Windows was written in C before C++ became widely used, the struct appears pervasively in Windows programming. It is also widely used today, so you really need to know something about structs. We’ll first take a look at (C-style) structs in this chapter before exploring the more extensive capabilities offered by classes.

What Is a struct?

Almost all the variables that you have seen up to now have been able to store a single type of entity — a number of some kind, a character, or an array of elements of the same type. The real world is a bit more complicated than that, and just about any physical object you can think of needs several items of data to describe it even minimally. Think about the information that might be needed to describe something as simple as a book. You might consider title, author, publisher, date of publication, number of pages, price, topic or classification, and ISBN number just for starters, and you can probably come up with a few more without too much difficulty. You could specify separate variables to contain each of the parameters that you need to describe a book, but ideally you would want to have a single data type, BOOK say, which embodied all of these parameters. I’m sure you won’t be surprised to hear that this is exactly what a struct can do for you.

Defining a struct

Let’s stick with the notion of a book, and suppose that you just want to include the title, author, publisher, and year of publication within your definition of a book. You could declare a structure to accommodate this as follows:

struct BOOK

{

char Title[80]; char Author[80]; char Publisher[80]; int Year;

};

This doesn’t define any variables, but it actually creates a new type for variables and the name of the type is BOOK. The keyword struct defines BOOK as such, and the elements making up an object of this type are defined within the braces. Note that each line defining an element in the struct is terminated by a semicolon, and that a semicolon also appears after the closing brace. The elements of a struct can be of any type, except the same type as the struct being defined. You couldn’t have an element of type BOOK included in the structure definition for BOOK, for example. You may think this to be a limitation, but note that you could include a pointer to a variable of type BOOK, as you’ll see a little later on.

The elements Title, Author, Publisher, and Year enclosed between the braces in the definition above may also be referred to as members or fields of the BOOK structure. Each object of type BOOK contains the members Title, Author, Publisher, and Year. You can now create variables of type BOOK in exactly the same way that you create variables of any other type:

324

Defining Your Own Data Types

BOOK Novel;

// Declare variable Novel of type BOOK

This declares a variable with the name Novel that you can now use to store information about a book. All you need now is to understand how you get data into the various members that make up a variable of type BOOK.

Initializing a struct

The first way to get data into the members of a struct is to define initial values in the declaration. Suppose you wanted to initialize the variable Novel to contain the data for one of your favorite books, Paneless Programming, published in 1981 by the Gutter Press. This is a story of a guy performing heroic code development while living in an igloo, and as you probably know, inspired the famous Hollywood box office success, Gone with the Window. It was written by I.C. Fingers, who is also the author of that seminal three-volume work, The Connoisseur’s Guide to the Paper Clip. With this wealth of information you can write the declaration for the variable Novel as:

BOOK Novel =

 

{

 

“Paneless Programming”,

// Initial value for Title

“I.C. Fingers”,

// Initial value for Author

“Gutter Press”,

// Initial value for Publisher

1981

// Initial value for Year

};

 

 

 

The initializing values appear between braces, separated by commas, in much the same way that you defined initial values for members of an array. As with arrays, the sequence of initial values obviously needs to be the same as the sequence of the members of the struct in its definition. Each member of the structure Novel has the corresponding value assigned to it, as indicated in the comments.

Accessing the Members of a struct

To access individual members of a struct, you can use the member selection operator, which is a period; this is sometimes referred to as the member access operator. To refer to a particular member, you write the struct variable name, followed by a period, followed by the name of the member that you want to access. To change the Year member of the Novel structure, you could write:

Novel.Year = 1988;

This would set the value of the Year member to 1988. You can use a member of a structure in exactly the same way as any other variable of the same type as the member. To increment the member Year by two, for example, you can write:

Novel.Year += 2;

This increments the value of the Year member of the struct, just like any other variable.

325

Chapter 7

Try It Out

Using structs

Now use another console application example to exercise a little further how referencing the members of a struct works. Suppose you want to write a program to deal with some of the things you might find in a yard, such as those illustrated in the professionally landscaped yard in Figure 7-1.

House

Position 0,0

 

 

 

 

10

 

70

 

 

30

 

 

40

 

 

 

80

Hut

 

 

 

 

 

 

25

 

 

 

 

90

 

 

 

 

30

 

 

 

 

110

 

 

Pool

 

 

 

 

 

 

 

70

 

 

10

 

 

 

 

 

Hut

 

 

 

 

25

 

 

 

Position 100,120

Figure 7-1

I have arbitrarily assigned the coordinates 0,0 to the top-left corner of the yard. The bottom-right corner has the coordinates 100,120. Thus, the first coordinate value is a measure of the horizontal position relative to the top-left corner, with values increasing from left to right, and the second coordinate is a measure of the vertical position from the same reference point, with values increasing from top to bottom.

326

Defining Your Own Data Types

Figure 7-1 also shows the position of the pool and that of the two huts relative to the top-left corner of the yard. Because the yard, huts, and pool are all rectangular, you could define a struct type to represent any of these objects:

struct RECTANGLE

 

{

 

int Left;

// Top left point

int Top;

// coordinate pair

int Right;

// Bottom right point

int Bottom;

// coordinate pair

};

 

 

 

The first two members of the RECTANGLE structure type correspond to the coordinates of the top-left point of a rectangle, and the next two to the coordinates of the bottom-right point. You can use this in an elementary example dealing with the objects in the yard as follows:

//Ex7_01.cpp

//Exercising structures in the yard #include <iostream>

using std::cout; using std::endl;

//Definition of a struct to represent rectangles struct RECTANGLE

{

int Left;

// Top left point

int Top;

// coordinate pair

int Right;

//

Bottom right point

int Bottom;

//

coordinate pair

};

//Prototype of function to calculate the area of a rectangle long Area(RECTANGLE& aRect);

//Prototype of a function to move a rectangle

void MoveRect(RECTANGLE& aRect, int x, int y);

int main(void)

{

RECTANGLE Yard = { 0, 0, 100, 120 }; RECTANGLE Pool = { 30, 40, 70, 80 }; RECTANGLE Hut1, Hut2;

Hut1.Left = 70;

Hut1.Top = 10;

Hut1.Right = Hut1.Left + 25;

Hut1.Bottom = 30;

Hut2 = Hut1;

//

Define Hut2 the same as Hut1

MoveRect(Hut2, 10, 90);

//

Now move it to the right position

cout << endl

 

 

327

Chapter 7

<<“Coordinates of Hut2 are “

<<Hut2.Left << “,” << Hut2.Top << “ and “

<<Hut2.Right << “,” << Hut2.Bottom;

cout << endl

<<“The area of the yard is “

<<Area(Yard);

cout << endl

<<“The area of the pool is “

<<Area(Pool)

<<endl;

return 0;

}

//Function to calculate the area of a rectangle long Area(RECTANGLE& aRect)

{

return (aRect.Right - aRect.Left)*(aRect.Bottom - aRect.Top);

}

//Function to Move a Rectangle

void MoveRect(RECTANGLE& aRect, int x, int y)

{

int length = aRect.Right - aRect.Left; int width = aRect.Bottom - aRect.Top;

aRect.Left = x; aRect.Top = y; aRect.Right = x + length; aRect.Bottom = y + width;

return;

}

The output from this example is:

Coordinates of Hut2 are 10,90 and 35,110 The area of the yard is 12000

The area of the pool is 1600

//Get length of rectangle

//Get width of rectangle

//Set top left point

//to new position

//Get bottom right point as

//increment from new position

How It Works

Note that the struct definition appears at global scope in this example. You’ll be able to see it in the Class View tab for the project. Putting the definition of the struct at global scope allows you to declare a variable of type RECTANGLE anywhere in the .cpp file. In a program with a more significant amount of code, such definitions would normally be stored in a .h file and then added to each .cpp file where necessary by using a #include directive.

You have defined two functions to process RECTANGLE objects. The function Area() calculates the area of the RECTANGLE object that you pass as a reference argument as the product of the length and the width, where the length is the difference between the horizontal positions of the defining points, and the

328