notes

Структуры и классы

Структуры

  1. Структуры - это способ синтаксически (и физически) сгруппировать логически связанные данные
     struct Point {
         double x;
         double y;
     };
     // | double | double |
    
     struct Segment {
         Point p1;
         Point p2;
     };
     // | Point | Point |
     // | x | y | x | y |
    
  2. Доступ к полям структуры осуществляется через оператор .
     #include <cmath>
    
     double length (Segment s) {
         double dx = s.p1.x - s.p2.x;
         double dy = s.p1.y - s.p2.y;
         return sqrt (dx * dx + dy * dy);
     }
    
  3. Для указателей на структуры используется оператор ->.
     double length (Segment * s) {
         double dx = s->p1.x - s->p2.x;
         double dy = s->p1.y - s->p2.y;
         return sqrt (dx * dx + dy * dy);
     }
    
  4. Инициализация структур
    • Поля структур можно инициализировать подобно массивам
        Point p1 = { 0.5, 0.5 };
        Point p2 = { 1.2, 6.3 };
        Segment s = { p1, p2 };
      
    • Структуры могут хранить переменные разных типов
        struct IntArray2D {
            size_t a;
            size_t b;
            int ** data;
        };
      
        IntArray2D a = { n, m, create_array2d(n, m) };
      
  5. Методы реализованны как функции с неявным параметром this, который указывает на текущий объект.
     // point.hpp
     struct Point {
         double x;
         double y;
    
         void shift (double x, double y);
     };
    
     // point.cpp
     void Point::shift(double x, double y) {
             this->x += x;
             this->y += y;
     }
    

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

  1. Конструкторы это методы для инициализации структур
     struct Point {
         double x;
         double y;
    
         Point () {
             x = 0;
             y = 0;
         }
    
         Point (double x, double y) {
             this->x = x;
             this->y = y;
         }
     };
    
     Point p1; // {0, 0};
     Point p2(3,7);
    
  2. Список инициализации позволяет проинициализировать поля до входа в конструктор
     struct Point {
         double x;
         double y;
         Point () : x(0), y(0) {}
    
         Point (double x, double y) : x(x), y(y) {}
     };
    
    • Инициализация полей в списке происходит в порядке объявления полей в структуре
  3. Значения по умолчанию
    • Функции могут иметь значения параметров по умолчанию
    • Значения параметров по умолчанию нужно указывать в объявлении функции
       struct Point {
        double x;
        double y;
        Point (double x = 0, double y = 0)
            : x(x), y(y) {}
       };
      
  4. Конструкторы от одного параметра задают неявное пользовательское преобразование. Чтобы предотвратить его нужно каждый конструктор с одним параметром определять со словом explicit. cpp struct Point { double x; double y; explicit Point (double x = 0, double y = 0) : x(x), y(y) {} }; Point p = 12; // error (without explicit keyworkd this code works)
  5. Если у структуры нет конструкторов, то конструктор без параметров (конструктор по умолчанию), генерируется компилятором.

Особенности синтаксиса С++

  1. Если что-то похоже на объявление функции, то это и есть объявление функции
     Point p1; // определение переменной
     Point p2; // объявление функции
    
     double k = 5.1;
     Point p3( int(k) ); // объявление функции
     Point p4( (int)k ); // определение переменой
    

Деструктор

  1. Деструктор - это метод, который вызывается при удалении структуры, генерируется компилятором.
     struct IntArray {
         size_t size;
         int * data;
    
         explicit IntArray(size_t)
             : size(size),
             data(new int [size])
         { }
    
         ~IntArray() {
             delete [] data;
         }
     };
    
  2. Срабатывает:
    • при определении объекта на стеке - после выхода из зоны видимости
    • при определении объекта в куче - при вызове delete

Классы

  1. Единственное отличие классов от структур в том что в структурах по умолчанию все поля и методы публичные, в классах - приватные.
  2. Структуры принято использовать для POD (Plain Old Data) - связаные данные без методов, конструкторов и деструкторов. Для всего остального лучше использовать классы.

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

  1. Конструктор копирования срабатвыает когда в результате копирования создаётся новый объект.
    • создание нового объекта на основе существующего
        struct IntArray {
            ...
        private:
            size_t size_;
            int * data_;
        };
      
        int main() {
            IntArray a1(10);
            IntArray a2(20);
        }
      
    • передача объекта в функцию по значению
        void some_function(IntArray a) {
            ....
        }
      
        IntArray a = IntArray(4);
      
        some_function(a);
      
  2. Если конструктор копирования не переопределён, то компилятор сгенерирует этот конструктор. Сгенерированный конструктор будет содержать неглубокое копирование всех полей.
  3. Определение конструктора копирования
     struct IntArray {
         IntArray(IntArray const & a)
                 : size(a.size_), data_(new int[size_]) {
             for (size_t i = 0; i < size_; ++i) {
                 data_[i] = a.data_[i];
             }
         }
     };
    

Оператор присваивания

  1. В тех случаях когда копирование происходит в уже существующий объект вызывается оператор присваивания.
  2. Если оператор присваивания не определён, то компилятор сгенерирует его. Такая функция будет содержать неглубокое копирование всех полей объекта.
  3. Определение оператора присваивания
     struct IntArray {
         IntArray & operator=(IntArray const & a) {
             if (this != &a) {
                 delete [] data_;
                 size_ = a.size;
                 data_ = new int[size_];
                 for (size_t i = 0; i < size_; ++i) {
                     data_[i] = a.data_[i];
                 }
             }
    
             return *this;
         }
     }
    
  4. Оператор присваивания через swap (swap меняет поля текущего объекта и объекта в аргументе)
     #include <algorithm>
    
     struct IntArray {
         void swap(IntArray & a) {
             std::swap(size_, a.size_);
             std::swap(data_, a.data_);
         }
    
         IntArray(IntArray const & a)
                 : size(a.size_), data_(new int[size_]) {
             for (size_t i = 0; i < size_; ++i) {
                 data_[i] = a.data_[i];
             }
         }
    
         IntArray & operator=(IntArray const & a) {
             if (this != &a) {
                 IntArray(a).swap(*this);
             }
    
             return *this;
         }
     }
    

Best practise

  1. Чтобы запретить копирование нужно объявить конструктор копирования и оператор присваивания как private. При этом их не нужно определять.
  2. Компилятор генерирует четыре метода:
    • конструктор по умолчанию
    • конструктор копирования
    • оператор присваивания
    • деструктор
  3. Если потребовалось переопределить конструктор копирования, оператор присваивания или деструктор, то нужно переопределить и остальные методы из этого списка.