bool type
/* File: hello.cpp
*
* Purpose: Hello world application in ANSI C++.
*
* Version: 1.0
*
* Date: August 28, 2000
*
* Author: Colin Flanagan
*
*/
#include <iostream>
using namespace std;
int main()
{
cout << "Hello World" << endl;
return 0;
}
|
.cpp (or
.C) on Unix.
hello.cpp on Windows and Hello.C
on Unix (but C++ does not enforce this).
cout is the standard output stream, usually connected
to a terminal window (MS-DOS prompt under Windows).
<< is the put-to operator.
endl is a stream manipulator. It means
"send a newline character to the output stream and flush it if it
is buffered" (the cout stream normally is buffered).
#include <iostream> and using
namespace std directives. These are needed so that C++ recognises
cout, the put-to operator and the endl
stream manipulator.
#include <iostream> is an include directive. It
instructs the C++ preprocessor to read the contents of a file
called iostream and include it in the compilation of this
program. iostream is a header file. It is part of
ANSI standard C++ and includes declarations for cout,
<< and endl, telling the compiler how
these may legally be used in a program.
cout, << and endl are
names defined as part of a C++ namespace. The using
namespace std
directive tells the compiler to assume that names are part of this
namespace unless otherwise instructed.
main. A C++ program starts executing by calling
main.
main.
int (simple
signed integer), to the operating system. Value 0 means that the program
completed correctly, any other value indicates some sort of failure.
/*
and */. There is also a comment marker //,
but it is not used in the above.
cout << "Hello World" << endl;
return 0;
{ and
}.
#include directive
on a line. In fact, most compilers will ignore everything
following the name of the included file on a line with an
#include directive.
It is recommended that you choose a formatting convention (like the above) and stick to it.
main should return a value. C-style void
main is not good C++.
main() means that main does not
expect any arguments. It is equivalent to C-style
main(void).
iostream does
not have a .h extension (ANSI C++).
using namespace std directive is also new
with ANSI C++.
hello.cpp and paste the code shown above into it.
// File: quadratic.cpp
//
// Purpose: Finds the roots of the quadratic equation
// ax^2 + bx + c = 0, using the formula
// x = (-b +- sqrt( b^2 - 4ac )) / 2a
//
// Version: 1.1
//
// Date: October 6, 2000
//
// Author: Colin Flanagan
//
//
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
double a, b, c;
cout << "Please enter values for a, b and c --> ";
cin >> a >> b >> c;
double x = b * b - 4.0 * a * c;
if ( x < 0 )
cout << "Roots are imaginary";
else {
double sq = sqrt( x );
cout << "Root 1 = " << (-b + sq) / (2.0 * a);
cout << ", Root 2 = " << (-b - sq) / (2.0 * a);
}
cout << endl;
return 0;
}
|
a, b, c, and
sq. They are all of type double, a predefined C++
data type used to represent double precision floating-point values.
double a, b, c; near the top of main.
double x =
b * b - 4.0 * a * c;.x
to be a double variable and also initialises
it to the value of the expression
b * b - 4.0 * a * c.
double sq = sqrt( x ); both
declares and initialises sq.
>> is the get-from operator. It instructs C++ to
read values from the input stream cin. This is usually
connected to the keyboard.
double values from cin
because the data type of the variables into which data is to be read is
double. The >> operator understands
C++ data types.
sqrt is a C++ library function. It computes the positive square
root of its (positive) argument. The include directive #include
<cmath> is needed at the start of the code to tell C++ about the
sqrt function, how to use it and how to find it in the standard
library. <cmath> contains definitions for all the standard
mathematical functions.
if ( sq < 0 )
cout << "Roots are imaginary";
else {
cout << "Root 1 = " << (-b + sq) / (2.0 * a);
cout << ", Root 2 = " << (-b - sq) / (2.0 * a);
} |
if
is executed (shown in red). If the condition evaluates as false,
that section of the code delimited by the else code is
executed (shown in green).
if action consists of a single
statement, so doesn't require brace delimiters. The else
action, on the other hand, consists of multiple statements, so has to
be grouped within a pair of braces.
{ & }).
* | multiply | / | divide |
+ | add | - | subtract/negate |
Parentheses are used for grouping and to enforce precedence.
//" comment delimiter. A comment
introduced by "//" continues to the end of the current
line.
The "//" comment delimiter is useful for short comments.
If a multi-line comment is needed, it is necessary to repeat the
delimiter at the start of each line. The decision
about which sort of comment delimiter to use is largely a matter of taste.
sq in the middle
of the function. This is good C++ but is illegal in C.
<< and >> operators are the C++
alternative to printf and scanf. They are
a considerable improvement over the old stdio.h functions
because they are type-safe (they automatically match arguments at
compile-time, instead of interpreting a format string at run-time).
//".
| Integral Types | ||
|---|---|---|
| Type | What it is | Typical size (bits) |
bool | Boolean truth value | 8 |
int | standard-width signed integer | 32 |
unsigned | standard-width unsigned integer | 32 |
long | wide-width signed integer | 32-64 |
unsigned long | wide-width unsigned integer | 32-64 |
short | narrow-width signed integer | 16-32 |
unsigned short | narrow-width unsigned integer | 16-32 |
char | character | 8 |
unsigned char | unsigned character | 8 |
signed char | signed character | 8 |
int is the "natural" width for an
integer on a given machine. Hence, it can vary. It is typically 32
bits on current microprocessors, but is 64 bits on 64-bit machines.
long is that
it is no shorter than an int.
short is that
it is no longer than an int.
bool type is new. C++ programmers don't have to
resort to typedefs or #define to create
a Boolean data type, it is built-in.
true and false are the
values it is legal to store in a variable of type bool.
bool value of true behaves like 1 in
an arithmetic expression, a bool value of
false behaves like 0.
bool. Any non-zero value results in true
being assigned to the variable, a zero value results in the
assignment of false.
| Floating-Point Types | ||
|---|---|---|
| Type | What it is | Typical size (bits) |
float | single-precision floating-point value | 32 |
double | double-precision floating-point value | 64 |
unsigned | extended-precision floating-point value | 80 |
CHAR_BIT is a constant declared in header file
climits. It defines the number of bits used by a
char in a C++ implementation.
sizeof() is a C++ operator which returns the amount of
storage occupied by a variable (or data type), in terms of the number
of chars required to occupy the same storage area.
\n is an Escape Character. It represents
newline, i.e., an instruction to advance the output print
position to the start of the next line.
true, false'a',
'b'.
The single quotes are character literal delimiters, they
are not part of the literal. To get a character literal containing a
single quote, use '\''. Here the quote has been
Escaped using the Escape Character backslash
(\). To get a character literal containing a backslash, use
'\\'. Note also the special escaped sequences
'\n' (newline) and '\t' (tab).
"This is a string literal\n". The double quotes
are string literal delimiters, they are not themselves part of the
literal. To get a double quote inside a string literal, it is
necessary to escape it, e.g., "\"".256, -3
0xF3E, 0Xffff3bc9
0777, 056
3U, 3u,
65535U
3L, 3l (use uppercase
L, as lowercase letter l looks too much
like digit 1).
3UL, 3LU. Lowercase
lu and ul are legal, but potentially
confusing.
1.23, .23, 0.23,
1., 1.0, 1.23e10,
1.23E10, 1.23e-15 (all floating-point
literals are type double by default).
1.23f, 1.23F, 1.23e10F,
1.23E10f (use the f or F
suffixes to force the floating-point literal to be of type
float).
_).
$ in an identifier) yield nonportable
code.
Hello,
HELLO and HeLLo are all distinct identifiers.
hello
this_is_a_long_name VALUE Value
0123 a-fool $sys class
pay.due
Enumerations are normally used when we need some constants with meaningful names in a program, but we don't care very much about their values. For example, we can write
enum months { Jan, Feb, Mar, April, May, June,
July, Aug, Sept, Oct, Nov, Dec
} thismonth, lastmonth;
|
Given this declaration in a program, we can use:
months. We can declare some more
variables of this type: months holiday;
Jan ... Dec.
thismonth and lastmonth. These
variables can be assigned values of type months.
The first constant in an enumeration has integer value 0, the next 1, etc. You can change this if you want to force the elements of an enumeration to have specific values.
enum revolutions { AMERICAN = 1776, FRENCH = 1789,
RUSSIAN = 1917, CHINESE = 1948 };
|
The constant AMERICAN of type revolutions has value
1776, etc.
The C++ operators include:
| Comparison Operators | ||
|---|---|---|
| Operator | Purpose | Example |
expr1 ==
expr2
| Test if expr1 equal to expr2 |
a == 5
|
| expr1 != expr2 | Test if expr1 not equal to expr2 |
a != 5
|
expr1 <
expr2
| Test if expr1 less than expr2 |
a < 5
|
expr1 <=
expr2
| Test if expr1 less than or equal to expr2 |
a <= 5
|
expr1 >
expr2
| Test if expr1 greater than expr2 |
a > 5
|
expr1 >=
expr2
| Test if expr1 greater than or equal to expr2 |
a >= 5
|
bool value, i.e., true or false.
==". The
operator "=" is the Assignment Operator.
| Logical Operators | ||||||||
|---|---|---|---|---|---|---|---|---|
| Operator | Purpose | Example | ||||||
expr1 &&
expr2
| Perform Boolean (logical) AND of expr1 and
expr2
|
| a >= 5 && a < 10
expr1 | ||
expr2
Perform Boolean (logical) OR of expr1 and
expr2
|
| a <= 5 || a > 10
| ! expr
Perform Boolean (logical) NOT on expr
|
| !(a <= 5 || a > 10)
|
&& and || are
Short-Circuiting operators. If the result of evaluating the
operand on their left is sufficient to determine the truth value of the
entire expression, the operand on the right is not evaluated at all.
| Operator | Purpose | Example | |||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
expr1 *
expr2
| Multiply expr1 by
expr2
|
| a * 10
expr1 | /
expr2
Divide expr1 by
expr2
|
| a / 10
expr1 | %
expr2
Find the remainder when expr1 is divided by
expr2
|
| 30 % 7
expr1 | +
expr2
Add expr2 to expr1
|
| a + 7
expr1 | -
expr2
Subtract expr2 from expr1
|
| a - 7
| -
expr
Negate expr
|
| -a
| ++
var
Add 1 to var (preincrement)
|
| ++a
var
| ++
Add 1 to var (postincrement)
|
| a++
| --
var
Subtract 1 from var (predecrement)
|
| --a
var
| --
Subtract 1 from var (postdecrement)
|
| a--
|
*, /, +, and -,
may be applied to integral or floating-point operands (or a mixture
of both).
% may only be applied to integral operands.
int i1, i2, i3, i4; i1 = i2 = 10; i3 = ++i1; // Both i1 and i3 are now 11. i4 = i2++; // i2 is 11, but i4 is 10. |
| Operator | Purpose | Example |
|---|---|---|
expr1
&
expr2
| Bitwise AND of expr1 and expr2 |
a & 0xff
|
expr1
|
expr2
| Bitwise OR of expr1 and expr2 |
a | 0555
|
expr1
^
expr2
| Bitwise Exclusive-OR of expr1 and expr2 |
a ^ 1
|
~
expr
| Negate bits of expr |
~a
|
expr1 <<
expr2
| Left shift expr1 by expr2 bit positions |
a << 1
|
expr1 >>
expr2
| Right shift expr1 by expr2 bit positions |
a >> 2
|
| Operator | Purpose | Example |
|---|---|---|
var
=
expr
| Put result of evaluating expr into var |
a = 20
|
var
*=
expr
| Multiply var by expr and put result back in var |
a *= 20
|
var
/=
expr
| Divide var by expr and put result back in var |
a /= 20
|
var
%=
expr
| Generate var %
expr and put result back in var
|
a %= 20
|
var
+=
expr
| Add expr to var and put result back in var |
a += 20
|
var
-=
expr
| Subtract expr from var and put result back in var |
a -= 20
|
var
&=
expr
| Bitwise AND expr with var and put result back in var |
a &= 20
|
var
|=
expr
| Bitwise OR expr with var and put result back in var |
a |= 20
|
var
^=
expr
| Bitwise Exclusive-OR expr with var and put result back in var |
a ^= 20
|
var
<<=
expr
| Left shift contents of var by expr bits and put result back in var |
a <<= 2
|
var
>>=
expr
| Right shift contents of var by expr bits and put result back in var |
a >>= 2
|
if (<Boolean Expression>)
<if-part>
else
<else-part>
|
if (<Boolean Expression>)
<if-part>
|
Examples:
/*
* File: ifelse.cpp
*
* Purpose: Illustrates the use of C++ if and if-else.
*
* Version: 1.0
*
* Date: September 20, 2000
*
* Author: Colin Flanagan
*
*/
#include <iostream>
using namespace std;
int main()
{
char ch;
cout << "Please enter a character "; cin >> ch;
if ( ch >= 'A' && ch <= 'Z' )
cout << "Uppercase";
else if ( ch >= 'a' && ch <= 'z' ) {
cout << "Lowercase, converting to uppercase";
ch += 'A' - 'a'; // ch = (ch - 'a') + 'A';
}
else if ( ch >= '0' && ch <= '9' )
cout << "Digit";
else
cout << "Not alphanumeric";
if ( ch == '.' || ch == ',' ||
ch == '?' || ch == '!' )
cout << ", punctuation";
cout << ": '" << ch << '\'' << endl;
return 0;
}
|
else if ( ch >= 'a' && ch <= 'z' ) {
cout << "Lowercase, converting to uppercase";
ch += 'A' - 'a'; // ch = (ch - 'a') + 'A';
}
|
max = (a > b) ? a : b; |
Here max gets assigned the value of the expression
following the ? if the Boolean preceding it is
true, and gets assigned the value of the expression
following the : if the Boolean is false.
It is equivalent to the if ... else code:
if ( a > b )
max = a;
else
max = b;
|
switch statement is an alternative approach to
handling multiway branches.
if ( ch == '+' ) acc += value;
else if ( ch == '-' ) acc -= value;
else if ( ch == '*' ) acc *= value;
else if ( ch == '/' ) acc /= value;
else
cout << "Error" << end;
|
switch statement, as:
switch ( ch ) {
case '+' : acc += value; break;
case '-' : acc -= value; break;
case '*' : acc *= value; break;
case '/' : acc /= value; break;
default :
cout << "Error" << end;
break;
}
|
case labels (e.g., case
'+') must be integral constants (i.e., character
or integer constant, or constant expression), or members of an enumeration.
No variables, non-constant expressions or floating-point values are allowed.
switch statement.
Duplicate labels are not allowed.
case block is
equality. If you need a more complex comparison, use a nested
if ... else structure.
switch statement matches its argument (the expression
in parentheses immediately following the switch keyword)
against each of the case labels in turn, looking for a
match. The case block following the first
matching case label is executed.
switch statement
contains the default keyword, then the block of code
associated with the default keyword is executed. The
default keyword may come anywhere in the
switch statement, but it is considered good style to
put it last.
A default block is not obligatory in a switch
statement. If the default block is omitted, the switch
will only be executed if a match is found between its argument and one of
the case labels.
switch statement starts executing code, it will
execute all the following statements in the switch
block, unless it encounters a break statement.
This can lead to non-intuitive behaviour by switch
statements. If you forget to terminate the sequence of statements
following a case label with a break
statement, then the code associated with the next case label
will be executed also.
Sometimes this is useful. Say you have a block of code which should be executed when any one of a number of conditions is true. You can achieve this effect with code like the following:
switch ( colour ) {
case Red :
case Green :
case Blue : colourType = PRIMARY; break;
default : colourType = COMPOSITE; break;
}
|
default block comes last in the
switch statement, it doesn't need a break to
terminate it. It is considered good style to include the break,
however.
while keyword, followed by a Boolean Expression enclosed in
parentheses, followed by a Loop Body, which is either a single statement
or a block of statements. Before the loop body can be executed, the
Boolean expression must be evaluated. If the Boolean is true,
the loop body is executed, and the while loops back to
evaluate the Boolean again. If the Boolean is false, the loop
exits, and execution of the program continues with the following statement.
int i = 0;
while ( i < 10 ) {
cout << i << ' ';
i++;
}
cout << "Loop done" << endl;
|
0 1 2 3 4 5 6 7 8 9 Loop done |
int i = 10;
do {
cout << i << ' ';
i++;
} while ( i < 10 );
cout << "Loop done" << endl;
|
10 Loop done |
The equivalent while loop would not execute its loop body at all if
i were initially 10.
for (int i = 0; i < 10; i++) cout << i << ' '; cout << "Loop done" << endl; |
This has the same effect as the while loop shown earlier. If fact, any for loop may always be rewritten as an equivalent while loop.
int i;
float fact10;
for (i = 2, fact10 = 1.0f; i <= 10; i++)
fact10 *= i;
i = 2;
fact10 = 1.0f;
while (i <= 10) {
fact10 *= i;
i++;
}
|
The for loop is very flexible in C++.
for (i = 2, fact10 = 1.0f; i <= 10; fact10 *= i, i++) ; |
for ( Initialisations
; Termination Test
; Loop Variable Modifications
) Loop Body
It is possible to omit any, or all, of these sections when a for loop is used. This is sometimes useful. The following for loop, for exaxmple, has no Loop Variable Modifications section.
for (i = 2, fact10 = 1.0f; i <= 10; ) fact10 *= i++; |
Such uses of the for loop often appear in idiomatic C++ code. It is important to be able to read and understand such code, however, it is better to use the while and do loops when these will improve the clarity of the code. Consider:
char ch;
for ( ; cin.get(ch); cout << ch)
if ( ch >= 'a' && ch <= 'z' ) ch += 'A' - 'a';
|
while ( cin.get(ch) ) {
if ( ch >= 'a' && ch <= 'z' ) ch += 'A' - 'a';
cout << ch;
}
|
break statement in the body of a for loop will cause
execution of the loop to halt immediately, regardless of whether the
termination condition is true or not. Consider the following code which
finds the largest value whose factorial can be represented in an
int on a given machine.
int n, factN, oldFact;
for (n = 2, factN = 1; n < 1000; n += 1) {
oldFact = factN; factN *= n;
if ( factN / oldFact != n ) break;
}
cout << "Factorial " << n - 1 << " = "
<< oldFact << " is the largest" << endl;
|
// Table of values of f(x) = 2x^2 + 3x + 4, x = -1.0 ... -0.5
cout << " x | f(x)\n";
for (float x = -1.0f; x <= -0.5f; x += 0.05f) { // x in scope here
float f = (2.0f * x + 3.0f) * x + 4.0f; // f in scope here
// Output x as a number of the form xx.xx, right justified
// and with a leading minus sign, in a 5 char
// field.
cout << right << fixed << setprecision(2) << setw(5)
<< x << " | ";
// Output f as a number of the form xx.xxxx, right
// justified, in a 7 char field.
cout << right << fixed << setprecision(4) << setw(7)
<< f << '\n';
} // Neither x nor f available beyond this point
|
right,
fixed, setprecision
& setw) may be found in
Johnsonbaugh & Kalin,
pp. 31-33.
#include <iostream>
using namespace std;
int main()
{
for (int i = 0; i < 10; i++) cout << i << ' ';
for (int i = 20; i >= 1; i--) cout << i << ' ';
return 0;
}
|
i
declared in the first loop goes out of scope at the end of that loop,
so it is valid to declare a different local variable with the
same name for the second loop.
However, in Visual C++ v6.0, the first declaration of i is
assumed to remain in scope until the end of the block which encloses it.
Hence, it remains an active variable until the end of main
and the second declaration of i generates an error.
main, which must be part of
every C++ program. All other C++ functions have a similar syntax to
main.
<Return Type> <Function Name> ( <Parameter List> ) {
<Statement>
.
.
.
<Statement>
}
|
/*
* File: function.cpp
*
* Purpose: Illustrates the use of C++ functions.
*
* Version: 1.0
*
* Date: October 5, 2000
*
* Author: Colin Flanagan
*
*/
#include <iostream>
#include <iomanip>
using namespace std;
/* ----------------------------------------------------------
*
* f: Calculate f(x) = 2x^2 + 3x + 4
*
* Input(s): x, a float
*
* Output(s): none
*
* Returns: f(x), a float
*
*/
float f(float x) {
return (2.0f * x + 3.0f) * x + 4.0f;
}
/* ----------------------------------------------------------
*
* main
*
*/
int main()
{
for (float val = -1.0f; val <= -0.5f; val += 0.05f)
cout << right << fixed << setprecision(2) << setw(5)
<< val << " | "
<< right << fixed << setprecision(4) << setw(7)
<< f(val) << '\n';
return 0;
}
|
f and main. The
definition of f comes before that of main so that the
compiler has "seen" f already when it compiles main.
This is needed because the compiler has to know what sort of code it is to
generate when it sees the call to f within main.
f returns a float and takes a
single Formal Parameter, x.
f is made within main. It is
the expression f(val) in the output statement. Note that
this call supplies an Actual Parameter, val. The
value of this actual parameter is copied into the formal parameter
x and used when f is executing. This style
of parameter passing is called Call By Value, and is the
default in C++.
Note that the number and types of the formal and actual parameters must agree. If they do not, a compile-time error is generated.
f's declaration states that it returns a
float, it must contain a return statement
with an expression of this type as its argument.
void". This tells C++ that this particular "function" does not
return a result.
void prettyPrint(float value, int fieldWidth, int precision) {
cout << right << fixed << setprecision(precision)
<< setw(fieldWidth) << value;
}
|
return statement as it does
not return a value to its caller. Any call to it will require three actual
parameters.
Function Prototypes are used in C++ to allow the definitions of functions to be placed in multiple source files. As long as a C++ compiler has access to a function prototype, it can generate the appropriate code for a function call. The actual code needed to implement the function may be compiled separately and later linked to the code of its caller to generate an executable file.
Example: Consider the function f above. It is not necessary to
have access to the definition of f in order to compile the code
for main, all the compiler needs to do this is know:
f, and;
f,
f within
main. Here is a version of the file function.cpp
which contains a function prototype for f, but does not
contain its definition.
#include <iostream>
#include <iomanip>
using namespace std;
float f(float x); // Function prototype for f.
int main()
{
for (float val = -1.0f; val <= -0.5f; val += 0.05f)
cout << right << fixed << setprecision(2) << setw(5)
<< val << " | "
<< right << fixed << setprecision(4) << setw(7)
<< f(val) << '\n';
return 0;
}
|
f. Note that the
function prototype is terminated by a semicolon in place of a function body.
This technique of Separate Compilation allows large projects to be broken up into a number of smaller units. These smaller units compile quickly and are linked together to generate an executable file.
It may be necessary for a function prototype to be visible in multiple source
files. Rather than repeat the function prototype in the various source files,
with the associated possibility of incorrect definition, it is common practice in
C++ to group sets of related function prototypes into a Header File, and
use an #include directive to incorporate the header file into a
source file where these function prototypes are needed.
Since there is now only one function
prototype for a function (held in the header file), errors are much less
likely to occur than if the function prototype is re-written in each source
file where it is needed.
The appropriate header file is commonly also #included in
the source file where a function is defined. Strictly speaking, this is not
necessary, the function prototype does not include any information which is
required in order to compile the function definition. However, the practice is
very useful because it serves as a consistency check, a guarantee
that the various uses of a function and its implementation are agreed about
what its interface should look like.
Here is the earlier example divided up into a header file f.h,
and two source files, f.cpp and main.cpp.
file.
#ifndef F_HEADER #define F_HEADER 1 /* * File: f.h * * Header file containing function prototype for function * f(x). Definition iof f(x) in file f.cpp. * */ float f(float x); #endif |
/*
* File: f.cpp
*
*/
#include "f.h" // Include header for function f(x).
/* --------------------------------------------------------
*
* f: Calculate f(x) = 2x^2 + 3x + 4
*
* Input(s): x, a float
*
* Output(s): none
*
* Returns: f(x), a float
*
*/
float f(float x) {
return (2.0f * x + 3.0f) * x + 4.0f;
}
|
/*
* File: main.cpp
*
*/
#include <iostream>
#include <iomanip>
#include "f.h" // Include header for function f(x).
using namespace std;
int main()
{
for (float val = -1.0f; val <= -0.5f; val += 0.05f)
cout << right << fixed << setprecision(2) << setw(5)
<< val << " | "
<< right << fixed << setprecision(4) << setw(7)
<< f(val) << '\n';
return 0;
}
|
#include "f.h" directive in
the two source files. This tells C++ to look for f.h in the
Current Working Directory. The angle brackets on the other
#include directives tell C++ to look for these header files
(iostream & iomanip) in the C++ System
Include Directory.
#ifndef, #define and
#endif directives in the header file. These are designed
to prevent multiple definitions of the function prototype for f
occurring in a source file.
This can easily happen in a large project
where the same header file may get #included more than once
in a source file through multiple inclusion routes (unintentional, but
very common with large software systems).
The mechanisim works as follows: #ifndef is a
Conditional Inclusion preprocessor directive. If the argument
to an #ifndef is defined (i.e., has any sort of
value), then everything following the #ifndef until an
#endif preprocessor directive is encountered is simply
ignored (treated like a comment) by the preprocessor. Hence, if
the symbol F_HEADER has a value when the #ifndef
is encountered, the function prototype will be ignored by the compiler.
Now, the first time the header file is #included in a
particular source file, F_HEADER is undefined, so the
#ifndef accepts everything in the header file and passes
it on to the compiler, making the function prototype available for
use.
Note, however, that one of the things in the header file
is a #define preprocessor directive, which defines
the symbol F_HEADER to have the value 1 (in fact, any
value will do). Hence, if the same header file is supsequently
#included in the same source file, F_HEADER
will be defined and the function prototype will be ignored.
This use of #ifndef, #endif and
#define is very common in header files, used to ensure
that C++'s Single Definition Rule (which says that any
entity in a C++ program may only be defined once) is not breached.
A final point about function prototypes. The formal parameter names in
a function prototype are dummies. They are not needed. All that you need
in a function prototype is the data type of each formal parameter,
(e.g., "float f(float);" is fine).
However, including formal parameter
names in function prototypes is perfectly legal and may help to make the
prototype more readable, so you are encouraged to do it.
char a[7], buff[256]; int vals[35]; float v[3]; double matrix[3][3]; |
a to be a 7-element array of chars.
buff is also an array of chars, but has 256 elements.
vals is an array of ints with 35 elements, and
v is an array of floats with 35 elements. All these
arrays are 1-dimensional. Array matrix, on the other hand, is
2-dimensional, comprising 9 doubles.
Accessing the elements of an array makes use of the Array Subscript
operator ([ ]). Here is an example program which sorts an
array of 5 values read from the standard input.
/*
* File: sort.cpp
*
* Purpose: Sorts an array of 5 integers read
* from the standard input.
* A simple bubble sort is used.
*
* Version: 1.0
*
* Date: October 6, 2000
*
* Author: Colin Flanagan
*
*/
#include <iostream>
using namespace std;
int main()
{
const int MAX = 5;
int elements[MAX], i;
cout << "Enter " << MAX << " integer values to sort --> ";
for (i = 0; i < MAX; i++) cin >> elements[i];
bool swappedElements = true;
while ( swappedElements ) {
swappedElements = false;
int j;
for (i = 0, j = 1; j < MAX; i++, j++)
if ( elements[i] > elements[j] ) {
int temp = elements[i];
elements[i] = elements[j];
elements[j] = temp;
swappedElements = true;
}
for (i = 0; i < MAX; i++) cout << elements[i] << ' ';
cout << endl;
}
cout << "Sorted elements: ";
for (i = 0; i < MAX; i++) cout << elements[i] << ' ';
cout << endl;
return 0;
}
|
elements
is elements[0], the second is elements[1]. The
last member of this array, of size five, is elements[4].
Forgeting this is a common source of C++ errors, called Off By One errors.
MAX.
Constants will be discussed later in the course.
Multi-dimensional arrays are "arrays of arrays". For example, matrix[3][3]
is an array of size 3, where each of its elements is also an array of size 3.
Access to multi-dimensional arrays just requires multiple [ ]
operators. Here is code which calculates and outputs the inverse of a 2 x 2
matrix. It is a good example of the use of multi-dimensional arrays.
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
double m[2][2], invM[2][2];
for (int i = 0; i < 2; i++) // Read in matrix m.
for (int j = 0; j < 2; j++)
cin >> m[i][j];
// Calculate "ad - bc", the scale factor for the
// inverse. This must be non-zero if the matrix
// has an inverse.
double adbc = m[0][0] * m[1][1] - m[0][1] * m[1][0];
if ( adbc == 0.0 )
cout << "matrix has no inverse" << endl;
else {
// Inverse calculation using the well-known
// formula.
invM[0][0] = m[1][1] / adbc;
invM[1][1] = m[0][0] / adbc;
invM[0][1] = -m[0][1] / adbc;
invM[1][0] = -m[1][0] / adbc;
// Code to display the inverse.
cout << fixed << setprecision(5);
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
cout << setw(9) << invM[i][j];
if ( j == 0 ) cout << ", ";
}
cout << '\n';
}
}
return 0;
}
|
Arrays may be passed to functions. A function which accepts a 1-dimensional array as an argument does not need to specify the array size in its formal argument list.
#include <iostream>
using namespace std;
const int VECTOR_SIZE = 3;
float innerprod(float v1[], float v2[]);
void readVector(float v[]);
//------------------------------------------------------------------
//
// Function: main
//
int main()
{
float v1[3], v2[3];
cout << "Enter vector 1 (" << VECTOR_SIZE << " elements) --> ";
readVector(v1);
cout << "Enter vector 2 (" << VECTOR_SIZE << " elements) --> ";
readVector(v2);
cout << "The inner product of v1 and v2 = " << innerprod(v1, v2)
<< endl;
return 0;
}
//------------------------------------------------------------------
//
// Function: innerprod
//
// Inputs: Two vectors of floats, both of size VECTOR_SIZE.
// Outputs: None.
// Returns: A float.
// Purpose: Calculates the inner product of the argument
// vectors.
// Side Effects: None.
//
float innerprod(float v1[], float v2[])
{
float acc = 0.0f;
for (int i = 0; i < VECTOR_SIZE; i++) acc += v1[i] * v2[i];
return acc;
}
//------------------------------------------------------------------
//
// Function: readVector
//
// Input: A vector of floats, of size VECTOR_SIZE.
// Output: The same vector, with its contents modified to
// values read from the standard input.
// Returns: Nothing.
// Purpose: Reads elements into vector supplied as actual
// parameter.
// Side Effects: Modifies actual parameter to call.
//
void readVector(float v[])
{
for (int i = 0; i < VECTOR_SIZE; i++) cin >> v[i];
}
|
float c[])
used to specify that a 1-dimensional array is being passed to a function.
This tells C++ that the argument is an array, but does not explicitly
mention its size. Sometimes this is useful. It is always legal to
specify the size of an array explicitly in a formal argument list.
readVector function relies on it
to ensure that v1 and v2 get the values read
from cin placed into them.
Note that this behaviour is in marked contrast to the behaviour of scalars used as formal parameters. A scalar formal parameter can be modified in a function, and this will not change the value of the corresponding actual parameter used in a call to that function.
The reason for this difference in treatment of array parameters and scalar parameters is one of efficiency. C++ always makes a local copy of any scalar parameters to a function, and it is the local copy that the function works with. For arrays, on the other hand, C++ passes the address of the first element to the function, (as it is potentially expensive to copy the whole array element by element). Thus, when working with an array parameter, a function is modifying the original array, hence pass by reference.
Arrays in C++ may be Explicitly Initialised when they are declared. If a 1-dimensional array is explicitly initialised it is not necessary to give its size, as C++ can work this out by counting the elements.
int primes[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23 };
|
primes is a 1-dimensional array of size 9.
Explicit initialisation applies to multi-dimensional arrays as well. However, in this case you have to specify the sizes of all the dimensions except the major one.
double matrix[][3] = { { 10.0, 20.0, 30.0 },
{ 40.0, 50.0, 60.0 } };
|
matrix is a 2-dimensional array containing 2 1-d arrays each of
size 3.
String literals in C++ are a type of array (array of char). A string
literal consists of the characters of the string, terminated by a Null
Character, automatically inserted by the C++ compiler to indicate "end of
string". Hence, the character literal "Hi" has three
elements, 'H', 'i' and '\0' (the C++
notation for the null character). The null character always has an integer
value of zero.
Arrays of char may be explicitly initialised by using string
literals.
char hello[] = "Hello"; |
char. Hello[0] is
'H', Hello[1] is 'e', and
Hello[5] is '\0'.
The fact that a null character is guaranteed to appear at the end of a properly
formed string literal is used extensively in C++ manipulation of arrays of
char.
void to_uppercase(char s[])
{
for (int i = 0; i < 1000 && s[i] != '\0'; i++)
if ( s[i] >= 'a' && s[i] <= 'z' ) s[i] += 'A' - 'a';
}
|
Date could be made up of integers representing the day, month and
year:
struct Date {
int day;
int month;
int year;
};
|
Dates can be declared in C++ programs just as if they were
built-in types.
Access to the members of a structure is performed using the Dot
Operator (".").
#include <iostream>
using namespace std;
struct Date {
int day;
int month;
int year;
};
char monthNames[][4] = { "Jan", "Feb", "Mar", "Apr",
"May", "Jun", "Jul", "Aug",
"Sep", "Oct", "Nov", "Dec" };
void displayDate(Date d);
int main()
{
Date today, last_year;
today.day = 10;
today.month = 10;
today.year = 2000;
Date tomorrow = today;
tomorrow.day++;
last_year = today; last_year.year--;
cout << "Today is "; displayDate(today);
cout << "\nTomorrow will be "; displayDate(tomorrow);
cout << "\nLast year was "; displayDate(last_year);
cout << endl;
return 0;
}
void displayDate(Date date)
{
cout << monthNames[date.month-1] << ' '
<< date.day << ", " << date.year;
}
|
Date structure is usually defined at File Scope so
that it is visible across several functions. Here, for example, it is
visible to both main and displayDate.
Date structure is defined, variables of type
Date may be declared. The type Date can be used
just like any of the built-in types in a declaration.
Date variable is via the dot
operator. Hence today.day accesses the day field
of the Date variable today, etc.
last_year = today; sets
all the fields of last_year equal to the fields of
today.
monthNames. The size of the
second dimension is given as 4 to allow space for the month names. Each
month name is an array of four characters, the three visible ones
plus a null ('\0') to terminate the string.
The fields within a structure do not have to be homogeneous. They may be of differing types.
struct student {
int id_number;
float qca;
char firstname[40];
lastname[40];
middleinitial;
};
|
Pointers have types, i.e., a given pointer variable is only allowed to point to
(i.e., contain the address of) other variables of a certain type.
Thus C++ has "pointers to int",
"pointers to float", "pointers to char", etc., but
a pointer to an int may only point to an int variable,
never a char variable or a float variable.
Pointers are declared very much like any other variables, except that they have
a special symbol, (the asterisk, "*") to denote that they are
pointers and not standard variables.
char ch, *chptr; int x, y, *pi; float f, *pf; double *pointerToDouble; |
chptr is a pointer to a char,
pi is a pointer to an int,
pf is a pointer to a float, and
pointerToDouble is a pointer to a double. Note how
declarations of pointers may be mixed with declarations of ordinary variables
(ch, x, y, & f).
Note that declaring a pointer does not initialise it. All the pointers above may be assumed initially to contain random values. Attempting to use an uninitialised pointer in a program will usually cause a run-time error (often the compiler will warn you if it thinks a pointer - or any variable - is being used before it is initialised).
To initialise a pointer to point to a valid variable requires the use of the
Address-Of operator ("&"). Here is code which declares
and initialises a pointer to an integer.
int value = 22, *ptr; ptr = &value; // "ptr" now contains the address of "value". |
To get at the value of a variable via a pointer requires the use of the
Dereference operator (the asterisk, "*").
int value = 22, *ptr;
ptr = &value; // "ptr" now contains the address of "value".
cout << value << ' '; // Print contents of "value" variable.
cout << *ptr << '\n'; // Print contents of "value", but access
// via pointer "ptr", using dereference.
|
Pointers to structures may be declared in a similar way to pointers to any
of the built-in types. For example, given the Date
structure from above, we may declare:
Date today, *ptoday;, where today is a variable of
type Date and ptoday is a pointer to a variable of
type Date.
C++ has a special operator to allow the elements of a structure to be accessed
via a pointer. We could use (*ptoday).day to access the
day field of variable today via dereferencing through
ptoday, or we may use ptoday->day to achieve
the same result. The Arrow operator ("->") allows
direct access to the fields of a structure through a pointer to that structure.
Date today, *ptoday;
ptoday = &today; // "ptoday" now contains the address of "today".
ptoday->day = 10; // Modify fields of "today" thru pointer, using
ptoday->month = 10; // arrow operator.
ptoday->year = 2000;
cout << today.day // today.day = 10 (modified thru pointer)
<< '/'
<< today.month // today.month = 10
<< '/'
<< today.year; // today.year = 2000
|
Pointers and arrays have a special relationship in C++.
x is an array
of 20 ints, we may access element 4 (the fifth element of
that array) using either the array subscript operator x[4],
or the dereferencing operator *(x+4). The two
approaches are completely equivalent in C++ (indeed, they are
synonyms).
int i, primes[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, -1 };
for (i = 0; primes[i] > 0; i++) cout << primes[i] << ' ';
for (i = 0; *(primes+i) > 0; i++) cout << *(primes+i) << ' ';
for (int *pptr = primes; *pptr > 0; pptr++) cout << *pptr << ' ';
|
Sometimes it is convenient to specify that a pointer variable currently doesn't
hold a valid address. Such a pointer is called a Null Pointer, and
is specified in C++ by using the constant 0. Any pointer can
be assigned this value, and any pointer containing this value is null, i.e.,
currently not pointing at anything.
Here is an example of the use of null pointers. A structure can be defined to contain a pointer to structures of the same type. This allows the structure to be used to build a linked-list.
struct node {
int id; // student id number
char name[40]; // student name
node *next; // next node in list
};
|
id and returns the name of
that student if a match is found. If no match is found, a null pointer
is returned.
char *search(node *listNode, int id)
{
while ( listNode != 0 && listNode->id != id )
listNode = listNode->next;
return (listNode ? listNode->name : 0);
}
|
node has no successor in the list, its next
field is null.
listNode != 0 ensures that listNode
points to a valid node. This is a good use of the
short-circuiting && operator.
while loop terminates when either a match has been found
or the end of the list reached. The return statement returns
a pointer to the char array representing the student name if
the end of the list has not been reached. Otherwise it returns
a null pointer to char. Note how the test to see if the
end of the list has been reached is represented: if listNode
is non-null, it behaves as true, hence
listNode->name is returned; if it is null, it behaves like
false and a null pointer to char (i.e.,
0) is returned.
Note for C programmers: C++ does not make use of the preprocessor
macro NULL. Anywhere a C program would use NULL, the
C++ style is to use 0.
main function in a special way:
int main(int argc, char *argv[]) |
argc, will be filled in by the
operating system with the number of command-line arguments passed to the
program when it is run. The second parameter is an array of pointers to
null-terminated arrays of char which are the string
representations of the arguments themselves.
The two formal parameters may have any names you like, but it is traditional
in C and C++ programming to call them argc and argv.
(argument count and argument vector). They
must have the types shown above, however.
Here is a program which echoes back the command-line parameters is receives:
/*
* Program: echoargs
*
* Purpose: Illustrate the use of command-line arguments by echoing all
* such arguments back to the user.
*
*/
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
cout << argc << " command-line parameters were passed to the program.\n";
for (int i = 0; i < argc; i++)
cout << "Argument[" << i << "] is :" << argv[i] << '\n';
return 0;
}
|
argv[0] is the name
of the program itself. C++ always returns the name of the currently executing
program as its first command-line parameter. Subsequent parameters will
appear in successive elements of argv:
F:\modules\c++\notes> echoargs hello world, my name is Colin 7 command-line parameters were passed to the program. Argument[0] is :F:\modules\c++\notes\echoargs.exe Argument[1] is :hello Argument[2] is :world, Argument[3] is :my Argument[4] is :name Argument[5] is :is Argument[6] is :Colin |
Colin Flanagan / 19-October-2000