140.74K
Category: programmingprogramming

Объектно-ориентированное программирование

1.

Объектно-ориентированное
программирование
АВторы: Р.Лафоре , Шилдт, Майерс, Мартин

https://msdn.microsoft.com/en-us/default.aspx

http://cpp-reference.ru/
Лабораторные
8+8+8+8+8+5=45
Практические
15
Экзамен
2 вопроса +тест=40
1

2.

Пользовательский тип данных организуется посредством
конструкции class (struct)
сlass имя_класса {
Члены класса:
-конструкторы;
-поля;
-методы;
-деструктор;
};
Конструктор создает объект класса
Значения полей определяют состояние объекта в каждый момент
времени выполнения программы.
Методы сообщают о состоянии объекта или изменяют состояние
Деструктор разрушает объект.
2

3.

class Alpha { // Начало определения
// Поля класса
private:
int _x=0;
double _y=0;
public:
//Конструкторы
Alpha (){}
Alpha(int x, double y){ _x=x; _y=y;}
//Alpha(int x, double y): _x(x), _y(y){}
//Методы
void setAlpha( int a, double b){_x=a; _y=b;}
int getX() const { return _x;}
double getY() const {return _y;}
//Деструктор
~Alpha(){}
}; //Конец определения класса
int main() {
double f;
Alpha q;
Alpha alp1(4,6.0);
Alpha alp2[3]; //массив объектов
alp2[0].setAlpha(7,-5.3);
cout<<alp2[0].getY()<<alp2[0].getX();
Alpha* alp3= new Alpha;
alp3->setAlpha(1,1);
delete alp3;
}
alp1:Alpha
Объект
al1
3

4.

class Beta {
public: int x=0;
};
В классе автоматически создаются
специальные методы:
• Конструктор
Beta(){}
• Деструктор
~Beta(){}
• Конструктор копирования
Beta (const Beta& other){
this->x=other.x }
Кроме этого пользователю предоставляется
операция присваивания для объектов класса
Если в классе явно определен один из
специальных методов, то аналогичный ему
автоматически не создается
int main (){
Beta b1, b2;
b1.x=5;
b2=b1;//Присваивание = почленное копирование
cout<<b2.x //5
//Конструктор копирования=
//почленное копирование
Beta b3(b2);
cout<<b3.x //5
// b3 и b2 – разные объекты
b3.x=6;
cout<<b2.x; //5
//Или
Beta b4=b3;
}
4

5.

Почленное копирование называется поверхностным. При наличии в классе указателя
оно приводит к созданию двух объектов таких, что при изменении одного изменяется
и другой. Для предотвращения этого надо создавать конструктор копирования такой,
который делал бы «глубокое» копирование, создавая в новом объекте новый
указатель.
class A{
int* x=0;
A(int n){x=new int[n];}
…….
~A(){delete[] x;}
};
A a1(5);
A a2=a1; // При изменении а1 изменяется и а2
5

6.

//Спецификация класса Complex
class Complex
{ //Поля
double _real=0;
double _image=0;
public:
//Конструкторы
Complex(); //Пустой
Complex(double, double);//С параметрами
Complex(const Complex&); //Копирования
// Методы
void setComplex(); // Ввод числа
void showComplex() const; //Вывод
//Перегруженные операции
Complex operator = (Complex);//присваивания
Complex& operator + (Complex&);// сложения
Complex& operator * (Complex&); // умножения
Complex& operator / (Complex&);//деления
//Дружественная функция вывода на консоль
friend void getComplex(Complex);
//Деструктор
~Complex();
};
UML –диаграмма класса
Complex
-real: double
-image: double
+Comlex()
+Complex(double, double)
+Complex(Complex&)
~Complex()
+setComplex(): void
+showComplex(): void
+operator+(Copmplex): Complex
+operator*(Complex): Complex
+getComplex(Complex): Complex
Дружественными называются функции, которые не являются членами класса,
но имеют доступ к закрытым его членам. Дружественная функция вызывается без объекта класса.
6

7.

