Тип double
хранится в памяти в виде знака, мантисы и порядка, а число представляется, как: .
Понятно, что вещественных чисел бесконечно, а представимых значений конечно, поэтому некоторые числа представить невозможно. Например, для иррациональных чисел хранятся близкие к ним значения, из-за чего некоторые математически верные равенства могут слегка отличаться. Чтобы бороться с этим, сравнивать double
нужно с использованием eps
(сравнивать модуль разности с эпсилоном, например). Точность double
уменьшается при увеличении модуля числа, из-за чего не надо использовать его в качестве счетчика и т.д.
Объект - сущность, обладающая 3 свойствами: состоянием (поля класса), поведением (методы класса, реакция объекта на получение внешних сообщений) и идентичностью (позволяет отличать объекты друг от друга).
Пример: int x
: хранит значение (состояние), операции сложения и т.д. (поведение), обладает адресом в памяти (идентичность).
Класс - множество объектов со схожими свойствами.
Инкапсуляция - отделение интерфейса от реализации.
Когда происходит подключение header-файла, его содержимое просто вставляется. .cpp
файлы компилируются раздельно, образуя .o
файлы, при этом файлы повторно компилируются только при изменении. В .o
содержатся "заглушки" на местах вызова функций других единиц трансляции, а линкер совмещает эти файлы.
Первый вариант реализации класса:
class A {
int f() {
...
return 0;
}
...
};
Реализация функции будет inline
(в место вызова функции будет вставляться код из f
), а значит при изменении f
будут перекомпилироваться все файлы, вызывающие эту функцию, но эта реализация незначительно быстрее.
Второй вариант (рекомендуемый) реализации класса:
A.h
class A {
int f();
};
A.cpp
#include "A.h"
int A::f() {
...
return 0;
}
При изменении функции перекомпилируется только A.cpp
, а не все используеющие его файлы. Также библиотеки, подключенные в .cpp
, не будут подключаться во всех файлах, использующих этот класс.
Обычно в начале определяют методы, потом поля, сначала public
, потом private
.
Пример forward declaration
для классов:
class B;
class A {
B* b;
};
class B {
A* a;
};
Но должен быть понятен размер переменной (у указателя фиксированный размер), так писать нельзя:
class B;
class A {
B b;
};
Ограничения на перегрузку операторов: некоторые операторы нельзя перегружать (".", "::", тернарный оператор), нельзя определять новые операторы, нельзя менять приоритеты операций.
Пример:
class A {
public:
...
...
int operator[](size_t index) const {
if (index >= size) {
return 0;
}
return data[index];
}
private:
int *data = nullptr;
size_t size = 0;
};
Работает получение переменной, но ее нельзя менять. Новый вариант:
int& operator[](size_t index) {
if (index >= size) {
return 0;
}
return data[index];
}
Если мы хотим использовать range-based for, надо реализовать методы .begin()
, .end()
и сделать класс итераторов. У итераторов должны быть определены операторы: "*", префиксный "++", "!=".
class Array {
public:
class ArrayIterator {
public:
ArrayIterator(Array& array, size_t index) : array(array), index(index) {}
int& operator*() {
return array_[index_];
}
ArrayIterator& operator++() { //++a
++index_;
return *this;
}
ArrayIterator& operator++(int) { //a++
return ArrayIterator(array_, index_++);
}
bool operator!= (const ArrayIterator& rhs) {
return index_ != rhs.index_; // упрощенный вариант, надо еще сравнивать array_;
}
private:
Array& array_;
size_t index_;
};
ArrayIterator begin() {
return ArrayIterator(*this, 0);
}
ArrayIterator end() {
return ArrayIterator(*this, size);
}
...
...
private:
int *data = nullptr;
size_t size = 0;
};
Пока можем реализовывать только перегрузку операторов, где левым операндом является наш класс. Второй вариант перегрузки (в виде свободной функции):
class A {
public:
...
friend A operator+(const A& lhs, size_t rhs);
private:
int x;
}
A A::operator+(const A& lhs, size_t rhs) {
return A(lhs.x + rhs);
}
Ключевое слово friend
позволяет получать доступ к приватным переменным из внешних функций (нарушает инкапсуляцию), поэтому нужно стараться не использовать его.
Перегрузка оператора вывода (первый аргумент - это поток вывода):
class A {
public:
...
friend std::ostream& operator<<(std::ostream& out, const A& rhs) {
out << A.x;
return out;
}
private:
int x;
}