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

A catalog of STL algorithms

This section provides a quick reference for when you’re searching for the appropriate algorithm. I leave the full exploration of all the STL algorithms to other references (see the end of this chapter, and Appendix XX), along with the more intimate details of complexity, performance, etc. My goal here is for you to become rapidly comfortable and facile with the algorithms, and I will assume you will look into the more specialized references if you need more depth of detail.

Although you will often see the algorithms described using their full template declaration syntax, I am not doing that here because you already know they are templates, and it’s quite easy to see what the template arguments are from the function declarations. The type names for the arguments provide descriptions for the types of iterators required. I think you’ll find this form is easier to read, while you can quickly find the full declaration in the template header file if for some reason you feel the need.

The names of the iterator classes describe the iterator type they must conform to. The iterator types were described in the previous chapter, but here is a summary:

InputIterator. You (or rather, the STL algorithm and any algorithms you write that use InputIterators) can increment this with operator++ and dereference it with operator* to read the value (and only read the value), but you can only read each value once. InputIterators can be tested with operator== and operator!=. That’s all. Because an InputIterator is so limited, it can be used with istreams (via istream_iterator).

OutputIterator. This can be incremented with operator++, and dereferenced with operator* to write the value (and only write the value), but you can only dereference/write each value once. OutputIterators cannot be tested with operator== and operator!=, however, because you assume that you can just keep sending elements to the destination and that you don’t have to see if the destination’s end marker has been reached. That is, the container that an OutputIterator references can take an infinite number of objects, so no end-checking is necessary. This requirement is important so that an OutputIterator can be used with ostreams (via ostream_iterator), but you’ll also commonly use the “insert” iterators insert_iterator, front_insert_iterator and back_insert_iterator (generated by the helper templates inserter( ), front_inserter( ) and back_inserter( )).

With both InputIterator and OutputIterator, you cannot have multiple iterators pointing to different parts of the same range. Just think in terms of iterators to support istreams and ostreams, and InputIterator and OutputIterator will make perfect sense. Also note that InputIterator and OutputIterator put the weakest restrictions on the types of iterators they will accept, which means that you can use any “more sophisticated” type of iterator when you see InputIterator or OutputIterator used as STL algorithm template arguments.

Chapter 15: Multiple Inheritance

285

ForwardIterator. InputIterator and OutputIterator are the most restricted, which means they’ll work with the largest number of actual iterators. However, there are some operations for which they are too restricted; you can only read from an InputIterator and write to an OutputIterator, so you can’t use them to read and modify a range, for example, and you can’t have more than one active iterator on a particular range, or dereference such an iterator more than once. With a ForwardIterator these restrictions are relaxed; you can still only move forward using operator++, but you can both write and read and you can write/read multiple times in each location. A ForwardIterator is much more like a regular pointer, whereas InputIterator and OutputIterator are a bit strange by comparison.

BidirectionalIterator. Effectively, this is a ForwardIterator that can also go backward. That is, a BidirectionalIterator supports all the operations that a ForwardIterator does, but in addition it has an operator--.

RandomAccessIterator. An iterator that is random access supports all the same operations that a regular pointer does: you can add and subtract integral values to move it forward and backward by jumps (rather than just one element at a time), you can subscript it with operator[ ], you can subtract one iterator from another, and iterators can be compared to see which is greater using operator<, operator>, etc. If you’re implementing a sorting routine or something similar, random access iterators are necessary to be able to create an efficient algorithm.

The names used for the template parameter types consist of the above iterator types (sometimes with a ‘1’ or ‘2’ appended to distinguish different template arguments), and may also include other arguments, often function objects.

When describing the group of elements that an operation is performed on, mathematical “range” notation will often be used. In this, the square bracket means “includes the end point” while the parenthesis means “does not include the end point.” When using iterators, a range is determined by the iterator pointing to the initial element, and the “past-the-end” iterator, pointing past the last element. Since the past-the-end element is never used, the range determined by a pair of iterators can thus be expressed as [first, last), where first is the iterator pointing to the initial element and last is the past-the-end iterator.

Most books and discussions of the STL algorithms arrange them according to side effects: non-mutating algorithms don’t change the elements in the range, mutating algorithms do change the elements, etc. These descriptions are based more on the underlying behavior or implementation of the algorithm – that is, the designer’s perspective. In practice, I don’t find this a very useful categorization so I shall instead organize them according to the problem you want to solve: are you searching for an element or set of elements, performing an operation on each element, counting elements, replacing elements, etc. This should help you find the one you want more easily.

Note that all the algorithms are in the namespace std. If you do not see a different header such as <utility> or <numerics> above the function declarations, that means it appears in

<algorithm>.

Chapter 15: Multiple Inheritance

286

Support tools for example creation

It’s useful to create some basic tools with which to test the algorithms.

Displaying a range is something that will be done constantly, so here is a templatized function that allows you to print any sequence, regardless of the type that’s in that sequence:

//: C05:PrintSequence.h

// Prints the contents of any sequence #ifndef PRINTSEQUENCE_H

#define PRINTSEQUENCE_H #include <iostream>

template<typename InputIter>