cout << "Массив из 2-х чисел\n";
Complex cm5[2];
for (int i = 0; i < 2; i++)
cm5[i].setComplex();
cout << "Объект-указатель\n";
Complex *cm6 = new Complex(3, 8);
cm6->showComplex();
//Копирование объектов
cout << "Объект-копия cm1\n ";
Complex cm7(cm1);
getComplex(cm7);
//или
cout << "Объект-копия cm3\n ";
Complex cm8 = cm3;
getComplex(cm8);
delete cm6;
int main(){
Complex cm1, cm2, cm3;
cout << "1-е число \n";
cm1.setComplex();
cout << "2-е число \n";
cm2.setComplex();
cout << "Сумма \n";
cm3 = cm1 + cm2;
cm3.showComplex();
cout << "Произведение \n";
cm3 = cm1*cm2;
cm3.showComplex();
cout << "Частное\n";
cm3 = cm1 / cm2;
cm3.showComplex();
Complex cm4(34.7, 21.8);
}
7

8.

//Реализация класса Complex
//Определения конструкторов
Complex::Complex() {}
Complex::Complex(const Complex& c1) {
this->_real = c1._real; this->_image = c1._image;
}
Complex::Complex(double r, double im) {
this->_real = r; this->_image = im;
}
//Деструктор
Complex::~Complex() { }
//Методы
void Complex::setComplex(int real, int image) {
cout << "Действительная часть ? "; cin >> _real;
cout << "Мнимая часть ? "; cin >> _image;
}
void Complex::showComplex()const {
cout << this->_real << " +i" << this->_image << endl;
)
void getComplex(Complex c1) { //friend
cout << c1._real << " +i" << c1._image << endl; }
Complex Complex:: operator = (Complex& c1) {
this->_real =c1._real;
this->_image = c1._image;
return *this;
}
Complex& Complex:: operator + (Complex& t) {
Complex tmp;
tmp._real = this->_real + t._real;
tmp._image = this->_image + t._image;
return tmp;
}
Complex& Complex::operator *(Complex& t) {
Complex tmp;
tmp._real = this->_real*t._real –
this->_image*t._image;
tmp._image = this->_image*t._real +
this->_real*t._image;
return tmp; }
8

9.

Метод сложения двух комплексных чисел
class Complex{
/*…*/
Complex addComplex(Complex&);
/*… */
}
Вызов: cm1.addComplex(cm2);
Реализация
Complex Complex::addComplex(Complex& t) {
Complex tmp;
tmp._real =this-> _real + t._real;
tmp._image =this->_image + t._image;
return tmp;
//или
// return Complex(this->_real + t._real, this-> _image + t._image);
}
9

10.

Перегрузка операций
тип_возвращаемого_значения operator знак_опеpации (параметр) {... }
Complex Complex::operator + (Complex& t) {
Complex tmp;
Complex Complex:: operator - ()
{
_real =-_real;
tmp._real = _real + t._real;
_image = -_image;
tmp._image =_image + t._image;
return *this;
return tmp;
}
}
Вызовы: cm1.operator-() или -cm1
Вызовы: cm1.operator + (cm2)
или cm1+cm2
При перегрузке остаются неизменными приоритет операции и количество ее операндов
10

11.

Полиморфизм операций
int x1=2, x2=3, x3;
Complex cm1(2,3), cm2(5,8), cm3;
x3=x1+x2;
cm3=cm1+cm2;
cout<<(x1<<2)
операция перегружена в ostream c аргументом стандартного типа
cout.operator<< (arg)
Перегрузка перегруженной операции << для аргумента пользовательского типа
friend ostream& operator << (ostream& out, Complex& t) {
out << t._real << " +i" << t._image << '\n’;
return out;
}
Вызов сout<<cm3;
11

12.

//Класс треугольников
class Triangle {
//Координаты вершин
int xVertice[3] = {0};
int yVertice[3] = {0};
public:
//Конструктор
Triangle() {};
//Установка треугольника
void setTriangle();
//Определение периметра
double getPerimeter();
};
void Triangle::setTriangle() {
cout << "Введите координаты\n";
for (int i = 0; i < 4; i++) {
cout << i + 1 << "-й вершины";
cin >> xVertice[i]>> yVertice[i]; }
}
double Triangle::getPerimetr() {
double a = sqrt(pow(xVertice[1] - xVertice[0], 2) +
pow(yVertice[1] - yVertice[0], 2));
double b = sqrt(pow(xVertice[2] - xVertice[1], 2) +
pow(yVertice[2] - yVertice[1], 2));
double c = sqrt(pow(xVertice[2] - xVertice[0], 2) +
pow(yVertice[2] - yVertice[0], 2));
return a + b + c;
}
int main() {
Triangle tr1;
tr1.setTriangle();
cout<<“Периметр\n"<<tr1.getPerimeter()<<endl;
}
Вывод
Введите координаты
1-й вершины 1 1
2-й вершины 2 2
3-й вершины 2 1
Периметр 3.41421
12

