Синтаксис
Это одно из первых понятий, которое вам стоит знать. Он буквально вам говорит какие конструкции можно использовать, а какие нет.
Где ошибка?
int main() {
std::cout << "hello, world << std::endl;
//Error(active) E0008 missing closing quote
return 0;
}
Ошибка заключается в незакрытой верхне кавычке. Если мы сделаем так:
std::cout << "hello, world" << std::endl;
Ошибка исчезнет.
У нас при написании кода существует свод правил, при нарушении которых компилятор не даст нам запустить код.
Таких моментов очень много, допустим отсутствие точки с запятой после std::endl
std::cout << "hello, world" << std::endl
//syntax error: missing ';' before 'return'
return 0;
Или отсутствие закрывающейся скобочки:
int main() {
std::cout << "hello, world" << std::endl;
return 0;
//Error C1075 '{': no matching token found
Такие ошибки проверяются компилятором на этапе сборки приложения. Кроме них есть ошибки, которые могут возникнуть во время выполнения, допустим переполнение стека, обращение к невалидному участку памяти и так далее.
То есть скомпилировать код это только часть успеха.
Комментарии в коде
Как вы уже могли заметить выше я использовал // для того, чтобы добавить комментарии в код.
В C++ есть два типа комментариев - однострочный и многострочный
// однострочный комментарий
/*
многострочный
комментарий
*/
Комментарии могут использоваться как по прямому назначению для внесения ясности относительно участков кода, так и в целях отладки, когда нужно временно исключить участок кода из выполнения.
Весь текст записанный после // или заключённый в /* */ удаляется на этапе препроцессинга и не попадает в компиляцию.
Директива include
Нужно кратко объяснить этот момент прямо сейчас, хоть и тема препроцессоров достойна большего внимания, но без этого у нас банально не будет работать стандартный вывод в консоль, который используется в C++. Эта директива используется для подключения заголовочных файлов и как частное следствие - исходного кода библиотек.
#include <iostream>
int main() {
std::cout << "hello, world" << std::endl;
}
Без подключения #include <iostream> мы получим ошибки
E0135 namespace "std" has no member "endl"
E0135 namespace "std" has no member "cout"
Типы данных
C++ это типизированный язык программирования. В нём переменная при создании имеет тип и не меняет его до окончания своего существования.
Существует множество типов.
У типа есть имя, диапазон значение и количество байт занимаемых в памяти. Некоторые типы в зависимости от архитектуры могут занимать разное количество байт в памяти.
Допустим самый распространённый тип это int. Он имеет диапазон значений от -2,147,483,648 до 2,147,483,647 и занимает в памяти 4 байта.
Этот тип используется в большинстве случаев для хранения целых чисел.
Подробнее об остальных типах, и их диапазонах можно почитать тут и тут тут.
Есть несколько фундаментальных типов:
- Целочисленные:
int,long,long long,short,char - Логический:
bool - С плавающей точкой:
float,double
Получение диапазона значений
Диапазон значений конкретного типа может быть получен из констант определённых в заголовочном файле <climits>
#include <iostream>
#include <climits>
int main() {
std::cout << INT_MIN << std::endl;
std::cout << INT_MAX << std::endl;
}
-2147483648
2147483647
Проблема <climits> заключается в том, что это C-style header, который пришёл из языка C.
Сейчас есть возможность использовать std::numeric_limits, который является универсальным.
Получение размера типа
Размер типа может быть получен с использованием оператора sizeof применяемого как к имени типа, так и к конкретной переменной
std::cout << sizeof(int) << std::endl;
int a = 123;
std::cout << sizeof(a) << std::endl;
4
4
Переменные
Это то, что хранит данные в любом языке программирования.
int main() {
int count = 10;
double pi = 3.14;
}
Сначала мы указываем тип переменной, название а затем можем присвоить ей значение.
int a = 10; //инициализация
int b; //объявление
b = 20; //инициализация
Значение можно не присваивать, тогда это называется объявление. Когда мы присваиванием значение переменной это называется инициализацией.
int a{10};
Так же вы можете встретить такой синтаксис инициализации, обычно он не применяется для одиночных переменных, а чаще встречается при работе с массивами.
int a(10);
Этот тип инициализации так же редко применяется к обычным переменным, но тем не менее существует.
int main() {
std::cout << a << std::endl; // 'a': undeclared identifier
int a;
}
Мы не можем использовать переменную, до её объявления.
int main() {
int a;
a = a + 1; // uninitialized local variable 'a' used
std::cout << a << std::endl; // uninitialized local variable 'a' used
}
Мы не можем использовать значение объявленной, но не инициализированной переменной.
Приведение типов
В C++ существуют неявные преобразования типов, часть правил которых пришла из языка C.
int a = 123;
float b = a;
std::cout << b << std::endl;
123
В этом случае происходит автоматическое приведение типа int к типу float. Всё в порядке и это стандартная практика.
А вот тут уже стоит обратить внимание:
float a = 12.3f;
int b = a;
std::cout << b << std::endl;
12
В случае выше у нас происходит усечение числа из-за того, что мы приводим тип float к типу int, который не может хранить дробную часть.
Подобное поведение с последующей потерей части информации будет и в таком случае тоже:
long long longValue = 3000000000;
int intValue = longValue;
std::cout << longValue << std::endl;
std::cout << intValue << std::endl;
3000000000
-1294967296
Тип int в x64 занимает 4 байта, в то время как long long занимает 8 байт и, соответственно, может хранить большие значения.
При таком преобразовании значение 3000000000 типа long long не может быть помещено в тип int и возникает потеря данных.
По сути когда мы используем такое приведение
float a = 12.3f;
int b = a;
Компилятор немного упрощает нам жизнь, используя неявное преобразование, которое по результату похоже на явное приведение:
float a = 12.3f;
int b = (int)a;
В языке C существовал только один тип приведения типов, который указан выше.
В некоторых случаях, такое приведение автоматически не возможно - нужно будет указывать его явно или вовсе реализовывать самостоятельно.
#include <iostream>
int main() {
std::string a = 10;
//no suitable constructor exists to convert from "int" to
//"std::basic_string<char, std::char_traits<char>, std::allocator<char>>"
}
int main() {
int a = "10";
//'initializing': cannot convert from 'const char [3]' to 'int'
}
В этих случаях у нас не определен способ автоматического преобразования строк в числа и обратно. Чтобы решить эту проблему необходимо реализовывать дополнительную логику.
Нужно отметить, что тема преобразования типов должна быть разобрана отдельно, поскольку уровень владения языком для её восприятия должен быть выше, чем при прочтении материала по первым темам.
И, конечно, разбирать static_cast, reinterpret_cast, const_cast в вводной части было бы опрометчиво.
Литералы
Литерал - это фиксированное значение, записанное прямо в коде.
int a = 10;
double b = 3.14;
char c = 'A';
std::string d = "some text";
Здесь 10, 3.14, 'A', "some text"- литералы.
Базовые литералы
Целочисленные литералы
int a = 123;
По умолчанию значение 123 считается типом int, если находятся в его диапазоне.
long long value = 9223372036854775807;
В ином случае число будет преобразовано в long или long long.
Литералы с плавающей точкой
Все числа, записанные в коде, с плавающей запятой имеют тип double по умолчанию.
double a = 3.14;
Если нужно явно указать float используется суффикс f
float a = 3.14f;
float b = 3.f; // так же можно не указывать 0 после точки
Символьные литералы
char c = 'A';
char newLine = '\n';
Для записи символьного литерала мы используем одинарные кавычки, такой литерал будет иметь тип char.
Строковые литералы
std::string str1 = "hello";
const char* str2 = "hello";
Строковые литералы записываются в двойных кавычках, могут быть преобразованы в разные типы, например std::string, const char*, std::wstring, wchar_t* или другой.
Изначально такой литерал имеет тип const char[6] где 6 это длинна строки "hello" +1. Добавление единицы к размеру обусловлено нулевым символом '\0' в конце строки, который добавляется автоматически.
Суффиксы и типизация литералов
Суффиксы позволяют явно задавать тип литерала, независимо от того, как его интерпретирует компилятор по умолчанию. Например:
unsigned int a = 10u; // unsigned
long b = 10l; // long
long long c = 10ll; // long long
unsigned long long value = 10ull; // комбинация u и ll
float a = 3.14f; // float
Это нужно в ситуациях, когда поведение кода зависит от типа, например при перегрузке функций.
Ввод / Вывод
printf / scanf
Начнём со стандартных способов вывода, которые пришли из языка C - printf и scanf.
#include <cstdio> //для их использования подключается этот заголовочный файл
int main() {
printf("Hello world\n");
return 0;
}
printf это функция, первый её аргумент это строка формата, а последующие - сами данные.
int value = 25;
printf("Value: %d\n", value);
Value: 25
Тут мы в строке используем форматный спецификатор %d, который означает что следующий аргумент (value) будет выведен как целое число.
Так же тут используется escape-последовательность \n, которая означает, что необходимо выполнить перенос строки в терминале после вывода.
Мы можем вывести два значения и более.
int value1 = 1;
int value2 = 2;
printf("value1: %d | value2: %d\n", value1, value2);
value1: 1 | value2: 2
Ввод значений с клавиатуры мы можем осуществлять используя scanf, но изначально будет возникать ошибка компиляции, если вы используете компилятор MSVC
int value;
scanf("%d", &value); //'scanf': This function or variable may be unsafe.
//Consider using scanf_s instead. To disable deprecation,
//use _CRT_SECURE_NO_WARNINGS. See online help for details.
printf("value: %d\n", value);
Чтобы избежать этой ошибки можно использовать scanf_s или указать определённый #define перед #include <cstdio>
#define _CRT_SECURE_NO_WARNINGS
#include <cstdio>
int main() {
int value;
scanf("%d", &value);
printf("value: %d\n", value);
return 0;
}
В этом случае ошибка не возникнет.
printf и scanf пришли в C++ из C, у меня нет необходимости использовать их в своём коде, по этой причине я не вижу смысла уделять им много внимания. Если у вас возникнет специфическая задача, я думаю, вам не составит труда найти по ним информацию.
std::cin / std::cout
Для повседневного использования и большинства задач серединой являются операторы std::cin и std::cout из #include <iostream>.
Оформите подписку, чтобы иметь доступ ко всем публикациям без ограничений. Вы получите мгновенный доступ к полным версиям материалов, исходному коду и новым публикациям сразу после их выхода.
Я регулярно публикую материал и разборы ситуаций для различных игр, движков, а так же подходов разработки включая Internal, External и DMA.