Литмир - Электронная Библиотека
A
A

2. Каким-то образом разделить документ и использовать обычную пунктуацию (например,

.
).

3. Разделить строку, длина которой превышает некий порог (например, 50 символов), на две.

Кроме этого, несомненно, существуют менее очевидные варианты. Для простоты выберем первую альтернативу.

Представим документ в нашем редакторе в виде объекта класса

Document
. Схематически наш тип должен выглядеть примерно так:

typedef vector<char> Line;  // строка — это вектор символов

struct Document {

  list<Line> line;          // документ — список строк

  Document() { line.push_back(Line()); }

};

Каждый объект класса

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'
). Хранение символов перехода на новую строку сильно упрощает дело, но при подсчете символов следует быть осторожным (простой подсчет символов будет учитывать пробелы и символы перехода на новую строку).

20.6.2. Итерация

 Если бы документ хранился как объект класса

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

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

  list<Line>::iterator ln;

  Line::iterator pos;

public:

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

  Text_iterator(list<Line>::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> 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;

}

278
{"b":"847443","o":1}