Методы и стандарты программирования Семинары
C и C++
Отчетность
Совместимость Си и C++
Стандарты C++
Общие различия
Типы данных
Приведение типов
Warning: use of old-style cast
Функции стандартной библиотеки С
Объекты с квалификатором const
Символьные литералы
Логический тип bool
Программа на языке C++
Изменения по сравнению с Си и работой в прошлом семестре
Ввод/вывод C++
Ввод/вывод C++
Манипуляторы ввода/вывода
Пример использования манипуляторов IO
Динамическое выделение памяти
Пространства имен
Пространства имен
using
Доступ к элементам
Ссылки
Ссылки
Пример
Задачи
Введение в ООП
Принципы ООП
Классы и объекты
Конструкторы и деструкторы
Виды конструкторов
Конструктор копирования
Деструктор
Rule of tree / rule of five
Rule of tree / rule of five
Инкапсуляция
Инкапсуляция
Полиморфизм
Статический полиморфизм
Статический полиморфизм
Пример перегрузки конструкторов в классе
Списки инициализации членов класса
Списки инициализации членов класса
Списки инициализации членов класса
Списки инициализации членов класса
Задачи
Задача*
Классы
Вспоминаем
Конструктор копирования
Оператор копирующего присваивания
Главная разница
Тонкости С++
Задача 1
Дополнительная задача
Перегрузка операторов
Правило трех
Оператор копирующего присваивания
Оператор копирующего присваивания
Safe for self-assignment
Перегрузка операторов (operator overloading)
Перегружаемые операторы
Перегружаемые операторы
Реализация перегрузки операторов
Перегрузка оператора как функция-член класса
Перегрузка оператора как функция-член класса
Выражение операторов в терминах других
Приведение типов и конструкторы
Приведение типов и конструкторы
Приведение типов и конструкторы
Ключевое слово explicit
Ключевое слово explicit
Перегрузка оператора как функция-член класса
Перегрузка оператора как функция-член класса
Операторы << и >>
Перегрузка оператора как свободная функция
Пример
Правила перегрузки операторов
Подробнее
Задача
Категории выражений, Move семантика
Возможны ли эти выражения?
Возможны ли эти выражения?
Возможны ли эти выражения?
Возможны ли эти выражения?
Свойства выражений в C++
Категории выражений до С++11
Категории выражений до С++11
Категории выражений и ссылки
Смена значений
Смена значений
Чего бы мы хотели
Смена значений
move в конструкторах
move присваивание
Ссылки lvalue и rvalue
std::move
std::move
move-конструктор и move-присваивание
Теперь вернуть ForwardList – дешево!
Источники
Задачи
ООП: наследование и динамический полиморфизм
Наследование
Наследование
Наследование
Наследование
Наследование
Наследование
Раннее и позднее связывание
Виртуальные функции
Виртуальные функции
Виртуальные функции
Пример
Особенности
Случаи реализации полиморфизма
Вызов виртуальной функции по указателю (->)
Вызов виртуальной функции через ссылку (&) на базовый класс
std::vector
std::vector
std::vector
std::string
std::string
Non-member функции
std::string
Задача
ООП: абстрактные классы
Чисто виртуальная функция
Абстрактный класс
Абстрактный класс
std::stringstream
Пример
Разбиение строки на токены
Задача
Задача
Friend Функции и классы, работа с файлами
Дружественная функция/класс
friend функции
friend функции
friend функции
friend функции
friend функции
friend классы
friend классы
friend классы
friend классы
friend классы
Плюсы и минусы friend
Где использовать friend
Классы файлового ввода/вывода
std::ofstream – файловый вывод
std::ofstream – файловый вывод
std::ifstream – файловый ввод
std::ifstream – файловый ввод
std::ifstream – файловый ввод
Буферизованный вывод
std::fstream – файловый ввод/вывод
Общие методы
Режимы открытия файлов
Задача
Задача
Задача
Стандартные средства
std::pair
std::pair
std::pair
std::map
std::map – итераторы
std::map – итераторы
Ключевое слово auto
std::map
std::map
std::map
std::map
Range-based циклы
Задача
шаблонные функции
Шаблонная функция (обобщенная функция, template function) 
Шаблонные функции
Вывод типов
Шаблонные функции
Шаблонные функции
Явная специализация
Нетипизированные параметры
Нетипизированные параметры
Задача
Задача
шаблонные классы
Шаблон класса
Шаблон класса
Шаблон класса
Шаблоны классов
Шаблоны классов и заголовочные файлы
Шаблоны классов и заголовочные файлы
Параметр non-type
Параметры шаблонов по умолчанию
Частичная специализация
Частичная специализация
Частичная специализация
Частичная специализация
Подробнее
Задачи
Обработка исключений
Обработка ошибок в C++
Ключевые слова
Генерация исключений
Поиск исключений
Обработка исключений
Обработка исключений
Обработчики всех типов исключений
Обработчики всех типов исключений
Классы-исключения
Классы-исключения
Исключения и наследование
std::exception
std::exception
Исключения
Исключения
Перебросить исключение
Перебросить исключение
std::rethrow_exception
Исключения пользовательских типов
Спецификатор noexcept
Спецификатор noexcept
Исключения в конструкторе
Исключения в деструкторе
6.85M
Категория: ПрограммированиеПрограммирование

Методы и стандарты программирования

1. Методы и стандарты программирования Семинары

МЕТОДЫ И СТАНДАРТЫ ПРОГРАММИРОВАНИЯ
СЕМИНАРЫ
II семестр

2. C и C++

2
C И C++
Семинар 1

3. Отчетность

3
Отчетность
Работа на семинарах + задачи
2. Две лабораторные работы (C++, Qt)
3. Экзамен
1.

4. Совместимость Си и C++

4
Совместимость Си и C++
• C++ создавался как потомок до стандартизированного C, по большей
части совместимый с ним на тот момент на уровне исходного кода и
компоновки.
• Средства разработки для обоих языков (такие, как среды разработки и
компиляторы) часто интегрируются в один продукт.
• C не является подмножеством C++, поэтому нетривиальные программы
на C могут не компилироваться на C++ без изменений.
• C++ вводит множество возможностей, недоступных в C, и на практике
почти весь код, написанный на C++, не соответствует коду на C.

5. Стандарты C++

5
Стандарты C++

6. Общие различия

6
Общие различия
C
C++
Код читается проще
Код выглядит сложнее
Проще портировать код на другие платформы
Из-за особенностей языка код портировать сложнее
Есть структуры и переменные типа «структура»
Есть классы и объекты
Ориентирован на структурное и процедурное
программирование
ООП
Не поддерживаем инкапсуляцию, данные могут
редактироваться внешним кодом
Инкапсуляция скрывает данные, чтобы гарантировать, что
структуры данных и операторы используются по назначению
Исключения нужно конструировать самому
Есть обработка исключений
Не поддерживает перегрузку функций и операторов
Поддерживает перегрузку функций и операторов, а также
пространства имен и ссылки, обработка исключений
Почти всё стандартно и предсказуемо
Легко написать код, в котором непонятно, где ошибка

7. Типы данных

7
Типы данных
Запрещается делать массивы переменной длины (Variable
Length Arrays, VLA).
Тип перечислений в C это –int, в C++ – это тип самого
перечисления.

8. Приведение типов

8
Приведение типов
Язык C++ сильнее типизирован, нежели C, т.е. данные
конструкции скомпилируется на C, а вот на C++ нет.
void* ptr;
int* i_ptr = ptr;
int* imass = malloc(128 * sizeof(int));
В C++ необходимо использовать явное приведение типа.
int* imass = (int*)malloc(128 * sizeof(int));
но в таком варианте приведения компилятор выдаст предупреждение

9. Warning: use of old-style cast

9
Warning: use of old-style cast
int* imass = (int*)malloc(128 * sizeof(int));
int* imass = static_cast<int*>(malloc(128 * sizeof(int)));
Для корректного приведения типов есть следующие операторы приведения:
• static_cast<T>(e) – преобразование выражения одного статического типа в значение
другого статического типа;
• const_cast<T>(e) – отбросить квалификатор const у изначально не константных
данных или добавить квалификатор const;
• reinterpret_cast<T>(e) – приведение типов без проверки, т.е. непосредственное
указание компилятору, используется только в случае полной уверенности
программиста в собственных действиях;
• dynamic_cast<T>(e) – преобразует тип данных (указатель или ссылку) вниз по
иерархии наследования (из типа-предка в тип-потомок), с проверкой с помощью
динамической идентификации типа данных.

