- •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
112 Part II: Becoming a Functional C++ Programmer
//output the address of each variable
//in order to get an idea of how variables are
//laid out in memory
cout << “--- = 0x” << &end << “\n”; cout << “&n = 0x” << &n << “\n”; cout << “&l = 0x” << &l << “\n”; cout << “&f = 0x” << &f << “\n”;
cout << “&d = 0x” << &d << “\n”;
//wait until user is ready before terminating program
//to allow the user to see the program results system(“PAUSE”);
return 0;
}
The program declares a set of variables. It then applies the & operator to each one to find out where it lies in memory. The results of one execution of this program with Dev-C++ appear as follows:
--- = 0x0x22ff6c &n = 0x0x22ff68 &l = 0x0x22ff64
&f = 0x0x22ff60 &d = 0x0x22ff58
Press any key to continue . . .
Your results may vary. The absolute address of program variables depends on a lot of factors. In general, it may even vary from one execution of the pro gram to the next.
Notice how the variable n is exactly 4 bytes from the first variable declared (m2). The variable l appears 4 bytes down from that. The double variable d is a full 8 bytes from its neighboring variable f. Each variable has been allo cated just the space needed for its type.
There is no requirement that the C++ compiler pack variables in memory with no spaces between them. Dev-C++ could have laid out the variables in memory in any other reasonable fashion.
Using Pointer Variables
A pointer variable is a variable that contains an address, usually the address of another variable. Returning to my hotel analogy for a moment, I might tell my son that I will be in room 0x100 on my trip. My son is a pointer variable of sorts. Anyone can ask him at any time, “Where’s your father staying?” and he’ll spill his guts without hesitation.
Chapter 8: Taking a First Look at C++ Pointers 113
The following pseudo-C++ demonstrates how the two address operators shown in Table 8-1 are used:
mySon = &DadsRoom; |
// |
tell mySon the address of Dad’s Room |
room = *mySon; |
// |
“Dad’s room number is” |
The following C++ code snippet shows these operators used correctly:
void fn()
{
int intVar; int* pintVar;
pintVar = &intVar; // pintVar now points to intVar *pintVar = 10; // stores 10 into int location
// pointed at by pintVar
}
The function fn() begins with the declaration of intVar. The next statement declares the variable pintVar to be a variable of type pointer to an int. (By the way, pintVar is pronounced pee-int-Var, not pint-Var.)
Pointer variables are declared like normal variables except for the addition of the unary * character. This * character can appear anywhere between the base type name — in this case int — and the variable name; however, it is becoming increasingly common to add the * to the end of the type.
The * character is called the asterisk character (that’s logical enough), but because asterisk is hard to say, many programmers have come to call it the splat character. Thus, they would say splat pintVar.
Many programmers adopt a naming convention in which the first character of the variable name indicates the type of the variable, such as n for int, d for double, and so on. A further aspect of this naming convention is to place a p at the beginning of a pointer variable name.
In an expression, the unary operator & means the address of. Thus, we would read the first assignment as store the address of intVar in pintVar.
To make this more concrete, assume that the memory for function fn() starts at location 0x100. In addition, assume that intVar is at address 0x102 and that pintVar is at 0x106. The layout here is simpler than the actual results from the Layout program; however, the concepts are identical.
The first assignment stores the value of & intVar (0x102) in the pointer vari able pintVar. The second assignment in the small program snippet says “store 10 in the location pointed at by pintVar.” The value 10 is stored in the address contained in pintVar, which is 0x102 (the address of intVar).
114 Part II: Becoming a Functional C++ Programmer
Comparing pointers and houses
A pointer is much like a house address. Your house has a unique address. Each byte in memory has an address that is unique. A house address is made up of both numbers and letters. For example, my address is 123 Main Street.
You can store a couch in the house at 123 Main Street — you can store a number in the byte located at 0x123456. Alternatively, you can take a piece of paper and write an address — I don’t know, say, 123 Main Street. You can now store a couch at the house with the address written on the piece of paper. In fact, this is the way delivery people work — their job is to deliver a couch to the address written down on the shipping orders whether it’s 123 Main Street or not. (I’m not maligning delivery people — they have brains — it’s just that this is more or less the way things work.)
In C++, the following code snippet finds the address of myHouse and stores a couch at that houseAddress (loosely speaking):
House myHouse;
House* houseAddress;
houseAddress = &myHouse; *houseAddress = couch;
In humanspeak, you would say myHouse is a House. houseAddress is the address of a House. Assign the address of myHouse to the House pointer, houseAddress. Now store a couch at the house located at the address stored in houseAddress.
Having said all that, take a look at the int and int* version of the earlier example code snippet:
int myInt;
int* intAddress;
intAddress = &myInt; *intAddress = 10;
That is, myInt is an int. intAddress is a pointer to an int. Assign the address of myInt to the pointer intAddress. Finally, assign 10 to the int that intAddress points to.
Using different types of pointers
Every expression has a type as well as a value. The type of the expression intVar is pointer to an integer, written as int*. Comparing this with the dec laration of pintVar, you see that the types match exactly:
int* pintVar = &intVar; // both sides of the assignment are
// of type int*
Chapter 8: Taking a First Look at C++ Pointers 115
Similarly, because pintVar is of type int*, the type of *pintVar is int:
*pintVar = 10; // both sides of the assignment are
// of type int
The type of the thing pointed to by pintVar is int. This is equivalent to saying that, if houseAddress is the address of a house, the thing pointed at by houseAddress must be a house. Amazing, but true.
Pointers to other types of variables are expressed the same way:
double doubleVar;
double* pdoubleVar = &doubleVar; *pdoubleVar = 10.0;
A pointer on a Pentium class machine takes 4 bytes no matter what it points to. That is, an address on a Pentium is 4 bytes long, period.
Matching pointer types is extremely important. Consider what might happen if the following were allowed:
int n1; int* pintVar;
pintVar = &n1; *pintVar = 100.0;
The second assignment attempts to store the 8-byte double value 100.0 into the 4-byte space allocated for n1. Actually, this isn’t as bad as it looks — C++ is smart enough to demote the constant 100.0 to an int before making the assignment.
It is possible to cast one type of variable into another:
int iVar;
double dVar = 10.0; iVar = (int)dVar;
Similarly, it is possible to cast one pointer type into another.
int* piVar;
double dVar = 10.0;
double* pdVar; piVar = (int*)pdVar;
Consider, however, what catastrophes can arise if this type of casting about of pointers were to get loose. Save a variable into an area of the wrong size, and nearby variables can be wiped out. This is demonstrated graphically in the following LayoutError program.
116 Part II: Becoming a Functional C++ Programmer
// LayoutError - demonstrate the results of // a messing up a pointer usage #include <cstdio>
#include <cstdlib> #include <iostream> using namespace std;
int main(int nNumberofArgs, char* pszArgs[])
{
int |
upper = 0; |
|
int |
n |
= 0; |
int |
lower = 0; |
// output the values of the three variables before...
cout << “the initial values are” << endl; cout << “upper = “ << upper << endl; cout << “n = “ << n << endl; cout << “lower = “ << lower << endl;
//now store a double into the space
//allocated for an int
cout << “\nStoring 13.0 into the location &n” << endl; double* pD = (double*)&n;
*pD = 13.0;
// display the results
cout << “\nThe final results are:” << endl; cout << “upper = “ << upper << endl;
cout << “n = “ << n << endl; cout << “lower = “ << lower << endl;
//wait until user is ready before terminating program
//to allow the user to see the program results system(“PAUSE”);
return 0;
}
The first three lines in main() declare three integers in the normal fashion. The assumption made here is that these three variables are laid out next to each other.
The next three executable lines output the value of the three variables. Not surprisingly, all three variables display as 0. The assignment *pD = 13.0; stores the double value 13.0 in the integer variable n. The three output state ments display the values of all three variables after the assignment.
After assigning the double value 13.0 in the integer variable n, n is not modi fied at all; however, the nearby variable upper is filled with a garbage value. This is not good, as the output from the program shows:
the initial values are
upper = 0