Skip to content

bmstu-iu8-cpp-sem-2/lab-01-string

Repository files navigation

Лабораторная работа № 1

Цель

Главная цель данной лабораторной работы научиться создавать пользовательские классы. На простом примере следует изучить как устроены класс, как создавать методы класса, что такое инкапсуляция, правильно реализовывать конструкторы копирования, операторы присваивания и деструктор.

Теоретическая справка

Класс представляет составной тип. Класс в С++ сильно напоминает структуру в С. Более того, класс и структура в С++, ничем практически не отличатся. Единственное отличие в модификаторах доступа по умолчанию. В классе по умолчанию все поля закрытые (private), в структурах открытые (public)

Следует различать понятия класс и объект (экземпляр класса)

Класс описывает свойства и методы, которые будут доступны у объекта, построенного по описанию, которое заложено в классе. Э

Объект представляет конкретное воплощение класса.

Пример
std::string name;

std::string - это класс;

name - это объект. Экземпляр класса std::string

Поля класса

Класс представляет составной тип. Класс, как и структура, может содержать дополнительные поля, члены класса.

Члены класса имеют разный модификатор доступа: private, public, protected.

  • private - член класса доступен только самому классу. Из вне доступа к этому полю нет. Если попытаться обратиться к закрытому полю, в коде вашей программы, то возникнет ошибка времени компиляции.
  • public - член класса доступен как самому классу, так внешнему по отношению к классу коду.
  • protected - будет рассмотрен в теме про наследование.

Friend

Дружественная (friend) функция — это функция, которая не является членом класса, но имеет доступ к членам класса, объявленным в полях private или protected.

Дружественные функции (или классы) нужно использовать в нескольких случаях, а именно только в случае сильной связности компонент. Чаще всего это два случая:

  • unit-test. Проверять иногда требуется и те только public методы. Для этого тест объявляется дружественным
  • сильная связность компонентов/классов. Например, итератор и контейнер. Итератор без контейнера вряд ли может существовать. И поэтому итератор иногда (не всегда) можно объявлять как дружественный к контейнеру.

Никогда не используйте friend чтобы нарушить инкапсуляцию.

Указатель this

Ключевое слово this представляет указатель на текущий объект данного класса. Соответственно через this мы можем обращаться внутри класса к любым его членам (зачастую это НЕ требуется делать). Явное использование this затрудняет понимание вашего кода. Использовать this явно советуется только при разработке шаблонов класса и в ограниченном случае.

ВАЖНО

Избегайте ситуаций следующего вида:

class Point {
  float x;
  float y;
  Point(float x, float y) {
    this->x = x;
    this->y = y;
  }
};

В примере выше имена полей класса и имена аргументов совпадают. И разработчику приходится явно использовать ключевое слово this, чтобы решить неясность в коде кто кому присвоится. Использование слова this не является идеальным решением проблемы. Лучшее решение - избавиться от дублирующих имен.

Методы класса

Чтобы взаимодействовать с классом используют методы класса - функции, которые имеют доступ к закрытым полям класса.

Методы бывают константными и не константными. Константный метод не может изменять поля класса. Такие методы могут только читать поля (исключением составляют mutable поля).

Если концептуально метод не должен изменять класс, то его следует объявлять константным! Например, методы size() у класса std::string не должен никак менять саму строку, поэтому он объявляется константным:

size_t string::size() const;

В каждый не статический метод класса первым аргументом неявно передается указатель на текущий объект (тот самый this). Именно благодаря этому, программа понимают к какому именно объекту идет обращение.

// первым аргументом неявно (поэтому и закомментирован) передается неизменяемый указатель на объект класса.
void string::push_back( /* std::string* const this, */ char ch ); 

Замечание

Отметим, что this - это неизменяемый указатель (MyClass *const), а не указатель на неизменяемый объект (const MyClass*).

Кстати, если метод является константным, то this будет неизменяемым указателем на константный объект (const MyClass* const)

Конструкторы

Конструктор - специальный метод, который вызывается при создании объекта класса. В нем принято инициализировать поля класса.

По сути конструктор представляет функцию, которая может принимать параметры, и которая должна называться по имени класса.