10. Функции стандартной библиотеки С

10
Функции стандартной библиотеки С
C++ изменяет некоторые функции стандартной библиотеки
языка Си, добавляя дополнительные перегруженные
функции с квалификатором типа const, например, strchr
возвращает char* в C, в то время как C++ поступает так, как
если бы существовали две перегруженные функции:
const char *strchr(const char *)
char *strchr(char *)

11. Объекты с квалификатором const

11
Объекты с квалификатором const
typedef struct
{
const int i = 65;
} Dot;
C–❌
Инициализировать
можно только при
создании объекта!
C++ – ✅

12. Символьные литералы

12
Символьные литералы
Символьные литералы, такие как 'a', имеют тип int в C и тип
char в C++, это означает, что sizeof 'a' обычно даёт разные
результаты на двух языках: в C++ это будет 1, в то время как
в C это будет sizeof(int).
Как ещё одно следствие этого различия в типах, в C 'a' всегда
будет выражением со знаком, независимо от того, является
char знаковым или беззнаковым, тогда как для C++ это
зависит от реализации компилятора.

13. Логический тип bool

13
Логический тип bool
И C99, и C++ имеют логический тип bool с константами true
и false, но они определены по-разному. В C++ bool — это
встроенный тип и зарезервированное ключевое слово.
В C99 новое ключевое слово _Bool вводится как новый
логический тип. Заголовок stdbool.h содержит макросы bool,
true и false, которые определены как _Bool, 1 и 0,
соответственно. Следовательно, true и false имеют тип int в
C.

14. Программа на языке C++

14
Программа на языке C++
// подключаем заголовочный файл iostream
#include <iostream>
int main() // определяем функцию main
{
// выводим строку на консоль
std::cout << "Hello World!" << std::endl;
return 0;
}

15. Изменения по сравнению с Си и работой в прошлом семестре

15
Изменения по сравнению с Си и работой в прошлом семестре
• Компилятор: gcc g++
• Расширения файлов: c cpp, h hpp/h (при этом
расширение hpp используется довольно редко)

16. Ввод/вывод C++

16
Ввод/вывод C++
Функции ввода/вывода языка C++ находятся в заголовочном
файле iostream.
• std::cout – поток стандартного вывода;
• std::cerr – поток стандартного вывода ошибок;
• std::cin – поток стандартного ввода.
Для работы с потоком вывода используется оператор <<, с
потоком ввода >>.
Потоки ввода/вывода работают с любыми
фундаментальными типами

17. Ввод/вывод C++

17
Ввод/вывод C++
printf(…) stdout std::cout <<
scanf(…) stdin std::cin >>
stderr std::cerr
#include <iostream>
int main(int argc, char **argv)
{
int i = 0;
double d = 0.;
char ch = 0;
Вывод:
299 0.1234555 7
i = 299 d = 0.123455 ch = 7
error: 299
Вывод:
a 0.1 c
i = 0 d = 0 ch =
error: 0
std::cin >> i >> d >> ch;
std::cout << "i = " << i << " d = " << d << " ch = " << ch << "\n";
std::cerr << "error: " << i << "\n";
return 0;
}

18. Манипуляторы ввода/вывода

18
Манипуляторы ввода/вывода
• std::endl – перевод строки ~ \n;
• std::setprecision(int) – установка точности вывода вещественных
типов (количество знаков);
• std::scienti c – “научный” формат вывода вещественных чисел
(1.000000e-02);
• std::fixed – вывод вещественного числа с фиксированной точкой;
• std::hex – ввод/вывод данных в шестнадцатеричной системе;
• std::oct – ввод/вывод данных в восьмеричной форме;
• std::setw(int w) – установка ширины поля равной w;
• остальные можно посмотреть в документации (<iomanip>).

19. Пример использования манипуляторов IO

19
Пример использования манипуляторов IO
#include <iostream>
#include <iomanip>
int main(int argc, char **argv)
{
int code = 120;
std::cout << code << " " << std::hex << code << " " << std::oct << code << std::endl;
double _pi = 3.14159265359;
std::cout << std::setprecision(4) << _pi << " " << std::scientific << _pi << std::endl;
std::cout << std::fixed << std::setw(6) << std::setprecision(2) << _pi << std::endl;
return 0;
}
Вывод:
120 78 170
3.142 3.1416e+00
3.14

20. Динамическое выделение памяти

20
Динамическое выделение памяти
#include <iostream>
• Выделение памяти:
new, new[]
• Очистка памяти:
delete, delete[]
Вывод:
3
[0]0 [1]1 [2]4 [3]9 [4]16 [5]25
[6]36 [7]49 [8]64 [9]81
int main(int argc, char** argv)
{
int* i = new int;
*i = 3;
std::cout << *i << std::endl;
delete i;
const size_t arr_size = 10;
int* mass = new int[arr_size];
for (size_t i = 0; i < arr_size; ++i)
{
mass[i] = i * i;
std::cout << "[" << i << "]" << mass[i] << " ";
}
delete[] mass;
return 0;
}

21. Пространства имен

21
Пространства имен
– это область определения переменных, типов, констант и
функций, объединенная в один или несколько блоков.
Позволяет избежать конфликта имен, а также повторно
использовать одни и те же именования переменных, функций
и т.д.
namespace Name
{
// Объявление переменных, констант, типов, функций
}

22. Пространства имен

22
Пространства имен
Пространство имен может быть безымянным, что обычно
используется для сокрытия каких-либо определений внутри одного
модуля.
Пространства имен могут быть вложенными и повторяться в
разных модулях (файлах).
// доступ к элементу ПИ
namespace MATH {
double PI = 3.14;
}
std::cout << MATH::PI << std::endl;

23. using

23
using
Доступ к элементам может быть упрощен с помощью
использования ключевого слова using, примененного либо к
пространству имен целиком, либо же к отдельному его
элементу:
// Позволит не указывать везде в коде std::
using namespace std;
// Позволит не указывать std:: для функции cin
using std::cin;

24. Доступ к элементам

24
Доступ к элементам
Элементы, объявленные вне пространства имен,
принадлежат к глобальному пространству имен, и при
совпадении имен доступ к ним осуществляется следующим
образом:
void sqrt() {}
имен
namespace MATH {
void sqrt() {}
}
::sqrt();
MATH::sqrt();
sqrt();
// Функция, объявленная в глобальном пространстве
// Функция, объявленная в пространстве имен MATH
// Вызов функции из глобального пространства имен
// Вызов функции из пространства имен MATH
// Ошибка компиляции - неоднозначный вызов функции

25. Ссылки

25
Ссылки
В C роль ссылок играют указатели, но работать с ними
бывает не очень удобно, поэтому в C++ появилась отдельная
сущность — ссылка (reference).
Ссылка – если T некоторый тип и есть переменная типа T, то
переменная типа T со спецификатором & будет ссылкой на
эту переменную, если она инициализирована этой
переменной.
T x;
T &rx = x; // rx это ссылка на x

26. Ссылки

26
Ссылки
• не могут быть NULL;
• не требуют разыменования;
• не может быть ссылки на ссылку;
• нельзя итерироваться (смещаться), так как это не область
памяти.

27. Пример

27
Пример
#include <iostream>
void swap(int &a, int &b)
{
int t = a;
a = b;
b = t;
}
void swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
int main(int argc, char **argv)
{
int a = 10, b = -10;
std::cout << "Before: " << a << " " << b << std::endl;
swap(&a, &b);
std::cout << "After: " << a << " " << b << std::endl;
std::cout << "Before: " << a << " " << b << std::endl;
swap(a, b);
std::cout << "After: " << a << " " << b << std::endl;
return 0;
}
Вывод:
Before: 10 -10
After: -10 10
Before: -10 10
After: 10 -10

28. Задачи

28
Задачи
Написать функцию, меняющую значения двух
переданных переменных местами без использования
указателей
2. Написать функцию, возвращающую указатель на
динамический массив значений факториала N. Значение
N запрашивается у пользователя и вводится с клавиатуры.
Вывести полученный массив на экран. Очистить память.
1.

29. Введение в ООП

29
ВВЕДЕНИЕ В ООП
Семинар 2

30. Принципы ООП

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

31. Классы и объекты

31
Классы и объекты
Класс – логическая абстракция, которая реально не
существует до тех пор, пока не будет создан объект этого
класса, т.е. то, что станет физическим представлением этого
класса в памяти компьютера.
Cat cat; // создание объекта cat класса Cat

