- •Table of Contents
- •Introduction
- •What Is C++?
- •Conventions Used in This Book
- •How This Book Is Organized
- •Part I: Introduction to C++ Programming
- •Part III: Introduction to Classes
- •Part IV: Inheritance
- •Part V: Optional Features
- •Part VI: The Part of Tens
- •Icons Used in This Book
- •Where to Go from Here
- •Grasping C++ Concepts
- •How do I program?
- •Installing Dev-C++
- •Setting the options
- •Creating Your First C++ Program
- •Entering the C++ code
- •Building your program
- •Executing Your Program
- •Dev-C++ is not Windows
- •Dev-C++ help
- •Reviewing the Annotated Program
- •Examining the framework for all C++ programs
- •Clarifying source code with comments
- •Basing programs on C++ statements
- •Writing declarations
- •Generating output
- •Calculating Expressions
- •Storing the results of expression
- •Declaring Variables
- •Declaring Different Types of Variables
- •Reviewing the limitations of integers in C++
- •Solving the truncation problem
- •Looking at the limits of floating-point numbers
- •Declaring Variable Types
- •Types of constants
- •Special characters
- •Are These Calculations Really Logical?
- •Mixed Mode Expressions
- •Performing Simple Binary Arithmetic
- •Decomposing Expressions
- •Determining the Order of Operations
- •Performing Unary Operations
- •Using Assignment Operators
- •Why Mess with Logical Operations?
- •Using the Simple Logical Operators
- •Storing logical values
- •Using logical int variables
- •Be careful performing logical operations on floating-point variables
- •Expressing Binary Numbers
- •The decimal number system
- •Other number systems
- •The binary number system
- •Performing Bitwise Logical Operations
- •The single bit operators
- •Using the bitwise operators
- •A simple test
- •Do something logical with logical calculations
- •Controlling Program Flow with the Branch Commands
- •Executing Loops in a Program
- •Looping while a condition is true
- •Using the for loop
- •Avoiding the dreaded infinite loop
- •Applying special loop controls
- •Nesting Control Commands
- •Switching to a Different Subject?
- •Writing and Using a Function
- •Divide and conquer
- •Understanding the Details of Functions
- •Understanding simple functions
- •Understanding functions with arguments
- •Overloading Function Names
- •Defining Function Prototypes
- •Variable Storage Types
- •Including Include Files
- •Considering the Need for Arrays
- •Using an array
- •Initializing an array
- •Accessing too far into an array
- •Using arrays
- •Defining and using arrays of arrays
- •Using Arrays of Characters
- •Creating an array of characters
- •Creating a string of characters
- •Manipulating Strings with Character
- •String-ing Along Variables
- •Variable Size
- •Address Operators
- •Using Pointer Variables
- •Comparing pointers and houses
- •Using different types of pointers
- •Passing Pointers to Functions
- •Passing by value
- •Passing pointer values
- •Passing by reference
- •Limiting scope
- •Examining the scope problem
- •Providing a solution using the heap
- •Defining Operations on Pointer Variables
- •Re-examining arrays in light of pointer variables
- •Applying operators to the address of an array
- •Expanding pointer operations to a string
- •Justifying pointer-based string manipulation
- •Applying operators to pointer types other than char
- •Contrasting a pointer with an array
- •Declaring and Using Arrays of Pointers
- •Utilizing arrays of character strings
- •Identifying Types of Errors
- •Choosing the WRITE Technique for the Problem
- •Catching bug #1
- •Catching bug #2
- •Calling for the Debugger
- •Defining the debugger
- •Finding commonalities among us
- •Running a test program
- •Single-stepping through a program
- •Abstracting Microwave Ovens
- •Preparing functional nachos
- •Preparing object-oriented nachos
- •Classifying Microwave Ovens
- •Why Classify?
- •Introducing the Class
- •The Format of a Class
- •Accessing the Members of a Class
- •Activating Our Objects
- •Simulating real-world objects
- •Why bother with member functions?
- •Adding a Member Function
- •Creating a member function
- •Naming class members
- •Calling a Member Function
- •Accessing a member function
- •Accessing other members from a member function
- •Defining a Member Function in the Class
- •Keeping a Member Function After Class
- •Overloading Member Functions
- •Defining Arrays of and Pointers to Simple Things
- •Declaring Arrays of Objects
- •Declaring Pointers to Objects
- •Dereferencing an object pointer
- •Pointing toward arrow pointers
- •Passing Objects to Functions
- •Calling a function with an object value
- •Calling a function with an object pointer
- •Calling a function by using the reference operator
- •Returning to the Heap
- •Comparing Pointers to References
- •Linking Up with Linked Lists
- •Performing other operations on a linked list
- •Hooking up with a LinkedListData program
- •A Ray of Hope: A List of Containers Linked to the C++ Library
- •Protecting Members
- •Why you need protected members
- •Discovering how protected members work
- •Protecting the internal state of the class
- •Using a class with a limited interface
- •Creating Objects
- •Using Constructors
- •Why you need constructors
- •Making constructors work
- •Dissecting a Destructor
- •Why you need the destructor
- •Working with destructors
- •Outfitting Constructors with Arguments
- •Justifying constructors
- •Using a constructor
- •Defaulting Default Constructors
- •Constructing Class Members
- •Constructing a complex data member
- •Constructing a constant data member
- •Constructing the Order of Construction
- •Local objects construct in order
- •Static objects construct only once
- •Global objects construct in no particular order
- •Members construct in the order in which they are declared
- •Destructors destruct in the reverse order of the constructors
- •Copying an Object
- •Why you need the copy constructor
- •Using the copy constructor
- •The Automatic Copy Constructor
- •Creating Shallow Copies versus Deep Copies
- •Avoiding temporaries, permanently
- •Defining a Static Member
- •Why you need static members
- •Using static members
- •Referencing static data members
- •Uses for static data members
- •Declaring Static Member Functions
- •What Is This About, Anyway?
- •Do I Need My Inheritance?
- •How Does a Class Inherit?
- •Using a subclass
- •Constructing a subclass
- •Destructing a subclass
- •Having a HAS_A Relationship
- •Why You Need Polymorphism
- •How Polymorphism Works
- •When Is a Virtual Function Not?
- •Considering Virtual Considerations
- •Factoring
- •Implementing Abstract Classes
- •Describing the abstract class concept
- •Making an honest class out of an abstract class
- •Passing abstract classes
- •Factoring C++ Source Code
- •Defining a namespace
- •Implementing Student
- •Implementing an application
- •Project file
- •Creating a project file under Dev-C++
- •Comparing Operators with Functions
- •Inserting a New Operator
- •Overloading the Assignment Operator
- •Protecting the Escape Hatch
- •How Stream I/O Works
- •The fstream Subclasses
- •Reading Directly from a Stream
- •Using the strstream Subclasses
- •Manipulating Manipulators
- •Justifying a New Error Mechanism?
- •Examining the Exception Mechanism
- •What Kinds of Things Can I Throw?
- •Adding Virtual Inheritance
- •Voicing a Contrary Opinion
- •Generalizing a Function into a Template
- •Template Classes
- •Do I Really Need Template Classes?
- •Tips for Using Templates
- •The string Container
- •The list Containers
- •Iterators
- •Using Maps
- •Enabling All Warnings and Error Messages
- •Insisting on Clean Compiles
- •Limiting the Visibility
- •Avoid Overloading Operators
- •Heap Handling
- •Using Exceptions to Handle Errors
- •Avoiding Multiple Inheritance
- •Customize Editor Settings to Your Taste
- •Highlight Matching Braces/Parentheses
- •Enable Exception Handling
- •Include Debugging Information (Sometimes)
- •Create a Project File
- •Customize the Help Menu
- •Reset Breakpoints after Editing the File
- •Avoid Illegal Filenames
- •Include #include Files in Your Project
- •Executing the Profiler
- •System Requirements
- •Using the CD with Microsoft Windows
- •Using the CD with Linux
- •Development tools
- •Program source code
- •Index
146 Part II: Becoming a Functional C++ Programmer
using namespace std;
int main(int nNumberofArgs, char* pszArgs[])
{
//accumulate input numbers until the
//user enters a negative number, then
//return the average
int nSum = 0;
for (int nNums = 0; ;nNums++)
{
//enter another number to add int nValue;
cout << “Enter another number:”; cin >> nValue;
cout << endl;
//if the input number is negative...
if (nValue < 0)
{
//...then output the average cout << “\nAverage is: “
<<nSum/nNums
<<“\n”;
break;
}
//not negative, add the value to
//the accumulator
nSum += nValue;
}
//wait until user is ready before terminating program
//to allow the user to see the program results system(“PAUSE”);
return 0;
}
Now rebuild the program and retest with the 1, 2, 3, and –1 sequence. This time you see the expected average value of 2. After testing the program with a number of other inputs, you convince yourself that the program is now exe cuting properly.
Calling for the Debugger
For small programs, the WRITE technique works reasonably well. Adding output statements is simple enough, and the programs rebuild quickly so the cycle time is short enough. Problems with this approach don’t really become obvious until the programs become large and complex.
Chapter 10: Debugging C++ 147
In large programs, the programmer often generally doesn’t know where to begin adding output statements. The constant cycle of adding write statements, exe cuting the program, adding write statements, and on and on becomes tedious. Further, in order to change an output statement, the programmer must rebuild the entire program. For a large program, this rebuild time can be significant.
(I have worked on programs that took most of the night to rebuild.)
Finally, finding pointer problems with the WRITE approach is almost impossi ble. A pointer written to the display in hex means nothing, and as soon as you attempt to dereference the pointer, the program blows.
A second, more sophisticated technique is based on a separate utility known as a debugger. This approach avoids the disadvantages of the WRITE state ment approach. However, this approach involves learning to use a debugger.
Defining the debugger
A debugger is actually a tool built into Dev-C++, Microsoft Visual Studio.NET, and most other development environments (though they differ, most debug gers work on the same principles).
The programmer controls the debugger through commands by means of the same interface as the editor. You can access these commands in menu items or by using hot keys.
The debugger allows the programmer to control the execution of her pro gram. She can execute one step at a time in the program, she can stop the program at any point, and she can examine the value of variables. To appreci ate the power of the debugger, you need to see it in action.
Finding commonalities among us
Unlike the C++ language, which is standardized across manufacturers, each debugger has its own command set. Fortunately, most debuggers offer the same basic commands. The commands you need are available in both the ubiquitous Microsoft Visual C++.NET and the Dev-C++ environments. In addi tion, in both environments, you can access debugger commands via menu items or function keys. Table 10-1 lists the command hot keys you use in both environments.
148 Part II: Becoming a Functional C++ Programmer
Table 10-1 |
Debugger Commands for Microsoft |
|
|
Visual C++.NET and Dev-C++ |
|
Command |
Visual C++ |
Dev-C++ |
Start executing in debugger |
F5 |
F8 |
|
|
|
Step in |
F11 |
Shift-F7 |
|
|
|
Step over (Next Step) |
F10 |
F7 |
|
|
|
Continue |
F5 |
Ctl-F7 |
|
|
|
View variable |
Menu only |
<Add Watch> |
|
|
|
Set breakpoint* |
Crl+B or click |
Ctl+F5 |
|
|
|
Add watch |
Menu only |
F4 |
|
|
|
Program reset |
Shift+F5 |
Ctl+Alt+F2 |
|
|
|
*Clicking in the trough to the left of the C++ source code listing is an alternate way to set a break point in both environments.
Running a test program
The best way to learn how to fix a program using the debugger is to go through the steps to fix a buggy program. The following program has several problems that need to be discovered and fixed. This version is found on the CD-ROM as Concatenate1.cpp.
// Concatenate - concatenate two strings
// |
with a “ - “ in the middle |
// |
(this version crashes) |
#include <cstdio> #include <cstdlib> #include <iostream> #include <string.h>
using namespace std;
void stringEmUp(char* szTarget, char* szSource1, char* szSource2, int nLength);
int main(int nNumberofArgs, char* pszArgs[])
{
cout << “This program concatenates two strings\n” << “(This version may crash.)” << endl;
char szStrBuffer[256];
Chapter 10: Debugging C++ 149
//create two strings of equal length...
char szString1[16];
strncpy(szString1, “This is a string”, 16); char szString2[16];
strncpy(szString2, “THIS IS A STRING”, 16);
//...now string them together stringEmUp(szStrBuffer,
szString1,
szString2,
16);
//output the result
cout << “<” << szStrBuffer << “>” << endl;
//wait until user is ready before terminating program
//to allow the user to see the program results system(“PAUSE”);
return 0;
}
void stringEmUp(char* szTarget, char* szSource1, char* szSource2, int nLength)
{
strcpy(szTarget, szSource1); strcat(szTarget, “ - “); strcat(szTarget, szSource2);
}
The program compiles uneventfully. Execute the program. Rather than gener ate the proper output, the program may return with almost anything. The first time I tried it, the program opened a console window and then immedi ately went away, without giving me any idea of what might be wrong. You’ll need to dive into the program using the debugger if you’re to have any hope of tracking down the problem.
Single-stepping through a program
The best first step when tracking down a program problem is to execute the program in debugger mode. Sometimes, the debugger can give you more information about the problem. The first time I executed the program under Dev-C++ using the debugger (by pressing F8), I received the error message “An Access Violation (Segmentation Fault) raised in your program.”
This is a little help, but you’ll need more information in order to track down the problem.
150 Part II: Becoming a Functional C++ Programmer
A Segmentation Fault usually indicates an errant pointer of some type.
You’ll have to reset the program back to the beginning before trying again. Click OK to acknowledge the error and then the Program Reset from the Debug menu or the Stop Execution command from the Debug toolbar to make sure that everything within the debugger is reset back to the beginning. It’s always a good idea to reset the debugger before starting again — doing so is necessary if the program is not at the starting point, and resetting the debug ger won’t hurt anything if the program is already at the beginning.
To see exactly where the problem occurs, execute just a part of the program. The debugger lets you do this through what is known as a breakpoint. The debugger stops the program if execution ever passes through a breakpoint. The debugger then gives control back to the programmer.
Now set a breakpoint at the first executable statement by clicking in the trough just to the left of the reference to cout immediately after main() or pressing F5 as shown in Table 10-1. A small red circle with a check appears. The display now appears like the one shown in Figure 10-2.
Now execute the program under the debugger again, either by selecting the Debug item under the Debug menu, by clicking the blue check mark on the debug toolbar, or by pressing F8. Program execution starts like normal but immediately stops on the first line. The line containing the breakpoint turns from red to blue, indicating that execution has halted at that point.
Figure 10-2:
A breakpoint shows up as a small red circle with a check.
Chapter 10: Debugging C++ 151
You can now select Next Step either from the Debug menu, from the debug toolbar, or by pressing F7 to execute one line at a time in the program. The blue marking moves to the next executable statement, skipping over both declarations. (A declaration is not a command and is not executed. A declara tion simply allocates space for a variable.) Executing a single C++ statement is also known as single stepping. You can switch to the Console window to see that the single output statement has executed, as shown in Figure 10-3.
Figure 10-3:
You can click the Console window at any time to see any program output.
Execute the Next Step two more times to move the point of execution to the call to StringEmUp(). So far, so good. When you select Next Step one more time, however, the program crashes ignominiously just as before. You now know that the problem is encountered somewhere within the StringEmUp() function.
When the program crashes within a function, either the function contains a bug, or the arguments passed to the function are incorrect.
The Next Step command treats a function call like a single command. This is known as stepping over the function. However, a function consists of a number of C++ statements of its own. You need to execute each of the statements within the function in order to better see what’s going on. I need a different type of single step command, one that steps into the function. This functional ity is provided by the Step Into debugger command.
Restart the program by selecting the Program Reset menu item from the Debug menu, by clicking on Stop Execution from the debug toolbar, or by pressing Alt+F2. This time, you want to save a little time executing right up to function call before stopping. Click the existing red circle to toggle the exist ing breakpoint off. The dot disappears. Next click in the trough across from the call to the function to set a new breakpoint, as shown in Figure 10-4.
152 Part II: Becoming a Functional C++ Programmer
Figure 10-4:
A breakpoint on the function call allows the programmer to execute up to the call.
You can have as many breakpoints active in a program at one time as you like. There is no (reasonable) limit.
Now start the program over again. This time, execution stops on the function call. Step into the function. The display appears like the one shown in
Figure 10-5.
Figure 10-5:
Stepping into a function moves control to the first executable statement within the function.
Chapter 10: Debugging C++ 153
You know that the program is about to crash. You could understand better what’s going on in the program if you could see the value of the arguments to the function. This is the function of Add Watch. A watch displays the value of a variable each time execution is halted. The easiest way to set a watch is to select the variable on the display and press F4. Figure 10-6 shows a watch set on all four arguments to the function.
Figure 10-6:
Setting a watch allows the programmer to monitor the value of a variable.
The numbers next to each name in the watch window are that variable’s address, which aren’t of much use in this case. szTarget appears to be an empty string — this makes sense because you’ve yet to copy anything there. The value of szSource1 looks okay, but the value of szSource2 includes both the “this is a string” and the “THIS IS A STRING” messages. Something seems to be amiss.
The answer actually lies in the final argument. The length of the two strings is not 16 characters but 17! The main program has failed to allocate room for the terminating null. The program terminates as soon as you execute the first statement within stringEmUp(), the call to strcpy().
The length of a string always includes the terminating null.
Now you update the program. This time, let C++ calculate the size of the string because it just naturally includes sufficient space. The resulting pro gram Concatenate2 works properly:
// Concatenate - concatenate two strings
// |
with a “ - “ in the middle |
// |
(this version crashes) |
#include <cstdio> |
|
#include <cstdlib>
#include <iostream> #include <string.h>
using namespace std;
void stringEmUp(char* szTarget,
154 Part II: Becoming a Functional C++ Programmer
char* szSource1, char* szSource2);
int main(int nNumberofArgs, char* pszArgs[])
{
cout << “This program concatenates two strings\n” << “(This version shouldn’t crash.)” << endl;
char szStrBuffer[256];
// define two strings...
char szString1[] = “This is a string”; char szString2[] = “THIS IS A STRING”;
//...now string them together stringEmUp(szStrBuffer,
szString1,
szString2);
//output the result
cout << “<” << szStrBuffer << “>” << endl;
//wait until user is ready before terminating program
//to allow the user to see the program results system(“PAUSE”);
return 0;
}
void stringEmUp(char* szTarget, char* szSource1, char* szSource2)
{
strcpy(szTarget, szSource1); strcat(szTarget, “ - “); strcat(szTarget, szSource2);
}
This version of the program generates the expected result:
This program concatenates two strings (This version shouldn’t crash.)
<This is a string - THIS IS A STRING> Press any key to continue . . .
Congratulations! You’re now a debugging expert.
Part III
Introduction to Classes
In this part . . .
The feature that differentiates C++ from other languages is C++’s support for object-oriented programming.
Object-oriented is about the most hyped term in the com puter world (okay, maybe .com has it beat). Computer languages, editors, and databases all claim to be objectoriented, sometimes with justification, but most of the time without.
Check out the BUDGET2 program on the enclosed CD-ROM to see an example program that can help you orient objects of object-oriented concepts.
What is it about being object-oriented that makes it so desired around the world? Read on to find out.