Глава 2. Базовый синтаксис
Вы познакомитесь с несколькими типами данных, переменными, операторами и простыми функциями. Это позволит сразу же приступить к практике. Подробнее каждая из этих тем будет раскрыта в посвященной ей главе.
Правила именования
Правил именования в C++ всего несколько. Они распространяются на переменные, функции, классы и другие сущности в программе.
Имя должно начинаться с буквы латинского алфавита или символа подчеркивания _
: i
, SearchEngine
, connect_to_db
, _abs_val
. Символ подчёркивания в начале имени использовать не рекомендуется: такие имена могут оказаться зарезервированными.
Имя может содержать только буквы, цифры и символы подчеркивания: API_v3
, isValid
, catch2
.
Имя не должно совпадать с ключевыми словами языка: int
, if
, union
и другими.
C++ — регистрозависимый язык. Поэтому count
, Count
и COUNT
— это разные имена.
Какие имена переменных составлены правильно? Перечислите номера строк через пробел.
1 $total_volume
2 codek.meta
3 loop
4 MDFormatter
5 %TOKEN%
6 hex_val
Имя $total_volume
содержит недопустимый символ $
. Имя codek.meta
содержит недопустимую в названии точку. Имя %TOKEN%
содержит недопустимый символ %
.
3 4 6
Правила форматирования
В C++ отсутствуют общепринятые правила форматирования. Например, нет разницы между пробелами и табуляцией, а наличие отступов опционально. Фигурные скобки можно ставить на любой строке.
Перед вами два разных подхода к форматированию:
int main() {int x = 5 + (2 - 1);}
int main(){int x=5+(2-1);}
Более того, вся программа может быть записана в одну строку, оставаясь при этом корректной. Хоть и не читабельной.
Точка входа в программу
Функция с именем main
— это точка входа в программу (entry point). Ее наличие обязательно: после запуска программы управление передается именно ей.
Так выглядит минимальная программа на C++, которая ничего не делает:
int main() { }
Функция main()
возвращает целое число типа int
вызвавшему программу окружению. Это статус завершения:
- 0 в случае успеха,
- другое значение в случае ошибки.
В нашем примере тело функции пустое: { }
. Но как же тогда формируется статус завершения? Функция main()
— особая: при отсутствии явно возвращаемого значения она возвращает 0. Для наглядности мы можем вернуть его явно:
int main(){return 0;}
Чтобы обеспечить выполнение кода, удостоверьтесь, что он вызывается из функции main()
.
Функции
При объявлении функции сначала указывается тип возвращаемого значения, потом имя функции, после него параметры. А затем тело функции, обрамленное фигурными скобками:
Напомним, что параметр — это имя в определении функции. А аргумент — это фактическое значение, переданное функции при вызове.
Рассмотрим реализацию функции is_error()
и ее вызов:
import std;bool is_error(int http_code){return http_code >= 300;}int main(){bool res = is_error(404);std::println("404 is error code? {}", res);return 0;}
404 is error code? true
Для возврата из функции значения мы использовали оператор return
.
А для вывода res
в консоль мы сделали две вещи:
- Импортировали стандартную библиотеку
std
. В ней содержится функция println(), отвечающая за форматированный вывод. - Вызвали
println()
. Она находится в пространстве имен (namespace)std
, и мы указали его при вызове:std::println()
.
Вы обратили внимание, что некоторые строки в программе заканчиваются точкой с запятой? Это инструкции (statements) — фрагменты кода, выполняемые последовательно.
Напишите функцию to_fahrenheit()
, которая:
- Принимает вещественное число типа
double
— температуру в градусах по Цельсию. - Возвращает градусы по шкале Фаренгейта (
double
). Формула:°F = °C × 9.0/5.0 + 32.0
. Чтобы ее реализовать, воспользуйтесь операторами для сложения+
, умножения*
и деления/
.
Возвращаемое функцией значение, если параметр называется celsius
: celsius * 9.0 / 5.0 + 32.0
.
double to_fahrenheit(double celsius)
{
return celsius * 9.0 / 5.0 + 32.0;
}
Переменные
Чтобы создать переменную, укажите ее тип и имя. А затем через оператор =
проинициализируйте значением:
int request_count = 0;
После типа можно перечислять несколько переменных, разделенных запятой:
int left = -100, right = 100;
Однако делать так не рекомендуется: такой код сложно читать. Лучше заводите по одной переменной на одну строку:
int left = -100;int right = 100;
В некоторых языках действует правило: если переменной не задано значение явно, то она инициализируется значением по умолчанию. C++ к таким языкам не относится:
int request_count; // Здесь может быть что угодно!
Поэтому при создании переменной обязательно задавайте ей значение.
Чтобы изменить значение переменной, применяется уже знакомый вам оператор присваивания (assignment operator):
double default_len = 6.7;double len = default_len;len = len + 2; // 8.7
Чему равны значения a
и b
? Введите их через пробел.
int a = -1int b = 4;int c = a;a = b;b = c;
В этом коде значения переменных a
и b
меняются местами с использованием переменной c
.
4 -1
Константы
Делать константами все переменные, которые не требуется изменять — это отличная практика. Она предотвращает случайную перезапись переменной.
Константы помечаются квалификатором типа const. Попытка перезаписи константы приведет к ошибке компиляции. Квалификатор const
может стоять как слева от типа, так и справа:
const int equator_len_km = 40075;int const winter_avg_temp = -5;
Знакомство с фундаментальными типами
Фундаментальные типы (fundamental types) — это типы, встроенные в язык. Их имена являются ключевыми словами (keywords). Рассмотрим некоторые из них:
int
— знаковое целое:93
,-3
,0
,9'100
.double
— число с плавающей точкой двойной точности:-45.5
,1e6
,0.0
,NAN
(not a number),INFINITY
.bool
— логическое значение:true
,false
.char
— ASCII-символ:'a'
,'9'
,'\t'
,50
.void
— отсутствие значения.
Типы int и double
Большие числовые значения удобно разбивать по разрядам символом штриха '
:
int avg_dist_to_moon_km = 384'400;
В литералах типа double
целая часть отделяется от дробной точкой.
double weight = 1008.9;
Тип double
поддерживает экспоненциальную запись числа. Она удобна для компактного представления длинных значений.
double a = 3e6; // 3x10^6 = 3'000'000.0double b = -7e-2; // -7x10^-2 = -0.07
Напишите экспоненциальное представление числа 0.00002.
Если вы раньше не работали с экспоненциальной записью, самое время разобраться в ней.
Представим число 0.00002 в виде мантиссы и порядка. Мантисса: 2. Порядок: -5.
2e-5
Тип bool
Логический тип bool
может принимать два значения: true
и false
.
bool is_eq = false;bool has_open_connections = true;
Тип char
Переменную символьного типа char
можно инициализировать символом в одинарных кавычках:
char letter = 'S';
А можно кодом символа из ASCII-таблицы:
char letter = 83;
Тип char
представляет собой целое число, которое можно трактовать как ASCII-код. Поэтому в обоих примерах переменная letter
содержит одно и то же значение — число 83, в ASCII-таблице соответствующее заглавной букве S латинского алфавита.
Тип void
Используйте тип void
в качестве типа возвращаемого значения функции, если она ничего не возвращает:
void show_warning(){std::println("Something went wrong");}
Кстати, вызывать return
в конце такой функции не обязательно. Но его можно использовать для раннего выхода (early exit):
if (!is_connection_opened){return;}
Знакомство с библиотечными типами
Итак, мы обсудили несколько встроенных в язык типов. А теперь взглянем на два типа из стандартной библиотеки C++. Они пригодятся вам уже в следующей главе:
std::size_t
— беззнаковое целое.std::string
— класс, реализующий строку.
Класс — это пользовательский тип данных, призванный объединять данные (поля класса) и методы по работе с ними.
Тип std::size_t
Тип std::size_t может хранить:
- Индекс элемента в контейнере. Контейнер — это коллекция элементов. Например, переменная типа
std::size_t
может хранить индекс символа строки. - Длину контейнера.
- Размер объекта в байтах.
- Счетчик цикла.
Под капотом std::size_t
— псевдоним (alias) для одного из фундаментальных беззнаковых целых типов.
import std;int main(){const std::size_t i = 9;std::println("{}", i);}
9
Класс std::string
Тип std::string реализует строку, не привязанную к кодировке. Она представляет собой последовательность символов типа char
.
Если std::size_t
— всего лишь псевдоним фундаментального типа, то std::string
— полноценный класс, содержащий методы для работы со строкой.
import std;int main(){std::string s = "The standard string class";// Получение символа по его индексу:const char c = s[1];// Длина строки:const std::size_t n = s.size();// Запись символа по индексу:s[n-1] = 'S';std::println("{}", s);std::println("{} {}", c, n);}
The standard string clasS
h 25
Из примера видно, что строковый литерал заключается в двойные кавычки. А для обращения к символу строки по индексу используется оператор []
. Индексация начинается с нуля.
Чтобы получить размер строки, мы вызвали метод size()
. Для вызова метода между объектом класса и именем метода ставится точка: s.find("st")
. Точка — это тоже оператор, и он нужен для доступа к членам класса (то есть его полям и методам).
У класса std::string
есть множество полезных методов. Вот некоторые из них с примерами для строки s="example"
:
size()
возвращает длину строки:s.size() // 7
.empty()
возвращаетtrue
, если строка пустая:s.empty() // false
.insert()
вставляет заданное количество символов по указанному индексу:s.insert(1, 2, '8') // e88xample
.contains()
проверяет, присутствует ли в строке подстрока или символ:s.contains("am") // true
,s.contains('y') // false
. Этот метод появился в C++23.
Операторы
Вы уже познакомились с оператором присваивания =
, оператором взятия элемента по индексу []
и оператором доступа к члену класса .
. Рассмотрим еще несколько категорий операторов, которые пригодятся в первую очередь.
Чтобы поменять приоритет выполнения операторов, они группируются скобками.
Операторы сравнения
Операторы сравнения (comparison operators) применимы к большинству фундаментальных типов:
==
— равенство.!=
— неравенство.<
,>
— меньше, больше.<=
,>=
— меньше или равно, больше или равно.
Выражения сравнения приводятся к типу bool
. Выражение (expression) — это последовательность операторов и операндов.
bool a = 8.1 < 16; // truebool b = -5 != -5; // falsestd::string s = "";bool c = s.empty(); // truebool d = s.size() == 4; // false
Чему равно значение b
?
std::string text = "Operator";bool b = text[text.size() - 1] == text[3];
text.size()
вернет 8. Символ по индексу 7 равен r
. Символ по индексу 3 тоже равен r
.
true
Логические операторы
Логические операторы применимы ко всем выражениям, которые приводятся к bool
:
&&
— «И»:is_filled && is_valid
.||
— «ИЛИ»:has_gps_location || connected_to_wifi
.!
— «НЕ» (отрицание):!is_valid
.
bool is_online = true;bool is_updated = false;std::println("{}", is_online || is_updated); // truestd::println("{}", !(is_online && is_updated)); // true
XOR — это булева функция, также известная как исключающее «ИЛИ». Она принимает два флага и возвращает true
, если один из них истинен, а другой — ложен. В остальных случаях она возвращает false
.
Напишите свою реализацию hello_xor()
.
Функция возвращает true
тогда и только тогда, когда один из аргументов равен true
, а другой — false
.
bool hello_xor(bool a, bool b)
{
return (!a && b) || (a && !b);
}
Арифметические операторы
Арифметические операторы (arithmetic operators) применимы к любым выражениям, которые приводятся к числам. Они позволяют осуществлять:
+
— сложение:5 + 6 == 11
.-
— вычитание.8 - 9 == -1
.*
— умножение.3 * 7 == 21
./
— деление.10 / 4 == 2
.%
— деление по модулю, то есть получение остатка от деления.11 % 3 == 2
.
Перечисленные операторы называются бинарными. Они применяются к двум операндам: a + b
. В C++ есть и унарные операторы — унарные плюс и минус: +a
, -a
.
Составное присваивание
Операторы составного присваивания (compound assignment) объединяют присваивание переменной с арифметическим действием над ней. Их ввели в язык, чтобы записывать простые арифметические действия более кратко:
x += 5; // x = x + 5x -= y; // x = x - yx *= 10; // x = x * 10;x /= y; // x = x / y;x %= 2; // x = x % 2;
Инкремент и декремент
Увеличение или уменьшение значения на единицу можно записывать еще короче! Оператор инкремента ++
увеличивает значение на 1, а оператор декремента --
уменьшает. Эти операторы применимы только к целым числам.
++x; // Эквивалентно x+=1--x; // Эквивалентно x-=1
В этом примере приведена префиксная форма операторов: пре-инкремент и пре-декремент.
Есть и постфиксная форма: в ней ++
и --
указываются после переменной. Это называется пост-инкрементом и пост-декрементом:
x++;x--;
Обе формы изменяют переменную и возвращают ее значение. Разница в очередности этих действий.
Префиксный оператор сначала изменяет переменную на 1, а потом возвращает значение:
a = 2;b = ++a; // a=3, b=3
Постфиксный оператор сначала возвращает значение переменной, и лишь затем увеличивает ее на 1:
a = 2;b = a++; // a=3, b=2
Постфиксные операторы инкремента и декремента имеют более высокий приоритет, чем префиксные.
И еще одно важное отличие: префиксные формы возвращают саму переменную, а постфиксные — ее неизменяемую копию. Поэтому такой код не скомпилируется:
--i++;
Вначале выполнится постфиксный оператор и вернет неизменяемую копию переменной: --(i++)
. А так как неизменяемое значение нельзя уменьшить, компилятор прервет сборку программы с ошибкой:
error: expression is not assignable
--i++;
^ ~~~
Приоритет операторов
Операторы с равным приоритетом применяются слева направо. Порядок вычисления можно изменять с помощью скобок.
Перечислим уже знакомые вам операторы по убыванию приоритета. Если в строке несколько операторов, то приоритет у них одинаковый.
a::b
— разрешение области видимости, напримерstd::println()
. Это оператор с наивысшим приоритетом.a++
,a--
,a[b]
,a.b
— постфиксные инкремент и декремент, взятие по индексу, доступ к члену класса.!a
,+a
,-a
,++a
,--a
— логическое отрицание, унарные плюс и минус, префиксные инкремент и декремент.a * b
,a / b
,a % b
— умножение, деление, модуль числа.a + b
,a - b
— сложение, вычитание.a < b
,a <= b
,a > b
,a >= b
— больше, меньше.a == b
,a != b
— равенство, неравенство.a && b
— логическое «И».a || b
— логическое «ИЛИ».a = b
,a += b
,a -= b
,a *= b
,a /= b
,a %= b
— присваивание, составное присваивание.
Таблицу с приоритетом всех операторов C++ вы можете посмотреть на cppreference.
Нужны ли скобки, чтобы это выражение вычислилось как ожидается? y/n
.
width < 0 || volume / length <= max_val
Приоритет деления /
выше, чем сравнения <=
. А приоритет ||
меньше, чем сравнения.
n
Как быть, если вы сомневаетесь, нужны ли в выражении скобки? Если без скобок код трудно читать, то ставьте их! Например, выражение из задачи выше со скобками выглядит проще: (width < 0) || (volume / length <= max_val)
.
Какое значение у переменной x
?
В случае ошибки напишите err
.
Ремарка: это пример плохого кода. В реальных проектах избегайте подобных трудночитаемых конструкций. Однако они встречаются на собеседованиях.
int a = 1, b = 2, c = 3;int x = a-- - b++ - c--;
Приоритет постфиксных операторов выше, чем у оператора вычитания -
. Постфиксный оператор сначала возвращает значение переменной, а потом изменяет его. Поэтому x
равен 1 - 2 - 3
.
-4
Что будет выведено в консоль?
В случае ошибки напишите err
.
int c = 2;int C = 5;std::print("{}", c++ * ++C);
В этом выражении у постфиксного оператора максимальный приоритет. Следующим по приоритету выполнится префиксный оператор. И лишь затем — оператор умножения. Мы получим 2 * 6
.
12
Резюме
- Функция
main()
— это точка входа в программу. - Если функция ничего не возвращает, то тип ее возвращаемого значения
void
. - При создании переменных всегда инициализируйте их значением.
- Неизменяемые переменные помечаются квалификатором типа
const
. - Несколько фундаментальных типов:
bool
,int
,double
,char
,void
. - Пара библиотечных типов:
std::size_t
,std::string
. - Операторы сравнения:
==
,!=
,<
,>
,<=
,>=
. - Логические операторы:
&&
,||
,!
. - Арифметические операторы:
+
,-
,*
,/
,%
. - Операторы составного присваивания — это краткая форма выполнения над переменной арифметического действия и присваивания ей. Например,
x *= 2
. - Операторы инкремента
++
и декремента--
бывают префиксными и постфиксными.