32. Конструкторы и деструкторы

32
Конструкторы и деструкторы
Конструктор вызывается при создании объекта класса. Как правило,
конструктор используется для:
• выделения памяти под объект класса;
• начальной инициализации внутренних данных класса.
Деструктор – специальный метод, который вызывается при удалении
объекта.
Деструктор – это обратная по отношению к конструктору функция.

33. Виды конструкторов

33
Виды конструкторов
• по умолчанию
• параметризованный
• копирования

34. Конструктор копирования

34
Конструктор копирования
В C++ полная копия объекта нужна в следующих случая:
1. В момент объявления нового объекта и его инициализации
данными другого объекта с помощью оператора =.
2. Когда нужно передать объект в функцию как параметрзначение.
3. Когда нужно вернуть объект из функции по значению. ! Если в
классе не объявлен конструктор копирования, то используется
конструктор копирования, который автоматически
генерируется компилятором. Этот конструктор копирования
реализует побитовое копирование для получения копии
объекта.

35. Деструктор

35
Деструктор
Имя деструктора совпадает с именем класса, перед которым
следует символ ‘~’.
Деструктор не может иметь параметров.
При объявлении класса, можно объявить только один
деструктор.

36. Rule of tree / rule of five

36
Rule of tree / rule of five
Один из основополагающих принципов C++ – RAII
(Resource Acquisition Is Initialization — “получение ресурса
есть инициализация”).

37. Rule of tree / rule of five

37
Rule of tree / rule of five
Правило трёх (rule of three, закон
«Большой тройки»). Если классу
необходим один из следующих
методов:
деструктор,
конструктор копирования,
оператор копирующего
присваивания,
то класс должен определять все три.
С выходом С++11 правило трёх
превратилось в правило пяти (rule of
five): если классу необходим один из
следующих методов:
• деструктор,
• конструктор копирования,
• оператор копирующего
присваивания,
• конструктор перемещения,
• оператор перемещающего
присваивания,
то класс должен определять все пять.

38. Инкапсуляция

38
Инкапсуляция
Для типа struct все данные и методы по умолчанию public.
Для типа class все данные и методы по умолчанию private.
В C++ доступно несколько спецификаторов, и они изменяют
доступ к данным следующим образом:
• публичные (public) – данные доступны всем;
• защищенные (protected) – доступны только классу и дочерним
классам;
• приватные (private) – доступны только классу которому они
принадлежат.

39. Инкапсуляция

39
Инкапсуляция

40. Полиморфизм

40
Полиморфизм
В языке C++ есть возможность реализовывать два вида
полиморфизма:
1. Статический. Этот вид полиморфизма достигается путём
использования перегруженных функций (раннее
связывание).
2. Динамический. В этом случае используется наследование
в сочетании с виртуальными функциями (позднее
связывание).

41. Статический полиморфизм

41
Статический полиморфизм
«Перегрузка» функции – это объявление функции с тем же именем несколько раз. Чтобы
компилятор мог отличать «перегруженные» функции между собой, эти функции должны
отличаться списком входных параметров.
В общем случае, объявление перегруженной функции в некоторой области видимости
выглядит следующим образом:
return_type1 FunName(parameters_list_1)
{
// ...
}
return_type2 FunName(parameters_list_2)
{
// ...
}
return_typeN FunName(parameters_list_N)
{
// ...
}

42. Статический полиморфизм

42
Статический полиморфизм

43. Пример перегрузки конструкторов в классе

43
Пример перегрузки конструкторов в классе

44. Списки инициализации членов класса

44
Списки инициализации членов класса
В данном примере в
конструкторе
используется
присваивание, а не
инициализация.

45. Списки инициализации членов класса

45
Списки инициализации членов класса
Если в классе Point2d появится
константное поле, то его
необходимо будет
проинициализировать либо при
создании объекта, либо через
список инициализации.
Поскольку разрешено
инициализировать константные
переменные (но не присваивать
им значения!).

46. Списки инициализации членов класса

46
Списки инициализации членов класса
Списки инициализации не
только позволяют
инициализировать
константы, но и работают
эффективнее (не
выполняется копирующая
инициализация).

47. Списки инициализации членов класса

