240.41K
Категория: ПрограммированиеПрограммирование

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

1.

Объектно-ориентированное
программирование
Объектно-ориентированное программирование (ООП) — это парадигма
программирования, основанная на концепции «объектов», которые содержат
данные и код: данные в форме полей (часто называемые атрибутами) и код в
форме функций (называемых методами).
ООП реализуется практически на всех современных языках программирования
Лабораторные Контрольная
Экзамен
Бонусы
9+9+9+9+9+5=
50
2 *20 = 40
<=10
10
Содержание курса:
ООП на С++, С#, Java
1

2.

C++
• Возник в начале 1980-х годов, когда сотрудник фирмы Bell Labs Бьёрн Страуструп придумал
ряд усовершенствований к языку C под собственные нужды
• Официальная стандартизация языка началась в 1998 году, когда был опубликован стандарт
языка ISO/IEC 14882:1998 (известный как C++98)
• В 2003 г. опубликован стандарт C++ ISO/IEC 14882:2003, где исправлены выявленные ошибки
и недочёты предыдущей версии стандарта
• C++11. В него включены дополнения в ядре языка и расширение стандартной библиотеки
• Следующая версия стандарта, C++14, вышла в августе 2014 года.
• Стандарт C++17, опубликованный в декабре 2017 года, включил в стандартную библиотеку
параллельные версии стандартных алгоритмов и удалил некоторые устаревшие и крайне
редко используемые элементы.
• Последняя стабильная на текущий момент действующая версия стандарта — C++20.
• Никто не обладает правами на язык C++, он является свободным.
Популярные авторы: Шилдт, Майерс, Мартин, Липпман

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

http://cppreference.en/
2

3.

Некоторые замечания по С++
• Варианты инициализации переменных
int x=1;
int x(1);
int x{1};
Фигурные скобки предпочтительней
Варианты инициализации по умолчанию:
int х{}; double y{}; string str{}; char alph{}; bool simb{}; int *ptr{};
0
0.0
пустая строка
‘\0’
false
nullptr
соответственно:
• auto – неявная типизация
auto x=1;
Возможна только для инициализации литералом или выражением в return
auto foo(){
double alpha=0;
alpha+=…
return alpha;
}
• цикл по диапазону
int arr[]={1,2,3,4}
for(auto elem:arr) cout<<elem;
• size_t – unsigned long long int
• using - используется вместо typedef
3

4.

Ссылки (reference)
Type& - ссылочный тип
Type x=expression;
Type& rc=x; // rc – ссылка на x
Ссылку называют другим именем переменной, изменение ссылки приводит к изменению
соответствующей переменной.
Инициализация ссылки обязательна, нулевые ссылки (ссылки на «ничто») не
поддерживаются.
void foo(int& arg){/*…*/}
foo(x); //При вызове ссылка arg инициализируется значением x
Можно определить ссылку на константу
const Type d =expression;
const T& rcd = d;
Rvalue-ссылки
Type&& rv = expression;
Rvalue-ссылки представляют отдельный ссылочный тип.
4

5.

