Tải bản đầy đủ (.pdf) (139 trang)

Ivor Horton’s BeginningVisual C++ 2008 phần 6 doc

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (1.84 MB, 139 trang )

Enter the phone number for Bill Smith: 213 466 7688
Entry successful.
Choose from the following options:
A Add an entry D Delete an entry G Get an entry
L List entries Q Quit
g
Enter a first name: Mary
Enter a second name: Miller
No entry found for Mary Miller
Choose from the following options:
A Add an entry D Delete an entry G Get an entry
L List entries Q Quit
g
Enter a first name: Mary
Enter a second name: Jones
The number for Mary Jones is 213 443 5671
Choose from the following options:
A Add an entry D Delete an entry G Get an entry
L List entries Q Quit
d
Enter a first name: Mary
Enter a second name: Jones
Mary Jones erased.
Choose from the following options:
A Add an entry D Delete an entry G Get an entry
L List entries Q Quit
L
Jack Bateman 312 455 6576
Jane Junket 413 222 8134
Bill Smith 213 466 7688
Choose from the following options:


A Add an entry D Delete an entry G Get an entry
L List entries Q Quit
q
How It Works
You define a map container in main() like this:
map<Person, string> phonebook;
The object in an entry in the map is a string containing a phone number and the key is a Person object.
You load up the map initially in a
while loop:
while(true)
{
cout << “Do you want to enter a phone book entry(Y or N): “ ;
cin >> answer;
cin.ignore(); // Ignore newline in buffer
661
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 661
if(toupper(answer) == ‘N’)
break;
if(toupper(answer) != ‘Y’)
{
cout << “Invalid response. Try again.” << endl;
continue;
}
addEntry(phonebook);
}
You check whether an entry is to be read by reading a character from the standard input stream. Reading
a character from
cin leaves a newline character in the buffer and this can cause problems for subsequent
input. Calling

ignore() for cin ignores the next character so subsequent input will work properly. If
‘n’ or ‘N’ is entered, the loop is terminated. When ‘y’ or ‘Y’ is entered, an entry is created by calling
the helper function
addEntry() that is coded like this:
void addEntry(map<Person, string>& book)
{
pair<Person, string> entry; // Stores a phone book entry
string number;
Person person = getPerson();
cout << “Enter the phone number for “
<< person.getName() << “: “;
getline(cin, number);
entry = make_pair(person, number);
pair<map<Person,string>::iterator, bool> pr = book.insert(entry);
if(pr.second)
cout << “Entry successful.” << endl;
else
{
cout << “Entry exists for “ << person.getName()
<< “. The number is “ << pr.first->second << endl;
}
}
Note that the parameter for addEntry() is a reference. The function modifies the container that is passed
as the argument, so the function must have access to the original object. In any event, even if only access
to the container argument was needed, it is important not to allow potentially very large objects such as a
map container to be passed by value because this can seriously degrade performance.
The process for adding an entry is essentially as you have seen in the previous section. The
getPerson()
helper function reads a first name and a second name and then returns a Person object that is created
using the names. The

getName() member of the Person class returns a name as a string object so you
use this in the prompt for a number. Calling the
make_pair() function returns a pair<Person, string>
object that you store in entry. You then call insert() for the container object and store the object returned
in
pr. The pr object enables you to check that the entry was successfully inserted into the map by testing its
bool member. The first member of pr provides access to the entry, whether it’s an existing entry or the new
entry, and you use this to output a message when insertion fails.
After initial input is complete, a
while loop provides the mechanism for querying and modifying the
phone book. The
switch statement in the body of the loop decides the action to be taken based on the
662
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 662
character that is entered and stored in answer. Querying the phone book is managed by the getEntry()
function:
void getEntry(map<Person, string>& book)
{
Person person = getPerson();
map<Person, string>::const_iterator iter = book.find(person);
if(iter == book.end())
cout << “No entry found for “ << person.getName() << endl;
else
cout << “The number for “ << person.getName()
<< “ is “ << iter->second << endl;
}
A Person object is created from a name that is read from the standard input stream by calling the
getPerson() function. The Person object is then used as the argument to the find() function for
the map object. This returns an iterator that either points to the required entry, or points to one past the

last entry in the map. If an entry is found, accessing the
second member of the pair pointed to by the
iterator provides the number corresponding to the
Person object key.
The
deleteEntry() function deletes an entry from the map. The process is similar to that used in the
getEntry() function, the difference being that when an entry is found by the find() function, the
erase() function is called to remove it. You could use another version of erase() to do this, in which
case the code would be like this:
void deleteEntry(map<Person, string>& book)
{
Person person = getPerson();
if(book.erase(person))
cout << person.getName() << “ erased.” << endl;
else
cout << “No entry found for “ << person.getName() << endl;
}
The code turns out to be much simpler if you pass the key to the erase() function.
The
listEntries() function lists the contents of a phone book:
void listEntries(map<Person, string>& book)
{
if(book.empty())
{
cout << “The phone book is empty.” << endl;
return;
}
map<Person, string>::iterator iter;
cout << setiosflags(ios::left); // Left justify output
for(iter = book.begin() ; iter != book.end() ; iter++)

{
cout << setw(30) << iter->first.getName()
<< setw(12) << iter->second << endl;
}
cout << resetiosflags(ios::right); // Right justify output
}
663
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 663
After an initial check for an empty map, the entries are listed in a for loop using an iterator. The output
is left-justified by the
setiosflags manipulator to produce tidy output. This remains in effect until
resetiosflags manipulator is used to restore right-justification.
Using a Multimap Container
A multimap container works very much like the map container in that it supports the same range of
functions except for the subscript operator, which you cannot use with a multimap. The principle dif-
ference between a map and a multimap is that you can have multiple entries with the same key in a
multimap and this affects the way some of the functions behave. Obviously, with the possibility of
several keys having the same value, overloading the
operator[]() function would not make much
sense for a multimap.
The
insert() function flavors for a multimap are a little different from the function for a map. The sim-
plest version of
insert() that accepts a pair<K, T> object as an argument returns an iterator pointing
to the entry that was inserted in the multimap. The equivalent function for a map returns a pair object
because this provides an indication of when the key already exists in the map and the insertion is not
possible; of course, this cannot arise with a multimap. A multimap also has a version of
insert() with
two arguments, the second being the pair to be inserted, the first being an iterator pointing to the posi-

