Читаем Программирование полностью

Каждый объект класса Document начинается с пустой строки: конструктор класса Document сначала создает пустую строку, а затем заполняет список строка за строкой.

Чтение и разделение на строки можно выполнить следующим образом:

istream& operator>>(istream& is, Document& d)

{

  char ch;

  while (is.get(ch)) {

    d.line.back().push_back(ch); // добавляем символ

    if (ch=='\n')

      d.line.push_back(Line());  // добавляем новую строку

  }

  if (d.line.back().size())

    d.line.push_back(Line());    // добавляем пустую строку

  return is;

}

Классы vector и list имеют функцию-член back(), возвращающую ссылку на последний элемент. Для ее использования вы должны быть уверены, что она действительно ссылается на последний элемент, — функцию back() нельзя применять к пустому контейнеру. Вот почему в соответствии с определением каждый объект класса Document должен содержать пустой объект класса Line. Обратите внимание на то, что мы храним каждый введенный символ, даже символы перехода на новую строку ('\n'). Хранение символов перехода на новую строку сильно упрощает дело, но при подсчете символов следует быть осторожным (простой подсчет символов будет учитывать пробелы и символы перехода на новую строку).

<p id="AutBody_Root382"><strong>20.6.2. Итерация</strong></p>

 Если бы документ хранился как объект класса vector, перемещаться по нему было бы просто. Как перемещать итератор по списку строк? Очевидно, что перемещаться по списку можно с помощью класса list::iterator. Однако, что, если мы хотим пройтись по символам один за другим, не беспокоясь о разбиении строки? Мы могли бы использовать итератор, специально разработанный для нашего класса Document.

class Text_iterator { // отслеживает позицию символа в строке

  list::iterator ln;

  Line::iterator pos;

public:

  // устанавливает итератор на позицию pp в ll-й строке

  Text_iterator(list::iterator ll, Line::iterator pp)

  :ln(ll), pos(pp) { }

  char& operator*() { return *pos; }

  Text_iterator& operator++();

  bool operator==(const Text_iterator& other) const

    { return ln==other.ln && pos==other.pos; }

  bool operator!=(const Text_iterator& other) const

    { return !(*this==other); }

};

Text_iterator& Text_iterator::operator++()

{

  if (pos==(*ln).end()) {

    ++ln; // переход на новую строку

    pos = (*ln).begin();

  }

  ++pos; // переход на новый символ

  return *this;

}

Для того чтобы класс Text_iterator стал полезным, необходимо снабдить класс Document традиционными функциями begin() и end().

struct Document {

  list line;

  Text_iterator begin() // первый символ первой строки

    { return Text_iterator(line.begin(),

    (*line.begin()).begin()); }

  Text_iterator end()   // за последним символом последней строки

    { return(line.end(), (*line.end()).end));}

};

Мы использовали любопытную конструкцию (*line.begin()).begin(), потому что хотим начинать перемещение итератора с позиции, на которую ссылается итератор line.begin(); в качестве альтернативы можно было бы использовать функцию line.begin()–>begin(), так как стандартные итераторы поддерживают операцию –>.

Теперь можем перемещаться по символам документа.

void print(Document& d)

{

  for (Text_iterator p = d.begin();

  p!=d.end(); ++p) cout << *p;

}

print(my_doc);

Представление документа в виде последовательности символов полезно по многим причинам, но обычно мы перемещаемся по документам, просматривая более специфичную информацию, чем символ. Например, рассмотрим фрагмент кода, удаляющий строку n.

void erase_line(Document& d, int n)

{

  if (n<0 || d.line.size()<=n) return; // игнорируем строки,

                                       // находящиеся

                                       // за пределами диапазона

Перейти на страницу:
Нет соединения с сервером, попробуйте зайти чуть позже