array
#include <array>
using array <int,3> arr=massiv;
//Использование как параметра функции
int foo(massiv ar){ //
/* ar[0]=…. */
}
//Вызов
massiv arr{0,1,6};
foo(arr);
//Использование как типа функции
massiv foo(…){
{ massiv ar{0,0,0};
ar[0]=2;
return ar;
}
array является классом – контейнером библиотеки STL с соответствующими возможностями (см. раздел STL),
но без автоматического увеличения емкости.
STL
5

6.

Пара
Библиотека utility или любой класс-контейнер из STL
Пара (pair) – cтруктура, два объекта которой могут иметь разные типы
pair<T1 , T2 >
pair<int, double> p = make_pair<int, double>(1, 2.6);
pair<int, string> arr[] = { {1,”Понедельник”}, {2,”Вторник”}, {3,”Среда”} };
p.first=5; arr[0].first //Имя первого элемента пары
p.second=9.8; arr[0].second//Имя второго элемента пары
Две пары, в которых T1 и T2 одинаковы, могут сравниваться между
собой, если в типах определена операция «меньше».
6

7.

Кортеж
• Кортеж (tuple) - коллекция элементов с фиксированным размером.
Любая связанная пара, тройка, четверка и т.д. элементов является
кортежем. В качестве элементов кортежа могут выступать объекты
произвольного типа.
• Для кортежей определены функции сравнения и хеширования.
#include <tuple>
typedef std::tuple<int, double, int, double> myTuple;
myTuple c0(0, 1, 2, 3);
cout << std::get<0>(c0) << " "; //Чтение 1-го элемента
cout << std::get<1>(c0) << " "; //Чтение 2-го элемента
get<2>(c0) =8; // Изменение 3-го элемента
c0 = make_tuple(4, 5, 6, 7);//Создание нового значения кортежа
7

8.

Явное преобразования типов с использованием cast
Явные static_cast, dynamic_cast, const_cast, reinterpret_cast.
cast-name< type >( expression );
double d = 97.0;
сhar ch =static_cast< char >( d );
reinterpret_cast работает с внутренними представлениями объектов (re-interpret – другая интерпретация того
же внутреннего представления), причем правильность этой операции целиком зависит от программиста.
Например:
int i;
char p[] = "Это строка";
// приведение типа указателя к типу целого
i = reinterpret_cast<int> (p); //Предупреждение компилятора и ошибка
cout << i;
dynamic_cast применяется при идентификации типа во время выполнения (run-time type identification).
Оператор dynamic_cast можно применять для преобразования указателя, ссылающегося на объект типа класса
в указатель на тип класса из той же иерархии
const_cast служит для трансформации константного типа в неконстантный и подвижного (volatile) – в
неподвижный.
extern char *string_copy( char* );
const char *pc_str;
char *pc = string_copy( const_cast< char* >( pc_str ));
8

9.

Перегруженные функции
C++ позволяет указать несколько функций одного и того же имени в
одной области. Эти функции называются перегруженными
функциями. Перегруженные функции должны отличаться друг от
друга списком параметров: их количеством или/и типами.
int print(string s);
int print(double dvalue);
int print(double dvalue, int prec);
Существование перегруженных функций является одной из
разновидностей полиморфизма.
9

10.

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

11.

class Alpha { // Начало определения
// Поля класса
private:
int _x=0;
double _y=0;
public:
//Конструкторы
Alpha (){} // или Alpha()=default
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(){} //или ~Alpha()=default;
}; //Конец определения класса
int main() {
double f;
Alpha q; // или Alpha q=Alpha();
Alpha alp1(4,6.0); //Alpha alp1=Alpha(4, 6.0);
Alpha alp2[3]; //массив объектов
alp2[0].setAlpha(7,-5.3);
cout<<alp2[0].getY()<<alp2[0].getX();
Alpha* alp3=nullptr;
alp3= new Alpha;
alp3->setAlpha(1,1);
delete alp3;
alp1:Alpha
}
Объект al1
public и private определяют доступ к членам класса
из точек программы вне определения класса 11

12.

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

13.

//Спецификация класса Complex
#include <iostream>
#include <vector>
using namespace std;
//Объявление класса
class Complex
{ //Поля
double real_ = 0;
double image_ = 0;
public:
//Конструкторы
Complex() {}; //Стандартный
//или Complex()=default;
Complex(double, double);//С
параметрами
Complex(const Complex&); //Копирования
// Методы
// Ввод числа
void setComplex(const double&, const double&);
string showComplex() const; //Вывод на консоль
//Перегруженные операции
Complex operator + (const Complex&);// сложения
Complex operator * (const Complex&); //
умножения
Complex operator / (const Complex& );//деления
Complex operator = (const Complex& );
//Копирования
Complex operator -(); //Инверсии знака
//Дружественная функция вывода на консоль
friend void getComplex(Complex);
//Деструктор
~Complex();
//или ~Complex() = default;
};
Дружественными называются функции, которые не
являются членами класса, но имеют доступ к закрытым
его членам. Дружественная функция вызывается без
объекта класса. Хотя бы один из параметров функции
13
должен иметь тип этого класса.

14.

//Реализация класса 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;
}
void Complex::setComplex(const double& r, const double&
im) {
real_ = r; image_ = im;
}
string Complex::showComplex()const {
string str = std::to_string(real_) + " +i" +
std::to_string(image_);
return str;
}
Complex Complex:: operator + (const Complex& t)
{
Complex tmp;
tmp.real_ = this->real_ + t.real_;
tmp.image_ = this->image_ + t.image_;
return tmp;
}
Complex Complex::operator *(const Complex& t) {
Complex tmp;
tmp.real_ = this->real_ * t.real_ - this->image_ * t.image_;
tmp.image_ = this->image_ * t.real_ + this->real_ *
Complex Complex::operator / (const Complex& t) {
Complex tmp;
double buf = t.real_ * t.real_ + t.image_ * t.image_;
//выброс исключения
if (buf == 0) throw 1;
tmp.real_ = (this->real_ * t.real_ + this->image_ *
t.image_) / buf;
tmp.image_ = (this->image_ * t.real_ - this->real_ *
t.image_) / buf;
return tmp;
}
Complex Complex:: operator = (const Complex& c1) {
this->real_ = c1.real_; this->image_ = c1.image_;
return *this;
}
Complex Complex::operator -() {
real_ = -real_; image_ = -image_;
return *this; }
string getComplex(const Complex& c1) {
string str = std::to_string(c1.real_) + " +i" +
std::to_string(c1.image_);
return str;
}
14

15.

Complex cm1, cm2, cm3, cm4;
try {
double temp1 = 0, temp2 = 0;
cout << "1-е число " << endl;
cin >> temp1 >> temp2;
cm1.setComplex(temp1, temp2);
cout << "2-е число " << endl;
cin >> temp1 >> temp2;
cm2.setComplex(temp1, temp2);
cout << "3-е число " << endl;
cin >> temp1 >> temp2;
cm3.setComplex(temp1, temp2);
cout << "Сумма \n";
cm4 = cm1 + (cm2 + cm3);
cout << cm4.showComplex() << endl;
cout << "Произведение \n";
cm3 = cm1 * cm2 * cm4;
cout << cm3.showComplex();
cout << "Частное\n";
cm3 = cm1 / cm2;
cout << cm3.showComplex() << endl;
cout << "Массив из 2-х чисел \n";
Complex cm5[] = { {2.1, 3.0},{-4.8, 2.7}
};
cout << "Объект-указатель \n";
Complex* cm6 = new Complex(3, 8);
cout << cm6->showComplex() << endl;
//Копирование объектов
cout << "Объект-копия cm3 \n ";
Complex cm8 = cm3;
cout << getComplex(cm8) << endl;
cout << "Инверсия объекта \n ";
cout << getComplex(-cm8) << endl;
delete cm6;
}//try
catch (int) {
cout << "Деление на 0!\n";
return 1;
}
return 0;
}
15

