Работа с потоками в С++
9.1. Структура стандартной библиотеки ввода-вывода.
В языке программирования С++ стандартная библиотека ввода-вывода организована как иерархия шаблонов классов. Эта библиотека содержит два стандартных множества конкретизированных шаблонов этой иерархии: одно для работы с символами типа char, другое для работы с символами типа wchar_t. Классы для работы с символами типа wchar_t имеют те же имена, что и классы для работы с символами типа char, но с префиксом w.
Ниже перечислены заголовочные файлы, которые содержат интерфейсы классов из стандартной библиотеки ввода-вывода:
<ios> | ios_base, ios |
<istream> | istream |
<ostream> | ostream |
<iostream> | iostream, cin, cout, cerr |
<fstream> | ifstream, fstream, ofstream, filebuf |
<sstream> | istringstream, stringstream, ostringstream, stringbuf |
<streambuf> | streambuf |
9.2. Классы ios_base и ios.
В классах ios_base и ios определены методы, которые являются общими для входных и выходных потоков. Класс ios_base содержит методы, которые не зависят от параметров шаблона. Наоборот, класс ios содержит шаблонно-зависимые методы. Объект класса ios_base не может быть создан непосредственно, а только в наследуемых классах.
Объекты типа ios_base поддерживают следующую информацию о состоянии потока.
Информацию о форматировании ввода-вывода:
- флаги форматирования,
- длину полей ввода-вывода,
- разрешимость дисплея.
Информацию о состоянии потока:
- состояние ошибки,
- маску исключений,
Другая информация:
- стек вызовов callback функций при наступлении некоторых событий,
- внутренний массив с элементами типа long,
- внутренний массив с элементами типа void*.
Перечислим некоторые методы класса ios_base:
flags чтение-установка флагов форматирования,
precision чтение-установка точности double,
setf установка некоторых флагов форматирования,
unsetf сброс флагов форматирования.
Например, в следующей программе показано как установить в качестве базовой 16 с/с.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <iostream> using namespace std; int main () { // установить 16c/c в качестве базовой cout.setf ( ios_base::hex, ios_base::basefield ); // показывать базу cout.setf ( ios_base::showbase ); cout << 100 << endl; // не показывать базу cout.setf ( 0, ios_base::showbase ); cout << 100 << endl; return 1; } |
Класс ios поддерживает следующую информацию о потоке:
- символ заполнитель,
- указатель на поток вывода, связанный с объектом типа ios,
- указатель на объект типа streambuf, связанный с объектом типа ios.
Перечислим некоторые методы класса ios:
operator! возвращает true, если установлен любой из флагов failbit или badbit, иначе возвращает false,
bad возвращает true в случае неустранимой ошибки ввода-вывода,
clear сбрасывает управляющие состояния,
eof проверяет на конец файла,
exception читает-устанавливает маску исключений,
fail проверяет на ошибку, исключая конец файла,
fill читает-устанавливает символ заполнитель,
good возвращает true, если нет ошибок,
rdstate читает управляющие состояния,
setstate устанавливает управляющие состояния.
Кроме того, класс ios поддерживает режимы работы потока, которые задаются следующими флагами:
ios::app данные всегда записываются в конец файла,
ios::ate первый байт записывается в конец файла, остальные байты с текущей позиции,
ios::binary бинарный файл,
ios::in входной файл,
ios::nocreate открыть существующий файл, если файла нет, то ошибка,
ios::noreplace открыть новый файл, если файл уже есть, то ошибка,
ios::out выходной файл,
ios::trunc файл открывается и его содержимое стирается,
Например, в следующей программе показано, как проверить, успешно ли открыт файл.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <iostream> #include <fstream> using namespace std; int main () { ifstream in; in.open ("test.txt"); if (!in) cerr << "Error: open file ‘test.txt’ failed." << endl; return 0; } |
9.3. Потоки вывода.
Потоки вывода создаются на базе класса ostream, который обеспечивает методы для записи данных в буфер потока. Перечислим основные методы класса ostream:
operator<< форматированный вывод данных в поток,
flush очищает буфер,
put выводит символ,
seekp устанавливает позицию для функции put,
tellp читает позицию указателя для функции put,
write пишет последовательность байтов,
От класса ostream наследуются класс ofstream, который содержит дополнительные методы:
open открыть файл,
close закрыть файл,
is_open проверка открыт ли файл.
Сейчас приведем прототипы этих функций и примеры их использования.
1. Функции open, close и is_open имеют следующие прототипы:
void open ( const char * filename, openmode mode = out | trunc );
void close ( );
bool is_open ( );
При открытии потока вывода нужно указать имя файла и режимы работы потока с этим файлом. При этом нужно запомнить, что режимы работы с потоками ввода и вывода совпадают.
Например, следующая программа добавляет предложение в конец текстового файла и показывает, как использовать вышеперечисленные функции.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream.h> #include <fstream> using namespace std; int main () { ofstream out; out.open ("test.txt", ofstream::out | ofstream::app); if (out.is_open()) out << "This sentence is appended to the file content.\n"; else cerr << "Open file failed." << endl; out.close(); return 0; } |
2. Функцияflush очищает буфер. Эта функция имеет следующий прототип:
ostream& flush ( );
Ниже приведен пример использования функции flush. Этот пример также показывает, как можно создавать текстовый файл
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <fstream.h> int main () { ofstream out("test.txt"); for (int n=0; n<100; n++) { out << n << ' '; out.flush(); } out << '\n'; // переход на новую строку for (n=0; n<100; n++) out << (n+0.1) << ' ' << flush; out << endl; // переход на новую строку out.close(); return 0; } |
3. Функция put выводит символ в файл. Эта функция имеет следующий прототип:
ostream& put ( char ch );
В следующем примере показано, как водить символы с консоли и выводить их в файл.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <iostream.h> #include <fstream.h> int main () { char ch; ofstream out("test.txt"); cout << "Input chars then input '.' to exit." << endl; do { ch = cin.get(); out.put(ch); } while (ch!='.'); return 0; } |
4. Функция seekp устанавливает, а функция tellp читает позицию в файле для функции put. Эти функции имеют следующие прототипы:
streampos tellp ( );
ostream& seekp ( streampos pos );
где pos - новая позиция в потоке;
ostream& seekp ( streamoff off, ios_base::seekdir dir );
где параметры имеют следующее назначение:
off смещение в потоке относительно позиции, указанной в параметре dir.
dir направление поиска позиции, может принимать одно из следующих значений:
- ios_base::beg – поиск от начала файла;
- ios_base::cur - поиск от текущей позиции;
- ios_base::end – поиск от конца файла.
Ниже приведен пример использования этих функций.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <fstream.h> int main () { long pos; ofstream out; out.open ("test.txt"); out << "This is a string." << endl; pos=out.tellp(); out.seekp (pos - 11); out << "an example."; out.close(); return 0; } |
5. Функция write пишет последовательность символов (байтов) в файл. Эта функция имеет следующий прототип:
ostream& write ( const char* s , size n );
где s указывает на область памяти, из которой данные записываются в файл, а n – количество записываемых символов (байтов). Отметим, что эта функция используется для записи данных в бинарные файлы, то есть содержимое области памяти записывается в файл без изменения. Ниже приведен пример создания бинарного файла, используя функцию write.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
#include <iostream.h> #include <fstream.h> struct emp { int code; char name[20]; double salary; }; int main() { ofstream out; // выходной поток struct emp s; // для записей файла // открываем выходной поток в бинарном режиме out.open("demo.bin", ofstream::binary); if(!out) { cerr << "Open file failed." << endl; return 1; } cout << "Input code, name and salary." << endl; cout << "Press Ctrl+z to exit." << endl; cout << '>'; // вводим первую запись с консоли cin >> s.code >> s.name >> s.salary; while (!cin.eof()) { // пишем запись в файл out.write((const char*)&s, sizeof(struct emp)); cout << '>'; // вводим следующие записи с консоли cin >> s.code >> s.name >> s.salary; } // закрываем выходной поток out.close(); return 0; } |
От класса ostream также наследуются класс ostringstream, который обеспечивает интерфейс для работы со строками.
Класс ostringstream содержит дополнительный метод:
str – чтение и запись строки в поток.
6. Функция str выполняет запись и чтение строки в поток. Эта функция имеет следующий прототип:
string str ( ) const чтение строки из потока
void str ( string & s ); запись строки s в поток
Например, в следующей программе показано, как ввести строку в поток типа ostringstream, а затем прочитать его содержимое в объект типа string.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include <iostream> #include <sstream> #include <string> using namespace std; int main () { ostringstream oss; string s; oss.str("Sample string_1."); s = oss.str(); cout << s << endl; oss << "Sample string_2."; s = oss.str(); cout << s << endl; return 0; } |
1.4. Потоки ввода.
Потоки ввода являются объектами класса istream, который обеспечивает методы для чтения информации из буфера потока. Перечислим основные методы класса istream:
operator >> форматированный ввод,
gcount возвращает количество символов, прочитанных последней операцией ввода,
get ввод символа,
getline ввод строки,
ignore удаление символов из буфера,
peek читает символ, но не удаляет его из буфера,
putback возвращает символ в буфер,
read читает блок данных,
seekg устанавливает указатель позиции файла для метода get,
sync синхронизирует буфер потока с внешним устройством,
tellg получает указатель позиции файла для метода get,
unget возвращает символ в поток,
От класса istream наследуется класс ifstream, который обеспечивает интерфейс для работы с файлами. Класс ifstream содержит дополнительные методы:
open открыть файл,
close закрыть файл,
is_open проверить, открыт ли файл.
Как работать с этими функциями, было показано в предыдущем параграфе. Сейчас же подробно рассмотрим работу с функциями класса istream.
1. Функция gcount возвращает количество символов, прочитанных последней не форматирующей операцией ввода. Эта функция имеет следующий прототип:
streamsize gcount ( ) const;
К не форматирующим операциям ввода относятся следующие операции: get, getline, ignore, read и readsome. Пример использования функции gcount показан в следующей программе:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <iostream> #include <string> using namespace std; int main () { char line[80]; int length; cout << "Input a string: " << endl; cin.getline(line, 80); length = cin.gcount(); cout << "String length = " << length-1 << endl; return 0; } |
2. Функция get вводит символ из входного потока. Эта функция имеет следующий прототип:
int get();
возвращает символ, извлеченный из потока;
istream& get (char& c );
извлекает символ из потока и сохраняет его в переменной с;
istream& get (char* s, streamsize n, char delim = ‘\n’ );
вводит из потока символы и записывает их в массив s, ввод символов прекращается в следующих случаях:
- если введен (n-1) символ;
- если в потоке встретился символ разделитель delim, сам символ разделитель из потока не извлекается;
- файл закончился раньше, чем прочитан (n-1) символ.
После записи прочитанных символов в массив s функция get записывает в этот массив пустой символ NULL, отмечающий конец строки. Ниже приведена программа, которая распечатывает содержимое текстового файла.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <iostream> #include <fstream> using namespace std; int main () { ifstream in; in.open ("test.txt", ifstream::in); while (in.good()) cout << (char) in.get(); cout << endl; in.close(); return 0; } |
А теперь приведена программа, которая читает символы из входного потока, используя функцию get.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <iostream.h> int main () { char c; char a[3]; cout << "Input a char: "; c = cin.get(); cout << c << endl; cin.get(); // удаляем символ конца строки cout << "Input a char: "; cin.get(c); cout << c << endl; cin.get(); // удаляем символ конца строки cout << "Input a string: "; cin.get(a, 3); cout << endl << "The first two symbols: "; cout << a << endl << endl; return 0; } |
3. Функция getline вводит строки из файла. Эта функция имеет следующий прототип:
istream& getline (char* s, streamsize n, char delim = ‘\n’);
вводит из потока символы и записывает их в массив s, ввод символов прекращается в следующих случаях:
- если введен (n-1) символ;
- если в потоке встретился символ разделитель delim, причем сам символ разделитель извлекается из потока, но не записывается в массив s;
- файл закончился раньше, чем прочитан (n-1) символ.
После записи прочитанных символов в массив s функция getline записывает в этот массив пустой символ NULL, отмечающий конец строки. Ниже приведена программа, которая вводит строки из потока и распечатывает их на консоли.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <iostream.h> int main () { char line[80]; cout << "Input strings. " << endl; cout << "To exit press CTRL+Z." << endl; cout << '>'; while (!cin.eof()) { cin.getline(line, 80); cout << ' ' << line << endl; cout << '>'; } return 0; } |
4. Функция ignore удаляет символы из буфера потока. Эта функция имеет следующий прототип:
istream& ignore ( streamsize n = 1, int delim = EOF );
где n – количество символов, которые необходимо удалить из потока, а delim – это символ ограничитель. Функция удаляет символы из потока до тех пор, пока не удалит n символов или не встретит символ разделитель. Например, в следующей программе выводятся только первые символы введенных слов.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <iostream> using namespace std; int main () { char c; cout << "Input a word: "; c = cin.get(); cin.ignore(256,'\n'); cout << "The first letter is: " << c << endl; cout << "Input another word: "; cin.ignore(); c = cin.get(); cin.ignore(256,'\n'); cout << "The second letter is: " << c << endl; return 0; } |
5. Функция peek читает символ, но не удаляет его из буфера. Эта функция имеет следующий прототип:
int peek ( ); возвращает прочитанный символ, а если файл закончился или в потоке произошла ошибка, то возвращает EOF.
Ниже приведена программа, в которой используется функция peek.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include <iostream> using namespace std; int main () { char c; int n; char s[256]; cout << "Enter a number or a word: "; c = cin.peek(); if ( (c >= '0') && (c <= '9') ) { cin >> n; cout << "It is a number: " << n << endl; } else { cin >> s; cout << "It is a word: " << s << endl; } return 0; } |
6. Функция putback возвращает символ в буфер. Эта функция имеет следующий прототип:
istream& putback (char ch ); если введенный символ совпадает с символом ch, то функция возвращает его в буфер ввода, иначе поведение функции непредсказуемо.
Использование функции putback показано в следующей программе.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include <iostream> using namespace std; int main () { char c; int n; char s[256]; cout << "Enter a number or a word: "; c = cin.get(); cin.putback (c); if ( (c >= '0') && (c <= '9') ) { cin >> n; cout << "It is a number: " << n << endl; } else { cin >> s; cout << " It is a word: " << s << endl; } return 0; } |
7. Функция read читает блок данных из файла и имеет следующий прототип:
istream& read (char* s, streamsize n );
где s указывает на область памяти, в которую данные читаются из файла, а n – количество читаемых символов (байтов). Отметим, что эта функция используется для чтения данных из бинарных файлов, то есть содержимое файла без изменения читается в область памяти. Ниже приведен пример чтения записей бинарного файла, используя функцию read.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
// чтение бинарного файла #include <iostream.h> #include <fstream.h> struct emp { int code; char name[20]; double salary; }; int main() { ifstream in; // выходной поток struct emp s; // для записей файла unsigned i; // номер записи // открываем входной поток в бинарном режиме in.open("demo.bin"); if(!in) { cout << "Open file failed.\n"; return 1; } cout << "Press Ctrl+z to exit.\n"; // читаем индекс cout << "Input an index: "; cin >> i; while (!cin.eof()) { // устанавливает указатель на нужную запись in.seekg(i*sizeof(struct emp)); // читаем запись из файла in.read((char*)&s, sizeof(struct emp)); if (!in.good()) { cout << "The wrong index.\n"; cout << "Input an index: "; cin >> i; in.clear(); // очищаем ошибку continue; } // выводим запись на консоль cout << "\tcode = " << s.code << " name = " << s.name << " sal = " << s.salary << endl, // читаем индекс cout << "Input an index: "; cin >> i; } // закрываем входной поток in.close(); return 0; } |
8. Функция seekg устанавливает указатель позиции файла для метода get, а функция tellg получает указатель позиции файла для метода get. Эти функции имеют следующие прототипы:
istream& seekg ( streampos pos ); устанавливает позицию файла на значение pos, тип streampos эквивалентен типу long.
istream& seekg ( streamoff off, ios_base::seekdir dir ); сдвигает позицию файла на значение off относительно значения параметра dir, который может принимать следующие значения:
- ios_base::beg смещение относительно начала файла;
- ios_base::cur смещение относительно текущей позиции;
- ios_base::end смещение относительно конца файла.
streampos tellg ( ); возвращает указатель позиции файла для функции get, тип streampos эквивалентен типу long.
Ниже приведена программа, которая демонстрирует использование вышеуказанных функций.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
// чтение файла в память #include <iostream.h> #include <fstream.h> struct emp { int code; char name[20]; double salary; }; int main () { int length; emp* buffer; ifstream in; in.open("demo.bin", ios::binary); if (!in) { cout << "Open file failed" << endl; return 1; } // определеям длину файла in.seekg(0, ios::end); length = in.tellg(); in.seekg(0, ios::beg); // распределяем память под файл buffer = (emp*) new char[length]; // читаем файл in.read((char*)buffer,length); in.close(); // выводим содержимое на консоль for (int i=0; i<length/(int)sizeof(emp); ++i) cout << buffer[i].code << ' ' << buffer[i].name << ' ' << buffer[i].salary << endl; return 0; } |
9. Функция sync синхронизирует буфер потока с внешним устройством. Эта функция имеет следующий прототип:
int sync ( );
Как работает эта функция, и что такое синхронизация буфера потока с внешним устройством смотри в параграфе 1.6.
10. Функция unget возвращает в поток символ, предварительно извлеченный из потока. Эта функция имеет следующий прототип:
int unget(); возвращает символ, извлеченный из потока.
Ниже приведена программа, в которой показано использование функции unget.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <iostream> using namespace std; int main () { char c; int n; char s[256]; cout << "Enter a number or a word: "; c = cin.get(); cin.unget(); if ( (c >= '0') && (c <= '9') ) { cin >> n; cout << "It is a number: " << n << endl; } else { cin >> s; cout << "It is a word: " << s << endl; } return 0; } |
От класса istream наследуется класс istringstream, который обеспечивает интерфейс для работы со строками. Класс istringstream содержит дополнительный метод:
str чтение или запись строки в поток.
11. Функция str для записи и чтения строки в поток.
Прототипы этой функции были рассмотрены в предыдущем параграфе пункт 6. Сейчас же приведем только программу, в которой показано, как записать строку в поток типа istringstream, а затем вывести содержимое этого потока.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include <iostream> #include <sstream> #include <string> using namespace std; int main () { int n; istringstream iss; string s = "0 1 2"; iss.str(s); for (int i = 0; i < 3; ++i) { iss >> n; cout << n + 1 << endl; } return 0; } |
1.5. Потоки ввода-вывода.
Потоки ввода-вывода создаются на базе класса iostream, который является производным классом от классов istream и ostream и поэтому наследует все их методы. Для работы с файлами и со строками из класса iostream наследуются соответственно классы fstream и stringstream, которые позволяют, как писать данные в потоки, так и читать данные из потоков. Для наглядного представления отношения наследования между этими классами смотри диаграмму наследования в параграфе 1.1.
Здесь же заметим, что заголовочный файл <iostream> содержит потоки ввода-вывода: cin, cout, cerr и clog, примеры работы с которыми смотри в параграфе 1.8.
1.6. Буферы и синхронизация.
Потоки выполняют обмен данными между прикладной программой и файлом через буферы, которые являются объектами классов filebuf и stringbuf соответственно. Эти классы являются производными от абстрактного класса streambuf. Каждый буфер содержит в зависимости от режима доступа один или два символьных массива: один для ввода, а второй для вывода данных. Каждый поток поддерживает внутренний указатель на буфер. Ввод-вывод данных из буфера в файл или в строку называется синхронизацией буфера с внешним устройством ввода-вывода. Синхронизация буфера с внешним устройством может выполняться неявно или явно. Неявно синхронизация выполняется в случае закрытия файла или заполнения буфера. Для явной синхронизации используют манипуляторы flush и endl или функцию sync.
Например, в следующей программе показана синхронизация буфера с входным потоком.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <iostream> using namespace std; int main () { char a, b; cout << "Enter a word: "; a = cin.get(); cin.sync(); cout << "Enter another word: "; b = cin.get(); cout << "The first word began by: " << a << endl; cout << "The second word began by: " << b << endl; return 0; } |
1.7. Манипуляторы.
Манипулятором называется глобальная функция, которая изменяет поток. Манипуляторы предназначены для включения в последовательность операторов ввода или вывода и могут изменять состояние потока, управляя его флагами. Манипуляторы подразделяются на две группы: простые манипуляторы и параметризованные манипуляторы.
Простые манипуляторы не требуют аргументов. Ниже перечислены простые манипуляторы:
endl помещает в выходной поток символ ‘\n’ и вызывает манипулятор flush,
ends помещает в поток символ ‘\0’,
flush освобождает поток,
dec устанавливает 10 c/c,
hex устанавливает 16 c/c,
oct устанавливает 8 c/c,
ws игнорирует при вводе ведущие пробелы.
Манипуляторы с аргументами называются параметризованными. Ниже перечислены параметризованные манипуляторы, которые объявлены в заголовочном файле <iomanip>:
resetiosflags сбрасывает флаги форматирование,
setiosflags устанавливает флаги форматирования,
setbase устанавливает систему счисления,
setfill устанавливает символ заполнитель,
setprecision устанавливает точность для плавающих чисел,
setw устанавливает ширину поля для символа заполнителя.
Можно написать свой манипулятор. Например, следующая функция является манипулятором, который выполняет переход на новый абзац.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <iostream.h> ostream& tabl(ostream& out) { out << '\n' << '\t'; return out; } int main() { cout << "This is a test for the manipulator tabl" << tabl << "The end of the test" << endl; return 0; } |
9.8. Стандартные потоки ввода-вывода.
Стандартный входной поток cin является объектом класса istream и по умолчанию связан с клавиатурой. Стандартные потоки cout, cerr и clog являются объектами класса ostream и по умолчанию связаны с дисплеем. Для работы со стандартными потоками в программу необходимо включить заголовочный файл <iostream>. Например,
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <iostream.h> int main() { int n = 0; clog << "n = " << n << endl; ++n; clog << "n = " << n << endl; return 1; } |
А. П. Побегайло
А че у тебя подсветки кода то нету? - скопипастил и даже код тегами выделить поленился?
Упырь тупой. Репетитор еще...
исправил. пишите еще предложения/замечания