Mechatronic Systems Programming in C
Mechatronic Systems Programming in C
in C++
Tamás, Péter
Huba, Antal
Gräff, József
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.
iii
Created by XMLmind XSL-FO Converter.
Mechatronic Systems Programming
in C++
iv
Created by XMLmind XSL-FO Converter.
Mechatronic Systems Programming
in C++
v
Created by XMLmind XSL-FO Converter.
Mechatronic Systems Programming
in C++
vi
Created by XMLmind XSL-FO Converter.
Mechatronic Systems Programming
in C++
2.15. Control with the ability to visualize PixelGrapic images: PictureBox ................ 244
2.16. Menu bar at the top of our window: MenuStrip control ..................................... 247
2.17. The ContextMenuStrip control which is invisible in basic mode ....................... 250
2.18. The menu bar of the toolkit: the control ToolStrip ............................................. 250
2.19. The status bar appearing at the bottom of the window, the StatusStrip control .. 251
2.20. Dialog windows helping file usage: OpenFileDialog, SaveFileDialog and
FolderBrowserDialog .................................................................................................. 251
2.21. The predefined message window: MessageBox ................................................. 251
2.22. Control used for timing: Timer ........................................................................... 252
2.23. SerialPort ............................................................................................................ 253
3. Text and binary files, data streams .................................................................................... 253
3.1. Preparing to handling files .................................................................................... 254
3.2. Methods of the File static class ............................................................................. 254
3.3. The FileStream reference class ............................................................................. 255
3.4. The BinaryReader reference class ........................................................................ 255
3.5. The BinaryWriter reference class ......................................................................... 256
3.6. Processing text files: the StreamReader and StreamWriter reference classes ...... 256
3.7. The MemoryStream reference class ..................................................................... 258
4. The GDI+ .......................................................................................................................... 258
4.1. The usage of GDI+ ............................................................................................... 258
4.2. Drawing features of GDI ...................................................................................... 258
4.3. The Graphics class ................................................................................................ 259
4.4. Coordinate systems ............................................................................................... 261
4.5. Coordinate transformation .................................................................................... 268
4.6. Color handling of GDI+ (Color) ........................................................................... 274
4.7. Geometric data (Point, Size, Rectangle, GraphicsPath) ........................................ 275
4.7.1. Storing dimensions ................................................................................... 275
4.7.2. Storing planar points ................................................................................ 276
4.7.3. Storing planar rectangles .......................................................................... 277
4.7.4. Geometric shapes ..................................................................................... 278
4.8. Regions ................................................................................................................. 288
4.9. Image handling (Image, Bitmap, MetaFile, Icon) ................................................. 291
4.10. Brushes ............................................................................................................... 298
4.11. Pens .................................................................................................................... 301
4.12. Font, FontFamily ................................................................................................ 303
4.13. Drawing routines ................................................................................................ 307
4.14. Printing ............................................................................................................... 313
V. Developing open-source systems ............................................................................................... 316
1. The structure of Linux systems ......................................................................................... 316
1.1. History of Unix ..................................................................................................... 316
1.2. The Open Source software development model ................................................... 316
1.3. The Linux operating system ................................................................................. 316
1.4. Linux distributions ................................................................................................ 317
1.5. X Window System ................................................................................................ 317
1.6. Embedded Linux ................................................................................................... 318
2. The GCC compiler ............................................................................................................ 318
2.1. The origins of GCC .............................................................................................. 318
2.2. Steps of compilation with GCC ............................................................................ 318
2.3. Host and Target .................................................................................................... 323
2.4. The frequently used options of GCC .................................................................... 323
2.5. The make utility .................................................................................................... 324
2.6. The gdb debugger ................................................................................................. 325
3. Posix C, C++ system libraries ........................................................................................... 326
3.1. stdio.h ................................................................................................................... 327
3.2. math.h ................................................................................................................... 328
3.3. stdlib.h .................................................................................................................. 328
3.4. time.h .................................................................................................................... 329
3.5. stdarg.h ................................................................................................................. 329
3.6. string.h .................................................................................................................. 329
3.7. dirent.h .................................................................................................................. 330
vii
Created by XMLmind XSL-FO Converter.
Mechatronic Systems Programming
in C++
viii
Created by XMLmind XSL-FO Converter.
Az ábrák listája
I.1. Project selection ........................................................................................................................... 3
I.2. Project settings ............................................................................................................................. 3
I.3. Possible source files ..................................................................................................................... 4
I.4. Window of the running program .................................................................................................. 5
I.5. Steps of C++ program compilation .............................................................................................. 5
I.6. Classification of C++ data types .................................................................................................. 8
I.7. Functioning of a simple if statement .......................................................................................... 31
I.8. Logical representation of if-else structures ................................................................................ 32
I.9. Logical representation of multi-way branches ........................................................................... 33
I.10. Logical representation of while loops ...................................................................................... 38
I.11. Logical representation of for loops ........................................................................................... 40
I.12. Logical representation of do-while loops ................................................................................. 41
I.13. C++ program memory usage .................................................................................................... 51
I.14. Dynamic memory allocation .................................................................................................... 58
I.15. Graphical representation of an one-dimensional array ............................................................. 61
I.16. Graphical representation of a two-dimensional array ............................................................... 65
I.17. The relationship between pointers and arrays .......................................................................... 67
I.18. Two-dimensional arrays in memory ......................................................................................... 68
I.19. Dynamically allocated row vectors .......................................................................................... 70
I.20. Dynamically allocated pointer vector and row vectors ............................................................ 71
I.21. String constant in memory ....................................................................................................... 73
I.22. String array stored in a two-dimensional array ......................................................................... 77
I.23. Optimally stored string array .................................................................................................... 77
I.24. Structure in memory ................................................................................................................. 81
I.25. Processing data in the program CDCatalogue .......................................................................... 87
I.26. A singly linked list ................................................................................................................... 88
I.27. Union in memory ..................................................................................................................... 93
I.28. The layout of the structure date in memory .............................................................................. 95
II.1. Function definition .................................................................................................................... 98
II.2. Steps of calling a function ......................................................................................................... 99
II.3. Graph of the third degree polynomial ..................................................................................... 112
II.4. The interpretation of the parameter argv ................................................................................. 117
II.5. Providing command line arguments ........................................................................................ 118
II.6. Calculating the area of a triangle ............................................................................................ 119
II.7. Variable scopes ....................................................................................................................... 138
II.8. The compilation process in C++ ............................................................................................. 150
III.1. The object myCar (an instance of the class Truck) ................................................................ 163
III.2. Inheritance ............................................................................................................................. 164
III.3. Multiple inheritance ............................................................................................................... 164
III.4. The class Employee and its objects ........................................................................................ 173
III.5. The multiple inheritance of I/O classes in C++ ..................................................................... 188
III.6. Hierarchy of geometrical classes ........................................................................................... 188
III.7. Using virtual base classes ...................................................................................................... 194
III.8. Early binding example ........................................................................................................... 200
III.9. Late binding example ............................................................................................................ 201
III.10. Virtual method tables of the example code .......................................................................... 202
IV.1. The memory before cleaning ................................................................................................. 221
IV.2. The memory after cleaning .................................................................................................... 221
IV.3. The window in the View/Designer ........................................................................................ 230
IV.4. The program in the View/Code window ............................................................................... 230
IV.5. The Toolbox .......................................................................................................................... 231
IV.6. The Control menu .................................................................................................................. 232
IV.7. The Properties Window ......................................................................................................... 233
IV.8. The Event handlers ................................................................................................................ 234
IV.9. A defined Event Handler ....................................................................................................... 234
IV.10. The Intellisense window ...................................................................................................... 235
ix
Created by XMLmind XSL-FO Converter.
Mechatronic Systems Programming
in C++
x
Created by XMLmind XSL-FO Converter.
Mechatronic Systems Programming
in C++
xi
Created by XMLmind XSL-FO Converter.
I. fejezet - Basics and data
management of C++
Knowledge necessary for program development in language C++ is detailed divided into three large categories.
Category one (I. fejezet - 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 (II. fejezet - 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 (III. fejezet - 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.
• The basic elements of the program can only contain the characters of the 7 bit ASCII code table (see in
Appendix 1. szakasz - 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 2. szakasz - 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.
1
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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
2
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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).
After selections File / New / Project… Win32 / Win32 Console Application the name of the project has to be
entered:
After key OK is pressed, the Console application wizard starts, and using its settings an empty project can be
created:
3
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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 .
4
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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).
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. I.5. ábra - Steps of C++ program compilation
shows these steps.
5
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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 ).
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,
6
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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 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 III. fejezet - 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
7
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
Circle circle(radius);
cout << "Perimeter: " << circle.Perimeter() << endl;
cout << "Area: " << circle.Area() << endl;
// Waiting for pressing Enter
cin.get();
cin.get();
return 0;
}
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.
8
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
C++ data types (shortened as types) can be classified in several ways. Let's use the classification of Microsoft
VC++ language (I.6. ábra - 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.
long long int long long signed long long int signed long long
unsigned char
The required memory of arithmetical types with type modifiers and the value range of stored data are
summarized in Appendix 4. szakasz - 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 I.
fejezet - Basics and data management of C++ .
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〉 〈 , … 〉 ;
9
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
〈 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.
• 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
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);
10
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
It is important that definition and value assignment statements end with a semicolon.
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.
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 3. szakasz - 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.
or
11
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
The type of constant integer values can be provided by the U and L postfixes. U means unsigned, L means
long:
2012 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;
12
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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 10. szakasz - 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;
}
13
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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 ↯
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.
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).
In enumerations that do not contain direct value assignment, the number of elements can be obtained by adding
an extra element:
In the following example the usage of enum types and enum constants are demonstrated:
#include <iostream>
using namespace std;
14
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
int main()
{
enum card { clubs, diamonds, hearts, spades };
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.)
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
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:
This declaration creates type name uint16, therefore the definition of variable sign is:
uint16 sign;
15
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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.
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;
}
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;
16
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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.
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.
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,
Most operations have two operands – these are called two operand (binary) operators:
operand1 op operand2
17
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
In this group bitwise operations are also present besides the traditional arithmetic and relational operations:
n + 2 calculation of n + 2,
The C++ language has one three operand operation, this is the conditional operator:
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
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:
18
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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 denominator is put into brackets, therefore the term is divided by a product: a+b*c/(d*e),
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).
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.
19
Created by XMLmind XSL-FO Converter.
Basics and data management of 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;
}
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.
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.)
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; // ↯
20
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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 = 4) + 10)* 4;
b = (a+10)*4;
a = 10; a = b = 10;
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;
a += 2;
form expressions can also be written using the so called compound assignment operation:
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.
// 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:
21
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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--;
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; // ↯
a = b a == b a is equal to b
a ≠ b a != b a is not equal to b
22
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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
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:
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.
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:
23
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
During logical negation all relations are changed to their opposite relation, while operator AND to operator OR
(and vice versa).
!ok instead of ok == 0
ok instead of ok != 0
Right side expressions are recommended to be used mainly with bool type variable ok.
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++)
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
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:
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 ↯
24
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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.
Operator Operation
| 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.
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.
25
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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.
(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
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 (~, &, |).
26
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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;
decimal (hexadecimal)
binary
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;
27
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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
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.
28
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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.
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
The (static) type conversions below are all carried out during the compilation of the C++ program. A possible
grouping of type conversions:
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;
29
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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;
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
}
• when more statements forming together a logical unit should be treated as one (in these cases, blocks only
contain statements in general),
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;
30
Created by XMLmind XSL-FO Converter.
Basics and data management of 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;
}
}
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 I.7. ábra - 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;
31
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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) (I.8. ábra - 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
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;
}
32
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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;
}
33
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
Multi-way branches
A frequent case of nested if statements is to use further if statements in else branches (I.9. ábra - 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
34
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
statement4
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;
35
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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;
36
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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;
}
}
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.
37
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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;
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.
while (condition)
38
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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:
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.
39
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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;
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;
40
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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:
or
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;
}
}
41
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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>
42
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
#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.
{ { {
i+=2; } 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.
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++;
43
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
}
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()
44
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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;
}
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.
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;
45
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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-
try
statements
statements1
statements2
catch (...) {
46
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
statements3
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);
}
}
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.
47
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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 <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 III. fejezet - 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)));
48
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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()
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.
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;
49
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
}
}
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
}
50
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
Based on the storage class, compiler places global I.13. ábra - C++ program memory usage
(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 (I.13. ábra - 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.
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).
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:
If more pointers are created within a single statement, all identifier names should be preceded by an asterisk:
51
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
Many operations can be carried out for pointers; however, there are three operators that can exclusively be used
with pointers:
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”.)
*p = x +10;
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;
52
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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; // ↯ ↯ ↯
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):
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)
53
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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;
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;
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;
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.
54
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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.
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;
iptr p;
ipptr q;
It should be noted that when interpreting more complex pointer relations, graphical representations provide a
useful help.
55
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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! ↯
6.2. References
Reference types make it possible to reference already existing variables while defining an alternative name. The
general form of their definition:
& 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:
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.
56
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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).
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.
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
57
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
Reference cannot be created for bitfields (8. szakasz - User-defined data types), and we cannot even define an
array consisting of reference elements (7. szakasz - Arrays and strings).
We will experience the real significance of reference types when creating functions.
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).
• allocating a free memory block while verifying the success of the allocation,
pointer = newtype;
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 (7. szakasz - Arrays and strings).
pointer = newtype[number_of_elements];
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 (I.14. ábra - Dynamic memory allocation).
58
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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;
59
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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
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;
}
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.
60
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
Before detailing the two array types, let's see how to use arrays in general. The definition of n-dimensional
arrays:
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:
Presented in that way, arrays might seem frightening but in many cases, it is a useful, easy and comfortable
solution to store data.
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).
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 (I.15. ábra - 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]);
61
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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;
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
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:
62
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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));
}
63
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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.
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;
64
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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 7.
szakasz - 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
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 I.16. ábra - Graphical representation of a two-dimensional array, the two-dimensional matrix array is
represented with their row and column indexes (r/c).
65
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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;
}
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;
}
}
66
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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] (I.17. ábra - 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.
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 I.17. ábra - The relationship between pointers and arrays the references figuring in the
same row of the following table are identical:
67
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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 (I.18. ábra -
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.
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)
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.
68
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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.
type *pointer;
or
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;
69
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
return 0;
}
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.
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 (I.19. ábra - Dynamically allocated row vectors).
• The pointer vector of 3 elements that selects the rows: int* ps[3]= {0};
70
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
• 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 * (I.20. ábra - 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];
71
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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.
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);
72
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
It should be noted that STL data containers can be managed completely with predefined algorithms ( algorithm
header file).
int main() {
int number_of_rows, rowlength;
cout<<"Number of rows: "; cin >> number_of_rows;
cout<<"The length of rows: "; cin >> rowlength;
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.
When interpreting the statement above, the string is copied into memory, (I.21. ábra - 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.
In C++, the types string and wstring can also be used to process texts, so we give an overview of these types,
too.
73
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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' };
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";
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.
reading text from the keyboard cin >>, cin . get (), cin . wcin >>, wcin . get (), wcin .
getline () getline ()
74
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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;
}
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.
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:
75
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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;
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.
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.
76
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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 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 (I.22. ábra - 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 I.23. ábra - Optimally stored
string array:
77
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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
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.
concatenation + , += + , +=
comparison of strings .compare (), == , != , < , <= .compare (), == , != , < , <=
, > , >= , > , >=
conversion into C-style character .c_str (), .data () .c_str (), .data ()
sequence
Let's rewrite our encrypting program by using C++ style string processing.
#include <string>
#include <iostream>
using namespace std;
78
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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;
p=(wchar_t *)s.c_str();
while (*p) // decryption
*p++ ^= key;
wcout<<L"The original text: "<<s<<endl;
}
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.
79
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
struct structure_type {
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:
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;
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.
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];
80
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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
};
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 I.24. ábra - 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.
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:
81
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
#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()
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;
82
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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 III. fejezet - 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;
83
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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;
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.
84
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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.
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;
};
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;
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.
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:
The structure stored in an array element can be accessed by using the dot operator:
85
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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');
86
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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
The second command writes the results to a separate file (I.25. ábra - 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 .)
87
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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
88
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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()),
• 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
89
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
}
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
90
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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!
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:
}; int second;
};
91
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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};
}
• 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 I.27. ábra - 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];
92
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
};
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
}
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
93
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
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
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
94
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
int main() {
date today = { 0, 2, 10, 2012 };
date holiday = {1};
holiday.year = 2012;
holiday.month = 12;
holiday .day = 25;
}
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;
};
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:
95
Created by XMLmind XSL-FO Converter.
Basics and data management of C++
• 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).
96
Created by XMLmind XSL-FO Converter.
II. fejezet - Modular programming in
C++
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.
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.
sqrt() cmath
isalpha() cctype
97
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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.
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 II.2. ábra - Steps of calling a function.
98
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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).
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:
• checks the compatibility of the number and the types of parameters by comparing these parameters with the
argument list,
99
Created by XMLmind XSL-FO Converter.
Modular programming in C++
• 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:
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 (5. szakasz - 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:
〈 statements〉
return 〈 expression〉 ;
The prototype corresponding to the definition also has to contain the keyword throw:
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
100
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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;
}
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;
}
}
101
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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;
int main() {
double *pd = Allocate(2012);
pd[2] = 8;
delete []pd;
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.
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;
int main() {
Greeting("Good morning");
Greeting("Good evening!");
}
102
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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.
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
}
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;
}
103
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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.
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
}
104
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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;
}
105
Created by XMLmind XSL-FO Converter.
Modular programming in C++
int main() {
cout << Gcd(24, 32) <<endl; // 8
}
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);
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.
106
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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;
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.
107
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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};
108
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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:
109
Created by XMLmind XSL-FO Converter.
Modular programming in C++
10 2 12
23 7 29
0 0 0 1
0 0 1 0
0 1 0 0
1 0 0 0
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;
}
110
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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;
}
The functions have to be called by arguments having the same type as the parameters:
111
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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
}
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.
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
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.
If the type mathfunction is used, the prototype of the function poly3() is:
mathfunction poly3;
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)
112
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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:
or
double y = functionref(12.3);
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;
113
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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"};
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 results:
114
Created by XMLmind XSL-FO Converter.
Modular programming in C++
0.5000 0.2500000
1.0000 1.0000000
1.5000 2.2500000
2.0000 4.0000000
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);
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.
115
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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_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;
int main() {
double avg = Average(7,1.2,2.3,3.4,4.5,5.6,6.7,7.8);
cout << avg << endl;
}
116
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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( )
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 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;
117
Created by XMLmind XSL-FO Converter.
Modular programming in C++
or in the Visual Studio development environment (II.5. ábra - Providing command line arguments).
C:\C++Book> 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;
118
Created by XMLmind XSL-FO Converter.
Modular programming in C++
Wrong number of parameters: command2 Correct number of parameters: command2 alfa beta
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.
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;
119
Created by XMLmind XSL-FO Converter.
Modular programming in C++
This program is very simple but it has some drawbacks because it cannot be reused and its structure is not easy
to understand.
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));
}
120
Created by XMLmind XSL-FO Converter.
Modular programming in 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.
int main() {
double x, y, z;
ReadData(x , y , z);
cout << "perimeter: " << Perimeter(x, y, z)<<endl;
cout << "area: " << Area(x, y, z) <<endl;
}
Now let's see the main () function if two triangles are to be dealt with.
121
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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;
};
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;
}
122
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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;
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
123
Created by XMLmind XSL-FO Converter.
Modular programming in C++
factorial:
Fibonacci numbers:
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
124
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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
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.
125
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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);
}
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;
int main() {
Matrix m2 = {{1, 2},
{2, 3}};
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);
126
Created by XMLmind XSL-FO Converter.
Modular programming in C++
PrintOutMatrix(m4,4);
cout << "Determinant(m4) = " << Determinant(m4,4) << endl;
cin.get();
return 0;
}
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
127
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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.
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;
}
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;
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
}
• 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.
128
Created by XMLmind XSL-FO Converter.
Modular programming in C++
• 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.
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;
int main() {
cout << Absolute(-7) << endl; // int
129
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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;
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);
130
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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.
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
С#).
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;
}
131
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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;
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)
}
132
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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.
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;
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
133
Created by XMLmind XSL-FO Converter.
Modular programming in C++
delete px;
delete py;
}
From among the different specialised versions, compilers first choose always the most specialised (concrete)
template to compile the function call.
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);
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;
134
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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;
string Character(char c) {
stringstream ss;
if (isprint((unsigned char)c))
ss << "'" << c << "'";
else
ss << (int)c;
return ss.str();
}
int main() {
Function(1, 'c');
Function(1.5);
Function<int>();
135
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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)
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.
• 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;
136
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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;
}
}
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.
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:
137
Created by XMLmind XSL-FO Converter.
Modular programming in C++
File and program level scopes can be divided furthermore by namespaces presented later which introduce the
notion of qualified identifiers.
138
Created by XMLmind XSL-FO Converter.
Modular programming in C++
139
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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
140
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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!).
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.
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;
141
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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;
}
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;
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).
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
142
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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:
double sum = 0;
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.
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.
143
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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
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
144
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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.
#include <cmath>
using namespace std;
#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 4. szakasz - Preprocessor directives of C++ provides readers with more information on the usage of
preprocessors.)
145
Created by XMLmind XSL-FO Converter.
Modular programming in C++
// MainModule.cpp // MatModule.cpp
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
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:
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.
146
Created by XMLmind XSL-FO Converter.
Modular programming in C++
#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();
}
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;
}
}
147
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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::).
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.
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();
}
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();
}
148
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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);
}
}
}
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;
149
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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.
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;
}
150
Created by XMLmind XSL-FO Converter.
Modular programming 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).
151
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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.
#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:
Comments // comment
152
Created by XMLmind XSL-FO Converter.
Modular programming in C++
• variable definitions,
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_
#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)
153
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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;
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
}
}
154
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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
}
The following example integrates the declaration file on the basis of the manufacturer:
#define IBM 1
#define HP 2
#define DEVICE IBM
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
155
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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 .
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
}
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
}
156
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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) )
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;
157
Created by XMLmind XSL-FO Converter.
Modular programming in C++
int main() {
int x = -5;
x = Abs(x);
cout << Sqr(Abs(x));
int a = Random(0,100);
cout << Max(a, Random(12,23));
}
The following example code traces the functioning of #define and #undef statements:
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;
158
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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;
};
int main(){
person people[] = {
PERSON (Ivan),
PERSON (Alice)
};
}
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
159
Created by XMLmind XSL-FO Converter.
Modular programming in C++
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.
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
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
160
Created by XMLmind XSL-FO Converter.
Modular programming in C++
#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
The empty directive (#) can also be used, but it does not affect preprocessing:
#
161
Created by XMLmind XSL-FO Converter.
III. fejezet - Object-oriented
programming in C++
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.
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.
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,
162
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
activating (calling) the method Brake() will only slow down one given vehicle. In C++, methods are rather
called member functions.
III.1. ábra - 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++.
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.
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.
163
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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.
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.
164
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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.
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.
class Truck {
protected:
string manufacturer;
string engine;
165
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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() { }
};
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.
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:
166
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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.
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;
Of course, structured and efficient program codes can be created in this way too; however, in this chapter, we
intend to go further.
167
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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;
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 3. szakasz - 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;
}
168
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
int main() {
Employee engineer;
engineer.SetData(1234, "Tony Clever", 2e5);
engineer.IncreaseSalary(12);
cout << engineer.GetSalary() << endl;
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;
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.
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);
}
169
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
void SetName(string n) {
name = n;
}
float GetSalary() const {
return salary;
}
};
int main() {
Employee employee;
employee.SetName("Stephen Smith");
Employee *pAccountant;
pAccountant = new Employee(1235, "Sarah Rich", 3e5);
pAccountant->IncreaseSalary(10);
cout << pAccountant->GetSalary() << endl;
delete pAccountant;
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;
}
…
170
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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.
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.
2.1.5. Destructor
171
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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
172
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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.
each object has its own data members; however, these objects share only one instance of member functions
(III.4. ábra - 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
173
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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.
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.
174
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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;
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.
175
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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();
}
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"; }
};
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"; }
};
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.
176
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
#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;
}
void Point::SetX(int a) {
x = a;
}
void Point::SetY(int a) {
y = a;
}
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);
177
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
#endif
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;
// ...
};
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; }
178
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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"; }
};
int main() {
Point p, q;
p.Move(1,2);
q.Move(4,6);
cout<<Distance(p,q)<<endl;
}
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.
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.
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;
179
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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();
}
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();
}
In order to define pointers correctly, we have to use the name of the class and the scope operator:
180
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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_int intptr = &Class::a;
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.
181
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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:
* + - ~ ! / % new[]
!= ^ | && || = *= 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.
++ --
%= ^= &= |= <<=
182
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
>>= []
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:
Unary operands:
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);
183
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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];
}
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
184
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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;)
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…
}
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);}
185
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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:
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);}
186
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
return k;
}
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;
}
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++.
the operation:
187
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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.
C++ supports multiple inheritance, during which a new class is derived from more base classes (that is it has
more direct ancestors) (III.5. ábra - 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) (III.6. ábra - Hierarchy of geometrical classes).
188
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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):
189
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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; }
}
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);
190
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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
protected protected
protected protected
protected 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;
};
191
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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).
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;}
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 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.
Class_name::member_name
192
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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 3.1. szakasz - 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 protected: x, y, z
193
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
194
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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().
• 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:
195
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
• 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
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;
}
196
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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"
int main() {
Circle c1(100, 200, 10);
c1.PrintOut();
cout<<c1.GetX()<<endl;
cout<<c1.GetR()<<endl;
}
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).
197
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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.
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.
198
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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;}
};
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();
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;
}
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.
199
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
class Base {
protected:
int value;
public:
Base(int a=0) : value(a) { }
const char* GetName() const { return "Base"; }
int GetValue() const { return value; }
};
int main() {
Base a;
Derived b(12, 23);
a = b;
Base &rA = b;
Base *pA = &b;
200
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
The calls of the member function GetValue() is presented in III.8. ábra - Early binding example. The results:
a Base 12
b Derived 23
rA base 12
pA Base 12
201
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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.
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]).
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;
202
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
public:
Base(int a=0) : value(a) { }
virtual const char* GetName() const { return "Base"; }
virtual int GetValue() const { return value; }
virtual ~Base() {}
};
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; }
};
int main() {
Point fixPoint(12, 23);
fixPoint.PrintOut(); // (12, 23)
MovingPoint movingPoint;
movingPoint.PrintOut(); // (0, 0)
movingPoint.Move(fixPoint);
movingPoint.PrintOut(); // (12, 23)
203
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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;}
};
int main() {
Descendant * pDescendant = new Descendant;
Ancestor * pAncestor = 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 {
204
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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;
}
};
int main() {
const int db=3;
Animal* p[db] = {new Bird, new Fish, new Mammal};
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};
205
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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.
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];
};
206
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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;
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:
207
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
By defining a type
typedef Array<int, 32> IntArray;
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;
208
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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.
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 {
…
209
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
};
or
template <typename type1, … typename typeN>
class Classname {
…
};
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; }
210
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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.
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
};
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;}
// …
};
211
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
int main() {
DataStream<char, int> s4 ; // Complete specialisation
DataStream<int, double> s1 ;
DataStream<double*, int*> s2; // Specialisation
DataStream<double, int> s3 ; // Partial specialisation
}
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;
In that case, in order to create the type IntArray, the generic class can be specialised without arguments:
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;
212
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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
sStack.Push("language");
sStack.Push("C++");
do {
cout << sStack.Pop()<<endl;;
} while (!sStack.isEmpty());
}
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;
};
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;
213
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
// Forward declarations
template <typename T> class Class;
template <typename T> void Funct(Class<T>&);
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;
int main() {
Class <double> dObj1, dObj2;
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.
214
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
• containers – data structures making it possible to store data in memory (vector, list, map, set, deque, …)
• 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:
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;
215
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
void IntPrintOut(int a) {
cout << "\t" << a << endl;
}
int main() {
const int db = 7;
int data[db]={2, 7, 10, 12, 23, 29, 80};
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:
216
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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
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.
• 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;
217
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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};
The results:
218
Created by XMLmind XSL-FO Converter.
Object-oriented programming in
C++
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:
const value_type& top() const accessing the top element of the stack,
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));
}
}
219
Created by XMLmind XSL-FO Converter.
IV. fejezet - Programming Microsoft
Windows in C++
In this chapter we will present how to use the C++ for developing Windows-specific applications.
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.
• 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.
• 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.
220
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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 (IV.1. ábra - The memory before cleaning) and after cleaning
(GC::Collect()) (IV.2. ábra - The memory after cleaning)
221
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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).
222
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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).
223
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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).
• 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 1.10.
szakasz - 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.
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.
224
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
• 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).
225
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
• 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:
Referring to an Pointer (*) Pointer (*) _nogc Pointer: to Pointer (*): to native
object native data, data,
• 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:
226
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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
227
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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);
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){"","","",""};
228
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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;
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:
229
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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:
After selecting the “View/Code” menuitem our window will look like this:
230
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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:
231
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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:
232
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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.
233
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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.
234
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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.
235
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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:
• "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).
236
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
• "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.
• 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;
237
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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 (4.1. szakasz - 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
238
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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.
239
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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 2.22.
szakasz - Control used for timing: Timer).
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.
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.
240
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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);
}
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;
241
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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();
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.
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;
242
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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);
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.
243
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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();
}
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.
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;
244
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
With SizeMode=StretchImage setting the scales do not match either: the map is rectangular, the PictureBox is a
square:
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:
245
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
With SizeMode=CenterImage setting: there is no resize, the centre of Bitmap was set into the PictureBox
246
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
With SizeMode=Zoom Bitmap is resized by keeping its scales so that it fits into the PictureBox:
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
247
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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:
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.
248
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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:
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.
}
249
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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 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:
250
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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.
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.
251
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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:
• 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 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:
252
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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.
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();
}
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.
Another thing to do is to decide what the file to be handled contains and what we would like to do with it:
• We would like to handle it by bytes (as a block containing bytes) if we are brave enough: virus detection,
character encoding etc.
• If it is a text file with variable-length lines (Strings), and the lines end with line feed.
• The file name is given in the code, therefore it is hard coded. If the file is only for the usage of programmer
and it is always the same file, than this is a simple and rapid way of providing the name of the file.
• Users can select the file by using OpenFileDialog or SaveFileDialog (see section 2.20. szakasz - Dialog
windows helping file usage: OpenFileDialog, SaveFileDialog and FolderBrowserDialog). In this case, the
name of the file is stored in the FileName property of these dialog boxes.
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:
254
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
• 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.
• 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.
255
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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
256
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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
257
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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+.
• 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.] .
258
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
The System::Drawing namespace contains the classes required for drawing (IV.27. ábra - 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).
259
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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
(IV.28. ábra - 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);
}
IV.28. ábra - 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);
IV.29. ábra - If we do not draw in Paint then the blue line disappears when maximizing
after minimizing.
260
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
We can clear the given drawing paper with the defined background color by the
Clear(Color & Color);
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 (IV.30. ábra - General axonometry). According to IV.30.
ábra - 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.
261
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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)
(IV.30. ábra - 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 (IV.31. ábra - Isometric axonometry).
262
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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.
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 (IV.33. ábra - 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.
263
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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));
}
264
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
It is clearly shown in IV.34. ábra - 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 (IV.35. ábra - Central projection). Let‟s find the
mapping of the central projection corresponding to (IV.4.1).
265
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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)
(IV.4.5)
that is
266
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
(IV.4.6)
(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)
(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 (IV.36. ábra - 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;
267
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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.
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.
268
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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
)
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 IV.33. ábra - 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 (IV.37. ábra - 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 (IV.37. ábra - 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).
269
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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 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);
270
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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])
271
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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);
}
}
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.
(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]);
272
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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 (IV.39. ábra - 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);
}
273
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
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.
The well-known application used to set the background color of the window shows well the usage/applicability
of RGB colors. The background color of the window can be only an RGB color. Let us put three vertical sliders
(scrollbars) to the form. The names (Name) should be red, green and blue respectively.
Let‟s set their upper limit to 255 (not caring about the fact that the slider cannot go up to 255). The Scroll
event of each should be the evet handler of the red, as it is apparent in the code generated by the Designer.
//
// red
//
this->red->Location = System::Drawing::Point(68, 30);
this->red->Maximum = 255;
this->red->Name = L"red";
this->red->Size = System::Drawing::Size(48, 247);
this->red->TabIndex = 0;
this->red->Scroll += gcnew
System::Windows::Forms::ScrollEventHandler(
this, &Form1::red_Scroll);
//
// green
//
this->green->Location = System::Drawing::Point(187, 30);
this->green ->Maximum = 255;
this->green ->Name = L"green";
this->green ->Size = System::Drawing::Size(48, 247);
this->green ld->TabIndex = 1;
this->green ->Scroll += gcnew
System::Windows::Forms::ScrollEventHandler(
this, &Form1::red_Scroll);
//
// blue
//
this->blue->Location = System::Drawing::Point(303, 30);
this->blue->Maximum = 255;
this->blue->Name = L"blue";
this->blue->Size = System::Drawing::Size(48, 247);
this->blue->TabIndex = 2;
274
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
this->blue->Scroll += gcnew
System::Windows::Forms::ScrollEventHandler(
this, &Form1::red_Scroll);
The event handler of the red slider‟s Scroll event uses the position of all the three sliders to set the background
color (Value property):
private: System::Void red_Scroll(System::Object^ sender,
System::Windows::Forms::ScrollEventArgs^ e) {
this->BackColor = Color::FromArgb(
red->Value, green->Value, blue->Value);
}
We will get familiar with the usage of transparent colors with the pen and brush drawing tools. The read only
property of the Color structure, that is, the R, G and B (Byte type) properties contain the color components.
Byte r = this->BackColor.R;
constructors. The Height and Width properties of the Size and the SizeF structures (int and float) contain the
height and width values of rectangles. The logical type IsEmpty property informs about if both size properties
are set to 0 (true) or not (false).
With the size data we can perform operations with the methods of Size(F) structures. The Add() method adds
the Width and Height values of Size type parameters, whereas Subtract() subtracts them.
static Size Add(Size sz1, Size sz2);
static Size Subtract(Size sz1, Size sz2);
275
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
According to the above methods there are defined the overloaded +, - and == operators of the Size(F)
structures.
SizeF type values are rounded up to Size type with the Ceiling(), whereas the Round() method rounds in the
usual way.
static Size Ceiling(SizeF value);
static Size Round(SizeF value);
The ToString() method converts the Size(F) structure to the form of a {Width=xxx, Height=yyy} string.
It is important to note that since the forms have a Size property, the Size type structure can be used only by
defining the namespace. The following example writes the size values of the generated Size to the header of the
form:
System::Drawing::Size ^ x= gcnew System::Drawing::Size(100,100);
this->Text=x->ToString();
In the first case the coordinate x is defined by the lower 16 bits of the parameter and the coordinate y is defined
by the upper. In the second case x is defined by the Width property of the Size type parameter and y is defined
by the Height property. The basic properties of the point structures are the X, Y coordinate values and the
IsEmpty analysis.
We can perform point operations with the methods of the Point(F) structures. The Add() method adds the
Width and Height values of the Size structure that was received also as a parameter, whereas Subtract()
subtracts them.
static Point Add(Point pt, Size sz);
static Size Subtract(Point pt, Size sz);
We can investigate the equality of two Point type structures with the Equals() method:
virtual bool Equals(Object^ obj) override;
According to the above methods there are defined the overloaded +, - and == operators of the Point(F)
structures.
A PointF type value rounds up to Point type the Ceiling() method, whereas the Round() method rounds in
the usual way.
static Size Ceiling(SizeF value);
static Size Round(SizeF value);
276
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
The ToString() method converts the Point(F) structures to the form of a {X=xxx, Y=yyy} string.
constructors. The X, Y, the Left, Top and the Location properties of the Rectangle(F) structure contain the
coordinates of the upper-left corner. The Height, Width and Size are the latitude and elevation dimensions of
the rectangles, the Right and Bottom properties are the coordinates of the lower-right corner. The IsEmpty
informs about whether our rectangle is real or not. The Empty is a rectangle with undefined data:
Rectagle Empty;
Empty.X=12;
We can create a rectangle (Rectangle, RectangleF) with the specified upper-left and lower-right corner
coordinates:
static Rectangle FromLTRB(int left,int top,
int right,int bottom);
static RectangleF FromLTRB(float left, float top,
float right, float bottom)
The Rectangle structure offers a lot of methods to be used. From the RectanleF structure creates a Rectangle
structure by rounding up the Ceiling() method, by normal rounding the Round() method and by cutting the
decimal part the Truncate() method.
We can decide if the rectangle contains a point or a rectangle. With the methods of the Rectangle structure:
bool Contains(Point p);
bool Contains(Rectangle r);
bool Contains(int x, int y);
We can increase the area of the current Rectangle(F) by specifying the width and height data:
void Inflate(Size size);
void Inflate(SizeF size);
void Inflate(int width, int height);
void Inflate(single width, single height);
The following methods produce new, increased Rectangle(F) instances from existing rectangles:
static Rectangle Inflate(Rectangle rect, int x, int y);
static RectangleF Inflate(RectangleF rect, single x, single y);
277
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
methods.
The
void Intersect(Rectangle rect);
void Intersect(RectangleF rect);
methods replace the current rectangle with the intersection of itself and the specified rectangle parameter. The
static methods below produce new instance from the intersection of the Rectangle(F) type parameters:
static Rectangle Intersect(Rectangle a, Rectangle b);
static RectangleF Intersect(RectangleF a, RectangleF b);
We can also investigate if the currecnt rectangle intersects with an another with the
bool IntersectsWith(Rectangle rect);
bool IntersectsWith(RectangleF rect);
methods.
The methods
static Rectangle Union(Rectangle a, Rectangle b);
static RectangleF Union(RectangleF a, RectangleF b);
produce a new rectangle that bounds the union of two Rectangle(F) type parameters.
methods.
For the Rectangle(F) type elements can be used the == and != operators as well.
constructor creates an empty shape. We can cater for the future painting with the contructor:
278
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
GraphicsPath(FillMode fillMode)
The figure defined by Point(F) type array (pts) can be created by the contructors:
GraphicsPath(array<Point>^ pts, array<unsigned char>^ types
[,FillMode fillmode]);
GraphicsPath(array<PointF>^ pts, array<unsigned char>^ types
[,FillMode fillmode]);
The types array is an array of PathPointType type elements, that defines a curvetype to every pts point . Its
possible values are for example: Start, Line, Bezier, Bezier3, DashMode.
The properties of the GraphicsPath class are the PathPoints and PathType arrays, the PointCounts number
of elements and the FillMode filling type. The PathData class is the encapsulation of PathPoints and
PathType arrays, and its properties are the Points and Types.
We can append line sections and arrays of line sections to the figure with the methods:
void AddLine(Point pt1, Point pt2);
void AddLine(PointF pt1, PointF pt2);
void AddLine(int x1, int y1, int x2, int y2);
void AddLine(float x1, float y1, float x2, float y2);
void AddLines(array<Point>^ points);
void AddLines(array<PointF>^ points);
Polygons defined by the Point(F) type arrays can join the figure with the methods:
void AddPolygon(array<Point>^ points);
void AddPolygon(array<PointF>^ points);
We can add rectangles and array of rectangles to the figure with the methods:
void AddRectangle(Rectangle rect);
void AddRectangle(RectangleF rect);
void AddRectangles(array<Rectangle>^ rects);
void AddRectangles(array<RectangleF>^ rects);
We can add ellipses to the figure with the data of their bounding rectangles:
void AddEllipse(Rectangle rect);
void AddEllipse( RectangleF rect);
279
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
Elliptical arcs and the arrays of elliptical arcs can be added to the figures with the methods:
void AddArc(Rectangle rect, float startAngle, float sweepAngle);
void AddArc(RectangleF rect,
float startAngle, float sweepAngle);
void AddArc(int x, int y, int width, int height,
float startAngle, float sweepAngle);
void AddArc(float x, float y, float width, float height,
float startAngle, float sweepAngle);
An elliptical arc is always defined by the bounding rectangle, the start angle and the sweep angle.
Pie is a shape defined by an arc of an ellipse and the two radial lines that intersect with the endpoints of the arc.
These can be also added to the current figure with the methods:
void AddPie(Rectangle rect, float startAngle, float sweepAngle);
void AddPie(int x, int y, int width, int height,
float startAngle, float sweepAngle);
void AddPie(float x, float y, float width, float height,
float startAngle, float sweepAngle);
We can append Bezier curves to the figures.A Bezier curve of degree 3 is described by four points in the plane.
The starting point is the first one, the ending point is the fourth one. The starting tangent is defined by the first
and second point, the closing tangent is defined by the third and the fourth point in a way that the vector
between the points is exactly three times of its derivative (IV.46. ábra - Cubic Bezier curve). The parametric
equation [4.4.] contains the parametric description of the curve (IV.4.12).
280
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
(IV.4.12
)
The following methods can be used for adding cubic Bezier curves:
void AddBezier(Point pt1, Point pt2, Point pt3, Point pt4);
void AddBezier(PointF pt1, PointF pt2, PointF pt3, PointF pt4);
void AddBezier(int x1, int y1, int x2, int y2,
int x3, int y3, int x4, int y4);
void AddBezier(float x1, float y1, float x2, float y2,
float x3, float y3, float x4, float y4);
The following methods add a sequence of connected cubic Bezier curves to the current figure:
void AddBeziers(array<Point>^ points);
void AddBeziers(array<PointF>^ points);
The Points arrays contain the endpoints and the control points in a way that the first curve is defined by the first
four points (IV.46. ábra - Cubic Bezier curve, equations IV.4.12), whereas each additional curve is defined by
three additional points. The endpoint of the curve prior to the current curve is the startpoint. The two control
points and one endpoint are the three points belonging to the current curve. If the previous curve was another
one then its endpoint might also be the first point (order 0 continuous join). The DrawPath() method of the
Graphics class draw the figure (IV.47. ábra - Cubic Bezier curve joined continously).
281
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
We can lay an interpolation curve to the series of points in a way that the curve passes through the points and in
each point the tangent of the curve is proportional to the vector defined by the two neighbouring points, in this
case the curve is called cardinal spline. Looking at the parametric description of the curve according to time we
can say that at the time t k the curve passes through the point of P k (IV.48. ábra - The cardinal spline). If the
tangent of the curve is V k , at the time t k then the tangent is defined by the equation (IV.4.13).
(IV.4.13
)
where f(<1) is the tension of the curve. If f=0, then it is just the Catmull-Rom spline [4.4.] .
The (IV.4.14) equations contain the parametric description of the cardinal spline:
(IV.4.14
)
Az usual, at the edges the tangent is calculated by the one-sided difference and this way the curve passes
through every point. The advantage of using cardinal splines is that there is no need to solve the system of
282
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
equations, the curve passes through all the points. Its disadvantage however is that they can be derived
continously only once.
The
void AddCurve(array<Point>^ points,
[[int offset, int numberOfSegments],
float tension]);
void AddCurve(array<PointF>^ points,
[[int offset, int numberOfSegments],
loat tension]);
methods add a cardinal spline to the figure. The points array contains the support points, the optional tension
parameter contains the tension; and the optional offset parameter defines from which point we would like to
consider the points in the points array. We can also set with the numberOfSegments parameter the number of the
curve sections we want (that is, how long shall we consider the elements of the points array). By omitting the
tension parameter (or setting it to 0), we can define a Catmull-Rom curve.
The following example lays a (Catmull-Rom) cardinal spline to the points of the previous example (IV.49. ábra
- Catmull-Rom spline).
private: System::Void Form1_Paint(System::Object^ sender,
System::Windows::Forms::PaintEventArgs^ e) {
array<Point>^ pontok = {Point(20,100), Point(40,75),
Point(60,125), Point(80,100),
Point(100,150), Point(120,250),
Point(140,200)};
GraphicsPath^ cPath = gcnew GraphicsPath;
cPath->AddCurve( pontok);
Pen^ cToll = gcnew Pen( Color::Blue );
e->Graphics->DrawPath( cToll, cPath );
}
283
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
The s parameter is the reference to the string to be printed out. We will talk about the features of letters and
fonts later, for now we just share information that are necessary for our current example. The family reference
contains the font family of the print out. We can create a similar one using the name of an already existing font
family (see the next example). The style parameter is an element of the FontStyle enumeration
(FontStyle::Italic, FontStyle::Bold …), the emSize is the vertical size of the rectangle enclosing the
letter. We can define the location of the print out either with the Point(F) type origin parameter or with the
Rectangle(F) layoutRect parameter. The format parameter is a reference to the instance of the StringFormat
class, we can use the StringFormat::GenericDefault property.
The next example fits the GDI+ and Drawing words into a figure and draws it:
private: System::Void Form1_Paint(System::Object^ sender,
System::Windows::Forms::PaintEventArgs^ e) {
GraphicsPath^ sPath = gcnew GraphicsPath;
FontFamily^ family = gcnew FontFamily( "Arial" );
sPath->AddString("GDI+", family, (int)FontStyle::Italic,
20, Point(100,100),
StringFormat::GenericDefault);
sPath->AddString("drawing", family, (int)FontStyle::Italic,
20, Point(160,100),
StringFormat::GenericDefault);
Pen^ cToll = gcnew Pen( Color::Blue );
e->Graphics->DrawPath( cToll, sPath );
}
We can add a figure (addingPath) to the current figure instance with the method:
void AddPath(GraphicsPath^ addingPath, bool connect);
The connect parameter caters for making the two elements connected. The following exaple connects two
wedges.
private: System::Void Form1_Paint(System::Object^ sender,
System::Windows::Forms::PaintEventArgs^ e) {
array<Point>^ tomb1 = {Point(100,100), Point(200,200),
Point(300,100)};
GraphicsPath^ Path1 = gcnew GraphicsPath;
Path1->AddLines( tomb1 );
array<Point>^ Tomb2 = {Point(400,100), Point(500,200),
Point(600,100)};
284
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
We can widen the figure creating an outline around the original lines (the same applies to the lines of the string
characters) possibly at a given distance and after a given geometric transformation. This can be achieved with
the method:
void Widen(Pen^ pen[, Matrix^ matrix[, float flatness]]);
The width of the pen parameter gives the distance between the existing lines and the new outline, and the
optional matrix parameter defines a transformation before widening. The flatness parameter defines how
accurately the outline follows the original curve (this can transform a circle into a polygon). The next example
widens two circles while translating (IV.52. ábra - Widened figure):
private: System::Void Form1_Paint(System::Object^ sender,
System::Windows::Forms::PaintEventArgs^ e) {
GraphicsPath^ Path = gcnew GraphicsPath;
Path->AddEllipse( 0, 0, 100, 100 );
Path->AddEllipse( 100, 0, 100, 100 );
e->Graphics->DrawPath( gcnew Pen(Color::Black), Path );
Pen^ widenPen = gcnew Pen( Color::Black,10.0f );
Matrix^ widenMatrix = gcnew Matrix;
widenMatrix->Translate( 50, 50 );
Path->Widen( widenPen, widenMatrix, 10.0f );
e->Graphics->DrawPath( gcnew Pen( Color::Red ), Path );
}
285
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
If we do not want to get a parallel outline but want to replace the figure with a sequence of connected line
segments, then we can use the method:
void Flatten([Matrix^ matrix[, float flatness]]);
The interpretation of the parameters is the same as mentioned already when explaining the Widen() method.
In this case the srcRect rectangle is transformed to a quadrangle defined by the destPoints point array as if the
plane were made from rubber. Moreover we can apply any geometric transformation using the optional matrix
parameter. The warpMode optional parameter can be WarpMode::Perspective (this is the default one) and
WarpMode::Bilinear as shown in the equation IV.4.15. We can also define the accuracy of the splitting into
segments with the flatness optional parameter.
(IV.4.15
)
In the next example a text is distorted as defined by the related rectangle (black) and quadrangle (red) in a
perspective method and translated.
private: System::Void Form1_Paint(System::Object^ sender,
System::Windows::Forms::PaintEventArgs^ e) {
GraphicsPath^ myPath = gcnew GraphicsPath;
RectangleF srcRect = RectangleF(10,10,100,200);
myPath->AddRectangle( srcRect );
FontFamily^ family = gcnew FontFamily( "Arial" );
myPath->AddString("Distortion", family, (int)FontStyle::Italic,
30,Point(100,100),StringFormat::GenericDefault);
e->Graphics->DrawPath( Pens::Black, myPath );
PointF point1 = PointF(200,200);
PointF point2 = PointF(400,250);
PointF point3 = PointF(220,400);
PointF point4 = PointF(500,350);
array<PointF>^ destPoints = {point1,point2,point3,point4};
Matrix^ translateMatrix = gcnew Matrix;
translateMatrix->Translate( 20, 0 );
myPath->Warp(destPoints, srcRect, translateMatrix,
WarpMode::Perspective, 0.5f );
e->Graphics->DrawPath( gcnew Pen( Color::Red ), myPath );
}
286
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
With the help of the GraphicsPathIterator class we can go through the points of the figure, we can set
markers with the a SetMarker() method and we can slice the figure with the NextMarker() method. The
ClearMarker() method removes the markers.
Within one figure we can define more subfigures that can be opened with the method:
void StartFigure();
We can make the figure closed by connecting the last and the first point by a line using the method:
void CloseFigure();
The GetBounds() method gives back the bounding rectangle of the figure:
RectangleF GetBounds([Matrix^ matrix[, Pen^ pen]]);
The optional matrix parameter specifies a transformation to be applied to this path before the bounding rectangle
is calculated. The width of the optional pen parameter will raise the size of the bounding rectangle on each side.
The
bool IsVisible(Point point[, Graphics ^ graph]);
bool IsVisible(PointF point[, Graphics ^ graph]);
bool IsVisible(int x, int y[, Graphics ^ graph]);
bool IsVisible(float x, float y[, Graphics ^ graph]);
methods will return true values, if the point of x and y coordinates or defined by the point parameter is contained
within this figure. The optional graph parameter defines a drawing paper with a current clipping area, where we
test the visibility (the concept of ClipRegion will be described in the next chapter).
The
bool IsOutlineVisible(Point point, Pen^ pen);
bool IsOutlineVisible(PointF point, Pen^ pen);
bool IsOutlineVisible(int x, int y, Pen^ pen);
bool IsOutlineVisible(float x, float y, Pen^ pen);
bool IsOutlineVisible(Point point, Pen^ pen
[, Graphics ^ graph]);
287
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
methods will return true values if the point of x and y coordinates or defined by the point parameter is contained
within (under) the outline of this figure. The optional graph parameter defines a drawing paper with a current
clipping area, where we test the visibility
The
void Reverse();
method reverses the order of the points of the figure (in the PathPoints array property).
The
void Reset();
4.8. Regions
The region class (System::Drawing::Region) models the interior area of a graphics shape composed of
rectangles and closed paths (contour), this way the area of the region might be of a complex shape.
If we do not define any parameter, then an empty region will be created. The path and the rect parameters define
the initial shape of the region.
We can set the clipping region with the methods of the Graphics class:
void SetClip(Graphics^ g[, CombineMode combineMode]);
void SetClip(GraphicsPath^ path[, CombineMode combineMode]);
void SetClip(Rectangle rect[, CombineMode combineMode]);
void SetClip(RectangleF rect[, CombineMode combineMode]);
void SetClip(Region^ region, CombineMode combineMode);
This way our shape can be used as a special picture-frame. The geometry of the picture-frame is defined by the
picture-frame of another drawing paper (parameter g), the figure (path), the rectangle (rect) or the region
(region). The combineMode parameter (optional for all methods except for the last) may have the values of the
of the CombineMode enumeration:
• The Replace (the currecnt clipping region (picture-frame) is replaced by the given geometry parameter G),
288
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
• Exclude (the difference between the existing and the given region ) and
• Complement (the difference between the given and the existing region ).
The RegionData class contains a byte array in the Data property and this array describes the data of the region.
The
RegionData^ GetRegionData()
method returns an instance of the RegionData class, which describes to the current region. The
array<RectangleF>^ GetRegionScans(Matrix^ matrix)
method approximates the selected region with the array of rectangles. The matrix parameter contains the matrix
of a previous transformation.
The
void MakeEmpty()
method turns the current region into an empty one, whereas the
void MakeInfinite()
We can perform operations with the regions. As the result of the query of the following methods
void Complement(Region^ region);
void Complement(RectangleF rect);
void Complement(Rectangle rect);
void Complement(GraphicsPath^ path);
the current region will be the intersection of the geometry defined as parameter and the complementer of the
current region. That is, the current region will be the difference of the geometry and the current region. (Let‟s
have R as the set of the points of the current region, and G to be the set of the given geometry, then we can
describe this operation as ).
With the
void Exclude(Region^ region);
void Exclude(RectangleF rect);
void Exclude(Rectangle rect);
void Exclude(GraphicsPath^ path);
methods the current region will be the intersection of the current region and the complementer of the shape
defined as parameter, that is, the result will be the difference of the current region and the given geometry (
).
With the
void Intersect(Region^ region);
void Intersect(RectangleF rect);
void Intersect(Rectangle rect);
289
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
methods the current region will be the intersection of itself and the geometry defined as parameter (
).
With the
void Union(Region^ region);
void Union(RectangleF rect);
void Union(Rectangle rect);
void Union(GraphicsPath^ path);
methods the current region will be the union of itself and the geometry defined as parameter ( ).
With the
void Xor(Region^ region);
void Xor(RectangleF rect);
void Xor(Rectangle rect);
void Xor(GraphicsPath^ path);
methods the current region will contain only those points that were only in one of the geometries. That geometry
can be achieved by removing the intersection of the current region and the given geometry from the union of the
current region and the given geometry (symmetric difference ).
We can transform the region with a transformation matrix or with given translation coordinates:
void Transform(Matrix^ matrix);
void Translate(int dx, int dy);
void Translate(float dx, float dy);
We can examine if a point is included in the current region with the methods:
bool IsVisible(Point point[, Graphics gr]);
bool IsVisible(PointF point[, Graphics gr]);
bool IsVisible(float x, float y);
bool IsVisible(int x, int y[, Graphics gr]);
We can decide if rectangles intersect with the current region using the methods:
bool IsVisible(Rectangle rect[, Graphics gr]);
bool IsVisible(RectangleF rect[, Graphics gr]);
bool IsVisible(int x, int y, int width, int height
[, Graphics gr]);
bool IsVisible(float x, float y, float width, float height
[, Graphics gr]);
In both cases the we can define the drawing paper with the optional Graphics type parameter.
The
bool IsEmpty(Graphics^ g);
290
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
The following example creates a figure using text and defines a clipping region before to draw the figure:
private: System::Void Form1_Paint(System::Object^ sender,
System::Windows::Forms::PaintEventArgs^ e) {
GraphicsPath^ sPath = gcnew GraphicsPath;
FontFamily^ family = gcnew FontFamily( "Arial" );
sPath->AddString("Clipped text", family, (int)FontStyle::Italic,
40,Point(0,0),StringFormat::GenericDefault);
System::Drawing::Region ^ clip = gcnew System::Drawing::Region(
Rectangle(20,20,340,15));
e->Graphics->SetClip(clip, CombineMode::Replace);
e->Graphics->DrawPath( gcnew Pen(Color::Red), sPath );
}
We also have the possibility to divide the image into pixels (raster points) and to store the color of each and
every pixel. This rasterized picture is called bitmap considering its similarity to maps. IV.56. ábra - Rasterized
A shows the black and white bitmap of the capital A.
291
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
The Image is an abstract image storing class. It is the parent of the rasterized System::Drawing::Bitmap and
vectorial System::Drawing::Imaging::MetaFile classes that contain Windows drawings. In order to create
Image objects we can use the static methods of the Image class and with these we can load images from the
given files (filename).
static Image^ FromFile(String^ filename
[, bool useEmbeddedColorManagement]);
static Image^ FromStream(Stream^ stream
[, bool useEmbeddedColorManagement
[, bool validateImageData]]);
If the useEmbeddedColorManagement logical variable is true then the color handling information of the file is
used by the method, otherwise not (the true value is the default one). In case of stream the validateImageData
logical variable controls the data check of the image. Data check works by default. We can also use the old GDI
bitmaps with the FromHbitmap() method.
We can draw the loaded bitmaps with the Graphics::DrawImage(Image,Point) method. The next example
loads the image of the Visual Studio development environment to the form (IV.57. ábra - Image on the form).
private: System::Void Form1_Paint(System::Object^ sender,
System::Windows::Forms::PaintEventArgs^ e) {
Image^ kep = Image::FromFile( "C:\\VC++.png" );
e->Graphics->DrawImage( kep, Point(10,10) );
}
Properties of the Image class store the data of the image. The int type Width and Height read only properties
define the width and the height of the image in pixels. The Size structure type Size ready only property also
stores the size data. The float type HorizontalResolution and VerticalResolution properties define the
resolution of the image in pixel/inch dimension. The SizeF structure type PhisicalDimension read only
property defines the real size of the image, in case of bitmap it is defined in pixels whereas in case of metafile
picture it is defined in 0.01 mm units.
With the ImageFormat class type RawFormat property we can specify the file format of the image, which can
be used when storing the image. The Bmp property of the ImageFormat class specifies a bitmap, the Emf
specifies an extended metafile, the Exif specifies an exchangeable image file. We can use the Gif property for
the standard image format (Graphics Interchange Format). The Guid property contains the global object
identifier applied by Microsoft. We can use the Icon property for the Windows icons. The Jpeg property
specifies the format of the JPEG (Joint Photographic Experts Group) standard, the MemoryBmp property
specifies the format of the memory bitmaps and the Png property belongs to PNG format of W3C (World Wide
Web Consortium [4.5.] ) for transmission of graphical elements through the network (Portable Network
Graphics). The Tiff property specifies the format of the TIFF (Tagged Image File Format) standard, whereas the
Wmf property specifies the format of the Windows metafiles.
292
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
The bits of the int type Flags property specifies the attributes of the pixel data stored in the image (color
handling, transparency, enlargement etc.). Some constants adequate for a typical bit state are the
ImageFlagsNone (0), the ImageFlagsScalable (1), the ImageFlagsHasAlpha (2) and the
ImageFlagsColorSpaceRGB (16).
Images can store their colors in the color palette related to the image. The ColorPaletteClass type Palette
property is nothing but a color array (array<Color>^ Entries) that contain the colors. The
ColorPaletteClass also has a Flags property. The interpretation of this by bits is as follows: the color
contains alpha information (1), the color defines a grayscale (2), the color gives the so called halftone [4.6.]
information when the colors percieved by human eyes as grayscale are built from black and white elements
(IV.58. ábra - Halftone representation).
The bounds of the picture defined in the given graphical units returns the method:
RectangleF GetBounds(GraphicsUnit pageUnit);
The
static int GetPixelFormatSize(PixelFormat pixfmt);
method defines how the image is stored, this can be a palette index or the value of the color itself. Some possible
values: Gdi – the pixel contains GDI color code (rgb), Alpha – the pixel contains trasnparency information too,
Format8bppIndexed – index, 8 bits per pixel color(256 colors).
With the
293
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
method we can rotate and flip the image according to the elements of the RotateFlipType enumeration (e.g
Rotate90FlipNone rotates with 90 degrees and does not do flipping, RotateNoneFlipX flips to the y axis,
Rotate90FlipXY rotates with 90 degrees and flips centrally).
The next example displays the image of IV.57. ábra - Image on the form form flipping it to the y axis:
private: System::Void Form1_Paint(System::Object^ sender,
System::Windows::Forms::PaintEventArgs^ e) {
Image^ kep = Image::FromFile( "C:\\VC++.png" );
kep->RotateFlip(RotateFlipType::RotateNoneFlipX);
e->Graphics->DrawImage( kep, Point(10,10) );
}
method. The filename parameter contains the name of the file, the file extension depends on the extension in the
filename. We can define the saving format with the optional format parameter. If there is no coding information
defined for the file formats (like e.g. Wmf) then the method saves into Png format.
We can also save the data of the image into a stream with the method:
void Save(Stream^ stream, ImageFormat^ format);
While the Bitmap is the descendant of the Image class, it inherits all the properties and functions of the Image,
however the data structure of the Bitmap corresponds to method of storing data in the memory, which gives the
possibility to use a set of special methods as well. The Bitmap class have constructors and with their help we
can create a bitmap from Image, from Stream, from a file, moreover from any graphics or from pixel data
stored in the memory.
The following constructor creates an empty bitmap with given size (width, height parameters):
Bitmap(int width, int height[,, PixelFormat format]);
if we use the PixelFormat type format parameter, then we can specify the color depth as we could see it at the
Image::GepPixelFormat() method.
The
294
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
Bitmap(Image^ original);
Bitmap(Image^ original, Size newSize);
Bitmap(Image^ original, int width, int height);
constructors create a bitmap from the Image defined as parameter. If we define the newSize parameter or the
width and height parameters, then the bitmap is created with the rescaling of the image to the new size.
With the
Bitmap(Stream^ stream[, bool useIcm]);
overloaded constructor we can create a bitmap from a stream, and with the
Bitmap(String^ filename[, bool useIcm]);
overloaded constructor we can create it from a file. If we use the useIcm logical parameter, then we can specify
whether to use color correction or not.
We can create a bitmap of any Graphics instance, for example of the screen with the
Bitmap(int width, int height, Graphics^ g);
constructor. The integers of width and height define the size, the g parameter define the instance of the drawing
paper, the DpiX and DpiY properties of this define the bitmap resolution. We can also create a bitmap from the
memory referenced by the scan0 pointer that points to the integer, if we set the width, the height and the stride
(difference between two bitmap rows) in bytes:
Bitmap(int width, int height, int stride,
PixelFormat format, IntPtr scan0);
The resolution of a newly created bitmap can be set with the method:
void SetResolution(float xDpi, float yDpi);
The xDpi and yDpi parameters are in dot per inch units.
Some methods not inhereted from the Image class can supplement the opportunities offered by bitmaps. We can
reach one point of the bitmap directly. The color of the given pixel (x, y) is returned by the method:
Color GetPixel(int x, int y);
We can set the color of the specified pixel with the method:
void SetPixel(int x, int y, Color color);
The
void MakeTransparent([Color transparentColor]);
function makes the default transparent color transparent for this Bitmap. If we also define the
transparentColor parameter, then this color will be transparent.
The next example loads a bitmap and draws it. After it the points of the bitmap colored to a color that is close to
white will be painted green and then the program draws the modified bitmap. In the next step it makes the green
color transparent and draws the bitmap again moved with half of the size (IV.60. ábra - Coloring bitmap).
295
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
Bitmap handling with the GetPixel() and SetPixel() methods is not effective enough. We have the option to
load the bits of the bitmap into the memory and to modify them, then we can pump the bits back to the bitmap.
In this case we need to ensure that the operating system does not disturb our actions. In order to store the
bitmaps in the memory we can use the function:
BitmapData^ LockBits(Rectangle rect, ImageLockMode flags,
PixelFormat format[, BitmapData^ bitmapData]);
void UnlockBits(BitmapData^ bitmapdata);
Where the rect parameter selects the part of the bitmap we would like to store, the enumeration type
ImageLockMode flags parameter defines the way of data handling ( ReadOnly, WriteOnly, ReadWrite,
UserInputBuffer). We are already familiar with the PixelFormat enumeration. The BitmapData class type
bitmapdata optional parameter has the properties: Height and Width are the size of the bitmap, the Stride is
the width of the scanline, whereas scan0 is the address of the first pixel. The returned value contains the data of
the bitmap in an isntance of the BitmapData reference class.
After calling the function the bitmapdata is locked and it can be manipulated with the methods of the
System::Runtime::InteropServices ::Marshal class. With the methods of the Marshal class we can
allocate a non-managed memory block, we can copy the non-managed memory blocks, or we can copy managed
memory objects to a non-managed memory area. For example the
static void Copy(IntPtr source,
array<unsigned char>^ destination,
int startIndex, int length);
method of the Marshal class copies length bytes from the startIndex position of the memory area pointed
by the source pointer to the destination managed array, whereas the
296
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
method copies length bytes in reverse order from the source managed array from the startindex position to
the destination non-managed area.
Once we copied the parts from the bitmap that we intended to modify, we modified them and copied them back,
then we can unlock the locked memory with the method:
void UnlockBits(BitmapData^ bitmapdata);
The following example draws a managed bitmap that was loaded to the memory when clicking on the form.
After it locks it, copies the bytes of the image, erases the middle third of the image, stores the data back into the
bitmap, unlocks the memory and draws the bitmap.
private: System::Void Form1_Click(System::Object^ sender,
System::EventArgs^ e) {
Bitmap ^ bm = gcnew Bitmap("C:\\D\\X.png");
Graphics ^ g = this->CreateGraphics();
g->DrawImage( bm, Point(0,0) );
Rectangle rect = Rectangle(0,0,bm->Width,bm->Height);
System::Drawing::Imaging::BitmapData^ bmpData = bm->LockBits(
rect, System::Drawing::Imaging::ImageLockMode::ReadWrite,
bm->PixelFormat );
IntPtr ptr = bmpData->Scan0;
int bytes = Math::Abs(bmpData->Stride) * bm->Height;
array<Byte>^Values = gcnew array<Byte>(bytes);
System::Runtime::InteropServices::Marshal::Copy( ptr, Values, 0, bytes );
for ( int counter = Values->Length/3; counter < 2*Values->Length/3; counter ++ )
{
Values[ counter ] = 255;
}
System::Runtime::InteropServices::Marshal::Copy( Values, 0,
ptr, bytes );
bm->UnlockBits( bmpData );
g->DrawImage( bm, bm->Width, 0 );}
We can also work with metafiles because we can use the methods of the Image class. The next example loads a
Png file into a bitmap and saves it as a Wmf metafile.
private: System::Void Form1_Click(System::Object^ sender,
System::EventArgs^ e) {
Bitmap ^ bm = gcnew Bitmap("C:\\D\\X.png");
Graphics ^ g = this->CreateGraphics();
g->DrawImage( bm, bm->Width, 0 );
bm->Save("C:\\D\\X.wmf");
}
297
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
4.10. Brushes
Brushes can be used to paint areas in GDI+ [4.1.] . Each brush type is a descendant of the
System::Drawing::Brush class (IV.27. ábra - The classes of GDI+). The simplest brush is of a single color
(SolidBrush). The constructor of this creates a brush from the given color. Accordingly the Color property of
the class picks up the color of the brush.
SolidBrush(Color color);
The following example paints a red rectangle on the form with the FillRectangle() method of the Graphics
class (IV.62. ábra - Brushes).
private: System::Void Form1_Paint(System::Object^ sender,
System::Windows::Forms::PaintEventArgs^ e) {
SolidBrush ^ sb = gcnew SolidBrush(Color::Red);
e->Graphics->FillRectangle(sb,200,20,150,50);
}
HatchBrush, the patterned brush is also a descendant of the Brush class and it is defined in the
System::Drawing::Drawing2D namespace, that is why it is necessary to specify:
The constructor of the patterned brush defines the pattern of the brush, the color of the pattern and the
background. If we omit the last parameter (squared brackets mark the optional parameters), then the background
color will be black:
HatchBrush(HatchStyle hatchstyle, Color foreColor,
[Color backColor])
The HatchStyle enumeration of the Drawing2D namespace contains a lot of predefined patterns. The
horizontal one is the HatchStyle::Horizontal, the vertical one is Vertical, the diagonal one (leaning to
the left) is the ForwardDiagonal etc. The next example paints a yellow rectangle with a red diagonal-hatched
brush (IV.62. ábra - Brushes).
private: System::Void Form1_Paint(System::Object^ sender,
System::Windows::Forms::PaintEventArgs^ e) {
HatchBrush ^ hb = gcnew HatchBrush(HatchStyle::ForwardDiagonal,
Color::Red,Color::Yellow);
e->Graphics->FillRectangle(hb,200,80,150,50);
}
The characteristic properties of the patterned brushes are the BackGroundColor for the color of the background,
the ForegroundColor for the color of the foreground and HatchStyle for the pattern type.
We can create brushes painting with images using the instances of the TextureBrush class. These are its
constructors:
TextureBrush(Image^ image);
TextureBrush(Image^ image, WrapMode wrapMode);
TextureBrush(Image^ image, Rectangle dstRect);
TextureBrush(Image^ image, RectangleF dstRect);
TextureBrush(Image^ image, WrapMode wrapMode,
Rectangle dstRect);
TextureBrush(Image^ image, WrapMode wrapMode,
RectangleF dstRect);
TextureBrush(Image^ image, Rectangle dstRect,
ImageAttributes^ imageAttr);
TextureBrush(Image^ image, RectangleF dstRect,
298
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
ImageAttributes^ imageAttr);
The image defines the picture used for painting, the dstRect defines the distortion of the picture when painting.
The wrapMode parameter specifies how the images used for painting are tiled. (The members of the WrapMode
enumeration: Tile – as if it was built from tiles, TileFlipX – built from tiles mirrored to the y axis,
TileFlipY - built from tiles mirrored to the x axis, TileFlipXY – built from centrally mirrored tiles, Clamped –
the texture is not built from tiles). The System::Drawing::Imaging ::ImageAttributes^ type imageAttr
contains additional information about the image (colors, color corrections, painting methods etc).
The Image (Image^ type) property of the TextureBrush class defines the graphics of the painting, the
Transform^ type Transform property and its methods can be used similarly to their usage already discussed for
transformations. The WrapMode property contains the arrangement.
The following example paints a rectangle with images (IV.62. ábra - Brushes).
private: System::Void Form1_Paint(System::Object^ sender,
System::Windows::Forms::PaintEventArgs^ e) {
Image ^ im = Image::FromFile("c:\\M\\mogi.png");
TextureBrush ^ tb = gcnew TextureBrush(im);
Matrix ^ m= gcnew Matrix();
m->Translate(5,5);
tb->Transform->Reset();
tb->Transform=m;
Graphics ^ g = this->CreateGraphics();
g->FillRectangle(tb, 200, 140, 150, 50);
}
The Drawing2D namespace contains the LinearGradientBrush class. With its constructors we can create
gradient brushes. With the various constructors we can set different gradient types. The
System::Windows::Media::BrushMappingMode enumeration defines a coordinate systems for the gradient
effect. The elements of this enumertion are Absolute and RelativeToBoudingBox. The Absolute means the
points are interpreted in the current coordinate system. When the RelativeToBoudingBox is set, the top left
corner of the bounding rectangle of the given painting will be (0,0), whereas the bottom right corner will be
(1,1).
The linear gradient brushes can be created with different constructor calls. Using the constructors
LinearGradientBrush(Rectangle rect,
Color startColor, Color endColor,
double angle, [isAgleScaleable]);
LinearGradientBrush(RectangleF rect,
Color startColor, Color endColor,
double angle, [isAgleScaleable]);
we can set the starting color (startColor) and the ending color (endColor). In the RelativeToBoudingBox
brush coordinate system the angle of the gradient is given in degrees (0 is a horizontal gradient, 90 is vertical).
299
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
we can set the starting color (startColor) and the ending color (endColor) too. In the
RelativeToBoudingBox brush coordinate system we can set the angle of the gradient with the points startPoint
and endPoint or with the corners of rect in the units of %. The elements of the LinearGradientMode
enumeration (Horizontal, Vertical, ForwardDiagonal, BackwardDiagonal) define the direction of the
color transition.
With the
PathGradientBrush(GraphicsPath path);
PathGradientBrush(Point[] points);
PathGradientBrush(PointF[] points);
PathGradientBrush(Point[] points, WrapMode wrapMode);
constructors we can create a brush which changes its color along a curve. We can define the curve with the path
and the points parameters. We already got familiar with the wrapMode WrapMode type parameter when
discussing the texture brushes.
Both gradient brushes have a WrapMode property that defines the method how to repeat the painting and they
also have a Transform feature that contains local transformation. Both gradient brushes contain Blend^ type
Blend property. The Factors and Positions properties of the Blend class are float arrays with elements
between 0 and 1, which define the intensity of the color in % and the length position in %. Both gradient brushes
have an InterpolationColors (ColorBlend class type) property where we can set the colors besides the
positions in % instead of the color intensity. Both gradient brushes have a bounding Rectangle property.
The Color array type SurroundColors property of the PathGradientBrush contains the applied colors.
A brush can be transparent, if we use ARGB colors. The following example draws a transparent rectangle on the
top of the rectangles painted differently.
private: System::Void Form1_Paint(System::Object^ sender,
System::Windows::Forms::PaintEventArgs^ e) {
SolidBrush ^ sb = gcnew SolidBrush(Color::Red);
e->Graphics->FillRectangle(sb,200,20,150,50);
HatchBrush ^ hb = gcnew HatchBrush(
HatchStyle::ForwardDiagonal,
Color::Red,Color::Yellow);
e->Graphics->FillRectangle(hb,200,80,150,50);
Image ^ im = Image::FromFile("c:\\M\\mogi.png");
TextureBrush ^ tb = gcnew TextureBrush(im);
Matrix ^ m= gcnew Matrix();
m->Translate(5,5);
tb->Transform->Reset();
tb->Transform=m;
e->Graphics->FillRectangle(tb, 200, 140, 150, 50);
LinearGradientBrush ^ lgb = gcnew LinearGradientBrush(
PointF(0,0), PointF(100,100), Color::Blue, Color::Red);
e->Graphics->FillRectangle(lgb, 200, 200, 150, 50);
SolidBrush ^ at = gcnew SolidBrush(
Color::FromArgb(127,Color::Red));
e->Graphics->FillRectangle(at, 250, 10, 200, 270);
}
300
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
4.11. Pens
We can draw line pictures with pens. The pens have color and width. With the
Pen(Brush brush [, float width]);
Pen(Color color [, float width]);
constructors of the Pen class we can create a pen defined by its color or its brush. We can also specify the width
of the pen. The Brush, Color and Width properties store the mentioned features of the pen.
The DashStyle property can set with one of the elements of the DashStyle enumeration (
System::Drawing::Drawing2D namespace). We can set this way the pattern of the pen ( Solid – continuous,
Dash – dashed, Dot – dotted, DashDot – dashes and dots, DashDotDot – dashes and double dots, and Custom).
In case of the the last dash style we can define the pattern, with the consecutive elements of the DashPattern
float array consisting the length of dashes and spaces.
With the float type DashOffset property we can set the distance between the starting point of the line and
starting point of the dash. In case of dashed lines we can also set the cap style used at the end of the dashes with
the DashCap ( System::Drawing::Drawing2D ). The elements of the DashCap enumeration are: Flat – flat,
Round - rounded, Triangle – triangular.
In order to achieve a joint geometry of the consecutive lines of the shapes we can use the LineJoin property of
the LineJoin enumeration type (Bevel – a corner clipped in a certain angle, Round – rounded, Miter and
MiterClipped also mean a clipped connection, if the length of the miter exceeds the float type MiterLimit
value).
In order to control the drawing of the line endings the StartCap and the EndCap
System::Drawing::Drawing2D::LineCap enumeration type properties can be used (Flat – flat, Square -
square, Round – rounded, ArrowAnchor – ending with an arrow, RoundAnchor – ending with a circle etc). The
CustomStartCap and the CustomEndCap properties can be interesting and those are instances of the
CustomLineCap class. The instance can be created with the
constructor. We can define the filling with a figure (fillPath) and the outline (strokePath). We can use an
existing line cap (baseCap), or we can insert a gap into the cap and the line (baseInset). The BaseCap property
of the CustomLineCap class identifies the parent line cap and the BaseInset contains the gap. We can
301
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
determine the mode how lines are joined to each other ( StrokeJoin), or we can set a scale for their width
(WidthScale property). By default this is the width of the line.
The ready only PenType enumeration type PenType property of the Pen class tells us the type of the pen
(SolidColor, HatchFill, TextureFill, LinearGradient, PathGradient)
Of course, pens also have a Transform property that defines the local transformation.
The following example draws a line with a red solid pen, then with a pen defined with a linear gradient brush.
After this it draws a line on one of the end with an arrow, then on the other end with a rounded cap. Then two
dashed lines come, the first with a predefined pattern, whereas the second one with an pattern array. This is
followed by two rectangle shapes, one of them with a clipped and default OutSet Alignment property, then the
other with the InSet property without clipping (IV.63. ábra - Pens).
private: System::Void Form1_Paint(System::Object^ sender,
System::Windows::Forms::PaintEventArgs^ e) {
Pen ^ ps = gcnew Pen(Color::Red,5);
e->Graphics->DrawLine(ps,10,25,100,25);
LinearGradientBrush ^ gb = gcnew LinearGradientBrush(
PointF(10, 10), PointF(110, 10),
Color::Red, Color::Blue);
Pen ^ pg = gcnew Pen(gb,5);
e->Graphics->DrawLine(pg,10,50,100,50);
ps->StartCap=LineCap::ArrowAnchor;
pg->EndCap=LineCap::Round;
e->Graphics->DrawLine(pg,10,75,100,75);
pg->DashStyle=DashStyle::DashDot;
e->Graphics->DrawLine(pg,10,100,100,100);
302
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
When using the TrueType character set, then the so called TrueType rasterizer creates a bitmap character set
from the points and algorithms according to the requirements. The result of this is that if we plan the page to be
printed with a publishing software, then the printout will be the same what was visible on the screen (WYSIWYG
- What You See Is What You Get). Microsoft bought the right of using the TrueType fonts from Apple Computer
[4.7.] . It is obvious that the first two character representation methods have their advantages and disadvantages
as well. It is possible to resize the vector based fonts as wished, however, in a lot of cases their visualization is
not aesthetic enough and is not readable. On the other hand, bitmap fonts are well readable but they cannot be
deform freely. The usage of the TrueType type character set guarantees the above benefits and eliminates
disadvantages.
Let us get familiar with some notions that help us to navigate in the world of fonts. It is a basic question whether
the width of the line of the letter can be changed when displaying the character. Is it possible to use a thinner or
a thicker letter drawing? The line of the letter is called „stroke” in the English terminology.
There are more character sets that uses small crossing lines to close the line of the letters when drawing them.
The letters will have small soles and hat. These small crossing lines are called serif. This way that character set
that do not use these closing serifsis called sanserif, that is, without serif.
The rectangular area that bounds the character is called character cell. The size of this can be characterized by
the emSize parameter, which shows the size of the character in points (1/72 inch). The em prefix comes from
pronounciation of the M letter because it is used to sizing the font. Characters do not fully fill in their cells. The
cell is characterized by its width, height and its starting point which is usually the top left corner. The character
cell is devided into two parts by a horizontal line. Characters are sitting on this line, that is, the base line. The
part above the base line is called ascent and the part below the base line is called descent. The distance of two
base lines that are in two character cells placed below each other is called leading. Capital letters written on he
base line do not reach the top of the character cell either.
The internal leading is the area between the top edge of the character cell and the top edge of capital letters. If
the top and bottom edge of the character cells placed below each other do not touch, then the distance between
these two is called the externalleading.
A given size of a character type is called typeface. Windows is capable of creating typefaces with distortion in
the given size. Italic letters (italic) are created in a way that the points of the character remain unchanged on the
base line, whereas the other parts of the character are slipped depending on their distance from the base line (the
character leans). The bold letters (bold) can be easily derived from making the stroke thicker. Obviously it is
easy to create underlined and lined strikeout characters with adding a horizontal line to a specific place. The
303
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
notion of pitch is also in use which is specifying how many characters have to be written next to each other from
a given font so that the width of the written text is one inch (~2.54 cm). It is worth mentioning that the logical
inch is used for displaying characters on the screen. The size of the logical inch is defined in a way that the text
is as well readable on screen as on paper. Two basic font type exist. One is non-proportional – fix sized – in
which each character is placed in a cell with the same size, being either an „i” or an „m”. Recently these font
types are called monospace fonts. The other is proportional in which case the width of the cell depends on the
given letter. In this latest case the average character widths and the average and maximum pitch sizes can be
used.
It is worth dealing with the with the aspect ratio of the characters, given that not all the fonts could be displayed
on all output devices (e.g dot matrix printer). Windows can perform aspect ratio conversion in order to display
successfully. The uses font families depending on the appearance of the characters.
Traditional character sets use the data shown on IV.65. ábra - Traditional character widths to descript the
dimensions of the characters. Instead of this traditional interpretation the TrueType character sets use the so
called ABC data. As it is visible on IV.66. ábra - ABC character widths the A and C values can be negative as
well.
Font families contain fonts that have similar characteristics. The .Net defines the FontFamily class to model
font families (this class cannot be inherited). We can create FontFamily instances with the
FontFamily(GenericFontFamilies genericFamily);
FontFamily(String^ name[, FontCollection^ fontCollection]);
constructors. We can choose the value of the genericFamily parameter from the elements of the
GenericFontFamilies enumeration in order to create a family. The possible values of the enumeration ( Serif,
SansSerif and Monospace) cover already known notions. The name parameter contain the name of the new
font family. It can be an empty string or the name of a font family that has not been installed on the computer or
a name of a non TrueType font. We can also create collections from font families. We can define the collection
with the fontCollection parameterwhere we can place the new font family.
We can get information opportunities from the properties of the FontFamily class. With the static Families
property we can query the font families of the current drawing paper into a FontFamily type array. The
FontFamily type, static GenericMonospace, GenericSansSerif and GenericSerif properties give a non
304
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
proportional, a sansserif and a serif font family. The string type Name property contains the name of the font
family.
The static
FontFamily[] GetFamilies(Graphics graphics);
method returns all family font names that can be found on the given drawing paper.
The following example collects the names of all font families of the drawing paper to the listbox when clicking
on the form.
private: System::Void Form1_Click(System::Object^ sender,
System::EventArgs^ e) {
Graphics ^ g = this->CreateGraphics();
array <FontFamily ^> ^ f = gcnew array<FontFamily^>(100);
f=FontFamily::GetFamilies(g);
for (int i=0; i<f->GetLength(0); i++) {
listBox1->Items->Add(f[i]->Name);
}
}
The
int GetCellAscent(FontStyle style);
int GetCellDescent(FontStyle style);
int GetEmHeight(FontStyle style);
int GetLineSpacing(FontStyle style);
String^ GetName(int language);
method queries the data defined by the style parameter of the given font family.
The
bool IsStyleAvailable(FontStyle style);
method indicates if the given font family has the given style. The values of the FontStyle enumeration are:
Bold, Italic, Underline and Strikeout.
305
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
The GDI+ uses the Font class to model the font types. We can create the instances of the class with the
constructors:
Font(FontFamily^ family, float emSize[, FontStyle style
[, GraphicsUnit unit [, unsigned char gdiCharSet
[, bool gdiVerticalFont]]]]);
Font(FontFamily^ family, float emSize, GraphicsUnit unit);
Font(String^ familyName, float emSize[, FontStyle style
[, GraphicsUnit unit[, unsigned char gdiCharSet
[, bool gdiVerticalFont]]]]);
Font(String^ familyName, float emSize, GraphicsUnit unit);
Font(Font^ prototype, FontStyle newStyle);
The family parameter defines the familiy of the new font, the familyName defines the name of this family. The
emSize is the size of the font in points, the style is the font type (italic, bold etc.). The unit parameter defines the
units (Pixel, Inch, Millimeter etc.) used by the font
The gdiCharset indentifies the character set provided by GDI+. The gdiVerticalFont regulates vertical writing.
We can define a new font from an existing font and with the new newStyle style.
All properties of the Font class are read-only. If we would like to make changes, then we need to create a new
font. The FontFamily^ type FontFamily property identifies the font family. The Name string defines the name
of the font, whereas the OriginalFontName string defines the original name of the font. If the IsSystemFont
logical property is true then we work with a system font. In this case the SystemFontName string property
defines the name of the system font. The GdiVerticalFont logical property defines whether the font is
vertical. The GdiCharSet byte type property identifies the GDI character set. The Size (emSize) and the
SizeInPonts define the font size in points. The Style logical property indentifies the font style with the
elements of the FontStyle type enumeration (Italic, Bold, Underline and Strikeout). The Unit property
defines the unit of measure for this font.
The
float GetHeight();
method returns the distance that can be measured between lines in pixels. The
float GetHeight(Graphics^ graphics);
method returns the distance that can be measured between lines in GraphicsUnit type units. The
float GetHeight(float dpi);
method defines the distance that can be measured between lines in pixels on a device with a given dpi (dot per
inch – pont per inch) resolution.
The next example shows the distortion of a font chosen by clicking on the Type button with the Bold, Italic and
Underline checkboxes. (IV.68. ábra - Font distortions).
306
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
In the next drawing routines the first parameter identifies the pen of the drawing. We can draw straight line
sections with the methods:
void DrawLine(Pen^ pen, Point pt1, Point pt2);
void DrawLine(Pen^ pen, PointF pt1, PointF pt2);
void DrawLine(Pen^ pen, int x1, int y1, int x2, int y2);
void DrawLine(Pen^ pen, float x1, float y1, float x2, float y2);
The data of the rectangle are defined by the rect structure or the (x,y) upper-left corner, the width and the height
parameters.
We can draw a set of rectangles defined by the rects array at once with the method:
void DrawRectangles(Pen^ pen, array<Rectangle>^ rects);
void DrawRectangles(Pen^ pen, array<RectangleF>^ rects);
The following methods draw polygons defined by the points structure array:
307
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
The following methods draw an ellipse defined by the bounding rectangle specified by the rect structure or the
(x,y) and (height, width) data:
void DrawEllipse(Pen^ pen, Rectangle rect);
void DrawEllipse(Pen^ pen, RectangleF rect);
void DrawEllipse(Pen^ pen, int x, int y, int width, int height);
void DrawEllipse(Pen^ pen, float x, float y,
float width, float height);
We can draw an arc representing a portion of an ellipse (as visible on IV.45. ábra - Elliptical arc) with the
methods:
void DrawArc(Pen^ pen, Rectangle rect,
float startAngle, float sweepAngle);
void DrawArc(Pen^ pen, RectangleF rect,
float startAngle, float sweepAngle);
void DrawArc(Pen^ pen, int x, int y, int width, int height,
int startAngle,int sweepAngle);
void DrawArc(Pen^ pen, float x, float y,
float width, float height,
float startAngle, float sweepAngle);
The
void DrawPie(Pen^ pen, Rectangle rect,
float startAngle, float sweepAngle);
void DrawPie(Pen^ pen, RectangleF rect,
float startAngle, float sweepAngle);
void DrawPie(Pen^ pen, int x, int y, int width, int height,
int startAngle,int sweepAngle);
void DrawPie(Pen^ pen, float x, float y,
float width, float height,
float startAngle, float sweepAngle);
methods draw pie slices from the arcs (straight lines go to the center point).
The following methods draw a Bezier curve according to IV.46. ábra - Cubic Bezier curve:
void DrawBezier(Pen^ pen, Point pt1, Point pt2,
Point pt3, Point pt4);
void DrawBezier(Pen^ pen, PointF pt1, PointF pt2,
PointF pt3, PointF pt4);
void DrawBezier(Pen^ pen, float x1, float y1,
float x2, float y2,
float x3, float y3,
float x4, float y4);
The control points can be defined with the pt i structures or with the x i , y i coordinates.
After the first 4 points each upcoming three points define the next curve segment.
The
308
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
methods draw a cardinal spline (IV.49. ábra - Catmull-Rom spline) with the given pen (pen) through the points
of the points array. We can set the tension and the number of the considered curve segments
(numberOfSegments).
through the points of the points array with the fillmode, which can be an element (Alternate or Winding) of
the – already known - FillMode enumeration type (IV.44. ábra - Alternate and Winding curve chains).
The following method draws a shape (path) with the given pen:
void DrawPath(Pen^ pen, GraphicsPath^ path);
The
void DrawString(String^ s, Font^ font, Brush^ brush,
PointF point[, StringFormat^ format]);
void DrawString(String^ s, Font^ font, Brush^ brush,
float x, float y[, StringFormat^ format]);
void DrawString(String^ s, Font^ font, Brush^ brush,
RectangleF layoutRectangle[,
StringFormat^ format]);
methods draws the specified text string s on the drawing paper with the given font and brush. The place of the
writing can be set with the point or the x and y parameters or with the layoutRectangle bounding rectangle. An
instance of the StringFormat class defines the format of the appearance. (For example with the values
(DirectionRightToLeft, DirectionVertical, NoWrap etc.) of the StringFormatFlags enumeration type
FormatFlags property or in the Alignment property with the Near, Center and Far elements of the
Alignment enumeration.)
into point x, y on the drawing paper. We can scale the icon to the targetRectangle rectangle:
void DrawIcon(Icon^ icon, Rectangle targetRect);
The icon is drawn in the rectangle without scaling with the method:
void DrawIconUnstretched(Icon^ icon, Rectangle targetRect);
We can draw the picture defined by the image or a chosen part of it (srcRect in the given srcUnit units) to the
point or to the (x,y) point with the methods:
309
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
The
void DrawImage(Image^ image, Rectangle rect);
void DrawImage(Image^ image, RectangleF rect);
void DrawImage(Image^ image, int x, int y,
int width, int height);
void DrawImage(Image^ image, float x, float y,
float width, float height);
methods stretch the image to the rect or the (x, y, width, height) rectangle
One rectangle (srcRect) portion of the picture (image) can be drawn to another rectangle of the drawing
(destRect) with the methods:
void DrawImage(Image^ image, Rectangle destRect,
Rectangle srcRect, GraphicsUnit srcUnit);
void DrawImage(Image^ image, RectangleF destRect,
RectangleF srcRect, GraphicsUnit srcUnit);
If we would like to achieve something similar to the above, setting the source rectangle with int or float
cornerpoint data (srcX, srcY) and int or float width and height data (srcWidth, srcHeight) will not be enough
but we also have to define the graphical units of the source (srcUnit). In this case we can also use an instance of
the ImageAttributes class to recolor (imageAttr) , morevover we can also define an error handling callback
function and we can send data to the callback function (callbackData):
void DrawImage(Image^ image, Rectangle destRect,
int srcX, int srcY, int srcWidth, int srcHeight,
GraphicsUnit srcUnit
[, ImageAttributes^ imageAttr
[,Graphics::DrawImageAbort^ callback
[,IntPtr callbackData]]]);
We can put the scaled and sheared image in a parallelogram with the method:
void DrawImage(Image^ image, array<Point>^ destPoints
[, Rectangle srcRect, GraphicsUnit srcUnit
[, ImageAttributes^ imageAttr[,
Graphics::DrawImageAbort^ callback
[,int callbackData]]]]);
The image is the picture we would like to draw, the destPoints array is three points of the parallelogram of the
scaled image. Further parameters are the same as the parameters of those functions that were mentioned lately.
The following example draws an image straight and distorted as well.
private: System::Void Form1_Paint(System::Object^ sender,
System::Windows::Forms::PaintEventArgs^ e) {
310
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
We can draw painted shapes with the Graphics class Fillxxx()methods. The
void FillRectangle(Brush^ brush, Rectangle rect);
void FillRectangle(Brush^ brush, RectangleF rect);
void FillRectangle(Brush^ brush, int x, int y,
int width, int height);
void FillRectangle(Brush^ brush, float x, float y,
float width, float height);
methods fill the interior of a rectangle defined by the rect structure or with the x,y, and height, width data with
the brush.
The rectangles specified in the rects array are filled by the methods:
void FillRectangles(Brush^ brush, array<Rectangle>^ rects);
void FillRectangles(Brush^ brush, array<RectangleF>^ rects);
Closed polygons defined by the points array parameter are filled by the brush:
void FillPolygon(Brush^ brush, array<Point>^ points
[, FillMode fillMode]);
void FillPolygon(Brush^ brush, array<PointF>^ points,
[FillMode fillMode]);
Using the optional fillMode parameter (Alternate, Winding – Graphics2D) we can define the fill mode.
The
void FillEllipse(Brush^ brush, Rectangle rect);
void FillEllipse(Brush^ brush, RectangleF rect);
void FillEllipse(Brush^ brush, int x, int y,
311
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
methods fill the interior of an ellipse defined by the rect structure or with the x,y, and height, width data with the
brush.
The interior of a pie section of an ellipse defined by the rect structure or with the x,y, and height, width data is
can be filled with the brush using the following methods:
void FillPie(Brush^ brush, Rectangle rect,
float startAngle, float sweepAngle);
void FillPie(Brush^ brush, int x, int y,
int width, int height,
int startAngle, int sweepAngle);
void FillPie(Brush^ brush, float x, float y,
float width, float height,
float startAngle, float sweepAngle);
The area bounded by a closed cardinal curve defined by the control points of the points array can be filled with
the brush using the following methods. We can set the optional filling mode (fillmode) and the tension of the
curve (tension) parameters.
void FillClosedCurve(Brush^ brush, array<Point>^ points, [
FillMode fillmode, [float tension]]);
void FillClosedCurve(Brush^ brush, array<PointF>^ points, [
FillMode fillmode, [float tension]]);
The path shape can be filled with the brush using the method:
void FillPath(Brush^ brush, GraphicsPath^ path);
We can copy a blocskRegionSize size rectangle from the given point (upperLeftSource or sourceX, sourceY) of
the screen to a rectangle placed in the point (upperLeftDestination)of the drawing paper. We can set with the
copypixelOperation parameter what to happen with the points that originally stand in the target place (the
elements of the CopyPixelOperation enumeration that define the logical operations). Its elements are
SourceCopy, SourceAnd, SourceInvert, MergeCopy etc.).
We can save the state of the graphical settings, the transformations and the setting of the clippings with the
GraphicsState^ Save()
method. Later we can restore the saved settings using the gstate parameter, which is returned by the Save()
method:
312
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
Similarly the
GraphicsContainer^ BeginContainer();
method saves a graphics container with the current state of the Graphics and opens and uses a new graphics
container.
The
void EndContainer(GraphicsContainer^ container);
method closes the current graphics container and restores the state of the Graphics to the state saved (container)
previously by a call of the BeginContainer() method.
4.14. Printing
Printing options are defined in the System::Drawing::Printing namespace.
The printing can be done with the help of the PrintDocument class. We can either place one instance of the
class from the Toolbox to the formor we can create it with the constructor:
PrintDocument()
We can print documents with an instance of the PrintDocument class. We can set the name of the document
with the String type read and write property:
DocumentName
The
PrinterSettings
The static InstalledPrinters property of this is a collection of strings that contains the name of the printers
installed into the system. We can use the PrinterName string property to set where we would like to print. If we
do not do anything then we will use the default printer. The IsValid property tells us whether the right printer
was set.
The PrinterSettings class contains a set of properties that help to query the options and the settings of the
printer. For example the CanDuplex logical property informs about the option to make two-sided printouts, the
Duplex - another logical property - controls the usage of this. The Collate informs about the collateral options,
Copies gets the number of printouts and sets it up to the value of the MaximumCopies property. The logical
IsDefaultPrinter tells if we work with the default printer, IsPlotter another logical property indicates a
plotter is used whereas the SupportColor property informs about the color printer. The PaperSizes collection
depends on the properties of the printer and it contains the available paper sizes, PaperSources contains the
available paper sources while PrinterResolution contains the available resolutions. LandScapeAngle tells
the angle between portrait and landscape settings.
The DefaultPageSettings property is an instance of the PageSetting class that contains all the settings of
the page (colors, margin sizes, resolutions).
We can select a part of the document using the PrintRange property (AllPages, SomePages, Selection,
CurrentPage etc. values). We can also set the first page (FromPage) and the last page (ToPage) of the printing.
313
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
We can also cater for printing into a file with setting the PrintToFile logical property to true. In this case the
name of the file can be given in the PrintFileName string property.
method of the PrintDocument class. When printing the events of the class can be raised: BeginPrint (the
beginning of printing), EndPrint (the end of printing) and PrintPage (printing of a page). Each event has a
PrintEventArgs reference class type parameter. The properties of this parameter are the instance of the
PageSettings class (with properties: Bounds – bounding rectagle of the page, Color – color printing,
LandsCape - landscape, Margins - margins, PaperSize - the size of the paper, PrinterResolution – printer
resolution), the margin and page settings and the already known Graphics type printer drawing paper as well.
The HasMorePages read and write logical property indicates if there are more pages to be printed.
The following example draws a line on the default printer (in our case it is the Microsoft Office OneNote
program) when clicking on the form:
private: System::Void printDocument1_PrintPage(
System::Object^ sender,
System::Drawing::Printing::PrintPageEventArgs^ e) {
e->Graphics->DrawLine(gcnew Pen(Color::Red,2),10,10,100,100);
}
private: System::Void Form1_Click(System::Object^ sender,
System::EventArgs^ e) {
printDocument1->Print();
}
References:
[4.1.] MicroSoft Developer Network http://msdn.microsoft.com/. 2012.07.
314
Created by XMLmind XSL-FO Converter.
Programming Microsoft Windows in
C++
[4.2.] Benkő , Tiborné, Kiss , Zoltán, Kuzmina , Jekatyerina, Tamás , Péter , és Tóth , Bertalan. Könnyű a
Windowst programozni!? . ComputerBooks . Budapest . 1998.
[4.4.] Szirmay-Kalos , László, Antal , György , és Csonka , Ferenc. Háromdimenziós Grafika, animáció és
játékfejlesztés. ComputerBooks . Budapest . 2003.
315
Created by XMLmind XSL-FO Converter.
V. fejezet - Developing open-source
systems
1. The structure of Linux systems
In the previous parts of this book, we have mainly dealt with Microsoft Windows-based software development.
However, there are more successful software platforms that are widespread nowadays. One of these is
constituted by Unix-like systems, the code portions, concepts, solutions and ideas of which have been integrated
into Windows. The following part of the book deals with differences and similarities between the two.
An important element of Unix is standardization. From the beginning, there were many systems in order that
each software could run under all systems. System calls were standardized (e.g. opening a file, printing a
character to the console). Softwares containing system calls complying with the standards can run under
standard Unix operating systems. The standard was named POSIX in 1988. Many organisations dealt with
standardization, so the proprietor of the UNIX brand name changed many times. Proprietors were X/Open, OSF,
The Open Group. Nowadays, from these groups, the Austin Common Standards Revision Group was founded. It
has more than 500 members, coming from the industrial, governmental sector and from the open-source
community. A system can be considered as Unix if it has been standardized at The Open Group of this
organisation (of course by paying its costs). For example, Mac OS X, HP-UX, Sun Solaris and IBM AIX can be
considered as Unix. BSD is not Unix in spite of the fact that standard system functions (Posix - IEEE-1003.1)
are tested under BSD.
316
Created by XMLmind XSL-FO Converter.
Developing open-source systems
educational purposes for PCs of the 80's (with a 8086 and 80286 CPU). Microsoft Windows was not too much
successful (rapid) either for these CPUs. At that time, as a result of a great development, Intel launched the
80386 CPU with 32-bit memory addressing and real protected mode. This CPU was able to support multi-task
operating systems.
A Finnish student, Linus Torvalds, dealt with the memory management of the 80386 CPU. He published the
developments carried out on a mailing list on the Internet, some people helped him, and suddenly the kernel of a
Unix-like operating system was born. This was named Linux, in a way that was similar to the transformation of
UNICS to UNIX. It was created with a GNU GPL license and was distributed at that time (in the middle and
late 90's) on the Internet that was available for most people. Linux was also chosen as an operating system for
many servers because it achieved sometimes a higher performance on a relatively cheap PC, than standardized
Unix systems on one or two times more expensive mainframe computers. It should be noted that Linux cannot
be considered as UNIX. Although Linux complies with Posix standards, it did not pay for a license, so, (just
like BSD) cannot be considered as Unix only as "Unix-like". Strictly speaking, Linux means only the kernel but
the kernel itself is non-functional. In Linux, the C compiler is the already mentioned GCC, most of the utility
software come from the GNU project, so Linux is named GNU/Linux at the request of the GNU project.
According to its definition, a Linux kernel "is written in a way that it could be compiled with GCC”.
• it is not part of the operating system, it is just an application. Unix works without it
• network-transparent: an Internet (TCP/IP) connection is sufficient between the running application and its
graphical display.
• it does not have a surface itself, it consists of a graphical desktop and a mouse cursor. In order to be used, it
needs the Window Manager (WM) software that handles windows, icons and launches the applications that
are behind the icons.
• there is an open-source version to be used under GPL operating systems. This can be found on the X.org
website if we want to compile it. It does not contain graphical card drivers; these can be downloaded from the
xfree86.org website. Linux distributions contain xfree86 and some of them can be installed graphically.
317
Created by XMLmind XSL-FO Converter.
Developing open-source systems
Let's see the following and very simple example code which is typed with a text editor and saved in a file named
x.c:
#include <stdio.h>
int main(void) {
int i;
i=0;
i++;
printf("%i\n",i);
return 0;
}
This code is first processed by the preprocessor, which resolves all #include and #define directives: it
replaces #define directives (this code does not require this step) and at the #include directive, it copies the
needed header. We chose the simplest header to print a text on the screen. The preprocessed C source stored is
x.i file is the following:
# 1 "x.c"
# 1 "<built-in>"
# 1 "<command line>"
# 1 "x.c"
# 1 "/usr/include/stdio.h" 1 3 4
# 40 "/usr/include/stdio.h" 3 4
# 1 "/usr/include/sys/cdefs.h" 1 3 4
# 59 "/usr/include/sys/cdefs.h" 3 4
# 1 "/usr/include/machine/cdefs.h" 1 3 4
# 60 "/usr/include/sys/cdefs.h" 2 3 4
# 1 "/usr/include/sys/cdefs_elf.h" 1 3 4
318
Created by XMLmind XSL-FO Converter.
Developing open-source systems
# 62 "/usr/include/sys/cdefs.h" 2 3 4
# 41 "/usr/include/stdio.h" 2 3 4
# 1 "/usr/include/sys/featuretest.h" 1 3 4
# 42 "/usr/include/stdio.h" 2 3 4
# 1 "/usr/include/sys/ansi.h" 1 3 4
# 35 "/usr/include/sys/ansi.h" 3 4
# 1 "/usr/include/machine/int_types.h" 1 3 4
# 47 "/usr/include/machine/int_types.h" 3 4
typedef signed char __int8_t;
typedef unsigned char __uint8_t;
typedef short int __int16_t;
typedef unsigned short int __uint16_t;
typedef int __int32_t;
typedef unsigned int __uint32_t;
typedef long int __int64_t;
typedef unsigned long int __uint64_t;
typedef long __intptr_t;
typedef unsigned long __uintptr_t;
# 36 "/usr/include/sys/ansi.h" 2 3 4
typedef char * __caddr_t;
typedef __uint32_t __gid_t;
typedef __uint32_t __in_addr_t;
typedef __uint16_t __in_port_t;
typedef __uint32_t __mode_t;
typedef __int64_t __off_t;
typedef __int32_t __pid_t;
typedef __uint8_t __sa_family_t;
typedef unsigned int __socklen_t;
typedef __uint32_t __uid_t;
typedef __uint64_t __fsblkcnt_t;
typedef __uint64_t __fsfilcnt_t;
# 43 "/usr/include/stdio.h" 2 3 4
# 1 "/usr/include/machine/ansi.h" 1 3 4
# 75 "/usr/include/machine/ansi.h" 3 4
typedef union {
__int64_t __mbstateL;
char __mbstate8[128];
} __mbstate_t;
# 45 "/usr/include/stdio.h" 2 3 4
typedef unsigned long size_t;
# 1 "/usr/include/sys/null.h" 1 3 4
# 51 "/usr/include/stdio.h" 2 3 4
typedef __off_t fpos_t;
# 74 "/usr/include/stdio.h" 3 4
struct __sbuf {
unsigned char *_base;
int _size;
};
# 105 "/usr/include/stdio.h" 3 4
typedef struct __sFILE {
unsigned char *_p;
int _r;
int _w;
unsigned short _flags;
short _file;
struct __sbuf _bf;
int _lbfsize;
void *_cookie;
int (*_close)(void *);
int (*_read) (void *, char *, int);
fpos_t (*_seek) (void *, fpos_t, int);
int (*_write)(void *, const char *, int);
struct __sbuf _ext;
unsigned char *_up;
int _ur;
unsigned char _ubuf[3];
unsigned char _nbuf[1];
struct __sbuf _lb;
int _blksize;
fpos_t _offset;
} FILE;
extern FILE __sF[];
319
Created by XMLmind XSL-FO Converter.
Developing open-source systems
# 214 "/usr/include/stdio.h" 3 4
void clearerr(FILE *);
int fclose(FILE *);
int feof(FILE *);
int ferror(FILE *);
int fflush(FILE *);
int fgetc(FILE *);
int fgetpos(FILE * __restrict, fpos_t * __restrict);
char *fgets(char * __restrict, int, FILE * __restrict);
FILE *fopen(const char * __restrict , const char * __restrict);
int fprintf(FILE * __restrict , const char * __restrict, ...)
__attribute__((__format__(__printf__, 2, 3)));
int fputc(int, FILE *);
int fputs(const char * __restrict, FILE * __restrict);
size_t fread(void * __restrict, size_t, size_t, FILE * __restrict);
FILE *freopen(const char * __restrict, const char * __restrict,
FILE * __restrict);
int fscanf(FILE * __restrict, const char * __restrict, ...)
__attribute__((__format__(__scanf__, 2, 3)));
int fseek(FILE *, long, int);
int fsetpos(FILE *, const fpos_t *);
long ftell(FILE *);
size_t fwrite(const void * __restrict, size_t, size_t, FILE * __restrict);
int getc(FILE *);
int getchar(void);
void perror(const char *);
int printf(const char * __restrict, ...)
__attribute__((__format__(__printf__, 1, 2)));
int putc(int, FILE *);
int putchar(int);
int puts(const char *);
int remove(const char *);
void rewind(FILE *);
int scanf(const char * __restrict, ...)
__attribute__((__format__(__scanf__, 1, 2)));
void setbuf(FILE * __restrict, char * __restrict);
int setvbuf(FILE * __restrict, char * __restrict, int, size_t);
int sscanf(const char * __restrict, const char * __restrict, ...)
__attribute__((__format__(__scanf__, 2, 3)));
FILE *tmpfile(void);
int ungetc(int, FILE *);
int vfprintf(FILE * __restrict, const char * __restrict, __builtin_va_list)
__attribute__((__format__(__printf__, 2, 0)));
int vprintf(const char * __restrict, __builtin_va_list)
__attribute__((__format__(__printf__, 1, 0)));
char *gets(char *);
int sprintf(char * __restrict, const char * __restrict, ...)
__attribute__((__format__(__printf__, 2, 3)));
char *tmpnam(char *);
int vsprintf(char * __restrict, const char * __restrict,
__builtin_va_list)
__attribute__((__format__(__printf__, 2, 0)));
int rename (const char *, const char *);
# 285 "/usr/include/stdio.h" 3 4
char *ctermid(char *);
char *cuserid(char *);
FILE *fdopen(int, const char *);
int fileno(FILE *);
void flockfile(FILE *);
int ftrylockfile(FILE *);
void funlockfile(FILE *);
int getc_unlocked(FILE *);
int getchar_unlocked(void);
int putc_unlocked(int, FILE *);
int putchar_unlocked(int);
int pclose(FILE *);
FILE *popen(const char *, const char *);
# 332 "/usr/include/stdio.h" 3 4
int snprintf(char * __restrict, size_t, const char * __restrict, ...)
__attribute__((__format__(__printf__, 3, 4)));
int vsnprintf(char * __restrict, size_t, const char * __restrict,
__builtin_va_list)
320
Created by XMLmind XSL-FO Converter.
Developing open-source systems
__attribute__((__format__(__printf__, 3, 0)));
int getw(FILE *);
int putw(int, FILE *);
char *tempnam(const char *, const char *);
# 361 "/usr/include/stdio.h" 3 4
typedef __off_t off_t;
int fseeko(FILE *, __off_t, int);
__off_t ftello(FILE *);
int vscanf(const char * __restrict, __builtin_va_list)
__attribute__((__format__(__scanf__, 1, 0)));
int vfscanf(FILE * __restrict, const char * __restrict, __builtin_va_list)
__attribute__((__format__(__scanf__, 2, 0)));
int vsscanf(const char * __restrict, const char * __restrict,
__builtin_va_list)
__attribute__((__format__(__scanf__, 2, 0)));
# 398 "/usr/include/stdio.h" 3 4
int asprintf(char ** __restrict, const char * __restrict, ...)
__attribute__((__format__(__printf__, 2, 3)));
char *fgetln(FILE * __restrict, size_t * __restrict);
char *fparseln(FILE *, size_t *, size_t *, const char[3], int);
int fpurge(FILE *);
void setbuffer(FILE *, char *, int);
int setlinebuf(FILE *);
int vasprintf(char ** __restrict, const char * __restrict,
__builtin_va_list)
__attribute__((__format__(__printf__, 2, 0)));
const char *fmtcheck(const char *, const char *)
__attribute__((__format_arg__(2)));
FILE *funopen(const void *,
int (*)(void *, char *, int),
int (*)(void *, const char *, int),
fpos_t (*)(void *, fpos_t, int),
int (*)(void *));
int __srget(FILE *);
int __swbuf(int, FILE *);
static __inline int __sputc(int _c, FILE *_p) {
if (--_p->_w >= 0 || (_p->_w >= _p->_lbfsize && (char)_c != '\n'))
return (*_p->_p++ = _c);
else
return (__swbuf(_c, _p));
}
# 2 "x.c" 2
int main(void) {
int i;
i=0;
i++;
printf("%i\n",i);
return 0;
}
The code became a little bit long, we can see that an include can include several other files as well. The
preprocessed source, compiled into assembly, in the file named x.s:
.file "x.c"
.section .rodata
.LC0:
.string "%i\n"
.text
.globl main
.type main, @function
main:
.LFB3:
pushq %rbp
.LCFI0:
movq %rsp, %rbp
.LCFI1:
subq $16, %rsp
.LCFI2:
movl $0, -4(%rbp)
321
Created by XMLmind XSL-FO Converter.
Developing open-source systems
incl -4(%rbp)
movl -4(%rbp), %esi
movl $.LC0, %edi
movl $0, %eax
call printf
movl $0, %eax
leave
ret
.LFE3:
.size main, .-main
.section .eh_frame,"a",@progbits
.Lframe1:
.long .LECIE1-.LSCIE1
.LSCIE1:
.long 0x0
.byte 0x1
.string "zR"
.uleb128 0x1
.sleb128 -8
.byte 0x10
.uleb128 0x1
.byte 0x3
.byte 0xc
.uleb128 0x7
.uleb128 0x8
.byte 0x90
.uleb128 0x1
.align 8
.LECIE1:
.LSFDE1:
.long .LEFDE1-.LASFDE1
.LASFDE1:
.long .LASFDE1-.Lframe1
.long .LFB3
.long .LFE3-.LFB3
.uleb128 0x0
.byte 0x4
.long .LCFI0-.LFB3
.byte 0xe
.uleb128 0x10
.byte 0x86
.uleb128 0x2
.byte 0x4
.long .LCFI1-.LCFI0
.byte 0xd
.uleb128 0x6
.align 8
.LEFDE1:
.ident "GCC: (GNU) 4.1.3 20080704"
Here as well, we can recognise our program code: e.g. i++ has become incl -4(%rbp). The compiler has also
inserted its version number as well but only as a text. This text part remains in the executable file even after
linking.
By default (i.e. without using any specific option), these work files are deleted. Only one executable file remains
besides the C file. If no name is assigned to the executable, it will be a.out. If a name is intended to be given to
the executable file, the compiler should be executed by the option –o filename:
322
Created by XMLmind XSL-FO Converter.
Developing open-source systems
-Wall gcc –Wall x.c All warnings are printed out, e.g. a decrease
in precision during conversion or another
interpretation of the data a pointer points to.
-save-temps gcc –save-temps x.c Does not delete temporary files created
during the compilation process.
323
Created by XMLmind XSL-FO Converter.
Developing open-source systems
-Ilibrary_name gcc –I. x.c The library name is added to the path of
include files. So the includes of our
program can also be there. The option is a
capital “I” letter, as in "Include”.
-nostdinc gcc –nostdinc x.c Does not search for standard include files.
In SOC, it is the includes of the given
system that have to be used, our example
code will not work either without the option
–I.
-llibrary gcc –l. x.c The path used for linking may also be .lib
files. The option is a lower-case "l” letter,
as in „link dir”.
-time gcc –time x.c It prints out the time spent on each
compilation step under a Unix approach:
user and operating system time
consumptions.
-fverbose-asm gcc –S –fverbose-asm x.c Comments are inserted in the assembly file,
e.g. the actual line of the C source in order
that it becomes more legible.
324
Created by XMLmind XSL-FO Converter.
Developing open-source systems
created for executable programs to track changes of .o files. The program that can check these conditions is
called make. For using that program, we should create a file named Makefile in which we specify for each file
which file it is derived from and if it has to be recompiled which command should be used. Besides that, make
has further possibilities: in a Makefile, further command line options can be specified. For example what
commands should be executed when commands like make clean or make install are used. A Makefile example
for a program composed of more than one files:
tr1660: main.o libtr1660.a screen.o
gcc -o tr1660 -lcurses main.o libtr1660.a screen.o
size tr1660
main.o: main.c
gcc -c -Wall -o main.o main.c
condi.o: condi.c
gcc -c -Wall -o condi.o condi.c
clean:
rm -f *.o *.a *.so tr1660 condi *~ a.out
The name of the main program is tr1660, and is made up of 3 separate source files: main.c, libtr1660 (one
library is created from the header and the source) and screen.c. The program named condi can also be
compiled from condi.c using the library above and the whole project can be cleaned by make clean.
In the next example, let's debug the code of 2.2. szakasz - Steps of compilation with GCC: let's insert a
breakpoint in the program, let's execute the program step by step and track the value of the variable:
Let's ask for a source list if we do not remember any more what we have written.
325
Created by XMLmind XSL-FO Converter.
Developing open-source systems
(gdb) l
1 #include <stdio.h>
2
3 int main(void) {
4 int i;
5 i=0;
6 i++;
7 printf("%i\n",i);
8 return 0;
9 }
The program stopped at line 5. Let's make a step: let's execute i=0!
(gdb) n
6 i++;
326
Created by XMLmind XSL-FO Converter.
Developing open-source systems
files and includes. However, the following part will only present the functions to be mentioned during the
training because if all functions were mentioned, it would exceed size limitations of this book. The whole
system can be found on Wikipedia, in the article named "C POSIX library”. In the following parts, the described
functions will be grouped on the basis of their header files.
3.1. stdio.h
stdio.h (c++: cstdio) contains basic I/O operations.
• scanf/fscanf/sscanf: reads formatted data from the standard input/a file/a string. See format settings in 2.
szakasz - Basic data types, variables and constants
• printf/fprintf/sprintf/snprintf: writes formatted data to the standard output/a file/a string/a string of
given length. With snprintf() cannot occur buffer overrun error.
• fseek : sets the current file position, moves the file pointer to a specific location
• rewind: moves the file position indicator to the starting position of the file
• perror: printing a text and an error message into the standard error file
• tmpfile: creates a temporary file with a unique name and returns the pointer to that file
• SEEK_CUR: moves the file pointer to a position relative to the current file position
• SEEK_END: moves the file pointer to a position relative to the end of the file
327
Created by XMLmind XSL-FO Converter.
Developing open-source systems
• SEEK_SET: moves the file pointer to a position relative to the beginning of the file
3.2. math.h
The file named math.h (c++: cmath) contains mathematical functions and constants
• div/ldiv: the quotient and remainder of integer division into a structure of type div_t
• pow: raises a number to a given power (there is no power operator in C++, only a function)
• ceil: returns the integer that is not bigger than its argument (rounds down)
• floor: returns the integer that is not smaller than its argument (rounds up)
3.3. stdlib.h
stdlib.h (c++: cstdlib) contains other, typical and frequently used functions needed in Unix: string-numeric
conversion, generating random numbers, memory allocation, process control.
• srand: sets the beginning value of the random number generator. It is normally initialised by system time
• calloc: memory allocation and initializes all bytes of the allocated memory block to zero
328
Created by XMLmind XSL-FO Converter.
Developing open-source systems
• atexit: registers a function that will be called when the program is exited
• getenv: accesses the environment variables of the program. These are in the format name=value
• system: calls the command processor of the operating system with the command specified in its argument. It
can be used to run external commands.
3.4. time.h
The header time.h (c++: ctime) contains data structures and functions necessary to handle time. A time of
Unix type is an integer number representing the seconds elapsed since the 1st of January 1970 midnight, in UTC
(formerly called GMT) in order that computers in different time zones would communicate well with each other.
The current computer has to compute the corresponding local time with the help of its current time zone. Using
32-bit values for storing time seemed to be a good idea when Unix system was planned but nowadays it should
be taken into consideration that the 32-bit time cannot be used for dates after the 18th of January 2038.
Nowadays, time_t has become 64-bit, which can be used for 293 billion years in the future.
• clock: returns the number of CPU ticks from the beginning of the execution of the current program (as
process)
• ctime: converts the time (as local time) that can be found in a tm structure into a string
• strftime: like asctime, but the format can be specified (e.g. yyyy-mm-dd hh:mm:ss)
• tm: time data type. Members: tm_sec seconds, tm_min minutes, tm_hour hours, tm_mday day of the month,
tm_mon month 0:january, 11:december, tm_year: years since 1900, tm_wday: day of the week: 0:sunday,
tm_yday: days since 1st January, tm_isdst: daylight saving active flag
• time_t: time of integer type, number of seconds since 1st January 1970
3.5. stdarg.h
stdarg.h (c++: cstdarg) contains types and macros that is needed to write functions with varying number of
arguments. Some of these functions (like printf) are treated in 2. szakasz - How to use functions on a more
professional level?.
3.6. string.h
329
Created by XMLmind XSL-FO Converter.
Developing open-source systems
The headers in string.h (c++: cstring) contains functions and constants handling "C style" strings, ending
with 0 and composed of characters of 1 byte. Manipulation of strings and contiguous memory blocks is an
important task in every programming language. There are functions that work until the closing 0 byte on a data
array and there are functions that do not take into consideration the end sign: the latter ones always have a
parameter that indicates the length of the data to be processed.
• strcat: appends a string to another, concatenation, until the zero closing byte is reached
• strcmp: compares two strings, until 0 end sign. Output: 0, if the two strings contain the same characters, a
negative number if the first argument is smaller (in ASCII) than the second, positive if the second argument is
smaller
• strncmp: compares two strings, in the provided character length. The output is the same as defined at
strcmp()
• strchr: searches for a character (char) in a string. If it finds that, it returns a pointer to that character, if not, it
returns NULL
• strstr: search for another string in a string. Returns the same as strchr()
• strtok: splits a string into tokens. Attention! It modifies the original content of the string
• strerror: converts a numeric error code returned by the operating system into a string containing the errror
message in English
• memset: fills up a memory block the pointer points to with the same characters, their quantity can be set in an
argument
• memcpy: copies a memory block a pointer points to into an area the other pointer points to, the number of
bytes to be copied is given in an argument
• memmove: like memcpy, but memory blocks can overlap (that is one of the pointers+length<=other pointer). In
the latter case, it copies them into a temporary memory space and then to the target memory space
• memchr: searches for the given character in the memory block, at a given length. If it does not find that, it
returns NULL.
3.7. dirent.h
The file named dirent.h contains data types and functions to manage directories on data storage devices.
dirent.h is not part of the C standards but POSIX system contains it. Microsoft Visual C++ does not contain
dirent.h. There, the corresponding functions of Win32 should be used instead of it ( FindFirstFile,
FindNextFile, FindClose).
• struct dirent: structure type applied to return some information about the directory. It contains the serial
number and the name of the file as an array of type char[]. It can also contain file offset, record size, name
length and file type.
• readdir: reads the next entry from an opened directory. At the end of the directory (if there are no more
entries), returns NULL
330
Created by XMLmind XSL-FO Converter.
Developing open-source systems
• readdir_r: similarly to random files, the catalogue can be read by direct addressing
• closedir: closes the directory. (The number of file and directory desciptors are finite.)
3.8. sys/stat.h
This header file contains functions and constants related to the state of files.
• struct stat: reads the state of a file into that structure. The members of the structure:
• st_mode: the access rights of the file and its type: ST_ISREG: ordinary file, ST_ISDIR: directory.
• st_nlink: the number of hard links (means other filenames ) belonging to the file
• stat: a function returning the state of a file (the name of which is given) as a pointer of type struct stat. If the
file is a symbolic link, stat returns the parameters of the original file
• fstat: like stat(), but it does not take a file name argument but the descriptor of the opened file
• lstat: like stat(), but it does not return the state of the original file but the link.
3.9. unistd.h
The file named unistd.h contains constants and functions related to program execution and management under
standard Unix-type systems. Only some of them are treated here:
331
Created by XMLmind XSL-FO Converter.
Developing open-source systems
• fork: runs a subprocess called from the parent process. The environment is copied for the subprocess, it is
launched and then the parent process waits until it ends
332
Created by XMLmind XSL-FO Converter.
VI. fejezet - Aim-specific applications
1. SOC (System On a Chip)
1.1. What is SOC?
The abbreviation "SOC” stands for "System on a Chip”, that is a system in an integrated circuit case. When it
was being designed, the important factors to be taken into consideration were the low total cost, the small size
and consumption of the unit. There is no equivocal boundary between SOCs and microcontrollers. Generally,
we mean by a SOC a device having more than 128 kb of RAM, and by a microcontroller a device having less
than that. Of course, this boundary is not evident since there are devices with more than 512k of memory
manufactured as micorcontrollers. There is another difference between the two (but this is not always true
either): on a SOC, an interactive operating system is run (in most cases a Unix-clone, e.g. Linux), whereas a
task-specific program runs on a microcontroller. This aspect has a relationship with memory size: in general, a
Unix-clone needs more than 128 kb of memory.
• RAM: operative memory. In the case of microcontrollers, the small-sized memory is placed in the case,
whereas in the case of SOCs, there is a memory bus output to which an external, in general a dynamic RAM
can be attached to the SOC. In the case of the latter solution, system designers can decide about the size of the
memory necessary for their systems. Microcontrollers are manufactured with different memory sizes in order
that the one that is the most suitable and most economic for a given application could be bought.
• ROM: read-only memory. In microcontrollers, the ROM stores the whole program and is placed in the case;
whereas, in a SOC, there is an external ROM bus and it contains either the whole operating system or only a
bootloader and the real operating system is loaded from elsewhere (storing device or network). Nowadays,
SOCs and microcontrollers are provided with a flash EEPROM in order that their software (firmware) could
be replaced by their users. It is a really comfortable solution for software development: correcting a program
error, in the era of EPPROMs could be deleted by UV-light, took at least 10 minutes because of deleting, but
nowadays it takes some milliseconds. A SOC/microcontroller may contain a code that is able to program its
own ROM memory (self-write, bootloader). When a program ready to be used and a big quantity of
microcontroller is ordered, the manufacturers produce microcontrollers with a mask-programmed ROM,
when the unchanged program is written in the ROM already during manufacturing, so they have the lowest
unit cost.
• Oscillator: the smaller microcontrollers contain an internal RC oscillator, which fulfils the role of clock
signal generators. The produced frequency is not precise enough for USB and Ethernet but is sufficient for
RS-232.
• Watchdog: is a counter independent of CPU, which has to be reset periodically from the program. In case the
program “hangs up” (or does not reset it), the watchdog in case of overflow sends a hardware reset signal to
the CPU , that is a hardware restart takes place.
• Interrupt and DMA controllers: control interrupts and data movement in the direct memory. All peripheral
devices (timers, A/D converters, GPIO) are able to ask for an interrupt under certain conditions, after which
interrupt handlers start to run.
333
Created by XMLmind XSL-FO Converter.
Aim-specific applications
• A GPIO is a free to use digital input/output.. In general it has a parallel port interface, that is the number of
wires corresponds to the length of a word of the processor and these wires can be updated and read with one
statement. All SOCs have a GPIO in order that designer of the system could be able to signal the operational
status of the device with the help of peripheral devices (e.g. with a LED). A GPIO can be programmed for
both the input and the output: a code portion tells it whether the pin gets the value of the ouput register (so
reading this we will get the value of the output register) or the content of the bit can be set in a high
impedance state (TRI-State) from the outside, from an input (e.g. from a button).
• A/D converter: analog voltage can be connected to the given pin, which can be read from the program.
Integrated converters are of 10-12 bits, and their conversion clock signal is of some megahertz. More pins can
constitute an analog input to the converter. In that case, the channel intended to be used has to be chosen with
a multiplexer. The reference voltage of an A/D converter may be the power supply voltage of the controller, a
pin able to receive analog input or an internal, programmable voltage reference.
• Comparator: pins used for analog input can be connected to a comparator circuit, on the other input of which
one can choose between the same possibilities as in the case of A/D converters. The output of a comparator is
the result of comparison of the two signals (smaller than, greater than or equal to).
• Timer/Counter: These devices contain an incremental counter. When a timer reaches a given value, it gives
an output signal or requests an interrupt. The input of counters may be a digital input pin or the signal of the
system clock after a prescaler. When the counter overflows, it requests an interrupt. The value of the counter
can be read. In general, 8- or 16-bit counters are used, the other bits have to be realized in the interrupt
handler, from software.
• PWM output: pulse-width modulated output. A square wave of a given frequency (output of a timer) the fill
factor of which can be programmed (at 0% it is only Low level, at 100% it is only High level). It can be used
to regulate the performance of DC motors and lamps in a low-loss way. In general, there are two of them in
order that the complete H-bridge could be realized (with two other digital outputs to regulate the direction of
rotation). There is no integrated power level controller, external FETs have to be adapted to them.
• Serial input/output: two-wired asynchronous communication, easy to be realized even in machine code. One
could connect to serial ports (COM) of older PCs. These ports have disappeared from PCs by now but they
are still in use in industry because of their simplicity and robustness. The power supply voltages of a SOC
(and a microcontroller) do not make possible the signal levels that comply with RS-232 standards so an
external level adapter should always be used. Similarly, the system can be adapted to RS-422 if a
transmission of a long distance is needed. The data transfer rate can range from 75 bit/sec (baud) to 115200
bit/sec. Ordinary speed is between 9600 and 19200 bit/sec. Connection to modern PCs can be carried out by a
serial extension card or an USB-serial converter, which create a virtual serial port on the PC. In the case of
SOCs (on which an operating system runs), console messages are often directed to the serial port, starting
from launching the kernel. The header of the serial port plug is led out on the integrated circuit plate
containing the SOC, without level adapting. With a level adapter and a terminal program running on a PC,
commands can be given directly to the operating system.
• USB: nowadays, nearly all computers contain USB ports and many USB-devices are avaliable. The
advantages of USB: gives power supply (5V/500mA), data transmission speed is bigger than that of a serial
port: 1.5 Mbit/sec (1.0 low speed), 12 Mbit/sec (1.0 full speed), 480 Mbit/sec (2.0 high speed), 5 Gbit/sec (3.0
superspeed) and that it can be plugged into the PC any time, which runs its driver automatically. USB
communication can be realized in many ways: PC may be the master and SOC the USB peripheral devices, or
a SOC may be the master (USB Host) and a USB-stick or an external HDD, webcam may be the peripheral
device. The way how a peripheral device is realized by a SOC can also be various: in general, they use one of
the levels of the USB stack, for example CDC, which is detected as a serial port in the PC, therefore it can
even communicate with programs that are able to handle serial ports without any drivers or program library.
Communication can also be realized with a HID (human interface device) protocol and open-source libusb. In
the latter cases, the necessary programs have to be installed on the PC.
• Ethernet: local network with a speed of 10/100/1000 Mbit/sec. In general, it is not integrated in a SOC
because it requires an external transformer and transceiver but a SOC should be prepared to handle that with
a MII port of the necessary bandwidth. A SOC equipped with Ethernet is generally not managed with native
Ethernet protocol but with TCP/IP. Therefore a SOC can be connected to the Internet, so there will not be any
problems with distance in communication. Above the TCP/IP level, there may be applications with standard
output: for example a web server. In the latter case, communication can be established with a SOC through a
334
Created by XMLmind XSL-FO Converter.
Aim-specific applications
web browser on the PC (or on a cellphone). A SOC may typically be used as a wide-band router, which
contains an Ethernet port for Internet connection (a DHCP or PPPoE client, logged in a service provider, run
on that port) and a 4-port Ethernet switch. Through these ports, DHCP and other servers provide the
connection of the computers. Configuring takes place on a web interface, with the help of an integrated web
server. The operating system is generally Linux, the EEPROM and the RAM are external. A serial port is
integrated on the printed circuit board, that is where kernel messages are sent to. In some devices, there is also
an USB port. Peripheral devices connected to that port (printer, external HDD) can be seen by the devices of
the network.
• I2C: two-wired (SDA – serial data line, SCL – serial clock line) serial communication developed by Philips,
where more slave devices can be connected to a master device, thanks to the open collector outputs and pull-
up resistors. A SOC is able to function both as a master and as a slave but it is most frequently used as a
master. With I2C, different peripheral devices can be connected to a SOC: for example an EEPROM, a
thermometer, an accelerometer, a volume control, a tv-tuner. The speed of the clock signal of I2C is max. 100
kHz, and 400 kHz in the case of newer devices. Each device has a 7-bit address, therefore 127 devices can be
used on the bus. Intel applies this protocol on some of its motherboards, under the name smbus.
Communication is always initiated by the master, and at the end of this, the addressed device sends an
acknowledge ("ACK”) signal, so its presence can be detected.
• SPI: serial peripheral interface. The SPI bus uses two data lines: one from the master to the slave, the other in
the opposite direction, therefore communication may be full duplex. There is only one clock signal: from the
master to the slave. It was designed as a point-to-point connection but more slaves can be connected to the
bus with the help of the "Slave Select” signal, and among slaves only one has to be activated. The standards
do not mention a maximal speed; information can be obtained from the technical specifications sheet of the
given manufacturer. However, this speed is in the order of Mbit/seconds, which is more than at I 2C.
• RTCC: real-time clock with a separate source of power. System time is updated even after SOC is turned off.
• CAN bus interface: hardware and software protocol mainly used in automotive industry, for the
communication between the central computer (ECU or eventually a SOC) and sensors and actuators. Some
industrial and health care devices also use that.
• Video display device: SOCs, integrated in media players, contain an mpeg-2 and h.264 video decoder,
nowadays with a HDMI output.
Now let's see two typical block diagrams: that of an omniscient microcontroller (source: Microchip, type:
PIC32MX family), and that of a SOC designed for a media player device (source: Realtek, type: RTD1073)
Realtek is less documented, because there only the external connections were mentioned.
335
Created by XMLmind XSL-FO Converter.
Aim-specific applications
336
Created by XMLmind XSL-FO Converter.
Aim-specific applications
In the case of microcontrollers, the situation is different. They are devices, produced in large quantities, without
software and with general purposes, making it possible for users to write a program appropriate for the task. The
language most frequently applied is K&R C. (Running a C++ code would require too much memory.) The
smaller, 8-bit microcontrollers can be programmed in machine code as well. The advantage of the machine code
is that it results in a small, efficient and fast program. Its disadvantage is that the code (and the knowledge about
the instruction set) is not portable. Microcontroller manufacturers provide development tools in all cases:
starting from the free compilers, intended to be used for an educational purpose (but already working and
downloadable from the website of the manufacturer) to the professional development environments intended for
companies. Other microcontrollers are supported by GCC as well. For these, the manufacturers provide only
header files and front-ends. Since compilers compile files in C into executable ROM files as command line
compilers, Visual Studio can also be used as a front-end when it is adequately set. The code ready to be used can
337
Created by XMLmind XSL-FO Converter.
Aim-specific applications
be loaded into the microcontroller with JTAG or with a programmer of the given microcontroller. In general,
contrary to the costly JTAG, these programmers can be bought at a price near 50$ and the Internet is full of
circuit diagrams which may be used to get a useful solution for 1 or 2$.
In the following parts, the devices of the two largest microcontroller manufacturers, Atmel and Microchip, are
presented. The two manufacturers produce more or less the same products: it's a matter of taste which one to
choose. Atmel mostly aims at professional users, and this is reflected in the price, too. Microchip produces for
beginners as well: a microcontroller with DIP socket to be soldered at home can be bought in small quantities as
well (even one) from an official distributor in Hungary. Both manufacturer produce so-called starter-kits which
contain a ready test panel with a microcontroller, input and output peripheral devices (in the cheaper version
these are LEDs and buttons, in the more expensive version these are DC motors and touch screens), a
programmer and demo-programs ready to be compiled and executed.
338
Created by XMLmind XSL-FO Converter.
Aim-specific applications
{ unsigned char i;
// set PORTD for output
DDRB = 0xFF;
34: 8f ef ldi r24, 0xFF ; 255
36: 87 bb out 0x17, r24 ; 23
PORTB = 0b00000001;
38: 81 e0 ldi r24, 0x01 ; 1
3a: 88 bb out 0x18, r24 ; 24
The EEPROM content (led.eep) is now ready, and this can now be burnt into the ATTiny2313 with the
avrdude programming utility and with an stk600 (200$) or with an AVRISP (40$) hardware.
339
Created by XMLmind XSL-FO Converter.
Aim-specific applications
Atmel also made its own IDE to which they provide many program libraries - this is called AVR Studio. It
contains an integrated C compiler, but the actual Internet using society use the GCC compiler, so the AVR
Studio can be configured in a way that it would work with the GCC compiler that can be found in WinAVR.
The code portion on VI.4. ábra - The MPLAB IDE can be burnt into a PIC18f4550 with a PicKit2 (original:
40$, clone: 25$) and after that communication with the hardware is possible through USB.
The problems arising during the development of MPLAB IDE (dependency on Windows) are attempted to be
solved in the new MPLAB-X. The NetBeans-based environment runs on both Linux and MacOS-X. This
contains the automatic code completion functionality of the Visual Studio and the function definition finder.
340
Created by XMLmind XSL-FO Converter.
Aim-specific applications
3.1. CORBA
341
Created by XMLmind XSL-FO Converter.
Aim-specific applications
CORBA is a standardized object environment defined by OMG (Object Management Group). Its first version
appeared in 1990 with which applications written in different languages and/or running on separate computers
can work together. This application can use methods on the remote machines in the same way as on the local
machines. An important element of the standard is interface definition language (IDL), which is independent of
the actually used compiler (that may compile from C, C++, Java, Cobol and from some other languages).
CORBA compilers make function headers (skeletons) for the objects written in IDL for the given language.
These skeletons have to be implemented for the given language by programmers: this process is called
"mapping". The objects made during this process are called "adapters". Mapping is easy in Java because of the
strong object-oriented properties of that language. In C++, a little bit more work has to be carried out to achieve
the same: complex data structures have to be used and these may sometimes become mixed with the types of
Standard Template Library (STL). The CORBA implementation contains an IDL compiler that compiles to the
used language (e.g. C++). An executable program can be created with an ordinary compiler and linker, of course
by defining functions compiled from IDL (by "dressing" the skeletons) in the given language. Besides that, it
contains native communication components which carry out communication on the Internet on the socket level
in a way that is transparent to users. The VI.5. ábra - The structure of CORBA demonstrates the access of an
object operating on a server from a client.
MICO: www.mico.org contains only Unix API calls, written in C++, developed by the University of Frankfurt.
Source files are stored on SourceForge, from where they can be downloaded
JacORB: www.jacorb.org, ORB written in Java, with an LGPL license. Needs JDK 1.6 to function.
TAO: www.ociweb.com, developed by the University of Washington, it has a free and a commercial version.
Can be used both under Unix and Windows and there is also a version that runs under a real-time operating
system.
ORBiT: orbit-resource.sourceforge.net can be used in basic C and Perl, an open-source CORBA 2.4
implementation. Since it was developed for GNOME system, most Linux distributions contain it. It has a
version running under Win32.
The following example carries out a "remote" calculation: the client determines the operation, the server carries
it out and then the client prints out the result. When tested, both the server-side and the client-side program run
on the same computer. The IDL file is as simple as possible, it contains an interface with two functions:
// Calculator interface: calculator.idl
//
342
Created by XMLmind XSL-FO Converter.
Aim-specific applications
interface Calculator
{
double add(in double number1, in double number2);
double sub(in double number1, in double number2);
};
Now the skeleton (calculator-skel.c), the stub that can be executed on the client ( calculator-stubs.c)
and the headers (calculator.h) are generated. The header file contains a CORBA-compatible class (basic C
with struct functions):
typedef struct {
void *_private;
CORBA_double (*add)(PortableServer_Servant _servant, const CORBA_double number1, const
CORBA_double number2, CORBA_Environment *ev);
CORBA_double (*sub)(PortableServer_Servant _servant, const CORBA_double number1, const
CORBA_double number2, CORBA_Environment *ev);
} POA_Calculator__epv;
return retval;
}
static CORBA_double
impl_Calculator_sub(impl_POA_Calculator * servant,
const CORBA_double number1,
const CORBA_double number2, CORBA_Environment * ev)
{
CORBA_double retval;
return retval;
}
343
Created by XMLmind XSL-FO Converter.
Aim-specific applications
return ref;
}
The stub does not have to be modified on the client, it is only the main() function that has to be written:
static
void
client_run (Calculator service,
CORBA_Environment *ev)
{
CORBA_double result=0.0;
result = Calculator_add(service, 1.0, 2.0, ev);
g_print("Result: 1.0 + 2.0 = %2.0f\n", result);
}
This is called from the main() function of the client and the server does the calculation:
./calculator-server the server is launched in the background, and it waits for the connection of the client
1.000000 + 2.000000
The ICE distribution contains everything the environment needs: devices, APIs, libraries for the creation of
object-oriented client-server applications. It supports the compatibility between architectures and languages: a
client and a server can run under a different platform and the two may be written in other languages. The
descriptor language in which an ICE object is defined is called Slice (Specification Language for Ice). It
contains interfaces, operations and data types defined commonly on both the server and a client. These Slice
files are compiled by a compiler to API calls, to code portions in different languages, which can be compiled on
the currently used system and programming language, but of course only after skeletons are filled in (i.e. the
portion between {} of functions are written). Compilation to a given language is called "language mapping".
The language used in the Ice system can be C++, Java, C#, Python and Objective-C. The client-side can use
PHP as well. The structure of programs using ICE can be seen in the VI.6. ábra - The structure of ICE programs.
344
Created by XMLmind XSL-FO Converter.
Aim-specific applications
ICE uses its own protocol over IP: this may be TCP or UDP. If objects contain important data, SSL can also be
used between servers and clients. Besides ordinary remote objects, it offers extra services, like IceGrid, in which
more servers can contain the same object for the purpose of load-balancing; or IceStorm, which share events
between publishers and subscribers interested in those events.
In the following simplified example, the ICE server-side of an industrial robot is created. The clients connected
to the robot are able to move the robot and to obtain the actual coordinates. The slice definition file is the
following (KukaRobot.ice – it can only be opened as a text file in the Visual Studio):
module CyberLab
{
struct e6pos
{
float x; // x,y,z are millimeters
float y;
float z;
float a; // a,b,c angles in degrees
float b;
float c;
};
interface Robot6Dof
{
void GotoHome(); // home position defined in robot program
e6pos GetPosition(); // where are you ?
void GotoPosition(e6pos p); // go to the position
};
};
When this file is compiled with slice2cpp.exe, 2 files are created: KukaRobot.cpp and KukaRobot.h. This is
the so-called skeleton, in which the defined methods figure as abstract virtual member functions:
virtual void GotoPosition(const ::CyberLab::e6pos&, const ::Ice::Current& =
::Ice::Current()) = 0;
345
Created by XMLmind XSL-FO Converter.
Aim-specific applications
The last function of the interface can easily be recognized. In order to instantiate the class, we have to define
this function. A new class (the implementation class: Robot6DofI) is derived from the class named Robot6Dof.
This new class contains the implementation of these member functions – prototypes:
class Robot6DofI : public Robot6Dof {
public:
virtual void GotoHome(const Current& c);
virtual e6pos GetPosition(const Current& c);
virtual void GotoPosition(const e6pos& e6, const Current& c);
};
and defintions:
void Robot6DofI::GotoHome(const Current& c)
{
Send_command_to_robot(CMD_ROBOT_HOME);
}
When these functions were defined, we used the already existing communication functions that sent instructions
through RS-232 with the robot. The last thing to be done is writing the main program and creating the
communication adapter (on the basis of the ICE demo):
int _tmain(int argc, _TCHAR* argv[])
{
int status = 0;
std::string s1="KukaRobotAdapter";
std::string s2="default -p 10000";
Ice::CommunicatorPtr ic;
Ice::InitializationData id;
argc=0;
try
{
ic = Ice::initialize(id);
Ice::ObjectAdapterPtr adapter=ic->createObjectAdapterWithEndpoints
(s1,s2);
Ice::ObjectPtr object = new Robot6DofI;
adapter->add(object,ic->stringToIdentity("KukaRobot"));
adapter->activate();
ic->waitForShutdown();
}
catch (const Ice::Exception& e)
{
cerr << e << endl;
status = 1;
}
catch (const char* msg)
{
cerr << msg << endl;
status = 1;
}
if (ic)
{
try
{
ic->destroy();
}
346
Created by XMLmind XSL-FO Converter.
Aim-specific applications
Do not forget about setting the ICE include and library directories in the Settings of the project because it is not
able to compile and link the program without them.
When compilation has taken place correctly, the thing left to be done is making available the DLLs belonging to
the ICE while the program runs and then starting the server. When the file named KukaRobot.ice is sent to the
foreign partner, the latter creates the client (in a language, e.g. C#) and can control the robot remotely.
347
Created by XMLmind XSL-FO Converter.
A. függelék - Appendix – Standard
C++ summary tables
1. ASCII code table
CHA DEC HEX CHA DEC HEX CHA DEC HEX CHA DEC HEX
R R R R
[NU 0 00 32 20 @ 64 40 ` 96 60
L]
[SO 1 01 ! 33 21 A 65 41 a 97 61
H]
[STX 2 02 " 34 22 B 66 42 b 98 62
]
[ET 3 03 # 35 23 C 67 43 c 99 63
X]
[EO 4 04 $ 36 24 D 68 44 d 100 64
T]
[EN 5 05 % 37 25 E 69 45 e 101 65
Q]
[BS] 8 08 ( 40 28 H 72 48 h 104 68
[HT] 9 09 ) 41 29 I 73 49 i 105 69
[LF] 10 0A * 42 2A J 74 4A j 106 6A
[VT] 11 0B + 43 2B K 75 4B k 107 6B
[FF] 12 0C , 44 2C L 76 4C l 108 6C
[CR] 13 0D - 45 2D M 77 4D m 109 6D
[SO] 14 0E . 46 2E N 78 4E n 110 6E
[SI] 15 0F / 47 2F O 79 4F o 111 6F
[DL 16 10 0 48 30 P 80 50 p 112 70
E]
348
Created by XMLmind XSL-FO Converter.
Appendix – Standard C++ summary
tables
CHA DEC HEX CHA DEC HEX CHA DEC HEX CHA DEC HEX
R R R R
[DC1 17 11 1 49 31 Q 81 51 q 113 71
]
[DC2 18 12 2 50 32 R 82 52 r 114 72
]
[DC3 19 13 3 51 33 S 83 53 s 115 73
]
[DC4 20 14 4 52 34 T 84 54 t 116 74
]
[NA 21 15 5 53 35 U 85 55 u 117 75
K]
[SY 22 16 6 54 36 V 86 56 v 118 76
N]
[ET 23 17 7 55 37 W 87 57 w 119 77
B]
[CA 24 18 8 56 38 X 88 58 x 120 78
N]
[EM] 25 19 9 57 39 Y 89 59 y 121 79
[SUB 26 1A : 58 3A Z 90 5A z 122 7A
]
[ESC 27 1B ; 59 3B [ 91 5B { 123 7B
]
[GS] 29 1D = 61 3D ] 93 5D } 125 7D
349
Created by XMLmind XSL-FO Converter.
Appendix – Standard C++ summary
tables
do new template
3. Escape characters
backspace BS '\b'
350
Created by XMLmind XSL-FO Converter.
Appendix – Standard C++ summary
tables
backslash \ '\\'
(bytes) (digits)
char -128..127 1
wchar_t 0..65535 2
int -2147483648..2147483647 4
short -32768..32767 2
long -2147483648..2147483647 4
351
Created by XMLmind XSL-FO Converter.
Appendix – Standard C++ summary
tables
(bytes) (digits)
float 3.4E-38..3.8E+38 4 6
double 1.7E-308..1.7E+308 8 15
5. Statements in C++
Empty statement ;
Category Directive
352
Created by XMLmind XSL-FO Converter.
Appendix – Standard C++ summary
tables
Category Directive
Predefined macros
Macro Description
[] array indexing
++ (postfix) increment
-- (postfix) decrement
353
Created by XMLmind XSL-FO Converter.
Appendix – Standard C++ summary
tables
~ bitwise negation
+ + sign (numbers)
- - sign (numbers)
++ (prefix) increment
-- (prefix) decrement
* indirection operator
354
Created by XMLmind XSL-FO Converter.
Appendix – Standard C++ summary
tables
/ division
% modulo
– subtraction
!= not equal to
*= multiplication assignment
/= division assignment
%= modulo assignment
+= addition assignment
-= subtraction assignment
355
Created by XMLmind XSL-FO Converter.
Appendix – Standard C++ summary
tables
|= bitwise OR assignment
356
Created by XMLmind XSL-FO Converter.
Appendix – Standard C++ summary
tables
Where the type real designates one of the following types: float, double or long double. The type integer
designates one of the int or long types
Properties Result
The following manipulators are used in pairs, they have their effects from the time they are set until they are
unset:
357
Created by XMLmind XSL-FO Converter.
Appendix – Standard C++ summary
tables
ws I Extract whitespaces.
The effects of the following manipulators can be modified by those provided in the same group:
Alignment
left O Left-aligned
Numeral system
358
Created by XMLmind XSL-FO Converter.
Appendix – Standard C++ summary
tables
Printing out real numbers (if these manipulator flags are not set, the compiler decides their format)
setw affects only the data element that immediately follows him, and the others affect all subsequent data
elements for the output stream.
Diagnostic library
359
Created by XMLmind XSL-FO Converter.
Appendix – Standard C++ summary
tables
String library
Algorithms library
360
Created by XMLmind XSL-FO Converter.
Appendix – Standard C++ summary
tables
Numeric library
The underscored names are the header files of the C++11 standard.
361
Created by XMLmind XSL-FO Converter.
B. függelék - Appendix – C++/CLI
summary tables
1. C++/CLI reserved words
362
Created by XMLmind XSL-FO Converter.
Appendix – C++/CLI summary
tables
Char wchar_t
Boolean bool
SByte char
Int16 short
Double double
Single float
363
Created by XMLmind XSL-FO Converter.