tion in the multimap to start searching for an insertion point. This gives you some control over where a
pair will be inserted when the same key already exists. This version of
insert() also returns an iterator
pointing to the element that was inserted. The third version of
insert() accepts two iterator arguments
that specify a range of elements to be inserted from some other source.
When you pass a key to the
erase() function for a multimap, it erases all entries with the same key and
the value returned indicates how many entries were deleted. The significance of having another version
of
erase() available that accepts an iterator as an argument should now be apparent — it allows you to
delete a single element.
The
find() function can only find the first element with a given key in a multimap. You really need a way
to find several elements with the same key and the
lower_bound(), upper_bound(), and equal_range()
functions provide you with a way to do this. For example, given a phonebook object that is type
multimap<Person, string>
rather than type map<Person, string>, you could list the phone numbers corresponding to a given
key like this:
Person person = Person(“Jack”, “Jones”);
multimap<Person, string>::iterator iter = phonebook.lower_bound(person);
if(iter == phonebook.end())
cout << “The are no entries for “ << person.getName() << endl;
else
{
cout << “The following numbers are listed for “ << person.getName() << “:” << endl;
for( ; iter != phonebook.upper_bound(person) ; iter++)
cout << iter->second << endl;
}

664
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 664
It’s important to check the iterator returned by the lower_bound() function. If you don’t, you could end
up trying to reference an entry one beyond the last entry.
More on Iterators
The <iterator> header defines several templates for iterators for transferring data from a source to a
destination. Stream iterators act as pointers to a stream for input or output and they enable you to trans-
fer data between a stream and any source or destination that works with iterators, such as an algorithm.
Inserter interators can transfer data into a basic sequence container. The
<iterator> header defines two
stream iterator templates,
istream_iterator<T> for input streams and ostream_iterator<T> for
output streams, where
T is the type of object to be extracted from, or written to, the stream. The header
also defines three inserter templates,
inserter<T>, back_inserter<T> and front_inserter<T>,
where
T is the type of sequence container in which data is to be inserted.
Let’s explore some of these iterators in a little more depth.
Using Input Stream Iterators
Here’s an example of how you create an input stream iterator:
istream_iterator<int> numbersInput(cin);
This creates the iterator numbersInput of type istream_iterator<int> that can point to objects of
type
int in a stream. The argument to the constructor specifies the actual stream to which the iterator
relates, so this is an iterator that can read integers from
cin, the standard input stream.
The default
istream_iterator<T> constructor creates an end-of-stream iterator, which will be the equiv-

alent to the end iterator for a container that you have been obtaining by calling the
end() function. Here’s
how you could create an end-of-stream iterator for
cin complementing the numbersInput iterator:
istream_iterator<int> numbersEnd;
Now you have a pair of iterators that define a sequence of values of type int from cin. You could use
these to load values from
cin into a vector<int> container for example:
vector<int> numbers;
istream_iterator<int> numbersInput(cin), numbersEnd;
cout << “Enter integers separated by spaces then a letter to end:” << endl;
while(numbersInput != numbersEnd)
numbers.pushback(*numbersIn++);
After defining the vector container to hold values of type int, you create two input stream iterators:
numbersIn is an input stream iterator reading values of type int from cin, and numbersEnd is an
end-of-stream iterator for the same input stream. The
while loop continues as long as numbersEnd is
not equal to the end-of-stream iterator,
numbersEnd. When you execute this fragment, input continues
until end-of-stream is recognized for
cin, but what produces that condition? The end-of-stream condi-
tion will arise if you enter
Ctrl+Z to close the input stream, or you enter an invalid character such as
a letter.
665
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 665
Of course, you are not limited to using input stream iterators as loop control variables. You can use them
to pass data to an algorithm such as the
accumulate() that is defined in the <numeric> header:

vector<int> numbers;
istream_iterator<int> numbersInput(cin), numbersEnd;
cout << “Enter integers separated by spaces then a letter to end:” << endl;
cout << “The sum of the input values that you entered is “
<< accumulate(intvecRead, endStream, 0) << endl;
This fragment outputs the sum of however many integers you enter. You will recall that the arguments to
the
accumulate() algorithm are an iterator pointing to the first value in the sequence, an iterator pointing
to one past the last value, and the initial value for the sum. Here you are transferring data directly from
cin
to the algorithm.
The
<sstream> header defines the basic_istringstream<char> type that defines an object type that
can access data from a stream buffer such as a
string object. The header also defines the istringstream
type as basic_istringstream<char>, which will be a stream of characters of type char. You can con-
struct an
istringstream object from a string object, which means you can read data from the string
object just as you read from cin. Because an istringstream<T> object is a stream, you can pass it to an
input iterator constructor and use the iterator to access the data in the underlying stream buffer. Here’s an
example of how you do that:
string data(“2.4 2.5 3.6 2.1 6.7 6.8 94 95 1.1 1.4 32”);
istringstream input(data);
istream_iterator<double> begin(input), end;
cout << “The sum of the values from the data string is “
<< accumulate(begin, end, 0.0) << endl;
You create the istringstream object, input, from the string object, data, so you can read from data
as a stream. You create two stream iterators that can access double values in the input stream, and you
use these to pass the contents of
data to the accumulate() algorithm. Note that the type of the third