13.

Включение
struct Point { double x, y; };
class Triangle1 {
Point* vert = nullptr;
public:
//Конструктор
Triangle1() {
vert = new Point[3];
}
double getPerimeter() {
double a = sqrt(pow(vert[1].x - vert[0].x, 2) +
pow(vert[1].y - vert[0].y, 2));
double b = sqrt(pow(vert[2].x - vert[1].x, 2) +
pow(vert[2].y - vert[1].y, 2));
double c = sqrt(pow(vert[2].x - vert[0].x, 2) +
pow(vert[2].y - vert[0].y, 2));
return a + b + c;
}
void setTriangle() {
cout << "Введите координаты\n";
for (int i = 0; i < 3; i++) {
cout << i + 1 << "-го угла\n";
cin >> vert[i].x >> vert[i].y;
}
}
~Triangle1() {
delete[] vert;
}
};
int main()
Triangle1 tr1;
tr1.setTriangle();
cout<<tr1.getPerimeter();
}
13

14.

Варианты включения
1.
class A {
public:
int a = 0;
void fooA() { cout << a << endl; }
};
class B {
public:
A a2;
B(){}
void fooB(int x) { a2.a = x; }
};
-----------------------B b1;
b1.fooB(5);
cout << b1.a2.a << endl;
2.
class A {
public:
int a = 0;
void fooA() { cout << a << endl; }
};
class B {
public:
A* a2;
B(){a2=new A;}
void fooB(int x) { a2->a = x; }
~B(){delete a2;}
};
------------------------------------B b1;
b1.fooB(5);
cout << b1.a2->a << endl;
14

15.

3.
class A {
public:
int a = 0;
void fooA() { cout << a << endl; }
};
class B {
public:
A* a2;
B(A* a3) {
a2 = a3;
}
void fooB(int x) { a2->a = x; }
};
----------------------------------------A* a1 = new A;
B b1(a1); или B b1(new A);
или
B* b1=new B(new A);
b1.fooB(5);
cout << b1.a2->a << endl;
15

16.

Зависимость (использование)
//Класс Студент
class Student {
string name = ""; //Имя студента
string group = ""; //Группа
string notepad = ""; //Блокнот
public:
//Конструктор
Student(string n, string g):name(n),group(g) {}
//Деструктор
~Student(){}
//Добавление записей в блокнот
void updateNotepad(string inf) { notepad +=inf; }
//Демонстрация содержимого блокнота
string showNotepad() { return notepad; }
};
//Класс Учитель
class Teacher {
string name = ""; // Имя преподавателя
string desk = ""; //Доска
public:
//Конструктор
Teacher(string n) : name(n) { }
//Деструктор
~Teacher() {}
//Изменяет состояние доски
void writeDesk(string inf) { desk = inf; }
//Показывает состояние доски
string showDesk() const { return desk; }
//Передает студенту информацию
void messageStudent(Student& st, string inf) {
st.updateNotepad(inf) ; }
};
16

17.

int main() {
//Создаем преподавателя и студента
Teacher t("Сидоров");
Student s1 ("Иванов", "KTбо-1");
//Teacher записывает на доске
t.writeDesk("Привет, ");
//и передает информацию студентам
t.messageStudent(s1, t.showDesk());
// Можно проверить что записано в блокноте
cout << s1.showNotepad() << endl;
//Teacher добавляет информацию на доску
t.writeDesk("мир");
//и опять заставляет студентов записывать
t.messageStudent(s1, t.showDesk());
//Окончательная запись в блокноте
cout << s1.showNotepad() << endl;
}
Привет,
Привет, мир
17

18.

