Choose from the following options: A Add an entry D Delete an entry G Get an entry L List entries Q QuitL Jack Bateman 312 455 6576Jane Junket 413 222 8134Bill Smith 213 466 7688Choose f
Trang 1Enter the phone number for Bill Smith: 213 466 7688Entry successful.
Choose from the following options:
A Add an entry D Delete an entry G Get an entry
L List entries Q Quitg
Enter a first name: MaryEnter a second name: Miller
No entry found for Mary MillerChoose from the following options:
A Add an entry D Delete an entry G Get an entry
L List entries Q Quitg
Enter a first name: MaryEnter a second name: JonesThe number for Mary Jones is 213 443 5671Choose from the following options:
A Add an entry D Delete an entry G Get an entry
L List entries Q Quitd
Enter a first name: MaryEnter a second name: JonesMary Jones erased
Choose from the following options:
A Add an entry D Delete an entry G Get an entry
L List entries Q QuitL
Jack Bateman 312 455 6576Jane Junket 413 222 8134Bill Smith 213 466 7688Choose from the following options:
A Add an entry D Delete an entry G Get an entry
L List entries Q Quitq
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 stringcontaining a phone number and the key is a Personobject.You load up the map initially in a whileloop:
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
Trang 2if(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 cinleaves a newline character in the buffer and this can cause problems for subsequentinput Calling ignore()for cinignores 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 callingthe 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 amap 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 Personobject that is createdusing the names The getName()member of the Personclass returns a name as a stringobject so youuse 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 probject enables you to check that the entry was successfully inserted into the map by testing itsboolmember The first member of prprovides access to the entry, whether it’s an existing entry or the newentry, and you use this to output a message when insertion fails
After initial input is complete, a whileloop provides the mechanism for querying and modifying thephone book The switchstatement in the body of the loop decides the action to be taken based on the
662
Chapter 10: The Standard Template Library
Trang 3character 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;
elsecout << “The number for “ << person.getName()
<< “ is “ << iter->second << endl;
}
A Personobject is created from a name that is read from the standard input stream by calling thegetPerson()function The Personobject 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 thelast entry in the map If an entry is found, accessing the secondmember of the pair pointed to by the iterator provides the number corresponding to the Personobject key
The deleteEntry()function deletes an entry from the map The process is similar to that used in thegetEntry()function, the difference being that when an entry is found by the find()function, theerase()function is called to remove it You could use another version of erase()to do this, in whichcase the code would be like this:
void deleteEntry(map<Person, string>& book){
Person person = getPerson();
if(book.erase(person))cout << person.getName() << “ erased.” << endl;
elsecout << “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 outputfor(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
Trang 4After an initial check for an empty map, the entries are listed in a forloop using an iterator The output
is left-justified by the setiosflagsmanipulator to produce tidy output This remains in effect untilresetiosflagsmanipulator 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 offunctions 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 amultimap 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 muchsense for a multimap
The insert()function flavors for a multimap are a little different from the function for a map The plest version of insert()that accepts a pair<K, T>object as an argument returns an iterator pointing
sim-to the entry that was inserted in the multimap The equivalent function for a map returns a pair objectbecause this provides an indication of when the key already exists in the map and the insertion is notpossible; of course, this cannot arise with a multimap A multimap also has a version of insert()withtwo 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 apair will be inserted when the same key already exists This version of insert()also returns an iteratorpointing to the element that was inserted The third version of insert()accepts two iterator argumentsthat 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 andthe 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 todelete 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 phonebookobject that is type
multimap<Person, string>
rather than type map<Person, string>, you could list the phone numbers corresponding to a givenkey like this:
Person person = Person(“Jack”, “Jones”);
multimap<Person, string>::iterator iter = phonebook.lower_bound(person);
Trang 5It’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 Iterator s
The <iterator>header defines several templates for iterators for transferring data from a source to adestination 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 twostream iterator templates, istream_iterator<T>for input streams and ostream_iterator<T>foroutput streams, where Tis the type of object to be extracted from, or written to, the stream The headeralso defines three inserter templates, inserter<T>, back_inserter<T>and front_inserter<T>,where Tis 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 numbersInputof type istream_iterator<int>that can point to objects oftype intin a stream The argument to the constructor specifies the actual stream to which the iteratorrelates, 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 alent to the end iterator for a container that you have been obtaining by calling the end()function Here’show you could create an end-of-stream iterator for cincomplementing the numbersInputiterator:istream_iterator<int> numbersEnd;
equiv-Now you have a pair of iterators that define a sequence of values of type intfrom cin You could usethese to load values from cininto 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:numbersInis an input stream iterator reading values of type intfrom cin, and numbersEndis anend-of-stream iterator for the same input stream The whileloop continues as long as numbersEndisnot equal to the end-of-stream iterator, numbersEnd When you execute this fragment, input continuesuntil end-of-stream is recognized for cin, but what produces that condition? The end-of-stream condi-tion will arise if you enter Ctrl+Zto close the input stream, or you enter an invalid character such as
a letter
665
Chapter 10: The Standard Template Library
Trang 6Of 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 tothe 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 thatcan access data from a stream buffer such as a stringobject The header also defines the istringstreamtype as basic_istringstream<char>, which will be a stream of characters of type char You can con-struct an istringstreamobject from a stringobject, which means you can read data from the stringobject just as you read from cin Because an istringstream<T> object is a stream, you can pass it to aninput iterator constructor and use the iterator to access the data in the underlying stream buffer Here’s anexample 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 istringstreamobject, input, from the stringobject, data, so you can read from data
as a stream You create two stream iterators that can access doublevalues in the inputstream, and youuse these to pass the contents of datato the accumulate()algorithm Note that the type of the thirdargument to the accumulate()function determines the type of the result so you must specify this as avalue of type doubleto 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 amap container to produce a collocation for the text Here’s the code:
Trang 7typedef std::map<string, int>::const_iterator Iter;
std::map<string, int> words; // Map to store words and word countscout << “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 iteratorwhile(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 pepperWhere’s the peck of pickled pepper Peter Piper picked
^ZHere are the word counts for the text you entered:
Trang 8How It Works
You first define a type for a constiterator for the map container:
typedef std::map<string, int>::const_iterator Iter;
Using this typedefstatement to define the Itertype will make the loop statement that outputs thecontents 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 countsThis container stores each word count of type intusing the word of type stringas the key This willmake it easy to accumulate the count for each word when you read from the input stream using streamiterators
std::istream_iterator<string> begin(cin); // Stream iterator
std::istream_iterator<string> end; // End stream iterator
The beginiterator is a stream iterator for the standard input stream and endis an end-of-stream iteratorthat 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 countThis simple whileloop does a great deal of work The loop control expression will iterate over the wordsentered via the standard input stream until the end-of-stream state is reached The stream iterator readswords from cindelimited by whitespace, just like the overloaded >>operator for cin Within the loopyou 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 *beginaccesses a wordand 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 anew entry with the count having the default value 0, and increment the beginiterator to the next word,ready for the next loop iteration The whole expression words[*begin++]++will increment the countfor the entry, regardless of whether it is a new entry or not Thus an existing entry will just get its countincremented 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 forloop:
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 controlexpressions are very much easier to read because of the typedeffor Iter
668
Chapter 10: The Standard Template Library
Trang 9Using Inserter Iterators
An inserter iterator is an iterator that can add new elements to any of the sequence containersvector<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>containernumbers
Inserting a value into the container is very simple:
*iter = 99; // Insert 99 at the front of the numbers containerThe 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 numberscontainer
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 cinand 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 doublevalues After a prompt for input, you create two inputstream iterators for values of type double The first iterator points to cinand the second iterator is anend-of-stream iterator created by the default constructor You specify the input to the copy()functionwith 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
Trang 10the copy operation to the list container, values This is quite powerful stuff If you ignore the prompt, inthree 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 put stream iterators for writing objects of type Tto an output stream There are two constructors for aninstance of the output stream iterator template One creates an iterator that just transfers data to the des-tination stream:
out-ostream_iterator<int> out(cout);
The type argument, int, to the template specifies the type of data to be handled and the constructorargument, cout, specifies the stream that will be the destination for data so the outiterator can writevalue of type intto 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 fied by the first two iterator arguments to the output iterator specified by the third argument Here thefunction copies the elements from the numbersvector to the outiterator, which will write the elements
speci-to cout The result of executing this fragment will be:
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 cinand 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:
Trang 11vector<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 ^ZYou entered the following values:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15The sum of these values is 120
Data is read from cinand 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
Trang 12You specify the source of data for the copy operation by the two input stream iterators, inputandinput_end, and the destination for the copy operation is a back inserter iterator for the numberscon-tainer Thus the copy operation will transfer data values from cinto the numberscontainer via theback 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 thedestination is the output stream iterator, out This operation will therefore write the data from numbers
to coutwith the values separated by a space
Finally you calculate the sum of the values in the numberscontainer in the output statement using theaccumulate()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 andthe initial value for the sum is zero If you wanted the average rather than the sum, this is easy too, beinggiven 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 youcan 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 functionobject 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 afunction 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
Trang 13Here’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 not2constructor is greater<string>(), which is a call to the constructor for thegreater<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 followingtable where the parameter Tspecifies the type of the operands
To make use of these you need to apply the transform()function, and I’ll explain how this works inthe 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 Tby 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 Tby 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 forcomparing objects of type intthat returns trueif the left operand isnot 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
Trang 14More 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 thealgorithm 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 thisintroductory 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 beginand endwith value For example, given a vector vstoring values of type stringcontaining more than 10 elements, you could write:
fill(v.begin(), v.begin()+9, “invalid”);
This would set the first 10 elements in vto 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 beginand endand replaces each occurrence
of oldValueby newValue Given a vector vthat stores stringobjects, 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()functionwill 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’, ‘*’);
Trang 15The 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 vcontaining values of type int, you could write:
vector<int>::iterator iter = find(v.begin(), v.end(), 21);
Obviously by using iteras the starting point for a new search, you could use the find()algorithmrepeatedly 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 vfor all occurrences of value On the first loop iteration, the searchstarts at v.begin() On subsequent iterations, the search starts at one past the previous position thatwas found The loop will accumulate the total number of occurrences of value in v You could also codethe loop as a forloop:
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 iterafter the resultfrom the find()function is stored In my view the whileloop is a better solution because it’s easier tounderstand
transform()
The transform()function comes in two versions The first version applies an operation specified by aunary 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 fto all elements in the range specified by the iter-ators beginand endand stores the results beginning at the position specified by the iterator result Theresultiterator can be the same as begin, in which case the results will replace the original elements Thefunction returns an iterator that is one past the last result stored
Trang 16vector<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 tor, data The results are stored back in dataand overwrite the original values; so after this operation thevector will contain:
transform(InputIterator1 begin1, InputIterator1 end1, InputIterator2 begin2,
OutputIterator result, BinaryFunction f)The range specified by begin1and end1represents the set of left operands for the binary function fthat
is specified by the last argument The range representing the right operands starts at the position specified
by the begin2iterator; an end iterator does not need to be supplied for this range because there must bethe same number of elements as in the range specified by begin1and end1 The results will be stored
in the range starting at the resultiterator position The resultiterator can be the same as begin1ifyou want the results stored back in that range but it must not be any other position between begin1andend1 Here’s an example of how you might use this version of the transform()algorithm:
copy(data.begin(), data.end(), out);
You initialize the datavector with the contents of the valuesarray You then create a vector squares
to store the results of the transform()operation with the same number of elements as data Thetransform()function uses the multiplies<double>()function object to multiply each element
of databy itself The results are stored in the squaresvector The last two statements use an outputstream 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 TheSTL/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 usethe STL/CLR with examples
676
Chapter 10: The Standard Template Library
Trang 17The STL/CLR library is contained within the cliextnamespace so all STL names are qualified by cliextrather than std There are cliextinclude subdirectories that are equivalents for each of the standard C++STL headers, including those for container adapters, algorithms and function objects and the STL/CLRsubdirectory 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 #includedirective for
<cliext/vector>, and to use the queue<T>adapter the include file is <cliext/queue>
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 thecontainer 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 TheSTL/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 throughsome examples
677
Chapter 10: The Standard Template Library
Trang 18Try It Out Storing Handles in a Vector
First, define Personas a refclass type:
// Person.h
// A class defining a person
#pragma once
using namespace System;
ref class Person
// String representation of a person
virtual String^ ToString() override
You can define a main()program that will store Personobject 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
Trang 19Console::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 vectorConsole::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: MarilynEnter a second name: Monroe
Enter a first name or press Enter to end: NicoleEnter a second name: Kidman
Enter a first name or press Enter to end: JudyEnter a second name: Dench
Enter a first name or press Enter to end: SallyEnter a second name: Field
Enter a first name or press Enter to end:
The persons in the vector are:
Marilyn MonroeNicole KidmanJudy DenchSally 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 Personobject The container is alsocreated on the CLR heap so the peoplevariable 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 nameString^ second; // Stores a second namePerson^ person; // Stores a PersonThe first two refer to Stringobjects and the third is a handle to a Personobject
679
Chapter 10: The Standard Template Library
Trang 20You read names from the standard input stream and create Personobjects in an indefinite whileloop: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: “);
The objects exist independently of the container, so if you were to discard the container (by assigningnullptrto peoplefor 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 thecontainer would result in the objects not being referenced anywhere so eventually the garbage collectorwould 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 eachloop:
for each(Person^ person in people)
Console::WriteLine(“{0}”,person);
The for eachloop works directly with sequence containers so you can use this to iterate over all thehandles stored in the peoplevector The Console::WriteLine()function calls the ToString()function for each Personobject 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 refclassobjects in a sequence container, rather than handles
Try It Out Storing Reference Class Objects in a Double-Ended Queue
You will use Personobject again but this time you will sort the contents of the container before you generate the output Here’s the new version of the Personclass:
Trang 21using namespace System;
ref class Person{
Person(Person^ p):firstname(p->firstname), secondname(p->secondname){}
// Destructor
~Person(){}
// Assignment operatorPerson% operator=(const Person% p){
if(this != %p){
firstname = p.firstname;
secondname = p.secondname;
}return *this;
}// Less-than operatorbool 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 personvirtual String^ ToString() override{
return firstname + L” “ + secondname;
}private:
by the container The operator<()function is needed for the sort()algorithm
681
Chapter 10: The Standard Template Library
Trang 22Here’s the program code that will utilize this version of the Personclass:
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
Console::Write(L”Enter a second name: “);
// Output the contents of the vector
Console::WriteLine(L”\nThe persons in the vector are:”);
for each(Person^ p in people)
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
Trang 23The persons in the vector are:
George ClooneyClint EastwoodMel GibsonBrad 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 Personobjects, nothandles
The storage for the element to be inserted into the container is now defined like this:
Person person; // Stores a PersonYou are now going to store Personobjects, so the working storage is no longer a handle as in the previousexample
The input loop is the same as in the previous example except for the last two statements in the whileloop:
person = Person(first->Trim(),second->Trim());
people->push_back(person);
You create a Personobject using the same syntax as in native C++ Although you don’t use the gcnewkeyword here, the compiler will arrange for the object to be created on the CLR heap because refclassobjects 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 obtainthese by calling the begin()and end()functions for the container
Finally you list the contents of the container in a for eachloop:
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 selves, you access them through a handle in a for eachloop
them-Of course, you could use the subscript operator for the container to access the objects In this case theoutput 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
Trang 24The 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:
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 thesort()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)
Trang 25Here is the output from this example:
The list contains:
2.5 -4.5 6.5 -2.5 2.5 7.5 1.5 3.5After 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 thenative STL list
The elements from the valuesarray are stored in the datacontainer in a loop:
for(int i = 0 ; i<8 ; i++)data->push_back(values[i]);
This is a straightforward loop that indexes through the valuesarray and passes each element to thepush_back()function for the list Of course, you could also use a for eachloop 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 valuesarray:
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 dard output stream, you sort the contents of the list using the sort()function that is defined in thelist<double>class:
stan-data->sort(greater<double>());
The sort()function will use the default function object, less<double>(), to sort the list unless you ify an alternative function object as the argument to the function Here you specify greater<double>()asthe 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
spec-Using Associative Containers
All the associative containers in STL/CLR work in essentially the same way as the equivalent nativeSTL containers but there are some important small differences, generally to do with how pairs are represented
685
Chapter 10: The Standard Template Library
Trang 26First, the type of an element that you store in a map of type
map<K, T>
in native STL is of type pair<K, T>, but in an STL/CLR map container it is of type
map<K,T>::value_type, which is a value type This implies that you can no longer use the
make_pair()function to create a map entry in STL/CLR Instead you use the static make_value()function that is defined in the map<K, T>class
Second, the insert()function that inserts a single element in a map<K, T>returns a value of typepair<map<K, T>::iterator, bool>for a native STL container, whereas for a STL/CLR map<K, T>container the insert()function returns an object of type
Try It Out Implementing a Phone Book Using a Map
This example works in more or less the same way as the native STL example you saw earlier, Ex10_10.First you must define a suitable version of the refclass Personthat will represent keys in the map:// Person.h
// A class defining a person
#pragma once
using namespace System;
ref class Person
Trang 27(String::Compare(secondname, p->secondname)== 0 &&
String::Compare(firstname, p->firstname) < 0))return true;
return false;
}// String representation of a personvirtual String^ ToString() override{
return firstname + L” “ + secondname;
}private:
using namespace System;
using namespace cliext;
// Read a person from standard inputPerson^ getPerson()
{String^ first;
map<Person^, String^>::value_type entry; // Stores a phone book entryString^ number;
Person^ person = getPerson();
Console::Write(L”Enter the phone number for {0}: “, person);
Trang 28map<Person^,String^>::pair_iter_bool pr = book->insert(entry);if(pr.second)
Console::WriteLine(L”Entry successful.”);
elseConsole::WriteLine(L”Entry exists for {0} The number is {1}”,
person, pr.first->second);}
// List the contents of a phone book
void listEntries(map<Person^, String^>^ book)
map<Person^, String^>::iterator iter;
for(iter = book->begin() ; iter != book->end() ; iter++)
Console::WriteLine(L”{0, -30}{1,-12}”,
iter->first, iter->second);
}
// Retrieve an entry from a phone book
void getEntry(map<Person^, String^>^ book)
{
Person^ person = getPerson();
map<Person^, String^>::const_iterator iter = book->find(person);if(iter == book->end())
Console::WriteLine(L”No entry found for {0}”, person);
else
Console::WriteLine(L”The number for {0} is {1}”,
person, iter->second);}
// Delete an entry from a phone book
void deleteEntry(map<Person^, String^>^ book)
{
Person^ person = getPerson();
map<Person^, String^>::iterator iter = book->find(person);
Trang 29Console::Write(L”Do you want to enter a phone book entry(Y or N): “) ;answer = Console::ReadLine()->Trim();
if(Char::ToUpper(answer[0]) == L’N’)break;
addEntry(phonebook);
}// Query the phonebookwhile(true)
{Console::WriteLine(L”\nChoose from the following options:”);
Console::WriteLine(L”A Add an entry D Delete an entry G Get an entry”);Console::WriteLine(L”L List entries Q Quit”);
answer = Console::ReadLine()->Trim();
switch(Char::ToUpper(answer[0])){
}Here’s a sample of output from this example:
Do you want to enter a phone book entry(Y or N): yEnter a first name: Jack
Enter a second name: BatemanEnter the phone number for Jack Bateman: 312 455 6576Entry successful
Do you want to enter a phone book entry(Y or N): yEnter a first name: Mary
Enter a second name: JonesEnter the phone number for Mary Jones: 213 443 5671
689
Chapter 10: The Standard Template Library
Trang 30Entry successful.
Do you want to enter a phone book entry(Y or N): yEnter a first name: Jane
Enter a second name: Junket
Enter the phone number for Jane Junket: 413 222 8134Entry successful
Do you want to enter a phone book entry(Y or N): nChoose from the following options:
A Add an entry D Delete an entry G Get an entry
L List entries Q Quit
a
Enter a first name: Bill
Enter a second name: Smith
Enter the phone number for Bill Smith: 213 466 7688Entry 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
690
Chapter 10: The Standard Template Library
Trang 31How It Works
The first action in main()is to define a suitable map object:
map<Person^, String^>^ phonebook = gcnew map<Person^, String^>();
This map stores an object that combines a handle to a Personobject as the key and a handle to aStringobject as the associated object Because this is an STL/CLR map, elements in the map are of typemap<Person^, String^>::value_type; for a native STL map the elements would typically be of typepair<Person, string>
Next you define somewhere to store an input response:
String^ answer;
The Console::ReadLine()function that you will use to obtain input from the standard input streamalways reads a line of input as a Stringobject, so it’s convenient to store a response as type String^.Within the whileloop that loads the map with new entries, you obtain a response to the first promptlike this:
answer = Console::ReadLine()->Trim();
The Console::ReadLine()call reads a line of input as a Stringobject and you call the Trim()functionfor the object returned to eliminate leading and railing spaces The result is stored in answerand the firstcharacter will be the input response
You check for L’n’or L’N’being entered as the response like this:
if(Char::ToUpper(answer[0]) == L’N’)break;
This uses the ToUpper()function in the Charclass to convert the first character in answerto uppercasebefore comparing it to L’N’ If the response is negative, you exit the loop As it is coded, entering any
response other than L’n’or L’N’is interpreted as L’Y’ To remove this anomaly you could add this ifstatement following the one above:
if(Char::ToUpper(answer[0]) != L’Y’){
Console::WriteLine(L”’{0}’ response is not valid Try again.”, answer[0]);continue;
}With this amendment, you output a message and go to the next iteration if an invalid response is entered
If the response in answeris not in the negative, you call the addEntry()helper function to add a new entry
to the map The first statement in addEntry()defines a variable you will use to store a new map entry:map<Person^, String^>::value_type entry; // Stores a phone book entry
691
Chapter 10: The Standard Template Library
Trang 32The value_typetype is defined in the map<K,T>template and is the equivalent of a pair in native STL.You obtain a handle to a new Personobject by calling the getPerson()helper function This usesConsole::ReadLine()to read the names and creates the Personobject on the CLR heap The phonenumber corresponding to the Personobject is read in the getEntry()function, and you call themake_value()function for the map with personand numberas arguments to create the new mapentry You insert the new entry using this statement:
map<Person^,String^>::pair_iter_bool pr = book->insert(entry);
The insert()function returns an object of type pair_iter_bool, which is similar to the pair objectreturned by the native STL version of the function The firstfield in the probject is a handle to an itera-tor and the secondfield is a boolvalue that indicates whether or not the insert operation was successful.You use the secondfield in prto output a suitable message, depending on how effective the insert oper-ation was
When the input loop ends, you prompt for operations on the map using a switch, as in the native version
of the program All the code for these operations is very similar to that in the native version Note that theoutput in the listEntries()function could be coded like this:
for each(map<Person^, String^>::value_type entry in book)
Console::WriteLine(L”{0, -30}{1,-12}”, entry->first, entry->second);
This iterates over all the entries in the map using a for eachloop You access the key and object in eachentry via the firstand secondfields
Summar y
This chapter introduced the capabilities of the STL in native C++ and how the same facilities are provided
by STL/CLR for use in your C++/CLI programs The important points discussed in this chapter are:
❑ The STL and STL/CLR capabilities include templates for containers, iterators, algorithms, andfunction objects
❑ A container is a class object for storing and organizing other objects Sequence containers storeobjects in a sequence, like an array Associative containers store elements that are key/objectpairs, where the key determines where the pair is stored in the container
❑ Iterators are objects that behave like pointers Iterators are used in pairs to define a set of objects
by a semi-open interval, where the first iterator points to the first object in the series and the ond iterator points to a position one past the last object in the series
sec-❑ Stream iterators are iterators that allow you to access or modify the contents of a stream
❑ There are four categories of iterators: input and output iterators, forward iterators, bidirectionaliterators, and random access iterators Each successive category of iterator provides more func-tionality than the previous one, so input and output iterators provide the least functionality andrandom access iterators provide the most
692
Chapter 10: The Standard Template Library
Trang 33❑ Algorithms are template functions that operate on a sequence of objects specified by a pair ofiterators.
❑ Function objects are objects of a type that overloads the ()operator (by implementing the tion operator()()in the class) The STL and STL/CLR define a wide range of standard itera-tors for use with containers and algorithms, and you can also write your own classes to definefunction objects
func-My objective in this chapter was to introduce enough of the details of the STL and STL/CLR to enableyou to explore the rest on your own There’s a great deal more there than I was able to discuss here, so Iencourage you to browse the documentation
Exercises
1. Write a native C++ program that will read some text from the standard input stream, possiblyinvolving several lines of input, and store the letters from the text in a list<T>container Sortthe letters in ascending sequence and output them
2. Use a priority_queue<T>container from the native STL to achieve the same result as Exercise 1
3. Implement Exercise 2 as a C++/CLI program.
4. Modify Ex10_10.cppso that it allows multiple phone numbers to be stored for a given name.The functionality in the program should reflect this, so the getEntry()function should displayall numbers for a given name and the deleteEntry()function should delete a particular person/number combination
5. Modify Ex10_16.cppto use an STL/CLR multimap to support multiple phone numbers for aperson in the phone book
6. Write a native C++ program to implement a phone book capability that will allow a name to be
entered to retrieve one or more numbers or a number to be entered to retrieve a name
7. Implement the previous exercise solution as a C++/CLI program
693
Chapter 10: The Standard Template Library
Trang 35Debugging Techniques
If you have been doing the exercises in the previous chapters, you have most likely been battlingwith bugs in your code In this chapter you will explore how the basic debugging capabilities builtinto Visual C++ 2008 can help with this You will also investigate some additional tools that you canuse to find and eliminate errors from your programs, and see some of the ways in which you canequip your programs with specific code to check for errors
In this chapter, you will learn about:
❑ How to run your program under the control of the Visual C++ 2008 debugger
❑ How to step through your program a statement at a time
❑ How to monitor or change the values of variables in your programs
❑ How to monitor the value of an expression in your program
❑ The call stack
❑ Assertions and how to use them to check your code
❑ How to add debugging specific code to a program
❑ How to detect memory leaks in a native C++ program
❑ How to use the execution tracing facilities and generate debugging output in C++/CLIprograms
Under standing Debugging
Bugs are errors in your program and debugging is the process of finding and eliminating them You
are undoubtedly aware by now that debugging is an integral part of the programming process — itgoes with the territory as they say The facts about bugs in your programs are rather depressing:
❑ Every program you write that is more than trivial will contain bugs that you need to try toexpose, find, and eliminate if your program is to be reliable and effective Note the threephases here — a program bug is not necessarily apparent; even when it is apparent you maynot know where it is in your source code; and even when you know roughly where it is, itmay not be easy to determine what exactly is causing the problem and thus eliminate it
Trang 36❑ Many programs that you write will contain bugs even after you think you have fully tested them.
❑ Program bugs can remain hidden in a program that is apparently operating correctly — times for years They generally become apparent at the most inconvenient moment
some-❑ Programs beyond a certain size and complexity always contain bugs, no matter how much timeand effort you expend testing them (The measure of size and complexity that guarantees thepresence of bugs is not precisely defined, but Visual C++ 2008 and your operating system cer-tainly come into this category!)
It is unwise to dwell on this last point if you are of a nervous disposition, especially if you fly a lot orregularly are in the vicinity of any process dependent on computers for proper operation that can bedamaging to your health in the event of failure
Many potential bugs are eliminated during the compile and link phases, but there are still quite a fewleft even after you manage to produce an executable module for your program Unfortunately, despitethe fact that program bugs are as inevitable as death and taxes, debugging is not an exact science; how-ever, you can still adopt a structured approach to eliminating bugs There are four broad strategies youcan adopt to make debugging as painless as possible:
❑ Don’t re-invent the wheel Understand and use the library facilities provided as part of VisualC++ 2008 (or other commercial software components you have access to) so that your programuses as much pre-tested code as possible Note that while this will reduce the likelihood of bugs
in your code, libraries, operating systems, and commercial software, components will still tain bugs in general, so your code can share those bugs
con-❑ Develop and test your code incrementally By testing each significant class and function ually, and gradually assembling separate code components after testing them, you can make thedevelopment process much easier, with fewer obscure bugs occurring along the way
individ-❑ Code defensively — which means writing code to guard against potential errors For example,declare member functions of native C++ classes that don’t modify an object as const Use constparameters where appropriate Don’t use ‘magic numbers’ in your code — define constobjectswith the required values
❑ Include debugging code that checks and validates data and conditions in your program fromthe outset This is something you will look at in detail later in this chapter
Because of the importance of ending up with programs that are as bug-free as is humanly possible, VisualC++ 2008 provides you with a powerful armory of tools for finding bugs Before you get into the detailedmechanics, however, look a little closer at how bugs arise
Program Bugs
Of course, the primary originator of bugs in your program is you and the mistakes you make Thesemistakes range from simple typos — just pressing the wrong key — to getting the logic completelywrong I, too, find it hard to believe that I can make such silly mistakes so often, but no one has yetmanaged to come up with a credible alternative as to how bugs get into your code — so it must betrue! Humans are creatures of habit so you will probably find yourself making some mistakes timeand time again Frustratingly, many errors are glaringly obvious to others, but invisible to you — this
696
Chapter 11: Debugging Techniques
Trang 37is just your computer’s way of teaching you a bit of humility Broadly there are two kinds of errorsyou can make in your code that result in program bugs:
❑ Syntactic errors— These are errors that result from statements that are not of the correct form; forexample, if you miss a semicolon from the end of a statement or use a colon where you should put a comma You don’t have to worry too much about syntactic errors The compiler recognizes allsyntactic errors, and you generally get a fairly good indication of what the error is so it’s easy to fix
❑ Semantic errors— These are errors where the code is syntactically correct, but it does not dowhat you intended The compiler cannot know what you intended to achieve with your pro-gram, so it cannot detect semantic errors; however, you will often get an indication that some-thing is wrong because the program terminates abnormally The debugging facilities in VisualC++ 2008 are aimed at helping you find semantic errors Semantic errors can be very subtle and difficult to find, for example, where the program occasionally produces the wrong results
or crashes infrequently Perhaps the most difficult of such bugs arise in multi-threaded grams where concurrent paths of execution are not managed properly
pro-Of course, there are bugs in the system environment that you are using (Visual C++ 2008 included) but thisshould be the last place you suspect when your program doesn’t work Even when you do conclude that it
must be the compiler or the operating system, nine times out of ten you will be wrong There are certainly
bugs in Visual C++ 2008, however, and if you want to keep up with those identified to date, together withany fixes available, you can search the information provided on the Microsoft Web site related to Visual C++( http://msdn2.microsoft.com/en-us/visualc/default.aspx), or better still, if you can afford a sub-scription to Microsoft Developer Network, you get quarterly updates on the latest bugs and fixes
It can be helpful to make a checklist of bugs you find in your code for future reference By examiningnew code that you write for the kinds of errors you have made in the past, you can often reduce the time needed to debug new projects
From the nature of programming, bugs are virtually infinite in their variety, but there are some kinds thatare particularly common You may be well aware of most of these, but take a quick look at them anyway
Common Bugs
A useful way of cataloguing bugs is to relate them to the symptoms they cause because this is how you experience them in the first instance The following list of five common symptoms is by no meansexhaustive, and you are certainly able to add to it as you gain programming experience:
Continued
Data corrupted Failure to initialize variable
Exceeding integer type rangeInvalid pointer
Error in array index expressionLoop condition error
Error in size of dynamically allocated arrayFailing to implement class copy constructor, assignment operator,
or destructor
697
Chapter 11: Debugging Techniques
Trang 38Look at how many different kinds of errors can be caused by invalid pointers and the myriad symptomsthat bad pointers can generate This is possibly the most frequent cause of those bugs that are hard to find,
so always double-check your pointer operations If you are conscious of the ways in which bad pointersarise, you can avoid many of the pitfalls The common ways in which bad pointers arise are:
❑ Failing to initialize a pointer when you declare it
❑ Failing to set a pointer to free store memory to null when you delete the space allocated
❑ Returning the address of a local variable from a function
❑ Failing to implement the copy constructor and assignment operator for classes that allocate freestore memory
Even if you do all this, there will still be bugs in your code, so now look at the tools that Visual C++ 2008provides to assist debugging
Basic Debugging Operations
So far, although you have been creating debug versions of the program examples, you haven’t been
using the debugger The debugger is a program that controls the execution of your program in such a
way that you can step through the source code one line at a time, or run to a particular point in the gram At each point in your code where the debugger stops, you can inspect or even change the values ofvariables before continuing You can also change the source code, recompile, and then restart the programfrom the beginning You can even change the source code in the middle of stepping through a program.When you move to the next step after modifying the code, the debugger automatically recompiles beforeexecuting the next statement
Unhandled exceptions Invalid pointer or reference
Missing catch handlerProgram hangs or crashes Failure to initialize variable
Infinite loopInvalid pointerFreeing the same free store memory twiceFailure to implement, or error in, class destructorFailure to process unexpected user input properlyStream input data incorrect Reading using the extraction operator and the getline() function
Incorrect results Typographical error: = instead of ==, or i instead of j etc
Failure to initialize a variableExceeding the range of an integer type Invalid pointer
Omitting break in a switch statement
698
Chapter 11: Debugging Techniques
Trang 39To understand the basic debug capabilities of Visual C++ 2008, you will use the debugger on a programthat you are reasonably sure works You can then just pull the levers to see how things operate Take asimple example from back in Chapter 4 that uses pointers:
// Ex4_05.cpp// Exercising pointers
#include <iostream>
using namespace std;
int main(){
long* pnumber = NULL; // Pointer declaration & initializationlong number1 = 55, number2 = 99;
pnumber = &number1; // Store address in pointer
*pnumber += 11; // Increment number1 by 11cout << endl
<< “number1 = “ << number1
<< “ &number1 = “ << hex << pnumber;
pnumber = &number2; // Change pointer to address of number2number1 = *pnumber*10; // 10 times number2
cout << endl
<< “number1 = “ << dec << number1
<< “ pnumber = “ << hex << pnumber
<< “ *pnumber = “ << dec << *pnumber;
of your program’s data at any time during execution You arrange to execute this example one statement
at a time and to monitor the contents of the variables that you are interested in In this case you want tolook at pnumber, the contents of the location pointed to by pnumber(which is *pnumber), number1, andnumber2
First you need to be sure that the build configuration for the example is set to Win32 Debug rather thanWin32 Release (Win32 Debug is the default, unless you’ve changed it) The build configuration selectsthe set of project settings for the build operation on your program that you can see when you select theProject/Settings menu option The current build configuration in effect is shown in the pair of adjacentdrop-down lists on the Standard toolbar To display or remove a particular toolbar you just right-clickthe toolbar and select or deselect a toolbar in the list Make sure you check the box against Debug to dis-play the debugging toolbar It comes up automatically when the debugger is operating, but you shouldtake a look at what it contains before you get to start the debugger You can change the build configuration
by extending the drop-down list and choosing the alternative You can also use the Build > ConfigurationManager menu option The Standard toolbar is shown in Figure 11-1
699
Chapter 11: Debugging Techniques
Trang 40Figure 11-1
You can find out what the toolbar buttons are for by letting the mouse cursor linger over a toolbar button
A tool tip for that button appears that identifies its function
The Debugconfiguration in a project causes additional information to be included in your executable gram when you compile it so that the debugging facilities can be used This extra information is stored inthe pdbfile that will be in the Debug folder for your project The ‘release’ configuration omits this infor-mation as it represents overhead that you wouldn’t want in a fully tested program With the Professionalversion of Visual C++ 2008, the compiler also optimizes the code when compiling the release version of aprogram Optimization is inhibited when the debug version is compiled because the optimization processcan involve resequencing code to make it more efficient, or even omitting redundant code altogether.Because this destroys the one-to-one mapping between the source code and corresponding blocks ofmachine code, optimization makes stepping through a program potentially confusing to say the least.The Debug toolbar is shown in Figure 11-2
pro-Figure 11-2
If you inspect the tooltips for the buttons on this toolbar, you get a preliminary idea of what they do —you will use some of them shortly With the example from Chapter 4, you won’t use all the debuggingfacilities available to you, but you will try out some of the more important features After you are famil-iar with stepping through a program using the debugger, you explore more of the features with a pro-gram that has bugs
You can start the debugger by clicking the leftmost button on the Debug toolbar, by selecting the Debug >Start Debugging menu item, or by pressing F5 I suggest that you use the toolbar for the example Thedebugger has two primary modes of operation — it works through the code by single stepping (which isessentially executing one statement at a time), or runs to a particular point in the source code The point
in the source where the debugger is to stop is determined either by where you have placed the cursor or,
more usefully, at a designated stopping point called a breakpoint Check out how you define breakpoints.
Setting Breakpoints
A breakpoint is a point in your program where the debugger automatically suspends execution when in
debugging mode You can specify multiple breakpoints so that you can run your program, stopping atpoints of interest that you select along the way At each breakpoint you can look at variables within theprogram and change them if they don’t have the values they should You are going to execute the Ex4_05program one statement at a time, but with a large program this would be impractical Usually, you will onlywant to look at a particular area of the program where you think there might be an error Consequently, youwould usually set breakpoints where you think the error is and run the program so that it halts at the firstbreakpoint You can then single step from that point if you want, where a single step implies executing asingle source code statement
700
Chapter 11: Debugging Techniques