argument to the
accumulate() function determines the type of the result so you must specify this as a
value of type
double to get the sum produced correctly.
Let’s try a working example.
Try It Out Using an Input Stream Iterator
In this example you use a stream iterator to read text from the standard input stream and transfer it to a
map container to produce a collocation for the text. Here’s the code:
// Ex10_11.cpp
// A simple word collocation
#include <iostream>
#include <iomanip>
#include <string>
#include <map>
666
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 666
using std::cout;
using std::cin;
using std::endl;
using std::string;
int main()
{
typedef std::map<string, int>::const_iterator Iter;
std::map<string, int> words; // Map to store words and word counts
cout << “Enter some text and press Enter followed by Ctrl+Z to end:”
<< endl << endl;
std::istream_iterator<string> begin(cin); // Stream iterator
std::istream_iterator<string> end; // End stream iterator
while(begin != end ) // Iterate over words in the stream

words[*begin++]++; // Increment and store a word count
// Output the words and their counts
cout << endl << “Here are the word counts for the text you entered:” << endl;
for(Iter iter = words.begin() ; iter != words.end() ; ++iter)
cout << std::setw(5) << iter->second << “ “ << iter->first << endl;
return 0;
}
Here’s an example of some output from this program:
Enter some text and press Enter followed by Ctrl+Z to end:
Peter Piper picked a peck of pickled pepper
A peck of pickled pepper Peter Piper picked
If Peter Piper picked a peck of pickled pepper
Where’s the peck of pickled pepper Peter Piper picked
^Z
Here are the word counts for the text you entered:
1 A
1 If
4 Peter
4 Piper
1 Where’s
2 a
4 of
4 peck
4 pepper
4 picked
4 pickled
1 the
667
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 667

How It Works
You first define a type for a const iterator for the map container:
typedef std::map<string, int>::const_iterator Iter;
Using this typedef statement to define the Iter type will make the loop statement that outputs the
contents of the map much more readable.
Next you define a map container to store the words and the word counts:
std::map<string, int> words; // Map to store words and word counts
This container stores each word count of type int using the word of type string as the key. This will
make it easy to accumulate the count for each word when you read from the input stream using stream
iterators.
std::istream_iterator<string> begin(cin); // Stream iterator
std::istream_iterator<string> end; // End stream iterator
The begin iterator is a stream iterator for the standard input stream and end is an end-of-stream iterator
that you can use to detect when the end of the input is reached.
You read the words and accumulate the counts in a loop:
while(begin != end ) // Iterate over words in the stream
words[*begin++]++; // Increment and store a word count
This simple while loop does a great deal of work. The loop control expression will iterate over the words
entered via the standard input stream until the end-of-stream state is reached. The stream iterator reads
words from
cin delimited by whitespace, just like the overloaded >> operator for cin. Within the loop
you use the subscript operator for the map container to store a count with the word as the key; remem-
ber, the argument to the subscript operator for a map is the key. The expression
*begin accesses a word
and the expression
*begin++ increments the iterator after accessing the word.
The first time a word is read, it will not be in the map, so the expression
words[*begin++] will store a
new entry with the count having the default value 0, and increment the
begin iterator to the next word,

ready for the next loop iteration. The whole expression
words[*begin++]++ will increment the count
for the entry, regardless of whether it is a new entry or not. Thus an existing entry will just get its count
incremented whereas a new entry will be created and then its count incremented from 0 to 1.
Finally you output the count for each word in a
for loop:
for(Iter iter = words.begin() ; iter != words.end() ; ++iter)
cout << std::setw(5) << iter->second << “ “ << iter->first << endl;
This uses the iterator for the container in the way you have seen several times before. The loop control
expressions are very much easier to read because of the
typedef for Iter.
668
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 668
Using Inserter Iterators
An inserter iterator is an iterator that can add new elements to any of the sequence containers
vector<T>, deque<T>, and list<T>. There are three templates that create inserter iterators:

back_inserter<T> inserts elements at the end of a container of type T.

front_inserter<T> inserts elements at the beginning of a container of type T.

inserter<T> inserts elements starting at a specified position within a container of type T.
The constructors for the first two types of inserter iterators expect a single argument specifying the con-
tainer in which elements are to be inserted. For example:
vector<int> numbers;
front_inserter<vector<int>> iter(numbers);
Here you create an inserter iterator that can insert data at the beginning of the vector<int> container
numbers.
Inserting a value into the container is very simple:

*iter = 99; // Insert 99 at the front of the numbers container
The constructor for an inserter<T> iterator requires two arguments:
inserter<vector<int>> iter_anywhere(numbers, numbers.begin());
The second argument to the constructor is an iterator specifying where data is to be inserted — the start
in the sequence in this instance. You can use this iterator in exactly the same way as the previous one.
Here’s how you could insert a series of values into a vector container using this iterator:
for(int i = 0 ; i<100 ; i++)
*iter_anywhere = i + 1;
This loop inserts the values from 1 to 100 in the numbers container.
The inserter iterators can be used in conjunction with the
copy() algorithm in a particularly useful way.
Here’s how you could read values from
cin and transfer them to a list<T> container:
list<double> values;
cout << “Enter a series of values separated by spaces”
<< “ followed by Ctrl+Z or a letter to end:” << endl;
istream_iterator<double> input(cin), input_end;
copy(input, input_end, back_inserter<list<double>>(values));
You first create a list container that stores double values. After a prompt for input, you create two input
stream iterators for values of type
double. The first iterator points to cin and the second iterator is an
end-of-stream iterator created by the default constructor. You specify the input to the
copy() function
with the two iterators and the destination for the copy operation is a back inserter iterator that you cre-
ate in the third argument to the
copy() function. The back inserter iterator adds the data transferred by
669
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 669
the copy operation to the list container, values. This is quite powerful stuff. If you ignore the prompt, in