47
Списки инициализации членов класса
Если член класса –
массив, то список
инициализации
приобретает
следующий вид.

48. Задачи

48
Задачи
1. Реализовать класс Human:
Конструкторы: Human(string name); Human(string name, int age);
Функции: printAge(), printName();
2. Реализовать класс Children
Определить класс Children с правильной декомпозицией по файлам.
Класс содержит такие закрытые поля: имя, фамилия, возраст.
Реализовать функции ввода данных и вывода их на экран. (для каждого поля своя функция)
Объявить 2 объекта класса, внести данные и показать их.
Все поля классов Human и Children должны быть реализованы с использованием
динамического выделения памяти(указателей). Необходимо обеспечить корректное
освобождение ресурсов при завершении работы программы.(деструкторы)

49. Задача*

49
Задача*
Реализовать в собственном пространстве имен класс RGBA (конструктор по умолчанию (черный цвет), с
параметрами), хранящий в себе настройки цвета. Реализовать функции:
• setColor(r, g, b, a = 255) – принимает все значения цвета и устанавливает их (alpha по
умолчанию 255)
• setColor() – не принимает параметров, устанавливает всем цветам значение 0
• red(), green(), blue(), alpha() – возвращают значения конкретных цветов (константность)
• print(enum PrintType = E_RGBA) – печать на экран значений в соответствии с переданным
типом
• E_RGBA: в строку (Пример: 255 255 255 255)
• E_HEX: в hex-формате (Пример: `#ffffffff`)
⁎ E_INT: в числовом формате (int, побитовый сдвиг)
• sum(const &RGBA) – сложение цвета с переданным цветом, возвращает новый объект RGBA.
Декомпозировать реализацию на файлы, в функции main создать объект класса, протестировать все
реализованные функции.

50. Классы

КЛАССЫ
Семинар 3

51. Вспоминаем

51
Вспоминаем
• Ссылки: для чего, отличия от указателей
• Чем классы отличаются от структур?
• Классы: инициализация и освобождение. Какая концепция C++?
• Виды конструкторов, их применение
• Правило при написании конструкторов/деструкторов
• Что будет если не написать конструкторы или деструктор?
• В каком пространстве имен находятся методы класса?
• Пример статического полиморфизма
• Для чего нужны списки инициализации?

52. Конструктор копирования

52

53. Оператор копирующего присваивания

53

54. Главная разница

54
Главная разница

55. Тонкости С++

55
Тонкости С++

56. Задача 1

56
Задача 1
1. Реализовать класс Matrix для хранения прямоугольной матрицы вещественных чисел типа double в динамической
памяти. Реализовать:
Приватные поля:
• rows – количество строк.
• Cols – количество столбцов.
• Data – указатель на динамический массив элементов матрицы.
Функции:
• Matrix(int rows, int cols, double value) – конструктор, создает матрицу заданного размера и заполняет
значениями value.
• ~Matrix() – деструктор класса.
• set(int row, int col, double value) – устанавливает на заданную позицию значение value.
• get(int row, int col) – возвращает значение элемента матрицы на заданной позиции.
• getRows() – возвращает количество строк в матрице.
• getCols() – возвращает количество столбцов.
• print() – выводит матрицу на экран в виде таблицы.
Реализовать для класса Matrix конструкторы копирования и копирующего присваивания.
Декомпозировать реализацию на файлы(Matrix.h, Matrix.cpp, main.cpp) в функции main создать объекты класса и
протестировать все реализованные функции.

57. Дополнительная задача

57
Дополнительная задача
Реализовать в собственном пространстве имен класс односвязного списка ForwardList,
хранящий значения типа double. Реализовать:
• внутреннюю структуру узла Node
• push_front(double) – добавление элемента в начало списка
• pop_front() – удаляет первый элемент
• front() – возврат ссылки на первый элемент (перегрузка)
• size() – возврат количества элементов в списке
• empty() – проверяет, пуст ли список
• display() – вывод списка на экран
• erase(double) – удаляет элементы с переданным значением
• clear() – очистка и освобождение памяти
Декомпозировать реализацию на файлы, в функции main создать объект класса,
протестировать все реализованные функции.

58. Перегрузка операторов

ПЕРЕГРУЗКА ОПЕРАТОРОВ
Семинар 4