16.

UML диаграмма класса
Диаграмма создана uml-редактором (слева). Справа схема класса, созданная в VS
16

17.

Конструкторы
• Конструктор – специальный метод, имеющий имя класса и не возвращающий значения. Конструктор
создает объект класса или во время компиляции или во время выполнения программы функцией new.
• Конструкторов в классе может быть несколько. Отличаться друг от друга они должны списком
параметров – количеством или типами.
• Кроме конструкторов без параметров и с параметрами для создания объектов применяется
конструктор копирования. Конструктор копирования создает новый объект класса и копирует
значения полей уже имеющегося объекта в поля нового.
• В классе конструкторы и деструктор можно не определять явно. В этом случае компилятор
автоматически создает конструктор с пустым списком параметров и пустым телом, конструктор
копирования и деструктор с пустым телом – они называются конструкторами и деструктором по
умолчанию или неявными.
• При наличии в классе поля-указателя применение стандартного конструктора копирования приводит к
созданию двух объектов, у которых указатели имеют одно и то же значение, так что при изменении
одного объекта изменяется и другой. Для предотвращения этого надо создавать конструктор
копирования такой, который делал бы создавал в новом объекте новый указатель и только тогда
выполнял копирование

18.

Доступ к полям объекта
Созданный объект имеет доступ к полям и методам класса согласно
расстановке спецификаторов доступа в определении класса. Те члены класса,
которые перечислены после двоеточия, называются в случае public
открытыми, а в случае private закрытыми. К закрытым членам из точки
программы вне класса обращаться нельзя.
int main()
{
Complex cm1;
cm1._real = 1; //Ошибка: поле _real
cm1._image = 4; //Ошибка: поле _image
закрыто (private по умолчанию)
закрыто
cm1.showComplex(); //Верно, метод открыт (public)
}
Доступ к полям и методам объекта, созданного оператором new (cm6),
производится с помощью не точки, а стрелки.
cm6->showComplex();
В классе, созданном с помощью ключевого слова class, по умолчанию все
члены закрыты. Если класс определяется с помощью слова struct, то по
умолчанию все члены public.
18

19.

Деструктор
Деструктор в классе всегда один. Он разрушает объект и, по определению, не может
иметь параметров. Деструктор имеет имя класса с предшествующим символом “~”.
Тело деструктора зависит от полей класса. Если среди них нет указателей и в
конструкторе или методах нет оператора new, их инициализирующего, то тело может
быть пустым. Такой деструктор называется неявным деструктором.
Если среди полей класса есть указатель и используется оператор new, то в деструкторе
следует задействовать оператор delete или delete[]
сlass Gamma {
int *ptr=nullptr;
Gamma() { ptr=new int[2];}
...
//Деструктор
~Gamma(){delete[] ptr; }
};
Деструктор объекта, заданного во время компиляции, вызывается автоматически при
выходе объекта из блока, в котором он был создан.
Деструктор объекта, созданного во время выполнения программы функцией new, надо
вызывать посредством оператора delete.
Деструктор может быть вызван явно
Gamma g1; . . . ~g1.Gamma();
19

20.

Замечания о полях и методах класса
Поля
В созданном объекте поля должны быть инициализированы. Инициализация
выполняется конструктором с параметрами или, если такового нет,
значениями, записанными при объявлении поля в классе.
Методы
• У методов класса, реализующих операцию с несколькими операндами,
количество параметров на единицу меньше количества операндов, так как в
качестве одного из операндов выступает объект класса, вызывающий этот
метод. У дружественных функций и статических методов, которые
вызываются без объекта класса, количество параметров не уменьшается.
20

21.

• Современный С++ рекомендует в качестве параметра использовать ссылку на
объект. Объясняется это следующим. В С++ так же, как и в Си, операторы в теле
функции (метода) работают с копией аргумента. Для аргумента-объекта это
означает автоматический вызов конструктора копирования класса. Если среди
членов класса нет поля-указателя (как в классе Complex), то вызов метода с
использованием неявного конструктора копирования закончится успешно.
Если такое поле есть, то следует специально создать конструктор, копирующий
объект с учетом наличия в нем указателя.
Ссылка или указатель на параметр метода позволяет избежать вызова
конструктора копирования.
Если параметр не изменяется в теле функции, то следует использовать const:
double foo(const double& x, const int& y){ return x*y; }
• В методе, в сигнатуре которого const помещен перед телом, запрещены
изменения полей класса.
auto get() const {return ….}
21

22.

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

23.

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

24.

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

25.

Включение
struct Point { double x=0, y=0; };
class Triangle1 {
Point* vert = nullptr;//Массив для координат
public:
//Конструктор
Triangle1() {vert = new Point[3];
~Triangle1() {delete vert;};
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() {
for (int i = 0; i < 3; i++) {
cin >> vert[i].x >> vert[i].y;
}
}
~Triangle1() {
delete[] vert;
}
};
int main()
Triangle1 tr1;
tr1.setTriangle();
cout<<tr1.getPerimeter();
}
25

26.

Варианты включения
1.
class A {
public:
int a = 0;
void fooA() { cout << a << endl; }
};
2. То же, но с указателями
class A {
public:
int a = 0;
void fooA() { cout << a << endl; }
};
class B {
public:
A a2;
B(){ }
void fooB(int x) { a2.a = x;
//Делегирование
a2.fooA(); }
};
class B {
public:
A* a2=nullptr;
B(){a2=new A;}
void fooB(int x) { a2->a = x;
a2->fooA(); }
~B(){delete a2;}
};
-----------------------B b1;
b1.fooB(5);
cout << b1.a2.a << endl;
------------------------------------B b1;
b1.fooB(5);
26

27.

3. С параметрическим конструктором
class A {
public:
int a = 0;
void fooA() { cout << a << endl; }
};
class B {
public:
A* a2=nullptr;
B(A* a3) {
a2 = a3;
}
void fooB(int x) { a2->a = x; }
~B()[ delete a2;]
};
-----------------------------------------
1. A* a1 = new A;
B b1(a1); или B b1(new A);
b2.fooB(5);
2. B* b2=new B(new A);
b2->fooB(6)
27

28.

Зависимость (использование)
//Класс Студент
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 = {}; //Доска
Student st;
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) ; }
};
28

29.

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;
}
Привет,
Привет, мир
29

30.

