Chapter 19 Standard Template Library

advertisement
Chapter 19
Standard Template Library
0. Introduction
The Standard Template Library (STL) is a subset of the ISO/ANSI C++ standard library.
The STL provides five kinds of components and facilities for C++ programmers to use:
containers, iterators, generic algorithms, function objects, and allocators.
A container is an object that holds other objects. We have seen linked list classes, the
primitive array and the STL vector that can contain other objects.
An iterator is an object used to specify a position in a container. All iterators can be
incremented to move the position towards the end by one or dereferenced to fetch the
value in the container to which the iterator refers. Thus iterators allow traversal of a
container without compromising the internal structure of the container.
A generic algorithm, or just algorithm, is a template function that uses iterator types for
its parameter types. Generic algorithms are used to manipulate the contents of a container
for which an iterator type with sufficient strength is available.
A function object is a class that overloads the function call operator, operator () so
that an object of this class can be used like a function and can be passed as a template
parameter.
An allocator makes room in memory for STL components. Each container is given a
memory allocator that the container's constructor uses as it needs memory. Allocators are
required in environments with multiple memory models. Typically, 16 bit PC
applications require allocators beyond the defaults provided in the STL. Except for
embedded applications, these are apparently obsolete. Our purposes are well served by
the default allocators, and allocators are beyond the scope of the text and this document,
we will not discuss allocators. See the book by Josuttis in the text's references.
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
Page 2
1. Outline of topics in the chapter
19.1 Iterators
Iterator Basics
Kinds of Iterators
Constant and Mutable Iterators
Reverse Iterators
Pitfall: Compiler Problems
Other kinds of Iterators
19.2 Containers
Sequential Containers
Pitfall: Iterators and Removing Elements
Tip: Type Definitions in Containers
The Container Adapters stack and queue
The Associative Containers set and map
Efficiency
19.3 Generic Algorithms
Running Times and Big-O Notation
Container Acce3ss Running Times
Nonmodifying Sequence Algorithms
Modifying Sequence Algorithms
Set Algorithms
Sorting Algorithms
2. General remarks on the chapter
19.1 Iterators
Iterator Basics
In many data structures and in containers, we need some idea of position or location of an
item in the structure. In C, and in C++ before the Standard Template Library (STL), the
pointer met this need. Pointers have the advantage of speed but are dangerous. The STL
provides the iterator as a replacement for pointers that is as efficient as a pointer but is
much safer.
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
Page 3
The iterator is an object used to specify a position in a container and is a generalization
of the notion of pointer. The syntax for manipulating an iterator is a subset of the syntax
of pointers. To access an item referred to by an iterator iter, one writes *iter. Such
an access can be used to fetch the value of the element (used as an r-value) or to assign
this value (used as an l-value). To step the iterator forward to refer to the next item in the
container, one writes iter++. Thus iterators allow traversal of a container without
compromising the internal structure of the container.
Each STL container defines one or more iterator types, specified by syntax such as
vector<int>::iterator. The specific operations and properties of the iterator
defined by a container depend heavily on what can be efficiently implemented given
internal the structure of the container. The inner workings of the iterator, such as the
detail of advancing the position in the container are also specific to the container being
used and are hidden from the user.
In addition to overloading the dereferencing operator (*), the following operators are
overloaded for some classes of iterators. prefix and postfix forms of the ++ and -operator, equal and unequal operators (== and !=). Each of the STL containers provides
functions that return the first and last elements in the container. If c is a container, then
c.front() returns the first element in the container.
c.back() returns the last element in the container.
There are also member functions that return iterators that refer to special places in the
container.
c.begin() returns an iterator that refers to the first data item in the container.
c.end() returns an iterator that refers to a position beyond the end of the
container.
Do not to confuse the past-end iterator value and the null pointer value. That there is an
essential difference between these ideas can be seen from the following scenario.
Suppose you have an iterator of a type that supports the decrement operator, --, and
suppose the iterator has the past-end value for a container that is not empty. You can
decrement the iterator with the result being an iterator that refers to the same element that
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
Page 4
that c.back()returns. You cannot decrement or increment the null pointer to obtain
anything useful. There is a programming analogy, but you should take care not to confuse
these ideas.
In the next section we see that the STL classifies iterators according to the features
available to the iterator.
Kinds of Iterators
Different STL containers have different properties, so iterators for the various containers
also have different properties. Similarly, different STL algorithms require different
properties of the iterators they use. For example, sorting a container requires random
access; otherwise, performance suffers. Consequently, iterators are classified in
increasing "strength" of properties as input iterators, output iterators, forward iterators,
bidirectional iterators, and random access iterators. The following figure displays the
relationships among the iterator categories. We will see later that if an STL algorithm
requires an iterator of a particular category, then the programmer can use an iterator of
that category or of any stronger category, but not of a weaker category.
Random
Access
Iterators
are
Bidirectional
are
Forward
are
Input &
stronger
Iterators
stronger Iterators stronger Output
than
than
than
Iterators
Some additional types of iterators are reverse iterators, stream iterators, insertion
iterators, and raw storage iterators. Reverse iterators allow iteration through containers
from the back to the front. Stream iterators allow you to read and write files from STL
algorithms. Insertion iterators allow adding new items to the container rather than
overwriting the position referred to by the iterator.
To say random access means that all accesses take the same time, that is the runtime for
accesses are O(1). Random access iterators provide random access (in this sense) to the
elements in the container that defines the iterator type. In the STL, random access
iterators provide indexing and the mixed iterator expression of adding an integer
constant, n, to an iterator, iter . The expression , n + iter, yields an iterator value
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
Page 5
that refers to the element n elements away from the element referred to by iter, in a
direction that depends on the sign of n, provided there is an element at that position.
Input Iterators
Input iterators can only be stepped forward by one item per step. Furthermore, they may
be dereferenced only to fetch (but not store into) an item from a collection. The following
table displays the allowable operations on an input iterator.
Input Iterator Operations
r and r2 are input iterators.
value =*r Provides read access to the item at the position denoted by r
r++
Steps r forward one position in the container; returns the old position
++r
Steps r forward one position in the container; returns the new position
r == r2
Returns true if r and r2 denote the same position, else returns false
r != r2
Returns true if r and r2 denote different positions, else returns false
Output Iterators
As with input iterators, output iterators can only be stepped forward by one item per step.
However, output iterators may be dereferenced only to store a value at (but not fetch a
value from) the specified location. The next table shows the allowable operations on an
output iterator.
Output Iterator Operations
r is an output iterator.
*r = value
Stores value at the position denoted by r
r++
Steps r forward one position in the container; returns the old position
++r
Steps r forward one position in the container; returns the new position
At first, it might seem strange that we can modify something but not fetch it, but it begins
to make more sense once you know that output iterators are most often used for sending
data to an output stream such as cout. (The iostream header file provides output
stream iterators.) The idea is to treat an output stream as a "container" of characters and,
using an output iterator, write each new character to the stream, one by one. We will not
discuss stream iterators further.
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
Page 6
Forward Iterators
We have looked at two categories of iterators that step forward through a container, one
permitting only fetching, the other permitting only storing. Often we would like to both
fetch and store a value at a particular position as we step through a container. Forward
iterators allow us to do so. Specifically, the valid operations on a forward iterator are the
same as those of an input iterator plus the ability to store into the referenced item (see the
next table).
Forward Iterator Operations
r and r2 are forward iterators.
value = *r
Provides read access to the item at the position denoted by r
*r = value
Stores value at the position denoted by r
r++
Steps r forward one position in the container; returns the old position
++r
Steps r forward one position in the container; returns the new position
r == r2
Returns true if r and r2 denote the same position, else returns false
r != r2
Returns true if r and r2 denote different positions, else returns false
Bidirectional Iterators
Bidirectional iterators have all the properties of forward iterators but allow backward
movement through a container as well as forward.
Bidirectional Iterator Operations
Add the following operations to the operations for forward iterators,
r-- Steps r backward one position in the container; returns the old position
--r Steps r backward one position in the container; returns the new position
Adding the decrement operator makes some algorithms easier to implement. We will see
that all the (Standard) STL containers support either bidirectional iterators or random
access iterators. The list container provides bidirectional iterators. (Hence, some
people like to think of list as an abstraction of a doubly linked list because it allows
efficient traversal in either direction. Actually, the STL list is more than just an
abstraction of a doubly linked list because the STL list is almost always implemented
as a doubly linked list.)
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
Page 7
Random Access Iterators
Random access iterators have all the properties of bidirectional iterators but support
additional operations to allow random (direct) access to any item in a container.
Random Access Iterator Operations
Add the following operations to the operations on bidirectional iterators,.
r[i]
Provides indexed read or store access to the item at the position denoted by r
r += i
Steps r forward by i positions in the container
r -= i
Steps r backward by i positions in the container
r + i
Returns an iterator value that is i positions beyond r in the container
r – i
Returns an iterator value that is i positions prior to r in the container
r – r2
Returns the number of items that exist between the positions denoted by r
and r2
r < r2
Returns true if and only if the position denoted by r is before the position
denoted by r2
r > r2
Returns true if and only if the position denoted by r is after the position
denoted by r2
r <= r2 Returns true if and only if the position denoted by r is not after the position
denoted by r2
r >= r2 Returns true if and only if the position denoted by r is not before the
position denoted by r2
The vector container provides random access iterators, which makes sense because
vector is an abstraction of a one-dimensional array.
Constant and Mutable Iterators
The STL containers provide constant iterators. The available iterator types are defined in
the container class.
vector<char>::const_iterator constP =container.begin();
You cannot modify the elements in container using the constant iterator constP.
This does not make the location itself constant. If I define
vector<char>::iterator p = container.begin();
While we cannot change the front element from the container through dereferenced
constP, we can change the front element of container by assigning dereferenced p.
Here is a code fragment illustrating this.
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
Page 8
*constP = value; // illegal
*p = value;
// OK
This should not be surprising, since this is true of memory locations referred to by
const references and const pointers as well.
The notion of mutable and constant iterators refers to the ability to change what the
iterator refers to, not to the iterator itself. A mutable iterator is an iterator for which the
dereferenced iterator may be assigned. The iterator p above is a mutable iterator referring
to the first element in container. The iterator constP is a constant iterator that also
refers to the first element in container. On the other hand, a iterator variable of type
const_iterator or of an ordinary iterator type may be assigned another iterator
value, unless the keyword const is used in the definition.
#include <vector>
using std::vector;
vector<char> container;
std::vector<char>::const_iterator constP = container.begin();
constP = container.end(); //OK
const std::vector<char>::iterator P = container.begin();
//P = container.end();
// error: l-value specifies a const object
Reverse Iterators
If you need to cycle through a container from back to front, it may be inconvenient or
impossible to rewrite code to use -- instead of ++, especially if the code has been
precompiled. (Please ignore the difficulty in pre-compiling template code.)
The answer to this difficulty is the reverse iterator. Suppose you have an algorithm that
normally accepts an iterator to the first element of a container and cycles from the first
through to the last element. Then you can pass a reverse iterator initialized to refer to the
last element of the container, and the algorithm will cycle from the back of the container
through the front.
Such iterators reverse the effect of ++ and --. The normal effect of ++ on an ordinary
iterator is to move the place the iterator refers to toward the back of the container. The
effect of ++ on a reverse iterator is to move the place towards the front of the container.
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
Page 9
Similarly, -- moves the place a reverse iterator refers to towards the back of the
container. Containers that support reverse iterators provide members that provide iterator
values necessary to a reverse traversal. These members are rbegin() which returns
an iterator referring to the last element in the container and rend() which returns an
iterator referring to a position prior to the first element of the container1.
Other kinds of Iterators
We examined the input and output iterator above.
Pitfall: Compiler Problems
You compiler might not have read the latest ANSI C++ Standard. I have pointed out
several times that the Standard says what the language ought to be, but your compiler
defines the language you are dealing with is. Sometimes compilers will refuse to accept
the some of syntax we present here. There may be a kludge (work-around) that will make
the compiler work. Such a the case is described in the Pitfall on page 802 of the text.
Compilers may not accept
using std::vector<char>::iterator;
iterator p;
But this may work. (VC++6.0 at patch level 5 accepts this workaround.)
std::vector<char>::iterator p;
Perhaps this will work.
using namespace std;
vector<char>::iterator p;
Other kinds of Iterators
We examined the input and output iterator above.
9.2 Containers
The STL container classes are template classes for structures that hold any programmer
supplied data type that satisfies conditions we will outline presently. The STL provides
1
There are some implementation details we are ignoring here. Overloaded functions and operators allow
rend() to return an iterator referring to the first element, but to behave in the manner I describe here.
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
Page 10
three kinds of containers: sequence containers, container adapters, and associative
containers.
Sequential Containers
In a sequential container, or sequence container, the position of each item in the container
depends on the time and place of insertion but not on the item's value. The sequence
containers specified in the C++ Standard are vector, list, and deque2. These
containers are sometimes referred to as "first class containers". Each STL container
provides a different set of operations that have different time and space complexities.
Selection of a container should be made accordingly. The vector provides random
access to its elements, and insertions and deletions at the back (but not in the middle) are
very efficient. The C++ Standard suggests that vector is the sequence type that should
be used by default. However, if your program requires frequent insertions and deletions
in the middle of a sequence, the list should be used. The deque should be used if
frequent insertions and deletions are needed at the beginning and the end of the sequence.
I will not reproduce the tables of member functions provided in the text.
Pitfall: Iterators and Removing Elements
Be very careful using iterators after you delete elements. The list container guarantees
that deletions only invalidate iterators to the deleted element. Iterators into a vector
container at and after the point of deletion are invalidated.
Pitfall: Iterators and Inserting Elements
You also need to be careful using iterators after you insert elements. The list container
guarantees that insertions will not affect iterators. Iterators into a vector container at and
after the point of insertion are invalidated. If insertion of an element requires reallocation,
then all iterators are invalidated. To prevent being left with invalid iterators, start out
with a vector that is big enough using the member functions provided, or adjust the size
yourself using resize, but remember that resizing still invalidates all iterators.
The text discusses a singly linked list, the slist, but this is not in the Standard. It is not available in all
STL implementations, so I will not discuss it here.
2
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
Page 11
Tip: Type Definitions in Containers
See the Tip on page 808. The containers defined in the C++ Standard provide the iterator
typenames iterator, const_iterator, reverse_iterator, and
const_reverse_iterator. The typename value_type is typedef'ed to the
type held by the container, that is, the type that was passed to the container in the
template parameter. The typename size_type is an unsigned integral type for the size
of a container.
Access to these names is available by qualifying the type name in the manner we have
seen.
list<int>::const_iterator p;
The qualified typename list<int>::value_type is typedef'ed to int in the
list container template.
The Container Adapters stack, queue, and priority_queue
A container adapter does not directly implement the structures that hold the data items.
Rather, it provides a new interface between the user and an existing container. These are
called adapters because they use one of the three sequence containers (vector, list,
or deque) to actually hold the items and simply present a different interface (stack,
queue, or priority queue) to the programmer. It "adapts" the interface of one of these
sequence containers to provide the operations appropriate for a stack or queue.
The container adapters are queue, stack, and priority_queue.
The container for the stack adapter A sequence container that provides members
empty(), size(), push_back(), pop_back(), and back() is suitable as a
container for the stack container adapter. All three sequence containers—vector<T>,
deque<T>, and list<T>—satisfy these requirements. By default, deque<T> is the
container used by the stack<T> adapter.
The container for the queue adapter A sequence container that has members
back(), front(), push_back(), pop_front(), empty(), and size() can be
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
Page 12
used to hold queue<T> items. In particular, list<T> and deque<T> may be used. By
default, deque<T> is the container used by the queue<T> adapter.
Notice in Display19.9 that the queue uses push() instead of enqueue() and pop()
instead of dequeue(), as you might have expected. Regardless of what we call the
operation, we are still dealing with a queue—a FIFO (first-in, first-out) data structure—
and push()and pop()mean insert at the rear and remove from the front, respetively.
The structure still returns the oldest item in the structure, just as a queue should.
The Associative Containers set and map
The associative containers are set, map, multimap, and multiset.
The set container stores items without repeats. The set container behaves
(unsurprisingly) like the mathematical set. If an item has been inserted, inserting that item
again does no effect. The fundamental operations are insertion, deletion, and a test for
membership. The set container has constructors, copy constructor, assignment and
destructor. There are member functions to insert, delete, and test for membership, test for
being empty, request size and so on. The set container maintains the data items in
sorted order to allow access in O(log n) time. This requires that the type of item inserted
in the set container either support operator < or provide an ordering function.
The map and multimap containers are based on the concept of an associative array, a
data structure that holds pairs of values: a key and a value associated with that key. An
associative array can be thought of as an array that allows index types other than integer.
By default, the contents of a map are ordered by key values using the < operator, though
the programmer may either overload this operator or provide a comparison function.
Iterators for the map container step through the container, delivering the items in
ascending order of the keys.
Efficiency
Efficiency was a concern in the design of the STL components. The Standard specifies a
maximum runtime for each member of each container that a compliant implementation
must provide. It does not specify the implementation.
19.3 Generic Algorithms
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
Page 13
We have said that the STL provides containers, iterators, and generic algorithms. In
computer science terminology, an algorithm is (roughly) a sequence of steps that solves a
problem. In contrast, the term algorithm in the STL context means something much more
specific, namely, a template function that has iterators for its parameter types.
The STL supplies a large set of generic algorithms that operate on containers, whether
they are STL containers or user-defined containers. The algorithms are generic because
each algorithm is a template and the arguments in a function call are iterators, not
containers. Hence, a generic algorithm may operate on any data structure that provides an
iterator type that meets the iterator requirements of that algorithm. Let's look at an
example.
The STL supplies a sort algorithm with the following signature (the required header
file and the function prototype):
#include <algorithm>
template <class RandomAccessIterator>
void sort( RandomAccessIterator first,
RandomAccessIterator pastLast );
Given any container that supports random access iterators, the sort algorithm sorts the
container elements into ascending order using the < operator. We know that the STL
vector container supports random access iterators, so we could use sort as follows:
#include <vector>
// For vector<T> class
#include <algorithm>
// For sort() generic algorithm
vector<int> v;
sort(v.begin(), v.end());
// Sort the entire vector
Because sort works on any container whose iterators meet its requirements, we can sort
a built-in array as follows. (Recall that pointers into a built-in array meet the
requirements of a random access iterator.)
int arr[100];

sort(&arr[0], &arr[100]);
// Or
sort(arr, arr+100);
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
Page 14
In this code, the call to sort passes as arguments the location of the first array element
and the location of the imaginary past-the-end element (because arr[99] is the last
valid array element).
Running Times and Big-O Notation
There are two constraints on computation. One is the space required to carry out the
computation, the other is the time requires. The space or time required for a specific
computation for a specific data set will not be generally useful. However, the amount of
time or space required as functions of the problem size turn out to be of great interest. If
we know these, we can predict whether the algorithm might produce an answer in a
reasonable time for a problem that is ten or a hundred times as big as the one we are
presently able to solve. Similarly, we could predict whether we might have the necessary
space.
However, even if we do know an exact expression for the time an algorithm to run on a
specific computer, this won’t be useful on a different machine, especially if the
architecture is different from the computer for which the expression works. To sidestep at
least some of this, we use is an "order of magnitude" estimate of the growth of the
expression for runtime or memory use. I won't go into detail here, but for polynomials,
the highest degree term dominates the polynomial for large size. Hence for polynomials
we keep only the highest order term, dropping any lower order terms.
What of log functions, square roots, and so on? For fractional powers, Nx dominates Ny
if x > y. Any power, Nx, of degree x > 0 dominates any logarithm, to any base. As
for polynomials, we keep only the dominant terms.
Such estimates are expressed in the so called big-O notation. Crudely put, the big O
notation retains only the dominant term, and ignores the lead coefficient. For a
polynomial, 6*N3 – 2*N2 + 3*N + 14, the big O estimate is O(N3). Note that this
is an upper bound estimate.
There is a sound mathematical basis for this notation. First, notice that it is only
meaningful for large values of N. Second, it is the growth rate, not the specific multiplier
that is of interest. What f(N) = O(N2) means is that for some multiplier, say A, and for all
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
Page 15
sufficiently large values of N, the value of f(N) <= A*N2 for all values of N that are
sufficiently large.
Container Access Running Times
In what follows, N is the number of items in the vector. List insertion is O(1) anywhere.
Vector insertion at the back of a vector is also O(1). For a vector or a deque, insertion in
the middle takes O(N) time. Almost all operations for the set or map take O(log N) time.
Nonmodifying Sequence Algorithms
These are algorithms that access contents of containers but do not modify the elements
nor the sequence of elements in a container. Typical of these is the find algorithm.
The sequence algorithms operate on a half-open interval defined by two iterators, usually
written [start, past-end), where the '[' indicates that the element referred to by
the start iterator value in the range is processed, and every item is processed up to but
not including the past-end iterator value. In my opinion, it is unfortunate that the
names are frequently [begin, end), nevertheless, the element referred to by the
iterator value end is not included in the range.
Modifying Sequence Algorithms
Algorithms such as swap, replace, or remove are typical of modifying algorithms.
Like non-modifying algorithms, modifying sequence algorithms operate on half-open
iterator ranges of a container that the iterators refer to. They are called modifying because
the either can or will modify objects pointed to be the iterators or objects in the container
in the iterator range. Some of these algorithms will that make copies of the input
container will not modify the input container. These are still classified as modifying
because the output container is modified.
Sorting Algorithms
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
Page 16
The sorting algorithms provided in a compliant implementation of the STL are required
to run in O(N*log N) time. The standard specifies only the runtime requirement, but does
not specify an implementation. It is proved in the algorithm course that the fastest
possible algorithm for sorting that works by swapping elements runs in O(N*log N) time,
so the algorithms in the STL are as fast as possible, up to a constant multiple.
3. Solutions to, and remarks on, selected Programming Projects
1. Iterator A object that behaves like an iterator is an iterator.
a) Argue from the properties of a random access iterator that a pointer to an element of an
array behaves exactly as a random access iterator.
Answer:
A random access iterator provides dereferencing and indexing of an iterator value for
access to a container element, prefix and postfix -- and ++ operators and the ability to
add or subtract an iterator and an int value to produce other iterator values. A value
having type pointer to array base type has exactly these properties.
b) Argue further that:
i) The name of a pointer to double that points to the first element in an array of double
so the array name can serve as a "begin" iterator.
ii) The name + size of the array server as an "end" iterator value.
Answer:
The name of an array carries type "pointer to base type" and it points to the
first element of the array. This means that the expression *(arrayName) will return
the value of the first entry. Further, applying the ++ operator repeatedly will ultimately
reach each array entry in its turn, and will ultimately reach the pointer value,
arrayName +arraySize, which serves as the "past-end" iterator value.
2. Erase Use the remove generic algorithm to illustrate removal of several instances of a
particular item from a container.
No solution is provided.
3. Sieve of Eratosthenes.
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
Page 17
No solution is provided.
4. Student Records.
Define struct StudentInfo; maintain records in a vector<StudentInfo>.
Program prompts for and fetches data, builds vector of student records, sorts by name,
calculates max and min grades (no names, just max and min numbers), class average,
prints summarizing data along with class roll and grades. Test program.
//File: Ch19Prog4.cpp
//
// Using struct StudentInfo with string name and int grade members,
// prompt for and fetch names and grades. Sort by name, output class
// average, maximum and minimum grades, and sorted roll with grades.
// There is a quirk in VC++ getline that prevents clean exit from
// i/o loop.
#include
#include
#include
#include
#include
<algorithm>
<iomanip>
<iostream>
<string>
<vector>
// This is only needed if you are using MS VC++.
// The VC++ library does not define min/max.
#ifdef _MSC_VER
template <class T>
T max(const T& a, const T& b)
{
return (a > b) ? a : b;
}
template <class T>
T min(const T& a, const T& b)
{
return (a < b) ? a : b;;
}
#else
using std::max;
#endif
using
using
using
using
using
std::cin;
std::cout;
std::endl;
std::istream;
std::setprecision;
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
Page 18
using std::sort;
using std::string;
using std::vector;
struct StudentInfo
{
string name;
int grade;
};
//Used by the sort algorithm
bool compare(const StudentInfo& x, const StudentInfo& y)
{
return x.name < y.name;
}
int main()
{
vector<StudentInfo> students;
StudentInfo record;
string::size_type maxLength = 0;
int maxGrade = 0;
int minGrade = 101;
int sumGrade = 0;
int count = 0;
double averageGrade;
// Find the largest and smallest grades
// Count the students and compute the sum of grades for class average
// Find length of the longest string for nice output
cout << "Enter a name and an integer grade between 0 and 100\n"
<< "Separate with white space, terminate with <eof><cr><eof>\n";
while(cin >> record.name >> record.grade)
{
maxLength = max(maxLength, record.name.size());
maxGrade = max(maxGrade, record.grade);
minGrade = min(minGrade, record.grade);
sumGrade = sumGrade + record.grade;
count++;
students.push_back(record);
}
averageGrade = static_cast<double>(sumGrade) / count;
// Sort the student record vector
sort(students.begin(), students.end(), compare);
cout <<
<<
<<
<<
"\maximum grade is " << maxGrade
"\minimum grade is " << minGrade
"\average grade is " << averageGrade
endl << endl;
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
Page 19
for (std::vector<StudentInfo>::size_type i = 0; i != students.size();
++i)
{
// write names, pad output on right with spaces to maxLength + 1
characters
cout << students[i].name
<< string(maxLength + 1 - students[i].name.size(), ' ')
<< students[i].grade << endl;
}
return 0;
}
5. Continue Project 4 (Student Records).
In two ways (see below), write a function that separates student records from the vector
of StudentInfo records into two vectors, one with records of passing students and one
containing records of failing students. (Grade of 60 or better is passing.)
a) Consider continuing to use a vector. You could generate the second vector of passing
students and a third vector of failing students. This would keep duplicate records some of
the time so don't do it this way.
Instead, create a vector of failing student records and a test for failing function. Then use
push_back to insert the failing student records, then use the erase member function
to erase the failing student records. Code this.
b) There are efficiency concerns with the solution in part a). Potentially, we ease O(N)
members from the middle of a vector. Each erase from the middle take O(N) time. Give
a bit O estimate of the runtime for this solution.
Answer: O(N) erasures each of which takes O(N) time has a runtime of O(N2).
c) If you use a list<StudentInfo>, what are the runtime estimates for the erase
and insert functions? Consider how the runtime estimate for erase for a list
affects the runtime for the program. Code the problem using a list instead of a
vector. Remember that the list iterator is bi-directional, not random access.
Answer: Insertion and deletion for a list container are each O(1). O(N) erasures
(anywhere) that take O(1) time produces O(N) runtime.
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
Page 20
CODE:
PART A)
//File: Ch19Prog4.cpp
//
// Part a)
// Using struct StudentInfo with string name and int grade members,
// prompt for and fetch names and grades. Sort by name, output class
// average, maximum and minimum grades, and sorted roll with grades.
// Separate the failing students' records from the passing students'.
// Display this data.
#include
#include
#include
#include
#include
<algorithm>
<iomanip>
<iostream>
<string>
<vector>
// This is only needed if you are using MS VC++.
// The VC++ library does not define min/max.
#ifdef _MSC_VER
template <class T>
T max(const T& a, const T& b)
{
return (a > b) ? a : b;
}
template <class T>
T min(const T& a, const T& b)
{
return (a < b) ? a : b;;
}
#else
using std::max;
#endif
using
using
using
using
using
using
using
using
std::cin;
std::cout;
std::endl;
std::istream;
std::setprecision;
std::sort;
std::string;
std::vector;
struct StudentInfo
{
string name;
int grade;
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
Page 21
};
typedef std::vector<StudentInfo>::iterator iterator;
typedef std::vector<StudentInfo>::size_type size_type;
//Used by the sort algorithm
bool compare(const StudentInfo& x, const StudentInfo& y)
{
return x.name < y.name;
}
inline bool isFailing(const StudentInfo& record)
{
return record.grade < 60;
}
int main()
{
vector<StudentInfo> students;
StudentInfo record;
vector<StudentInfo> failingStudents;
string::size_type maxLength = 0;
int maxGrade = 0;
int minGrade = 101;
int sumGrade = 0;
int count = 0;
double averageGrade;
// Find the largest and smallest grades
// Count the students and compute the sum of grades for class average
// Find length of the longest string for nice output
cout << "Enter a last name and an integer grade between 0 and 100\n"
<< "Separate with white space, terminate with <eof><cr><eof>\n"
<< "Capitalize properly as the sort routine is case
sensitive.\n";
while(cin >> record.name >> record.grade)
{
maxLength = max(maxLength, record.name.size());
maxGrade = max(maxGrade, record.grade);
minGrade = min(minGrade, record.grade);
sumGrade = sumGrade + record.grade;
count++;
students.push_back(record);
}
averageGrade = static_cast<double>(sumGrade) / count;
// Sort the student record vector
sort(students.begin(), students.end(), compare);
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
cout <<
<<
<<
<<
Page 22
"\nMaximum grade is " << maxGrade
"\nMinimum grade is " << minGrade
"\nAverage grade is " << averageGrade
endl;
cout << "\nClass names and grades are: \n";
size_type i; // VC++6.0, in violation of the ANSI C++ Standard,
// puts for loop control variable outside the for loop.
// So we put it outside as well. Saves grief.
for (i = 0; i != students.size(); ++i)
{
// write names, pad output on right with spaces to maxLength + 1
characters
cout << students[i].name
<< string(maxLength + 1 - students[i].name.size(), ' ')
<< students[i].grade << endl;
}
//copy failing student records to another vector, erase from students
vector
iterator itr = students.begin();
while(itr < students.end())
{
if(isFailing(*itr))
{
failingStudents.push_back(*itr);
itr = students.erase(itr);
}
else
itr++;
}
cout << "\nPassing students and grades are: \n";
for (i = 0; i != students.size(); ++i)
{
// write names, pad output on right with spaces to maxLength + 1
characters
cout << students[i].name
<< string(maxLength + 1 - students[i].name.size(), ' ')
<< students[i].grade << endl;
}
cout << "\n\nFailing students and grades are: \n";
for (i = 0; i != failingStudents.size(); ++i)
{
// write names, pad output on right with spaces to maxLength + 1
characters
cout << failingStudents[i].name
<< string(maxLength + 1 - failingStudents[i].name.size(), ' ')
<< failingStudents[i].grade << endl;
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
Page 23
}
return 0;
}
PART C
//File: Ch19Prog5c.cpp
//
// Part c)
// Using struct StudentInfo with string name and int grade members,
// prompt for and fetch names and grades. Sort by name, output class
// average, maximum and minimum grades, and sorted roll with grades.
// Part c) Use a list<StudentInfo> rather than a list.
// Separate the failing students' records from the passing students'.
// Display this data.
// Comment on efficiencies of erase and insert members.
#include
#include
#include
#include
#include
<algorithm>
<iomanip>
<iostream>
<string>
<list>
// This is only needed if you are using MS VC++.
// The VC++ library does not define min/max.
#ifdef _MSC_VER
template <class T>
T max(const T& a, const T& b)
{
return (a > b) ? a : b;
}
template <class T>
T min(const T& a, const T& b)
{
return (a < b) ? a : b;;
}
#else
using std::max;
#endif
using
using
using
using
using
using
using
std::cin;
std::cout;
std::endl;
std::istream;
std::setprecision;
std::sort;
std::string;
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
Page 24
using std::list;
struct StudentInfo
{
string name;
int grade;
};
typedef std::list<StudentInfo>::iterator iterator;
typedef std::list<StudentInfo>::size_type
size_type;
//Used by operator< with ANSI C++ Standard compliant compilers
bool compare(const StudentInfo& x, const StudentInfo& y)
{
return x.name < y.name;
}
#ifdef _MSC_VER
//MS VC++ does cannot pass a comparison function as an argument
//to list::sort, so we must overload operator<
bool operator<(const StudentInfo& lhs, const StudentInfo rhs)
{
return compare(lhs, rhs);
}
#endif
inline bool isFailing(const StudentInfo& record)
{
return record.grade < 60;
}
int main()
{
list<StudentInfo> students;
StudentInfo record;
list<StudentInfo> failingStudents;
string::size_type maxLength = 0;
int maxGrade = 0;
int minGrade = 101;
int sumGrade = 0;
int count = 0;
double averageGrade;
// Find the largest and smallest grades
// Count the students and compute the sum of grades for class average
// Find length of the longest string for nice output
cout << "Enter a last name and an integer grade between 0 and 100\n"
<< "Separate with white space, terminate with <eof><cr><eof>\n"
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
Page 25
<< "Capitalize properly as the sort member is case sensitive.\n";
while(cin >> record.name >> record.grade)
{
maxLength = max(maxLength, record.name.size());
maxGrade = max(maxGrade, record.grade);
minGrade = min(minGrade, record.grade);
sumGrade = sumGrade + record.grade;
count++;
students.push_back(record);
}
averageGrade = static_cast<double>(sumGrade) / count;
// Sort the student record list
#ifdef _MSC_VER
students.sort();
#else
students.sort(compare);
#endif
cout <<
<<
<<
<<
"\nMaximum grade is " << maxGrade
"\nMinimum grade is " << minGrade
"\nAverage grade is " << averageGrade
endl;
cout << "\nClass names and grades are: \n";
iterator itr;
for(itr = students.begin(); itr != students.end(); itr++)
{
// write names, pad output on right with spaces to maxLength + 1
characters
cout << itr->name
<< string(maxLength + 1 - itr->name.size(), ' ')
<< itr->grade << endl;
}
//copy failing student records to another list, erase from students
list
itr = students.begin();
while(itr != students.end())
{
if(isFailing(*itr))
{
failingStudents.push_back(*itr);
itr = students.erase(itr);
}
else
itr++;
}
Instructor’s Resource Manual for Savitch Absolute C++ 03/09/16
Chapter 19 CHAPTER Standard Template Library
Page 26
cout << "\nPassing students and grades are: \n";
for(itr = students.begin(); itr != students.end(); itr++)
{
// write names, pad output on right with spaces to maxLength + 1
characters
cout << itr->name
<< string(maxLength + 1 - itr->name.size(), ' ')
<< itr->grade << endl;
}
cout << "\n\nFailing students and grades are: \n";
for(itr = failingStudents.begin(); itr != failingStudents.end();
itr++)
{
// write names, pad output on right with spaces to maxLength + 1
characters
cout << itr->name
<< string(maxLength + 1 - itr->name.size(), ' ')
<< itr->grade << endl;
}
return 0;
}
Download