Каждый объект класса 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
, перемещаться по нему было бы просто. Как перемещать итератор по списку строк? Очевидно, что перемещаться по списку можно с помощью класса list
. Однако, что, если мы хотим пройтись по символам один за другим, не беспокоясь о разбиении строки? Мы могли бы использовать итератор, специально разработанный для нашего класса Document
.
class Text_iterator { // отслеживает позицию символа в строке
list
Line::iterator pos;
public:
// устанавливает итератор на позицию pp в ll-й строке
Text_iterator(list
: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
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; // игнорируем строки,
// находящиеся
// за пределами диапазона