Статические члены класса: поле и метод
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 = 1;
int main()
{
Alpha m1, m2(5), m3(7);
//Вызов метода с объектом класса
cout << m1.getCount()<< m2.getCount()<< m3.getCount();
//Вызов метода без объекта класса
cout << Alpha::getCount();
//Изменение нестатического поля
m3.setAl(8);
}
Статическая переменная count увеличивает свое
значение при каждом вызове конструктора и во всех
объектах класса имеет одно и то же значение, равное 4.
Локальное поле _al ведет себя обычным образом – в
объекте m3 имеет значение 8, в m2 -5,а в m1 – 0 по
умолчанию.
Статический метод может вызываться как через объект
класса, так и без него. В его теле используются
30
статические поля класса и глобальные поля любого типа.

31.

Вложенные классы
Nested class - класс, определение которого находится внутри другого класса. Обычно вложенные классы
применяются для описания таких сущностей, которые могут существовать только в рамках объекта
внешнего класса, особенно когда внешний класс работает с набором объектов вложенного класса.
class BufferedIO
{
public:
enum IOError { None, Access, General };
// Вложенный класс
class BufferedInput
{
public:
int read();
int good()
{
return _inputerror == None;
}
private:
IOError _inputerror;
};
// Вложенный класс
class BufferedOutput
{
// Member list
};
BufferedInput bI;
BufferedOutput bO;
};
int main() {
BufferedIO bIO;
bIO.bI.read();
}
31

32.

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

33.

В С++ работа с исключениями формализована с помощью блоков 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 используются три
точки, то обработчик перехватывает любое исключение
33

34.

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; //Обработка
}}
34

35.

Механизм обработки исключений, вообще говоря, обеспечивает корректное завершение программы: при
возникновении исключительной ситуации в блоке try с помощью деструктора уничтожаются созданные в
начале блока объекты и сворачиваются соответствующие открытые стеки; при сбоях в работе с файлами
последние закрываются и т.д.
class MyException {
public:
int x=0;
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)
Конструктор
Деструктор
Обработка исключения!
Продолжение программы
35

36.

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

37.

Класс 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.is_open()) // вызов метода is_open()
if (!inFile) throw 1;
// Чтение содержимого
……
else
}
{
catch (int) {
cout << "Файл не открыт!\n";
cerr<<“File not\n”;
return -1;
return -1;
}
}
37

38.

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

39.

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

40.

• Метод 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();
40

41.

Класс ofstream
Используется для записи в файл. Открытие, проверка открытия и закрытие файла выполняются аналогично ifstream.
Если указываемый файл не существует, то он будет создан. Используются режимы out, app, trunc.
• Оператор << Перенаправляет форматированный вывод в текстовый файл. Совместно с << могут использоваться
манипуляторы width, precision
#include <fstream>
#include <iomanip>
using namespace std;
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
41

42.

Метод 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() {}
};
Аналогичный процесс в С#
называется сериализацией
(serialize). Сериализация во
многих случаях экономит код по
сравнению со стандартными
средствами IO.
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 outFile1("D:\\Data.dat", ios::in | ios::binary);
Point p;
outFile1.read(reinterpret_cast<char*>&p, sizeof(Point));
cout << p.x << " "<< p.y;
outFile1.read((char*)&p, sizeof(Point));
cout << endl<<p.x << " " << p.y;
outFile1.close();
}
34
78
42

43.

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

44.

Библиотека sstream
sstream — заголовочный файл с
классами, функциями и переменными
для организации работы со строками
через интерфейс потоков.
#include <iostream>
#include <sstream>
using namespace std;
int main()
{
int val=123;
stringstream geek;
geek << val;
string x;
geek >> x;
cout<<x+"4"<<endl;
return 0;
}
1234
Временная сложность: O(n) ,
n — длина целого числа.
44

45.

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

46.

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

47.

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

48.

Умные указатели (smart pointers)
Одним из основных источников ошибок в программировании на C/C++
является утечка памяти. Утечки часто возникают из-за невозможности вызвать
delete для памяти, выделенной с помощью new. Современный C++
придерживается принципа: получение ресурса есть инициализация (Resource
Acquisition Is Initialization (RAII)). Идея проста. Ресурсы (heap, дескрипторы
файлов, сокеты и т. д.) должны принадлежать объекту. Этот объект создает и
получает новый выделенный ресурс в конструкторе и удаляет его в его
деструкторе. Принцип RAII гарантирует, что все ресурсы должным образом
возвращаются операционной системе, когда объект-владелец выходит за
пределы области.
Одной из реализацией RAII являются умные указатели
Интеллектуальные указатели (современный C++):
https://learn.microsoft.com/ru-ru/cpp/cpp/smart-pointers-moderncpp?view=msvc-170
48

49.

