Mechatronic Systems Programming in C++

Péter Tamás

Antal Huba

József Gräff

A tananyag a TÁMOP-4.1.2.A/1-11/1-2011-0042 azonosító számú „ Mechatronikai mérnök MSc tananyagfejlesztés ” projekt keretében készült. A tananyagfejlesztés az Európai Unió támogatásával és az Európai Szociális Alap társfinanszírozásával valósult meg.

Manuscript completed: February 2014

Language reviewed by: Ágoston Nagy

Published by: BME MOGI

Editor by: BME MOGI

2014


Table of Contents
I. Basics and data management of C++
I.1. Creation of C++ programs
I.1.1. Some important rules
I.1.2. The first C++ program in two versions
I.1.3. Compilation and running of C++ programs
I.1.4. Structure of C++ programs
I.2. Basic data types, variables and constants
I.2.1. Classification of C++ data types
I.2.1.1. Type modifiers
I.2.2. Defining variables
I.2.2.1. Initial values of variables
I.2.3. Basic data types
I.2.3.1. Character types
I.2.3.2. Logical Boolean type
I.2.3.3. Integer types
I.2.3.4. Floating point types
I.2.3.5. enum type
I.2.3.6. sizeof operation
I.2.4. Creation of alias type names
I.2.5. Constants in language C++
I.3. Basic operations and expressions
I.3.1. Classification of operators based on the number of operands
I.3.2. Precedence and grouping rules
I.3.2.1. Rule of precedence
I.3.2.2. Rule of associativity
I.3.3. Mathematical expressions
I.3.3.1. Arithmetical operators
I.3.3.2. Mathematical functions
I.3.4. Assignment
I.3.4.1. Left value and right value
I.3.4.2. Side effects in evaluation
I.3.4.3. I.3.4.3 Assignment operators
I.3.5. Increment and decrement operations
I.3.6. Phrasing of conditions
I.3.6.1. Relational and equalityoperations
I.3.6.2. Logical operations
I.3.6.3. Short circuit evaluation
I.3.6.4. Conditional operator
I.3.7. Bit operations
I.3.7.1. Bitwise logical operations
I.3.7.2. Bit shift operations
I.3.7.3. Bit operations in compound assigment
I.3.8. Comma operator
I.3.9. Type conversions
I.3.9.1. Implicit type conversions
I.3.9.2. Explicit type conversions
I.4. Control program structures
I.4.1. Empty statements and statement blocks
I.4.2. Selective structures
I.4.2.1. if statements
I.4.2.2. switch statements
I.4.3. Iteration structures (loops)
I.4.3.1. while loops
I.4.3.2. for loops
I.4.3.3. do-while loops
I.4.3.4. break statements in loops
I.4.3.5. continue statements
I.4.4. goto statements
I.5. Exception handling
I.5.1. The try – catch program structure
I.5.2. Provoking exceptions - the throw statement
I.5.3. Filtering exceptions
I.5.4. Nested exceptions
I.6. Pointers, references and dynamic memory management
I.6.1. Pointers
I.6.1.1. Single indirection pointers
I.6.1.2. Pointer arithmetic
I.6.1.3. void * type general pointers
I.6.1.4. Multiple indirection pointers
I.6.1.5. Constant pointers
I.6.2. References
I.6.3. Dynamic memory management
I.6.3.1. Allocating and accessing heap memory
I.6.3.2. Deallocating allocated memory
I.7. Arrays and strings
I.7.1. C++ array types
I.7.1.1. One-dimensional arrays
I.7.1.1.1. Initializing and assigning values to one-dimensional arrays
I.7.1.1.2. One-dimensional arrays and the  typedef
I.7.1.2. Two-dimensional arrays
I.7.1.3. Variable-length arrays
I.7.1.4. The relationship between pointers and arrays
I.7.2. Dynamically allocated arrays
I.7.2.1. One-dimensional dynamic arrays
I.7.2.2. Two-dimensional dynamic arrays
I.7.3. The usage of the vector type
I.7.3.1. One-dimensional arrays in vectors
I.7.3.2. Two-dimensional arrays in vectors
I.7.4. Handling C-style strings
I.7.4.1. Strings in one-dimensional arrays
I.7.4.2. Strings and pointers
I.7.4.3. Using string arrays
I.7.5. The string type
I.8. User-defined data types
I.8.1. The structure type
I.8.1.1. Structure type and structure variables
I.8.1.2. Accessing the data members of structures
I.8.1.3. Nested structures
I.8.1.4. Structures and arrays
I.8.1.4.1. Arrays as data members of a structure
I.8.1.4.2. Structures as array elements
I.8.1.5. Creating singly linked lists
I.8.2. The class type
I.8.3. The union type
I.8.3.1. Anonymous unions
I.8.4. Bit fields
II. Modular programming in C++
II.1. The basics of functions
II.1.1. Defining, calling and declaring functions
II.1.2. The return value of functions
II.1.3. Parametrizing functions
II.1.3.1. Parameter passing methods
II.1.3.1.1. Passing parameters by value
II.1.3.1.2. Passing parameters by reference
II.1.3.2. Using parameters of different types
II.1.3.2.1. Arithmetic type parameters
II.1.3.2.2. User-defined type parameters
II.1.3.2.3. Passing arrays to functions
II.1.3.2.4. String arguments
II.1.3.2.5. Functions as arguments
II.1.3.2.5.1. Function types and typedef
II.1.3.2.5.2. Pointers to functions
II.1.3.2.5.3. Examples for pointers to functions
II.1.3.2.6. Default arguments
II.1.3.2.7. Variable length argument list
II.1.3.2.8. Parameters and return value of the main() function
II.1.4. Programming with functions
II.1.4.1. Exchanging data between functions using global variables
II.1.4.2. Exchanging data between functions using parameters
II.1.4.3. Implementing a simple menu driven program structure
II.1.4.4. Recursive functions
II.2. How to use functions on a more professional level?
II.2.1. Inline functions
II.2.2. Overloading (redefining) function names
II.2.3. Function templates
II.2.3.1. Creating and using function templates
II.2.3.2. Function template instantiation
II.2.3.3. Function template specialization
II.2.3.4. Some further function template examples
II.3. Namespaces and storage classes
II.3.1. Storage classes of variables
II.3.1.1. Accessibility (scope) and linkage of variables
II.3.1.2. Lifetime of variables
II.3.1.3. Storage classes of block level variables
II.3.1.3.1. Automatic variables
II.3.1.3.2. The register storage class
II.3.1.3.3. Local variables with static lifetime
II.3.1.4. Storage classes of file level variables
II.3.1.5. Storage classes of program level variables
II.3.2. Storage classes of functions
II.3.2.1. Accessing the compiled C functions from within C++ source
II.3.3. Modular programs in C++
II.3.4. Namespaces
II.3.4.1. The default namespaces of C++ and the scope operator
II.3.4.2. Creating and using user-defined namespaces
II.3.4.2.1. Creating namespaces
II.3.4.2.2. Accessing the identifiers of a namespace
II.3.4.2.3. Nested namespaces, namespace aliases
II.3.4.2.4. Anonymous namespaces
II.4. Preprocessor directives of C++
II.4.1. Including files
II.4.2. Conditional compilation
II.4.3. Using macros
II.4.3.1. Symbolic constants
II.4.3.2. Parameterized macros
II.4.3.3. Undefining a macro
II.4.3.4. Macro operators
II.4.3.5. Predefined macros
II.4.3.6. #line, #error and #pragma directives
III. Object-oriented programming in C++
III.1. Introduction to the object-oriented world
III.1.1. Basics
III.1.2. Basic principles
III.1.2.1. Encapsulation, data hiding
III.1.2.2. Inheritance
III.1.2.3. Abstraction
III.1.2.4. Polymorphism
III.1.3. An object-oriented example code
III.2. Classes and objects
III.2.1. From structures to classes
III.2.1.1. A little revision
III.2.1.2. Grouping together data and operations
III.2.1.3. Data hiding
III.2.1.4. Constructors
III.2.1.4.1. Using member initialisation lists
III.2.1.4.2. Explicit initialisation of objects
III.2.1.5. Destructor
III.2.1.6. Objects of a class, the pointer this
III.2.2. More about classes
III.2.2.1. Static class members
III.2.2.2. How to structure classes
III.2.2.2.1. Implicit inline member functions
III.2.2.2.2. Class structures in C++/CLI applications
III.2.2.2.3. Storing member functions in separate modules
III.2.2.3. Friend functions and classes
III.2.2.4. What can we also add to classes?
III.2.2.4.1. Constant data members of objects
III.2.2.4.2. Reference type data members
III.2.2.4.3. Data members as objects
III.2.2.5. Pointers to class members
III.2.3. Operator overloading
III.2.3.1. Creating operator functions
III.2.3.2. Using type conversion operator functions
III.2.3.3. Extending classes with input/output operations
III.3. Inheritance (derivation)
III.3.1. Derivation of classes
III.3.2. Initialising base class(es)
III.3.3. Accessing class members in case of inheritance
III.3.3.1. Accessing inherited members
III.3.3.2. The friend relationship in inheritance
III.3.4. Virtual base classes in case of multiple inheritance
III.3.5. Inheritance and/or composition?
III.3.5.1. Reuse with composition
III.3.5.2. Reuse by public inheritance
III.4. Polymorphism
III.4.1. Virtual member functions
III.4.2. Redefining virtual functions
III.4.3. Early and late binding
III.4.3.1. Static early binding
III.4.3.2. Dynamic late binding
III.4.3.3. Virtual method table
III.4.4. Virtual destructors
III.4.5. Abstract classes and interfaces
III.4.6. Run-time type informations in case of classes
III.5. Class templates
III.5.1. A step-be-step tutorial for creating and using class templates
III.5.2. Defining a generic class
III.5.3. Instantiation and specialisation
III.5.4. Value parameters and default template parameters
III.5.5. The "friends" and static data members of a class template
III.5.6. The Standard Template Library (STL) of C++
III.5.6.1. The structure of STL
III.5.6.2. STL and C++ arrays
III.5.6.3. Using STL containers
III.5.6.4. Using STL container adaptors
IV. Programming Microsoft Windows in C++
IV.1. Specialties of CLI, standard C++ and C++/CLI
IV.1.1. Compiling and running native code under Windows
IV.1.2. Problems during developing and using programs in native code
IV.1.3. Platform independence
IV.1.4. Running MSIL code
IV.1.5. Integrated development environment
IV.1.6. Controllers, visual programming
IV.1.7. The .NET framework
IV.1.8. C#
IV.1.9. Extension of C++ to CLI
IV.1.10. Extended data types of C++/CLI
IV.1.11. The predefined reference class: String
IV.1.12. The System::Convert static class
IV.1.13. The reference class of the array implemented with the CLI array template
IV.1.14. C++/CLI: Practical realization in e.g. in the Visual Studio 2008
IV.1.15. The Intellisense embedded help
IV.1.16. Setting the type of a CLR program.
IV.2. The window model and the basic controls
IV.2.1. The Form basic controller
IV.2.2. Often used properties of the Form control
IV.2.3. Events of the Form control
IV.2.4. Updating the status of controls
IV.2.5. Basic controls: Label control
IV.2.6. Basic controls: TextBox control
IV.2.7. Basic controls: Button control
IV.2.8. Controls used for logical values: CheckBox
IV.2.9. Controls used for logical values: RadioButton
IV.2.10. Container object control: GroupBox
IV.2.11. Controls inputting discrete values: HscrollBar and VscrollBar
IV.2.12. Control inputting integer numbers: NumericUpDown
IV.2.13. Controls with the ability to choose from several objects: ListBox and ComboBox
IV.2.14. Control showing the status of progressing: ProgressBar
IV.2.15. Control with the ability to visualize PixelGrapic images: PictureBox
IV.2.16. Menu bar at the top of our window: MenuStrip control
IV.2.17. The ContextMenuStrip control which is invisible in basic mode
IV.2.18. The menu bar of the toolkit: the control ToolStrip
IV.2.19. The status bar appearing at the bottom of the window, the StatusStrip control
IV.2.20. Dialog windows helping file usage: OpenFileDialog, SaveFileDialog and FolderBrowserDialog
IV.2.21. The predefined message window: MessageBox
IV.2.22. Control used for timing: Timer
IV.2.23. SerialPort
IV.3. Text and binary files, data streams
IV.3.1. Preparing to handling files
IV.3.2. Methods of the File static class
IV.3.3. The FileStream reference class
IV.3.4. The BinaryReader reference class
IV.3.5. The BinaryWriter reference class
IV.3.6. Processing text files: the StreamReader and StreamWriter reference classes
IV.3.7. The MemoryStream reference class
IV.4. The GDI+
IV.4.1. The usage of GDI+
IV.4.2. Drawing features of GDI
IV.4.3. The Graphics class
IV.4.4. Coordinate systems
IV.4.5. Coordinate transformation
IV.4.6. Color handling of GDI+ (Color)
IV.4.7. Geometric data (Point, Size, Rectangle, GraphicsPath)
IV.4.7.1. Storing dimensions
IV.4.7.2. Storing planar points
IV.4.7.3. Storing planar rectangles
IV.4.7.4. Geometric shapes
IV.4.8. Regions
IV.4.9. Image handling (Image, Bitmap, MetaFile, Icon)
IV.4.10. Brushes
IV.4.11. Pens
IV.4.12. Font, FontFamily
IV.4.13. Drawing routines
IV.4.14. Printing
References:
V. Developing open-source systems
V.1. The structure of Linux systems
V.1.1. History of Unix
V.1.2. The Open Source software development model
V.1.3. The Linux operating system
V.1.4. Linux distributions
V.1.5. X Window System
V.1.6. Embedded Linux
V.2. The GCC compiler
V.2.1. The origins of GCC
V.2.2. Steps of compilation with GCC
V.2.3. Host and Target
V.2.4. The frequently used options of GCC
V.2.5. The make utility
V.2.6. The gdb debugger
V.3. Posix C, C++ system libraries
V.3.1. stdio.h
V.3.2. math.h
V.3.3. stdlib.h
V.3.4. time.h
V.3.5. stdarg.h
V.3.6. string.h
V.3.7. dirent.h
V.3.8. sys/stat.h
V.3.9. unistd.h
VI. Aim-specific applications
VI.1. SOC (System On a Chip)
VI.1.1. What is SOC?
VI.1.2. Parts of a SOC
VI.2. Embedded devices, development environments for PCs
VI.2.1. Atmel: WinAVR and the AVR Studio
VI.2.2. Microchip: MPLAB IDE and MPLAB-X
VI.3. Programming of distributed systems
VI.3.1. CORBA
VI.3.2. Open-source implementations of CORBA
VI.3.3. ICE – internet communication engine
A. Appendix – Standard C++ summary tables
A.1. ASCII code table
A.2. Reserved keywords in C++
A.3. Escape characters
A.4. C++ data types and their range of values
A.5. Statements in C++
A.6. C++ preprocessor directives
A.7. Precedence and associativity of C++ operations
A.8. Some frequently used mathematical functions
A.9. C++ storage classes
A.10. Input/Output (I/O) manipulators
A.11. The standard C++ library header files
B. Appendix – C++/CLI summary tables
B.1. C++/CLI reserved words
B.2. The types of classes and structures in C++/CLI
B.3. Operators for pointers and references in C++/CLI
B.4. The .NET (CTS) and the C++/CLI primitive types
List of Figures
I.1. Project selection
I.2. Project settings
I.3. Possible source files
I.4. Window of the running program
I.5. Steps of C++ program compilation
I.6. Classification of C++ data types
I.7. Functioning of a simple if statement
I.8. Logical representation of if-else structures
I.9. Logical representation of multi-way branches
I.10. Logical representation of while loops
I.11. Logical representation of for loops
I.12. Logical representation of do-while loops
I.13. C++ program memory usage
I.14. Dynamic memory allocation
I.15. Graphical representation of an one-dimensional array
I.16. Graphical representation of a two-dimensional array
I.17. The relationship between pointers and arrays
I.18. Two-dimensional arrays in memory
I.19. Dynamically allocated row vectors
I.20. Dynamically allocated pointer vector and row vectors
I.21. String constant in memory
I.22. String array stored in a two-dimensional array
I.23. Optimally stored string array
I.24. Structure in memory
I.25. Processing data in the program CDCatalogue
I.26. A singly linked list
I.27. Union in memory
I.28. The layout of the structure date in memory
II.1. Function definition
II.2. Steps of calling a function
II.3. Graph of the third degree polynomial
II.4. The interpretation of the parameter argv
II.5. Providing command line arguments
II.6. Calculating the area of a triangle
II.7. Variable scopes
II.8. The compilation process in C++
III.1. The object myCar (an instance of the class Truck)
III.2. Inheritance
III.3. Multiple inheritance
III.4. The class Employee and its objects
III.5. The multiple inheritance of I/O classes in C++
III.6. Hierarchy of geometrical classes
III.7. Using virtual base classes
III.8. Early binding example
III.9. Late binding example
III.10. Virtual method tables of the example code
IV.1. The memory before cleaning
IV.2. The memory after cleaning
IV.3. The window in the View/Designer
IV.4. The program in the View/Code window
IV.5. The Toolbox
IV.6. The Control menu
IV.7. The Properties Window
IV.8. The Event handlers
IV.9. A defined Event Handler
IV.10. The Intellisense window
IV.11. Solution Explorer menu
IV.12. Project properties
IV.13. Part of the program’s window
IV.14. Normal size picturebox on the form
IV.15. Stretched size picturebox on the form
IV.16. Automatic sized picturebox on the form
IV.17. Centered image in the picturebox on the form
IV.18. Zoomed bitmap in the picturebox on the form
IV.19. Menustrip
IV.20. Menuitem on the menustrip
IV.21. The Help menu
IV.22. The submenu
IV.23. The contextmenu
IV.24. Toolkit on toolstrip
IV.25. The MessageBox
IV.26. Binary file processing
IV.27. The classes of GDI+
IV.28. The drawn line automatically appears after every resizing activity.
IV.29. If we do not draw in Paint then the blue line disappears when maximizing after minimizing.
IV.30. General axonometry
IV.31. Isometric axonometry
IV.32. Military axonometry
IV.33. The default coordinate-system on form
IV.34. Cube in axonometry
IV.35. Central projection
IV.36. The perspective views of the cube
IV.37. Setting the distortion
IV.38. The distortion
IV.39. Trasnlating and scaling with a matrix
IV.40. Translation and rotation
IV.41. Trasnlating and shearing
IV.42. The mm scale and the PageScale property
IV.43. Color mixer
IV.44. Alternate and Winding curve chains
IV.45. Elliptical arc
IV.46. Cubic Bezier curve
IV.47. Cubic Bezier curve joined continously
IV.48. The cardinal spline
IV.49. Catmull-Rom spline
IV.50. Text in the figure
IV.51. Two concatenated figures connected and disconnected
IV.52. Widened figure
IV.53. Distorted shape
IV.54. Clipped figure
IV.55. Vectorial A
IV.56. Rasterized A
IV.57. Image on the form
IV.58. Halftone representation
IV.59. Rotated image
IV.60. Coloring bitmap
IV.61. Non-managed bitmap manipulating
IV.62. Brushes
IV.63. Pens
IV.64. Character features
IV.65. Traditional character widths
IV.66. ABC character widths
IV.67. Font families
IV.68. Font distortions
IV.69. Zoomed image and distorted zoomed image
IV.70. The OneNote program is the default printer
VI.1. The block diagram of PIC32MX
VI.2. Possibilities of connecting a RealTek SOC
VI.3. The example program
VI.4. The MPLAB IDE
VI.5. The structure of CORBA
VI.6. The structure of ICE programs
VI.7. The ICE libraries

Chapter I. Basics and data management of C++

Table of Contents
I.1. Creation of C++ programs
I.1.1. Some important rules
I.1.2. The first C++ program in two versions
I.1.3. Compilation and running of C++ programs
I.1.4. Structure of C++ programs
I.2. Basic data types, variables and constants
I.2.1. Classification of C++ data types
I.2.1.1. Type modifiers
I.2.2. Defining variables
I.2.2.1. Initial values of variables
I.2.3. Basic data types
I.2.3.1. Character types
I.2.3.2. Logical Boolean type
I.2.3.3. Integer types
I.2.3.4. Floating point types
I.2.3.5. enum type
I.2.3.6. sizeof operation
I.2.4. Creation of alias type names
I.2.5. Constants in language C++
I.3. Basic operations and expressions
I.3.1. Classification of operators based on the number of operands
I.3.2. Precedence and grouping rules
I.3.2.1. Rule of precedence
I.3.2.2. Rule of associativity
I.3.3. Mathematical expressions
I.3.3.1. Arithmetical operators
I.3.3.2. Mathematical functions
I.3.4. Assignment
I.3.4.1. Left value and right value
I.3.4.2. Side effects in evaluation
I.3.4.3. I.3.4.3 Assignment operators
I.3.5. Increment and decrement operations
I.3.6. Phrasing of conditions
I.3.6.1. Relational and equalityoperations
I.3.6.2. Logical operations
I.3.6.3. Short circuit evaluation
I.3.6.4. Conditional operator
I.3.7. Bit operations
I.3.7.1. Bitwise logical operations
I.3.7.2. Bit shift operations
I.3.7.3. Bit operations in compound assigment
I.3.8. Comma operator
I.3.9. Type conversions
I.3.9.1. Implicit type conversions
I.3.9.2. Explicit type conversions
I.4. Control program structures
I.4.1. Empty statements and statement blocks
I.4.2. Selective structures
I.4.2.1. if statements
I.4.2.2. switch statements
I.4.3. Iteration structures (loops)
I.4.3.1. while loops
I.4.3.2. for loops
I.4.3.3. do-while loops
I.4.3.4. break statements in loops
I.4.3.5. continue statements
I.4.4. goto statements
I.5. Exception handling
I.5.1. The try – catch program structure
I.5.2. Provoking exceptions - the throw statement
I.5.3. Filtering exceptions
I.5.4. Nested exceptions
I.6. Pointers, references and dynamic memory management
I.6.1. Pointers
I.6.1.1. Single indirection pointers
I.6.1.2. Pointer arithmetic
I.6.1.3. void * type general pointers
I.6.1.4. Multiple indirection pointers
I.6.1.5. Constant pointers
I.6.2. References
I.6.3. Dynamic memory management
I.6.3.1. Allocating and accessing heap memory
I.6.3.2. Deallocating allocated memory
I.7. Arrays and strings
I.7.1. C++ array types
I.7.1.1. One-dimensional arrays
I.7.1.1.1. Initializing and assigning values to one-dimensional arrays
I.7.1.1.2. One-dimensional arrays and the  typedef
I.7.1.2. Two-dimensional arrays
I.7.1.3. Variable-length arrays
I.7.1.4. The relationship between pointers and arrays
I.7.2. Dynamically allocated arrays
I.7.2.1. One-dimensional dynamic arrays
I.7.2.2. Two-dimensional dynamic arrays
I.7.3. The usage of the vector type
I.7.3.1. One-dimensional arrays in vectors
I.7.3.2. Two-dimensional arrays in vectors
I.7.4. Handling C-style strings
I.7.4.1. Strings in one-dimensional arrays
I.7.4.2. Strings and pointers
I.7.4.3. Using string arrays
I.7.5. The string type
I.8. User-defined data types
I.8.1. The structure type
I.8.1.1. Structure type and structure variables
I.8.1.2. Accessing the data members of structures
I.8.1.3. Nested structures
I.8.1.4. Structures and arrays
I.8.1.4.1. Arrays as data members of a structure
I.8.1.4.2. Structures as array elements
I.8.1.5. Creating singly linked lists
I.8.2. The class type
I.8.3. The union type
I.8.3.1. Anonymous unions
I.8.4. Bit fields

Knowledge necessary for program development in language C++ is detailed divided into three large categories. Category one (Chapter I, Basics and data management of C++ ) presents basic elements and program structures most of which can be found both in language C and C++. A program containing one single main() function is enough to practice the curriculum.

The next chapter (Chapter II, Modular programming in C++) assists in creating well-structured C and C++ programs according to algorithmic thinking using the presented solutions. Functions play the main role in this part.

The third chapter (Chapter III, Object-oriented programming in C++) presents the means of the nowadays more and more dominant object-oriented program building. Here classes that encapsulate data and the operations to be carried out on them into one single unit are in focus.

I.1. Creation of C++ programs

Before the elements of language C++ are detailed, issues on the creation and running of C++ programs are to be overviewed. A few rules that are to be applied when writing C++ source codes, program structures and steps needed for running in Microsoft Visual C++ system are described.

I.1.1. Some important rules

Standard C++ language belongs to conventional programming languages in case of which the creation of the program involves typing the whole text of the program, as well. When typing the text (source code) of the program a few restrictions have to be considered:

  • The basic elements of the program can only contain the characters of the 7 bit ASCII code table (see in Appendix Section A.1, “ASCII code table”), however character and text constants, as well as remarks may contain characters of any coding (ANSI, UTF-8, Unicode). A few examples:

    /* Value is given for an integer, a character and a text
      (string)  variable (multiline remark) */
    int variable = 12.23;  // value giving (remark until the 
                           // end of the line)
    char sign = 'Á';    
    string header  = "Programming is fun" 
    
  • C++ compiler differentiates small and capital letters in the words (names) used in the program. Most of names that make up the language contain only small letters.

  • Certain (English) words cannot be used as own names since these are keywords of the compiler (see in Appendix Section A.2, “Reserved keywords in C++”).

  • In case of creating own names please note that they have to start with a letter (or underscore sign), and should contain letters, numbers or underscore signs in their other positions. (Please note that it is not recommended to use the underscore sign.)

  • One last rule before writing the first C++ program is that we should not too long however so called talkative names define such as: ElementSum, measurementlimit, piece, RootFinder.

I.1.2. The first C++ program in two versions

Since language C++ is compatible from the top with the standard (1995) C language, in case of creating simple programs C programming knowledge can also result in success. Let’s take the example of perimeter and area calculation of a circle in plane. The algorithm is very simple, since after the radius is entered, only a few formulas have to be calculated.

The two solutions below only differ from each other in the input/output operations basically. In style C case printf() and scanf() functions are used, while in the second C++ type case objects cout and cin are applied. (In case of further examples the latter solution is used.) The source code has to be placed into a .CPP extension text file in both cases.

Style C solution with a slight modification can also be compiled with a C compiler:

// Circle1.cpp
#include "cstdio"
#include "cmath"
using namespace std;
 
int main()
{
    const double pi = 3.14159265359;
    double radius, area, perimeter;
    // Reading radius
    printf("Radius = ");
    scanf("%lf", &radius);
    // Calculations
    perimeter = 2*radius*pi;
    area = pow(radius,2)*pi;
    printf("Perimeter: %7.3f\n", perimeter);
    printf("Area: %7.3f\n", area);
    // Waiting for pressing Enter
    getchar();
getchar();
    return 0; 
}
The solution that uses C++ objects is a little easier to understand:
// Circle2.cpp
#include "iostream"
#include "cmath"
using namespace std;
 
int main()
{
    const double pi = 3.14159265359;
    // Reading radius
    double radius;
    cout << "Radius = ";
    cin >> radius;
    // Calculations
    double perimeter = 2*radius*pi;
    double area = pow(radius,2)*pi;
    cout << "Perimeter: " << perimeter << endl;
    cout << "Area: " << area << endl;
    // Waiting for pressing Enter
    cin.get();
    cin.get();
    return 0; 
}

Both solutions use C++ and own names as well (radius, area, perimeter, pi). It is an essential rule that all names have to be declared for the C++ compiler before first usage. In the example lines that start with double and constdouble not only declare the names but also create (define) their related storages in the memory. However, similar descriptions are not found for names printf(), scanf(), pow(), cin and cout. The declarations of these names can be found in the (#include) files (cstdio, cmath and iostream, respectively) included at the beginning of the program. The names are closed in the namespace std.

Function printf() presents data in a formatted way. If data are directed (<<) to object cout, formatting is more complicated, but in that case format elements belonging to different data types does not have to be dealt with. The same is true for scanf() and cin elements used for data entry. Another important difference is the security of the applied solution. In case scanf() is called, the beginning address (&) of the memory space for data storage has to be entered, and this way several errors may arise in the program. Oppositely, application of cin is completely safe.

Another remark to getchar() and cin.get() calls at the end of programs. After the last call of scanf() and cin the data entry buffer maintains data correspondent to key Enter. Since both functions that read characters carry out processing after key Enter is pressed, the first calls only remove Enter that remained in the buffer, and only the second call is waiting for another Enter pressing.

In both cases an integer (int) type function, named main() , contains the main part of the program, closed between the curly brackets that include body the of the function. Functions – as in mathematics – have values that are defined after statement return in language C++. The explanation of values comes from the ancient versions of language C, and accordingly 0 means that everything was all right. In case of main() this function value is received by the operation system since, which calls the function as well (starts the program running this way).

I.1.3. Compilation and running of C++ programs

In most development systems the basis of program creation is the generation of a so called project. Firstly, the type of the application has to be chosen, and then the source files have to be added to the project. From the several possibilities offered by system Visual C++ the Win32 console application is the simple C++ application type with text interface. Let’s see the necessary steps!

After selections File / New / Project… Win32 / Win32 Console Application the name of the project has to be entered:

Project selection
Figure I.1. Project selection


After key OK is pressed, the Console application wizard starts, and using its settings an empty project can be created:

Project settings
Figure I.2. Project settings


After pressing key Finish the solution window (Solution Explorer) appears, where a new source can be added to the project ( Add / New Item… ) using mouse right click on Source Files .

Possible source files
Figure I.3. Possible source files


After the text of the program is typed, compilation can done through menu points Build / Build Solution or Build / Rebuild Solution . In case of successful compilation (CircleCalculation - 0 error(s), 0 warning(s)) the program can be started by choosing menuitem Debug / Start Debugging (F5) or Debug / Start Without Debugging (Ctrl+F5).

Window of the running program
Figure I.4. Window of the running program


After menu Build / Configuration Manager... is selected a window pops up where either the debug ( Debug ) or final ( Release ) version can be chosen to be compiled. (This selection determines the content of the file to be run and its place on the disk.)

In case of any Build , compilation is carried out in several steps. Figure I.5, “Steps of C++ program compilation” shows these steps.

Steps of C++ program compilation
Figure I.5. Steps of C++ program compilation


The preprocessor interprets lines starting with hash mark sign (#) and as a result source code in language C++ is created. C++ compiler compiles this code to an object code that misses the machine code that realizes library elements. As the last step the linker fills this gap and transforms the complete machine (native) code to an executable application.

It is to be noted that in case of projects that contain more source files (modules) preprocessor and compiler carry out compilation module by module and the object modules created this way are built together into one single executable file by the linker.

After running the program has to be saved so that we can work with it later. There are several possible solutions, however the next, already proven steps can help us: first all files are saved onto the disk ( File / SaveAll ), then the project is closed together with the solution ( File / Close Solution ). (Solution denotes the set of linked projects that can be recompiled in one single step if necessary.)

Finally let’s take a look at the directory structure that is created on the hard disk when the project is compiled.

C:\Work\CircleCalculation\CircleCalculation.sln
C:\Work\CircleCalculation\CircleCalculation.ncb
C:\Work\CircleCalculation\Debug\CircleCalculation.exe
C:\Work\CircleCalculation\Release\CircleCalculation.exe
C:\Work\CircleCalculation\CircleCalculation\CircleCalculation.vcproj
C:\Work\CircleCalculation\CircleCalculation\Circle1.cpp
C:\Work\CircleCalculation\CircleCalculation\Debug\Circle1.obj
C:\Work\CircleCalculation\CircleCalculation\Release\ Circle1.obj

Debug and Release directories that can be found above this level contain the executable application, while directories below with the same names contain work files. These four folders can be deleted since they will be created again during compilation. It is also recommended to delete file Circle calculation.ncb that assists the intellisense services of development environment since its size can be quite large. The solution (project) can be reopened with the Circle calculation.sln file ( File / Open / Project / Solution ).

I.1.4. Structure of C++ programs

As the previous part revealed, all programs written in language C++ can be found in one or more source files (compilation unit, module), the extension of which is .CPP. C++ modules can be compiled to object codes independently.

So called declaration (include, header) files usually belong to the program as well and they can be integrated in the source files using precompilation statement #include. Include files cannot be compiled independently, however most development environments support their precompilation, accelerating the processing of C++ modules this way.

The structure of C++ modules follows that of C language programs. The program code – according to the principle of procedural programming – is placed in functions. Data (declarations/definitions) can be found both outside (globally, at file level) and within (on local level) the functions. The former are called external (extern) while the latter are classified in the automatic (auto) storage class by the compiler. The example program below illustrates this:

// C++ preprocessor directives
#include <iostream>
#define MAX 2012
 
// in order to reach the standard library names
using namespace std;
 
// global declarations and definitions
double fv1(int, long);            // function prototype
const double pi = 3.14159265;     // definition
 
// the main() function
int main()
{
   /* local declarations and definitions   
      statements                               */
   return 0;  // exit the program
}
// function definition
double fv1(int a, long b) 
{
   /* local declarations and definitions   
      statements                               */
   return a+b; // return from the functions
}

In language C++ object-oriented (OO) approach may also be used when creating programs. According to this principle, the basic unit of our program is the class that encapsulates functions and data definitions (for details see Chapter III, Object-oriented programming in C++). In this case function main() defines the entry point of our program. Classes are usually placed between global declarations, either directly in the C++ module or by the including of a declaration file. „Knowledge” placed in a class can be reached through the instances (variables) of the class.

Let’s take the example of circle calculation task defined with object-oriented approach.

/// Circle3.cpp
#include "iostream"
#include "cmath"
using namespace std;
 
// Class definition
class Circle 
{
        double radius;
        static const double pi;
    public:
        Circle(double r) { radius = r; }
        double Perimeter() { return 2*radius*pi; }
        double Area() { return pow(radius,2)*pi; }
};
const double Circle::pi = 3.14159265359;
 
int main()
{
    // Reading radius
    double radius;
    cout << "Radius = ";
    cin >> radius;
    // Creation and usage of object Circle
    Circle circle(radius);
    cout << "Perimeter: " << circle.Perimeter() << endl;
    cout << "Area: " << circle.Area() << endl;
    // Waiting for pressing Enter
    cin.get();
    cin.get();
    return 0; 
}

I.2. Basic data types, variables and constants

When programming, we attempt to make our activities comprehensible for computers in order that they could help us do those tasks or that they do those tasks for us. When we work, we receive data that we store in general to process them and to extract information from them later. Data are really diverse but most of them consist of numbers or texts in everyday life.

In this chapter, we deal with describing and storing data in C++. We also learn how to receive data (from an input) and how to visualize them.

On the basis of the Neumann principle, data are stored in a uniform way in computer memory, that is why programmers have to provide the type and the features of the data in a C++ program.

Classification of C++ data types
Figure I.6. Classification of C++ data types


I.2.1. Classification of C++ data types

The data type determines the number of bits they occupy in memory and their interpretation (variable). It also affects the way data are processed since C++ is a strongly typed language, therefore compilers check many things.

C++ data types (shortened as types) can be classified in several ways. Let's use the classification of Microsoft VC++ language (Figure I.6, “Classification of C++ data types”). According to it, there are basic data types that can store one value (integer, character, real number). However, there are also derived data types that are based on basic types, but they allow the creation of data structures that may store more values.

I.2.1.1. Type modifiers

In C++ language the meaning of basic integer types can be altered by type modifiers . The signed/unsigned modifier pair determines whether the stored bits can be interpreted as negative numbers or not. With the short/long pair size of the storage can be fixed to 16 or 32 bits. Most C++ compilers support 64 bits storage with the long long modifier, therefore it will also be dealt with in this book. Type modifiers can also be used as type definitions alone. Possible type modifiers are summarized in the following table.­ Elements in each row designate the same data type.

char

signed char

  

short int

short

signed short int

signed short

int

signed

signed int

 

long int

long

signed long int

signed long

long long int

long long

signed long long int

signed long long

unsigned char

   

unsigned short int

unsigned short

  

unsigned int

unsigned

  

unsigned long int

unsigned long

  

unsigned long long int

unsigned long long

  

The required memory of arithmetical types with type modifiers and the value range of stored data are summarized in Appendix Section A.4, “C++ data types and their range of values”.

Basic types are detailed in the present subchapter, while derived types are treated in the following parts of Chapter I, Basics and data management of C++ .

I.2.2. Defining variables

Storing data in memory and accessing them is vital for every C++ computer program. That is why, we start with getting to know memory spaces to which names are assigned, i.e. variables. In most cases, variables are defined, i.e. their type is provided (they are declared), and memory space is allocated for them. (In the beginning, we rely on compilers for memory allocation.)

The total definition row of a variable is very complex at first sight; however, it is done in a much simpler way in practice.

        〈storage class〉 〈type qualifier〉 〈type modifier ... 〉 typevariable name 〈= initial value〉 〈, … 〉;

        〈storage class〉 〈type qualifier〉 〈type modifier ... 〉 typevariable name 〈(initial value〉 〈, … 〉;

(In the previous generalized forms, the 〈 〉 signs indicate optional elements while the three points show that a definition element can be repeated.)

The storage classes auto, register, staticand extern – of C++ determine the lifetime and visibility of variables. At first, storage classes are not defined explicitly, therefore the default case of C++ is used, in which variables defined outside functions have extern (global), while variables defined within a function have auto (local) storage classes. Extern variables are created when the program is started, exist until its end and can be accessed from anywhere during execution. On the contrary, auto variables are born when a function is entered and they are deleted when the function is exited. Therefore they can be accessed within the function.

With type qualifiers further information can be assigned to variables.

  • Variables with const keyword cannot be modified (they are read-only, i.e. constants).

  • The volatile type qualifier indicates that the value of the variable can be modified by a code independent of our program (e.g. by another running process or thread). The word volatile tells the compiler that it is not known in advance what will happen to that variable. (That is why, compilers get the value of the variable from the memory each time a volatile variable is referenced.)

    int const
    const double
    volatile char
    float volatile
    const volatile bool
    

I.2.2.1. Initial values of variables

Variable definition ends with giving an initial value. Initial values can be provided after an equal sign or between parentheses:

using namespace std;
 
int sum, product(1);
 
int main()
{
    int a, b=2012, c(2004);
    double d=12.23, e(b);
}

In this example, there is no initial value for two variables (sum and a), which leads in general to a program error. However, the variable sum has the initial value of 0, since global variables are always initialized (to zero) by compilers. But the local a is a different case since its initial value is provided by the actual content of the memory allocated for the variable and that can be anything. In these cases, the value of these variables can be set by assignment before their usage. During assignment, the value of the expression on the right of the equal sign is assigned to the variable on the left:

a = 1004;

In C++ language the initial values can be provided by any compile-time and run-time expressions.:

#include <cmath>
#include <cstdlib>
using namespace std;
 
double pi = 4.0*atan(1.0);   // π
int randomnumber(rand() % 1000);
 
int main()
{
    double alimit = sin(pi/2);
} 

It is important that definition and value assignment statements end with a semicolon.

I.2.3. Basic data types

Basic data types are the equivalents of digits or letters in human language. A PhD dissertation in Mathematics or Winnie the Pooh can be created with the help of them. In the following overview, integer types are divided into smaller groups.

I.2.3.1. Character types

The char type has a double role. On one hand, it makes it possible to store ASCII (American Standard Code for Information Interchange) characters (Appendix Section A.1, “ASCII code table”), on the other hand, it can be used as one byte signed integer.

char lettera = 'A';
cout << lettera << endl;
char response;
cout << "Yes or No? ";
cin>>response; 
// or
response = cin.get();

The double nature of char type is well represented by the possibilities how constant values (literals) can be assigned to them. Characters can be provided between apostrophes or by their integer code. Besides decimal numbers, character codes can be given in octal format (starting with zero) or in a hexadecimal format (starting with 0x). As an example, let's see what the equivalents of capital letter C are.

        ’C’                67                0103                0x43

Certain standard control and special characters can be given by the so-called escape sequences. In an escape sequence, character backslash (\) is followed by special characters or numbers, as it can be seen in the table of Appendix Section A.3, “Escape characters”:  ’\n’,  ’\t’,  ’\’’,  ’\”’,  ’\\’.

If we want to work with characters of the 8-bit ANSI code table or with a one-byte integer value, it is recommended to use the unsigned char type.

In order to process a character of the Unicode table, the variable should be the two-byte wchar_t type, and constant character values should be preceded by capital letter L.

wchar_t uch1 = L'\u221E';
wchar_t uch2 = L'K';
wcout<<uch1;
wcin>>uch1;
uch1 = wcin.get();

We should always make sure not to confuse apostrophes (’) with quotation marks ("). Quotation marks are used for string constants (string literals) in the computer program.

        "This is an ANSI string constant."

        or

        L"This is a Unicode string constant."

I.2.3.2. Logical Boolean type

Bool type variables can have two values: logical false is 0, while logical true is 1. In Input/Output (I/O) operations logical values are represented by integer values:

bool start=true, end(false);
cout << start;
cin >>end;
This default operation can be overridden by boolalpha and noboolalpha I/O manipu­lators:
bool start=true, end(false);
cout << boolalpha << start << noboolalpha; // true
cout << start;                             // 1 
cin >> boolalpha>> end;                    // false
cout << end;                               // 0

I.2.3.3. Integer types

Probably the most frequently used basic data type of the C++ language is int together with its type modifiers. When an integer value is provided in the program, compilers attempt to assign automatically the int type. If the value is out of the value range of the int type, it uses an integer type with a broader range or it gives an error message in case of a too big constant.

The type of constant integer values can be provided by the U and L postfixes. U means unsigned, L means long:

2012

int

2012U

unsigned int

2012L

long int

2012UL

unsigned long int

2012LL

long long int

2012ULL

unsigned long long int

Of course, integer values can be given not only in decimal format (2012) but also in octal (03724) or hexadecimal (0x7DC) number system. The choice of these number systems can be expressed in I/O operations by stream manipulators ( dec , oct , hex ), the effects of which last until the next manipulator:

#include <iostream>
using namespace std;
 
int main()
{
    int x=20121004;
    cout << hex << x << endl;
    cout << oct << x << endl;
    cout << dec << x << endl;
    cin>> hex >> x;
}

It is not needed to provide the prefixes indicating number systems in case of data entering. There are some manipulators that ensure simple formatting possibilities. With parameterized manipulator setw () the width of the field to be used in printing operations can be set; and within that the content can be aligned to the ( left ) or to the right ( right ), which is the default value. setw () effects only the next data element, while alignment manipulators keep their effect until the next alignment manipulator.

#include <iostream>
#include <iomanip>
using namespace std;
 
int main()
{
    unsigned int number = 123456;
    cout<<'|' << setw(10) << number << '|' << endl;
    cout<<'|' << right << setw(10) << number << '|' << endl;
    cout<<'|' << left << setw(10) << number << '|' << endl;
    cout<<'|' << setw(10) << number << '|' << endl;
}

The output reflects the effects of these manipulators well:

|    123456|
|    123456|
|123456    |
|123456    |

I.2.3.4. Floating point types

Mathematical and technical calculations require the use of real numbers containing fractions as well. Since the place of the decimal point is not fixed in these values, these numbers can be stored by floating point types: float, double, long double. These types are only different from one another concerning the necessary memory size, the range of values and the number of exact decimal places (see Appendix Section A.4, “C++ data types and their range of values”). (Contrary to the standard recommendation, Visual C++ treats the long double type as double.)

It has to be noted already at the very beginning that floating point types do not make it possible to represent fractions precisely, because the numbers are stored in the scientific form (mantissa, exponent), in the binary number system.

double d =0.01;
float f = d;
cout<<setprecision(12)<<d*d<< endl;  // 0.0001
cout<<setprecision(12)<<f*f<< endl;  // 9.99999974738e-005

There is only one value the value of which is surely exact: 0. Therefore if floating point variables are set to 0, their value is 0.0.

Floating point constants can be provided in two ways. In case of smaller numbers decimal representation is used generally, where the decimal point separates the integer part from the fraction part, e.g. 3.141592653, 100., 3.0. In case of bigger numbers, the computerized version of scientific form, well known from mathematics is applied, where letter e or E is followed by the exponent (the power of 10): 12.34E-4, 1e6.

Floating point constant values are double type by default. Postfix F designates a float type variable, whereas L designates a long double variable: 12.3F, 1.2345E-10L. (It is a frequent programming error that if the constant contains neither decimal point nor exponent, the constant value will be treated as an integer and not as a floating point type as expected.)

During printing the value of floating point variables, the already mentioned field width ( setw ()), as well as the number of digits after the decimal point - setprecision () can be set (see Appendix Section A.10, “Input/Output (I/O) manipulators”). If this value cannot be printed in the set format the default visualization is used. Manipulator fixed is used for decimal representation whereas scientific is applied for scientific representation.

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

int main()
{
    double a = 2E2, b=12.345, c=1.;
    cout << fixed; 
    cout << setw(10)<< setprecision(4) << a << endl;
    cout << setw(10)<< setprecision(4) << b << endl;
    cout << setw(10)<< setprecision(4) << c << endl;
}

The results of program running are:

  200.0000
   12.3450
    1.0000

Before getting on, it is worth having a look at automatic type conversion between C++ arithmetical types. It is evident that a type with a smaller value range can be converted into a type with a wider range without data loss. However, in the reverse direction, the conversion generally provokes data loss for which compilers do not alert, and one part of the bigger number may appear in the "smaller" type variable.

    short int s;
    double d;
    float f;
    unsigned char b;
 
    s = 0x1234;
    b = s; // 0x34 ↯
    // ------------------------
    f = 1234567.0F;
    b = f; // 135 ↯
    s = f; // -10617 ↯
    // ------------------------
    d = 123456789012345.0;
    b = d; // 0 ↯
    s = d; // 0 ↯
    f = d; // f=1.23457e+014 – precision loss ↯

I.2.3.5. enum type

In computer programs integer type constant values that are logically in connection with one another are often used. The readability of our programs is much better if these values are replaced by names. For that purpose, it is worth defining a new type (enum) with its range of values:

        enum 〈type identifier〉 { enumeration };

If type identifier is not given, the type is not created only the constants. Let's see the following example with an enumeration that contains the days of the week.

        enum workdays {Monday, Tuesday, Wednesday, Thursday, Friday};

A separate integer value is associated to the names in this enumeration. By default, the value of the first element (Monday) is 0, that of the next one (Tuesday) is 1, and so on (the value of Friday is 4).

In enumerations, we can directly assign values to their elements. In that case, automatic incrementation continues from the given value. It is not a problem if the same values are repeated or if we assign negative values to the elements. However, we have to make sure that in the definitions there are not two enum elements with the same name within a given visibility scope (namespace).

        enum consolecolours {black,blue,green,red=4,yellow=14,white};

In the enumeration named consolecolours the value of white is 15.

In enumerations that do not contain direct value assignment, the number of elements can be obtained by adding an extra element:

        enum stateofmatter { ice, water, vapour, numberofstates};

The value of element numberofstates equals to the number of states, i.e. 3.

In the following example the usage of enum types and enum constants are demonstrated:

#include <iostream>
using namespace std;
 
int main()
{
    enum card { clubs, diamonds, hearts, spades };
 
    enum card cardcolour1 = diamonds; 
    card cardcolour2 = spades;
    cout << cardcolour2 << endl;
 
    int colour = spades;
    cin >> colour;
    cardcolour1 = card(colour);
}

Enumeration type variables can be defined according to the rules of both C and C++ languages. In C language enum types are defined by keyword enum and type identifiers together. In C++ language type identifiers represent alone enum types.

When we print an enumeration type variable or an enumeration constant, by default we get the integer corresponding to the given element. However, when the input is read in, the situation is completely different. Since enum is not a predefined type of C++ language, contrary to the above mentioned types, cin does not know it. As it can be seen in the example, reading in can be realized by using an int type variable. However, the typedness of C++ language may cause problems here since it only does certain conversions if it is "asked" to do so with type conversion (cast) operation: typename(value). (The programmers have to check the values since C++ does not deal with them.)

I.2.3.6. sizeof operation

C++ language contains an operator that is evaluated during compilation and that determines the size of any type or any variable and expression type in bytes.­

        sizeof(typename)

        sizeofvariable/expression

        sizeof(variable/expression)

From that, we can infer the type of the result of a given expression:

cout << sizeof('A' + 'B') <<endl;        // 4 - int
cout << sizeof(10 + 5) << endl;        // 4 - int
cout << sizeof(10 + 5.0) << endl;        // 8 - double
cout << sizeof(10 + 5.0F) << endl;        // 4 - float

I.2.4. Creation of alias type names

When variables are defined, their types are composed of more keywords in general because of type qualifiers and type modifiers. These declaration instructions are difficult to read and they can often be misleading.

        volatile unsigned short intsign;

In fact, we would like to store unsigned 16-bit integers in variable sign. Keyword volatile only gives complementary information to the compiler, we do not deal with it during programming. Declaration typedef makes the above mentioned definition more readable:

        typedef volatile unsigned short intuint16;

This declaration creates type name uint16, therefore the definition of variable sign is:

uint16 sign;

typedef can also be useful in case of enumerations:

typedef enum {falsevalue = -1, unknown, truevalue} bool3;  
bool3 start = unknown;

Creating type names is always successful if we respect the following empiric rule:

  • Give a variable definition without an initial value and with the type for which we would like to create an alias name.

  • Give the keyword typedef before the definition, because of which the given name will not designate a variable but a type.

It is particularly useful to use typedef in case of complex types, where type definition is not always simple.

Finally, let's see some frequently used alias type names.

        typedef unsigned charbyte, uint8;

        typedef unsigned shortword, uint16;

        typedef long long intint64;

I.2.5. Constants in language C++

Using names instead of constant values makes program codes more readable. In C++ language we can choose from many possibilities, following the traditions of the C language.

Let's start with constants (macros) #define that should be avoided in C++ language. Preprocessor directive #define is followed by two texts, separated from each other by a space. The preprocessor reads the whole C++ source code and replaces the defined first word with the second one. It should be noted that all characters of the names used by preprocessor are always written in capital letters and that preprocessor stastements should not be terminated by semicolons.

#define ON 1
#define OFF 0
#define PI 3.14159265
 
int main()
{
    int switched = ON;
    double rad90 = 90*PI/180;
    switched = OFF;
}

The compiler gets the following C++ computer program from the prepocessor:

int main()
{
    int switched = 1;
    double rad90 = 90*3.14159265/180;
    switched = 0;
}

The big advantage and disadvantage of this solution is untypedness.

Constant solutions supported by C++ language are based on const type qualifiers and the enum type. Keyword const can transform any variable with an initial value to a constant. C++ compilers do not allow the value modification of these constants at all. The previous example code can be rewritten in the following way:

const int on = 1;
const int off = 0;
const double pi = 3.14159265;
 
int main()
{
    int switched = on;
    double rad90 = 90*pi/180;
    switch = off;
}

The third possibility is to use an enum type, which can only be applied in case of integer (int) type constants. The swiching constants in the preceding example are now created as elements of an enumeration:

enum onoff { off, on };
int switched = on;
switch = off;

enum and const constants are real constants since they are not stored in the memory by compilers. While #define constants have their effects from the place of their definition until the end of the file, enum and const constants observe the traditional C++ visibility and lifetime rules.

I.3. Basic operations and expressions

After data storage is solved we can move on in the direction of obtaining information. Information is usually created as a result of a data processing that means the execution of a series of instructions in C++ language. The simplest data processing method is when different operations (arithmetic, logical, bitwise by bit etc.) are performed on our data as operands. The result of these operations is new data or the information itself that is necessary for us. (Aimed data becomes information.) Operands linked with operators are called expression s. In language C++ the most frequent instruction group consists of expressions (assignment, function call, …) closed with a semicolon.

Evaluation of an expression usually results in the calculation of a value, generates a function call or causes a side effect. In most cases a combination of these three effects occurs during processing (evaluating) the expressions.

Operations have impact on operands . The operands that require no further evaluation are called primary expressions. Identifiers, constant values and expressions in brackets are this kind.

I.3.1. Classification of operators based on the number of operands

Operators can be classified based on more criteria. Classification – for instance – can be carried out based on the number of operands. In case of operators with one operand (unary) the general form of the expression is:

        op operand                or                operand op

In the first case, where the operator (op) precedes the operand is called a prefix form, while the second case is called postfix form:

-n

sign change,

n++

incrementing the value of n (postfix),

--n

decrementing the value of n (prefix),

double(n)

transformation of the value of n to real.

Most operations have two operands – these are called two operand (binary) operators:

        operand1 op operand2

In this group bitwise operations are also present besides the traditional arithmetic and relational operations:

n & 0xFF

obtaining the low byte of n,

n + 2

calculation of n + 2,

n << 3

shift the bits of n to the left with 3 positions,

n += 5

increasing the value of n with 5.

The C++ language has one three operand operation, this is the conditional operator:

        operand1 ? operand2 : operand3

I.3.2. Precedence and grouping rules

As in mathematics, the evaluation of expressions is carried out according to the rules of precedence. These rules determine the execution sequence of different precedence operations in an expression. In case of identical precedence operators grouping from left to right or from right to left (associativity) provides guidance. Operations of the C++ language can be found in Appendix Section A.7, “Precedence and associativity of C++ operations”, listed starting from the highest precedence. The right side of the table shows the execution direction of identical precedence operations, as well.

I.3.2.1. Rule of precedence

If different precedence operations are found in one expression, then always the part that contains an operator of higher precedence is evaluated first.

The sequence of evaluation can be confirmed or changed using brackets, already known from mathematics. In C++ language only round brackets () can be used, no matter how deep bracketing is needed. As an empirical rule if there are two or more different operations in one expression, brackets should be used in order to make sure that operations are carried out in the desired sequence. We should rather have one pair of redundant brackets than a wrong expression.

The evaluation sequence of expressions a+b*c-d*e and a+(b*c)-(d*e) is the same therefore the steps of evaluation are (* denotes the operation of multiplication):

int a = 6, b = 5, c = 4, d = 2, e = 3;
b * c   ⇒ 20
d * e   ⇒  6
a + b * c ⇒ a + 20  ⇒ 26
a + b * c - d * e ⇒ 26 - 6 ⇒ 20

The steps of processing expression  (a+b)*(c-d)*e  are:

int a = 6, b = 5, c = 4, d = 2, e = 3;
(a + b)  ⇒  11
(c - d)  ⇒   2
(a + b) * (c - d) ⇒ 11 * 2  ⇒ 22
22 * e   ⇒ 22 * 3   ⇒ 66

I.3.2.2. Rule of associativity

Associativity determines whether the operation of the same precedence level is carried out form left to right or from right to left.

For example, in the group of assignment statements evaluation is carried out from the right to the left and this way more variables can obtain values at the same time:

        a = b = c = 0;  identical with  a = (b = (c = 0));

In case operations of the same precedence level can be found in one arithmetic expression, the rule from left to right is applied. The evaluation of expression a+b*c/d*e starts with the execution of three identical precedence operations. Due to associativity the evaluation sequence is:

int a = 6, b = 5, c = 4, d = 2, e = 3;
b * c   ⇒ 20
b * c / d ⇒ 20 / d ⇒ 10
b * c / d * e ⇒ 10 * e ⇒ 30
a + b * c / d * e ⇒ a + 30 ⇒ 36

The sequence of operations is well visible if transformed into a mathematical formula:

If the task is to program formula , it can be solved in two ways:

  • the denominator is put into brackets, therefore the term is divided by a product:  a+b*c/(d*e),

  • division with both terms of the product in the denominator:  a+b*c/d/e.

I.3.3. Mathematical expressions

The simplest programs are usually used for the solution of mathematical tasks. In mathematical expressions different functions are also used besides basic operations (arithmetic operators in the wording of C++).

I.3.3.1. Arithmetical operators

The group of arithmetical operators includes the operator of modulo (%) besides the conventional four basic operations. Addition (+), subtraction (-), multiplication (*) and division (/) can be carried out both in case of integer and floating point numbers. Division denotes integer division in case of integer type operands:

        29 / 7                value of the expression (quotient)                4

        29 % 7                value of the expression (remainder)                1

In case of integers a and b that are not equal to zero, the following formula is always valid:

        (a / b) * b + (a % b) a

One operand minus (-) and plus (+) operators also belong to this group. Sign minus changes the value of the operand that stands behind it to the opposite sign (negation).

I.3.3.2. Mathematical functions

In case we need further mathematical operations besides the basic ones mentioned above, the mathematical functions of the standard C++ library have to be used. Declaration file cmath should be included into our program in order to reach the functions. The most frequently used mathematical functions are summarized in Appendix Section A.8, “Some frequently used mathematical functions”. The library provides us all functions in three versions according to the three floating point types (float, double, long double).

Let’s take the example of the well-known solution formula of a quadratic equation with one unknown where a, b and c are the coefficients of the equation.

 

 

The solution formula program in C++:

#include <iostream>
#include <cmath>
using namespace std;
 
int main() 
{
    double a = 1, b = -5, c =6, x1, x2;
    x1 = (-b + sqrt(b*b-4*a*c))/(2*a);
    x2 = (-b - sqrt(b*b-4*a*c))/(2*a);
    cout << x1 << endl;
    cout << x2 << endl;
}

I.3.4. Assignment

Variables obtain value usually during assignment (value giving), the general form which is:

        variable = value;

In C++ language assignment operation (=) is an expression in fact that is evaluated by the compiler program, and its value is the value on its right side. Both sides of the assignment operator can contain expressions, however they differ basically. The expression on the left side denotes the memory space where the value of the expression on the right side should be stored.

I.3.4.1.  Left value and right value

The C++ language has different names for the expressions on the two sides of assignment . The value of the expression on the left side of the equation sign is called left value (lvalue), while the expression on the right side is called right value (rvalue). Let’s take an example of two simple assignments.

        int x;

        x = 12;

        x = x + 11;

During the first assignment variable x is present as left value, meaning that the address of the variable denotes the storage where the constant value given in the right side has to be copied into. During the second assignment variable x can be found on both sides of the assignment. x on the left side again denotes the storage in the memory (lvalue), while x on the right side is a right value expression, the value (23) of which is determined by the compiler before executing the assignment. (It is to be noted that the value of the whole expression is a right value that is not used for anything.)

I.3.4.2.  Side effects in evaluation

As it was already mentioned the basic aim of the evaluation of all expressions is to calculate the value of them. However, during processing certain operations – assignment, function call and increment, decrement (++, --) presented later – the value of operands may also change besides the value of the expression. This phenomenon is called side effect.

The C++ standard does not defines the evaluation sequence of side effects, therefore all solutions that result of which depend on the sequence of side effect evaluation should be avoided, e.g.:

a[i] = i++;            // ↯
y = y++ + ++y;            // ↯
cout<<++n<<pow(2,n)<<endl;    // ↯

I.3.4.3. I.3.4.3 Assignment operators

As it was already mentioned assignment in language C++ is an expression that gives the value entered on the right side to the storage denoted by the left side operand, and this value is also the value of the assignment expression. Consequently, assignment may occur in any expression. In the example below the result of expressions on the left side is the same as that of the right side:

a = 4;

b = (a+10)*4;

b = ((a = 4) + 10)* 4;

a = 10;

b = 10;

a = b = 10;

A frequently used form of the assignment is when the value of a variable is modified with any operation and the value created this way is stored in the variable:

        a = a + 2;

This kind of expressions can be written in a shorter form as well:

        a += 2;

In general, it can be stated that

        expression1 = expression 1 op expression 2

form expressions can also be written using the so called compound assignment operation:

        expression 1 op= expression 2

The two forms are equal except that in the second case the evaluation of the left side expression is carried out only once. Two operand arithmetic and bitwise operations can be used as operator (op). (It is to be noted that no space can be entered among the characters in the operators.)

The compound assignment usually results in a faster code, and therefore the source program can be interpreted easier.

I.3.5. Increment and decrement operations

The C++ language provides an efficient possibility to increase the value of numerical variables with one ++ (increment), and to decrease those with one -- (decrement). The operators can be used only with left value operands, however both prefix and postfix forms can be applied:

int a;
 
// prefix forms:
++a;            --a;
 
// postfix forms:
a++;            a--;

In case operators are used in the way presented above, there is no difference between the prefix and postfix form, since the value of the variable is incremented/decremented in both cases. In case the operator is applied in a more complex expression, using the prefix form increment or decrement takes place before processing the expression and the operand takes part in the evaluation of the expression with its new value:

int n, m = 5;
m = ++n;    // m ⇒ 6, n ⇒ 6

In case of postfix form increment or decrement follows the evaluation of the expression; therefore the operand has its original value in processing the expression:

double x, y = 5.0;
x = y++;    // x ⇒ 5.0, y ⇒ 6.0

The operation of increment and decrement operators can be understood more easily if the more complex expressions are decomposed to part expressions. Expression

int a = 2, b = 3, c;
c = ++a + b--;        // a will be 3, b 2 and c 6 

provides the same result as the expressions (containing one or more statements) below (comma operation will be mentioned later):

a++, c=a+b, b--;        a++;  c=a+b;  b--;

Instead of the conventional forms of increasing and decreasing by one

a = a + 1;        a += 1;
a = a - 1;        a -= 1;

it is always recommended to use the adequate increment or decrement operator

++a;     or         a++;
--a;     or        a--;

that provides faster code generation and becomes easier to overview.

It is to be noted that a variable should not be the operand of an increment or decrement operation more times within one expression. The value of such expression depends on the compiler completely.

a += a++ * ++a; // ↯

I.3.6. Phrasing of conditions

Some satetements of the C++ language work depending on a condition. The conditions in the statemnets can be any expression the zero or not zero value of which provide the logical false or true results. Comparative (relation) and logical operations can be used when conditions are created.

I.3.6.1. Relational and equalityoperations

Two operand, relational operators are available for carrying out comparisons, according to the table below:

Mathematical form

C++ expression

Meaning

a < b

a < b

a is less than b

a b

a <= b

a is less than or equal to b

a > b

a > b

a is greater than b

a b

a >= b

a is greater than or equal to b

a = b

a == b

a is equal to b

a b

a != b

a is not equal to b

All C++ expressions above are int type. The value of expressions is true (1) if the examined relation is true and false (0) if not.

Let’s take the example of some true expressions that contain different type operands.

int i = 3, k = 2, n = -3;
i > k        n <= 0        i+k > n        i != k
 
char first = 'A', last = 'Z';
first <= last        first == 65        'N' > first
 
double x = 1.2, y = -1.23E-7;
-1.0 < y        3 * x >= (2 + y)        fabs(x-y)>1E-5

It is to be noted that due to the computational and representation inaccuracy the identity of two floating point variables cannot be checked with operator ==. The absolute value of the difference of the two variables should be checked instead within the given error limit:

double x = log(sin(3.1415926/2));
double y = exp(x);
cout << setprecision(15)<<scientific<< x<< endl; 
// x ⇒ -3.330669073875470e-016
cout << setprecision(15)<<scientific<< y<< endl; 
// y ⇒ .999999999999997e-001
cout << (x == 0) << endl;         // false
cout << (y == 1) << endl;        // false
cout << (fabs(x)<1e-6) << endl;    // true
cout << (fabs(y-1.0)<1e-6)<< endl;    // true

Frequent program error is to confuse the operations of assignmnet (=) and identity testing (==). Comparison of a variable with a constant can be made safer if the left side operand is a constant, since the compiler expects a left value during assignment in this case:

        2004 == dt    instead of    dt == 2004

I.3.6.2. Logical operations

In order to be able to word more complex conditions logical operators are also needed besides relational operators. In language C++ the operation of logical AND (conjunction, &&), logical OR (disjunction, //) and negation (!) can be used when phrasing conditions.

The operation of logical operators can be described with a so called truth table:

       

The condition below is true if the value of variable x is between -1 and +1. Parentheses only confirm precedence.

        -1 < x && x < 1

        (-1 < x) && (x < 1)

There are cases when it is simpler to phrase the opposite condition instead of the condition itself, and apply a logical negation (NO) operator (!) on it. The condition of the previous example is identical with the condition below:

        !(-1 >= x || x >= 1)

During logical negation all relations are changed to their opposite relation, while operator AND to operator OR (and vice versa).

In C++ programs numerical variable ok is frequently used in expressions

        !ok                instead of         ok == 0

        ok                 instead of         ok != 0

Right side expressions are recommended to be used mainly with bool type variable ok.

I.3.6.3. Short circuit evaluation

The table of operations reveals that the evaluation of logical expressions is carried out from left to right. In case of certain operations, it is not necessary to process the whole expression in order to make the value of expression unambiguous.

Let’s take the example of operation logical AND (&&) during the usage of which the false (0) value of the left side operand makes the processing of the right side operand unnecessary. This evaluation method is called short circuit evaluation.

If there is a side effect expression on the right side of the logical operator during the evaluation, the

        x || y++

result is not always what we expect. In the example above if the value of x is not zero y is not incremented. Short circuit evaluation takes place even if the operands of the logical operations are put in parentheses:

        (x) || (y++)

I.3.6.4. Conditional operator

Conditional operator (?:) has three operands:

        condition ? true_expression : false_expression

If the condition is true, the value of true_expression provides the value of the conditional expression, otherwise the false_expression after the colon (:). This way only one expression is evaluated out of the two expressions on the two sides of the colon. The type of the conditional expression is the same as that of the part with higher accuracy. The type of expression

        (n > 0) ? 3.141534 : 54321L;

is always double independent of the value of n.

Let’s take a typical example for the application of the conditional operator. With the help of the expression below the values between 0 and 15 of variable n are transformed into hexadecimal numbers:

        ch = n >= 0 && n <= 9 ? '0' + n : 'A' + n - 10;

It is to be noted that the precedence of conditional operation is relatively low, slightly precedes that of assignment, therefore parentheses should be used in more complex expressions:

        c = 1 > 2 ? 4 : 7 * 2 < 3 ? 4 : 7 ; // 7

        c = (1 > 2 ? 4 : (7 * 2)) < 3 ? 4 : 7 ; // 7

       

        c = (1 > 2 ? 4 : 7) * (2 < 3 ? 4 : 7) ; //28

I.3.7. Bit operations

Computers had quite small memory in the past therefore solutions that made it possible to store and process more data within one byte, the smallest unit that can be addressed, were quite worthy. Using bit operations even 8 logical values can be stored within one byte. Nowadays this aspect is only considered rarely, easily understandable programs are in focus instead.

However, there is a field where bit operations are still used, and that is programming different hardware elements, microcontrollers. The C++ language contains six operators with the help of which different bitwise operations can be carried out on signed and unsigned integer data.

I.3.7.1.  Bitwise logical operations

The first group of operations, the bitwise logical operations make it possible to test, delete or set bits:

Operator

Operation

~

Unary complement, bitwise negation

&

bitwise AND

|

bitwise OR

^

bitwise exclusive OR

The description of bitwise logical operations can be found in the table below, where 0 and 1 numerals denote deleted and set bit status, respectively.

a

b

a & b

a | b

a ^ b

~a

0

0

0

0

0

1

0

1

0

1

1

1

1

0

0

1

1

0

1

1

1

1

0

0

The low level control of the computer hardware elements requires setting, deletion and switching of certain bits. All these operations are called “masking” since an adequate bitmask should be prepared for every single operation, and then should also be linked logically with the value desired to be changed, and this way the desired bit operation takes place.

Before all conventional bit operations are described one after the other, bit numbering in integer data elements has to be discussed. Bit numbering in a byte starts from the smallest order bit from 0 and increases from right to left. In case of integers composed of more bytes the byte sequence applied by the processor of the computer should also be discussed.

In case “big-endian” byte sequence supported by Motorola 68000, SPARC, PowerPC etc. processors the most significant byte (MSB) is stored in the lowest memory address, while an order smaller byte in the next address and so on.

However, the members of the most widespread Intel x86 based processor family use “little-endian” byte sequence according to which the least significant byte (LSB) is stored in the lowest memory address.

In order to avoid long bit series unsigned short int type data elements are used in our examples. Let’s take a look at the structure and storing of these data according to both byte sequences. The stored data is 2012 that is 0x07DC in hexadecimal numbering.

big-endian byte sequence:

       

little-endian byte sequence:

       

(Memory addresses increase from left to right in the example.) The figure shows clearly that when the hexadecimal constant values are entered the first big-endian form is used that corresponds to the mathematical interpretation of the hexadecimal number system. This is not an issue since storing in the memory is the task of the compiler. However, if the integer variables are processed bytewise, byte sequence should be known. Hereinafter our example programs are prepared in little-endian form but they can be adapted to the first storage method, as well based on the above mentioned facts.

In the example below bits 4 and 13 of the unsigned shortint type number 2525 are handled:        unsigned short int x = 2525; // 0x09dd

Operation

Mask

C++ instruction

Result

Bit setting

0010 0000 0001 0000

x = x | 0x2010;

0x29dd

Bit deletion

1101 1111 1110 1111

x = x & 0xdfef;

0x09cd

Bit negation

(switching)

0010 0000 0001 0000

x = x ^ 0x2010;

x = x ^ 0x2010;

0x29cd (10701)

0x09dd (2525)

Negation of all bits

1111 1111 1111 1111

x = x ^ 0xFFFF;

0xf622

Negation of all bits

 

x = ~x;

0xf622

Attention should be drawn to the strange behavior of exclusive or operator (^). If the exclusive or operation is carried out twice using the same mask the original value is returned, in this case 2525. This operation can be used for exchanging the values of two integer variables without the use of an auxiliary variable:

int m = 2, n = 7;
m = m ^ n;
n = m ^ n;
m = m ^ n;

A program error difficult to find is the result if the logical operators (!, &&, ||) used in conditions are interchanged with the bitwise operators (~, &, |).

I.3.7.2. Bit shift operations

Bit shift operators belong to another group of bit operations. Shift can be carried out either to the left (<<) or to the right (>>). During shifting the bits of the left side operand move to the left (right) as many times as the value of the right side operand shows.

In case of shifting to the left bit 0 is placed into the free bit positions, while the exiting bits are lost. However, shift to the right takes into consideration whether the number is signed or not. In case of unsigned types bit 0 enters from the left, while in case of signed numbers bit 1 comes in. This means that bit shift to the right keeps the sign.

        short int x;

Value giving

Binary value

Operation

Result

decimal (hexadecimal)

binary

x = 2525;

0000 1001 1101 1101

x = x << 2;

10100 (0x2774)

0010 0111 0111 0100

x = 2525;

0000 1001 1101 1101

x = x >> 3;

315 (0x013b)

0000 0001 0011 1011

x = -2525;

1111 0110 0010 0011

x = x >> 3;

-316 (0xfec4)

1111 1110 1100 0100

If the results are examined it can be seen that due to shift to the left with 2 bits the value of variable x increased four (22) times, while shift to the right with three steps resulted in a value decrease of x to its eighth (23). It can be stated generally that if the bits of an integer number is shifted to the left by n steps, the result is the multiplication of that number with 2n. Shift to the right by m bits means integer division by 2m. It is to be noted that this is the fastest way to multiply/divide an integer number with/by 2n.

In the example below the 16-bit integer number is divided into two bytes:

  short int num;
  unsigned char lo, hi;
 
  // Reading the number 
  cout<<"\nPlease enter an integer number [-32768,32767] : ";
  cin>>num;
 
  // Determination of the lower byte by masking 
  lo=num & 0x00FFU;
 
  // Determination of the upper byte by bit shift
  hi=num >> 8;
In the last example the byte sequence of the 4 byte int type variable is reversed:
int n =0x12345678U;
n =     (n >> 24) |        // first byte is moved to the end,
    ((n << 8) & 0x00FF0000U) |    // 2nd byte into the 3rd 
byte,
    ((n >> 8) & 0x0000FF00U) |    // 3rd byte into the 2nd byte,
    (n << 24);        // the last byte to the beginning. 
cout <<hex << n <<endl;        // 78563412

I.3.7.3. Bit operations in compound assigment

In language C++ a compound assignmnet operation also belongs to all the five two-operand bit operations, and this way the value of variables can be modified more easily.

Operator

Relation sign

Usage

Operation

Assignmnet by shift left

<<=

x <<= y

shift of bits of x to the left with y bits,

Assignmnet by shift right

>>=

x >>= y

shift of bits of x to the right with y bits,

Assignmnet by bitwise OR

|=

x |= y

new value of x: x | y,

Assignmnet by bitwise AND

&=

x &= y

new value of x: x & y,

Assignmnet by bitwise exclusive OR

^=

x ^= y

new value of x: x ^ y,

It is important to note that the type of the result of bit operations is an integer type, at least int or larger than int, depending on the left side operand type. In case of bit shift any number of steps can be entered but the compiler uses the remainder created with the bit size of the type for shifting. For example, the phenomenon listed below is experienced in case of 32 bit int type variables.

unsigned z;

z = 0xFFFFFFFF, z <<= 31; // z ⇒ 80000000
z = 0xFFFFFFFF, z <<= 32; // z ⇒ ffffffff
z = 0xFFFFFFFF, z <<= 33; // z ⇒ fffffffe

I.3.8. Comma operator

In one expression more, even independent expressions can be placed, using the lowest precedence comma operator. Expressions containing comma operator is evaluated from left to right, and the value and type of the expression is the same as that of the right side operand. Let’s take the example of expression

        x = (y = 4 , y + 3);

Evaluation starts with the comma operator in the parentheses, first variable y obtains a value (4), then the expression in parentheses (4+3=7). Finally variable x obtains 7 as its value.

Comma operator is frequently used when setting different initial values for variables in one single statement (expression):

        x = 2, y = 7, z = 1.2345 ;

Comma operator should be used also when the values of two variables should be changed within one statement (using a third variable):

        c = a, a = b, b = c;

It is to be noted that commas that separate variable names in declarations and arguments in function calls are not comma operators.

I.3.9. Type conversions

It happens frequently during expression evaluation that a two-operand operation has to be carried out with different type operands. However, in order to be able to carry out the operation, the compiler has to transform the two operands to the same type, i.e. type conversion takes place.

In C++ some type conversions are carried out automatically without the intervention of the programmer, based on the rules laid in the definition of the language. These conversions are called implicit or automatic conversions.

In a C++ program the programmer may also request type conversion using type converter operators (cast) – explicit type conversion.

I.3.9.1. Implicit type conversions

It can be stated in general that during automatic conversions the operand with “narrower value range” is converted to the operand type with “wider value range” without data loss. In the example below during the evaluation of expression m+dint type m operand is converted to double type and that is the type of the expression as well:

int m=4, n;
double d=3.75;
n = m + d;

Implicit conversions do not always take place without data loss. During assigment and function call conversion among any types may happen. For instance in the example above when the sum is filled into variable n data loss occurs since the fraction part of the sum is lost, and 7 will be the value of the variable. (It is to be noted that no rounding was done during value giving.)

Automatically carried out conversions during evaluation of x op y form expressions are summarized briefly below.

  1. char, wchar_t, short, bool, enum type data are automatically converted to int type. If the int type is not capable of storing the value, the aim type of conversion will be unsigned int. This type conversion rule is called “integer conversion” (integral promotion). The above mentioned conversions are value keeping ones, since they provide correct results regarding value and sign.

  2. If there are different types in the expression after the first step, conversion according to type hierarchy starts. During type conversion the “smaller” type operand is converted to the “larger” type. The rules used during conversion are called “common arithmetical conversions”.

    int < unsigned < long < unsignedlong < longlong < unsignedlonglong < float < double < long double

I.3.9.2. Explicit type conversions

The aim of type conversions commanded explicitly by the user is to carry out conversions that do not take place implicitly. Now only conversions that can be used with the basic types are dealt with, while operations const_cast, dynamic_cast and reinterpret_cast are detailed in Section I.6, “Pointers, references and dynamic memory management”.

The (static) type conversions below are all carried out during the compilation of the C++ program. A possible grouping of type conversions:

type conversion (C/C++)

(type name) expression

(long)p

function-like form

type name (expression)

int(a)

checked type conversions

static_cast< type name >(expression)

static_cast<double>(x)

In case of writing any expression implicit and the maybe necessary explicit conversions have to be considered always. The program part below aims to determine the average of two long integer variables and store it as a double type variable:

long a =12, b=7;
double d = (a+b)/2;
cout << d << endl;    // 9

The result is false since due to integer conversions the right side of value giving has a long type result and this is placed into variable d. The result will only be right (9.5) if any operand of the division is converted to type double using any of the methods shown below:

d = (a+b)/2.0;
d = (double)(a+b)/2;
d = double(a+b)/2;
d = static_cast<double>(a+b)/2;

I.4. Control program structures

On the basis of what we have learnt so far, we can only make program codes in the main () function of which only contains expressions ending with semicolon (data input, value assignment, printing, etc.). In order to realize more complex algorithms, this program structure is insufficient. We have to get to know the control structures of the C++ language that make possible the execution of certain program code lines repeatedly or depending on certain conditions. (For a summary of all C++ statements, see Appendix Section A.5, “Statements in C++”.)

I.4.1. Empty statements and statement blocks

Control statements of C++ "control" the execution of other statements. If we would not like to control any activity, we should provide an empty statement. However, if several statements need control, then the so-called compound statements, i.e. statement blocks should be used.

Empty statements consist only of a semicolon (;). They should be used if no activity has to be performed logically, but there has to be a statement at the given point of the code according to syntax rules.

Curly brace brackets ( { and } ) enclose declarations and statements that make up a coherent unit together within a compound statement or block . Compound statements can be used at any place where only one statement is allowed in C++. Compound statements, the general representation of which is:

{
   local definitions, declarations
   statements
}

are used in the following three cases:

  • when more statements forming together a logical unit should be treated as one (in these cases, blocks only contain statements in general),

  • in the body of functions,

  • to localize the validity of definitions and declarations.

In the statement blocks statements and definitions/declarations can be typed in any order we want. (It should be noted that blocks do not end with a semicolon.)

In the following example, the quadratic equation with one unknown is solved if and only if the discriminant (the number appearing under the square root) of the equation is not negative. In order that the code would function correctly, the following if control structure is used:

#include <iostream>
#include <cmath>
using namespace std;
 
int main() 
{
    double a, b, c;
    cout << "a = "; cin >> a;
    cout << "b = "; cin >> b;
    cout << "c = "; cin >> c;
    
    if (b*b-4*a*c>=0) {
        double x1, x2;
        x1 = (-b + sqrt(b*b-4*a*c))/(2*a);
        x2 = (-b - sqrt(b*b-4*a*c))/(2*a);
        cout << x1 << endl;
        cout << x2 << endl;
    }
}

I.4.2. Selective structures

Selective structures (if, switch) decide on the following steps to be executed by a program on the basis of certain conditions. These structures allow for one-way, two-way or multiple-way branches. Selections can be nested in one another, too. Conditions are expressed by comparison (relational) and logical operations.

I.4.2.1. if statements

In the case of an if statement, the execution of an activity (statement) depends on the value of an expression (condition). if statements have three forms

One-way branch

In the following form of if, the statement is only executed if the value of condition is not zero (i.e. true). (It should be noted that conditions should always be within brackets.)

        if (condition)

                statement

The functioning of the different control structures can be demonstrated by the following block diagram. The simple if statement is represented in Figure I.7, “Functioning of a simple if statement”.

Functioning of a simple if statement
Figure I.7. Functioning of a simple if statement


In the following example, the square root of the number read from the keyboard is only calculated if it is not negative:

#include <iostream>
#include <cmath>
using namespace std;
 
int main() {
  double x = 0;
  cout << "x = "; cin >> x;
  if (x >= 0) {
    cout<<sqrt(x)<<endl;
  }
}

Two-way branches

In the complete version of an if statement, an activity can be provided (statement2) when the value of the condition is zero (i.e. false) (Figure I.8, “Logical representation of if-else structures”). (If statement1 and statement2 are not compound statements, they should end with a semicolon.)

        if (condition)

                statement1

        else

                statement2

Logical representation of if-else structures
Figure I.8. Logical representation of if-else structures


In the following example, the if statement decides whether the number read from the keyboard is even or odd:

#include <iostream>
using namespace std;
 
int main()
{
  int n;
  cout<<"Type an integer number: "; cin>>n;
  if (n % 2 == 0)
     cout<<"The number is even!"<<endl;
  else
     cout<<"The number is odd!"<<endl;
}

if statements can be nested in one another. However, in such cases, we have to be careful when using else branches. Compilers connect else branches to the closest preceding if statement.

The following example decides whether a given integer number is a positive and even number or whether it is a non-positive number. The correct solution can be realized in two different ways. One possibility is to attach an else branch containing an empty statement (;) to the internal if statement:

if (n > 0)
    if (n % 2 == 1)
        cout<<"Positive odd number."<< endl;
    else ;
else
    cout<<"Not a positive number."<<endl;

Another possibility is to enclose the internal if statement in braces, that is to place it in a statement block:

if (n > 0) {
    if (n % 2 == 1)
        cout<<"Positive odd number."<< endl;
} else
    cout<<"Not a positive number."<<endl;

This problem does not arise if statement blocks are used in the case of both if, which is by the way required by safe programming:

if (n > 0) {
    if (n % 2 == 1) {
        cout<<"Positive odd number."<< endl;
    }
} 
else {
    cout<<"Not a positive number."<<endl;
}

In this case, both branches can be safely expanded by further statements.

Logical representation of multi-way branches
Figure I.9. Logical representation of multi-way branches


Multi-way branches

A frequent case of nested if statements is to use further if statements in else branches (Figure I.9, “Logical representation of multi-way branches”).

This structure realizes multi-way branches. If any condition is true, the corresponding statement is executed. If no condition is fulfilled, only the last else statement is executed.

        if (condition1)

                statement1

        else if (condition2)

                statement2

        else if (condition3)

                statement3

        else

        statement4

The following example decides whether number n is negative, 0 or positive:

if (n > 0)
    cout<<"Positive number"<<endl;
else if (n==0)
    cout<<"0"<<endl;
else
    cout<<"Negative number"<<endl;

A special case of else-if structures is when we check whether two elements are equal or not (==). The following example demonstrates a calculator counting the result of a simple addition and subtraction:

#include <iostream>
using namespace std;
 
int main()
{
    char op;
    double a, b, c;
    cout<<"expression : ";
    cin >>a>>op>>b;      // reading from keyboard: 4+10 <Enter>
 
    if (op == '+')
        c = a + b;
    else if (op == '-')
        c = a - b;
    else {
        cout << "Not a valid operator: " << op <<endl;
        return -1;
    }
    cout <<a<<op<<b<<'='<<c<<endl;
    return 0;
}

The following example counts the grades to be given to students on the basis of the achieved points in C++ programming:

#include <iostream>
using namespace std;
 
int main()
{
    int points
    char grade = 0;
    cout << "Points: ";  cin >> points;
 
    if (points >= 0 && points <= 100)
    {
        if (points < 40)
            grade = 'A';
        else if (points >= 40 && points < 55)
            grade = 'B';
        else if (points >= 55 && points < 70)
            grade = 'C';
        else if (points >= 70 && points < 85)
            grade = 'D';
        else if (points >= 86)
            grade = 'F';
        cout << "Grade: " << grade << endl;
    }
    else
        cout <<"Not a valid number!" << endl;
}

I.4.2.2. switch statements

In fact, switch statements are statement blocks that we can enter into depending on the value of a given integer expression. The parts of the code to be executed are determined by the so-called case labels (caseconstant expressions).

        switch (expression)

        {

        caseconstant_expression1 :

                        statements1

        caseconstant_expression2 :

                        statements2

        caseconstant_expression3 :

                        statements3

        default :

                        statements4

        }

First, switch statements evaluate the expression, then transfer control to the case label in which the value of the constant_expression equals to the value of the evaluated expression. After that, all statements are executed from the entering point until the end of the block. If none of the case constants are equal to the value of the expression, control passes to the statement with label default. If no default label is provided, control passes to the statement following the brace closing the block of the switch statement.

This little bit weird functioning is demonstrated by an exceptional example code. The following switch statement is able to count the factorial of all integer numbers between 0 and 5. (In this case, the adjective exceptional means 'not to be followed'.)

int n = 4, f(1);
switch (n) {
    case 5: f *= 5;
    case 4: f *= 4;
    case 3: f *= 3;
    case 2: f *= 2;
    case 1: f *= 1;
    case 0: f *= 1;
}

In most cases switch statements are used, similarly to else-if structures, to realize multi-way branches. For that purpose, all statement blocks that correspond to a case have to end with a jump statement (break, goto or return). break statements transfer control to the statement immediately following the switch block, goto to the statement with the specified label within the function block and finally return exits the function.

Since our aim is to create well functioning and easily comprehensible source codes, the number of jump statements should be reduced to a minimum level. However, the usage of break is completely allowed in switch statements. In general, statements even at the end of the default label is followed by a break since the default case can be placed anywhere within switch statements.

On the basis of the above, the calculator program of the previous subsection can be rewritten by switch in the following way:

#include <iostream>
using namespace std;
 
int main()
{
    char op;
    double a, b, c;
    cout<<"expression :";
    cin >>a>>op>>b;     
 
    switch (op) {
        case '+':
            c = a + b;
            break;
        case '-':
            c = a - b;
            break;
        default:
            cout << "Not a valid operator: " << op <<endl;
            return -1;
    }
    cout <<a<<op<<b<<'='<<c<<endl;
    return 0;
}

The next example demonstrates how to associate the same code part to more cases. In the source code, the case labels corresponding to cases 'y' and 'Y', as well as 'n' and 'N' were placed one after the other in the switch statement processing the response character.

#include <iostream>
using namespace std;
 
int main() 
{
    cout<<"The response [Y/N]?";
    char response=cin.get();
    
    switch (response) {
        case 'y':  case 'Y':
            cout<<"The answer is YES."<<endl;
            break;
        case 'n':
        case 'N':
            cout<<"The answer is NO."<<endl;
            break;
        default:
            cout<<"Wrong response!"<<endl;
            break;
    }
}

I.4.3. Iteration structures (loops)

In programming, program structures ensuring automatic repetitions of statements are called iterations or loops. In C++, loop statements repeat a given statement in the function of the repetition condition until the condition is true.

        while (condition) statement

        for (initialisation opt ; condition opt ; increment_expression opt ) statement

        dostatementwhile (condition)

In the case of for statement, opt means that the corresponding expressions are optional.

Loops can be classified on the basis of the place where control conditions are processed. Loops in which the control condition is evaluated before statements are executed are called pre-test loops. Any statement in the loop is executed if and only if the condition is true. Pre-test loops of C++ are while and for.

On the contrary, the statements of do loops are always executed at least once since control condition is evaluated after the execution of the statement – post-test loops.

In all three cases, correctly organized loops terminate if the control condition becomes false (0). However, there are times we create intentionally loops the control condition of which never becomes false. These loops are called infinite loops :

for (;;) statement;
 
while (true) statement;
 
do statement while (true); 

Loops (even infinite loops) can be exited before the control condition becomes false. For that purpose, C++ offers statements like break, return or goto, which points to a place outside the body of the loop. Certain statements of the body of the loop can be bypass by using continue. The continue makes the program continue with executing the next iteration of the loop.

Logical representation of while loops
Figure I.10. Logical representation of while loops


I.4.3.1. while loops

while loops repeat statements belonging to them (the body of the loop), while the value of the examined condition is true (not 0). Evaluation of the condition always precedes the execution of the statement. The process of the functioning of while loops can be traced in Figure I.10, “Logical representation of while loops”.

        while (condition)

                statement

The next example code determines the sum of the first n natural numbers:

#include <iostream>
using namespace std;
 
int main()
{
    int n = 2012;
    cout<<"The sum of the first "<<n<<" natural number ";
    unsigned long sum = 0;
    while (n>0) {
        sum += n; 
        n--;
    } 
    cout<<"is: "<<sum<<endl;
}

Of course, the while loop of the previous example can be simplified but this step decreases its readability:

while (n>0) {

sum += n;

n--;

}

while (n>0)

sum += n--;

while (sum += n--, n);

C++ makes it possible to place variable declarations anywhere in a program code. The only condition is that all variables have to be declared (defined) before they are used. In certain cases, variables can be defined in loop statement header in case they are immediately initialized, e.g. by random numbers.

The while loop of the following example code does not terminate until it reaches the first number, divisible by 10. In the code, statement

        srand((unsigned)time(NULL));

initializes the random number generator with the actual time, therefore every execution result in a new number sequence. Random numbers are provided by function rand () in the value range between 0 and RAND_MAX (32767).

#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
 
int main()
{
    srand((unsigned)time(NULL));
    while (int n = rand()%10)
        cout<< n<< endl;
}

It should be noted that variable n defined in that way can be accessed within the while loop, thus it is local only with respect to the while loop.

I.4.3.2. for loops

In general, for statements are used if the statements within its body should be executed in a given number of times (Figure I.11, “Logical representation of for loops”). In the general form of for statements, the role of each expression is also mentioned:

        for (initialization; condition; increment)

        statement

In reality, for statements are the specialized versions of while statements, so the above for loop can perfectly be transformed into a while loop:

        initialization;

        while (condition) {

                statement;

                increment;

        }

Logical representation of for loops
Figure I.11. Logical representation of for loops


The following program represents a for loop and does the same as the above one: determines the sum of natural numbers. It is obvious at first sight that this solution of the problem is much more readable and simpler:

#include <iostream>
using namespace std;
    
int main()
{
    unsigned long sum;
    int i, n = 2012;
    cout<<"The sum of the first "<<n<<" natural number ";
    for (i=1, sum=0 ; i<=n ; i++)
        sum += i;
    cout<<"is: "<<sum<<endl;
}

There is only one expression-statement in the body of the loop in the example, so the for loop can be condensed in the following way:

        for (i=1, sum=0 ; i<=n ; sum += i, i++) ;

or

        for (i=1, sum=0 ; i<=n ; sum += i++) ;

Loops can be nested in one another, since their body can contain other loop statements. The following example uses nested loops to print a pyramid of a given size. In all three loops, loop variable is made local:

#include <iostream>
using namespace std;
 
int main ()
{
    const int maxn = 12; 
    for (int i=0; i<maxn; i++) { 
        for (int j=0; j<maxn-i; j++) {
            cout <<" "; 
        } 
        for (int j=0; j<i; j++) { 
            cout <<"* ";
        }       
        cout << endl;
    }
} 
            *
           * *
          * * *
         * * * *
        * * * * *
       * * * * * *
      * * * * * * *
     * * * * * * * *
    * * * * * * * * *
   * * * * * * * * * *
  * * * * * * * * * * *

I.4.3.3. do-while loops

In do-while loops, evaluation of the condition takes place after the first execution of the body of the loop (Figure I.12, “Logical representation of do-while loops”), so the loop body is always executed at least once.

Logical representation of do-while loops
Figure I.12. Logical representation of do-while loops


        do

                statement

        while (condition);

As a first step, let's create the version of the program computing the sum of natural numbers with a do-while loop.

#include <iostream>
using namespace std;
 
int main()
{
    int n = 2012;
    cout<<"The sum of the first "<<n<<" natural number ";
    unsigned long sum = 0;
    do {
        sum += n; 
        n--;
    } while (n>0);  
    cout<<"is: "<<sum<<endl;
}

With the help of the following loop, let's read an integer number between 2 and 100 from the input and verify it:

int m =0;
do {
    cout<<"Enter an integer number betwwen 2 and 100: ";
    cin >> m;
} while (m < 2 || m > 100);
The last example calculates the power of a number with an integer exponent:
#include <iostream>
#include <cstdlib>
using namespace std;
 
int main()
{
    double base;
    int exponent;
    cout << "base:  "; cin >> base;
    cout << "exponent:  "; cin >> exponent;
    double power = 1;
    if (exponent != 0)
    {
        int i = 1; 
        do
        {
            power *= base;
            i++;
        } while (i <= abs(exponent));
        power = exponent < 0 ? 1.0 / power : power;
    }
    cout <<"The power is :  " << power << endl;
}

A frequent programming error is to terminate loop headers with a semicolon. Let's see some attempts to print odd integer numbers between 1 and 10.

int i = 1;

while(i<10);

{

cout <<i<< endl;

i+=2;

}

int i;

for (i=1; i<10; i+=2);

{

cout<<i<<endl;

}

int i = 1;

do

{

cout <<i<< endl;

i+=2;

} while(i<10);

In the case of while, the empty statement is repeated endlessly in the loop. The for loop repeats the empty statement and then prints the value of the loop variable after the loop is exited, i.e. 11. In the case of the do-while loop, the compiler gives an error message if do is followed by a semicolon, so the example code is a perfect solution.

I.4.3.4. break statements in loops

There are cases when the regular functioning of a loop has to be directly intervened into. For example, if a loop has to be exited if a given condition is fulfilled. A simple solution for that is the break statement, which interrupts the execution of the nearest while, for and do-while statements and control passes to the first statement following the interrupted loop.

The following while loop is interrupted if the lowest common multiple of two integer numbers is found:

#include <iostream>
#include <cstdlib>
using namespace std;
 
int main()
{
    int a, b, lcm;
    cout << "a = "; cin >> a;
    cout << "b = "; cin >> b;
    lcm = min(a,b);
    while (lcm<=a*b) {
        if (lcm % a == 0 && lcm % b == 0)
            break;
        lcm++;
    }
    cout << "The lowest common multiple is: " << lcm << endl;
}

The usage of break can be avoided if the condition of the if statement corresponding to it can be integrated into the condition of the loop, that will become more complicated by that step:

    while (lcm<=a*b && !(lcm % a == 0 && lcm % b == 0)) {
        lcm++;
    }

If break statement is used in nested loops in an inner loop, it is only that inner loop that is exited. The following example prints prime numbers between 2 and maxn. The reason for exiting the loop is provided by a logical flag, by the variable (prime), to the outer loop:

#include <iostream>
using namespace std;
 
int main () {
    const int maxn =2012;
    int divisor;
    bool prime;
    for(int num=2; num<=maxn; num++) {
        prime = true;
        for(divisor = 2; divisor <= (num/divisor); divisor++) {
            if (num % divisor == 0) {
                prime = false;
                break; // has a divisor, not a prime
            }
        }
        if (prime) 
cout << num << " is a prime number" << endl;
    }
}

If the task is to find the first Pythagorean triple in a given interval, two for loops have to be exited if a match is found. Then the simplest solution is to use the flag (found):

#include <iostream>
#include<cmath>
using namespace std;
 
int main () {
    int left, right;
    cout <<"left  = "; cin >> left;
    cout <<"right = "; cin >> right;
    bool found = false;
    for(int a = left, c, c2; a<=right && !found; a++) {
        for(int b = left; b<=right && !found; b++) {
            c2 = a*a + b*b;
            c = static_cast<int>(sqrt(float(c2)));
            if (c*c == c2) {
                found = true;
                cout << a << ", " << b  << ", " << c << endl;
            } // if
        } // for
    } // for
} // main()

I.4.3.5. continue statements

continue statements start the next iteration of while, for and do-while loops. In the body of these loops, the statements placed after continue are not executed.

 In the case of while and do-while loops, the next iteration begins with evaluating again the condition. However, in for loops, the processing of the condition is preceded by the increment.

In the following example, with the help of continue, it was realized that only the numbers divisible by 7 or 12 are printed out in the loop incrementing from 1 to maxn by one:

#include <iostream>
using namespace std;
 
int main(){
    const int maxn = 123;
    for (int i = 1; i <= maxn; i++) {
        if ((i % 7 != 0) && (i % 12 != 0)) 
            continue;
        cout<<i<<endl;
    }
}

It is a bad practice in programming to use often break and continue statements. It has to be always rethought if given program structures can be solved without jump statements. In the preceding example, this can easily be done by inverting the condition of the if statement:

    for (int i = 1; i <= maxn; i++) {
        if ((i % 7 == 0) || (i % 12 == 0)) 
            cout<<i<<endl;
    }

I.4.4. goto statements

To close the present chapter, let's see one jump statement of C++ not presented yet. We should already be careful when using break and continue statements, but this is even truer for goto statements.

goto statements that can be used to jump within functions have a bad reputation because they make program codes less understandable and more unstructured. Not to mention the tragic consequences of jumping into a loop. So, it should be stated that goto statements are to be avoided if a structured and a clearly understandable code is to be written. In most software developer companies, it is even forbidden.

In order to use goto statements, the statement to which we want to jump has to be labeled. A label is an identifier which is separated by a colon from the following statement:

        label:  statement

The goto statement with which control can be transferred to the line marked with the label above:

        goto  label;

In some and only justified cases, e.g. jumping out from a very complicated program structure, goto provides a simple alternative.

I.5. Exception handling

An anomalous state or event that hinders the normal flow of the execution of a program is called an exception.

In C language, errors were handled locally at every eventual anomalous point of a program. In general, this meant printing out an error message ( cerr ) and then interrupting the execution of that program ( exit ()). For example, when all coefficients of a quadratic equation have been read from the keyboard and the quadratic coefficient is 0, then the quadratic formula cannot be applied to solve the equation:

#include <iostream>
#include <cstdlib>
using namespace std;
 
int main()
{
    double a, b, c;
    cout<<" coefficients (separated from each" 
           "other by a space) :";
    cin >> a >> b >> c;
    if (0 == a) {
        cerr << "The equation is not quadratic!" << endl;
        exit(-1);
    }
    cout <<a<<"x^2+"<<b<<"x+"<<c<<"=0"<< endl;
}

C++ makes it possible to assign types to exceptions (errors) and to carry out all exception handling tasks at the same place. Exception handling takes place on the basis of the termination model, which means that the execution of the code part (function) that provoked an exception is interrupted when that exception arises.

Standard C++ language contains relatively few integrated exceptions; however, different class libraries prefer exceptions to signal erroneous states. Exceptions can be triggered manually by "throwing" a value of a given type. Exceptions are transferred (thrown) to the nearest exception handlers that either catch or transfer them.

This above mentioned process functions only if the code part susceptible to trigger an error is enclosed in a statement block that attempts (try) to execute that.

So, the three elements that are needed for the type-oriented exception handling of C are the following:

  • selecting the code part under exception inspection (try-block),

  • transferring exceptions (throw),

  • catching and handling exceptions (catch).

I.5.1. The try – catch program structure

In general, a try-catch program structure contains only one try block and any number of catch blocks:

        try

        {

                // statements under exception inspection

                statements

        }

        catch (exception1 declaration)  {

                // the handler of exception1

                statements1

        }

        catch (exception2 declaration)  {

                // the handler of exception2

                statements2

        }

        catch (...)  {

                // the handler of any other exception

                statements3

        }

        // statements that are executed after successful handling

        statements4

In case any statement of a try block causes an exception, the execution of statements is interrupted and control is passed to the catch block (that follows that try block) of the given type (if there is one). If there is no handler with the given type, then the exception is transferred to a handler belonging to an external try block. Exceptions without any type match are finally transferred to the execution environment (as unhandled exceptions). This situation is signalled by the following message: "This application has requested the Runtime to terminate it in an unusual way. Please contact the application's support team for more information.

The exception declarations that follow the keyword catch can be provided in many ways. These declarations may contain a type , a variable declaration (type identifier) or three dots . The thrown exception can only be accessed by using the second form. The three dots means that that block catches any exception. Since exceptions are identified in the order catch blocks are provided, the three dotted version should be the last in the catch-block list.

As an example, let's transform the error inspection of the introduction part into an exception handling.

#include <iostream>
#include <cstdlib>
using namespace std;
 
int main()
{
    try {
        double a, b, c;
        cout << " coefficients (separated from each" 
                "other by a space) :";
        cin >> a >> b >> c;
        if (0 == a) throw false;
        cout <<a<<"x^2+"<<b<<"x+"<<c<<"=0"<< endl;
    }
    catch (bool) {
        cerr << "The equation is not quadratic!" << endl;
        exit(-1);
    }
    catch (...) {
        cerr << "An error occurred..." << endl;
        exit(-1);
    }
}

I.5.2. Provoking exceptions - the throw statement

Exceptions have to be handled locally within the given part of a code with the keywords try, throw and catch. Exception handling only takes place in case the code in the catch block (and the code called from there) is executed. From the selected code portion, a throw statement

        throwexpression;

passes control to the handler that corresponds to the type of the expression, which should be provided after the keyword catch.

When an exception is transferred by a throw statement, then the value of the expression given in the statement is copied into the parameter figuring in the header of the catch block, so this value can be processed in that specific handler. For that purpose, the type also has to have an identifier in that catch statement.

If a catch block contains a throw statement, the caught exception can be transferred as another exception, either transformed to another or not. If the exception is not intended to be transformed, the empty throw statement has to be used: throw;

Exception handling can be made more complete by defining exception classes. The header file named exception contains the description of the exception class, which is the base class of different logical and run-time exceptions. The function named what () of these classes returns the string identifying that exception. The following table summarises the exceptions thrown by the classes defined in the C++ standard library. All exceptions have the exception class as their base class.

Exception classes

Header file

exception

  

<exception>

 

bad_alloc

 

<new>

 

bad_cast

 

<typeinfo>

 

bad_typeid

 

<typeinfo>

 

logic_failure

 

<stdexcept>

  

domain_error

<stdexcept>

  

invalid_argument

<stdexcept>

  

length_error

<stdexcept>

  

out_of_range

<stdexcept>

 

runtime_error

 

<stdexcept>

  

range_error

<stdexcept>

  

overflow_error

<stdexcept>

  

underflow_error

<stdexcept>

 

ios_base::failure

 

<ios>

 

bad_exception

 

<exception>

Exception classes make it possible to pass "smart" objects as exceptions instead of simple values. The knowledge necessary for that process is only treated in Chapter III, Object-oriented programming in C++ of the present book.

A comfortable way of exception handling is when a string is passed as exception. In that way, the users of a computer program will always receive meaningful messages during their work. (String constants will be declared as of type const char *, which will be treated in the next chapter.)

#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
 
int main() {
    int number;        
    srand(unsigned(time(NULL)));
    try {
        while (true) {
            number = rand();
            if (number>1000)
                throw "The number is too big";
            else if (number<10)
                throw "The number is too small";
            cout << 10*number << endl;
        } // while
        
    } // try
    catch (const char * s) {
        cerr << s << endl;
    } // catch
} // main()

I.5.3. Filtering exceptions

Functions in C++ have an important role in exception handling. An exception arising in a function is generally processed within that function since related data are only available from there. However, there are some exceptions that indicate an unsuccessful working of the function back to the element that called the latter. Of course, these exceptions are transferred outside the function.

In the header of a function definition, the keyword throw can be used in a specific way, so the type of the exception to be thrown to the handler can also be defined. By default, all exceptions are transferred.

// all exceptions are transferred.
int funct1();
// only exceptions of type char and bool are transferred
int funct2() throw(char, bool); 
// only exceptions of type bool are transferred 
int funct3() throw(bool); 
// no exceptions are transferred
int funct4() throw();       

When a program throws an exception that does not have a handler in that program, the execution environment activates the function named terminate (), which makes the program abort ( abort ()). If a function body throws an exception that is not listed in the exceptions of the given function to be thrown, then the unexpected () system call terminates the program. Programmers can intervene in both terminating process by defining their own handlers that can be registered with the help of the functions set_terminate () or set_unexpected () declared in the header file named except.

I.5.4. Nested exceptions

A try-catch exception handling structure can be placed within another try-block, either directly or indirectly (i.e. in the function called from that try-block).

As an example, a string exception is thrown from an inner try-block. This is then processed in the inner structure and then transferred by using the empty throw; statement. The outer structure treats that exception finally.

#include <iostream>
using namespace std;
 
int main()
{  
    try {
        try {
            throw "exception";    
        }
        catch (bool) {
            cerr << "bool error" << endl;
        }
        catch(const char * s) {
            cout << "inner " << s << endl;
            throw;
        }
    }
    catch(const char * s) {
        cout << "outer " << s << endl;
    }
    catch(...) {
        cout << "unknown exception" << endl;
    }
}

As the final example of the present section, let's have a look at the following calculator program. Our own exceptions are stored in enumerations. The program can be exited by using the operator x.

#include <iostream>
using namespace std;
enum zerodiv {DividingWithZero};
enum wrongoperation {WrongOperation};
int main()
{
    double a, b, e=0;
    char op;
    while (true)  // exiting if op is x or X
    {
        cout << ':'; 
        cin >>a>>op>>b;
        try {
            switch (op) {
                case '+':    e = a + b;
                        break;
                case '-':    e = a - b;
                        break;
                case '*':    e = a * b;
                        break;
                case '/':    if (b==0)
                        throw DividingWithZero;
                        else
                            e = a / b;
                        break;
                case 'x': case 'X' : 
                        throw true;
                default: throw WrongOperation;
            }
            cout << e <<endl;
        } // try
        catch (zerodiv) {
            cout <<" Dividing with zero" <<endl;
        }
        catch (wrongoperation) {
            cout <<" Wrong operation" <<endl;
        }
        catch (bool) {
            cout<<"Off"<<endl;
            break;   // while
        }
        catch(...) {
            cout<<" Something is bad "<< endl;
        }
    } // while
}

I.6. Pointers, references and dynamic memory management

The most important resource of every program is computer memory since all directly accessible (on-line) data is stored there along with executed program code. In our previous examples, it is compiler that was entrusted with memory management.

Based on the storage class, compiler places global (extern) data grouped together on a memory place which is always accessible while the program is running. Local (auto) data are stored in a stack memory block when the corresponding functions are entered into and they are deleted when these functions are exited (Figure I.13, “C++ program memory usage”).

However, there is a space named heap, where programmers can place variables and delete those that are not needed anymore. These dynamically managed variables are different from those used so far since they have no name. They can be referenced by variables storing their address (pointers).  C++ has a couple of operators that make dynamic memory management possible: *, &, new, delete.

C++ program memory usage
Figure I.13. C++ program memory usage


Besides the above mentioned areas, there are many domains where pointers are used in C/C++ programs: function parameter passing, linked data structure management, etc. C++ attempts to counterbalance the exclusive usage of pointers with the help of a safer type, namely with the help of references.

I.6.1. Pointers

When defining a variable, compilers reserve the quantity of memory space that corresponds to the type of the given variable and associate to the latter the name used in its definition. In most cases, we assign values to variables and read their content by using their names. There are cases where this approach is insufficient and the address of a variable in the memory should be used directly (for example when calling the Standard Library function scanf ()).

       

With the help of pointers, the address of variables (data stored in memory) and functions can be stored and managed. A pointer does not only store address but also have information how to interpret how many bytes from that given address. And this is the type of the referenced data, which is provided in the definition of pointers (variables).

I.6.1.1. Single indirection pointers

First, let's start with the most frequently used and the simplest form of pointers, that is with single indirection pointers the general definition of which is:

        type *identifier;

The asterisk indicates that now a pointer is defined, and the type before the asterisk indicates the referenced data type. The default automatic initial value of pointers is determined by the usual rules: it is 0, if it is created outside functions and undefined when created within a function. It is a safe solution to initialize always pointers after they are created with NULL value, which can be found in most header files:

        type *identifier = NULL;

If more pointers are created within a single statement, all identifier names should be preceded by an asterisk:

        type *identifier1, *identifier2;

Many operations can be carried out for pointers; however, there are three operators that can exclusively be used with pointers:

*ptr

Accessing the object ptr points to.

ptr->member

The access of the given member of the structure ptr points to (Section I.8, “User-defined data types”).

&leftvalue

Getting the address of leftvalue.

The two levels presented in the introduction become more understandable if they are demonstrated by an example. Let's create an integer type variable.

        int x = 2;

When the variable is defined, an (int type) space is created in memory (e.g. with the address of 2004) into which the initial value is copied:

       

The definition of

        int *p;

creates a variable (e.g. at the address of 2008), the type of which is int*. C++ generally considers p as an int* type variable or p can also be considered as an int type pointer. This pointer can be used for storing address of  int type variables, that can be accessed by the "address of" & operation, e.g.

        p = &x;

After that operation, the x name and *p value reference the same memory space. (the expression *p designate the storage place „p points to”.)

       

As a consequence, after the processing of

        *p = x +10;

the value of x becomes 12.

An alternative pointer type can also be created by using the keyword typedef:

int x = 2;
typedef int *tpi;
tpi p = &x;

A single variable can be referenced by many pointers and its value can be modified by any of them:

int x = 2;
int * p, *pp;
p    = &x;
pp   = p;
*pp += 10; // the value of x is 12 again 

       

If a pointer is initialized with an address of a variable of a different type, a compiler error message is sent.

long y = 0;
char *p = &y;     // error! ↯

If it is not an error and the long type data is to be accessed by bytes, compilers can be asked to do value assignment by type cast:

long y = 0;
char *p = (char *)&y;

or

char *p = reinterpret_cast<char *>(&y);

It is a frequent error to use a pointer without initialization. In general, this results in the interruption of the execution of the program:

int *p;
*p = 1002;  // ↯ ↯ ↯

I.6.1.2. Pointer arithmetic

Besides the already presented operators (* and &), other ones can also be used. These operations together are covered by pointer arithmetic. Every other operation results in an undefined result, so they are to be avoided.

The allowed pointer arithmetic operations are summarised in a table where q and p are pointers (not of void* type), n is an integer number (int or long):

Operation

Expression

Result

two pointers of the same type can be subtracted from each other

q - p

integer number

an integer number can be added to a pointer

p + n, p++,++p, p += n,

pointer

an integer number can be subtracted from a pointer

p – n, p--, --p, p -= n

pointer

two pointers can be compared

p == q, p > q, etc.

bool (false or true)

When an integer number is added to or subtracted from a pointer, compilers automatically scale the integer number according to the type of the pointer, therefore the stored address is not modified by n bytes but by

        n * sizeof(pointer_type)

bytes, so the pointer "steps" by the size of n elements in memory.

Accordingly, increment and decrement operators can also be used for pointers and not only for arithmetic types, but in the former case it means going to the neighbouring element and not an increment or decrement by one byte.

Incrementing a pointer to the neighbouring element can be carried out in many ways:

int *p, *q;
p  = p + 1;
p += 1;
p++;
++p;

Decrementing to the preceding element can also be done in many ways:

p  = p - 1;
p -= 1;
p--;
--p;

Scaling also works when the difference of two pointers is counted, so we get the number of elements between the two pointers as a result:

int h = p - q;

I.6.1.3. void * type general pointers

C++ also allows for using general pointers without a type (void types),

int x;
void * ptr = &x;

which only store addresses, so they are not associated to any variable.

C++ ensures two implicit conversions for pointers. A pointer with any type can be transformed into a general (void) type pointer, and all pointers can be initialized by zero (0). For a conversion in the other direction, explicit type cast should be used.

That is why, if a value is to be given to variable referenced by ptr, the address should be associated with a type by a manual type cast. Type cast can be done in many ways:

int x;
void *ptr = &x;
 
*(int *)ptr = 1002;
 
typedef int * iptr;
*iptr(ptr) = 1002;
 
*static_cast<int *>(ptr) = 1002;
 
*reinterpret_cast<int *>(ptr) = 1002;

After any of these indirect value assignments, the value of x becomes 1002.

It should be noted that most of the functions in the Standard Library that return pointer are of void* type.

I.6.1.4. Multiple indirection pointers

Pointers can also be used in the case of multiple indirection relations. In these cases, the definition of pointers contains more asterisks (*):

        type * *pointer;

The asterisk standing immediately before the pointer indicates that a pointer is defined, and what can be found before this asterisk is the type of the pointer (type *). Similarly to that, any number of asterisks can be interpreted to but in order to ease our readers, we note that the elements of C++ Standard Library use double indirection pointers at most.

Let's have a look at some definitions and let's find out what the created variable is.

int x;

x is an int type variable,

int *p;

p is an int type pointer (that can point to an int variable),

int * *q;

q is an int* type pointer (that may point to an int* variable, that is to a pointer pointing to an integer).

The above detailed definitions can be rewritten in a more understandable way by alternative (typedef) type names:

// iptr - pointer type pointing to an integer 
typedef int *iptr; 
iptr p, *q;

or

// iptr - pointer type pointing to an integer 
typedef int *iptr; 
 
// type of a pointer pointing to an iptr type variable
typedef iptr *ipptr;  
 
iptr p;
ipptr q;

When these definitions are provided, and when the statements

x = 2;
p = &x;
q = &p;
x = x + *p + **q;

are executed, the value of x will be 6.

It should be noted that when interpreting more complex pointer relations, graphical representations provide a useful help.

I.6.1.5. Constant pointers

C++ compilers strictly verify the usage of const type constants, for example a constant can be referenced by an appropriate pointer:

const double pi = 3.141592565;
// pointer pointing to a double type data
double *ppi = &pi;          // error! ↯

The assignment can be carried out by a pointer pointing to the constant:

// pointer pointing to a double constant
const double *pdc;  
const double dc = 10.2;
double   d = 2012;
pdc  = &dc;                   // pdc pointer points to dc
cout <<*pdc<<endl;         // 10.2
pdc  = &d;                    // pdc pointer is set to d
cout <<*pdc<<endl;         // 2012

By using the pdc pointer, we cannot modify the value of the d variable:

*pdc = 7.29;                  // error! ↯

It is a pointer with a constant value, so the value of the pointer cannot be changed:

int month;
// constant pointer pointing to an int type data
int *const actmonth = &month; // 

The value of the actmonth pointer cannot be changed only that of *actmonth.

*actmonth = 9;
cout<< month << endl;        // 9
actmonth = &month;           // error! ↯

Pointer with a constant value pointing to a constant:

const int month = 10;
const int amonth = 8;
// constant pointer pointing to an int type constant
const int *const actmonth = &month; 
cout << *actmonth << endl;    // 10

Neither the pointer, nor the referenced data can be changed.

actmonth = &amonth;         // error! ↯
*actmonth = 12;             // error! ↯

I.6.2. References

Reference types make it possible to reference already existing variables while defining an alternative name. The general form of their definition:

        type &identifier = variable;

& indicates that a reference is defined, while the type before the & designates the type of the referenced data that have to be the same as that of the variable set as its initial value. When defining many references with the same type, the sign & should be typed before all references:

        type &identifier1 = variable1, &identifier2 = variable2 ;

When a reference is defined, initialisation has to be done with a left value. Let's make a reference to the int type x variable as an example.

int x = 2;
int &r = x;

Contrary to pointers, no variable is created to store references in general. Compilers just give a new name as a second name to the variable x (r).

       

As a consequence, the value of x becomes 12 when the following statement is evaluated:

r = x + 10;

While the value of a pointer, and the referenced storage place as a consequence, can be modified at any time, the reference r is bound to a variable.

int x = 2, y = 4;
int &r = x;
r = y;            // normal value assignment
cout << x << endl;     // 4

If a reference is initialized by a constant value or a variable of a different type, a compiler error message is sent. It does not even help if type cast is used in the latter case. These cases are treated by compilers if a so-called constant (read-only) reference is created. In that case, the compiler creates first the storage place of the same type as that of the reference, then it initializes it with the right value standing after the equal sign.

const char &lf = '\n';
unsigned int b = 2004;
const int &r = b;
b = 2012;
cout << r << endl; // 2004

Synonymous reference types can also be created by the keyword typedef:

typedef int &rint;
int x = 2;
rint r = x;

References can be created to pointers just like for other variable types:

int n = 10;
int *p = &n;        // p points to the variable n
int* &rp = p;        // rp is a reference to the pointer p
*rp = 4; 
cout << n << endl;    // 4

The same can be done with typedef:

typedef int *tpi;     // type of a pointer pointing to an integer
typedef tpi &rtpi;    // reference to a pointer pointing to an integer
int n = 10;
tpi p = &n;
rtpi rp = p;

It should be noted that a reference or a pointer cannot be defined for a reference.

int n = 10;
int &r = n;
int& *pr = &r;       // error! ↯
int& &rr =r;        // error! ↯
int *p = &r; 
// pointer p points to n via the reference r

Reference cannot be created for bitfields (Section I.8, “User-defined data types”), and we cannot even define an array consisting of reference elements (Section I.7, “Arrays and strings”).

We will experience the real significance of reference types when creating functions.

I.6.3. Dynamic memory management

In general, pointers are not used to point to other variables but it can also happen (e.g. when parameters are passed). Pointers are the key to one of the most important facilities of C++, namely to dynamic memory management. With pointers, it is programmer who allocates memory space necessary for the execution of a program and it is him who frees it if they are not needed anymore.

The dynamic management of free memory (heap) is a vital part of all programs. C ensures Standard Library functions for the needed memory allocation ( malloc (),...) and deallocation ( free ()) operations. In C++, the operators new and delete replace the above mentioned library functions (although the latter are also available).

Dynamic memory management consists of the following three steps:

  • allocating a free memory block while verifying the success of the allocation,

  • accessing the memory space with a pointer,

  • freeing (deallocating) the previously allocated memory space.

I.6.3.1. Allocating and accessing heap memory

The first step of dynamic memory management is allocating a memory space of the necessary size from the heap memory. For that purpose, the operator new can be used. The operator new allocates a memory space in the heap of the size corresponding to the type provided in its operand and returns a pointer pointing to the beginning of that memory space. If needed, an initial value can also be provided in the parentheses following the type.

        pointer = newtype;

        pointer = newtype(initial value);

new cannot only allocate memory space for one element but also for many elements standing one after the other. The data structure created in this way is called a dynamic array (Section I.7, “Arrays and strings”).

        pointer = newtype[number_of_elements];

Let's see some examples for the operation new.

int main() {
    int *p1;
    double *p2;
    p1 = new int(2);
    p2 = new double;
}

The above mentioned definitions result in the creation of the pointer variables p1 and p2 in the stack. After value assignment, two dynamic variables are born in the stack memory. Their address appear in the corresponding pointers (Figure I.14, “Dynamic memory allocation”).

Dynamic memory allocation
Figure I.14. Dynamic memory allocation


When memory space is allocated, especially for a big sized dynamic array, it may happen that there is no enough contiguous free memory at our disposal. C++ run-time system signals this issue by throwing a bad_alloc exception (exception header file). Program codes can be therefore made safe by using exception handling methods.

#include <iostream>
#include <exception>
using namespace std;
 
int main() {
   long * pdata;
   // Memory allocation
   try {
     pdata = new long;
   }
   catch (bad_alloc) {
      // Unsuccessful allocation
      cerr << "\nThere is no enough memory!" << endl;
      return -1;  // Program is exited
   }
   // ...
   // Deallocating (freeing) allocated memory space
   delete pdata;
   return 0;
}

If we do not benefit from exception handling opportunities, the nothrow constant should be used as an argument for the new operator (new header file). As a result, the new operator returns a 0 value instead of throwing an exception when memory allocation is unsuccessful.

#include <iostream>
#include <new>
using namespace std;
 
int main() {
   long * pdata;
   // Memory allocation
   pdata = new (nothrow)long;
   if (0 == pdata) {
      // Unsuccessful allocation
      cerr << "\nThere is no enough memory!" << endl;
      return -1;  // Program is exited
   }
   // ...
   // Deallocating (freeing) allocated memory space
   delete pdata;
   return 0;
}

Finally, we should not forget about another possibility of the new operator. new can also be followed directly by a pointer in parenthesis, which makes the operator return the value of the pointer (thus it does not allocate memory):

int *p=new int(10);
int *q=new(p) int(2);
cout <<*p << endl;   // 2

In the examples above, the pointer q reference the memory space p points to. Pointers can be of a different type:

long a = 0x20042012;
short *p = new(&a) short;
cout << hex <<*p << endl;  // 2012

I.6.3.2. Deallocating allocated memory

Memory blocks allocated by the operator new can be deallocated by the operator delete:

        delete pointer;

        delete[] pointer;

The first form of the operation is used to deallocate one single dynamic variable, whereas the second one is used in the case of dynamic arrays.

delete operation also works correctly with pointers of 0 value. In every other case where the value was not assigned by new, the result of delete is unpredictable.

The introductory example of the previous section can be made complete if memory space is deallocated:

int main() {
    int *p1;
    double *p2;
    p1 = new int(2);
    p2 = new double;
    delete p1;
    delete p2;
    p1 = 0;
    p2 = 0;
}

I.7. Arrays and strings

The variables in our previous examples were only able to store one (scalar) value at a time. Programming work often requires to store data sets comprising many similar or different elements in memory and to do operations on them. In C++, these tasks can efficiently be realised with the help of arrays and user-defined types (struct, class, union, see Section I.8, “User-defined data types”), that is derived types.

I.7.1. C++ array types

An array is a set of data of the same type (elements) that are placed in memory in a linear sequence. In order to access its elements, we should use the name of the array followed by the indexing operator(s) ( [] ) that enclose the index(es) of the given element in the array. The first element has always the index(es) 0.

The most frequently used array type has only one dimension: one-dimensional array (vector). If the elements of an array are intended to be identified by more integer numbers, storage should be realised by multi-dimensional arrays. From among these, we only detail the second most frequent array type, the two-dimensional array, i.e. (matrix), the elements of which are stored in a linear sequence by rows.

Before detailing the two array types, let's see how to use arrays in general. The definition of n-dimensional arrays:

        element_typearray_name[size 1 ][size 2 ][size 3 ]…[size n-1 ][size n ]

where size i determines the size of the ith dimension. In order to access the array elements, an index should be provided for every dimension in the closed interval between 0,size i -1:

        array_name[index 1 ][index 2 ][index 3 ]…[index n-1 ][index n ]

Presented in that way, arrays might seem frightening but in many cases, it is a useful, easy and comfortable solution to store data.

I.7.1.1. One-dimensional arrays

Definition of one-dimensional arrays:

        element_typearray_name[size];

The element_type determining the type of the stored elements can be of any type with the exception of void and function types. The size provided between square brackets has to be a constant expression that compilers can calculate. The size defines the number of elements that can be stored in the array. Elements are indexed between 0 and (size-1).

Graphical representation of an one-dimensional array
Figure I.15. Graphical representation of an one-dimensional array


As an example, let's have a look at an integer array having 7 elements. The integer type elements of the array will all have the value of the square of their indexes (Figure I.15, “Graphical representation of an one-dimensional array”). It is a good solution to store the size of an array as a constant. According to what has been said so far, the definition of the array square is the following:

const int maxn =7;
int square[maxn];

In order to access the elements of the array in a linear order from its beginning to its end, we use in general a for loop, the variable of which stores the index of the array. (Normally, a correct for-loop runs from 0 to size minus one). Array elements are accessed by the indexing operator ([]).

for (int i = 0; i< maxn; i++)
    square[i] = i * i;

The size of memory in bytes allocated for the array square is returned by the expression sizeof (square), whereas the expression sizeof (square[0]) returns the size of one element. The number of elements of an array can therefore always be calculated by dividing the two elements by integer division:

int number_of_elements = sizeof(square) / sizeof(square[0]);

It should be noted that C++ carry out no check on array indexing. Trying to access an element at an index that is outside the array bounds can lead to runtime errors, and tracing back these errors can take too much time.

double october [31];
october [-1] = 0;    // error ↯
october [31] = 0;    // error ↯

In the following example, we calculate the average of float numbers read from the keyboard to an array of five elements, and then print out this average and the difference between each element and this average.

#include <iostream>
using namespace std;
 
const int maxn = 5 ;
int main()
{
    float numbers[maxn], average = 0.0;
    for (int i = 0; i <maxn; i++)  
    {
        cout<<"numbers["<< i <<"] = ";
        cin>>numbers[i];        // reading from keyboard
        average += numbers[i];    // sum of all elements
    }
    cin.get();
    average /= maxn;            // counting the average
    cout<< endl << "The average is " << average << endl;
 
    // printing out differences
    for (int i = 0; i <maxn; i++) {
        cout<< i << ".\t" << numbers[i];
        cout<< '\t' << average-numbers[i] << endl;
    }
}

We should pay attention to how elements of an array are read from the keyboard. Array elements can only be accessed and set (from the keyboard) one by one.

numbers[0] = 12.23
numbers[1] = 10.2
numbers[2] = 7.29
numbers[3] = 11.3
numbers[4] = 12.7
 
The average is 10.744
0.      12.23   -1.486
1.      10.2    0.544001
2.      7.29    3.454
3.      11.3    -0.556
4.      12.7    -1.956
I.7.1.1.1. Initializing and assigning values to one-dimensional arrays

C++ language makes possible to give an initial value to array elements. When arrays are defined, the equal sign is followed by the values of the initialization list that are enclosed within curly brackets. These values are associated with the indexes of the array in that order:

        element_typearray_name[size] = { initialization list delimited by commas };

Let's see some examples how to initialize vectors:

int primes[10] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 27 };
 
char name[8] = { 'I', 'v', 'a', 'n'};
 
double numbers[] = { 1.23, 2.34, 3.45, 4.56, 5.67 };

In the case of the array primes, we made sure that the number of elements in the list is equal to the size of the array. If the list contained more initial values than needed, compilers would send us an error message.

In the second example, the initialisation list contains less elements than the size of the array. In that case, the first four elements of the array name will have the given values, while the others will be 0. If we benefit from this possibility, we can easily set 0 for all elements of any array by:

int big[2012] = {0};

In the last example, the compiler sets the number of elements of the array numbers based on the number of constants provided in the initialisation list (5). This is a good solution if the number of elements is often changed between different compilations. In that case, the number of the elements can be obtained by the above mentioned method:

double numbers[] = { 1.23, 2.34, 3.45, 4.56, 5.67 };
const int numbers = sizeof(numbers) / sizeof(numbers[0]);

Initialisation lists may contain expressions the values of which are calculated runtime:

double eh[3]= { sqrt(2.3), exp(1.2), sin(3.14159265/4) };

For a more rapid but less secure management of arrays, the Standard Library functions declared in the cstring header file and beginning with ”mem” can also be used. The function memset () makes possible to set all elements of a char array to the same character or to set all elements of an array of any type to bytes with value 0:

char line[80];
memset( line, '=', 80 );
double balance[365];
memset( balance, 0, 365*sizeof(double) );
//or
memset( balance, 0, sizeof(balance) );

The latest example gives rise to the question what operations can be used in C++ for arrays besides the indexing and the sizeof operators. The response is easy: nothing. The reason for that is that C/C++ languages treat array names as constant value pointers that are set by compilers. We benefitted from that when we called memset () since the first parameter of the function should be a pointer.

There are two methods for value assignment between two arrays of the same type and size. In the first case, elements are copied from one to another in a for loop, in the second one, the Library function memcpy () is called.

#include <iostream>
#include <cstring>
using namespace std;
 
int main()
{
    const int maxn = 8 ;
    int source[maxn]= { 2, 10, 29, 7, 30, 11, 7, 12 };
    int destination[maxn];
    for (int i=0; i<maxn; i++) {
        destination[i] = source[i];    // copying elements 
    }
    // or
    memcpy(destination, source, sizeof(destination));
}

memcpy () does not always function correctly if there is an overlap between source and destination, for example if only one part of an array has to be copied to free space for a new element. In that case as well, there are two possibilities: a for loop or the memmove () Standard Library function. In the following example, a new element is intended to be inserted in the array ordered at the position with index 1:

#include <iostream>
#include <cstring>
using namespace std;
 
int main()
{
    const int maxn = 10 ;
    int ordered[maxn]= { 2, 7, 12, 23, 29 };
    for (int i=5; i>1; i--) {
        ordered[i] = ordered[i-1];  // copying elements 
    }
    ordered[1] = 3;
    // or
    memmove(ordered+2, ordered+1, 4*sizeof(int));
    ordered[1] = 3;
}

It should be noted that the address of the destination and source area is provided by using pointer arithmetic: ordered+2, ordered+1.

I.7.1.1.2. One-dimensional arrays and the  typedef

As we have already mentioned, the readability of program code is increased if more complex type names are replaced by synonymous names. For that reason, typedef should be used for derived types.

Let's look an example where we aim at calculating the vector multiplication of two vectors of 3 integer elements and to place the new vector in a third vector. The general definition of vector multiplication:

 

 
 

 

The arrays necessary to solve the problem can be created in two ways:

int a[3], b[3], c[3];

or

typedef int vector3[3];
vector3 a, b, c;

The vectors a and b are initialized with constants:

typedef int vector3[3];
vector3 a = {1, 0, 0}, b = {0, 1, 0}, c;
c[0] =   a[1]*b[2] - a[2]*b[1];
c[1] = -(a[0]*b[2] - a[2]*b[0]);
c[2] =   a[0]*b[1] - a[1]*b[0];

typedef is also useful to reference an array containing 12 elements of type double with a pointer. The first idea should be to type

double *xp[12];

to create the type. When interpreting type expressions, the priority table of operators is useful (Appendix Section A.7, “Precedence and associativity of C++ operations”). On the basis of that, xp is an array of 12 elements and the array name is preceded by the type of its elements. That is why, xp is a pointer array with 12 elements. The order of the interpretation can be modified by parentheses:

double (*xp)[12];

In that case, xp is a pointer and the type of the referenced data is double[12], that is a double array of 12 elements. Now we are done! However, it is more rapid and safe to use the keyword typedef:

typedef double dvect12[12];
dvect12 *xp;
double a[12];
xp = &a;
(*xp)[0]=12.3;
cout << a[0];    // 12.3

I.7.1.2. Two-dimensional arrays

When scientific tasks are to be solved, it is often needed to store matrices in memory. For that purpose, the simplest form of multi-dimensional arrays has to be used, namely two-dimensional arrays:

        element_typearray_name[size1][size2];

where sizes have to be specified by dimensions. As an example, let's store the following 3x4 matrix containing integers in a two-dimensional array.

 

 

In the definition, matrix elements can be given in many ways, we only have to make sure that the initialization list should contain the elements in the correct sequence.

int matrix[3][4] = { { 12, 23,  7, 29 }, 
                     { 11, 30, 12,  7 },
                     { 10,  2, 20, 12 } };

In order to access the elements of the array, we should use the indexing operator twice. The expression

matrix[1][2]

refers to the 2nd element of the 1st row (12). (It should be kept in mind that the first index is 0 in every dimension.)

On Figure I.16, “Graphical representation of a two-dimensional array”, the two-dimensional matrix array is represented with their row and column indexes (r/c).

Graphical representation of a two-dimensional array
Figure I.16. Graphical representation of a two-dimensional array


The next program code searches for the biggest (maxe) and smallest (mine) element of the matrix above. In the solution there are nested for loops, generally used for the processing of two-dimensional arrays:

int maxe, mine;
maxe = mine = matrix[0][0];
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 4; j++) {
        if (matrix[i][j] > maxe )
             maxe = matrix[i][j];
        if (matrix[i][j] < mine )
             mine = matrix[i][j];
    }
}

Printing out two-dimensional arrays as a matrix is done by the following source code:

for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 4; j++)
        cout<<'\t'<<matrix[i][j];
    cout<<endl;
}

I.7.1.3. Variable-length arrays

According to the previous C++ standard, arrays are created during compilation and for that purpose constant expressions defining their size are made use of. The C++11 standard (Visual C++ 2012) introduces the notion of variable-length arrays and therefore it extends the number of areas where they can be used. The variable-length array created during compilation can only have automatic lifetime, can only be local and cannot be defined with an initial value. Since these arrays can only be used in functions, it is possible that the size of these arrays are different each time the function is called - that is why they are called variable-length arrays.

The size of variable-length arrays can be provided by any integer type expression; however, the size cannot be modified after creation. Compilers apply the runtime version of the sizeof operator for variable-length arrays.

In the following example, the array is created only after its size is read from the keyboard:

#include <iostream>
using namespace std;
 
int main() {
    int size;
    cout << "The number of elements of the vector: ";
    cin >> size;
    int vector[size];
    for (int i=0; i<size; i++) {
        vector[i] = i*i;
    }
}

I.7.1.4. The relationship between pointers and arrays

In C++, there is a strong relation between pointers and arrays. Every operation that can be used with array indexing can also be carried out with pointers. One-dimensional arrays (vectors) and single indirection (”one-asterisked”) pointers are completely analogous with respect to form and content. The relationship between multi-dimensional arrays and multiple indirection pointers (having more asterisks) is only formal.

Let's see from where this strong relation between vectors and single indirection pointers comes from. Let's define a vector of five integer elements.

int a[5];

The elements of a vector are placed in memory in a linear sequence from a given address. All elements can be referenced by the form a[i] (Figure I.17, “The relationship between pointers and arrays”). Let's define a pointer p pointing to an integer, and then set it to point to the first element (to the 0th element) of the array, by the "address of" operator.

int *p;
p = &a[0];        or        p = a;

A pointer can be set by the name of the array since this is an int * type pointer that cannot be modified. (However, the statement p = &a; leads to a compilation error since the type of the right-hand side is int (*)[5].)

After these, if the variable *p - to which the pointer p points - is referenced, then it is actually the element a[0] that is accessed.

The relationship between pointers and arrays
Figure I.17. The relationship between pointers and arrays


On the basis of the rules of pointer arithmetic, the addresses p+1, p+2 etc. refer the elements that follow the element to which p points. (It should be noted that elements preceding the variable can be adressed using negative numbers.) On the basis of that, every element of the array can be accessed by the expression *(p+i).

The role of the pointer p equals completely with that of the name of the array, since both define the beginning of the sequence of the elements. However, there is an important difference between the two pointers: pointer p is a variable (its value can therefore be modified any time), while a is a constant value pointer that the compiler fixes in memory.

Using the expressions of Figure I.17, “The relationship between pointers and arrays” the references figuring in the same row of the following table are identical:

   

The address of the ith element:

 

&a[i]

&p[i]

a+i

p+i

  
 

The 0th element of the array:

 

a[0]

p[0]

*a

*p

*(a+0)

*(p+0)

 

The ith element of the array:

 

a[i]

p[i]

*(a+i)

*(p+i)

  

Most of C++ compilers transform all a[i] references automatically into *(a+i) and compiles this latter pointer form. However, this analogy is true in the inverse direction too, so instead of the indirection (*) operator, the indexing operator ([]) can always be used.

In the case of multi-dimensions, analogy is only formal; however, it can often help using correctly more complex data structures. Let's see the following double type matrix:

double m[2][3] = { { 10, 2, 4 },
                   { 7,  2, 9 } };

The rows of the array are placed in memory in a linear sequence. If the last dimension is left out, the pointer of the selected row is accessed: m[0], m[1], whereas the name of the array m points to the whole array  (Figure I.18, “Two-dimensional arrays in memory”). It can be concluded from the above that two-dimensional arrays are actually vectors (one-dimensional arrays), the elements of which are vectors (pointers). Nevertheless, multi-dimensional arrays are always stored in a linear sequence in memory. In the previous example, the rows of the matrix given as initial value constitute the vectors from which the vector m is built up of.

Two-dimensional arrays in memory
Figure I.18. Two-dimensional arrays in memory


The formal analogy between vectors and pointers makes it possible to rewrite indexing operators into indirection operators without a problem. All of the following expressions refer the same element of the two-dimensional array m (precisely 9):

m[1][2]     *(m[1] + 2)     *(*(m+1)+2)

I.7.2. Dynamically allocated arrays

A big quantity of data can be stored and processed easily and effectively with the help of arrays. However, the big disadvantage of traditional arrays is that their size is fixed during compilation and it is only during execution that we learn if too much memory was allocated. It is especially true for local arrays created within functions.

It is a general rule that only arrays of little size can be defined within functions. If a bigger quantity of data has to be stored, the methods for dynamic memory allocation should be used since there is much more space in the heap than in stack memory.

We should not forget that memory space can be allocated by the new operator not only for one but also for more elements standing one after the other. In that case, the allocated memory space can be freed by the delete[] operator.

Methods for dynamic memory allocation and deallocation are present in the form of functions in the Standard Library of C/C++. To access these functions, the cstdlib header file should be included in the source code. The malloc () function allocates a memory space of a given byte size and returns a pointer to the beginning of the block. The calloc () works in a similar way; however, the size of the memory block should be given in number of elements and element size, and it initializes the allocated memory block to zero. The realloc () resizes the already allocated memory block while keeping its original content (if a bigger is allocated). The free () deallocates the reserved memory block.

In case of objects, the new and delete operators should be used to access all relating operations.

I.7.2.1. One-dimensional dynamic arrays

It is one-dimensional dynamic arrays that are the most frequently used in programming.

        type *pointer;

        pointer = newtype [number_of_elements];

        or

        pointer = new (nothrow) type [number_of_elements];

The difference between the two methods can only be perceived if allocation has not been successful. In the first case, the compiler throws an exception ( bad_alloc ) if there is not enough available contiguous memory, and in the second case, a pointer with the value of 0 is returned.

In case of arrays, it is extremely important not to forget about deleting allocated memory:

delete [] pointer; 

In the following example, the user is asked to enter the array size. Then a memory block is allocated for the array and finally it is filled up with random numbers.

#include <iostream>
#include <exception>
#include <ctime>
#include <cstdlib>
using namespace std;
 
int main() {
   long *data, size;
   cout << "\nEnter the array size: ";
   cin >> size;
   cin.get();
 
   // Memory allocation
   try {
     data = new long [size];
   }
   catch (bad_alloc) {
      // Unsuccessful allocation
      cerr << "\nThere is not enough memory!\n" << endl;
      return -1;  // Program is exited
   }
   // Filling up the array with random numbers
   srand(unsigned(time(0)));
   for (int i=0; i<size; i++) {
       data[i] = rand() % 2012;
       // or  *(data+i) = rand() %2012;
   }
   // Deleting (freeing) allocated memory space
   delete[] data;
   return 0;
}

I.7.2.2. Two-dimensional dynamic arrays

We have already mentioned in the previous subchapters that two-dimensional arrays are actually vectors of one-dimensional vectors (rows). As a consequence, a two-dimensional array of any size cannot be created at runtime since compiler has to know the type (the size) of the elements (the row vectors). However, the number of the elements (the rows) can be provided later. The new in the following example returns a pointer to the created two-dimensional array.

const int rowlength = 4;
int number_of_rows;
cout<<"Number of rows: "; cin >> number_of_rows;
int (*mp)[rowlength] = new int [number_of_rows][rowlength];

The solution becomes much more understandable if the keyword typedef is used:

const int rowlength = 4;
int number_of_rows;
cout<<"Number of rows: "; cin >> number_of_rows;
typedef int rowtype[rowlength];
rowtype *mp = new rowtype [number_of_rows];

For both solutions, setting all elements to zero can be carried out by the following loops:

for (int i=0; i<number_of_rows; i++)
    for (int j=0; j<rowlength; j++)
        mp[i][j]=0;

where mp[i] refers to the ith row, and mp[i][j] refers to the jth element of the ith row. If the former statements are placed within the main () function, the mp pointer is placed in stack while the whole two-dimensional array in the heap. This solution has two drawbacks: arrays need memory space on the first hand, and it is annoying to provide the length of rows compile-time on the other hand.

Let's see how to avoid the constraints of memory and those of fixed row length. The next two solutions are based on the idea that arrays can be made from pointers.

Dynamically allocated row vectors
Figure I.19. Dynamically allocated row vectors


In the first case, the pointer array fixing the number of rows is created in the stack and it is only row vectors that are created dynamically (Figure I.19, “Dynamically allocated row vectors”).

  • The pointer vector of 3 elements that selects the rows:        int* ps[3]= {0};

  • The dynamic creation of rows, if rows have 4 elements:

    for (int i=0; i<3; i++)
        ps[i] = new int[4];
    
  • Elements can be accessed by the operators * and []:

    *(ps[1] + 2) = 123;
    cout << ps[1][2];
    
  • Finally, we should not forget about freeing the dynamically allocated memory blocks.

    for (int i=0; i<3; i++)
        delete[] ps[i];
    

If the number of rows and columns are to be set runtime, the pointer array of the previous example should be defined dynamically. In that case, stack only contains one pointer of type int * (Figure I.20, “Dynamically allocated pointer vector and row vectors”).

Memory allocation, access and deallocation can easily be traced in the following example code:

#include <iostream>
using namespace std;
 
int main() {
    int number_of_rows, rowlength;
    cout<<"Number of rows: "; cin >> number_of_rows;
    cout<<"The length of rows: "; cin >> rowlength;
    
    // Memory allocation
    int* *pps;
    // Defining the pointer vector
    pps = new int* [number_of_rows];
    
    // Allocating rows
    for (int i=0; i<number_of_rows; i++)
        pps[i] = new int [rowlength];
    
    // Accessing the array
    for (int i=0; i<number_of_rows; i++)
        for (int j=0; j<rowlength; j++)
            pps[i][j]=0;
    
    // Deleting (freeing) allocated memory space
    for (int i=0; i<number_of_rows; i++)
        delete pps[i];
    delete pps;
}

Dynamically allocated pointer vector and row vectors
Figure I.20. Dynamically allocated pointer vector and row vectors


I.7.3. The usage of the vector type

Nowadays, C++ source codes use more and more frequently the vector type of C++ Standard Library (STL) instead of traditional one- or two-dimensional arrays. This type has all the features (resizing, checking that the provided index is within boundaries, automatic memory deallocation) needed for a secure usage of arrays.

Contrary to the already discussed types, vector type is parameterizable, which means that the type of stored elements should be provided within <> signs after the word vector . The created vectors, similarly to the cout and cin objects, are also objects that have their own operations and functions.

I.7.3.1. One-dimensional arrays in vectors

As its name suggests, vector type replaces one-dimensional arrays. Instead of presenting all features of that type, we will only concentrate on some basic useful information.

Let's see a solution for defining vectors, which needs that the vector header file be included in the source code.

vector<int> ivector; 
ivector.resize(10);
 
vector<long> lvector(12);
vector<float> fvector(7, 1.0);

ivector is an empty vector, the size of which is set by the function resize () to 10. lvector contains 12 elements of type long. In both cases, all elements are initialized to 0. fvector is created with 7 elements of type float, the values of which are all initialized to 1.0.

The actual number of elements can be obtained by the function size (), accessing elements is possible by the traditional indexing operator. An important feature of vector type is the function push_back () that adds an element to the vector.

#include <iostream>  
#include <vector>  
using namespace std;  
 
int main() {  
    vector<int> ivector(10, 5); 
     
    ivector.push_back(10);
    ivector.push_back(2);
    
    for (unsigned index=0; index<ivector.size(); index++) {  
        cout << ivector[index] << endl;
    }  
}

It should be noted that STL data containers can be managed completely with predefined algorithms ( algorithm header file).

I.7.3.2. Two-dimensional arrays in vectors

Two-dimensional, dynamic arrays can be created by nesting vector types, and it is much easier than the methods mentioned before.

#include <iostream>  
#include <vector>  
using namespace std;
 
int main() {
    int number_of_rows, rowlength;
    cout<<"Number of rows: "; cin >> number_of_rows;
    cout<<"The length of rows: "; cin >> rowlength;
    
    vector< vector<int> > m (number_of_rows, rowlength);
    
    // Accessing the array
    for (int i=0; i<number_of_rows; i++)
        for (int j=0; j<rowlength; j++)
            m[i][j]= i+j;
}

I.7.4. Handling C-style strings

The primitive types of C/C++ do not contain the string type appropriate for storing character sequences. But since it is indispensable to store and to process strings in C/C++ programs, storage can be realised by one-dimensional character arrays. For string processing, it is also needed that valuable characters are always closed by a byte with value 0. There are no operators for managing strings; however, there are many functions for that purpose (in the cstring header file).

String constant in memory
Figure I.21. String constant in memory


In program codes, we often use texts enclosed within quotation marks (string literals), that compilers store among the initialized data according to the things said before.

        cout << "C++ language";

When interpreting the statement above, the string is copied into memory, (Figure I.21, “String constant in memory”) and as a right operand of the operation << , an address of type const char * is compiled. When the program is executed, the cout object prints out character by character the content of the selected memory block until it reaches the byte with value 0.

Strings composed of wide characters are also stored in that way but in this case the type of the elements of the array is wchar_t.

        wcout << L"C++ language";

In C++, the types string and wstring can also be used to process texts, so we give an overview of these types, too.

I.7.4.1. Strings in one-dimensional arrays

When memory space is allocated for a character string, the byte indicating the end of the string should also be taken into consideration. If a text of at most 80 characters is intended to be stored in the array named str then its size should be 80+1=81:

char line[81];

In programming tasks, we often use strings having an initial value. In order to provide an initial value, we can use the solutions already presented in relation with arrays; however, we should not forget about the final '\0' character:

char st1[10] = { 'O', 'm', 'e', 'g', 'a', '\0' };
 
wchar_t wst1[10] = { L'O', L'm', L'e', L'g', L'a', L'\0' };
 
char st2[] = { 'O', 'm', 'e', 'g', 'a', '\0' };
 
wchar_t wst2[] = { L'O', L'm', L'e', L'g', L'a', L'\0' };

Compiler allocates 10 bytes of memory for the string st1 and the given characters are copied into the first 6 bytes. However, st2 will be of a size of as many bytes as many characters are provided in the initialization list. For the wst1 and wst2 wide character strings, compilers allocate a memory space twice as much (in bytes) as in the previous ones.

Initializing character arrays is much safer by using string literals (string constants):

char st1[10] = "Omega";    wchar_t wst1[10] = L"Omega";
 
char st2[] = "Omega";        wchar_t wst2[] = L"Omega";

Whereas the initialization have the same results in both cases (i.e. with characters and with string constants), using string constants is easier to understand. Not to mention the fact that the 0 byte closing strings is placed in memory by the compiler.

The result of the storage in arrays is that there is no operation available in C++ for strings either (value assignment, comparison etc.). However, there are many Library functions for processing character sequences. Let's see some basic functions processing character sequences.

Operation

Function (char)

Function (wchar_t)

reading text from the keyboard

cin >>, cin . get (), cin . getline ()

wcin >>, wcin . get (), wcin . getline ()

printing out a text

cout <<

wcout <<

value assignment

strcpy (), strncpy ()

wcscpy(), wcsncpy()

concatenation

strcat (), strncat ()

wcscat(), wcsncat()

getting the length of a string

strlen ()

wcslen()

comparison of strings

strcmp (), strcnmp ()

wcscmp(), wcsncmp()

searching for a character in a string

strchr ()

wcschr()

In order to manage strings with char type characters, we need to include the iostream and cstring header files, whereas wide character functions are found in cwchar .

The following example code transforms the text read from the keyboard and prints out all its characters in capital letters and in reverse order. It can clearly be seen from the example that we should use both Library functions and character array notion to efficiently manage strings.

#include <iostream>
#include <cstring>
#include <cctype>
using namespace std;
 
int main() {
   char s[80];
   cout <<"Type in a text: ";
   cin.get(s, 80);
   cout<<"The read text: "<< s << endl;
   for (int i = strlen(s)-1; i >= 0; i--)
      cout<<(char)toupper(s[i]);
   cout<<endl;
}

The wide character version of the previous solution is:

#include <iostream>
#include <cwchar>
#include <cwctype>
using namespace std;
 
int main() {
   wchar_t s[80];
   wcout <<L"Type a text: ";
   wcin.get(s, 80);       
   wcout<<L"The read text: "<< s << endl;
   for (int i = wcslen(s)-1; i >= 0; i--)
      wcout<<(wchar_t)towupper(s[i]);
   wcout<<endl;
}

In both examples, we used the secure cin . get () function to read a text from the input. The function reads all characters until <Enter> is pressed. However, the given array can only have a number of characters same as or less than size-1, which is provided as one of its argument.

I.7.4.2. Strings and pointers

Character arrays and character pointers can both be used to manage strings but pointers should be used more carefully. Let's see the following frequent definitions.

char str[16] = "alfa";
 
char *pstr = "gamma";

In the first case, the compiler creates the array str of 16 elements and then it copies in the characters of the provided initial value and the byte with value 0. In the second case, the compiler stores the initial text in the area reserved for string literals, then it initializes the pointer pstr to the beginning address of the string.

The value of the pointer pstr can be modified later (which causes the loss of the string "gamma"):

pstr = "iota";

A pointer value assignment takes place here since pstr now points to the address of the new string literal. On the contrary, if it is the name of the array str to which a value is assigned, an error message is obtained:

str = "iota";   // error! ↯

If a string has to be processed character by character, then we can choose from the array and the pointer approach. In the following example code, the read character sequence is first encrypted with the exclusive-or operation and then its content will be replaced again by its original content. (During encryption the string is treated as an array, and during decryption the pointer approach is used.) In both cases, the loop ends if the string closing zero byte is reached.

#include <iostream>
using namespace std;
 
const unsigned char key = 0xCD;
int main() {
    char s[80], *p;
    cout <<"Type in a text: ";
    cin.get(s, 80);
 
    for (int i = 0; s[i]; i++)            // encryption   
        s[i] ^= key;
    cout << "The encrypted text: "<< s << endl;
 
    p = s;
    while (*p)                         // decryption 
        *p++ ^= key;
    cout << "The original text: "<< s << endl;
}

In the following example, the increment and indirection operators are used together, which requires more carefulness. In the following example, the pointer sp points to a dynamically stored character sequence. (It should be noted that most C++ implementations do not allow modifications of string literals.)

char *sp = new char [33];
strcpy(sp, "C++");
cout << ++*sp << endl;        // D 
cout << sp << endl;        // D++
cout << *sp++ << endl;        // D 
cout << sp << endl;        // ++

In the first case (++*sp), the compiler interprets first the indirection operator and then increments the referred charater. In the second case (*sp++), the compiler first steps the pointer to the next character but since it is a post-increment operator, increment takes place after processing the whole expression. The value of the expression is the referenced character.

I.7.4.3. Using string arrays

Most C++ programs contain texts (e.g. error messages) that are printed on the basis of a certain index (error code). The simplest solution for storing such texts is defining string arrays.

When string arrays are planned, it should be decided whether they will be two-dimensional or pointer arrays. For beginners in C++ programming, it is often difficult to differentiate between the two. Let's see the following two definitions.

int a[4][6];
int* b[4];

a is a "real” two-dimensional array for which the compiler allocates a continuous memory block for 24 (4x6) elements of type int. On the contrary, b is a pointer vector of 4 elements. The compiler allocates space for only four pointers based on this definition. The other parts of initialization is done later in the code. Let's initialize the pointer array so that it would be able to store 5x10 integer elements.

int s1[6], s2[6], s3[6], s4[6];
int* b[4] = { s1, s2, s3, s4 };

It is clear that besides the memory block needed for 24 int elements, further memory space was also used (for the pointers). At this point, it would be logical to ask what the advantages are of using pointer arrays. The response can be found in the length of rows. While in a two-dimensional array every row contains the same number of elements,

       

the size of each row can be different in pointer arrays.

int s1[1], s2[3], s3[2], s4[6];
int* b[4] = { s1, s2, s3, s4 };

       

The other advantage of pointer arrays is that their structure is in line with the possibilities of dynamic memory allocation, thus it has an important role when dynamically allocated arrays are to be created.

After this short introduction, let's see the subject of the present subchapter: the creation of string arrays. In general, string arrays are defined by providing them initial values.  In the first example, a two-dimensional array is defined with the following statement:

static char names1[][10] = { "Ivan",
                             "Olesya",
                             "Anna",
                             "Adrienn" };

This definition results in the creation of a 4x10 character array: the number of the rows is determined by the compiler on the basis of the initialization list. The rows of the two-dimensional character array are placed in a linear sequence in memory (Figure I.22, “String array stored in a two-dimensional array”).

String array stored in a two-dimensional array
Figure I.22. String array stored in a two-dimensional array


In the second case, a pointer array is used to store the addresses of the names:

static char* names2[] = { "Ivan",
                          "Olesya",
                          "Anna",
                          "Adrienn" };

The compiler allocates four blocks of different size in memory, as it is shown on Figure I.23, “Optimally stored string array”:

Optimally stored string array
Figure I.23. Optimally stored string array


It is worth comparing the two solutions with respect to definition and memory access.

cout << names1[0] << endl;        // Ivan
cout << names1[1][4] << endl;    // y
cout << names2[0] << endl;        // Ivan
cout << names2[1][4] << endl;    // y

I.7.5. The string type

In the C++ Standard Template Library (STL), there are classes that support string management. The solutions presented above are useful for C-style character sequences. Now let's learn the features of the types string and wstring.

In order to access C++ style character sequence management, it is necessary to include the string header file in the source code. If string objects are defined as type string, many comfortable string processing operations can be carried out with the help of operators and member functions. Let's see some of them. (In the table, member functions are preceded by a point. The name of a member function is provided after the name of the object and is separated from that by a dot.) In order to process wide character strings, we need not only the string header file but also the cwchar file.

Operation

C++ solution - string

C++ solution - wstring

reading text from the keyboard

cin>> , getline ()

wcin>> , getline ()

printing out a text

cout<<

wcout<<

value assignment

= , .assign ()

= , .assign ()

concatenation

+ , +=

+ , +=

accessing the characters of a string

[]

[]

getting the length of a string

.size ()

.size ()

comparison of strings

.compare (), == , != , < , <= , > , >=

.compare (), == , != , < , <= , > , >=

conversion into C-style character sequence

.c_str (), .data ()

.c_str (), .data ()

Let's rewrite our encrypting program by using C++ style string processing.

#include <string>
#include <iostream>
using namespace std;
 
const unsigned char key = 0xCD;
int main() {
    string s;
    char *p;
    cout <<"Type in a text   : ";
    getline(cin, s);
 
    for (int i = 0; s[i]; i++)     // encryption
        s[i] ^= key;
    cout << "The encrypted text: "<< s << endl;
 
    p=(char *)s.c_str();
    while (*p)                    // decryption
        *p++ ^= key;
    cout << "The original text: "<< s << endl;
}

The solution of the task with wide character strings (wstring) can be found in the following code:

#include <string>
#include <iostream>
#include <cwchar>
using namespace std;
 
const unsigned wchar_t key = 0xCD;
int main()
{
    wstring s;
    wchar_t *p;
    wcout <<L"Type a text  : ";
    getline(wcin, s);
 
    for (int i = 0; s[i]; i++)     // encryption
        s[i] ^= key;
    wcout<<L"The encrypted text: "<< s << endl;
 
    p=(wchar_t *)s.c_str();
    while (*p)                    // decryption
        *p++ ^= key;
    wcout<<L"The original text: "<<s<<endl;
}

I.8. User-defined data types

Arrays can store and process easily a set of data of the same type. However, most programming tasks require treating data of different types logically as one unit. In C++, there are many possibilities for that among which the most important ones are structures and classes since they provide the basis for the object-oriented solutions discussed in Chapter III, Object-oriented programming in C++ of the present book.

In the following, we will learn more about structure, class, bit fields and union types from among the aggregate types. In this chapter, stress will be laid more on structures (structs). The reason for that is that all notions and solutions related to structures can be used for other user-defined types without exception.

I.8.1. The structure type

In C++, a structure (struct) is a type that is a set of many data of any type (except for void and function types). These data members that are traditionally called structure elements or data members, have names that are only valid within their structure. (In other languages, the notion field is used for these elements; however, in C++, this name is attached to bit structures.)

I.8.1.1. Structure type and structure variables

A variable of a structure type can be created in two steps. First, a structure type has to be declared and then variables can be defined. The general declaration of structures is as follows:

        struct  structure_type {

                        type 1 member 1 ;                // without an initial value!

                        type 2 member 2 ;

                        . . .

                        type n member n ;

        };

It should be noted that the curly brackets enclosing structure declarations have to be followed by a semicolon. Data members should be declared according to the standard variable declaration rules of C++; however, initial values cannot be given for them. A structure variable (a structure) of the type above can be created by the already known method:

        struct  structure_type structure_variable;          // C/C++

        structure_type structure_variable;                         // only in C++

In C++, the name standing after the keywords struct, union and class can be used as type names without using the keywords. When typedef is used, the difference between the two programming languages disappears:

        typedef struct {

                        type 1 member 1 ;

                        type 2 member 2 ;

                . . .

                        type n member n ;

                } structure_type;

        structure_type structure_variable;                 // C/C++

Similarly to those of arrays, the definitions of structure variables may contain initial values. The lists of the expressions, separated from one another by commas, initializing the data members should be enclosed within curly brackets.

        structure_type structure_variable= {initial_value_list};                // C/C++

It should be noted that most software development companies support structure definitions with typedef in order that confusions be avoided.

It should be noted that the visibility of member names within a struct is limited to the structure. This means that the same name can be used within the given visibility level (module, block), in other structures or as independent variable names:

struct s1 {
    int a;
    double b;
};
 
struct s2 {
    char a[7];
    s1 b;
};
 
long a;
double b[12];

Let's see an example for defining a structure type. The following data structure can be useful for a software cataloguing music CDs:

struct musicCD {
       // the performer and the title of the CD
       string performer, title;    
       int year;            // year of publication
       int price;        // price of the CD
};

Variables can be defined with the type musicCD:

musicCD classic = {"Vivaldi","The Four Seasons", 2009, 2590};
musicCD *pmusic = 0, rock, tale = {};

From the structure type variables above, rock is not initialised whereas all members of the structure tale will have the default initial value corresponding to their type. The pointer pmusic does not refer to any structure.

We created a new user-defined type by having declared the structure. The data members of the variable of a structure type are stored in memory in the order of their declaration. On Figure I.24, “Structure in memory” , we can see the graphical layout of the data structure created by the definition

        musicCD relax;

It can be clearly seen from the figure that the names of the data members indicate their distance from the beginning of the structure. In general, the size of a structure is equal to the sum of the sizes of its data members. However, in some cases, "holes" may appear between the members of a structure (when speed is optimized or when members are aligned to memory boundaries etc.). But it is always the punctual value that is obtained by the sizeof operator.

In most cases, the compiler is entrusted with the alignment of data members to memory boundaries. For a rapid access, it places the data by optimizing storage to the given hardware. However, if structures are exchanged by files between different platforms, the alignment used for saving has to be set in the reader program.

Structure in memory
Figure I.24. Structure in memory


In order to control the C++ compilers, we use the preprocessor directive #pragma, the features of which are completely implementation-dependent. For example, in Visual C++, the alignment to a byte boundary is done by the following directive, which has to be placed before the structure definition:

#pragma pack(1)

The argument of pack can be 1, 2, 4, 8 or 16. Without arguments, Visual C++ uses the default value for 32-bit systems: 4. Let's see the effects of the directive in the case of the following structure type.

#pragma pack(alignment)
struct mix {
    char ch1, ch2, ch3;
    short sh1, sh2;
    int n;
    double d;
};
#pragma pack()

I.8.1.2. Accessing the data members of structures

The word structure is often used alone but in that case it means a variable created by a given structure type and not the type. Let's define some variables by using the type musicCD declared before.

        musicCD s1, s2, *ps;

The memory space necessary for storing the variables s1 and s2 of type musicCD is allocated by the compiler. In order to be able to access the structure with the pointer ps of type musicCD, we have two possibilities. In the first case, ps is simply redirected to the structure s1:

        ps = &s1;

The other possibility is to use dynamic memory allocation. In the following example code, memory is reserved for the structure musicCD, than it is deallocated:

ps = new (nothrow) musicCD;
if (!ps) exit(-1);
// ...
delete ps;

After providing the appropriate definitions, we have three structures: s1, s2 and *ps. Let's see how to give values to data members. In C++, it is done by the dot ( . ) operator. The left-hand side operand of dot operators is a structure variable, the right-hand side operand selects a data member within the structure.

s1.performer = "Vivaldi";
s1.title    = "The Four Seasons";
s1.year     = 2005;
s1.price     = 2560;

If the dot operator is used on a structure to which ps points, precedence rules make it obligatory to enclose within parentheses the expression *ps:

(*ps).performer = "Vivaldi";
(*ps).title    = "The Four Seasons";
(*ps).year     = 2005;
(*ps).price     = 2560;

Since in C++, we often use structures to which a pointer points, this programming language reserves an independent operator, more precisely the arrow operator (->), for referencing data members. (The arrow operator consists of two characters: a minus and a greater than sign.) Arrow operators make more readable value assignments for data members of a structure to which ps points to:

ps->performer = "Vivaldi";
ps->title    = "The Four Seasons";
ps->year     = 2005;
ps->price     = 2560;

The left-hand operand of arrow operators is the pointer to a structure variable, whereas the right-hand operand selects the data member within that structure, similarly to dot operators. Accordingly, the meaning of the expression ps->price is: "the data member named price within the structure to which the pointer ps points".

We recommend that dot operators should be used only for direct references (accessing a data member of a variable of structure type) while arrow operators should be used only for indirect references (accessing a data member of the structure to which a pointer points).

A special case of structure value assignment is when the content of a structure type variable is intended to be assigned to another variable of the same type. This operation can even be carried out by data members:

s2.performer = s1.performer;
s2.title     = s1.title;
s2.year      = s1.year;
s2.price     = s1.price;

however, C++ can also interpret the assignment operator (=), so it can be used for structure variables:

s2  = s1 ;  // this corresponds to the 4 assignments above
*ps = s2 ;
s1  = *ps = s2 ;

This way of value assignment simply means copying the memory block occupied by the structure. However, this operation results in a problem if the structure contains a pointer that refers to an external memory space. In that case, it is programmers that have to solve this problem by assigning values for each data member one by one or the other possibility is to overload the copy operation (operator overloading) by creating an assignment operator (see Chapter III, Object-oriented programming in C++) for the structure.

Structures are accessed by data members during data input/output operations. The following example code reads the structure musicCD by asking the user to type them in and then prints out the entered data:

#include <iostream>
#include <string>
using namespace std;
 
struct musicCD {
       string performer, title;    
       int year, price;
};
 
int main() {
 
   musicCD cd;
 
   // reading the data from the keyboard
   cout<<"Please type in the data of the music CD." << endl;
   cout<<"Performer           : ";    getline(cin, cd.performer);
   cout<<"Title               : ";    getline(cin, cd.title);
   cout<<"Year of publication : ";    cin>>cd.year;
   cout<<"Price               : ";    cin>>cd.price;
   cin.get();
 
   // printing out the data
   cout<<"\nData of the music CD:" << endl;
   cout<<"Performer           : ";    cout << cd.performer << endl;
   cout<<"Title               : ";    cout << cd.title << endl;
   cout<<"Year of publication : ";    cout << cd.year << endl;
   cout<<"Price               : ";    cout << cd.price << endl;
}

I.8.1.3. Nested structures

As we have already said, structures can have data members of any type. If a structure has one or more data members that are also structures, it is a nested structure.

Let's suppose that some personal data are intended to be stored in a structure. Among personal data, a separate structure is defined for dates:

struct date {
    int year, month, day;
};
 
struct person {
    string name;
    date birthday;
};

Let's create two persons in the following way: the first one will be initialized by an initialization list, and let's assign values separately to the members for the other one.

person brother = { "Ivan", {2004, 10, 2} };
person student;
 
student.name = "Bill King";
student.birthday.year = 1990;
student.birthday.month = 10;
student.birthday.day = 20;

In the initialization list, constants initializing inner structure do not necessarily have to be enclosed within curly brackets. In the second case, student.birthday refers the structure birthday of the structure student. This is followed by the dot operator (.) and the name of a data member of the inner structure.

If the structure of type date is not used anywhere else then it can be integrated directly as an anonymous structure in the structure person:

struct person {
    string name;
    struct {
        int year, month day;
    } birthday;
};

When creating more complex dynamic data structures (e.g. linear lists), elements of a given type have to be concatenated into a chain. Elements of this kind contain some kind of data and a pointer in general. C++ makes it possible to define a pointer with the type of the structure to be declared. These structures, which contain a pointer to themselves as a data member, are called self-referential structures. As an example, let's see the declaration of list_element.

struct list_element {
      double data_member;
      list_element *link;
};

This recursive declaration makes it possible that the pointer link points to the structure of type list_element. The declaration above does not nest the two structures in each other since the structure which we will reference later with the pointer will be placed somewhere else in memory. However, C++ compilers need this declaration in order to be able to allocate memory in compliance with the declaration, that is to get to know the size of the variable to be created. The declaration above makes compilers allocate for the pointer memory space the size of which is independent of that of the structure.

I.8.1.4. Structures and arrays

Programming can be made much more efficient if arrays and structures are used together in one data type. In the following simple codes, we first place a one-dimensional array within a structure then we create a one-dimensional array of structure type elements.

I.8.1.4.1. Arrays as data members of a structure

In the following example, besides an integer vector (v), we also store the number of valuable elements (n) in the structure svector:

const int maxn = 10;
struct svector {
    int v[maxn];
    int n;
};
 
svector a = {{23, 7, 12}, 3};
svector b = {{0}, maxn};
svector c = {};
 
int sum=0;
for (int i=0; i<a.n; i++) {
    sum += a.v[i];
}
 
c = a;

In the expression a.v[i], there is no need to use parentheses since the two operations has the same precedence so the expression is evaluated from left to right. So first the member v is selected from the structure a, then the ith element of the array a.v is accessed. Another interesting part of the solution is that the elements of the vector are also copied from one of the structures to another when value assignment takes place between the two structures.

The structure of type svector can also be created dynamically. However, in that case, the structure should be accessed by an arrow operator.

svector *p = new svector;
p->v[0] =  2;
p->v[1] = 10;
p->n    =  2;
delete p;
I.8.1.4.2. Structures as array elements

A structure array has to be defined exactly in the same way as arrays of any other type. As an example, let's make use of the type musicCD declared above to create a CD-catalogue of 100 elements and let's give initial values for the first two elements of CDcatalog.

        musicCD CDcatalog[100]={{"Vivaldi","The Four Seasons",2004,1002},{} };

In order to reference the data members of structures as array elements, we have to first select the array element and then the structure member:

        CDcatalog[10].price = 2004;

If the CD-catalogue is intended to be created dynamically, identification has to be done with a pointer:

        musicCD *pCDcatalog;

Memory space for structure elements can be allocated by the operator new in the dynamically managed memory space:

        pCDcatalog = new musicCD[100];

The structure stored in an array element can be accessed by using the dot operator:

        pCDcatalog[10].price = 2004;

If array elements are not needed anymore, then the allocated memory space should be freed by the operator delete []:

        delete[] pCDcatalog;

Certain operations (like sorting) can be carried out more efficiently if the pointers of the dynamically created CDs are stored in a pointer array:

        musicCD* dCDcatalog[100];

The following loop allocates space for the structures in the dynamically managed memory:

for (int i=0; i<100; i++)
  dCDcatalog[i] = new musicCD;

Then, the structures selected by the array elements can be referenced by the arrow operator:

        dCDcatalog[10]->price = 2004;

If these structures are not needed anymore, then we should iterate through the elements and delete them from the memory space:

for (int i = 0; i < 100; i++)  
   delete dCDcatalog[i];

The following example searches for all CDs published between 2010 and 2012 in a dynamically created CD catalogue containing a fix number of CDs.

#include <iostream>
#include <string>
using namespace std;
 
struct musicCD {
    string performer, title;
    int year, price;
};
 
int main() {
    cout<<"The number of CDs:";
    int num;
    cin>>num;
    cin.ignore(80, '\n');
 
    // Memory allocation with checking
    musicCD *pCDcatalog = new (nothrow) musicCD[num];
    if (!pCDcatalog) {
        cerr<<"\a\nThere is not enough memory!\n";
        return -1;
    }
 
    // Reading the data of CDs from the keyboard
    for (int i=0; i<num; i++) {
        cout<<endl<<"The data of the "<<i<<"th CD:"<<endl;
        cout<<"Performer: "; 
                   getline(cin, pCDcatalog[i].performer);
        cout<<"Title:    "; getline(cin, pCDcatalog[i].title);
        cout<<"Year:     "; cin>>pCDcatalog[i].year;
        cout<<"Price:     "; cin>>pCDcatalog[i].price;
        cin.ignore(80, '\n');
    }
 
    // Searching for the requested CDs
    int found = 0;
    for (int i = 0; i < num; i++) {
        if (pCDcatalog[i].year >= 2010 && 
                pCDcatalog[i].year <= 2012) {
            cout<<endl<<pCDcatalog[i].performer<<endl;
            cout<<pCDcatalog[i].title<<endl;
cout<<pCDcatalog[i].year<<endl;
            found++;
        }
    }
 
    // Printing out the results
    if (found)
        cout<<"\nThe number of found elements: "<<found<<endl;
    else
        cout<<"There is no CD that matches the criteria!"<<endl;
 
    // Deallocating memory space
    delete [] pCDcatalog;
}

This program is interactive, that is data should be provided by a user, and prints out results on the screen. Testing with a bigger amount of data is more difficult in that way.

However, most operating systems make it possible to redirect the standard input and output of a program to a file. For that purpose, the input data should be typed in a file exactly in the same way the program expects them (e.g. CDs.txt), and this file name should be provided after a lower than sign when the program (CDCatalogue) is executed from the command line:

CDCatalogue <CDs.txt
 
CDCatalogue <CDs.txt >Results.txt

The second command writes the results to a separate file (Figure I.25, “Processing data in the program CDCatalogue”). (In the development environment of Visual C++ , the redirection properties can be set in the window Project /project Properties , on the tab named Debugging , in the line of Command Arguments .)

Processing data in the program CDCatalogue
Figure I.25. Processing data in the program CDCatalogue


I.8.1.5. Creating singly linked lists

The simplest forms of linked lists are singly linked lists in which all elements possess a reference to the next list element. The reference in the last element has the value null (Figure I.26, “A singly linked list”).

A singly linked list
Figure I.26. A singly linked list


The list is selected in the memory by the pointer pStart so the value of the latter should always be kept. If the value of pStart is set to another value, the list becomes inaccessible.

Let's see what advantages the usage of a linear list has as compared with vectors (one-dimensional arrays). The size of a vector is fixed when it is defined; however, the size of a list can be increased or decreased dynamically. There is also an important difference between the two how elements are inserted in or removed from them. Whereas these operations only require the copy of some pointers in the case of lists, these operations require moving a big amount of data in vectors. There is also a significant difference between the two with respect to the structure of the storage unit, that is that of the element:

       

Vector elements only contain the stored data whereas list elements also contain a reference (a pointer) to (an)other element(s). C++ list elements can be created by the already presented self-referential structure.

As an example, let's store integer numbers in a linear list, the elements of which have the following type:

struct list_element {
    int data;
    list_element *pnext;
};

Since we allocate memory for each new list element in this example, this operation is carried out by a function to be presented in the next chapter of the present book:

list_element *NewElement(int data) {
    list_element *p = new (nothrow) list_element;
    assert(p);
    p->data = data;
    p->pnext = NULL;
    return p;
}

That function returns the pointer of the new list element and initializes it if this function has run with success. We should not forget about calling the function assert (). This macro interrupts running the program and prints out the message "Assertion failed: p, file c:\temp\list.cpp, line 16” if its argument has the value of 0.

When created, the list is filled up by the elements of the following array:

int data [] = {2, 7, 10, 12, 23, 29, 30};
const int num_of_elements = sizeof(data)/sizeof(data[0]);

For a successful management of the list, we need additional variables and the pointer pStart to the beginning of the list:

list_element *pStart = NULL, *pActual, *pPrev, *pNext;

When we are dealing with a given element (pActual) of the list, we might need to know where the preceding element (pPrev) and the next one (pNext) are. In this example, in order to facilitate our task we hypothesise that the list always exists when it has been created, that is the pointer pStart is never null.

Creating the list and filling up it from the array data. When a list is created, there are three separate tasks to carry out with each element:

  • memory allocation (with checking if it has taken place) for a new list element (NewElement()),

  • assigning data to the list element (NewElement()),

  • adding the list element to (the end of) the list. When an element is added to a list, the things to be done are different in the case of first and non-first elements.

    for (int index = 0; index<num_of_elements; index++) {
        pNext = NewElement(data[index]);
        if (pStart==NULL)
           pActual = pStart = pNext ;        // first element
        else
           pActual = pActual->pnext = pNext;    // not a first element
    }
    pActual->pnext = NULL;    // closing the list
    // list: pStart ➝ 2 ➝ 7 ➝ 10 ➝ 12 ➝ 23 ➝ 29 ➝ 30
    

When printing out the elements of the list, we start from the pointer pStart and step to the next element in a loop until the null pointer indicating the end of the list is reached:

pActual = pStart;
while (pActual != NULL) {
    cout<< pActual->data << endl;
    // stepping to the next element
    pActual = pActual->pnext;
}

It is often needed to remove an element from a list. In the following example, the list element to be removed is identified by its index (Indexing starts with 0 from the element to which the pointer pStart points to. This example code is not able to remove the 0th and the last element!) The removal operation also consists of three steps:

// identifying the place of the element having the index 4 (23)
pActual = pStart;
for (int index = 0; index<4; index++) {
    pPrev    = pActual;
    pActual = pActual->pnext;
}
// removing the element of the linked list
pPrev->pnext = pActual->pnext;
// deallocating memory space
delete pActual;
// the list: pStart ➝ 2 ➝ 7 ➝ 10 ➝ 12 ➝ 29 ➝ 30

When the 0th element is removed, the pointer pStart has to be set to pStart->pnext before removal. When it is the last element that is removed, the member pnext of the element immediately before the last one has to be set to null.

Another operation is inserting a new element to a list between two already existing elements. The place of the insertion is identified by the index of the element after which the new element is to be inserted. In the example, a new list element is inserted after the element having the index 3:

// determining the place of the preceding element of index 3 (12) 
pActual = pStart;
for (int index = 0; index<3; index++)
    pActual = pActual->pnext;
// allocating memory for the new element
pNext = NewElement(23);
// inserting the new element in the linked list
pNext->pnext = pActual->pnext;
pActual->pnext = pNext;
// list: pStart ➝ 2 ➝ 7 ➝ 10 ➝ 12 ➝ 23 ➝ 29 ➝ 30

The code part above can even insert a new list element to the end of the list.

It is also frequent to add a new element to (the end of) the list.

// searching for the last element
pActual = pStart;
while (pActual->pnext!=NULL && (pActual = pActual->pnext));
// allocating memory for the new element
pNext = NewElement(80);
// adding the new element to the end of the list
pActual->pnext = pNext;
// the list: pStart ➝ 2 ➝ 7 ➝ 10 ➝ 12 ➝ 23 ➝ 29 ➝ 30 ➝ 80

We might also need to search for an element of a given value (sdata) in the list.

int sdata = 29;
pActual = pStart;
while (pActual->data!=sdata && (pActual = pActual->pnext));
if (pActual!=NULL)
    cout<<"Found: "<<pActual->data<< endl;
else
    cout<<" Not found!"<<endl;

Before exiting the program, the dynamically allocated memory space has to be freed. In order to delete the elements of a list, we have to iterate through the list while making sure to read the next element before deleting the actual list element:

pActual = pStart;
while (pActual != NULL) {
    pNext = pActual->pnext;
    delete pActual;
    pActual = pNext;
}
pStart = NULL;  // there is no list element!

I.8.2. The class type

Chapter III, Object-oriented programming in C++ of our book will discuss object-oriented programming. For that purpose, C++ use the extended version of the type struct and introduces a new notion named class. Both types can be used to define classes. (A class can contain member functions besides its data members.) All that we have said about structures before is valid for the class type but there is a little but important difference. The difference is in the default accessibility of data members.

In order to remain compatible with C, C++ had to keep the access without restrictions (public access) of structure members. However, the basic principles of object-oriented programming require data structures, the members of which cannot be accessed by default. In order that both requirements would be met, the new keyword class was introduced in C++. With the help of class, we can define "structures", the members of which cannot be accessed from the outside (private) by default.

In order that the access of data members would be regulated, the keywords public, private and protected can be placed in structure and class declarations. If accessibility is not given for a member, the default case is private for the members of a class (that is they cannot be accessed from the outside), whereas the default is public for the data members of a class of type struct.

On the basis of these facts, the type definitions of the following table are the same in each line with respect to the accessibility of their members:

struct time {

    int hour;

    int minute;

    int second;

};

class time {

 public:

    int hour;

    int minute;

    int second;

};

struct time {

 private:

    int hour;

    int minute;

    int second;

};

class time {

    int hour;

    int minute;

    int second;

};

The definitions of variables of a struct or class type can only contain initial values, if the given class type only has public data members.

class time {
 public:
    int hour;
    int minute;
    int second;
};
 
int main() {
    time beginning ={7, 10, 2};
}

I.8.3. The union type

When C language was created, in order that memory usage would be more economical, features were integrated in the language. These features have less importance than for example dynamic memory management. Let's see the essentials of the solutions presented in the next two subchapters.

  • Memory place is spared if more variables use mutually the same memory space (but not at the same time). These variables can be grouped together by the union type.

  • Another possibility is to place, in one byte, variables, the values of which occupy a space less than 1 byte. For that, we can use the bit fields of C++. When a bit structure (which is analogous to the struct type) is declared, it is also decided what data (of how many bits) are grouped.

We cannot economize much on memory by using unions and bit structures and also, the portability of our program decreases a lot. The portable version of the methods aiming at decreasing memory need is dynamic memory management. It should be noted that the public access of the members of a union cannot be restricted.

Nowadays, unions are mainly used for rapid and machine-dependent data conversions, whereas bit structures are used to generate command words controlling the different elements of a hardware device.

The solutions said about the struct type can be used for the union type as well: from declaration to the creation of structure arrays, including point and arrow operators. The only and important difference between the two types is the relative position of data members. Whereas the data members of a structure are placed in a linear sequence in memory, those of a union start all on the same address (they overlap). The size of a struct type is the total size of all of its data members (the size corrected after alignments), whereas the size of a union is equal to the size of its "longest" member.

In the following example, a data member of type unsignedlong can be accessed by bytes or by words. The position of the data members of the union conversion in memory is demonstrated by Figure I.27, “Union in memory”.

#include <iostream>
using namespace std;
 
union conversion {
  unsigned long l;
  struct {
    unsigned short lo;
    unsigned short hi;
    } s;
  unsigned char c[4];
}; 
 
int main()
{
    conversion data = { 0xABCD1234 };
    cout<<hex<<data.s.lo<<endl;        // 1234
    cout<<data.s.hi<<endl;            // ABCD
    for (int i=0; i<4; i++)
        cout<<(int)data.c[i]<<endl;    // 34 12 CD AB
 
    data.c[0]++;
    data.s.hi+=2;
    cout <<data.l<<endl;            // ABCF1235
}

Union in memory
Figure I.27. Union in memory


I.8.3.1. Anonymous unions

The standard of C++ makes it possible that a definition of a union only contains data members. The members of these anonymous unions appear as the variables of the environment containing the union. This environment can be a module, a function, a structure or a class.

In the following example, a and b, as well as c and f can be accessed as normal variables; however, they are stored overlapped in memory, as it is required by the union type.

static union {
    long a;
    double b;
};
 
int main() {
    union {
        char c[4];
        float f;
    };
 
    a = 2012;
    b = 1.2;    // the value of a changed!
    f = 0;
    c[0] = 1;    // the value of f changed!
}    

If an anonymous union is nested within a structure (a class), its data members become the members of the structure (the class) but they will remain overlapped.

The next example illustrates how to use struct and union types together. It is often necessary that the data stored in the records of a file would have different structure for every record. Let's suppose that every record contains a name and a value, the value of which is sometimes a string sometimes a number. Memory space is spared if the two possible values are combined into a union within a structure ( variant record ):

#include <iostream>
using namespace std;
 
struct vrecord  {
    char type;
    char name[25];
    union { 
        char address[50];
        unsigned long ID;
    };               // <---- there is no member name!
};
 
int main() {
    vrecord vr1={0,"BME","Budapest, Muegyetem rkpt 3-11."};
    vrecord vr2={1, "National Bank"};
    vr2.ID=3751564U;
    for (int i=0; i<2; i++) {
        cout<<"Name    : "<<vr1.name<<endl;
        switch (vr1.type) {
            case 1 :
                cout<<"ID      : "<<vr1.ID<<endl;
                break;
            case 0 :
                cout<<"Address : "<<vr1.address<<endl;
                break;
            default  :
                cout<<"Not a valid data type!"<<endl;
        }
        vr1 = vr2;
    }
}

The results:

Name    : BME
Address : Budapest, Muegyetem rkpt 3-11.
Name    : National Bank
ID      : 3751564

I.8.4. Bit fields

Classes and structures may contain members for which compilers use a space less than for integer types. Since the storage space is determined by number of bits for these members, they are called bit fields. The general declaration of bit fields:

        type  name_of_bitfield : bitlength;

A type can be of an integral type (enum is also). If no name is given for a bit field, an anonymous bit field is created, the function of which is to fill up the non-used bit positions. The length of a bit field has to be provided with a constant expression. The maximal value of bitlength is decided on the basis of the bit size of the biggest integer type on the given computer.

Bit fields and data members may figure in a mixed way in structure and class types:

#include <iostream>
using namespace std;
 
#pragma pack(1)
struct date {
   unsigned char holiday    : 1;    // 0..1   
   unsigned char day        : 6;    // 0..31  
   unsigned char month        : 5;    // 0..16  
   unsigned short year; 
};
 
int main() {
    date today = { 0, 2, 10, 2012 };
    date holiday = {1};
    holiday.year = 2012;
    holiday.month = 12;
    holiday .day = 25;
}

The layout of the structure date in memory
Figure I.28. The layout of the structure date in memory


If a bit field is not given a name in its declaration, then the space of the given bit length cannot be accessed (they are used for padding). If the length of an anonymous bit field is set to 0, then the compiler forces alignment of the following data member (or bitfield) to the next int boundary.

The following example makes more comfortable the access of the line control register (LCR) of RS232 ports by using bit fields:

#include <iostream>
#include <conio.h>
using namespace std;
 
union LCR {
    struct {
        unsigned char datalength : 2;
        unsigned char stopbits   : 1;
        unsigned char parity     : 3; 
        unsigned char            : 2;
    } bsLCR;
    unsigned char byLCR;
};
 
enum RS232port {eCOM1=0x3f8, eCOM2=0x2f8 };
 
int main() {
    LCR reg = {};
    reg.bsLCR.datalength = 3; // 8 data bits
    reg.bsLCR.stopbits = 0;   // 1 stopbit
    reg.bsLCR.parity   = 0;   // no parity
    outport(eCOM1+3, reg.byLCR);
}

The necessary operations can also be carried out with the already detailed bitwise operations; however, using bit fields make the source code more structured.

The end of the present chapter also enumerates the disadvantages of using bit fields:

  • The source code becomes non-portable since the position of bits within bytes or words may be different in different systems.

  • The address of bit fields cannot be obtained (&), since it is not sure whether they are positioned on a byte boundary.

  • If more variables are placed in a storage unit used together with bit fields as well, compilers generate a complementary code to manage the variables (therefore programs run slower and the size of the code increases).

Chapter II. Modular programming in C++

Table of Contents
II.1. The basics of functions
II.1.1. Defining, calling and declaring functions
II.1.2. The return value of functions
II.1.3. Parametrizing functions
II.1.3.1. Parameter passing methods
II.1.3.1.1. Passing parameters by value
II.1.3.1.2. Passing parameters by reference
II.1.3.2. Using parameters of different types
II.1.3.2.1. Arithmetic type parameters
II.1.3.2.2. User-defined type parameters
II.1.3.2.3. Passing arrays to functions
II.1.3.2.4. String arguments
II.1.3.2.5. Functions as arguments
II.1.3.2.5.1. Function types and typedef
II.1.3.2.5.2. Pointers to functions
II.1.3.2.5.3. Examples for pointers to functions
II.1.3.2.6. Default arguments
II.1.3.2.7. Variable length argument list
II.1.3.2.8. Parameters and return value of the main() function
II.1.4. Programming with functions
II.1.4.1. Exchanging data between functions using global variables
II.1.4.2. Exchanging data between functions using parameters
II.1.4.3. Implementing a simple menu driven program structure
II.1.4.4. Recursive functions
II.2. How to use functions on a more professional level?
II.2.1. Inline functions
II.2.2. Overloading (redefining) function names
II.2.3. Function templates
II.2.3.1. Creating and using function templates
II.2.3.2. Function template instantiation
II.2.3.3. Function template specialization
II.2.3.4. Some further function template examples
II.3. Namespaces and storage classes
II.3.1. Storage classes of variables
II.3.1.1. Accessibility (scope) and linkage of variables
II.3.1.2. Lifetime of variables
II.3.1.3. Storage classes of block level variables
II.3.1.3.1. Automatic variables
II.3.1.3.2. The register storage class
II.3.1.3.3. Local variables with static lifetime
II.3.1.4. Storage classes of file level variables
II.3.1.5. Storage classes of program level variables
II.3.2. Storage classes of functions
II.3.2.1. Accessing the compiled C functions from within C++ source
II.3.3. Modular programs in C++
II.3.4. Namespaces
II.3.4.1. The default namespaces of C++ and the scope operator
II.3.4.2. Creating and using user-defined namespaces
II.3.4.2.1. Creating namespaces
II.3.4.2.2. Accessing the identifiers of a namespace
II.3.4.2.3. Nested namespaces, namespace aliases
II.3.4.2.4. Anonymous namespaces
II.4. Preprocessor directives of C++
II.4.1. Including files
II.4.2. Conditional compilation
II.4.3. Using macros
II.4.3.1. Symbolic constants
II.4.3.2. Parameterized macros
II.4.3.3. Undefining a macro
II.4.3.4. Macro operators
II.4.3.5. Predefined macros
II.4.3.6. #line, #error and #pragma directives

C++ supports many programming techniques. The previous part of this book focussed on structured programming , which is based on the fact that computer programs have the following three components: sequences (the statements of which are provided in the order of their execution), decisions (if, switch) and loops (while, for, do). As it can be seen, the statement goto is not included in the previous list because its usage is to be avoided.

Structured programming relies on top-down design, which consists of dividing programming tasks into smaller units until program blocks easy to handle and to test are achieved. In C and C++ languages, the smallest structural unit having independent functionality is called function .

If functions or a group of functions belonging together are put in a separate module (source file), modular programming is realised. Modules can be compiled and tested separately, and they can be imported into other projects as well. The contents of modules (compiled or source code version) can be made available for other modules by interfaces (header files in C/C++). And certain parts of these modules are hidden from the outside (data hiding). Structural programming also contributes to creating new programs from achieved modules (components) by bottom-up design.

The next parts aim at introducing our readers into modular and procedural programming in C++. Procedural programming means solving a task by subprograms (functions) that are more or less independent from one another. These subprograms call one another directly or indirectly from the main program (main()) and communicate with each other by parameters. Procedural programming can be well combined with structural and modular programming.

II.1. The basics of functions

In C++, a function is a unit (a subprogram) that has a name and that can be called from the other parts of a program as many times as it is needed. A traditional C++ program has small size and easy to handle functions. Compiled functions can be put in libraries, from which the development kit integrates the code of the referenced functions in our programs.

In order to use a function efficiently, some of its inner variables are assigned a value when the function is called. These storage units called parameters should be declared in parentheses in the function definition after the function name. When a function is called (activated), the values (arguments) to be assigned to each parameter have to be passed in a similar way.

When a function is called, arguments (if there are) are passed to the called function and control passes to the activated function. After the algorithm of a function is executed by a return statement, or the physical end of the function is reached, the called function passes control back to the place where it was called by a return statement. The value of the expression in the return statement is the return value returned back by the function, which is the result of the function call expression.

II.1.1. Defining, calling and declaring functions

C++ Standard Library provides us many useful predefined functions. We only have to declare these functions before using them. For that purpose, the corresponding header file should be included in the source code. The following table enumerates some frequently used functions and the corresponding include files:

function

header file

sqrt()

cmath

isalpha()

cctype

atoi()

cstdlib

rand()

cstdlib

strlen()

cstring

wcslen()

cwchar

Contrary to Library functions, our own functions should always be defined as well. This function definition can be placed anywhere in a C++ program, but only once. If the definition of a function precedes the place where it is called (used), then it is also a declaration.

The general form of a function definition is the following (the signs 〈 〉 indicate optional parts): A parameter declaration list in the function header enumerates each parameter separated by each other by a comma, and every parameter is preceded by its type.

Function definition
Figure II.1. Function definition


A storage class can also be given before the return type in the definition of functions. In the case of functions, the default storage class is extern, which indicates that the function can be accessed from other modules. If the accessibility of a function needs to be restricted to a given module, the static storage class should be used. (When parameters are declared, only the register storage class can be specified). If a function is intended to be placed within our own namespace, then the definition and the prototype of that function have to be put in the chosen namespace block. (Storage classes and namespaces are detailed later in this book.)

The next example contains a function that calculates the sum of the first n positive integer numbers. The function isum() expects an int type value and returns an int type result.

int isum(int n)
{
    int s = 0;
    for (int i=1; i<=n; i++)
        s += i;
    return s;
}

Suppose that in the source code, the definition of isum() is before the main () function from where the function isum() is called:

int main()

{
    cout << isum(10) << endl;
    int s = isum(7) * isum(10) + 2; 
}

When a function is called , the name of the function is followed by a list of arguments separated from each other by a comma. The steps of calling a function can be traced on Figure II.2, “Steps of calling a function”.

        function_name (〈argument 1 , argument 2 , … argument n 〉)

Parentheses should be used even if a function does not have any parameters. A function can be called from anywhere where a statement can be given.

The order in which arguments are evaluated is not defined by the language C++. Function call operators guarantee only one thing: by the time control is passed to a called function, the argument list has completely been evaluated (together with all of its side-effects).

Steps of calling a function
Figure II.2. Steps of calling a function


C++ standards require that functions have to be declared before they are called. Defining a function is therefore declaring a function. Then it may be logical to ask how we can make sure that the called function would always precede the place where it is called. Of course, this cannot be ensured because there are functions calling each other. In case the functions main () and isum() are swapped in the previous example, we get compilation errors until the prototype containing the whole description of the function is placed before the function is called:

int isum(int); // prototype
 
int main() {
 cout << isum(10) << endl;
 int s = isum(7) * isum(10) + 2;
}
 
int isum(int n)
{
    int s = 0;
    for (int i=1; i<=n; i++)
        s += i;
    return s;
}

The complete declaration of a function (its prototype ) contains the name and the type of the function and provides information about the number and the type of the parameters:

        return_value function_name(〈parameter declaration list〉);

        return_value function_name(〈type_list〉);

C++ compilers compile function calls if the prototype is already known:

  • checks the compatibility of the number and the types of parameters by comparing these parameters with the argument list,

  • converts arguments according to the types defined in the prototype and not according to the rules of automatic conversion.

(It should be noted that function definitions replace prototypes.) In most cases, a function header is used as a prototype, and it ends with a semicolon. In prototypes, parameter names do not have any importance, they can be left out or any other name can be used. The following prototypes are completely equal for compilers:

int isum(int);
int isum(int n);
int isum(int lots);

The prototype of a function can figure many times in the source code; however, they have to be the same, only parameter names can be different.

It should be noted that the prototype of functions without parameters is interpreted differently by C and C++ languages:

declaration

C interpretation

C++ interpretation

type funct();

type funct(...);

type funct(void);

type funct(...);

type funct(...);

type funct(...);

type funct(void);

type funct(void);

type funct(void);

C++ makes it possible that a parameter list containing at least one parameter should end with three dots (...). A function defined in that way can be called with at least one parameter but also with any number or type of arguments. Let's look at the prototype of the function sscanf ().

int sscanf ( const char * str, const char * format, ...);

Chapter (Section I.5, “Exception handling”) dealing with exceptions also mentioned that the transferring (throw) of exceptions to the caller function can be enabled or disabled in function header. When the keyword throw is used, the definition of functions is modified in the following way:

        return_type function_name (〈parameterlist〉) 〈throw(〈type_list〉)〉

        {

                〈local definitions and declarations

                〈statements

                returnexpression〉;

        }

The prototype corresponding to the definition also has to contain the keyword throw:

return_type function _ name (〈parameterlist〉) 〈throw(〈type_list〉)〉;

Let's see some prototypes mentioning the type of the thrown exceptions:

int funct() throw(int, const char*); // int and const char*
int funct();                   // all
int funct() throw();               // not any

II.1.2. The return value of functions

The return_type figuring in the definition/declaration of a function determines the return type of the function, which can be of any C++ type with the exception of arrays and functions. Functions cannot return data with volatile or const type qualifiers; however, they can return a reference or a pointer to such data.

When the return statement is processed, the function passes control back to the caller, and the return value of type return_type can be used in the place where the function has been called.

        return expression;

Within a function, many return statements may be placed; however, structured programming requires that if it is possible, only one exit point should be used.

The following prime number checker function can be exited at three points because there are three return statements, which results in a program structure more difficult to understand globally.

bool IsPrime(unsigned n)
{
    if (n<2) // 0, 1
        return false;
    else {
        unsigned limit = (unsigned)sqrt(1.0*n);
        for (unsigned d=2; d<=limit; d++)
            if ((n % d) == 0) 
                return false;
    }
    return true;
}

If an additional variable (result) is introduced, the code becomes much clearer:

bool IsPrime(unsigned n)
{
    bool result = true;
    if (n<2) // 0, 1
        result = false;
    else {
        unsigned limit = (unsigned)sqrt(1.0*n);
        for (unsigned d=2; d<=limit && result; d++)
            if ((n % d) == 0) 
                result = false;
    }
    return result;
}

By using the type void, we can create functions that do not return any value. (Other programming languages call these procedures.) In that case, exiting the function is carried out by a return statement without a return value. Functions of type void are mostly exited when the closing curly bracket is reached.

The following function prints out all perfect numbers within a given interval. (A positive integer number is perfect if the sum of its positive divisors is equal to the given number. The smallest perfect number is 6 because 6 = 1+2+3, but 28 is also perfect because 28 = 1+2+4+7+14.)

void PerfectNumbers(int from, int to)
{
      int sum = 0;
      for(int i=from; i<=to; i++) {
          sum = 0;
          for(int j=1; j<i; j++) {
              if(i%j == 0)  
                  sum += j;
          }
          if(sum == i)  
              cout<< i <<endl;
      }
 }

Functions can return pointers or references; however, it is forbidden to return local variables or the address of local variables since they are deleted when the function is exited. Let's see some good solutions.

#include <cassert>
#include <new>
using namespace std;
 
double * Allocate(int size) {
    double *p = new (nothrow) double[size];
    assert(p);
    return p;
}
 
int  & DinInt() {
    int *p = new (nothrow) int;
    assert(p);
    return *p;
}
 
int main() {
    double *pd = Allocate(2012);
    pd[2] = 8;
    delete []pd;

    int &x = DinInt(); 
    x = 10;
    delete &x;
}

The function named Allocate() allocates an array of type double with a given number of elements and returns the beginning address of the dynamic array. The function DinInt() allocates space on the heap for only one integer variable and returns the reference of the dynamic variable. This value can be accessed by a variable of reference type, the dynamic variable can be accessed without the * operator.

II.1.3. Parametrizing functions

When we create functions we have to tend to use the algorithm in the function in a range as wide as possible. This is needed because the input values (parameters) of an algorithm are assigned when the function is called. The following function without a parameter prints out a greeting:

void Greeting(void) {
    cout << "Welcome on board!" << endl;
}

Every time this function is called, we always get the same message:

Greeting();

What should we do to greet users according to the part of the day? Then, the function should be parametrized:

#include <iostream>
#include <string>
using namespace std;
 
void Greeting(string greeting) {
    cout << greeting << endl;
}
 
int main() {
    Greeting("Good morning");
    Greeting("Good evening!");
}

In a C++ function definition, each parameter is preceded by its type, no simplification is allowed. A declared parameter can be used as a local variable within a function; however, it is only accessible from the outside when they are passed as arguments. A parameter can be scalar (bool, char, wchar_t, short, int, long, long long, float, double, enumeration, reference and pointer) or structure, union, class or array.

In order to demonstrate these different types of parameters, let's make the function calculating the value of a polynomial on the basis of Horner's method.

        The general form of polynomials:       

        Horner's scheme       

The input parameters of the function are: the value of x, the degree of the polynomial and an array of the coefficients of the polynomial (having degree+1 elements). (The type qualifier const forbids the modification of the elements of the array within the function.)

double Polynomial(double x, int n, const double c[]) {
    double y = 0;
    for (int i = n; i > 0; i--)
       y = (y + c[i]) * x;
    return y + c[0];
}
 
int main(){
    const int degree = 3;
    double coefficients[degree + 1] = { 5, 2, 3, 1};
    cout << Polynomial(2,degree,coefficients)<< endl; // 29
}

II.1.3.1. Parameter passing methods

In the language C++ parameters can be grouped in two categories on the basis of how they are passed. There are input parameters passed by value and variable parameters  passed by reference.

II.1.3.1.1. Passing parameters by value

If parameters are passed by value , it is their value that is passed to the called function. The parameters of the function are initialised to the passed values, and after, the relation between these arguments and parameters ends. As a consequence, the operations carried out on parameters have no effect on the arguments with which the function is called.

Arguments can only be expressions, the type of which can be converted into the type of the corresponding parameters of the called function.

The function enumber() returns the approximate value of e by summing up the first n+1 elements of the sequence:

       

double enumber(int n) {
    double f = 1;
    double eseq = 1;
    for (int i=2; i<=n; i++) {
        eseq += 1.0 / f;
        f *= i;
    }
    return eseq;
}

The function enumber() can be called by any numeric expression:

int main(){
    long x =1000;
    cout << enumber(x)<< endl;
    cout << enumber(123) << endl;
    cout << enumber(x + 12.34) << endl;
    cout << enumber(&x) << endl;
}

When it is first called, the value of the variable x of type long is passed to the function converted to type int. In the second case, the value of the parameter becomes a constant of type int. The argument of the third call is an expression of type double, the value of which is converted to an integer before it is passed. This conversion may provoke data loss - that is why the compiler sends us an alert message. The last case is an odd-one-out in the list of the calls since in that case, the compiler rejects to convert the address of the variable x to an integer. Since type conversions of this type can lead to run-time errors, conversions have to be asked for separately:

cout << enumber((int)&x) << endl;

If the value of an external variable is intended to be modified within the function, it is the address of the variable that have to be passed and the address has to be received as a parameter of pointer type. As an example, let's have a look at the classical function swapping the values of two variables:

void pswap(double *p, double *q) {
    double c = *p;
    *p = *q;
    *q = c;
}
 
int main(){
    double a = 12, b =23;
    pswap(&a, &b);
    cout << a << ", " << b<< endl; // 23, 12
}

Arguments may also be expressions, but in that case these expressions have to be left value expressions. It should be noted that arrays are passed to functions by their beginning address.

If the type qualifier const is placed in the parameter list, we can restrict the modification of the memory space to which a pointer points ( const double *p) and restrict the modification of the value of the pointer ( double * const p) within a function.

II.1.3.1.2. Passing parameters by reference

Parameters passed by value are used as local variables in functions. However, reference parameters are not independent variables; they are only alternative names for the arguments provided in a function call.

Reference parameters are marked with a & character placed between the type and the parameter name in the function header. When the function is called, the argument variables have to have the same type as that of parameters. The function swapping the value of variables becomes simpler if we use parameters passed by reference:

void rswap(double & a, double & b) {
    double c = a;
    a = b;
    b = c;
}
 
int main(){
    double x = 12, y =23;
    rswap(x, y);
    cout << x << ", " << y << endl; // 23, 12
}

The value (right-value) and the address (left-value) of a reference parameter equals with the value and the address of the referenced variable, so it completely replaces the latter.

It should be noted that the compiled code of the functions pswap() and rswap() are completely the same in Visual Studio. That is why it is not more efficient to use pswap() in C++ program codes.

Independently of the parameter passing method, compilers allocate memory space for parameters in the stack. In the case of value parameters, the size of the allocated memory depends on the type of the parameter; therefore it can be really big, while in the case of reference parameters it is the pointer size used in the given system that counts. In the case of a bigger structure or object, we should not only consider the increased memory need but also the longer time a function call requires.

In the following example, a reference to a structure is passed to a function but we would like to prevent the modification of the structure within the function. For that purpose, the most efficient method is to use a parameter of constant reference type :

#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
 
struct svector {
    int size;
    int a[1000];
};
 
void MinMax(const svector & sv, int & mi, int & ma) {
mi = ma = sv.a[0];
for (int i=1; i<sv.size; i++) {
        if (sv.a[i]>ma)
            ma = sv.a[i];
        if (sv.a[i]<mi)
            mi = sv.a[i];
}
}
 
int main() {
    const int maxn = 1000;
    srand(unsigned(time(0)));
    svector v;
    v.size=maxn;
    for (int i=0; i<maxn; i++) {
        v.a[i]=rand() % 102 + (rand() % 2012);
    }
    int min, max;
    MinMax(v, min, max);
    cout << min << endl;
    cout << max << endl;
}

It should be noted that while in the solution above calling the function MinMax() is faster and less memory space is required in the stack, however accessing parameters within the function is more efficient by using value type parameters.

Constant reference parameters completely replace constant value parameters, so any expression (not only variables) can be used as arguments. This fact is demonstrated by the example determining the greatest common divisor of two numbers:

int Gcd(const int & a, const int & b ) {
    int min = a<b ? a : b, gcd = 0;  
    for (int n=min; n>0; n--)
        if ( (a % n == 0) && (b % n) == 0) {
            gcd = n;
            break;
        }
    return gcd;
}
 
int main() {
    cout << Gcd(24, 32) <<endl; // 8
}

II.1.3.2. Using parameters of different types

The following subchapters will broaden our knowledge about parameters for different types with the help of examples. In the examples of this subchapter, the definition of functions is followed by the presentation of the function call after a dotted line. Of course, we give the whole program code for more complicated cases.

II.1.3.2.1. Arithmetic type parameters

Parameters can be declared by the types bool, char, wchar_t, int, enum, float and double and these types can also be function types.

In general, we do not have to hesitate much when deciding what the used parameters and the returned value will be. If a function does not return any value, i.e. it only carries out an operation, then we use the return type void and value parameters:

void PrintOutF(double data, int field, int precision) {
    cout.width(field);
    cout.precision(precision);
    cout << fixed << data << endl;
}
...
PrintOutF(123.456789, 10, 4);  //  123.4568

We also have an easy task when a function returns one value from the input values:

long Product(int a, int b) {
   return long(a) * b;
}
...
cout << Product(12, 23) <<endl; // 276

However, if we want a function to return more values, then the solution should be based on reference (or pointer) parameters. In that case, the function is of type void or of a type that indicates the successfulness of an operation, for example bool. The following function Cube() calculates the surface area, the volume and the length of the body diagonal of the cube on the basis of its edge length:

void Cube(double a, double & surface_area, double & volume, 
          double & diagonal) {
    surface_area = 6 * a * a;
    volume = a * a * a;
    diagonal = a * sqrt(3.0);
}
...
double f, v, d;
Cube(10, f, v, d);
II.1.3.2.2. User-defined type parameters

If user-defined types (struct, class, union) are used in parameter lists or a return value, then the solutions presented for arithmetic types can be used. The basis of that is provided by the fact that C++ defines value assignment between objects and unions of the same type.

Arguments of user-defined types can be passed to functions by value, by reference or by a pointer. In the standard C++, the return value of functions can be of a user-defined type. Therefore there are many possibilities. We only have to decide which one is the best for the given task.

As an example, let's see a structure appropriate for storing complex numbers.

struct complex {
           double re, im;
       };

Let's create a function to add two complex numbers. Its members and the structure that stores the result are passed to the function with a pointer to them (CSum1()). Since input parameters are not intended to be modified within the function, the type qualifier const is used.

void CSum1(const complex*pa, const complex*pb, complex *pc){
  pc->re = pa->re + pb->re;
  pc->im = pa->im + pb->im;
}

The second function returns the sum of the two complex numbers passed by value (CSum2()). Addition is carried out in a local structure, and it is only its value that is passed by the return statement.

complex CSum2(complex a, complex b) {
  complex c;
  c.re = a.re + b.re;
  c.im = a.im + b.im;
  return c;
}

The second solution is of course much safer and expresses more the essential of this operation than the first one. In any other aspect (memory need, speed), we have to choose the first function. However, if a reference type is used, the solution we get realises the advantages of the function CSum2() and is also able to beat the function CSum1().

complex Csum3(const complex & a, const complex & b) {
  complex c;
  c.re = a.re + b.re;
  c.im = a.im + b.im;
  return c;
}

The three different solutions require two different function calls. The following main() function contains a call for all the three adding function:

int main() {
    complex c1 = {10, 2}, c2 = {7, 12}, c3;
    
    // all the three arguments are pointers
    CSum1(&c1, &c2, &c3); 
    
    c3 = CSum2(c1, c2);   // arguments are structures
    
    // arguments are structure references
    c3 = CSum3(c1, c2);  
}
II.1.3.2.3. Passing arrays to functions

Now let's see how we can pass arrays to functions in C++. We have already mentioned that arrays cannot be passed or returned by value (by copying their values). There is even a difference between passing one-dimensional arrays (vectors) and multi-dimensional ones as arguments.

If one-dimensional arrays (vectors) are passed as function arguments, it is a pointer to their first value that is passed. That is why, the modifications carried out on the elements of the vector within the function will remain even after the function is exited.

Parameters of a vector type can be declared either as pointers or with empty indexing operators . Since C++ arrays do not contain any information about the number of their elements, the latter information should also be passed as a separate parameter. Within functions, accessing their elements can be done by any of the already presented two methods (indexing, pointer). A function calculating the sum of the first n elements of a vector of type int can be realised in many ways. The parameters of the vector should be qualified as const because that ensures that the vector elements could not be modified within the function.

long VectorSum1 (const int vector[], int n) {
  long sum = 0;
  for (int i = 0; i < n; i++) {
     sum+=vector[i];   // or sum+=*(vector+i);
  }
  return sum;
}
...
int v[7] = { 10, 2, 11, 30, 12, 7, 23};
cout <<VectorSum1(v, 7)<< endl;  // 95
cout <<VectorSum1(v, 3)<< endl;  // 23

However, there is a solution that is completely equal to the solution above: if the address of the vector is received in a pointer. (The second const qualifier makes it impossible to modify the pointer value.)

long VectorSum2 (const int * const vector, int n) {
  long sum = 0;
  for (int i = 0; i < n; i++) {
     sum+=vector[i];   // or sum+=*(vector+i);
  }
  return sum;
}

In case the elements of a passed vector have to be modified, for example, they have to be sorted, the first const type qualifier has to be left out:

void Sort(double v[], int n) {
    double temp;
    for (int i = 0; i < n-1; i++) 
        for (int j=i+1; j<n; j++)
            if (v[i]>v[j]) {
                temp = v[i];
                v[i] = v[j];
                v[j] = temp;
            }
}
 
int main() {
    const int size=7;
    double v[size]={10.2, 2.10, 11, 30, 12.23, 7.29, 23.};
    Sort(v, size);
    for (int i = 0; i < size; i++) 
        cout << v[i]<< '\t';
    cout << endl;
}

One-dimensional arrays can be passed by reference , too. However, in that case, the size of the vector to be processed has to be fixed.

long VectorSum3 (const int (&vector)[6]) {
  long sum = 0;
  for (int i = 0; i < 6; i++) {
     sum+=vector[i];   // or sum+=*(vector+i);
  }
  return sum;
}
...
int v[6] = { 10, 2, 11, 30, 12, 7};
cout << VectorSum3(v) << endl;

When two-dimensional array arguments are presented, we mean by the notion of two-dimensional arrays the arrays that are created (in a static way) by the compiler:

int m[2][3];

A reference to the elements of the array (m[i][j]) can also be expressed in the form *(( int *)m+(i*3)+j) (in fact, that is what compilers also do). From that expression, it can be clearly seen that the second dimension of two-dimensional arrays (3) has a vital importance for compilers, while the number of rows can be anything.

Our aim is to make a function that prints out the elements of a two-dimensional integer array of any size in a matrix form. As a first step, let's create the version of this function that is able to print out arrays of size 2x3.

void PrintMatrix23(const int matrix[2][3]) {
  for (int i=0; i<2; i++) {
     for (int j=0; j<3; j++)
       cout <<matrix[i][j] <<'\t';
     cout<<endl;
  }
}
...
int m[2][3] = { {10, 2, 12}, {23, 7, 29} };
PrintMatrix23(m);
10      2       12
23      7       29

The two-dimensional array is passed to the function as a pointer to the start of the used memory space. When accessing array elements, the compiler makes use of the fact that rows contain 3 elements. In that way, the function above can simply be transformed into another function that prints out an array of any nx3 size, we only have to pass the number of rows as a second argument:

void PrintMatrixN3(const int matrix[][3], int n) {
  for (int i=0; i<n; i++) {
     for (int j=0; j<3; j++)
       cout <<matrix[i][j] <<'\t';
     cout<<endl;
  }
}
...
int m[2][3] = { {10, 2, 12}, {23, 7, 29} };
PrintMatrixN3(m, 2);
cout << endl;
int m2[3][3] = { {1}, {0, 1}, {0, 0, 1} };
PrintMatrixN3(m2, 3);
10      2       12
23      7       29

1       0       0
0       1       0
0       0       1

However, there is no possibility to leave out the second dimension because in that case the compiler is not able to identify the rows of the array. We can only do one thing to get a general solution: we take over the task of accessing the memory space of the array from the compiler by using the above mentioned expression:

void PrintMatrixNM(const void *pm, int n, int m) {
  for (int i=0; i<n; i++) {
     for (int j=0; j<m; j++)
        cout <<*((int *)pm+i*m+j) <<'\t';
     cout<<endl;
  }
}
...
int m[2][3] = { {10, 2, 12}, {23, 7, 29} };
PrintMatrixNM(m, 2, 3);
cout << endl;
int m2[4][4] = { {0,0,0,1}, {0,0,1}, {0,1}, {1} };
PrintMatrixNM(m2, 4, 4);
10     2      12
23     7      29

0      0      0      1
0      0      1      0
0      1      0      0
1      0      0      0
II.1.3.2.4. String arguments

When creating functions, we can also choose between C-style (char vector) or user-defined type ( string ) string processing. Since we have already presented both parameter types, we only show here some examples.

Processing character sequences can be done in three ways. First, strings are treated as vectors (with the help of indexes), or we can carry out the necessary operations by pointers or finally we can use the member functions of the type string .

It should be noted that modifying the content of string literals (character sequence constants) leads in general to a run-time error, therefore they can only be passed to functions as constants or a value parameter of type string .

In the first example, we only iterate through the elements of a character sequence while counting the number of occurrence of a given character.

If a string is processed as a vector , we need an index variable (i) to index its characters. The condition that stops counting is fulfilled if the 0 byte closing the character sequence is reached. In the meanwhile, the index variable is incremented continuously by one.

unsigned CountChC1(const char s[], char ch) {    
    unsigned cnt = 0;
    for (int i=0; s[i]; i++) {
        if (s[i] == ch) 
            cnt++;
    }
    return cnt;
}

If we use a pointer , we go through all characters by incrementing the pointer pointing first to the beginning of the character sequence until we reach the final '\0' character (byte 0).

unsigned CountChC2(const char *s, char ch) {    
    unsigned cnt = 0;
    while (*s) {
        if (*s++ == ch) 
            cnt++;
    }
    return cnt;
}

If the string has the type string , its member functions can also be used besides indexing:

unsigned CountChCpp(const string & s, char ch) {    
    int cnt = -1, position = -1;
    do {
        cnt++;
        position = s.find(ch, position+1);
    } while (position != string::npos); 
    return cnt;
}

The corresponding function calls are demonstrated with a string constant, a character array and a string type argument. All the three functions can be called with a string literal or a character array argument:

char s1[] = "C, C++, Java, C++/CLI / C#";
string s2 = s1;
cout<<CountChC1("C, C++, Java, C++/CLI / C#", 'C')<<endl;
cout<<CountChC2(s1, 'C')<<endl;

However, if we use arguments of type string , the first two functions need a little manipulation:

cout << CountChC2(s2.c_str(), 'C') << endl;
cout << CountChCpp(s2, 'C') << endl;

Conditions become more strict if the declaration const is left out before the parameters. The next example reverses the string passed to the function, and returns a pointer to the new string. The solution is carried out on all the three cases presented above:

char * StrReverseC1(char s[]) {
    char ch;
    int length = -1; 
    while(s[++length]); // determining the string length
    for (int i = 0; i < length / 2; i++) {
        ch = s[i];
        s[i] = s[length-i-1];
        s[length-i-1] = ch;
    }
    return s;
}
 
char * StrReverseC2(char *s) {
    char *q, *p, ch;
    p = q = s; 
    while (*q) q++;     // going through the elements until byte 0 closing the string
    p--;            // p points to the first character of the string
    while (++p <= --q) {
        ch = *p;
        *p = *q;
        *q = ch;    
    }
    return s;
}
 
string& StrReverseCpp(string &s) {
    char ch;
    int length = s.size();
    for (int i = 0; i < length / 2; i++) {
        ch = s[i];
        s[i] = s[length-i-1];
        s[length-i-1] = ch;
    }
    return s;
}

The functions have to be called by arguments having the same type as the parameters:

int main() {
    char s1[] = "C++ programming";
    cout << StrReverseC1(s1) << endl;    // gnimmargorp ++C
    cout << StrReverseC2(s1) << endl;    // C++ programming
    string s2 = s1;
    cout << StrReverseCpp(s2) << endl;    // gnimmargorp ++C
    cout << StrReverseCpp(string(s1));    // gnimmargorp ++C
}
II.1.3.2.5. Functions as arguments

When developing mathematical applications, it is normal to expect that a well elaborated algorithm could be used in many functions. For that purpose, the function has to be passed as an argument to the function executing the algorithm.

II.1.3.2.5.1. Function types and typedef

With typedef, the type of a function can be indicated by a synonymous name. The function type declares the function that has the given number and type of parameters and returns the given data type. Let's have a look at the following example function that calculates the value of the third degree polynomial if x is given. The prototype and the definition of the function are:

double poly3(double);    // prototype 
 
double poly3(double x)    // definition
{
    return x*x*x - 6*x*x - x + 30;
}

Graph of the third degree polynomial
Figure II.3. Graph of the third degree polynomial


Let's have a look at the prototype of the function and let's type typedef before it and replace the name poly3 with mathfunction.

        typedef double mathfunction(double);

If the type mathfunction is used, the prototype of the function poly3() is:

        mathfunction poly3;

II.1.3.2.5.2. Pointers to functions

In C++, function names can be used in two ways. A function name can be the left operand of a function call operator: in this case, it is a function call expression

        poly3(12.3)

the value of which is the value returned by the function. However, if the function name is used alone

        poly3

we get a pointer, the value of which is a memory address where the code of the function is (code pointer) and the type of which is that of the function.

Let's define a pointer to the function poly3(). This pointer can be assigned the address of the function poly3() as a value. The definition can be obtained easily if the name in the header of the function poly3 is replaced by the expression (*functionptr):

        double (*functionptr)(double);

functionptr is a pointer to a function that returns a double value and that has a parameter of type double.

However, this definition can be provided in a much more legible way if we use the type mathfunction created with typedef:

        mathfunction *functionptr;

When the pointer functionptr is initialised, the function poly3 can be called indirectly:

        functionptr = poly3;

        double y = (*functionptr)(12.3);

or

        double y = functionptr(12.3);

If we want to create a reference to this function, we have to follow the same steps. But in this case, the initial value has to be given already in the definition:

        double (&functionref)(double) = poly3;

or

        mathfunction &functionref = poly3;

Calling the function by using the reference:

        double y = functionref(12.3);

II.1.3.2.5.3. Examples for pointers to functions

On the basis of the things said above, the prototype of the Library function qsort () becomes more comprehensible:

void qsort(void *base, size_t n, size_t width,
           int (*fcmp)(const void *, const void *));

With qsort (), we can sort data stored in an array by using the Quicksort algorithm. This function makes it possible to sort an array starting at the address base, having n number of elements and each element being allocated width bytes. The comparator function called during sorting has to be provided by ourselves as the parameter fcmp.

The following example uses the function qsort () to sort integer and string arrays:

#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;
 
int icmp(const void *p, const void *q) {
 return *(int *)p-*(int *)q;
}
 
int scmp(const void *p, const void *q) {
 return strcmp((char *)p,(char *)q);
}
 
int main() {
    int m[8]={2, 10, 7, 12, 23, 29, 11, 30};
    char names[6][20]={"Dennis Ritchie", "Bjarne Stroustrup",
                       "Anders Hejlsberg","Patrick Naughton",
                       "James Gosling", "Mike Sheridan"};
 
    qsort(m, 8, sizeof(int), icmp);
    for (int i=0; i<8; i++)
      cout<<m[i]<<endl;
 
    qsort(names, 6, 20, scmp);
    for (int i=0; i<6; i++)
      cout<<names[i]<<endl;
}

The function named tabulate() in the following example codes can be used to print out in tabular form the values of any function having a parameter of type double and returning a double value. The parameters of the function tabulate() contains the two interval boundaries and the step value.

#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
 
// Prototypes
void tabulate(double (*)(double), double, double, double);
double sqr(double); 
 
int main() {
    cout<<"\n\nThe values of the function sqr() ([-2,2] dx=0.5)"<<endl;
    tabulate(sqr, -2, 2, 0.5);
    cout<<"\n\nThe values of the function sqrt() ([0,2] dx=0.2)"<<endl;
    tabulate(sqrt, 0, 2, 0.2);
}
 
// The definition of the function tabulate()
void tabulate(double (*fp)(double), double a, double b,
              double step){
    for (double x=a; x<=b; x+=step) {
        cout.precision(4);
        cout<<setw(12)<<fixed<<x<< '\t';
        cout.precision(7);
        cout<<setw(12)<<fixed<<(*fp)(x)<< endl;
    }
}
 
// The definition of the function sqr()
double sqr(double x) {
    return x * x;
}

The results:

The values of the function sqr() ([-2,2] dx=0.5)
     -2.0000       4.0000000
     -1.5000       2.2500000
     -1.0000       1.0000000
     -0.5000       0.2500000
      0.0000       0.0000000
      0.5000       0.2500000
      1.0000       1.0000000
      1.5000       2.2500000
      2.0000       4.0000000
 
The values of the function sqrt() ([0,2] dx=0.2)
      0.0000       0.0000000
      0.2000       0.4472136
      0.4000       0.6324555
      0.6000       0.7745967
      0.8000       0.8944272
      1.0000       1.0000000
      1.2000       1.0954451
      1.4000       1.1832160
      1.6000       1.2649111
      1.8000       1.3416408
      2.0000       1.4142136
II.1.3.2.6. Default arguments

In the prototype of C++ functions, certain parameters can be assigned a so-called default value. Compilers use these values if there is no argument corresponding to the given parameter when the function is called:

// prototype
long SeqSum(int n = 10, int d = 1, int a0 = 1); 
 
long SeqSum(int n, int d, int a0) {  // definition 
    long sum = 0, ai;
    for(int i = 0; i < n; i++) {
        ai = a0 + d * i;
        cout << setw(5) << ai;
        sum += ai;
    }
    return sum;
}

The function named SeqSum() creates an arithmetic sequence of n elements. The first element is a0, the difference between elements is d. The function returns the sum of the elements of the sequence.

It should be noted that parameters having a default value are placed one after another continuously from right to left, while arguments have to be provided continuously from left to right when the function is called. If a prototype is used, default values can only be provided in the prototype.

Now let's have a look at the value of the parameters after some possible calls of the function above.

Call

Parameters

n

d

a0

SeqSum()

10

1

1

SeqSum(12)

12

1

1

SeqSum(12,3)

12

3

1

SeqSum(12, 3, 7)

12

3

7'

Default arguments make functions more flexible. For example, if a function is often called with the same argument list, it is worth making frequently used parameters default and calling the function without those arguments.

II.1.3.2.7. Variable length argument list

There are cases when the number and the type of the parameters of a function are not known in advance. In the declaration of these functions, the parameter list ends with an ellipsis (three dots):

int printf(const char * format, ... );

The ellipsis indicates to the compiler that further arguments can be expected. The function printf () (cstdio) has to be called by at least one argument but this can be followed by other arguments the number of which is not specified:

char name[] = "Bjarne Stroustrup";
double a=12.3, b=23.4, c=a+b;
printf("C++ language\n");
printf("Name: %s \n", name);
printf("Result: %5.3f + %5.3f = %8.4f\n", a, b, c);

The function printf () processes the next argument on the basis of format.

When functions with similar declaration are called, compilers only check the type of the parameters and that of the arguments until they reach the "..." element. After that, passing the arguments to the function is carried out on the basis of the type of the given (or eventually converted) arguments.

C++ makes it possible to use the ellipsis in our own functions, i.e. variable length argument lists. In order that the value of the passed arguments be found in the memory space containing the parameters, the first parameter always has to be provided.

C++ standard contains some macros with the help of which a variable length argument list can be processed. The macros defined in the header file cstdarg use pointers of type va_list to access arguments:

type va_arg(va_list ap, type);

returns the following element in the argument list.

void va_end(va_list ap);

cleaning up after all arguments are processed.

void va_start(va_list ap, fixparam);

initializes the pointer used to access the arguments.

As an example, let's look at the function Average(), which calculates the average of any number of values of type double. The number of the elements has to be provided as the first argument.

#include<iostream>
#include<cstdarg>
using namespace std;
 
double Average(int num, ... ) {
    va_list numbers; 
    // passing through the first (num) arguments
    va_start(numbers, num); 
    double sum = 0;
    for(int i = 0; i < num; ++i ) {
        // accessing the double arguments
        sum += va_arg(numbers, double);  
    }
    va_end(numbers);
    return (sum/num);
}
 
int main() {
    double avg = Average(7,1.2,2.3,3.4,4.5,5.6,6.7,7.8);
    cout << avg << endl;
}
II.1.3.2.8. Parameters and return value of the main() function

The interesting thing about the main () function is not only that program execution starts here but also that it can have many parameterizing options:

int main( )
 
int main( int argc) ( )
 
int main( int argc, char *argv[]) ( )

argv points to an array (a vector) of character pointers, argc gives the number of strings in the array. (The value of argc is at least 1 since argv[0] refers to the character sequence containing the name of the program.)

The interpretation of the parameter argv
Figure II.4. The interpretation of the parameter argv


The return value of main (), which is of an int type according to the standard, can be provided within the main () function in a return statement or as an argument of the Standard Library function exit () that can be placed anywhere in a program. The header file cstdlib contains standard constants:

#define EXIT_SUCCESS 0
#define EXIT_FAILURE 1

These can be used as an exit code signalling whether the execution of the program was successful or not.

The main () function differs from other normal C++ functions in many ways: it cannot be declared as a static or inline function, it is not obligatory to use return in it and it cannot be overloaded. On the basis of the recommendations of C++ standards, it cannot be called within a program and its address cannot be obtained.

The following command1.cpp program prints out the number of arguments and then all of its arguments:

#include <iostream> 
using namespace std;
  
int main(int argc, char *argv[]) { 
    cout << "number of arguments: " << argc << endl;  
    for (int narg=0; narg < argc; narg++)   
        cout << narg << " " << argv[narg] << endl; 
    return 0; 
}

Command line arguments can be provided in the console window

C:\C++Book>command1 first 2nd third

or in the Visual Studio development environment (Figure II.5, “Providing command line arguments”).

Providing command line arguments
Figure II.5. Providing command line arguments


The results of executing command1.exe:

In a console window

In a development environment

C:\C++Book>command1 first 2nd third
number of arguments: 4
0 command1
1 first
2 2nd
3 third
 
C:\C++Book>
C:\C++Book>command1 first 2nd third
number of arguments: 4
0 C:\C++Book\command1.exe
1 first
2 2nd
3 third
 
C:\C++Book>

The next example code (command2.cpp) demonstrates a solution easy to use when creating utility software to test whether input arguments are well provided or not. The program only starts if it is called with two arguments. Otherwise, it sends an error message and prints out how to start the program from the command line properly.

#include <iostream>
#include <cstdlib>
using namespace std;
 
int main(int argc, char *argv[]) {
    if (argc !=3 ) {
        cerr<<"Wrong number of parameters!"<<endl;
        cerr<<"Usage: command2 arg1 arg2"<<endl;
        return EXIT_FAILURE;
    }
    cout<<"Correct number of parameters:"<<endl;
    cout<<"1. argument: "<<argv[1]<<endl;
    cout<<"2. argument: "<<argv[2]<<endl;
    return EXIT_SUCCESS;
}

The results of the execution of command2.exe:

Wrong number of parameters: command2

Correct number of parameters: command2 alfa beta

Wrong number of parameters!
Usage: command2 arg1 arg2
 
Correct number of parameters:
1. argument: alfa
2. argument: beta

II.1.4. Programming with functions

Procedural programming requires that separate functions be created for each subtask and that these functions could be tested separately. Solving the original task is achieved by calling these already tested functions. It is important to arrange communication between these functions. As we have already seen in some functions, parameterizing and return values represent this communication. However, in the case of functions being in a logical relationship with one another, there are more possibilities that can even increase the efficiency of the solution.

As an example, let's have a look at a task everybody knows: determining the area and the perimeter of a triangle. Calculating the area is carried out with Heron's formula , the prerequisite of which is the fulfilment of triangle inequality. This states that every side of a triangle is smaller that the sum of the two other sides.

Calculating the area of a triangle
Figure II.6. Calculating the area of a triangle


The solution consisting of only the main () function can be realised easily even if we have read only the first some chapters of this book:

#include <iostream>
#include <cmath>
using namespace std;
 
int main() {
    double a, b, c;
    do {
        cout <<"a side: "; cin>>a;
        cout <<"b side: "; cin>>b;
        cout <<"c side: "; cin>>c;
    } while(!((a<b+c) && (b<a+c) && (c<a+b)));
    double p = a + b + c; 
    double s = p/2;
    double area = sqrt(s*(s-a)*(s-b)*(s-c));
    cout << "perimeter: " << p <<endl;
    cout << "area: " << area <<endl;
}

This program is very simple but it has some drawbacks because it cannot be reused and its structure is not easy to understand.

II.1.4.1. Exchanging data between functions using global variables

The solution of the problem above can be divided into parts that are logically independent of each other, more precisely:

  • reading the three side lengths of the triangle,

  • checking Triangle inequality,

  • calculating the perimeter,

  • calculating the area

  • and printing out calculated data.

The first four of the above mentioned activities will be realized by independent functions that communicate with each other and the main() function by shared (global) variables. Global variables should be provided outside function blocks and before the functions. (We should not forget about providing the prototypes!)

#include <iostream>
#include <cmath>
using namespace std;
 
// global variables
double a, b, c;
 
// prototypes
void ReadData();
bool TriangleInequality();
double Perimeter();
double Area();
 
int main() {
    ReadData();
    cout << "perimeter: " << Perimeter()<<endl;
    cout << "area: " << Area()<<endl;
}
 
void ReadData() {
    do {
        cout <<"a side: "; cin>>a;
        cout <<"b side: "; cin>>b;
        cout <<"c side: "; cin>>c;
    } while(!TriangleInequality() );
}
 
bool TriangleInequality() {
    if ((a<b+c) && (b<a+c) && (c<a+b))
        return true;
    else
        return false;
}
 
double Perimeter() {
    return a + b + c;
}
 
double Area() {
    double s = Perimeter()/2;
    return sqrt(s*(s-a)*(s-b)*(s-c));
}

It's clear that the code has become more legible; however, it still cannot benefit from a broader use. For example, if we want to store the data of other triangles as well and to reuse them later in other tasks, we have to make sure to set appropriately the global variables a, b and c.

int main() {
    double a1, b1, c1, a2, b2, c2;
    ReadData();
    a1 = a, b1 = b, c1 = c;
    ReadData();
    a2 = a, b2 = b, c2 = c;
    a = a1, b = b1, c = c1;
    cout << "perimeter: " << Perimeter()<<endl;
    cout << "area: " << Area()<<endl;
    a = a2, b = b2, c = c2;
    cout << "perimeter: " << Perimeter()<<endl;
    cout << "area: " << Area()<<endl;
}

Therefore, the solution would be better if the data of triangles are passed to the concerned functions as arguments.

II.1.4.2. Exchanging data between functions using parameters

Now we will not detail the following solutions since we have already presented the things to know about that in the previous sections.

#include <iostream>
#include <cmath>
using namespace std;
 
void ReadData(double &a, double &b, double &c);
bool TriangleInequality(double a, double b, double c);
double Perimeter(double a, double b, double c);
double Area(double a, double b, double c);
 
int main() {
    double x, y, z;
    ReadData(x , y , z);
    cout << "perimeter: " << Perimeter(x, y, z)<<endl;
    cout << "area: " << Area(x, y, z) <<endl;
}
 
void ReadData(double &a, double &b, double &c) {
    do {
        cout <<"a side: "; cin>>a;
        cout <<"b side: "; cin>>b;
        cout <<"c side: "; cin>>c;
    } while(!TriangleInequality(a, b, c) );
}
 
bool TriangleInequality(double a, double b, double c) {
    if ((a<b+c) && (b<a+c) && (c<a+b))
        return true;
    else
        return false;
}
 
double Perimeter(double a, double b, double c) {
    return a + b + c;
}
 
double Area(double a, double b, double c) {
    double s = Perimeter(a, b, c)/2;
    return sqrt(s*(s-a)*(s-b)*(s-c));
}

Now let's see the main () function if two triangles are to be dealt with.

int main() {
    double a1, b1, c1, a2, b2, c2;
    ReadData(a1, b1, c1);
    ReadData(a2, b2, c2);
    cout << "perimeter: " << Perimeter(a1, b1, c1)<<endl;
    cout << "area: " << Area(a1, b1, c1)<<endl;
    cout << "perimeter: " << Perimeter(a2, b2, c2)<<endl;
    cout << "area: " << Area(a2, b2, c2)<<endl;
}

When the two methods are compared with respect to speed, we could observe that global variables make programs run faster (and therefore they become more efficient) since arguments are not copied to the stack before a function is called. We have also seen the drawbacks of the solution, which do strongly prevent the reusability of the program code. How could we decrease the inconvenience of using a lot of arguments and of using long argument lists?

The following solution is much better and is more recommended than using global variables. Global variables and the parameters corresponding to them have to be collected in a structure or have to be passed to functions by reference or by constant reference.

#include <iostream>
#include <cmath>
using namespace std;
 
struct triangle {
    double a, b, c;
};
 
void ReadData(triangle &h);
bool TriangleInequality(const triangle &h);
double Perimeter(const triangle &h);
double Area(const triangle &h);
 
int main() {
    triangle h1, h2;
    ReadData(h1);
    ReadData(h2);
    cout << "perimeter: " << Perimeter(h1)<<endl;
    cout << "area: " << Area(h1)<<endl;
    cout << "perimeter: " << Perimeter(h2)<<endl;
    cout << "area: " << Area(h2)<<endl;
}
 
void ReadData(triangle &h) {
    do {
        cout <<"a side: "; cin>>h.a;
        cout <<"b side: "; cin>>h.b;
        cout <<"c side: "; cin>>h.c;
    } while(!TriangleInequality(h) );
}
 
bool TriangleInequality(const triangle &h) {
    if ((h.a<h.b+h.c) && (h.b<h.a+h.c) && (h.c<h.a+h.b))
        return true;
    else
        return false;
}
 
double Perimeter(const triangle &h) {
    return h.a + h.b + h.c;
}
 
double Area(const triangle &h) {
    double s = Perimeter(h)/2;
    return sqrt(s*(s-h.a)*(s-h.b)*(s-h.c));
}

II.1.4.3. Implementing a simple menu driven program structure

Softwares with a text-based user interface can be controlled much better if it has a simple menu. To solve this problem, the menu entries are stored in a string array:

// Defintion of the menu
char * menu[] = {"\n1. Sides",
                 "2. Perimeter",
                 "3. Area",
                 "----------",
                 "0. Exit" , NULL };

Now let's transform the main () function of the previous example to handle the user’s menu selections:

int main() {
    triangle h = {3,4,5};
    char ch;
    do {
      // printing out the menu
      for (int i=0; menu[i]; i++)
          cout<<menu[i]<< endl;
    
      // Processing the answer
      cin>>ch; cin.get();
      switch (ch) {
        case '0': break;
        case '1': ReadData(h);
                  break;
        case '2': cout << "perimeter: ";
                   cout << Perimeter(h)<<endl;
                  break;
        case '3': cout << "area: " << Area(h)<<endl;
                  break;
        default:  cout<<'\a';
      }
    } while (ch != '0');
}

The window when the program runs

1. Sides
2. Perimeter
3. Area
----------
0. Exit
1
a side: 3
b side: 4
c side: 5
 
1. Sides
2. Perimeter
3. Area
----------
0. Exit
2
perimeter: 12
 
1. Sides
2. Perimeter
3. Area
----------
0. Exit
3
area: 6

II.1.4.4. Recursive functions

In mathematics, there might be tasks that can be solved by producing certain data and states in a recursive way. In this case, we define data and states by providing a start state then a general state is determined with the help of the previous states of a finite set.

Now let's have a look at some well-known recursive definitions.

        factorial:

       

        Fibonacci numbers:

       

        greatest common divisor (gcd):

       

        binomial numbers:

       

       

In programming, recursion means that an algorithm calls itself either directly (direct recursion) or via other functions it calls (indirect recursion). A classic example of recursion is calculating factorials.

On the basis of the recursive definition of calculating a factorial, 5! can be calculated in the following way:

5! = 5 * 4!
         4 * 3!
             3 * 2!
                 2 * 1! 
                     1 * 0!
                          1  = 5 * 4 * 3 * 2 * 1 * 1 = 120

The C++ function that realizes the calculating above:

unsigned long long Factorial(int n) 
{
    if (n == 0)
       return 1;
     else
       return n * Factorial(n-1);
}

Recursive functions solve problems elegantly but they are not efficient enough. Until a start state is reached, the allocated memory space (stack) can have a significant size because of multiple function calls, and the function call mechanism can take a lot of time.

That is why it is important to know that all recursive problems can be transformed into an iterative one (i. e. that uses loops), which is more difficult to elaborate but is more efficient. The non-recursive version of the function calculating factorials:

unsigned long long Factorial(int n)
{
    unsigned long long f=1;
    while (n != 0)
        f *= n--;
    return f;
}

Now, let's see how we can determine the nth element of a Fibonacci sequence.

0, 1, 1, 2, 3, 5, 8, 13, 21, ...

The following recursive rule can be used to calculate the nth element of the sequence :

 

a0 = 0

 

a1 = 1

 

an = an-1 + an-2,  n = 2 ,3, 4,…

Based on the recursive rule, we can now create the function that has completely the same structure as that of the mathematical definition:

unsigned long Fibonacci( int n ) {
   if (n<2)
      return n;
   else
      return Fibonacci(n-1) + Fibonacci(n-2);
}

Like in the preceding case, we recommend here using iterative structures in order to save time and memory:

unsigned long Fibonacci( int n ) {
   unsigned long f0 = 0, f1 = 1, f2 = n;
   while (n-- > 1) {
      f2 = f0 + f1;
      f0 = f1;
      f1 = f2;
   }
   return f2;
}

While the running time of the iterative solution increases linearly with n, that of the recursive solution increases exponentially ().

The last part of this subchapter details only the recursive solution of the problems, so we entrust our readers with the iterative solutions.

The greatest common divisors of two natural numbers can be easily determined recursively:

int Gcd(int p, int q) {
    if (q == 0)
        return p;
    else
        return Gcd(q, p % q);
}

If binomial coefficients have to be calculated, we can choose between two recursive functions:

int Binom1(int n, int k)
{
    if (k == 0 || k == n)
        return 1;
    else
        return Binom1(n-1,k-1) + Binom1(n-1,k);
}
 
int Binom2(int n, int k)
{
    if (k==0)
        return 1;
    else
        return Binom2(n, k-1) * (n-k+1) / k;
}

From the two solutions, Binom2() is more efficient.

In the last example, we calculate a determinant recursively. The essential of the solution is that the calculation of a determinant of degree N is reduced to the problem of calculating N determinants of degree (N-1). And we do that until second degree determinants are reached. The solution is elegant and easy to understand but it is not very efficient. Function calls are stored in tree structures chained into each other, which require a lot of time and memory.

For example, if we want to calculate a determinant of size 4x4, the function Determinant() is called 17 times (once for a 4x4 matrix, four times for a 3x3 matrix and for 4 times 3 matrices of size 2x2) and the number of times the function is called increases to 5·17+1 = 86 for a fifth degree determinant. (It should be noted that in the case of a 12x12 matrix, the function Determinant() is called more than 300 million times, which may require many minutes.)

#include <iostream>
#include <iomanip>
using namespace std;
 
typedef double Matrix[12][12];
 
double Determinant(Matrix m, int n);
void PrintOutMatrix(Matrix m, int n);
 
int main() {
  Matrix m2 = {{1, 2},
               {2, 3}};
 
  Matrix m3 = {{1, 2, 3},
               {2, 1, 1},
               {1, 0, 1}};
 
  Matrix m4 = {{ 2, 0, 4, 3},
               {-1, 2, 6, 1},
               {10, 3, 4,-2},
               { 2, 1, 4, 0}};
 
  PrintOutMatrix(m2,2);
  cout << "Determinant(m2) = " << Determinant(m2,2) << endl;
 
  PrintOutMatrix(m3,3);
  cout << "Determinant(m3) = " << Determinant(m3,3) << endl;
 
  PrintOutMatrix(m4,4);
  cout << "Determinant(m4) = " << Determinant(m4,4) << endl;
 
  cin.get();
  return 0;
}
 
void PrintOutMatrix(Matrix m, int n)
{
   for (int i=0; i<n; i++) {
       for (int j=0; j<n; j++) {
         cout << setw(6) << setprecision(0) << fixed;
         cout << m[i][j];
       }
       cout << endl;
   }
}
 
double Determinant(Matrix m, int n)
{
  int q;
  Matrix x;
 
  if (n==2)
    return m[0][0]*m[1][1]-m[1][0]*m[0][1];
 
  double s = 0;
  for (int k=0; k<n; k++)  // n subdeterminants
  {
        // creating submatrices
        for (int i=1; i<n; i++)    // rows
        {
           q = -1;
           for (int j=0; j<n; j++) // columns
            if (j!=k)
              x[i-1][++q] = m[i][j];
        }
        s+=(k % 2 ? -1 : 1) * m[0][k] * Determinant(x, n-1);
  }
  return s;
}

The results:

     1     2
     2     3
Determinant(m2) = -1

     1     2     3
     2     1     1
     1     0     1
Determinant(m3) = -4

     2     0     4     3
    -1     2     6     1
    10     3     4    -2
     2     1     4     0
Determinant(m4) = 144

II.2. How to use functions on a more professional level?

The previous chapter has given us enough knowledge to solve programming tasks with functions. Using functions leads to a structured code that is easier to test and to a decrease in the time spent for searching for program errors.

In the following, we will review the solutions that can make function usage more efficient, in the sense that code execution becomes faster, developing and algorithmization becomes more comfortable.

II.2.1. Inline functions

Calling a function takes more time and memory than those that result from the algorithm contained within that function. Passing parameters, passing control to the function and returning from the function all take time.

C++ compilers decrease the time spent on calling the functions marked with the keyword inline by compiling the statements of these functions into the place from where they are called. (This means that the compiler replaces function calls in a program code with a code sequence made on the basis of the definition and arguments of the given functions instead of passing arguments and control to a separate code compiled from the body of these functions.)

This solution is recommended to be used for small-sized and frequently called functions.

inline double Max(double a, double b) {
    return a > b ? a : b;
}
 
inline char LowerCase( char ch ) {
   return ((ch >= 'A' && ch <= 'Z') ? ch + ('a'-'A') : ch );
}

Contrary to macros that will be presented later in this book, the advantage of using inline functions is that arguments are processed with complete type checking when these functions is called. In the following example, one function, which finds an element in a sorted vector by binary search, is marked as inline:

#include <iostream>
using namespace std;
 
inline int BinarySearch(int vector[], int size, int key) {
    int result = -1; // not found
    int lower = 0;
    int upper = size - 1, middle;
    while (upper >= lower) {
        middle = (lower + upper) / 2;
        if (key < vector[middle])
            upper = middle - 1;
        else if (key == vector[middle]) {
            result = middle;
            break;
        }
        else
            lower = middle + 1;
    } // while
    return result;
}
 
int main() {
    int v[8] = {2, 7, 10, 11, 12, 23, 29, 30};
    cout << BinarySearch(v, 8, 23)<< endl;  // 5
    cout << BinarySearch(v, 8,  4)<< endl;  // -1
}

Some further remarks about inline functions:

  • In general, the keyword inline can be used efficiently for small-sized, not complicated functions.

  • inline functions should be placed in a header file in case they are called in more compilation units; however, they can only be integrated in a source file only once.

  • Inline functions result in a faster code but lead to a bigger compiled code and compilation takes longer.

  • The keyword inline is only a recommendation for compilers which may take it into consideration under certain circumstances (but not always!). Most compilers do not take into consideration the keyword inline in case of functions containing a loop or a recursive call.

II.2.2. Overloading (redefining) function names

At first sight, the usage of functions in a broader range seems to be impeded by strict type checking of parameters. From among value parameters, this obstacle only concerns pointers, arrays and user-defined types. The following function that returns the absolute value of the argument, can be called with any numeric arguments but it always returns a result of type int:

inline int Absolute(int x) {
    return x < 0 ? -x : x;
}

Some example function calls and the returned values:

Absolute(-7)

7

Absolute(-1.2)

1

Absolute(2.3F)

2

Absolute('I')

73

Absolute(L'\x807F')

32895

In the second and third cases, the compiler warns us about the data loss, but it creates an executable code.

In case of non-constant reference parameters, there are more strict constraints since the function ValueAssignment() has to be called with parameters among which the first two variables must be of type int:

void ValueAssignment(int & a, int & b, int c) {
     a = b + c;
     b = c;
}

The algorithm realised by a function would be more useful if the latter could be executed with parameters of different types and if the same function name could be used for that purpose. The same function name is useful because the name of a function normally reflects the activity and algorithm that that function carries out. This need is satisfied by C++ language by supporting  function name overloading.

Overloading a function means that different functions can be defined with the same name and within the same scope (the meaning of which is to be explained later) but with a different parameter list (parameter signature). (Parameter signature means the number and the type order of parameters.)

Having considered the things mentioned above, let's create two versions of the function Absolute()!

#include <iostream>
using namespace std;
 
inline int Absolute(int x) {
    return x < 0 ? -x : x;
}
 
inline double Absolute(double x) {
    return x < 0 ? -x : x;
}
 
int main() {
    cout << Absolute(-7) << endl;        // int
    cout << Absolute(-1.2) << endl;    // double
    cout << Absolute(2.3F) << endl;    // double
    cout << Absolute('I') << endl;        // int
    cout << Absolute(L'\x807F') << endl;    // int
}

Now we will not receive any message indicating a data loss from the compiler so it is the function of type double that is called with floating point values.

In that case, the function name itself does not determine unequivocally which function will be executed. The compiler matches the signature of the arguments in function calls with that of the parameters of each function having the same name. While a compiler is trying to match a function call with a function, the following cases may occur:

  • There is only one function that completely matches the call with respect to the types of its arguments - then it is that function that is chosen by the compiler.

  • There is only one function that matches the argument list of the function call when the type of the arguments has been automatically converted - it is evident in this case as well which one to choose.

  • There are not any functions that would match the argument list - the compiler sends an error message.

  • There are more functions that match the argument list to the same extent - the compiler sends an error message.

It should be noted that the return type of a function does not have any role when a compiler chooses the appropriate function variant.

If it is needed, for example if there are more functions that would match the given argument list to the same extent, programmers can help compilers choose the appropriate function by using a type-cast:

int main() {
    cout << Absolute((double)-7) << endl;    // double
    cout << Absolute(-1.2) << endl;        // double
    cout << Absolute((int)2.3F) << endl;          // int
    cout << Absolute((float)'I') << endl;    // double
    cout << Absolute(L'\x807F') << endl;     // int
}

In the following example, the function named VectorSum() has two redefined forms to calculate the sum of the elements in an array: one for arrays of type unsigned int and one for arrays of type double:

#include <iostream>
using namespace std;
 
unsigned int VectorSum(unsigned int a[], int n) {
   int sum=0;
   for (int i=0; i<n; i++)
      sum += a[i];
   return sum;
}
 
double VectorSum(double a[], int n) {
   double sum = 0;
   for (int i=0; i<n; i++)
     sum += a[i];
   return sum;
}
 
int main() {
   unsigned int vu[]={1,1,2,3,5,8,13};
   const int nu=sizeof(vu) / sizeof(vu[0]);
   cout <<"\nThe sum of the elements of the unsigned array: "
        <<VectorSum(vu, nu);
 
   double    vd[]={1.2,2.3,3.4,4.5,5.6};
   const int nd=sizeof(vd) / sizeof(vd[0]);
   cout << "\nThe sum of the elements of the double array: "
        << VectorSum(vd, nd);
}

In the first case, the compiler finds appropriate the signature of the function VectorSum(unsigned int*, int), in the second case, that of the function VectorSum(double*, int). For arrays of other types (like int and float), the compiler sends an error message, since the automatic conversion of pointers is very restricted in C++.

It should not be forgotten that operator overloading can be used in a similar way than function overloading. However, operators can only be overloaded in the case of user-defined types (struct, class), so this method will only be treated in the next part of the present book.

From the examples above, it can be clearly seen that the task of creating new function variants with new types and realising the same algorithms will become a word processing task with this method (copying blocks and replacing text). In order to avoid this, C++ compilers can be entrusted with the creation of redefined function variants with the usage of function templates.

II.2.3. Function templates

In the previous parts of this chapter, we have provided an overview of functions which make program codes safer and easier to maintain. Although functions make possible efficient and flexible code development, they have some disadvantages since all parameters have to be assigned a type. Overloading, which was presented in the previous section, helps us make functions independent of types but only to a certain extent. However, function overloading can treat only a finite number of types and also, it is not so efficient to update or to correct repeated codes in the overloaded functions.

So there should be a possibility to create a function only once and to use it as many times as possible with as many types as possible. Nowadays, most programming languages offer the method called generic programming for solving this problem.

Generic programming is a general programming model. This technique means developing a code that is independent of types. So source codes use so-called generic or parameterized types. This principle increases a lot the extent to which a code can be reused since containers and algorithms independent of types can be created with this method. C++ offers templates in order that generic programming could be realised at compilation time. However, there are languages and systems that realise generic programming at run-time (for example Java and С#).

II.2.3.1. Creating and using function templates

A template declaration starts with the keyword template, followed by the parameters of the template enclosed within the signs < and >. In most cases, these parameters are generic type names but they can contain variables as well. Generic type names should be preceded by the keyword class or typename.

When we create a function template, we should start from a working function in which the certain types should be replaced by generic types (TYPE). Afterwards, the compiler should be told which types should be replaced in the function template (template<class TYPE>). The first step to take in our function named Absolute():

inline TYPE Absolute(TYPE x) {
    return x < 0 ? -x : x;
}

Then the final version should be provided in two ways:

template <class TYPE>
inline TYPE Absolute(TYPE x) {
    return x < 0 ? -x : x;
}
 
template <typename TYPE>
inline TYPE Absolute(TYPE x) {
    return x < 0 ? -x : x;
}

From the function template that is now done, the compiler will create the needed function variant when the function is called. It should be noted that the template named Absolute() can only be used with numeric types among which the operations 'lower than' and 'changing the sign' can be interpreted.

When the function is called in a traditional way, the compiler determines, based on the type of the argument, which version of the function Absolute() it will create and compile.

cout << Absolute(123)<< endl;            // TYPE = int
cout << Absolute(123.45)<< endl;        // TYPE = double

Programmers themselves can carry out a function variant using the type-cast manually in the call:

cout << Absolute((float)123.45)<< endl;    // TYPE = float
cout << Absolute((int)123.45)<< endl;    // TYPE = int

The same result is achieved if the type name is provided within the signs < and > after the name of the function:

cout << Absolute<float>(123.45)<< endl;    // TYPE = float
cout << Absolute<int>(123.45)<< endl;    // TYPE = int

In a function template, more types can be replaced, as it can be seen in the following example:

template <typename T>
inline T Maximum(T a, T b) {
   return (a>b ? a : b);
}
 
int main() {
    int a = 12, b=23;
    float c = 7.29, d = 10.2;
 
    cout<<Maximum(a, b); // Maximum(int, int)
    cout<<Maximum(c, d); // Maximum(float, float)
  ↯ cout<<Maximum(a, c); // there is no Maximum(int, float)
    cout<<Maximum<int>(a,c);  
    // it is the function Maximum(int, int) that is called with type conversion
}

In order to use different types, more generic types should be provided in the function template header:

template <typename T1, typename T2>
inline T1 Maximum(T1 a, T2 b) {
   return (a>b ? a : b);
}
 
int main() {
    cout<<Maximum(5,4);       // Maximum(int, int)
    cout<<Maximum(5.6,4);     // Maximum(double, int)
    cout<<Maximum('A',66L);   // Maximum(char, long)
    cout<<Maximum<float, double>(5.6,4); 
                              // Maximum(float, double)
    cout<<Maximum<int, char>('A',66L);   
                              // Maximum(int, char)
}

II.2.3.2. Function template instantiation

When a C++ compiler first encounters a function template call, it creates an instance of the function with the given type(s). Each function instance is a specialised version of the function template with the given types. In the compiled program code, exactly one function instance belongs to each used type. The calls above were examples of the so-called implicit instantiation.

A function template can be instantiated in an explicit way as well if concrete types are provided in the template line containing the header of the function:

template inline int Absolute<int>(int);
template inline float Absolute(float);
template inline double Maximum(double, long);
template inline char Maximum<char, float>(char, float);

It should be noted that only one explicit instantiation can figure in the source code for (a) given type(s).

Since a C++ compiler processes function templates at compilation time, templates have to be available in the form of a source code in order that function variants could be created. It is recommended to place function templates in a header file to make sure that the header file can only be included once in C++ modules.

II.2.3.3. Function template specialization

There are cases when it is not precisely the algorithm defined in the generic function template that has to be used for some types or type groups. In this case, the function template itself has to be overloaded (specialized) – explicit specialization.

The function template Swap() of the following example code exchanges the values of the passed arguments. In the case of pointers, the specialised version does not exchange pointers but the referenced values. The second specialised version can be used to swap strictly C-styled strings.

#include <iostream>
#include <cstring>
using namespace std;
 
// The basic template
template< typename T > void Swap(T& a, T& b) {
   T c(a);
   a = b;
   b = c;
}
 
// Template specialised for pointers
template<typename T> void Swap(T* a, T* b) {
   T c(*a);
   *a = *b;
   *b = c;
}
 
// Template specialised for C strings
template<> void Swap(char *a, char *b) {
   char buffer[123];
   strcpy(buffer, a);
   strcpy(a, b);
   strcpy(b, buffer);
}
 
int main() {
    int a=12, b=23;
    Swap(a, b);
    cout << a << "," << b << endl;     // 23,12
 
    Swap(&a, &b);
    cout << a << "," << b << endl;        // 12,23
 
    char *px = new char[32]; strcpy(px, "First");
    char *py = new char[32]; strcpy(py, "Second");
    Swap(px, py);
    cout << px << ", " << py << endl;    // Second, First
    delete px;
    delete py;
}

From among the different specialised versions, compilers first choose always the most specialised (concrete) template to compile the function call.

II.2.3.4. Some further function template examples

In order to sum up function templates, let's see some more complex examples. Let's start with making a generic version of the function VectorSum() of Section II.2.2, “Overloading (redefining) function names”. The function template can determine the sum of the elements of any one-dimensional numeric array:

#include <iostream>
using namespace std;
 
template<class TYPE>
TYPE VectorSum(TYPE a[], int n) {
   TYPE sum = 0;
   for (int i=0; i<n; i++)
      sum += a[i];
   return sum;
}
 
int main() {
   int       ai[]={1,1,2,3,5,8,13};
   const int ni=sizeof(ai) / sizeof(ai[0]);
   cout << "\nThe sum of the elements of the int array: "
        <<VectorSum(ai,ni);
 
   double    ad[]={1.2,2.3,3.4,4.5,5.6};
   const int nd=sizeof(ad) / sizeof(ad[0]);
   cout << "\nThe sum of the elements of the double array: "
        <<VectorSum(ad,nd);
 
   float     af[]={3, 2, 4, 5};
   const int nf=sizeof(af) / sizeof(af[0]);
   cout << "\nThe sum of the elements of the  float  array: "
        <<VectorSum(af,nf);
 
   long      al[]={1223, 19800729, 2004102};
   const int nl=sizeof(al) / sizeof(al[0]);
   cout << "\nThe sum of the elements of the  long   array: "
        <<VectorSum(al,nl);
}

The function template Sort() sorts the elements of any one-dimensional array of type Typ and of size Size in an ascending order. The function moves the elements within the array and the result is stored in the same array as well. Since this declaration contains an integer parameter (Size) besides the type parameter (Typ), the function has to be called in the extended version:

        Sort<int, size>(array);

The example code also calls the Swap() template from the the template Sort():

#include <iostream>
using namespace std;
 
template< typename T > void Swap(T& a, T& b) {
   T c(a);
   a = b;
   b = c;
}
 
template<class Typ, int Size> void Sort(Typ vector[]) {
   int j;
   for(int i = 1; i < Size; i++){
      j = i;
      while(0 < j && vector[j] < vector[j-1]){
          Swap<Typ>(vector[j], vector[j-1]);
          j--;
      }
   }
}
 
int main() {
    const int size = 6;
    int array[size] = {12, 29, 23, 2, 10, 7};
    Sort<int, size>(array);
    for (int i = 0; i < size; i++)
        cout << array[i] << " ";
    cout << endl;
}

Our last example also prints out the different variants of the function Function() used by the compiler when these functions are called. From the specialised variants, it is the generic template that is activated which prints out the elements of the complete function call with the help of the template PrintOut(). The name of the types is returned by the member name () of the object returned by the typeid operator. The function template Character() is used to print out characters enclosed in apostrophes. This little example code presents some ways function templates can be used.

#include <iostream>
#include <sstream>
#include <string>
#include <typeinfo>
#include <cctype>
using namespace std;
 
template <typename T> T Character(T a) { 
    return a; 
}
 
string Character(char c) {
    stringstream ss;
    if (isprint((unsigned char)c)) 
        ss << "'" << c << "'";
    else 
        ss << (int)c;
    return ss.str();
}
 
template <typename TA, typename TB> 
void PrintOut(TA a, TB b){
    cout << "Function<" << typeid(TA).name() << "," 
         << typeid(TB).name();
    cout << ">(" << Character(a) << ", " << Character(b)
         << ")\n";
}
 
template <typename TA, typename TB> 
void Function(TA a = 0, TB b = 0){
    PrintOut<TA, TB>(a,b);
}
 
template <typename TA> 
void Function(TA a = 0, double b = 0) {
    Function<TA, double>(a, b);
}
 
void Function(int a = 12, int b = 23) {
    Function<int, int>(a, b);
}
 
int main() {
        Function(1, 'c');
        Function(1.5); 
        Function<int>(); 
        Function<int, char>(); 
        Function('A', 'X');
        Function();
}

The results:

Function<int,char>(1, 'c')
Function<double,double>(1.5, 0)
Function<int,double>(0, 0)
Function<int,char>(0, 0)
Function<char,char>('A', 'X')
Function<int,int>(12, 23)

II.3. Namespaces and storage classes

In C++, it is easy to create an unstructured source code. In order that a bigger program code remains structured, we have to respect many rules that the syntax of C++ does not require; however, these rules have to be observed for usability purposes. Most of these rules to be applied are the direct consequence of modular programming.

In order to keep code visibility efficiently, we should divide a program in many parts which have independent functionality and which are therefore realized (implemented) in different source files or modules (.cpp). The relation between modules is ensured by interface (declaration) files (.h). In each module, the principle of data hiding should always be respected: this means that from the variables and functions defined in each module, we only share with the others those that are really necessary while hiding other definitions from the outside.

The aim of the second part of the present book is to present the C++ tools that aim at implementing well-structured modular programming. The most important element of that is constituted by the already presented functions; however, but other solutions are also needed:

  • Storage classes determine explicitly for every C++ variable its lifetime, accessibility and the place of memory where it is stored. They also determine the visibility of functions from other functions.

  • Namespaces ensure that the identifiers used in programs consisting of more modules be used securely.

II.3.1. Storage classes of variables

When a variable is created in a C++ code, its storage class can also be defined besides its type. A storage class

  • defines the lifetime or storage duration of a variable, that is the time when it is created and deleted from memory,

  • determines the place from where the name of a variable can be accessed directly – visibility, scope – and also determines which name designates which variable – linkage.

As it is already known, variables can be used after they have been declared in their respective compilation unit (module). (We should not forget that a definition is also a declaration!) We mean by compilation units all C++ source files (.cpp) together with their integrated (included) header files.

A storage class (auto, register, static, extern) can be assigned to variables when they are defined. If it lacks from the definition, compilers assign them automatically a storage class on the basis of the place where the definition is placed.

The following example that converts an integer number into a hexadecimal string demonstrates the usage of storage classes:

#include <iostream>
using namespace std;
 
static int bufferpointer;  // module level definition
 
// n and first are block level definitions
void IntToHex(register unsigned n, auto bool first = true) {
    extern char buffer[];  // program level declaration
auto int digit;     // block level definition
// hex is block level definition
    static const char hex[] = "0123456789ABCDEF";   
    if (first)
        bufferpointer=0;
    if ((digit = n / 16) != 0)
        IntToHex(digit, false);   // recursive call
    buffer[bufferpointer++] = hex[n % 16];
 }
 
extern char buffer[];  // program level declaration
 
int main() {
    auto unsigned n;   // block level definition
    for (n=0; n<123456; n+=9876) {
        IntToHex(n);
        buffer[bufferpointer]=0;  // closing the string
        cout << buffer << endl;
    }
}
 
extern char buffer [123] = {0};   // program level definition

In blocks, the default storage class is auto and, as it can be seen in the example, we stroke that word through because we practically never use that modifier. The storage class of variables defined outside function blocks are external by default; however, the keyword extern only has to be provided when we declare (not define!) external variables.

II.3.1.1. Accessibility (scope) and linkage of variables

The notion of storage class is strongly related to that of variable accessibility and scope. Every identifier is visible only within its scope. There are three basic scope types: block level (local), file level and program level (global) scope (Figure II.7, “Variable scopes”).

The accessibility of variables is determined by default by the place where they are defined in the source code. We can only create block level and program level variables in that way. However, we can also assign variables a storage class directly.

The notion of scope is similar to that of linkage but it is not completely equal to that. The same identifiers can be used to designate different variables in different scopes. However, the identifiers declared in different scopes or the identifiers declared more than once in the same scope can reference the same variable because of linkage. In C++, there are three types of linkage: internal, external and no linkage.

In a C++ source code, variables can have one of the following scopes:

block level

A variable of this type is only visible in the block (function block) where it has been defined so its accessibility is local, that is its accessibility is determined by the place where it is defined. When program control reaches the block closing curly bracket, a block level identifier is not going to be accessible any more. If a variable is defined on a block level without the storage classes extern and static, it does not have any linkage.

file level

A file level variable is only visible in the module containing its declaration. These variables can only be referenced from the functions of the module but not from other modules. Identifiers having file level scope are those that are declared outside the functions of the module and that are declared with internal linkage using the static storage class. (It should be noted that the C++ standard declared deprecated the usage of the keyword static for that purpose, and it recommends using anonymous namespaces for selecting internal linkage variables).

program level

A program level variable is accessible from the functions of all the modules (all compilation units) of a program. Global variables that are declared outside the functions of a module (that is with external linkage) have program level scope. For that purpose, global variables should be declared without a storage class or with the extern storage class.

File and program level scopes can be divided furthermore by namespaces presented later which introduce the notion of qualified identifiers.

Variable scopes
Figure II.7. Variable scopes


II.3.1.2. Lifetime of variables

A lifetime is a period of program execution where the given variable exists. On the basis of lifetime, identifiers can be divided into three groups: names of static, automatic and dynamic lifetime.

static lifetime

Identifiers having a static or extern storage class have a static lifetime. The memory space allocated for static variables (and the data contained within it) remains until the end of program execution. A variable of this type is initialised once: when the program is launched.

automatic lifetime

Within blocks, variables defined without the static storage class and the parameters of functions have automatic (local) lifetime. The memory space of automatic lifetime variables (and the data contained within it) exist only in the block where they are defined. Variables with local lifetime are created on a new segment of memory each time their block is entered, and they are deleted when the block is exited (their content becomes lost). If a variable of this type is assigned an initial value, this initialization takes place every time the variable is created.

dynamic lifetime

Independently of their storage classes, memory blocks that are allocated by the operator new and deallocated by the operator delete have dynamic lifetime.

II.3.1.3. Storage classes of block level variables

Variables that are defined within a function/program block or in the parameter list of functions are automatic (auto) by default. The keyword auto is not used in general. In addition, in the latest C++11 standard, this keyword has a new interpretation. Block level variables are often called local variables.

II.3.1.3.1. Automatic variables

Automatic variables are created when control is passed to their block and they are deleted when that block is exited.

As we have already said in previous chapters, compound statements (blocks) can be nested into each other. Each block can contain definitions (declarations) and statements in any order:

{
   definitions, declarations and
   statements
}

An automatic variable becomes temporarily inaccessible if another variable with the same name is defined in an inner block. So it is hidden until the end of that inner block and there is not any possibility to access the hidden (existing) variable:

int main()
{
   double limit = 123.729;
   // the value of the double type variable limit is 123.729 
   {
      long limit = 41002L;
      // the value of the long type variable limit is 41002L 
   }
   // the value of the double type variable limit is 123.729
}

Since automatic variables cease to exist when the function containing them is exited, it is forbidden to create a function that returns the address or reference to a local variable. If we want to allocate space within a function for a variable that is used outside the function, then we should allocate that memory space dynamically.

In C++, loop variables defined in the header of a for loop are also automatic:

for (int i=12; i<23; i+=2)
   cout << i << endl;
↯ cout << i << endl; // compilation error: i is not defined

The initialization of automatic variables always takes place if control is passed to their block. However, it is only the variables defined with an initial value that receive an initial value. (The value of the other variables is undefined!).

II.3.1.3.2. The register storage class

The register storage class can only be used for automatic local variables and function parameters. If we provide the keyword register for a variable, we ask the compiler to create that variable in the register of the processor, if possible. In that way, the compiled code may be executed faster. If there is no free register for that purpose, the variable is created as an automatic variable. However, register variables are created in memory if the address operator (&) is used with them.

It should be noted that C++ compilers are able to optimize the compiled code, which results in general in a more efficient program code than if the keyword register was used. So it is not surely more efficient to use register storage classes.

The following Swap() function can be executed much faster than its previously presented version. (We use the word "can" because it is not known in advance how much register storage class specification takes into consideration the compiler.)

void Swap(register int &a, register int &b) {
   register int c = a;
                a = b;
                b = c;
}

The function StrToLong() of the following example converts a decimal number stored in a string into a value of type long:

long StrToLong(const string& str)
{
    register int index = 0;
    register long sign = 1, num = 0;
    // leaving out leading blanks
    for(index=0; index < str.size() 
                 && isspace(str[index]); ++index);
    // sign?
    if( index < str.size()) {
        if( str[index] == '+' ) { sign =  1; ++index; }
        if( str[index] == '-' ) { sign = -1; ++index; }
    }
    // digits
    for(; index < str.size() && isdigit(str[index]); ++index)
        num = num * 10 + (str[index] - '0');
    return sign * num;
}

The lifetime, visibility of register variables and the method of their initialization equal with those of variables having an automatic storage class.

II.3.1.3.3. Local variables with static lifetime

By using static storage classes instead of auto, we can create block level variables with static lifetime. The visibility of a variable like this is restricted to the block containing its definition; however, the variable exists from the time the program is launched until the program is exited. Since the initialization of static local variables takes place only once (when they are created), these variables keep their value even after the block is exited (between function calls).

The function Fibo() of the following example determines the next element of a Fibonacci sequence from the 3th element. Within the function, the static local variables a0 and a1 keep the value of the last two elements even after the block is exited.

#include <iostream>
using namespace std;
 
unsigned Fibo() {
    static unsigned a0 = 0, a1 = 1;
    unsigned a2 = a0 + a1;
    a0 = a1;
    a1 = a2;
    return a2;
}
 
int main() {
    for (register int i=2; i<10; i++)
        cout << Fibo() << endl;
}

II.3.1.4. Storage classes of file level variables

Variables defined outside functions automatically have extern, i.e. program level scope. When software is developed from many modules (source files), it is not sufficient to use global variables in order that the principles of modular programming be observed. We might also need variables defined on module level: the access of these variables can be restricted to the source file (data hiding). This can be achieved by assigning extern variables a static storage class. If constants are defined outside functions, they have also internal linkage by default.

In the following program generating a Fibonacci sequence, the variables a0 and a1 were defined on a module level in order that their content remain accessible from another function (FiboInit()).

#include <iostream>
using namespace std;
 
const unsigned first = 0, second = 1;
static unsigned a0 = first, a1 = second;
 
void FiboInit() {
    a0 = first;
    a1 = second;
}
 
unsigned Fibo() {
    unsigned register a2 = a0 + a1;
    a0 = a1;
    a1 = a2;
    return a2;
}
 
int main() {
    for (register int i=2; i<5; i++)
        cout << Fibo() << endl;
    FiboInit();
    for (register int i=2; i<8; i++)
        cout << Fibo() << endl;
}

The initialization of static variables takes place only once: when the program is launched. In case we do not provide an initial value for them in their definition, then the compiler initializes these variables automatically to 0 (by filling up its memory space with 0 bytes). In C++, static variables can be initialized by any expression.

C++ standards recommend using anonymous namespaces instead of static external variables (see later).

II.3.1.5. Storage classes of program level variables

Variables that are defined outside functions and to which a storage class is not assigned have an extern storage class by default. (Of course, the keyword extern can be used here, even if that is the default case.)

The lifetime of extern (global) variables begins with the launching and ends with the end of the program. However, there might be problems with visibility. A variable defined in a given module can only be accessed from another module, if the latter contains the declaration of that variable (for example by including its declaration file).

When using program level global variables, we have to pay attention whether we define or declare them. If a variable is used without a storage class specification outside functions, this means defining this variable (independently of the fact that it is provided an explicit initial value or not). On the contrary, assigning them extern explicitly can have two consequences: without initial value, they are declared , with initial value, they are defined . The following examples demonstrate what we have said so far:

Same definitions (only one of them can be used)

Declarations

double sum;

double sum = 0;

extern double sum = 0;

extern double sum;

 

 

int vector[12];

extern int vector[12] = {0};

extern int vector[];

extern int vector[12];

extern const int size = 7;

extern const int size;

It should be kept in mind that global variables can be declared anywhere in a program but the effects of these declarations start from the place of the declaration until the end of the given scope (block or module). It is a frequent solution to store declarations in header files for each module and to include them at the beginning of every source file.

The initialization of global variables takes place only once when the program starts. In case we do not provide any initial value to them, then the compiler initializes these variables automatically to 0. In C++, global variables can be initialised by any expression.

It should be kept in mind that global variables should only be used with caution and only if they are really needed. Even in the case of a bigger program, only some central extern variables are allowed.

II.3.2. Storage classes of functions

Functions can only have two storage classes. Functions cannot be defined in a block; therefore a function cannot be created within another function!

The storage class of a function determines its accessibility. Therefore functions without a storage class or those defined with the extern storage class have program level scope. On the contrary, functions having a static storage class have file level scope.

We mean by the definition of a function the function itself, whereas a declaration of a function means its prototype. The following table summarizes the definitions and declarations of the function calculating the geometric mean of two numbers, in the case of both storage class.

Definitions (only one of them can be used)

Prototypes

double GeomMean(double a, double b) 
{
    return sqrt(a*b);
}
 
extern double GeomMean(double a, double b)
{
    return sqrt(a*b);
}
double GeomMean(double, double); 
extern double GeomMean(double, double);
 
 
 
 
 
 
 
static double GeomMean(double a, double b) 
{
    return sqrt(a*b);
}
static double GeomMean(double, double);
 
 
 

Prototypes can be placed anywhere in a program code but their effects start from the place of declaration and ends at the end of the given scope (block or module). It is common to store prototypes for each module in separate header files that have to be included at the beginning of each concerned source file.

File and program level declarations and definitions are demonstrated by the following example code made up of two modules. The mathematical module provides the program with the mathematical constants, an "intelligent" Random() function and a function Sin() capable of calculating in degrees.

// MainModule.cpp

 

// MatModule.cpp

#include <iostream>
using namespace std;
 
extern int Random(int);
double Sin(double);
 
int main() {
    for (int i=0; i<5; i++)
        cout << Random(12)<< endl;
    cout << Sin(45) << endl;
    extern const double e;
    cout << e << endl;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
#include <ctime>
#include <cstdlib>
#include <cmath>
using namespace std;
 
extern const double pi = asin(1)*2;
extern const double e = exp(1);
 
static double Degree2Radian(double);
static bool firstrnd = true;
 
int Random(int n) {
    if (firstrnd) {
        srand((unsigned)time(0));
        firstrnd = false;
    }
    return rand() % n;
}
 
double Sin(double degree) {
    return sin(Degree2Radian(degree));
}
 
static double Degree2Radian(double degree) {
   return degree/180*pi;
}

II.3.2.1. Accessing the compiled C functions from within C++ source

C++ compilers complete the name we have given for a function in the object code with the letters of the types from the parameter list of the function. This solution named C++ linkage makes possible the possibilities presented earlier in this chapter (overloading, function template).

When C++ was born, C language had already existed for nearly ten years. It was rightful to claim that the algorithms of compiled object modules and Library functions developed for C programs would also be accessible from C++ programs. (Programs in C source code could therefore be implemented in C++ with slight modifications.) C compilers place function names without any modification in the object code (C linkage), so they are not accessible by the C++ declarations presented above.

To handle this problem, C++ contains the type modifier extern "C". If a function sqrt() coded in C is intended to be used from C++ source code, it should be declared as

extern "C" double sqrt(double a);

This modifier makes C++ compilers to use the habitual C name generation for the function sqrt(). As a consequence, C functions cannot be redefined (overloaded)!

In case more C functions are needed to be accessed from a C++ code, these functions should be grouped under one extern "C" :

extern "C" {
   double sin(double a);
   double cos(double a);
}

If the descriptions of the functions of a C library are contained in a separated declaration file, it should be worth the following method:

extern "C" {
   #include <rs232.h>
}

We should also use the extern "C" declaration if functions written and compiled in C++ are intended to be accessed from a C program code.

It is also worth mentioning here that the main () function also has extern "C" linkage.

II.3.3. Modular programs in C++

The structure of the example code of the preceding part becomes more structured if we create for the MatModule.cpp file a header file named MatModule.h that describes the interface of the module. Using constants becomes also simpler if they are placed in the file level scope (static). According to what has been said so far, the content of the MatModule.h header file:

// MatModule.h
#ifndef _MATMODULE_H_
#define _MATMODULE_H_
 
    #include <cmath>
    using namespace std;
 
    // file level definitions/declarations
    const double pi = asin(1)*2;
    const double e = exp(1);
    static double Degree2Radian(double);
 
    // program level declarations
    int Random(int n);
    double Sin(double degree);
 
#endif

Lines with green and bold characters contain preprocessing statements that guarantee that the content of the MatModule.h header file would be included exactly once in the module containing the line

#include "MatModule.h"

(The Section II.4, “Preprocessor directives of C++” provides readers with more information on the usage of preprocessors.)

Including the header file results in modifying the C++ modules:

// MainModule.cpp

 

// MatModule.cpp

#include <iostream>
using namespace std;
#include "MatModul.h"
 
int main() {
    for (int i=0; i<5; i++)
       cout << Random(12)<< endl;
    cout << Sin(45) << endl;
    cout << e << endl;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
#include <ctime>
#include <cstdlib>
using namespace std;
#include "MatModul.h"
 
static bool firstrnd = true;
 
int Random(int n) {
    if (firstrnd) {
        srand((unsigned)time(0));
        firstrnd = false;
    }
    return rand() % n;
}
 
double Sin(double degree) {
    return sin(Degree2Radian(degree));
}
 
static double Degree2Radian(double degree) {
   return degree/180*pi;
}

C++ compilers compile each module separately and create separate object modules for them because all the identifiers of each module have to be known in advance by compilers. This can be realized by placing declarations/prototypes and definitions adequately. After compilation, it is the task of the linker software to build one executable program from the object modules by resolving extern linkages from the object modules themselves or from standard libraries.

Integrated development environments automatize compilation and linking by creating projects. The only task of programmers is to add to the project the already existing C++ source files. In general, if we use the Build command from the menu, the whole compilation process takes place.

The GNU C++ compiler can be activated for compilation and for linking with the following command line command. It also makes the compiler create the Project.exe executable file:

g++ -o Project MainModul.cpp MatModul.cpp

II.3.4. Namespaces

Names (identifiers) used in the program are stored by C++ compilers in different places (on the basis of how they are used) that are called namespaces. Within a given namespace, stored names have to be unique but there may be identical names in different namespaces. Two identical names that are situated in the same scope but not in the same namespace designate different identifiers. C++ compilers differentiate between the following namespaces:

II.3.4.1. The default namespaces of C++ and the scope operator

In C language, variables and functions defined on the file level (as well as Standard library functions) can be found in the same common namespace. C++ enclose Standard library elements in the namespace called std whereas the other elements defined on a file level are contained within the global namespace.

In order to be able to refer the elements of a namespace, we have to know how to use the scope operator (::). We will talk more about the scope operator later in that book, so now we only show examples for two of its possible usages.

With the help of :: operator, we can refer names having file and program level scope (that is identifiers of the global namespace) from any block of a program.

#include <iostream>
using namespace std;
 
long a = 12;
static int b = 23;
 
int main() {
   double a = 3.14159265;
   double b = 2.71828182;
   {
      long a = 7, b;
      b = a * (::a + ::b);   
      // the value of b is 7*(12+23) = 245
      ::a = 7;
      ::b = 29;
   }
}

We also use the scope operator to access directly names defined in the namespace std :

#include <iostream>
int main() {
   std::cout<<"C++ language"<<std::endl;
   std::cin.get();
}

II.3.4.2. Creating and using user-defined namespaces

Bigger C++ source codes are in general developed by numerous programmers. The functionalities of C++ presented so far are not enough to avoid collision or conflict of global (program level) names. Introducing namespaces having a user-defined name gives a good solution for these problems.

II.3.4.2.1. Creating namespaces

In source files, namespaces can be created either outside function blocks or in header files. In case a compiler find more namespaces with the same name, it unifies their contents, thus it extends the namespace defined earlier. To select a namespace, we have to use the keyword namespace:

namespace myOwnNamespace {
   definitions and declarations
}

When planning program level namespaces, it is worth differentiating the namespaces containing definitions from those which can be placed in header files and which contain only declarations.

#include <iostream>
#include <string>
 
// declarations
namespace nsexample {
    extern int value;
    extern std::string name;
    void ReadData(int & a, std::string & s);
    void PrintData();
}
 
// definitions
namespace nsexample {
    int value;
    std::string name;
    void PrintData() {
        std::cout << name << " = " << value << std::endl;
    }
}
 
void nsexample::ReadData(int & a, std::string & s) {
    std::cout << "name:   "; getline(std::cin, s);
    std::cout << "value: "; std::cin >> a;
}

If a function declared in a namespace is created outside that namespace, the name of the function has to be qualified by the name of its namespace (in our example nsexample::).

II.3.4.2.2. Accessing the identifiers of a namespace

The identifiers provided in the namespace called nsexample can be accessed from every module to which the version of that namespace containing the declarations is included. Identifiers of a namespace can be accessed in many ways.

Directly by using the scope operator:

int main() {
    nsexample::name = "Sum";
    nsexample::value = 123;
    nsexample::PrintData();
    nsexample::ReadData(nsexample::value, nsexample::name);
    nsexample::PrintData();
    std::cin.get();
}

Or by using the directive using namespace :

It is of course more comfortable to make accessible all names for the whole program with the directive using namespace:

using namespace nsexample;
int main() {
    name = "Sum";
    value = 123;
    PrintData();
    ReadData(value, name);
    PrintData();
    std::cin.get();
}

Or by providing using -declarations:

With the help of using-declarations, we make available only the necessary elements from those of the namespace. This is the solution to choose if there is a name conflict between certain identifiers of a namespace.

int PrintData() {
    std::cout << "Name conflict" << std::endl;
    return 1;
}
 
int main() {
    using nsexample::name;
    using nsexample::value;
    using nsexample::ReadData;
    name = "Sum";
    value = 123;
    nsexample::PrintData();
    ReadData(value, name);
    PrintData();
    std::cin.get();
}
II.3.4.2.3. Nested namespaces, namespace aliases

C++ makes it possible to create new namespaces within namespaces, that is to nest them in each other (nested namespaces). This procedure makes it possible to organize global names into a structured system. As an example, let's see the namespace called Project in which we define separate functions for every processor family!

namespace Project {
    typedef unsigned char byte;
    typedef unsigned short word;
 
    namespace Intel {
        word ToWord(byte lo, byte hi) {
            return lo + (hi<<8);
        }
    }
 
    namespace Motorola {
        word ToWord(byte lo, byte hi) {
            return hi + (lo<<8);
        }
    }
}

The elements of the namespace Project can be accessed in many ways:

using namespace Project::Intel;
int main() {
    cout << hex;
    cout << ToWord(0xab,0x12)<< endl;             // 12ab
}
// -------------------------------------------------------
int main() {
    using Project::Motorola::ToWord;
    cout << hex;
    cout << ToWord(0xab,0x12)<< endl;            // ab12
}
// -------------------------------------------------------
using namespace Project;
int main() {
    cout << hex;
    cout << Intel::ToWord(0xab,0x12)<< endl;     // 12ab
    cout << Motorola::ToWord(0xab,0x12)<< endl;  // ab12
}
// -------------------------------------------------------
int main() {
    cout<<hex;
    cout<<Project::Intel::ToWord(0xab,0x12)<< endl;   // 12ab
    cout<<Project::Motorola::ToWord(0xab,0x12)<<endl; // ab12
}

Accessing the elements of complex and multiple nested namespaces with long complex names (the elements of which are separated from each other by the namespace operator) makes program codes less legible. In order to solve this problem, C++ introduced the notion of namespace aliases:

namespace alias =externalNS::internalNS … ::most_internal_NS;

If we use aliases, the example above becomes much simpler:

int main() {
    namespace ProcI = Project::Intel;
    namespace ProcM = Project::Motorola;
    cout << hex;
    cout << ProcI::ToWord(0xab,0x12)<< endl;     // 12ab
    cout << ProcM::ToWord(0xab,0x12)<< endl;     // ab12
}
II.3.4.2.4. Anonymous namespaces

C++ standards do not support the usage of the keyword static either in file level scopes or in namespaces. In module level scopes, we can create variables and functions in anonymous namespaces that are not accessible from outside of the compilation unit. However, the identifiers of anonymous namespaces can be used without restrictions within a module.

Within the same module, many anonymous namespaces can be created:

#include <iostream>
#include <string>
using namespace std;
 
namespace {
    string password;
}
 
namespace {
    bool ChangePassword() {
        bool success = false;
        string old, new1, new2;
        cout << "Previous password: "; getline(cin, old);
        if (password == old) {
            cout << "New Password: "; getline(cin, new1);
            cout << "New Password: "; getline(cin, new2);
            if (new1==new2) {
                password = new1;
                success = true;
            }
        }
        return success;
    } // ChangePassword()
}
 
int main() {
    password = "qwerty";
    if (ChangePassword())
         cout << "Successful password change!" << endl;
    else
         cout << "Not a successful password change!" << endl;
}

II.4. Preprocessor directives of C++

All C and C++ compilers contain a so-called preprocessor, which do not know anything about C++ language. Its only task is to create a program module completely in C++ from the source files of a computer program by carrying out word-processing operations. Consequently, a C++ compiler does not compile the source code programmers write but it processes the text the preprocessor creates (see Figure II.8, “The compilation process in C++”). It can clearly be deduced from that that finding program errors that result from the statements of the preprocessor is not an easy task.

The compilation process in C++
Figure II.8. The compilation process in C++


In most C++ compilers, preprocessing and real compilation are not separated from each other, that is the output of the preprocessor is not stored in any text file. (It should not be forgotten that the g++ compiler of C++ carries out only preprocessing if it is used by the switch –E .)

Actually, a preprocessor is a text processing software that is line-oriented and that can be programmed in a macro language. The rules of C++ language do not applied to the preprocessor:

  • the directives of the preprocessor are based on stricter rules than C++ statements (a single line can only contain one directive and directives cannot extend across more than one line unless this extension is not marked at the end of the line),

  • all operations carried out by the preprocessor are proper text processing tasks (independently of the fact that they may contain C++ keywords, expressions or variables).

The directives of the preprocessor start with a hash sign (#) at the beginning of the line. The most frequent operations that they carry out are: text replacement ( #define ), including a text file ( #include ) or keeping certain parts of the code under certain conditions ( #if ).

C++ relies completely on the features of the preprocessor directives of C. Since preprocessing is out of the scope of compilers of the strictly typed C++ language, C++ attempts to reduce the need of using preprocessors to a minimal level by introducing new solutions. In a C++ code, some preprocessor directives can be replaced with C++ solutions: instead of using #define constants, one can use constants of type const; instead of using #define macros , one can write inline function templates.

II.4.1. Including files

When the preprocessor encounters an #include directive, the content of the file figuring in the directive is integrated (inserted) in the code in the place of the directive. Generally, it is header files containing declarations and preprocessor directives that are integrated in the beginning of the code; however, inclusion can be applied to any text file.

#include <header>
 
#include "filepath"

The first form is used to include the standard header files of C++ from the folder named include of the development kit:

#include <iostream>
#include <cmath>
#include <cstdlib>
#include <ctime>
using namespace std;

The second form is used to include non-standard files from the folder storing the actual C++ module or from any other folder:

#include "Module.h"
#include "..\Common.h"

The following table sums up the keywords and language elements that can be used in header files:

C++ elements

Example

Comments

// comment

Conditional directives

#ifdef MOTOROLA

Macro definitions

#define INTEL

#include directives

#include <string>

Enumerations

enum response {no, yes, maybe};

Constant definitions

const double pi=3.1415265;

Namespaces having an identifier

namespace nsrand { }

Name declarations

struct vector3D;

Type definitions

struct complex {double re, im;};

Variable declarations

extern double point[];

Function prototypes

double Average(double, double);

inline function definitions

inline int Sqr(int a) {return a*a;}

template declarations

template <class T> T Sqr(T a);

template definitions

template <class T> T Sqr(T a) {return a*a;}

The following elements should never be placed in include files:

  • definitions of non-inline functions,

  • variable definitions,

  • definitions of anonymous namespaces.

One part of the elements that can be placed in a header file have to be included only once in a code. That is why, all header files have to have a special preprocessing structure based on conditional directives:

// Module.h
#ifndef _MODULE_H_
#define _MODULE_H_
 
    the content of the header file
 
#endif

II.4.2. Conditional compilation

By using conditional directives, one can achieve that certain parts of a source code would be inserted into the C++ program created by the preprocessor only if given conditions are fulfilled. The code parts to be compiled under conditions can be selected in many ways, by using the following preprocessor directives: #if, #ifdef, #ifndef, #elif, #else and #endif.

In an #if statement, the condition can be expressed by a constant expression. If the value of the latter is zero, it means false; if the latter has a value other than 0, that means true. In an expression, only the following elements are allowed: integer and character constants, operations of type defined.

#if 'O' + 29 == VALUE

In a condition, we can check whether a symbol is defined or not with the help of the operator named defined. This operator returns 1, if its operand exists. Otherwise it returns 0:

#if defined symbol
 
#if defined(symbol)

Since checkings of this kind are often used, we can test whether a symbol exists or not with separate preprocessing statements:

#ifdef symbol        #if defined(symbol)
 
#ifndef symbol        #if !defined(symbol)

More complex conditions can be checked by #if:

#if 'O' + 29 == VALUE && (!defined(NAME) || defined(NUMBER))

The following simple text makes the code part enclosed within the #if and #endif lines integrated in the compilation unit:

#if constant_expression
      code part
#endif

This structure can well be used to integrate information into a source code in order that debugging become simpler. In that example, if the value of the symbol TEST is 1, this complementary information gets compiled in the code, and if it is 0, this information is not enabled:

#define TEST 1
 
int main() {
    const int size = 5;
    int array[size] = { 12, 23, 34, 45, 56 };
    int i;
 
    for (int i = 0; i < size; i++) {
        array[i] *= 2;
        #if TEST != 0
            cout << "i = " << i << endl;
            cout << "\tarray[i] = " << array[i] << endl;
        #endif
    }
}

Instead of the structure above, it is better to use a solution that examines the definition of the symbol TEST. For that purpose, TEST can be defined without a value, which can be assigned to it from a parameter of the compiler:

#define TEST
 
int main() {
    const int size = 5;
    int array[size] = { 12, 23, 34, 45, 56 };
    int i;
    for (int i = 0; i < size; i++) {
        array[i] *= 2;
        #if defined(TEST)
            cout << "i = " << i << endl;
            cout << "\tarray[i] = " << array[i] << endl;
        #endif
    }
}

Each pair of the following checkings return the same results:

#if defined(TEST)
... // defined
#endif
 
#ifdef TEST
... // defined
#endif
   
#if !defined(TEST)
... // not defined
#endif
 
#ifndef TEST
... // not defined
#endif

If the following structure is used, we can choose between two code parts:

#if constant_expression
      codepart1
#else
     codepart2
#endif

The following example creates the two-dimensional array named array depending on the value of the symbol SIZE:

#define SIZE 5
int main() {
    #if SIZE <= 2
        int array[2][2];
        const int size = 2;
    #else
        int array[SIZE][SIZE];
        const int size = SIZE;
    #endif
}

For more complex structures, it is recommended to use multi-way branches.

#if constant_expression1
      codepart1
#elif constant_expression2 
      codepart2
#else
     codepart3
#endif

The following example integrates the declaration file on the basis of the manufacturer:

#define IBM 1
#define HP 2
#define DEVICE IBM
 
#if DEVICE == IBM
    #define DEVICE_H "ibm.h"
#elif DEVICE == HP
    #define DEVICE_H "hp.h"
#else
    #define DEVICE_H "other.h"
#endif
#include DEVICE_H

II.4.3. Using macros

The most controversial domain about using preprocessors is the usage of #define macros in C++ programs. The reason for that is that the result of processing the macro may not always correspond to the expectations of programmers. Macros are widely used in codes written in C, but they have less importance in C++. The definition of the C++ programming language prefers const and inline function templates to macros. In the present section, we will always prefer a secure macro usage.

The #define directive is used to assign an identifier to C++ constants, keywords and frequently used statements and expressions. The symbols defined for preprocessors are called macros. In a C++ source code, it is recommended that macro names should be completely written in upper-case letters in order that they become visibly separated from C++ identifiers used in the code.

Preprocessors check the source code line by line whether it contains an already defined macro name. If yes, they replace it with the appropriate replacement text and then they check again the line for further macros, which may be followed by further replacements. This process is called macro replacement or macro substitution. Macros are created by a #define statement and deleted by the directive named #undef .

II.4.3.1. Symbolic constants

Symbolic constants can be created by using the simple form of the #define directive:

#define  identifier
 
#define  identifier  replacement_text

Let's see some examples how to define and use symbolic constants.

#define SIZE 4
#define DEBUG
#define AND &&
#define VERSION "v1.2.3"
#define BYTE unsigned char
#define EOS '\0'
 
int main() {
    int v[SIZE];
    BYTE m = SIZE;
    if (m > 2 AND m < 5)
        cout << "2 < m < 5" << endl;
    #ifdef DEBUG
        cout << VERSION << endl;
    #endif
}

The source file after the preprocessing (replacements) is the following:

int main() {
    int v[4];
    unsigned char m = 4;
    if (m > 2 && m < 5)
        cout << "2 < m < 5" << endl;
        cout << "v1.2.3" << endl;
}

In the previous example, some symbolic constants can be replaced by identifiers of types const and typedef in C++:

#define DEBUG
#define AND &&
const int size = 4;
const string version = "v1.2.3";
const char eos = '\0';
typedef unsigned char byte;
 
int main() {
    int v[size];
    byte m = size;
    if (m > 2 AND m < 5)
        cout << "2 < m < 5" << endl;
    #ifdef DEBUG
        cout << version << endl;
    #endif
}

II.4.3.2. Parameterized macros

The macros can be efficiently used in much more cases if they are parameterized. The general form of a function-like parameterized macro:

#define  identifier(parameter_list)  replacement_text

Using (calling) a macro:

identifier(argument_list)

The number of the arguments in a macro call has to be identical with that of the parameters in the definition.

In order that parameterized macros work securely, the following two rules have to observed:

  • The parameters of a macro always have to be enclosed within parentheses in the body of the macro (in the replacement text).

  • Increment (++) and decrement (--) operators should not be used in the argument list when the macro is called.

The following example code demonstrates some frequently used macro definitions and the way they can be called:

// determining the absolute value of x
#define ABS(x)   ( (x) < 0 ? (-(x)) : (x) )
 
// calculating the maximum value of a and b 
#define MAX(a,b) ( (a) > (b) ? (a) : (b) )
 
// calculating the square of X 
#define SQR(X)   ( (X) * (X) )
 
// generating a random number within a given interval
#define RANDOM(min, max) \
    ((rand()%(int)(((max) + 1)-(min)))+ (min))
 
int main() {
    int x = -5;
    x = ABS(x);
    cout << SQR(ABS(x));
    int a = RANDOM(0,100);
    cout << MAX(a, RANDOM(12,23));
}

The backslash (\) character at the end of the first line of the macro named RANDOM() indicates that the definition of that macro is continued in the next line. The content of the main () function cannot be qualified as legible at all after the preprocessing process:

int main() {
   int x = -5;
   x = ( (x) < 0 ? (-(x)) : (x) );
   cout << ( (( (x) < 0 ? (-(x)) : (x) )) * 
          (( (x) < 0 ? (-(x)) : (x) )) );
   int a = ((rand()%(int)(((100) + 1)-(0)))+ (0));
   cout << ( (a) > (((rand()%(int)(((23) + 1)-(12)))+ (12)))
        ? (a) : (((rand()%(int)(((23) + 1)-(12)))+ (12))) );
}

All advantages of macros (type-independence, faster code) can be achieved by using inline functions/function templates (instead of macros) while keeping the C++ code legible:

template <class tip> inline tip Abs(tip x) {
  return  x < 0 ? -x : x;
}
 
template <class tip> inline tip Max(tip a, tip b) {
  return a > b ? a : b;
}
 
template <class tip> inline tip Sqr(tip x) {
  return x * x;
}
 
inline int Random(int min, int max) {
  return rand() % (max + 1 - min) + min;
}
 
int main() {
    int x = -5;
    x = Abs(x);
    cout << Sqr(Abs(x));
    int a = Random(0,100);
    cout << Max(a, Random(12,23));
}

II.4.3.3. Undefining a macro

A macro can be undefined anytime and can be redefined again, even with a different content. It can be undefined by using the #undef directive. Before redefining a macro with a new content, the old definition always has to be undefined. The #undef statement does not signal an error if the macro to be undefined does not exist:

#undef identifier

The following example code traces the functioning of #define and #undef statements:

Original source code

Substituted code

int main() {
    #define MACRO(x) (x) + 7
    int a = MACRO(12);
    #undef MACRO
    a = MACRO(12);
    #define MACRO 123
    a = MACRO
}
int main() {
 
    int a = (12) + 7;
 
    a = MACRO(12);
 
    a = 123
}

II.4.3.4. Macro operators

In our previous examples, replacement was carried out by the preprocessor only in case of separate parameters. There are cases where a parameter is a part of an identifier or where the value of an argument is to be reused as a string. If replacement has to be done in these cases as well, then the macro operators ## and # have to be used.

If the # character is placed before the parameter in a macro, the value of the parameter is replaced as enclosed within quotation marks (i.e. as a string). With this method, replacement can be carried out on strings as well, since the compiler will concatenate the string literals standing one after another into one string constant. The following code prints out the name and value of any variable by using the macro named INFO():

#include <iostream>
using namespace std;
 
#define INFO(variable) \
        cout << #variable " = " << variable <<endl;
 
int main() {
   unsigned  x = 23;
   double   pi = 3.14259265;
   string    s = "C++ language";
   INFO(x);
   INFO(s);
   INFO(pi);
}
x = 23
s = C++ language
pi = 3.14259

By using the ## operator, two syntactic units (tokens) can be concatenated. To achieve this, the ## operator has to be inserted between the parameters in the body of a macro.

In the following example, the macro named PERSON() populates the array named people with values of structure type:

#include <string>
using namespace std;
 
struct person {
    string name;
    string info;
};
 
string Ivan_Info ="K. Ivan, Budapest,  9";
string Alice_Info ="O. Alice, Budapest, 33";
 
#define PERSON(NAME) { #NAME, NAME ## _Info }
 
int main(){
    person people[] = {
        PERSON (Ivan),
        PERSON (Alice)
    };
}

The content of the main () function after preprocessing:

int main(){
    person people[] = {
        { "Ivan", Ivan_Info },
        { "Alice", Alice_Info }
    };
}

There are cases when one macro is not sufficient for a given purpose. In the following example, the version number is created by concatenating the given symbolic constants:

#define MAJOR 7
#define MINOR 29
 
#define VERSION(major, minor) VERSION_MINOR(major, minor)
#define VERSION_MINOR(major, minor) #major "." #minor
 
static char VERSION1[] = VERSION(MAJOR,MINOR);       // "7.29"
static char VERSION2[] = VERSION_MINOR(MAJOR,MINOR); //"MAJOR.MINOR"

II.4.3.5. Predefined macros

ANSI C++ standard contains the following predefined macros. (Almost all identifiers start and end with a double underscore character.) The name of predefined macros cannot be used in #define and #undef statements. The value of predefined macros can be integrated into the text of a code but can also be used as a condition in conditional directives.

Macro

Description

Example

__DATE__

String constant containing the date of the compilation.

"Oct 02 2013"

__TIME__

String constant containing the time of the compilation.

"10:02:04"

__TIMESTAMP__

The date and time of the last modification of the source file in a string constant"

"Mon Jul 29

07:33:29 2013"

__FILE__

String constant containing the name of the source file.

"c:\\preproc.cpp"

__LINE__

A numeric constant, containing the number of the actual line of the source file (numbering starts from 1).

1223

__STDC__

Its value is 1 if the compiler works as an ANSI C++, otherwise it is not defined.

 

__cplusplus

Its value is 1, if its value is tested in a C++ source file, otherwise it is not defined.

 

II.4.3.6. #line, #error and #pragma directives

There are a lot of utility software that transforms a code written in a special programming language to a C++ source file (program generators).

The #line directive forces C++ compilers not to signal the error code in the C++ source text but in the original source file written in another special language. (The code and file name set by a #line statement are also present in the value of the symbols __LINE__ and __FILE__ .)

#line beginning_number
 
#line beginning_number "filename"

If it is inserted in a code, an #error directive can be used to print out a compilation error message which contains the text provided in the statement:

#error error_message

In the following example, compilation ends with an error message if we do not use the C++ mode:

#if !defined(__cplusplus)
    #error Compilation can only be carried out in C++ mode!
#endif

#pragma directives are used to control the compilation process in an implementation-dependent way. (There are not any standard solution for this directive.)

#pragma instruction

If the compiler encounters an unknown #pragma instruction, it does not consider that. Therefore the portability of computer programs is not endangered by this directive. For example, the alignment of structure members to different boundaries and disabling the printing out of warning compilation messages having a specific index can be done in the following way:

#pragma pack(8)
// aligned to 8 byte boundary
#pragma pack(push, 1)
// aligned to 1 byte boundary
#pragma pack(pop) 
// aligned again to 8 byte boundary
 
#pragma warning( disable : 2312 79 ) 

The empty directive (#) can also be used, but it does not affect preprocessing:

#

Chapter III. Object-oriented programming in C++

Table of Contents
III.1. Introduction to the object-oriented world
III.1.1. Basics
III.1.2. Basic principles
III.1.2.1. Encapsulation, data hiding
III.1.2.2. Inheritance
III.1.2.3. Abstraction
III.1.2.4. Polymorphism
III.1.3. An object-oriented example code
III.2. Classes and objects
III.2.1. From structures to classes
III.2.1.1. A little revision
III.2.1.2. Grouping together data and operations
III.2.1.3. Data hiding
III.2.1.4. Constructors
III.2.1.4.1. Using member initialisation lists
III.2.1.4.2. Explicit initialisation of objects
III.2.1.5. Destructor
III.2.1.6. Objects of a class, the pointer this
III.2.2. More about classes
III.2.2.1. Static class members
III.2.2.2. How to structure classes
III.2.2.2.1. Implicit inline member functions
III.2.2.2.2. Class structures in C++/CLI applications
III.2.2.2.3. Storing member functions in separate modules
III.2.2.3. Friend functions and classes
III.2.2.4. What can we also add to classes?
III.2.2.4.1. Constant data members of objects
III.2.2.4.2. Reference type data members
III.2.2.4.3. Data members as objects
III.2.2.5. Pointers to class members
III.2.3. Operator overloading
III.2.3.1. Creating operator functions
III.2.3.2. Using type conversion operator functions
III.2.3.3. Extending classes with input/output operations
III.3. Inheritance (derivation)
III.3.1. Derivation of classes
III.3.2. Initialising base class(es)
III.3.3. Accessing class members in case of inheritance
III.3.3.1. Accessing inherited members
III.3.3.2. The friend relationship in inheritance
III.3.4. Virtual base classes in case of multiple inheritance
III.3.5. Inheritance and/or composition?
III.3.5.1. Reuse with composition
III.3.5.2. Reuse by public inheritance
III.4. Polymorphism
III.4.1. Virtual member functions
III.4.2. Redefining virtual functions
III.4.3. Early and late binding
III.4.3.1. Static early binding
III.4.3.2. Dynamic late binding
III.4.3.3. Virtual method table
III.4.4. Virtual destructors
III.4.5. Abstract classes and interfaces
III.4.6. Run-time type informations in case of classes
III.5. Class templates
III.5.1. A step-be-step tutorial for creating and using class templates
III.5.2. Defining a generic class
III.5.3. Instantiation and specialisation
III.5.4. Value parameters and default template parameters
III.5.5. The "friends" and static data members of a class template
III.5.6. The Standard Template Library (STL) of C++
III.5.6.1. The structure of STL
III.5.6.2. STL and C++ arrays
III.5.6.3. Using STL containers
III.5.6.4. Using STL container adaptors

Object-oriented programming (OOP) is a contemporary programming methodology (paradigm), which divides whole programs into closed program units (objects) that have individual features and that are able to function by themselves. Object-oriented programming offers a much more efficient solution to most problems than classic, structured programming and it can reduce the time necessary to develop software by supporting the creation and reuseability of objects doing abstract operations.

III.1. Introduction to the object-oriented world

Object-oriented programming uses the "things" ("objects") and the interaction between them to plan and to develop applications and computer programs. This methodology comprises solutions, like encapsulation, modularity, polymorphism and inheritance.

It should be noted that OOP languages only offer tools and support in order that object-orientation be realised. In the present book, we will also focus on presenting these tools after a short overview.

III.1.1. Basics

Now let's have a look at the basic notions of object-orientation. To understand these, we do not even have to have much knowledge in programming.

Class

A class determines the abstract features of a thing (object), including its features (attributes, fields, properties) and its behaviour (what the thing can do, methods, operations and functions).

We can say that a class is a scheme describing the nature of something. For example, the class Truck has to contain the common properties of trucks (manufacturer, engine, brake system, maximum load etc.), as well as their specific behaviour, like braking, turning to left etc.

Classes offer by themselves modularity and structuredness for object-oriented computer programs. The notion of class should also be understandable for those people who are familiar with the given problem but not familiar with programming that is the features of a class should be expressive. The code of a class should be relatively independent (encapsulation). Both the integrated properties and the methods of a class are called class members (data member and member function in C++).

Object

An object is a scheme (an example) of a class. With the help of the class Truck, we can define all possible trucks by enumerating their properties and behaviour forms. For example, the class Truck has the feature brake system, but the brake system of the object myCar can be an electronically controlled one (EBS) or a simple air brake.

Instance

The notion of instance (of a class) is synonymous with the notion of object. Instance means an actual object created at runtime. So, myCar is an instance of the class Truck. The set of the property values of the actual object is called the state of that object. Therefore all objects are characterised by the state and behaviour defined in their corresponding class.

Method

Methods are responsible for the capabilities of objects. In spoken language, the corresponding term for methods is verbs. Since myCar is a Truck, it has the ability of braking, so Brake() is one of the methods of myCar. Of course, it may have other methods as well, like Ignition(), StepOnTheGas(), TurnLeft() or TurnRight(). Within a program, a method has only effect on the given object in general when it is used. Although all trucks can brake, activating (calling) the method Brake() will only slow down one given vehicle. In C++, methods are rather called member functions.

The object myCar (an instance of the class Truck)
Figure III.1. The object myCar (an instance of the class Truck)


Message passing

Message passing is the process during which an object sends data to another object or "asks" another object to execute one of its methods. The role of message passing can be better understood if we think of the simulation of the functioning of trucks. In that simulation, the object driver sends a "brake" message to activate the Brake() method of myCar, which results in braking the vehicle. The syntax of message passing may be very different among different programming languages. On the code level, message passing is realised by calling a method in C++.

III.1.2. Basic principles

The object-orientation of a system, of a programming language can be measured by supporting the following principles. If it is only some of these principles that are realised, that is an object-based system, if all four principles are supported, that is an object-oriented system.

III.1.2.1. Encapsulation, data hiding

As we have already seen, classes principally consist of features (state) and methods (behaviour). However, the state and behaviour of objects can be divided into two groups. There are some features and methods that we hide from other objects. These are internal (private or protected) states and behaviour. However, the others are made public. According to the basic principles of OOP, the state features have to be private while most of the methods may be public. If it is needed, public methods can be used to access private features in a controlled way.

It is not necessary for an object that passes a message to another object to know the inner structure of the latter. For example, the Truck has the method Brake(), which exactly defines how braking takes place. However, the driver of myCar does not have to know how that car brakes.

All objects provide a well-defined interface for the external world. That interface defines what can be accessed from the given object from the outside. If this interface is completely written, there is not any problem to modify the internal world of the class in the future for client applications using that object. For example, it can be ensured that trailers may only be connected to objects of the class HeavyTruck.

III.1.2.2. Inheritance

Inheritance means creating specific versions of a class that inherit the features and behaviour of their parent class (base class) and use them as if they were of their own. The classes created in this way are called subclasses or derived classes.

For example, the subclasses Van and HeavyTruck are derived from the class Truck. In the following, myCar will be an instance of the class HeavyTruck. Let's also suppose that the class Truck defines the method Brake() and the property brake system. All classes derived from that class (Van and HeavyTruck) inherit these members, so programmers have to write only once the code corresponding to them.

Inheritance
Figure III.2. Inheritance


Subclasses may change the inherited properties. For example, the class Van can prescribe that its maximum load is 20 tons. The subclass HeavyTruck may make EBS braking as its default for its method Brake().

Derived classes can be extended with new members as well. The method Navigate() can be added to the class HeavyTruck. On the basis of what has been said so far, the method Brake() of the given instance HeavyTruck uses an EBS-based brake in spite of the fact that it inherits a traditional Brake() method from the class Truck; it also has a new method named Navigate(), which cannot be found in the class Van.

Actually, inheritance is an is-a relation: myCar is a HeavyTruck, a HeavyTruck is a Truck. So myCar has the methods of both HeavyTruck and Truck.

Both derived classes have one direct parent class, namely Truck. This inheritance method is called single inheritance to be differentiated from multiple inheritance.

Multiple inheritance means that a derived class inherits the members of more direct parent classes. For example, we can define two classes totally independent of each other with the names Truck and Ship. From these classes, we can create a new class named Amphibious, which has the features and behaviour of both trucks and ships. Most programming languages (ObjectPascal, Java, C#) support only single inheritance; however, C++ offers both inheritance methods.

Multiple inheritance
Figure III.3. Multiple inheritance


III.1.2.3. Abstraction

Abstraction simplifies complex reality by modelling problems with their corresponding classes and it has its effects on the level of inheritance appropriate for these problems. For example, myCar can be treated as a Truck in most cases; however, it may also be a HeavyTruck as well if the specific features and behaviour of HeavyTruck are needed but it can also be considered as a Vehicle if it is treated as a member of a fleet of vehicles. (Vehicle is the parent class of Truck in the example.)

Abstraction can be achieved through composition. For example, a class named Car has to contain the components engine, gearbox, steering gear and many others. In order to construct a Car, we do not have to know how the different components work, we only have to know how to connect to them (i.e. its interface). An interface determines how to send them or receive from them messages and it gives information about the interaction between the components of the class.

III.1.2.4. Polymorphism

Polymorphism makes it possible to replace the content of some inherited (deprecated) behaviour forms (methods) with a new one in the derived class and to treat the new, replaced methods as the members of the parent class.

For the next example, let's suppose that the classes Truck and Bicycle inherit the method Accelerate() of the class Vehicle. In the case of a Truck, the command Accelerate() means the operation StepOnTheGas(), whereas in the case of a Bicycle it means calling the method Pedal(). In order that acceleration function correctly, the method Accelerate() of the derived classes should override the method Accelerate() inherited from the class Vehicle. This is overriding polymorphism.

Most OOP languages support parametric polymorphism as well where methods are written for compilers as schemes independently of types. In C++, this can be carried out with templates.

III.1.3. An object-oriented example code

Finally, let's see a C++ code written on the basis of the things said so far. Now, we mainly aim at giving readers some impressions about OOP, since OOP is only detailed in the following subchapters of the present book.

#include <iostream>
#include <string>
using namespace std;
 
class Truck {
   protected:
      string manufacturer;
      string engine;
      string brake_system;
      string maximum_load;
   public:
      Truck(string ma, string en, string brake,
                double load) {
         manufacturer = ma;
         engine = en;
         brake_system = brake;
         maximum_load = load;
      }
      void StartUp() { }
      void StepOnTheGas() { }
      virtual void Brake() {
           cout<<"Classic braking."<< endl;
      }
      void TurnLeft() { }
      void TurnRight() { }
};
 
class Van : public Truck {
    public:
      Van(string ma, string en, string brake)
            : Truck(ma, en, brake, 20) {  }
};
 
class HeavyTruck : public Truck {
    public:
      HeavyTruck(string ma, string en, string brake, double load) 
            : Truck(ma, en, brake, load) {  }
      void Brake() { cout<<"Brake with EBS."<< endl; }
      void Navigate() {}
};
 
int main() {
    Van post("ZIL", "Diesel", "airbrake");
    post.Brake();      // Classic braking.
    HeavyTruck myCar("Kamaz", "gas engine", "EBS", 40);
    myCar.Brake();    // Brake with EBS.
}

The following chapters of this book presents the C++ tools that realise the solutions related to the notions presented above. However, this overview is not enough for acquiring a routine in OOP, it only helps getting started.

III.2. Classes and objects

In C++ language, there are two possibilities to create an ADT – abstract data type of the object-oriented framework. The type struct of C++ is an extension to the structure type of C language and as such, it has become able to define abstract data types. C++ also offers us the new class type.

The types struct and class are built up of data members and related operations (member functions). Classes can be created by both data types; however, it is the class type that observes more the principles of obejct- orientation because of the default access restriction of its members. By default, all members of a type struct is public, whereas the members of a type class can only be accessed by the member functions of that class.

A class declaration has two parts. The header of the class contains the keyword class/struct, followed by the name of the class. The header is followed by the class body, which is enclosed within curly brackets followed by a semi-colon. Declarations contain not only data members and member functions but also keywords regulating access to the members and followed by a colon: public, private (hidden) and protected.

class ClassName {
  public:
   type4 Function1(parameterlist1) {  }
   type5 Function2(parameterlist2) {  }
  protected:
    type3 data3;
  private:
    type1 data11, data12;
    type2 data2;
};

The declaration of class and struct classes can be placed anywhere in a C++ code where a declaration is allowed; however what comply the most with contemporary software development methods is a declaration on a module (file) level.

III.2.1. From structures to classes

In this chapter, based on our previous knowledge about the type struct, we will learn how to create and use objects. The problems raised in the subchapters are first solved in a conventional way than in an object-oriented aspect.

III.2.1.1. A little revision

We have already learnt how to group the data necessary to solve a problem with a structure type if the data are to be stored in the members of that structure:

struct Employee{
    int employeeID;
    string name;
    float salary;
};

We are also able to define operations with functions that get as an argument the structure type variable:

void IncreaseSalary(Employee& a, float percent) {
    a.salary *= (1 + percent/100);
}

The members of the structure can be accessed by the dot or arrow operator depending on who we entrust with memory allocation: ourselves or compilers:

int main() {
    Employee engineer;
    engineer.employeeID = 1234;
    engineer.name = "Tony Clever";
    engineer.salary = 2e5;
    IncreaseSalary(engineer,12);
    cout << engineer.salary << endl;
 
    Employee *pAccountant = new Employee;
    pAccountant->employeeID = 1235;
    pAccountant->name = "Sarah Rich";
    pAccountant->salary = 3e5;
    IncreaseSalary(*pAccountant,10);
    cout << pAccountant->salary << endl;
    delete pAccountant;
}

Of course, structured and efficient program codes can be created in this way too; however, in this chapter, we intend to go further.

III.2.1.2. Grouping together data and operations

As a first step, relying on the basis of the principle named encapsulation, we group together data and operations to be done on them in one code unit, which we call class.

struct Employee {
    int employeeID;
    string name;
    float salary;
    void IncreaseSalary(float percent) {
        salary *= (1 + percent/100);
    }
};

It can be seen at first glance, that the function IncreaseSalary() does not receive the class type variable (object) as a parameter, since it carries out an operation on the object by default. The main () function that demonstrates the usage of the objects of type Employee, since we only call now the member function corresponding to the variable:

int main() {
    Employee engineer;
    engineer.employeeID = 1234;
    engineer.name = "Tony Clever";
    engineer.salary = 2e5;
    engineer.IncreaseSalary(12);
    cout << engineer.salary << endl;
 
    Employee *pAccountant = new Employee;
    pAccountant->employeeID = 1235;
    pAccountant->name = "Sarah Rich";
    pAccountant->salary = 3e5;
    pAccountant->IncreaseSalary(10);
    cout << pAccountant->salary << endl;
    delete pAccountant;
}

III.2.1.3. Data hiding

Accessing class type variables (objects) directly contradicts the principle of data hiding. In object-oriented programming it is required that the data members of classes could not be accessed directly from the outside. The type struct offers complete access to its members by default, whereas the class type completely hides its members from the outside, which complies much more with the principles of OOP. It should be noted that the access of class elements can be defined by programmers as well with the keywords private, protected and public.

public members can be accessed anywhere within the code where the object itself is accessible. On the contrary, private members can be accessed only from the the member functions of their class. (The access type protected will be applied in the case of inheritance treated in Chapter Section III.3, “Inheritance (derivation)”

Within a class, a member group of any number of elements can be created with the usage of the keywords (private, protected, public) and there is no restriction on the order of groups.

If we stick to the previous example, it is necessary to write further member functions with which we can set and get in a controlled way the value of data members because of their restricted access modes. The setting functions may also check that only valid data could appear in the object of type Employee. Getting functions are often defined as a constant, which means that the value of data members cannot be modified with that function. In a constant member function, the reserved word const is put between the header and the body of that function. In our example, GetSalary() is a constant member function.

class Employee{
  private:
    int employeeID;
    string name;
    float salary;
  public:
    void IncreaseSalary(float percent) {
        salary *= (1 + percent/100);
    }
    void SetData(int code, string n, float s) {
        employeeID = code;
        name = n;
        salary = s;
    }
    float GetSalary() const {
        return salary;
    }
};
 
int main() {
    Employee engineer;
    engineer.SetData(1234, "Tony Clever", 2e5);
    engineer.IncreaseSalary(12);
    cout << engineer.GetSalary() << endl;
 
    Employee *pAccountant = new Employee;
    pAccountant->SetData(1235, "Sarah Rich", 3e5);
    pAccountant->IncreaseSalary(10);
    cout << pAccountant->GetSalary() << endl;
    delete pAccountant;
}

It should be noted that data members can be changed with the help of constant member functions, too, in case they are declared with the keyword mutable, for example:

mutable float salary;

However, these solutions are rarely used.

It should be noted that if all data members of a class have public access, the object can be initialised by the solution already presented in the case of structures, for example:

Employee doorman = {1122, "John Secure", 1e5};

Since the useability of the formula above will be later restricted by other constraints as well (it should not be a derived class, it cannot have virtual member functions), it is recommended that they be initialised by the special member functions of classes, namely by the so-called constructors.

III.2.1.4. Constructors

In program codes using classes, one of the most frequent operations is creating objects. Some objects are created by programmers themselves by static or dynamic memory allocation (see example above); however, there are cases when the compiler creates so-called temporary object instances. How can we assign the data members of objects to be created an initial value? By data members that are called constructors.

class Employee{
  private:
    int employeeID;
    string name;
    float salary;
  public:
    Employee() {                           // default
        employeeID = 0;
        name = "";
        salary = 0;
    }
    Employee(int code, string n, float s) { // by parameters
        employeeID = code;
        name = n;
        salary = s;
    }
    Employee(const Employee & a) {      // by copying values 
        employeeID = a.employeeID;
        name = a.name;
        salary = a.salary;
    }
    void IncreaseSalary(float percent) {
        salary *= (1 + percent/100);
    }
    void SetName(string n) {
        name = n;
    }
    float GetSalary() const {
        return salary;
    }
};
 
int main() {
    Employee employee;
    employee.SetName("Stephen Smith");
 
    Employee engineer(1234, "Tony Clever", 2e5);
    engineer.IncreaseSalary(12);
    cout << engineer.GetSalary() << endl;
 
    Employee firstEngineer = engineer;
    // or: Employee firstEngineer(engineer);
    firstEngineer.IncreaseSalary(50);
    cout << firstEngineer.GetSalary() << endl;
 
    Employee *pEmployee = new Employee;
    pEmployee->SetName("Stephen Smith");
    delete pEmployee;
 
    Employee *pAccountant;
    pAccountant = new Employee(1235, "Sarah Rich", 3e5);
    pAccountant->IncreaseSalary(10);
    cout << pAccountant->GetSalary() << endl;
    delete pAccountant;
 
    Employee *pFirstEngineer=new Employee(engineer);
    pFirstEngineer->IncreaseSalary(50);
    cout << pFirstEngineer->GetSalary() << endl;
    delete pFirstEngineer;
}

In the example above, we created a constructor without parameters, a constructor with parameters and a copy constructor, making use of function name overloading. Thus, a constructor is a member function the name of which corresponds to the name of the class and has no return type. The constructor of a class is called automatically by compilers every time an object of the given class is created. A constructor has no return value. Except from that, it behaves like any other member function. With redefined (overloaded) constructors, an object can be initialised in many ways.

A constructor does not allocate memory space for the object to be created, it only has to initialise the memory space already allocated for it. However, if the object contains a pointer, then the constructor has to ensure that a memory space to which the pointer points is allocated.

A class has two constructors by default: a constructor without parameters (default) and a copy constructor. If we create a personalised constructor, then the default constructor is not available any more, so it has to be defined as well. We generally use our own copy constructor if a dynamic memory space is associated with the instances of a class.

Constructors with and without parameters are often contracted by introducing default arguments:

class Employee{
  private:
    int employeeID;
    string name;
    float salary;
  public:
    Employee(int code = 0, string n ="", float s=0) { 
        employeeID = code;
        name = n;
        salary = s;
    }
    …
}
III.2.1.4.1. Using member initialisation lists

The members of a class can be assigned a value in two different ways from a constructor. We have already mentioned the solution consisting of assigning values within the body of a constructor. Besides that, there is another possibility in C++, namely member initialisation lists. An initialisation list is provided after a colon after the header of a constructor. The elements, separated from each other by a comma, are the data members of the class, and they are followed by their initial value enclosed within parentheses. If member initialisation lists are used, the constructors of the example above become empty:

class Employee{
  private:
    int employeeID;
    string name;
    float salary;
  public:
    Employee(int code=0, string n="", float s=0) 
        : employeeID(code), name(n), salary(s) { }
    Employee(const Employee & a)
        : employeeID(a.employeeID), name (a.name), 
          salary(a.salary) { }
    …
}

It should be noted that when a constructor is called, it is processing the initialisation list that takes place first, and the execution of the body of the constructor only after.

III.2.1.4.2. Explicit initialisation of objects

In the case of constructors having only one parameter, compilers carry out implicit type conversion, if needed, in order to choose the appropriate constructor. If the keyword explicit is used before a constructor, conversion can be hindered after the constructor is called.

In the following example, we differentiate between the two types of initial value assignment (explicit and implicit):

class Number 
{
  private:
    int n;
  public:
    explicit Number( int x) {
        n = x;
        cout << "int: " << n << endl;
    }
    Number( float x) {
        n = x < 0 ? int(x-0.5) : int(x+0.5);
        cout << "float: " << n << endl;
    }
};

int main() {
  Number a(123);    // explicit call
  Number b = 123;   // implicit (not explicit) call
}

When the object a is created, it is the explicit constructor that is called, whereas in the case of the object b, it is the constructor with the parameter of type float. If the keyword explicit is missing, it is the first constructor that is activated in both cases.

III.2.1.5. Destructor

The resources (memory, files, etc.) allocated often during the object creation have to be freed, when the object is destroying. Otherwise, these resources will become unavailable for the program.

For that purpose, C++ offers a special member function, the destructor, in which we can free the allocated resources. The name of a destructor has to be provided as a class name with the tidle character (~). A destructor, just like constructors, does not return any value.

In the following example, a dynamically allocated array with 12 elements is created in the constructors in order that the work hours done by employees could be stored for every month. The memory allocated for the array is freed in the destructor.

class Employee{
  private:
    int employeeID;
    string name;
    float salary;
    int *pWorkhours;
  public:
    Employee(int code = 0, string n ="", float s=0) { 
        employeeID = code;
        name = n;
        salary = s;
        pWorkhours = new int[12];
        for (int i=0; i<12; i++) pWorkhours[i]=0;
    }
    Employee(const Employee & a) {
        employeeID = a.employeeID;
        name = a.name;
        salary = a.salary;
        pWorkhours = new int[12];
        for (int i=0; i<12; i++) 
            pWorkhours[i]=a.pWorkhours[i];
    }
    ~Employee() {
        delete[] pWorkhours;
        cout << name << " deleted" << endl;
    }
    void IncreaseSalary(float percent) {
        salary *= (1 + percent/100);
    }
    void SetWorkhours(int month, int hours) {
        if (month >= 1 && month <=12) {
            pWorkhours[month-1]=hours;
        }
    }
    float GetSalary() const {
        return salary;
    }
};
 
int main() {
    Employee engineer(1234, "Tony Clever", 2e5);
    engineer.IncreaseSalary(12);
    engineer.SetWorkhours(3,192);
    cout << engineer.GetSalary() << endl;
 
    Employee *pAccountant;
    pAccountant = new Employee(1235, "Sarah Rich", 3e5);
    pAccountant->IncreaseSalary(10);
    pAccountant->SetWorkhours(1,160);
    pAccountant->SetWorkhours(12,140);
    cout << pAccountant->GetSalary() << endl;
    delete pAccountant;
}

Compilers call the destructor of a class every time when the object is not valid any more. An exception to that rule is constituted by dynamic objects created by the operator new, the destructor of which can be activated only by the operator delete. It should be noted that a destructor does not delete the object itself but automatically do some "cleaning" tasks that is told to it.

When this example code is executed, the following text is printed out:

224000
330000
Sarah Rich deleted
Tony Clever deleted

So, it can be clearly seen that first it is the destructor of the object *pAccountant that is called when the operator delete is used. Then, when the closing curly bracket of the body of the main () function has been reached, the destructor of the object engineer is automatically called.

If a destructor is not written for a class, the compiler automatically adds an empty destructor for that class.

III.2.1.6. Objects of a class, the pointer this

If objects (variables of type class) of the class type Employee are created:

Employee engineer(1234, "Tony Clever", 2e5);
Employee *pAccountant;
pAccountant = new Employee(1235, "Sarah Rich", 3e5);

each object has its own data members; however, these objects share only one instance of member functions (Figure III.4, “The class Employee and its objects”).

engineer.IncreaseSalary(12);
pAccountant->IncreaseSalary(10);

Then how the function IncreaseSalary() know that when it is called which memory space should be accessed?

The answer to this question is provided by an invisible activity of compilers: each member function, even those without a parameter, has an invisible parameter (this) in which a pointer to the actual object is passed to the function when it is called. All references to data members are inserted in a program code automatically in the following way:

this->datamember

The class Employee and its objects
Figure III.4. The class Employee and its objects


Programmers may also use the pointer this within member functions. This is a good solution to handle ambiguities when the name of a parameter equals with that of a data member:

class Employee{
  private:
    int employeeID;
    string name;
    float salary;
  public:
    Employee(int employeeID=0, string name="", float salary=0){
        this->employeeID = employeeID;
        this->name = name;
        this->salary = salary;
    }
};

In normal member functions, the pointer this is declared as Classtype* const this, and in constant member functions as const Classtype*const this.

III.2.2. More about classes

In the previous subchapter, we arrived to classes from structures. We learned how to create well useable classes. Now, it is time to solve tasks with the help of classes.

The present subchapter deals with less general knowledge about classes. This subchapter is mainly dedicated to those who already has some practice in how to create classes.

III.2.2.1. Static class members

In C++, the keyword static can be typed before the data members of classes in order that the objects of these classes could share these members (just like member functions). A static data member that is created in only one instance belongs directly to the class; therefore it is available for it even if there are no objects for that class.

A static data member should not be initialised within its class (independently of its access restriction). An exception to that rule is constituted by data members of type static const integer and enumeration to which a value can be assigned even within the class.

If a static data member is public, then it can be used anywhere in the program code by the name of the class and the scope operator (::). Otherwise, only instances of the class can access these members.

Besides presenting the usage of static members, the following example also demonstrates how to insert constants (static const and enum) in classes. Our mathematics class defined by ourselves (Math) makes it possible for us to call Sin() and Cos() member functions with data in radians or in degrees:

#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
 
class Math {
  public:
    enum Unit {degree, radian};
  private:
    static double dDegree2Radian;
    static Unit eMode;
  public:
    static const double Pi;
 
    static double Sin(double x)
           {return sin(eMode == radian ? x : dDegree2Radian*x);}
    static double Cos(double x)
           {return cos(eMode == radian ? x : dDegree2Radian*x);}
static void MeasureUnit(Unit mode = radian) { 
                                          eMode = mode; }
    void PrintOutPI() { cout.precision(18); cout<<Pi<<endl;}
};
 
// creating and initialising static members
const double Math::Pi = 3.141592653589793238462;
double Math::dDegree2Radian = Math::Pi/180;
Math::Unit Math::eMode = Math::radian;

As it can be seen in the example, an enumeration can also be placed within a class. The type name Unit and the enumerated constants (degree, radian) can be referenced with a name qualified with the class name. These names have class level scope, independently of the type name preceded by the keyword enum (Math::Unit): Math::radian, Math::degree.

To manage static data members, we use static member functions in general (Math::Sin(), Math::Cos(), Math::MeasureUnit()). However, normal data members cannot be accessed from static functions since the pointer this does not figure among their parameters. The static members of a class can be accessed from non-static member functions without restrictions.

A possible usage of the class Math is presented in the following example:

int main() {
   double y = Math::Sin(Math::Pi/6);    // counts in radian
   Math::MeasureUnit(Math::degree);     // counts in degrees
   y = Math::Sin(30);
   Math::MeasureUnit(Math::radian);  // counts in radian
   y = Math::Sin(Math::Pi/6);
 
   Math m;                            // class instance
   m.MeasureUnit(Math::degree);       // or
   m.MeasureUnit(m.degree);
   y = m.Sin(30);
   m.MeasureUnit(m.radian);           // or
   m.MeasureUnit(Math::radian);
   y = m.Sin(Math::Pi/6);
   m.PrintOutPI();
}

III.2.2.2. How to structure classes

The rules of C++ make it possible to structure classes in many ways. In the following examples, each of these methods is strictly separated but in the everyday practice we use them in different combinations.

III.2.2.2.1. Implicit inline member functions

In the first subchapter, the whole definition of member functions is placed in the description of a class. Compilers consider these member functions automatically as inline functions. A big advantage of the solution is that the whole class can be stored in a header file, and the members of the class are logically grouped together. In general, this solution is useful for small-sized classes.

As an example, let's see the class Point, which is able to handle points in the plane.

class Point {
   private:
      int x,y;
   public:
      Point(int a = 0, int b = 0) { x = a; y = b; }
      int GetX() const { return x; }
      int GetY() const { return y; }
      void SetX(int a) { x = a; }
      void SetY(int a) { y = a; }
      void Move(int a, int b) { x = a; y = b; }
      void Move(const Point& p) { x = p.x; y = p.y; }
      void PrintOut() const { cout<<"("<<x<<","<<y<<")\n"; }
};
III.2.2.2.2. Class structures in C++/CLI applications

The .NET project of Visual C++, Java and C# languages follow principles similar to those above. But there is a big difference: class members are not grouped on the basis of their accessibility, so the accessibility can be provided to each member separately.

class Point {
   private: int x,y;
   public: Point(int a = 0, int b = 0) { x = a; y = b; }
   public: int GetX() const { return x; }
   public: int GetY() const { return y; }
   public: void SetX(int a) { x = a; }
   public: void SetY(int a) { y = a; }
   public: void Move(int a, int b) { x = a; y = b; }
   public: void Move(const Point& p) { x = p.x; y = p.y; }
   public: void PrintOut() const { cout<<"("<<x<<","<<y<<")\n"; }
};
III.2.2.2.3. Storing member functions in separate modules

It is easier to manage a bigger-sized class if its member functions are stored in a separate C++ module. Then the declaration of the class contains not only data members but also the prototype of member functions. The header file containing the description (.H) and the module storing the definition of member functions (.CPP) have the same name in general and refer to their corresponding class.

The description of the class Point in the Point.h header file:

#ifndef __PONT_H__
#define __PONT_H__
 class Point {
   private:
      int x,y;
   public:
      Point(int a = 0, int b = 0);
      int GetX() const;
      int GetY() const;
      void SetX(int a);
      void SetY(int a);
      void Move(int a, int b);
      void Move(const Point& p);
      void PrintOut() const;
 };
#endif

The name of member functions has to be qualified with the name of the class (::) in the file Point.cpp:

#include <iostream>
using namespace std;
#include "Point.h"
 
Point::Point(int a, int b) { 
   x = a; y = b;
}            
 
int Point::GetX() const {
   return x; 
} 
 
int Point::GetY() const {
   return y; 
} 
 
void Point::SetX(int a) {
   x = a; 
}
 
void Point::SetY(int a) {
   y = a; 
}
 
void Point::Move(int a, int b) { 
   x = a; y = b; 
}            
 
void Point::Move(const Pont& p) {
   x = p.x; y = p.y; 
}
 
void Point::PrintOut() const {
   cout<<"("<<x<<","<<y<<")\n"; 
}

Of course, the keyword inline can be used explicitly for member functions; however, in that case, the definition of inline member functions has to be moved from the C++ module to the header file:

#ifndef __PONT_H__
#define __PONT_H__
class Point {
   private:
      int x,y;
   public:
      Point(int a = 0, int b = 0);
      int GetX() const;
      int GetY() const;
      void SetX(int a);
      void SetY(int a);
      void Move(int a, int b);
      void Move(const Point& p);
      inline void PrintOut() const;
};
 
void Point::PrintOut() const {
   cout<<"("<<x<<","<<y<<")\n";
}
 
#endif

III.2.2.3. Friend functions and classes

There are cases where the data hiding principles of C++ prevents us to write an efficient program code.  However, the friend mechanism makes it possible for us to access the private and protected members of a class from a function outside the class.

A friend declaration can be placed anywhere within the description of that class. An external function, a member function of another class and even a whole other class (that is all of its member functions) can be a "friend". Accordingly, a friend declaration consists of the prototype of the functions and the name of the class, the whole introduced by the keyword class.

It should be noted that a "friend" relationship is not mutual in the case of a friend class because it is only the member functions of the class figuring in the friend declaration that have full access to the members of the class containing that description.

In the following example, the external function Sum(), the public member function named Count() of BClass as well as all data functions of AClass have full access to all members of CClass:

class AClass;
 
class BClass {
 public:
  int Count(int x) { return x++; }
};
 
class CClass {
  friend long Sum(int a, int b);
  friend int BClass::Count(int x);
  friend class AClass;
  // ...
};
 
long Sum(int a, int b) {
  return long(a) + b;
}

For another example, let's see the simplified version of our class Point. Since the operation calculating the distance of points cannot be related to any points, a separate external function is written in order that the distance could be determined. The two points are passed as arguments to that function. In order that data members be quickly accessed, a direct access is needed, and this can be realised by the "friend" mechanism.

#include <iostream>
#include <cmath>
using namespace std;
 
class Point {
      friend double Distance(const Point & p1,
                             const Point & p2);
   private:
      int x,y;
   public:
      Point(int a = 0, int b = 0) { x = a; y = b; }
      int GetX() const { return x; }
      int GetY() const { return y; }
      void SetX(int a) { x = a; }
      void SetY(int a) { y = a; }
      void Move(int a, int b) { x = a; y = b; }
      void Move(const Point& p) { x = p.x; y = p.y; }
      void PrintOut() const { cout<<"("<<x<<","<<y<<")\n"; }
};
 
double Distance(const Point & p1, const Point & p2) {
  return sqrt(pow(p1.x-p2.x,2.0)+pow(p1.y-p2.y,2.0));
}
 
int main() {
  Point p, q;
  p.Move(1,2);
  q.Move(4,6);
  cout<<Distance(p,q)<<endl;
}

III.2.2.4. What can we also add to classes?

Until now, we have only placed data and functions in classes. We have also added static constants and the type enum to classes.

In the following parts, we will see how to put constants and references in classes, and after that we will also summarise the rules of using nested classes.

III.2.2.4.1. Constant data members of objects

There are cases where a unique constant value is needed to be assigned to object instances, for example a name or an identifier. It is possible if the the data member is preceded by the keyword const and if it is added to the member initialisation list of constructors.

In the following example, objects of type User are created and the public name of users are used as constants:

class User {
    string password;
 public:
    const string name;
    User(string user, string psw="") : name(user) {
         password=psw;
    }
    void SetPassword(string newpsw) { password = newpsw;}
};
 
int main() {
   User nata("Lafenita");
   User gardener("Liza");
   nata.SetPassword("Atinefal1223");
   gardener.SetPassword("Azil729");
   cout<<nata.name<<endl;
   cout<<gardener.name<<endl;
   User alias = nata;
   // alias = gardener;   // error!
}

It should be noted that the traditional member by member copy does not work between objects of the same type if these contain constant members.

III.2.2.4.2. Reference type data members

Since a reference can only be created for an already existing variable, the same is true for references like for constants when objects are constructed. In the following example, the sensor object is linked with the controller object by a reference:

class Sensor {
   private:
      int data;
   public:
     Sensor(int x) { data = x; }
     int Read() { return data; }
};
 
class Controller {
   private:
     Sensor& sensor;
   public:
     Controller(Sensor& s) : sensor(s) {}
     void ReceiveData() { cout<<sensor.Read(); }
};
 
int main() {
   Sensor speed(0x17);
   Controller ABS(speed);
   ABS.ReceiveData();
}
III.2.2.4.3. Data members as objects

It is frequent to place the object instance of a class in another class as a data member. It is an important rule that when objects of a class like this are created, the inner objects also have to be initialised, which can be achieved by adding them to the member initialisation list of the appropriate constructor call.

Calling a constructor is not necessary if the class of the member object has a constructor without a parameter (default constructor), which is automatically called.

The previous controller-sensor example code can be modified in order that the sensor appear as an object in the controller object:

class Sensor {
   private:
      int data;
   public:
     Sensor(int x) { data = x; }
     int Read() { return data; }
};
 
class Controller {
   private:
     Sensor sensor;
   public:
     Controller() : sensor(0x17) {}
     void ReceiveData() { cout<<sensor.Read(); }
};
 
int main() {
   Controller ABS;
   ABS.ReceiveData();
}

III.2.2.5. Pointers to class members

In C++, a pointer to a function cannot be assigned the address of a member function even if their type and parameter list are completely the same. The reason for that is that (non-static) member functions have their effects on the instances of their class. This is also true for the pointers to data members. (Another important difference between these pointers and traditional ones is that the later detailed polymorphism also has its effect if virtual member functions are called with pointers.)

In order to define pointers correctly, we have to use the name of the class and the scope operator:

class Class;

forward class declaration,

int Class::*p;

p is a pointer to a data member of type int,

void (Class::*pf unct )(int);

pfunct may point to a member function that is called with an argument of type int and that returns no value.

In the following example, we present how to use pointers to class members. We will access both class members by pointers. If such pointers are used, the members are referenced by the operators .* (dot asterisk) and ->* (arrow asterisk) instead of traditional operators. In order that the address of data members and member functions could be obtained, the "address of" operator (&) has to be used.

#include <iostream>
using namespace std;
 
class Class {
  public:
    int a;
    void f(int b) { a += b;}
};
 
int main() {
   // pointer to a data member of Class of type int
   int Class::*intptr = &Class::a;
 
   // pointer to the member function of Class 
   // of type void and with a parameter of type int
   void (Class::* functptr)(int) = &Class::f;
 
   // creating object instances
   Class object;
   Class * pobject = new Class();
 
   // accessing the data member with a pointer
   object.*intptr = 10;
   pobject->*intptr = 100;
 
   // calling the member function f() with a pointer
   (object.*functptr)(20);
   (pobject->*functptr)(200);
 
   cout << object.a << endl;    // 30
   cout << pobject->a << endl;    // 300
   delete pobject;
}

By using typedef, expressions containing pointers are easier to be handled:

typedef int Class::*pointer_int;
 
typedef void (Class::*pointer_funct)(int);
 
…
pointer_int intptr = &Class::a;
 
pointer_funct functptr = &Class::f;

III.2.3. Operator overloading

So far, operations belonging to a class have been realised in the form of member functions. And doing operations has meant calling member functions. Program codes become more legible if an operator of a similar content can be used instead of calling functions.

C++ offers the possibility to link a function created by a programmer to a traditional operator, extending the functioning of that operator. This function is called automatically if an operator is used in a specific context.

However, an operator function can be used if one of its parameters is a class of type class or struct. This means that functions without parameters and functions using only arguments of a basic data type cannot become operator functions. General declaration of operator functions:

        type operator op(parameterlist);

where the sequence op can be replaced by any of the following operators:

[]

()

.

->

++

--

&

new

*

+

-

~

!

/

%

new[]

<<

>>

<

>

<=

>=

==

delete

!=

^

|

&&

||

=

*=

delete[]

/=

%=

+=

-=

<<=

>>=

&=

 

^=

|=

,

->*

    

After type conversion, an operator function will have the following form:

        operator type();

The following operators cannot be overloaded : member selection (.), indirect member selection (.*), scope (::), conditional (?:) and the operators sizeof and typeid since their overloading would result in undesired side effects.

The assignment (=), the "address of" (&) and the comma (,) operations can be applied to objects without overloading.

It should be noted that overloading operators does not result in modifying the operator precedence and associativity, and it is not possible to introduce new operations.

III.2.3.1. Creating operator functions

The choice of an operator function, which realise operator overloading, depends largely on the chosen operator. The following table summarises the possibilities. The type and return value of most operator functions can be provided without restrictions.

Expression

Operator( )

Member function

External function

♣a

+ - * & ! ~

++ --

A::operator ()

operator (A)

a♣

++ --

A::operator (int)

operator (A, int)

a♣b

+ - * / % ^ &

| < > == != <=

>= << >> && || ,

A::operator (B)

operator (A, B)

a♣b

= += -= *= /=

%= ^= &= |= <<=

>>= []

A::operator (B)

-

a(b, c...)

()

A::operator()(B, C...)

-

a->b

->

A::operator->()

-

Operator functions are generally defined within classes in order that the features of user-defined types could be extended. The operators =, (), [] and -> can only be overloaded by non-static member functions. The operators new and delete are overloaded with static member functions. All other operator functions can be created as member functions or external (in general friend) functions.

The table above summarise well the things we have just said because it categorises into groups the overloadable operations of C++ while leaving out operators of dynamic memory management. The character ♣ in the table replaces the operator, whereas a, b and c are the objects of a class A, B and C.

Most of the overloadable C++ operators can be divided into two categories on the basis of the number of their operands. The following table summarises how to call these two types of operator functions.

Binary operands:

Realisation

Syntax

Actual call

member function

X op Y

X.operator op(Y)

external function

X op Y

operator op(X,Y)

Unary operands:

Realisation

Syntax

Actual call

member function

op X

X.operator op()

member function

X op

X.operator op(0)

external function

op X

operator op(X)

external function

X op

operator op(X,0)

When certain operations are redefined, there are other things that have to be considered than in general. These operators are treated more in the following parts of this chapter.

As an example, let's have a look at the class Vector in which the indexing ([]), value assignment (=) and addition (+, +=) operators are overloaded. The reason for modifying the value assignment operator is to be able to copy better the elements of an array. The operator + is realised by a friend function since the resulting vector does not belong to any of the operands logically. On the contrary, overloading the operation += is carried out by a member function since the elements of the left side operand are modified. The whole declaration of the class (together with inline functions) is contained by the file Vector.h.

#ifndef __VectorH__
#define __VectorH__
 
class Vector {
inline friend Vector  operator+ (const Vector& v1, 
                                 const Vector& v2);
  private:
    int size, *p;
  public:
    // ----- Constructors -----
    // initialising a vector of a given size
    Vector(int n=10) {
        p = new int[size=n];
        for (int i = 0; i < size; ++i)  
            p[i] = 0;    // initialising all elements to zero
    }
    // Initialising by another vector - a copy constructor
    Vector(const Vector& v) {
         p = new int[size=v.size];
         for (int i = 0; i < size; ++i) 
            p[i] = v.p[i];   // copying all elements
    }
    // Init.by a traditional vector with n number of elements
    Vector(const int a[], int n) {
        p = new int[size=n];
        for (int i = 0; i < size; ++i)
           p[i] = a[i];
    }
 
    // ----- Destructor -----
    ~Vector() {delete[] p; }
 
    // ----- Member function returning the size -----
    int GetSize() const { return size; } 
 
    // ----- Operator functions -----
    int& operator [] (int i) {
        // checking whether index is within bounds
        if (i < 0 || i > size-1)  
            throw i;
        return p[i];
    }
 
    const int& operator [] (int i) const {
        return p[i];
    }
 
    Vector operator = (const Vector& v) {
        delete[] p;
        p=new int [size=v.size];
        for (int i = 0; i < size; ++i)
           p[i] = v.p[i];
        return *this;
    }
 
    Vector operator += (const Vector& v) {
        int m = (size < v.size) ? size: v.size;
        for (int i = 0; i < m; ++i)
           p[i] += v.p[i];
        return *this;
    }
};
 
// ----- External function  -----
inline Vector operator+(const Vector& v1, const Vector& v2) {
    Vector sum(v1);
    sum+=v2;
    return sum;
}
#endif

In order that the example program code be more easily understood, we will give now the readers some further remarks.

  • Two operator functions were created for the operation of indexing, compilers use the second one with constant vectors. The two operator[]() functions are the overloaded versions of each other although their parameter list is the same. This is possible because C++ compilers also store whether the function is of type const or not in the trace of the function.

  • The pointer this points to an object; however, the expression *this designates the object itself. Functions of type Vector that return a *this value actually return the actual copy of the object. (It should be noted that functions of type Vektor& return a reference to the actual object because of the statement return *this;)

The usage of the class Vector is exemplified by the following code:

#include <iostream>
using namespace std;
#include "Vector.h"
 
void show(const Vector& v) {
  for (int i=0; i<v.GetSize(); i++)
    cout<<v[i]<<'\t';
  cout<<endl;
}
 
int main() {
    int a[5]={7, 12}, b[7]={2, 7, 12, 23, 29};
    Vector x(a,5);     // x:  7  12   0   0  0
    Vector y(b,7);     // y:  2   7  12  23 29  0  0
    Vector z;          // z:  0   0   0   0  0  0  0  0  0  0
    try {
       x = y;          // x:  2   7  12  23 29  0  0
       x = Vector(a,5);// x:  7  12   0   0  0
       x += y;         // x:  9  19  12  23 29
       z = x + y;      // z: 11  26  24  46 58
       z[0] = 102;
       show(z);        // z:102  26  24  46 58
    }
    catch (int n) {
        cout<<"Not a valid array index: "<<n<<endl;
    }
    const Vector v(z);
    show(v);            // v:102  26  24  46 58
    // v[0] = v[1]+5;   // error: assignment of read-only…
}

III.2.3.2. Using type conversion operator functions

C++ language supports that classes could be assigned type conversion. The declaration of a user-defined operator function doing type conversion:

        operator type();

The type of the return value of the function equals with the type figuring in the name of the function. The type conversion operator function can only be a function without return value and argument list.

In the following example, the type Complex is realised as a class. The only constructor in the example not having an argument of type Complex converts a double into a Complex. For the opposite conversion, we create a conversion operator named double.

#include <cmath>
#include <iostream>
using namespace std;
 
class Complex {
  public:
    Complex () { re=im=0; }
    Complex(double a) : re(a), im(0) { }
    // conversion constructor
    Complex(double a, double b) : re(a), im(b) { }
    // conversion operator
    operator double() {return sqrt(re*re+im*im);}
 
    Complex operator *= (const Complex & k) {
       Complex t;
       t.re=(k.re*re)-(k.im*im);
       t.im=(k.re*im)+(re*k.im);
       return *this = t;
    }
    void Print() const { cout << re << "+" << im << "i"; }
  private:
    double re, im;
  friend Complex operator*(const Complex&, const Complex&);
};
 
Complex operator*(const Complex& k1, const Complex& k2) {
   Complex k=k1;
   k*= k2;
   return k;
}
 
int main() {
   Complex k1(7), k2(3,4), k3(k2);
   cout << double(k3)<< endl;           // printed value:  5
   cout <<double(Complex(10))<< endl;   // printed value: 10
   Complex x(2,-1), y(3,4);
   x*=y;
   x.Print();                           // 10+5i
}

It should be noted that three constructors of the class Complex can be replaced by one if default arguments are used:

        Complex(double a=0, double b=0) : re(a), im(b) {}

III.2.3.3. Extending classes with input/output operations

C++ makes it possible for us to "teach" I/O data streams based on classes to handle the objects of user-defined classes. From among the data stream classes, istream is responsible for data input, whereas ostream for data output.

In order to carry out input/output operations, we use the overloaded versions of the operators >> and <<. In order to achieve the desired functioning, we should write our proper versions of the above mentioned operations as friend operator functions, as it can be seen in the extended version of the class Complex:

#include <cmath>
#include <sstream>
#include <iostream>
using namespace std;
 
class Complex {
  public:
    Complex(double a=0, double b=0) : re(a), im(b) {}
    operator double() {return sqrt(re*re+im*im);}
 
    Complex operator *= (const Complex & k) {
       Complex t;
       t.re=(k.re*re)-(k.im*im);
       t.im=(k.re*im)+(re*k.im);
       return *this = t;
    }
  private:
    double re, im;
  friend Complex operator*(const Complex&, const Complex&);
  friend istream & operator>>(istream &, Complex &);
  friend ostream & operator<<(ostream &, const Complex &);
};
 
Complex operator*(const Complex& k1, const Complex& k2) {
   Complex k=k1;
   k*= k2;
   return k;
}
 
// The format of data input: 12.23+7.29i and 12.23-7.29i
istream & operator>>(istream & is, Complex & c) {
   string s;
   getline(is, s);
   stringstream ss(s);
   if (!(ss>>c.re>>c.im))
      c=Complex(0);
   return is;
}
 
// The format of data output: 12.23+7.29i and 12.23-7.29i
ostream & operator<<(ostream & os, const Complex & c) {
   os<<c.re<<(c.im<0? '-' : '+')<<fabs(c.im)<<'i';
   return os;
}
 
int main() {
   Complex a, b;
   cout<<"Please enter a complex number: ";  cin >> a;
   cout<<"Please enter a complex number: ";  cin >> b;
   cout<<"The product of the complex numbers: " << a*b;
   cout<<endl;
}

III.3. Inheritance (derivation)

The previous sections treated how tasks can be solved with classes independent from one another. However, object-oriented programming offers more features besides that. When a problem is processed in an object-oriented manner, the program developing method named derivation (inheritance) is often applied. Derivation makes it possible to use the data and operations of already existing classes in a new aspect or to use or expand them in compliance with the need of the new task. Consequently, problems are not solved with the help of one (big) class but with the help of a system (generally a hierarchy) of classes.

Inheritance is the most important feature of object-oriented C++. This mechanism makes it possible to create (to derive) new classes from already existing ones. Derivation means that a new class inherits the public and protected properties (data members) and behaviour (member functions) of already existing classes and it then uses them as its own. However, already existing classes may be extended with a new class, new data members and member functions may be defined or inherited member functions may be reinterpreted (replaced) if they become deprecated concerning their functioning (polymorphism).

Specialised literature uses many expressions to designate the notions related to inheritance. First, we will recapitulate these expressions and underline those that are used in C++.

class A, that is derived or from which members are inherited:

base class , ancestor class , parent class, superclass

the operation:

inheritance , derivation , extending, subclassing

class B, the result of derivation:

descendant class , derived class , extended class, child class, subclass

The C++ program code that realises the relation above:

class ClassA {
    // ...
};
 
class ClassB : public  ClassA {
    // ...
};

Specialised literature does not use notions consequently: for example, the notions 'base class' or 'ancestor class' of a given class may designate any ancestor and the notions 'descendant class' or 'derived class' may be used to designate any class created by derivation. The present book uses these notions to designate direct ancestor and direct descendant classes, respectively.

The multiple inheritance of I/O classes in C++
Figure III.5. The multiple inheritance of I/O classes in C++


C++ supports multiple inheritance, during which a new class is derived from more base classes (that is it has more direct ancestors) (Figure III.5, “The multiple inheritance of I/O classes in C++”). The class structure that results from multiple inheritance has a mesh structure which is difficult to manage and to interpret. That is why, this solution is only used at a restricted extent. So it is often replaced with single inheritance. The latter means that a class may have at most one ancestor class and any number of descendants. Multi-level inheritance results in a real tree structure (class hierarchy) (Figure III.6, “Hierarchy of geometrical classes”).

Hierarchy of geometrical classes
Figure III.6. Hierarchy of geometrical classes


III.3.1. Derivation of classes

A derived (descendant) class is a class that inherits its data members and member functions from one or more already defined class(es). The class from which a derived class inherits is called base class (ancestor class). A derived class may also become a base class for other classes, so it can contribute to a class hierarchy.

A derived class inherits all the members of its base class; however, it only have access to the public and protected members of its base class as its own. The protected access involves a double behaviour. It means private access to the users of the given class, who create objects from that; however, it offers public access to developers who derive new classes from it. In general, member functions are defined to be public or protected while data members are protected or private. (Private access hides these members even from the member functions of their derived classes.) Derived classes may complete inherited members with new data members and member functions.

The place where a derivation is indicated in a program code is the class header where the mode of derivation (public, protected, private) is indicated before the names of base classes:

class Derived : public Base1, ...private BaseN
{
 // the class body
};

Independently of their access restriction in their base class, constructors, destructors, assignment operator and friend relations are not inherited. In most cases, it is public inheritance that is used because, in this way, descendant objects can replace their ancestor objects in all contexts.

The following example shows how to use inheritance by defining points in a two-dimensional plane and in a three-dimensional space. The resulting class hierarchy is very simple (the red arrow points directly to the direct base class):

class Point2D {
   protected:
      int x,y;
   public:
      Point2D(int a = 0, int b = 0) { x = a; y = b; }
      void GetPoint2D(int& a, int& b) const { a=x; b=y;}
      void Move(int a=0, int b=0) { x = a; y = b; }
      void Move(const Point2D& p) { x = p.x; y = p.y; }
      void PrintOut() const {
               cout<<'('<<x<<','<<y<<')'<<endl; } 
}
 
class Point3D : public Point2D {
   protected:
      int z;
   public:
      Point3D(int a=0, int b=0, int c=0):Point2D(a,b),z(c) {}
      Point3D(Point3D & p):Point2D(p.x, p.y),z(p.z) {}
      void GetPoint3D(int& a, int& b, int& c) const { 
           a = x; b = y; c = z; }
      void Move(int a=0, int b=0, int c=0) { 
           x = a; y = b; z = c; }
      void Move(const Point3D& p) { 
           Point2D::x = p.x; y = p.y; z = p.z;}
      void PrintOut() const {
           cout<<'('<<x<<','<<y<<','<<z<<')'<<endl;}
};
 
void Visualise(const Point2D & p) {
   p.PrintOut();
}
 
int main() {
  Point2D p1(12,23), p2(p1), p3;
  Point3D q1(7,29,80), q2(q1), q3;
  p1.PrintOut();            // (12,23)
  q1.PrintOut();            // (7,29,80)
  // q1 = p1;            // ↯ - error!
  q2.Move(10,2,4);
  p2 = q2;
  p2.PrintOut();            // (10,2)
  q2.PrintOut();            // (10,2,4)
  q2.Point2D::PrintOut();    // (10,2)
  Visualise(p2);              // (10,2)
  Visualise(q2);              // (10,2)
}

In the example, elements that are marked with blue are program elements used as a consequence of inheritance. These elements are treated later in this chapter.

It can be seen that the objects of the class created by public derivation replace the object of the base class in all cases (assignment, function argument, ...):

  p2 = q2;
  Visualise(q2);

The reason for that is that a derived class completely comprises its base class in case of inheritance. However, this is not true in the opposite direction, so the following value assignment leads to a compilation error:

  q1 = p1;    // ↯ 

The keywords public, protected and private used in a derivation list restrict the access of inherited (public and protected) members in their new classes, in the way summarised in the following table:

Mode of inheritance

Access in the base class

Access in the derived class

public

public

protected

public

protected

protected

public

protected

protected

protected

private

public

protected

private

private

When a class is derived as public, the inherited members keep their access mode in their base class, whereas if a derivation is private, the inherited members become the private members in the derived class, therefore they become hidden to the users of the new class and to developers who would develop it further. If a class is derived as protected, the inherited members become protected in the new class so they can be further inherited. (The default derivation mode for base classes of type class is private, whereas for those of type struct, it is public.)

This default behaviour leads to an appropriate result in most cases and offers an appropriate access to the inherited members of derived classes. However, the access of any member (the access type of which is protected or public in the base class) can be manually set directly, too. For that purpose, the name of the members qualified with the base class has to be simply inserted in the appropriate access type group. It should be made sure that a new access type cannot give members an access superior to that in their base class. For example, if a member that is protected in the ancestor class is inherited privately, it automatically becomes private in the derived class, but it can be placed in the protected access group (but not in the public group).

To exemplify this, let's derive by private derivation the class named Point3D but let's assign the data members the same access restriction as the one they had if the derivation was public.

class Point3D : private Point2D {
   protected:
      int z;
      Point2D::x;
      Point2D::y;
   public:
      Point3D(int a=0, int b=0, int c=0):Point2D(a,b),z(c) {}
      Point3D(Point3D & p):Point2D(p.x, p.y),z(p.z) {}
      void GetPoint3D(int& a, int& b, int& c) const {
           a = x; b = y; c = z; }
      void Move(int a=0, int b=0, int c=0) {
           x = a; y = b; z = c; }
      void Move(const Point3D& p) {
           x = p.x; y = p.y; z = p.z;}
      void PrintOut() const {
           cout<<'('<<x<<','<<y<<','<<z<<')'<<endl;}
      Point2D:: GetPoint2D;
};

III.3.2. Initialising base class(es)

In order that base class(es) be initialised, it is the extended version of member initialisation lists that is used, where, besides members, the constructor calls of direct ancestors are also enumerated.

class Point3D : public Point2D {
   protected:
      int z;
   public:
      Point3D(int a=0, int b=0, int c=0):Point2D(a,b),z(c) {}
      Point3D(Point3D & p):Point2D(p.x, p.y),z(p.z) {}
      // …
};

When a derived class is instantiated, the compiler calls the constructors in the following order:

  • The constructors of the base class are executed in the order corresponding to the initialisation list.

  • The constructors of the member objects of the derived class are called in the order in which object members were defined (they do not figure in this example).

  • The constructor of the derived class is executed.

Calling the constructor of the ancestor class is not necessary, if the base class has a constructor without parameters, which is called automatically by compilers. Since this condition is fulfilled in this example, the second constructor can be realised in the following form:

      Point3D(Point3D & p) { *this = p;}

(Another condition of that solution is public inheritance, which is also fulfilled.)

When a class hierarchy is developed, it is enough that each class takes care of initialising only its direct ancestor(s). In that way, all parts of the instance of classes that are situated at a higher level (farer from the root) are automatically assigned an initial value if the object is created.

When the derived object instance is destroying, destructors are executed in the order that is reverse to the above described one.

  • The destructor of the derived class is executed.

  • The destructors of the member objects of the derived class are called in the order reverse to their definition.

  • The destructors of base classes are executed, in an order opposite to that of the classes in the derivation list.

III.3.3. Accessing class members in case of inheritance

In Section III.2, “Classes and objects” class members were divided into two groups on the basis of their access: accessible and inaccessible. These two groups are further nuanced by inherited and not inherited categories. Section III.3.1, “Derivation of classes” that detailed the way of deriving classes, described how basic access restrictions work. Now we will only give an overview of further solutions that precise what we have already learnt about inheritance.

III.3.3.1. Accessing inherited members

In general, the inherited members of derived classes can be accessed in the same way as their own members. However, if a data member or a member function is created with the same name in the derived class as the name of an inherited member, then the member of the ancestor class becomes hidden. In such cases, the scope operator has to be used to reference them:

        Class_name::member_name

Compilers identify member names together with their class scope, that is why all member names can be used in the form above. The example code of Section III.3.1, “Derivation of classes” shows us some examples for the things that has been said.

class Point3D : public Point2D {
   protected:
      int z;
   public:
      Point3D(int a=0, int b=0, int c=0):Point2D(a,b),z(c) {}
      Point3D(Point3D & p):Point2D(p.x, p.y),z(p.z) {}
      // …
      void Move(const Point3D& p) { 
           Point2D::x = p.x; y = p.y; z = p.z;}
      // …
};
int main() {
  Point2D p1(12,23), p2(p1), p3;
  Point3D q1(7,29,80), q2(q1), q3;
  q2.Point2D::PrintOut();
  q2.Point2D::Move(1,2); // Moving in the x-y plane
  q2.Point2D::Move(p1);
  // …
}

The following table summarises what kind of members the classes of the example code have access. If a member is hidden, it is provided with their class name:

The members of the base class Point2D:

The members of the derived class Point3D

protected: x, y

public: Point2D(),

GetPoint2D(), Move(int…), Move(const…), PrintOut()

protected: x, y, z

public: Point3D(int…), Point3D(Point3D&…),

GetPoint2D(),Point2D()::Move(int…), Point2D()::Move(const…), Point2D()::PrintOut(), GetPoint3D(), Move(int…), Move(const…), PrintOut()

III.3.3.2. The friend relationship in inheritance

In a derived class, a friend of the base class can only access the members inherited from the base class. A "friend" of a derived class can only access public and protected members from the base class.

III.3.4. Virtual base classes in case of multiple inheritance

In case of multiple inheritance, it may be a problem if the same base class appears as many instances in the derived class. If virtual base classes are used, problems of that type can be avoided (Figure III.7, “Using virtual base classes”).

class Base {
    int q;
  public:
     Base(int v=0) : q(v) {};
     int GetQ() { return q;}
     void SetQ(int q) { this->q = q;}
};
 
// the virtual base class named Base
class Base1 : virtual public Base {
    int x;
 public:
    Base1(int i): x(i) {}
};
 
// the virtual base class named Base
class Base2: public virtual Base {
    int y;
  public:
    Base2(int i): y(i) {}
};
 
class Descendant:  public Base1,  public Base2 {
    int a,b;
  public:
    Descendant(int i=0,int j=0): Base1(i+j),Base2(j*i),a(i),b(j) {}
};
int main() {
   Descendant descendant;
   descendant.Base1::SetQ(100);
   cout << descendant.GetQ()<<endl;          // 100
   cout << descendant.Base1::GetQ()<<endl;   // 100
   cout << descendant.Base2::GetQ()<<endl;   // 100
   descendant.Base1::SetQ(200);
   cout << descendant.GetQ()<<endl;          // 200
   cout << descendant.Base1::GetQ()<<endl;   // 200
   cout << descendant.Base2::GetQ()<<endl;   // 200
}

Using virtual base classes
Figure III.7. Using virtual base classes


A virtual base class will only be present in only one instance in its derived classes, independently of its number of occurrences in the inheritance chain. In the example, the data member named q of the virtual base class are inherited both by the base classes Base1 and Base2. Because of its virtuality, the class named Base occurs only in one instance, so Base1::q and Base2::q reference the same data member. Without using the keyword virtual, Base1::q and Base2::q designate different data members, which leads to a compilation error, since the compiler will not know for sure how to resolve the reference descendant.GetQ().

III.3.5. Inheritance and/or composition?

A big advantage of C++ programming language is that it supports the reusability of program code. Reusing means that a new program code is made without modifying the original one. If the object-oriented tools of C++ are used, there are three approaches to choose from:

  • The most simple and frequent reuse of a code stored in a given class is when an object instance is created or when already existing objects ( cin , cout , string , STL etc.) are used in a program.

class X {
   // …
};
      
int main() {
   X a, *pb;
   pb = new X();
   cout<<"C++"<<endl;
   // …
   delete pb;
}
  • Another possibility is to place objects of other classes in our own codes as member objects. Since the new class is created by reusing already existing ones, this method is called composition. During composition, the relation between new and included objects becomes a (has-a) relation. If the new object will only contain a pointer or a reference to other objects, it is called an aggregation.

class X {
   // …
};
 
class Y {
  X x;    // composition
};    
 
class Z {
  X& x;  // aggregation
  X *px;
};
  • The third solution is related to the subject of the present chapter. When a new class is created by public derivation from other classes, then the relationship is of an is-a type. This relation means that a derived object behaves exactly the same way as its ancestor class (that is the derived object is also an ancestor object); however, this is not true in the reverse direction.

// base class
class X {
   // …
};
 
// derived class
class Y : public X {
   // …
};    

If a problem is solved in an object-oriented way, it has to be determined that the desired model is achieved better by inheritance or by composition. The decision is not easy to take but the following guidelines may help a lot:

  • The starting point should be composition. However, if later it turns out that a class is actually a special type of another class, inheritance should be used.

  • Derivation is really needed if there is a need to cast the type of a derived class to the base class. For example, if all elements of a geometrical system is to be stored in a linked list.

  • The starting point should be composition. However, if later it turns out that a class is actually a special type of another class, inheritance should be used.

The following two example codes clearly show the differences and similarities between composition and derivation. In both cases, it is the class Point that is reused. This class is stored in the file named Point.h.

#ifndef __POINT_H__
#define __POINT_H__
class Point {
   protected:
      int x,y;
   public:
      Point(int a = 0, int b = 0) : x(a), y(b) {}
      Point(const Point& p) : x(p.x), y(p.y) {}
      int GetX() const { return x; }
      int GetY() const { return y; }
      void SetX(int a) { x = a; }
      void SetY(int a) { y = a; }
      void Move(int a, int b) { x = a; y = b; }
      void Move(const Point& p) { x = p.x; y = p.y; }
      void PrintOut() const { cout<<'('<<x<<','<<y<<')'<< endl; }
};
#endif

III.3.5.1. Reuse with composition

In real life, complex objects are often made up of smaller and simpler objects. For example, a car can be put together from a chassis, an engine, some tyres, a gear box, steerable wheels and a huge quantity of other spare parts. If the composition is done, the car has-a gearbox, has an engine, etc.

In the example named Circle, the circle has a centre which is stored in a member object of type Point named p. It should be noted that only the public members of the class Point can be accessed from the member functions of the class Circle.

#include "Point.h"
 
class Circle {
  protected:
    int r;
  public:
    Point p; // composition
    Circle(int x=0, int y=0, int r=0)
        : p(x, y), r(r) {}
    Circle(const Point& p, int r=0)
        : p(p), r(r) {}
    int GetR() {return r;}
    void SetR(int a) { r = a; }
};
 
int main() {
    Circle c1(100, 200, 10);
    c1.p.PrintOut();
    cout<<c1.p.GetX()<<endl;
    cout<<c1.GetR()<<endl;
}

III.3.5.2. Reuse by public inheritance

Like for composition, there are many examples in real life for inheritance. Everyone inherits genes from his/her parents; C++ language has also inherited many things from C language, which also inherited one part of its properties of its ancestors. In inheritance, the attributes and behaviour of base class(es) are obtained directly, and they may be extended or precised.

In our example, the object of type Point as the centre of the circle becomes integrated in the object of type Circle, which completely complies with the definition of a circle. Contrary to composition, the protected and public members of the class Point can be accessed from the member functions of the class Circle.

#include "Point.h"
 
class Circle : public Point { // inheritance
  protected:
    int r;
  public:
    Circle(int x=0, int y=0, int r=0)
        : Point(x, y), r(r) {}
    Circle(const Point & p, int r=0)
        : Point(p), r(r) {}
    int GetR() {return r;}
    void SetR(int a) { r = a; }
};
 
int main() {
    Circle c1(100, 200, 10);
    c1.PrintOut();
    cout<<c1.GetX()<<endl;
    cout<<c1.GetR()<<endl;
}

III.4. Polymorphism

In C++, polymorphism rather means that the object of a derived class is accessed by a pointer or a reference in the base class. Although the subject of this subsection is the solution called subtype or run-time polymorphism or simply overriding, let's review further "polymorphous" solutions of C++.

  • Coercion polymorphism means implicit and explicit type casts. In that case, the polymorphism of a given operation is made possible by different types that may be converted if needed.

  • As an opposite to coercion, the so-called ad-hoc („for that purpose”) polymorphism is better known by the name of function overloading. In that case, a compiler chooses the appropriate function from the variants on the basis of parameter types.

  • The extended version of this polymorphism is called parametrical or compile-time polymorphism, which makes it possible to execute the same code with any type. In C++, parametric polymorphism is realised by function and class templates. Using templates actually means reusing a C++ source code.

Previously, we have said that a derived class inherits all properties and behaviour (operations) of its ancestor. These inherited member functions can be reused without restrictions in the objects of the derived class since they comprise their ancestors. Since a derived class is often specialised, it may be necessary that certain inherited functions work in another way. This need is satisfied by introducing virtual member functions. Thanks to real-time polymorphism, an object may react to the same message in another way, depending on the level it occupies in the class hierarchy. And the choice of the member function that will be called from the inheritance chain only becomes known during execution (late binding).

III.4.1. Virtual member functions

A virtual function is a public or protected member function of the base class. It can be redefined in the derived class in order that the behaviour of the class would change. A virtual function is generally called by a reference or a pointer of a public base class, the actual value of which is determined at run-time (dynamic binding, late binding).

In order that a member function become virtual, the keyword virtual has to be provided in the class before the declaration of the function:

class Example {
  public:
    virtual int vf();
};

It is not necessary that a virtual function in the base class have a definition as well. Instead, the prototype of the function should be ended with the expression =0; . In that case, it is a so-called pure virtual function:

class Example {
  public:
    virtual int pvf() = 0;
};

An object instance cannot be created from a class containing one or more pure virtual functions (an abstract class). An abstract class can only be used as a starting point, as a base class for inheritance.

In case a member function becomes virtual at a level in the class hierarchy, then it can be replaced in any subsequent class in the inheritance chain.

III.4.2. Redefining virtual functions

If a function is declared as virtual in the base class, then it preserves this property during inheritance. In the derived class, a virtual function can be redefined with another version but the inherited version may also be used. If a new version is defined, it is not necessary to use the keyword virtual.

If a derived class inherits a pure virtual function, it has to be redefined by a new version, otherwise the new class will also be abstract. A derived class may contain virtual functions that it has not inherited from its direct base class.

The prototype of a virtual function redefined in the derived class has to be precisely identical (name, type, parameter list) with the one defined in the base class. If the parameterizations of the two declarations are not completely identical, then it is not redefining but overloading.

In the following example code, each shape calculates its own area and perimeter; however, visualisation is carried out by an abstract base class (Shape). The hierarchy of class is represented in the following figure:

// Abstract base class
class Shape {
  protected:
     int x, y;
  public:
     Shape(int x=0, int y=0) : x(x), y(y) {}
     virtual double Area()=0;
     virtual double Perimeter()=0;
     void Visualise() {
          cout<<'('<<x<<','<<y<<")\t";
          cout<<"\tArea: "<< Area();
          cout<<"\tPerimeter: "<< Perimeter() <<endl;
     }
};
 
class Square : public Shape {
  protected:
     double a;
  public:
     Square(int x=0, int y=0, double a=0) 
             : Shape(x,y), a(a) {}
     double Area() {return a*a;}
     double Perimeter() {return 4*a;}
};
 
class Rectangle : public Square {
  protected:
     double b;
  public:
     Rectangle(int x=0, int y=0, double a=0,  double b=0)
             : Square(x,y,a), b(b) {}
     double Area() {return a*b;}
     double Perimeter() {return 2*(a+b);}
};
 
class Circle : public Square {
  const double pi;
  public:
     Circle(int x=0, int y=0, double r=0)
             : Square(x,y,r), pi(3.14159265) {}
     double Area() {return a*a*pi;}
     double Perimeter() {return 2*a*pi;}
};
 
int main() {
     Square s(12,23,10);
     cout<<"Square: ";
     s.Visualise();
 
     Circle c(23,12,10);
     cout<<"Circle: ";
     c.Visualise();
 
     Rectangle r(12,7,10,20);
     cout<<"Rectangle: ";
     r.Visualise();
 
     Shape* shapes[3] = {&s, &c, &r} ;
     for (int i=0; i<3; i++)
       shapes[i]->Visualise();
}

Virtual functions and public inheritance make it possible to create external functions that can be called by every object in the class hierarchy:

void VisualiseAll(Shape& a) {
     cout<<"Area: "<<a.Area()<<endl;
     cout<<"Perimeter: "<<a.Perimeter()<<endl;
}

III.4.3. Early and late binding

In order to explain more how run-time polymorphism works, we will examine, through examples, the resolution of member function calls at compilation time (early binding) and at run-time (late binding).

In the example codes, two member functions having the same prototype are defined (GetName(), GetValue()) both in the base class and the derived class. The main() function contains a pointer and a reference to an instance of the derived class. The pointer (pA) and the reference (rA) are of the type of the base class.

III.4.3.1. Static early binding

During early binding, compilers integrate statically direct member function calls into the code. In the case of classes, this is the default operation mode, which is clearly reflected by the following example code.

Early binding example
Figure III.8. Early binding example


class Base {
 protected:
     int value;
 public:
     Base(int a=0) : value(a) { }
     const char* GetName() const { return "Base"; }
     int GetValue() const { return value; }
 };
 
class Derived: public Base {
 protected:
     int value;
 public:
     Derived(int a=0, int b=0) : Base(a), value(b) { }
     const char* GetName() const { return "Derived"; }
     int GetValue() const { return value; }
 };
 
int main() {
  Base a;
  Derived b(12, 23);
 
  a = b;
  Base &rA = b;
  Base *pA = &b;
 
  cout<<"a \t" <<  a.GetName()<<"\t"<<  a.GetValue()<<endl;
  cout<<"a \t" <<  b.GetName()<<"\t"<<  b.GetValue()<<endl;
  cout<<"rA\t" << rA.GetName()<<"\t"<< rA.GetValue()<<endl;
  cout<<"pA\t" << pA->GetName()<<"\t"<< pA->GetValue()<<endl;
}

The calls of the member function GetValue() is presented in Figure III.8, “Early binding example”. The results:

a       Base    12
b       Derived 23
rA      base    12
pA      Base    12

III.4.3.2. Dynamic late binding

The situation changes a lot (Figure III.8, “Early binding example”), if the member functions GetName(), GetValue() are made virtual in the class Base.

class Base {
 protected:
     int value;
 public:
     Base(int a=0) : value(a) { }
     virtual const char* GetName() const { return "Base"; }
     virtual int GetValue() const { return value; }
 };

The results have also changed:

a       Base      12
b       Derived   23
rA      Derived   23
pA      Derived   23

Late binding example
Figure III.9. Late binding example


Compilers place the calls of virtual functions indirectly in the code: as jumps to the address stored in the memory. The virtual method table (VMT) used for storing addresses is created for each class during execution, at the first instantiation of the given class. VMT contains the address of redefined virtual functions. Virtual functions having the same name and being situated in the class hierarchy have the same index in these tables, which makes it possible to replace completely virtual member functions.

III.4.3.3. Virtual method table

In case a class has one or more virtual member functions, compilers complete the object with a "virtual pointer" to the global data table called virtual method table (VMT) or virtual function table (VFTable). VMT contains function pointers to the virtual member functions redefined the last of the given class and the base classes (Figure III.10, “Virtual method tables of the example code”). The address of the virtual functions having the same name has the same index in these tables.

Virtual method tables of the example code
Figure III.10. Virtual method tables of the example code


VMTs for classes are created at run-time when the first constructor is called. As a consequence, the relation between the caller and the called member function is also realised at run-time. The compiler will only insert in the code a call that takes place by using the ith element of the VMT (call VMT[i]).

III.4.4. Virtual destructors

A destructor can be defined as a virtual function. If the destructor of the base class is virtual, then the destructor of all classes derived from that will also be virtual. That is why, it is sure that it is always the appropriate destructor that is called if the object ceases to exist even if a pointer or a reference of the type of a base class references that instance of the derived class.

In order to activate this mechanism, it is enough to place a virtual, empty destructor in a class somewhere in the beginning of the inheritance chain:

class Base {
 protected:
     int value;
 public:
     Base(int a=0) : value(a) { }
     virtual const char* GetName() const { return "Base"; }
     virtual int GetValue() const { return value; }
     virtual ~Base() {}
 };

III.4.5. Abstract classes and interfaces

As we have already seen, abstract classes are good bases for inheritance chains. In C++, there are no keywords to designate abstract classes, they can only be recognised if we know whether they contain pure virtual functions or not. The reason for treating them again in this part is to follow the traditions of other programming languages, and to find elements from them that can be realised in C++ as well.

The Java, C# and Object Pascal programming languages support only single inheritance; however, they make it possible to implement any number of interfaces. In a C++ environment, an interface is an abstract class that only has virtual functions. The only aim of interfaces is to force developers (if they derive them) to create the member functions that have been declared as public in them.

The traps of multiple inheritance can be avoided if the base classes only contain one "real" class (that is a class that has data members, too) and the others are only interface classes. (The names of interfaces classes generally begin with a capital "I".)

To exemplify interfaces, in a previous class named Point, we separate now the class storing the geometrical values and the interface defining the ability to move, since the latter is not always needed.

// the geometrical Point class
class Point {
   protected:
      int x, y;
   public:
      Point(int a = 0, int b = 0) : x(a), y(b) {}
      Point(const Point& p) : x(p.x), y(p.y) {}
      int GetX() const { return x; }
      int GetY() const { return y; }
      void SetX(int a) { x = a; }
      void SetY(int a) { y = a; }
      void PrintOut() const { cout<<'('<<x<<','<<y<<')'<< endl; }
};
 
// an abstract class needed for moving - interface
class IMove {
   public:
      virtual void Move(int a, int b) = 0;
      virtual void Move(const Point& p) = 0;
};
 
// a Point that is able to move
class MovingPoint: public Point, public IMove {
    public:
      MovingPoint(int a=0, int b=0) : Point(a,b) {}
      void Move(int a, int b) { x = a; y = b; }
      void Move(const Point& p) {
          x = p.GetX();
          y = p.GetY();
      }
};
 
int main() {
    Point fixPoint(12, 23);
    fixPoint.PrintOut();         // (12, 23)
 
    MovingPoint movingPoint;
    movingPoint.PrintOut();       // (0, 0)
    movingPoint.Move(fixPoint);
    movingPoint.PrintOut();       // (12, 23)
}

III.4.6. Run-time type informations in case of classes

Visual development kits store Run-time Type Information, RTTI for each object instances. With the help of them, it is executing environments that may be entrusted with identifying the type of objects, so new data members do not have to introduced for that purpose.

In order that RTTI mechanism function correctly, a polymorphous base class has to be created, that is at least a virtual member function should be inserted into it and the storage of RTTI has to be enabled. (The possibility to enable this feature can be found among the settings of the compiler.) In order that the type of pointers and references be identified, the operations dynamic_cast and typeid are used. In order that appropriate type cast be carried out, the operator dynamic_cast is used.

The operator typeid returns an object of type const type_info , the members of which provide information about the type of the operand. The character sequence returned by the member function named name() of the object depends on the used compiler. In order to use the operator, the header file typeinfo should be included in the code.

#include <typeinfo>
#include <iostream>
using namespace std;
 
class Ancestor {
   public:
      virtual void Vf(){} // without this, RTTI is not stored
      void FunctAncestor() {cout<<"Ancestor"<<endl;}
  };
 
class Descendant : public Ancestor {
   public:
      void FunctDescendant() {cout<<"Descendant"<<endl;}
 };
 
int main() {
   Descendant * pDescendant = new Descendant;
   Ancestor * pAncestor = pDescendant;
 
    // the type_info of the pointer:
   const type_info& tiAncestor = typeid(pAncestor); 
   cout<< tiAncestor.name() <<endl;
 
   // the type_info of the Descendant:
   const type_info& tiDescendant = typeid(*pAncestor); 
   cout<< tiDescendant.name() <<endl;
 
   // points to the Descendant?
   if (typeid(*pAncestor) == typeid(Descendant)) 
      dynamic_cast<Descendant *>(pAncestor)
                                      ->FunctDescendant();
 
   // points to the Descendant?
   if (dynamic_cast<Descendant*>(pAncestor)) 
      dynamic_cast<Descendant*>(pAncestor) 
                                      ->FunctDescendant();
   delete pDescendant;
} 

The following example code needs run-time type information if members of different classes are to be accessed.

#include <iostream>
#include <string>
#include <typeinfo>
using namespace std;
 
class Animal {
  protected:
     int legs;
  public:
     virtual const string Species() = 0;
     Animal(int n) {legs=n;}
     void Info() {
       cout<<"A(n) "<<Species()<<" has "
           <<legs<<" leg(s)."<<endl;
      }
};
 
class Fish : public Animal {
  protected:
     const string Species() {return "fish";}
  public:
     Fish(int n=0) : Animal(n) {}
     void Swims() {cout<<"swims"<<endl;}
};
 
class Bird : public Animal {
  protected:
     const string Species() {return "bird";}
  public:
     Bird(int n=0) : Animal(n) {}
     void Flies() {cout<<"flies"<<endl;}
};
 
class Mammal : public Animal {
  protected:
     const string Species() {return "mammal";}
  public:
     Mammal(int n=4) : Animal(n) {}
     void Runs() {cout<<"runs"<<endl;}
};
 
int main() {
    const int db=3;
    Animal* p[db] = {new Bird, new Fish, new Mammal};
 
    // accessing data without RTTI
    for (int i=0; i<db; i++)
      p[i]->Info();
 
    // processing with the help of RTTI
    for (int i=0; i<db; i++)
       if (dynamic_cast<Fish*>(p[i]))        // Fish?
             dynamic_cast<Fish*>(p[i])->Swims();
       else
       if (typeid(*p[i])==typeid(Bird))           // Bird?
             dynamic_cast<Bird*>(p[i])->Flies();
       else
       if (typeid(*p[i])==typeid(Mammal))     // Mammal?
             dynamic_cast<Mammal*>(p[i])->Runs();
 
    for (int i=0; i<db; i++)
        delete p[i];
}

For the sake of comparison, let's also have a look at the version of the code above that does not use run-time type information. In that case, the value of the virtual function Species() of the class Animal can be used to identify the class and type cast is carried out by the operator static_cast. It is only the content of the main() function that has changed:

int main() {
    const int db=3;
    Animal* p[db] = {new Bird, new Fish, new Mammal};
 
    for (int i=0; i<db; i++)
      p[i]->Info();
 
    for (int i=0; i<db; i++)
       if (p[i]->Species()=="fish")
             static_cast<Fish*>(p[i])->Swims();
       else
       if (p[i]->Species()=="bird")
             static_cast<Bird*>(p[i])->Flies();
       else
       if (p[i]->Species()=="mammal")
             static_cast<Mammal*>(p[i])->Runs();
 
    for (int i=0; i<db; i++)
        delete p[i];
}

The results of executing both versions are the same:

A(n) bird has two leg(s).
A(n) fish has 0 leg(s).
A(n) mammal has 4 leg(s).
flies
swims
runs

III.5. Class templates

The solutions of most typed languages depend on types: when a useful function or class is created, it only works correctly with data of the types fixed in it. If the same function/class is to be used for other types of data, it has to be rewritten by replacing the concerned types.

C++ language introduces function and class templates to prevent developers from doing this kind of type replacing method. The only task of a programmer in C++ is to write the needed function or class and to mark all types to be replaced, and the other things are done by compilers.

III.5.1. A step-be-step tutorial for creating and using class templates

As an introduction, we will review how to make and use templates step by step. For that purpose, we rely on knowledge already treated in the present book.

As an example, let's see the class named IntArray, which is a simplified class handling an one-dimensional integer array of 32 elements with bound index checking.

#include <iostream>
#include <cassert>
#include <cstring>
using namespace std;
 
class IntArray {
  public:
     IntArray(bool initialise = true) : size(32) {
        if (initialise) memset(storage, 0, 32*sizeof(int));
     }
     int& operator [](int index);
     const int size;
  private:
     int storage[32];
};
 
int & IntArray::operator [](int index) {
  if (index<0 || index>=32) assert(0);     // index error
  return storage[index];              // success
}

The objects of this class not only store 32 integer elements but also check index bounds when an element is accessed. For the sake of simplicity, a wrong index makes the program exit. When an array (an object) is created, all elements are initialised to zero,

IntArray a;

except for the case where the constructor is called by a false argument.

IntArray a(false);

The array stores the number of elements in the constant data member named size and redefines the indexing operator. The elements of the array can be accessed in the following way:

int main() {
    IntArray a;
    a[ 7] = 12;
    a[29] = 23;
    for (int i=0; i<a.size; i++)
        cout<<a[i]<<'\t';
}

What should we do if we do not want to store 32 elements or if we need to store data of type double? We should use a class template, which generalises the int types of the class IntArray (all marked with red in the example). And the number of elements (32) is passed to the class as a parameter.

#include <iostream>
#include <cassert>
#include <cstring>
using namespace std;
 
template <class type, int numberOfElements>
class Array {
  public:
     Array(bool initialise=true): size(numberOfElements) { 
        if (initialise) 
         memset(storage,0,numberOfElements*sizeof(type)); }
     type& operator [](int index);  
     const int size;
  private:
     type storage[numberOfElements];
};

In the case of external member functions, the name of the class has to be used together with the generic type and the parameter Array<type, numberOfElements>:

template <class type, int numberOfElements>
type & Array<type, numberOfElements>::operator [](int index) { 
  if (index<0 || index>=numberOfElements) 
     assert(0);                // index error
  return storage[index];    // success
}

It should be noted that the non-implicit inline member functions of a class template have to be included in each source file from which they are called. Otherwise, a compiler will not be able to create the source code of the function. In the case of a project consisting of more source modules, the elements of a class template (together with the member functions defined outside the class) should be placed in a header file, which can be included in all source modules without receiving a "multiply defined symbols" error message.

The main aim of a class template (a generic class) is to make compilers create the real, type-dependant class together with all of its components on the basis of the template, just as in the case of the already presented function templates. A class template is always used with parameters when objects are to be created:

Array<int, 32> av, bv(false);

By defining a type

typedef Array<int, 32> IntArray;

creating objects is simpler:

IntArray av, bv(false);

The size figuring in the definition of the template is a constant parameter, the value of which is made use of by the compiler during compilation. When a template is processed, the parameter is replaced by a constant value or a C++ constant (const). Of course, templates can also be created without constant parameters, which we will also do in the further sections of the chapter.

Before going further, let's see how a class template can help us. Its simplest usage form has already been presented, so we repeat now that code:

int main() {
    Array<int, 32> a;
    a[ 7] = 12;
    a[29] = 23;
    for (int i=0; i<a.size; i++)
        cout<<a[i]<<'\t';
}

The template created from that can be used also for storing character sequences and objects.

const int asize=8;
Array<char *, asize> s1;
s1[2] = (char*)"C++";
s1[4] = (char*)"java";
s1[7] = (char*)"C#";
for (int i=0; i<s1.size; i++)
    if (s1[i]) cout<<s1[i]<<'\t';

It should be noted that the solution that we use now to initialise all elements to zero is too radical for class type array elements, so it has to be avoided when the constructor of the class Array is called with a false argument. (In that case, the constructors of array elements are called, and then the constructor of the class Array<string, 8>, which frees by default the memory space of already initialised element objects.)

const int asize=8;
Array<string, asize> s2 (false);
s2[2] = "C++";
s2[4] = "java";
s2[7] = "C#";
for (int i=0; i<s2.size; i++)
    cout<<s2[i]<<'\t';

Of course, an array object can be created dynamically as well; however, then we should make sure to use indexing correctly and to access correctly the data member size. The array object can be accessed by the expression (*dt) or dt[0], which is followed by the indexing operator:

Array<double, 3> *dt;
dt = new Array<double, 3>;
(*dt)[0] =12.23;
 dt[0][1]=34.45;
 for (int i=0; i<dt->size; i++) 
      cout<<(*dt)[i]<<'\t';  
 delete dt; 

By dynamic memory allocation, the vector of five elements of the array objects of type Array<double, 3> can be easily created. In order to access data elements of type double, let's choose double indexing. The first index indicates the element within the dynamic array, whereas the second one within the object of type Array<double, 3>. Apparently, this solution leads to a two-dimensional array of type double.

Array<double, 3> *dm;
dm = new Array<double, 3> [5];
dm[0][1] =12.23;
dm[4][2]=34.45;
for (int i=0; i<5; i++) {
  for (int j=0; j<dm[0].size; j++)
      cout<<dm[i][j]<<'\t';
  cout<<endl;
}
delete []dm;

In case a static array is created instead of the dynamic memory allocation vector of five elements, the solution remains similar:

Array<int, 3> m[5]; 
m[0][1] = 12;
m[4][2] = 23;
for (int i=0; i<5; i++) {
  for (int j=0; j<m[0].size; j++)
      cout<<m[i][j]<<'\t';
  cout<<endl;
} 

Finally, let's have a look at the following instantiation where an object vector of five elements is created. All of its elements is of type Array<int,3>. The result is a kind of a two-dimensional array containing elements of type int.

Array< Array<int,3>, 5> p(false);
p[0][1] = 12;
p[4][2] = 23;
for (int i=0; i<p.size; i++) {
  for (int j=0; j<p[0].size; j++)
      cout<<p[i][j]<<'\t';
  cout<<endl;
} 

On the basis of the examples, it can be clearly seen that class templates offer an efficient programming tool. However, using them involves thinking in the way programmers do and involves having to know completely the functioning and features of object-oriented tools of C++ language. This is especially true if a hierarchy of class templates is to be realised. In the following parts, we will review the notions and techniques of creating and using templates.

Unfortunately, this is the most difficult part of programming in C++ because it requires the complete knowledge of this language. If the Reader does not intend to develop his/her own class templates, it is enough to go to Chapter III.5.6. which details how to use the Standard Template Library.

III.5.2. Defining a generic class

A parametrized or generic class makes it possible for us to use the parametrized class as a template to create new classes. So a given class definition can be used for all types.

Let's see how to define class templates in a general way, where type1,..typeN designate type parameters. In the template heading (template<>) type parameters are indicated by the keywords class or typename:

template <class type1, … class typeN> 
class Classname {    
   …
};

or

template <typename type1, … typename typeN> 
class Classname {
   …
};

The non-inline member functions of the class should be defined as follows:

template <class type1, … class typeN > 
functtype Classname< type1, … typeN> ::
                                Functname(parameter_list) {
   …
}

or

template <typename type1, … typename typeN > 
functtype Classname< type1, … typeN> :: 
                                Functname(parameter_list) {
   …
}

As an example, let's have a look at the generic class made from the class Point, that has implicit inline member functions.

template <typename type>
class Point {
   protected:
      type x, y;
   public:
      Point(type a = 0, type b = 0) : x(a), y(b) {}
      Point(const Point& p) : x(p.x), y(p.y) {}
      type GetX() const { return x; }
      type GetY() const { return y; }
      void SetX(type a) { x = a; }
      void SetY(type a) { y = a; }
      void PrintOut() const { cout<<'('<<x<<','<<y<<')'<< endl; }
};

The same Point class becomes much more complicated if one part of its member functions is defined outside the class:

template <typename type>
class Point {
   protected:
      type x, y;
   public:
      Point(type a = 0, type b = 0) : x(a), y(b) {}
      Point(const Point& p) : x(p.x), y(p.y) {}
      type GetX() const;
      type GetY() const { return y; }
      void SetX(type a);
      void SetY(type a) { y = a; }
      void PrintOut() const;
};
template <typename type>
type Point<type>::GetX() const { return x; }
 
template <typename type>
void Point<type>::SetX(type a) { x = a; }
 
template <typename type>
void Point<type>::PrintOut() const { 
     cout<<'('<<x<<','<<y<<')'<< endl; 
}

Both version of the class Point is a generic class or a class template, which is only a declaration available in the source code and in which the compiler only checks syntax. Compilation to a machine code only takes place where a template is instantiated with concrete type arguments, that is when template classes are created.

III.5.3. Instantiation and specialisation

The relation between a class template and the classes instantiated from it is similar to the relation between a normal class and its objects. A normal class determines how objects can be instantiated from it, whereas a template class contains information about how to generate instances from it.

A template can be defined in many ways. In implicit instantiation, type parameters are replaced by concrete types. First the version of the given type of a class is created (if it has not yet been created), then the object instance:

Point<double> p1(1.2, 2.3), p2(p1);
Point<int> *pp;  // the class Point<int> is not created

In explicit instantiation, the compiler is asked to create an instance of the class by using the given types, so when the object is being created, the class is ready to be used:

template class Point<double>;
…
Point<double> p1(1.2, 2.3), p2(p1);

There are cases when it is easier to use templates if the generic version is specialised for a purpose (explicit specialisation). Among the following declarations, the first one is a general template, the second one is a version tailored to pointers, the third is a version specialised to void* pointers.

template <class type> class Point {
  // the class template above
};
 
template <class type> class Point <type *> {
  // has to be created
};
 
template <> class Point <void *> {
  // has to be created
};

These specialised versions can be used in the case of the following instantiations:

Point<double> pa;
Point<int *> pp;
Point<void *> pv;

Let's examine the functioning of instantiation and specialisation in the case of templates with two parameters. In that case, leaving out one of the template parameters leads to a partial specialisation:

template <typename T1, typename T2>
class DataStream {
   public:
      DataStream() { cout << "DataStream<T1,T2>"<<endl;}
      // …
};
 
template < typename T1, typename T2>     // Specialisation
class DataStream<T1*, T2*> {
   public:
      DataStream() { cout << "DataStream<T1*,T2*>"<<endl;}
      // …
};
 
template < typename T1>             // Partial specialisation
class DataStream<T1, int> {
   public:
      DataStream() { cout << "DataStream<T1, int>"<<endl;}
      // …
};
 
template <>                    // Complete specialisation
class DataStream<char, int> {
   public:
      DataStream() { cout << "DataStream<char, int>"<<endl;}
      // …
} ;
 
int main() {
   DataStream<char, int> s4 ;       // Complete specialisation
   DataStream<int, double> s1 ;
   DataStream<double*, int*> s2;    // Specialisation
   DataStream<double, int> s3 ;    // Partial specialisation
}

III.5.4. Value parameters and default template parameters

In the introductory example of this chapter, the class template not only had a type parameter but also an integer type value parameter. With the help of that, a constant value was passed to the compiler during instantiation.

C++ supports default template parameters. Now let's assign default values to the parameters of the class template Array.

#include <iostream>
#include <cassert>
#include <cstring>
using namespace std;
 
template <typename type=int, int numberOfElements=32>
class Array {
  public:
    Array(bool initialise=true): size(numberOfElements) {
       if (initialise) 
         memset(storage, 0, numberOfElements*sizeof(type)); 
    }
    type& operator [](int index) {
       if (index<0 || index>=numberOfElements) assert(0); 
       return storage[index]; 
     }
     const int size;
  private:
     type storage[numberOfElements];
};

In that case, in order to create the type IntArray, the generic class can be specialised without arguments:

        typedef Array<> IntArray;

The following simple example shows how to realise the Stack data structure as a class template. The parameters of the stack template will also have default values.

#include <iostream>
#include <string>
using namespace std;
 
template<typename Type=int, int MaxSize=100>
class Stack {
   Type array[MaxSize];
   int sp;
  public:
   Stack(void) { sp = 0; };
   void Push(Type data) {
      if (sp < MaxSize) array[sp++] = data;
   }
   Type Pop(void) {
      return array[sp > 0 ? --sp : sp];
   }
   bool isEmpty(void) const { return sp == 0; };
};
 
int main(void) {
  Stack<double,1000> dStack; // stack of 1000 double elements
  Stack<string> sStack;      // stack of 100 string elements
  Stack<> iStack;            // stack of 100 int elements
 
  int a=102, b=729;
  iStack.Push(a);
  iStack.Push(b);
  a=iStack.Pop();
  b=iStack.Pop();
 
  sStack.Push("language");
  sStack.Push("C++");
  do {
     cout << sStack.Pop()<<endl;;
  } while (!sStack.isEmpty());
}

III.5.5. The "friends" and static data members of a class template

A class template may also have friends, which may behave differently. Those that do not have a template specification become common "friends" of all specialised classes. Otherwise, an external function can only be used as a friend function of the given instantiated class variant.

In the first example, each instantiation of the class template BClass becomes a friend class of the class ClassA:

#include <iostream>
using namespace std;
 
class ClassA {
   void Operation() { cout << "Operation carried out."<< endl; };
   template<typename T> friend class BClass;
};
 
template<class T> class BClass {
   public:
      void Execute(ClassA& a) { a.Operation(); }
};
 
int main() {
   BClass<int> b;
   BClass<double> c;
   ClassA a;
   b.Execute(a);
   c.Execute(a);
}

In the second example, the defined friend function (Funct) is a template itself:

#include <iostream>
using namespace std;
 
// Forward declarations
template <typename T> class Class;
template <typename T> void Funct(Class<T>&);
 
template <typename T> class Class {
    friend void Funct<T>(Class<T>&);
   public:
    T GetData(){return data;}
    void SetData(T a){data=a;}
   private:
    T data;
};
 
template<typename T> void Funct(Class<T>& x) {
    cout<<"Result: "<<x.GetData()<<endl;
}
 
int main() {
    Class<int> obj1;
    obj1.SetData(7);
    Funct(obj1);
    Class<double> obj2;
    obj2.SetData(7.29);
    Funct(obj2);
}

The static data members defined in a generic class have to be created for each template class:

#include <iostream>
using namespace std;
 
template<typename type> class Class {
  public:
    static int ID;
    static type data;
    Class() {}
};
 
// Definitions of static data members
template <typename type> int Class<type>::ID = 23;
template <typename type> type Class<type>::data = 12.34;
 
int main() {
    Class <double> dObj1, dObj2;
 
    cout << dObj1.ID++   << endl;           // 23
    cout << dObj1.data-- << endl;          // 12.34
 
    cout << dObj2.ID   << endl;             // 24
    cout << dObj2.data << endl;            // 11.34
 
    cout <<Class<double>::ID << endl;        // 24
    cout <<Class<double>::data << endl;      // 11.34
}

III.5.6. The Standard Template Library (STL) of C++

The Standard Template Library (STL) is a software library and is an integral part of the Standard Library of C++ language. STL is a collection of containers, algorithms and iterators, and also contains many fundamental computational algorithms and data structures. The elements of STL are template classes and functions. In order to use these, it is recommended to understand the basics of using templates in C++.

The following review cannot replace a deeper description of the whole STL; however, it is enough to be able to use the fundamental elements of the Library in program codes.

III.5.6.1. The structure of STL

The elements of the Library can be grouped into five groups:

  • containers – data structures making it possible to store data in memory (vector, list, map, set, deque, …)

  • adaptors – higher-level data structures based on containers (stack, queue, priority_queue)

  • algorithms - operations that can be carried out on data stored in containers (sort, copy, search, min, max, …)

  • iterators – generic pointers that ensure access to the data stored in containers (iterator, const_iterator, ostream_iterator<>, … )

  • function objects – functions are covered by classes, for other components (divides, greater_equal, logical_and, …).

In compliance with template management, these features can be integrated into source codes by including the corresponding header files. The following table recapitulates the most frequently used declaration files of STL:

Short description

Header file

Managing, sorting data in  containers and searching in them

<algorithm>

Associative container for storing bits: bitset

<bitset>

Associative containers that store elements: multiset (may have the same elements more times), and set (stores only unique elements)

<set>

Associative container storing key/value pairs in a 1:1 relation (map), or in a 1:n relation (multiset)

<map>

Predefined iterators, datastream iterators

<iterator>

Container: dynamic array

<vector>

Container: double ended queue

<deque>

Container: linear list

<list>

Container adaptor: queue

<queue>

Container adaptor: stack

<stack>

III.5.6.2. STL and C++ arrays

The algorithms and data stream iterators of STL can all be used for one-dimensional arrays in C++. This makes it possible to use the same operations for pointers and for iterators in C++: dereference (*), increment (++) etc. Most function templates expect a generic pointer to the beginning of the container (begin) and a pointer to the position after the last data (end) as arguments.

The following example carries out different operations with the help of STL algorithms on the elements of an array containing seven integers. Most of the algorithms (the number of which is more than 60) can be used successfully in traditional C++ program codes.

#include <iostream>
#include <iterator>
#include <algorithm>
using namespace std;
 
void PrintOut(const int x[], int n) {
    static ostream_iterator<int> out(cout,"\t");
    cout<< "\t";
    copy(x, x+n, out);
    cout<<endl;
}
 
void IntPrintOut(int a) {
    cout << "\t" << a << endl;
}
 
int main() {
    const int db = 7;
    int data[db]={2, 7, 10, 12, 23, 29, 80};
 
    cout << "Original array: " << endl;
    PrintOut(data, db);
 
    cout << "Next permutation: " << endl;
    next_permutation(data,data+db);
    PrintOut(data, db);
 
    cout << "In the reverse order: " << endl;
    reverse(data,data+db);
    PrintOut(data, db);
 
    cout << "Random shuffle: " << endl;
    for (int i=0; i<db; i++) {
       random_shuffle(data,data+db);
       PrintOut(data, db);
    }
 
    cout << "The greatest element: ";
    cout << *max_element(data,data+db) << endl;
 
    cout << "Finding an element:";
    int *p=find(data,data+db, 7);
    if (p != data+db)
      cout << "\tfound" <<endl;
     else
      cout << "\tnot found" <<endl;
 
    cout << "Sort: " << endl;
    sort(data,data+db);
    PrintOut(data, db);
 
    cout << "Printing out each element in a new line:"
                                                   << endl;
    for_each(data, data+db, IntPrintOut);
    PrintOut(data, db);
 
    cout << "Swap: " << endl;
    swap(data[2],data[4]);
    PrintOut(data, db);
 
    cout << "Filling the container: " << endl;
    fill(data,data+db, 123);
    PrintOut(data, db);
}

The results of the execution of the program:

Original array:
        2       7       10      12      23      29      80
Next permutation:
        2       7       10      12      23      80      29
In the reverse order:
        29      80      23      12      10      7       2
Random shuffle:
        10      80      2       23      29      7       12
        2       10      23      80      29      12      7
        7       12      2       10      80      29      23
        2       12      29      10      80      7       23
        12      23      7       29      10      2       80
        7       23      12      2       80      10      29
        7       12      23      2       29      10      80
The greatest element: 80
Finding an element:  found
Sort:
        2       7       10      12      23      29      80
Printing out each element in a new line:
        2
        7
        10
        12
        23
        29
        80
        2       7       10      12      23      29      80
Swap:
        2       7       23      12      10      29      80
Filling the container:
        123     123     123     123     123     123     123

III.5.6.3. Using STL containers

Containers can be categorised into two groups: sequence containers and associative containers. A property of sequence containers (vector, list, double ended queue: deque) is that the order of their elements is determined by programmers. Associative containers (map, set, bitset etc.) are characterised by the following properties: the order of their elements is determined by the containers themselves and that their elements can be accessed by their key. All containers manage memory space dynamically, that is their number of data can be changed any time.

The member functions of containers helps us manage and access data in them. Since the available function set depends on the type of the corresponding container, it is worth checking in a reference manual (on the Internet) what possibilities a given container can provide. Now, we will only focus on reviewing some generic operations:

  • An element can be inserted (insert()) into or deleted (erase()) from a position to which an iterator points.

  • Elements can be inserted (push) at the beginning of a sequence container (front) or to its end (back), or an element can be both accessed and then removed (pop): push_back(), pop_front() etc.

  • Certain containers can be indexed in the same ways as arrays ([]).

  • The functions begin() and end() returns iterators that can be well used in the case of algorithms. These iterators help us iterate through the elements of data structures.

In the following parts, the things said so far are demonstrated by a code using a container of type vector:

#include <vector>
#include <algorithm>
#include <iterator>
#include <iostream>
using namespace std;
 
double Sum(const vector<double>& dv) {
   vector<double>::const_iterator p; // constant iterator
   double s = 0;
   for (p = dv.begin(); p != dv.end(); p++)
      s += *p;
   return s;
}
 
bool isOdd (int n) {
    return (n % 2) == 1;
}
 
int main() {
   // output operator
   ostream_iterator<double>out(cout, " ");
   double data[]  = {1.2, 2.3, 3.4, 4.5, 5.6};
 
   // Initialising the vector with the elements of the array
   vector<double> v(data, data+5);
 
   // Printing out the vector
   copy(v.begin(), v.end(), out);  cout << endl;
   cout<<"Sum of the elements: "<<Sum(v)<<endl;
 
   // Adding elements to the vector
   for (int i=1; i<=5; i++)
       v.push_back(i-i/10.0);
   copy(v.begin(), v.end(), out);  cout << endl;
 
   // Adding 4.5 to all elements
   for (int i=0; i<v.size(); i++)
       v[i] += 4.5;
   copy(v.begin(), v.end(), out);  cout << endl;
 
   // Converting all elements to an integer
   vector<double>::iterator p;
   for (p=v.begin(); p!=v.end(); p++)
      *p = int(*p);
   copy(v.begin(), v.end(), out);  cout << endl;
 
   // Deleting each second element
   int index = v.size()-1;
   for (p=v.end(); p!=v.begin(); p--)
      if (index-- % 2 ==0)
         v.erase(p);
   copy(v.begin(), v.end(), out);  cout << endl;
 
   // Sorting the elements of the vector
   sort(v.begin(), v.end() );
   copy(v.begin(), v.end(), out);  cout << endl;
 
   // Searching for 7 in the vector
   p = find(v.begin(), v.end(), 7);
   if (p != v.end() )
       cout << "found"<< endl;
   else
       cout << "not found"<< endl;
 
   // The number of odd elements
   cout<< count_if(v.begin(), v.end(), isOdd)<< endl;
}

The results:

1.2 2.3 3.4 4.5 5.6
Sum of the elements: 17
1.2 2.3 3.4 4.5 5.6 0.9 1.8 2.7 3.6 4.5
5.7 6.8 7.9 9 10.1 5.4 6.3 7.2 8.1 9
5 6 7 9 10 5 6 7 8 9
5 7 10 6 8
5 6 7 8 10
found
2

III.5.6.4. Using STL container adaptors

Container adaptors are containers that modify the container classes above in order to ensure a behaviour that is not their default. Supported adaptors are: stack, queue and priority_queue.

Adaptors have a relatively few member functions and they only provide a specific interface for objects of other container classes. For an example, let's see the class template stack.

The stact, which functions on a "last-in, first-out” basis can be adapted from containers of type vector, list and deque. The adapted stack functions are summarised in the following table:

void push(const value_type& a)

inserting  in the stack,

void pop()

removing the top element of the stack,

value_type& top()

accessing the top element of the stack,

const value_type& top() const

accessing the top element of the stack,

bool empty() const

returns true, if the stack is empty,

size_type size()const

the number of elements in the stack,

operator== and operator<

the operations "equals" and "smaller than".

The following example code uses a stack to convert a number into another numeral system:

#include <iostream>
#include <stack>
#include <vector>
using namespace std;
 
int main() {
   int number=2013, base=16;
   stack<int, vector<int> > istack;
 
   do {
     istack.push(number % base);
     number /= base;
   } while (number>0);
 
   while (!istack.empty()) {
      number = istack.top();
      istack.pop();
      cout<<(number<10 ? char(number+'0'): 
                         char(number+'A'-10));
   }
}

Chapter IV. Programming Microsoft Windows in C++

Table of Contents
IV.1. Specialties of CLI, standard C++ and C++/CLI
IV.1.1. Compiling and running native code under Windows
IV.1.2. Problems during developing and using programs in native code
IV.1.3. Platform independence
IV.1.4. Running MSIL code
IV.1.5. Integrated development environment
IV.1.6. Controllers, visual programming
IV.1.7. The .NET framework
IV.1.8. C#
IV.1.9. Extension of C++ to CLI
IV.1.10. Extended data types of C++/CLI
IV.1.11. The predefined reference class: String
IV.1.12. The System::Convert static class
IV.1.13. The reference class of the array implemented with the CLI array template
IV.1.14. C++/CLI: Practical realization in e.g. in the Visual Studio 2008
IV.1.15. The Intellisense embedded help
IV.1.16. Setting the type of a CLR program.
IV.2. The window model and the basic controls
IV.2.1. The Form basic controller
IV.2.2. Often used properties of the Form control
IV.2.3. Events of the Form control
IV.2.4. Updating the status of controls
IV.2.5. Basic controls: Label control
IV.2.6. Basic controls: TextBox control
IV.2.7. Basic controls: Button control
IV.2.8. Controls used for logical values: CheckBox
IV.2.9. Controls used for logical values: RadioButton
IV.2.10. Container object control: GroupBox
IV.2.11. Controls inputting discrete values: HscrollBar and VscrollBar
IV.2.12. Control inputting integer numbers: NumericUpDown
IV.2.13. Controls with the ability to choose from several objects: ListBox and ComboBox
IV.2.14. Control showing the status of progressing: ProgressBar
IV.2.15. Control with the ability to visualize PixelGrapic images: PictureBox
IV.2.16. Menu bar at the top of our window: MenuStrip control
IV.2.17. The ContextMenuStrip control which is invisible in basic mode
IV.2.18. The menu bar of the toolkit: the control ToolStrip
IV.2.19. The status bar appearing at the bottom of the window, the StatusStrip control
IV.2.20. Dialog windows helping file usage: OpenFileDialog, SaveFileDialog and FolderBrowserDialog
IV.2.21. The predefined message window: MessageBox
IV.2.22. Control used for timing: Timer
IV.2.23. SerialPort
IV.3. Text and binary files, data streams
IV.3.1. Preparing to handling files
IV.3.2. Methods of the File static class
IV.3.3. The FileStream reference class
IV.3.4. The BinaryReader reference class
IV.3.5. The BinaryWriter reference class
IV.3.6. Processing text files: the StreamReader and StreamWriter reference classes
IV.3.7. The MemoryStream reference class
IV.4. The GDI+
IV.4.1. The usage of GDI+
IV.4.2. Drawing features of GDI
IV.4.3. The Graphics class
IV.4.4. Coordinate systems
IV.4.5. Coordinate transformation
IV.4.6. Color handling of GDI+ (Color)
IV.4.7. Geometric data (Point, Size, Rectangle, GraphicsPath)
IV.4.7.1. Storing dimensions
IV.4.7.2. Storing planar points
IV.4.7.3. Storing planar rectangles
IV.4.7.4. Geometric shapes
IV.4.8. Regions
IV.4.9. Image handling (Image, Bitmap, MetaFile, Icon)
IV.4.10. Brushes
IV.4.11. Pens
IV.4.12. Font, FontFamily
IV.4.13. Drawing routines
IV.4.14. Printing
References:

In this chapter we will present how to use the C++ for developing Windows-specific applications.

IV.1. Specialties of CLI, standard C++ and C++/CLI

There are several ways to develop applications for a computer running the Windows operating system:

  1. We implement the application with the help of a development kit and it will operate within this run-time environment. The file cannot be run directly by the operating system (e.g. MatLab, LabView) because it contains commands for the run-time environment and not for the CPU of the computer. Sometimes there is a pure run-time environment also available beside the development kit for the use of the application developed, or an executable (exe) file is created from our program, which includes the run-time needed for running the program.

  2. The development kit prepares a stand-alone executable application file (exe), which contains the commands written in machine code runnable on the given operating system and processor (native code). This file is run while developing and testing the program. Such tools are e.g. Borland Delphi and Microsoft Visual Studio, frequently used in industry.

Both ways of development are characterized by the fact that if the application has a graphical user interface the applied elements are created by a graphical editor, and the state of the element during operation is visible during the development as well. This principle is called RAD (rapid application development). Developing in C++ belongs to the second group while both ways of development are present in C++/CLI.

IV.1.1. Compiling and running native code under Windows

When we create a so-called console application under Windows, Visual Studio applies a syntax that corresponds to standard C++. In this way, programs and parts of programs made for Unix, Mac or other systems can be compiled (e.g. WinSock from the sockets toolkit or the database manager MySql). The process of compliation is the following:

  • C++ sources are stored in files with the extension .cpp, headers in files with the extension .h. There can be more than one of them, if the program parts that logically belong together are placed separately in files, or the program has been developed by more than one person.

  • Preprocessor: resolving #define macros, inserting #include files into the source.

  • Preprocessed C source: it contains all the necessary function definitions.

  • C compiler: it creates an .OBJ object file from the preprocessed sources.

  • OBJ files: they contain machine code parts (making their names public – export) and external references to parts in other files.

  • Linker: after having resolved references in OBJ files and files with the extension .LIB that contain precompiled functions (e.g. printf()), having cleaned the unnecessary functions and having specified the entry point (function main()), the runnable file with the extension .EXE is created, which contains the statements in machine code runnable on the given processor.

IV.1.2. Problems during developing and using programs in native code

As we saw in the pervious chapters, in programs in native code, the programmer can use dynamic memory allocation for the data/objects if necessary. These variables have only an address; we cannot refer to them with names only with pointers, loading their addresses into the pointer. For instance, the output of the function malloc() and the operator new is such a memory allocation, which allocates a contiguous space and returns its address, which we put into a pointer with a value assignment operator. After this, we can use the variable (through the pointer) and the space can be deallocated. The pointer is an effective but dangerous tool: its value can be changed by pointer arithmetics so that it does not point to the memory space allocated by us but farther. A typical example of this occurs for beginners in the case of arrays: they create an array of 5 elements (there is 5 in the definition), and they refer to the element with the index 5, which is most probably in the memory space of their program but not in the array (indexes of the elements can be understood from 0 to 4). Using an assignment statement, the given value is added to the memory next to the array in an almost blind way, changing the other variable located there “by chance”. This kind of error may be hidden from us since the change of the value of the the other variable is not recognized but “the program sometimes returns strange results”. The error is easier to recognize if the pointer does not point to our own memory space but to e.g. that of the operating system. In this case we get an error message and the operating system rejects our program from the memory.

If we pay much attention to our pointers, change by accident can be avoided. However, we cannot avoid fragmentation of the memory. When memory blocks are not deallocated exactly in the reverse order compared to allocation, “holes” are created in the memory – a free block between two occupied blocks. In the case of a multitask operating system other programs also use the memory, so holes are created even if the memory is deallocated exactly in the reverse order. Allocation must be always contiguous so if the user needs more space than the free block, it can not be allocated, and the small-sized memory block will remain unused. In other words, the memory will be “fragmented”. This is the same phenomenon as the fragmentation of storages after deletion and overwriting of files.

For the storage there is a tool program which puts the files onto a contiguous area, but this takes long time, and there is no defragmenter for the memory of native code programs. It is because the operating system cannot know which pointer contains the address of which memory block and if the block is moved, it should load the new address of the block into the pointer.

Thus, there are two needs for the memory: the first is to avoid the random change of variables or the programs stopped by the operating system (everyone has seen already the blue screen) with the help of managing the variables of the program, moreover, cleaning and garbage collection. The figure below from MSDN illustrates how garbage collection works, before cleaning (Figure IV.1, “The memory before cleaning”) and after cleaning (GC::Collect()) (Figure IV.2, “The memory after cleaning”)

The memory before cleaning
Figure IV.1. The memory before cleaning


The memory after cleaning
Figure IV.2. The memory after cleaning


.

It can be seen that memory areas (objects) which were not referred to have disappeared, and the references now point to the new addresses and the pointer that identifies the location of the free area has moved to a lower address (that is, the free contiguous memory has grown).

IV.1.3. Platform independence

Some computers have already left their initial metal box, now we take them with us everywhere in our pockets or we wear them as clothing or accessories. In the case of such computers (which are not called computer anymore: telephone, e-book reader, tablet, media player, glasses, car), manufacturers aim at minimizing consumption besides the relatively smaller computational capacity, since the power of some 100 W necessary for their functioning is not available. The processors made by Intel (and their secondary manufacturers) rather focused on computational capacity and thus mobile devices with batteries are assigned with CPUs made by other manufacturers (ARM, MIPS, etc.). The task of program developers has become more complex: for each CPU (and platform) the application should (have been) developed. It seemed appropriate to create one run-time environment for each platform and then to create applications only once and to compile them to some intermediate code in order to protect intellectual products. This was correctly realized by Sun Microsystems when they created from the languages C and C++ the Java language, which has a simple object model and has no pointers. From Java the application is compiled to bytecode, which is run on a virtual machine (Java VM) or it is translated into native code and is runnable. Nowadays many well-known platforms use the language which has now become the property of Oracle: such an example is the Android operating system supported by Google. Obviously where there is a trademark, there is suing as weel: Oracle did not agree to use the name Java because “its intellectual rights were consciously violated”. The same happened to Microsoft in the case of Java integrated to Windows: current Windows editions do not contain Java support, JRE (the run-time environment) or JDK (the development kit) must be downloaded from the website of Oracle. In the PC world, there is an intermediate stage even without this: the 32 bit operating system cannot handle 4 GB memory. AMD developed the 64 bit instruction set extension, which was later integrated by Intel too. Since XP, Windows can be bought with two types of memory handling: 32 bit for earlier PCs with less than 4 GB memory and 64 bit for PCs with newer CPU and at least 4 GB memory. The 64 bit version runs the earlier, 32 bit applications with the help of emulation (WoW64 – Windows on Windows). When a program is complied with Visual Studio 2005 or a newer (2008, 2010, 2013) version under a 64 bit operating system, we can choose mode x64, then we get a 64 bit application. Thus, there was a need for the ability of running a program on both configurations (x86, x64) and all Windows operating system versions (XP,Vista, 7,8) even if it is not known at the moment of compiling which environment we will have later but we do not want to make more exe files. This requirement can only be fulfilled with the insertion of an intermediate running/compiling level. For bigger programs, it might be necessary that more people be involved in the development, probably with different programming languages. Given the intermediate level, it is also possible: each language compiler (C++, C#, Basic, F# etc.) compiles to this intermediate language, then the application is compiled from this to a runnable one. The intermediate language is called MSIL, which is a stack-oriented language similar to machine code. The first two letters of MSIL refers to the name of the manufacturer and later it was changed to CIL (Common Intermediate Language), which can be seen as the solution of Microsoft for the basic idea of Java.

IV.1.4. Running MSIL code

The CIL code presented in the previous paragraph is transformed into a file with .EXE extension, where it is runable. But this code is not the native code of the processor, so the operating system must recognize that one more step is necessary. This step can be done in two ways, according to the principles used in Java system:

  1. interpreting and running the statements one by one. This method is called JIT (Just In Time) execution. Its use is recommended for the step by step running of the source code and for debug including break points.

  2. generating native code from all statements at the same time and starting it. This method is called AOT (Ahead of Time), and it can be created by the Native Image Generator (NGEN). We use it in the case of well functioning, tested, ready programs (release).

IV.1.5. Integrated development environment

We have not mentioned yet the applied program tools in the development process of the native code that was discussed in the previous paragraph. Initially all the steps were performed by one or more (command line) programs: the developer created/extended/fixed the .C and .H source files with an optional text editor, then the preprocessor, the C compiler and the linker came. When the developer ran the application in debug mode then it meant a new program (the debugger). In case the program contained more source files then only the amended ones had to be recompiled. This was the purpose of the make utility program. When searching among the sources (e.g. searching for in which .H file can a function definition be found) then we could use the grep utility program. Batch files were created for the compiling and those files parametrized the compiler accordingly. In case of a compiling error, the number of the erroneous line was listed on the console then we reloaded the editor, navigated to the erroneous line, we fixed the error and then we restarted the compiler. Once the compiling was completed and the program was started then sometimes it gave erroneous results. In this case we ran it with the debugger then after having the erroneous part found we used the text editor again. This procedure was not effective because of the several needs for restarting the program and the manual information input (line number). On the other hand products with text editor, compiler and runner were developed already in the 70s, 80s, before the PC era. This principal tool was called the integrated development environment (IDE). This IDE type environment was also the Turbo Pascal developed by Borland Inc. that already included a text editor, a compiler and a runner in one program on an 8 bit computer (debugger was not included yet). The program was developed by a certain Anders Hejlsberg who later worked for Microsoft on the development of programming languages. Such languages are J++ and C#. IDE tool for a character screen was created at Microsoft as well: BASIC in DOS was replaced by Quick Basic that already contained an editor and a debugger.

IV.1.6. Controllers, visual programming

Applications that run on operating systems with a graphical user interface (GUI) consist of two parts at least: the code part that contains the algorithm of the program and the interface that implements the user interface (UI). The two parts are logically linked: events (event) happening in the user interface trigger the run of the defined subprograms of the algorithm part (these subprograms are called functions in C type languages). Hence these functions are called “event handler functions” and in the development kit of the operating system (SDK) we can find definitions (in the header files) that are necessary to write them. Initially, programs with a user interface contained also the program parts necessary for the UI: a C language program with 50-100 lines was capable of displaying an empty window in Windows and to manage the “window closing” event (that is, the window could be closed). This time the main part of the development consisted of developing the UI, programming the algorithm could come only after it. In the UI program all coordinates were placed as numbers and after modifying those we could check how the interface looked like. The first similar product of Microsoft (and the recent development tool was named after this) was Visual basic. In the first version of it we could place predefined controls to our form with a GUI (that was basically the user interface of our program that we were developing). A text format code was created from the controls drawn by the user and once needed it could be modified with the embedded text editor then it was compiled before running was initiated. For running the program there was needed a library, consisting the runable parts of the controls. Characteristically because of this small size exe files were created but for the completed program that version of the run-time environment had to be installed in which the program was developed. Visual Basic was later followed by Visual C++ (VC++) and other similar programs, then – based on the example of Office – instead of separate products the development tools were integrated into one product; this was called Visual Studio.

IV.1.7. The .NET framework

The programs that implement the principles discussed till this point were collected by Microsoft into one common software package that could be installed from one file only and they called it .net. During its development several versions of it were published, now at the time when this book is being written version 4.0 is the stable one and 4.5 is the pilot test version. In order to install it we need to know the type of Windows. Different versions have to be installed for each Windows, each CPU and different versions are needed for 32 and 64 bit (see the “Platform independency” chapter).

Parts of the framework:

  • Common Language Infrastructure (CLI), and its realization the Common Language Runtime (CLR): the common language compiler and run-time environment. MSIL contains a compiler, a debugger and a run-time. It is capable of collecting garbages in the memory (Garbage Collection, GC) and handling exceptions (Exception Handling).

  • Base Class Library: the library of the basic classes. GUIs can be programmed comfortably in OOP only with well prepared base classes. These cannot be instantiated directly (in most of the cases it is impossible since they are abstract classes). As an example it contains an interface class called “Object” (see later in Section IV.1.10, “Extended data types of C++/CLI”).

  • WinForms: contols preprepared for the Windows applications, inherited from the Base Class Library. We put these to the form during development and the user interface of our program will be consisted of these. They are language independent contorls and we can use them from any applications according to the syntax of the given language. It is worth mentioning that our program will use not only those controls that were put on the form during development but the program can also create instances from these when running. That is, once putting those controls to the form a piece of the program code is created that runs when the program is initiated. This automatically created source code can be written by us (we can copy it) and it can be run at a later point as well.

  • Additional parts: these could be the ASP.NET system that supports application development on the web, the ADO.NET that allows access to databases and Task Parallel Library that supports multiprocessor systems. We do not discuss these here because of space restrictions.

IV.1.8. C#

The .NET framework and the pure managed code can be programmed with C# easily. The developer of the language is Anders Hejlsberg. He derived it from the C++ and Pascal languages, kept their advantages, made it simpler and made the usage of more difficult elements (e.g. pointers) optional. It is recommended to amateurs and students in higher education (not for programmers – their universal tools are the languages K&R C and C++). The .NET framework contains a command line C# compiler and we can also download freely the Visual C# Express Edition from Microsoft. Their goal with this is to spread C# (and .NET). Similarly, we can find free books for C# in Hungarian language on the internet.

IV.1.9. Extension of C++ to CLI

The C++ compiler developed by Microsoft can be considered as a standard C++ as long as it is used to compile a native win32 application. However, in order to reach CLI new data types and operations were needed. The statements necessary to handle the managed code (MC) appeared first in the 2002 version of Visual Studio.NET then these were simplified in version 2005. The defined language cannot be considered as C++ because the statements and data types of MC do not fit in C++ standard definition (ISO/IEC 14882:2003). The language was called C++/CLI and it was standardized (ECMA-372). Let us make a note here that usually the goal of standardization is to allow the 3rd party manufacturers to go to the market with the related product, however, in this case it did not happen: C++/CLI can be compiled only by Visual Studio.

IV.1.10. Extended data types of C++/CLI

Variables on the managed heap have to be declared differently than the variables of the native code. The allocation is not automatic because the compiler cannot make a decision instead of us: the native and the managed code can be mixed within one program (only C++/CLI is capable of doing so, the other compilers compile managed code only, e.g. there is no native int type in C#, the Int32 (its abbreviation is int) is already a class). In C++ the class on the managed heap is called reference class (ref class). It can be declared with this keyword the same way as for the native class. E.g. the .NET system contains an embedded “ref class String” type to store and manage accentuated character chains. If we create a "CLR/Windows Forms Application" with Visual Studio, the window of our program will be (Form1) a reference class. A native class cannot be defined within the reference class. The reference class behaves differently compared to the C++ class:

  • Static samples do not exist, only dynamic ones (that is, its sample has to be created from the program code). The following declaration is wrong: String text;

  • It is not pointer that points to it but handle (handler) and its sign is ^. Handle has pointer like features, for instance the sign of a reference to a member function is ->. Correct declaration is String ^text; in this case the text does not have any content yet given that its default constructor creates an empty, string with length of 0 (“”).

  • When creating we do not use the new operator but the gcnew. An example: text=gcnew String(""); creation of a string with length of 0 with a constructor. Here we do not have to use the ^ sign, its usage would be wrong.

  • Its deletion is not handled by using the delete operator but by giving a value of handle nullptr. After a while the garbage collector will free up the used space automatically. An example: text=nullptr; delete can be used as well, it will call the destructor but the object will stay in the memory.

  • It can be inherited only publicly and only from one parent (multiple inheritances are possible only with an interface class).

  • There is the option to create an interior pointer to the reference class that is initiated by the garbage collector. This way, however, we loose the security advantages of the managed code (e.g preventing memory overrun).

  • The reference class – similarly to the native one – can have data members, methods, constructors (with overloading). We can create properties (property) that contain the data in themselves (trivial property) or contain functions (scalar property) to reach the data after checking (e.g. the age cannot be set as to be a negative number). Property can be virtual as well or multidimensional, in the latest case it will have an index as well. Big advantage of property is that it does not have parenthesis, compared to a native C++ function that is used to reach member data. An example: int length=text->Length; the Length a read only property gives the number of the characters in the string.

  • Beside the destructor that runs when deleting the class (and for this it can be called deterministic) can contain a finalizer() method which is called by the GC (garbage collector) when cleaning the object from the memory. We do not know when GC calls the finalizer that is why we can call it non-deterministic.

  • The abstract and the override keywords must be specified in each case when the parent contains virtual method or property.

  • All data and methods will be private if we do not specify any access modifier.

  • If the virtual function does not have phrasing, it has to be declared as abstract: virtual type functionname() abstract; or virtual type functionname() =0; (the =0 is the standard C++. the abstract is defined as =0). It is mandatory to override it in the child. If we do not want to override the (not purely) virtual method, then we can create a new one with the new keyword.

  • It can be set at the reference class that no new class could be created from it with inheritance (with overriding the methods), and it could be only instantiated. In this case the class is defined as sealed. The compiler contains a lot of predefined classes that could not be modified e.g. the already mentioned String class.

  • We can create an Interface class type for multiple inheritances. Instead of reference we can write an interface class/struct (their meaning is the same at the interface). The access to all the members of the interface (data members, methods, events, properties) is automatically public. Methods and properties cannot be expanded (mandatorily abstract), while data can only be static. Constructors cannot be defined either. The interface cannot be instantiated, only ref/value class/struct can be created from it with inheritance. Another interface can be inherited from an interface. A derived reference class (ref class) can have any interface as base class. The interface class is usually used on the top of the class hierarchy, for example the Object class that is inherited by almost all.

  • We can use value class to store data. What refers to it is not a handle but it is a static class type (that is, a simple unspecified variable). It can be derived from an interface class (or it can be defined locally without inheritance).

  • Beside function pointers we can define a delegate also to the methods of a (reference) class that appears as a procedure that can be used independently. This procedure is secured, and errors are not faced that cause a mix up of the types and is possible with pointers of a native code. Delegate is applied by the .NET system to set and call the event handler methods, that belong to the events of the controls.

In the next table we sum up the operations of memory allocation and unallocation:

Operation

K&R C

C++

Managed C++ (VS 2002)

C++/CLI (VS 2005-)

Memory allocation for the object (dynamic variable)

malloc(…), calloc()

new …

_gc new ...

gcnew ...

Memory unallocation

free(...)

delete ...

Automatic, after ...=nullptr GC::Collect()

<- similarly as in 2002

Referring to an object

Pointer (*)

Pointer (*)

_nogc Pointer: to native data,

_gc Pointer: to managed data

Pointer (*): to native data,

Handle (^): to managed data

IV.1.11. The predefined reference class: String

The System::String class was created on the basis of C++ string type in order to store text. Its definition is: public sealed ref class String. The text is stored with the series of Unicode characters (wchar_t) (there is no problem with accentuated characters, it is not mandatory to put an L letter in front of the constant, the compiler “imagines” that it is there: L”cat” and “cat” can be used as well). Its default constructor creates a 0 length (“”) text. Its other constructors allow that we create it from char*, native string, wchar_t* or from an array that consists of strings. Since the String is a reference class, we create a handle (^) to it and we can reach its properties and methods with ->. Properties and methods that are often used:

  • String->Length length. An example: s=”ittykitty”; int i=s->Length; after the value of i will be 9

  • String[ordinal number] character (0.. as by arrays). An example: value of s[1] will be the ‘t’ character.

  • String->Substring(from which ordinal number, how many) copying a part. An example: the value of s->Substring(1,3) will be ”tty”.

  • String->Split(delimiter) : it separates the string with the delimiter to the array of words that are contained in it. An example: s=”12;34”; t=s->Split(‘;’); after t a 2 element array that contains strings (the string array has to be declared). The 0. its element is “12”, and the 1. its elements is “34”.

  • in what -> IndexOf(what) search. We get a number, the initiating position of the what parameter in the original string (starting with 0 as an array index). If the part was not found, it returns -1. Note that it will not be 0 because 0 is a valid character position. As an example: with the s is “ittykitty”, the value of s->IndexOf(“ki”) will be 4, but the value of s->IndexOf(“dog”) will be -1.

  • Standard operators are defined: ==, !=, +, +=. By native (char*) strings the comparing operator (==) checks whether the two pointers are equal, and it does not check the equality of their content. When using String type the == operator checks the equality of the contents using operator overloading. Similarly, the addition operator means concatenation. As an example: the value of s+”, hey” will be “ittykitty, hey”.

  • String->ToString() exists as well because of inheritance. It does not have any pratical importance since it returns the original string. On the other hand, there is no method that converts to a native string (char*). Let us see a function as an example that performs this conversion:

    char * Managed2char(String ^s)
    {
      int i, size=s->Length;
      char *result=(char *)malloc(size+1); // place for the converted string
      memset(result,0,size+1); // we fill the converted with end signs
      for (i=0; i<size;i++)    // we go through the characters
          result[i]=(char)s[i]; // here we will got a warning: s[i]
    //stored on 2 bytes unicode wchar_t type character.
    //Converting ASCII from this the accents will disappear
      return result; // we will return the pointer to the result
    }
    

IV.1.12. The System::Convert static class

We store our data in variables with a type that was chosen for their purpose. For example in case we have to count the number of vehicles passing a certain point of the road per hour, we usually use the int type even if we are aware that its value will never be negative. The negative value (that can be given to the variable because it is signed) can be used in this example to mark an exception (no measuring happened yet, an error occurred etc.). The int (and the other numeric types also) stores the numbers in the memory in binary format, allowing this way performing arithmetic operations (for instance addition, substraction) and calling mathematical functions (e.g. sqrt, sin).

When the user input happens (our program asks for a number), the user will type characters. Number 10 is typed with a ‘1’ and ‘0’ character and from this a string is created: “10”. If we would like to add 20 to this and it was also entered as a string then the result will be “1020” because the “+” operator of the String class copies strings after each other. When using the scanf function of the native Win32 code and the cin standard input stream, if the input was put into numeric type, conversion will happen when reading to the type that is specified in the scanf format argument or after the cin >> operator. In case of the predefined input controls of windows it does not work like this: their output is always String type. Similarly, we always have to create String type for the output because on our controls we can display only this type. Also text files that are used to establish communication between programs (export/import) consist of strings in which numbers or other data types (date, logical, currency) can be as well. The System namespace contains a class called Convert. The Convert class has numerous overloaded static methods, which help the data conversion tasks. For performing the most common text <-> number conversions the Convert::ToString(NumericType) and the Convert::ToNumericType(String) methods are defined. For example, if in the above example s1=”10” and s2=”20”, then we add them considered as integers in the following way:

int total=Convert::ToInt32(s1)+Convert::ToInt32(s2);

In case s1 or s2 cannot be converted to a number (for example one of them is of 0 length or it contains an illegal character) an exception arises. The exception can be handled with a try/catch block. In case of real numbers we have to pay attention to one more thing: these are the region and language settings. As known, in Hungary the decimal part of numbers are separated from the integer with a comma: 1,5. On the other hand, in English speaking countries point is used for this purpose:1.5. In the source code of the C++/CLI program we always use points in case of real numbers. The Convert class, however, performs the real <-> string conversion according to the region and language settings (CultureInfo). The CultureInfo can be set for the current program, if for example we got a text file that contains real numbers in English format. The next program part sets its own culture information so that it could handle such a file:

// c is the instance of the CultureInfor reference class
System::Globalization::CultureInfo^ c;
// Like we were in the USA
c = gcnew System::Globalization::CultureInfo("en-US");
System::Threading::Thread::CurrentThread->CurrentCulture = c;
// from now onwards in the program the decimal separator is the point, the list delimiter is the comma

The methods of the Convert class can appear also in the methods of the data class. For example the instance created by the Int32 class has a ToString() method to convert to a string and a Parse() method to convert from a string. These methods can be parameterized in several ways. We often use hexadecimal numbers in computer/hardware related programs. The next example communicates with an external hardware with the use of strings containing hexadecimal numbers through a serial port:

if (checkBox7->Checked) c|=0x40;
if (checkBox8->Checked) c|=0x80;
sc="C"+String::Format("{0:X2}",c);// A 2 character hex number is created from the byte type c. C is the command;
//if the value of c was 8: “C08” will be the output, if c was 255 "CFF”.
serialPort1->Write(sc); // we sent it to the hardware
s=serialPort1->ReadLine(); // the answer was returned
// let us convert the answer to an integer
status = Int32::Parse(s, System::Globalization::NumberStyles::AllowHexSpecifier);

IV.1.13. The reference class of the array implemented with the CLI array template

In programming the array is an often used data structure with basic algorithms. Developers of .NET developed a generic array definition class template. With help of this – like a producer tool - the user can define a reference class from the required basic data type using the (<>) sign introduced in C++ to mark templates. It can be used for multidimensional arrays as well. Accessing the elements in the array can happen with the integer number (index) put into the traditional square brackets, that is with the [ ] operator.

Declaration: cli::array<type, dimension=1>^ arrayname, the dimension is optional; in this case its value is 1. The ^ is the sign of the ref class, the cli:: is also omissible, if we use at the beginning of our file the using namespace cli; statement.

We have to allocate space for the array with the gcnew operator before using – since it is a reference class when declaring a variable only the handle is created, and it is not pointing to anywhere. We can make the allocation in the declaration statement as well: we can list the elements of the array between { } as used in C++.

Array’s property: Length gives the number of elements of the onedimensional array. For arrays passed to a function we do not have to pass the size, like in the basic C. The size can be used in the loop statement, which does not address out from the array:

for (i=0; i<arrayname->Length; i++)….

For the basic array algorithms static methods were created, and those are stored in the System::Array class:

Clear(array, from where, how many) deletion. The value of the array elements will be 0, false, null, nullptr (depending on the base type of the array),

Resize(array, new size) in case of resizing (expanding) after the old elements it fills the array with the values used with Clear().

Sort(array) sorting the elements of the array. It can be used by default to order numerical data in ascendant order. We can set keys and a comparing function to sort any type data.

CopyTo(target array, starting index) copying elements. Note: the = operator duplicates the reference only. If an element of the array is changed, this changed element is reached using the other reference as well. Similarly, the == oparetor that the two references are the same but it does not compare the elements themselves.

If the type from which we create the array is another reference class (e.g. String^) then we have to set it in the definition. After creating the array we have to create each element one after the other because by default it would contain nullptrs. An example: String^ like an array element with initial value setting. If we do not list the 0 length strings, the array elements would have been nullptrs

array<String^>^ sn= gcnew array<String^>(4){"","","",""};

In the next example we create lottery numbers in an array then we check them whether they can be used in the game: not to have two identical ones. In order to do this we sort them so that we had to check only the neighbouring elements and we could list the result in ascendent order:

array<int>^ numbers; // managed array type, reference
Random ^r = gcnew Random();// random number generator instance
int piece=5, max=90,i; // we set how many numbers we need and the highest number.
//It could be set as an input after conversion.
numbers = gcnew array<int>(piece); // managed array on the heap created
for(i=0;i<numbers->Length;i++)
    numbers[i]=r->Next(max)+1;// the raw random numbers are in the array
Array::Sort(numbers); // with the embedded method we set the numbers in order
// check: two identical next to each other?
bool rightnumber=true;
for (i=0;i<numbers->Length-2;i++)
     if (numbers[i]==numbers[i+1]) rightnumber=false;

IV.1.14. C++/CLI: Practical realization in e.g. in the Visual Studio 2008

If we would like to create a program with CLR (that is, in .NET with windows) in Visual Studio we have to choose one of the “Application” elements of the CLR category in the new element wizard. The CLR console looks like the ”Win32 console app”, that is, it has a command line interface. Therefore we should not choose the console but the ”Windows Forms Application”. In this case the window of our program that is the container object called Form1 will be created and its code will be in the Form1.h header file. The Form Designer will place the code of the drawn controls here (and it puts a comment ahead of it saying that we should not modify the code, of course in certain cases the amendment is necessary). In the attached figure you can see the element to be selected:

After making our selection, the folder structure of our project is created with the necessary files in it. Now we can already place controls on the form. In the “Solution Explorer” window we can find for the source files and we can modify all of them. In the next figure you can see a project that has just been started:

Our program is in Form1.h (it has a form icon). Usually there is code placed into stdafx.h too. In the main program (mea_1.cpp) we should not modify anything. Using the “View/Designer” menuitem, we can select the graphical editor, while with the “View/Code” menuitem the source program. After selecting the “View/Designer” menuitem our window will look like this:

The window in the View/Designer
Figure IV.3. The window in the View/Designer


After selecting the “View/Code” menuitem our window will look like this:

The program in the View/Code window
Figure IV.4. The program in the View/Code window


Selecting the “View/Designer” menuitem we will need the Toolbox where the additional controls can be found (the toolbox contains additional elements only in designer state). In case it is not visible we can set it back with the “View/Toolbox” menuitem. The toolbox contains a case-sensitive help as well: leaving the cursor on top of the controls we will get a short summary of the use of the control. See the next figure where we selected the label control:

The Toolbox
Figure IV.5. The Toolbox


Selection of the control happens with the usual left mouse button. After this the bounding rectangle of the control will be drawn on the form if we chose a visible control. The non-visible controls (e.g. timer) can be placed in a separated band at the bottom of the form. When the drawing is done an instance of the control is put to on the form with an automatically given name. In the figure we selected the “Label” control (upper case: type), if we draw the first of this control, the developing environment will name it “label1” (lower case: instance). After drawing the controls if needed, we can set their properties and the functions that are related to their events. After selecting the control and with right mouse click we can achieve the setting in the window, opened with the “Properties” menuitem. It is important to note that these settings refer to the currently selected control and the properties windows of the certain controls differ from each other. On the next figure we select the “Properties” window of the label1 control:

The Control menu
Figure IV.6. The Control menu


After this we can set the properties in a separate window:

The Properties Window
Figure IV.7. The Properties Window


The same window serves for selecting the event handlers. We have to click on the blitz icon () to define the event handlers. In this case all the reacting options will appear that are possible for all the events of the given control. In case the right side of the list is empty then the control will not react to that event.

The Event handlers
Figure IV.8. The Event handlers


In the example the label1 control does not react when clicking on it (the label control can handle the click event but it is not common to use the control this way). A function can be added to the list in two ways: if we would like to run an already existing function when the event of the control happens (and its parameters equal to the event parameters), then we can choose the function name from the drop down list. If it does not exist then clicking on the empty area the header of a new function is created and we will reach the code editor. Each control has a default event (for example click is the default event of the button), clicking twice on the control in the designer window we will reach the code editor of the event. If such a function does not exist yet its header and an association are created. We have to be aware that the control does not work without its association! It is a typical problem to write the button1_Click function, setting the parameters correctly but without associating them. In this case – after compiling without errors – the button does not react when clicking on it. The button1 will react only if the “Click” row in the events window contains the button1_Click name.

A defined Event Handler
Figure IV.9. A defined Event Handler


IV.1.15. The Intellisense embedded help

As we saw it in the previous figures, a control can have a lot of properties. We can use the property names in the text of the program, but since they are considered as identifiers they have to equal from character to character to the property name as set in the definition, considering case sensitivity as well. The names of the properties are often very long (for example UseCompatibleTextRendering). The programmer has to type these without mistake. The text editor contains some help: after the name of the object (button1) typing the operator that refers to the data member (->) it creates a list from the possible properties. It displays them in a short menu, we can select the ones we need with the cursor control arrows or with the mouse, then it adds the chosen name to the text of our program pushing the tab key. The help list will appear also if we start to type the name of the property. Visual Studio stores these control properties in a big size .NCB extension file and if we delete it (e.g we transfer a source file to another computer via pen drive), once opening it, it will be regenerated. Intellisense does not work in certain cases: if our program has syntax errors, and the number of opening curly brackets does not equal to the closing curly brackets, then it will stop. Similar to this it does not work in Visual Studio 2010, if we write a CLR code. In the next figure we would like to change the label7->Text property, because of the high number of properties we type the T letter then we select Text with the mouse.

The Intellisense window
Figure IV.10. The Intellisense window


IV.1.16. Setting the type of a CLR program.

As we already mentioned previously, C++/CLR is capable of developing mixed mode programs (native+managed). In case we use settings described in the previous section, our program will have purely managed code, the native code cannot be compiled. At the beginning it is worth to start with these settings because this way the window and the controls of our program will be usable. We can make these settings in the project properties (we select the project then right click and “Properties”). Be aware that it does not refer to the top level solution but to the properties of the project that is below the solution.

Solution Explorer menu
Figure IV.11. Solution Explorer menu


In the "Property Pages" window in the line “Common Language Runtime Support" we can set whether it should be native/mixed/purely managed code. We can choose from 5 setting types:

Project properties
Figure IV.12. Project properties


The meanings of the settings are as follows:

  • "No common Language Runtime Support" – there is no managed code. It is the same if we create a Win32 console application or a native Win32 project. With this setting it is not capable of compiling the parts of the .NET system (handles, garbage collector, reference classes, assemblies).

  • "Common Language Runtime Support" – there is native and managed code compiling as well. With this setting we can create mixed mode programs, that is, if we started to develop our program with the default window settings and we would like to use native code data and functions, then we have to set the drop down menu to this item.

  • "Pure MSIL Common Language Runtime Support" – purely managed code compiling. The default setting of programs created from the “Windows Form Application”. This is the only possible setting of C# compiler. Note: this code type can contain native code data that we can reach through managed code programs.

  • "Safe MSIL Common Language Runtime Support" – it is similar to the previous one but it cannot contain native code data either and it allows the security check of the CRL code with a tool created for this purpose (peverify.exe).

  • "Common Language Runtime Support, Old Syntax" – this also creates a mixed code program but with Visual Studio 2002 syntax. (_gc new instead of gcnew). This setting was kept to ensure compatibility with older versions, however, it is not recommended to be used.

IV.2. The window model and the basic controls

IV.2.1. The Form basic controller

Form could not be added from the toolbox, it is created with a new project. By default it creates an empty, rectangle shape window. Its settings can be found in the properties, e.g. Text is its header and by default the name of the control (if this is the first form then it will be Form1) will be added into it. We can reach it from here or from the program as well ((this->Text=…) because in the Form1.h can be used the instance of the reference class of our form (inherited from the Form class), and we can refer to this instance with this pointer within our program. From the property settings (as well) a program part is created at the beginning of form1.h in a separate section:

#pragma region Windows Form Designer generated code
    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    // button1
    this->button1->Location = System::Drawing::Point(16, 214);
    this->button1->Name = L"button1";
    this->button1->Size = System::Drawing::Size(75, 23);
    this->button1->TabIndex = 0;
    this->button1->Text = L"button1";
    this->button1->UseVisualStyleBackColor = true;
    this->button1->Click += gcnew System::EventHandler(this, &Form1::button1_Click);

IV.2.2. Often used properties of the Form control

  • Text – title of the form. This property can be found at each control that contains text (as well).

  • Size – the size of the form, by default in pixels. It contains the Width and Height properties that are directly accessible. Also the visible controls have these properties.

  • BackColor – color of the background. By default it has the same color as the background of the controls defined in the system (System::Drawing::SystemColors::Control). This property will be important if we would like to delete the graphics on the form because deletion means filling with a color.

  • ControlBox – the system menu of the window (minimalizer, maximalizer buttons and windows menu on the left side). It can be enabled (by default) and disabled.

  • FormBorderStyle – We can set here whether our window can be resized or it should have a fix size or whether it had a frame or not.

  • Locked – we can prohibit resizing and movement of the window with the help of this.

  • AutoSize – the window is able to change its size aligning to its content

  • StartPosition – when starting the program where should the form appear on the Windows desktop. Its application: if we use a multiscreen environment, then we can set the x,y coordinates of the second screen, our program will be lunched there then. It is useful to set this property in a conditional statement because in case the program is lunched in one screen only the form will not be visible.

  • WindowState – we can set here whether our program would be a window (Normal), whether it would run full screen (Maximized) or whether it would run in the background (Minimized). Of course, like any of the other properties, it is reachable during run-time as well, that is, if the program lunched in the small window would like to maximalize itself (for example because it would like to show many things) then we have an option for this setting as well: this->WindowState=FormWindowState::Maximized;

IV.2.3. Events of the Form control

Load – a program part that appears when starting the program before displaying it. Load is the default event of the Form control, that is, when double clicking on its header in the Designer its handler function is placed in the editor. This function as usual can fill in the role of an initializer since it runs once when launching the program. We can set values to the variables, we can create the necessary dynamic variables, and we can change title on our other controls (if we have not already done ), we can also set the size of the form dynamically etc.

As an example let us see the Load event handler of the program of the quadratic equation:

private: System::Void Form1_Load(System::Object^  sender, System::EventArgs^  e) {
             this->Text = "quadratic";
             textBox1->Text = "1";  textBox2->Text = "-2"; textBox3->Text = "1";
             label1->Text = "x^2+"; label2->Text = "x+";   label3->Text = "=0";
             label4->Text = "x1=";  label5->Text = "x2=";  label6->Text = "";
             label7->Text = "";     label8->Text = "";
             button1->Text = "Solve it";
 if (this->ClientRectangle.Width < label3->Left + label3->Width) // the form is not wide enough
               this->Width = label3->Left + label3->Width + 24;
             if (this->ClientRectangle.Height < label8->Top + label8->Height)
               this->Height = label8->Top + label8->Height + 48; // it is not high enough
             button1->Left = this->ClientRectangle.Width - button1->Width-10; // pixel
             button1->Top = this->ClientRectangle.Height - button1->Height - 10;
           }

In the above example after setting the initial values we set the titles then we set the size of the form to a value that the whole equation and the results (label3 was the right-most control and label8 was at the bottom of the form) were visible.

In case we find in this function that running of the program does not make sense (we would process a file but we could not find it, we would like to communicate with a hardware but we could not find it, we would like to use the Internet but we do not have connection etc.) then after displaying a window of an error message we can leave the program. Here comes a hardware example:

if (!controller_exist) {
  MessageBox::Show("No iCorset controller.",
  "Error",MessageBoxButtons::OK);
  Application::Exit();
} else {
  // controller exist, initialize the controller.
}

Let us pay attention to a thing: the Application::Exit() does not leave the program immediately, it puts a message to the Windows message queue for us to warn about leaving the program. That is, the program part after if… will run also, moreover the window of our program will show up for a second before closing it. If we would like to avoid the run of the further program part (we would communicate with the hardware that does not exist), then let us do it in the else branch of the if statement and the else branch should reach the end of the Load function. This way we can guarantee that the program part that supposedly caused the error will not run.

Resize – An event handler that runs when resizing our form (minimalizing, maximalizing, setting it to its normal size could be also considered here). It runs when loading the program, this way the increase of the form size from the previous example could have been mentioned here as well, and in this case the form could not be resized to smaller in order to ensure the visibility of our controls. In case we have a graphic which size depens on the window size, then we can resize it here.

Paint – the form has to be repainted. See the examples in the “The usage of GDI+” chapter (Section IV.4.1, “The usage of GDI+”).

MouseClick, MouseDoubleClick – we click once or we double click on the Form with the mouse. In case we have other controls on the form then this event runs if we do not click on neither of the controls just on the empty area. In one of the arguments of the event handler we got the handle to the reference class

System::Windows::Forms::MouseEventArgs^ e

The referenced object contains the coordinates (X,Y) of the click beside others.

MouseDown, MouseUp – we clicked or released one of the mouse buttons on the Form.The Button propety of the MouseEventArgs contains which button was clicked or released. The next program part saves the coordinates of the clicks into a file, this way for example we can create a very basic drawing program:

// if save is set and we pushed the left button
    if (toolStripMenuItem2->Checked &&  (e->Button ==     System::Windows::Forms::MouseButtons::Left))  {
    // we write the two coordinates into the file, x and y as int32
                bw->Write(e->X); // we write int32
                bw->Write(e->Y); // that is 2*4 byte/point
    }

MouseMove: - the function running when moving the mouse. It works independently from the buttons of the mouse. In case our mouse is moved over the form, its coordinates can be read. The next program part displays these coordinates in the title of the window (that is, in the Text property), of course after the needed conversions:

private: System::Void Form1_MouseMove(System::Object^  sender, System::Windows::Forms::MouseEventArgs^  e) {
             // coordinates into the header, nobody looks at those ever
             this->Text = "x:" + Convert::ToString(e->X) + " y=" + Convert::ToString(e->Y);
         }

FormClosing – our program got a Terminate() Windows message for some reason. The source of the message could be anything: the program itself with Application::Exit(), the user with clicking on the “close window”, or the user with the Alt+F4 key combination, we are before stopping the operating system etc. When this function runs the Form is closed, its window disappears, resources used by it will be unallocated. In case our program decides that this is not possible yet, the program stop can be avoided by setting the Cancel member of the event’s parameter to true, and the program will run further. The operating system however, if we would like to prevent it from stopping, it will close our program after a while. In the next example the program let itself to be closed only after a question appearing in a dialog window:

void Form1_FormClosing(System::Object^  sender, System::Windows::Forms::FormClosingEventArgs^  e) {
   System::Windows::Forms::DialogResult d;
   d=MessageBox::Show("Are you sure that you would like to use the airbag?”,
  " Important security warning ", MessageBoxButtons::YesNo);
  if (d == System::Windows::Forms::DialogResult::No) e->Cancel=true;
}

FormClosed – our program is already in the last step of closure process, the window do not exist anymore. There is no way back from here, this is the last event.

IV.2.4. Updating the status of controls

After the running of the event handlers (Load, Click) the system updates the status of controls so that they are already in the new, updated state for the next event. However, let’s have a look at the following sample program part that writes increasing numbers into the form’s title bar. It can be placed for example into the button_click event handler:

int i;
for (i=0;i<100;i++)
{
     this->Text=Convert::ToString(i);
}

During the running of the program (apart from the speed problem) there is no change in the form’s title. Though Text property was rewritten, the control still shows the old content. It will be changed only once, when the given event handler function terminates. But then „99” will appear in the control. In case of longer operations (image processing, large text files) it would be good to somehow inform the user about how the program proceeds, showing the current status, otherwise one might suppose that the program has stopped responding. Function call Application::DoEvents() serves exactly this purpose. It updates the current status of the controls: in our example this function will replace the form’s title. Unfortunately, from this we cannot see anything, we can read the numbers only if we build an awaiting time (Sleep) as well into the loop:

int i;
for (i=0;i<100;i++)
{
 this->Text=Convert::ToString(i);
 Application::DoEvents();
 Threading::Thread::Sleep(500);
}

The parameter of the Sleep() function is the waiting time in milliseconds. The slow process was simulated by this. In case we need algorithm components recurring periodically, Timer control should be used (see Section IV.2.22, “Control used for timing: Timer”).

IV.2.5. Basic controls: Label control

The simplest control is the Label which displays text. It’s String ^ type property called Text includes the text to be displayed. By default its width aligns to the text to be displayed (AutoSize=true). In case we display text with the help of it, its events (eg. Click) are normally not used. By default the Label has no border (BorderStyle=None), but it can be framed (FixedSingle). The background color can be found in BackColor property, the text color in ForeColor property. In case we would like to remove the displayed text, we have two choices: we either set the logical type property called Visible to false, in this case the Text property does not change, but the control is not visible, or we set the Text property to an empty string with length 0 (””).

All visible controls have this property called Visible. By setting the property to false, the control disappears, by setting it to true, the control will be visible again. In case we are sure that the control’s label will be different at the next display, it is practical to use the ‘empty string’ version, since in this case the new value appears immediately after the assignment; while in case of the other version ‘Visible property’ a new allowing program line is needed as well.

IV.2.6. Basic controls: TextBox control

TextBox control can be used for entering text (String^) (in case we need to enter numbers, we use the same control, but in this case the processing starts with a conversion). The Text property contains the text, which can be rewritten from the program and the user can also change it while the program is running. The already mentioned Visible property appears here as well as the Enabled property is available too. By setting Enabled to false, the control is visible on the form, however appears in gray and cannot be used by the user: one can neither change nor click on it. For example, the command button “Next” in an installation process has the same state until the license agreement is not accepted.

TextBox has a default event as well: it is TextChanged, which runs after each and every change (per character). In case of multi-digit numbers, data with more than one characters or in case of more input data (in several TextBoxes) we usually do not use it, since it would be pointless. For example, the user has to enter his name into the TextBox and the program stores it in a file. It would be unnecessary to write all the current content into a file in case each of the characters, since we do not know what the last character will be. Instead, we wait until editing is over, data entry is ready (maybe there are more TextBoxes in our form), and the user can give a signal by pressing a properly named (ready, save, processing) command button meaning that text boxes include the program’s input data.

Unlike the other controls that have Text property, where the Designer writes the control’s name into the Text property, it does not happen here, Text property remains empty. It can be set to multiline by switching the MultiLine property to true. Then line feeds appear in the Text, and the lines can be found in the Lines property just like the elements of a string array. Some programmers use TextBox control for output as well by setting ReadOnly property to true. In case we do not want to write back the entered characters, we can switch the TextBox to password input mode by setting UseSystemPasswordChar property to true. We have already seen the function running when starting the quadratic equation program; now let’s have a look at the calculation part. The user has written in the TextBoxes the coefficients (a,b,c) and clicked on the “solve” button. Our first job is to get data from the TextBoxes by using conversion. Then the next step is the calculation and the displaying of results.

double a, b, c, d, x1, x2, e1, e2; // local variables
// in case we forget to give values to any of the variables -> error
a = Convert::ToDouble(textBox1->Text); // String -> double
b = Convert::ToDouble(textBox2->Text);
c = Convert::ToDouble(textBox3->Text);
d = Math::Pow(b,2) - 4 * a * c; // the method for exponentiation exists as well
if (d >= 0) // real roots
{
  x1=(-b+Math::Sqrt(d))/(2*a);
  x2=(-b-Math::Sqrt(d))/(2*a);
  label4->Text = "x1=" + Convert::ToString(x1);
  label5->Text = "x2=" + Convert::ToString(x2);
  // checking
  e1 = a * x1 * x1 + b * x1 + c; // this way we typed less than in Pow
  e2 = a * x2 * x2 + b * x2 + c;
  label6->Text = "...=" + Convert::ToString(e1);
  label7->Text = "...=" + Convert::ToString(e2);
}

IV.2.7. Basic controls: Button control

Button control denotes a command button that “sags” when clicking on it. We use command button(s) if the number of currently selectable functions are low. The function can be complicated as well, in this case a long function belongs to it. Button control supports the usual properties of visible controls: its caption is Text, its event is Click, which runs when clicking on the button. This is its default and commonly used event. From the event handler’s parameters the coordinates of the click cannot be told. The header of the event handler is:

private: System::Void button1_Click(System::Object^  sender, System::EventArgs^  e) {
}

Button control gives an opportunity to apply the nowadays so fashionable shortcut icon operation by using small graphics instead of captions. To do this, the following steps are needed: we load the small graphic to a Bitmap type variable, we set the Button’s sizes to the sizes of Bitmap, finally, we store the reference of the Bitmap type variable in the Image property of the button as it happened in the example below: the image called “service.png” appears in the command button called “button2”.

Bitmap^ bm;
bm=gcnew Bitmap("service.png");
button2->Width=bm->Width;
button2->Height=bm->Height;
button2->Image=bm;

IV.2.8. Controls used for logical values: CheckBox

Text property of the CheckBox control is the text (String^ type), written next to it. Its bool type property is the Checked, which is true in case it is checked. CheckedState property can take up three values: apart from ‘on’ and ‘off’ it has a third, middle value as well, which can be set up only from the program, when it is running, however it is considered to be checked. In case there are more CheckBoxes in a Form, these are independent of each other: we can set any of them checked or unchecked. Its event: CheckedChanged occurs when the value of the Checked property changes.

The sample program below is a part of an interval bisection program: when switching on the checkbox called “Stepwise”, one step will be completed from the algorithm, when switching it off, the result is provided within one loop. If we started to make the program running stepwise, the checkbox cannot be unchecked: further counting has to be performed stepwise.

switch (checkBox1->Checked)
{
  case true:
    checkBox1->Enabled = false;
    step();
    break;
  case false:
    while (Math::Abs(f(xko)) > eps) step();
    break;
}
write();

IV.2.9. Controls used for logical values: RadioButton

RadioButton is a circular option button. It is similar to the CheckBox, but within one container object (such as Form: we put other objects in it) only one of them can be checked at the same time. It was named after the old radio containing waveband switch: when one of the buttons was pressed, all the others were deactivated. When one of the buttons is marked/activated by the circle (either by the program: Checked = true, or by the user clicking on it), the previous button (and all the others) becomes deactivated (its Checked property changes to false). We store the text, which is next to the circle in the control’s Text property. A question might be raised: if only one RadioButton can be active at the same time, then what if we have to choose from two option lists at the same time using RadioButtons? The answer is simple: we can have one active RadioButton per one container object, therefore, we have to place some container objects on the form.

IV.2.10. Container object control: GroupBox

GroupBox is a rectangular frame with text (Text, String^ type) on its top left line. We placed controls in it, which are framed. On the one hand, it is aesthetic, as the logically related controls appear within one frame, on the other hand it is useful for RadioButton type controls. Furthermore, controls appearing here can be moved and removed with a single command by customizing the appropriate property of GroupBox. The example below shows marking: we can see the entry and processing of a small number of discrete elements:

groupBox1->Text="ProgDes-I";
radioButton1->Text="excellent";
radioButton2->Text="good";
radioButton3->Text="average";// no other marks can be received here
…
int mark;
if (radioButton1->Checked) mark = 5;
if (radioButton2->Checked) mark = 4;

Part of the program’s window
Figure IV.13. Part of the program’s window


IV.2.11. Controls inputting discrete values: HscrollBar and VscrollBar

The two controls that are called scrollbars differ only in direction. They do not have labels. In case we would like to indicate their end positions or their current status, we can do this by using separate label controls. The current value, where the scrollbar is standing, is the integer numerical value found in Value property. This value is located between the values of Minimum and Maximum properties. The scrollbar can be set to the Minimum property, this is its left/upper end position. However, for its right/lower end position we have a formula, which includes the quick-change unit of the scrollbar, called LargeChange property (the value changes this much, when we click on the empty space with the mouse): Value_max=1+Maximum-LargeChange. LargeChange and SmallChange are property values set by us. When moving the scrollbar a Change event is running with the updated value. In the example below we would like to generate 1 byte values (0..255) with the help of three equally sized horizontal scrollbars. The program part for setting the scrollbar properties in the Form_Load event are the following:

int mx;
  mx=254 + hScrollBar1->LargeChange; // We would like to have 255 in the right position
  hScrollBar1->Maximum = mx; // max. 1 byte
  hScrollBar2->Maximum = mx;
  hScrollBar3->Maximum = mx;

When the scrollbars are changing, we read their value, convert them to a color and write the values to the labels next to the scrollbars as a check:

System::Drawing::Color c; // color variable
r = hScrollBar1->Value ;  // from 0 to 255
g = hScrollBar2->Value;
b = hScrollBar3->Value;
c = Color::FromArgb(Convert::ToByte(r),Convert::ToByte(g),Convert::ToByte(b));
label1->Text = "R=" + Convert::ToString(r); //we can also have them written
label2->Text = "G=" + Convert::ToString(g);
label3->Text = "B=" + Convert::ToString(b);

IV.2.12. Control inputting integer numbers: NumericUpDown

With the help of NumericUpDown control we can enter an integer number. It will appear in Value property, between the Minimum and Maximum values. The user can increase and decrease the Value by 1, when clicking on the up and down arrows. All the integer numbers between the Minimum and Maximum values appear among the values to choose from. Event: ValueChanged is running after every change.

IV.2.13. Controls with the ability to choose from several objects: ListBox and ComboBox

ListBox control offers an arbitrary list to be uploaded, from which the user can choose. Beyond the list, ComboBox contains a TextBox as well, which can get the selected item as well and the user is also free to type a string. This is the Text property of ComboBox. The control’s list property is called Items, which can be upgraded by using Add() method and can be read indexed. SelectedIndex points the current item. Whenever selection changes, SelectedIndexChanged event is running. ComboBox control is used by several controls implementing more complex functions: for example OpenFileDialog. In the example below we fill up the list of ComboBox with elements. When the selection is changed, we put the new selected item in label4.

comboBox1->Items->Add("Excellent");
comboBox1->Items->Add("Good");
comboBox1->Items->Add("Average");
comboBox1->Items->Add("Pass");
comboBox1->Items->Add("Fail");
private: System::Void comboBox1_SelectedIndexChanged(System::Object^  sender, System::EventArgs^  e) {
  if (comboBox1->SelectedIndex>=0)
    label4->Text=comboBox1->Items[comboBox1->SelectedIndex]->ToString();
}

IV.2.14. Control showing the status of progressing: ProgressBar

With the help of ProgressBar we can give information about the status of the process, how much is still left, the program has not frozen, it is working. We have to know in advance when the process will be ready, when it should reach the maximum. The control’s properties are similar to HScrollBar, however the values cannot be modified with the help of the mouse. Certain Windows versions animate the control even if it shows a constant value. Practically, this control used to be placed in the StatusStrip in the bottom left corner of our window. When clicking on it, its Click event is running, however, it is something we normally do not deal with.

IV.2.15. Control with the ability to visualize PixelGrapic images: PictureBox

This control has the ability to visualize an image. Its Image property contains the reference of the Bitmap to be visualized. Height and Width properties show its size, while Left and Top properties give its distance from the left side and top of the window measured in pixels. It is practical to have the size of the PictureBox exactly the same as the size of the Bitmap to be visualized (which can be loaded from almost all kinds of image files) in order to avoid resizing.

The SizeMode property contains the info about what to do if you need to resize the image: in case of Normal value, there is no resizing, the image is placed to the left top corner. If Bitmap is larger, the extra part will not be displayed. In case of StretchImage value, Bitmap is resized to be as large as the PictureBox. In case of AutoSize option, the size of the PictureBox is resized according to the size of the Bitmap. In the program below we display a temperature map (found in idokep.hu) in the PictureBox control. SizeMode property is preset in the Designer, the size parameters of the images are shown in the label:

Bitmap ^ bm=gcnew Bitmap("mo.png");
label1->Text="PictureBox:"+Convert::ToString(pictureBox1->Width)+"x"+
     Convert::ToString(pictureBox1->Height)+" Bitmap:"+
     Convert::ToString(bm->Width)+"x"+Convert::ToString(bm->Height);
pictureBox1->Image=bm;

The result of the program above in case SizeMode=Normal is the following:

Normal size picturebox on the form
Figure IV.14. Normal size picturebox on the form


With SizeMode=StretchImage setting the scales do not match either: the map is rectangular, the PictureBox is a square:

Stretched size picturebox on the form
Figure IV.15. Stretched size picturebox on the form


In case of SizeMode=AutoSize the PictureBox has increased, but the Form has not. The Form had to be resized manually to reach the following size:

Automatic sized picturebox on the form
Figure IV.16. Automatic sized picturebox on the form


With SizeMode=CenterImage setting: there is no resize, the centre of Bitmap was set into the PictureBox

Centered image in the picturebox on the form
Figure IV.17. Centered image in the picturebox on the form


With SizeMode=Zoom Bitmap is resized by keeping its scales so that it fits into the PictureBox:

Zoomed bitmap in the picturebox on the form
Figure IV.18. Zoomed bitmap in the picturebox on the form


IV.2.16. Menu bar at the top of our window: MenuStrip control

In case our program implements so many functions that we would require a large number of command buttons in order to start them, it is practical to group functions hierarchically and settle them into a menu. Imagine for example that if all the functions of Visual Studio could be operated with command buttons, editor window could not fit because of the large number of buttons. The menu starts from the main menu, this is always visible at the top of the program’s window and every menu item can have submenus. The traditional menu includes labels (Text), but newer menu items can show bitmaps as well, or there can be TextBoxes and ComboBoxes used for editing too. We can put a separator among menu items (being not in the main menu) and we can also put a checkbox in front of the menu items. Menu items similarly to command buttons get a unique identifier. The identifier can be typed by us, or similarly to the other controls, it can be named automatically. MenuStrip control appears under the form, but in the meanwhile we can write the name of the menu item in the top corner of our program, or we can choose the automatic name by using the “MenuItem” label. In the figure below we can see the start of menu editing, when we included MenuStrip:

Menustrip
Figure IV.19. Menustrip


Let’s select a menu item in the menu above:

Menuitem on the menustrip
Figure IV.20. Menuitem on the menustrip


The name of the menu item in the main menu is now toolStripMenuItem1. To the place of the menu item being next to it (in the main menu) let’s type: Help.

The Help menu
Figure IV.21. The Help menu


The name of the new menu item is now helpToolStripMenuItem, however its Text property does not have to be set: it is already done. Let’s create three submenu items under MenuItem1 using a separator before the third one:

The submenu
Figure IV.22. The submenu


From now programming is the same as in case of buttons: in form_load function we set their labels (Text), then we write the Click event handler methods by clicking on the menuitems in the editor:

private: System::Void toolStripMenuItem2_Click(System::Object^  sender, System::EventArgs^  e) {
// We have to write here what should happen after we have selected the menu item.
 }

IV.2.17. The ContextMenuStrip control which is invisible in basic mode

The main menu of the MenuStrip control is always visible. The ContextMenuStrip can only be seen while designing, during running it is only visible if it is visualized from the program. It is recommended to use it with the local menu appearing at the mouse cursor after clicking the right mouse button. We create the items of the menu in Designer, we write the Click event hanler of the menu items in the editor then in the MouseDown or MouseClick event handler of the Form (they have a coordinate parameter) we visualize the ContextMenu.

The program part below contains a ContextMenu with three menuitems:

The contextmenu
Figure IV.23. The contextmenu


The event control Form_MouseClick looks like this:

private: System::Void Form1_MouseClick(System::Object^  sender, System::Windows::Forms::MouseEventArgs^  e) {
  if (e->Button == Windows::Forms::MouseButtons::Right) // only for the right button
    contextMenuStrip1->Show(this->ActiveForm, e->X, e->Y); // we show the menu to the Form
}

IV.2.18. The menu bar of the toolkit: the control ToolStrip

The toolkit contains graphical command buttons next to each other. The Image property of the buttons contains the visualized image. Since they are command buttons, they run the Click event when clicking on them. The figure below shows the elements of the toolstrip, with the button contatining the image at the top, which will bear a name starting with toolStripButton:

Toolkit on toolstrip
Figure IV.24. Toolkit on toolstrip


IV.2.19. The status bar appearing at the bottom of the window, the StatusStrip control

A status bar visualises status information, therefore it is useful to create a label and a progressbar for it. The name of the label placed over it starts with toolStripLabel, that of the progressbar will start with toolStripProgressBar. It is not clicked in general, so Click event handlers are not written for it.

IV.2.20. Dialog windows helping file usage: OpenFileDialog, SaveFileDialog and FolderBrowserDialog

Almost all computer programs in which modifications carried out can be saved contain the Open file and Save file functions. Selecting a file to be saved or to be opened is done with the same window type in each software because the developers of .NET created a uniform control for these windows.

When program is designed, controls are placed under the form, while they are only visible if they are activated from the software. Activating them is carried out by a method (function) of the control object, the output is the result of the dialog (DialogResult: OK, Cancel etc). In the next code portion, a file with the extension .CSV is selected and opened with the help of the OpenFileDialog control in case the user has not clicked the Cancel button instead of Open:

System::Windows::Forms::DialogResult dr;
openFileDialog1->FileName = "";
openFileDialog1->Filter = "CSV files (*.csv)|*.csv";
dr=openFileDialog1->ShowDialog(); // open dialog window for a csv file
filename = openFileDialog1->FileName; // getting the file name.
if (dr==System::Windows::Forms::DialogResult::OK ) // if it is not the Cancel button that was clicked
  sr = gcnew StreamReader(filename); // opened for reading

The SaveFileDialog can be used in the same way but that one can be used to save a new file. The FolderBrowserDialog can be used to select a folder, in which for example we process all the images.

IV.2.21. The predefined message window: MessageBox

A MessageBox is a dialog window containing a message and is provided by the Windows. In a message box, the user can choose one of the command buttons. The combinations of the buttons are predefined constants and are provided as argumnets when the window is activated. This control cannot be found in the ToolBox, it is not necessay to put it on the Form because it was defined as a static class. It can be made appear by calling its Show() method the return value of which is the button the user clicked. In case we are sure that only one button can be chosen (because MessageBox only contains the OK button), the method can be called as a statement.

 The execution of the program is suspended by the MessageBox: its controls do not function at that time, the event handler does not continue to run until one of the command buttons is chosen. The syntax of calling the Show() method: result=MessageBox::Show(the message in the window, the header of the window, buttons);, where the data type of each element is as follows:

  • result: variable of type System::Windows::Forms::DialogResult

  • The message in the window and the header of the window: data of type String^

  • buttons: a data member of the MessageBoxButtons static class, from the following:

MessageBox::Show() can be called with one string argument: in that case, it will not have a header, and will have only the "OK” button. It is not a beautiful solution, is it?

The following example was already mentioned, now we will be able to interpret it:

System::Windows::Forms::DialogResult d;
d=MessageBox::Show("Are you sure that you would like to use the airbag?”, "Important security warning", MessageBoxButtons::YesNo);
if (d == System::Windows::Forms::DialogResult::No) e->Cancel=true;

The result of executing the code portion:

The MessageBox
Figure IV.25. The MessageBox


IV.2.22. Control used for timing: Timer

The Timer control makes it possible to run a code portion at given time intervals. It should be put under the form in the Designer, because it is not visible during execution. Its property named Interval contains the interval in milliseconds, and if the Enabled property is set to false, the timer can be disabled. If it is enabled and Interval contains a useful value (>=20 ms), the Timer executes its default event handler, the Tick. Progammers have to make sure that the code portion finish before the next event is occured (that is when time is out). The following code portion sets up the timer1 to run in every second:

timer1->Interval=1000; // every second
timer1->Enabled=true; // go !

The programmed timer of the next code prints out the current time in seconds into the title of the form:

DateTime^ now=gcnew DateTime(); // variable of type time.
now=now->Now; // what's the time?
this->Text=Convert::ToString(now); // show in the title of the form.

This code portion can further be developed in order that it would play the cuckoo.wav sound file which is longer than one second: if (now->Minute==0) && (now->Second==0). If we think that the timer has already run enough times, int the Tick event handler the Enabled property can be set to false: at that case, the current event was the last.

IV.2.23. SerialPort

The SerialPort control makes possible the communication between a serial port of rs-232 standard and the peripherial devices connected to it (modem, SOC, microcontroller, Bluetooth device). The port has to be parameterized then opened. Then, textual information (Write, WriteLine, Read, ReadLine) and binary data (WriteByte, ReadByte) can be sent and received. It also has an event handler function: if data arrive, the DataReceived event is run. The following code portion checks all available serial ports, and searches the hardware named "iCorset" by sending a "?" character. If it is not found, it exits with an error message. In the case of virtual serial ports (USBs), the value of the BaudRate parameter can be anything.

array<String^>^ portnames;
bool there_is_a_controller=false;
int i;
portnames=Ports::SerialPort::GetPortNames();
i=0;
while (i<portnames->Length && (!there_is_a_controller))
{
    if (serialPort1->IsOpen) serialPort1->Close();
    serialPort1->PortName=portnames[i];
    serialPort1->BaudRate=9600;
serialPort1->DataBits=8;
    serialPort1->StopBits=Ports::StopBits::One;
serialPort1->Parity = Ports::Parity::None;
    serialPort1->Handshake = Ports::Handshake::None;
    serialPort1->RtsEnable = true;
    serialPort1->DtrEnable = false;
    serialPort1->ReadTimeout=200; // 0.2 s
    serialPort1->NewLine="\r\n";
    try {
        serialPort1->Open();
        serialPort1->DiscardInBuffer();// usb !
        serialPort1->Write("?");
        s=serialPort1->ReadLine();
        serialPort1->Close();
    } catch (Exception^ ex) {
        s="Timeout";
    }
    if (s=="iCorset") there_is_a_controller=true;
    i++;
}
if (! there_is_a_controller) {
    MessageBox::Show("No iCorset Controller.",
"Error",MessageBoxButtons::OK);
    Application::Exit();
}

IV.3. Text and binary files, data streams

The operative memory of a PC has a small size (as compared to the quantity of data to be stored) and forgets data easily: PC does not even have to be turned off, it is enough to exit a program, and the values of the variables stored in the memory are lost. That is why, even the first computers, manufactured after the ferrite-ring memory period, contained a data storage device that stored the software currently not in use or the data currently not used. The storage unit is the file which is a set of logically related data. It is the task of the operating system to connect a logical file to the physical organisation of the disk and to make them available for computer programs and to create and manage the file system. The programs using these files refer to them by their name. There are operations that process the content of the files and those that do not. For example, renaming or deleting a file does not require processing its content, it only requires its name. The name of the file may contain its path. If it does not contain it, the default path is the folder of the project during software development. When the .exe file, ready to be used, is run, the default folder is that of the .exe file.

IV.3.1. Preparing to handling files

Contrary to graphics or to controls, file handling namespace is not inserted in the form when a new project is created. This is the task of programmers to insert it at the beginning of the form1.h, where the other namespaces are:

using namespace System::IO;

Another thing to do is to decide what the file to be handled contains and what we would like to do with it:

  • Only deleting, renaming, copying it or checking whether it exists.

  • We would like to handle it by bytes (as a block containing bytes) if we are brave enough: virus detection, character encoding etc.

  • It is a binary file with fixed-length record structure.

  • If it is a text file with variable-length lines (Strings), and the lines end with line feed.

The file name can be given in two ways:

IV.3.2. Methods of the File static class

bool File::Exists(String^ filename) It examines the existence of the file given in filename, if it exists, the output is true if not, it is false. By using it we can avoid some errors: opening a non-existing file, overwriting an important data file by mistake.

void File::Delete(String^ filename) It deletes the file given in filename. As opposed to current operating systems, deletion does not mean moving to a recycle bin but a real deletion.

void File::Move(String^ oldname, String^ newname) It renames the disk file named oldname to newname. If there are different paths in the filenames, the file moves into the other directory.

void File::Copy(String^ sourcefile, String^ targetfile) This method is similar to Move, except that the source file does not disappear but the file is duplicated. A new file is created on the disk, with the content of the source file.

FileStream^ File::Open(String^ filename, FileMode mode) Opening the given file. The FileStream^ does not get its value with gcnew but with this method. It does not have to be used for text files but for all the other files (containing bytes or binary files containing records) opening should be used. The values of mode:

  • FileMode::Append we go to the end of the text file and switch to write mode. If the file does not exist, a new file is created.

  • FileMode::Create this mode creates a new file. If the file already exists, it is overwritten. In the directory of the path, the current user should have a right for writing.

  • FileMode::CreateNew this mode also creates a new file but if the file already exists, we get an exception.

  • FileMode::Open opening an existing file for reading/writing. This mode is generally used after creating the file, e.g. after using the FileOpenDialog.

  • FileMode::OpenOrCreate we open an existing file, if it does not exist, a file with the given name is created.

  • FileMode::Truncate we open an existing file and delete its content. The length of the file will be 0 byte.

IV.3.3. The FileStream reference class

If files are processed by bytes or they are binary, we need to create a FileStream^ object for the file. The class instance of FileStream is not created by gcnew but by File::Open(), so the physical disk file and FileStream are assigned to each other. With the help of FileStream the actual file pointer is accessable, and it can be moved. The measure unit of the position and the movement is byte; its data type is 64 bit integer so that it should manage files bigger than 2GB. Its most frequently used properties and methods:

  • Length: read-only property, the actual size of the file in bytes.

  • Name: the name of the disk file that we opened.

  • Position: writable/readable property, the current file position in bytes. The next writing operation will write into this position, the next reading will read from here.

  • Seek(how much, with regard to what) method for movng the file pointer. With regard to the Position property, it can be given how we understand movement: from the beginning of the file (SeekOrigin::Begin), from the current position (SeekOrigin::Current), from the end of the file (SeekOrigin::End). This operation must be used also when we attach BinaryReader or BinaryWriter to FileStream since they do not have a Seek() method.

  • int ReadByte(), WriteByte(unsigned char) methods for reading and writing data of one byte. Reading and writing happens in the current position. At the level of the operating system, file reading is carried out into a byte array; these functions are realized as reading an array with one element.

  • int Read(array<unsigned char>, offset, count): a method for reading bytes into a byte array. The bytes will be placed from the array’s element with the index offset and the count is maximum number of bytes to read. Its return value is: how many bytes could be read.

  • Write(array<unsigned char>,offset, count): a method for writing a block of bytes from a byte array. Writing begins at at the element with the index offset and it writes maximum count elements.

  • Flush(void): clears buffers for this stream and causes any buffered data to be written to the file.

  • Close(): closing FileStream.  Files should be closed after use in order to avoid data loss and running out of resources.

IV.3.4. The BinaryReader reference class

If we want to read non-byte type binary data from a file, we use BinaryReader. In the BinaryReader ‘s constructor we give the opened FileStream handle as an argument. BinaryReader is created with a regular gcnew operator. It is important to note that BinaryReader is not able to open the disk file and to assign it to a FileStream. BinaryReader contains methods for the basic data types: ReadBool(), ReadChar(), ReadDouble(), ReadInt16(), ReadInt32(), ReadInt64(), ReadUInt16(), ReadString(), ReadSingle() etc. The file pointer is incremented with the length of the read data. BinaryReader also should be closed after use with the method Close() before closing the FileStream.

IV.3.5. The BinaryWriter reference class

If we want to write binary data into FileStream, we use BinaryWriter. It is created similarly to BinaryReader, with the operator gcnew. The difference is that Reader contained methods with a given return data type but Writer contains a method with a given parameter and without a return value, with a number of overloaded versions. The name of the method is Write and it can be used with several data types: from Bool to cli::Array^ in the order of complexity. The overview of binary file processing can be seen below:

Binary file processing
Figure IV.26. Binary file processing


IV.3.6. Processing text files: the StreamReader and StreamWriter reference classes

Binary files that have been treated in the previous subsections are composed of fixed-length records. So a program can easily to calculate by a simple multiplication at which byte position the data to be searched for is and it can move the pointer to that position by the Seek() operation. For example if data are stored as 32-bit integers, each element occupies 4 bytes, the element having the index 10 (which actually is the 11th element) starts at the 10*4=40th byte position. In that case, as the file pointer can be moved anywhere, we speak about random access. Binary files can only be processed by a program which knows their record structure, which is in general, the program that created them.

Text files are composed of variable-length lines that are legible for human beings as well. In these files, characters are stored in ASCII, Unicode, UTF-8 etc. encoding, one line of a text file corresponds to the data type String^ of the compiler. Lines end with CR/LF (two characters) under DOS/Windows-based systems. Because of variable-length lines, text files can only be processed sequentially: reading the 10th line can be done by reading the first 9 lines and finally the requested 10th line. When the file is opened, it cannot be calculated at which byte the 10th line starts in the file, only after all preceding lines have been read.

Text files have an important role in realizing communication between different computer programs. Since they can be read for example in NotePad, humans can also modify their content. Text files are used, among other things, to save databases (in that case, a text file is called dump and it contains SQL statements that create the saved database on an empty system), to communicate with Excel (comma or tabulator separated files with CSV extension) and even e-mails are transferred as text files between incoming and outgoing e-mail servers. Measuring devices also create text files containing the measurement results. In these files, each line contains one measurement data in order that these data could be processed or visualized with any software (even with Excel) by a user carrying out the measurement.

Text files can be processed by reference variables of type StreamReader and StreamWriter classes. For that purpose, the gcnew operator should be used, and the name of the file should be specified in its constructor. A FileStream is not needed to be defined because StreamReader and StreamWriter use exclusively disk files; therefore they can create for themselves their own FileStream (BaseStream) with which programmers do not have to deal. The most frequently used method of StreamReader is ReadLine(), which reads the next line of the text file and its most frequently used property is EndOfStream, which becomes true accessing the end of the file. Attention: EndOfStream shows the state of the latest reading operation, ReadLine() returns a zero-length string at the end of the file, and the value of EndOfStream will be true. Thus, the ordinary pre-test loops can be used (while (! StreamReader->EndOfStream) …). One only has to examine if the length of the currently read line is greater than 0. The most frequently used method of StreamWriter is WriteLine(String), which writes the string passed as a parameter and `the newline character in the text file. Write(String) is the same but it does not write the newline character. The newline character(s) (CR,LF,CR/LF) can be set by the NewLine property.

The following code portion that does not function independently (i.e. without the helper functions and the initialisation parts) processes text files of CSV extension composed of semi-colon separated lines. The user is asked to select the file to be processed, the program reads the data line by line, calculates something from the read data and prints out the result at the end of the read line. After a file is processed, a total value is also calculated. Th program writes all outputs into a temporary file since the original text file is opened for reading. If data processing is done, the original file is deleted and the temporary file will receive the name of the original file. The result: the original text files will contain the results that have been calculated.

private: System::Void button1_Click(System::Object^  sender, System::EventArgs^  e) {
int m, credits, grade, creditsum = 0, gradesum = 0;
System::Windows::Forms::DialogResult dr;
String^ subject, ^line, ^outputstring = "";
openFileDialog1->FileName = "";
openFileDialog1->Filter = "CSV files (*.csv)|*.csv";
dr=openFileDialog1->ShowDialog(); // file open dialogbox, csv file
filename = openFileDialog1->FileName; // getting the file name
if (dr==System::Windows::Forms::DialogResult::OK ) // if it is not the Cancel button that was clicked
{
  sr = gcnew StreamReader(filename); // open for reading
  sw = gcnew StreamWriter(tmpname); // open the file for writing
  while (!sr->EndOfStream) // always using a pre-test loop
  {
    line = sr->ReadLine(); // a line is read
    if ((line->Substring(0,1) != csch) && (line->Length > 0))
    // if it is not a separating character, it is processed
    {
      m = 0; // all lines are read from the first character
      subject = newdata(line,  m); // lines are split
      credits = Convert::ToInt32(newdata(line, m)); // into 3 parts
      grade = Convert::ToInt32(newdata(line,  m));
      // composing the output string
      outputstring = outputstring + "subject:"+subject + " credits:" +
             Convert::ToString(credits) +" grade:" +
             Convert::ToString(grade) + "\n";
      // and a weighted average is counted
      creditsum = creditsum + credits;
      gradesum = gradesum + credits * grade;
      sw->WriteLine(line+csch+Convert::ToString(credits*grade));
   } else { // is not processed but written back to the file.
      sw->WriteLine(line);
    } // if
  } // while
  sr->Close(); // do not forget to close the file
  wa = (double) gradesum / creditsum; // otherwise the result is integer
  // the previous line contained \n at its end, the result is written in a new line
  outputstring = outputstring + "weighted average:" + Convert::ToString(sa);
  label1->Text = outputstring;
  sw->WriteLine(csch + "weighted average"+ csch + Convert::ToString(wa));
  sw->Close(); // output file is also closed,
  File::Delete(filename); // the old data file is deleted
  // and the temporary file is renamed to the original data file.
  File::Move(tmpname, filename);
  }
}

IV.3.7. The MemoryStream reference class

One can also create sequential files, composed of bytes that are not stored on a disk but in the memory. A great advantage of streams created in the memory is the speed (a memory is at least two times faster than a storage device), its disadvantage is its smaller size and that its content is lost if the program is exited. MemoryStream has the same methods as FileStream: it reads/writes a byte or an array of bytes. It can be created by the gcnew operator. The maximal size of a MemoryStream can be set in the parameter of the constructor. If no parameter is given, MemoryStream will allocate memory dynamically for writing. Using that class has two advantages as compared to arrays: on one hand, automatic allocation and on the other hand, a MemoryStream can easily be transformed into a FileStream if memory runs out by using File::Open() instead of gcnew.

IV.4. The GDI+

The GDI Graphics Device Interface is a device independent (printer, screen) module of Windows systems used to develop and visualize 2D graphics (gdi.dll, gdi32.dll). The GDI+ is the improved version of this module that is also 2D and was introduced first in Windows XP and Windows Server 2003 operating systems (GdiPlus.dll). Newer Windows systems ensure the compatibility with programs developed with older versions, however, programmers can also use the optimized, inherited and new features of GDI+.

IV.4.1. The usage of GDI+

GDI+ was developed with object-oriented approach, that is, a 32/64 bit graphical programming interface which offers classes of C++ to draw in managed (.NET) as well as non-managed (native) programs. The GDI is not directly connected to the graphical hardware but to its driver. The GDI is capable of visualizing 2D vector-graphic illustrations, handling images and displaying textual information of publications in a flexible manner:

  • 2D vector graphics: when making drawings, lines, curves and shapes bounded by these are defined by points given in coordinate system, and can be drawn with pens and painted with brushes..

  • Storing and displaying images: besides the flexible image storing and manipulating features, it offers the possibility to read and save a variety of image formats (BMP, GIF, JPEG, Exif, PNG, TIFF, ICON, WMF, EMFll).

  • Displaying texts: it is the task of GDI+ to visualize plenty of fonts on the screen as well as on the printer device.

The basic functions of GDI+ are contained in the System::Drawing namespace. Further options of 2D drawing can be reached using the elements of the System::Drawing::Drawing2D namespace.

The System::Drawing::Imaging namespace contains classes for handling images in an advanced level, whereas the System::Drawing::Text namespace ensures the special options of textual visualization [4.1.] .

IV.4.2. Drawing features of GDI

In order to use the basic functions of GDI+ in our program we have to use the System::Drawing namespace. (So that it can be reached the System.Drawing DLL file should be listed among the references Project / Properties / References: ).

using namespace System::Drawing;

The System::Drawing namespace contains the classes required for drawing (Figure IV.27, “The classes of GDI+”). It is worth to highlight the Graphics class which is modelling the drawing area like it was a drawing paper. In order to draw on the drawing paper some data structures are required that are defined in the System::Drawing namespace. We can define on the drawing paper which coordinate system to use. The color model used by GDI+ is the alfa-r-g-b model (the Color structure), that is, colors can be mixed not only with the primary colors of red, green and blue but the level of transparency of the color can be set by the alfa parameter.

The integer type x and y coordinates of points can be stored in the Point structure, in case of real (float) coordinates PointF can be used. Similarly to the points the Rectangle and RectangleF structures can be used to store and access the corners of rectangles in a variety of ways. The Size and SizeF structures can be used to store the vertical and horizontal size of shapes.

Real drawing tools are modeled by classes. Our basic drawing tool is the pen (Pen class that cannot be inherited). We can set its color, width and pattern. The tool for painting planar shapes is the brush (Brush parent class). The derived classes of Brush are the SolidBrush that are modeling a brush dipped into a single color and the TextureBrush that leaves marks of a bitmap pattern. The HatchBrush paints a hatched pattern, whereas the LinearGradientBrush and PathGradientBrush leaves gradient marks. In order to use these latest classes the System::Drawing::Drawing2D namespace is required, too. The derived brushes cannot be inherited further. The Image abstract class has properties and member functions used to store and handle bitmaps and metafiles. The Font class contains various character forms, that is fonts. The FontFamily is the model of font families. Neither the Font nor the FontFamily classes cannot be inherited further. The Region class (cannot be inherited) is modelling an area of the graphic tool bounded by the sides of rectangles and paths. The Icon class serves to handle Windows icons (small size bitmaps).

The classes of GDI+
Figure IV.27. The classes of GDI+


IV.4.3. The Graphics class

Before getting familiar with the Graphics class, let’s have a few words about the Paint event of the controls. The graphic objects appearing in the windows have to be often redrawn for example when being repainted. This happens in a way that the Windows automatically invalidates the area that we would like to repaint and calls the handler function of the objects’ Paint event. In case we would like to refresh our drawing on top of the objects popup from the hiding, then we have to place the drawing methods of the Graphic class in the handler functions of the Paint event. The event handler of the Paint event has a PaintEventArgs type reference parameter that are defined by Windows when calling [4.2.] .

private: System::Void Form1_Paint(
    System::Object^ sender,
    System::Windows::Forms::PaintEventArgs^ e){ }

The PaintEventArgs class have two properties, the Rectangle type ClipRectangle that contains data of the area that we would like to repaint and the Graphics type read-only Graphics that identifies the drawing paper as a drawing tool when repainting.

Later, we will show in more details how to create Pen type pen for drawing with the gcnew operator and how to define its color by the constructor argument using the Color::Red constant of the System::Drawing namespace. Once we have our pen, then the Line() method of the Graphics class can draw lines with it (Figure IV.28, “The drawn line automatically appears after every resizing activity.”).

private: System::Void Form1_Paint(System::Object^ sender,
    System::Windows::Forms::PaintEventArgs^ e){
    Pen ^ p= gcnew Pen(Color::Red);
    e->Graphics->DrawLine(p,10,10,100,100);
}

The drawn line automatically appears after every resizing activity.
Figure IV.28. The drawn line automatically appears after every resizing activity.


We can also initiate the repainting of windows and controls anywhere in the program. The Invalidate(), Invalidate(Rectangle) and Invalidate(Region) methods of the Control class invalidate the entire window / control or the selected area of them and activate the Paint event. The Refresh() method of the Control class invalidates the area of the window / control and initiates the repainting immediately.

We can draw on the window anywhere from the program if we create an instance of the drawing paper from the Graphics class with the CreateGraphics() method of the control / window. This time, however, the line drawn with the blue pen appears only once and will not be redrawn after hiding.

    Pen ^ p= gcnew Pen(Color::Blue);
    Graphics ^ g=this->CreateGraphics();
    g->DrawLine(p,100,10,10,100);

If we do not draw in Paint then the blue line disappears when maximizing after minimizing.
Figure IV.29. If we do not draw in Paint then the blue line disappears when maximizing after minimizing.


We can clear the given drawing paper with the defined background color by the

    Clear(Color & Color);

method of the Graphics class.

IV.4.4. Coordinate systems

With the help of GDI+ we can think in three coordinate systems. We can create our model that we like to draw in 2D in the world coordinate system. It is useful to use such mapping that we could give these world coordinates to the drawing methods.  Since the drawing methods of the Graphics class are able to receive the integer numbers (int) and floatingpoint real numbers (float) as parameters, that is why it is useful to store the world coordinates as int or float type too.  We have to do ourselves the projection of 3D world to plane. The drawing paper modeled by the Graphics type reference uses the page coordinate system and it can be a form, an image storage or even a printer. It is the device coordinate system that a given tool uses (for example the left upper corner of the screen is the origin and the pixels are the units). Thus when drawing two mapping should happen:

    world_coordinate → page_coordinate → device_coordinate

The first mapping have to be done by ourselves. We have more choices to obtain this. Spatial points can be projected to the plane in two ways, with parallel or central projection rays.

Projection with parallel ray is called axonometry. We can set up the mathematical model of projection by drawing the parallely projected image of x-y-z three-dimensional coordinate system to the planar sheet of the ξ-η coordinate system, assuming that the image of the three-dimensional coordinate system’s origin is the origin of the planar coordinate system. The image of x-y-z three-dimensional coordinate system appears with dashed lines in the ξ-η coordinate system drawn with solid lines (Figure IV.30, “General axonometry”). According to Figure IV.30, “General axonometry” let’s mark the angle between ξ- and x-axes with α, the angle between ξ- and y-axes with β and the angle between η- and z-axes with γ! As the x-y-z coordinate axes are not parallel with the plane ξ-η, the image of coordinate units projected to the planar coordinate system seems to be shorter. The x-direction seems to be q x (≤1) long, the y-direction unit q y (≤1) and the z-direction unit q z (≤1) long.

General axonometry
Figure IV.30. General axonometry


Therefore, if we are looking for the planar, mapped coordinates (ξ,η) of a point with coordinates (x,y,z), then the pair of functions (f ξ , f η ) implements the mapping according to (IV.4.1).

 

(IV.4.1)

Mapping can be described simply, if we consider that starting from the origin we are moving units x, y and z parallel to the images of the axes according to the shortenings, thus getting to the image (ξ, η) of point (x, y, z) (Figure IV.30, “General axonometry”). It follows that summarizing the projections of ξ and η with the red arrow, we get the coordinates as follows (IV.4.2).

 

(IV.4.2)

A special type of axonometry is the isometric axonometry, when there is a 120° angle between the projections of x-y-z axes, the projection of z-axis coincides with η-axis (α=30, β=30, γ=0) and the shortenings are q x =q y =q z =1 (Figure IV.31, “Isometric axonometry”).

Isometric axonometry
Figure IV.31. Isometric axonometry


Another widely used axonometry is the Cavalier or military axonometry, where the horizontal y- axis coincides with the ξ-axis, the vertical z-axis coincides with the η-axis and there is a 135° angle between the projection of the x-axis and the other two axes (α=45, β=0, γ=0). The shortenings are q x =0,5, q y =q z =1.

Military axonometry
Figure IV.32. Military axonometry


By default, the origin of the sheet’s 2D coordinate system – in case of form – is in the top left corner of the form’s active area, the x-axis is pointing to the right, while the y-axis is pointing down and the units are pixels in both axes (Figure IV.33, “The default coordinate-system on form”).

Therefore, until any further actions are taken, the arguments of the already-known DrawLine() method of the Graphics class are values in this coordinate system.

As an example, let’s create the function producing the mapping of Cavalier axonometry. The input parameters of this function are the spatial coordinates (x, y, z), by substituting the constants of the Cavalier axonometry (IV.4.2) the function returns a PointF type sheet point.

The default coordinate-system on form
Figure IV.33. The default coordinate-system on form


PointF Cavalier(float x, float y, float z) {
float X = 0;
float Y = 0;
    X=(-x*(float)Math::Sqrt(2)/2/2+y);
    Y=(-x*(float)Math::Sqrt(2)/2/2+z);
return PointF(X, Y);
}

As an example, using the Cavalier() function and the DrawLine() method of the Graphics class, let’s draw in the Paint event handler a 100 units sided cube centred at point (250,250,250) illustrated in axonometry.

private: System::Void Form1_Paint(System::Object^  sender,
    System::Windows::Forms::PaintEventArgs^  e) {
// the centre and the half side            
float ox=250;
    float oy=250;
    float oz=250;
    float d=50;
// The red pen
    Pen ^p=gcnew Pen(Color::Red);
// The upper side
    e->Graphics->DrawLine(p,Cavalier(ox-d,oy-d,oz-d),
                    Cavalier(ox+d,oy-d,oz-d));
    e->Graphics->DrawLine(p,Cavalier(ox+d,oy-d,oz-d),
                    Cavalier(ox+d,oy+d,oz-d));
    e->Graphics->DrawLine(p,Cavalier(ox+d,oy+d,oz-d),
                    Cavalier(ox-d,oy+d,oz-d));
    e->Graphics->DrawLine(p,Cavalier(ox-d,oy+d,oz-d),
                    Cavalier(ox-d,oy-d,oz-d));
// The lower side
    e->Graphics->DrawLine(p,Cavalier(ox-d,oy-d,oz+d),
                    Cavalier(ox+d,oy-d,oz+d));
    e->Graphics->DrawLine(p,Cavalier(ox+d,oy-d,oz+d),
                    Cavalier(ox+d,oy+d,oz+d));
    e->Graphics->DrawLine(p,Cavalier(ox+d,oy+d,oz+d),
                    Cavalier(ox-d,oy+d,oz+d));
    e->Graphics->DrawLine(p,Cavalier(ox-d,oy+d,oz+d),
                    Cavalier(ox-d,oy-d,oz+d));
// The lateral edges
    e->Graphics->DrawLine(p,Cavalier(ox-d,oy-d,oz-d),
                    Cavalier(ox-d,oy-d,oz+d));
    e->Graphics->DrawLine(p,Cavalier(ox+d,oy-d,oz-d),
                Cavalier(ox+d,oy-d,oz+d));
    e->Graphics->DrawLine(p,Cavalier(ox+d,oy+d,oz-d),
                    Cavalier(ox+d,oy+d,oz+d));
    e->Graphics->DrawLine(p,Cavalier(ox-d,oy+d,oz-d),
                    Cavalier(ox-d,oy+d,oz+d));
}

Cube in axonometry
Figure IV.34. Cube in axonometry


It is clearly shown in Figure IV.34, “Cube in axonometry” that axonometric mapping keeps distance in the given coordinate direction. Therefore, the back edge of the cube seems to have the same length as the front edge. However, using our eyes, we do not get this very same image. Our eyes perform central projection.

Central projection projects spatial points to the picture plane with the help of rays arriving at a certain point. In order to get the simple formulas of central projection, let’s suppose that we project the points of the spatial x-y-z coordinate system to the picture plane S parallel with x-y plane. Let’s mark the origin of the spatial x-y-z coordinate system with C. C will be the centre of projection. The orogin of ξ-η coordinate system in plane S should be O, and let it to be of d distance from C on the z-axis of the spatial coordinate system. The axes of ξ-η coordinate system of plane S are parallel with x- and y-axes (Figure IV.35, “Central projection”). Let’s find the mapping of the central projection corresponding to (IV.4.1).

Central projection
Figure IV.35. Central projection


As shown in the graphic, let the distance of the origin and centre be d. Let x, y, z be the coordinates of a spatial point P and let the projection of this point be P * in the plane with coordinates ξ, η. The projection of point P to x-z plane is Pxz (of which distance from z-axis is coordinate x exactly), to y-z plane it is P yz (of which distance from z axis is coordinate y exactly). The projection of P xz  in plane S is P * ξ , of which distance from the origin of the planar coordinate-system is coordinate ξ exactly, the projection of P yz in plane S is P * η of which distance from the zero point of the planar coordinate-system is coordinate η exactly. As triangle COP * ξ is similar to triangle CTP xz , therefore

 

(IV.4.3)

that is

 

(IV.4.4)

Similarly, in plane yz triangle CTP yz is similar to triangle COP η , therefore

 

(IV.4.5)

that is

 

(IV.4.6)

With this we generated (IV.4.1) mapping, as after rearranging we get

 

(IV.4.7)

The problem with formulas in (IV.4.7) is that the mapping is not linear. We can make it linear by increasing the number of dimensions (from two to three), and we introduce homogeneous coordinates. Let’s have a four-dimensional point [x, y, z, 1] instead of the spatial point [x, y, z]. That is we are thinking in the w=1 three-dimensional subspace within the four-dimensional space of [x, y, z, w]. In this space the mapping of (IV.4.7) is

 

(IV.4.8)

which is linear since it can be rewritten to form (IV.4.9).

 

(IV.4.9)

As an example, let’s make the Perspective function performing the mapping of central projection. Input parameters are spatial coordinates (x, y, z). By giving the focal length d and using formulas (IV.4.4, IV.4.6), the function gives a PointF type sheet point.

PointF Perspective(float d, float x, float y, float z) {
float X = 0;
float Y = 0;
    X = d * x / z;
    Y = d * y / z;
return PointF(X, Y);
}

As an example, using the Perspective() function and the well-known DrawLine() method of the Graphics class, let’s draw in the Paint event handler a 100 units sided cube centred at point (150,150,150) illustrated in perspectiveaxonometry with f=150, f=450 and f=1050 focal lengths (Figure IV.36, “The perspective views of the cube”).

private: System::Void Form1_Paint(
        System::Object^  sender,
        System::Windows::Forms::PaintEventArgs^  e) {
// the centre and the half side            
float ox=150;
float oy=150;
float oz=150;
float d=50;
float f=350;
// The red pen
    Pen ^p=gcnew Pen(Color::Red);
// The upper side
    e->Graphics->DrawLine(p,Perspective(f,ox-d,oy-d,oz-d),
                    Perspective(f,ox+d,oy-d,oz-d));
    e->Graphics->DrawLine(p,Perspective(f,ox+d,oy-d,oz-d),
                    Perspective(f,ox+d,oy+d,oz-d));
    e->Graphics->DrawLine(p,Perspective(f,ox+d,oy+d,oz-d),
                    Perspective(f,ox-d,oy+d,oz-d));
    e->Graphics->DrawLine(p,Perspective(f,ox-d,oy+d,oz-d),
                    Perspective(f,ox-d,oy-d,oz-d));
// The lower side
    e->Graphics->DrawLine(p,Perspective(f,ox-d,oy-d,oz+d),
                    Perspective(f,ox+d,oy-d,oz+d));
    e->Graphics->DrawLine(p,Perspective(f,ox+d,oy-d,oz+d),
                    Perspective(f,ox+d,oy+d,oz+d));
    e->Graphics->DrawLine(p,Perspective(f,ox+d,oy+d,oz+d),
                    Perspective(f,ox-d,oy+d,oz+d));
    e->Graphics->DrawLine(p,Perspective(f,ox-d,oy+d,oz+d),
                    Perspective(f,ox-d,oy-d,oz+d));
// The lateral edges
    e->Graphics->DrawLine(p,Perspective(f,ox-d,oy-d,oz-d),
                    Perspective(f,ox-d,oy-d,oz+d));
e->Graphics->DrawLine(p,Perspective(f,ox+d,oy-d,oz-d),
                    Perspective(f,ox+d,oy-d,oz+d));
    e->Graphics->DrawLine(p,Perspective(f,ox+d,oy+d,oz-d),
                    Perspective(f,ox+d,oy+d,oz+d));
    e->Graphics->DrawLine(p,Perspective(f,ox-d,oy+d,oz-d),
                    Perspective(f,ox-d,oy+d,oz+d));
}

We can see the effect of focal length. Focal length-change behaves exactly the same as the objective of cameras. In case of small focal length there is a large viewing angle and strong perspective, in case of large focal length there is a small viewing angle and weak perspective.

The perspective views of the cube
Figure IV.36. The perspective views of the cube


In the examples above we ensured that spatial coordinates are always projected to the plane, and we chose the centre and size of the cube in a way that it surely gets to the picture. The units of the cube’s data can be given either in mm, m or km. By drawing the cube to the form, the centres and the length of the edges were all given in pixels. However, by this we implicitly performed a zoon in/zoom out (mm->pixel, m->pixel or km->pixel). Based on the window sizes and the geometry to be displayed this should always be defined (Window-Viewport transformation [4.3.] ). In GDI+ we can use the Transform property of the Graphics class to create coordinate transformation, thereby a Window-Viewport transformation can also be easily performed.

IV.4.5. Coordinate transformation

As we have seen the origin of the page coordinate system is the top left corner of the  active area of the window, the x-axis points to the right, the y-axis points down and the units are pixels (Figure IV.33, “The default coordinate-system on form”). The Transform property of the Graphics class is a reference pointing to an instance of the Matrix class. The Matrix class is defined on the System::Drawing::Drawing2D; namespace.

The Matrix class is the model of a transformation matrix of the 2D plane with homogeneous coordinates that contain 3x3 elements and its third column [0,0,1]T. The m 11 , m 12 , m 21 , m 22 mean the rotation and the scaling along the axes of the coordinate transformation, whereas d x , d y mean the translation (IV.4.10).

 

(IV.4.10)

Transformation matrixes implement geometric transformations [4.4.] . Processing the geometric transformations one after the other results in one single geometric transformation. The transformation matrix of this is the first transformation matrix multiplied by the second transformation matrix from the left. Applying the geometric transformation matrixes one after the other is a non-commutative operation similarly to multiplying matrixes which is also non- commutative. We have to pay attention to this when assigning the Transform property. For defining the transformation we can use the methods of the Matrix class.

We can create a transformation matrix instance with the constructors of the Mátrix class. The

Matrix()

constructor activated without a parameter creates a three dimensional identity matrix. For example if we create an E identity matrix and we assign it to the Graphics->Trasform property, it will keep the coordinate system shown on Figure IV.33, “The default coordinate-system on form”.

private: System::Void Form1_Paint(System::Object^  sender,
        System::Windows::Forms::PaintEventArgs^  e) {
    Matrix ^ E=Matrix();
    e->Graphics->Transform=E;
    Pen ^ p= gcnew Pen(Color::Red);
    e->Graphics->DrawLine(p,10,10,100,100);
}

We can achieve the same result with the void type Reset() method of the Matrix class:

    Matrix ^ E=Matrix();
    E->Reset();

We can define any transformation (that is linear in homogeneous coordinates) if we use one of the parametric constructors. As a definition of the transformation we can distort a rectangle into a parallelogram while moving it at the same time (Figure IV.37, “Setting the distortion”). In order to achieve this we can create the transformation with the following parametric constructor:

    Matrix(Rectangle rect, array<Point>^ points)

The constructor creates such a geometric transformation that distorts the rect rectangle set as the parameter to a parallelogram (Figure IV.37, “Setting the distortion”). The points array contains three points. Respectively the first element is the upper left corner point of the parallelogram (ul), the second element is the upper right corner point (ur) and the lower left corner point is the third one (ll) (the place of the fourth point will be derived).

Setting the distortion
Figure IV.37. Setting the distortion


The DrawRectangle() method of the Graphics class draws a rectangle on the form with setting the pen, the starting coordinates, the width and the height. In the next example we will draw a rectangle of (10,10) starting points, 100 width and 100 height size values in red. With setting a transformation we modified the rectangle in a way that it was placed into (20,20) starting points, its height to be 200 and to be inclined 45 degrees. After this we drew the same rectangle in blue, and we can see the translation, the scaling and the shearing were applied.

private: System::Void Form1_Paint(System::Object^ sender,
        System::Windows::Forms::PaintEventArgs^ e) {
    Pen ^ pen= gcnew Pen(Color::Red);
    e->Graphics->DrawRectangle(pen,10,10,100,100);
array< PointF >^ p = {PointF(120,220), PointF(220,220),
                PointF(20,20)};
    Matrix ^ m = gcnew Matrix(RectangleF(10,10,100,100),p);
    e->Graphics->Transform=m;
    pen= gcnew Pen(Color::Blue);
    e->Graphics->DrawRectangle(pen,10,10,100,100);
}

The distortion
Figure IV.38. The distortion


The transformation matrix can be set by elements (formula IV.4.7) as well using the other parametric constructor:

    Matrix(float m11, float m12, float m21, float m22, float dx, float dy);

The next example uses a translaion (10, 10), double scaling in x-direction and 1.5x scaling in y-direction:

private: System::Void Form1_Paint(System::Object^  sender,
        System::Windows::Forms::PaintEventArgs^  e) {
    Pen ^ pen= gcnew Pen(Color::Red);
    e->Graphics->DrawRectangle(pen,0,0,100,100);
    Matrix ^ m = gcnew Matrix(2,0,0,1.5,50,50);
    e->Graphics->Transform=m;
    pen= gcnew Pen(Color::Blue);
    e->Graphics->DrawRectangle(pen,0,0,100,100);
}

Trasnlating and scaling with a matrix
Figure IV.39. Trasnlating and scaling with a matrix


Of course we can access the elements of the matrix using the (Array<float> type) Elements read only property of the Matrix class.

Fortunately we can create a transformation not only with setting the coordinates in the matrix but with the transformational methods of the Matrix class as well. We can apply the transformational functions to the instaces of the Matrix class as we multiplied the matrix of the current instance from the right or from the left by the matrix of the transformational method. The MatrixOrder type enumeration helps to control this and its members are MatrixOrder::Prepend (0, from the right) and MatrixOrder::Append (1, from the left).

We can directly multiply the instance of the class that contains the matrix by the given Matrix transformation matrix:

    Multiply(Matrix matrix [,MatrixOrder order])

The square bracket means that it is not mandatory to set the MatrixOrder type order parameter. The default value of the order parameter is MatrixOrder::Prepend. We skip the explanation of the square brackets in case of the next methods.

We can define a translation and apply it on the matrix instance with the method:

    Translate(float offsetX, float offsetY
        [,MatrixOrder order]);

We can perform rotation transformation around the origin of the coordinate system with angle alfa

    Rotate(float alfa [, MatrixOrder order])

or around a given point with alfa angle (set in degrees!);

    RotateAt(float alfa, PointF  point [,MatrixOrder order]);

Both methods are void methods.

The next example rotates a line around the midpoint of the form. In order to achieve this we have to know that the Size tpye ClientSize property of the form contains the dimensions of the client area.

private: System::Void Form1_Paint(System::Object^  sender,
        System::Windows::Forms::PaintEventArgs^  e) {
    Matrix ^ m = gcnew Matrix();
    Pen ^ p = gcnew Pen(Color::Red);
float x=this->ClientSize.Width/2;
float y=this->ClientSize.Width/2;
for (int i=0; i<360; i+=5) {
        m->Reset();
        m->Translate(x,y);
        m->Rotate(i);
    e->Graphics->Transform=m;
    e->Graphics->DrawLine(p,0.0F,0.0F,
(float)Math::Min(this->ClientSize.Width/2,
this->ClientSize.Height/2),0.0F);
    }
}

Translation and rotation
Figure IV.40. Translation and rotation


We can apply scaling (scaleX, scaleY) along the coordinate axes with a similar function:

    Scale(float scaleX, float scaleY[, MatrixOrder order]);

Shearing along one of the coordinate direction means that the axis remains in place and as we move away from the axis parallel translation of the points along the fixed axis is proportional with the distance from the axis.

The transformation matrix contaning homogeneous coordinate of the shearing is:

 

(IV.4.11)

We also have a function to define the shearing, where we can set the shearing coordinates of the matrix:

    Shear(float mX, float mY, [, MatrixOrder order]);

In the next example we translate and shear a rectangle in both directions:

private: System::Void Form1_Paint(System::Object^  sender,
        System::Windows::Forms::PaintEventArgs^  e) {
    Pen ^ pen= gcnew Pen(Color::Red);
    e->Graphics->DrawRectangle(pen,0,0,100,100);
    Matrix ^m=gcnew Matrix();
            m->Translate(10,10);
            m->Shear(2,1.5);
    e->Graphics->Transform=m;
    pen= gcnew Pen(Color::Blue);
    e->Graphics->DrawRectangle(pen,0,0,100,100);
}

Trasnlating and shearing
Figure IV.41. Trasnlating and shearing


We can calculate the inverse of the transformation matrix with the void tpye method of the Matrix class:

    Invert();

If we do not want to use the Transform property of the Graphics class then we can use directly the following methods of the Graphics class that directly modify the Transform property:

    ResetTransform()
    MultiplyTransform(Matrix m, [, MatrixOrder order]);
    RotateTransform(float szog , [, MatrixOrder order])
    ScaleTransform(float sx, float sy[, MatrixOrder order])
    TranslateTransform Method(float dx, float dy
                    [, MatrixOrder order])

The Sheet transformation that maps sheets to a device can use the PageUnit and PageScale properties of the Graphics class in order to define the mapping. The PageUnit property can take a value of the System::Drawing::GraphicsUnit enumeration (UnitPixel=2, UnitInch=4, UnitMillimeter=6…).

For the example we set millimeter as a sheet unit. If the length of side of the square is 20 mm, then it will be also around 20 mm on the screen. If we set the float type PageScale property of the Graphics class then it will set our scaling on the sheet. For example if PageScale = 0.5 then the 20 mm sided square will become 10 mm (Figure IV.39, “Trasnlating and scaling with a matrix”).

private: System::Void Form1_Paint(System::Object^  sender,
        System::Windows::Forms::PaintEventArgs^  e) {
    e->Graphics->PageUnit=GraphicsUnit::Millimeter;
    Pen ^ pen= gcnew Pen(Color::Red);
    e->Graphics->DrawRectangle(pen,10,10,30,30);
    e->Graphics->PageScale=0.5;
    pen= gcnew Pen(Color::Blue);
    e->Graphics->DrawRectangle(pen,10,10,30,30);
}

The mm scale and the PageScale property
Figure IV.42. The mm scale and the PageScale property


The GDI+ receives the screen resolution information from the driver and it can set the data of the mapping from here. The DpiX and DpiY read only properties contain the dot/inch resolution for the given device.

IV.4.6. Color handling of GDI+ (Color)

We use the Color structure to store GDI+ colors. This is nothing more than a 32-bit integer which each byte has a meaning (compared to traditional GDI colors where only 24 bits from the 32 coded colors). The 32-bit information is called ARGB color, the most significant byte (Alpha) shows the transparency, the rest 24 bits code the intensity of the GDI related Red, Green and Blue color components. (Alpha=0 is fully transparent, 255 is not transparent, the values for the color componenets indicate the intensity of them.) We can create a color structure with the FromArgb() method of the System::Drawing::Color structure. Calling the overloaded versions of the FromArgb() we can