We're always interested in getting feedback. E-mail us if you like this guide, if you think that important material is omitted, if you encounter errors in the code examples or in the documentation, if you find any typos, or generally just if you feel like e-mailing. Mail to Karel Kubat (karel@icce.rug.nl
) or use an e-mail form . Please state the concerned document version, found in the title. If you're interested in a printable PostScript copy, use the form . or better yet, pick up your own copy viaftp
at ftp.icce.rug.nl/pub/http ,
In this chapter the usage of C++ is further explored. The possibility to
declare functions in struct
s is further illustrated using examples. The
concept of a class
is introduced.
Before we continue with the `real' object-oriented approach to programming, we first introduce further extensions of C++.
In analogy to C, C++ defines standard input- and output streams which are opened when a program is executed. The streams are:
cout
, analogous to stdout
,
cin
, analogous to stdin
,
cerr
, analogous to stderr
.
Syntactically these streams are not used with functions: instead, data are
read from the streams or written to them using the operators <<
and
>>
. This is illustrated in the example below:
#include <iostream.h>
int main ()
{
int
ival;
char
sval [30];
cout << "Enter a number:" << '\n';
cin >> ival;
cout << "And now a string:" << '\n';
cin >> sval;
cout << "The number is: " << ival
<< "\nAnd the string is: " << sval
<< '\n';
return (0);
}
This program reads a number and a string from the cin
stream (usually the
keyboard) and prints these data to cout
. Concerning the streams and their
usage we remark the following:
iostream.h
.
cout
, cin
and cerr
are in fact `objects'
of a given class (more on classes later) which processes the input and
output of a program. Note that the terminology `object' means here: the
set of data and functions which define the item in question.
cin
reads data and copies the information to
variables (e.g., ival
in the above example) using the right-shift
operator >>
. We will describe later how operators in C++
can perform quite other actions than what they are defined to do by the
language grammar, such as is the case here.
cin
, cout
and cerr
(which are >>
and <<
) also manipulate variables of
different types. In the above example cout
<<
ival
stands for the printing of an integer value. The actions of the operators
therefore depend on the type of supplied variables.
The streams cin
, cout
and cerr
are not part of C++ grammar
sec, as defined in the compiler which parses source files. The streams
are part of the definitions in the header file iostream.h
. This is
comparable to the fact that functions as printf()
are not part of the
C grammar, but were originally written by people who considered such
functions handy and collected them in a run-time library.
Whether a program uses the old-style functions like printf()
and
scanf()
or whether it employs the new-style streams is a matter of taste.
Both styles can even be mixed. A number of advantages and disadvantages is
given below:
printf()
and
scanf()
, the usage of the operators <<
and
>>
with their respective streams is more type-safe.
The format strings which are used with printf()
and
scanf()
can define wrong format specifiers for their arguments,
for which the compiler (usually) doesn't warn. In contrast, argument
checking with cin
, cout
and cerr
is performed
by the compiler.
printf()
and scanf()
, and other
functions which use format strings, in fact implement a mini-language
which is interpreted at run-time. In contrast, the C++ compiler
knows exactly which in- or output action to perform given which
argument.
The keyword const
very often occurs in C++ programs, even though it
is also part of the C grammar.
This keyword is a modifier which states that the value of a variable or of an
argument may not be modified. In the below example an attempt is made to
change the value of a variable ival
, which is not legal:
int main ()
{
int const // a constant int..
ival = 3; // initialized to 3
ival = 4; // assignment leads
// to an error message
return (0);
}
This example shows how ival
may be initialized to a given value in its
definition; attempts to change the value later (in an assignment) are not
permitted.
Variables which are declared const
can, in contrast to C, be used as
the specification of the size of an array, as in the following example:
int const
size = 20;
char
buf [size]; // 20 chars big
A further usage of the keyword const
is seen in the declaration of
pointers, e.g., in pointer-arguments. In the declaration
char const *buf;
buf
is a pointer variable, which points to char
s. Whatever is
pointed to by buf
may not be changed: the char
s are declared as
const
. The pointer buf
itself however may be changed. A statement as
*buf
=
'a'
is therefore not allowed, while buf++
is.
In the declaration
char *const buf;
buf
itself is a const
pointer which may not be changed. Whatever
char
s are pointed to by buf
may be changed at will.
Finally, the declaration
char const *const buf;
is also possible; here, neither the pointer nor what it points to may be changed.
The rule of thumb for the placement of the keyword const
is the
following: whatever occurs just prior to the keyword may not be changed.
Besides the normal declaration of variables, C++ allows `references' to be declared as synonyms for variables. A reference to a variable is like an alias; the variable name and the reference name can both be used in statements which affect the variable:
int
int_value;
int
&ref = int_value;
In the above example a variable int_value
is defined. Subsequently a
reference ref
is defined, which due to its initialization addresses the
same memory location which int_value
occupies. In the definition of
ref
, the reference operator &
indicates that ref
is not
itself an integer but a reference to one. The two statements
int_value++; // alternative 1
ref++; // alternative 2
have the same effect, as expected. At some memory location an int
value
is increased by one --- whether that location is called int_value
or
ref
does not matter.
References serve an important role in C++ as a means to pass arguments
which can be modified (`variable arguments' in Pascal-terms). E.g., in
standard C, a function which increases the value of its argument by five
but which returns nothing (void
), needs a pointer argument:
void increase (int *valp) // expects a pointer
{ // to an int
*valp += 5;
}
int main ()
{
int
x;
increase (&x) // the address of x is
return (0); // passed as argument
}
This construction can naturally also be used in C++ but the same effect can be achieved with a reference:
void increase (int &valr) // expects a reference
{ // to an int
valr += 5;
}
int main ()
{
int
x;
increase (x); // a reference to x is
return (0); // passed as argument
}
The way in which C++ compilers implements references is by internally
using pointers: in other words, references in C++ are just ordinary
pointers -- as far as the compiler is concerned. However, the programmer does
not need to know or to bother about levels of indirection.
(var
is in fact
also a pointer, but the programmer needn't know.
It can be argued whether code such as the above is clear: the statement
increase
(x)
in the main()
function suggests that not x
itself but a copy is passed. Yet the value of x
changes because of
the way increase()
is defined.
Our suggestions for the usage of references as arguments to functions are therefore the following:
void some_func (int val)
{
printf ("%d\n", val);
}
int main ()
{
int
x;
some_func (x); // a copy is passed, so
return (0); // x won't be changed
}
void increase (int *valp)
{
*valp += 5;
}
int main ()
{
int
x;
increase (&x); // a pointer is passed, so
return (0); // x might be changed
}
struct
, is passed as argument: in this case the stack usage becomes
significant when a copy of the entire structure is passed.
This is illustrated below:
struct person // some large structure
{
char
name [80],
address [90];
double
salary;
};
void printperson (person &p) // printperson expects a
{ // reference to a structure
printf ("Name: %s\n"
"Address: %s\n",
p.name, p.address);
}
int main ()
{
person
boss;
.
.
printperson (boss); // no pointer is passed,
} // so variable won't be
// altered by function
An improvement is the addition of the keyword const
:
void printperson (person const &p)
After such a declaration of the function printperson()
no ambiguity
exists.
References also can lead to extremely `ugly' code. A function can also return a reference to a variable, as in the following example:
int &func (void)
{
static int
value;
return (value);
}
This allows the following constructions:
func () = 20;
func () += func ();
It is probably superfluous to note that such constructions should not normally be used (however, there are situations where it is useful to return a reference, this is discussed later). C++ provides as far as references are concerned a powerful tool, which however can mutilate the code.
A number of differences between pointers and references is pointed out in the list below:
int &ref;
is not allowed; what would ref
refer to? An exception to this rule
are references which are declared as external
(these references are
assumed to be initialized elsewhere), references as arguments of functions
(the caller then initializes the reference), references as return types of
functions (the function determines to what the return value will refer)
and references as data members of classes (we will describe this later).
In contrast, pointers are variables by themselves. They point, whether at
something concrete or just ``at nothing''.
&
is used on a reference,
the expression yields the address of the variable to which the reference
applies. In contrast, ordinary pointers are variables themselves, so the
address of a pointer variable has nothing to do with the address of the
variable pointed to.
The first chapter described that functions can be part of struct
s (see
section
FunctionInStruct
). Such functions are called `member'
functions. This section discusses the actual definition of such functions.
The code fragment below illustrates a struct
in which data fields for a
name and address are present. A function print()
is included in the
struct
definition:
struct person
{
char
name [80],
address [80];
void
print (void);
}
The member function print()
is defined using the structure name
(person
) and the scope resolution operator (::
):
void person::print ()
{
printf ("Name: %s\n"
"Address: %s\n", name, address);
}
In the definition of this member function, the function name is preceded by
the struct
name followed by ::
. The code of the function shows how
the fields of the struct
can be addressed without using the type name: in
this example the function print()
prints a variable name
. Since
print()
is a part of the struct
person
, the variable name
implicitly refers to the same type.
The usage of this struct
could be, e.g.:
person
p;
strcpy (p.name, "Karel");
strcpy (p.address, "Rietveldlaan 37");
.
.
p.print ();
The advantage of member functions lies in the fact that the called function
can automatically address the data fields of the structure for which it was
invoked. As such, in the statement p.print()
the structure p
is the
`substrate': the variables name
and address
which are used in the
code of print()
refer to the same struct
p
.
As mentioned previously (see section Pretensions ), C++ contains special syntactical possibilities to implement data hiding. Data hiding is the ability of one program part to hide its data from other parts; thus avoiding improper addressing or name collisions of data.
C++ has two special keywords which are concerned with data hiding:
private
and public
. These keywords can be inserted in the definition
of a struct
. The keyword public
defines all subsequent fields of a
structure as accessible by all code; the keyword private
defines all
subsequent fields as only accessible by the code which is part of the
struct
(i.e., only accessible for the member functions)
(public
and private
, C++ defines the keyword protected
.
This keyword is not often used and it is left for the reader to
explore.struct
all fields are public
, unless
explicitly stated otherwise.
With this knowledge we can expand the struct
person
:
struct person
{
public:
void
setname (char const *n),
setaddress (char const *a),
print (void);
char const
*getname (void),
*getaddress (void);
private:
char
name [80],
address [80];
};
The data fields name
and address
are only accessible for the member
functions which are defined in the struct
: these are the functions
setname()
, setaddress()
etc.. This property of the data type is
given by the fact that the fields name
and address
are preceded by
the keyword private
. As an illustration consider the following code
fragment:
person
x;
x.setname ("Frank"); // ok, setname() is public
strcpy (x.name, "Knarf"); // error, name is private
The concept of data hiding is realized here in the following manner. The
actual data of a struct
person
are named only in the structure
definition. The data are accessed by the outside world by special functions,
which are also part of the definition. These member functions control all
traffic between the data fields and other parts of the program and are
therefore also called `interface' functions.
The data hiding which is thus realized is further illustrated in the following figure:
Also note that the functions setname()
and setaddress()
are declared
as having a char
const
*
argument. This means that the
functions will not alter the strings which are supplied as their arguments.
In the same vein, the functions getname()
and getaddress()
return a
char
const
*
: the caller may not modify the strings which are
pointed to by the return values.
Two examples of member functions of the struct
person
are shown
below:
void person::setname (char const *n)
{
strncpy (name, n, 79);
name [79] = '\0';
}
char const *person::getname ()
{
return ( (char const *) name );
}
In general, the power of the member functions and of the concept of data
hiding lies in the fact that the interface functions can perform special
tasks, e.g., checks for the validity of data. In the above example
setname()
copies only up to 79 characters from its argument to the data
member name
, thereby avoiding array boundary overflow.
Another example of the concept of data hiding is the following. As an
alternative to member functions which keep their data in memory (as do the
above code examples), a runtime library could be developed with interface
functions which store their data on file. The conversion of a program which
stores person
structures in memory to one that stores the data on disk
would mean the relinking of the program with a different library.
Though data hiding can be realized with structs
, more often (almost
always) classes are used instead. A class
is in principle equivalent to a
struct
except that unless specified otherwise, all members (data or
functions) are private
. As far as private
and public
are
concerned, a class
is therefore the opposite of a struct
. The
definition of a class
person
would therefore look exactly as shown
above, except for the fact that instead of the keyword struct
, class
would be used. Our typographic suggestion for class names is a capital as
first character, followed by the remainder of the name in lower case (e.g.,
Person
).
At the end of this chapter we would like to illustrate the analogy between
C and C++ as far as struct
s are concerned. In C it is not
uncommon to define several functions to process a struct
, which then
require a pointer to the struct
as one of their arguments. A fragment
of an imaginary C header file is given below:
/* definition of a struct PERSON_ */
typedef struct
{
char
name [80],
address [80];
} PERSON_;
/* some functions to manipulate PERSON_ structs */
/* initialize fields with a name and address */
extern void initialize (PERSON_ *p, char const *nm,
char const *adr);
/* print information */
extern void print (PERSON_ const *p);
/* etc.. */
In C++, the declarations of the involved functions are placed inside the
definition of the struct
or class
. The argument which denotes which
struct
is involved is no longer needed.
class Person
{
public:
void initialize (char const *nm, char const *adr);
void print (void);
// etc..
private:
char name [80], address [80];
};
The struct
argument is implicit in C++. A function call in C
like
PERSON_
x;
initialize (&x, "some name", "some address");
is in C++:
Person
x;
x.initialize ("some name", "some address");
Next Chapter, Previous Chapter
Table of contents of this chapter, General table of contents
Top of the document, Beginning of this Chapter