II Flashcards
Операторы “.”, “→”, ключевое слово this
- как обратиться к полю (аналогично к методу):
объект.поле
- стрелочка это то же самое, но не для самих объектов, а указателей для них
(*p).f( ); или же p -> f( );
- this это указатель на тот объект, в котором мы “сейчас” находимся
Пример:
class String {
public:
String (const char* str, int size) {
this str -> new char [size];
….
}
~String () {delete [] this str}
private:
char* str;
int size;
}
};
Оператор присваивания
- оператор присваивания нужно определять “руками” так как по умолчанию оператор просто копирует поля
- это метод, возвращает ссылку на класс на котором его определяют
String& operator = (const String& s) {
if (&s == this) return *this; // проверка, что объект не равен самому себе
delete[] str;
str = new char [s.size];
for ( … ) {…};
return *this;
}
Правило трех (the rule of three)
Эмпирическое/эвристическое правило, которое говорит, что если класс требует определения одного из трех (хотя бы одного из трех):
- Конструктора копировани
- Оператора присваивания
- Деструктора
То значит он требует определения всех этих вещей (иначе крах)
В С++11 вместо “Правила трех появилось “Правило пяти”
Списки инициализации в конструкторах
- Как в конструкторе инициализировать поля, которые нельзя изменять внутри тела конструктора? Для этого существуют списки инициализации в конструкторах.
int z = 10;
Class C {
const int x = 3;
int& y = z;
C (const int x, int& y) : x (x); y (y) {} // название поля (значение, которым его надо проинициализировать)
}
};
- ! Поля инициализируются ДО входа в тело конструктора
- Лучше всегда пользоваться списками инициализации, а не обычным присваиванием (эффективнее с точки зрения управления памятью)
Ключевое слово explicit
String s = s1 + ‘a’; // a имеет тип char и сложение строки с ним не определено
- Можно из типа char сделать строку и потом производить действия
- Неявная конверсия в int даст некорректный результат, поэтому конверсию нужно делать в явном виде. Поэтому возникает желание запретить неявные преобразования типов - для этого существует ключевое слово explicit, которое пишется перед конструктором
explicit String (int size) { … }
- Если это не конструктор копирования, то рекомендуется везде делать explicit конструкторы
Константные методы
Пусть есть метод в конструкторе int size () {return size_;}
- По умолчанию считается, что каждый метод класса имеет право менять поля этого класса
сonst String s =”abc”;
s. size (); // вызовет ошибку компиляции так как метод size потенциально может менять поля, которые мы задали как неизменяемые (константы)
* чтобы метод класса можно было вызывать над константным объектом нужно явно пометить, что он не меняет поля класса, то есть допускает вызов от константного объекта.
Что нужно сделать:
int size () const {return size_;}
- сам метод является константным по отношению к объекту, которым вызывается. Вот тогда можно будет этот метод вызывать от константных объектов, но все поля внутри этого метода считаются константными (то есть нельзя из этого метода вызвать модифицирующие операции над полями объектов)
- жизненный метод, нужно не забывать ставить метку там, где это нужно
Ключевое слово mutable
- допустим есть объект, который служит для логирования и в нём есть поле, в котором считается, сколько раз методы были вызваны - при каждом вызове некоторого метода счетчик увеличивается на 1
- проблема: допустим мы создали константный объект и у него начинаем вызывать методы, но это затруднительно, так как поля не могут меняться (но мы хотим менять одно конкретное поле)
- для этого есть специальное ключевое слово, которое разрешает менять отдельные поля в константном методе (mutable)
определяем поле класса следующим образом:
mutable int x;
Ключевое слово friend
- Допустим имеется класс и в нём есть некоторое внутреннее приватное представление, а вы хотите определить оператор ввода этого в поток (cin)
- Проблема: вы не можете этого сделать, так как поля приватные и их нельзя трогать извне, а оператор ввода в поток вы не можете определить внутри класса как член (потому что у него левый операнд это поток, а внутри класса вы можете определять только те операторы, у которых левый операнд это объект вашего класса)
- Таким образом, нужен оператор, который не является членом класса, но может иметь допступ к его приватным полям (исключительный). Для этого есть ключевой слово friend, которое ставится перед оператором
friend f ( … )
- не рекомендуется делать много друзей - нарушается принцип инкапсуляции
- друзьями можно объявлять целые классы, это будет означать, что все методы одного класса имеют доступ к приватной части другого класса
- друзья не взаимны (если А это друг Б, то обратное может быть не верно)
Делегирующие конструкторы
- пишем конструктор класса String от двух параметров: String (const char* str, int size) и вы хотите написать другой конструктор, который делает всё то же самое, что и первый, но кроме этого еще что-то делает (доп параметры)
- Проблема: не хочется переписывать наполнение старого конструктора
- Допустим мы хотим написать конструктор, в котором переменные принимаются в обратном порядке (int, const chat*). Для этого применяются делегирующие конструкторы (появились в C++11).
String (int size, const chat* str) : String (str, size) { };
Ключевое слово static
- представьте, что у вас есть класс в котором помимо прочего хранится какая-то информация (например, сколько было создано объектов этого класса и существует сейчас). То есть должно быть в классе поле про сам класс, которое не принадлежит ни одному объекту. Для этого некоторое поле класса можно объявить static
static int x;
- это поле будет видимо для всех объектов (на общих основаниях)
- static может быть не только в классе, но и в функциях
- переменные static удалятся после завершения программы
- представьте, что у вас есть класс в котором помимо прочего хранится какая-то информация (например, сколько было создано объектов этого класса и существует сейчас). То есть должно быть в классе поле про сам класс, которое не принадлежит ни одному объекту. Для этого некоторое поле класса можно объявить static
static int x;
- это поле будет видимо для всех объектов (на общих основаниях)
- static может быть не только в классе, но и в функциях
- переменные static удалятся после завершения программы
Указатели на члены
class C {public: int a; char b;}
- Пусть есть класс с некоторыми полями и вы хотите объявить указатель не на сам объект типа C, а на некоторое поле этого объекта
int main ( ) {
int C::* p = &C::a;
C c;
c.*p = 5; // можно менять значение параметра а
}
- аналогично можно вызывать методы класса
Перегрузка операторов, арифметические операторы
- пусть есть класс BigInteger - сколько угодно большое число, записанное в виде массивов и мы хотим определить операции над ним
Сложение:
BigInteger operator + (const BigInteger& x) const { // этот оператор возвращает некоторый новый объект, не меняя старый, поэтому нужно добавить константный метод
// const BigInteger& мы пишем для того, чтобы каждый раз не создавалась лишняя копия BigInteger
BigInteger sum = * this;
return sum += x;
}
Оператор += :
BigInteger& operator += (const BigInteger& x) {
}
Операция И и ИЛИ
- Логическая операция И выполняется с помощью оператора &&.
- Логическая операция ИЛИ выполняется с помощью оператора | | .
Синтаксис цикла for
Синтаксис цикла for таков:
for (Выражение инициализации, выполняемое только р а з ;
Условие выхода, проверяемое в начале каждой итерации;
Выражение цикла, выполняемое в конце каждой итерации)
{
Блок инструкций;
}