February 04 (Wednesday) February 09 (Monday)
This chapter is fairly straightforward. You should already be familiar with most of the concepts presented. Some of the more subtle issues are presented in more detail after the program below. The topics in the chapter that you should already know from your Java background and the C lectures are:
while
and for
loops (K&M §
2.3.1, 2.5.2),
if...else
) (K&M §
2.4.1.1),
!=
, ==
,
<
, etc.) (K&M § 2.3.1),
||
and &&
(K&M § 2.4.1.2).
We will not be discussing the above ideas in class. Consult the text if you are unfamiliar with these language features -- they are pretty much the same for Java, C and C++.
This chapter also introduces the concept of a loop invariant
(K&M § 2.3.2). This concept can be quite useful when designing
while
loops for example, and can help reduce or eliminate
subtle off-by-one errors which can cause severe problems in
programs. We won't be talking too much about loop invariants in class --
the presentation in the textbook is quite clear.
There is a precedence table on page 32. Don't feel obligated to memorize it. Refer to it when you are in doubt about the order of execution of various operators. (Incidentally, K&R also have a precedence table on page 53).
Chapter 2 describes the implementation of the following program which,
like Chapter 1, displays a frame around a greeting, except that the
code is a bit more flexible in that an arbitrary number of padding spaces
can be defined between the greeting and the border. We can change how
much padding occurs by changing the initialization of the pad
constant and recompiling and rerunning the program. The asterisks and
spaces that comprise the output are displayed one at a time (instead of
as strings, which was how they were displayed in Chapter 1).
#include <iostream>
#include <string>
// say what standard-library names we use
using std::cin; using std::endl;
using std::cout; using std::string;
int main()
{
// ask for the person's name
cout << "Please enter your first name: ";
// read the name
string name;
cin >> name;
// build the message that we intend to write
const string greeting = "Hello, " + name + "!";
// the number of blanks surrounding the greeting
const int pad = 1;
// the number of rows and columns to write
const int rows = pad * 2 + 3;
const string::size_type cols = greeting.size() + pad * 2 + 2;
// write a blank line to separate the output from the input
cout << endl;
// write `rows' rows of output
// invariant: we have written `r' rows so far
for (int r = 0; r != rows; ++r) {
string::size_type c = 0;
// invariant: we have written `c' characters so far
// in the current row
while (c != cols) {
// is it time to write the greeting?
if (r == pad + 1 && c == pad + 1) {
cout << greeting;
c += greeting.size();
} else {
// are we on the border?
if (r == 0 || r == rows - 1 ||
c == 0 || c == cols - 1)
cout << "*";
else
cout << " ";
++c;
}
}
cout << endl;
}
return 0;
}
frame02.cpp
The program should be straightforward to follow -- it contains two loops to display the appropriate characters and greeting at the right time. You should be able to understand the logic behind the program. The textbook gives a detailed description of the evolution of the code and gives a good justification for many of the decisions adopted by the program. There are some new concepts being introduced in the above program:
bool
, whose valid values
include true
and false
. This type is not
present in C; it is analogous to Java's boolean
type.
Conditional expressions all have bool
type in C++.
When used in a conditional context, zero values will be converted
to false
and non-zero values will be converted to
true
. Outputting a bool
will cause
the boolean to be converted to 0
or 1
.
#include <iostream>
int main()
{
int num;
std::cin >> num;
bool t = true;
t = num != 10;
std::cout << t << std::endl;
}
bool.cpp
Note that in C++, you can define variables almost anywhere you want. In C, you had to declare them at the beginning of a block.
using
declarations:
using std::cin; using std::endl; using std::cout; using std::string;
These declarations provide a way to import a limited number of symbols
from the std
namespace. With these declarations in
place, we can just write cout
in our program instead
of std::cout
all the time. The same idea applies
to endl
and even string
. Here is our
first Hello world!
program from last day written with
using
declarations:
#include <iostream>
using std::cout;
using std::endl;
int main()
{
cout << "Hello world!" << endl;
return 0;
}
hello2.cpp
greeting.size()
, which determines how
many characters are in the greeting
string, is of particular
interest. The type returned is determined by the string
class itself. We must use the scope resolution operator to access the
type within the string
class as follows:
std::string::size_type
This type can store the length of the longest possible string
object. Of course, if we say using std::string;
, we can
access the above type simply by saying string::size_type
.
When assigning the result of the size()
method of a string
object to a variable, we must make sure that the variable is of type
std::string::size_type
:
using std::string; ... string::size_type len; string word; cin >> word; len = word.size();
Many classes define a size_type
type and the type is
accessible the same way (i.e. using the scope resolution
operator).
for
loop is r != rows
. Section 2.6
gives some justification for this decision as well as a discussion of
half-open ranges. We'll see another reason why the relational
operator !=
is used to terminate a loop when we study
iterators and containers in the standard library.
The sample programs in this chapter deal with reading a list of students grades (midterm, final, homework assignments) from standard input and calculating a final mark.
The first program below uses the average of the homework assignment marks in the calculation of the final grade. After obtaining the student's name, midterm and final exam marks, the program loops to input all the assignment marks and keeps a running total of these marks. Finally, it displays the overall final grade (using the average mark for all the assignments) for the student (with appropriate numeric precision).
#include <iomanip>
#include <iostream>
#include <ios>
#include <string>
using std::cin; using std::setprecision;
using std::cout; using std::string;
using std::endl; using std::streamsize;
int main()
{
// ask for and read the student's name
cout << "Please enter your first name: ";
string name;
cin >> name;
cout << "Hello, " << name << "!" << endl;
// ask for and read the midterm and final grades
cout << "Please enter your midterm and final exam grades: ";
double midterm, final;
cin >> midterm >> final;
// ask for the homework grades
cout << "Enter all your homework grades, "
"followed by end-of-file: ";
// the number and sum of grades read so far
int count = 0;
double sum = 0;
// a variable into which to read
double x;
// invariant:
// we have read `count' grades so far, and
// `sum' is the sum of the first `count' grades
while (cin >> x) {
++count;
sum += x;
}
// write the result
streamsize prec = cout.precision();
cout << "Your final grade is " << setprecision(3)
<< 0.2 * midterm + 0.4 * final + 0.4 * sum / count
<< setprecision(prec) << endl;
return 0;
}
avg.cpp
Some of the salient features of the above program are given below (they are all explained in greater detail in the text):
midterm
and
final
, both of type double
) simultaneously
using the cin
object and two >>
operators:
cin >> midterm >> final;
+
operator (doing so would not work). Rather,
the concatenation is implicit.
cin >> x
expression returns its left operand,
cin
, which can be used in a conditional. If the input
was successful, then using the resulting cin
object
in a conditional will return true
. If cin
receives invalid input or hits the end of input (e.g. a Ctrl-D
from the keyboard), testing the cin
object in a conditional
context will yield false
. The program takes advantage
of this fact to input several homework grades in a while
loop. Incidentally, cin
is of type istream
.
(Remember that cout
is of type ostream
.)
setprecision()
manipulator,
which is declared in the <iomanip>
header. A
manipulator is an entity that, when sent to a stream, modifies how
the stream behaves. In the context of the above program, if we
send setprecision(3)
to the cout
object,
then writing subsequent floating point numbers will result in
three significant digits being displayed to standard output.
cout
stream to its original state (i.e. to undo
the effects of any manipulators that we used on the stream). In our
case, we want to restore the original precision. In order to do this,
we must obtain the original precision and store it in a variable before
we actually do the output. The original precision can be retrieved by
calling the precision()
method of the cout
object before we do any output. The only question is: What type does
cout.precision()
return? It turns out that it returns
a value of type std::streamsize
. This type is defined in
the <ios>
header. Hence we can save the original
precision, modify the precision and restore the original precision using
the following two statements.
streamsize prec = cout.precision(); cout << "Your final grade is " << setprecision(3) << 0.2 * midterm + 0.4 * final + 0.4 * sum / count << setprecision(prec) << endl;
This program is similar to the one above except that instead of using the average mark of the homework to calculate the student's final overall mark, it uses the median (i.e. "middle") assignment mark. In order to determine the median, we must input and store all the assignment marks, sort them, then pick the one in the middle of the sorted list.
#include <algorithm>
#include <iomanip>
#include <ios>
#include <iostream>
#include <string>
#include <vector>
using std::cin; using std::sort;
using std::cout; using std::streamsize;
using std::endl; using std::string;
using std::setprecision; using std::vector;
int main()
{
// ask for and read the student's name
cout << "Please enter your first name: ";
string name;
cin >> name;
cout << "Hello, " << name << "!" << endl;
// ask for and read the midterm and final grades
cout << "Please enter your midterm and final exam grades: ";
double midterm, final;
cin >> midterm >> final;
// ask for and read the homework grades
cout << "Enter all your homework grades, "
"followed by end-of-file: ";
vector<double> homework;
double x;
// invariant: `homework' contains all the homework grades read so far
while (cin >> x)
homework.push_back(x);
// check that the student entered some homework grades
typedef vector<double>::size_type vec_sz;
vec_sz size = homework.size();
if (size == 0) {
cout << endl << "You must enter your grades. "
"Please try again." << endl;
return 1;
}
// sort the grades
sort(homework.begin(), homework.end());
// compute the median homework grade
vec_sz mid = size/2;
double median;
median = size % 2 == 0 ? (homework[mid] + homework[mid-1]) / 2
: homework[mid];
// compute and write the final grade
streamsize prec = cout.precision();
cout << "Your final grade is " << setprecision(3)
<< 0.2 * midterm + 0.4 * final + 0.4 * median
<< setprecision(prec) << endl;
return 0;
}
med.cpp
Some of the important features of the above program are presented below (again, they are all explained in greater detail in the text):
homework
variable:
vector<double> homework;
This is defining a vector
container type that holds
double
values. A vector
(which is declared
in the <vector>
header) is very similar to an array
in C, except that it is more powerful. We use the homework
variable to store all the homework assignment grades that we read from
standard input so that we can calculate the median. Note that, unlike
an array in C, we do not specify the number of elements that the vector
can hold.
vector
is an example of a template class.
Because it is a template class, we can define a vector
that stores values of any uniform type. For example:
vector<int> naturals; // a vector of ints vector<char> letters; // a vector of chars vector<string> names; // a vector of strings
push_back()
member function of the vector
class, which stores new
elements at the end of the vector:
There is no need to worry about memory allocation details when storing values in ahomework.push_back(x);
vector
-- the vector
class will
automatically grow to accommodate new elements. Note that if we tried to
call push_back()
with a non-double
parameter,
the compiler will generate an error because the homework
vector can store only doubles.
string
class, the vector
class has a size()
member function which returns
the number of elements currently stored in the vector. Again,
as with the string
class, the type returned by this
method is defined within the vector
class itself.
For a vector containing double values, we can access this type
using the scope resolution operator:
vector<double>::size_type
Because the program requires us to define two variables of this
type, the program uses a typedef
to create a synonym for
the vector's size type:
typedef vector<double>::size_type vec_sz;
Now, whenever we want to create a variable that can store the number of elements in a vector of doubles, we can simply write:
vec_sz size = homework.size();
This saves us the trouble of having to write
vector<double>::size_type
all the time.
vector
, we must sort them in order to calculate
the median. To do so, we use the sort
function which is
defined in the <algorithm>
header.
// sort the grades sort(homework.begin(), homework.end());
The begin()
and end()
methods of the
vector
class denote the first element and one
past the last element in the vector. These parameters basically
specify the interval of elements within the vector that we want
to sort. Using begin()
and end()
will sort
all the elements.
[]
, to access the double
s stored in the
homework
vector. This is very similar to how we access
elements in an array in C. Note that, like C (but unlike Java), the
indexing operator does not check the index value to
make sure that it is within bounds. If the index is out of range,
bad things can happen.
When calculating the median, we test if the number of homework assignments was even or odd. In the former case we take the average of the two grades in the middle; in the latter case we simply use the grade that occupies the center position of the vector.
vec_sz mid = size/2; double median; median = size % 2 == 0 ? (homework[mid] + homework[mid-1]) / 2 : homework[mid];
Last modified: February 9, 2004 00:40:54 NST (Monday)