These notes are intended to give an introduction to the C++ programming
language. They have been adapted and extended from several sources (see
credits at the end of these notes). The material can be easily covered
in a week for the reader with some experience in programming. At the end
of that the reader will be able to write and run his own programs.
C is in many ways hard to categorise. Compared to assembly language it is high-level, but it nevertheless includes many low-level facilities to directly manipulate the computer's memory. It is therefore an excellent language for writing efficient "systems" programs. But for other types of programs, C code can be hard to understand, and C programs can therefore be particularly prone to certain types of error. The extra object-oriented facilities in C++ are partly included to overcome these shortcomings.
// The C++ compiler ignores comments which start with
// double slashes like this, up to the end of the line.
/* Comments can also be written starting with a slash
followed by a star, and ending with a star followed by
a slash. As you can see, comments written in this way
can span more than one line. */
/* This program prompts the user for the current year, the user's
current age, and another year. It then calculates the age
that the user was or will be in the second year entered. */
#include <iostream.h>
int main()
{
int year_now, age_now, another_year, another_age;
cout << "Enter the current year then press RETURN.\n";
cin >> year_now;
cout << "Enter your current age in years.\n";
cin >> age_now;
cout << "Enter the year for which you wish to know your age.\n";
cin >> another_year;
another_age = another_year - (year_now - age_now);
if (another_age >= 0) {
cout << "Your age in " << another_year << ": ";
cout << another_age << "\n";
}
else {
cout << "You weren't even born in ";
cout << another_year << "!\n";
}
return 0;
}
This program illustrates several general features of all C++ programs.
It begins (after the comment lines) with the statement
This statement is called an include directive. It tells the compiler and the linker that the program will need to be linked to a library of routines that handle input from the keyboard and output to the screen. The header file "iostream.h" contains basic information about this library.
Because the program is short, it is easily packaged up into a single list of program statements and commands. After the include directive, the basic structure of the program is:
int main()
{
First statement;
...
...
Last statement;
return 0;
}
All C++ programs have this basic "top-level" structure. Notice that each
statement in the body of the program ends with a semicolon. In a well-designed
large program, many of these statements will include references or calls
to sub-programs, listed after the main program or in a separate file. These
sub-programs have roughly the same outline structure as the program here,
but there is always exactly one such structure called main. Again,
you will learn more about sub-programs later in the course.
When at the end of the main program, the line
means "return the value 0 to the computer's operating system to signal that the program has completed successfully". More generally, return statements signal that the particular sub-program has finished, and return a value, along with the flow of control, to the program level above. More about this later.
Our example program uses four variables:
Program variables are not like variables in mathematics. They are more like symbolic names for "pockets of computer memory" which can be used to store different values at different times during the program execution. These variables are first introduced in our program in the variable declaration
int year_now, age_now, another_year, another_age;which signals to the compiler that it should set aside enough memory to store four variables of type "int" (integer) during the rest of the program execution. Hence variables should always be declared before being used in a program. Indeed, it is considered good style and practice to declare all the variables to be used in a program or sub-program at the beginning. Variables can be one of several different types in C++, and we will discuss variables and types at some length later.
Enter current year then press RETURN. 1996 Enter your current age in years. 36 Enter the year for which you wish to know your age. 2001 Your age in 2001: 41The first, third, fifth and seventh lines above are produced on the screen by the program. In general, the program statement
cout << Expression1 << Expression2 << ... << ExpressionN;will produce the screen output
Expression1Expression2...ExpressionNThe series of statements
cout << Expression1; cout << Expression2; ... ... cout << ExpressionN;will produce an identical output. If spaces or new lines are needed between the output expressions, these have to be included explicitly, with a " " or a "\n" respectively.
The numbers in bold in the example screen output above have been typed in by the user. In this particular program run, the program statement
cin >> year_now;has resulted in the variable year_now being assigned the value 1996 at the point when the user pressed RETURN after typing in "1996". Programs can also include assignment statements, a simple example of which is the statement
another_age = another_year - (year_now - age_now);Hence the symbol = means "is assigned the value of". ("Equals" is represented in C++ as ==.)
if (another_age >= 0) {
cout << "Your age in " << another_year << ": ";
cout << another_age << "\n";
}
else {
cout << "You weren't even born in ";
cout << another_year << "!\n";
}
The "if ... else ..." branching mechanism is a familiar construct in many
procedural programming languages. In C++, it is simply called an if
statement, and the general syntax is
if (condition) {
Statement1;
...
...
StatementN;
}
else {
StatementN+1;
...
...
StatementN+M;
}
The "else" part of an "if statement" may be omitted, and furthermore, if
there is just one Statement after the "if (condition)",
it may be simply written as
if (condition) Statement;
It is quite common to find "if statements" strung together in programs,
as follows:
...
...
if (total_test_score < 50)
cout << "You are a failure. You must study much harder.\n";
else if (total_test_score < 65)
cout << "You have just scraped through the test.\n";
else if (total_test_score < 80)
cout << "You have done quite well.\n";
else if (total_test_score < 95)
cout << "Your score is excellent. Well done.\n";
else {
cout << "You cheated!\n";
total_test_score = 0;
}
...
...
This program fragment has quite a complicated logical structure, but we
can confirm that it is legal in C++ by referring to the syntax diagram
for "if statements". In such diagrams, the terms enclosed in ovals or circles
refer to program components that literally appear in programs. Terms enclosed
in boxes refer to program components that require further definition, perhaps
with another syntax diagram. A collection of such diagrams can serve as
a formal definition of a programming language's syntax (although they do
not help distinguish between good and bad programming style!).
Below is the syntax diagram for an "if statement". It is best understood
in conjunction with the syntax diagram for a "statement" .
In particular, notice that the diagram doesn't explicitly include the
";" or "{}" delimiters, since these are built into the
definition (syntax diagram) of "statement".
The C++ compiler accepts the program fragment in our example by counting all of the bold text in
...
...
if (total_test_score < 50)
cout << "You are a failure. You must study much harder.\n";
else if (total_test_score < 65)
cout << "You have just scraped through the test.\n";
else if (total_test_score < 80)
cout << "You have done quite well.\n";
else if (total_test_score < 95)
cout << "Your score is excellent. Well done.\n";
else {
cout << "You cheated!\n";
total_test_score = 0;
}
...
...
as the single statement which must follow the first else.
#include <iostream.h> int main() { int year_now, age_now, another_year, another_age; cout <<
"Enter the current year then press RETURN.\n"; cin >> year_now;
cout << "Enter your current age in years.\n"; cin >> age_now; cout <<
"Enter the year for which you wish to know your age.\n"; cin >>
another_year; another_age = another_year - (year_now - age_now); if
(another_age >= 0) { cout << "Your age in " << another_year << ": ";
cout << another_age << "\n"; } else { cout <<
"You weren't even born in "; cout << another_year << "!\n"; } return
0; }
However, the lack of program comments, spaces, new lines
and indentation makes this program unacceptable. There is much more
to developing a good programming style than learning to lay out programs
properly, but it is a good start! Be consistent with your program layout,
and make sure the indentation and spacing reflects the logical structure
of your program. It is also a good idea to pick meaningful names for variables;
"year_now", "age_now", "another_year " and "another__age
"
are better names than "y_n", "a_n", "a_y" and
"a_a", and much better than "w", "x", "y"
and "z". Remember that your programs might need modification by
other programmers at a later date.
|
|
asm
auto break case catch char class const
|
|
continue
default delete do double else enum extern
|
|
float
for friend goto if inline int long
|
|
new
operator private protected public register return short
|
|
signed
sizeof static struct switch template this throw
|
|
try
typedef union unsigned virtual void volatile while
|
unsigned short int year_now, age_now, another_year, another_age;reserves memory for representing four relatively small non-negative integers.
Some rules have to be observed when writing integer values in programs:
answer = double(numerator) / denominatorthe "/" will always be interpreted as real-number division, even when both "numerator" and "denominator" have integer values. Other type names can also be used for type casting. For example, "int(14.35)" has an integer value of 14.
Character constants of type "char" must be enclosed in single quotation marks when used in a program, otherwise they will be misinterpreted and may cause a compilation error or unexpected program behaviour. For example, "'A'" is a character constant, but "A" will be interpreted as a program variable. Similarly, "'9'" is a character, but "9" is an integer.
There is, however, an important (and perhaps somewhat confusing) technical point concerning data of type "char". Characters are represented as integers inside the computer. Hence the data type "char" is simply a subset of the data type "int". We can even do arithmetic with characters. For example, the following expression is evaluated as true on any computer using the ASCII character set:
However, declaring a variable to be of type "char" rather than type "int" makes an important difference as regards the type of input the program expects, and the format of the output it produces. For example, the program
#include <iostream.h>
int main()
{
int number;
char character;
cout << "Type in a character:\n";
cin >> character;
number = character;
cout << "The character '" << character;
cout << "' is represented as the number ";
cout << number << " in the computer.\n";
return 0;
}
produces output such as
Type in a character: 9 The character '9' is represented as the number 57 in the computer.
We could modify the above program to print out the whole ASCII table
of characters using a "for loop". The "for loop" is an example of a repetition
statement - we will discuss these in more detail later. The general
syntax is:
for (initialisation; repetition_condition ; update) {
Statement1;
...
...
StatementN;
}
C++ executes such statements as follows: (1) it executes the initialisation
statement. (2) it checks to see if repetition_condition
is true. If it isn't, it finishes with the "for loop" completely. But if
it is, it executes each of the statements
Statement1 ...
StatementN
in turn, and then executes the expression update. After
this, it goes back to the beginning of step (2) again.
Hence to print out the ASCII table, the program above can be modified to:
#include <iostream.h>
int main()
{
int number;
char character;
for (number = 32 ; number <= 126 ; number = number + 1) {
character = number;
cout << "The character '" << character;
cout << "' is represented as the number ";
cout << number << " in the computer.\n";
}
return 0;
}
which produces the output:
The character ' ' is represented as the number 32 in the computer. The character '!' is represented as the number 33 in the computer. ... ... The character '}' is represented as the number 125 in the computer. The character '~' is represented as the number 126 in the computer.
in programs. In fact, "string" is not a fundamental data type
such as "int", "float" or "char". Instead, strings
are represented as arrays of characters, so we will return to subject of
strings later, when we discuss arrays in general.
#include <iostream.h>
#include <math.h>
int main()
{
float number;
cout << "Type in a real number.\n";
cin >> number;
cout.setf(ios::fixed); // LINE 10
cout.precision(2);
cout << "The square root of " << number << " is approximately ";
cout << sqrt(number) << ".\n";
return 0;
}
This produces the output
Type in a real number. 200 The square root of 200.00 is approximately 14.14.whereas replacing line 10 with "cout.setf(ios::scientific)" produces the output:
Type in a real number. 200 The square root of 2.00e+02 is approximately 1.41e+01.We can also include tabbing in the output using a statement such as "cout.width(20)". This specifies that the next item output will have a width of at least 20 characters (with blank space appropriately added if necessary). This is useful in generating tables:
#include <iostream.h>
#include <math.h>
int main()
{
int number;
cout.width(20);
cout << "Number" << "Square Root\n\n";
cout.setf(ios::fixed);
cout.precision(2);
for (number = 1 ; number <= 10 ; number = number + 1) {
cout.width(20);
cout << number << sqrt(number) << "\n";
}
return 0;
}
This program produces the output
Number Square Root 1 1.00 2 1.41 3 1.73 4 2.00 5 2.24 6 2.45 7 2.65 8 2.83 9 3.00 10 3.16(In fact, the above programs work because "cout" is an identifier for an object belonging to the class "stream", and "setf(...)", "precision(...)" and "width(...)" are member functions of "stream". Don't worry too much about this for now - you will learn more about objects, classes and member functions later in the object-oriented part of the course.)
float number;Between this statement and the first statement which assigns "number" an explicit value, the value contained in the variable "number" is arbitrary. But in C++ it is possible (and desirable) to initialise variables with a particular value at the same time as declaring them. Hence we can write
float PI = 3.1416;Furthermore, we can specify that a variable's value cannot be altered during the execution of a program with the reserved word "const":
enum { MON, TUES, WED, THURS, FRI, SAT, SUN };
is shorthand for
const int MON = 0; const int TUES = 1; const int WED = 2; const int THURS = 3; const int FRI = 4; const int SAT = 5; const int SUN = 6;By default, members of an "enum" list are given the values 0, 1, 2, etc., but when "enum" members are explicitly initialised, uninitialised members of the list have values that are one more than the previous value on the list:
enum { MON = 1, TUES, WED, THURS, FRI, SAT = -1, SUN };
In this case, the value of "FRI" is 5, and the value of "SUN"
is 0.
#include <iostream.h>
const float PI = 3.1416;
const float SCREEN_WIDTH = 317.24;
int drawCircle(float diameter); /* this is a "function prototype" */
int main()
{
float radius = 0;
cout << "Type in the radius of the circle.\n";
cin >> radius;
drawCircle(radius * 2);
cout.setf(ios::fixed);
cout.precision(2);
cout << "The circumference of a circle of radius " << radius;
cout << " is approximately " << 2 * PI * radius << ".\n";
return 0;
}
int drawCircle(float diameter)
{
float radius = 0;
if (diameter > SCREEN_WIDTH)
radius = SCREEN_WIDTH / 2.0;
else
radius = diameter / 2.0;
...
...
}
Ater the definition of "main()", this program includes a definition
of the function "drawCircle(...)", the details of which need not
concern us here (we can simply think of "drawCircle(...)" as a
function like "sqrt(...)"). But notice that although both "main()"
and "drawCircle(...)" use the identifier "radius", this
refers to a different variable in "main()" than in "drawCircle(...)".
Had a variable "radius" been declared before the "main"
program heading, it would have been a public or global variable.
In this case, and assuming there was no other variable declaration inside
the function "drawCircle(...)", if "drawCircle(...)"
had assigned it the value "SCREEN_WIDTH / 2.0", "main()"
would have subsequently printed out the wrong value for the circumference
of the circle. We say that the (first) variable "radius" is local
to the main part of the program, or has the function mainas
its scope. In contrast, it usually makes sense to make constants such
as "PI" and "SCREEN_WIDTH" global, i.e. available
to every function.
In any case, notice that the program above incorporates the safety measure
of echoing the input. In other words, the given value of "radius"
is printed on the screen again, just before the circumference of the circle
is output.
number = number + 1;Since it is often the case that variables are assigned a new value in function of their old value, C++ provides a shorthand notation. Any of the operators "+" (addition), "-" (subtraction), "*" (multiplication), "/" (division) and "%" (modulus) can be prefixed to the assignment operator (=), as in the following examples:
|
|
Example:
number += 1; total -= discount; bonus *= 2; time /= rush_factor; change %= 100; amount *= count1 + count2; |
|
Equivalent to:
number = number + 1; total = total - discount; bonus = bonus * 2; time = time / rush_factor; change = change % 100; amount = amount * (count1 + count2); |
The first of the above examples may written in even shorter form. Using the increment operator "++", we may simply write
number++;The operator "++" may also be used as a prefix operator:
++number;but care must be taken, since in some contexts the prefix and postfix modes of use have different effects. For example, the program fragment
x = 4; y = x++;results in "x" having the value 5 and "y" having the value 4, whereas
x = 4; y = ++x;results in both variables having value 5. This is because "++x" increments the value of "x" before its value is used, whereas "x++" increments the value afterwards. There is also an operator "--", which decrements variables by 1, and which can also be used in prefix or postfix form.
In general, assignment statements have a value equal to the value of the left hand side after the assignment. Hence the following is a legal expression which can be included in a program and which might be either evaluated as true or as false:
(y = ++x) == 5It can be read as the assertion: "after x is incremented and its new value assigned to y, y's value is equal to 5".
|
|
Expression:
(6 <= 6) && (5 < 3) (6 <= 6) || (5 < 3) (5 != 6) (5 < 3) && (6 <= 6) || (5 != 6) (5 < 3) && ((6 <= 6) || (5 != 6)) !((5 < 3) && ((6 <= 6) || (5 != 6))) |
|
True or False:
false true true true false true |
The fourth of these expressions is true because the operator "&&" has a higher precedence than the operator "||". You can check the relative precedence of the different C++ operators in a C++ programming manual or text book. But if in doubt use ( ) parentheses, which in any case often make the program easier to read.
Compound Boolean expressions are typically used as the condition in "if statements" and "for loops". For example:
... ... if (total_test_score >= 50 && total_test_score < 65) cout << "You have just scraped through the test.\n"; ... ...Once again, there is an important technical point concerning Boolean expressions. In C++, "true" is represented simply as the value 1 (or any positive integer in some systems), and "false" is represented as the value 0. This can lead to errors. For example, it is quite easy to type "=" instead of "==". Unfortunately, the program fragment
... ... if (number_of_people = 1) cout << "There is only one person.\n"; ... ...will always result in the message "There is only one person" being output to the screen, even if the previous value of the variable "number_of_people" was not 1.
We have already been using sub-programs. For example, in the program which generated a table of square roots, we used the following "for loop":
...
#include<math.h>
...
...
for (number = 1 ; number <= 10 ; number = number + 1) {
cout.width(20);
cout << number << sqrt(number) << "\n";
}
...
...
The function "sqrt(...)" is defined in a sub-program accessed
via the library file "math.h". The sub-program takes "number",
uses a particular algorithm to compute its square root, and then returns
the computed value back to the program. We don't care what the algorithm
is as long as it gives the correct result. It would be ridiculous to have
to explicitly (and perhaps repeatedly) include this algorithm in the "main"
program.
In this chapter we will discuss how the programmer can define his or
her own functions. At first, we will put these functions in the same file
as "main". Later we will see how to place different functions
in different files.
#include<iostream.h>
int area(int length, int width); /* function declaration */
/* MAIN PROGRAM: */
int main()
{
int this_length, this_width;
cout << "Enter the length: "; /* <--- line 9 */
cin >> this_length;
cout << "Enter the width: ";
cin >> this_width;
cout << "\n"; /* <--- line 13 */
cout << "The area of a " << this_length << "x" << this_width;
cout << " rectangle is " << area(this_length, this_width);
return 0;
}
/* END OF MAIN PROGRAM */
/* FUNCTION TO CALCULATE AREA: */
int area(int length, int width) /* start of function definition */
{
int number;
number = length * width;
return number;
} /* end of function definition */
/* END OF FUNCTION */
Although this program is not written in the most succinct form possible,
it serves to illustrate a number of features concerning functions:
double absolute_value(double number)
{
if (number >= 0)
return number;
else
return 0 - number;
}
Enter a positive integer: 4 The factorial of 4 is 24, and the square root of 4 is 2.
It would make sense to use the predefined function "sqrt(...)"
in our program, and write another function "factorial(...)" to
compute the factorial n! = (1 x 2 x ... x n) of any given positive integer
n. Here's the complete program:
#include<iostream.h>
#include<math.h>
int factorial(int number);
/* MAIN PROGRAM: */
int main()
{
int whole_number;
cout << "Enter a positive integer:\n";
cin >> whole_number;
cout << "The factorial of " << whole_number << " is ";
cout << factorial(whole_number);
cout << ", and the square root of " << whole_number << " is ";
cout << sqrt(whole_number) << ".\n";
return 0;
}
/* END OF MAIN PROGRAM */
/* FUNCTION TO CALCULATE FACTORIAL: */
int factorial(int number)
{
int product = 1;
for ( ; number > 0 ; number--)
product *= number;
return product;
}
/* END OF FUNCTION */
By the use of a value parameter, we have avoided the (correct but
unwanted) output
Enter a positive integer: 4 The factorial of 4 is 24, and the square root of 1 is 1.which would have resulted if the function "factorial(...)" had permanently changed the value of the variable "whole_number".
int area(int length, int width);
void get_dimensions(int& length, int& width);
/* MAIN PROGRAM: */
int main()
{
int this_length, this_width;
get_dimensions(this_length, this_width);
cout << "The area of a " << this_length << "x" <<
this_width;
cout << " rectangle is " << area(this_length, this_width);
return 0;
}
/* END OF MAIN PROGRAM */
/* FUNCTION TO INPUT RECTANGLE DIMENSIONS: */
void get_dimensions(int& length, int& width)
{
cout << "Enter the length: ";
cin >> length;
cout << "Enter the width: ";
cin >> width;
cout << "\n";
}
/* END OF FUNCTION */
/* FUNCTION TO CALCULATE AREA: */
int area(int length, int width)
{
return length * width;
}
/* END OF FUNCTION */
Notice that, although the function "get_dimensions" permanently alters the values of the parameters "this_length" and "this_width", it does not return any other value (i.e. is not a "function" in the mathematical sense). This is signified in both the function declaration and the function heading by the reserved word "void".
#include<iostream.h>
int average(int first_number, int second_number, int third_number);
int average(int first_number, int second_number);
/* MAIN PROGRAM: */
int main()
{
int number_A = 5, number_B = 3, number_C = 10;
cout << "The integer average of " << number_A << " and ";
cout << number_B << " is ";
cout << average(number_A, number_B) << ".\n\n";
cout << "The integer average of " << number_A << ", ";
cout << number_B << " and " << number_C << " is ";
cout << average(number_A, number_B, number_C) << ".\n";
return 0;
}
/* END OF MAIN PROGRAM */
/* FUNCTION TO COMPUTE INTEGER AVERAGE OF 3 INTEGERS: */
int average(int first_number, int second_number, int third_number)
{
return ((first_number + second_number + third_number) / 3);
}
/* END OF FUNCTION */
/* FUNCTION TO COMPUTE INTEGER AVERAGE OF 2 INTEGERS: */
int average(int first_number, int second_number)
{
return ((first_number + second_number) / 2);
}
/* END OF FUNCTION */
This program produces the output:
The integer average of 5 and 3 is 4. The integer average of 5, 3 and 10 is 6.
Developing functions in this manner is referred to as functional or procedural abstraction. This process is aided by the use of value parameters and local variables declared within the body of a function. Functions written in this manner can be regarded as "black boxes". As users of the function, we neither know nor care why they work.
The code in the main program file is as follows:
#include<iostream.h> #include"averages.h" int main() { int number_A = 5, number_B = 3, number_C = 10; cout << "The integer average of " << number_A << " and "; cout << number_B << " is "; cout << average(number_A, number_B) << ".\n\n"; cout << "The integer average of " << number_A << ", "; cout << number_B << " and " << number_C << " is "; cout << average(number_A, number_B, number_C) << ".\n"; return 0; }
Notice that whereas "include" statements for standard libraries
such as "iostream.h" delimit the file name with angle ("<>")
brackets, the usual convention is to delimit user-defined library file
names with double quotation marks - hence the line " #include"averages.h"
" in the listing above.
The code in the header file ">averages.h>" is listed below. Notice the use of the file identifier "AVERAGES_H", and the reserved words "ifndef" ("if not defined"), "define", and "endif". "AVERAGES_H" is a (global) symbolic name for the file. The first line and last two lines of code ensure that the compiler (in fact, the preprocessor) only works through the code in between once, even if the line " #include"averages.h" " is included in more than one other file.
Constant and type definitions are also often included in header files. You will learn more about this in the object-oriented part of the course.
#ifndef AVERAGES_H
/* (constant and type definitions could go here) */
/* FUNCTION TO COMPUTE INTEGER AVERAGE OF 3 INTEGERS: */
int average(int first_number, int second_number, int third_number);
/* FUNCTION TO COMPUTE INTEGER AVERAGE OF 2 INTEGERS: */
int average(int first_number, int second_number);
#define AVERAGES_H
#endif
Finally, the code in the implementation file "averages.cp" is as
follows:
#include<iostream.h>
#include"averages.h"
/* FUNCTION TO COMPUTE INTEGER AVERAGE OF 3 INTEGERS: */
int average(int first_number, int second_number, int third_number)
{
return ((first_number + second_number + third_number) / 3);
}
/* END OF FUNCTION */
/* FUNCTION TO COMPUTE INTEGER AVERAGE OF 2 INTEGERS: */
int average(int first_number, int second_number)
{
return ((first_number + second_number) / 2);
}
/* END OF FUNCTION */
Note the modularity of this approach. We could change the details
of the code in "averages.cp" without making any changes to the code in
"averages.h" or in the main program file.
The easiest way to think about a file is as a linear sequence of characters. In a simplifed picture (which ignores special characters for text formatting) these lecture notes might be stored in a file called "Lecture_4" as:
Figure 4.1.1
In fact, input and output streams such as "cin" and "cout" are examples of (stream) objects. So learning about streams is a good way to introduce some of the syntax and ideas behind the object-oriented part of C++. The header file which lists the operations on streams both to and from files is called "fstream.h". We will therefore assume that the program fragments discussed below are embedded in programs containing the "include" statement
As we shall see, the essential characteristic of stream processing is
that data elements must be sent to or received from a stream one at a time,
i.e. in serial fashion.
ifstream in_stream; ofstream out_stream;respectively create a stream called "in_stream" belonging to the class (like type) "ifstream" (input-file-stream), and a stream called "out_stream" belonging to the class "ofstream" (output-file-stream). However, the analogy between streams and ordinary variables (of type "int", "char", etc.) can't be taken too far. We cannot, for example, use simple assignment statements with streams (e.g. we can't just write "in_stream1 = in_stream2").
To connect the ifstream "in_stream" to the file "Lecture_4", we use the following statement:
in_stream.open("Lecture_4");
This connects "in_stream" to the beginning of "Lecture_4". Diagramatically,
we end up in the following situation:
Figure 4.2.1
To connect the ofstream "out_stream" to the file "Lecture_4", we use an analogous statement:
out_stream.open("Lecture_4");
Although this connects "out_stream" to "Lecture_4", it also deletes
the previous contents of the file, ready for new input. Diagramatically,
we end up as follows:
Figure 4.2.2
To disconnect connect the ifstream "in_stream" to whatever file it is connected to, we write:
in_stream.close();Diagramatically, the situation changes from that of Figure 4.2.1 to:
Figure 4.2.3
The statement:
out_stream.close();has a similar effect, but in addition the system will "clean up" by adding an "end-of-file" marker at the end of the file. Thus, if no data has been output to "Lecture_4" since "out_stream" was connected to it, we change from the situation in Figure 4.2.2 to:
Figure 4.2.4
In this case, the file "Lecture_4" still exists, but is empty.
in_stream.fail();returns TRUE if the previous stream operation on "in_stream" was not successful (perhaps we tried to open a file which didn't exist). If a failure has occurred, "in_stream" may be in a corrupted state, and it is best not to attempt any more operations with it. The following example program fragment plays very safe by quitting the program entirely, using the "exit(1)" command from the library "stdlib.h":
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
int main()
{
ifstream in_stream;
in_stream.open("Lecture_4");
if (in_stream.fail())
{
cout << "Sorry, the file couldn't be opened!\n";
exit(1);
}
...
in_stream.get(ch);has two effects: (i) the variable "ch" is assigned the value "'4'", and (ii) the ifstream "in_stream" is re- positioned so as to be ready to input the next character in the file. Diagramatically, the new situation is:
Figure 4.4.1
out_stream.put('4');
changes the state to:
Figure 4.4.2
in_stream.putback(ch);we would end up in the state:
Figure 4.4.3
Indeed, we can "putback" any character we want to. The alternative statement
in_stream.putback('7');
would result in:
Figure 4.4.4
In such systems, when an ifstream is initially connected to a file, the EOF flag is set to FALSE (even if the file is empty). However, if the ifstream "in_stream" is positioned at the end of a file, and the EOF flag is FALSE, the statement
in_stream.get(ch);will leave the variable "ch" in an unpredictable state, and set the EOF flag to TRUE. Once the EOF flag is set to TRUE, no attempt should be made to read from the file, since the results will be unpredictable. To illustrate with a diagramatic example, if we start from
Figure 4.5.1
and then execute the statement
in_stream.get(ch);this results in the state
Figure 4.5.2
If we again execute the statement
in_stream.get(ch);this now results in the state
Figure 4.5.3
The boolean expression
in_stream.eof()will now evaluate to TRUE.
Below is a simple program which uses these techniques to copy the file
"Lecture_4" simultaneously to the screen and to the file "Copy_of_4". Note
the use of a while loop in this program. "While" loops are simplified
versions of "for" loops, without the initialisation and update statements
in the "()" parentheses (we will look at such statements again
in the next section).
#include <iostream.h>
#include <fstream.h>
int main()
{
char character;
ifstream in_stream;
ofstream out_stream;
in_stream.open("Lecture_4");
out_stream.open("Copy_of_4");
in_stream.get(character);
while (!in_stream.eof())
{
cout << character;
out_stream.put(character);
in_stream.get(character);
}
out_stream.close();
in_stream.close();
return 0;
}
in_stream.get(ch);
Figure 4.5.3
This doesn't matter too much, as long as we are careful not to use "in_stream"
for any more operations. For such systems, the program can be rewritten
as
#include <iostream.h> #include <fstream.h> int main() { char character; ifstream in_stream; ofstream out_stream; in_stream.open("Lecture_4"); out_stream.open("Copy_of_4"); in_stream.get(character); while (!in_stream.fail()) { cout << character; out_stream.put(character); in_stream.get(character); } out_stream.close(); in_stream.close(); return 0; }
#include <iostream.h>
#include <fstream.h>
void copy_to(ifstream& in, ofstream& out);
/* MAIN PROGRAM: */
int main()
{
ifstream in_stream;
ofstream out_stream;
in_stream.open("Lecture_4");
out_stream.open("Copy_of_4");
copy_to(in_stream, out_stream);
out_stream.close();
in_stream.close();
return 0;
}
/* END OF MAIN PROGRAM */
/* FUNCTION TO COPY A FILE TO ANOTHER FILE AND TO THE SCREEN: */
void copy_to(ifstream& in, ofstream& out)
{
char character;
in.get(character);
while (!in.fail())
{
cout << character;
out.put(character);
in.get(character);
}
}
/* END OF FUNCTION */
However, the operators ">>" and "<<", which we have already met in the context of keyboard input and screen output, do some of this conversion automatically. For example, from the state
Figure 4.7.1
execution of the statement
out_stream << 437 << ' ';will result in the new state
Figure 4.7.2
When using these higher level facilities, it is important to include at least one "' '" (blank) character (or a new-line character) after each item of data written. This ensures that the data items are correctly separated in the file, ready for input using ">>". For example, from the state
Figure 4.7.3
where "n" has been declared as of data type "int", execution of the statement
in_stream >> n;
will result in the new state
Figure 4.7.3
Notice that the operation ">>" has skipped over the blank space "' '" before the number 437. It always does this, no matter what data type it has been reading or expects to read (even characters).
The following program, which first creates a file called "Integers" containing the integers 51, 52, 53, 54 and 55, further illustrates these techniques.
#include <iostream.h>
#include <fstream.h>
int main()
{
char character;
int number = 51;
int count = 0;
ofstream out_stream;
ifstream in_stream1; /* Stream for counting integers. */
ifstream in_stream2; /* Stream for counting characters. */
/* Create the file */
out_stream.open("Integers");
for (count = 1 ; count <= 5 ; count++)
out_stream << number++ << ' ';
out_stream.close();
/* Count the integers in the file */
in_stream1.open("Integers");
count = 0;
in_stream1 >> number;
while (!in_stream1.fail())
{
count++;
in_stream1 >> number;
}
in_stream1.close();
cout << "There are " << count << " integers in the file,\n";
/* Count the non-blank characters */
in_stream2.open("Integers");
count = 0;
in_stream2 >> character;
while (!in_stream2.fail())
{
count++;
in_stream2 >> character;
}
in_stream2.close();
cout << "represented using " << count << " characters.\n";
return 0;
}
This program produces the following output:
There are 5 integers in the file, represented using 10 characters.Once again, notice that, unlike the function "get(...)", the operator ">>" has jumped over the blank (i.e. space) characters in the file (which separate the five integers) when used in counting characters in the last part of the program.
As we have seen, in reality C++ represents "True" as the integer 1, and "False" as 0. However, expressions such as
or
aren't particularly clear - it would be better to be able to follow our intuition and write
and
Furthermore, it is desirable to have a seperate type for variables such as "condition1", rather than having to declare them as of type "int". We can achieve all of this with a named enumeration:
enum Logical {False, True}
which is equivalent to
enum Logical {False = 0, True = 1}
This line acts a kind of type definition for a new data type "Logical",
so that lower down the program we can add variable declarations such as:
Logical condition1, condition2;
Indeed, we can now use the identifier "Logical" in exactly the
same way as we use the identifiers "int", "char", etc.
In particular, we can write functions which return a value of type "Logical".
The following example program takes a candidate's age and test score, and
reports whether the candidate has passed the test. It uses the following
criteria: candidates between 0 and 14 years old have a pass mark of 50%,
15 and 16 year olds have a pass mark of 55%, over 16's have a pass mark
of 60%:
#include <iostream.h>
enum Logical {False, True};
Logical acceptable(int age, int score);
/* START OF MAIN PROGRAM */
int main()
{
int candidate_age, candidate_score;
cout << "Enter the candidate's age: ";
cin >> candidate_age;
cout << "Enter the candidate's score: ";
cin >> candidate_score;
if (acceptable(candidate_age, candidate_score))
cout << "This candidate passed the test.\n";
else
cout << "This candidate failed the test.\n";
return 0;
}
/* END OF MAIN PROGRAM */
/* FUNCTION TO EVALUATE IF TEST SCORE IS ACCEPTABLE */
Logical acceptable(int age, int score)
{
if (age <= 14 && score >= 50)
return True;
else if (age <= 16 && score >= 55)
return True;
else if (score >= 60)
return True;
else
return False;
}
/*END OF FUNCTION */
Note that since "True" and "False" are constants,
it makes sense to declare them outside the scope of the main program, so
that the type "Logical" can be used by every function in the file.
An alternative way to write the above function "acceptable(...)"
would be:
/* FUNCTION TO EVALUATE IF TEST SCORE IS ACCEPTABLE */
Logical acceptable(int age, int score)
{
Logical passed_test = False;
if (age <= 14 && score >= 50)
passed_test = True;
else if (age <= 16 && score >= 55)
passed_test = True;
else if (score >= 60)
passed_test = True;
return passed_test;
}
&