void print(InputIter first, InputIter last, char* nm = "", char* sep = "\n", std::ostream& os = std::cout) {

if(*nm != '\0') // Only if you provide a string os << nm << ": " << sep; // is this printed

while(first != last)

os << *first++ << sep; os << std::endl;

}

//Use template-templates to allow type deduction

//of the typename T:

template<typename T, template<typename> class C> void print(C<T>& c, char* nm = "",

char* sep = "\n",

std::ostream& os = std::cout) {

if(*nm != '\0') // Only if you provide a string os << nm << ": " << sep; // is this printed

std::copy(c.begin(), c.end(), std::ostream_iterator<T>(os, " "));

cout << endl;

}

#endif // PRINTSEQUENCE_H ///:~

There are two forms here, one that requires you to give an explicit range (this allows you to print an array or a sub-sequence) and one that prints any of the STL containers, which provides notational convenience when printing the entire contents of that container. The second form performs template type deduction to determine the type of T so it can be used in the copy( ) algorithm. That trick wouldn’t work with the first form, so the copy( ) algorithm is avoided and the copying is just done by hand (this could have been done with the second form

Chapter 15: Multiple Inheritance

287

as well, but it’s instructive to see a template-template in use). Because of this, you never need to specify the type that you’re printing when you call either template function.

The default is to print to cout with newlines as separators, but you can change that. You may also provide a message to print at the head of the output.

Next, it’s useful to have some generators (classes with an operator( ) that returns values of the appropriate type) which allow a sequence to be rapidly filled with different values.

//: C05:Generators.h

//Different ways to fill sequences #ifndef GENERATORS_H

#define GENERATORS_H #include <set> #include <cstdlib> #include <cstring> #include <ctime>

//A generator that can skip over numbers: class SkipGen {

int i; int skp;

public:

SkipGen(int start = 0, int skip = 1)

:i(start), skp(skip) {}

int operator()() { int r = i;

i += skp; return r;

}

};

// Generate unique random numbers from 0 to mod: class URandGen {

std::set<int> used; int modulus;

public:

URandGen(int mod) : modulus(mod) { std::srand(std::time(0));

}

int operator()() { while(true) {

int i = (int)std::rand() % modulus; if(used.find(i) == used.end()) {

used.insert(i);

Chapter 15: Multiple Inheritance

288

return i;

}

}

}

};

//Produces random characters: class CharGen {

static const char* source; static const int len;

public:

CharGen() { std::srand(std::time(0)); } char operator()() {

return source[std::rand() % len];

}

};

//Statics created here for convenience, but

//will cause problems if multiply included: const char* CharGen::source = "ABCDEFGHIJK"

"LMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; const int CharGen::len = std::strlen(source); #endif // GENERATORS_H ///:~

To create some interesting values, the SkipGen generator skips by the value skp each time its operator( ) is called. You can initialize both the start value and the skip value in the constructor.

URandGen (‘U’ for “unique”) is a generator for random ints between 0 and mod, with the additional constraint that each value can only be produced once (thus you must be careful not to use up all the values). This is easily accomplished with a set.

CharGen generates chars and can be used to fill up a string (when treating a string as a sequence container). You’ll note that the one member function that any generator implements is operator( ) (with no arguments). This is what is called by the “generate” functions.

The use of the generators and the print( ) functions is shown in the following section.

Finally, a number of the STL algorithms that move elements of a sequence around distinguish between “stable” and “unstable” reordering of a sequence. This refers to preserving the original order of the elements for those elements that are equivalent but not identical. For example, consider a sequence { c(1), b(1), c(2), a(1), b(2), a(2) }. These elements are tested for equivalence based on their letters, but their numbers indicate how they first appeared in the sequence. If you sort (for example) this sequence using an unstable sort, there’s no guarantee of any particular order among equivalent letters, so you could end up with { a(2),

Chapter 15: Multiple Inheritance

289

a(1), b(1), b(2), c(2), c(1) }. However, if you used a stable sort, it guarantees you will get { a(1), a(2), b(1), b(2), c(1), c(2) }.

To demonstrate the stability versus instability of algorithms that reorder a sequence, we need some way to keep track of how the elements originally appeared. The following is a kind of string object that keeps track of the order in which that particular object originally appeared, using a static map that maps NStrings to Counters. Each NString then contains an occurrence field that indicates the order in which this NString was discovered:

//: C05:NString.h

//A "numbered string" that indicates which

//occurrence this is of a particular word #ifndef NSTRING_H

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

class NString { std::string s; int occurrence; struct Counter {

int i;

Counter() : i(0) {} Counter& operator++(int) {

i++;

return *this; } // Post-incr

operator int() { return i; }

};

// Keep track of the number of occurrences: typedef std::map<std::string, Counter> csmap; static csmap occurMap;

public:

NString() : occurrence(0) {} NString(const std::string& x)

:s(x), occurrence(occurMap[s]++) {} NString(const char* x)

:s(x), occurrence(occurMap[s]++) {}

//The synthesized operator= and

//copy-constructor are OK here

friend std::ostream& operator<<( std::ostream& os, const NString& ns) { return os << ns.s << " ["

Chapter 15: Multiple Inheritance

290

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