Thinking in C plus plus (P24) docx

50 279 0
Thinking in C plus plus (P24) docx

Đ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

Chapter 15: Multiple Inheritance 251 // A map of vectors #include <map> #include <vector> #include <string> #include <iostream> #include <algorithm> #include <ctime> using namespace std; typedef map<string, vector<string> > Thesaurus; typedef pair<string, vector<string> > TEntry; typedef Thesaurus::iterator TIter; ostream& operator<<(ostream& os,const TEntry& t){ os << t.first << ": "; copy(t.second.begin(), t.second.end(), ostream_iterator<string>(os, " ")); return os; } // A generator for thesaurus test entries: class ThesaurusGen { static const string letters; static int count; public: int maxSize() { return letters.size(); } ThesaurusGen() { srand(time(0)); } TEntry operator()() { TEntry result; if(count >= maxSize()) count = 0; result.first = letters[count++]; int entries = (rand() % 5) + 2; for(int i = 0; i < entries; i++) { int choice = rand() % maxSize(); char cbuf[2] = { 0 }; cbuf[0] = letters[choice]; result.second.push_back(cbuf); } return result; } }; int ThesaurusGen::count = 0; Chapter 15: Multiple Inheritance 252 const string ThesaurusGen::letters("ABCDEFGHIJKL" "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); int main() { Thesaurus thesaurus; // Fill with 10 entries: generate_n( inserter(thesaurus, thesaurus.begin()), 10, ThesaurusGen()); // Print everything: copy(thesaurus.begin(), thesaurus.end(), ostream_iterator<TEntry>(cout, "\n")); // Ask for a "word" to look up: while(true) { cout << "Select a \"word\", 0 to quit: "; for(TIter it = thesaurus.begin(); it != thesaurus.end(); it++) cout << (*it).first << ' '; cout << endl; string reply; cin >> reply; if(reply.at(0) == '0') return 0; // Quit if(thesaurus.find(reply) == thesaurus.end()) continue; // Not in list, try again vector<string>& v = thesaurus[reply]; copy(v.begin(), v.end(), ostream_iterator<string>(cout, " ")); cout << endl; } } ///:~ A Thesaurus maps a string (the word) to a vector<string> (the synonyms). A TEntry is a single entry in a Thesaurus . By creating an ostream operator<< for a TEntry , a single entry from the Thesaurus can easily be printed (and the whole Thesaurus can easily be printed with copy( ) ). The ThesaurusGen creates “words” (which are just single letters) and “synonyms” for those words (which are just other randomly-chosen single letters) to be used as thesaurus entries. It randomly chooses the number of synonym entries to make, but there must be at least two. All the letters are chosen by indexing into a static string that is part of ThesaurusGen . In main( ) , a Thesaurus is created, filled with 10 entries and printed using the copy( ) algorithm. Then the user is requested to choose a “word” to look up by typing the letter of that word. The find( ) member function is used to find whether the entry exists in the map (remember, you don’t want to use operator[ ] or it will automatically make a new entry if it Chapter 15: Multiple Inheritance 253 doesn’t find a match!). If so, operator[ ] is used to fetch out the vector<string> which is displayed. Because templates make the expression of powerful concepts easy, you can take this concept much further, creating a map of vector s containing map s, etc. For that matter, you can combine any of the STL containers this way. Cleaning up containers of pointers In Stlshape.cpp , the pointers did not clean themselves up automatically. It would be convenient to be able to do this easily, rather than writing out the code each time. Here is a function template that will clean up the pointers in any sequence container; note that it is placed in the book’s root directory for easy access: //: :purge.h // Delete pointers in an STL sequence container #ifndef PURGE_H #define PURGE_H #include <algorithm> template<class Seq> void purge(Seq& c) { typename Seq::iterator i; for(i = c.begin(); i != c.end(); i++) { delete *i; *i = 0; } } // Iterator version: template<class InpIt> void purge(InpIt begin, InpIt end) { while(begin != end) { delete *begin; *begin = 0; begin++; } } #endif // PURGE_H ///:~ In the first version of purge( ) , note that typename is absolutely necessary; indeed this is exactly the case that the keyword was added for: Seq is a template argument, and iterator is Chapter 15: Multiple Inheritance 254 something that is nested within that template. So what does Seq::iterator refer to? The typename keyword specifies that it refers to a type, and not something else. While the container version of purge must work with an STL-style container, the iterator version of purge( ) will work with any range, including an array. Here is Stlshape.cpp , modified to use the purge( ) function: //: C04:Stlshape2.cpp // Stlshape.cpp with the purge() function #include " /purge.h" #include <vector> #include <iostream> using namespace std; class Shape { public: virtual void draw() = 0; virtual ~Shape() {}; }; class Circle : public Shape { public: void draw() { cout << "Circle::draw\n"; } ~Circle() { cout << "~Circle\n"; } }; class Triangle : public Shape { public: void draw() { cout << "Triangle::draw\n"; } ~Triangle() { cout << "~Triangle\n"; } }; class Square : public Shape { public: void draw() { cout << "Square::draw\n"; } ~Square() { cout << "~Square\n"; } }; typedef std::vector<Shape*> Container; typedef Container::iterator Iter; int main() { Container shapes; shapes.push_back(new Circle); Chapter 15: Multiple Inheritance 255 shapes.push_back(new Square); shapes.push_back(new Triangle); for(Iter i = shapes.begin(); i != shapes.end(); i++) (*i)->draw(); purge(shapes); } ///:~ When using purge( ) , you must be careful to consider ownership issues – if an object pointer is held in more than one container, then you must be sure not to delete it twice, and you don’t want to destroy the object in the first container before the second one is finished with it. Purging the same container twice is not a problem, because purge( ) sets the pointer to zero once it deletes that pointer, and calling delete for a zero pointer is a safe operation. Creating your own containers With the STL as a foundation, it’s possible to create your own containers. Assuming you follow the same model of providing iterators, your new container will behave as if it were a built-in STL container. Consider the “ring” data structure, which is a circular sequence container. If you reach the end, it just wraps around to the beginning. This can be implemented on top of a list as follows: //: C04:Ring.cpp // Making a "ring" data structure from the STL #include <iostream> #include <list> #include <string> using namespace std; template<class T> class Ring { list<T> lst; public: // Declaration necessary so the following // 'friend' statement sees this 'iterator' // instead of std::iterator: class iterator; friend class iterator; class iterator : public std::iterator< std::bidirectional_iterator_tag,T,ptrdiff_t>{ list<T>::iterator it; list<T>* r; Chapter 15: Multiple Inheritance 256 public: // "typename" necessary to resolve nesting: iterator(list<T>& lst, const typename list<T>::iterator& i) : r(&lst), it(i) {} bool operator==(const iterator& x) const { return it == x.it; } bool operator!=(const iterator& x) const { return !(*this == x); } list<T>::reference operator*() const { return *it; } iterator& operator++() { ++it; if(it == r->end()) it = r->begin(); return *this; } iterator operator++(int) { iterator tmp = *this; ++*this; return tmp; } iterator& operator () { if(it == r->begin()) it = r->end(); it; return *this; } iterator operator (int) { iterator tmp = *this; *this; return tmp; } iterator insert(const T& x){ return iterator(*r, r->insert(it, x)); } iterator erase() { return iterator(*r, r->erase(it)); } }; Chapter 15: Multiple Inheritance 257 void push_back(const T& x) { lst.push_back(x); } iterator begin() { return iterator(lst, lst.begin()); } int size() { return lst.size(); } }; int main() { Ring<string> rs; rs.push_back("one"); rs.push_back("two"); rs.push_back("three"); rs.push_back("four"); rs.push_back("five"); Ring<string>::iterator it = rs.begin(); it++; it++; it.insert("six"); it = rs.begin(); // Twice around the ring: for(int i = 0; i < rs.size() * 2; i++) cout << *it++ << endl; } ///:~ You can see that the iterator is where most of the coding is done. The Ring iterator must know how to loop back to the beginning, so it must keep a reference to the list of its “parent” Ring object in order to know if it’s at the end and how to get back to the beginning. You’ll notice that the interface for Ring is quite limited; in particular there is no end( ) , since a ring just keeps looping. This means that you won’t be able to use a Ring in any STL algorithms that require a past-the-end iterator – which is many of them. (It turns out that adding this feature is a non-trivial exercise). Although this can seem limiting, consider stack , queue and priority_queue , which don’t produce any iterators at all! Freely-available STL extensions Although the STL containers may provide all the functionality you’ll ever need, they are not complete. For example, the standard implementations of set and map use trees, and although these are reasonably fast they may not be fast enough for your needs. In the C++ Standards Committee it was generally agreed that hashed implementations of set and map should have Chapter 15: Multiple Inheritance 258 been included in Standard C++, however there was not considered to be enough time to add these components, and thus they were left out. Fortunately, there are freely-available alternatives. One of the nice things about the STL is that it establishes a basic model for creating STL-like classes, so anything built using the same model is easy to understand if you are already familiar with the STL. The SGI STL (freely available at http://www.sgi.com/Technology/STL/) is one of the most robust implementations of the STL, and can be used to replace your compiler’s STL if that is found wanting. In addition they’ve added a number of extensions including hash_set , hash_multiset , hash_map , hash_multimap , slist (a singly-linked list) and rope (a variant of string optimized for very large strings and fast concatenation and substring operations). Let’s consider a performance comparison between a tree-based map and the SGI hash_map . To keep things simple, the mappings will be from int to int : //: C04:MapVsHashMap.cpp // The hash_map header is not part of the // Standard C++ STL. It is an extension that // is only available as part of the SGI STL: #include <hash_map> #include <iostream> #include <map> #include <ctime> using namespace std; int main(){ hash_map<int, int> hm; map<int, int> m; clock_t ticks = clock(); for(int i = 0; i < 100; i++) for(int j = 0; j < 1000; j++) m.insert(make_pair(j,j)); cout << "map insertions: " << clock() - ticks << endl; ticks = clock(); for(int i = 0; i < 100; i++) for(int j = 0; j < 1000; j++) hm.insert(make_pair(j,j)); cout << "hash_map insertions: " << clock() - ticks << endl; ticks = clock(); for(int i = 0; i < 100; i++) for(int j = 0; j < 1000; j++) m[j]; Chapter 15: Multiple Inheritance 259 cout << "map::operator[] lookups: " << clock() - ticks << endl; ticks = clock(); for(int i = 0; i < 100; i++) for(int j = 0; j < 1000; j++) hm[j]; cout << "hash_map::operator[] lookups: " << clock() - ticks << endl; ticks = clock(); for(int i = 0; i < 100; i++) for(int j = 0; j < 1000; j++) m.find(j); cout << "map::find() lookups: " << clock() - ticks << endl; ticks = clock(); for(int i = 0; i < 100; i++) for(int j = 0; j < 1000; j++) hm.find(j); cout << "hash_map::find() lookups: " << clock() - ticks << endl; } ///:~ The performance test I ran showed a speed improvement of roughly 4:1 for the hash_map over the map in all operations (and as expected, find( ) is slightly faster than operator[ ] for lookups for both types of map). If a profiler shows a bottleneck in your map , you should consider a hash_map . Summary The goal of this chapter was not just to introduce the STL containers in some considerable depth (of course, not every detail could be covered here, but you should have enough now that you can look up further information in the other resources). My higher hope is that this chapter has made you grasp the incredible power available in the STL, and shown you how much faster and more efficient your programming activities can be by using and understanding the STL. The fact that I could not escape from introducing some of the STL algorithms in this chapter suggests how useful they can be. In the next chapter you’ll get a much more focused look at the algorithms. Chapter 15: Multiple Inheritance 260 Exercises 1. Create a set<char> , then open a file (whose name is provided on the command line) and read that file in a char at a time, placing each char in the set . Print the results and observe the organization, and whether there are any letters in the alphabet that are not used in that particular file. 2. Create a kind of “hangman” game. Create a class that contains a char and a bool to indicate whether that char has been guessed yet. Randomly select a word from a file, and read it into a vector of your new type. Repeatedly ask the user for a character guess, and after each guess display the characters in the word that have been guessed, and underscores for the characters that haven’t. Allow a way for the user to guess the whole word. Decrement a value for each guess, and if the user can get the whole word before the value goes to zero, they win. 3. Modify WordCount.cpp so that it uses insert( ) instead of operator[ ] to insert elements in the map . 4. Modify WordCount.cpp so that it uses a multimap instead of a map . 5. Create a generator that produces random int values between 0 and 20. Use this to fill a multiset<int> . Count the occurrences of each value, following the example given in MultiSetWordCount.cpp . 6. Change StlShape.cpp so that it uses a deque instead of a vector . 7. Modify Reversible.cpp so it works with deque and list instead of vector . 8. Modify Progvals.h and ProgVals.cpp so that they expect leading hyphens to distinguish command-line arguments. 9. Create a second version of Progvals.h and ProgVals.cpp that uses a set instead of a map to manage single-character flags on the command line (such as -a -b -c etc) and also allows the characters to be ganged up behind a single hyphen (such as -abc ). 10. Use a stack<int> and build a Fibonacci sequence on the stack. The program’s command line should take the number of Fibonacci elements desired, and you should have a loop that looks at the last two elements on the stack and pushes a new one for every pass through the loop. 11. Open a text file whose name is provided on the command line. Read the file a word at a time (hint: use >> ) and use a multiset<string> to create a word count for each word. 12. Modify BankTeller.cpp so that the policy that decides when a teller is added or removed is encapsulated inside a class. 13. Create two classes A and B (feel free to choose more interesting names). Create a multimap<A, B> and fill it with key-value pairs, ensuring that there are some duplicate keys. Use equal_range( ) to discover and print a [...]... this could rapidly get very difficult to decipher Instead of all this composing and transforming, you can write your own function objects (without using the SGI extensions) as follows: //: C0 5:NoCompose.cpp // Writing out the function objects explicitly #include "copy_if.h" #include #include #include #include #include #include #include ... a second example, using a vector and replacing strings that satisfy particular conditions: //: C0 5:Binder2.cpp // More binders #include #include #include #include #include using namespace std; int main() { ostream_iterator out(cout, " "); vector v, r; v.push_back("Hi"); v.push_back("Hi"); v.push_back("Hey"); v.push_back("Hee");... second and third function objects, respectively The function object that results from compose2( ) expects one argument, and it feeds that argument to the second and third function objects Here is an example: //: C0 5:Compose2.cpp // Using the SGI STL compose2() function #include "copy_if.h" #include #include #include #include #include #include ... function to each pointer in a container of Shape: //: C0 5:MemFun1.cpp // Applying pointers to member functions #include " /purge.h" #include #include #include #include using namespace std; class Shape { public: virtual void draw() = 0; virtual ~Shape() {} }; class Circle : public Shape { public: virtual void draw() { cout . this concept much further, creating a map of vector s containing map s, etc. For that matter, you can combine any of the STL containers this way. Cleaning up containers of pointers In Stlshape.cpp ,. Contain2, typename BinaryFunc> void testBinary(Contain1& src1, Contain1& src2, Contain2& dest, BinaryFunc f) { transform(src1.begin(), src1.end(), src2.begin(), dest.begin(),. <iostream> #include <map> #include <ctime> using namespace std; int main(){ hash_map<int, int> hm; map<int, int> m; clock_t ticks = clock(); for(int i = 0;

Ngày đăng: 05/07/2014, 19:20

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

Tài liệu liên quan