Есть несколько видов конструкторов:

  1. конструктор по умолчанию (не принимает никаких аргументов) string::string()
  2. конструктор копирования (принимает константную ссылку на объект такого же класса) string::string(const string&)
  3. конструктор перемещения (принимает rvalue ссылку на объект такого же класса) string::string(string&&). Рассмотрен будет в 3 семестре
  4. пользовательский конструктор. Объявляется самим разработчиком, принимает любое количество аргументов и разных типов.

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

Список инициализации

Подробнее про список инициализации можно прочитать по ссылке - https://ravesli.com/urok-117-spisok-initsializatsii-chlenov-klassa/ Здесь мы отметим, что предпочтительнее использовать список инициализации полей класса, а не инициализация в теле конструктора.

Таким образом, используйте вариант

Student(const std::string& name, const std::string& group_id)
  : name_(name), group_id_(group_id)
{}

Вариант с копированием в теле, менее предпочтительный

Student(const std::string& name, const std::string& group_id) {
  name_ = name;
  group_id_ = group_id;
}

Деструктор

Деструктор - специальный метод, который вызывается при уничтожении объекта. В деструкторе требуется освобождать выделенные ресурсы, которые требовались для работы объекта, например, динамическая память и прочие ресурсы, требующие освобождения.

На заметку студенту

Студенты очень любят писать следующий код:

~MyClass() {
  var_a = 0;
  var_b = 0;
  ptr = nullptr;
  // ...
}

Т.е. студенты по какой-то причине считают, что есть скрытый смысл в обнулении полей класса. В большинстве случае это лишь захламляет код. Не надо так делать, без сильных обоснований.

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

В качестве "синтаксического сахара" в С++ разрешена перегрузка операторов класса. Чаще всего к перегрузке прибегают для удобства разработчика. Правда же соединить две строки удобнее и выразительнее через оператор +, чем прибегать к какому-то специальному методу.

В перегрузках операторов особняком стоят перегрузка присваивания operator=.

Перегрузка присваивания

Эти операторы вызываются, когда разработчику требуется присвоить один объект другому.

name_one = name_two;

Есть два вида таких операторов:

  1. Оператор присваивания (или копирования) - присваивает правый операнд левому. Принимает константную ссылку на объект. Возвращает ссылку на левый операнд.
string& operator=(const string& oth);

Возвращает ссылку на левый операнд, чтобы возможно было написать код вида a = b = c = d;.

Так же в реализации этого оператора принято проверять, что левый и правый операнды не являются одним и тем же объектом.

string name;
string& ref = name;
name = ref;

Типичный оператор присваивания выглядит так:

ClassName& operator=(const ClassName& rhs) {
  if (&rhs != this) {
    // реализация присваивания.
    ...
  }
  return *this;
}
  1. Оператор перемещения - перемещает правый операнд в левый. Будет рассмотрен в следующем семестре.

ВАЖНО

Операторы присваивания так же могут быть сгенерированы компилятором (не факт, что корректно). Про генерацию операторов компилятором читайте правило трёх и правило пяти!

Перегрузка остальных операторов

В перегрузке операторов для класса нет ничего сложного или необычного. Оставим на самостоятельное рассмотрение - https://metanit.com/cpp/tutorial/5.14.php

Единственное замечание, которое необходимо дать. Не стоит злоупотреблять перегрузкой операторов. И не надо использовать операторы класса для специфичных задач. Т.е. если вы реализуете свой класс и для него концептуально не применимы различные операторы, то не надо их перегружать. Например, для класса "строки" сложно определить оператор вычитания, поэтому не надо реализовывать этот оператор. Как использовать перегруженные вами операторы для класса должно быть понятно не только вам, но и вашим коллегам. Поэтому настоятельно просим избегать ситуаций, когда только вы знаете, что делает перегруженный оператор.

Задание

  1. Реализуйте класс String. Интерфейс класса объявлен в string.hpp.
  2. Реализованный класс должен проходить unit-тесты
  3. Разработайте программу для демонстрации работы вашего класса String (в функции main вызовите все реализованные вами функции и методы)

Полезные ссылки

About

No description, website, or topics provided.

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors