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

The following example shows stack<string> implemented in the three ways: the default (which uses deque), with a vector, and with a list:

//: C07:Stack1.cpp

// Demonstrates the STL stack

#include <fstream>

#include <iostream>

#include <list>

#include <stack>

#include <string>

#include <vector>

using namespace std;

// Rearrange comments below to use different versions.

typedef stack<string> Stack1; // Default: deque<string>

// typedef stack<string, vector<string> > Stack2;

// typedef stack<string, list<string> > Stack3;

int main() {

  ifstream in("Stack1.cpp");

  Stack1 textlines; // Try the different versions

  // Read file and store lines in the stack:

  string line;

  while(getline(in, line))

    textlines.push(line + "\n");

  // Print lines from the stack and pop them:

  while(!textlines.empty()) {

    cout << textlines.top();

    textlines.pop();

  }

} ///:~

The top( ) and pop( ) operations will probably seem non-intuitive if you’ve used other stack classes. When you call pop( ), it returns void rather than the top element that you might have expected. If you want the top element, you get a reference to it with top( ). It turns out this is more efficient, since a traditional pop( ) would have to return a value rather than a reference and thus invoke the copy-constructor. More important, it is exception safe, as we discussed in Chapter 1. If pop( ) both changed the state of the stack and attempted to return the top element, an exception in the element’s copy-constructor could cause the element to be lost. When you’re using a stack (or a priority_queue, described later), you can efficiently refer to top( ) as many times as you want and then discard the top element explicitly using pop( ). (Perhaps if some term other than the familiar "pop" had been used, this would have been a bit clearer.)

The stack template has a simple interface—essentially the member functions you saw earlier. Since it only makes sense to access a stack at its top, no iterators are available for traversing it. Nor are there sophisticated forms of initialization, but if you need that, you can use the underlying container upon which the stack is implemented. For example, suppose you have a function that expects a stack interface, but in the rest of your program you need the objects stored in a list. The following program stores each line of a file along with the leading number of spaces in that line. (You might imagine it as a starting point for performing some kind of source-code reformatting.)

//: C07:Stack2.cpp

// Converting a list to a stack

#include <iostream>

#include <fstream>

#include <stack>

#include <list>

#include <string>

using namespace std;

// Expects a stack:

template<class Stk>

void stackOut(Stk& s, ostream& os = cout) {

  while(!s.empty()) {

    os << s.top() << "\n";

    s.pop();

  }

}

class Line {

  string line; // Without leading spaces

  int lspaces; // Number of leading spaces

public:

  Line(string s) : line(s) {

    lspaces = line.find_first_not_of(' ');

    if(lspaces == string::npos)

      lspaces = 0;

    line = line.substr(lspaces);

  }

  friend ostream&

  operator<<(ostream& os, const Line& l) {

    for(int i = 0; i < l.lspaces; i++)

      os << ' ';

    return os << l.line;

  }

  // Other functions here...

};

int main() {

  ifstream in("Stack2.cpp");

  list<Line> lines;

  // Read file and store lines in the list:

  string s;

  while(getline(in, s))

    lines.push_front(s);

  // Turn the list into a stack for printing:

  stack<Line, list<Line> > stk(lines);

  stackOut(stk);

} ///:~

The function that requires the stack interface just sends each top( ) object to an ostream and then removes it by calling pop( ). The Line class determines the number of leading spaces and then stores the contents of the line without the leading spaces. The ostream operator<< re-inserts the leading spaces so the line prints properly, but you can easily change the number of spaces by changing the value of lspaces. (The member functions to do this are not shown here.)

In main( ), the input file is read into a list<Line>, and then each line in the list is copied into a stack that is sent to stackOut( ).

You cannot iterate through a stack; this emphasizes that you only want to perform stack operations when you create a stack. You can get equivalent "stack" functionality using a vector and its back( ), push_back( ), and pop_back( ) member functions, and then you have all the additional functionality of the vector. Stack1.cpp can be rewritten to show this:

//: C07:Stack3.cpp

// Using a vector as a stack; modified Stack1.cpp

#include <fstream>

#include <iostream>

#include <string>

#include <vector>

using namespace std;

int main() {

  ifstream in("Stack3.cpp");

  vector<string> textlines;

  string line;

  while(getline(in, line))

    textlines.push_back(line + "\n");

  while(!textlines.empty()) {

    cout << textlines.back();

    textlines.pop_back();

  }

} ///:~

This produces the same output as Stack1.cpp, but you can now perform vector operations as well. Of course, list can also push things at the front, but it’s generally less efficient than using push_back( ) with vector. (In addition, deque is usually more efficient than list for pushing things at the front.)