three statements you can read an arbitrary number of values from the standard input stream and trans-
fer them to a list container.
Using Output Stream Iterators
Complementing the input stream iterator template, the ostream_iterator<T> template provides out-
put stream iterators for writing objects of type
T to an output stream. There are two constructors for an
instance of the output stream iterator template. One creates an iterator that just transfers data to the des-
tination stream:
ostream_iterator<int> out(cout);
The type argument, int, to the template specifies the type of data to be handled and the constructor
argument,
cout, specifies the stream that will be the destination for data so the out iterator can write
value of type
int to the standard output stream. Here’s how you might use this iterator:
int data[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
vector<int> numbers(data, data+9); // Contents 1 2 3 4 5 6 7 8 9
copy(numbers.begin(), numbers.end(), out);
The copy() algorithm that is defined in the <algorithm> header copies the sequence of objects speci-
fied by the first two iterator arguments to the output iterator specified by the third argument. Here the
function copies the elements from the
numbers vector to the out iterator, which will write the elements
to
cout. The result of executing this fragment will be:
123456789
As you can see, the values are written to the standard output stream with no spaces between. The second
output stream iterator constructor can improve on this:
ostream_iterator<int> out(cout, “, “);
The second argument to the constructor is a string to be used as a delimiter for output values. If you use
this iterator as the third argument to the
copy() function in the previous fragment, the output will be:

1, 2, 3, 4, 5, 6, 7, 8, 9,
The delimiter string that you specify as a second constructor argument is written to the stream following
each value that is written out.
Let’s see how an output stream iterator works in practice.
Try It Out Using an Inserter Iterator
Suppose you want to read a series of integer values from cin and store them in a vector. You then want
to output the values and their sum. Here’s how you could do this with the STL:
// Ex10_12.cpp
// Using stream and inserter iterators
#include <iostream>
670
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 670
#include <numeric>
#include <vector>
using std::cout;
using std::cin;
using std::endl;
using std::vector;
using std::istream_iterator;
using std::ostream_iterator;
using std::back_inserter;
using std::accumulate;
int main()
{
vector<int> numbers;
cout << “Enter a series of integers separated by spaces”
<< “ followed by Ctrl+Z or a letter:” << endl;
istream_iterator<int> input(cin), input_end;
ostream_iterator<int> out(cout, “ “);

copy(input, input_end, back_inserter<vector<int>>(numbers));
cout << “You entered the following values:” << endl;
copy(numbers.begin(), numbers.end(), out);
cout << endl << “The sum of these values is “
<< accumulate(numbers.begin(), numbers.end(), 0) << endl;
return 0;
}
Here’s an example of some output:
Enter a series of integers separated by spaces followed by Ctrl+Z or a letter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ^Z
You entered the following values:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
The sum of these values is 120
How It Works
After creating the numbers vector to store integers and issuing a prompt for input, you create three
stream iterators:
istream_iterator<int> input(cin), input_end;
ostream_iterator<int> out(cout, “ “);
The first statement creates two input stream iterators for reading values of type int from the standard
input stream,
input and input_end, the latter being an end-of-stream iterator. The second statement
creates an output stream iterator for transferring values of type
int to the standard output stream with
the delimiter following each output value being a single space.
Data is read from
cin and transferred to the vector container using the copy() algorithm:
copy(input, input_end, back_inserter<vector<int>>(numbers));
671
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 671

You specify the source of data for the copy operation by the two input stream iterators, input and
input_end, and the destination for the copy operation is a back inserter iterator for the numbers con-
tainer. Thus the copy operation will transfer data values from
cin to the numbers container via the
back inserter.
You output the values that have been stored in the container using another copy operation:
copy(numbers.begin(), numbers.end(), out);
Here the source for the copy is specified by the begin() and end() iterators for the container, and the
destination is the output stream iterator,
out. This operation will therefore write the data from numbers
to cout with the values separated by a space.
Finally you calculate the sum of the values in the
numbers container in the output statement using the
accumulate() algorithm:
cout << endl << “The sum of these values is “
<< accumulate(numbers.begin(), numbers.end(), 0) << endl;
You specify the range of values to be summed by the begin() and end() iterators for the container and
the initial value for the sum is zero. If you wanted the average rather than the sum, this is easy too, being
given by the expression:
accumulate(numbers.begin(), numbers.end(), 0)/numbers.size()
More on Function Objects
The <functional> header defines an extensive set of templates for creating function objects that you
can use with algorithms and containers. I won’t discuss them in detail but I’ll summarize the most use-
ful ones. The function objects for comparisons are shown in the following table.
Function Object Template Description
less<T>
Creates a binary predicate representing the < operation between
objects of type
T. For example, less<string>() defines a function
object for comparing objects of type

string.
less_equal<T>
Creates a binary predicate representing the <= operation between
objects of type
T. For example, less_equal<double>() defines a
function object for comparing objects of type
double.
equal<T>
Creates a binary predicate representing the == operation between
objects of type
T.
not_equal<T>
Creates a binary predicate representing the!= operation between
objects of type
T.
672
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 672
Here’s how you could use the not2<B> template to define a binary predicate for use with the sort()
algorithm:
sort(v.begin(), v.end(), not2(greater<string>()));
The argument to the not2 constructor is greater<string>(), which is a call to the constructor for the
greater<string> class type, so the sort() function will sort using “not greater than” as the compari-
son between objects in the container,
v.
The
<functional> header also defines function objects for performing arithmetic operations on elements.
You would typically use these to apply operations to sequences of numerical values using the
transform()
algorithm that is defined in the <algorithm> header. These function objects are described in the following

table where the parameter
T specifies the type of the operands.
To make use of these you need to apply the
transform() function, and I’ll explain how this works in
the next section.
Function Object Template Description
plus<T>
Calculates the sum of two elements of type T.
minus<T>
Calculates the difference between two elements of type T by sub-
tracting the second operand from the first.
multiplies<T>
Calculates the product of two elements of type T.
divides<T>
Divides the first operand of type T by the second operand of type T.
modulus<T>
Calculates the remainder after dividing the first operand of type T
by the second.
negate<T>
Returns the negative of its operand of type T.
Function Object Template Description
greater_equal<T>
Creates a binary predicate representing the >= operation between
objects of type
T.
greater<T>
Creates a binary predicate representing the > operation between
objects of type
T.
not2<B>

Creates a binary predicate that is the negation of a binary predicate of
type
B. For example, not2(less<int>) creates a binary predicate for
comparing objects of type
int that returns true if the left operand is
not less than the right operand. The template type parameter value
B
is deduced from the type of the constructor argument.
673
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 673
More on Algorithms
The <algorithm> and<numeric> headers define a large number of algorithms. The algorithms in the
<numeric> header are primarily devoted to processing arrays numerical values whereas those in the
algorithm header are more general purpose and provide such things as the ability to search, sort, copy,
and merge sequences of objects specified by iterators. There are far too many to discuss in detail in this
introductory chapter, so I’ll just introduce a few of the most useful algorithms from the
<algorithm>
header to give you a basic idea of how they can be used.
You have already seen the
sort() and copy() algorithms from the <algorithm> header in action. Take
a brief look at a few more of the more interesting functions in the
<algorithm> header.
fill()
The fill() function is of this form:
fill(ForwardIterator begin, ForwardIterator end, const Type& value)
This fills the elements specified by the iterators begin and end with value. For example, given a vector v
storing values of type string containing more than 10 elements, you could write:
fill(v.begin(), v.begin()+9, “invalid”);
This would set the first 10 elements in v to the value specified by the last argument to fill().

replace()
The replace() algorithm is of the form:
replace(ForwardIterator begin, ForwardIterator end,
const Type& oldValue, const Type& newValue)
This function examines each element in the range specified by begin and end and replaces each occurrence
of
oldValue by newValue. Given a vector v that stores string objects, you could replace occurrences of
“yes” by “no” with the following statement:
replace(v.begin(), v.end(), “yes”, “no”);
Like all the algorithms that receive an interval defined by a couple of iterators, the replace() function
will also work with pointers. For example:
char str[] = “A nod is as good as a wink to a blind horse.”;
replace(str, str+strlen(str), ‘o’, ‘*’);
cout << str << endl;
This will replace every occurrence of ‘o’ in the null-terminated string str by ‘*’, so the result of exe-
cuting this fragment will be the output:
A n*d is as g**d as a wink t* a blind h*rse.
674
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 674
find()
The find() function is of the form:
find(InputIterator begin, InputIterator end, const Type& value)
This function searches the sequence specified by the first two arguments for the first occurrence of value.
For example, given a vector
v containing values of type int, you could write:
vector<int>::iterator iter = find(v.begin(), v.end(), 21);
Obviously by using iter as the starting point for a new search, you could use the find() algorithm
repeatedly to find all occurrences of a given value. Perhaps like this:
vector<int>::iterator iter = v.begin();

int value = 21, count = 0;
while((iter = find(iter, v.end(), value)) != v.end())
{
iter++;
count++;
}
cout << “The vector contains “ << count << “ occurrences of “ << value << endl;
This fragment searches the vector v for all occurrences of value. On the first loop iteration, the search
starts at
v.begin(). On subsequent iterations, the search starts at one past the previous position that
was found. The loop will accumulate the total number of occurrences of value in
v. You could also code
the loop as a
for loop:
for((iter = find(v.begin(), v.end(), value)); iter != v.end() ;
(iter = find(iter, v.end(), value))++, count++);
Now the find operation is in the third loop control expression and you increment iter after the result
from the
find() function is stored. In my view the while loop is a better solution because it’s easier to
understand.
transform()
The transform() function comes in two versions. The first version applies an operation specified by a
unary function object to a set of elements specified by a pair of iterators, and is of the form:
transform(InputIterator begin, InputIterator end,
OutputIterator result, UnaryFunction f)
This version of transform() applies the unary function f to all elements in the range specified by the iter-
ators
begin and end and stores the results beginning at the position specified by the iterator result. The
result iterator can be the same as begin, in which case the results will replace the original elements. The
function returns an iterator that is one past the last result stored.

Here’s an example:
double values[] = { 2.5, -3.5, 4.5, -5.5, 6.5, -7.5};
675
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 675
vector<double> data(values, values+6);
transform(data.begin(),data.end(),data.begin(), negate<double>());
The transform() function call applies a negate<double> function object to all the elements in the vec-
tor,
data. The results are stored back in data and overwrite the original values; so after this operation the
vector will contain:
-2.5, 3.5, -4.5, 5.5, -6.5, 7.5
Because the operation writes the results back to the data vector, the transform() function will return
the iterator
data.end().
The second version of
transform() applies a binary function with the operands coming from two ranges
specified by iterators. The function is of the form:
transform(InputIterator1 begin1, InputIterator1 end1, InputIterator2 begin2,
OutputIterator result, BinaryFunction f)
The range specified by begin1 and end1 represents the set of left operands for the binary function f that
is specified by the last argument. The range representing the right operands starts at the position specified
by the
begin2 iterator; an end iterator does not need to be supplied for this range because there must be
the same number of elements as in the range specified by
begin1 and end1. The results will be stored
in the range starting at the
result iterator position. The result iterator can be the same as begin1 if
you want the results stored back in that range but it must not be any other position between
begin1 and

end1. Here’s an example of how you might use this version of the transform() algorithm:
double values[] = { 2.5, -3.5, 4.5, -5.5, 6.5, -7.5};
vector<double> data(values, values+6);
vector<double> squares(data.size());
transform(data.begin(),data.end(),data.begin(),
squares.begin(), multiplies<double>());
ostream_iterator<double> out(cout, “ “);
copy(data.begin(), data.end(), out);
You initialize the data vector with the contents of the values array. You then create a vector squares
to store the results of the transform() operation with the same number of elements as data. The
transform() function uses the multiplies<double>() function object to multiply each element
of
data by itself. The results are stored in the squares vector. The last two statements use an output
stream iterator to list the contents of
squares, which will be:
6.25 12.25 20.25 30.25 42.25 56.25
The STL for C++/CLI Programs
The STL/CLR library is an implementation of the STL for use with C++/CLI programs and the CLR. The
STL/CLR library covers all the capability that I have described for the STL for standard C++, so I won’t
go over the same ground again. I’ll simply highlight some of the differences and illustrate how you use
the STL/CLR with examples.
676
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 676
The STL/CLR library is contained within the cliext namespace so all STL names are qualified by cliext
rather than std. There are cliext include subdirectories that are equivalents for each of the standard C++
STL headers, including those for container adapters, algorithms and function objects and the STL/CLR
subdirectory name is the same in every case. Thus the C++/CLI equivalents to the templates defined in
a standard STL header such as
<functional> can be found in <cliext/functional>. So for example,

if you want to use vector containers in your C++/CLI program you need an
#include directive for
<cliext/vector>, and to use the queue<T> adapter the include file is <cliext/queue>.
STL/CLR Containers
STL/CLR containers can store reference types, handles to reference types, and unboxed value types;
they cannot store boxed value types. Most of the time, you will use STL/CLR containers with handles
or value types. If you do choose to store reference types in a container, they will be passed by value,
and the requirements for storing such objects in an STL/CLR container are essentially the same as for
a native STL container. Any reference type stored in an STL/CLR container must at least implement a
public copy constructor, a public assignment operator, and a public destructor. These requirements do
not apply when you are storing value types or handles to reference types in an STL/CLR container.
Don’t forget that the compiler does not supply default versions of the copy constructor and assignment
operator for reference types so when they are needed, you must always define them in your
ref classes.
Just to remind you, for a type
T, in a C++/CLI class these functions will be of the form:
T(const T% t) // Copy constructor
{
// Function body
}
T% operator=(const T% t) // Assignement operator
{
// Function body
}
~T() // Destructor
{
// Function body
}
Some container operations also require a no-arg constructor and the operator==() function to be defined.
A no-arg constructor may be used when space for elements in a container need to be allocated when the

container is storing objects rather than handles. If you are storing handles to reference types or value types
in an associative container such as a set or a map, they must overload at least one comparison operator —
the default requirement is for
operator<().
Using Sequence Containers
All the sequence containers provided by STL for native C++ are also available with the STL/CLR. The
STL/CLR implements all the operations that the native STL containers support, so the differences
tend to be notational arising from the use of C++/CLI types. Let’s explore the differences through
some examples.
677
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 677
Try It Out Storing Handles in a Vector
First, define Person as a ref class type:
// Person.h
// A class defining a person
#pragma once
using namespace System;
ref class Person
{
public:
Person():firstname(“”), secondname(“”){}
Person(String^ first, String^ second):firstname(first), secondname(second) {}
// Destructor
~Person(){}
// String representation of a person
virtual String^ ToString() override
{
return firstname + L” “ + secondname;
}

private:
String^ firstname;
String^ secondname;
};
The class has two constructors including a no-arg constructor, and a destructor. You will be only storing
handles to
Person objects in a vector, so you don’t need to implement a copy constructor or an assign-
ment operator. The
ToString() function here overrides the version inherited from class Object and
provides the way to get a
String representation of a Person object for output purposes.
You can define a
main() program that will store Person object handles in a vector like this:
// Ex10_13.cpp
// Storing handles in a vector
#include “Person.h”
#include <cliext/vector>
using namespace System;
using namespace cliext;
int main(array<System::String ^> ^args)
{
vector<Person^>^ people = gcnew vector<Person^>();
String^ first; // Stores a first name
String^ second; // Stores a second name
Person^ person; // Stores a Person
678
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 678
while(true)
{

Console::Write(L”Enter a first name or press Enter to end: “);
first = Console::ReadLine();
if(first->Length == 0)
break;
Console::Write(L”Enter a second name: “);
second = Console::ReadLine();
person = gcnew Person(first->Trim(),second->Trim());
people->push_back(person);
}
// Output the contents of the vector
Console::WriteLine(L”\nThe persons in the vector are:”);
for each(Person^ person in people)
Console::WriteLine(“{0}”,person);
return 0;
}
Here is a sample of output from this program:
Enter a first name or press Enter to end: Marilyn
Enter a second name: Monroe
Enter a first name or press Enter to end: Nicole
Enter a second name: Kidman
Enter a first name or press Enter to end: Judy
Enter a second name: Dench
Enter a first name or press Enter to end: Sally
Enter a second name: Field
Enter a first name or press Enter to end:
The persons in the vector are:
Marilyn Monroe
Nicole Kidman
Judy Dench
Sally Field

How It Works
You create the vector container on the CLR heap like this:
vector<Person^>^ people = gcnew vector<Person^>();
The template type argument is Person^, which is a handle to a Person object. The container is also
created on the CLR heap so the
people variable is a handle of type vector<Person^>^.
You create three handles for use as working storage in the input process:
String^ first; // Stores a first name
String^ second; // Stores a second name
Person^ person; // Stores a Person
The first two refer to String objects and the third is a handle to a Person object.
679
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 679
You read names from the standard input stream and create Person objects in an indefinite while loop:
while(true)
{
Console::Write(L”Enter a first name or press Enter to end: “);
first = Console::ReadLine();
if(first->Length == 0)
break;
Console::Write(L”Enter a second name: “);
second = Console::ReadLine();
person = gcnew Person(first->Trim(),second->Trim());
people->push_back(person);
}
After prompting for the input, the Console::ReadLine() function reads a name from the standard
input stream and stores it in
first. If just the Enter key was pressed, the length of the string will be
zero so you test for this to decide when to exit the loop. If the first name read is not of zero length, you

read the second name. You then create a
Person object on the CLR heap and store the handle in person.
For each
String^ argument to the constructor, you call the Trim() function to remove any leading or
trailing spaces. Calling the
push_back() function for the people container stores the person handle in
the vector.
The objects exist independently of the container, so if you were to discard the container (by assigning
nullptr to people for example) it would not necessarily destroy the objects pointed to by the handles
it contains. In our example, we do not retain any handles to the objects, so in this case destroying the
container would result in the objects not being referenced anywhere so eventually the garbage collector
would get around to destroying them and freeing the memory they occupy on the CLR heap.
After the input loop ends, you output the contents of a vector in a
for each loop:
for each(Person^ person in people)
Console::WriteLine(“{0}”,person);
The for each loop works directly with sequence containers so you can use this to iterate over all the
handles stored in the
people vector. The Console::WriteLine() function calls the ToString()
function for each Person object to produce the string to be inserted in the first argument string.
It is not normal usage but let’s look at another working example to explore how you can store
ref class
objects in a sequence container, rather than handles.
Try It Out Storing Reference Class Objects in a Double-Ended Queue
You will use Person object again but this time you will sort the contents of the container before you
generate the output. Here’s the new version of the
Person class:
// Person.h
// A class defining a person
#pragma once

680
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 680
using namespace System;
ref class Person
{
public:
Person():firstname(“”), secondname(“”){}
Person(String^ first, String^ second):firstname(first), secondname(second)
{}
// Copy constructors
Person(const Person% p):firstname(p.firstname),secondname(p.secondname){}
Person(Person^ p):firstname(p->firstname), secondname(p->secondname){}
// Destructor
~Person(){}
// Assignment operator
Person% operator=(const Person% p)
{
if(this != %p)
{
firstname = p.firstname;
secondname = p.secondname;
}
return *this;
}
// Less-than operator
bool operator<(Person^ p)
{
if(String::Compare(secondname, p->secondname) < 0 ||
(String::Compare(secondname, p->secondname)== 0 &&

String::Compare(firstname, p->firstname) < 0))
return true;
return false;
}
// String representation of a person
virtual String^ ToString() override
{
return firstname + L” “ + secondname;
}
private:
String^ firstname;
String^ secondname;
};
You now have two copy constructors for Person objects, one accepting a Person object as an argument
and the other accepting a handle to a
Person object. A sequence container requires both copy construc-
tors if it is to compile. You also have the assignment operator for
Person objects, which is also required
by the container. The
operator<() function is needed for the sort() algorithm.
681
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 681
Here’s the program code that will utilize this version of the Person class:
// Ex10_14.cpp
// Storing ref class objects in a double-ended queue
#include “Person.h”
#include <cliext/deque>
#include <cliext/algorithm>
using namespace System;

using namespace cliext;
int main(array<System::String ^> ^args)
{
deque<Person>^ people = gcnew deque<Person>();
String^ first; // Stores a first name
String^ second; // Stores a second name
Person person; // Stores a Person
while(true)
{
Console::Write(L”Enter a first name or press Enter to end: “);
first = Console::ReadLine();
if(first->Length == 0)
break;
Console::Write(L”Enter a second name: “);
second = Console::ReadLine();
person = Person(first->Trim(),second->Trim());
people->push_back(person);
}
sort(people->begin(), people->end());
// Output the contents of the vector
Console::WriteLine(L”\nThe persons in the vector are:”);
for each(Person^ p in people)
Console::WriteLine(“{0}”,p);
return 0;
}
Here is some output, similar to that of the previous example, except that the objects have been sorted in
ascending sequence:
Enter a first name or press Enter to end: Brad
Enter a second name: Pitt
Enter a first name or press Enter to end: George

Enter a second name: Clooney
Enter a first name or press Enter to end: Mel
Enter a second name: Gibson
Enter a first name or press Enter to end: Clint
Enter a second name: Eastwood
Enter a first name or press Enter to end:
682
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 682
The persons in the vector are:
George Clooney
Clint Eastwood
Mel Gibson
Brad Pitt
How It Works
You create the double-ended queue container like this:
deque<Person>^ people = gcnew deque<Person>();
The type parameter to the deque<T> template is now Person, so you will be storing Person objects, not
handles.
The storage for the element to be inserted into the container is now defined like this:
Person person; // Stores a Person
You are now going to store Person objects, so the working storage is no longer a handle as in the previous
example.
The input loop is the same as in the previous example except for the last two statements in the
while loop:
person = Person(first->Trim(),second->Trim());
people->push_back(person);
You create a Person object using the same syntax as in native C++. Although you don’t use the gcnew
keyword here, the compiler will arrange for the object to be created on the CLR heap because ref class
objects cannot be created on the stack.

After the input loop, you sort the elements in the container:
sort(people->begin(), people->end());
The sort() algorithm expects two iterators to specify the range of elements to be sorted and you obtain
these by calling the
begin() and end() functions for the container.
Finally you list the contents of the container in a
for each loop:
for each(Person^ p in people)
Console::WriteLine(“{0}”,p);
Note that you still use a handle as the loop variable. Even though the container stores the objects them-
selves, you access them through a handle in a
for each loop.
Of course, you could use the subscript operator for the container to access the objects. In this case the
output loop could be like this:
for(int i = 0 ; i<people->size() ; i++)
Console::WriteLine(“{0}”, %people[i]);
683
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 683
The subscript operator returns a reference to an object in the container, so because the
Console::WriteLine() function expects a handle, you have to use the % operator to obtain
the address of the object.
You also have the possibility to use iterators:
deque<Person>::iterator iter;
for(iter = people->begin() ; iter < people->end() ; ++iter)
Console::WriteLine(“{0}”, *iter);
The iterator type is defined in the deque<Person> class, as in native STL. The loop is very similar to a
native STL iterator loop but when you dereference the iterator, you get a handle to the element to which
the iterator points. This gives a clue as to why the
for each loop iterates over handles — because it uses

an iterator.
Just to complete the set, let’s see a sequence container storing elements that are value types.
Try It Out Storing Values Types in a List
This time you will use a list as the container and store values of type double. You will also try out the
sort() member of the list<T> container. Here’s the code:
// Ex10_15.cpp
// Storing value class objects in a list
#include <cliext/list>
using namespace System;
using namespace cliext;
int main(array<System::String ^> ^args)
{
array<double>^ values = {2.5, -4.5, 6.5, -2.5, 2.5, 7.5, 1.5, 3.5};
list<double>^ data = gcnew list<double>();
for(int i = 0 ; i<8 ; i++)
data->push_back(values[i]);
Console::WriteLine(“The list contains: “);
for each(double value in data)
Console::Write(“{0} “, value);
Console::WriteLine();
data->sort(greater<double>());
Console::WriteLine(“\nAfter sorting the list contains: “);
for each(double value in data)
Console::Write(“{0} “, value);
Console::WriteLine();
return 0;
}
684
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 684

Here is the output from this example:
The list contains:
2.5 -4.5 6.5 -2.5 2.5 7.5 1.5 3.5
After sorting the list contains:
7.5 6.5 3.5 2.5 2.5 1.5 -2.5 -4.5
How It Works
You first create a handle to a list<T> object with this statement:
list<double>^ data = gcnew list<double>();
The elements to be stored are of type double, so the template type parameter is the same as for the
native STL list.
The elements from the
values array are stored in the data container in a loop:
for(int i = 0 ; i<8 ; i++)
data->push_back(values[i]);
This is a straightforward loop that indexes through the values array and passes each element to the
push_back() function for the list. Of course, you could also use a for each loop for this:
for each(double value in values)
data->push_back(value);
You could also have initialized the list container with the contents of the values array:
list<double>^ data = gcnew list<double>(values);
Once the elements have been inserted in the list and the list contents have been written to the stan-
dard output stream, you sort the contents of the list using the
sort() function that is defined in the
list<double> class:
data->sort(greater<double>());
The sort() function will use the default function object, less<double>(), to sort the list unless you spec-
ify an alternative function object as the argument to the function. Here you specify
greater<double>() as
the function object to be used so the contents of the list are sorted in ascending sequence. Finally you out-
put the contents of the list so you can confirm the sort does work as it should.

Using Associative Containers
All the associative containers in STL/CLR work in essentially the same way as the equivalent native
STL containers but there are some important small differences, generally to do with how pairs are
represented.
685
Chapter 10: The Standard Template Library
25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 685

×