Smart pointers - объекты шаблонных классов:
•unique_ptr
•weak_ptr
•shared_ptr
Классы имеют деструкторы, разрушающие соответствующие объекты при выходе из области
видимости.
shared_ptr имеет следующие конструкторы:
•shared_ptr<Type> имя (new Type); //то же, что и тип *имя=new тип
•shared_ptr<Type[]>имя(new Type[целое]);
и методы:
•make_shared создает указатель на объект;
•reset сбрасывает или заменяет объект, которым владеет
STL
49

50.

include <memory>
class A { public: int x; };
int main()
{
//Стандартные указатели
A* ptrA = new A;
ptrA->x = 9;
delete ptrA;
//Умные указатели
shared_ptr<A> ptrAs(new A);
ptrAs->x = 5;
shared_ptr<A[]> arr(new A[5]);
arr[0].x = 4;
//Умные указатели разрушаются автоматически
}
50

51.

Массив с задаваемой длиной
class Array1D {
public:
int length = 0;
shared_ptr<int[]> ptrD; //int* ptrD
//Конструкторы
Array1D(int);
Array1D(shared_ptr<int[]>, int);
Array1D(const Array1D&);
//Методы
public:
void viewArray();
void setArray();
Array1D operator+(Array1D&);
~Array1D(){}
};
Array1D::Array1D(shared_ptr<int[]> tmp, int n) {
length = n;
ptrD.reset(new int[length]);
for (int i = 0; i < length; i++)
ptrD[i] = tmp[i];
}
Array1D::Array1D(const Array1D& s) {
length = s.length;
ptrD.reset(new int[length]);
for (int i = 0; i < length; i++)
ptrD[i] = s.ptr[i];
}
Array1D::Array1D(int p) {
length = p;
ptrD.reset(new int[length]);
for (int i = 0; i < length; i++)
ptrD[i] = 0;
}
void Array1D::setArray() {
for (int i = 0; i < length; i++)
cin >> ptrD[i];
}
void Array1D::viewArray() {
for (int i = 0; i < length; i++)
cout << ptrD[i] << " ";
}
Array1D Array1D::operator+(Array1D& s) {
shared_ptr<int[]> tmp(new int[s.length]);
for (int i = 0; i < length; i++)
tmp[i] = s.ptrD[i] + ptrD[i];
Array1D ret(tmp, s.length);
return ret;
}
STL
51

52.

int main()
{
Array1D ar1(3);
ar1.setArray();
Array1D ar2(3);
ar2.setArray();
Array1D ar3 = ar1 + ar2;
ar3.viewArray();
//Разрушение объектов выполняется автоматически
}
STL
52

53.

Дополнительные темы
1. Указатели на функцию
2. Обратный вызов
Наследование и полиморфизм в С++
53

54.

Указатель на функцию
тип (*имя_указателя)(параметры);
указатель на функцию – ее имя
int main()
{
void (*message)();
void hello()
{
cout <<"Hello, World\n";
}
void goodbye()
{
cout <<"Good Bye, World\n";
}
message=hello;
message();
message = goodbye;
message();
}
return 0;
Hello, World
Good Bye, World
Указатель на функцию – адрес ее первого
выполняемого оператора.
STL
54

55.

Массив указателей на функцию
тип (*имя_массива[размер]) (параметры)
int main()
{
int a = 10;
int b = 5;
double (*operations[3])(int, int) = {add, subtract, multiply};
// возвращаем длину массива
int length = sizeof(operations)/sizeof(operations[0]);
for(int i=0; i < length;i++)
{ // Вызов функций
cout<<operations[i](a, b)<<‘n’; }
return 0;
Сигнатура следующих функций: double имя (int, int)
double add(int x, int y)
{
return x + y;
}
double subtract(int x, int y)
{
return x - y;
}
double multiply(int x, int y)
{
return x * y;
}
}
15
5
50
Здесь массив является контейнером для указателей на
функции
STL
55

56.

Обратный вызов (callback)
имя функции используется в качестве параметра
Функция integral вычисляет определенный интеграл по методу прямоугольников.
Подинтегральная функция имеет сигнатуру double имя (double)
double integral(double a, double b, int M, double(*subIntegral)(double)) {
double sum = 0;
double step=(b - a) / M;
for (int i = 0; i < M; i++)
sum += step * (*funct)(a + i * step / 2);
return sum;
}
int main(){
integral (0,1,10, std::sin);
integral (2,3,12, std::cos);
}
STL
56
English     Русский Правила