Borland_C++_Version_3.1_Programmers_Guide_1992 Borland C Version 3.1 Programmers Guide 1992
Borland_C++_Version_3.1_Programmers_Guide_1992 Borland_C++_Version_3.1_Programmers_Guide_1992
User Manual: Borland_C++_Version_3.1_Programmers_Guide_1992
Open the PDF directly: View PDF
.
Page Count: 483
| Download | |
| Open PDF In Browser | View PDF |
3.1 PROGRAMMER'S GUIDE • LANGUAGE STRUCTURE • CLASS LIBRARIES • ADVANCED PROGRAMMING TECHNIQUES • ANSI CIMPLEMENTATION BORLAND Borland C++ Version 3.1 Programmer's Guide BORLAND INTERNATIONAL INC. 1800 GREEN HILLS ROAD P.O. BOX 660001, SCOTTS VALLEY, CA 95067-0001 Copyright © 1991, 1992 by Borland International. All rights reserved. All Borland products are trademarks or registered trademarks of Borland International, Inc. Other brand and product names are trademarks or registered trademarks of their respective holders. Windows, as used in this manual, refers to Microsoft's im plementation of a windows system. PRINTED IN THE USA. Rl 10 9 8 7 6 5 4 c o N T Introduction What's in this book .................... An introduction to the formal definitions. Syntax and terminology ............. 1 1 2 3 Chapter 1 Lexical elements 5 Whitespace .......................... 6 Line splicing with \ ................. 6 Comments ......................... 7 C comments . . . . . . . . . . . . . . . . . . . . .. 7 Nested comments ................. 7 C++ comments ................... 8 Comment delimiters and whitespace . 8 Tokens .............................. 8 Keywords .......................... 9 Identifiers . . . . . . . . . . . . . . . . . . . . . . . .. 10 Naming and length restrictions .... 10 Identifiers and case sensitivity ..... 10 Uniqueness and scope ............ 11 Constants . . . . . . . . . . . . . . . . . . . . . . . .. 11 Integer constants . . . . . . . . . . . . . . . .. 11 Decimal constants . . . . . . . . . . . . .. 11 Octal constants ................ 12 Hexadecimal constants ......... 13 Long and unsigned suffixes ..... 13 Character constants .............. 14 Escape sequences .............. 14 Borland C++ special two-character constants ..................... 15 Signed and unsigned char ....... 15 Wide character constants ........ 16 Floating-point constants .......... 16 Floating-point constants-data types ......................... 16 Enumeration constants ......... 17 String literals ... . . . . . . . . . . . . . . . .. 18 E N T s Constants and internal representation ................... 19 .Constant expressions .. . . . . . . . . . .. 20 Punctuators ....................... 21 Brackets ........................ 21 Parentheses ..................... 21 Braces .......................... 21 Comma ......................... 22 Semicolon. . . . . . . . . . . . . . . . . . . . . .. 22 Colon .......................... 23 Ellipsis ......................... 23 Asterisk (pointer declaration) ...... 23 Equal sign (initializer) ............ 23 Pound sign (preprocessor directive) .24 Chapter 2 Language structure Declarations ......................... Objects ........................... Lvalues ........................... Rvalues ......................... Types and storage classes ........... Scope ............................ Block scope ..................... Function scope .................. Function prototype scope ......... File scope ....................... Class scope (C++) ................ Scope and name spaces ........... Visibility. . . . . . . . . . . . . . . . . . . . . . . . .. Duration .......................... Static duration ................... Local duration . . . . . . . . . . . . . . . . . .. Dynamic duration ............... Translation units ................... Linkage ........................... N arne mangling ................. 25 25 25 26 27 27 27 28 28 28 28 28 28 29 29 29 30 30 31 31 32 Declaration syntax ................... 33 Tentative definitions ............... 33 Possible declarations ............... 34 External declarations and definitions . 36 Type specifiers .................... 38 Type taxonomy . . . . . . . . . . . . . . . . . . .. 38 Type void . . . . . . . . . . . . . . . . . . . . . .. 39 The fundamental types ............. 39 Integral types .................... 40 Floating-point types .............. 41 Standard conversions . . . . . . . . . . . .. 41 Special char, int, and enum conver~ons ..................... 42 Initialization ...................... 42 Arrays, structures, and unions ..... 43 Simple declarations ................ 44 Storage class specifiers . . . . . . . . . . . . .. 45 Use of storage class specifier auto .. 45 Use of storage class specifier extern. 45 Use of storage class specifier register ......................... 45 Use of storage class specifier static .. 46 Use of storage class specifier typedef ......................... 46 Modifiers ......................... 47 The const modifier ............... 47 The interrupt function modifier .... 49 The volatile modifier ............. 49 The cdecl and pascal modifiers . . . .. 50 pascal ........................ 50 cdecl ......................... 50 The pointer modifiers ............. 51 Function type modifiers . . . . . . . . . .. 52 Complex declarations and declarators .53 Pointers ............................ 54 Pointers to objects . . . . . . . . . . . . . . . . .. 55 Pointers to functions ............... 55 Pointer declarations ................ 56 Pointers and constants . . . . . . . . . . . . .. 57 Pointer arithmetic . . . . . . . . . . . . . . . . .. 58 Pointer conversions ................ 59 C++ reference declarations .......... 59 Arrays .............................. 59 Functions ........................... 60 Declarations and definitions ......... Declarations and prototypes . . . . . . . .. Definitions ........................ Formal parameter declarations. . . . . .. Function calls and argument conver~ons ....................... Structures . . . . . . . . . . . . . . . . . . . . . . . . . .. Untagged structures and typedefs .... Structure member declarations ...... Structures and functions ............ Structure member access . . . . . . . . . . .. Structure word alignment . . . . . . . . . .. Structure name spaces .............. Incomplete declarations ............. Bit fields . . . . . . . . . . . . . . . . . . . . . . . . .. Unions ............................. Anonymous unions (C++ only) ...... Union declarations ................. Enumerations ....................... Expressions ......................... Expressions and C++ ............... Evaluation order ................... Errors and overflows ............... Operator semantics .................. Operator descriptions ................ Unary operators ............. ,..... Binaryoperators ................... Additive operators . . . . . . . . . . . . . .. Multiplicative operators .......... Shift opera tors . . . . . . . . . . . . . . . . . .. Bitwise operators ................ Logical operators ................ Assignment operators ............ Relational operators .............. Equality operators ............... Component selection operators .... Class-member operators .......... Conditional operator ............. Comma operator ................. Postfix and prefix operators ......... Array subscript operator [ ] ........ Function call operators () ......... Structure/union member operator . (dot) .......................... 60 61 63 64 64 65 66 66 67 67 69 69 70 70 71 72 73 73 75 78 78 79 79 79 81 81 81 81 81 81 81 81 82 82 82 82 82 82 82 82 83 83 Structure/union pointer operator -> ..................... Postfix increment operator ++ ..... Postfix decrement operator - - ..... Increment and decrement operators .. Prefix increment operator ......... Prefix decrement operator ..... -... , Unary operators ................... Address operator & .............. Indirection operator * ............ , Unary plus operator + ............ Unary minus operator - .......... , Bitwise complement operator ~ .... Logical negation operator! ........ The sizeof operator. . . . . . . . . . . . . . . .. Multiplicative operators ............ Additive operators ................. The addition operator + ........... The subtraction operator - ........ Bitwise shift operators ....... . . . . . .. Bitwise shift operators «< and ») . Relational operators ................ The less-than operator < .......... The greater-than operator> ....... The less-than or equal-to operator <= ... ~ ......................... The greater-than or equal-to operator >= ..................... Equalityoperators ................. The equal-to operator == .......... The inequality operator != ......... Bitwise AND operator & ............ Bitwise exclusive OR operator /\ ..... Bitwise inclusive OR operator I ...... Logical AND operator && .......... Logical OR operator I I ............. Conditional operator? : . . . . . . . . . . . .. Assignment operators .............. The simple assignment operator The compound assignment operators ....................... Comma operator ......... , .. , ..... , C++ operators ..................... Statements .......................... = .. Blocks 98 Labeled statements ................ . 98 Expression statements ............. . 99 Selection statements ............... . 99 if statements ................... . 99 switch statements .. , ........... . 100 Iteration statements .............. . 101 while statements ............... . 101 do while statements ............ . 101 for statements ................. . 102 Jump statements ................. . 103 break statements ............... . 103 continue statements ............ . 103 goto statements ................ . 103 return statements .............. . 104 83 84 84 84 84 84 85 85 86 86 86 86 86 87 87 88 88 89 89 89 90 90 91 Chapter 3 C++ specifics Referencing ........................ Simple references ................. Reference arguments .............. Scope access operator ............... , The new and delete operators ........ Handling errors .............,..... The operator new with arrays ...... The operator delete with arrays .. , .. The ::operator new ................ Initializers with the new operator ... Classes ............................ Class names . . . . . . . . . . . . . . . . . . . . .. Class types . . . . . . . . . . . . . . . . . . . . . .. Class name scope ................. Class objects ..................... Class member list. . . . . . . . . . . . . . . .. Member functions ................ The keyword this ................. Inline functions . . . . . . . . . . . . . . . . . .. Static members ................... Member scope . . . . . . . . . . . . . . . . . . .. Nested types ................... Member access control. . . . . . . . . .. Base and derived class access ....... Virtual base classes .................. Friends of classes ................... Constructors and destructors ......... 91 91 91 91 92 92 93 93 93 94 94 95 95 96 96 97 97 iii 105 105 106 106 108 108 109 109 109 110 110 111 111 111 112 113 113 113 113 114 115 116 117 118 120 122 122 124 Constructors ....................... Constructor defaults ............... The copy constructor ............. , Overloading constructors .......... Order of calling constructors ....... Class initialization ................ Destructors ........................ When destructors are invoked ...... atexit, #pragma exit, and destructors. exit and destructors ............... abort and destructors .............. Virtual destructors ................ Overloaded operators ............... Operator functions .......... . . . . . . .. Overloaded operators and inheritance ....................... Overloading new and delete. . . . . . .. Overloading unary operators ....... Overloading binary operators ...... Overloading the assignment operator = ....................... Overloading the function call operator 0 ....................... Overloading the subscript operator .. Overloading the class member access operator .......................... Virtual functions . . . . . . . . . . . . . . . . . . .. Abstract classes . . . . . . . . . . . . . . . . . . . .. C++ scope ......................... Class scope . . . . . . . . . . . . . . . . . . . . . .. Hiding .......................... C++ scoping rules summary . . . . . . .. Templates .......................... Function templates . . . . . . . . . . . . . . .. Overriding a template function .. , Implicit and explicit template functions ...................... Class templates ................... Arguments ..................... Angle brackets .................. Type-safe generic lists ........... Eliminating pointers ............. Template compiler switches ........ Using template switches ......... Chapter 4 The preprocessor 157 Null directive # . . . . . . . . . . . . . . . . . . . .. 159 The #define and #Undef directives . . . .. 159 Simple #define macros ............. 159 The #Undef directive .............. 160 The -D and -U options ............ 161 The Define option . . . . . . . . . . . . . . . .. 161 Keywords and protected words . . . .. 162 Macros with parameters ........... 162 File inclusion with #include .......... 165 Header file search with.................. 166 Header file search with "header_name" ................... 166 Conditional compilation ............. 166 The #if, #elif, #else, and #endif conditional directives ........................ 167 The operator defined ............ 167 The #ifdef and #ifndef conditional directives ........................ 168 The #line line control directive ........ 169 The #error directive ................. 170 The #pragma directive . . . . . . . . . . . . . .. 171 #pragma argsused ................ 171 #pragma exit and #pragma startup .. 171 #pragma hdrfile .................. 172 #pragma hdrstop ................. 173 #pragma inline ................... 173 #pragma intrinsic ................. 173 #pragma option . . . . . . . . . . . . . . . . . .. 173 #pragma saveregs ................. 175 #pragma warn . . . . . . . . . . . . . . . . . . .. 175 Predefined macros .................. 175 __BCPLUSPLUS__ ............... 175 __BORLANDC__ ................ 176 __CDECL__ ..................... 176 __cplusplus . . . . . . . . . . . . . . . . . . . . .. 176 __DATE__ ...................... 177 __DLL__ ........................ 177 __FILE__ ....................... 177 __LINE ....................... 177 __MSDOS __ ..................... 177 __OVERLAY__ .................. 178 __PASCAL__ .................... 178 125 126 127 127 128 129 132 132 133 133 133 134 135 136 136 137 138 139 139 140 140 140 140 142 143 144 144 144 145 146 148 148 149 150 150 151 152 152 153 iv __STDC_ _ 000000 0000000000000000 __TCPLUSPLUS__ 000000000000000 __TEMPLATES__ 00000000·00000000 __TIME__ 00000000000000000000000 __TURBOC 0000000000000000000 _Windows 00000000000000000000000 178 178 178 178 179 179 Chapter 5 Using C++ streams What is a stream? 0000000000000000000 The iostream library 00000000000000000 The streambuf class 000000000000000 The ios class 0000000000000000000000 Output 0000000000000000000000000000 Fundamental types 0000000000000000 Output formatting 0000000000000000 Manipulators 000000000000000000000 Filling and padding 000000000000000 Input 000000000000000000000000000000 I/O of user-defined types 000000000000 Simple file I/O 000000000000000000000 String stream processing 0000000000000 Screen output streams 0000.0000000000 Stream class reference 000000000000000 conbuf 00. 00000000000000000000000000 Member functions 00000000000000000 constream 00000000000000000000000000 Member functions 00000000000000000 filebuf 00 000000000000000000000000000 Member functions 00000000000000000 fstream 0000000000000000 000000000000 Member functions 00000000000000000 fstreambase 000000000000000000000000 Member functions 00000000000000000 ifstream 000000000000000000000000000 Member functions 00000000000000000 ios 000000000000000000000000000000 00 Data members 00000000000000000000 Member functions 00000000000000000 iostreamo 00000000000000000000000000 iostream_withassign 0000000000000000 Member functions 00000000000000000 istream 0000000000000000000000000000 Member functions 00000000000000000 istream_withassign 000000000000000000 181 181 182 182 182 183 184 184 185 187 187 188 189 190 192 193 194 194 195 195 196 196 197 197 197 198 198 199 199 199 200 202 202 202 202 203 204 v Member functions 0000000000000000 istrstream 00000 0000000000 0000000 0000 ofstream 000000000000: 00000000000000 Member functions 00. 0000000000000 ostream 00000000000000. 0000000000000 Member functions 00.. 0.... 0. . . . .. ostream_withassign .0 .. 00 .... '0, ... 0 Member functions ................ ostrstream ...... 0............ 00... 0 Member functions ............. 0" streambuf 0... 00... 00.. 0...... 0... o. Member functions ....... 00... 0.. 0 strstreambase 000 0. 000. 000. 000. 00000 Member functions 00. 0. 0. 000000. 00 strstreambuf 000000000. 000. 0. 0000. 00 Member functions 00. 0... 0.. 000. 00 strstream 000000 .. 000.. 00.. 000000.. 0 Member function 000. 000000. 000000 204 204 205 205 205 206 206 206 206 207 207 207 210 210 210 211 211 212 Chapter 6 The container class libraries What's new since version 200? 000000000 Why two sets of libraries? 000000000000 Container basics 000 000. 0000000. 00000 Object-based and other classes 0000 Class categories 00000' 0000000000000 Non-container classes 0000000000000 Error class 000000000000. 00000. 00 Sortable class 000000 000000. 000000 Associa tion class 00. 0000000000000 Container classes 00 o. 0000000000000 Containers and ownership 00.000000 Container iterators 000000. 000. 00000 Sequence classes 00000000. 000. 0.. 00 Collections 000. 0000000000. 00000000 Unordered collections 0000.000.00 Ordered collections 0000. 00000000 The BIDS template library 000000000000 Templates, classes, and containers. 00 Container implementation 000000.. 0 The template solution 000000000·0000 ADTs and FDSs .. 000000000000000 Class templates 000000.0000000.00 Container class compatibility 0000. 00 213 214 215 216 218 218 218 218 219 219 219 220 222 223 223 224 224 224 225 225 226 226 227 229 Header files ...................... Tuning an application ............. FDS implementation .............. ADT implementation . . . . . . . . . . . . .. The class library directory . . . . . . . . . . .. The INCLUDE directory ....... , ... The SOURCE directory ............ The LIB directory ................. The EXAMPLES directory . . . . . . . . .. Preconditions and checks ............ Container class reference ........... " AbstractArray ...................... Data members . . . . . . . . . . . . . . . . . . .. Member functions ............... " Friends .......................... Array ............................. Example ......................... Member functions. . . . . . . . . . . . . . . .. Arraylterator . . . . . . . . . . . . . . . . . . . . . .. Member functions. . . . . . . . . . . . . . . .. Association ........................ Member functions. . . . . . . . . . . . . . . .. Example . . . . . . . . . . . . . . . . . . . . . . . .. Bag ............................... Member functions. . . . . . . . . . . . . . . .. BaseDate . . . . . . . . . . . . . . . . . . . . . . . . . .. Member functions. . . . . . . . . . . . . . . .. BaseTime .......................... Member functions . . . . . . . . . . . . . . . .. Btree .............................. Member functions. . . . . . . . . . . . . . . .. Friends .......................... BtreeIterator ........................ Member functions. . . . . . . . . . . . . . . .. Collection ........................ " Member functions. . . . . . . . . . . . . . . .. Container ... . . . . . . . . . . . . . . . . . . . . . .. Member functions . . . . . . . . . . . . . . . .. Friends .......................... ContainerIterator ................... Member functions. . . . . . . . . . . . . . . .. Date ............................... Member functions . . . . . . . . . . . . . . . .. Deque ............................. 230 231 231 235 238 238 239 239 240 240 241 242 242 243 245 245 245 246 247 247 248 248 249 250 250 252 252 253 253 255 255 257 257 257 258 259 259 261 263 263 263 264 264 265 Example . . . . . . . . . . . . . . . . . . . . . . . .. Member functions ................ Dictionary ......................... Member functions ................ DoubleList . . . . . . . . . . . . . . . . . . . . . . . .. Member functions ................ Friends ........................ " DoubleListIterator .................. Member functions ................ Error .............................. Member functions ................ HashTable ......................... Member functions ................ Friends .......................... HashTableIterator ................. " Member functions ................ List .......... '.' ................... Member functions ................ Friends .......................... ListIterator . . . . . . . . . . . . . . . . . . . . . . . .. Member functions ................ MemBlocks ........................ MemStack ......................... Object ........... '" .... , .. , ....... Data member. . . . . . . . . . . . . . . . . . . .. Member functions ................ Friends .......................... Related functions ................. PriorityQueue .................... " Member functions ................ Queue ............................. Exam pIe . . . . . . . . . . . . . . . . . . . . . . . .. Member functions ................ Set ......................... " . " .. Member functions ................ Sortable ........................... Member functions ................ Related functions ................. SortedArray ...................... " Stack .. " .... , ..................... Example . . . . . . . . . . . . . . . . . . . . . . . .. Member functions ................ String ............................. Member functions ................ vi 265 266 267 268 268 268 270 270 270 271 271 272 273 274 274 274 275 275 276 276 276 277 278 279 279 279 281 282 282 283 284 284 285 285 286 286 288 288 289 289 290 291 292 292 Example . . . . . . . . . . . . . . . . . . . . . . . .. Time .............................. Member functions . . . . . . . . . . . . . . . .. Timer ............................. Member functions. . . . . . . . . . . . . . . .. TShouldDelete . . . . . . . . . . . . . . . . . . . . .. Member functions. . . . . . . . . . . . . . . .. Windows Explicit Functions Exported (-WE) ........................... 323 Windows Smart Callbacks (-WS) .... 323 Windows DLL All Functions Exportable (-WD) ........................... 324 Windows DLL Explicit Functions Exported (-WDE) ................. 324 The _export keyword ............. , 324 Prologs, epilogs, and exports: a summary ........................ 325 Memory models .................... 326 Module definition files ............... 326 A quick example . . . . . . . . . . . . . . . . .. 327 Linking for Windows . . . . . . . . . . . . . . .. 328 Linking in the IDE ................ 329 Linking with TLINK .............. 329 Linker options . . . . . . . . . . . . . . . . .. 329 Linking .OBJ and .LIB files ....... 330 Linking .OBJ and .LIB files for DLLs .......................... 331 Dynamic link libraries ............... 332 Compiling and linking a DLL within the IDE ............................. 332 Compiling and linking a DLL from the command line ............... . . . .. 332 Module definition files ........... 333 Import libraries ................. 333 Creating DLLs .................... 333 LibMain and WEP .............. 334 Pointers and memory. . . . . . . . . . .. 335 Static data in DLLs ............ 336 C++ classes and pointers ......... 336 293 294 294 295 295 296 296 Chapter 7 Converting from Microsoft C 299 Environment and tools .............. 299 Paths for .h and .LIB files ........... 300 MAKE ........................... 301 Command-line compiler ........... 302 Command-line options and libraries. 306 Linker ................... '" ..... 306 Source-level compatibility ........... , 308 __MSC macro .................... 308 Header files ...................... 308 Memory models .................. 309 Keywords. . . . . . . . . . . . . . . . . . . . . . .. 310 Floating-point return values ....... , 310 Structures returned by value ....... 310 Conversion hints. . . . . . . . . . . . . . . . . . .. 311 Chapter 8 Building a Windows application Compiling and linking within the IDE Understanding resource files ....... Understanding module definition files ............................. Compiling and linking WHELLO ... Using the project manager ....... Setting compile and link options .. WinMain .......................... Compiling and linking from the command line ...................... Compiling from the command line .. Linking from the command line . . . .. Using a makefile .................. Another makefile for Windows ... Prologs and epilogs ................. Windows All Functions Exportable (-W) ............................. 313 314 315 315 315 316 317 318 Chapter 9 DOS memory management Running out of memory ............. Memory models .................... The 8086 registers . . . . . . . . . . . . . . . .. General-purpose registers ........ Segment registers ............... Special-purpose registers ......... The flags register . . . . . . . . . . . . . . .. Memory segmentation ............. Address calculation ............. 318 319 320 321 322 322 323 vii 339 339 339 340 340 341 341 341 342 343 Pointers ......................... 344 Near pointers ................... 344 Far pointers .................... 344 Huge pointers ................... 345 The six memory models. . . . . . . . . . .. 346 Mixed-model programming: Addressing modifiers .......................... 350 Segment pointers ................. 351 Declaring far objects ............... 352 Declaring functions to be near or far . 352 Declaring pointers to be near, far, or huge ............................ 353 Pointing to a given segment:offset address ........................ 355 Using library files ................. 355 Linking mixed modules . . . . . . . . . . .. 355 Overlays (VROOMM) for DOS. . . . . . .. 357 How overlays work ............... 357 Getting the best out of Borland C++ overlays ....................... 359 Requirements .................... 359 Using overlays ................... 360 Overlay example ................ 360 Overlaying in the IDE ........... 361 Overlaid programs . . . . . . . . . . . . . . .. 361 The far call requirement. . . . . . . . .. 361 Buffer size ..................... 362 What not to overlay ............. 362 Debugging overlays . . . . . . . . . . . .. 362 External routines in overlays ..... 363 Swapping . . . . . . . . . . . . . . . . . . . . . . .. 364 Expanded memory . . . . . . . . . . . . .. 364 Extended memory .............. 364 Chapter 10 Math Floating-point options ............... Emulating the 80x87 chip .......... Using 80x87 code ................. No floating-point code ............. Fast floating-point option ....... "... The 87 environment variable ....... Registers and the 80x87 ............ Disabling floating-point exceptions .. 367 367 368 368 368 368 369 370 370 viii Using complex math ................ Using BCD math .................. Converting BCD numbers . . . . . . .. Number of decimal digits ........ 371 372 373 373 Chapter 11 Video functions Some words about video modes ...... Some words about windows and viewports .......................... What is a window? ................ What is a viewport? ............... Coordinates ...................... Programming in text mode . . . . . . . . . .. The console I/O functions .......... Text output and manipulation .... Window and mode control .. . . . .. Attribute control . . . . . . . . . . . . . . .. State query ..................... Cursor shape ................... Text windows .................... An example .................... The text_modes type ............... Textcolors ....................... High-performance output .......... Programming in graphics mode. . . . . .. The graphics library functions ...... Graphics system control ......... A more detailed discussion . . . . . .. Drawing and filling ............. Manipulating the screen and viewport . . . . . . . . . . . . . . . . . . . . . .. Text output in graphics mode ..... Color control . . . . . . . . . . . . . . . . . .. Pixels and ralettes .............. Background and drawing color ... Color control on a CGA . . . . . . . . .. CGA low resolution ........... CGA high resolution .......... CGA palette routines .......... Color control on the EGA and VGA .......................... Error handling in graphics mode .. State query ..................... 375 375 376 376 377 377 377 377 377 379 379 380 380 380 381 381 382 383 384 385 385 387 387 389 390 392 392 393 393 393 394 395 395 395 396 Chapter 12 BASM and inline assembly 399 Inline assembly language .......... 399 BASM ......................... 400 Inline syntax ................... 400 Opcodes ....................... 402 String instructions ............ 403 Prefixes . . . . . . . . . . . . . . . . . . . . .. 403 Jump instructions ............. 403 Assembly directives ........... 404 Inline assembly references to data and functions ...................... 404 Inline assembly and register variables . . . . . . . . . . . . . . . . . . . .. 404 Inline assembly, offsets, and size overrides .................... 404 Using C structure members ...... 405 Using jump instructions and labels. 406 Interrupt functions . . . . . . . . . . . . . . .. 406 Using low-Iev,el practices .......... 408 ix Appendix A ANSI implementationspecific standards 411 Index 423 T A B L E s 2.13: Borland C++ statements ........... 98 4.1: Borland C++ preprocessing directives syntax ........................... 158 5.1: Stream manipulators .............. 186 5.2: File modes ....................... 190 5.3: Console stream manipulators ...... 192 6.1: ADTs as fundamental data structures ........................ 226 6.2: FDS class templates ............... 227 6.3: Abbreviations in CLASSLIB names .228 6.4: ADT class templates .............. 228 6.5: Object-based FDS classes .......... 229 6.6: Class debugging modes ........... 241 7.1: CL and BCC options compared ..... 302 7.2: LINK and TLINK options compared ....................... 307 8.1: Compiler options and the _export keyword ........................ 325 8.2: Startup and library files for DLLs ... 331 9.1: Memory models .................. 349 9.2: Pointer results ................... 351 11.1: Graphics mode state query functions ....................... 397 12.1: Opcode mnemonics .............. 402 12.2: String instructions ............... 403 12.3: Jump instructions ............... 404 A.1: Identifying diagnostics in C++ ..... 411 1.1: All Borland C++ keywords ........... 9 1.2: Borland C++ extensions to C ......... 9 1.3: Keywords specific to C++ ........... 9 1.4: Borland C++ register pseudovariables ................... 10 1.5: Constants-formal definitions ....... 12 1.6: Borland C++ integer constants without Lor U ............................ 13 1.7: Borland C++ escape sequences ...... 15 1.8: Borland C++ floating constant sizes and ranges ........................... 17 1.9: Data types, sizes, and ranges ........ 19 2.1: Borland C++ declaration syntax ..... 35 2.2: Borland C++ declarator syntax ...... 36 2.3: Borland C++ class declarations (C++ only) ............................. 37 2.4: Declaring types ................... 39 2.5: Integral types ..................... 40 2.6: Methods used in standard arithmetic conversions; ...................... 42 2.7: Borland C++ modifiers ............. 47 2.8: Complex declarations .............. 54 2.9: External function definitions ........ 63 2.10: Associativity and precedence of Borland C++ operators ............ 76 2.11: Borland C++ expressions .......... 77 2.12: Bitwise operators truth table ....... 93 x F G u R E s 9.3: Tiny model memory segmentation .. 347 9.4: Small model memory segmentation .347 9.5: Medium model memory segmentation .................... 348 9.6: Compact model memory segmentation .................... 348 9.7: Large model memory segmentation .348 9.8: Huge model memory segmentation .349 9.9: Memory maps for overlays ........ 359 11.1: A window in 80x25 text mode ..... 381 1.1: Internal representations of data types .20 5.1: Class streambuf and its derived classes .......................... 182 5.2: Class ios and its derived classes .. , .183 6.1: Class hierarchies in CLASSLIB ..... 217 6.2: TShouldDelete hierarchy .......... 236 6.3: Class hierarchies in CLASSLIB ..... 296 8.1: Compiling and linking a Windows program .............. 314 9.1: 8086 registers .................... 340 9.2: Flags register of the 8086 .......... 342 xi N T o R To get an overview of the Borland C++ documentation set, start with the User's Guide. Read the introduction and Chapter 7 in that book for information on how to most effectively use the Borland C++ manuals. D u c T o N This manual contains materials for the advanced programmer. If you already know how to program well (whether in C, C++, or another language), this manual is for you. It provides a language reference, and programming information on C++ streams, object container classes, converting from Microsoft C, Windows applications, memory models, floating point, overlays, video functions, BASM, inline assembly, and ANSI implementation. Code examples have a main function. EasyWin makes all these examples work in Windows so you don't need Win Main and its complicated parameters. Typefaces and icons used in these books are described in the User's Guide. What's in this book Chapters 1 through 4: Lexical elements, Language structure, C++ specifics, and The preprocessor, describe the Borland C++ language. Any extensions to the ANSI C standard are noted in these chapters. These chapters provide a formal language definition, reference, and syntax for both the C and C++ aspects of Borland C++. Some overall information for Chapters 1 through 4 is included in the next section of this introduction. Chapter 5: Using C++ streams tells you how to use the C++ version 2.1 stream library. Chapter 6: The container class library tells you how to use the Borland C++ object container classes (including templates) in your programs. Introduction Chapter 7: Converting from Microsoft C provides some guidelines on converting your Microsoft C programs to Borland C++. Chapter 8: Building a Windows application gets you started in Windows programming. Chapter 9: DOS memory management covers memory models, overlays, and mixed-model programming. Chapter 10: Math covers floating point and BCD math. Chapter 11: Video functions is devoted to handling text and graphics in Borland C++. Chapter 12: BASM and inline assembly tells how to write assembly language programs so they work well when called from Borland C++ programs. It includes information on the built-in assembler in the IDE. Appendix A: ANSI implementation-specific standards describes those aspects of the ANSI C standard that have been left loosely defined or undefined by ANSI. This appendix tells how Borland C++ operates in respect to each of these aspects. An introduction to the formal definitions Chapters 1 through 4 constitute a formal description of the C and C++ languages as implemented in Borland c++. Together, these chapters describe the Borland C++ language; they provide a formal language definition, reference, and syntax for both the C++ and C aspects of Borland C++. These chapters do not provide a language tutorial. We've used a modified Backus-Naur form notation to indicate syntax, supplemented where necessary by brief explanations and program examples. They are organized in this manner: • Chapter 1, "Lexical elements," shows how the lexical tokens for Borland C++ are categorized. Lexical elements is concerned with the different categories of word-like units, known as tokens, recognized by a language. II Chapter 2, "Language structure," explains how to use the elements of Borland C++. Language structure details the legal ways in which tokens can be grouped together to form expressions, statements, and other significant units. 2 Borland C++ Programmer's Guide Chapter 3, "C++ specifics," covers those aspects specific to C++ . • Chapter 4, "The preprocessor," covers the preprocessor, including macros, includes, and pragmas, as well as many other easy yet useful items. II Borland C++ is a full implementation of AT&T's C++ version 2.1, the object-oriented superset of C developed by Bjarne Stroustrup of AT&T Bell Laboratories. This manual refers to AT&T's previous version as C++ 2.0. In addition to offering many new features and capabilities, C++ often veers from C by small or large amounts. We've made note of these differences throughout these chapters. All the Borland C++ language features derived from C++ are discussed in greater detail in Chapter 3. Borland C++ also fully implements the ANSI C standard, with several extensions as indicated in the text. You can set options in the compiler to warn you if any such extensions are encountered. You can also set the compiler to treat the Borland C++ extension keywords as normal identifiers (see Chapter 5, "The commandline compiler," in the User's Guide). There are also "conforming" extensions provided via the #pragma directives offered by ANSI C for handling nonstandard, implementation-dependent features. Syntax and terminology Syntactic definitions consist of the name of the nonterminal token or symbol being defined, followed by a colon (:). Alternatives usually follow on separate lines, but a single line of alternatives can be used if prefixed by the phrase "one of." For example, external-definition: function-definition declaration octal-digit: one of 01234567 Optional elements in a construct are printed within angle brackets: integer-suffix: unsigned-suffix Throughout these chapters, the word "argument" is used to mean the actual value passed in a call to a function. "Parameter" is used Introduction 3 to mean the variable defined in the function header to hold the value. 4 Borland C++ Programmer's Guide c H A p T R E 1 Lexical elements This chapter provides a formal definition of the Borland C++ lexical elements. It is concerned with the different categories of word-like units, known as tokens, recognized by a language. By contrast, language structure (covered in Chapter 2) details the legal ways in which tokens can be grouped together to form expressions, statements, and other significant units. The tokens in Borland C++ are derived from a series of operations performed on your programs by the compiler and its built-in preprocessor. A Borland C++ program starts life as a sequence of ASCII characters representing the source code, created by keystrokes using a suitable text editor (such as the Borland C++ editor). The basic program unit in Borland C++ is the file. This usually corresponds to a named DOS file located in RAM or on disk and having the extension .C or .CPP. The preprocessor first scans the program text for special preprocessor directives (see page 157). For example, the directive #include adds (or includes) the contents of the file inc_file to the program before the compilation phase. The preprocessor also expands any macros found in the program and include files. Chapter 7, Lexical elements 5 Whitespace In the tokenizing phase of compilation, the source code file is parsed (that is, broken down) into tokens and whitespace. Whitespace is the collective name given to spaces (blanks), horizontal and vertical tabs, newline characters, and comments. Whitespace can serve to indicate where tokens start and end, but beyond this function, any surplus whitespace is discarded. For example, the two sequences int i; float f; and int i float f; are lexically equivalent and parse identically to give the six tokens: 1. int 2. 3. 4. float 5. f 6. ; The ASCII characters representing whitespace can occur within literal strings, in which case they are protected from the normal parsing process; in other words, they remain as part of the string: char narne[] = "Borland International"; parses to seven tokens, including the single literal-string token "Borland International". Line splicing with \ A special case occurs if the final newline character encountered is preceded by a backslash (\). The backslash and new line are both discarded, allowing two physical lines of text to be treated as one unit. "Borland \ International" 6 Borland C++ Programmer's Guide is parsed as "Borland International" (see page 18, "String literals," for more information). Comments Comments are pieces of text used to annotate a program. Comments are for the programmer's use only; they are stripped from the source text before parsing. There are two ways to delineate comments: the Cmethod and the C++ method. Both are supported by Borland C++, with an additional, optional extension permitting nested comments. You can mix and match either kind of comment in both C and C++ programs. C comments See page 163 for a description of token pasting. A C comment is any sequence of characters placed after the symbol pair 1*. The comment terminates at the first occurrence of the pair */ following the initial/*. The entire sequence, including the four comment delimiter symbols, is replaced by one space after macro expansion. Note that some C implementations remove comments without space replacements. Borland C++ does not support the nonportable token pasting strategy using 1**/. Token pasting in Borland C++ is performed with the ANSI-specified pair ##, as follows: #define VAR(i,j) #define VAR(i,j) #define VAR (i, j ) (i/**/j) (i##j) (i ## j) 1* won't work *1 1* OK in Borland 1* Also OK *1 ett *1 In Borland C++, int 1* declaration *1 1* counter *1; parses as int to give the three tokens: int i ; Nested comments ANSI C doesn't allow nested comments. Attempting to comment out the preceding line with 1* int 1* declaration *1 i 1* counter *1; *1 fails, since the scope of the first 1* ends at the first */. This gives i ;' * I which would generate a syntax error. Chapter 7, Lexical elements 7 By default, Borland C++ won't allow nested comments, but you can override this with compiler options. You can enable nested comments via the Source Options dialog box (0 I C I Source) in the IDE or with the -C option (for the command-line compiler). c++ comments You can also use / / to create comments in C code. This is specific to Borland C++. C++ allows a single-line comment using two adjacent slashes U I). The comment can start in any position, and extends until the next new line: class X { II this is a comment ... }i Comment delimiters and whitespace In rare cases, some whitespace before /* and II, and after */, although not syntactically mandatory, can avoid portability problems. For example, this C++ code int i = jll* divide by k*/ki tIDi parses as int i = j int i tffi; not as = j/ki tIDi as expected under the C convention. The more legible int i = jl 1* divide by k*1 ki tIDi avoids this problem. Tokens Borland C++ recognizes six classes of tokens. The formal definition of a token is as follows: token: keyword identifier constant string-literal operator punctuator Punctuators are also known as separators. 8 Borland C++ Programmer's Guide As the source code is parsed, tokens are extracted in such a way that the longest possible token from the character sequence is selected. For example, external would be parsed as a single identifier, rather than as the keyword extern followed by the identifier al. Keywords Keywords are words reserved for special purposes and must not be used as normal identifier names. The following two tables list the Borland C++ keywords. You can use options in the IDE (or command-line compiler options) to select ANSI keywords only, UNIX keywords, and so on; see Chapter 2, "IDE Basics" and Chapter 5, "The command-line compiler," in the User's Guide, for information on these options. _asm asm auto break case _cdecl cdecl char class const continue _cs default delete do double _ds else enum _es _export extern _far far _fastcall float for friend goto _huge huge if inline Table 1.2 Borland C++ extensions to C _cdecl cdecl _cs _ds _es _export _far far _fastcall Table 1.3 Keywords specific to C++ asm class delete friend inline new Table 1.1 All Borland C++ keywords Chapter 7, Lexical elements int _interrupt interrupt _Ioadds long near near new operator _pascal pascal private protected public register return _saveregs huge interrupt _Ioadds near near _seg short signed sizeof _ss static struct switch template this typedef union unsigned virtual void volatile while _pascal pascal _saveregs _seg _ss operator private protected public template this virtual 9 Table 1,4 Borland C++ register pseudovariables AH AL - AX BH BL BP BX CH - CL - CS - - - CX OH 01 OL OS OX ES FLAGS - SI - SP - SS Identifiers The formal definition of an identifier is as follows: identifier: l10ndigit identifier l10ndigit identifier digit nondigit: one of abcdefghijklmnopqrstuvwxyz_ ABC 0 E FG H IJ K L M N 0 P Q RS T U V W X Y Z digit: one of o1 Naming and length restrictions 2 3 4 5 6 789 Identifiers are arbitrary names of any length given to classes, objects, functions, variables, user-defined data types, and so on. Identifiers can contain the letters A to Z and a to 2, the underscore character <-), and the digits 0 to 9. There are only two restrictions: 1. The first character Inust be a letter or an underscore. Identifiers in C++ programs are significant to any lenfjth, Identifiers and case sensitivity 2. By default, Borland C++ recognizes only the first 32 characters as significant. The number of significant characters can be reduced by Inenu and comlnand-line options, but not increased. Use the -in command-line option (where 1 <= n <= 32) or Identifier Length in the Source Options dialog box (0 I C I Source). Borland c++ identifiers are case sensitive, so that Sum, sum, and sliM are distinct identifiers. Global identifiers imported from other nlodules follow the same naming and significance rules as normal identifiers. However, Borland C++ offers the option of suspending case sensitivity to allow conlpatibility when linking with case-insensitive languages. By checking Case-sensitive Link in the Linker dialog box 10 Borland C++ Programmer's Guide (Options I Linker I Settings), or using the Ie cOll1111and-line switch with TLINK, you can ensure that global identifiers are case insensitive. Under this regime, the globals Slim and sum are considered identical, resulting in a possible "Duplicate symbol" warning during linking. An exception to these rules is that identifiers of type pascal are always converted to all uppercase for linking purposes. Uniqueness and scope Although identifier names are arbit:cary (within the rules stated), errors result if the same name is used for more than one identifier within the same scope and sharing the same name space. Duplicate names are always legal for different name spaces regardless of scope. The rules are covered in the discussion on scope starting on page 27. Constants Constants are tokens representing fixed numeric or character values. Borland C++ supports four classes of constants: floating point, integer, enumeration, and character. The data type of a constant is deduced by the cOlnpiler using such clues as numeric value and the format used in the source code. The formal definition of a constant is shown in Table 1.5. Integer constants Integer constants can be decimal (base 10), octal (base 8) or hexadecimal (base 16). In the absence of any overriding suffixes, the data type of an integer constant is derived from its value, as shown in Table 1.6. Note that the rules vary between decilnal and nondecimal constants. Decimal constants Decimal constants from 0 to 4,294,967,295 are allowed. Constants exceeding this limit will be truncated. Decin1al constants Inust not use an initial zero. An integer constant that has an initial zero is interpreted as an octal constant. Thus, int i = 10 ; /*decimal 10 */ int i = 010; /*decimal 8 */ /*decimal 0 = octal 0 */ int i = 0; Chapter 7, Lexical elements 11 Table 1.5: Constants-formal definitions oX hexadecimal-digit hexadecimal-constant hexadecimal-digit constant: floating-constant integer-constant enumeration-constant character-constant nonzero-digit: one of 1 234 5 6 789 floating-constant: fractional-constant digit-sequence exponent-part fractional-constant: . digit-sequence digit-sequence . exponent-part: e digit-sequence E digit-sequence octal-digit: one of o1 234 5 6 7 hexadecimal-digit: one of o 1 234 5 6 789 abcdef ABCDEF integer-suffix: unsigned-suffix long-suffix unsigned-suffix: one of uU sign: one of + - long-suffix: one of 1L digit-sequence: digit digit-sequence digit enumeration-constant: identifier floating-SUffix: one of f I F L character-constant: c-char-sequence integer-constant: decimal-constant octal-constant hexadecimal-constant c-char-sequence: c-char c-char-sequence c-char decimal-constant: nonzero-digit decimal-constant digit c-char: Any character in the source character set except the single-quote ('), backslash (\), or newline character escape-sequence. octal-constant: escape-sequence: one of o octal-constant octal-digit hexadecimal-constant: ox hexadecimal-digit \" \a \' \b \? \f \0 \00 \000 \\ \n \r \t \v \Xh ... \xh ... Octal constants All constants with an initial zero are taken to be octal. If an octal constant contains the illegal digits 8 or 9, an error is reported. Octal constants exceeding 037777777777 will be truncated. 12 Borland c++ Programmer's Guide Hexadecimal constants All constants starting with Ox (or OX) are taken to be hexadecimal. Hexadecimal constants exceeding OxFFFFFFFF will be truncated. Long and unsigned suffixes The suffix L (or 1) attached to any constant forces it to be represented as a long. Similarly, the suffix U (or u) forces the constant to be unsigned. It is unsigned long if the value of the number itself is greater than decimal 65,535, regardless of which base is used. You can use both Land U suffixes on the same constant in any order or case: uI, Iu, UL, and so on. Table 1.6 Borland C++ integer constants without L or U Decimal constants oto 32,767 32,768 to 2,147,483,647 2,147,483,648 to 4,294,967,295 > 4294967295 int long unsigned long truncated Octal constants 00 0100000 02000000 020000000000 to 077777 to 0177777 to 017777777777 to 037777777777 > 037777777777 int unsigned int long unsigned long truncated Hexadecimal constants OxOOOO Ox8000 Oxl0000 Ox80000000 to Ox7FFF to OxFFFF to Ox7FFFFFFF to OxFFFFFFFF int unsigned int long unsigned long > OxFFFFFFFF truncated The data type of a constant in the absence of any suffix (U, U, L, or 1) is the first of the following types that can accommodate its value: decimal int, long int, unsigned long int octal int, unsigned int, long int, unsigned long int hexadecimal int, unsigned int, long int, unsigned long int If the constant has a U or u suffix, its data type will be the first of unsigned int, unsigned long int that can accommodate its value. Chapter 7, Lexical elements 13 If the constant has an L or I suffix, its data type will be the first of long int, unsigned long int that can accommodate its value. If the constant has both u and I suffixes (ul, lu, Ul, IU, uL, Lu, LU, or UL), its data type will be unsigned long int. Table 1.6 summarizes the representations of integer constants in all three bases. The data types indicated assume no overriding L or U suffix has been used. Character constants A character constant is one or more characters enclosed in single quotes, such as ' A', ' =' , ' \n' . In C, single character constants have data type int; they are represented internally with 16 bits, with the upper byte zero or sign-extended. In C++, a character constant has type char. Multicharacter constants in both C and C++ have data type int. Escape sequences The backslash character (\) is used to introduce an escape sequence, allowing the visual representation of certain nongraphic characters. For example, the constant \n is used for the single newline character. A backslash is used with octal or hexadecimal numbers to represent the ASCII symbol or control code corresponding to that value; for example, ' \03' for Ctrl-C or ' \x3F' for the question mark. You can use any string of up to three octal or any number of hexadecimal numbers in an escape sequence, provided that the value is within legal range for data type char (0 to Oxff for Borland C++). Larger numbers generate the compiler error, "Numeric constant too large." For example, the octal number \777 is larger than the maximum value allowed, \377, and will generate an error. The first nonoctal or nonhexadecimal character encountered in an octal or hexadecimal escape sequence marks the end of the sequence. Originally, Turbo C allowed only three digits in a hexadecimal escape sequence. The ANSI C rules adopted in Borland C++ might cause problems with old code that assumes only the first three characters are converted. For example, using Turbo C l.x to define a string with a bell (ASCII 7) followed by numeric characters, a programmer might write: printf("\x0072.1A Simple Operating System"); 14 Borland C++ Prdgrammer's Guide This is intended to be interpreted as \x007 and I/2.1A Simple Operating System". However, Borland C++ compiles it as the hexadecimal number \x0072 and the literal string I/.1A Simple Operating System". To avoid such problems, rewrite your code like this: printf("\x007" "2.1A Simple Operating System"); Ambiguities may also arise if an octal escape sequence is followed by a nonoctal digit. For example, because 8 and 9 are not legal octal digits, the constant \258 would be interpreted as a twocharacter constant made up of the characters \25 and 8. The next table shows the available escape sequences. Table 1.7 Borland C++ escape sequences The \ \ must be used to represent a real ASCII backslash, as used in DOS paths. Sequence \a \b \f \n \r \t \v \\ \, \" \? \0 \xH \XH Value Ox07 Ox08 OxOC OxOA OxOD Ox09 OxOB Ox5c Ox27 Ox22 Ox3F Char What it does BEL BS FF LF CR HT VT \ Audible bell Backspace Formfeed Newline (linefeed) Carriage return Tab (horizontal) Vertical tab Backslash Single quote (apostrophe) Double quote Question mark o = a string of up to three octal digits H = a string of hex digits H = a string of hex digits ? any any any Borland C++ special two-character constants Borland C++ also supports two-character constants (for example, , An', ' \n \t', and' \007\007'). These constants are represented as 16-bit int values, with the first character in the low-order byte and the second character in the high-order byte. These constants are not portable to other C compilers. Signed and unsigned char In C, one-character constants, such as ' A', ' \ t' , and ' \007' , are also represented as 16-bit int values. In this case, the low-order byte is sign extended into the high byte; that is, if the value is greater than 127 (base 10), the upper byte is set to -1 (=OxFF). This Chapter 7, Lexical elements 15 can be disabled by declaring that the default char type is unsigned (use the -K command-line compiler option or choose Unsigned Characters in the Options I Compiler I Code Generation dialog box), which forces the high byte to be zero regardless of the value of the low byte. Wide character constants A character constant preceded by an L is a wide-character constant of data type wchar_t (an integral type defined in stddef.h). For example, x Floating-point constants = L I A'; A floating constant consists of: • • • • decimal integer decimal point decimal fraction e or E and a signed integer exponent (optional) iii type suffix: for For 1or L (optional) You can omit either the decimal integer or the decimal fraction (but not both). You can omit either the decimal point or the letter e (or E) and the signed integer exponent (but not both). These rules allow for conventional and scientific (exponent) notations. Negative floating constants are taken as positive constants with the unary operator minus (-) prefixed. Examples: Constant Value 23.45e6 23.45 X 106 .0 o. 1. -1.23 2e-5 3E+10 .09E34 o o 1.0 x 100 = 1.0 -1.23 2.0 x 10-5 3.0 X 1010 0.09 X 1034 Floating-point constants-data types In the absence of any suffixes, floating-point constants are of type double. However, you can coerce a floating constant to be of type 16 Borland C++ Programmer's Guide float by adding an f or F suffix to the constant. Similarly, the suffix 1or L forces the constant to be data type long double. The next table shows the ranges available for float, double, and long double. Table 1.8 Borland C++ floating constant sizes and ranges Type Size (bits) Range float 32 3.4 x 10-38 to 3.4 X 1038 double 64 1.7 x 10-308 to 1.7 X 10308 long double 80 3.4 x 10-4932 to 1.1 X 104932 Enumeration constants Enumeration constants are identifiers defined in enum type declarations. The identifiers are usually chosen as mnemonics to assist legibility. Enumeration constants are integer data types. They can be used in any expression where integer constants are valid. The identifiers used must be unique within the scope of the enum declaration. Negative initializers are allowed. See page 73 for a detailed look at enum dec/a rations. The values acquired by enumeration constants depend on the format of the enumeration declaration and the presence of optional initializers. In this example, enum team { giants, cubs, dodgers }; giants, cubs, and dodgers are enumeration constants of type team that can be assigned to any variables of type team or to any other variable of integer type. The values acquired by the enumeration constants are giants = 0, cubs = 1, dodgers = 2 in the absence of explicit initializers. In the following example, enum team { giants, cubs=3, dodgers = giants + 1 }; the constants are set as follows: giants = 0, cubs = 3, dodgers = 1 The constant values need not be unique: enum team { giants, cubs = 1, dodgers = cubs - 1 }; Chapter 7, Lexical elements 17 String literals String literals, also known as string constants, form a special category of constants used to handle fixed sequences of characters. A string literal is of data type array of char and storage class static, written as a sequence of any number of characters surrounded by double quotes: "This is literally a string!" The null (empty) string is written" ". The characters inside the double quotes can include escape sequences (see page 14). This code, for example, "\t\t\"Name\"\\\tAddress\n\n" prints out like this: "Name"\ Address "Name" is preceded by two tabs; Address is preceded by one tab. The line is followed by two new lines. The \" provides interior double quotes. A literal string is stored internally as the given sequence of characters plus a final null character ('\0'). A null string is stored as a single \0 character. I I Adjacent string literals separated only by whitespace are concatenated during the parsing phase. In the following example, #include int main () { char p *p; "This is an example of how Borland Ctt" " will automatically\ndo the concatenation for" " you on very long strings, \nresulting in nicer" " looking programs."; printf (p) ; return(O) ; = The output of the program is This is an example of how Borland Ctt will automatically do the concatenation for you on very long strings, resulting in nicer looking.programs. 18 Borland C++ Programmer's Guide You can also use the backslash (\) as a continuation character in order to extend a string constant across line boundaries: puts("This is really \ a one-line string"); Constants and internal representation ANSI C acknowledges that the size and numeric range of the basic data types (and their various permutations) are implementation specific and usually derive from the architecture of the host computer. For Borland C++, the target platform is the IBM PC family (and compatibles), so the architecture of the Intel 8088 and 80x86 microprocessors governs the choices of inner representations for the various data types. The next table lists the sizes and resulting ranges of the data types for Borland C++; see page 39 for more information on these data types. Figure 1.1 shows how these types are represented internally. Table 1.9: Data types, sizes, and ranges Size (bits) Range Sample applications unsigned char 8 o to 255 Small numbers and full PC character set char 8 -128 to 127 Very small numbers and ASCII characters Type enum 16 -32,768 to 32,767 Ordered sets of values unsigned int 16 o to 65,535 Larger numbers and loops short int 16 -32,768 to 32,767 Counting, small numbers, loop control int 16 -32,768 to 32,767 Counting, small numbers, loop control unsigned long 32 o to 4,294,967,295 Astronomical distances long 32 -2,147,483,648 to 2,147,483,647 Large numbers, populations float 32 3.4 x 10-38 to 3.4 X 1038 Scientific (7-digit precision) double 64 1.7 x 10-308 to 1.7 X 10 308 Scientific (15-digit precision) 10-4932 long double 80 3.4 x near pointer 16 Not applicable Manipulating memory addresses Not applicable Manipulating addresses outside current segment far pointer 32 Chapter 1, Lexical elements to 1.1 x 104932 Financial (19-digit precision) 19 Figure 1.1 Internal representations of data types - Increasing significance (2's complement) long int s~l___ ,:--1 m_a_g_nit_ud_e_ _.-.JI (2's complement) 31 double lsi 63 long double e~~~sne~nt i1 significand I 51 lsi e~~~s';~nt significand 111 7L9L-----~ML6~3--------------------------------~ s Sign bit (0 = positive, 1 = negative) Position of implicit binary point Integer bit of significand: Stored in long double Implicit (always 1) in float, double Exponent bias (normalized values): float : 127 (7FH) double : 1023 (3FFH) long double: 16,383 (3FFFH) Constant expressions A constant expression is an expression that always evaluates to a constant (and it must evaluate to a constant that is in the range of representable values for its type). Constant expressions are evaluated just as regular expressions are. You can use a constant expression anywhere that a constant is legal. The syntax for constant expressions is constant-expression: Conditional-expression 20 Borland C++ Programmer's Guide Constant expressions cannot contain any of the following operators, unless the operators are contained within the operand of a sizeof operator: assignment comma III decrement III function call II increment II III Punctuators The punctuators (also known as separators) in Borland C++ are defined as follows: punctuator: one of [](){},;: Brackets [ ] (open and close brackets) indicate single and multidimensional array subscripts: char ch, str [l int mat[3] [4]; ch = str[3]; Parentheses ... * = # "Stan" ; /* 3 x 4 matrix */ /* 4th element */ () (open and close parentheses) group expressions, isolate condi- tional expressions, and indicate function calls and function parameters: d = c * (a + b); /* override normal precedence */ if (d /* essential with conditional statement */ == z) ttX; func (); int (*fptr) () ; fptr = func; /* function call, no args */ /* function pointer declaration */ /* no () means func pointer */ void func2(int n); /* function declaration with args */ Parentheses are recommended in macro definitions to avoid potential precedence problems during expansion: #define CUBE (x) ((x) * (x) * (x)) The use of parentheses to alter the normal operator precedence and associativity rules is covered on page 79. Chapter 7, Lexical elements 21 Braces {} (open and close braces) indicate the start and end of a compound statement: if (d == z) { ttX; func() ; The closing brace serves as a terminator for the compound statement, so a ; (semicolon) is not required after the }, except in structure or class declarations. Often, the semicolon is illegal, as in if (statement) /*illegal semicolon*/ {}; else Comma The comma (,) separates the elements of a function argument list: void func(int n , float f, char ch); The comma is also used as an operator in comma expressions. Mixing the two uses of comma is legal, but you must use parentheses to distinguish them: func (i j); func ( (expl, exp2) I Semicolon I (exp3 I exp4 I / * call func wi th two args */ exp5)); / * also calls func with two args! */ The semicolon (;) is a statement terminator. Any legal C or C++ expression (including the empty expression) followed by ; is interpreted as a statement, known as an expression statement. The expression is evaluated and its value is discarded. If the expression statement has no side effects, Borland C++ may ignore it. a t b; tta; /* maybe evaluate a t b, but discard value */ /* side effect on a, but discard value of tta */ /* empty expression = null statement */ Semicolons are often used to create an empty statement: for (i = 0; i < n; itt) { 22 Borland C++ Programmer's Guide Colon Use the colon (:) to indicate a labeled statement: start: x=O; goto start; switch (a) case 1: puts("One"); break; case 2: puts("Two"); break; default: puts("None of the above!"); break; Labels are covered on page 98. Ellipsis Ellipsis (. .. ) are three successive periods with no whitespace intervening. Ellipsis are used in the formal argument lists of function prototypes to indicate a variable number of arguments, or arguments with varying types: void func(int n, char ch, ... ); This declaration indicates that func will be defined in such a way that calls must have at least two arguments, an int and a char, but can also have any number of additional arguments. ~ Asterisk (pointer declaration) In C++, you can omit the comma preceding the ellipsis. The * (asterisk) in a variable declaration denotes the creation of a pointer to a type: char *char-ptr; /* a pointer to char is declared */ Pointers with multiple levels of indirection can be declared by indicating a pertinent number of asterisks: int **int-ptr; double ***double-ptr; /* a pointer to a pointer to an int */ /* a pointer to a pointer to a pointer to doubles */ You can also use the asterisk as an operator to either dereference a pointer or as the multiplication operator: i a Chapter 7, Lexical elements = *int-ptr; = b * 3.14; 23 Equal sign (initializer) The =(equal sign) separates variable declarations from initialization lists: char array [5] = { 1, 2, 3, 4, 5 }; int x = 5; In C++, declarations of any type can appear (with some restrictions) at any point within the code. In a C function, no code can precede any variable declarations. In a C++ function argument list, the equal sign indicates the default value for a parameter: int f (int i = 0) { ... } /* parameter i has default value of zero */ The equal sign is also used as the assignment operator in expressions: a = b + c; = farmalloc(sizeof(float)*100); ptr Pound sign (preprocessor directive) The # (pound sign) indicates a preprocessor directive when it occurs as the first nonwhitespace character on a line. It signifies a compiler action, not necessarily associated with code generation. See page 157 for more on the preprocessor directives. # ~md ## (double pound signs) are also used as operators to perform token replacement and merging during the preprocessor scanning phase. 24 Bar/and C++ Programmer's Guide c H A p T R E 2 Language structure This chapter provides a formal definition of Borland C++'s language structure. It details the legal ways in which tokens can be grouped together to form expressions, statements, and other significant units. By contrast, lexical elements (described in Chapter 1) are concerned with the different categories of wordlike units, known as tokens, recognized by a language. Declarations Scope is discussed starting on page 27; visibility on page 29; duration on page 29; and linkage on page 31. This section briefly reviews concepts related to declarations: objects, types, storage classes, scope, visibility, duration, and linkage. A general knowledge of these is essential before tackling the full declaration syntax. Scope, visibility, duration, and linkage determine those portions of a program that can make legal references to an identifier in order to access its object. Objects An object is an identifiable region of memory that can hold a fixed or variable value (or set of values). (This use of the word object is not to be confused with the more general term used in objectoriented languages.) Each value has an associated name and type (also known as a data type). The name is used to access the object. This name can be a simple identifier, or it can be a complex expression that uniquely "points" to the object. The type is used Chapter 2, Language structure 25 • to determine the correct memory allocation required initially • to interpret the bit patterns found in the object during subsequent accesses • in many type-checking situations, to ensure that illegal assignments are trapped Borland C++ supports many standard (predefined) and userdefined data types, including signed and unsigned integers in various sizes, floating-point numbers in various precisions, structures, unions, arrays, and classes. In addition, pointers to most of these objects can be established and manipulated in various memory models. The Borland c++ standard libraries and your own program and header files must provide unambiguous identifiers (or expressions derived from them) and types so that Borland C++ can consistently access, interpret, and (possibly) change the bit patterns in memory corresponding to each active object in your program. Declarations establish the necessary mapping between identifiers and objects. Each declaration associates an identifier with a data type. Most declarations, known as defining declarations, also establish the creation (where and when) of the object, that is, the allocation of physical memory and its possible initialization. Other declarations, known as referencing declarations, simply make their identifiers and types known to the compiler. There can be many referencing declarations for the same identifier, especially in a multifile program, but only one defining declaration for that identifier is allowed. Generally speaking, an identifier cannot be legally used in a program before its declaration point in the source code. Legal exceptions to this rule, known as forward references, are labels, calls to undeclared functions, and class, struct, or union tags. Lvalues An lvalue is an object locator: An expression that designates an object. An example of an lvalue expression is *P, where P is any expression evaluating to a nonnull pointer. A modifiable lvalue is an identifier or expression that relates to an object that can be accessed and legally changed in memory. A const pointer to a constant, for example, is not a modifiable lvalue. A pointer to a constant can be changed (but its dereferenced value cannot). 26 Borland C++ Programmer's Guide Historically, the I stood for "left," meaning that an lvalue could legally stand on the left (the receiving end) of an assignment statement. Now only modifiable lvalues can legally stand to the left of an assignment statement. For example, if a and bare nonconstant integer identifiers with properly allocated memory storage, they are both modifiable lvalues, and assignments such as a = 1; and b =a + b are legal. Rvalues Types and storage classes The expression a + b is not an lvalue: a + b = a is illegal because the expression on the left is not related to an object. Such expressions are often called rvalues (short for right values). Associating identifiers with objects requires that each identifier has at least two attributes: storage class and type (sometimes referred to as data type). The Borland c++ compiler deduces these attributes from implicit or explicit declarations in the source code. Storage class dictates the location (data segment, register, heap, or stack) of the object and its duration or lifetime (the entire running time of the program, or during execution of some blocks of code). Storage class can be established by the syntax of the declaration, by its placement in the source code, or by both of these factors. The type, as explained earlier, determines how much memory is allocated to an object and how the program will interpret the bit patterns found in the object's storage allocation. A given data type can be viewed as the set of values (often implementation-dependent) that identifiers of that type can assume, together with the set of operations allowed on those values. The special compile-time operator, sizeof, lets you determine the size in bytes of any standard or user-defined type; see page 87 for more on this operator. Scope The scope of an identifier is that part of the program in which the identifier can be used to access its object. There are five categories of scope: block (or local), function, function prototype, file, and class (C++ only). These depend on how and where identifiers are declared. Chapter 2, Language structure 27 Block scope The scope of an identifier with block (or local) scope starts at the declaration point and ends at the end of the block containing the declaration (such a block is known as the enclosing block). Parameter declarations with a function definition also have block scope, limited to the scope of the block that defines the function. Function scope The only identifiers having function scope are statement labels. Label names can be used with gata statements anywhere in the function in which the label is declared. Labels are declared implicitly by writing label_name: followed by a statement. Label names must be unique within a function. Function prototype scope Identifiers declared within the list of parameter declarations in a function prototype (not part of a function definition) have function prototype scope. This scope ends at the end of the function prototype. File scope File scope identifiers, also known as globals, are declared outside of all blocks and classes; their scope is from the point of declaration to the end of the source file. Class scope (C++) For now, think of a class as a named collection of members, including data structures and functions that act on them. Class scope applies to the names of the members of a particular class. Classes and their objects have many special access and scoping rules; see pages 111 to 124. Scope and name spaces Name space is the scope within which an identifier must be unique. There are four distinct classes of identifiers in C: Structures, classes, and enumerations are in the same name space in C++. 28 1. gata label names. These must be unique within the function in which they are declared. 2. Structure, union, and enumeration tags. These must be unique within the block in which they are defined. Tags declared outside of any function must be unique within all tags defined externally. 3. Structure and union member names. These must be unique within the structure or union in which they are defined. There is no restriction on the type or offset of members with the same member name in different structures. Borland C++ Programmer's Guide 4. Variables, typedefs, functions, and enumeration members. These must be unique within the scope in which they are defined. Externally declared identifiers must be unique among externally declared variables. Visibility The visibility of an identifier is that region of the program source code from which legal access can be made to the identifier's associated object. Scope and visibility usually coincide, though there are circumstances under which an object becomes temporarily hidden by the appearance of a duplicate identifier: The object still exists but the original identifier cannot be used to access it until the scope of the duplicate identifier is ended. Visibility cannot exceed scope, but scope can exceed visibility. int ii char i = 3i double i ch Chi ii 3.0e3i = = += Ii II auto by default II int i and char ch in scope and visible 'A' i II double i in scope and visible II int i=3 in scope but hidden II char ch in scope and visible II double i out of scope II int i visible and = 4 II char ch still in scope & visible = 'A' II int i and char ch out of scope ~ Again, special rules apply to hidden class names and class member names: Special C++ operators allow hidden identifiers to be accessed under certain conditions (see page 112). Duration Duration, closely related to storage class, defines the period during which the declared identifiers have real, physical objects allocated in memory. We also distinguish between compile-time and run-time objects. Variables, for instance, unlike typedefs and types, have real memory allocated during run time. There are three kinds of duration: static, local, and dynamic. Chapter 2, Language structure 29 Static duration Objects with static duration are allocated memory as soon as execution is underway; this storage allocation lasts until the program terminates. Static duration objects usually reside in fixed data segments allocated according to the memory model in force. All functions, wherever defined, are objects with static duration. All variables with file scope have static duration. Other variables can be given static duration by using the explicit static or extern storage class specifiers. Static duration objects are initialized to zero (or null) in the absence of any explicit initializer or, in C++, constructor. Static duration must not be confused with file or global scope. An object can have static duration and local scope. Local duration An object with local duration a/so has local scope, since it does not exist outside of its enclosing block. The converse is not true: A local scope object can have static duration. Local duration objects, also known as automatic objects, lead a more precarious existence. They are created on the stack (or in a register) when the enclosing block or function is entered. They are deallocated when the program exits that block or function. Local duration objects must be explicitly initialized; otherwise, their contents are unpredictable. Local duration objects always must have local or function scope. The storage class specifier auto may be used when declaring local duration variables, but is usually redundant, since auto is the default for variables declared within a block. When declaring variables (for example, int, char, float), the storage class specifier register also implies auto; but a request (or hint) is passed to the compiler that the object be allocated a register if possible. Borland C++ can be set to allocate a register to a local integral or pointer variable, if one is free. If no register is free, the variable is allocated as an auto, local object with no warning or error. Dynamic duration Dynamic duration objects are created and destroyed by specific function calls during a program. They are allocated storage from a special memory reserve known as the heap, using either standard library functions such as malloc, or by using the C++ operator new. The corresponding dealloca tians are made using free or delete. 30 Borland C++ Programmer's Guide Translation units The term translation unit refers to a source code file together with any included files, but less any source lines omitted by conditional preprocessor directives. Syntactically, a translation unit is defined as a sequence of external declarations: transla tion-un it: external-declaration translation-unit external-declaration external-declaration fu nction-defin itioll declaration For more details, see "External dec/orations and definitions" on page 36. The word external has several connotations in C; here it refers to declarations made outside of any function, and which therefore have file scope. (External linkage is a distinct property; see the following section, "Linkage.") Any declaration that also reserves storage for an object or function is called a definition (or defining declaration). Linkage An executable program is usually created by compiling several independent translation units, then linking the resulting object files with preexisting libraries. A problem arises when the same identifier is declared in different scopes (for example, in different files), or declared more than once in the same scope. Linkage is the process that allows each instance of an identifier to be associated correctly with one particular object or function. All identifiers have one of three linkage attributes, closely related to their scope: external linkage, internal linkage, or no linkage. These attributes are determined by the placement and format of your declarations, together with the explicit (or implicit by default) use of the storage class specifier static or extern. Each instance of a particular identifier with external linkage represents the same object or function throughout the entire set of files and libraries making up the program. Each instance of a particular identifier with internal linkage represents the same object or function only within one file. Identifiers with no linkage represent unique entities. External and internal linkage rules are as follows: Chapter 2, Language structure 31 1. Any object or file identifier having file scope will have internal linkage if its declaration contains the storage class specifier static. For C++, if the same identifier appears with both internal and external linkage within the same file, the identifier will have external linkage. In C, it will have internal linkage. 2. If the declaration of an object or function identifier contains the storage class specifier extern, the identifier has the same linkage as any visible declaration of the identifier with file scope. If there is no such visible declaration, the identifier has external linkage. 3. If a function is declared without a storage class specifier, its linkage is determined as if the storage class specifier extern had been used. 4. If an object identifier with file scope is declared without a storage class specifier, the identifier has external linkage. The following identifiers have no linkage attribute: 1. any identifier declared to be other than an object or a function (for example, a typedef identifier) 2. function parameters 3. block scope identifiers for objects declared without the storage class specifier extern Name mangling When a C++ module is compiled, the compiler generates function names that include an encoding of the function's argument types. This is known as name mangling. It makes overloaded functions possible, and helps the linker catch errors in calls to functions in other modules. However, there are times when you won't want name mangling. When compiling a C++ module to be linked with a module that does not have mangled names, the C++ compiler has to be told not to mangle the names of the functions from the other module. This situation typically arises when linking with libraries or .OBJ files compiled with a C compiler. To tell the C++ compiler not to mangle the name of a function, simply declare the function as extern "C", like this: extern "CO void Cfunc( int ); This declaration tells the compiler that references to the function Cfunc should not be mangled. 32 Borland C++ Programmer's Guide You can also apply the extern "e" declaration to a block of names: extern "C" { void Cfuncl( int ) i void Cfunc2( int ) i void Cfunc3( int )i }i As with the declaration for a single function, this declaration tells the compiler that references to the functions Cfunc1, Cfunc2, and Cfunc3 should not be mangled. You can also use this form of block declaration when the block of function names is contained in a header file: extern "C" { #include "locallib.h" }i Declaration syntax All six interrelated attributes (storage class, type, scope, visibility, duration, and linkage) are determined in diverse ways by declarations. Declarations can be defining declarations (also known simply as definitions) or referencing declarations (sometimes known as nondefining declarations). A defining declaration, as the name implies, performs both the duties of declaring and defining; the nondefining declarations require a definition to be added somewhere in the program. A referencing declaration simply introduces one or more identifier names into a program. A definition actually allocates memory to an object and associates an identifier with that object. Tentative definitions The ANSI C standard introduces a new concept: that of the tentative definition. Any external data declaration that has no storage class specifier and no initializer is considered a tentative definition. If the identifier declared appears in a later definition, then the tentative definition is treated as if the extern storage class specifier were present. In other words, the tentative definition becomes a simple referencing declaration. Chapter 2, Language structure 33 If the end of the translation unit is reached and no definition has appeared with an initializer for the identifier, then the tentative definition becomes a full definition, and the object defined has uninitialized (zero-filled) space reserved for it. For example, int int Xi /*legal, one copy of Xi int Yi int y = 4i int z int z -.. Possible declarations = 5i = 6i X is reserved */ /* legal, y is initialized to 4 */ /* not legal, both are initialized definitions */ Unlike ANSI C, C++ doesn't have the concept of a tentative declaration; an external data declaration without a storage class specifier is always a definition. The range of objects that can be declared includes • variables functions II classes and class members (C++) • types • structure, union, and enumeration tags • structure members II union members • arrays of other types • enumeration constants • statement labels 1:11 preprocessor macros II The full syntax for declarations is shown in the following tables. The recursive nature of the declarator syntax allows complex declarators. We encourage the use of typedefs to improve legibility. 34 Borland C++ Programmer's Guide Table 2.1 Borland C++ declaration syntax declaration: ; asm-declaration Junction-declaration linkage-specification decl-specifier: s tora ge-class-specifier type-specifier Jct-specifier friend (C++ specific) typedef decl-specifiers: decl-specifier storage-class-specifier: auto register static extern Jct-specifier: (C++ specific) inline virtual type-specifier: simple-type-name class-specifier enum-specifier elaborated-type-specifier const volatile simple-type-name: class-name typedef-name char short int long signed unsigned float double void elaborated-type-specifier: class-key identifier class-key class-name enum enum-name class-key: (C++ specific) class struct union enum-specifier: enum { } mum-list: enumerator enumerator-list, enumerator enumerator: identifier identifier =constant-expression cons tan t-expression: conditional-expression linkage-specification: (C++ specific) extern string { } extern string declaration declaration-list: declaration declaration-list; declaration For the following table, note that there are restrictions on the number and order of modifiers and qualifiers. Also, the modifiers listed are the only addition to the declarator syntax that are not ANSI C or C++. These modifiers are each discussed in greater detail starting on page 47. Chapter 2, Language structure 35 Table 2.2: Borland C++ declarator syntax declarator-list: in it-declarator declarator-list , in it-declarator class-name (C++ specific) - class-lzame (C++ specific) typedef-lzame in it-declarator: declarator type-name: type-specifier declarator: dname modifier-list ptr-operator declarator declarator ( parameter-declaration-list ) (The is for c++ only.) declarator [ I ( declarator) abstract-declarator: ptr-operator ( arglllnent-declaration-list ) [ I (abstract-declarator) modifier-list: modifier modifier-list modifier arg-declaratiOll-list: argumellt-declaratiOll arg-declaration-list , argument-declaration modifier: cdecl pascal interrupt near far huge argulllent-declaration: decl-specifiers declarator decl-specifiers declarator =expression (C++ specific) decl-specifiers decl-specifiers =expression (C++ specific) ptr-operator: * & (C++ specific) class-name :: * (C++ specific) cv-qualifier-list: cv-qllalifier cv-qualifier canst volatile dllame: name External declarations and definitions argllment-declaratioll-list: arg-declaratioll-list, .. . ... (C++ specific) fct-defiIllHoll: declarator fct-body fct-body: compoulUt-statemellt initializer: =expression ={initializer-list I ( expression-list) (C++ specific) initializer-list: expression initializer-list , expression ( illitializer-list <,> I The storage class specifiers auto and register cannot appear in an external declaration (see "Translation units," page 31). For each identifier in a translation unit declared with internal linkage, there can be no more than one external definition. An external definition is an external declaration that also defines an object or function; that is, it also allocates storage. If an identifier declared with external linkage is used in an expression (other than as part of the operand of sizeof), there must be exactly one external definition of that identifier somewhere in the entire program. 36 Borland C++ Programmer's Guide Borland c++ allows later re-declarations of external names, such as arrays, structures, and unions, to add information to earlier declarations. For example, int a [] i struct mystructi II no size II tag only, no member declarators int a[3] = {l, 2, 3}i II supply size and initialize struct mystruct { int i, ji II add member declarators }i The following table covers class declaration syntax. Page 105 covers C++ reference types (closely related to pointer types) in detail. Table 2,3: Borland C++ class declarations (C++ only) class-specifier: class-head { } class-head: class-key class-key class-name member-list: member-declaration access-specifier: member-declaration: ; function-definition <;> qualified-l1ame ; member-declarator-list: member-declarator member-declarator-list, member-declarator member-declarator: declarator : c011stant-expression pure-specifier: =0 base-spec: : base-list base-list: base-specifier base-list, base-specifier base-specifier: class-name virtual class-11ame Chapter 2, Language structure access-specifier class-name access-specifier: private protected public conversio11-function-name: operator conversion-type-name conversion-type-name: type-specifiers ctor-initializer: : 111e111-initializer-list 111em-in itializer-l ist: me111-i nitia Iizer mem-initializer, 111em-initializer-list me111- in itia Iizer: class name ( ) identifier ( ) operator-function-name: operator operator operator: one of new delete sizeof + & += &= -++ 1 -= 1= != *= « <= 1= » >= ->* % = %= »= && -> 1\ <> 1\= «= II () [] 37 Type specifiers The type specifier with one or more optional modifiers is used to specify the type of the declared identifier: int ii unsigned char chl, ch2i II declare i as a signed integer II declare two unsigned chars By long-standing tradition, if the type specifier is omitted, type signed int (or equivalently, int) is the assumed default. However, in C++ there are some situations where a missing type specifier leads to syntactic ambiguity, so C++ practice uses the explicit entry of all int type specifiers. Type taxonomy There are four basic type categories: void, scalar, function, and aggregate. The scalar and aggregate types can be further divided as follows: 1:1 Scalar: arithmetic, enumeration, pointer, and reference types (C++) c Aggregate: array, structure, union, and class types (C++) Types can also be divided into fundamental and derived types. The fundamental types are void, char, int, float, and double, together with short, long, signed, and unsigned variants of some of these. The derived types include pointers and references to other types, arrays of other types, function types, class types, structures, and unions. ~ .A class object, for example, can hold a number of objects of different types together with functions for manipulating these objects, plus a mechanism to control access and inheritance from other classes. Given any nonvoid type type (with some provisos), you can declare derived types as follows: 38 Borland C++ Programmer's Guide Table 2.4 Declaring types Note that type& var, type &var, and type & var are all equivalent. type t; An object of type type type army[lO]; Ten types: array[O] - army[9] type *ptr; ptr is a pointer to type type &ref = t; ref is a reference to type (C++) type func(void); func returns value of type type void func1 (type t); func1 takes a type type parameter struct st {type t1; type t2J; structure st holds two types And here's how you could declare derived types in a class: II class ct holds ptr to type plus a function II taking a type parameter (ett) class ct { type *ptr; public: void func(type*); Type void void is a special type specifier indicating the absence of any values. It is used in the following situations: c++ handles func() in a special manner. See "Declarations and prototypes" on page 61 and code examples on page 62. [J An empty parameter list in a function declaration: int func(void); l'l When the declared function does not return a value: C As a generic pointer: A pointer to void is a generic pointer to anything: I'J In typecasting expressions: void func(int n); II return value void *ptr; The fundamental types II func takes no arguments II ptr can later be set to point to any object extern int errfunc(); II returns an error code (void) errfunc(); II discard return value The fundamental type specifiers are built from the following keywords: signed and unsigned are modifiers that can be applied to the integral types. Chapter 2, Language structure char double float int long short signed unsigned 39 From these keywords, you can build the integral and floatingpoint types, which are together known as the arithmetic types. The include file limits.h contains definitions of the value ranges for all the fundamental types. Integral types char, short, int, and long, together with their unsigned variants, are all considered integral data types. The integral type specifiers are as follows, with synonyms listed on the same line: Table 2.5 Integral types char, signed char . Synonyms if default char set to signed unsigned char Synonyms if default char set to unsigned char, unsigned char signed char int, signed int unsigned, unsigned int short, short int, signed short int unsigned short, unsigned short int long, long int, signed long int unsigned long, unsigned long int At most, one of signed and unsigned can be used with char, short, int, or long. If you use the keywords signed and unsigned on their own, they mean signed int and unsigned int, respectively. In the absence of unsigned, signed is usually assumed. An exception arises with char. Borland C++ lets you set the default for char to be signed or unsigned. (The default, if you don't set it yourself, is signed.) If the default is set to unsigned, then the decla:r:ation char ch declares ch as unsigned. You would need to use signed char ch to override the default. Similarly, with a signed default for char, you would need an explicit unsigned char ch to declare an unsigned char. At most, one of long and short can be used with int. The keywords long and short used on their own mean long int and short int. ANSI C does not dictate the sizes or internal representations of these types, except to insist that short, int, and long form a nondecreasing sequence with "short <= int <= long." All three types can legally be the same. This is important if you want to write portable code aimed at other platforms. In Borland C++, the types int and short are equivalent, both being 16 bits. long is a 32-bit object. The signed varieties are all stored in 2's complement format using the most significant bit (MSB) as a 40 Borland C++ Programmer's Guide sign bit: 0 for positive, 1 for negative (which explains the ranges shown in Table 1.9 on page 19). In the unsigned versions, all bits are used to give a range of 0 - (2 11 - 1), where n is 8, 16, or 32. Floating-point types The representations and sets of values for the floating-point types are implementation dependent; that is, each implementation of C is free to define them. Borland C++ uses the IEEE floating-point formats. (Appendix A, "ANSI implementation-specific standards," tells more about implementation-specific items.) float and double are 32- and 64-bit floating-point data types, respectively. long can be used with double to declare an 80-bit precision floating-point identifier: long double test_case, for example. Table 1.9 on page 19 indicates the storage allocations for the floating-point types. Standard conversions When you use an arithmetic expression, such as a + b, where a and b are different arithmetic types, Borland C++ performs certain internal conversions before the expression is evaluated. These standard conversions include promotions of "lower" types to "higher" types in the interests of accuracy and consistency. Here are the steps Borland C++ uses to convert the operands in an arithmetic expression: 1. Any small integral types are converted as shown in Table 2.6. After this, any two values associated with an operator are either int (including the long and unsigned modifiers, double, float, or long double). 2. If either operand is of type long double, the other operand is converted to long double. 3. Otherwise, if either operand is of type double, the other operand is converted to double. 4. Otherwise, if either operand is of type float, the other operand is converted to float. 5. Otherwise, if either operand is of type unsigned long, the other operand is converted to unsigned long. 6. Otherwise, if either operand is of type long, then the other operand is converted to long. 7. Otherwise, if either operand is of type unsigned, then the other operand is converted to unsigned. 8. Otherwise, both operands are of type int. Chapter 2, Language structure 41 The result of the expression is the same type as that of the two operands. Table 2.6 Methods used in standard arithmetic conversions Special char, int, and enum conversions The conversions discussed in this section are specific to Borland C++. Type Converts to Method char int unsigned char signed char short unsigned short enum int int int unsigned int int Zero or sign-extended (depends on default char type) Zero-filled high byte (always) Sign-extended (always) Same value Same value Same value Assigning a signed character object (such as a variable) to an integral object results in automatic sign extension. Objects of type signed char always use sign extension; objects of type unsigned char always set the high byte to zero when converted to int. Converting a longer integral type to a shorter type truncates the higher order bits and leaves low-order bits unchanged. Converting a shorter integral type to a longer type either sign extends or zero fills the extra bits of the new value, depending on whether the shorter type is signed or unsigned, respectively. Initialization Initializers set the initial value that is stored in an object (variables, arrays, structures, and so on). If you don't initialize an object, and it has static duration, it will be initialized by default in the following manner: If it has automatic storage duration, its value is indeterminate. to zero if it is of an arithmetic type .. to null if it is a pointer type II The syntax for initializers is as follows: initializer = expression = {initializer-list} <,>} (expression list) initializer-list expression initializer-list, expression {initializer-list} <,>} Rules governing initializers are 42 Borland C++ Programmer's Guide 1. The number of initializers in the initializer list cannot be larger than the number of objects to be initialized. 2. The item to be initialized must be an object type or an array of unknown size. 3. For C (not required for C++), all expressions must be constants if they appear in one of these places: a. in an initializer for an object that has static duration b. in an initializer list for an array, structure, or union (expressions using sizeof are also allowed) 4. If a declaration for an identifier has block scope, and the identifier has external or internal linkage, the declaration cannot have an initializer for the identifier. 5. If there are fewer initializers in a brace-enclosed list than there are members of a structure, the remainder of the structure is initialized implicitly in the same way as objects with static storage duration. Scalar types are initialized with a single expression, which can optionally be enclo'sed in braces. The initial value of the object is that of the expression; the same constraints for type and conversions apply as for simple assignments. For unions, a brace-enclosed initializer initializes the member that first appears in the union's declaration list. For structures or unions with automatic storage duration, the initializer must be one of the following: Arrays, structures, and unions IJ an initializer list as described in the following section fl a single expression with compatible union or structure type. In this case, the initial value of the object is that of the expression. You initialize arrays and structures (at declaration time, if you like) with a brace-enclosed list of initializers for the members or elements of the object in question. The initializers are given in increasing array subscript or member order. You initialize unions with a brace-enclosed initializer for the first member of the union. For example, you could declare an array days, intended to count how many times each day of the week appears in a month (and assuming that each day will appear at least once), as follows: int days [7] = { 1 Chapter 2, Language structure f 1 f 1 f 1 1, 1 f f 1 } 43 Use these rules to initialize character arrays and wide character arrays: 1. You can initialize arrays of character type with a literal string, optionally enclosed in braces. Each character in the string, including the null terminator, initializes successive elements in the array. For example, you could declare char name [] = { "Unknown" }; which sets up an eight-element array, whose elements are 'U' (for name[O]), 'n' (for name[1]), and so on (and including a null termina tor). 2. You can initialize a wide character array (one that is compatible with wchar_t) by using a wide string literal, optionally enclosed in braces. As with character arrays, the codes of the wide string literal initialize successive elements of the array. Here is an example of a structure initialization: struct mystruct { int i; char str[21]; double d; s = { 20, "Borland", 3.141 }; Complex members of a structure, such as arrays or structures, can be initialized with suitable expressions inside nested braces. You can eliminate the braces, but you must follow certain rules, and it isn't recommended practice. Simple declarations Simple declarations of variable identifiers have the following pattern: data-type varl <=initl>, var2 <=init2>, ... ; where varl, var2, ... are any sequence of distinct identifiers with optional initializers. Each of the variables is declared to be of type data-type. For example, int x = I, y = 2; creates two integer variables called x and y (and initializes them to the values 1 and 2, respectively). 44 Borland C++ Programmer's Guide These are all defining declarations; storage is allocated and any optional initializers are applied. The initializer for an automatic object can be any legal expression that evaluates to an assignment-compatible value for the type of the variable involved. Initializers for static objects must be constants or constant expressions. ~ Storage class specifiers In C++, an initializer for a static object can be any expression involving constants and previously declared variables and functions. A storage class specifier, or a type specifier, must be present in a declaration. The storage class specifiers can be one of the following: auto extern register static typedef Use of storage class specifier auto The storage class specifier auto is used only with local scope variable declarations. It conveys local (automatic) duration, but since this is the default for all local scope variable declarations, its use is rare. Use of storage class specifier extern The storage class specifier extern can be used with function and variable file scope and local scope declarations to indicate external linkage. With file scope variables, the default storage class specifier is extern. When used with variables, extern indicates that the variable has static duration. (Remember that functions always have static duration.) See page 32 for information on using extern to prevent name mangling when combining C and C++ code. Use of storage class specifier regis"ter The storage class specifier register is allowed only for local variable and function parameter declarations. It is equivalent to auto, but it makes a request to the compiler that the variable should be allocated to a register if possible. The allocation of a register can significantly reduce the size and improve the performance of programs in many situations. However, since Borland C++ does a good job of placing variables in registers, it is rarely necessary to use the register keyword. Chapter 2, Language structure 45 Borland C++ lets you select register variable options from the Options I Compiler I Optimizations Options dialog box. If you check Automatic, Borland C++ will try to allocate registers even if you have not used the register storage class specifiers. Use of storage class specifier static ~ Use of storage class specifier typedef The storage class specifier static can be used with function and variable file scope and local scope declarations to indicate internal linkage. static also indicates that the variable has static duration. In the absence of constructors or explicit initializers, static variables are initialized with a or null. In C++, a static data member of a class has the same value for all instances of a class. A static member function of a class can be invoked independently of any class instance. The keyword typedef indicates that you are defining a new data type specifier rather than declaring an object. typedef is included as a storage class specifier because of syntactical rather than functional similarities. static long int biggy; typedef long int BIGGY; The first declaration creates a 32-bit, long intI static-duration object called biggy. The second declaration establishes the identifier BlGGY as a new type specifier, but does not create any run-time object. BlGGY can be used in any subsequent declaration where a type specifier would be legal. For example, extern BIGGY salary; has the same effect as extern long int salarYi Although this simple example can be achieved by #define BIGGY long intI more complex typedef applications achieve more than is possible with textual substitutions. Important! typedef does not create new data types; it merely creates useful mnemonic synonyms or aliases for existing types. It is especially valuable in simplifying complex declarations: typedef double (*PFD) ()i PFD array-pfd[10]; /* array-pfd is an array of 10 pointers to functions returning double */ 46 Borland C++ Programmer's Guide You can't use typedef identifiers with other data-type specifiers: 1* ILLEGAL *1 unsigned BIGGY pay; Modifiers In addition to the storage class specifier keywords, a declaration can use certain modifiers to alter some aspect of the identifier / object mapping. The modifiers available with Borland C++ are summarized in Table 2.7. The canst modifier The const modifier prevents any assignments to the object or any other side effects, such as increment or decrement. A const pointer cannot be modified, though the object to which it points can be. Consider the following examples: The modifier const used by itself is equivalent to const const char char const into float *const const pi maxint str *str2 = 3.1415926; = 32767; = "Hello, world" ; II A constant pointer = "Hello, world" ; 1* A pointer to a constant char *1 Given these, the following statements are illegal: pi = 3.0; 1* Assigns a value to a canst *1 1* Increments a canst *1 1* Points str to something else *1 maxinttt; str = "Hi, there! "; = Note, however, that the function call strcpy (str, "Hi there!") is legal, since it does a character-by-character copy from the string literal "Hi, there!" into the memory locations pointed to by str. I ~ In C++, const also hides the const object and prevents external linkage. You need to use extern const. A pointer to a const can't be assigned to a pointer to a non-const (otherwise, the const value could be assigned to using the non-const pointer). For example, char *str3 = str2 1* disallowed *1 Only const member functions can be called for a const object. Table 2.7 Borland C++ modifiers c++ extends const and volatile to include classes and member functions. Use with Use canst Variables only Prevents changes to object. volatile Variables only Prevents register allocation and some optimization. Warns compiler that Modifier Chapter 2, Language structure 47 Table 2,7: Borland C++ modifiers (continued) object may be subject to outside change during evaluation. Borland C++ extensions cdecl Functions Forces C argument-passing convention. Affects Linker and linktime names. cdecl Variables Forces global identifier case-sensitivity and leading underscores. pascal Functions Forces Pascal argument-passing convention. Affects Linker and linktime names. pascal Variables Forces global identifier caseinsensitivity with no leading underscores. interrupt Functions Function compiles with the additional register-housekeeping code needed when writing interrupt handlers. near, far, huge Pointer types Overrides the default pointer type specified by the current memory model. _cs, _ds, _es, _seg, - ss Pointer types Segment pointers. See page 350. near, far, Functions Overrides the default function type specified by the current memory huge model. near, far Variables _export Functions/ classes Tells the compiler which functions or classes to export. loadds Functions Sets DS to point to the current data segment. _saveregs Functions fastcall Functions Preserves all register values (except for return values) during execution of the function. Forces register parameter passing convention. Affects the linker and link-time names. - - 48 Directs the placement of the object in memory. Borland C++ Programmer's Guide The interrupt function modifier The interrupt modifier is specific to Borland C++. interrupt functions are designed to be used with the 8086/8088 interrupt vectors. Borland C++ will compile an interrupt function with extra function entry and exit code so that registers AX, BX, CX, DX, SI, DI, ES, and DS are preserved. The other registers (BP, SP, SS, CS, and IP) are preserved as part of the C-calling sequence or as part of the interrupt handling itself. The function will use an iret instruction to return, so that the function can be used to service hardware or software interrupts. Here is an example of a typical interrupt definition: void interrupt myhandler() { You should declare interrupt functions to be of type void. Interrupt functions can be declared in any memory model. For all memory models except huge, DS is set to the program data segment. For the huge model, DS is set to the module's data segment. The volatile modifier In C++, volatile has a special meaning for class member functions. If you've declared a volatile object, you can only use its volatile member functions. The volatile modifier indicates that the object may be modified; not only by you, but also by something outside of your program, such as an interrupt routine or an I/O port. Declaring an object to be volatile warns the compiler not to make assumptions concerning the value of the object while evaluating expressions containing it, since the value could (in theory) change at any moment. It also prevents the compiler from making the variable a register variable. volatile int ticks; interrupt timer () { ticks++ ; wait(int interval) { ticks = 0; while (ticks < interval); II Do nothing These routines (assuming timer has been properly associated with a hardware clock interrupt) implement a timed wait of ticks Chapter 2, Language structure 49 specified by the argument interval. A highly optimizing compiler might not load the value of ticks inside the test of the while loop, since the loop doesn't change the value of ticks. The cdecl and pascal modifiers Page 31 tells how to use extern, which allows C names to be referenced from a C++ program. Borland C++ allows your programs to easily call routines written in other languages, and vice versa. When you mix languages like this, you have to deal with two important issues: identifiers and parameter passing. In Borland C++, all global identifiers are saved in their original case (lower, upper, or mixed) with an underscore C) prepended to the front of the identifier, unless you have selected the -u - option or unchecked the Generate Underbars box in the Options I Compiler I Advanced Code Generation dialog box. pascal In Pascal, global identifiers are not saved in their original case, nor are underscores prep ended to them. Borland C++ lets you declare any identifier to be of type pascal; the identifier is converted to uppercase, and no underscore is prepended. (If the identifier is a function, this also affects the parameter-passing sequence used; see "Function type modifiers," page 52, for more details.) The -p compiler option or Calling Convention Pascal in the Options I Compiler I Entry I Exit Code dialog box causes all functions (and pointers to those functions) to be treated as if they were of type pascal. The pascal modifier is specific to Borland C++; it is intended for functions (and pointers to functions) that use the Pascal parameter-passing sequence. Also, functions declared to be of type pascal can still be called from C routines, so long as the C routine sees that the function is of type pascal. pascal putnums(int i, int j, int k) { printf("And the answers are: %d, %d, and %d\n",i,j,k); Functions of type pascal cannot take a variable number of arguments, unlike functions such as printf. For this reason, you cannot use an ellipsis (. .. ) in a pascal function definition. .. Most of the Windows API functions are pascal functions. cdecl Once you have compiled with Pascal calling convention turned on (using the -p option or IDE Options I Compiler I Entry/Exit 50 . Borland C++ Programmer's Guide Code), you may want to ensure that certain identifiers have their case preserved and keep the underscore on the front, especially if they're C identifiers from another file. You can do so by declaring those identifiers to be cdecl. (This also has an effect on parameter passing for functions). main must be declared as cdec/: this is because the C start-up code a/ways tries to call main with the C calling convention. Like pascal, the cdecl modifier is specific to Borland C++. It is used with functions and pointers to functions. It overrides the -p option or IDE Options I Compiler I Entry /Exit Code compiler directive and allows a function to be called as a regular C function. For example, if you were to compile the previous program with the Pascal calling option set but wanted to use printf, you might do something like this: extern cdecl printf(); void putnums(int i, int j, int k); cdecl main () { putnums(l,4,9); void putnums(int i, int j, int k) { printf("And the answers are: %d, %d, and %d\n",i,j,k); If you compile a program with the -p option or IDE Options I Compiler I Entry /Exit Code, all functions used from the run-time library will need to have cdecl declarations. If you look at the header files (such as stdio.h), you'll see that every function is explicitly defined as cdecl in anticipation of this. The pointer modifiers Borland C++ has eight modifiers that affect the pointer declarator (*); that is, they modify pointers to data. These are near, far, huge, _cs, _ds, _es, _seg, and _ss. C lets you compile using one of several memory models. The model you use determines (among other things) the internal format of pointers. For example, if you use a small data model (tiny, small, medium), all data pointers contain a 16-bit offset from the data segment (OS) register. If you use a large data model (compact,large, huge), all pointers to data are 32 bits long and give both a segment address and an offset. Sometimes, when using one size of data model, you want to declare a pointer to be of a different size or format than the current default. You do so using the pointer modifiers. Chapter 2, Language structure 51 See the discussion starting on page 344 in Chapter 9 for an indepth explanation of near, far, and huge pointers, and page 345 for a description of normalized pointers. Also see the discussion starting on page 350 for more on _cs, _ds, _es, _seg, and _ss. Function type modifiers The near, far, and huge modifiers can also be used as function type modifiers; that is, they can modify functions and function pointers as well as data pointers. In addition, you can use the _export, _Ioadds, and _saveregs modifiers to modify functions. The near, far, and huge function modifiers can be combined with cdecl or pascal, but not with interrupt. Functions of type huge are useful when interfacing with code in assembly language that doesn't use the same memory allocation as Borland C++. A non-interrupt function can be declared to be near, far, or huge in order to override the default settings for the current memory model. A near function uses near calls; a far or huge function uses far call instructions. In the tiny, small, and compact memory models, an unqualified function defaults to type near. In the medium and large models, an unqualified function defaults to type far. In the huge memory model, it defaults to type huge. A huge function is the same as a far function, except that the OS register is set to the data segment address of the source module when a huge function is entered, but left unset for a far function. Br ~ The _export modifier makes the function exportable from Windows. It's used in an executable (if you don't use smart callbacks) or in a OLL; see page 323 of Chapter 8 for details. The _export lTIodifier has no significance for DOS programs. The _Ioadds modifier indicates that a function should set the OS register, just as a huge function does, but does not imply near or far calls. Thus, _Ioadds far is equivalent to huge. The _saveregs modifier causes the function to preserve all register values and restore them before returning (except for explicit return values passed in registers such as AX or OX). The _Ioadds and _saveregs modifiers are useful for writing lowlevel interface routines, such as mouse support routines. 52 Borland C++ Programmer's Guide The _fastcall modifier is documented in Appendix A, "The Optimizer" in the User's Guide. Complex declarations and declarators See Table 2.1 on page 35 for the declarator syntax. The definition covers both identifier ond function declorators. Simple declarations have a list of comma-delimited identifiers following the optional storage class specifiers, type specifiers, and other modifiers. A complex declaration uses a comma-delimited list of declarators following the :various specifiers and modifiers. Within each declarator, there exists just one identifier, namely the identifier being declared. Each of the de clara tors in the list is associated with the leading storage class and type specifier. The format of the declarator indicates how the declared dname is to be interpreted when used in an expression. If type is any type, and storage dass specifier is any storage class specifier, and if 01 and 02 are any two declarators, then the declaration storage-dass-specifier type 01,02; indicates that each occurrence of 01 or 02 in an expression will be treated as an object of type type and storage class storage dass specifier. The type of the dname embedded in the declarator will be some phrase containing type, such as "type," "pointer to type," "array of type," "function returning type," or "pointer to function returning type," and so on. For example, in the declarations int n, nao[], naf[3], *pn, *apn[], (*pan) [], &nr=n; int f (void), *fnp,(void), (*pfn) (void); each of the de clara tors could be used as rvalues (or possibly lvalues in some cases) in expressions where a single int object would be appropriate. The types of the embedded identifiers are derived from their declarators as follows: Chapter 2, Language structure 53 Table 2.8: Complex declarations Declarator syntax Implied type of name Example type name; type int count; type name [ ) ; (open) array of type int count[) ; type name [3) ; Fixed array of three elements, all of type (name[O], name[1], and name[2]) int count [3); type *name; Pointer to type int *count; type *name [) ; (open) array of pointers to type int *count[) ; type * (name [) ) ; Same as above int * (count [ ) ) ; type (*name) [) ; Pointer to an (open) array of type int (*count) type &name; Reference to type (C++ only) int &count; type name ( ) ; Function returning type int count(); type *name() ; Function returning pointer to type int *count () ; type * (name () ) ; Same as above int * (count ( ) ) ; type (*name) () ; Pointer to function returning type int (*count) [) ; () ; Note the need for parentheses in (*name)[] and (*name)O,·since the precedence of both the array declarator [ ] and the function declarator ( ) is higher than the 'pointer declarator *. The parentheses in *(name[]) are optional. Pointers See page 85 for a discussion of referencing and dereferencing. Pointers fall into two main categories: pointers to objects and pointers to functions. Both types of pointers are special objects for holding memory addresses. The two pointer classes have distinct properties, purposes, and rules for manipulation, although they do share certain-Borland C++ operations. Generally speaking, pointers to functions are used to access functions and to pass functions as arguments to other functions; performing arithmetic on pointers to functions is not allowed. Pointers to objects, on the other hand, are regularly incremented and decremented as you scan arrays or more complex data structures in memory. 54 Borland C++ Programmer's Guide Although pointers contain numbers with most of the characteristics of unsigned integers, they have their own rules and restrictions for assignments, conversions, and arithmetic. The examples in the next few sections illustrate these rules and restrictions. Pointers to objects A pointer of type "pointer to object of type" holds the address of (that is, points to) an object of type. Since pointers are objects, you can have a pointer pointing to a pointer (and so on). Other objects commonly pointed at include arrays, structures, unions, and classes. The size of pointers to objects is dependent on the memory model and the size and disposition of your data segments, possibly influenced by the optional pointer modifiers (discussed starting on page 51). Pointers to functions A pointer to a function is best thought of as an address, usually in a code segment, where that function's executable code is stored; that is, the address to which control is transferred when that function is called. The size and disposition of your code segments is determined by the memory model in force, which in turn dictates the size of the function pointers needed to call your functions. A pointer to a function has a type called "pointer to function returning type," where type is the function's return type. ~ Under C++, which has stronger type checking, a pointer to a function has type "pointer to function taking argument types type and returning type." In fact, under C, a function defined with argument types will also have this narrower type. For example, void (*func)()i In C, this is a pointer to a function returning nothing. In C++, it's a pointer to a function taking no arguments and returning nothing. In this example, void (*func) (int) i *func is a pointer to a function taking an int argument and returning nothing. Chapter 2, Language structure 55 Pointer declarations See page 39 for details on void. A pointer must be declared as pointing to some particular type, even if that type is void (which really means a pointer to anything). Once declared, though, a pointer can usually be reassigned so that it points to an object of another type. Borland C++ lets you reassign pointers like this without typecasting, but the compiler will warn you unless the pointer was originally declared to be of type pointer to void. And in C, but not C++, you can assign a void* pointer to a non-void* pointer. If type is any predefined or user-defined type, including void, the declara tion Warning! You need to initialize pointers before using them. type *ptr; /* Danger--uninitialized pointer */ declares ptr to be of type "pointer to type." All the scoping, duration, and visibility rules apply to the ptr object just declared. A null pointer value is an address that is guaranteed to be different from any valid pointer in use in a program. Assigning the integer constant 0 to a pointer assigns a null pointer value to it. The mnemonic NULL (defined in the standard library header files, such as stdio.h) can be used for legibility. All pointers can be successfully tested for equality or inequality to NULL. The pointer type "pointer to void" must not be confused with the null pointer. The declaration void *vptr; declares that vptr is a generic pointer capable of being assigned to by any "pointer to type" value, including null, without complaint. Assignments without proper casting between a "pointer to type1" and a "pointer to type2," where type1 and type2 are different types, can invoke a compiler warning or error. If type1 is a function and type2 isn't (or vice versa), pointer assignments are illegal. If type1 is a pointer to void, no cast is needed. Under C, if type2 is a pointer to void, no cast is needed. Assignment restrictions also apply to pointers of different sizes (near, far, and huge). You can assign a smaller pointer to a larger one without error, but you can't assign a larger pointer to a smaller one unless you are using an explicit cast. For example, 56 Borland C++ Programmer's Guide char char char fcp hcp fcp ncp ncp Pointers and constants near *ncp; far *fcp; huge *hcp; ncp; fcp; hcp; fcp; (char near*)fcp; II II II II II legal legal not legal not legal now legal A pointer or the pointed-at object can be declared with the canst modifier. Anything declared as a canst cannot be assigned to. It is also illegal to create a pointer that might violate the nonassignability of a constant object. Consider the following examples: int i·, II i is an int int * pi; (uninitialized) II pi is a pointer to int int * const cp const int ci &i; = 7', const int * pci; const int * const cpc II cp is a constant pointer to int. II ci is a constant int II pci is a pointer to constant int = &ci; II cpc is a constant pointer to a II constant int The following assignments are legal: i = *cp ci; = ci; = II Assign const-int to Ilobject-pointed-at-by-a-const-pointer II Increment a pointer-to-const ttpci; pci II Assign const-int to int cpc; II Assign a const-pointer-to-a-const to a II pointer-to-const The following assignments are illegal: ci = 0; II NO--cannot assign to a const-int ci--; II NO--cannot change a const-int *pci = 3 ,. II NO--cannot assign to an object II pointed at by pointer-to-const cp II NO--cannot assign to a const-pointer, II even if value would be unchanged &ci; CpCtt ; Chapter 2, Language structure II NO--cannot change const-pointer 57 pi = pci; II NO--if this assignment were allowed, II you would be able to assign to *pci II (a const value) by assigning to *pi. Similar rules apply to the volatile modifier. Note that canst and volatile can both appear as modifiers to the same identifier. Pointer arithmetic The internal arithmetic performed on pointers depends on the memory model in force and the presence of any overriding pointer modifiers. The difference between two pointers only has meaning if both pointers point into the same array. Pointer arithmetic is limited to addition, subtraction, and comparison. Arithmetical operations on object pointers of type "pointer to type" automatically take into account the size of type; that is, the number of bytes needed to store a type object. When performing arithmetic with pointers, it is assumed that the pointer points to an array of objects. Thus, if a pointer is declared to point to type, adding an integral value to the pointer advances the pointer by that number of objects of type. If type has size 10 bytes, then adding an integer 5 to a pointer to type advances the pointer 50 bytes in memory. The difference has as its value the number of array elements separating the two pointer values. For example, if ptrl points to the third element of an array, and ptr2 points to the tenth element, then the result of ptr2 - ptrl would be7. When an integral value is added to or subtracted from a "pointer to type," the result is also of type "pointer to type." There is no such element as "one past the last element", of course, but a pointer is allowed to assume such a value. If P points to the last array element, P + 1 is legal, but P + 2 is undefined. If P points to one past the last array element, P - 1 is legal, giving a pointer to the last element. However, applying the indirection operator * to a "pointer to one past the last element" leads to undefined behavior. Informally, you can think of P + n as advancing the pointer by (n * sizeof(type» bytes, as long as the pointer remains within the legal range (first element to one beyond the last element). Subtracting two pointers to elements of the same array object gives an integral value of type ptrdiff_t defined in stddef.h (signed long for huge and far pointers; signed int for all others). This value represents the difference between the subscripts of the two referenced elements, provided it is in the range of ptrdiff_t. In the expression Pl - P2, where Pl and P2 are of type pointer to type (or pointer to qualified type), Pl and P2 must point to existing. elements or to one past the last element. If Pl points to the i-th ,/ 58 Borland C++ Programmer's Guide element, and P2 points to the j-th element, Pl - P2 has the value (i - j). Pointer conversions Pointer types can be converted to other pointer types using the typecasting mechanism: char *stri int *ipi str = (char *)ipi More generally, the cast (type*) will convert a pointer to type "pointer to type." c++ reference declarations c++ reference types are closely related to pointer types. Reference types create aliases for objects and let you pass arguments to functions by reference. C passes arguments only by value. In C++ you can pass arguments by value or by reference. See page 105, "Referencing," for complete details. Arrays The declaration type declarator [ ] declares an array composed of elements of type. An array consists of a contiguous region of storage exactly large enough to hold all of its elements. If an expression is given in an array declarator, it must evaluate to a positive constant integer. The value is the number of elements in the array. Each of the elements of an array is numbered from 0 through the number of elements minus one. Multidimensional arrays are constructed by declaring arrays of array type. Thus, a two-dimensional array of five rows and seven columns called alpha is declared as type alpha [5] [7] i In certain contexts, the first array declarator of a series may have no expression inside the brackets. Such an array is of indeter- Chapter 2, Language structure 59 minate size. The contexts where this is legitimate are ones in which the size of the array is not needed to reserve space. For example, an extern declaration of an array object does not need the exact dimension of the array, nor does an array function parameter. As a special extension to ANSI C, Borland C++ also allows an array of indeterminate size as the final member of a structure. Such an array does not increase the size of the structure, except that padding can be added to ensure that the array is properly aligned. These structures are normally used in dynamic allocation, and the size of the actual array needed must be explicitly added to the size of the structure in order to properly reserve space. Except when it is the operand of a sizeof or & operator, an array type expression is converted to a pointer to the first element of the array. Functions Functions are central to C and C++ programming. Languages such as Pascal distinguish between procedure and function. Borland C++ functions play both roles. Declarations and definitions Each program must have a single external function named main marking the entry point of the program. Functions are usually declared as prototypes in standard or user-supplied header files, or within program files. Functions are external by default and are normally accessible from any file in the program. They can be re- , stricted by using the static storage class specifier (see page 31). Functions are defined in your source files or made available by linking precompiled libraries. /n c++ you must a/ways use function prototypes. We recommend that you a/so use them in C. A given function can be declared several times in a program, provided the declarations are compatible. Nondefining function declarations using the function prototype format provide Borland C++ with detailed parameter information, allowing better control over argument number and type checking, and type conversions. Excluding C++ function overloading, only one definition of any given function is allowed. The declarations, if any, must also match this definition. (The essential difference between a 60 Borland C++ Programmer's Guide definition and a declaration is that the definition has a function body.) Declarations and prototypes In the original Kernighan and Ritchie style of declaration, a function could be implicitly declared by its appearance in a function call, or explicitly declared as follows: In c++, this declaration means func(void) funcO where type is the optional return type defaulting to int. A function can be declared to return any type except an array or function type. This approach does not allow the compiler to check that the type or number of arguments used in a function call match the declaration. You can enable a warning within the IDE or with the command-line compiler: .. Function called without a prototype. " This problem was eased by the introduction of function prototypes with the following declaration syntax: func(parameter-declarator-list); Declarators specify the type of each function parameter. The compiler uses this information to check function calls for validity. The compiler is also able to coerce arguments to the proper type. Suppose you have the following code fragment: extern long lmax(long vI, long v2); /* prototype */ faa () { int limit = 32; char ch = -' A' ; long mval; mval = lmax(limit,ch); /* function call */ Since it has the function prototype for Imax, this program converts limit and ch to long, using the standard rules of assignment, before it places them on the stack for the call to Imax. Without the function prototype, limit and ch would have been placed on the stack as an integer and a character, respectively; in that case, the stack passed to Imax would not match in size or content what Imax was expecting, leading to problems. The classic declaration style does not allow any checking of parameter type or number, so using function prototypes aids greatly in tracking down programming errors. Chapter 2, Language structure 61 Function prototypes also aid in documenting code. For example, the function strcpy takes two parameters: a source string and a destination string. The question is, which is which? The function prototype char *strcpy(char *dest, const char *source}; makes it clear. If a header file contains function prototypes, then you can print that file to get most of the information you need for writing programs that call those functions. If you include an identifier in a prototype parameter, it is only used for any later error messages involving that parameter; it has no other effect. A function declarator with parentheses containing the single word void indicates a function that takes no arguments at all: func(void); ~ stdarg.h contains macros that you can use in userdefined functions with variable numbers of parameters. In C++, funcO also declares a function taking no arguments. A function prototype normally declares a function as accepting a fixed number of parameters. For functions that accept a variable number of parameters (such as printf), a function prototype can end with an ellipsis (. .. ), like this: f(int *count, long total, ... } With this form of prototype, the fixed parameters are checked at compile time, and the variable parameters are passed with no type checking. Here are some more examples of function declarators and prototypes: int f () ; /* In C, a function returning an int with no information about parameters. This is the K&R "classic style." */ int f () ; /* In int f (void) ; /* A function returning an int that takes no parameters. */ int p(int,long}; /* A function returning an int that accepts two parameters: the first, an inti the second, a long. */ int pascal q(void}; /* A pascal function returning an int that takes no parameters at all. */ Ctt, a function taking no arguments */ char far *s(char *source, int kind}; /* A function returning a far pointer to a char and accepting two parameters: the first, a pointer to a char; the second, an into */ 62 Borland C++ Programmer's Guide int printf(char *format, ... ); /* A function returning an int and accepting a pointer to a char fixed parameter and any number of additional parameters of unknown type. */ int (*fp) (int); /* A pointer to a function returning an int and accepting a single int parameter. */ Definitions The general syntax for external function definitions is given in the following table: Table 2,9 External function definitions file external-definition file external-definition external-definition: function-definition declaration asm-statement function-definition: declarator compound-statement In general, a function definition consists of the following sections (the grammar allows for more complicated cases): You can mix elements from 1 and 2, 1. Optional storage class specifiers: extern or static. The default is extern. 2. A return type, possibly void. The default is int. 3. Optional modifiers: pascal, cdecl, interrupt, near, far, huge, _export, _Ioadds, _saveregs. The defaults depend on the memory model and compiler option settings. 4. The name of the function. 5. A parameter declaration list, possibly empty, enclosed in parentheses. In C, the preferred way of showing an empty list is func(void). The old style of funcO is legal in C but antiquated and possibly unsafe. 6. A function body representing the code to be executed when the function is called. Chapter 2, Language structure 63 Formal parameter declarations The formal parameter declaration list follows a similar syntax to that of the declarators found in normal identifier declarations. Here are a few examples: int func(void) ( II no args int func(Tl tl, T2 t2, T3 t3=1) ( II three simple parameters, one II with default argument int func(Tl* ptrl, T2& tref) int func(register int i) ( II a pointer and a reference arg II request register for arg int func(char *str, ... ) ( 1* one string arg with a variable number of other args, or with a fixed number of args with varying types *1 ~ In C++, you can give default arguments as shown. Parameters with default values must be the last arguments in the parameter list. The arguments' types can be scalars, structures, unions, enumerations; pointers or references to structures and unions; or pointers to functions or classes. The ellipsis (. .. ) indicates that the function will be called with different sets of arguments on different occasions. The ellipsis can follow a sublist of known argument declarations. This form of prototype reduces the amount of checking the compiler can make. The parameters declared all enjoy automatic scope and duration for the duration of the function. The only legal storage class specifier is register. The canst and volatile modifiers can be used with formal argument declarators. Function calls and argument conversions A function is called with actual arguments placed in the same sequence as their matching formal arguments. The actual arguments are converted as if by initialization to the declared types of the formal arguments. Here is a summary of the rules governing how Borland C++ deals with language modifiers and formal parameters in function calls, both with and without prototypes: 64 Borland C++ Programmer's Guide 1. The language modifiers for a function definition must match the modifiers used in the declaration of the function at all calls to the function. 2. A function may modify the values of its formal parameters, but this has no effect on the actual arguments in the calling routine, except for reference arguments in C++. When a function prototype has not been previously declared, Borland C++ converts integral arguments to a function call according to the integral widening (expansion) rules described in the section "Standard conversions," starting on page 41. When a function prototype is in scope, Borland C++ converts the given argument to the type of the declared parameter as if by assignment. When a function prototype includes an ellipsis ( ... ), Borland C++ converts all given function arguments as in any other prototype (up to the ellipsis). The compiler widens any arguments given beyond the fixed parameters, according to the normal rules for function arguments without prototypes. If a prototype is present, the number of arguments must match (unless an ellipsis is present in the prototype). The types need only be compatible to the extent that an assignment can legally convert them. You can always use an explicit cast to convert an argument to a type that is acceptable to a function prototype. Important! If your function prototype does not match the actual function definition, Borland C++ will detect this if and only if tha t definition is in the same compilation unit as the prototype. If you create a library of routines with a corresponding header file of prototypes, consider including that header file when you compile the library, so that any discrepancies between the prototypes and the actual definitions will be caught. C++ provides type-safe linkage, so differences between expected and actual parameters will be caught by the linker. Structures Structure initialization is discussed on page 42. A structure is a derived type usually representing a user-defined collection of named members (or components). The members can be of any type, either fundamental or derived (with some restrictions to be noted later), in any sequence. In addition, a structure Chapter 2, Language structure 65 member can be a bit field type not allowed elsewhere. The Borland C++ structure type lets you handle complex data structures almost as easily as single variables. ~ . In C++, a structure type is treated as a class type (with certain differences: Default access is public, and the default for the base class is also public). This allows more sophisticated control over access to structure members by using the C++ access specifiers: public (the default), private, and protected. Apart from these optional access mechanisms, and from exceptions as noted, the following discussion on structure syntax and usage applies equally to C and C++ structures. Structures are declared using the keyword struct. For example, struct mystruct { .. , }i II mystruct is the structure tag struct mystruct s, *ps, arrs[lOli 1* s is type struct mystructi ps is type pointer to struct mystruct; arrs is array of struct mystruct. *1 Untagged structures and If you omit the structure tag, you can get an untagged structure. typedefs You can use untagged structures to declare the identifiers in the comma-delimited struct-id-list to be of the given structure type (or derived from it), but you cannot declare additional objects of this type elsewhere: Untagged structure and union members are ignored during initialization. struct { ... } s, *ps, arrs[lOli II untagged structure It is possible to create a typedef while declaring a structure, with or without a tag: typedef struct mystruct { ... } MYSTRUCTi MYSTRUCT s, *ps, arrs[lOli II same as struct mystruct s, etc. typedef struct { ... } YRSTRUCTi II no tag YRSTRUCT y, *yp, arry[201i You don't usually need both a tag and a typedef: Either can be used in structure declarations. Structure member declarations The member-decl-list within the braces declares the types and names of the structure members using the declarator syntax shown in Table 2.2 on page 36. 66 Borland C++ Programmer's Guide A structure member can be of any type, with two exceptions: 1. The member type cannot be the same as the struct type being currently declared: You can omit the struct keyword in C++. struct mystruct { mystruct s } sl, s2; II illegal A member can be a pointer to the structure being declared, as in the following example: struct mystruct { mystruct *ps } sl, s2; II OK Also, a structure can contain previously defined structure types when declaring an instance of a declared structure. 2. Except in C++, a member cannot have the type "function returning ... ," but the type "pointer to function returning ... " is allowed. In C++, a struct can have member functions. Structures and functions A function can return a structure type or a pointer to a structure type: mystruct func1(void); II func1() returns a structure mystruct *func2(void); II func2() returns pointer to structure A structure can be passed as an argument to a function in the following ways: void func1(mystruct s); void func2(mystruct *sptr); void func3(mystruct &sref); Structure member access II directly II via a pointer II as a reference (ett only) Structure and union members are accessed using the selection operators. and ->. Suppose that the object s is of struct type 5, and sptr is a pointer to S. Then if m is a member identifier of type M declared in 5, the expressions s.m and sptr->m are of type M, and both represent the member object min s. The expression sptr->m is a convenient synonym for (* sptr) . m. The operator. is called the direct member selector; the operator -> is called the indirect (or pointer) member selector; for example, struct mystruct { int i; char str[21]; double d; Chapter 2, Language structure 67 } s, *sptr=&s; s.i = 3; sptr->d = 1.23; II assign to the i member of mystruct s II assign to the d member of mystruct s The expression s.m is an lvalue, provided that s is not an lvalue and m is not an array type. The expression sptr->m is an lvalue unless m is an array type. If structure B contains a field whose type is structure A, the members of A can be accessed by two applications of the member selectors: struct A int j; double x; }; struct B { int i; struct A ai double d; s, *sptr; s.i = 3; s.a.j = 2; sptr->d = 1.23; (sptr->a).x = 3.14 II II II II assign assign assign assign to to to to the i member of B the j member of A the d member of B x member of A Each structure declaration introduces a unique structure type, so that in struct A int i,j; double d; a, al; struct B { int i,j; double d; b; the objects a and al are both of type struct A, but the objects a and b are of different structure types. Structures can be assigned only if the source and destination have the same type: a = al; II OK: same type, so member by member assignment a = b; II ILLEGAL: different types a.i = b.i; a.j = b.j; a.d = b.d 1* but you can assign member-by-member *1 68 Borland C++ Programmer's Guide Structure word alignment Memory is allocated to'a structure member-by-member from left to right, from low to high memory address. In this example, struct mystruct { int i; char str[21]; double d; s; the object s occupies sufficient memory to hold a 2-byte integer, a 21-byte string, and an 8-byte double. The format of this object in memory is determined by the Borland C++ word alignment option. With this option off (the default), s will be allocated 31 contiguous bytes. If you turn on word alignment with the Options I Compiler I Code Generation dialog box or with the -a compiler option, Borland C++ pads the structure with bytes to ensure the structure is aligned as follows: 1. The structure will start on a word boundary (even address). 2. Any non-char member will have an even byte offset from the start of the structure. 3. A final byte is added (if necessary) at the end to ensure that the whole structure contains an even number of bytes. With word alignment on, the structure would therefore have a byte added before the double, making a 32-byte object. Structure name spaces Structure tag names share the same name space with union tags and enumeration tags (but enums within a structure are in a different name space in C++). This means that such tags must be uniquely named within the same scope. However, tag names need not differ from identifiers in the other three name spaces: the label name space, the member name space(s), and the single name space (which consists of variables, functions, typedef names, and enumerators). Chapter 2, Language structure 69 Member names within a given structure or union must be unique, but they can share the names of members in other structures or unions. For example, goto s; s: struct s int s; float s; s; union s int s; float f; f; struct t int s; s; Incomplete declarations II II II II II OK: tag and label name spaces different OK: label, tag and member name spaces different ILLEGAL: member name duplicated OK: var name space different. In Ctt, this can only be done if s does not have a constructor. II ILLEGAL: tag space duplicate II OK: new member space II OK: var name space II OK: different member spaceII ILLEGAL: var name duplicate A pointer to a structure type A can legally appear in the declaration of another structure B before A has been declared: struct A; II incomplete struct B { struct A *pa }i struct A { struct B *pb }; The first appearance of A is called incomplete because there is no definition for it at that point. An incomplete declaration is allowed here, since the definition of B doesn't need the size of A. Bit fields A structure can contain any mixture of bit field and nonbit field types. You can declare signed or unsigned integer members as bit fields from 1 to 16 bits wide. You specify the bit field width and optional identifier as follows: type-specifier : width; where type-specifier is char, unsigned char, int, or unsigned into Bit fields are allocated from low-order to high-order bits within a word. The expression width must be present and must evaluate to . a constant integer in the range 1 to 16. 70 Borland C++ Programmer's Guide If the bit field identifier is omitted, the number of bits specified in width is allocated, but the field is not accessible. This lets you match bit patterns in, say, hardware registers where some bits are unused. For example, struct mystruct { int 2; unsigned 5; int 4; int k 1i unsigned m 4; a, b, Ci produces the following layout: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 x x x x x x x x x x x x x x x x . ~~ m k " (unused) ,... ... ... J .I .... ~ j i Integer fields are stored in 2's-complement form, with the leftmost bit being the MSB (most significant bit). With int (for example, Signed) bit fields, the MSB is interpreted as a sign bit. A bit field of width 2 holding binary II, therefore, would be interpreted as 3 if unsigned, but as -1 if int. In the previous example, the legal assignment a. i = 6 would leave binary 10 =-2 in a.i with no warning. The signed int field k of width 1 can hold only the values -1 and 0, since the bit pattern 1 is interpreted as -l. '- Bit fields can be declared only in structures, unions, and classes. They are accessed with the same member selectors ( . and -» used for non-bit field members. Also, bit fields pose several problems when writing portable code, since the organization of bitswithin-bytes and bytes-within-words is machine dependent. The expression &mystruct.x is illegal if x is a bit field identifier, since there is no guarantee that mystruct.x lies at a byte address. Unions Unions correspond to the variant record types of Pascal and Modula-2. Union types are derived types sharing many of the syntactical and functional features of structure types. The key difference is that a union allows only one of its members to be "active" at any one time. The size of a union is the size of its largest member. The Chapter 2, Language structure 71 value of only one of its members can be stored at any time. In the following simple case, union myunion int i; double d; char Chi } mu, *muptr=μ 1* union tag = myunion *1 the identifier mu, of type union myunion, can be used to hold a 2byte int, an 8-byte double, or a single-byte char, but only one of these at the same time. sizeof(union myunion) and sizeof(mu) both return 8, but 6 bytes are unused (padded) when mu holds an int object, and 7 bytes are unused when mu holds a char. You access union members with the structure member selectors (. and -», but care is needed: mu.d = 4.016; printf(" mu .d = %f\n",mu.d}; printf("mu .i = %d\n",mu.i}; mu.ch = I A'; printf(" mu . ch = %c\n",mu.ch}; printf("mu.d = %f\n",mu.d}; muptr->i = 3; printf(" mu .i = %d\n",mu.i); II OK: displays mu.d = 4.016 II peculiar result II OK: displays mu.ch /1 peculiar result =A II OK: displays mu.i = 3 The second printf is legal, since mu.i is an integer type. However, the bit pattern in mu.i corresponds to parts of the double previously assigned, and will not usually provide a useful integer . interpretation. When properly converted, a pointer to a union points to each of its members, and vice versa. Anonymous unions (C++ only) ~ A union that doesn't have a tag and is not used to declare a named object (or other type) is called an anonymous union. It has the following form: union { member-list }; Its members can be accessed directly in the scope where this union is declared, without using the x.y or p->y syntax. 72 Borland c++ Programmer's Guide Anonymous unions can't have member functions and at file level must be declared static. In other words, an anonymous union may not have external linkage. Union declarations ©$> The general declaration syntax for unions is pretty much the same as that for structures. Differences are 1. Unions can contain bit fields, but only one can be active. They all start at the beginning of the union (and remember that, because bit fields are machine dependent, they pose several problems when writing portable code). 2. Unlike c++ structures, c++ union types cannot use the class access specifiers: public, private, and protected. All fields of a union are public. 3. Unions can be initialized only through their first declared member: union loca187 int i; double d; a = { 20 }; ©$> 4. A union can't participate in a class hierarchy. It can't be derived from any class, nor can it be a base class. A union can have a constructor. Enumerations An enumeration data type is used to provide mnemonic identifiers for a set of integer values. For example, the following declara tion, enum days { sun, mon, tues, wed, thur, fri, sat} anyday; establishes a unique integral type, enum days, a variable anyday of this type, and a set of enumerators (sun, man, ... ) with constant integer values. Borland C++ is free to store enumerators in a single byte when Treat enums as ints is unchecked (0 I C I Code Generation) or the -b flag. The default is on (meaning enums are always ints) if the range of values permits, but the value is always promoted to an int when used in expressions. The identifiers used in an Chapter 2, Language structure 73 enumerator list are implicitly of type signed char, unsigned char, or int, depending on the values of the enumerators. If all values can be represented in a signed or unsigned char, that is the type of each enumerator. ~ In C, a variable of an enumerated type can be assigned any value of type int-no type checking beyond that is enforced. In C++, a variable of an enumerated type can be assigned only one of its enumerators. That is, anyday anyday = moni = 1i II OK II illegal, even though mon == 1 The identifier days is the optional enumeration tag that can be used in subsequent declarations of enumeration variables of type en urn days: enum days payday, holidaYi II declare two variables ~ In C++, you can omit the enurn keyword if days is not the name of anything else in the same scope. As with struct and union declarations, you can omit the tag if no further variables of this enurn type are required: enum { sun, mon, tues, wed, thur, fri, sat} anydaYi 1* anonymous enum type *1 See page 17 for more on enumeration constants. The enumerators listed inside the braces are also known as enumeration constants. Each is assigned a fixed integral value. In the absence of explicit initializers, the first enumerator (sun) is set to zero, and each succeeding enumerator is set to one more than its predecessor (mon = 1, tues = 2, and so on). With explicit integral initializers, you can set one or more enumerators to specific values. Any subsequent names without initializers will then increase by one. For example, in the following declaration, 1* initializer expression can include previously declared enumerators *1 enum coins { penny = 1, tuppence, nickel = penny +4, dime quarter = nickel * nickel } smallchangei = 10, tuppence would acquire the value 2, nickel the value 5, and quarter the value 25. The initializer can be any expression yielding a positive or negative integer value (after possible integer promotions). These values are usually unique, but duplicates are legal. 74 Borland C++ Programmer's Guide enum types can appear wherever int types are permitted. enum days { sun, man, tues, wed, thur, fri, sat} anydaYi enum days payday; typedef enum days DAYS; DAYS *daysptr; int i = tues; any day = man; /I OK *daysptr = anyday; /I OK II ILLEGAL: man is a constant man = tues; Enumeration tags share the same name space as structure and union tags. Enumerators share the same name space as ordinary variable identifiers: int man = 11; enum days { sun, man, tues, wed, thur, fri, sat} anyday; 1* enumerator man hides outer declaration of int man *1 struct days { int i, j;}; II ILLEGAL: days duplicate tag double sat; II ILLEGAL: redefinition of sat man ~ = 12; II back in int mon scope In C++, enumerators declared within a class are in the scope of that class. Expressions Table 2.11 shows how identifiers and operators are combined to form grammatically legal "phrases. " The standard conversions are detailed in Table 2.6 on page 42. An expression is a sequence of operators, operands, and ~ punctuators that specifies a computation. The formal syntax, listed in Table 2.11, indicates that expressions are defined recursively: Subexpressions can be nested without formal limit. (However, the compiler will report an out-of-memory error if it can't compile an expression that is too complex.) Expressions are evaluated according to certain conversion, grouping, associativity, and precedence rules which depend on the operators used, the presence of parentheses, and the data types of the operands. The way operands and sub expressions are grouped does not necessarily specify the actual order in which they are evaluated by Borland C++ (see "Evaluation order" on page 78). Chapter 2, Language structure 75 Expressions can produce an lvalue, an rvalue, or no value. Expressions may cause side effects whether they produce a value or not. We've summarized the precedence and associativity of the operators in Table 2.10. The grammar in Table 2.11 on page 77 completely defines the precedence and associativity of the operators. There are sixteen precedence categories, some of which contain only one operator. Operators in the same category have equal precedence with each other. Where there are duplicates of operators in the table, the first occurrence is unary, the second binary. Each category has an associativity rule: left to right, or right to left. In the absence of parentheses, these rules resolve the grouping of expressions with operators of equal precedence. Table 2.10 Associativity and precedence of Borland C++ operators Precedence of each category is indicated by order in this table. The first category (the first line) has the highest precedence. Associativity Operators () [] -> :: ! - + - ++ - - &* (typecast) sizeof new delete .* ->* * 1 % + - « » < <= > >= -- != & " 1 && II ?: (conditional expression) = *= 1= 0/0= += -= &= "= 1= «= »= 76 Left to righ t Right to left Left to right Left to right Left to right Left to right Left to right Left to right Left to right Left to right Left to right Left to right Left to right Right to left Right to left Left to right Borland C++ Programmer's Guide Table 2.11: Borland C++ expressions primary-expression: literal this (C++ specific) :: identifier (C++ specific) :: operator-fllnction-name (C++ specific) ::qllalified-name (C++ specific) (expression) name literal: integer-constant c/zaracter-collstall t floating-constant string-literal name: identifier operator-function-name (C++ specific) conversion-function-name (C++ specific) - class-name qualified-name (C++ specific) qllalified-name: (C++ specific) qualified-class-name:: name ( type-name) cast-expression pm-expression: cast-expressioll pill-expression .* cast-expressioll (C++ specific) pm-expression ->* cast-expression (C++ specific) mllitiplicative-expression: pm-expression mllltiplicative-expression * plll-expressioll multiplicative-expression 1 pm-expression mllitiplicative-expression % pill-expression additive-expression: mllitiplicative-expression additive-expression + mllltiplicative-expressioll additive-expression - multiplicative-expressioll shift-expression: additive-expression slzift-expression « additive-expressioll shift-expression » additive-expression reiational-expression: shift-expression reiational-expression reiational-expression reiational-expression relational-expression postfix-expression: primary-expression postfix-expressioll [expression 1 postfix-expressioll «expressioll-list» simple-type-name «expression-list» (C++ specific) postfix-expression . name postfix-expression -> Ilame postfix-expression ++ postfix-expression -- eq lIality-expression: reiational-expression eqllalityexpression == relational-expression eqllality expression != relational-expression expressioll-list: assignment-expression expression-list , assignment-expressioll AND-expressioll: eqllality-expression AND-expression & eqllality-expression lInary-expression: postfix-expression ++ lInary-expression - - lInary-expression lInary-operator cast-expression sizeof unary-expression sizeof ( type-name) al/ocation-expression (C++ specific) deal/ocation-expression (C++ specific) exclusive-OR-expression: AND-expression exclusive-OR-expression lIllary-operator: one of & • + al/ocation-expression: (C++ specific) <::> new new-type-name <::> new (type-name) placement: (C++ specific) ( expression-list) new-type-name: (C++ specific) type-specifiers new-declarator: (C++ specific) ptr-operator new-declarator [ 1 deal/ocation-expression: (C++ specific) <::> delete cast-expression <::> delete [ 1cast-expression cast-expression: unary-expression Chapter 2, Language structure < shift-expressioll > slzift-expression <= shift-expression >= shift-expression 1\ AND-expression inclusive-OR-expressioll: exclusive-OR -expression illclusive-OR-expressioll I exclllsive-OR-expression logical-AND-expression: inclusive-OR-expression logical-AND-expression && inclllsive-OR-expression logical-OR-expression: logica I-AN D-expression logical-OR-expressioll II logical-AND-expression conditional-expression: logical-OR-expression logical-OR-expression ? expressioll : conditiollal-expression assignment -expression: collditiollal-expression unary-expression assignment-operator assignment-expression assigmnent-operator: one of 1= += &= 1= «= »= expression: assignmellt-expression expression, assigllment-expression constallt-expression: conditional-expression 77 Expressions and C++ c++ allows the overloading of certain standard C operators, as explained starting 'on page 136. An overloaded operator is defined to behave in a special way when applied to expressions of class type. For instance, the relational operator == might be defined in the class complex to test the equality of two complex numbers without changing its normal usage with non-class data types. An overloaded operator is implemented as a function; this function determines the operand type,lvalue, and evaluation order to be applied when the overloaded operator is used. However, overloading cannot change the precedence ~t an operator. Similarly, C++ allows user-defined conversions between class objects and fundamental types. Keep in mind, then, that some of the rules for operators and conversions discussed in this section may not apply to expressions in C++. Evaluation order The order in which Borland C++ evaluates the operands of an expression is not specified, except where an operator specifically states otherwise. The compiler will try to rearrange the expression in order to improve the quality of the generated code. Care is therefore needed with expressions in which a value is modified more than once. In general, avoid writing expressions that both modify and use the value of the same object. Consider the expression i = v[i++j; II i is undefined The value of i depends on whether i is incremented before or after the assignment. Similarly, int total = 0; sum = (total = 3) + (++total)i II sum = 4 or sum = 7 ?? is ambiguous for sum and total. The solution is to revamp the expression, using a temporary variable: int temp, total = Oi temp = tttotal; sum = (total = 3) + tempi Where the syntax does enforce an evaluation sequence, it is safe to have multiple evaluations: sum 78 = (i = 3, itt, itt); I I OK: sum = 4, i =5 Borland C++ Programmer's Guide Each subexpression of the comma expression is evaluated from left to right, and the whole expression evaluates to the rightmost value. Borland C++ regroups expressions, rearranging associative and commutative operators regardless of parentheses, in order to create an efficiently compiled expression; in no case will the rearrangement affect the value of the expression. You can use parentheses to force the order of evaluation in expressions. For example, if you have the variables a, b, c, and I, then the expression I = a + (b + c) forces (b + c) to be evaluated before adding the result to a. Errors and overflows See math err and signal in the Library Reference. We've summarized the precedence and associativity of the operators in Table 2.10. During the evaluation of an expression, Borland C++ can encounter many problematic situations, such as division by zero or out-of-range floating-point values. Integer overflow is ignored (C uses modulo 211 arithmetic on n-bit registers), but errors detected by math library functions can be handled by standard or user-defined routines. Operator semantics The Borland C++ operators described here are the standard ANSI C operators. Unless the operators are overloaded, the following information is true in both C and C++. In C++ you can overload all of these operators with the exception of . (member operator) and ?: (conditional operator) (and you also can't overload the C++ operators :: and .*). . If an operator is overloaded, the discussion may not be true for it anymore. Table 2.11 on page 77 gives the syntax for all operators and operator expressions. Operator descriptions Operators are tokens that trigger some computation when applied to variables and other objects in an expression. Borland C++ is especially rich in operators, offering not only the common arithmetical and logical operators, but also many for bit-level Chapter 2, Language structure 79 manipulations, structure and union component access, and pointer operations (referencing and dereferencing). ~ Overlo?ding is covered startIng on page 735. C++ extensions offer additional operators for accessing class members and their objects, together with a mechanism for overloading operators. Overloading lets you redefine the action of any standard operators when applied to the objects of a given class. In this section, we confine our discussion to the standard operators of Borland c++. After defining the standard operators, we discuss data types and declarations, and explain how these affect the actions of each operator. From therewe can proceed with the syntax for building expressions from operators, punctuators, and objects. The operators in Borland c++ are defined as follows: operator: one of The operators # and ## are used only by the preprocessor (see page 757). -> ++ % « >= -- » != = «= < A *= »= # ## [] () & * + sizeof 1 > 1 <= && %= A= 1= &= " += 1= ?: -= And the following operators specific to C++: * ->* Except for [], (), and ?:, which bracket expressions, the multi character operators are considered as single tokens. The same operator token can have more than one interpretation, depending on the context. For example, A* B *ptr Multiplication Dereference (indirection) A &B Bitwise AND Address operation Reference modifier (C++) &A int & Statement label Conditional statement label: a ? x : y void func(int a = (b+c) *di a, b, 80 Ci n)i Function declaration Parenthesized expression Comma expression Borland c++ Programmer's Guide fune(a, b, Function call e)i a = -bi -fune() {delete ai} Bitwise negation (one's complement) Destructor (C++) Unary operators & * + ++ Address opera tor Indirection operator Unary plus Unary minus Bitwise complement (1's complement) Logical negation Prefix: preincrement; Postfix: postincrement Prefix: predecrement; Postfix: postdecrement Binary operators Additive operators + Binary plus (addition) Binary minus (subtraction) Multiplicative operators * 1 % Multiply Divide Remainder Shift operators « Shift left Shift right » Bitwise operators & A Logical operators && 1\ Assignment operators = *= 1= 0/0= += -= Chapter 2, Language structure Bitwise AND Bitwise XOR (exclusive OR) Bitwise inclusive OR Logical AND Logical OR Assignment Assign product Assign quotient Assign remainder (modulus) Assign sum Assign difference 81 «= »= 1= Assign left shift Assign right shift Assign bitwise AND Assign bitwise XOR Assign bitwise OR < > <= >= Less than Grea ter than Less than or equal to Greater than or equal to != Equal to Not equal to -> Direct component selector Indirect component selector &= Relational operators Equality operators Component selection operators Class-member operators Conditional operator ->* Scope access/ resolution Dereference pointer to class member Dereference pointer to class member a? x: y "if a then x; else y" * Comma operator Evaluate; e.g., a, b, c; from left to right The operator functions, as well as their syntax, precedences, and associativities, are covered starting on page 75. Postfix and prefix operators Array subscript operator [] The six postfix operators [] () . -> ++ and - - are used to build postfix expressions as shown in the expressions syntax table (Table 2.11). The increment and decrement operators (++ and --) are also prefix and unary operators; they are discussed starting oil page 84. In the expression postfix-expression [expression] either postfix-expression or expression must be a pOinter and the other an integral type. 82 Borland C++ Programmer's Guide In C, but not necessarily in C++, the expression exp1[exp2J is defined as * ((expl) + (exp2)) where either expl is a pointer and exp2 is an integer, or expl is an integer and exp2 is a pointer. (The punctuators [ ], *, and + can be individually overloaded in C++.) Function call operators ( ) The expression postfix-expression ( In the expression postfix-expression. identifier the postfix expression must be of type structure or union; the identifier must be the name of a member of that structure or union type. The expression designates a member of a structure or union object. The value of the expression is the value of the selected member; it will be an lvalue if and only if the postfix expression is an lvalue. Detailed examples of the use of . and -> for structures are given on page 67. In the expression postfix-expression -> identifier the postfix expression must be of type pointer to structure or pointer to union; the identifier must be the name of a member of that structure or union type. The expression designates a member of a structure or union object. The value of the expression is the value of the selected member; it will be an lvalue if and only if the postfix expression is an lvalue. Chapter 2, Language structure 83 Postfix increment operator ++ In the expression postfix-expression ++ the postfix expression is the operand; it must be of scalar type (arithmetic or pointer types) and must be a modifiable lvalue (see page 26 for more on modifiable lvalues). The postfix ++ is also known as the postincrement operator. The value of the whole expression is the value of the postfix expression before the increment is applied. After the postfix expression is evaluated, the operand is incremented by 1. The increment value is appropriate to the type of the operand. Pointer types are subject to the rules for pointer arithmetic. Postfix decrement operator -- Increment and decrement operators Prefix increment operator The postfix decrement, also known as the postdecrement, operator follows the same rules as the postfix increment, except that 1 is subtracted from the operand after the evaluation. The first two unary operators are ++ and - -. These are also postfix and prefix operators, so they are discussed here. The remaining six unary operators are covered following this discussion. In the expression ++ unary-expression the unary expression is the operand; it must be of scalar type and must be a modifiable lvalue. The prefix increment operator is also known as the preincrement operator. The operand is incremented by 1 before the expression is evaluated; the value of the whole expression is the incremented value of the operand. The 1 used to increment is the appropriate value for the type of the operand. Pointer types follow the rules of pointer arithmetic. Prefix decrement operator The prefix decrement, also known as the predecrement, operator has the following syntax: - - unary-expression 84 Borland C++ Programmer's Guide It follows the same rules as the prefix increment operator, except that the operand is decremented by 1 before the whole expression is evaluated. Unary operators The six unary operators (aside from ++ and - -) are & * + - and !. The syntax is unary-operator cast-expression cast-expression: unary-expression (type-name) cast-expression Address operator & The symbol & is also used in c++ to specify reference types: see page 705. The & operator and * operator (the * operator is described in the next section) work together as the referencing and dereferencing operators. In the expression & cast-expression the cast-expression operand must be either a function designator or an lvalue designating an object that is not a bit field and is not declared with the register storage class specifier. If the operand is of type type, the result is of type pointer to type. Some non-lvalue identifiers, such as function names and array names, are automatically converted into "pointer to X" types when appearing in certain contexts. The & operator can be used with such objects, but its use is redundant and therefore discouraged. Consider the following extract: type t1 = 1, t2 = 2; type *ptr = &t1; II initialized pointer II same effect as tl = t2 *ptr = t2i type *ptr = &t1 is treated as T *ptr; ptr = &tii so it is ptr, not *ptr, that gets assigned. Once ptr has been initialized with the address &t1, it can be safely dereferenced to give the lvalue *ptr. Chapter 2, Language structure 85 Indirection operator * In the expression * cast-expression the cast-expression operand must have type "pointer to type," where type is any type. The result of the indirection is of type type. If the operand is of type "pointer to function," the result is a function designator; if the operand is a pointer to an object, the result is an lvalue designating that object. In the following situations, the result of indirection is undefined: 1. The cast-expression is a null pointer. 2. The cast-expression is the address of an automatic variable and execution of its block has terminated. Unary plus operator + In the expression + cast-expression the cast-expression operand must be of arithmetic type. The result is the value of the operand after any required integral promotions. Unary minus operator - In the expression - cast-expression the cast-expression operand must be of arithmetic type. The result is the negative of the value of the operand after any required integral promotions. Bitwise complement operator - In the expression - cast-expression the cast-expression operand must be of integral type. The result is the bitwise complement of the operand after any required integral promotions. Each 0 bit in the operand is set to 1, and each 1 bit in the operand is set to O. Logical negation operator! In the expression ! cast-expression the cast-expression operand must be of scalar type. The result is of type int and is the logical negation of the operand: 0 if the op- 86 Borland C++ Programmer's Guide erand is nonzero; 1 if the operand is zero. The expression fE is equivalent to (0 == E). The sizeof operator There are two distinct uses of the sizeof operator: sizeof unary-expression sizeof (type-name) How much space is set aside for each type depends on the machine. The result in both cases is an integer constant that gives the size in bytes of how much memory space is used by the operand (determined by its type, with some exceptions). In the first use, the type of the operand expression is determined without evaluating the expression (and therefore without side effects). When the operand is of type char (signed or unsigned), sizeof gives the result 1. When the operand is a non-parameter of array type, the result is the total number of bytes in the array (in other words, an array name is not converted to a pointer type). The number of elements in an array equals sizeof array I sizeof array[O]. If the operand is a parameter declared as array type or function type, sizeof gives the size of the pointer. When applied to structures and unions, sizeof gives the total number of bytes, including any padding. sizeof cannot be used with expressions of function type, incomplete types, parenthesized names of such types, or with an lvalue that designates a bit field object. The integer type of the result of sizeof is size_t, defined as unsigned int in stddef.h. You can use sizeof in preprocessor directives; this is specific to Borland C++. ~ Multiplicative operators In C++, sizeof(classtype), where class type is derived from some base class, returns the size of the .object (remember, this includes the size of the base class size). There are three multiplicative operators: * I and %. The syntax is multiplica tive-expression: cast-expression multiplicative-expression * cast-expression Chapter 2, Language structure 87 multiplicative-expression! cast-expression multiplicative-expression % cast-expression The operands for * (multiplication) and! (division) must be of arithmetical type. The operands for % (modulus, or remainder) must be of integral type. The usual arithmetic conversions are made on the operands (see page 41). The result of (opl * op2) is the product of the two operands. The results of (opl! op2) and (opl % op2) are the quotient and remainder, respectively, when opl is divided by op2, provided that op2 is nonzero. Use of ! or % with a zero second operand results in an error. When opl and op2 are integers and the quotient is not an integer, the results are as follows: Rounding is a/ways toward zero. Additive operators 1. If opl and op2 have the same sign, opl ! op2 is the largest integer less than the true quotient, and opl % op2 has the sign of opl. 2. If opl and op2 have opposite signs, opl ! op2 is the smallest integer greater than the true quotient, and opl % op2 has the sign of opl. There are two additive operators: + and -. The syntax is additive-expression: multiplicative-expression additive-expression + multiplicative-expression additive-expression - multiplicative-expression The addition operator + The legal operand types for opl + op2 are 1. Both opl and op2 are of arithmetic type. 2. opl is of integral type, and op2 is of pointer to object type. 3. op2 is of integral type, and opl is of pointer to object type. In case 1, the operands are subjected to the standard arithmetical conversions, and the result is the arithmetical sum of the operands. In cases 2 and 3, the rules of pointer arithmetic apply. (Pointer arithmetic is covered on page 58.) 88 Borland C++ Programmer's Guide The subtraction operator - The legal operand types for opl - op2 are 1. Both opl and op2 are of arithmetic type. _ 2. Both opl and op2 are pointers to compatible object types. The unqualified type type is considered to be compatible with the qualified types canst type, volatile type, and canst volatile type. 3. opl is of pointer to object type, and op2 is integral type. In case 1, the operands are subjected to the standard arithmetic conversions, and the result is the arithmetic difference of the operands. In cases 2 and 3, the rules of pointer arithmetic apply. Bitwise shift operators There are two bitwise shift operators: «and ». The syntax is shift-expression: addi tive-expression shift-expression « additive-expression shift-expression » additive-expression Bitwise shift operators and ») «< The constants ULONG_MAX and UINCMAX are defined in limits.h. In the expressions El «E2 and El » E2, the operands E1 and E2 must be of integral type. The normal integral promotions are performed on El and E2, and the type of the result is the type of the promoted El. If E2 is negative or is greater than or equal to the width in bits of El, the operation is undefined. The result of El « E2 is the value of El left-shifted by E2 bit positions, zero-filled from the right if necessary. Left shifts of an unsigned long El are equivalent to multiplying El by 2E2 , reduced modulo ULONG_MAX + 1; left shifts of unsigned ints are equivalent to multiplying by 2 E2 reduced modulo UINT_MAX + 1. If El is a signed integer, the result must be interpreted with care, since the sign bit may change. The result of El » E2 is the value of El right-shifted by E2 bit positions. If El is of unsigned type, zero-fill occurs from the left if necessary. If El is of signed type, the fill from the left uses the sign bit (0 for positive, 1 for negative El). This sign-bit extension ensures that the sign of El » E2 is the same as the sign of El. Except for signed types, the value of El » E2 is the integral part of the quotient El/2E2. Chapter 2, Language structure 89 Relational operators There are four relational operators: < > <= and >=. The syntax for these operators is: relational-expression: shift-expression relational-expression < shift-expression relational-expression> shift-expression relational-expression <= shift-expression relational-expression >= shift-expression The less-than operator < Qualified names are defined on page 117. In the expression El < E2, the operands must conform to one of the following sets of conditions: 1. Both El and E2 are of arithmetic type. 2. Both El and E2 are pointers to qualified or unqualified versions of compatible object types. 3. Both El and E2 are pointers to qualified or unqualified versions of compatible incomplete types. In case I, the usual arithmetic conversions are performed. The result of El < E2 is of type int. If the value of El is less than the value of E2, the result is 1 (true); otherwise, the result is zero (false). In cases 2 and 3, where El and E2 are pointers to compatible types, the result of El < E2 depends on the relative locations (addresses) of the two objects being pointed at. When comparing structure members within the same structure, the "higher" pointer indicates a later declaration. Within arrays, the "higher" pointer indicates a larger subscript value. All pointers to members of the same union object compare as equal. Normally, the comparison of pointers to different structure, array, or union objects, or the comparison of pointers outside the range of an array object give undefined results; however, an exception is made for the "pointer beyond the last element" situation as discussed under "Pointer arithmetic" on page 58. If P points to an element of an array object, and Q points to the last element, the expression P < Q + 1 is allowed, evaluating to 1 (true), even though Q + 1 does not point to an element of the array object. 90 Borland C++ Programmer's Guide The greater-than operator> The expression E1 > E2 gives 1 (true) if the value of E1 is greater than the value of E2; otherwise, the result is 0 (false), using the same interpretations for arithmetic and pointer comparisons, as defined for the less-than operator. The same operand rules and restrictions also apply. The less-than or equalto operator <= Similarly, the expression E1 <= E2 gives 1 (true) if the value of E1 is less than or equal to the value of E2. Otherwise, the result is 0 (false), using the same interpretations for arithmetic and pointer comparisons, as defined for the less-than operator. The same operand rules and restrictions also apply. The greater-than or equal-to operator >= Finally, the expression E1 >= E2 gives 1 (true) if the value of E1 is greater than or equal to the value of E2. Otherwise, the result is 0 (false), using the same interpretations for arithmetic and pointer comparisons, as defined for the less-than operator. The same operand rules and restrictions also apply. Equality operators .. There are two equality operators: == and !=. They test for equality and inequality between arithmetic or pointer values, following rules very similar to those for the relational operators. However, == and != have a lower precedence than the relational operators < >, <=, and >=. Also, == and != can compare certain pointer types for equality and inequality where the relational operators would not be allowed. The syntax is equality-expression: relational-expression equality-expression == relational-expression equality-expression != relational-expression The equal-to operator == In the expression E1 == £2, the operands must conform to one of the following sets of conditions: 1. Both E1 and £2 are of arithmetic type. 2. Both E1 and £2 are pointers to qualified or unqualified versions of compatible types. Chapter 2, Language structure 91 3. One of El and E2 is a pointer to an object or incomplete type, and the other is a pointer to a qualified or unqualified version of void. 4. One of El or E2 is a pointer and the other is a null pointer constant. If El and E2 have types that are valid operand types for a relational operator, the same comparison rules just detailed for El < E2, El <= E2, and so on, apply. In case I, for example, the usual arithmetic conversions are performed, and the result of El == E2 is of type int. If the value of El is equal to the value of E2, the result is 1 (true); otherwise, the result is zero (false). In case 2, El == E2 gives 1 (true) if El and E2 point to the same object, or both point "one past the last element" of the same array object, or both are null pointers. If El and E2 are pointers to function types, El == E2 gives 1 (true) if they are both null or if they both point to the same function. Conversely, if El == E2 gives 1 (true), then either El and E2 point to the same function, or they are both null. In case 4, the pointer to an object or incomplete type is converted to the type of the other operand (pointer to a qualified or unqualified version of void). The inequality operator != Bitwise AND operator & The expression El != E2 follows the same rules as those for El == E2, except that the result is 1 (true) if the operands are unequal, and 0 (false) if the operands are equal. The syntax is AND-expression: equal ity-expression AND-expression & equality-expression In the expression El & E2, both operands must be of integral type. The usual arithmetical conversions are performed on El and E2, and the result is the bitwise AND of El and E2. Each bit in the result is determined as shown in Table 2.12. 92 Borland C++ Programmer's Guide Table 2.12 Bitwise operators truth table Bit value in E1 0 1 0 1 Bitwise exclusive OR operator 1\ Bit value in E2 E1 & E2 E1 " E2 0 0 0 0 0 1 1 1 0 1 1 1 0 0 1 1 E11 E2 The syntax is exc lusive-OR-expression: AND-expression exclusive-OR-expression 1\ AND-expression In the expression El 1\ E2, both operands must be of integral type. The usual arithmetic conversions are performed on El and E2, and the result is the bitwise exclusive OR of El and E2. Each bit in the result is determined as shown in Table 2.12. Bitwise inclusive OR operator I The syntax is inclusive-OR-expression: exclusive-OR-expression inclusive-OR-expression I exclusive-OR-expression In the expression El I E2, both operands must be of integral type. The usual arithmetic conversions are performed on El and E2, and the result is the bitwise inclusive OR of El and E2. Each bit in the result is determined as shown in Table 2.12. Logical AND operator && The syntax is logical-AND-expression: inclusive-OR-expression logical-AND-expression && inclusive-OR-expression In the expression El && E2, both operands must be of scalar type. The result is of type int, the result is 1 (true) if the values of El and E2 are both nonzero; otherwise, the result is 0 (false). Chapter 2, Language structure 93 Unlike the bitwise & operator, && guarantees left-to-right evaluation. E1 is evaluated first; if E1 is zero, E1 && E2 gives 0 (false), and E2 is not evaluated. Logical OR operator I I The syntax is logical-DR-expression: logical-AND-expression logical-DR-expression Illogical-AND-expression In the expression E1 II E2, both operands must be of scalar type. The result is of type int, and the result is 1 (true) if either of the values of E1 and E2 are nonzero. Otherwise, the result is 0 (false). Unlike the bitwise I operator, II guarantees left-to-right evaluation. E1 is evaluated first; if E1 is nonzero, E1 II E2 gives 1 (true), and E2 is not evaluated. Conditional operator ?: The syntax is conditional-expression logical-DR-expression logical-DR-expression ? expression: conditional-expression In C++, the result is an Ivalue. In the expression E1 ? E2 : E3, the operand E1 must be of scalar type. The operands E2 and E3 must obey one of the following sets of rules: 1. 2. 3. 4. Both of arithmetic type Both of compatible structure or union types Both of void type Both of type pointer to qualified or unqualified versions of compatible types 5. One operand of pointer type, the other a null pointer constant 6. One operand of type pointer to an object or incomplete type, the other of type pointer to a qualified or unqualified version of void First, E1 is evaluated; if its value is nonzero (true), then E2 is evaluated and E3 is ignored. If E1 evaluates to zero (false), then E3 is 94 Borland C++ Programmer's Guide evaluated and E2 is ignored. The result of El ? E2 : E3 will be the value of whichever of E2 and E3 is evaluated. In case 1, both E2 and E3 are subject to the usual arithmetic conversions, and the type of the result is the common type resulting from these conversions. In case 2, the type of the result is the structure or union type of E2 and E3. In case 3, the result is of type void. In cases 4 and 5, the type of the result is pointer to a type qualified with all the type qualifiers of the types pointed to by both operands. In case 6, the type of the result is that of the nonpointer-to-void operand. Assignment operators = There are eleven assignment operators. The operator is the simple assignment operator; the other ten are known as compound assignment operators. The syntax is ass ignmen t-expression: condi tional-expression unary-expression assignmen t-operator assignment-expression assignment-operator: one of *= /= «= »= &= %= += "= 1= -= The simple assignment In the expression El = E2, El must be a modifiable lvalue. The operator = value of E2, after conversion to the type of El, is stored in the object designated by El (replacing El's previous value). The value of the assignment expression is the value of El after the assignment. The assignment expression is not itself an lvalue. In C++, the result is an Iva/ue. The operands El and E2 must obey one of the following sets of rules: 1. El is of qualified or unqualified arithmetic type and E2 is of arithmetic type. Chapter 2, Language structure 95 2. El has a qualified or unqualified version of a structure or union type compatible with the type of E2. 3. El and E2 are pointers to qualified or unqualified versions of compatible types, and the type pointed to by the left has all the qualifiers of the type pointed to by the right. 4. One of El or E2 is a pointer to an object or incomplete type and the other is a pointer to a qualified or unqualified version of void. The type pointed to by the left has all the qualifiers of the type pointed to by the right. 5. El is a pointer and E2 is a null pointer constant. The compound assignment operators The compound assignments op=, where op can be anyone of the ten operator symbols * I % + - « » & A I, are interpreted as follows: El op= E2 has the same effect as El =El op E2 except that the lvalue El is evaluated only once. For example, El += E2 is the same as El = El + E2. The rules for compound assignment are therefore covered in the previous section (on the simple assignment operator =). Comma operator The syntax is expression: assignment-expression expression, assignment-expression In c++, the result is an Ivalue. In the comma expression El,E2 the left operand El is evaluated as a void expression, then E2 is evaluated to give the result and type of the comma expression. By recursion, the expression El, E2, ... , En results in the left-to-right evaluation of each Ei, with the value and type of En giving the result of the whole expression. To avoid 96 Bor/and C++ Programmer's Guide ambiguity with the commas used in function argument and initializer lists, parentheses must be used. For example, func (1, (j = 1, j + 4), k); calls func with three arguments, not four. The arguments are i, 5, andk. c++ operators See page 108 for information on the scope access operator (::). The operators specific to c++ are:: .* ->*. The syntax for the .* and ->* operators is as follows: pm-expression cast-expression pm expression .* cast-expression pm expression ->* cast-expression The .* operator dereferences pointers to class members. It binds the cast-expression, which must be of type "pointer to member of class type", to the pm-expression, which must be of class type or of a class publicly derived from class type. The result is an object or function of the type specified by the cast-expression. The ->* operator dereferences pointers to pointers to class members (no, that isn't a typo; it does indeed dereference pointers to pointers). It binds the cast-expression, which must be of type "pointer to member of type," to the pm-expression, which must be of type pointer to type or of type "pointer to class publicly derived from type." The result is an object or function of the type specified by the cast-expression. If the result of either of these operators is a function, you can only use that result as the operand for the function call operator ( ). For example, (ptr2object->*ptr2memberfunc) (10); calls the member function denoted by ptr2memberfunc for the object pointed to be ptr2object. Statements Statements specify the flow of control as a program executes. In the absence of specific jump and selection statements, statements are executed sequentially in the order of appearance in the source code. Table 2.13 on page 98 lays out the syntax for statements: Chapter 2, Language structure 97 Blocks A compound statement, or block, is a list (possibly empty) of statements enclosed in matching braces ({ }). Syntactically, a block can be considered to be a single statement, but it also plays a role in the scoping of identifiers. An identifier declared within a block has a scope starting at the point of declaration and ending at the closing brace. Blocks can be nested to any depth. Table 2.13: Borland C++ statements declaration-list declaration statement: labeled-statement compound-statement expression-statement selection-statement iteration-statement jump-statement asm-statement declaration (C++ specific) statement-list: statement statement-list statement expression-statement: ; asm-statement: asm tokens newline asm tokens; asm {tokens; = I labeled-statement: identifier : statement case constant-expression : statement default : statement compound-statement: { } declaration-list: declaration Labeled statements ... selection-statement: if (expression) statement if (expression) statement else statement switch (expression) statement iteration-statement: while ( expression) statement do statement while (expression); for (for-init-statement ; ; A statement can be labeled in the following ways: 1. label-identifier: statement The label identifier serves as a target for tJ.:1e unconditional goto statement. Label identifiers have their own name space and enjoy function scope. In C++ you can label both declaration and non-declaration statements. 2. case constant-expression: statement default: statement Case and default labeled statements are used only in conjunction with switch statements. 98 Borland C++ Programmer's Guide Expression statements Any expression followed by a semicolon forms an expression statement: ; Borland C++ executes an expression statement by evaluating the expression. All side effects from this evaluation are completed before the next statement is executed. Most expression statements are assignment statements or function calls. A special case is the null statement, consisting of a single semicolon (;). The null statement does nothing. It is nevertheless useful in situations where the Borland C++ syntax expects a statement but your program does not need one. Selection statements if statements The parentheses around cond-expression are essential. Selection or flow-control statements select from alternative courses of action by testing certain values. There are two types of selection statements: the it ... else and the switch. The basic it statement has the following pattern: it (eond-expression) t-st The eond-expression must be of scalar type. The expression is evaluated. 1£ the value is zero (or null for pointer types), we say that the eond-expression is false; otherwise, it is true. 1£ there is no e,lse clause and eond-expression is true, t-st is executed; otherwise, t-st is ignored. 1£ the optional else f-st is present and eond-expression is true, t-st is executed; otherwise, t-st is ignored and f-st is executed. _ Unlike, say, Pascal, Borland C++ does not have a specific Boolean data type. Any expression of integer or pointer type can serve a Boolean role in conditional tests. The relational expression (a > b) (if legal) evaluates to int 1 (true) if (a > b), and to int 0 (false) if (a <= b). Pointer conversions are such that a pointer can always be correctly compared to a constant expression evaluating to O. That is, the test for null pointers can be written if (lptr) ... or if (ptr Chapter 2, Language structure == 0) .... 99 The f-st and t-st statements can themselves be if statements, allowing for a series of conditional tests nested to any depth. Care is needed with nested if ... else constructs to ensure that the correct statements are selected. There is no endif statement: Any "else" ambiguity is resolved by matching an else with the last encountered if-without-an-else at the same block level. For example, if (x == 1) if (y == 1) puts("x=l and y=l"); else puts("x != 1"); draws the wrong conclusion! The else matches with the second if, despite the indentation. The correct conclusion is that x = 1 and y != 1. Note the effect of braces: if (x == 1) { if (y == 1) puts("x = 1 and y = 1"); else puts("x != 1"); II correct conclusion switch statements The switch statement uses the following basic format: switch (sw-expression) case-st A switch statement lets you transfer control to one of several case-labeled statements, depending on the value of sw-expression. The latter must be of integral type (in C++, it can be of class type, provided that there is an unambiguous conversion to integral type available). Any statement in case-st (including empty statements) can be labeled with one or more case labels: It is illegal to have duplicate case constants in the same switch statement. case const-exp-i : case-st-i where each case constant, const-exp-i, is a constant expression with a unique integer value (converted to the type of the controlling expression) within its enclosing switch statement. There can also be at most one default label: default: default-st After evaluating sw-expression, a match is sought with one of the const-exp-i. If a match is found, control passes to the statement case-st-i with the matching case label. . If no match is found and there is a default label, control passes to default-st. If no match is found and there is no default label, none 100 Borland C++ Programmer's Guide of the statements in case-st is executed. Program execution is not affected when case and default labels are encountered. Control simply passes through the labels to the following statement or switch. To stop execution at the end of a group of statements for a particular case, use break. Iteration statements while statements Iteration statements let you loop a set of statements. There are three forms of iteration in Borland C++: while, do, and for loops. The general format for this statement is while (cond-exp) t-st The parentheses are essential. The loop statement, t-st, will be executed repeatedly until the conditional expression, cond-exp, compares equal to zero (false). The cond-exp is evaluated and tested first (as described on page 99). If this value is nonzero (true), t-st is executed; if no jump statements that exit from the loop are encountered, cond-exp is evaluated again. This cycle repeats until cond-exp is zero. As with if statements, pointer type expressions can be compared with the null pointer, so that while (ptr) ... is equivalent to while (ptr != NULL) ... The while loop offers a concise method for scanning strings and other null-terminated data structures: char str[lO]="Borland"; char *ptr=&str[O]; int count=O; // ... while (*ptr++) count++; II loop until end of string ,In the absence of jump statements, t-st must affect the value of cond-exp in some way, or cond-exp itself must change during evaluation in order to prevent unwanted endless loops. do while statements The general format is do do-st while (cond-exp); The do-st statement is executed repeatedly until cond-exp compares equal to zero (false). The key difference from the while statement is that cond-exp is tested after, rather than before, each Chapter 2, Language structure 101 execution of the loop statement. At least one execution of do-st is assured. The same restrictions apply to the type of cond-exp (scalar). for statements For C++, can be an expression or a declaration. The for statement format in C is for «init-exp>; ; ; delete pointer-to-name; name can be of any type except "function returning ... " (however, pointers to functions are allowed). new tries to create an object of type name by allocating (if possible) sizeof(name) bytes in free store (also called the heap). The storage duration of the new object is from the point of creation until the operator delete kills it by deallocating its memory, or until the end of the program. If successful, new returns a pointer to the new object. A null pointer indicates a failure (such as insufficient or fragmented heap memory). As with malloe, you need to test for null before trying to access the new object (unless you use a new-handler; see the following section for details). However, unlike malloe, new calculates the size of name without the need for an explicit sizeof 108 Borland C++ Programmer's Guide operator. Further, the pointer returned is of the correct type, "pointer to name," without the need for explicit casting. new, being a keyword, doesn't need a prototype. name *nameptr; II name is any nonfunction type if (! (nameptr = new name)) { errmsg(IIInsufficient memory for name exit (1); ll ); II use *nameptr to initialize new name object delete nameptr; II destroy name and deallocate sizeof(name) bytes Handling errors You can define a function that will be called if the new operator fails (returns 0). To tell the new operator about the new-handler function, call set_new_handler and supply a pointer to the newhandler. The prototype for set_new_handler is as follows (from new.h): void (*set_new_handler( void (*) () )) (); set_new_handler returns the old new-handler, and changes the function _new_handler so that it, in turn, points to the newhandler that you define. The operator new with arrays If name is an array, the pointer returned by new points to the first element of the array. When creating multidimensional arrays with new, all array sizes must be supplied (although the left-most dimension doesn't have to be a compile-time constant): mat-ptr = new int[3] [10] [12]; mat-ptr = new int[n] [10] [12]; mat-ptr = new int[3] [] [12]; mat-ptr = new int[] [10] [12]; /I OK II OK I I illegal I I illegal Although the first array dimension may be a variable, all following dimensions must be constants. The operator delete with arrays You must use the syntax "delete [] expr" when deleting an array. In C++ 2.1, the array dimension should not be specified within the brackets: Chapter 3, C++ specifics 109 char * P; void func () { P = new char[10]; delete[] Pi II allocate 10 chars II delete 10 chars c++ 2.0 code required the array size. In order to allow 2.0 code to compile, Borland C++ issues a warning and simply ignores any size that is specified. For example, if the preceding example reads delete[lO] p and is compiled, the warning is as follows: Warning: Array size for 'delete' ignored in function func() With Borland C++, the [] is actually only required when the array element is a class with a destructor. But it is good programming practice to always tell the compiler that an array is being deleted. The ::operator new Initializers with the new operator When used with non-class objects, new works by calling a standard library routine, the global ::operator new. With class objects of type name, a specific operator called name::operator new can be defined. new applied to class name objects invokes the appropriate name::operator new if present; otherwise, the standard ::operator new is used. The optional initializer is another advantage new has over malloc (although calloc does clear its allocations to zero). In the absence of explicit initializers, the object created by new contains unpredictable data (garbage). The objects allocated by new, other than arrays, can be initialized with a suitable expression between parentheses: int-ptr = new int(3); Arrays of classes with constructors are initialized with the default constructor (see page 126). The user-defined new operator with customized initialization plays a key role in C++ constructors for class-type objects. 110 Borland C++ Programmer's Guide Classes c++ classes offer extensions to the predefined type system. Each class type represents a unique set of objects and the operations (methods) and conversions available to create, manipulate, and destroy such objects. Derived classes can be declared that inherit the members of one or more base (or parent) classes. In C++, structures and unions are considered as classes with certain access defaults. A simplified, "first-look" syntax for class declarations is class-key class-name <: base-list> { } class-key is one of class, struct, or union. The optional base-list lists the base class or classes from which the class class-name will derive (or inherit) objects and methods. If any base classes are specified, the class class-name is called a derived class (see page 120, "Base and derived class access"). The base-list has default and optional overriding access specifiers that can modify the access rights of the derived class to members of the base classes (see page 118, "Member access control"). The optional member-list declares the class members (data and functions) of class-name with default and optional overriding access specifiers that may affect which functions can access which members. Class names class-name is any identifier unique within its scope. With structures, classes, and unions, class-name can be omitted (see "Untagged structures and typedefs," page 66.) Class types The declaration creates a unique type, class type class-name. This lets you declare further class objects (or instances) of this type, and objects derived from this type (such as pointers to, references to, arrays of class-name, and so on): class X { .,. }i X x, &xr, *xptr, xarray[lO]i /* four objects: type X, reference to X, pointer to X and array of X* / Chapter 3, C++ specifics 111 struct Y { ... }i Y y, &yr, *yptr, yarray[lO) i II C would have II struct Y y, *yptr, yarray[lO)i union z { ... }i Z z, &zr, *zptr, zarray[lO)i II C would have II union Z z, *zptr, zarray[lO)i Note the difference between C and C++ structure and union declarations: The keywords struct and union are essential in C, but in C++ they are needed only when the class names, Y and Z, are hidden (see the following section). Class name scope The scope of a class name is local, with some tricks peculiar to classes. Class name scope starts at the point of declaration and ends with the enclosing block. A class name hides any class, object, enumerator, or function with the same name in the enclosing scope. If a class name is declared in a scope containing the declaration of an object, function, or enumerator of the same name, the class can only be referred to using the elaborated type specifier. This means that the class key, class, struct, or union must be used with the class name. For example, struct S { ... }i int S(struct S *Sptr)i void func(void) { S ti struct S Si S (&s) i II II II II ILLEGAL declaration: no class key and function S in scope OK: elaborated with class key OK: this is a function call C++ also allows an incomplete class declaration: class Xi II no members, yet! Incomplete declarations permit certain references to class name X (usually references to pointers to class objects) before the class has been fully defined (see "Structure member declarations," page 66). Of course, you must make a complete class declaration with members before you can define and use class objects. 112 Borland C++ Programmer's Guide Class objects Class objects can be assigned (unless copying has been restricted), passed as arguments to functions, returned by functions (with some exceptions), and so on. Other operations on class objects and members can be user-defined in many ways, including member and friend functions, and the redefinition of standard functions and operators when used with objects of a certain class. Redefined functions and operators are said to be overloaded. Operators and functions that are restricted to objects of a certain class (or related group of classes) are called member functions for that class. C++ offers a mechanism whereby the same function or operator name can be called to perform different tasks, depending on the type or number of arguments or operands. Class member list The optional member-list is a sequence of data declarations (of any type, including enumerations, bit fields and other classes) and function declarations and definitions, all with optional storage class specifiers and access modifiers. The objects thus defined are called class members. The storage class specifiers auto, extern, and register are not allowed. Members can be declared with the static storage class specifiers. Member functions A function declared without the friend specifier is known as a member function of the class. Functions declared with the friend modifier are called friend functions. The same name can be used to denote more than one function, provided that they differ in argument type or number of arguments. The keyword this Nonstatic member functions operate on the· class type object with which they are called. For example, if x is an object of class X and f is a member function of X, the function call x. f () operates on x. Similarly, if xptr is a pointer to an X object, the function call xptr->f () operates on *xptr. But how does f know which x it is operating on? C++ provides f with a pointer to x called this. this is Chapter 3, C++ specifics 113 passed as a hidden argument in all calls to nonstatic member functions. The keyword this is a local variable available in the body of any nonstatic member function. this does not need to be declared and is rarely referred to explicitly in a function definition. However, it is used implicitly within the function for member references. If x.f(y) is called, for example, where y is a member of X, this is set to &x and y is set to this->y, which is equivalent to x.y. Inline functions You can declare a member function within its class and define it elsewhere. Alternatively, you can both declare and define a member function within its class, in which case it is called an inline function. Borland C++ can sometimes reduce the normal function call overhead by substituting the function call directly with the compiled code of the function body. This process, called an inline expansion of the function body, does not affect the scope of the function name or its arguments. Inline expansion is not always possible or feasible. The inline specifier is a request (or hint) to the compiler that you would welcome an inline expansion. As with the register storage class specifier, the compiler mayor may not take the hint! Explicit and implicit inline requests are best reserved for small, frequently used functions, such as the operator functions that implement overloaded operators. For example, the following class declaration: int i; II global int class X { public: char* func(void) { return i;} char* i; II inline by default }; is equivalent to: inline char* X::func(void) { return i; } func is defined "outside" the class with an explicit inline specifier. The i returned by func is the char* i of class X-see the section on member scope starting on page 116. 114 Borland C++ Programmer's Guide Static members The storage class specifier static can be used in class declarations of data and function members. Such members are called static members and have distinct properties from nonstatic members. With nonstatic members, a distinct copy "exists" for each object in its class; with static members, only one copy exists, and it can be accessed without reference to any particular object in its class. If x is a static member of class X, it can be referenced as X::x (even if objects of class X haven't been created yet). It is still possible to access x using the normal member access operators. For example, y.x and yptr->x, where y is an object of class X and yptr is a pointer to an object of class X, although the expressions y and yptr are not evaluated. In particular, a static member function can be called with or without the special member function syntax: class X { int member_inti pUblic: static void func(int i, X* ptr) i }i void g (void) i { X obji func(l, &Obj)i II error unless there is a global func() II defined elsewhere X: : func (1, &obj) i I I calls the static fune () in X II OK for static functions only obj.func(l, &obj) i II so does this (OK for static and II nonstatic functions) Since a static member function can be called with no particular object in mind, it has no this pointer. A consequence of this is that a static member function cannot access nonstatic members without explicitly specifying an object with. or ->. For example, with the declarations of the previous example, func might be defined as follows: void X::func(int i, X* ptr) { member_int = ii II which object does member_int II refer to? Error ptr->member_int Chapter 3, C++ specifics = ii II OK: now we know! 115 Apart from inline functions, static member functions of global classes have external linkage. Static member functions cannot be virtual functions. It is illegal to have a static and nonstatic member function with the same name and argument types. The declaration of a static data member in its class declaration is not a definition, so a definition must be provided elsewhere to allocate storage and provide initialization. Static members of a class declared local to some function have no linkage and cannot be initialized. Static members of a global class can be initialized like ordinary global objects, but only in file scope. Static members obey the usual class member access rules, except they can be initialized. class X { static int x; }; int X::x = 1; The main use for static members is to keep track of data common to all objects of a class, such as the number of objects created, or the last-used resource from a pool shared by all such objects. Static members are also used to reduce the number of visible global names • make obvious which static objects logically belong to which class • permit access control to their names II Member scope The expression X::func() in the example on page 114 uses the class name X with the scope access modifier to signify that func, although defined "outside" the class, is indeed a member function of X, and it exists within the scope of X. The influence of X:: extends into the body of the definition. This explains why the i returned by func refers to X::i, the char* i of X, rather than the global int i. Without the X:: modifier, the function func would represent an ordinary non-class function, returning the global int i. All member functions, then, are in the scope of their class, even if defined outside the class. 116 Borland C++ Programmer's Guide Data members of class X can be referenced using the selection operators . and -> (as with C structures). Member functions can also be called using the selection operators (see also "The keyword this," page 113). For example, class X { public: int i; char name[20]; X *ptr1; X *ptr2; void Xfunc(char*data, X* left, X* right); II define elsewhere }; void f (void) ; { X xl, x2, *xptr=&x1; xl. i = 0; x2. i = xl. i; xptr->i = 1; x1.Xfunc("stan", &x2, xptr); If m is a member or base member of class X, the expression X::m is called a qualified name; it has the same type as m, and it is an lvalue only if m is an lvalue. A key point is that even if the class name X is hidden by a non-type name, the qualified name X::m will access the correct class member, m. Class members cannot be added to a class by another section of your program. The class X cannot contain objects of class X, but can contain pointers or references to objects of class X (note the similarity with C's structure and union types). Nested types In C++ 2.1, even tag or typedef names declared inside a class lexically belong to the scope of that class. Such names can in general be accessed only using the xxx::yyy notation, except when in the scope of the appropriate class. A class declared within another class is called a nested class. Its name is local to the enclosing class; the nested class is in the scope of the enclosing class. This is purely lexical nesting. The nested class has no additional privileges in accessing members of the enclosing class (and vice versa). .. Chapter 3, C++ specifics Classes can be nested in this way to an arbitrary level. For example: 117 struct outer typedef int ti II 'outer: :t' is a typedef name struct inner II'outer::inner' is a class static int Xi }i static int Xi int f () i }i Iidefine static data member int outer::Xi int outer::f() { t II 't' visible directly here Xi return Xi int outer::inner::xi outer::t Xi Iidefine static data member II have to use 'outer: :t' here With C++ 2.0, any tags or typedef names declared inside a class actually belong to the global (file) scope. For example: struct foo enum bar { X}i II 2.0 rules: 'bar' belongs to file scope II 2.1 rules: 'bar' belongs to 'foo' scope }i bar Xi The preceding fragment compiles without errors. But, because the code is illegal under the 2.1 rules, a warning is issued as follows: Warning: Use qualified name to access nested type 'foo: :bar' Member access control Friend function declarations are not affected by access specifiers (see "Friends of Members of a class acquire access attributes either by default (depending on class key and declaration placement) or by the use of one of the three access specifiers: public, private, and protected. The significance of these attributes is as follows: public The member can be used by any function. private The member call be used only by member functions and friends of the class in which it is declared. classes, "page 722). 118 Borland c++ Programmer's Guide protected Same as for private, but additionally, the member can be used by member functions and friends of classes derived from the declared class, but only in objects of the derived type. (Derived classes are explained in the next section.) Members of a class are private by default, so you need explicit public or protected access specifiers to override the default. Members of a struct are public by default, but you can override this with the private or protected access specifier. Members of a union are public by default; this cannot be changed. All three access specifiers are illegal with union members. A default or overriding access modifier remains effective for all subsequent member declarations until a different access modifier is encountered. For example, class X { int ii char Chi public: int j; int ki protected: int 1; II x::i is private by default Iisoisx::ch II next two are public II x::l is protected }; struct Y { int i; private: int ji pUblic: int k; II Y::i is public by default II Y::j is private II Y::k is public }i union z { int ii II public by default; no other choice double d; }; The access specifiers can be listed and grouped in any convenient sequence. You can save a little typing effort by declaring all the private members together, and so on. Chapter 3, C++ specifics 119 Base and derived class access When you declare a derived class 0, you list the base classes 81, 82, .. , in a comma-delimited base-list: class-key D: base-list { } Since a base class can itself be a derived class, the access attribute question is recursive: You backtrack until you reach the basest of the base classes, those that do not inherit. D inherits all the members of these base classes. (Redefined base class members are inherited and can be accessed using scope overrides, if needed.) D can use only the public and protected members of its base classes. But, what will be the access attributes of the inherited members as viewed by D? 0 may want to use a public member from a base class, but make it private as far as outside functions are concerned. The solution is to use access specifiers in the base-list. When declaring D, you can use the access specifier public, protected, or private in front of the classes in the base-list: Unions cannot have base classes, and unions cannot be used as base classes. class D : public Bl, private B2, ... { These modifiers do not alter the access attributes of base members as viewed by the base class, though they can alter the access attributes of base members as viewed by the derived class. The default is private if 0 is a class declaration, and public if D is a struct declaration. The derived class inherits access attributes from a base class as follows: public base class: public members of the base class are public members of the derived class. Protected members of the base class are protected members of the derived class. Private members of the base class remain private to the base class. protected base class: Both public and protected members of the base class are protected members of the derived class. Private members of the base class remain private to the base class. private base class: 120 Both public and protected members of the base class are private members of the Borland C++ Programmer's Guide derived class. Private members of the base class remain private to the base class. In both cases, note carefully that private members of a base class are, and remain, inaccessible to member functions of the derived class unless friend declarations are explicitly declared in the base class granting access. For example, II default for class is 'private A class X : A { 1* class X is derived from class A *1 class Y : BI II override default for C public C { 1* class Y is derived (multiple inheritance) from Band C B defaults to private B *1 II default for struct is public 0 1* struct S is derived from 0 *1 struct S : 0 { struct T private 0 E { 1 II override default for 0 II E is public by default 1* struct T is derived (multiple inheritance) from 0 and E E defaults to public E *1 The effect of access specifiers in the base list can be adjusted by using a qualified-name in the public or protected declarations in the derived class. For example, class B { int ai pUblic: int b Ci int Bfunc (void) II private by default l i }i class X : private B int di II a b C Bfunc are now private in X II private by default NOTE: a is not II accessible in X l l I l public: II c was private now is public B: :Ci int ei int Xfunc (void) l i }; int Efunc (X& x) ; Chapter 3, C++ specifics II external to B and X 121 The function Efune can use only the public names c, e, and Xfune. The function Xfune is in X, which is derived from private B, so it has access to • The "adjusted-to-public" c • The "private-to-X" members from B: band Bfune • X's own private and public members: d, e, and Xfune However, Xfune cannot access the "private-to-B" member, a. Virtual base classes With multiple inheritance, a base class can't be specified more than once in a derived class: class B { ... }; class D : B, B { ... }; II Illegal However, a base class can be indirectly passed to the derived class more than once: class X : public B { ... } class Y : public B { ... } class Z : public X, public Y { ... } II OK In this case, each object of class Z will have two sub-objects of class B. If this causes problems, the keyword virtual can be added to a base class specifier. For example, class X : virtual public B { ... } class Y : virtual public B { ... } class Z : public X, public Y { .. . B is now a virtual base class, and class Z has only one sub-object of class B. Friends of classes A friend F of a class X is a function or class that, although not a member function of X, has full access rights to the private and protected members of X. In all other respects, F is a normal function with respect to scope, declarations, and definitions. 122 Borland C++ Programmer's Guide Since F is not a member of X, it is not in the scope of X and it cannot be called with the x.F and xptr->F selector operators (where x is an X object, and xptr is a pointer to an X object). If the specifier friend is used with a function declaration or definition within the class X, it becomes a friend of X. Friend functions defined within a class obey the same inline rules as member functions (see "Inline functions," page 114). Friend functions are not affected by their position within the class or by any access specifiers. For example, class X { int ii II private to X friend void friend_func(X*, int}i 1* friend_func is not private, even though it's declared in the private section *1 pUblic: void member_func(int}i }i 1* definitionsi note both functions access private int void friend_func(X* xptr, int a} { xptr->i = ai } void X::member_func(int a} { i = ai } *1 X xobj i 1* note difference in function calls *1 friend_func(&xobj, 6}i xobj.member_func(6}i You can make all the functions of class Y into friends of class X with a single declaration: class Yi class X { friend Y; int ii void member_funcX(} i II incomplete declaration }i class Yi { void friend_Xl(X&}i void friend_X2(X*} i II complete the declaration }i The functions declared in Yare friends of X, although they have no friend specifiers. They can access the private members of X, such as i and member_funcX. Chapter 3, C++ specifics 123 It is also possible for an individual member function of class X to be a friend of class Y: class X { void member_funcX(); class Y { int i; friend void X::member_funcX(); }; Class friendship is not transitive: X friend of Y and Y friend of Z does not imply X friend of Z. However, friendship is inherited. Constructors and destructors There are several special member functions that determine how the objects of a class are created, initialized, copied, and destroyed. Constructors and destructors are the most important of these. They have many of the characteristics of normal member functions-you declare and define them within the class, or declare them within the class and define them outside-but they have some unique features. 1. They do not have return value declarations (not even void). 2. They cannot be inherited, though a derived class can call the base class's constructors and destructors. 3. Constructors, like most C++ functions, can have default arguments or use member initialization lists. 4. Destructors can be virtual, but constructors cannot. 5. You can't take their addresses. int main(void) { void *ptr = base::base; / / illegal 6. Constructors and destructors can be generated by Borland C++ if they haven't been explicitly defined; they are also invoked on many occasions without explicit calls in your 124 Borland C++ Programmer's Guide program. Any constructor or destructor generated by the compiler will be public. 7. You cannot call constructors the way you call a normal function. Destructors can be called if you use their fully qualified name. X *Pi p->X::-X()i x: :X () i II legal call of destructor II illegal call of constructor 8. The compiler automatically calls constructors and destructors when defining and destroying objects. 9. Constructors and destructors can make implicit calls to operator new and operator delete if allocation is required for an object. 10. An object with a constructor or destructor cannot be used as a member of a union. If a class X has one or more constructors, one of them is invoked each time you define an object x of class X. The constructor creates x and initializes it. Destructors reverse the process by destroying the class objects created by constructors. Constructors are also invoked when local or temporary objects of a class are created; destructors are invoked when these objects go out of scope. Constructors Constructors are distinguished from all other member functions by having the same name as the class they belong to. When an object of that class is created or is being copied, the appropriate constructor is called implicitly. Constructors for global variables are called before the main function is called. When the pragma startup directive is used to install a function prior to the main function, global variable constructors are called prior to the startup functions. Chapter 3, C++ specifics 125 Local objects are created as the scope of the variable becomes active. A constructor is also invoked when a temporary object of the class is created. class X public: X(); II class X constructor }; A class X constructor cannot take X as an argument: class X { public: X(X) ; II illegal The parameters to the constructor can be of any type except that of the class of which it is a member. The constructor can accept a reference to its own class as a parameter; when it does so, it is called the copy constructor. A constructor that accepts no parameters is called the default constructor. We discuss the default constructor next; the description of the copy constructor starts on page 127. Constructor defaults Important! The default constructor for class X is one that takes no arguments; it usually has the form X: : X ( ) . If no user-defined constructors exist for a class, Borland C++ generates a default constructor. On a declaration such as X x, the default constructor creates the object x. Like all functions, constructors can have default arguments. For example, the constructor X: :X(int, int = 0) can take one or two arguments. When presented with one argument, the missing second argument is assumed to be a zero into Similarly, the constructor X::X(int = 5, int = 6) could take two, one, or no arguments, with appropriate defaults. However, the default constructor X: : X () takes no arguments and must not be confused with, say, x: :X (int = 0), which can be called with no arguments as a default constructor, or can take an argument. 126 Borland C++ Programmer's Guide Take care to avoid ambiguity in calling constructors. In the following case, the two .default constructors could become ambiguous: class X pUblic: X(); X(int i = 0); }; main() { X one(10); X two; II OK; uses X: :X(int) II illegal; ambiguous whether to call X: :X() or I I X: :X(int = 0) return 0; The copy constructor Overloading constructors A copy constructor for class X is one that can be called with a single argument of type x: x: :X(const X&) or x: :X(const X&, int = 0). Default arguments are also allowed in a copy constructor. Copy constructors are invoked when copying a class objeCt, typically when you declare with initialization by another class object: X x = y. Borland C++ generates a copy constructor for class X if one is needed and none is defined in class X. Constructors can be overloaded, allowing objects to be created, depending on the values being used for initialization. class X int integer-part; double double-part; pUblic: {integer-part = i; } X(int i) X(double d) { double-part = d; } }; main() { X one(10); II invokes X::X(int) and sets integer-part to 10 X one(3.14); II invokes X::X(double) setting double-part Chapter 3, C++ specifics 127 return 0; Order of calling constructors In the case where a class has one or more base classes, the base class constructors are invoked before the derived class constructor. The base class constructors are called in the order they are declared. For example, in this setup, class Y {... } class X : public Y {... } X one; the constructors are called in this order: Y(); X(); II base class constructor II derived class constructor For the case of multiple base classes: class X : public Y, public X onei z the constructors are called in the order of declaration: Y()i II base class constructors corne first Z()i X()i Constructors for virtual base classes are invoked before any nonvirtual base classes. If the hierarchy contains multiple virtual base classes, the virtual base class constructors are invoked in the order in which they were declared. Any non-virtual bases are then constructed before the derived class constructor is called. If a virtual class is derived from a non-virtual base, that nonvirtual base will be first so that the virtual base class may be properly constructed. The code class X : public Y, virtual public Z X onei produces this order: Z() i Y(); X(); 128 II virtual base class initialization II non-virtual base class II derived class Borland C++ Programmer's Guide Or for a more complicated example: class base; class base2; class levell : public base2, virtual public base; class leve12 : public base2, virtual public base; class toplevel : public levell, virtual public leve12; toplevel view; The construction order of view would be as follows: basel) ; base2(); leve12 () ; base2 () ; levell () ; toplevel(); II II II II II II II virtual base class highest in hierarchy base is only constructed once non-virtual base of virtual base leve12 must be called to construct leve12 virtual base class non-virtual base of levell other non-virtual base In the event that a class hierarchy contains multiple instances of a virtual base class, that base class is only constructed once. If, however, there exist both virtual and non-virtual instances of the base class, the class constructor is invoked a single time for all virtual instances and then once for each non-virtual occurrence of the base class. Constructors for elements of an array are called in increasing order of the subscript. Class initialization An object of a class with only public members and no constructors or base classes (typically a structure) can be initialized with an initializer list. If a class has a constructor, its objects must be either initialized or have a default constructor. The latter is used for objects not explicitly initialized. Objects of classes with constructors can be initialized with an expression list in parentheses. This list is used as an argument list to the constructor. An alternative is to use an equal sign followed by a single value. The single value can be of the type of the first argument accepted by a constructor of that class, in which case either there are no additional arguments, or the remaining arguments have default values. It could also be an object of that class type. In the former case, the matching constructor is called to create the object. In the latter case, the copy constructor is called to initialize the object. Chapter 3, C++ specifics 129 class X int i; public: X(); X(int x); X(const X&); II function bodies omitted for clarity }; main() { X one; X two(l); X three = 1; X four = one; X five(two); II II II II II default constructor invoked constructor X::X(int) is used calls X::X(int) invokes X::X(const X&) for copy calls X::X(const X&) The constructor can assign values to its members in two ways. It can accept the values as parameters and make assignments to the member variables within the function body of the constructor: class X int a, bi public: X(int i, int j) { a =ii b =j } }i Or it can use an initializer list prior to the function body: class X int a, b; pUblic: X(int i, int j) : a (i), b (j) {} }i In both cases, Cin initialization of X x (1, 2) assigns a value of 1 to x::a and 2 to x::b. The second method, the initializer list, provides a mechanism for passing values along to base class constructors. class basel Base class constructors must be declared as either public or protected to be called from a derived class. int Xi public: basel(int i) { x = i; }i class base2 130 Borland C++ Programmer's Guide int Xi public: base2(int i) : x(i) {} }i class top : public basel, public base2 { int a, bi public: top(int i, int j) : basel(i*5), base2(j+i), a(i) {b = ji} }i With this class hierarchy, a declaration of top one (1, 2) would result in the initialization of base1 with the value 5 and base2 with the value 3. The methods of initialization can be intermixed. As described previously, the base classes are initialized in declaration order. Then the members are initialized, also in declaration order, independent of the initialization list. class X int a, bi pUblic: X(int i, j) : a(i), b(a+j) {} }i With this class, a declaration of X x (1,1) results in an assignment of 1 to x::a and 2 to x::b. Base class constructors are called prior to the construction of any of the derived classes members. The values of the derived class can't be changed arid then have an affect on the base class's creation. class base int Xi public: base (int i) : X(i) {} }i class derived : base int ai public: derived(int i) a(i*lO), base(a) { } II Watch out! Base will be II passed an uninitialized a }i Chapter 3, C++ specifics 131 With this class setup, a call of derived d(1) will not result in a value of 10' for the base class member x. The value passed to the base class constructor will be undefined. When you want an initializer list in a non-inline constructor, don't place the list in the class definition. Instead, put it at the point at which the function is defined. derived::derived(int i) : a(i) { Destructors The destructor for a class is called to free members of an object before the object is itself destroyed. The destructor is a member function whose name is that of the class preceded by a tilde (-). A destructor cannot accept any parameters, nor will it have a return type or value declared. class X pUblic: -X(); II destructor for class X }; If a destructor is not explicitly defined for a class, the compiler will generate one. When destructors are invoked A destructor is called implicitly when a variable goes out of its declared scope. Destructors for local variables are called when the block they are declared in is no longer active. In the case of global variables, destructors are called as part of the exit procedure after the main function. When pointers to objects go out of scope, a destructor is not implicitly called. This means that the delete operator must be called to destroy such an object. Destructors are called in the exact opposite order from which their corresponding constructors were called (see page 128). 132 Borland C++ Programmer's Guide atexit, #pragma exit, and destructors All global objects are active until the code in all exit procedures has executed. Local variables, including those declared in the main function, are destroyed as they go out of scope. The order of execution at the end of a Borland C++ program in these regards is as follows: • atexit functions are executed in the order they were inserted. • #pragma exit functions are executed in the order of their priority codes. • Destructors for global variables are called. exit and destructors abort and destructors When you call exit from within a program, destructors are not called for any local variables in the current scope. Global variables are destroyed in their normal order. If you call abort anywhere in a program, no destructors are called, not even for variables with a global scope. A destructor can also be invoked explicitly in one of two ways: indirectly through a call to delete, or directly by using the destructor's fully qualified name. You can use delete to destroy objects that have been allocated using new. Explicit calls to the destructor are only necessary for objects allocated a specific address through calls to new. class X { -X() ; }; void* operator new(size_t size, void *ptr) { return ptr; char buffer[sizeof(X)]; main() { Chapter 3, C++ specifics 133 X* pointer = new Xi X* exact-pointer; exact-pointer = new(&buffer) delete pointeri exact-pointer->X::-X(); Xi II pointer initialized at II address of buffer II delete used to destroy pointer II direct call used to deallocate Virtual destructors A destructor can be declared as virtual. This allows a pointer to a base class object to call the correct destructor in the event that the pointer actually refers to a derived class object. The destructor of a class derived from a class with a virtual destructor is itself virtual. class color public: virtual -color()i II virtual destructor for color }; class red : public color public: -red () i II destructor for red is also virtual }i class brightred: public red { pUblic: -brightred(); II brightred's destructor also virtual }i The previously listed classes and the following declarations color *palette[3]; palette[O] = new redi palette[l] = new brightredi palette[2] = new color; will produce these results delete palette[O]i II The destructor for red is called followed by the II destructor for color. delete palette[l]i II The destructor for brightred is called, followed by -red 134 Borland C++ Programmer's Guide II and -color. delete palette[21j II The destructor for color is invoked. However, in the event that no destructors were declared as virtual, delete palette[O], delete palette[l], and delete palette[2] would all call only the destructor for class color. This would incorrectly destruct the first two elements, which were actually of type red and brightred. Overloaded operators c++ lets you redefine the action of most operators, so that they perform specified functions when used with objects of a particular class. As with overloaded C++ functions in general, the compiler distinguishes the different functions by noting the context of the call: the number and types of the arguments or operands. All the operators on page 79 can be overloaded except for .. * :: ?: The preprocessing symbols # and ## also cannot be overloaded. The keyword operator followed by the operator symbol is called the operator function name; it is used like a normal function name when defining the new (overloaded) action of the operator. A function operator called with arguments behaves like an operator working on its operands in an expression. The operator function can't alter the number of arguments or the precedence and associativity rules (Table 2.10 on page 76) applying to normal operator use. Consider the class complex: This class was invented for illustrative purposes only. It isn't the same as the class complex in the run-time library. class complex { double real, imagj public: complex() { real = imag = OJ } complex(double r , double i = 0) real = ri imag = ii II private by default II inline constructor II another one We could easily devise a function for adding complex numbers, say, Chapter 3, C++ specifics 135 complex AddComplex(complex cl, complex c2) i but it would be more natural to be able to write: complex cl(O,l), c2(l,O), c3 c3 = cl + C2i than c3 = AddComplex(cl, C2)i The operator + is easily overloaded by including the following declaration in the class complex: friend complex operator + (complex cl, complex C2)i and defining it (possibly inline) as: complex operator + (complex cl, complex c2) { return complex(cl.real + c2.real, cl.imag + c2.imag) i Operator functions Operator functions can be called directly, although they are usually invoked indirectly by the use of the overload operator: c3 = cl.operator + (C2)i II same as c3 = cl + c2 Apart from new and delete, which have their own rules (see the next sections), an operator function must either be a nonstatic member function or have at least one argument of class type. The operator functions =, ( ), [ ] and -> must be nonstatic member functions. Overloaded operators and inheritance 136 With the exception of the assignment function operator =( ) (see "Overloading the assignment operator =" on page 139), all overloaded operator functions for class X are inherited by classes derived from X, with the standard resolution rules for overloaded functions. If X is a base class for V, an overloaded operator function for X may possibly be further overloaded for Y. Borland C++ Programmer's Guide Overloading new and de!ete The type size_t is defined in std/ib.h The operators new and delete can be overloaded to provide alternative free storage (heap) memory-management routines. A userdefined operator new must return a void* and must have a size_t as its first argument. A user-defined operator delete must have a void return type and void* as its first argument; a second argument of type size_t is optional. For example, #include class X { pUblic: void* operator new(size_t size) { return newalloc(size);} void operator delete (void* p) { newfree(p) i } X() { 1* initialize here *1 } X(char ch) { 1* and here *1 } -X() { 1* clean up here *1 } }i The size argument gives the size of the object being created, and newalloc and newfree are user-supplied memory allocation and deallocation functions. Constructor and destructor calls for objects of class X (or objects of classes derived from X that do not have their own overloaded operators new and delete) will invoke the matching user-defined X::operator new() and X::operator delete(), respectively. The X::operator new and X::operator delete operator functions are static members of X whether explicitly declared as static or not, so they cannot be virtual functions. The standard, predefined (global) new and delete operators can still be used within the scope of X, either explicitly with the global scope operator (::operator new and ::operator delete), or implicitly when creating and destroying non-X or non-X-derived class objects. For example, you could use the standard new and delete when defining the overloaded versions: void* X::operator new(size_t s) { void* ptr = new char[s] i II standard new called return ptr; Chapter 3, C++ specifics 137 void X::operator delete(void* ptr}· { delete (void*) ptri II standard delete called The reason for the size argument is that classes derived from X inherit the X::operator new. The size of a derived class object may well differ from that of the base class. Overloading unary operators You can overload a prefix or postfix unary operator by declaring a nonstatic member function taking no arguments, or by declaring a non-member function taking one argument. If @ represents a unary operator, @x and x@ can both be interpreted as either x.operator@() or operator@(x), depending on the declarations made. If both forms have been declared, standard argument matching is applied to resolve any ambiguity. ~ Under c++ 2.0, an overloaded operator++ or - is used for both prefix and postfix uses of the operator. For example: struct foo operator: : ( ) ; operator--(}i Xi void func () { xt+; ++Xi II calls x.operatort+(} II calls x.operator++(} x--; --Xi II calls x.operator--(} II calls x.operator--(} With C++ 2.1, when an operator++ or operator':"- is declared as a member function with no parameters, or as a nonmember function with one parameter, it only overloads the prefix operator ++ or operator -. You can only overload a postfix operator++ or operator- by defining it as a member function taking an int parameter or as a nonmember function taking one class and one int parameter. For example add the following lines to the previous code: 138 Borland C++ Programmer's Guide operator++ (int) i operator-- (int) j When only the prefix version of an operator++ or operator- is overloaded and the operator is applied to a class object as a postfix operator, the compiler issues a warning. Then it calls the prefix operator, allowing 2.0 code to compile. The preceding example results in the following warnings: Warning: Overloaded prefix 'operator ++' used as a postfix operator in function func() Warning: Overloaded prefix 'operator --' used as a postfix operator in function func() Overloading binary operators Overloading the assignment operator = You can overload a binary operator by declaring a nonstatic member function taking one argument, or by declaring a nonmember function (usually friend) taking two arguments. If @ represents a binary operator, x@y can be interpreted as either x.operator@(y) or operator@(x,y), depending on the declarations made. If both forms have been declared, standard argument matching is applied to resolve any ambiguity. = The assignment operator can be overloaded by declaring a nonstatic member function. For example, class String { String& operator = (String& str)i String (String&)i -String () j This code, with suitable definitions of String::operator =(), allows string assignments strl =str2, just like other languages. Unlike the other operator functions, the assignment operator function cannot be inherited by derived classes. If, for any class X, there is no user-defined operator =, the operator = is defined by default as a member-by-member assignment of the members of class X: X& X::operator = (canst X& source) { Chapter 3, C++ specifics 139 II memberwise assignment Overloading the function call operator () The function call primary-expression ( ) is considered a binary operator with operands primary-expression and expression-list (possibly empty). The corresponding operator function is operatorO. This function can be user-defined for a class X (and any derived classes) only by means of a nonstatic member function. A call x(argl, arg2), where x is an object of class X, is interpreted as x.operatorO(argl,arg2). Overloading the subscript operator Similarly, the subscripting operation primary-expression [ expression] is considered a binary operator with operands primary-expression and expression. The corresponding operator function is operator[]; this can be user-defined for a class X (and any derived classes) only by means of a nonstatic member function. The expression x[yL where x is an object of class X, is interpreted as x.operator[] (y). Overloading the class member access operator Class member access using primary-expression -> expression is considered a unary operator. The function operator-> must be a nonstatic member function. The expression x->m, where x is a class X object, is interpreted as (x.operator->(»->m, so that the function operator->() must either return a pointer to a class object or return an object of a class for which operator-> is defined. Virtual functions Virtu a! functions can oniv be member functions. 140 Virtual functions allow derived classes to provide different versions of a base class function. You can use the virtual keyword Borland C++ Programmers Guide to declare a virtual function in a base class, then redefine it in any derived class, even if the number and type of arguments are the same. The redefined function is said to override the base class function. You can also declare the functions int Base:: Fun (int) and int Derived:: Fun (int) even when they are not virtual. The base class version is available to derived class objects via scope override. If they are virtual, only the function associated with the actual type of the object is available. With virtual functions, you cannot change just the function type. It is illegal, therefore, to redefine a virtual function so that it differs only in the return type. If two functions with the same name have different arguments, c++ considers them different, and the virtual function mechanism is ignored. If a base class B contains a virtual function vf, and class 0, derived from B, contains a function vf of the same type, then if vf is called for an object d or 0, the call made is D: :vf, even if the access is via a pointer or reference to B. For example, struct B { virtual void vfl(}; virtual void vf2(}; virtual void vf3(}; void f () ; }; class D : public B virtual void vfl(}; II virtual specifier is legal but redundant void vf2(int}; II not virtual, since it's using a different II arg list char vf3 (); II Illegal: return-type-only change! void f {) ; }; void extf () { D d; B* bp = &d; bp->vfl (); bp->vf2 (); bp->f(}; II declare a D object II standard conversion from D* to B* I I calls D: :vfl II call B::vf2 since D's vf2 has different args II calls B::f (not virtual) The overriding function vf1 in 0 is automatically virtual. The virtual specifier can be used with an overriding function declaration in the derived class, but its use is redundant. The interpretation of a virtual function call depends on the type of the object for which it is called; with non-virtual function calls, the Chapter 3, C++ specifics 141 interpretation depends only on the type of the pointer or reference denoting the object for which it is called. .. Virtual functions must be members of some class, but they cannot be static members. A virtual function can be a friend of another class. A virtual function in a base class, like all member functions of a base class, must be defined or, if not defined, declared pure: class B { virtual void vf(int) = 0; II = 0 means 'pure' In a class derived from such a base class, each pure function must be defined or redeclared as pure (see the next section, "Abstract classes"). If a virtual function is ~efined in the base it need not necessarily be redefined in the derived class. Calls will simply call the base function. Virtual functions exact a price for their versatility: Each object in the derived class needs to carry a pointer to a table of functions in order to select the correct one at run time (late binding). Abstract classes An abstract class is a class with at least one pure virtual function. A virtual function is specified as pure by using the pure-specifier. An abstract class can be used only as a base class for other classes. No objects of an abstract class can be created. An abstract class cannot be used as an argument type or as a function return type. However, you can declare pointers to an abstract class. References to an abstract class are allowed, provided that a temporary object is not needed in the initialization. For example, class shape { point center; II abstract class public: where() { return center; } move(point p) { center = p; draw(); } virtual void rotate(int) = 0; II pure virtual function virtual void draw() = 0; II pure virtual function virtual void hilite() = 0; II pure virtual function 142 Borland C++ Programmer's Guide II ERROR: attempted creation of an object of II an abstract class shape* sptr; II pointer to abstract class is OK shape f(); II ERROR: abstract class cannot be a return II type int g (shape s); II ERROR: abstract class cannot be a Ilfunction argument type shape& h(shape&); II reference to abstract class as return II value or function argument is OK shape x; Suppose that 0 is a derived class with the abstract class 8 as its immediate base class. Then for each pure virtual function pvf in B, if 0 doesn't provide a definition for pvf, pvf becomes a pure member function of 0, and 0 will also be an abstract class. For example, using the class shape previously outlined, class circle: public shape { II circle derived from II abstract class int radius; II private public: void rotate(int) { } void draw() ; II II /I II II virtual function defined: no action to rotate a circle circle::draw must be defined somewhere Member functions can be called from a constructor of an abstract class, but calling a pure virtual function directly or indirectly from such a constructor provokes a run-time error. c++ scope The lexical scoping rules for C++, apart from class scope, follow the general rules for C, with the proviso that C++, unlike C, permits both data and function declarations to appear wherever a statement may appear. The latter flexibility means that care is needed when interpreting such phrases as "enclosing scope" and "point of declaration." Chapter 3, C++ specifics 143 Class seope The name M of a member of a class X has class scope "local to X;" it can only be used in the following situations: • In member functions of X • In expressions such as x.M, where x is an object of X • In expressions such as xptr->M, where xptr is a pointer to an object of X • In expressions such as X::M or D::M, where 0 is a derived class of X • In forward references within the class of which it is a member. Names of functions declared as friends of X are not members of X; their names simply have enclosing scope. Hiding A name can be hidden by an explicit declaration of the same name in an enclosed block or in a class. A hidden class member is still accessible using the scope modifier with a class name: X::M. A hidden file scope (global) name can be referenced with the unary operator ::; for example, ::g. A class name X can be hidden by the name of an object, function, or enumerator declared within the scope of X, regardless of the order in which the names are declared. However, the hidden class name X can still be accessed by prefixing X with the appropriate keyword: class, struct, or union. The point of declaration for a name x is immediately after its complete declaration but before its initializer, if one exists. c++ seoping rules summary The following rules apply to all names, including typedef names and class names, provided that C++ allows such names in the particular context discussed: 1. The name itself is tested for ambiguity. If no ambiguities are detected within its scope, the access sequence is initiated. 2. If no access control errors occur, the type of the object, function, class, typedef, and so on, is tested. 144 Borland C++ Programmer's Guide 3. If the name is used outside any function and class, or is prefixed by the unary scope access operator ::, and if the name is not qualified by the binary:: operator or the member selection operators. and ->, then the name must be a global object, function, or enumerator. 4. If the name n appears in any of the forms X::n, x.n (where x is an object of X or a reference to X), or ptr->n (where ptr is a pointer to X), then n is the name of a member of X or the member of a class from which X is derived. 5. Any name not covered so far that is used in a static member function must be declared in the block in which it occurs or in an enclosing block, or be a global name. The declaration of a local name n hides declarations of n in enclosing blocks and global declarations of n. Names in different scopes are not overloaded. 6. Any name not covered so far that is used in a nonstatic member function of class X must be declared in the block in which it occurs or in an enclosing block, be a member of class X or a base class of X, or be a global name. The declaration of a local name n hides declarations of n in enclosing blocks, members of the function's class, and global declarations of n. The declaration of a member name hides declarations of the same name in base classes. 7. The name of a function argument in a function definition is in the scope of the outermost block of the function. The name of a function argument in a non-defining function declaration has no scope at all. The scope of a default argument is determined by the point of declaration of its argument, but it can't access local variables or nonstatic class members. Default arguments are evaluated at each point of call. S. A constructor initializer (see ctor-initializer in the class declarator syntax, Table 2.3 on page 37) is evaluated in the scope of the outermost block of its constructor, so it can refer to the constructor's argument names. Templates For a discussion of templates in the container class library see Chapter 6, page 224. Chapter 3, C++ specifics Templates, also called generics or parameterized types, allow you to construct a family of related functions or classes. In this section, we'll introduce the basic concept then some specific points. 145 Syntax: Template-declaration: template < template-argument-list > declaration template-argument-list: template-argument template-argument-list, template argument template-argument: type-argument argumen t-declara tion type-argument: class identifier template-class-name: template-name < template-arg-list > template-arg-list: template-arg template-arg-list , template-arg tern pIa te-arg: expression type-name Function templates Consider a function max(x,y) that returns the larger of its two arguments. x and y can be of any type that has the ability to be ordered. But, since C++ is a strongly typed language, it expects the types of the parameters x and y to be declared at compile time. Without using templates, many overloaded versions of maxO are required, one for each data type to be supported, even though the code for each version is essentially identical. Each version compares the arguments and returns the larger. For example, int max(int x, int y) { return (X > y) ? X : y; long max (long x, long y) { return (x > y) ? X : y; followed by other versions of max. 146 Borland c++ Programmer's Guide One way around this problem is to use a macro: #define max(x,y) ((x> y) ? x : y) However, using the #define circumvents the type-checking mechanism that makes C++ such an improvement over C. In fact, this use of macros is almost obsolete in C++. Clearly, the intent of max(x,y) to compare compatible types. Unfortunately, using the macro allows a comparison between an int and a struct, which are incompatible. Another problem with the macro approach is that substitution will be performed where you don't want it to be: class Faa pUblic: int max(int, int); II Results in syntax error; this gets expanded! ! ! I I ... }; By using a template instead, you can define a pattern for a family of related overloaded functions by letting the data type itself be a parameter: Function template definition template T max(T x, T y) { return (x > y) ? x : y; }; The data type is represented by the template argument: . When used in an application, the compiler generates the appropriate function according to the data type actually used in the call: int i; Myclass a, b; int j = max(i,O); Myclass m = max(a,b); .. Chapter 3, C++ speCifics II arguments are integers II arguments are type Myclass Any data type (not just a class) can be used for . The compiler takes care of calling the appropriate operator>O, so you can use max with arguments of any type for which operator>O is defined. 147 Overriding a template function The previous example is called a function template (or generic function, if you like). A specific instantiation of a function template is called a template function. You can override the generation of a template function for a specific type with a non-template function: #include char *max(char *x, char *y} { return(strcmp(x,Y}>O} ?X:Yi If you call the function with string arguments, it's executed in place of the automatic template function. In this case, calling the function avoided a meaningless comparison between two pointers. Only trivial argument conversions are performed with compilergenerated template functions. The argument type(s) of a template function must use all of the template formal agruments. If it doesn't there is no way of deducing the actual values for the unused template arguments when the function is called. Implicit and explicit template functions When doing overload resolution (following the steps of looking for an exact match), the compiler in gores template functions that have been generated implicitly by the compiler. template T max(T a, T b) { return void (a > b) ? a : bi f(int i, char c) max (i, max (c, max (i, max (c, i) i c) i c); i); II II II II calls max(int ,int } calls max (char,char) no match for max(int,char} no match for max(char,int} This code results in the following error messages. Could not find a match for 'max(int,char), in function f(int,char) Could not find a match for 'max(char,int), in function f(int,char) If the user explicitly declares a template function, this function, on the other hand, will participate fully in overload resolution. For example: 148 Borland C++ Programmer's Guide template T max{T a, T b) { return (a > b) ? a : b; int max{int, int); void f{int i, char c) max{i, max(c, max{i, max{c, i) ; c) ; c) ; i) ; II declare max(int,int) explicitly II II II II calls calls calls calls max{int ,int ) max {char, char) max{int,int) max{int,int) Class templates A class template (also called a generic class or class generator) allows you to define a pattern for class definitions. Generic container classes are good examples. Consider the following example of a vector class (a one-dimensional array). Whether you have a vector of integers or any other type, the basic operations performed on the type are the same (insert, delete, index, and so on). With the element type treated as a type parameter to the class, the system will generate type-safe class definitions on the fly: Class template definition #include template class Vector T *data; int size; public: Vector (int) ; -Vector() {delete[J data;} T& operator[J (int i) {return data[iJ;} }; II Note the syntax for out-of-line definitions: template Vector ::Vector(int n) { data size = new T[nJ ; = n; }; main{) { Vector x(5) ;11 Generate a vector of ints Chapter 3, C++ specifics 149 for (int i = 0; i < 5; Hi) x [i] = i; for (i = 0; i < 5; Hi) cout « xli] « ' '; cout « ' \n' ; return 0; II Output will be: 0 1 2 3 4 As with function templates, an explicit template class definition may be provided to override the automatic definition for a given type: class Vector { ... }; The symbol Vector must be always be accompanied by a data type in angle brackets. It cannot appear alone, except in some cases in the original template definition. For a more complete implementation of a vector class, see the file vectimp.h in the container class library source code, found in the \BORLANDC\CLASSLIB\INCLUDE subdirectory. Also see Chapter 6, "The container class library," page 230. Arguments Although these examples use only one template argument, multiple arguments are allowed. Template arguments can also represent values in addition to data types: template class Buffer { ... }; Non-type template arguments such as size can have default arguments. The value supplied for a non-type template argument must be a constant expression: canst int N = 128; int i = 256; Buffer bl;11 OK Buffer b2;11 Error: i is not constant Since each instantiation of a template class is indeed a class, it receives its own copy of static members. Similarly, template functions get their own copy of static local variables. Angle brackets Take care when using the right angle bracket character upon instantiation: Buffer lOa? 1024 : 64» 150 buf; Borland C++ Programmer's Guide In the preceding example, without the parentheses around the second argument, the> between x and 100 would prematurely close the template argument list. Type-safe generic lists In general, when you need to write lots of nearly identical things, think templates. The problems with the following class definition, a generic list class, class GList public: void insert ( void * ) i void *peek ( ) i I I ... }i are that it isn't type-safe and common solutions ne~d repeated class definitions. Since there's no type checking on what gets inserted, you have no way of knowing what you'll get back out. You can solve the type-safe problem by writing a wrapper class: class FooList : public GList pUblic: void insert ( Foo *f ) { GList::insert( f ) i } Foo *peek() { return (Foo *)GList::peek()i } I I ... }i This is type-safe. insert will only take arguments of type pointerto-Foo or object-derived-from-Foo, so the underlying container will only hold pointers that in fact point to something of type Foo. This means that the cast in FooList::peek is always safe, and you've created a true FooList. Now to do the same thing for a BarList, a BazList, and so on, you need repeated separate class definitions. To solve the problem of repeated class definitions and be type-safe, once again, templates to the rescue: Type-safe generic list class definition template class List : public GList { public: void insert ( T *t ) { GList::insert( t ) i T *peek () { return (T *) GList: :peek () i } I I ... }i List fListi II create a FooList class and an instance" named fList. List bListi II create a BarList class and an instance Chapter 3, C++ specifics 151 named bList. List zList; II create a BazList class and an instance named zList. By using templates, you can create whatever type-safe lists you want, as needed, with a simple declaration. And there's no code generated by the type conversions from each wrapper class so there's no run-time overhead imposed by this type safety. Eliminating pointers Template definition that eliminates pointers Another design technique is to include actual objects, making pointers unnecessary. This can also reduce the number of virtual function calls required, since the compiler knows the actual types of the objects. This is a big benefit if the virtual functions are small enough to be effectively inlined. It's difficult to inline virtual functions when called through pointers, because the compiler doesn't know the actual types of the objects being pointed to. template aBase { I I ... private: T buffer; }; class anObject public aSubject, public aBase { I I ... }; All the functions in aBase can call functions defined in aFilebuf directly, without having to go through a pointer. And if any of the functions in aFilebuf can be inlined, you'll get a speed improvement, since templates allow them to be inlined. Template compiler switches The -Jg family of switches control how instances of templates are generated by the compiler. Every template instance encountered by the compiler will be affected by the value of the switch at the point where the first occurence of that particular instance is seen by the compiler. For template functions the switch applies to the function instances; for template classes, it will apply to all member functions and s.tatic data members of the template class. In all cases this switch applies only to compiler-generated template instances, and never to user-defined instances, although 152 Borland C++ Programmer's Guide it can be used to tell the compiler which instances will be userdefined so that they are not generated from the template. -Jg Default value of the switch. All template instances first encountered when this switch value is in effect will be generated, such that if several compilation units generate the same template instance, the linker will merge them to produce a single copy of the instance. This is the most convenient approach to generating template instances, because it's almost entirely automatic. Note, though, that in order to be able to generate the template instances, the compiler must have the function body (in case of a template function) or bodies of member functions and definitions for static data members (in case of a template class). -Jgd Instructs the compiler to generate public definitions for template instances. This is similar to -Jg, but if more than one compilation unit generates a definition for the same template instance, the linker will report public symbol redefinition errors. -Jgx Instructs the compiler to generate external references to template instances. Some other compilation unit must generate a public definition for that template instance (using the -Jgd switch) so that the external references can be satisfied. Using template switches Using the -Jg family of switches, there are two basic approaches for generating template instances: 1. Include the function body (for a function template) or member function and static data member definitions (for a template class) in the header file that defines the particular template, and use the default setting of the template switch (-Jg). If some instances of the template are user-defined, the declarations (prototypes, for example) for them should be included in the same header, but preceded by #pragma option -Jgx, thus letting the compiler know that it should not generate those particular instances. Here's an example of a template function header file: II Declare a template function along with its body template void sort(T* array, int size) { body of template function goes here ... Chapter 3, C++ specifics 153 II Sorting of 'int' elements done by user-defined instance #pragma option -Jgx extern void sort(int* array, int size)i II Restore the template switch to its original state #pragma option -Jg. If the preceding header file is included in a C++ source file, the 'sort' template can be used without worrying about how the various instances are generated (with the exception of 'sort' for int arrays, which is declared as a user-defined instance, and whose definition must be defined by the user). 2. Compile all of the source files comprising the program with the -Jgx switch (causing external references to templates to be generated); this way, template bodies don't need to appear in header files. In order to provide the definitions for all of the template instances, add a file (or files) to the program that includes the template bodies (including any user-defined instance definitions), and list all the template instances needed in the rest of the program, to provide the necessary public symbol definitions. Compile the file (or files) with the -Jgd switch. Here's an example: II vector.h template class vector { elem * valuei public: vector()i elem & operator[J (int index) { return value[indexJi } }i II MAIN.CPP #include "vector.h" II Tell the compiler that the template instances that follow II will be defined elsewhere. #pragma option -Jgx II Use two instances of the 'vector' template class. vector int_lOOi vector char_lOi main() { return int_lOO[OJ + char_lO[OJi 154 Borland C++ Programmer's Guide II TEMPLATE.CPP #include #include "vector.h" II Define any template bodies template vector : :vector(} { value = new elem[sizeli memset(value, 0, size * sizeof(elem}}i II Generate the necessary instances #pragma option -Jgd typedef vector fake_int_100i typedef vector fake_char_10; Chapter 3, C++ specifics 155 156 Borland C++ Programmer's Guide c H A p T E R 4 The preprocessor Although Borland C++ uses an integrated single-pass compiler for its IDE and command-line versions, it is useful to retain the terminology associated with earlier multipass compilers. The independent preprocessor is documented online. With a multipass compiler, a first pass of the source text would pull in any include files, test for any conditional-compilation directives, expand any macros, and produce an intermediate file for further compiler passes. Since the IDE and command-line versions of the Borland C++ compiler perform this first pass with no intermediate output, Borland C++ provides an independent preprocessor, CPP.EXE, that does produce such an output file. The independent preprocessor is useful as a debugging aid, l~tting you see the net result of include directives, conditional compilation directives, and complex macro expansions. The following discussion on preprocessor directives, their syntax and semantics, therefore, applies both to the CPP preprocessor and to the preprocessor functionality built into the Borland C++ compiler. The preprocessor detects preprocessor directives (a/so known as contro/lines) and parses the tokens embedded in them. The Borland C++ preprocessor includes a sophisticated macro processor that scans your source code before the compiler itself gets to work. The preprocessor gives you great power and flexibility in the following areas: • Defining macros that reduce programming effort and improve your source code legibility. Some macros can also eliminate the overhead of function calls. Chapter 4, The preprocessor 157 • Including text from other files, such as header files containing standard library and user-supplied function prototypes and manifest constants . • Setting up conditional compilations for improved portability and for debugging sessions. Preprocessor directives are usually placed at the beginning of your source code, but they can legally appear at any point in a program. Any line with a leading # is taken as a preprocessing directive, unless the # is within a string literal, in a character constant, or embedded in a comment. The initial # can be preceded or followed by whitespace (excluding new lines). The full syntax for Borland C++'s preprocessor directives is given ' in the next table. Table 4.1: Borland C++ preprocessing directives syntax preprocessing-file: group group: group-part group group-part group-part: newline if-section control-line if-section: if-group endif-line if-group: #if constant-expression newline #ifdef identifier newline #ifndef identifier newline elif-groups: elif-group elit-groups elif-group elif-group: #elif constant-expression newline else-group: #else newline endif-line: #endif newline control-line: #include #define #define #undef #line #error #pragma 158 pp-tokens newline identifier replacement-list newline identifier lparen newline newline #pragma warn action abbreviation newline #pragma inline newline # newline action: one of + - . abbreviation: nondigit nondigit nondigit lparen: the left parenthesis character without preceding whitespace replacement-list: pp-tokens: preprocessing-token pp-tokens preprocessing-token preprocessing-token: header-name (only within an #inc1ude directive) identifier (no keyword distinction) constant string-literal operator punctuator each non-whitespace character that cannot be one of the preceding header-name: h-char-sequence: h-char h-char-sequence h-char h-char: any character in the source character set except the newline (\n) or greater than (» character newline: the newline character Borland C++ Programmer's Guide Null directive # The null directive consists of a line containing the single character #. This directive is always ignored. The #define and #undef directives The #define directive defines a macro. Macros provide a mechanism for token replacement with or without a set of formal, function-like parameters. Simple #define macros In the simple case with no parameters, the syntax is as follows: #define macro_identifier Each occurrence of macro_identifier in your source code following this control line will be replaced in situ with the possibly empty token_sequence (there are some exceptions, which are noted later). Such replacements are known as macro expansions. The token sequence is sometimes called the body of the macro. Any occurrences of the macro identifier found within literal strings, character constants, or comments in the source code are not expanded. An empty token sequence results in the effective removal of each affected macro identifier from the source code: #define HI "Have a nice day! #define empty #define NIL puts(HI)i /* puts(NIL)i /* puts(l empt Y")i /* /* NOR any expansion 11 expands to puts ("Have a nice daY!")i */ expands to putS("I); */ NO expansion of empty! */ of the empty within comments! */ After each individual macro expansion, a further scan is made of the newly expanded text. This allows for the possibility of nested macros: The expanded text may contain macro identifiers that are subject to replacement. However, if the macro expands into what looks like a preprocessing directive, such a directive will not be recognized by the preprocessor: Chapter 4, The preprocessor 159 #define GETSTD #include GETSTD . / * cornpil er error */ GET5TD will expand to #include . However, the preprocessor itself will not obey this apparently legal directive, but will pass it verbatim to the compiler. The compiler will reject #include as illegal input. A macro won't be expanded during its own expansion. 50 #define A A won't expand indefinitely. The #undef directive You can undefine a macro using the #undef directive: #undef macro_identifier This line detaches any previous token sequence from the macro identifier; the macro definition has been forgotten, and the macro identifier is undefined. No macro expansion occurs within #undef lines. The state of being defined or undefined turns out to be an important property of an identifier, regardless of the actual definition. The #ifdef and #ifndef conditional directives, used to test whether any identifier is currently defined or not, offer a flexible mechanism for controlling many aspects of a compilation. After a macro identifier has been undefined, it can be redefined with #define, using the same or a different token sequence. #define BLOCK_SIZE 512 buff = BLOCK_SIZE*blks; /* expands as 512*blks * #undef BLOCK_SIZE /* use of BLOCK_SIZE now would be illegal "unknown" identifier */ #define BLOCK_SIZE 128 /* redefinition */ buf = BLOCK_SIZE*blks; /* expands as 128*blks */ Attempting to redefine an already defined macro identifier will result in a warning unless the new definition is exactly the same, token-by-token definition as the existing one. The preferred strategy where definitions may exist in other header files is as follows: 160 Borland C++ Programmer's Guide #ifndef BLOCK_SIZE #define BLOCK_SIZE 512 #endif The middle line is bypassed if BLOCK_SIZE is currently defined; if BLOCK_SIZE is not currently defined, the middle line is invoked to define it. No semicolon (;) is needed to terminate a preprocessor directive. Any character found in the token sequence, including semicolons, will appear in the macro expansion. The token sequence terminates at the first non-backslashed new line encountered. Any sequence of whitespace, including comments in the token sequence, is replaced with a single space character. Assembly language programmers must resist the temptation to write: #define BLOCK_SIZE = 512 /* ?? token sequence includes the = */ The -D and-U options Identifiers can be defined and undefined using the command-line compiler options -0 and -U (see Chapter 5, "The command-line compiler," in the User's Guide). Identifiers can be defined, but not explicitly undefined, from the IDE Options I Compiler I Code Generation dialog box (see Chapter 2, "IDE basics," also in the User's Guide). The command line BCc -Ddebug=li paradox=Oi x -Umysym myprog.c is equivalent to placing #define debug 1 #define paradox 0 #define X #undef mysym in the program. The Define option Identifiers can be defined, but not explicitly undefined, from the Defines input box in the Code Generation I Options dialog box (under a I C I Code Generation) (see Chapter 2, "IDE basics," in the User's Guide). Chapter 4, The preprocessor 161 Keywords and protected words It is legal but ill-advised to use Borland C++ keywords as macro identifiers: #define int long #define INT long /* legal but probably catastrophic */ /* legal and possibly useful */ The following predefined global identifiers may not appear immediately following a #define or #undef directive: Note the double underscores, leading and trailing. Macros with parameters __STDC__ __ FILE__ LlNE __ DATE__ __TIME __ The following syntax is used to define a macro with parameters: #define macro_identifier( { #include "header_name" #include macro_identifier The third variant assumes that neither < nor" appears as the first non-whitespace character following #include; further, it assumes that a macro definition exists that will expand the macro identifier into a valid delimited header name with either of the or "header_name" formats. The first and second variant imply that no macro expansion will be attempted; in other words, header_name is never scanned for macro identifiers. header_name must be a valid DOS file name with an extension (traditionally .h for header) and optional path name and path delimiters. The preprocessor removes the #include line and conceptually replaces it with the entire text of the header file at that point in the source code. The source code itself is not changed, but the compiler "sees" the enlarged text. The placement of the #include may therefore influence the scope and duration of any identifiers in the included file. If you place an explicit path in the header_name, only that directory will be searched. The difference between the and "header_name" formats lies in the searching algorithm employed in trying to locate the include file; these algorithms are described in the following two sections. Chapter 4, The preprocessor 165 Header file search with Header file search with \\ header_name II The variant specifies a standard include file; the search is made successively in each of the include directories in the order they are defined. If the file is not located in any of the default directories, an error message is issued. The "header_name" variant specifies a user-supplied include file; the file is sought first in the current directory (usually the directory holding the source file being compiled). If the file is not found there, the search continues in the include directories as in the situation. The following example clarifies these differences: #include /* header in standard include directory */ #define myinclud C:\BORLANDC\INCLUDE\MYSTUFF.H" /* Note: Single backslashes OK here; within a C statement you would 'need "C: \ \ BORLANDC \ \ INCLUDE \ \MYSTUFF. H" * / #include myinclud /* macro expansion */ #include "myinclud.h" /* no macro expansion */ After expansion, the second #include statement causes the preprocessor to look in C: \ BORLANDC \INCLUDE \MYSTUFF.H and nowhere else. The third #include causes it to look for MYINCLUD.H in the current directory, then in the default directories. Conditional compilation Borland C++ supports conditional compilation by replacing the appropriate source-code lines with a blank line. The lines thus ignored are those beginning with # (except the #if, #ifdef, #ifndef, #else, #elif, and #endif directives), as well as any lines that are not to be compiled as a result of the directives. All conditional compilation directives must be completed in the source or include file in which they are begun. 166 Borland C++ Programmer's Guide The #if, #elif, #else, and #endif conditional directives The conditional directives #if, #elif, #else, and #endif work like the normal C conditional operators. They are used as follows: #if cons tan t-expression-l <#elif constant-expression-2 newline section-2> <#elif constant-expression-n newline section-n> <#else final-section> #endif If the constant-expression-l (subject to macro expansion) evaluates to nonzero (true), the lines of code (possibly empty) represented by section-l, whether preprocessor command lines or normal source lines, are preprocessed and, as appropriate, passed to the Borland C++ compiler. Otherwise, if constant-expression-l evaluates to zero (false), section-l is ignored (no macro expansion and no compilation). In the true case, after section-l has been preprocessed, control passes to the matching #endif (which ends this conditional interlude) and continues with next-section. In the false case, control passes to the next #elif line (if any) where constant-expression-2 is evaluated. If true, section-2 is processed, after which control moves on to the matching #endif. Otherwise, if constantexpression-2 is false, control passes to the next #elif, and so on, until either #else or #endif is reached. The optional #else is used as an alternative condition for which all previous tests have proved false. The #endif ends the conditional sequence. The processed section can contain further conditional clauses, nested to any depth; each #if must be carefully balanced with a closing #endif. The net result of the preceding scenario is that only one section (possibly empty) is passed on for further processing. The bypassed sections are relevant only for keeping track of any nested conditionals, so that each #if can be matched with its correct #endif. The constant expressions to be tested must evaluate to a constant integral value. Chapter 4, The preprocessor 167 The operator defined The defined operator offers an alternative, more flexible way of testing whether combinations of identifiers are defined or not. It is valid only in #if and #elif expressions. The expression defined(identifier) or defined identifier (parentheses are optional) evaluates to 1 (true) if the symbol has been previously defined (using #define) and has not been subsequently undefined (using #undef); otherwise, it evaluates to o (false). So the directive #if defined (mysym) is the same as #ifdef mysym The advantage is that you can use defined repeatedly in a complex expression following the #if directive, such as #if defined (mysym) && !defined(yoursym) The #ifdef and #ifndef conditional directives The #ifdef and #ifndef conditional directives let you test whether an identifier is currently defined or not, that is, whether a previous #define command has been processed for that identifier and is still in force. The line #ifdef identifier has exactly the same effect as #if 1 if identifier is currently defined, and the same effect as #if 0 if identifier is currently undefined. #ifndef tests true for the "not-defined" condition, so the line #ifndef identifier has exactly the same effect as #if 0 if identifier is currently defined, and the same effect as #if 1 if identifier is currently undefined. 168 Borland c++ Programmer's Guide The syntax thereafter follows that of the #if, #elif, #else, and #endif given in the previous section. An identifier defined as NULL is considered to be defined. The #Iine line control directive You can use the #Iine command to supply line numbers to a program for cross-reference and error reporting. If your program consists of sections derived from some other program file, it is often useful to mark such sections with the line numbers of the original source rather than the normal sequential line numbers derived from the composite program. The syntax is #line integer_constant <"filename"> indicating that the following source line originally came from line number integer_constant of filename. Once the filename has been registered, subsequent #line commands relating to that file can omit the explicit filename argument. The inclusion of stdio.h means that the preprocessor output will be somewhat large. /* TEMP.C: An example of the #line directive */ #inelude #line 4 "junk.e" void main () { printf (" in line %d of %s", __LINE __ , __ FILE __ ) ; #line 12 "temp.e" print f ( "\n" ) ; printf(" in line %d of %s", __LINE __ ,_ JILE __ ); #line 8 printf("\n") ; printf (" in line %d of %s", __LINE__ , __ FILE__ ) ; If you run TEMP.C through CPP (cpp temp), you'll get an output file TEMP.!; it should look like this: temp.e 1: C:\BORLAND\BORLANDC\CPP\INCLUDE\STDIO.H 1: C:\BORLAND\BORLANDC\CPP\INCLUDE\STDIO.H 2: C:\BORLAND\BORLANDC\CPP\INCLUDE\STDIO.H 3: We've eliminated most of the stdio.h portion. Chapter 4, The preprocessor C:\BORLAND\BORLANDC\CPP\INCLUDE\STDIO.H 212: C:\BORLAND\BORLANDC\CPP\INCLUDE\STDIO.H 213: 169 temp.c 2: temp.c 3: junk.c 4: void main() junk.c 5: { junk.c 6: printf(" in line %d of %s",6,"junk.c"); junk.c 7: temp.c 12: printf("\n"); temp.c 13: printf(" in line %d of %s",13,"temp.c"); temp.c 14: temp.c 8: printf("\n"); temp.c 9: printf(" in line %d of %s",9, "temp.c"); temp. c'. 10: } temp. c 11: If you then compile and run TEMP.C, you'll get the output shown here: in line 6 of junk.c in line 13 of temp.c in line 9 of temp.c Macros are expanded in #line arguments as they are in the #include directive. The #line directive is primarily used by utilities that produce C code as output, and not in human-written code. The #error directive The #error directive has the following syntax: #error errmsg This generates the message: Error: filename line# : Error directive: errmsg This directive is usually embedded in a preprocessor conditional that catches some undesired compile~time condition. In the normal case, that condition will be false. If the condition is true, you want the compiler to print an error message and stop the compile. You do this by putting an #error directive within a conditional that is true for the undesired case. For example, suppose you #define MYVAL, which must be either o or 1. You could then include the following conditional in your source code to test for an incorrect value of MYVAL: 170 Borland C++ Programmer's Guide #if (MYVAL != 0 && MYVAL != 1) #error MYVAL must be defined to either 0 or 1 #endif The #progmo directive The #pragma directive permits implementation-specific directives of the form: #pragma directive-name With #pragma, Borland c++ can define whatever directives it desires without interfering with other compilers that support #pragma. If the compiler doesn't recognize directive-name, it ignores the #pragma directive without any error or warning message. Borland C++ supports the following #pragma directives: &I #pragma Borland C++ only argsused 1'1 #pragma exit .. #pragma hdrfile • #pragma hdrstop #pragma inline .. #pragma option • #pragma saveregs .. #pragma startup .. #pragma warn II #pragma intrinsic #pragma argsused The argsused pragma is only allowed between function definitions, and it affects only the next function. It disables the warning message: "Parameter name is never used in function tunc-name" #pragma exit and #pragma startup Chapter 4, The preprocessor These two pragmas allow the program to specify function{s) that should be called either upon program startup (before the main 171 function is called), or program exit (just before the program terminates through _exit). The syntax is as follows: #pragma startup function-name #pragma exit function-name The specified function-name must be a previously declared function taking no arguments and returning void; in other words, it should be declared as void func (void) ; Priorities from a to 63 are used bV the C libraries, and should not be used bV the user. The optional priority parameter should be an integer in the range 64 to 255. The highest priority is O. Functions with higher priorities are called first at startup and last at exit. If you don't specify a priority, it defaults to 100. For example, #include Note that the function name used in pragma startup or exit must be defined (or declared) before the pragma line is reached. void startFunc(void) { printf("Startup function.\n"); #pragrna startup startFunc 64 /* priority 64 --> called first at startup */ void exitFunc(void) { printf ("Wrapping up execution. \n n) ; #pragrna exit exitFunc /* default priority is 100 */ void main(void) ( printf("This is main.\n"); #pragmo hdrfile , This directive sets the name of the file in which to store precompiled headers. The default file name is TCDEF.SYM. The syntax is #pragma hdrfile "filename.5YM" 172 Borland c++ Programmer's Guide See Appendix 0, "Precompiled headers" in the User's Guide for more details. If you aren't using precompiled headers, this directive has no , effect. You can use the command-line compiler option -H=filename or the Precompiled Header (0 IC ICode Generation) to change the name of the file used to store precompiled headers. #pragma hdrstop This directive terminates the list of header files that are eligible for precompilation. You can use it to reduce the amount of disk space used by precompiled headers. (See Appendix 0 in the User's Guide for more on precompiled headers.) #pragma inline This directive is equivalent to the -8 command-line compiler option or the IDE inline option. It tells the compiler that there is inline assembly language code in your program (see Chapter 12, "BASM and inline assembly"). The syntax is #pragma inline . This is best placed at the top of the file, since the compiler restarts itself with the -8 option when it encounters #pragma inline. Actually, you can leave off both the -8 option and the #pragma inline directive, and the compiler will restart itself anyway as soon as it encounters a5m statements; The purpose of the option and the directive is to save some compilation time. #pragma intrinsic #pragma intrinsic is documented in Appendix A, "The Optimizer" in the User's Guide. #pragma option Use #pragma option to include command-line options within your program code. The syntax is #p.ragma option The command-line compiler options are defined in Chapter 5 in the User's Guide. Chapter 4, The preprocessor [options ... ] options can be any command-line option (except those listed in the following paragraph). Any number of options can appear in one directive. Any of the toggle options (such as -a or -K) can be turned on and off as on the command line. For these toggle options, you can also put a period following the option to return 173 the option to its command-line, configuration file, or option-menu setting. This allows you to temporarily change an option, then return it to its default, without you having to remember (or even needing to know) what. the exact default setting was. Options that cannot appear in a pragma option include -8 -H -Q -c -I filename -Lfilename -Ixset -M -S -T -Uname -V -0 -X -V -dname -Dname string -efilename = -E -Fx -P You can use #pragmas, #includes, #define, and some #ifs before 1. The use of any macro name that begins with two underscores (and is therefore a possible built-in macro) in an #if, #ifdef, #ifndef or #elif directive. 2. The occurrence of the first real token (the first C or C++ declaration). Certain command-line options can only appear in a #pragma option command before these events. These options are -Efilename -m * -u ~* ~~~ -W -i# -ofilename -z * Other options can be changed anywhere. The following options will only affect the compiler if they get changed between functions or object declarations: -1 -h -r -2 -k -rd -a -N -v -ff -0 -y -G -p -Z The following options can be changed at any time and take effect immediately: See page 352 for more on using #pragma option with for objects. 174 -A -gn -b -jn -c -K -d -wxxx -zE -zF -zH Borland C++ Programmer's Guide They can additionally appear followed by a dot (.) to reset the option to its command-line state. #pragma saveregs The saveregs pragma guarantees that a huge function will not change the value of any of the registers when it is entered. This directive is sometimes needed for interfacing with assembly language code. The directive should be placed immediately before the function definition. It applies to that function alone. #pragma warn The warn directive lets you override specific -wxxx command-line options or check Display Warnings settings in the Options I Compiler IMessages dialog boxes. For example, if your source code contains the directives #pragma warn txxx #pragma warn -yyy #pragma warn .zzz the xxx warning will be turned on (even if on the Options I Compiler IMessages menu it was toggled to Of!>, the yyy warning will be turned off, and the zzz warning will be restored to the value it had when compilation of the file began. A complete list of the three-letter abbreviations and the warnings to which they apply is given in Chapter 5, "The command-line compiler" in the User's Guide. Predefined macros Borland C++ predefines certain global identifiers, each of which is discussed in this section. Except for __cplusplus and _Windows, each of these starts and ends with two underscore characters C _). These macros are also known as manifest constants. BCPLUSPLUS__ This macro is specific to Borland's C and C++ family of compilers. It is only defined for C++ compilation. If you've selected C++ Chapter 4, The preprocessor 175 compilation, it is defined as Ox0300, a hexadecimal constant. This increase in later releases. numeric value will This macro is specific to Borland's C and C++ family of compilers. It is defined as Ox0400, a hexadecimal constant. This numeric value will increase in later releases. __CDECL__ This macro is specific to Borland's C and C++ family of compilers. It signals that the -p flag was not used (the C radio button in the Entry /Exit Code Generation dialog box). Set to the integer constant 1 if calling was not used; otherwise, undefined. The following six symbols are defined based on the memory model chosen at compile time. __MEDIUM__ __COMPACT__ __HUGE __ __SMALL__ __ LARGE__ __TINY__ Only one is defined for any given compilation; the others, by definition, are undefined. For example, if you compile with the small model, the __SMALL__ macro is defined and the rest are not, so that the directive #if defined{ __ SMALL__ ) will be true, while #if defined{ __LARGE __ ) (or any of the others) will be false. The actual value for any of these defined macros is 1. _ _cplusplus This macro is defined as 1 if in C++ mode; it's undefined otherwise. This allows you to write a module that will be compiled sometimes as C and sometimes as C++. Using conditional compilation, you can control which C and C++ parts are included. 176 Borland C++ Programmer's Guide This macro provides the date the preprocessor began processing the current source file (as a string literal). Each inclusion of __ DATE__ in a given file contains the same value, regardless of how long the processing takes. The date appears in the format mmm dd yyyy, where mmm equals the month (Jan, Feb, and so forth), dd equals the day (1 to 31, with the first character of dd a blank if the value is less than 10), and yyyy equals the year (1990, 1991, and so forth). This macro is specific to Borland's C and C++ family of compilers. It is defined to be 1 if you compile a module with the -WD command-line compiler option or are using the Windows DLL All Functions Exportable radio button (0 IC IC I Entry /Exit Code) to generate code for Windows DLLs; otherwise it remains undefined. This macro provides the name of the current source file being processed (as a string literal). This macro changes whenever the compiler processes an #include directive or a #line directive, or when the include file is complete. This macro provides the number of the current source-file line being processed (as a decimal constant). Normally, the first line of a source file is defined to be 1, through the #line directive can affect this. See page 169 for information on the #line directive. This macro is specific to Borland's C/C++ family of compilers. It provides the integer constant 1 for all compilations. Chapter 4, The preprocessor 177 __ OVERLAY__ This macro is specific to Borland's C and C++ family of compilers. It is predefined to be 1 if you compile a module with the -y option (enable overlay support). If you don't enable overlay support, this macro is undefined. This macro is specific to Borland's C and C++ family of compilers. It signals that the -p flag or the Pascal calling convention (0 I C I C I Exit/Entry) has been used. The macro is set to the integer constant 1 if used; otherwise, it remains undefined. This macro is defined as the constant 1 if you compile with the ANSI compatibility flag (-A) or ANSI radio button (Source Options); otherwise, the macro is undefined. __TCPLUSPLUS_'_ This macro is specific to Borland's C and C++ family of compilers. It is only defined for C++ compilation. If you've selected C++ compilation, it is defined as Ox0300, a hexadecimal constant. This numeric value will increase in later releases. __TEMPLATES__ This macro is specific to Borland's C and C++ family of compilers. It is defined as 1 for C++ 'files (meaning that Borland C++ supports templates); it's undefined otherwise. This macro keeps track of the time the preprocessor began processing the current source file (as a string literal). As with __DATE_ -' each inclusion of __TIME__ contains the same value, regardless of how long the processing takes. It takes the format hh:mm:ss, where hh equals the hour (00 to 23), mm equals minutes (00 to 59), and ss equals seconds (00 to 59). 178 Borland C++ Programmer's Guide This macro is specific to Borland's C and C++ family of compilers. It is defined as Ox0400, a hexadecimal constant. This numeric value will increase in later releases. _Windows Indicates that Windows-specific code is being generated. This macro is defined if you compile a module with any of the -W command-line compiler options enabled (generate Windows applications). If you don't enable any of these options, this macro is undefined. Chapter 4, The preprocessor 179 180 Borland C++ Programmer's Guide c A H p T E R 5 Using C++ streams This chapter is divided into two sections: a brief, practical overview of using C++ stream I/O, and a reference section to the C++ stream class library. Stream input/output in C++ (commonly referred to as iostreams, or merely streams) provide all the functionality to the stdio library in C. iostreams are used to convert typed objects into readable text, and vice versa. Streams may also read and write binary data. The C++ language allows you to define or overload I/O functions and operators that are then called automatically for corresponding user-defined types. What is a stream? A stream is an abstraction referring to any flow of data from a source (or producer) to a sink (or consumer). We also use the synonyms extracting, getting, and fetching when speaking of inputting characters from a source; and inserting, putting, or storing when speaking of outputting characters to a sink. Classes are provided that support console output (constrea.h), memory buffers . (iostream.h), files (fstream.h), and strings (strstrea.h) as sources or sinks (or both). Chapter 5, Using C++ streams 181 The iostream library The iostream library has two parallel families of classes: those derived from streambuf, and those derived from ios. Both are low-level classes, each doing a different set of jobs. All stream classes have at least one of these two classes as a base class. Access from ios-based classes to streambuf-based classes is through a pointer. The streambuf class The streambuf class provides an interface to physical devices. streambuf provides general methods for buffering and handling streams when little or no formatting is required. streambuf is a useful base class employed by other parts of the iostream library, though you can also derive classes from it for your own functions and libraries. The classes conbuf, filebuf and strstreambuf are derived from streambuf. Figure 5.1 Class streambuf and its derived classes The ios class The class ios (and hence any of its derived classes) contains a pointer to a streambuf. It performs formatted I/O with errorchecking using a streambuf. An inheritance diagram for all the ios family of classes is found in Figure 5.2. For example, the ifstream class is derived from the istream and fstreambase classes, and istrstream is derived from istream and strstreambase. This diagram is not a simple hierarchy because of the generous use of multiple inheritance. With tnultiple inheritance, a single class can inherit from more than one base class. (The C++ language provides for virtual inheritance to avoid multiple declarations.) This means, for example, that all the members (data and functions) of iostream, istream, ostream, fstreambase, and ios are part of objects of the fstream class. All classes in the ios-based tree use a streambuf (or a fHebuf or strstreambuf, which are special cases of a streambuf) as its source and/or sink. 182 Borland C++ Programmer's Guide c++ programs start with four predefined open streams, declared as objects of withassign classes as follows: extern extern extern extern istream_withassign ostream_withassign ostream_withassign ostream_withassign cin; cout; cerri clog; II II II II Corresponds to stdin Corresponds to stdout Corresponds to stderr A buffered cerr Figure 5.2 Class ios and its derived classes By accepted practice, the arrows point from the derived class to the base class. ostream_withassign Output Stream output is accomplished with the insertion (or put to) operator, «. The standard left shift operator, «, is overloaded for output operations. Its left operand is an object of type ostream. Its right operand is any type for which stream output has been defined (that is, fundamental types or any types you have overloaded it for). For example, Chapter 5, Using C++ streams 183 cout « "Hello!\n"; writes the string "Hello!" to cout (the standard output stream, normally your screen) followed by a new line. The« operator associates from left to right and returns a reference to the ostream object for which it is invoked. This allows several insertions to be cascaded as follows: int i = 8; double d = 2.34; cout « "i = n « i « ", d = n « d « "\n"; This will write the following to standard output: i Fundamental types = 8, d = 2.34 The fundamental data types directly supported are char, short, int, long, char* (treated as a string), float, double, long double, and void*. Integral types are formatted according to the default rules for printf (unless you've changed these rules by setting various ios flags). For example, the following two output statements give the same result:· int i; long 1; cout « i « " " « 1;· printf("%d %ld", i, 1); The pointer (void *) inserter is used to display pointer addresses: int i; cout « &i; II display pointer address in hex Read the description of the ostream class (page 205) for other output functions. Output formatting Formatting for both input and output is determined by various format state flags contained in the class ios. The format flags are as follows: public: enum { skipws, left, right, 184 II skip whitespace on input II left-adjust output II right-adjust output Borland C++ Programmer's Guide internal, dec, oct, hex, showbase, showpoint, uppercase, showpos, scientific, fixed, uni tbuf, stdio, II II II II II II II II II pad after sign or base indicator decimal conversion octal conversion hexadecimal conversion show base indicator on output show decimal point (floating-point output) uppercase hex output show '+' with positive integers suffix floating-point numbers with exponential (E) notation on output II us~ fixed decimal point for floating-point numbers II flush all streams after insertion II flush stdout, stderr after insertion }i These flags are read and set with the flags, setf, and unsetf member functions (see class ios starting on page 199). Manipulators A simple way to change some of the format variables is to use a special function-like operator called a manipulator. Manipulators take a stream reference as an argument and return a reference to the same stream. You can embed manipulators in a chain of insertions (or extractions) to alter stream states as a side effect without actually performing any insertions (or extractions). For example, Parameterized manipulators must be called for each stream operation. #include #include II Required for parameterized manipulators. int main (void) { int i = 6789, j = 1234, k cout « setw(6)« cout « "\n"; cout « setw(6)« return(O)i = 10; «j« i « k « ji «setw(6)« «setw(6) .« k; } Produces this output: 678912346789101234 6789 1234 10 setw is a parameterized manipulator declared in iomanip.h. Other parameterized manipulators, setbase, setfill, setprecision, setiosflags and resetiosflags, work in the same way. To make use Chapter 5, Using C++ streams 185 of these, your program must include iomanip.h. You can write your own manipulators without parameters: #include II Tab and prefix the output with a dollar sign. ostream& money ( ostream& output) { return output « "\t$"; } int main (void) { float owed = 1.35, earned = 23.1; cout « money « owed « money « earned; return (0) ; } produces the following output: $1.35 $23.1 The non-parameterized manipulators dec, hex, and oct (declared in iostream.h) take no arguments and simply change the conversion base (and leave it changed): int i = 36; cout « dec « i « " " « hex « i « " " « oct« cout «dec; II Must reset to use decimal base. II displays 36 24 44 Table 5.1 Stream manipulators Manipulator «endl; Action Set decimal conversion base format flag. Set hexadecimal conversion base format flag. Set octal conversion base format flag. Extract whitespace characters. Insert newline and flush stream. Insert terminal null in string. Flush an ostream. Set conversion base format to base n (0,8, 10, or 16). a means the default: decimal on output, ANSI C rules for literal integers on input. resetiosflags(long j) Clear the format bits specified by f. setiosflags(long j) Set the format bits specified by f. setfill(int c) Set the fill character to c. setprecision(int n) Set the floating-point precision to n. setw(int n) Set field width to n. dec hex oct ws endl ends flush setbase(int n) The manipulator endl inserts a newline character and flushes the stream. You can also the flush an ostream at any time with ostream « flush; 186 Borland C++ Programmer's Guide Filling and padding The fill character and the direction of the padding depend on the setting of the fill character and the left, right, and internal flags. The default fill character is a space. You can vary this by using the function fill: int i = 123; cout.fill('*'); cout.width(6); cout « ij II display ***123 The default direction of padding gives right-justification (pad on the left). You can vary these defaults (and other format flags) with the functions setf and unsetf: int i = 56j cout.width(6)j cout.fill('#')j cout.setf(ios::left,ios::adjustfield) ; II display 56#### cout « i; The second argument, ios::adjustfield, tells setf which bits to set. The first argument, ios::left, tells setf what to set those bits to. Alternatively, you can use the manipulators setfill, setiosflags, and resetiosflags to modify the fill character and padding mode. See ios data members on page 199 for a list of masks used by setf. Input Stream input is similar to output but uses the overloaded right shift operator, », known as the extraction (get from) operator, or extractor. The left operand of» is an object of type class istream. As with output, the right operand can be of any type for which stream input has been defined. By default,» skips whitespace (as defined by the isspace function in ctype.h), then reads in characters appropriate to the type of the input object. Whitespace skipping is controlled by the ios::skipws flag in the format state's enumeration. The skipws flag is normally set to give whitespace skipping. Clearing this flag (with setf, for example) turns off whitespace skipping. There is also a special "sink'" manipulator, ws, that lets you discard whitespace. Chapter 5, Using C++ streams 187 Consider the following example: int i; double d; cin » i » d; When the last line is executed, the program skips any leading. whitespace. The integer value (i) is then read. Any whitespace following the integer is ignored. Finally, the floating-point value (d) is read. For type char (signed or unsigned), the effect of the» operator is to skip whitespace and store the next (non-whitespace) character. If you need to read the next character, whether it is whitespace or not, you can use one of the get member functions (see the discussion of istream, beginning on page 202). For type char* (treated as a string), the effect of the » operator is to skip whitespace and store the next (non-whitespace) characters until another whitespace character is found. A final null character is then appended. Care is needed to avoid "overflowing" a string. You can alter the default width of zero (meaning no limit) using width as follows: char array[SIZE]; cin.width(sizeof(array)); cin » array; II Avoids overflow. For all input of fundamental types, if only whitespace is encountered nothing is stored in the target, and the istream state is set to fail. The target will retain its previous value; if it was uninitialized, it remains uninitialized. I/O of user-defined types To input or output your own defined types, you must overload the extraction and insertion operators. Here is an example: #include struct info { char *name; double val; char *units; }; II You can overload « for output as follows: ostream& operator « (ostream& s, info& m) { 188 Borland C++ Programmer's Guide s « m.name « return Si II II « m.val « II II « m.unitsi }i II You can overload » for input as follows: istream& operator » (istream& s, info& m) { s » m.name » m.val » m.unitsi return Si }i int main (void) info Xi x.name = new char[15]i x.units = new char[lO]i cout « "\nInput name, value and units: "i cin » Xi cout « \nMy input: « Xi return(O) i II II } Simple file I/O The class ofstream inherits the insertion operations from ostream, while ifstream inherits the extraction operations from istream. The file-stream classes also provide constructors and member functions for creating files and handling file I/O. You must include fstream.h in all programs using these classes. Consider the following example that copies the file FILE.lN to the file FILE.OUT: #include int main (void) { char Chi ifstream f1("FILE.IN")i of stream f2("FILE.OUT")i if (! f1) cerr « "Cannot open FILE. IN for input "i if (!f2) cerr « "Cannot open FILE.OUT for output"i while (f2 && fl.get(ch)) f2 .put (ch) i return(O)i } Note that if the ifstream or ofstream constructors are unable to open the specified files, the appropriate stream error state is set. Chapter 5, Using C++ streams 189 The constructors allow you to declare a file stream without specifying a named file. Later, you can associate the file stream with a particular file: of stream ofilei II creates output file stream ofile.open("payroll")i II ofile connects to file II do some payrolling ... II payroll II II close the ofile stream ofile.close() i ofile.open("employee")i II ofile can be reused ... By default, files are opened in text mode. This means that on input, carriage-return/linefeed sequences are converted to the '\n' character. On output, the '\n' character is converted to a carriagereturn/linefeed sequence. These translations are not done in binary mode. The file opening mode is set with an optional second parameter to the open function, chosen from the following table: Table 5.2 File modes Mode bit Action ios::app ios::ate ios::in ios::out ios::binary ios::trunc Append data-always write at end of file. Seek to end of file upon original open. Open for input (default for ifstreams). Open for output (default for ofstreams). Open file in binary mode. Discard contents if file exists (default if ios::out is specified and neither ios::ate nor ios::app is specified). If file does not exist, open fails. If file exists, open for output fails unless ate or app is set. ios::nocreate ios:: noreplace String stream processing The functions defined in strstrea.h support in-memory formatting, similar to sscanf and sprintf, but much more flexible. All of the istream functions are available for the class istrstream (input string stream"); likewise for output: ostrstream inherits from ostream. Given a text file with the following format: 101 191 Cedar Chest 102 1999.99 Livingroom Set 190 Borland C++ Programmer's Guide Each line can be parsed into three components: an integer ID, a floating-point price, and a description. The output produced is: 1: 101 191.00 Cedar Chest 2: 102 1999.99 Livingroom Set Here is the program: #include #include #include #include int main(int argc, char **argv) { int id; float amount; char description[41]; ifstream inf(argv[l]); if (inf) { char inbuf [81] ; int lineno = 0; II Want floats to print as fixed point cout.setf(ios: :fixed, ios::floatfield); II Want floats to always have decimal point cout.setf(ios::showpoint) ; while (inf.getline(inbuf,81)) { II 'ins' is the string stream: istrstream ins (inbuf,strlen(inbuf) ); ins » id » amount » ws; ins.getline(description,41); II Linefeed not copied. cout « ttlineno « ": " « id « '\t' « setprecision(2) « amount « '\t' « description « "\n"; return (0) ; Note the use of format flags and manipulators in this example. The calls to setf coupled with setprecision allow floating-point numbers to be printed in a money format. The manipulator ws skips whitespace before the description string is read. Chapter 5, Using C++ streams 191 Screen output streams The class constream, derived from ostream and defined in constrea.h, provides the functionality of conio.h for use with C++ streams. This allows you to create output streams that write to specified areas of the screen, in specified colors, and at specific locations. .. As with conio.h functions, constreams are not compatible with Windows. The screen area created by constream is not bordered or otherwise disinguished from the surrounding screen. Console stream manipulators are provided to facilitate formatting of console streams. These manipulators work in the same way as the corresponding function provided by conio.h. For a detailed description of the manipulators' behavior and valid arguments, see the Library Reference. Table 5.3 Console stream manipulators Typical use of parameterized manipulators. Manipulator conic function clreol clreol delline delline highvideo highvideo insline insline lowvideo lowvideo normvideo normvideo setaHr(int) setbk(int) setclr(int) setcrstype(int) setxy(int, int) textaHr textcolor textcolor _setcursortype gotoxy Action Clears to end of line in text window. Deletes line in the text window. Selects high-intensity characters. Inserts a blank line in the text window. Selects low-intensity characters. Selects normal-intensity characters. Sets screen attributes. Sets new character color. Set the color. Selects cursor appearance. Positions the cursor at the specified position. #include int main (void) { constream winl; winl;window(l, 1, 40, 20); II Initialize the desired space. winl.clrscr() i II Clear this rectangle. II Use the parameterized manipulator to set screen attributes. winl « setattr((BLUE«4) I WHITE) 192 Borland C++ Programmer's Guide « "This text is white on blue."; II Use this parameterized manipulator to specify output area. winl « setxy(10, 10) « "This text is in the middle of the window."; return(O) ; } You can create multiple constreams, each writing to its own portion of the screen. Then, you can output to any them without having to reset the window each time. #include int main(void) { constream demol, demo2; demol.window( I, 2, 40, 10 ); demo2.window( I, 12, 40, 20 ); demol. clrscr ( ) ; demo2. clrscr ( ) ; demol « demo2 « demol « demo2 « return(O) "Text in first window" « endl; "Text in second window· « endl; "Back to the first window" « endl; "And back to the second window" « endl; ; } Stream class reference The stream class library in C++ consists of several classes. This reference presents some of the most useful details of these classes, in alphabetical organization. The following cross-reference lists tell which classes belong to which header files. constrea.h: conbuf, constream iostream.h: ios, iostream, iostream_withassign, istream, istream_withassign, ostream, ostream_withassign, streambuf. fstream.h: filebuf, fstream, fstreambase, ifstream, ofstream. strstrea.h: istrstream, ostrstream, strstream, strstreambase, strstreambuf. Chapter 5, Using C++ streams 193 conbuf conbuf Specializes streambuf to handle console output. constructor conbuf ( ) Makes an unattached conbuf. Member functions clreol void clreol () Clears to end of line in text window. clrscr void clrscr ( ) Clears the defined screen. delline void delline () Deletes a line in the window. gotoxy void gotoxy (int X, int y) Positions the cursor in the window at the specified location. highvideo 'void highvideo () Selects high-intensity characters. insline void ins line ( ) Inserts a blank line. lowvideo void lowvideo ( ) Selects low-intensity characters. normvideo void normvideo ( ) Selects normal-intensity characters. overflow virtual int overflow ( int = EOF ) Flushes the conbuf to its destination. setcursortype void setcursortype (int cur_type) Selects the cursor appearance. textattr void textattr(int newattributel Selects cursor appearance. 194 Borland C++ Programmer's Guide conbuf textbackground void textbackground (int newcolor) Selects the text background color. textcolor void textcolor ( int newcolor) Selects character color in text mode. textmode static void textmode (int newmode) Puts the screen in text mode. wherex int wherex ( ) Gets the horizontal cursor position. wherey int wherey () Gets the vertical cursor position. window void window(int left, int top, int right, int bottom) Defines the active window. constream Provides console output streams. This class is derived from ostream. constructor constream() Provides an unattached output stream to the console. Member functions clrscr void clrscr ( ) Clears the screen. rdbuf conbuf *rdbuf () Returns a pointer to this constream's assigned conbuf. textmode void textmode(int newmode) Puts the screen in text mode. window void window(int left, int top, int right, int bottom) Defines the active wiridow. Chapter 5, Using C++ streams 195 filebuf filebuf Specializes streambuf to handle files. constructor filebuf () ; Makes a filebuf that isn't attached to a file. constructor filebuf (int fd); Makes a filebuf attached to a file as specified by file descriptor fd. constructor filebuf (int fd, char *, int n); Makes a filebuf attached to a file and uses a specified n-character buffer. Member functions attach filebuf* attach(int) Attaches this closed filebuf to opened file descriptor. close filebuf* closet) Flushes and closes the file. Returns 0 on error. fd is_open Returns the file descriptor or EOF. int is_open () ; Returns nonzero if the file is open. open filebuf* open (const char*, int mode, int prot = filebuf:: openprot) ; Opens the given file and connects to it. overflow virtual int overflow (int = EOF); Flushes a buffer to its destination. Every derived class should define the actions to be taken. seekoff virtual streampos seekoff (streamoff, ios:: seek_dir, int); Moves the file pointer relative to the current position. setbuf virtual streambuf* setbuf(char*, int); Specifies a buffer for this filebuf. sync virtual int sync(); Establishes consistency between internal data structures and the external stream representation. 196 Borland C++ Programmer's Guide filebuf underflow virtual int underflow ( ) ; Makes input available. This is called when no more data exists in the input buffer. Every derived class should define the actions to be taken. fstream This stream class, derived from fstreambase and iostream, provides for simultaneous input and output on a filebuf. constructor fstream() ; Makes an fstream that isn't attached to a file. constructor fstream(const char*, int, int = filebuf::openprot); Makes an fstream, opens a file, and connects to it. constructor f stream ( int) ; Makes an fstream, connects to an open file descriptor. constructor fstream(int , char* lint); Makes an fstream connected to an open file and uses a specified buffer. Member functions open void open (const char* I int lint f ilebuf : : openprot) ; Opens a file for an fstream. rdbuf filebuf* rdbuf () ; Returns the filebuf used. fstreambase This stream class, derived from ios, provides operations common to file streams. It serves as a base for fstream, ifstream, and ofstream. constructor fstreambase () ; Makes an fstreambase that isn't attached to a file. Chapter 5, Using C++ streams 197 fstreambase constructor fstrearnbase (const char* I int lint = filebuf:: openprot) ; Makes an fstreambase, opens a file, and connects to it. constructor fstrearnbase (int) ; Makes an fstreambase, connects to an open file descriptor. constructor fstrearnbase (int I char* lint) ; Makes an fstreambase connected to an open file and uses a specified buffer. Member functions attach void attach (int) ; Connects to an open file descriptor. close void closet); Closes the associated filebuf and file. open void open(const char* I int, int = filebuf: :openprot); Opens a file for an fstreambase. rdbuf filebuf* rdbuf () ; Returns the filebuf used. setbuf void setbuf (char* lint) ; Uses a specified buffer. ifstream This stream class, derived from fstreambase and istream, provides input operations on a filebuf. constructor ifstream() ; Makes an ifstream that isn't attached to a file. constructor ifstream(const char*, int = ios::in , int = filebuf::openprot); Makes an ifstream, opens an input file in protected mode, and connects to it. The existing file contents are preserved; new writes are appended. 198 Borland C++ Programmer's Guide ifstream constructor ifstream(int); Makes an ifstream, connects to an open file descriptor. constructor ifstream(int fd, char *, int); Makes an ifstream connected to an open file and uses a specified buffer. Member functions open void open (const char*, int, int f ilebuf: : openprot) ; Opens a file for an ifstream. rdbuf filebuf* rdbuf () ; Returns the filebuf used. ios Provides operations common to both, input and output. Its derived classes (istream, ostream, iostream) specialize I/O with high-level formatting operations. The ios class is a base for istream, ostream, fstreambase, and strstreambase. constructor ios () ; protected Constructs an ios object that has no corresponding streambuf. constructor ios (streambuf *); Associates a given streambuf with the stream. Data members left I right I internal dec I oct I hex scientific I fixed the associated streambuf padding character on / / output x_flags; / / formatting flag bits x-precision; / / floating-point precision on / / output static const long adjustfield; static const long basefield; static const long floatfield; *bp; streambuf x_fill ; int long int Chapter 5, Using C++ streams // // // // // protected protected protected protected 199 ios int ostrearn int state; *x_tie; x_width; / / current state of the / / streambuf / / the tied ostream, if any / / field width on output protected protected protected Member functions bad int bad ( ) ; Nonzero if error occurred. bitalloc static long bitalloc () ; Acquires a new flag bit set. The return value may be used to set, clear, and test the flag. This is for user-defined formatting flags. clear void clear (int = 0); Sets the stream state to the given value. eof int eof ( ) ; Nonzero on end of file. fail int fail () ; Nonzero if an operation failed. fill char fill () Returns the current fill character. fill char fill (char) ; Resets the fill character; returns the previous one. flags long flags ( ) i Returns the current format flags. flags long flags (long) ; Sets the format flags to be identical to the given long; returns previous flags. Use 1Iag5(0) to set the default format. good int good ( ) ; Nonzero if no state bits set (that is, no errors appeared). init void ini t (strearnbuf *); protected Provides the actual initialization. 200 Borland C++ Programmer's Guide ios precision int precision () ; Returns the current floating-point precision. precision int precision (int) ; Sets the floating-point precision; returns previous setting. rdbuf streambuf* rdbuf () ; Returns a pointer to this stream's assigned streambuf. rdstote int rdstate(); Returns the stream state. seH long setf (long); Sets the flags corresponding to those marked in the given long; returns previous settings. seH long setf (long _setbits, long _field); The bits corresponding to those marked in _field are cleared, and then reset to be those marked in _setbits. setstote protected:void setstate(int); Sets all status bits. sync_with_stdio static void sync_with_stdio (); Mixes stdio files and iostreams. This should not be used for new code. He ostream* tie(); Returns the tied stream, or zero if none. Tied streams are those that are connected such that when one is used, the other is affected. For example, cin and cout are tied; when cin is used, it flushes cout first. tie ostream* tie (ostream*) ; Ties' another stream to this one and returns the previously tied stream, if any. When an input stream has characters to be consumed, or if an output stream needs more characters, the tied stream is first flushed automatically. By default, cin, cerr and clog are tied to cout. unseH long unsetf (long) ; Clears the bits corresponding to those marked in the given long; returns previous settings. Chapter 5, Using C++ streams 201 ios width int width ( ) i Returns the current width setting. width int width (int) i Sets the width as given; returns the previous width. xalloc static int xalloc () i Returns an array index of previously unused words that can be used as user-defined formatting flags. iostream This class, derived from istream and ostream, is simply a mixture of its base classes! allowing both input and output on a stream. It is a base for fstream and strstream. constructor iostrearn (streambuf *) i Associates a given streambuf with the stream. iostream_withassign This class is an iostream with an added assignment operator. constructor iostrearn_wi thassign () i Default constructor (calls iostream's constructor). Member functions None (although the istream =operator is overloaded). Provides formatted and unformatted input from a streambuf. The» operator is overloaded for all fundamental types, as explained in the narrative at the beginning of the chapter. This ios class is a base for ifstream, iostream, istrstream, and istream_withassign. 202 Borland C++ Programmer's Guide istream constructor istream(streambuf *); Associates a given streambuf with the stream. Member functions gcount int gcount ( ) ; Returns the number of characters last extracted. get int get ( ) ; Extracts the next character or EOP. get istream& get (signed chart, int len, char = '\n'); istream& get (unsigned chart, int len, char = '\n'); Extracts characters into the given char * until the delimiter (third parameter) or end-of-file is encountered, or until (len -1) bytes have been read. A terminating null is always placed in the output string; the delimiter never is. Fails only if no characters were extracted. get istream& get (signed char&); istream& get (unsigned char&); Extracts a single character into the given character reference. get istream& get (streambuf&, char = ' \n'); Extracts characters into the given streambuf until the delimiter is encountered. getline istream& getline(signed char *buffer, int, char = '\n'); istream& getline(unsigned char *buffer, int, char = '\n'); Same as get, except the delimiter is also extracted. The delimiter is not copied to buffer. ignore istream& ignore(int n = 1, int delim = EOF); Causes up to n characters in the input stream to be skipped; stops if delim is encountered. peek int peek ( ) ; Returns next char without extraction. putback istream& putback (char) ; Pushes back a character into the stream. Chapter 5, Using C++ streams 203 istream read istrearn& read (signed char*, int); istrearn& read(unsigned char*, int); Extracts a given number of characters into an array. Use gcountO for the number of characters actually extracted if an error occurred. seekg istrearn& seekg (long) ; Moves to an absolute position (as returned from tellg). seekg istrearn& seekg (long, seek_dir); Moves to a position relative to the current position, following the definition: enum seek_dir {beg, cur, end}; teUg long tellg ( ) i Returns the current stream position. istream_withassign This class is an istream with an added assignment operator. constructor istrearn_wi thassign () ; Default constructor (calls istream's constructor). Member functions None (although the =operator is overloaded). istrstream Provides input operations on a strstreambuf. This class is derived from strstreambase and istream. constructor istrstrearn(const char *); Makes an istrstream with a specified string (a null character is never extracted). constructor istrstrearn (canst char *, int n); Makes an istrstream using up to n bytes of a specified string. 204 Borland C++ Programmer's Guide ofstream ofstream Provides input operations on a filebuf. This class is derived from fstreambase and ostream. constructor of stream () i Makes an ofstream that isn't attached to a file. constructor ofstream(const char*, int = ios::out, int = filebuf::openprot)i Makes an ofstream, opens a file, and connects to it. constructor ofstream(int) i Makes an ofstream, connects to an open file descriptor. constructor ofstream(int fd, char*, int) i Makes an ofstream connected to an open file and uses a specified buffer. Member functions open void open (const char*, int, int = f ilebuf : : openprot) i Opens a file for an ofstream. rdbuf filebuf* rdbuf () i Returns the filebuf used. ostream Provides formatted and unformatted output to a streambuf. The« operator is overloaded for all fundamental types, as explained on page 183. This ios-based class is a base for constream, iostream, ofstream, ostrstream, and ostream_withassign. constructor ostream(strearnbuf *) i Associates a given streambuf with the stream. Chapter 5, Using C++ streams 205 ostream Member functions flush ostream& flush(); Flushes the stream. put ostream& put (char); Inserts the character. seekp ostream& seekp(long); Moves to an absolute position (as returned from tellp). seekp ostream& seekp (long ,seek_dir) ; Moves to a position relative to the current position, following the definition: enum seek_dir {beg, cur, end}; tellp long tellp ( ) ; Returns the current stream position. write ostream& write (canst signed char*, int n); ostream& write(const unsigned char*, int n); Inserts n characters (nulls included). ostream_withassign This class is an ostream with an added assignment operator. constructor ostream_wi thassign () ; Default constructor (calls ostream's constructor). Member functions None (although the =operator is overloaded). ostrstream Provides output operations on a strstreambuf. This class is derived from strstreambase and ostream. 206 Borland C++ Programmer's Guide ostrstream constructor ostrstream() ; Makes a dynamic ostrstream. constructor ostrstream(char*, int, int = ios::out); Makes a ostrstream with a specified n-byte buffer. If mode is ios::app or ios::ate, the get/put pointer is positioned at the null character of the string. Member functions pcount char *pcount () ; Returns the number of bytes currently stored in the buffer. str char *str () ; Returns and freezes the buffer. You must deallocate it if it was dynamic. streambuf This is a buffer-handling class. Your applications gain access to buffers and buffering functions through a pointer to streambuf that is set by ios. streambuf is a base for filebuf and strstreambuf. constructor streambuf(); Creates an empty buffer object. constructor streambuf(char *, int); Uses the given array and size as the buffer. Member functions allocate int allocate () ; protected Sets up a buffer area. base char *base(); protected Returns the start of the buffer area. bien int blen(); protected Returns the length of buffer area. Chapter 5, Using C++ streams 207 streambuf eback protected char *eback ( ) ; Returns the base of putback section of get area. ebut protected char *ebuf () ; Returns the end +1 of the buffer area. egptr char *egptr(); protected Returns the end +1 of the get area. epptr char *epptr(); protected Returns the end+l of the put area. gbump void gbump(int); protected Advances the get pointer. gptr char *gptr(); protected Returns the next location in get area. in_avail int in_avail ( ) ; Returns the number of characters remaining in the input buffer. ouCwaiting int out_waiting (); Returns the number of characters remaining in the output buffer. pbose protected char *pbase () ; Returns the start of put area. pbump void pbump (int) ; protected Advances the put pointer. pptr char *pptr ( ) ; protected Returns the next location in put area. sbumpc int sbumpc ( ) ; Returns the current character from the input buffer, then advances. seekoff virtual streampos seekoff(streamoff, ios: :seek_dir, int = (ios::in I ios::out); Moves the get or put pointer (the third argument determines which one or both) relative to the current position. 208 Borland C++ Programmer's Guide streambuf seekpos virtual streampos seekpos (streampos, int = (ios:: in I ios:: out) ) ; Moves the get or put pointer to an absolute position. setb void setb(char *, char *, int = 0 ); protected Sets the buffer area. setbuf virtual streambuf* setbuf(signed char *, int); streambuf* setbuf(unsigned char *, int); Connects to a given buffer. setg void setg(char *, char *, char *); protected Initializes the get pointers. setp void setp(char *, char *); protected Initializes the put pointers. sgetc int sgetc ( ) ; Peeks at the next character in the input buffer. sgetn int sgetn (char*, int n); Gets the next n characters from the input buffer. snextc int snextc ( ) ; Advances to and returns the next character from the input buffer. sputbackc int sputbackc (char) ; Returns a character to input. sputc int sputc (int) ; Puts one character into the output buffer. sputn int sputn(const char*, int n); Puts n characters into the output buffer. stossc void stossc(); Advances to the next character in the input buffer. unbuffered void unbuffered(int); protected Sets the buffering state. unbuffered int unbuf fered ( ) ; protected Returns non-zero if not buffered. Chapter 5, Using C++ streams 209 strstreombase strstreambase Specializes ios to string streams. This class is entirely protected except for the member function strstreambase::rdbufO. This class is a base for strstream, istrstream, and ostrstream. constructor strstreambase () ; protected Makes an empty strstreambase. constructor strstreambase(char *, int, char *start); protected Makes an strstreambase with a specified buffer and starting position. Member functions rdbuf strstreambuf * rdbuf ( ) ; Returns a pointer to the strstreambuf associated with this object. strstreambuf Specializes streambuf for in-memory formatting. constructor strstreambuf () ; Makes a dynamic strstreambuf. Memory will be dynamically allocated as needed. constructor s trs treambuf (void * (*) (long), void (*) (void *)); Makes a dynamic buffer with specified allocation and free functions. constructor strstreambuf (int n); Makes a dynamic strstreambuf, initially allocating a buffer of at least n bytes. constructor strstreambuf (signed char *, int, signed char *end = 0); strstreambuf(unsigned char *, int, unsigned char *end = 0); Makes a static strstreambuf with a specified buffer. If end is not null, it delimits the buffer. 210 Borland C++ Programmer's Guide strstreambuf Member functions doallocate virtual int doallocate ( ) i Performs low-level buffer allocation. fr~eze void freeze (int = 1) i If the input parameter is nonzero, disallows storing any characters in the buffer. Unfreeze by passing a zero. overflow virtual int overflow (int = EOF) i Flushes a buffer to its destination. Every derived class should define the actions to be taken. seekoff virtual streampos seekoff (streamoff, ios:: seek_dir, int) i Moves the pointer relative to the current position. setbuf virtual streambuf* setbuf(char*, int)i Specifies the buffer to use. str char *str () i Returns a pointer to the buffer and freezes it. underflow virtual int underflow ( ) i Makes input available. This is called when a character is requested and the strstreambuf is empty. Every derived class should define the actions to be taken. strstream Provides for simultaneous input and output on a strstreambuf. This class is derived from strstreambase and iostream. constructor strstream() i Makes a dynamic strstream. constructor strstream(char*, int n, int mode) i Makes a strstream with a specified n-byte buffer. If mode is ios: :app or ios::ate, the get/put pointer is positioned at the null character of the string. Chapter 5, Using C++ streams 211 strstream Member function str char *str () ; Returns and freezes the buffer. The user must deallocate it if it was dynmnic. 212 Borland C++ Programmer's Guide c H A p T E R 6 The container class libraries For more information about templates, see Chapter 3, "C++ specifics. " Borland C++ version 3.0 includes two complete container class libraries: an enhanced version of the Object-based library supplied with version 2.0, plus a brand-new implementation based on templates. This chapter describes both libraries. We assume that you are familiar with the syntax and semantics of C++ and with the basic concepts of object-oriented programming (OOP). To understand the template-based version (called BIDS, for Borland International Data Structures), you should be acquainted with C++'s new template mechanism. The chapter is divided into seven parts: • A review of the difference between versions 2.0 and 3.0 of the class libraries • An overview of the Object- and template-based libraries • A survey of the Object container classes, introducing the basic concepts and terminology ~ An overview of the BIDS library • The CLASSLIB directory and how to use it • The new debugging tools • An alphabetic reference guide to the Object container library, listing each class and its members Chapter 6, The container class libraries 213 What's new since version 2.0? The version 2.0 container library is an Object-based implementation. Both container objects and the elements stored in them are all ultimately derived from the class Object. Further, the data structures used to implement each container class were fixed and (usually) hidden from the programmer. This provides a simple, effective model for most container applications. Version 3.0 therefore offers an enhanced, code-compatible version of the previous Object-based container library. We call this the Object container class library. In addition, a more flexible (but more complex), template-based container library, called BIDS (Borland International Data Structures), is supplied with version 3.0. Through the power of templates, BIDS lets you vary the underpinning data structure for a container and lets you store arbitrary objects in a container. With the appropriate template parameters, BIDS can actually emulate the Object container library. Before we review the differences between the Object and BIDS models, we'll list the changes to the Object container library since version 2.0: • New Btree and PriorityQueue classes. • New TShouldDelete class gives the programmer control over container / element ownership. You can control the fate of objects when they are detached from a container and when the container is flushed (using the new flush method) or destroyed. • New memory management classes, MemBlocks and MemStack, for efficient memory block and memory stack (mark-andrelease) allocations. • New PRECONDITION and CHECK macros provide sophisticated assert mechanisms to speed application development and debugging. • New Timer class gives you a stopwatch for timing program execution (not in Microsoft Windows). • New DLL library and support for Windows applications. When you choose Container Class Library in the IDE's Link Libraries dialog box, the Object-based libraries will be automatically linked in. 214 Existing Borland C++ version 2.0 container class code will still run with the version 3.0 libraries. The new Object container class libraries, in directory /CLASSLIB, are distinguished by the prefix TC: TCLASSx.LIB, TCLASDBx.LIB, TCLASDLL.LIB, and TCLASS.DLL, where x specifies the memory model, and DB indicates the special debug versions. To reduce verbiage, we will Borland C++ Programmer's Guide often refer to this container implementation as the Object or TC version. To use the template-based libraries, you must explicitly add the appropriate BIDS(DB)x.LlB library to your project or makefile. The corresponding libraries for the new template-based container classes are distinguished by the prefix BIDS: BIOSx.LIB, BIOSOBx.LIB, BIOSOLL.LIB, and BIOS.OLL. Let's review the reasons for having two sets of container libraries. The use of all these libraries is covered on page 238. Why two sets of libraries? The Object container classes have been retained and enhanced to provide code compatibility with the version 2.0 library. They provide a gentler learning curve than the template-based BIDS library. The Object container code offers faster compilation but slightly slower execution than the template version. The project files for the example and demo programs are set up to use the Object version of the container libraries. BIDS exploits the new exciting templates feature of C++ 2.1. It offers you considerable flexibility in choosing the best underlying data structure for a given container application. With the Object version, each container is implemented with a fixed data structure, chosen to meet the space/ speed requirements of most container applications. For example, a Bag object is implemented with a hash table, and a Deque object with a double list. With BIDS you can fine-tune your application by varying the container implementation with the minimum recoding-often a single typedef will suffice. You can switch easily from StackAsList to StackAsVector and test the results. In fact, you'll see that by setting appropriate values for , a generic class parameter, you can implement the Object model exactly. With BIDS, you can even choose between polymorphic and non-polymorphic implementations of the Object container model. Such choices between execution speed (non-polymorphic) and future flexibility (polymorphic) can be tested without major recoding. Existing code based on the Object container classes will compile and run perfectly using the new BIDS classes, just by linking in the appropriate library. Both the Object and BIDS versions provide the same functional interface. For example, the push and pop member functions work the same for all Stack objects. This makes the new template-based libraries highly compatible with existing code written for the Object library. Chapter 6, The container class libraries 215 The objects stored in Object library containers must be derived from the class Object. To store ints, say, you would have to derive an Integer class from Object (you'll see how later). With BIDS you have complete freedom and direct control over the types of objects stored in a container. The stored data type is simply a value passed as a template parameter. For instance, BI_ListImpdnt> gives you a list of ints, and BI_IBtreeImp gives you a B-tree of pointers to Emp (a user-defined struct). Regardless of which container class model you elect to use, you should be familiar with container terminology, the Object class hierarchy, and the functionality provided for each container type. Although the classes in the BIDS library have different naming conventions and special template parameters, the prototypes and functionality of each class member are the same as those in the Object library. Container basics If you are fully versed in the Borland C++ 2.0 version of the container library, you should first check out the Object library enhancements before moving to the templates section on page 224. Using ObjectBrowser in Windows, you can see the class hierarchy. Here we highlight the basic structure. 216 We start by describing the Object container class hierarchy as enhanced for Borland C++ version 3.0. This hierarchy offers a high degree of modularity through inheritance and polymorphism. You can use these classes as they are, or you can extend and expand them to produce an object-oriented software package specific to your needs. At the top of the class hierarchy is the Object class (see Figure 6.1), an abstract class that cannot be instantiated (no objects of its type can be declared). An abstract class serves as an umbrella for related classes. As such, it has few if any data members, and some or all of its member functions are pure virtual functions. Pure virtual functions serve as placeholders for functions of the same name and signature intended to be defined eventually in derived classes. In fact, any class with at least one pure virtual function is, by definition, an abstract class. Borland C++ Programmer's Guide Figure 6.1: Class hierarchies in CLASSLIB __________ Error Object ~ ..___----------string //Array '<:~.. - Sortable ~ Base~ate- ~ate //AbstractArray~ '"'''''' ~ BaseTlme-Tlme// HashTable SortedArray ~~Assoclatlon ~Bag - Set - Dictionary TShouldDelete <:....",. ~Collectlon~ L"ISt ~'. ~ ~ ';:Container~~ Stack "~DoubleList "<~~DeqUe - Queue~'Btree , PriorityQueue Memblocks MemStack ~ HaShTablelterator Listlterator Contalnerlterator ~ DoubleListlterator Btreelterator Arraylterator Abstract Classes in normal type Instance Classes in bold type Note that TShouldDelete provides a second base (multiple inheritance) for both Container and Association. A class derived from an abstract class can provide a body defining the inherited pure virtual function. If it doesn't, the derived class remains abstract, providing a base for further derivations. When you reach a derived class with no pure virtual functions, the class is called a non-abstract or instance class. As the name implies, instance classes can be instantiated to provide usable objects. An abstract class can be the base for both abstract and instance classes. For example, you'll see that Container, an abstract class derived from the abstract class Object, is the base for both Collection (abstract) and Stack (instance). To enhance your understanding of the classes, you can review their declarations in the source code files in the CLASSLIB directory. As you read this chapter, bear in mind that a derived class inherits and can access all non-private data members and member functions fro~ all its ancestral base classes. For example, the Array class does not need to explicitly define a function to print an array, because its immediate parent class AbstractArray does so. The Container class, an ancestor further up the Class tree, defines a different print function that can also be used with an array, because an array is a container. To determine all the member functions available to an object, you will have to ascend the class hierarchy tree. Because the public interface is intended to be sufficient for applications, object-oriented programming makes a knowledge of private data members unnecessary; therefore, Chapter 6, The container class libraries 217 private members (with a few exceptions) are not documented in this chapter. Object-based and other classes The Object-based hierarchy contains classes derived from the class Object (together with some other utility classes). Object provides a minimal set of members representing what every derived object must do; these are described in the reference section under Object (page 279). Both the containers-as-objects and the objects they store are objects derived (ultimately) from Object. Later you'll see that the template-based containers can contain objects of any data type, not just those derived from Object. Class categories The classes in or near the Object hierarchy can be divided into three groups: • The non-container classes include Object itself, and those classes derived from Object, such as String and Date, which cannot store other objects. • The container classes (also derived from Object), such as Array and List, which can store objects of other, usually noncontainer, class types. .. • The helper and utility classes not derived from Object, such as TShouldDelete, Listlterator and MemStack. Let's look at each category in more detail, although as with most OOP topics, they are closely related. Non-container classes Error class For details on Error see page 271 in the reference section. 218 The basic non-container classes provided are Object and its three children: Error (instance), Sortable (abstract), and Association (instance). Recall that the main purpose of these classes is to provide objects that can be stored as data elements in con.tainers. To this end, all Object-derived classes provide a hashing function allowing any of their objects to be stored in a hash table. Error is not a normal class; it exists solely to define a unique, special object called theErrorObject. A pointer to theErrorObject carries the mnemonic NOOBJECT. NOOBJECTisrather like a null pointer, but serves the vital function of occupying empty slots in a container. For example, when an Array object is created (not to be confused with a traditional C array), each of its elements will initially contain NOOBJECT.. . Borland C++ Programmer's Guide Sortable class For more details on Sortable see page 286 in the reference section. Association class For details on Association see page 248 in the reference . section. Sortable is an abstract class from which sortable classes must be derived. Some containers, known as ordered collections, need to maintain their elements in a particular sequence. Collections such as SortedArray and PriorityQueue, for example, must have consistent methods for comparing the "magnitude" of objects. Sortable adds the pure virtual function isLessThan to its base, Object. Classes derived from Sortable need to define IsLessThan and IsEqual (inherited from Object) for their particular objects. Using these members, the relational operators <, ==, >, >=, and so on, can be overloaded for sortable objects. Typical sortable classes are String, Date, and Time, the objects of which are ordered in the natural way. Of course, string ordering may depend on your locale, but you can always override the comparison functions (another plus for C++). Distinguish between the container object and the objects it contains: Sortable is the base for non-container objects; it is not a base for ordered collections. Every class derived from Object inherits the isSortable member function so that objects can be queried as to their "sortability." Association is a non-container, instance class providing special objects to be stored (typically) in Dictionary collections. An Association object, known as an association, is a pair of objects known as the key and the value. The key (which is unique in the dictionary) can be used to retrieve the value. Every class derived from Object inherits the isAssociation member function so that objects can report whether they are associations or not. Container classes In the Object-based library, all the container storage and access methods assume that the stored elements are derived from Object. They are actually stored as references or pointers to Object offering the advantages and disadvantages of polymorphism. Most of the container access member functions are virtual, so a container does not need to "know" how its contained elements were derived. A container can, in theory, contain mixed objects of different class types, so proper care is needed to maintain type-safe linkage. Every class has member functions called IsA and nameOf, which allow objects to announce their class ID and name. As you've seen, there are also isSortable and isAssociation member functions for testing object types. Chapter 6, The container class libraries 219 All the container classes are derived from the abstract Container class, a child of Object. Container encapsulates just a few simple properties upon which more specialized containers can be built. The basic container provides the following functionality: • Displays its elements • Calculates its hash value • Pure virtual slot for counting the number of items with getitemslnContainer • Pure virtual slot for flushing (emptying) the container with flush • Performs iterations over its elements • Reports and changes the ownership of its elements (inherited from TShouldDelete) So far, our containers have no store, access, or detach methods. (We can flush the container but we cannot detach individual elements.) Nor is there a hasMember member function, that is, a general way of determining whether a given object is an element of the container. This is a deliberate design decision. As we move up the hierarchy, you'll see that what distinguishes the various derived container classes are the storage and access rules that actually define each container's underlying data structure. Thus push and pop member functions are added for Stack, indexing operators are added for Array, and so on. There is not enough in common to warrant having generic add and retrieve methods at the Container level. There is no one perfect way of extracting common properties from groups of containers, and therefore no perfect container class hierarchy. The Object-based container hierarchy is just one possible design based on reasonable compromises. The BIDS version, as you'll see, offers a different perspective. The first three Container functions listed previously are fairly self-explanatory. We'll discuss the important subjects of ownership and iteration in the next two sections. Containers and ownership 220 Before you use the Container family, you must understand the concept of ownership. As in real life, a C++ container starts out empty and must be filled with objects before the objects can be said to be in the container. Unlike the real world, however, when objects are placed in the container, they are, by default, owned by Borland C++ Programmer's Guide the container. The basic idea is that when a container owns its objects, the objects are destroyed when the container is destroyed. Recall that containers are themselves objects subject to the usual C++ scoping rules, so local containers come and go as they move in and out of scope. Care is needed, therefore, to prevent unwanted destruction of a container's contents, so provision is made for changing ownership. A container can, throughout its lifetime, relinquish and regain ownership of its objects as often as it likes by calling the ownsElements member function (inherited from TShouldDelete). The fate of its objects when the container disappears is determined by the ownership status ruling at the time of death. Consider the following: void test () { Array a1( 10 Array a2( 10 )i )i II construct an array II and another a1.ownsElements( 1 ) i a2.ownsElements( 0 ) i II arrayal owns its objects (the default) II array a2 relinquishes ownership II load and manipUlate the arrays here When test exits, al will destroy its objects, but the objects in a2 will survive (subject, of course, to their own scope). The al. ownsElernents ( 1 ) call is not really needed since, by default, containers own their contents. Ownership also plays a role when an object is removed from a container. The pure virtual function Container: :flush is declared as virtual void flush( DeleteType dt = DefDelete ) = 0: flush empties the container but whether the flushed objects are destroyed or not is controlled by the dt argument. DeleteType is an enum defined in TShouldDelete. A value of NoDelete means preserve the flushed objects regardless of ownership; Delete means destroy the objects regardless of ownership; DefDelete, the default value, means destroy the objects only if owned by the container. Similarly Collection (derived from Container) has a detach member function, declared as virtual void detach( Object& obj, DeleteType dt = NoDelete = 0; Chapter 6, The container class libraries 221 which looks for obj in the collection and removes it if found. Again, the fate of the detached object is determined by the value dt. Here, the default is not to destroy the detached object. Collection::destroy is a variant that calls detach with DefDelete. A related problem occurs if you destroy an object that resides in a container without "notifying" the container. The safest approach is to use the container's methods to detach and destroy its contents. Important! Container iterators Under Containerlferator on page 263 in the reference section, you see how the pre- and post-increment operators ++ are overloaded to simplify your iterator programs. 222 If you declare an automatic object (an object that's local to your routine) and place that object in a global container, your local object will be destroyed when the routine leaves the scope in which it was declared. To prevent this, you must only add heap objects (objects that aren't local to the current scope) to global containers. Similarly, when you remove an object from a global container, you are responsible for destroying it and freeing the space in which it resides. You saw earlier that Container, the base for all containers in the Object-based library, supports iteration. Iteration means traversing or scanning a container, accessing each stored object in turn to perform some test or action. The separate Containerlterator-based hierarchy provides this functionality. Iterator classes are derived from this base to provide iterators for particular groups of containers, so you'll find HashTablelterator, Listlterator, Btreelterator, and so on. There are two flavors of iterators: internal and external. Each container inherits the three member functions: firstThat, lastThat, and forEach, via the Object and Container classes. As the names indicate, these let you scan through a container either testing each element for a condition or performing an action on each of the container's elements. When you invoke one of these three member functions, the appropriate iterator object is created for you internally to support the iteration. Most iterations can be performed in this way since the three iterating functions are very flexible. They take a pointer-to-function argument together with an arbitrary parameter list, so you can do almost anything. For even more flexibility, there are external iterators that you can build via the initlterator member function. With these, you have to set up your Ov\Tn loops and test for the end-af-container. Borland C++ Programmer's Guide Returning to the container class hierarchy, we look at three classes derived directly from Container: Stack, Deque, and PriorityQueue. Sequence classes The instance classes Stack, Deque (and its offspring Queue), and PriorityQueue are containers collectively known as sequence classes. A sequence class is characterized by the following properties: 1. Objects can be inserted and removed. 2. The order of insertions and deletions is significant. 3. Insertions and extractions can occur only at specific points, as defined by the individual class. In other words, access is nonrandom and restricted. Sequences (like all containers) know how many elements they have (using getltemslnContainer) and if they are empty or not (using isEmpty). However, they ca:r:tnot usually determine if a given object is a member or not (there is still no general hasMember or find Member member function). Stacks, queues, priority queues, and deques vary in their access methods as explained in more detail in the reference section. Sequence is not itself a class because sequences do not share enough in common to warrant a separate base class. However, you might find it helpful to consider the classes together when reviewing the container hierarchy. Collections The next level of specialization is the abstract class Collection, derived from Container, and poised to provide a slew of widely used data structures. The key difference between collections and containers is that we now have general has Member and find Member member functions. From Collection we derive the unordered collections Bag, HashTable, List, DoubleList, and AbstractArray, and the ordered collection Btree. In turn, AbstractArray spawns the unordered Array and the ordered SortedArray. Bag serves as the base for Set which in turn is the base for Dictionary. These collections all refine the storage and retrieval methods in their own fashions. Chapter 6, The container class libraries 223 Unordered collections With unordered collections, any objects derived from Object can be stored, retrieved, and detached. The objects do not have to be sortable because the access methods do not depend on the relative "magnitude" of the elements. Classes that fall into this category are • • • • Ordered collections HashTable Bag, Set, and Dictionary List and DoubleList Array An ordered collection depends on relative "magnitude" when adding or retrieving its elements. Hence these elements must be objects for which the isLessThan member function is defined. In other words, the elements in an ordered collection must be derived from the class Sortable. The following are ordered collections: • Btree • SortedArray The BIDS template library The BIDS container class library can be used as a springboard for creating useful classes for your individual needs. Unlike the Object container library, BIDS lets you fine-tune your applications by varying the underlying data structures for different containers with minimum reprogramming. This extends the power of encapsulation: the implementor can change the internals of a class with little recoding and the user can easily replace a class with one that provides a more appropriate algorithm. The BIDS class library achieves this flexibility by using the C++ template mechanism. For a basic description of C++ templates see page 745 in Chapter 3. 224 With BIDS, the container is considered as an ADT (abstract data type), and its underlying data structure is independently treated as an FDS (fundamental data structure). BIDS also allows separate selections of the type of objects to be stored, and whether to store the objects themselves or pointers to objects. Borland C++ Programmer's Guide Templates, classes, and containers Computer science has devoted much attention to devising suitable data structures for different applications. Recall Wirth's equation, Programs = Algorithms + Data Structures, which stresses the equal importance of data structures and their access methods. As used in current OOP terminology, a container is simply an object that implements a particular data structure, offering member functions for adding and accessing its data elements (usually other objects) while hiding from the user as much of the inner detail as possible. There are no simple rules to determine the best data structure for a given program. Often, the choice is a compromise between competing space (RAM) and time (accessibility) considerations, and even here the balance can shift suddenly if the number of elements or the frequency of access grows or falls beyond a certain number. Container implementation Often, you can implement the desired container properties in many ways using different underlying data structures. For example, a stack, characterized by its Last-In-First-Out (LIFO) access, can be implemented as a vector, a linked list, or perhaps some other structure. The vector-based stack is appropriate when the maximum number of elements to be stacked is known in advance. A vector allocates space for all its elements when it is created. The stack as a list is needed when there is no reasonable upper bound to the size of the stack. The list is a slower implementation than the vector, but it doesn't use any more memory than it needs for the current state of the stack. The way objects are stored in the container also affects size and performance: they can be stored directly by copying the object into the data structure, or stored indirectly via pointers. The type of data to be stored is a key factor. A stack of ints, for example, would probably be stored directly, where large structs would call for indirect storage to reduce copying time. For "in-between" cases, however, choosing strategies is not always so easy. Performance tuning requires the comparison of different container implementations, yet traditionally this entails drastic recoding. Chapter 6, The container class libraries 225 The template solution The template approach lets you develop a stack-based application, say, using vectors as the underlying structure. You can change this to a list implementation without major recoding (a single typedef change, in fact). The BIDS library also lets you experiment with object-storage strategies late in the development cycle. Each container-data structure combination is implemented with both direct and indirect object storage, and the template approach allows a switch of strategy with minimal rewriting. The data type of the stored elements is also easy to change using template parameters, and you are free to use any data type you want. As you'll see, BIDS offers container / data structure combinations that match those of the Object-based version. For example, the Object library implements Stack using lists, so Stack can be simulated exactly with the template class BLTCStackAsList. Let's look at the template approach in more detail. ADTs and FDSs Table 6.1 ADTs as fundamental data structures We discussed earlier the stack and its possible implementations as a linked list or as a vector. The potential for confusion is that stacks, lists, and vectors are all commonly referred to as data structures. However, there is a difference. We can define a stack abstractly in terms of its LIFO accessibility, but it's difficult to envision a list without thinking of specifics such as nodes and pointers. Likewise, we picture a vector as a concrete sequence of adjacent memory locations. So we call the stack an ADT (abstract data type) and we call the list and vector FOSs (fundamental data structures). The BIDS container library offers each of the standard ADTs implemented with a choice of appropriate FOSs. Table 6.1 indicates the combinations provided: Stack Queue Deque ADT Bag Set Array Vector • • • • • • List • • • FDS DoubleList Sorted Array • The abstract data types involved are Stacks, Queues, Deques, Bags, Sets, and Arrays. Each ADT can be implemented several different ways using the fundamental data structures Vector, List, and DoubleList as indicated by a bullet ( • ) in the table. Thus, all ADTs are implemented as vectors. In addition, Stacks are 226 Borland C++ Programmer's Guide implemented as a list; Queues and Oeques implemented as doubly-linked lists. (Not shown in the table are the sorted and counted FOSs from which various AOTs can be developed.) There is nothing sacred about these combinations; you can use the template classes to develop your own AOT /FOS implementations. Class templates AOTs are implemented in both direct and indirect versions. The direct versions store the objects themselves, while the indirect versions store pointers to objects. You can store whatever objects you want as elements in these FOSs using the power of templates. Here are the AOT and FOS templates we provide: Table 6.2 FDS class templates Class template Description BI_Vectorlmp BLVectorlteratorlmp BI_CVectorlmp BI_SVectorlmp BLIVectorlmp BLIVectorlteratorl m p BI_ICVectorlmp BLISVectorlmp BI_Listlmp BI_SListlmp BI_IListlmp BLISListlmp BLDoubleListlmp BI_SDoubleListlmp BLIDoubleListlmp BLISDoubleListlmp vector of Ts iterator for a vector of Ts counted vector of Ts sorted vector of Ts vector of pointers to T iterator for a vector of pointers to T counted vector of pointers to T sorted vector of pointers to T list of Ts sorted list of Ts list of pointers to T sorted list of pointers to T double-linked list of Ts sorted double-linked list of Ts double-linked list of pointers to T sorted double-linked list of pointers to T .. Each basic FOT has a direct and indirect iterator; to save space we have shown only the Vector iterators. The BI_ prefix stands for Borland International and the suffix Imp for implementation. The indirect versions have the prefix BI_I with the extra I for Indirect. The extra prefixes 5 and C mean Sorted and Counted respectively. The template parameter Tin represents the data type of the objects to be stored. You instantiate the template by supplying this data type. For example, BI_ListImp gives you a list of doubles, and BI_IBtreeImp gives you a B-tree of pointers to Cust (a userdefined type). See Table 6.3 on page 228 for a summary of these abbreviations. For direct object storage, the type Tmust have meaningful copy semantics and a default constructor. Indirect containers, however, hold pointers to T, and pointers always have Chapter 6, The container class libraries 227 good copy semantics. This means that indirect containers can contain objects of any type. Table 6.3 Abbreviations in CLASSLIB names Abbreviation BI I C S o TC For details see the discussion under Sortable on page 287. Description Borland International Indirect Counted Sorted Object-based, non-polymorphic Object-based, polymorphic (compatible with original Turbo C library) For the sorted FDSs (BI_SVectorlmp, BI_ISVectorlmp, and so on), Tmust have valid == and < operators to define the ordering of the elements. It should be clear that the IS variants refer to the objects being sorted, not that the pointers to the objects are sorted. Each implementation of an ADT with an FDS is named using the convention (ADT)As (FDS) , as follows: Table 6.4 ADT class templates There are also BC Oxxx and BCTCxxx variants discussed soon. 228 Class name Description BI_StackAsVector BI_QueueAsVector BI_DequeAsVector BI_BagAsVector BI_SetAsVector BI_ArrayAsVector BI_SArray AsVector Stack of Ts as a vector Queue of Ts as a vector Deque of Ts as a veCtor Bag of Ts as a vector Set of Ts as a vector Array of Ts as a vector Sorted array of Ts as a vector BI_IStackAsVector BI_IQueueAsVector Stack of pointers to T as a vector Queue of pointers to T as a vector and so on BI_StackAsList BI_IStackAsList Stack of Ts as a list Stack of pointers to T as a list BI_QueueAsDoubleList BI_DequeAsDoubleList BI_IQueueAsDoubleLiskT> BLIDequeAsDoubleList Queue of Ts as a double list Deque of Ts as a double list Queue of pointers to T as a double list Deque of pointers to T as a double list Again, the argument, either a class or predefined data type, provides the data type for the contained elements. Each of the bullets ( • ) in Table 6.1 can be mapped to two templates (direct and indirect versions) with names following this convention. Borland C++ Programmer's Guide Container class compatibility Each template must be instantiated with a particular data type as the type of the element that it will hold. This allows the compiler to generate the correct code for dealing with any possible type of element without restricting the elements to just those derived from Object. Each ADT is also used to instantiate two classes that imitate the behavior of the Object class libraries. Here is a list of them: Table 6.5 Object-based FDS classes Class name Description BLOStackAsVector BLOQueueAsVector BI_ODequeAsVector BLOBagAsVector BLOSetAsVector BLOArrayAsVector BI_OSArrayAsVector Stack of pointers to Object, as a vector Queue of pointers to Object, as a vector Deque of pointers to Object, as a vector Bag of pointers to Object, as a vector Set of pointers to Object, as a vector Array of pointers to Object, as a vector Sorted array of pointers to Object, as a vector BLTCStackAsVector Polymorphic stack of pointers to Object, as a vector Polymorphic queue of pointers to Object, as a vector Polymorphic deque of pointers to Object, as a vector Polymorphic bag of pointers to Object, as a vector Polymorphic set of pointers to Object, as a vector Polymorphic array of pointers to Object, as a vector Polymorphic sorted array of pointers to Object, as a vector BLTCQueueAsVector BLTCDequeAsVector BLTCBagAsVector BLTCSetAsVector BLTCArray AsVector BI_TCSArrayAsVector BLOStackAsList BI_TCStackAsList Stack of pointers to Object, as a list Polymorphic stack of pointers to Object, as a list BLOQueueAsDoubleList BLODequeAsDoubleList Queue of pointers to Object, as a double list Deque of pointers to Object, as a double list BLTCQueueAsDoubleList Polymorphic queue of pointers to Object, as a double list Polymorphic deque of pointers to Object, as a double list BI_TCDequeAsDoubleList Chapter 6, The container class libraries 229 The TCxxx versions offer the same behavior and interfaces as the Object library. Note that these versions have no explicit parameters; they use the fixed data types shown (pointers to Object). The BI_Oxxx (0 for Object library) versions of these classes have no virtual functions. This makes it easier for the compiler to generate inline function expansions, which in turn makes the BI_Oxxx versions of the containers somewhat faster than the corresponding polymorphic BeTCxxx (TC for Turbo C) versions. The obverse of the coin is that the BLOxxxversions do not share the polymorphic behavior of the Object container library. In the Object container library, Stack implements a stack as a polymorphic list of pointers to Object. The BIDS class BI_TCStackAsList therefore mirrors the Object-based class Stack. Even with BLTCStackAsVector, the public interface and semantics are the same as for the Object-based Stack. The user "sees" the ADT while the FDS is "hidden." For these reasons, we will not repeat the alphabetic list of Object-based classes and member functions for the BIDS library. Consider your many choices when writing container code with the BIDS model. You can gain speed over future flexibility by using the non-polymorphic classes, such as BLOStackAsList or BLOStackAsVector. Or you can retain the polymorphism of the Object-based hierarchy by using the BLTCxxx classes. Header files Each group of FOSs is defined in its own header file, which contains templates for both the direct and the indirect versions. The names of the headers are as follows: vectimp.h listimp.h dlistimp.h In vectimp.h, for example, you'll find declarations for all the vector, counted vector, and sorted vector templates, together those for a direct and indirect vector iterator. Note also the stdtempl.h file that defines the useful template functions min, max, and range. If you are new to templates, this file offers a useful, gentle introduction to the subject. Each ADT family is defined in its own header file, named as follows: 230 Borland C++ Programmer's Guide stacks.h queues.h deques.h bags.h sets.h arrays.h Note the plural form that distinguishes the BIDS include files from the Object-based include file Tuning an application The file stacks.h, for example, defines the following templates: BI_StackAsVector BI_IStackAsVector BI_OStackAsVector BI_TCStackAsvector BI_StackAsList BI_IStackAsList BI_OStackAsList BI_TCStackAsList Consider the following example: typedef BI_StackAsVector intStack; int main () { intStack is; for( int i = 0; i < 10; itt is.push( i ); for( i = 0; i < 10; itt cout « is.pop() « endl; return(O); } Here we are implementing a stack of ints using a vector as the underlying data structure. If you later determine that a list would be a more suitable implementation for the stack, you can simply replace the typedef with the following: typedef BI_StackAsList intStack; After recompilation, the stack implementation is changed from vector to list. Similarly, you can try a stack of pointers to int, with: typedef BI_IStackAsList intStack; FDS implementation Each FDS is implemented as two templates, one that provides the direct version, and one that provides the indirect version. The indirect version makes use of an Internallxxxlmp class. The Chapter 6, The container class libraries 231 following simplified extract from listimp.h will give you an idea how the different list FDSs are implemented. Note that BI_ListElement is an internal template class used to implement the node (data of type T and pointer to next node) of a list. The direct list of objects of type T is implemented by the template class BI_Listlmp , which also provides the base for BI_SListlmp (sorted lists). The example shows how the add member function is implemented in the direct, indirect, sorted and unsorted lists. template class BI_ListElement { pUblic: BI_ListElement( T t, BI_ListElement *p { next = p->next; p->next = this; } II constructor BI_ListElement *next; T data; data(t) II pointer to next node II object at node }; template class BI_Listlmp II linked list (unsorted) of type T objects; assumes T has meaningful II copy semantics and a default constructor { pUblic: void add( T t ) { new BI_ListElement ( t, &head ) i } II adds objects at head of list (shown inline here to save space) T peekHead() const { return head.next->datai } }i template class BI_SListlmp : public BI_Listlmp II sorted list; assumes T has meaningful copy semantics and a default II constructor pUblic: void add( T t ) { new BI_ListElement ( t, findPred(t) ) i } II adds object in sorted position }; template class BI_InternalIListlmp public List { void add( T *t ) { List::add ( t 232 )i } Bar/and C++ Programmer's Guide }; II The work is done in this intermediate class used as base for II BI_IListlmp; list is unsorted so we use List::add template class BI_IListlmp : public BI_InternalIListlmp > { ... }; 1* unsorted list of pointers to objects of type T; since pointers * always have meaningful copy semantics, this class can handle any * object type; add comes straight from BI_InternalIListlmp *1 template class BI_ISListlmp : public BI_InternalIListlmp , BSListlmp< void * » { ... }; 1* sorted list of pointers to objects of type T; since pointers * always have meaningful copy semantics, this class can handle any * object type; *1 In addition to the template classes shown here, listimp.h also declares BI_Listiteratorlmp and BI_IListlteratorlmp , the iterators for direct and indirect lists. In the next section on AOTs, you'll see how the different stack implementations in stacks.h pull in the vector and list FOSs declared in vectimp.h and listimp.h. The double list templates, in dlistimp.h, follows the same pattern. The sorted versions of list and double list provide exactly the same interface as the non-sorted ones, except that the add member function adds new elements in sorted order. This speeds up subsequent access and also makes it easier to implement priority queues. vectimp.h also follows a similar pattern to listimp.h, implementing BLVectorlmp (direct) and BI_IVectorlmp (indirect). These are low-level vectors with no notion of add or detach. To support more sophisticated AOTs, the counted vector, BI_CVectorlmp , derived from BI_Vectorlmp , is provided. This maintains a pointer to the last valid entry in the underlying Vector. It has an add member function that inserts its argument at the top (the next available slot), and a detach member function that removes its argument and compresses the array. BLCVectorlmp provides the base for the sorted vector template BI_SVectorlmp . With a sorted vector, you can run through the indices from 0 to the last valid entry, and the objects will emerge in sort order. Here's a simplified extract from vectimp.h: Chapter 6, The container class libraries 233 II extract from vectimp.h template class BI_Vectorlmp ... }; II direct uncounted, unsorted vector template class BI_CVectorlmp : public BI_Vectorlmp II direct counted, unsorted vector { public: void add ( T t ); II add at top of array; inc count; resize array if necessary void detach( T t, int del = 0 ); void detach( unsigned loc, int del = 0 ); II detach given object or object at loc }; template