- •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
Chapter 9: Taking a Second Look at C++ Pointers 129
int main(int nNumberofArgs, char* pszArgs[])
{
int array[] = {4, 3, 2, 1}; displayArray(array, 4);
//wait until user is ready before terminating program
//to allow the user to see the program results system(“PAUSE”);
return 0;
}
The output from this program is
The value of the array is:
0:4
1:3
2:2
3:1
Press any key to continue . . .
You may think this pointer conversion is silly; however, the pointer version of displayArray() is actually more common among C++ programmers in the know than the array version. For some reason, C++ programmers don’t seem to like arrays.
The use of pointers to access arrays is nowhere more common than in the accessing of character arrays.
Expanding pointer operations to a string
A null terminated string is simply a character array whose last character is a null. C++ uses the null character at the end to serve as a terminator. This null terminated array serves as a quasi-variable type of its own. (See Chapter 7 for an explanation of string arrays.) Often C++ programmers use character point ers to manipulate such strings. The following code examples compare this technique to the earlier technique of indexing in the array.
Character pointers enjoy the same relationship with a character array that any other pointer and array share. However, the fact that strings end in a terminat ing null makes them especially amenable to pointer-based manipulation, as shown in the following DisplayString() program:
// DisplayString - display an array of characters both // using a pointer and an array index #include <cstdio>
#include <cstdlib> #include <iostream>
130 Part II: Becoming a Functional C++ Programmer
using namespace std;
int main(int nNumberofArgs, char* pszArgs[])
{
//declare a string char* szString = “Randy”;
cout << “The array is ‘“ << szString << “‘“ << endl;
//display szString as an array
cout << “Display the string as an array: “; for(int i = 0; i < 5; i++)
{
cout << szString[i];
}
cout << endl;
//now using typical pointer arithmetic cout << “Display string using a pointer: “; char* pszString = szString; while(*pszString)
{
cout << *pszString; pszString++;
}
cout << endl;
//wait until user is ready before terminating program
//to allow the user to see the program results system(“PAUSE”);
return 0;
}
The program first makes its way through the array szString by indexing into the array of characters. The for loop chosen stops when the index reaches 5, the length of the string.
The second loop displays the same string using a pointer. The program sets the variable pszString equal to the address of the first character in the array. It then enters a loop that will continue until the char pointed at by pszString is equal to false — in other words, until the character is a null.
The integer value 0 is interpreted as false — all other values are true.
The program outputs the character pointed at by pszString and then incre ments the pointer so that it points to the next character in the string before being returned to the top of the loop.
The dereference and increment can be (and usually are) combined into a single expression as follows:
cout << *pszString++;
Chapter 9: Taking a Second Look at C++ Pointers 131
The output of the program appears as follows:
The array is ‘Randy’
Display the string as an array: Randy
Display string using a pointer: Randy
Press any key to continue . . .
Justifying pointer-based string manipulation
The sometimes-cryptic nature of pointer-based manipulation of character strings might lead the reader to wonder, “Why?” That is, what advantage does the char* pointer version have over the easier-to-read index version?
The answer is partially (pre-)historic and partially human nature. When C, the progenitor to C++, was invented, compilers were pretty simplistic. These compilers could not perform the complicated optimizations that modern compilers can. As complicated as it might appear to the human reader, a statement such as *pszString++ could be converted into an amazingly small number of machine level instructions even by a stupid compiler.
Older computer processors were not very fast by today’s standards. In the old days of C, saving a few computer instructions was a big deal. This gave C a big advantage over other languages of the day, notably Fortran, which did not offer pointer arithmetic.
In addition to the efficiency factor, programmers like to generate clever pro gram statements. After C++ programmers learn how to write compact and cryptic but efficient statements, there is no getting them back to accessing arrays with indices.
Do not generate complex C++ expressions in order to create a more efficient program. There is no obvious relationship between the number of C++ state ments and the number of machine instructions generated.
Applying operators to pointer types other than char
It is not too hard to convince yourself that szTarget + n points to szTarget [n] when szTarget is an array of chars. After all, a char occupies a single byte. If szTarget is stored at 0x100, the sixth element is located at 0x105.
132 Part II: Becoming a Functional C++ Programmer
It is not so obvious that pointer addition works in exactly the same way for an int array because an int takes 4 bytes for each char’s 1 byte (at least it does on a 32-bit Intel processor). If the first element in intArray were located at 0x100, the sixth element would be located at 0x114 (0x100 + (5 * 4) = 0x114) and not 0x104.
Fortunately for us, array + n points at array[n] no matter how large a single element of array might be. C++ takes care of the element size for us — it’s clever that way.
Once again, the dusty old house analogy works here as well. (I mean dusty analogy, not dusty house.) The third house down from 123 Main is 126 Main, no matter how large the building might be, even if it’s a hotel.
Contrasting a pointer with an array
There are some differences between indexing into an array and using a pointer. For one, the array allocates space for the data, whereas the pointer does not, as shown here:
void arrayVsPointer()
{
//allocate storage for 128 characters char charArray[128];
//allocate space for a pointer but not for
//the thing pointed at
char* pArray;
}
Here charArray allocates room for 128 characters. pArray allocates only 4 bytes — the amount of storage required by a pointer.
The following function does not work:
void arrayVsPointer()
{
//this works fine char charArray[128]; charArray[10] = ‘0’;
*(charArray + 10) = ‘0’;
//this does not work char* pArray; pArray[10] = ‘0’; *(pArray + 10) = ‘0’;
}