0-07-222680-3.All trademarks are trademarks of their respective owners. Rather than put a trademark symbol after every occurrence of a trademarked [630814]
C++:
The Complete Reference,
Fourth Edition
About the Author
Herbert Schildt is the world’s leading programming
author. He is an authority on the C, C++, Java, and C#
languages, and is a master Windows programmer. Hisprogramming books have sold more than 3 millioncopies worldwide and have been translated into allmajor foreign languages. He is the author of numerousbestsellers, including C++: The Complete Reference, C#:
The Complete Reference, Java 2: The Complete Reference,C: The Complete Reference, C++ from the Ground Up,C++: A Beginner’s Guide, C#: A Beginner’s Guide, and
Java 2: A Beginner’s Guide. Schildt holds a master’sdegree in computer science from the University ofIllinois. He can be reached at his consulting office at(217) 586-4683.
Copyright © 2003 by The McGraw-Hill Companies. Click here for terms of use.
C++:
The Complete Reference,
Fourth Edition
Herbert Schildt
McGraw-Hill/Osborne
New York Chicago San Francisco
Lisbon London Madrid Mexico City
Milan New Delhi San Juan
Seoul Singapore Sydney Toronto
Copyright © 2003 by The McGraw-Hill Companies. All rights reserved. Manufactured in the United States of America. Except as per –
mitted under the United States Copyright Act of 1976, no part of this publication may be reproduced or distributed in any form or by any
means, or stored in a database or retrieval system, without the prior written permission of the publisher.
0-07-150239-4The material in this eBook also appears in the print version of this title: 0-07-222680-3.All trademarks are trademarks of their respective owners. Rather than put a trademark symbol after every occurrence of a trademarked
name, we use names in an editorial fashion only, and to the benefit of the trademark owner, with no intention of infringement of the trademark. Where such designations appear in this book, they have been printed with initial caps.
McGraw-Hill eBooks are available at special quantity discounts to use as premiums and sales promotions, or for use in corporate
training programs. For more information, please contact George Hoare, Special Sales, at [anonimizat] or
(212) 904-4069.
TERMS OF USE This is a copyrighted work and The McGraw-Hill Companies, Inc. (“McGraw-Hill”) and its licensors reserve all rights in and to t he work.
Use of this work is subject to these terms. Except as permitted under the Copyright Act of 1976 and the right to store and retrieve one
copy of the work, you may not decompile, disassemble, reverse engineer, reproduce, modify, create derivative works based upon, trans-
mit, distribute, disseminate, sell, publish or sublicense the work or any part of it without McGraw-Hill’ s prior consent. Y ou m ay use the
work for your own noncommercial and personal use; any other use of the work is strictly prohibited. Y our right to use the work may be
terminated if you fail to comply with these terms.
THE WORK IS PROVIDED “AS IS.” McGRAW-HILL AND ITS LICENSORS MAKE NO GUARANTEES OR WARRANTIES AS TO
THE ACCURACY , ADEQUACY OR COMPLETENESS OF OR RESULTS TO BE OBTAINED FROM USING THE WORK,INCLUDING ANY INFORMATION THAT CAN BE ACCESSED THROUGH THE WORK VIA HYPERLINK OR OTHERWISE,AND EXPRESSL Y DISCLAIM ANY WARRANTY , EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WAR-RANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. McGraw-Hill and its licensors do not warrantor guarantee that the functions contained in the work will meet your requirements or that its operation will be uninterrupted o r error free.
Neither McGraw-Hill nor its licensors shall be liable to you or anyone else for any inaccuracy, error or omission, regardless of cause, inthe work or for any damages resulting therefrom. McGraw-Hill has no responsibility for the content of any information accessed through
the work. Under no circumstances shall McGraw-Hill and/or its licensors be liable for any indirect, incidental, special, puniti ve, conse-
quential or similar damages that result from the use of or inability to use the work, even if any of them has been advised of the possibil-ity of such damages. This limitation of liability shall apply to any claim or cause whatsoever whether such claim or cause arises in con-tract, tort or otherwise.
DOI: 10.1036/0072226803
We hope you enjoy this
McGraw-Hill eBook! If
you’d like more information about this book,its author, or related books and websites,please click here.Professional
Want to learn more?
Contents at a Glance
Part I The Foundation of C++: The C Subset
1An Overview of C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
4Arrays and Null-Terminated Strings . . . . . . . . . . . . . . . . 89
5Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
6Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
7Structures, Unions, Enumerations,
and User-Defined Types . . . . . . . . . . . . . . . . . . . . . . . . 161
8C-Style Console I/O . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
9File I/O . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
10 The Preprocessor and Comments . . . . . . . . . . . . . . . . . . 237
Part II C++
11 An Overview of C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255
12 Classes and Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289
v
13 Arrays, Pointers, References, and the Dynamic
Allocation Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . 325
14 Function Overloading, Copy Constructors,
and Default Arguments . . . . . . . . . . . . . . . . . . . . . . . . . 359
15 Operator Overloading . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383
16 Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417
17 Virtual Functions and Polymorphism . . . . . . . . . . . . . . . 443
18 Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 459
19 Exception Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 487
20 The C++ I/O System Basics . . . . . . . . . . . . . . . . . . . . . . . 509
21 C++ File I/O . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 539
22 Run-Time Type ID and the Casting Operators . . . . . . . 567
23 Namespaces, Conversion Functions,
and Other Advanced Topics . . . . . . . . . . . . . . . . . . . . . 591
24 Introducing the Standard Template Library . . . . . . . . . . 629
Part III The Standard Function Library
25 The C-Based I/O Functions . . . . . . . . . . . . . . . . . . . . . . . 699
26 The String and Character Functions . . . . . . . . . . . . . . . . 723
27 The Mathematical Functions . . . . . . . . . . . . . . . . . . . . . . 737
28 Time, Date, and Localization Functions . . . . . . . . . . . . . 747
29 The Dynamic Allocation Functions . . . . . . . . . . . . . . . . . 757
30 Utility Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 761
31 The Wide-Character Functions . . . . . . . . . . . . . . . . . . . . . 775
Part IV The Standard C++ Class Library
32 The Standard C++ I/O Classes . . . . . . . . . . . . . . . . . . . . 787
33 The STL Container Classes . . . . . . . . . . . . . . . . . . . . . . . . 811
34 The STL Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 839
35 STL Iterators, Allocators, and Function Objects . . . . . . 861
36 The String Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 881
37 The Numeric Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 897
38 Exception Handling and Miscellaneous Classes . . . . . . 925vi C++: The Complete Reference
Part V Applying C++
39 Integrating New Classes: A Custom String Class . . . . . 935
40 Parsing Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 963
A The .NET Managed Extensions to C++ . . . . . . . . . . . . . . 999
B C++ and the Robotics Age . . . . . . . . . . . . . . . . . . . . . . . . 1005
Index. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1009Contents at a Glance vii
This page intentionally left blank
Contents
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxix
Part I
The Foundation of C++: The C Subset
1An Overview of C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
The Origins and History of C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
C Is a Middle-Level Language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5C Is a Structured Language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6C Is a Programmer’s Language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8The Form of a C Program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9The Library and Linking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10Separate Compilation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12Understanding the .C and .CPP File Extensions . . . . . . . . . . . . . . . . 12
2Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
The Five Basic Data Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14Modifying the Basic Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15Identifier Names . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Where Variables Are Declared . . . . . . . . . . . . . . . . . . . . . . . . 18Local Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
ixFor more information about this title, click here
x C++: The Complete Reference
Formal Parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Global Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
The const and volatile Qualifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
const . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23volatile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Storage Class Specifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
extern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25static Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28register Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Variable Initializations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Hexadecimal and Octal Constants . . . . . . . . . . . . . . . . . . . . 33String Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33Backslash Character Constants . . . . . . . . . . . . . . . . . . . . . . . 33
Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
The Assignment Operator . . . . . . . . . . . . . . . . . . . . . . . . . . . 35Type Conversion in Assignments . . . . . . . . . . . . . . . . . . . . . 35Multiple Assignments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37Arithmetic Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37Increment and Decrement . . . . . . . . . . . . . . . . . . . . . . . . . . . 38Relational and Logical Operators . . . . . . . . . . . . . . . . . . . . . 40Bitwise Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42The ? Operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47The & and * Pointer Operators . . . . . . . . . . . . . . . . . . . . . . . 48The Compile-Time Operator sizeof . . . . . . . . . . . . . . . . . . . 50The Comma Operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50The Dot (.) and Arrow (−>) Operators . . . . . . . . . . . . . . . . . 51
The [ ] and ( ) Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51Precedence Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Order of Evaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52Type Conversion in Expressions . . . . . . . . . . . . . . . . . . . . . . 53Casts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54Spacing and Parentheses . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55Compound Assignments . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
3Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
True and False in C and C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58Selection Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59Nested ifs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60The if-else-if Ladder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62The ? Alternative . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63The Conditional Expression . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Contents xi
switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Nested switch Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
Iteration Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
The for Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70for Loop Variations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72The Infinite Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76for Loops with No Bodies . . . . . . . . . . . . . . . . . . . . . . . . . . . 77The while Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77The do-while Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Declaring Variables Within Selection and Iteration Statements . . . 81Jump Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
The return Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82The goto Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83The break Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83The exit( ) Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85The continue Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
Expression Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88Block Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
4Arrays and Null-Terminated Strings . . . . . . . . . . . . . . . . 89
Single-Dimension Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90Generating a Pointer to an Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92Passing Single-Dimension Arrays to Functions . . . . . . . . . . . . . . . . 92Null-Terminated Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94Two-Dimensional Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
Arrays of Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
Multidimensional Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101Indexing Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102Array Initialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
Unsized Array Initializations . . . . . . . . . . . . . . . . . . . . . . . . . 106
A Tic-Tac-Toe Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
5Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
What Are Pointers? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114Pointer Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115The Pointer Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115Pointer Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
Pointer Assignments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117Pointer Arithmetic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117Pointer Comparisons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
Pointers and Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
Arrays of Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
Multiple Indirection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123Initializing Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125Pointers to Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
xii C++: The Complete Reference
C's Dynamic Allocation Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
Problems with Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
6Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
The General Form of a Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138Scope Rules of Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138Function Arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
Call by Value, Call by Reference . . . . . . . . . . . . . . . . . . . . . . 139Creating a Call by Reference . . . . . . . . . . . . . . . . . . . . . . . . . 140Calling Functions with Arrays . . . . . . . . . . . . . . . . . . . . . . . 142
argc and argv—Arguments to main( ) . . . . . . . . . . . . . . . . . . . . . . . . 144The return Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
Returning from a Function . . . . . . . . . . . . . . . . . . . . . . . . . . . 148Returning Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149Returning Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151Functions of Type void . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152What Does main( ) Return? . . . . . . . . . . . . . . . . . . . . . . . . . . 153
Recursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153Function Prototypes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
Standard Library Function Prototypes . . . . . . . . . . . . . . . . . 157
Declaring Variable-Length Parameter Lists . . . . . . . . . . . . . . . . . . . . 158Old-Style Versus Modern FunctionParameter Declarations . . . . . . 158
7Structures, Unions, Enumerations,
and User-Defined Types . . . . . . . . . . . . . . . . . . . . . . . . . 161
Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
Accessing Structure Members . . . . . . . . . . . . . . . . . . . . . . . . 165Structure Assignments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
Arrays of Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166Passing Structures to Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
Passing Structure Members to Functions . . . . . . . . . . . . . . . 167Passing Entire Structures to Functions . . . . . . . . . . . . . . . . . 168
Structure Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
Declaring a Structure Pointer . . . . . . . . . . . . . . . . . . . . . . . . . 170Using Structure Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
Arrays and Structures Within Structures . . . . . . . . . . . . . . . . . . . . . . 173Bit-Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174Unions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176Enumerations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180Using sizeof to Ensure Portability . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183typedef . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
8C-Style Console I/O . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
An Important Application Note . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
Contents xiii
Reading and Writing Characters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
A Problem with getchar( ) . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
Alternatives to getchar( ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
Reading and Writing Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192Formatted Console I/O . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195printf( ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
Printing Characters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196Printing Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196Displaying an Address . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198The %n Specifier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198Format Modifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199The Minimum Field Width Specifier . . . . . . . . . . . . . . . . . . 199The Precision Specifier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200Justifying Output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201Handling Other Data Types . . . . . . . . . . . . . . . . . . . . . . . . . . 202The * and # Modifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
scanf( ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
Format Specifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203Inputting Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203Inputting Unsigned Integers . . . . . . . . . . . . . . . . . . . . . . . . . 205Reading Individual Characters Using scanf( ) . . . . . . . . . . 205Reading Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205Inputting an Address . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206The %n Specifier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206Using a Scanset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206Discarding Unwanted White Space . . . . . . . . . . . . . . . . . . . 207Non-White-Space Characters in the Control String . . . . . . 208You Must Pass scanf( ) Addresses . . . . . . . . . . . . . . . . . . . . . 208Format Modifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208Suppressing Input . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
9File I/O . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
C Versus C++ File I/O . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212Streams and Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
Text Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213Binary Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213File System Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
The File Pointer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215Opening a File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215Closing a File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217Writing a Character . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217Reading a Character . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
xiv C++: The Complete Reference
Using fopen( ), getc( ), putc( ), and fclose( ) . . . . . . . . . . . . . 218
Using feof( ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220Working with Strings: fputs( ) and fgets( ) . . . . . . . . . . . . . . 222rewind( ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223ferror( ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224Erasing Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226Flushing a Stream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226
fread( ) and fwrite( ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
Using fread( ) and fwrite( ) . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
fseek( ) and Random-Access I/O . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229fprintf( ) and fscanf( ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230The Standard Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
The Console I/O Connection . . . . . . . . . . . . . . . . . . . . . . . . . 233Using freopen( ) to Redirect the Standard Streams . . . . . . 234
10 The Preprocessor and Comments . . . . . . . . . . . . . . . . . . 237
The Preprocessor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238#define . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
Defining Function-like Macros . . . . . . . . . . . . . . . . . . . . . . . 240
#error . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241#include . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242Conditional Compilation Directives . . . . . . . . . . . . . . . . . . . . . . . . . . 242
#if, #else, #elif, and #endif . . . . . . . . . . . . . . . . . . . . . . . . . . . 243#ifdef and #ifndef . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
#undef . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246Using defined . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247#line . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248#pragma . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248The # and ## Preprocessor Operators . . . . . . . . . . . . . . . . . . . . . . . . . 248Predefined Macro Names . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250Comments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250Single-Line Comments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
Part II
C++
11 An Overview of C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255
The Origins of C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256What Is Object-Oriented Programming? . . . . . . . . . . . . . . . . . . . . . . 257
Encapsulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258Polymorphism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259
Some C++ Fundamentals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259
A Sample C++ Program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260
Contents xv
A Closer Look at the I/O Operators . . . . . . . . . . . . . . . . . . . 263
Declaring Local Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264No Default to int . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265The bool Data Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266
Old-Style vs. Modern C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
The New C++ Headers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269Working with an Old Compiler . . . . . . . . . . . . . . . . . . . . . . . 270
Introducing C++ Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270Function Overloading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275Operator Overloading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278Constructors and Destructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283The C++ Keywords . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287The General Form of a C++ Program . . . . . . . . . . . . . . . . . . . . . . . . . 288
12 Classes and Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289
Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290Structures and Classes Are Related . . . . . . . . . . . . . . . . . . . . . . . . . . . 293Unions and Classes Are Related . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295
Anonymous Unions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296
Friend Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297Friend Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302Inline Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303
Defining Inline Functions Within a Class . . . . . . . . . . . . . . 306
Parameterized Constructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307
Constructors with One Parameter: A Special Case . . . . . . . 309
Static Class Members . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310
Static Data Members . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310Static Member Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315
When Constructors and Destructors Are Executed . . . . . . . . . . . . . 317The Scope Resolution Operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319Nested Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319Local Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 320Passing Objects to Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 320Returning Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323Object Assignment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324
13 Arrays, Pointers, References, and the Dynamic
Allocation Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . 325
Arrays of Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326
Creating Initialized vs. Uninitialized Arrays . . . . . . . . . . . 328
Pointers to Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329Type Checking C++ Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332
xvi C++: The Complete Reference
The this Pointer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332
Pointers to Derived Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334Pointers to Class Members . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 339
Reference Parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 339Passing References to Objects . . . . . . . . . . . . . . . . . . . . . . . . 343Returning References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344Independent References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345References to Derived Types . . . . . . . . . . . . . . . . . . . . . . . . . 346Restrictions to References . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347
A Matter of Style . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347C++'s Dynamic Allocation Operators . . . . . . . . . . . . . . . . . . . . . . . . . 347
Initializing Allocated Memory . . . . . . . . . . . . . . . . . . . . . . . 349Allocating Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350Allocating Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351The nothrow Alternative . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356The Placement Form of new . . . . . . . . . . . . . . . . . . . . . . . . . 357
14 Function Overloading, Copy Constructors,
and Default Arguments . . . . . . . . . . . . . . . . . . . . . . . . . 359
Function Overloading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360Overloading Constructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362
Overloading a Constructor to Gain Flexibility . . . . . . . . . . 362Allowing Both Initialized and Uninitialized Objects . . . . . 364
Copy Constructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 366Finding the Address of an Overloaded Function . . . . . . . . . . . . . . . 370The overload Anachronism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371Default Function Arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371
Default Arguments vs. Overloading . . . . . . . . . . . . . . . . . . 376Using Default Arguments Correctly . . . . . . . . . . . . . . . . . . . 377
Function Overloading and Ambiguity . . . . . . . . . . . . . . . . . . . . . . . . 378
15 Operator Overloading . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383
Creating a Member Operator Function . . . . . . . . . . . . . . . . . . . . . . . 384
Creating Prefix and Postfix Forms
of the Increment and Decrement Operators . . . . . . . . . . 389
Overloading the Shorthand Operators . . . . . . . . . . . . . . . . . 390Operator Overloading Restrictions . . . . . . . . . . . . . . . . . . . . 390
Operator Overloading Using a Friend Function . . . . . . . . . . . . . . . . 391
Using a Friend to Overload ++ or – – . . . . . . . . . . . . . . . . . . 393Friend Operator Functions Add Flexibility . . . . . . . . . . . . . 396
Overloading new and delete . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 398
Overloading new and delete for Arrays . . . . . . . . . . . . . . . 403Overloading the nothrow Version of new and delete . . . . 406
Overloading Some Special Operators . . . . . . . . . . . . . . . . . . . . . . . . . 407
Overloading [ ] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407
Overloading ( ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 411Overloading –> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413
Overloading the Comma Operator . . . . . . . . . . . . . . . . . . . . . . . . . . . 414
16 Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417
Base-Class Access Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418Inheritance and protected Members . . . . . . . . . . . . . . . . . . . . . . . . . . 420
Protected Base-Class Inheritance . . . . . . . . . . . . . . . . . . . . . . 424
Inheriting Multiple Base Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 425Constructors, Destructors, and Inheritance . . . . . . . . . . . . . . . . . . . . 426
When Constructors and Destructors Are Executed . . . . . . 426Passing Parameters to Base-Class Constructors . . . . . . . . . 430
Granting Access . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 434Virtual Base Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 437
17 Virtual Functions and Polymorphism . . . . . . . . . . . . . . . 443
Virtual Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444
Calling a Virtual Function Through a Base
Class Reference . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 447
The Virtual Attribute Is Inherited . . . . . . . . . . . . . . . . . . . . . . . . . . . . 448Virtual Functions Are Hierarchical . . . . . . . . . . . . . . . . . . . . . . . . . . . 450Pure Virtual Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 453
Abstract Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455
Using Virtual Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455Early vs. Late Binding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 458
18 Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 459
Generic Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 460
A Function with Two Generic Types . . . . . . . . . . . . . . . . . . . 463Explicitly Overloading a Generic Function . . . . . . . . . . . . . 463Overloading a Function Template . . . . . . . . . . . . . . . . . . . . . 466Using Standard Parameters with Template Functions . . . . 466Generic Function Restrictions . . . . . . . . . . . . . . . . . . . . . . . . 467
Applying Generic Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 468
A Generic Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 469Compacting an Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 470
Generic Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 472
An Example with Two Generic Data Types . . . . . . . . . . . . . 476Applying Template Classes: A Generic Array Class . . . . . 477Using Non-Type Arguments with Generic Classes . . . . . . 479Using Default Arguments with Template Classes . . . . . . . 481Explicit Class Specializations . . . . . . . . . . . . . . . . . . . . . . . . . 483Contents xvii
The typename and export Keywords . . . . . . . . . . . . . . . . . . . . . . . . . 484
The Power of Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 485
19 Exception Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 487
Exception Handling Fundamentals . . . . . . . . . . . . . . . . . . . . . . . . . . 488
Catching Class Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 494Using Multiple catch Statements . . . . . . . . . . . . . . . . . . . . . . 495
Handling Derived-Class Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . 497Exception Handling Options . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 498
Catching All Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 498Restricting Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 500Rethrowing an Exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . 502
Understanding terminate( ) and unexpected( ) . . . . . . . . . . . . . . . . . 503
Setting the Terminate and Unexpected Handlers . . . . . . . . 504
The uncaught_exception( ) Function . . . . . . . . . . . . . . . . . . . . . . . . . 505The exception and bad_exception Classes . . . . . . . . . . . . . . . . . . . . . 506Applying Exception Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 506
20 The C++ I/O System Basics . . . . . . . . . . . . . . . . . . . . . . . 509
Old vs. Modern C++ I/O . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 510C++ Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 511The C++ Stream Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 511
C++'s Predefined Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . 512
Formatted I/O . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 513
Formatting Using the ios Members . . . . . . . . . . . . . . . . . . . . 513Setting the Format Flags . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 514Clearing Format Flags . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 515An Overloaded Form of setf( ) . . . . . . . . . . . . . . . . . . . . . . . . 516Examining the Formatting Flags . . . . . . . . . . . . . . . . . . . . . . 518Setting All Flags . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 519Using width( ), precision( ), and fill( ) . . . . . . . . . . . . . . . . . 520Using Manipulators to Format I/O . . . . . . . . . . . . . . . . . . . 522
Overloading << and >> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 526
Creating Your Own Inserters . . . . . . . . . . . . . . . . . . . . . . . . . 526Creating Your Own Extractors . . . . . . . . . . . . . . . . . . . . . . . . 532Creating Your Own Manipulator Functions . . . . . . . . . . . . 535
21 C++ File I/O . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 539
<fstream> and the File Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 540Opening and Closing a File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 540Reading and Writing Text Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 543Unformatted and Binary I/O . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 545
Characters vs. Bytes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 545put( ) and get( ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 546read( ) and write( ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 548xviii C++: The Complete Reference
Contents xix
More get( ) Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 551
getline( ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 551Detecting EOF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 553The ignore( ) Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 555peek( ) and putback( ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 556flush( ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 556Random Access . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 557
Obtaining the Current File Position . . . . . . . . . . . . . . . . . . . 561
I/O Status . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 561Customized I/O and Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 563
22 Run-Time Type ID and the Casting Operators . . . . . . . 567
Run-Time Type Identification (RTTI) . . . . . . . . . . . . . . . . . . . . . . . . . 568The Casting Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 578dynamic_cast . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 578
23 Namespaces, Conversion Functions,
and Other Advanced Topics . . . . . . . . . . . . . . . . . . . . . 591
Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 592
Namespace Fundamentals . . . . . . . . . . . . . . . . . . . . . . . . . . . 592using . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 596Unnamed Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 598Some Namespace Options . . . . . . . . . . . . . . . . . . . . . . . . . . . 599
The std Namespace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 601Creating Conversion Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 603const Member Functions and mutable . . . . . . . . . . . . . . . . . . . . . . . . 607Volatile Member Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 609Explicit Constructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 610The Member Initialization Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . 611Using the asm Keyword . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 616Linkage Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 617Array-Based I/O . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 618
The Array-Based Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 619Creating an Array-Based Output Stream . . . . . . . . . . . . . . . 619Using an Array as Input . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 621Input/Output Array-Based Streams . . . . . . . . . . . . . . . . . . . 623Using Dynamic Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 624Using Binary I/O with Array-Based Streams . . . . . . . . . . . 625
Summarizing the Differences Between C and C++ . . . . . . . . . . . . . 626
24 Introducing the Standard Template Library . . . . . . . . . . 629
An Overview of the STL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 630
Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 630
xx C++: The Complete Reference
Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 631
Iterators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 631Other STL Elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 632
The Container Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 633General Theory of Operation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 634Vectors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 635
Accessing a Vector Through an Iterator . . . . . . . . . . . . . . . . 639Inserting and Deleting Elements in a Vector . . . . . . . . . . . . 641Storing Class Objects in a Vector . . . . . . . . . . . . . . . . . . . . . . 643
Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 645
Understanding end( ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 649push_front( ) vs. push_back( ) . . . . . . . . . . . . . . . . . . . . . . . . 651Sort a List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 652Merging One List with Another . . . . . . . . . . . . . . . . . . . . . . 653Storing Class Objects in a List . . . . . . . . . . . . . . . . . . . . . . . . 655
Maps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 658
Storing Class Objects in a Map . . . . . . . . . . . . . . . . . . . . . . . 662
Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 664
Counting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 664Removing and Replacing Elements . . . . . . . . . . . . . . . . . . . 670Reversing a Sequence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 672Transforming a Sequence . . . . . . . . . . . . . . . . . . . . . . . . . . . . 673
Using Function Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 675
Unary and Binary Function Objects . . . . . . . . . . . . . . . . . . . 675Using the Built-in Function Objects . . . . . . . . . . . . . . . . . . . 675Creating a Function Object . . . . . . . . . . . . . . . . . . . . . . . . . . . 678Using Binders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 680
The string Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 683
Some string Member Functions . . . . . . . . . . . . . . . . . . . . . . . 687Strings Are Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 693Putting Strings into Other Containers . . . . . . . . . . . . . . . . . 694
Final Thoughts on the STL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 695
Part III
The Standard Function Library
25 The C-Based I/O Functions . . . . . . . . . . . . . . . . . . . . . . . 699
clearerr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 700fclose . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 701feof . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 701ferror . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 701fflush . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 702fgetc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 702fgetpos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 702
Contents xxi
fgets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 703
fopen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 703fprintf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 705fputc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 705fputs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 706fread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 706freopen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 706fscanf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 707fseek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 707fsetpos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 708ftell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 708fwrite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 709getc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 709getchar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 710gets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 710perror . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 710printf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 711putc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 714putchar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 714puts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 714remove . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 715rename . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 715rewind . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 715scanf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 715setbuf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 719setvbuf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 719sprintf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 720sscanf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 720tmpfile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 720tmpnam . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 721ungetc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 721vprintf, vfprintf, and vsprintf . . . . . . . . . . . . . . . . . . . . . . . . 722
26 The String and Character Functions . . . . . . . . . . . . . . . . 723
isalnum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 724isalpha . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 724iscntrl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 725isdigit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 725isgraph . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 725islower . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 725isprint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 726ispunct . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 726isspace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 726isupper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 727
xxii C++: The Complete Reference
isxdigit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 727
memchr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 727memcmp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 727memcpy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 728memmove . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 728memset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 729strcat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 729strchr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 729strcmp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 730strcoll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 730strcpy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 731strcspn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 731strerror . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 731strlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 731strncat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 732strncmp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 732strncpy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 733strpbrk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 733strrchr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 733strspn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 734strstr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 734strtok . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 734strxfrm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 735tolower . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 735toupper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 735
27 The Mathematical Functions . . . . . . . . . . . . . . . . . . . . . . 737
acos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 738asin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 738atan . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 739atan2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 739ceil . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 739cos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 740cosh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 740exp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 740fabs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 741floor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 741fmod . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 741frexp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 741ldexp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 742log . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 742log10 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 742modf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 743pow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 743sin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 743
Contents xxiii
sinh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 744
sqrt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 744tan . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 744tanh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 745
28 Time, Date, and Localization Functions . . . . . . . . . . . . . 747
asctime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 748clock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 749ctime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 749difftime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 750gmtime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 750localeconv . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 750localtime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 752mktime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 752setlocale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 752strftime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 753time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 754
29 The Dynamic Allocation Functions . . . . . . . . . . . . . . . . . 757
calloc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 758free . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 758malloc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 759realloc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 759
30 Utility Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 761
abort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 762abs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 762assert . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 763atexit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 763atof . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 763atoi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 764atol . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 764bsearch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 764div . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 765exit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 766getenv . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 766labs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 766ldiv . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 767longjmp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 767mblen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 767mbstowcs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 768mbtowc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 768qsort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 768raise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 769
xxiv C++: The Complete Reference
rand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 770
setjmp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 770signal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 770srand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 771strtod . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 771strtol . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 772strtoul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 772system . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 773va_arg, va_start, and va_end . . . . . . . . . . . . . . . . . . . . . . . . . 773wcstombs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 774wctomb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 774
31 The Wide-Character Functions . . . . . . . . . . . . . . . . . . . . . 775
The Wide-Character Classification Functions . . . . . . . . . . . . . . . . . . 776The Wide-Character I/O Functions . . . . . . . . . . . . . . . . . . . . . . . . . . 779The Wide-Character String Functions . . . . . . . . . . . . . . . . . . . . . . . . . 779Wide-Character String Conversion Functions . . . . . . . . . . . . . . . . . . 779Wide-Character Array Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 782Multibyte/Wide-Character Conversion Functions . . . . . . . . . . . . . . 783
Part IV
The Standard C++ Class Library
32 The Standard C++ I/O Classes . . . . . . . . . . . . . . . . . . . . 787
The I/O Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 788The I/O Headers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 790The Format Flags and I/O Manipulators . . . . . . . . . . . . . . . . . . . . . . 791Several Data Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 793
The streamsize and streamoff Types . . . . . . . . . . . . . . . . . . . 793The streampos and wstreampos Types . . . . . . . . . . . . . . . . 793The pos_type and off_type Types . . . . . . . . . . . . . . . . . . . . . 793The openmode Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 793The iostate Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 794The seekdir Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 794The failure Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 794
Overload << and >> Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 794The General-Purpose I/O Functions . . . . . . . . . . . . . . . . . . . . . . . . . 795
bad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 795clear . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 795eof . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 795exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 796fail . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 796fill . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 796flags . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 797
Contents xxv
flush . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 797
fstream, ifstream, and ofstream . . . . . . . . . . . . . . . . . . . . . . . 797gcount . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 798get . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 798getline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 799good . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 800ignore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 800open . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 800peek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 801precision . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 802put . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 802putback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 802rdstate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 802read . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 803readsome . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 803seekg and seekp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 804setf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 805setstate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 805str . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 806stringstream, istringstream, ostringstream . . . . . . . . . . . . . 806sync_with_stdio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 807tellg and tellp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 808unsetf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 808width . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 808write . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 809
33 The STL Container Classes . . . . . . . . . . . . . . . . . . . . . . . . 811
The Container Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 812
bitset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 814deque . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 816list . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 819map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 822multimap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 824multiset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 827queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 829priority_queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 830set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 831stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 833vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 834
34 The STL Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 839
adjacent_find . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 840binary_search . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 840copy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 841
xxvi C++: The Complete Reference
copy_backward . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 841
count . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 841count_if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 842equal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 842equal_range . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 842fill and fill_n . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 843find . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 843find_end . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 843find_first_of . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 843find_if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 844for_each . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 844generate and generate_n . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 844includes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 845inplace_merge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 845iter_swap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 845lexicographical_compare . . . . . . . . . . . . . . . . . . . . . . . . . . . . 846lower_bound . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 846make_heap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 846max . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 847max_element . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 847merge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 847min . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 848min_element . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 848mismatch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 848next_permutation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 849nth_element . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 849partial_sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 849partial_sort_copy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 850partition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 850pop_heap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 850prev_permutation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 851push_heap. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .851random_shuffle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 851remove, remove_if, remove_copy, and remove_copy_if . 852replace, replace_copy, replace_if, and replace_copy_if . . . 852reverse and reverse_copy . . . . . . . . . . . . . . . . . . . . . . . . . . . . 853rotate and rotate_copy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 853search . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 854search_n . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 854set_difference . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 854set_intersection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 855set_symmetric_difference . . . . . . . . . . . . . . . . . . . . . . . . . . . . 855set_union . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 856sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 856sort_heap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 856
Contents xxvii
stable_partition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 857
stable_sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 857swap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 857swap_ranges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 858transform . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 858unique and unique_copy . . . . . . . . . . . . . . . . . . . . . . . . . . . . 858upper_bound . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 859
35 STL Iterators, Allocators, and Function Objects . . . . . . 861
Iterators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 862
The Basic Iterator Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 862The Low-Level Iterator Classes . . . . . . . . . . . . . . . . . . . . . . . 863The Predefined Iterators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 864Two Iterator Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 872
Function Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 872
Function Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 873Binders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 874Negators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 875Adaptors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 876
Allocators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 879
36 The String Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 881
The basic_string Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 882The char_traits Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 894
37 The Numeric Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 897
The complex Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 898The valarray Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 902
The slice and gslice Classes . . . . . . . . . . . . . . . . . . . . . . . . . . 917The Helper Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 920
The Numeric Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 920
accumulate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 920adjacent_difference . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 921inner_product . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 922partial_sum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 923
38 Exception Handling and Miscellaneous Classes . . . . . . 925
Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 926
<exception> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 926<stdexcept> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 927
auto_ptr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 928The pair Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 930Localization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 931Other Classes of Interest . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 931
Part V
Applying C++
39 Integrating New Classes: A Custom String Class . . . . . 935
The StrType Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 936
The Constructors and Destructors . . . . . . . . . . . . . . . . . . . . . . . . . . . 938I/O on Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 939The Assignment Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 941Concatenation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 942Substring Subtraction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 945The Relational Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 947Miscellaneous String Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 948The Entire StrType Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 949Using the StrType Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 958Creating and Integrating New Types in General . . . . . . . . . . . . . . . 961A Challenge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 961
40 Parsing Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 963
Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 964Parsing Expressions: The Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . 965Parsing an Expression . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 966The Parser Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 968Dissecting an Expression . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 969A Simple Expression Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 971
Understanding the Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . 977
Adding Variables to the Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 978Syntax Checking in a Recursive-Descent Parser . . . . . . . . . . . . . . . . 988Building a Generic Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 989Some Things to Try . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 997
A The .NET Managed Extensions to C++ . . . . . . . . . . . . . . 999
The .NET Keyword Extensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1000Preprocessor Extensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1002The attribute Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1003Compiling Managed C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1003
B C++ and the Robotics Age . . . . . . . . . . . . . . . . . . . . . . . . 1005
Index. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1009xxviii C++: The Complete Reference
If there is one language that defines modern programming, it is C++. Its syntax,
style, and philosophy have set the standard by which all other languages arejudged. Furthermore, C++ is the universal language of programming. When an
algorithm or technique is described, it is usually done so using the C++ syntax. Thelong-term success of C++ has also left a lasting impression on computer languagedevelopment. For example, both Java and C# are descended from C++. Frankly, to bea professional programmer implies proficiency in C++. It is the one language that noprogrammer can afford to ignore.
This is the fourth edition of C++: The Complete Reference. It fully describes and
demonstrates the keywords, syntax, functions, classes, and features that define the C++language. More specifically, this book fully describes Standard C++. This is the versionof C++ defined by the ANSI/ISO Standard for C++ and it is the version of C++ that issupported by all major compilers, including Microsoft’s Visual C++ and Borland’s C++Builder. Thus, the information in this book is applicable to all modern programmingenvironments.
In the time that has passed since the previous edition of this book, there have
been no changes to the C++ language. There have, however, been big changes to thecomputing environment. For example, a new standard for C, called C99, was created,Java became the dominant language for Web programming, the .NET Framework was
xxix
Copyright © 2003 by The McGraw-Hill Companies. Click here for terms of use.
released, and C# was invented. Through all the changes of the past few years, one thing
has remained constant: the staying power of C++. C++ has been, is, and will remain thepreeminent language for the development of high-performance software well into theforeseeable future.
What’s New in the Fourth Edition
The overall structure and organization of the fourth edition is similar to the third edition.Thus, if you have been using the third edition, you will feel right at home with the fourthedition. Most of the changes to the fourth edition involve updating and expanding thecoverage throughout. In some cases, additional details were added. In other cases, thepresentation of a topic was improved. In still other situations, descriptions weremodernized to reflect the current programming environment. Several new sections were also
added. In Part One, the relationship of C++ to the new C standard, called C99, is notedwhere appropriate.
Two appendices were also added. The first described the extended keywords
defined by Microsoft that are used for creating managed code for the .NET Framework.The second shows off an area of personal interest: robotics. Robotics has long been ahobby of mine and I thought that many readers would find my experimental robot tobe of interest. Most of the software that drives it is, of course, written in C++!
Finally, all code examples were retested against the current crop of compilers,
including Microsoft’s Visual Studio .NET and Borland’s C++ Builder.
What’s Inside
This books covers in detail all aspects of the C++ language, including its foundation, C.The book is divided into these five parts:
șThe C Subset—The foundation of C++
șThe C++ language
șThe Standard Function Library
șThe Standard Class Library
șSample C++ applications
Part One provides a comprehensive discussion of the C subset of C++. As most
readers will know, C is the foundation upon which C++ was built. It is the C subsetthat defines the bedrock features of C++, including such things as forloops and if
statements. It also defines the essential nature of C++’s block structure, pointers, andfunctions. Since many readers are already familiar with and proficient in C, discussingthe C subset separately in Part One prevents the knowledgeable C programmer fromhaving to “wade through” reams of information he or she already knows. Instead, thexxx C++: The Complete Reference
experienced C programmer can simply turn to the sections of this book that cover the
C++-specific features.
Part Two discusses in detail the features that move beyond the C foundation and
define the C++ language These include its object-oriented features such as classes,constructors, destructors, RTTI, and templates. Thus, Part Two covers those constructsthat “make C++, C++.”
Part Three describes the standard function library and Part Four examines the
standard class library, including the STL (Standard Template Library). Part Fiveshows two practical examples of applying C++ and object-oriented programming.
A Book for All Programmers
This C++ reference is designed for all C++ programmers, regardless of their experiencelevel. It does assume, however, a reader able to create at least a simple program. If youare just learning C++, this book will make an excellent companion to any C++ tutorialand serve as a source of answers to your specific questions. Experienced C++ pros willfind the in-depth coverage of C++’s more advanced features especially useful.
If You’re Using Windows
If your computer uses Windows, then you have chosen the right language. C++ iscompletely at home with Windows programming. However, none of the programs inthis book are Windows programs. Instead, they are console-based programs. The reason forthis is easy to understand: Windows programs are, by their nature, large and complex. Theoverhead required to create even a minimal Windows skeletal program is 50 to 70 linesof code. To write Windows programs that demonstrate the features of C++ wouldrequire hundreds of lines of code each. Put simply, Windows is not an appropriateenvironment in which to discuss the features of a programming language. However,you can still use a Windows-based compiler to compile the programs in this bookbecause the compiler will automatically create a console session in which to executeyour program.
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 www.osborne.com. Downloading this code prevents you from
having to type in the examples.xxxi
For Further Study
C++: The Complete Reference is your gateway to the Herb Schildt series of
programming books. Here are some others that you will find of interest.
To learn more about C++, try
C++: A Beginner’s Guide
C++ from the Ground UpTeach Yourself C++STL Programming from the Ground UpC++ Programmer’s Reference
To learn about Java programming, we recommend the following:
Java 2: A Beginner’s GuideJava 2: The Complete ReferenceJava 2 Programmer’s Reference
To learn about C#, Herb offers these books:
C#: A Beginner’s GuideC#: The Complete Reference
To learn about Windows programming we suggest the following Schildt books:
Windows 98 Programming from the Ground UpWindows 2000 Programming from the Ground UpMFC Programming from the Ground UpThe Windows Programming Annotated Archives
If you want to learn about the C language, which is the foundation of all modern
programming, then the following titles will be of interest.
C: The Complete ReferenceTeach Yourself C
When you need solid answers, fast, turn to Herbert Schildt,
the recognized authority on programming.xxxii C++: The Complete Reference
Part I
The Foundation of C++:
The C Subset
This book divides its description of the C++ language into two parts.
Part One discusses the C-like features of C++. This is commonlyreferred to as the C subset of C++. Part Two describes those features
specific to C++. Together, these parts describe the entire C++ language.
Copyright © 2003 by The McGraw-Hill Companies. Click here for terms of use.
As you may know, C++ was built upon the foundation of C. In fact, C++ includes
the entire C language, and (with minor exceptions) all C programs are also C++ programs.
When C++ was invented, the C language was used as the starting point. To C were addedseveral new features and extensions designed to support object oriented programming(OOP). However, the C-like aspects of C++ were never abandoned, and the 1989 ANSI/ISO C Standard is a base document for the International Standard for C++. Thus, an
understanding of C++ implies an understanding of 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:
șThe dividing line between C and C++ is clearly delineated.
șReaders already familiar with C can easily find the C++-specific information.
șIt provides a convenient place in which to discuss those features of C++ that
relate mostly to the C subset.
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. When working on C code, you need to know where Cends and C++ begins. Many C++ programmers will, from time to time, be required towrite code that is limited to the “C subset.” This will be especially true for embeddedsystems programming and the maintenance of existing applications. Knowing thedifference between C and C++ is simply part of being a top-notch professional C++programmer.
A clear understanding of C is also valuable when converting C code into C++.
To do this in a professional manner, a solid knowledge of C is required. For example,without a thorough understanding of the C I/O system, it is not possible to convertan I/O-intensive C program into C++ in an efficient manner.
Many readers already know C. Covering the C-like features of C++ in their own
section makes it easier for the experienced C programmer to find information aboutC++ quickly and easily, without having to “wade through” reams of information thathe or she already knows. Of course, throughout Part One, any minor differences between Cand C++ are noted. Also, separating the C foundation from the more advanced, object-oriented features of C++ makes it possible to tightly focus on those advanced featuresbecause all of the basics have already been discussed.
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. For example, theC I/O system is still available to the C++ programmer even though C++ defines itsown, object-oriented version. The preprocessor is another example. The preprocessoris very important to C, but less so to C++. Discussing several of the “C-only” featuresin Part I prevents them from cluttering up the remainder of the book.
Remember: The C subset described in Part One constitutes the core of C++ and the
foundation upon which C++’s object-oriented features are built. All the featuresdescribed here are part of C++ and available for your use.
Part I of this book is excerpted from my book C: The Complete Reference (McGraw-Hill/Osborne). If you are particularly interested in C, you will find this book helpful.
Chapter 1
An Overview of C
3
Copyright © 2003 by The McGraw-Hill Companies. Click here for terms of use.
4 C++: The Complete Reference
To understand C++ is to understand the forces that drove its creation, the ideas
that shaped it, and the legacy it inherits. Thus, the story of C++ begins with C.
This chapter presents an overview of the C programming language, its origins, its
uses, and its underlying philosophy. Because C++ is built upon C, this chapter providesan important historical perspective on the roots of C++. Much of what “makes C++,C++” had its genesis in the C language.
The Origins and History of C
C was invented and first implemented by Dennis Ritchie on a DEC PDP-11 that usedthe UNIX operating system. C is the result of a development process that started withan older language called BCPL. BCPL was developed by Martin Richards, and itinfluenced a language called B, which was invented by Ken Thompson. B led to thedevelopment of C in the 1970s.
For many years, the de facto standard for C was the version supplied with the UNIX
operating system. It was first described in The C Programming Language by Brian Kernighan
and Dennis Ritchie (Englewood Cliffs, N.J.: Prentice-Hall, 1978). In the summer of 1983a committee was established to create an ANSI (American National Standards Institute)standard that would define the C language. The standardization process took six years(much longer than anyone reasonably expected at the time).
The ANSI C standard was finally adopted in December of 1989, with the first copies
becoming available in early 1990. The standard was also adopted by ISO (InternationalStandards Organization) and the resulting standard was typically referred to as ANSI/ISO Standard C. In 1995, Amendment 1 to the C standard was adopted, which, amongother things, added several new library functions. The 1989 standard for C, along withAmendment 1, became the base document for Standard C++, defining the C subset of
C++. The version of C defined by the 1989 standard is commonly referred to as C89.
After 1989, C++ took center stage, and during the 1990s the development of a
standard for C++ consumed most programmers’ attention, with a standard for C++being adopted by the end of 1998. However, work on C continued along quietly. Theend result was the 1999 standard for C, usually referred to as C99. In general, C99 retainednearly all of the features of C89 and did not alter the main aspects of the language. Thus,the C language described by C99 is essentially the same as the one described by C89.The C99 standardization committee focused on two main areas: the addition of severalnumeric libraries and the development of some special-use, but highly innovative, newfeatures, such as variable- length arrays and the restrict pointer qualifier. In a few cases,
features originally from C++, such as single-line comments, were also incorporated intoC99. Because the standard for C++ was finalized before C99 was created, none of theC99 innovations are found in Standard C++.
C89 vs. C99
Although the innovations in C99 are important from a computer science point of view,they are currently of little practical consequence because, at the time of this writing, nowidely-used compiler implements C99. Rather, it is C89 that defines the version of C
THE FOUNDATION OF C++:
THE C SUBSETChapter 1: An Overview of C 5
that most programmers think of as “C” and that all mainstream compilers recognize.
Furthermore, it is C89 that forms the C subset of C++. Although several of the newfeatures added by C99 will eventually find their way into the next standard for C++,currently these new features are incompatible with C++.
Because C89 is the standard that forms the C subset of C++, and because it is the
version of C that the vast majority of C programmers currently know, it is the versionof C discussed in Part I. Thus, when the term Cis used, take it to mean the C defined
by C89. However, important differences between C89 and C99 that relate specifically toC++ are noted, such as when C99 adds a feature that improves compatibility with C++.
C Is a Middle-Level Language
C is often called a middle-level computer language. This does not mean that C is less
powerful, harder to use, or less developed than a high-level language such as BASIC orPascal; nor does it imply that C has the cumbersome nature of assembly language (andits associated troubles). Rather, C is thought of as a middle-level language because itcombines the best elements of high-level languages with the control and flexibility ofassembly language. Table 1-1 shows how C fits into the spectrum of computer languages.
As a middle-level language, C allows the manipulation of bits, bytes, and addresses—
the basic elements with which the computer functions. Despite this fact, C code is also
Highest level Ada
Modula-2PascalCOBOLFORTRANBASIC
Middle level JavaC#C++CForth
Macro-assembler
Lowest level Assembler
Table 1-1. C’s Place in the World of Languages
6 C++: The Complete Reference
portable. Portability means that it is easy to adapt software written for one type of
computer or operating system to another. For example, if you can easily convert a
program written for UNIX so that it runs under Windows, that program is portable.
All high-level programming languages support the concept of data types. A data
type defines a set of values that a variable can store along with a set of operations that
can be performed on that variable. Common data types are integer, character, and real.Although C has five basic built-in data types, it is not a strongly typed language, as arePascal and Ada. C permits almost all type conversions. For example, you may freelyintermix character and integer types in an expression.
Unlike a high-level language, C performs almost no run-time error checking. For
example, no check is performed to ensure that array boundaries are not overrun. Thesetypes of checks are the responsibility of the programmer.
In the same vein, C does not demand strict type compatibility between a parameter
and an argument. 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 orless) exactly the same type as the parameter that will receive the argument. However,such is not the case for C. Instead, C allows an argument to be of any type so long as itcan be reasonably converted into the type of the parameter. Further, C provides all ofthe automatic conversions to accomplish this.
C is special in that it allows the direct manipulation of bits, bytes, words, and pointers.
This makes it well suited for system-level programming, where these operations arecommon.
Another important aspect of C is that it has only a few keywords, which are the
commands that make up the C language. For example, C89 defines only 32 keywords,with C99 adding just another 5. Some computer languages have several times more.For comparison, 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 block-structured
applied to a computer language. Although the term block-structured language does
not strictly apply to C, C is commonly referred to simply as a structured language. It has
many similarities to other structured languages, such as ALGOL, Pascal, and Modula-2.
The reason that C (and C++) is not, technically, a block-structured language is thatblock-structured languages permit procedures or functions to be declared inside otherprocedures or functions. However, since C does not allow the creation of functionswithin functions, it cannot formally be called block-structured.
The distinguishing feature of a structured language is compartmentalization of code
and data. This is the ability of a language to section off and hide from the rest of theprogram all information and instructions necessary to perform a specific task. Oneway that you achieve compartmentalization is by using subroutines that employ local(temporary) variables. By using local variables, you can write subroutines so thatthe events that occur within them cause no side effects in other parts of the program.
Chapter 1: An Overview of C 7THE FOUNDATION OF C++:
THE C SUBSETThis capability makes it easier for programs to share sections of code. If you develop
compartmentalized functions, you only need to know what a function does, not howit does it. Remember, excessive use of global variables (variables known throughoutthe entire program) may allow bugs to creep into a program by allowing unwantedside effects. (Anyone who has programmed in standard BASIC is well aware of thisproblem.)
The concept of compartmentalization is greatly expanded by C++. Specifically, in C++,one part of your program may tightly control which other parts of your program areallowed access.
A structured language allows you a variety of programming possibilities. It directly
supports several loop constructs, such as while, do-while, and for. In a structured
language, the use of goto is either prohibited or discouraged and is not the common
form of program control (as is the case in standard BASIC and traditional FORTRAN,for example). A structured language allows you to place statements anywhere on a lineand does not require a strict field concept (as some older FORTRANs do).
Here are some examples of structured and nonstructured languages:
Nonstructured Structured
FORTRAN Pascal
BASIC Ada
COBOL Java
C#C++CModula-2
Structured languages tend to be modern. In fact, a mark of an old computer
language is that it is nonstructured. Today, few programmers would consider using
a nonstructured language for serious, new programs.
New versions of many older languages have attempted to add structured elements.BASIC is an example—in particular Visual Basic by Microsoft. However, theshortcomings of these languages can never be fully mitigated because they werenot designed with structured features from the beginning.
C’s main structural component is the function—C’s stand-alone subroutine. In C,
functions are the building blocks in which all program activity occurs. They allow you todefine and code separately the separate tasks in a program, thus allowing your programsto be modular. After you have created a function, you can rely on it to work properlyin various situations without creating side effects in other parts of the program. Being
8 C++: The Complete Reference
able to create stand-alone functions is extremely critical in larger projects where one
programmer’s code must not accidentally affect another’s code.
Another way to structure and compartmentalize code in C is through the use of
code blocks. A code block is a logically connected group of program statements that is
treated as a unit. In C, you create a code block by placing a sequence of statementsbetween opening and closing curly braces. In this example,
if (x < 10) {
printf("Too low, try again.\n");
scanf("%d", &x);
}
the two statements after the ifand between the curly braces are both executed if x
is less than 10. These two statements together with the braces represent a code block.
They are a logical unit: one of the statements cannot execute without the other executingalso. Code blocks allow many algorithms to be implemented with clarity, elegance, andefficiency. Moreover, they help the programmer better conceptualize the true nature ofthe algorithm being implemented.
C Is a Programmer’s Language
Surprisingly, not all computer programming languages are for programmers. Considerthe classic examples of nonprogrammer languages, COBOL and BASIC. COBOL wasdesigned not to better the programmer’s lot, not to improve the reliability of the codeproduced, and not even to improve the speed with which code can be written. Rather,COBOL was designed, in part, to enable nonprogrammers to read and presumably(however unlikely) to understand the program. BASIC was created essentially to allownonprogrammers to program a computer to solve relatively simple problems.
In contrast, C was created, influenced, and field-tested by working programmers.
The end result is that C gives the programmer what the programmer wants: fewrestrictions, few complaints, block structures, stand-alone functions, and a compact setof keywords. By using C, you can nearly achieve the efficiency of assembly code combined with
the structure of ALGOL or Modula-2. It is no wonder that C and C++ are easily two ofthe most popular languages among topflight professional programmers.
The fact that you can often use C in place of assembly language is a major factor in
its popularity among programmers. Assembly language uses a symbolic representationof the actual binary code that the computer executes directly. Each assembly-languageoperation maps into a single task for the computer to perform. Although assemblylanguage gives programmers the potential to accomplish tasks with maximum flexibilityand efficiency, it is notoriously difficult to work with when developing and debugginga program. Furthermore, since assembly language is unstructured, the final programtends to be spaghetti code—a tangled mess of jumps, calls, and indexes. This lack ofstructure makes assembly-language programs difficult to read, enhance, and maintain.
Chapter 1: An Overview of C 9THE FOUNDATION OF C++:
THE C SUBSETPerhaps more important, assembly-language routines are not portable between
machines with different central processing units (CPUs).
Initially, C was used for systems programming. A systems program forms a portion
of the operating system of the computer or its support utilities. For example, the following areusually called systems programs:
șOperating systems
șInterpreters
șEditors
șCompilers
șFile utilities
șPerformance enhancers
șReal-time executives
șDevice drivers
As C grew in popularity, many programmers began to use it to program all tasks
because of its portability and efficiency—and because they liked it! At the time of itscreation, C was a much longed-for, dramatic improvement in programming languages.Of course, C++ has carried on this tradition.
With the advent of C++, some thought that C as a distinct language would die out.
Such has not been the case. First, not all programs require the application of the object-oriented programming features provided by C++. For example, applications such asembedded systems are still typically programmed in C. Second, much of the worldstill runs on C code, and those programs will continue to be enhanced and maintained.While C’s greatest legacy is as the foundation for C++, C will continue to be a vibrant,widely used language for many years to come.
The Form of a C Program
Table 1-2 lists the 32 keywords that, combined with the formal C syntax, form C89, theC subset of C++. All are, of course, also keywords in C++.
In addition, many compilers have added several keywords that better exploit their
operating environment. For example, several compilers include keywords to managethe memory organization of the 8086 family of processors, to support inter-languageprogramming, and to access interrupts. Here is a list of some commonly used extendedkeywords:
asm _cs _ds _es
_ss cdecl far hugeinterrupt near pascal
10 C++: The Complete Reference
Your compiler may also support other extensions that help it take better advantage of
its specific environment.
Notice that all of the keywords are lowercase. C/C++ is case-sensitive. Thus, in
a C/C++ program, uppercase and lowercase are different. This means that else is a
keyword, while ELSE is not. You may not use a keyword for any other purpose in
a program— that is, you may not use it as a variable or function name.
All C programs consist of one or more functions. The only function that must be
present is called main( ), which is the first function called when program execution
begins. In well-written C code, main( ) contains what is, in essence, an outline of what
the program does. The outline is composed of function calls. Although main( ) is not
a keyword, treat it as if it were. For example, don’t try to use main as the name of a
variable because you will probably confuse the compiler.
The general form of a C program is illustrated in Figure 1-1, where f1( ) through
fN( ) represent user-defined functions.
The Library and Linking
Technically speaking, you can create a useful, functional C or C++ program that consists s olely
of the statements that you actually created. However, this is quite rare because neitherC nor C++ provides any keywords that perform such things as I/O operations,high-level mathematical computations, or character handling. As a result, most programsinclude calls to various functions contained in the standard library .
All C++ compilers come with a standard library of functions that perform most
commonly needed tasks. Standard C++ specifies a minimal set of functions that will besupported by all compilers. However, your compiler will probably contain many otherfunctions. For example, the standard library does not define any graphics functions,but your compiler will probably include some.auto double int struct
break else long switch
case enum register typedef
char extern return union
const float short unsigned
continue for signed void
default goto sizeof volatile
do if static while
Table 1-2. The 32 Keywords Defined by the C Subset of C++
Chapter 1: An Overview of C 11THE FOUNDATION OF C++:
THE C SUBSET
The C++ standard library can be divided into two halves: the standard function
library and the class library. The standard function library is inherited from the C
language. C++ supports the entire function library defined by C89. Thus, all of thestandard C functions are available for use in C++ programs that you write.
In addition to the standard function library, C++ also defines its own class library.
The class library provides object-oriented routines that your programs may use. It alsodefines the Standard Template Library (STL), which offers off-the-shelf solutions toa variety of programming problems. Both the class library and the STL are discussedlater in this book. In Part One, only the standard function library is used, since it is theonly one that is also defined by C.global declarations
return-type main (parameter list){
statement sequence
}return-type f1 (parameter list){
statement sequence
}return-type f2 (parameter list){
statement sequence
}…return-type fN(parameter list){
statement sequence
}
Figure 1-1. The general form of a C program
12 C++: The Complete Reference
The standard function library contains most of the general-purpose functions that
you will use. When you call a library function, the compiler “remembers” its name.
Later, the linker combines the code you wrote with the object code for the libraryfunction, which is found in the standard library. This process is called linking. Some
compilers have their own linker, while others use the standard linker supplied byyour operating system.
The functions in the library are in relocatable format. This means that the memory
addresses for the various machine-code instructions have not been absolutely defined—only offset information has been kept. When your program links with the functions inthe standard library, these memory offsets are used to create the actual addresses used.Several technical manuals and books explain this process in more detail. However, youdo not need any further explanation of the actual relocation process to program in C++.
Many of the functions that you will need as you write programs are in the standard
library. They act as building blocks that you combine. If you write a function that youwill use again and again, you can place it into a library, too.
Separate Compilation
Most short programs are completely contained within one source file. However, asa program’s length grows, so does its compile time (and long compile times make forshort tempers). Hence, C/C++ allows a program to be contained in multiple files andlets you compile each file separately. Once you have compiled all files, they are linked,along with any library routines, to form the complete object code. The advantage ofseparate compilation is that if you change the code of one file, you do not need to recompile theentire program. On all but the most simple projects, this saves a substantial amount oftime. The user documentation to your C/C++ compiler will contain instructions forcompiling multiple-file programs.
Understanding the .C and .CPP File Extensions
The programs in Part One of this book are, of course, valid C++ programs and can becompiled using any modern C++ compiler. They are also valid C programs and canbe compiled using a C compiler. Thus, if you are called upon to write C programs,the programs shown in Part One qualify as examples. Traditionally, C programs use thefile extension .Cand C++ programs use the extension .CPP. A C++ compiler uses
the file extension to determine what type of program it is compiling. This is importantbecause the compiler assumes that any program using the .Cextension is a C program
and that any file using .CPP is a C++ program. Unless explicitly noted otherwise, you
may use either extension for the programs in Part One. However, the programs in therest of this book will require .CPP .
One last point: Although C is a subset of C++, there are a few minor differences
between the two languages and in a few cases, you may need to compile a C programas a C program (using the .Cextension). Any instances of this will be noted.
Chapter 2
Expressions
13
Copyright © 2003 by The McGraw-Hill Companies. Click here for terms of use.
This chapter examines the most fundamental element of the C (as well as the C++)
language: the expression. As you will see, expressions in C/C++ are substantiallymore general and more powerful than in most other computer languages.
Expressions are formed from these atomic elements: data and operators. Data may berepresented either by variables or by constants. Like most other computer languages,C/C++ supports a number of different types of data. It also provides a wide varietyof operators.
The Five Basic Data Types
There are five atomic data types in the C subset: character, integer, floating-point,double floating-point, and valueless (char ,int,float, double, and void, respectively).
As you will see, all other data types in C are based upon one of these types. The sizeand range of these data types may vary between processor types and compilers. However,in all cases a character is 1 byte. The size of an integer is usually the same as the wordlength of the execution environment of the program. For most 16-bit environments, suchas DOS or Windows 3.1, an integer is 16 bits. For most 32-bit environments, such asWindows 2000, an integer is 32 bits. However, you cannot make assumptions aboutthe size of an integer if you want your programs to be portable to the widest range ofenvironments. It is important to understand that both C and C++ only stipulate theminimal range of each data type, not its size in bytes.
To the five basic data types defined by C, C++ adds two more: bool andwchar_t. These
are discussed in Part Two.
The exact format of floating-point values will depend upon how they are implemented.
Integers will generally correspond to the natural size of a word on the host computer.Values of type char are generally used to hold values defined by the ASCII character
set. Values outside that range may be handled differently by different compilers.
The range of float and double will depend upon the method used to represent
the floating-point numbers. Whatever the method, the range is quite large. StandardC specifies that the minimum range for a floating-point value is 1E −37 to 1E+37. The
minimum number of digits of precision for each floating-point type is shown inTable 2-1.
Standard C++ does not specify a minimum size or range for the basic types. Instead, itsimply states that they must meet certain requirements. For example, Standard C++states that an intwill “have the natural size suggested by the architecture of the
execution environment." In all cases, this will meet or exceed the minimum rangesspecified by Standard C. Each C++ compiler specifies the size and range of the basictypes in the header <climits>.14 C++: The Complete Reference
The type void either explicitly declares a function as returning no value or creates
generic pointers. Both of these uses are discussed in subsequent chapters.
Modifying the Basic Types
Except for type void, the basic data types may have various modifiers preceding them.
You use a modifier to alter the meaning of the base type to fit various situations more
precisely. The list of modifiers is shown here:
signed
unsignedlongshortChapter 2: Expressions 15THE FOUNDATION OF C++:
THE C SUBSET Type Typical Size in Bits Minimal Range
char 8 −127 to 127
unsigned char 8 0 to 255
signed char 8 −127 to 127
int 16 or 32 −32,767 to 32,767
unsigned int 16 or 32 0 to 65,535
signed int 16 or 32 same as int
short int 16 −32,767 to 32,767
unsigned short int 16 0 to 65,535
signed short int 16 same as short int
long int 32 −2,147,483,647 to2,147,483,647
signed long int 32 same as long int
unsigned long int 32 0 to 4,294,967,295
float 32 Six digits of precision
double 64 Ten digits of precision
long double 80 Ten digits of precision
Table 2-1. All Data Types Defined by the ANSI/ISO C Standard
You can apply the modifiers signed, short, long, and unsigned to integer base types.
You can apply unsigned and signed to characters. You may also apply long todouble.
Table 2-1 shows all valid data type combinations, along with their minimal ranges and
approximate bit widths. (These values also apply to a typical C++ implementation.)Remember, the table shows the minimum range that these types will have as specified
by Standard C/C++, not their typical range. For example, on computers that use two'scomplement arithmetic (which is nearly all), an integer will have a range of at least32,767 to –32,768.
The use of signed on integers is allowed, but redundant because the default integer
declaration assumes a signed number. The most important use of signed is to modify
char in implementations in which char is unsigned by default.
The difference between signed and unsigned integers is in the way that the high-
order bit of the integer is interpreted. If you specify a signed integer, the compilergenerates code that assumes that the high-order bit of an integer is to be used as asign flag. If the sign flag is 0, the number is positive; if it is 1, the number is negative.
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, andsets the sign flag to 1.
Signed integers are important for a great many algorithms, but they only have half
the absolute magnitude of their unsigned relatives. For example, here is 32,767:
0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
If the high-order bit were set to 1, the number would be interpreted as −1. However,
if you declare this to be an unsigned int, the number becomes 65,535 when the high-
order bit is set to 1.
When a type modifier is used by itself (that is, when it does not precede a basic
type), then intis assumed. Thus, the following sets of type specifiers are equivalent:
Specifier Same As
signed signed int
unsigned unsigned int
long long int
short short int
Although the intis implied, many programmers specify the intanyway.
Identifier Names
In C/C++, the names of variables, functions, labels, and various other user-definedobjects are called identifiers. These identifiers can vary from one to several characters.16 C++: The Complete Reference
The first character must be a letter or an underscore, and subsequent characters must
be either letters, digits, or underscores. Here are some correct and incorrect identifiernames:
Correct Incorrect
Count 1count
test23 hi!there
high_balance high…balance
In C, identifiers may be of any length. However, not all characters will necessarily
be significant. If the identifier will be involved in an external link process, then atleast the first six characters will be significant. These identifiers, called external names,
include function names and global variables that are shared between files. If theidentifier is not used in an external link process, then at least the first 31 characterswill be significant. This type of identifier is called an internal name and includes the
names of local variables, for example. In C++, there is no limit to the length of anidentifier, and at least the first 1,024 characters are significant. This difference maybe important if you are converting a program from C to C++.
In an identifier, upper- and lowercase are treated as distinct. Hence, count, Count,
and COUNT are three separate identifiers.
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.
Variables
As you probably know, a variable is a named location in memory that is used to hold a
value that may be modified by the program. All variables must be declared before theycan be used. The general form of a declaration is
type variable_list;
Here, type must be a valid data type plus any modifiers, and variable_list may consist of
one or more identifier names separated by commas. 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.Chapter 2: Expressions 17THE FOUNDATION OF C++:
THE C SUBSET
Where Variables Are Declared
Variables will be declared in three basic places: inside functions, in the definition of
function parameters, and outside of all functions. These are local variables, formalparameters, and global variables.
Local Variables
Variables that are declared inside a function are called local variables. In some C/C++
literature, these variables are referred to as automatic variables. This book uses the more
common term, local variable. Local variables may be referenced only by statements thatare inside the block in which the variables are declared. In other words, local variablesare not known outside their own code block. Remember, a block of code begins with anopening curly brace and terminates with a closing curly brace.
Local variables exist only while the block of code in which they are declared is
executing. That is, a local variable is created upon entry into its block and destroyedupon exit.
The most common code block in which local variables are declared is the function.
For example, consider the following two functions:
void func1(void)
{
int x;
x = 10;
}void func2(void)
{
int x;
x = -199;
}
The integer variable xis declared twice, once in func1( ) and once in func2( ). The xin
func1( ) has no bearing on or relationship to the xinfunc2( ) . This is because each x
is known only to the code within the block in which it is declared.
The C language contains the keyword auto, which you can use to declare local
variables. However, since all nonglobal variables are, by default, assumed to be auto,
this keyword is virtually never used. Hence, the examples in this book will not use it.
(It has been said that auto was included in C to provide for source-level compatibility
with its predecessor B. Further, auto is supported in C++ to provide compatibility
with C.)18 C++: The Complete Reference
Chapter 2: Expressions 19THE FOUNDATION OF C++:
THE C SUBSETFor reasons of convenience and tradition, most programmers declare all the variables
used by a function immediately after the function's opening curly braceand before any
other statements. However, you may declare local variables within any code block. Theblock defined by a function is simply a special case. For example,
void f(void)
{
int t;
scanf("%d%*c", &t);if(t==1) {
char s[80]; /* this is created only upon
entry into this block */
printf("Enter name:");
gets(s);/* do something … */
}
}
Here, the local variable sis created upon entry into the ifcode block and destroyed
upon exit. Furthermore, sis known only within the ifblock and may not be referenced
elsewhere—even in other parts of the function that contains it.
Declaring variables within the block of code that uses them helps prevent
unwanted side effects. Since a variable does not exist outside the block in which it is
declared, it cannot be accidentally altered.
There is an important difference between C (as defined by C89) and C++ as to
where you can declare local variables. In C, you must declare all local variables at thestart of a block, prior to any "action" statements. For example, in C89 the followingfunction is in error.
/* For C89, this function is in error,
but it is perfectly acceptable for C++.
*/
void f(void){
int i;
i = 10;int j; /* this line will cause an error */
j = 20;
}
However, in C++, this function is perfectly valid because you can declare local
variables at any point within a block, prior to their first use. (The topic of C++ variabledeclaration is discussed in depth in Part Two.) As a point of interest, C99 allows you todefine variables at any point within a block.
Because local variables are created and destroyed with each entry and exit from
the block in which they are declared, their content is lost once the block is left. This isespecially important to remember when calling a function. When a function is called,its local variables are created, and upon its return they are destroyed. This means thatlocal variables cannot retain their values between calls. (However, you can direct thecompiler to retain their values by using the static modifier.)
Unless otherwise specified, local variables are stored on the stack. The fact that
the stack is a dynamic and changing region of memory explains why local variablescannot, in general, hold their values between function calls.
You can initialize a local variable to some known value. This value will be assigned
to the variable each time the block of code in which it is declared is entered. For example,the following program prints the number 10 ten times:
#include <stdio.h>
void f(void);int main(void)
{
int i;
for(i=0; i<10; i++) f();return 0;
}void f(void)
{
int j = 10;
printf("%d ", j);j++; /* this line has no lasting effect */
}20 C++: The Complete Reference
Formal Parameters
If a function is to use arguments, it must declare variables that will accept the values
of the arguments. These variables are called the formal parameters of the function. They
behave like any other local variables inside the function. As shown in the followingprogram fragment, their declarations occur after the function name and insideparentheses:
/* 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: sand c. This function returns 1 if the character
specified in cis contained within the string s; 0 if it is not.
You must specify the type of the formal parameters by declaring them as just shown.
Then you may use them inside the function as normal local variables. Keep in mind that,
as local variables, they are also dynamic and are destroyed upon exit from the function.
As with local variables, you may make assignments to a function's formal parameters
or use them in any allowable expression. Even though these variables receive the value ofthe arguments passed to the function, you can use them like any other local variable.
Global Variables
Unlike local variables, global variables are known throughout the program and may be
used by any piece of code. Also, they will hold their value throughout the program'sexecution. You create global variables by declaring them outside of any function. Anyexpression may access them, regardless of what block of code that expression is in.
In the following program, the variable count has been declared outside of all functions.
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. However, it is usuallybest to declare global variables at the top of the program.
#include <stdio.h>
int count; /* count is global */
void func1(void);Chapter 2: Expressions 21THE FOUNDATION OF C++:
THE C SUBSET
void func2(void);
int main(void)
{
count = 100;func1();
return 0;
}void func1(void)
{
int temp;
temp = count;
func2();printf("count is %d", count); /* will print 100 */
}
void func2(void)
{
int count;
for(count=1; count<10; count++)
putchar('.');
}
Look closely at this program. Notice that although neither main( ) norfunc1( ) has
declared the variable count, both may use it. func2( ), however, has declared a local
variable called count. When func2( ) refers to count, it refers to only its local variable,
not the global one. 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 isdeclared will refer to that local variable and have no effect on the global variable.This can be convenient, but forgetting it can cause your program to act strangely,even though it looks correct.
Storage for global variables is in a fixed region of memory set aside for this purpose
by the compiler. Global variables are helpful when many functions in your programuse the same data. You should avoid using unnecessary global variables, however.They take up memory the entire time your program is executing, not just when they areneeded. In addition, using a global where a local variable would do makes a functionless general because it relies on something that must be defined outside itself. Finally,using a large number of global variables can lead to program errors because of unknown22 C++: The Complete Reference
Chapter 2: Expressions 23THE FOUNDATION OF C++:
THE C SUBSETand unwanted side effects. A major problem in developing large programs is the
accidental changing of a variable's value because it was used elsewhere in the program.This can happen in C/C++ if you use too many global variables in your programs.
The const and volatile Qualifiers
There are two qualifiers that control how variables may be accessed or modified:const and volatile. They must precede the type modifiers and the type names that
they qualify. These qualifiers are formally referred to as the cv-qualifiers.
const
Variables of type const may not be changed by your program. (A const variable can be
given an initial value, however.) The compiler is free to place variables of this type intoread-only memory (ROM). For example,
const int a=10;
creates an integer variable called awith an initial value of 10 that your program
may not modify. However, you can use the variable ain other types of expressions.
Aconst variable will receive its value either from an explicit initialization or by some
hardware-dependent means.
The const qualifier can be used to protect the objects pointed to by the arguments
to a function from being modified by that function. That is, when a pointer is passed toa function, that function can modify the actual variable pointed to by the pointer. 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. For example, the sp_to_dash( ) function in the
following program prints a dash for each space in its string argument. That is, the string"this is a test" will be printed as "this-is-a-test". The use of const in the parameter
declaration ensures that the code inside the function cannot modify the object pointedto by the parameter.
#include <stdio.h>
void sp_to_dash(const char *str);int main(void)
{
sp_to_dash("this is a test");
return 0;
}
void sp_to_dash(const char *str)
{
while(*str) {
if(*str== ' ') printf("%c", '-');
else printf("%c", *str);str++;
}
}
If you had written sp_to_dash( ) in such a way that the string would be modified, it
would not compile. For example, if you had coded sp_to_dash( ) as follows, you would
receive a compile-time error:
/* This is wrong. */void sp_to_dash(const char *str){
while(*str) {
if(*str==' ' ) *str = '-'; /* can't do this; str is const */printf("%c", *str);str++;
}
}
Many functions in the standard library use const in their parameter declarations.
For example, the strlen( ) function has this prototype:
size_t strlen(const char *str);
Specifying strasconst ensures that strlen( ) will not modify the string pointed to by str.
In general, when a standard library function has no need to modify an object pointed to
by a calling argument, it is declared as const.
You can also use const to verify that your program does not modify a variable.
Remember, a variable of type const can be modified by something outside your
program. For example, a hardware device may set its value. However, by declaringa variable as const, you can prove that any changes to that variable occur because of
external events.
volatile
The modifier volatile tells the compiler that a variable's value may be changed in ways
not explicitly specified by the program. For example, a global variable's address maybe passed to the operating system's clock routine and used to hold the real time of the24 C++: The Complete Reference
Chapter 2: Expressions 25THE FOUNDATION OF C++:
THE C SUBSETsystem. In this situation, the contents of the variable are altered without any explicit
assignment statements in the program. This is important because most C/C++ compilersautomatically optimize certain expressions by assuming that a variable's content isunchanging if it does not occur on the left side of an assignment statement; thus, itmight not be reexamined each time it is referenced. Also, some compilers change theorder of evaluation of an expression during the compilation process. The volatile
modifier prevents these changes.
You can use const and volatile together. For example, if 0x30 is assumed to be the
value of a port that is changed by external conditions only, the following declarationwould prevent any possibility of accidental side effects:
const volatile char *port = (const volatile char *) 0x30;
Storage Class Specifiers
There are four storage class specifiers supported by C:
externstaticregisterauto
These specifiers tell the compiler how to store the subsequent variable. The generalform of a declaration that uses one is shown here.
storage_specifier type var_name;
Notice that the storage specifier precedes the rest of the variable declaration.
C++ adds another storage-class specifier called mutable, which is described in
Part Two.
extern
Before examining extern, a brief description of C/C++ linkage is in order. C and C++
define three categories of linkage: external, internal, and none. In general, functionsand global variables have external linkage. This means that they are available to allfiles that comprise a program. Global objects declared as static (described in the next
section) have internal linkage. These are known only within the file in which they aredeclared. Local variables have no linkage and are therefore known only within theirown block.
The principal use of extern is to specify that an object is declared with external
linkage elsewhere in the program. To understand why this is important it is necessary
to understand the difference between a declaration and a definition. A declaration declares
the name and type of an object. A definition causes storage to be allocated for the
object. While there can be many declarations of the same object, there can be only one
definition for the object.
In most cases, variable declarations are also definitions. However, by preceding a
variable name with the extern specifier, you can declare a variable without defining it.
Thus, when you need to refer to a variable that is defined in another part of your program,you can declare that variable using extern.
Here is an example that uses extern. Notice that the global variables first and last
are declared after main( ).
#include <stdio.h>
int main(void)
{
extern int first, last; /* use global vars */
printf("%d %d", first, last);return 0;
}/* global definition of first and last */
int first = 10, last = 20;
This programs outputs 10 20 because the global variables first and lastused by the
printf( ) statement are initialized to these values. Because the extern declaration in
main( ) tells the compiler that first and lastare declared elsewhere (in this case, later
in the same file), the program can be compiled without error even though first and
lastare used prior to their definition.
It is important to understand that the extern variable declarations as shown in the
preceding program are necessary only because first and lasthad not yet been declared
prior to their use in main( ). Had their declarations occurred prior to main( ), then there
would have been no need for the extern statement. Remember, if the compiler finds a
variable that has not been declared within the current block, the compiler checks if it
matches any of the variables declared within enclosing blocks. If it does not, the compilerthen checks the previously declared global variables. If a match is found, the compilerassumes that that is the variable being referenced. The extern specifier is needed when
you want to use a variable that is declared later in the file.
As mentioned, extern allows you to declare a variable without defining it. However,
if you give that variable an initialization, then the extern declaration becomes a definition.
This is important because an object can have multiple declarations, but only onedefinition.26 C++: The Complete Reference
There is an important use of extern that relates to mutiple-file programs. In C/C++,
a program can be spread across two or more files, compiled separately, and then linked
together. When this is the case, there must be some way of telling all the files about theglobal variables required by the program. The best (and most portable) way to do thisis to declare all of your global variables in one file and use extern declarations in the
other, as in Figure 2-1.
In File Two, the global variable list was copied from File One and the extern specifier
was added to the declarations. The extern specifier tells the compiler that the variable
types and names that follow it have been defined elsewhere. In other words, extern lets
the compiler know what the types and names are for these global variables withoutactually creating storage for them again. When the linker links the two modules, allreferences to the external variables are resolved.
In real world, multi-file programs, extern declarations are normally contained in
a header file that is simply included with each source code file. This is both easier andless error prone than manually duplicating extern declarations in each file.
In C++, the extern specifier has another use, which is described in Part Two.
extern can also be applied to a function declaration, but doing so is redundant.Chapter 2: Expressions 27THE FOUNDATION OF C++:
THE C SUBSET
File One File Two
int x, y; extern int x, y;
char ch; extern char ch;
int main(void) void func22(void){{
/* … */ x = y / 10;
}}
void func1(void) void func23(void)
{{
x = 123; y = 10;
}}
Figure 2-1. Using global variables in separately compiled modules
28 C++: The Complete Reference
static Variables
static variables are permanent variables within their own function or file. Unlike global
variables, they are not known outside their function or file, but they maintain their
values between calls. This feature makes them useful when you write generalizedfunctions and function libraries that other programmers may use. static has different
effects upon local variables and global variables.
static Local Variables
When you apply the static modifier to a local variable, the compiler creates permanent
storage for it, much as it creates storage for a global variable. The key differencebetween 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. In simple terms, a static local
variable is a local variable that retains its value between function calls.
static local variables are very important to the creation of stand-alone functions
because several types of routines must preserve a value between calls. If static variables
were not allowed, globals would have to be used, opening the door to possible sideeffects. An example of a function that benefits from a static local variable is a number-
series generator that produces a new value based on the previous one. You could usea global variable to hold this value. However, each time the function is used in aprogram, you would have to declare that global variable and make sure that it did notconflict with any other global variables already in place. The better solution is to declarethe variable that holds the generated number to be static, as in this program fragment:
int series(void)
{
static int series_num;
series_num = series_num+23;
return series_num;
}
In this example, the variable series_num stays in existence between function calls,
instead of coming and going the way a normal local variable would. 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.
You can give a static local variable an initialization value. This value is assigned
only once, at program start-up—not each time the block of code is entered, as withnormal local variables. For example, this version of series( ) initializes series_num
to 100:
int series(void)
{
Chapter 2: Expressions 29THE FOUNDATION OF C++:
THE C SUBSETstatic int series_num = 100;
series_num = series_num+23;
return series_num;
}
As the function now stands, the series will always begin with the same value—in this
case, 123. While this might be acceptable for some applications, most series generatorsneed to let the user specify the starting point. One way to give series_num a user-specified
value is to make it a global variable and then let the user set its value. However, notdefining series_num as global was the point of making it static. This leads to the second
use of 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. This meansthat even though the variable is global, routines in other files may have no knowledgeof it or alter its contents directly, keeping it free from side effects. For the few situationswhere a local static variable 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.
To illustrate a global static variable, the series generator example from the previous
section is recoded so that a seed value initializes the series through a call to a secondfunction called series_start( ). The entire file containing series( ), series_start( ), and
series_num is shown here:
/* This must all be in one file – preferably by itself. */
static int series_num;
void series_start(int seed);int series(void);
int series(void)
{
series_num = series_num+23;return series_num;
}
/* initialize series_num */
void series_start(int seed){
series_num = seed;
}
Calling series_start( ) with some known integer value initializes the series generator.
After that, calls to series( ) generate the next element in the series.
To review: The names of local static variables are known only to the block of code
in which they are declared; the names of global static variables are known only to the
file in which they reside. 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 fact, you can even declare and
use another variable called series_num in your program (in another file, of course). In
essence, the static modifier permits variables that are known only to the functions that
need them, without unwanted side effects.
static variables enable you to hide portions of your program from other portions.
This can be a tremendous advantage when you are trying to manage a very large andcomplex program.
In C++, the preceding use of static is still supported, but deprecated. This means that it
is not recommended for new code. Instead, you should use a namespace, which is describedin Part Two.
register Variables
The register storage specifier originally applied only to variables of type int, char , or
pointer types. However, register's definition has been broadened so that it applies to
any type of variable.
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 arestored. This meant that operations on a register variable could occur much faster than
on a normal variable because the register variable was actually held in the CPU and
did not require a memory access to determine or modify its value.
Today, the definition of register has been greatly expanded and it now may be applied
to any type of variable. Standard C simply states "that access to the object be as fast aspossible." (Standard C++ states that register is a "hint to the implementation that the
object so declared will be heavily used.") In practice, characters and integers are stillstored in registers in the CPU. Larger objects like arrays obviously cannot be stored ina register, but they may still receive preferential treatment by the compiler. Dependingupon the implementation of the C/C++ compiler and its operating environment, register
variables may be handled in any way deemed fit by the compiler's implementor. 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.
You can only apply the register specifier to local variables and to the formal
parameters in a function. Global register variables are not allowed. Here is an example
that uses register variables. This function computes the result of M
efor integers:30 C++: The Complete Reference
Chapter 2: Expressions 31THE FOUNDATION OF C++:
THE C SUBSETint int_pwr(register int m, register int e)
{
register int temp;
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. The fact that register variables are optimized for speed
makes them ideal for control of or use in loops. 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. This is important because you can declare any numberof variables as being of type register , but not all will receive the same access speed
optimization.
The number of register variables optimized for speed within any one code block is
determined by both the environment and the specific implementation of C/C++. Youdon't have to worry about declaring too many register variables because the compiler
automatically transforms register variables into nonregister variables when the limit is
reached. (This ensures portability of code across a broad line of processors.)
Usually at least two register variables of type char orintcan actually be held in
the registers of the CPU. Because environments vary widely, consult your compiler'sdocumentation to determine if you can apply any other types of optimization options.
In C, you cannot find the address of a register variable using the &operator (discussed
later in this chapter). This makes sense because a register variable might be stored in
a register of the CPU, which is not usually addressable. But this restriction does notapply to C++. However, taking the address of a register variable in C++ may prevent
it from being fully optimized.
Although the description of register has been broadened beyond its traditional
meaning, in practice it still generally has a significant effect only with integer andcharacter types. Thus, you should probably not count on substantial speed improvementsfor other variable types.
Variable Initializations
You can give variables a value as you declare them by placing an equal sign anda value after the variable name. The general form of initialization is
type variable_name = value;
32 C++: The Complete Reference
Some examples are
char ch = 'a';
int first = 0;float balance = 123.23;
Global and static local variables are initialized only at the start of the program. Local
variables (excluding static local variables) are initialized each time the block in which
they are declared is entered. Local variables that are not initialized have unknown
values before the first assignment is made to them. Uninitialized global and static local
variables are automatically set to zero.
Constants
Constants refer to fixed values that the program cannot alter. Constants can be of any
of the basic data types. The way each constant is represented depends upon its type.Constants are also called literals.
Character constants are enclosed between single quotes. For example 'a' and '%'
are both character constants. Both C and C++ define wide characters (used mostly innon-English language environments), which are 16 bits long. To specify a wide characterconstant, precede the character with an L. For example,
wchar_t wc;
wc = L'A';
Here, wcis assigned the wide-character constant equivalent of A. The type of wide
characters is wchar_t. In C, this type is defined in a header file and is not a built-in
type. In C++, wchar_t is built in.
Integer constants are specified as numbers without fractional components. For
example, 10 and –100 are integer constants. Floating-point constants require the
decimal point followed by the number's fractional component. For example, 11.123is a floating-point constant. C/C++ also allows you to use scientific notation forfloating-point numbers.
There are two floating-point types: float and double. There are also several variations
of the basic types that you can generate using the type modifiers. By default, the compilerfits a numeric constant into the smallest compatible data type that will hold it. Therefore,assuming 16-bit integers, 10 is intby default, but 103,000 is a long. Even though the
value 10 could fit into a character type, the compiler will not cross type boundaries. Theonly exception to the smallest type rule ar e floating-point constants, which are assumed
to be doubles.
For most programs you will write, the compiler defaults are adequate. However,
you can specify precisely the type of numeric constant you want by using a suffix.For floating-point types, if you follow the number with an F, the number is treated
Chapter 2: Expressions 33THE FOUNDATION OF C++:
THE C SUBSETas a float. If you follow it with an L, the number becomes a long double. For integer
types, the U suffix stands for unsigned and the L for long. Here are some examples:
Data type Constant examples
int 1 123 21000 −234
long int 35000L −34L
unsigned int 10000U 987U 40000U
float 123.23F 4.34e−3F
double 123.23 1.0 −0.9876324
long double 1001.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). The number system based on 8 is called octal and uses the
digits 0 through 7. In octal, the number 10 is the same as 8 in decimal. The base 16 numbersystem is called hexadecimal and uses the digits 0 through 9 plus the letters A through F,
which stand for 10, 11, 12, 13, 14, and 15, respectively. For example, the hexadecimalnumber 10 is 16 in decimal. Because these two number systems are used frequently,C/C++ allows you to specify integer constants in hexadecimal or octal instead ofdecimal. A hexadecimal constant must consist of a 0x followed by the constant inhexadecimal form. An octal constant begins with a 0. Here are some examples:
int hex = 0x80; /* 128 in decimal */
int oct = 012; /* 10 in decimal */
String Constants
C/C++ supports one other type of constant: the string. A string is a set of characters
enclosed in double quotes. For example, "this is a test" is a string. You have seen examples
of strings in some of the printf( ) statements in the sample programs. Although C
allows you to define string constants, it does not formally have a string data type.(C++ does define a string class, however.)
You must not confuse strings with characters. A single character constant is enclosed
in single quotes, as in 'a'. However, "a" is a string containing only one letter.
Backslash Character Constants
Enclosing character constants in single quotes works for most printing characters. Afew, however, such as the carriage return, are impossible to enter into a string from thekeyboard. For this reason, C/C++ include the special backslash character constants shown
in Table 2-2 so that you may easily enter these special characters as constants. These are
also referred to as escape sequences. You should use the backslash codes instead of their
ASCII equivalents to help ensure portability.
For example, the following program outputs a new line and a tab and then prints
the string This is a test.
#include <stdio.h>
int main(void)
{
printf("\n\tThis is a test.");
return 0;
}34 C++: The Complete Reference
Code Meaning
\b Backspace
\f Form feed
\n New line
\r Carriage return
\t Horizontal tab
\" Double quote
\' Single quote
\0 Null
\\ Backslash
\v Vertical tab
\a Alert
\? Question mark
\N Octal constant (where N is an octal constant)
\xN Hexadecimal constant (where N is a hexadecimal
constant)
Table 2-2. Backslash Codes
Chapter 2: Expressions 35THE FOUNDATION OF C++:
THE C SUBSET Operators
C/C++ is rich in built-in operators. In fact, it places more significance on operators than
do most other computer languages. There are four main classes of operators: arithmetic,
relational, logical, and bitwise. In addition, there are some special operators for
particular tasks.
The Assignment Operator
You can use the assignment operator within any valid expression. This is not the casewith many computer languages (including Pascal, BASIC, and FORTRAN), which treatthe assignment operator as a special case statement. The general form of the assignmentoperator is
variable_name = expression;
where an expression may be as simple as a single constant or as complex as you require.C/C++ uses a single equal sign to indicate assignment (unlike Pascal or Modula-2,which use the := construct). The target, or left part, of the assignment must be a variable
or a pointer, not a function or a constant.
Frequently in literature on C/C++ and in compiler error messages you will see
these two terms: lvalue and rvalue. Simply put, an lvalue is any object that can occur
on the left side of an assignment statement. For all practical purposes, "lvalue" means"variable." The term rvalue refers to expressions on the right side of an assignment and
simply means the value of an expression.
Type Conversion in Assignments
When variables of one type are mixed with variables of another type, a type conversion
will occur. In an assignment statement, the type conversion rule is easy: The value ofthe right side (expression side) of the assignment is converted to the type of the leftside (target variable), as illustrated here:
int x;
char ch;float f;
void func(void)
{
ch = x; /* line 1 */x = f; /* line 2 */f = ch; /* line 3 */f = x; /* line 4 */
}
In line 1, the left high-order bits of the integer variable xare lopped off, leaving chwith
the lower 8 bits. If xwere between 255 and 0, chand xwould have identical values.
Otherwise, the value of chwould reflect only the lower-order bits of x. In line 2, xwill
receive the nonfractional part of f. In line 3, fwill convert the 8-bit integer value stored
inchto the same value in the floating-point format. This also happens in line 4, except
that fwill convert an integer value into floating-point format.
When converting from integers to characters and long integers to integers, the
appropriate amount of high-order bits will be removed. In many 16-bit environments,
this means that 8 bits will be lost when going from an integer to a character and 16 bitswill be lost when going from a long integer to an integer. For 32-bit environments,24 bits will be lost when converting from an integer to a character and 16 bits will belost when converting from an integer to a short integer.
Table 2-3 summarizes the assignment type conversions. Remember that the conversion
of an intto a float,o rafloat to a double, and so on, does not add any precision or
accuracy. These kinds of conversions only change the form in which the value isrepresented. In addition, some compilers always treat a char variable as positive, no
matter what value it has, when converting it to an intorfloat. Other compilers treat
char variable values greater than 127 as negative numbers when converting. Generally36 C++: The Complete Reference
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. The Outcome of Common Type Conversions
Chapter 2: Expressions 37THE FOUNDATION OF C++:
THE C SUBSETspeaking, you should use char variables for characters, and use ints, short ints, or
signed chars when needed to avoid possible portability problems.
To use Table 2-3 to make a conversion not shown, simply convert one type at a time
until you finish. For example, to convert from double toint, first convert from double
tofloat and then from float toint.
Multiple Assignments
C/C++ allows you to assign many variables the same value by using multiple assignments
in a single statement. For example, this program fragment assigns x,y, and zthe value 0:
x = y = z = 0;
In professional programs, variables are frequently assigned common values using thismethod.
Arithmetic Operators
Table 2-4 lists C/C++'s arithmetic operators. The operators +,−,*, and /work as they
do in most other computer languages. You can apply them to almost any built-in datatype. When you apply /to an integer or character, any remainder will be truncated.
For example, 5/2 will equal 2 in integer division.
The modulus operator %also works in C/C++ as it does in other languages,
yielding the remainder of an integer division. However, you cannot use it onfloating-point types. The following code fragment illustrates %:
int x, y;
x = 5;
y = 2;
printf("%d ", x/y); /* will display 2 */
printf("%d ", x%y); /* will display 1, the remainder of
the integer division */
x = 1;y = 2;
printf("%d %d", x/y, x%y); /* will display 0 1 */
The last line prints a 0 and a 1 because 1/2 in integer division is 0 with a remainder of 1.
The unary minus multiplies its operand by –1. That is, any number preceded by
a minus sign switches its sign.
Increment and Decrement
C/C++ includes two useful operators not found in some other computer languages.
These are the increment and decrement operators, ++and − −. The operator ++adds 1
to its operand, and − −subtracts 1. In other words:
x = x+1;
is the same as
++x;
and
x = x-1;
is the same as
x–;
Both the increment and decrement operators may either precede (prefix) or follow(postfix) the operand. For example,38 C++: The Complete Reference
Operator Action
− Subtraction, also unary minus
+ Addition
* Multiplication
/ Division
% Modulus
– – Decrement
++ Increment
Table 2-4. Arithmetic Operators
Chapter 2: Expressions 39THE FOUNDATION OF C++:
THE C SUBSETx = 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. When an increment or decrement operator precedesits operand, the increment or decrement operation is performed before obtaining the valueof the operand for use in the expression. If the operator follows its operand, the value ofthe operand is obtained before incrementing or decrementing it. For instance,
x = 10;
y = ++x;
sets yto 11. However, if you write the code as
x = 10;y = x++;
yis set to 10. Either way, xis set to 11; the difference is in when it happens.
Most C/C++ compilers produce very fast, efficient object code for increment and
decrement operations—code that is better than that generated by using the equivalent
assignment statement. For this reason, you should use the increment and decrementoperators when you can.
Here is the precedence of the arithmetic operators:
highest ++ – –
– (unary minus)* / %
lowest + –
Operators on the same level of precedence are evaluated by the compiler from left to
right. Of course, you can use parentheses to alter the order of evaluation. C/C++ treatsparentheses in the same way as virtually all other computer languages. Parenthesesforce an operation, or set of operations, to have a higher level of precedence.
Relational and Logical Operators
In the term relational operator , relational refers to the relationships that values canhave
with one another. In the term logical operator , logical refers to the ways these relationships
can be connected. Because the relational and logical operators oftenwork together, theyare discussed together here.
The idea of true and false underlies the concepts of relational and logical operators.
In C, true is any value other than zero. False is zero. Expressions that use relational orlogical operators return 0 for false and 1 for true.
C++ fully supports the zero/non-zero concept of true and false. However, it also
defines the bool data type and the Boolean constants true and false. In C++, a 0 value
is automatically converted into false, and a non-zero value is automatically converted
into true. The reverse also applies: true converts to 1 and false converts to 0. In C++,
the outcome of a relational or logical operation is true orfalse. But since this automatically
converts into 1 or 0, the distinction between C and C++ on this issue is mostly academic.
Table 2-5 shows the relational and logical operators. The truth table for the logical
operators is shown here using 1's and 0's.
p q p && q p || q !p
00 0 0 1
01 0 1 111 1 1 010 0 1 0
Both the relational and logical operators are lower in precedence than the
arithmetic operators. That is, an expression like 10 > 1+12 is evaluated as if it were
written 10 > (1+12). Of course, the result is false.
You can combine several operations together into one expression, as shown here:
10>5 && !(10<9) || 3<=4
In this case, the result is true.
Although neither C nor C++ contain an exclusive OR (XOR) logical operator, you
can easily create a function that performs this task using the other logical operators.
The outcome of an XOR operation is true if and only if one operand (but not both) is40 C++: The Complete Reference
Chapter 2: Expressions 41THE FOUNDATION OF C++:
THE C SUBSET
true. The following program contains the function xor( ), which returns the outcome of
an exclusive OR operation performed on its two arguments:
#include <stdio.h>
int xor(int a, int b);int main(void)
{
printf("%d", xor(1, 0));printf("%d", xor(1, 1));printf("%d", xor(0, 1));printf("%d", xor(0, 0));
return 0;
}Relational Operators
Operator Action
> Greater than
>= Greater than or equal
< Less than
<= Less than or equal
= = Equal
!= Not equal
Logical Operators
Operator Action
&& AND
|| OR
! NOT
Table 2-5. Relational and Logical Operators
42 C++: The Complete Reference
/* Perform a logical XOR operation using the
two arguments. */
int xor(int a, int b)
{
return (a || b) && !(a && b);
}
The following table shows the relative precedence of the relational and logical
operators:
Highest !
> >= < <=== !=&&
Lowest ||
As with arithmetic expressions, you can use parentheses to alter the natural order of
evaluation in a relational and/or logical expression. For example,
!0 && 0 || 0
is false. However, when you add parentheses to the same expression, as shown here,the result is true:
!(0 && 0) || 0
Remember, all relational and logical expressions produce either a true or false
result. Therefore, the following program fragment is not only correct, but will print
the number 1.
int x;
x = 100;
printf("%d", x>10);
Bitwise Operators
Unlike many other languages, C/C++ supports a full complement of bitwise operators.
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. Bitwise operation refers to testing, setting, or shifting the
actual bits in a byte or word, which correspond to the char and intdata types and
variants. You cannot use bitwise operations on float, double, long double, void,
bool, or other, more complex types. Table 2-6 lists the operators that apply to bitwiseoperations. These operations are applied to the individual bits of the operands.
The bitwise AND, OR, and NOT (one's complement) are governed by the same
truth table as their logical equivalents, except that they work bit by bit. The exclusiveOR has the truth table shown here:
pqp ^q
000
101110011
As the table indicates, the outcome of an XOR is true only if exactly one of the operands
is true; otherwise, it is false.
Bitwise operations most often find application in device drivers—such as modem
programs, disk file routines, and printer routines — because the bitwise operationscan be used to mask off certain bits, such as parity. (The parity bit confirms that therest of the bits in the byte are unchanged. It is usually the high-order bit in each byte.)Chapter 2: Expressions 43THE FOUNDATION OF C++:
THE C SUBSET
Operator Action
& AND
|O R
^ Exclusive OR (XOR)
~ One's complement (NOT)
>> Shift right
<< Shift left
Table 2-6. Bitwise Operators
44 C++: The Complete Reference
Think of the bitwise AND as a way to clear a bit. That is, any bit that is 0 in either
operand causes the corresponding bit in the outcome to be set to 0. 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);
}
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 expression ch & 127 means
to AND together the bits in chwith the bits that make up the number 127. The net
result is that the eighth bit of chis set to 0. In the following example, assume that ch
had received the character "A" and had the parity bit set:
The bitwise OR, as the reverse of AND, can be used to set a bit. Any bit that is set
to 1 in either operand causes the corresponding bit in the outcome to be set to 1. For
example, the following is 128 | 3:
Ill 2-2Parity bit
1 1 0 0 0 0 0 1 chcontaining an "A" with parity set
0 1 1 1 1 1 1 1 127 in binary
&___________ bitwise AND
0 1 0 0 0 0 0 1 "A" without parity
1 0 0 0 0 0 0 0 128 in binary
0 0 0 0 0 0 1 1 3 in binary
¦___________ bitwise OR
1 0 0 0 0 0 1 1 result
An exclusive OR, usually abbreviated XOR, will set a bit on if and only if the bits
being compared are different. For example, 127 ^120 is
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. In other words, bitwise operations mayproduce values other than 0 or 1, while logical operators will always evaluate to 0 or 1.
The bit-shift operators, >> and <<, move all bits in a value to the right or left as
specified. The general form of the shift-right statement is
value >> number of bit positions
The general form of the shift-left statement is
value << number of bit positions
As bits are shifted off one end, 0's are brought in the other end. (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.) Remember, a shift is not a rotate. That is, the bits shifted off one end donot come back around to the other. The bits shifted off are lost.
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. The bitwise shiftoperators can also quickly multiply and divide integers. A shift right effectively dividesa number by 2 and a shift left multiplies it by 2, as shown in Table 2-7. The followingprogram illustrates the shift operators:
/* A bit shift example. */
#include <stdio.h>
int main(void)
{
unsigned int i;int j;
i = 1;Chapter 2: Expressions 45THE FOUNDATION OF C++:
THE C SUBSET
0 1 1 1 1 1 1 1 127 in binary
0 1 1 1 1 0 0 0 120 in binary
^___________ bitwise XOR
0 0 0 0 0 1 1 1 result
/* left shifts */
for(j=0; j<4; j++) {
i = i << 1; /* left shift i by 1, which
is same as a multiply by 2 */
printf("Left shift %d: %d\n", j, i);
}
/* right shifts */
for(j=0; j<4; j++) {
i = i >> 1; /* right shift i by 1, which
is same as a division by 2 */
printf("Right shift %d: %d\n", j, i);
}
return 0;
}
The one's complement operator, ~, reverses the state of each bit in its operand. That
is, all 1's are set to 0, and all 0's are set to 1.
The bitwise operators are often used in cipher routines. If you want to make a disk
file appear unreadable, perform some bitwise manipulations on it. One of the simplest46 C++: The Complete Reference
unsigned char x;x as each statement
executes value of x
x = 7; 0 0 0 0 0 1 1 1 7
x = x<<1; 0 0 0 0 1 1 1 0 14
x = x<<3; 0 1 1 1 0 0 0 0 112
x = x<<2; 1 1 0 0 0 0 0 0 192
x = x>>1; 0 1 1 0 0 0 0 0 96
x = x>>2; 0 0 0 1 1 0 0 0 24
*Each left shift multiplies by 2. Notice that information has been lost after x<<2 because
a bit was shifted off the end.
**Each right shift divides by 2. Notice that subsequent divisions do not bring back any
lost bits.
Table 2-7. Multiplication and Division with Shift Operators
Chapter 2: Expressions 47THE FOUNDATION OF C++:
THE C SUBSETmethods is to complement each byte by using the one's complement to reverse each bit
in the byte, as is shown here:
Notice that a sequence of two complements in a row always produces the original
number. Thus, the first complement represents the coded version of that byte. Thesecond complement decodes the byte to its original value.
You could use the encode( ) function shown here to encode a character.
/* A simple cipher function. */
char encode(char ch){
return(~ch); /* complement it */
}
Of course, a file encoded using encode( ) would be very easy to crack!
The ? Operator
C/C++ contains a very powerful and convenient operator that replaces certain
statements of the if-then-else form. The ternary operator ?takes the general form
Exp1 ? Exp2 : Exp3;
where Exp1, Exp2, and Exp3 are expressions. Notice the use and placement of the colon.
The ?operator works like this: Exp1 is evaluated. If it is true, Exp2 is evaluated
and becomes the value of the expression. If Exp1 is false, Exp3 is evaluated and its
value becomes the value of the expression. For example, in
x = 10;
y = x>9 ? 100 : 200;
yis assigned the value 100. If xhad been less than 9, ywould have received the value
200. The same code written using the if-else statement is
x = 10;SameOriginal byte 0 0 1 0 1 1 0 0
After 1st complement 1 1 0 1 0 0 1 1
After 2nd complement 0 0 1 0 1 1 0 0
48 C++: The Complete Reference
if(x>9) y = 100;
else y = 200;
The ?operator will be discussed more fully in Chapter 3 in relationship to the other
conditional statements.
The & and * Pointer Operators
Apointer is the memory address of some object. A pointer variable is a variable that is
specifically declared to hold a pointer to an object of its specified type. Knowing a
variable's address can be of great help in certain types of routines. However, pointershave three main functions in C/C++. They can provide a fast means of referencingarray elements. They allow functions to modify their calling parameters. Lastly,they support linked lists and other dynamic data structures. Chapter 5 is devotedexclusively to pointers. However, this chapter briefly covers the two operators thatare used to manipulate pointers.
The first pointer operator is &, a unary operator that returns the memory address
of its operand. (Remember, a unary operator only requires one operand.) For example,
m = &count;
places into mthe memory address of the variable count. This address is the computer's
internal location of the variable. It has nothing to do with the value of count. You can
think of &as meaning "the address of." Therefore, the preceding assignment statement
means "m receives the address of count."
To better understand this assignment, assume that the variable count is at memory
location 2000. Also assume that count has a value of 100. Then, after the previous
assignment, mwill have the value 2000.
The second pointer operator is *, which is the complement of &. The *is a unary
operator that returns the value of the variable located at the address that follows it.For example, if mcontains the memory address of the variable count,
q = *m;
places the value of count into q. Now qhas the value 100 because 100 is stored at
location 2000, the memory address that was stored in m. Think of *as meaning
"at address." In this case, you could read the statement as "q receives the value at
address m."
Unfortunately, the multiplication symbol and the "at address" symbol are the
same, and the symbol for the bitwise AND and the "address of" symbol are the same.
Chapter 2: Expressions 49THE FOUNDATION OF C++:
THE C SUBSETThese operators have no relationship to each other. Both &and *have a higher
precedence than all other arithmetic operators except the unary minus, with which
they share equal precedence.
Variables that will hold memory addresses (i.e., pointers), must be declared by
putting *in front of the variable name. This indicates to the compiler that it will hold
a pointer. For example, to declare chas a pointer to a character, write
char *ch;
Here, chis not a character but a pointer to a character—there is a big difference. The
type of data that a pointer points to, in this case char, is called the base type of the pointer.
However, the pointer variable itself is a variable that holds the address to an object ofthe base type. Thus, a character pointer (or any pointer) is of sufficient size to hold anyaddress as defined by the architecture of the computer. However, as a rule, a pointershould only point to data that is of that pointer's base type.
You can mix both pointer and nonpointer variables in the same declaration
statement. For example,
int x, *y, count;
declares xand count as integer types and yas a pointer to an integer type.
The following program uses *and &operators to put the value 10 into a variable
called target. As expected, this program displays the value 10 on the screen.
#include <stdio.h>
int main(void)
{
int target, source;int *m;
source = 10;
m = &source;target = *m;
printf("%d", target);return 0;
}
50 C++: The Complete Reference
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. For example, assuming that integers
are 4 bytes and doubles are 8 bytes,
double f;
printf("%d ", sizeof f);
printf("%d", sizeof(int));
will display 8 4.
Remember, to compute the size of a type, you must enclose the type name in parentheses.
This is not necessary for variable names, although there is no harm done if you do so.
C/C++ defines (using typedef) a special type called size_t, which corresponds
loosely to an unsigned integer. Technically, the value returned by sizeof is of type
size_t. For all practical purposes, however, you can think of it (and use it) as if it were
an unsigned integer value.
sizeof primarily helps to generate portable code that depends upon the size of the
built-in data types. For example, imagine a database program that needs to store sixinteger values per record. If you want to port the database program to a variety ofcomputers, you must not assume the size of an integer, but must determine its actuallength using sizeof. 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. */
void put_rec(int rec[6], FILE *fp){
int len;
len = fwrite(rec, sizeof(int)*6, 1, fp);
if(len != 1) printf("Write Error");
}
Coded as shown, put_rec( ) compiles and runs correctly in any environment, including
those that use 16- and 32-bit integers.
One final point: sizeof is evaluated at compile time, and the value it produces is
treated as a constant within your program.
The Comma Operator
The comma operator strings together several expressions. The left side of the comma
operator is always evaluated as void. This means that the expression on the right side
becomes the value of the total comma-separated expression. For example,
x = (y=3, y+1);
first assigns ythe value 3 and then assigns xthe value 4. The parentheses are necessary
because the comma operator has a lower precedence than the assignment operator.
Essentially, the comma causes a sequence of operations. When you use it on the
right side of an assignment statement, the value assigned is the value of the last
expression of the comma-separated list.
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."
The Dot (.) and Arrow ( >) Operators
In C, the .(dot) and the >(arrow) operators access individual elements of structures
and unions. Structures and unions are compound (also called aggregate) data types that
may be referenced under a single name (see Chapter 7). In C++, the dot and arrowoperators are also used to access the members of a class.
The dot operator is used when working with a structure or union directly. The
arrow operator is used when a pointer to a structure or union is used. 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.23 to the wage member of
structure variable emp:
emp.wage = 123.23;
However, the same assignment using a pointer to emp would be
p->wage = 123.23;
The [ ] and ( ) Operators
Parentheses are operators that increase the precedence of the operations inside them.
Square brackets perform array indexing (arrays are discussed fully in Chapter 4). GivenChapter 2: Expressions 51THE FOUNDATION OF C++:
THE C SUBSET
an array, the expression within square brackets provides an index into that array. For
example,
#include <stdio.h>
char s[80];
int main(void)
{
s[3] = 'X';printf("%c", s[3]);
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.
Precedence Summary
Table 2-8 lists the precedence of all operators defined by C. Note that all operators,except the unary operators and ?, associate from left to right. The unary operators
(*,&,
) and ?associate from right to left.
C++ defines a few additional operators, which are discussed at length in Part Two.
Expressions
Operators, constants, and variables are the constituents of expressions. An expression in
C/C++ is any valid combination of these elements. Because most expressions tend tofollow the general rules of algebra, they are often taken for granted. However, a fewaspects of expressions relate specifically to C and C++.
Order of Evaluation
Neither C nor C++ specifies the order in which the subexpressions of an expression areevaluated. This leaves the compiler free to rearrange an expression to produce moreoptimal code. However, it also means that your code should never rely upon the orderin which subexpressions are evaluated. For example, the expression
x = f1() + f2();
does not ensure that f1( ) will be called before f2( ).52 C++: The Complete Reference
Chapter 2: Expressions 53THE FOUNDATION OF C++:
THE C SUBSET
Type Conversion in Expressions
When constants and variables of different types are mixed in an expression, they are
all converted to the same type. The compiler converts all operands up to the type ofthe largest operand, which is called type promotion. First, all char and short int values
are automatically elevated to int. (This process is called integral promotion.) Once this
step has been completed, all other conversions are done operation by operation, asdescribed 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 doubleHighest ( ) [ ] −> .
! ~ ++ – – (type) * & sizeof
* / %
+−
<< >>
< <= > >=== !=&^|&&||
Highest
?:
= += −= *= /= etc.
Lowest ,
Table 2-8. The Precedence of C Operators
54 C++: The Complete Reference
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
There is one additional special case: If one operand is long and the other is
unsigned int, and if the value of the unsigned int cannot be represented by a long,
both operands are converted to unsigned long.
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.
For example, consider the type conversions that occur in Figure 2-2. First, the
character chis converted to an integer. Then the outcome of ch/i is converted to a
double because f*disdouble. The outcome of f+iisfloat, because fis afloat. The
final result is double.
Casts
You can force an expression to be of a specific type by using a cast. The general form of
a cast is
(type) expression
char ch;
int i;float f;double d;result=(ch/i) + (f*d) – (f+i);
int double float
int double float
double
Figure 2-2. A type conversion example
Chapter 2: Expressions 55THE FOUNDATION OF C++:
THE C SUBSETwhere type is a valid data type. For example, to make sure that the expression x/2
evaluates to type float, write
(float) x/2
Casts are technically operators. As an operator, a cast is unary and has the same
precedence as any other unary operator.
Although casts are not usually used a great deal in programming, they can be very
helpful when needed. For example, suppose you wish to use an integer for loop control,
yet to perform computation on it requires a fractional part, as in the following program:
#include <stdio.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. The cast
ensures that the fractional part of the answer is displayed.
C++ adds four more casting operators, such as const_cast andstatic_cast. These
operators are discussed in Part Two.
Spacing and Parentheses
You can add tabs and spaces to expressions to make them easier to read. For example,
the following two expressions are the same:
x=10/y~(127/x);
x = 10 / y ~(127/x);
Redundant or additional parentheses do not cause errors or slow down the execution
of an expression. You should use parentheses to clarify the exact order of evaluation,
both for yourself and for others. For example, which of the following two expressions
is easier to read?
x = y/3-34*temp+127;
x = (y/3) – (34*temp) + 127;
Compound Assignments
There is a variation on the assignment statement, called compound assignment, that
simplifies the coding of a certain type of assignment operation. For example,
x = x+10;
can be written as
x += 10;
The operator +=tells the compiler to assign to xthe value of xplus 10.
Compound assignment operators exist for all the binary operators (those that
require two operands). 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;
Compound assignment is widely used in professionally written C/C++ programs;
you should become familiar with it. Compound assignment is also commonly referred
to as shorthand assignment because it is more compact.56 C++: The Complete Reference
Chapter 3
Statements
57
Copyright © 2003 by The McGraw-Hill Companies. Click here for terms of use.
58 C++: The Complete Reference
This chapter discusses the statement. In the most general sense, a statement is a
part of your program that can be executed. That is, a statement specifies an
action. C and C++ categorize statements into these groups:
șSelection
șIteration
șJump
șLabel
șExpression
șBlock
Included in the selection statements are ifand switch. (The term conditional
statement is often used in place of "selection statement.") The iteration statements are
while, for, and do-while. These are also commonly called loop statements. The jump
statements are break, continue, goto, and return. The label statements include the
case and default statements (discussed along with the switch statement) and the label
statement (discussed with goto). Expression statements are statements composed
of a valid expression. Block statements are simply blocks of code. (Remember, ablock begins with a { and ends with a }.) Block statements are also referred to ascompound statements.
C++ adds two additional statement types: the tryblock (used by exception handling) and
the declaration statement. These are discussed in Part Two.
Since many statements rely upon the outcome of some conditional test, let's begin
by reviewing the concepts of true and false.
True and False in C and C++
Many C/C++ statements rely upon a conditional expression that determines what courseof action is to be taken. A conditional expression evaluates to either a true or false value.In C, a true value is any nonzero value, including negative numbers. A false value is 0.This approach to true and false allows a wide range of routines to be coded extremelyefficiently.
C++ fully supports the zero/nonzero definition of true and false just described. But
C++ also defines a Boolean data type called bool, which can have only the values true
and false. As explained in Chapter 2, in C++, a 0 value is automatically converted into
false and a nonzero value is automatically converted into true. The reverse also applies:
true converts to 1 and false converts to 0. In C++, the expression that controls a
conditional statement is technically of type bool. But since any nonzero value converts
totrue and any zero value converts to false, there is no practical difference between C
and C++ on this point.
C99 has added a Boolean type called _Bool, but it is incompatible with C++. See Part
Two for a discussion on how to achieve compatibility between C99’s _Bool and C++’s
bool types.
Selection Statements
C/C++ supports two types of selection statements: ifand switch. In addition, the ?
operator is an alternative to ifin certain circumstances.
if
The general form of the ifstatement 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). The else clause is optional.
Ifexpression evaluates to true (anything other than 0), the statement or block that
forms the target of ifis executed; otherwise, the statement or block that is the target
ofelse will be executed, if it exists. Remember, only the code associated with ifor the
code associated with else executes, never both.
In C, the conditional statement controlling ifmust produce a scalar result. A scalar
is either an integer, character, pointer, or floating-point type. In C++, it may also be of
type bool. It is rare to use a floating-point number to control a conditional statement
because this slows execution time considerably. (It takes several instructions to performa floating-point operation. It takes relatively few instructions to perform an integer orcharacter operation.)
The following program contains an example of if. The program plays a very simple
version of the "guess the magic number" game. It prints the message ** Right ** when
the player guesses the magic number. It generates the magic number using the standardrandom number generator rand( ), which returns an arbitrary number between 0 and
RAND_MAX (which defines an integer value that is 32,767 or larger). rand( ) requires
the header file stdlib.h. (A C++ program may also use the new-style header <cstdlib>.)
/* Magic number program #1. */
#include <stdio.h>#include <stdlib.h>
int main(void)
{
int magic; /* magic number */Chapter 3: Statements 59THE FOUNDATION OF C++:
THE C SUBSET
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.
/* Magic number program #2. */
#include <stdio.h>#include <stdlib.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 ifis an ifthat is the target of another iforelse. Nested ifs are very common in
programming. In a nested if, an else statement always refers to the nearest ifstatement
that is within the same block as the else and that is not already associated with an else.
For example,60 C++: The Complete Reference
if(i)
{
if(j) statement 1;if(k) statement 2; /* this if */else statement 3; /* is associated with this else */
}
else statement 4; /* associated with if(i) */
As noted, the final else is not associated with if(j) because it is not in the same block.
Rather, the final else is associated with if(i). Also, the inner else is associated with if(k),
which is the nearest if.
The C language guarantees at least 15 levels of nesting. In practice, most compilers
allow substantially more. More importantly, Standard C++ suggests that at least 256 levels
of nested ifs be allowed in a C++ program. However, nesting beyond a few levels is seldom
necessary, and excessive nesting can quickly confuse the meaning of an algorithm.
You can use a nested ifto further improve the magic number program by providing
the player with feedback about a wrong guess.
/* Magic number program #3. */
#include <stdio.h>#include <stdlib.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");
}Chapter 3: Statements 61THE FOUNDATION OF C++:
THE C SUBSET
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. Its general form is
if (expression) statement;
else
if (expression) statement;
else
if(expression) statement;
.
..else statement;
The conditions are evaluated from the top downward. As soon as a true conditionis found, the statement associated with it is executed and the rest of the ladder isbypassed. If none of the conditions are true, the final else is executed. That is, if all
other conditional tests fail, the last else statement is performed. If the final else is not
present, no action takes place if all other conditions are false.
Although the indentation of the preceding if-else-if ladder is technically correct, it
can lead to overly deep indentation. For this reason, the if-else-if ladder is generallyindented like this:
if (expression)
statement;
else if (expression)
statement;
else if (expression)
statement;
…else
statement;
Using an if-else-if ladder, the magic number program becomes
/* Magic number program #4. */
#include <stdio.h>
#include <stdlib.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 ifand else must be a single expression—not another
statement.
The ?is called a ternary operator because it requires three operands. It takes the
general form
Exp1 ? Exp2 : Exp3
where Exp1, Exp2, and Exp3 are expressions. Notice the use and placement of the colon.
The value of a ?expression is determined as follows: Exp1 is evaluated. If it is true, Exp2
is evaluated and becomes the value of the entire ?expression. If Exp1 is false, then Exp3 is
evaluated and its value becomes the value of the expression. For example, considerChapter 3: Statements 63THE FOUNDATION OF C++:
THE C SUBSET
x = 10;
y = x>9 ? 100 : 200;
In this example, yis assigned the value 100. If xhad been less than 9, ywould have
received the value 200. The same code written with the if-else statement would be
x = 10;if(x>9) y = 100;else y = 200;
The following program uses the ?operator to square an integer value entered by
the user. However, this program preserves the sign (10 squared is 100 and −10 squared
is−100).
#include <stdio.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. Remember, all functions (except those declared as void) return a value. Thus,
you can use one or more function calls in a ?expression. When the function's name
is encountered, the function is executed so that its return value may be determined.
Therefore, you can execute one or more function calls using the ?operator by placing
the calls in the expressions that form the ?'s operands. Here is an example.
#include <stdio.h>
int f1(int n);
int f2(void);64 C++: The Complete Reference
int main(void)
{
int t;
printf("Enter a number: ");
scanf("%d", &t);
/* print proper message */
t ? f1(t) + f2() : printf("zero entered.\n");
return 0;
}int f1(int n)
{
printf("%d ", n);return 0;
}
int f2(void)
{
printf("entered.\n");return 0;
}
Entering a 0 in this example calls the printf( ) function and displays the message zero
entered. If you enter any other number, both f1( ) and f2( ) execute. Note that the value
of the ?expression is discarded in this example. You don't need to assign it to anything.
A word of warning: Some C++ compilers rearrange the order of evaluation of an
expression in an attempt to optimize the object code. This could cause functions that
form the operands of the ?operator to execute in an unintended sequence.
Using the ?operator, you can rewrite the magic number program yet again.
/* Magic number program #5. */
#include <stdio.h>#include <stdlib.h>
int main(void)
{
int magic;int guess;Chapter 3: Statements 65THE FOUNDATION OF C++:
THE C SUBSET
66 C++: The Complete Reference
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
guess > magic ? printf("High") : printf("Low");
return 0;
}
Here, the ?operator displays the proper message based on the outcome of the test
guess > magic.
The Conditional Expression
Sometimes newcomers to C/C++ are confused by the fact that you can use any
valid expression to control the ifor the ?operator. That is, you are not restricted to
expressions involving the relational and logical operators (as is the case in languageslike BASIC or Pascal). The expression must simply evaluate to either a true or false(zero or nonzero) value. For example, the following program reads two integers fromthe keyboard and displays the quotient. It uses an ifstatement, controlled by the
second number, to avoid a divide-by-zero error.
/* Divide the first number by the second. */
#include <stdio.h>int main(void)
{
int a, b;
printf("Enter two numbers: ");
scanf("%d%d", &a, &b);
if(b) printf("%d\n", a/b);
else printf("Cannot divide by zero.\n");
return 0;
}
Chapter 3: Statements 67THE FOUNDATION OF C++:
THE C SUBSETThis approach works because if bis 0, the condition controlling the ifis false and the
else executes. Otherwise, the condition is true (nonzero) and the division takes place.
One other point: Writing the ifstatement as shown here
if(b != 0) printf("%d\n", a/b);
is redundant, potentially inefficient, and is considered bad style. Since the value of b
alone is sufficient to control the if, there is no need to test it against 0.
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.
When a match is found, the statements associated with that constant are executed. Thegeneral form of the switch statement is
switch (expression) {
case constant1:
statement sequencebreak;
case constant2:
statement sequencebreak;
case constant3:
statement sequencebreak;
…default
statement sequence
}
The expression must evaluate to a character or integer value. Floating-point expressions,
for example, are not allowed. The value of expression is tested, in order, against the
values of the constants specified in the case statements. 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 statement is executed if no
matches are found. The default is optional and, if it is not present, no action takes place
if all matches fail.
In C, a switch can have at least 257 case statements. 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. Although case is a label
statement, it cannot exist by itself, outside of a switch.
68 C++: The Complete Reference
The break statement is one of C/C++'s jump statements. You can use it in loops as
well as in the switch statement (see the section "Iteration Statements"). When break is
encountered in a switch, program execution "jumps" to the line of code following the
switch statement.
There are three important things to know about the switch statement:
șThe switch differs from the ifin that switch can only test for equality , whereas
ifcan evaluate any type of relational or logical expression.
șNo two case constants in the same switch can have identical values. Of course,
aswitch statement enclosed by an outer switch may have case constants that
are the same.
șIf character constants are used in the switch statement, they are automatically
converted to integers.
The switch statement is often used to process keyboard commands, such as menu
selection. 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. Check Spelling\n");
printf("2. Correct Spelling Errors\n");printf("3. Display Spelling Errors\n");printf("Strike Any Other Key to Skip\n");printf(" Enter your choice: ");
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;
default :
printf("No option selected");
}
}
Technically, the break statements inside the switch statement are optional. They
terminate the statement sequence associated with each constant. If the break statement
is omitted, execution will continue on into the next case's statements until either a break
or the end of the switch is reached. 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. */case 3:
flag = 0;break;
case 4:
flag = 1;
case 5:
error(flag);
break;
default:
process(i);
}
}
This example illustrates two aspects of switch. First, you can have case statements
that have no statement sequence associated with them. When this occurs, execution
simply drops through to the next case. In this example, the first three cases all execute
the same statements, which are
flag = 0;
break;Chapter 3: Statements 69THE FOUNDATION OF C++:
THE C SUBSET
70 C++: The Complete Reference
Second, execution of one statement sequence continues into the next case if no
break statement is present. If imatches 4, flag is set to 1 and, because there is no break
statement at the end of that case, execution continues and the call to error(flag) is executed.
Ifihad matched 5, error(flag) would have been called with a flag value of −1 (rather
than 1).
The fact that cases can run together when no break is present prevents the
unnecessary duplication of statements, resulting in more efficient code.
Nested switch Statements
You can have a switch as part of the statement sequence of an outer switch. Even if the
case constants of the inner and outer switch contain common values, no conflicts arise.
For example, the following code fragment is perfectly acceptable:
switch(x) {
case 1:
switch(y) {
case 0: printf("Divide by zero error.\n");
break;
case 1: process(x,y);
}
break;
case 2:
…
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. This condition may be predefined (as in the forloop), or open-ended (as in
thewhile and do-while loops).
The for Loop
The general design of the forloop is reflected in some form or another in all procedural
programming languages. However, in C/C++, it provides unexpected flexibilityand power.
The general form of the forstatement is
for(initialization; condition; increment) statement;
The forloop allows many variations, but its most common form works like this. The
initialization is an assignment statement that is used to set the loop control variable. The
Chapter 3: Statements 71THE FOUNDATION OF C++:
THE C SUBSETcondition is a relational expression that determines when the loop exits. The increment
defines how the loop control variable changes each time the loop is repeated. You must
separate these three major sections by semicolons. The forloop continues to execute
as long as the condition is true. Once the condition becomes false, program executionresumes on the statement following the for.
In the following program, a forloop is used to print the numbers 1 through 100 on
the screen:
#include <stdio.h>
int main(void)
{
int x;
for(x=1; x <= 100; x++) printf("%d ", x);return 0;
}
In the loop, xis initially set to 1 and then compared with 100. Since xis less than 100,
printf( ) is called and the loop iterates. This causes xto be increased by 1 and again tested
to see if it is still less than or equal to 100. If it is, printf( ) is called. This process repeats
until xis greater than 100, at which point the loop terminates. In this example, xis the
loop control variable, which is changed and checked each time the loop repeats.
The following example is a forloop 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 xand the call to printf( ) are executed until xequals 65. Note that
the loop is negative running: xis initialized to 100 and 5 is subtracted from it each time the
loop repeats.
Inforloops, the conditional test is always performed at the top of the loop. This
means that the code inside the loop may not be executed at all if the condition is false
to begin with. 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 */
72 C++: The Complete Reference
the loop will never execute because xand yare equal when the loop is entered. Because
this causes the conditional expression to evaluate to false, neither the body of the loop
nor the increment portion of the loop executes. Hence, ystill has the value 10, and the
only output produced by the fragment is the number 10 printed once on the screen.
for Loop Variations
The previous discussion described the most common form of the forloop. However,
several variations of the forare allowed that increase its power, flexibility, and
applicability to certain programming situations.
One of the most common variations uses the comma operator to allow two or
more variables to control the loop. (Remember, you use the comma operator to stringtogether a number of expressions in a "do this and this" fashion. See Chapter 2.) Forexample, the variables xand ycontrol the following loop, and both are initialized
inside the forstatement:
for(x=0, y=0; x+y<10; ++x) {
y = getchar();
y = y – '0'; /* subtract the ASCII code for 0
from y */
…
}
Commas separate the two initialization statements. Each time the loop repeats, xis
incremented and y's value is set by keyboard input. Both xand ymust be at the correct
value for the loop to terminate. Even though y's value is set by keyboard input, ymust
be initialized to 0 so that its value is defined before the first evaluation of the conditional
expression. (If ywere not defined, it could by chance contain the value 10, making the
conditional test false and preventing the loop from executing.)
The converge( ) function, shown next, demonstrates multiple loop control variables
in action. The converge( ) function copies the contents of one string into another by
moving characters from both ends, converging in the middle.
/* Demonstrate multiple loop control variables. */
#include <stdio.h>#include <string.h>
void converge(char *targ, char *src);int main(void)
{
Chapter 3: Statements 73THE FOUNDATION OF C++:
THE C SUBSETchar target[80] = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
converge(target, "This is a test of converge().");
printf("Final string: %s\n", target);
return 0;
}/* This function copies one string into another.
It copies characters to both the ends,
converging at the middle. */
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.
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXTXXXXXXXXXXXXXXXXXXXXXXXXXXXXThXXXXXXXXXXXXXXXXXXXXXXXXXX.ThiXXXXXXXXXXXXXXXXXXXXXXXX).ThisXXXXXXXXXXXXXXXXXXXXXX().This XXXXXXXXXXXXXXXXXXXXe().This iXXXXXXXXXXXXXXXXXXge().This isXXXXXXXXXXXXXXXXrge().This is XXXXXXXXXXXXXXerge().This is aXXXXXXXXXXXXverge().This is a XXXXXXXXXXnverge().This is a tXXXXXXXXonverge().This is a teXXXXXXconverge().This is a tesXXXX converge().This is a testXXf converge().This is a test of converge().Final string: This is a test of converge().
74 C++: The Complete Reference
Inconverge( ), the forloop uses two loop control variables, iand j, to index the
string from opposite ends. As the loop iterates, iis increased and jis decreased. The
loop stops when iis greater than j, thus ensuring that all characters are copied.
The conditional expression does not have to involve testing the loop control variable
against some target value. In fact, the condition may be any relational or logical
statement. This means that you can test for several possible terminating conditions.
For example, you could use the following function to log a user onto a remote
system. The user has three tries to enter the password. The loop terminates when thethree tries are used up or the user enters the correct password.
void sign_on(void)
{
char str[20] = "";int x;
for(x=0; x<3 && strcmp(str, "password"); ++x) {
printf("Enter password please:");
gets(str);
}
if(x==3) return;
/* else log user in … */
}
This function uses strcmp( ), the standard library function that compares two strings
and returns 0 if they match.
Remember, each of the three sections of the forloop may consist of any valid
expression. The expressions need not actually have anything to do with what the
sections are generally used for. With this in mind, consider the following example:
#include <stdio.h>
int sqrnum(int num);
int readnum(void);int prompt(void);
int main(void)
{
int t;
for(prompt(); t=readnum(); prompt())
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 forloop in main( ) . Notice that each part of the forloop is composed
of function calls that prompt the user and read a number entered from the keyboard. If
the number entered is 0, the loop terminates because the conditional expression will befalse. Otherwise, the number is squared. Thus, this forloop uses the initialization and
increment portions in a nontraditional but completely valid sense.
Another interesting trait of the forloop is that pieces of the loop definition need
not be there. In fact, there need not be an expression present for any of the sections—the expressions are optional. For example, this loop will run until the user enters 123:
for(x=0; x!=123; ) scanf("%d", &x);
Notice that the increment portion of the fordefinition is blank. This means that each
time the loop repeats, xis tested to see if it equals 123, but no further action takes place.
If you type 123at the keyboard, however, the loop condition becomes false and the
loop terminates.Chapter 3: Statements 75THE FOUNDATION OF C++:
THE C SUBSET
The initialization of the loop control variable can occur outside the forstatement.
This most frequently happens when the initial condition of the loop control variable
must be computed by some complex means as in this example:
gets(s); /* read a string into s */
if(*s) x = strlen(s); /* get the string's length */else x = 10;
for( ; x<10; ) {
printf("%d", x);
++x;
}
The initialization section has been left blank and xis initialized before the loop is entered.
The Infinite Loop
Although you can use any loop statement to create an infinite loop, foris traditionally
used for this purpose. Since none of the three expressions that form the forloop are
required, you can make an endless loop by leaving the conditional expression empty:
for( ; ; ) printf("This loop will run forever.\n");
When the conditional expression is absent, it is assumed to be true. You may have an
initialization and increment expression, but C++ programmers more commonly usethefor(;;) construct to signify an infinite loop.
Actually, the for(;;) construct does not guarantee an infinite loop because a break
statement, encountered anywhere inside the body of a loop, causes immediatetermination. (break is discussed in detail later in this chapter.) 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 Aat the keyboard.76 C++: The Complete Reference
for Loops with No Bodies
A statement may be empty . This means that the body of the forloop (or any other loop)
may also be empty . You can use this fact to improve the efficiency of certain algorithms
and to create time delay loops.
Removing spaces from an input stream is a common programming task. For example,
a database program may allow a query such as "show all balances less than 400." Thedatabase needs to have each word fed to it separately , without leading spaces. That is,the database input processor recognizes "show" but not " show". The following loop
shows one way to accomplish this. It advances past leading spaces in the string pointedto by str.
for( ; *str == ' '; str++) ;
As you can see, this loop has no body—and no need for one either.
Time delay loops are often used in programs. The following code shows how to create
one by using for:
for(t=0; t<SOME_VALUE; t++) ;
The while Loop
The second loop available in C/C++ is the while loop. Its general form is
while(condition) statement;
where statement is either an empty statement, a single statement, or a block of
statements. The condition may be any expression, and true is any nonzero value. The
loop iterates while the condition is true. When the condition becomes false, programcontrol passes to the line of code immediately following the loop.
The following example shows a keyboard input routine that simply loops until the
user types A:
char wait_for_char(void)
{
char ch;
ch = '\0'; /* initialize ch */
while(ch != 'A') ch = getchar();return ch;
}Chapter 3: Statements 77THE FOUNDATION OF C++:
THE C SUBSET
First, chis initialized to null. As a local variable, its value is not known when
wait_for_char( ) is executed. The while loop then checks to see if chis not equal to A.
Because chwas initialized to null, the test is true and the loop begins. Each time you
press a key, the condition is tested again. Once you enter an A, the condition becomes
false because chequals A, and the loop terminates.
Like forloops, while loops check the test condition at the top of the loop, which
means that the body of the loop will not execute if the condition is false to begin with.
This feature may eliminate the need to perform a separate conditional test before theloop. The pad( ) function provides a good illustration of this. It adds spaces to the end
of a string to fill the string to a predefined length. If the string is already at the desiredlength, no spaces are added.
#include <stdio.h>
#include <string.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. */
void pad(char *s, int length){
int l;
l = strlen(s); /* find out how long it is */while(l<length) {
s[l] = ' '; /* insert a space */
l++;
}s[l]= '\0'; /* strings need to be
terminated in a null */
}78 C++: The Complete Reference
The two arguments of pad( ) ares, a pointer to the string to lengthen, and length, the
number of characters that sshould have. If the length of string sis already equal to or
greater than length, the code inside the while loop does not execute. If sis shorter than
length, pad( ) adds the required number of spaces. The strlen( ) function, part of the
standard library, returns the length of the string.
If several separate conditions need to terminate a while loop, a single variable
commonly forms the conditional expression. The value of this variable is set at various
points throughout the loop. In this example,
void func1(void)
{
int working;
working = 1; /* i.e., true */while(working) {
working = process1();
if(working)
working = process2();
if(working)
working = process3();
}
}
any of the three routines may return false and cause the loop to exit.
There need not be any statements in the body of the while loop. For example,
while((ch=getchar()) != 'A') ;
will simply loop until the user types A. 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.
The do-while Loop
Unlike forand while loops, which test the loop condition at the top of the loop, the
do-while loop checks its condition at the bottom of the loop. This means that a do-while
loop always executes at least once. The general form of the do-while loop is
do {
statement;
} while(condition);Chapter 3: Statements 79THE FOUNDATION OF C++:
THE C SUBSET
80 C++: The Complete Reference
Although the curly braces are not necessary when only one statement is present, they
are usually used to avoid confusion (to you, not the compiler) with the while. The
do-while loop iterates until condition becomes false.
The following do-while loop will read numbers from the keyboard until it finds a
number less than or equal to 100.
do {
scanf("%d", &num);
} while(num > 100);
Perhaps the most common use of the do-while loop is in a menu selection function.
When the user enters a valid response, it is returned as the value of the function.Invalid responses cause a reprompt. The following code shows an improved versionof the spelling-checker menu developed earlier in this chapter:
void menu(void)
{
char ch;
printf("1. Check Spelling\n");
printf("2. Correct Spelling Errors\n");printf("3. 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 81THE FOUNDATION OF C++:
THE C SUBSETHere, the do-while loop is a good choice because you will always want a menu function
to display the menu at least once. After the options have been displayed, the program
will loop until a valid option is selected.
Declaring Variables within Selection
and Iteration Statements
In C++ (but not C89), it is possible to declare a variable within the conditional expression
of an iforswitch, within the conditional expression of a while loop, or within the
initialization portion of a forloop. A variable declared in one of these places has its
scope limited to the block of code controlled by that statement. For example, a variabledeclared within a forloop will be local to that loop.
Here is an example that declares a variable within the initialization portion of a
forloop:
/* i is local to for loop; j is known outside loop. */
int j;for(int i = 0; i<10; i++)
j = i * i;
/* i = 10; // *** Error *** – i not known here! */
Here, iis declared within the initialization portion of the forand is used to control the
loop. Outside the loop, iis unknown.
Since often a loop control variable in a foris needed only by that loop, the declaration
of the variable in the initialization portion of the foris becoming common practice.
Remember, however, that this is not supported by C89. (This restriction was removed
from C by C99.)
Whether a variable declared within the initialization portion of a forloop is local to that
loop has changed over time. Originally, the variable was available after the for. However ,
Standard C++ restricts the variable to the scope of the forloop as just described.
If your compiler fully complies with Standard C++, then you can also declare a
variable within any conditional expression, such as those used by the ifor a while. For
example, this fragment,
if(int x = 20) {
x = x – y;
82 C++: The Complete Reference
if(x>10) y = 0;
}
declares xand assigns it the value 20. Since this is a true value, the target of the if
executes. Variables declared within a conditional statement have their scope limited
to the block of code controlled by that statement. Thus, in this case, xis not known
outside the if. Frankly , not all programmers believe that declaring variables within
conditional statements is good practice, and this technique will not be used inthis book.
Jump Statements
C/C++ has four statements that perform an unconditional branch: return, goto, break,
and continue. Of these, you may use return and goto anywhere in your program. You
may use the break and continue statements in conjunction with any of the loop
statements. As discussed earlier in this chapter, you can also use break with switch.
The return Statement
The return statement is used to return from a function. It is categorized as a jump
statement because it causes execution to return (jump back) to the point at which thecall to the function was made. A return may or may not have a value associated with
it. If return has a value associated with it, that value becomes the return value of the
function. In C89, a non-void function does not technically have to return a value. If no
return value is specified, a garbage value is returned. However, in C++ (and in C99),a non-void function must return a value. 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.
(Even in C89, if a function is declared as returning a value, it is good practice toactually return one!)
The general form of the return statement is
return expression;
The expression is present only if the function is declared as returning a value. In this
case, the value of expression will become the return value of the function.
You can use as many return statements as you like within a function. However,
the function will stop executing as soon as it encounters the first return. The }that ends
a function also causes the function to return. It is the same as a return without any
specified value. If this occurs within a non-void function, then the return value of the
function is undefined.
A function declared as void may not contain a return statement that specifies a
value. Since a void function has no return value, it makes sense that no return
statement within a void function can return a value.
See Chapter 6 for more information on return.
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. Most programmers' chief concern
about the goto is its tendency to render programs unreadable. Nevertheless, although
thegoto statement fell out of favor some years ago, it occasionally has its uses. There
are no programming situations that require goto. Rather, it is a convenience, which,
if used wisely , can be a benefit in a narrow set of programming situations, such asjumping out of a set of deeply nested loops. The goto is not used outside of this section.
The goto statement requires a label for operation. (A label is a valid identifier
followed by a colon.) Furthermore, the label must be in the same function as the goto
that uses it—you cannot jump between functions. The general form of the goto
statement is
goto label;
…label:
where label is any valid label either before or after goto. For example, you could create a
loop from 1 to 100 using the goto and a label, as shown here:
x = 1;
loop1:
x++;if(x<100) goto loop1;
The break Statement
The break statement has two uses. You can use it to terminate a case in the switch
statement (covered in the section on switch earlier in this chapter). You can also use it
to force immediate termination of a loop, bypassing the normal loop conditional test.
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. For
example,
#include <stdio.h>
int main(void)
{
int t;Chapter 3: Statements 83THE FOUNDATION OF C++:
THE C SUBSET
for(t=0; t<100; t++) {
printf("%d ", t);
if(t==10) break;
}
return 0;
}
prints the numbers 0 through 10 on the screen. Then the loop terminates because break
causes immediate exit from the loop, overriding the conditional test t<100.
Programmers often use the break statement in loops in which a special condition
can cause immediate termination. For example, here a keypress can stop the execution
of the look_up( ) function:
void look_up(char *name)
{
do {
/* look up names … */if(kbhit()) break;
} while(!found);/* process match */
}
The kbhit( ) function returns 0 if you do not press a key. Otherwise, it returns a
nonzero value. 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.
Abreak causes an exit from only the innermost loop. For example,
for(t=0; t<100; ++t) {
count = 1;
for(;;) {
printf("%d ", count);count++;if(count==10) break;
}
}
prints the numbers 1 through 10 on the screen 100 times. Each time execution encounters
break, control is passed back to the outer forloop.84 C++: The Complete Reference
Chapter 3: Statements 85THE FOUNDATION OF C++:
THE C SUBSETAbreak used in a switch statement will affect only that switch. It does not affect
any loop the switch happens to be in.
The exit( ) Function
Although exit( ) is not a program control statement, a short digression that discusses
it is in order at this time. Just as you can break out of a loop, you can break out of a
program by using the standard library function exit( ). This function causes immediate
termination of the entire program, forcing a return to the operating system. In effect,theexit( ) function acts as if it were breaking out of the entire program.
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. Zero is generally used as a return code to indicate normal program termination.Other arguments are used to indicate some sort of error. You can also use the macrosEXIT_SUCCESS and EXIT_FAILURE for the return_code. The exit( ) function requires
the header stdlib.h. A C++ program may also use the C++-style header <cstdlib>.
Programmers frequently use exit( ) when a mandatory condition for program
execution is not satisfied. For example, imagine a virtual reality computer game thatrequires a special graphics adapter. The main( ) function of this game might look
like this:
#include <stdlib.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. If the adapter is not in the system, virtual_graphics( )
returns false and the program terminates.
As another example, this version of menu( ) uses exit( ) to quit the program and
return to the operating system:
void menu(void){
86 C++: The Complete Reference
char ch;
printf("1. Check Spelling\n");
printf("2. Correct Spelling Errors\n");printf("3. Display Spelling Errors\n");printf("4. Quit\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;
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. Instead of forcing
termination, however, continue forces the next iteration of the loop to take place,
skipping any code in between. For the forloop, continue causes the conditional test
and increment portions of the loop to execute. For the while and do-while loops,
program control passes to the conditional tests. For example, the following program
counts the number of spaces contained in the string entered by the user:
/* Count spaces */
#include <stdio.h>
int main(void)
{
char s[80], *str;
int space;
printf("Enter a string: ");
gets(s);str = s;
for(space=0; *str; str++) {
if(*str != ' ') continue;
space++;
}printf("%d spaces\n", space);
return 0;
}
Each character is tested to see if it is a space. If it is not, the continue statement forces
theforto iterate again. If the character isa space, space is incremented.
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. For
example, an Abecomes a B. The function will terminate when you type a $. After a $
has been input, no further output will occur because the conditional test, brought intoeffect by continue, will find done to be true and will cause the loop to exit.Chapter 3: Statements 87THE FOUNDATION OF C++:
THE C SUBSET
Expression Statements
Chapter 2 covered expressions thoroughly. However, a few special points are mentioned
here. Remember, an expression statement is simply a valid expression followed by asemicolon, as in
func(); /* a function call */
a = b+c; /* an assignment statement */b+f(); /* a valid, but strange statement */; /* an empty statement */
The first expression statement executes a function call. The second is an assignment.
The third expression, though strange, is still evaluated by the C++ compiler andthe function f( )is called. The final example shows that a statement can be empty
(sometimes called a null statement).
Block Statements
Block statements are simply groups of related statements that are treated as a unit. Thestatements that make up a block are logically bound together. Block statements are alsocalled compound statements. A block is begun with a {and terminated by its matching }.
Programmers use block statements most commonly to create a multistatement targetfor some other statement, such as if. However, you may place a block statement
anywhere you would put any other statement. For example, this is perfectly valid(although unusual) C/C++ code:
#include <stdio.h>
int main(void)
{
int i;
{ /* a block statement */
i = 120;
printf("%d", i);
}
return 0;
}88 C++: The Complete Reference
Chapter 4
Arrays and
Null-Terminated Strings
89
Copyright © 2003 by The McGraw-Hill Companies. Click here for terms of use.
90 C++: The Complete Reference
Anarray is a collection of variables of the same type that are referred to through
a common name. A specific element in an array is accessed by an index.
In C/C++, all arrays consist of contiguous memory locations. The lowest
address corresponds to the first element and the highest address to the last element.
Arrays may have from one to several dimensions. The most common array is thenull-terminated string, which is simply an array of characters terminated by a null.
Arrays and pointers are closely related; a discussion of one usually refers to the
other. This chapter focuses on arrays, while Chapter 5 looks closely at pointers. Youshould read both to understand fully these important constructs.
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 allocatespace for them in memory. Here, type declares the base type of the array, which is the
type of each element in the array, and sizedefines how many elements the array will
hold. 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. This is done by placing the
index of the element within square brackets after the name of the array. For example,
balance[3] = 12.23;
assigns element number 3 in balance the value 12.23.
In C/C++, all arrays have 0 as the index of their first element. Therefore, when
you write
char p[10];
you are declaring a character array that has ten elements, p[0] through p[9]. For example,
the following program loads an integer array with the numbers 0 through 99:
#include <stdio.h>
int main(void)
{
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. For a single-dimension array, the total size in bytes is computed as shown here:
total bytes = sizeof(base type) x size of array
C/C++ has no bounds checking on arrays. You could overwrite either end of an
array and write into some other variable's data or even into the program's code. As the
programmer, it is your job to provide bounds checking where needed. For example,this code will compile without error, but is incorrect because the forloop will cause
the array count to be overrun.
int count[10], i;
/* this causes count to be overrun */
for(i=0; i<100; i++) count[i] = i;
Single-dimension arrays are essentially lists of information of the same type that
are stored in contiguous memory locations in index order. For example, Figure 4-1
shows how array aappears in memory if it starts at memory location 1000 and is
declared as shown here:
char a[7];Chapter 4: Arrays and Null-Terminated Strings 91THE FOUNDATION OF C++:
THE C SUBSET
Element a[0] a[1] a[2] a[3] a[4] a[5] a[6]Address 1000 1001 1002 1003 1004 1005 1006
Figure 4-1. A seven-element character array beginning at location 1000
Generating a Pointer to an Array
You can generate a pointer to the first element of an array by simply specifying the
array name, without any index. For example, given
int sample[10];
you can generate a pointer to the first element by using the name sample. Thus, the
following program fragment assigns pthe address of the first element of sample:
int *p;
int sample[10];
p = sample;
You can also specify the address of the first element of an array using the &operator.
For example, sample and &sample[0] both produce the same results. However, in
professionally written C/C++ code, you will almost never see &sample[0].
Passing Single-Dimension Arrays to Functions
In C/C++, you cannot pass an entire array as an argument to a function. You can,
however, pass to the function a pointer to an array by specifying the array's namewithout an index. For example, the following program fragment passes the addressofitofunc1( ):
int main(void)
{
int i[10];
func1(i);
…
}
If a function receives a single-dimension array, you may declare its formal parameter
in one of three ways: as a pointer, as a sized array, or as an unsized array. For example,
to receive i, a function called func1( ) can be declared as
void func1(int *x) /* pointer */
{92 C++: The Complete Reference
.
..
}
or
void func1(int x[10]) /* sized array */
{
…
}
or finally as
void func1(int x[]) /* unsized array */{
…
}
All three declaration methods produce similar results because each tells the
compiler that an integer pointer is going to be received. The first declaration actually
uses a pointer. The second employs the standard array declaration. In the final version,a modified version of an array declaration simply specifies that an array of type intof
some length is to be received. As you can see, the length of the array doesn't matter asfar as the function is concerned because C/C++ performs no bounds checking. In fact,as far as the compiler is concerned,
void func1(int x[32])
{
…
}
also works because the compiler generates code that instructs func1( ) to receive
a pointer—it does not actually create a 32-element array.Chapter 4: Arrays and Null-Terminated Strings 93THE FOUNDATION OF C++:
THE C SUBSET
Null-Terminated Strings
By far the most common use of the one-dimensional array is as a character string.
C++ supports two types of strings. The first is the null-terminated string, which is a
null-terminated character array. (A null is zero.) Thus a null-terminated string containsthe characters that comprise the string followed by a null. This is the only type of stringdefined by C, and it is still the most widely used. Sometimes null-terminated stringsare called C-strings. C++ also defines a string class, called string, which provides an
object-oriented approach to string handling. It is described later in this book. Here,null-terminated strings are examined.
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. Forexample, to declare an array strthat can hold a 10-character string, you would write
char str[11];
This makes room for the null at the end of the string.
When you use a quoted string constant in your program, you are also creating a
null-terminated string. A string constant is a list of characters enclosed in double quotes.
For example,
"hello there"
You do not need to add the null to the end of string constants manually—the compilerdoes this for you automatically.
C/C++ supports a wide range of functions that manipulate null-terminated strings.
The most common are
Name Function
strcpy(s1, s2) Copies s2into s1.
strcat(s1, s2) Concatenates s2onto the end of s1.
strlen(s1) Returns the length of s1.
strcmp(s1, s2) Returns 0 if s1and s2are the same; less than 0 if s1<s2;
greater than 0 if s1>s2.
strchr(s1, ch) Returns a pointer to the first occurrence of chins1.
strstr(s1, s2) Returns a pointer to the first occurrence of s2ins1.
These functions use the standard header file string.h. (C++ programs can also use the
C++-style header <cstring>.) The following program illustrates the use of these string
functions:94 C++: The Complete Reference
#include <stdio.h>
#include <string.h>
int main(void)
{
char s1[80], s2[80];
gets(s1);
gets(s2);
printf("lengths: %d %d\n", strlen(s1), strlen(s2));if(!strcmp(s1, s2)) printf("The strings are equal\n");strcat(s1, s2);
printf("%s\n", s1);
strcpy(s1, "This is a test.\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 equalhellohelloThis is a test.e is in hellofound hi
Remember, strcmp( ) returns false if the strings are equal. Be sure to use the logical
operator !to reverse the condition, as just shown, if you are testing for equality.
Although C++ defines a string class, null-terminated strings are still widely used
in existing programs. They will probably stay in wide use because they offer a high
level of efficiency and afford the programmer detailed control of string operations.However, for many simple string-handling chores, C++'s string class providesa convenient alternative.THE FOUNDATION OF C++:
THE C SUBSETChapter 4: Arrays and Null-Terminated Strings 95
Two-Dimensional Arrays
C/C++ supports multidimensional arrays. The simplest form of the multidimensional
array is the two-dimensional array. A two-dimensional array is, essentially, an array ofone-dimensional arrays. To declare a two-dimensional integer array dof size 10,20, you
would write
int d[10][20];
Pay careful attention to the declaration. Some other computer languages use commasto separate the array dimensions; C/C++, in contrast, places each dimension in its ownset of brackets.
Similarly, to access point 1,2 of array d, you would use
d[1][2]
The following example loads a two-dimensional array with the numbers 1 through 12and prints them row by row.
#include <stdio.h>
int main(void)
{
int t, i, num[3][4];
for(t=0; t<3; ++t)
for(i=0; i<4; ++i)
num[t][i] = (t*4)+i+1;
/* now print them out */
for(t=0; t<3; ++t) {
for(i=0; i<4; ++i)
printf("%3d ", num[t][i]);
printf("\n");
}
return 0;
}
In this example, num[0][0] has the value 1, num[0][1] the value 2, num[0][2] the value 3,
and so on. The value of num[2][3] will be 12. You can visualize the num array as
shown here:96 C++: The Complete Reference
Two-dimensional arrays are stored in a row-column matrix, where the first index
indicates the row and the second indicates the column. This means that the rightmost
index changes faster than the leftmost when accessing the elements in the array inthe order in which they are actually stored in memory. See Figure 4-2 for a graphicrepresentation of a two-dimensional array 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.THE FOUNDATION OF C++:
THE C SUBSETChapter 4: Arrays and Null-Terminated Strings 97
Figure 4-2. A two-dimensional array in memory
When a two-dimensional array is used as an argument to a function, only a pointer
to the first element is actually passed. However, the parameter receiving a two-dimensional
array must define at least the size of the rightmost dimension. (You can specify the leftdimension if you like, but it is not necessary.) The rightmost dimension is neededbecause the compiler must know the length of each row if it is to index the arraycorrectly. For example, a function that receives a two-dimensional integer array withdimensions 10,10 is declared like this:
void func1(int x[][10])
{
…
}
The compiler needs to know the size of the right dimension in order to correctly
execute expressions such as
x[2][4]
inside the function. If the length of the rows is not known, the compiler cannot determinewhere the third row begins.
The following short program uses a two-dimensional array to store the numeric
grade for each student in a teacher's classes. The program assumes that the teacher hasthree classes and a maximum of 30 students per class. Notice the way the array grade
is accessed by each of the functions.
/* A simple student grades database. */
#include <stdio.h>#include <ctype.h>#include <stdlib.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]);98 C++: The Complete Reference
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. */
void enter_grades(void){
int t, i;
for(t=0; t<CLASSES; t++) {
printf("Class # %d:\n", t+1);
for(i=0; i<GRADES; ++i)
grade[t][i] = get_grade(i);
}
}
/* Read a grade. */
int get_grade(int num)Chapter 4: Arrays and Null-Terminated Strings 99THE FOUNDATION OF C++:
THE C SUBSET
100 C++: The Complete Reference
{
char s[80];
printf("Enter grade for student # %d:\n", num+1);
gets(s);return(atoi(s));
}
/* Display grades. */
void disp_grades(int g[][GRADES]){
int t, i;
for(t=0; t<CLASSES; ++t) {
printf("Class # %d:\n", t+1);
for(i=0; i<GRADES; ++i)
printf("Student #%d is %d\n", i+1, g[t][i]);
}
}
Arrays of Strings
It is not uncommon in programming to use an array of strings. For example, the input
processor to a database may verify user commands against an array of valid commands.To create an array of null-terminated strings, use a two-dimensional character array.The size of the left index determines the number of strings and the size of the rightindex specifies the maximum length of each string. The following code declares an arrayof 30 strings, each with a maximum length of 79 characters, plus the null terminator.
char str_array[30][80];
It is easy to access an individual string: You simply specify only the left index.
For example, the following statement calls gets( ) with the third string in str_array .
gets(str_array[2]);
The preceding statement is functionally equivalent to
gets(&str_array[2][0]);
but the first of the two forms is much more common in professionally writtenC/C++ code.
THE FOUNDATION OF C++:
THE C SUBSETTo 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. */
#include <stdio.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.\n");for(t=0; t<MAX; t++) {
printf("%d: ", t);
gets(text[t]);if(!*text[t]) break; /* quit on blank line */
}
for(i=0; i<t; i++) {
for(j=0; text[i][j]; j++) putchar(text[i][j]);
putchar('\n');
}
return 0;
}
This program inputs lines of text until a blank line is entered. Then it redisplays
each line one character at a time.
Multidimensional Arrays
C/C++ allows arrays of more than two dimensions. The exact limit, if any, isdetermined by your compiler. The general form of a multidimensional arraydeclaration is
type name[Size1][Size2][Size3]. . .[SizeN];Chapter 4: Arrays and Null-Terminated Strings 101
102 C++: The Complete Reference
Arrays of more than three dimensions are not often used because of the amount
of memory they require. For example, a four-dimensional character array withdimensions 10,6,9,4 requires
10 * 6 * 9 * 4
or 2,160 bytes. If the array held 2-byte integers, 4,320 bytes would be needed. If thearray held doubles (assuming 8 bytes per double), 17,280 bytes would be required.
The storage required increases exponentially with the number of dimensions. Forexample, if a fifth dimension of size 10 was added to the preceding array, then 172,800 bytes would be required.
In multidimensional arrays, it takes the computer time to compute each index.
This means that accessing an element in a multidimensional array can be slower thanaccessing an element in a single-dimension array.
When passing multidimensional arrays into functions, you must declare all but
the leftmost dimension. For example, if you declare array mas
int m[4][3][6][5];
a function, func1( ), that receives m, would look like this:
void func1(int d[][3][6][5])
{
…
}
Of course, you can include the first dimension if you like.
Indexing Pointers
In C/C++, pointers and arrays are closely related. As you know, an array name
without an index is a pointer to the first element in the array. For example, considerthe following array.
char p[10];
The following statements are identical:
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.
As stated, an array name without an index generates a pointer. Conversely, a
pointer can be indexed as if it were declared to be an array. For example, considerthis program fragment:
int *p, i[10];
p = i;p[5] = 100; /* assign using index */*(p+5) = 100; /* assign using pointer arithmetic */
Both assignment statements place the value 100 in the sixth element of i. The first
statement indexes p; the second uses pointer arithmetic. Either way, the result is the
same. (Chapter 5 discusses pointers and pointer arithmetic.)
This same concept also applies to arrays of two or more dimensions. For example,
assuming that ais a 10-by-10 integer array, these two statements are equivalent:
a&a[0][0]
Furthermore, the 0,4 element of amay be referenced two ways: either by array indexing,
a[0][4], or by the pointer, *((int *)a+4). Similarly, element 1,2 is either a[1][2] or
*((int *)a+12). 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. Pointers are sometimes used to accessarrays because pointer arithmetic is often faster than array indexing.
A two-dimensional array can be reduced to a pointer to an array of one-dimensional
arrays. Therefore, using a separate pointer variable is one easy way to use pointersto access elements within a row of a two-dimensional array. The following functionillustrates this technique. It will print the contents of the specified row for the globalinteger array num:
int num[10][10];
.Chapter 4: Arrays and Null-Terminated Strings 103THE FOUNDATION OF C++:
THE C SUBSET
.
.
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<row_dimension; ++t)
printf("%d ", *(p+t));
}
.
..
void f(void){
int num[10][10];
pr_row(0, 10, (int *) num); /* print first row */
}
Arrays of greater than two dimensions may be reduced in a similar way. 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. Generally, an n-dimensional
array can be reduced to a pointer and an (n-1)-dimensional array. This new array can be
reduced again with the same method. The process ends when a single-dimension arrayis produced.104 C++: The Complete Reference
Chapter 4: Arrays and Null-Terminated Strings 105THE FOUNDATION OF C++:
THE C SUBSET Array Initialization
C/C++ allows the initialization of arrays at the time of their declaration. The general
form of array initialization is similar to that of other variables, as shown here:
type_specifier array_name[size1]. . .[sizeN] = { value_list };
The value_list is a comma-separated list of values whose type is compatible with
type_specifier . The first value is placed in the first position of the array, the second
value in the second position, and so on. Note that a semicolon follows the }.
In the following example, a 10-element integer array is initialized with the numbers
1 through 10:
int i[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
This means that i[0]will have the value 1 and i[9]will have the value 10.
Character arrays that hold strings allow a shorthand initialization that takes
the form:
char array_name[size] = "string";
For example, this code fragment initializes strto the phrase "I like C++".
char str[11] = "I like C++";
This is the same as writing
char str[11] = {'I', ' ', 'l', 'i', 'k', 'e',' ', 'C',
'+', '+', '\0'};
Because null-terminated strings end with a null, you must make sure that the arrayyou declare is long enough to include the null. This is why stris 11 characters long
even though "I like C++" is only 10. When you use the string constant, the compilerautomatically supplies the null terminator.
Multidimensional arrays are initialized the same as single-dimension ones. For
example, the following initializes sqrs with the numbers 1 through 10 and their
squares.
int sqrs[10][2] = {
1, 1,
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. This is called subaggregate grouping. For example, here
is another way to write the preceding declaration.
int sqrs[10][2] = {
{1, 1},
{2, 4},{3, 9},{4, 16},{5, 25},{6, 36},{7, 49},{8, 64},{9, 81},{10, 100}
};
When using subaggregate grouping, if you don't supply enough initializers for
a given group, the remaining members will be set to zero automatically.
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. Fortunately , you can let the compiler106 C++: The Complete Reference
automatically calculate the dimensions of the arrays. If, in an array initialization
statement, the size of the array is not specified, the C/C++ compiler automaticallycreates an array big enough to hold all the initializers present. This is called an unsized
array . 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.
Unsized array initializations are not restricted to one-dimensional arrays. For
multidimensional arrays, you must specify all but the leftmost dimension. (The otherdimensions are needed to allow the compiler to index the array properly.) In this way,you may build tables of varying lengths and the compiler automatically allocatesenough storage for them. For example, the declaration of sqrs as an unsized array
is shown here:
int sqrs[][2] = {
{1, 1},
{2, 4},{3, 9},{4, 16},{5, 25},{6, 36},{7, 49},{8, 64},{9, 81},{10, 100}
};
The advantage of this declaration over the sized version is that you may lengthen or
shorten the table without changing the array dimensions.Chapter 4: Arrays and Null-Terminated Strings 107THE FOUNDATION OF C++:
THE C SUBSET
A Tic-Tac-Toe Example
The longer example that follows illustrates many of the ways that you can manipulate
arrays with C/C++. This section develops a simple tic-tac-toe program. Two-dimensionalarrays are commonly used to simulate board game matrices.
The computer plays a very simple game. When it is the computer's turn, it uses
get_computer_move( ) to scan the matrix, looking for an unoccupied cell. When it
finds one, it puts an Othere. If it cannot find an empty location, it reports a draw
game and exits. The get_player_move( ) function asks you where you want to place
anX.The upper-left corner is location 1,1; the lower-right corner is 3,3.
The matrix array is initialized to contain spaces. Each move made by the player
or the computer changes a space into either an X or an O. This makes it easy to displaythe matrix on the screen.
Each time a move has been made, the program calls the check( ) function. This
function returns a space if there is no winner yet, an X if you have won, or an O if thecomputer has won. It scans the rows, the columns, and then the diagonals, looking forone that contains either all X's or all O's.
The disp_matrix( ) function displays the current state of the game. Notice how
initializing the matrix with spaces simplified this function.
The routines in this example all access the matrix array differently. Study them to
make sure that you understand each array operation.
/* A simple Tic Tac Toe game. */
#include <stdio.h>#include <stdlib.h>
char matrix[3][3]; /* the tic tac toe matrix */char check(void);
void init_matrix(void);void get_player_move(void);void get_computer_move(void);void disp_matrix(void);
int main(void)
{
char done;
printf("This is the game of Tic Tac Toe.\n");
printf("You will be playing against the computer.\n");
done = ' ';108 C++: The Complete Reference
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 init_matrix(void){
int i, j;
for(i=0; i<3; i++)
for(j=0; j<3; j++) matrix[i][j] = ' ';
}/* Get a player's move. */
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.\n");
get_player_move();
}else matrix[x][y] = 'X';
}Chapter 4: Arrays and Null-Terminated Strings 109THE FOUNDATION OF C++:
THE C SUBSET
/* Get a move from the computer. */
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. */
void disp_matrix(void){
int t;
for(t=0; t<3; t++) {
printf(" %c | %c | %c ",matrix[t][0],
matrix[t][1], matrix [t][2]);
if(t!=2) printf("\n–|–|–\n");
}
printf("\n");
}
/* See if there is a winner. */
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] &&110 C++: The Complete Reference
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 ' ';
}Chapter 4: Arrays and Null-Terminated Strings 111THE FOUNDATION OF C++:
THE C SUBSET
This page intentionally left blank
Chapter 5
Pointers
113
Copyright © 2003 by The McGraw-Hill Companies. Click here for terms of use.
The correct understanding and use of pointers is critical to successful C/C++
programming. There are three reasons for this: First, pointers provide the meansby which functions can modify their calling arguments. Second, pointers support
dynamic allocation. Third, pointers can improve the efficiency of certain routines. Also,as you will see in Part Two, pointers take on additional roles in C++.
Pointers are one of the strongest but also one of the most dangerous features in
C/C++. For example, uninitialized pointers (or pointers containing invalid values)can cause your system to crash. Perhaps worse, it is easy to use pointers incorrectly,causing bugs that are very difficult to find.
Because of both their importance and their potential for abuse, this chapter examines
the subject of pointers in detail.
What Are Pointers?
Apointer is a variable that holds a memory address. This address is the location of
another object (typically another variable) in memory. For example, if one variablecontains the address of another variable, the first variable is said to point to the second.
Figure 5-1 illustrates this situation.114 C++: The Complete Reference
Figure 5-1. One variable points to another
Chapter 5: Pointers 115THE FOUNDATION OF C++:
THE C SUBSET Pointer Variables
If a variable is going to hold a pointer, it must be declared as such. A pointer declaration
consists of a base type, an *, and the variable name. The general form for declaring apointer variable is
type *name;
where type is the base type of the pointer and may be any valid type. The name of
the pointer variable is specified by name.
The base type of the pointer defines what type of variables the pointer can point to.
Technically, any type of pointer can point anywhere in memory. However, all pointerarithmetic is done relative to its base type, so it is important to declare the pointer correctly.(Pointer arithmetic is discussed later in this chapter.)
The Pointer Operators
The pointer operators were discussed in Chapter 2. We will take a closer look at themhere, beginning with a review of their basic operation. There are two special pointeroperators: *and &. The &is a unary operator that returns the memory address of
its operand. (Remember, a unary operator only requires one operand.) For example,
m = &count;
places into mthe memory address of the variable count. This address is the computer's
internal location of the variable. It has nothing to do with the value of count. You can
think of &as returning "the address of." Therefore, the preceding assignment statement
means "m receives the address of count."
To understand the above assignment better, assume that the variable count uses
memory location 2000 to store its value. Also assume that count has a value of 100.
Then, after the preceding assignment, mwill have the value 2000.
The second pointer operator, *, is the complement of &. It is a unary operator that
returns the value located at the address that follows. For example, if mcontains the
memory address of the variable count,
q = *m;
places the value of count into q. Thus, qwill have the value 100 because 100 is stored
at location 2000, which is the memory address that was stored in m. You can think of
*as "at address." In this case, the preceding statement means "q receives the value at
address m."
116 C++: The Complete Reference
Both &and *have a higher precedence than all other arithmetic operators except
the unary minus, with which they are equal.
You must make sure that your pointer variables always point to the correct type of
data. 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. Because you can assign any address you want to a pointer variable, thefollowing program compiles without error, but does not produce the desired result:
#include <stdio.h>
int main(void)
{
double x = 100.1, y;int *p;
/* The next statement causes p (which is an
integer pointer) to point to a double. */
p = (int *)&x;/* The next statement does not operate as
expected. */
y = *p;
printf("%f", y); /* won't output 100.1 */
return 0;
}
This will not assign the value of xtoy. Because pis declared as an integer pointer,
only 4 bytes of information (assuming 4-byte integers) will be transferred to y, not
the 8 bytes that normally make up a double.
In C++, it is illegal to convert one type of pointer into another without the use of an
explicit type cast. In C, casts should be used for most pointer conversions.
Pointer Expressions
In general, expressions involving pointers conform to the same rules as otherexpressions. This section examines a few special aspects of pointer expressions.
Chapter 5: Pointers 117THE FOUNDATION OF C++:
THE C SUBSETPointer Assignments
As with any variable, you may use a pointer on the right-hand side of an assignment
statement to assign its value to another pointer. For example,
#include <stdio.h>
int main(void)
{
int x;int *p1, *p2;
p1 = &x;
p2 = p1;
printf(" %p", p2); /* print the address of x, not x's value! */return 0;
}
Both p1and p2now point to x. The address of xis displayed by using the %p printf( )
format specifier, which causes printf( ) to display an address in the format used by the
host computer.
Pointer Arithmetic
There are only two arithmetic operations that you may use on pointers: addition
and subtraction. To understand what occurs in pointer arithmetic, let p1be an
integer pointer with a current value of 2000. Also, assume integers are 2 bytes long.After the expression
p1++;
p1contains 2002, not 2001. The reason for this is that each time p1is incremented, it
will point to the next integer. The same is true of decrements. For example, assumingthat p1has the value 2000, the expression
p1–;
causes p1to have the value 1998.
Generalizing from the preceding example, the following rules govern pointer
arithmetic. Each time a pointer is incremented, it points to the memory location
of the next element of its base type. Each time it is decremented, it points to the
location of the previous element. When applied to character pointers, this willappear as "normal" arithmetic because characters are always 1 byte long. All otherpointers will increase or decrease by the length of the data type they point to. Thisapproach ensures that a pointer is always pointing to an appropriate element of itsbase type. Figure 5-2 illustrates this concept.
You are not limited to the increment and decrement operators. For example, you
may add or subtract integers to or from pointers. The expression
p1 = p1 + 12;
makes p1point to the twelfth element of p1's type beyond the one it currently points to.
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 tofind the number of objects of their base type that separate the two. All other arithmeticoperations are prohibited. Specifically , you may not multiply or divide pointers; youmay not add two pointers; you may not apply the bitwise operators to them; andyou may not add or subtract type float ordouble to or from pointers.118 C++: The Complete Reference
Figure 5-2. All pointer arithmetic is relative to its base type (assume 2-byte
integers)
Chapter 5: Pointers 119THE FOUNDATION OF C++:
THE C SUBSETPointer Comparisons
You can compare two pointers in a relational expression. For instance, given two
pointers pand q, the following statement is perfectly valid:
if(p<q) printf("p points to lower memory than q\n");
Generally, pointer comparisons are used when two or more pointers point to
a common object, such as an array. As an example, a pair of stack routines aredeveloped that store and retrieve integer values. A stack is a list that uses first-in,last-out accessing. It is often compared to a stack of plates on a table—the firstone set down is the last one to be used. Stacks are used frequently in compilers,interpreters, spreadsheets, and other system-related software. To create a stack,you need two functions: push( ) and pop( ). The push( ) function places values on
the stack and pop( ) takes them off. These routines are shown here with a simple
main( ) function to drive them. The program puts the values you enter into the stack.
If you enter 0, a value is popped from the stack. To stop the program, enter
1.
#include <stdio.h>
#include <stdlib.h>
#define SIZE 50void 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);
120 C++: The Complete Reference
return 0;
}
void push(int i)
{
p1++;
if(p1==(tos+SIZE)) {
printf("Stack Overflow.\n");
exit(1);
}*p1 = i;
}
int pop(void)
{
if(p1==tos) {
printf("Stack Underflow.\n");exit(1);
}p1–;return *(p1+1);
}
You can see that memory for the stack is provided by the array stack. The pointer
p1is set to point to the first element in stack. The p1variable accesses the stack. The
variable tosholds the memory address of the top of the stack. It is used to prevent
stack overflows and underflows. Once the stack has been initialized, push( ) and
pop( ) may be used. Both the push( ) and pop( ) functions perform a relational test
on the pointer p1to detect limit errors. In push( ), p1is tested against the end of
stack by adding SIZE (the size of the stack) to tos. This prevents an overflow. In
pop( ), p1is checked against tosto be sure that a stack underflow has not occurred.
Inpop( ), the parentheses are necessary in the return statement. Without them, the
statement would look like this:
return *p1 +1;
which would return the value at location p1plus one, not the value of the location p1+1.
Chapter 5: Pointers 121THE FOUNDATION OF C++:
THE C SUBSET Pointers and Arrays
There is a close relationship between pointers and arrays. Consider this program
fragment:
char str[80], *p1;
p1 = str;
Here, p1has been set to the address of the first array element in str. To access the fifth
element in str, you could write
str[4]
or
*(p1+4)
Both statements will return the fifth element. Remember, arrays start at 0. To access
the fifth element, you must use 4 to index str. You also add 4 to the pointer p1to
access the fifth element because p1currently points to the first element of str. (Recall
that an array name without an index returns the starting address of the array, whichis the address of the first element.)
The preceding example can be generalized. In essence, C/C++ provides two methods
of accessing array elements: pointer arithmetic and array indexing. Although thestandard array-indexing notation is sometimes easier to understand, pointer arithmeticcan be faster. Since speed is often a consideration in programming, C/C++ programmerscommonly use pointers to access array elements.
These two versions of putstr( )—one with array indexing and one with pointers—
illustrate how you can use pointers in place of array indexing. The putstr( ) function
writes a string to the standard output device one character at a time.
/* Index s as an array. */
void putstr(char *s){
register int t;
for(t=0; s[t]; ++t) putchar(s[t]);
}
122 C++: The Complete Reference
/* Access s as a pointer. */
void putstr(char *s){
while(*s) putchar(*s++);
}
Most professional C/C++ programmers would find the second version easier to
read and understand. In fact, the pointer version is the way routines of this sortare commonly written in C/C++.
Arrays of Pointers
Pointers may be arrayed like any other data type. The declaration for an intpointer
array of size 10 is
int *x[10];
To assign the address of an integer variable called varto 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 arrayname without any indexes. For example, a function that can receive array xlooks
like this:
void display_array(int *q[])
{
int t;
for(t=0; t<10; t++)
printf("%d ", *q[t]);
}
Chapter 5: Pointers 123THE FOUNDATION OF C++:
THE C SUBSETRemember, qis not a pointer to integers, but rather a pointer to an array of pointers to
integers. Therefore you need to declare the parameter qas an array of integer pointers,
as just shown. You cannot declare qsimply as an integer pointer because that is not
what it is.
Pointer arrays are often used to hold pointers to strings. You can create a function
that outputs an error message given its code number, as shown here:
void syntax_error(int num)
{
static char *err[] = {
"Cannot Open File\n","Read Error\n","Write Error\n","Media Failure\n"
};
printf("%s", err[num]);
}
The array errholds pointers to each string. 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. For example,
ifnum is passed a 2, the message Write Error is displayed.
As a point of interest, note that the command line argument argv is an array of
character pointers. (See Chapter 6.)
Multiple Indirection
You can have a pointer point to another pointer that points to the target value. Thissituation is called multiple indirection, or pointers to pointers. Pointers to pointers can
be confusing. Figure 5-3 helps clarify the concept of multiple indirection. As you cansee, the value of a normal pointer is the address of the object that contains the valuedesired. In the case of a pointer to a pointer, the first pointer contains the address ofthe second pointer, which points to 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. In fact, excessive indirection is difficult to followand prone to conceptual errors.
Do not confuse multiple indirection with high-level data structures, such as linked lists,that use pointers. These are two fundamentally different concepts.
124 C++: The Complete Reference
A variable that is a pointer to a pointer must be declared as such. You do this by
placing an additional asterisk in front of the variable name. 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.
To access the target value indirectly pointed to by a pointer to a pointer, you must
apply the asterisk operator twice, as in this example:
#include <stdio.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, pis declared as a pointer to an integer and qas a pointer to a pointer to an
integer. The call to printf( ) prints the number 10on the screen.Figure 5-3. Single and multiple indirection
Chapter 5: Pointers 125THE FOUNDATION OF C++:
THE C SUBSET Initializing Pointers
After a nonstatic local pointer is declared but before it has been assigned a value,
it contains an unknown value. (Global and static local pointers are automatically
initialized to null.) Should you try to use the pointer before giving it a valid value,you will probably crash your program—and possibly your computer's operatingsystem as well—a very nasty type of error!
There is an important convention that most C/C++ programmers follow when
working with pointers: A pointer that does not currently point to a valid memorylocation is given the value null (which is zero). By convention, any pointer that isnull implies that it points to nothing and should not be used. However, just becausea pointer has a null value does not make it "safe." The use of null is simply a conventionthat programmers follow. It is not a rule enforced by the C or C++ languages. Forexample, if you use a null pointer on the left side of an assignment statement, you stillrun the risk of crashing your program or operating system.
Because a null pointer is assumed to be unused, you can use the null pointer to
make many of your pointer routines easier to code and more efficient. For example,you could use a null pointer to mark the end of a pointer array. A routine that accessesthat array knows that it has reached the end when it encounters the null value. Thesearch( ) function shown here illustrates this type of approach.
/* look up a name */
int search(char *p[], char *name){
register int t;
for(t=0; p[t]; ++t)
if(!strcmp(p[t], name)) return t;return -1; /* not found */
}
The forloop inside search( ) runs until either a match is found or a null pointer
is encountered. Assuming the end of the array is marked with a null, the condition
controlling the loop fails when it is reached.
C/C++ programmers commonly initialize strings. You saw an example of this in the
syntax_error( ) function in the section "Arrays of Pointers." Another variation on the
initialization theme is the following type of string declaration:
char *p = "hello world";
As you can see, the pointer pis not an array. The reason this sort of initialization
works is because of the way the compiler operates. All C/C++ compilers create
what is called a string table, which is used to store the string constants used by
the program. Therefore, the preceding declaration statement places the address
ofhello world, as stored in the string table, into the pointer p. Throughout a
program, pcan be used like any other string (except that it should not be altered).
For example, the following program is perfectly valid:
#include <stdio.h>
#include <string.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 *. But C++
provides an automatic conversion to char *. Thus, the preceding program is still valid.
However, this automatic conversion is a deprecated feature, which means that you
should not rely upon it for new code. For new programs, you should assume that stringliterals are indeed constants and the declaration of pin the preceding program should
be written like this.
const char *p = "hello world";
Pointers to Functions
A particularly confusing yet powerful feature of C++ is the function pointer . Even
though a function is not a variable, it still has a physical location in memory thatcan be assigned to a pointer. This address is the entry point of the function and it isthe address used when the function is called. Once a pointer points to a function, thefunction can be called through that pointer. Function pointers also allow functionsto be passed as arguments to other functions.
You obtain the address of a function by using the function's name without any
parentheses or arguments. (This is similar to the way an array's address is obtained126 C++: The Complete Reference
when only the array name, without indexes, is used.) To see how this is done, study
the following program, paying close attention to the declarations:
#include <stdio.h>
#include <string.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.\n");
if(!(*cmp)(a, b)) printf("Equal");else printf("Not Equal");
}
When the check( ) function is called, two character pointers and one function pointer
are passed as parameters. Inside the function check( ), the arguments are declared as
character pointers and a function pointer. Notice how the function pointer is declared.
You must use a similar form when declaring other function pointers, although thereturn type and parameters of the function may differ. The parentheses around the*cmp are necessary for the compiler to interpret this statement correctly.
Inside check( ), the expression
(*cmp)(a, b)Chapter 5: Pointers 127THE FOUNDATION OF C++:
THE C SUBSET
calls strcmp( ), which is pointed to by cmp, with the arguments aand b. The
parentheses around *cmp are necessary. This is one way to call a function through
a pointer. A second, simpler syntax, as shown here, may also be used.
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. (That is, that cmp is a
function pointer, not the name of a function.) Other than that, the two expressionsare equivalent.
Note that you can call check( ) by using strcmp( ) directly, as shown here:
check(s1, s2, strcmp);
This eliminates the need for an additional pointer variable.
You may wonder why anyone would write a program in this way. Obviously,
nothing is gained and significant confusion is introduced in the previous example.However, at times it is advantageous to pass functions as parameters or to create anarray of functions. For example, when a compiler or interpreter is written, the parser(the part that evaluates expressions) often calls various support functions, such asthose that compute mathematical operations (sine, cosine, tangent, etc.), performI/O, or access system resources. Instead of having a large switch statement with all
of these functions listed in it, an array of function pointers can be created. In thisapproach, the proper function is selected by its index. You can get the flavor of thistype of usage by studying the expanded version of the previous example. In thisprogram, check( ) can be made to check for either alphabetical equality or numeric
equality by simply calling it with a different comparison function.
#include <stdio.h>
#include <ctype.h>#include <stdlib.h>#include <string.h>
void check(char *a, char *b,
int (*cmp)(const char *, const char *));
int numcmp(const char *a, const char *b);int main(void)
{
char s1[80], s2[80];128 C++: The Complete Reference
gets(s1);
gets(s2);
if(isalpha(*s1))
check(s1, s2, strcmp);
else
check(s1, s2, numcmp);
return 0;
}void check(char *a, char *b,
int (*cmp)(const char *, const char *))
{
printf("Testing for equality.\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( ). Otherwise,
numcmp( ) is used. Since check( ) calls the function that it is passed, it can use
different comparison functions in different cases.
C's Dynamic Allocation Functions
Pointers provide necessary support for C/C++'s dynamic allocation system. Dynamic
allocation is the means by which a program can obtain memory while it is running.
As you know, global variables are allocated storage at compile time. Local variables
use the stack. However, neither global nor local variables can be added during programexecution. Yet there will be times when the storage needs of a program cannot beknown ahead of time. For example, a program might use a dynamic data structure,such as a linked list or binary tree. Such structures are inherently dynamic in nature,growing or shrinking as needed. To implement such a data structure requires that aprogram be able to allocate and free memory.Chapter 5: Pointers 129THE FOUNDATION OF C++:
THE C SUBSET
130 C++: The Complete Reference
C++ actually supports two complete dynamic allocation systems: the one defined
by C and the one specific to C++. The system specific to C++ contains several
improvements over that used by C, and this approach is discussed in Part Two.
Here, C's dynamic allocation functions are described.
Memory allocated by C's dynamic allocation functions is obtained from the
heap—the region of free memory that lies between your program and its permanentstorage area and the stack. Although the size of the heap is unknown, it generallycontains a fairly large amount of free memory.
The core of C's allocation system consists of the functions malloc( ) and free( ).
(Most compilers supply several other dynamic allocation functions, but these twoare the most important.) These functions work together using the free memory regionto establish and maintain a list of available storage. The malloc( ) function allocates
memory and the free( ) function releases it. That is, each time a malloc( ) memory
request is made, a portion of the remaining free memory is allocated. Each time afree( ) memory release call is made, memory is returned to the system. Any program
that uses these functions should include the header file stdlib.h. (A C++ program may
also use the C++-style header <cstdlib>.)
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. (The
type size_t is defined in stdlib.h as, more or less, an unsigned integer.) The malloc( )
function returns a pointer of type void *, which means that you can assign it to any
type of pointer. After a successful call, malloc( ) returns a pointer to the first byte
of the region of memory allocated from the heap. If there is not enough availablememory to satisfy the malloc( ) request, an allocation failure occurs and malloc( )
returns a null.
The code fragment shown here allocates 1,000 bytes of contiguous memory:
char *p;
p = malloc(1000); /* get 1000 bytes */
After the assignment, ppoints to the start of 1,000 bytes of free memory.
In the preceding example, notice that no type cast is used to assign the return
value of malloc( ) top.In C, a void * pointer is automatically converted to the type
of the pointer on the left side of an assignment. However, it is important to understand
that this automatic conversion does not occur in C++. In C++, an explicit type cast is
needed when a void * pointer is assigned to another type of pointer. Thus, in C++, the
preceding assignment must be written like this:
Chapter 5: Pointers 131THE FOUNDATION OF C++:
THE C SUBSETp = (char *) malloc(1000);
As a general rule, in C++ you must use a type cast when assigning (or otherwise
converting) one type of pointer to another. This is one of the few fundamentaldifferences between C and C++.
The next example allocates space for 50 integers. Notice the use of sizeof to ensure
portability.
int *p;
p = (int *) malloc(50*sizeof(int));
Since the heap is not infinite, whenever you allocate memory , you must check
the value returned by malloc( ) to make sure that it is not null before using the pointer.
Using a null pointer will almost certainly crash your program. The proper way to
allocate memory and test for a valid pointer is illustrated in this code fragment:
p = (int *) malloc(100);
if(!p) {
printf("Out of memory.\n");exit(1);
}
Of course, you can substitute some other sort of error handler in place of the call to
exit( ). Just make sure that you do not use the pointer pif it is null.
The free( ) function is the opposite of malloc( ) in that it returns previously allocated
memory to the system. Once the memory has been freed, it may be reused by a subsequentcall to malloc( ). The function free( ) has this prototype:
void free(void *p);
Here, pis a pointer to memory that was previously allocated using malloc( ).I ti s
critical that you never callfree( ) with an invalid argument; otherwise, you will
destroy the free list.
Problems with Pointers
Nothing will get you into more trouble than a wild pointer! Pointers are a mixedblessing. They give you tremendous power and are necessary for many programs.At the same time, when a pointer accidentally contains a wrong value, it can be themost difficult bug to find.
An erroneous pointer is difficult to find because the pointer itself is not the problem.
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. If you read from it, the worstthat can happen is that you get garbage. However, if you write to it, you might bewriting over other pieces of your code or data. This may not show up until later in theexecution of your program, and may lead you to look for the bug in the wrong place.There may be little or no evidence to suggest that the pointer is the original cause ofthe problem. This type of bug causes programmers to lose sleep time and time again.
Because pointer errors are such nightmares, you should do your best never to
generate one. To help you avoid them, a few of the more common errors are discussedhere. The classic example of a pointer error is the uninitialized pointer . Consider this
program.
/* This program is wrong. */
int main(void){
int x, *p;
x = 10;
*p = x;
return 0;
}
This program assigns the value 10 to some unknown memory location. Here is why:
Since the pointer phas never been given a value, it contains an unknown value when
the assignment *p = x takes place. This causes the value of xto be written to some
unknown memory location. This type of problem often goes unnoticed when yourprogram is small because the odds are in favor of pcontaining a "safe" address—one
that is not in your code, data area, or operating system. However, as your programgrows, the probability increases of ppointing to something vital. Eventually, your
program stops working. The solution is to always make sure that a pointer is pointingat something valid before it is used.
A second common error is caused by a simple misunderstanding of how to use
a pointer. Consider the following:
/* This program is wrong. */
#include <stdio.h>
int main(void)
{132 C++: The Complete Reference
Chapter 5: Pointers 133THE FOUNDATION OF C++:
THE C SUBSETint x, *p;
x = 10;
p = x;
printf("%d", *p);return 0;
}
The call to printf( ) does not print the value of x, which is 10, on the screen. It prints
some unknown value because the assignment
p = x;
is wrong. That statement assigns the value 10 to the pointer p. However, pis supposed
to contain an address, not a value. To correct the program, write
p = &x;
Another error that sometimes occurs is caused by incorrect assumptions about
the placement of variables in memory. 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 eachcompiler will treat it in the same way. For these reasons, making any comparisonsbetween pointers that do not point to a common object may yield unexpected results.For example,
char s[80], y[80];
char *p1, *p2;
p1 = s;
p2 = y;if(p1 < p2) . . .
is generally an invalid concept. (In very unusual situations, you might use something
like this to determine the relative position of the variables. But this would be rare.)
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. For example,
int first[10], second[10];
int *p, t;
134 C++: The Complete Reference
p = first;
for(t=0; t<20; ++t) *p++ = t;
This is not a good way to initialize the arrays first and second with the numbers 0
through 19. 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. This
may not always be the case.
The next program illustrates a very dangerous type of bug. See if you can find it.
/* This program has a bug. */
#include <string.h>#include <stdio.h>
int main(void)
{
char *p1;char s[80];
p1 = s;
do {
gets(s); /* read a string */
/* print the decimal equivalent of each
character */
while(*p1) printf(" %d", *p1++);
} while(strcmp(s, "done"));return 0;
}
This program uses p1to print the ASCII values associated with the characters
contained in s. The problem is that p1is assigned the address of sonly once. The
first time through the loop, p1points to the first character in s. However, the second
time through, it continues where it left off because it is not reset to the start of s. This
next character may be part of the second string, another variable, or a piece of the
program! The proper way to write this program is
/* This program is now correct. */
#include <string.h>
#include <stdio.h>
int main(void)
{
char *p1;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, p1is set to the start of the string. In general, you should
remember to reinitialize a pointer if it is to be reused.
The fact that handling pointers incorrectly can cause tricky bugs is no reason to
avoid using them. Just be careful, and make sure that you know where each pointer
is pointing before you use it.Chapter 5: Pointers 135THE FOUNDATION OF C++:
THE C SUBSET
This page intentionally left blank
Chapter 6
Functions
137
Copyright © 2003 by The McGraw-Hill Companies. Click here for terms of use.
138 C++: The Complete Reference
Functions are the building blocks of C and C++ and the place where all program
activity occurs. This chapter examines their C-like features, including passingarguments, returning values, prototypes, and recursion. Part Two discusses
the C++-specific features of functions, such as function overloading and referenceparameters.
The General Form of a Function
The general form of a function is
ret-type function-name(parameter list){
body of the function
}
Theret-type specifies the type of data that the function returns. A function may return
any type of data except an array. The parameter list is a comma-separated list of variable
names and their associated types that receive the values of the arguments when thefunction is called. A function may be without parameters, in which case the parameterlist is empty. However, even if there are no parameters, the parentheses are still required.
In variable declarations, you can declare many variables to be of a common type
by using a comma-separated list of variable names. In contrast, all function parametersmust be declared individually, each including both the type and name. That is, theparameter declaration list for a function takes this general form:
f(type varname1, type varname2, . . . , 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.
Each function is a discrete block of code. 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. (For instance, you cannot use goto to jump into the middle of another
function.) The code that constitutes the body of a function is hidden from the rest of theprogram and, unless it uses global variables or data, it can neither affect nor be affected
by other parts of the program. Stated another way, the code and data that are defined
within one function cannot interact with the code or data defined in another functionbecause the two functions have a different scope.
Variables that are defined within a function are called local variables. A local
variable comes into existence when the function is entered and is destroyed uponexit. That is, local variables cannot hold their value between function calls. The onlyexception to this rule is when the variable is declared with the static storage class
specifier. This causes the compiler to treat the variable as if it were a global variablefor storage purposes, but limits its scope to within the function. (Chapter 2 coversglobal and local variables in depth.)
In C (and C++) you cannot define a function within a function. This is why neither
C nor C++ are technically block-structured languages.
Function Arguments
If a function is to use arguments, it must declare variables that accept the valuesof the arguments. These variables are called the formal parameters of the function.
They behave like other local variables inside the function and are created upon entryinto the function and destroyed upon exit. As shown in the following function, theparameter declarations occur after the function name:
/* 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: sand c. This function returns 1 if the
character cis part of the string s; otherwise, it returns 0.
As with local variables, you may make assignments to a function's formal parameters
or use them in an expression. Even though these variables perform the special task of
receiving the value of the arguments passed to the function, you can use them as youdo any other local variable.
Call by Value, Call by Reference
In a computer language, there are two ways that arguments can be passed to asubroutine. The first is known as call by value. This method copies the value of anChapter 6: Functions 139THE FOUNDATION OF C++:
THE C SUBSET
argument into the formal parameter of the subroutine. In this case, changes made to
the parameter have no effect on the argument.
Call by reference is the second way of passing arguments to a subroutine. In this
method, the address of an argument is copied into the parameter. Inside the subroutine,
the address is used to access the actual argument used in the call. This means thatchanges made to the parameter affect the argument.
By default, C/C++ uses call by value to pass arguments. In general, this means that
code within a function cannot alter the arguments used to call the function. Considerthe following program:
#include <stdio.h>
int sqr(int x);int main(void)
{
int t=10;
printf("%d %d", sqr(t), t);return 0;
}int sqr(int x)
{
x = x*x;return(x);
}
In this example, the value of the argument to sqr( ), 10, is copied into the parameter
x. When the assignment x = x*x takes place, only the local variable xis modified. The
variable t, used to call sqr( ), still has the value 10. Hence, the output is 100 10.
Remember that it is a copy of the value of the argument that is passed into the function.
What occurs inside the function has no effect on the variable used in the call.
Creating a Call by Reference
Even though C/C++ uses call by value for passing parameters, you can create a
call by reference by passing a pointer to an argument, instead of the argument itself.Since the address of the argument is passed to the function, code within the functioncan change the value of the argument outside the function.
Pointers are passed to functions just like any other value. Of course, you need
to declare the parameters as pointer types. For example, the function swap( ),140 C++: The Complete Reference
which exchanges the values of the two integer variables pointed to by its arguments,
shows how.
void swap(int *x, int *y)
{
int temp;
temp = *x; /* save the value at address x */
*x = *y; /* put y into x */*y = temp; /* put x into y */
}
swap( ) is able to exchange the values of the two variables pointed to by xand ybecause
their addresses (not their values) are passed. Thus, within the function, the contents of
the variables can be accessed using standard pointer operations, and the contents of thevariables used to call the function are swapped.
Remember that swap( ) (or any other function that uses pointer parameters) must
be called with the addresses of the arguments. The following fragment shows the correct
way to call swap( ):
void swap(int *x, int *y);
int main(void)
{
int i, j;
i = 10;
j = 20;printf("%d %d", i, j);swap(&i, &j); /* pass the addresses of i and j */printf("%d %d", i, j);return 0;
}
In this example, the variable iis assigned the value 10 and jis assigned the value 20.
Then swap( ) is called with the addresses of iand j. (The unary operator &is used to
produce the address of the variables.) Therefore, the addresses of iand j, not their values,
are passed into the function swap( ). After swap( ) returns, the values of iand jwill be
exchanged.
C++ allows you to fully automate a call by reference through the use of reference
parameters. This feature is described in Part Two.Chapter 6: Functions 141THE FOUNDATION OF C++:
THE C SUBSET
Calling Functions with Arrays
Arrays are covered in detail in Chapter 4. However, this section discusses passing
arrays as arguments to functions because it is an exception to the normal call-by-valueparameter passing.
When an array is used as a function argument, its address is passed to a function.
This is an exception to the call-by-value parameter passing convention. In this case, thecode inside the function is operating on, and potentially altering, the actual contents ofthe array used to call the function. For example, consider the function print_upper( ),
which prints its string argument in uppercase:
#include <stdio.h>
#include <ctype.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. */
void print_upper(char *string){
register int t;
for(t=0; string[t]; ++t) {
string[t] = toupper(string[t]);
putchar(string[t]);
}
}
After the call to print_upper( ) , the contents of array sinmain( ) have also been changed
to uppercase. If this is not what you want, you could write the program like this:
#include <stdio.h>#include <ctype.h>142 C++: The Complete Reference
Chapter 6: Functions 143THE FOUNDATION OF C++:
THE C SUBSETvoid 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 sremain unchanged because its values are not
altered inside print_upper( ).
The standard library function gets( ) is a classic example of passing arrays into
functions. 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.
/* A simple version of the standard
gets() library function. */
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) {
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. This, of course, can
be the name of a character array, which by definition is a character pointer. Upon entry,
xgets( ) establishes a forloop from 0 to 79. This prevents larger strings from being
entered at the keyboard. If more than 80 characters are entered, the function returns.(The real gets( ) function does not have this restriction.) 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. As you type characters on the keyboard, they are placed in the string.If you type a backspace, the counter tis reduced by 1, effectively removing the previous
character from the array. When you press
ENTER , a null is placed at the end of the string,
signaling its termination. Because the actual array used to call xgets( ) is modified, upon
return it contains the characters that you type.
argc and argv—Arguments to main( )
Sometimes it is useful to pass information into a program when you run it. Generally, youpass information into the main( ) function via command line arguments. A command
line argument is the information that follows the program's name on the command line
of the operating system. For example, when you compile a program, you might typesomething like the following after the command prompt:
ccprogram_name
where program_name is a command line argument that specifies the name of the
program you wish to compile.
There are two special built-in arguments, argv and argc, that are used to receive
command line arguments. The argc parameter holds the number of arguments on
the command line and is an integer. It is always at least 1 because the name of theprogram qualifies as the first argument. The argv parameter is a pointer to an array
Chapter 6: Functions 145THE FOUNDATION OF C++:
THE C SUBSETof character pointers. Each element in this array points to a command line argument.
All command line arguments are strings—any numbers will have to be converted bythe program into the proper internal format. For example, this simple program printsHello and your name on the screen if you type it directly after the program name.
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
if(argc!=2) {
printf("You forgot to type your name.\n");exit(1);
}printf("Hello %s", argv[1]);
return 0;
}
If you called this program name and your name were Tom, you would type name Tom
to run the program. The output from the program would be Hello Tom.
In many environments, each command line argument must be separated by a space
or a tab. Commas, semicolons, and the like are not considered separators. For example,
run Spot, run
is made up of three strings, while
Herb,Rick,Fred
is a single string since commas are not generally legal separators.
Some environments allow you to enclose within double quotes a string containing
spaces. This causes the entire string to be treated as a single argument. Check your
operating system documentation for details on the definition of command line parametersfor your system.
You must declare argv properly. The most common method is
char *argv[];
The empty brackets indicate that the array is of undetermined length. You can nowaccess the individual arguments by indexing argv . For example, argv[0] points to the
146 C++: The Complete Reference
first string, which is always the program's name; argv[1] points to the first argument,
and so on.
Another short example using command line arguments is the program called
countdown, shown here. It counts down from a starting value (which is specified
on the command line) and beeps when it reaches 0. Notice that the first argumentcontaining the number is converted into an integer by the standard function atoi( ).If
the string "display" is the second command line argument, the countdown will also bedisplayed on the screen.
/* Countdown program. */
#include <stdio.h>#include <stdlib.h>#include <ctype.h>#include <string.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. Try again.\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. A program with command line arguments often issues instructions if theuser attempts to run the program without entering the proper information.
To access an individual character in one of the command line arguments, add a
second index to argv . For example, the next program displays all of the arguments
with which it was called, one character at a time:
Chapter 6: Functions 147THE FOUNDATION OF C++:
THE C SUBSET#include <stdio.h>
int main(int argc, char *argv[])
{
int t, i;
for(t=0; t<argc; ++t) {
i = 0;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.
Normally, you use argc and argv to get initial commands into your program. In
theory, you can have up to 32,767 arguments, but most operating systems do not allowmore than a few. You typically use these arguments to indicate a filename or an option.Using command line arguments gives your program a professional appearance andfacilitates its use in batch files.
When a program does not require command line parameters, it is common
practice to explicitly declare main( ) as having no parameters. For C programs this is
accomplished by using the void keyword in its parameter list. (This is the approach
used by the programs in Part One of this book.) However, for C++ programs you maysimply specify an empty parameter list. In C++, the use of void to indicate an empty
parameter list is allowed, but redundant.
The names argc and argv are traditional but arbitrary. You may name these two
parameters to main( ) anything you like. Also, some compilers may support additional
arguments to main( ), so be sure to check your user's manual.
The return Statement
The return statement itself is described in Chapter 3. As explained, it has two important
uses. First, it causes an immediate exit from the function that it is in. That is, it causesprogram execution to return to the calling code. Second, it may be used to return a value.This section examines how the return statement is used.
148 C++: The Complete Reference
Returning from a Function
There are two ways that a function terminates execution and returns to the caller. The
first occurs when the last statement in the function has executed and, conceptually,the function's ending curly brace (} ) is encountered. (Of course, the curly brace isn't
actually present in the object code, but you can think of it in this way.) For example, thepr_reverse( ) function in this program simply prints the string "I like C++" backwards
on the screen and then returns.
#include <string.h>
#include <stdio.h>
void pr_reverse(char *s);int main(void)
{
pr_reverse("I like C++");
return 0;
}void pr_reverse(char *s)
{
register int t;
for(t=strlen(s)-1; t>=0; t–) putchar(s[t]);
}
Once the string has been displayed, there is nothing left for pr_reverse( ) to do, so it
returns to the place from which it was called.
Actually, not many functions use this default method of terminating their execution.
Most functions rely on the return statement to stop execution either because a value
must be returned or to make a function's code simpler and more efficient.
A function may contain several return statements. For example, the find_substr( )
function in the following program returns the starting position of a substring within
a string, or returns −1 if no match is found.
#include <stdio.h>
int find_substr(char *s1, char *s2);int main(void)
{
THE FOUNDATION OF C++:
THE C SUBSETif(find_substr("C++ is fun", "is") != -1)
printf("substring is found");
return 0;
}
/* Return index of first match of s2 in s1. */
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. This value is specified by the
return statement. In C89, if a non-void function does not explicitly return a value via
areturn statement, then a garbage value is returned. In C++ (and C99), a non- void
function must contain a return statement that returns a value. 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. However, if execution reaches the end of a non-void function, then
a garbage value is returned. Although this condition is not a syntax error, it is still a
fundamental flaw and should be avoided.
As long as a function is not declared as void, you may use it as an operand in an
expression. Therefore, each of the following expressions is valid:
x = power(y);
if(max(x,y) > 100) printf("greater");for(ch=getchar(); isdigit(ch); ) … ;Chapter 6: Functions 149
150 C++: The Complete Reference
As a general rule, a function cannot be the target of an assignment. A statement
such as
swap(x,y) = 100; /* incorrect statement */
is wrong. The C/C++ compiler will flag it as an error and will not compile a program
that contains it. (As is discussed in Part Two, C++ allows some interesting exceptionsto this general rule, enabling some types of functions to occur on the left side of anassignment.)
When you write programs, your functions generally will be of three types. The
first type is simply computational. These functions are specifically designed toperform operations on their arguments and return a value based on that operation.A computational function is a "pure" function. Examples are the standard libraryfunctions sqrt( ) and sin( ) , which compute the square root and sine of their arguments.
The second type of function manipulates information and returns a value that
simply indicates the success or failure of that manipulation. An example is the libraryfunction fclose( ) , which is used to close a file. If the close operation is successful,
the function returns 0; if the operation is unsuccessful, it returns EOF .
The last type of function has no explicit return value. In essence, the function is
strictly procedural and produces no value. An example is exit( ), which terminates a
program. All functions that do not return values should be declared as returning typevoid. By declaring a function as void, you keep it from being used in an expression,
thus preventing accidental misuse.
Sometimes, functions that really don't produce an interesting result return something
anyway. For example, printf( ) returns the number of characters written. Yet it would
be unusual to find a program that actually checked this. In other words, although allfunctions, except those of type void, return values, you don't have to use the return
value for anything. A common question concerning function return values is, "Don't Ihave to assign this value to some variable since a value is being returned?" The answeris no. If there is no assignment specified, the return value is simply discarded. Considerthe following program, which uses the function mul( ):
#include <stdio.h>
int mul(int a, int b);int main(void)
{
int x, y, z;
x = 10; y = 20;
z = mul(x, y); /* 1 */
printf("%d", mul(x,y)); /* 2 */mul(x, y); /* 3 */
return 0;
}int mul(int a, int b)
{
return a*b;
}
In line 1, the return value of mul( ) is assigned to z. In line 2, the return value is not
actually assigned, but it is used by the printf( ) function. Finally, in line 3, the return
value is lost because it is neither assigned to another variable nor used as part of an
expression.
Returning Pointers
Although functions that return pointers are handled just like any other type offunction, a few important concepts need to be discussed.
Pointers to variables are neither integers nor unsigned integers. They are the memory
addresses of a certain type of data. The reason for this distinction is because pointerarithmetic is relative to the base type. 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).In general, each time a pointer is incremented (or decremented), it points to the next (orprevious) item of its type. Since the length of different data types may differ, the compilermust know what type of data the pointer is pointing to. For this reason, a function thatreturns a pointer must declare explicitly what type of pointer it is returning. 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.
For example, this function returns a pointer to the first occurrence of the character c
in string s:
/* Return pointer of first occurrence of c in s. */
char *match(char c, char *s){
while(c!=*s && *s) s++;return(s);
}Chapter 6: Functions 151THE FOUNDATION OF C++:
THE C SUBSET
152 C++: The Complete Reference
If no match is found, a pointer to the null terminator is returned. Here is a short
program that uses match( ):
#include <stdio.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.");
return 0;
}
This program reads a string and then a character. If the character is in the string, the
program prints the string from the point of match. Otherwise, it prints No match found.
Functions of Type void
One of void's uses is to explicitly declare functions that do not return values. This
prevents their use in any expression and helps avert accidental misuse. For example,the function print_vertical( ) prints its string argument vertically down the side of the
screen. Since it returns no value, it is declared as void.
void print_vertical(char *str)
{
while(*str)
printf("%c\n", *str++);
}
Here is an example that uses print_vertical( ).
#include <stdio.h>
Chapter 6: Functions 153THE FOUNDATION OF C++:
THE C SUBSETvoid print_vertical(char *str); /* prototype */
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. Thus, in
early C programs, functions that did not return values simply defaulted to type int.
Therefore, don't be surprised to see many examples of this in older code.
What Does main( ) Return?
The main( ) function returns an integer to the calling process, which is generally the
operating system. Returning a value from main( ) is the equivalent of calling exit( )
with the same value. If main( ) does not explicitly return a value, the value passed
to the calling process is technically undefined. In practice, most C/C++ compilers
automatically return 0, but do not rely on this if portability is a concern.
Recursion
In C/C++, a function can call itself. A function is said to be recursive if a statement in
the body of the function calls itself. Recursion is the process of defining somethingin terms of itself, and is sometimes called circular definition.
A simple example of a recursive function is factr( ), which computes the factorial
of an integer. The factorial of a number nis the product of all the whole numbers
between 1 and n. For example, 3 factorial is 1 x 2 x 3, or 6. Both factr( ) and its iterative
equivalent are shown here:
/* recursive */
int factr(int n) {
int answer;
if(n==1) return(1);
154 C++: The Complete Reference
answer = factr(n-1)*n; /* recursive call */
return(answer);
}
/* non-recursive */
int fact(int n) {
int t, answer;
answer = 1;for(t=1; t<=n; t++)
answer=answer*(t);
return(answer);
}
The nonrecursive version of fact( ) should be clear. It uses a loop that runs from 1 to
nand progressively multiplies each number by the moving product.
The operation of the recursive factr( ) is a little more complex. When factr( ) is
called with an argument of 1, the function returns 1. Otherwise, it returns the product
offactr(n−1)*n. To evaluate this expression, factr( ) is called with n−1. This happens
until nequals 1 and the calls to the function begin returning.
Computing the factorial of 2, the first call to factr( ) causes a second, recursive call
with the argument of 1. This call returns 1, which is then multiplied by 2 (the originalnvalue). The answer is then 2. Try working through the computation of 3 factorial on
your own. (You might want to insert printf( ) statements into factr( ) to see the level of
each call and what the intermediate answers are.)
When a function calls itself, a new set of local variables and parameters are allocated
storage on the stack, and the function code is executed from the top with these newvariables. A recursive call does not make a new copy of the function. Only the valuesbeing operated upon are new. As each recursive call returns, the old local variablesand parameters are removed from the stack and execution resumes at the point of thefunction call inside the function. Recursive functions could be said to "telescope" outand back.
Often, recursive routines do not significantly reduce code size or improve memory
utilization over their iterative counterparts. Also, the recursive versions of most routinesmay execute a bit slower than their iterative equivalents because of the overhead of therepeated function calls. In fact, many recursive calls to a function could cause a stackoverrun. Because storage for function parameters and local variables is on the stackand each new call creates a new copy of these variables, the stack could be exhausted.However, you probably will not have to worry about this unless a recursive functionruns wild.
Chapter 6: Functions 155THE FOUNDATION OF C++:
THE C SUBSETThe main advantage to recursive functions is that you can use them to create clearer
and simpler versions of several algorithms. For example, the Quicksort algorithm is
difficult to implement in an iterative way. Also, some problems, especially ones relatedto artificial intelligence, lend themselves to recursive solutions. Finally, some peopleseem to think recursively more easily than iteratively.
When writing recursive functions, you must have a conditional statement, such
as an if,somewhere to force the function to return without the recursive call being
executed. If you don't, the function will never return once you call it. Omitting theconditional statement is a common error when writing recursive functions. Use printf( )
liberally during program development so that you can watch what is going on andabort execution if you see a mistake.
Function Prototypes
In C++ all functions must be declared before they are used. This is normally accomplishedusing a function prototype. Function prototypes were not part of the original C language.
They were, however, added when C was standardized. While prototypes are nottechnically required by Standard C, their use is strongly encouraged. Prototypes havealways been required by C++. In this book, all examples include full function prototypes.
Prototypes enable both C and C++ to provide stronger type checking, somewhat likethat provided by languages such as Pascal. When you use prototypes, the compiler canfind and report any illegal type conversions between the type of arguments used to calla function and the type definition of its parameters. The compiler will also catch differencesbetween the number of arguments used to call a function and the number of parametersin the function.
The general form of a function prototype is
type func_name(type parm_name1, type parm_name2,. . .,
type parm_nameN);
The use of parameter names is optional. However, they enable the compiler to identify
any type mismatches by name when an error occurs, so it is a good idea to include them.
The following program illustrates the value of function prototypes. It produces an
error message because it contains an attempt to call sqr_it( ) with an integer argument
instead of the integer pointer required. (It is illegal to convert an integer into a pointer.)
/* This program uses a function prototype to
enforce strong type checking. */
void sqr_it(int *i); /* prototype */
int main(void)
{
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. For example, this is a valid program.
#include <stdio.h>
/* This definition will also serve
as a prototype within this program. */
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. While it is possible for a function's definition to serve as its prototype in
small programs, it is seldom possible in large ones⎯especially when several files areused. The programs in this book include a separate prototype for each function becausethat is the way C/C++ code is normally written in practice.
The only function that does not require a prototype is main( ), since it is the first
function called when your program begins.
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 function that has no parameters. In C++, an empty parameter list is simply indicated
in the prototype by the absence of any parameters. For example,
int f(); /* C++ prototype for a function with no parameters */
However, in C this prototype means something different. For historical reasons,
an empty parameter list simply says that no parameter information is given. As far as the
compiler is concerned, the function could have several parameters or no parameters. InC, when a function has no parameters, its prototype uses void inside the parameter list.
For example, here is f( )'s prototype as it would appear in a C program.
float f(void);
This tells the compiler that the function has no parameters, and any call to that functionthat has parameters is an error. In C++, the use of void inside an empty parameter list
is still allowed, but is redundant.
In C++, f( )andf(void) are equivalent.
Function prototypes help you trap bugs before they occur. In addition, they help
verify that your program is working correctly by not allowing functions to be calledwith mismatched arguments.
One last point: Since early versions of C did not support the full prototype syntax,
prototypes are technically optional in C. This is necessary to support pre-prototypeC code. If you are porting older C code to C++, you may need to add full functionprototypes before it will compile. Remember: Although prototypes are optional in C,they are required by C++. This means that every function in a C++ program must befully prototyped.
Standard Library Function Prototypes
Any standard library function used by your program must be prototyped. To accomplishthis, you must include the appropriate header for each library function. All necessary
headers are provided by the C/C++ compiler. In C, the library headers are (usually)files that use the .H extension. In C++, headers may be either separate files or built intothe compiler itself. In either case, a header contains two main elements: any definitionsused by the library functions and the prototypes for the library functions. For example,stdio.h is included in almost all programs in this part of the book because it contains the
prototype for printf( ). The headers for the standard library are described in Part Three.Chapter 6: Functions 157THE FOUNDATION OF C++:
THE C SUBSET
158 C++: The Complete Reference
Declaring Variable-Length Parameter Lists
You can specify a function that has a variable number of parameters. The most common
example is printf( ). To tell the compiler that an unknown number of arguments may
be passed to a function, you must end the declaration of its parameters using threeperiods. For example, this prototype specifies that func( ) will have at least two integer
parameters and an unknown number (including 0) of parameters after that.
int func(int a, int b, …);
This form of declaration is also used by a function's definition.
Any function that uses a variable number of parameters must have at least one
actual parameter. For example, this is incorrect:
int func(…); /* illegal */
Old-Style Versus Modern
Function Parameter Declarations
Early versions of C used a different parameter declaration method than does either
Standard C or Standard C++. This early approach is sometimes called the classic form.
This book uses a declaration approach called the modern form. Standard C supports
both forms, but strongly recommends the modern form. Standard C++ only supportsthe modern parameter declaration method. However, you should know the old-styleform because many older C programs still use it.
The old-style function parameter declaration consists of two parts: a parameter
list, which goes inside the parentheses that follow the function name, and the actualparameter declarations, which go between the closing parentheses and the function'sopening curly brace. The general form of the old-style parameter definition is
type func_name(parm1, parm2, . . .parmN)
type parm1;type parm2;
…
type parmN;{
function code
}
For example, this modern declaration:
float f(int a, int b, char ch)
{
/* … */
}
will look like this in its old-style form:
float f(a, b, ch)int a, b;char ch;{
/* … */
}
Notice that the old-style form allows the declaration of more than one parameter in
a list after the type name.
The old-style form of parameter declaration is designated as obsolete by the C languageand is not supported by C++.Chapter 6: Functions 159THE FOUNDATION OF C++:
THE C SUBSET
This page intentionally left blank
Chapter 7
Structures, Unions,
Enumerations, andUser-Defined Types
161
Copyright © 2003 by The McGraw-Hill Companies. Click here for terms of use.
The C language gives you five ways to create a custom data type:
1. The structure, which is a grouping of variables under one name and is called an
aggregate data type. (The terms compound orconglomerate are also commonly used.)
2. The bit-field, which is a variation on the structure and allows easy access to
individual bits.
3. The union, which enables the same piece of memory to be defined as two or
more different types of variables.
4. The enumeration, which is a list of named integer constants.
5. The typedef keyword, which defines a new name for an existing type.
C++ supports all of the above and adds classes, which are described in Part Two.
The other methods of creating custom data types are described here.
In C++, structures and unions have both object-oriented and non-object-oriented
attributes. This chapter discusses only their C-like, non-object-oriented features.Their object-oriented qualities are described later in this book.
Structures
A structure is a collection of variables referenced under one name, providing aconvenient means of keeping related information together. A structure declaration
forms a template that may be used to create structure objects (that is, instances ofa structure). The variables that make up the structure are called members. (Structure
members are also commonly referred to as elements orfields.)
Generally , all of the members of a structure are logically related. For example, the
name and address information in a mailing list would normally be represented in astructure. The following code fragment shows how to declare a structure that definesthe name and address fields. The keyword struct tells the compiler that a structure is
being declared.
struct addr
{
char name[30];char street[40];char city[20];char state[3];unsigned long int zip;
};162 C++: The Complete Reference
Chapter 7: Structures, Unions, Enumerations, and User-Defined Types 163THE FOUNDATION OF C++:
THE C SUBSETNotice that the declaration is terminated by a semicolon. This is because a structure
declaration is a statement. The type name of the structure is addr . As such, addr
identifies this particular data structure and is its type specifier.
At this point, no variable has actually been created. Only the form of the data has been
defined. When you define a structure, you are defining a compound variable type, nota variable. Not until you declare a variable of that type does one actually exist. In C, todeclare a variable (i.e., a physical object) of type addr , write
struct addr addr_info;
This declares a variable of type addr called addr_info. In C++, you may use this
shorter form.
addr addr_info;
As you can see, the keyword struct is not needed. In C++, once a structure has been
declared, you may declare variables of its type using only its type name, without precedingit with the keyword struct. The reason for this difference is that in C, a structure's name
does not define a complete type name. In fact, Standard C refers to a structure's nameas atag. In C, you must precede the tag with the keyword struct when declaring variables.
However, in C++, a structure's name is a complete type name and may be used by itselfto define variables. Keep in mind, however, that it is still perfectly legal to use the C-styledeclaration in a C++ program. Since the programs in Part One of this book are valid forboth C and C++, they will use the C declaration method. Just remember that C++ allowsthe shorter form.
When a structure variable (such as addr_info) is declared, the compiler automatically
allocates sufficient memory to accommodate all of its members. Figure 7-1 shows howaddr_info appears in memory assuming 1-byte characters and 4-byte long integers.
You may also declare one or more structure variables when you declare a structure.
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. It is important to understand that each structure object contains its own
164 C++: The Complete Reference
copies of the structure’s members. For example, the zipfield of binfo is separate from
thezipfield of cinfo. Thus, changes to zipinbinfo do not affect the zipincinfo.
If you only need one structure variable, the structure type name is not needed.
That means that
struct {
char name[30];
char street[40];char city[20];char state[3];unsigned long int zip;
} addr_info;
declares one variable named addr_info as defined by the structure preceding it.
The general form of a structure declaration is
struct struct-type-name {
type member-name;
type member-name;type member-name;..Figure 7-1. Theaddr_info structure in memory
Chapter 7: Structures, Unions, Enumerations, and User-Defined Types 165THE FOUNDATION OF C++:
THE C SUBSET.
}structure-variables;
where either struct-type-name orstructure-variables may be omitted, but not both.
Accessing Structure Members
Individual members of a structure are accessed through the use of the .operator
(usually called the dot operator). For example, the following code assigns the ZIP
code 12345 to the zipfield of the structure variable addr_info declared earlier:
addr_info.zip = 12345;
The structure variable name followed by a period and the member name references
that individual member. The general form for accessing a member of a structure is
structure-name.member-name
Therefore, to print the ZIP code on the screen, write
printf("%lu", addr_info.zip);
This prints the ZIP code contained in the zipmember of the structure variable addr_info.
In the same fashion, the character array addr_info.name can be used to call gets( ),
as shown here:
gets(addr_info.name);
This passes a character pointer to the start of name.
Since name is a character array, you can access the individual characters of
addr_info.name by indexing name. For example, you can print the contents
ofaddr_info.name one character at a time by using the following code:
register int t;
for(t=0; addr_info.name[t]; ++t)
putchar(addr_info.name[t]);
Structure Assignments
The information contained in one structure may be assigned to another structure of the
same type using a single assignment statement. That is, you do not need to assign the
166 C++: The Complete Reference
value of each member separately. The following program illustrates structure
assignments:
#include <stdio.h>
int main(void)
{
struct {
int a;int b;
} x, y;
x.a = 10;y = x; /* assign one structure to another */printf("%d", y.a);return 0;
}
After the assignment, y.awill contain the value 10.
Arrays of Structures
Perhaps the most common usage of structures is in arrays of structures. To declare
an array of structures, you must first define a structure and then declare an arrayvariable of that type. For example, to declare a 100-element array of structures oftype addr , defined earlier, write
struct addr addr_info[100];
This creates 100 sets of variables that are organized as defined in the structure addr .
To access a specific structure, index the structure name. For example, to print the
ZIP code of structure 3, write
printf("%lu", addr_info[2].zip);
Like all array variables, arrays of structures begin indexing at 0.
Chapter 7: Structures, Unions, Enumerations, and User-Defined Types 167THE FOUNDATION OF C++:
THE C SUBSET Passing Structures to Functions
This section discusses passing structures and their members to functions.
Passing Structure Members to Functions
When you pass a member of a structure to a function, you are actually passing the
value of that member to the function. Therefore, you are passing a simple variable(unless, of course, that element is compound, such as an array). For example,consider this structure:
struct fred
{
char x;int y;float z;char s[10];
} mike;
Here are examples of each member being passed to a function:
func(mike.x); /* passes character value of x */func2(mike.y); /* passes integer value of y */func3(mike.z); /* passes float value of z */func4(mike.s); /* passes address of string s */func(mike.s[2]); /* passes character value of s[2] */
If you wish to pass the address of an individual structure member, put the &operator
before the structure name. For example, to pass the address of the members of the
structure mike, write
func(&mike.x); /* passes address of character x */
func2(&mike.y); /* passes address of integer y */func3(&mike.z); /* passes address of float z */func4(mike.s); /* passes address of string s */func(&mike.s[2]); /* passes address of character s[2] */
Note that the &operator precedes the structure name, not the individual member
name. Note also that salready signifies an address, so no &is required.
168 C++: The Complete Reference
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. Of course, this means that any changesmade to the contents of the structure inside the function to which it is passed do not
affect the structure used as an argument.
When using a structure as a parameter, remember that the type of the argument
must match the type of the parameter. For example, in the following program both theargument argand the parameter parm are declared as the same type of structure.
#include <stdio.h>
/* Define a structure type. */
struct struct_type {
int a, b;char ch;
} ;
void f1(struct struct_type parm);int main(void)
{
struct struct_type arg;
arg.a = 1000;f1(arg);return 0;
}void f1(struct struct_type parm)
{
printf("%d", parm.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 yourprogram can use it. For example, had struct_type been declared inside main( ) (for
example), then it would not have been visible to f1( ).
As just stated, when passing structures, the type of the argument must match
the type of the parameter. It is not sufficient for them to simply be physically similar;their type names must match. For example, the following version of the preceding
Chapter 7: Structures, Unions, Enumerations, and User-Defined Types 169THE FOUNDATION OF C++:
THE C SUBSETprogram 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.
/* This program is incorrect and will not compile. */
#include <stdio.h>
/* Define a structure type. */
struct struct_type {
int a, b;char ch;
} ;
/* Define a structure similar to struct_type,
but with a different name. */
struct struct_type2 {
int a, b;
char ch;
} ;
void f1(struct struct_type2 parm);int main(void)
{
struct struct_type arg;
arg.a = 1000;f1(arg); /* type mismatch */return 0;
}void f1(struct struct_type2 parm)
{
printf("%d", parm.a);
}
Structure Pointers
C/C++ allows pointers to structures just as it allows pointers to any other type
of variable. However, there are some special aspects to structure pointers thatyou should know.
170 C++: The Complete Reference
Declaring a Structure Pointer
Like other pointers, structure pointers are declared by placing *in front of a structure
variable's name. 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.
Using Structure Pointers
There are two primary uses for structure pointers: to pass a structure to a function usingcall by reference, and to create linked lists and other dynamic data structures that relyon dynamic allocation. This chapter covers the first use.
There is one major drawback to passing all but the simplest structures to functions:
the overhead needed to push the structure onto the stack when the function call isexecuted. (Recall that arguments are passed to functions on the stack.) For simplestructures with few members, this overhead is not too great. If the structure containsmany members, however, or if some of its members are arrays, run-time performancemay degrade to unacceptable levels. The solution to this problem is to pass only apointer to the structure.
When a pointer to a structure is passed to a function, only the address of the
structure is pushed on the stack. This makes for very fast function calls. A secondadvantage, in some cases, is when a function needs to reference the actual structureused as the argument, instead of a copy. By passing a pointer, the function canmodify the contents of the structure used in the call.
To find the address of a structure, place the &operator before the structure's name.
For example, given the following fragment:
struct bal {
float balance;
char name[80];
} person;
struct bal *p; /* declare a structure pointer */
then
p = &person;
places the address of the structure person into the pointer p.
To access the members of a structure using a pointer to that structure, you must
use the >operator. For example, this references the balance field:
p->balance
The >is usually called the arrow operator , and consists of the minus sign followed
by a greater-than sign. The arrow is used in place of the dot operator when you are
accessing a structure member through a pointer to the structure.
To see how a structure pointer can be used, examine this simple program, which
prints the hours, minutes, and seconds on your screen using a software timer.
/* Display a software timer. */
#include <stdio.h>
#define DELAY 128000struct 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.hours = 0;
systime.minutes = 0;systime.seconds = 0;
for(;;) {
update(&systime);
display(&systime);
}
return 0;
}Chapter 7: Structures, Unions, Enumerations, and User-Defined Types 171THE FOUNDATION OF C++:
THE C SUBSET
void update(struct my_time *t)
{
t->seconds++;
if(t->seconds==60) {
t->seconds = 0;t->minutes++;
}
if(t->minutes==60) {
t->minutes = 0;
t->hours++;
}
if(t->hours==24) t->hours = 0;
delay();
}
void display(struct my_time *t)
{
printf("%02d:", t->hours);printf("%02d:", t->minutes);printf("%02d\n", t->seconds);
}
void delay(void)
{
long int t;
/* change this as needed */
for(t=1; t<DELAY; ++t) ;
}
The timing of this program is adjusted by changing the definition of DELAY .
As you can see, a global structure called my_time is defined but no variable is
declared. Inside main( ), the structure systime is declared and initialized to 00:00:00.
This means that systime is known directly only to the main( ) function.
The functions update( ) (which changes the time) and display( ) (which prints
the time) are passed the address of systime. In both functions, their arguments are
declared as a pointer to a my_time structure.
Inside update( ) and display( ), each member of systime is accessed via a pointer.
Because update( ) receives a pointer to the systime structure, it can update its value.172 C++: The Complete Reference
Chapter 7: Structures, Unions, Enumerations, and User-Defined Types 173THE FOUNDATION OF C++:
THE C SUBSETFor example, to set the hours back to 0 when 24:00:00 is reached, update( ) contains
this line of code:
if(t->hours==24) t->hours = 0;
This tells the compiler to take the address in t(which points to systime inmain( ))
and use it to reset hours to zero.
Remember, use the dot operator to access structure elements when operating on
the structure itself. When you have a pointer to a structure, use the arrow operator.
Arrays and Structures Within Structures
A member of a structure may be either a simple or aggregate type. A simple member
is one that is of any of the built-in data types, such as integer or character. You havealready seen one type of aggregate element: the character arrays used in addr . Other
aggregate data types include one-dimensional and multidimensional arrays of theother data types, and structures.
A member of a structure that is an array is treated as you might expect from the
earlier examples. For example, consider this structure:
struct x {
int a[10][10]; /* 10 x 10 array of ints */
float b;
} y;
To reference integer 3,7 in aof structure y, write
y.a[3][7]
When a structure is a member of another structure, it is called a nested structure.
For example, the structure address is nested inside emp in this example:
struct emp {
struct addr address; /* nested structure */float wage;
} worker;
Here, structure emp has been defined as having two members. The first is a structure
of type addr , which contains an employee's address. The other is wage, which holds
174 C++: The Complete Reference
the employee's wage. The following code fragment assigns 93456 to the zipelement
ofaddress.
worker.address.zip = 93456;
As you can see, the members of each structure are referenced from outermost to
innermost. C guarantees that structures can be nested to at least 15 levels. StandardC++ suggests that at least 256 levels of nesting be allowed.
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. Bit-fields can be useful for a number of reasons,such as:
șIf storage is limited, you can store several Boolean (true/false) variables inone byte.
șCertain devices transmit status information encoded into one or more bitswithin a byte.
șCertain encryption routines need to access the bits within a byte.
Although these tasks can be performed using the bitwise operators, a bit-field canadd more clarity (and possibly efficiency) to your code.
To access individual bits, C/C++ uses a method based on the structure. 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. The general form of a bit-field definition is
struct struct-type-name {
type name1 :length;
type name2 :length;
…type nameN :length;
}variable_list;
Here, typeis the type of the bit-field and length is the number of bits in the field. A bit-field
must be declared as an integral or enumeration type. Bit-fields of length 1 should bedeclared as unsigned, because a single bit cannot have a sign.
Chapter 7: Structures, Unions, Enumerations, and User-Defined Types 175THE FOUNDATION OF C++:
THE C SUBSETBit-fields are frequently used when analyzing input from a hardware device.
For example, the status port of a serial communications adapter might return a
status byte organized like this:
Bit Meaning When Set
0 Change in clear-to-send line
1 Change in data-set-ready2 Trailing edge detected3 Change in receive line4 Clear-to-send5 Data-set-ready6 Telephone ringing7 Received signal
You can represent the information in a status byte using the following bit-field:
struct status_type {
unsigned delta_cts: 1;
unsigned delta_dsr: 1;unsigned tr_edge: 1;unsigned delta_rec: 1;unsigned cts: 1;unsigned dsr: 1;unsigned ring: 1;unsigned rec_line: 1;
} status;
You might use a routine similar to that shown here to enable a program to determine
when it can send or receive data.
status = get_port_status();
if(status.cts) printf("clear to send");if(status.dsr) printf("data ready");
To assign a value to a bit-field, simply use the form you would use for any other type
of structure element. For example, this code fragment clears the ring field:
status.ring = 0;
176 C++: The Complete Reference
As you can see from this example, each bit-field is accessed with the dot operator. However,
if the structure is referenced through a pointer, you must use the >operator.
You do not have to name each bit-field. This makes it easy to reach the bit you want,
bypassing unused ones. For example, if you only care about the ctsand dsrbits, you
could declare the status_type structure like this:
struct status_type {
unsigned : 4;
unsigned cts: 1;unsigned dsr: 1;
} status;
Also, notice that the bits after dsrdo not need to be specified if they are not used.
It is valid to mix normal structure members with bit-fields. For example,
struct emp {
struct addr address;float pay;unsigned lay_off: 1; /* lay off or active */unsigned hourly: 1; /* hourly pay or wage */unsigned deductions: 3; /* IRS deductions */
};
defines an employee record that uses only 1 byte to hold three pieces of information:
the employee's status, whether the employee is salaried, and the number of deductions.Without the bit-field, this information would have taken 3 bytes.
Bit-fields have certain restrictions. You cannot take the address of a bit-field. Bit-
fields cannot be arrayed. They cannot be declared as static. You cannot know , from
machine to machine, whether the fields will run from right to left or from left to right;this implies that any code using bit-fields may have some machine dependencies.Other restrictions may be imposed by various specific implementations.
Unions
Aunion is a memory location that is shared by two or more different types of variables.
A union provides a way of interpreting the same bit pattern in two or more differentways. Declaring a union is similar to declaring a structure. Its general form is
union union-type-name {
type member-name;
Chapter 7: Structures, Unions, Enumerations, and User-Defined Types 177THE FOUNDATION OF C++:
THE C SUBSETtype member-name;
type member-name;
…
}union-variables;
For example:
union u_type {
int i;
char ch;
};
This declaration does not create any variables. You may declare a variable either
by placing its name at the end of the declaration or by using a separate declarationstatement. 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. For example, this is how
cnvt is declared in C++:
u_type cnvt;
In C++, preceding this declaration with the keyword union is allowed, but redundant.
In C++, the name of a union defines a complete type name. In C, a union name is its
tag and it must be preceded by the keyword union. (This is similar to the situation with
structures described earlier.) However, since the programs in this chapter are valid forboth C and C++, the C-style declaration form will be used.
Incnvt, both integer iand character chshare the same memory location. Of
course, ioccupies 2 bytes (assuming 2-byte integers) and chuses only 1. Figure 7-2
shows how iand chshare the same address. At any point in your program, you can
refer to the data stored in a cnvt as either an integer or a character.
When a union variable is declared, the compiler automatically allocates enough
storage to hold the largest member of the union. For example (assuming 2-byte integers),
cnvt is 2 bytes long so that it can hold i,even though chrequires only 1 byte.
178 C++: The Complete Reference
To access a member of a union, use the same syntax that you would use for
structures: the dot and arrow operators. If you are operating on the union directly,
use the dot operator. If the union is accessed through a pointer, use the arrow
operator. For example, to assign the integer 10 to element iofcnvt, write
cnvt.i = 10;
In the next example, a pointer to cnvt is passed to a function:
void func1(union u_type *un)
{
un->i = 10; /* assign 10 to cnvt through
a pointer */
}
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. For example,
you may use a union to manipulate the bytes that comprise a double in order to alter its
precision or to perform some unusual type of rounding.
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. The C/C++
standard library defines no function specifically designed to write a short integer toa file. While you can write any type of data to a file using fwrite( ), using fwrite( )
incurs excessive overhead for such a simple operation. However, using a union you
can easily create a function called putw( ), which writes the binary representation of
a short integer to a file one byte at a time. (This example assumes that short integersare 2 bytes long.) To see how, first create a union consisting of one short integer and
a 2-byte character array:
union pw {
short int i;Figure 7-2. Howiandchutilize the union cnvt(assume 2-byte integers)
Chapter 7: Structures, Unions, Enumerations, and User-Defined Types 179THE FOUNDATION OF C++:
THE C SUBSETchar ch[2];
};
Now, you can use pwto create the version of putw( ) shown in the following program.
#include <stdio.h>
union pw {
short int i;
char ch[2];
};
int putw(short int num, FILE *fp);int main(void)
{
FILE *fp;
fp = fopen("test.tmp", "wb+");putw(1000, fp); /* write the value 1000 as an integer */
fclose(fp);
return 0;
}int putw(short int num, FILE *fp)
{
union pw word;
word.i = num;putc(word.ch[0], fp); /* write first half */
return putc(word.ch[1], fp); /* write second half */
}
Although putw( ) is called with a short integer, it can still use the standard function
putc( ) to write each byte in the integer to a disk file one byte at a time.
C++ supports a special type of union called an anonymous union which is discussed in
Part Two of this book.
180 C++: The Complete Reference
Enumerations
Anenumeration is a set of named integer constants that specify all the legal values
a variable of that type may have. Enumerations are common in everyday life. For
example, an enumeration of the coins used in the United States is
penny, nickel, dime, quarter, half-dollar, dollar
Enumerations are defined much like structures; the keyword enum signals the start
of an enumeration type. 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. (But at least one mustbe present.) 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. In C, the
following declares money to be a variable of type coin.
enum coin money;
In C++, the variable money may be declared using this shorter form:
coin money;
In C++, an enumeration name specifies a complete type. In C, an enumeration name isits tag and it requires the keyword enum to complete it. (This is similar to the situation
as it applies to structures and unions, described earlier.)
Given these declarations, the following types of statements are perfectly valid:
money = dime;
if(money==quarter) printf("Money is a quarter.\n");
The key point to understand about an enumeration is that each of the symbols
stands for an integer value. As such, they may be used anywhere that an integer may
be used. Each symbol is given a value one greater than the symbol that precedes it.The value of the first enumeration symbol is 0. Therefore,
Chapter 7: Structures, Unions, Enumerations, and User-Defined Types 181THE FOUNDATION OF C++:
THE C SUBSETprintf("%d %d", penny, dime);
displays 0 2on the screen.
You can specify the value of one or more of the symbols by using an initializer.
Do this by following the symbol with an equal sign and an integer value. Symbols
that appear after initializers are assigned values greater than the previous initializationvalue. 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 1dime 2quarter 100half_dollar 101dollar 102
One common but erroneous assumption about enumerations is that the symbols
can be input and output directly. This is not the case. For example, the following code
fragment will not perform as desired:
/* this will not work */
money = dollar;printf("%s", money);
Remember, dollar is simply a name for an integer; it is not a string. For the same
reason, you cannot use this code to achieve the desired results:
/* this code is wrong */strcpy(money, "dime");
That is, a string that contains the name of a symbol is not automatically converted to
that symbol.
Actually, creating code to input and output enumeration symbols is quite tedious
(unless you are willing to settle for their integer values). For example, you need the
following code to display, in words, the kind of coins that money contains:
switch(money) {
case penny: printf("penny");
break;
case nickel: printf("nickel");
break;
case dime: printf("dime");
break;
case quarter: printf("quarter");
break;
case half_dollar: printf("half_dollar");
break;
case dollar: printf("dollar");
}
Sometimes you can declare an array of strings and use the enumeration value as an
index to translate that value into its corresponding string. For example, this code alsooutputs the proper string:
char name[][12]={
"penny",
"nickel","dime","quarter","half_dollar","dollar"
};printf("%s", name[money]);
Of course, this only works if no symbol is initialized, because the string array must
be indexed starting at 0 in strictly ascending order using increments of 1.
Since enumeration values must be converted manually to their human-readable
string values for I/O operations, they are most useful in routines that do not makesuch conversions. An enumeration is often used to define a compiler's symbol table,for example. Enumerations are also used to help prove the validity of a program byproviding a compile-time redundancy check confirming that a variable is assignedonly valid values.182 C++: The Complete Reference
Chapter 7: Structures, Unions, Enumerations, and User-Defined Types 183THE FOUNDATION OF C++:
THE C SUBSET 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.The sizeof operator computes the size of any variable or type and can help eliminate
machine-dependent code from your programs. This operator is especially useful wherestructures or unions are concerned.
For the following discussion, assume an implementation, common to many C/C++
compilers, that has the sizes for data types shown here:
Type Size in Bytes
char 1
int 4
double 8
Therefore, the following code will print the numbers 1, 4, and 8 on the screen:
char ch;
int i;double f;
printf("%d", sizeof(ch));
printf("%d", sizeof(i));printf("%d", sizeof(f));
The size of a structure is equal to or greater than the sum of the sizes of its members.
For example,
struct s {
char ch;int i;double f;
} s_var;
Here, sizeof(s_var) is at least 13 (8 + 4 + 1). However, the size of s_var might be
greater because the compiler is allowed to pad a structure in order to achieve word
or paragraph alignment. (A paragraph is 16 bytes.) Since the size of a structure maybe greater than the sum of the sizes of its members, you should always use sizeof
when you need to know the size of a structure.
184 C++: The Complete Reference
Since sizeof is a compile-time operator, all the information necessary to compute
the size of any variable is known at compile time. This is especially meaningful for
unions, because the size of a union is always equal to the size of its largest member.
For example, consider
union u {
char ch;
int i;double f;
} u_var;
Here, the sizeof(u_var) is 8. At run time, it does not matter what u_var is actually
holding. All that matters is the size of its largest member, because any union must
be as large as its largest element.
typedef
You can define new data type names by using the keyword typedef. You are not
actually creating a new data type, but rather defining a new name for an existing
type. This process can help make machine-dependent programs more portable. If
you define your own type name for each machine-dependent data type used by yourprogram, then only the typedef statements have to be changed when compiling for a
new environment. typedef also can aid in self-documenting your code by allowing
descriptive names for the standard data types. The general form of the typedef
statement is
typedef type newname;
where type is any valid data type and newname is the new name for this type. The
new name you define is in addition to, not a replacement for, the existing type name.
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.
Next, you could create a float variable using balance:
balance over_due;
Here, over_due is a floating-point variable of type balance, which is another word
forfloat.
Now that balance has been defined, it can be used in another typedef. For example,
typedef balance overdraft;
tells the compiler to recognize overdraft as another name for balance, which is another
name for float.
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.Chapter 7: Structures, Unions, Enumerations, and User-Defined Types 185THE FOUNDATION OF C++:
THE C SUBSET
This page intentionally left blank
Chapter 8
C-Style Console I/O
187
Copyright © 2003 by The McGraw-Hill Companies. Click here for terms of use.
188 C++: The Complete Reference
C++ supports two complete I/O systems. The first it inherits from C. The second
is the object-oriented I/O system defined by C++. This and the next chapterdiscuss the C-like I/O system. (Part Two examines C++ I/O.) While you
will probably want to use the C++ I/O system for most new projects, C-style I/Ois still quite common, and knowledge of its features is fundamental to a completeunderstanding of C++.
In C, input and output are accomplished through library functions. There are both
console and file I/O functions. Technically , there is little distinction between consoleI/O and file I/O, but conceptually they are in very different worlds. This chapterexamines in detail the console I/O functions. The next chapter presents the file I/Osystem and describes how the two systems relate.
With one exception, this chapter covers only console I/O functions defined by
Standard C++. Standard C++ does not define any functions that perform variousscreen control operations (such as cursor positioning) or that display graphics,because these operations vary widely between machines. Nor does it define anyfunctions that write to a window or dialog box under Windows. Instead, the consoleI/O functions perform only TTY-based output. However, most compilers include intheir libraries screen control and graphics functions that apply to the specific environmentin which the compiler is designed to run. And, of course, you may use C++ to writeWindows programs, but keep in mind that the C++ language does not directly definefunctions that perform these tasks.
The Standard C I/O functions all use the header file stdio.h. C++ programs can
also use the C++-style header <cstdio>.
This chapter refers to the console I/O functions as performing input from the
keyboard and output to the screen. However, these functions actually have thestandard input and standard output of the system as the target and/or source oftheir I/O operations. Furthermore, standard input and standard output may beredirected to other devices. These concepts are covered in Chapter 9.
An Important Application Note
Part One of this book uses the C-like I/O system because it is the only style of I/Othat is defined for the C subset of C++. As explained, C++ also defines its ownobject-oriented I/O system. For most C++ applications, you will want to use theC++-specific I/O system, not the C I/O system described in this chapter. However,an understanding of C-based I/O is important for the following reasons:
șAt some point in your career you may be called upon to write code that isrestricted to the C subset. In this case, you will need to use the C-like I/Ofunctions.
șFor the foreseeable future, C and C++ will coexist. Also, many programs will behybrids of both C and C++ code. Further, it will be common for C programs tobe "upgraded" into C++ programs. Thus, knowledge of both the C and the C++
Chapter 8: C-Style Console I/O 189THE FOUNDATION OF C++:
THE C SUBSETI/O system will be necessary. For example, in order to change the C-style I/O
functions into their C++ object-oriented equivalents, you will need to knowhow both the C and C++ I/O systems operate.
șAn understanding of the basic principles behind the C-like I/O system iscrucial to an understanding of the C++ object-oriented I/O system. (Bothshare the same general concepts.)
șIn certain situations (for example, in very short programs), it may be easier touse C's non-object-oriented approach to I/O than it is to use the object-orientedI/O defined by C++.
In addition, there is an unwritten rule that any C++ programmer must also be a C
programmer. If you don't know how to use the C I/O system, you will be limiting yourprofessional horizons.
Reading and Writing Characters
The simplest of the console I/O functions are getchar( ), which reads a character from
the keyboard, and putchar( ), which prints a character to the screen. The getchar( )
function waits until a key is pressed and then returns its value. The key pressed is alsoautomatically echoed to the screen. The putchar( ) function writes a character to the
screen at the current cursor position. The prototypes for getchar( ) and putchar( ) are
shown here:
int getchar(void);int putchar(int c);
As its prototype shows, the getchar( ) function is declared as returning an integer.
However, you can assign this value to a char variable, as is usually done, because the
character is contained in the low-order byte. (The high-order byte is normally zero.)getchar( ) returns EOF if an error occurs.
In the case of putchar( ), even though it is declared as taking an integer parameter,
you will generally call it using a character argument. Only the low-order byte of itsparameter is actually output to the screen. The putchar( ) function returns the character
written, or EOF if an error occurs. (The EOF macro is defined in stdio.h and is
generally equal to −1.)
The following program illustrates getchar( ) and putchar( ). It inputs characters
from the keyboard and displays them in reverse case⎯that is, it prints uppercase aslowercase and lowercase as uppercase. To stop the program, enter a period.
#include <stdio.h>
#include <ctype.h>
int main(void)
{
char ch;
printf("Enter some text (type a period to quit).\n");
do {
ch = getchar();
if(islower(ch)) ch = toupper(ch);
else ch = tolower(ch);
putchar(ch);
} while (ch != '.');return 0;
}
A Problem with getchar( )
There are some potential problems with getchar( ). Normally, getchar( ) is implemented
in such a way that it buffers input until ENTER is pressed. This is called line-buffered input;
you have to press ENTER before anything you typed is actually sent to your program.
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. Even though Standard C/C++ specify that getchar( ) can be implemented
as an interactive function, it seldom is. Therefore, if the preceding program did notbehave as you expected, you now know why.
Alternatives to getchar( )
getchar( ) might not be implemented by your compiler in such a way that it is useful in
an interactive environment. If this is the case, you might want to use a different functionto read characters from the keyboard. Standard C++ does not define any function that isguaranteed to provide interactive input, but virtually all C++ compilers do. Althoughthese functions are not defined by Standard C++, they are commonly used since getchar( )
does not fill the needs of most programmers.
Two of the most common alternative functions, getch( ) and getche( ), have these
prototypes:
int getch(void);int getche(void);190 C++: The Complete Reference
Chapter 8: C-Style Console I/O 191THE FOUNDATION OF C++:
THE C SUBSETFor most compilers, the prototypes for these functions are found in the header file
conio.h. For some compilers, these functions have a leading underscore. For example,in Microsoft's Visual C++, they are called _getch( ) and _getche( ).
The getch( ) function waits for a keypress, after which it returns immediately.
It does not echo the character to the screen. The getche( ) function is the same as
getch( ) , but the key is echoed. You will frequently see getche( ) orgetch( ) used
instead of getchar( ) when a character needs to be read from the keyboard in an
interactive program. However, if your compiler does not support these alternativefunctions, or if getchar( ) is implemented as an interactive function by your compiler,
you should substitute getchar( ) when necessary.
For example, the previous program is shown here using getch( ) instead of getchar( ):
#include <stdio.h>
#include <conio.h>#include <ctype.h>
int main(void)
{
char ch;
printf("Enter some text (type a period to quit).\n");
do {
ch = getch();
if(islower(ch)) ch = toupper(ch);
else ch = tolower(ch);
putchar(ch);
} while (ch != '.');return 0;
}
When you run this version of the program, each time you press a key, it is
immediately transmitted to the program and displayed in reverse case. Input is no
longer line-buffered. While the code in this book will not make further use of getch( )
orgetche( ), they may be useful in the programs that you write.
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( )
orgets( ). Instead, you must use special versions of the standard functions, such as
cscanf( ) orcgets( ) . You will need to examine the Visual C++ documentation for details.
192 C++: The Complete Reference
Reading and Writing Strings
The next step up in console I/O, in terms of complexity and power, are the functions
gets( ) and puts( ). They enable you to read and write strings of characters.
The gets( ) function reads a string of characters entered at the keyboard and
places them at the address pointed to by its argument. You may type characters atthe keyboard until you press
ENTER . The carriage return does not become part of the
string; instead, a null terminator is placed at the end and gets( ) returns. In fact, you
cannot use gets( ) to return a carriage return (although getchar( ) can do so). You can
correct typing mistakes by using the backspace key before pressing ENTER. The
prototype for gets( ) is
char *gets(char *str);
where stris a character array that receives the characters input by the user. gets( ) also
returns str. The following program reads a string into the array strand prints its length:
#include <stdio.h>
#include <string.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. Thus, it is possible for the user to enter more characters than
the array can hold. 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. One alternative isthefgets( ) function described in the next chapter, which allows you to prevent an
array overrun.
The puts( ) function writes its string argument to the screen followed by a newline.
Its prototype is:
int puts(const char *str);
puts( ) recognizes the same backslash codes as printf( ), such as '\t' for tab. A call
toputs( ) requires far less overhead than the same call to printf( ) because puts( )
can only output a string of characters ⎯it cannot output numbers or do format
conversions. Therefore, puts( ) takes up less space and runs faster than printf( ). For
this reason, the puts( ) function is often used when it is important to have highly
optimized code. The puts( ) function returns EOF if an error occurs. Otherwise, it
returns a nonnegative value. However, when writing to the console, you can usually
assume that no error will occur, so the return value of puts( ) is seldom monitored.
The following statement displays hello:
puts("hello");
Table 8-1 summarizes the basic console I/O functions.
The following program, a simple computerized dictionary , demonstrates several
of the basic console I/O functions. It prompts the user to enter a word and thenchecks to see if the word matches one in its built-in database. If a match is found,the program prints the word's meaning. Pay special attention to the indirection usedin this program. If you have any trouble understanding it, remember that the dic
array is an array of pointers to strings. Notice that the list must be terminated bytwo nulls.Chapter 8: C-Style Console I/O 193THE FOUNDATION OF C++:
THE C SUBSET
Function Operation
getchar( ) Reads a character from the keyboard;waits for carriage return.
getche( ) Reads a character with echo; does notwait for carriage return; not defined byStandard C/C++, but a common extension.
getch( ) Reads a character without echo; does notwait for carriage return; not defined byStandard C/C++, but a common extension.
putchar( ) Writes a character to the screen.
gets( ) Reads a string from the keyboard.
puts( ) Writes a string to the screen.
Table 8-1. The Basic I/O Functions
/* A simple dictionary. */
#include <stdio.h>#include <string.h>#include <ctype.h>
/* list of words and meanings */
char *dic[][40] = {
"atlas", "A volume of maps.","car", "A motorized vehicle.","telephone", "A communication device.","airplane", "A flying machine.","", "" /* 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.");printf("Another? (y/n): ");scanf(" %c%*c", &ch);
} while(toupper(ch) != 'N');
return 0;
}194 C++: The Complete Reference
Formatted Console I/O
The functions printf( ) and scanf( ) perform formatted output and input⎯that is, they
can read and write data in various formats that are under your control. The printf( )
function writes data to the console. The scanf( ) function, its complement, reads data
from the keyboard. Both functions can operate on any of the built-in data types,
including characters, strings, and numbers.
printf( )
The prototype for printf( ) is
int printf(const char *control_string, …);
The printf( ) function returns the number of characters written or a negative value if an
error occurs.
The control_string consists of two types of items. The first type is composed of
characters that will be printed on the screen. The second type contains format specifiersthat define the way the subsequent arguments are displayed. A format specifier beginswith a percent sign and is followed by the format code. There must be exactly the samenumber of arguments as there are format specifiers, and the format specifiers and thearguments are matched in order from left to right. 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.Chapter 8: C-Style Console I/O 195THE FOUNDATION OF C++:
THE C SUBSET
Code Format
%c Character
%d Signed decimal integers
Table 8-2. printf( ) Format Specifiers
Printing Characters
To print an individual character, use %c. This causes its matching argument to be
output, unmodified, to the screen.
To print a string, use %s.
Printing Numbers
You may use either %dor%ito indicate a signed decimal number. These format
specifiers are equivalent; both are supported for historical reasons.
To output an unsigned value, use %u.
The %fformat specifier displays numbers in floating point.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. This specifier causes the number ofcharacters written so far to be put into that integer.
%% Prints a % sign
Table 8-2. printf( ) Format Specifiers (continued)
The %eand %Especifiers tell printf( ) to display a double argument in scientific
notation. Numbers represented in scientific notation take this general form:
x.dddddE+/−yy
If you want to display the letter "E" in uppercase, use the %Eformat; otherwise use %e.
You can tell printf( ) to use either %for%eby using the %gor%G format specifiers.
This causes printf( ) to select the format specifier that produces the shortest output.
Where applicable, use %G if you want "E" shown in uppercase; otherwise, use %g. The
following program demonstrates the effect of the %gformat specifier:
#include <stdio.h>
int main(void)
{
double f;
for(f=1.0; f<1.0e+10; f=f*10)
printf("%g ", f);
return 0;
}
It produces the following output.
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 %oand
%x, respectively. Since the hexadecimal number system uses the letters A through F
to represent the numbers 10 through 15, you can display these letters in either upper-or lowercase. For uppercase, use the %Xformat specifier; for lowercase, use %x, as
shown here:
#include <stdio.h>
int main(void)
{
unsigned num;
for(num=0; num<255; num++) {
printf("%o ", num);
printf("%x ", num);Chapter 8: C-Style Console I/O 197THE FOUNDATION OF C++:
THE C SUBSET
198 C++: The Complete Reference
printf("%X\n", num);
}
return 0;
}
Displaying an Address
If you wish to display an address, use %p. This format specifier causes printf( ) to
display a machine address in a format compatible with the type of addressing used
by the computer. The next program displays the address of sample:
#include <stdio.h>
int sample;int main(void)
{
printf("%p", &sample);
return 0;
}
The %n Specifier
The %nformat specifier is different from the others. Instead of telling printf( ) to
display something, it causes printf( ) to load the variable pointed to by its corresponding
argument with a value equal to the number of characters that have been output. In other
words, the value that corresponds to the %nformat specifier must be a pointer to a
variable. After the call to printf( ) has returned, this variable will hold the number of
characters output, up to the point at which the %nwas encountered. Examine this
program to understand this somewhat unusual format code.
#include <stdio.h>
int main(void)
{
int count;
printf("this%n is a test\n", &count);
printf("%d", count);
Chapter 8: C-Style Console I/O 199THE FOUNDATION OF C++:
THE C SUBSETreturn 0;
}
This program displays this is a test followed by the number 4. The %nformat specifier
is used primarily to enable your program to perform dynamic formatting.
Format Modifiers
Many format specifiers may take modifiers that alter their meaning slightly. For
example, you can specify a minimum field width, the number of decimal places, andleft justification. The format modifier goes between the percent sign and the formatcode. These modifiers are discussed next.
The Minimum Field Width Specifier
An integer placed between the % sign and the format code acts as a minimum field width
specifier . This pads the output with spaces to ensure that it reaches a certain minimum
length. If the string or number is longer than that minimum, it will still be printed infull. The default padding is done with spaces. If you wish to pad with 0's, place a 0before the field width specifier. For example, %05d will pad a number of less than five
digits with 0's so that its total length is five. The following program demonstrates theminimum field width specifier:
#include <stdio.h>
int main(void)
{
double item;
item = 10.12304;printf("%f\n", item);
printf("%10f\n", item);printf("%012f\n", item);
return 0;
}
This program produces the following output:
10.123040
10.123040
00010.123040
200 C++: The Complete Reference
The minimum field width modifier is most commonly used to produce tables in which
the columns line up. For example, the next program produces a table of squares andcubes for the numbers between 1 and 19:
#include <stdio.h>
int main(void)
{
int i;
/* display a table of squares and cubes */
for(i=1; i<20; i++)
printf("%8d %8d %8d\n", i, i*i, i*i*i);
return 0;
}
A sample of its output is shown here:
1 1 12 4 83 9 274 16 645 25 1256 36 2167 49 3438 64 5129 81 729
10 100 100011 121 133112 144 172813 169 219714 196 274415 225 337516 256 409617 289 491318 324 583219 361 6859
The Precision Specifier
The precision specifier follows the minimum field width specifier (if there is one). It
consists of a period followed by an integer. Its exact meaning depends upon the
type of data it is applied to.
Chapter 8: C-Style Console I/O 201THE FOUNDATION OF C++:
THE C SUBSETWhen you apply the precision specifier to floating-point data using the %f,%e,
or%Especifiers, it determines the number of decimal places displayed. For example,
%10.4f displays a number at least ten characters wide with four decimal places.
When the precision specifier is applied to %gor%G, it specifies the number of
significant digits.
Applied to strings, the precision specifier specifies the maximum field length. For
example, %5.7s displays a string at least five and not exceeding seven characters long.
If the string is longer than the maximum field width, the end characters will be truncated.
When applied to integer types, the precision specifier determines the minimum
number of digits that will appear for each number. Leading zeros are added to achieve
the required number of digits.
The following program illustrates the precision specifier:
#include <stdio.h>
int main(void)
{
printf("%.4f\n", 123.1234567);printf("%3.8d\n", 1000);printf("%10.15s\n", "This is a simple test.");
return 0;
}
It produces the following output:
123.1235
00001000This is a simpl
Justifying Output
By default, all output is right-justified. That is, if the field width is larger than the data
printed, the data will be placed on the right edge of the field. You can force output tobe left-justified by placing a minus sign directly after the %. For example, %
10.2f left-
justifies a floating-point number with two decimal places in a 10-character field.
The following program illustrates left justification:
#include <stdio.h>
int main(void)
{
printf("right-justified:%8d\n", 100);printf("left-justified:%-8d\n", 100);
202 C++: The Complete Reference
return 0;
}
Handling Other Data Types
There are two format modifiers that allow printf( ) to display short and long integers.
These modifiers may be applied to the d,i,o,u, and xtype specifiers. The l(ell) modifier
tells printf( ) that a long data type follows. For example, %ld means that a long int is to
be displayed. The hmodifier instructs printf( ) to display a short integer. For instance,
%hu indicates that the data is of type short unsigned int .
The land hmodifiers can also be applied to the nspecifier, to indicate that the
corresponding argument is a pointer to a long or short integer, respectively.
If your compiler fully complies with Standard C++, then you can use the lmodifier
with the cformat to indicate a wide-character. You can also use the lmodifier with the
sformat to indicate a wide-character string.
The Lmodifier may prefix the floating-point specifiers e,f, and g, and indicates that
along double follows.
The * and # Modifiers
The printf( ) function supports two additional modifiers to some of its format specifiers:
*and #.
Preceding g,G,f,E,o re specifiers with a #ensures that there will be a decimal point
even if there are no decimal digits. If you precede the xorXformat specifier with a #,
the hexadecimal number will be printed with a 0xprefix. Preceding the ospecifier with
#causes the number to be printed with a leading zero. You cannot apply #to any other
format specifiers.
Instead of constants, the minimum field width and precision specifiers may be
provided by arguments to printf( ). To accomplish this, use an *as a placeholder. When
the format string is scanned, printf( ) will match the *to an argument in the order in which
they occur. For example, in Figure 8-1, the minimum field width is 10, the precision is 4,
and the value to be displayed is 123.3.
Figure 8-1. How the *is matched to its value
Chapter 8: C-Style Console I/O 203THE FOUNDATION OF C++:
THE C SUBSETThe following program illustrates both #and *:
#include <stdio.h>
int main(void)
{
printf("%x %#x\n", 10, 10);printf("%*.*f", 10, 4, 1234.34);
return 0;
}
scanf( )
scanf( ) is the general-purpose console input routine. It can read all the built-in data
types and automatically convert numbers into the proper internal format. It is much
like the reverse of printf( ). The prototype for scanf( ) is
int scanf(const char *control_string, …);
The scanf( ) function returns the number of data items successfully assigned a value. If
an error occurs, scanf( ) returns EOF . The control_string determines how values are read
into the variables pointed to in the argument list.
The control string consists of three classifications of characters:
șFormat specifiers
șWhite-space characters
șNon-white-space characters
Let's take a look at each of these now.
Format Specifiers
The input format specifiers are preceded by a % sign and tell scanf( ) what type of
data is to be read next. These codes are listed in Table 8-3. The format specifiers arematched, in order from left to right, with the arguments in the argument list. Let'slook at some examples.
Inputting Numbers
To read an integer, use either the %dor%ispecifier. To read a floating-point number
represented in either standard or scientific notation, use %e, %f, or %g.
You can use scanf( ) to read integers in either octal or hexadecimal form by using the
%oand%xformat commands, respectively. The %xmay be in either upper- or lowercase.
204 C++: The Complete Reference
Either way, you may enter the letters "A" through "F" in either case when entering
hexadecimal numbers. The following program reads an octal and hexadecimal number:
#include <stdio.h>
int main(void)
{
int i, j;
scanf("%o%x", &i, &j);
printf("%o %x", i, j);
return 0;
}Code Meaning
%c Read a single character.
%d Read a decimal integer.
%i Read an integer in either decimal, octal, or
hexadecimal format.
%e Read a floating-point number.
%f Read a floating-point number.
%g Read a floating-point number.
%o Read an octal number.
%s Read a string.
%x Read a hexadecimal number.
%p Read a pointer.
%n Receives an integer value equal to the numberof characters read so far.
%u Read an unsigned decimal integer.
%[ ] Scan for a set of characters.
%% Read a percent sign.
Table 8-3. scanf( ) Format Specifiers
Chapter 8: C-Style Console I/O 205THE FOUNDATION OF C++:
THE C SUBSETThe scanf( ) function stops reading a number when the first nonnumeric character is
encountered.
Inputting Unsigned Integers
To input an unsigned integer, use the %uformat specifier. For example,
unsigned num;
scanf("%u", &num);
reads an unsigned number and puts its value into num.
Reading Individual Characters Using scanf( )
As explained earlier in this chapter, you can read individual characters using
getchar( ) or a derivative function. You can also use scanf( ) for this purpose if
you use the %cformat specifier. However, like most implementations of getchar( ),
scanf( ) will generally line-buffer input when the %cspecifier is used. This makes
it somewhat troublesome in an interactive environment.
Although spaces, tabs, and newlines are used as field separators when reading
other types of data, when reading a single character, white-space characters are readlike any other character. For example, with an input stream of "x y ," this code fragment
scanf("%c%c%c", &a, &b, &c);
returns with the character xina, a space in b, and the character yinc.
Reading Strings
The scanf( ) function can be used to read a string from the input stream using the %s
format specifier. Using %scauses scanf( ) to read characters until it encounters a
white-space character. The characters that are read are put into the character arraypointed to by the corresponding argument and the result is null terminated. As itapplies to scanf( ), a white-space character is either a space, a newline, a tab, a vertical
tab, or a form feed. Unlike gets( ), which reads a string until a carriage return is typed,
scanf( ) reads a string until the first white space is entered. This means that you cannot
usescanf( ) to read a string like "this is a test" because the first space terminates the
reading process. To see the effect of the %sspecifier, try this program using the string
"hello there".
#include <stdio.h>
int main(void)
{
char str[80];
printf("Enter a string: ");
scanf("%s", str);printf("Here's your string: %s", str);
return 0;
}
The program responds with only the "hello" portion of the string.
Inputting an Address
To input a memory address, use the %pformat specifier. This specifier causes scanf( )
to read an address in the format defined by the architecture of the CPU. For example,
this program inputs an address and then displays what is at that memory address:
#include <stdio.h>
int main(void)
{
char *p;
printf("Enter an address: ");
scanf("%p", &p);printf("Value at location %p is %c\n", p, *p);
return 0;
}
The %n Specifier
The %nspecifier instructs scanf( ) to assign the number of characters read from the
input stream at the point at which the %nwas encountered to the variable pointed
to by the corresponding argument.
Using a Scanset
The scanf( ) function supports a general-purpose format specifier called a scanset. A
scanset defines a set of characters. When scanf( ) processes a scanset, it will input characters
as long as those characters are part of the set defined by the scanset. The characters read
will be assigned to the character array that is pointed to by the scanset's corresponding206 C++: The Complete Reference
argument. You define a scanset by putting the characters to scan for inside square
brackets. The beginning square bracket must be prefixed by a percent sign. Forexample, the following scanset tells scanf( ) to read only the characters X, Y, and Z.
%[XYZ]
When you use a scanset, scanf( ) continues to read characters, putting them into the
corresponding character array until it encounters a character that is not in the scanset.Upon return from scanf( ), this array will contain a null-terminated string that consists
of the characters that have been read. To see how this works, try this program:
#include <stdio.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 . The program will then display 123 abcd tye.
Because the "t" is not part of the scanset, scanf( ) stops reading characters into str
when it encounters the "t." The remaining characters are put into str2.
You can specify an inverted set if the first character in the set is a ^. The ^instructs
scanf( ) to accept any character that is notdefined by the scanset.
In most implementations you can specify a range using a hyphen. For example, this
tells scanf( ) to accept the characters A through Z:
%[A-Z]
One important point to remember is that the scanset is case sensitive. If you want
to scan for both upper- and lowercase letters, you must specify them individually.
Discarding Unwanted White Space
A white-space character in the control string causes scanf( ) to skip over one or more
leading white-space characters in the input stream. A white-space character is either aChapter 8: C-Style Console I/O 207THE FOUNDATION OF C++:
THE C SUBSET
208 C++: The Complete Reference
space, a tab, vertical tab, form feed, or a newline. In essence, one white-space character
in the control string causes scanf( ) to read, but not store, any number (including zero)
of white-space characters up to the first non-white-space character.
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. For example, "%d,%d" causes scanf( ) to read
an integer, read and discard a comma, and then read another integer. If the specifiedcharacter is not found, scanf( ) terminates. If you wish to read and discard a percent
sign, use %% in the control string.
You Must Pass scanf( ) Addresses
All the variables used to receive values through scanf( ) must be passed by their
addresses. This means that all arguments must be pointers to the variables used asarguments. Recall that this is one way of creating a call by reference, and it allowsa function to alter the contents of an argument. For example, to read an integer intothe variable count, you would use the following scanf( ) call:
scanf("%d", &count);
Strings will be read into character arrays, and the array name, without any index, is
the address of the first element of the array. So, to read a string into the character arraystr, you would use
scanf("%s", str);
In this case, stris already a pointer and need not be preceded by the &operator.
Format Modifiers
As with printf( ), scanf( ) allows a number of its format specifiers to be modified.
The format specifiers can include a maximum field length modifier. This is an
integer, placed between the % and the format specifier, that limits the number ofcharacters read for that field. For example, to read no more than 20 characters intostr, write
scanf("%20s", str);
If the input stream is greater than 20 characters, a subsequent call to input beginswhere this call leaves off. For example, if you enter
ABCDEFGHIJKLMNOPQRSTUVWXYZ
as the response to the scanf( ) call in this example, only the first 20 characters, or up
to the "T," are placed into strbecause of the maximum field width specifier. This means
that the remaining characters, UVWXYZ, have not yet been used. If another scanf( )
call is made, such as
scanf("%s", str);
the letters UVWXYZ are placed into str. Input for a field may terminate before the
maximum field length is reached if a white space is encountered. In this case, scanf( )
moves on to the next field.
To read a long integer, put an l(ell) in front of the format specifier. To read a short
integer, put an hin front of the format specifier. These modifiers can be used with the
d,i,o,u,x, and nformat codes.
By default, the f,e, and gspecifiers instruct scanf( ) to assign data to a float. If you
put an l(ell) in front of one of these specifiers, scanf( ) assigns the data to a double.
Using an Ltells scanf( ) that the variable receiving the data is a long 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 *. For example, given
scanf("%d%*c%d", &x, &y);
you could enter the coordinate pair 10,10. The comma would be correctly read, but not
assigned to anything. Assignment suppression is especially useful when you need to
process only a part of what is being entered.Chapter 8: C-Style Console I/O 209THE FOUNDATION OF C++:
THE C SUBSET
This page intentionally left blank
Chapter 9
File I/O
211
Copyright © 2003 by The McGraw-Hill Companies. Click here for terms of use.
This chapter describes the C file system. As explained in Chapter 8, C++ supports
two complete I/O systems: the one inherited from C and the object-orientedsystem defined by C++. This chapter covers the C file system. (The C++ file
system is discussed in Part Two.) While most new code will use the C++ file system,knowledge of the C file system is still important for the reasons given in the precedingchapter.
C Versus C++ File I/O
There is sometimes confusion over how C's file system relates to C++. First, C++supports the entire Standard C file system. Thus, if you will be porting C code toC++, you will not have to change all of your I/O routines right away. Second, C++defines its own, object-oriented I/O system, which includes both I/O functions andI/O operators. The C++ I/O system completely duplicates the functionality of the CI/O system and renders the C file system redundant. While you will usually want touse the C++ I/O system, you are free to use the C file system if you like. Of course,most C++ programmers elect to use the C++ I/O system for reasons that are madeclear in Part Two of this book.
Streams and Files
Before beginning our discussion of the C file system, it is necessary to know the differencebetween the terms streams and files. The C I/O system supplies a consistent interface
to the programmer independent of the actual device being accessed. That is, the C I/Osystem provides a level of abstraction between the programmer and the device. Thisabstraction is called a stream and the actual device is called a file. It is important to
understand how streams and files interact.
The concept of streams and files is also important to the C++ I/O system discussed inPart Two.
Streams
The C file system is designed to work with a wide variety of devices, including terminals,disk drives, and tape drives. Even though each device is very different, the file systemtransforms each into a logical device called a stream. All streams behave similarly. Becausestreams are largely device independent, the same function that can write to a disk filecan also be used to write to another type of device, such as the console. There are twotypes of streams: text and binary.212 C++: The Complete Reference
Text Streams
Atext stream is a sequence of characters. Standard C allows (but does not require) a
text stream to be organized into lines terminated by a newline character. However,
the newline character is optional on the last line. (Actually, most C/C++ compilers donot terminate text streams with newline characters.) In a text stream, certain charactertranslations may occur as required by the host environment. For example, a newlinemay be converted to a carriage return/linefeed pair. Therefore, there may not be aone-to-one relationship between the characters that are written (or read) and thoseon the external device. Also, because of possible translations, the number of characterswritten (or read) may not be the same as those on the external device.
Binary Streams
Abinary stream is a sequence of bytes that have a one-to-one correspondence to those
in the external device ⎯that is, no character translations occur. Also, the number of
bytes written (or read) is the same as the number on the external device. However,an implementation-defined number of null bytes may be appended to a binary stream.These null bytes might be used to pad the information so that it fills a sector on a disk,for example.
Files
In C/C++, a filemay be anything from a disk file to a terminal or printer. You associate
a stream with a specific file by performing an open operation. Once a file is open,information may be exchanged between it and your program.
Not all files have the same capabilities. For example, a disk file can support random
access while some printers cannot. This brings up an important point about the C I/Osystem: All streams are the same but all files are not.
If the file can support position requests, opening that file also initializes the file
position indicator to the start of the file. As each character is read from or written to
the file, the position indicator is incremented, ensuring progression through the file.
You disassociate a file from a specific stream with a close operation. If you close
a file opened for output, the contents, if any, of its associated stream are written to theexternal device. This process is generally referred to as flushing the stream, and guarantees
that no information is accidentally left in the disk buffer. All files are closed automaticallywhen your program terminates normally, either by main( ) returning to the operating
system or by a call to exit( ) . Files are not closed when a program terminates abnormally,
such as when it crashes or when it calls abort( ).
Each stream that is associated with a file has a file control structure of type FILE.
Never modify this file control block.
If you are new to programming, the separation of streams and files may seem
unnecessary or contrived. Just remember that its main purpose is to provideChapter 9: File I/O 213THE FOUNDATION OF C++:
THE C SUBSET
a consistent interface. You need only think in terms of streams and use only one file
system to accomplish all I/O operations. The I/O system automatically converts theraw input or output from each device into an easily managed stream.
File System Basics
The C file system is composed of several interrelated functions. The most common ofthese are shown in Table 9-1. They require the header stdio.h. C++ programs may also
use the C++-style header <cstdio>.214 C++: The Complete Reference
Name Function
fopen( ) Opens a file.
fclose( ) Closes a file.
putc( ) Writes a character to a file.
fputc( ) Same as putc( ).
getc( ) Reads a character from a file.
fgetc( ) Same as getc( ).
fgets( ) Reads a string from a file.
fputs( ) Writes a string to a file.
fseek( ) Seeks to a specified byte in a file.
ftell( ) Returns the current file position.
fprintf( ) Is to a file what printf( ) is to the console.
fscanf( ) Is to a file what scanf( ) is to the console.
feof( ) Returns true if end-of-file is reached.
ferror( ) Returns true if an error has occurred.
rewind( ) Resets the file position indicator to thebeginning of the file.
remove( ) Erases a file.
fflush( ) Flushes a file.
Table 9-1. Commonly Used C File-System Functions
The header file stdio.h and <cstdio> header provide the prototypes for the I/O
functions and define these three types: size_t, fpos_t, and FILE. The size_t type is some
variety of unsigned integer, as is fpos_t. The FILE type is discussed in the next section.
Also defined in stdio.h and <cstdio> are several macros. The ones relevant to this
chapter are NULL, EOF ,FOPEN_MAX, SEEK_SET ,SEEK_CUR, and SEEK_END.
The NULL macro defines a null pointer. 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.
FOPEN_MAX defines an integer value that determines the number of files that may
be open at any one time. The other macros are used with fseek( ), which is the function
that performs random access on a file.
The File Pointer
The file pointer is the common thread that unites the C I/O system. A file pointer is a
pointer to a structure of type FILE. It points to information that defines various things
about the file, including its name, status, and the current position of the file. In essence,the file pointer identifies a specific file and is used by the associated stream to direct theoperation of the I/O functions. In order to read or write files, your program needs to usefile pointers. 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. Then
it returns the file pointer associated with that file. Most often (and for the rest of thisdiscussion), the file is a disk file. The fopen( ) function has this prototype:
FILE *fopen(const char *filename, const char *mode);
where filename is a pointer to a string of characters that make up a valid filename and
may include a path specification. The string pointed to by mode determines how the file
will be opened. Table 9-2 shows the legal values for mode. Strings like "r+b" may also be
represented as "rb+."Chapter 9: File I/O 215THE FOUNDATION OF C++:
THE C SUBSET
Mode Meaning
r Open a text file for reading.
w Create a text file for writing.
a Append to a text file.
Table 9-2. The Legal Values for Mode
As stated, the fopen( ) function returns a file pointer. Your program should
never alter the value of this pointer. If an error occurs when it is trying to open
the file, fopen( ) returns a null pointer.
The following code uses fopen( ) to open a file named TEST for output.
FILE *fp;
fp = fopen("test", "w");
While technically correct, you will usually see the preceding code written like this:
FILE *fp;
if ((fp = fopen("test","w"))==NULL) {
printf("Cannot open file.\n");
exit(1);
}
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. In general, you will always want toconfirm that fopen( ) succeeded before attempting any other operations on the file.216 C++: The Complete Reference
Mode Meaning
rb Open a binary file for reading.
wb Create a binary file for writing.
ab Append to a binary file.
r+ Open a text file for read/write.
w+ Create a text file for read/write.
a+ Append or create a text file forread/write.
r+b Open a binary file for read/write.
w+b Create a binary file for read/write.
a+b Append or create a binary file forread/write.
Table 9-2. The Legal Values for Mode (continued)
Although most of the file modes are self-explanatory, a few comments are in order.
If, when opening a file for read-only operations, the file does not exist, fopen( ) will fail.
When opening a file using append mode, if the file does not exist, it will be created.
Further, when a file is opened for append, all new data written to the file will be writtento the end of the file. The original contents will remain unchanged. If, when a file isopened for writing, the file does not exist, it will be created. If it does exist, the contentsof the original file will be destroyed and a new file created. The difference betweenmodes r+and w+is that r+will not create a file if it does not exist; however, w+will.
Further, if the file already exists, opening it with w+destroys its contents; opening it
with r+does not.
As Table 9-2 shows, a file may be opened in either text or binary mode. In most
implementations, in text mode, carriage return/linefeed sequences are translated tonewline characters on input. On output, the reverse occurs: newlines are translatedto carriage return/linefeeds. No such translations occur on binary files.
The number of files that may be open at any one time is specified by FOPEN_MAX.
This value will usually be at least 8, but you must check your compiler’s documentationfor its exact value.
Closing a File
The fclose( ) function closes a stream that was opened by a call to fopen( ). It writes
any data still remaining in the disk buffer to the file and does a formal operating-system-level close on the file. Failure to close a stream invites all kinds of trouble,including lost data, destroyed files, and possible intermittent errors in your program.fclose( ) also frees the file control block associated with the stream, making it available
for reuse. There is an operating-system limit to the number of open files you may haveat any one time, so you may have to close one file before opening another.
The fclose( ) function has this prototype:
int fclose(FILE *fp);
where fpis the file pointer returned by the call to fopen( ). A return value of zero signifies
a successful close operation. The function returns EOF if an error occurs. You can use the
standard function ferror( ) (discussed shortly) to determine and report any problems.
Generally, fclose( ) will fail only when a disk has been prematurely removed from the
drive or there is no more space on the disk.
Writing a Character
The C I/O system defines two equivalent functions that output a character: putc( ) and
fputc( ). (Actually, putc( ) is usually implemented as a macro.) There are two identical
functions simply to preserve compatibility with older versions of C. This book usesputc( ), but you can use fputc( ) if you like.Chapter 9: File I/O 217THE FOUNDATION OF C++:
THE C SUBSET
The putc( ) function writes characters to a file that was previously opened for
writing using the fopen( ) function. The prototype of this function is
int putc(int ch, FILE *fp);
where fpis the file pointer returned by fopen( ) and chis the character to be output.
The file pointer tells putc( ) which file to write to. Although chis defined as an int,
only the low-order byte is written.
If aputc( ) operation is successful, it returns the character written. Otherwise, it
returns EOF .
Reading a Character
There are also two equivalent functions that input a character: getc( ) and fgetc( ).
Both are defined to preserve compatibility with older versions of C. This book uses
getc( ) (which is usually implemented as a macro), but you can use fgetc( ) if you like.
The getc( ) function reads characters from a file opened in read mode by fopen( ).
The prototype of getc( ) is
int getc(FILE *fp);
where fpis a file pointer of type FILE returned by fopen( ). getc( ) returns an integer,
but the character is contained in the low-order byte. Unless an error occurs, the high-order byte is zero.
The getc( ) function returns an EOF when the end of the file has been reached.
Therefore, to read to the end of a text file, you could use the following code:
do {
ch = getc(fp);
} while(ch!=EOF);
However, getc( ) also returns EOF if an error occurs. You can use ferror( ) to determine
precisely what has occurred.
Using fopen( ), getc( ), putc( ), and fclose( )
The functions fopen( ), getc( ), putc( ), and fclose( ) constitute the minimal set of file
routines. The following program, KTOD, is a simple example of using putc( ), fopen( ),
and fclose( ). It reads characters from the keyboard and writes them to a disk file until
the user types a dollar sign. The filename is specified from the command line. For example,if you call this program KTOD, typing KTOD TEST allows you to enter lines of text
into the file called TEST.218 C++: The Complete Reference
/* KTOD: A key to disk program. */
#include <stdio.h>#include <stdlib.h>
int main(int argc, char *argv[])
{
FILE *fp;
char ch;
if(argc!=2) {
printf("You forgot to enter the filename.\n");
exit(1);
}
if((fp=fopen(argv[1], "w"))==NULL) {
printf("Cannot open file.\n");
exit(1);
}
do {
ch = getchar();
putc(ch, fp);
} while (ch != '$');
fclose(fp);return 0;
}
The complementary program DTOS reads any text file and displays the contents on
the screen. It demonstrates getc( ).
/* DTOS: A program that reads files and displays them
on the screen. */
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
FILE *fp;char ch;Chapter 9: File I/O 219THE FOUNDATION OF C++:
THE C SUBSET
if(argc!=2) {
printf("You forgot to enter the filename.\n");
exit(1);
}
if((fp=fopen(argv[1], "r"))==NULL) {
printf("Cannot open file.\n");
exit(1);
}
ch = getc(fp); /* read one character */while (ch!=EOF) {
putchar(ch); /* print on screen */
ch = getc(fp);
}
fclose(fp);return 0;
}
To try these two programs, first use KTOD to create a text file. Then read its
contents using DTOS.
Using feof( )
As just described, getc( ) returns EOF when the end of the file has been encountered.
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. First, the file system can operate on both
text and binary files. When a file is opened for binary input, an integer value that willtest equal to EOF may be read. This would cause the input routine to indicate an end-of-file
condition even though the physical end of the file had not been reached. Second, getc( )
returns EOF when it fails and when it reaches the end of the file. Using only the return
value of getc( ), it is impossible to know which occurred. To solve these problems, the
C file system includes the function feof( ), which determines when the end of the file
has been encountered. The feof( ) function has this prototype:
int feof(FILE *fp);
feof( ) returns true if the end of the file has been reached; otherwise, it returns 0. Therefore,
the following routine reads a binary file until the end of the file is encountered:220 C++: The Complete Reference
while(!feof(fp)) ch = getc(fp);
Of course, you can apply this method to text files as well as binary files.
The following program, which copies text or binary files, contains an example of
feof( ). The files are opened in binary mode and feof( ) checks for the end of the file.
/* Copy a file. */
#include <stdio.h>#include <stdlib.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((in=fopen(argv[1], "rb"))==NULL) {
printf("Cannot open source file.\n");
exit(1);
}if((out=fopen(argv[2], "wb")) == NULL) {
printf("Cannot open destination file.\n");exit(1);
}
/* This code actually copies the file. */
while(!feof(in)) {
ch = getc(in);if(!feof(in)) putc(ch, out);
}
fclose(in);
fclose(out);
return 0;
}Chapter 9: File I/O 221THE FOUNDATION OF C++:
THE C SUBSET
222 C++: The Complete Reference
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.
These functions work just like putc( ) and getc( ), but instead of reading or writing
a single character, they read or write strings. 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 strto the specified stream.
It returns EOF if an error occurs.
Thefgets( ) function reads a string from the specified stream until either a newline
character is read or length −1 characters have been read. If a newline is read, it will be part
of the string (unlike the gets( ) function). The resultant string will be null terminated. The
function returns strif successful and a null pointer if an error occurs.
The following program demonstrates fputs( ). It reads strings from the keyboard
and writes them to the file called TEST. To terminate the program, enter a blank line.Since gets( ) does not store the newline character, one is added before each string is
written to the file so that the file can be read more easily.
#include <stdio.h>
#include <stdlib.h>#include <string.h>
int main(void)
{
char str[80];FILE *fp;
if((fp = fopen("TEST", "w"))==NULL) {
printf("Cannot open file.\n");
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;
}
Chapter 9: File I/O 223THE FOUNDATION OF C++:
THE C SUBSETrewind( )
The rewind( ) function resets the file position indicator to the beginning of the file
specified as its argument. That is, it "rewinds" the file. Its prototype is
void rewind(FILE *fp);
where fpis a valid file pointer.
To see an example of rewind( ), you can modify the program from the previous
section so that it displays the contents of the file just created. To accomplish this, the
program rewinds the file after input is complete and then uses fgets( ) to read back
the file. Notice that the file must now be opened in read/write mode using "w+"
for the mode parameter.
#include <stdio.h>
#include <stdlib.h>#include <string.h>
int main(void)
{
char str[80];FILE *fp;
if((fp = fopen("TEST", "w+"))==NULL) {
printf("Cannot open file.\n");
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');
/* now, read and display the file */
rewind(fp); /* reset file position indicator to
start of the file. */
while(!feof(fp)) {
fgets(str, 79, fp);printf(str);
}
224 C++: The Complete Reference
return 0;
}
ferror( )
The ferror( ) function determines whether a file operation has produced an error. The
ferror( ) function has this prototype:
int ferror(FILE *fp);
where fpis a valid file pointer. It returns true if an error has occurred during the last
file operation; otherwise, it returns false. Because each file operation sets the error
condition, ferror( ) should be called immediately after each file operation; otherwise,
an error may be lost.
The following program illustrates ferror( ) by removing tabs from a file and
substituting the appropriate number of spaces. The tab size is defined by TAB_SIZE.
Notice how ferror( ) is called after each file operation. To use the program, specify the
names of the input and output files on the command line.
/* The program substitutes spaces for tabs
in a text file and supplies error checking. */
#include <stdio.h>
#include <stdlib.h>
#define TAB_SIZE 8
#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 <in> <out>\n");
exit(1);
}
if((in = fopen(argv[1], "rb"))==NULL) {
printf("Cannot open %s.\n", argv[1]);
exit(1);
}
if((out = fopen(argv[2], "wb"))==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);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");else printf("Error on output.\n");exit(1);
}Chapter 9: File I/O 225THE FOUNDATION OF C++:
THE C SUBSET
Erasing Files
The remove( ) function erases the specified file. Its prototype is
int remove(const char *filename);
It returns zero if successful; otherwise, it returns a nonzero value.
The following program erases the file specified on the command line. However, it
first gives you a chance to change your mind. A utility like this might be useful to new
computer users.
/* Double check before erasing. */
#include <stdio.h>#include <stdlib.h>#include <ctype.h>
int main(int argc, char *argv[])
{
char str[80];
if(argc!=2) {
printf("usage: xerase <filename>\n");
exit(1);
}
printf("Erase %s? (Y/N): ", argv[1]);
gets(str);
if(toupper(*str)=='Y')
if(remove(argv[1])) {
printf("Cannot erase file.\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);226 C++: The Complete Reference
Chapter 9: File I/O 227THE FOUNDATION OF C++:
THE C SUBSETThis function writes the contents of any buffered data to the file associated with fp.
If you call fflush( ) with fpbeing null, all files opened for output are flushed.
The fflush( ) function returns 0 if successful; otherwise, it returns EOF .
fread( ) and fwrite( )
To read and write data types that are longer than one byte, the C file system provides
two functions: fread( ) and fwrite( ). These functions allow the reading and writing
of blocks of any type of data. Their prototypes are
size_t fread(void *buffer, size_t num_bytes, size_t count, FILE *fp);
size_t fwrite(const void *buffer, size_t num_bytes, size_t count, FILE *fp);
Forfread( ), buffer is a pointer to a region of memory that will receive the data from
the file. For fwrite( ), buffer is a pointer to the information that will be written to the
file. The value of count determines how many items are read or written, with each
item being num_bytes bytes in length. (Remember, the type size_t is defined as some
type of unsigned integer.) Finally, fpis a file pointer to a previously opened stream.
The fread( ) function returns the number of items read. This value may be less than
count if the end of the file is reached or an error occurs. The fwrite( ) function returns
the number of items written. This value will equal count unless an error occurs.
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. For example, the following program writes andthen reads back a double,a nint, and a long to and from a disk file. Notice how it
uses sizeof to determine the length of each data type.
/* Write some non-character data to a disk file
and read it back. */
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fp;double d = 12.23;int i = 101;long l = 123023L;
if((fp=fopen("test", "wb+"))==NULL) {
printf("Cannot open file.\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 this simple program, the return values of fread( ) and fwrite( ) are
ignored. In the real world, however, you should check their return values for errors.
One of the most useful applications of fread( ) and fwrite( ) involves reading
and writing user-defined data types, especially structures. For example, given thisstructure:
struct struct_type {
float balance;
char name[80];
} cust;
the following statement writes the contents of cust to the file pointed to by fp.
fwrite(&cust, sizeof(struct struct_type), 1, fp);228 C++: The Complete Reference
Chapter 9: File I/O 229THE FOUNDATION OF C++:
THE C SUBSET fseek( ) and Random-Access I/O
You can perform random-access read and write operations using the C I/O system with
the help of fseek( ) , which sets the file position indicator. Its prototype is shown here:
int fseek(FILE *fp, long int numbytes, int origin);
Here, fpis a file pointer returned by a call to fopen( ). numbytes is the number of bytes
from origin that will become the new current position, and origin is one of the following
macros:
Origin Macro Name
Beginning of file SEEK_SET
Current position SEEK_CUR
End of file SEEK_END
Therefore, to seek numbytes from the start of the file, origin should be SEEK_SET . To
seek from the current position, use SEEK_CUR; and to seek from the end of the file,
useSEEK_END. The fseek( ) function returns 0 when successful and a nonzero value
if an error occurs.
The following program illustrates fseek( ). It seeks to and displays the specified
byte in the specified file. Specify the filename and then the byte to seek to on thecommand line.
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
FILE *fp;
if(argc!=3) {
printf("Usage: SEEK filename byte\n");
exit(1);
}
if((fp = fopen(argv[1], "rb"))==NULL) {
printf("Cannot open file.\n");
exit(1);
}
if(fseek(fp, atol(argv[2]), SEEK_SET)) {
printf("Seek error.\n");
exit(1);
}
printf("Byte at %ld is %c.\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. For example, assume
that you have a mailing list that consists of structures of type list_type. 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( ). Its prototype is
long int ftell(FILE *fp);
It returns the location of the current position of the file associated with fp. If a failure
occurs, it returns −1.
In general, you will want to use random access only on binary files. The reason
for this is simple. Because text files may have character translations performed onthem, there may not be a direct correspondence between what is in the file and thebyte to which it would appear that you want to seek. The only time you should usefseek( ) with a text file is when seeking to a position previously determined by ftell( ),
using SEEK_SET as the origin.
Remember one important point: Even a file that contains only text can be opened
as a binary file, if you like. There is no inherent restriction about random access on filescontaining text. The restriction applies only to files opened as text files.
fprintf( ) and fscanf( )
In addition to the basic I/O functions already discussed, the C I/O system includesfprintf( ) and fscanf( ). These functions behave exactly like printf( ) and scanf( )
except that they operate with files. The prototypes of fprintf( ) and fscanf( ) are230 C++: The Complete Reference
int fprintf(FILE *fp, const char *control_string,. . .);
int fscanf(FILE *fp, const char *control_string,. . .);
where fpis a file pointer returned by a call to fopen( ). fprintf( ) and fscanf( ) direct
their I/O operations to the file pointed to by fp.
As an example, the following program reads a string and an integer from the keyboard
and writes them to a disk file called TEST. The program then reads the file and displaysthe information on the screen. After running this program, examine the TEST file. Asyou will see, it contains human-readable text.
/* fscanf() – fprintf() example */
#include <stdio.h>#include <io.h>#include <stdlib.h>
int main(void)
{
FILE *fp;char s[80];int t;
if((fp=fopen("test", "w")) == NULL) {
printf("Cannot open file.\n");
exit(1);
}
printf("Enter a string and a number: ");
fscanf(stdin, "%s%d", s, &t); /* read from keyboard */
fprintf(fp, "%s %d", s, t); /* write to file */
fclose(fp);
if((fp=fopen("test","r")) == NULL) {
printf("Cannot open file.\n");
exit(1);
}
fscanf(fp, "%s%d", s, &t); /* read from file */
fprintf(stdout, "%s %d", s, t); /* print on screen */
return 0;
}Chapter 9: File I/O 231THE FOUNDATION OF C++:
THE C SUBSET
A word of warning: Although fprintf( ) and fscanf( ) often are the easiest way to
write and read assorted data to disk files, they are not always the most efficient.
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. So, if speed or file sizeis a concern, you should probably use fread( ) and fwrite( ).
The Standard Streams
As it relates to the C file system, when a program starts execution, three streams areopened automatically. They are stdin (standard input), stdout (standard output), and
stderr (standard error). Normally, these streams refer to the console, but they may be
redirected by the operating system to some other device in environments that supportredirectable I/O. (Redirectable I/O is supported by Windows, DOS, Unix, and OS/2,for example.)
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. For example, putchar( ) could be
defined like this:
int putchar(char c)
{
return putc(c, stdout);
}
In general, stdin is used to read from the console, and stdout and stderr are used to
write to the console.
You may use stdin, stdout, and stderr as file pointers in any function that uses a
variable of type FILE *. For example, you could use fgets( ) to input a string from the
console using a call like this:
char str[255];fgets(str, 80, stdin);
In fact, using fgets( ) in this manner can be quite useful. As mentioned earlier in this
book, 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. When
used with stdin, the fgets( ) function offers a useful alternative because it can limit the
number of characters read and thus prevent array overruns. 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.232 C++: The Complete Reference
Chapter 9: File I/O 233THE FOUNDATION OF C++:
THE C SUBSET#include <stdio.h>
#include <string.h>
int main(void)
{
char str[80];
int i;
printf("Enter a string: ");
fgets(str, 10, stdin);
/* remove newline, if present */
i = strlen(str)-1;if(str[i]=='\n') str[i] = '\0';
printf("This is your string: %s", str);return 0;
}
Keep in mind that stdin, stdout, and stderr are not variables in the normal sense
and may not be assigned a value using fopen( ). 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 Connection
There is actually little distinction between console I/O and file I/O. The console I/Ofunctions described in Chapter 8 actually direct their I/O operations to either stdin or
stdout. In essence, the console I/O functions are simply special versions of their parallelfile functions. The reason they exist is as a convenience to you, the programmer.
As described in the previous section, you can perform console I/O using any of the
file system functions. However, what might surprise you is that you can perform diskfile 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. In environments that allow redirection
of I/O, this means that stdin and stdout could refer to a device other than the keyboard
and screen. For example, consider this program:
#include <stdio.h>
234 C++: The Complete Reference
int main(void)
{
char str[80];
printf("Enter a string: ");
gets(str);printf(str);
return 0;
}
Assume that this program is called TEST. 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. However, in an environment that supports I/O redirection, either stdin, stdout,
or both could be redirected to a file. 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. Executing TESTlike this:
TEST < INPUT > OUTPUT
directs stdin to the file called INPUT and sends output to the file called OUTPUT.
When a program terminates, any redirected streams are reset to their default status.
Using freopen( ) to Redirect the Standard Streams
You can redirect the standard streams by using the freopen( ) function. This function
associates an existing stream with a new file. Thus, you can use it to associate a standardstream 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. The file is opened using the value of mode, which may have
the same values as those used with fopen( ). freopen( ) returns stream if successful
orNULL on failure.
The following program uses freopen( ) to redirect stdout to a file called OUTPUT:
#include <stdio.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. However, performing disk I/O using redirected stdin
and stdout is not as efficient as using functions like fread( ) orfwrite( ).Chapter 9: File I/O 235THE FOUNDATION OF C++:
THE C SUBSET
This page intentionally left blank
Chapter 10
The Preprocessor
and Comments
237
Copyright © 2003 by The McGraw-Hill Companies. Click here for terms of use.
You can include various instructions to the compiler in the source code of a
C/C++ program. 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. This chapter also examines comments.
The Preprocessor
Before beginning, it is important to put the preprocessor in historical perspective.As it relates to C++, the preprocessor is largely a holdover from C. Moreover, theC++ preprocessor is virtually identical to the one defined by C. The main differencebetween C and C++ in this regard is the degree to which each relies upon thepreprocessor. In C, each preprocessor directive is necessary. In C++, some featureshave been rendered redundant by newer and better C++ language elements. In fact,one of the long-term design goals of C++ is the elimination of the preprocessoraltogether. But for now and well into the foreseeable future, the preprocessor willstill be widely used.
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. In addition, each
preprocessing directive must be on its own line. For example,
#include <stdio.h> #include <stdlib.h>
will not work.
#define
The #define directive defines an identifier and a character sequence (i.e., a set of
characters) that will be substituted for the identifier each time it is encountered in the
source file. The identifier is referred to as a macro name and the replacement process as
macro replacement. The general form of the directive is
#define macro-name char-sequence
Notice that there is no semicolon in this statement. There may be any number of spacesbetween the identifier and the character sequence, but once the character sequencebegins, it is terminated only by a newline.238 C++: The Complete Reference
Chapter 10: The Preprocessor and Comments 239THE FOUNDATION OF C++:
THE C SUBSETFor example, if you wish to use the word LEFT for the value 1 and the word RIGHT
for the value 0, you could declare these two #define directives:
#define LEFT 1
#define RIGHT 0
This causes the compiler to substitutea1o ra0each time LEFT orRIGHT is encountered
in your source file. 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. For example, this code defines the values of ONE, TWO, and THREE:
#define ONE 1#define TWO ONE+ONE#define THREE ONE+TWO
Macro substitution is simply the replacement of an identifier by the character
sequence associated with it. Therefore, if you wish to define a standard error message,
you might write something like this:
#define E_MS "standard error on input\n"
/* … */printf(E_MS);
The compiler will actually substitute the string "standard error on input\n" when the
identifier E_MS is encountered. 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. For example,
#define XYZ this is a test
printf("XYZ");
does not print this is a test, but rather XYZ.
240 C++: The Complete Reference
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.
This convention helps anyone reading the program know at a glance that a macro
replacement will take place. 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.
Macros are most frequently used to define names for "magic numbers" that occur
in a program. For example, you may have a program that defines an array and hasseveral routines that access that array. Instead of "hard-coding" the array's size with aconstant, you can define the size using a #define statement and then use that macro
name whenever the array size is needed. In this way, if you need to change the sizeof the array, you will only need to change the #define statement and then recompile
your program. For example,
#define MAX_SIZE 100
/* … */float balance[MAX_SIZE];/* … */for(i=0; i<MAX_SIZE; i++) printf("%f", balance[i]);/* … */for(i=0; i<MAX_SIZE; i++) x =+ balance[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. All
subsequent references to it will be automatically updated when you recompile
your program.
C++ provides a better way of defining constants, which uses the const keyword.
This is described in Part Two.
Defining Function-like Macros
The #define directive has another powerful feature: the macro name can have arguments.
Each time the macro name is encountered, the arguments used in its definition arereplaced by the actual arguments found in the program. This form of a macro is calledafunction-like macro. For example,
#include <stdio.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, ain the macro definition will be substituted with
the values –1 and 1. The parentheses that enclose aensure proper substitution in all
cases. For example, if the parentheses around awere 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.
The use of a function-like macro in place of real functions has one major benefit: It
increases the execution speed of the code because there is no function call overhead.
However, if the size of the function-like macro is very large, this increased speed maybe paid for with an increase in the size of the program because of duplicated code.
Although parameterized macros are a valuable feature, C++ has a better way of creatinginline code, which uses the inline keyword.
#error
The #error directive forces the compiler to stop compilation. It is used primarily for
debugging. The general form of the #error directive is
#error error-message
The error-message is not between double quotes. When the #error directive is encountered,
the error message is displayed, possibly along with other information defined by thecompiler.THE FOUNDATION OF C++:
THE C SUBSETChapter 10: The Preprocessor and Comments 241
#include
The #include directive instructs the compiler to read another source file in addition
to the one that contains the #include directive. The name of the additional source file
must be enclosed between double quotes or angle brackets. For example,
#include "stdio.h"
#include <stdio.h>
both instruct the compiler to read and compile the header for the C I/O system library
functions.
Include files can have #include directives in them. This is referred to as nested
includes. The number of levels of nesting allowed varies between compilers. However,Standard C stipulates that at least eight nested inclusions will be available. StandardC++ recommends that at least 256 levels of nesting be supported.
Whether the filename is enclosed by quotes or by angle brackets determines
how the search for the specified file is conducted. If the filename is enclosed in anglebrackets, the file is searched for in a manner defined by the creator of the compiler.Often, this means searching some special directory set aside for include files. If thefilename is enclosed in quotes, the file is looked for in another implementation-definedmanner. For many compilers, this means searching the current working directory. Ifthe file is not found, the search is repeated as if the filename had been enclosed inangle brackets.
Typically, most programmers use angle brackets to include the standard header
files. The use of quotes is generally reserved for including files specifically related tothe program at hand. However, there is no hard and fast rule that demands this usage.
In addition to files, a C++ program can use the #include directive to include a C++
header . C++ defines a set of standard headers that provide the information necessary
to the various C++ libraries. A header is a standard identifier that might, but neednot, map to a filename. Thus, a header is simply an abstraction that guarantees thatthe appropriate information required by your program is included. Various issuesassociated with headers are described in Part Two.
Conditional Compilation Directives
There are several directives that allow you to selectively compile portions of yourprogram's source code. This process is called conditional compilation and is used widely
by commercial software houses that provide and maintain many customized versionsof one program.242 C++: The Complete Reference
Chapter 10: The Preprocessor and Comments 243THE FOUNDATION OF C++:
THE C SUBSET#if, #else, #elif, and #endif
Perhaps the most commonly used conditional compilation directives are the #if,#else,
#elif, and #endif. These directives allow you to conditionally include portions of code
based upon the outcome of a constant expression.
The general form of #ifis
#ifconstant-expression
statement sequence
#endif
If the constant expression following #ifis true, the code that is between it and #endif is
compiled. Otherwise, the intervening code is skipped. The #endif directive marks the
end of an #ifblock. For example,
/* Simple #if example. */
#include <stdio.h>
#define MAX 100int main(void)
{#if MAX>99
printf("Compiled for array greater than 99.\n");
#endif
return 0;
}
This program displays the message on the screen because MAX is greater than 99.
This example illustrates an important point. The expression that follows the #ifis
evaluated at compile time. Therefore, it must contain only previously defined identifiers
and constants—no variables may be used.
The #else directive works much like the else that is part of the C++ language: it
establishes an alternative if #iffails. The previous example can be expanded as
shown here:
/* Simple #if/#else example. */
#include <stdio.h>
244 C++: The Complete Reference
#define MAX 10
int main(void)
{#if MAX>99
printf("Compiled for array greater than 99.\n");
#else
printf("Compiled for small array.\n");
#endif
return 0;
}
In this case, MAX is defined to be less than 99, so the #ifportion of the code is not
compiled. The #else alternative is compiled, however, and the message Compiled for
small array is displayed.
Notice that #else is used to mark both the end of the #ifblock and the beginning of
the#else block. This is necessary because there can only be one #endif associated with
any #if.
The #elif directive means "else if" and establishes an if-else-if chain for multiple
compilation options. #elif is followed by a constant expression. If the expression is true,
that block of code is compiled and no other #elif expressions are tested. Otherwise, the
next block in the series is checked. The general form for #elif is
#ifexpression
statement sequence
#elifexpression 1
statement sequence
#elifexpression 2
statement sequence
#elifexpression 3
statement sequence
#elifexpression 4
.
..#elif
expression N
statement sequence
#endif
Chapter 10: The Preprocessor and Comments 245THE FOUNDATION OF C++:
THE C SUBSETFor example, the following fragment uses the value of ACTIVE_COUNTRY to
define the currency sign:
#define US 0
#define ENGLAND 1#define JAPAN 2
#define ACTIVE_COUNTRY US#if ACTIVE_COUNTRY == US
char currency[] = "dollar";
#elif ACTIVE_COUNTRY == ENGLAND
char currency[] = "pound";
#else
char currency[] = "yen";
#endif
Standard C states that #ifs and #elifs may be nested at least eight levels. Standard
C++ suggests that at least 256 levels of nesting be allowed. When nested, each #endif,
#else, or #elif associates with the nearest #ifor#elif. For example, the following is
perfectly valid:
#if MAX>100
#if SERIAL_VERSION
int port=198;
#elif
int port=200;
#endif
#else
char out_buffer[100];
#endif
#ifdef and #ifndef
Another method of conditional compilation uses the directives #ifdef and #ifndef, which
mean "if defined" and "if not defined," respectively. The general form of #ifdef is
#ifdef macro-name
statement sequence
#endif
Ifmacro-name has been previously defined in a #define statement, the block of code will
be compiled.
The general form of #ifndef is
#ifndef macro-name
statement sequence
#endif
Ifmacro-name is currently undefined by a #define statement, the block of code is
compiled.
Both #ifdef and #ifndef may use an #else or#elif statement. For example,
#include <stdio.h>
#define TED 10int 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. However, if TED were not defined, Hi
anyone would be displayed, followed by RALPH not defined.
You may nest #ifdefs and #ifndefs to at least eight levels in Standard C. Standard
C++ suggests that at least 256 levels of nesting be supported.
#undef
The #undef directive removes a previously defined definition of the macro name that
follows it. That is, it "undefines" a macro. The general form for #undef is
#undef macro-name246 C++: The Complete Reference
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.
#undef is used principally to allow macro names to be localized to only those
sections of code that need them.
Using defined
In addition to #ifdef, there is a second way to determine if a macro name is defined.
You can use the #ifdirective in conjunction with the defined compile-time operator.
The defined operator has this general form:
defined macro-name
Ifmacro-name is currently defined, then the expression is true. Otherwise, it is false. 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. For example, the
following fragment is compiled only if DEBUG is not defined.
#if !defined DEBUG
printf("Final version!\n");
#endifChapter 10: The Preprocessor and Comments 247THE FOUNDATION OF C++:
THE C SUBSET
One reason for using defined is that it allows the existence of a macro name to be
determined by a #elif statement.
#line
The #line directive changes the contents of _ _LINE_ _ and _ _FILE_ _ , which are
predefined identifiers in the compiler. The _ _LINE_ _ identifier contains the line
number of the currently compiled line of code. The _ _FILE_ _ identifier is a string
that contains the name of the source file being compiled. 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_ _. #line is primarily used for debugging and special applications.
For example, the following code specifies that the line count will begin with 100.
The printf( ) statement displays the number 102 because it is the third line in the
program after the #line 100 statement.
#include <stdio.h>
#line 100 /* reset the line counter */
int main(void) /* line 100 */{ /* line 101 */
printf("%d\n",__LINE__); /* line 102 */
return 0;
}
#pragma
#pragma is an implementation-defined directive that allows various instructions to
be given to the compiler. For example, a compiler may have an option that supports
program execution tracing. A trace option would then be specified by a #pragma
statement. You must check the compiler's documentation for details and options.
The # and ## Preprocessor Operators
There are two preprocessor operators: #and ##.These operators are used with the
#define statement.248 C++: The Complete Reference
The #operator, which is generally called the stringize operator, turns the argument
it precedes into a quoted string. For example, consider this program.
#include <stdio.h>
#define mkstr(s) # sint 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. For example,
#include <stdio.h>#define concat(a, b) a ## bint main(void)
{
int xy = 10;
printf("%d", concat(x, y));return 0;
}
The preprocessor transforms
printf("%d", concat(x, y));Chapter 10: The Preprocessor and Comments 249THE FOUNDATION OF C++:
THE C SUBSET
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. They exist primarily to allow the preprocessor to handle some
special cases.
Predefined Macro Names
C++ specifies six built-in predefined macro names. They are
_ _LINE_ __ _FILE_ __ _DATE_ __ _TIME_ __ _STDC_ __ _cplusplus
The C language defines the first five of these. Each will be described here, in turn.
The _ _LINE_ _ and _ _FILE_ _ macros were described in the discussion of #line.
Briefly, they contain the current line number and filename of the program when it isbeing compiled.
The _ _DATE_ _ macro contains a string of the form month/day/year that is the date
of the translation of the source file into object code.
The _ _TIME_ _ macro contains the time at which the program was compiled. The
time is represented in a string having the form hour:minute:second.
The meaning of _ _STDC_ _ is implementation-defined. Generally, if _ _STDC_ _ is
defined, the compiler will accept only standard C/C++ code that does not contain anynonstandard extensions.
A compiler conforming to Standard C++ will define_ _cplusplus as a value
containing at least six digits. Nonconforming compilers will use a value with five orless digits.
Comments
C89 defines only one style of comment, which begins with the character pair /*and
ends with */.There must be no spaces between the asterisk and the slash. The compiler
Chapter 10: The Preprocessor and Comments 251THE FOUNDATION OF C++:
THE C SUBSETignores any text between the beginning and ending comment symbols. For example,
this program prints only hello on the screen:
#include <stdio.h>
int main(void)
{
printf("hello");/* printf("there"); */
return 0;
}
This style of comment is commonly called a multiline comment because the text of
the comment may extend over two or more lines. For example,
/* this is a
multi-linecomment */
Comments may be placed anywhere in a program, as long as they do not appear in
the middle of a keyword or identifier. For example, this comment is valid:
x = 10+ /* add the numbers */5;
while
swi/*this will not work*/tch(c) { …
is incorrect because a keyword cannot contain a comment. However, you should not
generally place comments in the middle of expressions because it obscures theirmeaning.
Multiline comments may not be nested. That is, one comment may not contain
another comment. For example, this code fragment causes a compile-time error:
/* this is an outer comment
x = y/a;
/* this is an inner comment – and causes an error */
*/
Single-Line Comments
C++ (and C99) supports two types of comments. The first is the multiline comment.
The second is the single-line comment. Single-line comments begin with a //and end
at the end of the line. For example,
// this is a single-line comment
Single line comments are especially useful when short, line-by-line descriptions areneeded. Although they are not technically supported by C89, many C compilers willaccept them anyway, and single-line comments were added to C by C99. One lastpoint: a single-line comment can be nested within a multiline comment.
You should include comments whenever they are needed to explain the operation
of the code. All but the most obvious functions should have a comment at the top thatstates what the function does, how it is called, and what it returns.252 C++: The Complete Reference
Part II
C
Part One examined the C subset of C++. Part Two describes those
features of the language specific to C++. That is, it discusses thosefeatures of C++ that it does not have in common with C. Because manyof the C++ features are designed to support object-orientedprogramming (OOP), Part Two also provides a discussion of its theoryand merits. We will begin with an overview of C++.
Copyright © 2003 by The McGraw-Hill Companies. Click here for terms of use.
This page intentionally left blank
Chapter 11
An Overview of C++
255
Copyright © 2003 by The McGraw-Hill Companies. Click here for terms of use.
This chapter provides an overview of the key concepts embodied in C++. C++ is
an object-oriented programming language, and its object-oriented features arehighly interrelated. In several instances, this interrelatedness makes it difficult
to describe one feature of C++ without implicitly involving several others. Moreover,the object-oriented features of C++ are, in many places, so intertwined that discussionof one feature implies prior knowledge of another. 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++. Theremaining chapters examine C++ in detail.
The Origins of C++
C++ began as an expanded version of C. The C++ extensions were first inventedby Bjarne Stroustrup in 1979 at Bell Laboratories in Murray Hill, New Jersey. Heinitially called the new language "C with Classes." However, in 1983 the name waschanged to C++.
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 program-ming factor: increasing complexity. Over the years, computer programs have becomelarger and more complex. Even though C is an excellent programming language, it hasits limits. In C, once a program exceeds from 25,000 to 100,000 lines of code, it becomesso complex that it is difficult to grasp as a totality. The purpose of C++ is to allow thisbarrier to be broken. The essence of C++ is to allow the programmer to comprehendand manage larger, more complex programs.
Most additions made by Stroustrup to C support object-oriented programming,
sometimes referred to as OOP . (See the next section for a brief explanation of object-oriented programming.) Stroustrup states that some of C++'s object-oriented featureswere inspired by another object-oriented language called Simula67. Therefore, C++represents the blending of two powerful programming methods.
Since C++ was first invented, it has undergone three major revisions, with each
adding to and altering the language. The first revision was in 1985 and the second in1990. The third occurred during the standardization of C++. Several years ago, workbegan on a standard for C++. Toward that end, a joint ANSI (American NationalStandards Institute) and ISO (International Standards Organization) standardizationcommittee was formed. The first draft of the proposed standard was created onJanuary 25, 1994. In that draft, the ANSI/ISO C++ committee (of which I was a member)kept the features first defined by Stroustrup and added some new ones as well. But ingeneral, this initial draft reflected the state of C++ at the time.
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 TemplateLibrary (STL) by Alexander Stepanov. The STL is a set of generic routines that you canuse to manipulate data. It is both powerful and elegant, but also quite large. Subsequent256 C++: The Complete Reference
to the first draft, the committee voted to include the STL in the specification for C++. The
addition of the STL expanded the scope of C++ well beyond its original definition. Whileimportant, the inclusion of the STL, among other things, slowed the standardizationof C++.
It is fair to say that the standardization of C++ took far longer than anyone had
expected when it began. In the process, many new features were added to the languageand many small changes were made. In fact, the version of C++ defined by the C++committee is much larger and more complex than Stroustrup's original design. Thefinal draft was passed out of committee on November 14, 1997 and an ANSI/ISOstandard for C++ became a reality in 1998. This specification for C++ is commonlyreferred to as Standard C++.
The material in this book describes Standard C++, including all of its newest
features. This is the version of C++ created by the ANSI/ISO standardizationcommittee, and it is the one that is currently accepted by all major compilers.
What Is Object-Oriented Programming?
Since object-oriented programming (OOP) drove the creation of C++, it is necessaryto understand its foundational principles. OOP is a powerful way to approach the jobof programming. Programming methodologies have changed dramatically since theinvention of the computer, primarily to accommodate the increasing complexity ofprograms. For example, when computers were first invented, programming was doneby toggling in the binary machine instructions using the computer's front panel. Aslong as programs were just a few hundred instructions long, this approach worked.As programs grew, assembly language was invented so that a programmer could dealwith larger, increasingly complex programs, using symbolic representations of themachine instructions. As programs continued to grow, high-level languages wereintroduced that gave the programmer more tools with which to handle complexity.The first widespread language was, of course, FORTRAN. Although FORTRAN wasa very impressive first step, it is hardly a language that encourages clear, easy-to-understand programs.
The 1960s gave birth to structured programming. This is the method encouraged by
languages such as C and Pascal. The use of structured languages made it possible towrite moderately complex programs fairly easily. Structured languages are characterizedby their support for stand-alone subroutines, local variables, rich control constructs, andtheir lack of reliance upon the GOTO. Although structured languages are a powerful tool,they reach their limit when a project becomes too large.
Consider this: At each milestone in the development of programming, techniques
and tools were created to allow the programmer to deal with increasingly greatercomplexity. Each step of the way, the new approach took the best elements of theprevious methods and moved forward. Prior to the invention of OOP , many projectswere nearing (or exceeding) the point where the structured approach no longerC++Chapter 11: An Overview of C++ 257
258 C++: The Complete Reference
worked. Object-oriented methods were created to help programmers break through
these barriers.
Object-oriented programming took the best ideas of structured programming
and combined them with several new concepts. The result was a different way oforganizing a program. In the most general sense, a program can be organized inone of two ways: around its code (what is happening) or around its data (who is beingaffected). Using only structured programming techniques, programs are typicallyorganized around code. This approach can be thought of as "code acting on data."For example, a program written in a structured language such as C is defined byits functions, any of which may operate on any type of data used by the program.
Object-oriented programs work the other way around. They are organized
around data, with the key principle being "data controlling access to code." In anobject-oriented language, you define the data and the routines that are permittedto act on that data. Thus, a data type defines precisely what sort of operations canbe applied to that data.
To support the principles of object-oriented programming, all OOP languages
have three traits in common: encapsulation, polymorphism, and inheritance. Let'sexamine each.
Encapsulation
Encapsulation is the mechanism that binds together code and the data it manipulates,
and keeps both safe from outside interference and misuse. In an object-orientedlanguage, code and data may be combined in such a way that a self-contained "blackbox" is created. When code and data are linked together in this fashion, an object is
created. In other words, an object is the device that supports encapsulation.
Within an object, code, data, or both may be private to that object or public. Private
code or data is known to and accessible only by another part of the object. That is,private code or data may not be accessed by a piece of the program that exists outsidethe object. When code or data is public, other parts of your program may access it eventhough it is defined within an object. Typically, the public parts of an object are used toprovide a controlled interface to the private elements of the object.
For all intents and purposes, an object is a variable of a user-defined type. It may
seem strange that an object that links both code and data can be thought of as avariable. However, in object-oriented programming, this is precisely the case. Eachtime you define a new type of object, you are creating a new data type. Each specificinstance of this data type is a compound variable.
Polymorphism
Object-oriented programming languages support polymorphism, which is characterized
by the phrase "one interface, multiple methods." In simple terms, polymorphism is theattribute that allows one interface to control access to a general class of actions. The
Chapter 11: An Overview of C++ 259C++specific action selected is determined by the exact nature of the situation. A real-world
example of polymorphism is a thermostat. No matter what type of furnace your househas (gas, oil, electric, etc.), the thermostat works the same way. In this case, the thermostat(which is the interface) is the same no matter what type of furnace (method) you have.For example, if you want a 70-degree temperature, you set the thermostat to 70 degrees.It doesn't matter what type of furnace actually provides the heat.
This same principle can also apply to programming. For example, you might
have a program that defines three different types of stacks. One stack is used forinteger values, one for character values, and one for floating-point values. Becauseof polymorphism, you can define one set of names, push( ) and pop( ), that can be used
for all three stacks. In your program you will create three specific versions of thesefunctions, one for each type of stack, but names of the functions will be the same. Thecompiler will automatically select the right function based upon the data being stored.Thus, the interface to a stack—the functions push( ) and pop( ) —are the same no
matter which type of stack is being used. The individual versions of these functionsdefine the specific implementations (methods) for each type of data.
Polymorphism helps reduce complexity by allowing the same interface to be used
to access a general class of actions. It is the compiler's job to select the specific action
(i.e., method) as it applies to each situation. You, the programmer, don't need to dothis selection manually. You need only remember and utilize the general interface.
The first object-oriented programming languages were interpreters, so poly-
morphism was, of course, supported at run time. However, C++ is a compiledlanguage. Therefore, in C++, both run-time and compile-time polymorphismare supported.
Inheritance
Inheritance is the process by which one object can acquire the properties of another
object. This is important because it supports the concept of classification. If you think
about it, most knowledge is made manageable by hierarchical classifications. Forexample, a Red Delicious apple is part of the classification apple, which in turn is part
of the fruit class, which is under the larger class food. Without the use of classifications,
each object would have to define explicitly all of its characteristics. However, throughthe use of classifications, an object need only define those qualities that make it uniquewithin its class. It is the inheritance mechanism that makes it possible for one object tobe a specific instance of a more general case. As you will see, inheritance is an importantaspect of object-oriented programming.
Some C++ Fundamentals
In Part One, the C subset of C++ was described and C programs were used todemonstrate those features. From this point forward, all examples will be "C++
programs." That is, they will be making use of features unique to C++. For ease of
discussion, we will refer to these C++-specific features simply as "C++ features" fromnow on.
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 someimportant respects. Most of the differences have to do with taking advantage of C++'sobject-oriented capabilities. But C++ programs differ from C programs in other ways,including how I/O is performed and what headers are included. Also, most C++programs share a set of common traits that clearly identify them asC++ programs.
Before moving on to C++'s object-oriented constructs, an understanding of thefundamental elements of a C++ program is required.
This section describes several issues relating to nearly all C++ programs. Along the
way, some important differences with C and earlier versions of C++ are pointed out.
A Sample C++ Program
Let's start with the short sample C++ program shown here.
#include <iostream>
using namespace std;
int main()
{
int i;
cout << "This is output.\n"; // this is a single line comment
/* you can still use C style comments */
// input a number using >>
cout << "Enter a number: ";cin >> i;
// now, output a number using <<
cout << i << " squared is " << i*i << "\n";
return 0;
}
As you can see, this program looks much different from the C subset programs
found in Part One. A line-by-line commentary will be useful. To begin, the header
<iostream> is included. This header supports C++-style I/O operations. ( <iostream>
is to C++ what stdio.h is to C.) Notice one other thing: there is no .hextension to the260 C++: The Complete Reference
name iostream. The reason is that <iostream> is one of the modern-style headers
defined by Standard C++. Modern C++ headers do not use the .hextension.
The next line in the program is
using namespace std;
This tells the compiler to use the stdnamespace. Namespaces are a recent addition
to C++. A namespace creates a declarative region in which various program elements can
be placed. Namespaces help in the organization of large programs. The using statement
informs the compiler that you want to use the stdnamespace. This is the namespace in
which the entire Standard C++ library is declared. By using the stdnamespace you
simplify access to the standard library. The programs in Part One, which use only the Csubset, don't need a namespace statement because the C library functions are alsoavailable in the default, global namespace.
Since both new-style headers and namespaces are recent additions to C++, you mayencounter older code that does not use them. Also, if you are using an older compiler,it may not support them. Instructions for using an older compiler are found later inthis chapter.
Now examine the following line.
int main()
Notice that the parameter list in main( ) is empty. In C++, this indicates that main( )
has no parameters. This differs from C. In C, a function that has no parameters mustusevoid in its parameter list, as shown here:
int main(void)
This was the way main( ) was declared in the programs in Part One. However, in
C++, the use of void is redundant and unnecessary. As a general rule, in C++ when
a function takes no parameters, its parameter list is simply empty; the use of void is
not required.
The next line contains two C++ features.
cout << "This is output.\n"; // this is a single line comment
First, the statement
cout << "This is output.\n";Chapter 11: An Overview of C++ 261C++
262 C++: The Complete Reference
causes This is output. to be displayed on the screen, followed by a carriage return-
linefeed combination. In C++, the << has an expanded role. It is still the left shift
operator, but when it is used as shown in this example, it is also an output operator . The
word cout is an identifier that is linked to the screen. (Actually, like C, C++ supports
I/O redirection, but for the sake of discussion, assume that cout refers to the screen.)
You can use cout and the <<to output any of the built-in data types, as well as strings
of characters.
Note that you can still use printf( ) or any other of C's I/O functions in a C++
program. However, most programmers feel that using <<is more in the spirit of C++.
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 thatyou define (something that you cannot do using printf( )).
What follows the output expression is a C++ single-line comment. As mentioned
in Chapter 10, C++ defines two types of comments. First, you may use a multilinecomment, which works the same in C++ as in C. You can also define a single-line commentby using //; whatever follows such a comment is ignored by the compiler until the end of
the line is reached. In general, C++ programmers use multiline comments when a longercomment is being created and use single-line comments when only a short remark isneeded.
Next, the program prompts the user for a number. The number is read from the
keyboard with this statement:
cin >> i;
In C++, the >>operator still retains its right shift meaning. However, when used
as shown, it also is C++'s input operator . This statement causes ito be given a value
read from the keyboard. The identifier cinrefers to the standard input device, which
is usually the keyboard. In general, you can use cin >> to input a variable of any of
the basic data types plus strings.
The line of code just described is not misprinted. Specifically, there is not supposed to bean&in front of the i. 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. Thismeans preceding the variable name with the "address of" operator, &. However, because of
the way the >> operator is implemented in C++, you do not need (in fact, must not use)the&. The reason for this is explained in Chapter 13.
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 >>. However, as with
cout, most programmers feel that cin >> is more in the spirit of C++.
Another interesting line in the program is shown here:
cout << i << "squared is " << i*i << "\n";
Assuming that ihas the value 10, this statement causes the phrase 10 squared is 100
to be displayed, followed by a carriage return-linefeed. As this line illustrates, you can
run together several <<output operations.
The program ends with this statement:
return 0;
This causes zero to be returned to the calling process (which is usually the operatingsystem). This works the same in C++ as it does in C. Returning zero indicates that theprogram terminated normally. Abnormal program termination should be signaled byreturning a nonzero value. You may also use the values EXIT_SUCCESS and EXIT_
FAILURE if you like.
A Closer Look at the I/O Operators
As stated, when used for I/O, the <<and >>operators are capable of handling any
of C++'s built-in data types. For example, this program inputs a float, a double, and
a string and then outputs them:
#include <iostream>
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 prompted for the
string. When the program redisplays the information you entered, only the word "This"
will be displayed. The rest of the string is not shown because the >>operator stops
reading input when the first white-space character is encountered. Thus, "is a test" isChapter 11: An Overview of C++ 263C++
never read by the program. This program also illustrates that you can string together
several input operations in a single statement.
The C++ I/O operators recognize the entire set of backslash character constants
described in Chapter 2. For example, it is perfectly acceptable to write
cout << "A\tB\tC";
This statement outputs the letters A, B, and C, separated by tabs.
Declaring Local Variables
If you come from a C background, you need to be aware of an important differencebetween C and C++ regarding when local variables can be declared. In C89, you mustdeclare all local variables used within a block at the start of that block. You cannot declarea variable in a block after an "action" statement has occurred. For example, in C89, thisfragment is incorrect:
/* Incorrect in C89. OK in C++. */
int f(){
int i;i = 10;
int j; /* won't compile as a C program */
j = i*2;
return j;
}
In a C89 program, this function is in error because the assignment intervenes between
the declaration of iand that of j. However, when compiling it as a C++ program, this
fragment is perfectly acceptable. In C++ (and C99) you may declare local variables atany point within a block—not just at the beginning.
Here is another example. This version of the program from the preceding section
declares strjust before it is needed.
#include <iostream>
using namespace std;
int main()
{
float f;264 C++: The Complete Reference
Chapter 11: An Overview of C++ 265C++double d;
cout << "Enter two floating point numbers: ";cin >> f >> d;
cout << "Enter a string: ";
char str[80]; // str declared here, just before 1st usecin >> 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. Since much of the philosophy behind C++ is the encapsulation of
code and data, it makes sense that you can declare variables close to where they are usedinstead of just at the beginning of the block. In the preceding example, the declarationsare separated simply for illustration, but it is easy to imagine more complex examples inwhich this feature of C++ is more valuable.
Declaring variables close to where they are used can help you avoid accidental side
effects. However, the greatest benefit of declaring variables at the point of first use isgained in large functions. Frankly, in short functions (like many of the examples in thisbook), there is little reason not to simply declare variables at the start of a function. Forthis reason, this book will declare variables at the point of first use only when it seemswarranted by the size or complexity of a function.
There is some debate as to the general wisdom of localizing the declaration of
variables. Opponents suggest that sprinkling declarations throughout a block makesit harder, not easier, for someone reading the code to find quickly the declarationsof all variables used in that block, making the program harder to maintain. For thisreason, some C++ programmers do not make significant use of this feature. This bookwill not take a stand either way on this issue. However, when applied properly,especially in large functions, declaring variables at the point of their first use canhelp you create bug-free programs more easily.
No Default to int
A few years ago, there was a change to C++ that may affect older C++ code as well as Ccode being ported to C++. Both C89 and the original specification for C++ state thatwhen no explicit type is specified in a declaration, type intis assumed. However, the
"default-to-int" rule was dropped from C++ during standardization. C99 also drops thisrule. However, there is still a large body of C and older C++ code that uses this rule.
The most common use of the "default-to-int" rule is with function return types. It
was common practice to not specify intexplicitly when a function returned an integer
result. For example, in C89 and older C++ code the following function is valid.
func(int i)
{
return i*i;
}
In Standard C++, this function must have the return type of intspecified, as shown here.
int func(int i){
return i*i;
}
As a practical matter, nearly all C++ compilers still support the "default-to-int" rule for
compatibility with older code. However, you should not use this feature for new codebecause it is no longer allowed.
The bool Data Type
C++ defines a built-in Boolean type called bool. Objects of type bool can store only the
values true orfalse, which are keywords defined by C++. As explained in Part One,
automatic conversions take place which allow bool values to be converted to integers,
and vice versa. Specifically, any non-zero value is converted to true and zero is converted
tofalse. The reverse also occurs; true is converted to 1 and false is converted to zero.
Thus, the fundamental concept of zero being false and non-zero being true is still fullyentrenched in the C++ language.
Although C89 (the C subset of C++) does not define a Boolean type, C99 adds tothe C language a type called _Bool, which is capable of storing the values 1 and 0
(i.e., true/false). Unlike C++, C99 does not define true andfalse as keywords.
Thus, _Bool as defined by C99 is incompatible with bool as defined by C++.
The reason that C99 specifies _Bool rather than bool as a keyword is that many
preexisting C programs have aleady defined their own custom versions of bool. By
defining the Boolean type as _Bool, C99 avoids breaking this preexisting code. However,
it is possible to achieve compatibility between C++ and C99 on this point because C99adds the header <stdbool.h> which defines the macros bool, true, and false. By
including this header, you can create code that is compatible with both C99 and C++.266 C++: The Complete Reference
C++Chapter 11: An Overview of C++ 267
Old-Style vs. Modern C++
As explained, C++ underwent a rather extensive evolutionary process during its
development and standardization. As a result, there are really two versions of C++.The first is the traditional version that is based upon Bjarne Stroustrup's originaldesigns. The second is Standard C++, which was created by Stroustrup and theANSI/ISO standardization committee. While these two versions of C++ are verysimilar at their core, Standard C++ contains several enhancements not found intraditional C++. Thus, Standard C++ is essentially a superset of traditional C++.
This book describes Standard C++. This is the version of C++ defined by the
ANSI/ISO standardization committee and the one implemented by all modern C++compilers. The code in this book reflects the contemporary coding style and practicesas encouraged by Standard C++. However, if you are using an older compiler, itmay not accept all of the programs in this book. Here's why. During the process ofstandardization, the ANSI/ISO committee added many new features to the language.As these features were defined, they were implemented by compiler developers. Ofcourse, there is always a lag time between when a new feature is added to the languageand when it is available in commercial compilers. Since features were added to C++over a period of years, an older compiler might not support one or more of them. Thisis important because two recent additions to the C++ language affect every programthat you will write—even the simplest. If you are using an older compiler that doesnot accept these new features, don't worry. There is an easy work-around, which isdescribed here.
The key differences between old-style and modern code involve two features:
new-style headers and the namespace statement. To understand the differences, we
will begin by looking at two versions of a minimal, do-nothing C++ program. Thefirst version shown here reflects the way C++ programs were written using old-stylecoding.
/*
An old-style C++ program.
*/
#include <iostream.h>int main()
{
return 0;
}
Pay special attention to the #include statement. It includes the file iostream.h, not the
header <iostream>. Also notice that no namespace statement is present.
Here is the second version of the skeleton, which uses the modern style.
/*
A modern-style C++ program that uses
the new-style headers and a namespace.
*/#include <iostream>using namespace std;
int main()
{
return 0;
}
This version uses the C++-style header and specifies a namespace. Both of these
features were mentioned in passing earlier. Let's look closely at them now.
The New C++ Headers
As you know, when you use a library function in a program, you must include itsheader. This is done using the #include statement. For example, in C, to include the
header for the I/O functions, you include stdio.h with a statement like this:
#include <stdio.h>
Here, stdio.h is the name of the file used by the I/O functions, and the preceding
statement causes that file to be included in your program. The key point is that this#include statement normally includes a file.
When C++ was first invented and for several years after that, it used the same style
of headers as did C. That is, it used header files. In fact, Standard C++ still supports
C-style headers for header files that you create and for backward compatibility.However, Standard C++ created a new kind of header that is used by the StandardC++ library. The new-style headers do not specify filenames. Instead, they simply
specify standard identifiers that may be mapped to files by the compiler, althoughthey need not be. The new-style C++ headers are an abstraction that simply guaranteethat the appropriate prototypes and definitions required by the C++ library havebeen declared.
Since the new-style headers are not filenames, they do not have a .hextension. They
consist solely of the header name contained between angle brackets. For example, hereare some of the new-style headers supported by Standard C++.
<iostream> <fstream> <vector> <string>268 C++: The Complete Reference
Chapter 11: An Overview of C++ 269C++The new-style headers are included using the #include statement. The only difference
is that the new-style headers do not necessarily represent filenames.
Because C++ includes the entire C function library, it still supports the standard
C-style header files associated with that library. That is, header files such as stdio.h
orctype.h are still available. However, Standard C++ also defines new-style headers
that you can use in place of these header files. The C++ versions of the C standard
headers simply add a "c" prefix to the filename and drop the .h. For example, the C++
new-style header for math.h is<cmath>. The one for string.h is<cstring>. Although it
is currently permissible to include a C-style header file when using C library functions,this approach is deprecated by Standard C++ (that is, it is not recommended). Forthis reason, from this point forward, this book will use new-style C++ headers in all#include statements. If your compiler does not support new-style headers for the C
function library, then simply substitute the old-style, C-like headers.
Since the new-style header is a relatively recent addition to C++, you will still find
many, many older programs that don't use it. These programs employ C-style headers,in which a filename is specified. As the old-style skeletal program shows, the traditionalway to include the I/O header is as shown here.
#include <iostream.h>
This causes the file iostream.h to be included in your program. In general, an old-style
header file will use the same name as its corresponding new-style header with a.happended.
As of this writing, all C++ compilers support the old-style headers. However, the
old-style headers have been declared obsolete and their use in new programs is notrecommended. This is why they are not used in this book.
While still common in existing C++ code, old-style headers are obsolete.
Namespaces
When you include a new-style header in your program, the contents of that headerare contained in the stdnamespace. A namespace is simply a declarative region. The
purpose of a namespace is to localize the names of identifiers to avoid name collisions.Elements declared in one namespace are separate from elements declared in another.Originally, the names of the C++ library functions, etc., were simply put into the globalnamespace (as they are in C). However, with the advent of the new-style headers, thecontents of these headers were placed in the stdnamespace. We will look closely at
namespaces later in this book. For now, you won't need to worry about them becausethe statement
using namespace std;
270 C++: The Complete Reference
brings the stdnamespace into visibility (i.e., it puts stdinto the global namespace).
After this statement has been compiled, there is no difference between working with
an old-style header and a new-style one.
One other point: for the sake of compatibility, when a C++ program includes a C
header, such as stdio.h, its contents are put into the global namespace. This allows a
C++ compiler to compile C-subset programs.
Working with an Old Compiler
As explained, both namespaces and the new-style headers are fairly recent additionsto the C++ language, added during standardization. While all new C++ compilerssupport these features, older compilers may not. When this is the case, your compilerwill report one or more errors when it tries to compile the first two lines of the sampleprograms in this book. If this is the case, there is an easy work-around: simply use anold-style header and delete the namespace statement. That is, just replace
#include <iostream>
using namespace std;
with
#include <iostream.h>
This change transforms a modern program into an old-style one. Since the old-style
header reads all of its contents into the global namespace, there is no need for anamespace statement.
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. Your
C++ compiler will be able to compile them just fine. However, for new programs,you should use the modern style because it is the only style of program that complieswith the C++ Standard. While old-style programs will continue to be supported formany years, they are technically noncompliant.
Introducing C++ Classes
This section introduces C++'s most important feature: the class. In C++, to create anobject, you first must define its general form by using the keyword class. A class is
similar syntactically to a structure. Here is an example. The following class definesa type called stack, which will be used to create a stack:
#define SIZE 100
// This creates the class stack.
class stack {
int stck[SIZE];
int tos;
public:
void init();
void push(int i);int pop();
};
Aclass may contain private as well as public parts. By default, all items defined in
aclass are private. For example, the variables stck and tosare private. This means that
they cannot be accessed by any function that is not a member of the class. This is one
way that encapsulation is achieved—access to certain items of data may be tightly
controlled by keeping them private. Although it is not shown in this example, youcan also define private functions, which then may be called only by other membersof the class.
To make parts of a class public (that is, accessible to other parts of your program),
you must declare them after the public keyword. All variables or functions defined
after public can be accessed by all other functions in the program. Essentially, the rest
of your program accesses an object through its public functions. Although you canhave public variables, good practice dictates that you should try to limit their use.Instead, you should make all data private and control access to it through publicfunctions. One other point: Notice that the public keyword is followed by a colon.
The functions init( ), push( ), and pop( ) are called member functions because they
are part of the class stack. The variables stck and tosare called member variables (ordata
members). Remember, an object forms a bond between code and data. Only memberfunctions have access to the private members of their class. Thus, only init( ), push( ),
and pop( ) may access stck and tos.
Once you have defined a class, you can create an object of that type by using the
class name. In essence, the class name becomes a new data type specifier. 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. In this
case, mystack is an instance of stack. You may also create objects when the class is
defined by putting their names after the closing curly brace, in exactly the same wayas you would with a structure.
To review: In C++, class creates a new data type that may be used to create objects
of that type. Therefore, an object is an instance of a class in just the same way that someother variable is an instance of the intdata type, for example. Put differently, a class is aChapter 11: An Overview of C++ 271C++
272 C++: The Complete Reference
logical abstraction, while an object is real. (That is, an object exists inside the memory of
the computer.)
The general form of a simple class declaration is
class class-name {
private data and functions
public:
public data and functions
}object name list;
Of course, the object name list may be empty.
Inside the declaration of stack, member functions were identified using their
prototypes. In C++, all functions must be prototyped. Prototypes are not optional.The prototype for a member function within a class definition serves as that function'sprototype in general.
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 withthe name of the class of which it is a member. For example, here is one way to code thepush( ) function:
void stack::push(int i)
{
if(tos==SIZE) {
cout << "Stack is full.\n";return;
}stck[tos] = i;tos++;
}
The ::is called the scope resolution operator . Essentially, it tells the compiler that this
version of push( ) belongs to the stack class or, put differently, that this push( ) is in
stack's scope. In C++, several different classes can use the same function name. The
compiler knows which function belongs to which class because of the scope resolutionoperator.
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. To do so, usethe object's name, followed by the dot operator, followed by the name of the member.This rule applies whether you are accessing a data member or a function member. Forexample, this calls init( ) for object stack1.
stack stack1, stack2;
stack1.init();
Chapter 11: An Overview of C++ 273C++This fragment creates two objects, stack1 and stack2, and initializes stack1.
Understand that stack1 and stack2 are two separate objects. This means, for example,
that initializing stack1 does notcause stack2 to be initialized as well. The only
relationship stack1 has with stack2 is that they are objects of the same type.
Within a class, one member function can call another member function or refer to
a data member directly, without using the dot operator. It is only when a member is
referred to by code that does not belong to the class that the object name and the dotoperator must be used.
The program shown here puts together all the pieces and missing details and
illustrates the stack class:
#include <iostream>
using namespace std;
#define SIZE 100// This creates the class stack.
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;
}stck[tos] = i;tos++;
}
int stack::pop()
{
if(tos==0) {
274 C++: The Complete Reference
cout << "Stack underflow.\n";
return 0;
}
tos–;return stck[tos];
}
int main()
{
stack stack1, stack2; // create two stack objects
stack1.init();
stack2.init();
stack1.push(1);
stack2.push(2);
stack1.push(3);
stack2.push(4);
cout << stack1.pop() << " ";
cout << stack1.pop() << " ";cout << stack2.pop() << " ";cout << stack2.pop() << "\n";
return 0;
}
The output from this program is shown here.
3 1 4 2
One last point: Recall that the private members of an object are accessible only by
functions that are members of that object. For example, a statement like
stack1.tos = 0; // Error, tos is private.
could not be in the main( ) function of the previous program because tosis private.
Chapter 11: An Overview of C++ 275C++Function Overloading
One way that C++ achieves polymorphism is through the use of function overloading.
In C++, two or more functions can share the same name as long as their parameterdeclarations are different. In this situation, the functions that share the same name aresaid to be overloaded, and the process is referred to as function overloading.
To see why function overloading is important, first consider three functions defined
by the C subset: abs( ), labs( ), and fabs( ). The abs( ) function returns the absolute
value of an integer, labs( ) returns the absolute value of a long, and fabs( ) returns the
absolute value of a double. Although these functions perform almost identical actions,
in C three slightly different names must be used to represent these essentially similartasks. This makes the situation more complex, conceptually, than it actually is. Eventhough the underlying concept of each function is the same, the programmer has toremember three things, not just one. However, in C++, you can use just one name forall three functions, as this program illustrates:
#include <iostream>
using namespace std;
// abs is overloaded three ways
int abs(int i);double abs(double d);long abs(long l);
int main()
{
cout << abs(-10) << "\n";
cout << abs(-11.0) << "\n";cout << abs(-9L) << "\n";return 0;
}int abs(int i)
{
cout << "Using integer abs()\n";
276 C++: The Complete Reference
return i<0 ? -i : i;
}
double abs(double d)
{
cout << "Using double abs()\n";
return d<0.0 ? -d : d;
}long abs(long l)
{
cout << "Using long abs()\n";
return l<0 ? -l : l;
}
The output from this program is shown here.
Using integer abs()
10Using double abs()11Using long abs()9
This program creates three similar but different functions called abs( ), each of
which returns the absolute value of its argument. The compiler knows which function
to call in each situation because of the type of the argument. The value of overloadedfunctions is that they allow related sets of functions to be accessed with a commonname. Thus, the name abs( ) represents the general action that is being performed. It is
left to the compiler to choose the right specific method for a particular circumstance.
You need only remember the general action being performed. Due to polymorphism,three things to remember have been reduced to one. This example is fairly trivial, butif you expand the concept, you can see how polymorphism can help you manage verycomplex programs.
In general, to overload a function, simply declare different versions of it. The
compiler takes care of the rest. You must observe one important restriction whenoverloading a function: the type and/or number of the parameters of each overloadedfunction must differ. It is not sufficient for two functions to differ only in their returntypes. They must differ in the types or number of their parameters. (Return types donot provide sufficient information in all cases for the compiler to decide which functionto use.) Of course, overloaded functions may differ in their return types, too.
Here is another example that uses overloaded functions:
#include <iostream>
#include <cstdio>#include <cstring>using namespace std;
void stradd(char *s1, char *s2);
void stradd(char *s1, int i);
int main()
{
char str[80];
strcpy(str, "Hello ");
stradd(str, "there");cout << str << "\n";
stradd(str, 100);
cout << str << "\n";
return 0;
}// concatenate two strings
void stradd(char *s1, char *s2){
strcat(s1, s2);
}
// concatenate a string with a "stringized" integer
void stradd(char *s1, int i){
char temp[80];
sprintf(temp, "%d", i);
strcat(s1, temp);
}
In this program, the function stradd( ) is overloaded. One version concatenates
two strings (just like strcat( ) does). The other version "stringizes" an integer and then
appends that to a string. Here, overloading is used to create one interface that appends
either a string or an integer to another string.Chapter 11: An Overview of C++ 277C++
You can use the same name to overload unrelated functions, but you should
not. For example, you could use the name sqr( ) to create functions that return the
square of an intand the square root of a double. However, these two operations are
fundamentally different; applying function overloading in this manner defeats its
purpose (and, in fact, is considered bad programming style). In practice, you shouldoverload only closely related operations.
Operator Overloading
Polymorphism is also achieved in C++ through operator overloading. As you know, inC++, it is possible to use the <<and >>operators to perform console I/O operations.
They can perform these extra operations because in the <iostream> header, these
operators are overloaded. When an operator is overloaded, it takes on an additionalmeaning relative to a certain class. However, it still retains all of its old meanings.
In general, you can overload most of C++'s operators by defining what they mean
relative to a specific class. For example, think back to the stack class developed earlier
in this chapter. 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. However, the +
still retains its original meaning relative to other types of data.
Because operator overloading is, in practice, somewhat more complex than function
overloading, examples are deferred until Chapter 14.
Inheritance
As stated earlier in this chapter, inheritance is one of the major traits of an object-oriented programming language. In C++, inheritance is supported by allowing oneclass to incorporate another class into its declaration. Inheritance allows a hierarchyof classes to be built, moving from most general to most specific. The process involvesfirst defining a base class, which defines those qualities common to all objects to be
derived from the base. The base class represents the most general description. Theclasses derived from the base are usually referred to as derived classes. A derived class
includes all features of the generic base class and then adds qualities specific to thederived class. To demonstrate how this works, the next example creates classes thatcategorize different types of buildings.
To begin, the building class is declared, as shown here. It will serve as the base for
two derived classes.
class building {
int rooms;
int floors;int area;278 C++: The Complete Reference
public:
void set_rooms(int num);
int get_rooms();void set_floors(int num);int get_floors();void set_area(int num);int get_area();
};
Because (for the sake of this example) all buildings have three common features—
one or more rooms, one or more floors, and a total area—the building class embodies
these components into its declaration. The member functions beginning with setset
the values of the private data. The functions starting with get return those values.
You can now use this broad definition of a building to create derived classes that
describe specific types of buildings. For example, here is a derived class called house:
// house is derived from building
class house : public building {
int bedrooms;int baths;
public:
void set_bedrooms(int num);int get_bedrooms();void set_baths(int num);int get_baths();
};
Notice how building is inherited. The general form for inheritance is
class derived-class : access base-class {
//body of new class
}
Here, access is optional. However, if present, it must be public, private, or protected.
(These options are further examined in Chapter 12.) For now, all inherited classes
will use public. Using public means that all of the public members of the base class
will become public members of the derived class. Therefore, the public members of theclass building become public members of the derived class house and are available to
the member functions of house just as if they had been declared inside house. However,
house's member functions do not have access to the private elements of building. This
is an important point. Even though house inherits building, it has access only to theChapter 11: An Overview of C++ 279C++
public members of building. In this way, inheritance does not circumvent the principles
of encapsulation necessary to OOP .
A derived class has direct access to both its own members and the public members of
the base class.
Here is a program illustrating inheritance. It creates two derived classes of building
using inheritance; one is house, the other, school.
#include <iostream>
using namespace std;
class building {
int rooms;
int floors;int area;
public:
void set_rooms(int num);int get_rooms();void set_floors(int num);int get_floors();void set_area(int num);int get_area();
};
// house is derived from building
class house : public building {
int bedrooms;int baths;
public:
void set_bedrooms(int num);int get_bedrooms();void set_baths(int num);int get_baths();
};
// school is also derived from building
class school : public building {
int classrooms;int offices;
public:
void set_classrooms(int num);280 C++: The Complete Reference
int get_classrooms();
void set_offices(int num);int get_offices();
};
void building::set_rooms(int num)
{
rooms = num;
}
void building::set_floors(int num)
{
floors = num;
}
void building::set_area(int num)
{
area = num;
}
int building::get_rooms()
{
return rooms;
}
int building::get_floors()
{
return floors;
}
int building::get_area()
{
return area;
}
void house::set_bedrooms(int num)
{
bedrooms = num;
}
void house::set_baths(int num)
{Chapter 11: An Overview of C++ 281C++
baths = num;
}
int house::get_bedrooms()
{
return bedrooms;
}
int house::get_baths()
{
return baths;
}
void school::set_classrooms(int num)
{
classrooms = num;
}
void school::set_offices(int num)
{
offices = num;
}
int school::get_classrooms()
{
return classrooms;
}
int school::get_offices()
{
return offices;
}
int main()
{
house h;school s;
h.set_rooms(12);
h.set_floors(3);h.set_area(4500);h.set_bedrooms(5);282 C++: The Complete Reference
h.set_baths(3);
cout << "house has " << h.get_bedrooms();
cout << " bedrooms\n";
s.set_rooms(200);
s.set_classrooms(180);s.set_offices(5);s.set_area(25000);
cout << "school has " << s.get_classrooms();
cout << " classrooms\n";cout << "Its area is " << s.get_area();
return 0;
}
The output produced by this program is shown here.
house has 5 bedrooms
school has 180 classroomsIts 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. In this way, each
object can precisely represent its own subclass.
When writing about C++, the terms base and derived are generally used to describe
the inheritance relationship. However, the terms parent and child are also used. You
may also see the terms superclass and subclass.
Aside from providing the advantages of hierarchical classification, inheritance
also provides support for run-time polymorphism through the mechanism of virtual
functions. (Refer to Chapter 16 for details.)
Constructors and Destructors
It is very common for some part of an object to require initialization before it can beused. For example, think back to the stack class developed earlier in this chapter.
Before the stack could be used, toshad to be set to zero. This was performed by using
the function init( ). Because the requirement for initialization is so common, C++ allows
objects to initialize themselves when they are created. This automatic initialization isperformed through the use of a constructor function.Chapter 11: An Overview of C++ 283C++
284 C++: The Complete Reference
Aconstructor is a special function that is a member of a class and has
the same name as that class. For example, here is how the stack class looks when
converted to use a constructor for initialization:
// This creates the class stack.
class stack {
int stck[SIZE];int tos;
public:
stack(); // constructorvoid push(int i);int pop();
};
Notice that the constructor stack( ) has no return type specified. In C++, constructors
cannot return values and, thus, have no return type.
The stack( ) constructor is coded like this:
// stack's constructorstack::stack(){
tos = 0;cout << "Stack Initialized\n";
}
Keep in mind that the message Stack Initialized is output as a way to illustrate
the constructor. In actual practice, most constructors will not output or input anything.
They will simply perform various initializations.
An object's constructor is automatically called when the object is created. This
means that it is called when the object's declaration is executed. If you are accustomedto thinking of a declaration statement as being passive, this is not the case for C++. InC++, a declaration statement is a statement that is executed. This distinction is not justacademic. The code executed to construct an object may be quite significant. An object'sconstructor is called once for global or static local objects. For local objects, the constructor
is called each time the object declaration is encountered.
The complement of the constructor is the destructor . In many circumstances, an
object will need to perform some action or actions when it is destroyed. Local objectsare created when their block is entered, and destroyed when the block is left. Globalobjects are destroyed when the program terminates. When an object is destroyed, itsdestructor (if it has one) is automatically called. There are many reasons why a destructormay be needed. For example, an object may need to deallocate memory that it hadpreviously allocated or it may need to close a file that it had opened. In C++, it is thedestructor that handles deactivation events. The destructor has the same name as the
Chapter 11: An Overview of C++ 285C++constructor, but it is preceded by a ~. For example, here is the stack class and its constructor
and destructor. (Keep in mind that the stack class does not require a destructor; the one
shown here is just for illustration.)
// This creates the class stack.
class stack {
int stck[SIZE];int tos;
public:
stack(); // constructor~stack(); // destructorvoid push(int i);int pop();
};
// stack's constructor
stack::stack(){
tos = 0;cout << "Stack Initialized\n";
}
// stack's destructor
stack::~stack(){
cout << "Stack Destroyed\n";
}
Notice that, like constructors, destructors do not have return values.
To see how constructors and destructors work, here is a new version of the stack
program examined earlier in this chapter. Observe that init( ) is no longer needed.
// Using a constructor and destructor.#include <iostream>using namespace std;
#define SIZE 100// This creates the class stack.
class stack {
int stck[SIZE];int tos;
public:
286 C++: The Complete Reference
stack(); // constructor
~stack(); // destructorvoid push(int i);int pop();
};
// stack's constructor
stack::stack(){
tos = 0;cout << "Stack Initialized\n";
}
// stack's destructor
stack::~stack(){
cout << "Stack Destroyed\n";
}
void stack::push(int i)
{
if(tos==SIZE) {
cout << "Stack is full.\n";return;
}stck[tos] = i;tos++;
}
int stack::pop()
{
if(tos==0) {
cout << "Stack underflow.\n";return 0;
}tos–;return stck[tos];
}
int main()
{
stack a, b; // create two stack objects
a.push(1);
b.push(2);
a.push(3);
b.push(4);
cout << a.pop() << " ";
cout << a.pop() << " ";cout << b.pop() << " ";cout << b.pop() << "\n";
return 0;
}
This program displays the following:
Stack Initialized
Stack Initialized3 1 4 2Stack DestroyedStack Destroyed
The C++ Keywords
There are 63 keywords currently defined for Standard C++. These are shown in
Table 11-1. Together with the formal C++ syntax, they form the C++ programminglanguage. Also, early versions of C++ defined the overload keyword, but it is obsolete.
Keep in mind that C++ is a case-sensitive language and it requires that all keywordsbe in lowercase.Chapter 11: An Overview of C++ 287C++
asm auto bool break
case catch char class
const const_cast continue defaultdelete do double dynamic_castelse enum explicit export
Table 11-1. The C++ keywords
The General Form of a C++ Program
Although individual styles will differ, most C++ programs will have this general form:
#includes
base-class declarationsderived class declarationsnonmember function prototypesint main( ){
//…
}nonmember function definitions
In most large projects, all class declarations will be put into a header file and included
with each module. But the general organization of a program remains the same.
The remaining chapters in this section examine in greater detail the features
discussed in this chapter, as well as all other aspects of C++.288 C++: The Complete Reference
extern false float for
friend goto if inline
int long mutable namespace
new operator private protected
public register reinterpret_cast return
short signed sizeof static
static_cast struct switch template
this throw true try
typedef typeid typename union
unsigned using virtual void
volatile wchar_t while
Table 11-1. The C++ keywords (continued)
Chapter 12
Classes and Objects
289
Copyright © 2003 by The McGraw-Hill Companies. Click here for terms of use.
In C++, the class forms the basis for object-oriented programming. The class is used
to define the nature of an object, and it is C++'s basic unit of encapsulation. Thischapter examines classes and objects in detail.
Classes
Classes are created using the keyword class. A class declaration defines a new type
that links code and data. This new type is then used to declare objects of that class.Thus, a class is a logical abstraction, but an object has physical existence. In otherwords, an object is an instance of a class.
A class declaration is similar syntactically to a structure. In Chapter 11, a simplified
general form of a class declaration was shown. Here is the entire general form of aclass declaration that does not inherit any other class.
class class-name {
private data and functions
access-specifier:
data and functions
access-specifier:
data and functions
// …access-specifier:
data and functions
} object-list;
The object-list is optional. If present, it declares objects of the class. Here, access- specifier
is one of these three C++ keywords:
public
privateprotected
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 public access specifier
allows functions or data to be accessible to other parts of your program. The protected
access specifier is needed only when inheritance is involved (see Chapter 15). Once
an access specifier has been used, it remains in effect until either another accessspecifier is encountered or the end of the class declaration is reached.
You may change access specifications as often as you like within a class declaration.
For example, you may switch to public for some declarations and then switch back to
private again. The class declaration in the following example illustrates this feature:290 C++: The Complete Reference
Chapter 12: Classes and Objects 291C++#include <iostream>
#include <cstring>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 publicdouble 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;char name[80];
ted.putname("Ted Jones");
ted.putwage(75000);
292 C++: The Complete Reference
ted.getname(name);
cout << name << " makes $";cout << ted.getwage() << " per year.";
return 0;
}
Here, employee is a simple class that is used to store an employee's name and wage.
Notice that the public access specifier is used twice.
Although you may use the access specifiers as often as you like within a class
declaration, the only advantage of doing so is that by visually grouping various parts
of a class, you may make it easier for someone else reading the program to understandit. However, to the compiler, using multiple access specifiers makes no difference.Actually, most programmers find it easier to have only one private, protected, and
public section within each class. 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. Member
functions may access any element of the class of which they are a part. This includes
allprivate elements. Variables that are elements of a class are called member variables
ordata members. (The term instance variable is also used.) Collectively, any element of
a class can be referred to as a member of that class.
There are a few restrictions that apply to class members. A non-static member
variable cannot have an initializer. No member can be an object of the class that isbeing declared. (Although a member can be a pointer to the class that is beingdeclared.) No member can be declared as auto, extern, or register .
In general, you should make all data members of a class private to that class. This
is part of the way that encapsulation is achieved. However, there may be situations inwhich you will need to make one or more variables public. (For example, a heavily
Chapter 12: Classes and Objects 293C++used variable may need to be accessible globally in order to achieve faster run times.)
When a variable is public, it may be accessed directly by any other part of yourprogram. The syntax for accessing a public data member is the same as for calling amember function: Specify the object's name, the dot operator, and the variable name.This simple program illustrates the use of a public variable:
#include <iostream>
using namespace std;
class myclass {
public:
int i, j, k; // accessible to entire program
};
int main()
{
myclass a, b;
a.i = 100; // access to i, j, and k is OK
a.j = 4;a.k = a.i * a.j;
b.k = 12; // remember, a.k and b.k are different
cout << a.k << " " << b.k;
return 0;
}
Structures and Classes Are Related
Structures are part of the C subset and were inherited from the C language. As you
have seen, a class is syntactically similar to a struct. But the relationship between a
class and a struct is closer than you may at first think. In C++, the role of the structure
was expanded, making it an alternative way to specify a class. In fact, the only differencebetween a class and a struct is that by default all members are public in a struct and
private in a class. In all other respects, structures and classes are equivalent. That is,
in C++, a structure defines a class type. For example, consider this short program, which
uses a structure to declare a class that controls access to a string:
// Using a structure to define a class.
#include <iostream>#include <cstring>
294 C++: The Complete Reference
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 stringelse strcat(str, s);
}
void mystr::showstr()
{
cout << str << "\n";
}
int main()
{
mystr s;
s.buildstr(""); // init
s.buildstr("Hello ");s.buildstr("there!");
s.showstr();return 0;
}
This program displays the string Hello there!.
The class mystr could be rewritten by using class as shown here:
class mystr {
char str[255];
public:
void buildstr(char *s); // public
void showstr();
} ;
Chapter 12: Classes and Objects 295C++You might wonder why C++ contains the two virtually equivalent keywords struct
and class. This seeming redundancy is justified for several reasons. First, there is no
fundamental reason not to increase the capabilities of a structure. In C, structures
already provide a means of grouping data. Therefore, it is a small step to allow them toinclude member functions. Second, because structures and classes are related, it may beeasier to port existing C programs to C++. Finally, although struct and class are virtually
equivalent today, providing two different keywords allows the definition of a class to
be free to evolve. In order for C++ to remain compatible with C, the definition of struct
must always be tied to its C definition.
Although you can use a struct where you use a class, most programmers don't.
Usually it is best to use a class when you want a class, and a struct when you want a
C-like structure. This is the style that this book will follow. Sometimes the acronymPOD is used to describe a C-style structure—one that does not contain member
functions, constructors, or destructors. It stands for Plain Old Data.
In C++, a structure declaration defines a class type.
Unions and Classes Are Related
Like a structure, a union may also be used to define a class. In C++, unions may
contain both member functions and variables. They may also include constructorsand destructors. A union in C++ retains all of its C-like features, the most important
being that all data elements share the same location in memory. Like the structure,union members are public by default and are fully compatible with C. In the next
example, a union is used to swap the bytes that make up an unsigned short integer.
(This example assumes that short integers are 2 bytes long.)
#include <iostream>
using namespace std;
union swap_byte {
void swap();
void set_byte(unsigned short i);void show_word();
unsigned short u;
unsigned char c[2];
};
void swap_byte::swap()
{
296 C++: The Complete Reference
unsigned char t;
t = c[0];
c[0] = c[1];c[1] = t;
}
void swap_byte::show_word()
{
cout << u;
}
void swap_byte::set_byte(unsigned short i)
{
u = i;
}
int main()
{
swap_byte b;
b.set_byte(49034);
b.swap();b.show_word();
return 0;
}
Like a structure, a union declaration in C++ defines a special type of class. This
means that the principle of encapsulation is preserved.
There are several restrictions that must be observed when you use C++ unions.
First, a union cannot inherit any other classes of any type. Further, a union cannot be
a base class. A union cannot have virtual member functions. (Virtual functions are
discussed in Chapter 17.) No static variables can be members of a union. A reference
member cannot be used. A union cannot have as a member any object that overloads
the=operator. Finally, no object can be a member of a union if the object has an explicit
constructor or destructor function.
As with struct, the term POD is also commonly applied to unions that do not contain
member functions, constructors, or destructors.
Anonymous Unions
There is a special type of union in C++ called an anonymous union. An anonymous
union does not include a type name, and no objects of the union can be declared.
Chapter 12: Classes and Objects 297C++Instead, an anonymous union tells the compiler that its member variables are to
share the same location. However, the variables themselves are referred to directly,without the normal dot operator syntax. For example, consider this program:
#include <iostream>
#include <cstring>using namespace std;
int main()
{
// define anonymous unionunion {
long l;double d;char s[4];
} ;
// now, reference union elements directly
l = 100000;cout << l << " ";d = 123.2342;cout << d << " ";strcpy(s, "hi");cout << s;
return 0;
}
As you can see, the elements of the union are referenced as if they had been
declared as normal local variables. In fact, relative to your program, that is exactly how
you will use them. Further, even though they are defined within a union declaration,
they are at the same scope level as any other local variable within the same block. Thisimplies that the names of the members of an anonymous union must not conflict withother identifiers known within the same scope.
All restrictions involving unions apply to anonymous ones, with these additions.
First, the only elements contained within an anonymous union must be data. Nomember functions are allowed. Anonymous unions cannot contain private or
protected elements. Finally, global anonymous unions must be specified as static.
Friend Functions
It is possible to grant a nonmember function access to the private members of a classby using a friend. A friend function has access to all private and protected members
of the class for which it is a friend. To declare a friend function, include its prototype
within the class, preceding it with the keyword friend. Consider this program:
#include <iostream>
using namespace std;
class myclass {
int a, b;
public:
friend int sum(myclass x);
void set_ab(int i, int j);
};
void myclass::set_ab(int i, int j)
{
a = i;b = j;
}
// Note: sum() is not a member function of any class.
int sum(myclass x){
/* Because sum() is a friend of myclass, it can
directly access a and b. */
return x.a + x.b;
}
int main()
{
myclass n;
n.set_ab(3, 4);cout << sum(n);return 0;
}
In this example, the sum( ) function is not a member of myclass. However, it still
has full access to its private members. Also, notice that sum( ) is called without the use
of the dot operator. Because it is not a member function, it does not need to be (indeed,
it may not be) qualified with an object's name.298 C++: The Complete Reference
Chapter 12: Classes and Objects 299C++Although there is nothing gained by making sum( ) afriend rather than a member
function of myclass, there are some circumstances in which friend functions are quite
valuable. First, friends can be useful when you are overloading certain types of operators
(see Chapter 14). Second, friend functions make the creation of some types of I/O
functions easier (see Chapter 18). The third reason that friend functions may be desirable
is that in some cases, two or more classes may contain members that are interrelatedrelative to other parts of your program. Let's examine this third usage now.
To begin, imagine two different classes, each of which displays a pop-up message
on the screen when error conditions occur. Other parts of your program may wishto know if a pop-up message is currently being displayed before writing to the screenso that no message is accidentally overwritten. Although you can create memberfunctions in each class that return a value indicating whether a message is active,this means additional overhead when the condition is checked (that is, two functioncalls, not just one). If the condition needs to be checked frequently, this additionaloverhead may not be acceptable. However, using a function that is a friend of each
class, it is possible to check the status of each object by calling only this one function.Thus, in situations like this, a friend function allows you to generate more efficient
code. The following program illustrates this concept:
#include <iostream>
using namespace std;
const int IDLE = 0;
const int INUSE = 1;
class C2; // forward declarationclass C1 {
int status; // IDLE if off, INUSE if on screen
// …
public:
void set_status(int state);friend int idle(C1 a, C2 b);
};
class C2 {
int status; // IDLE if off, INUSE if on screen
// …
public:
void set_status(int state);friend int idle(C1 a, C2 b);
};
void C1::set_status(int state)
{
status = state;
}
void C2::set_status(int state)
{
status = state;
}
int idle(C1 a, C2 b)
{
if(a.status || b.status) return 0;else return 1;
}
int main()
{
C1 x;C2 y;
x.set_status(IDLE);
y.set_status(IDLE);
if(idle(x, y)) cout << "Screen can be used.\n";
else cout << "In use.\n";
x.set_status(INUSE);if(idle(x, y)) cout << "Screen can be used.\n";
else cout << "In use.\n";
return 0;
}
Notice that this program uses a forward declaration (also called a forward reference)
for the class C2. This is necessary because the declaration of idle( ) inside C1refers
toC2before it is declared. To create a forward declaration to a class, simply use the
form shown in this program.
Afriend of one class may be a member of another. For example, here is the
preceding program rewritten so that idle( ) is a member of C1:300 C++: The Complete Reference
#include <iostream>
using namespace std;
const int IDLE = 0;
const int INUSE = 1;
class C2; // forward declarationclass C1 {
int status; // IDLE if off, INUSE if on screen
// …
public:
void set_status(int state);int idle(C2 b); // now a member of C1
};
class C2 {
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){
if(status || b.status) return 0;else return 1;
}
int main()
{Chapter 12: Classes and Objects 301C++
C1 x;
C2 y;
x.set_status(IDLE);
y.set_status(IDLE);
if(x.idle(y)) cout << "Screen can be used.\n";
else cout << "In use.\n";x.set_status(INUSE);
if(x.idle(y)) cout << "Screen can be used.\n";
else cout << "In use.\n";
return 0;
}
Because idle( ) is a member of C1, it can access the status variable of objects of type
C1directly. Thus, only objects of type C2need be passed to idle( ).
There are two important restrictions that apply to friend functions. First, a derived
class does not inherit friend functions. Second, friend functions may not have a
storage-class specifier. That is, they may not be declared as static orextern.
Friend Classes
It is possible for one class to be a friend of another class. When this is the case, the
friend class and all of its member functions have access to the private members
defined within the other class. For example,
// Using a friend class.
#include <iostream>using namespace std;
class TwoValues {
int a;
int b;
public:
TwoValues(int i, int j) { a = i; b = j; }friend class Min;
};
class Min {302 C++: The Complete Reference
public:
int min(TwoValues x);
};
int Min::min(TwoValues x)
{
return x.a < x.b ? x.a : x.b;
}
int main()
{
TwoValues ob(10, 20);Min m;
cout << m.min(ob);return 0;
}
In this example, class Min has access to the private variables aand bdeclared within
theTwoValues class.
It is critical to understand that when one class is a friend of another, it only has
access to names defined within the other class. It does not inherit the other class.
Specifically, the members of the first class do not become members of the friend class.
Friend classes are seldom used. They are supported to allow certain special case
situations to be handled.
Inline Functions
There is an important feature in C++, called an inline function, that is commonly used
with classes. Since the rest of this chapter (and the rest of the book) will make heavyuse of it, inline functions are examined here.
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. This process is similar to using afunction-like macro. To cause a function to be expanded in line rather than called,precede its definition with the inline keyword. For example, in this program, the
function max( ) is expanded in line instead of called:
#include <iostream>
using namespace std;Chapter 12: Classes and Objects 303C++
inline int max(int a, int b)
{
return a>b ? a : b;
}
int main()
{
cout << max(10, 20);cout << " " << max(99, 88);
return 0;
}
As far as the compiler is concerned, the preceding program is equivalent to this one:
#include <iostream>
using namespace std;
int main()
{
cout << (10>20 ? 10 : 20);cout << " " << (99>88 ? 99 : 88);
return 0;
}
The reason that inline functions are an important addition to C++ is that they allow
you to create very efficient code. Since classes typically require several frequently
executed interface functions (which provide access to private data), the efficiency ofthese functions is of critical concern. As you probably know, each time a function iscalled, a significant amount of overhead is generated by the calling and returnmechanism. Typically, arguments are pushed onto the stack and various registers aresaved when a function is called, and then restored when the function returns. Thetrouble is that these instructions take time. However, when a function is expanded inline, none of those operations occur. Although expanding function calls in line canproduce faster run times, it can also result in larger code size because of duplicatedcode. For this reason, it is best to inline only very small functions. Further, it is also
a good idea to inline only those functions that will have significant impact on the
performance of your program.
Like the register specifier, inline is actually just a request, not a command, to the
compiler. The compiler can choose to ignore it. Also, some compilers may not inline304 C++: The Complete Reference
Chapter 12: Classes and Objects 305C++all types of functions. For example, it is common for a compiler not to inline a recursive
function. You will need to check your compiler's documentation for any restrictionstoinline. Remember, if a function cannot be inlined, it will simply be called as a
normal function.
Inline functions may be class member functions. For example, this is a perfectly
valid C++ program:
#include <iostream>
using namespace std;
class myclass {
int a, b;
public:
void init(int i, int j);
void show();
};
// Create an inline function.
inline void myclass::init(int i, int j){
a = i;b = j;
}
// Create another inline function.
inline void myclass::show(){
cout << a << " " << b << "\n";
}
int main()
{
myclass x;
x.init(10, 20);
x.show();
return 0;
}
Theinline keyword is not part of the C subset of C++. Thus, it is not defined by C89.
However, it has been added by C99.
306 C++: The Complete Reference
Defining Inline Functions Within a Class
It is possible to define short functions completely within a class declaration. When a
function is defined inside a class declaration, it is automatically made into an inline
function (if possible). It is not necessary (but not an error) to precede its declarationwith the inline keyword. For example, the preceding program is rewritten here with
the definitions of init( ) and show( ) contained within the declaration of myclass:
#include <iostream>
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.init(10, 20);
x.show();
return 0;
}
Notice the format of the function code within myclass. Because inline functions are
often short, this style of coding within a class is fairly typical. However, you are free
to use any format you like. For example, this is a perfectly valid way to rewrite themyclass declaration:
#include <iostream>
using namespace std;
class myclass {
int a, b;
public:
// automatic inline
void init(int i, int j){
a = i;
Chapter 12: Classes and Objects 307C++b = j;
}
void show()
{
cout << a << " " << b << "\n";
}
};
Technically, the inlining of the show( ) function is of limited value because (in
general) the amount of time the I/O statement will take far exceeds the overhead
of a function call. However, it is extremely common to see all short member functionsdefined inside their class in C++ programs. (In fact, it is rare to see short memberfunctions defined outside their class declarations in professionally written C++ code.)
Constructor and destructor functions may also be inlined, either by default, if
defined within their class, or explicitly.
Parameterized Constructors
It is possible to pass arguments to constructors. Typically, these arguments helpinitialize an object when it is created. To create a parameterized constructor, simplyadd parameters to it the way you would to any other function. When you define theconstructor's body, use the parameters to initialize the object. For example, here is asimple class that includes a parameterized constructor:
#include <iostream>
using namespace std;
class myclass {
int a, b;
public:
myclass(int i, int j) {a=i; b=j;}
void show() {cout << a << " " << b;}
};
int main()
{
myclass ob(3, 5);
ob.show();return 0;
}
308 C++: The Complete Reference
Notice that in the definition of myclass( ), the parameters iand jare used to give initial
values to aand b.
The program illustrates the most common way to specify arguments when you
declare an object that uses a parameterized constructor. Specifically, this statement
myclass ob(3, 4);
causes an object called obto be created and passes the arguments 3and 4to the iand j
parameters of myclass( ). You may also pass arguments using this type of declaration
statement:
myclass ob = myclass(3, 4);
However, the first method is the one generally used, and this is the approach taken
by most of the examples in this book. Actually, there is a small technical differencebetween the two types of declarations that relates to copy constructors. (Copyconstructors are discussed in Chapter 14.)
Here is another example that uses a parameterized constructor. It creates a class
that stores information about library books.
#include <iostream>
#include <cstring>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)
{
strcpy(author, n);
Chapter 12: Classes and Objects 309C++strcpy(title, t);
status = s;
}
void book::show()
{
cout << title << " by " << author;
cout << " is ";if(status==IN) cout << "in.\n";else cout << "out.\n";
}
int main()
{
book b1("Twain", "Tom Sawyer", IN);book b2("Melville", "Moby Dick", CHECKED_OUT);
b1.show();
b2.show();
return 0;
}
Parameterized constructors 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. Each function call you can avoid makes your program more efficient. Also,notice that the short get_status( ) and set_status( ) functions are defined in line, within
thebook class. This is a common practice when writing C++ programs.
Constructors with One Parameter: A Special Case
If a constructor only has one parameter, there is a third way to pass an initial value tothat constructor. For example, consider the following short program.
#include <iostream>
using namespace std;
class X {
int a;
public:
310 C++: The Complete Reference
X(int j) { a = j; }
int geta() { return a; }
};
int main()
{
X ob = 99; // passes 99 to j
cout << ob.geta(); // outputs 99return 0;
}
Here, the constructor for Xtakes one parameter. Pay special attention to how ob
is declared in main( ). In this form of initialization, 99 is automatically passed to the j
parameter in the X( )constructor. 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=ito initialize an object. The reason for this is that whenever
you create a constructor that takes one argument, you are also implicitly creating a
conversion from the type of that argument to the type of the class.
Remember that the alternative shown here applies only to constructors that have
exactly one parameter.
Static Class Members
Both function and data members of a class can be made static. This section explains the
consequences of each.
Static Data Members
When you precede a member variable's declaration with static, you are telling the
compiler that only one copy of that variable will exist and that all objects of the classwill share that variable. Unlike regular data members, individual copies of a static
member variable are not made for each object. No matter how many objects of a classare created, only one copy of a static data member exists. Thus, all objects of that class
use that same variable. All static variables are initialized to zero before the first object
is created.
Chapter 12: Classes and Objects 311C++When you declare a static data member within a class, you are notdefining it. (That
is, you are not allocating storage for it.) Instead, you must provide a global definition
for it elsewhere, outside the class. This is done by redeclaring the static variable using
the scope resolution operator to identify the class to which it belongs. This causes storagefor the variable to be allocated. (Remember, a class declaration is simply a logicalconstruct that does not have physical reality.)
To understand the usage and effect of a static data member, consider this program:
#include <iostream>
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 avoid shared::show()
{
cout << "This is static a: " << a;cout << "\nThis is non-static b: " << b;cout << "\n";
}
int main()
{
shared x, y;
x.set(1, 1); // set a to 1
x.show();
y.set(2, 2); // change a to 2
y.show();
x.show(); /* Here, a has been changed for both x and y
because a is shared by both objects. */
return 0;
}
This program displays the following output when run.
This is static a: 1
This is non-static b: 1This is static a: 2This is non-static b: 2This is static a: 2This is non-static b: 1
Notice that the integer ais declared both inside shared and outside of it. As
mentioned earlier, this is necessary because the declaration of ainside shared does
not allocate storage.
As a convenience, older versions of C++ did not require the second declaration of a
static member variable. However, this convenience gave rise to serious inconsistencies
and it was eliminated several years ago. However, you may still find older C++ codethat does not redeclare static member variables. In these cases, you will need to add the
required definitions.
Astatic member variable exists before any object of its class is created. For example,
in the following short program, ais both public and static. Thus it may be directly
accessed in main( ). Further, since aexists before an object of shared is created, acan
be given a value at any time. As this program illustrates, the value of ais unchanged
by the creation of object x. For this reason, both output statements display the same
value: 99.
#include <iostream>
using namespace std;
class shared {
public:
static int a;
} ;
int shared::a; // define aint main()
{
// initialize a before creating any objectsshared::a = 99;
cout << "This is initial value of a: " << shared::a;312 C++: The Complete Reference
cout << "\n";
shared x;cout << "This is x.a: " << x.a;return 0;
}
Notice how ais referred to through the use of the class name and the scope resolution
operator. 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.
One use of a static member variable is to provide access control to some shared
resource used by all objects of a class. For example, you might create several objects,
each of which needs to write to a specific disk file. Clearly, however, only one objectcan be allowed to write to the file at a time. In this case, you will want to declare astatic variable that indicates when the file is in use and when it is free. Each object then
interrogates this variable before writing to the file. The following program shows howyou might use a static variable of this type to control access to a scarce resource:
#include <iostream>
using namespace std;
class cl {
static int resource;
public:
int get_resource();
void free_resource() {resource = 0;}
};
int cl::resource; // define resourceint cl::get_resource()
{
if(resource) return 0; // resource already in useelse {
resource = 1;return 1; // resource allocated to this object
}
}Chapter 12: Classes and Objects 313C++
314 C++: The Complete Reference
int main()
{
cl ob1, ob2;
if(ob1.get_resource()) cout << "ob1 has resource\n";if(!ob2.get_resource()) cout << "ob2 denied resource\n";ob1.free_resource(); // let someone else use itif(ob2.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. For example,
#include <iostream>
using namespace std;
class Counter {
public:
static int count;Counter() { count++; }~Counter() { count–; }
};int Counter::count;
void f();int main(void)
{
Counter o1;cout << "Objects in existence: ";cout << Counter::count << "\n";
Counter o2;
cout << "Objects in existence: ";cout << Counter::count << "\n";
f();
cout << "Objects in existence: ";cout << Counter::count << "\n";
return 0;
}void f()
{
Counter temp;cout << "Objects in existence: ";cout << Counter::count << "\n";// temp is destroyed when f() returns
}
This program produces the following output.
Objects in existence: 1Objects in existence: 2Objects in existence: 3Objects in existence: 2
As you can see, the static member variable count is incremented whenever an object is
created and decremented when an object is destroyed. This way, it keeps track of how
many objects of type Counter are currently in existence.
By using static member variables, you should be able to virtually eliminate any
need for global variables. The trouble with global variables relative to OOP is that theyalmost always violate the principle of encapsulation.
Static Member Functions
Member functions may also be declared as static. There are several restrictions placed
onstatic member functions. They may only directly refer to other static members of the
class. (Of course, global functions and data may be accessed by static member functions.)
Astatic member function does not have a this pointer. (See Chapter 13 for information
onthis.) There cannot be a static and a non-static version of the same function. A static
member function may not be virtual. Finally, they cannot be declared as const orvolatile.
Following is a slightly reworked version of the shared-resource program from the
previous section. Notice that get_resource( ) is now declared as static. As the program
illustrates, get_resource( ) may be called either by itself, independently of any object, by
using the class name and the scope resolution operator, or in connection with an object.Chapter 12: Classes and Objects 315C++
#include <iostream>
using namespace std;
class cl {
static int resource;
public:
static int get_resource();
void free_resource() { resource = 0; }
};
int cl::resource; // define resourceint cl::get_resource()
{
if(resource) return 0; // resource already in useelse {
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. */
if(cl::get_resource()) cout << "ob1 has resource\n";if(!cl::get_resource()) cout << "ob2 denied resource\n";ob1.free_resource();if(ob2.get_resource()) // can still call using object syntax
cout << "ob2 can now use resource\n";
return 0;
}
Actually, static member functions have limited applications, but one good use
for them is to "preinitialize" private static data before any object is actually created. For
example, this is a perfectly valid C++ program:316 C++: The Complete Reference
Chapter 12: Classes and Objects 317C++#include <iostream>
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 iint main()
{
// init static data before object creationstatic_type::init(100);
static_type x;
x.show(); // displays 100
return 0;
}
When Constructors and Destructors
Are Executed
As a general rule, an object's constructor is called when the object comes into existence,
and an object's destructor is called when the object is destroyed. Precisely when theseevents occur is discussed here.
A local object's constructor is executed when the object's declaration statement is
encountered. The destructors for local objects are executed in the reverse order of theconstructor functions.
Global objects have their constructors execute before main( ) begins execution. Global
constructors are executed in order of their declaration, within the same file. You cannotknow the order of execution of global constructors spread among several files. Globaldestructors execute in reverse order after main( ) has terminated.
This program illustrates when constructors and destructors are executed:
#include <iostream>
using namespace std;
318 C++: The Complete Reference
class myclass {
public:
int who;
myclass(int id);~myclass();
} glob_ob1(1), glob_ob2(2);
myclass::myclass(int id)
{
cout << "Initializing " << id << "\n";who = id;
}
myclass::~myclass()
{
cout << "Destructing " << who << "\n";
}
int main()
{
myclass local_ob1(3);
cout << "This will not be first line displayed.\n";myclass local_ob2(4);return 0;
}
It displays this output:
Initializing 1
Initializing 2Initializing 3This will not be first line displayed.Initializing 4Destructing 4Destructing 3Destructing 2Destructing 1
One thing: Because of differences between compilers and execution environments, you
may or may not see the last two lines of output.
Chapter 12: Classes and Objects 319C++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. However, the scope resolution
operator has another related use: it can allow access to a name in an enclosing scopethat is "hidden" by a local declaration of the same name. For example, consider thisfragment:
int i; // global i
void f()
{
int i; // local i
i = 10; // uses local i
…
}
As the comment suggests, the assignment i = 10 refers to the local i. But what if
function f( )needs to access the global version of i? It may do so by preceding the
iwith the ::operator, as shown here.
int i; // global i
void f()
{
int i; // local i
::i = 10; // now refers to global i
…
}
Nested Classes
It is possible to define one class within another. Doing so creates a nested class. Since
aclass declaration does, in fact, define a scope, a nested class is valid only within
the scope of the enclosing class. Frankly, nested classes are seldom used. Because of
C++'s flexible and powerful inheritance mechanism, the need for nested classes isvirtually nonexistent.
320 C++: The Complete Reference
Local Classes
A class may be defined within a function. For example, this is a valid C++ program:
#include <iostream>
using namespace std;
void f();int main()
{
f();
// myclass not known herereturn 0;
}
void f()
{
class myclass {
int i;
public:
void put_i(int n) { i=n; }int get_i() { return i; }
} ob;
ob.put_i(10);
cout << ob.get_i();
}
When a class is declared within a function, it is known only to that function and
unknown outside of it.
Several restrictions apply to local classes. First, all member functions must be
defined within the class declaration. The local class may not use or access local
variables of the function in which it is declared (except that a local class has accesstostatic local variables declared within the function or those declared as extern). It
may access type names and enumerators defined by the enclosing function, however.Nostatic variables may be declared inside a local class. Because of these restrictions,
local classes are not common in C++ programming.
Passing Objects to Functions
Objects may be passed to functions in just the same way that any other type ofvariable can. Objects are passed to functions through the use of the standard call-by-value mechanism. Although the passing of objects is straightforward, some rather
Chapter 12: Classes and Objects 321C++unexpected events occur that relate to constructors and destructors. To understand
why, consider this short program.
// Passing an object to a function.
#include <iostream>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);int main()
{
myclass o(1);
f(o);
cout << "This is i in main: ";cout << o.get_i() << "\n";
return 0;
}void f(myclass ob)
{
ob.set_i(2);
cout << "This is local i: " << ob.get_i();
cout << "\n";
}
This program produces this output:
Constructing 1
This is local i: 2Destroying 2This is i in main: 1Destroying 1
As the output shows, there is one call to the constructor, which occurs when ois
created in main( ), but there are twocalls to the destructor. Let's see why this is the case.
When an object is passed to a function, a copy of that object is made (and this copy
becomes the parameter in the function). This means that a new object comes into
existence. When the function terminates, the copy of the argument (i.e., the parameter)is destroyed. This raises two fundamental questions: First, is the object's constructorcalled when the copy is made? Second, is the object's destructor called when the copyis destroyed? The answers may, at first, surprise you.
When a copy of an argument is made during a function call, the normal constructor
isnotcalled. Instead, the object's copy constructor is called. A copy constructor defines
how a copy of an object is made. As explained in Chapter 14, you can explicitly definea copy constructor for a class that you create . However, if a class does not explicitlydefine a copy constructor, as is the case here, then C++ provides one by default. Thedefault copy constructor creates a bitwise (that is, identical) copy of the object. Thereason a bitwise copy is made is easy to understand if you think about it. Since a normalconstructor is used to initialize some aspect of an object, it must not be called to makea copy of an already existing object. Such a call would alter the contents of the object.When passing an object to a function, you want to use the current state of the object,not its initial state.
However, when the function terminates and the copy of the object used as an
argument is destroyed, the destructor iscalled. This is necessary because the object has
gone out of scope. This is why the preceding program had two calls to the destructor.The first was when the parameter to f( )went out-of-scope. The second is when oinside
main( ) was destroyed when the program ended.
To summarize: When a copy of an object is created to be used as an argument to
a function, the normal constructor is not called. Instead, the default copy constructormakes a bit-by-bit identical copy. However, when the copy is destroyed (usually bygoing out of scope when the function returns), the destructor is called.
Because the default copy constructor creates an exact duplicate of the original, it
can, at times, be a source of trouble. Even though objects are passed to functions bymeans of the normal call-by-value parameter passing mechanism which, in theory,322 C++: The Complete Reference
Chapter 12: Classes and Objects 323C++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. For example, if anobject used as an argument allocates memory and frees that memory when it isdestroyed, then its local copy inside the function will free the same memory when itsdestructor is called. This will leave the original object damaged and effectively useless.To prevent this type of problem you will need to define the copy operation by creatinga copy constructor for the class, as explained in Chapter 14.
Returning Objects
A function may return an object to the caller. For example, this is a valid C++ program:
// Returning objects from a function.
#include <iostream>using namespace std;
class myclass {
int i;
public:
void set_i(int n) { i=n; }
int get_i() { return i; }
};
myclass f(); // return object of type myclassint main()
{
myclass o;
o = f();cout << o.get_i() << "\n";return 0;
}myclass f()
{
myclass x;
x.set_i(1);
return x;
}
When an object is returned by a function, a temporary object is automatically
created that holds the return value. It is this object that is actually returned by the
function. After the value has been returned, this object is destroyed. The destructionof this temporary object may cause unexpected side effects in some situations. Forexample, if the object returned by the function has a destructor that frees dynamicallyallocated memory, that memory will be freed even though the object that is receivingthe return value is still using it. There are ways to overcome this problem that involveoverloading the assignment operator (see Chapter 15) and defining a copy constructor(see Chapter 14).
Object Assignment
Assuming that both objects are of the same type, you can assign one object to another.This causes the data of the object on the right side to be copied into the data of theobject on the left. For example, this program displays 99:
// Assigning objects.
#include <iostream>using namespace std;
class myclass {
int i;
public:
void set_i(int n) { i=n; }
int get_i() { return i; }
};
int main()
{
myclass ob1, ob2;
ob1.set_i(99);
ob2 = ob1; // assign data from ob1 to ob2
cout << "This is ob2's i: " << ob2.get_i();return 0;
}
By default, all data from one object is assigned to the other by use of a bit-by-bit
copy. However, it is possible to overload the assignment operator and define some
other assignment procedure (see Chapter 15).324 C++: The Complete Reference
Chapter 13
Arrays, Pointers, References,
and the Dynamic AllocationOperators
325
Copyright © 2003 by The McGraw-Hill Companies. Click here for terms of use.
In Part One, pointers and arrays were examined as they relate to C++'s built-in
types. Here, they are discussed relative to objects. This chapter also looks at afeature related to the pointer called a reference. The chapter concludes with an
examination of C++'s dynamic allocation operators.
Arrays of Objects
In C++, it is possible to have arrays of objects. The syntax for declaring and using anobject array is exactly the same as it is for any other type of array. For example, thisprogram uses a three-element array of objects:
#include <iostream>
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].set_i(i+1);for(i=0; i<3; i++)
cout << ob[i].get_i() << "\n";
return 0;
}
This program displays the numbers 1,2, and 3on the screen.
If a class defines a parameterized constructor, you may initialize each object in
an array by specifying an initialization list, just like you do for other types of arrays.
However, the exact form of the initialization list will be decided by the number ofparameters required by the object's constructors. For objects whose constructors haveonly one parameter, you can simply specify a list of initial values, using the normalarray-initialization syntax. As each element in the array is created, a value from the326 C++: The Complete Reference
list is passed to the constructor's parameter. For example, here is a slightly different
version of the preceding program that uses an initialization:
#include <iostream>
using namespace std;
class cl {
int i;
public:
cl(int j) { i=j; } // constructor
int get_i() { return i; }
};
int main()
{
cl ob[3] = {1, 2, 3}; // initializersint i;
for(i=0; i<3; i++)
cout << ob[i].get_i() << "\n";
return 0;
}
As before, this program displays the numbers 1,2, and 3on the screen.
Actually, the initialization syntax shown in the preceding program is shorthand for
this longer form:
cl ob[3] = { cl(1), cl(2), cl(3) };
Here, the constructor for clis invoked explicitly. Of course, the short form used in the
program is more common. The short form works because of the automatic conversion
that applies to constructors taking only one argument (see Chapter 12). Thus, the shortform can only be used to initialize object arrays whose constructors only require oneargument.
If an object's constructor requires two or more arguments, you will have to use the
longer initialization form. For example,
#include <iostream>
using namespace std;Chapter 13: Arrays, Pointers, References, and the Dynamic Allocation Operators 327C++
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), // initializecl(3, 4),cl(5, 6)
};
int i;for(i=0; i<3; i++) {
cout << ob[i].get_h();
cout << ", ";cout << ob[i].get_i() << "\n";
}
return 0;
}
Here, cl's constructor has two parameters and, therefore, requires two arguments. This
means that the shorthand initialization format cannot be used and the long form, shown
in the example, must be employed.
Creating Initialized vs. Uninitialized Arrays
A special case situation occurs if you intend to create both initialized and uninitializedarrays of objects. Consider the following class.
class cl {
int i;
public:
cl(int j) { i=j; }328 C++: The Complete Reference
Chapter 13: Arrays, Pointers, References, and the Dynamic Allocation Operators 329C++int get_i() { return i; }
};
Here, the constructor defined by clrequires one parameter. This implies that any array
declared of this type must be initialized. That is, it precludes this array declaration:
cl a[9]; // error, constructor requires initializers
The reason that this statement isn't valid (as clis currently defined) is that it implies
that clhas a parameterless constructor because no initializers are specified. However,
as it stands, cldoes not have a parameterless constructor. Because there is no valid
constructor that corresponds to this declaration, the compiler will report an error.
To solve this problem, you need to overload the constructor, adding one that takesno parameters, as shown next. In this way, arrays that are initialized and those thatare not are both allowed.
class cl {
int i;
public:
cl() { i=0; } // called for non-initialized arrays
cl(int j) { i=j; } // called for initialized arraysint 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. When accessing members of a class given a pointer to an object, use the arrow(–>) operator instead of the dot operator. The next program illustrates how to access anobject given a pointer to it:
#include <iostream>
using namespace std;
330 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 obcout << 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.
For example, an integer pointer will point to the next integer. In general, all pointer
arithmetic is relative to the base type of the pointer. (That is, it is relative to the type ofdata that the pointer is declared as pointing to.) The same is true of pointers to objects.For example, this program uses a pointer to access all three elements of array obafter
being assigned ob's starting address:
#include <iostream>
using namespace std;
class cl {
int i;
public:
cl() { i=0; }
cl(int j) { i=j; }int get_i() { return i; }
};
int main()
{
cl ob[3] = {1, 2, 3};
C++cl *p;
int i;
p = ob; // get start of array
for(i=0; i<3; i++) {
cout << p->get_i() << "\n";
p++; // point to next object
}
return 0;
}
You can assign the address of a public member of an object to a pointer and then
access that member by using the pointer. For example, this is a valid C++ program
that displays the number 1on the screen:
#include <iostream>
using namespace std;
class cl {
public:
int i;cl(int j) { i=j; }
};
int main()
{
cl ob(1);
int *p;
p = &ob.i; // get address of ob.icout << *p; // access ob.i via preturn 0;
}
Because pis pointing to an integer, it is declared as an integer pointer. It is irrelevant
that iis a member of obin this situation.Chapter 13: Arrays, Pointers, References, and the Dynamic Allocation Operators 331
Type Checking C++ Pointers
There is one important thing to understand about pointers in C++: You may assign one
pointer to another only if the two pointer types are compatible. For example, given:
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 thatis a pointer to the invoking object (that is, the object on which the function is called).This pointer is called this. 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 <iostream>
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;332 C++: The Complete Reference
}
int main()
{
pwr x(4.0, 2), y(2.5, 1), z(5.7, 0);
cout << x.get_pwr() << " ";
cout << y.get_pwr() << " ";cout << z.get_pwr() << "\n";
return 0;
}
Within a member function, the members of a class can be accessed directly, without
any object or class qualification. Thus, inside pwr( ), the statement
b = base;
means that the copy of bassociated with the invoking object will be assigned the value
contained in base. However, the same statement can also be written like this:
this->b = base;
The this pointer points to the object that invoked pwr( ). Thus, this –>b refers to that
object's copy of b. For example, if pwr( ) had been invoked by x(as in x(4.0, 2)), then
this in the preceding statement would have been pointing to x. Writing the statement
without using this is really just shorthand.
Here is the entire pwr( ) constructor written using the this pointer:
pwr::pwr(double base, int exp)
{
this->b = base;this->e = exp;this->val = 1;if(exp==0) return;for( ; exp>0; exp–)
this->val = this->val * this->b;
}
Actually, no C++ programmer would write pwr( ) as just shown because nothing
is gained, and the standard form is easier. However, the this pointer is very importantChapter 13: Arrays, Pointers, References, and the Dynamic Allocation Operators 333C++
when operators are overloaded and whenever a member function must utilize a pointer
to the object that invoked it.
Remember that the this pointer is automatically passed to all member functions.
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.get_pwr();
then this will point to object y.
Two final points about this. First, friend functions are not members of a class and,
therefore, are not passed a this pointer. Second, static member functions do not have
athis pointer.
Pointers to Derived Types
In general, a pointer of one type cannot point to an object of a different type. However,there is an important exception to this rule that relates only to derived classes. To begin,assume two classes called Band D. Further, assume that Dis derived from the base
class B. In this situation, a pointer of type B*may also point to an object of type D.
More generally, a base class pointer can also be used as a pointer to an object of anyclass derived from that base.
Although a base class pointer can be used to point to a derived object, the opposite
is not true. A pointer of type D *may not point to an object of type B. Further, although
you can use a base pointer to point to a derived object, you can access only the membersof the derived type that were inherited from the base. That is, you won't be able to accessany members added by the derived class. (You can cast a base pointer into a derivedpointer and gain full access to the entire derived class, however.)
Here is a short program that illustrates the use of a base pointer to access
derived objects.
#include <iostream>
using namespace std;
class base {
int i;
public:
void set_i(int num) { i=num; }
int get_i() { return i; }334 C++: The Complete Reference
};
class derived: public base {
int j;
public:
void set_j(int num) { j=num; }
int get_j() { return j; }
};
int main()
{
base *bp;derived d;
bp = &d; // base pointer points to derived object// access derived object using base pointer
bp->set_i(10);cout << bp->get_i() << " ";
/* The following won't work. You can't access elements of
a derived class using a base class pointer.
bp->set_j(88); // error
cout << bp->get_j(); // error
*/
return 0;
}
As you can see, a base pointer is used to access an object of a derived class.
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
example, this is valid C++ code:
// access now allowed because of cast
((derived *)bp)->set_j(88);cout << ((derived *)bp)->get_j();
It is important to remember that pointer arithmetic is relative to the base type
of the 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 derivedtype. Instead, it will point to what it thinks is the next object of the base type. This,Chapter 13: Arrays, Pointers, References, and the Dynamic Allocation Operators 335C++
of course, usually spells trouble. For example, this program, while syntactically correct,
contains this error.
// This program contains an error.
#include <iostream>using namespace std;
class base {
int i;
public:
void set_i(int num) { i=num; }
int get_i() { return i; }
};
class derived: public base {
int j;
public:
void set_j(int num) {j=num;}
int get_j() {return j;}
};
int main()
{
base *bp;derived d[2];
bp = d;d[0].set_i(1);
d[1].set_i(2);
cout << bp->get_i() << " ";
bp++; // relative to base, not derivedcout << bp->get_i(); // garbage value displayed
return 0;
}
The use of base pointers to derived types is most useful when creating run-time
polymorphism through the mechanism of virtual functions (see Chapter 17).336 C++: The Complete Reference
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. This sort ofpointer is called a pointer to a class member or a pointer-to-member , for short. A pointer
to a member is not the same as a normal C++ pointer. Instead, a pointer to a memberprovides only an offset into an object of the member's class at which that member canbe found. Since member pointers are not true pointers, the .and –>cannot be applied
to them. To access a member of a class given a pointer to it, you must use the specialpointer-to-member operators .*and –>*. Their job is to allow you to access a member
of a class given a pointer to that member.
Here is an example:
#include <iostream>
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 pointerint (cl::*func)(); // function member pointercl ob1(1), ob2(2); // create objects
data = &cl::val; // get offset of val
func = &cl::double_val; // get offset of double_val()
cout << "Here are values: ";
cout << ob1.*data << " " << ob2.*data << "\n";
cout << "Here they are doubled: ";
cout << (ob1.*func)() << " ";cout << (ob2.*func)() << "\n";
return 0;
}Chapter 13: Arrays, Pointers, References, and the Dynamic Allocation Operators 337C++
Inmain( ) , this program creates two member pointers: data and func. Note
carefully the syntax of each declaration. When declaring pointers to members, you
must specify the class and use the scope resolution operator. The program also createsobjects of clcalled ob1 and ob2. As the program illustrates, member pointers may
point to either functions or data. Next, the program obtains the addresses of valand
double_val( ). As stated earlier, these “addresses” are really just offsets into an objectof type cl, at which point valand double_val( ) will be found. Next, to display the
values of each object's val, each is accessed through data. Finally, the program uses
func to call the double_val( ) function. The extra parentheses are necessary in order
to correctly associate the .*operator.
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 .*operator. 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 <iostream>
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 pointerint (cl::*func)(); // function member pointercl ob1(1), ob2(2); // create objectscl *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: ";338 C++: The Complete Reference
cout << (p1->*func)() << " ";
cout << (p2->*func)() << "\n";
return 0;
}
In this version, p1and p2are pointers to objects of type cl. Therefore, the –>*operator
is used to access valand double_val( ).
Remember, pointers to members are different from pointers to specific instances of
elements of an object. Consider this fragment (assume that clis declared as shown in
the preceding programs):
int cl::*d;
int *p;cl o;
p = &o.val // this is address of a specific vald = &cl::val // this is offset of generic val
Here, pis a pointer to an integer inside a specific object. However, dis simply an offset
that indicates where valwill be found in any object of type cl.
In general, pointer-to-member operators are applied in special-case situations. They
are not typically used in day-to-day programming.
References
C++ contains a feature that is related to the pointer called a reference. A reference is
essentially an implicit pointer. 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. Eachis examined here.
Reference Parameters
Probably the most important use for a reference is to allow you to create functionsthat automatically use call-by-reference parameter passing. As explained in Chapter 6,arguments can be passed to functions in one of two ways: using call-by-value orcall-by-reference. When using call-by-value, a copy of the argument is passed to thefunction. Call-by-reference passes the address of the argument to the function. Bydefault, C++ uses call-by-value, but it provides two ways to achieve call-by-referenceparameter passing. First, you can explicitly pass a pointer to the argument. Second,Chapter 13: Arrays, Pointers, References, and the Dynamic Allocation Operators 339C++
you can use a reference parameter. For most circumstances the best way is to 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.The following program manually creates a call-by-reference parameter using a pointerin the function called neg( ), which reverses the sign of the integer variable pointed to
by its argument.
// Manually create a call-by-reference using a pointer.
#include <iostream>using namespace std;
void neg(int *i);int main()
{
int x;
x = 10;
cout << x << " negated is ";
neg(&x);
cout << x << "\n";
return 0;
}void neg(int *i)
{
*i = -*i;
}
In this program, neg( ) takes as a parameter a pointer to the integer whose sign it
will reverse. Therefore, neg( ) must be explicitly called with the address of x. Further,
inside neg( ) the*operator must be used to access the variable pointed to by i. 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. Fortunately, in C++ you can automate
this feature by using a reference parameter.
To create a reference parameter, precede the parameter's name with an &. For
example, here is how to declare neg( ) with ideclared as a reference parameter:
void neg(int &i);340 C++: The Complete Reference
For all practical purposes, this causes ito become another name for whatever argument
neg( ) is called with. Any operations that are applied to iactually affect the calling
argument. In technical terms, iis an implicit pointer that automatically refers to
the argument used in the call to neg( ). Once ihas been made into a reference, it is no
longer necessary (or even legal) to apply the *operator. Instead, each time iis used, it
is implicitly a reference to the argument and any changes made to iaffect the argument.
Further, when calling neg( ), it is no longer necessary (or legal) to precede the argument's
name with the &operator. Instead, the compiler does this automatically. Here is the
reference version of the preceding program:
// Use a reference parameter.
#include <iostream>using namespace std;
void neg(int &i); // i now a referenceint main()
{
int x;
x = 10;
cout << x << " negated is ";
neg(x); // no longer need the & operator
cout << x << "\n";
return 0;
}void neg(int &i)
{
i = -i; // i is now a reference, don't need *
}
To review: When you create a reference parameter, it automatically refers to (implicitly
points to) the argument used to call the function. Therefore, in the preceding program,the statement
i = -i ;
actually operates on x,not on a copy of x. There is no need to apply the &operator to
an argument. Also, inside the function, the reference parameter is used directly withoutC++Chapter 13: Arrays, Pointers, References, and the Dynamic Allocation Operators 341
342 C++: The Complete Reference
the need to apply the *operator. In general, when you assign a value to a reference,
you are actually assigning that value to the variable that the reference points to.
Inside the function, it is not possible to change what the reference parameter is
pointing to. That is, a statement like
i++:
inside neg( ) increments the value of the variable used in the call. It does not cause i
to point to some new location.
Here is another example. This program uses reference parameters to swap the
values of the variables it is called with. The swap( ) function is the classic example
of call-by-reference parameter passing.
#include <iostream>
using namespace std;
void swap(int &i, int &j);int main()
{
int a, b, c, d;
a = 1;
b = 2;c = 3;d = 4;
cout << "a and b: " << a << " " << b << "\n";
swap(a, b); // no & operator neededcout << "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;
t = i; // no * operator needed
Chapter 13: Arrays, Pointers, References, and the Dynamic Allocation Operators 343C++i = j;
j = t;
}
This program displays the following:
a and b: 1 2
a and b: 2 1c and d: 3 4c and d: 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. When the function terminates, the copy's destructor iscalled. However, when you pass by reference, no copy of the object is made. Thismeans that no object used as a parameter is destroyed when the function terminates,and the parameter's destructor is not called. For example, try this program:
#include <iostream>
using namespace std;
class cl {
int id;
public:
int i;
cl(int i);~cl();void neg(cl &o) { o.i = -o.i; } // no temporary created
};
cl::cl(int num)
{
cout << "Constructing " << num << "\n";id = num;
}
cl::~cl()
{
cout << "Destructing " << id << "\n";
}
344 C++: The Complete Reference
int main()
{
cl o(1);
o.i = 10;
o.neg(o);
cout << o.i << "\n";return 0;
}
Here is the output of this program:
Constructing 1
-10Destructing 1
As you can see, only one call is made to cl's destructor. Had obeen 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.
As the code inside neg( ) illustrates, when you access a member of a class through
a reference, you use the dot operator. The arrow operator is reserved for use with
pointers only.
When passing parameters by reference, remember that changes to the object inside
the function affect the calling object.
One other point: Passing all but the smallest objects by reference is faster than
passing them by value. Arguments are usually passed on the stack. Thus, large objectstake a considerable number of CPU cycles to push onto and pop from the stack.
Returning References
A function may return a reference. This has the rather startling effect of allowing afunction to be used on the left side of an assignment statement! For example, considerthis simple program:
#include <iostream>
using namespace std;
char &replace(int i); // return a reference
Chapter 13: Arrays, Pointers, References, and the Dynamic Allocation Operators 345C++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. That is, the
program displays HelloXthere. Take a look at how this is accomplished. First, replace( )
is declared as returning a reference to a character. As replace( ) is coded, it returns a
reference to the element of sthat is specified by its argument i.The reference returned
byreplace( ) is then used in main( ) to assign to that element the character X.
One thing you must be careful about when returning references is that the object
being referred to does not go out of scope after the function terminates.
Independent References
By far the most common uses for references are to pass an argument using call-by-
reference and to act as a return value from a function. However, you can declarea reference that is simply a variable. This type of reference is called an independent
reference.
When you create an independent reference, all you are creating is another name
for an object. All independent references must be initialized when they are created. Thereason for this is easy to understand. Aside from initialization, you cannot changewhat object a reference variable points to. Therefore, it must be initialized when itis declared. (In C++, initialization is a wholly separate operation from assignment.)
The following program illustrates an independent reference:
#include <iostream>
using namespace std;
346 C++: The Complete Reference
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 acout << 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 10019 1918 18
Actually, independent references are of little real value because each one is, literally,
just another name for another variable. Having two names to describe the same object
is likely to confuse, not organize, your program.
References to Derived Types
Similar to the situation as described for pointers earlier, a base class reference can beused to refer to an object of a derived class. The most common application of this isfound in function parameters. A base class reference parameter can receive objects ofthe base class as well as any other type derived from that base.
Restrictions to References
There are a number of restrictions that apply to references. You cannot reference another
reference. Put differently, you cannot obtain the address of a reference. You cannot createarrays of references. You cannot create a pointer to a reference. You cannot referencea bit-field.
A reference variable must be initialized when it is declared unless it is a member
of a class, a function parameter, or a return value. Null references are prohibited.
A Matter of Style
When declaring pointer and reference variables, some C++ programmers use a uniquecoding style that associates the *or the &with the type name and not the variable. 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. However, the trouble with associating the &
or*with the type name rather than the variable is that, according to the formal C++
syntax, neither the &nor the *is distributive over a list of variables. Thus, misleading
declarations are easily created. For example, the following declaration creates one, not
two, integer pointers.
int* a, b;
Here, bis 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. The trouble with this declaration is that
the visual message suggests that both aand bare pointer types, even though, in fact,
only ais a pointer. This visual confusion not only misleads novice C++ programmers,
but occasionally old pros, too.
It is important to understand that, as far as the C++ compiler is concerned, it doesn't
matter whether you write int *p orint* p. Thus, if you prefer to associate the *or&
with the type rather than the variable, feel free to do so. However, to avoid confusion,this book will continue to associate the *and the &with the variables that they modify
rather than their types.
C++'s Dynamic Allocation Operators
C++ provides two dynamic allocation operators: new and delete. These operators are
used to allocate and free memory at run time. Dynamic allocation is an important partChapter 13: Arrays, Pointers, References, and the Dynamic Allocation Operators 347C++
348 C++: The Complete Reference
of almost all real-world programs. As explained in Part One, C++ also supports
dynamic memory allocation functions, called malloc( ) and free( ). These are included
for the sake of compatibility with C. However, for C++ code, you should use the new
and delete operators because they have several advantages.
The new operator allocates memory and returns a pointer to the start of it. The
delete operator frees memory previously allocated using new. 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.
Since the heap is finite, it can become exhausted. If there is insufficient available
memory to fill an allocation request, then new will fail and a bad_alloc exception will be
generated. This exception is defined in the header <new>. Your program should handle
this exception and take appropriate action if a failure occurs. (Exception handling isdescribed in Chapter 19.) If this exception is not handled by your program, then yourprogram will be terminated.
The actions of new on failure as just described are specified by Standard C++. The
trouble is that not all compilers, especially older ones, will have implemented new in
compliance with Standard C++. When C++ was first invented, new returned null on
failure. Later, this was changed such that new caused an exception on failure. Finally,
it was decided that a new failure will generate an exception by default, but that a null
pointer could be returned instead, as an option. Thus, new has been implemented
differently, at different times, by compiler manufacturers. Although all compilers willeventually implement new in compliance with Standard C++, currently the only way
to know the precise action of new on failure is to check your compiler's documentation.
Since Standard C++ specifies that new generates an exception on failure, this is
the way the code in this book is written. If your compiler handles an allocation failuredifferently, you will need to make the appropriate changes.
Here is a program that allocates memory to hold an integer:
#include <iostream>
#include <new>using namespace std;
int main()
{
int *p;
try {
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 pan address in the heap that is large enough to hold an
integer. It then assigns that memory the value 100 and displays the contents of the
memory on the screen. Finally, it frees the dynamically allocated memory. Remember,if your compiler implements new such that it returns null on failure, you must change
the preceding program appropriately.
The delete operator must be used only with a valid pointer previously allocated by
using new . Using any other type of pointer with delete is undefined and will almost
certainly cause serious problems, such as a system crash.
Although new and delete perform functions similar to malloc( ) and free( ) , they
have several advantages. First, new automatically allocates enough memory to hold an
object of the specified type. You do not need to use the sizeof operator. Because the size
is computed automatically, it eliminates any possibility for error in this regard. Second,new automatically returns a pointer of the specified type. You don't need to use an
explicit type cast as you do when allocating memory by using malloc( ) . Finally, both
new and delete can be overloaded, allowing you to create customized allocation systems.
Although there is no formal rule that states this, it is best not to mix new and delete
with malloc( ) and free( ) in the same program. There is no guarantee that they are
mutually compatible.
Initializing Allocated Memory
You can initialize allocated memory to some known value by putting an initializerafter the type name in the new statement. Here is the general form of new when an
initialization is included:
p_var = new var_type (initializer);Chapter 13: Arrays, Pointers, References, and the Dynamic Allocation Operators 349C++
350 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.
This program gives the allocated integer an initial value of 87:
#include <iostream>
#include <new>using namespace std;
int main()
{
int *p;
try {
p = new int (87); // initialize to 87
} catch (bad_alloc xa) {
cout << "Allocation Failure\n";
return 1;
}
cout << "At " << p << " ";
cout << "is the value " << *p << "\n";
delete p;return 0;
}
Allocating Arrays
You can allocate arrays using new by using this general form:
p_var = new array_type [size];
Here, sizespecifies the number of elements in the array.
To free an array, use this form of delete:
delete [ ] p_var;
Here, the [ ]informs delete that an array is being released.
For example, the next program allocates a 10-element integer array.
Chapter 13: Arrays, Pointers, References, and the Dynamic Allocation Operators 351C++#include <iostream>
#include <new>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 arrayreturn 0;
}
Notice the delete statement. As just mentioned, when an array allocated by new
is released, delete must be made aware that an array is being freed by using the [ ].
(As you will see in the next section, this is especially important when you are allocating
arrays of objects.)
One restriction applies to allocating arrays: They may not be given initial values.
That is, you may not specify an initializer when allocating arrays.
Allocating Objects
You can allocate objects dynamically by using new. When you do this, an object is
created and a pointer is returned to it. The dynamically created object acts just likeany other object. When it is created, its constructor (if it has one) is called. When theobject is freed, its destructor is executed.
Here is a short program that creates a class called balance that links a person's
name with his or her account balance. Inside main( ), an object of type balance is
created dynamically.
#include <iostream>
#include <new>#include <cstring>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.87, "Ralph Wilson");p->get_bal(n, s);cout << s << "'s balance is: " << n;352 C++: The Complete Reference
Chapter 13: Arrays, Pointers, References, and the Dynamic Allocation Operators 353C++cout << "\n";
delete p;return 0;
}
Because pcontains a pointer to an object, the arrow operator is used to access members
of the object.
As stated, dynamically allocated objects may have constructors and destructors.
Also, the constructors can be parameterized. Examine this version of the previous
program:
#include <iostream>
#include <new>#include <cstring>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.87, "Ralph Wilson");
} catch (bad_alloc xa) {
cout << "Allocation Failure\n";
return 1;
}
p->get_bal(n, s);cout << s << "'s balance is: " << n;
cout << "\n";
delete p;return 0;
}
Notice that the parameters to the object's constructor are specified after the type name,
just as in other sorts of initializations.
You can allocate arrays of objects, but there is one catch. Since no array allocated by
new can have an initializer, you must make sure that if the class contains constructors,
one will be parameterless. If you don't, the C++ compiler will not find a matchingconstructor when you attempt to allocate the array and will not compile your program.
In this version of the preceding program, an array of balance objects is allocated,
and the parameterless constructor is called.
#include <iostream>
#include <new>#include <cstring>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 constructor354 C++: The Complete Reference
Chapter 13: Arrays, Pointers, References, and the Dynamic Allocation Operators 355C++~balance() {
cout << "Destructing ";
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].set(12387.87, "Ralph Wilson");p[1].set(144.00, "A. C. Conners");p[2].set(-11.23, "I. M. Overdrawn");
for(i=0; i<3; i++) {
p[i].get_bal(n, s);cout << s << "'s balance is: " << n;
cout << "\n";
}
delete [] p;
return 0;
}
356 C++: The Complete Reference
The output from this program is shown here.
Ralph Wilson's balance is: 12387.9
A. C. Conners's balance is: 144I. M. Overdrawn's balance is: -11.23Destructing I. M. OverdrawnDestructing A. C. ConnersDestructing 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 can be called for each object
in the array.
The nothrow Alternative
In Standard C++ it is possible to have new return null instead of throwing an exception
when an allocation failure occurs. This form of new is most useful when you are compiling
older code with a modern C++ compiler. It is also valuable when you are replacing callstomalloc( ) with new. (This is common when updating C code to C++.) This form of
new is shown here:
p_var = new(nothrow) type;
Here, p_var is a pointer variable of type. The nothrow form of new works like the
original version of new from years ago. Since it returns null on failure, it can be
"dropped into" older code without having to add exception handling. However,for new code, exceptions provide a better alternative. To use the nothrow option,
you must include the header <new>.
The following program shows how to use the new(nothrow) alternative.
// Demonstrate nothrow version of new.
#include <iostream>#include <new>using namespace std;
int main()
{
int *p, i;
p = new(nothrow) int[32]; // use nothrow option
if(!p) {
cout << "Allocation failure.\n";
Chapter 13: Arrays, Pointers, References, and the Dynamic Allocation Operators 357C++return 1;
}
for(i=0; i<32; i++) p[i] = i;for(i=0; i<32; i++) cout << p[i] << " ";delete [] p; // free the memoryreturn 0;
}
As this program demonstrates, when using the nothrow approach, you must check the
pointer returned by new after each allocation request.
The Placement Form of new
There is a special form of new, called the placement form, that can be used to specify an
alternative method of allocating memory. It is primarily useful when overloading the
new operator for special circumstances. Here is its general form:
p_var = new (arg-list) type;
Here, arg-list is a comma-separated list of values passed to an overloaded form of new.
This page intentionally left blank
Chapter 14
Function Overloading,
Copy Constructors,and Default Arguments
359
Copyright © 2003 by The McGraw-Hill Companies. Click here for terms of use.
This chapter examines function overloading, copy constructors, and default
arguments. Function overloading is one of the defining aspects of the C++programming language. Not only does it provide support for compile-time
polymorphism, it also adds flexibility and convenience. Some of the most commonlyoverloaded functions are constructors. Perhaps the most important form of an overloadedconstructor is the copy constructor. Closely related to function overloading are defaultarguments. Default arguments can sometimes provide an alternative to functionoverloading.
Function Overloading
Function overloading is the process of using the same name for two or more functions.The secret to overloading is that each redefinition of the function must use eitherdifferent types of parameters or a different number of parameters. It is only throughthese differences that the compiler knows which function to call in any given situation.For example, this program overloads myfunc( ) by using different types of parameters.
#include <iostream>
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.4); // calls myfunc(double i)
return 0;
}double myfunc(double i)
{
return i;
}
int myfunc(int i)
{
return i;
}360 C++: The Complete Reference
The next program overloads myfunc( ) using a different number of parameters:
#include <iostream>
using namespace std;
int myfunc(int i); // these differ in number of parameters
int myfunc(int i, int j);
int main()
{
cout << myfunc(10) << " "; // calls myfunc(int i)cout << myfunc(4, 5); // calls myfunc(int i, int j)
return 0;
}int myfunc(int i)
{
return i;
}
int myfunc(int i, int j)
{
return i*j;
}
As mentioned, the key point about function overloading is that the functions must
differ in regard to the types and/or number of parameters. Two functions differing
only in their return types cannot be overloaded. For example, this is an invalid attemptto overload myfunc( ):
int myfunc(int i); // Error: differing return types are
float myfunc(int i); // insufficient when overloading.
Sometimes, two function declarations will appear to differ, when in fact they do not.
For example, consider the following declarations.
void f(int *p);
void f(int p[]); // error, *p is same as p[]
Remember, to the compiler *pis the same as p[ ]. Therefore, although the two
prototypes appear to differ in the types of their parameter, in actuality they do not.Chapter 14: Function Overloading, Copy Constructors, and Default Arguments 361C++
Overloading Constructors
Constructors can be overloaded; in fact, overloaded constructors are very common.
There are three main reasons why you will want to overload a constructor: to gainflexibility, to allow both initialized and uninitialized objects to be created, and todefine copy constructors. In this section, the first two of these are examined. Thefollowing section describes the copy constructor.
Overloading a Constructor to Gain Flexibility
Many times you will create a class for which there are two or more possible ways toconstruct an object. In these cases, you will want to provide an overloaded constructorfor each way. This is a self-enforcing rule because if you attempt to create an object forwhich there is no matching constructor, a compile-time error results.
By providing a constructor for each way that a user of your class may plausibly
want to construct an object, you increase the flexibility of your class. The user is free tochoose the best way to construct an object given the specific circumstance. Considerthis program that creates a class called date, which holds a calendar date. Notice that
the constructor is overloaded two ways:
#include <iostream>
#include <cstdio>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(char *d){
sscanf(d, "%d%*c%d%*c%d", &month, &day, &year);
}
// Initialize using integers.
date::date(int m, int d, int y){362 C++: The Complete Reference
day = d;
month = m;year = y;
}
void date::show_date()
{
cout << month << "/" << day;cout << "/" << year << "\n";
}
int main()
{
date ob1(12, 4, 2003), ob2("10/22/2003");
ob1.show_date();
ob2.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.
As the date class illustrates, perhaps the most common reason to overload a
constructor is to allow an object to be created by using the most appropriate andnatural means for each particular circumstance. For example, in the following main( ),
the user is prompted for the date, which is input to array s. This string can then be used
directly to create d. There is no need for it to be converted to any other form. However,
ifdate( ) were not overloaded to accept the string form, you would have to manually
convert it into three integers.
int main()
{
char s[80];Chapter 14: Function Overloading, Copy Constructors, and Default Arguments 363C++
cout << "Enter new date: ";
cin >> s;
date d(s);
d.show_date();
return 0;
}
In another situation, initializing an object of type date by using three integers
may be more convenient. 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. The point here is that by overloadingdate's constructor, you have made it more flexible and easier to use. This increasedflexibility and ease of use are especially important if you are creating class librariesthat will be used by other programmers.
Allowing Both Initialized and Uninitialized Objects
Another common reason constructors are overloaded is to allow both initialized anduninitialized objects (or, more precisely, default initialized objects) to be created. Thisis especially important if you want to be able to create dynamic arrays of objects ofsome class, since it is not possible to initialize a dynamically allocated array. To allowuninitialized arrays of objects along with initialized objects, you must include aconstructor that supports initialization and one that does not.
For example, the following program declares two arrays of type powers; one is
initialized and the other is not. It also dynamically allocates an array.
#include <iostream>
#include <new>using namespace std;
class powers {
int x;
public:
// overload constructor two ways
powers() { x = 0; } // no initializerpowers(int n) { x = n; } // initializer
int getx() { return x; }
void setx(int i) { x = i; }364 C++: The Complete Reference
};
int main()
{
powers ofTwo[] = {1, 2, 4, 8, 16}; // initializedpowers ofThree[5]; // uninitializedpowers *p;int i;
// show powers of two
cout << "Powers of two: ";for(i=0; i<5; i++) {
cout << ofTwo[i].getx() << " ";
}cout << "\n\n";
// set powers of three
ofThree[0].setx(1);ofThree[1].setx(3);ofThree[2].setx(9);ofThree[3].setx(27);ofThree[4].setx(81);
// show powers of three
cout << "Powers of three: ";for(i=0; i<5; i++) {
cout << ofThree[i].getx() << " ";
}cout << "\n\n";
// dynamically allocate an array
try {
p = new powers[5]; // no initialization
} catch (bad_alloc xa) {
cout << "Allocation Failure\n";return 1;
}
// initialize dynamic array with powers of two
for(i=0; i<5; i++) {
p[i].setx(ofTwo[i].getx());Chapter 14: Function Overloading, Copy Constructors, and Default Arguments 365C++
}
// show powers of two
cout << "Powers of two: ";for(i=0; i<5; i++) {
cout << p[i].getx() << " ";
}cout << "\n\n";
delete [] p;
return 0;
}
In this example, both constructors are necessary. The default constructor is used to
construct the uninitialized ofThree array and the dynamically allocated array. The
parameterized constructor is called to create the objects for the ofTwo array.
Copy Constructors
One of the more important forms of an overloaded constructor is the copy constructor .
Defining a copy constructor can help you prevent problems that might occur whenone object is used to initialize another.
Let's begin by restating the problem that the copy constructor is designed to solve.
By default, when one object is used to initialize another, C++ performs a bitwise copy.That is, an identical copy of the initializing object is created in the target object.Although this is perfectly adequate for many cases—and generally exactly what youwant to happen—there are situations in which a bitwise copy should not be used.One of the most common is when an object allocates memory when it is created. Forexample, assume a class called MyClass that allocates memory for each object when
it is created, and an object Aof that class. This means that Ahas already allocated its
memory. Further, assume that Ais used to initialize B, as shown here:
MyClass B = A;
If a bitwise copy is performed, then Bwill be an exact copy of A. This means that B
will be using the same piece of allocated memory that Ais using, instead of allocating
its own. Clearly, this is not the desired outcome. For example, if MyClass includes a
destructor that frees the memory, then the same piece of memory will be freed twicewhen Aand Bare destroyed!
The same type of problem can occur in two additional ways: first, when a copy of
an object is made when it is passed as an argument to a function; second, when atemporary object is created as a return value from a function. Remember, temporary366 C++: The Complete Reference
objects are automatically created to hold the return value of a function and they may
also be created in certain other circumstances.
To solve the type of problem just described, C++ allows you to create a copy
constructor, which the compiler uses when one object initializes another. Thus, yourcopy constructor bypasses the default bitwise copy. The most common general formof a copy constructor is
classname (const classname &o) {
// body of constructor
}
Here, ois a reference to the object on the right side of the initialization. It is permissible
for a copy constructor to have additional parameters as long as they have defaultarguments defined for them. However, in all cases the first parameter must be a referenceto the object doing the initializing.
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 first is assignment. The second isinitialization, which can occur any of three ways:
șWhen one object explicitly initializes another, such as in a declaration
șWhen a copy of an object is made to be passed to a function
șWhen a temporary object is generated (most commonly, as a return value)
The copy constructor applies only to initializations. For example, assuming a classcalled myclass, and that yis an object of type myclass, each of the following statements
involves initialization.
myclass x = y; // y explicitly initializing x
func(y); // y passed as a parametery = func(); // y receiving a temporary, return object
Following is an example where an explicit copy constructor is needed. This program
creates a very limited "safe" integer array type that prevents array boundaries from
being overrun. (Chapter 15 shows a better way to create a safe array that usesoverloaded operators.) Storage for each array is allocated by the use of new , and a
pointer to the memory is maintained within each array object.
/* This program creates a "safe" array class. Since space
for the array is allocated using new, a copy constructor
is provided to allocate memory when one array object isused to initialize another.
*/Chapter 14: Function Overloading, Copy Constructors, and Default Arguments 367C++
368 C++: The Complete Reference
#include <iostream>
#include <new>#include <cstdlib>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<size) p[i] = j;
}
int get(int i) {
return p[i];
}
};
// Copy Constructor
array::array(const array &a) {
int i;
try {
p = new int[a.size];
} catch (bad_alloc xa) {
cout << "Allocation Failure\n";
exit(EXIT_FAILURE);
}for(i=0; i<a.size; i++) p[i] = a.p[i];
}
Chapter 14: Function Overloading, Copy Constructors, and Default Arguments 369C++int main()
{
array num(10);
int i;
for(i=0; i<10; i++) num.put(i, i);
for(i=9; i>=0; i–) cout << num.get(i);cout << "\n";
// create another array and initialize with num
array x(num); // invokes copy constructorfor(i=0; i<10; i++) cout << x.get(i);
return 0;
}
Let's look closely at what happens when num is used to initialize xin the statement
array x(num); // invokes copy constructor
The copy constructor is called, memory for the new array is allocated and stored in x.p,
and the contents of num are copied to x's array. In this way, xand num have arrays
that contain the same values, but each array is separate and distinct. (That is, num.p
and x.pdo not point to the same piece of memory.) If the copy constructor had not been
created, the default bitwise initialization would have resulted in xand num sharing the
same memory for their arrays. (That is, num.p and x.pwould have indeed pointed to
the same location.)
Remember that the copy constructor is called only for initializations. For example,
this sequence does not call the copy constructor defined in the preceding program:
array a(10);
// …array b(10);
b = a; // does not call copy constructor
In this case, b = a performs the assignment operation. If =is not overloaded (as it is not
here), a bitwise copy will be made. 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).
Finding the Address of an Overloaded Function
As explained in Chapter 5, you can obtain the address of a function. One reason to
do so is to assign the address of the function to a pointer and then call that functionthrough that pointer. If the function is not overloaded, this process is straightforward.However, for overloaded functions, the process requires a little more subtlety. Tounderstand why, first consider this statement, which assigns the address of somefunction called myfunc( ) to a pointer called p:
p = myfunc;
Ifmyfunc( ) is not overloaded, there is one and only one function called myfunc( ),
and the compiler has no difficulty assigning its address to p. 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 pis declared. For example, consider this program:
#include <iostream>
using namespace std;
int myfunc(int a);
int myfunc(int a, int b);
int main()
{
int (*fp)(int a); // pointer to int f(int)
fp = myfunc; // points to myfunc(int)cout << fp(5);return 0;
}int myfunc(int a)
{
return a;
}
int myfunc(int a, int b)
{
return a*b;
}370 C++: The Complete Reference
Chapter 14: Function Overloading, Copy Constructors, and Default Arguments 371C++Here, there are two versions of myfunc( ). Both return int, but one takes a single
integer argument; the other requires two integer arguments. In the program, fpis
declared as a pointer to a function that returns an integer and that takes one integer
argument. When fpis assigned the address of myfunc( ), C++ uses this information
to select the myfunc(int a) version of myfunc( ). Had fpbeen declared like this:
int (*fp)(int a, int b);
then fpwould have been assigned the address of the myfunc(int a, int b) 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 addressis obtained. Further, the declaration of the function pointer must exactly match oneand only one of the overloaded function's declarations.
The overload Anachronism
When C++ was created, the keyword overload was required to create an overloaded
function. It is obsolete and no longer used or supported. Indeed, it is not even areserved word in Standard C++. However, because you might encounter olderprograms, and for its historical interest, it is a good idea to know how overload
was used. Here is its general form:
overload func-name;
Here, func-name is the name of the function that you will be overloading. This
statement must precede the overloaded declarations. For example, this tells anold-style compiler that you will be overloading a function called test( ):
overload test;
Default Function Arguments
C++ allows a function to assign a parameter a default value when no argumentcorresponding to that parameter is specified in a call to that function. The default valueis specified in a manner syntactically similar to a variable initialization. For example,this declares myfunc( ) as taking one double argument with a default value of 0.0:
void myfunc(double d = 0.0)
{
// …
}
Now, myfunc( ) can be called one of two ways, as the following examples show:
myfunc(198.234); // pass an explicit value
myfunc(); // let function use default
The first call passes the value 198.234 to d. The second call automatically gives dthe
default value zero.
One reason that default arguments are included in C++ is because they provide
another method for the programmer to manage greater complexity. To handle the
widest variety of situations, quite frequently a function contains more parameters thanare required for its most common usage. Thus, when the default arguments apply, youneed specify only the arguments that are meaningful to the exact situation, not all thoseneeded by the most general case. For example, many of the C++ I/O functions makeuse of default arguments for just this reason.
A simple illustration of how useful a default function argument can be is shown by
theclrscr( ) function in the following program. The clrscr( ) function clears the screen
by outputting a series of linefeeds (not the most efficient way, but sufficient for thisexample). Because a very common video mode displays 25 lines of text, the defaultargument of 25 is provided. However, because some video modes display more or lessthan 25 lines, you can override the default argument by specifying one explicitly.
#include <iostream>
using namespace std;
void clrscr(int size=25);int main()
{
register int i;
for(i=0; i<30; i++ ) cout << i << endl;
cin.get();clrscr(); // clears 25 lines
for(i=0; i<30; i++ ) cout << i << endl;
cin.get();clrscr(10); // clears 10 lines
return 0;
}void clrscr(int size)372 C++: The Complete Reference
Chapter 14: Function Overloading, Copy Constructors, and Default Arguments 373C++{
for(; size; size–) cout << endl;
}
As this program illustrates, when the default value is appropriate to the situation,
no argument need be specified when clrscr( ) is called. However, it is still possible to
override the default and give size a different value when needed.
A default argument can also be used as a flag telling the function to reuse a
previous argument. To illustrate this usage, a function called iputs( ) is developed here
that automatically indents a string by a specified amount. To begin, here is a version of
this function that does not use a default argument:
void iputs(char *str, int indent)
{
if(indent < 0) indent = 0;
for( ; indent; indent–) cout << " ";cout << str << "\n";
}
This version of iputs( ) is called with the string to output as the first argument and
the amount to indent as the second. 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. It is quite common
to display a block of text with each line indented the same amount. In this situation,
instead of having to supply the same indent argument over and over, you can give
indent a default value that tells iputs( ) to indent to the level of the previous call. This
approach is illustrated in the following program:
#include <iostream>
using namespace std;
/* Default indent to -1. This value tells the function
to reuse the previous value. */
void iputs(char *str, int indent = -1);int main()
{
iputs("Hello there", 10);iputs("This will be indented 10 spaces by default");
iputs("This will be indented 5 spaces", 5);
iputs("This is not indented", 0);
return 0;
}void iputs(char *str, int indent)
{
static i = 0; // holds previous indent value
if(indent >= 0)
i = indent;
else // reuse old indent value
indent = i;
for( ; indent; indent–) cout << " ";cout << str << "\n";
}
This program displays this output:
Hello there
This will be indented 10 spaces by default
This will be indented 5 spaces
This is not indented
When you are creating functions that have default arguments, it is important to
remember that the default values must be specified only once, and this must be the
first time the function is declared within the file. In the preceding example, the defaultargument was specified in iputs( )'s prototype. If you try to specify new (or even the
same) default values in iputs( )'s definition, the compiler will display an error and not
compile your program. Even though default arguments for the same function cannotbe redefined, you can specify different default arguments for each version of anoverloaded function.
All parameters that take default values must appear to the right of those that do
not. For example, it is incorrect to define iputs( ) like this:
// wrong!
void iputs(int indent = -1, char *str);374 C++: The Complete Reference
Once you begin to define parameters that take default values, you cannot specify
a nondefaulting parameter. That is, a declaration like this is also wrong and will
not compile:
int myfunc(float f, char *str, int i=10, int j);
Because ihas been given a default value, jmust be given one too.
You can also use default parameters in an object's constructor. For example, the
cube class shown here maintains the integer dimensions of a cube. Its constructor
defaults all dimensions to zero if no other arguments are supplied, as shown here:
#include <iostream>
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;
}
};int main()
{
cube a(2,3,4), b;
cout << a.volume() << endl;
cout << b.volume();
return 0;
}
There are two advantages to including default arguments, when appropriate, in a
constructor. First, they prevent you from having to provide an overloaded constructor
that takes no parameters. For example, if the parameters to cube( ) were not givenChapter 14: Function Overloading, Copy Constructors, and Default Arguments 375C++
376 C++: The Complete Reference
defaults, the second constructor shown here would be needed to handle the declaration
ofb(which specified no arguments).
cube() {x=0; y=0; z=0}
Second, defaulting common initial values is more convenient than specifying themeach time an object is declared.
Default Arguments vs. Overloading
In some situations, default arguments can be used as a shorthand form of functionoverloading. The cube class's constructor just shown is one example. Let's look at
another. Imagine that you want to create two customized versions of the standardstrcat( ) function. The first version will operate like strcat( ) and concatenate the entire
contents of one string to the end of another. The second version takes a third argumentthat specifies the number of characters to concatenate. That is, the second versionwill only concatenate a specified number of characters from one string to the end ofanother. 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 will copy lencharacters from s2to the end of s1. The second version
will copy the entire string pointed to by s2onto the end of the string pointed to by s1
and operates like strcat( ).
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. Using a default argument, you can
create only one version of mystrcat( ) that performs both functions. The following
program demonstrates this.
// A customized version of strcat().
#include <iostream>#include <cstring>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 str1mystrcat(str1, str2); // concatenate entire string
cout << str1 << '\n';
return 0;
}// A custom version of strcat().
void mystrcat(char *s1, char *s2, int len){
// find end of s1while(*s1) s1++;
if(len == -1) len = strlen(s2);while(*s2 && len) {
*s1 = *s2; // copy chars
s1++;s2++;len–;
}
*s1 = '\0'; // null terminate s1
}
Here, mystrcat( ) concatenates up to lencharacters from the string pointed to by s2
onto the end of the string pointed to by s1. However, if lenis –1, as it will be when it is
allowed to default, mystrcat( ) concatenates the entire string pointed to by s2onto s1.
(Thus, when lenis –1, the function operates like the standard strcat( ) function.) By
using a default argument for len, it is possible to combine both operations into one
function. In this way, default arguments sometimes provide an alternative to function
overloading.
Using Default Arguments Correctly
Although default arguments can be a very powerful tool when used correctly, they canalso be misused. The point of default arguments is to allow a function to perform its jobin an efficient, easy-to-use manner while still allowing considerable flexibility. TowardChapter 14: Function Overloading, Copy Constructors, and Default Arguments 377C++
378 C++: The Complete Reference
this end, all default arguments should reflect the way a function is generally used, or
a reasonable alternate usage. When there is no single value that can be meaningfullyassociated with a parameter, there is no reason to declare a default argument. In fact,declaring default arguments when there is insufficient basis for doing so destructuresyour code, because they are liable to mislead and confuse anyone reading your program.
One other important guideline you should follow when using default arguments is
this: No default argument should cause a harmful or destructive action. That is, theaccidental use of a default argument should not cause a catastrophe.
Function Overloading and Ambiguity
You can create a situation in which the compiler is unable to choose between two (ormore) overloaded functions. When this happens, the situation is said to be ambiguous.
Ambiguous statements are errors, and programs containing ambiguity will not compile.
By far the main cause of ambiguity involves C++'s automatic type conversions.
As you know, C++ automatically attempts to convert the arguments used to call afunction into the type of arguments expected by the function. For example, considerthis fragment:
int myfunc(double d);
// …cout << myfunc('c'); // not an error, conversion applied
As the comment indicates, this is not an error because C++ automatically converts the
character cinto its double equivalent. In C++, very few type conversions of this sort
are actually disallowed. Although automatic type conversions are convenient, theyare also a prime cause of ambiguity. For example, consider the following program:
#include <iostream>
using namespace std;
float myfunc(float i);
double myfunc(double i);
int main()
{
cout << myfunc(10.1) << " "; // unambiguous, calls myfunc(double)cout << myfunc(10); // ambiguous
return 0;
}
float myfunc(float i)
{
return i;
}
double myfunc(double i)
{
return -i;
}
Here, myfunc( ) is overloaded so that it can take arguments of either type float or
type double. 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. Hence, that call is unambiguous. 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. This causes an error
message to be displayed, and the program will not compile.
As the preceding example illustrates, it is not the overloading of myfunc( ) relative
todouble and float that causes the ambiguity. Rather, it is the specific call to myfunc( )
using an indeterminate type of argument that causes the confusion. Put differently, theerror is not caused by the overloading of myfunc( ), but by the specific invocation.
Here is another example of ambiguity caused by C++'s automatic type conversions:
#include <iostream>
using namespace std;
char myfunc(unsigned char ch);
char myfunc(char ch);
int main()
{
cout << myfunc('c'); // this calls myfunc(char)cout << myfunc(88) << " "; // ambiguous
return 0;
}char myfunc(unsigned char ch)
{
return ch-1;Chapter 14: Function Overloading, Copy Constructors, and Default Arguments 379C++
}
char myfunc(char ch)
{
return ch+1;
}
In C++, unsigned char and char arenotinherently ambiguous. However, when
myfunc( ) is called by using the integer 88, the compiler does not know which function
to call. That is, should 88 be converted into a char or an unsigned char?
Another way you can cause ambiguity is by using default arguments in overloaded
functions. To see how, examine this program:
#include <iostream>using namespace std;
int myfunc(int i);
int myfunc(int i, int j=1);
int main()
{
cout << myfunc(4, 5) << " "; // unambiguouscout << myfunc(10); // ambiguous
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. However, when the second
call to myfunc( ) is made, ambiguity occurs because the compiler does not know whether
to call the version of myfunc( ) that takes one argument or to apply the default to the
version that takes two arguments.380 C++: The Complete Reference
Some types of overloaded functions are simply inherently ambiguous even if, at
first, they may not seem so. For example, consider this program.
// This program contains an error.
#include <iostream>using namespace std;
void f(int x);
void f(int &x); // error
int main()
{
int a=10;
f(a); // error, which f()?return 0;
}void f(int x)
{
cout << "In f(int)\n";
}
void f(int &x)
{
cout << "In f(int &)\n";
}
As the comments in the program describe, two functions cannot be overloaded when
the only difference is that one takes a reference parameter and the other takes a normal,call-by-value parameter. In this situation, the compiler has no way of knowing whichversion of the function is intended when it is called. Remember, there is no syntacticaldifference in the way that an argument is specified when it will be received by areference parameter or by a value parameter.Chapter 14: Function Overloading, Copy Constructors, and Default Arguments 381C++
This page intentionally left blank
Chapter 15
Operator Overloading
383
Copyright © 2003 by The McGraw-Hill Companies. Click here for terms of use.
384 C++: The Complete Reference
Closely related to function overloading is operator overloading. In C++, you
can overload most operators so that they perform special operations relativeto classes that you create. For example, a class that maintains a stack might
overload +to perform a push operation and – –to perform a pop. When an operator
is overloaded, none of its original meanings are lost. Instead, the type of objects it canbe applied to is expanded.
The ability to overload operators is one of C++'s most powerful features. It allows
the full integration of new class types into the programming environment. Afteroverloading the appropriate operators, you can use objects in expressions in just thesame way that you use C++'s built-in data types. Operator overloading also forms thebasis of C++'s approach to I/O.
You overload operators by creating operator functions. An operator function defines the
operations that the overloaded operator will perform relative to the class upon which itwill work. An operator function is created using the keyword operator . Operator functions
can be either members or nonmembers of a class. Nonmember operator functions arealmost always friend functions of the class, however. The way operator functionsare written differs between member and nonmember functions. Therefore, each willbe examined separately, beginning with member operator functions.
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. The #is a placeholder. When you create an operator function,
substitute the operator for the #. For example, if you are overloading the /operator,
useoperator/. When you are overloading a unary operator, arg-list will be empty.
When you are overloading binary operators, arg-list will contain one parameter.
(The reasons for this seemingly unusual situation will be made clear in a moment.)
Here is a simple first example of operator overloading. This program creates a
class called loc, which stores longitude and latitude values. It overloads the +operator
relative to this class. Examine this program carefully, paying special attention to thedefinition of operator+( ):
#include <iostream>
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.
loc loc::operator+(loc op2){
loc temp;
temp.longitude = op2.longitude + longitude;
temp.latitude = op2.latitude + latitude;
return temp;
}int main()
{
loc ob1(10, 20), ob2( 5, 30);
ob1.show(); // displays 10 20
ob2.show(); // displays 5 30
ob1 = ob1 + ob2;
ob1.show(); // displays 15 50
return 0;
}Chapter 15: Operator Overloading 385C++
As you can see, operator+( ) has only one parameter even though it overloads the
binary +operator. (You might expect two parameters corresponding to the two operands
of a 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 operand on the right is passed in the parameter op2. 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.
As mentioned, it is common for an overloaded operator function to return an
object of the class it operates upon. By doing so, it allows the operator to be used in
larger expressions. For example, if the operator+( ) function returned some other
type, this expression would not have been valid:
ob1 = ob1 + ob2;
In order for the sum of ob1 and ob2 to be assigned to ob1, the outcome of that operation
must be an object of type loc.
Further, having operator+( ) return an object of type locmakes possible the
following statement:
(ob1+ob2).show(); // displays outcome of ob1+ob2
In this situation, ob1+ob2 generates a temporary object that ceases to exist after the call
toshow( ) terminates.
It is important to understand that an operator function can return any type and that
the type returned depends solely upon your specific application. It is just that, often,an operator function will return an object of the class upon which it operates.
One last point about the operator+( ) function: It does not modify either operand.
Because the traditional use of the +operator does not modify either operand, it makes
sense for the overloaded version not to do so either. (For example, 5+7 yields 12, butneither 5 nor 7 is changed.) Although you are free to perform any operation you wantinside an operator function, it is usually best to stay within the context of the normaluse of the operator.
The next program adds three additional overloaded operators to the locclass: the –,
the=,and the unary ++.Pay special attention to how these functions are defined.
#include <iostream>
using namespace std;
class loc {
int longitude, latitude;386 C++: The Complete Reference
public:
loc() {} // needed to construct temporaries
loc(int lg, int lt) {
longitude = lg;
latitude = lt;
}
void show() {
cout << longitude << " ";
cout << latitude << "\n";
}
loc operator+(loc op2);
loc operator-(loc op2);loc operator=(loc op2);loc operator++();
};
// Overload + for loc.
loc loc::operator+(loc op2){
loc temp;
temp.longitude = op2.longitude + longitude;
temp.latitude = op2.latitude + latitude;
return temp;
}// Overload – for loc.
loc loc::operator-(loc op2){
loc temp;
// notice order of operands
temp.longitude = longitude – op2.longitude;temp.latitude = latitude – op2.latitude;
return temp;
}// Overload asignment for loc.Chapter 15: Operator Overloading 387C++
loc loc::operator=(loc op2)
{
longitude = op2.longitude;
latitude = op2.latitude;
return *this; // i.e., return object that generated call
}// Overload prefix ++ for loc.
loc loc::operator++(){
longitude++;latitude++;
return *this;
}int main()
{
loc ob1(10, 20), ob2( 5, 30), ob3(90, 90);
ob1.show();
ob2.show();
++ob1;
ob1.show(); // displays 11 21
ob2 = ++ob1;
ob1.show(); // displays 12 22ob2.show(); // displays 12 22
ob1 = ob2 = ob3; // multiple assignment
ob1.show(); // displays 90 90ob2.show(); // displays 90 90
return 0;
}
First, examine the operator–( ) function. Notice the order of the operands in the
subtraction. In keeping with the meaning of subtraction, the operand on the right side
of the minus sign is subtracted from the operand on the left. Because it is the object onthe left that generates the call to the operator–( ) function, op2's data must be subtracted388 C++: The Complete Reference
C++Chapter 15: Operator Overloading 389
from the data pointed to by this. It is important to remember which operand generates
the call to the function.
In C++, if the =is not overloaded, a default assignment operation is created auto-
matically for any class you define. The default assignment is simply a member- by-member,
bitwise copy. By overloading the =, you can define explicitly what the assignment does
relative to a class. In this example, the overloaded =does exactly the same thing as
the default, but in other situations, it could perform other operations. Notice that theoperator=( ) function returns *this, which is the object that generated the call. This
arrangement is necessary if you want to be able to use multiple assignment operationssuch as this:
ob1 = ob2 = ob3; // multiple assignment
Now, look at the definition of operator++( ). As you can see, it takes no parameters.
Since ++is a unary operator, its only operand is implicitly passed by using the
this pointer.
Notice that both operator=( ) and operator++( ) alter the value of an operand.
In the case of assignment, the operand on the left (the one generating the call to theoperator=( ) function) is assigned a new value. In the case of the ++, the operand is
incremented. As stated previously, although you are free to make these operators doanything you please, it is almost always wisest to stay consistent with their originalmeanings.
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.
However, Standard C++ allows you to explicitly create separate prefix and postfixversions of the increment or decrement operators. To accomplish this, you must definetwo versions of the operator++( ) function. One is defined as shown in the foregoing
program. The other is declared like this:
loc operator++(int x);
If the ++precedes its operand, the operator++( ) function is called. If the ++follows its
operand, the operator++(int x) is called and xhas the value zero.
The preceding example can be generalized. Here are the general forms for the prefix
and postfix ++and ––operator functions.
// Prefix increment
type operator++( ) {
// body of prefix operator
}
390 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. In older versions of C++, it was not possibleto specify separate prefix and postfix versions of an overloaded ++or––. The prefix
form was used for both.
Overloading the Shorthand Operators
You can overload any of C++'s "shorthand" operators, such as +=, –=, and the like.
For example, this function overloads +=relative to loc:
loc loc::operator+=(loc op2)
{
longitude = op2.longitude + longitude;latitude = 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.
Operator Overloading Restrictions
There are some restrictions that apply to operator overloading. You cannot alter the
precedence of an operator. You cannot change the number of operands that an operatortakes. (You can choose to ignore an operand, however.) Except for the function call
Chapter 15: Operator Overloading 391C++operator (described later), operator functions cannot have default arguments. Finally,
these operators cannot be overloaded:
. : : .* ?
As stated, technically you are free to perform any activity inside an operator
function. 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. However, when you stray
significantly from the normal meaning of an operator, you run the risk of dangerously
destructuring your program. When someone reading your program sees a statementlikeOb1+Ob2, he or she expects something resembling addition to be taking place—
not a disk access, for example. Therefore, before decoupling an overloaded operatorfrom its normal meaning, be sure that you have sufficient reason to do so. One goodexample where decoupling is successful is found in the way C++ overloads the <<and
>>operators for I/O. Although the I/O operations have no relationship to bit shifting,
these operators provide a visual "clue" as to their meaning relative to both I/O and bitshifting, and this decoupling works. In general, however, it is best to stay within thecontext of the expected meaning of an operator when overloading it.
Except for the =operator, operator functions are inherited by a derived class.
However, a derived class is free to overload any operator (including those overloadedby the base class) it chooses relative to itself.
Operator Overloading Using a Friend Function
You can overload an operator for a class by using a nonmember function, which isusually a friend of the class. Since a friend function is not a member of the class, it
does not have a this pointer. Therefore, an overloaded friend operator function is passed
the operands explicitly. This means that a friend function that overloads a binary operatorhas two parameters, and a friend function that overloads a unary operator has oneparameter. When overloading a binary operator using a friend function, the left operandis passed in the first parameter and the right operand is passed in the second parameter.
In this program, the operator+( ) function is made into a friend:
#include <iostream>
using namespace std;
class loc {
int longitude, latitude;
public:
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.
loc operator+(loc op1, loc op2){
loc temp;
temp.longitude = op1.longitude + op2.longitude;
temp.latitude = op1.latitude + op2.latitude;
return temp;
}// Overload – for loc.
loc loc::operator-(loc op2){
loc temp;
// notice order of operands
temp.longitude = longitude – op2.longitude;temp.latitude = latitude – op2.latitude;
return temp;
}// Overload assignment for loc.392 C++: The Complete Reference
Chapter 15: Operator Overloading 393C++loc loc::operator=(loc op2)
{
longitude = op2.longitude;
latitude = op2.latitude;
return *this; // i.e., return object that generated call
}// Overload ++ for loc.
loc loc::operator++(){
longitude++;latitude++;
return *this;
}int main()
{
loc ob1(10, 20), ob2( 5, 30);
ob1 = ob1 + ob2;
ob1.show();
return 0;
}
There are some restrictions that apply to friend operator functions. First, you
may not overload the =,( ),[ ], or –>operators by using a friend function. Second, as
explained in the next section, when overloading the increment or decrement operators,
you will need to use a reference parameter when 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. This is because friend functionsdo not have this pointers. Assuming that you stay true to the original meaning of the
++and – – operators, these operations imply the modification of the operand they
operate upon. However, if you overload these operators by using a friend, then theoperand is passed by value as a parameter. This means that a friend operator functionhas no way to modify the operand. Since the friend operator function is not passed
athis pointer to the operand, but rather a copy of the operand, no changes made to
that parameter affect the operand that generated the call. However, you can remedy
this situation by specifying the parameter to the friend operator function as a referenceparameter. This causes any changes made to the parameter inside the function to affectthe operand that generated the call. For example, this program uses friend functions tooverload the prefix versions of ++and ––operators relative to the locclass:
#include <iostream>
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.
loc loc::operator=(loc op2){
longitude = op2.longitude;latitude = op2.latitude;
return *this; // i.e., return object that generated call
}// Now a friend; use a reference parameter.
loc operator++(loc &op){394 C++: The Complete Reference
op.longitude++;
op.latitude++;
return op;
}// Make op– a friend; use reference.
loc operator–(loc &op){
op.longitude–;op.latitude–;
return op;
}int main()
{
loc ob1(10, 20), ob2;
ob1.show();
++ob1;ob1.show(); // displays 11 21
ob2 = ++ob1;
ob2.show(); // displays 12 22
–ob2;
ob2.show(); // displays 11 21
return 0;
}
If you want to overload the postfix versions of the increment and decrement operators
using a friend, simply specify a second, dummy integer parameter. For example, thisshows the prototype for the friend, postfix version of the increment operator relative
toloc.
// friend, postfix version of ++
friend loc operator++(loc &op, int x);Chapter 15: Operator Overloading 395C++
396 C++: The Complete Reference
Friend Operator Functions Add Flexibility
In many cases, whether you overload an operator by using a friend or a member
function makes no functional difference. In those cases, it is usually best to overloadby using member functions. However, there is one situation in which overloading byusing a friend increases the flexibility of an overloaded operator. Let's examine thiscase now.
As you know, when you overload a binary operator by using a member function,
the object on the left side of the operator generates the call to the operator function.Further, a pointer to that object is passed in the this pointer. Now, assume some
class that defines a member operator+( ) function that adds an object of the class to
an integer. Given an object of that class called Ob, the following expression is valid:
Ob + 100 // valid
In this case, Obgenerates the call to the overloaded +function, and the addition is
performed. But what happens if the expression is written like this?
100 + Ob // invalid
In this case, it is the integer that appears on the left. Since an integer is a built-in type,no operation between an integer and an object of Ob's type is defined. Therefore, the
compiler will not compile this expression. As you can imagine, in some applications,having to always position the object on the left could be a significant burden and causeof frustration.
The solution to the preceding problem is to overload addition using a friend, not
a member, function. When this is done, both arguments are explicitly passed to theoperator function. Therefore, to allow both object+integer and integer+object, simply
overload the function twice—one version for each situation. Thus, when you overloadan operator by using two friend functions, the object may appear on either the left or
right side of the operator.
This program illustrates how friend functions are used to define an operation that
involves an object and built-in type:
#include <iostream>
using namespace std;
class loc {
int longitude, latitude;
public:
Chapter 15: Operator Overloading 397C++loc() {}
loc(int lg, int lt) {
longitude = lg;
latitude = lt;
}
void show() {
cout << longitude << " ";
cout << latitude << "\n";
}
friend loc operator+(loc op1, int op2);
friend loc operator+(int op1, loc op2);
};
// + is overloaded for loc + int.
loc operator+(loc op1, int op2){
loc temp;
temp.longitude = op1.longitude + op2;
temp.latitude = op1.latitude + op2;
return temp;
}
// + is overloaded for int + loc.loc operator+(int op1, loc op2){
loc temp;
temp.longitude = op1 + op2.longitude;
temp.latitude = op1 + op2.latitude;
return temp;
}int main()
{
loc ob1(10, 20), ob2( 5, 30), ob3(7, 14);
ob1.show();
ob2.show();
ob3.show();
ob1 = ob2 + 10; // both of these
ob3 = 10 + ob2; // are valid
ob1.show();
ob3.show();
return 0;
}
Overloading new and delete
It is possible to overload new and delete. You might choose to do this if you want
to use some special allocation method. For example, you may want allocation routines
that automatically begin using a disk file as virtual memory when the heap has beenexhausted. Whatever the reason, it is a very simple matter to overload these operators.
The skeletons for the functions that overload new and delete are shown here:
// Allocate an object.
void *operator new(size_t size){
/* Perform allocation. Throw bad_alloc on failure.
Constructor called automatically. */
return pointer_to_memory;
}
// Delete an object.
void operator delete(void *p){
/* Free memory pointed to by p.
Destructor called automatically. */
}
The type size_t is a defined type capable of containing the largest single piece
of memory that can be allocated. (size_t is essentially an unsigned integer.) The
parameter size will contain the number of bytes needed to hold the object being
allocated. This is the amount of memory that your version of new must allocate. The
overloaded new function must return a pointer to the memory that it allocates, or
throw a bad_alloc exception if an allocation error occurs. Beyond these constraints,
the overloaded new function can do anything else you require. When you allocate an398 C++: The Complete Reference
Chapter 15: Operator Overloading 399C++object using new (whether your own version or not), the object's constructor is
automatically called.
The delete function receives a pointer to the region of memory to be freed. It
then releases the previously allocated memory back to the system. When an object
is deleted, its destructor is automatically called.
The new and delete operators may be overloaded globally so that all uses of these
operators call your custom versions. They may also be overloaded relative to one ormore classes. Lets begin with an example of overloading new and delete relative to
a class. For the sake of simplicity, no new allocation scheme will be used. Instead, theoverloaded operators will simply invoke the standard library functions malloc( ) and
free( ). (In your own application, you may, of course, implement any alternative allocation
scheme you like.)
To overload the new and delete operators for a class, simply make the overloaded
operator functions class members. For example, here the new and delete operators are
overloaded for the locclass:
#include <iostream>
#include <cstdlib>#include <new>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.
void *loc::operator new(size_t size){
void *p;
400 C++: The Complete Reference
cout << "In overloaded new.\n";
p = malloc(size);if(!p) {
bad_alloc ba;
throw ba;
}
return p;
}
// delete overloaded relative to loc.
void loc::operator delete(void *p){
cout << "In overloaded delete.\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;
}
try {
p2 = new loc (-10, -20);
} catch (bad_alloc xa) {
cout << "Allocation error for p2.\n";
return 1;;
}
p1->show();
p2->show();
delete p1;
delete p2;
return 0;
}
Output from this program is shown here.
In overloaded new.
In overloaded new.10 20-10 -20In overloaded delete.In overloaded delete.
When new and delete are for a specific class, the use of these operators on any
other type of data causes the original new ordelete to be employed. The overloaded
operators are only applied to the types for which they are defined. This means that if
you add this line to the main( ), the default new will be executed:
int *f = new float; // uses default new
You can overload new and delete globally by overloading these operators outside
of any class declaration. 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.
Of course, if you have defined any versions of new and delete relative to one or more
classes, then the class-specific versions are used when allocating objects of the class forwhich they are defined. In other words, when new ordelete are encountered, the
compiler first checks to see whether they are defined relative to the class they areoperating on. If so, those specific versions are used. If not, C++ uses the globally definednew and delete. If these have been overloaded, the overloaded versions are used.
To see an example of overloading new and delete globally, examine this program:
#include <iostream>
#include <cstdlib>#include <new>using namespace std;
class loc {
int longitude, latitude;public:
loc() {}loc(int lg, int lt) {
longitude = lg;latitude = lt;
}Chapter 15: Operator Overloading 401C++
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) {
cout << "Allocation error for p1.\n";
return 1;;
}
try {
p2 = new loc (-10, -20);
} catch (bad_alloc xa) {
cout << "Allocation error for p2.\n";
return 1;;402 C++: The Complete Reference
}
try {
f = new float; // uses overloaded new, too
} catch (bad_alloc xa) {
cout << "Allocation error for f.\n";
return 1;;
}
*f = 10.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.
Overloading new and delete for Arrays
If you want to be able to allocate arrays of objects using your own allocation system,
you will need to overload new and delete a second time. To allocate and free arrays,
you must use these forms of new and delete.
// Allocate an array of objects.
void *operator new[](size_t size){
/* Perform allocation. Throw bad_alloc on failure.
Constructor for each element called automatically. */
return pointer_to_memory;
}
// Delete an array of objects.
void operator delete[](void *p)Chapter 15: Operator Overloading 403C++
{
/* Free memory pointed to by p.
Destructor for each element called automatically.
*/
}
When allocating an array, the constructor for each object in the array is automatically
called. When freeing an array, each object's destructor is automatically called. You do
not have to provide explicit code to accomplish these actions.
The following program allocates and frees an object and an array of objects of
type loc.
#include <iostream>
#include <cstdlib>#include <new>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);
void operator delete(void *p);
void *operator new[](size_t size);
void operator delete[](void *p);
};
// new overloaded relative to loc.
void *loc::operator new(size_t size){404 C++: The Complete Reference
void *p;
cout << "In overloaded new.\n";
p = malloc(size);if(!p) {
bad_alloc ba;
throw ba;
}
return p;
}
// delete overloaded relative to loc.
void loc::operator delete(void *p){
cout << "In overloaded delete.\n";free(p);
}
// new overloaded for loc arrays.
void *loc::operator new[](size_t size){
void *p;
cout << "Using overload new[].\n";
p = malloc(size);if(!p) {
bad_alloc ba;throw ba;
}return p;
}
// delete overloaded for loc arrays.
void loc::operator delete[](void *p){
cout << "Freeing array using overloaded delete[]\n";free(p);
}
int main()
{
loc *p1, *p2;Chapter 15: Operator Overloading 405C++
int i;
try {
p1 = new loc (10, 20); // allocate an object
} catch (bad_alloc xa) {
cout << "Allocation error for p1.\n";
return 1;;
}
try {
p2 = new loc [10]; // allocate an array
} catch (bad_alloc xa) {
cout << "Allocation error for p2.\n";
return 1;;
}
p1->show();for(i=0; i<10; i++)
p2[i].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. To do so, use
these skeletons.
// Nothrow version of new.
void *operator new(size_t size, const nothrow_t &n){
// Perform allocation.if(success) return pointer_to_memory;else return 0;
}
// Nothrow version of new for arrays.406 C++: The Complete Reference
void *operator new[](size_t size, const nothrow_t &n)
{
// Perform allocation.
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 <new>. This is the type of the nothrow object. The
nothrow_t parameter is unused.
Overloading Some Special Operators
C++ defines array subscripting, function calling, and class member access as operations.
The operators that perform these functions are the [] ,() , and–>, respectively. These rather
exotic operators may be overloaded in C++, opening up some very interesting uses.
One important restriction applies to overloading these three operators: They must
be nonstatic member functions. They cannot be friends.
Overloading [ ]
In C++, the [ ]is considered a binary operator when you are overloading it. Therefore,
the general form of a member operator[ ]( ) function is as shown here:
type class-name::operator[](int i)
{
// . . .
}
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 isgenerally used.Chapter 15: Operator Overloading 407C++
408 C++: The Complete Reference
Given an object called O, the expression
O[3]
translates into this call to the operator[ ]( ) function:
O.operator[](3)
That is, the value of the expression within the subscripting operators is passed to the
operator[ ]( ) function in its explicit parameter. The this pointer will point to O, the object
that generated the call.
In the following program, atype declares an array of three integers. Its constructor
initializes each member of the array to the specified values. The overloaded operator[ ]( )
function returns the value of the array as indexed by the value of its parameter.
#include <iostream>
using namespace std;
class atype {
int a[3];
public:
atype(int i, int j, int k) {
a[0] = i;
a[1] = j;a[2] = k;
}int operator[](int i) { return a[i]; }
};
int main()
{
atype ob(1, 2, 3);
cout << ob[1]; // displays 2return 0;
}
You can design the operator[ ]( ) function in such a way that the [ ]can be used on
both the left and right sides of an assignment statement. To do this, simply specify the
return value of operator[ ]( ) as a reference. The following program makes this change
and shows its use:
#include <iostream>
using namespace std;
class atype {
int a[3];
public:
atype(int i, int j, int k) {
a[0] = i;
a[1] = j;a[2] = k;
}int &operator[](int i) { return a[i]; }
};
int main()
{
atype ob(1, 2, 3);
cout << ob[1]; // displays 2
cout << " ";
ob[1] = 25; // [] on left of =cout << ob[1]; // now displays 25return 0;
}
Because operator[ ]( ) now returns a reference to the array element indexed by i,
it can be used on the left side of an assignment to modify an element of the array. (Of
course, it may still be used on the right side as well.)
One advantage of being able to overload the [ ]operator is that it allows a means
of implementing safe array indexing in C++. As you know, in C++, it is possible tooverrun (or underrun) an array boundary at run time without generating a run-timeerror message. However, if you create a class that contains the array, and allow accessto that array only through the overloaded []subscripting operator, then you can
intercept an out-of-range index. For example, this program adds a range check tothe preceding program and proves that it works:Chapter 15: Operator Overloading 409C++
410 C++: The Complete Reference
// A safe array example.
#include <iostream>#include <cstdlib>using namespace std;
class atype {
int a[3];
public:
atype(int i, int j, int k) {
a[0] = i;
a[1] = j;a[2] = k;
}int &operator[](int i);
};
// Provide range checking for atype.
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);
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-rangereturn 0;
}
Chapter 15: Operator Overloading 411C++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. (In actual practice, some sort of
error-handling function would be called to deal with the out-of-range condition;the program would not have to terminate.)
Overloading ( )
When you overload the ( )function call operator, you are not, per se, creating a new
way to call a function. Rather, you are creating an operator function that can be passedan arbitrary number of parameters. Let's begin with an example. Given the overloadedoperator function declaration
double operator()(int a, float f, char *s);
and an object Oof its class, then the statement
O(10, 23.34, "hi");
translates into this call to the operator( ) function.
O.operator()(10, 23.34, "hi");
In general, when you overload the ( )operator, you define the parameters that
you want to pass to that function. When you use the ( )operator in your program,
the arguments you specify are copied to those parameters. As always, the objectthat generates the call (O in this example) is pointed to by the this pointer.
Here is an example of overloading ( )for the locclass. It assigns the value of its
two arguments to the longitude and latitude of the object to which it is applied.
#include <iostream>
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()(int i, int j){
longitude = i;latitude = j;
return *this;
}// Overload + for loc.
loc loc::operator+(loc op2){
loc temp;
temp.longitude = op2.longitude + longitude;
temp.latitude = op2.latitude + latitude;return temp;
}
int main()
{
loc ob1(10, 20), ob2(1, 1);
ob1.show();
ob1(7, 8); // can be executed by itselfob1.show();412 C++: The Complete Reference
ob1 = ob2 + ob1(10, 10); // can be used in expressions
ob1.show();
return 0;
}
The output produced by the program is shown here.
10 20
7 811 11
Remember, when overloading ( ),you can use any type of parameters and return
any type of value. These types will be dictated by the demands of your programs. You
can also specify default arguments.
Overloading –>
The –>pointer operator, also called the class member access operator, is considered
a unary operator when overloading. Its general usage is shown here:
object->element;
Here, object is the object that activates the call. The operator–>( ) function must return
a pointer to an object of the class that operator–>( ) operates upon. The element must be
some member accessible within the object.
The following program illustrates overloading the –>by showing the equivalence
between ob.i and ob–>i when operator–>( ) returns the this pointer:
#include <iostream>
using namespace std;
class myclass {
public:
int i;myclass *operator->() {return this;}
};
int main()
{
myclass ob;Chapter 15: Operator Overloading 413C++
414 C++: The Complete Reference
ob->i = 10; // same as ob.i
cout << ob.i << " " << ob->i;return 0;
}
Anoperator–>( ) function must be a member of the class upon which it works.
Overloading the Comma Operator
You can overload C++'s comma operator. The comma is a binary operator, and like all
overloaded operators, you can make an overloaded comma perform any operation youwant. However, if you want the overloaded comma to perform in a fashion similar toits normal operation, then your version must discard the values of all operands exceptthe rightmost. The rightmost value becomes the result of the comma operation. Thisis the way the comma works by default in C++.
Here is a program that illustrates the effect of overloading the comma operator.
#include <iostream>
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,(loc op2);
Chapter 15: Operator Overloading 415C++};
// overload comma for loc
loc loc::operator,(loc op2){
loc temp;
temp.longitude = op2.longitude;
temp.latitude = op2.latitude;cout << op2.longitude << " " << op2.latitude << "\n";
return temp;
}// Overload + for loc
loc loc::operator+(loc op2){
loc temp;
temp.longitude = op2.longitude + longitude;
temp.latitude = op2.latitude + latitude;
return temp;
}int main()
{
loc ob1(10, 20), ob2( 5, 30), ob3(1, 1);
ob1.show();
ob2.show();ob3.show();cout << "\n";
ob1 = (ob1, ob2+ob2, ob3);ob1.show(); // displays 1 1, the value of ob3return 0;
}
This program displays the following output:
10 20
5 301 1
10 60
1 11 1
Notice that although the values of the left-hand operands are discarded, each expression
is still evaluated by the compiler so that any desired side effects will be performed.
Remember, the left-hand operand is passed via this, and its value is discarded
by the operator,( ) function. The value of the right-hand operation is returned by
the function. This causes the overloaded comma to behave similarly to its defaultoperation. If you want the overloaded comma to do something else, you will haveto change these two features.416 C++: The Complete Reference
Chapter 16
Inheritance
417
Copyright © 2003 by The McGraw-Hill Companies. Click here for terms of use.
418 C++: The Complete Reference
Inheritance is one of the cornerstones of OOP because it allows the creation of
hierarchical classifications. Using inheritance, you can create a general class thatdefines traits common to a set of related items. This class may then be inherited
by other, more specific classes, each adding only those things that are unique to theinheriting class.
In keeping with standard C++ terminology, a class that is inherited is referred to
as a base class. The class that does the inheriting is called the derived class. Further, a
derived class can be used as a base class for another derived class. In this way, multipleinheritance is achieved.
C++'s support of inheritance is both rich and flexible. Inheritance was introduced in
Chapter 11. It is examined in detail here.
Base-Class Access Control
When a class inherits another, the members of the base class become members of thederived class. Class inheritance uses this general form:
class derived-class-name : access base-class-name {
//body of class
};
The access status of the base-class members inside the derived class is determined byaccess. The base-class access specifier must be either public, private,o rprotected. If no
access specifier is present, the access specifier is private by default if the derived class
is aclass. If the derived class is a struct, then public is the default in the absence of an
explicit access specifier. Let's examine the ramifications of using public orprivate
access. (The protected specifier is examined in the next section.)
When the access specifier for a base class is public, all public members of the base
become public members of the derived class, and all protected members of the basebecome protected members of the derived class. In all cases, the base's private elementsremain private to the base and are not accessible by members of the derived class. Forexample, as illustrated in this program, objects of type derived can directly access the
public members of base:
#include <iostream>
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"; }
};
class derived : public base {
int k;
public:
derived(int x) { k=x; }
void showk() { cout << k << "\n"; }
};
int main()
{
derived ob(3);
ob.set(1, 2); // access member of base
ob.show(); // access member of base
ob.showk(); // uses member of derived classreturn 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. For
example, the following program will not even compile because both set( ) and show( )
are now private elements of derived:
// This program won't compile.
#include <iostream>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.
class derived : private base {
int k;Chapter 16: Inheritance 419C++
420 C++: The Complete Reference
public:
derived(int x) { k=x; }
void showk() { cout << k << "\n"; }
};
int main()
{
derived ob(3);
ob.set(1, 2); // error, can't access set()
ob.show(); // error, can't access show()
return 0;
}
When a base class' access specifier is private, public and protected members of the base
become private members of the derived class. This means that they are still accessible by
members of the derived class but cannot be accessed by parts of your program that arenot members of either the base or derived class.
Inheritance and protected Members
The protected keyword is included in C++ to provide greater flexibility in the
inheritance mechanism. When a member of a class is declared as protected, that
member is not accessible by other, nonmember elements of the program. With oneimportant exception, access to a protected member is the same as access to a privatemember—it can be accessed only by other members of its class. The sole exception tothis is when a protected member is inherited. In this case, a protected member differssubstantially from a private one.
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. However,protected members behave differently. If the base class is inherited as public, then
the base class' protected members become protected members of the derived class andare, therefore, accessible by the derived class. By using protected, you can create class
members that are private to their class but that can still be inherited and accessed by aderived class. Here is an example:
#include <iostream>
using namespace std;
class base {
Chapter 16: Inheritance 421C++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.set(2, 3); // OK, known to derived
ob.show(); // OK, known to derived
ob.setk();
ob.showk();
return 0;
}
In this example, because base is inherited by derived aspublic and because iand
jare declared as protected, derived's function setk( ) may access them. If iand jhad
been declared as private bybase, then derived would not have access to them, and the
program would not compile.
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. For example, thisprogram is correct, and derived2 does indeed have access to iand j.
#include <iostream>
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"; }
};
// i and j inherited as protected.
class derived1 : public base {
int k;
public:
void setk() { k = i*j; } // legalvoid showk() { cout << k << "\n"; }
};
// i and j inherited indirectly through derived1.
class derived2 : public derived1 {
int m;
public:
void setm() { m = i-j; } // legalvoid showm() { cout << m << "\n"; }
};
int main()
{
derived1 ob1;derived2 ob2;
ob1.set(2, 3);
ob1.show();ob1.setk();ob1.showk();
ob2.set(3, 4);
ob2.show();ob2.setk();ob2.setm();ob2.showk();ob2.showm();
return 0;
}422 C++: The Complete Reference
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
byderived2. (However, iand jwould still be accessible by derived1.) This situation
is illustrated by the following program, which is in error (and won't compile). The
comments describe each error:
// This program won't compile.
#include <iostream>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 derived1 : private base {
int k;
public:
// this is legal because i and j are private to derived1void setk() { k = i*j; } // OKvoid showk() { cout << k << "\n"; }
};
// Access to i, j, set(), and show() not inherited.
class derived2 : public derived1 {
int m;
public:
// illegal because i and j are private to derived1void setm() { m = i-j; } // Errorvoid showm() { cout << m << "\n"; }
};
int main()
{
derived1 ob1;derived2 ob2;
ob1.set(1, 2); // error, can't use set()C++Chapter 16: Inheritance 423
424 C++: The Complete Reference
ob1.show(); // error, can't use show()
ob2.set(3, 4); // error, can't use set()
ob2.show(); // error, can't use show()
return 0;
}
Even though base is inherited as private byderived1, derived1 still has access to
base's public andprotected elements. However, it cannot pass along this privilege.
Protected Base-Class Inheritance
It is possible to inherit a base class as protected. When this is done, all public and
protected members of the base class become protected members of the derived class.
For example,
#include <iostream>
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.
class derived : protected base{
int k;
public:
// derived may access base's i and j and setij().void setk() { setij(10, 12); k = i*j; }
// may access showij() here
void showall() { cout << k << " "; showij(); }
};
int main()
{
derived ob;
// ob.setij(2, 3); // illegal, setij() is
// protected member of derived
ob.setk(); // OK, public member of derivedob.showall(); // 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. This means that they will not be
accessible inside main( ) .
Inheriting Multiple Base Classes
It is possible for a derived class to inherit two or more base classes. For example, in this
short example, derived inherits both base1 and base2.
// An example of multiple base classes.
#include <iostream>
using namespace std;
class base1 {
protected:
int x;
public:
void showx() { cout << x << "\n"; }
};
class base2 {
protected:
int y;
public:Chapter 16: Inheritance 425C++
426 C++: The Complete Reference
void showy() {cout << y << "\n";}
};
// Inherit 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.set(10, 20); // provided by derived
ob.showx(); // from base1ob.showy(); // from base2
return 0;
}
As the example illustrates, to inherit more than one base class, use a comma-
separated list. Further, be sure to use an access-specifier for each base inherited.
Constructors, Destructors, and Inheritance
There are two major questions that arise relative to constructors and destructors when
inheritance is involved. First, when are base-class and derived-class constructors anddestructors called? Second, how can parameters be passed to base-class constructors?This section examines these two important topics.
When Constructors and Destructors
Are Executed
It is possible for a base class, a derived class, or both to contain constructors and/or
destructors. It is important to understand the order in which these functions areexecuted when an object of a derived class comes into existence and when it goes outof existence. To begin, examine this short program:
#include <iostream>
using namespace std;
Chapter 16: Inheritance 427C++class base {
public:
base() { cout << "Constructing base\n"; }
~base() { cout << "Destructing base\n"; }
};
class derived: public base {
public:
derived() { cout << "Constructing derived\n"; }~derived() { cout << "Destructing derived\n"; }
};
int main()
{
derived ob;
// do nothing but construct and destruct obreturn 0;
}
As the comment in main( ) indicates, this program simply constructs and then
destroys an object called obthat is of class derived. When executed, this program
displays
Constructing base
Constructing derivedDestructing derivedDestructing base
As you can see, first base's constructor is executed followed by derived's. Next (because
obis immediately destroyed in this program), derived's destructor is called, followed
bybase's.
The results of the foregoing experiment can be generalized. When an object of a
derived class is created, the base class’ constructor will be called first, followed by the
derived class’ constructor. When a derived object is destroyed, its destructor is calledfirst, followed by the base class' destructor. Put differently, constructors are executed intheir order of derivation. Destructors are executed in reverse order of derivation.
If you think about it, it makes sense that constructors are executed in order of
derivation. Because a base class has no knowledge of any derived class, any
428 C++: The Complete Reference
initialization it needs to perform is separate from and possibly prerequisite to any
initialization performed by the derived class. Therefore, it must be executed first.
Likewise, it is quite sensible that destructors be executed in reverse order of
derivation. Because the base class underlies the derived class, the destruction ofthe base object implies the destruction of the derived object. Therefore, the deriveddestructor must be called before the object is fully destroyed.
In cases of multiple inheritance (that is, where a derived class becomes the base
class for another derived class), the general rule applies: Constructors are called inorder of derivation, destructors in reverse order. For example, this program
#include <iostream>
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 obreturn 0;
}
displays this output:
Constructing base
Constructing derived1Constructing derived2Destructing derived2Destructing derived1Destructing base
The same general rule applies in situations involving multiple base classes.
For example, this program
#include <iostream>using namespace std;
class base1 {
public:
base1() { cout << "Constructing base1\n"; }~base1() { cout << "Destructing base1\n"; }
};
class base2 {
public:
base2() { cout << "Constructing base2\n"; }~base2() { cout << "Destructing base2\n"; }
};
class derived: public base1, public base2 {
public:
derived() { cout << "Constructing derived\n"; }~derived() { cout << "Destructing derived\n"; }
};
int main()
{
derived ob;
// construct and destruct obreturn 0;
}Chapter 16: Inheritance 429C++
produces this output:
Constructing base1
Constructing base2Constructing derivedDestructing derivedDestructing base2Destructing base1
As you can see, constructors are called in order of derivation, left to right, as specified
inderived's inheritance list. Destructors are called in reverse order, right to left. This
means that had base2 been specified before base1 inderived's list, as shown here:
class derived: public base2, public base1 {
then the output of this program would have looked like this:
Constructing base2Constructing base1Constructing derivedDestructing derivedDestructing base1Destructing base2
Passing Parameters to Base-Class Constructors
So far, none of the preceding examples have included constructors that require
arguments. In cases where only the derived class' constructor requires one or moreparameters, you simply use the standard parameterized constructor syntax (seeChapter 12). However, how do you pass arguments to a constructor in a base class?The answer is to use an expanded form of the derived class's constructor declarationthat passes along arguments to one or more base-class constructors. The general formof this expanded derived-class constructor declaration is shown here:
derived-constructor(arg-list) : base1(arg-list),
base2(arg-list),
// …baseN(arg-list)
{
//body of derived constructor
}430 C++: The Complete Reference
Chapter 16: Inheritance 431C++Here, base1 through baseN are the names of the base classes inherited by the derived
class. Notice that a colon separates the derived class' constructor declaration from the
base-class specifications, and that the base-class specifications are separated from eachother by commas, in the case of multiple base classes. Consider this program:
#include <iostream>
using namespace std;
class base {
protected:
int i;
public:
base(int x) { i=x; cout << "Constructing base\n"; }~base() { cout << "Destructing base\n"; }
};
class derived: public base {
int j;
public:
// derived uses x; y is passed along to base.
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.show(); // displays 4 3return 0;
}
Here, derived's constructor is declared as taking two parameters, xand y. However,
derived( ) uses only x; yis passed along to base( ). In general, the derived class' constructor
must declare both the parameter(s) that it requires as well as any required by the base
class. As the example illustrates, any parameters required by the base class are passedto it in the base class' argument list specified after the colon.
Here is an example that uses multiple base classes:
#include <iostream>
using namespace std;
class base1 {
protected:
int i;
public:
base1(int x) { i=x; cout << "Constructing base1\n"; }~base1() { cout << "Destructing base1\n"; }
};
class base2 {
protected:
int k;
public:
base2(int x) { k=x; cout << "Constructing base2\n"; }~base2() { cout << "Destructing base1\n"; }
};
class derived: public base1, public base2 {
int j;
public:
derived(int x, int y, int z): base1(y), base2(z)
{ j=x; cout << "Constructing derived\n"; }
~derived() { cout << "Destructing derived\n"; }
void show() { cout << i << " " << j << " " << k << "\n"; }
};
int main()
{
derived ob(3, 4, 5);
ob.show(); // displays 4 3 5return 0;
}
It is important to understand that arguments to a base-class constructor are passed
via arguments to the derived class' constructor. Therefore, even if a derived class'
constructor does not use any arguments, it will still need to declare one if the base class432 C++: The Complete Reference
Chapter 16: Inheritance 433C++requires it. In this situation, the arguments passed to the derived class are simply
passed along to the base. For example, in this program, the derived class' constructortakes no arguments, but base1( ) and base2( ) do:
#include <iostream>
using namespace std;
class base1 {
protected:
int i;
public:
base1(int x) { i=x; cout << "Constructing base1\n"; }~base1() { cout << "Destructing base1\n"; }
};
class base2 {
protected:
int k;
public:
base2(int x) { k=x; cout << "Constructing base2\n"; }~base2() { cout << "Destructing base2\n"; }
};
class derived: public base1, public base2 {
public:
/* Derived constructor uses no parameter,
but still must be declared as taking them topass them along to base classes.
*/
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.show(); // displays 3 4
434 C++: The Complete Reference
return 0;
}
A derived class' constructor is free to make use of any and all parameters that it is
declared as taking, even if one or more are passed along to a base class. Put differently,
passing an argument along to a base class does not preclude its use by the derived classas well. For example, this fragment is perfectly valid:
class derived: public base {
int j;
public:
// derived uses both x and y and then passes them to base.
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 includes function
calls and variables. This is in keeping with the fact that C++ allows dynamicinitialization.
Granting Access
When a base class is inherited as private, all public and protected members of that
class become private members of the derived class. However, in certain circumstances,you may want to restore one or more inherited members to their original accessspecification. For example, you might want to grant certain public members of thebase class public status in the derived class even though the base class is inherited asprivate. In Standard C++, you have two ways to accomplish this. First, you can use ausing statement, which is the preferred way. The using statement is designed primarily
to support namespaces and is discussed in Chapter 23. The second way to restore aninherited member's access specification is to employ an access declaration within the derived
class. Access declarations are currently supported by Standard C++, but they aredeprecated. This means that they should not be used for new code. Since there are stillmany, many existing programs that use access declarations, they will be examined here.
An access declaration takes this general form:
base-class::member;
Chapter 16: Inheritance 435C++The access declaration is put under the appropriate access heading in the derived class'
declaration. Notice that no type declaration is required (or, indeed, allowed) in anaccess declaration.
To see how an access declaration works, let's begin with this short fragment:
class base {
public:
int j; // public in base
};
// Inherit base as private.
class derived: private base {public:
// here is access declarationbase::j; // make j public again…
};
Because base is inherited as private byderived, the public member jis made a private
member of derived. However, by including
base::j;
as the access declaration under derived's public heading, jis restored to its public status.
You can use an access declaration to restore the access rights of public and protected
members. However, you cannot use an access declaration to raise or lower a member's
access status. For example, a member declared as private in a base class cannot bemade public by a derived class. (If C++ allowed this to occur, it would destroy itsencapsulation mechanism!)
The following program illustrates the access declaration; notice how it uses access
declarations to restore j,seti( ), and geti( ) topublic status.
#include <iostream>
using namespace std;
class base {
int i; // private to base
public:
int j, k;
void seti(int x) { i = x; }int geti() { return i; }
};
// Inherit base as private.
class derived: private base {public:
/* The next three statements override
base's inheritance as private and restore j,seti(), and geti() to public access. */
base::j; // make j public again – but not kbase::seti; // make seti() publicbase::geti; // make geti() public
// base::i; // illegal, you cannot elevate access
int a; // public
};
int main()
{
derived ob;
//ob.i = 10; // illegal because i is private in derived
ob.j = 20; // legal because j is made public in derived
//ob.k = 30; // illegal because k is private in derived
ob.a = 40; // legal because a is public in derivedob.seti(10);
cout << ob.geti() << " " << ob.j << " " << ob.a;return 0;
}
Access declarations are supported in C++ to accommodate those situations in
which most of an inherited class is intended to be made private, but a few members
are to retain their public or protected status.436 C++: The Complete Reference
Chapter 16: Inheritance 437C++While Standard C++ still supports access declarations, they are deprecated. This means
that they are allowed for now, but they might not be supported in the future. Instead, thestandard suggests achieving the same effect by applying the using keyword.
Virtual Base Classes
An element of ambiguity can be introduced into a C++ program when multiple baseclasses are inherited. For example, consider this incorrect program:
// This program contains an error and will not compile.
#include <iostream>using namespace std;
class base {
public:
int i;
};
// derived1 inherits base.
class derived1 : public base {public:
int j;
};
// derived2 inherits base.
class derived2 : public base {public:
int k;
};
/* derived3 inherits both derived1 and derived2.
This means that there are two copies of base
in derived3! */
class derived3 : public derived1, public derived2 {public:
int sum;
};
int main()
{
derived3 ob;
ob.i = 10; // this is ambiguous, which i???
ob.j = 20;ob.k = 30;
// i ambiguous here, too
ob.sum = ob.i + ob.j + ob.k;
// also ambiguous, which i?
cout << ob.i << " ";
cout << ob.j << " " << ob.k << " ";
cout << ob.sum;
return 0;
}
As the comments in the program indicate, both derived1 and derived2 inherit base.
However, derived3 inherits both derived1 and derived2. This means that there are two
copies of base present in an object of type derived3. Therefore, in an expression like
ob.i = 10;
which iis being referred to, the one in derived1 or the one in derived2? Because there
are two copies of base present in object ob, there are two ob.is! As you can see, the
statement is inherently ambiguous.
There are two ways to remedy the preceding program. The first is to apply the
scope resolution operator to iand manually select one i.For example, this version of
the program does compile and run as expected:
// This program uses explicit scope resolution to select i.
#include <iostream>using namespace std;
class base {
public:
int i;
};
// derived1 inherits base.438 C++: The Complete Reference
class derived1 : public base {
public:
int j;
};
// derived2 inherits base.
class derived2 : public base {public:
int k;
};
/* derived3 inherits both derived1 and derived2.
This means that there are two copies of base
in derived3! */
class derived3 : public derived1, public derived2 {public:
int sum;
};
int main()
{
derived3 ob;
ob.derived1::i = 10; // scope resolved, use derived1's i
ob.j = 20;ob.k = 30;
// scope resolved
ob.sum = ob.derived1::i + ob.j + ob.k;
// also resolved here
cout << ob.derived1::i << " ";
cout << ob.j << " " << ob.k << " ";
cout << ob.sum;
return 0;
}
As you can see, because the ::was applied, the program has manually selected
derived1's version of base. However, this solution raises a deeper issue: What if only
one copy of base is actually required? Is there some way to prevent two copies fromChapter 16: Inheritance 439C++
being included in derived3? The answer, as you probably have guessed, is yes. This
solution is achieved using virtual base classes.
When two or more objects are derived from a common base class, you can prevent
multiple copies of the base class from being present in an object derived from those
objects by declaring the base class as virtual when it is inherited. You accomplish this
by preceding the base class' name with the keyword virtual when it is inherited. For
example, here is another version of the example program in which derived3 contains
only one copy of base:
// This program uses virtual base classes.
#include <iostream>using namespace std;
class base {
public:
int i;
};
// derived1 inherits base as virtual.
class derived1 : virtual public base {public:
int j;
};
// derived2 inherits base as virtual.
class derived2 : virtual public base {public:
int k;
};
/* derived3 inherits both derived1 and derived2.
This time, there is only one copy of base class. */
class derived3 : public derived1, public derived2 {
public:
int sum;
};
int main()
{
derived3 ob;
ob.i = 10; // now unambiguous440 C++: The Complete Reference
ob.j = 20;
ob.k = 30;
// unambiguous
ob.sum = ob.i + ob.j + ob.k;
// unambiguous
cout << ob.i << " ";
cout << ob.j << " " << ob.k << " ";
cout << ob.sum;
return 0;
}
As you can see, the keyword virtual precedes the rest of the inherited class'
specification. Now that both derived1 and derived2 have inherited base asvirtual,
any multiple inheritance involving them will cause only one copy of base to be present.
Therefore, in derived3, there is only one copy of base and ob.i = 10 is perfectly valid
and unambiguous.
One further point to keep in mind: Even though both derived1 and derived2
specify base asvirtual, base is still present in objects of either type. For example, the
following sequence is perfectly valid:
// define a class of type derived1
derived1 myclass;
myclass.i = 88;
The only difference between a normal base class and a virtual one is what occurs
when an object inherits the base more than once. If virtual base classes are used, then
only one base class is present in the object. Otherwise, multiple copies will be found.Chapter 16: Inheritance 441C++
This page intentionally left blank
Chapter 1 7
Virtual Functions
and Polymorphism
443
Copyright © 2003 by The McGraw-Hill Companies. Click here for terms of use.
Polymorphism is supported by C++ both at compile time and at run time. As
discussed in earlier chapters, compile-time polymorphism is achieved byoverloading functions and operators. Run-time polymorphism is accomplished
by using inheritance and virtual functions, and these are the topics of this chapter.
Virtual Functions
Avirtual function is a member function that is declared within a base class and redefined
by a derived class. To create a virtual function, precede the function's declaration in thebase class with the keyword virtual. When a class containing a virtual function is inherited,
the derived class redefines the virtual function to fit its own needs. In essence, virtualfunctions implement the "one interface, multiple methods" philosophy that underliespolymorphism. The virtual function within the base class defines the form of the interface
to that function. Each redefinition of the virtual function by a derived class implementsits operation as it relates specifically to the derived class. That is, the redefinitioncreates a specific method.
When accessed "normally," virtual functions behave just like any other type of class
member function. However, what makes virtual functions important and capable ofsupporting run-time polymorphism is how they behave when accessed via a pointer.As discussed in Chapter 13, a base-class pointer can be used to point to an object ofany class derived from that base. When a base pointer points to a derived object thatcontains a virtual function, C++ determines which version of that function to call basedupon the type of object pointed to by the pointer. And this determination is made at run
time. Thus, when different objects are pointed to, different versions of the virtual functionare executed. The same effect applies to base-class references.
To begin, examine this short example:
#include <iostream>
using namespace std;
class base {
public:
virtual void vfunc() {
cout << "This is base's vfunc().\n";
}
};
class derived1 : public base {
public:
void vfunc() {
cout << "This is derived1's vfunc().\n";
}444 C++: The Complete Reference
};
class derived2 : public base {
public:
void vfunc() {
cout << "This is derived2's vfunc().\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()
return 0;
}
This program displays the following:
This is base's vfunc().
This is derived1's vfunc().This is derived2's vfunc().
As the program illustrates, inside base, the virtual function vfunc( ) is declared.
Notice that the keyword virtual precedes the rest of the function declaration. When
vfunc( ) is redefined by derived1 and derived2, the keyword virtual is not needed.
(However, it is not an error to include it when redefining a virtual function inside a
derived class; it's just not needed.)Chapter 17: Virtual Functions and Polymorphism 445C++
In this program, base is inherited by both derived1 and derived2. Inside each
class definition, vfunc( ) is redefined relative to that class. Inside main( ), four
variables are declared:
Name Type
p base class pointer
b object of base
d1 object of derived1d2 object of derived2
Next, pis assigned the address of b,and vfunc( ) is called via p. Since pis pointing
to an object of type base, that version of vfunc( ) is executed. Next, pis set to the
address of d1, and again vfunc( ) is called by using p. This time ppoints to an object
of type derived1. This causes derived1::vfunc( ) to be executed. Finally, pis assigned
the address of d2, and p
>vfunc( ) causes the version of vfunc( ) redefined inside
derived2 to be executed. The key point here is that the kind of object to which ppoints
determines which version of vfunc( ) is executed. Further, this determination is made
at run time, and this process forms the basis for run-time polymorphism.
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. For example, assuming the precedingexample, this is syntactically valid:
d2.vfunc(); // calls derived2's vfunc()
Although calling a virtual function in this manner is not wrong, it simply does not takeadvantage of the virtual nature of vfunc( ).
At first glance, the redefinition of a virtual function by a derived class appears similar
to function overloading. However, this is not the case, and the term overloading is not
applied to virtual function redefinition because several differences exist. Perhaps themost important is that the prototype for a redefined virtual function must match exactlythe prototype specified in the base class. This differs from overloading a normal function,in which return types and the number and type of parameters may differ. (In fact, whenyou overload a function, either the number or the type of the parameters must differ! It
is through these differences that C++ can select the correct version of an overloadedfunction.) However, when a virtual function is redefined, all aspects of its prototype mustbe the same. If you change the prototype when you attempt to redefine a virtual function,the function will simply be considered overloaded by the C++ compiler, and its virtualnature will be lost. Another important restriction is that virtual functions must be446 C++: The Complete Reference
C++Chapter 17: Virtual Functions and Polymorphism 447
nonstatic members of the classes of which they are part. They cannot be friends. Finally,
constructor functions cannot be virtual, but destructor functions can.
Because of the restrictions and differences between function overloading and
virtual function redefinition, the term overriding is used to describe virtual function
redefinition by a derived class.
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 througha base-class reference. As explained in Chapter 13, a reference is an implicit pointer.Thus, a base-class reference can be used to refer to an object of the base class or anyobject derived from that base. When a virtual function is called through a base-classreference, the version of the function executed is determined by the object beingreferred to at the time of the call.
The most common situation in which a virtual function is invoked through a base
class reference is when the reference is a function parameter. For example, consider thefollowing variation on the preceding program.
/* Here, a base class reference is used to access
a virtual function. */
#include <iostream>
using namespace std;
class base {
public:
virtual void vfunc() {
cout << "This is base's vfunc().\n";
}
};
class derived1 : public base {
public:
void vfunc() {
cout << "This is derived1's vfunc().\n";
}
};
class derived2 : public base {
public:
448 C++: The Complete Reference
void vfunc() {
cout << "This is derived2's vfunc().\n";
}
};
// Use a base class reference parameter.
void f(base &r) {
r.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. In this example, the
function f( )defines a reference parameter of type base. Inside main( ), the function
is called using objects of type base, derived1, and derived2. Inside f( ), the specific
version of vfunc( ) that is called is determined by the type of object being referenced
when the function is called.
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.
The Virtual Attribute Is Inherited
When a virtual function is inherited, its virtual nature is also inherited. This means thatwhen a derived class that has inherited a virtual function is itself used as a base classfor another derived class, the virtual function can still be overridden. Put differently, nomatter how many times a virtual function is inherited, it remains virtual. For example,consider this program:
#include <iostream>
using namespace std;
class base {
public:
virtual void vfunc() {
cout << "This is base's vfunc().\n";
}
};
class derived1 : public base {
public:
void vfunc() {
cout << "This is derived1's vfunc().\n";
}
};
/* derived2 inherits virtual function vfunc()
from derived1. */
class derived2 : public derived1 {
public:
// vfunc() is still virtualvoid vfunc() {
cout << "This is derived2's vfunc().\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()Chapter 17: Virtual Functions and Polymorphism 449C++
450 C++: The Complete Reference
return 0;
}
As expected, the preceding program displays this output:
This is base's vfunc().
This is derived1's vfunc().This is derived2's vfunc().
In this case, derived2 inherits derived1 rather than base, but vfunc( ) is still virtual.
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. However, the function does not have to be overridden.
When a derived class fails to override a virtual function, then when an object of thatderived class accesses that function, the function defined by the base class is used. Forexample, consider this program in which derived2 does not override vfunc( ):
#include <iostream>
using namespace std;
class base {
public:
virtual void vfunc() {
cout << "This is base's vfunc().\n";
}
};
class derived1 : public base {
public:
void vfunc() {
cout << "This is derived1's vfunc().\n";
}
};
class derived2 : public base {
Chapter 17: Virtual Functions and Polymorphism 451C++public:
// vfunc() not overridden by derived2, base's is used};
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(); // use base's vfunc()
return 0;
}
The program produces this output:
This is base's vfunc().
This is derived1's vfunc().This is base'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.
The preceding program illustrates a special case of a more general rule. Because
inheritance is hierarchical in C++, it makes sense that virtual functions are also
hierarchical. This means that when a derived class fails to override a virtual function,the first redefinition found in reverse order of derivation is used. For example, in thefollowing program, derived2 is derived from derived1, which is derived from base.
However, derived2 does not override vfunc( ). This means that, relative to derived2,
the closest version of vfunc( ) is in derived1. Therefore, it is derived1::vfunc( ) that is
used when an object of derived2 attempts to call vfunc( ).
#include <iostream>
using namespace std;
class base {
public:
virtual void vfunc() {
cout << "This is base's vfunc().\n";
}
};
class derived1 : public base {
public:
void vfunc() {
cout << "This is derived1's vfunc().\n";
}
};
class derived2 : public derived1 {
public:/* vfunc() not overridden by derived2.
In this case, since derived2 is derived fromderived1, derived1's vfunc() is used.
*/};
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()452 C++: The Complete Reference
Chapter 17: Virtual Functions and Polymorphism 453C++// 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().This is derived1's vfunc().
Pure Virtual Functions
As the examples in the preceding section illustrate, when a virtual function is not
redefined by a derived class, the version defined in the base class will be used.However, in many situations there can be no meaningful definition of a virtualfunction within a base class. For example, a base class may not be able to definean object sufficiently to allow a base-class virtual function to be created. Further,in some situations you will want to ensure that all derived classes override a virtualfunction. To handle these two cases, C++ supports the pure virtual function.
Apure virtual function is a virtual function that has no definition within the base
class. To declare a pure virtual function, use this general form:
virtual type func-name(parameter-list) = 0;
When a virtual function is made pure, any derived class must provide its own
definition. If the derived class fails to override the pure virtual function, a compile-timeerror will result.
The following program contains a simple example of a pure virtual function. The
base class, number , contains an integer called val, the function setval( ), and the pure
virtual function show( ). The derived classes hextype, dectype, and octtype inherit
number and redefine show( ) so that it outputs the value of valin each respective number
base (that is, hexadecimal, decimal, or octal).
#include <iostream>
using namespace std;
class number {
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.setval(20);
d.show(); // displays 20 – decimal
h.setval(20);
h.show(); // displays 14 – hexadecimal454 C++: The Complete Reference
o.setval(20);
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. In this case, number simply provides
the common interface for the derived types to use. There is no reason to define show( )
inside number since the base of the number is undefined. Of course, you can always
create a placeholder definition of a virtual function. However, making show( ) pure
also ensures that all derived classes will indeed redefine it to meet their own needs.
Keep in mind that when a virtual function is declared as pure, all derived classes
must override it. If a derived class fails to do this, a compile-time error will result.
Abstract Classes
A class that contains at least one pure virtual function is said to be abstract. 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. Instead, an abstractclass constitutes an incomplete type that is used as a foundation for derived classes.
Although you cannot create objects of an abstract class, you can create pointers
and references to an abstract class. This allows abstract classes to support run-timepolymorphism, which relies upon base-class pointers and references to select theproper virtual function.
Using Virtual Functions
One of the central aspects of object-oriented programming is the principle of "oneinterface, multiple methods." This means that a general class of actions can be defined,the interface to which is constant, with each derivation defining its own specific operations.In concrete C++ terms, a base class can be used to define the nature of the interface to ageneral class. Each derived class then implements the specific operations as they relateto the type of data used by the derived type.
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-timepolymorphism. Using these features, you create a class hierarchy that moves fromgeneral to specific (base to derived). Following this philosophy, you define allcommon features and interfaces in a base class. In cases where certain actions can beimplemented only by the derived class, use a virtual function. In essence, in the baseChapter 17: Virtual Functions and Polymorphism 455C++
class you create and define everything you can that relates to the general case. The
derived class fills in the specific details.
Following is a simple example that illustrates the value of the "one interface,
multiple methods" philosophy. A class hierarchy is created that performs conversionsfrom one system of units to another. (For example, liters to gallons.) The base classconvert declares two variables, val1 and val2, which hold the initial and converted
values, respectively. It also defines the functions getinit( ) and getconv( ), which return
the initial value and the converted value. These elements of convert are fixed and
applicable to all derived classes that will inherit convert. 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. The specific nature of compute( )
will be determined by what type of conversion is taking place.
// Virtual function practical example.
#include <iostream>using namespace std;
class convert {
protected:
double val1; // initial valuedouble val2; // converted value
public:
convert(double i) {
val1 = i;
}double getconv() { return val2; }double getinit() { return val1; }
virtual void compute() = 0;
};// Liters to gallons.
class l_to_g : public convert {public:
l_to_g(double i) : convert(i) { }void compute() {
val2 = val1 / 3.7854;
}
};
// Fahrenheit to Celsius
class f_to_c : public convert {456 C++: The Complete Reference
public:
f_to_c(double i) : convert(i) { }
void compute() {
val2 = (val1-32) / 1.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. These classes perform the conversions of liters to gallons and Fahrenheit
to Celsius, respectively. Each derived class overrides compute( ) in its own way to
perform the desired conversion. However, even though the actual conversion (that
is, method) differs between l_to_g and f_to_c, the interface remains constant.
One of the benefits of derived classes and virtual functions is that handling a new
case is a very easy matter. For example, assuming the preceding program, you can adda conversion from feet to meters by including this class:
// Feet to meters
class f_to_m : public convert {public:
f_to_m(double i) : convert(i) { }Chapter 17: Virtual Functions and Polymorphism 457C++
void compute() {
val2 = val1 / 3.28;
}
};
An important use of abstract classes and virtual functions is in class libraries. You
can create a generic, extensible class library that will be used by other programmers.
Another programmer will inherit your general class, which defines the interface andall elements common to all classes derived from it, and will add those functionsspecific to the derived class. By creating class libraries, you are able to create andcontrol the interface of a general class while still letting other programmers adaptit to their specific needs.
One final point: The base class convert is an example of an abstract class. The
virtual function compute( ) is not defined within convert because no meaningful
definition can be provided. The class convert simply does not contain sufficient
information for compute( ) to be defined. It is only when convert is inherited by a
derived class that a complete type is created.
Early vs. Late Binding
Before concluding this chapter on virtual functions and run-time polymorphism, thereare two terms that need to be defined because they are used frequently in discussionsof C++ and object-oriented programming: early binding and late binding.
Early binding refers to events that occur at compile time. In essence, early binding
occurs when all information needed to call a function is known at compile time. (Putdifferently, early binding means that an object and a function call are bound duringcompilation.) Examples of early binding include normal function calls (includingstandard library functions), overloaded function calls, and overloaded operators. Themain advantage to early binding is efficiency. Because all information necessary to calla function is determined at compile time, these types of function calls are very fast.
The opposite of early binding is late binding. As it relates to C++, late binding refers
to function calls that are not resolved until run time. Virtual functions are used toachieve late binding. As you know, when access is via a base pointer or reference, thevirtual function actually called is determined by the type of object pointed to by thepointer. Because in most cases this cannot be determined at compile time, the objectand the function are not linked until run time. The main advantage to late binding isflexibility. Unlike early binding, late binding allows you to create programs that canrespond to events occurring while the program executes without having to create alarge amount of "contingency code." Keep in mind that because a function call is notresolved until run time, late binding can make for somewhat slower execution times.458 C++: The Complete Reference
Chapter 18
Templates
459
Copyright © 2003 by The McGraw-Hill Companies. Click here for terms of use.
The template is one of C++'s most sophisticated and high-powered features.
Although not part of the original specification for C++, it was added severalyears ago and is supported by all modern C++ compilers. Using templates, it
is possible to create generic functions and classes. In a generic function or class, thetype of data upon which the function or class operates is specified as a parameter.Thus, you can use one function or class with several different types of data withouthaving to explicitly recode specific versions for each data type. Both generic functionsand generic classes are discussed in this chapter.
Generic Functions
A generic function defines a general set of operations that will be applied to various typesof data. The type of data that the function will operate upon is passed to it as a parameter.Through a generic function, a single general procedure can be applied to a wide range ofdata. As you probably know, many algorithms are logically the same no matter whattype of data is being operated upon. For example, the Quicksort sorting algorithm is thesame whether it is applied to an array of integers or an array of floats. It is just that thetype of the data being sorted is different. By creating a generic function, you can definethe nature of the algorithm, independent of any data. Once you have done this, thecompiler will automatically generate the correct code for the type of data that is actuallyused when you execute the function. In essence, when you create a generic function youare creating a function that can automatically overload itself.
A generic function is created using the keyword template. The normal meaning of
the word "template" accurately reflects its use in C++. It is used to create a template (orframework) that describes what a function will do, leaving it to the compiler to fill inthe details as needed. The general form of a template function definition is shown here:
template <class Ttype> ret-type func-name(parameter list)
{
//body of function
}
Here, Ttype is a placeholder name for a data type used by the function. This name
may be used within the function definition. However, it is only a placeholder that thecompiler will automatically replace with an actual data type when it creates a specificversion of the function. Although the use of the keyword class to specify a generic type
in atemplate declaration is traditional, you may also use the keyword typename.
The following example creates a generic function that swaps the values of the two
variables with which it is called. Because the general process of exchanging two valuesis independent of the type of the variables, it is a good candidate for being made into ageneric function.460 C++: The Complete Reference
// Function template example.
#include <iostream>using namespace std;
// This is a function template.
template <class X> void swapargs(X &a, X &b){
X temp;
temp = a;
a = b;b = temp;
}
int main()
{
int i=10, j=20;double x=10.1, y=23.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 floatsswapargs(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. The line:
template <class X> void swapargs(X &a, X &b)
tells the compiler two things: that a template is being created and that a generic
definition is beginning. Here, Xis a generic type that is used as a placeholder. After the
template portion, the function swapargs( ) is declared, using Xas the data type of the
values that will be swapped. In main( ), the swapargs( ) function is called using threeChapter 18: Templates 461C++
462 C++: The Complete Reference
different types of data: ints, doubles, and chars. Because swapargs( ) is a generic
function, the compiler automatically creates three versions of swapargs( ): one that
will exchange integer values, one that will exchange floating-point values, and one
that will swap characters.
Here are some important terms related to templates. First, a generic function (that is,
a function definition preceded by a template statement) is also called a template function.
Both terms will be used interchangeably in this book. When the compiler creates a specificversion of this function, it is said to have created a specialization. This is also called a
generated function. The act of generating a function is referred to as instantiating it. Put
differently, a generated function is a specific instance of a template function.
Since C++ does not recognize end-of-line as a statement terminator, the template
clause of a generic function definition does not have to be on the same line as thefunction's name. The following example shows another common way to format theswapargs( ) function.
template <class X>
void swapargs(X &a, X &b){
X temp;
temp = a;
a = b;b = temp;
}
If you use this form, it is important to understand that no other statements can occur
between the template statement and the start of the generic function definition. For
example, the fragment shown next will not compile.
// This will not compile.
template <class X>int i; // this is an errorvoid 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.
C++Chapter 18: Templates 463
A Function with Two Generic Types
You can define more than one generic data type in the template statement by using a
comma-separated list. For example, this program creates a template function that has
two generic types.
#include <iostream>
using namespace std;
template <class type1, class type2>
void myfunc(type1 x, type2 y){
cout << x << ' ' << y << '\n';
}
int main()
{
myfunc(10, "I like C++");
myfunc(98.6, 19L);return 0;
}
In this example, the placeholder types type1 and type2 are replaced by the
compiler with the data types intand char *, and double and long, respectively,
when the compiler generates the specific instances of myfunc( ) within main( ).
When you create a template function, you are, in essence, allowing the compiler to
generate as many different versions of that function as are necessary for handlingthe various ways that your program calls the function.
Explicitly Overloading a Generic Function
Even though a generic function overloads itself as needed, you can explicitly overloadone, too. This is formally called explicit specialization. If you overload a generic function,
that overloaded function overrides (or "hides") the generic function relative to thatspecific version. For example, consider the following revised version of the argument-swapping example shown earlier.
// Overriding a template function.
#include <iostream>using namespace std;
template <class X> void swapargs(X &a, X &b)
{
X temp;
temp = a;
a = b;b = temp;cout << "Inside template swapargs.\n";
}
// This overrides the generic version of swapargs() for ints.
void swapargs(int &a, int &b){
int temp;
temp = a;
a = b;b = temp;cout << "Inside swapargs int specialization.\n";
}
int main()
{
int i=10, j=20;double x=10.1, y=23.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;
}464 C++: The Complete Reference
Chapter 18: Templates 465C++This program displays the following output.
Original i, j: 10 20
Original x, y: 10.1 23.3Original a, b: x zInside swapargs int specialization.Inside template swapargs.Inside template swapargs.Swapped i, j: 20 10Swapped x, y: 23.3 10.1Swapped 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. Thus,
the compiler does not generate this version of the generic swapargs( ) function, because
the generic function is overridden by the explicit overloading.
Recently, a new-style syntax was introduced to denote the explicit specialization
of a function. This new method uses the template keyword. For example, using the
new-style specialization syntax, the overloaded swapargs( ) function from the
preceding program looks like this.
// Use new-style specialization syntax.template<> void swapargs<int>(int &a, int &b){
int temp;
temp = a;
a = b;b = temp;cout << "Inside swapargs int specialization.\n";
}
As you can see, the new-style syntax uses the template<> construct to indicate
specialization. The type of data for which the specialization is being created is placed
inside the angle brackets following the function name. This same syntax is usedto specialize any type of generic function. While there is no advantage to using onespecialization syntax over the other at this time, the new-style is probably a betterapproach for the long term.
Explicit specialization of a template allows you to tailor a version of a generic
function to accommodate a unique situation—perhaps to take advantage of someperformance boost that applies to only one type of data, for example. However, asa general rule, if you need to have different versions of a function for different datatypes, you should use overloaded functions rather than templates.
466 C++: The Complete Reference
Overloading a Function Template
In addition to creating explicit, overloaded versions of a generic function, you can also
overload the template specification itself. To do so, simply create another version of the
template that differs from any others in its parameter list. For example:
// Overload a function template declaration.
#include <iostream>using namespace std;
// First version of f() template.
template <class X> void f(X a){
cout << "Inside f(X a)\n";
}
// Second version of f() template.
template <class X, class Y> void f(X a, Y b){
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.
Using Standard Parameters with Template Functions
You can mix standard parameters with generic type parameters in a template function.
These nongeneric parameters work just like they do with any other function. Forexample:
// Using standard parameters in a template function.
#include <iostream>using namespace std;
Copyright Notice
© Licențiada.org respectă drepturile de proprietate intelectuală și așteaptă ca toți utilizatorii să facă același lucru. Dacă consideri că un conținut de pe site încalcă drepturile tale de autor, te rugăm să trimiți o notificare DMCA.
Acest articol: 0-07-222680-3.All trademarks are trademarks of their respective owners. Rather than put a trademark symbol after every occurrence of a trademarked [630814] (ID: 630814)
Dacă considerați că acest conținut vă încalcă drepturile de autor, vă rugăm să depuneți o cerere pe pagina noastră Copyright Takedown.