Статические члены класса
class Alpha {
int _al=0;
//Объявление статической переменной
static int count;
public:
Alpha(int al=0) {
_al = al;
count++;
}
//Статический метод
static int getCount() {
return count;
}
void setAl(int p) {
_al = p;
}
};
//Инициализация статической переменной
int Alpha::count = 0;
int main()
{
Alpha m1, m2(5), m3(7);
//Вызов метода с объектом класса
cout << m1.getCount()<< m2.getCount()<< m3.getCount();
//Вызов метода без объекта класса
cout << Alpha::getCount();
//Изменение нестатического поля
m3.setAl(8);
}
Статическая переменная count увеличивает свое
значение при каждом вызове конструктора и во всех
объектах класса имеет одно и то же значение, равное
трем. Локальное поле _al ведет себя обычным образом
– в объекте m3 имеет значение 8, в m2 -5,а в m1 – 0 по
умолчанию.
Статический метод может вызываться как через объект
класса, так и без него. В его теле используются
статические поля класса и глобальные поля любого типа.
18

19.

Обработка исключительных ситуаций
Исключительная ситуация (исключение) рассматривается как ошибка; различают
логические ошибки (logic errors) и ошибки времени выполнения (runtime errors).
Различного рода исключения определены как типы в стандартном классе exception.
Cреди логических ошибок:
– invalid_argument – использование неверного аргумента при вызове функции;
– length_error – попытка создания слишком большого объекта;
– out of range – попытка обращения к элементу вне заданного диапазона.
К ошибкам времени выполнения относятся:
– range_error – слишком большое или слишком маленькое число с плавающей
точкой.
– overflow_error – слишком большое число с плавающей точкой.
Появление исключительной ситуации приводит к прекращению работы программы с
кодом, отличным от 0. Например, при выходе индекса цикла за пределы строки
программа прерывается с кодом завершения 3.
19

20.

