hse-oimp.github.io

Лекция 7. Namespace. Классы.

Namespace

namespace можно представлять как некую фамилию для переменной, функции, класса и т.д. По умолчанию все лежит в глобальном namespace.

Как задать свой namespace:

namespace hse {
    void f() {
    }
}

Чтобы обратиться к заданной выше f, нужно написать:

hse::f();

Вместо этого можно добавить namespace в global namespace (но так делать не стоит, так как могут наложиться названия):

using namespace hse; // Не стоит так делать
f();

Вместо этого лучше добавлять в global namespace только нужные имена:

using std::vector; // Добавлять весь namespace std плохо, так как он займет большое количество имен

Namespace можно объявлять и внутри другого namespace. Таким образом, они будут образовывать дерево.

namespace hse {
    namespace hse2 {
        void f() {
        }
    }
}

hse::hse2::f();

Другой вариант обратиться к f():

using namespace hse;
hse2::f();

Обратиться к global namespace:

::f(); // Вызывает f из глобального namespace

Анонимный namespace - доступен только внутри данного cpp файла:

namespace {
}

using/typedef

Позволяет дать типу второе имя

using Numbers = std::vector<int>;
typedef std::vector<int> Numbers;

Полезно, чтобы определять длинные типы. При этом typedef является устаревшией версией, в отличии от using, не поддерживает работу с шаблонами, поэтому всегда стоит использовать using.

Директивы препроцессора (#define)

#define просто подставляет вместо одного имени другое. Лучше не использовать, так как меньше защита от ошибок, чем при использовании using.

Виды директив:

#pragma once // Следующее инструкция выполняется, только если уже не была выполнена в другом файле
#include <string> // По умолчанию можно случайно подключить одинаковые библиотеки, так как директива просто подставляет код

#ifndef и #ifdef позволяют создавать условия (например, подключать библиотеку, только если еще не подключена), но вместо них лучше использовать #pragma once

Классы

Классы стоит использовать, если кроме хранения данных в одном месте, требуется еще и логика работы с ними. Для просто хранения данных лучше использовать struct.

  1. Данные - это состояние класса
  2. Методы - это поведение класса

В отличии от структуры, в классе все поля по умолчанию private, то есть доступны только из этого класса (это их единственное отличие).

  1. public: - делает все дальше публичным, то есть доступным не только из класса(вплоть до конца класса или следующего слова из этих трех)
  2. private: - делает все дальше приватным, то есть доступным только из самого класса
  3. protected: - делает все дальше доступным только из самого класса и из его наследников

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

Конструктор позволяет задавать состояние класса при создании (можно создавать конструкторы как с параметрами, так и без):

class Array {
public:
    Array(size_t size) {
        this->data = new int[size]();
        this->size = size;
    }
private:
    int* data = nullptr;  // Внутри класса эти поля можно использовать в любом порядке
    size_t size = 0;
};

this - внутри класса возвращает указатель на этот класс. Внутри константного метода возвращает константный указатель.

Ключевое слово explicit - запрещает вызывать конструктор с неявным преобразование аргументов.

class Array {
public:
    explicit Array(size_t size) {
    }
};

default конструктор - вызывается по умолчанию, задает всем полям базовые для их типов значения.

Если среди полей класса есть ссылка, нужно инициализировать ее с помощью следующего синтаксиса (аналогично для всех полей, которые должны быть инициализированны в теле конструктора):

class Array {
public:
    explicit Array(std::string& word_argument) : word(word_argument) {
    }
    std::string& word;
};

Константные классы (константные методы)

const методы - те, которые не меняют поля класса (для них все поля класса - const). По умолчанию лучше делать все методы, которые не меняют поля класса, const

Внутри константного класса можно вызывать только const методы.

Деструктор

Позволяет очистить поля класса и вызывать необходимые методы при уничтожении.

class Array {
public:
    ~Array() {
        delete[] data; // Следить, чтобы data был валидным указателем (delete nullptr сработает, ничего не сделав)
    }
private:
    int* data;
};

Деструктор у класса только один и вызывается при уничтожении объекта.

void f() {
    Array arr; // Вызывается пустой конструктор arr
} // Вызовется деструктор arr (статические объекты уничтожаются в конце main)

Статические функции

Статические функции вызываются не от объекта класса. Для них класс выполняет роль своебразного namespace.

class Array {
public:
    static void Print() {
    }
};

Вызвать статическую функцию:

Array::Print();

Статические функции могут обращаться к приватным полям класса:

class Array {
public:
    static void Print(Array& const arr) {
        std::cout << arr.size << std::endl;
    }
private:
    size_t size;
};