- •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
140 Part II: Becoming a Functional C++ Programmer
program as these different output statements are executed. A second approach is to use a separate program called a debugger. A debugger enables you to con trol your program as it executes.
I cover both of these debugging techniques in this chapter.
Choosing the WRITE Technique for the Problem
Adding output statements to the C++ source code to find out what’s going on within the program is known as using the WRITE statement approach. It gained this name back in the days of early programs when programs were written mostly in COBOL and FORTRAN. Fortran’s output is through the WRITE command.
The following “buggy” program shows how the WRITE approach works.
The following program is supposed to read a series of numbers from the key board and return their average. Unfortunately, the program contains two errors, one that makes the program crash and one that causes the program to generate incorrect results.
The following steps route out the problem. First, enter the program as writ ten (or copy the program ErrorProgram1.cpp from the CD-ROM).
// ErrorProgram - this program averages a series
// |
of numbers, except that it contains |
// |
at least one fatal bug |
#include <cstdio> |
|
#include <cstdlib> |
|
#include <iostream> using namespace std;
int main(int nNumberofArgs, char* pszArgs[])
{
cout << “This program is designed to crash!”
<< endl; int nSum; int nNums;
// accumulate input numbers until the // user enters a negative number, then // return the average
nNums = 0; while(true)
{
// enter another number to add
Chapter 10: Debugging C++ 141
int nValue;
cout << “Enter another number:”; cin >> nValue;
cout << endl;
// if the input number is negative...
if (nValue < 0)
{
// ...then output the average cout << “Average is: “
<<nSum/nNums
<<endl;
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;
}
Build and execute the program as normal. I enter my trusty 1, 2, and 3 followed by –1, but get quite a shock when the nasty message shown in Figure 10-1 appears instead of the expected average.
Figure 10-1 appeared using Dev-C++ 4.9.8.0. User results may differ with differ ent C++ compilers, but the general idea that you did something really bad should stay the same.
Figure 10-1:
The initial version of Error Program terminates suddenly instead of generating the expected output.
142 Part II: Becoming a Functional C++ Programmer
Catching bug #1
Though unexpected, the error message shown in Figure 10-1 contains some useful information. However, don’t let the Cancel button get your hopes up. Despite the message, clicking on Cancel doesn’t bring you any closer to find ing the problem than does the OK button. Windows simply says that it can’t really help you find the problem.
Fortunately, the first line of the error message is descriptive of the problem. “Application error” means that someone tapped Windows on the back with an important message that Windows wasn’t expecting and didn’t know what to do with. The message is “Integer division by zero.” Apparently, someone divided a number by zero (pretty astute, huh?). The message also spits out the memory address where the division occurred, but this is of little use because you have no idea where in the program that address may be.
The divide by zero error message isn’t always so straightforward. For exam ple, suppose that the program lost its way and began executing instructions that aren’t part of the program? (That happens a lot more often than you might think.) The CPU may just happen to execute a divide instruction with a denominator of zero, thereby generating a divide by zero error message and masking the source of the problem. (An errant program is like a train that’s jumped the track — the program doesn’t stop executing until it hits some thing really big.)
A review of the program reveals only one obvious division:
cout << “Average is: “
<<nSum/nNums
<<endl;
Just because division appears only once, it doesn’t meant that this is the only place where division occurs. The compiler may have generated a division on its own as a result of some other C++ instruction that you wrote. In addition, the Standard C++ Library is just full of divisions.
I feel reasonably certain that at the time of the division, nNums must have been equal to zero. nNums is supposed to be a count of the number of values entered. You can add a cout statement to track the value of nNums within the while loop as follows:
while(true)
{
// output
cout << “nNums = “ << nNums << endl;
// ...the rest of program unchanged...
Chapter 10: Debugging C++ 143
This addition generates the following output:
This program is designed to crash! nNums = 0
Enter another number:1
nNums = 0
Enter another number:2
nNums = 0
Enter another number:3
nNums = 0
Enter another number:
You can see where nNums is initialized to 0, but where is it incremented? It isn’t, and this is the bug. Clearly nNums should have been incremented during each loop of the input section. I edit the while loop into a for loop as follows:
for (int nNums = 0; ;nNums++)
Catching bug #2
Having fixed a bug, execute the program using the same 1, 2, 3, and –1 input that crashed the program earlier. This time, the program doesn’t crash, but it doesn’t work either. The output shown here includes a ridiculous value for average:
This program generates incorrect results
Enter another number:1
Enter another number:2
Enter another number:3
Enter another number:-1
Average is: 1456814
Press any key to continue...
Apparently, either nSum or nNums (or both) isn’t being calculated properly. To get any farther, you need to know the value of these variables. In fact, it would help to know the value of nValue as well because nValue is used to calculate nSum.
Now you modify the for loop as follows to learn the values of the nSum, nNums, and nValue (this version of the program appears on the CD-ROM as ErrorProgram2.cpp):
144 Part II: Becoming a Functional C++ Programmer
// ErrorProgram - this program averages a series |
|
// |
of numbers, except that it contains |
// |
at least one fatal bug |
#include <cstdio> |
|
#include <cstdlib> |
|
#include <iostream> |
|
using namespace std; |
|
int main(int nNumberofArgs, char* pszArgs[]) |
|
{ |
cout << “This program generates incorrect results” |
|
|
|
<< endl; |
|
// accumulate input numbers until the |
|
// user enters a negative number, then |
|
// return the average |
|
int nSum; |
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;
}
// output critical information
cout << “nSum = “ << nSum << “\n”; cout << “nNums= “ << nNums << “\n”; cout << “nValue= “<< nValue << “\n”; cout << endl;
//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;
}
Chapter 10: Debugging C++ 145
Notice the addition of the output statements to display nSum, nNums, and nValue on each iteration through the loop.
The result of executing the program with the now standard 1, 2, 3, and –1 input is shown next. Even on the first loop, the value of nSum is unreasonable. In fact, at this point during the first loop, the program has yet to add a new value to nSum. You would think that the value of nSum should be 0.
This program generates incorrect results
Enter another number:1
nSum = 4370436 nNums= 0 nValue= 1
Enter another number:2
nSum = 4370437 nNums= 1 nValue= 2
Enter another number:3
nSum = 4370439 nNums= 2 nValue= 3
Enter another number:-1
Average is: 1456814
Press any key to continue . . .
On careful examination of the program, nSum is declared, but it isn’t initial ized to anything. The solution is to change the declaration of nSum to the following:
int nSum = 0;
Note: Until a variable has been initialized, the value of that variable is indeterminate.
When you have convinced yourself that you have found the problem, “clean up” the program as follows (this version is ErrorProgram3.cpp on the enclosed CD-ROM):
// ErrorProgram - this program averages a series // of numbers
#include <cstdio> #include <cstdlib> #include <iostream>