Стандартные конструкции языка
Оглавление
if
Базовый пример:
1
2
3
4
5
6
7
8
int a = 5, b = 10;
if (a < b) {
cout << "a < b";
} else if (a == b) {
cout << "a == b";
} else {
cout << "a > b";
}
Особенности:
- Условие обязательно оборачивается в круглые скобки
- Тело всегда нужно оборачивать в фигурные скобки (есть исключения, но по любому кодстайлу лучше всегда использовать фигурные скобки)
- В условии можно использовать логические операторы
&&
(и),||
(или),!
(не) - Логические операнды не обязательно оборачивать в скобки; порядок действий такой же, как и в математике (смотри справку)
1
2
3
if (a < b && a > 0) {
cout << "a < b AND a > 0";
}
- Блок
else if
иelse
не обязателен - Все переменные, созданные внутри блока
if
,else if
илиelse
, видны только внутри этого блока, а затем удаляются
1
2
3
4
5
if (a < b) {
int c = 42;
cout << c; // 42
}
cout << c; // Ошибка компиляции
- В условии можно использовать любые типы данных, которые можно привести к
bool
(например,int
,double
,char
, объекты классов с перегруженным операторомbool
)
1
2
3
if (a) {
cout << "a is not zero";
}
- Начиная с C++17, в условии можно использовать инициализацию переменной
1
2
3
if (int c = a + b; c > 10) {
cout << "a + b > 10";
}
Тернарный оператор
Тернарный оператор (англ. conditional operator) - это сокращенная форма if
-else
, которая позволяет в одну строку записать условие и возвращаемое значение:
1
2
int a = 5, b = 10;
cout << (a < b ? "a < b" : "a >= b");
Особенности:
- Обе ветки должны что-то возвращать
- Результирующее значение в обеих ветках должно иметь одинаковый тип данных, иначе будет ошибка компиляции
switch
Конструкция и ограничения
switch
- это конструкция, которая позволяет сравнивать переменную с несколькими значениями:
1
2
3
4
5
6
7
8
9
10
11
int a = 5;
switch (a) {
case 1:
cout << "a == 1";
break;
case 2:
cout << "a == 2";
break;
default:
cout << "a is not 1 or 2";
}
Особенности:
- В каждом
case
должен быть операторbreak
, иначе выполнение пойдет дальше по следующимcase
. Это может быть полезно в случаях, подобных этому:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
char c = 'a';
switch (c) {
case 'a':
case 'b':
case 'c':
cout << "a or b or c is lower";
break;
case 'A':
case 'B':
case 'C':
cout << "A or B or C is upper";
break;
default:
cout << "not a or b or c or upper";
}
- В каждом
case
можно использовать только базовые типы данных (например,char
,enum
,int
,bool
,double
). То есть нельзя, например, использоватьstring
илиvector
. - В
case
можно использовать только константы, то есть нельзя использовать переменные:
1
2
3
4
5
6
7
8
9
int a = 5;
switch (a) {
case 1:
cout << "a == 1";
break;
case a:
cout << "a == 5"; // Ошибка компиляции
break;
}
Быстродействие
Вы можете задаться вопросом: почему у данного оператора так много ограничений? Всё дело в том, что независимо от размера switch
любой подходящий case
может быть найден за ровно одну операцию, то есть данный оператор не будет размернуть в кучу if-else, вместо этого будет намного эффективнее! Перед тем, как читать дальше, попробуйте самостоятельно ответить на вопрос: как это работает?
Ответ
switch
внутри себя использует таблицу переходов, которая хранит адреса всех case
. При сравнении переменной с case
компилятор сначала сравнивает переменную с case
и, если находит совпадение, переходит по адресу из таблицы. Таким образом, время выполнения оператора switch
не зависит от количества case
и всегда равно O(1)
.
Циклы
Общие особенности циклов:
- Любая переменная, объявленная внутри цикла (например, в условии или теле), будет видна только внутри этого цикла и удаляется после его завершения, как и у условного оператора
if
. - Для выхода из цикла можно использовать операторы
break
(выход из цикла) иcontinue
(переход к следующей итерации цикла). Однако их использование не рекомендуется, так как они могут сделать код менее читаемым (всё зависит от конкретного случая). Вместо этого лучше использовать логические операторы в условии цикла. - Важно понимать, что
break
иcontinue
действуют только на ближайший цикл, в котором они находятся.
while
Самый простой цикл - while
. Он будет выполняться, пока условие истинно:
1
2
3
4
5
int a = 0;
while (a < 10) {
cout << a << " ";
a++;
}
do-while
do-while
- это цикл, который выполнится хотя бы один раз, даже если условие ложно. Данный вид цикла нужен крайне редко, однако нельзя его не упомянуть в рамках данного раздела:
1
2
3
4
5
int a = 0;
do {
cout << a << " ";
a++;
} while (a < 5); // 0 1 2 3 4
for
for
- это цикл, который позволяет задать начальное значение, условие и шаг:
1
2
3
for (int i = 0; i < 5; i++) {
cout << i << " "; // 0 1 2 3 4
}
Особенности:
- В месте объявления переменной можно объявить несколько переменных через запятую, однако они обязаны быть одного типа:
1
2
3
for (int i = 0, j = 10; i < j; i++, j--) {
cout << i << " " << j << "\n"; // (0, 10), (1, 9), (2, 8), ..., (4, 6)
}
- Любой из блоков
for
может быть пустым:
1
2
3
4
5
int i = 0;
for (; i < 5;) {
cout << i << " "; // 0 1 2 3 4
i++;
}
- Начиная с C++11, можно удобно перебирать элементы в контейнере:
1
2
3
4
vector<int> v = {1, 2, 3, 4, 5};
for (int i : v) {
cout << i << " "; // 1 2 3 4 5
}
Данный цикл будет равносилен следующему:
1
2
3
for (auto it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
О том, что такое begin и end, вы узнаете в следующих разделах (об итераторах).
- Начиная с C++20, можно использовать развертывание параметров:
1
2
3
4
map<int, string> m = { {1, "one"}, {2, "two"}, {3, "three"} };
for (const auto& [key, value] : m) {
cout << key << " " << value << "\n"; // 1 one, 2 two, 3 three
}
Расширения языка
Разные компиляторы поддерживают разного вида расширения стандартных конструкций:
- GNU-расширения языка C, в частности к этому разделу будет полезным дополнением знать о:
- Разрешить пустые ветки в тернарном операторе
- ranges в switch-case - это конструкция вида
case 'a' ... 'z':
- Microsoft-расширения языка C++
Данные расширения обычно необходимо включать и всё зависит исключительно от настроек вашего проекта, поэтому не стоит использовать их в качестве стандартных конструкций, но знать о них полезно.