59. Правило трех

59
Правило трех
В чем оно заключается?
class ForwardList
{
Node *head;
public:
ForwardList(); // default constuctor
ForwardList(const ForwardList&); // copy constructor
~ForwardList(); // destructor
// ...
};

60. Оператор копирующего присваивания

60
Оператор копирующего присваивания
class ForwardList
{
public:
ForwardList();
ForwardList(const ForwardList&);
??? operator=(const ForwardList& list) {
Node *current = list.head;
while (current) {
// ...
}
}
~ForwardList();
};

61. Оператор копирующего присваивания

61
Оператор копирующего присваивания
class ForwardList
{
public:
ForwardList();
ForwardList(const ForwardList&);
ForwardList& operator=(const ForwardList& list) {
Node *current = list.head;
while (current) {
// ...
}
return *this;
}
~ForwardList();
};

62. Safe for self-assignment

62
Safe for self-assignment
class ForwardList
{
public:
ForwardList();
ForwardList(const ForwardList&);
ForwardList& operator=(const ForwardList& list) {
if (this == &list)
return *this;
// ...
return *this;
}
~ForwardList();
// ...
};
C.62: Make copy assignment safe for self-assignment

63. Перегрузка операторов (operator overloading)

63
Перегрузка операторов (operator overloading)
• возможность применять встроенные операторы языка к
разным типам, в том числе и пользовательским;
• повышает читаемость кода, делает его более лаконичным и
выразительным, упрощает написание обобщенного кода;
• в C++ перегрузка операторов имеет серьезную поддержку и
активно используется в стандартной библиотеке.

64. Перегружаемые операторы

64
Перегружаемые операторы
Если не рассматривать операторы
управления памятью (new, new[],
delete, delete[]), то условно
остальные можно разделить на
следующие типы:
• унарные (один параметр)
• бинарные (два параметра)
• операторы +, -, *, &, ++, -- (оба
варианта)
• оператор () (произвольное
число параметров)

65. Перегружаемые операторы

65
Перегружаемые операторы
• Необходимо учитывать приоритет и ассоциативность операторов
• Не рекомендуется перегружать следующие три бинарных
оператора: , (запятая), &&, ||. Стандарт для них
предусматривает порядок вычислений, а для последних двух –
семантику быстрых вычислений (short-circuit evaluation), что не
гарантируется для перегруженных операторов.
• Не рекомендуется перегружать унарный оператор & (взятие
адреса). Тип с перегруженным оператором & опасно использовать
с шаблонами, так как они могут использовать стандартную
семантику этого оператора (std::addressof() с C++11 может
помочь).

66. Реализация перегрузки операторов

66
Реализация перегрузки операторов
Операторы можно перегружать в двух вариантах: как
функцию-член класса и как свободную функцию.
Четыре оператора можно перегрузить только как функциючлен класса: =, ->, [], ().
Для перечислений, операторы можно перегружать только как
свободные функции.
Операторы << и >> нельзя перегрузить через функцию-член
класса.

67. Перегрузка оператора как функция-член класса

67
Перегрузка оператора как функция-член класса
• объявить нестатическую
функцию-член
return_type operator#()
• в случае перегрузки унарного
оператора эта функция не
должна иметь параметров,
• в случае бинарного должна
иметь ровно один параметр,
• в случае перегрузки оператора
() эта функция может иметь
произвольное число
параметров.
class Complex
{
public:
Complex(int r = 0, int i = 0) {
real = r;
img = i;
}
// return_type operator#(operands)
Complex operator+(const Complex &obj) {
Complex res;
res.real = real + obj.real;
res.img = img + obj.img;
return res;
}
void print() {
std::cout << real << " " << img << std::endl;
}
private:
int real, img;
};

68. Перегрузка оператора как функция-член класса

68
Перегрузка оператора как функция-член класса
int main(int argc, char **argv)
{
Complex comp1(4, -2), comp2(3, 5);
Complex comp3 = comp1 + comp2;
comp3.print();
return 0;
}
Вызов можно представить следующим
образом:
comp1.operator+(comp2)
class Complex
{
public:
Complex(int r = 0, int i = 0) {
real = r;
img = i;
}
// return_type operator#(operands)
Complex operator+(const Complex &obj) {
Complex res;
res.real = real + obj.real;
res.img = img + obj.img;
return res;
}
void print() {
std::cout << real << " " << img << std::endl;
}
private:
int real, img;
};

