0% found this document useful (0 votes)
395 views1,170 pages

Programming for Beginners

This document appears to be the introduction or first chapter of a book about programming in C++. It introduces the topics that will be covered, such as hardware fundamentals, basics of programming, functions, data types, classes and objects. It includes sample code examples and exercises for the reader. The chapter discusses low-level concepts like binary numbers, memory addresses, and the CPU before building up to higher-level programming concepts.

Uploaded by

Shplad
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
395 views1,170 pages

Programming for Beginners

This document appears to be the introduction or first chapter of a book about programming in C++. It introduces the topics that will be covered, such as hardware fundamentals, basics of programming, functions, data types, classes and objects. It includes sample code examples and exercises for the reader. The chapter discusses low-level concepts like binary numbers, memory addresses, and the CPU before building up to higher-level programming concepts.

Uploaded by

Shplad
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 1170

This book is dedicated to Susan Patricia Caffee Heller, the light of my

life. Without her, this book would not be what it is; even more
important, I would not be what I am: a happy man.
List of Figures xix
Foreword xxxvii
Preface xxxix
Acknowledgements xlv
Letter from a Novice xlvii

CHAPTER 1 Introduction to Programming 1


How to Write a Program 5
Baby Steps 7
On with the Show 9

CHAPTER 2 Hardware Fundamentals 11


Objectives of This Chapter 13
Inside the Box 14
Disk 14
Seventeen Years of Progress 17
RAM 18
Memory Addresses 19
The CPU 22
How Caches Improve Performance 27
How Registers Improve Performance 28
The Binary Number System 32
Signed Variables 37
A (Very) Short Course in Hexadecimal Notation 41
Exercises 48
Using the 16-bit Register Names 49
Revisiting the Memory Hierarchy 52
The Advantages and Disadvantages of Using
Registers 52
The Effect of Registers on Program Size 54
Reading Instructions into Memory in Advance 62
Review 63
Conclusion 64
Answers to Exercises 65

CHAPTER 3 Basics of Programming 67


Objectives of This Chapter 68
Rapid and Accurate Calculation 69
The Real Reason for “Computer Problems” 70
Nonnumeric Variables 71
The Compiler 72
How the CPU Stores and Manipulates Data in
Memory 77
The Layout of Data in Memory 80
Exercises, First Set 82
Playing Compiler 82
Executing Our Little Program Fragment 92
The char and string Types 96
using, namespace, and std 98

vi C++: A Dialog
int main() 100
A Byte by Any Other Name... 106
Nonprinting Characters 107
Exercises, Second Set
109 Input and Output 110
Changing the Course of Execution 112
The while Loop 115
Exercises, Third Set 117
Our First Slightly Realistic Program 118 Susan
Tries to Write the Pumpkin Program Herself
126
Try the Pumpkin Program Yourself 128
Exercises, Fourth Set 128
Review 129
Conclusion 132
Answers to Exercises 133

CHAPTER 4 More Basics 149


Algorithmic Thinking 150
Susan Finds a Bug 154
Even a Simple Change to a Program Can
Cause Errors 159
Handling Any Number of Prizes 159
The vector Data Type 161
The Vec Data Type 162
A Historical Example of One-based Indexing 167
Index Variables 169
The Selection Sort 178
The Selection Sort in More Detail 189
Making Assumptions Can Be Dangerous 191
Why We’re Using Vec Rather Than vector 194
Uninitialized Variables 194
Program Failure200
Why We Need to Initialize Variables Explicitly 202
Preventing Improper Data Input 203
Review 207
Exercises 209
Conclusion 211
Answers to Exercises 212

CHAPTER 5 Functional Literacy 219


Objectives of This Chapter 223
Modules vs. Functions 223
An Example of Using a Function 229
Returning Data to the Calling Function 231
Function Arguments 233
How the Average Function Works 238
Using a Function 239
Software Is a Virtual Computer 247
Object Files 248
Operating System Facilities 250
Library Modules 252
Why All Variables Aren’t Automatically Initialized 254
Nested Functions 258
Returning to the Calling Function 260
The Layout of Data in a Stack 262
Scope of Variables 267 Automatic
vs. Static Allocation 268 The Scope
Resolution Operator 273
The Disadvantages of Global Variables 282
Using a Very Primitive BASIC Language 283
The Solution to My Problem with BASIC 284
A Scope Defines a namespace 285
More on Using the Stack 285
Review 287
Exercises 290
Conclusion 291
Answers to Exercises 291

viii C++: A Dialog


CHAPTER 6 Taking Inventory 295
Objectives of This
Chapter 298 User-defined
Data Types 299 The StockItem
class 305 More
Definitions 308
Concrete Data Types 309
The class Scope 327
More about the StockItem class Interface 333
Referring to Standard Library Identifiers in Header
Files 335
The Need for More Than One Constructor 336
Handling a Number of StockItems with a Vec 344
Working around the Standard Library 348
The Second Version of the StockItem Interface 349
Reference Arguments 350
Fencepost Errors 357
Other Possible Transactions with the Inventory 360
Attempting to Access private Member Variables 363
Making the Program More User-friendly 367
The Inventory class 372
Using a Null Object 377
Checking Inventory for a Misplaced Item 391
Review 392
Exercises 401
Conclusion 402
Answers to Exercises 402

CHAPTER 7 Creating a Homegrown string


class 405
C String Literals vs. strings 406
Pointers 414
Dynamic Memory Allocation via new and
delete 417
Constructing a string from a C String 424
Calling the string(char*) Constructor 436
Assignment Operator Issues 438
Solving the Assignment Operator Problem 441
The const Modifier for Reference
Arguments 444
Calling operator= 448
The Keyword this 450
Memory Allocation Errors 453
More on operator = 457 The
Destructor 463
Review 466
Exercises 467
Conclusion 469
Answers to Exercises 470

CHAPTER 8 Finishing Our homegrown string


class 473
Why We Need a Reference Argument for operator
= 474
Premature Destruction 475
The Compiler Generates a Temporary Variable 478
The string Copy Constructor 483
Screen Output 485
The Array 489
The Equivalence of Arrays and Pointers 491
The char As a Very Short Numeric Variable 494
Running off the End of an Array 495
More about the private Access Specifier 500
The Advantages of Encapsulation 503
First Review506
Adding Further Facilities to our string
class 510
Implementing operator < 514
Details of the Comparison Algorithm 516
The Logical AND Operator 525
Other Possible Results of the Comparison 529

x C++: A Dialog
Using a Standard Library Function to Simplify
the Code 529
Implementing operator == 532 Implementation
vs. Declaration Revisited 534 Using cout With
User-defined Types 535 How cout Works
With Pre-existing Types 536 Writing Our Own
Standard Library-Compatible operator <<
537
The friend Keyword 539
Reading a string from an istream 541
Initialization vs. Assignment 543
The memset Standard Library Function 545
Second Review 550
Exercises 552
Conclusion 554
Answers to Exercises 555

CHAPTER 9 Inheritance563
Objectives of This Chapter 565
Two Reasons to Use Inheritance 566
Taking Inventory 574
Adding ReorderItems to the Inventory
class 575
Adding Expiration Dates 581
Reducing Maintenance Via Inheritance 584
A Note on Proper Object-oriented Design 589
Overriding a Base class Function 589
The protected Access Specifier 596
public Inheritance 613
private Inheritance 613
static Member Functions 616 The
stream classes 621 More
about stringstream 624
Controlling Output Formatting 629
Using iostream Manipulators 632
Base class Constructors 634
The Reorder Function for the DatedStockItem
class 642
Review 651
Exercises 654
Conclusion 656

CHAPTER 10 Polymorphism 657


Objectives of This Chapter 659
Introduction to Polymorphism 659
The virtual Keyword 662
Problems with Using Pointers for Polymorphism 676
Using a virtual Function for I/O 687
References to Pointers 689
Exercises, First Set 693
Polymorphic Objects
694
More Definitions 694
Why We Need Polymorphic Objects695 Using a Base
class Pointer to Point to Derived class Objects 699
Implementing Safe Polymorphism 700
Reimplementing the Standard Member Functions
for the New Version of StockItem 715
Avoiding an Infinite Regress During
Construction 720
Reference Counting723
Sharing a Worker Object 724
The Order of Destructor Calls 735
Tracing the Destruction of Our
Polymorphic Objects 740
Why We Need m_Count in StockItem 743
Executing Code before the Beginning of main 744
Review 746
Exercises, Second Set 751
Conclusion 752
CHAPTER 11 The Home Inventory Project 753
Objectives of This Chapter 754
Data Items for the Home Inventory Project 755
Choosing Categories for Items 756
The Manager/Worker Idiom Again 758 Similarities
and Differences between Polymorphic Objects
759
Hiding Unnecessary Information from the class
User 763
The Initial HomeItem Test Program 765
Creating Local Variables When They Are Needed 778
The Scope of an Index Variable 779
One of the Oddities of the Value 0 781
Reusing Code Via a virtual Function 783
The Problem with Calling resize Repeatedly 791
Saving the Number of the Elements in the File
793
Handling the End-of-File Condition Properly 795
Creating a Data File Programmatically 797
Comparing Two streams 804 Inheriting
the Implementation of a virtual Function
808
Adding the Ability to Edit a Record 814 The New
Implementation of operator >> 818 Reducing
Code Duplication 820
The enum Type 824
Automatic Conversion from enum 827
The Implementation of HomeItem::Read 828
The Implementation of
HomeItemBasic::ReadInteractive 829
The Implementation of
HomeItemBasic::ReadFromFile 833
The Implementation of HomeItemBasic::Edit 834
The Implementation of
HomeItemBasic::FormattedDisplay 835
The Implementation of
HomeItemBasic::EditField 836 The
New Member Functions of
HomeItemMusic 839
Keeping Track of the Next Field Number 840
Review 846
Exercises 853
Conclusion 854

CHAPTER 12 More on the Home Inventory


Project 855
Objectives of This Chapter 857
Extending the Functionality of strings 857 How
to Implement Our New string Functionality
858
Why Our New xstring class Is a Good Design
Choice 860
The Interface of the New xstring class 862
The Include Guard863
The Constructors for the xstring class 866
Automatic Conversion from the Standard string
class 867
Preventing Accidental Initialization by 0 873
The explicit Keyword 875
Lessons of the xstring class
Implementation 878
One More Design Decision Regarding the xstring
class 879
Case-Insensitive Searching 880 Searching for
an Item by a Substring 888 Putting It All
Together 895
First Test Session: Change Requests and Problem
Reports 897
Second Test Session: Change Requests and Problem
Reports 900
Third Test Session: Change Requests and Problem
Reports 902
Fourth Test Session: Change Requests and
Problem Reports 903
Fifth Test Session: Change Requests and Problem
Reports 905
How Software Development Really Works 906
Review 907
Conclusion 910

CHAPTER 13 Analyzing the Home Inventory


Project 911
Objectives of This Chapter 912
The Final Version of the Home Inventory
Program 913
Using a namespace to Group Utility
Functions 925
The Advantages of Using namespaces 926
Default Arguments 927
The Functions of the HomeUtility
namespace 930
Returning More Than One Value from a Function 931
Reading a Date Field 931
Reading Numeric Data from a Line Oriented File 936
A Tradeoff between Convenience and Error
Control 937
Single Keystroke Data Entry 938
Echoing the Typed Character to the Screen
943 Getting the Rest of the Numeric Value 945
Clearing Part of the Screen 947
The Implementation of
HomeUtility::SelectItem 949
Checking the Inventory 955
Sorting the Inventory 965
Selecting by Category 969
Finishing up the HomeItem class 974
Are We Having Fun Yet?983
Review 983
Exercises 988
Conclusion 989
DIX A Tying up Loose Ends 991 Operator
Precedence 991 Another Native Data
Type 992
Wrapping Up 993

APPENDIX B Glossary 995


Special Characters 995
A 999
B 1002
C 1004
D 1010
E 1013
F 1014
G 1017
H 1017
I 1018
K 1021
L 1021
M 1023
N 1025
O 1028
P 1030
R 1033
S 1036
T 1041
U 1043
V 1044
W 1045
Y 1045
Z 1046
About the Author 1047
Bibliography 1049
Index 1051
xviii C++: A Dialog
List of Figures

Chapter 1: Introduction to Programming 1

Chapter 2: Hardware Fundamentals 11

Figure 2.1. RAM vs. CPU speeds 23 Figure 2.2. A


memory hierarchy 26 Figure 2.3. The first few
numbers 33 Figure 2.4. The next few numbers 33
Figure 2.5. How many combinations? 35
Figure 2.6. Binary to hex conversion table 46
Figure 2.7. Different representations of the same numbers 47 Figure 2.8. 32 and
16 bit registers, before add ax,1 50
Figure 2.9. 32 and 16 bit registers, after add ax,1 51
Figure 2.10. 32 and 16 bit registers, after add eax,1 51
Figure 2.11. 32 and 16 bit register codes 54
Figure 2.12. Instruction execution time, using registers and prefetching 63

Chapter 3: Basics of Programming 67


Figure 3.1. A little numeric calculation 72 Figure 3.2.
A small section of RAM 78 Figure 3.3. One little
endian value 81 Figure 3.4. A big endian example 81
Figure 3.5. Exercise 1 82
Figure 3.6. A really little numeric calculation 83 Figure 3.7.
Compiling, part 1 84
Figure 3.8. Compiling, part 2 85
Figure 3.9. Compiling, part 3 86
Figure 3.10. Compiling, part 4 91
Figure 3.11. Before execution 93
Figure 3.12. After the first instruction is executed 94 Figure 3.13.
After execution of second instruction 94 Figure 3.14. After execution
of third instruction 95 Figure 3.15. After execution of final instruction
95
Figure 3.16. Some real characters and strings (code\basic00.cpp) 98 Figure 3.17. Yet
another small section of RAM 106
Figure 3.18. Special characters for program text 109 Figure 3.19. A
small section of RAM 109
Figure 3.20. Some simple output (code\basic01.cpp) 110
Figure 3.21. Some simple input and output (code\basic02.cpp) 111 Figure 3.22. Using an if
statement (code\basic03.cpp) 113
Figure 3.23. Using a while statement (code\basic04.cpp) 115 Figure 3.24. A C++
program (code\pump1.cpp) 120
Figure 3.25. First dinner party program (code\basic05.cpp) 133 Figure 3.26. Second
dinner party program (code\basic06.cpp) 135 Figure 3.27. else if example 135
Figure 3.28. Name and age program (code\basic07.cpp) 136 Figure 3.29. Novice
program (code\basic08.cpp) 141
Figure 3.30. Allowance program (code\basic09.cpp) 143

Chapter 4: More Basics 149

Figure 4.1. Finding the top two weights, first try (code\pump1a.cpp) 150 Figure 4.2. Susan’s
solution to the bug in the first attempt 155
Figure 4.3. Using an if statement with an else clause 155

xx C++: A Dialog
Figure 4.4. Finding the top two weights (code\pump2.cpp) 156 Figure 4.5. What if?
158
Figure 4.6. Using a Vec (code\vect1.cpp) 164
Figure 4.7. Using a for loop (from code\vect1.cpp) 170 Figure 4.8. Sorting
the weights (from code\vect1.cpp) 179 Figure 4.9. Elements vs. values 181
Figure 4.10. Initial situation 190 Figure 4.11.
After the first pass 190 Figure 4.12. After the
second pass 191 Figure 4.13. Final situation 191
Figure 4.14. A possible error message 192
Figure 4.15. Sorting the weights, again (from code\vect1.cc) 192 Figure 4.16. Sorting
the weights, with correct initialization (from
code\vect2.cpp) 199
Figure 4.17. Garbage prevention, first attempt (from code\vect2a.cc) 203 Figure 4.18. Finding
the top three weights using Vecs (code\vect3.cc) 204 Figure 4.19. Exercise 1
(code\morbas00.cpp) 210
Figure 4.20. Exercise 2 (code\morbas01.cpp) 210
Figure 4.21. Weight requesting program, first try (code\morbas02.cpp) 213 Figure 4.22. Error
messages from the erroneous weight program
(code\morbas02.cpp) 215
Figure 4.23. The corrected weight program (code\morbas03.cpp) 215 Figure 4.24. The weight
totalling program (code\morbas04.cpp) 216

Chapter 5: Functional Literacy 219

Figure 5.1. A sample program with duplicated code (code\nofunc.cpp) 224 Figure 5.2. A function
call 226
Figure 5.3. A function to average two values 229
Figure 5.4. Argument passing with one argument (code\birthday.cpp) 234 Figure 5.5. Using the
Average function (code\func1.cpp) 239
Figure 5.6. Making an executable 250 Figure 5.7. A
stack with one entry 259 Figure 5.8. A stack with two
entries 259 Figure 5.9. A stack with three entries 259
Figure 5.10. An empty stack 263
Figure 5.11. The stack immediately after the call to Average 264 Figure 5.12. The
stack after auto variable allocation 264
Figure 5.13. Scope vs. storage class 267
Figure 5.14. Using an auto variable and initializing it (code\count1.cpp) 270 Figure 5.15. Using
an auto variable and not initializing it (code\count2.cpp)
271
Figure 5.16. Using a local static variable and initializing it explicitly (code\count3.cpp) 272
Figure 5.17. Using a local static variable and not initializing it explicitly (code\count4.cpp) 272
Figure 5.18. Using a global variable and initializing it explicitly (code\count5.cpp) 274
Figure 5.19. Using a global variable and not initializing it explicitly (code\count6.cpp) 274
Figure 5.20. Using variables of different scopes and storage classes (code\scopclas.cpp)
280
Figure 5.21. The results of using variables of different scopes and storage classes
(code\scopclas.out) 281
Figure 5.22. The stack after the initialization of Result 285 Figure 5.23. The
stack after exiting from Average 286 Figure 5.24. Exercise 1
(code\calc1.cpp) 290

Chapter 6: Taking Inventory 295

Figure 6.1. The initial sample program for the StockItem class (code\itemtst1.cpp) 307
Figure 6.2. Comparison between native and user-defined types 310
Figure 6.3. The initial interface of the StockItem class (code\item1.h) 316 Figure 6.4. The
default constructor for the StockItem class (from
code\item1.cpp) 321
Figure 6.5. Declaring the default constructor for the StockItem class 322 Figure 6.6. Another
way to write the default StockItem constructor 324 Figure 6.7. Another constructor for the
StockItem class (from
code\item1.cpp) 334
Figure 6.8. Display member function for the StockItem class (from code\item1.cpp) 338
Figure 6.9. The initial interface of the StockItem class (code\item1.h) 341 Figure 6.10. The initial
implementation of the StockItem class
(code\item1.cpp) 342
Figure 6.11. Reading and displaying a Vec of StockItems (code\itemtst2.cpp) 345
Figure 6.12. The Read function for the StockItem class (from code\item2.cpp) 347
Figure 6.13. The second version of the interface for the StockItem class (code\item2.h) 350
Figure 6.14. The declaration of StockItem::Read in code\item2.h 352 Figure 6.15. First
attempt to update inventory of StockItems
(code\itemtst3.cpp) 361
Figure 6.16. Unauthorized access prohibited 364
Figure 6.17. An enhanced interface for the StockItem class (code\item4.h) 366
Figure 6.18. StockItem::CheckUPC (from code\item4.cpp) 368 Figure 6.19.
StockItem::DeductSaleFromInventory (from
code\item4.cpp) 369
Figure 6.20. StockItem::GetInventory (from code\item4.cpp) 369 Figure 6.21.
StockItem::GetName (from code\item4.cpp) 369 Figure 6.22. Updating StockItem
inventory (code\itemtst4.cpp) 370 Figure 6.23. Interface of Inventory class
(code\invent1.h) 373 Figure 6.24. Default constructor for Inventory class (from
code\invent1.cpp) 375
Figure 6.25. LoadInventory function for the Inventory class (from code\invent1.cpp) 376
Figure 6.26. The implementation of IsNull (from code\item5.cpp) 378 Figure 6.27. FindItem
function for Inventory class (from
code\invent1.cpp) 379
Figure 6.28. UpdateItem function for the Inventory class (from code\invent1.cpp) 380
Figure 6.29. The implementation of GetUPC (from code\item5.cpp) 381 Figure 6.30. The
implementation of GetPrice (from code\item5.cpp) 382 Figure 6.31. Current interface for
Inventory class (code\invent1.h) 382 Figure 6.32. Current implementation for Inventory class
(code\invent1.cpp) 382
Figure 6.33. Current interface for StockItem class (code\item5.h) 384 Figure 6.34. Current
implementation for StockItem class (code\item5.cpp)
385
Figure 6.35. Updated inventory application (code\itemtst5.cpp) 388 Figure 6.36. The Write
member function for the StockItem class (from
code\item6.cpp 403
Figure 6.37. The StoreInventory member function for the Inventory class (from
code\invent2.cpp) 403
Figure 6.38. The changes to the application program (from
code\itemtst6.cpp) 403

Chapter 7: Creating a Homegrown string class 405

Figure 7.1. Our string class interface, initial version (code\string1.h) 409 Figure 7.2. The
initial implementation for the string class
(code\string1.cpp) 410
Figure 7.3. The default constructor for our string class (from code\string1.cpp) 412
Figure 7.4. An empty string in memory 422
Figure 7.5. Our first test program for the string class (code\strtst1.cpp) 423 Figure 7.6. The
char* constructor for our string class (from
code\string1.cpp) 427 Figure 7.7. string
n during construction 434 Figure 7.8. string n in
memory 435
Figure 7.9. A simple test program for the string class (code\strtst1.cpp) 436 Figure 7.10. The
char* constructor for the string class, again (from
code\string1.cpp) 436
Figure 7.11. strings n and s in memory after compiler-generated = 439 Figure 7.12. strings
n and s in memory after custom = 442
Figure 7.13. The declaration of operator = for the string class 447 Figure 7.14.
Calling the operator = implementation 449
Figure 7.15. The assignment operator (operator =) for the string class (from
code\string1.cpp) 450
Figure 7.16. Checking for an exception from new 456
Figure 7.17. A hypothetical assignment operator (operator =) for the string class with explicit
this 462
Figure 7.18. The destructor for the string class (from code/string1.cpp) 465 Figure 7.19.
Exercise 1 (code\strex1.cpp) 467
Figure 7.20. Exercise 2 (code\strex2.cpp) 468
Figure 7.21. Exercise 3 (code\strex3.cpp) 469

Chapter 8: Finishing Our homegrown string class 473

Figure 8.1. Call by value ("normal argument") using the compiler-generated copy constructor
474
Figure 8.2. Call by reference 476
Figure 8.3. Our first test program for the string class (code\strtst1.cpp) 477 Figure 8.4. Assigning
a char* value to a string via string::string(char*)
479
Figure 8.5. The string class interface (code\string1.h) 483 Figure 8.6. The
copy constructor for the string class (from
code\string1.cpp) 484
Figure 8.7. The string class interface, with Display function (code\string3.h) 486
Figure 8.8. The latest version of the string class test program, using the Display function
(code\strtst3.cpp) 486
Figure 8.9. The first few lines of the latest implementation of the string class (from
string3.cpp) 487
Figure 8.10. The string class implementation of the Display function (from string3.cpp)
489
Figure 8.11. Dangerous characters (code\dangchar.cpp) 492
Figure 8.12. Reaping the whirlwind 495
Figure 8.13. The memory layout before overwriting the data 499 Figure 8.14. The
memory layout after overwriting the data 500 Figure 8.15. Attempted privacy violation
(code\strtst3a.cpp) 501 Figure 8.16. Trying to access a private member variable illegally
501 Figure 8.17. Yet another version of the string class interface
(code\string4.h) 504
Figure 8.18. The string class implementation of the GetLength function (from
code\string4.cpp) 505
Figure 8.19. Using the GetLength function in the string class (code\strtst4.cpp) 506
Figure 8.20. Sorting a Vec of strings (code\strsort1.cpp) 510
Figure 8.21. The updated string class interface, including comparison and
I/O operators (code\string5.h) 514 Figure 8.22.
strings x and y in memory 518
Figure 8.23. strings x and y in memory, with an embedded null byte 519 Figure 8.24. Using
operator < for strings (code\strtst5x.cpp) 522
Figure 8.25. The implementation of operator < for strings (from code\string5a.cpp) 523
Figure 8.26. Is our character less than the one from the other string? (from code\string5a.cpp) 526
Figure 8.27. The else clause in the comparison loop (from
code\string5a.cpp) 527
Figure 8.28. Handling the return value (from code\string5a.cpp) 528 Figure 8.29.
Implementing operator < for strings (from code\string5.cpp)
529
Figure 8.30. Implementing operator == for strings (from code\string5.cpp) 533
Figure 8.31. Chaining several operator << expressions together (code\cout1.cpp) 535
Figure 8.32. An operator << function to output a string (from code\string5.cpp) 538
Figure 8.33. Why operator << has to be implemented via a global function 541
Figure 8.34. A operator >> function to input a string (from
code\string5.cpp) 541
Figure 8.35. Error from an uninitialized const (code\string5x.err) 543 Figure 8.36. Use of a
non-const array size (code\string5y.cpp) 545 Figure 8.37. Exercise 1 (code\strex5.cpp) 552
Figure 8.38. Exercise 2 (code\strex6.cpp) 553
Figure 8.39. The string class interface file (from code\string6.h) 556 Figure 8.40. The
string class implementation of operator > (from
code\string6.cpp) 557
Figure 8.41. The string class implementation of operator >= (from code\string6.cpp) 557
Figure 8.42. The string class implementation of operator != (from code\string6.cpp) 558
Figure 8.43. The string class implementation of operator <= (from code\string6.cpp) 558
Figure 8.44. The test program for the comparison operators of the string class
(code\strcmp.cpp) 559

xxvi C++: A Dialog


Chapter 9: Inheritance 563

Figure 9.1. The next StockItem header file (code\item20.h) 568


Figure 9.2. The next implementation of StockItem (code\item20.cpp) 570 Figure 9.3. The next
header file for the Inventory class (code\invent20.h)
574
Figure 9.4. The StockItem test program for the base StockItem class (code\itmtst20.cpp) 576
Figure 9.5. The implementation of the Inventory class (code\invent20.cpp) 577
Figure 9.6. The Reorder function for the StockItem class (from code\item20.cpp) 580
Figure 9.7. Simplified interface for StockItem and DatedStockItem class- es (code\itema.h)
585
Figure 9.8. A simplified StockItem object 586 Figure 9.9.
A DatedStockItem object 587
Figure 9.10. A derived class object with its base class part 600 Figure 9.11. The next
version of the inventory control test program
(code\itmtst21.cpp) 602
Figure 9.12. Full interface for StockItem and DatedStockItem (code\item21.h) 604
Figure 9.13. Latest implementation of StockItem class and first implemen- tation of
DatedStockItem class (code\item21.cpp) 606
Figure 9.14. DatedStockItem::Today() (from code\item21.cpp) 618 Figure 9.15. A
simple stream example (code\stream1.cpp) 621 Figure 9.16. An empty ostream object
622
Figure 9.17. An ostream object with some data 623 Figure 9.18. An
ostream object with some more data. 623 Figure 9.19. An empty
ostream object 624
Figure 9.20. A stringstream formatting example (code\stream2.cpp) 624 Figure 9.21. An empty
stringstream object 625
Figure 9.22. A stringstream object with some contents 627 Figure 9.23. A
stringstream object with some more contents 628 Figure 9.24. A stringstream
object with even more contents 628 Figure 9.25. A stringstream object after
reading its contents 629 Figure 9.26. Default formatting example
(code\coutdef1.cpp) 630
Figure 9.27. Output of default formatting example (code\coutdef1.out) 631 Figure 9.28. Output
of controlled formatting example (code\coutdef2.out)
632
Figure 9.29. Controlled formatting example (code\coutdef2.cpp) 632 Figure 9.30. Default
constructor for DatedStockItem (from
code\item21.cpp) 635
Figure 9.31. Specifying the base class constructor for a derived class object 636
Figure 9.32. Constructing a default DatedStockItem object 638 Figure 9.33. Normal
constructor for DatedStockItem (from
code\item21.cpp) 639
Figure 9.34. Constructing a DatedStockItem object 640 Figure 9.35. The
Reorder function for DatedStockItem (from
code\item21.cpp) 642
Figure 9.36. Calling Reorder through a StockItem pointer, part 1 645 Figure 9.37. Calling
Reorder through a DatedStockItem pointer 646 Figure 9.38. Calling Reorder through a
StockItem pointer, part 2 647 Figure 9.39. Function call example (code\nvirtual.cpp) 647
Figure 9.40. Function call example output (code\nvirtual.out) 648
Figure 9.41. Simplified implementation for StockItem and DatedStock- Item classes
(code\itema.cpp) 649

Chapter 10: Polymorphism 657

Figure 10.1. Dangerous polymorphism: Interfaces of StockItem and Dat- edStockItem with
virtual Reorder function (code\itemb.h) 663
Figure 10.2. virtual function call example output (code\virtual.out) 664 Figure 10.3. A simplified
StockItem object without virtual functions 666 Figure 10.4. Dangerous polymorphism: A
simplified StockItem object with
a virtual function 668
Figure 10.5. Dangerous polymorphism: A simplified DatedStockItem ob- ject with a virtual
function 669
Figure 10.6. Dangerous polymorphism: Calling a virtual Reorder function through a StockItem
pointer to a StockItem object 670
Figure 10.7. Dangerous polymorphism: Calling a virtual Reorder function through a
DatedStockItem pointer to a DatedStockItem object 671

xxviii C++: A Dialog


Figure 10.8. Dangerous polymorphism: Calling a virtual Reorder function through a StockItem
pointer to a DatedStockItem object 672
Figure 10.9. Dangerous polymorphism: A simplified StockItem object with two virtual functions
674
Figure 10.10. Dangerous polymorphism: A simplified DatedStockItem with two virtual
functions 675
Figure 10.11. Dangerous polymorphism: Using operator << with a Stock- Item*
(code\polyioa.cpp) 676
Figure 10.12. Result of using operator << with a StockItem* (code\poly- ioa.out) 677
Figure 10.13. Dangerous polymorphism: StockItem interface with operator << and
operator >> (code\itemc.h) 677
Figure 10.14. Dangerous polymorphism: StockItem implementation with operator << and
operator >> (code\itemc.cpp) 680
Figure 10.15. Dangerous polymorphism: The implementation of
operator << with a StockItem* (from code\itemc.cpp) 687 Figure 10.16.
Dangerous polymorphism: StockItem::Write (from
code\itemc.cpp) 688
Figure 10.17. Dangerous polymorphism: DatedStockItem::Write (from code\itemc.cpp) 688
Figure 10.18. Dangerous polymorphism: Using operator >> and operator << with a
StockItem* (code\polyiob.cpp) 689
Figure 10.19. Dangerous polymorphism: The results of using operator >> and operator <<
with a StockItem* (code\polyiob.out) 690
Figure 10.20. Dangerous polymorphism: The implementation of operator >> (from
code\itemc.cpp) 692
Figure 10.21. Safe polymorphism: Using operator >> and operator << with a polymorphic
StockItem (code\polyioc.cpp) 696
Figure 10.22. Safe polymorphism: The polymorphic object version of the StockItem interface
(code\itemp.h) 697
Figure 10.23. Safe polymorphism: The UndatedStockItem and Dated- StockItem interfaces for
the polymorphic version of Stoc- kItem (code\itempi.h) 700
Figure 10.24. Safe polymorphism: The implementation of the Undated- StockItem and
DatedStockItem classes (code\itemp.cpp) 701
Figure 10.25. Safe polymorphism: The implementation of operator << for a polymorphic
StockItem (from code\itemp.cpp) 710
Figure 10.26. Safe polymorphism: A polymorphic StockItem object with no date 712
Figure 10.27. Safe polymorphism: A polymorphic StockItem object with a date 713
Figure 10.28. A simplified version of the structure of a DatedStockItem object 715
Figure 10.29. Safe polymorphism: The default constructor for the polymor- phic StockItem
class (from code\itemp.cpp) 716
Figure 10.30. Safe polymorphism: A default-constructed polymorphic StockItem object
718
Figure 10.31. Safe polymorphism: The default constructor for the Undated- StockItem class
(from code\itemp.cpp) 719
Figure 10.32. Safe polymorphism: Implementing a special protected con- structor for
StockItem (from code\itemp.cpp) 721
Figure 10.33. Safe polymorphism: An example program for reference- counting with
StockItems (code\refcnt1.cpp) 724
Figure 10.34. Safe polymorphism: A normal constructor to create a Stock- Item without a date
(from code\itemp.cpp) 725
Figure 10.35. Safe polymorphism: A polymorphic StockItem object with an
UndatedStockItem worker 727
Figure 10.36. Safe polymorphism: A normal constructor that constructs a StockItem having
a date (from code\itemp.cpp) 727
Figure 10.37. Safe polymorphism: A polymorphic StockItem object with a DatedStockItem worker 728
Figure 10.38. Safe polymorphism: The copy constructor for StockItem (from
code\itemp.cpp) 728
Figure 10.39. Safe polymorphism: Two polymorphic StockItem objects sharing the same
UndatedStockItem worker object 730
Figure 10.40. Safe polymorphism: The assignment operator (operator =) for StockItem
(from code\itemp.cpp) 731
Figure 10.41. Safe polymorphism: Two polymorphic StockItem objects sharing the same
DatedStockItem worker object 733
Figure 10.42. Safe polymorphism: A polymorphic StockItem object 735 Figure 10.43. Safe
polymorphism: The destructor for the StockItem class
(from code\itemp.cpp) 735
Figure 10.44. Safe polymorphism: The destructor for the StockItem class

xxx C++: A Dialog


(from code\itemp.cpp) 740

Chapter 11: The Home Inventory Project 753

Figure 11.1. The initial interface for the HomeItem manager class (code/ hmit1.h) 758
Figure 11.2. The initial interface for the HomeItemBasic and HomeItem- Music worker classes
(code\hmiti1.h) 761
Figure 11.3. The initial test program for the HomeItem classes (code\hmtst1.cpp) 765
Figure 11.4. Results of running the first HomeItem test program (code\hmit1.out) 766
Figure 11.5. Initial implementation of HomeItem manager and worker classes
(code\hmit1.cpp) 766
Figure 11.6. HomeItem::Write (from code\hmit1.cpp) 773
Figure 11.7. The HomeItem implementation of operator >> (from code\hmit1.cpp) 774
Figure 11.8. The (incorrect) while loop in the original implementation of operator >> 777
Figure 11.9. A legal program (code\fortest.cpp) 780
Figure 11.10. An incorrect default constructor for the HomeItemBasic class (strzero.err)
781
Figure 11.11. HomeItemBasic::GetType (from code\hmit1.cpp) 782
Figure 11.12. HomeItemMusic::GetType (from code\hmit1.cpp) 782
Figure 11.13. HomeItemBasic::Write (from code\hmit1.cpp) 783
Figure 11.14. HomeItemMusic::Write (from code\hmit1.cpp) 783 Figure 11.15. The initial
HomeInventory class interface (code\hmin2.h)
787
Figure 11.16. The initial implementation of HomeInventory (code\hmin2.cpp) 788
Figure 11.17. Yet another implementation of LoadInventory (from code\hmin3.cpp) 794
Figure 11.18. The next interface for the HomeInventory class (code\hmin4.h) 797
Figure 11.19. The AddItem member function of HomeInventory (from code\hmin4.cpp) 798
Figure 11.20. The new interface for HomeItem (code\hmit4.h) 800 Figure 11.21. The
implementation of HomeItem::NewItem() (from
code\hmit4.cpp) 801
Figure 11.22. The new version of operator >> (from code\hmit4.cpp) 802 Figure 11.23.
HomeItemBasic::FormattedDisplay (from
code\hmit4.cpp) 807
Figure 11.24. HomeItemMusic::FormattedDisplay (from code\hmit4.cpp)
807
Figure 11.25. The test program for adding a HomeItem interactively (hmtst4.cpp) 809
Figure 11.26. The next version of the interface for HomeInventory (code\hmin5.h) 810
Figure 11.27. The next version of the HomeInventory test program (code\hmtst5.cpp) 811
Figure 11.28. The EditItem function of HomeInventory (from code\hmin5.cpp) 813
Figure 11.29. The latest version of the Homeitem class interface (code\hmit5.h) 814
Figure 11.30. HomeItem::Edit (from code\hmit5.cpp) 815
Figure 11.31. HomeItemBasic::CopyData() (from code\hmit5.cpp) 817 Figure 11.32. The latest
version of operator >> (from code\hmit5.cpp) 818 Figure 11.33. The latest version of the
interface for the HomeItem worker
classes (code\hmiti5.h) 821
Figure 11.34. HomeItemBasic::GetFieldName (from code\hmit5.cpp) 826
Figure 11.35. HomeItem::Read (from code\hmit5.cpp) 828
Figure 11.36. HomeItemBasic::Read (from code\hmit5.cpp) 829
Figure 11.37. HomeItemBasic::ReadInteractive (from code\hmit5.cpp) 830
Figure 11.38. HomeItemBasic::ReadFromFile (from code\hmit5.cpp) 833
Figure 11.39. HomeItemBasic::Edit (from code\hmit5.cpp) 834 Figure 11.40.
HomeItemBasic::FormattedDisplay (from
code\hmit5.cpp) 835
Figure 11.41. HomeItemBasic::EditField (from code\hmit5.cpp) 836 Figure 11.42.
HomeItemMusic::FormattedDisplay (from
code\hmit5.cpp) 839
Figure 11.43. HomeItemMusic::ReadInteractive (from code\hmit5.cpp)
842
Figure 11.44. HomeItemMusic::ReadFromFile (from code\hmit5.cpp) 843
Figure 11.45. HomeItemMusic::EditField (from code\hmit5.cpp) 843

Chapter 12: More on the Home Inventory Project 855

Figure 12.1. The new xstring class interface (code\xstring.h) 862 Figure 12.2. The
default constructor for the xstring class (from
code\xstring.h) 866
Figure 12.3. The copy constructor for the xstring class (from code\xstring.h) 866
Figure 12.4. Another constructor for the xstring class (from code\xstring.h) 867
Figure 12.5. A little test program for an early version of the xstring class (code\xstrtstc.cpp) 868
Figure 12.6. An early version of the xstring header file (code\xstringc.h) 869
Figure 12.7. An error message from mixing strings and xstrings 870 Figure 12.8. Another
version of the xstring header file (code\xstringd.h)
870
Figure 12.9. A successful attempt to mix strings and xstrings (code\xstrt- std.cpp) 871
Figure 12.10. The char* constructor for the xstring class (from code\xstring.h) 871
Figure 12.11. Another constructor for the xstring class (from code\xstring.h) 872
Figure 12.12. The final constructor for the xstring class (from code\xstring.h) 872
Figure 12.13. An illegal program (code\strzero.cpp) 873
Figure 12.14. The error message from compiling that illegal program (code\strzero.err) 873
Figure 12.15. A legal but dubious program (code\strone.cpp) 874
Figure 12.16. The final version of the xstring header file (code\xstring.h) 876
Figure 12.17. An illegal program (code\strfix.cpp) 877
Figure 12.18. The error message from strfix.cpp (code\strfix.err) 877 Figure 12.19.
Using xstring::find_nocase (code\xstrtstb.cpp) 881 Figure 12.20. The implementation of
xstring::find_nocase (from
code\xstring.cpp) 883
Figure 12.21. The less_nocase function (from code\xstring.cpp) 887 Figure 12.22. The
latest home inventory application program
(code\hmtst6.cpp) 888
Figure 12.23. The latest version of the HomeInventory interface (hmin6.h) 889
Figure 12.24. HomeInventory::FindItemByDescription (from code\hmin6.cpp) 891
Figure 12.25. The new version of the HomeItem interface (code\hmit6.h) 892
Figure 12.26. HomeItemBasic::IsNull (from code\hmit6.cpp) 894

Chapter 13: Analyzing the Home Inventory Project 911

Figure 13.1. The main() function of the final version of the home inventory main program
(from code\hmtst8.cpp) 913
Figure 13.2. The MenuItem enum (from code\hmtst8.cpp) 915
Figure 13.3. The GetMenuChoice function (from code\hmtst8.cpp) 915 Figure 13.4.
ExecuteMenuChoice (from code\hmtst8.cpp) 918
Figure 13.5. The HomeUtility interface (code\hmutil1.h) 925 Figure 13.6. Not
declaring a default argument (code\nodef.h) 928 Figure 13.7. Not using a default
argument (code\nodef.cpp) 928 Figure 13.8. Declaring a default argument
(code\default.h) 929 Figure 13.9. Using a default argument (code\nodef.cpp) 929
Figure 13.10. HomeUtility::ReadDoubleFromLine (from
code\hmutil1.cpp) 930
Figure 13.11. HomeUtility::ReadLongFromLine (from code\hmutil1.cpp)
931
Figure 13.12. HomeUtility::ReadDateFromLine (from code\hmutil1.cpp)
932
Figure 13.13. HomeUtility::IgnoreTillCR (from code\hmutil1.cpp) 933 Figure 13.14.
HomeUtility::HandleError (from code\hmutil1.cpp) 934 Figure 13.15.
HomeUtility::CheckNumericInput (from
code\hmutil1.cpp) 935
Figure 13.16. HomeUtility::GetNumberOrEnter (from code\hmutil1.cpp) 938
Figure 13.17. HomeUtility::ClearRestOfScreen (from code\hmutil1.cpp) 947
Figure 13.18. The HomeUtility::SelectItem function (from code\hmutil1.cpp) 949
Figure 13.19. The latest header file for the HomeInventory class (code\hmin8.h) 955
Figure 13.20. The latest version of HomeInventory::AddItem (from code\hmin8.cpp) 957
Figure 13.21. The latest version of HomeInventory::EditItem (from code\hmin8.cpp) 958
Figure 13.22. The latest version of HomeInventory::LocateItemByDe- scription (from
code\hmin8.cpp) 959
Figure 13.23. HomeInventory::LocateItemByCategory (from code\hmin8.cpp) 960
Figure 13.24. The HomeInventory::PrintNames function (from code\hmin8.cpp) 961
Figure 13.25. The HomeInventory::PrintAll function (from code\hmin8.cpp) 962
Figure 13.26. The HomeInventory::StoreInventory function (from code\hmin8.cpp) 963
Figure 13.27. The HomeInventory::DisplayItem function (from code\hmin8.cpp) 964
Figure 13.28. The HomeInventory::SortInventoryByName function (from
code\hmin8.cpp) 965
Figure 13.29. The HomeInventory::SelectItemByPartialName function (from
code\hmin8.cpp) 966
Figure 13.30. The HomeInventory::SelectItemFromNameList function (from code\hmin8.cpp)
968
Figure 13.31. The HomeInventory::SelectItemFromCategoryList func- tion (from
code\hmin8.cpp) 969
Figure 13.32. The HomeInventory::DeleteItem function (from code\hmin8.cpp) 973
Figure 13.33. The new operator >> implementation for the HomeItem classes (from
code\hmit8.cpp) 974
Figure 13.34. The latest version of HomeItemBasic::Edit (from
code\hmit8.cpp) 976
Figure 13.35. The latest version of HomeItemBasic::ReadInteractive (from
code\hmit8.cpp) 977
Figure 13.36. The latest version of the HomeItemBasic::EditItem func- tion (from
code\hmit8.cpp) 979
Figure 13.37. The latest version of HomeItemMusic::ReadInteractive (from
code\hmit8.cpp) 980
Figure 13.38. The latest version of HomeItemMusic::EditField (from code\hmit8.cpp) 982

xxxvi C++: A Dialog


The fundamental problem with most technical books is that the authors
are just too smart for their readers’ good. By the time one becomes an
expert at a highly technical field, such as C++ programming, one has
usually lost the knack of identifying with the poor struggling newbie.
Also — it has to be said — most techies are not very good writers.
Fortunately, the most able 10% of programmers also tend to be
rather good writers — and my old friend Steve Heller is well up in
both categories. He’s also clever enough to know that he’s not really
very good at thinking like a programming newbie, and to go find help
from people who are.
But the book you’re holding in your hands isn’t just clever; it has a
special, serendipitous magic that makes it one of the most remarkable
technical books I’ve ever seen. Because Steve found a writing partner
whose intelligence matched his own, and whose eager, questioning
ignorance was the perfect complement of his seasoned expertise.
This book, besides being an omnibus of the books Steve and Susan
have written together, is also the story of the meeting of two
very remarkable minds. Their continuing dialogue gives it a human
texture that is sadly absent from most technical tutorials. They bring
out the best in each other; Steve continually challenges Susan to think
and learn and grow, and Susan draws Steve out of the high fastnesses
of abstraction into generating examples and analogies that are
grounded in everyday experience.
The result is a lovely fugue in two voices that weaves together
many themes. Steve’s unfolding of the mysteries of C++ intertwines
with Susan’s developing understanding of the language. Their growing
affection and respect for each other complements the tale of bytes and
silicon as they explore the inner world of the computer together. Susan
discovers what she did not know, and Steve re- discovers what he
does. Both processes are a pleasure to behold.
How many introductions to programming are there that are also
love stories? This may well be the only one. I won’t spoil the ending
for you — but I will say that if nothing else, Steve and Susan’s
relationship has produced work that is not merely top-caliber
technical instruction but arguably art of a high and subtle order.
It is an honor to know these two human beings, and a privilege to
introduce their book. I hope you find that their journey of discovery
guides you on one of your own.

Eric S. Raymond

xxxviii C++: A Dialog


Is this book for you? If you’re a programmer in a language other than
C++ and you want to upgrade your skills, then you shouldn’t have
much difficulty figuring that out for yourself by reading a few pages.
But what if you have no previous programming experience? In that
case, here’s a little quiz that may help you decide:
1. Do you want to know how the programs in your computer work inside
and how to write some of your own?
2. Are you willing to exert yourself mentally to learn a complex
technical subject?
3. Do you have a sense of humor?

If you’ve answered yes to these questions and follow through with the
effort required, then you will get a lot out of this book.
The common wisdom states that programming is a difficult subject
that should be reserved for a small number of specialists. One of the
main reasons that I have written this book is that I believe this attitude
is wrong; it is possible, and even desirable, for you to learn how
programs work and how to write them. Those who don’t
understand how computers perform their seemingly magical feats are
at an ever-increasing disadvantage in a society ever more dependent
on these extraordinary machines.
Regardless of the topic, I can see no valid reason for a book to be
stuffy and dry, and I’ve done everything possible to make this one
approachable. However, don’t let the casual tone fool you into
thinking that the subject is easy; there is no royal road to programming,
any more than there is to geometry. Especially if you have no prior
experience in programming, C++ will stretch your mind more than
virtually any other area of study.
Assuming that you want to learn C++, why should you read this
book rather than any of dozens of other introductory C++ books? One
difference between this book and other introductory books is that many
of them still don’t use the C++ standard library1, a very important
part of the C++ language definition. We’ll make use of some of the
features of the standard library in this book, to get you started on
learning this large and complex part of the C++ language.
However, we certainly won’t cover it in its entirety; that would
require much more room that we can devote to it here. In fact, many
books can be and have been written about the standard library alone,
although I don’t know of any that would be suitable for novices to
programming.
But there is one ingredient that makes this book unique: the
participation of a real, live person who didn’t already know the
material before reading it, namely Susan Heller, my wife.2 Her main
contribution has been to read every line of the book, starting with the
first draft, and to ask questions via e-mail about anything she didn’t
understand. I answered her questions, also by e-mail, until both of us
were satisfied that she understood the material in question and that
1. A “library”, in computer programming terms, is a collection of programs or
parts of programs that have already been written so you don’t have to write
them yourself.
2. To avoid confusion, I should explain that we were not married when we
started working on the first edition of this book, which was called Who’s Afraid
of C++? In fact, we met as a result of working on that book.

xl C++: A Dialog
the text was clear. After the text was otherwise complete, I extracted
appropriate parts of the e-mail exchanges, edited them for spelling,
punctuation, and so forth, and included them in the text where they will
be most useful to the reader.
For this latest version of the book, we have discussed the changes
caused by the adoption of the standard library. As a result of our
discussions, I have added to and modified the existing e-mail
conversations as appropriate so that they make sense in the context of
those changes. However, Susan still has the final say on what goes in
her side of these conversations, so they are still authentic
conversations.
Of course, these exchanges do take up room in the book that might
otherwise be filled with more information about C++ and
programming. Therefore, if you want to get the absolute maximum of
new information per page, you might want to select another book such
as Bjarne Stroustrup’s excellent The C++ Programming Language
(ISBN 0-201-88954-4), or perhaps Accelerated C++, by Andrew
Koenig and Barbara Moo (ISBN 0-201-70353-X). However, the vast
majority of comments I’ve received from readers of my other books for
beginners have indicated that they found my approach very helpful,
and I suspect that most readers of this book will feel the same.
Susan has written an account of part of her involvement in this
project, which immediately follows this Preface. I recommend that
you read that account before continuing with the technical material
following it, as it explains how and why she has contributed to making
your task easier and more enjoyable.
Speaking of Susan, here is a bit of correspondence between us on
the topic of how one should read this book, which occurred after her
first reading of what is now Chapter 2, “Hardware Fundamentals”,
and Chapter 3, “Basics of Programming”.

Susan: Let me say this: to feel like I would truly understand it, I would really need
to study this about two more times. Now, I could
do this, but I am not sure you would want me to do so. I think reading a chapter once
is enough for most people.

Steve: As a matter of fact, I would expect the reader of my book to read and
study these chapters several times if necessary; for someone completely new to
programming, I imagine that it would be necessary. Programming is one of the most
complex human disciplines, although it doesn’t take the mathematical skills of a
subject such as nuclear physics, for example.3 I’ve tried to make my explanations as
simple as possible, but there’s no way to learn programming (or any other complex
subject) without investing a significant amount of work and thought.

After she had gone through the text a number of times and had learned
a lot from the process, we continued this discussion as follows:

Susan: Well then, maybe this should be pointed out in a Preface or something. Of
course, it would eventually be obvious to the reader as it was to me, but it took me a
while to come to that conclusion. The advantage of knowing this in advance is that
maybe I would not be so discouraged that I was not brilliant after one read of a
chapter.

Steve: I will indeed mention in the preface that the reader shouldn’t be fooled by
the casual tone into thinking that this is going to be a walk in the park. In any event,
please don’t be discouraged. It seems to me that you have absorbed a fair amount of
very technical material with no previous background; that’s something to be proud
of!

3. Or deciphering a long distance telephone bill.


We’ll be hearing from Susan many more times in the course of the
book. She will be checking in frequently in the form of extracts from
the e-mail discussion we engaged in during the testing and revising
process. I hope you will find her comments and my replies add a
personal touch to your study of this technical material.
While we’re on the topic of your studying, this would be a good
time to tell you how to get updates and help with any errors you might
find in the book or with any other questions you might have. The best
way is to visit my WWW site, steveheller.com. My e-mail address is
[email protected].
In the event that you enjoy this book and would like to tell others
about it, you might want to write an online review on Amazon.com,
which you can do by visiting my home page and following the links to
the “customer reviews” on Amazon.
I should also tell you how the various typefaces are used in the
book. Helvetica is used for program listings, for terms used in
programs, and for words defined in the C++ language. Italics are used
primarily for technical terms that are found in the glossary, although
they are also used for emphasis in some places. The first time that a
particular technical term is used, it is in bold face; if it is a term
defined in the C++ language, it will be in bold Helvetica .4
Please note that I have used a consistent Helvetica font for words
like “ classes ” and other plurals of C++ terms, rather than putting the
plural ending in a different font, e.g., “ class es”, because I find frequent
changes of font inside individual words distracting. There are no
plural terms in C++ itself. If you remember that, you won’t make the
mistake of thinking that “ classes ” is a C++ term, when the term is
really “ class ”. If you do make such a mistake, you’ll quickly

4. Those of you who have read other programming books that use monospace
fonts for code examples may find the use of a proportional-width font peculiar.
However, I am following the example of Bjarne Stroustrup’s The C++
Programming Language, in which he points out that “[P]roportional- width
fonts are generally regarded as better than constant-width fonts for
presentation of text.” (p. 5)
find out about it because you’ll get an error message when you try to
use the term incorrectly in your program.
Now that those preliminaries are out of the way, let’s proceed. The
next voice you will hear is that of Susan, my test reader. I hope you get
as much out of her participation in this book as I have.
I’d like to thank all the readers of my previous books who have
written to me with praise and sometimes with corrections. Of course, I
appreciate the former very much, but I appreciate the latter as well;
every correction a reader sends me makes the next printing or edition
of the book that much better.
My primary technical reviewer, Donovan Rebbechi, found a
number of ways to improve my code and my explanations thereof.
My copy editor, Vivian Clark, has the rare facility of correcting
errors without interfering with my style in the process. Working with
her, despite a few technical glitches, was a great improvement over
my previous experience with this phase of book production.
Finally, I’d also like to thank my mother, Sali Heller Neff, and
father, the late Leonard Heller. They had that rare but invaluable
characteristic among parents: the courage and patience to let me do it
my way.
One day in March, 1995 I found myself reading this little message on
my computer monitor:

[#: 288196 S10/Have You Heard?] [25-Mar-95


20:34:55]
[Sb: Readers for my new book] [Fm: Steve
Heller 71101,1702] [To: all]

Hi!
I’m looking for some readers for a book I’m working on, which teaches people how to
program, using C++ as the language. The ideal candidate is someone who wants to learn how
to program, but has little or no knowledge or experience in programming. I can’t pay anything,
but participants will learn how to program, and will be acknowledged in the book, as well as
getting an autographed copy. If you’re interested, please reply by e-mail.

Steve
As I considered my response to this message I felt a little trepidation
for what was to come. I have known only one profession: nursing. I
had owned and used a computer for only a little over two years at the
time and thought DOS was too difficult to understand. Not only had I
no prior knowledge of programming, I had very little knowledge of
computers in general. Yet, what knowledge I did have of the computer
fed my curiosity for more, and as my love of the computer grew, it
soon was apparent I had no choice. I replied by e-mail, waited
impatiently for several days, and then...

I jumped to the sound of an ominous knock at my front door. A sense of foreboding


heightened with every step I made toward the sound. Cracking it open, I saw dark
bluish-grey clouds hanging low overhead, threatening to unleash their contents at any
moment. The tenseness of the postal worker’s expression changed suddenly to an
implacable smile as he quickly slipped a large envelope into my unsuspecting hands.
Almost as soon as I realized I held the envelope, I looked up again to find the
postman gone. The sky began to swirl, though there was only a hint of a breeze on
my face. A slow shudder engulfed me as I quickly locked the door.

Half dazed, I was left standing at my doorway, staring at my name on the envelope,
written in a handwriting nearly identical to my own. Confused, but with a penetrating
sense of courage, I tore open the mysterious yellow mailer. It in turn seemed to be the
catalyst for the rupturing of the sky, and my roof and heart pounded in unison. As the
contents of the envelope spilled out on my lap, I caught a glimpse of the words
“Who’s Afraid of C++?” on the looseleaf manuscript. I was briefly frozen by a
paralyzing shiver that gripped my body, as I began to wonder what would become of
me. What did this mean? What was C++ and why should I be afraid of it? What
was I getting myself into?

And one more question, most mysterious of all: Who is Steve Heller?

xlviii C++: A Dialog


Never in my wildest dreams would I have ever expected that I would
end up reading every page of this book with utmost attention to detail,
leaving no word unturned, no concept overlooked, no hair on my head
left unpulled for the next nine months of my life. But, if we were going
to make this book as clear as possible for anyone who wanted to read
it, there was no other way.
The process of writing this book was an enormous effort on both
our parts. Neither Steve nor I could have ever imagined the type of
dialogue that would ensue. It just happened; as I asked the questions,
he answered the questions and I asked again. The exchange would
continue until we were both satisfied that I “had it”. When that
happened, Steve knew the right wording for the book, and I could
move on to the next concept. It was an experience like no other in my
life. It was a time filled with confusion, frustration, anger, acceptance,
understanding, and joy. The process often gave cause for others to
question my motivation for doing it. Admittedly, at times my
frustration was such that I wanted to give up. But it became my
mountain, and I had to climb it. I would not accept defeat.
The material was not the only source of my frustration. There was
the inherent difficulty that Steve and I had in keeping communication
flowing and speaking the same language. We found we had very
different writing styles, which added another obstacle to our
undertaking. He, the ultimate professional, and I, the incorrigible
misfit, finally managed a happy medium. We corresponded on a daily
basis, almost exclusively by e-mail. Through that medium it was our
challenge to get into each other’s minds. He had to learn how I thought
and I had to learn how he thought — not an easy task for an expert and
a novice who were total strangers.
What you are about to read is the refinement of the writings of the
mind of a genius filtered through the mind of a novice. Ideally, the
result of this combination has produced the best possible information,
written in the most understandable form. The book as you see it is
considerably different from the original version, as a result of this
filtering process. For that same reason, it is also different from any
other book of its kind. To our knowledge, this is the first time that
someone with no previous background in programming has been
enlisted in the creation of such a book. Of course, it took tutoring, but
that tutoring is what led to the simplification of the text so that future
novices could enjoy a relatively pain-free reading experience.
During the first few months of this process, I had to take it on faith
that somehow all of this material would eventually make sense. But
late one quiet night while first reading about object-oriented
programming (the creation of “classes”), I was abruptly shaken by the
most incredible feeling. I was held spellbound for a few moments, I
gasped and was gripped with the sudden realization and awe of the
profound beauty of the code. I had finally caught a glimpse of what
programming was really all about. This experience later led me to
write the following in a letter to my sister:
Programming is SOOOO gorgeous. So fine, so delicate. It is so beautifully spun with
silk threads, tiny and intricate. Yet like silk it can be strong and powerful, it all
depends on how you use it.

At last, I had “gotten it”. Within these pages the beauty lies dormant,
waiting to be viewed only when the work has been done and the
mountain scaled by those who embark on the task of learning. It is an
exquisite panorama and a most worthy journey.

Further Adventures in C++

That was in 1995. As I write this years later, I have gained further
insight into the art of programming. When Steve Heller asked me to
help with the writing of the second part of this book, originally
published as Who’s Afraid of More C++?, I felt that shiver down my
back again. I groaned, knowing full well this time what I was getting
myself into and wondering if I had the stamina to do it again.
Well, I did and we had another book. I also have the answers to
the questions that I asked myself those years ago.
I have learned what C++ is. It is a computer programming
language. It is an immense language, it is a complicated language,

l C++: A Dialog
and it is a very powerful language. I learned through the writing of this
book that C++ can be molded and shaped to do just about anything you
want it to do. It convolutes, twists and turns corners. You can play hide
and seek with it. Yet, in the hands of an expert, it is amazing to see it
come to life.
As Steve and I wrote this book, I became more than a test reader, I
also became a usability tester. Through this role I saw just how
complicated writing a program is. I had already seen all the source
code for the home inventory program that Steve wrote for this book,
and was amazed to see just this one little screen of words to show for
all our efforts. I knew what was underneath that screen. I knew all the
hours, the false starts, the redos, the polishing that it took to get to
where we were in the program, and I could not believe that so little
showed for our efforts.
Just when Steve thought he was done with it, I was quick to inform
him that indeed he was not. It didn’t take me long to “break” the
program, causing more redos. Then there were things I wanted in the
program to make it just a little easier, or a just little prettier. Back
Steve went to the compiler. Actually, I think this was as much of a
lesson on software design as it was on “more C++”. There is so much
to think about when writing a program; you have to program not only
what you want it to do, but also what you don’t want it to do.
I can’t say that there is no reason to fear C++. It is a difficult thing
to learn, but no more so than any other language, including human
spoken ones. It takes interest, work, and practice. But I think that as
with any difficult subject, it can be mastered with the right learning
tools and a competent teacher. I believe Steve to be a natural teacher
and in this book, he has created an excellent learning tool.
As for the other question, Who is Steve Heller?: he is now my
husband. Even after all the trouble he caused me with C++, I figured
anyone who has the same handwriting as I do must be my soulmate.

Susan Patricia Caffee Heller


Sulphur Springs, Texas
Introduction to Programming

“Begin at the beginning, and go on till you come to the end: then stop.”
This method of telling a story is as good today as it was when the King
of Hearts prescribed it to the White Rabbit. In this book, we must
begin with you, the reader, since my job is to explain a technical
subject to you. It might appear that I’m at a severe disadvantage; after
all, I’ve never met you.
Nevertheless, I can make some pretty good guesses about you. You
almost certainly own a computer and know how to use its most
common application, browsing the World Wide Web. If you use the
computer in business, you probably also have an acquaintance with
spreadsheets and word processors and perhaps some database
experience as well. Now you have decided to learn how to program
the computer yourself rather than relying completely on programs
written by others. On the other hand, you might be a student using this
book as a text in an introductory course on programming. In that case,
you’ll be happy to know that this book isn’t written in the dry, overly
academic style employed by many textbook writers. I hope that you
will enjoy reading it as much as my previous readers have.
Whether you are using this book on your own or in school, there
are many good reasons to learn how to program. You may have a
problem that hasn’t been solved by commercial software, you may
want a better understanding of how commercial programs function so
you can figure out how to get around their shortcomings and
peculiarities, or perhaps you’re just curious about how computers
perform their seemingly magical feats. Whatever the initial reason, I
hope you come to appreciate the great creative possibilities opened up
by this most ubiquitous of modern inventions.1
Before we begin, however, we should agree on definitions for
some fundamental words in the computing field. Susan had some
incisive observations about the power of words. Here is our exchange
on that issue:

Susan: I will read something usually at face value, but often there is much more
to it; that is why I don’t get it. Then, when I go back and really think about what
those words mean, it will make more sense. This book almost needs to be written in
ALL CAPS to get the novice to pay closer attention to each and every word.

Steve: IMAGINE WRITING A BOOK IN ALL CAPS! THAT WOULD BE


VERY DIFFICULT TO READ, DON’T YOU THINK?

Many of the technical words used in this book are defined in the
glossary at the end of the book. It is also very helpful to have a good
dictionary of computer terms, as well as a good English dictionary.
Of course, you may not be able to remember all of these technical
definitions the first time through. If you can’t recall the exact meaning
of one of these terms, just look up the word or phrase in the index or in
the glossary.

1. Of course, it’s also possible that you already know how to program in another
language and you’re using this book to learn how to do so in C++. If so, you’ll
have a head start; I hope that you’ll learn enough to repay the effort of wading
through some material you already know.
Before we continue, let’s check in again with Susan. The following
is from her first letter to me about the contents of this book:

Susan: I like the one-on-one feel of your text, like you are talking just to me. Now,
you did make a few references to how simple some things were which I didn’t catch
on to, so it kinda made me feel I was not too bright for not seeing how apparently
simple those things were...

I think maybe it would have been helpful if you could have stated from the onset of
this book just what direction you were taking, at least chapter by chapter. I would
have liked to have seen a goal stated or a least a summary of objectives from the
beginning. I often would have the feeling I was just suddenly thrown into something
as I was reading along. Also (maybe you should call this C++ for Dummies, or is
that taken already?)2, you might even define what programming is! What a concept!
Because it did occur to me that since I have never seen it done, I really don’t know
what programming is! I just knew it was something that nerds do.

Susan’s wish is my command, so I have provided a list of objectives


at the beginning of each chapter after this one. I’ve also fulfilled her
request for a definition of some programming terms.

1.1. Definitions

An algorithm is a set of precisely defined steps to calculate an answer


to a problem or set of problems, and which is guaranteed to arrive at
such an answer eventually. As this implies, a set of steps that
might never end is not, strictly speaking, an algorithm.3

2. As it happens, that title is indeed taken. However, I’m not sure it’s been
applied appropriately, since that book assumes previous knowledge of C! What
that says about C programmers is better left to the imagination.
Programming is the art and science of solving problems by the
following procedure:4
1. Find or invent a general solution to a class of problems.
2. Express this solution as an algorithm or set of algorithms.
3. Translate the algorithm(s) into terms so simple that a stupid machine
like a computer can follow them to calculate the specific answer
for any specific problem in the class.

At this point, let’s see what Susan had to say about the above
definition and my response to her.

Susan: Very descriptive. How about this definition: Programming is the process of
being creative using the tools of science such as incremental problem solving to
make a stupid computer do what you want it to. That I understand!

Your definition is just fine. A definition has to be concise and descriptive, and that
you have done — and covered all the bases. But you know what is lacking? An
example of what it looks like. Maybe just a little statement that really looks bizarre to
me, and then say that by the end of the chapter you, the reader, will actually know
what this stuff really means! Sort of like a coming attraction type of thing.

Steve: I understand the idea of trying to draw the reader into the “game”.
However, I think that presenting a bunch of apparent
3. I should note that there is some disagreement on this fine point of definition.
Some people consider a procedure to be an algorithm even if it may never end.
4. This definition is possibly somewhat misleading since it implies that the
development of a program is straightforward and linear, with no revisions
required. This is known as the “waterfall model” of programming, since water
going over a waterfall follows a preordained course in one direction. However,
real-life programming doesn’t usually work this way; rather, most programs are
written in an incremental process as assumptions are changed and errors are
found and corrected, as we’ll see in Chapters 11 and 12.
gibberish with no warning could frighten readers as easily as it might intrigue them. I
think it’s better to delay showing examples until they have some background.

Now let’s return to our list of definitions:

Hardware refers to the physical components of a computer, the ones


you can touch. Examples include the keyboard, the monitor, the printer.

Software refers to the nonphysical components of a computer, the


ones you cannot touch. If you can install it on your hard disk, it’s
software. Examples include a spreadsheet, a word processor, a
database program.

Source code is a program in a form suitable for reading and writing


by a human being.

An executable program (or an executable, for short) is a program in


a form suitable for running on a computer.

Object code is a portion of a program in a form suitable for


incorporation into an executable program.

Compilation is the process of translating source code into object


code. Almost all of the software on your computer was created by this
process.

A compiler is a program that performs compilation as defined above.

How to Write a Program

Now you have a definition of programming. Unfortunately, however,


this doesn’t tell you how to write a program. The process of solving a
problem by programming in C++ follows these steps:
1. Problem: After discussions between the user and the programmer, the
programmer defines the problem precisely.
2. Algorithms: The programmer finds and/or creates algorithms that
will solve the problem.
3. C++: The programmer implements these algorithms as source
code in C++.
4. Executable: The programmer runs the C++ compiler, which must
already be present on the programmer’s machine, to translate the
source code into an executable program.
5. Hardware: The user runs the resulting executable program on a
computer.

These steps advance from the most abstract to the most concrete,
which is perfectly appropriate for an experienced C++ programmer.
However, if you’re using this book to learn how to program in C++,
obviously you’re not an experienced C++ programmer, so before you
can follow this path to solving a problem you’re going to need a fairly
thorough grounding in all of these steps.
This description is actually a bit oversimplified, as we’ll see in
the discussion of linking in Chapter 5, “Functional Literacy”. For
now, let’s see what Susan thinks about this issue.

Susan: With all the new concepts and all the new language and terms, it is so hard
to know what one thing has to do with the other and where things are supposed to fit
into the big picture. Anyway, you have to understand; for someone like me, this is an
enormous amount of new material to be introduced to all at once. When you are
bombarded with so many new terms and so many abstract concepts, it is a little hard
to sort out what is what. Will you have guidelines for each of the steps? Since I
know a little about this already, the more I look at the steps, I just know that what is
coming is going to be a big deal. For example, take step 1; you have to give the
ingredients for properly defining a problem. If something is left out, then everything
that follows won’t work.
Steve: I hope you won’t find it that frustrating, because I explain all of the steps
carefully as I do them. Of course, it’s possible that I haven’t been careful enough,
but in that case you can let me know and I’ll explain it further.

Unfortunately, it’s not possible for me to provide a thorough guide to all of those
steps, as that would be a series of books in itself. However, there’s a wonderful
small book called How to Solve It, by
G. Polya, that you should be able to get at your local library. It was written to help
students solve geometry problems, but the techniques are applicable in areas other
than geometry. I’m going to recommend that readers of my book read it if they have
any trouble with general problem solving.

The steps for solving a problem via programming might sound


reasonable in the abstract, but that doesn’t mean that you can follow
them easily without practice. Assuming that you already have a pretty
good idea of what the problem is that you’re trying to solve, the
algorithms step is likely to be the biggest stumbling block. Therefore,
it might be very helpful to go into that step in a bit more detail.

1.2. Baby Steps

If we already understand the problem we’re going to solve, the next


step is to figure out a plan of attack which we will then break down
into steps small enough that they can be expressed in C++. This is
called stepwise refinement, since we start out with a “coarse”
solution and refine it until the steps are within the capability of the
C++ language. For a complex problem, this may take several
intermediate steps; but let’s start out with a simple example. Say that
we want to know how much older one person is than another. We
might start with the following general outline:
1. Get two ages to be compared.
2. Calculate difference of ages.
3. Display the result.

This in turn can be broken down further, as follows:


1. Get two ages to be compared.
a. Ask user for first age.
b. Ask user for second age.
2. Subtract second age from first age.
3. Display result.

This looks okay, except that if the first person is younger than the
second one, the result will be negative. That may be acceptable. If so,
we’re just about done, since these steps are simple enough for us to
translate them into C++ fairly directly. Otherwise, we’ll have to
modify our program to do something different, depending on which
age is higher. For example:
1. Get two ages to be compared.
a. Ask user for first age.
b. Ask user for second age.
2. Compute difference of ages.
a. If first age is greater than second, subtract second age from first
age.
b. Otherwise, subtract first age from second age.
3. Display result.

You’ve probably noticed that this is a much more detailed description


than would be needed to tell a human being what you want to do.
That’s because the computer is extremely stupid and literal: it does
only what you tell it to do, not what you meant to tell it to do.5
Unfortunately, it’s very easy to get one of the steps wrong, especially
in a complex program. In that case, the computer will do something
ridiculous and you’ll have to figure out what you did wrong. This
debugging, as it’s called, is one of the hardest parts of programming.
Actually, it shouldn’t be too difficult to understand why that is the
case; after all, you’re looking for a mistake you’ve made yourself. If
you knew exactly what you were doing, you wouldn’t have made the
mistake in the first place.6
I hope that this brief discussion has made the process of
programming a little less mysterious. In the final analysis, it’s
basically just logical thinking.7

On with the Show

Now that you have some idea how programming works, it’s time to
see exactly how the computer actually performs the steps in a
program. This is the topic of Chapter 2, “Hardware Fundamentals”.

5. In case you would like to see a C++ program that performs this calculation,
I’ve written one. It’s called “ages.cpp”, and can be found in the code directory
mentioned in the compiler installation instructions on the CD.
6. For this reason, one of the best ways to debug a program that is giving you
trouble is to explain it in great detail to someone else. If you don’t see the bug
yourself, the other person almost certainly will find it for you.
7. Of course, the word just in this sentence is a bit misleading; taking logical
thinking for granted is a sure recipe for trouble.
CHAPTER 2 Hardware Fundamentals

Like any complex tool, the computer can be understood on several


levels. For example, it’s entirely possible to learn to drive an
automobile without having the slightest idea how it works. The
analogy with computers is that it’s relatively easy to learn how to use
a Web browser without having any notion of how such programs work.
On the other hand, programming is much more closely analogous to
designing an automobile than it is to driving one. Therefore, we’re
going to have to go into some detail about the internal workings of a
computer, not at the level of electronic components, but at the lowest
level accessible to a programmer.
This is a book on learning to program in C++, not on how a
computer works.1 Therefore, it might seem better to start there and
eliminate this detour, and indeed many (perhaps most) books on C++
do exactly that. However, in working out in detail how I’m going to
explain C++ to you, I’ve come to the conclusion that it would be

1. For a really in-depth (or at least very funny) explanation of how computers
work, you might want to take a look at Dave Barry in Cyberspace (ISBN 0-
517-59575-3).
virtually impossible to explain why certain features of the language
exist and how they actually work, without your understanding how they
relate to the underlying computer hardware.
I haven’t come to this position by pure logical deduction, either. In
fact, I’ve worked backward from the concepts that you will need to
know to program in C++ to the specific underlying information that
you will have to understand first. I’m thinking in particular of one
specific concept, the pointer, that is supposed to be extremely difficult
for a beginning programmer in C++ to grasp. With the approach we’re
taking, you shouldn’t have much trouble understanding this concept by
the time you get to it in Chapter 7, “Creating a Homegrown string
class”. I’d be interested to know how you find my explanation, given
the background that you’ll have by that point. Don’t hesitate to e-mail
me about this topic (or any other, for that matter).
On the other hand, if you’re an experienced programmer a lot of
this will be just review for you. Nonetheless, it can’t hurt to go over
the basics one more time before diving into the ideas and techniques
that make C++ different from other languages.2
Now let’s begin with
some definitions and objectives.

2.1. Definitions

A digit is one of the characters used in any positional numbering


system to represent all numbers starting at 0 and ending at one less
than the base of the numbering system. In the decimal system, there are
ten digits, 0 –9, and in the hexadecimal system there are sixteen digits,
0–9 and a– f.

2. Some people believe that you should learn C before you learn C++. Obviously,
I’m not one of those people; for that matter, neither is the inventor of C++,
Bjarne Stroustrup. On page 169 of his book, The Design and Evolution of
C++, he says “Learn C++ first. The C subset is easier to learn for C/C++
novices and easier to use than C itself.”
Objectives of This Chapter

A binary number system is one that uses only two digits, 0 and 1.

A hexadecimal number system is one that uses sixteen digits, 0 –9 and


a–f.

CPU is an abbreviation for central processing unit. This is the


“active” part of your computer, which executes all the machine
instructions that make the computer do useful work.

A machine instruction is one of the fundamental operations that a


CPU can perform. Some examples of these operations are addition,
subtraction, or other arithmetic operations; other possibilities include
operations that control what instruction will be executed next. All C++
programs must be converted into machine instructions by a compiler
before they can be executed by the CPU.

An assembly language program is the human-readable representation


of a set of machine instructions; each assembly language statement
corresponds to one machine instruction. By contrast, a C++ program
consists of much higher-level operations which cannot be directly
translated one-for-one into machine instructions.

2.2. Objectives of This Chapter

By the end of this chapter, you should


• Understand the programmer’s view of the most important pieces of
software in your computer.
• Understand the programmer’s view of the most important pieces of
hardware in your computer.
• Understand how whole numbers are stored in the computer.
2.3. Inside the Box

First we’ll need to expand on the definition of hardware. As noted


earlier, hardware means the physical components of a computer, the
ones you can touch.3 Examples are the monitor, which displays your
data while you’re looking at it, the keyboard, the printer, and all of the
interesting electronic and electromechanical components inside the
case of your computer.4 Right now, we’re concerned with the
programmer’s view of the hardware. The hardware components of a
computer with which you’ll be primarily concerned are the disk, RAM
(Random Access Memory), and last but certainly not least, the CPU.5
We’ll take up each of these topics in turn.

Disk

When you sit down at your computer in the morning, before you turn it
on, where are the programs you’re going to run? To make this more
specific, suppose you’re going to use a word processor to revise a
letter you wrote yesterday before you turned the computer off. Where is
the letter, and where is the word processing program?
3. Whenever I refer to a computer, I mean a modern microcomputer capable of
running MS-DOS® or some version of Windows®; these are commonly referred
to as PCs. Most of the fundamental concepts are the same in other kinds of
computers, but the details differ.
4. Although it’s entirely possible to program without ever seeing the inside of a
computer, you might want to look in there anyway, just to see what the CPU,
RAM chips, disk drives, and other components, look like. Some familiarization
with the components will give you a head start if you ever want to expand the
capacity of your machine.
By the way, Susan recommends that you clean out the dust bunnies with a
computer vacuum cleaner while you are in there; it’s amazing how much dust
can accumulate inside a computer case in a year or two!
5. Other hardware components can be important to programmers of specialized
applications; for example, game programmers need extremely fine control of
how information is displayed on the monitor. However, we have enough to keep
us busy learning how to write general data-handling programs. You can always
learn how to write games later, if you’re interested in doing so.
You probably know the answer to this question; they are stored on
a disk inside the case of your computer. Technically, this is a hard
disk, to differentiate it from a floppy disk, the removable storage
medium often used to distribute software or transfer files from one
computer to another.6 Disks use magnetic recording media, much like
the material used to record speech and music on cassette tapes, to
store information in a way that will not be lost when power is turned
off. How exactly is this information (which may be either executable
programs or data such as word processing documents) stored?
W e don’t have to go into excruciating detail on the storage
mechanism, but it is important to understand some of its
characteristics. A disk consists of one or more circular platters, which
are extremely flat and smooth pieces of metal or glass covered with a
material that can be very rapidly and accurately magnetized in either
of two directions, “north” or “south”. To store large amounts of data,
each platter is divided into many billions of small regions, each of
which can be magnetized in either direction, independent of the other
regions. The magnetization is detected and modified by recording
heads, similar in principle to those used in tape cassette decks.
However, in contrast to the cassette heads, which make contact with
the tape while they are recording or playing back music or speech, the
disk heads “fly” a few millionths of an inch away from the platters,
which rotate at very high velocity in a vacuum.7

6. Although at one time, many small computers used floppy disks for their main
storage, the tremendous decrease in hard disk prices means that today even the
most inexpensive computer stores programs and data on a hard disk.
7. The heads have to be as close as possible to the platters because the influence
of a magnet (called the magnetic field) drops off very rapidly with distance.
Thus, the closer the heads are, the more powerful the magnetic field is and the
smaller the region that can be used to store data so that it can be retrieved
reliably, and therefore the more data that can be stored in the same physical
space. Of course, this leaves open the question of why the heads aren’t in
contact with the surface; that would certainly solve the problem of being too far
away. Unfortunately, this seemingly simple solution would not work at all. There
is a name for the contact of heads and disk surface while the disk is spinning,
head crash. The friction caused by such an event destroys both the heads and
disk surface almost instantly.
The separately magnetizable regions used to store information are
arranged in groups called sectors, which are in turn arranged in
concentric circles called tracks. All tracks on one side of a given
platter (a recording surface) can be accessed by a recording head
dedicated to that recording surface; each sector is used to store some
number of bytes of the data, generally a few hundred to a few
thousand. Byte is a coined word meaning a group of binary digits, or
bits for short; there are 8 bits in a byte in just about every modern
general-purpose computer system, including PCs and Macintoshes.8
You may wonder why the data aren’t stored in the more familiar
decimal system, which of course uses the digits from 0 through 9. This
is not an arbitrary decision; on the contrary, there are a couple of very
good reasons that data on a disk are stored using the binary system, in
which each digit has only two possible states, 0 and 1. One of these
reasons is that i t ’s a lot easier to determine reliably whether a
particular area on a disk is magnetized “north” or “south” than it is to
determine 1 of 10 possible levels of magnetization. Another reason is
that the binary system is also the natural system for data storage using
electronic circuitry, which is used to store data in the rest of the
computer.
While magnetic storage devices have been around in one form or
another since the very early days of computing, the advances in
technology just in the last 16 years have been staggering. To
comprehend just how large these advances have been, we need to
define the term used to describe storage capacities: the megabyte. The
standard engineering meaning of mega is “multiply by 1 million”,
which would make a megabyte equal to 1 million (1,000,000) bytes.
As we have just seen, however, the natural number system in the
computer field is binary. Therefore, one megabyte is often used
instead to specify the nearest round number in the binary

8. In some old machines, bytes sometimes contained more or less than 8 bits, and
there are specialized machines today that have different byte sizes,. The C++
language specification requires only that a byte has at least eight bits.
system, which is 220 (2 to the power of 20), or 1,048,576 bytes. This
wasn’t obvious to Susan, so I explained it some more:

Susan: Just how important is it to really understand that the megabyte is 220
(1,048,576) bytes? I know that a meg is not really a meg; that is, it’s more than a
million. But I don’t understand 220, so is it enough to just take your word on this and
not get bogged down as to why I didn’t go any further than plane geometry in high
school? You see, it makes me worry and upsets me that I don’t understand how you
“round” a binary number.

Steve: 220 would be 2 to the power of 20; that is, 20 twos multiplied together.
This is a “round” number in binary, just as 106 (1,000,000, or 6 tens multiplied
together) is a round number in decimal.

Seventeen Years of Progress

With that detail out of the way, we can see just how far we’ve come in
a relatively short time. In 1985, I purchased a 20 megabyte disk for
$900 ($45 per megabyte) and its access time, which measures how
long it takes to retrieve data, was approximately 100 milliseconds
(milli = 1/1000, so a millisecond is 1/1000 of a second). In October
2001, a 40,000 megabyte disk cost as little as $130, or approximately
0.3 cent per megabyte; in addition to delivering almost 14000 times as
much storage per dollar, this disk had an access time of 9
milliseconds, which is approximately 11 times as fast as the old disk.
Of course, this significantly understates the amount of progress in
technology in both economic and technical terms. For one thing, a
2001 dollar is worth considerably less than a 1985 dollar. In addition,
the new drive is superior in every other measure as well. It is much
smaller than the old one, consumes much less power, and has many
times the projected reliability of the old drive.
This tremendous increase in performance and decrease in price
has prevented the long-predicted demise of disk drives in favor of
new technology. However, the inherent speed limitations of disks still
require us to restrict their role to the storage and retrieval of data for
which we can afford to wait a relatively long time.
You see, while 9 milliseconds isn’t very long by human standards,
it is a long time indeed to a modern computer. This will become more
evident as we examine the next essential component of the computer,
the RAM.

RAM

The working storage of the computer, where data and programs are
stored while we’re using them, is called RAM, which is an acronym
for random access memory. For example, a word processor is stored
in RAM while you’re using it. The document you’re working on is
likely to be there as well unless it’s too large to fit all at once, in
which case parts of it will be retrieved from the disk as needed. Since
we have already seen that both the word processor and the document
are stored on the disk in the first place, why not leave them there and
use them in place, rather than copying them into RAM?
The answer, in a word, is speed. RAM, which is sometimes called
“internal storage”, as opposed to “external storage” (the disk), is
physically composed of millions of microscopic switches on a small
piece of silicon known as a chip: a 4-megabit RAM chip has
approximately 4 million of them.9 Each of these switches can be either
on or off; we consider a switch that is on to be storing a 1, and a
switch that is off to be storing a 0. Just as in storing information on a
disk, where it is easier to magnetize a region in either of two
directions, it’s a lot easier to make a switch that can be turned on or
off reliably and quickly than one that can be set to any value from 0 to 9
reliably and quickly. This is particularly important when you’re

9. Each switch is made of several transistors. Unfortunately, an explanation of


how a transistor works would take us too far afield. Consult any good
encyclopedia, such as the Encyclopedia Britannica, for this explanation.
manufacturing millions of them on a silicon chip the size of your
fingernail.
A main difference between disk and RAM is what steps are
needed to access different areas of storage. In the case of the disk, the
head has to be moved to the right track (an operation known as a
seek), and then we have to wait for the platter to spin so that the region
we want to access is under the head (called rotational delay). On the
other hand, with RAM, the entire process is electronic; we can read or
write any byte immediately as long as we know which byte we want.
To specify a given byte, we have to supply a unique number, called its
memory address, or just address for short.

Memory Addresses

What is an address good for? Let’s see how my discussion with Susan
on this topic started.

Susan: About memory addresses, are you saying that each little itty bitty tiny byte
of RAM is a separate address? Well, this is a little hard to imagine.

Steve: Actually, each byte of RAM has a separate address, which doesn’t
change, and a value, which does.

In case the notion of an address of a byte of memory on a piece of


silicon is too abstract, it might help to think of an address as a set of
directions to find the byte being addressed, much like directions to
someone’s house. For example, “Go three streets down, then turn left.
It’s the second house on the right”. With such directions, the house
number wouldn’t need to be written on the house. Similarly, the
memory storage areas in RAM are addressed by position; you can
think of the address as telling the hardware which street and house you
want, by giving directions similar in concept to the preceding
example. Therefore, it’s not necessary to encode the addresses into the
RAM explicitly.
Susan wanted a better picture of this somewhat abstract idea:

Susan: Where are the bytes on the RAM, and what do they look like?

Steve: Each byte corresponds to a microscopic region of the RAM chip. As to


what they look like, have you ever seen a printed circuit board such as the ones
inside your computer? Imagine the lines on that circuit board reduced thousands of
times in size to microscopic dimensions, and you’ll have an idea of what a RAM chip
looks like inside.

Since it has no moving parts, storing and retrieving data in RAM is


much faster than waiting for the mechanical motion of a disk platter
turning.10 As we’ve just seen, disk access times are measured in
milliseconds, or thousandths of a second. However, RAM access
times are measured in nanoseconds (ns); nano means one-billionth. In
late 2001, a typical speed for RAM was 10 ns, which means that it is
possible to read a given data item from RAM about 1,000,000 times
as quickly as from a disk. In that case, why not use disks only for
permanent storage, and read everything into RAM in the morning when
we turn on the machine?
The reason is cost. In late 2001, the cost of 512 megabytes of
RAM was approximately $120. For that same amount of money, you
could have bought 37000 megabytes of disk space! Therefore, we
must reserve RAM for tasks where speed is all-important, such as

10. There’s also another kind of electronic storage, called ROM, for read-only
memory; as its name indicates, you can read from it, but you can’t write to it.
This is used for storing permanent information, such as the program that allows
your computer to read a small program from your boot disk; that program, in
turn, reads in the rest of the data and programs needed to start up the
computer. This process, as you probably know, is called booting the computer.
In case you’re wondering where that term came from, it’s an abbreviation for
bootstrapping, which is intended to suggest the fanciful notion of pulling
yourself up by your bootstraps. Also, you may have noticed that the terms
RAM and ROM aren’t symmetrical; why isn’t RAM called RWM, read-write
memory? Probably because it’s too hard to pronounce.
running your word processing program and holding a letter while
you’re working on it. Also, since RAM is an electronic storage
medium (rather than a magnetic one), it does not maintain its contents
when the power is turned off. This means that if you had a power
failure while working with data only in RAM, you would lose
everything you had been doing.11 This is not merely a theoretical
problem, by the way; if you don’t remember to save what you’re doing
in your word processor once in a while, you might lose a whole day’s
work from a power outage of a few seconds.12
Before we get to how a program actually works, we need to
develop a better picture of how RAM is used. As I’ve mentioned
before, you can think of RAM as consisting of a large number of bytes,
each of which has a unique identifier called an address. This address
can be used to specify which byte we mean, so the program might
specify that it wants to read the value in byte 148257, or change the
value in byte 66666. Susan wanted to make sure she had the correct
understanding of this topic:

Susan: Are the values changed in RAM depending on what program is loaded
in it?

Steve: Yes, and they also change while the program is executing. RAM is used to
store both the program and its data.

This is all very well, but it doesn’t answer the question of how the
program actually uses or changes values in RAM, or performs
arithmetic and other operations; that’s the job of the CPU, which we
will take up next.

11. The same disaster would happen if your system were to crash, which is not
that unlikely under certain operating systems.
12. Most modern word processors can automatically save your work once in a
while, for this very reason. I heartily recommend using this facility; it’s saved
my bacon more than once.
The CPU

The CPU (central processing unit) is the “active” component in the


computer. Like RAM, it is physically composed of millions of
microscopic transistors on a chip; however, the organization of these
transistors in a CPU is much more complex than on a RAM chip, as
the latter’s functions are limited to the storage and retrieval of data.
The CPU, on the other hand, is capable of performing dozens or
hundreds of different fundamental operations called machine
instructions, or instructions for short. While each instruction
performs a very simple function, the tremendous power of the
computer lies in the fact that the CPU can perform (or execute) tens or
hundreds of millions of these instructions per second.
These instructions fall into a number of categories: instructions
that perform arithmetic operations such as adding, subtracting,
multiplying, and dividing; instructions that move information from one
place to another in RAM; instructions that compare two quantities to
help make a determination as to which instructions need to be executed
next and instructions that implement that decision; and other, more
specialized types of instructions.13
Of course, adding two numbers together, for example, requires that
the numbers be available for use. Possibly the most straightforward
way of making them available is to store them in and retrieve them
from RAM whenever they are needed, and indeed this is done
sometimes. However, as fast as RAM is compared to disk drives (not
to mention human beings), it’s still pretty slow compared to modern
CPUs. For example, the computer I’m using right now has

13. Each type of CPU has a different set of instructions, so programs compiled for
one CPU cannot in general be run on a different CPU. Some CPUs, such as
the very popular Pentium series from Intel, fall into a “family” of CPUs in
which each new CPU can execute all of the instructions of the previous family
members. This allows upgrading to a new CPU without having to throw out all
of your old programs, but limits the ways in which the new CPU can be
improved without affecting this “family compatibility”.
a 500 megahertz (MHz) Pentium III CPU, which can execute an
instruction in 2 ns.14
To see why RAM is a bottleneck, let’s calculate how long it would
take to execute an instruction if all the data had to come from and go
back to RAM. A typical instruction would have to read some data
from RAM and write its result back there; first, though, the instruction
itself has to be loaded (or fetched) into the CPU before it can be
executed. Let’s suppose we have an instruction in RAM, reading and
writing data also in RAM. Then the minimum timing to do such an
instruction could be calculated as in Figure 2.1.

FIGURE 2.1. RAM vs. CPU speeds

Time Function
10 ns Read instruction from RAM 10
ns Read data from RAM
2 ns Execute instruction
10 ns Write result back to RAM
32 ns Total instruction execution time

To compute the effective speed of a CPU, we divide 1 second by the


time it takes to execute one instruction.15 Given the assumptions in this
example, the CPU could execute only about 31 MIPS (million

14. Since frequency is measured in decimal units rather than in binary units, the
mega in megahertz means one million (106), not 1,048,576 (220) as it does when
referring to memory and disk capacity. I’m sorry if this is confusing, but it can’t
be helped.
15. In fact, the Pentium III or 4 can actually execute more than one instruction at
a time if conditions are right. I’ll ignore this detail in my analysis, but if I
considered it, the discrepancy between memory speeds and CPU speeds would
be even greater.
instructions per second), which is a far cry from the 500 MIPS or
more that we might expect.16 This seems very wasteful; is there a way
to get more speed?
In fact, there is. As a result of a lot of research and development
both in academia and in the semiconductor industry, it is possible to
approach the rated performance of fast CPUs, as will be illustrated in
Figure 2.12. Some of these techniques have been around for as long as
we’ve had computers; others have fairly recently trickled down from
supercomputers to microcomputer CPUs. One of the most important of
these techniques is the use of a number of different kinds of storage
devices having different performance characteristics; the arrangement
of these devices is called the memory hierarchy. Figure 2.2
illustrates the memory hierarchy of my home machine. Susan and I had
a short discussion about the layout of this figure.

Susan: OK, just one question on Figure 2.2. If you are going to include the disk in
this hierarchy, I don’t know why you have placed it over to the side of RAM and not
above it, since it is slower and you appear to be presenting this figure in ascending
order of speed from the top of the figure downward. Did you do this because it is
external rather than internal memory and it doesn’t “deserve” to be in the same
lineage as the others?

Steve: Yes; it’s not the same as “real” memory, so I wanted to distinguish it.

Before we get to the diagram, I should explain that a cache is a small


amount of fast memory where frequently used data are stored
temporarily. According to this definition, RAM functions more or less
as a cache for the disk; after all, we have to copy data from a slow
disk into fast RAM before we can use it for anything. However, while
this is a valid analogy, I should point out that the situations aren’t quite
parallel. Our programs usually read data from disk into

16. 1 second/32 ns per instruction = 31,250,000 instructions per second.


RAM explicitly; that is, we’re aware of whether it’s on the disk or in
RAM, and we have to issue commands to transfer it from one place to
the other. On the other hand, caches are “automatic” in their
functioning. We don’t have to worry about them and our programs
work in exactly the same way with them as without them, except faster.
In any event, the basic idea is the same: to use a faster type of memory
to speed up repetitive access to data usually stored on a slower
storage device.
We’ve already seen that the disk is necessary to store data and
programs when the machine is turned off, while RAM is needed for its
higher speed in accessing data and programs we’re currently using.17
But why do we need the external cache?
Actually, we’ve been around this track before when we questioned
why everything isn’t loaded into RAM rather than read from the disk
as needed; we’re trading speed for cost. To have a cost- effective
computer with good performance requires the designer to choose the
correct amount of each storage medium.
So just as with the disk vs. RAM trade-off, the reason that we use
the external cache is to improve performance. While RAM can be
accessed about 100 million times per second, the external cache is
made from a faster type of memory chip, which can be accessed about
250 million times per second. While not as extreme as the speed
differential between disk and RAM, it is still significant.
However, we can’t afford to use external cache exclusively
instead of RAM because there isn’t enough of it. Therefore, we must
reserve external cache for tasks where speed is all-important, such as
supplying frequently used data or programs to the CPU.

17. These complementary roles played by RAM and the disk explain why the
speed of the disk is also illustrated in the memory hierarchy.
FIGURE 2.2. A memory hierarchy
The same analysis applies to the trade-off between the external
cache and the internal cache. The internal cache’s characteristics are
similar to those of the external cache, but to a greater degree; it’s even
smaller and faster, allowing access at the rated speed of the CPU.
Both characteristics have to do with its privileged position on the
same chip as the CPU; this reduces the delays in communication
between the internal cache and the CPU, but means that chip area
devoted to the cache has to compete with area for the CPU, as long as
the total chip size is held constant.
Unfortunately, we can’t just increase the size of the chip to
accommodate more internal cache because of the expense of doing so.
Larger chips are more difficult to make, which reduces their yield, or
the percentage of good chips. In addition, fewer of them fit on one
wafer, which is the unit of manufacturing. Both of these attributes
make larger chips more expensive to make.

How Caches Improve Performance

To oversimplify a bit, here’s how caching reduces the effects of slow


RAM. Whenever a data item is requested by the CPU, there are three
possibilities.
1. It is already in the internal cache. In this case, the value is sent to the
CPU without referring to RAM at all.
2. It is in the external cache. In this case, it will be “promoted” to the
internal cache and sent to the CPU at the same time.
3. It is not in either the internal or external cache. In this case, it has to
be entered into a location in the internal cache. If there is nothing
already stored in that cache location, the new item is simply added
to the cache. However, if there is a data item already in that cache
location, then the old item is displaced to the external cache, and the
new item is written in its place.18 If the external
cache location is empty, that ends the activity; if it is not empty, then
the item previously in that location is written out to RAM and its
slot is used for the one displaced from the internal cache.19

How Registers Improve Performance

Another way to improve performance which has been employed for


many years is to create a small number of private storage areas, called
registers, that are on the same chip as the CPU itself.20 Programs use
these registers to hold data items that are actively in use; data in
registers can be accessed within the time allocated to instruction
execution (2 ns in our example), rather than in the much longer time
needed to access data in RAM. This means that the time needed to
access data in registers is predictable, unlike data that may have been
displaced from the internal cache by more recent arrivals and thus
must be reloaded from the external cache or even from RAM. Most
CPUs have some dedicated registers, which aren’t available to
application programmers (that’s us), but are reserved for the operating
system (e.g., DOS, Unix, OS/2) or have special functions dictated by
the hardware design; however, we will be concerned
18. It’s also possible to have a cache that stores more than one item in a
“location”, in which case one of the other items already there will be displaced
to make room for the new one. The one selected is usually the one that hasn’t
been accessed for the longest time, on the theory that it’s probably not going to
be accessed again soon; this is called the least recently used (abbreviated
LRU) replacement algorithm.
19. This is fairly close to the actual way caches are used to reduce the time it
takes to get frequently used data from RAM (known as caching reads);
reducing the time needed to write changed values back to RAM (caching
writes) is more complicated.
20. In case you’re wondering how a small number of registers can help the speed
of a large program, I should point out that no matter how large a program is, the
vast majority of instructions and data items in the program are inactive at any
given moment. In fact, perhaps only a dozen instructions are in various stages
of execution at any given time even in the most advanced microprocessor CPU
available in 2001. The computer’s apparent ability to run several distinct
programs simultaneously is an illusion produced by the extremely high rate of
execution of instructions.
primarily with the general registers intended for our use.21 These are
used to hold working copies of data items called variables, which
otherwise reside in RAM during the execution of the program. These
variables represent specific items of data that we wish to keep track
of in our programs.
The notion of using registers to hold temporary copies of
variables wasn’t crystal clear to Susan. Here’s our discussion:

Susan: Here we go, getting lost. When you say, “The general registers are used
to hold working copies of data items called variables, which reside in RAM”, are you
saying RAM stores info when not in use?

Steve: During execution of a program, when data aren’t in the general registers,
they are generally stored in RAM.

Susan: I didn’t think RAM stores anything when turned off.

Steve: You’re correct; RAM doesn’t retain information when the machine is
turned off. However, it is used to keep the “real” copies of data that we want to
process but won’t fit in the registers.22

You can put something in a variable and it will stay there until you
store something else there; you can also look at it to find out what’s in
it. As you might expect, several types of variables are used to hold
different kinds of data; the first ones we will look at are variables
representing whole numbers (the so-called integer variables), which
21. All of the registers are physically similar, being just a collection of circuits in
the CPU used to hold a value. As indicated here, some registers are dedicated
to certain uses by the design of the CPU, whereas others are generally usable.
In the case of the general registers, which are all functionally similar or
identical, a compiler often uses them in a conventional way; this stylized usage
simplifies the compiler writer’s job.
22. Since RAM doesn’t maintain its contents when power is turned off, anything
that a program needs to keep around for a long time, such as inventory data to
be used later, should be saved on the disk. We’ll see how that is accomplished in
a future chapter.
are a subset of the category called numeric variables. As this
suggests, other variable types can represent numbers with fractional
parts. We’ll use these so-called floating-point variables later.
Different types of variables require different amounts of RAM to
store them, depending on the amount of data they contain; a very
common type of numeric variable, known as a short , as implemented
by the compiler on the CD in the back of the book, requires 16 bits
(that is, 2 bytes) of RAM to hold any of 65536 different values, from
–32768 to 32767, including 0.23 As we will see shortly, these odd-
looking numbers are the result of using the binary system. By no
coincidence at all, the early Intel CPUs such as the 8086 had general
registers that contained 16 bits each; these registers were named ax ,
bx , cx , dx , si , di , and bp . Why does it matter how many bits each
register holds? Because the number (and size) of instructions it takes
to process a variable is much less if the variable fits in a register;
therefore, most programming languages, C++ included, relate the size
of a variable to the size of the registers available to hold it. A short is
exactly the right size to fit into a 16-bit register and therefore can be
processed efficiently by the early Intel machines, whereas longer
variables had to be handled in pieces, causing a great decline in
efficiency of the program.
Progress marches on: more recent Intel CPUs, starting with the
80386, have 32-bit general registers; these registers are called eax ,
ebx , ecx , edx , esi , edi , and ebp . You may have noticed that these
names are simply the names of the old 16-bit registers with an e
tacked onto the front. The reason for the name change is that when Intel
increased the size of the registers to 32 bits with the advent of the
80386, it didn’t want to change the behavior of previously existing
programs that (of course) used the old names for the 16-bit registers.
So the old names, as illustrated in Figure 2.2, now refer to the bottom
halves of the “real” (that is, 32-bit) registers; instructions

23. The size of a short varies from one compiler and machine to another, but on
the most common current compilers, especially for machines such as the
ubiquitous “PC”, it is 16 bits.
using these old names behave exactly as though they were accessing
the 16-bit registers on earlier machines. T o refer to the 32-bit
registers, you use the new names eax , ebx , and so on, for extended ax ,
extended bx , and so forth.
We still have some way to go before we get to the end of our
investigation of the hardware inside a computer, but let’s pause here
for a question from Susan about how much there is to know about this
topic:

Susan: You said before that we were going to look at the computer at the “lowest
level accessible to a programmer”. Does it get any deeper than this?

Steve: Yes, it certainly does. There are entire disciplines devoted to layers below
those that we have any access to as programmers. To begin with, the machine
instructions and registers that we see as assembly language programmers are often
just the surface manifestation of underlying code and hardware that operates at a
deeper level (the so-called “microcode” level), just as C++ code is the surface
manifestation of assembly language instructions.

Below even the lowest level of programmable computer hardware is the physical
implementation of the circuitry as billions of transistors, and at the bottom we have
the quantum mechanical level that allows such things as transistors in the first place.
Luckily, the engineers who build the microprocessors (or actually, build the machines
that make the microprocessors) have taken care of all those lower levels for us, so
we don’t have to worry about them.

Now that we’ve cleared that up, let’s get back to the question of what
it means to say that instructions using the 16-bit register names behave
exactly as though they were accessing the 16-bit registers on earlier
machines. Before I can explain this, you’ll have to understand
the binary number system on which all modern computers are based.
To make this number system more intelligible, I have written the
following little fable.

2.4. The Binary Number System

Once upon a time, the Acme company had a factory that made golf
carts. One day, Bob, the President of Acme, decided to add an
odometer to the carts so that the purchaser of the cart could estimate
when to recharge the battery. To save money, Bob decided to buy the
little numbered wheels for the odometers and have his employees put
the odometers together. The minimum order was for a thousand
odometer wheels, which was more than he needed for his initial run of
50 odometers. When he got the wheels, however, he noticed that they
were defective. Instead of the numbers 0–9, each wheel had only two
numbers, 0 and 1. Of course, he was quite irritated by this error and
attempted to contact the company from which he had purchased the
wheels, but it had closed down for a month for summer vacation. What
was he to do until it reopened?
While he was fretting about this problem, the employee who had
been assigned the task of putting the odometers together from the
wheels came up with a possible solution. This employee, Jim, came
into Bob’s office and said, “Bob, I have an idea. Since we have lots of
orders for these odometer-equipped carts, maybe we can make an
odometer with these funny wheels and tell the customers how to read
the numbers on the odometer.”
Bob was taken aback by this idea. “What do you mean, Jim? How
can anyone read those screwy odometers?”
Jim had given this some thought. “Let’s take a look at what one of
these odometers, say with five wheels, can display. Obviously, it
would start out reading 00000, just like a normal odometer. Then
when one mile has elapsed, the rightmost wheel turns to 1, so the
whole display is 00001; again, this is just like a normal odometer.”
“Now we come to the tricky part. The rightmost wheel goes back
to 0, not having any more numbers to display, and pushes the ‘tens’
wheel to 1; the whole number now reads 00010. Obviously, one more
mile makes it 00011, which gives the situation shown in Figure 2.3.”

FIGURE 2.3. The first few numbers

Normal odometer Funny odometer


00000 00000
00001 00001
00002 00010
00003 00011

Jim continued, “What’s next? This time, the rightmost wheel turns over
again to 0, triggering the second wheel to its next position. However,
this time, the second wheel is already at its highest value, 1; therefore,
it also turns over to 0 and increments the third wheel. It’s not hard to
follow this for a few more miles, as shown in Figure 2.4.” Bob said, “I
get it. It’s almost as though we were counting normally, except that you
skip all the numbers that have anything but
0s or 1s in them.”

FIGURE 2.4. The next few numbers

Normal odometer Funny odometer


00004 00100
00005 00101
00006 00110
00007 00111
“That’s right, Bob. So I suppose we could make up a list of the ‘real’
numbers and give it to the customers to use until we can replace these
odometers with normal ones. Perhaps they’ll be willing to work with
us on this problem.”
“Okay, Jim, if you think they’ll buy it. Let’s get a few of the
customers we know best and ask them if they’ll try it; we won’t charge
them for the odometers until we have the real ones, but maybe they’ll
stick with us until then. Perhaps any odometer would be better than no
odometer at all.”
Jim went to work, making some odometers out of the defective
wheels; however, he soon figured out that he had to use more than five
wheels because that allowed only numbers from 0 to 31. How did he
know this?
Each wheel has two numbers, 0 and 1. So with one wheel, we
have a total of two combinations. Two wheels can have either a 0 or a
1 for the first number, and for each position of the first wheel there are
two possibilities for the second; 2*2 = 4, so there are a total of four
combinations for two wheels.24 With three wheels, the same analysis
holds: two numbers for the first wheel * two for the second wheel *
two for the third wheel, or eight possibilities in all; actually, they are
the same eight possibilities we saw in Figures 2.3 and 2.4.
A pattern is beginning to develop. For each added wheel, we get
twice as many possible combinations. To see how this continues, take
a look at Figure 2.525, which shows the count of combinations vs. the
number of wheels for all wheel counts up to 16 (i.e., quantities that
can be expressed by 16-bit numbers).

24. Note that the “*” symbol is used here to represent multiplication. We can’t use
the typical “x” symbol used in mathematics for this purpose, because letters are
used for specific purposes in the C++ language. We’ll get into that in detail
later.
25. If you think that last number in Figure 2.5 looks familiar, you’re right. It’s the
number of different values that, as I mentioned on page 30, can be stored in a
type of numeric variable called a short . This is no coincidence; read on for the
detailed explanation.
Jim decided that 14 wheels would do the job since the lifespan of
the golf cart probably wouldn’t exceed 16383 miles, and so he made
up the odometers. The selected customers turned out to be agreeable
and soon found that having even a weird odometer was better than
none, especially since they didn’t have to pay for it. However, one
customer did have a complaint. The numbers on the wheels didn’t seem
to make sense when translated with the chart supplied by Acme. The
customer estimated that he had driven the cart about 9 miles,
but the odometer displayed

11111111110111
which, according to his translation chart, was 16375 miles. What
could have gone wrong?
Jim decided to have the cart brought in for a checkup and what he
discovered was that the odometer cable had been hooked up
backwards. That is, instead of turning the wheels forward, they were
going backwards. That was part of the solution, but why was the value
16375? Just like a car odometer, in which 99999 (or 999999, if you
have a six-wheel odometer) is followed by 0, going backwards from 0
reverses that progression. Similarly, the number 11111111111111 on the
funny odometers would be followed by 00000000000000, since the
“carry” off the leftmost digit is lost.

FIGURE 2.5. How many combinations?

Number of wheels Number of combinations


1 2
2 4
3 8
4 16
5 32
6 64
Number of wheels Number of combinations
1 2
7 128
8 256
9 512
10 1024
11 2048
12 4096
13 8192
14 16384
15 32768
16 65536

Therefore, if you start out at 0 and go backward 1 mile, you’ll get


11111111111111
The next mile will turn the last digit back to 0, producing
11111111111110
What happens next? The last wheel turns back to 1 and triggers the
second wheel to switch as well, with the result

1111111111101
The next few “backward” numbers look like this:
11111111111100
11111111111011
11111111111010
11111111111001
11111111111000
11111111110111

and so on. If you look at the right-hand end of these numbers, you’ll see
that the progression is just the opposite of the “forward” numbers. As
for the customer’s actual mileage, the last one of these is the number
the customer saw on his backward odometer. Apparently, he was right
about the distance driven, since this is the ninth “backward” number.
So Jim fixed the backward odometer cable and reset the
value to the correct number, 00000000001001, or 9 miles.
Eventually, Acme got the right odometer wheels with 0 –9 on
them, replaced the peculiar ones and everyone lived happily ever
after.

THE END

Signed Variables

Since the wheels that made up the funny odometers contain only two
digits, 0 and 1, the odometers use the binary system for counting. Now
it should be obvious why we will see numbers like 65536 and 32768
in our discussions of the number of possible different values that a
variable can hold; variables are stored in RAM as collections of
bytes, each of which contains 8 bits.26 As the list of combinations
indicates, 8 bits (1 byte) provide 256 different combinations, while 16
bits (2 bytes) can represent 65536 different possible values.
But what about the “backward” numbers with a lot of 1s on the
left? As the fable suggests, they correspond to negative numbers. That
is, if moving 2 miles forward from 0 registers as 00000000000010,
and moving 2 miles backward from 0 registers as 11111111111110,
then the latter number is in some sense equivalent

26. I should mention here that the C++ standard does not require the size of a byte
to be 8 bits. But on the most common machines and compilers, it is.
to –2 miles. This in fact is the way that negative integers are stored in
the computer; integer variables that can store either positive or
negative values are called signed variables. If we don’t specify
whether we want to be able to store either positive or negative values
in a given variable, the C++ language assumes that we want that
ability and provides it for us by default.
However, adding the ability to represent negative numbers has a
drawback: you can’t represent as many positive numbers. This should
be fairly obvious, since if we interpret some of the possible patterns
as negative, they can’t also be used for positive values. Of course, we
don’t have to worry about negative numbers when counting, for
example, how many employees there are working for our company; in
such cases, we can specify that we want to use unsigned variables,
which will always be interpreted as positive (or 0) values. An
example is an unsigned short variable, which uses 16 bits (that is, 2
bytes) to hold any number from 0 to 65535, which totals 65536
different values.27 This capacity can be calculated as follows: since
each byte is 8 bits, 2 bytes contain a total of 16 bits, and 216 is 65536.
It’s important to understand that the difference between a short (that is,
a signed short ) and an unsigned short is not size, but which 65536 values
each can hold. An unsigned short can hold any whole number from 0 to
65535, whereas a short can hold any value from
–32768 to +32767.28
I hope this is clear to you, but in case it isn’t, let’s see how Susan
and I worked over this point.

Susan: I really don’t think I understand what a


short is besides being 2 bytes of
RAM, and I don’t really know what signed and unsigned mean.

27. As previously noted, the size of a short varies from one compiler and machine
to another. However, on the most common current compilers, including the one
on the CD in the back of the book, it is 16 bits.
28. You might also think that this would apply to numbers such as the total count of
books sold in a particular category since publication date. But apparently that
number can be negative, at least according to my royalty statements.
Steve: A short is indeed 2 bytes (that is, 16 bits) of RAM, at least with our
current compiler. This means that it can hold any of 216 (65536) different values.
This is a very nice range of values for holding the number of pounds that a pumpkin
weighs (for example). You’ll see some more uses for this type of variable later.

The difference between a ( signed ) short and an unsigned short is exactly which
65536 values each can hold. An unsigned short can hold any whole number from 0
to 65535, whereas a ( signed ) short can hold any value from – 32768 to +32767.
The difference between these is solely in the interpretation that we (and the
compiler) give to the values. In other words, it’s not possible to tell whether a given 2
bytes of RAM represent a short or an unsigned short by looking at the contents of
those bytes; you have to know how the variable was defined in the program.

Susan: OK, let’s start over. A short is 2 bytes of RAM. A short is a variable. A
short is a numeric variable. It can be signed (why is that a default?), meaning its
value can be –32768 to +32767, or unsigned , meaning its value can be 0–65535.
How’s that?

Steve: That’s fine. Since you’ve asked, the reason signed is the default is
because that’s the way it was in C, and changing it in C++ would “break” C
programs that depend on this default. Bjarne Stroustrup, the inventor of C++, has a
rule that C++ must be as close to C as possible but no closer. In this case, there’s no
real reason to change the default, so it wasn’t changed.

Susan: Oh, why is it that every time you say something is fairly obvious, my mind
just shuts down? When you say “if we interpret some of the possible patterns as
negative, they can’t also be used for positive values.” Huh? Then if that is the case,
would not the reverse also be true? I can see how this explains the values of the
signed and unsigned short, but really, I don’t think I have grasped this concept.

Steve: What I was trying to explain is that you have to choose one of the
following two possibilities:
1. ( signed ) short range: – 32768 to +32767
2. unsigned short range: 0 to 65535

In other words, you have to decide whether you want a given variable to represent

1. Any of 65536 values from –32768 to +32767, including 0.


2. Any of 65536 values from 0 to 65535

If you want a variable with a range like that in selection 1, use a ( signed ) short ; if
you prefer the range in selection 2, use an unsigned short . For example, for the
number of lines in a text file, you could use an unsigned short , since the maximum
number of lines could be limited to less than 65000 lines and couldn’t ever be
negative. On the other hand, to represent the number of copies of a book that have
been sold in the last month, including the possibility of returns exceeding sales, a
signed short would be better, since the
value could be either positive or negative.29

Susan: In other words, if you are going to be using variables that might have a
negative value, then use a signed short , and if you want strictly positive numbers,
including 0 as “positive”, then use an unsigned short . Right?

Steve: Exactly!

Susan: Well, then, how do you write a short to indicate that it is


signed or unsigned ?

Steve: When you define it, you have to specify that it is unsigned if you want it
to be unsigned ; the default is signed . In other words, if we define a variable x as
short x; , it will be signed , whereas if we

29. If neither of these does what you want, don’t despair. Other types of numeric
variables have different ranges, as we’ll see starting in Chapter 9.
want a variable called x that is an unsigned short, we have to say
unsigned short x; .

Susan: So does it make any difference if your variable is going to overlap the
signed and unsigned short ranges? For example, if you are using numbers from 10000
to 30000, would it matter which short you used? It falls under the definition of both.

Steve: You can use whichever you wish in that case.

A (Very) Short Course in Hexadecimal Notation

You may have noticed that it’s tedious and error prone to represent
numbers in binary; a long string of 0s and 1s is hard to remember or to
copy. For this reason, the pure binary system is hardly ever used to
specify numbers in computing. However, we have already seen that
binary is much more “natural” for computers than the more familiar
decimal system. Is there a number system that we humans can use a
little more easily than binary, while retaining the advantages of binary
for describing internal events in the computer?
As it happens, there is. It’s called hexadecimal, which means
“base 16”. As a rule, the term hexadecimal is abbreviated to hex.
Since there are 16 possible combinations of 4 bits (2*2*2*2),
hexadecimal notation allows 4 bits of a binary number to be
represented by one hex digit. Unfortunately, however, there are only
10 “normal” digits, 0– 9. To represent a number in any base, you need
as many different digit values as the base, so that any number less than
the base can be represented by one digit. For example, in base 2, you
need only two digits, 0 and 1. In base 8 (octal), you need eight digits,
0 –7.30 So far, so good. But what about base 16? To use this base, we
need 16 digits. Since only 10 numeric digits are available, hex
notation needs a source for the other six digits. Because letters of the
alphabet are available and familiar, the first six letters, a – f, were
adopted for this service.31,32
The notion of a base-16 numbering system can really throw
someone who learned normal decimal arithmetic solely by rote,
without understanding the concepts on which it is based. Susan and I
spent quite a bit of time on it, starting with this discussion:

Susan: I don’t get this at all! What is the deal with the letters in the hex system? I
guess it would be okay if 16 wasn’t represented by 10!

Steve: Well, there are only 10 “normal” digits, 0– 9. To represent a number in any
base, you need as many “digits” as the base, so that any number less than the base
can be represented by one “digit”. This is no problem with a base less than ten, such
as octal, but what about base 16? To use this base we need 16 digits, 0 –9 and a–f.
One way to remember this is to imagine that the “hex” in “hexadecimal” stands for
the six letters a–f and the “decimal” stands for the 10 digits 0– 9.

Susan: OK, so a hex digit represents 16 bits? So then is hex equal to 2 bytes?
According to the preceding a hex digit is 4 bits.

Steve: Yes, a hex digit represents 4 bits. Let’s try a new approach. First, let me
define a new term, a hexit. That’s short for “hexadecimal digit”, just like “bit” is
short for “binary digit”.

1. How many one-digit decimal numbers exist?

30. In the early days of computing, base 8 was sometimes used instead of base 16,
especially on machines that used 12-bit and 36-bit registers; however, it has
fallen into disuse because almost all current machines have 32-bit registers. 32,
of course, is divisible by 4 but not by 3. Base 8 is unlikely to make a comeback
because the upcoming generation of new CPUs will have 64-bit registers,
which are again evenly divisible into hex digits but not octal ones.
31. Either upper or lower case letters are acceptable to most programs (and
programmers). I’ll use lower case because such letters are easier to distinguish
than upper case ones; besides, I find them less irritating to look at.
32. See On Beyond Zebra (ISBN 0-394-80084-2) for another possible approach
to this problem.
2. How many two-digit decimal numbers exist?
3. How many three-digit decimal numbers exist?
4. How many four-digit decimal numbers exist?
5. How many one-bit binary numbers exist?
6. How many two-bit binary numbers exist?
7. How many three-bit binary numbers exist?
8. How many four-bit binary numbers exist?
9.How many one-hexit hexadecimal numbers exist? 10. How many
two-hexit hexadecimal numbers exist? 11. How many three-hexit
hexadecimal numbers exist? 12. How many four-hexit hexadecimal
numbers exist?
The answers are:

1. 10
2. 100
3. 1000
4. 10000
5. 2
6. 4
7. 8
8. 16
9. 16
10. 256
11. 4096
12. 65536
What do all these answers have in common? Let’s look at the answers a little
differently, in powers of 10, 2, and 16, respectively:

1. 10 = 101

2. 100 = 102

3. 1000 = 103

4. 10000 = 104

5. 2 = 21

6. 4 = 22

7. 8 = 23

8. 16 = 24

9. 16 = 161

10. 256 = 162

11. 4096 = 163

12. 65536 = 164

That is, a number that has one digit can represent “base” different values, where
“base” is two, ten, or sixteen (in our examples). Every time we increase the size of
the number by one more digit, we can represent “base” times as many possible
different values, or in other words, we multiply the range of values that the number
can represent by the base. Thus, a two-digit number can represent any of
“base*base” values, a three-digit number can represent any of “base*base*base”
values, and so on. That’s the way positional number systems such as decimal, binary,
and hex work. If you need a bigger number, you just add more digits.

Okay, so what does this have to do with hex? If you look at the
above table, you’ll see that 24 (16) is equal to 161. That means that 4 bits are exactly
equivalent to one hexit in their ability to represent
different numbers: exactly 16 possible numbers can be represented by four bits, and
exactly 16 possible numbers can be represented by one hexit.

This means that you can write one hexit wherever you would otherwise have to use
four bits, as illustrated in Figure 2.6.

So an 8-bit number, such as: 0101 1011


can be translated directly into a hex value, like this:
5 b

For this reason, binary is almost never used. Instead, we use hex as a shortcut to
eliminate the necessity of reading, writing, and remembering long strings of bits.

Susan: A hex digit or hexit is like a four-wheel odometer in binary. Since each
wheel is capable of only one of two values, being either (1) or (0), then the total
number of possible values is
16. Thus your 2*2*2*2 = 16. I think I’ve got this down.

Steve: You certainly do!

Susan: If it has 4 bits and you have 2 of them then won’t there be eight “wheels”
and so forth? So 2 hex would hold XXXXXXXX places and 3 hex would hold
XXXXXXXXXXXX places.

Steve: Correct. A one-hexit number is analogous to a one-digit decimal number.


A one-hexit number contains 4 bits and therefore can represent any of 16 values. A
two-hexit number contains 8 bits and therefore can represent any of 256 values.

Now that we’ve seen how each hex digit corresponds exactly to a
group of four binary digits, here’s an exercise you can use to improve
your understanding of this topic: Invent a random string of four binary
digits and see where it is in Figure 2.6. I guarantee it’ll be there
somewhere! Then look at the “hex” column and see what
“digit” it corresponds to. There’s nothing really mysterious about hex;
since we have run out of digits after 9, we have to use letters to
represent the numbers ‘ten’, ‘eleven’, ‘twelve’, ‘thirteen’, ‘fourteen’,
and ‘fifteen’.

FIGURE 2.6. Binary to hex


conversion table

4-bit value 1-hexit value


0000 0
0001 1
0010 2
0011 3
0100 4
0101 5
0110 6
0111 7
1000 8
1001 9
1010 a
1011 b
1100 c
1101 d
1110 e
1111 f

Another reason to use hex rather than decimal is that byte values
expressed as hex digits can be combined directly to produce larger
values, which is not true with decimal digits. In case this isn’t
obvious, let’s go over it in more detail. Since each hex digit (0–f)
represents exactly 4 bits, two of them (00–ff) represent 8 bits, or one
byte. Similarly, 4 hex digits (0000 –ffff) represent 16 bits, or a short
value; the first two digits represent the first byte of the 2-byte value,
and the last two digits, the second byte. This can be extended to any
number of bytes. On the other hand, representing 4 bits requires two
decimal digits, as the values range from 00–15, whereas it takes three
digits (000–255) to represent one byte. A 2-byte value requires five
decimal digits, since the value can be from 00000 to 65535. As you
can see, there’s no simple relationship between the decimal digits
representing each byte and the decimal representation of a 2-byte
value.
Figure 2.7 is a table showing the correspondence between some
decimal, hex, and binary numbers, with the values of each digit
position in each number base indicated, and the calculation of the total
of all of the bit values in the binary representation.

FIGURE 2.7. Different representations of the same numbers

Decimal Hexadecimal Binary Sum of binary


Place Values Place Values Place Values digit values
10 1 16 1 16 8 4 2 1

0 00 0 0 0 0 0 = 0 + 0 + 0 + 0 + 0
1 01 0 0 0 0 1 = 0 + 0 + 0 + 0 + 1
2 02 0 0 0 1 0 = 0 + 0 + 0 + 2 + 0
3 03 0 0 0 1 1 = 0 + 0 + 0 + 2 + 1
4 04 0 0 1 0 0 = 0 + 0 + 4 + 0 + 0
5 05 0 0 1 0 1 = 0 + 0 + 4 + 0 + 1
6 06 0 0 1 1 0 = 0 + 0 + 4 + 2 + 0
7 07 0 0 1 1 1 = 0 + 0 + 4 + 2 + 1
8 08 0 1 0 0 0 = 0 + 8 + 0 + 0 + 0
9 09 0 1 0 0 1 = 0 + 8 + 0 + 0 + 1
1 0 0a 0 1 0 1 0 = 0 + 8 + 0 + 2 + 0
1 1 0b 0 1 0 1 1 = 0 + 8 + 0 + 2 + 1
1 2 0c 0 1 1 0 0 = 0 + 8 + 4 + 0 + 0
1 3 0d 0 1 1 0 1 = 0 + 8 + 4 + 0 + 1
1 4 0e 0 1 1 1 0 = 0 + 8 + 4 + 2 + 0
1 5 0f 0 1 1 1 1 = 0 + 8 + 4 + 2 + 1
1 6 10 1 0 0 0 0 = 16 + 0 + 0 + 0 + 0
1 7 11 1 0 0 0 1 = 16 + 0 + 0 + 0 + 1
1 8 12 1 0 0 1 0 = 16 + 0 + 0 + 2 + 0
1 9 13 1 0 0 1 1 = 16 + 0 + 0 + 2 + 1

Susan had some more thoughts on the hexadecimal number system. Let’s
listen in:
Susan: I think you need to spend a little more time reviewing the hex system, like
an entire chapter.<G> Well, I am getting the impression that we are going to be
working with hex, so I am trying to concentrate my understanding on that instead of
binary. I think this all moves a little too fast for me. I don't know what your other
reviewers are saying but I just feel like I get a definition of an abstract concept, and
the next thing I know I am supposed to be doing something with it, like make it work.
Ha! I personally need to digest new concepts, I really need to think them over a bit,
to take them in and absorb them. I just can't start working with it right away.

As usual, I’ve complied with her request; the results are immediately
ahead.

2.5. Exercises

Here are some exercises that you can use to check your understanding
of the binary and hexadecimal number systems.
Consider the two types of numeric variables we’ve encountered so
far, short and unsigned short . Let’s suppose that x is a short , and y is an
unsigned short , both of them currently holding the value 32767, or 7fff in
hex.
1. What is the result of adding 1 to y, in both decimal and hex?
2. What is the result of adding 1 to x, in both decimal and hex?

Answers to exercises can be found at the end of the chapter.


2.6. Using the 16-bit Register Names

Before we took this detour into the binary and hexadecimal number
systems, I promised to explain what it means to say that the
instructions using the 16-bit register names “behave exactly as though
they were accessing the 16-bit registers on earlier machines”. After a
bit more preparation, we’ll be ready for that explanation.
First, let’s take a look at some characteristics of the human-
readable version of machine instructions: assembly language
instructions. The assembly language instructions we will look at have
a fairly simple format.33 The name of the operation is given first,
followed by one or more spaces. The next element is the
“destination”, which is the register or RAM location that will be
affected by the instruction’s execution. The last element in an
instruction is the “source”, which represents another register, a RAM
location, or a constant value to be used in the calculation. The source
and destination are separated by a comma.34 Here’s an example of a
simple assembly language instruction:

add ax,1

In this instruction, add is the operation, ax is the destination, and the


constant value 1 is the source. Thus, add ax,1 means to add 1 to the
contents of ax , replacing the old contents of ax with the result.

33. I’m simplifying here. There are instructions that follow other formats, but we’ll
stick with the simple ones for the time being.
34. The actual machine instructions being executed in the CPU don’t have
commas, register names, or any other human-readable form; they consist of
fixed-format sequences of bits stored in RAM. The CPU actually executes
machine language instructions rather than assembly language ones; a program
called an assembler takes care of translating the assembly language
instructions into machine instructions. However, we can usually ignore this step,
because each assembly language instruction corresponds to one machine
instruction. This correspondence is quite unlike the relationship between C++
statements and machine instructions, which is far more complex.
Let’s see what Susan has to say about the makeup of an assembly
language instruction:

Susan: Why is the destination first and the source last? That seems backward to
me.

Steve: I agree, it does seem backward. That’s just the way Intel did it in their
assembly language. Other machines and other assemblers have different
arrangements; for example, Motorola 68000 assembly language has the source on
the left and the destination on the right.

Susan: So the destination can be a register, cache, or RAM?

Steve: Yes, that’s right. However, the cache is transparent to the programmer.
That is, you don’t say “write to the cache” or “read from the cache”; you just use
RAM addresses and the hardware uses the cache as appropriate to speed up access
to frequently used locations. On the other hand, you do have to address registers
explicitly when writing an assembly language program.

Now we’re finally ready to see what the statement about using the 16-
bit register names on a 32-bit machine means. Suppose we have the
register contents shown in Figure 2.8 (indicated in hexadecimal). If
we were to add 1 to register ax , by executing the instruction add
ax,1 , the result would be as shown in Figure 2.9.

FIGURE 2.8. 32 and 16 bit


registers, before add ax,1

32-bit register

32-bit contents

16-bit register

16-bit contents

eax 1235ffff ax ffff


FIGURE 2.9. 32 and 16 bit registers, after add ax,1

32-bit register

32-bit contents

16-bit register

16-bit contents

eax 12350000 ax 0000

In case this makes no sense, consider what happens when you add 1 to
9999 on a four digit counter such as an odometer. It “turns over” to
0000, doesn’t it? The same applies here: ffff is the largest number that
can be represented as four hex digits, so if you add 1 to a register that
has only four (hex) digits of storage available, the result is 0000.
As you might imagine, Susan was quite intrigued with the above
detail; here is her reaction.

Susan: I have an understanding retention half-life of about 30 nanoseconds, but


while I was reading this I was understanding it except I am boggled as to how
adding 1 to ffff makes 0000, see, I am still not clear on hex. Question: When you
show the contents of a 32-bit register as being 12350000, then is the 1235 the upper
half and the 0000 the lower half? Is that what you are saying?

Steve: That’s right!

As this illustrates, instructions that refer to ax have no effect whatever


on the upper part of eax ; they behave exactly as though the upper part of
eax did not exist. However, if we were to execute the instruction add
eax,1 instead of add ax,1 , the result would look like Figure 2.10.

FIGURE 2.10. 32 and 16 bit registers, after add eax,1

32-bit register

32-bit contents

16-bit register
16-bit contents

eax 12360000 ax 0000


In this case, eax is treated as a whole. Similar results apply to the
other 32-bit registers and their 16-bit counterparts.

Revisiting the Memory Hierarchy

Unfortunately, it isn’t possible to use only registers and avoid


references to RAM entirely, if only because we’ll run out of registers
sooner or later. This is a good time to look back at the diagram of the
“memory hierarchy” (Figure 2.2) and examine the relative speed and
size of each different kind of memory.
The “size” attribute of the disk and RAM are specified in
megabytes, whereas the size of an external cache is generally in the
range from 64 kilobytes to a few megabytes. As I mentioned before,
the internal cache is considerably smaller, usually in the 8 to 16
kilobyte range. The general registers, however, provide a total of 28
bytes of storage; this should make clear that they are the scarcest
memory resource. To try to clarify why the registers are so important
to the performance of programs, I’ve listed the “speed” attribute in
number of accesses per second, rather than in milliseconds,
nanoseconds, and so forth. In the case of the disk, this is about 100
accesses per second. Normal RAM can be accessed about 100 million
times per second, while the external cache allows about 250 million
accesses per second. The clear winners, though, are the internal cache
and the registers, which can be accessed 500 million times per second
and 1 billion times per second, respectively.

The Advantages and Disadvantages of Using Registers

In a way, the latter figure (1 billion accesses per second for registers)
overstates the advantages of registers relative to the cache. You see,
any given register can be accessed only 500 million times per
second35; however, many instructions refer to two registers and still
execute in one CPU cycle. Therefore, the maximum number of
references per second is more than the number of instructions per
second.
However, this leads to another question: Why not use instructions
that can refer to more than one memory address (known as memory-
to-memory instructions) and still execute in one CPU cycle? In that
case, we wouldn’t have to worry about registers; since there’s
(relatively) a lot of cache and very few registers, it would seem to
make more sense to eliminate the middleman and simply refer to data
in the cache.36 Of course, there is a good reason for the provision of
both registers and cache. The main drawback of registers is that there
are so few of them; on the other hand, one of their main advantages is
also that there are so few of them. Why is this?
The main reason to use registers is that they make instructions
shorter: since there are only a few registers, we don’t have to use up a
lot of bits specifying which register(s) to use. That is, with eight
registers, we only need 3 bits to specify which register we need. In
fact, there are standardized 3-bit codes that might be thought of as
“register addresses”, which are used to specify each register when it
is used to hold a variable. Figure 2.11 is the table of these register
codes.37

35. As before, we are ignoring the ability of the CPU to execute more than one
instruction simultaneously.
36. Perhaps I should remind you that the programmer doesn’t explicitly refer to
the cache; you can just use normal RAM addresses and let the hardware take
care of making sure that the most frequently referenced data ends up in the
cache.
37. Don’t blame me for the seemingly scrambled order of the codes; that’s the
way Intel’s CPU architects assigned them to registers when they designed the
8086 and it’s much too late to change them now. Luckily, we almost never have
to worry about their values, because the assembler takes care of the translation
of register names to register addresses.
FIGURE 2.11. 32 and 16 bit
register codes

Register 16-bit 32-bit


address register register
000 ax eax
001 cx ecx
010 dx edx
011 bx ebx
100 sp esp
101 bp ebp
110 si esi
111 di edi

By contrast, with a “memory-to-memory” architecture, each


instruction would need at least 2 bytes for the source address, and 2
bytes for the destination address.38 Adding 1 byte to specify what the
instruction is going to do, this would make the minimum instruction
size 5 bytes, whereas some instructions that use only registers can be
as short as 1 byte. This makes a big difference in performance because
the caches are quite limited in size; big programs don’t fit in the
caches, and therefore require a large number of RAM accesses. As a
result, they execute much more slowly than small programs.

The Effect of Registers on Program Size

This explains why we want our programs to be smaller. However, it


may not be obvious why using registers reduces the size of
instructions, so here’s an explanation.

38. If we want to be able to access more than 64 kilobytes worth of data, which is
necessary in most modern programs, we’ll need even more room to store
addresses.
Most of the data in use by a program are stored in RAM. When
using a 32-bit CPU, it is theoretically possible to have over 4 billion
bytes of memory (2^32 is the exact number). Therefore, that many
distinct addresses for a given byte of data are possible, and to specify
any of these requires 32 bits. Since there are only a few registers,
specifying which one you want to use takes only a few bits; therefore,
programs use register addresses instead of memory addresses
wherever possible to reduce the number of bits in each instruction
required to specify addresses.
I hope this is clear, but it might not be. It certainly wasn’t to Susan.
Here’s the conversation we had on this topic:

Susan: I see that you are trying to make a point about why registers are more
efficient in terms of making instructions shorter, but I just am not picturing exactly
how they do this. How do you go from “make the instructions much shorter” to “we
don’t have to use up a lot of bits specifying which registers to use”?

Steve: Let’s suppose that we want to move data from one place to another in
memory. In that case, we’ll have to specify two addresses: the “from” address and
the “to” address. One way to do this is to store the addresses in the machine
language instruction. Since each address is at least 16 bits, an instruction that
contains two addresses needs to occupy at least 32 bits just for the addresses, as well
as some more bits to specify exactly what instruction we want to perform. Of
course, if we’re using 32-bit addresses, then a “two-address” instruction would
require 64 bits just for the two addresses, in addition to whatever bits were needed to
specify the type of instruction.

Susan: OK. . . think I got this. . .

Steve: On the other hand, if we use registers to hold the addresses of the data,
we need only enough bits to specify each of two registers. Since there aren’t that
many registers, we don’t need as many bits to specify which ones we’re referring
to. Even on a machine that has 32 general registers, we’d need only 10 bits to
specify two registers; on the Intel machines, with their shortage of registers, even
fewer bits are needed to specify which register we’re referring to.

Susan: Are you talking about the bits that are needed to define the instruction?

Steve: Yes.

Susan: How would you know how many bits are needed to specify the two
registers?

Steve: If you have 32 different possibilities to select from, you need 5 bits to
specify one of them, because 32 is 2 to the fifth power. If we have 32 registers, and
any of them can be selected, that takes 5 bits to select any one of them. If we have
to select two registers on a CPU with 32 registers, we need 10 bits to specify both
registers.

Susan: So what does that have to do with it? All we are talking about is the
instruction that indicates “select register” right? So that instruction should be the
same and contain the same number of bits whether you have 1 or 32 registers.

Steve: There is no “select register” instruction. Every instruction has to specify


whatever register or registers it uses. It takes 5 bits to select 1 of 32 items and only 3
bits to select 1 of 8 items; therefore, a CPU design that has 32 registers needs longer
instructions than one that has only 8 registers.

Susan: I don’t see why the number of registers should have an effect on the
number of bits one instruction should have.

Steve: If you have two possibilities, how many bits does it take to select one of
them? 1 bit. If you have four possibilities, how many bits does it take to select one of
them? 2 bits. Eight possibilities
require 3 bits; 16 possibilities require 4 bits; and finally 32 possibilities require 5 bits.

Susan: Some machines have 32 registers?

Steve: Ye s. The PowerPC, for example. Some machines have even more
registers than that.

Susan: If the instructions to specify a register are the same, then why would they
differ just because one machine has more registers than another?

Steve: They aren’t the same from one machine to another. Although every CPU
that I’m familiar with has registers, each type of machine has its own way of
executing instructions, including how you specify the registers.

Susan: OK, and in doing so it is selecting a register, right? An instruction should


contain the same number of bits no matter how many registers it has to call on.

Steve: Let’s take the example of an add instruction, which as its name implies,
adds two numbers. The name of the instruction is the same length, no matter how
many registers there are; that’s true. However, the actual representation of the
instruction in machine language has to have room for enough bits to specify which
register(s) are being used in the instruction.

Susan: They are statements right? So why should they be bigger or smaller if there
are more or fewer registers?

Steve: They are actually machine instructions, not C++ statements. The computer
doesn’t know how to execute C++ statements, so the C++ compiler is needed to
convert C++ statements into machine instructions. Machine instructions need bits to
specify which register(s) they are using; so, with more registers available, more
bits in the instructions have to be used to specify the register(s) that the instructions
are using.

Susan: Do all the statements change the values of bits they contain depending on
the number of registers that are on the CPU?

Steve: Yes, they certainly do. To be more precise, the machine language
instructions that execute a statement are larger or smaller depending on the number
of registers in the machine because they need more bits to specify one of a larger
number of registers.

Susan: “It takes five bits to select one of 32 items...” “...and only three
bits to select one of eight items.” Why?

Steve: What is a bit? It is the amount of information needed to select one of two
alternatives. For example, suppose you have to say whether a light is on or off. How
many possibilities exist? Two. Since a single bit has two possible states, 0 or 1, we
can represent “on” by 1 and “off” by 0 and thus represent the possible states of the
light by one bit.

Now suppose that we have a fan that has four settings: low, medium, high, and off.
Is one bit enough to specify the current setting of the fan? No, because one bit has
only two possible states, while the fan has four. However, if we use two bits, then it
will work. We can represent the states by bits as follows:

bits state
---- -----
00 off
01 low
10 medium
11 high

Note that this is an arbitrary mapping; there’s no reason that it couldn’t be like this
instead:
bits state
---- -----
00 medium
01 high
10 off
11 low

However, having the lowest “speed” (that is, off) represented by the lowest binary
value (00) and the increasing speeds corresponding to increasing binary values makes
more sense and therefore is easier to remember.

This same process can be extended to represent any number of possibilities. If we


have eight registers, for example, we can represent each one by 3 bits, as noted
previously in Figure 2.11 on page 54. That is the actual representation in the Intel
architecture; however, whatever representation might have been used, it would
require 3 bits to select among eight possibilities. The same is true for a machine that
has 32 registers, except that you need 5 bits instead of 3.

Susan: Okay, so then does that mean that more than one register can be in use at
a time? Wait, where is the room that you are talking about?

Steve: Some instructions specify only one register (a “one- register” instruction),
while others specify two (a “two-register” instruction); some don’t specify any
registers. For example, there are certain instructions whose effect is to determine
which instruction will be executed next (the so-called “branch” instructions). These
instructions often do not contain any register references at all, but rather specify the
address of the next instruction directly. These instructions are used to implement if
statements, for loops, and other flow control statements.

Susan: So, when you create an instruction you have to open up enough “room” to
talk to all the registers at once?
Steve: No, you have to have enough room to specify any one register, for a one-
register instruction, or any two registers for a two-register instruction.

Susan: Well, this still has me confused. If you need to specify only one register at
any given time, then why do you always need to have all the room available?
Anyway, where is this room? Is it in RAM or is it in the registers themselves? Let’s
say you are going to specify an instruction that uses only 1 of 32 registers. Are you
saying that even though you are going to use just one register you have to make
room for all 32?

Steve: The “room” that I’m referring to is the bits in the instruction that specify
which register the instruction is using. That is, if there are eight registers and you
want to use one of them in an instruction, 3 bits need to be set aside in the instruction
to indicate which register you’re referring to.

Susan: So you need the bits to represent the address of a register?

Steve: Right. However, don’t confuse the “address of a register” with a memory
address. They have nothing to do with one another, except that they both specify one
of a number of possible places to store information. That is, register ax doesn’t
correspond to memory address 0, and so on.

Susan: Yes, I understand the bit numbers in relation to the number of registers.

Steve: That’s good.

Susan: So the “address of a register” is just where the CPU can locate the
register in the CPU, not an address in RAM. Is that right?

Steve: Right. The address of a register merely specifies which of the registers
you’re referring to; all of them are in the CPU.
After that comedy routine, let’s go back to Susan’s reaction to
something I said earlier about registers and variables:

Susan: The registers hold only variables... Okay, I know what is bothering me!
What else is there besides variables? Besides nonvariables, please don’t tell me that.
(Actually that would be good, now that I think of it.) But this is where I am having
problems. You are talking about data, and a variable is a type of data. I need to know
what else is out there so I have something else to compare it with. When you say a
register can hold a variable, that is meaningless to me, unless I know what the
alternatives are and where they are held.

Steve: What else is there besides variables? Well, there are constants, like the
number 5 in the statement x = 5; . Constants can also be stored in registers. For
example, let’s suppose that the variable x , which is a short , is stored in location
1237. In that case, the statement x = 5; might generate an instruction sequence that
looks like this:

mov ax,5
mov [1237],ax

where the number in the [] is the address of the variable x . The first of these
instructions loads 5 into register ax , and the second one stores the contents of ax
( 5 , in this case) into the memory location 1237 .

Sometimes, however, constants aren’t loaded into registers as in this case but are
stored in the instructions that use them. This is the case in the following instruction:

add ax,3

This means to add 3 to whatever was formerly in register ax . The 3


never gets into a register but is stored as part of the instruction.39
Reading Instructions into Memory in Advance

Another way of reducing overhead is to read instructions from RAM


in chunks, rather than one at a time, and feed them into the CPU as it
needs them; this is called prefetching. This mechanism operates in
parallel with instruction execution, loading instructions from RAM
into special dedicated registers in the CPU before they’re actually
needed; these registers are known collectively as the prefetch queue.
Since the prefetching is done by a separate unit in the CPU, the time to
do the prefetching doesn’t increase the time needed for instruction
execution. When the CPU is ready to execute another instruction, it can
get it from the prefetch queue almost instantly, rather than having to
wait for the slow RAM to provide each instruction. Of course, it does
take a small amount of time to retrieve the next instruction from the
prefetch queue, but that amount of time is included in the normal
instruction execution time.

Susan: I don’t understand prefetching. What are “chunks”? I mean I understand


what you have written, but I can’t visualize this. So, there is just no time used to read
an instruction when something is prefetched?

Steve: A separate piece of the CPU does the prefetching at the same time as
instructions are being executed, so instructions that have already been fetched are
available without delay when the execution unit is ready to “do” them.

The effect of combining the use of registers and prefetching the


instructions can be very significant. In our example, if we use an
instruction that has already been loaded, which reads data from and
writes data only to registers, the timing reduces to that shown in
Figure 2.12.

39. We’ll go into this whole notion of using registers to represent and manipulate
variables in grotesque detail in Chapter 3.
FIGURE 2.12. Instruction execution time, using registers and prefetching

Time Function
0 ns Read instruction from RAM 0
ns Read data from RAM
2 ns Execute instruction
0 ns Write result back to RAM
2 ns Total instruction execution time

As I indicated near the beginning of this chapter, the manufacturers


aren’t lying to us: if we design our programs to take advantage of these
and other similar efficiency measures, we can often approach the
maximum theoretical performance figures. You’ve just been subjected
to a barrage of information on how a computer works. Let’s review
before continuing.

2.7. Review

The main components of the computer of most significance to


programmers are disk, RAM, and the CPU; the first two of these store
programs and data that are used by the CPU.
Computers represent pieces of information (or data) as binary
digits, universally referred to as bits. Each bit can have the value 0 or
1. The binary system is used instead of the more familiar decimal
system because it is much easier to make devices that can store and
retrieve 1 of 2 values, than 1 of 10. Bits are grouped into sets of eight,
called bytes.
The disk uses magnetic recording heads to store and retrieve
groups of a few hundred to a few thousand bytes on rapidly spinning
platters in a few milliseconds. The contents of the disk are not lost
when the power is turned off, so it is suitable for more or less
permanent storage of programs and data.
RAM, which is an acronym for Random Access Memory, is used
to hold programs and data while they’re in use. It is made of millions
of microscopic transistors on a piece of silicon called a chip. Each bit
is stored using a few of these transistors. RAM does not retain its
contents when power is removed, so it is not good for permanent
storage. However, any byte in a RAM chip can be accessed in about
10 nanoseconds, which is about a million times as fast as accessing a
disk. Each byte in a RAM chip can be independently stored and
retrieved without affecting other bytes, by providing the unique
memory address belonging to the byte you want.
The CPU (also called the processor) is the active component in the
computer. It is also made of millions of microscopic transistors on a
chip. The CPU executes programs consisting of instructions stored in
RAM, using data also stored in RAM. However, the CPU is so fast
that even the typical RAM access time of 10 nanoseconds is a
bottleneck; therefore, computer manufacturers have added both
external cache and internal cache, which are faster types of memory
used to reduce the amount of time that the CPU has to wait. The
internal cache resides on the same chip as the CPU and can be
accessed without delay. The external cache sits between the CPU and
the regular RAM; i t’s faster than the latter, but not as fast as the
internal cache. Finally, a very small part of the on-chip memory is
organized as registers, which can be accessed within the normal cycle
time of the CPU, thus allowing the fastest possible processing.

2.8. Conclusion

In this chapter, we’ve covered a lot of material on how a computer


actually works. As you’ll see, this background is essential if you’re
going to understand what really happens inside a program. In the next
chapter, we’ll get to the “real thing”: how to write a program to make
all this hardware do something useful.
2.9. Answers to Exercises
1. 32768 decimal, or 8000 in hex
2. –32768, or 8000 in hex
Why is the same hex value rendered here as –32768, while it was
32768 in question 1? The only difference between short and unsigned
short variables is how their values are interpreted. In particular,
short variables having hexadecimal values from 8000 to ffff are
considered negative, while unsigned short values in that range are
positive. That’s why the range of short values is –32768 to +32767,
whereas unsigned short variables can range from 0 to 65535.
By the way, whenever the number system being used isn’t obvious,
the convention is to add the letter h to the end of hexadecimal
numbers. Thus, if it wasn’t obvious that the number “8000” we
were referring to in the previous paragraph was hexadecimal, we
could write it as 8000h to make it perfectly clear.
CHAPTER 3 Basics of Programming

After that necessary detour into the workings of the hardware, we can
now resume our regularly scheduled explanation of the creative
possibilities of computers. It may sound odd to describe computers as
providing grand scope for creative activities: Aren’t they monotonous,
dull, unintelligent, and extremely limited? Yes, they are. However, they
have two redeeming virtues that make them ideal as the canvas of
invention: They are extraordinarily fast and spectacularly reliable.
These characteristics allow the creator of a program to weave
intricate chains of thought and have a fantastic number of steps carried
out without fail. We’ll begin to explore how this is possible after we
go over some definitions and objectives.

3.1. Definitions

An identifier is a user defined name; variable names are identifiers.


Identifiers must not be spelled the same as keywords such as if and
while ; for example, you cannot create a variable with the name while .
A keyword is a word defined in the C++ language such as if and
while . It is illegal to define an identifier such as a variable name that is
spelled the same as a keyword.

The C++ standard library is a collection of code defined by the ISO


(International Standards Organization) that must be included with
every standards-compliant compiler. Unfortunately, as of this writing,
there are no completely standards-compliant compilers, but the one on
the CD in the back of the book should be close enough for programs
you will be writing.

A C string literal is a literal value representing a variable number of


characters. An example is “This is a test.”. C string literals are
surrounded by double quotes (“).

A block is a section of code that acts like one statement, as far as the
language is concerned; that is, wherever a statement can occur, a block
can be substituted, and it will be treated as one statement for the
purposes of program organization.

3.2. Objectives of This Chapter

By the end of this chapter, you should


1. Understand what a program is and have some idea how one works.
2. Understand how to get information into and out of a program.
3. Understand how to use if and while to control the execution of a
program.1
4. Understand how a portion of a program can be marked off so that it
will be treated as one unit.
Rapid and Accurate Calculation

5.
Be able to read and understand a simple program I’ve written in
C++.

3.3. Rapid and Accurate Calculation

The most impressive attribute of modern computers, of course, is their


speed; as we have already seen, this is measured in MIPS (millions of
instructions per second).
Of course, raw speed is not very valuable if we can’t rely on the
results we get. ENIAC, one of the first electronic computers, had a
failure every few hours, on the average; since the problems it was
solving took about that long to run, the likelihood that the results were
correct wasn’t very high. Particularly critical calculations were often
run several times, and if the users got the same answer twice, they
figured it was probably correct. By contrast, modern computers are
almost incomprehensibly reliable. With almost any other machine, a
failure rate of one in every million operations would be considered
phenomenally low, but a computer with such a failure rate would make
thousands of errors per second.2

1. Please note that C++ is case sensitive, so IF and WHILE are not the same as
if and while. You have to use the latter versions when you are referring to the
keywords with those names. Although it is possible to define your own
variables called I F and WHILE, I don’t recommend this, as it will tend to
confuse other programmers.
2. However, we haven’t yet completely eliminated the possibility of hardware
errors, as the floating-point flaw in early versions of the Pentium™ processor
illustrates. In rare cases, the result of the divide instruction in those processors
was accurate to only about 5 decimal places rather than the normal 16 to 17
decimal places.
The Real Reason for “Computer Problems”

On the other hand, if computers are so reliable, why are they blamed
for so much that goes wrong with modern life? Who among us has not
been the victim of an erroneous credit report, or a bill sent to the
wrong address, or been put on hold for a long time because “the
computer is down”? The answer is fairly simple: It’s almost certainly
not the computer. More precisely, it’s very unlikely that the CPU was
at fault; it may be the software, other equipment such as telephone
lines, tape or disk drives, or any of the myriad “peripheral devices”
that the computer uses to store and retrieve information and interact
with the outside world. Usually, i t’s the software; when customer
service representatives tell you that they can’t do something obviously
reasonable, you can count on its being the software. For example, I
once belonged to a 401K plan whose administrators provided
statements only every three months, about three months after the end of
the quarter; in other words, in July I found out how much my account
had been worth at the end of March. The only way to estimate how
much I had in the meantime was to look up the share values in the
newspaper and multiply by the number of shares. Of course, the mutual
fund that issued the shares could tell its shareholders their account
balances at any time of the day or night; however, the company that
administered the 401K plan didn’t bother to provide such a service, as
it would have required doing some work.3 Needless to say, whenever I
hear that “the computer can’t do that” as an excuse for such poor
service, I reply “Then you need some different programmers.”

3. This was apparently against the plan administrator’s principles.


Nonnumeric Variables

All of this emphasis on computation, however, should not blind us to


the fact that computers are not solely arithmetic engines. The most
common application for which PCs are used is Web browsing, hardly
a hotbed of arithmetical calculation. While we have so far considered
only numeric data, this is a good illustration of the fact that computers
also deal with another kind of information, which is commonly
referred to by the imaginative term nonnumeric variables. Numeric
variables are those suited for use in calculations, such as in totalling a
set of weights. On the other hand, nonnumeric data are items that are
not used in calculations like adding, multiplying, or subtracting:
Examples are names, addresses, telephone numbers, Social Security
numbers, bank account numbers, or driver’s license numbers. Note
that just because something is called a number, or even is composed
entirely of the digits 0–9, does not make it numeric data by our
standards; the question is how the item is used. No one adds,
multiplies, or subtracts driver’s license numbers, for example; they
serve solely as identifiers and could just as easily have letters in them,
as indeed some do.
For the present, though, let’s stick with numeric variables. Now
that we have defined a couple of types of these variables, short and
unsigned short , what can we do with them? To do anything with them, we
have to write a C++ program, which consists primarily of a list of
operations to be performed by the computer, along with directions that
influence how these operations are to be translated into machine
instructions.
This raises an interesting point: Why does our C++ program have
to be translated into machine instructions? Isn’t it the computer’s job
to execute (or run) our program?
3.4. The Compiler

Yes, but it can’t run a C++ program. The only kind of program any
computer can run is one made of machine instructions; this is called a
machine language program, for obvious reasons. Therefore, to get our
C++ program to run, we have to translate it into a machine language
program. Don’t worry, you won’t have to do this yourself; that’s why
we have a program called a compiler.4 The most basic tasks that the
compiler performs are the following:
1. Assigning memory addresses to variables. This allows us to use
names for variables, rather than having to keep track of the address of
each variable ourselves.
2. Translating arithmetic and other operations (such as + , – , etc.) into
the equivalent machine instructions, including the addresses of
variables assigned in the previous step.5

This is probably a bit too abstract to be easily grasped, so let’s look at


an example as soon as we have defined some terms. Each complete
operation understood by the compiler is called a statement, and ends
with a semicolon ( ; ).6 Figure 3.1 shows some sample statements that
do arithmetic calculations.7

FIGURE 3.1. A little numeric


calculation

short i;

4. How is the compiler itself translated into machine language so it can be


executed? The most common method is to write the compiler in the same
language it compiles and use the previous version of the compiler to compile the
newest version! Of course, this looks like an infinite regress; how did the first
compiler get compiled? By manual translation into assembly language, which
was then translated by an assembler into machine language. To answer the
obvious question, at some point an assembler was coded directly in machine
language.
5. The compiler also does a lot of other work for us, which we’ll get into later.
short j;
short k;
short m;

i = 5;
j = i * 3; // j is now 15
k = j - i; // k is now 10
m = (k + j) / 5; // m is now 5
i = i + 1; // i is now 6

To enter such statements in the first place, you can use any text editor
that generates “plain” text files, such as the EDIT program that comes
with DOS or the Notepad program in Windows. Whichever text editor
you use, make sure that it produces files that contain only what you
type; stay away from programs like Windows Write™ or Word for
Windows™, as they add some of their own information to indicate
fonts, type sizes, and the like, to your file, which will foul up the
compiler.
Since we’re already on the subject of files, this would be a good
time to point out that the two main types of files in C++ are
implementation files (also known as source files), which in our case
have the extension .cpp , and header files, which by convention have the
extension .h .8 Implementation files contain statements that result
6. By the way, blank lines are ignored by the compiler; in fact, because of the
trailing semicolon on each statement, you can even run all the statements
together on one line if you want to, without confusing the compiler. However,
that will make it much harder for someone reading your code later to
understand what you’re trying to do. Programs aren’t written just for the
compiler’s benefit but to be read by other people; therefore, it is important to
write them so that they can be understood by those other people. One very
good reason for this is that more often than you might think, those “other
people” turn out to be you, six months later.
7. The // indicates the beginning of a comment, which is a note to you or another
programmer. Everything on a line after // is ignored by the compiler.
8. Except that the C++ standard library header files supplied with the compiler,
such as string, often have no extension at all. Also, other compilers sometimes
use other extensions for implementation files, such as .cc, and for header files,
such as .hpp .
in executable code, while each header file contains information that
allows us to access a set of language features.
Once we have entered the statements for our program, we use the
compiler to translate the programs we write into a form that the
computer can perform. As defined in Chapter 1, the form we create is
called source code, since it is the source of the program logic, while
the form of our program that the computer can execute is called an
executable program, or just an executable for short.
As I’ve mentioned before, there are several types of variables, the
short being only one of these types. Therefore, the compiler needs
some explanatory material so that it can tell what types of variables
you’re using; that’s what the first four lines of our little sample
program fragment are for. Each line tells the compiler that the type of
the variable i , j , k , or m is short ; that is, it can contain values from
–32768 to +32767.9
After this introductory material, we move into the list of
operations to be performed. This is called the executable portion of
the program, as it actually causes the computer to do something when
the program is executed; the operations to be performed, as mentioned
above, are called statements. The first one, i = 5; , sets the variable i
to the value 5 . A value such as 5 , which doesn’t have a name, but
represents itself in a literal manner, is called (appropriately enough) a
literal value.
This is as good a time as any for me to mention something that
experienced C++ programmers take for granted, but which has a
tendency to confuse novices. This is the choice of the = sign to
indicate the operation of setting a variable to a value, which is known
technically as assignment. As far as I’m concerned, an assignment
operation would be more properly indicated by some symbol
suggesting movement of data, such as 5 => i; , meaning “store the value
5 into variable i ”. Unfortunately, it’s too late to change the notation
for the assignment statement, as such a statement is called,

9. Other kinds of variables can hold larger (and smaller) values; we’ll go over
them in some detail in future chapters.
so you’ll just have to get used to it. The = means “set the variable on
the left to the value on the right”.10
Now that I’ve warned you about that possible confusion, let’s
continue looking at the operations in the program. The next one, j
= i * 3; , specifies that the variable j is to be set to the result of
multiplying the current value of i by the literal value 3 . The one after
that, k = j – i; , tells the computer to set k to the amount by which j is
greater than i ; that is, j – i . The most complicated line in our little
program fragment, m = (k + j) / 5; , calculates m as the sum of adding k
and j and dividing the result by the literal value 5 . Finally, the line
i = i + 1; sets i to the value of i plus the literal value 1 .
This last statement may be somewhat puzzling; how can i be equal
to i + 1 ? The answer is that an assignment statement is not an algebraic
equality, no matter how much it may resemble one. It is a command
telling the computer to assign a value to a variable. Therefore, what i
= i + 1; actually means is “Take the current value of i , add 1 to it, and
store the result back into i .” In other words, a C++ variable is a place
to store a value; the variable i can take on any number of values, but
only one at a time; any former value is lost when a new one is
assigned.
This notion of assignment was the topic of quite a few messages
with Susan. Let’s go to the first round:

Susan: I am confused with the statement i = i + 1; when you have stated


previously that i = 5; . So, which one is it? How can there be two values for i ?

10. At the risk of boring experienced C programmers, let me reiterate that = does
not mean “is equal to”; it means “set the variable to the left of the = to the
value of the expression to the right of the =. In fact, there is no equivalent in
C++ to the mathematical notion of equality. We have only the assignment
operator = and the comparison operator ==, which we will encounter in the
next chapter. The latter is used in i f statements to determine whether two
expressions have the same value. All of the valid comparison operators are
listed in Figure 4.5.
Steve: There can’t; that is, not at one time. However, i , like any other variable,
can take on any number of values, one after another. First, we set it to 5 ; then we
set it to 1 more than it was before ( i + 1 ), so it ends up as 6 .

Susan: Well, the example made it look as if the two values of


i were available to
be used by the computer at the same time. They were both lumped together as
executable material.

Steve: After the statement i = 5; , and before the statement i = i + 1; ,


the value of i is 5 . After the statement i = i + 1; , the value of i is 6 . The key
here is that a variable such as i is just our name for some area of memory that can
hold only one value at one time. Does that clear it up?

Susan: So, it is not like algebra? Then i is equal to an address of memory and
does not really equate with a numerical value? Well, I guess it does when you assign
a numerical value to it. Is that it?

Steve: Very close. A variable in C++ isn’t really like an algebraic variable, which
has a value that has to be figured out and doesn’t change in a given problem. A
programming language variable is just a name for a storage location that can contain
a value.

With any luck, that point has been pounded into the ground, so you
won’t have the same trouble that Susan did. Now let’s look at exactly
what an assignment statement does. If the value of i before the
statement i = i + 1; is 5 (for example), then that statement will cause the
CPU to perform the following steps:11
1. Take the current value of i (5).
11. If you have any programming experience whatever, you may think that I’m
spending too much effort on this very simple point. But I can report from
personal experience that it’s not necessarily easy for a complete novice to
grasp. Furthermore, without a solid understanding of the difference between an
algebraic equality and an assignment statement, that novice will be unable to
understand how to write a program.
2. Add one to that value (6).
3. Store the result back into i .

After the execution of this statement, i will have the value 6.

3.5. How the CPU Stores and Manipulates Data


in Memory

In a moment we’re going to dive a little deeper into how the CPU
accomplishes its task of manipulating data, such as we are doing here
with our arithmetic program. First, though, it’s time for a little pep talk
for those of you who might be wondering exactly why this apparent
digression is necessary. It’s because if you don’t understand what is
going on under the surface, you won’t be able to get past the “Sunday
driver” stage of programming in C++. In some languages it’s neither
necessary nor perhaps even possible to find out what the computer
actually does to execute your program, but C++ isn’t one of them. A
good C++ programmer needs an intimate acquaintance with the
internal workings of the language, for reasons which will become very
apparent when we get to Chapter 6. For the moment, you’ll just have
to take my word that working through these intricacies is essential; the
payoff for a thorough grounding in these fundamental concepts of
computing will be worth the struggle.
Now let’s get to the task of exploring how the CPU actually stores
and manipulates data in memory. As we saw previously, each memory
location in RAM has a unique memory address; machine instructions
that refer to RAM use this address to specify which byte or bytes of
memory they wish to retrieve or modify. This is fairly straightforward
in the case of a 1-byte variable, where the instruction merely specifies
the byte that corresponds to the variable. On the other hand, the
situation isn’t quite as simple in the case of a variable that occupies
more than 1 byte. Of course, no law of nature says that an instruction
couldn’t contain a number of addresses, one for each
byte of the variable. However, this solution is never adopted in
practice, as it would make instructions much longer than they need to
be. Instead, the address in such an instruction specifies the first byte of
RAM occupied by the variable, and the other bytes are assumed to
follow immediately after the first one. For example, in the case of a
short variable, which as we have seen occupies 2 bytes of RAM, the
instruction would specify the address of the first byte of the area of
RAM in which the variable is stored.12 However, there’s one point
that I haven’t brought up yet: how the data for a given variable are
actually arranged in memory. For example, suppose that the contents of
a small section of RAM (specified as two hex digits per byte) look
like Figure 3.2.

FIGURE 3.2. A small section of


RAM

Address Hex byte


value
1000 41
1001 42
1002 43
1003 44
1004 00

Also suppose that a short variable i is stored starting at address 1000.


To do much with a variable, we’re going to have to load it into a
general register, one of the small number of named data storage
locations in the CPU intended for general use by the programmer; this
proximity allows the CPU to operate on data in the registers at
maximum speed. You may recall that there are seven general registers
in the 386 CPU (and its successors); they’re named eax , ebx , ecx , edx ,
esi , edi , and ebp .13 Unfortunately, there’s another

12. Actually, the C++ language does not require that a s hort variable contain
exactly two bytes. However, it does on current Intel CPUs and other current
CPUs of which I am aware.
complication here; these registers are designed to operate on 4-byte
quantities, while our variable i , being of type short , is only two bytes
long. Are we out of luck? No, but we do have to specify how long the
variable is that we want to load. This problem is not unique to Intel
CPUs, since any CPU has to have the ability to load different-sized
variables into registers. Different CPUs use different methods of
specifying this important piece of information; in the Intel CPUs, one
way to do this is to alter the register name.14 As we saw in the
discussion of the development of Intel machines, we can remove the
leading e from the register name to specify that we’re dealing with 2-
byte values; the resulting name refers to the lower two bytes of the 4-
byte register. Therefore, if we wanted to load the value of i into
register ax (that is, the lower half of register eax ), the instruction
could be written as follows:15
mov ax,[1000] 16

As usual, our resident novice Susan had some questions on this topic.
Here is our conversation:

Susan: If you put something into 1000 that is “too big” for it, then it spills over to
the next address?

13. Besides these general registers, a dedicated register called e s p plays an


important role in the execution of real programs. We’ll see how it does this in
Chapter 5.
14. This is not the only possible solution to this problem, nor necessarily the best
one. For example, in many Motorola CPUs, you specify the length of the
variable directly in the instruction, so loading a word (i.e., 2-byte) variable might
be specified by the instruction move.w , where the .w means “word”. Similarly,
a longword (i.e., 4-byte) load might be specified as move.l, where the .l
means “long word”.
15. It’s also possible to load a 2-byte value into a 32-bit (i.e., 4-byte) register such
as eax and have the high part of that register set to 0 in one instruction, by using
an instruction designed specifically for that purpose. This approach has the
advantage that further processing can be done with the 32-bit registers.
16. The number inside the brackets [ ] represents a memory address.
Steve: Sort of. When you “put something into 1000” , you have to specify exactly
what it is you’re “putting in”. That is, it must be either a short , a char , or some
other type of variable that has a defined size.

Susan: Is that how it works? Why then is it not necessary to specify that it is
going to have to go into 1000 and 1001? So what you put in is not really in 1000
anymore, it is in 1000 and 1001? How do you refer to its REAL address? What if
there is no room in 1001? Would it go to 2003 if that is the next available space?

Steve: Because the rule is that you always specify the starting address of any
item (variable or constant) that is too big to fit in 1 byte. The other bytes of the item
are always stored immediately following the address you specify. No bytes will be
skipped when storing (or loading) one item; if the item needs 4 bytes and is to be
stored starting at 1000, it will be stored in 1000–1003.

Susan: I see. In other words, the compiler will always use the next bytes of
RAM, however many need to be used to store the item?

Steve: Right.

3.6. The Layout of Data in Memory

Now I have a question for you. After we execute the assembly


language statement mov ax,[1000] to load the value of i into ax , what’s in
register ax ? Of course; the answer is the value of i , but what is that
value? The first byte of i , at location 1000, has the value 41
hexadecimal (abbreviated 41h), and the second byte, at location 1001,
has the value 42h. But the value of i is 2 bytes long; is it 4142h or
4241h? These are clearly not the same!
That was a trick question; there’s no way for you to deduce the
answer with only the information I’ve given you so far. The answer
happens to be 4241h, because that’s the way Intel decided to do it; that
is, the low part of the value is stored in the byte of RAM where the
variable starts. Some other CPUs, e.g., Motorola’s 680x0 series, do it
the opposite way, where the high part of the value is stored in the byte
of RAM where the variable starts; this is called big-endian, since the
big end of the value is first, while the Intel way is correspondingly
called little-endian. And some machines can use either of these
methods according to how they are started up. This makes it easier for
them to run software written for either memory orientation.
As you might have surmised, the same system applies to 4-byte
values. Therefore, since we’re on a little-endian machine, if we wrote
the instruction mov eax,[1000] , it would load the eax register with the
value 44434241h; that is, the four bytes 41, 42, 43, and 44 (hex)
would be loaded into the eax register, with the byte having the lowest
address loaded into the low end of the register.
Here’s another example. A little-endian system would represent
the number 1234 (hex) stored at address 5000 as in Figure 3.3,
whereas a big-endian system would represent the same value 1234
(hex) as illustrated in Figure 3.4.

FIGURE 3.3. One little endian value


Address Value
5000 34
5001 12
FIGURE 3.4. A big endian example
Address Value
5000 12
5001 34
This really isn’t much of a problem as long as we don’t try to move
data from one type of machine to another; however, when such data
transportation is necessary, dealing with mixed endianness can be a
real nuisance.17 Before going on, let’s practice a bit with this notion of
how data are stored in memory.

3.7. Exercises, First Set


1. Assume that a short variable named z starts at location 1001 in a
little-endian machine. Using Figure 3.5 for the contents of
memory, what is the value of z , in hex?
FIGURE 3.5. Exercise 1
Address Hex byte value
1000 3a
1001 43
1002 3c
1003 99
1004 00

Playing Compiler

I can almost hear the wailing and tooth gnashing out there. Do I expect
you to deal with these instructions and addresses by yourself? You’ll
undoubtedly be happy to know that this isn’t necessary, as the
compiler takes care of these details. However, if you don’t have some
idea of how a compiler works, you’ll be at a disadvantage when

17. Luckily, we won’t run into this problem in this book. However, it is very
common in dealing with networks of computers, and there are industry
standards set up to allow diverse types of computers to coexist in a network.
you’re trying to figure out how to make it do what you want.
Therefore, we’re going to spend the next few pages “playing
compiler”; that is, I’ll examine each statement in a small program
fragment and indicate what action the compiler might take as a result.
I’ll simplify the statements a bit to make the explanation simpler; you
should still get the idea. Figure 3.6 illustrates the set of statements that
I’ll compile:18

FIGURE 3.6. A really little numeric calculation

short i;
short j;

i = 5;
j = i + 3;

Here are the rules of this game:


1. All numbers in the C++ program are decimal; all addresses and
numbers in the machine instructions are hexadecimal.19
2. All addresses are 2 bytes long.20
3. Variables are stored at addresses starting at 1000.
4. Machine instructions are stored at addresses starting at 2000.21

18. As I’ve mentioned previously, blank lines are ignored by the compiler; you can
put them in freely to improve readability.
19. However, I’ve cheated here by using small enough numbers in the C++
program that they are the same in hex as in decimal.
20. The real compiler on the CD actually uses 4-byte addresses, but this doesn’t
change any of the concepts involved.
21. These addresses are arbitrary; a real compiler will assign addresses to
variables and machine instructions by its own rules.
5. A number not enclosed in [] is a literal value, which represents
itself. For example, the instruction mov ax,1000 means to move the value
1000 into the ax register.
6. A number enclosed in [] is an address, which specifies where data
are to be stored or retrieved. For example, the instruction mov ax,
[1000] means to move 2 bytes of data starting at location 1000, not
the value 1000 itself, into the ax register.
7. All the data values are shown as “?? ??” to indicate that the
variables have not had values assigned to them yet.

Now let’s start compiling. The first statement, short i; says to allocate
storage for a 2-byte variable called i that will be treated as signed
(because that’s the default). Since no value has been assigned to this
variable yet, the resulting “memory map" looks like Figure 3.7.

FIGURE 3.7. Compiling, part 1

Address Variable Value


Name
1000 i ?? ??

As you might have guessed, this exercise was the topic of a


considerable amount of discussion with Susan. Here’s how it started:

Susan: So the first thing we do with a variable is to tell the address that its name is
i , but no one is home, right? It has to get ready to accept a value. Could you put a
value in it without naming it, just saying address 1000 has a value of 5? Why does it
have to be called i first?

Steve: The reason that we use names instead of addresses is because it’s much
easier for people to keep track of names than it is to keep track of addresses. Thus,
one of the main functions of a
compiler is to allow us to use names that are translated into addresses for the
computer’s use.

The second statement, short j; tells me to allocate storage for a 2-byte


variable called j that will be treated as signed (because that’s the
default). Since no value has been assigned to this variable yet, the
resulting “memory map” looks like Figure 3.8.

FIGURE 3.8. Compiling, part 2

Address Variable Value


Name
1000 i ?? ??
1002 j ?? ??

Here’s the exchange about this step:

Susan: Why isn’t the address for j 1001?

Steve: Because a short is 2 bytes, not 1. Therefore, if i is at address


1000 , j can’t
start before 1002 ; otherwise, the second byte of i would have the same address
as the first byte of j, which would cause chaos in the program. Imagine changing i
and having j change by itself!

Susan: Okay. I just thought that each address represented 2 bytes for some
reason. Then in reality each address always has just 1 byte?

Steve: Every byte of RAM has a distinct address, and there is one address for
each byte of RAM. However, it is often necessary to read or write more than one
byte at a time, as in the case of a short , which is 2 bytes in length. The machine
instructions that read or write more than 1 byte specify only the address of the first
byte of
the item to be read or written; the other byte or bytes of that item follow the first
byte immediately in memory.

Susan: Okay, this is why I was confused. I thought when you specified that the
RAM address 1000 was a short (2 bytes), it just made room for 2 bytes. So when
you specify address 1000 as a short , you know that 1001 will also be occupied
with what you put in 1000 .

Steve: Or to be more precise, location 1001 will contain the second byte of the
short value that starts in byte 1000 .

The next line is blank, so we skip it. This brings us to the statement i
= 5; which is an executable statement, so we need to generate one or
more machine instructions to execute it. We have already assigned
address 1000 to i , so we have to generate instructions that will set the
2 bytes starting at address 1000 to the value that represents 5 . One
way to do this is to start by setting ax to 5 , by the instruction mov ax,5 ,
then storing the contents of ax ( 5 , of course) into the two-byte
location where the value of i is kept, namely the two bytes starting at
address 1000 , via the instruction mov [1000],ax .
Figure 3.9 shows what our “memory map” looks like so far.

FIGURE 3.9. Compiling, part 3

Address Variable Value


Name
1000 i ?? ??
1002 j ?? ??
Address Machine Assembly
Instruction Language
Equivalent
2000 b8 05 00 mov ax, 5
2003 a9 00 10 mov [1000],ax

Here’s the next installment of my discussion with Susan on this topic:


Susan: When you use ax in an instruction, that is a register, not RAM?

Steve: Yes.

Susan: How do you know you want that register and not another one? What are
the differences in the registers? Is ax the first register that data will go into?

Steve: For our current purposes, all of the 16-bit general registers ( ax , bx , cx ,
dx , si , di , bp ) are the same. Some of them have other uses, but all of them can
be used for simple arithmetic such as we’re doing here.

Susan: How do you know that you are not overwriting something more important
than what you are presently writing?

Steve: In assembly language, the programmer has to keep track of that; in the
case of a compiled language, the compiler takes care of register allocation for you,
which is another reason to use a compiler rather than writing assembly language
programs yourself.

Susan: If it overwrites, you said important data will go somewhere else. How will
you know where it went? How does it know whether what is being overwritten is
important? Wait. If something is overwritten, it isn’t gone, is it? It is just moved,
right?

Steve: The automatic movement of data that you’re referring to applies only to
cached data being transferred to RAM. That is, if a slot in the cache is needed, the
data that it previously held is written out to RAM without the programmer’s
intervention. However, the content of registers is explicitly controlled by the
programmer (or the compiler, in the case of a compiled language). If you write
something into a register, whatever was there before is gone. So don’t do that if you
need the previous contents!
Susan: OK. Now I have another question: How do you know that the value 5
will require 2 bytes?

Steve: In C++, because it’s a short . In assembly language, because I’m loading
it into ax , which is a 2-byte register.

Susan: That makes sense. Now why do the variable addresses start at 1000 and
the machine addresses start at 2000 ?

Steve: It’s arbitrary; I picked those numbers out of the air. In a real program, the
compiler decides where to put things.

Susan: What do you mean by machine address? What is the machine? Where are
the machine addresses?

Steve: A machine address is a RAM address. The machine is the CPU. Machine
addresses are stored in the instructions so the CPU knows which RAM location
we’re referring to.

Susan: We talked about storing instructions before; is this what we are doing here?
Are those instructions the “machine instructions”?

Steve: Yes.

Susan: Now, this may sound like a very dumb question, but please tell me where
5 comes from? I mean if you are going to move the value of 5 into the register
ax , where is 5 hiding to take it from and to put it in ax ? Is it stored somewhere in
memory that has to be moved, or is it simply a function of the user just typing in that
value?

Steve: It is stored in the instruction as a literal value. If you look at the assembly
language illustration on page 86, you will see that the mov ax,5 instruction translates
into the three bytes b8 05 00 ; the 05 00 is the 5 in “little-endian” notation.
Susan: Now, what is so magical about ax (or any register for that matter) that
will transform the address 1000 to hold the value of 5 ?

Steve: The register doesn’t do it; the execution of the instruction mov [1000],ax
is what sets the memory starting at address 1000 to the value 5 .

Susan: What are those numbers supposed to be in the machine instruction box?
Those are bytes? Bytes of what? Why are they there? What do they do?

Steve: They represent the actual machine language program as it is executed by


the CPU. This is where “the rubber meets the road”. All of our C++ or even
assembly language programs have to be translated into machine language before
they can be executed by the CPU.

Susan: So this is where 5 comes from? I can’t believe that there seems to be
more code. What is b8 supposed to be? Is it some other type of machine language?

Steve: Machine language is exactly what it is. The first byte of each instruction is
the “operation code”, or “op code” for short. That tells the CPU what kind of
instruction to execute; in this case, b8 specifies a “load register ax with a literal
value” instruction. The literal value is the next 2 bytes, which represent the value 5
in “little-endian” notation; therefore, the full translation of the instruction is “load ax
with the literal value 5 ”.

Susan: So that is the “op code”? Okay, this makes sense. I don’t like it, but it
makes sense. Will the machine instructions always start with an op code?

Steve: Yes, there’s always an op code first; that’s what tells the CPU what the
rest of the bytes in the instruction mean.
Susan: Then I noticed that the remaining bytes seem to hold either a literal value
or a variable address. Are those the only possibilities?

Steve: Those are the ones that we will need to concern ourselves with.

Susan: I don’t understand why machine addresses aren’t in 2-byte increments


like variable addresses.

Steve: Variable addresses aren’t always in 2-byte increments either; it just


happens that short variables take up 2 bytes. Other kinds of variables can and often
do have other lengths.

Susan: So even though variable addresses are the same as instruction addresses
they really aren’t because they can’t share the same actual address. That is why you
distinguish the two by starting the instruction addresses at 2000 in the example and
variable addresses at 1000 , right?

Steve: Right. A particular memory location can hold only one data item at a time.
As far as RAM is concerned, machine instructions are just another kind of data. If a
particular location is used to store one data item, you can’t store anything else there
at the same time, whether it’s instructions or data.

The last statement, j = i + 3; is the most complicated statement in our


program, and it’s not that complicated. As with the previous
statement, it’s executable, which means we need to generate machine
instructions to execute it. Because we haven’t changed ax since we
used it to initialize the variable i with the value 5 , it still has that
value. Therefore, to calculate the value of j , we can just add 3 to the
value in ax by executing the instruction add ax,3 . After the execution of
this instruction, ax will contain i + 3 . Now all we have to do is to
store that value in j . As indicated in the translation of the statement
short j; the address used to hold the value of j is 1002 . Therefore, we
can set j to the value in ax by executing the instruction mov [1002],ax .
By the way, don’t be misled by this example into thinking that all
machine language instructions are 3 bytes in length. It’s just a
coincidence that all of the ones I’ve used here are of that length. The
actual size of an instruction on the Intel CPUs can vary considerably,
from 1 byte to a theoretical maximum of 12 bytes. Most instructions in
common use, however, range from 1 to 5 bytes.
Figure 3.10 shows what the “memory map” looks like now.

FIGURE 3.10. Compiling, part 4

Address Variable Value


Name
1000 i ?? ??
1002 j ?? ??
Address Machine Assembly
Instruction Language
Equivalent
2000 b8 05 00 mov ax, 5
2003 a9 00 10 mov [1000],ax
2006 05 03 00 add ax,3
2009 a9 02 10 mov [1002],ax

Here’s the rest of the discussion that we had about this little exercise:

Susan: In this case mov means add, right?

Steve: No, mov means “move” and add means “add”. When we write mov
ax,5, it means “move the value 5 into the ax register”. The instruction add ax,3
means “add 3 to the current contents of ax , replacing the old contents with this
new value”.

Susan: So you’re mov ing 5 but add ing 3? How do you know when to use mov
and when to use add if they both kind of mean the same thing?
Steve: It depends on whether you want to replace the contents of a register
without reference to whatever the contents were before ( mov ) or add something to
the contents of the register ( add ).

Susan: OK, here is what gets me: how do you get from address 1000 and i=5
to ax ? No, that’s not it; I want you to tell me what is the relationship between ax
and address 1000. I see ax as a register and that should contain the addresses, but
here you are adding ax to the address. This doesn’t make sense to me. Where are
these places? Is address 1000 in RAM?

Steve: The ax register doesn’t contain an address. It contains data. After the
instruction mov ax,5 , ax contains the number 5 . After the instruction mov
[1000],ax , memory location 1000 contains a copy of the 2-byte value in register
ax ; in this case, that is the value of the short variable i .

Susan: So do the machine addresses represent actual bytes?

Steve: The machine addresses specify the RAM locations where data (and
programs) are stored.

Executing Our Little Program Fragment

Having examined what the compiler does at compile time with the
preceding little program fragment, let’s see what happens when the
compiled program is executed at run time. When we start out, the
sections of RAM we’re concerned with will look like Figure 3.11; in
each of these figures, the italic text indicates the next instruction to be
executed.Now let’s start executing the program. The first instruction,
mov ax,5 , as we saw earlier, means “set the contents of ax to the value
5 ”.
Figure 3.12 shows the situation after mov ax,5 is executed. As you
can see, executing mov ax,5 has updated the contents of ax , and we’ve
advanced to the next instruction, mov [1000],ax . When we have executed
that instruction, the situation looks like Figure 3.13, with
the variable i set to 5. Figure 3.14 shows the result after the following
instruction, add ax,3 , is executed.

FIGURE 3.11. Before execution

Address Variable Contents


Name
1000 i ?? ??
1002 j ?? ??
-- ax ?? ??
Address Machine Assembly
Instruction Language
Equivalent
2000 b8 05 00 mov ax, 5
2003 a9 00 10 mov [1000],ax
2006 05 03 00 add ax,3
2009 a9 02 10 mov [1002],ax
FIGURE 3.12. After the first
instruction is executed

Address Variable Contents


Name
1000 i ?? ??
1002 j ?? ??
-- ax 5
Address Machine Assembly
Instruction Language
Equivalent
2000 b8 05 00 mov ax, 5
2003 a9 00 10 mov [1000],ax
2006 05 03 00 add ax,3
2009 a9 02 10 mov [1002],ax

FIGURE 3.13. After execution of


second instruction

Address Variable Contents


Name
1000 i 5
1002 j ?? ??
-- ax 5
Address Machine Assembly
Instruction Language
Equivalent
2000 b8 05 00 mov ax, 5
2003 a9 00 10 mov [1000],ax
2006 05 03 00 add ax,3
2009 a9 02 10 mov [1002],ax
FIGURE 3.14. After execution of third instruction

Address Variable Contents


Name
1000 i 5
1002 j ?? ??
-- ax 8
Address Machine Assembly
Instruction Language
Equivalent
2000 b8 05 00 mov ax, 5
2003 a9 00 10 mov [1000],ax
2006 05 03 00 add ax,3
2009 a9 02 10 mov [1002],ax

As expected, add ax,3 has increased the contents of ax by the value 3,


producing 8 . Now we’re ready for the final instruction.

FIGURE 3.15. After execution of final instruction

Address Variable Contents


Name
1000 i 5
1002 j 8
-- ax 8
Address Machine Assembly
Instruction Language
Equivalent
2000 b8 05 00 mov ax, 5
2003 a9 00 10 mov [1000],ax
2006 05 03 00 add ax,3
2009 a9 02 10 mov [1002],ax
Figure 3.15 shows the situation after the final instruction, mov
[1002],ax , has been executed.
After executing the final instruction, mov [1002],ax , the variable i
has the value 5 and the
variable j has the value 8.

3.8. The char and string Types

This should give you some idea of how numeric variables and values
work. But what about nonnumeric ones?
This brings us to the subject of two new variable types and the
values they can contain. These are the char (short for “character”) and
its relative, the string . What are these good for, and how do they work?
22

A variable of type char corresponds to 1 byte of storage. Since a


byte has 8 bits, it can hold any of 256 (28) values; the exact values
depend on whether it is signed or unsigned , as with the short variables we
have seen before.23 Going strictly according to this description, you
might get the idea that a char is just a “really short” numeric variable.
A char indeed can be used for this purpose in cases where no more than
256 different numeric values are to be represented. In fact, this
explains why you might want a signed char. Such a variable can be used
to hold numbers from –128 to +127; an unsigned char , on the other hand,
has a range from 0 to 255. This facility isn’t used very much any more,
but in the early days of C, memory was very expensive and scarce, so
it was sometimes worth the effort to use 1- byte variables to hold
small values.
However, the main purpose of a char is to represent an individual
letter, digit, punctuation mark, “special character” (e.g., $, @, #, %,
22. In case you were wondering, the most common pronunciation of char has an
a like the a in “married”, while the ch sounds like “k”.
23. Again, the C++ language does not require that a byte have exactly eight bits,
just that it has at least eight bits. On the other hand, by definition a char variable
occupies exactly one byte of storage, however big a byte may be.
and so on) or one of the other “printable” and displayable units from
which words, sentences, and other textual data such as this paragraph
are composed.24
These 256 different possibilities are plenty to represent any
character in English, as well as a number of other European languages.
But the written forms of “ideographic” languages, such as Chinese,
consist of far more than 256 characters, so 1 byte isn’t big enough to
hold a character in these languages. While they have been supported to
some extent by schemes that switch among a number of sets of 256
characters each, such clumsy approaches to the problem made
programs much more complicated and error prone. As the
international market for software is increasing rapidly, it has become
more important to have a convenient method of handling large
character sets; as a result, a standard method of representing the
characters of such languages by using 2 bytes per character has been
developed. It’s called the “Unicode standard”. There’s even a
proposed solution that uses 32 bits per character, for the day when
Unicode doesn’t have sufficient capacity.
Since one char isn’t good for much by itself, we often use groups of
them, called strings , to make them easier to handle. Just as with
numeric values, these variables can be set to literal values which
represent themselves. Figure 3.16 is an example of how to specify and
use each of these types we’ve just encountered. This is the first
complete program we’ve seen, so there are a couple of new constructs
that I’ll have to explain to you.
By the way, in case the program in Figure 3.16 doesn’t seem very
useful, that’s because it isn’t; i t’s just an example of the syntax of
defining and using variables and literal values. However, we’ll use
these constructs to do useful work later, so going over them now isn’t a
waste of time.

24. As we will see shortly, not all characters have visible representations; some of
these “nonprintable” characters are useful in controlling how our printed or
displayed information looks.
FIGURE 3.16. Some real
char acters and strings (code\basic00.cpp)

#include <string> using


namespace std;

int main()
{
char c1;
char c2;
string s1;
string s2;

c1 = ‘A’;
c2 = c1;

s1 = “This is a test “; s2 =
“and so is this.”;

return 0;
}

Why do we need the line #include <string> ? Because we have to tell the
compiler that we want to manipulate strings ; the code that allows us to
do that isn’t automatically included with our programs unless we ask
for it. For the moment, it’s enough to know that including
<string> is necessary to tell the compiler that we want to use strings ;
we’ll get into some details of this mechanism later.

3.9. using, namespace, and std

The next line, using namespace std; , will be present in nearly all of our
programs. It tells the compiler to treat the names in the standard
library, a very important part of the C++ language definition, as
though we had defined them in the current program. These names from
the standard library include string , which is why we need using
namespace std; here. I’ll go into this in much more detail later, but in the
meantime, here’s a little information about using , namespace , and std .
A namespace is a collection of identifiers (variable names and some
other types of names that we haven’t discussed yet) that all belong to a
“family” of sorts. To refer to a specific identifier that belongs to a
namespace , you can prefix the identifier with the name of the namespace
followed by two colons.
For example, all of the identifiers in the C++ standard library are
in the namespace called std . So, for example, if we want to refer to the
version of string that is in the namespace called std , we can refer to it
by the name std::string .
However, because the standard library identifiers are used so
frequently, it is annoying to have to say std:: every time we want to
refer to one of them. The designers of the language have anticipated
this problem and have provided the using facility to allow us to write
our programs in a more natural way. What using means is that we want
the compiler to add a certain name or set of names from a particular
namespace to the currently accessible set of names. In this particular
case, using namespace std; means that we want the compiler to make all
the names in the standard library accessible. If we left that line out,
whenever we wanted to create a variable of string type, we would have
to write std::string rather than just string , and the same would be true of
the other types defined in the standard library that we will be using
later. I think we have enough details to keep track of without having to
worry about that particular one, so I have included the line using
namespace std; in all of the example programs except where I have a
specific reason not to do so; I’ll note those cases when we get to them.
As you can probably imagine, Susan wasn’t entirely satisfied with
this explanation, even for the time being. Here’s the discussion we had
about it:
Susan: I am not sure I understand this. In other words you just say using
namespace std; and then every variable you use is going to be from the standard
library?

Steve: Not quite. When we say using namespace std; , that means "add the
names from the standard library to the names we have available for use". For
example, after that statement, if I say cout , I mean std::cout , and if I say string , I
mean std::string . Names not defined in the standard library aren't affected by this.

Susan: Isn't that what I said? My question is, if you don't want to use the standard
library, how do you indicate that?

Steve: No, what you said is that "every" variable is from the standard library. It's
only things that have names defined in the standard library that are affected.
However, if you have something called "string", for example, that is defined in the
standard library and also in your code, you can say "::string" if you don't want to use
the one from the standard library. We'll see an example of this in “The Scope
Resolution Operator” on page 273 in Chapter 5.

Susan: Okay.

3.10. int main()

The next construct we have to examine is the line int main() , which has
two new components. The first is the “return type”, which specifies
the type of value that will be returned from the program when it ends.
In this case, that type is int , which is an integral type exactly like
short , except that its size depends on the compiler that you're using.25
With the 32-bit compiler on the CD in this book, an int is 32 bits, or
twice the size of a short . With a 16-bit compiler such as

25. An integral type is a type of data that can hold an integer. This includes
short , int , and char , the last of these mostly for historical reasons.
Borland C++ version 3.1, an int is the same size as a short , whereas on a
64-bit compiler an int would be 64 bits in length. I don't like to use
int s where a short will do, because I want to minimize the changes in
behavior of my code with compilers that use different word lengths.
While it’s true that there isn’t much new development that uses 16-bit
compilers anymore, it is also true that one of these days we’ll all
probably be using 64-bit compilers, and I would like my code to be as
portable to them as possible.26 However, we don't have much choice
here, because the C++ language specifies that main has to have the
return type int .
This brings us to the meaning of main() . This tells the compiler
where to start executing the code: C++ has a rule that execution
always starts at the place called main .27
We’ll get into this in more detail later in this chapter and in
Chapter 5. For now, you’ll just have to take my word that this is
necessary; I promise I’ll explain what it really means when you have
enough background to understand the explanation.
There’s one more construct I should tell you about here: the curly
braces, { and } . The first one of these starts a section of code, in this
case the code that belongs to main, and the second one ends that
section of code. This is needed because otherwise the compiler
wouldn’t be able to tell where the code for main begins and ends. The
curly braces also have other uses, one of which we’ll get to later in
this chapter.28
You may also be puzzled by the function of the other statements in
this program. If so, you’re not alone. Let’s see the discussion that
Susan and I had about that topic.

26. Unfortunately, there is no guarantee that shorts will still be 16 bits on a 64-bit
compiler, but there isn’t much I can do about that.
27. Actually, this is an oversimplification. Some code that we write may be
executed before the beginning of main, but only under unusual circumstances
that we will not encounter in this book. If you’re burning with curiosity as to
how this can be done (and why), there’s an explanation in the section entitled
“Executing Code before the Beginning of main” on page 744.
Susan: Okay, in the example why did you have to write c2 = c1; ? Why not B ?
Why make one thing the same thing as the other? Make it different. Why would you
even want c2=c1; and not just say c1 twice, if that is what you want?

Steve: It’s very hard to think up examples that are both simple enough to explain
and realistic enough to make sense. You’re right that this example doesn’t do
anything useful; I’m just trying to introduce what both the char type and the string
type look like.

Susan: Also, do the names of the variables mean anything? Do the


c in c1 and the s in s1 stand for anything?

Steve: Yes, the c and s stand for “char” and “string”, respectively.

Susan: Okay, that makes more sense now. But come to think of it, what does
c1=’A’; have to do with the statement s1= “This is a test "; ? I don’t see any
relationship between one thing and the other.

Steve: This is the same problem as the last one. They have nothing to do with one
another; I’m using an admittedly contrived example to show how these variables are
used.

Susan: I am glad now that your example of chars and strings (put together)
didn’t make sense to me. That is progress; it wasn’t supposed to.

28. If you look at someone else’s C++ program, you’re likely to see a different
style for lining up the {} to indicate where a section of code begins and ends.
As you’ll notice, my style puts the { and } on separate lines rather than
running them together with the code they enclose, to make them stand out, and
indents them further than the conditional statement that controls the section of
code. I find this the clearest, but this is a matter where there is no consensus.
The compiler doesn’t care how you indent your code or whether you do so at
all; it’s a stylistic issue.
What does this useless but hopefully instructive program do? As is
always the case, we have to tell the compiler what the types of our
variables are before we can use them. In this case, c1 and c2 are of
type char , whereas s1 and s2 are strings . After taking care of these
formalities, we can start to use the variables. In the first executable
statement, c1 = ’A’; we set the char variable c1 to a literal value, in this
case a capital A; we need to surround this with single quotation marks
( ’ ) to tell the compiler that we mean the letter A rather than a variable
named A . In the next line, c2 = c1; we set c2 to the same value as c1 ,
which of course is ’A’ in this case. The next executable statement s1 =
"This is a test "; as you might expect, sets the string variable s1 to the
value " This is a test ",29 which is a literal of a type called a C string
literal. Don’t confuse a C string literal with a string . A C string literal
is a type of literal that we use to assign values to variables of type
string . In the statement s1 = "This is a test "; we use a quotation mark, in this
case the double quote ( " ), to tell the compiler where the literal value
starts and ends.
You may be wondering why we need two different kinds of quotes
in these two cases. The reason is that there are actually two types of
nonnumeric data, fixed-length data and variable-length data. Fixed-
length data are relatively easy to handle in a program, as the compiler
can set aside the correct amount of space in advance. Variables of type
char are 1 byte long and can thus contain exactly one character; as a
result, when we set a char to a literal value, as we do in the line c1 =
’A’; the code that executes that statement has the simple task of copying
exactly 1 byte representing the literal ’A’ to the address reserved for
variable c1 .30
However, C string literals such as "This is a test " are variable-
length data, and dealing with such data isn’t so easy. Since there could
be any number of characters in a C string literal, the code that does the
assignment of a literal value like "This is a test " to a string variable has
to have some way to tell where the literal value ends.

29. Please note that there is a space (blank) character at the end of that C string
literal, after the word "test". That space is part of the literal value.
One possible way to provide this needed information would be for the
compiler to store the length of the C string literal in memory
somewhere, possibly in the location immediately before the first
character in the literal. I would prefer this method; however, it is not
the method used in the C language (and its descendant, the C++
language). To be fair, the inventors of C didn’t make an arbitrary
choice; they had reasons for their decision on how to indicate the
length of a string. You see, if we were to reserve only 1 byte to store
the actual length in bytes of the character data in the string, then the
maximum length of a string would be limited to 255 bytes. This is
because the maximum value that could be stored in the length byte, as
in any other byte, is 255. Thus, if we had a string longer than 255
bytes, we would not be able to store the length of the string in the 1
byte reserved for that purpose. On the other hand, if we were to
reserve 2 bytes for the length of each string, then programs that contain
many strings would take more memory than they otherwise would.
While the extra memory consumption that would be caused by
using a 2-byte length code may not seem significant today, the situation
was considerably different when C was invented. At that time,
conserving memory was very important; the inventors of C therefore
chose to mark the end of a C string literal by a byte containing the
value 0, which is called a null byte.31 This solution has the advantage
that only one extra byte is needed to indicate the end of a C string
literal of any length. However, it also has some

30. Warning: Every character inside the quotes has an effect on the value of the
literal, whether the quotes are single or double; even “invisible” characters such
as the space (‘ ’) will change the literal’s value. In other words, the line c1 =
’A’; is not the same as the line c1 = ’A ’;. The latter statement may or may not be
legal, depending on the compiler you’re using, but it is virtually certain not to give
you what you want, which is to set the variable c1 to the value equivalent to the
character ’A’ . Instead, c 1 will have some weird value resulting from
combining the ’A’ and the space character. In the case of a string value
contained in double quotes, multiple characters are allowed, so "A B" and "AB"
both make sense, but the space still makes a difference; namely, it keeps the
’A’ and ’B’ from being next to one another.
serious drawbacks. First, this solution makes it impossible to have a
byte containing the value 0 in the middle of a C string literal, as all of
the C string literal manipulation routines would treat that null byte as
being the end of the C string literal. Second, it is a nontrivial operation
to determine the length of a C string literal; the only way to do it is to
scan through the C string literal until you find a null byte. As you can
probably tell, I’m not particularly impressed with this mechanism;
nevertheless, as it has been adopted into C++ for compatibility with
C, we’re stuck with it for literal strings in our programs.32 Therefore,
the literal string "ABCD" would occupy 5 bytes, 1 for each character,
and 1 for the null byte that the compiler adds automatically at the end
of the literal. But we’ve skipped one step: How do we represent
characters in memory? There’s no intuitively obvious way to convert
the character ’A’ into a value that can be stored in 1 byte of memory.
The answer, at least for our purposes in English, is called the
ASCII code standard. This stands for American Standard Code for
Information Interchange, which as the name suggests was invented to
allow the interchange of data between different programs and makes
of computers. Before the invention of ASCII, such interchange was
difficult or impossible, since every manufacturer made up its own
code or codes. Here are the specific character codes that we have to
be concerned with for the purposes of this book:
1. The codes for the capital letters start with hex 41 for ’A’ , and run
consecutively to hex 5a for ’Z’ .
2. The codes for the lower case letters start with hex 61 for ’a’ , and
run consecutively to hex 7a for ’z’ .33
31. I don’t want to mislead you about this notion of a byte having the value 0; it is
not the same as the representation of the decimal digit "0". As we’ll see, each
displayable character (and a number of invisible ones) is assigned a value to
represent it when it’s part of a string or literal value (i.e., a C string literal or
char literal). The 0 byte I’m referring to is a byte with the binary value 0.
32. Happily, we can improve on it in most other circumstances, as you’ll see later.
3. The codes for the numeric digits start with hex 30 for ’0’ , and run
consecutively to hex 39 for ’9’ .

Given these rules, the memory representation of the string "ABCD"


might look something like Figure 3.17.

FIGURE 3.17. Yet another small


section of RAM
Address Hex byte value
1000 41
1001 42
1002 43
1003 44
1004 00 (null byte; that is, end of C string literal)

Now that we see how strings are represented in memory, I can explain
why we need two kinds of quotes. The double quotes tell the compiler
to add the null byte at the end of the string literal, so that when the
assignment statement s1 = "This is a test "; is executed, the program knows
when to stop copying the value to the string variable.

A Byte by Any Other Name...

Have you noticed that I’ve played a little trick here? The illustration
of the string "ABCD" should look a bit familiar; its memory contents
are exactly the same as in Figure 3.2, where we were discussing
numeric variables. I did this to illustrate an important point: the

33. You may wonder why I have to specify that the codes for each case of letters
run consecutively. Believe it or not, there are a number of slightly differing
codes collectively called EBCDIC (Extended Binary Coded Decimal
Interchange Code), in which this is not true! Eric Raymond’s amusing and
interesting book, The New Hacker’s Dictionary, has details on this and many
other historical facts.
contents of memory actually consists of uninterpreted bytes, which
have meaning only when used in a particular way by a program. That
is, the same bytes can represent numeric data or characters, depending
on how they are referred to.
This is one of the main reasons why we need to tell the C++
compiler what types our variables have. Some languages allow
variables to be used in different ways at different times, but in C++
any given variable always has the same type; for example, a char
variable can’t change into a short . At first glance, it seems that it
would be much easier for programmers to be able to use variables any
way they like; why is C++ so restrictive?
The C++ type system, as this feature of a language is called, is
specifically designed to minimize the risk of misinterpreting or
otherwise misusing a variable. It’s entirely too easy in some
languages to change the type of a variable without meaning to and the
resulting errors can be very difficult to find, especially in a large
program. In C++, the usage of a variable can be checked by the
compiler. Such static type checking allows the compiler to tell you
about many errors that otherwise would not be detected until the
program is running (dynamic type checking). This is particularly
important in systems that need to run continuously for long periods of
time. While you can reboot your machine if your word processor
crashes due to a run-time error, this is not acceptable as a solution for
errors in the telephone network, for example.
Of course, you probably won’t be writing programs demanding
that degree of reliability any time soon, but strict static type checking
is still worthwhile in helping eliminate errors at the earliest possible
stage in the development of our programs.

Nonprinting Characters

After that infomercial for the advantages of static type checking, we


can resume our examination of strings. You may have noticed that
there’s a space character at the end of the string "This is a test " . That’s
another reason why we have to use a special character like "
(the double quote) to mark the beginning and end of a string; how else
would the compiler know whether that space is supposed to be part of
the string? The space character is one of the nonprinting characters
(or nondisplay characters) that controls the format of our displayed
or printed information; imagine how hard it would be to read this book
without space characters! While we’re on the subject, I should also
tell you about some other characters that have special meaning to the
compiler. They are listed in Figure 3.18.
I’ll be more specific about the uses of parentheses later, when we
have seen some examples. As for the backslash, if you wanted to (for
example) insert a " in a string, you would have to use \" , because just a
plain " would indicate the end of the string. That is, if you wanted to
display This is a "string". , you would have to write the value of the string
as "This is a \"string\"."
I compiled Figure 3.18 at the instigation of guess who:

Susan: How about you line up all your cute little " ’ \ ; things and just list their
meanings? I forget what they are by the time I get to the next one. Your
explanations of them are fine, but they are scattered all over; I want one place with
all the explanations.

Steve: That’s a good idea. As usual, you’re doing a good job representing the
novices; keep up the good work!
FIGURE 3.18. Special characters for program text

Name Graphic Purpose


Single quote ’ surrounds a single character
value
Double quote " surrounds a multi-character
value
Semicolon ; ends a statement
Curly braces {} groups statements together
Parentheses () surrounds part of a statement
Backslash \ Tells the compiler that the next
character should be treated
differ- ently from the way that it
would normally be treated.

Our next task, after a little bit of practice with the memory
representation of a C string literal, will be to see how we get the
values of our strings to show up on the screen.

3.11. Exercises, Second Set


2. Assume that a C string literal starts at memory location 1001. If
the contents of memory are as illustrated in Figure 3.19, what is
the value of the C string literal?

FIGURE 3.19. A small section of RAM

Address Hex byte value


1000 44
1001 48
1002 45
1003 4c
1004 4c
1005 4f
1006 00

3.12. Input and Output

Most programs need to interact with their users, both to ask them what
they want and to present the results when they are available. The
computer term for this topic is I/O (short for “input/output”). We’ll
start by getting information from the keyboard and displaying it on the
screen; later, we’ll go over the more complex I/O functions that allow
us to read and write data on the disk.
The program in Figure 3.20 displays the very informative text
" This is a test and so is this. ". The meaning of << is suggested by its
arrowlike shape: The information on its right is sent to the “output
target” on its left. In this case, we’re sending the information to a
predefined destination, cout , which stands for “character output”.34
Characters sent to cout are displayed on the screen.

FIGURE 3.20. Some simple output


(code\basic01.cpp)

#include <iostream>
#include <string> using
namespace std;

int main()
{
string s1;
string s2;

s1 = “This is a test “;

34. The line #include <iostream> is necessary here to tell the compiler about
cout and how it works. We’ll get into this in a bit more detail shortly.
s2 = “and so is this.”;

cout << s1;


cout << s2;

return 0;
}

This program will send the following output to the screen:


This is a test and so is this.

So much for simple output. Input from the keyboard can be just as
simple. Modifying our little sample to use it results in Figure 3.21.

FIGURE 3.21. Some simple input and output (code\basic02.cpp)

#include <iostream>
#include <string> using
namespace std;

int main()
{
string s1; string
s2;

cin >> s1;


cin >> s2;

cout << s1;


cout << “ “;
cout << s2;

return 0;
}

As you might have guessed, cin (shorthand for “character input”) is the
counterpart to cout as >> is the counterpart to << ; cin supplies
characters from the keyboard to the program via the >> operator.35
This program will wait for you to type in the first string , ended by
hitting the ENTER key or the space bar.36 Then it will wait for you to
type in the second string and hit ENTER. Then the program will display
the first string , followed by a blank, and then the second string .
Susan had some questions about these little programs, beginning
with the question of case sensitivity:

Susan: Are the words such as cout and cin case sensitive? I had capitalized a
few of them just out of habit because they begin the sentence and I am not sure if
that was the reason the compiler gave me so many error messages. I think after I
changed them I reduced a few messages.

Steve: Everything in C++ is case sensitive. That includes keywords like if ,


for , do , and so on, as well as your own variables. That is, if you have a variable
called Name and another one called name , those are completely different and
unrelated to one another. You have to write cin and cout just as they appear here,
or the compiler won’t understand you.

3.13. Changing the Course of Execution

In our examples so far, the program always executes the same


statements in the same order. However, any real program is going to
need to alter its behavior according to the data it is processing. For
example, in a banking application, it might be necessary to send out a
35. In case you were wondering, co u t is pronounced “see out”, while c i n is
pronounced “see in”.
36. The reason a space ends a string is that the designers of the C++ standard
library thought that would be more convenient than having everything up to the
end of a line read into one string. I don’t agree with this, but the advantages of
using the standard library generally outweigh its disadvantages, so we just have
to put up with any inconveniences that it causes.
notice to a depositor whenever the balance in a particular account
drops below a certain level; or perhaps the depositor would just be
charged some exorbitant fee in that case. Either way, the program has
to do something different depending on the balance. In particular, let’s
suppose that the “Absconders and Defaulters National Bank” has a
minimum balance requirement of $10,000.37 Furthermore, let’s assume
that if you have less than that amount on deposit, you are charged a
$20 “service charge”. However, if you are foolish enough to leave that
ridiculous amount of money on deposit, then they will graciously
allow you to get away with not paying them while they’re using your
money (without paying you interest, of course). To determine whether
you should be charged for your checking account, the bank can use an
if statement, as shown in Figure 3.22.

FIGURE 3.22. Using an if statement (code\basic03.cpp)

#include <iostream> using


namespace std;

int main()
{
short balance;

cout << “Please enter your bank balance: “; cin >>


balance;

if (balance < 10000)


cout << “Please remit $20 service charge.” << endl; else
cout << “Have a nice day!” << endl; return 0;

37. If you think the name of that bank is funny, you should get a copy of The Most
of S. J. Perelman (ISBN 0-671-41871-8). Unfortunately, it’s out of print, but
you might be able to find a used one at Amazon.com or another bookseller.
}

This program starts by displaying the line


Please enter your bank balance:

on the screen. Then it waits for you to type in your balance, followed
by the ENTER key (so it knows when you’re done). The conditional
statement checks whether you’re a “good customer”. If your balance is
less than $10,000, the next statement is executed, which displays

Please remit $20 service charge. 38

The phrase << endl is new here. It means “we’re done with this line of
output; send it out to the screen”. You could also use the special
character ’\n’ (“newline”), which means much the same thing.
Now let’s get back to our regularly scheduled program. If the
condition is false (that is, you have at least $10,000 in the bank), the
computer skips the statement that asks you to remit $20; instead, it
executes the one after the else , which tells you to have a nice day.
That’s what else is for; it specifies what to do if the condition
specified in the if statement is false (that is, not true ). If you typed in a
number 10000 or higher, the program would display the line
Have a nice day!

You don’t have to specify an else if you don’t want to. In that case, if
the if condition isn’t true , the program just goes to the next statement
as though the if had never been executed.

38. This explanation assumes that the “10000” is the balance in dollars. Of course,
this doesn’t account for the possibility of balances that aren’t a whole number
of dollars, and there’s also the problem of balances greater than
$32767, which wouldn’t fit into a short . As we’ll see in Chapter 9, both of these
problems can be solved by using a different data type called double .
3.14.The while Loop

The while statement is another way of affecting the order of program


execution. This conditional statement executes the statement under its
control as long as a certain condition is true . Such potentially repeated
execution is called a loop; a loop controlled by a while statement is
called, logically enough, a while loop. Figure 3.23 is a program that
uses a while loop to challenge the user to guess a secret number from 0
to 9, and keeps asking for guesses until the correct answer is entered.

FIGURE 3.23. Using a while statement (code\basic04.cpp)

#include <iostream> using


namespace std;

int main()
{
short Secret;
short Guess;

Secret = 3;

cout << “Try to guess my number. Hint: It’s from 0 to 9” << endl; cin >> Guess;

while (Guess != Secret)


{
cout << “Sorry, that’s not correct.” << endl; cin >> Guess;
}

cout << “You guessed right!” << endl;

return 0;
}
There are a few wrinkles here that we haven’t seen before. Although
the while statement itself is fairly straightforward, the meaning of its
condition, != , isn’t intuitively obvious. However, if you consider the
problem we’re trying to solve, you’ll probably come to the (correct)
conclusion that != means “not equal”, since we want to keep asking
for more guesses while the Guess is not equal to our Secret number.39
Since there is a comparison operator that tests for “not equal”, you
might wonder how to test for “equal”, as well. As is explained in
some detail in the next chapter, in C++ we have to use == rather than
= to compare whether two values are equal.
You might also be wondering whether an if statement with an else
clause would serve as well as the while ; after all, if is used to select
one of two alternatives, and the else could select the other one. The
answer is that this would allow the user to take only one guess before
the program ends; the while loop lets the user try again as many times
as needed to get the right answer.
This example also illustrates another use of the curly braces, {
and } . The first one of these starts a logical section of a program,
called a block, and the second one ends the block. Because the two
statements after the while are part of the same block, they are treated as
a unit; both are executed if the condition in the while is true , and
neither is executed if it is false . A block can be used anywhere that a
statement can be used, and is treated in exactly the same way as if it
were one statement.
Now you should have enough information to be able to write a
simple program of your own. Susan asked for an assignment to do just
that:

Susan: Based on what you have presented in the book so far, send me a setup, an
exercise for me to try to figure out how to program, and I will give it a try. I guess
that is the only way to do it. I can’t

39. Why do we need parentheses around the expression Guess != Secret ? The
conditional expression has to be in parentheses so that the compiler can tell
where it ends, and the statement to be controlled by the while begins.
even figure out a programmable situation on my own. So if you do that, I will do my
best with it, and that will help teach me to think. (Can that be?) Now, if you do this,
make it simple, and no tricks.

Of course, I did give her the exercise she asked for (exercise 3), but
also of course, that didn’t end the matter. She decided to add her own
flourish, which resulted in exercise 4. These exercises follow below.
However, you’ll have to install the compiler before you can write
a program. To do this, follow the instructions in the readme.txt file on
the CD.
Once you have followed those instructions, including successfully
compiling one of the sample programs, use a text editor such as
Notepad to write the source code for your program. Save the source
code as myprog.cpp . Then follow the instructions in the readme.txt file on
the CD to compile the source code. The resulting program will be
called myprog.exe . To run your program normally from a DOS prompt,
make sure you are in the “\dialog\code” directory, and then type the
name of the program, without the extension. In this case, you would
just type "myprog". You can also run the program under the debugger
to see how it works in detail, by following the instructions in the
readme.txt file on the CD.
Now here are the programs Susan came up with, along with some
others that fall in the same category.

3.15. Exercises, Third Set


3. Write a program that asks the user to type in the number of people
that are expected for dinner, not counting the user. Assuming that the
number typed in is n , display a message that says "A table for (n+1)
is ready." . For example, if the user types 3, display "A table for 4 is
ready." .

4. Modify the program from exercise 3 to display an error message if the


number of guests is more than 20.
5. Write a program that asks the user to type in his or her first name
and age. If the age is less than 53, then indicate that the user is a
youngster; otherwise, that he or she is getting on in years.
6. Write a program that asks the user whether Susan is the world’s
most tenacious novice. If the answer is “true”, then acknowledge
the user’s correct answer; if the answer is “false”, then indicate
that the answer is erroneous. If neither “true” nor “false” is typed
in, chastise the user for not following directions.
7. Write a program that calculates how much allowance a teenager
can earn by doing extra chores. Her allowance is calculated as $10
if she does no extra chores; she gets $1 additional for each extra
chore she does.

3.16. Our First Slightly Realistic Program

Now we’re ready to write a program that vaguely resembles a


solution to a real problem. We’ll start with a simple, rural type of
programming problem.
Imagine that you are at a county fair. The contest for the heaviest
pumpkin is about to get underway, and the judges have asked for your
help in operating the “pumpkin scoreboard”. This device has a slot for
the current pumpkin weight (the CurrentWeight slot), and another slot for
the highest weight so far (the HighestWeight slot). Each slot can hold
three digits from 0 to 9 and therefore can indicate any weight from 0 to
999. The judges want you to maintain an up-to- date display of the
current weight and of the highest weight seen so far. The weights are
expressed to the nearest pound. How would you go about this task?
Probably the best way to start is by setting the number in both slots
to the weight of the first pumpkin called out. Then, as each new weight
is called out, you change the number in the CurrentWeight slot to match
the current weight; if it’s higher than the number in the
slot, you change that one to match as well. Of course, you
HighestWeight
don’t have to do anything to the HighestWeight slot when a weight less
than the previous maximum weight is called out, because a pumpkin
with a lesser weight can’t be the winner. How do we know when we
are done? Since a pumpkin entered in this contest has to have a weight
of at least 1 pound, the weigher calls out 0 as the weight when the
weighing is finished. At that point, the number in the HighestWeight slot
is the weight of the winner.
The procedure you have just imagined performing can be expressed
a bit more precisely by the following algorithm:
1. Ask for the first weight.
2. Set the number in the CurrentWeight slot to this value.
3. Copy the number in the CurrentWeight slot to the HighestWeight
slot.
4. Display both the current weight and the highest weight so far
(which are the same, at this point)
5. While the CurrentWeight value is greater than 0 (that is, there are more
pumpkins to be weighed), do steps a to d:
a. Ask for the next weight.
b. Set the number in the CurrentWeight slot to this weight.
c. If the number in the CurrentWeight slot is greater than the
number in the HighestWeight slot, copy the number in the
CurrentWeight slot to the HighestWeight slot.

d. Display the current weight and the highest weight so far.


6. Stop. The number in the HighestWeight slot is the weight of the winner.

Figure 3.24 is the translation of our little problem into C++.


FIGURE 3.24. A C++ program
(code\pump1.cpp)

English C++
First, we provide some typical setup information for the beginning of a program
Define the standard #include <iostream>
input and output
func- tionality
Allow standard using namespace std;
names to be used by
default
This is the main part int main()
of the program
Execution starts here {
Define variables short CurrentWeight; short
HighestWeight;
Here’s the start of the executable code
Ask for the first cout << "Please enter the first weight: ";
weight
Set the number in the cin >> CurrentWeight;
CurrentWeight slot to
the value entered by
the user
Copy the number in HighestWeight = CurrentWeight;
the CurrentWeight
slot to the
HighestWeight slot
Display the current cout << "Current weight " << CurrentWeight
and highest weights << endl; cout << "Highest weight " <<
HighestWeight << endl;
While the number in while (CurrentWeight > 0)
the CurrentWeight
slot is greater than 0
(i.e., there are more
pump- kins to be
weighed)
Start repeatedsteps {
Ask for the next cout << “Please enter the next weight: ";
weight
Set the number in cin >> CurrentWeight;
the CurrentWeight
slot to this value
English C++
If the number in the if (CurrentWeight > HighestWeight)
CurrentWeight slot is
more than the
number in the
HighestWeight slot,
then copy the HighestWeight = CurrentWeight;
number in the
CurrentWeight slot to
the Highest- Weight
slot
Display the current cout << "Current weight " << CurrentWeight
and highest weights << endl; cout << "Highest weight " <<
HighestWeight << endl;
End repeated steps in }
while loop
We’ve finished the job; now to clean up
Tell the rest of the return 0;
sys- tem we’re okay
End of program }

Susan had a question about the formatting of the output statement


cout << "Highest weight " << HighestWeight << endl; .

Susan: Why do we need both "Highest weight" and


HighestWeight in this line?

Steve: Because "Highest weight" is displayed on the screen to tell the user that
the following number is supposed to represent the highest weight seen so far. On the
other hand, HighestWeight is the name of the variable that holds that information, so
including HighestWeight in the output statement will result in displaying the highest
weight we’ve seen so far on the screen. Of course, the same analysis applies to the
next line, which displays the label "Current weight" and the value of the variable
CurrentWeight .

You’ve already seen most of the constructs that this program contains,
but we’ll need to examine the role of the preprocessor directive
#include <iostream> , which we’ve used before without
much explanation. A #include statement causes the compiler to pretend that the code in
the included file was typed in instead of the #include statement. In this particular case,
<iostream> is the file that tells the compiler how to use the standard C++
I/O library.
The term preprocessor directive is a holdover from the days when
a separate program called the preprocessor handled functions such as
#include before handing the program over to the compiler. Nowadays,
these facilities are provided by the compiler rather than by a separate
program, but the name has stuck.
In this particular case, <iostream> defines the I/O functions and
variables cout , cin , << , and >> , along with others that we haven’t
used yet. If we left this line out, none of our I/O statements would
work.
Susan also had some questions about variable names:

Susan: Tell me again what the different short s mean in this figure. I am
confused, I just thought a short held a variable like i . What is going on when you
declare HighestWeight a short ? So do the “words” HighestWeight work in the
same way as i ?

Steve: A short is a variable. The name of a short , like the name of any other
variable, is made up of one or more characters; the first character must be a letter or
an underscore ( _ ), while any character after the first must be either a letter, an
underscore, or a digit from 0 to 9. To define a short , you write a line that gives the
name of the short . This is an example:

short HighestWeight ;

Susan: OK, but then how does i take 2 bytes of memory and how does
HighestWeight take up 2 bytes of memory? They look so different, how do you
know that HighestWeight will fit into a short ?

Steve: The length of the names that you give variables has nothing to do with the
amount of storage that the variables take up. After the compiler gets through with
your program, there aren’t any variable
names; each variable that you define in your source program is represented by the
address of some area of storage. If the variable is a short , that area of storage is 2
bytes long; if it’s a char, the area of storage is 1 byte long.

Susan: Then where do the names go? They don’t go “into” the
short ?

Steve: A variable name doesn’t “go” anywhere; it tells the compiler to set aside
an area of memory of a particular length that you will refer to by a given name. If
you write short xyz; you’re telling the compiler that you are going to use a short
(that is, 2 bytes of memory) called xyz .

Susan: If that is the case, then why bother defining the short at all?

Steve: So that you (the programmer) can use a name that makes sense to you.
Without this mechanism, you’d have to specify everything as an address. Isn’t it
easier to say

HighestWeight = CurrentWeight;

rather than
mov ax,[1000]
mov [1002],ax

or something similar?

The topic of #include statements was the cause of some discussion with
Susan. Here’s the play by play:

Susan: Is the include command the only time you will use the #
symbol?

Steve: There are other uses for # , but you won’t see any of them for a long

time.40
Susan: So #include is a command.

Steve: Right; it’s a command to the compiler.

Susan: Then what are the words we have been using for the most part called?
Are those just called code or just statements? Can you make a list of commands to
review?

Steve: The words that are defined in the language, such as if , while ,
for, and the
like are called keywords. User defined names such as function and variable names
are called identifiers.

Susan: So < iostream> is a header file telling the compiler that it is using info from
the iostream library?

Steve: Essentially correct; to be more precise, when we include


< iostream> , we’re telling the compiler to look into < iostream> for definitions that
we’re going to use.

Susan: Then the header file contains the secondary code of machine language to
transform cin and cout into something workable?

Steve: Close, but not quite right. The machine code that makes cin and cout do
their thing is in the iostream part of the standard library. The header file gives the
compiler the information it needs to compile your references to cout , cin , << ,
and >> into references to the machine code in the library.

Susan: So the header file directs the compiler to that section in the library where
that machine code is stored? In other words, it is like telling the compiler to look in
section XXX to find the machine code?

40. Not until “Definitions” on page 856 in Chapter 12, to be exact.


Steve: The header file tells the compiler what a particular part of the library does,
while the library contains the machine code that actually does it.

If you have previous experience as a programmer (other than in C),


you may wonder why we have to tell the compiler that we want to use
the standard I/O library. Why doesn’t the compiler know to use that
library automatically? This seeming oversight is actually the result of
a decision made very early in the evolution of C: to keep the language
itself (and therefore the compiler) as simple as possible, adding
functionality with the aid of standard libraries. Since a large part of
the libraries can be written in C (or C++), this decision reduces the
amount of work needed to “port” the C (or C++) language from one
machine architecture or operating system to another. Once the compiler
has been ported, it’s not too difficult to get the libraries to work on the
new machine. In fact, even the C (or C++) compiler can be written in
C (or C++), which makes the whole language quite portable. This may
seem impossible. How do you get started? In fact, the process is called
bootstrapping, from the impossible task of trying to lift yourself by
your own bootstraps.41 The secret is to have one compiler that’s
already running, then you use that compiler to compile the compiler
for the new machine. Once you have the new compiler running, it is
common to use it to compile itself so that you know it’s working. After
all, a compiler is a fairly complex program, so getting it to compile
and execute properly when i t ’s compiling itself is a pretty good
indication that it’s producing the right code.
Most of the rest of the program should be fairly easy to understand,
except for the two lines int main() and return 0; , which have related
functions. Let’s go into the line int main() in a little more detail than we
have already discussed. As previously mentioned, the purpose of the
main() part of this line is to tell the compiler where to

41. If this term sounds familiar, that’s because we’ve already seen it in the context
of how we start up a computer when it’s turned on, starting from a small boot
program in the ROM, or Read-Only Memory.
start execution; the C++ language definition specifies that execution
always starts at a block called main . This may seem redundant, as you
might expect the compiler to assume that we want to start execution at
the beginning of the program. However, C++ is intended to be useful
in the writing of very large programs; such programs can and usually
do consist of several implementation files, each of which contains
some of the functionality of the program. Without such a rule, the
compiler wouldn’t know which module should be executed first.
The int part of this same line specifies the type of the exit code
that will be returned from the program by a return statement when the
program is finished executing; in this case, that type is int . The exit
code can be used by a batch file to determine whether our program
finished executing correctly, and an exit code of 0, by convention,
means that it did.42 The final statement in the program is return 0; . This
is the return statement just mentioned, whose purpose is to return an exit
code of 0 when our program stops running. The value that is returned,
0 , is an acceptable value of the type we declared in the line int main() ,
namely, int ; if it didn’t match, the compiler would tell us we had made
an error.
Finally, the closing curly brace, } , tells the compiler that it can
stop compiling the current block, which in this case is the one called
main . Without this marker, the compiler would tell us that we have a
missing } , which of course would be true.

Susan Tries to Write the Pumpkin Program Herself

Susan decided a little later in our collaboration that she wanted to try
to reproduce this program just by considering the English description,
without looking at my solution. She didn’t quite make it without
peeking, but the results are illuminating nevertheless.
42. A batch file is a text file that directs the execution of a number of programs,
one after the other, without manual intervention. A similar facility, generically
referred to as scripting, is available in most operating systems.
Susan: What I did was to cover your code with a sheet of paper and just tried to
get the next line without looking, and then if I was totally stumped then I would look.
Anyway, when I saw that if statement then I knew what the next statement would
be but I am still having problems with writing backwards. For example

if (CurrentWeight > HighestWeight)


HighestWeight = CurrentWeight;

That is just so confusing because we just want to say that if the current weight is
higher than the highest weight, then the current weight will be the new highest
weight, so I want to write CurrentWeight = HighestWeight . Anyway, when I really
think about it I know it makes sense to do it the right way; I’m just having a hard time
thinking like that. Any suggestions on how to think backward?

Steve: What that statement means is “set HighestWeight to the current value
of CurrentWeight .” The point here is that = does not mean “is equal to”; it means
“set the variable to the left of the = to the value of the expression to the right of the
= ”. It may not be a very clear way of saying that, but that’s what it means.

Susan: With all the { and } all over the place, I was not sure where and when
the return 0; came in. So is it always right before the last
} ? OK, now that I think about it, I guess it always would be.

Steve: You have to put the return statement at a place where the program is
finished with whatever it was doing. That’s because whenever that statement is
executed, the program is going to stop running. Usually, as in this case, you want to
do that at the physical end of main .

Susan: Anyway, then maybe I am doing something wrong, and I am tired, but
after I compiled the program and ran it, I saw that the HighestWeight label was run
in together with the highest number and the next sentence, which said " Please enter
the next weight ".
All those things were on the same line and I thought that looked weird; I tried to fix it
but the best I had the stamina for at the moment was to put a space between the "
and the P, to at least make a separation.

Steve: It sounds as though you need some endls in there to separate the lines.

Try the Pumpkin Program Yourself

You can try this program out yourself. The name of the source code
file is pump1.cpp , and you can compile it and execute it just as you did
with the sample program when you installed the compiler. If you run
the compiled program under the debugger and wonder about the
seemingly meaningless values that the debugger shows for variables
before the first statement that sets each one to a value, let me assure
you that they are indeed meaningless. I’ll explain why that is in the
next chapter.
We’re almost done with this chapter, but first let’s practice a little
more with chars and strings .

3.17. Exercises, Fourth Set


8. Here are four possible versions of an output statement. Assuming
that the value of the string variable called name is “Joe Smith”,
what does each one of them do?

cout << "That is very old, " << name << ". " << endl; cout << "That

is very old, " << name << ’. ’ << endl; cout << "That is very old, "

<< name << "." << endl; cout << "That is very old, " << name << ’.’

<< endl;
Now it’s time for some review on what we’ve covered in this chapter.

3.18.Review

We started out by discussing the tremendous reliability of computers.


Whenever you hear “it’s the computer’s fault”, the overwhelming
likelihood is that the software is to blame rather than the hardware.
Then we took a look at the fact that although computers are calculating
engines, many of the functions we use them for don’t have much to do
with numeric calculations; for example, the most common use of
computers is probably Web browsing. Nevertheless, we started out
our investigation of programming with numeric variables, which are
easier to understand than non-numeric ones. To use variables, we need
to write a C++ program, which consists primarily of a list of
operations to be performed by the computer, along with directions that
influence how these operations are to be translated into machine
instructions.
That led us into a discussion of why and how our C++ program is
translated into machine instructions by a compiler. We examined an
example program that contained simple source code statements,
including some that define variables and others that use those
variables along with constants to calculate results. We covered the
symbols that are used to represent the operations of addition,
subtraction, multiplication, division, and assignment, which are + ,
–,
* , / , and = respectively. While the first four of these operators
should be familiar to you, the last one is a programming notion rather
than a mathematical one. This may be confusing because the operation
of assignment is expressed by the = sign, but is not the same as
mathematical equality. For example, the statement x = 3; does not mean
“x is equal to 3”, but rather "set the variable x to the value 3 ”. After
this discussion of the structure of statements in C++, we started an
exploration of how the CPU actually stores and manipulates data in
memory. The topics covered in this section included the order in
which multibyte data items are stored in memory and the use of
general registers to manipulate data efficiently.
Then we spent some time pretending to be a compiler, to see how
a simple C++ program looks from that point of view, in order to
improve our understanding of what the compiler does with our
programs. This exercise involved keeping track of the addresses of
variables and instructions and watching the effect of the instructions
on the general registers and memory locations. During this exploration
of the machine, we got acquainted with the machine language
representation of instructions, which is the actual form that our
executable programs take in memory. After a thorough examination of
what the compiler does with our simplified example of source code at
compile time, we followed what would happen to the registers and
memory locations at run time (that is, if the sample code were actually
executed).
Then we began to look at two data types that can hold nonnumeric
data, namely the char and the string . The char corresponds to 1 byte of
storage, and therefore can hold one character of data. Examples of
appropriate values for a char variable include letters (a–z, A–Z),
digits (0–9), and special characters (e.g., , . ! @ # $
%). A char can also represent a number of other “nonprintable”
characters such as the “space”, which causes the output position on the
screen to move to the next character. Actually, a char can also be used
as a “really short” numeric variable, but that’s mostly a holdover from
the days when memory was a lot more expensive, and every byte
counted.
One char doesn’t hold very much information by itself, so we often
want to deal with groups of them as a single unit; an example would
be a person’s name. This is the province of the string variable type:
variables of this type can handle an indefinitely long group of char s.
At the beginning of our sample program for strings and chars , we
encountered a new construct, the #include statement. This tells the
compiler where to find instructions on how to handle data types such
as strings , about which it doesn’t have any built-in knowledge. Then
we came across the line int main() , which indicates where we want to
start executing our program. A C++ program always starts execution at
the place indicated by such a line. We also investigated the meaning of
int , which must always be the return type of main . This return type
tells the compiler what sort of data this program returns to the
operating system when it finishes executing; the return value can be
used to determine what action a batch file should take next.
As we continued looking at the sample program for strings and
chars , we saw how to assign literal values to both of these types, and
noted that two different types of quotes are used to mark off the literal
values. The single quote (') is used in pairs to surround a literal char
value consisting of exactly one char , such as 'a' ; and the double quote
( " ) is used in pairs to surround a literal string value of the C string
literal type, such as "This is a test" .43 We also investigated the reason for
these two different types of literal values, which involves the notion of
a null byte (a byte with the value 0); this null byte is used to mark the
end of a C string literal in memory.
This led us to the discussion of the ASCII code, which is used to
represent characters by binary values. We also looked at the fact that
the same bytes can represent either a numeric value or a C string
literal, depending on how we use those bytes in our program. That’s
why it’s so important to tell the compiler which of these possibilities
we have in mind when we write our programs. The way in which the
compiler regulates our access to variables by their type is called the
type system; C++ uses static type checking, in which types are
determined at compile time rather than being deferred to run time
(dynamic type checking). This is one of the reasons that C++
programs can be made more robust than programs written in languages
that use dynamic type checking.
After a short discussion of some of the special characters that have
a predefined meaning to the compiler, we took an initial glance

43. The C string literal type is called that because it is inherited from C, which did
not have a real string type such as the C++ string. These two types are quite
different, although related in a way that we will see in a later chapter.
at the mechanisms that allow us to get information into and out of the
computer, known as I/O. W e looked at the << function, which
provides display on the screen when coupled with the built-in
destination called cout . Immediately afterwards, we encountered the
corresponding input function >> and its partner cin , which team up to
give us input from the keyboard.
Next, we went over some program organization concepts including
the if statement, which allows a program to choose between two
alternatives; the while statement, which causes another statement to be
executed while some condition is true; and the block, which allows
several statements to be grouped together into one logical statement.
Blocks are commonly used to enable several statements to be
controlled by an if or while statement.
At last we were ready to write a simple program that does something
resembling useful work, and we did just that. The starting point for
this program, as with all programs, was to define exactly what the
program should do; in this case, the task was to keep track of the
weight of the heaviest pumpkin at a county fair. The next step was to
define a solution to this problem in precise terms. Next, we broke the
solution down into steps small enough to be translated directly into
C++. Of course, the next step after that was to do that translation.
Finally, we went over the C++ code, line by line, to see what each line
of the program did.

3.19. Conclusion

We’ve come a long way from the beginning of this chapter. Starting
from basic information on how the hardware works, we’ve made it
through our first actual, runnable program. By now, you should have a
much better idea of whether you’re going to enjoy programming (and
this book). Assuming you aren’t discouraged on either of these points,
let’s proceed to gather some more tools so we can undertake a bigger
project.
3.20. Answers to Exercises
1. 3c43. In case you got a different result, here’s a little help:
a. If you got the result 433a , you started at the wrong address.
b. If you got the result 433c , you had the bytes reversed.
c. Finally, if you got 3a43 , you made both of these mistakes.
If you made one or more of these mistakes, don’t feel too bad;
even experienced programmers have trouble with hexadecimal
values once in awhile. That’s one reason we use compilers and
assemblers rather than writing everything in hex!
2. “HELLO”. If you couldn’t figure out what the “D” at the
beginning was for, you started at the wrong place.
3. Figure 3.25 is Susan’s answer to this problem.

FIGURE 3.25. First dinner party program (code\basic05.cpp)

#include <iostream> using


namespace std;

int main()
{
short n;

cout << “Please type in the number of guests “; cout << “of your
dinner party. “;
cin >> n;

cout << “A table for “ << n+1 << “is ready. “;

return 0;
}
By the way, the reason that this program uses two lines to produce
the sentence “Please type in the number of guests of your dinner
party.” is so that the program listing will fit on the page properly. If
you prefer, you can combine those into one line that says:
cout << "Please type in the number of guests of your dinner party. "; .

Of course, this also applies to the next exercise. Here’s the


discussion that Susan and I had about this exercise:

Susan: I would have sent it sooner had I not had the last cout arrows going like
this >> (details).<G> Also, it just didn’t like the use of endl; at the end of the last
cout statement. It just kept saying “parse error”.

Steve: If you wrote something like

cout << "A table for " << n+1 << "is ready. " << "endl;"

then it wouldn’t work for two reasons. First, "endl;" is just a character string, not
anything recognized by << . Second, you’re missing a closing ; , because
characters inside quotes are treated as just plain characters by the compiler, not as
having any effect on program structure.

The correct way to use endl in your second output statement is as follows:

cout << "A table for " << n+1 << "is ready. " << endl;

By the way, you might want to add a " " in front of the is in is ready , so that the
number doesn’t run up against the is . That would make the line look like this:

cout << "A table for " << n+1 << " is ready. " << endl;

Susan: Okay.
4. Figure 3.26 is Susan’s answer to this problem, followed by our
discussion.

FIGURE 3.26. Second dinner party program (code\basic06.cpp)

#include <iostream> using


namespace std;

int main()
{
short n;

cout << “Excluding yourself, please type the “; cout <<


“number of guests in your dinner party.\n”;
cin >> n; if
(n>20)
cout << “Sorry, your party is too large. “;
else
cout << “A table for “ << n+1 << “ is ready. “;

return 0;
}

Steve: Congratulations on getting your program to work!

Susan: Now, let me ask you this: can you ever modify else ? That is, could I
have written else (n>20) ?

Steve: You can say something like what is shown in Figure 3.27.

FIGURE 3.27. else if example

if (x < y)
{
cout << "x is less than y" << endl;
}
else
{
if (x > y)
cout << "x is greater than y" << endl; else
cout << "x must be equal to y!" << endl;
}

In other words, the controlled block of an if statement, or an else statement, can


have another if or else inside it. In fact, you can have as many “nested” if or else
statements as you wish; however, it’s best to avoid very deep nesting because it
tends to confuse the next programmer who has to read the program.

5. The answer to this problem should look like Figure 3.28.

FIGURE 3.28. Name and age


program (code\basic07.cpp)

#include <iostream>
#include <string> using
namespace std;

int main()
{
string name;
short age;

cout << “What is your first name? “; cin >>


name;
cout << “Thank you, “ << name << endl; cout <<
“What is your age? “;
cin >> age;

if (age < 53)


cout << “My, what a youngster!” << endl; else
cout << “That is very old, “ << name << “. “ << endl;
return 0;
}

One point that might be a bit puzzling in this program is why it’s
not necessary to add an << endl to the end of the lines that send data
to cout before we ask the user for input. For example, in the
sequence:
cout << "What is your first name? "; cin >>
name;
how do we know that the C string literal "What is your first name? " has
been displayed on the terminal before the user has to type in the
answer? Obviously, it would be hard for the user to answer our
request for information without a clue as to what we’re asking for.
As it happens, this is a common enough situation that the designers
of the iostream library have anticipated it and solved it for us.
When we use that library to do output to the screen and then
request input from the keyboard, we can be sure that any screen
output we have already requested will be displayed before any
input is requested from the user via the keyboard.
That wasn’t the only subtle point that this problem raised. You’ll
be happy (or at least unsurprised) to hear that Susan and I had quite
a discussion about this problem and its solution:

Susan: When I was trying to put that period in the answer, I finally got it to work
with double quotes. But then I thought that maybe it should have been surrounded by
single quotes ' instead of double quotes. It worked with a double quote but since it
was only one character it should have been a single quote, so I went back and
changed it to a single quote and the compiler didn’t like that at all. So I put it back to
the double. So what is the deal?

Steve: You should be able to use 'x' or "x" more or less interchangeably with << ,
because it can handle both of those data
types ( char and C string literal, respectively). However, they are indeed different
types. The first one specifies a literal char value, whereas the second specifies a C
string literal value. A char value can only contain one character, but a C string
literal can be as long as you want, from none to hundreds or thousands of
characters.

Susan: Here’s the line that gave me the trouble:

cout << "That is very old, " << name << ". " << endl;

Remember I wanted to put that period in at the end in that last line? It runs like this
but not with the single quotes around it. That I don’t understand. This should have
been an error. But I did something right by mistake <G>. Anyway, is there something
special about the way a period is handled?

Steve: I understand your problem now. No, it’s not the period; it’s the space after
the period. Here are four possible versions of that line:

1. cout << "That is very old, " << name << ". " << endl;
2. cout << "That is very old, " << name << ’. ’ << endl;
3. cout << "That is very old, " << name << "." << endl;
4. cout << "That is very old, " << name << ’.’ << endl;

None of these is exactly the same as any of the others. However, 1, 3, and 4 will do
what you expect, whereas 2 will produce weird looking output, with some bizarre
number where the " . " should be. Why is this? It’s not because " . " is handled
specially, but because the space ( " " ), when inside quotes, either single or double, is
a character like any other character. Thus, the expression '. ' in line 2 is a
“multicharacter constant”, which has a value dependent on the compiler; with the
compiler on the CD, you’ll get a short value equal to (256 * the ASCII value of the
period) + the ASCII value of the space. This comes out to 11808, as I calculate it. So
the line you see on the screen may look like this:
That is very old, Joe11808

Now why do all of the other lines work? Well, 1 works because a C string literal can
have any number of characters and be sent to cout correctly; 3 works for the same
reason; and 4 works because ’.’ is a valid one-character constant, which is another
type that << can handle.

I realize it’s hard to think of the space as a character when it doesn’t look like
anything; in addition, you can add spaces freely between variables, expressions, and
so forth, in the program text. However, once you’re dealing with C string literals and
literal character values, the space is just like any other character.

Susan: So it is okay to use single characters in double quotes? If so, why bother
with single quotes?

Steve: Single quotes surround a literal of type char . This is a 1-byte value that
can be thought of (and even used) as a very short number. Double quotes surround a
literal value of type “C string literal”. This is a multibyte value terminated by a 0
byte, which cannot be used or treated as a number.

Susan: I am not too clear on what exactly the difference is between the char and
“C string literal”. I thought a char was like an alpha letter, and a string was just a
bunch of letters.

Steve: Right. The difference is that a C string literal is variable length, and a
char isn’t; this makes a lot of difference in how they can be manipulated.

Susan: Am I right in thinking that a char could also be a small number that is not
being used for calculations?

Steve: Or that is used for (very small) calculations; for instance, if you add 1 to
the value ’A’ , you get the value for ’B’ . At least that’s logical.
Susan: What do you mean by “terminated by a 0 byte”? That sounds familiar;
was that something from an earlier chapter which is now ancient history?

Steve: Yes, we covered that some time ago. The way the program can tell that
it’s at the end of a C string literal (which is of variable length, remember) is that it
gets to a byte with the value 0. This wouldn’t be my preferred way to specify the
size of a variable- length string, but it’s too late to do anything about it; it’s built into
the compiler.

Susan: When you say a C string literal, do you mean the C programming language
in contrast to other languages?

Steve: Yes.

Susan: All right, then the 0 byte used to terminate a C string literal is the same
thing as a null byte?

Steve: Yes.

Susan: Then you mean that each C string literal must end in a 0 so that the
compiler will know when to stop processing the data for the string?

Steve: Yes.

Susan: Could you also just put 0? Hey, it doesn’t hurt to ask. I don’t see the
problem with the word hello; it ends with an o and not a 0. But what if you do need
to end the sentence with a 0?

Steve: It’s not the digit '0', which has the ASCII code 30h, but a byte with a 0
value. You can’t type in a null byte directly, although you can create one with a
special character sequence if you want to. However, there’s no point in doing that
usually, because all C string literals such as "hello" always have an invisible 0 byte
added
automatically by the compiler. If for some reason you need to explicitly create a
null byte, you can write it as ’\0’ , as in

char x = ’\0’;

which emphasizes that you really mean a null byte and not just a plain old 0 like
this:

char x = 0;

The difference between these two is solely for the benefit of the next programmer
who looks at your code; they’re exactly the same to the compiler.

6. Figure 3.29 shows Susan’s program, which is followed by our


discussion.

FIGURE 3.29. Novice program (code\basic08.cpp)

#include <iostream>
#include <string> using
namespace std;

int main()
{
string answer;

cout << “Please respond to the following statement “; cout << “with
either true or false\n”;

cout << “Susan is the world’s most tenacious novice.\n”; cin >> answer;

if (answer != “true”)
if (answer != “false”)
cout << “Please answer with either true or false.”;

if (answer == “true”)
cout << “Your answer is correct\n”;
if (answer == “false”)
cout << “Your answer is erroneous\n”;

return 0;
}

Susan: Steve, look at this. It even runs!

Also, I wanted to ask you one more question about this program. I wanted to put
double quotes around the words true and false in the 3rd output statement
because I wanted to emphasize those words, but I didn’t know if the compiler could
deal with that so I left it out. Would that have worked if I had?

Steve: Not if you just added quotes, because " is a special character that means
“beginning or end of C string literal”. Here’s what you would have to do to make it
work:

cout << "Please answer with either \"true\" or \"false\".";

The \ is a way of telling the compiler to treat the next character differently from its
normal usage. In this case, we are telling the compiler to treat the special character
" as “not special”; that is, \" means “just the character double quote, please, and no
nonsense”. This is called an escape sequence, because it allows you to get out of
the trap of having a " mean something special. We also use the \ to tell the
compiler to treat a “nonspecial” character as “special”; for example, we use it to
make up special characters that don’t have any visual representation. You’ve already
seen ’\n’ , the “newline” character, which means “start a new line on the screen”.

Susan: So if we want to write some character that means something “special”,


then we have to use a \ in front of it to tell the compiler to treat it like a “regular”
character?

Steve: Right.
Susan: And if we want to write some character that is “regular” and make it do
something “special”, then we have to use a \ in front of it to tell the compiler that it
means something “special”?

Steve: Yes, that’s the way it works.

Susan: I now just got it. I was going to say, why would you put the first quotation
mark before the slash in ‘\n’, but now I see. Since you are doing an endline
character, you have to have quotes on both sides to surround it which you don’t
usually have to do because the first quotes are usually started at the beginning of the
sentence, and in this case the quote was already ended.

Steve: You’ve got it.

Susan: Another thing I forgot is how you refer to the code in ()


next to the “if” keywords; what do you call that?

Steve: The condition.

7. Figure 3.30 is Susan’s version of this program. Actually, it was her


idea in the first place.

FIGURE 3.30. Allowance program (code\basic09.cpp)

#include <iostream> using


namespace std;

int main()
{
short x;

cout << “Elena can increase her $10 allowance each week “; cout << “by
adding new chores.” << endl;

cout << “For every extra chore Elena does, she gets “; cout << “another
dollar.” << endl;
cout << “How many extra chores were done? “ << endl; cin >> x;

if (x==0)
{
cout << “There is no extra allowance for Elena “; cout <<
“this week. “ << endl;
}
else
{
cout << “Elena will now earn “ << 10 + x; cout <<
“ dollars this week.” << endl;
}

return 0;
}

8. You’ll be happy (or at least unsurprised) to hear that Susan and I


had quite a discussion about this problem.

Susan: Remember on my “test” program how I finally got that period in there?
Then I got to thinking that maybe it should have been surrounded by single quotes '
instead of double quotes. It worked with a double quote but since it was only one
character it should have been a single quote, so I went back and changed it to a
single quote and the compiler didn’t like that at all. So I put it back to the double.
So what is the deal?

Steve: You should be able to use 'x' or "x" more or less interchangeably with
<< , because it can handle both of those data types ( char and C string,
respectively). However, they are indeed different types. The first one specifies a
literal char value, whereas the second specifies a literal C string value. A char
value can only contain one character, but a C string can be as long as you want,
from none to hundreds or thousands of characters.

Susan: Here’s the line that gave me the trouble:


cout << "That is very old, " << name << ". " << endl;

Remember I wanted to put that period in at the end in that last line? It runs like this
but not with the single quotes around it. That I don’t understand. This should have
been an error. But I did something right by mistake <G>. Anyway, is there something
special about the way a period is handled?

Steve: I understand your problem now. No, it’s not the period; it’s the space after
the period. Here are four possible versions of that line:

1. cout << "That is very old, " << name << ". " << endl;
2. cout << "That is very old, " << name << ’. ’ << endl;
3. cout << "That is very old, " << name << "." << endl;
4. cout << "That is very old, " << name << ’.’ << endl;

None of these is exactly the same as any of the others. However, 1, 3, and 4 will do
what you expect, whereas 2 will produce weird looking output, with some bizarre
number where the " . " should be. Why is this? It’s not because " . " is handled
specially, but because the space ( " " ), when inside quotes, either single or double, is
a character like any other character. Thus, the expression '. ' in line 2 is a
“multicharacter constant”, which has a value dependent on the compiler; in the case
of the compiler on the CD in the back of the book, you’ll get a short value equal to
(256 * the ASCII value of the space) + the ASCII value of the period. This comes
out to 8238, as I calculate it. So the line you see on the screen may look like this:

That is very old, Joe Smith8238

Now why do all of the other lines work? Well, 1 works because a C string can have
any number of characters and be sent to cout correctly; 3 works for the same
reason; and 4 works because ’.’ is a valid one-character constant, which is another
type that << can handle.
I realize it’s hard to think of the space as a character, when it doesn’t look like
anything; in addition, you can add spaces freely between variables, expressions, and
so forth, in the program text. However, once you’re dealing with C strings and literal
character values, the space is just like any other character.

Susan: So it is okay to use single characters in double quotes? If so, why bother
with single quotes?

Steve: Single quotes surround a literal of type char. This is a 1-byte value that
can be thought of (and even used) as a very short number. Double quotes surround a
literal of type “C string”. This is a multibyte value terminated by a 0 byte, which
cannot be used or treated as a number.

Susan: I am not too clear on what exactly the difference is between the char
and “C string”. I thought a char was like a alpha letter, and a string was just a
bunch of letters.

Steve: Right. The difference is that a C string is variable length, and a char
isn’t; this makes a lot of difference in how they can be manipulated.

Susan: Am I right in thinking that a char could also be a small number that is not
being used for calculations?

Steve: Or that is used for (very small) calculations; for instance, if you add 1 to
the value ’A’ , you get the value for ’B’ . At least that’s logical.

Susan: What do you mean by “terminated by a 0 byte”? That sounds familiar;


was that something from an earlier chapter which is now ancient history?

Steve: Yes, we covered that some time ago. The way the program can tell that it’s
at the end of a C string (which is of variable length, remember) is that it gets to a
byte with the value 0. This wouldn’t
be my preferred way to specify the size of a variable-length string, in my opinion, but
it’s too late to do anything about it; it’s built into the compiler.

Susan: When you say a C string, do you mean the C programming language in
contrast to other languages?

Steve: Yes.

Susan: All right, then the 0 byte used to terminate a C string is the same thing as
a null byte?

Steve: Yes.

Susan: Then you mean that each C string must end in a 0 so that the compiler will
know when to stop processing the data for the string?

Steve: Yes.

Susan: Could you also just put 0? Hey, it doesn’t hurt to ask. I don’t see the
problem with the word hello; it ends with an o and not a 0. But what if you do need
to end the sentence with a 0?

Steve: It’s not the digit '0', which has the ASCII code 30h, but a byte with a 0
value. You can’t type in a null byte directly, although you can create one with a
special character sequence if you want to. However, there’s no point in doing that
usually, because all literal C strings such as "hello" always have an invisible 0 byte
added automatically by the compiler. If for some reason you need to explicitly create
a null byte, you can write it as ’\0’ , as in

char x = ’\0’;

which emphasizes that you really mean a null byte and not just a plain old 0 like this:
char x = 0;

The difference between these two is solely for the benefit of the next programmer to
look at your code; they’re exactly the same to the compiler.
CHAPTER 4 More Basics

Now that we have seen how to write a simple program in C++, it’s
time to acquire some more tools. We’ll extend our example program
from Chapter 3 for finding the heaviest pumpkin. Eventually, we want
to provide the weights of the three heaviest pumpkins, so that first,
second, and third prizes can be awarded. It might seem that this would
require just a minor modification of the previous program, in which
we would keep track of the heaviest so far, second heaviest so far, and
third heaviest so far, rather than merely the heaviest so far. However,
this modification turns out to be a bit more complicated than it seems.
Since this book is intended to teach you how to program using C++,
rather than just how to use the C++ language, it’s worth investigating
why this is. First, though, here are the objectives of this chapter.

4.1. Objectives of This Chapter

By the end of this chapter, you should


1. Understand the likelihood of error in even a small change to a
program.
2. Be aware that even seemingly small changes in a problem can
result in large changes in the program that solves the problem.
3. Have some understanding of the type of thinking needed to solve
problems with programming.
4. Understand the selection sorting algorithm for arranging values in
order.
5. Understand how to use a Vec to maintain a number of values under
one name.
6. Be able to use the for statement to execute program statements a
(possibly varying) number of times.
7.Be familiar with the arithmetic operators ++ and += , which
are used to modify the value of variables.

4.2. Algorithmic Thinking

Let’s take our program modification one step at a time, starting with
just the top two weights. Figure 4.1 is one possible way to handle this
version of the problem.

FIGURE 4.1. Finding the top two


weights, first try (code\pump1a.cpp)

#include <iostream> using


namespace std;

int main()
{
short CurrentWeight; short
HighestWeight;
short SecondHighestWeight;
cout << "Please enter the first weight: "; cin
>> CurrentWeight;
HighestWeight = CurrentWeight;
SecondHighestWeight = 0;
cout << "Current weight " << CurrentWeight << endl; cout <<
"Highest weight " << HighestWeight << endl;

while (CurrentWeight > 0)


{
cout << "Please enter the next weight: "; cin
>> CurrentWeight;
if (CurrentWeight > HighestWeight)
{
SecondHighestWeight = HighestWeight;
HighestWeight = CurrentWeight;
}
cout << "Current weight " << CurrentWeight << endl; cout <<
"Highest weight " << HighestWeight << endl;
cout << "Second highest weight " << SecondHighestWeight << endl;
}

return 0;
}

The reasons behind some of the new code, shown in bold, should be
fairly obvious, but we’ll go over them anyway. First, of course, we
need a new variable, SecondHighestWeight , to hold the current value of
the second highest weight we’ve seen so far. Then, when the first
weight is entered, the statement SecondHighestWeight = 0; sets the
SecondHighestWeight to 0. After all, there isn’t any second-highest weight
when we’ve only seen one weight. The first nonobvious change is the
addition of the statement SecondHighestWeight = HighestWeight; , which copies
the old HighestWeight to SecondHighestWeight , whenever there’s a new
highest weight. On reflection, however, this should make sense; when
a new high is detected, the old high must be the second highest value
(so far). Also, we have to copy the old HighestWeight to
SecondHighestWeight before we change HighestWeight . After we have set
HighestWeight to a new value, it’s too late to copy its old value into
SecondHighestWeight .
First, let’s see how Susan viewed this solution:

Susan: I noticed that you separate out the main program { } from the other { }
by indenting. Is that how the compiler knows which set of { } goes to which
statements and doesn’t confuse them with the main ones that are the body of the
program?

Steve: The compiler doesn’t care about indentation at all; that’s just for the
people reading the program. All the compiler cares about is the number of { it has
seen so far without matching }. There aren’t any hard rules about indentation; it’s a
“religious” issue in C++, where different programmers can’t agree on the best way.

Susan: Now on this thing with setting SecondHighestWeight to


0. Is that initializing it? See, I know what you are doing, and yet I can’t see the
purpose of doing this clearly, unless it is initializing, and then it makes sense.

Steve: That’s correct.

Susan: How do you know how to order your statements? For example, why did
you put the " SecondHighestWeight = HighestWeight ;" above the other statement?
What would happen if you reversed that order?

Steve: Think about it. Let’s suppose that:

CurrentWeight is 40
HighestWeight is 30
SecondHighestWeight is 15

and the statements were executed in the following order:

1. HighestWeight = CurrentWeight
2. SecondHighestWeight = HighestWeight
What would happen to the values? Well, statement 1 would set
HighestWeight to CurrentWeight , so the values would be like this:

CurrentWeight is 40
HighestWeight is 40
SecondHighestWeight is 15

Then statement 2 would set SecondHighestWeight to


HighestWeight , leaving the situation as follows:

CurrentWeight is 40
HighestWeight is 40
SecondHighestWeight is 40

This is clearly wrong. The problem is that we need the value of HighestWeight
before it is set to the value of CurrentWeight , not afterward. After that occurs, the
previous value is lost.

Susan: Yes, that is apparent; I was just wondering if the computer had to read it
in the order that you wrote it, being that it was grouped together in the {} . For
example, you said that the compiler doesn’t read the {} as we write them, so I was
wondering if it read those statements as we write them. Obviously it has to. So then
everything descends in a progression downward and outward, as you get more
detailed in the instructions.

If you wish, you can try out this program. First, you have to compile it
by following the compilation instructions on the CD. Then type pump1a
to run the program under DOS. It will ask you for weights and keep
track of the highest weight and second-highest weight that you’ve
entered. Type 0 and hit ENTER to end the program.
You can also run it under the debugger by following the usual
instructions for that method.
Susan Finds a Bug

This program may seem to keep track of the highest and second highest
weights correctly, but in fact there’s a hole in the logic. To be exact, it
doesn’t work correctly when the user enters a new value that’s less
than the previous high value but more than the previous second-high
value. In that case, the new value should be the second- high value,
even though there’s no new high value. For example, suppose that you
enter the following weights: 5 2 1 1 3 7. If we were to update
SecondHighestWeight only when we see a new high, our program would
indicate that 11 was the high, and 5 the second highest. Since neither 3
nor 7 is a new high, SecondHighestWeight would remain as it was when
the 11 was entered.
Here’s what ensued when Susan tried out the program and
discovered this problem:

Susan: Steve, the program! I have been playing with it. Hey this is fun, but look, it
took me awhile. I had to go over it and over it, and then I was having trouble getting
it to put current weights that were higher than second weights into the second weight
slot. For example, if I had a highest weight of 40 and the second highest weight of 30
and then selected 35 for a current weight, it wouldn’t accept 35 as the second-
highest weight. It increased the highest weights just fine and it didn’t change
anything if I selected a lower number of the two for a current weight. Or did you
mean to do that to make a point? I am supposed to find the problem? I bet that is
what you are doing.

Steve: Yep, and I’m not sorry, either. <G>

Susan: You just had to do this to me, didn’t you? OK, what you need to do is to
put in a statement that says if the current weight is greater than the second-highest
weight, then set the second-highest weight to the current weight (as illustrated in
Figure 4.2).
FIGURE 4.2. Susan’s solution to the bug in the first attempt

else
{
if (CurrentWeight > Second HighestWeight) Second
HighestWeight = CurrentWeight;
}

I hope you are satisfied.

Steve: Satisfied? Well, no, I wouldn’t use that word. How about ecstatic? You
have just figured out a bug in a program, and determined what the solution is. Don’t
tell me you don’t understand how a program works.

Now I have to point out something about your code. I understood what you wrote
perfectly. Unfortunately, compilers aren’t very smart and therefore have to be
extremely picky. So you have to make sure to spell the variable names correctly, that
is, with no spaces between the words that make up a variable name. This would
make your answer like the else clause shown in Figure 4.3.

Congratulations again.

As Susan figured out, we have to add an else clause to our if


statement, so that the corrected version of the statement looks like
Figure 4.3.

FIGURE 4.3. Using an if statement with an else clause

if (CurrentWeight > HighestWeight)


{
SecondHighestWeight = HighestWeight; HighestWeight =
CurrentWeight;
}
else
{
if (CurrentWeight > SecondHighestWeight) SecondHighestWeight =
CurrentWeight;
}

In this case, the condition in the first if is checking whether


CurrentWeight is greater than the previous HighestWeight ; when this is true ,
we have a new HighestWeight and we can update both HighestWeight and
SecondHighestWeight . However, if CurrentWeight is not greater than
HighestWeight , the else clause is executed. It contains another if ; this one
checks whether CurrentWeight is greater than the old SecondHighestWeight .
If so, SecondHighestWeight is set to the value of CurrentWeight .
What happens if two (or more) pumpkins are tied for the highest
weight? In that case, the first one of them to be encountered is going to
set HighestWeight , as it will be the highest yet encountered. When the
second pumpkin of the same weight is seen, it won’t trigger a change
to HighestWeight , since it’s not higher than the current value of that
variable. It will pass the test in the else clause, if
(CurrentWeight > SecondHighestWeight) , however, which will cause
SecondHighestWeight to be set to the same value as HighestWeight . This is
reasonable behavior, unlikely to startle the (hypothetical) user of the
program, and therefore is good enough for our purposes. In a real
application program we’d have to try to determine what the user of
this program would want us to do.
Figure 4.4 shows the corrected program.

FIGURE 4.4. Finding the top two


weights (code\pump2.cpp)

#include <iostream> using


namespace std;

int main()
{
short CurrentWeight; short
HighestWeight;
short SecondHighestWeight;
cout << “Please enter the first weight: “; cin >>
CurrentWeight;
HighestWeight = CurrentWeight;
SecondHighestWeight = 0;
cout << “Current weight “ << CurrentWeight << endl; cout <<
“Highest weight “ << HighestWeight << endl;

while (CurrentWeight > 0)


{
cout << “Please enter the next weight: “; cin >>
CurrentWeight;
if (CurrentWeight > HighestWeight)
{
SecondHighestWeight = HighestWeight; HighestWeight =
CurrentWeight;
}
else
{
if (CurrentWeight > SecondHighestWeight) SecondHighestWeight =
CurrentWeight;
}
cout << “Current weight “ << CurrentWeight << endl; cout <<
“Highest weight “ << HighestWeight << endl;
cout << “Second highest weight “ << SecondHighestWeight << endl;
}

return 0;
}

If you wish, you can try out this program. First, you have to compile it
by following the compilation instructions on the CD. Then type pump2
to run the program under DOS. It will ask you for weights and keep
track of the highest weight and second-highest weight that you’ve
entered. Type 0 and hit ENTER to end the program.
You can also run it under the debugger, by following the usual
instructions for that method. When you are asked for a weight, type
one in and hit ENTER just as when executing normally. When you enter
a 0 weight, the program will stop looping and execution will take the
path to the end } .
By the way, since we’ve been using the if statement pretty heavily,
this would be a good time to list all of the conditions that it can test.
We’ve already seen some of them, but it can’t hurt to have them all in
one place. Figure 4.5 lists these conditions, with translations.

FIGURE 4.5. What if?

Symbol Controlled block will be executed if:


> First item is larger than second item
< First item is smaller than second item
>= First item is larger than or equal to second item
<= First item is smaller than or equal to second item
!= First item differs from second item
== First item has the same value as the second item

You may wonder why we have to use == to test for equality rather than
just = . That’s because = means “assign right hand value to variable
on left”, rather than “compare two items for equality”. This is a
“feature” of C++ (and C) that allows us to accidentally write if (a
= b) when we mean if (a == b) . What does if (a = b) mean? It means the
following:
1. Assign the value of b to a .
2. If that value is 0, then the result of the expression in parentheses is
false , so the controlled block of the if is not executed.

3. Otherwise, the result of the expression in parentheses is true , so the


controlled block of the if is executed.
Some people find this useful; I don’t. Therefore, whenever possible I
enable a compiler warning that tells you when you use = inside an if
statement in a way that looks like you meant to test for equality.

Even a Simple Change to a Program Can Cause Errors

I hope this excursion has given you some appreciation of the subtleties
that await in even the simplest change to a working program. Many
experienced programmers still underestimate such difficulties and the
amount of time that may be needed to ensure that the changes are
correct.1 I don’t think it’s necessary to continue along the same path
with a program that can award three prizes. The principle is the same,
although the complexity of the code grows with the number of special
cases we have to handle. Obviously, a solution that could handle any
number of prizes without special cases would be a big improvement,
but it will require some major changes in the organization of the
program. That’s what we’ll take up next.

4.3. Handling Any Number of Prizes

One of the primary advantages of the method we’ve used so far to find
the heaviest pumpkin(s) is that we didn’t have to save the weights of
all the pumpkins as we went along. If we don’t mind saving all the
weights, then we can solve the “three prize” problem in a different
way. Let’s assume for the purpose of simplicity that there are only five
weights to be saved, in which case the solution looks like this:

1. There is a book called Refactoring: Improving the Design of Existing Code


(ISBN 0-201-48567-2) that is very helpful to programmers with some
experience who want to improve their ability to modify programs without
introducing new errors. Reading this book has changed my attitude toward
maintenance from distaste to interest.
1. Read in all of the weights.
2. Make a list consisting of the three highest weights in descending
order.
3. Award the first, second, and third prizes, in that order, to the three
entries in the list of highest weights.

Now let’s break those down into substeps that can be more easily
translated into C++:
1. Read in all of the weights.
a. Read first number
b. Read next number
c. If we haven’t read five weights yet, go back to 1b
2. Make a list consisting of the three highest weights in descending
order.
a. Find the largest number in the original list of weights
b. Copy it to the sorted list
c. If we haven’t found the three highest numbers, go back to 2a

Oops. That’s not going to work, since we’ll get the same number each
time.2 T o prevent that from happening, we have to mark off each
number as we select it. Here’s the revised version of step 2:
2. Make a list consisting of the three highest weights in descending
order.
a. Find the largest number in the original list of weights
2. I realize I’m breaking a cardinal rule of textbooks: Never admit that the
solution to a problem is anything but obvious, so the student feels like an idiot if it
isn’t actually obvious. In reality, even a simple program is difficult to get right,
and indicating the sort of thought processes that go into analyzing a
programming problem might help demystify this difficult task.
b. Copy it to the sorted list
c. Mark it off in the original list of weights, so we don’t select it
again
d. If we haven’t found the three highest numbers, go back to 2a
3. Award the first, second, and third prizes, in that order, to the three
entries in the list of highest weights.
a. Display first number in the list
b. Display next number in the list
c. If we haven’t displayed them all, go back to 3b

Unlike our previous approach, this can easily be generalized to handle


any number of prizes. However, we have to address two problems
before we can use this approach: First, how do we keep track of the
weights? And second, how do we select out the highest three weights?
Both of these problems are much easier to solve if we don’t have a
separate variable for each weight.

The vector Data Type

We often need to keep track of a number of closely related variables in


C++ programs, so the standard library provides a type called vector ,
defined in the header file <vector> . A vector is a variable containing a
number of “sub-variables” that can be addressed by position in the
vector ; each of these sub-variables is called an element. A vector has a
name, just like a regular variable, but the elements do not. Instead,
each element has a number, corresponding to its position in the vector .
For example, we might want to create a vector of short values called
Weight , with five elements. To do this, we would write this line:
vector<short> Weight(5); . Then, to refer to one of the elements of the vector
Weight , say element 3, we could use the expression Weight[3] .
The standard library is the result of an immense amount of work by
very talented programmers over the course of quite a few years.
Whenever possible, in code that you intend to be used over a period of
time, you should use the standard library, as you are very unlikely to
be able to create better code yourself. And even if you could improve
on it somewhat, you would probably spend your time more
productively by writing the parts of your program that aren’t already
taken care of by the standard library.

The Vec Data Type


So you may be surprised to learn that we’re not going to use the vector
data type, but one called Vec, defined in the header file “Vec.h” . I have
created this type, which uses the standard library vector to do most of
the actual work, following the example of Bjarne Stroustrup in The
C++ Programming Language.
Why are Bjarne and I creating our own Vec types rather than using
one from the standard library? Because the standard library is
designed for professional programmers, not for people who are just
learning the language. For this reason, it is oriented more toward
efficiency than toward safety. To make the learning process easier and
more enjoyable for novices, the Ve c type always checks whether an
element number (such as 3 in the above example) is valid before
trying to use it, which the standard vector does not. We’ll see how this
helps us later on.
We haven’t heard from Susan for awhile, but the following
exchange should make up for that.

Susan: OK, why do we need another header ( #include “Vec.h” )?

Steve: Each header contains definitions for a specific purpose. For example,
< iostream> contains definitions that allow us to get information in (I) and out (O) of
the computer. On the other hand, “ Vec.h” contains definitions that allow us to use
Vecs .
Susan: So then using a Vec is just another way of writing this same program,
only making it a little more efficient?

Steve: In this case, the new program can do more than the old program could: the
new program can easily be changed to handle virtually any number of prizes,
whereas the old program couldn’t.

Susan: So there is more than one way to write a program that does basically the
same thing?

Steve: As many ways as there are to write a book about the same topic.

Susan: I find this to be very odd. I mean, on one hand the code seems to be so
unrelentingly exact; on the other, it can be done in as many ways as there are artists
to paint the same flower. That must be where the creativity comes in. Then I would
expect that the programs should behave in different manners, yet accomplish the
same goal.

Steve: It’s possible for two programs to produce similar (or even exactly the
same) results from the user’s perspective and yet work very differently internally.
For example, the "vectorized" version of the weighing program, if we had it display
only the top two weights, would produce exactly the same final results as the final
"non-vectorized" version, even though the method of finding the top two weights was
quite different.

Now we can refer to the individual elements of the Vec called Weight
by using their numbers, enclosed in square brackets ( [ ] ); the number
in the brackets is called the index. Here are some examples:

Weight[1] = 123;
Weight[2] = 456;
Weight[3] = Weight[1] + Weight[2];
Weight[i+1] = Weight[i] + 5;
As these examples indicate, an element of a Vec can be used anywhere
a “regular” variable can be used.3 But an element of a Vec has an
attribute that makes it much more valuable than a "regular" variable
for our purposes here: we can vary which element we are referring to
in a given statement, by varying an index. Take a look at the last
sample line, in which two elements of the Vec Weight are used: the first
one is element i+1 and the other is element i .4 As this indicates, we
don’t have to use a constant value for the element number, but can
calculate it while the program is executing. In this case, if i is 0, the
two elements referred to are element 1 and element 0, while if i is 5,
the two elements are elements 6 and 5, respectively.
The ability to refer to an element of a Vec by number rather than by
name allows us to write statements that can refer to any element in a
Vec, depending on the value of the index variable in the statements. To
see how this works in practice, let’s look at Figure 4.6, which solves
our three-prize problem.5
FIGURE 4.6. Using a Vec
(code\vect1.cpp)

#include <iostream>
#include “vec.h” using
namespace std;

int main()

3. What I’m calling a "regular" variable here is technically known as a scalar


variable; that is, one with only one value at any given time.
4. By the way, if you’re wondering how to pronounce Weight[i], it’s “weight sub
i”. “Sub” is short for subscript, which is an old term for “index”.
5. Note that the #include statement for Vec.h in Figure 4.6 uses "" rather than
<> around the file name. The use of "" tells the compiler to search for Vec.h in the
current directory first, and then the "normal" places that header files supplied
with the compiler are located. This is necessary because Vec.h, in fact, is in the
current directory. If we had written #include <Vec.h> , the compiler would look
only in the "normal" places, and therefore would not find Vec.h. We will use ""
for our header files, and <> for ones supplied with the compiler.
{
Vec<short> Weight(5); Vec<short>
SortedWeight(3); short
HighestWeight;
short HighestIndex;
short i;
short k;
cout << “I’m going to ask you to type in five weights, in pounds.” << endl; for (i = 0;
i < 5; i ++)
{
cout << “Please type in weight #” << i+1 << “: “; cin
>> Weight[i];
}

for (i = 0; i < 3; i ++)


{
HighestWeight = 0; for
(k = 0; k < 5; k ++)
{
if (Weight[k] > HighestWeight)
{
HighestWeight = Weight[k];
HighestIndex = k;
}
}
SortedWeight[i] = HighestWeight;
Weight[HighestIndex] = 0;
}

cout << “The highest weight was: “ << SortedWeight[0] << endl;
cout << “The second highest weight was: “ << SortedWeight[1] << endl; cout <<
“The third highest weight was: “ << SortedWeight[2] << endl;

return 0;
}

If you wish, you can try out this program. First, you have to compile it
by following the compilation instructions on the CD. Then type vect1
to run the program under DOS. It will ask you for five weights. After
you’ve entered five weights, the program will sort them and display
the top three.
You can also run it under the debugger, by following the usual
instructions for that method. When you are asked for a weight, type
one in and hit ENTER just as when executing normally. After you’ve
entered 5 weights, the program will start the sorting process.
This program uses several new features of C++ which need some
explanation. First, of course, there is the line that defines the Vec
Weight :

Vec<short> Weight(5);

As you might have guessed, this means that we want a Vec of five
elements, each of which is a short . As we have already seen, this
means that there are five distinct index values each of which refers to
one element. However, what isn’t so obvious is what those five
distinct index values actually are. You might expect them to be 1, 2, 3,
4 and 5; actually, they are 0, 1, 2, 3, and 4.
This method of referring to elements in a Vec is called zero- based
indexing, as contrasted with the seemingly more natural one- based
indexing where we start counting at 1. Although zero-based indexing
might seem odd, assembly language programmers find it perfectly
natural because the calculation of the address of an element is simpler
with such indexing; the formula is “address of an element = (address of
first element) + (element number) * (size of element)”.
This bit of history is relevant because C, the predecessor of C++,
was originally intended to replace assembly language so that programs
could be moved from one machine architecture to another with as little
difficulty as possible. One reason for some of the eccentricities of
C++ is that it has to be able to replace C as a “portable assembly
language” that doesn’t depend on any specific machine architecture.
This explains, for example, the great concern of the inventor of C++
for run-time efficiency, as he wished to allow programmers to avoid
the use of C or assembly language for efficiency.6 Since C++ was
intended to replace C completely, it has to be as efficient as possible;
otherwise, programmers might switch
back from C++ to C whenever they were concerned about the speed
and size of their programs.

A Historical Example of One-based Indexing

While we’re on the topic of zero-based indexing, I’d like to mention a


case where the inventors of a commonly used facility should have
used zero-based indexing, but didn't. We're still suffering from the
annoyances of this one.
Long ago, there was no standard calendar with year numbers
progressing from one to the next when January 1st came around.
Instead, years were numbered relative to the reign of the current
monarch; for example, the Bible might refer to “the third year of
Herod's reign”. This was fine in antiquity, when most people really
didn't care what year it was. There weren’t very many retirement
plans or 50th wedding anniversaries to celebrate anyway. However,
eventually historians realized that it was a major nuisance to try to
calculate the age of someone who was born in the fourth year of
someone's reign and died in the tenth year of someone else's.
According to Grolier's Multimedia Encyclopedia:

’About AD 525, a monk named Dionysius Exiguus suggested that years be counted
from the birth of Christ, which was designated AD (anno Domini, “the year of the
Lord”) 1. This proposal came to be adopted throughout Christendom during the next
500 years. The year before AD 1 is designated 1 BC (before Christ).'

The encyclopedia doesn't state when the use of the term BC started,
but the fact that its expansion is English rather than Latin is a
suspicious sign indicating that this development was considerably

6. Run-time efficiency means the amount of time a program takes to run, as


well as how much memory it uses. These issues are very significant when
writing a program to be sold to or used by others, as an inefficient program may
be unacceptable to the users.
later. In any event, this numbering system made matters considerably
easier. Now, you could tell that someone who was born in AD 600
and died in AD 650 was approximately 50 years old at death.
Unfortunately, however, there was still a small problem. Zero
hadn't yet made it to Europe from Asia when the new plan was
adopted, so the new calendar numbered the years starting with 1,
rather than 0; that is, the year after 1 BC was 1 AD. While this may
seem reasonable, it accounts for a number of oddities of our current
calendar:
1. Date ranges spanning AD and BC are hard to calculate, since you
can't just treat BC as negative. For example, if someone were born
in 1 BC and died in 1 AD, how old was that person? You might
think that this could be calculated as 1 - (-1), or 2; however, the last
day of 1 BC immediately preceded the first day of 1 AD, so the
person might have been only a few days old.
2. The 20th century consists of the years 1901 to 2000; the year
numbers of all but the last year of that century actually start with
the digits 19 rather than 20.
3. Similarly, the third millennium started on January 1, 2001, not
2000.

The reason for the second and third of these oddities is that since the
first century started in 1 AD, the second century had to start in 101
AD; if it started in 100 AD, the first century would have consisted of
only 99 years (1-99), rather than 100.
If only they had known about the zero! Then the zeroth century
would have started at the beginning of 0 AD and ended on the last day
of 99 AD. The first century would have started at 100 AD, and so on;
coming up to current time, we would be living through the first years
of the 20th century, which would be defined as all of those years
whose year numbers started with 20. The second millennium would
have started on January 1, 2000, as everyone would expect.7
4.4. Index Variables

Now let’s get back to our discussion of the revised pumpkin- weighing
program. The last two lines in the variable definition phase define two
variables, called i and k , which have been traditional names for
index vari ables (i.e., variables used to hold indexes) since the
invention of FORTRAN, one of the first relatively “user-friendly”
computer languages, in the 1950s. The inventors of FORTRAN used a
fairly simple method of determining the type of a variable: if it began
with one of the letters I through N, it was an integer. Otherwise, it was
a floating-point variable (i.e., one that can hold values that contain a
fractional part, such as 3.876). This rule was later changed so that the
user could specify the type of the variable regardless of its name, as in
C++, but the default rules were the same as in the earlier versions of
FORTRAN, to allow programs using the old rules to continue to
compile and run correctly.
Needless to say, Susan had some questions about the names of index
variables:

Susan: So whenever you see i or k you know you are dealing with a Vec ?

Steve: Not necessarily. Variables named i and k are commonly used as


indexes, but they are also used for other purposes sometimes.

Susan: Anyway, if i and k are sometimes used for other purposes, then the
compiler doesn’t care what you use as indexes? Again, no rules, just customs?

Steve: Right. It’s just for the benefit of other programmers, who will see i and
say “oh, this is probably an index variable”.

7. For much more on the history of zero, see Charles Seife’s Zero: The
Biography of a Dangerous Idea (ISBN 0-670-88457-X).
I suspect one reason for the durability of these short names is that
they’re easy to type, and many programmers aren’t very good typists.8
In C++, the letters i , j , k , m and n are commonly used as indexes;
however, l (the letter “ell”) generally isn’t, because it looks too much
like a 1 (the numeral one).9 The compiler doesn’t get confused by this
resemblance, but programmers very well might.
After the variable definitions are out of the way, we can proceed to
the executable portion of our program. First we type out a note to the
user, stating what to expect. Then we get to the code in Figure 4.7.

FIGURE 4.7. Using a for loop


(from code\vect1.cpp)

for (i = 0; i < 5; i ++)


{
cout << "Please type in weight #" << i+1 << ": "; cin >>
Weight[i];
}

8. I strongly recommend learning how to type (i.e., touch type). I was a


professional programmer without typing skills for over 10 years before agreeing
to type someone else’s book manuscript. At that point, I decided to teach
myself to touch-type, so I wrote a Dvorak keyboard driver for my Radio Shack
Model III computer and started typing. In about a month I could type faster
than with my previous two-finger method and eventually got up to 80+ words
per minute on English text. If you’ve never heard of the Dvorak keyboard, it’s
the one that has the letters laid out in an efficient manner; the “home row” keys
are AOEUIDHTNS rather than the absurd set ASDFGHJKL;. This “new”
(1930s) keyboard layout reduces effort and increases speed and accuracy
compared to the old QWERTY keyboard, which was invented in the 1880s to
prevent people from typing two keys in rapid succession and jamming the
typebars together. This problem has been nonexistent since the invention of the
Selectric typewriter (which uses a ball rather than typebars) in 1960, but inertia
keeps the old layout in use even though it is very inefficient. In any event, since
I learned to type, writing documentation has required much less effort. This
applies especially to writing articles or books, which would be a painful process
otherwise.
9. I also don’t use variables called j in the programs in this book, because j looks
too much like i and could therefore be confusing.
The first line here is called a ffor statement, which is used to control a
for loop; this is a loop control facility similar to the while loop we
encountered in Chapter 3. The difference between these two
statements is that a for loop allows us to specify more than just the
condition under which the controlled block will be repetitively
executed.10 A for statement specifies three expressions (separated by
“;”) that control the execution of the for loop: a starting expression, a
continuation expression, and a modification expression. In our case,
these are i = 0 , i < 5 , and i ++ , respectively. Let’s look at the function
and meaning of each of these components.
First, the starting expression, in this case i = 0 . The starting
expression is executed once before the block controlled by the for
statement is executed. In this case, we use it to set our index variable,
i , to 0, which will refer to the first element of our Weight Vec.
Next, the continuation expression, in this case i < 5 . As long as
this expression is true, the controlled block of the for will be
executed; in this case, we will continue executing the controlled block
as long as the value of i is less than 5. Note: the continuation
expression is actually executed before every execution of the
controlled block; thus, if the continuation expression is false when the
loop is entered for the first time, the controlled block will not be
executed at all.
The notion of the continuation expression is apparently confusing
to some novices. Susan fell into that group.

Susan: In your definition of for , how come there is no ending expression? Why
is it only a modification expression? Is there never a case for a conclusion?

Steve: The “continuation expression” tells the compiler when you want to
continue the loop; if the continuation expression comes out
10. You may sometimes see the term controlled statement used in place of
controlled block. Since, as we have already seen, a block can be used
anywhere that a single statement can be used, controlled statement and
controlled block are actually just two ways of saying nearly the same thing.
false , then the loop terminates. That serves the same purpose as an “ending
expression” might, but in reverse.

Finally, let’s consider the modification expression, i ++ .11 This is


exactly equivalent to i = i + 1 , which means “set i to one more than its
current value”, an operation technically referred to as incrementing a
variable. You may wonder why we need two ways to say the same
thing; actually, there are a few reasons. One is that ++ requires less
typing, which as I’ve already mentioned isn’t a strong point of many
programmers. Also, the ++ (pronounced “plus plus”) operator doesn’t
allow the possibility of mistyping the statement as, for example, i = j + 1;
when you really meant to increment i . Another reason why this feature
was added to the C language is that in the early days of C, compiler
technology wasn’t very advanced and the ++ operator allowed the
production of more efficient programs. You see, many machines can
add one to a memory location by a single machine language
instruction, usually called something like inc , shorthand for increment
memory. Even a simple compiler can generate an “increment memory”
instruction as a translation of i ++ , while it takes a bit more
sophistication for the compiler to recognize i= i+ 1 as an increment
operation. Since incrementing a variable is a very common operation
in C++, this was worth handling specially.12
Here’s an English translation of our sample for statement:
1. Set the index variable i to 0.
2. If the value of i is less than 5, execute the following block (in this
case, the block with the cout and cin statements). Otherwise, skip
to the next statement after the end of the controlled block; that is,
the one following the closing } .

11. You don’t need a space between the variable name and the ++ operator;
however, I think it’s easier to read this way.
12. By the way, the name C++ is sort of a pun using this notation; it’s supposed to
mean “the language following C”. In case you’re not doubled over with
laughter, you’re not alone. I guess you had to be there.
3. Add one to the value of i and go back to step 2.

Susan didn’t think these steps were very clear. Let’s listen in on the
conversation that ensued:

Susan: Where in the for statement does it say to skip to the next statement after
the end of the controlled block when i is 5 or more?

Steve: It doesn’t have to. Remember, the point of { } is to make a group of


statements act like one. A for statement always controls exactly one “statement”,
which can be a block contained in {} . Therefore, when the continuation expression
is no longer true, the next “statement” to be executed is whatever follows the } at
the end of the block.

Susan: Okay, now I get it. The { } curly brackets work together with the < 5
to determine that the program should go on to the next statement.

Steve: Right.

Susan: Now, on the “controlled block” — so other statements can be considered


controlled blocks too? I mean is a controlled block basically just the same thing as a
block? I reviewed your definition of block, and it seems to me that they are. I guess
it is just a statement that in this case is being controlled by for .

Steve: Correct. It’s called a controlled block because it’s under the control of
another statement.

Susan: So if we used while before the { } then that would be a


while controlled block?

Steve: Right.

Susan: Then where in step 3 or in i++ does it say to go back to step 2?


Steve: Again, the for statement executes one block (the controlled block)
repeatedly until the continuation expression is false. Since a block is equivalent to
one statement, the controlled block can also be referred to as the controlled
statement. In the current example, the block that is controlled by the for loop
consists of the four lines starting with the opening { on the next line after the for
statement itself and ending with the closing } after the line that says cin >>
Weight[i]; .

Susan: Okay. But now I am a little confused about something else here. I thought
that cout statements were just things that you would type in to be seen on the
screen.

Steve: That’s correct, except that cout is a variable used for I/O, not a
statement.

Susan: So then why is << i+1 << put in at this point? I understand what it does
now but I don’t understand why it is where it is.

Steve: Because we want to produce an output line that varies depending on the
value of i . The first time, it should say

Please enter weight #1:

The second time, it should say


Please enter weight #2:

and so on. The number of the weight we’re asking for is one more than i ; therefore
we insert the expression << i + 1 << in the output statement so that it will stick the
correct number into the output line at that point.

Susan: How does << i+1 << end up as #1 ?

Steve: The first time, i is 0; therefore, i + 1 is 1 . The # comes from the end
of the preceding part of the output statement.
Now let’s continue with the next step in the description of our for
loop, the modification expression i ++ . In our example, this will be
executed five times. The first time, i will be 0, then 1, 2, 3, and finally
4. When the loop is executed for the fifth time, i will be incremented
to 5; therefore, step 2 will end the loop by skipping to the next
statement after the controlled block.13 A bit of terminology is useful
here: each time through the loop is called an iteration.
Let’s hear Susan’s thoughts on this matter.

Susan: When you say that “step 2 will end the loop by skipping to the next
statement after the controlled block”, does that mean it is now going on to the next
for statement? So when i is no longer less than 5, the completion of the loop signals
the next controlled block?

Steve: Ye s and no. In general, after all the iterations in a loop have been
performed, execution proceeds to whatever statement follows the controlled block.
In this case, the next statement is indeed a for statement, so that’s the next
statement that is performed after the end of the current loop.

The discussion of the for statement led to some more questions about
loop control facilities and the use of parentheses:

Susan: How do you know when to use () ? Is it only with if and for and while and
else and stuff like that, whatever these statements are called? I mean they appear to
be modifiers of some sort; is there a special name for them?

Steve: The term loop control applies to statements that control loops that can
execute controlled blocks a (possibly varying)

13. Why is the value of i at the end of this loop 5 rather than 4? Because at the end
of each pass through the loop, the modification expression ( i ++ ) is executed
before the continuation expression that determines whether the next execution
will take place ( i < 5 ). Thus, at the end of the fifth pass through the loop, i is
incremented to 5 and then tested to see if it is still less than 5. Since it isn’t, the
loop terminates at that point.
number of times; these include for and while . The if statement is somewhat
different, since its controlled block is executed either once or not at all. The () are
needed in that case to indicate where the controlling expression(s) end and the
controlled block begins. You can also use () to control the order of evaluation of an
arithmetic expression: The part of the expression inside parentheses is executed first,
regardless of normal ordering rules. For example, 2*5+3 is 13, while 2*(5+3) is
16.

Susan: So then if you just wrote while CurrentWeight > 0 with no


() then the compiler couldn’t read it?

Steve: Correct.

Susan: Actually it is beginning to look to me as I scan over a few figures that


almost everything has a caption of some sort surrounding it. Everything either has a
" " or () or { } or [ ] or <> around it. Is that how it is going to be? I am still not
clear on the different uses of () and { } ; does it depend on the control loop?

Steve: The { } are used to mark the controlled block, while the () are used to
mark the conditional expression(s) for the if , while , for , and the like.
Unfortunately, () also have other meanings in C++, which we’ll get to eventually.
The inventor of the language considers them to have been overused for too many
different meanings, and I agree.

Susan: OK, I think I have it: { } define blocks and () define expressions. How
am I to know when a new block starts? I mean if I were doing the writing, it would
be like a new paragraph in English, right? So are there any rules for knowing when
to stop one block and start another?

Steve: It depends entirely on what you’re trying to accomplish. The main purpose
of a block is to make a group of statements act like one statement; therefore, for
example, when you want to
control a group of statements by one if or for , you group those statements into
a block.

Now that we’ve examined the for statement in excruciating detail,


what about the block it controls? The first statement in the block:

cout << "Please type in weight #" << i+1 << ": ";

doesn’t contain anything much we haven’t seen before; it just displays


a request to enter a weight. The only difference from previous uses
we’ve made of the cout facility, is that we’re inserting a numeric
expression containing a variable, i+1 , into the output. This causes the
expression to be translated into a human-readable form consisting of
digits. All of the expressions being sent to cout in one statement are
strung together to make one line of output, if we don’t specify
otherwise. Therefore, when this statement is executed during the first
iteration of the loop, the user of this program will see:

Please type in weight #1:

Then the user will type in the first weight. The same request, with a
different value for the weight number, will show up each time the user
hits ENTER, until five values have been accepted.
The second statement in the controlled block,

cin >> Weight[i];

is a little different. Here, we’re reading the number the user has typed
in at the keyboard and storing it in a variable. But the variable we’re
using is different each time through the loop; it’s the “ i th” element of
the Weight Vec. So, on the first iteration, the value the user types in will
go into Weight[0] , the value accepted on the second iteration will go
into Weight[1] , and so on, until on the fifth and last iteration, the typed-
in value will be stored in Weight[4] .
Here’s Susan’s take on this.
Susan: What do you mean by the i th element? So does Weight[i] mean you are
directing the number that the user types in to a certain location in memory?

Steve: Yes, to the element whose number is the current value of i.

Susan: When you say cin >> Weight[i] does that mean you are telling the
computer to place that variable in the index? So this serves two functions, displaying
the weight the user types in and associating it to the index?

Steve: No, that statement has the sole function of telling the computer to place
the value read in from the keyboard into element i of Vec Weight .

Susan: What I am confusing is what is being seen on the screen at the time that
the user types in the input. So, the user sees the number on the screen but then it
isn’t displayed anywhere after that number is entered? Then, the statement cin >>
Weight [i] directs it to a location somewhere in memory with a group of other
numbers that the user types in?

Steve: Correct. This will be illustrated under the contents of Weight heading in
Figures 4.10 –4.13.

4.5. The Selection Sort

Now that we have stored all of the weights, we want to find the three
highest of the weights. We’ll use a sorting algorithm called a
selection sort, which can be expressed in English as follows:
1. Repeat the following steps three times, once through for each
weight that we want to select.
2. Search through the list (i.e., the Weight Vec ), keeping track of the
highest weight seen so far in the list and the index of that highest
weight.
3. When we get to the end of the list, store the highest weight we’ve
found in another list (the “output list”, which in this case is the Vec
SortedWeight ).

4. Finally, set the highest weight we’ve found in the original list to 0,
so we won’t select it as the highest value again on the next pass
through the list.

Let’s take a look at the portion of our C++ program that implements this
sort, in Figure 4.8.

FIGURE 4.8. Sorting the weights (from code\vect1.cpp)

for (i = 0; i < 3; i ++)


{
HighestWeight = 0; for (k =
0; k < 5; k ++)
{
if (Weight[k] > HighestWeight)
{
HighestWeight = Weight[k]; HighestIndex
= k;
}
}
SortedWeight[i] = HighestWeight;
Weight[HighestIndex] = 0;
}

Susan had some interesting comments and questions on this algorithm.


Let’s take a look at our discussion of the use of the variable i :
Susan: Now I understand why you used the example of i = i + 1; in Chapter 3;
before, it didn’t make sense why you would do that silly thing. Anyway, now let me
get this straight. To say that, in the context of this exercise, means you can keep
adding 1 to the value of i ? I am finding it hard to see where this works for the
number 7, say, or anything above 5 for that matter. So, it just means you can have 4
+1 or + another 1, and so on? See where I am having trouble?

Steve: Remember, a short variable such as i is just a name for a 2- byte area
of RAM, which can hold any value between –32768 and
+32767. Therefore, the statement i ++; means that we want to recalculate the
contents of that area of RAM by adding 1 to its former contents.

Susan: No, that is not the answer to my question. Yes, I know all that <G>. What
I am saying is this: I assume that i ++; is the expression that handles any value over
4, right? Then let’s say that you have pumpkins that weigh 1, 2, 3, 4, and 5 pounds
consecutively. No problem, but what if the next pumpkin was not 6 but say 7
pounds? If at that point, the highest value for i was only 5 and you could only add 1
to it, how does that work? It just doesn’t yet have the base of 6 to add 1 to. Now do
you understand what I am saying?

Steve: I think I see the problem you’re having now. We’re using the variable i to
indicate which weight we’re talking about, not the weight itself. In other words, the
first weight is Weight[0] , the second is Weight[1] , the third is Weight[2] , the
fourth is Weight[3] , and the fifth is Weight[4] . The actual values of the weights
are whatever the user of the program types in. For example, if the user types in 3 for
the first weight, 9 for the second one, 6 for the third, 12 for the fourth, and 1 for the
fifth, then the Vec will look like Figure 4.9.
The value of i has to increase by only one each time because it indicates which
element of the Vec Weight is to store the current value being typed in by the user.
Does this clear up your confusion?

Susan: I think so. Then it can have any whole number value 0 or higher (well, up
to 32767); adding the 1 means you are permitting the addition of at least 1 to any
existing value, thereby allowing it to increase. Is that it?

FIGURE 4.9. Elements vs. values

Element Value
Weight[0] 3
Weight[1] 9
Weight[2] 6
Weight[3] 12
Weight[4] 1

Steve: No, I’m not permitting an addition; I’m performing it. Let’s suppose i is 0.
In that case, Weight[i] means Weight[0] , or the first element of the Weight Vec.
When I add 1 to i , i becomes 1. Therefore, Weight[i] now means Weight[1] .
The next execution of i ++; sets i to 2; therefore, Weight[i] now means
Weight[2] . Any time i is used in an expression, for example, Weight[i] , i + j , or i +
1 , you can replace the i by whatever the current value of i is. The only place
where you can’t replace a variable such as i by its current value is when it is being
modified, as in i ++ or the i in i = j + 1 . In those cases, i means the address
where the value of the variable i is stored.

Susan: OK, then i is not the number of the value typed in by the user; it is the
location of an element in the Weight Vec , and that is why it can increase by 1,
because of the i ++ ?
Steve: Correct, except that I would say “that is why it does
increase by 1”. This may just be terminology.

Susan: But in this case it can increase no more than 4 because of the i < 5 thing?

Steve: Correct.

Susan: But it has to start with a 0 because of the i = 0 thing?

Steve: Correct.

Susan: So then cin >> Weight [i] means that the number the user is typing has to
go into one of those locations but the only word that says what that location could be
is Weight ; it puts no limitations on the location in that Weight Vec other than when
you defined the index variable as short i; . This means the index cannot be more than
32767.

Steve: Correct. The current value of i is what determines which element of


Weight the user’s input goes into.

Susan: I think I was not understanding this because I kept thinking that i was
what the user typed in and we were defining its limitations. Instead we are telling it
where to go.

Steve: Correct.

Having beaten that topic into the ground, let’s look at the
correspondence between the English description of the algorithm and
the code:
1. Repeat the following steps once through for each prize:
for (i = 0; i < 3; i ++)
During this process the variable i is the index into the SortedWeight Vec
where we’re going to store the weight for the current prize we’re
working on. While we’re looking for the highest weight, i is 0; for
the second-highest weight, i is 1; finally, when we’re getting ready
to award a third prize, i will be 2.
2. Initialize the variable that we will use to keep track of the highest
weight for this pass through the data:
HighestWeight = 0;

3. Step through the input list:


for (k = 0; k < 5; k ++)

4. For each element of the list Weight , we check whether that element
( Weight[k] ) is greater than the highest weight seen so far in the list
( HighestWeight ). If that is the case, then we reset HighestWeight to the
value of the current element ( Weight[k] ) and the index of the highest
weight so far ( HighestIndex ) to the index of the current element ( k ):
if (Weight[k] > HighestWeight)
{
HighestWeight = Weight[k]; HighestIndex
= k;
}

5. When we get to the end of the input list, HighestWeight is the highest
weight in the list, and HighestIndex is the index of that element of the
list that had the highest weight. Therefore, we can copy the highest
weight to the current element of another list (the “output list”). As
mentioned earlier, i is the index of the current element in the output
list. Its value is the number of times we have been through the outer
loop before; that is, the highest weight, which we will identify first,
goes in position 0 of the output list, the next highest in position 1,
and so on:
SortedWeight[i] = HighestWeight;
6. Finally, set the highest weight in the input list to 0, so we won’t select
it as the highest value again on the next pass through the list.
Weight[HighestIndex] = 0;

This statement is the reason that we have to keep track of the


“highest index”; that is, the index of the highest weight.
Otherwise, we wouldn’t know which element of the original Weight
Vec we’ve used and therefore wouldn’t be able to set it to 0 to
prevent its being used again.

Here’s Susan’s rendition of this algorithm:

Susan: OK, let me repeat this back to you in English. The result of this program is
that after scanning the list of user input weights the weights are put in another list,
which is an ordering list, named k . The program starts by finding the highest weight
in the input list. It then takes it out, puts it in k , and replaces that value it took out
with a 0, so it won’t be picked up again. Then it comes back to find the next highest
weight and does the same thing all over again until nothing is left to order. Actually
this is more than that one statement. But is this what you mean? That one statement
is responsible for finding the highest weight in the user input list and placing it in k .
Is this right?

Steve: It’s almost exactly right. The only error is that the list that the weights are
moved to is the SortedWeight Vec , rather than k . The variable k is used to keep
track of which is the next entry to be put into the SortedWeight Vec .

Susan: OK. There was also something else I didn’t understand when tracing
through the program. I did see at one point during the execution of the program that
i=5 . Well, first I didn’t know how that could be because i is supposed to be < 5 , but
then I remembered that i ++ expression in the for loop, so I wondered if that is how
this happened. I forgot where I was at that point, but I think it was after I had just
completed entering 5 values and i was incrementing with
each value. But see, it really should not have been more than 4 because if you start
at 0 then that is where it should have ended up.

Steve: The reason that i gets to be 5 after the end of the loop is that at the end
of each pass through the loop, the modification expression ( i ++ ) is executed before
the continuation expression ( i < 5 ). So, at the end of the fifth pass through the loop,
i is incremented to 5 and then tested to see if it is still less than 5. Since it isn’t, the
loop terminates at that point.

Susan: I get that. But I still have a question about the line if (Weight[k] >
HighestWeight) . Well, the first time through, this will definitely be true because
we’ve initialized HighestWeight to 0, since any weight would be greater than 0. Is
that right?

Steve: Yes. Every time through the outer ( i ) loop, as we get to the top of the
inner loop, the 0 that we’ve just put in HighestWeight should be replaced by the
first element of Weight ; that is, Weight[0] , except of course if we’ve already
replaced Weight[0] by 0 during a previous pass. It would also be possible to
initialize HighestWeight to Weight[0] and then start the loop by setting k to 1
rather than 0. That would cause the inner ( k ) loop to be executed only four times
per outer loop execution, rather than five, and therefore would be more efficient.

Susan: Then HighestIndex=k; is the statement that sets the placement of the
highest number to its position in the Vec ?

Steve: Right.

Susan: Then I thought about this. It seems that the highest weight is set first, then
the sorting takes place so it makes four passes (actually five) to stop the loop.

Steve: The sorting is the whole process. Each pass through the outer loop locates
one more element to be put into the SortedWeight Vec .
Susan: Then the statement Weight[HighestIndex] = 0; comes into play,
replacing the highest number selected on that pass with 0.

Steve: Correct.

Susan: Oh, when k is going through the sorting process why does i increment
though each pass? It seems that k should be incrementing.

Steve: Actually, k increments on each pass through the inner loop, or 15 times in
all. It’s reset to 0 on each pass through the outer loop, so that we look at all of the
elements again when we’re trying to find the highest remaining weight. On the other
hand, i is incremented on each pass through the outer loop or three times in all,
once for each “highest” weight that gets put into the SortedWeight Vec.

Susan: OK, I get the idea with i , but what is the deal with k ? I mean I see it
was defined as a short , but what is it supposed to represent, and how did you know
in advance that you were going to need it?

Steve: It represents the position in the original list, as indicated in the description
of the algorithm.

Susan: I still don’t understand where k fits into this picture. What does it do?

Steve: It’s the index in the “inner loop”, which steps through the elements looking
for the highest one that’s still there. We get one “highest” value every time through
the “outer loop”, so we have to execute that outer loop three times. Each time
through the outer loop, we execute the inner loop five times, once for each entry in
the input list.

Susan: Too many terms again. Which is the “outer loop” and which is the “inner
loop”?
Steve: The outer loop executes once for each “highest” weight we’re locating.
Each time we find one, we set it to 0 (at the end of the loop) so that it won’t be
found again the next time through.

Susan: OK, but now I am confused with the statement: if (Weight[k]


> HighestWeight) . This is what gets me: if I understand this right (and obviously I
don’t) how could Weight[k] ever be greater than HighestWeight , since every
possible value of k represents one of the elements in the Weight Vec, and
HighestWeight is the highest weight in that Vec? For this reason I am having a hard
time understanding the code for step 2, but not the concept.

Steve: The value of HighestWeight at any time is equal to the highest weight
that has been seen so far. At the beginning of each execution of the outer loop,
HighestWeight is set to 0. Then, every time that the current weight ( Weight[k] ) is
higher than the current value of HighestWeight , we reset HighestWeight to the
value of the current weight.

Susan: I still don’t understand this statement. Help.

Steve: Remember that HighestWeight is reset to 0 on each pass through the


outer loop. Thus, this if statement checks whether the k th element of the Weight
Vec exceeds the highest weight we’ve seen before in this pass. If that is true,
obviously our “highest” weight isn’t really the highest, so we have to reset the
highest weight to the value of the k th element; if the k th element isn’t the true
highest weight, at least it’s higher than what we had before. Since we replace the
“highest” weight value with the k th value any time that the k th value is higher
than the current “highest” weight, at the end of the inner loop, the number remaining
in HighestWeight will be the true highest weight left in Weight . This is essentially
the same algorithm as we used to find the highest weight in the original version of
this program, but now we apply it several times to find successively lower “highest”
weights.
Susan: OK, I understand now, i increments to show how many times it has
looped through to find the highest number. You are doing a loop within a loop, really,
it is not side by side is it?

Steve: Correct.

Susan: So, when you first enter your numbers they are placed in an index called i,
then they are going to be cycled through again, placing them in a corresponding index
named k , looking for the top three numbers. To start out through each pass, you
first set the highest weight to the first weight since you have preset the highest
weight to 0. But, to find the top three numbers you have to look at each place or
element in the index. At the end of each loop you sort out the highest number and
then set that removed element to 0 so it won’t be selected again. You do this whole
thing three times.

Steve: That’s right, except for some terminology: where you say “an index called
i ”, you should say “a Vec called Weight ”, and where you say “an index called
k ”, you should say “a Vec called SortedWeight ”. The variables i and k are
used to step through the Vecs , but they are not the Vecs themselves.

Susan: OK, then the index variables just are the working representation of what is
going on in those Vecs . But are not the numbers “assigned” an index? Let’s see; if
you lined up your five numbers you could refer to each number as to its placement in
a Vec . Could you then have the column of weights in the middle of the two indexes
of i and k to each side?

Steve: If I understand your suggestion, it wouldn’t work, because k and i vary at


different speeds. During the first pass of the outer loop, i is 0, while k varies from
0 to 5; on the second pass of the outer loop, i is 1, while k varies from 0 to 5
again, and the same for the third pass of the outer loop. The value of i is used to
refer to an individual element of the SortedWeight Vec, the one that will receive the
next “highest” weight we locate. The value of k is used
to refer to an individual element of the Weight Vec , the one we’re examining to see
if it’s higher than the current HighestWeight .

Susan: This is what gets me, how do you know in advance that you are going to
have to set HighestIndex to k ? I see it in the program as it happens and I
understand it then, but how would you know that the program wouldn’t run without
doing that? Trial and error? Experience? Rule books? <G>

Steve: Logic. Let’s look at the problem again. The sorting algorithm that we’re
using here is called selection sort, because each time through the outer loop it
selects one element out of the input Vec and moves it to the output Vec. To prevent
our selecting the same weight (i.e., the highest one in the original input) every time
through the outer loop, we have to clear each weight to 0 as we select it. But, to do
that, we have to keep track of which one we selected; that’s why we need to save
HighestIndex .

The Selection Sort in More Detail

To help clear up any remaining questions Susan (and you) might have
about this algorithm, let’s go back and look at its steps more closely
(they start on page 182). Steps 1 through 3 should be fairly self-
explanatory, once you’re familiar with the syntax of the for statement;
they start the outer loop, initialize the highest weight value for the
current loop, and start the inner loop.
Step 4 is quite similar to the process we went through to find the
highest weight in our previous two programs; however, the reason for
the HighestIndex variable may still need some more clarification. We
need to keep track of which element of the original Vec (i.e., Weight )
we have decided is the highest so far, so that this element won’t be
selected as the highest weight on every pass through the Weight Vec. To
prevent this error, step 4 sets each “highest” weight to a value that
won’t be selected on a succeeding pass. Since we know there should
be no 0 weights in the Weight Vec, we can set each selected element to 0
after it has been selected, to prevent its reselection. Figure 4.10
shows a picture of the situation before the first pass through the data,
with ??? in SortedWeight to indicate that those locations contain
unknown data, as they haven’t been initialized yet.
In Figure 4.10, the highest value is 11 in Weight[2] . After we’ve
located it and copied its value to SortedWeight[0] , we set Weight[2] to 0,
yielding the situation in Figure 4.11.

FIGURE 4.10. Initial situation

Index Contents of Weight Contents of


SortedWeight
0 5 ???
1 2 ???
2 11 ???
3 3
4 7

FIGURE 4.11. After the first pass

Index Contents of Weight Contents of


SortedWeight
0 5 11
1 2 ???
2 0 ???
3 3
4 7

Now we’re ready for the second pass. This time, the highest value is
the 7 in Weight[4] . After we copy the 7 to SortedWeight[1] , we set Weight[4]
to 0, leaving the situation in Figure 4.12.
FIGURE 4.12. After the second pass

Index Contents of Weight Contents of


SortedWeight
0 5 11
1 2 7
2 0 ???
3 3
4 0

On the third and final pass, we locate the 5 in Weight[0] , copy it to


SortedWeight[2] , and set Weight[0] to 0. As you can see in Figure 4.13,
SortedWeight now has the results we were looking for: the top three
weights, in descending order.

FIGURE 4.13. Final situation

Index Contents of Weight Contents of


SortedWeight
0 0 11
1 2 7
2 0 5
3 3
4 0

Making Assumptions Can Be Dangerous

That accounts for all the steps in the sorting algorithm. However, our
implementation of the algorithm has a weak spot that we should fix. If
you want to try to find it yourself, look at the code and explanation
again before going on. Ready?
The key word in the explanation is “should” in the following
sentence: “Since we know there should be no 0 weights in the Weight
Vec , we can set each selected element to 0 after it has been selected,
to prevent its reselection.” How do we know that there are no 0
weights? We don’t, unless we screen for them when we accept input.
In the first pumpkin-weighing program, we stopped the input when we
got a 0, but in the programs in this chapter we ask for a set number of
weights. If one of them is 0, the program will continue along happily.14
Before we change the program, though, let’s try to figure out what
would happen if the user types in a 0 for every weight.
You can try this scenario out yourself. First, you have to compile
the program vect1 by the usual method, then run the program, either
normally or under the debugger. When it asks for weights, enter a 0 for
each of the five weights.
In case you’re reading this away from your computer, Figure 4.14
shows what might happen (although the element number in the message
may not be the same)15.

FIGURE 4.14. A possible error


message

You have tried to use element 68 of a vector which has only 5 elements.

Why doesn’t the program work in this case? Because we have an


uninitialized variable; that is, one that has never been set to a valid
value. In this case, it’s HighestIndex . Let’s look at the sorting code one
more time, in Figure 4.15.16

FIGURE 4.15. Sorting the weights,


again (from code\vect1.cc)

for (i = 0; i < 3; i ++)


{
HighestWeight = 0;
14. For that matter, what if someone types in a negative weight, such as – 5? Of
course, this doesn’t make any sense; but it’s a good idea to try to prevent errors
rather than assuming that users of a program will always act sensibly.
15. It’s even possible that you won’t get an error message at all and the program
will run correctly. However, if this happens, it just means that you’re lucky. As
you’ll see later, you shouldn’t count on that sort of luck.
for (k = 0; k < 5; k ++)
{
if (Weight[k] > HighestWeight)
{
HighestWeight = Weight[k]; HighestIndex
= k;
}
}
SortedWeight[i] = HighestWeight;
Weight[HighestIndex] = 0;
}

It’s clear that HighestWeight is initialized (i.e., given a valid value)


before it is ever used; the statement HighestWeight = 0; is the first
statement in the block controlled by the outer for loop. However, the
same is not true of HighestIndex . Whenever the condition in the if
statement is true , both HighestWeight and HighestIndex will indeed be set
to legitimate values: HighestWeight will be the highest weight seen so far
on this pass, and HighestIndex will be the index of that weight in the
Weight Vec. However, what happens if the condition in the if statement
never becomes true ? In that case, HighestIndex will have whatever
random value it started out with at the beginning of the program; it’s
very unlikely that such a value will be correct or even refer to an
actual element in the Weight Vec.

16. You may have noticed a slight oddity in this code. The block controlled by the
for statement consists of exactly one statement; namely, the if that checks for a
new HighestWeight value. According to the rules I’ve provided, that means we
don’t have to put curly braces ( { }) around it to make it a block. While this is
true, long experience has indicated that it’s a very good idea to make it a block
anyway, as a preventive measure. It’s very common to revisit old code to fix
bugs or add new functions, and in so doing we might add another statement
after the if statement at a later time, intending it to be controlled by the for .
The results wouldn’t be correct, since the added statement would be executed
exactly one time after the loop was finished, rather than once each time through
the loop. Such errors are very difficult to find, because the code looks all right
when inspected casually; therefore, a little extra caution when writing the
program in the first place often pays off handsomely.
Why We’re Using Vec Rather Than vector

By the way, this illustrates the reason why we are using the Vec type
rather than the standard library type vector ; that error message comes
from my code in the implementation of Vec, not from vector . If you try
to refer to a nonexistent element of a vector , the results will be
unpredictable. This is not a defect in the design of the vector type, by
the way; because of the emphasis on speed in C++, that particular
error check was left to be added by the programmer if he or she wants
to add it. If the program is designed in such a way that no illegal
reference can ever be made to an element of a vector , there is no
necessity to slow it down by doing the error check all the time.17
However, even as a professional programmer of many years
experience, I prefer to have the assurance that if I make a mistake in
using a vector , or similar data type, I will be notified of it rather than
having something go wrong that I don’t know about, so I usually accept
the performance penalty of such checking. Of course, this is even more
important when you are just getting started, as you have much less
experience to draw on to try to figure out why your program isn’t
working.

Uninitialized Variables

As you can imagine, Susan was interested in the notion of


uninitialized variables. Here’s the discussion we had on this topic:

Susan: You say that HighestIndex isn’t initialized properly. But what about when
you set k equal to 0 and then HighestIndex is set equal to k ? Is that not
initialized?

17. Although it would have been possible to make the normal indexing via [ ] do
the check as we do in the Vec type and add another way to say “don’t check
the validity of this index”. That would be a better design, in my opinion.
Steve: The problem is that the statement HighestIndex = k; is executed only
when Weight[k] is greater than HighestWeight . If that never occurs, then
HighestIndex is left in some random state.

Susan: OK, then why didn’t you say so in the first place? I understand that.
However, I still don’t understand why the program would fail if all the weights the
user typed in were 0. To me it would just have a very boring outcome.

Steve: That’s the case in which HighestIndex would never be initialized;


therefore, it would contain random garbage and would cause the program to try to
display an element at some random index value.

Susan: I traced through the program again briefly tonight and that reminds me to
ask you why you put the highest weight value to 1596 and the second-highest weight
value to 1614?

Steve: I didn’t. Those just happened to be the values that those memory locations
had in them before they were initialized.

Susan: I was totally confused right from the beginning when I saw that. But did
you do that to show that those were just the first two weights, and that they have not
been, how would you say this, “ordered” yet? I don’t know the language for this in
computerese, but I am sure you know what I am saying.

Steve: Not exactly; those variables haven’t been initialized at that point, so
whatever values they might contain would be garbage.

Susan: So at that point they were just the first and second weights, or did you just
arbitrarily put those weights in there to get it started? Anyway, that was baffling
when I saw that.

Steve: Before you set a variable to a particular value, it will have some kind of
random junk in it. That’s what you’re seeing at the
beginning of the program, before the variables have been initialized.

Susan: OK, I am glad this happened, I can see this better, but whose computer
did that? Was it yours or mine? I mean did you run it first and your computer did it, or
was it my computer that came up with those values?

Steve: It’s your computer. The program starts out with “undefined” values for all
of the uninitialized variables. What this means in practice is that their values are
whatever happened to be left around in memory at those addresses. This is quite
likely to be different on your machine from what it is on mine or even on yours at a
different time.

Susan: So something has to be there; and if you don’t tell it what it is, the old
contents of memory just comes up?

Steve: Right.

Susan: If it had worked out that the higher number had been in the first place,
then I would have just assumed that you put that there as a starting point. I am really
glad that this happened but I was not too happy about it when I was trying to figure it
out.

Steve: See, it’s all for your own good.

Susan: If that were the case, I would think it nearly impossible that we have the
same values at any given address. How could they ever be remotely the same?

Steve: It’s very unlikely that they would, unlessthe address was one that was
used by very basic software such as DOS or Windows, which might be the same on
our computers.

Susan: Anyway, then you must have known I was going to get “garbage” in those
two variables, didn’t you? Why didn’t you
advise me at least about that? Do you know how confusing it was to see that first
thing?

Steve: Yes, but it’s better for you to figure it out yourself. Now you really know it,
whereas if I had told you about it in advance, you would have relied on my
knowledge rather than developing your own.

I hope that has cleared up the confusion about the effect of an


uninitialized variable in this example. But you may still be wondering
why we have to initialize variables ourselves; surely they must have
some value at any given time. Let’s listen in on the conversation that
Susan and I had about this point:

Susan: So, each bit in RAM is capable of being turned on or off by a 1 or a 0?


Which one is on and which one is off? Or does that matter? How does this work
electronically? I mean how does the presence of a 0 or a 1 throw the RAM into a
different electronic state?

Steve: To be more exact, each “switch” is capable of existing in either the “on” or
“off” state. The assignment of states to 1s and 0s is our notion, which doesn’t affect
the fact that there are exactly two distinct states the switch can assume, just like a
light switch (without a dimmer). We say that if the switch is off, it’s storing a 0, and if
it’s on, it’s storing a 1.

Susan: What is the “normal state” of RAM: on or off?

Steve: It’s indeterminate. That’s one reason why we need to explicitly set our
variables to a known state before we use them.

Susan: That didn’t make sense to me originally, but I woke up this morning and
the first thing that came to my mind was the light switch analogy. I think I know
what you meant by indeterminate.
If we consider the light switch as imposed with our parental and financial values, it is
tempting to view the “normal state” of a light switch as off. Hey, does the light
switch really care? It could sit there for 100 years in the on position as easily as in
the off position. Who is to say what is normal? The only consequence is that the light
bulb will have been long burned out. So it doesn’t matter, it really doesn’t have a
normal state, unless people decide that there is one.

Steve: What you’ve said is correct. The switch doesn’t care whether it’s on or
off. In that sense, the “normal” position doesn’t really have a definition other than
one we give it.

However, what I meant by indeterminate is slightly different. When power is applied


to the RAM, each bit (or to be more precise, a switch that represents that bit) could
just as easily start out on as off. It’s actually either one or the other, but which one is
pretty much random so we have to set it to something before we know its value.

Susan: Oh, you broke my heart, when I thought I had it all figured out! Well, I
guess it was OK, at least as far as the light switch was concerned, but then RAM
and a light switch are not created equal. So RAM is pretty easy to please, I guess...

After that bit of comic relief, let’s get back to the analysis of this
program. It should be fairly obvious that if the user types in even one
weight greater than 0, the if statement will be true when that weight is
encountered, so the program will work. However, if the user typed in
all 0 weights, the program would fail, as we saw before, because the
condition in the if statement would never become true . To prevent this
from causing program failure, all we have to do is to add one more
line, the one in bold in Figure 4.16.
FIGURE 4.16. Sorting the weights, with correct initialization (from code\vect2.cpp)

for (i = 0; i < 3; i ++)


{
HighestWeight = 0;
HighestIndex = 0;
for (k = 0; k < 5; k ++)
{
if (Weight[k] > HighestWeight)
{
HighestWeight = Weight[k]; HighestIndex
= k;
}
}
SortedWeight[i] = HighestWeight;
Weight[HighestIndex] = 0;
}

Now we can be sure that HighestIndex always has a value that


corresponds to some element of the Weight Vec , so we won’t see the
program fail as the previous one would.
You can try this scenario out yourself. First you have to compile
the program vect2 by the usual method, then run the program, either
normally or under the debugger. When it asks for weights, enter a 0 for
each of the five weights.
After you’ve entered 5 weights, the program will start the sorting
process. This time, entering five 0 weights will produce the expected
result: the top three weights will all be 0.
By the way, it’s also possible to initialize a variable at the same
time as you define it. For example, the statement short i = 12; defines a
short variable called i and sets it to the value 12 at the same time. This
is generally a good practice to follow when possible; if you initialize
the variable when you define it, you don’t have to remember to write a
separate statement to do the initialization.
4.6. Program Failure

We should pay some more attention to the notion of program failure, as


it’s very important. The first question, of course, is what it means to
say that a program “fails”. A valid answer is that it doesn’t work
correctly, but that isn’t very specific.
As you can imagine, this notion was the topic of some discussion
with Susan:

Susan: What do you mean by a program failing? I know it means it won’t work,
but what happens? Do you just get error messages, and it won’t do anything? Or is it
like the message that you have on page 192?

Steve: In general, a program “failing” means that it does something unexpected


and erroneous. Because I have put some safety features into the implementation of
Vec, you’ll get an error message if you misuse a Vec by referring to a nonexistent
element.

In general, a program failure may or may not produce an error


message. In the specific case that we’ve just seen, we’ll probably get
an error message while trying to access a nonexistent element of the
Weight Vec . However, it’s entirely possible for a program to just “hang”
(run endlessly), “crash” your system, produce an obviously ridiculous
answer, or worst of all, provide a seemingly correct but actually
erroneous result.
The causes of program failures are legion. A few of the
possibilities are these:
1. Problems isolated to our code
a. The original problem could have been stated incorrectly.
b. The algorithm(s) we’re using could have been inappropriate for
the problem.
c. The algorithm(s) might have been implemented incorrectly.
d. An input value might be outside the expected range.

And so on...

2. Problems interacting with other programs


a. We might be misusing a function supplied by the standard
library, like the << operator.
b. The documentation for a standard library function might be
incorrect or incomplete. This is especially common in “guru”-
oriented operating systems, where the users are supposed to know
everything.
c. A standard library function might be unreliable. This is more
common than it should be.
d. The compiler might be generating the wrong instructions. I’ve seen
this on a few rare occasions.
e. Another program in the system might be interfering with our
program. This is quite common in some popular operating
environments that allow several programs to be executing
concurrently.

And so on...

With simple programs like the ones we’re writing here, errors such as
the ones listed under problems with our code are more likely as we
have relatively little interaction with the rest of the system. As we
start to use more sophisticated mechanisms in C++, we’re more likely
to run into instances of interaction problems.
Why We Need to Initialize Variables Explicitly

After that excursion into the sources of program failure, let’s get back
to our question about initializing variables. Why do we have to worry
about this at all? It would seem perfectly reasonable for the compiler
to make sure that our variables were always initialized to some
reasonable value; in the case of numeric variables such as a short , 0
would be a good choice. Surely Bjarne Stroustrup, the designer of
C++, didn’t overlook this.
No, he didn’t; he made a conscious decision not to provide this
facility. It’s not due to cruelty or unconcern with the needs of
programmers. On the contrary, he stated in the Preface to the first
Edition of The C++ Programming Language that “C++ is a general-
purpose programming language designed to make programming more
enjoyable for the serious programmer”.18 To allow C++ to replace C
completely, he could not add features that would penalize efficiency
for programmers who do not use these features. By the same reasoning
that prevented the inclusion of index error checking in the vector data
type, Bjarne decided not to add initialization as a built-in function of
the language because it would make programs larger and slower if the
programmer had already taken care of initializing all variables as
needed. This may not be obvious, but we’ll see in a later section why
it is so.
Here’s Susan’s reaction to these points about C++:

Susan: What is run-time efficiency?

Steve: How long it takes to run the program and how much memory it uses.

Susan: So are you saying that C++ is totally different from C? That one is not
based on the other?

18. The C++ Programming Language, 3rd Edition. ix.


Steve: No, C++ is a descendant of C. However, C++ provides much more
flexibility to programmers than C.

Susan: Now, about what Bjarne said back in 1986: Who enjoys this, and if C++ is
intended for a serious programmer, why am I reading this book? What is a serious
programmer? Would you not think a serious programmer should have at least taken
Computer Programming 101?

Steve: This book has been used as a textbook for such a course. Anyway, if you
want to learn how to program, you have to start somewhere, and it might as well be
with the intention of being a serious programmer.

Preventing Improper Data Input

In the meantime, there’s something else we should do if we want the


program to work as it should. As the old saying “Garbage in, garbage
out” suggests, by far the best solution to handling spurious input values
is to prevent them from being entered in the first place. What we want
to do is to check each input value and warn the user if it’s invalid.
Figure 4.17 illustrates a new input routine that looks like it might do
the trick.

FIGURE 4.17. Garbage prevention, first attempt (from code\vect2a.cc)

for (i = 0; i < 5; i ++)


{
cout << "Please type in weight #" << i+1 << ": "; cin >>
Weight[i];
if (Weight[i] <= 0)
{
cout << "I’m sorry, " << Weight[i] << " is not a valid weight."; cout << endl;
}
}
As usual, you can try this version out yourself. First, you have to
compile the program vect2a by the usual method, then run the program,
either normally or under the debugger. When you are asked for a
weight, type one in and hit ENTER just as when executing normally.
After you’ve entered 5 weights, the program will start the sorting
process. When finished, it will display the top three weights of the
five that were entered. Most of this should be familiar; the only line
that has a new construct in it is the if statement. The condition <=
means “less than or equal to”, which is reasonably intuitive.
Unfortunately, this program won’t really solve the problem of bad
input. The problem is what happens after the error message is
displayed; namely, the loop continues at the top with the next weight,
and we never correct the erroneous input. Susan didn’t have much
trouble figuring out exactly what that last statement meant:

Susan: When you say that “we never correct the erroneous input”, does that mean
that it is added to the list and not ignored?

Steve: Right.

To fix this problem completely, we need to use an approach similar to


the one shown in the final version of this program (Figure 4.18). To try
this version out yourself, you have to compile the program vect2a by
the usual method, then run the program, either normally or under the
debugger. When you are asked for a weight, type one in and hit ENTER.
After you’ve entered five weights, the program will start the sorting
process, and will display the results when finished.

FIGURE 4.18. Finding the top


three weights using Vecs (code\vect3.cc)

#include <iostream>
#include “Vec.h” using
namespace std;

int main()
{
Vec<short> Weight(5);
Vec<short> SortedWeight(3); short
HighestWeight;
short HighestIndex;
short i;
short k;
cout << “I’m going to ask you to type in five weights, in pounds.” << endl; for (i = 0;
i < 5; )
{
cout << “Please type in weight #” << i+1 << “: “; cin
>> Weight[i];
if (Weight[i] <= 0)
{
cout << “I’m sorry, “ << Weight[i] << “ is not a valid weight.”; cout
<< endl;
}
else
i ++;
}

for (i = 0; i < 3; i ++)


{
HighestIndex = 0;
HighestWeight = 0; for
(k = 0; k < 5; k ++)
{
if (Weight[k] > HighestWeight)
{
HighestWeight = Weight[k];
HighestIndex = k;
}
}
SortedWeight[i] = HighestWeight;
Weight[HighestIndex] = 0;
}

cout << “The highest weight was: “ << SortedWeight[0] << endl;
cout << “The second highest weight was: “ << SortedWeight[1] << endl; cout <<
“The third highest weight was: “ << SortedWeight[2] << endl;

return 0;
}
Now let’s look at the changes that we’ve made to the program from the
last revision. The first change is that the for statement that controls the
block where the input is accepted from the user has only two sections
rather than three. As you may recall, the first section specifies the
initial condition of the index variable; in this case, we’re starting i out
at 0, as is usual in C and C++. The second section indicates when we
should continue executing the loop; here, we should continue as long
as i is less than 5. But the third section, which usually indicates what to
do to the index variable, is missing. The reason for this is that we’re
going to adjust the index variable manually in the loop, depending on
what the user enters.
In this case, if the user enters an invalid value (i.e., less than or
equal to 0), we display an error message and leave i as it was, so that
the next time through the loop the value will go into the same element in
the Weight Vec. When the user enters a valid value, the else clause
increments i so that the next value will go into the next element in the
Vec. This fixes the error in our previous version that left incorrect
entries in the Vec.
After finishing with the pumpkin program, let’s tune in on a
discussion I had with Susan on how to create an algorithm in the first
place.

Susan: Do they make instruction sheets with directions of paths to follow? How
do you identify problems? I mean, don’t you encounter pretty much the same types
of problems frequently in programming and can they not be identified some way so
that if you knew a certain problem could be categorized as a Type C problem, let’s
say, you would approach it with a Type C methodology to the solution? Does that
make sense? Probably not.

Steve: It does make sense, and in fact that’s what the standard library is designed
to do for very commonly used algorithms and data structures. At another level,
another book of mine, Optimizing C++ (ISBN 0-13-977430-0), is designed to
provide something like you’re suggesting for more specific, but still relatively
common, problems at the algorithmic level. There’s also a book called
Design Patterns (ISBN 0-201-63361-2) that tries to provide tested solutions to
common design problems, at a much more abstract level, and a new book called
Modern C++ Design (ISBN 0-201- 70431-5) that shows how to use advanced
features of C++ to implement a number of the high-level design ideas from the
Design Patterns book (among a lot of other very useful material).

Now that we have beaten the pumpkin weighing example to a pulp19,


let’s review the mass of information to which I’ve subjected you so far
in this chapter.

4.7. Review

We started out by extending our pumpkin weighing program to tell us


the highest two weights rather than just the highest one. During this
exercise, we learned the use of the else clause of an if statement. We
also saw that making even an apparently simple change to a working
program can introduce an error; in this case we were copying the
highest weight to the next highest weight only when a new high weight
was detected. This would produce an incorrect result if a value higher
than the previous second highest but lower than the current highest
weight were entered.
Next we extended the program again, this time to handle any
number of prizes to be given to the highest weight, second-highest
weight, third-highest weights, and so on. This inspired a complete
reorganization of the program; the new version used the selection sort
algorithm to produce a list of as many of the highest weights as we
need, in descending order. To do this, we had to use a Vec, or set of
values with a common name, to store all of the weights as they were
read in. When they had all been entered, we searched through them
three times, once to find each of the top three elements. A Vec, just

19. Pumpkin pie, anyone?


like a regular variable, has a name. However, unlike a regular
variable, a Vec does not have a single value, but rather consists of a
number of elements, each of which has a separate value. An element is
referred to by a number, called an index, rather than by a unique name;
each element has a different index. The lowest index is 0, and the
highest index is 1 less than the number of elements in the Vec; for
example, with a 10 element Vec, the legal indexes are 0 through 9. The
ability to refer to an element by its index allows us to vary the element
we are referring to in a statement by varying the index; we put this
facility to good use in our implementation of the selection sort, which
we’ll review shortly.
We then added the for statement to our repertoire of loop control
facilities. This statement provides more precise control than the while
statement. Using for , we can specify a starting expression, a
continuation expression, and a modification expression. The starting
expression sets up the initial conditions for the loop. Before each
possible execution of the controlled block, the continuation expression
is checked and if it is true , the controlled block will be executed;
otherwise, the for loop will terminate. Finally, the modification
expression is executed after each execution of the controlled block.
Most commonly, the starting expression sets the initial value of a
variable, the continuation expression tests whether that variable is still
in the range we are interested in, and the modification expression
changes the value of the variable. For example, in the for statement

for (i = 0; i < 5; i ++)

the starting expression is i = 0 , the continuation expression is i < 5 , and


the modification expression is i ++ . Therefore, the block controlled by
the for statement will be executed first with the variable i set to 0; at
the end of the block, the variable i will be incremented by 1, and the
loop will continue if i is still less than 5.
Then we used the for statement and a couple of Vecs to implement a
selection sort. This algorithm goes through an “input list” of n
elements once for each desired “result element”. In our case, we want
the top three elements of the sorted list, so the input list has to be
scanned three times. On each time through, the algorithm picks the
highest value remaining in the list and adds that to the end of a new
“output list”. Then it removes the found value from the input list. At
the end of this process, the output list has all of the desired values
from the input list, in descending order of size. When going over the
program, we found a weak spot in the first version: If all the weights
the user typed were less than or equal to 0, the program would fail
because one of the variables in the program would never be
initialized; that is, set to a known value.
This led to a discussion of why variable initialization isn’t done
automatically in C++. Adding this feature to programs would make
them slower and larger than C programs doing the same task, and C++
was intended to replace C completely. If C++ programs were
significantly less efficient than equivalent C programs, this would not
be possible, so Bjarne Stroustrup omitted this feature.
While it’s important to insure that our programs work correctly even
when given unreasonable input, i t ’s even better to prevent this
situation from occurring in the first place. So, the next improvement
we made to our pumpkin weighing program was to tell the user when
an invalid value had been entered and ask for a valid value in its
place. This involved a for loop without a modification expression,
since we wanted to increment the index variable i to point to the next
element of the Ve c only when the user typed in a valid entry; if an
illegal value was typed in, we requested a legal value for the same
element of the Vec.

4.8. Exercises

So that you can test your understanding of this material, here are some
exercises.
1. If the program in Figure 4.19 is run, what will be displayed?
FIGURE 4.19. Exercise 1
(code\morbas00.cpp)

#include <iostream>
#include “Vec.h” using
namespace std;

int main()
{
Vec<short> x(5);
short Result; short i;

for (i = 0; i < 5; i ++)


{
x[i] = 2 * i;
}

for (i = 0; i < 5; i ++)


{
Result = Result + x[i];
}

cout << Result << endl;

return 0;
}

2. If the program in Figure 4.20 is run, what will be displayed?

FIGURE 4.20. Exercise 2


(code\morbas01.cpp)

#include <iostream>
#include “Vec.h” using
namespace std;

int main()
{
Vec<short> x(4);
short Result; short i;
x[0] = 3;
for (i = 1; i < 4; i ++) x[i]
= x[i-1] * 2;

Result = 0;
for (i = 0; i < 4; i ++) Result =
Result + x[i];

cout << Result << endl;

return 0;
}

3. Write a program that asks the user to type in a weight, and display the
weight on the screen.
4. Modify the program from exercise 3 to ask the user to type as many
weights as desired, stopping as soon as a 0 is entered. Add up all
of the weights entered and display the total on the screen at the end
of the program.

Answers to exercises can be found at the end of the chapter.

4.9. Conclusion

We’ve covered a lot of material in this chapter in our quest for better
pumpkin weighing, ranging from sorting data into order based on
numeric value through the anatomy of Vecs . Next, we’ll take up some
more of the language features you will need to write any significant
C++ programs.
4.10. Answers to Exercises
1. The correct answer is: “Who knows?” If you said “30”, you forgot
that the loop variable values are from 0 through 4, rather than from 1
through 5. On the other hand, if you said “20”, you had the right total
of the numbers 0, 2, 4, 6, and 8, but didn’t notice that the variable
Result was never initialized. Of course, adding anything to an
unknown value makes the final value unpredictable. Many current
compilers, unfortunately not including the one on the CD in the back
of this book, are capable of warning you about such problems. If
you’re using a compiler that supports such warnings, I
recommended that you enable them because that is the easiest way
to find such errors, especially in a large program. Unfortunately, a
compiler may produce such warnings even when they are not valid,
so the final decision is still up to you.
To try this program out, compile morbas00.cpp in the usual manner.
Running this program normally isn’t likely to give you much
information, so you’ll probably want to run it under the debugger.
2. The correct answer is 45. In case this isn’t obvious, consider the
following:
a. The value of x[0] is set to 3.
b. In the first for loop, the value of i starts out at 1.
c. Therefore, the first execution of the assignment statement
x[i] = x[i–1] * 2; is equivalent to x[1] = x[0] * 2; . This clearly sets
x[1] to 6.

d. The next time through the loop i is 2, so that same assignment


statement x[i] = x[i–1] * 2; is equivalent to x[2] = x[1] * 2; . This sets
x[2] to 12.

e. Finally, on the last pass through the loop, the value of i is 3, so


that assignment statement x[i] = x[i–1] * 2; is equivalent to
x[3] = x[2] * 2; This sets x[3] to 24.
f. The second for loop just adds up the values of all the entries in
the x Vec ; this time, we remembered to initialize the total, Result ,
to 0, so the total is calculated and displayed correctly.
Running this program normally isn’t likely to give you much
information, but you might want to run it under control of the
debugger.
3. Let’s start with Susan’s proposed solution to this problem, in
Figure 4.21, and the questions that came up during the process.

FIGURE 4.21. Weight requesting program, first try (code\morbas02.cpp)

#include <iostream> using


namespace std;

int main()
{
short weight;

cout << “Please write your weight here. “\n; cin >>

weight
return 0;
}

Susan: Would this work? Right now by just doing this it brought up several things
that I have not thought about before.

First, is the # standard for no matter what type of program you are doing?

Steve: The iostream header file is needed if you want to use << ,
>> , cin and cout , which most programs do, but not all.

Susan: Ok, but I meant the actual pound sign ( # ), is that always a part of
iostream ?
Steve: It’s not part of the filename, it’s part of the #include command, which
tells the compiler that you want it to include the definitions in the iostream file in
your program at that point.

Susan: So then this header is declaring that all you are going to be doing is input
and output?

Steve: Not exactly. It tells the compiler how to understand input and output via
<< and >> . Each header tells the compiler how to interpret some type of library
functions; iostream is the one for input and output.

Susan: Where is the word iostream derived from? (OK, io , but what about
stream ?)

Steve: A stream is C++ talk for “a place to get or put characters”. A given
stream is usually either an istream (input stream ) or an ostream (output
stream ). As these names suggest, you can read from an istream or write to an
ostream .

Susan: Second, is the \n really necessary here, or would the program work
without it?

Steve: It’s optional, however, if you want to use it the \n should be inside the
quotes, since it’s used to control the appearance of the output. It can’t do that if it’s
not sent to cout . Without the \n , the user would type the answer to the question on
the same line as the question. With the \n , the answer would be typed on the next
line as the \n would cause the active screen position to move to the next line at the
end of the question.

Susan: OK, that is good, since I intended for the weight to be typed on a different
line. Now I understand this much better. As far as why I didn’t include the \n inside
the quotes, I can’t tell you other than the time of night I was writing or it was an
oversight or a typo. I was following your examples and I am not a stickler for details
type person.
Now that that’s settled, I have another question: Is “return 0” the same thing as an
ENTER on the keyboard with nothing left to process?

Steve: Sort of. When you get to that statement in main , it means that you’re
done with whatever processing you were doing and are returning control to the
operating system (the C: prompt).

Susan: How does the program handle the ENTER? I don’t see where it comes
into the programs you have written. It just seems that, at the end of any pause, an
ENTER would be appropriate. So does the compiler just know by the way the code
is written that an ENTER will necessarily come next?

Steve: The >> input mechanism lets you type until you hit an ENTER (or a
space in the case of a string ), then takes the result up to that point.

One more point. We never tell the user that we have received the information. I’ve
added that to your example.

Figure 4.22 illustrates the compiler’s output for that erroneous program.

Error messages from the erroneous weight program


FIGURE 4.22.
(code\morbas02.cpp)

Error E2206 MORBAS02.cpp 8: Illegal character ’\’ (0x5c) in function main() Error
E2379 MORBAS02.cpp 8: Statement missing ; in function main() Error E2379
MORBAS02.cpp 12: Statement missing ; in function main()

And Figure 4.23 shows the corrected program.

FIGURE 4.23. The corrected weight program (code\morbas03.cpp)

#include <iostream>
using namespace std;

int main()
{
short weight;

cout << “Please write your weight here: “; cin >>

weight;
cout << “I wish I only weighed “ << weight << “ pounds.”;

return 0;
}

4. This was an offshoot of the previous question, which occurred


when Susan wondered when the program in Figure 4.23 would
terminate. Let’s start from that point in the conversation:

Susan: Would this only run once? If so how would you get it to repeat?

Steve: We could use a while loop. Let’s suppose that we wanted to add up all
the weights that were entered. Then the program might look like Figure 4.24.

FIGURE 4.24. The weight totalling


program (code\morbas04.cpp)

#include <iostream> using


namespace std;

int main()
{
short weight;
short total;

cout << “Please type in your weight, typing 0 to end:”;


cin >> weight;
total = weight; while
(weight > 0)
{
cout << “Please type in your weight, typing 0 to end:”; cin >> weight;
total = total + weight;
}

cout << “The total is: “ << total << endl; return 0;
}

In case you were wondering, the reason we have to duplicate the


statements to read in the weight is that we need an initial value for the
variable weight before we start the while loop, so that the condition in
the while will be calculated correctly.
By the way, there’s another way to write the statement
total = total + weight;

that uses an operator analogous to ++ , the increment operator:


total += weight;

This new operator, += , means “add what’s on the right to what’s


on the left”. The motivation for this shortcut, as you might imagine,
is the same as that for ++ : it requires less typing, is more likely to
be correct, and is easier to compile to efficient code. Just like the
“increment memory” instruction, many machines have an “add
(something) to memory” instruction. It’s easier to figure out that
such an instruction should be used for an expression like
x += y than in the case of the equivalent x = x + y . Let’s see what Susan
has to say about this notation:
Susan: Now I did find something that was very confusing. You say that +=
means to “add what’s on the right to what’s on the left” but your example shows that
it is the other way around. Unless this is supposed to be mirror imaged or something,
I don’t get it.

Steve: No, the example is correct. total += weight; is the same as


total = total + weight; , so we’re adding the value on the right of the
+= (i.e., weight ) to the variable on the left (i.e., total ). Is that clearer now?

Susan: OK, I think I got it now, I guess if it were more like an equation, you
would have to subtract total from the other side when you moved it. Why is it that
the math recollection that I have instead of helping me just confuses me?

Steve: Because, unfortunately, the = is the same symbol used to mean “is equal
to” in mathematics. The = in C++ means something completely different: “set the
thing on the left to the value on the right”.

Running this program normally isn’t likely to give you much


information, so you might want to run it under control of the
debugger.
CHAPTER 5 Functional Literacy

C++ was intended to be useful in writing large programs. Such


programs are usually composed of many implementation files, as I
mentioned in Chapter 3.
In such a case, we must have some way of creating an executable
program (sometimes abbreviated to just an executable) from a number
of implementation files. We also need some way for code in one
module to refer to code in another one. Similarly, we have to be able
to specify where execution of our program should start; this is taken
care of by the C++ rule that execution always starts at the block called
main .
As we’ve already seen, the computer can’t execute source code.
Therefore, any implementation files we write have to be translated
into object code; the result of such translation is an object code
module. One other kind of module we’re interested in is the library
module, which contains the object code from several implementation
files.
The idea of various types of modules led to the following
discussion with Susan:
Susan: So an object file is like some kind of interface between your code and the
binary numbers of the machine? I am confused.

Steve: Each implementation file is translated by the compiler into one object file.
Then these object files will be combined along with some previously prepared library
files to make an executable program.

Susan: iostream is a library? So are these already written programs that you can
refer to like a real library?

Steve: Yes. Actually, iostream is now part of the C++ standard library; that
happened a few years ago when they standardized the library.

Susan: The libraries contain code segments that are generalized, and the other
modules contain code segments that are program specific?

Steve: Right. One point that should be emphasized, though, is that a library
contains object code, not source code.

Susan: Where is the library? I am serious.

Steve: You can have more than one library, including ones written for more
specialized purposes by different companies. The library files for the compiler on the
CD in the back of the book are kept in a subdirectory under the directory where the
compiler is installed.

Susan: So what is an "implementation file"? Is it a set of code written by the


programmer?

Steve: It’s a file containing part of a program, in source code. Most significant
programs consist of a number of modules (files), rather than one big module (file),
partly because it’s easier to find and edit one logical segment of a program in a
separate file than a whole bunch of code all in one big file.
Susan: Okay then, so a module is just a big logical segment? How is it delineated
from the rest of the program? Is it named? How do you find it? Can you just tell by
looking where a module starts and ends?

Steve: A module, in C++ terminology, is another name for a file. Therefore, an


implementation module is an implementation file, which contains program statements.
It has a name, which is the name of the file.

Susan: So an implementation file is like a library, only as we have discussed it is


more specific than a library; it is for the program that you are working on?

Steve: Right.

Susan: Where are these modules and how do they get there?

Steve: Wherever you (or whoever wrote the code) put them. In the case of your
"weight-writing" program, the code you wrote is in an implementation file. That
module is compiled to make an object code module, which is then combined with
library module(s) that come with the compiler to make an executable file that can be
run.

Susan: So then the implementation file is a "miniprogram" within a program that


holds the source code to be later compiled?

Steve: It contains part of the source code of a program, which needs to be


compiled and combined with other previously compiled code before it can be used. I
think that’s the same as what you’re saying.

Actually, I’ve misused C++ terminology a little here in the interests of


comprehensibility. The term block isn’t quite correct as applied to
main() ; the correct term is function. Let’s take a look at the difference
between these two concepts, some other related definitions, and the
objectives of this chapter.
5.1. Definitions

An implementation file is a file that contains source code for a


program. Almost every part of every program starts out as an
implementation file.

Compilation of an implementation file produces a file called an


object code module (or object file), which contains object (machine)
code.

Several object code modules of a generally useful nature can be


combined to make a file called a library module, usually abbreviated
to library.

A function is a section of code somewhat like a block, but with


somewhat different characteristics. For one thing, you can’t substitute a
function for a statement; also, a function has a name, whereas blocks
are anonymous. This name enables one function to start execution of
another one.

A function call (or just "call" for short) causes execution to be


transferred temporarily from the current function to the one named in
the call.

A called function is a function that starts execution as a result of a


function call.

A calling function is a function that suspends execution as a result of a


function call.

A return statement is the mechanism used by a called function to return


to the calling function, which picks up just where it left off.

A console mode program is a program that looks like a DOS program


rather than a Windows program.
5.2. Objectives of This Chapter

By the end of this chapter, you should


1. Understand how and when to use functions to reduce the amount of
code you have to write.
2. Understand what software really is.
3. Understand how your source code is turned into an executable
program.
4. Understand how storage is assigned to different types of variables.
5.
Understand how functions can call one another.

5.3. Modules vs. Functions

Susan had a question about this new notion of a function, as related to


modules:

Susan: So a module has nothing to do with blocks and functions? If a function


only "calls" another function, then how do you call a module?

Steve: You can’t call a module. In fact, although a few language features apply to
modules rather than functions, modules don’t really have much significance in C++
other than as places to store related functions.

When we call a function, we usually have to provide it with input (for


example, some values to be averaged) and it usually produces output
which we use in further processing (for example, the average of the
input values). Some functions, though, have only one or the other. For
example, some functions are organized in pairs consisting of one
storage function and one retrieval function; the first stores data for
the second to retrieve later. In that case, the storage function may not
give us anything back when we call it, and the retrieving function may
not need any input from us.
To see how and why we might use a function, let’s take a look at a
program having some duplicated code (Figure 5.1).

FIGURE 5.1. A sample program


with duplicated code (code\nofunc.cpp)

#include <iostream> using


namespace std;

int main()
{
short FirstWeight; short
SecondWeight; short
FirstAge;
short SecondAge; short
AverageWeight; short
AverageAge;

cout << “Please type in the first weight: “; cin >>


FirstWeight;

cout << “Please type in the second weight: “; cin >>


SecondWeight;
AverageWeight = (FirstWeight + SecondWeight) / 2; cout << “Please
type in the first age: “;
cin >> FirstAge;

cout << “Please type in the second age: “; cin >>


SecondAge;

AverageAge = (FirstAge + SecondAge) / 2;

cout << “The average weight was: “ << AverageWeight << endl; cout << “The
average age was: “ << AverageAge << endl;
return 0;
}

I’d like you to look particularly at this line:

AverageWeight = (FirstWeight + SecondWeight) / 2;

and this one:

AverageAge = (FirstAge + SecondAge) / 2;

These two lines are awfully similar; the only difference between them
is that one of them averages two weights and the other averages two
ages. While this particular example doesn’t take too much code to
duplicate, it may not be difficult for you to imagine the inefficiency
and nuisance of having to copy and edit many lines of code every time
we want to do exactly the same thing with different data. Instead of
copying the code and editing it to change the name of the variables, we
can write a function that averages whatever data we give it.
Figure 5.2 is a picture of a function call. The calling function (1) is
main ; the function call is at position (2). The called function is Average
(3), and the return is at position (4); the returned value is stored in the
variable AvgAge, as indicated by the assignment operator = in the
statement
AvgAge = Average(FirstAge,SecondAge);

and the calling function, main , resumes execution at line (5).


FIGURE 5.2. A function call

By the way, it’s important to distinguish between returning a value


from a function, which is optional, and returning control from a called
function to its calling function, which always happens at the end of the
called function (unless the program has terminated due to an error in
the called function).1
While we’re on the subject of the calling function, you may be
wondering why we started the example at the beginning of main .
That’s because every C++ program starts executing at that point. When
the main function calls another function, such as Average , then
main is suspended until Average is finished. When Average finishes,
main resumes where it left off.
This isn’t limited to one "level" of calls. The same thing can
happen if Average (for example) calls another function, let’s say Funcx ;
Average will wait until Funcx returns before continuing. Then when
Average finishes, it will return to main , which will take up where it left
off.
This idea of calling and returning from functions led to the
following discussion with Susan:

Susan: So if you wanted to be really mean you could get into someone’s work in
progress and stick a return somewhere in the middle of it and it would end the
program right there? Now that I am thinking about it, I am sure you could do a whole
lot worse than that. Of course, I would never do such a thing, but what I am saying is
that whatever you are doing when the program gets to the return statement, then it
is the end? Next stop, C:\?

Steve: Yes and no. If you’re in main , then a return statement means the program
is finished and if it is a console mode program like the ones we are writing here,
you will indeed see the command line prompt come up on your screen. If you’re in a
function other than main , it means "return to the function that called this function".
In the case of a function that returns a value, the expression in the return statement
tells the compiler what value to use in place of the function call. For example, the
statement AvgAge = Average(i,j); sets AvgAge to the result in the return
statement of the function Average . As you can see by looking at that function, the
returned value is the average of the two input values, so that is the value that
AvgAge is set to by this statement.
1. If you don’t provide a return statement in a function that you’re calling, then
the called function will just return to the calling function when it gets to its
closing } . However, this is not legal for a function that is defined to return a
value. This of course leads to the question of why we’d call a function that
doesn’t return a value. One possibility is that the function exists only to produce
output on the screen, rather than to return any results. The actions that a
function performs other than returning a value are called side effects.
Susan: OK, but what about the return 0; at the end of the main program? Why
should it be 0?

Steve: The return statement in main can specify a different value if you wish.
However, the custom is to return 0 from main to mean "everything went well" and
some value greater than 0 to mean "there’s a problem". This isn’t entirely arbitrary,
because a batch file can test that return value and use it to alter the flow of the
execution of commands in the batch file.

Susan: OK, let’s see if I have this right: The return statement has to match the
main statement. This is so confusing. Look, when you say "The value that is
returned, 0, is an acceptable value of the type we declared in the line int main () "
— since I see no 0 anywhere around int main () — you are referring to the int .
An int can have a value of 0, right?

Steve: Right, the 0 has to match the int . That’s because a function can have a
return type, just like the type of a variable. In this case, int is the type of the main
function and the value is filled in by the return statement.

Susan: OK, then all this is saying is that the value that is produced is the same
type as that declared at the beginning of a program. Since we declared the type of
main as an int , if the value produced were a letter or a picture of a cow, then you
would get an error message?

Steve: Well, actually a letter (i.e., a char ) would be acceptable as an int , due to
rules left over from C. Otherwise, you’re exactly correct.

Susan: Hey, where is the 0 coming from to be returned?

Steve: It’s specified as a literal value in the return statement; you could put any
legal int value in there instead, if you wanted to.
Susan: So the return value doesn’t have to be a 0?

Steve: Right.

Susan: So 0 could be another int value, but it can’t be a variable? Even I don’t
know what I am talking about now!

Steve: I think I’ve confused you unnecessarily. You can certainly return a value
that is specified as a variable, such as return i; . What I meant was that the 0 we’re
returning in this case is a constant, not a variable.

Susan: The picture helps with the calling confusion. But I don’t understand why
main is the calling function if the calling function suspends execution. How can you
initiate a function if it starts out suspended? But I am serious.

Steve: The main function starts execution as the first function in your program.
Therefore, it isn’t suspended unless and until it calls another function.

An Example of Using a Function

I think it’s time for a more detailed example of how we would use a
function. Suppose we want to average several sets of two numbers
each and we don’t want to write the averaging code more than once.
The Average function just illustrated provides this service; its input is
the two numbers we want to average and its output is the average.
Figure 5.3 shows the code for the function Average without all the lines
and arrows:

FIGURE 5.3. A function to average two values

short Average(short First, short Second)


{
short Result;
Result = (First + Second) / 2;

return Result;
}

You can try out this function in a running program in the usual way. The
name of the program is func1.cpp and you can see it in Figure 5.5 on page
239.2 As had become routine, I couldn’t sneak this idea (of writing a
function) past Susan without a discussion.

Susan: Where you say "and we don’t want to write the averaging code more than
once", are you just saying if you didn’t do the Average function thing then you
would have to write this program twice? I mean for example would you have to
write a program separately for weights and then another one from the beginning for
ages?

Steve: We wouldn’t have to write an entirely separate program; however, we


would have to write the averaging code twice. One of the main purposes for writing
a function is so that we don’t have to repeat code.

To analyze this piece of code, let’s start at the beginning. Every


function starts with a function declaration, which tells the compiler
some vital statistics of the function. The function declaration consists
of three parts:
1. A return type;

2. If you run this program under the debugger and look at the variables at the
beginning of the program, please don’t be confused by the seemingly random
values that all of the variables start out with. These are just the garbage values
that happen to be lying around in memory where those variables reside; as
we’ve already seen, variables that haven’t yet been assigned a value are called
uninitialized variables. The variables in this program are all initialized before
they are used, but you can look at them in the debugger before the initializing
statements have been executed.
2. The function’s name;
3. An argument list.

In the case of our Averagefunction, the function declaration is short


Average(short First, short Second) . The return type is short , the name of the
function is Average , and the argument list is (short First, short Second) . Let’s
take these one at a time.

Returning Data to the Calling Function

The first part of the function declaration is the return type, in this
case short . This indicates that the function Average will provide a
value of type short to the calling function when the Average function
returns. Looking at the end of the function, you will see a statement that
says return Result; . Checking back to the variable definition part of the
function, we see that Result is indeed a short , so the value we’re returning
is of the correct type. If that were not the case, the compiler would tell
us that we had a discrepancy between the declared return type of our
function and the type actually returned in the code. This is another
example where the compiler helps us out with static type checking, as
mentioned in Chapter 3; if we say we want to return a short and then
return some other incompatible type such as a string , we’ve made a
mistake.3 It’s much easier for the compiler to catch this and warn us
than it is for us to locate the error ourselves when the program doesn’t
work correctly.
Susan wanted to know more about the return type. Here’s the
conversation that ensued:

3. What do I mean by an incompatible type? C++ allows us, for example, to


return a char variable where a short (or an int ) is expected; the compiler will
convert the c h a r into either of those types for us automatically. This is
convenient sometimes, but it reduces the chances of catching an error of this
kind and therefore is less safe than it could be. This practice, called implicit
conversion, is a legacy from C, which means that it can’t be changed for
practical reasons even though it is less than desirable theoretically.
Susan: This return type thing — it will have to be the same type of value as the
output is?

Steve: For our purposes here, the answer is yes. As I’ve already mentioned,
there are exceptions to this rule but we won’t need to worry about them.

Susan: Do you always use the word return when you write a function?

Steve: Yes, except that some functions have no return value. Such functions don’t
have to have an explicit return statement, but can just "fall off the end" of the
function, which acts like a return; statement. This is considered poor form, though;
it’s better to have a return statement.

The function name (in this case, Average ) follows the same rules as a
variable name. This is not a coincidence, because both function names
and variable names are identifiers, which is a fancy word for "user-
defined names". The rules for constructing an identifier are pretty
simple, as specified by the C++ Standard:4 “An identifier is an
arbitrarily long sequence of letters and digits. ... Upper- and lower-
case letters are different. All characters are significant.” (p. 14)

In other words:
1. Your identifiers can be as long as you wish. The compiler is
required to distinguish between two identifiers, no matter how
many identical characters they contain, as long as at least one
character is different in the two names.5

4. The official name of the C++ standard is ISO/IEC 14882:1998(E).


5. You don’t have to worry about wasting space in your program by using long
identifiers. They go away when your program is compiled and are replaced by
addresses of the variables or functions to which they refer.
2. They can be made of any combination of letters and digits, as long as
the first character is a letter.6
3. The upper and lower case versions of the same character aren’t
considered equal as far as names are concerned; that is, the variable
xyz is a different variable from Xyz , while XYZ is yet another
variable. Of course, you may get confused by having three variables
with those names, but the compiler considers them all distinct.

By the way, the reason that the first character of an identifier can’t be a
digit is to make it easier for the compiler to figure out what’s a number
and what isn’t. Another rule is that identifiers cannot conflict with
names defined by the C++ language (keywords); some examples of
keywords that we’ve already seen are if and short .
Finally, we have the argument list. In this case, it contains two
arguments, a short called First , which holds the first number that our
Average function uses to calculate its result; and a second argument
(also a short ) called Second , which of course is the other number
needed to calculate the average. In other cases, there might be several
entries in the argument list, each of which provides some information
to the called function. But what exactly is an argument?

Function Arguments

The question of what is an argument is more subtle than it may appear.


An argument is a value that is supplied by a function (the calling
function) that wishes to use the services of another function (the
called function). For example, the calling function might be our main
function, and the called function might be our Average function, while
the arguments are two short values to be averaged. Arguments

6. For historical reasons, the underscore character _ counts as a letter. However,


do not begin a variable or function name with an underscore, as such names
are "reserved" for use by compiler writers and other language implementers.
like the ones here are actually copies of values from the calling
function; that is, the compiler will initialize the variable named in the
argument list of the called function to the value supplied by the calling
function. This process of making a copy of the calling function’s
argument is referred to as call by value, and the resulting copy is
called a value argument.7 Figure 5.4 is an example of this argument
passing mechanism at work with only one argument.

FIGURE 5.4. Argument passing


with one argument (code\birthday.cpp)

#include <iostream> using


namespace std;

short Birthday(short age)


{
age ++;
return age;
}

int main()
{
short x;
short y;

x = 46;
y = Birthday(x);

cout << “Your age was: “ << x << endl;


cout << “Happy Birthday: your age is now “ << y << endl;

return 0;
}

7. This discussion might make you wonder whether there’s another type of
argument besides a value argument. There is, and we’ll find out about it in
Chapter 6.
In this program, main sets x to 46 and then calls Birthday with x as the
argument. When Birthday starts, a new variable called age is created
and set to 46, because that’s the value of x , the argument with which
main called Birthday . Birthday adds one to its variable age , and then
returns the new value of that variable to main . What number will be
printed for the variable x by the line cout << "Your age was: " << x << endl; ?
Answer: 46, because the variable age in Birthday was a copy of the
argument from main , not the actual variable x named in the call to
Birthday . On the other hand, the value of y in the main program will be
47, because that is the return value from Birthday .
As you might have guessed, the notion of copying the argument
when a function is called occasioned an intense conversation with
Susan.

Susan: This is tough. I don’t get it at all. Does this mean the value of the short
named x will then be copied to another location in the function named Birthday ?

Steve: Yes, the value in the short named x will be copied to another short
called age before the execution of the first line in the function Birthday . This
means that the original value of x in main won’t be affected by anything that
Birthday does.

Susan: Now for the really confusing part. I don’t understand where you say "An
argument like the one here ( short age ) is actually a copy of a value in the calling
function". Now, I have read this over and over and nothing helped. I thought I
understood it for a second or two and then I would lose it; finally I have decided that
there is very little in this section that I do understand. Help.

Steve: When you write a function, the normal behavior of the compiler is to insert
code at the beginning of the function to make a copy of the data that the calling
function supplies. This copy of the data is what the called function actually refers to,
not the original. Therefore, if you change the value of an argument, it doesn’t do
anything to the original data in the calling function.
If you (the programmer of the function) actually want to refer to the data in the
calling function and not a copy of it, you can specify this when you write the function.
There are cases in which this makes sense and we’ll see some of them in Chapter 6.

Susan: I don’t understand why it is a copy of the calling function and not the
called function.

Steve: It’s not a copy of the calling function; it’s a copy of the value from the
calling function, for the use of the called function. In the sample program, main sets
x to 46 and then calls Birthday with x as the argument. When Birthday starts, a
new variable called age is created, and set to 46, because that’s the value of x ,
the argument with which main called Birthday . Birthday adds 1 to its variable
age , and returns the new value of age to main . What will be printed by the line
" cout << x << endl; "? Answer: 46, because the variable age in Birthday was a
copy of the value of the argument from main , not the actual variable ( x ) specified
in the call to Birthday . Does this explanation clarify this point?

Susan: I still don’t understand the program. It doesn’t make any sense. If x =
46 , then it will always be 46 no matter what is going on in the called function. So
why call a function? You know what, I think my biggest problem is that I don’t
understand the argument list. I think that is where I am hung up on this.

Steve: The arguments to the function call ( x , in the case of the function call
Birthday(x) ) are transferred to the value of the argument in the function itself (the
short variable age , in the case of the function Birthday(short age) ).

Susan: In that case, why bother putting an x there, why just not put 46? Would it
not do the same thing in the called function, since it is already set to 46?

Steve: Yes, but what if you wanted to call this function from another place where
the value was 97 rather than 46? The reason
that the argument is a variable is so you can use whatever value you want.

Susan: If we called Birthday with the value 46, then the 46 would be 46++ ,
right?

Steve: 46++ is a syntax error, because you can’t change the value of a literal
constant. Only a variable can be modified.

Susan: So if you want to state a literal value, do you always have to declare a
variable first and then set a variable to that literal value?

Steve: No, sometimes you can use a literal value directly without storing it in a
variable. For example,

cout << 15;

or

cout << "Hello, my name is Steve Heller";

What I was trying to say is that you can’t change a literal value. Thus, 15++; is not
legal because a literal value such as 15 represents itself, that is, the value 15. If you
could write 15++; , what should it do? Change all occurrences of 15 to 16 in the
program?

Susan: Okay. Now, how does age get initialized to the value of x ?

Steve: The compiler does that when it starts the function, because you have
declared in the function declaration that the argument to the function is called age ,
and you have called the function with an argument called x . So the compiler copies
the value from x into age right before it starts executing the function.

Susan: Oh, I see. That makes sense, because maybe later on you want to call the
same function again and change only a little part of
it, but you still need the original to be the same, so you can just change the copy
instead of the original. Is that the purpose?

Steve: Not quite. The reason that the called function gets a copy of data, rather
than the original, is so that the person writing the calling function knows that the
original variable hasn’t been changed by calling a function. This makes it easier to
create programs by combining your own functions with functions that have already
been written (such as in the library). Is that what you meant?

Susan: So is everything copied? I am getting confused again, are you going to talk
a little more about copying in the book? Have I just not gotten there? Anyway, if you
haven’t mentioned this more, I think you should, it explains hidden stuff.

Steve: Don’t worry, we’re going to go into much more detail about how this
works. In fact, it’s a major topic in the rest of the book.

How the Average Function Works

The same analysis that we have just applied to the Birthday function
applies also to the Average function that we started out with; the
arguments First and Second are copies of the values specified in the
call to Average .
Now that we have accounted for the Average function’s input and
output, we can examine how it does its work. First, we have a
variable definition for Result , which will hold the value we will return
to the calling function; namely, the average of the two input values.
Then we calculate that average, with the statement

Result = (First + Second) / 2;

Once the average has been calculated, we’re ready to return it to the
calling program which is accomplished by the line return Result; .
Finally, we reach the closing } , which tells the compiler that the
function is done.
5.4. Using a Function

Now that we have seen how to write the Average function, let’s see how
to use it to solve our original problem. The program in Figure
5.5 uses our Average function twice, once to average two weights
and once to average two ages.

FIGURE 5.5. Using the Average function (code\func1.cpp)

#include <iostream> using


namespace std;

short Average(short First, short Second)


{
short Result;

Result = (First + Second) / 2;

return Result;
}

int main()
{
short FirstWeight; short
SecondWeight; short
FirstAge;
short SecondAge; short
AverageWeight; short
AverageAge;

cout << “Please type in the first weight: “; cin >>


FirstWeight;

cout << “Please type in the second weight: “; cin >>


SecondWeight;

AverageWeight = Average(FirstWeight, SecondWeight); cout <<

“Please type in the first age: “;


cin >> FirstAge;

cout << “Please type in the second age: “; cin >>


SecondAge;

AverageAge = Average(FirstAge, SecondAge);

cout << “The average weight was: “ << AverageWeight << endl; cout << “The
average age was: “ << AverageAge << endl;

return 0;
}

As always, calling a function requires specifying its name and its


argument(s) and doing something with the return value, if any. In this
case, we call Average with the arguments FirstWeight and SecondWeight ,
and store the result in AverageWeight . This is accomplished via the line
AverageWeight = Average(FirstWeight, SecondWeight); . Later, we call Average with
the arguments FirstAge and SecondAge , and store the result in
AverageAge . W e do this via the line AverageAge = Average(FirstAge,
SecondAge); .
The value of writing a function to average two numbers wasn’t
obvious to Susan at first. After some discussion, however, she agreed
that it was valuable. Here’s the conversation that convinced her:

Susan: In general, I just don’t understand why you even need to call the Average
function in the first place; it looks like extra steps to me. It seems to me that all you
need are your two input values, which end up just giving you the results right there
for weight and age. I think that this is what bothers me the most. For example, when
you get done with the set of weights, you should just have your results right then and
there instead of calling the function of Average .

Steve: But what is the result you want? You want the average of the weights.
Where is that calculated?
Susan: After you are done with that, then you already have written a set of ages
so you can just use the result of that. It just seems like you are going in circles
unnecessarily with this program. That is why I don’t understand it.

Steve: Again, just because you have a set of ages doesn’t mean that you have the
average age; some code somewhere has to calculate that average.

Susan still had a lot of trouble with visualizing the way this function
worked. However, running it in the debugger got her moving again,
resulting in the following discussion:

Susan: Why does everything start out initialized to 0 except


Result , which appears to hold an address in memory?

Steve: The values of uninitialized variables are not reliable. In this case, I’m
getting a similar value of Result to the one you’re getting; however, you cannot count
on this. There’s also no reason to think that the contents of Result are a memory
address; they’re just garbage until the variable is initialized.

Susan: Steve, I don’t understand this; first you tell me that those numbers are
garbage but represent addresses in memory and now you tell me that they are just
garbage, but that they are not reliable. I don’t understand, if they are uninitialized,
how they ever could be reliable. This implies that at some time you could get an
expected value even if they are uninitialized. They should always be garbage. So,
when do those numbers represent memory addresses and when not?

Steve: Apparently I’ve confused you unnecessarily again. Here are the facts:
1. A variable always represents an address in memory.
2. However, the contents of an uninitialized variable are garbage.
3. Since they are garbage, they represent nothing.
4.Since they are garbage, they can have any value, which may or may not appear to
have meaning. Regardless of appearances, the value of an uninitialized variable is
meaningless.

Then our discussion returned to the topic of how the main program
works:

Susan: Oh, OK, so AverageWeight = Average(FirstWeight, SecondWeight); is


the part that starts the Average function running?

Steve: Right.

Susan: Then after averaging the weights, why does Result go to 0? It looks to
me that Result has no value at these points and I don’t understand why.

Steve: Because you’re looking at the next call to Average , where its variable
Result is uninitialized again. By default, variables are uninitialized whenever they are
created, which occurs each time the function where they "live" is entered. The "old"
Result from the first call to Average “died” when the first call to Average was
finished, and the new Result that is created on the second call is uninitialized until
we set it to some known value.

The next topic we discussed was how to create a new program and
get it to run.

Susan: Now when you start out a new program; are all the new implementation
files named with a .cpp extension?

Steve: Yes.
Susan: So this code in Average is where the real averaging takes place, right? Is
this the "Average command"? I thought Average meant to average, so what is the
deal?

Steve: The deal is that something has to do the averaging; rather than writing the
same code every time we need to average another set of two numbers, we put that
code in one place (the Average function) and call it whenever we need its
assistance.

Susan: OK, then this brings up another one of my questions. How come you write
the Average function before the main function?

Steve: So that the main function knows how to call the Average function.
There’s another way to allow a function to call another one that doesn’t come before
it in the file, but I thought it was easier to show it this way at first.

Susan: If the main function is going to be executed first, then how come the
Average function is written first? Does the compiler always look for main first?

Steve: Yes.

Susan: Yeah, but could the Average function then just be written after main
instead of before it? Just to be there when it is needed, instead of before it is
needed? Am I right that you still would not have to write it twice; it would still be
there for the next time it is needed, right?

Steve: The Average function can be anywhere, even in a different module, but
at the point that you try to use it, the compiler has to know about it. To be precise,
you have to specify its name, return type, and what arguments it takes. Otherwise,
the program won’t compile. On the other hand, the compiled version of the Average
function doesn’t have to be available until the executable is
created;8 if it’s not available at that point, you’ll get an error saying that you have
referenced an undefined function.
Susan: So does that mean you could put the Average function anywhere you
want? Then could it or any "subfunction" be put anywhere you want because the
main function would always be executed first? Or could you mess up the code if
you put it in a really ridiculous place like inside an output or input statement. . . or
could the compiler be able to ignore something like that and go about business as
usual? I guess because of the brackets it should ignore such a thing but I am not
sure. See, these are the things that we novices are obliged to ponder.

Steve: You can’t start a function definition in the middle of another function.
That’s called a nested function and it’s not allowed in C++. The rule can be stated
approximately as "You can start a function definition anywhere except in the middle
of another definition."

Susan: So then the "cue" for the Average function to begin is the word Average
(weight) or (age), when the compiler sees that word it just begins that separate
function to start its little calculation.

Steve: That’s right, except that it needs two arguments, not just one.

Susan: And that function since it was named Average causes the averaging
function to work. Is that how it goes?

Steve: If I understand your question, it’s not the name that makes the Average
function do the averaging, it’s the code that adds up the two values and divides by 2.
We could replace all the references to Average with the word Glorp and the
compiler wouldn’t care; however, a future programmer trying to read the program
probably wouldn’t be amused by that name.

Susan: Oh, so there is nothing magical about the word Average , I thought it
might trigger a function of averaging. Well, that sounds
8. This is done by the linker, which we’ll get to later in this chapter.
reasonable; it’s more for us humans than the computer. And then that brings up
another question, along the same line of thinking. After the Average function has
done its thing, how does the program go from return Result; to the next output
statement that asks for the ages? What triggers that change? I am not seeing this in
the code.

Steve: The return keyword tells the compiler to hand back control to the function
that called the one where the return is, as indicated in Figure 5.2.

This discussion didn’t slake her thirst for knowledge about how to
write a program. Here is how we continued:

Susan: Can I mix shorts with strings using the headers that are already stated
in the test program?

Steve: Mixing shorts with strings is a dubious proposition, sort of like adding
apples and oranges together; could you be more specific about what you’re trying to
do?

Susan: What if you wanted to add a numerical value to your program such as
test : You have to put in a short , right? So if you added a short , what else would
you have to do to make it work? Or would you have to start over with another main
function after the first part and then declare new variables? I tried that too, and the
compiler did not like that either. Very inflexible it is. I will tell you after one more try
what I am doing. This will keep you in suspense.

Steve: It depends on what you’re trying to do with the short . It’s usually best to
have a specific problem in mind that you’re trying to solve by writing a program.
Then you may see how to use these facilities ( shorts , strings , Vecs, etc.) to solve
your problem; if not, you can ask me how they would fit into the solution.

As for your second suggestion, you’re not allowed to have more than one main
function in a program, because the compiler
wouldn’t know which one to use as the starting address for the program.

Susan: I am not really trying to solve anything, I just want to have the user type in
more info and that info is a number — wait!! That is it, in that case it will be like an
ASCII character and it doesn’t need a short , right? That’s right. I can still use a
string. We are not crunching any numbers with this.

Steve: As long as you don’t try to do any calculations, you can read the data into
a string, even data that looks like a number; of course, that data entry method is
pretty loose, since if the user types "abc" as an age, the program will accept it.

Susan: Can you define a string without a word but with just a wildcard type of
variable like when we use i in shorts ? In other words, does it matter what we call
a variable?

Steve: A variable is always a “wildcard”, whether it’s a short or a string . For


example, a short variable always has a name, such as i , or index (or whatever
makes sense to you), and a value, which is a number such as 14 or 99. A string
variable also has a name, such as FirstName , or street (or whatever makes sense
to you), and a value, which consists of characters rather than a number, such as
"Susan" or "Wesley".

As you can see, using a function isn’t very difficult. We have to


provide it with the material to work on (its arguments) and can store
its return value in a variable for further processing (or use it directly,
if we wish). But there’s a little more here than meets the eye. How
does the variable FirstWeight , for example, get transformed into the
variable First that we used when writing the function?
This explanation requires us to look at some more underlying
software technology. To be precise, we’re going to spend some time
examining the infrastructure that makes computers usable for
programmers. First, though, we have to consider a more general
notion, that of a "virtual computer".
5.5. Software Is a Virtual Computer

Unlike many words in the vocabulary of computing, virtual has more


or less retained its standard English definition: "That is so in essence
or effect, although not formally or actually; admitting of being called
by the name so far as the effect or result is concerned."9 In other
words, a virtual computer would be something that acts just like a
computer, but really isn’t one. Who would want such a thing?
Apparently everyone, since virtual computer is just another name
for what we have been calling software. This may seem a rash
statement, but it really isn’t. One of the most important mathematical
discoveries (inventions?) of the twentieth century was Alan Turing’s
demonstration in 1936 that it was possible to create a fairly simple
computing device (called a Turing machine for some obscure reason)
that could imitate any other computing device. This machine works in
the following way: You provide it with a description of the other
computer you want it to imitate and it follows those directions.
Suppose we want a computer that calculates only trigonometric
functions. We could theoretically write a set of instructions as to how
such a computer would behave, feed it into a Turing machine and have
the Turing machine imitate the behavior of this theoretical
"trigonometric computer".
Susan was quite impressed by this achievement:

Susan: Was this Turing guy some kind of genius? Steve:


Yes.

This is undoubtedly interesting, but you may be wondering what it has


to do with programming. Well, what do we do when we write a
program? In the case of our pumpkin weighing program, we’re
describing the actions that would be taken by a hypothetical

9. Oxford English Dictionary, first current definition (4).


"pumpkin weighing computer". When we run the program, the real
computer simulates these actions. In other words, we have created a
virtual pumpkin weighing computer.
The same analysis applies to any program. A program can most
fundamentally be defined as instructions to a "universal computer",
telling it how to simulate the specialized computer you actually want
to use. When the universal computer executes these instructions, it
behaves exactly as the hypothetical specialized computer would.
Of course, actual computers aren’t really universal; they have
limits in the amount of memory or disk space they contain and the
speed of their execution. However, for problems that can be solved
within those limits, they are truly universal computing devices that can
be tailored to a particular problem by programming.

Object Files

Now let’s take a look at one of these areas of software technology.


We’ve already seen that the function of a compiler is to convert our
human-readable C++ program into machine instructions that can be
executed by the computer. However, the compiler doesn’t actually
produce an executable program that can stand by itself; instead, it
translates each implementation file into a machine language file called
an object code module (or object file). This file contains the
instructions that correspond to the source code statements you’ve
written, but not the "infrastructure" needed to allow them to be
executed. We’ll see what that infrastructure does for us shortly.
The creation of an object file rather than a complete executable
program isn’t a universal characteristic of compilers, dictated by
nature. In fact, one of the most popular compilers in the early history
of the PC, the Turbo Pascal™ compiler, did create an executable file
directly from source code. This appears much simpler, so we have to
ask why this approach has been abandoned with C++. As I’ve
mentioned before, C++ was intended to be useful in writing large
programs. Such programs can consist of hundreds or even thousands of
modules (sections of source code) each containing hundreds or
thousands of lines of code. Once all the modules are compiled, the
object files resulting from the compilation are run through a program
called the linker. The linker combines information from all of the
object files, along with some previously prepared files called library
modules (or libraries), to produce an executable program; this is
called linking the program. One reason for this two-step approach is
that we wouldn’t want to have to recompile every module in a large
program every time we made a change in one section; therefore, only
those modules that have been affected are recompiled.10 When all of
the affected modules have been recompiled, the program is relinked to
produce an updated executable.
To make such a system work, it’s necessary to set up conventions
as to which parts will be executed first, where data needed by more
than one module will be stored, and so on. Also, a lot of operations
aren’t supplied as part of the language itself but are very handy in
writing programs, such as the I/O functions that we’ve already seen.
These make up the infrastructure needed to execute C++ programs.
Figure 5.6 is a picture of the process of turning source code into an
executable file.
Susan found this explanation and diagram to be helpful.

Susan: This is beginning to come into focus. So you write your source code, it has
a middle man called an object file and that just passes the buck over to a linker,
which gathers info the program may need from the libraries, and then the program is
ready to be read by the machine. Close?

Steve: Yes, very close indeed.

10. The alert reader may wonder why I referred to modules that have been
“affected" rather than “changed". The reason is that even if we don’t change a
particular module, we must recompile it if a header file that it uses is changed.
This is a serious maintenance problem in large systems but can be handled by
special programming methods which are beyond the scope of this book.
FIGURE 5.6. Making an executable

Operating System Facilities

As is often the case in programming, this infrastructure is divided into


several layers, the higher ones depending on the lower ones for more
fundamental services. The lowest level of the infrastructure is
supplied by the operating system, a program that deals with the actual
hardware of your computer. By far the most common operating system
for Intel® CPUs, as this is written, is some variant of Microsoft
Windows, with Linux® in a distant second place. All of these provide
some of the same facilities; for example, you are accustomed to
dealing with files and directories when using application programs
such as word processors and spreadsheets. However, the disk drive in
your computer doesn’t know anything about files or directories. As we
have seen in Chapter 2, all it can do is store and retrieve fixed-size
pieces of data called sectors, given an absolute address on the disk
described by a platter, track number, and
sector number. Files are a creation of the operating system, which
keeps track of which parts of which files are stored where on the
disk.11
A modern operating system provides many more facilities than just
keeping track of file storage. For example, it arranges for code and
data to be stored in separate areas of RAM with different access
rights, so that code can’t be accidentally overwritten by a runaway
program; that is, one that writes outside the memory areas it is
supposed to use. This is a valuable service, as errors of this kind are
quite difficult to find and can cause havoc when they occur.
That’s the good news. The bad news is that MS-DOS, which is
still the basis of all versions of Windows before Windows NT® and
Windows 2000®, was created before the widespread availability of
reasonably priced CPUs with memory protection facilities. For this
reason, when using those earlier operating systems, it’s entirely
possible for a runaway program to destroy anything else in memory.
Theoretically, we should all be running "real" operating systems by
the time you read this; so far, though, the rumors of the demise of MS-
DOS have been greatly exaggerated.
This notion intrigued Susan. Here’s how that conversation went:

Susan: What is a runaway program?

Steve: One that is writing in areas that it shouldn’t, thus destroying data or programs
outside its assigned memory areas.

Susan: How would an operating system actually separate code from data
areas? Would it be a physical thing?

11. You might say that files are "virtual"; that is, they’re a figment of the operating
system’s imagination. Nonetheless, they are quite useful. This reminds me of
the story about the man who went to a doctor, complaining that his brother had
thought he was a hen for many years. The doctor asked why the family hadn’t
tried to help the brother before and the man replied, "We needed the eggs".
Steve: What makes this possible are certain hardware mechanisms built into all
modern CPUs, so that certain areas of memory can be assigned to specific
programs for use in predefined ways. When these mechanisms are used, a program
can’t write (or read, in some cases) outside its assigned area. This prevents one
program from interfering with another.

Library Modules

The next level of the infrastructure is supplied by the aforementioned


library modules, which contain standardized segments of code that
can perform I/O, mathematical functions, and other commonly used
operations. So far, we have used the iostream and string parts of the
C++ standard library, which provided the keyboard input and screen
output in our example programs.12 We’ve also relied implicitly on the
"startup" library, which sets up the conditions necessary for any C++
program to execute properly.
Susan wanted to know how we were using the startup library:

Susan: I don’t know what you are talking about when you say that we have also
used a startup library. When did we do that? At startup? Well, is it something that
you are actually using without knowing you are using it?

Steve: Yes. It initializes the I/O system and generally makes the environment safe
for C++ programs; they’re more fragile than assembly language programs and have
to have everything set up for them before they can venture out.

12. We’ve also used the vector part of the library indirectly, through my Vec
type.
To understand the necessity for the startup library, we have to take a
look at the way variables are assigned to memory locations. So far,
we have just assumed that a particular variable had a certain address,
but how is this address determined in the real world?
There are several possible ways for this to occur; the particular
one employed for any given variable is determined by the variable’s
storage class. The simplest of these is the static storage class;
variables of this class are assigned memory addresses in the
executable program when the program is linked. The most common
way to put a variable in the static storage class is to define it outside
any function.13 Such a variable will be initialized only once before
main starts executing. We can specify the initial value if we wish; if we
don’t specify it, a default value (0 for numeric variables) will be
assigned.14 An example of such a definition would be writing the line
short x = 3; outside any function; this would cause x to be set to 3 before
main starts executing. We can change the value of such a variable
whenever we wish, just as with any other variable. The distinction I’m
making here is that a static variable is always initialized before main
begins executing. As you will see, this seemingly obvious
characteristic of static variables is not shared with variables of other
storage classes.
This idea of assigning storage at link time led to the following
discussion with Susan:

13. Another way to make a variable static is to state explicitly that the variable is
static. However, this only works for variables defined inside functions. The
keyword static is not used to specify that variables defined outside any function
variable are statically allocated; since globals are always statically allocated, the
keyword static means something different when applied to a global variable or
function. Even though we won’t be using static for global variables or functions,
it’s possible that you will run into it in other programs so it might be useful for
you to have some idea of its meaning in those situations. An approximate
translation of st at ic for global functions or variables is that the function or
variable is available for use only in the same file where it is defined, following
the point of its definition.
14. You can count on this because it’s part of the language definition, although it’s
nicer for the next programmer if you specify what you mean explicitly.
Susan: If you declare a variable with only one value then it isn’t a variable
anymore, is it?

Steve: A static variable can have its value changed, so it’s a genuine variable. I
was saying that it’s possible to specify what its initial value should be before main
starts executing.

Susan: Are you trying to say where in memory this variable is to be stored? Isn’t
the compiler supposed to worry about that?

Steve: I’m not specifying a location, but rather an attribute of the variable. A
static variable behaves differently from the "normal" variables that we’ve seen up
till now. One difference is that a static variable is always initialized to a known
value before main starts executing.

Why All Variables Aren’t Automatically Initialized


The notion of storage classes is essential to the solution of another
mystery. You may recall that I mentioned some time ago that C++
doesn’t provide automatic initialization of all variables because that
facility would make a program bigger and slower. I’ll admit that the
truth of this isn’t intuitively obvious to the casual observer; after all, a
variable (or more exactly, the storage location it occupies) has to have
some value, so why not something reasonable? As we have just seen,
this is done for static variables. However, there is another storage
class for which such a facility isn’t quite as easy or inexpensive to
implement;15 that’s the auto (short for "automatic") storage class,
which is the default class used for variables defined in functions. An
auto variable does not actually exist until the function in which it is
defined starts execution and even then has no known value until we
specifically assign a value to it.16

15. "Inexpensive", in programming parlance, means "not using up much time and/or
space".
This notion led to a fair amount of discussion with Susan.

Susan: Then so far I know about two types of variables, static and
auto , is this correct?

Steve: Right, those are the two "storage classes" that we’ve talked about so far.

Susan: The auto variables are made up of garbage and the static
variables are made up of something understandable, right?

Steve: The auto variables are uninitialized by default, and the


static variables are initialized to 0 (if they’re numeric, at least).

Susan: When you say that the auto class is used for functions by default does
this mean you don’t use static ones ever?

Steve: Default means "what you get if you don’t specify otherwise". For
example, these days, if you buy almost any type of car and don’t specify that you
want a manual transmission, you will get an automatic, so automatic transmissions
are the default.

Susan: So, since we have used auto variables up to this point, then I am confused
when we initialize them to a value. If we do, would that not make them static ?

Steve: This is a difficult topic, so it’s not surprising that you’re having trouble. I
didn’t realize quite how difficult it was until I tried to answer your question and ended
up with the essay found under the heading “Automatic vs. Static Allocation” on page
268.

16. I’m oversimplifying a bit here. Variables can actually be declared inside any
block, not just any function. An auto variable that is declared inside a block is
born when the block is entered and lives until that block is finished executing. A
st at ic variable that is declared inside a block is initialized when that block is
entered for the first time. It retains its value from that point on unless it is
explicitly changed, as with any other statically allocated variable.
Susan: How do you know what the address will be to assign to a variable? OK, I
mean this makes it sound like you, the programmer, know exactly which address in
memory will be used for the variable and you assign it to that location.

Steve: You don’t have to know the address; however, you do have to know that
the address is fixed at link time. That’s what makes it possible to initialize a static
variable before main starts (if outside all functions), or just once the first time its
definition is encountered (if inside a function). On the other hand, an auto variable
can’t be initialized until the beginning of each execution of the function in which it is
defined, because its address can change between executions of the function.

Susan: I am having a hard time trying to figure out what you mean by using static
variables outside functions. I have meant to ask you this, is main really a function
even if you don’t have anything to call? For example, in the first pumpkin weighing
program, we didn’t have to call another function but I have been wondering if main
is really a function that just has nothing to call? So in that case, the variables used in
main would be auto ?

Steve: You are correct that main is a function. To be precise, it’s


the first function executed in any C++ program.17 If it calls other functions, that’s
fine, but it doesn’t have to. As with any other function, the variables used in main
are auto by default; in the case of the pumpkin weighing program, since we didn’t
make any of them static , they’re all auto in fact.

17. As I have noted before, this is actually an oversimplification. It is possible to


write programs where some of our code is executed before the beginning of
main. I’ll explain how this can be done (and why) in the section entitled
“Executing Code before the Beginning of main” on page 744, but we won’t
actually make use of such a facility in this book.
So far, all of our variables have been auto , and in most programs the
vast majority of all variables are of this class.18 Why should we use
these variables when static ones have an initialization feature built in?
The first clue to this mystery is in the name auto . When we define a
variable of the auto class, its address is assigned automatically when
its function is entered; the address is valid for the duration of that
function.19 Since the address of the variable isn’t known until its
function is entered, it can’t be initialized until then, unlike the case
with static variables. Therefore, if auto variables were automatically
initialized, every function would have to start with some extra code to
initialize every auto variable, which would make the program both
slower and larger. Since Bjarne Stroustrup’s design goals required that
a C++ program should have the same run-time performance as a C
program and as little space overhead as possible, such a feature was
unacceptable. Luckily, forgetting to initialize an auto variable is
something that can be detected at compile time, so it’s possible for the
compiler to warn us if we make this error. In general, it’s a good idea
to tell the compiler to warn you about dubious practices. Although not
all of them may be real errors, some will be and this is by far the
fastest and best way to find them.20
Now we’ve seen why auto variables aren’t initialized by default:
Their addresses aren’t known until entering the function in which
they’re defined. But that doesn’t explain the advantage of assigning the
addresses then. Wouldn’t it be simpler (and faster) to assign them all
during the linking process, as is done with static variables?

18. You may have noticed that we haven’t defined any variables as auto. That’s
because any variables defined within a function and not marked as st at ic are
set to the default class, auto.
19. This is why variables defined outside a function are static rather than auto; if
they were auto, when would their addresses be assigned?
20. There are also commercial tools that help locate errors of this type, as well as
other errors, by analyzing the source code for inconsistencies.
Nested Functions

To understand why auto variables aren’t assigned addresses during the


linking process, we have to look at the way functions relate to one
another. In particular, it is very common for a statement in one
function to call another function; this is called nesting functions and
can continue to any number of levels.
Susan explained this in her inimitable way:

Susan: Nesting of functions—does that mean a whole bunch of functions calling


each other?

Steve: Not exactly. Usually functions don’t call one another.21 Nesting of
functions actually means one function calling another function, which in turn calls
another function, and so on.

Although functions can and often do call other functions, it is very


unlikely that every function in a large program will be in the midst of
execution at any given time. This means that reserving space in the
executable program for all of the variables in all of the functions will
make that executable considerably larger than it otherwise would be.
If we had only static variables, this wasteful situation would
indeed occur. The alternative, of course, is to use auto variables,
which as we have just noted are assigned storage at run time. But
where is that storage assigned, if not in the executable program?
While all static variables are assigned storage in the executable
program when it is linked, auto variables are instead stored in a data
structure called a stack; the name is intended to suggest the notion of
stacking clean plates on a spring-loaded holder such as you might see
in a cafeteria. The last plate deposited on the stack of plates will be
the first one to be removed when a customer needs a fresh plate. Back

21. There are cases in which functions call one another, e.g., one function calls a
second function, which calls the first function. However, we won’t see any
examples of this recursion in this book.
in the world of programming, a stack with one entry might look
something like Figure 5.7.

FIGURE 5.7. A stack with one entry

Name Value
TOP 1234

If we add (or push) another value on to the stack, say 999, the result
would look like Figure 5.8.

FIGURE 5.8. A stack with two entries

Name Value
TOP 999
2nd 1234

If we were to push one more item with the value 1666, the result
would look like Figure 5.9. Now, if we retrieve (or pop) a value,
we’ll get the one on top; namely 1666. Then the stack will look like it
did in Figure 5.8. The next value to be popped off the stack will be the
999, leaving us with the situation in Figure 5.7 again. If we continue
for one more round we’ll get the value 1234, leaving us with an empty
stack.

FIGURE 5.9. A stack with three entries

Name Value
TOP 1666
2nd 999
3rd 1234

The reason that stacks are used to store auto variables is that the way
items are pushed onto or popped off a stack exactly parallels what
happens when one function calls another.
Susan had a question about stacks:

Susan: How many things can you push on the stack?

Steve: That depends on what kind of compiler you have, what operating system
you are using and how much memory you have in your machine. With a 32-bit
compiler running on a modern operating system and a modern PC, you might be able
to store several million items on the stack before you run out of space.

Let’s look at this stack idea again, but this time from the point of view
of keeping track of where we are in one function when it calls another
one, as well as allocating storage for auto variables.22

Returning to the Calling Function

In Figure 5.5, there are two calls to the function Average : The first one
is used to average two weights and the other to average two ages. One
point I didn’t stress was exactly how the Average function "knew"
which call was which; that is, how did Average return to the right place
after each time it was called? In principle, the answer is fairly simple:
The calling function somehow notifies the called function of the
address of the next instruction that should be executed after the called
function is finished (the return address). There are several possible
ways to solve this problem. The simplest solution is to store the return
address at some standardized position in the code of the called
function; at the end of the called function, that address is used to get
back to the caller. While this used to be standard practice, it has a
number of drawbacks that have relegated it to the history books. A
major problem with this approach is that it requires
22. The actual memory locations used to hold the items in the stack are just like
any other locations in RAM; what makes them part of the stack is how they
are used. Of course, as always, one memory location can hold only one item at
a given time, so the locations used to hold entries on the stack cannot be
simultaneously used for something else like machine instructions.
changing data that are stored with the code of the called routine. As
we’ve already seen, when running a program on a modern CPU under a
modern operating system, code and data areas of memory are treated
differently and changing the contents of code areas at run time is not
allowed.
Luckily, there is another convenient place to store return
addresses: on the stack. This is such an important mechanism that all
modern CPUs have a dedicated register, usually called the stack
pointer, to make it easy and efficient to store and retrieve return
addresses and other data that are of interest only during the execution
of a particular function. In the case of the Intel CPUs, the stack
pointer’s name is esp .23 A machine instruction named call is designed to
push the return address on the stack and jump to the beginning of the
function being called.24 The call instruction isn’t very complex in its
operation, but before going into that explanation, you’ll need some
background information about how the CPU executes instructions.
How does the CPU "know" what instruction is the next to be
executed? By using another dedicated register that we haven’t
discussed before, the program counter, which holds the address of
the next instruction to be executed. Normally, this is the instruction
physically following the one currently being executed; however, when
we want to change the sequence of execution, as in an if statement or a
function call, the program counter is loaded with the address of the
instruction that logically follows the present one. Whatever instruction
is at the address specified in the program counter is by definition the
next instruction that will be executed; therefore, changing the address
in the program counter to the address of any instruction causes that
instruction to be the next one to be executed.
Here are the actual steps that the call instruction performs:
23. That’s the 32-bit stack pointer. As in the case of the other registers, there’s a
16-bit stack pointer called sp , which consists of the 16 lower bits of the "real"
stack pointer esp .
24. That is, its name is call on Intel machines and many others; all modern CPUs
have an equivalent instruction, although it may have a different name.
1. It saves the contents of the program counter on the stack.
2. Then it loads the program counter with the address of the first
instruction of the called function.

What does this sequence of events achieve? Well, since the program
counter always points to the next instruction to be executed, the
address stored on the stack by the first step is the address of the next
instruction after the call . Therefore, the last instruction in the called
function can resume execution of the calling function by loading the
program counter with the stored value on the stack. This will restart
execution of the calling function at the next instruction after the call ,
which is exactly what we want to achieve.
The effect of the second step is to continue execution of the
program with the first instruction of the called function; that’s because
the program counter is the register that specifies the address of the
next instruction to be executed.

The Layout of Data in a Stack

As we’ll see, the actual way that a stack is implemented is a bit


different than is suggested by the "stack of plates" analogy, although the
effect is exactly the same. Rather than keeping the top of the stack
where it is and moving the data (a slow operation), the data are left
where they are and the address stored in the stack pointer is changed,
which is a much faster operation. In other words, whatever address
the stack pointer is pointing to is by definition the top of the stack.
Before we get started on this analysis, here are some tips on how
to interpret the stack diagrams. First, please note that the range of
addresses that the stack occupies in these diagrams (given in
hexadecimal) is arbitrary. The actual address where the stack is
located in your program is determined by the linker and the operating
system.
Next, the "Top of Stack" address, that is, the address where the
stack pointer is pointing, will be in bold type. Also note that when we
push items on the stack the stack pointer will move upward in the
diagram. That’s because lower addresses appear first in the diagram,
and new items pushed onto the stack go at lower addresses. Anything
in the diagram "above" the stack pointer (i.e., at a lower address than
the stack pointer’s current value) is not a meaningful value, as
indicated in the "meaning" column.
Now let’s suppose we start with an empty stack, with the stack
pointer at 20001ffe , and ???? to indicate that we don’t know the
contents of that memory location. Thus, the stack will look like Figure
5.10.

FIGURE 5.10. An empty stack

Address Contents Meaning


20001ffe ???? none

Then, the user types in the two values "2" and "4" as the values of
FirstWeight and SecondWeight , and the first call to Average occurs; let’s
suppose that call is at location 10001000 . In that case, the actual
sequence of events is something like this, although it will vary
according to the compiler you are using:
1. The address of the next instruction to be executed (say, 10001005 ) is
pushed onto the stack, along with the values for the arguments First
and Second , which are copies of the arguments FirstWeight and
SecondWeight . In the process, the CPU will subtract eight (the size of
one address added to the size of two shorts , in bytes) from the stack
pointer (which is then 20001ff6 ) and store the return address at the
address currently pointed to by the stack pointer. Thus, the stack
looks like Figure 5.11.
2. Execution starts in the Average function. However, before the code
we write can be executed, we have to reserve space on the stack for
the auto variable(s) defined in Average (other than the
arguments First and Second , which have already been allocated); in
this case, there is only one, namely Result . Since this variable is a
short it takes 2 bytes, so the stack pointer has to be reduced by 2.

FIGURE 5.11. The stack


immediately after the call to Average

Address Contents Meaning


20001ff2 ???? none
20001ff4 ???? none
20001ff6 10001005 return address in
main
20001ffa 0004 Second
20001ffc 0002 First
20001ffe ???? none

After this operation is completed, the stack will look like Figure 5.12

FIGURE 5.12. The stack after auto


variable allocation

Address Contents Meaning


20001ff2 ???? none
20001ff4 ???? Result
20001ff6 10001005 return address in
main
20001ffa 0004 Second
20001ffc 0002 First
20001ffe ???? none

Wait a minute. What are those ???? doing at location 20001ff4 ?


They represent an uninitialized memory location. We don’t know
what’s in that location, because that depends on what it was used
for previously, which could be almost anything. The C++ compiler
uses stack-based addressing for auto variables, as well as copies of
arguments passed in from the calling function. That is, the
addresses of such variables are relative to the stack pointer, rather
than being fixed addresses. In this case, the address of Result
would be [esp] , or the current value of the stack pointer; Second
would be referred to in the object file as [esp+6] (i.e, 6 more than
the current value of the stack pointer, to leave room for the return
address and Result ). Similarly, the address of First would be
[esp+8] , or 8 more than the current value of the stack pointer.25 Since
the actual addresses occupied by these variables aren’t known until
the setup code at the beginning of the function is actually executed,
there’s no way to clear the variables out before then. That’s why
auto variables aren’t automatically initialized.

As you might have guessed, Susan and I went over this in gory detail.
Here’s the play by play.

Susan: Yes, I think this is what has confused me in the past about functions. I
never fully understood how the mechanism worked as how one function calls
another. I still don’t. But I guess it is by the position of the next address in a stack?

Steve: The stack is used to pass arguments and get return values from functions,
but its most important use is to keep track of the return address where the calling
function is supposed to continue after the called function is done.

Susan: This is how I am visualizing the use of the stack pointer. In one of my
other books it showed how the clock worked in the CPU and it seemed to cycle by
pointing in different directions as to what was to happen next in a program. So it was
sort of a pointer. Is this how this pointer works? So let me get this straight. All CPUs
have some kind of stack pointer, but they are used only for calling functions? Exactly
where is the instruction call ? It sounds to me

25. The actual mechanism used to refer to variables on the stack in a real compiler
is likely to be different from this one and indeed can vary among compilers.
However, this implementation is similar in principle to the mechanisms used by
compiler writers.
like it is in the hardware, and I am having a very difficult time understanding how a
piece of hardware can have an instruction.

Steve: All of the instructions executed in a program are executed by the


hardware. The call instruction, in particular, does two things:

1.It saves the address of the next instruction (the contents of the program
counter) on the stack.
2. It changes the program counter to point to the first instruction of the called function.

The return instruction is used to return to the calling function. It does this by the
following steps:

1. It retrieves the saved value of the program counter from the stack.26
2. It sets the program counter back to that value.

The result of this is that execution of the program continues with the next
instruction in the calling function.

Susan: Are you saying that, rather than the pointer of a stack actually pointing to
the top of the physical stack, wherever it points to by definition will be the top of the
stack, even if it really doesn’t look like it?

Steve: Exactly.

Susan: Now I see why we have to know what a short is. So then the pointer is
pointing to 20001ff4 as the top of the stack even though it doesn’t look like it?

26. Note that the compiler may be required to adjust the stack pointer before
retrieving the saved value of the program counter, to allow for the space used
by local variables in the function. In our example, we have to add 2 to the esp
register to skip over the local variable storage and point to the return address
saved on the stack.
Steve: Absolutely correct.

5.6. Scope of Variables

Now let’s look at another distinct way to categorize variables: the


scope of a variable is the part of the program in which it can be
accessed. Here, we are concerned with local scope and global scope.
Variables with global scope are called global variables. These
variables are defined outside any function and therefore by default can
be accessed from any function.27 Global variables are always in the
static storage class, as we have already seen. Variables with local
scope are called local variables. These variables are defined in a
function and are accessible only while that function is executing; they
can be either static or auto and are auto by default.
Figure 5.13 shows the valid combinations of scope and storage
class.

FIGURE 5.13. Scope vs. storage class

Scope Storage class

static auto

local
global

Susan had some comments on this topic.

Susan: On this scope stuff; I think you are going to have to help me understand
exactly what is inside a function and what is outside a function. I am not too sure I
know what the difference is.

27. Variables can be defined either inside a function (local variables) or outside a
function (global variables); by contrast, code must always be inside a function.
Steve: All code is inside a function. However, some variables (global variables)
are outside all functions and therefore shareable by all functions. Variables that are
defined inside functions are called local, because they are available only to the code
that’s in that function.

Susan: I am validating this with you now. Please correct any misconceptions.
1. Only variables are declared outside functions.
2. No code is written outside functions.
3. Up to this point I am not to be aware of anything else going on outside a
function.

Steve: Correct.

Before we get deeper into the notion of scope, I think we should


revisit the question of variable initialization in the light of the notion
of global and local variables. This is a difficult topic, so it wouldn’t
be surprising if you don’t find it obvious; Susan didn’t. I wrote the
next section to explain this topic to her.

Automatic vs. Static Allocation

What makes a variable static or auto is when its storage is assigned,


and therefore when its address is known. In the case of a static
variable, this happens at link time. In the case of an auto variable it
happens when the function where it is defined is entered.28
This distinction affects initialization because it’s impossible to
initialize something until you know where it is. Therefore, an auto

28. By the way, if you were worried about keeping track of the address of every
variable, that’s the compiler’s problem, not yours. The important distinction
between a static and an auto variable is when the address is assigned, not what
the actual address is.
variable cannot be initialized until the function where it is defined is
entered. This also means that you cannot assume that an auto variable
will retain its value from one execution of the function where it’s
defined to the next execution of that function, because the variable
might be at a different location the next time.
These restrictions do not apply to static variables, because their
addresses are known at link time and don’t change thereafter. A
variable defined outside all functions (a global variable) is
automatically in the static storage class, because otherwise its address
would never be assigned.29 Since its address is known at link time, the
initialization of such a variable can be and is performed before the
start of main .
A static variable that is defined inside a function is different from
one defined globally, in that it is not initialized until the function
where it is defined is entered for the first time. However, its value is
retained from one execution of its function to another, because its
address is fixed rather than possibly varying from one call of the
function to the next as can occur with an auto variable. For this
property to be of use, the initialization of a static variable in a function
must be performed only once; if it were performed on each entry to the
function, the value from the previous execution would be lost.
Therefore, that initialization is done only once, when the function is
first entered.
Susan wanted some more explanation of what happens at link time
and the related question of why we would want to use static variables.

Susan: Will you tell me again what happens at link time? Let’s see, I think it goes
like this: The source code is translated into an object file and then the object file is
linked to the hardware to make executable code. Is that how it goes?
29. Since all global variables are already in the static storage class, you don’t have
to (and shouldn’t) use the keyword static when declaring a global variable. In
another confusing legacy from C, the word static has an entirely different and
unrelated meaning when used for a global variable.
Steve: Not quite. The object file is linked with library files containing predefined
functions in compiled form.

Susan: Now, tell me again why you would want a variable to be static ? What is
the advantage to that? Does it just take up less room and be more efficient?

Steve: The advantage is that a static variable keeps its value from one function
call to the next. For example, suppose you wanted to count the number of times that
a function was called. You could use a static variable in the function, initialize it to 0,
and add 1 to it every time the function was called. When the program was done, the
value of that variable would be the number of times the function was called.
Obviously, we couldn’t use an auto variable to do that, as it would have to be
initialized every time the function started or we’d have an uninitialized variable.

Susan: I can see how using a static variable would work but I don’t see why an
auto variable couldn’t do the same thing. Well, I guess it would change each time
the function would be used. Are you saying in this case that the variable has to be
global?

Steve: Not exactly. Although we could use a global variable, we could also use a
static local variable. Figures 5.14 through 5.19 are some sample programs to
illustrate the situation.

FIGURE 5.14. Using an auto


variable and initializing it (code\count1.cpp)

#include <iostream> using


namespace std;

short counter()
{
short count = 0;

count ++;
cout << count << “ “;
return 0;
}

int main()
{
short i;

for (i = 0; i < 10; i ++)


counter();

return 0;
}

FIGURE 5.15. Using an auto variable and not initializing it (code\count2.cpp)

#include <iostream> using


namespace std;

short counter()
{
short count;

count ++;
cout << count << “ “;

return 0;
}

int main()
{
short i;

for (i = 0; i < 10; i ++)


counter();

return 0;
}
Using a local static variable and initializing it explicitly
FIGURE 5.16.
(code\count3.cpp)

#include <iostream> using


namespace std;

short counter()
{
static short count = 0; count

++;
cout << count << “ “;

return 0;
}

int main()
{
short i;

for (i = 0; i < 10; i ++)


counter();

return 0;
}

Using a local static variable and not initializing it explicitly


FIGURE 5.17.
(code\count4.cpp)

#include <iostream> using


namespace std;

short counter()
{
static short count;

count ++;
cout << count << “ “;

return 0;
}

int main()
{
short i;

for (i = 0; i < 10; i ++)


counter();

return 0;
}

The Scope Resolution Operator

Let me interrupt the conversation here to point out something new in


versions 5 and 6 of this little program: the “::” scope resolution
operator. This is an example of what we were discussing in the
section called “using, namespace, and std” on page 98 in Chapter 3.
Susan’s question was how we would refer to our own variables if they
had the same names as variables in the standard library. The answer is
to use the scope resolution operator to tell the compiler that we want
to use global identifiers defined in our source code, not ones of the
same name in the standard library. If we leave off the scope resolution
operator in front of count in these programs, the compiler will report
an error when it tries to compile them, because there is an identifier
named count in the std namespace , and and the line using namespace std; at
the beginning of each of our programs tells the compiler that we want
to be able to access all the identifiers in the std namespace without
specifying explicitly that they are from that namespace .
Now let’s continue with the analysis of scope.
Using a global variable and initializing it explicitly
FIGURE 5.18.
(code\count5.cpp)

#include <iostream> using


namespace std;
short ::count = 0; short
counter()
{
::count ++;

cout << ::count << “ “;

return 0;
}

int main()
{
short i;

for (i = 0; i < 10; i ++)


counter();

return 0;
}

Using a global variable and not initializing it explicitly


FIGURE 5.19.
(code\count6.cpp)

#include <iostream> using


namespace std;
short ::count; short
counter()
{
::count ++;

cout << ::count << “ “;


return 0;
}

int main()
{
short i;

for (i = 0; i < 10; i ++)


counter();

return 0;
}

Steve: Now that I’ve cleared that up, what will each of these programs do when
run?

Susan: Now, let me think about this. Variables are: static or auto , global or
local.

Local is for use only within functions. These variables are mostly auto , and will be
auto by default, but they can be static ; in the latter case, they will be initialized to 0.

Steve: Correct; a static numeric variable is initialized to 0 if no other provision is


made by the programmer to initialize it. One fine point: a local variable can be used
only within one function.

Susan: Global variables are declared only outside functions. They are always
allocated storage at link time, like static local variables.

Steve: Correct.

Susan: Variables with static allocation are fixed because they are initialized at
link time and thus are done just once and never change. But they can be local or
global.
Steve: Not exactly. Theaddress of a statically allocated variable is set once
and never changes; its value can change just like the value of an auto variable
can.

Susan: That’s what I had mixed up. I was confusing addresses with values.

Steve: OK, as long as you’re straightened out now.

Susan: An auto variable is assigned an address when the function where it is


defined is entered. All auto variables are local.

Steve: Correct.

Susan: Now, here is where I am confused. What is the difference between at


link time and when the function where it is defined is entered? Does at link time
mean when you are done with your source code and you are making an executable?

Steve: Yes.

Susan: And does when the function where it is defined is entered mean when
the program is already made into an executable and you are running the program?

Steve: Yes.

Susan: I am confused about what we mean by initialization. I am confusing


declaring a value for a variable and the designation of an address for a variable. It
almost seems as if we are using these two meanings for the same term. I always
thought that initializing a variable meant just assigning a value to it.

Steve: Initializing a variable means assigning an initial value to it. In the case of
an auto variable, this must be done every time the function where it is declared is
entered; whereas with a static variable, it is done once.
Susan: OK, this is what I imagined this to mean. Then how come, in your figure
of a stack, you have values assigned to the places where the variables are?

Steve: Those values are present only after the variables to which they
correspond have been initialized. In Figure 5.12, for example, the contents of the
address corresponding to Result are shown as
???, rather than as a valid value; whereas the values of First and Second are
shown as initialized, because they have already been set to values equal to the input
arguments provided by the caller.

Susan: Remember when I started tracing the “sorting” program? It had random
numbers in the places where numbers are supposed to go, and when I actually
entered a value then those random numbers were replaced by that value. And is that
why you put ??? there, because you know that something is there but you don’t
know exactly what? It is just whatever until you can put a real value into those slots.

Steve: Right.

Susan: So if you leave it alone by not initializing it, then it keeps the last value it
had each time it goes through the loop and therefore the count goes up?

Steve: Yes, except that the initial value isn’t reliable in that case. In the case of
the example count programs, that value happened to be 0, but there’s no reason to
expect that in general.

Susan: I want you to know that it was not immediately apparent to me just what
the code in the example programs was doing; it really does look kinda strange. Then
I noticed that this code called a function named counter . Why? Couldn’t this work
without using a function call?

Steve: No, it wouldn’t work without a function call, because the whole point is
that when a function is called, the auto variables
defined in that function have unknown values. How would I show that without a
function call?

Susan: I see that. But I still don’t get the point, because they all did the same thing
except 1. The results for that were the following: 11 11 11 11 1 1 . The results for
the rest of the programs were all the same, being 1 2 3 4 5 6 7 8 9 10 . So, if they
all do the same thing, then what is the point? Now, what really makes me mad about
this is why 1 has that result. This bothers me. Obviously it is not incrementing itself
by 1 after the first increment, it is just staying at one. Oh, wait, okay, okay, maybe. . .
how about this: If you initialize it to 0 then each time it comes up through the loop it is
always 0 and then it will always add 1 to 0 and it has to do that 10 times.

Steve: Right, except that #2 does the same thing as the others only by accident;
you got lucky and happened to have a 0 as the starting value of the uninitialized local
variable in that example.

Susan: Then on the initialized local static variable, why does it work? Because it
is static , and because its address is one place and won’t budge; that means its
value can increment. Well, would that mean it isn’t written over in its location every
time the function is called so we can add a value to it each time through?

Steve: Right.

Susan: And then the uninitialized static one works for the same reason the auto
uninitialized one works.

Steve: Not quite. A static local variable is always initialized to something, just
like a global variable is. If you don’t specify an initial value for a static local
numeric variable, it will be initialized to 0 during the first execution of the function
where it is defined. On the other hand, as I mentioned above, you just got lucky with
the uninitialized local variable example, which happened to have
the starting value 0. Other people using other computers to run the program may
have different results for that example.

Susan: Now as for global, this is hard. Let me guess. Do the global initialized and
uninitialized work for the same reasons I said earlier?

Steve: The global variables are always initialized, whether you specify an initial
value or not; if you don’t specify one, it will be 0.

Susan: That’s what you said about static numeric variables. Are they the same?
Well, they have to be the same because only static variables can be global, right?

Steve: Correct. Global variables are always statically allocated.

Susan: So if you don’t initialize a numeric variable then it can become any number
unless it is a static numeric without explicit initialization and then it will be 0 by
default?

Steve: Correct.

Susan: OK, let me see if I have this. All static really means is that the variable is
put in an address of memory that can’t be overwritten by another variable but can be
overwritten when we change the variable’s value?

Steve: Right.

Susan: These are tricks, and you know I don’t like tricks. If they are global
numeric variables, whether explicitly initialized or not, they are static ; therefore
they will at least have a value of 0. In example 5 this is stated explicitly but not in
example 6, so the variable also will take the value of 0 by default, therefore these
two programs are effectively identical, just like 3 and 4. That is why examples 3, 4,
5, and 6 have the same results.
Steve: Well, obviously the trick didn’t work; you crossed me up by getting the right
answers anyway.

Let’s pause here to look at a sample program that has examples of all
the types of variables and initialization states we’ve just discussed.
These are:30
1. global, not explicitly initialized
2. global, explicitly initialized
3. auto , uninitialized
4. auto , initialized
5. local static , not explicitly initialized
6. local static , explicitly initialized

Careful examination of the sample program shown in Figure 5.20 will


help you to visualize how and where each of these variable types
might be used. As usual, you can compile and run it to see what it
does; running it under the debugger is probably more helpful than
running it directly.

Using variables of different scopes and storage classes


FIGURE 5.20.
(code\scopclas.cpp)

#include <iostream> using


namespace std;

short count1; // A global variable, not explicitly initialized short count2 = 5; //


A global variable, explicitly initialized

short func1()
{

30. Remember, there aren’t any global auto variables, because they would never
be initialized.
short count3; // A local auto variable, not explicitly initialized short count4 = 22; //
A local auto variable, explicitly initialized static short count5; // A local static
variable, not explicitly initialized static short count6 = 9; // A local static variable,
explicitly initialized

count1 ++; // Incrementing the global variable count1. count2 ++; //


Incrementing the global variable count2.
count3 ++; // Incrementing the local uninitialized auto variable count3. count4 ++; //
Incrementing the local auto variable count4.
count5 ++; // Incrementing the local static variable count5. count6 ++; //
Incrementing the local static variable count6.

cout << “count1 = “ << count1 << endl; cout <<


“count2 = “ << count2 << endl; cout << “count3
= “ << count3 << endl; cout << “count4 = “ <<
count4 << endl; cout << “count5 = “ << count5
<< endl; cout << “count6 = “ << count6 << endl;
cout << endl;

return 0;
}

int main()
{
func1();
func1();

return 0;
}

The results of using variables of different scopes and storage classes


FIGURE 5.21.
(code\scopclas.out)

count1 = 1
count2 = 6
count3 = -32715
count4 = 23
count5 = 1
count6 = 10

count1 = 2
count2 = 7
count3 = -32715
count4 = 23
count5 = 2
count6 = 11

The results shown should help to answer the question of when we


would want to use a static variable rather than an auto variable:
whenever we need a variable that keeps its value from one execution
of a function to another. You may be wondering where that weird value
for count3 came from. Since we never initialized it, we can’t complain
when its value is meaningless. Although the compiler can warn us
about such problems in some cases, they are still a significant source
of errors in C++ programs, so it’s worthwhile remembering to look
out for them.

5.7. The Disadvantages of Global Variables

Now that we’ve cleared up the question of when different types of


variables are initialized, let’s continue with the distinction between
global and local variables. You may be surprised that a programmer
would accept the limitation of allowing certain variables to be
accessed only in certain functions. Surely it’s more powerful to be
able to access anything anywhere. Isn’t it?
Let me tell you a little story about the "power" of global variables.
Unlike the one about the funny odometers, this one is true.
Using a Very Primitive BASIC Language

In the late 1970s, I worked for a (very) small software house where I
developed a database program for the Radio Shack TRS-80 Model III
computer. This computer was fairly powerful for the time; it had two
79K floppy disks and a maximum of 48K memory. The database
program had to be able to find a subset of the few thousand records in
the database in a minute or so. The speed of the floppy drive was the
limiting factor. The only high-level language that was available was a
BASIC interpreter clearly related by ancestry to QBASIC, the BASIC
that comes with MS-DOS, but much more primitive; for example,
variable names were limited to 2 characters.31 There was also an
assembler, but even at that time I wasn’t thrilled with the idea of
writing a significant application program in assembly language. So we
were stuck with BASIC.
Actually, that wasn’t so bad. Even then, BASIC had pretty good
string manipulation functions (much better than the ones that come with
C) and the file access functions, although primitive, weren’t too hard
to work with for the application in question. You could read or write a
fixed number of bytes anywhere in a disk file, and since all of the
records in a given database were in fact the same length, that was
good enough for our purposes. However, there were a couple of
(related) glaring flaws in the language: there were no named
subroutines (like functions in C++), and all variables were global.
Instead of names, subroutines were addressed by line number. In
TRS-80 BASIC, each line had a number, and you could call a
subroutine that started at line 1000 by writing "GOSUB 1000". At the
end of the subroutine, a "RETURN" statement would cause control to
return to the next statement after the GOSUB.
While this was functional in a moronic way, it had some serious
drawbacks. First, of course, a number isn’t as mnemonic as a name.

31. That is, two significant characters; you could have names as long as you
wanted, but any two variables that had the same first two characters were
actually the same variable.
Remembering that line 1000 is the beginning of the invoice printing
routine, for example, isn’t as easy as remembering the name
PrintInvoice . In addition, if you "renumbered" the program to make room
for inserting new lines between previously existing lines, the line
numbers would change. The second drawback was that, as the
example suggests, there was no way to pass arguments to a subroutine
when it was called. Therefore, the only way for a subroutine to get
input or produce output was by using and changing global variables.
Yet another problem with this line-numbered subroutine facility was
that you could call any line as a subroutine; no block structure such as
we have in C++ was available to impose some order on the flow of
control.
With such an arrangement, it was almost impossible to make a
change anywhere in even a moderately large program without breaking
some subroutine. One reason for this fragility was that a variable
could be used or changed anywhere in the program; another was that it
was impossible to identify subroutines except by adding comments to
the program, which could be out of date. So almost any change could
have effects throughout the program.

The Solution to My Problem with BASIC

After some time struggling with this problem, I decided to end it, once
and for all, by adding named subroutines with arguments and local
variables to the language. This made it possible to maintain the
program and we ended up selling several hundred copies of it over a
couple of years. Besides, fixing the language was fun.
The moral? There’s almost always a way around a limitation of a
computer language, although it may not be worth the effort to find it.
Luckily, with C++, adding functionality is a bit easier than patching
BASIC in assembly language.
A Scope Defines a namespace

Before we finish with our discussion of scope, I should point out


something that may not be obvious to you. Every scope defines a new
namespace . This is why local variables in different functions don’t clash
with one another: since every function has its own scope, it also has its
own namespace , so a local variable in one function is distinct from any
local variables in other functions that have the same name.

5.8. More on Using the Stack

Now it’s time to get back to our analysis of the use of the stack in
storing information needed during execution of a function. The next
statement in our example program (Figure 5.5 on page 239) is Result =
(First + Second) / 2; . Since we’ve assumed that First is 2, and Second is 4,
the value of Result will be (4+2)/2, or 3. After this statement is
executed, the stack looks like Figure 5.22.

FIGURE 5.22. The stack after the initialization of Result

Address Contents Meaning


20001ff2 ???? none
20001ff4 0003 Result
20001ff6 10001005 return address in
main
20001ffa 0004 Second
20001ffc 0002 First
20001ffe ???? none

Finally, at the end of the function, the stack pointer will be


incremented to point to the stored return address. Then the return
instruction will reload the program counter with the stored return
address, which in this case is 10001005 . Then the value of Result will
be made available to the calling function and the stack pointer will be
adjusted so the stack looks as it did before we called Average .
After the return, the stack will be empty as we no longer need the
arguments, the auto variable Result , or the return address from the
Average function. Figure 5.23 shows what the stack looks like now.

FIGURE 5.23. The stack after


exiting from Average

Address Contents Meaning


20001ff2 ???? none
20001ff4 0003 none
20001ff6 10001005 none
20001ffa 0004 none
20001ffc 0002 none
20001ffe ???? none

Do not be fooled by the casual statement "the stack is empty". That


means only that the stack pointer ( esp ) is pointing to the same place it
was when we started our excursion into the Average function; namely,
20001ffe . The values that were stored in the memory locations used by
Average for its auto variables haven’t been erased by changing the stack
pointer. This illustrates one very good reason why we can’t rely on the
values of auto variables until they’ve been initialized; we don’t know
how the memory locations they occupy might have been used
previously.
The previous discussion of how arguments are copied into local
variables when a function is called applies directly to our Average
function. If we try to change the input arguments, we will change only
the copies of those arguments on the stack and the corresponding
variables in the calling function won’t be altered.32 That’s perfectly
acceptable here, since we don’t want to change the values in the
calling function; we just want to calculate their average and provide
the result to the calling function. An argument that is handled this way
is called a value argument, as its value is copied
into a newly created variable in the called function, rather than
allowing the called function access to the "real" argument in the
calling function.33
One thing we haven’t really discussed here is how the return value
gets back to the caller. One way is to store it in a register, which is then
available to the calling routine after we get back. This is a very easy
and fast way to pass a return value back to the caller. However, it has
a drawback: a register can hold only one value of 32 bits. Sometimes
this is not enough, in which case another mechanism will be used.
However, the compiler takes care of these details for us, so we don’t
have to worry about them.

5.9. Review

First, we added the fundamental programming concept of the function.


A function is a piece of code that can "stand alone"; it can be compiled
separately from other functions, and provides some service that we
can use via a mechanism known as a function call. The function that
makes the call is known as the calling function, and the one it calls is
known as the called function. Before we can call a function, we need
to know what input values it needs and what it returns. This
information is provided by a function declaration at the beginning of
each function. The function declaration includes an argument list,
which specifies input values that the called function uses (if any), and
a return type, which specifies the type of the value that it produces
when it’s finished (if any). When we call a function,

32. Actually, it’s generally a good idea not to change the values of arguments, even
value arguments. Although this is legal, it tends to confuse programmers who
read your code later as they often make the implicit assumption that arguments
have the same value throughout a function.
33. It’s also possible to define a function that has access to an actual variable in
the calling function; we’ll see how and when to do that at the appropriate time.
it executes until it reaches the end of its code or reaches a return
statement, whichever comes first. When either of these events
happens, the program continues execution in the calling function
immediately after the place where the function call occurs. Ordinarily,
as in our example, an argument to a function is actually a copy of the
variable in the calling program, so that the called function can’t modify
the "real" value in the caller. Such an argument is called a value
argument.34
We also saw that function and variable names can be of any length,
consisting of upper or lower case characters (or both), digits, and the
special character underscore (_). To make it easier for the compiler to
distinguish numbers from variable names, the first character can’t be a
digit. Also, a variable name can’t be the same as a keyword, or name
defined by the language; examples of keywords we’ve seen so far
include if , for , and short .
After finishing the construction of our Average function, we saw
how to use it by making a function call. Then we launched into an
examination of the way that values in the calling function are
converted into arguments in the called function, which required a
detour into the software infrastructure.
We started this excursion by looking at the linker, which is used to
construct programs from a number of functions compiled into separate
object files. Next, we explored the notion of storage class, which
determines the working lifetime of a variable. The simplest storage
class is static . Variables of this class, which includes all variables
defined outside any function, have storage assigned to them by the
linker and retain the same address during the lifetime of the program.
On the other hand, auto (for "automatic") variables are always defined
in a function and are assigned storage on the stack when that function
starts execution. The stack is the data structure that stores function
arguments, local variables, and return addresses during the execution
of a function; it’s called that because it behaves

34. As this might suggest, there is another type of argument, which we’ll get to in
Chapter 6.
like a spring-loaded stack of plates in a cafeteria, where the last one put
on the top is the first one to be removed.
Then we noted that each variable, in addition to a storage class,
has a scope, which is the part of a program in which the variable can
be accessed. At this point, the scopes that are important to us are local
scope and global scope. As you might guess, a global variable can be
referred to anywhere, while a local variable can be accessed only in
the function where it is defined. Although it may seem limiting to use
local variables rather than global ones, programs that rely on global
variables are very difficult to maintain, as a change anywhere can
affect the rest of the program. Programs that limit the scope of their
variables, on the other hand, minimize the amount of code that can be
affected by a change in one place. Because local variables are only
usable while in the function where they are defined, they can be stored
on the stack; therefore, they don’t occupy memory during the entire
lifetime of the program.
Of course, local variables take up room while they’re being used,
which means that the stack has to have enough storage to hold all of
the local variables for the current function and all of the functions that
haven’t finished executing. That is, the stack has to have enough room
for all of the variables in the current function, the function that called
the current function, the one that called that one, and so on up to the
main function, which is always the top-level function in a C++
program. Since the amount of memory that is allocated to the stack is
not unlimited, it’s possible to run out of space, in which case your
program will stop working. This is called a stack overflow, by
analogy with what happens if you put too many plates on the cafeteria
plate stack: it falls over and makes a mess. When using the compiler
that comes with this book, it’s unlikely that you’ll ever run out of stack
space unless you have a bug in your program. Some other compilers
aren’t as generous in their space allotments, so the likelihood of a
stack overflow is less remote. The solution to this problem, should it
arise, is to use another kind of storage allocation called dynamic
storage; we’ll see an example of this mechanism in Chapter 6.
Now that we’ve gone through that review, it’s time to do an exercise to
drive home some points about scope and storage classes.

5.10. Exercises
1. If the program in Figure 5.24 is run, what will be displayed?

FIGURE 5.24. Exercise 1


(code\calc1.cpp)

#include <iostream> using


namespace std;

short i;

short Calc(short x, short y)


{
static short j = 0;

cout << “The value of j in Calc is: “ << j << endl; i ++;
j = x + y + j;

return j;
}

int main()
{
short j;

for (i = 0; i < 5; i ++)


{
j = Calc(i + 5, i * 2) + 7;
cout << “The value of j in main is: “ << j << endl;
}
return 0;
}

Answers to exercises can be found at the end of the chapter.

5.11. Conclusion

We’ve covered a lot of material in this chapter, ranging from the


anatomy of functions through a lot more information about what’s
going on "underneath the covers" of even a fairly simple C++
program. Next, we’ll see how to write a realistic, although simplified,
application program using some more advanced concepts in C++.

5.12. Answers to Exercises


1. If you got this one right, congratulations! It’s just filled with tricks, but
they’re all things that you might run into in a real (although poorly
written) program. Here’s the answer:

The value of j in Calc is: 0 The value


of j in main is: 12 The value of j in
Calc is: 5 The value of j in main is:
23 The value of j in Calc is: 16 The
value of j in main is: 40

The first question is why there are only three values displayed by
each output statement. The for loop that calls the Calc routine and
displays the results should execute 5 times, shouldn’t it?
This is the first trick. Since i is a global variable, the statement i ++;
in the Calc function affects its value. Therefore, i starts out at 0 in
the main function, as usual, but when the Calc function is called, i
is incremented to 1. So the next time the modification expression i
++ in the for statement is executed, i is changed to 2. Now the
controlled block of the for statement is executed again, with i set to
2. Again, the call to Calc results in i being incremented an extra
time, to 3, so the next execution of the for loop sets i to 4. The final
call to Calc increments the value of i to 5, so the for loop
terminates, having executed only three times rather than the five you
would expect by looking at it. Now you can see why global
variables are dangerous!
Now what about the values that j takes on? Well, since the j in Calc
is a static variable, it is initialized only once. Because it is a local
static variable, that initialization is performed when Calc is called
for the first time. So the first time Calc is called, j is set to 0. The
arguments specified by main on the first call to Calc are 5 and 0;
this means that, inside Calc , x and y have those values,
respectively. Then the new value of j is calculated by the statement j
= x + y + j; , or 5 in total. The return j; statement specifies this as the
return value of Calc and this value is then added to 7 as specified
by the assignment statement j = Calc(i + 5, i * 2) + 7; in main . That
explains why the output statement in main displays the value of j as
12 the first time.
It’s very important to note that the variable j in main is completely
unrelated to the variable j in Calc . Since they are local variables,
they have nothing in common but their names. There is no risk of
confusion (at least on the compiler’s part), since we can access a
local variable only in the function in which it is defined. Therefore,
when we refer to j in main , we mean the one defined there; and
when we refer to j in Calc , we mean the one defined there.
Next, we call Calc again with the arguments 7 and 4. To compute
these arguments from the expressions i + 5 and i* 2 , you have to
remember that i has been modified by Calc and is now 2, not 1 as
we would expect normally. When we get to Calc , it displays the old
value of j (5), left over from the previous execution of this
function. This is because j is a local static variable; thus, the
initialization statement static short j = 0; is executed only once, upon
the first call to the function where it is defined. Once j has been set
to a value in Calc , it will retain that value even in a subsequent call
to Calc ; this is quite unlike a normal auto variable, which has no
known value at the beginning of execution of the function where it
is defined. A new value of j is now calculated as 7 + 4 + 5 , or 16,
and returned to main .
On return from Calc , the value of j in main is 23, as set by the
assignment statement j = Calc(i + 5, i * 2) + 7; . We also don’t want to
forget that i is now 3, having been changed in Calc .
Exactly the same steps occur for the last pass through the for loop:
we call Calc with the new values of i + 5 and i* 2 , which are 9 and
8, respectively, since i has been incremented to 4 by the for
statement’s modification expression i ++ . Then Calc displays the
old value of j , which is 16, and calculates the new value, which is
33. This is added to the literal value 7 and stored in j in main ,
resulting in the value 40, which is then displayed by the output
statement.
Don't get discouraged if you didn't get this one, especially the
effects caused by a global i . Even experienced programmers can be
taken by surprise by programs that use global variables in such
error-prone ways.
CHAPTER 6 Taking Inventory

Now we have enough of the fundamentals of programming under our


belts to look at some of the more powerful features of C++. As I’ve
mentioned before, C++ was intended as the successor to C. What I
haven’t told you is exactly why it was invented.
To understand this, we’ll have to consider the differences between
two basic kinds of variables that exist in both C and C++: native (i.e.,
defined in the language itself) and user-defined (i.e., defined by the
programmer). The native types that we’ve been using are char , short ,
and unsigned short (and int , but only for the return type of main ), all of
which have been inherited from C.1 The user-defined types we’ve
been using are the string , Vec, and the types of cin and cout .
1. There are actually several other native C++ types that we haven’t used: long,
float , double,
and bool. The long type is useful for storing whole-number values
that are larger than will fit into a short (hence the name), while float and double
are able to store values that have fractional parts as well as integral values.
These are useful in scientific and engineering calculations; we’ll use all of them
except for float later in the book. The bool type, a relatively recent addition to
C++, is useful for keeping track of a true/false condition. We’ll see how to use
this variable type later in this chapter.
What difference does it make whether a variable is native or user-
defined? Quite a bit of difference, in fact. In both C and C++,
variables of the native types are fully supported by the language. To be
“fully supported” means that variables can be defined, initialized,
assigned values, passed as arguments and return values, and compared
to other values of the same type. Such a variable can be assigned
storage in either the static or auto storage classes: If a variable is auto ,
the storage is assigned at entry to the function where it is defined, and
released automatically at exit from that function; if it is static , it is
initialized to some reasonable value either at link time (for a global
variable) or upon the first entry to the function where it is defined (for
a local variable).
However, most of these facilities aren’t available to user-defined
data types in C. For example, variables of such types can’t be
compared; this limitation results from the fact that the C compiler has
no idea how to compare two variables of a type that you define.
Similarly, what is a reasonable default value for a variable of a user-
defined type? Presumably, the user (i.e., the programmer) knows, but
the C compiler doesn’t.
In this chapter, we’ll see how to give a C++ compiler enough
information to allow data types that we define to behave almost
exactly like the native types. Susan had a few questions on this topic:

Susan: I think I need to find out something here. I am getting the impression that
what is "native" is C and what is "user-defined" is C++. Is that right? And if so,
why?

Steve: Pretty much so. As to why, the answer is pretty simple: the reason that
C++ was invented in the first place was to add good support for user-defined types
to the efficiency of C.

Susan: Okay, but why are cin and cout user-defined? We didn’t define them.
They’re from the standard library, right? Then shouldn’t they be native?
Steve: That’s a good point, but in fact things defined in the standard library aren’t
native. Here’s a quick way to tell whether a variable type is native or user-defined: if
you don’t need to
#include a header file to use it, then it’s native. Anything not native is user-defined.

6.1. Definitions

A class is a user-defined type.2

A class interface tells the compiler what facilities the class provides.
This interface is usually found in a header file, which by convention
has the extension .h .3

A class implementation tells the compiler how to implement the


facilities defined in the class interface. This is usually found in a
source code file, which in the case of the compiler on the CD in the
back of this book usually has the extension .cpp .

An object is a variable of a class type. Its behavior is defined by the


header file and implementation of the class .4

A member function is a function that is part of the definition of a


class .
2. Please note that the terms class and storage class have nothing to do with one
another. This is another case where C++ reuses the same word for different
concepts, although at least “storage class” isn’t a keyword. We should be
grateful for such small favors.
3. At least, that is the convention for classes created by “normal” programmers,
like you and me. The header files that define the classes in the C++ standard
library have no extensions and in fact may not even be stored in normal files.
An example is the <string> header file that we have been using.
4. Some people use “object” to refer to a variable of any type, whether native or
user-defined.
A member variable is a variable that is part of the definition of a
class .

Object-oriented programming is the organization of programs as


collections of objects exhibiting user-defined behavior, rather than as
collections of functions operating on variables of native data types.5

Encapsulation is the concept of hiding the details of a class inside the


implementation of that class, rather than exposing them in the interface.
This is one of the primary organizing principles that characterize
object-oriented programming.

Internals, in the case of native data types, refers to details of the


implementation of these types in the compiler. In the case of class
types, internals means the details of implementation of the type rather
than what it does for the user.

6.2. Objectives of This Chapter

By the end of this chapter, you should


1. Understand what a user-defined type (a class ) is, and how it is
defined.
2. Understand how variables of some simple classes are created,
destroyed, and copied.
3. Understand how and why access to the internals of a class is
controlled.
5. Purists may not approve of this use of the term object-oriented
programming, as I’m not using this term in its strictest technical sense.
However, since we are using objects and classes as our central organizing ideas,
using the term object-oriented programming seems reasonable to me in this
context. Chapters 9 and 10 will cover the other main concepts included in the
strict definition of object-oriented programming.
6.3. User-defined Data Types

In C++, a user-defined variable is called an object. Each object has a


type, just like variables of native types ( short , char , etc.). For
example, if we define a class called StockItem (as we will do in this
chapter), then an object can be of type StockItem , just as a native
variable can be of type short . However, an additional step is required
when we want to use user-defined types. Since the compiler has no
intrinsic knowledge of these types, we have to tell it exactly what they
are and how they work. We do this by defining a class , which specifies
both the data contained in the user-defined variable and what
operations can be performed on these data.
Here’s how Susan reacted upon her first encounter with this idea.

Susan: I can tell that there is only one thing that I think that I understand about
this. That is, that C++ is not a language. You have to make it up as you go along. . . .

That may be overdoing it a bit, but there is a grain of truth in her


observation: C++ is more of a "language kit" than it is a language.
What do I mean by this?
I mean that to use C++ in the most effective way, rather than merely
as a "better C", it is necessary to create data types and tell the
compiler how to treat them as though they were native data types. So
far in this book, we have been using data types that were previously
defined, either by the compiler and language (native types, e.g., short ,
char ) or by libraries ( class types, e.g., string ). Now we’re going to
actually make up our own types that will be usable just like native
types. The difference between using variables and making up new
variable types is analogous to the difference between using a program
and writing a program, but carried to the next higher level.
In the event that you find this notion hard to understand, you’re not
alone; so did Susan.
Susan: This is an outrage! I didn’t understand one other word after this as I was
far beyond anything that could even be described as shock. I think I did faint. I may
as well have been in a coma.

Interestingly enough, she actually did understand this idea of making


up our own data types, so perhaps she was overestimating the degree
of her shock.
Before we get back to the technical explanation of how we create
new data types, I’m sure one more question is burning in your mind:
Why should we do this? What’s wrong with the native types like char
and short ? The answer is simple: we make up types so that we can
match the language to the needs of the problem we’re trying to solve.
For example, suppose we want to write a program to do inventory
control for a small business like a grocery store. Such a program
needs objects representing items in the store, which have prices,
names, and so on. We need to define each of these types of objects so
that it can display the behavior appropriate to the thing it represents.
The availability of objects that have relevance to the problem being
solved makes it much easier to write (and read) a program to handle
inventory than if everything has to be made of shorts and chars .
I suspect that the advantages of making up one’s own data types
may still not be apparent to you, so let me make an analogy with
natural languages. Making up new data types in C++ is in some ways
quite similar to making up new words in English (for example). You
might think that if everyone made up new words, the result would be
chaos. Actually, this is correct, with the very important exception of
technical jargon and other vocabularies that are shared by people who
have more in common than simply being speakers of English. For
example, physicians have their own "language" in the form of medical
terminology. Of course, a cynical observer might conclude that the
reason for such specialized vocabulary is to befuddle or impress the
naive listener, and of course it can be used for that purpose. However,
there is also a much more significant and valid reason for using
technical jargon: to make it possible for experts in a field to
communicate with one another quickly and precisely. The
same is true of creating our own data types; they enable us to write
programs that are more understandable to those who are conversant
with the problems being solved. It’s much easier to talk to a store
owner about inventory objects than about shorts and chars !
Here’s the discussion that Susan and I had on this topic:

Susan: Why should we have user-defined data types?

Steve: So that you can match the language to the needs of the problem you’re
trying to solve. For example, if you were writing a nurse’s station program in C++,
you would want to have objects that represented nurses, doctors, patients, various
sorts of equipment, and so on. Each of these objects would display the behavior
appropriate to the thing or person it was representing.

Susan: Why do you need that? What if each individual who spoke English made
up a unique version of English (well, it is user- defined, right?), how could we
communicate? This is garbage.

Steve: We need user-defined types for the same reason that specialists need
jargon in their technical fields. For example, why do you health-care professionals
need words like tachycardia? Why don’t you just say "a fast heartbeat" in simple
English?

Hey, that’s not a bad way to explain this: Adding classes is like adding specialized
vocabulary to English. I don’t remember ever seeing that explanation before; what
do you think of it?

Susan: Huh? Then you are saying that, by defining a class of objects, they can
take on more realistic qualities than just abstract notions? That is, if I wanted to
represent nurses in a program, then I would do it with a class named nurse and
then I can define in that program the activities and functions that the nurse objects
would be doing. Is this how you keep everything straight, and not mix them up with
other objects?
Steve: Yes, that’s one of the main benefits of object-oriented programming. You
might be surprised how hard it is to teach an old-line C programmer the importance
of this point.

Susan: So is this what object-oriented programming is? I have heard of it, but
never knew what it meant. Could it also be described as user-defined programming? I
guess there are advantages to teaching a novice; you don’t have to undo old ideas to
make way for newer ones. So, anything that is user-defined is a class ? That is,
native variables are not classes ?

Steve: Right. Every user-defined type is a class ; data items of a class type are
called objects. Variables of native types are not objects in the object-oriented sense.

Susan: OK, so if I want to make up something, then what I make up is called a


class as opposed to the other type of stuff that isn’t made up and is really part of
C++; that is called native. That is intuitive, thank you. Then the class is made up of
data items? And what about native variables; are they objects? I guess just the
variables of the class are called objects because I just read your definition for
object. So native variables are not objects, they are just variables. Am I am talking
in circles again?

Steve: No, you’re not; you’re making perfect sense. The only point you have
missed is that there are functions in the objects, as well as data items. We’ll get into
that shortly.

Susan: So Steve, tell me: What have I been doing up to this point? How does this
new stuff compare to the old stuff and which one is it that the programmer really
uses? (Let’s see, do I want curtain 1 or 3; which one holds the prize?) I just want to
get a little sense of direction here; I don’t think that is a whole lot to ask, do you?

Steve: What you’ve been doing up to this point is using classes


( string , Vec ) as well as native types like short and char . This new
stuff shows how to create classes like string , rather than just using them.6

Assuming that I’ve sold you on the advantages of making up our own
data types, let’s see how we can actually do it. Each data type is
represented by a class , whose full definition is composed of two parts:
the interface definition (usually contained in a file with the extension
.h ), and the implementation definition (usually contained in a file
with the extension .cpp ). The interface definition tells the compiler
(and the class user) what the class does, while the implementation
definition tells the compiler how the objects of that class actually
perform the functions specified in the interface definition. Let’s take a
look at a step-by-step description of how to create and use a class .
1. Write the class interface definition, which will be stored in a file
with the extension .h . In our example of a StockItem class , we’ll use
item1.h to hold our first version of this interface definition. This
definition tells the compiler the names and types of the member
functions and member variables that make up the objects of the
class , which gives the compiler enough information to create objects
of this class in a user’s program.
2. Write the class implementation definition, which will be stored in a
file with the extension .cpp ; in our example, the first one of these
will be stored in the file item1.cpp . This definition is the code that
tells the compiler how to perform the operations that the interface
definition refers to. The implementation definition file must
#include the interface definition file ( item1.h , in this case) so that the
compiler has access to the interface that is being implemented. It
needs this information to check that the implementation is proper
(i.e., that it conforms to the interface specified in the interface
definition file).

6. In case you were wondering, you can’t create new native types.
3. Write the program that uses objects in the class to do some work. The
first such program we’ll write will be itemtst1.cpp . This program also
needs to #include the interface definition file, so that the compiler can
tell how to create objects of this class .
4. Compile the class implementation definition to produce an object file
( item1.o ). This makes the class available to the user program.
5. Compile the user program to produce an object file ( itemtst1.o ).
6. Link the object file from the user program, the object file from the
class implementation definition, and any necessary libraries together
to form a finished executable. Our first sample will be called
itemtst1.exe .

A couple of items in this list need some more discussion. Let’s see
how Susan brought them to my attention.

Susan: I have a problem here. First under item 2, you put "The class
implementation definition file must #include "; excuse me, but that doesn’t make
sense. What do you mean by #include ? How do you say that, "pound include"?

Steve: Yes, that’s how it’s pronounced. You could also leave off the "pound" and
just say "include", and every C and C++ programmer would understand you. As you
may recall, a #include statement causes the compiler to pretend that the code in the
included file was typed in instead of the #include statement.

Susan: Section 6 that stuff with the linking. . .isn’t that done by the compiler; if not,
how do you do it?

Steve: The linker does it, but the compiler is generally capable of calling the linker
automatically for you; that’s why we haven’t needed to worry about this before.

Susan: OK, where is the linker? Is it not part of the compiler software? If not,
where does it come from?
Steve: Every compiler comes with one, but you can also buy one separately if
you prefer.

Susan: Who puts it in your computer? Also, how do you "call" the linker if you
have always had the compiler do it for you?

Steve: It is installed along with the compiler. You can tell the compiler not to call it
automatically if you prefer to do it manually; there are reasons to do that sometimes.
For example, when you’re making a change that affects only one module in a large
program, you can recompile only that one module, then relink all the object files again
to make the executable.

Susan: How do you do that?

Steve: It varies according to what compiler you’re using. With the compiler on the
CD in the back of the book, you specify source files and object files in the command
line that you use to run the compiler. The compiler will compile the source files and
then link them with the object files.

6.4. The StockItem class

Now let’s start on our first class definition, which is designed to help
solve the problem of maintaining inventory in a small grocery store.
We need to keep track of all the items that we carry, so we’re going to
define a class called StockItem . The StockItem class , like other classes , is
composed of a number of functions and variables. As I suggested
earlier, to make this more concrete, think of something like Lego™
blocks, which you can put together to make parts that can in turn be
used to build bigger structures. The smallest Legos are the native
types, and the bigger, composite ones are class types.
For the compiler to be able to define an object correctly, we’ll
have to tell it the names and types of the member variables that will
be used to store the information about each StockItem ; otherwise, it
wouldn’t know how much memory to allocate for a StockItem object.
To figure out what member variables a StockItem needs, we must
consider what information it will require to keep track of its
corresponding item in the stock of the store. After some thought, I’ve
come up with the following list of member variables:
1. The name of the item ( m_Name ),
2. The number in stock ( m_InStock ),
3. The distributor that we purchase it from ( m_Distributor ),
4. The price we charge ( m_Price ), and
5. The item number, or UPC ( m_UPC ).

What I mean by “item” is actually something like "chunky chicken


soup, 16 oz.", rather than a specific object like a particular can of
soup. In other words, every can of soup with the same item number is
considered equivalent to every other can of soup with the same item
number, so all we have to keep track of for each item can be described
by the above data. For the item number, we’ll use the Universal
Product Code (UPC), which is printed as a bar code on almost every
product other than fresh produce; it’s a 10-digit number, which we’ll
represent as a string for convenience.
Susan took me to task about the notion of a StockItem object vs. a
specific object like a particular can of soup:

Susan: When you say "rather than a specific object", how much more specific can
you get than "chunky chicken soup, 16 oz."?

Steve: Each can of chunky chicken soup is at least slightly different from every
other one; at least they are in different places.

Let’s recap what we know about a StockItem so far. We need a member


variable in the StockItem class definition for each value in the above
description: the name of the item ( m_Name ), its price
( m_Price ), the number of items in stock ( m_InStock ), the name of the
distributor ( m_Distributor ), and the UPC ( m_UPC ) of the item. Of
course, merely storing these data isn’t very useful unless we can do
something with them. Therefore, objects of the StockItem class also
need to be able to perform several operations on their data; we’ll start
by giving them the ability to display their contents. Figure 6.1
illustrates a very simple way that this class might be used.
FIGURE 6.1.The initial sample program for the StockItem class
(code\itemtst1.cpp)

#include <iostream>
#include <string>
#include “item1.h” using
namespace std;

int main()
{
StockItem soup;

soup = StockItem(“Chunky Chicken”,32,129, “Bob’s


Distribution”,”123456789”);

soup.Display();

return 0;
}

This program defines a StockItem named soup with no initial data


specified, tells it to assign itself some data, asks it to display itself via
a function called Display , and finally terminates normally. By the time
we’re done with this chapter, you’ll understand exactly how every
operation in this program is performed by the StockItem class . Before we
get too deeply into this particular class , however, we should look at
the functions that almost all classes have in common. First, let’s define
some more terms we’ll need for the discussion.
6.5. More Definitions

A concrete data type is a class whose objects behave like variables


of native data types. That is, the class gives the compiler enough
information that its objects can be created, copied, assigned, and
automatically destroyed just as native variables are. The StockItem class
that we will construct in this chapter is a concrete data type.

A constructor is a member function that creates new variables of the


class to which it belongs. All constructors have the same name as the
class for which they are constructors; therefore, the constructors for
StockItem variables also have the name StockItem .

A default constructor is a constructor that is used when no initial


value is specified for an object. Because it is a constructor, it has the
same name as the class ; since it is used when no initial value is
specified, it has no arguments. Thus, StockItem() is the default
constructor for the StockItem class .

A copy constructor is a constructor that makes a new object with the


same contents as an existing object of the same type.

An assignment operator is a member function that sets a pre- existing


object to the same value as another object of the same type.

A destructor is a member function that cleans up when an object


expires; for a local object, this occurs at the end of the block where
that object is defined.

The object member access operator is “ . ” (period). It separates an


object name, on its left, from the member variable or member function
on its right.
:: ,
when used immediately after a class name, is the class
membership operator, which indicates the class a variable or function
belongs to.7

6.6. Concrete Data Types

While different classes vary considerably in the facilities that they


provide, there are significant benefits to a class whose objects behave
like those of native types. As I’ve just mentioned, such a class is
called a concrete data type. To make a class a concrete data type, we
must define certain member functions that allow creation, copying, and
deletion to behave as with a native variable.
Susan wanted to see a chart illustrating the correspondence
between what the compiler does for a native type and what we have to
do to make a type a concrete data type. Of course, I complied with her
request (see Figure 6.2).
Because the member functions listed in that chart are so
fundamental to the proper operation of a class , the compiler will
generate a version of each of them for us if we don’t write them
ourselves, just as the corresponding behavior is automatically
supplied for the native types. As we will see in Chapter 7, the
compiler-generated functions are generally too simplistic to be used in
a complex class . In such a case we need to create our own versions of
these functions and I’ll illustrate how to do that at the appropriate
time. However, with a simple class such as the one we’re creating
here, the compiler-generated versions of the assignment operator, copy
constructor, and destructor are perfectly adequate, so we won’t be
creating our own versions of these functions for StockItem .

7. :: is the scope resolution operator when it does not follow a class or namesp ace
name. This is another example of the confusing tendency of C++ to reuse the
same sequence of characters to mean something different.
FIGURE 6.2. Comparison between
native and user-defined types

The Native Problem A Concrete Plan

Here are the essential facilities that the compiler provides for every native type:

To make a concrete data type, we have to provide each of these facilities for our new
type. By no coincidence, there is a specific type of member function to provide each of
them. Here are the official names and descriptions of each of these four functions:

1. The ability to create a variable with no specified initial value, e.g., short x; .

1. A default constructor that can create an object when there is no initial value specified
for the object, e.g., “ StockItem x; ”

2. The ability to pass a variable as an argument to a function; in this case, the compiler has to make a copy of the variable so that
the called function doesn’t change the value of the variable in the calling function.

2. A copy constructor that can make a new object with the same contents as an existing
object of the same type, to allow arguments of that type to be passed by value.

3. The ability to assign a value of an appropriate type to an existing variable such as x = 22; or x = z; .

3. An assignment operator that is used to set an existing object to the value of another
object of the same type, e.g., “ x = y; ”.

4. Reclaiming the storage assigned to a variable when it ceases to exist, so that those memory addresses can be reallocated to
other uses. In the case of auto variables, this is at the end of the block where they were created; with static variables, it’s at the
end of execution of the program.

4. A destructor that cleans up when an object ceases to exist, including releasing the
memory that the object has occupied. For an auto object, this occurs at the end of the
block where the object was created; with static variables, it’s at the end of execution of
the program.
Susan was a bit confused about the distinction between the
compiler-generated versions of these essential functions and the
compiler’s built-in knowledge of the native types:

Susan: Aren’t the compiler-generated versions the same thing as the native
versions?

Steve: No, they’re analogous but not the same. The compiler- generated
functions are created only for objects, not for native types. The behavior of the
native types is implemented directly in the compiler, not by means of functions.

Susan: I’m confused. Maybe it would help if you explained what you mean by
"implemented directly in the compiler". Are you just saying that objects are
implemented only by functions, whereas the native types are implemented by the
built-in facilities of the compiler?

Steve: You’re not confused, you’re correct.

Susan: OK, here we go again. About the assignment operator, what is this
"version"? I thought you said earlier that if you don’t write your own assignment
operator it will use the native operator. So I don’t get this.

Steve: There is no native assignment operator for any class type; instead, the
compiler will generate an assignment operator for a class if we don’t do it
ourselves.

Susan: Then how can the compiler create an assignment operator if it doesn’t
know what it is doing?

Steve: All the compiler-generated assignment operator does is to copy all of the
members of the right-hand variable to the left-hand variable. This is good enough
with the StockItem class . We’ll see in Chapter 7 why this isn’t always acceptable.
Susan: Isn’t a simple copy all that the native assignment operator does?

Steve: The only native assignment operators that exist are for native types. Once
we define our own types, the compiler has to generate assignment operators for us if
we don’t do it ourselves; otherwise, it would be impossible to copy the value of one
variable of a class type to another without writing an assignment operator explicitly.

Susan: OK, this is what confused me, I just thought that the native functions
would be used as a default if we didn’t define our own in the class type, even
though they would not work well.

Steve: There aren’t any native functions that work on user-defined types. That’s
why the compiler has to generate them when necessary. But I think we have a
semantic problem here, not a substantive one.

Susan: Why doesn’t it default to the native assignment operator if it doesn’t have
any other information to direct it to make a class type operator? This is most
distressing to me.

Steve: There isn’t any native assignment operator for a StockItem . How could
there be? The compiler has never heard of a StockItem until we define that class .

Susan: So it would be a third type of assignment operator. At this point, I am


aware of the native type, the user-defined type and a compiler-generated type.

Steve: Right. The native type is built into the compiler, the user- defined type is
defined by the user, and the compiler-generated type is created by the compiler for
user-defined types where the user didn’t define his own.
Susan: Then the native and the compiler-generated assignment operator are the
same? If so, why did you agree with me that there must be three different types of
assignment operators? In that case there would really only be two.

Steve: No, there is a difference. Here is the rundown:

1.(Native assignment) The knowledge of how to assign values of every native type is
built into the compiler; whenever such an assignment is needed, the compiler emits
prewritten code that copies the value from the source variable to the destination variable.
2. (Compiler-generated assignment) The knowledge of how to create a default
assignment operator for any class type is built into the compiler; if we don’t define an
assignment operator for a given class , the compiler generates code for an assignment
operator that merely copies all of the members of the source variable to the destination
variable. Note that this is slightly different from 1, where the compiler copies canned
instructions directly into the object file whenever the assignment is done; here, it
generates an assignment operator for the specific class in question and then uses that
operator whenever an assignment is done.
3. (User-defined assignment) This does exactly what we define it to do.

Susan: Did you ever discuss the source variable and the destination variable? I
don’t recall that concept in past discussions. I like this. All I remember is when you
said that = means to set the variable on the left to the value on the right. Does this
mean that the variable on the left is the destination variable and the value on the right
is the source variable?

Steve: Yes, if the value on the right is a variable; it could also be an expression
such as " x + 2 ".

Susan: But how could it be a variable if it is a known value?


Steve: It’s not its value that is known, but its name. Its value can vary at run time,
depending on how the program has executed up till this point.

Susan: So the main difference is that in 1 the instructions are already there to be
used. In 2 the instructions for the assignment operator have to be generated before
they can be used.

Steve: That’s a good explanation.

After my explanation of the advantages of a concrete data type, Susan


became completely convinced, so much so that she wondered why we
would ever want anything else.

Susan: On your definition for concrete data types. . . this is fine, but what I am
thinking is that if something wasn’t a concrete data type, then it wouldn’t work, that
is unless it was native. So what would a workable alternative to a concrete data type
be?

Steve: Usually, we do want our objects to be concrete data types. However, there
are times when, say, we don’t want to copy a given object. For example, in the case
of an object representing a window on the screen, copying such an object might
cause another window to be displayed, which is probably not what we would want to
happen.

Susan: OK, so what would you call an object that isn’t of a concrete data type?

Steve: There’s no special name for an object that isn’t of a concrete data type.

Susan: So things that are not of a concrete data type have no names?

Steve: No, they have names; I was just saying that there’s no term like non-
concrete data type, meaning one that doesn’t act like a
native variable. There is a term abstract data type, but that means something else.

Susan: See, this is where I am still not clear. Again, if something is


not a concrete data type, then what is it?

Steve: What is the term for a person who is not a programmer? There isn’t any
special term for such a person. Similarly, there’s no special term for a class that
doesn’t act like a native variable type. If something isn’t a concrete data type, then
you can’t treat it like a native variable. Either you can’t copy it, or you can’t assign to
it, or you can’t construct it by default, or it doesn’t go away automatically at the end
of the function where it is defined (or some combination of these). The lack of any of
those features prevents a class from being a concrete data type.

Susan: Of what use would it be to have a class of a non-concrete data type? To


me, it just sounds like an error.

Steve: Sometimes it does make sense. For example, you might want to create a
class that has no default constructor; to create an element of such a class , you
would have to supply one or more arguments. This is useful in preventing the use of
an object that doesn’t have any meaningful content; however, the lack of a default
constructor does restrict the applicability of such a class , so it’s best to provide such
a constructor if possible.

Before we can implement the member functions for our StockItem class ,
we have to define what a StockItem is in more detail than my previous
sketch.8 Let’s start with the initial version of the interface

8. By the way, in using a reasonably functional clas s such as St ockIt em to


illustrate these concepts, I’m violating a venerable tradition in C++ tutorials.
Normally, example clas s es represent zoo animals, or shapes, or something
equally useful in common programming situations.
specification for that class (Figure 6.3), which includes the
specification of the default constructor, the Display function, and
another constructor that is specific to the StockItem class .
I strongly recommend that you print out the files that contain this
interface and its implementation (as well as the test program) for
reference as you are going through this part of the chapter. Those files
are item1.h , item1.cpp , and itemtst1.cpp , respectively.

FIGURE 6.3. The initial interface of


the StockItem class (code\item1.h)

class StockItem
{
public:
StockItem();

StockItem(std::string Name, short InStock, short Price, std::string Distributor,


std::string UPC);
void Display();
private:
short m_InStock;
short m_Price; std::string
m_Name; std::string
m_Distributor; std::string
m_UPC;
};

Your first reaction is probably something like "What a bunch of


malarkey!" Let’s take it a little at a time, and you’ll see that this
seeming gibberish actually has a rhyme and reason to it. First we have
the line class StockItem . This tells the compiler that what follows is the
definition of a class interface, which as we have already seen is a
description of the operations that can be performed on objects of a
given user-defined type; in this case, the type is
StockItem . So that the compiler knows where this description begins and
ends, it is enclosed in {} , just like any other block of information
that is to be treated as one item.9
After the opening { , the next line says public: . This is a new type of
declaration called an access specifier, which tells the compiler the
"security classification" of the item(s) following it, up to the next
access specifier. This particular access specifier, public , means that
any part of the program, regardless of whether it is defined in this
class , can use the items starting immediately after the public declaration
and continuing until there is another access specifier. In the current
case, all of the items following the public specifier are operations that
we wish to perform on StockItem objects. Since they are public , we can
use them anywhere in our programs. You may be wondering why
everything isn’t public ; why should we prevent ourselves (or users of
our classes ) from using everything in the classes ? It’s not just
hardheartedness; it’s actually a way of improving the reliability and
flexibility of our software, as I’ll explain later.
As you might imagine, this notion of access specifiers didn’t get
past Susan without a serious discussion. Here’s the play-by-play
account.

Susan: So, is public a word that is used often or is it just something you made up
for this example?

Steve: It’s a keyword of the C++ language, which has intrinsic meaning to the
compiler. In this context, it means "any function, inside or outside this class , can
access the following stuff, up to the next access specifier (if any)". Because it is a
keyword, you can’t have a variable named public , just as you can’t have one
named if .

9. However, there is one oddity about the declaration of a class when compared
with other blocks: you need the “;” at the end of the block, after the closing “}”,
which isn’t necessary for most other blocks. This is a leftover from C, as are
many of the quirks of C++.
Susan: These access specifiers: What are they, anyway? Are they always used in
classes ?

Steve: Yes.

Susan: Why aren’t they needed for native variables?

Steve: Because you can’t affect the implementation of native types; their
internals are all predefined in the compiler.

Susan: What does internals mean? Do you mean stuff that is done by the
compiler rather than stuff that can be done by the programmer?

Steve: Yes, in the case of native data types. In the case of class types,
internals means the details of implementation of the type rather than what it does
for the user.

Susan: You know, I understand what you are saying about internals; that is, I
know what the words mean, but I just can’t picture what you are doing when you
say implementation. I don’t see what is actually happening at this point.

Steve: The implementation of a class is the code that is responsible for actually
doing the things that the interface says the objects of the class can do. All of the
code in the item1.cpp file is part of the implementation of StockItem . In addition,
the private member variables in the header file are logically part of the
implementation, since the user of the class can’t access them directly.

Susan: Why is a class function called a member function? I like


class function better; it is more intuitive.

Steve: Sorry, I didn’t make up the terminology. However, I think member


function is actually more descriptive, because these functions are members (parts)
of the objects of the class .
Susan: So on these variables, that m_ stuff; do you just do that to differentiate
them from a native variable? If so, why would there be a confusion, since you have
already told the compiler you are defining a class ? Therefore, all that is in that
class should already be understood to be in the class rather than the native
language. I don’t like to look at that m_ stuff; it’s too cryptic.

Steve: It’s true that the compiler can tell whether a variable is a member variable
or a global variable. However, it can still be useful to give a different name to a
member variable so that the programmer can tell which is which. Remember, a
member variable looks like a global variable in a class implementation, because you
don’t declare it as you would an argument or a local variable.

Now we’re up to the line that says StockItem(); . This is the declaration
for a function called a constructor, which tells the compiler what to
do when we define a variable of a user-defined type. This particular
constructor is the default constructor for the StockItem class . It’s called
the "default" constructor because it is used when no initial value is
specified by the user; the empty parentheses after the name of the
function indicate the lack of arguments to the function. The name of the
function is the clue that it’s a constructor. The name of a constructor is
always the same as the name of the class for which it’s a constructor, to
make it easier for the compiler to identify constructors among all of
the possible functions in a class .
This idea of having variables and functions "inside" objects
wasn’t intuitively obvious to Susan:

Susan: Now, where you talk about mixing a string and a short in the same
function, can this not be done in the native language?

Steve: It’s not in the same function but in the same variable. We are creating a
user-defined variable that can be used just like a native variable.
Susan: OK, so you have a class StockItem . And it has a function called
StockItem . But a StockItem is a variable, so in this respect a function can be
inside a variable?

Steve: Correct. A StockItem is a variable that is composed of a number of


functions and other variables.

Susan: OK, I think I am seeing the big picture now. But you know that this seems
like such a departure from what I thought was going on before, where we used
native types in functions rather than the other way around. Like when I wrote my
little program, it would have shorts in it but they would be in the function main . So
this is a complete turnabout from the way I used to think about them; this is hard.

Steve: Yes, that is a difficult transition to make. Interestingly enough, experience


isn’t necessarily an advantage here; you haven’t had as much trouble with it as some
professional programmers who have a lot more experience in writing functions as
"stand-alone" things with no intrinsic ties to data structures. However, it is one of the
essentials in object-oriented programming; most functions live "inside" objects and do
the bidding of those objects, rather than being wild and free.

Why do we need to write our own default constructor? Well, although


we have already specified the member variables used by the class so
that the compiler can assign storage as with any other static or auto
variable, that isn’t enough information for the compiler to know how
to initialize the objects of the class correctly.10 Unlike a native
variable, the compiler can’t set a newly created StockItem to a
reasonable value, since it doesn’t understand what the member
variables of a StockItem are used for. That is, it can’t do the
initialization without help from us. In the code for our default
constructor, we will initialize the member variables to legitimate
values so that we don’t have to worry about having an uninitialized
StockItemlying around as we did with a short in a previous example.
Figure 6.4 shows what the code to our first default constructor looks
like.

FIGURE 6.4. The default constructor for the StockItem class (from code\item1.cpp)

StockItem::StockItem()
: m_Name(), m_InStock(0), m_Price(0), m_Distributor(), m_UPC()
{
}

Let’s use this example of a StockItem class to illuminate the distinction


between interface and implementation. As I’ve already mentioned, the
implementation of a class is the code that is responsible for actually
doing the things promised by the interface of that class . The interface
was laid out in Figure 6.3. With the exception of the different versions
of the test program that illustrates the use of the StockItem class , all of
the code that we will examine in this chapter is part of the
implementation. This includes the constructors and the Display member
function.
So you can keep track of where this fits into the "big picture", the
code in Figure 6.4 is the implementation of the function
StockItem::StockItem() (i.e., the default constructor for the class StockItem ),
whose interface was defined in Figure 6.3. Now, how does it work?
Actually, this function isn’t all that different from a "regular" function,
but there are some important differences. First of all, the name looks
sort of funny: Why is StockItem repeated?
10. In case it isn’t obvious how the compiler can figure out the size of the object,
consider that the class definition specifies all of the variables that are used to
implement the objects of the class . When we define a new class, the types of
all of the member variables of the class must already be defined. Therefore, the
compiler can calculate the size of our class variables based on the sizes of those
member variables. By the way, the size of our object isn’t necessarily the sum
of the sizes of its member variables; the compiler often has to add some other
information to the objects of a class besides the member variables. We’ll see
one of the reasons for this later in this book.
The answer is that, unlike "regular" (technically, global) functions,
a member function always belongs to a particular class . That is, such
a function has special access to the data and other functions in the
class , and vice versa. To mark its membership, its name consists of the
name of the class (in this case, StockItem ), followed by the class
membership operator :: , followed by the name of the function (which
in this case, is also StockItem ); as we have already seen, the name of a
constructor is always the same as the name of its class . Figure 6.5
shows how each component of the function declaration contributes to
the whole.

FIGURE 6.5. Declaring the default


constructor for the StockItem class

This function belongs to the StockItem class

ctor,
because its name is the same as he
class

and it has no arguments

(i.e., it is the default con-


structor for its class)

If you’ve really been paying attention, there’s one thing that you may
have noticed about this declaration as compared with the original
declaration of this function in the class interface definition for StockItem
(Figure 6.3). In that figure, we declared this same function as
StockItem(); , without the additional StockItem:: on the front.11 Why didn’t
we need to use the StockItem:: class membership notation in the class
interface definition? Because inside the declaration of a class , we
don’t have to specify what class the member functions belong to; by
definition, they belong to the class we’re defining.
11. By the way, spaces between components of the name aren’t significant; that is,
we can leave them out as in Figure 6.4, or include them as in Figure 6.5.
Thus, StockItem() in the class interface declaration means “the member
function StockItem , having no arguments”; i.e., the default constructor
for the StockItem class .
Susan didn’t have any trouble with this point, which was quite a
relief to me:

Susan: Oh, so you don’t have to write StockItem::StockItem in the interface


definition because it is implied by the class StockItem declaration?

Steve: Right.

Now let’s look at the part of the constructor that initializes the member
variables of the StockItem class , the member initialization list. The start
of a member initialization list is signified by a : after the closing “ ) ” of
the constructor declaration, and the expressions in the list are
separated by commas. A member initialization list can be used only
with constructors, not any other type of functions.
The member initialization list of the default StockItem constructor is:
: m_InStock(0), m_Price(0), m_Name(), m_Distributor(), m_UPC() . What does this
mean exactly? Well, as its name indicates, it is a list of member
initialization expressions, each of which initializes one member
variable. In the case of a member variable of a native type such as
short , a member initialization expression is equivalent to creating the
variable with the initial value specified in the parentheses. In the case
of a member variable of a class type, a member initialization
expression is equivalent to creating the variable by calling the
constructor that matches the type(s) of argument(s) specified in the
parentheses, or the default constructor if there are no arguments
specified. So the expression m_InStock(0) is equivalent to the creation
and simultaneous initialization of a local variable by the statement short
m_InStock = 0; . Similarly , the expression m_Name() is equivalent to the
creation and simultaneous initialization of a local variable by the
statement string m_Name; . Such a statement, of
course, would initialize the string m_Name to the default value for a
string , which happens to be the empty C string literal "".
Using a member initialization list is the best way to set up member
variables in a constructor, for two reasons. First, it’s more efficient
than using assignment statements to set the values of member
variables. For example, suppose that we were to write this constructor
as shown in Figure 6.6.
FIGURE 6.6. Another way to write
the default StockItem constructor

StockItem::StockItem()
{
m_InStock = 0;
m_Price = 0; m_Name
= ""; m_Distributor = "";
m_UPC = "";
}

If we wrote the constructor that way, before we got to the opening “ { ”


of the constructor, all of the member variables that had constructors
(here, the strings ) would be initialized to their default values. After the
“ { ”, they would be set to the values we specified in the code for the
constructor. It’s true that we could solve this problem in this specific
example by simply not initializing the strings at all, as that would mean
that they would be initialized to their default values anyway; but that
solution wouldn’t apply in other constructors such as the one in Figure
6.7, where the member variables have specified values rather than
default ones.
The second reason that we should use a member initialization list
to initialize our member variables is that some member “variables”
aren’t variables at all but constants. We’ll see how to define consts , as
they are called in C++, in a later chapter. For now, it’s enough to know
that you can’t assign a value to a const , but you can (and indeed have to)
initialize it; therefore, when dealing with member consts , a member
initialization list isn’t just a good idea, it’s the law.
There is one fine point that isn’t obvious from looking at the code
for this constructor: The expressions in a member initialization list are
executed in the order in which the member variables being initialized
are declared in the class definition, which is not necessarily the order
in which the expressions appear in the list. In our example, since
m_InStock appears before m_Name in the class definition, the member
initialization expression for m_InStock will be executed before the
expression initializing m_Name . This doesn’t matter right now, but it
will be important in Chapter 7, where we will be using initialization
expressions whose order of execution is important.
You may have noticed that the body of the function (the part inside
the {} ) shown in Figure 6.4 is empty, because all of the work has
already been done by the member initialization list. This is fairly
common when writing constructors, but not universal; as we’ll see in
Chapter 7, sometimes a constructor has to do something other than
initialize member variables, in which case we need some code inside
the {} .
Susan objected to my cavalier use of the empty C string literal "":

Susan: Excuse me, but what kind of value is " " ? Do you know how annoying it is
to keep working with nothing?

Steve: It’s not " ", but "". The former has a space between the quotes and the
latter does not; the former is a one-character C string literal consisting of one space,
while the latter is a zero-character C string literal.

Susan: OK, so "" is an empty C string literal, but could you please explain how
this works?

Steve: The "" means that we have a C string literal with no data in it. The
compiler generates a C string literal consisting of just the terminating null byte.

Susan: What good does that do? I don’t get it.


Steve: Well, a string has to have some value for its char* to point to; if we
don’t have any real data, then using an empty C string literal for that purpose is
analogous to setting a numeric value to 0.

Susan: OK, so this is only setting the strings in the default constructor to a value
that the compiler can understand so you don’t get an error message, although there is
no real data. We’re trying to fool the compiler, right?

Steve: Close, but not quite. Basically, we want to make sure that we know the
state of the strings in a default StockItem . We don’t want to have trouble with
uninitialized variables; remember how much trouble they can cause?

Susan: Yes, I remember. So this is just the way to initialize a


string when you don’t know what real value it will end up having?

Steve: Yes, that’s how we’re using it here.

Now let’s get back to the member variables of StockItem . One


important characteristic of any variable is its scope, so we should pay
attention to the scope of these variables. In Chapter 5, we saw two
scopes in which a variable could be defined: local (i.e., available only
within the block where it was defined) and global (i.e., available
anywhere in the program). Well, these variables aren’t arguments
(which have local scope) since they don’t appear in the function’s
header. On the other hand, they aren’t defined in the function;
therefore, they aren’t local variables. Surely they can’t be global
variables, after I showed you how treacherous those can be.
6.7. The class Scope

I haven’t misled you on that point; there is another scope called class
scope, which applies to all member variables of a class . Variables
with class scope occupy separate memory locations for each object;
i.e., each object has its own separate set of member variables distinct
from the member variables of any other objects.12 In the case of
StockItem , this set of member variables consists of m_InStock , m_Price ,
m_Name , m_Distributor , and m_UPC . Member functions of a class can
access member variables of objects of that class without defining
them, as though they were global variables.
In addition to scope, each member variable has another attribute
we have already encountered: an access specifier. The access of
nonmember functions to any member variable or member function
depends on the access specifier in effect when the member variable or
function was declared. If you look back at Figure 6.3, you’ll see that
the line private: precedes the declaration of the member variables in the
StockItem class . The keyword private is an access specifier, like public ;
however, where a public access specifier allows any function to access
the items that follow it, a private access specifier allows only member
functions to access items that follow it.
Susan had some more questions about access specifiers, including
this new one, private :

Susan: It seems to me that the access specifiers act more like scope than
anything. Are they about the same?

Steve: Yes, the difference between public and private is somewhat analogous
to the difference between global and local variables, but the latter distinction affects
where a variable is stored and when it is initialized, whereas an access specifier
controls what functions can
12. Actually, I’m describing "normal" member variables here. There is another
kind of member variable, called a static member variable, which is shared
among all objects of a given class . We won’t be using this type of member
variable in this book.
access the variable. However, because member variables are defined inside
classes , they can’t be global, nor can they be local in the sense that a "regular" (i.e.,
nonmember) variable can be; a member variable must always live inside a single
occurrence of an object of its class .

But although scope rules and access specifiers are similar in some ways in that they
affect where a variable can be used, they aren’t exactly the same. Scope defines
where a variable is visible, whereas access specifiers control where a variable (or
function) is accessible. That is, if you write a program that tries to read or modify a
private variable from outside the class implementation, the compiler knows what
you’re trying to do but won’t let you do it. On the other hand, if you try to access a
local variable from a function where it isn’t defined, the compiler just tells you it
never heard of that variable, which indeed it hasn’t in that context. For example, let’s
suppose that the local variable x defined in function abc has no existence in any
other function; in that case, if you try to access a variable named x in another
function, say def , where it hasn’t been defined, you’ll get an error message from
the compiler telling you that there is no variable x in function def . However, if
there is a private member variable called x defined in class ghi , and you try to access
that member variable from a nonmember function, the compiler will tell you that
you’re trying to do something illegal. It knows which x you mean, but it won’t let
you access it because you don’t have permission.

Susan: Are they necessary for every class ?

Steve: Pretty much. The default specifier for a class is private ; that is,
everything you declare in a class interface before the first explicit access specifier
is private . Of course, this also means that if you don’t ever provide an explicit
access specifier in a given class , then everything declared in that class will be
private . This isn’t usually very useful, because without any public functions it’s
hard to use a class at all.
Susan: There is a default? Then why would the default be the least useful?

Steve: To make the programmer specify what should be publicly accessible


rather than have it happen automatically. In general, it’s best to keep as much as
possible private , to reduce the dependency of external code on the internal
implementation of the class . This makes it easier to change that implementation
without causing trouble for the users of the class .

Susan: OK, that makes sense now. Are there any other kinds of access specifiers
or are these the only two?

Steve: Actually, there’s one more called protected , that is sort of in between
public and private ; we’ll start using it in Chapter 9.

Susan also wanted some more details about this new class scope.

Susan: How about explaining the difference between a class


scope and a public access specifier?

Steve: Variables declared in a class , regardless of their access specifier, have


class scope; that means that they live as long as the object that contains them. The
access specifier determines who can access these variables, but does not affect
their lifetime.

Of course, the constructor StockItem::StockItem() , by virtue of being a


member function, has access to all member variables, so the private
access specifier doesn’t apply to it. We’ll see later how that access
specifier comes into play.
Now that we know what kind of variables the StockItem::StockItem()
function deals with, its behavior isn’t very mysterious: it simply
initializes the member variables to 0 or the default string value (""),
whichever is appropriate to their types. That’s all very well, but it
doesn’t answer a very important question: What exactly do these
member variables do? The answer is that they
don’t do anything by themselves; rather, they are the "raw material" the
member functions use to implement the behavior that we want a
StockItem to display. If you recall the discussion of interface vs.
implementation, then you’ll appreciate that the private member
variables are also essentially part of the implementation and not part
of the interface: even though they are defined in the header file, the
user of the class can’t access them directly.
That’s why we call variables that are declared inside the class
definition member variables and functions that are declared inside the
class definition member functions; they "belong" to the class that we’re
defining. The member functions set, change, and use the values of the
member variables in the course of implementing the behaviors that the
StockItem class interface definition promises.13
Susan wasn’t buying all this malarkey about member variables
without some further explanation. Here’s how the discussion went:

Susan: What do m_InStock and m_Price and the others actually do? It seems
we are missing a verb here.

Steve: They don’t do anything by themselves. They are the member variables
used to store the count and price of the goods described by a StockItem object,
respectively. In the default constructor, they are both set to 0, indicating a StockItem
with no content. This is the equivalent of the value 0 that is used to initialize statically
allocated numeric variables and is used in the same way; that is, any StockItem
that is created without a value is set to this empty state. Notice that this doesn’t
apply only to statically allocated StockItems , but to all StockItems ; this is an
example where a user-defined type is superior to a native type. That is, we don’t
have to worry about having an uninitialized StockItem ,

13. The special status of member functions and variables as implementation aids
explains why access specifiers such as p ublic are applicable only to data or
functions declared in a class , since the purpose of access specifiers is to control
"outside" access to variables and functions used to implement a class . You can’t
apply access specifiers to native types, because the way these types are
implemented is not accessible to the programmer.
because the default constructor ensures that every StockItem is set to a known
value when it is created.

Susan: Ugh. Don’t remind me about uninitialized variables. Okay, that makes
more sense now.

So much for the "high-altitude" description of what a class does. Now


let’s get back to the details that make it work, starting with a little
puzzle: figuring out where the StockItem::StockItem() function is used in
the test program in Figure 6.1 on page 307. Believe it or not, this
constructor is actually used in that program; to be exact, the line
StockItem soup; calls it. Remember that the basic idea of constructing a
class is to add data types to the language that aren’t available "out of
the box". One of the functions that we have to help the compiler with
is initialization; a main purpose for the StockItem::StockItem() constructor
is to initialize variables of the StockItem type that aren’t explicitly
initialized. That’s why it’s called a default constructor.
Susan didn’t immediately cotton to the idea of calling a default
constructor by simply defining a variable of that class .

Susan: Sure, defining an object is simple if you don’t lose your mind defining the
classes first.

Steve: It is simple for the application programmer (the user of the class ). We’re
doing the hard part so he can just use the objects without having to worry about any
of this stuff.

Susan: Huh? Isn’t the "user of the class " always the same as the "writer of the
class "?

Steve: Not necessarily. You’ve been using strings (and Vecs , for that matter)
for some time now without having to be concerned about how they work. This is not
unusual.

Susan: Yeah, but if you are a programmer you will be a class


writer, not just a user.
Steve: Probably not with respect to all classes . You may very well write your
own application-specific classes but use existing ones for all of the low-level stuff
like Vecs, strings , etc.

That’s one reason why we separate interfaces and implementations: so that not
everyone who uses our classes has to know exactly how they are implemented.

You should generally write a default constructor for every class you
define, to guarantee the state of any "default constructed" variable. If
you don’t declare a default constructor, the compiler will supply one
for you; however, since it doesn’t know much about your class , it
won’t be able to guarantee very much about the initial state of one of
your variables. In fact, all the native types will be left in a random
state, as though they were declared but not initialized; this is an
undesirable condition, as we’ve already seen in another context. The
moral is that you should define your own default constructor. As you
can see from our example, it’s not much work.
So why did I say "generally", rather than "always"? Because there
are some times when you don’t want to allow an object to be created
unless the "real" data for it are available. As with the copy
constructor, the compiler will generate a default constructor for you
automatically if you don’t declare one yourself. If you want to make it
impossible to create an object via that compiler-generated default
constructor, you can declare a private default constructor that will
cause a compiler error in any user code that tries to define an object of
that class without specifying an initial value. Yo u don’t have to
implement this constructor, because a program that tries to use it won’t
compile.
Susan thought that the idea of having to define a default constructor
for each class was a bit off the wall.

Susan: When you say that "you should define one of these (default constructors)
for every class you define..." my question is how? What are you talking about? I
thought a default meant just that, it
was a default, you don’t have to do anything with it, it is set to a preassigned value.

Steve: It’s true that the class user doesn’t have to do anything with the default
constructors. However, the class writer (that’s us, in this case) has to define the
default constructor so that when the class user defines an object without initializing
it, the new object has a reasonable state for an "empty" object. This prevents
problems like those caused by uninitialized variables of native types.

6.8. More about the StockItem class Interface

Now let’s continue with our analysis of the class interface for the
StockItem class (Figure 6.3 on page 316). Before we can do anything with
a StockItem object, we have to enter the inventory data for that object.
This means that we need another constructor that actually sets the
values into a StockItem . We also need some way to display the data for
a StockItem on the screen, which means writing a Display function.
The next line of that figure is the declaration of the “normal”
constructor that creates an object with actual data:
StockItem(std::string Name, short InStock, short Price, std::string Distributor, std::string UPC);

We can tell that this function is a constructor because its name,


StockItem , is the same as the name of the class . One interesting thing
about this constructor is that its name is the same as the previous
constructor: namely, StockItem. This is a very handy facility, called
function overloading, which isn’t limited to constructors. The
combination of the function name and argument types is called the
signature of a function; two functions that have the same name but
differ in the type of at least one argument are distinct functions.14 In the
case of the default constructor, there are no arguments, so that
constructor is used where no initial data are specified for the object.
The statement StockItem soup; in the sample program (Figure 6.1) fits that
description, so the default constructor is used. However, in the next
statement of the sample program, we have the expression:
StockItem("Chunky Chicken", 32, 129, "Bob’s Distribution", "123456789");

This is clearly a call to a constructor, because the name of the function


is the name of a class , StockItem . Therefore, the compiler looks for a
constructor that can handle the set of arguments in this call and finds
the following declaration:
StockItem(std::string Name, short InStock, short Price, std::string Distributor, std::string UPC);

The first, fourth, and fifth arguments to the constructor are strings ,
while the second and third are shorts . Since these types all match those
specified in the expression in the sample program, the compiler can
translate that expression into a call to this constructor.
Figure 6.7 shows the code for that constructor.

FIGURE 6.7. Another constructor for the StockItem class (from code\item1.cpp)

StockItem::StockItem(string Name, short InStock, short Price, string


Distributor, string UPC)
: m_Name(Name), m_InStock(InStock), m_Price(Price), m_Distributor(Distributor),
m_UPC(UPC)
{
}

14. Note that the names of the arguments are not part of the signature; in fact, you
don’t have to specify them in the function declaration. However, you should use
meaningful argument names in the function declaration so the user of the
function has some idea what the arguments to the function might represent.
After all, the declaration StockItem(string, short, short, string, s t r in g); doesn’t
provide much information on what its arguments actually mean.
As you can see, nothing about this constructor is terribly complex; it
merely uses the member initialization list to set the member variables
of the object being constructed to the values of their corresponding
arguments.

Referring to Standard Library Identifiers in Header Files

Before we get to the implementation of this function, there’s one thing


in this header file that we haven’t seen before in real code, although
I’ve mentioned it in passing: the use of the standard library namespace
specifier std as it is used in the declaration:
StockItem(std::string Name, short InStock, short Price, std::string Distributor, std::string UPC);

What does std::string mean? It notifies the compiler that the name string
refers to a name from the standard library. Until this point, whenever
we needed to refer to identifiers from the standard library, we have
either given the compiler blanket permission to import all the names
from the standard library into the current namespace that we are using,
or to import specific names from the standard library into the current
namespace . To give the compiler permission to import all the names in
the standard library we use the statement using namespace std; and an
example of blanket namespace permission for a specific identifier is
using std::cout; . These two ways of telling the compiler what we mean
seem to cover all of the possibilities and are much more convenient
than having to write std:: many times. So why do we need to use this
seemingly redundant method of specifying the namespace of something
from the standard library?
Because it’s a very bad idea to have any blanket namespace
permissions in a header file. That can change the behavior of an
implementation file without the knowledge of the person who wrote
that implementation file. Let’s say, for example, that someone has
made up their own string class and therefore doesn’t want to tell the
compiler to import the identifier string from the standard library. If we
wrote the line using std::string; in a header file that he included in his
implementation file, the compiler would import the identifier string
from the standard library, which would cause confusion between his
string and the standard library version. So the only sensible thing to do
is to avoid using statements in header files.
But sometimes, we do need to refer to standard library names in
our header files. In that event, we just attach std:: to the beginning of
the names of standard library identifiers and then the compiler knows
exactly what we mean.

The Need for More Than One Constructor

Now that that’s cleared up, you may be wondering why we need more
than one constructor for the StockItem class . Susan had that same
question, and I had some answers for her.

But why do we need more than one constructor? Susan had that
same question, and I had some answers for her.

Susan: How many constructors do you need to say the same thing?

Steve: They don’t say exactly the same thing. It’s true that every constructor in
the StockItem class makes a StockItem ; however, each argument list varies. The
default constructor makes an empty StockItem and therefore doesn’t need any
arguments, whereas the constructor StockItem::StockItem(string Name, short
InStock, short Price, string Distributor, string UPC) makes a StockItem with the
values specified by the Name , InStock , Price , Distributor , and UPC
arguments in the constructor call.

Susan: Are you saying that in defining a class you can have two functions that
have the same name, but they are different in only their arguments and that makes
them unique?
Steve: Exactly. This is the language feature called function overloading.

Susan: So StockItem soup; is the default constructor in case you need


something that can create uninitialized objects?

Ste ve : Not quite; the default constructor for the StockItem class is
StockItem::StockItem() , which doesn’t need any arguments, because it constructs
an empty StockItem . The line StockItem soup; causes the default constructor to
be called to create an empty StockItem named soup .

Susan: And the line StockItem("Chunky Chicken",32,129,"Bob’s


Distribution","123456789"); is a constructor that finally gets around to telling us what
we are trying to accomplish here?

Steve: Again, not quite. That line causes a StockItem with the specified
contents to be created, by calling the constructor StockItem::StockItem (string
Name, short InStock, short Price, string Distributor, string UPC); .

Susan: So are you saying that for every new StockItem you have to have a new
constructor for it?

Steve: No, there’s one constructor for each way that we can construct a
StockItem . One for situations where we don’t have any initial data (the default
constructor), one for those where we’re copying one StockItem to another (the
compiler-generated copy constructor), and one for situations where we are supplying
the data for a StockItem . There could be other ones too, but those are all we have
right now.

Once that expression has been translated, the compiler has to figure
out how to assign the result of the expression to the StockItem object
called soup , as requested in the whole statement:
soup = StockItem("Chunky Chicken", 32, 129, "Bob’s Distribution", "123456789");
Since the compiler has generated its own version of the assignment
operator = for the StockItem class , it can translate that part of the
statement as well. The result of the entire statement is that the StockItem
object named soup has the value produced by the constructor.
Finally, we have the line soup.Display(); which asks soup to display
itself on the screen. Figure 6.8 shows the code for that function.

FIGURE 6.8. Display member function for the StockItem class (from
code\item1.cpp)

void StockItem::Display()
{
cout << “Name: “;
cout << m_Name << endl; cout <<
“Number in stock: “; cout <<
m_InStock << endl; cout << “Price:
“;
cout << m_Price << endl; cout
<< “Distributor: “;
cout << m_Distributor << endl; cout <<
“UPC: “;
cout << m_UPC << endl;
}

This is also not very complicated; it just uses << to copy each of the
parts of the StockItem object to cout , along with some identifying
information that makes it easier to figure out what the values represent.
Susan wanted to know how we could use << without defining a
special version for this class .

Susan: Hey, how come you don’t have to define << as a class operator? Does
the compiler just use the native << ? And that works OK?
Steve: We’re using << only for types that the standard library already knows
about, which includes all of the native types as well as its string class . If we
wanted to apply << to a StockItem itself, we’d have to write our own version;
you’ll see how we do that when we go into our implementation of string in Chapter
7.

Susan: Then please explain to me why << is being used in Figure 6.8, which is
for the StockItem class .

Steve: It’s being used for strings and shorts , not objects of the StockItem
class . The fact that the strings and shorts are inside the StockItem class is
irrelevant in this context; they’re still strings and shorts , and therefore can be
displayed by the << operators that handle strings and shorts .

Susan: So the stuff you get out of the standard library is only for the use of class
types? Not native?

Steve: No. The iostream part of the standard library is designed to be able to
handle both native types and class types; however, the latter use requires the class
writer to do some extra work, as we’ll see when we add these functions to our
version of the string class in Chapter 8.

Susan: So that means that the library is set up to understand things we make up
for our class types?

Steve: Sort of. As long as we follow the library’s rules when creating our own
versions of << and >>, we’ll be able to use those operators to read and write our
data as though the ability to handle those types was built into the library.

That should clear up most of the potential problems with the meaning
of this Display function. However, it does contain one construct that we
haven’t seen before: void . This is the return type of the Display
function, as might be apparent from its position immediately before
the class name StockItem . But what sort of return value is a void ? In this
context, it means simply that this function doesn’t supply a return value
at all.
You won’t be surprised to learn that Susan had a few questions
about this idea of functions that return no value.

Susan: How can a function not return a value? Then what is the point? Then
would it "have no function"?

Steve: Now you’re telling programming jokes? Seriously, though, the point of
calling a function that returns no value is that it causes something to happen. The
Display function is one example; it causes the value of a StockItem object to be
displayed on the screen. Another example is a "storage function"; calling such a
function can cause it to modify the value of some piece of data it is maintaining so
when you call the corresponding "retrieval function", you’ll get back the value the
"storage function" put away. Such lasting effects of a function call (other than
returning a value) are called side effects.

Susan: But even a side effect is a change, so then it does do something after all,
right?

Steve: Sure, it does something; every function should do something, or you


wouldn’t write (or call) it. However, some functions don’t return any value to the
calling program, in which case we specify their return type as void .

That takes care of the public part of the class definition. Now what
about the private part? As I mentioned before in the discussion of how a
class is defined, the access specifier private means that only member
functions of the class can access the items after that specifier. It’s
almost always a good idea to mark all the member variables in a class
as private , for two reasons.
1. If we know that only member functions of a class can change the
values of member data, then we know where to look if the values of
the data are incorrect. This can be extremely useful when debugging
a program.
2. Marking member variables as private simplifies the task of changing
or deleting those member variables should that become necessary. If
the member variables are public , then we have no idea what functions
are relying on their values. That means that changing or deleting
these member variables can cause havoc anywhere in the system.
Allowing access only by member functions means that we can make
changes freely as long as all of the member functions are kept up to
date.

Both of these advantages of keeping member variables private can be


summed up in the term encapsulation, which means "hiding the details
inside the class implementation rather than exposing them in the
interface". This is one of the primary organizing principles that
characterizes object-oriented programming.
Now that we’ve covered all of the member functions and variables
of the StockItem class , Figure 6.9 shows the interface for the StockItem class
again. As noted previously, the test program for this class , itemtst1.cpp ,
is shown in Figure 6.1.

FIGURE 6.9. The initial interface of the StockItem class (code\item1.h)

class StockItem
{
public:
StockItem();

StockItem(std::string Name, short InStock, short Price, std::string Distributor,


std::string UPC);

void Display();

private:
short m_InStock; short
m_Price; std::string
m_Name;
std::string m_Distributor;
std::string m_UPC;
};

There’s only one more point about the member variables in the
StockItem class that needs clarification; surely the price of an object in
the store should be in dollars and cents, and yet we have only a short
to represent it. As you know by now, a short can hold only a whole
number, from –32768 to 32767. What’s going on here?
Only that I’ve decided to store the price in cents rather than
dollars and cents. That is, when someone types in a price I’ll assume
that it’s in cents, so "246" would mean 246 cents or $2.46. This would
of course not be acceptable in a real program, but for now it’s not a
problem.
This “trick” allows prices up to $327.67 (as well as negative
numbers for things like coupons), which should be acceptable for our
hypothetical grocery store. In Chapter 9, I’ll give you some tips on
using a different kind of numeric variable that can hold a greater
variety of values. For now, though, let’s stick with the short .
Figure 6.10 shows the implementation for the StockItem class .
FIGURE 6.10. The initial implementation of the StockItem class
(code\item1.cpp)

#include <iostream>
#include <string>
#include “item1.h” using
namespace std;

StockItem::StockItem()
: m_Name(), m_InStock(0), m_Price(0), m_Distributor(), m_UPC()
{
}

StockItem::StockItem(string Name, short InStock,


short Price, string Distributor, string UPC)
: m_Name(Name), m_InStock(InStock), m_Price(Price), m_Distributor(Distributor),
m_UPC(UPC)
{
}

void StockItem::Display()
{
cout << “Name: “;
cout << m_Name << endl; cout <<
“Number in stock: “; cout <<
m_InStock << endl; cout <<
“Price: “;
cout << m_Price << endl; cout
<< “Distributor: “;
cout << m_Distributor << endl; cout
<< “UPC: “;
cout << m_UPC << endl;
}

Susan had a few questions about the way that we specified the header
files in this program:

Susan: How come the #include “ item1.h” are in "" instead of <> ? I thought all
header files are in <> ?

Steve: Putting an include file name in "" tells the compiler to start looking for the
file in the current directory and then search the "standard places". Using the <> tells
the compiler to skip the current directory and start with the "standard places"; that is,
the places where the standard library header files are stored. What the "standard
places" are depends on the compiler.

Susan: I know I have not been the most focused test reader in the last week, but
did I miss something here? Did you explain way back in an earlier chapter and I just
forgot? I want to make sure that you have explained this in the book, and even if you
had earlier it would not be a bad idea to remind the reader about this.
Steve: Yes, as a matter of fact I did mention it briefly, in a footnote on page 164,
but I’m not surprised that you don’t recall reading it. Actually, you’ve been quite
focused, although not in quite the same area.

Assuming that you’ve installed the software from the CD in the back of
this book, you can try out this program. First, you have to compile it by
following the compilation instructions on the CD. Then type itemtst1 to
run the program. You’ll see that it indeed prints out the information in
the StockItem object. You can also run it under the debugger by
following the usual instructions for that method.
That’s good as far as it goes, but how do we use this class to keep
track of all of the items in the store? Surely we aren’t going to have a
separately named StockItem variable for each one.

Handling a Number of StockItems with a Vec

This is another application for our old friend the Vec; specifically, we
need a Vec of StockItems to hold the data for all the StockItems in the
store. In a real application we would need to be able to vary the
number of elements in the Vec, unlike our previous use of Vecs. After
all, the number of items in a store can vary from time to time.
As it happens, the size of a Vec can be changed after it is created.
However, in our example program we’ll ignore this complication and
just use a Vec that can hold 100 StockItems .15 Even with this limitation,
we will have to keep track of the number of items that are in use, so
that we can store each new StockItem in its own Vec element and keep
track of how many items we may have to search through to find a
particular StockItem . Finally, we need something to read the data for
each StockItem from the inventory file where it’s stored when we’re not
running the program.
Susan had some questions about these details of the test program:

15. We’ll see how to change the size of a Vec in Chapter 11.
Susan: In the paragraph when you are talking about the number of items, I am a
little confused. That is, do you mean the number of different products that the store
carries or the quantity of an individual item available in the store at any given time?

Steve: The number of different products, which is the same as the number of
StockItems . Remember, each StockItem represents any number of objects of that
exact description.

Susan: So what you’re referring to is basically all the inventory in the store at any
given period of time?

Steve: Exactly.

Susan: What do you mean by "need something to read the data" and "where it’s
stored when we’re not running the program"? I don’t know what you are talking
about, I don’t know where that place would be.

Steve: Well, where are the data when we’re not running the program? The disk.
Therefore, we have to be able to read the information for the StockItems from the
disk when we start the program.

Susan: Okay, that makes sense now.

Steve: I’m glad to hear it.

Figure 6.11 is a little program that shows the code necessary to read
the data for the StockItem Vec into memory when the program starts up.
Reading and displaying a Vec of StockItems
FIGURE 6.11.
(code\itemtst2.cpp)

#include <iostream>
#include <fstream>
#include <string>
#include “Vec.h”
#include “item2.h” using
namespace std;

int main()
{
ifstream ShopInfo(“shop2.in”);
Vec<StockItem> AllItems(100); short i;
short InventoryCount;

for (i = 0; i < 100; i ++)


{
AllItems[i].Read(ShopInfo); if
(ShopInfo.fail() != 0)
break;
}

InventoryCount = i;

for (i = 0; i < InventoryCount; i ++)


{
AllItems[i].Display();
}

return 0;
}

This program has a number of new features that need examination.


First, we’ve had to add the "file stream " header file <fstream> to the list
of include files, so that we will be able to read data in from a file. The
way we do this is to create an ifstream object that is "attached" to a file
when the object is constructed. In this case, the line ifstream
ShopInfo("shop2.in"); creates an ifstream object called ShopInfo , and
connects it to the file named shop2.in .
The first line in the upper loop is AllItems[i].Read(ShopInfo); , which
calls the function Read for the i th StockItem in the AllItems Vec ,
passing the ShopInfo ifstream object to a new StockItem member function
called Read , which uses ShopInfo to get data from the file and store it
into its StockItem (i.e., the i th element in the Vec ).
This whole process was anything but obvious to Susan.

Susan: How does just adding the header file fstream enable you to read data in from
a file?

Steve: The file fstream contains the declaration of the ifstream class .

Susan: Where did the ifstream class come from?

Steve: It’s part of the standard library.

Susan: Where did it come from and how did it get there? Who defined it and
when was it written? Your only reference to this is just "The way we do this is to
create an ifstream object that is
`attached’ to a file when the object is constructed". If this is just something that you
wrote to aid this program that we don’t have to worry about at this time, then please
mention this.

Steve: I didn’t write it, but we don’t have to worry about it very much. I’ll
explain it to the minimum extent necessary.

After that bit of comic relief, let’s get back to reality. Figure 6.12 is
the implementation of the new Read function.

The Read function for the StockItem class (from


FIGURE 6.12.
code\item2.cpp)

void StockItem::Read(istream& is)


{
getline(is,m_Name); is >>
m_InStock;
is >> m_Price;
is.ignore();
getline(is,m_Distributor); getline(is,m_UPC);
}

The task this function performs is fairly simple, but there are some
complexities in its implementation due to peculiarities of the standard
library. Let’s start with the first line of the body of the function:

getline(s,m_Name);

What does this do? It gets a line of data from the input stream , whose
name is s , and puts it into our m_Name variable. Why can’t we use the
>> operator for this purpose, as we indeed do for the next two member
variables that we are reading, m_Instock and m_Price ?

6.9. Working around the Standard Library

We can’t use the >> operator because the implementation of that


operator in the C++ standard library stops reading data into a string
variable whenever it sees a blank or other “white space” character,
rather than only at the end of a line. Therefore, if we tried to use >> to
read a line of data such as “3-ounce cups” into m_Name , we would
only get the part of it before the first blank. The rest of the data would
be left on the input stream for the next read operation to retrieve.
Personally, I think it would be much better for >> to read the
whole line into a string and as we’ll see, that’s how I did it when I
implemented my own string class . But, as Bjarne has said, “Victories
over the type system of a language are always Pyrrhic victories”. Even
though the string class and the rest of the standard library aren’t quite an
intrinsic part of the C++ language in the sense that the native types are,
they are woven tightly enough into the fabric of the language that we
should use them unless we have a compelling reason not to do so. In
this case, the cure (throwing out the standard library string class ) is
worse than the disease, so we’ll just have to put
up with the peculiarities of the standard library even when they make
our programs somewhat more complicated.
The next two lines, which read the data from the input stream into
m_Instock and m_Price , respectively, aren’t anything special; we’ve seen
how we can use >> to read data into numeric variables before. But
what does that next line, s.ignore(); , do?
This is yet another of the peculiarities of the standard library. You
see, when we read data into a numeric variable via the >> operator,
that operator stops taking characters from the input stream as soon as it
sees a character that doesn’t belong in a number, like a space, newline
(‘\n’), or alphabetic character. In this case, that character is the
newline at the end of the line containing the price. That seems
reasonable enough in itself.
However, once it sees that character it doesn’t remove it from the
input stream , but leaves it there for the next input operator. Therefore, if
we didn’t take account of this odd behavior, our next input statement,
getline(s,m_Distributor); , would see the ‘\n’ as the first character in the input
stream and would stop reading at that point. That would leave our
m_Distributor variable empty.
In order to get around this problem, we have to use the ignore
function to ignore the next character in the input stream after we finish
reading the price. This allows the value of the m_Distributor variable to
be read correctly from the input stream .

The Second Version of the StockItem Interface


Now that we’re finished with that unfortunate complication in our
function, there’s just one more construct here we haven’t seen before:
the & in istream& . As soon as we take a look at the new interface to
the StockItem class (Figure 6.13), we’ll see exactly what that means. I
strongly recommend that you print out the files that contain this
interface and its implementation, as well as the test program, for
reference as you are going through this part of the chapter; those files
are item2.h , item2.cpp , and itemtst2.cpp , respectively.
FIGURE 6.13. The second version of the interface for the StockItem class
(code\item2.h)

class StockItem
{
public:
StockItem();

StockItem(std::string Name, short InStock, short Price, std::string Distributor,


std::string UPC);

void Display();
void Read(std::istream& is);

private:
short m_InStock; short
m_Price; std::string
m_Name;
std::string m_Distributor;
std::string m_UPC;
};

The & , as used here in the context of declaring an argument to a


function, means that the argument to which it refers is a reference
argument, rather than a "normal" (value) argument. It’s important to
understand this concept thoroughly, so let’s go into it in detail.

6.10.Reference Arguments

As you may recall from Chapter 5, when we call a function, the


arguments to that function are actually copies of the data supplied by
the calling function; that is, a new local variable is created and
initialized to the value of each expression from the calling function
and the called function works on that local variable. Such a local
variable is called a value argument, because it is a new variable with
the same value as the caller’s original argument. There’s nothing
wrong with this in many cases, but sometimes, as in the present
situation, we have to do it a bit differently. A reference argument, such
as the istream& argument to Read , is not a copy of the caller’s
argument, but another name for the actual argument passed by the
caller.
Reference arguments are often more efficient than value arguments,
because the overhead of making a copy for the called function is
avoided. Another difference between value arguments and reference
arguments is that any changes made to a reference argument change the
caller’s actual argument as well, which in turn means that the caller’s
actual argument must be a variable, not an expression like x + 3 ;
changing the value of such an expression wouldn’t make much sense.
This characteristic of reference arguments can confuse the readers of
the calling function; there’s no way to tell just by looking at the calling
function that some of its variables can be changed by calling another
function. This means that we should limit the use of reference
arguments to those cases where they are necessary.
In this case, however, it is necessary to change the stream object
that is the actual argument to the Read function, because that object
contains the information about what data we’ve already read from the
stream . If we passed the stream as a value argument, then the internal
state of the "real" stream in the calling function wouldn’t be altered to
reflect the data we’ve read in our Read function, so every time we
called Read , we would get the same input again. Therefore, we have
to pass the stream as a reference argument.
The complete decoding of the function declaration void
StockItem::Read(istream& s) is shown in Figure 6.14. Putting it all together:
we’re defining a void function (one that doesn’t return a value), called
Read , which belongs to class StockItem . This function takes an argument
named s that’s a reference to an istream . That is, the argument s is
another name for the istream passed to us by the caller, not a copy of
the caller’s istream .
FIGURE 6.14. The declaration of
StockItem::Read in code\item2.h

This function doesn’t return anything

It belongs to the StockItem class

Its name is Read;

and its argument is a reference to an istream


As you probably have guessed, Susan had some questions about this
whole concept.

Susan: How does Read make Shopinfo go get data?

Steve: Well, the argument s is a reference to the stream object provided by the
caller; in this case, Shopinfo . That stream is connected to the file shop2.in .

Susan: Ok, but in the test program, Shopinfo , which is an ifstream , is passed as
an argument to the Read member function in the StockItem class . But
Read(istream&) function in the StockItem class expects a reference to an
istream as an argument. I understand the code of both functions, but I don’t see how
you can pass an ifstream as an istream . As far as I know, we use fstreams to
write to and read from files and istream and ostream to read from the keyboard
and write to the screen. So, can you mix these when you pass them on as
arguments?
Steve: Yes. As we’ll see later, this is legal because of the relationship between

the ifstream and istream classes .16

Susan: How does Read do the reading? How come you are using
>> without a cin statement?

Steve: cin isn’t a statement, but a stream that is created automatically whenever
we #include < iostream> in our source file. Therefore, we can read from it without
connecting it to a file. In this case, we’re reading from a different stream, namely s .

Susan: How come this is a void type? I would think it would return data being read
from a file.

Steve: You would think so, wouldn’t you? I love it when you’re logical. However,
what it actually does is to read data from a file into the object for which it was
called. Therefore, it doesn’t need a return value.

Susan: So the ifstream object is a transfer mechanism? That is,


ifstream s; would read data from a file named s ?

Steve: Yes, ifstream is a transfer mechanism. However, ifstream s; would


create an ifstream called s that was not connected to any file; the file could be
specified later. If we wanted to create an ifstream called s that was connected to
a file called xyz , then we would write ifstream s("xyz"); .

Susan: OK. An ifstream just reads data from a file. It doesn’t care which file,
until you specify it?

Steve: Right.

16. As explained in the footnote on page 613.


Susan: What does this mean without cin ? Is it just the same thing, only you can’t
call it cin because cin is for native use and this is a class ? How come the >> is
preceded by the argument s ?

Steve: The s takes the place of cin , because we want to read from the stream
s , not the stream cin , which is a stream that is connected to the keyboard.
Whatever is typed on the keyboard goes into cin , whereas the source for other
streams depends on how they are set up. For example, in this program, we have
connected the stream called s to a file called “shop2.in”.

Susan: Tell me what you mean by "just a stream".

Steve: Think of it like a real stream, with the bytes as little barges that float
downstream. Isn’t that poetic? Anyway, there are three predefined streams that
we get automatically when we #include
<iostream> : cin , cout , and cerr . The first two we’ve already seen, and the last
one is intended for use in displaying error messages.

There is one point that we haven’t examined yet, though, which is how
this routine determines that it’s finished reading from the input file.
With keyboard input, we process each line separately after the user
hits ENTER, but that won’t do the job with a file, where we want to
read all the items in until we get to the end of the file. We actually
handle this detail in the main program itemtst2.cpp (Figure 6.11 on page
345) by asking ShopInfo whether there is any data left in the file; to be
more precise, we call the ifstream member function fail() to ask the
ShopInfo ifstream whether we have tried to read past the end of the file. If
we have, then the result of that call to ShopInfo.fail() will be nonzero
(which signifies true ). If we haven’t yet tried to read past the end of
the file, then the result of that call will be 0 (which signifies false ).
How do we use this information?
We use it to decide whether to execute a break statement. This is a
loop control device that interrupts processing of a loop whenever it is
executed. The flow of control passes to the next statement after the end
of the controlled block of the for statement.17
The loop will terminate in one of two ways. Either 100 records
have been read, in which case i will be 100; or the end of the file is
reached, in which case i is the number of records that have been read
successfully.
Susan had some questions about the implementation of this program.

Susan: What is fail() ?

Steve: It’s a member function of the ifstream class .

Susan: Where did it come from?

Steve: From the standard library.

Susan: But it could be used in other classes , right?

Steve: Not unless they define it as well.18

Susan: How does all the data having been read translate into "nonzero"? What
makes a "nonzero" value true ?

Steve: That’s a convention used by that function. Susan: So

anything other than 0 is considered true ? Steve: Yes.

Susan: Where did break come from?

17. The break statement can also terminate execution of a while loop, as well as
another type of control mechanism that we’ll cover in Chapter 11.
18. Actually, this isn’t quite true. As we’ll see in Chapter 9, there are mechanisms
in C++ that will allow us to reuse functionality from existing classes when we
create our own new classes .
Steve: It’s another keyword like for ; it means to terminate the loop that is in
progress.

Susan: I do not understand what is actually happening with the program at this
time. When is break implemented? Is it just to end the reading of the entire file?

Steve: We have to stop reading data when there is no more data in the file. The
break statement allows us to terminate the loop when that occurs.

Susan: What do you mean that the loop will terminate either by 100 records being
read or when the end of the file is reached? Isn’t that the same thing?

Steve: It’s the same thing only if there are exactly 100 records in the file.

Susan: So you mean when there are no more records to be read? So that the loop
won’t continue on till the end with nothing to do?

Steve: Exactly.

Susan: So does i just represent the number of records in the file?

Steve: Actually, it’s the number of records that we’ve read.

Susan: Does this library have a card catalogue? I would like to know what else is
in there.

Steve: There is a library reference manual for most libraries. If you get a library
with a commercial compiler, that manual comes with the compiler documentation;
otherwise, it’s usually an on-line reference (that is, a help file). There’s also quite a
good book about the C++ standard library, with the very imaginative name The C++
Standard Library, written by Nicolai Josuttis (ISBN 0-201-37926-
0). Every serious C++ programmer should have a copy of that book.

Susan: A novice would not know this. Put it in the book.

Steve: Done.

Susan: Well, the program sounded like that indeed there were 100 records in the
file. However, I see that in practice that might change, and why you would therefore
need to have a break .

Steve: You obviously understand this.

Whether there are 100 records in the file or fewer than that number,
obviously the number of items in the Vec is equal to the current value
of i . Or is it?

Fencepost Errors

Let’s examine this a bit more closely. You might be surprised at how
easy it is to make a mistake in counting objects when writing a
program. The most common error of this type is thinking you have one
more or one less than the actual number of objects. In fact, this error is
common enough to have a couple of widely known nicknames: off by
one error and fencepost error. The former name should be fairly
evident, but the latter name may require some explanation. First, let’s
try it as a "word problem". If you have to put up a fence 100 feet long
and each section of the fence is 10 feet long, how many sections of
fence do you need? Obviously, the answer is
10. Now, how many fenceposts do you need? 11. The confusion
caused by counting fenceposts when you should be counting segments
of the fence (or vice versa) is the cause of a fencepost error.
That’s fine as a general rule, but what about the specific example of
counting records in our file? Well, let’s start out by supposing that
we have an empty file, so the sequence of events in the first loop in
the current main program (Figure 6.11 on page 345) is as follows:
1. Set i to 0.
2. Is i less than 100? If not, exit. If so, continue.
3. Use the Read function to try to read a record into the i th element of
the AllItems Vec .
4. Call ShopInfo.fail() to find out whether we’ve tried to read past the end
of the file.
5. If so, execute the break statement to exit the loop.

The answer to the question in step 4 is that in fact nothing was read, so
we do execute the break and leave the loop. The value of i is clearly 0
here, because we never went back to the top of the loop; since we
haven’t read any records, setting InventoryCount to i works in this case.
Now let’s try the same thing, but this time assuming that there is one
record in the file. Here’s the sequence of events:
1. Set i to 0.
2. Is i less than 100? If not, exit. If so, continue.
3. Use the Read function to try to read a record into the i th element of
the AllItems Vec .
4. Call ShopInfo.fail() to find out whether we’ve tried to read past the end
of the file.
5. If so, execute the break statement to exit the loop. In this case, we
haven’t run off the end of the file, so we go back to the top of the
loop, and continue as follows:
6. Increment i to 1.
7. Is i less than 100? If not, exit. If so, continue.
8. Call Read to try to read a record into the AllItems Vec .
9. Call ShopInfo.fail() to find out whether we’ve tried to read past the end
of the file.
10. If so, execute the break statement to exit the loop.

The second time through, we execute the break. Since i is 1, and the
number of elements read was also 1, it’s correct to set the count of
elements to i .
It should be pretty clear that this same logic applies to all the
possible numbers of elements up to 99. But what if we have 100
elements in the file? Relax, I’m not going to go through these steps 100
times, but I think we should start out from the situation that would exist
after reading 99 elements and see if we get the right answer in this
case, too. After the 99th element has been read, i will be 99; we know
this from our previous analysis that indicates that whenever we start
executing the statements in the controlled block of the loop, i is
always equal to the number of elements previously read. So here’s the
100th iteration of the loop:
1. Call Read to try to read a record into the AllItems array.
2. Call ShopInfo.fail() to find out whether we’ve tried to read past the end
of the file.
3. If so, execute the break statement to exit the loop.
4. Otherwise, increment i to 100.
5. Is i less than 100? If not, exit. If so, continue.
6. Since i is not less than 100, we exit.

At this point, we’ve read 100 records and i is 100, so these two
numbers are still the same. Therefore, we can conclude that setting
InventoryCount equal to i when the loop is finished is correct; we have no
fencepost error here.
Susan wasn’t sure why I was hammering this fencepost thing into
the ground:
Susan: Why are you always saying that "it’s correct to set the count of
elements to i "?

Steve: Because I’m showing how to tell whether or not we have a fencepost
error. That requires a lot of analysis.

Actually, this whole procedure we’ve just been through reminds me of


the professor who claimed that some point he was making was
obvious. This was questioned by a student, so the professor spent 10
minutes absorbed in calculation and finally emerged triumphantly with
the news that it was indeed obvious.
Assuming that you’ve installed the software from the CD in the
back of this book, you can try out this program. First, you have to
compile it by following the compilation instructions on the CD. Then
type itemtst2 to run the program. You’ll see that it indeed prints out the
information from the StockItem objects. You can also run it under the
debugger by following the usual instructions for that method.

Other Possible Transactions with the Inventory

Of course, this isn’t all we want to do with the items in the store’s
inventory. Since we have a working means of reading and displaying
the items, let’s see what else we might want to do with them. Here are
a few possible transactions at the grocery store:
1. George comes in and buys 3 bags of marshmallows. We have to
adjust the inventory for the sale.
2. Sam wants to know the price of a can of string beans.
3. Judy comes in looking for chunky chicken soup; there’s none on the
shelf where it should be, so we have to check the inventory to see if
we’re supposed to have any.
All of these scenarios require the ability to find a StockItem object
given some information about it. Let’s start with the first example,
which we might state as a programming task in the following manner:
"Given the UPC from the bag of marshmallows and the number of bags
purchased, adjust the inventory by subtracting the number purchased
from the previous quantity on hand." Figure 6.15 is a program intended
to solve this problem.
First attempt to update inventory of StockItems
FIGURE 6.15.
(code\itemtst3.cpp)

#include <iostream>
#include <fstream>
#include <string>
#include “Vec.h”
#include “item2.h” using
namespace std;

int main()
{
ifstream ShopInfo(“shop2.in”);
Vec<StockItem> AllItems(100); short i;
short InventoryCount; string
PurchaseUPC; short
PurchaseCount; bool Found;

for (i = 0; i < 100; i ++)


{
AllItems[i].Read(ShopInfo); if
(ShopInfo.fail() != 0)
break;
}

InventoryCount = i;

cout << “What is the UPC of the item?” << endl;


cin >> PurchaseUPC;
cout << “How many items were sold?” << endl; cin >>
PurchaseCount;

Found = false;
for (i = 0; i < InventoryCount; i ++)
{
if (PurchaseUPC == AllItems[i].m_UPC)
{
Found = true;
break;
}
}

if (Found)
{
AllItems[i].m_InStock -= PurchaseCount;
cout << “The inventory has been updated.” << endl;
}
else
cout << “Can’t find that item. Please check UPC” << endl;

return 0;
}

Here is a more detailed analysis of the steps that the program in


Figure 6.15 is intended to perform:
1. Take the UPC from the item.
2. For every item in the inventory list, check whether its UPC is the
same as the one from the item.
3. If it doesn’t match, go back to step 2.
4. If it does match, subtract the number purchased from the inventory.
There’s nothing really new here except for the bool variable type,
which we’ll get to in a moment, and the -= operator that the program
uses to adjust the inventory. -= is just like += , except that it subtracts
the right-hand value from the left-hand variable, while += adds.
The bool variable type is a relatively new addition to C++ that
was added to C++ in the process of developing the standard and is
available on any compiler that conforms to the standard. Expressions
and variables of this type are limited to the two values true and
false .19 We’ve been using the terms true and false to refer to the result
of a logical expression such as if (x < y) ; similarly, a bool variable or
function return value can be either true or false .

Attempting to Access private Member Variables

If you compile the program in Figure 6.15, you’ll find that it is not
valid. The problem is the lines:

if (PurchaseUPC == AllItems[i].m_UPC)

and
AllItems[i].m_InStock -= PurchaseCount;

The first of these lines could be translated into English as follows:

"if the input UPC is the same as the value of the m_UPC member variable of the
object stored in the i th element of the AllItems Vec , then..."

while the second of these lines could be translated as:

19. The type bool is short for "boolean", which means "either true or false". The
derivation of the term "boolean" is interesting but not relevant here.
"subtract the number of items purchased from the value of the m_InStock member
variable of the object stored in the i th element of the AllItems Vec ".

While both of these lines are quite understandable to the compiler,


they are also illegal because they are trying to access private member
variables of the StockItem class , namely m_UPC and m_InStock , from
function main . Since main is not a member function of StockItem , this is
not allowed. The error message from the compiler should look
something like Figure 6.16.

FIGURE 6.16. Unauthorized access


prohibited

ITEMTST3.cpp:
Error E2247 ITEMTST3.cpp 36: ‘StockItem::m_UPC’ is not accessible in function main()
Error E2247 ITEMTST3.cpp 45: ‘StockItem::m_InStock’ is not accessible in function main()
*** 2 errors in Compile ***

Does this mean that we can’t accomplish our goal of updating the
inventory? Not at all. It merely means that we have to do things "by the
book" rather than going in directly and reading or changing member
variables that belong to the StockItem class . Of course, we could
theoretically "solve" this access problem by simply making these
member variables public rather than private . However, this would
allow anyone to mess around with the internal variables in our
StockItem objects, which would defeat one of the main purposes of
using class objects in the first place: that they behave like native types
as far as their users are concerned. We want the users of this class to
ignore the internal workings of its objects and merely use them
according to their externally defined interface; the implementation of
the class is our responsibility, not theirs.
This notion of implementation being separated from interface led to
an excellent question from Susan:
Susan: Please explain to me why you needed to list those member variables as
private in the interface of StockItem . Actually, why do they even need to be there
at all? Well, I guess you are telling the compiler that whenever it sees the member
variables that they will always have to be treated privately?

Steve: They have to be there so that the compiler can figure out how large an
object of that class is. Many people, including myself, consider this a flaw in the
language design because private variables should really be private, not exposed to
the class user.

Obviously, she’d lost her true novice status by this point. Six months
after finding out what a compiler is, she was questioning the design
decisions made by the inventor of C++; what is more, her objections
were quite well founded.
As it happens, we can easily solve our access problem without
exposing the implementation of our class to the user any more than it
already has been by virtue of the header file. All we have to do is to
add a couple of new member functions called CheckUPC and
DeductSaleFromInventory to the StockItem class ; the first of these allows us to
check whether a given UPC belongs to a given StockItem , and the
second allows us to adjust the inventory level of an item.
Susan had another suggestion as to how to solve this problem, as
well as a question about why I hadn’t anticipated it in the first place:

Susan: Hey, wouldn’t it be easier to write a special main that is a member


function to get around this?

Steve: That’s an interesting idea, but it wouldn’t work. For one thing, main is
never a member function; this is reasonable when you consider that you generally
have quite a few classes in a program. Which one would main be a member
function of?

Susan: So then all these new member functions do is to act as a gobetween


linking the StockItem class and the inventory update
program to compare data that is private ly held in the StockItem
class ?

Steve: Yes, the new entries in the interface are designed to make the private
data available in a safe manner. I think that’s the same as what you’re saying.

Susan: If you wanted to change the program, why didn’t you just do it in the first
place instead of breaking it down in parts like this?

Steve: Because that’s not the way it actually happens in real life.

Susan: Do you think it less confusing to do that, and also does this act as an
example of how you can modify a program as you see the need to do it?

Steve: Right on both counts.

Figure 6.17 shows the new, improved interface definition.

FIGURE 6.17. An enhanced


interface for the StockItem class (code\item4.h)

class StockItem
{
public:
StockItem();

StockItem(std::string Name, short InStock, short Price, std::string Distributor,


std::string UPC);

void Display();
void Read(std::istream& is);

bool CheckUPC(std::string ItemUPC);


void DeductSaleFromInventory(short QuantitySold); short
GetInventory();
std::string GetName();
private:
short m_InStock; short
m_Price; std::string
m_Name;
std::string m_Distributor;
std::string m_UPC;
};

I recommend that you print out the files that contain this interface and
its implementation as well as the test program, for reference as you
are going through this part of the chapter; those files are item4.h ,
item4.cpp , and itemtst4.cpp , respectively. The declarations of the two new
functions, CheckUPC and DeductSaleFromInventory , should be pretty easy to
figure out: CheckUPC takes the UPC that we want to find and compares
it to the UPC in its StockItem , then returns true if they match and false if
they don’t. Here’s another good use for the bool data type: the only
possible results of the CheckUPC function are that the UPC in the
StockItem matches the one we’ve supplied (in which case we return
true ) or it doesn’t match (in which case we return false ).
DeductSaleFromInventory takes the number of items sold and subtracts it
from the previous inventory. But where did GetInventory and GetName
come from?

Making the Program More User-friendly

I added those functions because I noticed that the "itemtst" program


wasn’t very user-friendly. Originally it followed these steps:
1. Ask for the UPC.
2. Ask for the number of items purchased.
3. Search through the list to see whether the UPC is legitimate.
4. If so, adjust the inventory.
5. If not, give an error message.
6. Exit.
What’s wrong with this picture? Well, for one thing, why should the
program make me type in the number of items sold if the UPC is no
good? Also, it never told me the new inventory or even what the name
of the item was. It may have known these things, but it never bothered
to inform me. So I changed the program to work as follows:
1. Ask for the UPC.
2. Search through the list to see whether the UPC was legitimate.
3. If not, give an error message and exit.
4. If the UPC was OK, then
a. Display the name of the item and the number in stock.
b. Ask for the number of items purchased.
c. Adjust the inventory.
d. Display a message with the name of the item and number of
remaining units in inventory.
5. Exit.

To do this, I needed those two new functions GetInventory and GetName ,


so as you’ve seen I added them to the class declaration. Figures 6.18-
6.21 show the implementation of all the new functions.

FIGURE 6.18. StockItem::CheckUPC


(from code\item4.cpp)

bool StockItem::CheckUPC(string ItemUPC)


{
if (m_UPC == ItemUPC)
return true;
else
return false;
}
FIGURE 6.19. StockItem::DeductSaleFromInventory (from code\item4.cpp)

void StockItem::DeductSaleFromInventory(short QuantitySold)


{
m_InStock -= QuantitySold;
}

FIGURE 6.20. StockItem::GetInventory (from code\item4.cpp)

short StockItem::GetInventory()
{
return m_InStock;
}

FIGURE 6.21. StockItem::GetName (from code\item4.cpp)

string StockItem::GetName()
{
return m_Name;
}

Our current itemtst example is getting to be enough like a real program


that I’m going to start using the term application program (or
equivalently, application) to refer to it sometimes. As is generally true
of C++ programs, the responsibility for doing the user’s work is
divided up into a main program (or application program) and a set of
classes (sometimes called infrastructure) used in the application. In
this case, itemtst4.cpp is the main program, or application program,
whereas the other two files ( item4.h and item4.cpp ) are the
infrastructure. Figure 6.22 shows the new, improved version of our
application, which updates the inventory and actually tells the user
what it’s doing.
FIGURE 6.22. Updating StockItem
inventory (code\itemtst4.cpp)

#include <iostream>
#include <fstream>
#include <string>
#include “Vec.h”
#include “item4.h” using
namespace std;

int main()
{
ifstream ShopInfo(“shop2.in”);
Vec<StockItem> AllItems(100); short i;
short InventoryCount; short
OldInventory; short
NewInventory; string
PurchaseUPC; string
ItemName; short
PurchaseCount; bool
Found;

for (i = 0; i < 100; i ++)


{
AllItems[i].Read(ShopInfo); if
(ShopInfo.fail() != 0)
break;
}

InventoryCount = i;
cout << “What is the UPC of the item? “; cin >>
PurchaseUPC;
Found = false;

for (i = 0; i < InventoryCount; i ++)


{
if (AllItems[i].CheckUPC(PurchaseUPC))
{
Found = true;
break;
}
}

if (Found)
{
OldInventory = AllItems[i].GetInventory();
ItemName = AllItems[i].GetName();

cout << “There are currently “ << OldInventory << “ units of “


<< ItemName << “ in stock.” << endl; cout <<
“How many items were sold? “; cin >>
PurchaseCount;

AllItems[i].DeductSaleFromInventory(PurchaseCount); cout <<


“The inventory has been updated.” << endl;

NewInventory = AllItems[i].GetInventory();
cout << “There are now “ << NewInventory << “ units of “
<< ItemName << “ in stock.” << endl;
}
else
cout << “Can’t find that item. Please check UPC” << endl;

return 0;
}

This code should be pretty easy to follow; it simply implements the


first item purchase scenario I outlined in the list on page 360.
Assuming that you’ve installed the software from the CD in the back of
this book, you can try out this program. First, you have to compile it by
following the compilation instructions on the CD. Then type itemtst4 to
run the program. You can also run it under the debugger by following
the usual instructions for that method.
In either case, the program will start up and ask you for the UPC;
you can use 7904886261, which is the (made-up) UPC for
"antihistamines". Type in that number and hit ENTER.
The Inventory class

Now let’s consider what might be needed to handle some of the other
possibilities, starting with the second scenario in that same list. To
refresh your memory, here it is again: "Sam wants to know the price of
a can of string beans". A possible way to express this as a
programming task is "Given a UPC, look up the price of the item in the
inventory".
Here is a set of steps to solve this problem:
1. Ask for the UPC.
2. Search through the list to see whether the UPC is legitimate.
3. If not, give an error message and exit.
4. If the UPC is OK, then display the name and price of the item.
5. Exit.

Have you noticed that this solution is very similar to the solution to the
first problem? For example, the search for an item with a given UPC
is exactly the same. It seems wasteful to duplicate code rather than
using the same code again, and in fact we’ve seen how to avoid code
duplication by using a function. Now that we’re doing "object-
oriented" programming, perhaps this new search function should be a
member function instead of a global one.
This is a good idea, except that the search function can’t be a
member function of StockItem , because we don’t have the right StockItem
yet; if we did, we wouldn’t need to search for it. Therefore, we have to
create a new class that contains a member variable that is a Ve c of
StockItems and write the search routine as a member function of this new
class ; the new member function would look through its Vec to find the
StockItem we want. Then we can use the member functions of StockItem
to do the rest. Figure 6.23 shows the interface ( class declaration) for
this new class , called Inventory .
FIGURE 6.23. Interface of Inventory class (code\invent1.h)

class Inventory
{
public:
Inventory();

short LoadInventory(std::ifstream& is); StockItem


FindItem(std::string UPC); bool
UpdateItem(StockItem Item);

private:
Vec<StockItem> m_Stock; short
m_StockCount;
};

I strongly recommend that you print out the files that contain this
interface and its implementation, as well as the test program, for
reference as you are going through this chapter; those files are
invent1.h , invent1.cpp , and itemtst5.cpp , respectively.
Susan was somewhat surprised that I would even consider writing a
global function to find a StockItem :

Susan: What do you mean by making this a member function instead of a global
function? When was it ever a global function?

Steve: It wasn’t any kind of function before. However, making it a function


would make sense; the question is what kind of function, global or member?

Susan: I am not sure if I truly understand the problem as to why you can’t search
StockItem as a member function.

Steve: A member function of StockItem always accesses a particular


StockItem . However, our problem is that we don’t know which StockItem we
want; therefore, a member function, which must apply to a particular StockItem ,
won’t solve our problem.
Susan: OK, Stevie, here is the deal. Why would you even consider making this a
global function? Of course it is a member function. We are doing object oriented
programming, aren’t we?

Steve: Aren’t we knowledgeable all of a sudden? Who was that person who
knew nothing about programming eight months ago?

Susan: You’ve got me there. But seriously, what would be the advantage of
making it a global function rather than a member function? This is what has me
bothered about the whole thing.

Steve: There wouldn’t be any advantage. I just wanted to point out that it clearly
can’t be a member function of StockItem , and indicate the possible alternatives.

Susan: Oh, then so far that is all our program is able to do? It is unable to locate
one item of all possible items and display it just from the UPC code? In fact that is
what we are trying to accomplish, right?

Steve: Exactly.

Susan also wasn’t sure why we needed LoadInventory .

Susan: What does the code short LoadInventory (ifstream& is); do? Does it just
give you an object named LoadInventory that reads a file that has a reference
argument named is ? I don’t get this.

Steve: That’s quite close. The line you’re referring to is the declaration of a
function named LoadInventory , which takes a reference to an ifstream . The
implementation of the function, as you’ll see shortly, reads StockItem records from
the file connected to the ifstream .

Once that was cleared up, she had some questions about the way the
FindItem function works, including its interface.
Susan: Is the argument UPC to the FindItem function a string
because it is returning the name of a stock item?

Steve: That argument is the input to the FindItem function, not its output;
therefore, it’s not "returning" anything. FindItem returns the StockItem that it finds.
Or did I misunderstand your question?

Susan: Let’s see if I even know what I was asking here. OK, how about this: I
wanted to know why UPC was a string and not a short , since a UPC is usually a
number. In this case, it will be returning a name of a "found item" so that is why it is
a string , right?

Steve: No, it’s because the UPC won’t fit in any of the types of numbers we
have available. Thus, the most sensible way to store it is as a string . Since we don’t
use it in calculations anyway, the fact that you can’t calculate with string variables
isn’t much of a restriction.

Susan: Oh. OK. So a string is more useful for storing numbers that are somewhat
lengthy as long as you don’t calculate with those numbers. They are nothing more
than "numerical words"?

Steve: Exactly.

Most of this should be fairly self-explanatory by this point. We start


out with the default constructor which makes an empty Inventory .
Figure 6.24 has the implementation for the default constructor.20

Default constructor for Inventory class (from


FIGURE 6.24.
code\invent1.cpp)

Inventory::Inventory()
: m_Stock (Vec<StockItem>(100)),
20. As before, we can count on the compiler to supply the other three standard
member functions needed for a concrete data type: the copy constructor, the
assignment operator =, and the destructor.
m_StockCount(0)
{
}

There’s nothing complex here; we’re using the member initialization


list to initialize the m_Stock variable to a newly constructed Vec of
100 StockItems and the number of active StockItems to 0. The latter, of
course, is because we haven’t yet read any data in from the file.
Then we have a couple of handy functions. The first is
LoadInventory , which will take data from an ifstream and store it in its
Inventory object, just as we did with the AllItems Vec in our application
itemtst4.cpp .
Susan had a question about this:

Susan: How did you know that you were going to need to use an
ifstream again?

Steve: Because we’re reading data from a file into a Vec of


StockItems , and reading data from a file is what ifstreams are for.

Figure 6.25 shows the implementation of LoadInventory .

FIGURE 6.25. LoadInventory function for the Inventory class (from


code\invent1.cpp)

short Inventory::LoadInventory(ifstream& is)


{
short i;

for (i = 0; i < 100; i ++)


{
m_Stock[i].Read(is); if
(is.fail() != 0)
break;
}

m_StockCount = i; return
m_StockCount;
}

Now we come to the FindItem member function. Its declaration is pretty


simple: it takes an argument of type string which contains the UPC that
we’re looking for. Its implementation should be pretty simple, too: it
will search the Inventory object for the StockItem that has that UPC and
return a copy of that StockItem , which can then be interrogated to find
the price or whatever other information we need. However, there’s a
serious design issue here: what should this function return if the UPC
doesn’t match the UPC in any of the StockItem entries in the Inventory
object? The application program has to be able to determine whether
or not the UPC is found. In the original program this was no problem,
because the main program maintained that information itself. But in
this case, the member function FindItem has to communicate success or
failure to the caller
somehow.
Of course, we could use a return value of true or false to indicate
whether the UPC is found, but we’re already using the return value to
return the StockItem to the calling function. We could add a reference
argument to the FindItem function and use it to set the value of a
variable in the caller’s code, but that’s very nonintuitive; functions that
don’t modify their arguments are easier to use and less likely to cause
surprises.

Using a Null Object

There’s one more possibility. We can return a null object of the


StockItem class ; that is, an object that exists solely to serve as a
placeholder, representing the desired object that we couldn’t find.
I like this solution, because when the member function terminates,
the application program has to test something anyway to see if the
desired StockItem was found; why not test whether the returned object
is a null StockItem ? This solution, while quite simple, requires a minor
change to our implementation of StockItem : we have to add an IsNull
member function to our StockItem class so that we
can tell whether the returned StockItem is a null StockItem or a "normal"
one. We have to add the line bool IsNull(); to the class interface and
provide the implementation as shown in Figure 6.26. I strongly
recommend that you print out the files that contain this interface and its
implementation, as well as the test program, for reference as you are
going through this part of the chapter; those files are item5.h , item5.cpp ,
and itemtst5.cpp , respectively.

FIGURE 6.26. The implementation


of IsNull (from code\item5.cpp)

bool StockItem::IsNull()
{
if (m_UPC == ““)
return true;

return false;
}

As you can see, not much rocket science is involved in this member
function: all we do is check whether the UPC in the item is the null
string "". If it is, we return true ; otherwise, we return false . Since no
real item can have a UPC of "", this should work well. Let’s hear from
Susan on the topic of this function (and function return values in
general).

Susan: This is something I have not thought about before: When you call a
function where does the return value go?

Steve: Wherever you put it. If you say x = sum(weight); , then the return value
goes into x . If you just say sum(weight); , then it is discarded.

Susan: Why is it discarded?

Steve: Because you didn’t use it; therefore, the compiler assumes you have no
further use for it.
Susan: So the return value can be used in only one place?

Steve: Yes, unless you save it in a variable, in which case you can use it however
you like.

Figure 6.27 shows the implementation of FindItem , which uses


CheckUPC to check whether the requested UPC is the one in the current
item and returns a null StockItem if the desired UPC isn’t found in the
inventory list.

FIGURE 6.27. FindItem function for Inventory class (from code\invent1.cpp)

StockItem Inventory::FindItem(string UPC)


{
short i;
bool Found = false;

for (i = 0; i < m_StockCount; i ++)


{
if (m_Stock[i].CheckUPC(UPC))
{
Found = true;
break;
}
}

if (Found)
return m_Stock[i];

return StockItem();
}

This function illustrates a new way to specify the condition of an if


statement: if a bool variable is specified as the condition, the if will be
true if the bool is true, and otherwise the if will be false . Thus, the
expression if (Found) will execute its controlled statement if the value
of Found is true , but not if the value of Found is false . This is
equivalent in effect to writing if (Found == true) , but is less error-
prone because you can’t make the mistake of writing = (assignment)
rather than == (testing for equality). Therefore, it’s best to test bool
conditions by this implicit method, even though the syntax is a little
more cryptic.
Here’s my interchange with Susan on CheckUPC :

Susan: About the first if statement in this CheckUPC function, if


(m_Stock[i].CheckUPC(UPC)) : does that mean if you find the UPC you are
looking for then the program breaks and you don’t need to continue looking? In that
case, what does the statement Found = true; do? Are you setting Found to the
value true ?

Steve: That’s right. If we’ve actually found the item we’re looking for, then
Found will have been set to true , so we’ll return the real item; otherwise, we’ll
return a null StockItem to indicate that we couldn’t find the one requested.

After we get a copy of the correct StockItem and update its inventory
via DeductSaleFromInventory , we’re not quite done; we still have to update
the "real" StockItem in the Inventory object. This is the task of the last
function in our Inventory class : UpdateItem . Figure 6.28 shows its
implementation.

FIGURE 6.28. UpdateItem function for the Inventory class (from code\invent1.cpp)

bool Inventory::UpdateItem(StockItem Item)


{
string UPC = Item.GetUPC();

short i;
bool Found = false;

for (i = 0; i < m_StockCount; i ++)


{
if (m_Stock[i].CheckUPC(UPC))
{
Found = true;
break;
}
}

if (Found) m_Stock[i] =
Item;

return Found;
}

Why do we need this function? Because we are no longer operating on


the "real" StockItem , as we had been when we accessed the inventory
V e c directly in the previous version of the application program.
Instead, we are getting a copy of the StockItem from the Inventory object
and changing that copy. Thus, to have the final result put back into the
Inventory object, we need to use the UpdateItem member function of
Inventory , which overwrites the original StockItem with our changed
version.
This function needs another function in the StockItem class to get the
UPC from a StockItem object, so that UpdateItem can tell which object in
the m_Stock Vec is the one that needs to be updated. This additional
function, GetUPC , is shown in Figure 6.29.

FIGURE 6.29. The implementation of GetUPC (from code\item5.cpp)

string StockItem::GetUPC()
{
return m_UPC;
}

The application program also needs one more function, GetPrice() , to


be added to the interface of StockItem to retrieve the price from the
object once we have found it. This is shown in Figure 6.30.
FIGURE 6.30. The implementation
of GetPrice (from code\item5.cpp)

short StockItem::GetPrice()
{
return m_Price;
}

We’re almost ready to examine the revised test program. First, though,
let’s pause for another look at all of the interfaces and implementations
of the StockItem and Inventory classes . The interface for the Inventory class is
in Figure 6.31.

FIGURE 6.31. Current interface for


Inventory class (code\invent1.h)

class Inventory
{
public:
Inventory();

short LoadInventory(std::ifstream& is); StockItem


FindItem(std::string UPC); bool
UpdateItem(StockItem Item);

private:
Vec<StockItem> m_Stock; short
m_StockCount;
};

Figure 6.32 contains the implementation for Inventory .

FIGURE 6.32. Current implementation for Inventory class


(code\invent1.cpp)

#include <iostream>
#include <fstream>
#include <string>
#include “Vec.h”
#include “item5.h”
#include “invent1.h” using
namespace std;

Inventory::Inventory()
: m_Stock (Vec<StockItem>(100)),
m_StockCount(0)
{
}

short Inventory::LoadInventory(ifstream& is)


{
short i;

for (i = 0; i < 100; i ++)


{
m_Stock[i].Read(is); if
(is.fail() != 0)
break;
}

m_StockCount = i; return
m_StockCount;
}

StockItem Inventory::FindItem(string UPC)


{
short i;
bool Found = false;

for (i = 0; i < m_StockCount; i ++)


{
if (m_Stock[i].CheckUPC(UPC))
{
Found = true;
break;
}
}

if (Found)
return m_Stock[i];
return StockItem();
}

bool Inventory::UpdateItem(StockItem Item)


{
string UPC = Item.GetUPC();

short i;
bool Found = false;

for (i = 0; i < m_StockCount; i ++)


{
if (m_Stock[i].CheckUPC(UPC))
{
Found = true;
break;
}
}

if (Found) m_Stock[i] =
Item;

return Found;
}

Figure 6.33 shows the interface for StockItem .

FIGURE 6.33. Current interface for


StockItem class (code\item5.h)

class StockItem
{
public:
StockItem();

StockItem(std::string Name, short InStock, short Price, std::string Distributor,


std::string UPC);

void Display();
void Read(std::istream& is);
bool CheckUPC(std::string ItemUPC);
void DeductSaleFromInventory(short QuantitySold); short
GetInventory();
std::string GetName(); bool
IsNull();
short GetPrice();
std::string GetUPC();

private:
short m_InStock; short
m_Price; std::string
m_Name;
std::string m_Distributor;
std::string m_UPC;
};

The implementation for StockItem is in Figure 6.34.

FIGURE 6.34. Current implementation for StockItem class (code\item5.cpp)

#include <iostream>
#include <fstream>
#include <string>
#include “item5.h” using
namespace std;

StockItem::StockItem()
: m_InStock(0), m_Price(0), m_Name(), m_Distributor(), m_UPC()
{
}

StockItem::StockItem(string Name, short InStock, short Price, string


Distributor, string UPC)
: m_InStock(InStock), m_Price(Price), m_Name(Name), m_Distributor(Distributor),
m_UPC(UPC)
{
}
void StockItem::Display()
{
cout << “Name: “;
cout << m_Name << endl; cout <<
“Number in stock: “; cout <<
m_InStock << endl; cout << “Price:
“;
cout << m_Price << endl; cout
<< “Distributor: “;
cout << m_Distributor << endl; cout <<
“UPC: “;
cout << m_UPC << endl; cout
<< endl;
}

void StockItem::Read(istream& is)


{
getline(is,m_Name); is >>
m_InStock;
is >> m_Price; is.ignore();
getline(is,m_Distributor);
getline(is,m_UPC);
}

bool StockItem::CheckUPC(string ItemUPC)


{
if (m_UPC == ItemUPC)
return true;

return false;
}

void StockItem::DeductSaleFromInventory(short QuantitySold)


{
m_InStock -= QuantitySold;
}

short StockItem::GetInventory()
{
return m_InStock;
}

string StockItem::GetName()
{
return m_Name;
}

bool StockItem::IsNull()
{
if (m_UPC == ““) return
true;

return false;
}

short StockItem::GetPrice()
{
return m_Price;
}

string StockItem::GetUPC()
{
return m_UPC;
}

To finish this stage of the inventory control project, Figure 6.35 is the
revised test program that uses the Inventory class rather than doing its
own search through a Ve c of StockItems . This program can perform
either of two operations, depending on what the user requests. Once
the UPC has been typed in, the user is prompted to type either "C" for
price check or "S" for sale. Then an if statement selects which of the
two operations to perform. The code for the S (i.e., sale) operation is
the same as it was in the previous version of this application, except
that, of course, at that time it was the only possible operation so it
wasn’t controlled by an if statement. The code for the C (i.e., price
check) operation is new, but it’s very simple. It merely displays both
the item name and the price.

FIGURE 6.35. Updated inventory


application (code\itemtst5.cpp)

#include <iostream>
#include <fstream>
#include <string>
#include “vec.h”
#include “item5.h”
#include “invent1.h” using
namespace std;

int main()
{
ifstream InputStream(“shop2.in”); string
PurchaseUPC;
short PurchaseCount; string
ItemName; short
OldInventory; short
NewInventory;
Inventory MyInventory;
StockItem FoundItem; string
TransactionCode;

MyInventory.LoadInventory(InputStream);

cout << “What is the UPC of the item? “; cin >>


PurchaseUPC;

FoundItem = MyInventory.FindItem(PurchaseUPC); if
(FoundItem.IsNull())
{
cout << “Can’t find that item. Please check UPC.” << endl; return 0;
}
OldInventory = FoundItem.GetInventory(); ItemName
= FoundItem.GetName();

cout << “There are currently “ << OldInventory << “ units of “


<< ItemName << “ in stock.” << endl;

cout << “Please enter transaction code as follows:\n”; cout << “S


(sale), C (price check): “;
cin >> TransactionCode;

if (TransactionCode == “C” || TransactionCode == “c”)


{
cout << “The name of that item is: “ << ItemName << endl; cout << “Its price
is: “ << FoundItem.GetPrice();
}
else if (TransactionCode == “S” || TransactionCode == “s”)
{
cout << “How many items were sold? “; cin >>
PurchaseCount;

FoundItem.DeductSaleFromInventory(PurchaseCount);
MyInventory.UpdateItem(FoundItem);

cout << “The inventory has been updated.” << endl;

FoundItem = MyInventory.FindItem(PurchaseUPC); NewInventory =


FoundItem.GetInventory();

cout << “There are now “ << NewInventory << “ units of “


<< ItemName << “ in stock.” << endl;
}

return 0;
}

The only part of the program that might not be obvious at this point is
the expression in the if statement that determines whether the user
wants to enter a price check or sale transaction. The first part of the
test is if (TransactionCode == "C" || TransactionCode == "c") . The || is
the "logical or" operator. An approximate translation of this
expression is "if at least one of the two expressions on its right or left
is true , then produce the result true ; if they’re both false , then produce
the result false ".21 In this case, this means that the if statement will be
true if the TransactionCode variable is either C or c . Why do we have to
check for either a lower- or upper-case letter, when the instructions to
the user clearly state that the choices are C or S ?
This is good practice because users generally consider upper and
lower case letters to be equivalent. Of course as programmers, we
know that the characters c and C are completely different; however,
we should humor the users in this harmless delusion. After all, they’re
our customers!
Susan had a couple of questions about this program.

Susan: What do the following output statements mean: cout << S (sale); and cout
<< C (price check); ? I am not clear as to what they are doing.

Steve: Nothing special; the prompts S (sale) and C (price check)


are just to notify the user what his or her choices are.

Susan: OK, so the line with the || is how you tell the computer to recognize
upper case as well as lower case to have the same meaning?

Steve: Yes, that’s how we’re using that operator here.

Susan: So what do you call those || thingys?

21. The reason it’s only an approximate translation is that there is a special rule in
C++ governing the execution of the || operator: if the expression on the left is
true, then the expression on the right is not executed at all. The reason for this
short-circuit evaluation rule is that in some cases you may want to write a
right-hand expression that will only be legal if the left-hand expression is false.
Steve: They’re called "vertical bars". The operator that is spelled || is called a
"logical OR" operator, because it results in the value true if either the left-hand or the
right-hand expression is true (or if both are true ).

Susan: What do you mean by using else and if in the line else if
(TransactionCode == "S" || TransactionCode == "s") ? I don’t believe I have seen
them used together before.

Steve: I think you’re right. Actually, it’s not that mysterious. As always, the else
means that we’re specifying actions to be taken if the original if isn’t true . The
second if merely checks whether another condition is true and executes its
controlled block if so.

Assuming that you’ve installed the software from the CD in the back of
this book, you can try out this program. First, you have to compile it by
following the compilation instructions on the CD. Then type itemtst5 to
run the program. When the program asks for a UPC, you can use
7904886261, which is the (made-up) UPC for "antihistamines". When
the program asks you for a transaction code, type S for "sale" or P
for "price check", and then hit ENTER.

6.11. Checking Inventory for a Misplaced Item

By this point, you very understandably might have gotten the notion
that we have to make changes to our classes every time we need to do
anything slightly different in our application program. In that case,
where’s the advantage of using classes instead of just writing the whole
program in terms of shorts , chars , and so on?
Well, this is your lucky day. It just so happens that the next (and
last) scenario we are going to examine requires no more member
functions at all; in fact, we don’t even have to change the application
program. Here it is, for reference: "Judy comes in looking for chunky
chicken soup; there’s none on the shelf where it should be, so we
have to check the inventory to see if we’re supposed to have any".
The reason we don’t have to do anything special for this scenario
is that we’re already displaying the name and inventory for the item as
soon as we find it. Of course, if we hadn’t already handled this issue,
there are many other ways that we could solve this same problem. For
example, we could use the Display member function of StockItem to
display an item as soon as the UPC lookup succeeds, rather than
waiting for the user to indicate what operation our application is
supposed to perform.
For that matter, we’d have to consider a number of other factors in
writing a real application program, even one that does such a simple
task as this one. For example, what would happen if the user indicated
that 200 units of a particular item had been sold when only 100 were
in stock? Also, how would we find an item if the UPC isn’t available?
The item might very well be in inventory somewhere, but the current
implementation of Inventory doesn’t allow for the possibility of looking
up an item by information other than the UPC.
Although these topics and many others are essential to the work of
a professional programmer, they would take us too far afield from our
purpose here. We’ll get into some similar issues later, when we
discuss the topic of "software engineering" in Chapter 12.
Now let’s review what
we’ve covered in this chapter.

6.12. Review

The most important concept in this chapter is the idea of creating user-
defined data types. In C++, this is done by defining a class for each
such data type. Each class has both a class interface, which describes
the behavior that the class displays to the "outside world" (i.e., other,
unrelated functions), and a class implementation, which tells the
compiler how to perform the behaviors promised in the interface
definition. A variable of a class type is called an object.
With proper attention to the interface and the implementation of a
class , it is possible to make objects behave just like native variables;
that is, they can be initialized, assigned, compared, passed as function
arguments, and returned as function return values.
Both the interface and the implementation of a class are described in
terms of the functions and variables of which the class is composed.
These are called member functions and member variables, because
they belong to the class rather than being "free-floating" or localized to
one function like the global functions and local variables we
encountered earlier.
Of course, one obvious question is why we need to make up our
own variable types. What’s wrong with char , short , and the rest of the
native types built into C++? The answer is that it’s easier to write an
inventory control program, for example, if we have data types
representing items in the stock of a store, rather than having to express
everything in terms of the native types. An analogy is the universal
preference of professionals to use technical jargon rather than "plain
English". Jargon conveys more information, more precisely, in less
time.
Creating our own types of variables allows us to use objects
rather than functions as the fundamental building blocks of our
programs, which is the basis of the "object-oriented programming"
paradigm.
Then we examined how creating classes differs from using classes ,
which we have been doing throughout the book. A fairly good analogy
is that creating your own classes is to using classes as writing a
program is to using a program.
Next, we went through the steps needed to actually create a new
class ; our example is the StockItem class , which is designed to simulate
tracking of inventory for a small grocery store. These steps include
writing the interface definition, writing the implementation, writing the
program that uses the class , compiling the implementation, compiling
the program that uses the class , and linking the object files resulting
from these compilation steps
together with any needed libraries to produce the final executable
program.
Then we moved from the general to the specific, analyzing the
particular data and functions that the StockItem class needed to perform
its duties in an application program. The member variables needed for
each StockItem object included the name, count, distributor, price, and
UPC. Of course, merely having these member variables doesn’t make
a StockItem object very useful if it can’t do anything with them. This led
us to the topic of what member functions might be needed for such a
class .
Rather than proceed immediately with the specialized member
functions that pertain only to StockItem , however, we started by
discussing the member functions that nearly every class needs to make
its objects act like native variables. A class that has (at least) the
capabilities of a native type is called a concrete data type. Such a
class requires the following member functions:

1. A default constructor, which makes it possible to create an object


of that class without supplying any initial data,
2. A copy constructor, which makes it possible to create a new object
of this type with the same contents as an existing object of the same
type,
3. An assignment operator, which copies the contents of one object
of this type to another object of the same type,
4. A destructor, which performs whatever cleanup is needed when an
object of this type "dies".

Since these member functions are so important to the proper


functioning of a class , the compiler will create a version of each of
them for us if we don’t write them ourselves. In the case of StockItem ,
these compiler-generated member functions are perfectly acceptable,
with the exception of the default constructor. The compiler-generated
default constructor doesn’t initialize a new StockItem object to a valid
state, so we had to write that constructor ourselves to be sure of what
a newly created StockItem contains. Next, we looked at the first version
of a class interface specification for StockItem (Figure 6.3), which tells
the user (and the compiler) exactly what functions objects of this class
can perform. Some items of note in this construct are these:
1. The access specifiers public and private , which control access to the
implementation of a class by functions not in the class (nonmember
functions). Member variables and functions in the public section
are available for use by nonmember functions, whereas member
variables and functions in the private section are accessible only by
member functions.
2. The declarations of the constructor functions, which construct a
new object of the class . The first noteworthy point about
constructors is that they have the same name as the class , which is
how the compiler identifies them as constructors. The second point
of note is that there can be more than one constructor for a given
class ; all constructors have the same name and are distinguished by
their argument lists. This facility, called function overloading, is
applicable to C++ functions in general, not just constructors. That
is, you can have any number of functions with the same name as long
as they have different argument lists; the difference in argument lists
is enough to make the compiler treat them as different functions. In
this case, we have written two constructors: the default constructor,
which is used to create a StockItem when we don’t specify an initial
value, and a constructor that has
arguments to specify values for all of the member variables.22

22. The compiler has also supplied a copy constructor for us, so that we can use
StockItem objects as function arguments and return values. In this case, the
compiler-generated copy constructor does exactly what we want, so we don’t
have to write our own. As we’ll see in the rest of the book, these compiler-
generated functions don’t always behave properly, especially with more
complicated classes than the StockItem class, where the compiler can’t figure out
how to copy or assign objects correctly without more help from us.
3. The declaration of a "normal" member function (that is, not a
constructor or other predefined function) named Display , which as its
name indicates, is used to display a StockItem on the screen.
4. The declarations of the member variables of StockItem , which are
used to keep track of the information for a given object of the
StockItem class .

Once we’d defined the class interface, we started on the class


implementation by writing the default constructor for the StockItem class :
StockItem::StockItem() . The reason for the doubled name is that when we
write the implementation of a member function, we have to specify
what class that member function belongs to. In this example, the first
StockItem is the name of the class , whereas the second StockItem is the
name of the function, which, as always with constructors, has the same
name as the class . By contrast, we didn’t have to specify the class
name when declaring member functions in the interface definition,
because all functions defined there are automatically member
functions of that class . During this discussion, we saw that the
preferred way to set the values of member variables is by using a
member initialization list.
The next topic we visited was the scope of member variables,
which is class scope. Each object of a given class has one set of
member variables, which live as long as the object does. These
member variables can be accessed from any member function as
though they were global variables.
Then we examined how the default constructor was actually used
in the example program, discovering that the line StockItem soup; was
enough to cause it to be called. This is appropriate because one of the
design goals of C++ was to allow a class object to be as easy to use as a
native variable. Since a native variable can be created simply by
specifying its type and name, the same should be true of a class object.
This led to a discussion of the fact that the person who writes a
class isn’t always the person who uses it. One reason for this is that
the skills required to write a program using a class are not necessarily
the same as those required to create the class in the first place.
Next, we covered the other constructor we wrote for the StockItem
class . This one has arguments specifying the values for all of the
member variables that make up the data part of the class .
Then we got to the final function of the first version of the StockItem
class , the Display function, which as its name indicates is used to
display the contents of a StockItem on the screen. This function uses the
pre-existing ability of << to display shorts and strings , including those
that hold the contents of a StockItem . The return type of this function is
a type we hadn’t seen before, void , which simply means that there is
no return value from this function. We don’t need a return value from
the Display function because we call it solely for its side effect:
displaying the value of its StockItem on the screen.
Next, we took up the private part of the StockItem class definition, which
contains the member variables. We covered two reasons why it is a
good idea to keep the member variables private : first, it makes
debugging easier, because only the member functions can modify the
member variables; second, we can change the names or types of our
member variables or delete them from the class definition much more
easily if we don’t have to worry about what other functions might be
relying on them. While we were on the subject of the member
variables of StockItem , I also explained how we could use a short to
store a price; by expressing the price in cents, rather than dollars and
cents, any price up to $327.67 could be stored in such a variable.
As we continued with the analysis of how the StockItem objects
would be used, we discovered that our example program actually
needed a Vec of such objects, one for each different item in the stock.
We also needed some way to read the information for these StockItem
objects from a disk file, so we wouldn’t have to type it in every time
we started the program up. So the next program we examined
provided this function via a C++ library class we hadn’t seen before:
ifstream (for input from a file). We also added a new function called
Read to use ifstream to read information for a StockItem from the file
containing that information.
While looking at the implementation of the new Read member
function we ran into the idea of a reference argument, which is an
argument that is another name for the caller’s variable, rather than a
copy of that variable (a value argument). This makes it possible to
change the caller’s variable by changing the value of the argument in
the function. In most cases, we don’t want to be able to change the
caller’s variable, but it is essential when reading from a stream,
because otherwise we’d get the same data every time we read
something from the stream. Therefore, we have to use a reference
argument in this case, so that the stream’s internal state will be
updated correctly when we retrieve data from it.
Then we got to the question of how we could tell when there were
no data left in the input file. The answer was to call the ifstream
member function fail , which returns zero if some data remain in the
stream and nonzero if we have tried to read past the end of the file. We
used a nonzero return value from fail to trigger a break statement,
which terminates whatever loop contains the break . In this case, the
loop was the one that read data from the input file, so the loop would
stop whenever we got to the end of the input file or when we had read
100 records, whichever came first.
This led to a detailed investigation of whether the number of
records read was always calculated correctly. The problem under
discussion was the potential for a fencepost error, also known as an
off by one error. After careful consideration, I concluded that the code
as written was correct.
Having cleared up that question, we proceeded to some other
scenarios that might occur in the grocery store for which this program
theoretically was being written. All of the scenarios we looked at had
a common requirement: to be able to look up a StockItem , given some
information about it. We first tried to handle this requirement by
reading the UPC directly from each StockItem object in the Vec. When
we found the correct StockItem , we would display and update the
inventory for that StockItem . However, this didn’t compile,
because we were trying to access private member variables of a
StockItem object from a nonmember function, which is illegal. While we
could have changed those variables from private to public , that would
directly contradict the reason that we made them private in the first
place; that is, to prevent external functions from interfering in the inner
workings of our StockItem objects. Therefore, we solved the problem by
adding some new member functions ( CheckUPC and
DeductSaleFromInventory ) to check the UPC of a StockItem and manipulate
the inventory information for each StockItem , respectively. At the same
time, we examined a new data type, bool , which is limited to the two
values true and false ; it is handy for keeping track of information such
as whether we have found the StockItem object we are looking for and
communicating such information back to a calling function.
While I was making these changes, I noticed that the original
version of the test program wasn’t very helpful to its user; it didn’t tell
the user whether the UPC was found, the name of the item, or how
much inventory was available for sale. So I added some more member
functions ( GetInventory and GetName ) to allow this more "user-friendly"
information to be displayed.
Then we progressed to the second of the grocery store scenarios,
in which the task was to find the price of an item, given its UPC. This
turned out to be very similar to the previous problem of finding an
item to update its inventory. Therefore, it was a pretty obvious step to
try to make a function out of the "find an item by UPC" operation,
rather than writing the code for the search again. Since we’re doing
"object-oriented" programming, such a function should probably be a
member function. The question was "of which class ?" It couldn’t be a
member function of StockItem , because the whole idea of this function
was to locate a StockItem . A member function of StockItem needs a
StockItem object to work on, but we didn’t have the StockItem object yet.
The solution was to make another class , called Inventory , which
had member functions to read the inventory information from the disk
file ( LoadInventory ) and search it for a particular StockItem
( FindItem ). Most of this class was pretty simple, but we did run into an
interesting design question: What should the FindItem function return if
the UPC didn’t match anything in the inventory? After some
consideration, I decided to use a null object of the class StockItem ; that
is, one that exists solely to serve as a placeholder representing a non-
existent object. This solution required adding an IsNull member
function to the StockItem class , so that the user of FindItem could
determine whether the returned object was "real" or just an indication
of a UPC that wasn’t found in the inventory.
Then we updated the test program to use this new means of
locating a StockItem . Since the new version of the test program could
perform either of two functions (price check or sale), we also added
some output and input statements to ask the user what he wanted to do.
To make this process more flexible, we allowed the user to type in
either an upper or lower case letter to select which function to
perform. This brought up the use of the "logical OR" operator || to
allow the controlled block of an if statement to be executed if either
(or both) of two expressions is true . We also saw how to combine an
else with a following if statement, when we wanted to select among
more than two alternatives.
We needed two more functions to make this new version of the
application program work correctly: one to update the item in the
inventory ( Inventory::UpdateItem(StockItem Item) ) and one to get the UPC of a
StockItem ( StockItem::GetUPC() ). The reason that we had to add these new
functions to the interfaces of Inventory and StockItem , respectively, is
that we were no longer operating on the "real" StockItem , as we had
been when we accessed the inventory Ve c directly in the previous
version of the application program. Instead, we were getting a copy of
the StockItem from the Inventory object and changing that copy; thus, to
have the final result put back into the Inventory object, we had to add
the UpdateItem member function of Inventory that overwrote the original
StockItem with our changed version. The GetUPC function’s role in all
this was to allow the UpdateItem function to look up the correct
StockItem to be replaced without the main program having to pass the
UPC in explicitly;
instead, the GetUPC function allowed the UpdateItem function to
retrieve the correct UPC from the updated object provided by the main
program.
This brought us to the final scenario, which required us to look up
the inventory for an item, given its UPC. As it happened, we had
already solved that problem by the simple expedient of displaying the
name and inventory of the StockItem as soon as it was located.
Finally, I mentioned a few other factors, such as alternative means
of looking up an item without knowing its UPC, that would be
important in writing a real application program and noted that we
couldn’t go into them here due to space limitations, but would deal
with similar issues later in the book.

6.13. Exercises
1. In a real inventory control program, we would need to do more than
merely read the inventory information in from a disk file, as we
have done in this chapter. We’d also want to be able to write the
updated inventory back to the disk file via an ofstream object, which
is exactly like an ifstream object except that it allows us to write to a
file rather than reading from one. Modify the header files item5.h and
invent1.h to include the declarations of the new functions StockItem::Write
and Inventory::StoreInventory , needed to support this new ability.
2. Implement the new functions that you declared in exercise 1. Then
update the test program to write the changed inventory to a new file.
To connect an ofstream called OutputStream to a file named "test.out",
you could use the line:
ofstream OutputStream("test.out");
6.14. Conclusion

In this chapter, we’ve delved into the concepts and implementations of


classes and objects, which are the constructs that make C++ an object-
oriented language. Of course, we have only scratched the surface of
these powerful topics; in fact, we’ll spend much of the rest of this
book on the fundamentals of classes and objects. Unfortunately, it’s
impossible to cover these constructs in every detail in any one book,
no matter how long or detailed it may be, and I’m not going to try to do
that. Instead, we’ll continue with our in- depth examination of the
basics of object-oriented programming. In the next chapter we’ll start
on the task of creating a string class almost, but not exactly, like the one
from the standard C++ library that we’ve been using so far in this
book.

6.15. Answers to Exercises


1. Here is the new function declaration that needs to be added to the
StockItem interface definition (from code\item6.h):

void Write(ofstream& s);

and the one to be added to the Inventory interface definition (from


code\invent2.h):
void StoreInventory(ofstream& OutputStream);

2. Figure 6.36 shows the implementation of the Write member function


for StockItem , and Figure 6.37 is the implementation of the
StoreInventory member function of the Inventory class . As you can see,
neither of these functions is tremendously complex or, for that
matter, very different from the Display function.
The Write member function for the StockItem class (from
FIGURE 6.36.
code\item6.cpp

void StockItem::Write(ofstream& os)


{
os << m_Name << endl; os <<
m_InStock << endl; os <<
m_Price << endl;
os << m_Distributor << endl; os <<
m_UPC << endl; return;
}

FIGURE 6.37.The StoreInventory member function for the Inventory class


(from code\invent2.cpp)

void Inventory::StoreInventory(ofstream& os)


{
short i;

for (i = 0; i < m_StockCount; i ++)


m_Stock[i].Write(os);
}

Finally, Figure 6.38 shows the changes needed to the application


program to write the updated inventory back to a new file.

FIGURE 6.38. The changes to the application program (from code\itemtst6.cpp)

ofstream OutputStream("shop2.out"); MyInventory.StoreInventory(OutputStream);

Of course in a real program, it would probably be better to write


the updated inventory back to the original file, so that the next time
we ran the program the updated inventory would be used.
However, in the case of a test application, it’s simpler to avoid
modifying the input file so we can run the same test again if
necessary.
Assuming that you’ve installed the software from the CD in the back
of this book, you can try out this program. First, you have to
compile it by following the compilation instructions on the CD.
Then type itemtst6 to run the program. When the program asks for a
UPC you can use 7904886261, which is the (made-up) UPC for
"antihistamines". When the program asks you for a transaction code,
type S for "sale" or P for "price check" and then hit ENTER.
CHAPTER 7 Creating a Homegrown
string class

You may recall the discussion near the beginning of Chapter 6 of


native vs. user-defined variable types. I provided a list of native C++
variable types: char , short , long , float , double , bool , and int . We’ve
already created several classes for our inventory control project, and
now it’s time to apply what we’ve learned to a more generally useful
type, the string . We’ve been using strings for a long time and now it’s
time to see exactly how to implement our own string class . This class is
similar, although not identical, to the standard string class we’ve been
using.
First, though, you may be wondering why we should go through the
trouble of learning how to implement a string class , when the standard
library provides one for us. The answer is simple: creating a string class
illustrates many of the subtle points of creating any class significantly
more complicated than the StockItem class in the previous chapter. Since
the purpose of this book is to teach you C++ programming, we might
as well use instructional classes that at least resemble actual, useful
classes like the string class .
7.1. Objectives of This Chapter

By the end of this chapter, you should


1. Understand how variables of a string class are created, destroyed,
and assigned to one another.
2. Understand how to assign memory to variables where the amount of
memory needed is not known until the program is running.
3. Understand how C strings work and how we can use them in our
string class implementation.
4.
Understand how C string literals can be used to initialize variables
of a string class .

7.2. C String Literals vs. strings

Susan had some questions about these objectives. Here’s the


discussion.

Susan: What is the difference between C string literals and variables of the
string class ?

Steve: A variable of the standard string class is what you’ve been using to store
variable-length alphanumeric data. You can copy them, input them from the
keyboard, assign values to them, and the like. By contrast, a C string literal is just a
bunch of characters in a row; all you can do with it is display it or assign it to a
string variable.

Susan: OK, then you are saying that variables of the string class are what I am
used to working with. On the other hand, a C string literal is just some nonsense that
you want me to learn to assign to something that might make sense? OK, this is
great; sure, this is logical. Hey, a C string literal must be a part of the native language?
Steve: Right all the way along.

Susan: Yes, but why would something so basic as string not be part of the native
language? This is what I don’t understand. And Vecs too; even though they are
difficult, I can see that they are a very necessary evil. So tell me why those basic
things would not be part of the native language?

Steve: That’s a very good question. That decision was made to keep the C++
language itself as simple as possible.1 So rather than include those data types
directly in the language, they were added
as part of the standard library.

Before we get into how to create a string class like the one we’ve been
using in this book, I should expand on the answer I gave Susan as to
why string isn’t a native type in the first place. One of the design goals
of C++, as of C, was to allow the language to be moved, or ported,
from one machine type to another as easily as possible. Since strings ,
vectors , and so on can be written in C++ (i.e., created out of the more
elementary parts of the language), they don’t have to be built in. This
reduces the amount of effort needed to port C++ to a new machine or
operating system. In addition, some applications don’t need and can’t
afford anything but the barest essentials; "embedded" CPUs such as
those in cameras, VCRs, elevators, or microwave ovens, are probably
the most important examples of such applications, and such devices
are much more common than "real" computers.
Even though the standard library strings aren’t native, we’ve been
using them for some time already without having to concern ourselves
with that fact, so it should be fairly obvious that such a class provides
the facilities of a concrete data type; that is, one whose objects can be
created, copied, assigned, and destroyed as though they were native
variables. You may recall from the discussion

1. Which, unfortunately for compiler writers, isn’t very simple.


starting in the section entitled “Concrete Data Types” on page 309 that
such data types need a default constructor, a copy constructor, an
assignment operator, and a destructor. To refresh your memory, here’s
the description of each of these member functions:
1. A default constructor creates an object when there is no initial
value specified for the object.
2. A copy constructor makes a new object with the same contents as an
existing object of the same type.
3. An assignment operator sets an existing object to the value of
another object of the same type.
4. A destructor cleans up when an object expires; for a local object,
this occurs at the end of the block where it was created.

In the StockItem and Inventory class definitions that we’ve created up to


this point, the compiler-generated versions of these functions were
fine for all but the default constructor. In the case of our string class ,
though, we’re going to have to create our own versions of all four of
these functions, for reasons that will become apparent as we examine
their implementations in this chapter and the next one.
Before we can implement these member functions for our string
class , though, we have to define exactly what a string is. A string class is a
data type that gives us the following capabilities in addition to those
facilities that every concrete data type provides:
1. We can set a string to a literal value like "abc" .

2. We can display a string on the screen with the << operator.


3. We can read a string in from the keyboard with the >> operator.
4. We can compare two string s to find out whether they are equal.
5. We can compare two string s to find out which is "less than" the
other; that is, which one would come first in the dictionary.
We’ll see how all of these capabilities work sometime in this chapter
or the next one. But for now, let’s start with Figure 7.1, a simplified
version of the interface specification for our string class that includes
the specification of the four member functions needed for a concrete
data type, as well as a special constructor that is specific to the string
class . I strongly recommend that you print out the files that contain this
interface and its implementation, as well as the test program, for
reference as you are going through this part of the chapter. Those files
are string1.h , string1.cpp , and strtst1.cpp , respectively.

FIGURE 7.1. Our string class interface, initial version (code\string1.h)

class string
{
public:
string();
string(const string& Str);
string& operator = (const string& Str);
~string();

string(char* p);
private:
short m_Length;
char* m_Data;
};

The first four member functions in that interface are the standard
concrete data type functions. In order, they are
1. The default constructor
2. The copy constructor
3. The assignment operator, operator =

4. The destructor
I’ve been instructed by Susan to let you see all of the code that
implements this initial version of our string class at once before we start
to analyze it. Of course I’ve done so, and Figure 7.2 is the result.
FIGURE 7.2. The initial implementation for the string class
(code\string1.cpp)

#include <cstring> using


std::memcpy; using std::strlen;
#include “string1.h”
string::string()
: m_Length(1),
m_Data(new char [m_Length])
{
memcpy(m_Data,””,m_Length);
}

string::string(const string& Str)


: m_Length(Str.m_Length), m_Data(new char
[m_Length])
{
memcpy(m_Data,Str.m_Data,m_Length);
}

string::string(char* p)
: m_Length(strlen(p) + 1), m_Data(new char
[m_Length])
{
memcpy(m_Data,p,m_Length);
}

string& string::operator = (const string& Str)


{
char* temp = new char[Str.m_Length];

m_Length = Str.m_Length; memcpy(temp,Str.m_Data,m_Length);


delete [ ] m_Data; m_Data =

temp;
return *this;
}

string::~string()
{
delete [ ] m_Data;
}

The first odd thing about that implementation file is the #include
<cstring> . So far, we’ve been using #include <string> to tell the compiler
that we want to use the standard C++ library string class . So what is
<cstring> ?
It’s a leftover from C that defines a number of functions that we
will need to implement our own string class , primarily having to do with
memory allocation and copying of data from one place to another. It
used to be called <string.h> , and in fact you can still refer to it by that
name, but all of the C standard library header files have now been
renamed to follow the new C++ standard of no extension. For your
reference, the new name for every C standard library header file
consists of the old name with the “.h” removed and a “c” added to the
beginning.
As for #include “string1.h” , as you can tell from the “” around the
name, that’s one of our own header files; in this case, it’s the header
file where we declare the interface for the first version of our string
class .
There’s one more thing I should explain about the programs in this
chapter and the next: they don’t include the usual line using namespace
std; . This is because we’re not using the standard library string class in
these programs. If we were to include that line, the compiler would
complain that it couldn’t tell which string class we were referring to, the
one from the standard library or the one that we are defining
ourselves.
Now that I hope I’ve cleared up any possible confusion about
those topics, let’s start our examination of our version of string by
looking at the default constructor. Figure 7.3 shows its
implementation.

FIGURE 7.3. The default constructor for our string class (from code\string1.cpp)

string::string()
: m_Length(1),
m_Data(new char [m_Length])
{
memcpy(m_Data,””,m_Length);
}

The member initialization list in this constructor contains two


expressions. The first of them, m_Length(1) , isn’t very complicated at
all. It simply sets the length of our new string to 1. However, this may
seem a bit odd; why do we need any characters at all for a string that
has no value? The answer to this riddle is quite simple, but to
understand it you’ll need to know something about a data type we
haven’t really discussed fully as yet: the C string.
We’ve been using one specific variety of that type, the C string
literal, for quite awhile now. A C string literal is just a literal
sequence of characters terminated by a null byte. A C string is exactly
the same, except that it isn’t necessarily defined literally. That is, a C
string is a sequence of characters, whether or not literally specified,
terminated by a null byte.
Why do we need to worry about this now when we have been able
to ignore it up to this point? Because many pre-existing C functions
that we may want to use in our programs use C strings rather than C++
strings . So, to make our strings as compatible as possible with those
pre-existing C functions, we need to include the null byte that
terminates all C strings. This means that when we calculate the number
of bytes needed to hold the data for a string , we need to reserve one
extra byte of memory beyond the number needed to hold
the actual contents of the string . In the current case of a zero-character
string , this means that we need one byte of storage for the null byte.
Next, it’s important to note that this is an example where the order
of execution of member initializer expressions is important: we
definitely want m_Length to be initialized before m_Data , because the
amount of data being assigned to m_Data depends on the value of
m_Length . As you may remember from Chapter 6, the order in which the
initialization expressions are executed is dependent not on the order
in which they are written in the list, but on the order in which the
member variables being initialized are declared in the class interface
definition. Therefore, i t’s important to make sure that the order in
which those member variables are declared is correct. In this case, it
is, because m_Length is declared before m_Data in string1.h .
Susan had a very good question at this point.

Susan: If the program might not work right if we mess up the order of
initialization, why isn’t it an error to do that? Can’t the compiler tell?

Steve: Very good point. It seems to me that the compiler should be able to tell and
perhaps some of them do. But the one on the CD in the back of the book doesn’t
seem to mind if I write the initialization expressions in a different order than the way
they will actually be executed.

Before proceeding to the next member initialization expression, let’s


take a look at the characteristics of the variables that we’re using here.
The scope of these variables, as we know from our previous
discussion of the StockItem class , is class scope; therefore, each object
of the string class has its own set of these variables, and they are
accessible from any member functions of the class as though they were
global variables.
However, an equally important characteristic of each of these
variables is its data type. The type of m_Length is short , which is a type
we’ve encountered before (a 16-bit integer variable that can hold a
number between -32768 and 32767). But what about the type of the
other member variable, m_Data , which is listed in Figure 7.1 as
char* ? We know what a char is, but what does that * mean?

Pointers

The star means pointer, which is just another term for a memory
address. In particular, char* (pronounced "char star") means "pointer
to a char ".2 The pointer is considered one of the most difficult
concepts for beginning programmers to grasp, but you shouldn’t have
any trouble understanding its definition if you’ve been following the
discussion so far. A pointer is the address of some data item in
memory. That is, to say "a variable points to a memory location" is
almost exactly the same as saying "a variable’s value is the address of
a memory location". In the specific case of a variable x of type char* ,
for example, to say "x points to a C string" is exactly the same as
saying "x contains the address of the first byte of the C string."3 The
m_Data variable is used to hold the address of the first char of the data
that a string contains; the rest of the characters follow the first
character at consecutively higher locations in memory.
If this sounds familiar, it should. A C string literal like "hello"
consists of a number of chars in consecutive memory locations; it
should come as no surprise, then, when I tell you that a C string literal
has the type char* .4
As you might infer from these cases, our use of one char* to refer
to multiple chars isn’t an isolated example. Actually, it’s quite a

2. By the way, char* can also be written as char * , but I find it clearer to attach
the * to the data type being pointed to.
3. C programmers are likely to object that a pointer has some properties that
differ from those of a memory address. Technically, they’re right, but in the
specific case of char* the differences between a pointer and a memory address
will never matter to us.
4. Actually, this isn’t quite correct. The type of a C string literal is slightly
different from char*, but that type is close enough for our purposes here. We’ll
see on page 446 what the exact type is and why it doesn’t matter for our
purposes.
widespread practice in C++, which brings up an important point: a
char* , or any other type of pointer for that matter, has two different
possible meanings in C++.5 One of these meanings is the obvious one
of signifying the address of a single item of the type the pointer points
to. In the case of a char* , that means the address of a char. However, in
the case of a C string literal, as well as in the case of our m_Data
member variable, we use a char* to indicate the address of the first
char of an indeterminate number of chars ; any chars after the first one
occupy consecutively higher addresses in memory. Most of the time,
this distinction has little effect on the way we write programs, but
sometimes we have to be sensitive to this "multiple personality" of
pointers. We’ll run across one of these cases later in this chapter.
Susan had some questions (and I had some answers) on this topic of
the true meaning of a char* :

Susan: What I get from this is that char* points to a char address either
singularly or as the beginning of a string of multiple addresses. Is that right?

Steve: Yes, except that it’s a string of several characters, not addresses.

Susan: Oh, here we go again; this is so confusing. So if I use a string "my name
is" then a char* points to the address that holds the string of all those letters. But
if the number of letters exceeds what the address can hold, won’t it take up the next
available address in memory and the char* point to it after it points to the first
address?

Steve: Each memory address can hold 1 byte; in the case of a


string , that byte is the value of one char of the string’s data. So a
5. As this implies, it’s possible to have a pointer to any type of variable, not just to
a char. For example, a pointer to a s hort would have the type short*, and
similarly for pointers to any other data type, including user-defined types. As
we will see in Chapter 10, pointers to user-defined types are very important in
some circumstances, but we don’t need to worry about them right now.
char* , as we use it, will always point to the first char of the chars that hold our
string’s value; the other chars follow that one immediately in memory.

Susan: Let me ask this: When you show an example of a string with the value
"Test" (Figure 7.8 on page 435), the pointer at address 12340002 containing the
address 1234febc is really pointing at the T as that would be the first char and
the rest of the letters will actually be in the other immediately following bytes of
memory?

Steve: Absolutely correct.

While we’re on the subject of that earlier discussion of C string


literals, you may recall that I bemoaned the fact that such C string
literals use a 0 byte to mark the end of the literal value, rather than
keeping track of the length separately. Nothing can be done about that
decision now, at least as it applies to C string literals. In the case of
our string class , however, the implementation is under our control rather
than the language designer’s; therefore, I’ve decided to use a length
variable ( m_Length ) along with the variable that holds the address of
the first char of the data ( m_Data ).
To recap, what we’re doing in this chapter and the next one is
synthesizing a data type called string . A string needs a length and a set of
characters to represent the actual data in the string . The short named
m_Length is used in the string class to keep track of the number of
characters in the data part of the string ; the char* named m_Data is used
to hold the address of the first character of the data part of the string .
The next member initialization expression, m_Data(new char
[m_Length]) , takes us on another of our side trips. This one has to do
with the (dreaded) topic of dynamic memory allocation.
7.3. Dynamic Memory Allocation via new and
delete

So far, we’ve encountered two storage classes: static and auto . As you
might recall from the discussion in Chapter 5, static variables are
allocated memory when the program is linked, while the memory for
auto variables is assigned to them at entry to the block where they are
defined. However, both mechanisms have a major limitation; the
amount of memory needed is fixed when the program is compiled. In
the case of a string , we need to allocate an amount of memory that in
general cannot be known until the program is executed, so we need
another storage class.
As you will be happy to learn, there is indeed another storage
class called dynamic storage that enables us to decide the amount of
memory to allocate at run time.6 To allocate memory dynamically, we
use the new operator, specifying the data type of the memory to be
allocated and the count of elements that we need. In the member
initialization expression m_Data(new char [m_Length]) , the type is char and
the count is m_Length . The result of calling new is a pointer to the
specified data type; in this case, since we want to store chars , the
result of calling new is a pointer to a char ; that is, a char* . This is a good
thing, because char* is the type of the variable m_Data that we’re
initializing to the address that is returned from new. So the result of the
member initialization expression we’re examining is to set m_Data to
the value returned from calling new ; that value is the address of a
newly assigned block of memory that can hold m_Length chars . In the
case of the default constructor, we’ve asked for a block of 1 byte,
which is just what we need to hold the contents of the zero- length C
string that represents the value of our empty string .

6. This terminology doesn’t exactly match the official nomenclature used by


Bjarne Stroustrup to describe dynamic memory allocation. However, every
C++ programmer will understand you if you talk about dynamic storage, and I
think this terminology is easier to understand than the official terminology.
It may not be obvious why we need to call new to get the address
where we will store our data. Doesn’t a char* always point to a byte in
memory? Yes, it does; the problem is which byte. We can’t use static
(link time) or auto (function entry time) allocation for our string class ,
because each string can have a different number of characters.
Therefore, we have to assign the memory after we find out how many
characters we need to store the value of the string . The new operator
reserves some memory and returns the address of the beginning of that
memory. In this case, we assign that address to our char* variable
called m_Data . An important point to note here is that in addition to
giving us the address of a section of memory, new also gives us the
right to use that memory for our own purposes. That same memory
area will not be made available for any other use until we say we’re
done with it by calling another operator called delete .
Susan had some questions about how (and why) we use new .
Here’s the discussion:

Susan: OK, so all Figure 7.3 does is lay the foundation to be able to acquire
memory to store the C string "" and then copy that information that will go into
m_Data that starts at a certain location in memory?

Steve: Right. Figure 7.6 is the code for the constructor that accomplishes that
task.

Susan: When you say that "the amount of memory needed is fixed when the
program is compiled" that bothers me. I don’t understand that in terms of auto
variables, or is this because that type is known such as a short ?

Steve: Right. As long as the types and the quantity of the data items in a class
definition are known at compile time, as is the case with auto and static variables,
the compiler can figure out the amount of memory they need. The addresses of
auto variables aren’t known at compile time, but how much space they use is.
Susan: OK, I understand the types of the data items. However, I am not sure
what you mean by the quantity; can you give me an example?

Steve: Sure. You might have three chars and four shorts in a particular class
definition; in that case, the compiler would add up three times the length of a char
and four times the length of a short and allocate that much memory (more or less).
Actually, some other considerations affect the size of a class object that aren’t
relevant to the discussion here, but they can all be handled at compile time and
therefore still allow the compiler to figure out the amount of memory needed to store
an object of any class .

The statement inside the {} in the default constructor, namely


memcpy(m_Data,"",m_Length); , is responsible for copying the null byte from
the C string "" to our newly allocated area of memory. The function
memcpy (short for "memory copy") is one of the C standard library
functions for C string and memory manipulation; it is declared in
<cstring> . As you can see, memcpy takes three arguments. The first
argument is a pointer to the destination, that is, the address that will
receive the data. The second argument is a pointer to the source of the
data; this, of course, is the address that we’re copying from (i.e., the
address of the null byte in the "" , in our example). The last argument is
the number of bytes to copy.
In other words, memcpy reads the bytes that start at the address
specified by its input argument (in this case, "" ) and writes a copy of
those bytes to addresses starting at the address specified by its output
argument (in this case, m_Data ). The amount copied is specified by the
length argument (in this case, m_Length ). Effectively, therefore, memcpy
copies a certain amount of data from one place in memory to another.
In this case, it copies 1 byte (which happens to be a null byte) from the
address of the C string literal "" to the address pointed to by m_Data
(that is, the place where we’re storing the data that make up the value
of our string ).
This notion of dynamic allocation was the subject of some more
discussion with Susan.
Susan: This stuff with operator new: I have no idea what you are talking about. I
am totally gone, left in the dust. What is this stuff? Why do you need new to point
to memory locations? I thought that is what char* did?

Steve: You’re right that char* points to a memory location. But which one? The
purpose of new is to get some memory for us from the operating system and return
the address of the first byte of that memory. In this case, we assign that address to
our char* variable called m_Data . Afterward, we can store data at that address.

Susan: I am not getting this because I just don’t get the purpose of char* , and
don’t just tell me that it points to an address in memory. I want to know why we
need it to point to a specific address in memory rather than let it use just any random
address in memory.

Steve: Because then there would be no way of guaranteeing that the memory that
it points to won’t be used by some other part of the program, or indeed some other
program entirely in a multitasking system that doesn’t provide a completely different
memory space for each program. We need to claim ownership of some memory by
calling new before we can use it.

Susan: I think I understand now why we need to use new, but why should the
result of calling new be a pointer? I am missing this completely. How does new
result in char* ?

Steve: Because that’s how new is defined: it gives you an address (pointer) to a
place where you can store some chars (or whatever type you requested).

Susan: OK, but in the statement m_Data = new char [m_Length] , why is char
in this statement not char* ? I am so confused on this.

Steve: Because you’re asking for an address (pointer) to a place where you can
store a bunch of chars .
Susan: But then wouldn’t it be necessary to specify char* rather than char in
the statement?

Steve: I admit that I find that syntax unclear as well. Yes, in my opinion, the type
should be stated as char* , but apparently Bjarne thought otherwise.

Susan: OK, so then m_Data is the pointer address where new


(memory from the free store) is going to store data of m_Length ?

Steve: Almost right. The value assigned to m_Data in the constructor is the
value returned from operator new ; this value is the address of an area of memory
allocated to this use. The area of memory is of length m_Length .

Susan: Well, I thought that the address stored in m_Data was the first place
where you stored your chars . So is new just what goes and gets that memory to
put the address in m_Data ?

Steve: Exactly.

Susan: Here’s what I understand about the purpose of char* . It functions as a


pointer to a specific memory address. We need to do that because the computer
doesn’t know where to put the char data, therefore we need char* to say "hey
you, computer, look over here, this is where we are going to put the data for you to
find and use".

Steve: That’s fine.

Susan: We need to use char* for variable length memory. This is because we
don’t know how much memory we will need until it is used. For this we need the
variable m_Data to hold the first address in memory for our char data. Then we
need the variable m_Length that we have set to the length of the C string that will
be used to get the initial data for the string . Then we have to have that nifty little
helper guy new to get some memory from the free store for the memory of our C
string data.
Steve: Sounds good to me.

Susan: Now about memcpy : This appears to be the same thing as initializing the
variable. I am so confused.

Steve: That’s correct. Maybe you shouldn’t get unconfused!

As the call to memcpy is the only statement in the constructor proper,


it’s time to see what we have accomplished. The constructor has
initialized a string by:
1. Setting the length of the string to the effective length of a null C
string, "", including the terminating null byte (i.e., 1 byte).
2. Allocating memory for a null C string.
3. Copying the contents of a null C string to the allocated memory.
The final result of all this work is a string with the value "" , whose
memory layout might look like Figure 7.4.

FIGURE 7.4. An empty string in


memory

Address Name string n


12340000 m_Length 0001
12340002 m_Data 1234febc

Address Name
1234febc none 00

Using the default constructor is considerably easier than defining it.


As we have seen in Chapter 6, the default constructor is called
whenever we declare an object without specifying any data to
initialize it with (for example, in the line string s; in Figure 7.5).
Although this program doesn’t do anything useful, it does illustrate
how we can use the member functions of our string class , so you should
pay attention to it.

FIGURE 7.5. Our first test program for the string class (code\strtst1.cpp)
#include “string1.h” int
main()
{
string s;
string n(“Test”); string
x;

s = n;
n = “My name is Susan”;

x = n;
return 0;
}

I should point out here that the only file the compiler needs to figure
out how to compile the line string s; is the header file, string1.h . The
actual implementation of the string class in string1.cpp isn’t required here,
because all the compiler cares about when compiling a program using
classes is the contract between the class implementer and the user; that
is, the header file. The actual implementation in string1.cpp that fulfills
this contract isn’t needed until the program is linked to make an
executable; at that point, the linker will complain if it can’t find an
implementation of any function that we’ve referred to.
7.4. Constructing a string from a C String

Now that we’ve disposed of the default constructor, let’s take a look at
the line in our string interface definition (Figure 7.1 on page 409):
string(char* p); .7 This is the declaration for another constructor; unlike the
default constructor we’ve already examined, this one has an argument,
namely, char* p .8
As we saw in Chapter 6, the combination of the function name and
argument types is called the signature of a function. Two functions that
have the same name but differ in the type of at least one argument are
distinct functions, and the compiler will use the difference(s) in the
type(s) of the argument(s) to figure out which function with a given
name should be called in any particular case. Of course, this leads to
the question of why we would need more than one string constructor;
they all make strings , don’t they?
Yes, they do, but not from the same "raw material". It’s true that
every constructor in our string class makes a string , but each constructor
has a unique argument list that determines exactly how the new string
will be constructed. The default constructor always makes an empty
string (like the C string literal ""), whereas the constructor string(char* p)
takes a C string as an argument and makes a string that has the same
value as that argument.
Susan wasn’t going to accept this without a struggle.

Susan: I don’t get "whereas the string(char* p) constructor takes a C string and
makes a string that has the same value as the C string does."

7. I know we’ve skipped the copy constructor, the assignment operator, and the
destructor. Don’t worry, we’ll get to them later.
8. There’s nothing magical about the name p for a pointer. You could call it
George if you wanted to, but it would just confuse people. The letter p is often
used for pointers, especially by programmers who can’t type, which
unfortunately is fairly common.
Steve: Well, when the compiler looks at the statement string n("Test"); it has
to follow some steps to figure it out.

1.The compiler knows that you want to create a string because you’ve defined a
variable called n with the type string ; that’s what string n means.
2.Therefore, since string is not a native data type, the compiler looks for a function
called string::string , which would create a string .
3. However, there can be several functions named string::string , with different
argument lists, because there are several possible ways to get the initial data for the
string you’re creating. In this case, you are supplying data in the form of a C string
literal, whose type is char* ; therefore, a constructor with the signature
string::string(char*) will match.
4.Since a function with the signature string::string(char*) has been declared in the
header file, the line string n("Test"); is translated to a call to that function.

Susan: So string(char* p) is just there in case you need it for "any given situation";
what situation is this?

Steve: It depends on what kind of data (if any) we’re supplying to the
constructor. If we don’t supply any data, then the default constructor is used. If we
supply a C string (such as a C string literal), then the constructor that takes a char*
is used, because the type of a C string is char* .

Susan: So string s; is the default constructor in case you need something that uses
uninitialized objects?

Steve: Not quite; that line calls the default constructor for the string class ,
string::string() , which doesn’t need any arguments, because it constructs an empty
string .

Susan: And the string n ("Test"); is a constructor that finally gets around to telling us
what we are trying to accomplish here?
Steve: Again, not quite. That line calls the constructor
string::string(char* p); to create a string with the value "Test".

Susan: See, you are talking first about string n("Test"); in Figure 7.5 on page
423 and then you get all excited that you just happen to have string::string(char* p)
hanging around which is way over in Figure 7.1 on page 409.

Steve: Now that you know that a C string literal such as "Test" has the data type
char* , does this make sense?

Susan: OK, I think this helped. I understand it better. Only now that I do, it raises
other questions that I accepted before but now don’t make sense due to what I do
understand. Does that make sense to you? I didn’t think so.

Steve: Sure, why not? You’ve reached a higher level of understanding, so you
can now see confusions that were obscured before.

Susan: So this is just the constructor part? What about the default constructor,
what happened to it?

Steve: We can’t use it with the statement string n(“Test”), because we have
some data to assign to the string when the string is created. A default constructor is
used only when there is no initial value for a variable.

Susan: So was the whole point of discussion about default constructors just to let
us know that they exist even though you aren’t really using them here?

Steve: We are using it in the statement string s; to create a string


with no initial value, as discussed before.
Susan wasn’t clear on why the C string "Test" would be of type char* ,
which is understandable because that’s anything but obvious. Here’s
the discussion we entered into on this point.

Susan: When you say "Test" is a C string literal of type char* and that the
compiler happily finds that declaration, that is fine. But see, it is not obvious to me
that it is type char* ; I can see char but not char* . Something is missing here so
that I would be able to follow the jump from char to char* .

Steve: A C string literal isn’t a single char , but a bunch of chars . Therefore,
we need to get the address of the first one; that gives us the addresses of the ones
after it.

Now that the reason why a C string literal is of type char* is a bit
clearer, Figure 7.6 shows the implementation for the constructor that
takes a char* argument.

FIGURE 7.6.The char* constructor for our string class (from


code\string1.cpp)

string::string(char* p)
: m_Length(strlen(p) + 1), m_Data(new char
[m_Length])
{
memcpy(m_Data,p,m_Length);
}

You should be able to decode the header string::string(char* p) . This


function is a constructor for class string (because its class is string and its
name is also string ) and its argument, named p , is of type char* . The
first member initialization expression is m_Length(strlen(p) + 1) . This is
obviously initializing the string’s length ( m_Length ) to something, but
what?
As you may recall, C strings are stored as a series of characters
terminated by a null byte (i.e., one with a 0 value). Therefore, unlike
the case with our strings, where the length is available by looking at a
member variable ( m_Length ), the only way to find the length of a C
string is to search from the beginning of the C string until you get to a
null byte. Since this is such a common operation in C, the C standard
library (which is a subset of the C++ standard library) provides the
function strlen (short for "string length") for this purpose; it returns a
result indicating the number of characters in the C string, not including
the null byte. So the member initialization expression m_Length(strlen(p) +
1) initializes our member variable m_Length to the length of the C
string p , which we compute as the length reported by strlen (which
doesn’t include the terminating null byte) + 1 for the terminating null
byte. We need this information because we’ve decided to store the
length explicitly in our string class rather than relying solely on a null
byte to mark the end of the string, as is done in C.9
Susan had some questions about the implementation of this
function, and I supplied some answers.

Susan: What is strlen ?

9. This is probably a good place to clear up any confusion you might have about
whether there are native and user defined functions; there is no such
distinction. Functions are never native in the way that variables are: built into
the language. Quite a few functions such as strlen and memcp y come with the
language; that is, they are supplied in the standard libraries that you get when
you buy the compiler. However, these functions are not privileged relative to the
functions you can write yourself, unlike the case with native variables in C. In
other words, you can write a function in C or C++ that looks and behaves
exactly like one in the library, whereas it’s impossible in C to add a type of
variable that has the same appearance and behavior as the native types; the
knowledge of the native variable types is built into the C compiler and cannot be
changed or added to by the programmer.
But why aren’t there any native functions? Because the language was designed
to be easy to move (or port) from one machine to another. This is easier if the
compiler is simpler; hence, most of the functionality of the language is provided
by functions that can be written in the "base language" the compiler knows
about. This includes basic functions such as strlen and memcpy , which can be
written in C. For purposes of performance, they are often written in assembly
language instead, but that’s not necessary to get the language running on a new
machine.
Steve: A function left over from C; it tells us how long a C string is.

Susan: Where did it come from?

Steve: It’s from the C standard library, which is part of the C++ standard
library.

Susan: What are you using it for here?

Steve: Finding out how long the C string is that we’re supposed to copy into our string.

Susan: Is this C or C++?

Steve: Both.

Susan: Why is char* so special that it deserves a pointer? What makes it


different?

Steve: The * means "pointer". In C++, char* means "pointer to a


char ".

Susan: I just don’t understand the need for the pointer in char. See when we
were using it ( char ) before, it didn’t have a pointer, so why now? Well, I guess it
was because I thought it was native back then when I didn’t know that there was
any other way. So why don’t you have a pointer to strings then? Are all variables in
classes going to have to be pointed to? I guess that is what I am asking.

Steve: We need pointers whenever we want to allocate an amount of memory


that isn’t known until the program is executing. If we wanted to have a rule that all
strings were 10 characters in length (for example), then we could allocate the space
for those characters in the string . However, we want to be able to handle strings
of any length, so we have to decide how much space to allocate for the
data when the constructor string::string(char* p) is called; the only way to do that is
to use a pointer to memory that is allocated at run time, namely m_Data . Then we
can use that memory to hold a copy of the C string pointed to by the parameter p .

Susan: Oh, no! Here we go again. Is m_Data a pointer? I thought it was just a
variable that held an address.

Steve: Those are equivalent statements.

Susan: Why does it point? (Do you know how much I am beginning to hate that
word?) I think you are going to have to clarify this.

Steve: It "points" in a metaphorical sense, but one that is second nature to


programmers in languages like C. In fact, it merely holds the address of some
memory location. Is that clearer?

Susan: So the purpose of m_Data is just a starting off point in memory?

Steve: Right. It’s the address of the first char used to store the value of the
string .

Susan: So the purpose of m_Length is to allot the length of memory that starts
at the location where m_Data is?

Steve: Close; actually, it’s to keep track of the amount of memory that has been
allocated for the data.

Susan: But I see here that you are setting m_Length to strlen , so that in effect
makes m_Length do the same thing?

Steve: Right; m_Length is the length of the string because it is set to the result
returned by strlen (after adding 1 for the null byte at the end of the C string).
Susan: Why would you want a string with no data, anyway? What purpose does
that serve?

Steve: So you can define a string before knowing what value it will eventually
have. For example, the statement string s; defines a string with no data; the value
can be assigned to the string later.

Susan: Oh yeah, just as you would have short x; . I forgot.

Steve: Yep.

Susan: Anyway, the first thing that helped me understand the need for pointers is
variable-length data. I am sure that you mentioned this somewhere, but I certainly
missed it. So this is a highlight. Once the need for it is understood then the rest falls
in place. Well, almost; it is still hard to visualize, but I can.

Steve: I’ll make sure to stress that point until it screams for mercy.

Susan: I think you might be able to take this information and draw a schematic for
it. That would help. And show the code next to each of the steps involved.

Steve: Don’t worry, we’ll see lots of diagrams later.

Susan: So strlen is a function like any member function?

Steve: Yes, except that it is a global function rather than a member function
belonging to a particular class . That’s because it’s a leftover from C, which doesn’t
have classes .

Susan: So it is what I can consider as a native function? Now I am getting


confused again. I thought that just variables can be either made up ( classes ) or
native. Why are we talking about functions in the same way? But then I remember
that, in a backward way, functions belong to variables in classes rather than the
other way around. This is just so crazy.
Steve: Functions are never native in the way that variables are; that is, built into
the language. A lot of functions come with the language, in the form of the libraries,
but they have no special characteristics that make them "better" than ones you write
yourself. However, this is not true of variables in C, because C doesn’t provide the
necessary facilities for the programmer to add variable types that have the
appearance and behavior of the native types.

Susan: You see I think it is hard for me to imagine a function as one word,
because I am so used to main() with a bunch of code following it and I think of that
as the whole function; see where I am getting confused?

Steve: When we call a function like strlen , that’s not the whole function, it’s just
the name of the function. This is exactly like the situation where we wrote Average
and then called it later to average some numbers.

Susan: A function has to "do something", so you will have to define what the
function does; then when we use the function, we just call the name and that sets the
action in gear?

Steve: Exactly.

Susan: Now, about this char* thing. . . (don’t go ballistic, please) .


. . exactly what is it? I mean it is a pointer to char , so what is * ? Is it like an
assignment operator? How is it classified?

Steve: After a type name, * means "pointer to the type preceding". So char*
means "pointer to char ", short* means "pointer to short ", and so on.

Susan: So that would be for a short with variable-length data? And that would
be a different kind of short than a native short ?
Steve: Almost but not quite correct. It would be for variable-length data consisting
of one or more shorts , just as a C string literal is variable-length data consisting of
one or more chars .

Susan: So it would be variable by virtue of the number of shorts ?

Steve: Actually, by virtue of the possibility of having a number of shorts other


than exactly one. If you might need two or three (or 100, for that matter) shorts (or
any other type), and you don’t know how many when the program is compiled, then
you pretty much have to use a pointer.

Susan: OK, yes, you said that about * and what it means to use a char* , but I
thought it would work only with char so I didn’t know I would be seeing it again
with other variable types. I can’t wait.

Steve: When we use pointers to other types, they will be to user- defined types,
and we’ll be using them for a different purpose than we are using char* . That
won’t be necessary until Chapter 10, so you have a reprieve for the time being.

The next member initialization expression in the constructor,


m_Data(new char [m_Length]) , is the same as the corresponding expression
in the default constructor. In this case, of course, the amount of
memory being allocated is equal to the length of the input C string
(including its terminating null byte), rather than the fixed value 1.
Now that we have the address of some memory that belongs to us,
we can use it to hold the characters that make up the value of our
string . The literal value that our test program uses to call this
constructor is "Test", which is four characters long, not counting the null
byte at the end. Since we have to make room for that null byte, the total
is 5 bytes, so that’s what we’d ask new to give us. Assuming that the
return value from new was 1234febc , Figure 7.7 illustrates what our
new string looks like at this point.
FIGURE 7.7. string n during
construction

Address Name string n


12340000 m_Length 0005
12340002 m_Data 1234febc

Address Name
1234febc none ????

The reason for the ???? is that we haven’t copied the character data
for our string to that location yet, so we don’t know what that location
contains. Actually, this brings up a point we’ve skipped so far: where
new gets the memory it allocates. The answer is that much, if not all, of
the "free" memory in your machine (i.e., memory that isn’t used to
store the operating system, the code for your program, statically
allocated variables, and the stack) is lumped into a large area called
the free store, which is where dynamically allocated memory
"lives".10 When you call new , it cordons off part of the free store as
being "in use" and returns a pointer to that portion.
It’s possible that the idea of a variable that holds a memory
address but which is itself stored in memory isn’t that obvious. It
wasn’t to Susan:

Susan: I don’t get this stuff about a pointer being stored in a memory address and
having a memory address in it. So what’s the deal?

Steve: Here’s an analogy that might help. What happens when there is something
too large to fit into a post office box? One
10. I’m assuming that you are using an operating system that can access all of the
memory in your computer. If not, the free store may be much smaller than this
suggests.
solution is to put the larger object into one of a few large mailboxes, and leave the key
to the larger mailbox in your regular mailbox. In this analogy, the small mailbox is like
a pointer variable and the key is like the contents of that pointer. The large mailbox
corresponds to the memory dynamically allocated by new.

So at this point, we have allocated m_Length bytes of memory, which


start at the address in the pointer variable m_Data . Now we need to
copy the current value of the input C string (pointed to by p ) into that
newly allocated area of memory. This is the job of the sole statement
inside the brackets of the constructor proper,

memcpy(m_Data, p, m_Length);

which copies the data from the C string pointed to by p to our newly
allocated memory.The final result is that we have made (constructed)
a string variable and set it to a value specified by a C string. Figure
7.8 shows what that string might look like in memory.

FIGURE 7.8. string n in memory

Address Name 12340000 m_Length


12340002 m_Data

1234febc none 1234febd none 1234febe none 1234febf none 1234fec0 none

string n

0005
1234febc

But how would this string::string(char* p) constructor


operate in a program? To answer that question, Figure 7.9 gives us
another look at our sample program.
FIGURE 7.9. A simple test
program for the string class (code\strtst1.cpp)
#include “string1.h” int
main()
{
string s;
string n(“Test”); string x;

s = n;
n = “My name is Susan”;

x = n;
return 0;
}

Calling the string(char*) Constructor

How does the compiler interpret the line string n("Test");? First, it
determines that string is the name of a class . A function with the name of a
class , as we have already seen, is always a constructor for that class .
The question is which constructor to call; the answer is determined by
the type(s) of the argument(s). In this case, the argument is a C string
literal, which has the type char* ; therefore, the compiler looks for a
constructor for class string that has an argument of type char* . Since
there is such a constructor, the one we have just examined, the
compiler generates a call to it. Figure 7.10 shows this constructor
again for reference, while we analyze it.

The char* constructor for the string class , again (from


FIGURE 7.10.
code\string1.cpp)

string::string(char* p)
: m_Length(strlen(p) + 1), m_Data(new char
[m_Length])
{
memcpy(m_Data,p,m_Length);
}

When the program executes, string::string(char* p) is called with the


argument "Test". L e t ’s trace the execution of the constructor,
remembering that member initialization expressions are executed in
the order in which the member variables being initialized are listed in
the class interface, not necessarily in the order in which they are
written in the member initialization list.
1. The first member initialization expression to be executed is
m_Length(strlen(p) + 1) . This initializes the member variable m_Length to
the length of the C string whose address is in p , with 1 added for the
null byte that terminates the string. In this case, the C string is "Test",
and its length, including the null byte, is 5.
2. Next, the member initialization expression m_Data(new char [m_Length])
is executed. This allocates m_Length (5, in this case) bytes of
memory from the free store and initializes the variable m_Data to
the address of that memory.
3. Finally, the statement memcpy(m_Data,p,m_Length); copies m_Length bytes
(5, in this case) of data from the C string pointed to by p to the
memory pointed to by m_Data .

When the constructor is finished, the string variable n has a length, 5,


and contents, "Test" , as shown in Figure 7.8. It’s now ready for use in
the rest of the program. After all the discussion, Susan provided this
rendition of the char* constructor for the string class .

Susan: So first we define a class . This means that we will have to have one or
more constructors, which are functions with the same name as the class , used to
create objects of that class . The char* constructor we’re dealing with here goes
through three steps, as follows: Step 1 sets the length of the string ; step 2 gets the
memory to store the data, and provides the address of that memory; step 3 does the
work; it copies what you want.
Steve: Right.

7.5. Assignment Operator Issues

Now, let’s look at the next line: s = n; . That looks harmless enough; it
just copies one string , n , to another string , s . But wait a second; how
does the compiler know how to assign a value to a variable of a type
we’ve made up?
Just as the compiler will generate a version of the default
constructor if we don’t define one, because every object has to be
initialized somehow, the ability to assign one value of a given type to
a variable of the same type is essential to being a concrete data type.
Therefore, the compiler will supply a version of operator = , the
assignment operator, if we don’t define one ourselves. In Chapter 6,
we were able to rely on the compiler-generated operator = , which
simply copies every member variable from the source object to the
target object. Unfortunately, that won’t work here. The reason is that
the member variable m_Data isn’t really the data for the string ; it’s a
pointer to (i.e., the address of) the data. The compiler-generated
operator = , however, wouldn’t be able to figure out how we’re using
m_Data , so it would copy the pointer rather than the data. In our
example, s = n; , the member variable m_Data in s would end up
pointing to the same place in memory as the member variable m_Data in
n . Thus, if either s or n did something to change "its" data, both strings
would have their values changed, which isn’t how we expect
variables to behave.
To make this more concrete, let’s look back at Figure 7.8. So far,
we have an object of type string that contains a length and a pointer to
dynamically allocated memory where its actual data are stored.
However, if we use the compiler-generated operator = to execute the
statement s = n; , the result looks like Figure 7.11.
FIGURE 7.11. string s n and s in memory after compiler-generated =

Address Name 12340000 m_Length


12340002 m_Data

1234febc none 1234febd none 1234febe none 1234febf none 1234fec0 none

12340020 m_Length
12340022 m_Data

string n

’T’
’e’
’s’
’t’
0

In other words, the two strings s and n are like


Siamese twins; whatever affects one of them affects the other, since
they share one copy of the data "Test". What we really want is two
strings that are independent of one another, so that we can change the
contents of one without affecting the other one. Very shortly, we’ll see
how to accomplish this.
As you might suspect, Susan didn’t think the need for us to define
our own operator = was obvious at all. Here’s how I started talking her
into it.

Susan: I have a little note to you off to the side in the margins about this
operator
= , it says "If it was good enough for native data then why not class data?" I think
that is a very good question, and I don’t care about that pointy thing. I don’t
understand why m_Data isn’t really data for the string .
Steve: It isn’t the data itself but the address where the data starts.

Susan: Actually, looking at these figures makes this whole idea more
understandable. Yes, I see somewhat your meaning in Figure 7.11; that pointy thing
is pointing all over the place. Oh no, I don’t want to see how to make two
independent strings ! Just eliminate the pointy thing and it will be all better. OK?

Steve: Sorry, that isn’t possible. You’ll just have to bear with me until I can
explain it to you better.

Susan: Well, let me ask you this: Is the whole point of writing the statement
s=n
just to sneak your way into this conversation about this use of operator = ?
Otherwise, I don’t see where it would make sense for the sample program.

Steve: Yes, that’s correct.

Susan: And the chief reason for creating a new = is that the new one makes a
copy of the data using a new memory address off the free store, rather than having
the pointer pointing to the same address while using the compiler-generated operator
= ? If so, why? Getting a little fuzzy around that point. With StockItem , the compiler-
generated operator = was good enough. Why not now?

Steve: Yes, that’s why we need to create our own operator = . We didn’t need
one before because the components of a StockItem are all concrete data types, so
we don’t have to worry about "sharing" the data as we do with the string class ,
which contains a char* .

Susan: So when you use char* or anything with a pointer, that is outside the
realm of concrete data types?

Steve: Right. However, the reason that we can’t allow pointers to be copied as
with the compiler-generated operator = isn’t that they aren’t concrete data types,
but that they aren’t the actual data of the strings . They’re the address of the actual
data; therefore, if we copy
the pointer in the process of copying a variable, both pointers hold the same address.
This means that changes to one of the variables affects the other one, which is not
how concrete data types behave.

Susan: I think I actually understand this now. At least, I’m not as confused as I
was before.

Steve: Good; it’s working.

7.6. Solving the Assignment Operator Problem

Although it’s actually possible to get the effect of two independent


strings without the extra work of allocating memory and copying data
every time an assignment is done, the mechanisms needed to do that
are beyond the scope of this book.11 By far the easiest way to have the
effect of two independent strings is to actually make another copy of a
string’s data whenever we copy the string, and that’s how we’ll do it here.
The results will be as indicated in Figure 7.12.
With this arrangement, a change to one of the string variables will
leave the other one unaffected, as the user would expect. To make this
happen, we have to implement our own operator = , which will copy the
data rather than just the pointer to the data. That’s the operator
declared in Figure 7.1 by the line:

string& operator = (const string& Str);

What exactly does this mean? Well, as with all function declarations,
the first part of the function declaration indicates the return type of the
function. In this case, we’re going to return a reference to the string to
which we’re assigning a value; that is, the string on the left of the = sign
in an assignment statement. While this may seem
11. We’ll see how to implement a similar feature in another context when we get
back to the discussion of inventory control later.
reasonable at first glance, actually it’s not at all obvious why we
should return anything from operator = . After all, if we say a = b; , after
a has been set to the same value as b , we’re done; that operation is
performed by the = operator, so no return value is needed after the
assignment is completed.

FIGURE 7.12.
strings n and s in memory after custom =

Address Name string n


12340000 m_Length 0005
12340002 m_Data 1234febc

1234febc none
1234febd none
1234febe none
1234febf none
1234fec0 none

string s
12340020 m_Length 0005
12340022 m_Data 12345600

12345600 none
12345601 none
12345602 none
12345603 none
12345604 none

However, there are two reasons why assignment of


native types returns a value, which is equal to the value that was
assigned to the left hand argument of = . First, it allows us to write an
ifstatement such as if (a = b) , when we really meant if (a == b) ; of
course, this will
cause a bug in the program, since these two statements don’t have the
same meaning. The first one sets a to b and returns the value of a ; if a
isn’t 0, then the if condition is considered true . The latter statement, of
course, compares a and b for equality and makes the if condition true
if they are equal. To help prevent the error of substituting = for == in
this situation, many compilers have a warning that indicates your use
of, say, if (a = b) ; unfortunately, this is a legal construct with native
types, and so cannot generate a compiler error. As it happens, using =
in this way is an illegal operation with class objects, so even if you
want to use this error-prone construct, you can’t. Since I never use that
construct with native variables, I don’t mind not having it for class
objects.
The other potential use of the return value from operator = is to
allow statements such as a = b = c; , where the current value of c is
assigned to b and the return value from that assignment is assigned to
a . Although I don’t use that construct either, since I find it more
confusing than useful, I have been told that this return value is required
to use some of the library facilities specified in the C++ standard.
Therefore, it is my obligation to teach you the "right" way to write
assignment operators so that you will be able to use these standard
facilities with your classes .
Now we’re up to the mysterious-looking construct operator = . This
portion of the function declaration tells the compiler the name of the
function we’re defining; namely, operator = . The operator keyword lets
the compiler know that the "name" of this function is actually an
operator name, rather than a "normal" function name. We have to say
operator = rather than merely = , for two reasons. First, because normal
function names can’t have a = character in them, but are limited to
upper and lower case letters, numbers, and the underscore ( _ ).
Second, because when we’re redefining any operator, even one like
new whose name is made of characters allowed in identifiers, we have
to tell the compiler that we’re doing that on purpose. Otherwise, we’ll
get an error telling us that we’re trying to define a function or variable
with the same name as a keyword.12
We’re ready to look at the argument to this function, specified by
the text inside the parentheses, const string& Str. We’ve already seen in
Chapter 6 that & in this context means that the argument to which it
refers is a reference argument rather than a value argument.13 In other
words, the variable Str is actually just another name for the argument
provided by the caller of this function, rather than being a separate
local variable with the same value as the caller’s argument. However,
there is a new keyword in this expression, const , which is short for
"constant". In this context, it means that we promise that this function
will not modify the argument to which const refers, namely string& Str.
This is essential in the current situation, but it will take some
discussion to explain why.

7.7. The const Modifier for Reference


Arguments

As you may recall from our discussion of types of arguments in earlier


chapters, when you call a function using a value argument, the
argument that you supply in the calling function isn’t the one that the
called function receives. Instead, a copy is made of the calling
function’s argument, and the called function works on the copy.

12. As this explanation may suggest, we can’t make up our own operators with
strange names by prefixing those names with op erator; we’re limited to those
operators that already exist in the C++ language.
13. In this section, you’re going to see a lot of hedging of the form "in this context,
x means y". The reason is that C and C++ both reuse keywords and symbols in
many different situations, often with different meanings in each situation. In my
opinion, this is a flaw in the design of these languages as it makes learning them
more difficult. The reason for this reuse is that every time a keyword is added
to the language, it’s possible that formerly working code that contains a variable
or function with the same name as the keyword will fail to compile. Personally,
I think the problem of breaking existing code is overrated compared to the
problems caused by overuse of the same keywords; however, I don’t have a lot
of old C or C++ code to maintain, so maybe I’m biased.
While this is fine most of the time, in this case it won’t work properly
for reasons that will be apparent shortly; instead, we have to use a
reference argument. As we saw in the discussion of reference
arguments in Chapter 6, such an argument is not a copy of the caller’s
argument, but another name for the actual argument provided by the
caller. This has a number of consequences. First, i t ’s often more
efficient than a "normal" argument, because the usual processing time
needed to make a copy for the called function isn’t required. Second,
any changes made to the reference argument change the caller’s
argument as well. The use of this mechanism should be limited to
those cases where it is really necessary, since it can confuse the
readers of the calling function. There’s no way to tell just by looking
at the calling function that some of its variables can be changed by
calling another function.
In this case, however, we have no intention of changing the input
argument. All we want to do is to copy its length and data into the
output string , the one for which operator = was called. Therefore, we
tell the compiler, by using the const modifier, that we aren’t going to
change the input argument. This removes the drawback of non- const
reference arguments: that they can change variables in the calling
function with no indication of that possibility in the calling function.
Therefore, using const reference arguments is quite a useful and safe
way to reduce the number of time-consuming copying operations
needed to make function calls.
However in this case, the use of a const reference argument is
more than just efficient. As we’ll see in the discussion starting under
the heading “The Compiler Generates a Temporary Variable” on page
478 in Chapter 8, such an argument allows us to assign a C string (i.e.,
bytes pointed to by a char* ) to one of our string variables without
having to write a special operator = for that purpose.14
You might be surprised to hear that Susan didn’t have too much
trouble accepting all this stuff about const reference arguments.
Obviously her resistance to new ideas was weakening by this point.
Susan: OK, so the reference operator just renames the argument and doesn’t
make a copy of it; that is why it is important to promise not to change it?

Steve: Right. A non- const reference argument can be changed in the called
function, because unlike a "regular" (i.e., value) argument, which is really a copy of
the calling function’s variable, a reference argument is just another name for the
caller’s variable. Therefore, if we change the reference argument we’re really
changing the caller’s variable, which is generally not a good idea.

Susan: OK. But in this case since we are going to want to change the meaning of
= in all strings it is OK?

Steve: Not quite. Every time we define an operator we’re changing the meaning
of that operator for all objects of that class . The question is whether we’re
intending to change the value of the caller’s variable that is referred to by the
reference argument. If we are, then we can’t use const to qualify the reference; if
not, we can use const . Does that answer your question?

Susan: Well, yes and no. I think I have it now: When you write that code it is for
that class only and won’t affect other classes that you may have written, because it is
contained within that particular class code. Right?

Steve: Correct.

14. Now I can tell you what the real type of a C string literal is. Before the
adoption of the C++ standard, the type actually was char*, as I said on page
414. Now, however, it’s actually a const char*, because you really shouldn’t
change literal values. The reason this difference doesn’t matter is that C++ will
automatically convert the type of a C string literal to char* whenever necessary,
to preserve the behavior of prestandard programs. This shouldn’t affect us,
because we know better than to change the value of a C string literal.
Susan: So we don’t want to change the input argument because we are basically
defining a new = for this class , right?

Steve: Right. The input argument is where we get the data to copy to the string
we’re assigning to. We don’t want to change the input argument, just the string
we’re assigning to.

Back to the discussion of the function declaration, we now have enough


information to decode the function declaration

string& string::operator = (const string& Str)

as illustrated in Figure 7.13.

FIGURE 7.13. The declaration of operator = for the string class

This function returns a refer- ence to a string

It belongs to the string class


It imple- ments oper- ator =

and it promises not to change its argu- ment, which is another name for the caller’s variable

Putting it all together, we’re defining a function belonging to class string


that returns a reference to a string . This function implements operator =
and takes an argument named Str that’s a constant reference to a string .
That is, the argument Str is another name for the
stringpassed to us by the caller, not a copy of the caller’s string .
Furthermore, we’re vowing not to use this argument to change the
caller’s variable.

7.8. Calling operator=

Now that we’ve dissected the header into its atomic components, the
actual implementation of the function should be trivial by comparison.
But first there’s a loose end to be tied up. That is, why was this
function named string::operator = called in the first place? The line that
caused the call was very simple: s = n; . There’s no explicit mention of
string or operator .
This is another of the ways in which C++ supports classes . Because
you can use the = operator to assign one variable of a native type to
another variable of the same type, C++ provides the same syntax for
user defined variable types. Similar reasoning applies to operators
like > , < , and so on, for classes where these operators make sense.
When the compiler sees the statement s = n; , it proceeds as
follows:
1. The variable s is an object of class string .

2. The statement appears to be an assignment statement (i.e., an


invocation of the C++ operator named operator = ) setting s equal to
the value of another string value named n .
3. Is there a definition of a member function of class string that
implements operator = and takes one argument of class string ?
4. Yes, there is. Therefore, translate the statement s = n; into a call to
operator = for class string .

5. Compile that statement as though it were the one in the program.


Susan was appreciative of the reminder that we started out discussing
the statement s = n; .

Susan: Oh, my gosh, I totally forgot about s = n ; thanks for the reminder. We did
digress a bit, didn’t we? Are you saying you have to go through the same thing to
define other operators in classes ?

Steve: Yes.

Susan: So are you saying that when you write the simple statement
s = n; that the = calls the function that we just went through?

Steve: Right.

Following this procedure, the correspondence between the tokens15 in


the original program and the call to the member function should be
fairly obvious, as we see them in Figure 7.14.

FIGURE 7.14. Calling the operator = implementation

s
=

But we’ve left out something. What does the string s correspond to in
the function call to operator = ?

15. A token is the smallest part of a program that the compiler treats as a separate
unit; it’s analogous to a word in English, with a statement being more like a
sentence. For example, string is a token, as are :: and (. On the other hand, x = 5;
is a statement.
The Keyword this

The string s corresponds to a hidden argument whose name is the


keyword this . Such an argument is automatically included in every call
to a member function in C++.16 The type of this is always a constant
pointer to an object of the class that a member function belongs to. In
the case of the string class , its type is const string* ; that is, a constant
pointer to a string ; the const means that we can’t change the value of this
by assigning a new value to it. The value of this is the address of the
class object for which the member function call was made. In this case,
the statement s = n; was translated into s.operator = (n); by the compiler;
therefore, when the statement s = n; is being executed, the value of this
is the address of the string s .
We’ll see why we need to be concerned about this at the end of
our analysis of the implementation of operator = (Figure 7.15).
FIGURE 7.15. The assignment operator ( operator = ) for the string class
(from code\string1.cpp)

string& string::operator = (const string& Str)


{
char* temp = new char[Str.m_Length];

m_Length = Str.m_Length;
memcpy(temp,Str.m_Data,m_Length); delete [ ]
m_Data;

m_Data = temp;

return *this;
}

16. Actually, there is a kind of member function called a static member function
that doesn’t get a t his pointer when it is called. We’ll discuss this type of
member function later, starting in Chapter 9.
This function starts out with char* temp = new char[Str.m_Length] , which
we use to acquire the address of some memory that we will use to
store our new copy of the data from Str. Along with the address, new
gives us the right to use that memory until we free it with delete . The
next statement is m_Length = Str.m_Length; . This is the first time we’ve
used the . operator to access a member variable of an object other
than the object for which the member function was called. Up until
now, we’ve been satisfied to refer to a member variable such as
m_Length just by that simple name, as we would with a local or
global variable. The name m_Length is called an unqualified name
because it doesn’t specify which object we’re referring to. The
expression m_Length by itself refers to the occurrence of the member
variable m_Length in the object for which the current function was
called; i.e., the string whose address is this
(the string s in our example line s = n; ).
If you think about it, this is a good default because member
functions refer to member variables of their "own" object more than
any other kinds of variables. Therefore, to reduce the amount of typing
the programmer has to do, whenever we refer to a member variable
without specifying the object to which it belongs, the compiler will
assume that we mean the variable that belongs to the object for which
the member function was called (i.e, the one whose address is the
current value of this ).
However, when we want to refer to a member variable of an
object other than the one pointed to by this , we have to indicate which
object we’re referring to, which we do by using the . operator. This
operator means that we want to access the member variable (or
function) whose name is on the right of the . for the object whose
name is on the left of the " . ". Hence, the expression Str.m_Length
specifies that we’re talking about the occurrence of m_Length that’s in
the variable Str, and the whole statement m_Length = Str.m_Length; means
that we want to set the length of "our" string (i.e., the one pointed to by
this ) to the length of the argument string Str.
Then we use memcpy to copy the data from Str (i.e., the group of
characters starting at the address stored in Str.m_Data ) to our newly
allocated memory, which at this point in the function is referred to by
temp (we’ll see why in a moment).
Next, we use the statement delete [ ] m_Data; to free the memory
previously used to store our string data. This corresponds to the new
statement that we used to allocate memory for a string in the
constructor string::string(char* p), as shown in Figure 7.6 on page
427.17 That is, the delete operator returns the memory to the available
pool called the free store. There are actually two versions of the delete
operator: one version frees memory for a single data item, and the
other frees memory for a group of items that are stored consecutively
in memory. Here, we’re using the version of the delete operator that
frees a group of items rather than a single item, which we indicate by
means of the [] after the keyword delete ; the version of delete that frees
only one item doesn’t have the [] .18 So after this statement is executed,
the memory that was allocated in the constructor to hold the characters
in our string has been handed back to the memory allocation routines
for possible reuse at a later time.
Susan had a few minor questions about this topic, but nothing too
alarming.

Susan: So delete just takes out the memory new allocated for
m_Data ?

Steve: Right.

17. Or any other constructor that allocates memory in which to store characters.
I’m just referring to the char* constructor because we’ve already analyzed that
one.
18. By the way, this is one of the previously mentioned times when we have to
explicitly deal with the difference between a pointer used as "the address of an
item" and one used as "the address of some number of items"; the [] after
delete tells the compiler that the latter is the current situation. The C++ standard
specifies that any memory that was allocated via a new expression containing
[] must be deleted via delet e []. Unfortunately, the compiler probably can’t
check this. If you get it wrong, your program probably won’t work as intended
and you may have a great deal of difficulty figuring out why. This is one of the
reasons why it’s important to use pointers only inside class implementations,
where you have some chance of using them correctly.
Susan: What do you mean by "frees a group of items"?

Steve: It returns the memory to the free store, so it can be used for some other
purpose.

Susan: Is that all the addresses in memory that contain the length of the string ?

Steve: Not the length of the string , but the data for the string , such as "Test" .

Memory Allocation Errors

A point that we should not overlook is the possibility of calling delete


for a pointer that has never been assigned a value. Calling delete on a
pointer that doesn’t point to a valid block of memory allocated by new
will cause the system to malfunction in some bizarre way, usually at a
time considerably after the improper call to delete .19 This occurs
because the dynamic memory allocation system will try to reclaim the
"allocated" memory pointed to by the invalid pointer by adding it back
to the free store. Eventually, some other function will come along, ask
for some memory, and be handed a pointer to this "available" block
that is actually nothing of the sort. The result of trying to use this area
of memory depends on which of three cases the erroneous address
falls into: the first is that the memory at that address is nonexistent, the
second is that the memory is already in use for some other purpose,
and the third is that the invalid address points to an area in the free
store that is already marked as available for allocation. In the first
case, the function that tries to store its data in this nonexistent area of
memory will cause a system crash or error message, depending on the
system’s ability and willingness to check

19. There’s an exception to this rule: calling delete for a pointer with the value 0
will not cause any untoward effects, as such a pointer is recognized as "pointing
to nowhere".
for such errors. In the second case, the function that is the "legal"
owner of the memory will find its stored values changed mysteriously
and will misbehave as a result. In the third case, the free store
management routines will probably get confused and start handing out
wrong addresses. Errors of this kind are common (and are extremely
difficult to find) in programs that use pointers heavily in uncontrolled
ways.20
Susan was interested in this topic of errors in memory allocation,
so we discussed it.

Susan: Can you give me an example of what an "invalid pointer" would be?
Would it be an address in memory that is in use for something else rather than
something that can be returned to the free store?

Steve: That’s one kind of invalid pointer. Another type would be an address that
doesn’t exist at all; that is, one that is past the end of the possible legal addresses.

Susan: Oh, wait, so it would be returned to the free store but later if it is allocated
to something else, it will cause just a tiny little problem because it is actually in use
somewhere else?

Steve: You bet.

Susan: Oh yeah, this is cool, this is exciting. So this is what really happens when a
crash occurs?

Steve: Yes, that is one of the major causes of crashes.

20. If you are going to develop commercial software someday, you’ll discover that
you may need a utility program to help you find these problems, especially if
you have to work on software designed and written by people who don’t realize
that pointers are dangerous. I’ve had pretty good luck with one called Purify,
which is a product of Rational Software.
Susan: I like this. So when you try to access memory where there is no memory
you get an error message?

Steve: Yes, or if it belongs to someone else. Of course, you’ll be lucky to get


anything other than a hard crash if it’s a DOS program; at least in Windows you’ll
probably get an error message instead.

Another way to go wrong with dynamic memory allocation is the


opposite one. Instead of trying to delete something that was never
dynamically allocated, you can forget to delete something that has been
dynamically allocated. This is called a memory leak; i t ’s very
insidious, because the program appears to work correctly when tested
casually. The usual way to find these errors is to notice that the
program apparently runs correctly for a (possibly long) time and then
fails due to running out of available memory. I should mention here
how we can tell that we’ve run out of memory: the new operator,
rather than returning a value, will " throw an exception" if there is no
free memory left. This will cause the program to terminate if we don’t
do anything to handle it.
Given all of the ways to misuse dynamic memory allocation, we’ll
use it only when its benefits clearly outweigh the risks. To be exact,
we’ll restrict its use to controlled circumstances inside class
implementations, to reduce the probability of such errors.
Susan had some questions about the idea of new throwing an
exception if no memory is left.

Susan: What is "throwing an exception"?

Steve: That’s what new does when it doesn’t have anything to give you. It
causes your program to be interrupted rather than continuing along without noticing
anything has happened.

Susan: How does a real program check for this?

Steve: By code that looks something like Figure 7.16.


FIGURE 7.16. Checking for an
exception from new

try
{
p = new char[1000];
}
catch (...)
{
cout << "You’re hosed!" << endl; exit(1);
}

The try keyword means "try to execute the following statements (called
a try block)", and “catch (...)” means "if any of the statements in the
previous try block generated an exception, execute the following
statements". If the statements in the try block don’t cause an exception
to be generated, then the catch block is ignored and execution
continues at the next statement after the end of the catch block.
Finally, exit means "bail out of the program right now, without
returning to the calling function, if any". The argument to exit is
reported back to DOS as the return value from the program; 0 means
OK, anything else means some sort of error. Of course, it’s better to
take some other action besides just quitting when you run into an
exception, if possible, but that would take us too far afield from the
discussion here.
The error prone nature of dynamic memory allocation is ironic,
since it would be entirely possible for the library implementers who
write the functions that are used by new and delete to prevent, or at
least detect, the problem of deleting something you haven’t allocated
or failing to delete something that you have allocated. After all, those
routines handle all of the memory allocation and deallocation for a
C++ program, so there’s no reason that they couldn’t keep track of
what has been allocated and released.21
Of course, an ounce of prevention is worth a pound of cure, so
avoiding these problems by proper design is the best solution.
Luckily, it is possible to write programs so that this type of error is
much less likely, by keeping all dynamic memory allocation inside
class implementations rather than exposing it to the application
programmer. We’re following this approach with our string class , and it
can also be applied to other situations where it is less straightforward,
as we’ll see when we get back to the inventory control application.
Susan was intrigued by the possible results of forgetting to
deallocate resources such as memory. Here’s the resulting discussion:

Susan: So when programs leak system resources, is that the result of just
forgetting to delete something that is dynamically allocated?

Steve: Yes.

Susan: Then that would be basically a programming error or at least


sloppiness on the part of the programmer?

Steve: Yes.

More on operator =

Having discussed some of the possible problems with dynamic


allocation, let’s continue with the code for operator = (Figure 7.15 on
page 450).
The next line in the function, m_Data = temp; , makes our char*
pointer refer to the newly allocated memory that holds a copy of the
data from the string that we received as an argument (i.e., the one on

21. Actually, most compilers now give you the option of being informed at the end
of execution of your program whether you have had any memory leaks, if you
are running under a debugger, and some will even tell you if you delete memory
that you haven’t allocated (at least in some cases). However, to really be sure
you don’t have such problems, you’ll need to use a utility such as the one I
mentioned before.
the right of the =). Now our target string is a fully independent entity
with the same value as the string that was passed in.
Finally, as is standard with assignment operators, we return *this ,
which means "the object to which this points", i.e., a reference to the
string whose value we have just set, so that it can be used in further
operations.
Susan had some questions about operator = and how it is
implemented.

Susan: Why would you want to copy a variable into another variable anyway?

Steve: Well, let’s say we have an application that keeps track of 300 CDs in a CD
jukebox. One of the things people would probably like to know is the name of the CD
that is playing right now. So you might have a CurrentSelection variable that would
be changed whenever the CD currently playing changed. The CD loading function
would copy the name of the CD being loaded to the CurrentSelection variable.

Steve: Okay. But I still don’t get the . thingy.

Steve: All . does is separate the object (on the left) from the member variable or
function (on the right). So s.operator=(n); might be roughly translated as "apply the
operator = to the object s , with the argument n ".

Susan: So wait: the . does more than separate; it allows access to other string
member variables?

Steve: Right. It separates an object’s name from the particular variable or


function that we’re accessing for that object. In other words, Str.m_Length means
"the instance of m_Length that is part of the object Str ."
Susan: So in the statement m_Length = Str.m_Length; what we are doing is
creating a new m_Length equal to the length of Str ’s m_Length for the =
operator?

Steve: Not exactly. What we’re doing is setting the value of the length
( m_Length ) for the string being assigned to (the left-hand string ) to the same
value as the length of the string being copied from (the right-hand string ).

Susan: But it is going to be specific for this string?

Steve: If I understand your question, the value of m_Length will be set for the
particular string that we’re assigning a new value to.

Susan: When we say Str , does that mean that we are not using the variable
pointed to by this ? I am now officially lost.

Steve: Yes, that’s what it means. In a member function, if we don’t specify the
object we are talking about, it’s the one pointed to by this ; of course, if we do
specify which object we mean, then we get the one we specify.

Although the individual statements weren’t too much of a problem,


Susan didn’t get the big picture. Here’s how I explained it:

Susan: I don’t get this whole code thing for Figure 7.15, now that I think about it.
Why does this stuff make a new operator = ? This is weird.

Steve: Well, what does operator = do? It makes the object on the left side have
the same value as the object on the right side. In the case of a string , this means
that the left-hand string should have the same length as the one on the right, and all
the chars used to store the data for the right-hand string need to be copied to the
address pointed to by m_Data for the left-hand string . That’s what our custom =
does.
Susan: Let’s see. First we have to get some new memory for the new m_Data ;
then we have to make a copy. . . So then the entire purpose of writing a new
operator = is to make sure that variables of that class can be made into separate
entities when using the = sign rather than sharing the same memory address for
their data?

Steve: Right.

Susan: I forget now why we did that.

Steve: We did it so that we could change the value of one of the variables without
affecting the other one.

Before we move on to the next member function, I should mention that


Susan and I had quite a lengthy correspondence about the notion of
this . Here are some more of the highlights of that discussion.

Susan: I still don’t understand this .

Steve: this refers to the object that a member function is being called for. For
example, in the statement xyz.Read(); , when the function named Read is called,
the value of this will be the address of the object xyz .

Susan: OK, then, is this the result of calling a function? Or the address of the
result?

Steve: Not quite either of those; this is the address of the object for which a class
function is called.

Susan: Now that I have really paid attention to this and tried to commit it to
memory it makes more sense. I think that what is so mysterious is that it is a hidden
argument. When I think of an argument I think of something in (), as an input
argument.

Steve: It actually is being passed as though it were specified in every call to a


member function. The reason it is hidden is not to
make it mysterious, but to reduce the amount of work the programmer has to do.
Since almost every member function needs to access something via this , supplying
it automatically is a serious convenience.

Susan: Now as far as my understanding of the meaning of this , it is the address


of the object whose value is the result of calling a member function.

Steve: Almost exactly right; this is the address of the object for which a member
function is called. Is this merely a semantic difference?

Susan: Not quite. Is there not a value to the object? Other than that we are
speaking the same language.

Steve: Yes, the object has a value. However, this is merely the address of the
object, not its value.

Susan: How about writing this as if it were not hidden and was in the argument
list; then show me how it would look. See what I mean? Show me what you think it
would look like if you were to write it out and not hide it.

Steve: OK, that sounds good. I was thinking of doing that anyway.

That hypothetical operator = implementation uses a new notation:


–> , which is the "pointer member access" operator. It separates a
pointer to an object or variable name, on its left, from the member
variable or member function on its right. In other words, –> does the
same thing for pointer variables that . does for objects. That is, if the
token on the right of –> is a member variable, that token refers to the
specific member variable belonging to the object pointed to by the
pointer on the left of –> ; if the token on the right of –> is a member
function, then it is called for the object pointed to by the pointer on the
left of –> . For example, this–>m_Data means "the m_Data that belongs
to the object pointed to by this ".
Given that new notation, Figure 7.17 shows what the code for
operator = might look like if the this pointer weren’t hidden, both in the
function declaration and as a qualifier for the member variable names.

FIGURE 7.17.A hypothetical assignment operator ( operator = ) for the string class
with explicit this

string& string::operator =(const string* this, const string& Str)


{
char* temp = new char[Str.m_Length];

this->m_Length = Str.m_Length;
memcpy(temp,Str.m_Data,this->m_Length); delete [ ]
this->m_Data;

this->m_Data = temp;

return *this;
}

Note that every reference to a member variable of the current object


would have to specify this . That would actually be more significant in
writing the code than the fact that we would have to supply this in the
call. Of course, how we would actually supply this when calling the
operator = function is also a good question. Clearly the necessity of
passing this explicitly would make for a messier syntax than just s
= n; .
The Destructor

Now that we have seen how operator = works in detail, let’s look at the
next member function in the initial version of our string class , the
destructor. A destructor is the opposite of a constructor; that is, it is
responsible for deallocating any memory allocated by the constructor
and performing whatever other functions have to be done before a
variable dies. It’s quite rare to call the destructor for a variable
explicitly; as a rule, the destructor is called automatically when the
variable goes out of scope. As we’ve seen, the most common way for
this to happen is that a function returns to its calling function; at that
time, destructors are called for all local variables that have
destructors, whereas local variables that don’t have destructors, such
as those of native types, just disappear silently.22
Susan had some questions about how variables are allocated and
deallocated. Here’s the discussion that ensued.

Susan: I remember we talked about the stack pointer and how it refers to
addresses in memory but I don’t remember deallocating anything. What is that?

Steve: Deallocating variables on the stack merely means that the same memory
locations can be reused for different local variables.

Susan: Oh, that is right, the data stays in the memory locations until the location is
used by something else. It really isn’t meaningful after it has been used unless it is
initialized again, right?

Steve: Yes, that’s right.

22. If we use new to allocate memory for a variable that has a destructor, then the
destructor is called when that variable is freed by delete. We’ll discuss this
when we get back to the inventory control application.
Susan: When I first read about the destructor my reaction was, "well, what is the
difference between this and delete ?" But basically it just is a function that makes
delete go into auto-pilot?

Steve: Basically correct, for variables that allocate memory dynamically. More
generally, it performs whatever cleanup is needed when a function goes out of
scope.

Susan: How does it know you are done with the variable, so that it can put the
memory back?

Steve: By definition, when the destructor is called, the variable is history. This
happens automatically when it goes out of scope. For an auto variable, whether of
native type or class type, this occurs at the end of the block where the variable was
defined.

Susan: I don’t understand this. I reread your explanation of "going out of scope"
and it is unclear to me what is happening and what the alternatives are. How does a
scope "disappear"?

Steve: The scope doesn’t disappear, but the execution of the program leaves it.
For example, when a function terminates, the local variables (which have local
scope), go out of scope and disappear. That is, they no longer have memory locations
assigned to them, until and unless the function starts execution again.

Susan: What if you need the variable again?

Steve: Then don’t let it go out of scope.

Because destructors are almost always called automatically when a


variable goes out of scope, rather than by an explicit statement written
by the programmer, the only information guaranteed to be available to
a destructor is the address of the variable to be destroyed. For this
reason, the C++ language specifies that a destructor cannot have
arguments. This in turn means that there can be only one
destructor for any class , since there can be at most one function in a
given class with a given name and the same type(s) of argument(s) (or,
as in this case, no arguments).
As with the constructor(s), the destructor has a special name to
identify it to the compiler. In this case, it’s the name of the class with
the token ~ (the tilde) prefixed to it, so the destructor for class string is
named ~string .23 The declaration of this function is the next line in
Figure 7.1 on page 409, ~string(); . Its implementation looks like Figure
7.18.

FIGURE 7.18. The destructor for the string class (from code/string1.cpp)

string::~string()
{
delete [ ] m_Data;
}

This function doesn’t use any new constructs other than the odd
notation for the name of the destructor; we’ve already seen that the
delete [ ] operator frees the memory allocated to the pointer variable it
operates on.24 In this case, that variable is m_Data , which holds the
address of the first one of the group of characters that make up the
actual data contained by the string .
Now that we’ve covered nearly all of the member functions in the
initial version of the string class , it’s time for some review.

23. In case you’re wondering, this somewhat obscure notation was chosen
because the tilde is used to indicate logical negation; that is, if some expression
x has the logical value true, then ~x will have the logical value false, and vice-
versa.
24. By the way, in case you were wondering what happened to the old values of
the m_Dat a and m_Lengt h member variables, we don’t have to worry about
those because the string being destroyed won’t ever be used again.
7.9. Review

We’ve almost finished building our own version of a concrete data


type called string , which provides a means of storing and processing a
group of characters in a more convenient form than a C string. The
fact that string is a concrete data type means that a string that is defined
as a local variable in a block should be created when the code in the
block begins execution and automatically destroyed when the block
ends. Another requirement for a concrete data type is to be able to
copy one variable of that type to another variable of the same type and
have the two copies behave like independent variables, not linked
together in the manner of Siamese twins.
As we’ve previously noted, the creation of an object is performed
by a special member function called a constructor. Any class can have
several constructors, one for each possible way that a newly created
object can be initialized. So far, we’ve examined the interface and
implementation of the default constructor, which takes no arguments,
and a constructor that takes a char* argument. The former is needed to
create a string that doesn’t have a specified initial value, while the
latter allows us to create a string that has the same contents as a C
string. The default constructor is one of the required member functions
in a concrete data type.
We’ve also seen that in the case of our string constructors, we need
to know the order in which the member initialization expressions are
executed. Since this is dependent on the order of declaration of
member variables, we have to make sure that those member variables
are declared in the correct order for our member initialization
expressions to work properly.
Continuing with the requirements for a concrete data type, we’ve
implemented our own version of operator = , which can set one string to
the same value as another string while leaving them independent.
We’ve also created one other required member function for a
concrete data type, the destructor, which is used to clean up after a
string when it expires. This function is called automatically for an auto
variable at the end of the block where the variable is defined.
We’re still short a copy constructor, which can create a string that has
the same value as another pre-existing string . This may sound just like
operator = , but it’s not exactly the same. While operator = is used to set a
string that already exists to the same value as another extant string , the
copy constructor creates a brand-new string with the same value as one
that already exists. We’ll see how this works in the next chapter; in the
meantime, let’s take a look at some exercises intended to test your
understanding of this material.

7.10. Exercises
1. What would happen if we compiled the program in Figure 7.19?
Why?

FIGURE 7.19. Exercise 1 (code\strex1.cpp)

class string
{
public:
string();
string(const string& Str); string(char* p);
string& operator = (const string& Str);
~string(); private:
short m_Length;
char* m_Data;
};

int main()
{
string s;
string n(“Test”); string
x;
short Length;
Length = n.m_Length;

s = n;
n = “My name is Susan”;

x = n;
return 0;
}

2. What would happen if we compiled the program in Figure 7.20?


Why?

FIGURE 7.20. Exercise 2


(code\strex2.cpp)

class string
{
public:
string(const string& Str);
string(char* p);
string& operator=(const string& Str);
~string();
private:
string();
short m_Length;
char* m_Data;
};

int main()
{
string s(“Test”); string n;

n = s;

return 0;
}
3. What would happen if we compiled the program in Figure 7.21?
Why?

FIGURE 7.21. Exercise 3 (code\strex3.cpp)

class string
{
public:
string();
string(const string& Str); string(char* p);
string& operator=(const string& Str); private:
~string();
short m_Length;
char* m_Data;
};

int main()
{
string s(“Test”);

return 0;
}

4.What would happen if a user of our string class wrote an


expression that tried to set a string variable to itself (e.g., a = a; )?

7.11. Conclusion

We’ve covered a lot of material about how a real, generally useful


class such as string works in this chapter. In the next chapter, we’ll
continue with the saga of our string class , finishing up the additional
functionality needed to turn it into a full-fledged concrete data type.
We’ll put this new functionality to the test in a modified version of the
sorting algorithm from the early chapters that sorts strings rather than
numeric values.

7.12. Answers to Exercises


1. The output of the compiler should look something like this:
Error E2247 STREX1.cpp 21: ‘string::m_Length’ is not accessible in function main()
Warning W8004 STREX1.cpp 28: ‘Length’ is assigned a value that is never used in
function main()

This one is simple; since m_Length is a private member variable of


string , a nonmember function such as main can’t access it. Also, the
compiler is warning us that we are never using the value of the
Length variable.

2. The output of the compiler should look something like this:


Error E2247 STREX2.cpp 17: ‘string::string()’ is not accessible in function main()

This is also pretty simple. Since the default constructor string::string()


is in the private area, it’s impossible for a nonmember function such
as main to use it. Notice that there was no error message about
string::string(char* p) ; that constructor is in the public area, so main is
permitted to create a string from a C string. It’s just the default
constructor that’s inaccessible.
3. The output of the compiler should look something like this:
Error E2166 STREX3.cpp 16: Destructor for ‘string’ is not accessible in function main()

This answer is considerably less obvious than the previous ones.


To be sure, the destructor is private and therefore can’t be called
from main , but that doesn’t explain why main is trying to call the
destructor in the first place. The reason is that every auto variable
of a type that has a destructor must have its destructor called at the
end of the function where the auto variable is defined. That’s part of
the mechanism that makes our objects act like "normal" variables,
which also lose their values at the end of the function
where they are declared.25 In the case of a user defined variable,
though, more cleanup may be required; this is certainly true for
strings , which have to deallocate the memory that they allocated to
store their character data.
Therefore, you cannot create an object of a class whose destructor
is private as an auto variable, as the automatic call of the destructor at
the end of the scope would be illegal.
Susan didn’t get this one exactly right, but she was close.

Susan: I have a note here that this program would not work because the ~string
() thingy should be public and that, if this were to run, it would cause a memory
leak. Am I on the right track?

Steve: Yes, you’re close. Actually, what would happen is that the compiler would
refuse to compile this program because it wouldn’t be able to call ~string at the end
of the function, since ~string is private . If the compiler would compile this program
without calling the destructor, there would indeed be a memory leak.

4. Let’s take a look at the sequence of events that would have


transpired if the user had typed a = a; .26 The first statement to be
executed would be char* temp = new char[Str.m_Length] , which would
allocate some new memory to store the contents of the string .
25. To be more precise, the destructor is called at the end of the scope in which
the variable was defined. It’s possible for a variable to have scope smaller than
an entire function; in that case, the variable is destroyed when its scope
expires.
26. See Figure 7.15 on page 450 for the code for op erator =.
The second statement to be executed would be m_Length =
Str.m_Length; . Since m_Length and Str.m_Length are actually the same
memory location in this case, this statement wouldn’t do anything.
Then the statement memcpy(temp,Str.m_Data,m_Length); would be executed.
This would copy m_Length bytes of data to the address stored in
temp , which points to the newly allocated piece of memory, from the
address stored in Str.m_Data.
The next statement to be executed would be delete [ ] m_Data; , which
would free the memory previously allocated to our string .
The next statement to be executed would be m_Data = temp; . This
would cause our string to refer to the newly allocated memory,
which contains a copy of the data from the right hand string (in this
case, the same string we are assigning to).
Finally, we would return *this , as we normally do from an
assignment operator.
The net result of all of this is that the m_Data member variable of
string a would point to a copy of the same data that it originally
pointed to; in other words, the assignment statement would work
correctly even for this case.
Susan had a very concise summary of this process:

Susan: So the data for the original string was stolen and replaced with an exact
replica?

Steve: Wright.27

27. See http://www.wam.umd.edu/~stwright/right/StevenWright.html for more


Steve Wright jokes.
CHAPTER 8 Finishing Our
homegrown string class

8.1. Objectives of This Chapter

By the end of this chapter, you should


1. Understand how to implement all the concrete data type functions for
a class that uses pointers, namely our homegrown string class .
2. Understand in detail the operation and structure of a string class
that is useful in some real programming situations.
3. Understand how to write appropriate input and output functions
( operator >> and operator << ) for the objects of our string class .
4. Understand how to use some additional C library functions such as
memcmp and memset .

5. Understand the (dreaded) C data type, the array, and some of the
reasons why it is hazardous to use.
6. Understand the friend declaration, which allows access to private
members by selected nonmember functions.
Why We Need a Reference Argument for operator =

Now we’re finally ready to examine exactly why the code for our
operator = needs a reference argument rather than a value argument.
I’ve drawn two diagrams that illustrate the difference between a value
argument and a reference argument. First, Figure 8.1 illustrates what
happens when we call a function with a value argument of type string
using the compiler-generated copy constructor.1

FIGURE 8.1. Call by value ("normal argument") using


the compiler- generated copy constructor

Address Name caller’s


argument
12340000 m_Length 0005
12340002 m_Data 1234febc

1234febc none ’T’


1234febd none ’e’
1234febe none ’s’
1234febf none ’t’
1234fec0 none 0

12340020 m_Length
12340022 m_Data

In other words, with a value argument, the called routine makes a copy
of the argument on its stack. This won’t work properly with a string
argument; instead, it will destroy the value of the caller’s variable
upon return to the calling function. Why is this?

1. If this diagram looks familiar, it’s the same as the one illustrating the problem
with the compiler-generated op erator =, Figure 7.11, except for labels.
Premature Destruction

The problem occurs when the destructor is called at the end of a


function’s execution to dispose of the copy of the input argument made
at entry. Since the copy points to the same data as the caller’s original
variable, the destruction of the copy causes the memory allocated to
the caller’s variable to be freed prematurely.
This is due to the way in which a variable is copied in C++ by the
compiler-generated copy constructor. This constructor, like the
compiler-generated operator = , makes a copy of all of the parts of the
variable (a so-called memberwise copy). In the case of our string
variable, this results in copying only the length m_Length and the
pointer m_Data , and not the data that m_Data points to. That is, both the
original and the copy refer to the same data, as indicated by Figure
8.1. If we were to implement our operator = with a string argument
rather than a string& argument, then the following sequence of events
would take place during execution of the statement s = n; :
1. A default copy like the one illustrated by Figure 8.1 would be made
of the input argument n , causing the variable Str in the operator =
code to point to the same data as the caller’s variable n .
2. The Str variable would be used in the operator = code.
3. The Str variable would be destroyed at the end of the operator =
function. During this process, the destructor would free the memory
that Str.m_Data points to by calling delete [] .

Since Str.m_Data holds


the same address as the caller’s variable
n.m_Data , the latter now points to memory that has been freed and may
be overwritten or assigned to some other use at any time. This is a bug
in the program caused by the string destructor being called for a
temporary copy of a string that shares data with a caller’s variable.
When we use a reference argument, however, the variable in the
called function is nothing more (and nothing less) than another name
for the caller’s variable. No copy is made on entry to the operator =
code; therefore, the destructor is not called on exit. This allows the
caller’s variable n to remain unmolested after operator = terminates.
That may sound good, but Susan wanted some more explanation.

Susan: I don’t get why a value argument makes a copy and a reference argument
doesn’t. Help.

Steve: The reason is that a argument:valuevalue argument is actually a new auto


variable, just like a regular auto variable, except that it is initialized to the value of
the caller’s actual argument. Therefore, it has to be destroyed when the called
function ends. On the other hand, a reference argument just renames the caller’s
variable; since the compiler hasn’t created a new auto variable when the called
routine starts, it doesn’t need to call the destructor to destroy that variable at the end
of the routine.

Figure 8.2 helped her out a bit by illustrating the same call as in
Figure 8.1, using a reference argument instead of a value argument.

FIGURE 8.2. Call by reference

Address Name 12340000 m_Length


12340002 m_Data

1234febc none 1234febd none 1234febe none 1234febf none 1234fec0 none

Caller’s string n

and called routine’s string Str


Finally, we’ve finished examining the intricacies that
result from the apparently simple statement s = n; in our test program
(Figure 8.3).
FIGURE 8.3. Our first test program for the string class (code\strtst1.cpp)
#include “string1.h” int
main()
{
string s;
string n(“Test”); string
x;

s = n;
n = “My name is Susan”;

x = n;
return 0;
}

Now let’s take a look at the next statement in that test program, n = "My
name is Susan"; . The type of the C string literal expression "My name is
Susan" is char* ; that is, the compiler stores the character data
somewhere and provides a pointer to it. In other words, this line is
attempting to assign a char* to a string . Although the compiler has no
built-in knowledge of how to do this, we don’t have to write any more
code to handle this situation because the code we’ve already written
is sufficient. That’s because if we supply a value of type char* where a
string is needed, the constructor string::string(char*) is automatically
invoked. Such automatic conversion is another of the features of C++
that makes user defined types more like native types.2
The sequence of events during compilation of the line n = "My name is
Susan"; is something like this:

2. There are situations, however, where this usually helpful feature is


undesirable; for this reason, C++ provides a way of preventing the compiler
from supplying such conversions automatically. We’ll see how to do that in
Chapter 12.
1. The compiler sees a string on the left of an =, which it interprets as
a call to some version of string::operator = .
2. It looks at the expression on the right of the = and sees that its type is
not string , but char* .
3. Have we defined a function with the signature string::operator =
(char*) ? If so, use it.

4. In this case, we have not defined such an operator. Therefore, the


compiler checks to see whether we have defined a constructor with
the signature string::string(char*) for the string class .
5. Yes, there is such a constructor. Therefore, the compiler interprets
the statement as n.operator = (string("My name is Susan")); . If there were no
such constructor, that line would be flagged as an error.

So the actual interpretation of n = "My name is Susan"; is n.operator =


(string("My name is Susan")); . What exactly does this do?
Figure 8.4 is a picture intended to illustrate the compiler’s
"thoughts" in this situation; that is, when we assign a C string with the
value "My name is Susan" to a string called n via the constructor
string::string(char*) .3

The Compiler Generates a Temporary Variable

Let’s go over Figure 8.4, step by step. The first thing that the compiler
does is to call the constructor string::string(char*) to create a temporary
(jargon for temporary variable) of type string , having the value "My
name is Susan" . This temporary is then used as the argument to the
function string::operator = (const string& Str) (see Figure 7.15).

3. Rather than showing each byte address of the characters in the strings and C
strings as I’ve done in previous diagrams, I’m just showing the address of the
first character in each group, so that the figure will fit on one page.
FIGURE 8.4. Assigning a char* value to a string via string::string(char*)

Step 1: Create temporary string from char* value

Address Name 12330000 none

char* value
"My name is Susan"

12340020 m_Length
12340022 m_Data

12345600 none

temporary string

Step 2: Use operator = to assign value of temporary to n

Address Name 12340120 m_Length


12340122 m_Data

12345700 none

string n
Step 3: Call destructor for temporary string

Since the argument is a reference, no copy is made of the temporary;


the variable Str in the operator = code actually refers to the (unnamed)
temporary variable. When the operator = code is finished executing, the
string n has been set to the same value as the temporary (i.e., "My
name is Susan" ). Upon return from the operator = code, the temporary is
automatically destroyed by a destructor call inserted by the compiler.
This sequence of events also holds the key to understanding why
the argument of string::operator = must be a const string& (that is, a constant
reference to a string ) rather than just a string& (that is, a reference to a
string ) if we want to allow automatic conversion from a C string to a
string . You see, if we declared the function string::operator = to have a
string& argument rather than a const string& argument, then it would be
possible for that function to change the value of the argument.
However, any attempt to change the caller’s argument wouldn’t work
properly if, as in the current example, the argument turned out to be a
temporary string constructed from the original argument (the char* value
"My name is Susan" ); clearly, changing the temporary string would have no
effect on the original argument. Therefore, if the argument to
string::operator = were a string& , the line n = "My name is Susan"; would
produce a compiler warning to the effect that we might be trying to
alter an argument that was a temporary value. The reason we don’t get
this warning is that the compiler knows that we aren’t going to try to
modify the value of an argument that has the specification const string& ;
therefore, constructing a temporary value and passing it to string::operator
= is guaranteed to have the behavior that we want.4
This example is anything but intuitively obvious and as you might
imagine, led to an extended discussion with Susan.

Susan: So no copy of the argument is made, but the temporary is a copy of the
variable to that argument?

Steve: The temporary is an unnamed string created from the C string literal that
was passed to operator = by the statement n = "My name is Susan"; .
4. By the way, the compiler insists that a function isn’t going to modify an
argument with the const specifier; if we wrote a function with a const argument
and then tried to modify such an argument, it wouldn’t compile.
Susan: Okay. But tell me this: Is the use of a temporary the result of specifying a
reference argument? If so, then why don’t you discuss this when you first discuss
reference arguments?

Steve: It’s not exactly because we’re using a reference argument. When a
function is called with the "wrong" type of argument but a constructor is available to
make the "right" type of argument from the "wrong" one that was supplied, then the
compiler will supply the conversion automatically. In the case of calling operator =
with a char* argument rather than a string , there is a constructor that can make a
string from a char* . Therefore, the compiler will use that constructor to make a
temporary string out of the supplied char* and use that temporary string as the
actual argument to the function operator = . However, if the argument type were
specified as a string& rather than a const string& , then the compiler would warn
us that we might be trying to change the temporary string that it had constructed.
Since we have a const string& argument, the compiler knows that we won’t try to
change that temporary string , so it doesn’t need to warn us about this possibility.

Susan: Well, I never looked at it that way, I just felt that if there is a constructor
for the argument then it is an OK argument.

Steve: As long as the actual argument matches the type that the constructor
expects, there is no problem.

Susan: So, if the argument type were a string& and we changed the temporary
argument, what would happen? I don’t see the problem with changing something that
was temporary; I see that it would be a problem for the original argument but not the
temporary.

Steve: The reason why generating a temporary is acceptable in this situation is


that the argument is a const reference. If we didn’t add the const in front of the
argument specifier, then the compiler would warn us about our possibly trying to
modify the temporary. Since we have a const reference, the compiler knows that we
won’t
try to modify the argument and thus it’s safe for the compiler to generate the
temporary value.

Susan: OK, then the temporary is created any time you call a reference
argument? I thought that the whole point of a temporary was so you could modify it
and not the original argument and the purpose of the const was to ensure that
would be the case.

Steve: The point is precisely that nothing would happen to the original argument if
we changed the copy. Since one of the reasons that reference arguments are
available is to allow changing of the caller’s argument, the compiler warns us if we
appear to be interested in doing that (a non- const reference argument) in a situation
where such a change would have no effect because the actual argument is a
temporary.

Susan: So, if we have a non- const string& argument specification with an actual
argument of type char* then a temporary is made that can be changed (without
affecting the original argument). If the argument is specified as a const string& and
the actual argument is of type char* then a temporary is made that cannot be
changed.

Steve: You’ve correctly covered the cases where a temporary is necessary, but
haven’t mentioned the other cases. Here is the whole truth and nothing but the truth:

1. If we specify the argument type as string& and a temporary has to be created because
the actual argument is a char* rather than a string , then the compiler will warn us that
changes to that temporary would not affect the original argument.
2.If we specify the argument type as const string& and a temporary has to be created
because the actual argument is a char* , then the compiler won’t warn us that our
(hypothetical) change would be ineffective, because it knows that we aren’t going to
make such a change.
3. However, if the actual argument is a string , then no temporary needs to be made in
either of these cases ( string& or const string& ). Therefore, the argument that we see
in the function is actually the real argument, not a temporary, and the compiler won’t warn
us about trying to change a (nonexistent) temporary.

Susan: OK, this clears up another confusion I believe, because I was getting
confused with the notion of creating a temporary that is basically a copy but I
remember that you said that a reference argument doesn’t make a copy; it just
renames the original argument. So that would be the case in 3 here, but the
temporary is called into action only when you have a situation such as in 1 or 2,
where a reference to a string is specified as the argument type in the function
declaration, while the actual argument is a char* .

Steve: Right.

8.2. The string Copy Constructor

Assuming you’ve followed this so far, you might have noticed one
loose end. What if we want to pass a string as a value argument to a
function? As we have seen, with the current setup bad things will
happen since the compiler-generated copy constructor doesn’t copy
strings correctly. Well, you’ll be relieved to learn that this, too, can be
fixed. The answer is to implement our own version of the copy
constructor. Let’s take another look at the header file for our string class ,
in Figure 8.5.

FIGURE 8.5. The string class interface (code\string1.h)

class string
{
public:
string();
string(const string& Str);
string& operator = (const string& Str);
~string();

string(char* p);
private:
short m_Length;
char* m_Data;
};

The line we’re interested in here is string(const string& Str); . This is a


constructor, since its name is the class name string . It takes one
argument, the type of which is const string& ( a reference to a constant
string ). This means that we’re not going to change the argument’s value
"through" the reference, as we could do via a non- const reference. The
code in Figure 8.6 implements this new constructor.

FIGURE 8.6.The copy constructor for the string class ( from


code\string1.cpp)

string::string(const string& Str)


: m_Length(Str.m_Length), m_Data(new char
[m_Length])
{
memcpy(m_Data,Str.m_Data,m_Length);
}

This function’s job is similar to that of operator = , which makes sense


because both of these functions are in the copying business. However,
there are also some differences; otherwise, we wouldn’t need two
separate functions.
One difference is that because a copy constructor is a constructor,
we can use a member initialization list to initialize the member
variables; this convenience is not available to operator = , as it is not a
constructor.
The other difference is that we don’t have to delete any previously
held storage that might have been assigned to m_Data . Of course, this
is also because we’re building a new string , not reusing one that
already exists. Therefore, we know that m_Data has never had any
storage assigned to it previously.
One fine point that might have slipped past you is why we can’t
use a value argument rather than a reference argument to our copy
constructor. The reason is that using a value argument of a class type
requires a copy of the actual argument to be made, using ... the copy
constructor! Obviously this won’t work when we’re writing the copy
constructor for that type, so the compiler will let us know if we try to
do this accidentally.
Now that we have a correct copy constructor, we can use a string
as a value argument to a function, and the copy that’s made by the
compiler when execution of the function starts will be an independent
string , not connected to the caller’s variable. When this copy is
destroyed at the end of the function, it will go away quietly and the
caller’s original variable won’t be disturbed. This is all very well in
theory, but it’s time to see some practice. Let’s write a function that we
can call with a string to do some useful work, like displaying the
characters in the string on the screen.

Screen Output

As I hope you remember from the previous chapters, we can send


output to the screen via cout , a predefined output destination. For
example, to write the character ’a’ to the screen we could use the
statement cout << ’a’; . Although we have previously used cout and <<
to display string variables of the standard library string class , the current
version of our string class doesn’t support such output. If we want this
ability, we’ll have to provide it ourselves. Since variables that can’t
be displayed are limited in usefulness, we’re going to start to do just
that right now. Figure 8.7 is the updated header file.
I strongly recommend that you print out the files that contain this
interface and its implementation, as well as the test program, for
reference as you are going through this part of the chapter; those files
are string3.h , string3.cpp , and strtst3.cpp , respectively.

FIGURE 8.7.The string class interface, with Display function


(code\string3.h)

class string
{
public:
string();
string(const string& Str);
string& operator = (const string& Str);
~string();

string(char* p);
void Display();

private:
short m_Length;
char* m_Data;
};

As you can see, the new function is declared as void Display(); . This
means that it returns no value, its name is Display , and it takes no
arguments. This last characteristic may seem odd at first, because
surely Display needs to know which string we want to display.
However, as we’ve already seen, each object has its own copy of all
of the variables defined in the class interface. In this case, the data that
are to be displayed are the characters pointed to by m_Data .
Figure 8.8 is an example of how Display can be used.
FIGURE 8.8. The latest version of the string class test program, using the
Display function
(code\strtst3.cpp)

#include <iostream>
#include “string3.h”
int main()
{
string s;
string n(“Test”); string
x;

s = n;
n = “My name is Susan”;

n.Display();
return 0;
}

See the line that says, n.Display(); ? That is how our new Display function
is called. Remember, it’s a member function, so it is always called
with respect to a particular string variable; in this case, that variable is
n.
As is often the case, Susan thought she didn’t understand this idea,
but actually did.

Susan: This Display stuff... I don’t get it. Do you also have to write the code
the classes need to display data on the screen?

Steve: Yes.

Before we get to the code for the Display function, let’s take a look at
the first few lines of the implementation file for this new version of the
string class , which is shown in Figure 8.9.

FIGURE 8.9.The first few lines of the latest implementation of the string class
(from string3.cpp)

#include <iostream>
#include <string.h>
using std::cout;
The first two lines of this file aren’t anything new: all they do is
specify two header files from the standard C++ library to allow us to
access the streams and C string functions from that library. But the third
line, “ using std::cout; ” is new. What does it do?
Up until now, our only use of using has been to import all the
names from the std namespace , where the names from the standard
library are defined. When we are writing our own string class , however,
we can’t import all those names without causing confusion between
our own string class and the one from the standard library.
Luckily, there is a way to import only specific names from the
standard library. That’s what that line does: it tells the compiler to
import the specific name cout from the standard library. Therefore,
whenever we refer to cout without qualifying which cout we mean, the
compiler will include std::cout in the list of possible matches for that
name. We’ll have to do that once in a while, whenever we want to use
some standard library names, and some of our own.
Susan had some questions about this new use of using .

Susan: What does this new using thing do that’s different from the old one?

Steve: When we say using namespace std; , that means that we want all the
names from the standard library to be “imported” into the present scope. In this
case, however, we don’t want it to do that because we have written our own string
class . If we wrote using namespace std; , the compiler would get confused
between the standard library string class and our string class . For that reason, we
will just tell the compiler about individual names from the standard library that we
want it to import, rather than doing it wholesale.

Now that that’s cleared up, let’s look at the implementation of this new
member function, Display , in Figure 8.10.
FIGURE 8.10.The string class implementation of the Display function (from
string3.cpp)

void string::Display()
{
short i;

for (i = 0; i < m_Length-1; i ++) cout


<< m_Data[i];
}

This should be looking almost sensible by now; here’s the detailed


analysis. We start out with the function declaration, which says we’re
defining a void function (i.e., one that returns no value) that is a
member function of the class string . This function is named Display , and
it takes no arguments. Then we define a short called i . The main part
of the function is a for loop, which is executed with the index starting
at 0 and incremented by one for each character, as usual. The for loop
continues while the index is less than the number of displayable
characters that we use to store the value of our string ; of course, we
don’t need to display the null byte at the end of the string . So far, so
good. Now comes the tricky part. The next statement, which is the
controlled block of the for loop, says to send something to cout ; that
is, display it on the screen. This makes sense, because after all that’s
the purpose of this function. But what is it that is being sent to cout ?

The Array

It’s just a char but that may not be obvious from the way it’s written.
This expression m_Data[i] looks just like a Vec element, doesn’t it? In
fact, m_Data[i] is an element, but not of a Vec. Instead, it’s an element of
an array, the C equivalent of a C++ Vec.
What’s an array? Well, it’s a bunch of data items (elements) of the
same type; in this case, it’s an array of chars . The array name, m_Data
in this case, corresponds to the address of the first of these
elements; the other elements follow the first one immediately in
memory. If this sounds familiar, it should; it’s very much like Susan’s
old nemesis, a pointer. However, like a Vec, we can also refer to the
individual elements by their indexes; so, m_Data[i] refers to the i th
element of the array, which in the case of a char array is, oddly
enough, a char.
So now it should be clear that each time through the loop, we’re
sending out the i th element of the array of chars where we stored our
string data.
Susan and I had quite a discussion about this topic, and here it is.

Susan: If this is an array or a Vec (I can’t tell which), how does the
class know about it if you haven’t written a constructor for it?

Steve: Arrays are a native feature of C++, left over from C. Thus, we don’t have
to (and can’t) create a constructor and the like for them.

Susan: The best I can figure out from this discussion is that an array is like a Vec
but instead of numbers it indexes char data and uses a pointer to do it?

Steve: Very close. I t ’s just like a Ve c except that it ’s missing some rather
important features of a Vec. The most important one from our perspective is that an
array doesn’t have any error-checking; if you give it a silly index you’ll get something
back, but exactly what is hard to determine.

Susan: If it is just like a Vec and it is not as useful as a real Vec , then why use
it? What can it do that a Vec can’t?

Steve: A Vec isn’t a native data type, whereas an array is. Therefore, you can
use arrays to make Vecs, which is in fact how a Vec is implemented at the lowest
level. We also wouldn’t want to use Vecs to hold our string data because they’re
much more "expensive" (i.e., large and slow) to use. I’m trying to illustrate how we
could make a string class that would resemble one that would
actually be usable in a real program and although simplicity is important, I didn’t want
to go off the deep end in hiding arrays from the reader.

Susan: So when you say that "we’re sending out the i th element of the array of
chars where we stored our value" does that mean that the " i th" element would be
the pointer to some memory address where char data is stored?

Steve: Not exactly. The i th element of the array is just like the i th element of a
Vec . If we had a Vec of four chars called x, we’d declare it as follows:

Vec<char> x(4);

Then we could refer to the individual chars in that Vec as x[0] , x[1] , x[2] , or x[3] .
It’s much the same for an array. If we had an array of four chars called y, we’d
declare it as follows:

char y[4];

Then we could refer to the individual chars in that array as y[0] , y[1] , y[2] , or
y[3] .

That’s all very well, but there’s one loose end. We defined m_Data as a
char* , which is a pointer to (i.e., the address of) a char. As is common
in C and C++, this particular char* is the first of a bunch of chars one
after the other in memory. So where did the array come from?

The Equivalence of Arrays and Pointers

Brace yourself for this one. In C++, a pointer and the address of an
array are for almost all purposes the same thing. You can treat an array
address as a pointer and a pointer as an array address, pretty much as
you please. This is a holdover from C and is necessary for
compatibility with C programs. People who like C will tell you how
"flexible" the equivalence of pointers and arrays is in C. That’s true,
but it’s also extremely dangerous because it means that arrays have no
error checking whatsoever. You can use whatever index you feel like,
and the compiled code will happily try to access the memory location
that would have corresponded to that index. The program in Figure
8.11 is an example of what can go wrong when using arrays.

FIGURE 8.11. Dangerous


char acters (code\dangchar.cpp)

#include <iostream>
using std::cout; using
std::endl;

int main()
{
char High[10]; char
Middle[10]; char
Low[10]; char*
Alias; short i;

for (i = 0; i < 10; i ++)


{
Middle[i] = ‘A’ + i;
High[i] = ‘0’;
Low[i] =’1’;
}

Alias = Middle;

for (i = 10; i < 20; i ++)


{
Alias[i] = ‘a’ + i;
}

cout << “Low: “;


for (i = 0; i < 10; i ++)
cout << Low[i];
cout << endl;

cout << “Middle: “; for (i


= 0; i < 10; i ++)
cout << Middle[i];

cout << endl;


cout << “Alias: “;
for (i = 0; i < 10; i ++) cout
<< Alias[i];
cout << endl; cout
<< “High: “;
for (i = 0; i < 10; i ++)
cout << High[i];

cout << endl;


}

First, before trying to analyze this program, I should point out that it
contains two using statements to tell the compiler that we want to
import individual names from the standard library. In this case, we
want the compiler to consider the names std::cout and std::endl as
matching the unqualified names cout and endl , respectively.
Now, let’s look at what this program does when it’s executed.
First we define three variables, High , Middle , and Low , each as an
array of 10 chars . Then we define a variable Alias as a char* ; as you
may recall, this is how we specify a pointer to a char . Such a pointer
is essentially equivalent to a plain old memory address.
Susan wanted to know something about this program before we got
any further into it.

Susan: Why are these arrays called High , Low and Middle ?
Steve: Good question. They are named after their relative positions on the stack
when the function is executing. We’ll see why that is important later.

In the next part of the program, we use a for loop to set each element
of the arrays High , Middle , and Low to a value. So far, so good, except
that the statement Middle[i] = ’A’ + i; may look a bit odd. How can we add
a char value like ’A’ and a short value such as i ?

The char As a Very Short Numeric Variable

Let us return to those thrilling days of yesteryear, or at least Chapter


3. Since then, we’ve been using chars to hold ASCII values, which is
their most common use. However, every char variable actually has a
"double life"; it can also be thought of as a "really short" numeric
variable that can take on any of 256 values. Thus, we can add and
subtract chars and shorts as long as we’re careful not to try to use a
char to hold a number greater than 255 (or greater than 127, for a signed
char ). In this case, there’s no problem with the magnitude of the result,
since we’re starting out with the value A and adding a number between
0 and 9 to it; the highest possible result is J, which is still well below
the maximum value that can be stored in a char.
With that detail taken care of, let’s proceed with the analysis of
this program. The next statement after the end of the first for loop is
the seemingly simple line Alias = Middle; . This is obviously an
assignment statement, but what is being assigned?
The value that Alias receives is the address of the first element of
the array Middle . That is, after the assignment statement is executed,
Alias is effectively another name for Middle . Therefore, the next loop,
which assigns values to elements 10 through 19 of the "array" Alias ,
actually operates on the array Middle , setting those elements to the
values k through t.
The rest of the program is pretty simple; it just displays the
characters from each of the Low , Middle , Alias , and High arrays. Of
course, Alias isn’t really an array, but it acts just like one. To be
precise, it acts just like Middle , since it points to the first character in
Middle . Therefore, the Alias and Middle loops will display the same
characters. Then the final loop displays the values in the High array.

Running off the End of an Array

That’s pretty simple, isn’t it? Not quite as simple as it looks. If you’ve
been following along closely, you’re probably thinking I’ve gone off
the deep end. First, I said that the array Middle had 10 elements (which
are numbered 0 through 9 as always in C++); now I’m assigning
values to elements numbered 10 through 19. Am I nuts?
No, but the program is. When you run it, you’ll discover that it
produces the output shown in Figure 8.12.

FIGURE 8.12. Reaping the whirlwind

Low: 1111111111 Middle:


ABCDEFGHIJ Alias:
ABCDEFGHIJ
High: mnopqrst00

Most of these results are pretty reasonable; Low is just as it was when
we initialized it, and Middle and Alias have the expected portion of the
alphabet. But look at High . Shouldn’t it be all 0s?
Yes, it should. However, we have broken the rules by writing "past
the end" of an array, and the result is that we have overwritten some
other data in our program, which in this case turned out to be most of
the original values of High . You may wonder why we didn’t get an
error message as we did when we tried to write to a nonexistent Vec
element in an earlier chapter. The reason is that in C, the name of an
array is translated into the address of the first element of a number of
elements stored consecutively in memory. In other words, an array acts
just like a pointer, except that the address of the first element it refers
to can’t be changed at run time.
In case this equivalence of arrays and pointers isn’t immediately
obvious to you, you’re not alone; it wasn’t obvious to Susan, either.

Susan: And when you say that "that is, a pointer (i.e., the address of) a char ,
which may be the first of a bunch of chars one after another in memory", does that
mean the char* points to the first address and then the second and then the third
individually, and an array will point at all of them at the same time?

Steve: No, the char* points to the first char , but we (and the compiler) can
figure out the addresses of the other chars because they follow the first char
sequentially in memory. The same is true of the array; the array name refers to the
address of the first char , and the other chars in the array can be addressed with
the index added to the array name. In other words, y[2] in the example means "the
char that is 2 bytes past the beginning of the array called y ".

You might think that this near-identity between pointers and arrays
means that the compiler does not keep track of how many elements are
in an array. Actually, it does, and it is possible to retrieve that
information in the same function where the array is declared, via a
mechanism we won’t get into in this book. However, array access has
no bounds-checking built into it, so the fact that the compiler knows
how many elements are in an array doesn’t help you when you run off
the end of an array. Also, whenever you pass an array as an argument,
the information on the number of elements in the array is not available
in the called function. So the theoretical possibility of finding out this
information in some cases isn’t much help in most practical situations.
This is why pointers and arrays are the single most error-prone
construct in C (and C++, when they’re used recklessly). It’s also why
we’re not going to use either of these constructs except when there’s
no other reasonable way to accomplish our goals; even then, we’ll
confine them to tightly controlled circumstances in the implementation
of a user defined data type. For example, we don’t have to worry
about going "off the end" of the array in our Display
function, because we know exactly how many characters we’ve stored
( m_Length ), and we’ve written the function to send exactly that many
characters to the screen via cout . In fact, all of the member functions
of our string class are carefully designed to allocate, use, and dispose of
the memory pointed to by m_Data so that the user of this class doesn’t
have to worry about pointers or arrays, or the problems they can
cause. After all, one of the main benefits of using C++ is that the users
of a class don’t have to concern themselves with the way it works, just
with what it does.
Assuming that you’ve installed the software from the CD in the
back of this book, you can try out this program. First, you have to
compile it by following the compilation instructions on the CD. Then
type dangchar to run the program. You’ll see that it indeed prints out the
erroneous data shown in Figure 8.12. You can also run it under the
debugger, by following the usual instructions for that method.
Susan and I had quite a discussion about this program:

Susan: It is still not clear to me why you assigned values to elements numbered
10–19. Was that for demonstration purposes to force "writing past the end"?

Steve: Yes.

Susan: So by doing this to Middle then it alters the place the pointer is going to
point for High ?

Steve: No, it doesn’t change the address of High . Instead, it uses some of the
same addresses that High uses, overwriting some of the data in High .

Susan: So then when High runs there isn’t any memory to put its results in?
Why does Middle overwrite High instead of High overwriting Middle ? But it
was Alias that took up high’s memory?
Steve: Actually High is filled up with the correct values, but then they’re
overwritten by the loop that stores via Alias , which is just another name for
Middle .

Susan: Is that why High took on the lower case letters? Because Middle took
the first loop and then Alias is the same as middle, so that is why it also has the
upper case letters but then when High looped it picked up the pointer where Alias
left off and that is why it is in lower case? But how did it manage two zeros at the
end? I better stop talking, this is getting too weird. You are going to think I am nuts.

Steve: No. You’re not nuts, but the program is. We’re breaking the rules by
writing "past the end" of the array Middle , using the pointer Alias to do so. We
could have gotten the same result by storing data into elements 10 through 19 of
Middle , but I wanted to show the equivalence of pointers and arrays.

Susan: I was just going to ask if Middle alone would have been sufficient to do
the job.

Steve: Yes, it would have been. However, it would not have made the point that
arrays and pointers are almost identical in C++.

Susan: I am confused as to why the lower case letters are in High and why k
and l are missing and two zeros made their way in. You never told me why those
zeros were there.

Steve: Because the end of one array isn’t necessarily immediately followed by
the beginning of the next array; this depends on the sizes of the arrays and on how
the compiler allocates local variables on the stack. What we’re doing here is
breaking the rules of the language so it shouldn’t be a surprise that the result isn’t
very sensible.
Susan: OK, so if you are breaking the rules you can’t predict an outcome? Here I
was trying to figure out what was going on by looking at the results even knowing it
was erroneous. Ugh.

Steve: Indeed. I guess I should have a couple of diagrams showing what the
memory layout looks like before and after the data is overwritten. Let’s start with
Figure 8.13. Most of that should be fairly obvious, but what are those boxes with
question marks in them after the data that we put in the various arrays?

FIGURE 8.13. The memory layout before overwriting the data

Address Name
12330000 L

1233000C M

12330018 H

Alias[10]

Contents

1111111111
ABCDEFGHIJ
0000000000

Those are just bytes of memory that aren’t being used to hold anything at the
moment. You see, the compiler doesn’t necessarily use every consecutive byte of
memory to store all of the variables that we declare. Sometimes, for various reasons
which aren’t relevant here, it leaves gaps between the memory addresses allocated
to our variables.

One more point about this diagram is that I’ve indicated the location referred to by
the expression Alias[10] , which as we’ve already seen is an invalid pointer/array
reference. That’s where the illegal array/pointer operations will start to mess things
up.

Now let’s see what the memory layout looks like after executing the loop where we
try to store something into memory through Alias[10] through Alias[19] .
FIGURE 8.14. The memory layout
after overwriting the data

Address Name 12330000 Low

1233000C Middle/Alias

12330018 High
Alias[10]

Contents

What we have done here is write off the end of the array called Middle and
overwrite most of the data for High in the process. Does that clear it up?

Susan: Yes, I think I’ve got it now. It’s sort of like if you dump garbage in a
stream running through your back yard and it ends up on your neighbor’s property.

Steve: Exactly.

8.3. More about the private Access Specifier

Now that we have disposed of the correspondence between arrays and


pointers, i t’s time to return to our discussion of the private access
specifier that we’ve used to control access to the member variables of
the class . First of all, let me refresh your memory as to what this
access specifier means: only member functions of the string class can
refer to variables or functions marked private . As a rule, no member
variables of a class should be public . By contrast, most member
functions are public , because such functions provide the interface that
is used by programmers who need the facilities of the class being
defined. However, non- public member functions are sometimes
useful for handling implementation details that aren’t of interest or use
to the "outside world" beyond the class boundaries, as we’ll see later.
Now that I’ve clarified the role of these access specifiers, let’s
take a look at the program in Figure 8.15. This program won’t compile
because it tries to refer to m_Length , a private member variable of
string .

FIGURE 8.15. Attempted privacy violation (code\strtst3a.cpp)

#include <iostream>
#include “string3.h”

int main()
{
string n(“Test”);

n.m_Length = 12;

n.Display();
return 0;
}

Figure 8.16 is the result of trying to compile this program.

FIGURE 8.16. Trying to access a private member variable illegally

STRTST3A.cpp:
Error E2247 STRTST3A.cpp 8: ‘string::m_Length’ is not accessible in function main()

As discussed previously, the reason that we want to prevent access to


a member variable is that public member variables cause problems
similar to those caused by global variables. To begin with, we want to
guarantee consistent, safe behavior of our strings , which is impossible if
a nonmember function outside our control can change one of our
variables. In the example program, assigning a new value to the
m_Length member variable (if that were allowed by the compiler)
would trick our Display member function into trying to display 12
characters, when our string contains only four characters of
displayable data. Similar bad results would occur if a nonmember
function were to change the value of m_Data ; we wouldn’t have any
idea of what it was pointing to or whether we should call delete in the
destructor to allow the memory formerly used for our string data to be
reused.
Of course, Susan had some questions about access restrictions
and public data.

Susan: What’s the difference between public and global? I see how they are
similar, but how are they different?

Steve: Both global variables and public member variables are accessible from
any function, but there is only one copy of any given global variable for the whole
program. On the other hand, there is a separate copy of each public member
variable for each object in the class where that member variable is defined.

Susan: Okay. Now let me see if I get the problem with Figure
8.15. When you originally wrote m_Length , it was placed in a
private area, so it couldn’t be accessed through this program?

Steve: Right.

Susan: I am confused on your use of the term nonmember function. Does that
mean a nonmember of a particular class or something that is native?

Steve: A nonmember function is any function that is not a member of the class
in question.

Susan: A simple concept but easy to forget for some reason.


Steve: Probably because it’s stated negatively.

While this may be a convincing argument against letting nonmember


functions change our member variables, what about letting them at
least retrieve the values of member variables? In other words, why
does the private access specifier prevent outside functions from even
reading our member variables?

The Advantages of Encapsulation

Unfortunately, even allowing that limited access would be hazardous


to the maintainability of our programs, too. The problem here is akin
to the other difficulty with global variables: removing or changing the
type of a global variable can cause repercussions everywhere in the
program. If we decide to implement our string class by a different
mechanism than a char* and a short , or even change the names of the
member variables from m_Data and m_Length , any programs that rely
on those types or names would have to be changed. If our string class
were to become popular, this might amount to dozens or even hundreds
of programs that would need to be changed if we were to make the
slightest change in our member variables. Therefore, the private access
specifier rightly prevents nonmember functions from having any direct
access to the values of member variables.
Even so, it is sometimes useful for a program that is using an
object to find out something about the object’s internal state. For
example, a user of a string variable might very well want to know how
many characters it is storing at the moment, such as when formatting a
report. Each string might require a different amount of padding to make
the columns on the report line up, depending on the number of visible
characters in the string . However, we don’t want the length the user
sees to include the null byte, which doesn’t take up any space on the
page. Susan wanted to know how we could allow the user to find out
the string’s length.
Susan: So how would you "fix" this so that it would run? If you don’t want to
change m_Length to something public , then would you have to rewrite another
string class for this program?

Steve: No, you would generally fix this by writing a member function that returns
the length of the string . The GetLength function to be implemented in Figure 8.18
is an example of that.

Susan: Oh, so m_Length stays private but GetLength is public ?

Steve: Exactly.

As I’ve just mentioned to Susan, it is indeed possible to provide such


a service without compromising the safety or maintainability of our
class, by writing a function that tells the user how long the string is.
Figure 8.17 shows the new interface that includes GetLength .
I strongly recommend that you print out the files that contain this
interface and its implementation, as well as the test program, for
reference as you are going through this part of the chapter; those files
are string4.h , string4.cpp , and strtst4.cpp , respectively.

FIGURE 8.17. Yet another version of the string class interface (code\string4.h)

class string
{
public:
string();
string(const string& Str);
string& operator = (const string& Str);
~string();

string(char* p); void


Display(); short
GetLength();

private:
short m_Length;
char* m_Data;
};

As you can see, all we have done here is to add the declaration of the
new function, GetLength . The implementation in Figure 8.18 is
extremely simple: it merely returns the number of chars in the string ,
deducting 1 for the null byte at the end.

The string class implementation of the GetLength function (from


FIGURE 8.18.
code\string4.cpp)

short string::GetLength()
{
return m_Length-1;
}

This solves the problem of letting the user of a string variable find out
how long the string is without allowing functions outside the class to
become overly dependent on our implementation. It’s also a good
example of how we can provide the right information to the user more
easily by creating an access function rather than letting the user get at
our member variables directly. After all, we know that m_Length
includes the null byte at the end of the string’s data, which is irrelevant
to the user of the string , so we can adjust our return value to indicate
the "visible length" rather than the actual one.
With this mechanism in place, we can make whatever changes we
like in how we store the length of our string ; as long as we don’t
change the name or return type of GetLength , no function outside the
string class would have to be changed. For example, we could eliminate
our m_Length member variable and just have the GetLength call strlen to
figure out the length. Because we’re not giving access to our member
variable, the user’s source code wouldn’t have to be changed just
because we changed the way we keep track of the length. Of course, if
we were to allow strings longer than 32767 bytes, we would have to
change the return type of GetLength to
something more capacious than a short , which would require our users
to modify their programs slightly. However, we still have a lot more
leeway to make changes in the implementation than we would have if
we allowed direct access to our member variables.
The example program in Figure 8.19 illustrates how to use this
new function.
FIGURE 8.19. Using the GetLength function in the string class
(code\strtst4.cpp)

#include <iostream>
using std::cout; using
std::endl;
#include “string4.h”

int main()
{
short len; string
n(“Test”);

len = n.GetLength();

cout << “The string has “ << len << “ characters.” << endl;

return 0;
}

8.4. First Review

After finishing most of the requirements to make the string class a


concrete data type in the previous chapter, we went back to look at
why operator = needs a reference argument rather than a value
argument. When we use a value argument, a copy of the argument is
made for the use of the called function. In the case of a user-defined
data type, this copy is made via the copy constructor defined for that
type. If we don’t define our own copy constructor, the compiler will
generate one for us, which will use memberwise copy; that is, simply
copying all of the member variables in the object. While a
memberwise copy is fine for simple objects whose data are wholly
contained within themselves, it isn’t sufficient for objects that contain
pointers to data stored in other places, because copying a pointer in
one object to a pointer in another object results in the two objects
sharing the same actual data. Since our string class does contain such a
pointer, the result of this simple(minded) copy is that the newly
created string points to the same data as the caller’s string . Therefore,
when the newly created local string expires at the end of the operator =
function, the destructor for that string frees the memory that the caller’s
string was using to store its data.
This problem is very similar to the reason why we had to write
our own operator = in the first place; the compiler-generated version of
operator = also simply copies the member variables from the source to
the destination object, which causes similar havoc when one of the
two "twinned" strings is changed. In the case of our operator = , we can
solve the twinning problem by using a reference argument rather than a
value argument. A reference argument is another name for the caller’s
variable rather than a copy of the value in that variable, so no
destructor is called for a reference argument when the function exits;
therefore, the caller’s variable is unchanged.
Next, we examined how it was possible to assign a C string to one
of our string variables. This didn’t require us to write any more code
because we already had a constructor that could create a string from a
C string, and an operator = that could assign one string to another one. The
compiler helps us out here by employing a rule that can be translated
roughly as follows: if we need an object of type A ( string , in this case)
and we have an object of type B ( char* , in this case), and there is a
constructor that constructs an A and requires exactly one argument, of
type B, then invoke that constructor automatically. The example code
is as follows:

n = "My name is Susan";


where n is a string , and "My name is Susan" is a C string literal, whose type
is char* . We have an operator = with the declaration:

string& string::operator = (const string& Str);

that takes a string reference argument, and we have a constructor of the


form:

string::string(char* p);

that takes a char* argument and creates a new string . So we have a


char* , "My name is Susan" , and we need a string . Since we have a
constructor string::string(char*) , the compiler will use that constructor to
make a temporary string with the same value as the char* , and then use
the assignment operator string::operator = (const string& Str) to assign the
value of that temporary string to the string n . The fact that the temporary
is created also provides a clue as to why the argument to string::operator =
(const string& Str) should be a const reference, rather than just a (non-
const ) reference, to a string . The temporary string having the value "My
name is Susan" created during the execution of the statement n = "My name is
Susan "; disappears after operator = is executed, taking with it any
changes that operator = might have wanted to apply to the original
argument. With a const reference argument, the compiler knows that
operator = doesn’t wish to change that argument and therefore doesn’t
give us a warning that we might be changing a temporary value.
At this point, we’ve taken care of operator = . However, to create a
concrete data type, we still have to allow our string variables to be
passed as value arguments, which means we need a copy constructor
to handle that task. Unfortunately, the compiler-generated copy
constructor suffers from the same drawback as the compiler-
generated operator = ; namely, it copies the pointer to the actual data of
the string, rather than copying the data itself. Logically, therefore, the
solution to this problem is quite similar to the solution for operator = ;
we write our own copy constructor that allocates space for
the character data to be stored in the newly created string, and then
copies the data from the old string to the new string .
However, we still can’t use a value argument to our copy
constructor, because a value argument needs a copy constructor to
make the copy which would cause an infinite regress.5 This obviously
won’t work, and will be caught by the compiler. Therefore, as in the
case of operator = , we have to use a reference argument; since this is
actually just another name for the caller’s variable rather than a copy
of it, this does not cause an infinite regress. Since we are not going to
change the caller’s argument, we specify a constant reference
argument of type string , or a const string& in C++ terms.
At that point in the chapter, we had met the requirements for a
concrete data type, but such a type is of limited usefulness as long as
we can’t get the values displayed on the screen. Therefore, the next
order of business was to add a Display member function that takes care
of this task. This function isn’t particularly complicated, but it does
require us to deal with the notion of a C legacy type, the array. Since
the compiler treats an array in almost the same way as a pointer, we
can use array notation to extract each character that needs to be sent
out to the screen. Continuing with our example of the Display function’s
use, the next topic was a discussion of how chars can be treated as
numeric variables.
Then we saw a demonstration of how easy it is to misuse an array
so that you destroy data that belong to some other variable. This is an
important warning of the dangers of uncontrolled use of pointers and
arrays; these are the most error-prone constructs in both C and C++,
when not kept under tight rein.
We continued by revisiting the topic of access control and why it is
advantageous to keep member variables out of the public section of the
class definition. The reasons are similar to the reasons why we should
avoid using global variables; it’s too hard to keep track of where the
value of a public member variable is being referenced, which in turn
makes it very difficult to update all the affected areas of
5. See recursion in the glossary.
the code when changing the class definition. However, it is often useful
to allow external functions access to certain information about a class
object. We saw how to do this by adding a GetLength member function
to our string class .

8.5. Adding Further Facilities to our string class

At this point, we have a fairly minimal string class . We can create a


string , assign it a literal value in the form of a C string literal, and copy
the value of one string to another; we can even pass a string as a value
argument. Now we’ll use our existing techniques along with some new
ones to improve the facilities that the string class provides. To make this
goal more concrete, let’s suppose that we want to modify the sorting
program of Chapter 4 to sort strings , rather than shorts . To use the
sorting algorithm from that program, we’ll need to be able to compare
two strings to see which would come after the other in the dictionary,
as we can compare two shorts to see which is greater. We also want to
be able to use cout and << to display strings
on the screen and cin and >> to read them from the keyboard.
Before we go into the changes needed in the string class to allow us
to write a string sorting program, Figure 8.20 shows our goal: the
selection sort adapted to sort a Vec of strings instead of one of shorts .
Assuming that you’ve installed the software from the CD in the back
of this book, you can compile this program in the usual way, then
run it by typing strsort1 . You’ll see that it indeed prints out the
information in the StockItem object. You can also run it under the
debugger, by following the usual instructions for that method.

FIGURE 8.20. Sorting a Vec of


strings (code\strsort1.cpp)

#include <iostream> using


std::cin;
using std::cout;
using std::endl;

#include “string5.h”
#include “Vec.h”

int main()
{
Vec<string> Name(5); Vec<string>
SortedName(5); string LowestName;
short FirstIndex;
short i;
short k;
string HighestName = “zzzzzzzz”;
cout << “I’m going to ask you to type in five last names.” << endl; for (i = 0; i < 5; i
++)
{
cout << “Please type in name #” << i+1 << “: “; cin >>
Name[i];
}

for (i = 0; i < 5; i ++)


{
LowestName = HighestName;
FirstIndex = 0;
for (k = 0; k < 5; k ++)
{
if (Name[k] < LowestName)
{
LowestName = Name[k];
FirstIndex = k;
}
}
SortedName[i] = LowestName;
Name[FirstIndex] = HighestName;
}
cout << “Here are the names, in alphabetical order: “ << endl; for (i = 0; i < 5; i
++)
cout << SortedName[i] << endl;

return 0;
}

Susan had a couple of comments and questions about this program:

Susan: Why aren’t you using caps when you initiate your variable of
HighestName ; I don’t understand why you use "zzzzzzzzzz" instead of
"ZZZZZZZZZZ"? Are you going to fix this later so that caps will work the same
way as lower case letters?

Steve: If I were to make that change, the program wouldn’t work correctly if
someone typed their name in lower case letters because lower case letters are
higher in ASCII value than upper case letters. That is, "abc" is higher than "ZZZ".
Thus, if someone typed in their name in lower case, the program would fail to find
their name as the lowest name. Actually, the way the string sorting function works,
"ABC" is completely different from "abc"; they won’t be next to one another in the
sorted list. We could fix this by using a different method of comparing the strings
that would ignore case, if that were necessary.

If you compare this program to the original one that sorts short values
(Figure 4.6 on page 164), you’ll see that they are very similar. This is
good because that’s what we wanted to achieve. Let’s take a look at
the differences between these two programs.
1. First, we have several using declarations to tell the compiler what
we mean by cin , cout , and endl . In the original program, we used a
blanket using namespace std; declaration, so we didn’t need specific
using declarations for these names. Now we’re being more specific
about which names we want to use from that library and which are
ours.
2. The next difference is that we’re sorting the names in ascending
alphabetical order, rather than descending order of weight as with the
original program. This means that we have to start out by finding the
name that would come first in the dictionary (the "lowest" name). By
contrast, in the original program we were looking for the highest
weight, not the lowest one; therefore, we have to do the sort
"backward" from the previous example.
3. The third difference is that the Vecs Name and SortedName are
collections of strings , rather than the corresponding Vecs of shorts in
the first program: Weight and SortedWeight .
4. The final difference is that we’ve added a new variable called
HighestName , which plays the role of the value 0 that was used to
initialize HighestWeight in the original program. That is, it is used to
initialize the variable LowestName to a value that will certainly be
replaced by the first name we find, just as 0 was used to initialize
the variable HighestWeight to a value that had to be lower than the first
weight we would find. The reason why we need a "really high"
name rather than a "really low" one is because we’re sorting the
"lowest" name to the front, rather than sorting the highest weight to
the front as we did originally.

You may think these changes to the program aren’t very significant.
That’s a correct conclusion; we’ll spend much more time on the
changes we have to make to our string class before this program will
run, or even compile. The advantage of making up our own data types
(like strings ) is that we can make them behave in any way we like. Of
course, the corresponding disadvantage is that we have to provide the
code to implement that behavior and give the compiler enough
information to use that code to perform the operations we request. In
this case, we’ll need to tell the compiler how to compare strings , read
them in via >> and write them out via << . Let’s start with Figure 8.21,
which shows the new interface specification of the string class ,
including all of the new member functions needed to implement the
comparison and I/O operators, as well as operator == , which we’ll
implement later in the chapter.

FIGURE 8.21.The updated string class interface, including comparison and I/O
operators (code\string5.h)

class string
{
friend std::ostream& operator << (std::ostream& os, const string& Str); friend
std::istream& operator >> (std::istream& is, string& Str);

public:
string();
string(const string& Str);
string& operator = (const string& Str);
~string();

string(char* p); short


GetLength();
bool operator < (const string& Str); bool
operator == (const string& Str);

private:
short m_Length;
char* m_Data;
};

I strongly recommend that you print out the files that contain this
interface and its implementation, as well as the test program, for
reference as you are going through this part of the chapter; those files
are string5.h , string5.cpp , and strtst5.cpp , respectively.

Implementing operator <

Our next topic is operator < (the "less than" operator), which we need
so that we can use the selection sort to arrange strings by their
dictionary order. The declaration of this operator is similar to that of
operator = , except that rather than defining what it means to say x = y; for
two strings x and y, we are defining what it means to say x < y. Of
course, we want our operator < to act analogously to the < operator for
short values; that is, our operator will compare two strings and return
true if the first string would come before the second string in the dictionary
and false otherwise, as needed for the selection sort.
All right, then, how do we actually implement this undoubtedly
useful facility? Let’s start by examining the function declaration bool
string::operator < (const string& Str); a little more closely. This means that
we’re declaring a function that returns a bool and is a member
function of class string ; its name is operator < , and it takes a constant
reference to a string as its argument. As we’ve seen before, operators
don’t look the same when we use them as when we define them. In
the sorting program in Figure 8.20, the line if (Name[k] < LowestName)
actually means if (Name[k].operator < (LowestName)) . In other words, if the
return value from the call to operator < is false , then the if expression
will also be considered false and the controlled block of the if won’t
be executed. On the other hand, if the return value from the call to
operator < is true , then the if expression will also be considered true and
the controlled block of the if will be executed. To make this work
correctly, our version of operator < will return the value true if the first
string is less than the second and false otherwise. Now that we’ve seen
how the compiler will use our new function,
let’s look at its implementation, which follows these steps:
1. Determine the length of the shorter of the two strings .
2. Compare a character from the first string with the corresponding
character from the second string .
3. If the character from the first string is less than the character from the
second string , then we know that the first string precedes the second
in the dictionary, so we’re done and the result is true .
4. If the character from the first string is greater than the character from
the second string, then we know that the first string follows the
second in the dictionary. Therefore, we’re done and the result is
false .

5. If the two characters are the same and we haven’t come to the end of
the shorter string , then move to the next character in each string , and
go back to step 2.
6. When we run out of characters to compare, if the strings are the
same length then the answer is that they are identical, so we’re
done and the result is false .
7. On the other hand, if the strings are different in length, and if we run
out of characters in the shorter string before finding a difference
between the two strings , then the longer string follows the shorter
one in the dictionary. In this case, the result is true if the second string
is longer and false if the first string is longer.

You may be wondering why we need special code to handle the case
where the strings differ in length. Wouldn’t it be simpler to compare up
to the length of the longer string ?

Details of the Comparison Algorithm

As it happens, that approach would work properly so long as both of


the strings we’re comparing have a null byte at their ends and neither
of them have a null byte anywhere else. To see the reason for the
limitation of that approach, let’s look at what the memory layout might
look like for two string variables x and y , with the contents "post"
and "poster" respectively. In Figure 8.22, the letters in the box labeled
"string contents" represent themselves, while the 0s represent the null
byte, not the digit 0.
If we were to compare the strings up to the longer of the two
lengths with this memory layout, the sequence of events would be:
1. Get character p from location 12345600.
2. Get character p from location 1234560a.
3. They are the same, so continue.
4. Get character o from location 12345601.
5. Get character o from location 1234560b.
6. They are the same, so continue.
7. Get character s from location 12345602.
8. Get character s from location 1234560c.
9. They are the same, so continue.
10. Get character t from location 12345603.
11. Get character t from location 1234560d.
12. They are the same, so continue.
13. Get a null byte from location 12345604.
14. Get character e from location 1234560e.
15. The character e from the second string is higher than the null byte
from the first string , so we conclude (correctly) that the second
string comes after the first one.
FIGURE 8.22. strings x and y in
memory

Address Name 12340000 m_Length


12340002 m_Data

12345600 none
12345605 none 1234560a none

string x 0005
12345600

12340020 m_Length
12340022 m_Data

This works because the null byte, having an ASCII code of 0, in fact
has a lower value than whatever non-null byte is in the corresponding
position of the other string .
However, this plan wouldn’t work reliably if we had a string with
a null byte in the middle. To see why, let’s change the memory layout
slightly to stick a null byte in the middle of string y. Figure 8.23 shows
the modified layout.
FIGURE 8.23. strings x and y in memory, with an embedded null byte

Address Name

string x

12340000 m_Length 0005


12340002 m_Data 12345600

12345600 none post0


12345605 none test0
1234560a none post0r0
string y
12340020 m_Length 0007
12340022 m_Data 1234560a

You may reasonably object that we don’t have any way to create a
string with a null byte in it. That’s true at the moment, but one reason
we’re storing the actual length of the string rather than relying on the
null byte to mark the end of a string , as is done with C strings, is that
keeping track of the length separately makes it possible to have a string
that has any characters whatever in it, even nulls.
For example, we could add a string constructor that takes an array
of bytes and a length and copies the specified number of bytes from the
array. Since an array of bytes can contain any characters in it,
including nulls, that new constructor would obviously allow us to
create a string with a null in the middle of it. If we tried to use the
preceding comparison mechanism, it wouldn’t work reliably, as
shown in the following analysis.
1. Get character p from location 12345600.
2. Get character p from location 1234560a.
3. They are the same, so continue.
4. Get character o from location 12345601.
5. Get character o from location 1234560b.
6. They are the same, so continue.
7. Get character s from location 12345602.
8. Get character s from location 1234560c.
9. They are the same, so continue.
10. Get character t from location 12345603.
11. Get character t from location 1234560d.
12. They are the same, so continue.
13. Get a null byte from location 12345604.
14. Get a null byte from location 1234560e.
15. They are the same, so continue.
16. Get character t from location 12345605.
17. Get character r from location 1234560f.
18. The character t from the first string is greater than the character r
from the second string , so we conclude that the first string comes
after the second one.

Unfortunately, this conclusion is incorrect; what we have actually done


is run off the end of the first string and started retrieving data from the
next location in memory. Since we want to be able to handle the
situation where one of the strings has one or more embedded nulls, we
have to stop the comparison as soon as we get to the end of the shorter
string . Whatever happens to be past the end of that string’s data is not
relevant to our comparison of the two strings .
Let’s listen in on the discussion Susan and I had on this topic.

Susan: Why is the return value from operator < a bool ?

Steve: Because there are only two possible answers that it can give: either the
first string is less than the second string or it isn’t. In
the first case true is the appropriate answer, and in the second case, of course,
false is the appropriate answer. Thus, a bool is appropriate for this use.

Susan: Again I am not seeing where we’re using


string::operator < (const string& Str); in the sorting program.

Steve: That’s because all you have to say isa < b , just as with operator = ; the
compiler knows that a < b , where a and b are strings , means string::operator <
(const string&) .

Susan: Why are you bringing up this stuff about what the operator looks like and
the way it is defined? Do you mean that’s what is really happening even though it
looks like built in code?

Steve: Yes.

Susan: Who puts those null bytes into memory?

Steve: The compiler supplies a null byte automatically at the end of every literal
string, such as "abc".

Susan: I don’t get where you are not using a null byte when storing the length; it
looks to me that you are. This is confusing. Ugh.

Steve: I understand why that’s confusing, I think. I am including the null byte at
the end of a string when we create it from a C string literal, so that we can mix our
strings with C strings more readily. However, because we store the length
separately, it’s possible to construct a string that has null bytes in the middle of it as
well as at the end. This is not possible with a C string, because that has no explicit
length stored with it; instead, the routines that operate on C strings assume that the
first null byte means the C string is finished.
Susan: Why do you jump from a null byte to a t? Didn’t it run out of letters? Is
this what you mean by retrieving data from the next location in memory? Why was a
t there?

Steve: Yes, this is an example of retrieving random information from the next
location in memory. We got a t because that just happened to be there. The problem
is that since we’re using an explicit length rather than a null byte to indicate the end
of our strings , we can’t count on a null byte stopping the comparison correctly.
Thus, we have to worry about handling the case where there is a null byte in the
middle of a string .

Now that we’ve examined why the algorithm for operator < works the
way it does, it will probably be easier to understand the code if we
follow an example of how it is used. I’ve written a program called
strtst5x.cpp for this purpose; Figure 8.24 has the code for that program.

FIGURE 8.24. Using operator < for


strings (code\strtst5x.cpp)

#include <iostream>
#include “string5.h”
using std::cout; using
std::endl;

int main()
{
string x;
string y;

x = “ape”;
y = “axes”;

if (x < y)
cout << x << “ comes before “ << y << endl; else
cout << x << “ doesn’t come before “ << y << endl;
return 0;
}

You can see that in this program the two strings being compared are
"ape" and "axes", which are assigned to strings x and y respectively.
As we’ve already discussed, the compiler translates a comparison
between two strings into a call to the function string::operator <(const string&
Str) ; in this case, the line that does that comparison is if (x < y) . Now
that we’ve seen how to use this comparison operator, Figure
8.25 shows one way to implement it.

FIGURE 8.25. The implementation of operator < for strings (from code\string5a.cpp)

bool string::operator < (const string& Str)


{
short i;
bool Result;
bool ResultFound; short
CompareLength;

if (Str.m_Length < m_Length)


CompareLength = Str.m_Length;
else
CompareLength = m_Length;

ResultFound = false;
for (i = 0; (i < CompareLength) && (ResultFound == false); i ++)
{
if (m_Data[i] < Str.m_Data[i])
{
Result = true; ResultFound =
true;
}
else
{
if (m_Data[i] > Str.m_Data[i])
{
Result = false;
ResultFound = true;
}
}
}

if (ResultFound == false)
{
if (m_Length < Str.m_Length) Result =
true;
else
Result = false;
}

return Result;
}

The variables we’ll use in this function are:


1. i , which is used as a loop index in the for loop that steps through all
of the characters to be compared.
2. Result , which is used to hold the true or false value that we’ll
return to the caller.
3. ResultFound , which we’ll use to keep track of whether we’ve found
the result yet.
4. CompareLength , which we’ll use to determine the number of
characters to compare in the two strings .

After defining variables, the next four lines of the code determine how
many characters from each string we actually have to compare; the
value of CompareLength is set to the lesser of the lengths of our string
and the string referred to by Str . In this case, that value is 4, the length
of our string (including the terminating null byte).
Now we’re ready to do the comparison. This takes the form of a
for loop that steps through all of the characters to be compared in each
string . The header of the for loop is for (i = 0; (i < CompareLength) &&
(ResultFound == false); i ++) . The first and last parts of the expression
controlling the for loop should be familiar by now; they initialize and
increment the loop control variable. But what about the continuation
expression (i < CompareLength) && (ResultFound == false) ?

The Logical AND Operator

That expression states a two-part condition for continuing the loop.


The first part, (i < CompareLength) , is the usual condition that allows the
program to execute the loop as long as the index variable is within the
correct range. The second part, (ResultFound == false) , should also be fairly
clear; we want to test whether we’ve already found the result we’re
looking for and continue only as long as that isn’t the case (i.e.,
ResultFound is still false ). The () around each of these expressions are used
to tell the compiler that we want to evaluate each of these expressions
first, before the && is applied to their results. That leaves the &&
symbol as the only mystery.
It’s really not too mysterious. The && operator is the symbol for
the "logical AND" operator, which means that we want to combine the
truth or falsity of two expressions each of which has a logical value of
true or false . The result of using && to combine the results of these
two expressions will also be a logical value. Here is the way the value
of that expression is determined:
1. If both of the expressions connected by the && are true , then the
value of the expression containing the && is also true ;
2. Otherwise, the value of the expression containing the && is false .

If you think about it for a minute, this should make sense. We want to
continue the loop as long as both of the conditions are true ; that is,
1. i is less than CompareLength ; and

2. ResultFound is false (we haven’t found what we’re looking for).


That’s why the && operator is called logical AND; it checks whether
condition 1 and condition 2 are both true . If either is false , we want to
stop the loop, and this continuation expression will do just that.6
Now let’s trace the path of execution through the for loop in Figure
8.25. On the first time through the loop, the index i is 0 and ResultFound
is false . Therefore, the continuation expression allows us to execute the
statements in the loop, where we test whether the current character in
our string , namely m_Data[i] , is less than the corresponding character
from the string Str, namely Str.m_Data[i] .
By the way, in case the expression in the if statement, if (m_Data[i]
< Str.m_Data[i]) , doesn’t make sense immediately, perhaps I should
remind you that the array notation m_Data[i] means the i th character of
the data pointed to by m_Data ; an index value of 0 means the first
element, as is always the case when using an array or Vec . We’ve
already covered this starting with the section entitled “The
Equivalence of Arrays and Pointers” on page 491; you should go back
and reread that section if you’re not comfortable with the equivalence
between pointers and arrays.
The code in Figure 8.26 compares characters from the two strings .

FIGURE 8.26. Is our


character less than the one from the other string ? (from code\string5a.cpp)

if (m_Data[i] < Str.m_Data[i])


{
Result = true;
ResultFound = true;
}

6. This operator follows a rule analogous to the one for ||: if the expression on
the left of the && is false, then the answer must be false and the expression on
the right is not executed at all. The reason for this "short-circuit evaluation rule"
is that in some cases you may want to write a right-hand expression for &&
that will only be legal if the left-hand expression is true.
If the current character in our string were less than the corresponding
character in Str , we would have our answer; our string would be less
than the other string . If that were the case, we would set Result to true
and ResultFound to true and would be finished with this execution of the
for loop.
As it happens, in our current example both m_Data[0] and
Str.m_Data[0] are equal to ‘a’, so they’re equal to each other as well.
What happens when the character from our string is the same as the one
from the string Str ?
In that case, the first if , whose condition is stated as if (m_Data[i] <
Str.m_Data[i]), is false . So we continue with the else clause of that if
statement, which looks like Figure 8.27.

The else clause in the comparison loop (from


FIGURE 8.27.
code\string5a.cpp)

else
{
if (m_Data[i] > Str.m_Data[i])
{
Result = false;
ResultFound = true;
}
}

This clause contains another if statement that compares the character


from our string to the one from Str. Since the two characters are the
same, this if also comes out false so the controlled block of the if
isn’t executed. After this if statement, we’ve reached the end of the
controlled block of the for statement. The next iteration of the for loop
starts by incrementing i to 1. Then the continuation expression is
evaluated again; i is still less than CompareLength and ResultFound is still
false , so we execute the controlled block of the loop again with i
equal to 1.
On this pass through the for loop, m_Data[1] (the character from
our string) is ‘p’ and Str.m_Data[1] (the character from the other
string ) is ‘x’. Therefore, the condition in the first if statement (that the
character from our string is less than the character from the other
string ) is true , so we execute the controlled block of the if statement.
This sets Result to true , and ResultFound also to true , as you can see in
Figure 8.26.
We’re now at the end of the for loop, so we return to the for
statement to continue execution. First, i is incremented again, to 2.
Then the continuation expression (i < CompareLength) && (ResultFound ==
false) is evaluated. The first part of the condition, i < CompareLength is
true , since i is 2 and CompareLength is 4. However, the second part of the
condition, ResultFound == false , is false , because we’ve just set ResultFound
to true . Since the result of the && operator is true only when both
subconditions are true , the for loop terminates, passing control to the
next statement after the controlled block of the loop (Figure 8.28).

FIGURE 8.28. Handling the return


value (from code\string5a.cpp)

if (ResultFound == false)
{
if (m_Length < Str.m_Length) Result = true;
else
Result = false;
}

In the current scenario, ResultFound is true because we have found a


character from m_Data that differs from the corresponding character
from Str.m_Data; therefore, the condition in the first if is false , and we
proceed to the next statement after the end of the if statement, return
Result; . This shouldn’t come as too much of a surprise; we know the
answer to the comparison, namely, that our string is less than the other
string , so we’re ready to tell the caller the information that he requested
by calling our routine.
Other Possible Results of the Comparison

The path of execution is almost exactly the same if, the first time we
find a mismatch between the two strings , the character from our string is
greater than the character from the other string . The only difference is
that the if statement that handles this scenario sets Result to false rather
than true (Figure 8.27), because our string is not less than the other
string ; of course, it still sets ResultFound to true , since we know the
result that will be returned.
There’s only one other possibility; that the two strings are the same
up to the length of the shorter one (e.g., "post" and "poster"). In that
case, the for loop will expire of natural causes when i gets to be
greater than or equal to CompareLength . Then the final if statement
shown in Figure 8.28 will evaluate to true , because ResultFound is still
false . In this case, if the length of our string is less than the length of the
other string , we will set Result to true , because a shorter string will
precede a longer one in the dictionary if the two strings are the same up
to the length of the shorter one.
Otherwise, we’ll set Result to false , because our string is at least as
long as the other one; since they’re equal up to the length of the shorter
one, our string can’t precede the other string . In this case, either they’re
identical, or our string is longer than the other one and therefore should
follow it. Either of these two conditions means that the result of operator
< is false , so that’s what we tell the caller via our return value.

Using a Standard Library Function to Simplify the Code

This implementation of operator < for strings works. However, there’s a


much simpler way to do it. Figure 8.29 shows the code.

FIGURE 8.29. Implementing operator < for strings (from code\string5.cpp)

bool string::operator < (const string& Str)


{
short Result;
short CompareLength;

if (Str.m_Length < m_Length)


CompareLength = Str.m_Length;
else
CompareLength = m_Length;
Result = memcmp(m_Data,Str.m_Data,CompareLength); if (Result < 0)
return true;

if (Result > 0)
return false;

if (m_Length < Str.m_Length) return


true;

return false;
}

This starts out in the same way as our previous version, by figuring out
how much of the two strings we actually need to compare character by
character. Right after that calculation, though, the code is very
different; where’s that big for loop?
It’s contained in the standard library function memcmp , a carryover
from C, which does exactly what that for loop did for us. Although C
doesn’t have the kind of strings that we’re implementing here, it does
have primitive facilities for dealing with arrays of characters,
including comparing one array with another, character by character.
One type of character array supported by C is the C string, which
we’ve already encountered. However, C strings have a serious
drawback for our purposes here; they use a null byte to mark the end
of a group of characters. This isn’t suitable for our strings , whose
length is explicitly stored; as noted previously, our strings could
theoretically have null bytes in them. There are several C functions
that compare C strings, but they rely on the null byte for their proper
operation so we can’t use them.
However, these limitations of C strings are so evident that the
library writers have supplied another set of functions that act almost
identically to the ones used for C strings, except that they don’t rely on
null bytes to determine how much data to process. Instead, whenever
you use one of these functions, you have to tell it how many characters
to manipulate. In this case, we’re calling memcmp , which compares
two arrays of characters up to a specified length. The first argument is
the first array to be compared (corresponding to our string ), the second
argument is the second array to be compared (corresponding to the
string Str ), and the third argument is the length for which the two arrays
are to be compared. The return value from memcmp is calculated by the
following rules:
1. It’s less than 0 if the first array would precede the second in the
dictionary, considering only the length specified;
2. It’s 0 if they are the same up to the length specified;
3. It’s greater than 0 if the first array would follow the second in the
dictionary, considering only the length specified.

This is very convenient for us, because if the return value from memcmp
is less than 0, we know that our result will be true , while if the return
value from memcmp is greater than 0, then our result will be false . The
only complication, which isn’t very complicated, is that if the return
value from memcmp is 0, meaning that the two arrays are the same up to
the length of the shorter character array, we have to see which is
longer. If the first one is shorter, then it precedes the second one;
therefore, our result is true . Otherwise, it’s false .
Susan had some questions about this version of operator < ,
including why we had to go through the previous exercise if we could
just use memcmp .

Susan: What is this? I suppose there was a purpose to all the confusing prior
discussion if you have an easier way of defining
operator < ? UGH! This new stuff just pops up out of the blue! What is going on?
Please explain the reason for the earlier torture.

Steve: I thought we should examine the character-by-character version of


operator < before taking the shortcut. That should make it easier to follow the
explanation of the "string overrun" problem, as each character comparison shows up
in the code.

Susan: So, memcmp is another library function, and does it stand for memory
compare? Also, are the return values built into memcmp ? This is very confusing,
because you have return values in the code.

Steve: Yes, memcmp stands for "memory compare". As for return values; yes, it
has them, but they aren’t exactly the ones that we want. We have to return the value
true for "less than" and false for "not less than", which aren’t the values that
memcmp returns. Also, memcmp doesn’t do the whole job when the strings
aren’t the same length; in that case, we have to handle the trailing part of the longer
string manually.

One small point that shouldn’t be overlooked is that in this version of


the operator < code, we have more than one return statement; in fact, we
have four! That’s perfectly legal and should be clear to a reader of this
function. It’s usually not a good idea to scatter return statements around
in a large function, because it’s easy to overlook them when trying to
follow the flow of control through the function. In this case, though,
that’s not likely to be a problem; any reasonably fluent reader of C++
code will find this organization easy to understand.

Implementing operator ==

Although our current task requires only operator < , another comparison
operator, operator == , will make an interesting contrast in
implementation; in addition, a concrete data type that allows
comparisons should really implement more than just operator < . Since
we’ve just finished one comparison operator, we might as well knock
this one off now (Figure 8.30).
This function is considerably simpler than the previous one. Why
is this, since they have almost the same purpose? It’s because in this
case we don’t care which of the two strings is greater than the other,
just whether they’re the same or different. Therefore, we don’t have to
worry about comparing the two char arrays if they’re of different
lengths. Two arrays of different lengths can’t be the same, so we can
just return false . Once we have determined that the two arrays are the
same length, we do the comparison via memcmp . This gives us the
answer directly, because if Result is 0, then the two strings are equal;
otherwise, they’re different.

FIGURE 8.30. Implementing operator == for strings (from code\string5.cpp)

bool string::operator == (const string& Str)


{
short Result;

if (m_Length != Str.m_Length) return false;


Result = memcmp(m_Data,Str.m_Data,m_Length); if (Result ==
0)
return true;

return false;
}

Even though this function is simpler than operator < , it’s not simple
enough to avoid Susan’s probing eye:

Susan: Does == only check to see if the lengths of the arrays are the same?
Can it not ever be used for a value?
Steve: It compares the values in the arrays, but only if they are the same length.
Since all it cares about is whether they are equal, and arrays of different length can’t
be equal, it doesn’t have to compare the character data unless the arrays are of the
same length.

Implementation vs. Declaration Revisited

Before moving on to see how we will display a string on the screen


via operator << , I should bring up a couple of points here because
otherwise they might pass you by. First, we didn’t have to change our
interface header file string5.h (Figure 8.21) just because we changed the
implementation of operator < between string5a.cpp and string5.cpp . Since
the signature of this function didn’t change, neither the header file nor
the user program had to change. Second, we didn’t even implement
operator == in the string5a.cpp version of the string library and yet our test
program still compiled without difficulty. How can this be?
In C++, you can declare all of the functions you want to, whether
they are member functions or global functions, without actually
defining them. As long as no one tries to actually use the functions,
everything will work fine. In fact, the compiler doesn’t even care
whether any functions you do refer to are available; that’s up to the
linker to worry about. This is very handy when you know that you’re
going to add functions in a later revision of a class , as was the case
here. Of course, you should warn your class users if you have listed
functions in the interface header file that aren’t available. It’s true that
they’ll find out about the missing functions the first time they try to link
a program that uses one of these functions, because the linker will
report that it can’t find the function; however, if they’ve spent a lot of
time writing a program using one of these functions, they’re likely to
get mad at you for misleading them. So let them know what’s actually
implemented and what’s "for later".
Now let’s continue with our extensions to the string class , by looking
at how we send a string out to the screen.
Using cout With User-defined Types

We’ve been using cout and its operator << for awhile, but have taken
them for granted. Now we have to look under the hood a bit.
The first question is what type of object cout is. The answer is that
it’s an ostream (short for "output stream"), which is an object that you can
use to send characters to some output device. I’m not sure of the origin
of this term, but you can imagine that you are pushing the characters
out into a "stream" that leads to the output device.
As you may recall from our uses of cout , you can chain a bunch of
<< expressions together in one statement, as in Figure 8.31. If you
compile and execute that program, it will display:

On test #1, your mark is: A

Notice that it displays the short as a number and the char as a letter,
just as we want it to do. This desirable event occurs because there’s a
separate version of << for each type of data that can be displayed; in
other words, operator << uses function overloading, just like the
constructors for the StockItem class and the string class . We’ll also use
function overloading to add support for our string class to the I/O
facilities supplied by the iostream library.

FIGURE 8.31. Chaining several operator << expressions together (code\cout1.cpp)

#include <iostream> using


namespace std;

int main()
{
short x;
char y;

x = 1; y
= ‘A’;
cout << “On test #” << x << “, your mark is: “ << y << endl;

return 0;
}

How cout Works With Pre-existing Types

Before we examine how to accomplish this goal, though, we’ll have to


go into some detail about how the pre-existing output functions
behave. Let’s start with a simple case using a version of operator <<
supplied by the iostream header file. The simplest possible use of
ostream’s operator << , of course, uses only one occurrence of the operator.
Here’s an example where the value is a char :

cout << ’a’;

As you may remember, using an operator such as << on an object is


always equivalent to a "normal" function call. This particular example
is equivalent to the following:

cout.operator << (’a’);

which calls ostream::operator << (char) (i.e., the version of the operator <<
member function of the iostream class that takes a char as its input) for the
predefined destination cout , which writes the char on the screen.
That takes care of the single occurrence of operator << . However, as
we’ve already seen, i t’s possible to string together any number of
occurrences of operator << , with the output of each successive
occurrence following the output created by the one to its left. We want
our string output function to behave just like the ones predefined in
iostream , so let’s look next at an example that illustrates multiple uses of
operator << , taking a char and a C string:

cout << ’a’ << " string";


This is equivalent to

(cout.operator << (’a’)).operator << (" string");

What does this mean? Well, since an expression in parentheses is


evaluated before anything outside the parentheses, the first thing that
happens is that ostream::operator << (char) is called for the predefined
destination cout , which writes the ‘a’ to the screen. Now here’s the
tricky part: the return value from every version of ostream::operator <<
is a reference to the ostream that it operates on ( cout , in this case).
Therefore, after the ‘a’ has been written on the screen, the rest of the
expression reduces to this:

cout.operator << (" string");

That is, the next output operation behaves exactly like the first one. In
this case, ostream::operator << (char*) is the function called, because char*
is the type of the argument to be written out. It too returns a reference
to the ostream for which it was called, so that any further << calls can
add their data to that same ostream . It should be fairly obvious how the
same process can be extended to handle any number of items to be
displayed.

Writing Our Own Standard Library-Compatible operator <<

That illustrates how the designers of ostream could create member


functions that would behave in this convenient way. However, we
can’t use the same mechanism that they did; we can’t modify the
definition of the ostream class in the library, because we didn’t write it
in the first place and don’t have access to its source code.7 Is there
some way to give our strings convenient input and output facilities?
In fact, there is. To do this, we create a global function called
operator << that accepts an ostream& (that is, a reference to an ostream ),
adds the contents of our string to the ostream , and then returns a
reference to the same ostream . This will support multiple
occurrences of operator << being chained together in one statement, just
like the operator << member functions from the iostream library. The
implementation of this function is shown in Figure 8.32.
As usual, we should first examine the function declaration; in this
case, a couple of points are worth noting. We’ve already seen that the
first argument is an ostream& , to which we will add the characters
from the string that is the second argument. Also notice that the second
argument is a const string& , that is, a reference to a constant string. This
is the best way to declare this argument because we aren’t going to
change the argument, and there’s no reason to make a copy of it.

FIGURE 8.32. An operator << function to output a string (from code\string5.cpp)

std::ostream& operator << (std::ostream& os, const string& Str)


{
short i;

for (i=0; i < Str.m_Length-1; i ++) os <<


Str.m_Data[i];

return os;
}

But possibly the most important point about the function declaration is
that this operator << is not a member function of the string class , which
explains why it isn’t called string::operator << . It’s a global function that
can be called anywhere in a program that needs to use it, so long as
that program has included the header file that defines it. Its

7. Even if we did have the source code to the ostream class, we wouldn’t want to
modify it, for a number of reasons. One excellent reason is that every time a
new version of the library came out, we’d have to make our changes again.
Also, there are other ways to reuse the code from the library for our own
purposes using mechanisms that we’ll get to later in this book, although we
won’t use them with the iostream classes .
operation is pretty simple. Since there is no ostream function to write
out a specified number of characters from a char array, we have to call
ostream::operator << (char) for each character in the array.
Therefore, we use the statement

os << Str.m_Data[i];

to write out each character from the array called m_Data that we use to
store the data for our string on the ostream called os , which is just
another name for the ostream that is the first argument to this function.
After all the characters have been written to the ostream , we return
it so that the next operator << call in the line can continue producing
output.
However, there’s a loose end here. How can a global function,
which by definition isn’t a member function of class string , get at the
internal workings of a string ? We declared that m_Length and m_Data
were private , so that they wouldn’t be accessible to just any old
function that wandered along to look at them. Is nothing sacred?

The friend Keyword

In fact, private data aren’t accessible to just any function. However,


operator << (std::ostream&, const string&) isn’t just any function. Take a look at
string5.h in Figure 8.21 to see why. The line we’re interested in here is
this one:

friend std::ostream& operator << (std::ostream& os, const string& Str);

The key word here is friend . We’re telling the compiler that a function
with the signature std::ostream& operator << (std::ostream&, const string&) is
permitted to access the information normally reserved for member
functions of the string class ; i.e., anything that isn’t marked public . It’s
possible to make an entire class a friend to another class ; here, we’re
specifying one function that is a friend to this class .8
You probably won’t be surprised to learn that Susan had some
questions about this operator. Let’s see how the discussion went:

Susan: Let’s start with friend . . . what is that?

Steve: A friend is a function or class that is allowed to access internals of this


class , as though the friend were a member function. In other words, the private
access specifier doesn’t have any effect on friends .

Susan: What is an ostream ? How is it related to istream ?

Steve: An ostream is a stream that is used for output; streams


can be either input ( istream ) or output ( ostream ).

Susan: Why does it have std:: in front of it?

Steve: Because we are specifying that we mean the ostream that is in the
standard library. It’s a good idea to avoid using declarations in commonly used
header files, as I’ve explained previously, and this is another way of telling the
compiler exactly which ostream we mean.

Susan: This stream character seems to have a lot of relatives.

Steve: You’re right; there are lots of classes in the stream family, including
istream , ostream , ifstream , and ofstream . And it really is a family, in the C++
sense at least; these classes are related by inheritance, which we’ll get to in
Chapter 9.

8. The signature of the function is important here, as elsewhere in C++; this


friend declaration would not permit a function with the same name and a
different signature, for example std::ostream& op erator << (st d::ost ream&, int ) to
access non-public members of string.
That explains why this global function can access our non- public data.
But why did we have to create a global function in the first place,
rather than just adding a member function to our string class ?
Because a member function of a class has to be called for an object
of that class , whose address then becomes the this pointer; in the case
of the << operator, the class of the object is ostream , not string . Figure
8.33 is an example.

FIGURE 8.33. Why operator << has to be implemented via a global function

string x = "this is it"; cout


<< x;

The line cout << x; is the same as cout.operator << (x); . Notice that the
object to which the operator << call is applied is cout , not x . Since cout
is an ostream , not a string , we can’t use a member function of string to
do our output, but a global function is perfectly suitable.

Reading a string from an istream

Now that we have an output function that will write our string
variables to an ostream , such as cout , it would be very handy to have
an input function that could read a string from an istream , such as cin .
You might expect that this would be pretty simple now that we’ve
worked through the previous exercise, and you’d be mostly right. As
usual, though, there are a few twists in the path.
Let’s start by looking at the code in Figure 8.34.

A operator >> function to input a string (from


FIGURE 8.34.
code\string5.cpp)

std::istream& operator >> (std::istream& is, string& Str)


{
const short BUFLEN = 256;
char Buf[BUFLEN];
memset(Buf,0,BUFLEN);

if (is.peek() == ‘\n’)
is.ignore();
is.getline(Buf,BUFLEN,’\n’); Str
= Buf;

return is;
}

The header is pretty similar to the one from the operator << function,
which is reasonable, since they’re complementary functions. In this
case, we’re defining a global function with the signature std::istream&
operator >> (std::istream& is, string& Str). In other words, this function, called
operator >> , has a first argument that is a reference to an istream , which is
just like an ostream except that we read data from it rather than writing
data to it. One significant difference between this function signature
and the one for operator << is that the second argument is a non- const
reference, rather than a const reference, to the string into which we want
to read the data from the istream . That’s because the whole purpose of
this function is to modify the string passed in as the second argument;
to be exact, we’re going to fill it in with the characters taken out of the
istream .
Continuing with the analysis of the function declaration, the return
value is another istream reference, which is passed to the next operator
>> function to the right, if there is one; otherwise it will just be
discarded.
After decoding the header, l et’s move to the first line in the
function body, const short BUFLEN = 256; . While we’ve encountered const
before, specifying that we aren’t going to change an argument passed
to us, that can’t be the meaning here. What does const mean in this
context?
It specifies that the item being defined, which in this case is short
BUFLEN , isn’t a variable, but a constant, or const value. That is, its
value can’t be changed. Of course, a logical question is how we can
use a const , if we can’t set its value.9
8.6. Initialization vs. Assignment

This is another of the places where i t’s important to differentiate


between initialization and assignment. We can’t assign a value to a
const , but we can initialize it; in fact, because an uninitialized const is
useless, the attempt to define a const without specifying its initial (and
only) value is a compile-time error. In this case, we’re initializing it to
the value 256; if we just wrote const short BUFLEN; , we’d get an error
report something like the one in Figure 8.35 when we tried to compile
it.

FIGURE 8.35. Error from an uninitialized const (code\string5x.err)

STRING5X.cpp:
Error E2304 STRING5X.cpp 82: Constant variable ‘BUFLEN’ must be initialized in function
operator >>(_STL::istream &,string &)
Error E2313 STRING5X.cpp 84: Constant expression required in function operator >>
(_STL::istream &,string &)
*** 2 errors in Compile ***

Susan wanted some further explanation.

Susan: I still don’t get why const is used here.

Steve: This is a different use of const than we’ve seen before; in this case, it’s
an instruction to the compiler meaning "the following ’variable’ isn’t really variable,
but constant. Don’t allow it to be modified." This allows us to use it where we would
otherwise have to use a literal constant, like 256 itself. The reason that using a const
is better than using a literal constant is that it makes it easier to change all the
occurrences of that value. In the present case, for
9. In case you were wondering how I came up with the name BUFLEN , it’s short
for "buffer length". Also, I should mention the reason that it is all caps rather
than mixed case or all lower case: an old C convention (carried over into C++)
specifies that named constants should be named in all caps to enable the reader
to distinguish them from variables at a glance.
example, we use BUFLEN three times after its definition; if we used the literal
constant 256 in all of those places, we’d have to change all of them if we decided to
make the buffer larger or smaller. As it is, however, we only have to change the
definition of BUFLEN and all of the places where it’s used will use the new value
automatically.

Susan: Okay, I think I have it now.

Now that we’ve disposed of that detail, l et’s continue with our
examination of the implementation of operator >> . The next nonblank line
is char Buf[BUFLEN]; . This is a little different from any variable
definition we’ve seen before; however, you might be able to guess
something about it from its appearance. It seems to be defining a
variable called Buf whose type is related in some way to char. But
what about the [BUFLEN] part?10
This is a definition of a variable of that dreaded type, the array;
specifically, we’re defining an array called Buf , which contains
BUFLEN chars. As you may recall, this is somewhat like the Vec type that
we’ve used before, except that it has absolutely no error checking; if
we try to access a char that is past the end of the array, something will
happen, but not anything good.11 In this case, as in our previous use of
pointers, we’ll use this dangerous construct only in a very small part
of our code, under controlled circumstances; the user of our string class
won’t be exposed to the array.
Before we continue analyzing this function, I should point out that
C++ has a rule that the number of elements of an array must be known
at compile time. That is, the program in Figure 8.36 isn’t legal C++.

10. This is another common C practice; using "buf" as shorthand for "buffer", or
"place to store stuff while we’re working on it".
11. See the discussion of arrays starting on page 489.
FIGURE 8.36. Use of a non- const array size (code\string5y.cpp)

int main()
{
short BUFLEN = 256;
char ch;

char Buf[BUFLEN];

ch = Buf[0];
}

I’ll admit that I don’t understand exactly why using a non- const array
size is illegal; a C++ compiler has enough information to create and
access an array whose length is known at run time. In fact, some
compilers do allow it.12 But it is not compliant with the standard, so
we won’t use it in our programs. Instead, we’ll use the const value
BUFLEN to specify the number of chars in the array Buf in the statement
char Buf[BUFLEN]; .

The memset Standard Library Function

Now we’re up to the first line of the executable part of the operator >>
function in Figure 8.34 on page 541: memset(Buf,0,BUFLEN); . This is a
call to a function called memset (short for “memory set”), which is in
the standard C library. You may be able to guess from its name that it is
related to the function memcmp that we used to compare two arrays of
chars . If so, your guess would be correct; memset is C-talk for "set all
the bytes in an area of memory to the same value". The first argument
is the address of the area of memory to be set to a specified value, the
second argument is the char value to which all the

12. According to Eric Raymond, there is no good reason for this limitation; it’s a
historical artifact. In fact, it may be removed in a future revision of the C++
standard, but for now we’ll have to live with this limitation of C++.
bytes will be set, and the third argument is the number of bytes to be
set to that value, starting at the address given in the first argument. In
other words, this statement will set all of the bytes in the array called
Buf to 0. This is important because we’re going to treat that array as a
C string later. As you may recall, a C string is terminated by a null
byte, so we want to make sure that the array Buf doesn’t contain any
junk that might be misinterpreted as part of the data we’re reading in
from the istream .
Next, we have an if statement controlling a function called ignore :

if (is.peek() == ’\n’)
is.ignore();

What exactly does this sequence do? It solves a problem with reading
C string data from a file; namely, where do we stop reading? With a
numeric variable, that’s easy; the answer is "whenever we see a
character that doesn’t look like part of a number". However, with a
data type like our string that can take just about any characters as part
of its value, it’s more difficult to figure out where we should stop
reading. The solution I’ve adopted is to stop reading when we get to a
newline (’\n’) character; that is, the end of a line.13 This is no problem
when reading from the keyboard, as long as each data item is on its
own line, but what about reading from a file?
When we read a C string from a file via the standard function
getline (described in detail below), as we do in our operator >>
implementation, the newline at the end of the line is discarded. As a
result, the next C string to be read in starts at the beginning of the next
line of the file, as we wish. This approach to handling newline
characters works well as long as all of the variables being read in are
strings . However, in the case of the StockItem class (for example), we
needed to be able to mix shorts and strings in the file. In that case,
reading a value for a short stops at the newline, because that character

13. Note that this is different from the behavior of the standard library string class,
which won’t keep reading data for a string from a stream past a blank.
isn’t a valid part of a numeric value. This is OK as long as the next
variable to be read is also a short , because spaces and newlines at the
beginning of the input data are ignored when we’re reading a numeric
value. However, when the next variable to be read after a short is a
string , the leftover newline from the previous read is interpreted as the
beginning of the data for the string , which terminates input for the string
before we ever read anything into it. Therefore, we have to check
whether the next available char in the input stream is a newline, in
which case we have to skip it. On the other hand, if the next character
to be read in is something other than a newline, we want to keep it as
the first character of our string . That’s what the if statement does.
First, the s.peek() function call returns the next character in the input
stream without removing it from the stream; then, if it turns out to be a
newline, we tell the input stream to ignore it, so it won’t mess up our
reading of the actual data in the next line.
You won’t be surprised to hear that Susan had a couple of questions
about this function.

Susan: Where do peek and ignore come from?

Steve: They’re defined in the iostream header file < iostream> .

Susan: How did you know that they were available?

Steve: By reading a book called C++ IOstreams Handbook by Steve Teale.


However, that book is obsolete now that the standard library has been adopted. A
Standard C++ IOStreams
newer book that I understand is very good is
and Locales Advanced Programmer's Guide and Reference by
Angelika Langer & Klaus Kreft (Addison-Wesley, January 2000, ISBN 0-201-
18395- 1). Of course, there is also a fair amount of coverage of streams in both
The C++ Programming Language and The C++ Standard Library, but those are
both quite technical and not terribly well suited for beginning programmers.
Now that we’ve dealt with that detail, we’re ready to read the data for
our string . T ha t ’s the job of the next line in the function:
is.getline(Buf,BUFLEN,’\n’); . Since is is an istream , this is a member
function of istream . To be precise, it’s the member function that reads a
number of characters into a char array. The arguments are as follows:
1. The array into which to read characters
2. The number of characters that the array can contain
3. The "terminating character", where getline should stop reading
characters

This function will read characters into the array (in this case Buf ) until
one of two events occurs:
1. The size of the array is reached
2. The "terminating character" is the next character to be read

Note that the terminating character is not read into the array.
Before continuing with the rest of the code for operator >> , let’s take
a closer look at the following two lines, so we can see why it’s a bad
idea to use the C string and memory manipulation library any more
than we have to. The lines in question are

memset(Buf,0,BUFLEN); is.getline(Buf,BUFLEN,’\n’);

The problem is that we have to specify the length of the array Buf
explicitly (as BUFLEN , in this case). In this small function, we can
keep track of that length without much effort, but in a large program
with many references to Buf , it would be all too easy to make a
mistake in specifying its length. As we’ve already seen, the result of
specifying a length that is greater than the actual length of the array
would be a serious error in the functioning of the program; namely,
some memory belonging to some other variable would be
overwritten. Whenever we use the mem functions in the C library,
we’re liable to run into such problems. That’s an excellent reason to
avoid them except in strictly controlled situations, such as the present
one, where the definition of the array is in the same small function as
the uses of the array. By no coincidence, this is the same problem
caused by the indiscriminate use of pointers; the difficulty with the C
memory manipulation functions is that they use pointers (or arrays,
which are essentially interchangeable with pointers), with all of the
hazards that such use entails.
Now that I’ve nagged you sufficiently about the dangers of arrays,
let’s look at the rest of the operator >> code. The next statement is Str =
Buf; , which sets the argument Str to the contents of the array Buf . Buf
is the address of the first char in an array of chars , so its type is char* ;
Str, on the other hand, is a string . Therefore, this apparently innocent
assignment statement actually calls string::string(char*) to make a
temporary string , and then calls string::operator=(const string&) to copy that
temporary string to Str. Because Str is a reference argument, this causes
the string that the caller provided on the right of the >> to be set to the
value of the temporary string that was just created.
Finally, we have the statement return is; . This simply returns the
same istream that we got as an argument, so that the next input operator
in the same statement can continue reading from the istream where we
left off. Now our strings can be read from an input stream (such as cin )
and written to an output stream (such as cout ), just like variables
known to the standard library. This allows our program that sorts
strings to do some useful work.14
Assuming that you’ve installed the software from the CD in the
back of this book, you can try out this program. First, you have to
compile it by following the compilation instructions on the CD. Then
type strsort1 to run the program. You can also run it under the debugger,
by following the usual instructions for that method.
Now that we’ve finished our upgrades to the string class , let’s look
back at what we’ve covered since our first review in this chapter.
8.7. Second Review

After finishing the requirements to make the string class a concrete data
type, we continued to add more facilities to this class ; to be precise,
we wanted to make it possible to modify the sorting program of
Chapter 4 to handle strings rather than shorts . To do this, we had to be
able to compare two strings to determine which of the two would come
first in the dictionary and to read strings from an input stream (like cin )
and write them to an output stream (like cout ). Although the Display
function provided a primitive mechanism for writing a string to cout ,
it’s much nicer to be able to use the standard >> and << operators that
can handle all of the native types so we resolved to make those
operators available for strings as well.
We started out by implementing the < operator so that we could
compare two strings x and y to see which would come before the other in
the dictionary, simply by writing if (x < y) . The implementation of this
function turned out to be a bit complicated because of the possibility
of "running off the end" of one of the strings , when the strings are of
different lengths.
Once we worked out the appropriate handling for this situation,
we examined two implementations of the algorithm for operator < . The
first implementation compared characters from the two strings one at a
time, while the second used memcmp , a C function that compares two
sets of bytes and returns a different value depending on
14. The implementation of op erat or << will also work for any other output
destination, such as a file; however, our current implementation of op erator >>
isn’t really suitable for reading a st ring from an arbitrary input source. The
reason is that we’re counting on the input data being able to fit into the Buf
array, which is 256 bytes in length. This is fine for input from the keyboard, at
least under DOS, because the maximum line length in that situation is 128
characters. It will also work for our inventory file, because the lines in that file
are shorter than 256 bytes. However, there’s no way to limit the length of lines
in any arbitrary data file we might want to read from, so this won’t do as a
general solution. Of course, increasing the size of the Buf array wouldn’t solve
the problem; no matter how large we make it, we couldn’t be sure that a line
from a file wouldn’t be too long. One solution would be to handle long lines in
sections.
whether the first set is "less than", "equal to", or "greater than" the
second one, using ASCII ordering to make this determination.
Then we developed an implementation of operator == for strings ,
which turned out to be considerably simpler than the second version of
operator < , even though both functions used memcmp to do most of the
work; the reason is that we have to compare the contents of the strings
only if they are of the same length, because strings of different lengths
cannot be equal.
Then we started looking beneath the covers of the output functions
called operator<< , starting with the predefined versions of
<< that handle char and C string arguments. The simplest case of using
this operator, of course, is to display one expression on the screen via
cout . Next, we examined the mechanism by which several uses of this
operator can be chained together to allow the displaying of a number
of expressions with one statement.
The next issue was how to provide these handy facilities for the
users of our string class . Would we have to modify the ostream classes to
add support for strings ? Luckily, the designers of the stream classes were
foresightful enough to enable us to add support for our own data types
without having to modify their code. The key is to create a global
function that can add the contents of our string to an existing ostream
variable and pass that ostream variable on to the next possible user,
just as in “chaining” for native types.
The implementation of this function wasn’t terribly complicated; it
merely wrote each char of the string’s data to the output stream . The
unusual attribute of this function was that it wasn’t a member function
of string , but a global function, as is needed to maintain the same
syntax as the output of native types. We used the friend specifier to allow
this version of operator << to access private members of string such as
m_Length and m_Data .
After we finished the examination of our version of operator << for
sending strings to an ostream , we went through the parallel exercise of
creating a version of operator >> to read strings from an istream . This
turned out to be a bit more complicated, since we had to make room
for the incoming data. This limited the maximum length
of a string that we could read. In the process of defining this maximum
length, we also encountered a new construct, the const . This is a data
item that is declared just like a variable, except that its value is
initialized once and cannot be changed . This makes the const ideal for
specifying a constant size for an array, a constant loop limit, or another
value that doesn’t change from one execution of the program to the
next. Next, we used this const value to declare an array of chars to
hold the input data to be stored in the string , and filled the array with
null bytes, by calling the C function memset . We followed this by using
some member functions of the istream class to eliminate any newline
( ’\n’ ) character that might have been left over from a previous input
operation.
Finally, we were ready to read the data into the array of chars , in
preparation for assigning it to our string . After doing that assignment,
we returned the original istream to the caller, to allow chaining of
operations as is standard with operator << and operator >> .
That completes the review of this chapter. Now let’s do some
exercises to help it all sink in.

8.8. Exercises
1. What would happen if we compiled the program in Figure 8.37?
Why?

FIGURE 8.37. Exercise 1


(code\strex5.cpp)

class string
{
public:
string(const string& Str);
string(char* p);
string& operator = (const string& Str);
~string();
private:
string();
short m_Length;
char* m_Data;
};

int main()
{
string n(“Test”); string
x = n;

n = “My name is Susan”;

return 0;
}

2. What would happen if we compiled the program in Figure 8.38?


Why?

FIGURE 8.38. Exercise 2 (code\strex6.cpp)

class string
{
public:
string();
string& operator = (const string& Str); private:
string(char* p);
short m_Length;
char* m_Data;
};

int main()
{
string n;

n = “My name is Susan”;

return 0;
}
3. We have already implemented operator < and operator == . However, a
concrete data type that allows for ordered comparisons such as <
should really implement all six of the comparison operators. The
other four of these operators are > , >= , <= , and != ("greater
than", "greater than or equal to", "less than or equal to", and "not
equal to", respectively). Add the declarations of each of these
operators to the string interface definition.
4. Implement the four comparison operators that you declared in the
previous exercise.
5. Write a test program to verify that all of the comparison operators
work. This program should test that each of the operators returns the
value true when its condition is true; equally important, it should test
that each of the operators returns the value false when the condition
is not true.

8.9. Conclusion

In this chapter, we have significantly improved the string class , learning


some generally useful techniques and lessons in the process. In the
next chapter, we’ll return to our inventory control example, which
we’ll extend with some techniques that we haven’t seen before. First,
though, you should finish up with this chapter by doing the exercises.
You have been doing the exercises, haven’t you? If not, you should
definitely go back and do them. If you can get all of the answers right,
including the reasons why the answers are the way they are, then you’ll
be ready to continue learning some of the more advanced concepts of
C++ in the rest of this book.
8.10. Answers to Exercises
1. This one was a little tricky. I’ll bet you thought that making the default
constructor private would keep this from compiling, but it turns out
that we’re not using the default constructor. That should be obvious
in the line string n("Test");, which clearly uses string::string(char* p) , but
what does the compiler do with the line string x = n; ? You might think
that it calls the default constructor to make x and then uses operator =
to copy the value of n into it. If that were true, the private status of
the default constructor would prevent the program from compiling.
However, what actually happens is that the copy constructor
string::string(const string&) is used to make a brand new string called x
with the same value as n . So, in this case, the private access
specifier on the default constructor doesn’t get in the way.
2. The output of the compiler should look something like this:

STREX6.cpp:
Error E2247 STREX6.cpp 16: ‘string::string(char *)’ is not accessible in function main()
*** 1 errors in Compile ***

This one is a bit tricky. The actual problem is that making the
constructor string::string(char*) private prevents the automatic conversion
from char* to string required for the string::operator = (const string&)
assignment operator to work. As long as there is an accessible
string::string(char*) constructor, the compiler will use that constructor to
build a temporary string from a char* argument on the right side of
an = . This temporary string will then be used by string::operator = (const
string&) as the source of data to modify the string on the left of the = .
However, this is not possible if the constructor that makes a string
from a char* isn’t accessible where
it is needed.15
3. The new class interface is shown in Figure 8.39.
FIGURE 8.39. The string class
interface file (from code\string6.h)

#ifndef STRING6_H
#define STRING6_H
#include <iostream>

class string
{
friend std::ostream& operator << (std::ostream& os, const string& Str); friend
std::istream& operator >> (std::istream& is, string& Str);

public:
string();
string(const string& Str);
string& operator = (const string& Str);
~string();

string(char* p); short


GetLength();
bool operator < (const string& Str); bool
operator == (const string& Str); bool operator >
(const string& Str); bool operator >= (const
string& Str); bool operator <= (const string&
Str); bool operator != (const string& Str);

private:
short m_Length;
char* m_Data;
};
#endif

4. The implementations of the comparison operators are shown in


Figures 8.40 through 8.43.

15. By the way, in case you’re wondering what char * means, it’s the same as
char*. As I’ve mentioned previously, I prefer the latter as being easier to
understand, but they mean the same to the compiler.
FIGURE
8.40. The string class implementation of operator > (from code\string6.cpp)

bool string::operator > (const string& Str)


{
short Result;
short CompareLength;

if (Str.m_Length < m_Length)


CompareLength = Str.m_Length;
else
CompareLength = m_Length;
Result = memcmp(m_Data,Str.m_Data,CompareLength); if (Result > 0)
return true;

if (Result < 0)
return false;

if (m_Length > Str.m_Length) return true;

return false;
}

FIGURE 8.41. The string class implementation of operator >= (from code\string6.cpp)

bool string::operator >= (const string& Str)


{
short Result;
short CompareLength;

if (Str.m_Length < m_Length)


CompareLength = Str.m_Length;
else
CompareLength = m_Length;
Result = memcmp(m_Data,Str.m_Data,CompareLength);

if (Result > 0)
return true;

if (Result < 0)
return false;

if (m_Length >= Str.m_Length) return true;

return false;
}

The string class implementation of operator != (from


FIGURE 8.42.
code\string6.cpp)

bool string::operator != (const string& Str)


{
short Result;

if (m_Length != Str.m_Length) return true;


Result = memcmp(m_Data,Str.m_Data,m_Length); if (Result ==
0)
return false;

return true;
}

The string class implementation of operator <= (from


FIGURE 8.43.
code\string6.cpp)

bool string::operator <= (const string& Str)


{
short Result;
short CompareLength;

if (Str.m_Length < m_Length) CompareLength


= Str.m_Length;
else
CompareLength = m_Length;
Result = memcmp(m_Data,Str.m_Data,CompareLength); if (Result < 0)
return true;

if (Result > 0)
return false;

if (m_Length <= Str.m_Length) return true;

return false;
}

5. The test program appears in Figure 8.44.

The test program for the comparison operators of the string class
FIGURE 8.44.
(code\strcmp.cpp)

#include <iostream>
#include “string6.h”
using std::cout; using
std::endl;

int main()
{
string x = “x”;
string xx = “xx”;
string y = “y”;
string yy = “yy”;
// testing < if
(x < x)
cout << “ERROR: x < x” << endl; else
cout << “OKAY: x NOT < x” << endl; if (x <
xx)
cout << “OKAY: x < xx” << endl; else
cout << “ERROR: x NOT < xx” << endl; if (x <
y)
cout << “OKAY: x < y” << endl; else
cout << “ERROR: x NOT < y” << endl;

// testing <= if
(x <= x)
cout << “OKAY: x <= x” << endl; else
cout << “ERROR: x NOT <= x” << endl; if (x <=
xx)
cout << “OKAY: x <= xx” << endl; else
cout << “ERROR: x NOT <= xx” << endl; if (x <=
y)
cout << “OKAY: x <= y” << endl; else
cout << “ERROR: x NOT <= y” << endl;

// testing > if
(y > y)
cout << “ERROR: y > y” << endl; else
cout << “OKAY: y NOT > y” << endl; if (yy >
y)
cout << “OKAY: yy > y” << endl; else
cout << “ERROR: yy NOT > y” << endl; if (y >
x)
cout << “OKAY: y > x” << endl; else
cout << “ERROR: y NOT > x” << endl;
// testing >= if
(y >= y)
cout << “OKAY: y >= y” << endl; else
cout << “ERROR: y NOT >= y” << endl; if (yy
>= y)
cout << “OKAY: yy >= y” << endl; else
cout << “ERROR: yy NOT >= y” << endl; if (y >=
x)
cout << “OKAY: y >= x” << endl; else
cout << “ERROR: y NOT >= x” << endl;

// testing == if
(x == x)
cout << “OKAY: x == x” << endl; else
cout << “ERROR: x NOT == x” << endl; if (x ==
xx)
cout << “ERROR: x == xx” << endl; else
cout << “OKAY: x NOT == xx” << endl; if (x ==
y)
cout << “ERROR: x == y” << endl; else
cout << “OKAY: x NOT == y” << endl;

// testing != if
(x != x)
cout << “ERROR: x != x” << endl; else
cout << “OKAY: x NOT != x” << endl; if (x !=
xx)
cout << “OKAY: x != xx” << endl; else
cout << “ERROR: x NOT != xx” << endl; if (x !=
y)
cout << “OKAY: x != y” << endl; else
cout << “ERROR: x NOT != y” << endl;

return 0;
}
CHAPTER 9 Inheritance

We ’r e going to continue our investigation of C++ concepts and


practices by revisiting the inventory control example from Chapter 6.
We’ll build on the StockItem class that we created there by using one of
the primary organizing principles of C++ that we haven’t encountered
before: inheritance. First, let’s define this term and a few others that
we’ll be using in this chapter; then we’ll take a look at the objectives.

9.1. Definitions

Inheritance is the definition of one class as a more specific version of


another class that has been previously defined. The newly defined class
is called the derived (or sometimes child) class , and the previously
defined class is called the base (or sometimes parent) class . In this
book, we will use the terms base and derived. The
derived class inherits all of the member variables and regular member
functions from the base class . Inheritance is one of the primary
organizing principles of object-oriented programming.

A regular member function is a member function that is not in any of


the following categories:
1. constructor,
2. destructor,
3. assignment operator (i.e., operator = ).

A member function in a derived class is said to override a base class


member function if the derived class function has the same signature
(name and argument types) as that of the base class member function.
The derived class member function will be called instead of the base
class member function when the member function is referred to via an
object of the derived class . A member function in a derived class with
the same name but a different signature from that of a member function
in the base class does not override the base class member function.
Instead, it “hides” that base class member function, which is no longer
accessible as a member function in the derived class .
For example, the function Reorder(ostream &) may be defined in a base
class ( StockItem ) and in a derived class ( DatedStockItem ). When Reorder is
called via an object of the base class StockItem , the base class version of
Reorder will be called; when Reorder is called via an object of the
derived class DatedStockItem , the derived class version of Reorder will be
called. This behavior of C++ allows a derived class to supply the
same functionality as a base class but implement that functionality in a
different way.

A manipulator is a special type of member function of one of the


iostream classes . Such a function controls how output will be formatted
without itself necessarily producing any output.
Objectives of This Chapter

A static member function is a member function of a class that can be


called without reference to an object of that class . Such a function has
no this pointer passed to it on entry, and therefore it cannot refer to
member variables of the class .

Buffering is the use of a buffer to store or retrieve information.

A normal constructor is a constructor whose arguments supply


enough information to initialize all of the member fields in the object
being created.

The base class part of a derived class object is an unnamed


component of the derived class object. Member variables and
functions of a base class part declared in the public or protected part of
the base class interface are accessible in the derived class member
functions as though they were defined in the derived class .

The keyword protected is an access specifier. When present in a base


class definition, it allows derived class functions access to members in
the base class part of a derived class object while preventing access
by other functions outside the base class .

9.2. Objectives of This Chapter

By the end of this chapter, you should


1. understand how we can use inheritance to create a new class by
extending an existing class , and
2. understand how to use manipulators to control the format of
iostream output.
Two Reasons to Use Inheritance

Before we return to the detailed examination of our inventory control


classes (StockItem and its companion class Inventory ) let’s expand a bit on
the first objective as it applies to this case.
There are two reasons to use inheritance. The first is to create a
new class that has all of the capabilities of an existing class while
adding capabilities that are unique to the new class . In such a case,
objects of the new class are clearly not equivalent to objects of the
existing class , which means that the user of these classes has to know
which class any given object belongs to so that he or she can tell
which operations that object can perform. We could call this use of
inheritance “inheritance for extension”. It’s illustrated by one of the
Employee class exercises in this chapter.1
In the current case, however, we’ll be using inheritance to create a
new class called DatedStockItem that will behave exactly like the
StockItem class except that its items will have expiration dates. As a
result, the user of these classes will be able to treat objects of the new
DatedStockItem class in the same way as those of the base class . Of course,
to create an object of this new class , the expiration date for the object
must be provided, but once such an object exists its user can view it
exactly as if it were as an object of the base class , which makes this an
example of “inheritance for reimplementation”. In such a case, it is
reasonable to be able to substitute objects of the derived class for
those of the base class. This relationship between a base class and a
derived class is commonly expressed by saying that the derived class
“isA” base class object. We will see how to use that characteristic of
inheritance in the next chapter.
Before we can do that, though, we’ll need to learn how to create a
new class by derivation from an existing class . In this case, we’re
going to start with a version of our previous StockItem class that has

1. As elsewhere in this book, when I speak of the “user” of a class , I mean the
application programmer who is using objects of the class to perform work in his
or her program, not the “end user” who is using the finished program.
been extended slightly to include a Reorder function that generates a
reordering report when we get low on an item, as well as some new
input and output facilities. We’ll also need to improve on our
companion Inventory class , which as before we’ll use to keep track of all
the StockItems in the store.
Now let’s get to the details of how this version of the StockItem class
works. Figure 9.1 shows the header file for that class .
Here’s a rundown on the various member functions of the
StockItem class , including those that we’ve already seen:

1. StockItem(); is the default constructor.


2. StockItem(string Name, short InStock, short Price, short MinimumStock, short
is the normal constructor,
MinimumReorder, string Distributor, string UPC);
which has a couple of new arguments, MinimumStock and
MinimumReorder , which are needed for the reordering function.

3. displays the member variables of a


void FormattedDisplay(ostream& os);
StockItem object with labels so you can tell which value is for
which member variable.
4. bool CheckUPC(string ItemUPC); returns true if its argument is the same as
the UPC (i.e., the m_UPC member variable) of its StockItem .
5. reduces the inventory (i.e.,
void DeductSaleFromInventory(short QuantitySold);
the value of the m_InStock member variable) by the value of its
argument.
6. short GetInventory(); returns the number of items in stock for this
StockItem (i.e., the value of the m_InStock member variable).
7. string GetName(); returns
the name of the StockItem (i.e., the value of the
m_Name member variable).
8. string GetUPC(); returns the UPC of the StockItem (i.e., the value of the
m_UPC member variable).
9. bool IsNull(); returns true if this
is a “null StockItem ”. This can
happen, for example, when a StockItem is returned as a “not
found” value by a search.
returns the price of the StockItem (i.e., the value of the
10. short GetPrice();
m_Price member variable).
11. void Reorder(ostream& os); generates
a reorder report based on the
relationship of the number in stock ( m_InStock ) versus the minimum
desired stock ( m_MinimumStock ), taking the minimum reorder
quantity ( m_MinimumReorder ) into account.

FIGURE 9.1. The next StockItem


header file (code\item20.h)

class StockItem
{
friend std::ostream& operator << (std::ostream& os, const
StockItem& Item);
friend std::istream& operator >> (std::istream& is, StockItem& Item); public:
StockItem();

StockItem(std::string Name, short InStock, short Price,


short MinimumStock,
short MinimumReorder, std::string Distributor, std::string UPC);

void FormattedDisplay(std::ostream& os); bool


CheckUPC(std::string ItemUPC);
void DeductSaleFromInventory(short QuantitySold); short
GetInventory();
std::string GetName();
std::string GetUPC(); bool
IsNull();
short GetPrice();

void Reorder(std::ostream& os);


private:
short m_InStock; short
m_Price;
short m_MinimumStock; short
m_MinimumReorder; std::string
m_Name; std::string
m_Distributor; std::string
m_UPC;
};

Here’s a brief description of the input/output operators for this class ,


which will allow us to read and write its objects in much the same
way as we can do with the built-in types and the string class we’ve
created earlier in the book:
1. sends a
friend ostream& operator << (ostream& os, const StockItem& Item);
human-readable version of the state of a StockItem object to the
ostream specified as the left-hand argument to << . This is analogous
to the use of operator << for output of the built-in types.
2. friend istream& operator >> (istream& is, StockItem& Item); creates a StockItem
object by reading a human-readable version of the state of the
object from the istream specified as the left-hand argument to >> .
This is analogous to the use of operator >> for input of the built-in
types.

Susan wanted to know why we needed the FormattedDisplay function.

Susan: Do we need the FormattedDisplay to make the data appear on the screen
the way we want it? I mean, does the FormattedDisplay function do something that
we can’t do by just using operator << ?

Steve: Yes. It puts labels on the data members so you can tell what they are.
Figure 9.2 shows the implementation of the StockItem class that we will
start our inheritance exercise from.

FIGURE 9.2. The next


implementation of StockItem (code\item20.cpp)

#include <iostream>
#include <string>
#include “item20.h” using
namespace std;

StockItem::StockItem()
: m_InStock(0), m_Price(0), m_MinimumStock(0),
m_MinimumReorder(0), m_Name(), m_Distributor(), m_UPC()
{
}

StockItem::StockItem(string Name, short InStock, short Price,


short MinimumStock,
short MinimumReorder, string Distributor, string UPC)
: m_InStock(InStock), m_Price(Price), m_MinimumStock(MinimumStock),
m_MinimumReorder(MinimumReorder), m_Name(Name),
m_Distributor(Distributor), m_UPC(UPC)
{
}

void StockItem::FormattedDisplay(ostream& os)


{
os << “Name: “;
os << m_Name << endl; os <<
“Number in stock: “; os <<
m_InStock << endl; os <<
“Price: “;
os << m_Price << endl; os <<
“Minimum stock: “;
os << m_MinimumStock << endl;
os << “Minimum Reorder quantity: “; os <<
m_MinimumReorder << endl; os <<
“Distributor: “;
os << m_Distributor << endl; os <<
“UPC: “;
os << m_UPC << endl;
}

ostream& operator << (ostream& os, const StockItem& Item)


{
os << Item.m_Name << endl; os <<
Item.m_InStock << endl; os <<
Item.m_Price << endl;
os << Item.m_MinimumStock << endl; os <<
Item.m_MinimumReorder << endl; os <<
Item.m_Distributor << endl;
os << Item.m_UPC << endl;

return os;
}

istream& operator >> (istream& is, StockItem& Item)


{
getline(is,Item.m_Name); is >>
Item.m_InStock;
is >> Item.m_Price;
is >> Item.m_MinimumStock;
is >> Item.m_MinimumReorder;
is.ignore();
getline(is,Item.m_Distributor);
getline(is,Item.m_UPC);
return is;
}

bool StockItem::CheckUPC(string ItemUPC)


{
if (m_UPC == ItemUPC)
return true;

return false;
}

void StockItem::DeductSaleFromInventory(short QuantitySold)


{
m_InStock -= QuantitySold;
}

short StockItem::GetInventory()
{
return m_InStock;
}

string StockItem::GetName()
{
return m_Name;
}

string StockItem::GetUPC()
{
return m_UPC;
}

bool StockItem::IsNull()
{
if (m_UPC == ““)
return true;

return false;
}

short StockItem::GetPrice()
{
return m_Price;
}

void StockItem::Reorder(ostream& os)


{
short ActualReorderQuantity;

if (m_InStock < m_MinimumStock)


{
ActualReorderQuantity = m_MinimumStock - m_InStock; if
(m_MinimumReorder > ActualReorderQuantity)
ActualReorderQuantity = m_MinimumReorder;
os << “Reorder “ << ActualReorderQuantity;
os << “ units of “ << m_Name; os << “
with UPC “ << m_UPC;
os << “ from “ << m_Distributor << endl;
}
}

Susan had a lot of questions about the operator << and operator >>
functions for this class as well as about streams in general.

Susan: Why do you have to define these functions again?

Steve: They have to be defined for every class of objects we want to be able to
use them for. After all, every class of objects has different data items in it; how is a
stream supposed to know how to read or write some object that we’ve made up,
unless we tell it how to?

Susan: What’s “ is ” again? I forgot. :-P

Steve: The istream that we’re using to get the data for the
StockItem .

Susan: So, it’s just a file? Is it always called is ?

Steve: No, it’s not a file; it’s an istream , which is an object connected to a file
that allows us to read from the file using >> .

Susan: Do you mean any file that has >> or << ? If it is like an istream where
does the data end up? Just how does it work? When does the istream start flowing
and at what point does the data jump in and get out? What is the istream doing
when there is no data to be transported? Where is it flowing? If it is not a file, then
where is it stored? So, whenever you read something from an istream , is it always
called “ is ”?

Steve: Obviously streams are going to take a lot more explaining, with pictures.

We’ll get to it later in this chapter.2


9.3. Taking Inventory

The StockItem class is designed to keep track of an individual item in the


inventory, but we also need a way to keep track of all the StockItems in
the store. So let’s take a look at the next version of the class that serves
that purpose in the inventory control application, Inventory . Figure 9.3
shows the header file for this version of the Inventory class , which is
essentially identical to the last one from Chapter 6, except that we
have added a ReorderItems function to allow us to generate a reorder
report for all of the items in stock.

FIGURE 9.3. The next header file


for the Inventory class (code\invent20.h)

class Inventory
{
public:
Inventory();

short LoadInventory(std::istream& is); void

StoreInventory(std::ostream& os);
StockItem FindItem(std::string UPC); bool
UpdateItem(StockItem Item); void
ReorderItems(std::ostream& os);

private:
Vec<StockItem> m_Stock; short
m_StockCount;
};

Besides the default constructor, this class has several other member
functions that we should discuss briefly, including those that we have
already discussed in Chapter 6.

2. See “The stream classes” on page 621.


1. LoadInventoryreads data from an istream to create StockItem
objects for the inventory.
2. StoreInventory writes the current StockItem data to an ostream to save it
for posterity.
3. FindItem locates an item in the inventory by its UPC.
4. UpdateItem updates the data for an item in the inventory.
5. ReorderItems calls each item in the inventory and asks it to generate a
line for the reordering report, which tells the user how many of
each item need to be reordered from the distributor.

Susan had some questions about the arguments to LoadInventory and


StoreInventory :

Susan: What are is and os ? Why didn’t you talk about them?

Steve: They’re the names of the reference arguments of type istream and
ostream , respectively, as indicated in the header file. They allow us to access the
streams that we use to read data from the input file and write data to the output file.

9.4. Adding ReorderItems to the Inventory class

Now let’s examine the details of the part of this inventory control
program that calculates how much of each item has to be ordered to
refill the stock. As I mentioned previously, I’ve chosen the imaginative
name ReorderItems for the member function in the Inventory class that will
perform this operation. The ReorderItems function is pretty simple. Its
behavior can be described as follows:
‘For each element in the StockItem Vec in the Inventory object, call
its member function Reorder to generate an order if that StockItem
object needs to be reordered.’
Of course, this algorithm is much simpler than we would need in the
real world; however, it’s realistic enough to be useful in illustrating
important issues in program design and implementation.
Figure 9.4 shows the version of the inventory test program that
uses the initial versions of those classes that we will build on in our
inheritance exercise.
FIGURE 9.4. The StockItem test program for the base StockItem class
(code\itmtst20.cpp)

#include <iostream>
#include <fstream>
#include “Vec.h”
#include “item20.h”
#include “invent20.h” using
namespace std;

int main()
{
ifstream ShopInfo(“shop20.in”); ofstream
ReorderInfo(“shop20.reo”);

Inventory MyInventory; MyInventory.LoadInventory(ShopInfo);

MyInventory.ReorderItems(ReorderInfo);
return 0;
}

This shouldn’t be too hard to follow. We start by creating an ifstream


(i.e., an input stream that can be connected to a file) and an ofstream
(i.e., an output stream that can be connected to a file). Then we create an
inventory object called MyInventory , load items into it from the ifstream ,
and call the ReorderItems function to create the reorder report that is
written to the ofstream . Finally, we return 0 to indicate successful
completion.
Assuming that you’ve installed the software from the CD in the
back of this book, you can try out this program. First, you have to
compile it by following the compilation instructions on the CD. Then
type itmtst20 to run the program. However, this program doesn’t have
much of a user interface so you might want to watch it operating under
the debugger, by following the usual instructions for that method.
When the program terminates, you can look at its output file, shop20.reo ,
to see what the reorder report looks like.
Now let’s take a look at how the Inventory class does its work
(Figure 9.5).3

FIGURE 9.5. The implementation of the Inventory class (code\invent20.cpp)

#include <iostream>
#include <fstream>
#include “Vec.h”
#include “item20.h”
#include “invent20.h” using
namespace std;

Inventory::Inventory()
: m_Stock (Vec<StockItem>(100)),
m_StockCount(0)
{
}

short Inventory::LoadInventory(istream& is)


{
short i;

for (i = 0; i < 100; i ++)


{

3. Note that the declarations of LoadInventory , StoreInventory , and ReorderItems


refer to istreams and ostreams , not the file stream types. As I mentioned previously,
this is legal because of the relationships among these types. I’ll explain exactly
why on page 613.
is >> m_Stock[i]; if
(is.fail()) break;
}

m_StockCount = i; return
m_StockCount;
}

StockItem Inventory::FindItem(string UPC)


{
short i;
bool Found = false;

for (i = 0; i < m_StockCount; i ++)


{
if (m_Stock[i].GetUPC() == UPC)
{
Found = true;
break;
}
}

if (Found)
return m_Stock[i];

return StockItem();
}

bool Inventory::UpdateItem(StockItem Item)


{
string UPC = Item.GetUPC();

short i;
bool Found = false;

for (i = 0; i < m_StockCount; i ++)


{
if (m_Stock[i].GetUPC() == UPC)
{
Found = true;
break;
}
}

if (Found) m_Stock[i] =
Item;

return Found;

void Inventory::StoreInventory(ostream& os)


{
short i;

for (i = 0; i < m_StockCount; i ++) os <<


m_Stock[i];
}

void Inventory::ReorderItems(ostream& os)


{
short i;

for (i = 0; i < m_StockCount; i ++)


m_Stock[i].Reorder(os);
}

The ReorderItems function can hardly be much simpler. As you can see,
it merely tells each StockItem element in the m_Stock Vec to execute its
Reorder function. Now let’s see what the latter function, whose full
name is void StockItem::Reorder(ostream&) , needs to do:
1. Check to see if the current stock of that item is less than the
desired minimum.
2. If we are below the desired stock minimum, order the amount needed
to bring us back to the stock minimum, unless that order amount is
less than the minimum allowable quantity from the distributor. In the
latter case, order the minimum allowable reorder quantity.
3. If we are not below the desired stock minimum, do nothing.

To support this Reorder function, StockItem uses the data items


m_MinimumStock and m_MinimumReorder to calculate the number of units of
the current StockItem object that have to be reordered. Figure
9.6 shows the code for the Reorder function.

The Reorder function for the StockItem class (from


FIGURE 9.6.
code\item20.cpp)

void StockItem::Reorder(ostream& os)


{
short ActualReorderQuantity;

if (m_InStock < m_MinimumStock)


{
ActualReorderQuantity = m_MinimumStock - m_InStock; if
(m_MinimumReorder > ActualReorderQuantity)
ActualReorderQuantity = m_MinimumReorder;
os << “Reorder “ << ActualReorderQuantity; os << “
units of “ << m_Name;
os << “ with UPC “ << m_UPC;
os << “ from “ << m_Distributor << endl;
}
}

Here’s the translation of this code:


1. If the number of units in stock is less than the minimum number
desired, we calculate the number needed to bring the inventory
back to the minimum.
2. However, the number we want to order may be less than the
minimum we are allowed to order; the latter quantity is specified
by the variable m_MinimumReorder .
3. If the amount we need is less than m_MinimumReorder , we have to order
the latter quantity
4. Finally, we display the order for the item. Of course, if we already
have enough units in stock we don’t have to reorder anything, so we
don’t display anything.

Susan had a question about the implementation of this function:

Susan: So, are you ordering more than needed in some cases?

Steve:
Yes, if that’s the minimum number that can be ordered.

9.5. Adding Expiration Dates

Now we want to add one wrinkle to this algorithm: handling items that
have expiration dates. This actually applies to a fair number of items
in a typical grocery store, including dairy products, meats, and even
dry cereals. To keep things as simple as possible, we’ll assume that
whenever we buy a batch of some item with an expiration date, all of
the items of that type have the same date. When we get to the
expiration date of a given StockItem , we send back all of the items and
reorder as though we had no items in stock.
The first question is how we store the expiration date. My first
inclination was to use an unsigned short to store each date as a number
representing the number of days from (for example) January 1, 2000,
to the date in question. Since there are approximately 365.25 days in a
year, the range of 65535 days should hold us roughly until the year
2179, which should be good enough for our purposes. Perhaps by that
year, we’ll all be eating food pills that don’t spoil.4
However, storing a date as a number of days since a “base date”,
such as January 1, 2000, does require a means of translating a human-
readable date format like “September 4, 2002” into a number of days
from the base date and vice versa. Owing to the peculiarities of our
Gregorian calendar (primarily the different numbers of days in
different months and the complication of leap years), this is not a
trivial matter and is a distraction from our goal here.
However, if we represent a date as a string of the form
YYYYMMDD, where YYYY is the year, MM is the month, and DD is
the day within the month, we can use the string comparison functions to
tell us which of two dates is later than the other one.5 Here’s the
analysis:
1. Of two dates with different year numbers, whichever has the
higher year number is a later date.
2. Of two dates with the same year number but different month numbers,
whichever has the higher month number is a later date.
3. Of two dates having the same year and month numbers, whichever has
the higher day number is a later date.

Because the string comparison operators compare bytes from left to


right and stop when a mismatch is detected, as is needed for
alphabetical sorting, it should be clear that dates using the
representation YYYYMMDD will have their year numbers compared
first, followed by the month numbers if needed, followed by the day

4. Of course, thinking that your program can’t possibly last long enough that you
need to worry about running off the end of its legal date range is what led to a
lot of frantic maintenance work as we approached the year 2000.
5. In case you’re wondering why I allocated 4 digits for the year, it was to ensure
that the program will work both before and after a change of the century part
of the date (e.g., from 1999 to 2000). Unfortunately, not all programmers have
been so considerate. Many programs use a 2-digit number to represent the year
portion of a date in the form YYMMDD and as a result, behaved oddly during
the so-called “Y2K transition”.
numbers if needed. Thus, comparing two strings via string::operator > will
produce the result true if the “date string ” on the left represents a date
later than the “date string ” on the right, exactly as it should.
Now that we’ve figured out that we can store the expiration date
as a string , how do we arrange for it to be included in the StockItem
object? One obvious solution is to make up a new class called, say,
DatedStockItem, by copying the interface and implementation from
StockItem , adding a new member variable m_Expires , and modifying the
copied Reorder member function to take the expiration date into
account. However, doing this would create a maintenance problem
when we had to make a change that would affect both of these classes ,
as we’d have to make such a change in two places. Just multiply this
nuisance ten or twenty times and you’ll get a pretty good idea of how
program maintenance has acquired its reputation as difficult and
tedious work.6
Susan had some questions about this notion of program
maintenance:

Susan: What kind of change would you want to make? What is maintenance?
What is a typical thing you would want to do to some code?

Steve: Maintenance means any kind of change to a program after it is already


being used. For example, if we decide that a short isn’t the right type of variable to
hold the price, then we have to change its definition to some other type. We’ll see
lots of examples of maintenance in Chapter 12.

Since one of the purposes of object-oriented programming is to reduce


the difficulty of maintaining programs, surely there must be a better
way to create a new class “just like” StockItem but with an added
member variable and a modified member function to use it.
6. While maintenance has long been a neglected area of computer science, and
the programming industry for that matter, that has changed for the better in
recent years.
Reducing Maintenance Via Inheritance

Yes, there is; i t’s called inheritance. We can define our new class
called DatedStockItem with a notation that it inherits (or derives) from
StockItem . This makes StockItem the base class ( sometimes referred to as
the parent class ) and our new DatedStockItem class the derived class
(sometimes referred to as the child class ). By doing this, we are
specifying that a DatedStockItem includes every data member and regular
member function a StockItem has. Since DatedStockItem is a separate class
from StockItem , when we define DatedStockItem we can also add
whatever other functions and data we need to handle the differences
between StockItem and DatedStockItem .
Susan wanted to clarify some terms:

Susan: Are inheritance and derivation the same thing?

Steve: Yes. To say that B inherits from A is the same as saying that B is derived
from A.

She also had some questions about the relationship between the
notions of friend and inheritance.

Susan: How about a little reminder about friend here, and how about explaining
the difference between friend and inheritance, other than inheritance being an
entirely different class . They kinda do the same thing.

Steve: When, in a class definition, you make a function or class a friend of


the one you’re defining, the friend function or class has access to all members of
the class you are defining, disregarding their access specifiers; however, the friend
has no other relationship to the class being defined. That is, making class B a friend to
class A does not make a B object a substitute for an A object.

On the other hand, if B is ( publicly ) derived from A, then a B object can be used
wherever an A object can be used.
I think a picture might help here. Let’s start with a simplified version
of the StockItem and DatedStockItem classes , whose interface is shown in
Figure 9.7. I recommend that you print out the file that contains these
interfaces ( code\itema.h ) for reference as you go through this section of
the chapter.
Simplified interface for StockItem and DatedStockItem classes
FIGURE 9.7.
(code\itema.h)

class StockItem
{
public:
StockItem(std::string Name, short InStock, short MinimumStock); void

Reorder(std::ostream& os);
protected:
std::string m_Name; short
m_InStock;
short m_MinimumStock;
};

class DatedStockItem: public StockItem // deriving a new class


{
public:
DatedStockItem(std::string Name, short InStock, short MinimumStock, std::string
Expires);
void Reorder(std::ostream& os); protected:
static std::string Today();

protected:
std::string m_Expires;
};

Given these definitions, a StockItem object might look as depicted in


Figure 9.8.7
FIGURE 9.8. A simplified StockItem object

Variable name

m_Name

m_InStock

m_MinimumStock

Type and contents

And a DatedStockItem object might look as depicted in Figure 9.9.


As you can see, an object of the new DatedStockItem class contains a
StockItem as part of its data. In this case, that base class part accounts
for most of the data of a DatedStockItem ; all we’ve added is a data
member called m_Expires . In fact, a derived class object always
contains all of the variables and “regular” member functions in the
base class because the derived class object effectively has an object of
the base class embedded in it, as indicated in Figure 9.9. We can access
those member variables and functions that are part of the base class
part of our derived class object exactly as though they were defined in
the derived class , so long as their access specifiers are either public or
protected . Although the public and private access specifiers have been
part of our arsenal of tools for some time, this is our first encounter
with the protected access specifier. We’ll see shortly that the sole
purpose of the protected

7. I’m simplifying by leaving out the internal structure of a string, which affects the
actual layout of the object; this detail isn’t relevant here.
access specifier is to allow derived class member functions to use
member functions and variables of the base class part of an object of
that derived class , while protecting those member functions and
variables from use by unrelated classes .

FIGURE 9.9. A DatedStockItem object

Variable name

Type and contents StockItem

base class part (unnamed)


string

m_Expires

Susan had some interesting comments and questions about the notion of
the base class part of a derived class object.
Susan: When I look at Figure 9.9 I get the feeling that every DatedStockitem
object contains a StockItem object; is this the “base class part of the derived
class object”?

Steve: Yes.

Susan: The word “derived” is confusing; if a DatedStockItem is derived from a


StockItem one tends to take a linear approach as in a family tree, which isn’t quite
right. It would be better to think of DatedStockItem as a fruit like a plum, with the
pit being the StockItem, which is the core of the object.

Steve: Or maybe an onion, where all the layers are edible, rather than making the
distinction between the flesh of the fruit and an inedible pit.

Susan: But if so, every member function of the derived class could access every
member variable of the base class because “They occur as the base class part of
a derived class object”.

Steve: No, as we’ll see, even though private members are actually present in
the derived class object, they are not accessible to derived class functions. That’s
why we need protected .

Of course, as noted before, we don’t have to rely solely on the


facilities we inherit from our base class ; we can also add whatever
new functions or variables we need to provide the new functionality
of the new class . As you will see, we don’t want or need to add any
public member functions in the present case because our eventual goal is
to allow the application programmer to treat objects of the new
DatedStockItem class as equivalent to objects of the StockItem class . To reach
this goal, these two classes must have the same class interface, i.e., the
same public member functions.
A Note on Proper Object-oriented Design

By the way, for proper object-oriented design, it’s not enough just to
have the same names for the member functions in the derived class ;
they have to have the same meanings as well. That is, the user
shouldn’t be surprised by the behavior of a derived class function if he
knows how the base class function behaves. For example, if the
DatedStockItem Reorder function were to rearrange the items in the
inventory rather than generate a reorder report, as the StockItem version
does, the user would get very confused! The solution to this problem
is simple: make sure that your derived class functions do the “same
thing” as the corresponding base class functions, differing only in how
they do it.

Overriding a Base class Function

Now let’s get back to the implementation of the DatedStockItem class .


Instead of adding new public member functions, we will override the
base class version of Reorder by writing a new version of Reorder for
our DatedStockItem class . Our new function, which has the same signature
as that of the base class Reorder function, will use the new data member
m_Expires . Since StockItem::Reorder has been overridden by
DatedStockItem::Reorder , the latter function will be called whenever the
user’s program calls the Reorder function of a DatedStockItem .
Susan wasn’t sure about the meaning of “overriding” a base class
function rather than writing an entirely new one. That discussion led to
a more general one about the whole idea of inheritance.

Susan: Why is the term “override” used here? The derived class member
function is called for an object of the derived class , so I don’t see how it
“overrides” the base class member function with the same signature.
Steve: What would happen if we didn’t write the derived class function? The
base class function would be called. Therefore, the derived class function is
overriding the previously existing base class function.

Susan: How does this differ from hiding? Give me an example.

Steve: If we wrote a Reorder function with a different signature in the derived


class , such as DatedStockItem::Reorder(int) , that would no longer override the
base class function, StockItem::Reorder(ostream&) , but would hide the base
class function. This would mean that the base class function would not be called
even if the argument was an ostream . Instead, calling Reorder for a
DatedStockItem object with an ostream argument in that case would be an error.

Susan: Ok, I guess I see the difference. But why do you write a new version of
Reorder instead of adding a new public member function?

Steve: Precisely because our eventual goal is to allow the user to use stock items
with and without dates interchangeably. If StockItem and DatedStockItem had
different names for their reordering function, the user would have to call a different
function depending on which type the object really was, which would defeat our
attempt to make them interchangeable.

Susan: But if the two versions of Reorder were exactly the same, couldn’t you
just declare them public ?

Steve: If they were exactly the same, we wouldn’t need two functions in the first
place. Reordering works slightly differently for dated than for undated items, so we
need two different functions to do the “same” thing in two different ways.
Susan: Yes, but if the names were the same couldn’t they be used anywhere just
by making them public ? I thought this was the whole idea: not to have to rewrite
these things.

Steve: They are public . The point is that StockItem::Reorder and


DatedStockItem::Reorder accomplish the same result in different ways, so the user
of these classes should be able to just call Reorder and get the correct function
executed without having to worry about which one that is.

Susan: So is it the expiration date that makes it necessary to make a derived


class ?

Steve: Yes.

Susan: Is it impossible to extend our old class so it can handle objects both with
and without expiration dates rather than making a new class ?

Steve: No, it is not impossible to do that. But if we did that, we would be


defeating one of the main purposes of object-oriented programming: the ability to
divide a task up into pieces that can be handled by different classes that can be
maintained more easily. To accomplish that goal, we need two different classes to
handle these two different kinds of objects.

Susan: Why can’t we just add some new member functions and member
variables to a class instead of making a derived class ? Are you using inheritance
here just to make a point, or is it vital to achieve what we want to achieve?

Steve: If you added more functions, then StockItem would not be StockItem
as it is, and needs to be, to handle its original task. You could copy the code for
StockItem and then change the copy to handle expiration dates, but that would
cause serious maintenance problems later if (when) you had to change the code,
because you
would have to make the changes in both places. Avoiding such problems was one of
the main reasons that C++ was invented.

Susan: Okay, so that explains why we shouldn’t add more functions to StockItem
but not why we shouldn’t add any functions to DatedStockItem .

Steve: Because a DatedStockItem should act just like a StockItem


but won’t if we add new functions.8 Instead, we’ll write new versions of the ones
we already have, like Reorder .

Susan: I still don’t understand why you have to write a new version of Reorder .
A DatedStockItem is supposed to act just like a StockItem .

Steve: Yes, it is supposed to act “just like” a StockItem , as far as the user of
these classes is concerned. However, that means that DatedStockItem has to do
the “same” things as StockItem but do them in a different way; in particular,
reordering items is different when you have to send things back because their
expiration dates have passed. However, this difference in implementation isn’t
important to the application program, which can treat DatedStockItems just like
StockItems .

Before we get into the details of the Reorder function in the


DatedStockItem class , I should explain what I mean by “regular member
function”. A regular member function is one that is not in any of the
following categories:
1. constructor,
2. destructor,

8. Actually, this is not strictly true. We can add functions to a derived class without
affecting how it appears to users, so long as the functions that we add are either
p rivate or p rotected, so that they don’t change the p ublic interface of the class .
We’ll see some examples of this later.
3. assignment operator ( operator = ).
When we write a derived class (in this case DatedStockItem ), it
inherits only the regular member functions, not the constructor,
destructor, or operator = functions, from the base class (in this case
StockItem ). Instead, we have to write our own derived class versions of
these functions if we don’t want to rely on the compiler-generated
versions in the derived class .
It may not be obvious why we have to write our own versions of
these functions. It wasn’t to Susan:

Susan: So in this case our derived class DatedStockitem doesn’t inherit the
constructor, destructor, and assignment operator because it takes an object of the
Stockitem class and combines it with a new member variable m_Expires to make
an object of the derived DatedStockItem class . But if the only differences between
the two classes are in the implementation of the “regular member functions” then
the default constructor, after the inheritance of the base class , should have no
problem making a new derived class object because it won’t contain any new
member variables.

Steve: You’re right: that would be possible in such a case, but not in this case,
because we are adding a new member variable. However, the code in the base
class functions isn’t wasted in any event because the base class constructor,
destructor, and operator = functions are used automatically in the implementation
of the corresponding derived class functions.

Susan: But what if they are similar to the derived class functions that do the
same thing? Can’t you use them then?