Don't hesitate to send in feedback: send an e-mail if you like the C++ Annotations; if you think that important material was omitted; if you find errors or typos in the text or the code examples; or if you just feel like e-mailing. Send your e-mail to Frank B. Brokken.Please state the document version you're referring to, as found in the title (in this document: 6.5.0) and please state chapter and paragraph name or number you're referring to.
All received mail is processed conscientiously, and received suggestions for improvements will usually have been processed by the time a new version of the Annotations is released. Except for the incidental case I will normally not acknowledge the receipt of suggestions for improvements. Please don't interpret this as me not appreciating your efforts.
This document offers an introduction to the C++ programming language. It is a guide for C/C++ programming courses, yearly presented by Frank at the University of Groningen. This document is not a complete C/C++ handbook, as much of the C-background of C++ is not covered. Other sources should be referred to for that (e.g., the Dutch book De programmeertaal C, Brokken and Kubat, University of Groningen, 1996) or the on-line book suggested to me by George Danchev (danchev at spnet dot net).
The reader should realize that extensive knowledge of the C programming language is actually assumed. The C++ Annotations continue where topics of the C programming language end, such as pointers, basic flow control and the construction of functions.
The version number of the C++ Annotations (currently 6.5.0) is updated when the contents of the document change. The first number is the major number, and will probably not be changed for some time: it indicates a major rewriting. The middle number is increased when new information is added to the document. The last number only indicates small changes; it is increased when, e.g., series of typos are corrected.
This document is published by the Computing Center, University of Groningen, the Netherlands under the GNU General Public License.
The C++ Annotations were typeset using the yodl formatting system.
All correspondence concerning suggestions, additions, improvements or changes to this document should be directed to the author:
Frank B. Brokken
Computing Center, University of Groningen
Nettelbosje 1,
P.O. Box 11044,
9700 CA Groningen
The Netherlands
(email: f.b.brokken@rug.nl)
In this chapter a first impression of C++ is presented. A few extensions to C are reviewed and the concepts of object based and object oriented programming (OOP) are briefly introduced.
unsigned
into size_t
where appropriate,
and explicitly mentioned int
-derived types like int16_t
. In-class
member function definitions were moved out of (below) their class definitions
as inline
defined members. A paragraphs about implementing pure virtual
member functions was added. Various bugs and compilation errors were fixed.
template
keyword to distinguish types nested
under template classes from template members. Furthermore,
Sergio Bacchi
s dot bacchi at gmail dot com
did an impressive job when translating the
Annotations into Portuguese. His translation (which may lag a distribution or
two behind the latest verstion of the Annotations) may also be retrieved
fromstatic const
data members; and the final chapter
(20) covers configurable template classes using local context
structs (replacing the previous ForEach, UnaryPredicate
and
BinaryPredicate
classes). Furthermore, the final section (covering a
C++ parser generator) now uses bisonc++, rather than the old
(and somewhat outdated) bison++ program.
.pdf
files are from now on
distributed with the C++ Annotations. Also, some sections were slightly
adapted.
try
-blocks (section 8.9);
calling conventions of static and global functions (section
10.2.1) and virtual constructors (section 14.10). The
chapter on templates was completely rewritten and split into two separate
chapters: chapter 18 discusses the syntax and use of template
functions; chapter 19 discusses template classes. Various
concrete examples were modified; new examples were included as well (chapter
20).
compare()
function in
chapter 4 contained an error, which was repaired.
iterator
types. This topic was further elaborated in chapter 20, where the
section about the construction of a reverse iterator (section
20.5) was completely rewritten. In the same chapter, a
universal text to anything convertor is discussed (section 20.6).
Also, LaTeX
, PostScript
and PDF
versions fitting the
US-letter
paper size are now available as
cplusplus
us versions: cplusplusus.latex,
cplusplusus.ps
and cplusplus.pdf
. The A4-paper size is of course
kept, and remains to be available in the cplusplus.latex, cplusplus.ps
and
cpluspl.pdf
files.
mutable
keyword (section 6.5), and after thoroughly changing the
discussion of the Fork()
abstract base class (section 20.3). All
examples should now be up-to-date with respect to the use of the std
namespace.
g++
compiler version 3.2 was
released (http://www.gnu.org). In this version extensions to
the
abstract containers (see chapter 12) like the
hash_map (see section 12.3.11) were placed in a separate
namespace,
__gnu_cxx
. This namespace should be used when using these
containers. However, this may break compilations of sources with g++
,
version 3.0. In that case, a compilation can be performed conditionally to
the 3.2 and the 3.0 compiler version, defining __gnu_cxx
for the 3.2
version. Alternatively, the
dirty trick
#define __gnu_cxx stdcan be placed just before header files in which the
__gnu_cxx
namespace is used. This might eventually result in name-collisions, and it's a
dirty trick by any standards, so please don't tell anybody I wrote this down.
fork()
system call in chapter 20. Under the
ANSI/ISO
standard many of the previously available extensions (like
procbuf
, and
vform()
) applied to streams were discontinued. Starting with version
5.1.1. ways of constructing these facilities under the ANSI/ISO standard are
discussed in the C++ Annotations. I consider the involved subject
sufficiently complex to warrant the upgrade to a new subversion.
g++
compiler version 3.00, a more
strict implementation of the
ANSI/ISO C++ standard became
available. This resulted in version 5.1.0 of the Annotations, appearing
shortly after version 5.0.0. In version 5.1.0 chapter 5 was
modified and several cosmetic changes took place (e.g., removing class
from template type parameter lists, see chapter 18). Intermediate
versions (like 5.0.0a, 5.0.0b) were not further documented, but were mere
intermediate releases while approaching version 5.1.0. Code
examples will gradually be adapted to the new release of the compiler.
In the meantime the reader should be prepared to insert
using namespace std;in many code examples, just beyond the
#include
preprocessor directives as a temporary measure to make the example accepted by the compiler.
stringstream
class, replacing the
strstream
class is now
covered too (sections 5.4.3 and 5.5.3). Actually,
the chapter on input and output was completely rewritten. Furthermore, the
operators new
and delete
are now discussed in chapter 7,
where they fit better than in a chapter on classes, where they previously were
discussed. Chapters were moved, split and reordered, so that subjects could
generally be introduced without forward references. Finally, the
html
,
PostScript and
pdf versions of the C++ Annotations now contain an
index (
sigh of relief ?) All in, considering the volume and nature of the
modifications, it seemed right to upgrade to a full major version. So here it
is.
Considering the volume of the Annotations, I'm sure there will be typos found every now and then. Please do not hesitate to send me mail containing any mistakes you find or corrections you would like to suggest.
4.4.1b
the pagesize in the LaTeX file was defined to
be din A4
. In countries where other pagesizes are standard the default
pagesize might be a better choice. In that case, remove the
a4paper,twoside
option from cplusplus.tex
(or cplusplus.yo
if you
have yodl
installed), and reconstruct the Annotations from the
TeX-file or Yodl
-files.
The Annotations mailing lists was stopped at release 4.4.1d
. From this
point on only minor modifications were expected, which are not anymore
generally announced.
At some point, I considered version 4.4.1
to be the final version of the
C++ Annotations. However, a section on special I/O functions was added to
cover unformatted I/O, and the section about the string
datatype had its
layout improved and was, due to its volume, given a chapter of its own
(chapter 4). All this eventually resulted in version 4.4.2
.
Version 4.4.1
again contains new material, and reflects the
ANSI/ISO standard (well, I try to
have it reflect the
ANSI/ISO standard). In version 4.4.1. several new
sections and chapters were added, among which a chapter about the
Standard Template Library (
STL) and
generic algorithms.
Version 4.4.0
(and subletters) was a mere construction version and was
never made available.
The version 4.3.1a
is a precursor of 4.3.2
. In 4.3.1a
most of the
typos I've received since the last update have been processed. In version
4.3.2
extra attention was paid to the syntax for
function addresses and
pointers to member functions.
The decision to upgrade from version 4.2.* to 4.3.* was made after realizing
that the lexical scanner function yylex()
can be defined in the
scanner class that is derived from yyFlexLexer
. Under this approach
the yylex()
function can access the members of the class derived from
yyFlexLexer
as well as the public and protected members of
yyFlexLexer
. The result of all this is a clean implementation of the rules
defined in the flex++
specification file.
The upgrade from version 4.1.* to 4.2.* was the result of the inclusion of section 3.3.1 about the bool data type in chapter 3. The distinction between differences between C and C++ and extensions of the C programming languages is (albeit a bit fuzzy) reflected in the introduction chapter and the chapter on first impressions of C++: The introduction chapter covers some differences between C and C++, whereas the chapter about first impressions of C++ covers some extensions of the C programming language as found in C++.
Major version 4 is a major rewrite of the previous version 3.4.14. The document was rewritten from SGML to Yodl and many new sections were added. All sections got a tune-up. The distribution basis, however, hasn't changed: see the introduction.
Modifications in versions 1.*.*, 2.*.*, and 3.*.* (replace the stars by any applicable number) were not logged.
Subreleases like 4.4.2a
etc. contain bugfixes and typographical
corrections.
C++ was originally a `pre-compiler', similar to the preprocessor of
C, which converted special constructions in its source code to plain
C. This code was then compiled by a normal C compiler. The
`pre-code', which was read by the C++ pre-compiler, was usually located
in a file with the extension .cc
, .C
or .cpp
. This file
would then be converted to a C source file with the extension .c
, which
was compiled and linked.
The nomenclature of C++ source files remains: the extensions .cc
and
.cpp
are still used. However, the preliminary work of a C++
pre-compiler is in modern compilers usually included in the actual compilation
process. Often compilers will determine the type of a source file by its
extension. This holds true for Borland's and Microsoft's C++ compilers,
which assume a C++ source for an extension .cpp
. The
Gnu
compiler
g++
, which is available on many Unix platforms, assumes for
C++ the extension .cc
.
The fact that C++ used to be compiled into C code is also visible
from the fact that C++ is a superset of C: C++ offers the full
C grammar and supports all C-library functions, and adds to this
features of its own. This makes the transition from C to
C++ quite easy. Programmers familiar with C may start
`programming in C++' by using source files having extensions .cc
or
.cpp
instead of .c
, and may then comfortably slip into all the
possibilities offered by C++. No abrupt change of habits is required.
LaTeX
. After some time, Karel rewrote the
text and converted the guide to a more suitable format and (of course) to
English in september 1994.
The first version of the guide appeared on the net in october 1994. By then it
was converted to SGML
.
Gradually new chapters were added, and the contents were modified and further improved (thanks to countless readers who sent us their comment).
The transition from major version three to major version four was realized by
Frank: again new chapters were added, and the source-document was converted
from SGML
to yodl.
The C++ Annotations are freely distributable. Be sure to read the legal notes.If you like this document, tell your friends about it. Even better, let us know by sending email to Frank.Reading the annotations beyond this point implies that you are aware of these notes and that you agree with them.
In the Internet, many useful hyperlinks exist to C++. Without even suggesting completeness (and without being checked regularly for existence: they might have died by the time you read this), the following might be worthwhile visiting:
.cc
and run
it through a C++ compiler:
sizeof
('c')
equals sizeof(int)
, 'c'
being
any ASCII character. The underlying philosophy is probably that char
s,
when passed as arguments to functions, are passed as integers
anyway. Furthermore, the C compiler handles a character constant like
'c'
as an integer constant. Hence, in C, the function calls
putchar(10);and
putchar('\n');are synonyms.
In contrast, in C++, sizeof('c')
is always 1 (but see also section
3.3.2), while an int
is still an int
. As we shall see later (see
section 2.5.11), the two function calls
somefunc(10);and
somefunc('\n');may be handled by quite separate functions: C++ distinguishes functions not only by their names, but also by their argument types, which are different in these two calls: one call using an
int
argument, the other
one using a char
.
extern void func();in C means that a function
func()
exists, which returns no
value. The declaration doesn't specify which arguments (if any) the function
takes.
In contrast, such a declaration in C++ means that the function
func()
takes no arguments at all: passing arguments to it results in a
compile-time error.
2.2.3.1: C++ under MS-Windows
For
MS-Windows
Cygnus
(http://sources.redhat.com/cygwin) provides the foundation
for installing the Windows port of the
Gnu
g++
compiler.
When visiting the above URL to obtain a
free g++
compiler, click on install now
. This will download the file
setup.exe
,
which can be run to install cygwin
. The software to be installed can be
downloaded by setup.exe
from the internet. There are alternatives (e.g.,
using a CD-ROM), which are described on the
Cygwin page. Installation
proceeds interactively. The offered defaults are normally what you would want.
The most recent Gnu g++
compiler can be obtained from
http://gcc.gnu.org. If the compiler that is
made available in the Cygnus distribution lags behind the latest version, the
sources of the latest version can be downloaded after which the compiler can
be built using an already available compiler. The compiler's webpage
(mentioned above) contains detailed instructions on how to proceed. In our
experience building a new compiler within the Cygnus environment works
flawlessly.
2.2.3.2: Compiling a C++ source text
In general, the following command is used to compile a C++ source file
`source.cc
':
g++ source.ccThis produces a binary program (
a.out
or a.exe
). If the default
name is not wanted, the name of the executable can be specified using the
-o
flag (here producing the program source
):
g++ -o source source.ccIf a mere compilation is required, the compiled module can be generated using the
-c
flag:
g++ -c source.ccThis produces the file
source.o
, which can be linked to other modules
later on.
Using the
icmake
program a maintenance script can be used to assist in the construction and
maintenance of C++ programs. A generic icmake
maintenance script
(
icmbuild
) is available as well. Alternatively, the standard
make
program can be used to maintain C++ programs. It is strongly advised to
start using maintenance scripts or programs early in the study of the C++
programming language. Alternative approaches were implemented by former
students, e.g., lake by Wybo
Wiersma and ccbuild by Bram Neijt.
Concerning the above allegations of C++, we support the following, however.
struct
s, typedef
s etc.. From these types other
types can be derived, thus leading to struct
s containing struct
s and
so on. In C++ these facilities are augmented by defining data types which
are completely `self supporting', taking care of, e.g., their memory
management automatically (without having to resort to an independently
operating memory management system as used in, e.g., Java).
xmalloc()
and xrealloc()
are used (allocating the memory or aborting the program
when the memory pool is exhausted). However, with malloc()
like functions
it is easy to err: miscalculating the required number of bytes in a
malloc()
call is a frequently occurring error. Instead, C++ offers
facilities for allocating memory in a somewhat safer way, through its
operator new
.
static
variables can be used and special
data types such as struct
s can be manipulated by dedicated functions.
Using such techniques, data hiding can be realized even in C; though it
must be admitted that C++ offers special syntactical constructions, making
it far easier to realize `data hiding' in C++ than in C.
static
).
In contrast (or maybe better: in addition) to this, an object-based approach identifies keywords in a problem. These keywords are then depicted in a diagram and arrows are drawn between these keywords to define an internal hierarchy. The keywords will be the objects in the implementation and the hierarchy defines the relationship between these objects. The term object is used here to describe a limited, well-defined structure, containing all information about an entity: data types and functions to manipulate the data. As an example of an object oriented approach, an illustration follows:
The employees and owner of a car dealer and auto garage company are paid as follows. First, mechanics who work in the garage are paid a certain sum each month. Second, the owner of the company receives a fixed amount each month. Third, there are car salesmen who work in the showroom and receive their salary each month plus a bonus per sold car. Finally, the company employs second-hand car purchasers who travel around; these employees receive their monthly salary, a bonus per bought car, and a restitution of their travel expenses.When representing the above salary administration, the keywords could be mechanics, owner, salesmen and purchasers. The properties of such units are: a monthly salary, sometimes a bonus per purchase or sale, and sometimes restitution of travel expenses. When analyzing the problem in this manner we arrive at the following representation:
In the hierarchy of objects we would define the dependency between the first two objects by letting the car salesmen be `derived' from the owner and mechanics.
The overall process in the definition of a hierarchy such as the above starts with the description of the most simple type. Subsequently more complex types are derived, while each derivation adds a little functionality. From these derived types, more complex types can be derived ad infinitum, until a representation of the entire problem can be made.
In C++ each of the objects can be represented in a class, containing the necessary functionality to do useful things with the variables (called objects) of these classes. Not all of the functionality and not all of the properties of a class are usually available to objects of other classes. As we will see, classes tend to hide their properties in such a way that they are not directly modifiable by the outside world. Instead, dedicated functions are used to reach or modify the properties of objects. Also, these objects tend to be self-contained. They encapsulate all the functionality and data required to perform their tasks and to uphold the object's integrity.
sin()
operating on degrees, but does not want to lose
the capability of using the standard sin()
function, operating on
radians.
Namespaces are covered extensively in section 3.7. For now it
should be noted that most compilers require the explicit declaration of a
standard namespace:
std
. So, unless otherwise indicated, it is
stressed that all examples in the Annotations now implicitly use the
using namespace std;declaration. So, if you actually intend to compile the examples given in the Annotations, make sure that the sources start with the above
using
declaration.
//
and ends with the
end-of-line marker. The standard C comment, delimited by /*
and */
can still be used in C++:
int main() { // this is end-of-line comment // one comment per line /* this is standard-C comment, covering multiple lines */ }Despite the example, it is advised not to use C type comment inside the body of C++ functions. At times you will temporarily want to suppress sections of existing code. In those cases it's very practical to be able to use standard C comment. If such suppressed code itself contains such comment, it would result in nested comment-lines, resulting in compiler errors. Therefore, the rule of thumb is not to use C type comment inside the body of C++ functions.
0
. In C, where pointers are
concerned,
NULL
is often used. This difference is purely stylistic, though
one that is widely adopted. In C++ there's no need anymore to use
NULL
, and using 0
is actually preferred when indicating null-pointer
values.
int main() { printf("Hello World\n"); }does often compile under C, though with a warning that
printf()
is
not a known function. Many C++ compilers will fail to produce code in such
a situation. The error is of course the missing
#include <stdio.h>
directive.
Although, while we're at it: in C++ the function
main()
always
uses the int
return value. It is possible to define
int main()
without an
explicit return statement, but a
return
statement without an
expression cannot be given inside the main()
function: a return
statement in main()
must always be given an int
-expression. For
example:
int main() { return; // won't compile: expects int expression }
(typename)expressionin which
typename
is
the name of a valid type, and expression
an expression. Apart from the
C style cast (now deprecated) C++ also supports the function call
notation:
typename(expression)This function call notation is not actually a cast, but the request to the compiler to construct an (anonymous) variable of type
typename
from the expression
expression
. This form is actually very often used in C++, but should
not be used for casting. Instead, four
new-style casts were
introduced:
static_cast<type>(expression)
const
type-modification:
const_cast<type>(expression)
reinterpret_cast<type>(expression)
dynamic_cast<type>(expression)is performed run-time to convert, e.g., a pointer to an object of a certain class to a pointer to an object further down its so-called class hierarchy. At this point in the Annotations it is a bit premature to discuss the
dynamic_cast
, but we will return to this topic in section
14.5.1.
2.5.5.1: The `static_cast'-operator
The
static_cast<type>(expression)
operator is used to convert one type
to an acceptable other type. E.g., double
to int
. An example of such a
cast is, assuming d
is of type double
and a
and b
are
int
-type variables. In that situation, computing the floating point
quotient of a
and b
requires a cast:
d = static_cast<double>(a) / b;If the cast is omitted, the division operator will cut-off the remainder, as its operands are
int
expressions. Note that the division should be
placed outside of the cast. If not, the (integer) division will be performed
before the cast has a chance to convert the type of the operand to double
.
Another nice example of code in which it is a good idea to use the
static_cast<>()
-operator is in situations where the arithmetic assignment
operators are used in mixed-type situations. E.g., consider the following
expression (assume doubleVar
is a variable of type double
):
intVar += doubleVar;This statement actually evaluates to:
intVar = static_cast<int>(static_cast<double>(intVar) + doubleVar);
IntVar
is first promoted to a double
, and is then added as
double
to doubleVar
. Next, the sum is cast back to an int
. These
two conversions are a bit overdone. The same result is obtained by explicitly
casting the doubleVar
to an int
, thus obtaining an int
-value for
the right-hand side of the expression:
intVar += static_cast<int>(doubleVar);
2.5.5.2: The `const_cast'-operator
The
const_cast<type>(expression)
operator is used to undo the
const
-ness of a (pointer) type. Assume that a function
fun(char *s)
is available, which performs some operation on its
char *s
parameter. Furthermore, assume that it's known that the
function does not actually alter the string it receives as its argument. How
can we use the function with a string like char const hello[] = "Hello
world"
?
Passing hello
to fun()
produces the warning
passing `const char *' as argument 1 of `fun(char *)' discards constwhich can be prevented using the call
fun(const_cast<char *>(hello));
2.5.5.3: The `reinterpret_cast'-operator
The
reinterpret_cast<type>(expression)
operator is used to reinterpret
pointers. For example, using a reinterpret_cast<>()
the individual
bytes making up a double
value can easily be reached. Assume doubleVar
is a variable of type double
, then the individual bytes can be reached
using
reinterpret_cast<char *>(&doubleVar)This particular example also suggests the danger of the cast: it looks as though a standard
C
-string is produced, but there is not normally a
trailing 0-byte. It's just a way to reach the individual bytes of the memory
holding a double value.
More in general: using the cast-operators is a dangerous habit, as it suppresses the normal type-checking mechanism of the compiler. It is suggested to prevent casts if at all possible. If circumstances arise in which casts have to be used, document the reasons for their use well in your code, to make double sure that the cast will not eventually be the underlying cause for a program to misbehave.
2.5.5.4: The `dynamic_cast'-operator
The
dynamic_cast<>()
operator is used in the context of
polymorphism. Its discussion is postponed until section 14.5.1.
void func();means that the argument list of the declared function is not prototyped: the compiler will not warn against improper argument usage. In C, to declare a function having no arguments, the keyword
void
is used:
void func(void);As C++ enforces strict type checking, an empty parameter list indicates the absence of any parameter. The keyword
void
can thus
be omitted: in C++ the above two function declarations are equivalent.
__cplusplus
: it is as if each source file were prefixed with the
preprocessor directive
#define __cplusplus
.
We shall see examples of the usage of this symbol in the following sections.
As an example, the following code fragment declares a function xmalloc()
as a C function:
extern "C" void *xmalloc(size_t size);This declaration is analogous to a declaration in C, except that the prototype is prefixed with
extern "C"
.
A slightly different way to declare C functions is the following:
extern "C" { // C-declarations go in here }It is also possible to place preprocessor directives at the location of the declarations. E.g., a C header file
myheader.h
which declares
C functions can be included in a C++ source file as follows:
extern "C" { #include <myheader.h> }Although these two approaches can be used, they are actually seldomly encountered in C++ sources. We will encounter a more frequently used method to declare external C functions in the next section.
__cplusplus
and of the
possibility to define
extern "C"
functions offers the ability to
create header files for both C and C++. Such a header file might,
e.g., declare a group of functions which are to be used in both C and
C++ programs.
The setup of such a header file is as follows:
#ifdef __cplusplus extern "C" { #endif // declaration of C-data and functions are inserted here. E.g., void *xmalloc(size_t size); #ifdef __cplusplus } #endifUsing this setup, a normal C header file is enclosed by
extern "C"
{
which occurs at the start of the file and by }
,
which occurs at the end of the file. The
#ifdef
directives test for the
type of the compilation: C or C++. The `standard' C header files,
such as
stdio.h
, are built in this manner and are therefore usable for
both C and C++.
In addition to this, C++ headers should support
include guards.
In C++ it is usually undesirable to include the same header file twice in
the same source file. Such
multiple inclusions can easily be avoided by
including an
#ifndef
directive in the header file. For example:
#ifndef _MYHEADER_H_ #define _MYHEADER_H_ // declarations of the header file is inserted here, // using #ifdef __cplusplus etc. directives #endifWhen this file is scanned for the first time by the preprocessor, the symbol
_MYHEADER_H_
is not yet defined. The #ifndef
condition
succeeds and all declarations are scanned. In addition, the symbol
_MYHEADER_H_
is defined.
When this file is scanned for a second time during the same compilation,
the symbol _MYHEADER_H_
has been defined and consequently all information
between the #ifndef
and #endif
directives is skipped by the compiler.
In this context the symbol name _MYHEADER_H_
serves only for
recognition purposes. E.g., the name of the header file can be used for this
purpose, in capitals, with an underscore character instead of a dot.
Apart from all this, the custom has evolved to give C header files the
extension
.h
, and to give C++
header files no extension. For
example, the standard iostreams cin, cout
and cerr
are available
after including the preprocessor directive
#include <iostream>
, rather
than #include <iostream.h>
in a source. In the Annotations this convention
is used with the standard C++ header files, but not everywhere else
(Frankly, we tend not to follow this convention: our C++ header files
still have the .h
extension, and apparently nobody cares...).
There is more to be said about header files. In section 6.6 the preferred organization of C++ header files is discussed.
Furthermore, local variables can be defined inside some statements, just prior
to their usage. A typical example is the for
statement:
#include <stdio.h> int main() { for (register int i = 0; i < 20; i++) printf("%d\n", i); return 0; }In this code fragment the variable
i
is created inside the for
statement. According to the ANSI-standard, the variable does not exist prior
to the for
-statement and not beyond the for
-statement. With some
older compilers, the variable continues to exist after the execution of the
for
-statement, but a warning like
warning: name lookup of `i' changed for new ANSI `for' scoping using obsolete binding at `i'will then be issued when the variable is used outside of the
for
-loop.
The implication seems clear: define a variable just before the
for
-statement if it's to be used after that statement, otherwise the
variable can be defined inside the for
-statement itself.
Defining local variables when they're needed requires a little getting used to. However, eventually it tends to produce more readable and often more efficient code than defining variables at the beginning of compound statements. We suggest the following rules of thumb for defining local variables:
for
-statement, but
also all situations where a variable is only needed, say, half-way through the
function.
{
.
If considered appropriate,
nested blocks can be used to localize
auxiliary variables. However, situations exist where local variables are
considered appropriate inside nested statements. The just mentioned for
statement is of course a case in point, but local variables can also be
defined within the condition clauses of if-else
statements, within
selection clauses of switch
statements and condition clauses of while
statements. Variables thus defined will be available in the full
statement, including its nested statements. For example, consider the
following switch
statement:
#include <stdio.h> int main() { switch (int c = getchar()) { case 'a': case 'e': case 'i': case 'o': case 'u': printf("Saw vowel %c\n", c); break; case EOF: printf("Saw EOF\n"); break; default: printf("Saw other character, hex value 0x%2x\n", c); } }Note the location of the definition of the character `
c
': it is
defined in the expression part of the switch()
statement. This implies
that `c
' is available only in the switch
statement itself,
including its nested (sub)statements, but not outside the scope of the
switch
.
The same approach can be used with if
and while
statements: a
variable that is defined in the condition part of an if
and while
statement is available in their nested statements. However, one should realize
that:
if
or while
statement,
the value of the variable must be interpretable as either zero (false) or
non-zero (true). Usually this is no problem, but in C++ objects (like
objects of the type std::string
(cf. chapter 4)) are often
returned by functions. Such objects may or may not be interpretable as
numerical values. If not (as is the case with std::string
objects), then
such variables can not be defined in the condition or expression parts of
condition- or repetition statements. The following example will, therefore,
not compile:
if (std::string myString = getString()) // assume getString() returns { // a std::string value // process myString }
The above deserves further clarification. Often a variable can profitably be given local scope, but an extra check is required immediately following its initialization. Both the initialization and the test cannot be combined in one expression, but two nested statements are required. The following example will therefore not compile either:
if ((int c = getchar()) && strchr("aeiou", c)) printf("Saw a vowel\n");If such a situation occurs, either use two nested
if
statements, or
localize the definition of int c
using a nested compound statement.
Actually, other approaches are possible as well, like using exceptions
(cf. chapter 8) and specialized functions, but that's jumping a
bit too far ahead. At this point in our discussion, we can suggest one of the
following approaches to remedy the problem introduced by the last example:
if (int c = getchar()) // nested if-statements if (strchr("aeiou", c)) printf("Saw a vowel\n"); { // nested compound statement int c = getchar(); if (c && strchr("aeiou", c)) printf("Saw a vowel\n"); }
const
attribute). An example is given
below:
#include <stdio.h> void show(int val) { printf("Integer: %d\n", val); } void show(double val) { printf("Double: %lf\n", val); } void show(char *val) { printf("String: %s\n", val); } int main() { show(12); show(3.1415); show("Hello World\n!"); }In the above fragment three functions
show()
are defined, which only
differ in their parameter lists: int
, double
and char *
. The
functions have identical names. The definition of several functions having
identical names is called `
function overloading'.
It is interesting that the way in which the C++ compiler implements
function overloading is quite simple. Although the functions share the same
name in the source text (in this example show()
), the compiler (and hence
the linker) use quite different names. The conversion of a name in the source
file to an internally used name is called `
name mangling'. E.g., the
C++ compiler might convert the name void
show
(int)
to the
internal name VshowI
, while an analogous function with a char*
argument might be called VshowCP
. The actual names which are internally
used depend on the compiler and are not relevant for the programmer, except
where these names show up in e.g., a listing of the contents of a library.
A few remarks concerning function overloading are:
show()
are still
somewhat related (they print information to the screen).
However, it is also quite possible to define two functions lookup()
,
one of which would find a name in a list while the other would determine the
video mode. In this case the two functions have nothing in common except for
their name. It would therefore be more practical to use names which suggest
the action; say, findname()
and vidmode()
.
printf("Hello World!\n");holds no information concerning the return value of the function
printf()
. Two functions printf()
which would only differ in their
return type could therefore not be distinguished by the compiler.
show(0);given the three functions
show()
above. The zero could be interpreted
here as a NULL
pointer to a char
, i.e., a (char *)0
, or as an
integer with the value zero. Here, C++ will call the function
expecting an integer argument, which might not be what one expects.
const
member functions will
be introduced (cf. section 6.2). Here it is merely mentioned
that classes normally have so-called member functions associated with them
(see, e.g., chapter 4 for an informal introduction of the
concept). Apart from
overloading member functions using different parameter lists, it
is then also possible to
overload member functions by their const
attributes. In those cases, classes may have pairs of identically named member
functions, having identical parameter lists. Then, these functions are
overloaded by their const
attribute: one of these function must
have the const
attribute, and the other must not.
#include <stdio.h> void showstring(char *str = "Hello World!\n"); int main() { showstring("Here's an explicit argument.\n"); showstring(); // in fact this says: // showstring("Hello World!\n"); }The possibility to omit arguments in situations where default arguments are defined is just a nice touch: the compiler will supply the missing argument unless explicitly specified in the call. The code of the program becomes by no means shorter or more efficient.
Functions may be defined with more than one default argument:
void two_ints(int a = 1, int b = 4); int main() { two_ints(); // arguments: 1, 4 two_ints(20); // arguments: 20, 4 two_ints(20, 5); // arguments: 20, 5 }When the function
two_ints()
is called, the compiler supplies one or
two arguments when necessary. A statement as two_ints(,6)
is however not
allowed: when arguments are omitted they must be on the right-hand side.
Default arguments must be known at compile-time, since at that moment arguments are supplied to functions. Therefore, the default arguments must be mentioned in the function's declaration, rather than in its implementation:
// sample header file extern void two_ints(int a = 1, int b = 4); // code of function in, say, two.cc void two_ints(int a, int b) { ... }Note that supplying the default arguments in function definitions instead of in function declarations in header files is incorrect: when the function is used in other sources the compiler will read the header file and not the function definition. Consequently, in those cases the compiler has no way to determine the values of default function arguments. Current compilers may generate errors when detecting default arguments in function definitions.
typedef
is still allowed in C++, but is not required
anymore when defining
union
,
struct
or
enum
definitions.
This is illustrated in the following example:
struct somestruct { int a; double d; char string[80]; };When a
struct
, union
or other compound type is defined, the tag of
this type can be used as type name (this is somestruct
in the above
example):
somestruct what; what.d = 3.1415;
A definition of a struct point
is given in the code fragment below.
In this structure, two int
data fields and one function draw()
are
declared.
struct point // definition of a screen { // dot: int x; // coordinates int y; // x/y void draw(void); // drawing function };A similar structure could be part of a painting program and could, e.g., represent a pixel in the drawing. With respect to this
struct
it should be
noted that:
draw()
mentioned in the struct
definition is a
mere declaration. The actual code of the function, or in other words the
actions performed by the function, are located elsewhere. We will describe the
actual definitions of functions inside struct
s later (see section
3.2).
struct
point
is equal to the size of its
two int
s. A function declared inside the structure does not affect its
size. The compiler implements this behavior by allowing the function
draw()
to be known only in the context of a point
.
point
structure could be used as follows:
point a; // two points on point b; // the screen a.x = 0; // define first dot a.y = 10; // and draw it a.draw(); b = a; // copy a to b b.y = 20; // redefine y-coord b.draw(); // and draw itThe function that is part of the structure is selected in a similar manner in which data fields are selected; i.e., using the field selector operator (
.
). When pointers to struct
s are used,
->
can be used.
The idea behind this syntactical construction is that several types may
contain
functions having identical names. E.g., a structure representing a
circle might contain three int
values: two values for the coordinates of
the center of the circle and one value for the radius. Analogously to the
point
structure, a function draw()
could be declared which would draw
the circle.