class Greeting {
public:
string str=“”;
void changeChar(char s, size_t index) {
if (index < str.size())
str[index] = s;
else throw out_of_range("Индекс за пределами строки");
}
};
int main()
{
try {
Greeting gr;
gr.str = "Привет!";
gr.changeChar('?’, 6);
}
catch (out_of_range& e) {
cerr << e.what();
return -1;
}
20

21.

В С++ работа с исключениями формализована с помощью блоков try, catch и оператора throw.
В блок try помещается вся программа или ее фрагмент, в котором, по мнению программиста, может
возникнуть исключительная ситуация. Непосредственно за этим блоком следует блок catch с параметром, в
котором выполняются действия, определенные пользователем на этапе проектирования, и которые носят
название обработки исключения.
Оператор throw генерирует исключение и помещается в той точке программы, где создаются условия
возникновения ошибки.
if (условие возникновения ошибки) throw выражение;
При выполнении условия возбуждается исключение, которое перехватывается и обрабатывается в блоке
catch. При этом тип выражения должен совпадать с типом параметра catch.
В методе changeChar сравнивается значение параметра index и количество символов в строке. Если
сравнение не проходит из-за значения параметра index, то возбуждается стандартное исключение типа
out_of_range с соответствующим сообщением. Параметром catch является объект e класса
Exception, имеющий метод what. Результатом обработки является сообщение в потоке cerr,
предназначенном для вывода сообщений об ошибках, и прекращение работы программы с кодом –1.
В блоке try может быть размещено несколько операторов throw, реагирующих на различные типы
исключений. Количество блоков catch должно совпадать с количеством типов исключений, генерируемых
различными операторами throw.
Выражение в throw и тип параметра в catch могут иметь не только тип стандартного исключения, но и
любой другой тип, в том числе и пользовательский. Если вместо параметра в catch используются три точки,
то обработчик перехватывает любое исключение
21

22.

Механизм обработки исключений, вообще говоря, обеспечивает корректное завершение программы: при
возникновении исключительной ситуации в блоке try с помощью деструктора уничтожаются созданные в
начале блока объекты и сворачиваются соответствующие открытые стеки; при сбоях в работе с файлами
последние закрываются и т.д.
class MyException {
public:
int x;
MyException () {cout << "Конструктор\n"; }
~ MyException () { cout << "Деструктор\n"; }
};
void foo() {
try{
MyException a1;
cin >> a1.x;
if (!a1.x) throw 1;
cout << "Продолжение работы foo!\n";
}
catch (int )
cout << "Обработка исключения!\n"; }
}
int main()
{
foo();
cout << "Продолжение программы\n";
}
Сообщения на мониторе:
• без исключения (x=1)
Конструктор
Продолжение работы foo!
Деструктор
Продолжение программы
• c исключением (x=0)
Конструктор
Деструктор
Обработка исключения!
Продолжение программы
22

23.

«Ключом к написанию хорошей программы является разработка
таких классов, чтобы каждый из них представлял одно основное
понятие. Обычно это означает, что программист должен
сосредоточиться на вопросах: Как создаются объекты этого класса?
Могут ли эти объекты копироваться и/или уничтожаться? Какие
действия можно производить над этими объектами? Если на такие
вопросы нет удовлетворительных ответов, то, во-первых, скорее
всего, понятие не было "ясно", и, может быть неплохо, еще
немного подумать над задачей и предлагаемым решением вместо
того, чтобы сразу начинать "программировать вокруг" сложностей»
Б.Страуструп
23

24.

Принцип единственной ответственности
Означает, что каждый класс работает только над одной целью, ответственен только за неё и
изменяется только по одной причине.
Мартин определяет ответственность как причину изменения и заключает, что классы должны
иметь одну и только одну причину для изменений. Например, представьте себе класс,
который составляет и печатает отчёт. Такой класс может измениться по двум причинам:
• может измениться само содержимое отчёта
• может измениться формат отчёта.
Логично, что оба аспекта этих причин на самом деле являются двумя разными
ответственностями. В таком случае нужно разделить класс на два новых класса, для которых
будет характерна только одна ответственность. Причина, почему нужно сохранять
направленность классов на единственную цель в том, что это делает классы более
здоровыми. Что касается класса, приведённого выше, если произошло изменение в процессе
составления отчёта — есть большая вероятность, что в негодность придёт код, отвечающий за
печать.
Божественный объект ( God object) — антипаттерн объектно-ориентированного
программирования, описывающий объект, который хранит в себе «слишком много» или
делает «слишком много».
Мартин Роберт С. Быстрая разработка программ. Принципы, примеры, практика. 2004
24

25.

Принцип открытости/закрытости
Класс должен быть открытым для расширения, но закрытым для изменений.
Проще говоря, вы можете добавлять новую функциональность в класс, но не
можете редактировать существующие функции таким образом, что они будут
противоречить используемому коду
Принцип открытости/закрытости означает, что программные сущности
должны быть:
• открыты для расширения: поведение сущности может быть расширено
путём создания новых типов сущностей;
• закрыты для изменения: в результате расширения поведения сущности не
должны вноситься изменения в код, который эту сущность использует.
Однажды разработанная реализация класса в дальнейшем требует только
исправления ошибок, а новые или изменённые функции требуют создания
нового класса. Этот новый класс может повторно использовать код исходного
класса через механизм наследования.
25

26.

Класс fstream
Наиболее часто встречаемые операции:
• Операторы перенаправления ввода\вывода – << и >>
• Методы записи и чтения строк и символов getline(), get(), put()
• Потоковая запись и чтение методами write() и read()
• Методы открытия\создания и закрытия файлов open() и close()
• Методы проверки открыт ли файл is_open() и достигнут ли конец файла eof()
• Настройка форматированного вывода для >> с помощью width() и precision()
• Операции позиционирования tellg(), tellp() и seekg(), seekp()
26

27.

Класс ifstream
Предоставляет возможности для чтения файлов.
Открыть файл (связать файловый объект с реальным файлом) можно двумя способами: вызвав метод open() или указав путь к нему в
конструкторе класса.
#include <iostream>
#include <ifstream> // подключаем библиотеку
int main()
{
ifstream inFile; // создаем объект класса ifstream
inFile.open("d:\\1\\test.txt“); // открываем файл
}
или
int main()
{
ifstream inFile ("d:\\1\\test.txt"); // открываем файл в конструкторе
}
Проверка открытия
try{
if (!inFile) throw 1;
……
if (inFile.is_open()) // вызов метода is_open()
}
// Чтение содержимого
catch (int) {
else
cerr<<“File not\n”;
{
return -1;
cout << "Файл не открыт!\n";
}
return -1;
}
27

28.

Кроме имени при открытии файла могут указываться атрибуты в формате ios::режим
string filename;
inFile.open (fileName, ios::in | ios::binary);
Режим
Назначение
in
Открыть для ввода (выбирается по умолчанию для ifstream)
out
Открыть для вывода (выбирается по умолчанию для ofstream)
binary
Открыть файл в бинарном виде
aрр
Присоединять данные; новая запись фиксируется в конце файла
ate
Установить файловый указатель на конец файла
trunc
Уничтожить содержимое, если файл существует (выбирается по
умолчанию, если флаг out указан, а флаги ate и арр — нет)
28

29.

• Оператор считывания >> для текстового файла.
Работает до конца строки файла.
double d;
int i;
string s;
inFile >> d >> i >> s;
• getline() Считывание целой строки до перевода
каретки
string s;
getline(inFile, s);//getline - функция
cout << s << endl;
• get() Считывание одного или нескольких символов
int n = 10;
//Создаем буфер для чтения
char* buffer = new char[n+1];
buffer[n]=0;
//Читаем n символов
inFile.get(buffer, n);
• read (char* s, int n); считывание
последовательности (массива) байтов из бинарных
файлов; n – количество байтов
Пример далее.
• Метод eof() Проверяет достигнут ли конец
файла при чтении.
• Метод close() Закрывает файл – разрывает
связь между файловым объектом и файлом.
//Освобождаем буфер
delete [] buffer;
29

30.

• Метод seekg()
Производит установку указателя файла с текущей позиции в нужную, указываемую числом.
ios_base::end – Отсчитать новую позицию с конца файла
ios_base::beg – Отсчитать новую позицию с начала файла
ios_base::cur – Перескочить на n байт начиная от текущей позиции в файле (по умолчанию)
inFile.seekg(0, ios_base::end); //Переходим в конец файла
inFile.seekg(10, ios_base::end); // Переходим на 10 байтов от конца
file.seekg(30, ios_base::beg); // Переходим на 31-й байт
inFile.seekg(3, ios_base::cur); // Перепрыгиваем через 3 байта от текущей позиции
или то же
inFile.seekg(3);
• Метод tellg() Возвращает число прочитанных байт
//Переходим в конец файла
inFile.seekg(0, ios_base::end);
cout << "Размер файла (в байтах): " << (int)inFile.tellg();
30

31.

Класс ofstream
Используется для записи в файл. Открытие, проверка открытия и закрытие файла выполняются аналогично ifstream.
Если указываемый файл не существует, то он будет создан. Используются режимы out, app, trunc.
• Оператор << Перенаправляет форматированный вывод в текстовый файл. Совместно с >> могут использоваться
манипуляторы width, precision
#include <fstream>
ofstream outFile((“D:\\test.txt“);
int main(){
double d = 1;
for (int i = 0; i < 10; i++) {
d += sin(i / d);
// Указываем ширину ячейки для целого и выводим целое
outFile.width(10);
outFile << i;
// Указываем ширину ячейки для вещественного и
// кол-во знаков после запятой
outFile.width(10);
outFile.precision(5);
outFile << d;
outFile << endl;
}
outFile.close();
}
0
1
2
3
4
5
6
7
8
9
1
1.8415
2.7263
3.6177
4.5114
5.4064
6.302
7.198
8.0944
8.9909
31

32.

Метод write (char* s, int n) Используется в бинарных файлах для записи блока памяти (массива
байт) в файл. В ООП применяется для записи в файл объекта класса целиком. Для чтения из
файла используется read().
class Point {
public:
int x, y = 0;
Point(int x, int y) {
this->x = x;
this->y = y;
}
Point() {}
};
int main() {
Point p1(3,4); // Объекты класса
Point p2(7, 8);
fstream outFile1("D:\\Data.dat", ios::out| ios::binary);
//Записываем в файл
outFile1.write((char*)&p1, sizeof(Point));
outFile1.write((char*)&p2, sizeof(Point));
outFile1.close();
//Читаем из файла
fstream outFile2("D:\\Data.dat", ios::in | ios::binary);
Point p;
outFile2.read((char*)&p, sizeof(Point));
cout << p.x << " "<< p.y;
outFile3.read((char*)&p, sizeof(Point));
cout << endl<<p.x << " " << p.y;
outFile2.close();
}
34
78
32

33.

• Метод put (char ch) заносит в файл один символ
//Записываем в файл символы, вводимые с клавиатуры.
do {ch = std::cin.get();
outFile.put(ch);
} while (ch!='.’);
• Методы tellp, seekp используются с теми же целями, что и tellg, seekg при чтении файлов.
• После записи информации файл закрывать обязательно.
33
English     Русский Rules