Выбрать главу

#include "FileEditor.h"

#include <fstream>

#include "../require.h"

using namespace std;

void FileEditor::open(const char* filename) {

  ifstream in(filename);

  assure(in, filename);

  string line;

  while(getline(in, line))

    push_back(line);

}

// Could also use copy() here:

void FileEditor::write(ostream& out) {

  for(iterator w = begin();  w != end(); w++)

    out << *w << endl;

} ///:~

The functions from StringVector.cpp are simply repackaged. Often this is the way classes evolve—you start by creating a program to solve a particular application and then discover some commonly used functionality within the program that can be turned into a class.

The line-numbering program can now be rewritten using FileEditor:.

//: C07:FEditTest.cpp

//{L} FileEditor

// Test the FileEditor tool

#include <sstream>

#include "FileEditor.h"

#include "../require.h"

using namespace std;

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

  FileEditor file;

  if(argc > 1) {

    file.open(argv[1]);

  } else {

    file.open("FEditTest.cpp");

  }

  // Do something to the lines...

  int i = 1;

  FileEditor::iterator w = file.begin();

  while(w != file.end()) {

    ostringstream ss;

    ss << i++;

    *w = ss.str() + ": " + *w;

    w++;

  }

  // Now send them to cout:

  file.write();

} ///:~

Now the operation of reading the file is in the constructor:.

FileEditor file(argv[1]);

(or in the open( ) member function), and writing happens in the single line (which defaults to sending the output to cout):

file.write();

The bulk of the program is involved with actually modifying the file in memory.

A plethora of iterators

As mentioned earlier in this and the previous chapter, an iterator is an abstraction that allows code to be generic, that is, to work with different types of containers without knowing the underlying structure of those containers. Most containers support iterators.[92] You can always say:.

<ContainerType>::iterator

<ContainerType>::const_iterator

to produce the types of the iterators produced by that container. Every container has a begin( ) member function that produces an iterator indicating the beginning of the elements in the container, and an end( ) member function that produces an iterator which is the as the past-the-end marker of the container. If the container is const¸ begin( ) and end( ) produce const iterators, which disallow changing the elements pointed to (because the appropriate operators are const).

All iterators can advance within their sequence (via operator++) and allow == and != comparisons. Thus, to move an iterator it forward without running it off the end, you say something like:.

while(it != pastEnd) {

  // Do something

  it++;

}

in which pastEnd is the past-the-end marker produced by the container’s end( ) member function.

An iterator can be used to produce the element that it is currently selecting within a container through the dereferencing operator (operator*). This can take two forms. If it is an iterator and f( ) is a member function of the objects held in the container that the iterator is pointing within, you can say either:.

(*it).f();

or.

it->f();

Knowing this, you can create a template that works with any container. Here, the apply( ) function template calls a member function for every object in the container, using a pointer to member that is passed as an argument:.

//: C07:Apply.cpp

// Using simple iteration

#include <iostream>

#include <vector>

#include <iterator>

using namespace std;

template<class Cont, class PtrMemFun>

void apply(Cont& c, PtrMemFun f) {

  typename Cont::iterator it = c.begin();

  while(it != c.end()) {

    ((*it).*f)(); // Alternate form

    it++;

  }

}

class Z {

  int i;

public:

  Z(int ii) : i(ii) {}

  void g() { i++; }

  friend ostream&

  operator<<(ostream& os, const Z& z) {

    return os << z.i;

  }

};

int main() {

  ostream_iterator<Z> out(cout, " ");

  vector<Z> vz;

  for(int i = 0; i < 10; i++)

    vz.push_back(Z(i));

  copy(vz.begin(), vz.end(), out);

  cout << endl;

  apply(vz, &Z::g);

  copy(vz.begin(), vz.end(), out);

} ///:~

You can’t use operator-> in this case, because the resulting statement would be

(it->*f)();

which attempts to use the iterator’s operator->*, which is not provided by the iterator classes.[93]

It is much easier to use either for_each( ) or transform( ) to apply functions to sequences anyway, as you saw in the previous chapter.

Iterators in reversible containers

A container may also be reversible, which means that it can produce iterators that move backward from the end, as well as the iterators that move forward from the beginning. All standard containers support such bidirectional iteration.

A reversible container has the member functions rbegin( ) (to produce a reverse_iterator selecting the end) and rend( ) (to produce a reverse_iterator indicating "one past the beginning"). If the container is const, rbegin( ) and rend( ) will produce const_reverse_iterators.

The following example uses vector, but will work with all containers that support iteration:

//: C07:Reversible.cpp

// Using reversible containers

#include <fstream>

#include <iostream>

#include <string>

#include <vector>

#include "../require.h"

using namespace std;

int main() {

вернуться

92

The container adaptors, stack, queue, and priority_queue do not support iterators, since they do not behave as sequences from the user’s point of view.

вернуться

93

It will only work for implementations of vector that uses a pointer (a T*) as the iterator type, like STLPort does.