• No results found

Wouldn't it be nice to have a tool that would show you, step by step, how your program is executing? Luckily, such tools exist and are called debuggers. There are many implementations of debuggers, but they all offer the same basic

funtionality. There is a debugger built into Microsoft Developer's Studio a.k.a. Visual C++, so I'll use it as an example.

Once you have your program built, you step into it. In VC++ you do it by pressing F11. What happens next is that your program starts executing, but it is stopped immediately after entering main. You have just stepped into main. You can

continue stepping through your program, line by line, by pressing the step over button. When you are positioned at a method call, stepping over will execute the method and position you right after the call. If, on the other hand, you'd like to follow the execution of this method, you should press the step into button. The debugger will jump into the method that's being called and show it to you.

All through the debugging session you'll have additional panes showing the state of the program (if you don't have these panes, go to the View menu and select Debug Windows). One pane shows the values of all the local variables in the current

scope. Another pane shows the call stack, or who called whom, called whom... The top of the call stack shows the place where you are currently stopped.

The great thing about the call stack is that you can double-click on any of the callers and the debugger will show you where in the calling method the call was made. At the same time, the local-variable window will be updated to show the values of local variables of this particular caller.

Look at the screen shot below. I first stepped into main by pressing F11. Then I did

a few step-overs to get to the line in main that calls num.AddInput(). (Incidentally,

when stepping over input statements, you have to go to your application window and actually type in the numbers before you can continue stepping.) Instead of stepping over this call, I chose to step into AddInput. I continued stepping over

until I got to the line that calls aNum.GetValue() at which point I stepped into GetValue(). You can see where I stopped inside GetValue()--there's an arrow

pointing at the appropriate line.

Then I double clicked on InputNum::AddInput() in the call stack in order to see

the values of local variables at the time GetValue() was called. You can see the

second (triangular) arrow pointing at the very point where the call was made. The variable window was updated appropriately, showing the values of aNum and this.

"This" is the current object--the object in whose context the call was made. Notice that, since the variables here are objects of the class InputNum, you can expand

them to see the values of their member variables. In this case, you see the values of _num for both, aNum and this. They are, respectively, 11 and 5. (Your debugger

Types

Built in types, typedefs.

Here is the list of built-in types with a short description of each of them.

● bool-Boolean type. Can take predefined values true and false.

● int -generic signed integral type. Its size is machine dependent, it is considered the most efficient

implementation of an integral type for a given architecture. At least 2 bytes long.

● double - generic signed floating point type.

● char -generic 8-bit character type. Used in ASCII strings. Depending on the compiler, the default signedness of char may be signed or unsigned.

● short and long are a little better defined signed integral types. Usually short is 2-byte long and long is 4-byte long, but on a 64-bit machine it may be 8-bytes long (same as int). If you are writing your program to be run on only one type of processor architectures, you may make assumptions about the sizes of shorts and longs (and chars, of course). Sizes become crucial when you start storing data on removable disks or move them across the network.

Short is often used as a cheap int, space-wise; for example, in large arrays. Long is often used when short or int may overflow. Remember, in two bytes you can store numbers between -32767 and 32767. Your

arithmetic can easily overflow such storage, and the values will wrap around.

● float is a cheaper version of double (again, space-wise), but it offers less precision. It makes sense to use it in large arrays, where size really matters.

● Additional floating point type of even larger size and precision is called long double.

● All integral types may be prefixed by the keyword unsigned or signed (default for all types--except, as

special interest is unsigned char corresponding to a machine byte, and unsigned long often used for low- level bit-wise operations.

When you are tired of typing unsigned long over and over again, C++ lets you define a shortcut called a typedef. It is a way of giving a different name to any type--built-in or user defined. For instance, an unsigned long is often typedef'd to (given the name of) ULONG:

typedef unsigned long ULONG;

After you define such a typedef, you can use ULONG anywhere you would use any other type, for example,

ULONG ValueHolder::SwapValue (ULONG newValue)

{

ULONG oldValue = _ulNumber; _ulNumber = newValue;

return oldValue; }

Similarly one often uses typedefs for BYTE typedef unsigned char BYTE;

Like every feature of C++, typedefs can be abused. Just because you can redefine all types using your own names, doesn't mean you should. What do you make of types like INT or LONG when you see them in somebody else's code? Are these typedef'd names for int or long? If so, why not use directly int or long? And if not, then what the heck is going on? I can't think of any rational reason to typedef such basic data types, other than following some kind of coding standard from hell.

Besides these basic types there is an infinite variety of

derived types and user defined types. We've had a glimpse of derived types in an array of chars. And we've seen user- defined types called classes.

Summary

Objects of any type (built-in, derived, or user defined) can be defined within the global scope or local scopes. Bodies of functions (such as main()) and member functions form local scopes. Within a given local scope one can create sub- scopes which can have sub-sub-scopes, and so on. A sub- scope is usually delimited by braces, but in certain

constructs, such as the for loop, or the conditional if/else (we'll see examples later) a single-statement body may be braceless and yet it will form a sub-scope.

A class is a user-defined type. Class behavior is specified by the interface--the set of member functions (including constructors and a destructor) together with the

description of their behavior. A member function takes zero or more arguments and returns a value (which can be

empty--the void return type). The implementation of the class is specified by the bodies of member functions and the types of data members. The implementation fulfills the contract declared by the interface.

Objects can be combined by embedding and inheritance-- semantically corresponding to the has-a and is-a

relationships. Embedding and inheritance may be combined in various way leading to objects containing objects that inherit from other objects, that inherit from yet other objects, that contain..., etc.

Related documents