Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Thinking In C++, 2nd Edition, Volume 2 Standard Libraries& Advanced Topics - Eckel B..pdf
Скачиваний:
314
Добавлен:
24.05.2014
Размер:
2.09 Mб
Скачать

ostream_iterator<pair<int,int> >(cout,"\n")); } ///:~

The second argument to inserter is an iterator, which actually isn’t used in the case of associative containers since they maintain their order internally, rather than allowing you to tell them where the element should be inserted. However, an insert_iterator can be used with many different types of containers so you must provide the iterator.

Note how the ostream_iterator is created to output a pair; this wouldn’t have worked if the operator<< hadn’t been created, and since it’s a template it is automatically instantiated for pair<int, int>.

The magic of maps

An ordinary array uses an integral value to index into a sequential set of elements of some type. A map is an associative array, which means you associate one object with another in an array-like fashion, but instead of selecting an array element with a number as you do with an ordinary array, you look it up with an object! The example which follows counts the words in a text file, so the index is the string object representing the word, and the value being looked up is the object that keeps count of the strings.

In a single-item container like a vector or list, there’s only one thing being held. But in a map, you’ve got two things: the key (what you look up by, as in mapname[key]) and the value that results from the lookup with the key. If you simply want to move through the entire map and list each key-value pair, you use an iterator, which when dereferenced produces a pair object containing both the key and the value. You access the members of a pair by selecting first or second.

This same philosophy of packaging two items together is also used to insert elements into the map, but the pair is created as part of the instantiated map and is called value_type, containing the key and the value. So one option for inserting a new element is to create a value_type object, loading it with the appropriate objects and then calling the insert( ) member function for the map. Instead, the following example makes use of the aforementioned special feature of map: if you’re trying to find an object by passing in a key to operator[ ] and that object doesn’t exist, operator[ ] will automatically insert a new keyvalue pair for you, using the default constructor for the value object. With that in mind, consider an implementation of a word counting program:

//: C04:WordCount.cpp //{L} StreamTokenizer

// Count occurrences of words using a map #include "StreamTokenizer.h"

#include "../require.h" #include <string> #include <map>

#include <iostream>

Chapter 15: Multiple Inheritance

239

#include <fstream> using namespace std;

class Count { int i;

public:

Count() : i(0) {}

void operator++(int) { i++; } // Post-increment int& val() { return i; }

};

typedef map<string, Count> WordMap; typedef WordMap::iterator WMIter;

int main(int argc, char* argv[]) { requireArgs(argc, 1);

ifstream in(argv[1]); assure(in, argv[1]); StreamTokenizer words(in); WordMap wordmap;

string word;

while((word = words.next()).size() != 0) wordmap[word]++;

for(WMIter w = wordmap.begin(); w != wordmap.end(); w++)

cout << (*w).first << ": "

<<(*w).second.val() << endl;

}///:~

The need for the Count class is to contain an int that’s automatically initialized to zero. This is necessary because of the crucial line:

wordmap[word]++;

This finds the word that has been produced by StreamTokenizer and increments the Count object associated with that word, which is fine as long as there is a key-value pair for that string. If there isn’t, the map automatically inserts a key for the word you’re looking up, and a Count object, which is initialized to zero by the default constructor. Thus, when it’s incremented the Count becomes 1.

Printing the entire list requires traversing it with an iterator (there’s no copy( ) shortcut for a map unless you want to write an operator<< for the pair in the map). As previously mentioned, dereferencing this iterator produces a pair object, with the first member the key and the second member the value. In this case second is a Count object, so its val( ) member must be called to produce the actual word count.

Chapter 15: Multiple Inheritance

240

If you want to find the count for a particular word, you can use the array index operator, like this:

cout << "the: " << wordmap["the"].val() << endl;

You can see that one of the great advantages of the map is the clarity of the syntax; an associative array makes intuitive sense to the reader (note, however, that if “the” isn’t already in the wordmap a new entry will be created!).

