Главная / Курсы / C++ по спирали / Базовый синтаксис

Глава 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
Задача # 1

Правила форматирования

В 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().

Функции

При объявлении функции сначала указывается тип возвращаемого значения, потом имя функции, после него параметры. А затем тело функции, обрамленное фигурными скобками:

Функции в C++

Напомним, что параметр — это имя в определении функции. А аргумент — это фактическое значение, переданное функции при вызове.

Рассмотрим реализацию функции 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; }
Задача # 2

Переменные

Чтобы создать переменную, укажите ее тип и имя. А затем через оператор = проинициализируйте значением:

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 = -1
int b = 4;
int c = a;
a = b;
b = c;

В этом коде значения переменных a и b меняются местами с использованием переменной c.

4 -1
Задача # 3

Константы

Делать константами все переменные, которые не требуется изменять — это отличная практика. Она предотвращает случайную перезапись переменной.

Константы помечаются квалификатором типа 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.0
double b = -7e-2; // -7x10^-2 = -0.07

Напишите экспоненциальное представление числа 0.00002.

Если вы раньше не работали с экспоненциальной записью, самое время разобраться в ней.


Представим число 0.00002 в виде мантиссы и порядка. Мантисса: 2. Порядок: -5.

2e-5
Задача # 4

Тип 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; // true
bool b = -5 != -5; // false

std::string s = "";

bool c = s.empty(); // true
bool 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
Задача # 5

Логические операторы

Логические операторы применимы ко всем выражениям, которые приводятся к 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); // true
std::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); }
Задача # 6

Арифметические операторы

Арифметические операторы (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 + 5
x -= y; // x = x - y
x *= 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
Задача # 7

Как быть, если вы сомневаетесь, нужны ли в выражении скобки? Если без скобок код трудно читать, то ставьте их! Например, выражение из задачи выше со скобками выглядит проще: (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
Задача # 8


Что будет выведено в консоль?

В случае ошибки напишите err.

int c = 2;
int C = 5;
std::print("{}", c++ * ++C);

В этом выражении у постфиксного оператора максимальный приоритет. Следующим по приоритету выполнится префиксный оператор. И лишь затем — оператор умножения. Мы получим 2 * 6.

12
Задача # 9


Резюме

  • Функция main() — это точка входа в программу.
  • Если функция ничего не возвращает, то тип ее возвращаемого значения void.
  • При создании переменных всегда инициализируйте их значением.
  • Неизменяемые переменные помечаются квалификатором типа const.
  • Несколько фундаментальных типов: bool, int, double, char, void.
  • Пара библиотечных типов: std::size_t, std::string.
  • Операторы сравнения: ==, !=, <, >, <=, >=.
  • Логические операторы: &&, ||, !.
  • Арифметические операторы: +, -, *, /, %.
  • Операторы составного присваивания — это краткая форма выполнения над переменной арифметического действия и присваивания ей. Например, x *= 2.
  • Операторы инкремента ++ и декремента -- бывают префиксными и постфиксными.
Отправка...
Наша группа в telegram. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!