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

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

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

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 [...]... 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 67 6... 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}; 67 5 25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 67 6 Chapter 10: The Standard Template Library vector data(values, values +6) ; transform(data.begin(),data.end(),data.begin(), negate()); The transform() function call applies a negate function... name: Jack Enter a second name: Bateman Enter the phone number for Jack Bateman: 312 455 65 76 Entry successful Do you want to enter a phone book entry(Y or N): y Enter a first name: Mary Enter a second name: Jones Enter the phone number for Mary Jones: 213 443 567 1 68 9 25905c10.qxd:WroxPro 2/21/08 9:12 AM Page 69 0 Chapter 10: The Standard Template Library Entry successful Do you want to enter a phone... Bateman 312 455 Jane Junket 413 222 Bill Smith 213 466 Choose from the following options: A Add an entry D Delete an entry L List entries Q Quit q 69 0 G Get an entry G Get an entry 65 76 8134 768 8 G Get an entry 25905c10.qxd:WroxPro 2/21/08 9:12 AM Page 69 1 Chapter 10: The Standard Template Library How It Works The first action in main() is to define a suitable map object: map^ phonebook... particular person/ number combination 69 3 25905c10.qxd:WroxPro 2/21/08 9:12 AM Page 69 4 25905c11.qxd:WroxPro 2/21/08 9:12 AM Page 69 5 11 Debugging Techniques If you have been doing the exercises in the previous chapters, you have most likely been battling with bugs in your code In this chapter you will explore how the basic debugging capabilities built into Visual C++ 2008 can help with this You will also... each(double value in data) Console::Write(“{0} “, value); Console::WriteLine(); return 0; } 68 4 25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 68 5 Chapter 10: The Standard Template Library 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 object with... to support multiple phone numbers for a person 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 Implement Exercise 2 as a C++/ CLI program Modify Ex10_10.cpp so that it allows multiple phone... 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 67 7 25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 67 8 Chapter 10: The Standard Template Library Try It... Jones The number for Mary Jones is 213 443 567 1 Choose from the following options: A Add an entry D Delete 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 L List entries Q Quit L Jack Bateman 312 455 Jane Junket 413 222 Bill Smith 213 466 Choose from the following options: A Add... second): firstname(first), secondname(second) {} // Destructor ~Person(){} // Less-than operator bool operatorsecondname) < 0 || 68 6 25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 68 7 Chapter 10: The Standard Template Library (String::Compare(secondname, p->secondname)== 0 && String::Compare(firstname, p->firstname) < 0)) return true; return false; } // String . <iomanip> #include <string> #include <map> 66 6 Chapter 10: The Standard Template Library 25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 66 6 using std::cout; using std::cin; using std::endl; using. Delete an entry G Get an entry L List entries Q Quit L Jack Bateman 312 455 65 76 Jane Junket 413 222 8134 Bill Smith 213 466 768 8 Choose from the following options: A Add an entry D Delete an entry. the loop decides the action to be taken based on the 66 2 Chapter 10: The Standard Template Library 25905c10.qxd:WroxPro 2/21/08 9:11 AM Page 66 2 character that is entered and stored in answer.

Ngày đăng: 12/08/2014, 19:20

Tài liệu cùng người dùng

Tài liệu liên quan