69. Выражение операторов в терминах других

69
Выражение операторов в терминах других
class Complex
{
public:
Complex(int r = 0, int i = 0) : real(r), img(i) {}
Complex& operator+=(const Complex &obj) {
real += obj.real;
img += obj.img;
return *this;
}
Complex operator+(const Complex &obj) const {
return Complex(*this) += obj;
}
private:
int real, img;
};

70. Приведение типов и конструкторы

70
Приведение типов и конструкторы
struct Rational {
int num;
int denum;
Rational(int n = 0, int d = 1) : num(n), denum(d) {}
};
int main()
{
Rational r1(1, 2);
Rational r2 = 2;
return 0;
}

71. Приведение типов и конструкторы

71
Приведение типов и конструкторы
struct Rational {
int num;
int denum;
Rational(int n = 0, int d = 1) : num(n), denum(d) {}
};
int main()
{
Rational r1(1, 2); // OK
Rational r2 = 2;
return 0;
}

72. Приведение типов и конструкторы

72
Приведение типов и конструкторы
struct Rational {
int num;
int denum;
Rational(int n = 0, int d = 1) : num(n), denum(d) {}
};
int main()
{
Rational r1(1, 2); // OK
Rational r2 = 2; // OK
return 0;
}

73. Ключевое слово explicit

73
Ключевое слово explicit
struct Rational {
int num;
int denum;
explicit Rational(int n = 0, int d = 1) : num(n), denum(d) {}
};
int main()
{
Rational r1(1, 2); // OK
Rational r2 = 2; // ERROR (no suitable constructor exists to convert from "int" to "Rational")
return 0;
}

74. Ключевое слово explicit

74
Ключевое слово explicit
• Ключевое слово explicit используется для предотвращения
неявных преобразований типов
• Применяется к конструкторам, которые принимают один
аргумент (или несколько аргументов, но только один из них
может быть неявно преобразован)
• Избегает неожиданных преобразований типов, которые
могут привести к ошибкам

75. Перегрузка оператора как функция-член класса

75
Перегрузка оператора как функция-член класса

76. Перегрузка оператора как функция-член класса

76
Перегрузка оператора как функция-член класса
Если преобразование типов
должно быть применимо ко всем
параметрам функции (включая и
скрытый параметр this), то
функция не должна быть членом
класса.

77. Операторы << и >>

77
Операторы << и >>
std::ostream& operator<<(std::ostream &out, const Complex &obj);
• При перегрузке через метод класса в качестве левого операнда
используется текущий объект. В этом случае левым операндом является
объект типа std::ostream.
• Мы не можем использовать std::ostream в качестве левого неявного
параметра, на который бы указывал скрытый указатель *this, так как
указатель *this может указывать только на текущий объект текущего
класса, члены которого мы можем изменить, поэтому перегрузка
оператора << может осуществляться через дружественную функцию.

78. Перегрузка оператора как свободная функция

78
Перегрузка оператора как свободная функция
• в случае перегрузки унарного оператора,
эта функция должна иметь один
параметр,
• в случае бинарного должна иметь два
параметра,
• в случае перегрузки бинарного оператора
– по крайней мере один из двух
параметров, а в случае унарного
единственный параметр, должен быть
того же типа (или типа ссылки), что и
тип, для которого реализуется перегрузка,
• функция должна находится в том же
пространстве имен, что и тип, для
которого реализуется перегрузка.

79. Пример

79
Пример

80. Правила перегрузки операторов

80
Правила перегрузки операторов
• тщательно продумывайте перегрузку операторов. Она должна повысить
наглядность и читаемость кода, но не наоборот;
• для унарных операторов используйте перегрузку через методы-класса;
• для бинарных операторов, которые изменяют левый операнд
(operator+= и т.д.), используйте перегрузку через методы класса,
если это возможно;
• для бинарных операторов, которые не изменяют левый операнд
(operator +() и т.д.), используйте перегрузку через
обычные/дружественные функции;
• eсли мы хотим реализовать обычные бинарные арифметические
операторы, удобнее сначала будет реализовать: += *= -= /= %=

81. Подробнее

81
Подробнее
English     Русский Правила