I Flashcards
Оператор sizeof
- sizeof (объект некоторого типа)
- Оператор, который возвращает число, равное количеству байт, которое бы занимал в памяти объект этого типа
- Полезен, когда нужно выделить количество памяти, которой бы хватило на n объектов нужного вам типа, вы пишете n * sizeof (объект некоторого типа)
- Причем в скобках можно написать как объект, так и просто название типа
Оператор запятая (,)
- Пример: x=5, y=3, z++, 7
- Оператор вычисляет сначала левую часть, потом вычисляет правую часть.. и возвращает правую часть (в выражении x=(5,3) x=3)
- В выражении: x=(x=5, y=3, z++, 7) получится: x=7, y=3, z увеличится на 1
- Это оператор с самым низким приоритетом из всех существующих операторов
- В выражении f(5,3) запятая это не оператор, а часть синтаксиса… аналогично тому, как в контексте объявлений int x=5; x это не оператор присваивания, а часть синтаксиса (инициализация)
Управляющие конструкции
1. if (bool-expression) statement;
- statement это инструкция
- если инструкция единичная, то фигурные скобки не нужны
- если несколько инструкций, то ставятся { }
2. while (bool-expression) statement;
- bool-expression - любое выражение, которое конвертируется в bool
- есть альтернативная форма (с пост-условием): do statement; while (bool-expression);
3. for (Выражение инициализации, выполняемое только раз; Условие выхода, проверяемое в начале каждой итерации; Выражение цикла, выполняемое в конце каждой итерации) statement;
- самая интересная конструкция
- | это “или”
- declaration|expression - это инициализатор (выполняется первым)
- далее если bool-expression истинно, то выполняется statement
- далее вычисляется expression и потом за ним снова bool-expression (и так по кругу)
- любую часть в скобках можно пропустить (и даже все)
- for(;;); statement это вечный цикл
4. switch
- switch ( <переменная> ) {
- case значение1:
- Выполнить если <переменная> == значение1
- break;
- case значение2:
- Выполнить если <переменная> == значение2
- break;
- …
- default:
- выполнить, если ни один вариант не подошел break;
- }
5. break;
- выйти из текущего цикла (любого, может быть внутри for, while и тд)
- из if выходить через break нельзя
6. continue;
перейти к следующей итерации цикла
7. return expression;
8. goto _(метка)_ ; устаревшее, почти не используется
Виды ошибок и UB
- Ошибка компиляции (compile-time errors) - программа написана так, что компилятор не может преобразовать её в выполняемый код (невозможно создать выполняемый файл).
- Ошибки компиляции делятся на лексические, синтаксические и семантические.
- Лексические ошибка - компилятор не может распознать написанную последовательность символов (например, 2b3;).
- Синтаксическая ошибка - компилятор не может разпарсить некоторую конструкцию (хотя и понимает отдельные части). Написанная инструкция в целом не корректна (слова не согласованы между собой)
- Семантическая ошибка - например используется переменная, которую забыли объявить. Или выполняется операция над объектом, которая не поддерживает операции над объектами такого типа (пример: “Съешь себя пожалуйста этим столом”).
- Ошибка выполнения (runtime errors) - выполняемый файл создался, но программа в ходе выполнения сделала что-то такое, что привело её к краху. Её невозможно отследить на этапе компиляции.
- Undefined behavior (UB) - написано нечто, не прописанное в стандарте C++. Например, int x=2<<40; Компилятор может исполнить это непредсказуемым образом. Другой пример: ++x = x++;
Указатели
- это боль, самое неприятное в языке
- объявление массива из 100 элементов: int a[100]; при этом массивы более 2-4 млн выдают runtime error!! что делать, если нужен очень большой массив?
- при этом a[150], a[-5], 150[a] тоже возвращают значения!
- операционная система изначально выделяет программе некоторую область памяти (stack)
- Указатель это специальный тип данных, который служит для предоставления адресов в памяти (int *x;); иначе это переменная, которая содержит адрес другой переменной
- Можно взять указатель и посмотреть, что “под ним лежит” (*x; вернет int)
- Указатели можно инкрементировать и дикрементировать, вычитать друг из друга
- a[5] это по сути указатель, эквивалентный *(a+5) - операция разыменования (* - оператор получения значения по адресу)
- int b; int *y = &b; (дает адрес этого объекта в памяти)… у это указатель (переменная адресного типа)
- кроме того, возможно выражение следующего типа (сколь угодно глубоко): int **x = &y;
- типа данных указателя должен соответствовать типу данных на которые он указывает
Указатели на функции
- объявленная функция хранится в памяти по некоторому адресу
int f(double, char*) //в функциях можно не указывать названия переменных
int (*p) (double, char*) = &f;
Функции с переменным числом аргументов
void f(int x, … ) { }
- главное, чтобы был первый параметр и дальше можно передавать любые типы в любом количестве
- чтобы обращаться к таким функциям нужно включить соответствующую библиотеку cstdarg
- в таком виде этим никто не пользуется
Аргументы по умолчанию
void f (int x, char y = ‘a’);
- эту функцию можно вызвать как функцию с одним аргументом, так и как функцию с двумя
- то есть можно не указывать второй аргумент, тогда он по умолчанию сделается равным а
- важно: аргументы по умолчанию должны быть в конец списка (их может быть несколько или все)
Перегрузка (перезагрузка) функций
- это когда есть несколько функций с одинаковым именем, но разным набором аргументов
Пример:
void f (int x);
int f (char x, int y);
char f(double z);
- это не ошибка компиляции, в зависимости от того, с какими параметрами будет вызываться f, будет вызываться наиболее подходящая функция
- если вызвать f от типа, не указанного в описаниях (например, char), то включаются правила разрешения перегрузки (overloading resolution rules)
- когда у вас есть несколько инструкций, и одна из них более специализированная, чем другие, то будет выбрана именно она
Пример:
void f (int x, int y)
void f (int x, …)
при вызове f (1,2) будет выбрана первая функция
Операторы new и delete
- как сделать так, чтобы та информация, которая была объявлена внутри функции, не пропала после ее исполнения?
- у ОС есть память (оперативная) и её может не хватить, и нужно будет попросить ОС выделить дополнительную память - для этого есть оператор new
new type; // возвращается указатель на эту переменную (адрес)
int *p = new int ( ____ ); // эта переменная будет существовать даже после исполнения функции
- чтобы ОС уничтожила эту переменную, имеется оператор delete
delete p;
- если вызвать delete от указателя, который ранее не был выдан через new, то будет crash
- если не использовать delete, то эта информация так и останется в памяти - так можно сожрать всю оперативную память - поэтому всё лишнее нужно удалять
- поэтому с этими операторами очень больно работать - всегда нужно помнить, что и где выделяется, чтобы это своевременно стирать
- как вызвать массив?
Пример:
int *p = new int [100];
delete[] p; // для уничтожения массива обязательно нужны []
Еще пример:
int *p = new int [100];
int *pp = new int [10];
delete[] p, pp; //в данном примере массив pp не уничтожится, так как оператор запятая имеет самый низкий приоритет действия
Ссылки (references)
- как написать функцию, которая меняет свои аргументы? (swap)
- был ли Ленинград столицей Российской Империи? (в C++ это не так)
- ссылка это по сути другое название для объекта
Пример 1:
type x;
type &y=x; // это ссылка на x, без & создался бы новый объект y (а не ссылка на старый)
- теперь y жестко привязан к x и всё, что вы делаете с одной переменной, делается с другой
Пример 2:
void swap (int &x, int &y) {
int t = x;
x=y;
y=t;
}
Пример 3 :
int &f (int x) { // это пример UB - компилятор будет работать некорректно
int y = x + 1
return y;
}
Константы
- нельзя написать int &x=5 так как 5 это константа
const int x = 3; // данный префикс означает, что x не должен меняться (запрещены модифицирующие операции)
const int &t = y; // t и y будут один и тот же объект, при этом t будет считаться константой, а y не константой… к у можно применять модифицирующие операции, а к t нельзя… и при изменении y будет изменяться t, потому что это один и тот же объект
const int *p = ….; // указатель на константу - он сам не является константой и его можно модифицировать, а то, что под ним лежит, нельзя
- Общее правило: если у вас есть ссылка или указатель на не константу, то вы с её помощью можете проинициализировать ссылку или указатель на константу (в обратную сторону нельзя)
- Константы и ссылки обязательно нужно инициализировать! (int &x; или const int y; выдаст ошибку компиляции)
const int *p = new int [10]; //такая запись допустима, мы не сможем менять ВСЕ элементы под p, а сам p сдвигать можно
Операторы явного преобразования типов
Именованный оператор приведения имеет следующую форму:
имя_приведения < тип> ( выражение ) ;
где тип - это результирующий тип преобразования, а выражение - приводимое значение. Если тип - ссылка, то результат l-значение. Имя_приведения может быть одним из следующих: static_cast, dynamic_cast, const_cast и reinterpret_cast.
1 способ:
(type) (выражение); // вычисляется выражение, а потом преобразуется в заданный тип - так было в C (c-style cast)
2 способ:
static_cast (выражение);
- возьми выражение и по правилам преобразования преобразуй в заданный тип; если такое преобразование не известно (например оно не возможно или это пользовательский тип), то ошибка компиляции (оператор работает на этапе компиляции)
- int i, j ;*
- double slope = static_cast ( j ) / i ;*
3 способ:
reinterpret_cast < > ( )
- возьми значение, которое тебе дали, и тебе биты в памяти, которые оно занимает, интерпретируй, как если они кодируют выражение типа type
- этот способ не всегда работает (есть ограничения)
- используется для приведения указателей разного типа друг к другу
*(reinterpret_cast (&obj));
4 способ:
- позволяет преобразовывать константы в неконстанты (единственный из этих операторов)
Пример 1:
int x=5;
const int &y = x;
int &z = const_cast (y);
Пример 2:
f (int&) и f (const int&); //заданы две функции (перегрузка)
f (const_cast (x)); такое выражение вынудит компилятор вызвать “f (const int&);” версию функции
5 способ:
dynamic_cast
Явное преобразование типов это “молоток”: static_cast это наиболее “ласковый” метод, reinterpret_cast это совсем хардкорные преобразование типов (лом)… c-style cast это худший вариант каста, “бульдозер” (сломать всё, лишь бы получилось)
Вывод: нужно стараться использовать касты пореже
Классы и структуры
- грубо говоря, это пользовательский тип данных
Пример 1:
class C; //объявление класса
Пример 2:
class C { // внутри класса находятся поля или методы (по умолчанию они считаются private)
public: // всё, что написано после считается public
}; // после определения класса нужно ставить ; после фигурной скобки!!! (тонкий момент)
Что находится внутри класса:
- поля - переменные/объекты, которые в классе хранятся
- методы класса - функции, выполняемые над объектами этого класса
Для того, чтобы разграничить методы и поля, которые видны и не видны внешнему пользователю, используются слова private и public (модификаторы доступа) и этим класс отличается от структуры.
Обычно сначала показывают public часть, потом private (code-style).
Пример 3:
struct s {
// в структуре всё по умолчанию public (наследование по умолчанию тоже публичное)
int x;
};
В остальных аспектах классы и структуры это одно и то же
Конструктор
- особый метод класса, вызываемый 1 раз сразу после создания класса
- конструктор имеет то же имя, что и имя класса
- причем конструктор ничего не возвращает (единственная функция в своем роде)
- конструктор должен быть публичным или возникнет вопрос с созданием экземпляра
Пример 1:
class tStudent{
int age // private
public:
tStudent (int _age) // конструктор
{
age = _age;
}
~tStuden () // деструктор
{
cout << “Пока!”;
}
}; // в конце должна быть ; !!!
Как вызвать конструктор:
tStudent s2(17); // специфический тип инициализации, без “=”
tStudent s3(s2); // конструктор с клонированием
_________________________________________
int main ( ) {
C x; // инструкция по созданию типа создается с помощью конструктора
}
Конструктор (пишется внутри класса)
С ( ….. ) { … }
Пример 2: (класс строк)
class string {
chat* str;
int size;
string (cons char* str_, int size_) {
str = new char [size_];
for (…)
}
};
- если конструктор не определен, то компилятор генерирует конструктор по умолчанию