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;
}