A command-line argument tool

A problem that often comes up in programming is the management of program arguments that you can specify on the command line. Usually you’d like to have a set of defaults that can be changed via the command line. The following tool expects the command line arguments to be in the form flag1=value1 with no spaces around the ‘=‘ (so it will be treated as a single argument). The ProgVal class simply inherits from map<string, string>:

//: C04:ProgVals.h

// Program values can be changed by command line #ifndef PROGVALS_H

#define PROGVALS_H #include <map> #include <iostream> #include <string>

class ProgVals

: public std::map<std::string, std::string> { public:

ProgVals(std::string defaults[][2], int sz); void parse(int argc, char* argv[],

std::string usage, int offset = 1);

void print(std::ostream& out = std::cout);

};

#endif // PROGVALS_H ///:~

The constructor expects an array of string pairs (as you’ll see, this allows you to initialize it with an array of char*) and the size of that array. The parse( ) member function is handed the command-line arguments along with a “usage” string to print if the command line is given incorrectly, and the “offset” which tells it which command-line argument to start with (so you can have non-flag arguments at the beginning of the command line). Finally, print( ) displays the values. Here is the implementation:

//: C04:ProgVals.cpp {O} #include "ProgVals.h" using namespace std;

ProgVals::ProgVals(

Chapter 15: Multiple Inheritance

241

std::string defaults[][2], int sz) { for(int i = 0; i < sz; i++)

insert(make_pair(

defaults[i][0], defaults[i][1]));

}

void ProgVals::parse(int argc, char* argv[], string usage, int offset) {

//Parse and apply additional

//command-line arguments:

for(int i = offset; i < argc; i++) { string flag(argv[i]);

int equal = flag.find('='); if(equal == string::npos) {

cerr << "Command line error: " << argv[i] << endl << usage << endl;

continue; // Next argument

}

string name = flag.substr(0, equal); string value = flag.substr(equal + 1); if(find(name) == end()) {

cerr << name << endl << usage << endl; continue; // Next argument

}

operator[](name) = value;

}

}

void ProgVals::print(ostream& out) { out << "Program values:" << endl;

for(iterator it = begin(); it != end(); it++) out << (*it).first << " = "

<< (*it).second << endl;

} ///:~

The constructor uses the STL make_pair( ) helper function to convert each pair of char* into a pair object that can be inserted into the map. In parse( ), each command-line argument is checked for the existence of the telltale ‘=‘ sign (reporting an error if it isn’t there), and then is broken into two strings, the name which appears before the ‘=‘, and the value which appears after. The operator[ ] is then used to change the existing value to the new one.

Here’s an example to test the tool:

//: C04:ProgValTest.cpp //{L} ProgVals

Chapter 15: Multiple Inheritance

242

#include "ProgVals.h" using namespace std;

string defaults[][2] = {

{"color", "red" },

{"size", "medium" },

{"shape", "rectangular" },

{"action", "hopping"},

};

const char* usage = "usage:\n"

"ProgValTest [flag1=val1 flag2=val2 ...]\n" "(Note no space around '=')\n"

"Where the flags can be any of: \n" "color, size, shape, action \n";

// So it can be used globally: ProgVals pvals(defaults,

sizeof defaults / sizeof *defaults);

class Animal {

string color, size, shape, action; public:

Animal(string col, string sz, string shp, string act)

:color(col),size(sz),shape(shp),action(act){}

//Default constructor uses program default

//values, possibly change on command line: Animal() : color(pvals["color"]),

size(pvals["size"]), shape(pvals["shape"]), action(pvals["action"]) {}

void print() {

cout << "color = " << color << endl

<<"size = " << size << endl

<<"shape = " << shape << endl

<<"action = " << action << endl;

}

//And of course pvals can be used anywhere

//else you'd like.

};

int main(int argc, char* argv[]) {

// Initialize and parse command line values

Chapter 15: Multiple Inheritance

243

Соседние файлы в предмете Программирование