Search for notes by fellow students, in your own course and all over the country.
Browse our notes for titles which look like what you need, you can preview any of the notes via a sample of the contents. After you're happy these are the notes you're after simply pop them into your shopping cart.
My Basket
SAN Switch£12.50
Placement C++ course£1.00
Linked list implementation of C/C+£1.50
Chimie £1.30
ENGLISH PLAY I HAVE A DREAM£75.00
Total£91.30
Or: Edit My Basket
Title: C PLUS PLUS PROGRAMING LANGUAGE
Description: this is notes for c ++ programing language in computer application
Description: this is notes for c ++ programing language in computer application
Document Preview
Extracts from the notes are below, to see the PDF you'll receive please use the links above
C++:
The Complete Reference
Third Edition
About the Author…
Herb Schildt is the leading authority on C and
C++ and a best-selling author whose books
have sold more than 1
...
His
acclaimed C and C++books include Teach
Yourself C, C++ from the Ground Up, Teach
Yourself C++, C++: The Complete Reference,
Borland C++: The Complete Reference, and C++
Programmer's Reference to name a few
...
Louis San Francisco
Auckland Bogotá Hamburg London Madrid
Mexico City Milan Montreal New Delhi Panama City
Paris São Paulo Singapore Sydney
Tokyo Toronto
abc
McGraw-Hill
Copyright © 1998 by McGraw-Hill Companies
...
Manufactured in the United States of America
...
0-07-213293-0
The material in this eBook also appears in the print version of this title: 0-07-882476-1
All trademarks are trademarks of their respective owners
...
Where such designations appear in this book, they have been printed with initial caps
...
For more information, please contact George Hoare, Special Sales, at george_hoare@mcgraw-hill
...
TERMS OF USE
This is a copyrighted work and The McGraw-Hill Companies, Inc
...
Use of this work is subject to these terms
...
You may
use the work for your own noncommercial and personal use; any other use of the work is strictly prohibited
...
THE WORK IS PROVIDED “AS IS”
...
McGraw-Hill and its licensors do not
warrant or guarantee that the functions contained in the work will meet your requirements or that its operation will be uninterrupted or
error free
...
McGraw-Hill has no responsibility for the content of any information
accessed through the work
...
This limitation of liability shall apply to any claim or cause whatsoever whether such claim
or cause arises in contract, tort or otherwise
...
1036/0072132930
Contents at a Glance
Part I The Foundation of C++: The C Subset
An Overview of C
...
Statements
...
Pointers
...
Structures, Unions, Enumerations, and UserDefined Types
...
9 File I/O
...
1
2
3
4
5
6
7
3
13
57
89
113
137
161
187
211
237
Part II C++
11 An Overview of C++
...
13 Arrays, Pointers, References and the Dynamic
Allocation Operators
...
15 Operator Overloading
...
Virtual Functions and Polymorphism
...
Exception Handling
...
C++ File I/O
...
Namespaces, Conversion Functions,and Other
Advanced Topics
...
16
17
18
19
20
21
22
23
419
445
461
489
511
541
569
593
625
Part III The Standard Function Library
25
26
27
28
29
30
31
The C-Based I/O Functions
...
The Mathematical Functions
...
The Dynamic Allocation Functions
...
The Wide-Character Functions
...
The STL Container Classes
...
STL Iterators, Allocators, and Function Objects
...
The Numeric Classes
...
783
807
835
857
877
893
921
Part V Applying C++
39 Integrating New Classes: A Custom String Class
...
Index
...
xxix
Part I
The Foundation of C++: The C Subset
2
An Overview of C
...
C Is a Middle-Level Language
...
C Is a Programmer's Language
...
The Library and Linking
...
Understanding the
...
CPP File Extensions
...
13
The Five Basic Data Types
...
Identifier Names
...
Where Variables Are Declared
...
Formal Parameters
...
Access Modifiers
...
volatile
...
extern
...
register Variables
...
Constants
...
String Constants
...
Operators
...
Type Conversion in Assignments
...
Arithmetic Operators
...
Relational and Logical Operators
...
The ? Operator
...
The Compile-Time Operator sizeof
...
The Dot (
...
The [ ] and ( ) Operators
...
Expressions
...
Type Conversion in Expressions
...
Spacing and Parentheses
...
3
15
16
17
17
17
21
21
23
23
24
25
25
27
29
31
31
32
33
33
34
34
35
36
37
37
39
42
47
47
49
50
51
51
52
53
53
53
54
55
56
Statements
...
58
Contents
Selection Statements
...
Nested ifs
...
The ? Alternative
...
switch
...
Iteration Statements
...
for Loop Variations
...
for Loops with No Bodies
...
The do-while Loop
...
Jump Statements
...
The goto Statement
...
The exit( ) Function
...
Expression Statements
...
59
59
60
62
63
66
67
70
70
70
72
76
77
77
79
81
82
82
83
83
85
86
88
88
5
Arrays and Null-Terminated Strings
...
Generating a Pointer to an Array
...
Null-Terminated Strings
...
Arrays of Strings
...
Indexing Pointers
...
Unsized Array Initializations
...
4
90
92
92
94
96
100
101
102
105
106
108
Pointers
...
Pointer Variables
...
Pointer Expressions
...
Pointer Arithmetic
...
Pointers and Arrays
...
Multiple Indirection
...
Pointers to Functions
...
Problems with Pointers
...
137
The General Form of a Function
...
Function Arguments
...
Creating a Call by Reference
...
argc and argv—Arguments to main( )
...
Returning from a Function
...
Returning Pointers
...
What Does main( ) Return?
...
Function Prototypes
...
Declaring Variable-Length Parameter Lists
...
Implementation Issues
...
Efficiency
...
161
Structures
...
Structure Assignments
...
Passing Structures to Functions
...
Passing Entire Structures to Functions
...
Declaring a Structure Pointer
...
Arrays and Structures Within Structures
...
Unions
...
Using sizeof to Ensure Portability
...
8
169
170
170
173
174
176
180
183
184
C-Style Console I/O
...
Reading and Writing Characters
...
Alternatives to getchar( )
...
Formatted Console I/O
...
Printing Characters
...
Displaying an Address
...
Format Modifiers
...
The Precision Specifier
...
Handling Other Data Types
...
scanf( )
...
Inputting Numbers
...
Reading Individual Characters Using scanf( )
...
Inputting an Address
...
Using a Scanset
...
Non-White-Space Characters in the Control String
...
Format Modifiers
...
188
189
190
190
192
195
195
196
196
198
198
199
199
200
201
202
202
203
203
203
205
205
205
206
206
206
207
208
208
208
209
xi
xii
C++: The Complete Reference
10
File I/O
...
Streams and Files
...
Text Streams
...
Files
...
The File Pointer
...
Closing a File
...
Reading a Character
...
Using feof( )
...
rewind( )
...
Erasing Files
...
fread( ) and fwrite( )
...
fseek( ) and Random-Access I/O
...
The Standard Streams
...
Using freopen( ) to Redirect the Standard Streams
...
237
The Preprocessor
...
Defining Function-like Macros
...
#include
...
#if, #else, #elif, and #endif
...
#undef
...
#line
...
The # and ## Preprocessor Operators
...
238
238
240
241
242
242
243
245
246
247
248
248
248
250
Contents
C-Style Comments
...
255
The Origins of C++
...
Encapsulation
...
Inheritance
...
A Sample C++ Program
...
Declaring Local Variables
...
The bool Data Type
...
Modern C++
...
Namespaces
...
Introducing C++ Classes
...
Operator Overloading
...
Constructors and Destructors
...
The General Form of a C++ Program
...
289
Classes
...
Unions and Classes Are Related
...
Friend Functions
...
Inline Functions
...
Parameterized Constructors
...
Static Class Members
...
290
293
295
297
298
302
303
306
307
309
310
310
xiii
xiv
C++: The Complete Reference
Static Member Functions
...
The Scope Resolution Operator
...
Local Classes
...
Returning Objects
...
13
315
317
319
320
320
321
323
324
Arrays, Pointers, References, and the Dynamic Allocation
Operators
...
Creating Initialized vs
...
Pointers to Objects
...
The this Pointer
...
Pointers to Class Members
...
Reference Parameters
...
Returning References
...
References to Derived Types
...
A Matter of Style
...
Initializing Allocated Memory
...
Allocating Objects
...
The Placement Forms of new and delete
...
361
Function Overloading
...
Overloading a Constructor to Gain Flexibility
...
Copy Constructors
...
The overload Anachronism
...
Default Arguments vs
...
Using Default Arguments Correctly
...
374
378
380
380
Operator Overloading
...
Creating Prefix and Postfix Forms of the
Increment and Decrement Operators
...
Operator Overloading Restrictions
...
Using a Friend to Overload ++ or – –
...
Overloading new and delete
...
Overloading the nothrow Version of new and delete
...
Overloading [ ]
...
Overloading –>
...
15
386
391
392
392
393
395
398
400
405
408
409
409
413
415
416
17
Inheritance
...
Inheritance and protected Members
...
Inheriting Multiple Base Classes
...
When Constructor and Destructor
Functions Are Executed
...
Granting Access
...
16
420
422
426
427
428
428
432
436
439
Virtual Functions and Polymorphism
...
Calling a Virtual Function Through a Base Class
Reference
...
Virtual Functions Are Hierarchical
...
Abstract Classes
...
Early vs
...
457
460
Templates
...
A Function with Two Generic Types
...
Overloading a Function Template
...
Generic Function Restrictions
...
A Generic Sort
...
Generic Classes
...
Applying Template Classes: A Generic Array Class
...
Using Default Arguments with Template Classes
...
The typename and export Keywords
...
18
462
465
465
468
468
469
470
471
472
474
478
479
481
483
485
486
487
20
Exception Handling
...
Catching Class Types
...
Handling Derived-Class Exceptions
...
Catching All Exceptions
...
Rethrowing an Exception
...
Setting the Terminate and Unexpected Handlers
...
The exception and bad_exception Classes
...
19
490
496
497
499
500
500
502
504
505
506
507
508
508
The C++ I/O System Basics
...
Modern C++ I/O
...
The C++ Stream Classes
...
Formatted I/O
...
Setting the Format Flags
...
An Overloaded Form of setf( )
...
Setting All Flags
...
Using Manipulators to Format I/O
...
Creating Your Own Inserters
...
Creating Your Own Manipulator Functions
...
541
...
Reading and Writing Text Files
...
Characters vs
...
put( ) and get( )
...
More get( ) Functions
...
Detecting EOF
...
peek( ) and putback( )
...
Random Access
...
I/O Status
...
21
542
542
545
547
547
548
550
553
553
555
557
558
558
559
563
563
565
Run-Time Type ID and the Casting Operators
...
A Simple Application of Run-Time Type ID
...
The Casting Operators
...
Replacing typeid with dynamic_cast
...
const_cast
...
reinterpret_cast
...
593
Namespaces
...
using
...
Some Namespace Options
...
Creating Conversion Functions
...
Volatile Member Functions
...
Using the asm Keyword
...
Array-Based I/O
...
Creating an Array-Based Output Stream
...
Input/Output Array-Based Streams
...
Using Binary I/O with Array-Based Streams
...
24
594
594
598
600
601
603
605
609
611
612
613
614
615
616
616
618
620
621
622
Introducing the Standard Template Library
...
Containers
...
Iterators
...
The Container Classes
...
Vectors
...
Inserting and Deleting Elements in a Vector
...
Lists
...
push_front( ) vs push_back( )
...
Merging One List with Another
...
Maps
...
Algorithms
...
Removing and Replacing Elements
...
Transforming a Sequence
...
Unary and Binary Function Objects
...
Creating a Function Object
...
The string Class
...
Strings Are Containers
...
Final Thoughts on the STL
...
695
clearerr
...
feof
...
fflush
...
fgetpos
...
fopen
...
fputc
...
fread
...
fscanf
...
fsetpos
...
fwrite
...
getchar
...
696
697
697
697
698
698
698
699
699
701
701
702
702
702
703
703
704
704
705
705
706
706
xix
xx
C++: The Complete Reference
perror
...
putc
...
puts
...
rename
...
scanf
...
setvbuf
...
sscanf
...
tmpnam
...
vprintf, vfprintf, and vsprintf
...
719
isalnum
...
iscntrl
...
isgraph
...
isprint
...
isspace
...
isxdigit
...
memcmp
...
memmove
...
strcat
...
strcmp
...
strcpy
...
strerror
...
720
720
721
721
721
721
722
722
722
723
723
723
723
724
724
725
725
725
726
726
727
727
727
727
Contents
strncat
...
strncpy
...
strrchr
...
strstr
...
strxfrm
...
toupper
...
733
acos
...
atan
...
ceil
...
cosh
...
fabs
...
fmod
...
ldexp
...
log10
...
pow
...
sinh
...
tan
...
27
728
728
729
729
729
730
730
730
731
731
731
734
734
735
735
735
736
736
736
737
737
737
737
738
738
738
739
739
739
740
740
740
741
Time, Date, and Localization Functions
...
clock
...
difftime
...
localeconv
...
mktime
...
strftime
...
30
The Dynamic Allocation Functions
...
free
...
realloc
...
757
abort
...
assert
...
atof
...
atol
...
div
...
getenv
...
ldiv
...
mblen
...
mbtowc
...
raise
...
setjmp
...
srand
...
strtol
...
system
...
wcstombs
...
758
758
759
759
759
760
760
760
761
762
762
762
763
763
763
764
764
764
765
766
766
766
767
767
768
768
769
769
770
770
Contents
The Wide-Character Functions
...
The Wide-Character I/O Functions
...
Wide-Character String Conversion Functions
...
Multibyte/Wide-Character Conversion Functions
...
783
The I/O Classes
...
The Format Flags and I/O Manipulators
...
The streamsize and streamoff Types
...
The pos_type and off_type Types
...
The iostate Type
...
The failure Class
...
The General-Purpose I/O Functions
...
clear
...
exceptions
...
fill
...
flush
...
gcount
...
getline
...
ignore
...
peek
...
784
786
787
789
789
789
789
789
790
790
790
790
791
791
791
791
792
792
792
793
793
793
794
794
795
796
796
796
797
798
xxiii
xxiv
C++: The Complete Reference
put
...
rdstate
...
readsome
...
setf
...
str
...
sync_with_stdio
...
unsetf
...
write
...
807
The Container Classes
...
deque
...
map
...
multiset
...
priority_queue
...
stack
...
33
808
810
812
815
818
820
823
825
826
827
829
830
The STL Algorithms
...
binary_search
...
copy_backward
...
count_if
...
equal_range
...
find
...
find_first_of
...
for_each
...
includes
...
iter_swap
...
lower_bound
...
max
...
merge
...
min_element
...
next_permutation
...
partial_sort
...
partition
...
prev_permutation
...
random_shuffle
...
replace, replace_copy, replace_if, and replace_copy_if
...
rotate and rotate_copy
...
search_n
...
set_intersection
...
set_union
...
sort_heap
...
stable_sort
...
swap_ranges
...
unique and unique_copy
...
840
840
840
841
841
841
842
842
842
843
843
843
844
844
844
845
845
845
846
846
846
847
847
847
848
848
849
849
850
850
850
851
851
852
852
852
853
853
853
854
854
854
855
xxv
xxvi
C++: The Complete Reference
STL Iterators, Allocators, and Function Objects
...
The Basic Iterator Types
...
The Predefined Iterators
...
Function Objects
...
Binders
...
Adaptors
...
35
858
858
859
860
868
868
869
870
871
872
875
878
890
The Numeric Classes
...
921
Exceptions
...
...
The pair Class
...
Other Classes of Interest
...
The valarray Class
...
The Helper Classes
...
accumulate
...
inner_product
...
37
The String Class
...
The char_traits Class
...
931
The StrType Class
...
I/O on Strings
...
Concatenation
...
The Relational Operators
...
The Entire StrType Class
...
Creating and Integrating New Types in General
...
40
934
935
937
938
941
943
944
945
954
957
957
An Object-Oriented Expression Parser
...
Parsing Expressions: The Problem
...
The Parser Class
...
A Simple Expression Parser
...
Adding Variables to the Parser
...
Building a Generic Parser
...
960
961
962
964
965
967
973
974
984
985
993
Index
995
...
Preface
This is the third edition of C++: The Complete Reference
...
Perhaps the most important
is that it is now a standardized language
...
This event marked the end of a very long, and at
times contentious, process
...
Near the
end, there was a world-wide, daily dialogue, conducted via e-mail, in which the pros
and cons of this or that issue were put forth, and finally resolved
...
We now have a standard for what is, without question, the most important
programming language in the world
...
Some are
relatively small
...
The net effect of the
additions was that the scope and range of the language were greatly expanded
...
Of course, the information contained in this edition
xxix
xxx
C++: The Complete Reference
reflects the International Standard for C++ defined by the ANSI/ISO committee,
including its new features
...
In fact, the length of the book has nearly doubled! The main reason for
this is that the third edition now includes comprehensive coverage of both the standard
function library and the standard class library
...
With the
standardization of C++ being complete, these topics can finally be added
...
Most is the result of features
that have been added to C++ since the previous edition was prepared
...
Also, some
fundamental changes to the way new and delete are implemented are described and
several new keywords are discussed
...
It's not the same old C++ that you learned years ago
...
The book is divided into these five parts:
s The C Subset — The foundation of C++
s The C++ language
s The Standard Function Library
s The Standard Class Library
s Sample C++ applications
Part One provides a comprehensive discussion of the C subset of C++
...
It is the C subset
that defines the bedrock features of C++, including such things as for loops and if
statements
...
Since many readers are already familiar with and proficient in C, discussing
the C subset separately in Part One prevents the knowledgeable C programmer from
having to "wade through" reams of information he or she already knows
...
Part Two discusses in detail the extensions and enhancements to C added by C++
...
Thus, Part Two covers those constructs that "make C++, C++
...
Part Five shows
two practical examples of applying C++ and object-oriented programming
...
It does assume, however, a reader able to create at least a simple program
...
Experienced C++ pros will
find the coverage of the many new features added by the International Standard
especially useful
...
C++ is
completely at home with Windows programming
...
Instead, they are console-based programs
...
The overhead required to create even a minimal Windows skeletal program
is 50 to 70 lines of code
...
Put simply, Windows is not an
appropriate environment in which to discuss the features of a programming language
...
Don't Forget: Code On The Web
Remember, the source code for all of the programs in this book is available
free-of-charge on the Web at http://www
...
com
...
xxxi
xxxii
C++: The Complete Reference
For Further Study
C++: The Complete Reference is your gateway into the "Herb Schildt" series of
programming books
...
If you want to learn more about C++, then you will find these books especially
helpful
...
Finally, if you want to program for Windows, we recommend
Windows 98 Programming From the Ground Up
Windows NT 4 From the Ground Up
MFC Programming From the Ground Up
When you need solid answers, fast, turn to Herbert Schildt,
the recognized authority on programming
...
Part One discusses the C-like features of C++
...
Part Two
describes those features specific to C++
...
As you may know, C++ was built upon the foundation of C
...
When C++
was invented, the C language was used as the starting point
...
However, the C-like aspects of
C++ were never abandoned, and the ANSI/ISO C standard is a base document for
the International Standard for C++
...
In a book such as this Complete Reference, dividing the C++ language into two
pieces—the C foundation and the C++-specific features—achieves three major benefits:
1
...
2
...
3
...
Understanding the dividing line between C and C++ is important because both are
widely used languages and it is very likely that you will be called upon to write or
maintain both C and C++ code
...
Many C++ programmers will, from time to time, be required to
write code that is limited to the "C subset
...
Knowing the
difference between C and C++ is simply part of being a top-notch professional C++
programmer
...
To
do this in a professional manner, a solid knowledge of C is required
...
Many readers already know C
...
Of course, throughout Part One, any minor differences between
C and C++ are noted
...
Although C++ contains the entire C language, not all of the features provided by
the C language are commonly used when writing "C++-style" programs
...
The preprocessor is another example
...
Discussing several of the "C-only" features in
Part One prevents them from cluttering up the remainder of the book
...
All the features described here
are part of C++ and available for your use
...
If you are particularly interested in C, you will find this
book helpful
...
Thus, the story of C++ begins with C
...
Since C++ is built upon C, this chapter provides
an important historical perspective on the roots of C++
...
T
The Origins of C
C was invented and first implemented by Dennis Ritchie on a DEC PDP-11 that used
the Unix operating system
...
BCPL was developed by Martin Richards, and it
influenced a language called B, which was invented by Ken Thompson
...
For many years, the de facto standard for C was the version supplied with the Unix
version 5 operating system
...
J
...
In the
summer of 1983 a committee was established to create an ANSI (American National
Standards Institute) standard that would define the C language once and for all
...
The ANSI C standard was finally adopted in December 1989, with the first copies
becoming available in early 1990
...
For
simplicity, this book will use the term Standard C when referring to the ANSI/ISO C
standard
...
Standard
C is the foundation upon which C++ is built
...
This does not mean that C is less
powerful, harder to use, or less developed than a high-level language such as BASIC
or Pascal, nor does it imply that C has the cumbersome nature of assembly language
(and its associated troubles)
...
Table 1-1 shows how C fits into the spectrum of computer
languages
...
Despite this fact
C code is also very portable
...
For example, if you can easily
convert a program written for DOS so that it runs under Windows, that program is
portable
...
C's Place in the World of Programming Languages
All high-level programming languages support the concept of data types
...
Common data types are integer, character, and real
...
C permits almost all type conversions
...
Unlike a high-level language, C performs almost no run-time error checking
...
These
types of checks are the responsibility of the programmer
...
As you may know from your other programming experience, a
high-level computer language will typically require that the type of an argument be
(more or less) exactly the same type as the parameter that will receive the argument
...
Instead, C allows an argument to be of any type
as long as it can be reasonably converted into the type of the parameter
...
5
6
C++: The Complete Reference
C is special in that it allows the direct manipulation of bits, bytes, words, and
pointers
...
Another important aspect of C is that it has only 32 keywords (27 from the
Kernighan and Ritchie de facto standard, and five added by the ANSI standardization
committee), which are the commands that make up the C language
...
As a comparison, consider
that most versions of BASIC have well over 100 keywords!
C Is a Structured Language
In your previous programming experience, you may have heard the term blockstructured applied to a computer language
...
It has many similarities to other structured languages, such
as ALGOL, Pascal, and Modula-2
...
Since C does not allow the creation of functions
within functions, it cannot formally be called block-structured
...
This is the ability of a language to section off and hide from the rest of the
program all information and instructions necessary to perform a specific task
...
By using local variables, you can write subroutines so that the
events that occur within them cause no side effects in other parts of the program
...
If you develop
compartmentalized functions, you only need to know what a function does, not how it
does it
...
(Anyone who has programmed in standard BASIC is well aware of this
problem
...
Specifically, in
C++, one part of your program may tightly control which other parts of your
program are allowed access
...
It
directly supports several loop constructs, such as while, do-while, and for
...
A structured language allows you to place statements
anywhere on a line and does not require a strict field concept (as some older
FORTRANs do)
...
In fact, a mark of an old computer
language is that it is nonstructured
...
Note
New versions of many older languages have attempted to add structured elements
...
However, the shortcomings of these languages can never be
fully mitigated because they were not designed with structured features from the
beginning
...
In
C, functions are the building blocks in which all program activity occurs
...
After you have created a function, you can rely on it to
work properly in various situations without creating side effects in other parts of
the program
...
Another way to structure and compartmentalize code in C is through the use of
code blocks
...
In C, you create a code block by placing a sequence of statements
between opening and closing curly braces
...
\n");
scanf("%d", &x);
}
7
8
C++: The Complete Reference
the two statements after the if and between the curly braces are both executed if x is
less than 10
...
They are a logical unit: One of the statements cannot execute without the other
executing also
...
Moreover, they help the programmer better conceptualize
the true nature of the algorithm being implemented
...
Consider
the classic examples of nonprogrammer languages, COBOL and BASIC
...
Rather,
COBOL was designed, in part, to enable nonprogrammers to read and presumably
(however unlikely) to understand the program
...
In contrast, C was created, influenced, and field-tested by working programmers
...
By using C, you can nearly achieve the efficiency of assembly code
combined with the structure of ALGOL or Modula-2
...
The fact that you can often use C in place of assembly language is a major factor in
its popularity among programmers
...
Each assembly-language
operation maps into a single task for the computer to perform
...
Furthermore, since assembly language is unstructured, the final
program tends to be spaghetti code—a tangled mess of jumps, calls, and indexes
...
Perhaps more important, assembly-language routines are not portable
between machines with different central processing units (CPUs)
...
A systems program forms a portion
of the operating system of the computer or its support utilities
...
Of course, C++ has carried on this tradition
...
Such has not been the case
...
For example, applications
such as embedded systems are still typically programmed in C
...
While C's greatest legacy is as the foundation for C++, it will continue to
be a vibrant, widely used language for many years to come
...
Of these, 27 were defined by the original version of C
...
All are, of course, part of the C++ language
...
The 32 Keywords Defined by Standard C
9
10
C++: The Complete Reference
In addition, many compilers have added several keywords that better exploit their
operating environment
...
Here is a list of some commonly used extended
keywords:
asm
_cs
_ds
_es
_ss
cdecl
far
huge
interrupt
near
pascal
Your compiler may also support other extensions that help it take better advantage
of its specific environment
...
Also, uppercase and lowercase are
different: else is a keyword; ELSE is not
...
All C programs consist of one or more functions
...
In well-written C code, main( ) contains what is, in essence, an outline of what
the program does
...
Although main( ) is not
a keyword, treat it as if it were
...
The general form of a C program is illustrated in Figure 1-1, where f1( ) through
fN( ) represent user-defined functions
...
However, this is quite
rare because neither C nor C++ provides any keywords that perform such things as
input/output (I/O) operations, high-level mathematical computations, or character
handling
...
All C++ compilers come with a standard library of functions that perform most
commonly needed tasks
...
However, your compiler will probably contain many other
functions
...
The C++ standard library can be divided into two halves: the standard function
library and the class library
...
C++ supports the entire function library defined by Standard C
...
Chapter 1:
An Overview of C
global declarations
return-type main(parameter list)
{
statement sequence
}
return-type f1(parameter list)
{
statement sequence
}
return-type f2(parameter list)
{
statement sequence
}
...
...
The general form of a C program
...
The class library provides object-oriented routines that your programs may use
...
However, both the class library and the STL are
discussed later in this book
...
The implementors of your compiler have already written most of the generalpurpose functions that you will use
...
Later, the linker combines the code you
11
12
C++: The Complete Reference
wrote with the object code already found in the standard library
...
Some compilers have their own linker, while others use the standard linker
supplied by the operating system
...
This means that the memory
addresses for the various machine-code instructions have not been absolutely
defined—only offset information has been kept
...
There are several technical manuals and books that explain this
process in more detail
...
Many of the functions that you will need as you write programs are in the standard
library
...
If you write a function that you
will use again and again, you can place it into a library, too
...
However, as a
program's length grows, so does its compile time (and long compile times make for
short tempers)
...
Once you have compiled all files, they are linked,
along with any library routines, to form the complete object code
...
On all but the simplest projects, this saves a substantial
amount of time
...
Understanding the
...
CPP File Extensions
The programs in Part One of this book are, of course, valid C++ programs and can be
compiled using any modern C++ compiler
...
Thus, if you are called upon to write C programs, the
ones shown in Part One qualify as examples
...
C, and C++ programs use the extension
...
A C++ compiler uses the file
extension to determine what type of program it is compiling
...
C extension is a C program and that
any file using
...
Unless explicitly noted otherwise, you may use
either extension for the programs in Part One
...
CPP
...
C extension)
...
C++
Chapter 2
Expressions
13
14
C++: The Complete Reference
his chapter examines the most fundamental element of the C (as well as the C++)
language: the expression
...
Expressions are formed from these atomic elements: data and operators
...
Like most other computer languages,
C/C++ supports a number of different types of data
...
T
The Five Basic Data Types
There are five atomic data types in C: character, integer, floating-point, double
floating-point, and valueless (char, int, float, double, and void, respectively)
...
The size and range
of these data types may vary between processor types and compilers
...
The size of an integer is usually the same as the word length
of the execution environment of the program
...
1, an integer is 16 bits
...
However, you cannot make assumptions about
the size of an integer if you want your programs to be portable to the widest range of
environments
...
Note
To the five basic data types defined by C, C++ adds two more: bool and wchar_t
...
The exact format of floating-point values will depend upon how they are
implemented
...
Values of type char are generally used to hold values defined by the
ASCII character set
...
The range of float and double will depend upon the method used to represent
the floating-point numbers
...
Standard
C specifies that the minimum range for a floating-point value is 1E−37 to 1E+37
...
Note
Standard C++ does not specify a minimum size or range for the basic types
...
For example, Standard
C++ states that an int will “have the natural size suggested by the architecture of
the execution environment
...
Each C++ compiler specifies the size
and range of the basic types in the header
...
All Data Types Defined by the ANSI/ISO C Standard
The type void either explicitly declares a function as returning no value or creates
generic pointers
...
Modifying the Basic Types
Except for type void, the basic data types may have various modifiers preceding them
...
The list of modifiers is shown here:
signed
unsigned
long
short
15
16
C++: The Complete Reference
You can apply the modifiers signed, short, long, and unsigned to integer base
types
...
You may also apply long to
double
...
(These values also apply to a typical C++
implementation
...
For example, on
computers that use two's complement arithmetic (which is nearly all), an integer will
have a range of at least 32,767 to –32,768
...
The most important use of signed is to modify
char in implementations in which char is unsigned by default
...
If you specify a signed integer, the compiler
generates code that assumes that the high-order bit of an integer is to be used as a sign
flag
...
In general, negative numbers are represented using the two's complement approach,
which reverses all bits in the number (except the sign flag), adds 1 to this number, and
sets the sign flag to 1
...
For example, here is 32,767:
01111111 11111111
If the high-order bit were set to 1, the number would be interpreted as −1
...
Identifier Names
In C/C++, the names of variables, functions, labels, and various other user-defined
objects are called identifiers
...
The first character must be a letter or an underscore, and subsequent characters must
be either letters, digits, or underscores
...
balance
Chapter 2:
Expressions
In C, identifiers may be of any length
...
If the identifier will be involved in an external link process, then at
least the first six characters will be significant
...
If the
identifier is not used in an external link process, then at least the first 31 characters
will be significant
...
In C++, there is no limit to the length of an
identifier, and at least the first 1,024 characters are significant
...
In an identifier, upper- and lowercase are treated as distinct
...
An identifier cannot be the same as a C or C++ keyword, and should not have the
same name as functions that are in the C or C++ library
...
All variables must be declared before they
can be used
...
Here are some declarations:
int i,j,l;
short int si;
unsigned int ui;
double balance, profit, loss;
Remember, in C/C++ the name of a variable has nothing to do with its type
...
These are local variables, formal
parameters, and global variables
...
In some C/C++
literature, these variables are referred to as automatic variables
...
Local variables may be referenced only by statements
that are inside the block in which the variables are declared
...
Remember, a block of code
begins with an opening curly brace and terminates with a closing curly brace
...
That is, a local variable is created upon entry into its block and destroyed
upon exit
...
For example, consider the following two functions:
void func1(void)
{
int x;
x = 10;
}
void func2(void)
{
int x;
x = -199;
}
The integer variable x is declared twice, once in func1( ) and once in func2( )
...
This is because each x is
only known to the code within the same block as the variable declaration
...
However, since all nonglobal variables are, by default, assumed to be auto,
this keyword is virtually never used
...
(It has been said that auto was included in C to provide for source-level compatibility
with its predecessor B
...
)
For reasons of convenience and tradition, most programmers declare all the
variables used by a function immediately after the function's opening curly brace
and before any other statements
...
The block defined by a function is simply a special case
...
*/
}
}
Here, the local variable s is created upon entry into the if code block and destroyed
upon exit
...
One advantage of declaring a local variable within a conditional block is that
memory for the variable will only be allocated if needed
...
You
might need to worry about this when producing code for dedicated controllers (like a
garage door opener that responds to a digital security code) in which RAM is in short
supply, for example
...
Since the variable does not exist outside the block in which it
is declared, it cannot be accidentally altered
...
In C, you must declare all local variables at the start of the block in
which they are defined, prior to any "action" statements
...
/* This function is in error if compiled as
a C program, but perfectly acceptable if
compiled as a C++ program
...
(The topic of C++ variable declaration is discussed in
depth in Part Two
...
This is
especially important to remember when calling a function
...
This means that
local variables cannot retain their values between calls
...
)
Unless otherwise specified, local variables are stored on the stack
...
You can initialize a local variable to some known value
...
For example,
the following program prints the number 10 ten times:
#include
These variables are called the formal parameters of the function
...
As shown in the following
program fragment, their declarations occur after the function name and inside
parentheses:
/* Return 1 if c is part of string s; 0 otherwise */
int is_in(char *s, char c)
{
while(*s)
if(*s==c) return 1;
else s++;
return 0;
}
The function is_in( ) has two parameters: s and c
...
You must specify the type of the formal parameters by declaring them as just shown
...
Keep in mind that,
as local variables, they are also dynamic and are destroyed upon exit from the function
...
Even though these variables receive the value of
the arguments passed to the function, you can use them like any other local variable
...
Also, they will hold their value throughout the program's
execution
...
Any
expression may access them, regardless of what block of code that expression is in
...
Although its declaration occurs before the main( ) function, you could have
placed it anywhere before its first use as long as it was not in a function
...
#include
');
}
Look closely at this program
...
func2( ), however, has declared a local
variable called count
...
If a global variable and a local variable have the same name, all
references to that variable name inside the code block in which the local variable is
declared will refer to that local variable and have no effect on the global variable
...
Storage for global variables is in a fixed region of memory set aside for this purpose
by the compiler
...
You should avoid using unnecessary global variables, however
...
In addition, using a global where a local variable would do makes a function
less general because it relies on something that must be defined outside itself
...
A major problem in developing large programs is the
accidental changing of a variable's value because it was used elsewhere in the program
...
Access Modifiers
There are two modifiers that control how variables may be accessed or modified
...
They must precede the type modifiers and the type
names that they qualify
...
const
Variables of type const may not be changed by your program
...
) The compiler is free to place variables of this type into
read-only memory (ROM)
...
However, you can use the variable a in other types of expressions
...
The const qualifier can be used to protect the objects pointed to by the arguments
to a function from being modified by that function
...
However, if the pointer is specified as const in the parameter declaration, the function
code won't be able to modify what it points to
...
That is,
the string "this is a test" will be printed as "this-is-a-test"
...
#include
For example, if you had coded sp_to_dash( ) as follows, you would
receive a compile-time error:
/* This is wrong
...
For example, the strlen( ) function has this prototype:
size_t strlen(const char *str);
Specifying str as const ensures that strlen( ) will not modify the string pointed to by str
...
You can also use const to verify that your program does not modify a variable
...
For example, a hardware device may set its value
...
volatile
The modifier volatile tells the compiler that a variable's value may be changed in ways
not explicitly specified by the program
...
In this situation, the contents of the variable are altered without any explicit
assignment statements in the program
...
Also, some compilers
change the order of evaluation of an expression during the compilation process
...
You can use const and volatile together
...
The general
form of a declaration that uses one is shown here
...
Note
C++ adds another storage-class specifier called mutable, which is described in
Part Two
...
Although C technically allows you to define a
global variable more than once, it is not good practice (and may cause problems when
linking)
...
How,
then, do you inform all the files in your program about the global variables used by
the program?
The solution to the problem is found in the distinction between the declaration
and the definition of a variable
...
25
26
C++: The Complete Reference
File One
File Two
int x, y;
extern int x, y;
char ch;
extern char ch;
int main(void)
void func22(void)
{
{
/*
...
Using global variables in separately compiled modules
A definition causes storage to be allocated for the variable
...
However, by preceding a variable name with the
extern specifier, you can declare a variable without defining it
...
In File Two, the global variable list was copied from File One and the extern
specifier was added to the declarations
...
In other words,
extern lets the compiler know what the types and names are for these global variables
without actually creating storage for them again
...
The extern keyword has this general form:
extern var-list;
There is another, optional use of extern that you may occasionally see
...
...
}
Although extern variable declarations as shown in this example are allowed, they are
not necessary
...
If it does not, the compiler then checks the global variables
...
In C++, the extern specifier has another use, which is described in Part Two
...
Unlike global
variables, they are not known outside their function or file, but they maintain their
values between calls
...
static has different
effects upon local variables and global variables
...
The key difference
between a static local variable and a global variable is that the static local variable
remains known only to the block in which it is declared
...
static local variables are very important to the creation of stand-alone functions
because several types of routines must preserve a value between calls
...
An example of a function that benefits from a static local variable is a numberseries generator that produces a new value based on the previous one
...
However, each time the function is used in a
program, you would have to declare that global variable and make sure that it did not
conflict with any other global variables already in place
...
This means that
each call to series( ) can produce a new member in the series based on the preceding
number without declaring that variable globally
...
This value is assigned
only once, at program start-up—not each time the block of code is entered, as with
normal local variables
...
While this
is acceptable for some applications, most series generators need to let the user specify
the starting point
...
However, not defining series_num
as global was the point of making it static
...
static Global Variables
Applying the specifier static to a global variable instructs the compiler to create a
global variable that is known only to the file in which you declared it
...
For the few situations
where a local static cannot do the job, you can create a small file that contains only the
functions that need the global static variable, separately compile that file, and use it
without fear of side effects
...
The entire file containing series( ), series_start( ), and series_num
is shown here:
Chapter 2:
Expressions
/* This must all be in one file - preferably by itself
...
After that, calls to series( ) generate the next element in the series
...
If you place the series( ) and series_start( ) functions in a
library, you can use the functions but cannot reference the variable series_num, which
is hidden from the rest of the code in your program
...
In
essence, the static modifier permits variables that are known only to the functions that
need them, without unwanted side effects
...
This can be a tremendous advantage when you are trying to manage a very large and
complex program
...
This means
that it is not recommended for new code
...
register Variables
The register storage specifier originally applied only to variables of type int, char, or
pointer types
...
Originally, the register specifier requested that the compiler keep the value of a
variable in a register of the CPU rather than in memory, where normal variables are
29
30
C++: The Complete Reference
stored
...
Today, the definition of register has been greatly expanded and it now may be
applied to any type of variable
...
" (Standard C++ states that register is a "hint to the implementation
that the object so declared will be heavily used
...
Larger objects like arrays obviously cannot be
stored in a register, but they may still receive preferential treatment by the compiler
...
In fact, it is technically permissible for a compiler to ignore
the register specifier altogether and treat variables modified by it as if they weren't,
but this is seldom done in practice
...
Global register variables are not allowed
...
This function computes the result of M for integers:
int int_pwr(register int m,
{
register int temp;
register int e)
temp = 1;
for(; e; e--) temp = temp * m;
return temp;
}
In this example, e, m, and temp are declared as register variables because they
are all used within the loop
...
Generally, register variables are used
where they will do the most good, which are often places where many references will
be made to the same variable
...
The number of register variables optimized for speed allowed within any one code
block is determined by both the environment and the specific implementation of
C/C++
...
(This ensures portability of code across a broad line of
processors
...
Because environments vary widely, consult your compiler's user
manual to determine if you can apply any other types of optimization options
...
This makes sense because a register variable might be
stored in a register of the CPU, which is not usually addressable
...
However, taking the address of a register variable in C++ may
prevent it from being fully optimized
...
Thus, you should probably not count on substantial speed
improvements for other variable types
...
The general form of initialization is
type variable_name = value;
Some examples are
char ch = 'a';
int first = 0;
float balance = 123
...
Local
variables (excluding static local variables) are initialized each time the block in which
they are declared is entered
...
Uninitialized global and static local
variables are automatically set to zero
...
Constants can be of any
of the basic data types
...
Constants are also called literals
...
For example 'a' and '%' are
both character constants
...
To specify a wide
character constant, precede the character with an L
...
The type of wide
characters is wchar_t
...
In C++, wchar_t is built in
...
For
example, 10 and –100 are integer constants
...
For example, 11
...
C/C++ also allows you to use scientific notation for
floating-point numbers
...
There are also several
variations of the basic types that you can generate using the type modifiers
...
Therefore, assuming 16-bit integers, 10 is int by default, but 103,000 is a long
...
The only exception to the smallest type rule are floating-point
constants, which are assumed to be doubles
...
However,
you can specify precisely the type of numeric constant you want by using a suffix
...
If you follow it with an L, the number becomes a long double
...
Here are some examples:
Data type
Constant examples
int
1 123 21000 −234
long int
35000L −34L
unsigned int
10000U 987U 40000U
float
123
...
34e−3F
double
123
...
0 −0
...
2L
Hexadecimal and Octal Constants
It is sometimes easier to use a number system based on 8 or 16 rather than 10 (our
standard decimal system)
...
In octal, the number 10 is the same as 8 in decimal
...
For example, the
hexadecimal number 10 is 16 in decimal
...
A hexadecimal constant must consist of a 0x followed by the
constant in hexadecimal form
...
Here are some
examples:
int hex = 0x80;
int oct = 012;
/* 128 in decimal */
/* 10 in decimal */
String Constants
C/C++ supports one other type of constant: the string
...
For example, "this is a test" is a string
...
Although C allows you to define string constants, it does not formally have a string
data type
...
)
You must not confuse strings with characters
...
However, "a" is a string containing only one letter
...
A
few, however, such as the carriage return, are impossible to enter into a string from the
keyboard
...
These are also referred to as escape sequences
...
For example, the following program outputs a new line and a tab and then prints
the string This is a test
...
h>
int main(void)
{
printf("\n\tThis is a test
...
Backslash Codes
Operators
C/C++ is very rich in built-in operators
...
There are four main classes
of operators: arithmetic, relational, logical, and bitwise
...
The Assignment Operator
You can use the assignment operator within any valid expression
...
The general form of the
assignment operator is
variable_name = expression;
Chapter 2:
Expressions
where an expression may be as simple as a single constant or as complex as you
require
...
The target, or left part, of the assignment
must be a variable or a pointer, not a function or a constant
...
Simply put, an lvalue is any object that can occur
on the left side of an assignment statement
...
" The term rvalue refers to expressions on the right side of an assignment and
simply means the value of an expression
...
In an assignment statement, the type conversion rule is easy: The value of
the right side (expression side) of the assignment is converted to the type of the left
side (target variable), as illustrated here:
int x;
char ch;
float f;
void func(void)
{
ch = x;
/*
x = f;
/*
f = ch;
/*
f = x;
/*
}
line
line
line
line
1
2
3
4
*/
*/
*/
*/
In line 1, the left high-order bits of the integer variable x are lopped off, leaving ch with
the lower 8 bits
...
Otherwise, the value of ch would reflect only the lower-order bits of x
...
In line 3, f will convert the 8-bit integer value stored
in ch to the same value in the floating-point format
...
When converting from integers to characters and long integers to integers, the
appropriate amount of high-order bits will be removed
...
For 32-bit environments, 24
bits will be lost when converting from an integer to a character and 16 bits will be lost
when converting from an integer to a short integer
...
Remember that the
conversion of an int to a float, or a float to a double, and so on, does not add any
35
36
C++: The Complete Reference
precision or accuracy
...
In addition, some compilers always treat a char variable as
positive, no matter what value it has, when converting it to an int or float
...
Generally speaking, you should use char variables for characters, and use
ints, short ints, or signed chars when needed to avoid possible portability problems
...
For example, to convert from double to int, first convert from double
to float and then from float to int
...
For example, this program fragment assigns x, y,
and z the value 0:
x = y = z = 0;
Target Type
Expression Type
Possible Info Loss
signed char
char
If value > 127, target is negative
char
short int
High-order 8 bits
char
int (16 bits)
High-order 8 bits
char
int (32 bits)
High-order 24 bits
char
long int
High-order 24 bits
short int
int (16 bits)
None
short int
int (32 bits)
High-order 16 bits
int (16 bits)
long int
High-order 16 bits
int (32 bits)
long int
None
int
float
Fractional part and possibly more
float
double
Precision, result rounded
double
long double
Precision, result rounded
Table 2-3
...
Arithmetic Operators
Table 2-4 lists C/C++'s arithmetic operators
...
You can apply them to almost any built-in data
type
...
For example, 5/2 will equal 2 in integer division
...
However, you cannot use it on
floating-point types
...
The unary minus multiplies its operand by –1
...
Increment and Decrement
C/C++ includes two useful operators not generally found in other computer
languages
...
The operator
++ adds 1 to its operand, and − − subtracts one
...
For example,
x = x+1;
can be written
++x;
or
x++;
There is, however, a difference between the prefix and postfix forms when you use
these operators in an expression
...
If the operator follows its operand,
Operator
Action
−
Subtraction, also unary minus
+
Addition
*
Multiplication
/
Division
%
Modulus
––
Decrement
++
Increment
Table 2-4
...
For
instance,
x = 10;
y = ++x;
sets y to 11
...
Either way, x is set to 11; the difference is in when it happens
...
For this reason, you should use the increment and decrement
operators when you can
...
Of course, you can use parentheses to alter the order of evaluation
...
Parentheses
force an operation, or set of operations, to have a higher level of precedence
...
In the term logical operator, logical refers to the ways these
relationships can be connected
...
The idea of true and false underlies the concepts of relational and logical operators
...
False is zero
...
C++ fully supports the zero/non-zero concept of true and false
...
In C++, a 0 value
is automatically converted into false, and a non-zero value is automatically converted
into true
...
In C++,
39
40
C++: The Complete Reference
the outcome of a relational or logical operation is true or false
...
Table 2-5 shows the relational and logical operators
...
p
q
p && q
p || q
!p
0
0
0
0
1
0
1
0
1
1
1
1
1
1
0
1
0
0
1
0
Both the relational and logical operators are lower in precedence than the
arithmetic operators
...
Of course, the result is false
...
Relational and Logical Operators
Chapter 2:
Expressions
In this case, the result is true
...
The outcome of an XOR operation is true if and only if one operand (but not both) is
true
...
h>
int xor(int a, int b);
int main(void)
{
printf("%d",
printf("%d",
printf("%d",
printf("%d",
xor(1,
xor(1,
xor(0,
xor(0,
0));
1));
1));
0));
return 0;
}
/* Perform a logical XOR operation using the
two arguments
...
For example,
41
42
C++: The Complete Reference
!0 && 0 || 0
is false
...
Therefore, the following program fragment is not only correct, but will print
the number 1
...
Since C was designed to take the place of assembly language for most
programming tasks, it needed to be able to support many operations that can be done
in assembler, including operations on bits
...
You cannot use bitwise operations on float, double, long double,
void, bool, or other, more complex types
...
These operations are applied to the individual bits of the
operands
...
Bitwise Operators
Chapter 2:
Operator
Action
>>
Shift right
<<
Expressions
Shift left
Table 2-6
...
The exclusive
OR has the truth table shown here:
p
q
p ^q
0
0
0
1
0
1
1
1
0
0
1
1
As the table indicates, the outcome of an XOR is true only if exactly one of the
operands is true; otherwise, it is false
...
(The parity bit confirms that the
rest of the bits in the byte are unchanged
...
)
Think of the bitwise AND as a way to clear a bit
...
For example, the
following function reads a character from the modem port and resets the parity bit to 0:
char get_char_from_modem(void)
{
char ch;
ch = read_modem(); /* get a character from the
modem port */
return(ch & 127);
}
43
44
C++: The Complete Reference
Parity is often indicated by the eighth bit, which is set to 0 by ANDing it with a
byte that has bits 1 through 7 set to 1 and bit 8 set to 0
...
The net
result is that the eighth bit of ch is set to 0
...
Any bit that is set
to 1 in either operand causes the corresponding bit in the outcome to be set to 1
...
For example, 127 ^120 is
01111111
01111000
^___________
00000111
127 in binary
120 in binary
bitwise XOR
result
Remember, relational and logical operators always produce a result that is either
true or false, whereas the similar bitwise operations may produce any arbitrary value
in accordance with the specific operation
...
The bit-shift operators, >> and <<, move all bits in a variable to the right or left as
specified
...
(In the case of a
signed, negative integer, a right shift will cause a 1 to be brought in so that the sign bit
is preserved
...
That is, the bits shifted off one end do
not come back around to the other
...
Bit-shift operations can be very useful when you are decoding input from an
external device, like a D/A converter, and reading status information
...
A shift right effectively divides
a number by 2 and a shift left multiplies it by 2, as shown in Table 2-7
...
*/
#include
Notice that information has been lost after x<<2 because
a bit was shifted off the end
...
Notice that subsequent divisions do not bring back any
lost bits
...
Multiplication and Division with Shift Operators
The one's complement operator, ~, reverses the state of each bit in its operand
...
The bitwise operators are often used in cipher routines
...
One of the simplest
methods is to complement each byte by using the one's complement to reverse each bit
in the byte, as is shown here:
Original byte
After 1st complement
After 2nd complement
00101100
11010011
00101100
Same
Notice that a sequence of two complements in a row always produces the original
number
...
The
second complement decodes the byte to its original value
...
/* A simple cipher function
...
The ternary operator ? takes the general form
Exp1 ? Exp2 : Exp3;
where Exp1, Exp2, and Exp3 are expressions
...
The ? operator works like this: Exp1 is evaluated
...
If Exp1 is false, Exp3 is evaluated and its
value becomes the value of the expression
...
If x had been less than 9, y would have received the value
200
...
The & and * Pointer Operators
A pointer is the memory address of some object
...
Knowing a
variable's address can be of great help in certain types of routines
...
They can provide a fast means of referencing
array elements
...
Lastly,
they support linked lists and other dynamic data structures
...
However, this chapter briefly covers the two operators that
are used to manipulate pointers
...
(Remember, a unary operator only requires one operand
...
This address is the computer's
internal location of the variable
...
You can
think of & as meaning "the address of
...
"
To better understand this assignment, assume that the variable count is at memory
location 2000
...
Then, after the previous
assignment, m will have the value 2000
...
The * is a unary
operator that returns the value of the variable located at the address that follows it
...
Now q has the value 100 because 100 is stored at
location 2000, the memory address that was stored in m
...
" In this case, you could read the statement as "q receives the value at
address m
...
These operators have no relationship to each other
...
Variables that will hold memory addresses (i
...
, pointers), must be declared by
putting * in front of the variable name
...
For example, to declare ch as a pointer to a character, write
char *ch;
Here, ch is not a character but a pointer to a character—there is a big difference
...
However, the pointer variable itself is a variable that holds the address to an
object of the base type
...
However, remember that a pointer should only point to data that is of that pointer's
base type
...
For example,
int x, *y, count;
declares x and count as integer types and y as a pointer to an integer type
...
As expected, this program displays the value 10 on the screen
...
h>
int main(void)
{
int target, source;
int *m;
source = 10;
m = &source;
target = *m;
printf("%d", target);
return 0;
}
The Compile-Time Operator sizeof
sizeof is a unary compile-time operator that returns the length, in bytes, of the variable
or parenthesized type-specifier that it precedes
...
Remember, to compute the size of a type, you must enclose the type name in
parentheses
...
49
50
C++: The Complete Reference
C/C++ defines (using typedef) a special type called size_t, which corresponds
loosely to an unsigned integer
...
For all practical purposes, however, you can think of it (and use it) as if it were
an unsigned integer value
...
For example, imagine a database program that needs to store six
integer values per record
...
This being the case, you could use the following routine to write a
record to a disk file:
/* Write 6 integers to a disk file
...
One final point: sizeof is evaluated at compile time, and the value it produces is
treated as a constant within your program
...
The left side of the comma
operator is always evaluated as void
...
For example,
x = (y=3, y+1);
first assigns y the value 3 and then assigns x the value 4
...
Essentially, the comma causes a sequence of operations
...
The comma operator has somewhat the same meaning as the word "and" in normal
English as used in the phrase "do this and this and this
...
) and Arrow (−>) Operators
In C, the
...
Structures and unions are compound (also called aggregate) data types that
may be referenced under a single name (see Chapter 7)
...
The dot operator is used when working with a structure or union directly
...
For example,
given the fragment
struct employee
{
char name[80];
int age;
float wage;
} emp;
struct employee *p = &emp; /* address of emp into p */
you would write the following code to assign the value 123
...
wage = 123
...
23;
The [ ] and ( ) Operators
Parentheses are operators that increase the precedence of the operations inside them
...
Given an array, the expression within square brackets provides an index into that
array
...
h>
char s[80];
int main(void)
{
s[3] = 'X';
printf("%c", s[3]);
51
52
C++: The Complete Reference
return 0;
}
first assigns the value 'X' to the fourth element (remember, all arrays begin at 0) of
array s, and then prints that element
...
Note that all operators,
except the unary operators and ?, associate from left to right
...
Highest
( ) [ ] −>
...
Lowest
Table 2-8
...
Expressions
Operators, constants, and variables are the constituents of expressions
...
Because most expressions tend to
follow the general rules of algebra, they are often taken for granted
...
Order of Evaluation
Neither C nor C++ specifies the order in which the subexpressions of an expression are
evaluated
...
However, it also means that your code should never rely upon the order
in which subexpressions are evaluated
...
Type Conversion in Expressions
When constants and variables of different types are mixed in an expression, they are
all converted to the same type
...
First, all char and short int values
are automatically elevated to int
...
) Once this
step has been completed, all other conversions are done operation by operation, as
described in the following type conversion algorithm:
IF an operand is a long double
THEN the second is converted to long double
ELSE IF an operand is a double
THEN the second is converted to double
ELSE IF an operand is a float
THEN the second is converted to float
ELSE IF an operand is an unsigned long
THEN the second is converted to unsigned long
ELSE IF an operand is long
THEN the second is converted to long
ELSE IF an operand is unsigned int
THEN the second is converted to unsigned int
53
54
C++: The Complete Reference
char ch;
int i;
float f;
double d;
result=(ch/i)
int
+
(f*d)
–
double
int
(f+i);
float
double
float
double
Figure 2-2
...
Once these conversion rules have been applied, each pair of operands is of the
same type and the result of each operation is the same as the type of both operands
...
First, the
character ch is converted to an integer
...
The outcome of f+i is float, because f is a float
...
Casts
You can force an expression to be of a specific type by using a cast
...
For example, to make sure that the expression x/2
evaluates to type float, write
(float) x/2
Chapter 2:
Expressions
Casts are technically operators
...
Although casts are not usually used a great deal in programming, they can be very
useful when needed
...
h>
int main(void) /* print i and i/2 with fractions */
{
int i;
for(i=1; i<=100; ++i)
printf("%d / 2 is: %f\n", i, (float) i /2);
return 0;
}
Without the cast (float), only an integer division would have been performed
...
Note
C++ adds four new casting operators, such as const_cast and static_cast
...
Spacing and Parentheses
You can add tabs and spaces to expressions to make them easier to read
...
You should use parentheses to clarify the exact order of evaluation,
both for yourself and for others
...
For
example,
x = x+10;
can be written as
x += 10;
The operator += tells the compiler to assign to x the value of x plus 10
...
In general, statements like:
var = var operator expression
can be rewritten as
var operator = expression
For another example,
x = x-100;
is the same as
x -= 100;
Shorthand notation is widely used in professionally written C/C++ programs; you
should become familiar with it
...
In the most general sense, a statement is a
part of your program that can be executed
...
C and C++ categorize statements into these groups:
s Selection
s Iteration
s Jump
s Label
s Expression
s Block
Included in the selection statements are if and switch
...
") The iteration statements are
while, for, and do-while
...
The jump
statements are break, continue, goto, and return
...
Expression statements are statements composed of a
valid expression
...
(Remember, a block
begins with a { and ends with a }
...
Note
C++ adds two additional statement types: the try block (used by exception handling)
and the declaration statement
...
Since many statements rely upon the outcome of some conditional test, let's begin
by reviewing the concepts of true and false
...
A conditional expression evaluates to either a true or
false value
...
A
false value is 0
...
C++ fully supports the zero/nonzero definition of true and false just described
...
As explained in Chapter 2, in C++, a 0 value is automatically converted into
false and a nonzero value is automatically converted into true
...
In C++, the expression that controls a
conditional statement is technically of type bool
...
Selection Statements
C/C++ supports two types of selection statements: if and switch
...
if
The general form of the if statement is
if (expression) statement;
else statement;
where a statement may consist of a single statement, a block of statements, or nothing
(in the case of empty statements)
...
If expression evaluates to true (anything other than 0), the statement or block that
forms the target of if is executed; otherwise, the statement or block that is the target of
else will be executed, if it exists
...
In C, the conditional statement controlling if must produce a scalar result
...
In C++, it may also be of
type bool
...
(It takes several instructions to perform
a floating-point operation
...
)
The following program contains an example of if
...
It prints the message ** Right ** when
the player guesses the magic number
...
rand( ) requires the header file stdlib
...
(A C++ program may also use the new-style
header
...
*/
#include
h>
int main(void)
{
int magic; /* magic number */
59
60
C++: The Complete Reference
int guess; /* user's guess */
magic = rand(); /* generate the magic number */
printf("Guess the magic number: ");
scanf("%d", &guess);
if(guess == magic) printf("** Right **");
return 0;
}
Taking the magic number program further, the next version illustrates the use of
the else statement to print a message in response to the wrong number
...
*/
#include
h>
int main(void)
{
int magic; /* magic number */
int guess; /* user's guess */
magic = rand(); /* generate the magic number */
printf("Guess the magic number: ");
scanf("%d", &guess);
if(guess == magic) printf("** Right **");
else printf("Wrong");
return 0;
}
Nested ifs
A nested if is an if that is the target of another if or else
...
In a nested if, an else statement always refers to the nearest if
statement that is within the same block as the else and that is not already associated
with an else
...
Rather, the final else is associated with if(i)
...
Standard C specifies that at least 15 levels of nesting must be supported
...
More importantly, Standard C++ suggests
that at least 256 levels of nested ifs be allowed in a C++ program
...
You can use a nested if to further improve the magic number program by
providing the player with feedback about a wrong guess
...
*/
#include
h>
int main(void)
{
int magic; /* magic number */
int guess; /* user's guess */
magic = rand(); /* get a random number */
printf("Guess the magic number: ");
scanf("%d", &guess);
if (guess == magic) {
printf("** Right **");
printf(" %d is the magic number\n", magic);
}
else {
printf("Wrong, ");
if(guess > magic) printf("too high\n");
else printf("too low\n");
}
61
62
C++: The Complete Reference
return 0;
}
The if-else-if Ladder
A common programming construct is the if-else-if ladder, sometimes called the if-else-if
staircase because of its appearance
...
...
else statement;
The conditions are evaluated from the top downward
...
If none of the conditions are true, the final else is executed
...
If the final else is not
present, no action takes place if all other conditions are false
...
For this reason, the if-else-if ladder is generally
indented like this:
if (expression)
statement;
else if (expression)
statement;
else if (expression)
statement;
...
...
*/
Chapter 3:
Statements
#include
h>
int main(void)
{
int magic; /* magic number */
int guess; /* user's guess */
magic = rand(); /* generate the magic number */
printf("Guess the magic number: ");
scanf("%d", &guess);
if(guess == magic) {
printf("** Right ** ");
printf("%d is the magic number", magic);
}
else if(guess > magic)
printf("Wrong, too high");
else printf("Wrong, too low");
return 0;
}
The ? Alternative
You can use the ? operator to replace if-else statements of the general form:
if(condition) expression;
else expression;
However, the target of both if and else must be a single expression—not another
statement
...
It takes the
general form
Exp1 ? Exp2 : Exp3
where Exp1, Exp2, and Exp3 are expressions
...
The value of a ? expression is determined as follows: Exp1 is evaluated
...
If Exp1 is false, then
Exp3 is evaluated and its value becomes the value of the expression
...
If x had been less than 9, y would have
received the value 200
...
However, this program preserves the sign (10 squared is 100 and −10 squared
is −100)
...
h>
int main(void)
{
int isqrd, i;
printf("Enter a number: ");
scanf("%d", &i);
isqrd = i>0 ? i*i : -(i*i);
printf("%d squared is %d", i, isqrd);
return 0;
}
The use of the ? operator to replace if-else statements is not restricted to
assignments only
...
Thus, you can use one or more function calls in a ? expression
...
Therefore, you can execute one or more function calls using the ? operator
by placing the calls in the expressions that form the ?'s operands
...
#include
\n");
return 0;
}
int f1(int n)
{
printf("%d ", n);
return 0;
}
int f2(void)
{
printf("entered
...
If you enter any other number, both f1( ) and f2( ) execute
...
You don't need to assign it to anything
...
This could cause functions that
form the operands of the ? operator to execute in an unintended sequence
...
/* Magic number program #5
...
h>
#include
The Conditional Expression
Sometimes newcomers to C/C++ are confused by the fact that you can use any valid
expression to control the if or the ? operator
...
The expression must simply evaluate to either a true or false
(zero or nonzero) value
...
It uses an if statement, controlled by the
second number, to avoid a divide-by-zero error
...
*/
#include
\n");
return 0;
}
Chapter 3:
Statements
This approach works because if b is 0, the condition controlling the if is false and the
else executes
...
One other point: Writing the if statement as shown here
if(b != 0) printf("%d\n", a/b);
is redundant, potentially inefficient, and is considered bad style
...
switch
C/C++ has a built-in multiple-branch selection statement, called switch, which
successively tests the value of an expression against a list of integer or character
constants
...
The general form of the switch statement is
switch (expression) {
case constant1:
statement sequence
break;
case constant2:
statement sequence
break;
case constant3:
statement sequence
break;
...
...
Floating-point expressions,
for example, are not allowed
...
When a match is found, the
statement sequence associated with that case is executed until the break statement or
the end of the switch statement is reached
...
The default is optional and, if it is not present, no action takes place
if all matches fail
...
Standard
C++ recommends that at least 16,384 case statements be supported! In practice, you
will want to limit the number of case statements to a smaller amount for efficiency
...
67
68
C++: The Complete Reference
The break statement is one of C/C++'s jump statements
...
When break is
encountered in a switch, program execution "jumps" to the line of code following the
switch statement
...
s No two case constants in the same switch can have identical values
...
s If character constants are used in the switch statement, they are automatically
converted to integers
...
As shown here, the function menu( ) displays a menu for a spelling-checker
program and calls the proper procedures:
void menu(void)
{
char ch;
printf("1
...
Correct Spelling Errors\n");
printf("3
...
They
terminate the statement sequence associated with each constant
...
For example, the following function uses the
"drop through" nature of the cases to simplify the code for a device-driver input
handler:
/* Process a value */
void inp_handler(int i)
{
int flag;
flag = -1;
switch(i) {
case 1: /* These cases have common */
case 2: /* statement sequences
...
First, you can have case statements
that have no statement sequence associated with them
...
In this example, the first three cases all execute
the same statements, which are
flag = 0;
break;
69
70
C++: The Complete Reference
Second, execution of one statement sequence continues into the next case if no
break statement is present
...
If i had matched 5, error(flag) would have been called with a flag value of −1
(rather than 1)
...
Nested switch Statements
You can have a switch as part of the statement sequence of an outer switch
...
For example, the following code fragment is perfectly acceptable:
switch(x) {
case 1:
switch(y) {
case 0: printf("Divide by zero error
...
...
Iteration Statements
In C/C++, and all other modern programming languages, iteration statements (also
called loops) allow a set of instructions to be executed repeatedly until a certain
condition is reached
...
The for Loop
The general design of the for loop is reflected in some form or another in all procedural
programming languages
...
The general form of the for statement is
for(initialization; condition; increment) statement;
The for loop allows many variations, but its most common form works like this
...
The
Chapter 3:
Statements
condition is a relational expression that determines when the loop exits
...
You must
separate these three major sections by semicolons
...
Once the condition becomes false, program execution
resumes on the statement following the for
...
h>
int main(void)
{
int x;
for(x=1; x <= 100; x++) printf("%d ", x);
return 0;
}
In the loop, x is initially set to 1 and then compared with 100
...
This causes x to be increased by 1 and again
tested to see if it is still less than or equal to 100
...
This process
repeats until x is greater than 100, at which point the loop terminates
...
The following example is a for loop that iterates multiple statements:
for(x=100; x != 65; x -= 5) {
z = x*x;
printf("The square of %d, %f", x, z);
}
Both the squaring of x and the call to printf( ) are executed until x equals 65
...
In for loops, the conditional test is always performed at the top of the loop
...
For example, in
x = 10;
for(y=10; y!=x; ++y) printf("%d", y);
printf("%d", y); /* this is the only printf()
statement that will execute */
71
72
C++: The Complete Reference
the loop will never execute because x and y are equal when the loop is entered
...
Hence, y still has the value 10, and the
only output produced by the fragment is the number 10 printed once on the screen
...
However,
several variations of the for are allowed that increase its power, flexibility, and
applicability to certain programming situations
...
(Remember, you use the comma operator to string
together a number of expressions in a "do this and this" fashion
...
) For
example, the variables x and y control the following loop, and both are initialized
inside the for statement:
for(x=0, y=0; x+y<10; ++x) {
y = getchar();
y = y - '0'; /* subtract the ASCII code for 0
from y */
...
...
Each time the loop repeats, x is
incremented and y's value is set by keyboard input
...
Even though y's value is set by keyboard input, y must
be initialized to 0 so that its value is defined before the first evaluation of the
conditional expression
...
)
The converge( ) function, shown next, demonstrates multiple loop control variables
in action
...
/* Demonstrate multiple loop control variables
...
h>
#include
");
printf("Final string: %s\n", target);
return 0;
}
/* This function copies one string into another
...
*/
void converge(char *targ, char *src)
{
int i, j;
printf("%s\n", targ);
for(i=0, j=strlen(src); i<=j; i++, j--) {
targ[i] = src[i];
targ[j] = src[j];
printf("%s\n", targ);
}
}
Here is the output produced by the program
...
ThiXXXXXXXXXXXXXXXXXXXXXXXX)
...
This XXXXXXXXXXXXXXXXXXXXe()
...
This isXXXXXXXXXXXXXXXXrge()
...
This is aXXXXXXXXXXXXverge()
...
This is a tXXXXXXXXonverge()
...
This is a tesXXXX converge()
...
This is a test of converge()
...
Statements
73
74
C++: The Complete Reference
In converge( ), the for loop uses two loop control variables, i and j, to index the
string from opposite ends
...
The
loop stops when i is greater than j, thus ensuring that all characters are copied
...
In fact, the condition may be any relational or
logical statement
...
For example, you could use the following function to log a user onto a remote
system
...
The loop terminates when the
three tries are used up or the user enters the correct password
...
*/
}
This function uses strcmp( ), the standard library function that compares two strings
and returns 0 if they match
...
The expressions need not actually have anything to do with what the
sections are generally used for
...
h>
int sqrnum(int num);
int readnum(void);
int prompt(void);
int main(void)
{
int t;
for(prompt(); t=readnum(); prompt())
Chapter 3:
Statements
sqrnum(t);
return 0;
}
int prompt(void)
{
printf("Enter a number: ");
return 0;
}
int readnum(void)
{
int t;
scanf("%d", &t);
return t;
}
int sqrnum(int num)
{
printf("%d\n", num*num);
return num*num;
}
Look closely at the for loop in main( )
...
If the number entered is 0, the loop terminates because the conditional
expression will be false
...
Thus, this for loop uses the
initialization and increment portions in a nontraditional but completely valid sense
...
In fact, there need not be an expression present for any of the sections—the
expressions are optional
...
This means that each
time the loop repeats, x is tested to see if it equals 123, but no further action takes place
...
75
76
C++: The Complete Reference
The initialization of the loop control variable can occur outside the for statement
...
The Infinite Loop
Although you can use any loop statement to create an infinite loop, for is traditionally
used for this purpose
...
\n");
When the conditional expression is absent, it is assumed to be true
...
Actually, the for(;;) construct does not guarantee an infinite loop because a break
statement, encountered anywhere inside the body of a loop, causes immediate
termination
...
) Program control then
resumes at the code following the loop, as shown here:
ch = '\0';
for( ; ; ) {
ch = getchar(); /* get a character */
if(ch=='A') break; /* exit the loop */
}
printf("you typed an A");
This loop will run until the user types an A at the keyboard
...
This means that the body of the for loop (or any other loop)
may also be empty
...
Removing spaces from an input stream is a common programming task
...
" The database needs to have each word fed to it separately, without leading
spaces
...
The
following loop shows one way to accomplish this
...
for( ; *str == ' '; str++) ;
As you can see, this loop has no body—and no need for one either
...
The following code shows how to
create one by using for:
for(t=0; t
The while Loop
The second loop available in C/C++ is the while loop
...
The condition may be any expression, and true is any nonzero value
...
When the condition becomes false, program
control passes to the line of code immediately following the loop
...
As a local variable, its value is not known when
wait_for_char( ) is executed
...
Because ch was initialized to null, the test is true and the loop begins
...
Once you enter an A, the condition becomes
false because ch equals A, and the loop terminates
...
This feature may eliminate the need to perform a separate conditional test before the
loop
...
It adds spaces to the end
of a string to fill the string to a predefined length
...
#include
h>
void pad(char *s, int length);
int main(void)
{
char str[80];
strcpy(str, "this is a test");
pad(str, 40);
printf("%d", strlen(str));
return 0;
}
/* Add spaces to the end of a string
...
If the length of string s is already equal to or
greater than length, the code inside the while loop does not execute
...
The strlen( ) function, part of the
standard library, returns the length of the string
...
The value of this variable is set at various
points throughout the loop
...
e
...
There need not be any statements in the body of the while loop
...
If you feel uncomfortable putting the
assignment inside the while conditional expression, remember that the equal sign is
just an operator that evaluates to the value of the right-hand operand
...
This means that a
do-while loop always executes at least once
...
The
do-while loop iterates until condition becomes false
...
do {
scanf("%d", &num);
} while(num > 100);
Perhaps the most common use of the do-while loop is in a menu selection function
...
Invalid responses cause a reprompt
...
Check Spelling\n");
printf("2
...
Display Spelling Errors\n");
printf("
Enter your choice: ");
do {
ch = getchar(); /* read the selection from
the keyboard */
switch(ch) {
case '1':
check_spelling();
break;
case '2':
correct_errors();
break;
case '3':
display_errors();
break;
}
} while(ch!='1' && ch!='2' && ch!='3');
}
Chapter 3:
Statements
Here, the do-while loop is a good choice because you will always want a menu
function to execute at least once
...
Declaring Variables within Selection and
Iteration Statements
In C++ (but not C), it is possible to declare a variable within the conditional expression
of an if or switch, within the conditional expression of a while loop, or within the
initialization portion of a for loop
...
For example, a variable
declared within a for loop will be local to that loop
...
*/
int j;
for(int i = 0; i<10; i++)
j = i * i;
/* i = 10; // *** Error *** -- i not known here! */
Here, i is declared within the initialization portion of the for and is used to control the
loop
...
Since often a loop control variable in a for is needed only by that loop, the
declaration of the variable in the initialization portion of the for is becoming common
practice
...
Tip
Whether a variable declared within the initialization portion of a for loop is local to
that loop has changed over time
...
However, Standard C++ restricts the variable to the scope of the for loop
...
For
example, this fragment,
if(int x = 20) {
x = x - y;
81
82
C++: The Complete Reference
if(x>10) y = 0;
}
declares x and assigns it the value 20
...
Variables declared within a conditional statement have their scope limited
to the block of code controlled by that statement
...
Frankly, not all programmers believe that declaring variables within
conditional statements is good practice, and this technique will not be used in
this book
...
Of these, you may use return and goto anywhere in your program
...
As discussed earlier in this chapter, you can also use break with switch
...
It is categorized as a jump
statement because it causes execution to return (jump back) to the point at which the
call to the function was made
...
If return has a value associated with it, that value becomes the return value of the
function
...
If no
return value is specified, a garbage value is returned
...
That is, in C++, if a function is specified as returning a
value, any return statement within it must have a value associated with it
...
)
The general form of the return statement is
return expression;
The expression is present only if the function is declared as returning a value
...
You can use as many return statements as you like within a function
...
The } that ends a
function also causes the function to return
...
If this occurs within a non-void function, then the return value of the
function is undefined
...
Since a void function has no return value, it makes sense that no return
statement within a void function can return a value
...
The goto Statement
Since C/C++ has a rich set of control structures and allows additional control using
break and continue, there is little need for goto
...
Nevertheless, although
the goto statement fell out of favor some years ago, it occasionally has its uses
...
Rather, it is a convenience, which, if
used wisely, can be a benefit in a narrow set of programming situations, such as
jumping out of a set of deeply nested loops
...
The goto statement requires a label for operation
...
) Furthermore, the label must be in the same function as the goto
that uses it—you cannot jump between functions
...
...
label:
where label is any valid label either before or after goto
...
You can use it to terminate a case in the switch
statement (covered in the section on switch earlier in this chapter)
...
When the break statement is encountered inside a loop, the loop is immediately
terminated and program control resumes at the next statement following the loop
...
h>
int main(void)
83
84
C++: The Complete Reference
{
int t;
for(t=0; t<100; t++) {
printf("%d ", t);
if(t==10) break;
}
return 0;
}
prints the numbers 0 through 10 on the screen
...
Programmers often use the break statement in loops in which a special condition
can cause immediate termination
...
*/
if(kbhit()) break;
} while(!found);
/* process match */
}
The kbhit( ) function returns 0 if you do not press a key
...
Because of the wide differences between computing environments,
neither Standard C nor Standard C++ defines kbhit( ), but you will almost certainly
have it (or one with a slightly different name) supplied with your compiler
...
For example,
for(t=0; t<100; ++t) {
count = 1;
for(;;) {
printf("%d ", count);
count++;
if(count==10) break;
}
}
Chapter 3:
Statements
prints the numbers 1 through 10 on the screen 100 times
...
A break used in a switch statement will affect only that switch
...
The exit( ) Function
Although exit( ) is not a program control statement, a short digression that discusses it
is in order at this time
...
This function causes immediate
termination of the entire program, forcing a return to the operating system
...
The general form of the exit( ) function is
void exit(int return_code);
The value of return_code is returned to the calling process, which is usually the
operating system
...
Other arguments are used to indicate some sort of error
...
The exit( )
function requires the header stdlib
...
A C++ program may also use the new-style
header
...
For example, imagine a virtual reality computer game that
requires a special graphics adapter
...
h>
int main(void)
{
if(!virtual_graphics()) exit(1);
play();
/*
...
*/
where virtual_graphics( ) is a user-defined function that returns true if the
virtual-reality graphics adapter is present
...
As another example, this version of menu( ) uses exit( ) to quit the program and
return to the operating system:
85
86
C++: The Complete Reference
void menu(void)
{
char ch;
printf("1
...
printf("3
...
printf("
Check Spelling\n");
Correct Spelling Errors\n");
Display Spelling Errors\n");
Quit\n");
Enter your choice: ");
do {
ch = getchar(); /* read the selection from
the keyboard */
switch(ch) {
case '1':
check_spelling();
break;
case '2':
correct_errors();
break;
case '3':
display_errors();
break;
case '4':
exit(0); /* return to OS */
}
} while(ch!='1' && ch!='2' && ch!='3');
}
The continue Statement
The continue statement works somewhat like the break statement
...
For the for loop, continue causes the conditional test
and increment portions of the loop to execute
...
For example, the following program
counts the number of spaces contained in the string entered by the user:
/* Count spaces */
#include
If it is not, the continue statement forces
the for to iterate again
...
The following example shows how you can use continue to expedite the exit from a
loop by forcing the conditional test to be performed sooner:
void code(void)
{
char done, ch;
done = 0;
while(!done) {
ch = getchar();
if(ch=='$') {
done = 1;
continue;
}
putchar(ch+1); /* shift the alphabet one
position higher */
}
}
This function codes a message by shifting all characters you type one letter higher
...
The function will terminate when you type a $
...
87
88
C++: The Complete Reference
Expression Statements
Chapter 2 covered expressions thoroughly
...
Remember, an expression statement is simply a valid expression
followed by a semicolon, as in
func();
a = b+c;
b+f();
;
/*
/*
/*
/*
a function call */
an assignment statement */
a valid, but strange statement */
an empty statement */
The first expression statement executes a function call
...
The third expression, though strange, is still evaluated by the C++ compiler because
the function f( ) may perform some necessary task
...
Block Statements
Block statements are simply groups of related statements that are treated as a unit
...
Block statements are also
called compound statements
...
Programmers use block statements most commonly to create a multistatement target
for some other statement, such as if
...
For example, this is perfectly valid
(although unusual) C/C++ code:
#include
A specific element in an array is accessed by an index
...
The lowest address
corresponds to the first element and the highest address to the last element
...
The most common array is the
null-terminated string, which is simply an array of characters terminated by a null
...
This chapter focuses on arrays, while Chapter 5 looks closely at pointers
...
A
Single-Dimension Arrays
The general form for declaring a single-dimension array is
type var_name[size];
Like other variables, arrays must be explicitly declared so that the compiler may
allocate space for them in memory
...
For example, to declare a 100-element array called balance of type double,
use this statement:
double balance[100];
An element is accessed by indexing the array name
...
For example,
balance[3] = 12
...
23
...
Therefore, when
you write
char p[10];
you are declaring a character array that has ten elements, p[0] through p[9]
...
h>
int main(void)
Chapter 4:
Arrays and Null-Terminated Strings
{
int x[100]; /* this declares a 100-integer array */
int t;
/* load x with values 0 through 99 */
for(t=0; t<100; ++t) x[t] = t;
/* display contents of x */
for(t=0; t<100; ++t) printf("%d ", x[t]);
return 0;
}
The amount of storage required to hold an array is directly related to its type and
size
...
You could overwrite either end of an
array and write into some other variable's data or even into the program's code
...
For example,
this code will compile without error, but is incorrect because the for loop will cause the
array count to be overrun
...
For example, Figure 4-1 shows
how array a appears in memory if it starts at memory location 1000 and is declared as
shown here:
char a[7];
Element
Address
Figure 4-1
...
For example, given
int sample[10];
you can generate a pointer to the first element by using the name sample
...
For example, sample and &sample[0] both produce the same results
...
Passing Single-Dimension Arrays to Functions
In C/C++, you cannot pass an entire array as an argument to a function
...
For example, the following program fragment passes the address of i
to func1( ):
int main(void)
{
int i[10];
func1(i);
...
...
For example, to receive i, a function called func1( ) can be declared as
Chapter 4:
Arrays and Null-Terminated Strings
void func1(int *x) /* pointer */
{
...
...
...
}
or finally as
void func1(int x[]) /* unsized array */
{
...
...
The first declaration actually
uses a pointer
...
In the final version,
a modified version of an array declaration simply specifies that an array of type int of
some length is to be received
...
In fact,
as far as the compiler is concerned,
void func1(int x[32])
{
...
...
93
94
C++: The Complete Reference
Null-Terminated Strings
By far the most common use of the one-dimensional array is as a character string
...
The first is the null-terminated string, which is a
null-terminated character array
...
) Thus a null-terminated string contains
the characters that comprise the string followed by a null
...
Sometimes null-terminated strings
are called C-strings
...
It is described later in this book
...
When declaring a character array that will hold a null-terminated string, you need
to declare it to be one character longer than the largest string that it is to hold
...
When you use a quoted string constant in your program, you are also creating a
null-terminated string
...
For example,
"hello there"
You do not need to add the null to the end of string constants manually—the compiler
does this for you automatically
...
The most common are
Name
Function
strcpy(s1, s2)
Copies s2 into s1
...
strlen(s1)
Returns the length of s1
...
strchr(s1, ch)
Returns a pointer to the first occurrence of ch in s1
...
These functions use the standard header file string
...
(C++ programs can also use the
new-style header
...
h>
#include
\n");
printf(s1);
if(strchr("hello", 'e')) printf("e is in hello\n");
if(strstr("hi there", "hi")) printf("found hi");
return 0;
}
If you run this program and enter the strings "hello" and "hello", the output is
lengths: 5 5
The strings are equal
hellohello
This is a test
...
Be sure to use the logical
operator ! to reverse the condition, as just shown, if you are testing for equality
...
They will probably stay in wide use because they offer a
high level of efficiency and afford the programmer detailed control of string
operations
...
95
96
C++: The Complete Reference
Two-Dimensional Arrays
C/C++ supports multidimensional arrays
...
A two-dimensional array is, essentially, an array of
one-dimensional arrays
...
Some other computer languages use commas
to separate the array dimensions; C/C++, in contrast, places each dimension in its own
set of brackets
...
#include
The value of num[2][3] will be 12
...
This means that the rightmost
index changes faster than the leftmost when accessing the elements in the array in the
order in which they are actually stored in memory
...
In the case of a two-dimensional array, the following formula yields the number of
bytes of memory needed to hold it:
bytes = size of 1st index x size of 2nd index x sizeof(base type)
Therefore, assuming 4-byte integers, an integer array with dimensions 10,5 would have
10 x 5 x 4
or 200 bytes allocated
...
A two-dimensional array in memory
97
98
C++: The Complete Reference
When a two-dimensional array is used as an argument to a function, only a
pointer to the first element is actually passed
...
(You
can specify the left dimension if you like, but it is not necessary
...
For example, a function that receives a two-dimensional
integer array with dimensions 10,10 is declared like this:
void func1(int x[][10])
{
...
...
If the length of the rows is not known, the compiler cannot
determine where the third row begins
...
The program assumes that the teacher has
three classes and a maximum of 30 students per class
...
/* A simple student grades database
...
h>
#include
h>
#define CLASSES 3
#define GRADES 30
int grade[CLASSES][GRADES];
void enter_grades(void);
int get_grade(int num);
void disp_grades(int g[][GRADES]);
Chapter 4:
Arrays and Null-Terminated Strings
int main(void)
{
char ch, str[80];
for(;;) {
do {
printf("(E)nter grades\n");
printf("(R)eport grades\n");
printf("(Q)uit\n");
gets(str);
ch = toupper(*str);
} while(ch!='E' && ch!='R' && ch!='Q');
switch(ch) {
case 'E':
enter_grades();
break;
case 'R':
disp_grades(grade);
break;
case 'Q':
exit(0);
}
}
return 0;
}
/* Enter the student's grades
...
*/
int get_grade(int num)
99
100
C++: The Complete Reference
{
char s[80];
printf("Enter grade for student # %d:\n", num+1);
gets(s);
return(atoi(s));
}
/* Display grades
...
For example, the input
processor to a database may verify user commands against an array of valid
commands
...
The size of the left index determines the number of strings and the size
of the right index specifies the maximum length of each string
...
For
example, the following statement calls gets( ) with the third string in str_array
...
Chapter 4:
Arrays and Null-Terminated Strings
To better understand how string arrays work, study the following short program,
which uses a string array as the basis for a very simple text editor:
/* A very simple text editor
...
h>
#define MAX 100
#define LEN 80
char text[MAX][LEN];
int main(void)
{
register int t, i, j;
printf("Enter an empty line to quit
...
Then it redisplays each
line one character at a time
...
The exact limit, if any, is
determined by your compiler
...
[SizeN];
101
102
C++: The Complete Reference
Arrays of more than three dimensions are not often used because of the amount of
memory they require
...
If the array held 2-byte integers, 4,320 bytes would be needed
...
The
storage required increases exponentially with the number of dimensions
...
In multidimensional arrays, it takes the computer time to compute each index
...
When passing multidimensional arrays into functions, you must declare all but the
leftmost dimension
...
...
}
Of course, you can include the first dimension if you like
...
As you know, an array name
without an index is a pointer to the first element in the array
...
char p[10];
The following statements are identical:
Chapter 4:
Arrays and Null-Terminated Strings
p
&p[0]
Put another way,
p == &p[0]
evaluates to true because the address of the first element of an array is the same as the
address of the array
...
Conversely, a
pointer can be indexed as if it were declared to be an array
...
The first
statement indexes p; the second uses pointer arithmetic
...
(Chapter 5 discusses pointers and pointer arithmetic
...
For example,
assuming that a is a 10-by-10 integer array, these two statements are equivalent:
a
&a[0][0]
Furthermore, the 0,4 element of a may be referenced two ways: either by array
indexing, a[0][4], or by the pointer, *((int *)a+4)
...
In general, for any two-dimensional array
a[j][k] is equivalent to *((base-type *)a+(j*row length)+k)
The cast of the pointer to the array into a pointer of its base type is necessary in order
for the pointer arithmetic to operate properly
...
A two-dimensional array can be reduced to a pointer to an array of onedimensional arrays
...
The
following function illustrates this technique
...
...
void pr_row(int j)
{
int *p, t;
p = (int *) &num[j][0]; /* get address of first
element in row j */
for(t=0; t<10; ++t) printf("%d ", *(p+t));
}
You can generalize this routine by making the calling arguments be the row, the row
length, and a pointer to the first array element, as shown here:
void pr_row(int j, int row_dimension, int *p)
{
int t;
p = p + (j * row_dimension);
for(t=0; t
}
...
...
For
example, a three-dimensional array can be reduced to a pointer to a two-dimensional
array, which can be reduced to a pointer to a single-dimension array
...
This
new array can be reduced again with the same method
...
Chapter 4:
Arrays and Null-Terminated Strings
Array Initialization
C/C++ allows the initialization of arrays at the time of their declaration
...
[sizeN] = { value_list };
The value_list is a comma-separated list of values whose type is compatible with
type_specifier
...
Note that a semicolon follows the }
...
Character arrays that hold strings allow a shorthand initialization that takes
the form:
char array_name[size] = "string";
For example, this code fragment initializes str to the phrase "I like C++"
...
This is why str is 11 characters long even
though "I like C++" is only 10
...
Multidimensional arrays are initialized the same as single-dimension ones
...
int sqrs[10][2] = {
1, 1,
105
106
C++: The Complete Reference
2, 4,
3, 9,
4, 16,
5, 25,
6, 36,
7, 49,
8, 64,
9, 81,
10, 100
};
When initializing a multidimensional array, you may add braces around the
initializers for each dimension
...
For example, here is
another way to write the preceding declaration
...
Unsized Array Initializations
Imagine that you are using array initialization to build a table of error messages, as
shown here:
char e1[12] = "Read error\n";
char e2[13] = "Write error\n";
char e3[18] = "Cannot open file\n";
As you might guess, it is tedious to count the characters in each message manually
to determine the correct array dimension
...
If, in an array initialization
statement, the size of the array is not specified, the C/C++ compiler automatically
creates an array big enough to hold all the initializers present
...
Using this approach, the message table becomes
char e1[] = "Read error\n";
char e2[] = "Write error\n";
char e3[] = "Cannot open file\n";
Given these initializations, this statement
printf("%s has length %d\n",
e2,
sizeof e2);
will print
Write error has length 13
Besides being less tedious, unsized array initialization allows you to change any of the
messages without fear of using incorrect array dimensions
...
For
multidimensional arrays, you must specify all but the leftmost dimension
...
) In this way,
you may build tables of varying lengths and the compiler automatically allocates
enough storage for them
...
107
108
C++: The Complete Reference
A Tic-Tac-Toe Example
The longer example that follows illustrates many of the ways that you can manipulate
arrays with C/C++
...
Two-dimensional arrays are commonly used to simulate board game matrices
...
When it is the computer's turn, it uses
get_computer_move( ) to scan the matrix, looking for an unoccupied cell
...
If it cannot find an empty location, it reports a draw game and
exits
...
The
upper-left corner is location 1,1; the lower-right corner is 3,3
...
Each move made by the player or
the computer changes a space into either an X or an O
...
Each time a move has been made, the program calls the check( ) function
...
It scans the rows, the columns, and then the diagonals, looking for
one that contains either all X's or all O's
...
Notice how
initializing the matrix with spaces simplified this function
...
Study them to
make sure that you understand each array operation
...
*/
#include
h>
char matrix[3][3];
char
void
void
void
void
/* the tic tac toe matrix */
check(void);
init_matrix(void);
get_player_move(void);
get_computer_move(void);
disp_matrix(void);
int main(void)
{
char done;
printf("This is the game of Tic Tac Toe
...
\n");
done =
' ';
Chapter 4:
Arrays and Null-Terminated Strings
init_matrix();
do{
disp_matrix();
get_player_move();
done = check(); /* see if winner */
if(done!= ' ') break; /* winner!*/
get_computer_move();
done = check(); /* see if winner */
} while(done== ' ');
if(done=='X') printf("You won!\n");
else printf("I won!!!!\n");
disp_matrix(); /* show final positions */
return 0;
}
/* Initialize the matrix
...
*/
void get_player_move(void)
{
int x, y;
printf("Enter X,Y coordinates for your move: ");
scanf("%d%*c%d", &x, &y);
x--; y--;
if(matrix[x][y]!= ' '){
printf("Invalid move, try again
...
*/
void get_computer_move(void)
{
int i, j;
for(i=0; i<3; i++){
for(j=0; j<3; j++)
if(matrix[i][j]==' ') break;
if(matrix[i][j]==' ') break;
}
if(i*j==9) {
printf("draw\n");
exit(0);
}
else
matrix[i][j] = 'O';
}
/* Display the matrix on the screen
...
*/
char check(void)
{
int i;
for(i=0; i<3; i++) /* check rows */
if(matrix[i][0]==matrix[i][1] &&
matrix[i][0]==matrix[i][2]) return matrix[i][0];
for(i=0; i<3; i++) /* check columns */
if(matrix[0][i]==matrix[1][i] &&
Chapter 4:
Arrays and Null-Terminated Strings
matrix[0][i]==matrix[2][i]) return matrix[0][i];
/* test diagonals */
if(matrix[0][0]==matrix[1][1] &&
matrix[1][1]==matrix[2][2])
return matrix[0][0];
if(matrix[0][2]==matrix[1][1] &&
matrix[1][1]==matrix[2][0])
return matrix[0][2];
return ' ';
}
111
This page intentionally left blank
...
There are three reasons for this: First, pointers provide the means
by which functions can modify their calling arguments
...
Third, pointers can improve the efficiency of certain routines
...
Pointers are one of the strongest but also one of the most dangerous features in
C/C++
...
Perhaps worse, it is easy to use pointers incorrectly,
causing bugs that are very difficult to find
...
T
What Are Pointers?
A pointer is a variable that holds a memory address
...
For example, if one variable
contains the address of another variable, the first variable is said to point to the second
...
Figure 5-1
...
A pointer
declaration consists of a base type, an *, and the variable name
...
The name of
the pointer variable is specified by name
...
Technically, any type of pointer can point anywhere in memory
...
(Pointer arithmetic is discussed later in this chapter
...
We will take a closer look at them
here, beginning with a review of their basic operation
...
The & is a unary operator that returns the memory address of its
operand
...
)
For example,
m = &count;
places into m the memory address of the variable count
...
It has nothing to do with the value of count
...
" Therefore, the preceding assignment statement
means "m receives the address of count
...
Also assume that count has a value of 100
...
The second pointer operator, *, is the complement of &
...
For example, if m contains the
memory address of the variable count,
q = *m;
places the value of count into q
...
You can think of
115
116
C++: The Complete Reference
* as "at address
...
"
Both & and * have a higher precedence than all other arithmetic operators except
the unary minus, with which they are equal
...
For example, when you declare a pointer to be of type int, the compiler assumes
that any address that it holds points to an integer variable—whether it actually does
or not
...
h>
int main(void)
{
double x = 100
...
*/
p = &x;
/* The next statement does not operate as
expected
...
1 */
return 0;
}
This will not assign the value of x to y
...
Note
In C++, it is illegal to convert one type of pointer into another without the use of an
explicit type cast
...
However, the type of
error described can still occur in C++ in a more roundabout manner
...
This section examines a few special aspects of pointer expressions
...
For example,
#include
The address of x is displayed by using the %p printf( )
format specifier, which causes printf( ) to display an address in the format used by the
host computer
...
To understand what occurs in pointer arithmetic, let p1 be an
integer pointer with a current value of 2000
...
After the expression
p1++;
p1 contains 2002, not 2001
...
The same is true of decrements
...
Generalizing from the preceding example, the following rules govern pointer
arithmetic
...
Each time it is decremented, it points to the
location of the previous element
...
All other
pointers will increase or decrease by the length of the data type they point to
...
Figure 5-2 illustrates this concept
...
For example, you
may add or subtract integers to or from pointers
...
Besides addition and subtraction of a pointer and an integer, only one other
arithmetic operation is allowed: You may subtract one pointer from another in
order to find the number of objects of their base type that separate the two
...
Specifically, you may not multiply or
divide pointers; you may not add two pointers; you may not aypply the bitwise
operators to them; and you may not add or subtract type float or double to or
from pointers
...
All pointer arithmetic is relative to its base type (assume 2-byte
integers)
Chapter 5:
Pointers
Pointer Comparisons
You can compare two pointers in a relational expression
...
As an example, a pair of stack routines are
developed that store and retrieve integer values
...
It is often compared to a stack of plates on a table—the first
one set down is the last one to be used
...
To create a stack,
you need two functions: push( ) and pop( )
...
These routines are shown here with a simple
main( ) function to drive them
...
If you enter 0, a value is popped from the stack
...
#include
h>
#define SIZE 50
void push(int i);
int pop(void);
int
*tos, *p1, stack[SIZE];
int main(void)
{
int value;
tos = stack; /* tos points to the top of stack */
p1 = stack; /* initialize p1 */
do {
printf("Enter value: ");
scanf("%d", &value);
if(value!=0) push(value);
else printf("value on top is %d\n", pop());
} while(value!=-1);
119
120
C++: The Complete Reference
return 0;
}
void push(int i)
{
p1++;
if(p1==(tos+SIZE)) {
printf("Stack Overflow
...
\n");
exit(1);
}
p1--;
return *(p1+1);
}
You can see that memory for the stack is provided by the array stack
...
The p1 variable accesses the stack
...
It is used to prevent
stack overflows and underflows
...
Both the push( ) and pop( ) functions perform a relational test
on the pointer p1 to detect limit errors
...
This prevents an overflow
...
In pop( ), the parentheses are necessary in the return statement
...
Pointers and Arrays
There is a close relationship between pointers and arrays
...
To access the fifth
element in str, you could write
str[4]
or
*(p1+4)
Both statements will return the fifth element
...
To access
the fifth element, you must use 4 to index str
...
(Recall
that an array name without an index returns the starting address of the array, which
is the address of the first element
...
In essence, C/C++ provides
two methods of accessing array elements: pointer arithmetic and array indexing
...
Since speed is often a consideration in programming,
C/C++ programmers commonly use pointers to access array elements
...
The putstr( ) function
writes a string to the standard output device one character at a time
...
*/
void putstr(char *s)
{
register int t;
for(t=0; s[t]; ++t) putchar(s[t]);
}
/* Access s as a pointer
...
In fact, the pointer version is the way routines of this sort
are commonly written in C/C++
...
The declaration for an int pointer
array of size 10 is
int *x[10];
To assign the address of an integer variable called var to the third element of the
pointer array, write
x[2] = &var;
To find the value of var, write
*x[2]
If you want to pass an array of pointers into a function, you can use the same
method that you use to pass other arrays—simply call the function with the array name
without any indexes
...
Therefore you need to declare the parameter q as an array of integer pointers,
as just shown
...
Pointer arrays are often used to hold pointers to strings
...
As you can see, printf( ) inside
syntax_error( ) is called with a character pointer that points to one of the
various error messages indexed by the error number passed to the function
...
As a point of interest, note that the command line argument argv is an
array of character pointers
...
)
Multiple Indirection
You can have a pointer point to another pointer that points to the target value
...
Pointers to pointers can
be confusing
...
As you can
see, the value of a normal pointer is the address of the object that contains the value
desired
...
Multiple indirection can be carried on to whatever extent rquired, but more than a
pointer to a pointer is rarely needed
...
Note
Do not confuse multiple indirection with high-level data structures, such as linked
lists, that use pointers
...
A variable that is a pointer to a pointer must be declared as such
...
For example, the following
declaration tells the compiler that newbalance is a pointer to a pointer of type float:
float **newbalance;
You should understand that newbalance is not a pointer to a floating-point number
but rather a pointer to a float pointer
...
h>
int main(void)
{
int x, *p, **q;
x = 10;
p = &x;
q = &p;
printf("%d", **q); /* print the value of x */
return 0;
}
Here, p is declared as a pointer to an integer and q as a pointer to a pointer to an
integer
...
Initializing Pointers
After a local pointer is declared but before it has been assigned a value, it contains
an unknown value
...
) Should
you try to use the pointer before giving it a valid value, you will probably crash
Figure 5-3
...
By convention, any pointer that is
null implies that it points to nothing and should not be used
...
" The use of null is simply a convention
that programmers follow
...
For
example, if you use a null pointer on the left side of an assignment statement, you still
run the risk of crashing your program or operating system
...
For example,
you could use a null pointer to mark the end of a pointer array
...
The
search( ) function shown here illustrates this type of approach
...
Assuming the end of the array is marked with a null, the condition
controlling the loop fails when it is reached
...
You saw an example of this in
the syntax_error( ) function in the section "Arrays of Pointers
...
The reason this sort of initialization
works is because of the way the compiler operates
...
Therefore, the preceding declaration statement places the address
of hello world, as stored in the string table, into the pointer p
...
For example, the following program is
perfectly valid:
#include
h>
char *p = "hello world";
int main(void)
{
register int t;
/* print the string forward and backwards */
printf(p);
for(t=strlen(p)-1; t>-1; t--) printf("%c", p[t]);
return 0;
}
In Standard C++, the type of a string literal is technically const char *
...
Thus, the preceding program is still valid
...
For new programs, you should assume that
string literals are constants and the declaration of p in the preceding program should
be written like this
...
Even
though a function is not a variable, it still has a physical location in memory that
can be assigned to a pointer
...
Once a pointer points to a function, the
function can be called through that pointer
...
You obtain the address of a function by using the function's name without any
parentheses or arguments
...
) To see how this is done, study the
following program, paying close attention to the declarations:
Chapter 5:
Pointers
#include
h>
void check(char *a, char *b,
int (*cmp)(const char *, const char *));
int main(void)
{
char s1[80], s2[80];
int (*p)(const char *, const char *);
p = strcmp;
gets(s1);
gets(s2);
check(s1, s2, p);
return 0;
}
void check(char *a, char *b,
int (*cmp)(const char *, const char *))
{
printf("Testing for equality
...
Inside the function check( ), the arguments are declared as
character pointers and a function pointer
...
You must use a similar form when declaring other function pointers, although the
return type and parameters of the function may differ
...
Inside check( ), the expression
(*cmp)(a, b)
calls strcmp( ), which is pointed to by cmp, with the arguments a and b
...
This is one way to call a function through
a pointer
...
127
128
C++: The Complete Reference
cmp(a, b);
The reason that you will frequently see the first style is that it tips off anyone reading
your code that a function is being called through a pointer
...
) Other than that, the two
expressions are equivalent
...
You may wonder why anyone would write a program in this way
...
However, at times it is advantageous to pass functions as parameters or to create an
array of functions
...
), perform
I/O, or access system resources
...
In this
approach, the proper function is selected by its index
...
In this
program, check( ) can be made to check for either alphabetical equality or numeric
equality by simply calling it with a different comparison function
...
h>
h>
\n");
if(!(*cmp)(a, b)) printf("Equal");
else printf("Not Equal");
}
int numcmp(const char *a, const char *b)
{
if(atoi(a)==atoi(b)) return 0;
else return 1;
}
In this program, if you enter a letter, strcmp( ) is passed to check( )
...
Since check( ) calls the function that it is passed, it can use different
comparison functions in different cases
...
Dynamic
allocation is the means by which a program can obtain memory while it is running
...
Local variables
use the stack
...
Yet there will be times when the storage needs of a program
cannot be known ahead of time
...
However, because the amount of available
RAM varies between computers, such programs will not be able to do so using
normal variables
...
C++ actually supports two complete dynamic allocation systems: the one
defined by C and the one specific to C++
...
Here, C's dynamic allocation functions are described
...
Although the size of the heap is unknown, it generally
contains a fairly large amount of free memory
...
(Most compilers supply several other dynamic allocation functions, but these two
are the most important
...
The malloc( ) function allocates
memory and the free( ) function releases it
...
Each time a
free( ) memory release call is made, memory is returned to the system
...
h
...
)
The malloc( ) function has this prototype:
void *malloc(size_t number_of_bytes);
Here, number_of_bytes is the number of bytes of memory you wish to allocate
...
h as, more or less, an unsigned integer
...
After a successful call, malloc( ) returns a pointer to the first byte
of the region of memory allocated from the heap
...
The code fragment shown here allocates 1,000 bytes of contiguous memory:
char *p;
p = malloc(1000); /* get 1000 bytes */
After the assignment, p points to the start of 1,000 bytes of free memory
...
In C, a void * pointer is automatically converted to the type
of the pointer on the left side of an assignment
...
In C++, an explicit type cast is
needed when a void * pointer is assigned to another type of pointer
...
This is one of the few fundamental
differences between C and C++
...
Notice the use of sizeof to ensure
portability
...
Using a null pointer will almost certainly crash your program
...
\n");
exit(1);
}
Of course, you can substitute some other sort of error handler in place of the call to
exit( )
...
The free( ) function is the opposite of malloc( ) in that it returns previously
allocated memory to the system
...
The function free( ) has this prototype:
void free(void *p);
Here, p is a pointer to memory that was previously allocated using malloc( )
...
Problems with Pointers
Nothing will get you into more trouble than a wild pointer! Pointers are a mixed
blessing
...
At the same time, when a pointer accidentally contains a wrong value, it can be the
most difficult bug to find
...
The problem is that each time you perform an operation using the bad
pointer, you are reading or writing to some unknown piece of memory
...
However, if you write to
it, you might be writing over other pieces of your code or data
...
There may be little or no evidence to suggest that the pointer is
131
132
C++: The Complete Reference
the original cause of the problem
...
Because pointer errors are such nightmares, you should do your best never to
generate one
...
The classic example of a pointer error is the uninitialized pointer
...
/* This program is wrong
...
Here is why:
Since the pointer p has never been given a value, it contains an unknown value when
the assignment *p = x takes place
...
This type of problem often goes unnoticed when your
program is small because the odds are in favor of p containing a "safe" address—one
that is not in your code, data area, or operating system
...
Eventually, your
program stops working
...
A second common error is caused by a simple misunderstanding of how to use a
pointer
...
*/
#include
It prints
some unknown value because the assignment
p = x;
is wrong
...
However, p is supposed
to contain an address, not a value
...
You can never know where your data will be
placed in memory, or if it will be placed there the same way again, or whether each
compiler will treat it in the same way
...
For example,
char s[80], y[80];
char *p1, *p2;
p1 = s;
p2 = y;
if(p1 < p2)
...
(In very unusual situations, you might use something
like this to determine the relative position of the variables
...
)
A related error results when you assume that two adjacent arrays may be indexed
as one by simply incrementing a pointer across the array boundaries
...
Even though it may work on some compilers under certain circumstances,
it assumes that both arrays will be placed back to back in memory with first first
...
The next program illustrates a very dangerous type of bug
...
/* This program has a bug
...
h>
#include
The problem is that p1 is assigned the address of s only once
...
However, the second
time through, it continues where it left off because it is not reset to the start of s
...
*/
#include
h>
int main(void)
{
char *p1;
Chapter 5:
Pointers
char s[80];
do {
p1 = s;
gets(s);
/* read a string */
/* print the decimal equivalent of each
character */
while(*p1) printf(" %d", *p1++);
} while(strcmp(s, "done"));
return 0;
}
Here, each time the loop iterates, p1 is set to the start of the string
...
The fact that handling pointers incorrectly can cause tricky bugs is no reason to
avoid using them
...
135
This page intentionally left blank
...
This chapter examines their C-like features, including passing
arguments, returning values, prototypes, and recursion
...
F
The General Form of a Function
The general form of a function is
ret-type function-name(parameter list)
{
body of the function
}
The ret-type specifies the type of data that the function returns
...
The parameter list is a comma-separated list of variable
names and their associated types that receive the values of the arguments when the
function is called
...
However, even if there are no parameters, the parentheses are still required
...
In contrast, all function parameters
must be declared individually, each including both the type and name
...
, type varnameN)
For example, here are correct and incorrect function parameter declarations:
f(int i, int k, int j) /* correct */
f(int i, k, float j)
/* incorrect */
Scope Rules of Functions
The scope rules of a language are the rules that govern whether a piece of code knows
about or has access to another piece of code or data
...
A function's code is private to that function
and cannot be accessed by any statement in any other function except through a call to
that function
...
) The code that constitutes the body of a function is hidden from the rest of the
program and, unless it uses global variables or data, it can neither affect nor be affected
Chapter 6:
Functions
by other parts of the program
...
Variables that are defined within a function are called local variables
...
That is, local variables cannot hold their value between function calls
...
This causes the compiler to treat the variable as if it were a global variable
for storage purposes, but limits its scope to within the function
...
)
In C (and C++) you cannot define a function within a function
...
Function Arguments
If a function is to use arguments, it must declare variables that accept the values
of the arguments
...
They behave like other local variables inside the function and are created upon entry
into the function and destroyed upon exit
...
*/
int is_in(char *s, char c)
{
while(*s)
if(*s==c) return 1;
else s++;
return 0;
}
The function is_in( ) has two parameters: s and c
...
As with local variables, you may make assignments to a function's formal
parameters or use them in an expression
...
Call by Value, Call by Reference
In a computer language, there are two ways that arguments can be passed to a
subroutine
...
This method copies the value of an
139
140
C++: The Complete Reference
argument into the formal parameter of the subroutine
...
Call by reference is the second way of passing arguments to a subroutine
...
Inside the subroutine,
the address is used to access the actual argument used in the call
...
By default, C/C++ uses call by value to pass arguments
...
Consider
the following program:
#include
When the assignment x = x*x takes place, only the local variable x is modified
...
Hence, the output is 100 10
...
What occurs inside the function has no effect on the variable used in the call
...
Since the address of the argument is passed to the function, code within the function
can change the value of the argument outside the function
...
Of course, you need
to declare the parameters as pointer types
...
void swap(int *x, int *y)
{
int temp;
temp = *x;
*x = *y;
*y = temp;
/* save the value at address x */
/* put y into x */
/* put x into y */
}
swap( ) is able to exchange the values of the two variables pointed to by x and y
because their addresses (not their values) are passed
...
Remember that swap( ) (or any other function that uses pointer parameters) must
be called with the addresses of the arguments
...
Then swap( ) is called with the addresses of i and j
...
) Therefore, the addresses of i and j, not their
values, are passed into the function swap( )
...
This feature is described in Part Two
...
However, this section discusses passing
arrays as arguments to functions because it is an exception to the normal call-by-value
parameter passing
...
This is an exception to the call-by-value parameter passing convention
...
For example, consider the function print_upper( ),
which prints its string argument in uppercase:
#include
h>
void print_upper(char *string);
int main(void)
{
char s[80];
gets(s);
print_upper(s);
printf("\ns is now uppercase: %s", s);
return 0;
}
/* Print a string in uppercase
...
If this is not what you want, you could write the program like this:
#include
h>
Chapter 6:
Functions
void print_upper(char *string);
int main(void)
{
char s[80];
gets(s);
print_upper(s);
printf("\ns is unchanged: %s", s);
return 0;
}
void print_upper(char *string)
{
register int t;
for(t=0; string[t]; ++t)
putchar(toupper(string[t]));
}
In this version, the contents of array s remain unchanged because its values are not
altered inside print_upper( )
...
Although the gets( ) in your standard library is more sophisticated, the
following simpler version, called xgets( ), will give you an idea of how it works
...
*/
char *xgets(char *s)
{
char ch, *p;
int t;
p = s;
/* gets() returns a pointer to s */
for(t=0; t<80; ++t){
ch = getchar();
switch(ch) {
143
144
C++: The Complete Reference
case '\n':
s[t] = '\0'; /* terminate the string */
return p;
case '\b':
if(t>0) t--;
break;
default:
s[t] = ch;
}
}
s[79] = '\0';
return p;
}
The xgets( ) function must be called with a character pointer
...
Upon entry,
xgets( ) establishes a for loop from 0 to 79
...
If more than 80 characters are entered, the function returns
...
) Because C/C++ has no built-in
bounds checking, you should make sure that any array used to call xgets( ) can accept
at least 80 characters
...
If you type a backspace, the counter t is reduced by 1, effectively removing the
previous character from the array
...
Because the actual array used to call xgets( ) is
modified, upon return it contains the characters that you type
...
Generally,
you pass information into the main( ) function via command line arguments
...
For example, when you compile a program,
you might type something like the following after the command prompt:
cc program_name
where program_name is a command line argument that specifies the name of the
program you wish to compile
...
The argc parameter holds the number of arguments on
Chapter 6:
Functions
the command line and is an integer
...
The argv parameter is a pointer to an array of
character pointers
...
All
command line arguments are strings—any numbers will have to be converted by the
program into the proper internal format
...
#include
h>
int main(int argc, char *argv[])
{
if(argc!=2) {
printf("You forgot to type your name
...
The output from the program would be Hello Tom
...
Commas, semicolons, and the like are not considered separators
...
Some environments allow you to enclose within double quotes a string containing
spaces
...
Check your
operating system documentation for details on the definition of command line
parameters for your system
...
The most common method is
char *argv[];
145
146
C++: The Complete Reference
The empty brackets indicate that the array is of undetermined length
...
For example, argv[0] points to the
first string, which is always the program's name; argv[1] points to the first argument,
and so on
...
It counts down from a starting value (which is specified on
the command line) and beeps when it reaches 0
...
If
the string "display" is the second command line argument, the countdown will also be
displayed on the screen
...
*/
#include
h>
#include
h>
int main(int argc, char *argv[])
{
int disp, count;
if(argc<2) {
printf("You must enter the length of the count\n");
printf("on the command line
...
\n");
exit(1);
}
if(argc==3 && !strcmp(argv[2], "display")) disp = 1;
else disp = 0;
for(count=atoi(argv[1]); count; --count)
if(disp) printf("%d\n", count);
putchar('\a'); /* this will ring the bell */
printf("Done");
return 0;
}
Notice that if no command line arguments have been specified, an error message is
printed
...
To access an individual character in one of the command line arguments, add a
second index to argv
...
h>
int main(int argc, char *argv[])
{
int t, i;
for(t=0; t
while(argv[t][i]) {
putchar(argv[t][i]);
++i;
}
printf("\n");
}
return 0;
}
Remember, the first index accesses the string, and the second index accesses the
individual characters of the string
...
In
theory, you can have up to 32,767 arguments, but most operating systems do not allow
more than a few
...
Using command line arguments gives your program a professional appearance and
facilitates its use in batch files
...
For C programs this is
accomplished by using the void keyword in its parameter list
...
) However, for C++ programs you
may simply specify an empty parameter list
...
The names argc and argv are traditional but arbitrary
...
Also, some compilers may support additional
arguments to main( ), so be sure to check your user's manual
...
As explained, it has two important
uses
...
That is, it causes
program execution to return to the calling code
...
This section examines how the return statement is used
...
The
first occurs when the last statement in the function has executed and, conceptually,
147
148
C++: The Complete Reference
the function's ending curly brace (}) is encountered
...
) For example, the
pr_reverse( ) function in this program simply prints the string "I like C++" backwards
on the screen and then returns
...
h>
#include
Actually, not many functions use this default method of terminating their
execution
...
A function may contain several return statements
...
#include
*/
int find_substr(char *s1, char *s2)
{
register int t;
char *p, *p2;
for(t=0; s1[t]; t++) {
p = &s1[t];
p2 = s2;
while(*p2 && *p2==*p) {
p++;
p2++;
}
if(!*p2) return t; /* 1st return */
}
return -1; /* 2nd return */
}
Returning Values
All functions, except those of type void, return a value
...
In C, if a non-void function does not explicitly return a value via a
return statement, then a garbage value is returned
...
That is, in C++, if a function is specified
as returning a value, any return statement within it must have a value associated with
it
...
Although this condition is not a syntax error, it is still a fundamental error
and should be avoided
...
Therefore, each of the following expressions is valid:
x = power(y);
if(max(x,y) > 100) printf("greater");
for(ch=getchar(); isdigit(ch); )
...
A statement
such as
149
150
C++: The Complete Reference
swap(x,y) = 100; /* incorrect statement */
is wrong
...
(As is discussed in Part Two, C++ allows some interesting exceptions
to this general rule, enabling some types of functions to occur on the left side of an
assignment
...
The
first type is simply computational
...
A computational function is a "pure" function
...
The second type of function manipulates information and returns a value that
simply indicates the success or failure of that manipulation
...
If the close operation is successful, the
function returns 0; if the operation is unsuccessful, it returns EOF
...
In essence, the function is
strictly procedural and produces no value
...
All functions that do not return values should be declared as returning type
void
...
Sometimes, functions that really don't produce an interesting result return
something anyway
...
Yet it would be unusual to find a program that actually checked this
...
A common question concerning function return values
is, "Don't I have to assign this value to some variable since a value is being returned?"
The answer is no
...
Consider the following program, which uses the function mul( ):
#include
In line 2, the return value is not
actually assigned, but it is used by the printf( ) function
...
Returning Pointers
Although functions that return pointers are handled just like any other type of
function, a few important concepts need to be discussed
...
They are the
memory addresses of a certain type of data
...
For example, if an integer pointer is
incremented, it will contain a value that is 4 greater than its previous value (assuming
4-byte integers)
...
Since the length of different data types
may differ, the compiler must know what type of data the pointer is pointing to
...
For example, you should not use a return type of int * to return
a char * pointer!
To return a pointer, a function must be declared as having a pointer return type
...
*/
char *match(char c, char *s)
{
while(c!=*s && *s) s++;
return(s);
}
If no match is found, a pointer to the null terminator is returned
...
h>
char *match(char c, char *s);
/* prototype */
int main(void)
{
char s[80], *p, ch;
gets(s);
ch = getchar();
p = match(ch, s);
if(*p) /* there is a match */
printf("%s ", p);
else
printf("No match found
...
If the character is in the string, the
program prints the string from the point of match
...
Functions of Type void
One of void's uses is to explicitly declare functions that do not return values
...
For example,
the function print_vertical( ) prints its string argument vertically down the side of
the screen
...
void print_vertical(char *str)
{
while(*str)
printf("%c\n", *str++);
}
Here is an example that uses print_vertical( )
...
h>
void print_vertical(char *str);
/* prototype */
Chapter 6:
Functions
int main(int argc, char *argv[])
{
if(argc > 1) print_vertical(argv[1]);
return 0;
}
void print_vertical(char *str)
{
while(*str)
printf("%c\n", *str++);
}
One last point: Early versions of C did not define the void keyword
...
Therefore, don't be surprised to see many examples of this in older code
...
Returning a value from main( ) is the equivalent of calling exit( )
with the same value
...
In practice, most C/C++ compilers
automatically return 0, but do not rely on this if portability is a concern
...
A function is said to be recursive if a statement in
the body of the function calls itself
...
A simple example of a recursive function is factr( ), which computes the factorial of
an integer
...
For example, 3 factorial is 1 x 2 x 3, or 6
...
It uses a loop that runs from 1 to
n and progressively multiplies each number by the moving product
...
When factr( ) is
called with an argument of 1, the function returns 1
...
To evaluate this expression, factr( ) is called with n−1
...
Computing the factorial of 2, the first call to factr( ) causes a second, recursive call
with the argument of 1
...
The answer is then 2
...
(You might want to insert printf( ) statements into factr( ) to see the level of
each call and what the intermediate answers are
...
A recursive call does not make a new copy of the function
...
As each recursive call returns, the old local
variables and parameters are removed from the stack and execution resumes at the
point of the function call inside the function
...
Most recursive routines do not significantly reduce code size or improve memory
utilization
...
In
fact, many recursive calls to a function could cause a stack overrun
...
However, you probably will not
have to worry about this unless a recursive function runs wild
...
For example, the quicksort algorithm is
difficult to implement in an iterative way
...
Finally, some people
seem to think recursively more easily than iteratively
...
If you don't, the function will never return once you call it
...
Use
printf( ) liberally during program development so that you can watch what is going
on and abort execution if you see a mistake
...
This is normally
accomplished using a function prototype
...
They were, however, added when C was standardized
...
Prototypes have always been required by C++
...
Prototypes enable both C and C++ to provide stronger type
checking, somewhat like that provided by languages such as Pascal
...
The
compiler will also catch differences between the number of arguments used to call a
function and the number of parameters in the function
...
,
type parm_nameN);
The use of parameter names is optional
...
The following program illustrates the value of function prototypes
...
(It is illegal to convert an integer into a pointer
...
*/
void sqr_it(int *i); /* prototype */
int main(void)
{
155
156
C++: The Complete Reference
int x;
x = 10;
sqr_it(x);
/* type mismatch */
return 0;
}
void sqr_it(int *i)
{
*i = *i * *i;
}
A function's definition can also serve as its prototype if the definition occurs prior
to the function's first use in the program
...
#include
*/
void f(int a, int b)
{
printf("%d ", a % b);
}
int main(void)
{
f(10,3);
return 0;
}
In this example, since f( ) is defined prior to its use in main( ), no separate
prototype is required
...
The programs in this book include a separate prototype for
each function because that is the way C/C++ code is normally written in practice
...
Because of the need for compatibility with the original version of C, there is a
small but important difference between how C and C++ handle the prototyping of a
Chapter 6:
Functions
function that has no parameters
...
For example,
int f(); /* C++ prototype for a function with no parameters */
However, in C this prototype means something different
...
As far as the
compiler is concerned, the function could have several parameters or no parameters
...
For example, here is f( )'s prototype as it would appear in a C program
...
In C++, the use of void inside an empty parameter list
is still allowed, but is redundant
...
Function prototypes help you trap bugs before they occur
...
One last point: Since early versions of C did not support the full prototype syntax,
prototypes are technically optional in C
...
If you are porting older C code to C++, you may need to add full function
prototypes before it will compile
...
This means that every function in a C++ program must be
fully prototyped
...
To
accomplish this, you must include the appropriate header for each library function
...
In C, all headers are files
that use the
...
In C++, headers may be either separate files or built into
the compiler itself
...
For example,
stdio
...
The headers for the standard library are described in
Part Three
...
The most
common example is printf( )
...
For example, this prototype specifies that func( )
will have at least two integer parameters and an unknown number (including 0)
of parameters after that
...
);
This form of declaration is also used by a function's definition
...
For example, this is incorrect:
int func(
...
This early approach is sometimes called the classic form
...
Standard C supports
both forms, but strongly recommends the modern form
...
However, you should know the old-style
form because many older C programs still use it
...
The general form of the old-style parameter definition is
type func_name(parm1, parm2,
...
...
type parmN;
{
function code
}
Chapter 6:
Functions
For example, this modern declaration:
float f(int a, int b, char ch)
{
/*
...
*/
}
Notice that the old-style form allows the declaration of more than one parameter in a
list after the type name
...
Implementation Issues
There are a few important things to remember about functions that affect their
efficiency and usability
...
Parameters and General-Purpose Functions
A general-purpose function is one that will be used in a variety of situations, perhaps
by many different programmers
...
All of the information a function needs should be passed
to it by its parameters
...
Besides making your functions general purpose, parameters keep your code
readable and less susceptible to bugs resulting from side effects
...
However, in certain specialized applications, you may need to eliminate
a function and replace it with inline code
...
For this reason,
inline code is often used instead of function calls when execution time is critical
...
First, a CALL instruction
takes time to execute
...
For most applications, this very slight increase in
execution time is of no significance
...
For example, the
following are two versions of a program that prints the square of the numbers from 1
to 10
...
in line
#include
h>
int sqr(int a);
int main(void)
{
int x;
for(x=1; x<11; ++x)
printf("%d", x*x);
for(x=1; x<11; ++x)
printf("%d", sqr(x));
return 0;
}
return 0;
}
int sqr(int a)
{
return a*a;
}
Note
In C++, the concept of inline functions is expanded and formalized
...
C++
Chapter 7
Structures, Unions,
Enumerations, and
User-Defined Types
161
162
C++: The Complete Reference
T
he C language gives you five ways to create a custom data type:
1
...
(The terms aggregate or conglomerate are also commonly
used
...
The bit-field, which is a variation on the structure and allows easy access to
individual bits
...
The union, which enables the same piece of memory to be defined as two or
more different types of variables
...
The enumeration, which is a list of named integer constants
...
The typedef keyword, which defines a new name for an existing type
...
The other methods of creating custom data types are described here
...
This chapter discusses only their C-like, non-object-oriented features
...
Structures
A structure is a collection of variables referenced under one name, providing a
convenient means of keeping related information together
...
The variables that make up the structure are called members
...
)
Generally, all of the members of a structure are logically related
...
The following code fragment shows how to declare a structure that defines
the name and address fields
...
struct addr
{
char name[30];
char street[40];
char city[20];
char state[3];
unsigned long int zip;
};
Chapter 7:
Structures, Unions, Enumerations, and User-Defined Types
Notice that the declaration is terminated by a semicolon
...
The type name of the structure is addr
...
At this point, no variable has actually been created
...
When you define a structure, you are defining a compound variable type, not
a variable
...
In C, to
declare a variable (i
...
, a physical object) of type addr, write
struct addr addr_info;
This declares a variable of type addr called addr_info
...
addr addr_info;
As you can see, the keyword struct is not needed
...
The reason for this difference is that
in C, a structure's name does not define a complete type name
...
In C, you must precede the tag with the keyword
struct when declaring variables
...
Keep in mind, however,
that it is still perfectly legal to use the C-style declaration in a C++ program
...
Just remember that C++ allows the shorter form
...
Figure 7-1
shows how addr_info appears in memory assuming 1-byte characters and 4-byte
long integers
...
For example,
struct addr {
char name[30];
char street[40];
char city[20];
char state[3];
unsigned long int zip;
} addr_info, binfo, cinfo;
defines a structure type called addr and declares variables addr_info, binfo, and cinfo
of that type
...
The addr_info structure in memory
If you only need one structure variable, the structure type name is not needed
...
The general form of a structure declaration is
struct struct-type-name {
type member-name;
type member-name;
type member-name;
...
...
Chapter 7:
Structures, Unions, Enumerations, and User-Defined Types
Accessing Structure Members
Individual members of a structure are accessed through the use of the
...
For example, the following code assigns the ZIP
code 12345 to the zip field of the structure variable addr_info declared earlier:
addr_info
...
The general form for accessing a member of a structure is
structure-name
...
zip);
This prints the ZIP code contained in the zip member of the structure variable
addr_info
...
name can be used to call
gets( ), as shown here:
gets(addr_info
...
Since name is a character array, you can access the individual characters of
addr_info
...
For example, you can print the contents of
addr_info
...
name[t]; ++t)
putchar(addr_info
...
That is, you do not need to assign the
value of each member separately
...
h>
int main(void)
165
166
C++: The Complete Reference
{
struct {
int a;
int b;
} x, y;
x
...
a);
return 0;
}
After the assignment, y
...
Arrays of Structures
Perhaps the most common usage of structures is in arrays of structures
...
For example, to declare a 100-element array of structures of
type addr, defined earlier, write
struct addr addr_info[100];
This creates 100 sets of variables that are organized as defined in the structure addr
...
For example, to print the
ZIP code of structure 3, write
printf("%d", addr_info[2]
...
Passing Structures to Functions
This section discusses passing structures and their members to functions
...
Therefore, you are passing a simple
variable (unless, of course, that element is compound, such as an array)
...
x);
func2(mike
...
z);
func4(mike
...
s[2]);
/*
/*
/*
/*
/*
passes
passes
passes
passes
passes
character value of x */
integer value of y */
float value of z */
address of string s */
character value of s[2] */
If you wish to pass the address of an individual structure member, put the & operator
before the structure name
...
x);
func2(&mike
...
z);
func4(mike
...
s[2]);
/*
/*
/*
/*
/*
passes
passes
passes
passes
passes
address
address
address
address
address
of
of
of
of
of
character x */
integer y */
float z */
string s */
character s[2] */
Remember that the & operator precedes the structure name, not the individual
member name
...
Passing Entire Structures to Functions
When a structure is used as an argument to a function, the entire structure is passed
using the standard call-by-value method
...
When using a structure as a parameter, remember that the type of the argument
must match the type of the parameter
...
#include
*/
struct struct_type {
int a, b;
char ch;
} ;
void f1(struct struct_type parm);
int main(void)
{
struct struct_type arg;
arg
...
a);
}
As this program illustrates, if you will be declaring parameters that are structures,
you must make the declaration of the structure type global so that all parts of your
program can use it
...
As just stated, when passing structures, the type of the argument must match
the type of the parameter
...
For example, the following version of the preceding
program is incorrect and will not compile because the type name of the argument
used to call f1( ) differs from the type name of its parameter
...
*/
#include
*/
struct struct_type {
int a, b;
char ch;
} ;
/* Define a structure similar to struct_type,
but with a different name
...
a = 1000;
f1(arg); /* type mismatch */
return 0;
}
void f1(struct struct_type2 parm)
{
printf("%d", parm
...
However, there are some special aspects to structure pointers that
you should know
...
For example, assuming the previously defined structure addr, the
following declares addr_pointer as a pointer to data of that type:
struct addr *addr_pointer;
Remember, in C++ it is not necessary to precede this declaration with the keyword
struct
...
This chapter covers the first use
...
(Recall that arguments are passed to functions on the stack
...
If the structure contains
many members, however, or if some of its members are arrays, run-time performance
may degrade to unacceptable levels
...
When a pointer to a structure is passed to a function, only the address of the
structure is pushed on the stack
...
A second
advantage, in some cases, is when a function needs to reference the actual structure
used as the argument, instead of a copy
...
To find the address of a structure variable, place the & operator before the
structure's name
...
To access the members of a structure using a pointer to that structure, you must
use the −> operator
...
The arrow is used in place of the dot operator when you are
accessing a structure member through a pointer to the structure
...
/* Display a software timer
...
h>
#define DELAY 128000
struct my_time {
int hours;
int minutes;
int seconds;
} ;
void display(struct my_time *t);
void update(struct my_time *t);
void delay(void);
int main(void)
{
struct my_time systime;
systime
...
minutes = 0;
systime
...
As you can see, a global structure called my_time is defined but no variable is
declared
...
This means that systime is known directly only to the main( ) function
...
In both functions, their arguments are
declared as a pointer to a my_time structure
...
Because update( ) receives a pointer to the systime structure, it can update its value
...
Remember, use the dot operator to access structure elements when operating on
the structure itself
...
Arrays and Structures Within Structures
A member of a structure may be either a simple or compound type
...
You have already seen one type of compound element: the character arrays used in
addr
...
A member of a structure that is an array is treated as you might expect from
the earlier examples
...
a[3][7]
When a structure is a member of another structure, it is called a nested structure
...
The first is a structure
of type addr, which contains an employee's address
...
The following code fragment assigns 93456 to the zip element
of address
...
address
...
Standard C specifies that structures may be nested to at least 15 levels
...
Bit-Fields
Unlike some other computer languages, C/C++ has a built-in feature called a bit-field
that allows you to access a single bit
...
s Certain devices transmit status information encoded into one or more bits
within a byte
...
Although these tasks can be performed using the bitwise operators, a bit-field can
add more structure (and possibly efficiency) to your code
...
In fact,
a bit-field is really just a special type of structure member that defines how long,
in bits, the field is to be
...
...
type nameN : length;
} variable_list;
Here, type is the type of the bit-field and length is the number of bits in the field
...
Bit-fields of length
1 should be declared as unsigned, because a single bit cannot have a sign
...
For example, the status port of a serial communications adapter might return a
status byte organized like this:
Chapter 7:
Structures, Unions, Enumerations, and User-Defined Types
Bit
Meaning When Set
0
Change in clear-to-send line
1
Change in data-set-ready
2
Trailing edge detected
3
Change in receive line
4
Clear-to-send
5
Data-set-ready
6
Telephone ringing
7
Received signal
You can represent the information in a status byte using the following bit-field:
struct status_type {
unsigned delta_cts:
unsigned delta_dsr:
unsigned tr_edge:
unsigned delta_rec:
unsigned cts:
unsigned dsr:
unsigned ring:
unsigned rec_line:
} status;
1;
1;
1;
1;
1;
1;
1;
1;
You might use a routine similar to that shown here to enable a program to determine
when it can send or receive data
...
cts) printf("clear to send");
if(status
...
For example, this code fragment clears the ring field:
status
...
However, if the structure is referenced through a pointer, you must use the −> operator
...
This makes it easy to reach the bit you
want, bypassing unused ones
...
It is valid to mix normal structure members with bit-fields
...
Without the bit-field, this information would have taken 3 bytes
...
You cannot take the address of a bit-field
...
They cannot be declared as static
...
Other restrictions may be imposed by various specific implementations, so check the
user manual for your compiler
...
Declaring a union is similar to
declaring a structure
...
...
} union-variables;
For example:
union u_type {
int i;
char ch;
};
This declaration does not create any variables
...
In C, to declare a union variable called cnvt of type u_type using the
definition just given, write
union u_type cnvt;
When declaring union variables in C++, you need use only the type name—
you don't need to precede it with the keyword union
...
In C++, the name of a union defines a complete type name
...
(This is similar to the situation
with structures described earlier
...
In cnvt, both integer i and character ch share the same memory location
...
Figure 7-2
shows how i and ch share the same address
...
When a union variable is declared, the compiler automatically allocates enough
storage to hold the largest member of the union
...
177
178
C++: The Complete Reference
Figure 7-2
...
If you are operating on the union directly,
use the dot operator
...
For example, to assign the integer 10 to element i of cnvt, write
cnvt
...
Because the compiler keeps track of the actual sizes of the union members,
no unnecessary machine dependencies are produced
...
Unions are used frequently when specialized type conversions are needed
because you can refer to the data held in the union in fundamentally different
ways
...
Chapter 7:
Structures, Unions, Enumerations, and User-Defined Types
To get an idea of the usefulness of a union when nonstandard type conversions
are needed, consider the problem of writing a short integer to a disk file
...
While you can write any type of data to a file using fwrite( ), using fwrite( )
incurs excessive overhead for such a simple operation
...
(This example assumes that short integers
are 2 bytes long
...
#include
tmp", "wb+");
putw(1000, fp);
fclose(fp);
/* write the value 1000 as an integer */
return 0;
}
int putw(short int num, FILE *fp)
{
union pw word;
179
180
C++: The Complete Reference
word
...
ch[0], fp); /* write first half */
return putc(word
...
Note
C++ supports a special type of union called an anonymous union which is
discussed in Part Two of this book
...
Enumerations are common in everyday life
...
The general form for enumerations is
enum enum-type-name { enumeration list } variable_list;
Here, both the type name and the variable list are optional
...
) The following code fragment defines an enumeration called coin:
enum coin { penny, nickel, dime, quarter,
half_dollar, dollar};
The enumeration type name can be used to declare variables of its type
...
enum coin money;
In C++, the variable money may be declared using this shorter form:
coin money;
Chapter 7:
Structures, Unions, Enumerations, and User-Defined Types
In C++, an enumeration name specifies a complete type
...
(This is similar to the situation
as it applies to structures and unions, described earlier
...
\n");
The key point to understand about an enumeration is that each of the symbols
stands for an integer value
...
Each symbol is given a value one greater than the symbol that precedes it
...
Therefore,
printf("%d %d", penny, dime);
displays 0 2 on the screen
...
Do this by following the symbol with an equal sign and an integer value
...
For example, the following code assigns the value of 100 to quarter:
enum coin { penny, nickel, dime, quarter=100,
half_dollar, dollar};
Now, the values of these symbols are
penny
0
nickel
1
dime
2
quarter
100
half_dollar
101
dollar
102
One common but erroneous assumption about enumerations is that the symbols
can be input and output directly
...
For example, the following code
fragment will not perform as desired:
181
182
C++: The Complete Reference
/* this will not work */
money = dollar;
printf("%s", money);
Remember, dollar is simply a name for an integer; it is not a string
...
Actually, creating code to input and output enumeration symbols is quite tedious
(unless you are willing to settle for their integer values)
...
For example, this code also
outputs the proper string:
char name[][12]={
"penny",
"nickel",
"dime",
"quarter",
"half_dollar",
"dollar"
Chapter 7:
Structures, Unions, Enumerations, and User-Defined Types
};
printf("%s", name[money]);
Of course, this only works if no symbol is initialized, because the string array must
be indexed starting at 0
...
An enumeration is often used to define a compiler's symbol table,
for example
...
Using sizeof to Ensure Portability
You have seen that structures and unions can be used to create variables of different
sizes, and that the actual size of these variables may change from machine to machine
...
This operator is especially useful where
structures or unions are concerned
...
For example,
183
184
C++: The Complete Reference
struct s {
char ch;
int i;
double f;
} s_var;
Here, sizeof(s_var) is at least 13 (8 + 4 + 1)
...
(A paragraph is 16 bytes
...
Since sizeof is a compile-time operator, all the information necessary to compute
the size of any variable is known at compile time
...
For example, consider
union u {
char ch;
int i;
double f;
} u_var;
Here, the sizeof(u_var) is 8
...
All that matters is the size of its largest member, because any union must
be as large as its largest element
...
You are not
actually creating a new data type, but rather defining a new name for an existing
type
...
If
you define your own type name for each machine-dependent data type used by your
program, then only the typedef statements have to be changed when compiling for a
new environment
...
The general form of the typedef
statement is
Chapter 7:
Structures, Unions, Enumerations, and User-Defined Types
typedef type newname;
where type is any valid data type and newname is the new name for this type
...
For example, you could create a new name for float by using
typedef float balance;
This statement tells the compiler to recognize balance as another name for float
...
Now that balance has been defined, it can be used in another typedef
...
Using typedef can make your code easier to read and easier to port to a new
machine, but you are not creating a new physical type
...
C++
Chapter 8
C-Style Console I/O
187
188
C++: The Complete Reference
++ supports two complete I/O systems
...
The second
is the object-oriented I/O system defined by C++
...
(Part Two examines C++ I/O
...
In C, input and output are accomplished through library functions
...
Technically, there is little distinction between console
I/O and file I/O, but conceptually they are in very different worlds
...
The next chapter presents the file I/O
system and describes how the two systems relate
...
Standard C++ does not define any functions that perform various
screen control operations (such as cursor positioning) or that display graphics,
because these operations vary widely between machines
...
Instead, the console
I/O functions perform only TTY-based output
...
And, of course, you may use
C++ to write Windows programs, but keep in mind that the C++ language does not
directly define functions that perform these tasks
...
h
...
This chapter refers to the console I/O functions as performing input from the
keyboard and output to the screen
...
Furthermore, standard input and standard output may be
redirected to other devices
...
C
An Important Application Note
Part One of this book uses the C-like I/O system because it is the only style of I/O
that is defined for the C subset of C++
...
For most C++ applications, you will want to use the
C++-specific I/O system, not the C I/O system described in this chapter
...
In this case, you will need to use the C-like I/O
functions
...
Also, many programs will be
hybrids of both C and C++ code
...
Thus, knowledge of both the C and the C++
Chapter 8:
C-Style Console I/O
I/O system will be necessary
...
s An understanding of the basic principles behind the C-like I/O system is
crucial to an understanding of the C++ object-oriented I/O system
...
)
s In certain situations (for example, in very short programs), it may be easier to
use C's non-object-oriented approach to I/O than it is to use the object-oriented
I/O defined by C++
...
If you don't know how to use the C I/O system, you will be limiting your
professional horizons
...
The getchar( )
function waits until a key is pressed and then returns its value
...
The putchar( ) function writes a character to the
screen at the current cursor position
...
However, you can assign this value to a char variable, as is usually done, because the
character is contained in the low-order byte
...
)
getchar( ) returns EOF if an error occurs
...
Only the low-order byte of its
parameter is actually output to the screen
...
(The EOF macro is defined in stdio
...
)
The following program illustrates getchar( ) and putchar( )
...
To stop the program, enter a period
...
h>
#include
\n");
do {
ch = getchar();
if(islower(ch)) ch = toupper(ch);
else ch = tolower(ch);
putchar(ch);
} while (ch != '
...
Normally, getchar( ) is implemented
in such a way that it buffers input until ENTER is pressed
...
Also, since getchar( ) inputs only one character each time it is called, line-buffering may
leave one or more characters waiting in the input queue, which is annoying in interactive
environments
...
Therefore, if the preceding program did not
behave as you expected, you now know why
...
If this is the case, you might want to use a different function
to read characters from the keyboard
...
Although
these functions are not defined by Standard C++, they are commonly used since
getchar( ) does not fill the needs of most programmers
...
h
...
For example,
in Microsoft's Visual C++, they are called _getch( ) and _getche( )
...
It does not echo the character to the screen
...
You will frequently see getche( ) or getch( ) used
instead of getchar( ) when a character needs to be read from the keyboard in an
interactive program
...
For example, the previous program is shown here using getch( ) instead of getchar( ):
#include
h>
#include
\n");
do {
ch = getch();
if(islower(ch)) ch = toupper(ch);
else ch = tolower(ch);
putchar(ch);
} while (ch != '
...
Input is
no longer line-buffered
...
Note
At the time of this writing, when using Microsoft's Visual C++ compiler,
_getche( ) and _getch( ) are not compatible with the standard C/C++
input functions, such as scanf( ) or gets( )
...
You will
need to examine the Visual C++ documentation for details
...
They enable you to read and write strings of characters
...
You may type characters at
the keyboard until you press ENTER
...
In fact, you
cannot use gets( ) to return a carriage return (although getchar( ) can do so)
...
The
prototype for gets( ) is
char *gets(char *str);
where str is a character array that receives the characters input by the user
...
The following program reads a string into the array str and prints its length:
#include
h>
int main(void)
{
char str[80];
gets(str);
printf("Length is %d", strlen(str));
return 0;
}
You need to be careful when using gets( ) because it performs no boundary checks on
the array that is receiving input
...
While gets( ) is fine for sample programs and simple utilities
that only you will use, you will want to avoid its use in commercial code
...
The puts( ) function writes its string argument to the screen followed by a newline
...
A call
to puts( ) requires far less overhead than the same call to printf( ) because puts( ) can
only output a string of charactersit cannot output numbers or do format
conversions
...
For
this reason, the puts( ) function is often used when it is important to have highly
optimized code
...
Otherwise, it
returns a nonnegative value
...
The following statement displays hello:
puts("hello");
Table 8-1 summarizes the basic console I/O functions
...
It prompts the user to enter a word and then checks
to see if the word matches one in its built-in database
...
Pay special attention to the indirection used
in this program
...
Notice that the list must be terminated by
two nulls
...
getche( )
Reads a character with echo; does not
wait for carriage return; not defined by
Standard C/C++, but a common extension
...
putchar( )
Writes a character to the screen
...
puts( )
Writes a string to the screen
...
The Basic I/O Functions
193
194
C++: The Complete Reference
/* A simple dictionary
...
h>
#include
h>
/* list of words and meanings */
char *dic[][40] = {
"atlas", "A volume of maps
...
",
"telephone", "A communication device
...
",
"", "" /* null terminate the list */
};
int main(void)
{
char word[80], ch;
char **p;
do {
puts("\nEnter word: ");
scanf("%s", word);
p = (char **)dic;
/* find matching word and print its meaning */
do {
if(!strcmp(*p, word)) {
puts("Meaning:");
puts(*(p+1));
break;
}
if(!strcmp(*p, word)) break;
p = p + 2; /* advance through the list */
} while(*p);
if(!*p) puts("Word not in dictionary
...
The printf( )
function writes data to the console
...
Both functions can operate on any of the built-in data types,
including characters, strings, and numbers
...
);
The printf( ) function returns the number of characters written or a negative value if an
error occurs
...
The first type is composed of
characters that will be printed on the screen
...
A format specifier begins
with a percent sign and is followed by the format code
...
For example, this printf( ) call
printf("I like %c%s", 'C', "++ very much!");
displays
I like C++ very much!
The printf( ) function accepts a wide variety of format specifiers, as shown in
Table 8-2
...
printf( ) Format Specifiers
195
196
C++: The Complete Reference
Code
Format
%i
Signed decimal integers
%e
Scientific notation (lowercase e)
%E
Scientific notation (uppercase E)
%f
Decimal floating point
%g
Uses %e or %f, whichever is shorter
%G
Uses %E or %F, whichever is shorter
%o
Unsigned octal
%s
String of characters
%u
Unsigned decimal integers
%x
Unsigned hexadecimal (lowercase letters)
%X
Unsigned hexadecimal (uppercase letters)
%p
Displays a pointer
%n
The associated argument must be a pointer to
an integer
...
%%
Prints a % sign
Table 8-2
...
This causes its matching argument to be
output, unmodified, to the screen
...
Printing Numbers
You may use either %d or %i to indicate a signed decimal number
...
To output an unsigned value, use %u
...
Chapter 8:
C-Style Console I/O
The %e and %E specifiers tell printf( ) to display a double argument in scientific
notation
...
dddddE+/−yy
If you want to display the letter "E" in uppercase, use the %E format; otherwise use %e
...
This causes printf( ) to select the format specifier that produces the shortest output
...
The
following program demonstrates the effect of the %g format specifier:
#include
0; f<1
...
1 10 100 1000 10000 100000 1e+006 1e+007 1e+008 1e+009
You can display unsigned integers in octal or hexadecimal format using %o and
%x, respectively
...
For uppercase, use the %X format specifier; for lowercase, use %x, as shown
here:
#include
This format specifier causes printf( ) to
display a machine address in a format compatible with the type of addressing used
by the computer
...
h>
int sample;
int main(void)
{
printf("%p", &sample);
return 0;
}
The %n Specifier
The %n format specifier is different from the others
...
In other
words, the value that corresponds to the %n format specifier must be a pointer to a
variable
...
Examine this
program to understand this somewhat unusual format code
...
h>
int main(void)
{
int count;
printf("this%n is a test\n", &count);
printf("%d", count);
return 0;
}
Chapter 8:
C-Style Console I/O
This program displays this is a test followed by the number 4
...
Format Modifiers
Many format specifiers may take modifiers that alter their meaning slightly
...
The format modifier goes between the percent sign and the
format code
...
The Minimum Field Width Specifier
An integer placed between the % sign and the format code acts as a minimum field width
specifier
...
If the string or number is longer than that minimum, it will still be printed in
full
...
If you wish to pad with 0's, place a 0
before the field width specifier
...
The following program demonstrates the
minimum field width specifier:
#include
12304;
printf("%f\n", item);
printf("%10f\n", item);
printf("%012f\n", item);
return 0;
}
This program produces the following output:
10
...
123040
00010
...
For example, the next program produces a table of squares and
cubes for the numbers between 1 and 19:
199
200
C++: The Complete Reference
#include
It
consists of a period followed by an integer
...
When you apply the precision specifier to floating-point data using the %f, %e,
or %E specifiers, it determines the number of decimal places displayed
...
4f displays a number at least ten characters wide with four decimal places
...
When the precision specifier is applied to %g or %G, it specifies the number of
significant digits
...
For
example, %5
...
If the string is longer than the maximum field width, the end characters will be
truncated
...
Leading zeros are added to achieve
the required number of digits
...
h>
int main(void)
{
printf("%
...
1234567);
printf("%3
...
15s\n", "This is a simple test
...
1235
00001000
This is a simpl
Justifying Output
By default, all output is right-justified
...
You can force output to
be left-justified by placing a minus sign directly after the %
...
2f leftjustifies a floating-point number with two decimal places in a 10-character field
...
h>
int main(void)
{
201
202
C++: The Complete Reference
printf("right-justified:%8d\n", 100);
printf("left-justified:%-8d\n", 100);
return 0;
}
Handling Other Data Types
There are two format modifiers that allow printf( ) to display short and long integers
...
The l (ell) modifier
tells printf( ) that a long data type follows
...
The h modifier instructs printf( ) to display a short integer
...
The L modifier may prefix the floating-point specifiers e, f, and g, and indicates that
a long double follows
...
Preceding g, G, f, E, or e specifiers with a # ensures that there will be a decimal
point even if there are no decimal digits
...
Preceding the o specifier
with # causes the number to be printed with a leading zero
...
Instead of constants, the minimum field width and precision specifiers may be
provided by arguments to printf( )
...
When the format string is scanned, printf( ) will match the * to an argument in the
order in which they occur
...
3
...
h>
int main(void)
{
printf("%x %#x\n", 10, 10);
printf("%*
...
34);
return 0;
}
Chapter 8:
C-Style Console I/O
printf("%*
...
3);
Figure 8-1
...
It can read all the built-in
data types and automatically convert numbers into the proper internal format
...
The prototype for scanf( ) is
int scanf(const char *control_string,
...
If an error occurs, scanf( ) returns EOF
...
The control string consists of three classifications of characters:
s Format specifiers
s White-space characters
s Non-white-space characters
Let's take a look at each of these now
...
These codes are listed in Table 8-3
...
Let's look
at some examples
...
To read a floating-point number
represented in either standard or scientific notation, use %e, %f, or %g
...
The %x may be in either upper- or
203
204
C++: The Complete Reference
lowercase
...
The following program reads an octal and hexadecimal
number:
#include
%d
Read a decimal integer
...
%e
Read a floating-point number
...
%g
Read a floating-point number
...
%s
Read a string
...
%p
Read a pointer
...
%u
Read an unsigned decimal integer
...
%%
Read a percent sign
...
scanf( ) Format Specifiers
Chapter 8:
C-Style Console I/O
The scanf( ) function stops reading a number when the first nonnumeric character is
encountered
...
For example,
unsigned num;
scanf("%u", &num);
reads an unsigned number and puts its value into num
...
You can also use scanf( ) for this purpose if
you use the %c format specifier
...
This makes
it somewhat troublesome in an interactive environment
...
For example, with an input stream of "x y," this code fragment
scanf("%c%c%c", &a, &b, &c);
returns with the character x in a, a space in b, and the character y in c
...
Using %s causes scanf( ) to read characters until it encounters a
white-space character
...
As it
applies to scanf( ), a white-space character is either a space, a newline, a tab, a vertical
tab, or a form feed
...
This means that you cannot
use scanf( ) to read a string like "this is a test" because the first space terminates the
reading process
...
#include
Inputting an Address
To input a memory address, use the %p format specifier
...
For example,
this program inputs an address and then displays what is at that memory address:
#include
Using a Scanset
The scanf( ) function supports a general-purpose format specifier called a scanset
...
When scanf( ) processes a scanset, it will input
characters as long as those characters are part of the set defined by the scanset
...
You define a scanset by putting the characters to scan for
inside square brackets
...
For example, the following scanset tells scanf( ) to read only the characters X, Y,
and Z
...
Upon return from scanf( ), this array will contain a null-terminated string that consists
of the characters that have been read
...
h>
int main(void)
{
int i;
char str[80], str2[80];
scanf("%d%[abcdefg]%s", &i, str, str2);
printf("%d %s %s", i, str, str2);
return 0;
}
Enter 123abcdtye followed by ENTER
...
Because the "t" is not part of the scanset, scanf( ) stops reading characters into str
when it encounters the "t
...
You can specify an inverted set if the first character in the set is a ^
...
In most implementations you can specify a range using a hyphen
...
If you want
to scan for both upper- and lowercase letters, you must specify them individually
...
A white-space character is either a
207
208
C++: The Complete Reference
space, a tab, vertical tab, form feed, or a newline
...
Non-White-Space Characters in the Control String
A non-white-space character in the control string causes scanf( ) to read and discard
matching characters in the input stream
...
If the specified
character is not found, scanf( ) terminates
...
You Must Pass scanf( ) Addresses
All the variables used to receive values through scanf( ) must be passed by their
addresses
...
Recall that this is one way of creating a call by reference, and it allows
a function to alter the contents of an argument
...
So, to read a string into the character array
str, you would use
scanf("%s", str);
In this case, str is already a pointer and need not be preceded by the & operator
...
The format specifiers can include a maximum field length modifier
...
For example, to read no more than 20 characters into
str, write
scanf("%20s", str);
If the input stream is greater than 20 characters, a subsequent call to input begins
where this call leaves off
...
This means
that the remaining characters, UVWXYZ, have not yet been used
...
Input for a field may terminate before the
maximum field length is reached if a white space is encountered
...
To read a long integer, put an l (ell) in front of the format specifier
...
These modifiers can be used with the
d, i, o, u, and x format codes
...
If you
put an l (ell) in front of one of these specifiers, scanf( ) assigns the data to a double
...
Suppressing Input
You can tell scanf( ) to read a field but not assign it to any variable by preceding that
field's format code with an *
...
The comma would be correctly read, but not
assigned to anything
...
209
This page intentionally left blank
...
As explained in Chapter 8, C++ supports
two complete I/O systems: the one inherited from C and the object-oriented
system defined by C++
...
(The C++ file
system is discussed in Part Two
...
T
C Versus C++ File I/O
There is sometimes confusion over how C's file system relates to C++
...
Thus, if you will be porting older C code
to C++, you will not have to change all of your I/O routines right away
...
The C++ I/O system completely duplicates the functionality of the C
I/O system and renders the C file system redundant
...
Of course,
most C++ programmers elect to use the C++ I/O system for reasons that are made
clear in Part Two of this book
...
The C I/O system supplies a consistent
interface to the programmer independent of the actual device being accessed
...
This abstraction is called a stream and the actual device is called a file
...
Note
The concept of streams and files is also important to the C++ I/O system discussed
in Part Two
...
Even though each device is very different, the
buffered file system transforms each into a logical device called a stream
...
Because streams are largely device independent, the same function
that can write to a disk file can also be used to write to another type of device, such as
the console
...
Chapter 9:
File I/O
Text Streams
A text stream is a sequence of characters
...
However,
the newline character is optional on the last line
...
) In a text stream, certain character
translations may occur as required by the host environment
...
Therefore, there may not be a
one-to-one relationship between the characters that are written (or read) and those
on the external device
...
Binary Streams
A binary stream is a sequence of bytes that have a one-to-one correspondence to those
in the external devicethat is, no character translations occur
...
However,
an implementation-defined number of null bytes may be appended to a binary stream
...
Files
In C/C++, a file may be anything from a disk file to a terminal or printer
...
Once a file is open,
information may be exchanged between it and your program
...
For example, a disk file can support random
access while some printers cannot
...
If the file can support position requests, opening that file also initializes the file
position indicator to the start of the file
...
You disassociate a file from a specific stream with a close operation
...
This process is generally referred to as flushing the stream, and
guarantees that no information is accidentally left in the disk buffer
...
Files are not closed when a
program terminates abnormally, such as when it crashes or when it calls abort( )
...
Never modify this file control block
...
Just remember that its main purpose is to provide a
consistent interface
...
The I/O system automatically converts the
raw input or output from each device into an easily managed stream
...
The most common of
these are shown in Table 9-1
...
h
...
Name
Function
fopen( )
Opens a file
...
putc( )
Writes a character to a file
...
getc( )
Reads a character from a file
...
fgets( )
Reads a string from a file
...
fseek( )
Seeks to a specified byte in a file
...
fprintf( )
Is to a file what printf( ) is to the console
...
feof( )
Returns true if end-of-file is reached
...
rewind( )
Resets the file position indicator to the
beginning of the file
...
fflush( )
Flushes a file
...
Commonly Used C File-System Functions
Chapter 9:
File I/O
The header file stdio
...
The size_t type is some
variety of unsigned integer, as is fpos_t
...
Also defined in stdio
...
The ones relevant to this
chapter are NULL, EOF, FOPEN_MAX, SEEK_SET, SEEK_CUR, and SEEK_END
...
The EOF macro is generally defined as −1
and is the value returned when an input function tries to read past the end of the file
...
The other macros are used with fseek( ), which is the function
that performs random access on a file
...
A file pointer is a
pointer to a structure of type FILE
...
In essence,
the file pointer identifies a specific file and is used by the associated stream to direct the
operation of the I/O functions
...
To obtain a file pointer variable, use a statement like this:
FILE *fp;
Opening a File
The fopen( ) function opens a stream for use and links a file with that stream
...
Most often (and for the rest of this
discussion), the file is a disk file
...
The string pointed to by mode determines how the file
will be opened
...
Strings like "r+b" may also be
represented as "rb+
...
w
Create a text file for writing
...
Table 9-2
...
wb
Create a binary file for writing
...
r+
Open a text file for read/write
...
a+
Append or create a text file for
read/write
...
w+b
Create a binary file for read/write
...
Table 9-2
...
Your program should
never alter the value of this pointer
...
The following code uses fopen( ) to open a file named TEST for output
...
\n");
exit(1);
}
Chapter 9:
File I/O
This method will detect any error in opening a file, such as a write-protected or a full
disk, before your program attempts to write to it
...
Although most of the file modes are self-explanatory, a few comments are in
order
...
When opening a file using append mode, if the file does not exist, it will be
created
...
The original contents will remain unchanged
...
If it does exist, the
contents of the original file will be destroyed and a new file created
...
Further, if the file already exists, opening it with w+ destroys its contents;
opening it with r+ does not
...
In most
implementations, in text mode, carriage return/linefeed sequences are translated to
newline characters on input
...
No such translations occur on binary files
...
This value will usually be at least 8, but you must check your compiler manual for its
exact value
...
It writes
any data still remaining in the disk buffer to the file and does a formal operatingsystem-level close on the file
...
fclose( ) also frees the file control block associated with the stream, making it available
for reuse
...
The fclose( ) function has this prototype:
int fclose(FILE *fp);
where fp is the file pointer returned by the call to fopen( )
...
The function returns EOF if an error occurs
...
Generally, fclose( ) will fail only when a disk has been prematurely removed from the
drive or there is no more space on the disk
...
(Actually, putc( ) is usually implemented as a macro
...
This book uses
putc( ), but you can use fputc( ) if you like
...
The prototype of this function is
int putc(int ch, FILE *fp);
where fp is the file pointer returned by fopen( ) and ch is the character to be output
...
For historical reasons, ch is defined
as an int but only the low-order byte is written
...
Otherwise, it
returns EOF
...
Both
are defined to preserve compatibility with older versions of C
...
The getc( ) function reads characters from a file opened in read mode by fopen( )
...
getc( ) returns an integer,
but the character is contained in the low-order byte
...
The getc( ) function returns an EOF when the end of the file has been reached
...
You can use ferror( ) to determine
precisely what has occurred
...
The following program, KTOD, is a simple example of using putc( ), fopen( ),
Chapter 9:
File I/O
and fclose( )
...
The filename is specified from the command line
...
/* KTOD: A key to disk program
...
h>
#include
\n");
exit(1);
}
if((fp=fopen(argv[1], "w"))==NULL) {
printf("Cannot open file
...
/* DTOS: A program that reads files and displays them
on the screen
...
h>
#include
\n");
exit(1);
}
if((fp=fopen(argv[1], "r"))==NULL) {
printf("Cannot open file
...
Then read its
contents using DTOS
...
However, testing the value returned by getc( ) may not be the best way to determine
when you have arrived at the end of a file
...
When a file is opened for binary input, an integer value that will
test equal to EOF may be read
...
Second, getc( ) returns EOF when it fails and when it reaches the end of the file
...
To solve these
Chapter 9:
File I/O
problems, the C file system includes the function feof( ), which determines when the
end of the file has been encountered
...
Therefore, the following routine reads a binary file until the end of the file is
encountered:
while(!feof(fp)) ch = getc(fp);
Of course, you can apply this method to text files as well as binary files
...
The files are opened in binary mode and feof( ) checks for the end of the file
...
*/
#include
h>
int main(int argc, char *argv[])
{
FILE *in, *out;
char ch;
if(argc!=3) {
printf("You forgot to enter a filename
...
\n");
exit(1);
}
if((out=fopen(argv[2], "wb")) == NULL) {
printf("Cannot open destination file
...
*/
while(!feof(in)) {
ch = getc(in);
221
222
C++: The Complete Reference
if(!feof(in)) putc(ch, out);
}
fclose(in);
fclose(out);
return 0;
}
Working with Strings: fputs( ) and fgets( )
In addition to getc( ) and putc( ), the C file system supports the related functions
fgets( ) and fputs( ), which read and write character strings from and to a disk file
...
They have the following prototypes:
int fputs(const char *str, FILE *fp);
char *fgets(char *str, int length, FILE *fp);
The fputs( ) function writes the string pointed to by str to the specified stream
...
The fgets( ) function reads a string from the specified stream until either a newline
character is read or length −1 characters have been read
...
The resultant string will be null terminated
...
The following program demonstrates fputs( )
...
To terminate the program, enter a blank line
...
#include
h>
#include
\n");
Chapter 9:
File I/O
exit(1);
}
do {
printf("Enter a string (CR to quit):\n");
gets(str);
strcat(str, "\n"); /* add a newline */
fputs(str, fp);
} while(*str!='\n');
return 0;
}
rewind( )
The rewind( ) function resets the file position indicator to the beginning of the file
specified as its argument
...
Its prototype is
void rewind(FILE *fp);
where fp is a valid file pointer
...
To accomplish this, the
program rewinds the file after input is complete and then uses fgets( ) to read back
the file
...
#include
h>
#include
\n");
exit(1);
}
223
224
C++: The Complete Reference
do {
printf("Enter a string (CR to quit):\n");
gets(str);
strcat(str, "\n"); /* add a newline */
fputs(str, fp);
} while(*str!='\n');
/* now, read and display the file */
rewind(fp); /* reset file position indicator to
start of the file
...
The
ferror( ) function has this prototype:
int ferror(FILE *fp);
where fp is a valid file pointer
...
Because each file operation sets the error
condition, ferror( ) should be called immediately after each file operation; otherwise,
an error may be lost
...
The tab size is defined by TAB_SIZE
...
To use the program, specify the
names of the input and output files on the command line
...
*/
#include
h>
#define TAB_SIZE 8
Chapter 9:
#define IN 0
#define OUT 1
void err(int e);
int main(int argc, char *argv[])
{
FILE *in, *out;
int tab, i;
char ch;
if(argc!=3) {
printf("usage: detab
exit(1);
}
if((in = fopen(argv[1], "rb"))==NULL) {
printf("Cannot open %s
...
\n", argv[1]);
exit(1);
}
tab = 0;
do {
ch = getc(in);
if(ferror(in)) err(IN);
/* if tab found, output appropriate number of spaces */
if(ch=='\t') {
for(i=tab; i<8; i++) {
putc(' ', out);
if(ferror(out)) err(OUT);
}
tab = 0;
}
else {
putc(ch, out);
if(ferror(out)) err(OUT);
File I/O
225
226
C++: The Complete Reference
tab++;
if(tab==TAB_SIZE) tab = 0;
if(ch=='\n' || ch=='\r') tab = 0;
}
} while(!feof(in));
fclose(in);
fclose(out);
return 0;
}
void err(int e)
{
if(e==IN) printf("Error on input
...
\n");
exit(1);
}
Erasing Files
The remove( ) function erases the specified file
...
The following program erases the file specified on the command line
...
A utility like this might be useful to new
computer users
...
*/
#include
h>
#include
\n");
exit(1);
}
return 0;
}
Flushing a Stream
If you wish to flush the contents of an output stream, use the fflush( ) function, whose
prototype is shown here:
int fflush(FILE *fp);
This function writes the contents of any buffered data to the file associated with fp
...
The fflush( ) function returns 0 if successful; otherwise, it returns EOF
...
These functions allow the reading and writing
of blocks of any type of data
...
For fwrite( ), buffer is a pointer to the information that will be written to the
file
...
(Remember, the type size_t is defined as some
type of unsigned integer
...
The fread( ) function returns the number of items read
...
The fwrite( ) function returns
the number of items written
...
227
228
C++: The Complete Reference
Using fread( ) and fwrite( )
As long as the file has been opened for binary data, fread( ) and fwrite( ) can read
and write any type of information
...
Notice how it
uses sizeof to determine the length of each data type
...
*/
#include
h>
int main(void)
{
FILE *fp;
double d = 12
...
\n");
exit(1);
}
fwrite(&d, sizeof(double), 1, fp);
fwrite(&i, sizeof(int), 1, fp);
fwrite(&l, sizeof(long), 1, fp);
rewind(fp);
fread(&d, sizeof(double), 1, fp);
fread(&i, sizeof(int), 1, fp);
fread(&l, sizeof(long), 1, fp);
printf("%f %d %ld", d, i, l);
fclose(fp);
return 0;
}
As this program illustrates, the buffer can be (and often is) merely the memory used to
hold a variable
...
In the real world, however, you should check their return values for errors
...
For example, given this
structure:
struct struct_type {
float balance;
char name[80];
} cust;
the following statement writes the contents of cust to the file pointed to by fp
...
Its prototype is shown here:
int fseek(FILE *fp, long numbytes, int origin);
Here, fp is a file pointer returned by a call to fopen( )
...
To
seek from the current position, use SEEK_CUR; and to seek from the end of the file,
use SEEK_END
...
The following program illustrates fseek( )
...
Specify the filename and then the byte to seek to on the
command line
...
h>
#include
\n");
exit(1);
}
if(fseek(fp, atol(argv[2]), SEEK_SET)) {
printf("Seek error
...
\n", atol(argv[2]), getc(fp));
fclose(fp);
return 0;
}
You can use fseek( ) to seek in multiples of any type of data by simply multiplying
the size of the data by the number of the item you want to reach
...
To seek to the
tenth address in the file that holds the addresses, use this statement:
fseek(fp, 9*sizeof(struct list_type), SEEK_SET);
You can determine the current location of a file using ftell( )
...
If a failure
occurs, it returns −1
...
The reason
for this is simple
...
The only time you should use
Chapter 9:
File I/O
fseek( ) with a text file is when seeking to a position previously determined by ftell( ),
using SEEK_SET as the origin
...
There is no inherent restriction about random access on files
containing text
...
fprintf( ) and fscanf( )
In addition to the basic I/O functions already discussed, the C I/O system includes
fprintf( ) and fscanf( )
...
The prototypes of fprintf( ) and fscanf( ) are
int fprintf(FILE *fp, const char *control_string,
...
);
where fp is a file pointer returned by a call to fopen( )
...
As an example, the following program reads a string and an integer from the
keyboard and writes them to a disk file called TEST
...
After running this program, examine the
TEST file
...
/* fscanf() - fprintf() example */
#include
h>
#include
\n");
exit(1);
}
printf("Enter a string and a number: ");
fscanf(stdin, "%s%d", s, &t); /* read from keyboard */
231
232
C++: The Complete Reference
fprintf(fp, "%s %d", s, t); /* write to file */
fclose(fp);
if((fp=fopen("test","r")) == NULL) {
printf("Cannot open file
...
Because formatted ASCII data is being written as it would appear on the screen
(instead of in binary), extra overhead is incurred with each call
...
The Standard Streams
As it relates to the C file system, when a program starts execution, three streams are
opened automatically
...
Normally, these streams refer to the console, but they may be
redirected by the operating system to some other device in environments that support
redirectable I/O
...
)
Because the standard streams are file pointers, they may be used by the C I/O
system to perform I/O operations on the console
...
Chapter 9:
File I/O
You may use stdin, stdout, and stderr as file pointers in any function that uses a
variable of type FILE *
...
As mentioned earlier in this
chapter, when using gets( ) it is possible to overrun the array that is being used to
receive the characters entered by the user because gets( ) provides no bounds checking
...
The only trouble
is that fgets( ) does not remove the newline character and gets( ) does, so you will have
to manually remove it, as shown in the following program
...
h>
#include
Also, just as these file pointers are
created automatically at the start of your program, they are closed automatically at the
end; you should not try to close them
...
The console I/O functions described in Chapter 8 actually direct their I/O operations to
either stdin or stdout
...
The reason they exist is as a convenience to you, the
programmer
...
However, what might surprise you is that you can perform disk
file I/O using console I/O functions, such as printf( )! This is because all of the console
I/O functions operate on stdin and stdout
...
For example, consider this program:
#include
If you execute TEST normally, it displays
its prompt on the screen, reads a string from the keyboard, and displays that string on
the display
...
For example, in a DOS or Windows
environment, executing TEST like this:
TEST > OUTPUT
causes the output of TEST to be written to a file called OUTPUT
...
When a program terminates, any redirected streams are reset to their default status
...
This function
associates an existing stream with a new file
...
Its prototype is
FILE *freopen(const char *filename, const char *mode, FILE *stream);
where filename is a pointer to the filename you wish associated with the stream
pointed to by stream
...
freopen( ) returns stream if successful
or NULL on failure
...
h>
int main(void)
{
char str[80];
freopen("OUTPUT", "w", stdout);
printf("Enter a string: ");
gets(str);
printf(str);
return 0;
}
In general, redirecting the standard streams by using freopen( ) is useful in special
situations, such as debugging
...
235
This page intentionally left blank
...
These are called preprocessor directives, and although not
actually part of the C or C++ language per se, they expand the scope of the
programming environment
...
Y
The Preprocessor
Before beginning, it is important to put the preprocessor in historical perspective
...
Moreover, the
C++ preprocessor is virtually identical to the one defined by C
...
In C, each preprocessor directive is necessary
...
In fact,
one of the long-term design goals of C++ is the elimination of the preprocessor
altogether
...
The preprocessor contains the following directives:
#define
#elif
#else
#endif
#error
#if
#ifdef
#ifndef
#include
#line
#pragma
#undef
As you can see, all preprocessor directives begin with a # sign
...
For example,
#include
h>
will not work
...
e
...
The identifier is referred to as a macro name and the replacement process as
macro replacement
...
There may be any number of spaces
between the identifier and the character sequence, but once the character sequence
begins, it is terminated only by a newline
...
For example, the following prints 0 1 2 on the screen:
printf("%d %d %d", RIGHT, LEFT, LEFT+1);
Once a macro name has been defined, it may be used as part of the definition of other
macro names
...
Therefore, if you wish to define a standard error message,
you might write something like this:
#define E_MS "standard error on input\n"
/*
...
To the compiler, the printf( ) statement will actually
appear to be
printf("standard error on input\n");
No text substitutions occur if the identifier is within a quoted string
...
If the character sequence is longer than one line, you may continue it on the next by
placing a backslash at the end of the line, as shown here:
#define LONG_STRING "this is a very long \
string that is used as an example"
C/C++ programmers commonly use uppercase letters for defined identifiers
...
Also, it is usually best to put all #defines at the start of the
file or in a separate header file rather than sprinkling them throughout the program
...
For example, you may have a program that defines an array and has
several routines that access that array
...
In this way, if you need to change the size of
the array, you will only need to change the #define statement and then recompile your
program
...
*/
float balance[MAX_SIZE];
/*
...
*/
for(i=0; i
Since MAX_SIZE defines the size of the array balance, if the size of balance needs to
be changed in the future, you need only change the definition of MAX_SIZE
...
Note
C++ provides a better way of defining constants, which uses the const keyword
...
Defining Function-like Macros
The #define directive has another powerful feature: the macro name can have
arguments
...
This form of a
macro is called a function-like macro
...
h>
#define ABS(a)
(a)<0 ? -(a) : (a)
int main(void)
{
printf("abs of -1 and 1: %d %d", ABS(-1), ABS(1));
return 0;
}
When this program is compiled, a in the macro definition will be substituted with
the values –1 and 1
...
For example, if the parentheses around a were removed, this expression
ABS(10-20)
would be converted to
10-20<0 ? -10-20 : 10-20
after macro replacement and would yield the wrong result
...
However, if the size of the function-like macro is very large, this increased speed may
be paid for with an increase in the size of the program because of duplicated code
...
#error
The #error directive forces the compiler to stop compilation
...
The general form of the #error directive is
#error error-message
The error-message is not between double quotes
...
241
242
C++: The Complete Reference
#include
The #include directive instructs the compiler to read another source file in addition to
the one that contains the #include directive
...
For example,
#include "stdio
...
h>
both instruct the compiler to read and compile the header for the C I/O system library
functions
...
This is referred to as nested
includes
...
However,
Standard C stipulates that at least eight nested inclusions will be available
...
Whether the filename is enclosed by quotes or by angle brackets determines
how the search for the specified file is conducted
...
Often, this means searching some special directory set aside for include files
...
For many compilers, this means searching the current working directory
...
Typically, most programmers use angle brackets to include the standard header
files
...
However, there is no hard and fast rule that demands this usage
...
C++ defines a set of standard headers that provide the information necessary
to the various C++ libraries
...
Thus, a header is simply an abstraction that guarantees that the
appropriate information required by your program is included
...
Conditional Compilation Directives
There are several directives that allow you to selectively compile portions of your
program's source code
...
Chapter 10:
The Preprocessor and Comments
#if, #else, #elif, and #endif
Perhaps the most commonly used conditional compilation directives are the #if, #else,
#elif, and #endif
...
The general form of #if is
#if constant-expression
statement sequence
#endif
If the constant expression following #if is true, the code that is between it and
#endif is compiled
...
The #endif directive
marks the end of an #if block
...
*/
#include
\n");
#endif
return 0;
}
This program displays the message on the screen because MAX is greater than 99
...
The expression that follows the #if is
evaluated at compile time
...
The #else directive works much like the else that is part of the C++ language: it
establishes an alternative if #if fails
...
*/
#include
\n");
#else
printf("Compiled for small array
...
The #else alternative is compiled, however, and the message Compiled for
small array is displayed
...
This is necessary because there can only be one #endif associated with
any #if
...
#elif is followed by a constant expression
...
Otherwise, the next block in the series is checked
...
...
#elif expression N
statement sequence
#endif
Chapter 10:
The Preprocessor and Comments
For example, the following fragment uses the value of ACTIVE_COUNTRY to
define the currency sign:
#define US 0
#define ENGLAND 1
#define FRANCE 2
#define ACTIVE_COUNTRY US
#if ACTIVE_COUNTRY == US
char currency[] = "dollar";
#elif ACTIVE_COUNTRY == ENGLAND
char currency[] = "pound";
#else
char currency[] = "franc";
#endif
Standard C states that #ifs and #elifs may be nested at least eight levels
...
When nested, each #endif,
#else, or #elif associates with the nearest #if or #elif
...
The general form of #ifdef is
#ifdef macro-name
statement sequence
#endif
245
246
C++: The Complete Reference
If macro-name has been previously defined in a #define statement, the block of code
will be compiled
...
Both #ifdef and #ifndef may use an #else or #elif statement
...
h>
#define TED 10
int main(void)
{
#ifdef TED
printf("Hi Ted\n");
#else
printf("Hi anyone\n");
#endif
#ifndef RALPH
printf("RALPH not defined\n");
#endif
return 0;
}
will print Hi Ted and RALPH not defined
...
You may nest #ifdefs and #ifndefs to at least eight levels in Standard C
...
#undef
The #undef directive removes a previously defined definition of the macro name that
follows it
...
The general form for #undef is
#undef macro-name
Chapter 10:
The Preprocessor and Comments
For example,
#define LEN 100
#define WIDTH 100
char array[LEN][WIDTH];
#undef LEN
#undef WIDTH
/* at this point both LEN and WIDTH are undefined */
Both LEN and WIDTH are defined until the #undef statements are encountered
...
Using defined
In addition to #ifdef, there is a second way to determine if a macro name is defined
...
The defined operator has this general form:
defined macro-name
If macro-name is currently defined, then the expression is true
...
For
example, to determine if the macro MYFILE is defined, you can use either of these two
preprocessing commands:
#if defined MYFILE
or
#ifdef MYFILE
You may also precede defined with the ! to reverse the condition
...
#if !defined DEBUG
printf("Final version!\n");
#endif
247
248
C++: The Complete Reference
One reason for using defined is that it allows the existence of a macro name to be
determined by a #elif statement
...
The _ _LINE_ _ identifier contains the line
number of the currently compiled line of code
...
The general form for #line is
#line number "filename"
where number is any positive integer and becomes the new value of _ _LINE_ _ , and
the optional filename is any valid file identifier, which becomes the new value of
_ _FILE_ _
...
For example, the following code specifies that the line count will begin with 100
...
#include
For example, a compiler may have an option that supports
program execution tracing
...
You must check the compiler's documentation for details and options
...
These operators are used with the
#define statement
...
For example, consider this program
...
h>
#define mkstr(s)
# s
int main(void)
{
printf(mkstr(I like C++));
return 0;
}
The preprocessor turns the line
printf(mkstr(I like C++));
into
printf("I like C++");
The ## operator, called the pasting operator, concatenates two tokens
...
h>
#define concat(a, b)
a ## b
int main(void)
{
int xy = 10;
printf("%d", concat(x, y));
return 0;
}
The preprocessor transforms
printf("%d", concat(x, y));
249
250
C++: The Complete Reference
into
printf("%d", xy);
If these operators seem strange to you, keep in mind that they are not needed or
used in most programs
...
Predefined Macro Names
C++ specifies six built-in predefined macro names
...
Each will be described here, in turn
...
Briefly, they contain the current line number and filename of the program when it is
being compiled
...
The _ _TIME_ _ macro contains the time at which the program was compiled
...
The meaning of _ _STDC_ _ is implementation-defined
...
A compiler conforming to Standard C++ will define_ _cplusplus as a value
containing at least six digits
...
C-Style Comments
A C-style comment begins with the character pair /* and ends with */
...
The compiler ignores any text between the
beginning and ending comment symbols
...
h>
int main(void)
{
printf("hello");
/* printf("there"); */
return 0;
}
A C-style comment is commonly called a multiline comment because the text of the
comment may extend over two or more lines
...
For example, this comment is valid:
x = 10+ /* add the numbers */5;
while
swi/*this will not work*/tch(c) {
...
However, you should not
generally place comments in the middle of expressions because it obscures their
meaning
...
That is, one comment may not contain
another comment
...
However, C++ supports two types of comments
...
The second is the single-line comment
...
For example,
251
252
C++: The Complete Reference
// this is a single-line comment
Although Standard C does not currently define the single-line comment, most C
compilers will accept it and it will probably be formally incorporated into Standard C
within the next year or two
...
You should include comments whenever they are needed to explain the operation
of the code
...
Part II
C++
P
art One examined the C subset of C++
...
That is, it
discusses those features of C++ that it does not have in common
with C
...
We will begin
with an overview of C++
...
C++
Chapter 11
An Overview of C++
255
256
C++: The Complete Reference
his chapter provides an overview of the key concepts embodied in C++
...
In several instances, this interrelatedness makes it difficult
to describe one feature of C++ without implicitly involving several others
...
To address this
problem, this chapter presents a quick overview of the most important aspects of
C++, including its history, its key features, and the difference between traditional
and Standard C++
...
T
The Origins of C++
C++ began as an expanded version of C
...
He
initially called the new language "C with Classes
...
Although C was one of the most liked and widely used professional programming
languages in the world, the invention of C++ was necessitated by one major programming factor: increasing complexity
...
Even though C is an excellent programming language, it has
its limits
...
The purpose of C++ is to allow this
barrier to be broken
...
Most additions made by Stroustrup to C support object-oriented programming,
sometimes referred to as OOP
...
) Stroustrup states that some of C++'s object-oriented features
were inspired by another object-oriented language called Simula67
...
Since C++ was first invented, it has undergone three major revisions, with each
adding to and altering the language
...
The third occurred during the standardization of C++
...
Toward that end, a joint ANSI (American National
Standards Institute) and ISO (International Standards Organization) standardization
committee was formed
...
In that draft, the ANSI/ISO C++ committee (of which I am a member)
kept the features first defined by Stroustrup and added some new ones as well
...
Soon after the completion of the first draft of the C++ standard, an event occurred
that caused the language to be greatly expanded: the creation of the Standard Template
Library (STL) by Alexander Stepanov
...
It is both powerful and elegant, but also quite large
...
The
addition of the STL expanded the scope of C++ well beyond its original definition
...
It is fair to say that the standardization of C++ took far longer than anyone had
expected when it began
...
In fact, the version of C++ defined by the C++
committee is much larger and more complex than Stroustrup's original design
...
The final draft was passed out of committee
on November 14, 1997
...
The material in this book describes Standard C++, including all of its newest
features
...
What Is Object-Oriented Programming?
Since object-oriented programming (OOP) drove the creation of C++, it is necessary to
understand its foundational principles
...
Programming methodologies have changed dramatically since the
invention of the computer, primarily to accommodate the increasing complexity of
programs
...
As
long as programs were just a few hundred instructions long, this approach worked
...
As programs continued to grow, high-level languages were
introduced that gave the programmer more tools with which to handle complexity
...
Although FORTRAN was a
very impressive first step, it is hardly a language that encourages clear, easy-tounderstand programs
...
This is the method encouraged by
languages such as C and Pascal
...
Structured languages are characterized by
their support for stand-alone subroutines, local variables, rich control constructs, and
their lack of reliance upon the GOTO
...
Consider this: At each milestone in the development of programming, techniques
and tools were created to allow the programmer to deal with increasingly greater
complexity
...
Prior to the invention of OOP, many projects
were nearing (or exceeding) the point where the structured approach no longer
257
258
C++: The Complete Reference
worked
...
Object-oriented programming took the best ideas of structured programming
and combined them with several new concepts
...
In the most general sense, a program can be organized in
one of two ways: around its code (what is happening) or around its data (who is being
affected)
...
This approach can be thought of as "code acting on data
...
Object-oriented programs work the other way around
...
" In an
object-oriented language, you define the data and the routines that are permitted
to act on that data
...
To support the principles of object-oriented programming, all OOP languages
have three traits in common: encapsulation, polymorphism, and inheritance
...
Encapsulation
Encapsulation is the mechanism that binds together code and the data it manipulates,
and keeps both safe from outside interference and misuse
...
When code and data are linked together in this fashion, an object is
created
...
Within an object, code, data, or both may be private to that object or public
...
That is,
private code or data may not be accessed by a piece of the program that exists outside
the object
...
Typically, the public parts of an object are used to
provide a controlled interface to the private elements of the object
...
It may
seem strange that an object that links both code and data can be thought of as a
variable
...
Each
time you define a new type of object, you are creating a new data type
...
Polymorphism
Object-oriented programming languages support polymorphism, which is characterized
by the phrase "one interface, multiple methods
...
The
Chapter 11:
An Overview of C++
specific action selected is determined by the exact nature of the situation
...
No matter what type of furnace your house
has (gas, oil, electric, etc
...
In this case, the
thermostat (which is the interface) is the same no matter what type of furnace (method)
you have
...
It doesn't matter what type of furnace actually provides the heat
...
For example, you might
have a program that defines three different types of stacks
...
Because
of polymorphism, you can define one set of names, push( ) and pop( ), that can be used
for all three stacks
...
The
compiler will automatically select the right function based upon the data being stored
...
The individual versions of these functions
define the specific implementations (methods) for each type of data
...
It is the compiler's job to select the specific action
(i
...
, method) as it applies to each situation
...
You need only remember and utilize the general interface
...
However, C++ is a compiled
language
...
Inheritance
Inheritance is the process by which one object can acquire the properties of another
object
...
If you think
about it, most knowledge is made manageable by hierarchical classifications
...
Without the use of classifications,
each object would have to define explicitly all of its characteristics
...
It is the inheritance mechanism that makes it possible for one object to
be a specific instance of a more general case
...
Some C++ Fundamentals
In Part One, the C subset of C++ was described and C programs were used to
demonstrate those features
...
" That is, they will be making use of features unique to C++
...
If you come from a C background, or if you have been studying the C subset
programs in Part One, be aware that C++ programs differ from C programs in some
important respects
...
But C++ programs differ from C programs in other ways,
including how I/O is performed and what headers are included
...
Before moving on to C++'s object-oriented constructs, an understanding of the
fundamental elements of a C++ program is required
...
Along the
way, some important differences with C and earlier versions of C++ are pointed out
...
#include
using namespace std;
int main()
{
int i;
cout << "This is output
...
A line-by-line commentary will be useful
...
This header supports C++-style I/O operations
...
h is to C
...
h extension to the
Chapter 11:
An Overview of C++
name iostream
...
New-style headers do not use the
...
The next line in the program is
using namespace std;
This tells the compiler to use the std namespace
...
A namespace creates a declarative region in which various program elements
can be placed
...
The using
statement informs the compiler that you want to use the std namespace
...
By using the std
namespace you simplify access to the standard library
...
Note
Since both new-style headers and namespaces are recent additions to C++, you may
encounter older code that does not use them
...
Instructions for using an older compiler are found later in
this chapter
...
int main()
Notice that the parameter list in main( ) is empty
...
This differs from C
...
However, in
C++, the use of void is redundant and unnecessary
...
The next line contains two C++ features
...
\n";
First, the statement
cout << "This is output
...
to be displayed on the screen, followed by a carriage returnlinefeed combination
...
It is still the left shift
operator, but when it is used as shown in this example, it is also an output operator
...
(Actually, like C, C++ supports
I/O redirection, but for the sake of discussion, assume that cout refers to the screen
...
Note that you can still use printf( ) or any other of C's I/O functions in a C++
program
...
Further, while using printf( ) to output a string is virtually equivalent to using << in
this case, the C++ I/O system can be expanded to perform operations on objects that
you define (something that you cannot do using printf( ))
...
As mentioned in
Chapter 10, C++ defines two types of comments
...
You can also define a single-line comment by
using //; whatever follows such a comment is ignored by the compiler until the end of
the line is reached
...
Next, the program prompts the user for a number
...
However, when used as
shown, it also is C++'s input operator
...
The identifier cin refers to the standard input device, which is
usually the keyboard
...
Note
The line of code just described is not misprinted
...
When inputting information using a C-based function like
scanf( ), you have to explicitly pass a pointer to the variable that will receive the
information
...
However, because of the way the >> operator is implemented in C++, you do not
need (in fact, must not use) the &
...
Although it is not illustrated by the example, you are free to use any of the
C-based input functions, such as scanf( ), instead of using >>
...
Another interesting line in the program is shown here:
cout << i << "squared is " << i*i << "\n";
Chapter 11:
An Overview of C++
Assuming that i has the value 10, this statement causes the phrase 10 squared is 100
to be displayed, followed by a carriage return-linefeed
...
The program ends with this statement:
return 0;
This causes zero to be returned to the calling process (which is usually the operating
system)
...
Returning zero indicates that the
program terminated normally
...
You may also use the values EXIT_SUCCESS and EXIT_
FAILURE if you like
...
For example, this program inputs a float, a double, and
a string and then outputs them:
#include
using namespace std;
int main()
{
float f;
char str[80];
double d;
cout << "Enter two floating point numbers: ";
cin >> f >> d;
cout << "Enter a string: ";
cin >> str;
cout << f << " " << d << " " << str;
return 0;
}
When you run this program, try entering This is a test
...
When the program redisplays the information you entered, only the word "This"
will be displayed
...
Thus, "is a test" is
263
264
C++: The Complete Reference
never read by the program
...
The C++ I/O operators recognize the entire set of backslash character constants
described in Chapter 2
...
Declaring Local Variables
If you come from a C background, you need to be aware of an important difference
between C and C++ regarding when local variables can be declared
...
You cannot
declare a variable in a block after an "action" statement has occurred
...
OK in C++
...
However, when compiling it as a C++
program, this fragment is perfectly acceptable
...
Here is another example
...
#include
using namespace std;
int main()
{
float f;
Chapter 11:
An Overview of C++
double d;
cout << "Enter two floating point numbers: ";
cin >> f >> d;
cout << "Enter a string: ";
char str[80]; // str declared here, just before 1st use
cin >> str;
cout << f << " " << d << " " << str;
return 0;
}
Whether you declare all variables at the start of a block or at the point of first use is
completely up to you
...
In the preceding example, the declarations
are separated simply for illustration, but it is easy to imagine more complex examples in
which this feature of C++ is more valuable
...
However, the greatest benefit of declaring variables at the point of first use is
gained in large functions
...
For
this reason, this book will declare variables at the point of first use only when it seems
warranted by the size or complexity of a function
...
Opponents suggest that sprinkling declarations throughout a block makes
it harder, not easier, for someone reading the code to find quickly the declarations
of all variables used in that block, making the program harder to maintain
...
This book
will not take a stand either way on this issue
...
No Default to int
There has been a fairly recent change to C++ that may affect older C++ code as well as
C code being ported to C++
...
However, the "default-to-int" rule was dropped from C++ a couple of years ago, during
standardization
...
The
"default-to-int" rule is also applied in much older C++ code
...
It
was common practice to not specify int explicitly when a function returned an integer
result
...
func(int i)
{
return i*i;
}
In Standard C++, this function must have the return type of int specified, as shown here
...
However, you should not use this feature for new code
because it is no longer allowed
...
At the time of this writing, Standard C
does not
...
As explained in Part One, automatic conversions take place which allow
bool values to be converted to integers, and vice versa
...
The reverse also occurs; true is
converted to 1 and false is converted to zero
...
Old-Style vs
...
As a result, there are really two versions of C++
...
This is the version of C++ that has been used by programmers for the past
decade
...
While these two versions of C++ are
very similar at their core, Standard C++ contains several enhancements not found
in traditional C++
...
Chapter 11:
An Overview of C++
This book describes Standard C++
...
The code in this book reflects the contemporary coding style and practices
as encouraged by Standard C++
...
Here's why
...
As these features were defined, they were implemented by compiler developers
...
Since features were added to C++
over a period of years, an older compiler might not support one or more of them
...
If you are using an older compiler that does not
accept these new features, don't worry
...
The key differences between old-style and modern code involve two features:
new-style headers and the namespace statement
...
The
first version shown here reflects the way C++ programs were written using old-style
coding
...
*/
#include
It includes the file iostream
...
Also notice that no namespace statement is present
...
/*
A modern-style C++ program that uses
the new-style headers and a namespace
...
Both of these
features were mentioned in passing earlier
...
The New C++ Headers
As you know, when you use a library function in a program, you must include its
header file
...
For example, in C, to include the
header file for the I/O functions, you include stdio
...
h>
Here, stdio
...
The key point is that this
#include statement includes a file
...
That is, it used header files
...
However, Standard C++ created a new kind of header that is used by the Standard
C++ library
...
Instead, they simply
specify standard identifiers that may be mapped to files by the compiler, although
they need not be
...
Since the new-style headers are not filenames, they do not have a
...
They
consist solely of the header name contained between angle brackets
...
The new-style headers are included using the #include statement
...
Because C++ includes the entire C function library, it still supports the standard
C-style header files associated with that library
...
h
or ctype
...
However, Standard C++ also defines new-style headers
that you can use in place of these header files
...
h
...
h is
...
h is
...
For this
reason, from this point forward, this book will use new-style C++ headers in all
#include statements
...
Since the new-style header is a recent addition to C++, you will still find many,
many older programs that don't use it
...
As the old-style skeletal program shows, the traditional
way to include the I/O header is as shown here
...
h>
This causes the file iostream
...
In general, an old-style
header file will use the same name as its corresponding new-style header with
a
...
As of this writing, all C++ compilers support the old-style headers
...
This is why they are not used in this book
...
Namespaces
When you include a new-style header in your program, the contents of that header
are contained in the std namespace
...
The
purpose of a namespace is to localize the names of identifiers to avoid name collisions
...
Originally, the names of the C++ library functions, etc
...
However, with the advent of the new-style headers, the
contents of these headers were placed in the std namespace
...
For now, you won't need to worry about them because
the statement
using namespace std;
brings the std namespace into visibility (i
...
, it puts std into the global namespace)
...
One other point: for the sake of compatibility, when a C++ program includes a C
header, such as stdio
...
This allows a
C++ compiler to compile C-subset programs
...
While all new C++ compilers
support these features, older compilers may not
...
If this is the case, there is an easy work-around: simply use an
old-style header and delete the namespace statement
...
h>
This change transforms a modern program into an old-style one
...
One other point: for now and for the next few years, you will see many C++
programs that use the old-style headers and do not include a using statement
...
However, for new programs, you
should use the modern style because it is the only style of program that complies with
the C++ Standard
...
Introducing C++ Classes
This section introduces C++'s most important feature: the class
...
A class is
similar syntactically to a structure
...
The following class defines a
type called stack, which will be used to create a stack:
#define SIZE 100
// This creates the class stack
...
By default, all items defined in
a class are private
...
This means that
they cannot be accessed by any function that is not a member of the class
...
Although it is not shown in this example, you can
also define private functions, which then may be called only by other members of the
class
...
All variables or functions defined
after public can be accessed by all other functions in the program
...
Although you can
have public variables, good practice dictates that you should try to limit their use
...
One other point: Notice that the public keyword is followed by a colon
...
The variables stck and tos are called member variables (or data
members)
...
Only member
functions have access to the private members of their class
...
Once you have defined a class, you can create an object of that type by using the
class name
...
For example,
this creates an object called mystack of type stack:
stack mystack;
When you declare an object of a class, you are creating an instance of that class
...
You may also create objects when the class is
defined by putting their names after the closing curly brace, in exactly the same way as
you would with a structure
...
Therefore, an object is an instance of a class in just the same way that some
other variable is an instance of the int data type, for example
...
(That is, an object exists inside the memory
of the computer
...
Inside the declaration of stack, member functions were identified using their
prototypes
...
Prototypes are not optional
...
When it comes time to actually code a function that is the member of a class, you
must tell the compiler which class the function belongs to by qualifying its name with
the name of the class of which it is a member
...
\n";
return;
}
stck[tos] = i;
tos++;
}
The :: is called the scope resolution operator
...
In C++, several different classes can use the same function name
...
When you refer to a member of a class from a piece of code that is not part of the
class, you must always do so in conjunction with an object of that class
...
This rule applies whether you are accessing a data member or a function member
...
stack stack1, stack2;
stack1
...
Understand that stack1 and stack2 are two separate objects
...
The only
relationship stack1 has with stack2 is that they are objects of the same type
...
It is only when a member is
referred to by code that does not belong to the class that the object name and the dot
operator must be used
...
class stack {
int stck[SIZE];
int tos;
public:
void init();
void push(int i);
int pop();
};
void stack::init()
{
tos = 0;
}
void stack::push(int i)
{
if(tos==SIZE) {
cout << "Stack is full
...
\n";
return 0;
}
tos--;
273
274
C++: The Complete Reference
return stck[tos];
}
int main()
{
stack stack1, stack2;
// create two stack objects
stack1
...
init();
stack1
...
push(2);
stack1
...
push(4);
cout
cout
cout
cout
<<
<<
<<
<<
stack1
...
pop()
stack2
...
pop()
<<
<<
<<
<<
" ";
" ";
" ";
"\n";
return 0;
}
The output from this program is shown here
...
For example, a statement like
stack1
...
could not be in the main( ) function of the previous program because tos is private
...
In C++, two or more functions can share the same name as long as their parameter
declarations are different
...
Chapter 11:
An Overview of C++
To see why function overloading is important, first consider three functions defined
by the C subset: abs( ), labs( ), and fabs( )
...
Although these functions perform almost identical actions,
in C three slightly different names must be used to represent these essentially similar
tasks
...
Even
though the underlying concept of each function is the same, the programmer has to
remember three things, not just one
...
0) << "\n";
cout << abs(-9L) << "\n";
return 0;
}
int abs(int i)
{
cout << "Using integer abs()\n";
return i<0 ? -i : i;
}
double abs(double d)
{
cout << "Using double abs()\n";
return d<0
...
Using integer abs()
10
Using double abs()
11
Using long abs()
9
This program creates three similar but different functions called abs( ), each of
which returns the absolute value of its argument
...
The value of overloaded
functions is that they allow related sets of functions to be accessed with a common
name
...
It is
left to the compiler to choose the right specific method for a particular circumstance
...
Due to polymorphism, three
things to remember have been reduced to one
...
In general, to overload a function, simply declare different versions of it
...
You must observe one important restriction when
overloading a function: the type and/or number of the parameters of each overloaded
function must differ
...
They must differ in the types or number of their parameters
...
) Of course, overloaded functions may differ in their return types, too
...
One version concatenates
two strings (just like strcat( ) does)
...
Here, overloading is used to create one interface that appends
either a string or an integer to another string
...
For example, you could use the name sqr( ) to create functions that return the
square of an int and the square root of a double
...
In practice, you should
overload only closely related operations
...
As you know, in
C++, it is possible to use the << and >> operators to perform console I/O operations
...
When an operator is overloaded, it takes on an additional
meaning relative to a certain class
...
In general, you can overload most of C++'s operators by defining what they mean
relative to a specific class
...
It is possible to overload the + operator relative to objects of type stack
so that it appends the contents of one stack to the contents of another
...
Because operator overloading is, in practice, somewhat more complex than function
overloading, examples are deferred until Chapter 14
...
In C++, inheritance is supported by allowing one
class to incorporate another class into its declaration
...
The process involves
first defining a base class, which defines those qualities common to all objects to be
derived from the base
...
The
classes derived from the base are usually referred to as derived classes
...
To demonstrate how this works, the next example creates classes that
categorize different types of buildings
...
It will serve as the base for
two derived classes
...
The member functions beginning
with set set the values of the private data
...
You can now use this broad definition of a building to create derived classes that
describe specific types of buildings
...
The general form for inheritance is
class derived-class : access base-class {
// body of new class
}
Here, access is optional
...
(These options are further examined in Chapter 12
...
Using public means that all of the public members of the base class
will become public members of the derived class
...
However, house's member functions do not have access to the private elements of
building
...
Even though house inherits building, it has
access only to the public members of building
...
Remember
A derived class has direct access to both its own members and the public members of
the base class
...
It creates two derived classes of building
using inheritance; one is house, the other, school
...
set_rooms(12);
h
...
set_area(4500);
h
...
set_baths(3);
cout << "house has " << h
...
set_rooms(200);
s
...
set_offices(5);
s
...
get_classrooms();
cout << " classrooms\n";
cout << "Its area is " << s
...
house has 5 bedrooms
school has 180 classrooms
Its area is 25000
As this program shows, the major advantage of inheritance is that you can create a
general classification that can be incorporated into more specific ones
...
When writing about C++, the terms base and derived are generally used to describe
the inheritance relationship
...
You
may also see the terms superclass and subclass
...
(Refer to Chapter 16 for details
...
For example, think back to the stack class developed earlier in this chapter
...
This was performed by
using the function init( )
...
This automatic
initialization is performed through the use of a constructor function
...
For example, here is how the stack class looks when
converted to use a constructor function for initialization:
// This creates the class stack
...
In C++, constructor
functions cannot return values and, thus, have no return type
...
In actual practice, most constructor functions will not output or input
anything
...
An object's constructor is automatically called when the object is created
...
If you are accustomed
to thinking of a declaration statement as being passive, this is not the case for C++
...
This distinction is not just
academic
...
An object's
constructor is called once for global or static local objects
...
The complement of the constructor is the destructor
...
Local objects
are created when their block is entered, and destroyed when the block is left
...
When an object is destroyed, its
destructor (if it has one) is automatically called
...
For example, an object may need to deallocate
memory that it had previously allocated or it may need to close a file that it had
opened
...
The
destructor has the same name as the constructor, but it is preceded by a ~
...
(Keep in mind that
the stack class does not require a destructor; the one shown here is just for illustration
...
class stack {
int stck[SIZE];
int tos;
Chapter 11:
An Overview of C++
public:
stack(); // constructor
~stack(); // destructor
void push(int i);
int pop();
};
// stack's constructor function
stack::stack()
{
tos = 0;
cout << "Stack Initialized\n";
}
// stack's destructor function
stack::~stack()
{
cout << "Stack Destroyed\n";
}
Notice that, like constructor functions, destructor functions do not have return values
...
Observe that init( ) is no longer needed
...
#include
using namespace std;
#define SIZE 100
// This creates the class stack
...
\n";
return;
}
stck[tos] = i;
tos++;
}
int stack::pop()
{
if(tos==0) {
cout << "Stack underflow
...
push(1);
b
...
push(3);
b
...
pop() << " ";
Chapter 11:
An Overview of C++
cout << a
...
pop() << " ";
cout << b
...
These are shown in Table
11-1
...
Also, early versions of C++ defined the overload keyword, but it is obsolete
...
asm
auto
bool
break
case
catch
char
class
const
const_cast
continue
default
delete
do
double
dynamic_cast
else
enum
explicit
export
extern
false
float
for
friend
goto
if
inline
int
long
mutable
namespace
new
operator
private
protected
Table 11-1
...
The C++ keywords (continued)
The General Form of a C++ Program
Although individual styles will differ, most C++ programs will have this general form:
#includes
base-class declarations
derived class declarations
nonmember function prototypes
int main( )
{
//
...
But the general organization of a program remains the same
...
C++
Chapter 12
Classes and Objects
289
290
C++: The Complete Reference
I
n C++, the class forms the basis for object-oriented programming
...
This
chapter examines classes and objects in detail
...
A class declaration defines a new type
that links code and data
...
Thus, a class is a logical abstraction, but an object has physical existence
...
A class declaration is similar syntactically to a structure
...
Here is the entire general form of a class
declaration that does not inherit any other class
...
access-specifier:
data and functions
} object-list;
The object-list is optional
...
Here,
access-specifier is one of these three C++ keywords:
public
private
protected
By default, functions and data declared within a class are private to that
class and may be accessed only by other members of the class
...
The protected access specifier is needed only when inheritance is
involved (see Chapter 15)
...
Chapter 12:
Classes and Objects
You may change access specifications as often as you like within a class declaration
...
The class declaration in the following example illustrates this feature:
#include
#include
using namespace std;
class employee {
char name[80]; // private by default
public:
void putname(char *n); // these are public
void getname(char *n);
private:
double wage; // now, private again
public:
void putwage(double w); // back to public
double getwage();
};
void employee::putname(char *n)
{
strcpy(name, n);
}
void employee::getname(char *n)
{
strcpy(n, name);
}
void employee::putwage(double w)
{
wage = w;
}
double employee::getwage()
{
return wage;
}
int main()
{
employee ted;
291
292
C++: The Complete Reference
char name[80];
ted
...
putwage(75000);
ted
...
getwage() << " per year
...
Notice that the public access specifier is used twice
...
However, to the compiler, using multiple access specifiers makes no difference
...
For example, most programmers would code the
employee class as shown here, with all private elements grouped together and all
public elements grouped together:
class employee {
char name[80];
double wage;
public:
void putname(char *n);
void getname(char *n);
void putwage(double w);
double getwage();
};
Functions that are declared within a class are called member functions
...
This includes all
private elements
...
Collectively, any element of a class can be referred to as a member
of that class
...
A non-static member
variable cannot have an initializer
...
(Although a member can be a pointer to the class that is being
declared
...
In general, you should make all data members of a class private to that class
...
However, there may be situations in
which you will need to make one or more variables public
...
)
When a variable is public, it may be accessed directly by any other part of your
program
...
This simple program illustrates the use of a public variable:
#include
using namespace std;
class myclass {
public:
int i, j, k; // accessible to entire program
};
int main()
{
myclass a, b;
a
...
j = 4;
a
...
i * a
...
k = 12; // remember, a
...
k are different
cout << a
...
k;
return 0;
}
Structures and Classes Are Related
Structures are part of the C subset and were inherited from the C language
...
But the relationship between a
class and a struct is closer than you may at first think
...
In fact, the only
difference between a class and a struct is that by default all members are public in a
struct and private in a class
...
293
294
C++: The Complete Reference
That is, in C++, a structure defines a class type
...
#include
#include
using namespace std;
struct mystr {
void buildstr(char *s); // public
void showstr();
private: // now go private
char str[255];
} ;
void mystr::buildstr(char *s)
{
if(!*s) *str = '\0'; // initialize string
else strcat(str, s);
}
void mystr::showstr()
{
cout << str << "\n";
}
int main()
{
mystr s;
s
...
buildstr("Hello ");
s
...
showstr();
return 0;
}
This program displays the string Hello there!
...
This seeming redundancy is justified for several reasons
...
In C, structures
already provide a means of grouping data
...
Second, because structures and classes are related, it may be
easier to port existing C programs to C++
...
In order for C++ to remain compatible with C, the definition
of struct must always be tied to its C definition
...
Usually it is best to use a class when you want a class, and a struct when you want a
C-like structure
...
Sometimes the acronym
POD is used to describe a C-style structure—one that does not contain member
functions, constructors, or destructors
...
(Actually, the term
POD is a bit more narrowly defined in the Standard C++ specification, but means
essentially the same thing
...
Unions and Classes Are Related
Like a structure, a union may also be used to define a class
...
They may also include constructor
and destructor functions
...
Like the
structure, union members are public by default and are fully compatible with C
...
(This example assumes that short integers are 2 bytes long
...
set_byte(49034);
b
...
show_word();
return 0;
}
Like a structure, a union declaration in C++ defines a special type of class
...
There are several restrictions that must be observed when you use C++ unions
...
Further, a union cannot be a
base class
...
(Virtual functions are
discussed in Chapter 17
...
A reference
member cannot be used
...
Finally, no object can be a member of a union if the object has an
explicit constructor or destructor function
...
Anonymous Unions
There is a special type of union in C++ called an anonymous union
...
Instead, an anonymous union tells the compiler that its member variables are to
share the same location
...
For example, consider this program:
#include
#include
using namespace std;
int main()
{
// define anonymous union
union {
long l;
double d;
char s[4];
} ;
// now, reference union elements directly
l = 100000;
cout << l << " ";
d = 123
...
In fact, relative to your program, that is exactly how
you will use them
...
This
implies that the names of the members of an anonymous union must not conflict with
other identifiers known within the same scope
...
First, the only elements contained within an anonymous union must be data
...
Anonymous unions cannot contain private or protected
elements
...
297
298
C++: The Complete Reference
Friend Functions
It is possible to grant a nonmember function access to the private members of a class
by using a friend
...
To declare a friend function, include its prototype
within the class, preceding it with the keyword friend
...
int sum(myclass x)
{
/* Because sum() is a friend of myclass, it can
directly access a and b
...
a + x
...
set_ab(3, 4);
cout << sum(n);
return 0;
}
Chapter 12:
Classes and Objects
In this example, the sum( ) function is not a member of myclass
...
Also, notice that sum( ) is called without the use
of the dot operator
...
Although there is nothing gained by making sum( ) a friend rather than a member
function of myclass, there are some circumstances in which friend functions are quite
valuable
...
Second, friend functions make the creation of some types of I/O
functions easier (see Chapter 18)
...
Let's examine this third usage now
...
Other parts of your program may wish
to know if a pop-up message is currently being displayed before writing to the screen
so that no message is accidentally overwritten
...
If the condition needs to be checked frequently, this additional
overhead may not be acceptable
...
Thus, in situations like this, a friend function allows you to generate more efficient
code
...
public:
void set_status(int state);
friend int idle(C1 a, C2 b);
};
class C2 {
int status; // IDLE if off, INUSE if on screen
//
...
status || b
...
set_status(IDLE);
y
...
\n";
else cout << "In use
...
set_status(INUSE);
if(idle(x, y)) cout << "Screen can be used
...
\n";
return 0;
}
Notice that this program uses a forward declaration (also called a forward reference) for
the class C2
...
To create a forward declaration to a class, simply use the
form shown in this program
...
For example, here is the
preceding program rewritten so that idle( ) is a member of C1:
#include
using namespace std;
const int IDLE = 0;
const int INUSE = 1;
class C2;
// forward declaration
class C1 {
int status; // IDLE if off, INUSE if on screen
//
...
public:
void set_status(int state);
friend int C1::idle(C2 b);
};
void C1::set_status(int state)
{
status = state;
}
void C2::set_status(int state)
{
status = state;
}
// idle() is member of C1, but friend of C2
int C1::idle(C2 b)
{
301
302
C++: The Complete Reference
if(status || b
...
set_status(IDLE);
y
...
idle(y)) cout << "Screen can be used
...
\n";
x
...
idle(y)) cout << "Screen can be used
...
\n";
return 0;
}
Because idle( ) is a member of C1, it can access the status variable of objects of type
C1 directly
...
There are two important restrictions that apply to friend functions
...
Second, friend functions may not have a
storage-class specifier
...
Friend Classes
It is possible for one class to be a friend of another class
...
For example,
// Using a friend class
...
a < x
...
a : x
...
min(ob);
return 0;
}
In this example, class Min has access to the private variables a and b declared within
the TwoValues class
...
It does not inherit the other class
...
Friend classes are seldom used
...
Inline Functions
There is an important feature in C++, called an inline function, that is commonly used
with classes
...
In C++, you can create short functions that are not actually called; rather, their code
is expanded in line at the point of each invocation
...
To cause a function to be expanded in line rather than called,
303
304
C++: The Complete Reference
precede its definition with the inline keyword
...
Since classes typically require several frequently
executed interface functions (which provide access to private data), the efficiency of
these functions is of critical concern
...
Typically, arguments are pushed onto the stack and various registers are
saved when a function is called, and then restored when the function returns
...
However, when a function is expanded in
line, none of those operations occur
...
For this reason, it is best to inline only very small functions
...
Like the register specifier, inline is actually just a request, not a command, to the
compiler
...
Also, some compilers may not inline all
types of functions
...
You will need to check your compiler's user manual for any restrictions to
inline
...
Inline functions may be class member functions
...
inline void myclass::init(int i, int j)
{
a = i;
b = j;
}
// Create another inline function
...
init(10, 20);
x
...
When a
function is defined inside a class declaration, it is automatically made into an inline
function (if possible)
...
For example, the preceding program is rewritten here with
the definitions of init( ) and show( ) contained within the declaration of myclass:
#include
using namespace std;
class myclass {
int a, b;
public:
// automatic inline
void init(int i, int j) { a=i; b=j; }
void show() { cout << a << " " << b << "\n"; }
};
int main()
{
myclass x;
x
...
show();
return 0;
}
Notice the format of the function code within myclass
...
However, you are free
to use any format you like
...
However, it is extremely common to see all short member functions
defined inside their class in C++ programs
...
)
Constructor and destructor functions may also be inlined, either by default, if
defined within their class, or explicitly
...
Typically, these arguments
help initialize an object when it is created
...
When you
define the constructor's body, use the parameters to initialize the object
...
show();
return 0;
}
307
308
C++: The Complete Reference
Notice that in the definition of myclass( ), the parameters i and j are used to give initial
values to a and b
...
Specifically, this
statement
myclass ob(3, 4);
causes an object called ob to be created and passes the arguments 3 and 4 to the i and j
parameters of myclass( )
...
Actually, there is a small technical difference
between the two types of declarations that relates to copy constructors
...
)
Here is another example that uses a parameterized constructor function
...
#include
#include
using namespace std;
const int IN = 1;
const int CHECKED_OUT = 0;
class book {
char author[40];
char title[40];
int status;
public:
book(char *n, char *t, int s);
int get_status() {return status;}
void set_status(int s) {status = s;}
void show();
};
book::book(char *n, char *t, int s)
{
Chapter 12:
Classes and Objects
strcpy(author, n);
strcpy(title, t);
status = s;
}
void book::show()
{
cout << title << " by " << author;
cout << " is ";
if(status==IN) cout << "in
...
\n";
}
int main()
{
book b1("Twain", "Tom Sawyer", IN);
book b2("Melville", "Moby Dick", CHECKED_OUT);
b1
...
show();
return 0;
}
Parameterized constructor functions are very useful because they allow you to
avoid having to make an additional function call simply to initialize one or more
variables in an object
...
Also, notice that the short get_status( ) and set_status( ) functions are defined
in line, within the book class
...
Constructors with One Parameter: A Special Case
If a constructor only has one parameter, there is a third way to pass an initial value to
that constructor
...
#include
using namespace std;
class X {
int a;
309
310
C++: The Complete Reference
public:
X(int j) { a = j; }
int geta() { return a; }
};
int main()
{
X ob = 99; // passes 99 to j
cout << ob
...
Pay special attention to how ob
is declared in main( )
...
That is, the declaration statement is handled by the
compiler as if it were written like this:
X ob = X(99);
In general, any time you have a constructor that requires only one argument, you
can use either ob(i) or ob = i to initialize an object
...
Remember that the alternative shown here applies only to constructors that have
exactly one parameter
...
This section explains the
consequences of each
...
Unlike regular data members, individual copies of a static
member variable are not made for each object
...
Thus, all objects of that class
use that same variable
...
Chapter 12:
Classes and Objects
When you declare a static data member within a class, you are not defining it
...
) Instead, you must provide a global definition
for it elsewhere, outside the class
...
This causes
storage for the variable to be allocated
...
)
To understand the usage and effect of a static data member, consider this program:
#include
using namespace std;
class shared {
static int a;
int b;
public:
void set(int i, int j) {a=i; b=j;}
void show();
} ;
int shared::a; // define a
void shared::show()
{
cout << "This is static a: " << a;
cout << "\nThis is non-static b: " << b;
cout << "\n";
}
int main()
{
shared x, y;
x
...
show();
y
...
show();
x
...
*/
return 0;
}
311
312
C++: The Complete Reference
This program displays the following output when run
...
As
mentioned earlier, this is necessary because the declaration of a inside shared does
not allocate storage
...
However, this convenience gave rise to serious
inconsistencies and it was eliminated several years ago
...
In these cases, you
will need to add the required definitions
...
For example,
in the following short program, a is both public and static
...
Further, since a exists before an object of shared is
created, a can be given a value at any time
...
For this reason, both output statements
display the same value: 99
...
a: " << x
...
In general, to refer to a static member independently of an object, you must
qualify it by using the name of the class of which it is a member
...
For example, you might create several objects,
each of which needs to write to a specific disk file
...
In this case, you will want to declare a
static variable that indicates when the file is in use and when it is free
...
The following program shows how
you might use a static variable of this type to control access to a scarce resource:
#include
using namespace std;
class cl {
static int resource;
public:
int get_resource();
void free_resource() {resource = 0;}
};
int cl::resource; // define resource
int cl::get_resource()
{
if(resource) return 0; // resource already in use
else {
resource = 1;
return 1; // resource allocated to this object
}
}
313
314
C++: The Complete Reference
int main()
{
cl ob1, ob2;
if(ob1
...
get_resource()) cout << "ob2 denied resource\n";
ob1
...
get_resource())
cout << "ob2 can now use resource\n";
return 0;
}
Another interesting use of a static member variable is to keep track of the number
of objects of a particular class type that are in existence
...
Objects
Objects
Objects
Objects
in
in
in
in
existence:
existence:
existence:
existence:
1
2
3
2
As you can see, the static member variable count is incremented whenever an object is
created and decremented when an object is destroyed
...
By using static member variables, you should be able to virtually eliminate any
need for global variables
...
Static Member Functions
Member functions may also be declared as static
...
They may only directly refer to other static members of the
class
...
) A static member function does not have a this pointer
...
) There cannot be a static and a non-static version of the same
function
...
Finally, they cannot be declared
as const or volatile
...
Notice that get_resource( ) is now declared as static
...
#include
using namespace std;
class cl {
static int resource;
public:
static int get_resource();
void free_resource() { resource = 0; }
};
int cl::resource; // define resource
int cl::get_resource()
{
if(resource) return 0; // resource already in use
else {
resource = 1;
return 1; // resource allocated to this object
}
}
int main()
{
cl ob1, ob2;
/* get_resource() is static so may be called independent
of any object
...
free_resource();
if(ob2
...
For
example, this is a perfectly valid C++ program:
#include
using namespace std;
class static_type {
static int i;
public:
static void init(int x) {i = x;}
void show() {cout << i;}
};
int static_type::i; // define i
int main()
{
// init static data before object creation
static_type::init(100);
static_type x;
x
...
Precisely when these
events occur is discussed here
...
The destructor functions for local objects are executed
in the reverse order of the constructor functions
...
Global constructors are executed in order of their declaration, within
the same file
...
Global destructors execute in reverse order after main( ) has
terminated
...
\n";
myclass local_ob2(4);
return 0;
}
It displays this output:
Initializing 1
Initializing 2
Initializing 3
This will not be first line displayed
...
The Scope Resolution Operator
As you know, the :: operator links a class name with a member name in order to
tell the compiler what class the member belongs to
...
For example, consider this
fragment:
int i;
// global i
void f()
{
int i; // local i
i = 10; // uses local i
...
...
But what if
function f( ) needs to access the global version of i? It may do so by preceding the i
with the :: operator, as shown here
...
...
}
319
320
C++: The Complete Reference
Nested Classes
It is possible to define one class within another
...
Since
a class declaration does, in fact, define a scope, a nested class is valid only within the
scope of the enclosing class
...
Because of
C++'s flexible and powerful inheritance mechanism, the need for nested classes is
virtually nonexistent
...
For example, this is a valid C++ program:
#include
using namespace std;
void f();
int main()
{
f();
// myclass not known here
return 0;
}
void f()
{
class myclass {
int i;
public:
void put_i(int n) { i=n; }
int get_i() { return i; }
} ob;
ob
...
get_i();
}
When a class is declared within a function, it is known only to that function and
unknown outside of it
...
First, all member functions must be
defined within the class declaration
...
It may
access type names and enumerators defined by the enclosing function, however
...
Because of these restrictions, local
classes are not common in C++ programming
...
Objects are passed to functions through the use of the standard call-byvalue mechanism
...
However, the fact that a copy is created means, in essence, that another
object is created
...
The answer to these two questions may surprise you
...
#include
using namespace std;
class myclass {
int i;
public:
myclass(int n);
~myclass();
void set_i(int n) { i=n; }
int get_i() { return i; }
};
myclass::myclass(int n)
{
i = n;
cout << "Constructing " << i << "\n";
}
myclass::~myclass()
{
cout << "Destroying " << i << "\n";
}
void f(myclass ob);
321
322
C++: The Complete Reference
int main()
{
myclass o(1);
f(o);
cout << "This is i in main: ";
cout << o
...
set_i(2);
cout << "This is local i: " << ob
...
As the output illustrates, the constructor function
is not called when the copy of o (in main( )) is passed to ob (within f( ))
...
When you pass an object to a function, you want the current state of that
object
...
Thus, the constructor function cannot be executed when
the copy of an object is generated in a function call
...
(The copy
is destroyed like any other local variable, when the function terminates
...
This means that
the copy could be performing operations that will require a destructor function to
be called when the copy is destroyed
...
For this reason, the destructor
function must be executed when the copy is destroyed
...
However, when the copy of the
object inside the function is destroyed, its destructor function is called
...
This means
that the new object is an exact duplicate of the original
...
Even though objects are passed to functions
by means of the normal call-by-value parameter passing mechanism, which, in theory,
protects and insulates the calling argument, it is still possible for a side effect to occur
that may affect, or even damage, the object used as an argument
...
This will leave the original object damaged and effectively useless
...
Returning Objects
A function may return an object to the caller
...
#include
using namespace std;
class myclass {
int i;
public:
void set_i(int n) { i=n; }
int get_i() { return i; }
};
myclass f();
int main()
{
myclass o;
o = f();
// return object of type myclass
323
324
C++: The Complete Reference
cout << o
...
set_i(1);
return x;
}
When an object is returned by a function, a temporary object is automatically
created that holds the return value
...
After the value has been returned, this object is destroyed
...
For
example, if the object returned by the function has a destructor that frees dynamically
allocated memory, that memory will be freed even though the object that is receiving
the return value is still using it
...
Object Assignment
Assuming that both objects are of the same type, you can assign one object to another
...
For example, this program displays 99:
// Assigning objects
...
set_i(99);
ob2 = ob1; // assign data from ob1 to ob2
cout << "This is ob2's i: " << ob2
...
However, it is possible to overload the assignment operator and define some
other assignment procedure (see Chapter 15)
...
C++
Chapter 13
Arrays, Pointers, References,
and the Dynamic Allocation
Operators
327
328
C++: The Complete Reference
n Part One, pointers and arrays were examined as they relate to C++'s built-in
types
...
This chapter also looks at a
feature related to the pointer called a reference
...
I
Arrays of Objects
In C++, it is possible to have arrays of objects
...
For example, this
program uses a three-element array of objects:
#include
using namespace std;
class cl {
int i;
public:
void set_i(int j) { i=j; }
int get_i() { return i; }
};
int main()
{
cl ob[3];
int i;
for(i=0; i<3; i++) ob[i]
...
get_i() << "\n";
return 0;
}
This program displays the numbers 1, 2, and 3 on the screen
...
However, the exact form of the initialization list will be decided by the number of
parameters required by the object's constructor function
...
As each element in the array is created, a
Chapter 13:
Arrays, Pointers, References, and the Dynamic Allocation Operators
value from the list is passed to the constructor's parameter
...
get_i() << "\n";
return 0;
}
As before, this program displays the numbers 1, 2, and 3 on the screen
...
Of course, the short form used in the
program is more common
...
Thus, the short
form can only be used to initialize object arrays whose constructors only require one
argument
...
For example,
#include
using namespace std;
329
330
C++: The Complete Reference
class cl {
int h;
int i;
public:
cl(int j, int k) { h=j; i=k; } // constructor with 2 parameters
int get_i() {return i;}
int get_h() {return h;}
};
int main()
{
cl ob[3] = {
cl(1, 2), // initialize
cl(3, 4),
cl(5, 6)
};
int i;
for(i=0; i<3; i++) {
cout << ob[i]
...
get_i() << "\n";
}
return 0;
}
Here, cl's constructor has two parameters and, therefore, requires two arguments
...
Creating Initialized vs
...
Consider the following class
...
This implies that
any array declared of this type must be initialized
...
However,
as it stands, cl does not have a parameterless constructor
...
To
solve this problem, you need to overload the constructor function, adding one that
takes no parameters
...
class cl {
int i;
public:
cl() { i=0; } // called for non-initialized arrays
cl(int j) { i=j; } // called for initialized arrays
int get_i() { return i; }
};
Given this class, both of the following statements are permissible:
cl a1[3] = {3, 5, 6}; // initialized
cl a2[34]; // uninitialized
Pointers to Objects
Just as you can have pointers to other types of variables, you can have pointers to
objects
...
The next program illustrates how to access an
object given a pointer to it:
#include
using namespace std;
331
332
C++: The Complete Reference
class cl {
int i;
public:
cl(int j) { i=j; }
int get_i() { return i; }
};
int main()
{
cl ob(88), *p;
p = &ob; // get address of ob
cout << p->get_i(); // use -> to call get_i()
return 0;
}
As you know, when a pointer is incremented, it points to the next element of its
type
...
In general, all
pointer arithmetic is relative to the base type of the pointer
...
) The same is true of pointers to
objects
...
For example, this is a valid C++ program that
displays the number 1 on the screen:
#include
using namespace std;
class cl {
public:
int i;
cl(int j) { i=j; }
};
int main()
{
cl ob(1);
int *p;
p = &ob
...
i
cout << *p; // access ob
...
It is irrelevant
that i is a member of ob in this situation
...
For example, given:
333
334
C++: The Complete Reference
int *pi;
float *pf;
in C++, the following assignment is illegal:
pi = pf; // error -- type mismatch
Of course, you can override any type incompatibilities using a cast, but doing so
bypasses C++'s type-checking mechanism
...
The this Pointer
When a member function is called, it is automatically passed an implicit argument that
is a pointer to the invoking object (that is, the object on which the function is called)
...
To understand this, first consider a program that creates a
class called pwr that computes the result of a number raised to some power:
#include
using namespace std;
class pwr {
double b;
int e;
double val;
public:
pwr(double base, int exp);
double get_pwr() { return val; }
};
pwr::pwr(double base, int exp)
{
b = base;
e = exp;
val = 1;
if(exp==0) return;
for( ; exp>0; exp--) val = val * b;
}
Chapter 13:
Arrays, Pointers, References, and the Dynamic Allocation Operators
int main()
{
pwr x(4
...
5, 1), z(5
...
get_pwr() << " ";
cout << y
...
get_pwr() << "\n";
return 0;
}
Within a member function, the members of a class can be accessed directly, without
any object or class qualification
...
However, the same statement can also be written like this:
this->b = base;
The this pointer points to the object that invoked pwr( )
...
For example, if pwr( ) had been invoked by x (as in x(4
...
Writing the statement
without using this is really just shorthand
...
However, the this pointer is very important
when operators are overloaded and whenever a member function must utilize a
pointer to the object that invoked it
...
Therefore, get_pwr( ) could also be rewritten as shown here:
double get_pwr() { return this->val; }
In this case, if get_pwr( ) is invoked like this:
y
...
Two final points about this
...
Second, static member functions do not have a
this pointer
...
However,
there is an important exception to this rule that relates only to derived classes
...
Further, assume that D is derived from the
base class B
...
More generally, a base class pointer can also be used as a pointer to an object of any
class derived from that base
...
A pointer of type D * may not point to an object of type B
...
That is, you won't be
able to access any members added by the derived class
...
)
Here is a short program that illustrates the use of a base pointer to access
derived objects
...
You can't access element of
a derived class using a base class pointer
...
Although you must be careful, it is possible to cast a base pointer into a pointer of
the derived type to access a member of the derived class through the base pointer
...
For this reason, when a base pointer is pointing to a derived object,
incrementing the pointer does not cause it to point to the next object of the derived
type
...
This, of
337
338
C++: The Complete Reference
course, usually spells trouble
...
// This program contains an error
...
set_i(1);
d[1]
...
Chapter 13:
Arrays, Pointers, References, and the Dynamic Allocation Operators
Pointers to Class Members
C++ allows you to generate a special type of pointer that "points" generically to a
member of a class, not to a specific instance of that member in an object
...
A pointer to
a member is not the same as a normal C++ pointer
...
Since member pointers are not true pointers, the
...
To access a member of a class given a pointer to it, you must use the special
pointer-to-member operators
...
Their job is to allow you to access a member of
a class given a pointer to that member
...
*data << " " << ob2
...
*func)() << " ";
cout << (ob2
...
Note
carefully the syntax of each declaration
...
The program also creates
objects of cl called ob1 and ob2
...
Next, the program obtains the addresses of val and
double_val( )
...
Next, to display the values
of each object's val, each is accessed through data
...
The extra parentheses are necessary in order to
correctly associate the
...
When you are accessing a member of an object by using an object or a reference
(discussed later in this chapter), you must use the
...
However, if you are
using a pointer to the object, you need to use the –>* operator, as illustrated in this
version of the preceding program:
#include
using namespace std;
class cl {
public:
cl(int i) { val=i; }
int val;
int double_val() { return val+val; }
};
int main()
{
int cl::*data; // data member pointer
int (cl::*func)(); // function member pointer
cl ob1(1), ob2(2); // create objects
cl *p1, *p2;
p1 = &ob1; // access objects through a pointer
p2 = &ob2;
data = &cl::val; // get offset of val
func = &cl::double_val; // get offset of double_val()
cout << "Here are values: ";
cout << p1->*data << " " << p2->*data << "\n";
cout << "Here they are doubled: ";
Chapter 13:
Arrays, Pointers, References, and the Dynamic Allocation Operators
cout << (p1->*func)() << " ";
cout << (p2->*func)() << "\n";
return 0;
}
In this version, p1 and p2 are pointers to objects of type cl
...
Remember, pointers to members are different from pointers to specific instances of
elements of an object
...
val // this is address of a specific val
d = &cl::val // this is offset of generic val
Here, p is a pointer to an integer inside a specific object
...
In general, pointer-to-member operators are applied in special-case situations
...
References
C++ contains a feature that is related to the pointer called a reference
...
There are three ways that a reference can be used: as a
function parameter, as a function return value, or as a stand-alone reference
...
Reference Parameters
Probably the most important use for a reference is to allow you to create functions that
automatically use call-by-reference parameter passing
...
When using call-by-value, a copy of the argument is passed to the
function
...
By
default, C++ uses call-by-value, but it provides two ways to achieve call-by-reference
parameter passing
...
Second, you
341
342
C++: The Complete Reference
can use a reference parameter
...
To fully understand what a reference parameter is and why it is valuable, we will
begin by reviewing how a call-by-reference can be generated using a pointer
parameter
...
// Manually create a call-by-reference using a pointer
...
Therefore, neg( ) must be explicitly called with the address of x
...
This is
how you generate a "manual" call-by-reference in C++, and it is the only way to obtain
a call-by-reference using the C subset
...
To create a reference parameter, precede the parameter's name with an &
...
Any operations that are applied to i actually affect the calling
argument
...
Once i has been made into a reference, it is no
longer necessary (or even legal) to apply the * operator
...
Further, when calling neg( ), it is no longer necessary (or legal) to precede
the argument's name with the & operator
...
Here is the reference version of the preceding program:
// Use a reference parameter
...
Therefore, in the preceding program,
the statement
i = -i ;
actually operates on x, not on a copy of x
...
Also, inside the function, the reference parameter is used directly
343
344
C++: The Complete Reference
without the need to apply the * operator
...
Inside the function, it is not possible to change what the reference parameter is
pointing to
...
It does not cause i to
point to some new location
...
This program uses reference parameters to swap the
values of the variables it is called with
...
#include
using namespace std;
void swap(int &i, int &j);
int main()
{
int a, b, c, d;
a
b
c
d
=
=
=
=
1;
2;
3;
4;
cout << "a and b: " << a << " " << b << "\n";
swap(a, b); // no & operator needed
cout << "a and b: " << a << " " << b << "\n";
cout << "c and d: " << c << " " << d << "\n";
swap(c, d);
cout << "c and d: " << c << " " << d << "\n";
return 0;
}
void swap(int &i, int &j)
{
int t;
Chapter 13:
Arrays, Pointers, References, and the Dynamic Allocation Operators
t = i; // no * operator needed
i = j;
j = t;
}
This program displays the following:
a
a
c
c
and
and
and
and
b:
b:
d:
d:
1
2
3
4
2
1
4
3
Passing References to Objects
In Chapter 12 it was explained that when an object is passed as an argument to a
function, a copy of that object is made
...
If for some reason you do not want the destructor function to be
called, simply pass the object by reference
...
) When you pass by reference, no copy of the object is made
...
For example, try this program:
#include
using namespace std;
class cl {
int id;
public:
int i;
cl(int i);
~cl();
void neg(cl &o) { o
...
i; } // no temporary created
};
cl::cl(int num)
{
cout << "Constructing " << num << "\n";
id = num;
}
cl::~cl()
345
346
C++: The Complete Reference
{
cout << "Destructing " << id << "\n";
}
int main()
{
cl o(1);
o
...
neg(o);
cout << o
...
Had o been passed
by value, a second object would have been created inside neg( ), and the destructor
would have been called a second time when that object was destroyed at the time
neg( ) terminated
...
The arrow operator is reserved for use with
pointers only
...
One other point: Passing all but the smallest objects by reference is faster than
passing them by value
...
Thus, large objects
take a considerable number of CPU cycles to push onto and pop from the stack
...
This has the rather startling effect of allowing a
function to be used on the left side of an assignment statement! For example, consider
this simple program:
Chapter 13:
Arrays, Pointers, References, and the Dynamic Allocation Operators
#include
using namespace std;
char &replace(int i); // return a reference
char s[80] = "Hello There";
int main()
{
replace(5) = 'X'; // assign X to space after Hello
cout << s;
return 0;
}
char &replace(int i)
{
return s[i];
}
This program replaces the space between Hello and There with an X
...
Take a look at how this is accomplished
...
As replace( ) is coded, it returns a
reference to the element of s that is specified by its argument i
...
One thing to beware of when returning references is that the object being referred
to does not go out of scope after the function terminates
...
However, you can
declare a reference that is simply a variable
...
When you create an independent reference, all you are creating is another name for
an object variable
...
The reason for this is easy to understand
...
Therefore, it must be initialized when
it is declared
...
)
The following program illustrates an independent reference:
347
348
C++: The Complete Reference
#include
using namespace std;
int main()
{
int a;
int &ref = a; // independent reference
a = 10;
cout << a << " " << ref << "\n";
ref = 100;
cout << a << " " << ref << "\n";
int b = 19;
ref = b; // this puts b's value into a
cout << a << " " << ref << "\n";
ref--; // this decrements a
// it does not affect what ref refers to
cout << a << " " << ref << "\n";
return 0;
}
The program displays this output:
10 10
100 100
19 19
18 18
Actually, independent references are of little real value because each one is,
literally, just another name for another variable
...
References to Derived Types
Similar to the situation as described for pointers earlier, a base class reference can be
used to refer to an object of a derived class
...
A base class reference parameter can receive objects of
the base class as well as any other type derived from that base
...
You cannot reference
another reference
...
You
cannot create arrays of references
...
You
cannot reference a bit-field
...
Null references are prohibited
...
For
example, here are two functionally equivalent declarations:
int& p; // & associated with type
int &p; // & associated with variable
Associating the * or & with the type name reflects the desire of some programmers
for C++ to contain a separate pointer type
...
Thus, misleading
declarations are easily created
...
int* a, b;
Here, b is declared as an integer (not an integer pointer) because, as specified by the
C++ syntax, when used in a declaration, the * (or &) is linked to the individual variable
that it precedes, not to the type that it follows
...
This visual confusion not only misleads novice C++ programmers,
but occasionally old pros, too
...
Thus, if you prefer to associate the *
or & with the type rather than the variable, feel free to do so
...
C++'s Dynamic Allocation Operators
C++ provides two dynamic allocation operators: new and delete
...
Dynamic allocation is an important part
349
350
C++: The Complete Reference
of almost all real-world programs
...
These are included
for the sake of compatibility with C
...
The new operator allocates memory and returns a pointer to the start of it
...
The general forms of
new and delete are shown here:
p_var = new type;
delete p_var;
Here, p_var is a pointer variable that receives a pointer to memory that is large enough
to hold an item of type type
...
If there is insufficient available
memory to fill an allocation request, then new will fail and a bad_alloc exception will be
generated
...
Your program should handle
this exception and take appropriate action if a failure occurs
...
) If this exception is not handled by your program, then your
program will be terminated
...
The
trouble is that not all compilers, especially older ones, will have implemented new in
compliance with Standard C++
...
Later, this was changed such that new caused an exception on failure
...
Thus, new has been implemented
differently, at different times, by compiler manufacturers
...
Since Standard C++ specifies that new generates an exception on failure, this is the
way the code in this book is written
...
Here is a program that allocates memory to hold an integer:
#include
#include
using namespace std;
int main()
{
int *p;
try {
Chapter 13:
Arrays, Pointers, References, and the Dynamic Allocation Operators
p = new int; // allocate space for an int
} catch (bad_alloc xa) {
cout << "Allocation Failure\n";
return 1;
}
*p = 100;
cout << "At " << p << " ";
cout << "is the value " << *p << "\n";
delete p;
return 0;
}
This program assigns to p an address in the heap that is large enough to hold an
integer
...
Finally, it frees the dynamically allocated memory
...
The delete operator must be used only with a valid pointer previously allocated by
using new
...
Although new and delete perform functions similar to malloc( ) and free( ), they
have several advantages
...
You do not need to use the sizeof operator
...
Second,
new automatically returns a pointer of the specified type
...
Finally, both
new and delete can be overloaded, allowing you to create customized allocation systems
...
There is no guarantee that they are
mutually compatible
...
Here is the general form of new when an
initialization is included:
p_var = new var_type (initializer);
351
352
C++: The Complete Reference
Of course, the type of the initializer must be compatible with the type of data for which
memory is being allocated
...
To free an array, use this form of delete:
delete [ ] p_var;
Here, the [ ] informs delete that an array is being released
...
Chapter 13:
Arrays, Pointers, References, and the Dynamic Allocation Operators
#include
#include
using namespace std;
int main()
{
int *p, i;
try {
p = new int [10]; // allocate 10 integer array
} catch (bad_alloc xa) {
cout << "Allocation Failure\n";
return 1;
}
for(i=0; i<10; i++ )
p[i] = i;
for(i=0; i<10; i++)
cout << p[i] << " ";
delete [] p; // release the array
return 0;
}
Notice the delete statement
...
(As
you will see in the next section, this is especially important when you are allocating
arrays of objects
...
That is, you may not specify an initializer when allocating arrays
...
When you do this, an object is
created and a pointer is returned to it
...
When it is created, its constructor function (if it has one) is called
...
Here is a short program that creates a class called balance that links a person's
name with his or her account balance
...
353
354
C++: The Complete Reference
#include
#include
#include
using namespace std;
class balance {
double cur_bal;
char name[80];
public:
void set(double n, char *s) {
cur_bal = n;
strcpy(name, s);
}
void get_bal(double &n, char *s) {
n = cur_bal;
strcpy(s, name);
}
};
int main()
{
balance *p;
char s[80];
double n;
try {
p = new balance;
} catch (bad_alloc xa) {
cout << "Allocation Failure\n";
return 1;
}
p->set(12387
...
As stated, dynamically allocated objects may have constructors and destructors
...
Examine this version of the
previous program:
#include
#include
#include
using namespace std;
class balance {
double cur_bal;
char name[80];
public:
balance(double n, char *s) {
cur_bal = n;
strcpy(name, s);
}
~balance() {
cout << "Destructing ";
cout << name << "\n";
}
void get_bal(double &n, char *s) {
n = cur_bal;
strcpy(s, name);
}
};
int main()
{
balance *p;
char s[80];
double n;
// this version uses an initializer
try {
p = new balance (12387
...
You can allocate arrays of objects, but there is one catch
...
If you don't, the C++ compiler will not find a
matching constructor when you attempt to allocate the array and will not compile your
program
...
#include
#include
#include
using namespace std;
class balance {
double cur_bal;
char name[80];
public:
balance(double n, char *s) {
cur_bal = n;
strcpy(name, s);
}
balance() {} // parameterless constructor
~balance() {
cout << "Destructing ";
Chapter 13:
Arrays, Pointers, References, and the Dynamic Allocation Operators
cout << name << "\n";
}
void set(double n, char *s) {
cur_bal = n;
strcpy(name, s);
}
void get_bal(double &n, char *s) {
n = cur_bal;
strcpy(s, name);
}
};
int main()
{
balance *p;
char s[80];
double n;
int i;
try {
p = new balance [3]; // allocate entire array
} catch (bad_alloc xa) {
cout << "Allocation Failure\n";
return 1;
}
// note use of dot, not arrow operators
p[0]
...
87, "Ralph Wilson");
p[1]
...
00, "A
...
Conners");
p[2]
...
23, "I
...
Overdrawn");
for(i=0; i<3; i++) {
p[i]
...
Ralph Wilson's balance is: 12387
...
C
...
M
...
23
Destructing I
...
Overdrawn
Destructing A
...
Conners
Destructing Ralph Wilson
One reason that you need to use the delete [ ] form when deleting an array of
dynamically allocated objects is so that the destructor function can be called for each
object in the array
...
This form of new is most useful when you
are compiling older code with a modern C++ compiler
...
(This is common when updating C code to C++
...
The nothrow form of new works like the
original version of new from years ago
...
However, for
new code, exceptions provide a better alternative
...
The following program shows how to use the new(nothrow) alternative
...
#include
#include
using namespace std;
int main()
{
int *p, i;
p = new(nothrow) int[32]; // use nothrow option
if(!p) {
cout << "Allocation failure
...
The Placement Forms of new and delete
There is a special form of new, called the placement form, that can be used to specify an
alternative method of allocating memory
...
There is a default implementation of the
placement new operator, which has this general form:
p_var = new (location) type;
Here, location specifies an address that is simply returned by new
...
359
This page intentionally left blank
...
Function overloading is one of the defining aspects of the C++
programming language
...
Some of the most commonly
overloaded functions are constructors
...
Closely related to function overloading
are default arguments
...
T
Function Overloading
Function overloading is the process of using the same name for two or more functions
...
It is only through
these differences that the compiler knows which function to call in any
given situation
...
#include
using namespace std;
int myfunc(int i); // these differ in types of parameters
double myfunc(double i);
int main()
{
cout << myfunc(10) << " "; // calls myfunc(int i)
cout << myfunc(5
...
Two functions differing
only in their return types cannot be overloaded
...
Sometimes, two function declarations will appear to differ, when in fact they do not
...
void f(int *p);
void f(int p[]); // error, *p is same as p[]
Remember, to the compiler *p is the same as p[ ]
...
363
364
C++: The Complete Reference
Overloading Constructor Functions
Constructor functions can be overloaded; in fact, overloaded constructors are very
common
...
In this section, the first two of these are
examined
...
Overloading a Constructor to Gain Flexibility
Many times you will create a class for which there are two or more possible ways to
construct an object
...
This is a self-enforcing rule because if you attempt to create an
object for which there is no matching constructor, a compile-time error results
...
The user is free to
choose the best way to construct an object given the specific circumstance
...
Notice that
the constructor is overloaded two ways:
#include
#include
using namespace std;
class date {
int day, month, year;
public:
date(char *d);
date(int m, int d, int y);
void show_date();
};
// Initialize using string
...
date::date(int m, int d, int y)
{
Chapter 14:
Function Overloading, Copy Constructors, and Default Arguments
day = d;
month = m;
year = y;
}
void date::show_date()
{
cout << month << "/" << day;
cout << "/" << year << "\n";
}
int main()
{
date ob1(12, 4, 2001), ob2("10/22/2001");
ob1
...
show_date();
return 0;
}
In this program, you can initialize an object of type date, either by specifying the
date using three integers to represent the month, day, and year, or by using a string
that contains the date in this general form:
mm/dd/yyyy
Since both are common ways to represent a date, it makes sense that date allow both
when constructing an object
...
For example, in the following main( ),
the user is prompted for the date, which is input to array s
...
There is no need for it to be converted to any other form
...
int main()
{
char s[80];
365
366
C++: The Complete Reference
cout << "Enter new date: ";
cin >> s;
date d(s);
d
...
For example, if the date is generated by some sort of
computational method, then creating a date object using date(int, int, int) is the most
natural and appropriate constructor to employ
...
This increased
flexibility and ease of use are especially important if you are creating class libraries that
will be used by other programmers
...
This is especially important if you want to be able to create dynamic arrays
of objects of some class, since it is not possible to initialize a dynamically allocated
array
...
For example, the following program declares two arrays of type powers; one is
initialized and the other is not
...
#include
#include
using namespace std;
class powers {
int x;
public:
// overload constructor two ways
powers() { x = 0; } // no initializer
powers(int n) { x = n; } // initializer
int getx() { return x; }
Chapter 14:
Function Overloading, Copy Constructors, and Default Arguments
void setx(int i) { x = i; }
};
int main()
{
powers ofTwo[] = {1, 2, 4, 8, 16}; // initialized
powers ofThree[5]; // uninitialized
powers *p;
int i;
// show powers of two
cout << "Powers of two: ";
for(i=0; i<5; i++) {
cout << ofTwo[i]
...
setx(1);
ofThree[1]
...
setx(9);
ofThree[3]
...
setx(81);
// show powers of three
cout << "Powers of three: ";
for(i=0; i<5; i++) {
cout << ofThree[i]
...
setx(ofTwo[i]
...
getx() << " ";
}
cout << "\n\n";
delete [] p;
return 0;
}
In this example, both constructors are necessary
...
The
parameterized constructor is called to create the objects for the ofTwo array
...
Defining a copy constructor can help you prevent problems that might occur when one
object is used to initialize another
...
By default, when one object is used to initialize another, C++ performs a bitwise copy
...
Although this is perfectly adequate for many cases—and generally exactly what you
want to happen—there are situations in which a bitwise copy should not be used
...
For
example, assume a class called MyClass that allocates memory for each object when it is
created, and an object A of that class
...
Further, assume that A is used to initialize B, as shown here:
MyClass B = A;
If a bitwise copy is performed, then B will be an exact copy of A
...
Clearly, this is not the desired outcome
...
Remember, temporary
objects are automatically created to hold the return value of a function and they may
also be created in certain other circumstances
...
When a copy
constructor exists, the default, bitwise copy is bypassed
...
It is permissible
for a copy constructor to have additional parameters as long as they have default
arguments defined for them
...
It is important to understand that C++ defines two distinct types of situations in
which the value of one object is given to another
...
The second is
initialization, which can occur any of three ways:
s When one object explicitly initializes another, such as in a declaration
s When a copy of an object is made to be passed to a function
s When a temporary object is generated (most commonly, as a return value)
The copy constructor applies only to initializations
...
myclass x = y; // y explicitly initializing x
func(y);
// y passed as a parameter
y = func();
// y receiving a temporary, return object
Following is an example where an explicit copy constructor function is needed
...
(Chapter 15 shows a better way to create a safe array
that uses overloaded operators
...
369
370
C++: The Complete Reference
/* This program creates a "safe" array class
...
*/
#include
#include
#include
using namespace std;
class array {
int *p;
int size;
public:
array(int sz) {
try {
p = new int[sz];
} catch (bad_alloc xa) {
cout << "Allocation Failure\n";
exit(EXIT_FAILURE);
}
size = sz;
}
~array() { delete [] p; }
// copy constructor
array(const array &a);
void put(int i, int j) {
if(i>=0 && i
int get(int i) {
return p[i];
}
};
// Copy Constructor
array::array(const array &a) {
int i;
try {
p = new int[a
...
size; i++) p[i] = a
...
put(i, i);
for(i=9; i>=0; i--) cout << num
...
get(i);
return 0;
}
Let's look closely at what happens when num is used to initialize x in the statement
array x(num); // invokes copy constructor
The copy constructor is called, memory for the new array is allocated and stored in x
...
In this way, x and num have arrays
that contain the same values, but each array is separate and distinct
...
p
and x
...
) If the copy constructor had not
been created, the default bitwise initialization would have resulted in x and num
sharing the same memory for their arrays
...
p and x
...
)
Remember that the copy constructor is called only for initializations
...
array b(10);
b = a; // does not call copy constructor
371
372
C++: The Complete Reference
In this case, b = a performs the assignment operation
...
Therefore, in some cases, you may need to overload
the = operator as well as create a copy constructor to avoid certain types of problems
(see Chapter 15)
...
One reason to do
so is to assign the address of the function to a pointer and then call that function
through that pointer
...
However, for overloaded functions, the process requires a little more subtlety
...
However, if myfunc( ) is
overloaded, how does the compiler know which version's address to assign to p? The
answer is that it depends upon how p is declared
...
Both return int, but one takes a single
integer argument; the other requires two integer arguments
...
When fp is assigned the address of myfunc( ), C++ uses this information to
select the myfunc(int a) version of myfunc( )
...
In general, when you assign the address of an overloaded function to a function
pointer, it is the declaration of the pointer that determines which function's address is
obtained
...
The overload Anachronism
When C++ was created, the keyword overload was required to create an overloaded
function
...
Indeed, it is not even a
reserved word in Standard C++
...
Here is its general form:
overload func-name;
Here, func-name is the name of the function that you will be overloading
...
For example, this tells an
old-style compiler that you will be overloading a function called test( ):
overload test;
373
374
C++: The Complete Reference
Default Function Arguments
C++ allows a function to assign a parameter a default value when no argument
corresponding to that parameter is specified in a call to that function
...
For example,
this declares myfunc( ) as taking one double argument with a default value of 0
...
0)
{
//
...
234); // pass an explicit value
myfunc();
// let function use default
The first call passes the value 198
...
The second call automatically gives d the
default value zero
...
To handle the
widest variety of situations, quite frequently a function contains more parameters than
are required for its most common usage
...
For example, many of the C++ I/O functions make
use of default arguments for just this reason
...
The clrscr( ) function clears the screen
by outputting a series of linefeeds (not the most efficient way, but sufficient for this
example)
...
However, because some terminals can display more or less
than 25 lines (often depending upon what type of video mode is used), you can
override the default argument by specifying one explicitly
...
get();
clrscr(); // clears 25 lines
for(i=0; i<30; i++ ) cout << i << endl;
cin
...
However, it is still possible to
override the default and give size a different value when needed
...
To illustrate this usage, a function called iputs( ) is developed here
that automatically indents a string by a specified amount
...
Although there is nothing wrong with writing iputs( ) this
way, you can improve its usability by providing a default argument for the indent
parameter that tells iputs( ) to indent to the previously specified level
...
In this situation,
instead of having to supply the same indent argument over and over, you can give
375
376
C++: The Complete Reference
indent a default value that tells iputs( ) to indent to the level of the previous call
...
This value tells the function
to reuse the previous value
...
In the preceding example, the default
argument was specified in iputs( )'s prototype
...
Even though default arguments for the same function cannot
be redefined, you can specify different default arguments for each version of an
overloaded function
...
For example, it is incorrect to define iputs( ) like this:
// wrong!
void iputs(int indent = -1, char *str);
Once you begin to define parameters that take default values, you cannot specify a
nondefaulting parameter
...
You can also use default parameters in an object's constructor function
...
Its
constructor function defaults all dimensions to zero if no other arguments are supplied,
as shown here:
#include
using namespace std;
class cube {
int x, y, z;
public:
cube(int i=0, int j=0, int k=0) {
x=i;
y=j;
z=k;
}
int volume() {
return x*y*z;
}
377
378
C++: The Complete Reference
};
int main()
{
cube a(2,3,4), b;
cout << a
...
volume();
return 0;
}
There are two advantages to including default arguments, when appropriate, in a
constructor function
...
For example, if the parameters to cube( ) were
not given defaults, the second constructor shown here would be needed to handle the
declaration of b (which specified no arguments)
...
Default Arguments vs
...
The cube class's constructor just shown is one example
...
Imagine that you want to create two customized versions of the standard
strcat( ) function
...
The second version takes a third argument
that specifies the number of characters to concatenate
...
Thus, assuming that you call your customized functions mystrcat( ), they will
have the following prototypes:
void mystrcat(char *s1, char *s2, int len);
void mystrcat(char *s1, char *s2);
The first version would copy len characters from s2 to the end of s1
...
Chapter 14:
Function Overloading, Copy Constructors, and Default Arguments
While it would not be wrong to implement two versions of mystrcat( ) to create the
two versions that you desire, there is an easier way
...
The following
program demonstrates this
...
#include
#include
using namespace std;
void mystrcat(char *s1, char *s2, int len = -1);
int main()
{
char str1[80] = "This is a test";
char str2[80] = "0123456789";
mystrcat(str1, str2, 5); // concatenate 5 chars
cout << str1 << '\n';
strcpy(str1, "This is a test"); // reset str1
mystrcat(str1, str2); // concatenate entire string
cout << str1 << '\n';
return 0;
}
// A custom version of strcat()
...
However, if len is –1, as it will be when it is
allowed to default, mystrcat( ) concatenates the entire string pointed to by s2 onto s1
...
) By
using a default argument for len, it is possible to combine both operations into one
function
...
Using Default Arguments Correctly
Although default arguments can be a very powerful tool when used correctly, they can
also be misused
...
Toward
this end, all default arguments should reflect the way a function is generally used, or a
reasonable alternate usage
...
In fact, declaring
default arguments when there is insufficient basis for doing so destructures your code,
because they are liable to mislead and confuse anyone reading your program
...
That is, the
accidental use of a default argument should not cause a catastrophe
...
When this happens, the situation is said to be ambiguous
...
By far the main cause of ambiguity involves C++'s automatic type conversions
...
For example, consider this
fragment:
int myfunc(double d);
//
...
In C++, very few type conversions of this sort
are actually disallowed
...
For example, consider the following program:
#include
using namespace std;
float myfunc(float i);
double myfunc(double i);
int main()
{
cout << myfunc(10
...
In the unambiguous line, myfunc(double) is called because, unless
explicitly specified as float, all floating-point constants in C++ are automatically of
type double
...
However, when myfunc( ) is called by
using the integer 10, ambiguity is introduced because the compiler has no way of
knowing whether it should be converted to a float or to a double
...
As the preceding example illustrates, it is not the overloading of myfunc( ) relative
to double and float that causes the ambiguity
...
Put differently, the
error is not caused by the overloading of myfunc( ), but by the specific invocation
...
However, when
myfunc( ) is called by using the integer 88, the compiler does not know which function
to call
...
To see how, examine this program:
#include
using namespace std;
int myfunc(int i);
int myfunc(int i, int j=1);
int main()
{
cout << myfunc(4, 5) << " "; // unambiguous
cout << myfunc(10); // ambiguous
Chapter 14:
Function Overloading, Copy Constructors, and Default Arguments
return 0;
}
int myfunc(int i)
{
return i;
}
int myfunc(int i, int j)
{
return i*j;
}
Here, in the first call to myfunc( ), two arguments are specified; therefore, no
ambiguity is introduced and myfunc(int i, int j) is called
...
Some types of overloaded functions are simply inherently ambiguous even if, at
first, they may not seem so
...
// This program contains an error
...
In this situation, the compiler has no way of knowing which
version of the function is intended when it is called
...
C++
Chapter 15
Operator Overloading
385
386
C++: The Complete Reference
losely related to function overloading is operator overloading
...
For example, a class that maintains a stack might
overload + to perform a push operation and – – to perform a pop
...
Instead, the type of objects it can be
applied to is expanded
...
It allows
the full integration of new class types into the programming environment
...
Operator overloading also forms the
basis of C++'s approach to I/O
...
An operator function defines
the operations that the overloaded operator will perform relative to the
class upon which it will work
...
Operator functions can be either members or nonmembers of a class
...
The way operator functions are written differs between member and
nonmember functions
...
C
Creating a Member Operator Function
A member operator function takes this general form:
ret-type class-name::operator#(arg-list)
{
// operations
}
Often, operator functions return an object of the class they operate on, but ret-type can
be any valid type
...
When you create an operator function,
substitute the operator for the #
...
When you are overloading a unary operator, arg-list will be empty
...
(The reasons
for this seemingly unusual situation will be made clear in a moment
...
This program creates a class
called loc, which stores longitude and latitude values
...
Examine this program carefully, paying special attention to the
definition of operator+( ):
Chapter 15:
#include
using namespace std;
class loc {
int longitude, latitude;
public:
loc() {}
loc(int lg, int lt) {
longitude = lg;
latitude = lt;
}
void show() {
cout << longitude << " ";
cout << latitude << "\n";
}
loc operator+(loc op2);
};
// Overload + for loc
...
longitude = op2
...
latitude = op2
...
show(); // displays 10 20
ob2
...
show(); // displays 15 50
return 0;
}
Operator Overloading
387
388
C++: The Complete Reference
As you can see, operator+( ) has only one parameter even though it overloads the
binary + operator
...
) The reason that operator+( ) takes only one parameter
is that the operand on the left side of the + is passed implicitly to the function through
the this pointer
...
The fact that
the left operand is passed using this also implies one important point: When binary
operators are overloaded, it is the object on the left that generates the call to the
operator function
...
By doing so, it allows the operator to be used in larger
expressions
...
Further, having operator+( ) return an object of type loc makes possible the
following statement:
(ob1+ob2)
...
It is important to understand that an operator function can return any type and that
the type returned depends solely upon your specific application
...
One last point about the operator+( ) function: It does not modify either operand
...
(For example, 5+7 yields 12, but
neither 5 nor 7 is changed
...
The next program adds three additional overloaded operators to the loc class: the –,
the =, and the unary ++
...
#include
using namespace std;
class loc {
int longitude, latitude;
Chapter 15:
public:
loc() {} // needed to construct temporaries
loc(int lg, int lt) {
longitude = lg;
latitude = lt;
}
void show() {
cout << longitude << " ";
cout << latitude << "\n";
}
loc
loc
loc
loc
operator+(loc op2);
operator-(loc op2);
operator=(loc op2);
operator++();
};
// Overload + for loc
...
longitude = op2
...
latitude = op2
...
loc loc::operator-(loc op2)
{
loc temp;
// notice order of operands
temp
...
longitude;
temp
...
latitude;
return temp;
}
// Overload asignment for loc
...
longitude;
latitude = op2
...
e
...
loc loc::operator++()
{
longitude++;
latitude++;
return *this;
}
int main()
{
loc ob1(10, 20), ob2( 5, 30), ob3(90, 90);
ob1
...
show();
++ob1;
ob1
...
show(); // displays 12 22
ob2
...
show(); // displays 90 90
ob2
...
Notice the order of the operands in the
subtraction
...
Because it is the object on
the left that generates the call to the operator–( ) function, op2's data must be
Chapter 15:
Operator Overloading
subtracted from the data pointed to by this
...
In C++, if the = is not overloaded, a default assignment operation is created
automatically for any class you define
...
By overloading the =, you can define explicitly
what the assignment does relative to a class
...
Notice that the operator=( ) function returns *this, which is the object that
generated the call
...
As you can see, it takes no parameters
...
Notice that both operator=( ) and operator++( ) alter the value of an operand
...
In the case of the ++, the operand is
incremented
...
Creating Prefix and Postfix Forms of the
Increment and Decrement Operators
In the preceding program, only the prefix form of the increment operator was
overloaded
...
To accomplish this, you must
define two versions of the operator++( ) function
...
The other is declared like this:
loc operator++(int x);
If the ++ precedes its operand, the operator++( ) function is called
...
The preceding example can be generalized
...
// Prefix increment
type operator++( ) {
// body of prefix operator
}
391
392
C++: The Complete Reference
// Postfix increment
type operator++(int x) {
// body of postfix operator
}
// Prefix decrement
type operator– –( ) {
// body of prefix operator
}
// Postfix decrement
type operator– –(int x) {
// body of postfix operator
}
You should be careful when working with older C++ programs where the increment
and decrement operators are concerned
...
The prefix
form was used for both
...
For
example, this function overloads += relative to loc:
loc loc::operator+=(loc op2)
{
longitude = op2
...
latitude + latitude;
return *this;
}
When overloading one of these operators, keep in mind that you are simply
combining an assignment with another type of operation
...
You cannot alter the
precedence of an operator
...
(You can choose to ignore an operand, however
...
Finally,
these operators cannot be overloaded:
...
For example, if you want to overload the + operator in such a way that it
writes I like C++ 10 times to a disk file, you can do so
...
When someone reading your program sees a statement
like Ob1+Ob2, he or she expects something resembling addition to be taking
place—not a disk access, for example
...
One
good example where decoupling is successful is found in the way C++ overloads the
<< and >> operators for I/O
...
In general, however, it is best to stay
within the context of the expected meaning of an operator when overloading it
...
However, a derived class is free to overload any operator (including those overloaded
by the base class) it chooses relative to itself
...
Since a friend function is not a member of the class, it does
not have a this pointer
...
This means that a friend function that overloads a binary operator
has two parameters, and a friend function that overloads a unary operator has one
parameter
...
In this program, the operator+( ) function is made into a friend:
#include
using namespace std;
class loc {
int longitude, latitude;
public:
393
394
C++: The Complete Reference
loc() {} // needed to construct temporaries
loc(int lg, int lt) {
longitude = lg;
latitude = lt;
}
void show() {
cout << longitude << " ";
cout << latitude << "\n";
}
friend loc operator+(loc op1, loc op2); // now a friend
loc operator-(loc op2);
loc operator=(loc op2);
loc operator++();
};
// Now, + is overloaded using friend function
...
longitude = op1
...
longitude;
temp
...
latitude + op2
...
loc loc::operator-(loc op2)
{
loc temp;
// notice order of operands
temp
...
longitude;
temp
...
latitude;
return temp;
}
// Overload assignment for loc
...
longitude;
latitude = op2
...
e
...
loc loc::operator++()
{
longitude++;
latitude++;
return *this;
}
int main()
{
loc ob1(10, 20), ob2( 5, 30);
ob1 = ob1 + ob2;
ob1
...
First, you may
not overload the =, ( ), [ ], or –> operators by using a friend function
...
Using a Friend to Overload ++ or – –
If you want to use a friend function to overload the increment or decrement operators,
you must pass the operand as a reference parameter
...
Assuming that you stay true to the original meaning of the
++ and – – operators, these operations imply the modification of the operand they
operate upon
...
This means that a friend operator function
has no way to modify the operand
...
However, you can remedy this
situation by specifying the parameter to the friend operator function as a reference
parameter
...
For example, this program uses friend functions to
overload the prefix versions of ++ and – – operators relative to the loc class:
#include
using namespace std;
class loc {
int longitude, latitude;
public:
loc() {}
loc(int lg, int lt) {
longitude = lg;
latitude = lt;
}
void show() {
cout << longitude << " ";
cout << latitude << "\n";
}
loc operator=(loc op2);
friend loc operator++(loc &op);
friend loc operator--(loc &op);
};
// Overload assignment for loc
...
longitude;
latitude = op2
...
e
...
loc operator++(loc &op)
{
Chapter 15:
Operator Overloading
op
...
latitude++;
return op;
}
// Make op-- a friend; use reference
...
longitude--;
op
...
show();
++ob1;
ob1
...
show(); // displays 12 22
--ob2;
ob2
...
For example, this
shows the prototype for the friend, postfix version of the increment operator relative
to loc
...
In those cases, it is usually best to overload by
using member functions
...
Let's examine this
case now
...
Further, a pointer to that object is passed in the this pointer
...
Given an object of that class called Ob, the following expression
is valid:
Ob + 100 // valid
In this case, Ob generates the call to the overloaded + function, and the addition is
performed
...
Since an integer is a built-in type,
no operation between an integer and an object of Ob's type is defined
...
As you can imagine, in some applications,
having to always position the object on the left could be a significant burden and cause
of frustration
...
When this is done, both arguments are explicitly passed to the
operator function
...
Thus, when you overload
an operator by using two friend functions, the object may appear on either the left or
right side of the operator
...
loc operator+(loc op1, int op2)
{
loc temp;
temp
...
longitude + op2;
temp
...
latitude + op2;
return temp;
}
// + is overloaded for int + loc
...
longitude = op1 + op2
...
latitude = op1 + op2
...
show();
Operator Overloading
399
400
C++: The Complete Reference
ob2
...
show();
ob1 = ob2 + 10; // both of these
ob3 = 10 + ob2; // are valid
ob1
...
show();
return 0;
}
Overloading new and delete
It is possible to overload new and delete
...
For example, you may want allocation routines
that automatically begin using a disk file as virtual memory when the heap has
been exhausted
...
The skeletons for the functions that overload new and delete are shown here:
// Allocate an object
...
Throw bad_alloc on failure
...
*/
return pointer_to_memory;
}
// Delete an object
...
Destructor called automatically
...
(size_t is essentially an unsigned integer
...
This is the amount of memory that your version of new must allocate
...
Beyond these constraints, the
Chapter 15:
Operator Overloading
overloaded new function can do anything else you require
...
The delete function receives a pointer to the region of memory to be freed
...
When an object is
deleted, its destructor function is automatically called
...
They may also be overloaded relative to one or
more classes
...
For the sake of simplicity, no new allocation scheme will be used
...
(In your own application, you may, of course, implement any alternative
allocation scheme you like
...
For example, here the new and delete operators are
overloaded for the loc class:
#include
#include
#include
using namespace std;
class loc {
int longitude, latitude;
public:
loc() {}
loc(int lg, int lt) {
longitude = lg;
latitude = lt;
}
void show() {
cout << longitude << " ";
cout << latitude << "\n";
}
void *operator new(size_t size);
void operator delete(void *p);
};
// new overloaded relative to loc
...
\n";
p = malloc(size);
if(!p) {
bad_alloc ba;
throw ba;
}
return p;
}
// delete overloaded relative to loc
...
\n";
free(p);
}
int main()
{
loc *p1, *p2;
try {
p1 = new loc (10, 20);
} catch (bad_alloc xa) {
cout << "Allocation error for p1
...
\n";
return 1;;
}
p1->show();
p2->show();
delete p1;
Chapter 15:
Operator Overloading
delete p2;
return 0;
}
Output from this program is shown here
...
new
...
delete
...
The overloaded
operators are only applied to the types for which they are defined
...
When new and delete are overloaded globally, C++'s default
new and delete are ignored and the new operators are used for all allocation
requests
...
In other words, when new or delete are
encountered, the compiler first checks to see whether they are defined relative to the
class they are operating on
...
If not, C++ uses the
globally defined new and delete
...
To see an example of overloading new and delete globally, examine this program:
#include
#include
#include
using namespace std;
class loc {
403
404
C++: The Complete Reference
int longitude, latitude;
public:
loc() {}
loc(int lg, int lt) {
longitude = lg;
latitude = lt;
}
void show() {
cout << longitude << " ";
cout << latitude << "\n";
}
};
// Global new
void *operator new(size_t size)
{
void *p;
p = malloc(size);
if(!p) {
bad_alloc ba;
throw ba;
}
return p;
}
// Global delete
void operator delete(void *p)
{
free(p);
}
int main()
{
loc *p1, *p2;
float *f;
try {
p1 = new loc (10, 20);
} catch (bad_alloc xa) {
Chapter 15:
Operator Overloading
cout << "Allocation error for p1
...
\n";
return 1;;
}
try {
f = new float; // uses overloaded new, too
} catch (bad_alloc xa) {
cout << "Allocation error for f
...
10F;
cout << *f << "\n";
p1->show();
p2->show();
delete p1;
delete p2;
delete f;
return 0;
}
Run this program to prove to yourself that the built-in new and delete operators
have indeed been overloaded
...
To allocate and free arrays,
you must use these forms of new and delete
...
void *operator new[](size_t size)
{
/* Perform allocation
...
Constructor for each element called automatically
...
void operator delete[](void *p)
{
/* Free memory pointed to by p
...
*/
}
When allocating an array, the constructor function for each object in the array is
automatically called
...
You do not have to provide explicit code to accomplish these actions
...
#include
#include
#include
using namespace std;
class loc {
int longitude, latitude;
public:
loc() {longitude = latitude = 0;}
loc(int lg, int lt) {
longitude = lg;
latitude = lt;
}
void show() {
cout << longitude << " ";
cout << latitude << "\n";
}
void *operator new(size_t size);
Chapter 15:
void operator delete(void *p);
void *operator new[](size_t size);
void operator delete[](void *p);
};
// new overloaded relative to loc
...
\n";
p = malloc(size);
if(!p) {
bad_alloc ba;
throw ba;
}
return p;
}
// delete overloaded relative to loc
...
\n";
free(p);
}
// new overloaded for loc arrays
...
\n";
p = malloc(size);
if(!p) {
bad_alloc ba;
throw ba;
}
return p;
}
Operator Overloading
407
408
C++: The Complete Reference
// delete overloaded for loc arrays
...
\n";
return 1;;
}
try {
p2 = new loc [10]; // allocate an array
} catch (bad_alloc xa) {
cout << "Allocation error for p2
...
show();
delete p1; // free an object
delete [] p2; // free an array
return 0;
}
Overloading the nothrow Version of new and delete
You can also create overloaded nothrow versions of new and delete
...
Chapter 15:
Operator Overloading
// Nothrow version of new
...
if(success) return pointer_to_memory;
else return 0;
}
// Nothrow version of new for arrays
...
if(success) return pointer_to_memory;
else return 0;
}
void operator delete(void *p, const nothrow_t &n)
{
// free memory
}
void operator delete[](void *p, const nothrow_t &n)
{
// free memory
}
The type nothrow_t is defined in
...
The
nothrow_t parameter is unused
...
The operators that perform these functions are the [ ], ( ), and –>,
respectively
...
One important restriction applies to overloading these three operators: They must
be nonstatic member functions
...
Overloading [ ]
In C++, the [ ] is considered a binary operator when you are overloading it
...
}
Technically, the parameter does not have to be of type int, but an operator[ ]( ) function
is typically used to provide array subscripting, and as such, an integer value is
generally used
...
operator[](3)
That is, the value of the expression within the subscripting operators is passed to the
operator[ ]( ) function in its explicit parameter
...
In the following program, atype declares an array of three integers
...
The overloaded
operator[ ]( ) function returns the value of the array as indexed by the value of its
parameter
...
To do this, simply specify the
return value of operator[ ]( ) as a reference
...
(Of
course, it may still be used on the right side as well
...
As you know, in C++, it is possible to
overrun (or underrun) an array boundary at run time without generating a run-time
error message
...
For example, this program adds a range check to the
preceding program and proves that it works:
// A safe array example
...
int &atype::operator[](int i)
{
if(i<0 || i> 2) {
cout << "Boundary Error\n";
exit(1);
}
return a[i];
}
int main()
{
atype ob(1, 2, 3);
Chapter 15:
Operator Overloading
cout << ob[1]; // displays 2
cout << " ";
ob[1] = 25; // [] appears on left
cout << ob[1]; // displays 25
ob[3] = 44; // generates runtime error, 3 out-of-range
return 0;
}
In this program, when the statement
ob[3] = 44;
executes, the boundary error is intercepted by operator[]( ), and the program is
terminated before any damage can be done
...
)
Overloading ( )
When you overload the ( ) function call operator, you are not, per se, creating a new
way to call a function
...
Let's begin with an example
...
34, "hi");
translates into this call to the operator( ) function
...
operator()(10, 23
...
When you use the ( ) operator in your program, the
413
414
C++: The Complete Reference
arguments you specify are copied to those parameters
...
Here is an example of overloading ( ) for the loc class
...
#include
using namespace std;
class loc {
int longitude, latitude;
public:
loc() {}
loc(int lg, int lt) {
longitude = lg;
latitude = lt;
}
void show() {
cout << longitude << " ";
cout << latitude << "\n";
}
loc operator+(loc op2);
loc operator()(int i, int j);
};
// Overload ( ) for loc
...
loc loc::operator+(loc op2)
{
loc temp;
temp
...
longitude + longitude;
temp
...
latitude + latitude;
Chapter 15:
Operator Overloading
return temp;
}
int main()
{
loc ob1(10, 20), ob2(1, 1);
ob1
...
show();
ob1 = ob2 + ob1(10, 10); // can be used in expressions
ob1
...
10 20
7 8
11 11
Remember, when overloading ( ), you can use any type of parameters and return
any type of value
...
You
can also specify default arguments
...
Its general usage is shown here:
object->element;
Here, object is the object that activates the call
...
The element must be
some member accessible within the object
...
i and ob–>i when operator–>( ) returns the this pointer:
415
416
C++: The Complete Reference
#include
using namespace std;
class myclass {
public:
int i;
myclass *operator->() {return this;}
};
int main()
{
myclass ob;
ob->i = 10; // same as ob
...
i << " " << ob->i;
return 0;
}
An operator–>( ) function must be a member of the class upon which it works
...
The comma is a binary operator, and like all
overloaded operators, you can make an overloaded comma perform any operation you
want
...
The rightmost value becomes the result of the comma operation
...
Here is a program that illustrates the effect of overloading the comma operator
...
longitude = op2
...
latitude = op2
...
longitude << " " << op2
...
longitude = op2
...
latitude = op2
...
show();
ob2
...
show();
417
418
C++: The Complete Reference
cout << "\n";
ob1 = (ob1, ob2+ob2, ob3);
ob1
...
Remember, the left-hand operand is passed via this, and its value is discarded by
the operator,( ) function
...
This causes the overloaded comma to behave similarly to its default
operation
...
C++
Chapter 16
Inheritance
419
420
C++: The Complete Reference
nheritance is one of the cornerstones of OOP because it allows the creation of
hierarchical classifications
...
This class may then be inherited by
other, more specific classes, each adding only those things that are unique to the
inheriting class
...
The class that does the inheriting is called the derived class
...
In this way, multiple
inheritance is achieved
...
Inheritance was introduced in
Chapter 11
...
I
Base-Class Access Control
When a class inherits another, the members of the base class become members of the
derived class
...
The base-class access specifier must be either public, private, or protected
...
If the derived class is a struct, then public is the default in the absence of an
explicit access specifier
...
(The protected specifier is examined in the next section
...
In all cases, the base's private elements
remain private to the base and are not accessible by members of the derived class
...
set(1, 2); // access member of base
ob
...
showk(); // uses member of derived class
return 0;
}
When the base class is inherited by using the private access specifier, all public and
protected members of the base class become private members of the derived class
...
#include
using namespace std;
class base {
int i, j;
public:
void set(int a, int b) { i=a; j=b; }
void show() { cout << i << " " << j << "\n";}
};
// Public elements of base are private in derived
...
set(1, 2); // error, can't access set()
ob
...
This means that they are still
accessible by members of the derived class but cannot be accessed by parts of your
program that are not members of either the base or derived class
...
When a member of a class is declared as protected, that
member is not accessible by other, nonmember elements of the program
...
The sole exception to
this is when a protected member is inherited
...
As explained in the preceding section, a private member of a base class is not
accessible by other parts of your program, including any derived class
...
If the base class is inherited as public, then the
base class' protected members become protected members of the derived class and are,
therefore, accessible by the derived class
...
Here is an example:
#include
using namespace std;
class base {
Chapter 16:
Inheritance
protected:
int i, j; // private to base, but accessible by derived
public:
void set(int a, int b) { i=a; j=b; }
void show() { cout << i << " " << j << "\n"; }
};
class derived : public base {
int k;
public:
// derived may access base's i and j
void setk() { k=i*j; }
void showk() { cout << k << "\n"; }
};
int main()
{
derived ob;
ob
...
show(); // OK, known to derived
ob
...
showk();
return 0;
}
In this example, because base is inherited by derived as public and because i and j
are declared as protected, derived's function setk( ) may access them
...
When a derived class is used as a base class for another derived class, any protected
member of the initial base class that is inherited (as public) by the first derived class
may also be inherited as protected again by a second derived class
...
#include
using namespace std;
class base {
423
424
C++: The Complete Reference
protected:
int i, j;
public:
void set(int a, int b) { i=a; j=b; }
void show() { cout << i << " " << j << "\n"; }
};
// i and j inherited as protected
...
class derived2 : public derived1 {
int m;
public:
void setm() { m = i-j; } // legal
void showm() { cout << m << "\n"; }
};
int main()
{
derived1 ob1;
derived2 ob2;
ob1
...
show();
ob1
...
showk();
ob2
...
show();
ob2
...
setm();
ob2
...
showm();
return 0;
}
Chapter 16:
Inheritance
If, however, base were inherited as private, then all members of base would
become private members of derived1, which means that they would not be accessible
by derived2
...
) This situation is
illustrated by the following program, which is in error (and won't compile)
...
#include
using namespace std;
class base {
protected:
int i, j;
public:
void set(int a, int b) { i=a; j=b; }
void show() { cout << i << " " << j << "\n"; }
};
// Now, all elements of base are private in derived1
...
class derived2 : public derived1 {
int m;
public:
// illegal because i and j are private to derived1
void setm() { m = i-j; } // Error
void showm() { cout << m << "\n"; }
};
int main()
{
derived1 ob1;
derived2 ob2;
ob1
...
show(); // error, can't use show()
ob2
...
show(); // error, can't use show()
return 0;
}
Even though base is inherited as private by derived1, derived1 still has access to
base's public and protected elements
...
Note
Protected Base-Class Inheritance
It is possible to inherit a base class as protected
...
For example,
#include
using namespace std;
class base {
protected:
int i, j; // private to base, but accessible by derived
public:
void setij(int a, int b) { i=a; j=b; }
void showij() { cout << i << " " << j << "\n"; }
};
// Inherit base as protected
...
void setk() { setij(10, 12); k = i*j; }
// may access showij() here
void showall() { cout << k << " "; showij(); }
};
int main()
Chapter 16:
Inheritance
{
derived ob;
//
//
ob
...
setk(); // OK, public member of derived
ob
...
showij(); // illegal, showij() is protected
//
member of derived
return 0;
}
As you can see by reading the comments, even though setij( ) and showij( ) are
public members of base, they become protected members of derived when it is
inherited using the protected access specifier
...
Inheriting Multiple Base Classes
It is possible for a derived class to inherit two or more base classes
...
// An example of multiple base classes
...
class derived: public base1, public base2 {
public:
void set(int i, int j) { x=i; y=j; }
};
int main()
{
derived ob;
ob
...
showx(); // from base1
ob
...
Further, be sure to use an access-specifier for each base
inherited
...
First, when are base-class and derived-class constructor and
destructor functions called? Second, how can parameters be passed to base-class
constructor functions? This section examines these two important topics
...
It is important to understand the order in which these functions
are executed when an object of a derived class comes into existence and when it goes
out of existence
...
When executed, this program
displays
Constructing base
Constructing derived
Destructing derived
Destructing base
As you can see, first base's constructor is executed followed by derived's
...
The results of the foregoing experiment can be generalized
...
When a derived object is destroyed, its
destructor is called first, followed by the base class' destructor, if it exists
...
Destructor
functions are executed in reverse order of derivation
...
Because a base class has no knowledge of any derived class, any
429
430
C++: The Complete Reference
initialization it needs to perform is separate from and possibly prerequisite to any
initialization performed by the derived class
...
Likewise, it is quite sensible that destructors be executed in reverse order of
derivation
...
Therefore, the derived
destructor must be called before the object is fully destroyed
...
For example, this program
#include
using namespace std;
class base {
public:
base() { cout << "Constructing base\n"; }
~base() { cout << "Destructing base\n"; }
};
class derived1 : public base {
public:
derived1() { cout << "Constructing derived1\n"; }
~derived1() { cout << "Destructing derived1\n"; }
};
class derived2: public derived1 {
public:
derived2() { cout << "Constructing derived2\n"; }
~derived2() { cout << "Destructing derived2\n"; }
};
int main()
{
derived2 ob;
// construct and destruct ob
return 0;
}
Chapter 16:
Inheritance
displays this output:
Constructing base
Constructing derived1
Constructing derived2
Destructing derived2
Destructing derived1
Destructing base
The same general rule applies in situations involving multiple base classes
...
Destructors are called in reverse order, right to left
...
In cases where only the derived class' constructor requires one or
more parameters, you simply use the standard parameterized constructor syntax (see
Chapter 12)
...
The general form
of this expanded derived-class constructor declaration is shown here:
derived-constructor(arg-list) : base1(arg-list),
base2(arg-list),
//
...
Notice that a colon separates the derived class' constructor declaration from the
base-class specifications, and that the base-class specifications are separated from each
other by commas, in the case of multiple base classes
...
derived(int x, int y): base(y)
{ j=x; cout << "Constructing derived\n"; }
~derived() { cout << "Destructing derived\n"; }
void show() { cout << i << " " << j << "\n"; }
};
int main()
{
derived ob(3, 4);
ob
...
However, derived( ) uses only x; y is passed along to base( )
...
As the example illustrates, any parameters required by the
base class are passed to it in the base class' argument list specified after the colon
...
show(); // displays 4 3 5
return 0;
}
It is important to understand that arguments to a base-class constructor are passed
via arguments to the derived class' constructor
...
In this situation, the arguments passed to the derived class are simply
passed along to the base
...
*/
derived(int x, int y): base1(x), base2(y)
{ cout << "Constructing derived\n"; }
~derived() { cout << "Destructing derived\n"; }
void show() { cout << i << " " << k << "\n"; }
};
int main()
{
derived ob(3, 4);
ob
...
Put
differently, passing an argument along to a base class does not preclude its use by the
derived class as well
...
derived(int x, int y): base(x, y)
{ j = x*y; cout << "Constructing derived\n"; }
One final point to keep in mind when passing arguments to base-class constructors:
The argument can consist of any expression valid at the time
...
This is in keeping with the fact that C++ allows dynamic
initialization
...
However, in certain circumstances, you
may want to restore one or more inherited members to their original access
specification
...
In Standard C++, you have two ways to accomplish this
...
The using statement is designed
primarily to support namespaces and is discussed in Chapter 23
...
Access declarations are currently supported by Standard C++,
but they are deprecated
...
Since
there are still many, many existing programs that use access declarations, they will be
examined here
...
Notice that no type declaration is required (or, indeed, allowed) in an
access declaration
...
class derived: private base {
public:
// here is access declaration
base::j; // make j public again
...
...
However, by including
base::j;
as the access declaration under derived's public heading, j is restored to its public status
...
However, you cannot use an access declaration to raise or lower a
member's access status
...
(If C++ allowed this to occur, it would
destroy its encapsulation mechanism!)
The following program illustrates the access declaration; notice how it uses access
declarations to restore j, seti( ), and geti( ) to public status
...
class derived: private base {
public:
/* The next three statements override
base's inheritance as private and restore j,
seti(), and geti() to public access
...
i = 10; // illegal because i is private in derived
ob
...
k = 30; // illegal because k is private in derived
ob
...
seti(10);
cout << ob
...
j << " " << ob
...
Chapter 16:
Remember
Inheritance
While Standard C++ still supports access declarations, they are deprecated
...
Instead, the standard suggests achieving the same effect by applying the using
keyword
...
For example, consider this incorrect program:
// This program contains an error and will not compile
...
class derived1 : public base {
public:
int j;
};
// derived2 inherits base
...
This means that there are two copies of base
in derived3! */
class derived3 : public derived1, public derived2 {
public:
int sum;
};
int main()
{
439
440
C++: The Complete Reference
derived3 ob;
ob
...
j = 20;
ob
...
sum = ob
...
j + ob
...
i << " ";
cout << ob
...
k << " ";
cout << ob
...
However, derived3 inherits both derived1 and derived2
...
Therefore, in an expression like
ob
...
is! As you can see, the
statement is inherently ambiguous
...
The first is to apply the
scope resolution operator to i and manually select one i
...
#include
using namespace std;
class base {
public:
int i;
};
// derived1 inherits base
...
class derived2 : public base {
public:
int k;
};
/* derived3 inherits both derived1 and derived2
...
derived1::i = 10; // scope resolved, use derived1's i
ob
...
k = 30;
// scope resolved
ob
...
derived1::i + ob
...
k;
// also resolved here
cout << ob
...
j << " " << ob
...
sum;
return 0;
}
As you can see, because the :: was applied, the program has manually selected
derived1's version of base
...
This
solution is achieved using virtual base classes
...
You accomplish this
by preceding the base class' name with the keyword virtual when it is inherited
...
#include
using namespace std;
class base {
public:
int i;
};
// derived1 inherits base as virtual
...
class derived2 : virtual public base {
public:
int k;
};
/* derived3 inherits both derived1 and derived2
...
*/
class derived3 : public derived1, public derived2 {
public:
int sum;
};
int main()
{
derived3 ob;
ob
...
j = 20;
ob
...
sum = ob
...
j + ob
...
i << " ";
cout << ob
...
k << " ";
cout << ob
...
Now that both derived1 and derived2 have inherited base as virtual, any
multiple inheritance involving them will cause only one copy of base to be present
...
i = 10 is perfectly valid
and unambiguous
...
For example, the
following sequence is perfectly valid:
// define a class of type derived1
derived1 myclass;
myclass
...
If virtual base classes are used, then only
one base class is present in the object
...
443
This page intentionally left blank
...
As
discussed in earlier chapters, compile-time polymorphism is achieved by
overloading functions and operators
...
P
Virtual Functions
A virtual function is a member function that is declared within a base class and
redefined by a derived class
...
When a class containing a
virtual function is inherited, the derived class redefines the virtual function to fit its
own needs
...
The virtual function within the
base class defines the form of the interface to that function
...
That is, the redefinition creates a specific method
...
However, what makes virtual functions important and capable of
supporting run-time polymorphism is how they behave when accessed via a pointer
...
When a base pointer points to a derived object that
contains a virtual function, C++ determines which version of that function to call based
upon the type of object pointed to by the pointer
...
Thus, when different objects are pointed to, different versions of the virtual
function are executed
...
To begin, examine this short example:
#include
using namespace std;
class base {
public:
virtual void vfunc() {
cout << "This is base's vfunc()
...
\n";
}
Chapter 17:
Virtual Functions and Polymorphism
};
class derived2 : public base {
public:
void vfunc() {
cout << "This is derived2's vfunc()
...
This is derived1's vfunc()
...
As the program illustrates, inside base, the virtual function vfunc( ) is declared
...
When
vfunc( ) is redefined by derived1 and derived2, the keyword virtual is not needed
...
)
447
448
C++: The Complete Reference
In this program, base is inherited by both derived1 and derived2
...
Inside main( ), four
variables are declared:
Name
Type
p
base class pointer
b
object of base
d1
object of derived1
d2
object of derived2
Next, p is assigned the address of b, and vfunc( ) is called via p
...
Next, p is set to the
address of d1, and again vfunc( ) is called by using p
...
This causes derived1::vfunc( ) to be executed
...
The key point here is that the kind of object to which p points
determines which version of vfunc( ) is executed
...
Although you can call a virtual function in the "normal" manner by using an
object's name and the dot operator, it is only when access is through a base-class
pointer (or reference) that run-time polymorphism is achieved
...
vfunc(); // calls derived2's vfunc()
Although calling a virtual function in this manner is not wrong, it simply does not take
advantage of the virtual nature of vfunc( )
...
However, this is not the case, and the term overloading is not
applied to virtual function redefinition because several differences exist
...
This differs from overloading a normal function,
in which return types and the number and type of parameters may differ
...
) However, when a virtual function is redefined, all aspects of its prototype
must be the same
...
Another important restriction is that virtual functions must be
Chapter 17:
Virtual Functions and Polymorphism
nonstatic members of the classes of which they are part
...
Finally,
constructor functions cannot be virtual, but destructor functions can
...
Calling a Virtual Function Through a
Base Class Reference
In the preceding example, a virtual function was called through a base-class pointer,
but the polymorphic nature of a virtual function is also available when called through a
base-class reference
...
Thus, a base-class reference can be used to refer to an object of the base class or any
object derived from that base
...
The most common situation in which a virtual function is invoked through a base
class reference is when the reference is a function parameter
...
/* Here, a base class reference is used to access
a virtual function
...
\n";
}
};
class derived1 : public base {
public:
void vfunc() {
cout << "This is derived1's vfunc()
...
\n";
}
};
// Use a base class reference parameter
...
vfunc();
}
int main()
{
base b;
derived1 d1;
derived2 d2;
f(b); // pass a base object to f()
f(d1); // pass a derived1 object to f()
f(d2); // pass a derived2 object to f()
return 0;
}
This program produces the same output as its preceding version
...
Inside main( ), the function is
called using objects of type base, derived1, and derived2
...
For the sake of simplicity, the rest of the examples in this chapter will call virtual
functions through base-class pointers, but the effects are same for base-class references
...
This means that
when a derived class that has inherited a virtual function is itself used as a base class
for another derived class, the virtual function can still be overridden
...
For
example, consider this program:
#include
using namespace std;
Chapter 17:
Virtual Functions and Polymorphism
class base {
public:
virtual void vfunc() {
cout << "This is base's vfunc()
...
\n";
}
};
/* derived2 inherits virtual function vfunc()
from derived1
...
\n";
}
};
int main()
{
base *p, b;
derived1 d1;
derived2 d2;
// point to base
p = &b;
p->vfunc(); // access base's vfunc()
// point to derived1
p = &d1;
p->vfunc(); // access derived1's vfunc()
// point to derived2
p = &d2;
p->vfunc(); // access derived2's vfunc()
451
452
C++: The Complete Reference
return 0;
}
As expected, the preceding program displays this output:
This is base's vfunc()
...
This is derived2's vfunc()
...
Virtual Functions Are Hierarchical
As explained, when a function is declared as virtual by a base class, it may be
overridden by a derived class
...
When a derived class fails to override a virtual function, then when an object of that
derived class accesses that function, the function defined by the base class is used
...
\n";
}
};
class derived1 : public base {
public:
void vfunc() {
cout << "This is derived1's vfunc()
...
This is derived1's vfunc()
...
Because derived2 does not override vfunc( ), the function defined by base is used
when vfunc( ) is referenced relative to objects of type derived2
...
Because
inheritance is hierarchical in C++, it makes sense that virtual functions are also
hierarchical
...
For example, in the
following program, derived2 is derived from derived1, which is derived from base
...
This means that, relative to derived2,
453
454
C++: The Complete Reference
the closest version of vfunc( ) is in derived1
...
#include
using namespace std;
class base {
public:
virtual void vfunc() {
cout << "This is base's vfunc()
...
\n";
}
};
class derived2 : public derived1 {
public:
/* vfunc() not overridden by derived2
...
*/
};
int main()
{
base *p, b;
derived1 d1;
derived2 d2;
// point to base
p = &b;
p->vfunc(); // access base's vfunc()
// point to derived1
p = &d1;
p->vfunc(); // access derived1's vfunc()
Chapter 17:
Virtual Functions and Polymorphism
// point to derived2
p = &d2;
p->vfunc(); // use derived1's vfunc()
return 0;
}
The program displays the following:
This is base's vfunc()
...
This is derived1's vfunc()
...
However, in many situations there can be no meaningful definition of a virtual
function within a base class
...
Further, in some
situations you will want to ensure that all derived classes override a virtual function
...
A pure virtual function is a virtual function that has no definition within the base
class
...
If the derived class fails to override the pure virtual function, a compile-time
error will result
...
The
base class, number, contains an integer called val, the function setval( ), and the pure
virtual function show( )
...
#include
using namespace std;
class number {
455
456
C++: The Complete Reference
protected:
int val;
public:
void setval(int i) { val = i; }
// show() is a pure virtual function
virtual void show() = 0;
};
class hextype : public number {
public:
void show() {
cout << hex << val << "\n";
}
};
class dectype : public number {
public:
void show() {
cout << val << "\n";
}
};
class octtype : public number {
public:
void show() {
cout << oct << val << "\n";
}
};
int main()
{
dectype d;
hextype h;
octtype o;
d
...
show(); // displays 20 - decimal
h
...
show(); // displays 14 - hexadecimal
Chapter 17:
Virtual Functions and Polymorphism
o
...
show(); // displays 24 - octal
return 0;
}
Although this example is quite simple, it illustrates how a base class may not be
able to meaningfully define a virtual function
...
There is no reason to define show( )
inside number since the base of the number is undefined
...
However, making show( ) pure
also ensures that all derived classes will indeed redefine it to meet their own needs
...
If a derived class fails to do this, a compile-time error will result
...
Because an
abstract class contains one or more functions for which there is no definition (that is, a
pure virtual function), no objects of an abstract class may be created
...
Although you cannot create objects of an abstract class, you can create pointers and
references to an abstract class
...
Using Virtual Functions
One of the central aspects of object-oriented programming is the principle of "one
interface, multiple methods
...
In concrete C++ terms, a base class can be used to define the nature of the
interface to a general class
...
One of the most powerful and flexible ways to implement the "one interface,
multiple methods" approach is to use virtual functions, abstract classes, and run-time
polymorphism
...
Following this philosophy, you define all common
features and interfaces in a base class
...
In essence, in the base
457
458
C++: The Complete Reference
class you create and define everything you can that relates to the general case
...
Following is a simple example that illustrates the value of the "one interface,
multiple methods" philosophy
...
(For example, liters to gallons
...
It also defines the functions getinit( ) and getconv( ), which return
the initial value and the converted value
...
However, the function that
will actually perform the conversion, compute( ), is a pure virtual function that must be
defined by the classes derived from convert
...
// Virtual function practical example
...
class l_to_g : public convert {
public:
l_to_g(double i) : convert(i) { }
void compute() {
val2 = val1 / 3
...
8;
}
};
int main()
{
convert *p;
// pointer to base class
l_to_g lgob(4);
f_to_c fcob(70);
// use virtual function mechanism to convert
p = &lgob;
cout << p->getinit() << " liters is ";
p->compute();
cout << p->getconv() << " gallons\n"; // l_to_g
p = &fcob;
cout << p->getinit() << " in Fahrenheit is ";
p->compute();
cout << p->getconv() << " Celsius\n"; // f_to_c
return 0;
}
The preceding program creates two derived classes from convert, called l_to_g and
f_to_c
...
Each derived class overrides compute( ) in its own way to
perform the desired conversion
...
One of the benefits of derived classes and virtual functions is that handling a new
case is a very easy matter
...
28;
}
};
An important use of abstract classes and virtual functions is in class libraries
...
Another programmer will inherit your general class, which defines the interface and
all elements common to all classes derived from it, and will add those functions
specific to the derived class
...
One final point: The base class convert is an example of an abstract class
...
The class convert simply does not contain sufficient
information for compute( ) to be defined
...
Early vs
...
Early binding refers to events that occur at compile time
...
(Put
differently, early binding means that an object and a function call are bound during
compilation
...
The
main advantage to early binding is efficiency
...
The opposite of early binding is late binding
...
Virtual functions are used to
achieve late binding
...
Because in most cases this cannot be determined at compile time, the object
and the function are not linked until run time
...
Unlike early binding, late binding allows you to create programs that can
respond to events occurring while the program executes without having to create a
large amount of "contingency code
...
C++
Chapter 18
Templates
461
462
C++: The Complete Reference
he template is one of C++'s most sophisticated and high-powered features
...
Using templates, it
is possible to create generic functions and classes
...
Thus, you can use one function or class with several different types of data without
having to explicitly recode specific versions for each data type
...
T
Generic Functions
A generic function defines a general set of operations that will be applied to various
types of data
...
Through a generic function, a single general procedure can be applied to a
wide range of data
...
For example, the Quicksort sorting
algorithm is the same whether it is applied to an array of integers or an array of floats
...
By creating a generic function,
you can define the nature of the algorithm, independent of any data
...
In essence, when you create a
generic function you are creating a function that can automatically overload itself
...
The normal meaning of
the word "template" accurately reflects its use in C++
...
The general form of a template function definition is shown here:
template
{
// body of function
}
Here, Ttype is a placeholder name for a data type used by the function
...
However, it is only a placeholder that the
compiler will automatically replace with an actual data type when it creates a specific
version of the function
...
The following example creates a generic function that swaps the values of the two
variables with which it is called
...
Chapter 18:
Templates
// Function template example
...
template
{
X temp;
temp = a;
a = b;
b = temp;
}
int main()
{
int i=10, j=20;
double x=10
...
3;
char a='x', b='z';
cout << "Original i, j: " << i << ' ' << j << '\n';
cout << "Original x, y: " << x << ' ' << y << '\n';
cout << "Original a, b: " << a << ' ' << b << '\n';
swapargs(i, j); // swap integers
swapargs(x, y); // swap floats
swapargs(a, b); // swap chars
cout << "Swapped i, j: " << i << ' ' << j << '\n';
cout << "Swapped x, y: " << x << ' ' << y << '\n';
cout << "Swapped a, b: " << a << ' ' << b << '\n';
return 0;
}
Let's look closely at this program
...
Here, X is a generic type that is used as a placeholder
...
In main( ), the swapargs( ) function is called using three
463
464
C++: The Complete Reference
different types of data: ints, doubles, and chars
...
Here are some important terms related to templates
...
Both terms will be used interchangeably in this book
...
This is also called
a generated function
...
Put
differently, a generated function is a specific instance of a template function
...
The following example shows another common way to format the
swapargs( ) function
...
For
example, the fragment shown next will not compile
...
template
int i; // this is an error
void swapargs(X &a, X &b)
{
X temp;
temp = a;
a = b;
b = temp;
}
As the comments imply, the template specification must directly precede the
function definition
...
For example, this program creates a template function that has
two generic types
...
6, 19L);
return 0;
}
In this example, the placeholder types type1 and type2 are replaced by the compiler
with the data types int and char *, and double and long, respectively, when the
compiler generates the specific instances of myfunc( ) within main( )
...
Explicitly Overloading a Generic Function
Even though a generic function overloads itself as needed, you can explicitly overload
one, too
...
If you overload a generic function,
that overloaded function overrides (or "hides") the generic function relative to that
specific version
...
// Overriding a template function
...
\n";
}
// This overrides the generic version of swapargs() for ints
...
\n";
}
int main()
{
int i=10, j=20;
double x=10
...
3;
char a='x', b='z';
cout << "Original i, j: " << i << ' ' << j << '\n';
cout << "Original x, y: " << x << ' ' << y << '\n';
cout << "Original a, b: " << a << ' ' << b << '\n';
swapargs(i, j); // calls explicitly overloaded swapargs()
swapargs(x, y); // calls generic swapargs()
swapargs(a, b); // calls generic swapargs()
cout << "Swapped i, j: " << i << ' ' << j << '\n';
cout << "Swapped x, y: " << x << ' ' << y << '\n';
cout << "Swapped a, b: " << a << ' ' << b << '\n';
return 0;
}
Chapter 18:
Templates
This program displays the following output
...
1 23
...
Inside template swapargs
...
Swapped i, j: 20 10
Swapped x, y: 23
...
1
Swapped a, b: z x
As the comments inside the program indicate, when swapargs(i, j) is called, it
invokes the explicitly overloaded version of swapargs( ) defined in the program
...
Recently, a new-style syntax was introduced to denote the explicit specialization of
a function
...
For example, using the
new-style specialization syntax, the overloaded swapargs( ) function from the
preceding program looks like this
...
template<> void swapargs
{
int temp;
temp = a;
a = b;
b = temp;
cout << "Inside swapargs int specialization
...
The type of data for which the specialization is being created is placed
inside the angle brackets following the function name
...
While there is no advantage to using one
specialization syntax over the other at this time, the new-style is probably a better
approach for the long term
...
However, as a
general rule, if you need to have different versions of a function for different data
types, you should use overloaded functions rather than templates
...
To do so, simply create another version of the
template that differs from any others in its parameter list
...
#include
using namespace std;
// First version of f() template
...
template
{
cout << "Inside f(X a, Y b)\n";
}
int main()
{
f(10);
// calls f(X)
f(10, 20); // calls f(X, Y)
return 0;
}
Here, the template for f( ) is overloaded to accept either one or two parameters
...
These nongeneric parameters work just like they do with any other function
...
#include
using namespace std;
Chapter 18:
Templates
const int TABWIDTH = 8;
// Display data at specified tab position
...
This is a test
100
X
3
In the program, the function tabOut( ) displays its first argument at the tab position
requested by its second argument
...
The tab parameter is a standard, call-by-value
parameter
...
Generic Function Restrictions
Generic functions are similar to overloaded functions except that they are more
restrictive
...
But a generic function must perform the same general
action for all versions—only the type of data can differ
...
These functions could not be replaced by
a generic function because they do not do the same thing
...
2);
return 0;
}
Applying Generic Functions
Generic functions are one of C++'s most useful features
...
As mentioned earlier, whenever you have a function that defines a
generalizable algorithm, you can make it into a template function
...
Before moving on
to generic classes, two examples of applying generic functions will be given
...
Chapter 18:
Templates
A Generic Sort
Sorting is exactly the type of operation for which generic functions were designed
...
The following program illustrates this by creating a generic bubble sort
...
The bubble( ) function will
sort any type of array
...
// A Generic bubble sort
...
3, 2
...
9, 100
...
0};
int i;
cout << "Here is unsorted integer array: ";
for(i=0; i<7; i++)
471
472
C++: The Complete Reference
cout << iarray[i] << ' ';
cout << endl;
cout << "Here is unsorted double array: ";
for(i=0; i<5; i++)
cout << darray[i] << ' ';
cout << endl;
bubble(iarray, 7);
bubble(darray, 5);
cout << "Here is sorted integer array: ";
for(i=0; i<7; i++)
cout << iarray[i] << ' ';
cout << endl;
cout << "Here is sorted double array: ";
for(i=0; i<5; i++)
cout << darray[i] << ' ';
cout << endl;
return 0;
}
The output produced by the program is shown here
...
3 2
...
9 100
...
9 2
...
3 100
...
It then sorts each
...
You might want to try
using bubble( ) to sort other types of data, including classes that you create
...
Compacting an Array
Another function that benefits from being made into a template is called compact( )
...
It is not uncommon to want to remove
elements from the middle of an array and then move the remaining elements down so
Chapter 18:
Templates
that all unused elements are at the end
...
The
generic compact( ) function shown in the following program is called with a pointer to
the first element in the array, the number of elements in the array, and the starting and
ending indexes of the elements to be removed
...
For the purposes of illustration, it also zeroes the
unused elements at the end of the array that have been freed by the compaction
...
#include
using namespace std;
template
X *items, // pointer to array to be compacted
int count, // number of items in array
int start, // starting index of compacted region
int end)
// ending index of compacted region
{
register int i;
for(i=end+1; i
/* For the sake of illustration, the remainder of
the array will be zeroed
...
One is an integer array, and the
other is a string
...
The
output from this program in shown here
...
As long as the underlying logic of a
function is independent of the data, it can be made into a generic function
...
When you do this,
you create a class that defines all the algorithms used by that class; however, the actual
type of the data being manipulated will be specified as a parameter when objects of
that class are created
...
For
example, the same algorithms that maintain a queue of integers will also work for a
queue of characters, and the same mechanism that maintains a linked list of mailing
Chapter 18:
Templates
addresses will also maintain a linked list of auto part information
...
The compiler will automatically generate the correct
type of object, based upon the type you specify when the object is created
...
...
}
Here, Ttype is the placeholder type name, which will be specified when a class is
instantiated
...
Once you have created a generic class, you create a specific instance of that class
using the following general form:
class-name
Here, type is the type name of the data that the class will be operating upon
...
You need not use
template to explicitly specify them as such
...
Thus, it can be used to store objects of any type
...
// This function demonstrates a generic stack
...
template
{
if(tos==SIZE) {
cout << "Stack is full
...
template
{
if(tos==0) {
cout << "Stack is empty
...
stack
int i;
s1
...
push('x');
s1
...
push('y');
s1
...
push('z');
for(i=0; i<3; i++) cout << "Pop s1: " << s1
...
pop() << "\n";
// demonstrate double stacks
stack
Chapter 18:
Templates
ds1
...
1);
ds2
...
2);
ds1
...
3);
ds2
...
4);
ds1
...
5);
ds2
...
6);
for(i=0; i<3; i++) cout << "Pop ds1: " << ds1
...
pop() << "\n";
return 0;
}
As you can see, the declaration of a generic class is similar to that of a generic
function
...
It is not until an object of the stack is declared that the actual data type is determined
...
In this example, two
different types of stacks are declared
...
Two are stacks of
doubles
...
By changing the
type of data specified when stack objects are created, you can change the type of data
stored in that stack
...
stack
You can also create stacks to store data types that you create
...
You are
saved from the tedium of creating separate implementations for each data type with
which you want the algorithm to work
...
An Example with Two Generic Data Types
A template class can have more than one generic data type
...
For example, the following short example creates a class that uses two generic data types
...
*/
#include
using namespace std;
template
{
Type1 i;
Type2 j;
public:
myclass(Type1 a, Type2 b) { i = a; j = b; }
void show() { cout << i << ' ' << j << '\n'; }
};
int main()
{
myclass
...
");
Chapter 18:
Templates
ob1
...
show(); // show char, char *
return 0;
}
This program produces the following output:
10 0
...
The program declares two types of objects
...
ob2 uses a
character and a character pointer
...
Applying Template Classes: A Generic Array Class
To illustrate the practical benefits of template classes, let's look at one way in which
they are commonly applied
...
Doing so allows you to create your own array implementations, including
"safe arrays" that provide run-time boundary checking
...
However, if you create a class that contains the array, and
allow access to that array only through the overloaded [ ] subscripting operator, then
you can intercept an out-of-range index
...
This
type of array is shown in the following program:
// A generic safe array example
...
template
{
if(i<0 || i> SIZE-1) {
cout << "\nIndex value of ";
cout << i << " is out-of-bounds
...
You should try creating other
types of arrays
...
Using Non-Type Arguments with Generic Classes
In the template specification for a generic class, you may also specify non-type
arguments
...
The syntax
to accomplish this is essentially the same as for normal function parameters: simply
include the type and name of the argument
...
// Demonstrate non-type template arguments
...
template
AType a[size]; // length of array is passed in size
public:
atype() {
register int i;
for(i=0; i
AType &operator[](int i);
};
// Provide range checking for atype
...
\n";
exit(1);
}
return a[i];
}
int main()
{
481
482
C++: The Complete Reference
atype
// integer array of size 10
atype
int i;
cout << "Integer array: ";
for(i=0; i<10; i++) intob[i] = i;
for(i=0; i<10; i++) cout << intob[i] << "
cout << '\n';
";
cout << "Double array: ";
for(i=0; i<15; i++) doubleob[i] = (double) i/3;
for(i=0; i<15; i++) cout << doubleob[i] << " ";
cout << '\n';
intob[12] = 100; // generates runtime error
return 0;
}
Look carefully at the template specification for atype
...
This parameter is then used within atype to declare the size of the array a
...
This allows it to be used to set the size of the array
...
Within main( ), notice how the integer and
floating-point arrays are created
...
Non-type parameters are restricted to integers, pointers, or references
...
The arguments that you pass to a non-type parameter
must consist of either an integer constant, or a pointer or reference to a global function
or object
...
For example, inside operator[ ]( ), the following
statement is not allowed
...
As the safe-array example illustrates, the use of non-type parameters greatly
expands the utility of template classes
...
Chapter 18:
Templates
Using Default Arguments with Template Classes
A template class can have a default argument associated with a generic type
...
Here, the type int will be used if no other type is specified when an object of type
myclass is instantiated
...
The default
value is used when no explicit value is specified when the class is instantiated
...
Here is another version of the safe-array class that uses default arguments for both
the type of data and the size of the array
...
#include
#include
using namespace std;
// Here, AType defaults to int and size defaults to 10
...
template
AType &atype
{
if(i<0 || i> size-1) {
cout << "\nIndex value of ";
cout << i << " is out-of-bounds
...
As the program illustrates,
atype objects can be created three ways:
s explicitly specifying both the type and size of the array
s explicitly specifying the type, but letting the size default to 10
s letting the type default to int and the size default to 10
Chapter 18:
Templates
The use of default arguments—especially default types—adds versatility to your
template classes
...
Explicit Class Specializations
As with template functions, you can create an explicit specialization of a generic class
...
For example:
// Demonstrate class specialization
...
template <> class myclass
int x;
public:
myclass(int a) {
cout << "Inside myclass
x = a * a;
}
int getx() { return x; }
};
int main()
{
myclass
...
getx() << "\n\n";
myclass
485
486
C++: The Complete Reference
cout << "int: " << i
...
1
Inside myclass
int: 25
In the program, pay close attention to this line:
template <> class myclass
It tells the compiler that an explicit integer specialization of myclass is being created
...
Explicit class specialization expands the utility of generic classes because it lets you
easily handle one or two special cases while allowing all others to be automatically
processed by the compiler
...
The typename and export Keywords
Recently, two keywords were added to C++ that relate specifically to templates:
typename and export
...
Each is
briefly examined
...
First, as mentioned earlier, it can be
substituted for the keyword class in a template declaration
...
There is no difference between using class
and using typename in this context
...
For example,
typename X::Name someObject;
ensures that X::Name is treated as a type name
...
It allows other files to use
a template declared in a different file by specifying only its declaration rather than
duplicating its entire definition
...
Through the use of template classes you can create frameworks that
can be applied over and over again to a variety of programming situations
...
When first shown in Chapter 11, it could only be
used to store integer values
...
However, by making stack into a generic class, it can create a
stack for any type of data
...
Once you have written and debugged a template class, you
have a solid software component that you can use with confidence in a variety of
different situations
...
While it is true that the template syntax can seem a bit intimidating at first, the
rewards are well worth the time it takes to become comfortable with it
...
For example, the STL (Standard Template Library)
defined by C++ is, as its name implies, built upon templates
...
487
This page intentionally left blank
...
Exception handling allows
you to manage run-time errors in an orderly fashion
...
The principal advantage of exception handling is that it automates much of the
error-handling code that previously had to be coded "by hand" in any large program
...
In the
most general terms, program statements that you want to monitor for exceptions are
contained in a try block
...
e
...
The exception is caught, using catch, and processed
...
Code that you want to monitor for exceptions must have been executed from
within a try block
...
) Exceptions that can be thrown by the monitored code are caught by a catch
statement, which immediately follows the try statement in which the exception was
thrown
...
try {
// try block
}
catch (type1 arg) {
// catch block
}
catch (type2 arg) {
// catch block
}
catch (type3 arg) {
// catch block
}
...
...
Chapter 19:
Exception Handling
When an exception is thrown, it is caught by its corresponding catch statement,
which processes the exception
...
Which catch statement is used is determined by the type of the exception
...
When an exception is caught,
arg will receive its value
...
If no exception is thrown (that is, no error occurs within the try block), then no
catch statement is executed
...
If this exception is to be caught,
then throw must be executed either from within a try block itself, or from any function
called from within the try block (directly or indirectly)
...
Throwing an unhandled exception causes
the standard library function terminate( ) to be invoked
...
Here is a simple example that shows the way C++ exception handling operates
...
#include
using namespace std;
int main()
{
cout << "Start\n";
try { // start a try block
cout << "Inside try block\n";
throw 100; // throw an error
cout << "This will not execute";
}
catch (int i) { // catch an error
cout << "Caught an exception -- value is: ";
cout << i << "\n";
}
cout << "End";
491
492
C++: The Complete Reference
return 0;
}
This program displays the following output:
Start
Inside try block
Caught an exception -- value is: 100
End
Look carefully at this program
...
Within the
try block, only two of the three statements will execute: the first cout statement and the
throw
...
That is, catch is not called
...
(The program's stack is automatically reset as needed to accomplish
this
...
Usually, the code within a catch statement attempts to remedy an error by taking
appropriate action
...
However, often an error cannot be fixed and a catch
block will terminate the program with a call to exit( ) or abort( )
...
For example, in the preceding example, if you change the type in the catch
statement to double, the exception will not be caught and abnormal termination will
occur
...
// This example will not work
...
Start
Inside try block
Abnormal program termination
An exception can be thrown from outside the try block as long as it is thrown by a
function that is called from within try block
...
/* Throwing an exception from a function outside the
try block
...
When this is the case, each time the
function is entered, the exception handling relative to that function is reset
...
#include
using namespace std;
// Localize a try/catch to a function
...
After each exception, the function returns
...
It is important to understand that the code associated with a catch statement will
be executed only if it catches an exception
...
(That is, execution never flows into a catch statement
...
#include
using namespace std;
int main()
{
cout << "Start\n";
try { // start a try block
cout << "Inside try block\n";
cout << "Still inside try block\n";
}
catch (int i) { // catch an error
cout << "Caught an exception -- value is: ";
cout << i << "\n";
}
495
496
C++: The Complete Reference
cout << "End";
return 0;
}
The preceding program produces the following output
...
Catching Class Types
An exception can be of any type, including class types that you create
...
Perhaps the most common reason that you will want to define a class type for an
exception is to create an object that describes the error that occurred
...
The following
example demonstrates this
...
#include
#include
using namespace std;
class MyException {
public:
char str_what[80];
int what;
MyException() { *str_what = 0; what = 0; }
MyException(char *s, int e) {
strcpy(str_what, s);
what = e;
}
};
Chapter 19:
Exception Handling
int main()
{
int i;
try {
cout << "Enter a positive number: ";
cin >> i;
if(i<0)
throw MyException("Not Positive", i);
}
catch (MyException e) { // catch an error
cout << e
...
what << "\n";
}
return 0;
}
Here is a sample run:
Enter a positive number: -4
Not Positive: -4
The program prompts the user for a positive number
...
Thus, MyException
encapsulates information about the error
...
In general, you will want to create exception classes that will encapsulate
information about an error to enable the exception handler to respond effectively
...
In fact, it is common
to do so
...
For example,
this program catches both integers and strings
...
497
498
C++: The Complete Reference
void Xhandler(int test)
{
try{
if(test) throw test;
else throw "Value is zero";
}
catch(int i) {
cout << "Caught Exception #: " << i << '\n';
}
catch(const char *str) {
cout << "Caught a string: ";
cout << str << '\n';
}
}
int main()
{
cout << "Start\n";
Xhandler(1);
Xhandler(2);
Xhandler(0);
Xhandler(3);
cout << "End";
return 0;
}
This program produces the following output:
Start
Caught
Caught
Caught
Caught
End
Exception
Exception
a string:
Exception
#: 1
#: 2
Value is zero
#: 3
As you can see, each catch statement responds only to its own type
...
Only a matching statement is executed
...
Chapter 19:
Exception Handling
Handling Derived-Class Exceptions
You need to be careful how you order your catch statements when trying to catch
exception types that involve base and derived classes because a catch clause for a base
class will also match any class derived from that base
...
If you don't do this, the base class catch will also catch all
derived classes
...
// Catching derived classes
...
\n";
}
catch(D d) {
cout << "This won't execute
...
Some compilers will flag
this condition with a warning message
...
Either way, to fix
this condition, reverse the order of the catch clauses
...
These attributes are discussed here
...
This is easy to accomplish
...
catch(
...
The following program illustrates catch(
...
#include
using namespace std;
void Xhandler(int test)
{
try{
if(test==0) throw test; // throw int
if(test==1) throw 'a'; // throw char
if(test==2) throw 123
...
) { // catch all exceptions
cout << "Caught One!\n";
}
}
int main()
{
cout << "Start\n";
Xhandler(0);
Xhandler(1);
Xhandler(2);
cout << "End";
Chapter 19:
Exception Handling
return 0;
}
This program displays the following output
...
One very good use for catch(
...
In this
capacity it provides a useful default or "catch all" statement
...
) to catch all others
...
) as a default
...
23; // throw double
}
catch(int i) { // catch an int exception
cout << "Caught an integer\n";
}
catch(
...
Start
Caught an integer
Caught One!
Caught One!
End
As this example suggests, using catch(
...
Also, by catching all exceptions,
you prevent an unhandled exception from causing an abnormal program termination
...
In
fact, you can also prevent a function from throwing any exceptions whatsoever
...
The
general form of this is shown here:
ret-type func-name(arg-list) throw(type-list)
{
//
...
Throwing any other type of expression will cause abnormal program
termination
...
Attempting to throw an exception that is not supported by a function will cause the
standard library function unexpected( ) to be called
...
However, you can specify your
own unexpected handler if you like, as described later in this chapter
...
// Restricting function throw types
...
void Xhandler(int test) throw(int, char, double)
{
if(test==0) throw test; // throw int
if(test==1) throw 'a'; // throw char
if(test==2) throw 123
...
If it attempts to throw any other type of exception, an abnormal
program termination will occur
...
) To see an
example of this, remove int from the list and retry the program
...
That is, a try block within a
503
504
C++: The Complete Reference
function may throw any type of exception so long as it is caught within that function
...
The following change to Xhandler( ) prevents it from throwing any exceptions
...
Instead,
they will cause an abnormal program termination
...
23;
}
Note
At the time of this writing, Microsoft's Visual C++ does not support the throw( )
clause for functions
...
This causes the current exception to be
passed on to an outer try/catch sequence
...
For example, perhaps one exception
handler manages one aspect of an exception and a second handler copes with another
...
When you rethrow an exception, it will not be recaught
by the same catch statement
...
The
following program illustrates rethrowing an exception, in this case a char * exception
...
#include
using namespace std;
void Xhandler()
{
try {
throw "hello"; // throw a char *
}
catch(const char *) { // catch a char *
cout << "Caught char * inside Xhandler\n";
throw ; // rethrow char * out of function
}
}
Chapter 19:
Exception Handling
int main()
{
cout << "Start\n";
try{
Xhandler();
}
catch(const char *) {
cout << "Caught char * inside main\n";
}
cout << "End";
return 0;
}
This program displays this output:
Start
Caught char * inside Xhandler
Caught char * inside main
End
Understanding terminate( ) and unexpected( )
As mentioned earlier, terminate( ) and unexpected( ) are called when something goes
wrong during the exception handling process
...
Their prototypes are shown here:
void terminate( );
void unexpected( );
These functions require the header
...
It is also called if your program
attempts to rethrow an exception when no exception was originally thrown
...
For example, such a circumstance could occur when, in the process of unwinding the
stack because of an exception, a destructor for an object being destroyed throws an
exception
...
By default, terminate( ) calls abort( )
...
By default, unexpected( ) calls terminate( )
...
As just explained, by default terminate( ) calls abort( ), and
unexpected( ) calls terminate( )
...
However, you can change the
functions that are called by terminate( ) and unexpected( )
...
To change the terminate handler, use set_terminate( ), shown here:
terminate_handler set_terminate(terminate_handler newhandler) throw( );
Here, newhandler is a pointer to the new terminate handler
...
The new terminate handler must be of type
terminate_handler, which is defined like this:
typedef void (*terminate_handler) ( );
The only thing that your terminate handler must do is stop program execution
...
To change the unexpected handler, use set_unexpected( ), shown here:
unexpected_handler set_unexpected(unexpected_handler newhandler) throw( );
Here, newhandler is a pointer to the new unexpected handler
...
The new unexpected handler must be of type
unexpected_handler, which is defined like this:
typedef void (*unexpected_handler) ( );
This handler may itself throw an exception, stop the program, or call terminate( )
...
Both set_terminate( ) and set_unexpected( ) require the header
...
// Set a new terminate handler
...
}
return 0;
}
The output from this program is shown here
...
Its prototype is shown here:
bool uncaught_exception( );
This function returns true if an exception has been thrown but not yet caught
...
507
508
C++: The Complete Reference
The exception and bad_exception Classes
When a function supplied by the C++ standard library throws an exception, it will be
an object derived from the base class exception
...
These classes require the header
...
This implies that the error handler must do something
rational when an error occurs
...
It
inputs two numbers and divides the first by the second
...
#include
using namespace std;
void divide(double a, double b);
int main()
{
double i, j;
do {
cout << "Enter numerator (0 to stop): ";
cin >> i;
cout << "Enter denominator: ";
cin >> j;
divide(i, j);
} while(i != 0);
return 0;
}
void divide(double a, double b)
{
try {
if(!b) throw b; // check for divide-by-zero
cout << "Result: " << a/b << endl;
}
catch (double b) {
cout << "Can't divide by zero
...
Since division by zero is illegal, the program cannot
continue if a zero is entered for the second number
...
The program then reprompts the user
for two more numbers
...
The same basic concepts will apply to more
complex applications of exception handling
...
In this regard, C++'s exception handling is
designed to replace the rather clumsy C-based setjmp( ) and longjmp( ) functions
...
This means rectifying the situation, if possible
...
C++
Chapter 20
The C++ I/O System Basics
511
512
C++: The Complete Reference
++ supports two complete I/O systems: the one inherited from C and the
object-oriented I/O system defined by C++ (hereafter called simply the C++ I/O
system)
...
Here we will begin
to examine the C++ I/O system
...
The different aspects of C++'s I/O system, such as console I/O and disk
I/O, are actually just different perspectives on the same mechanism
...
Although the examples in this
chapter use "console" I/O, the information is applicable to other devices, including
disk files (discussed in Chapter 21)
...
The answer is that C's I/O
system knows nothing about objects
...
In addition to support for objects, there are several
benefits to using C++'s I/O system even in programs that don't make extensive (or
any) use of user-defined objects
...
The C I/O is supported by C++ only for compatibility
...
C
Old vs
...
The old I/O library is supported by the header file
The new I/O library is supported by the header
...
This is because the
new I/O library is, in essence, simply an updated and improved version of the old
one
...
From the programmer's perspective, there are two main differences between the
old and new C++ I/O libraries
...
Thus, the new I/O library is essentially a
superset of the old one
...
Second, the
old-style I/O library was in the global namespace
...
(Recall that the std namespace is used by all of the Standard C++ libraries
...
Chapter 20:
The C++ I/O System Basics
C++ Streams
Like the C-based I/O system, the C++ I/O system operates through streams
...
However, to summarize: A stream is a logical device that either produces or consumes
information
...
All streams
behave in the same way even though the actual physical devices they are connected to
may differ substantially
...
For example, you can use the
same function that writes to a file to write to the printer or to the screen
...
The C++ Stream Classes
As mentioned, Standard C++ provides support for its I/O system in
...
The I/O classes begin with a system of template classes
...
Once a template class has been defined, specific
instances of it can be created
...
This book will use only the 8-bit character classes since they are by far
the most common
...
The C++ I/O system is built upon two related but different template class
hierarchies
...
This class supplies the basic, low-level input and output operations, and provides the
underlying support for the entire C++ I/O system
...
The class hierarchy
that you will most commonly be working with is derived from basic_ios
...
(A base class for basic_ios is called ios_base, which defines
several nontemplate traits used by basic_ios
...
These
classes are used to create streams capable of input, output, and input/output,
respectively
...
Here is
a list of the mapping of template class names to their character and wide-character
versions
...
They are also
the same names that were used by the old I/O library
...
One last point: The ios class contains many member functions and variables that
control or monitor the fundamental operation of a stream
...
Just remember that if you include
have access to this important class
...
They are:
Stream
Meaning
Default Device
cin
Standard input
Keyboard
cout
Standard output
Screen
cerr
Standard error output
Screen
clog
Buffered version of cerr
Screen
Streams cin, cout, and cerr correspond to C's stdin, stdout, and stderr
...
However, in environments that support I/O redirection (such as DOS, Unix, OS/2, and
Windows), the standard streams can be redirected to other devices or files
...
Chapter 20:
The C++ I/O System Basics
Standard C++ also defines these four additional streams: win, wout, werr, and
wlog
...
Wide characters are
of type wchar_t and are generally 16-bit quantities
...
Formatted I/O
The C++ I/O system allows you to format I/O operations
...
There are two related but conceptually different ways that
you can format data
...
Specifically, you can set various format status flags defined inside the ios class or
call various ios member functions
...
We will begin the discussion of formatted I/O by using the ios member functions
and flags
...
The ios class declares a bitmask enumeration called fmtflags
in which the following values are defined
...
)
adjustfield
basefield
boolalpha
dec
fixed
floatfield
hex
internal
left
oct
right
scientific
showbase
showpoint
showpos
skipws
unitbuf
uppercase
These values are used to set or clear the format flags
...
In this case, the format flags
will be encoded into a long integer
...
When skipws is cleared,
white-space characters are not discarded
...
When right is set, output is right
justified
...
If none of these flags are set,
output is right justified by default
...
However, it is possible to change
the number base
...
Setting the
hex flag causes output to be displayed in hexadecimal
...
Setting showbase causes the base of numeric values to be shown
...
By default, when scientific notation is displayed, the e is in lowercase
...
When uppercase is set, these
characters are displayed in uppercase
...
Setting showpoint causes a decimal point and trailing zeros to be displayed for all
floating-point output—whether needed or not
...
When fixed is set, floating-point values are displayed using normal
notation
...
When unitbuf is set, the buffer is flushed after each insertion operation
...
Since it is common to refer to the oct, dec, and hex fields, they can be collectively
referred to as basefield
...
Finally, the scientific and fixed fields can be referenced as floatfield
...
This function is a member of ios
...
For example, to turn on the showpos flag, you can use this statement:
stream
...
Notice the use of ios:: to qualify showpos
...
The following program displays the value 100 with the showpos and showpoint
flags turned on
...
setf(ios::showpoint);
cout
...
0; // displays +100
...
Therefore, any call to setf( ) is done relative to a
specific stream
...
Put differently, there is
no concept in C++ of global format status
...
Although there is nothing technically wrong with the preceding program, there
is a more efficient way to write it
...
For example, this single call
accomplishes the same thing:
// You can OR together two or more flags,
cout
...
For example, showbase by
itself will not be recognized
...
Clearing Format Flags
The complement of setf( ) is unsetf( )
...
Its general form is
void unsetf(fmtflags flags);
The flags specified by flags are cleared
...
) The previous
flag settings are returned
...
It first sets both the uppercase and
scientific flags
...
12 in scientific notation
...
Next, it clears the uppercase flag and again
outputs 100
...
"
#include
using namespace std;
int main()
{
cout
...
12;
// displays 1
...
unsetf(ios::uppercase); // clear uppercase
cout << " \n" << 100
...
0012e+02
return 0;
}
An Overloaded Form of setf( )
There is an overloaded form of setf( ) that takes this general form:
fmtflags setf(fmtflags flags1, fmtflags flags2);
In this version, only the flags specified by flags2 are affected
...
Note that even if flags1 contains other
flags, only those specified by flags2 will be affected
...
For example,
#include
using namespace std;
int main( )
{
cout
...
0; // displays 100
...
0
return 0;
}
Chapter 20:
The C++ I/O System Basics
Here, showpoint is set, but not showpos, since it is not specified in the second
parameter
...
As explained, references to the oct,
dec, and hex fields can collectively be referred to as basefield
...
Finally, the scientific and fixed
fields can be referenced as floatfield
...
For
example, the following program sets output to hexadecimal
...
This is most easily accomplished using the
two-parameter form of setf( )
...
setf(ios::hex, ios::basefield);
cout << 100; // this displays 64
return 0;
}
Here, the basefield flags (i
...
, dec, oct, and hex) are first cleared and then the hex flag
is set
...
For example, in this program, the first attempt to set the showpos flag fails
...
#include
using namespace std;
int main()
{
cout
...
setf(ios::showpos, ios::showpos); // this is correct
519
520
C++: The Complete Reference
cout << 100; // now displays +100
return 0;
}
Keep in mind that most of the time you will want to use unsetf( ) to clear flags and
the single parameter version of setf( ) (described earlier) to set flags
...
Another good use may involve a situation in which you are using a
flag template that specifies the state of all format flags but wish to alter only one or
two
...
Examining the Formatting Flags
There will be times when you only want to know the current format settings but not
alter any
...
Its prototype is shown here:
fmtflags flags( );
The following program uses flags( ) to display the setting of the format flags
relative to cout
...
You might find it
useful in programs you write
...
setf(ios::right | ios::showpoint | ios::fixed);
showflags();
return 0;
}
Chapter 20:
The C++ I/O System Basics
// This function displays the status of the format flags
...
flags(); // get flag settings
// check each flag
for(i=0x4000; i; i = i >> 1)
if(i & f) cout << "1 ";
else cout << "0 ";
cout << " \n";
}
The output from the program is shown here:
0 0 0 0 0 1 0 0 0 0 0 0 0 0 1
0 1 0 0 0 1 0 1 0 0 1 0 0 0 1
Setting All Flags
The flags( ) function has a second form that allows you to set all format flags associated
with a stream
...
Thus, all format flags are affected
...
The next program illustrates this version of flags( )
...
All other flags are off
...
The function
showflags( ) verifies that the flags are set as indicated
...
)
#include
using namespace std;
521
522
C++: The Complete Reference
void showflags();
int main()
{
// show default condition of format flags
showflags();
// showpos, showbase, oct, right are on, others off
long f = ios::showpos | ios::showbase | ios::oct | ios::right;
cout
...
The functions that do these things are width( ), precision( ), and fill( ), respectively
...
By default, when a value is output, it occupies only as much space as the number
of characters it takes to display it
...
Its prototype is shown here:
streamsize width(streamsize w);
Here, w becomes the field width, and the previous field width is returned
...
If it isn't, the default
field width is used
...
After you set a minimum field width, when a value uses less than the specified
width, the field will be padded with the current fill character (space, by default) to
reach the field width
...
No values are truncated
...
Its prototype is
shown here:
streamsize precision(streamsize p);
Chapter 20:
The C++ I/O System Basics
Here, the precision is set to p, and the old value is returned
...
In some implementations, the precision must be set before each floating-point output
...
By default, when a field needs to be filled, it is filled with spaces
...
Its prototype is
char fill(char ch);
After a call to fill( ), ch becomes the new fill character, and the old one is returned
...
precision(4) ;
cout
...
12345 << "\n";
// displays 10
...
fill('*');
cout
...
12345 << "\n"; // displays *****10
...
width(10);
cout << "Hi!" << "\n"; // displays *******Hi!
cout
...
setf(ios::left); // left justify
cout << 10
...
12*****
return 0;
}
This program's output is shown here:
10
...
12
*******Hi!
10
...
These forms are shown here:
char fill( );
streamsize width( );
streamsize precision( );
Using Manipulators to Format I/O
The second way you can alter the format parameters of a stream is through the use of
special functions called manipulators that can be included in an I/O expression
...
As you can see by examining the table,
many of the I/O manipulators parallel member functions of the ios class
...
Manipulator
Purpose
Input/Output
boolalpha
Turns on boolapha flag
...
Input/Output
endl
Output a newline character
and flush the stream
...
Output
fixed
Turns on fixed flag
...
Output
hex
Turns on hex flag
...
Output
left
Turns on left flag
...
Input/Output
noshowbase
Turns off showbase flag
...
Output
noshowpos
Turns off showpos flag
...
The C++ Manipulators
Chapter 20:
The C++ I/O System Basics
Manipulator
Purpose
Input/Output
noskipws
Turns off skipws flag
...
Output
nouppercase
Turns off uppercase flag
...
Input/Output
resetiosflags (fmtflags f )
Turn off the flags
specified in f
...
Output
scientific
Turns on scientific flag
...
Input/Output
setfill(int ch)
Set the fill character to ch
...
Input/output
setprecision (int p)
Set the number of digits of
precision
...
Output
showbase
Turns on showbase flag
...
Output
showpos
Turns on showpos flag
...
Input
unitbuf
Turns on unitbuf flag
...
Output
ws
Skip leading white space
...
The C++ Manipulators (continued)
To access manipulators that take parameters (such as setw( )), you must include
...
0;
return 0;
}
This displays
64
??????2343
Notice how the manipulators occur within a larger I/O expression
...
This is because it is the address of the function that is passed
to the overloaded << operator
...
setf(ios::hex, ios::basefield);
cout << 100 << "\n"; // 100 in hex
cout
...
width(10);
cout << 2343
...
You can use the setiosflags( ) manipulator to directly set the various format flags
related to a stream
...
One of the more interesting manipulators is boolapha
...
For example,
#include
using namespace std;
int main()
{
bool b;
b = true;
cout << b << " " << boolalpha << b << endl;
cout << "Enter a Boolean value: ";
cin >> boolalpha >> b;
cout << "Here is what you entered:
return 0;
}
" << b;
527
528
C++: The Complete Reference
Here is a sample run
...
You can also overload these operators so that they
perform I/O operations on types that you create
...
Likewise, the >> input operator is called the
extraction operator because it extracts characters from a stream
...
Creating Your Own Inserters
It is quite simple to create an inserter for a class that you create
...
(Remember,
ostream is a class derived from ios that supports output
...
The second parameter is the object
being inserted
...
) The last thing the inserter must do before exiting is return stream
...
Within an inserter function, you may put any type of procedures or operations that
you want
...
However,
for the inserter to be in keeping with good programming practices, you should limit its
operations to outputting information to a stream
...
class phonebook {
public:
Chapter 20:
The C++ I/O System Basics
char name[80];
int areacode;
int prefix;
int num;
phonebook(char *n, int a, int p, int nm)
{
strcpy(name, n);
areacode = a;
prefix = p;
num = nm;
}
};
This class holds a person's name and telephone number
...
// Display name and phone number
ostream &operator<<(ostream &stream, phonebook o)
{
stream << o
...
areacode << ") ";
stream << o
...
num << "\n";
return stream; // must return stream
}
Here is a short program that illustrates the phonebook inserter function:
#include
#include
using namespace std;
class phonebook {
public:
char name[80];
int areacode;
int prefix;
int num;
phonebook(char *n, int a, int p, int nm)
{
strcpy(name, n);
areacode = a;
prefix = p;
num = nm;
529
530
C++: The Complete Reference
}
};
// Display name and phone number
...
name << " ";
stream << "(" << o
...
prefix << "-" << o
...
Although this may seem weird at first, the reason is easy to understand
...
Further, this object is an object of the class for which the operator function is a member
...
If an overloaded operator function is a member of a
class, the left operand must be an object of that class
...
Therefore, overloaded inserters cannot be members of the class for which they are
overloaded
...
Chapter 20:
The C++ I/O System Basics
The fact that inserters cannot be members of the class for which they are defined
seems to be a serious flaw in C++
...
However, encapsulation is an essential component of objectoriented programming
...
Fortunately, there is a solution to this dilemma: Make the inserter
a friend of the class
...
Here is the same program modified
to make the inserter into a friend function:
#include
#include
using namespace std;
class phonebook {
// now private
char name[80];
int areacode;
int prefix;
int num;
public:
phonebook(char *n, int a, int p, int nm)
{
strcpy(name, n);
areacode = a;
prefix = p;
num = nm;
}
friend ostream &operator<<(ostream &stream, phonebook o);
};
// Display name and phone number
...
name << " ";
stream << "(" << o
...
prefix << "-" << o
...
For example, the inserter shown in the preceding example can be used with
any stream because the body of the function directs its output to stream, which is the
stream that invoked the inserter
...
name << " ";
as
cout << o
...
The original
version will work with any stream, including those linked to disk files
...
In general, the more flexible
your inserters are, the more valuable they are
...
To fix
this, you can either make num into a string or you can set the fill character to zero
and use the width( ) format function to generate the leading zeroes
...
Before moving on to extractors, let's look at one more example of an inserter
function
...
An inserter can be used
to output data in any form that makes sense
...
Another inserter might
generate graphics images
...
To sample the flavor of outputting things other than text, examine the
following program, which draws boxes on the screen
...
)
#include
using namespace std;
class box {
int x, y;
public:
box(int i, int j) { x=i; y=j; }
friend ostream &operator<<(ostream &stream, box o);
};
// Output a box
...
x; i++)
stream << "*";
stream << "\n";
for(j=1; j
x; i++)
if(i==0 || i==o
...
x; i++)
stream << "*";
stream << "\n";
return stream;
}
int main()
{
box a(14, 6), b(30, 7), c(40, 5);
533
534
C++: The Complete Reference
cout << "Here are some boxes:\n";
cout << a << b << c;
return 0;
}
The program displays the following:
Here are some boxes:
**************
*
*
*
*
*
*
*
*
**************
******************************
*
*
*
*
*
*
*
*
*
*
******************************
****************************************
*
*
*
*
*
*
****************************************
Creating Your Own Extractors
Extractors are the complement of inserters
...
The
first parameter must also be a reference to a stream of type istream
...
This is so the object can be modified by the input (extraction) operation
...
name;
cout << "Enter area code: ";
stream >> o
...
prefix;
cout << "Enter number: ";
stream >> o
...
The point is that although the main purpose of an extractor is input, it can
perform any operations necessary to achieve that end
...
If you
don't, you run the risk of losing much in terms of structure and clarity
...
ostream &operator<<(ostream &stream, phonebook o)
{
stream << o
...
areacode << ") ";
stream << o
...
num << "\n";
return stream; // must return stream
}
// Input name and telephone number
...
name;
cout << "Enter area code: ";
stream >> o
...
prefix;
cout << "Enter number: ";
stream >> o
...
If the extractor is used on a
stream connected to a disk file, for example, then the cout statements would not be
applicable
...
For example, you might use if statements such as
the one shown here
...
Creating Your Own Manipulator Functions
In addition to overloading the insertion and extraction operators, you can further
customize C++'s I/O system by creating your own manipulator functions
...
First, you can consolidate a sequence
of several separate I/O operations into one manipulator
...
In these cases you can use a custom manipulator to
perform these actions, thus simplifying your source code and preventing accidental
errors
...
For example, you might use a manipulator to send
control codes to a special type of printer or to an optical recognition system
...
As you will see, custom manipulators can help
make any I/O-intensive program clearer and more efficient
...
In addition to these two broad
categories, there is a secondary division: those manipulators that take an argument and
those that don't
...
For this reason, you must consult the documentation to
your compiler for instructions on creating parameterized manipulators
...
It is described here
...
Notice that a reference to a stream of
type ostream is returned
...
It is important to note that even though the manipulator has as its
single argument a reference to the stream upon which it is operating, no argument is
used when the manipulator is inserted in an output operation
...
#include
#include
using namespace std;
// A simple output manipulator
...
setf(ios::showbase);
stream
...
As you can see, sethex is used as part of an I/O
expression in the same way as any of the built-in manipulators
...
For example, the simple
manipulators la( ) and ra( ) display a left and right arrow for emphasis, as shown here:
#include
#include
using namespace std;
// Right Arrow
ostream &ra(ostream &stream)
{
stream << "-------> ";
return stream;
Chapter 20:
The C++ I/O System Basics
}
// Left Arrow
ostream &la(ostream &stream)
{
stream << " <-------";
return stream;
}
int main()
{
cout << "High balance " << ra << 1233
...
66 << la;
return 0;
}
This program displays:
High balance -------> 1233
...
66 <-------
If used frequently, these simple manipulators save you from some tedious typing
...
For example, a printer may be able to accept various codes that change the type
size or font, or that position the print head in a special location
...
All parameterless input manipulator functions have this skeleton:
istream &manip-name(istream &stream)
{
// your code here
return stream;
}
An input manipulator receives a reference to the stream for which it was invoked
...
The following program creates the getpass( ) input manipulator, which rings the
bell and then prompts for a password:
#include
#include
539
540
C++: The Complete Reference
using namespace std;
// A simple input manipulator
...
If it does not, your
manipulator cannot be used in a series of input or output operations
...
In part, this is because the most common file is a disk file, and disk files
have capabilities and features that most other devices don't
...
A
To perform file I/O, you must include the header
...
These classes are
derived from istream, ostream, and iostream, respectively
...
Another class used by the file system is filebuf, which provides low-level facilities to
manage a file stream
...
Opening and Closing a File
In C++, you open a file by linking it to a stream
...
There are three types of streams: input, output, and input/output
...
To
create an output stream, you must declare it as class ofstream
...
For
example, this fragment creates one input stream, one output stream, and one stream
capable of both input and output:
ifstream in; // input
ofstream out; // output
fstream io;
// input and output
Once you have created a stream, one way to associate it with a file is by using
open( )
...
The prototype for
each is shown here:
void ifstream::open(const char *filename, ios::openmode mode = ios::in);
void ofstream::open(const char *filename, ios::openmode mode = ios::out | ios::trunc);
void fstream::open(const char *filename, ios::openmode mode = ios::in | ios::out);
Chapter 21:
C++ File I/O
Here, filename is the name of the file; it can include a path specifier
...
It must be one or more of the following values defined
by openmode, which is an enumeration defined by ios (through its base class ios_base)
...
Including ios::app causes all output to that file to be appended to the end
...
Including ios::ate causes a seek to
the end of the file to occur when the file is opened
...
The ios::in value specifies that the file is capable of input
...
The ios::binary value causes a file to be opened in binary mode
...
In text mode, various character translations may take place,
such as carriage return/linefeed sequences being converted into newlines
...
Understand that any file, whether it contains formatted text or raw data, can be opened
in either binary or text mode
...
The ios::trunc value causes the contents of a preexisting file by the same name to be
destroyed, and the file is truncated to zero length
...
The following fragment opens a normal output file
...
open("test", ios::out);
However, you will seldom see open( ) called as shown, because the mode parameter
provides default values for each type of stream
...
Therefore, the preceding statement will usually look like this:
out
...
Therefore, you might need to specify this explicitly
...
Therefore, before using a file, you should test to make sure that the open operation
succeeded
...
\n";
// handle error
}
Although it is entirely proper to open a file by using the open( ) function, most of
the time you will not do so because the ifstream, ofstream, and fstream classes have
constructor functions that automatically open the file
...
Therefore, you will most
commonly see a file opened as shown here:
ifstream mystream("myfile"); // open file for input
As stated, if for some reason the file cannot be opened, the value of the associated
stream variable will evaluate to false
...
You can also check to see if you have successfully opened a file by using the
is_open( ) function, which is a member of fstream, ifstream, and ofstream
...
For example,
the following checks if mystream is currently open:
if(!mystream
...
\n";
//
...
For example, to close the file linked
to a stream called mystream, use this statement:
mystream
...
Chapter 21:
C++ File I/O
Reading and Writing Text Files
It is very easy to read from or write to a text file
...
For example, this program creates a
short inventory file that contains each item's name and its cost:
#include
#include
using namespace std;
int main()
{
ofstream out("INVNTRY"); // output, normal file
if(!out) {
cout << "Cannot open INVENTORY file
...
95 << endl;
out << "Toasters " << 19
...
80 << endl;
out
...
\n";
return 1;
545
546
C++: The Complete Reference
}
char item[20];
float cost;
in >> item >> cost;
cout << item << " " << cost << "\n";
in >> item >> cost;
cout << item << " " << cost << "\n";
in >> item >> cost;
cout << item << " " << cost << "\n";
in
...
All information is stored in the file in the
same format as it would be displayed on the screen
...
This program reads strings entered at
the keyboard and writes them to disk
...
To use the program, specify the name of the output file on the
command line
...
\n";
return 1;
}
Chapter 21:
C++ File I/O
char str[80];
cout << "Write strings to disk
...
\n";
do {
cout << ": ";
cin >> str;
out << str << endl;
} while (*str != '!');
out
...
For example, white-space characters are omitted
...
When inputting, if end-of-file is encountered, the stream linked to that file will
evaluate as false
...
)
Unformatted and Binary I/O
While reading and writing formatted text files is very easy, it is not always the most
efficient way to handle files
...
The functions that allow you to do this are
described here
...
Although the unformatted file functions will work on files
opened for text mode, some character translations may occur
...
Characters vs
...
For many years, I/O in C and C++ was thought of as byte oriented
...
However, with the advent of wide characters (of type wchar_t) and
their attendant streams, we can no longer say that C++ I/O is byte oriented
...
Of course, char streams are still byte oriented
and we can continue to think in terms of bytes, especially when operating on
547
548
C++: The Complete Reference
nontextual data
...
As explained in Chapter 20, all of the streams used in this book are char streams
since they are by far the most common
...
put( ) and get( )
One way that you may read and write unformatted data is by using the member
functions get( ) and put( )
...
That is, get( ) will
read a character and put( ) will write a character
...
The get( ) function has many forms, but the most commonly used version is shown
here along with put( ):
istream &get(char &ch);
ostream &put(char ch);
The get( ) function reads a single character from the invoking stream and puts that
value in ch
...
The put( ) function writes ch to the
stream and returns a reference to the stream
...
It uses the get( ) function
...
";
Chapter 21:
C++ File I/O
return 1;
}
while(in) { // in will be false when eof is reached
in
...
Therefore, when in reaches the end of the file, it
will be false, causing the while loop to stop
...
get(ch))
cout << ch;
This works because get( ) returns a reference to the stream in, and in will be false when
the end of the file is encountered
...
As you probably know, the ASCII characters occupy only about half the
available values that can be held by a char
...
(Not all systems support the extended character set, but most do
...
\n";
return 1;
}
549
550
C++: The Complete Reference
// write all characters to disk
for(i=0; i<256; i++) out
...
close();
return 0;
}
You might find it interesting to examine the contents of the CHARS file to see what
extended characters your computer has available
...
Their prototypes are
istream &read(char *buf, streamsize num);
ostream &write(const char *buf, streamsize num);
The read( ) function reads num characters from the invoking stream and puts them in
the buffer pointed to by buf
...
As mentioned in the preceding chapter,
streamsize is a type defined by the C++ library as some form of integer
...
The next program writes a structure to disk and then reads it back in:
#include
#include