Этот раздел является переводом туториала C++ Language
Полезность программы "Hello, world", рассмотренной в предыдущем разделе, весьма сомнительна. Нам пришлось написать несколько строк кода, скомпилировать их, а затем выполнить полученную программу для того, чтобы в результате получить простое предложение, написанное на экране. Намного быстрее ввести это предложение вручную.
Тем не менее программирование не ограничивается печатью простых текстов на экране. Для того чтобы пойти немного дальше и получить возможность писать программы, которые выполняют полезные задачи, реально помогающие в работе, нам нужно ввести понятие переменных.
Давайте представим, что я попрошу Вас запомнить число 5, а затем я попрошу запомнить также число 2 в то же самое время. Вы только что сохранили два различных значения в памяти (5 и 2). Сейчас, если я попрошу Вас прибавить 1 к первому числу, которое я назвал, Вы должны сохранить число 6 (т.е. 5+1) и 2 в своей памяти. Теперь мы можем, например, вычесть эти значения и получить 4 в качестве результата.
Весь процесс, описанный выше, это подобие того, что компьютер может делать с двумя переменными. Такой же процесс может быть выражен в C++ следующим набором операторов:
a = 5;
b = 2;
a = a + 1;
result = a - b;
Очевидно, что это очень простой пример, так как мы использовали только два небольших целочисленных значения, но компьютер может хранить миллионы чисел в одно и то же время и производить сложные математические операции с ними.
Таким образом, мы можем определить переменную, как область памяти для хранения значения.
Каждой переменной нужно имя, которое идентифицирует его и отличает от других. Например, в предыдущем коде именами переменных были a, b и result, но мы можем называть переменные любыми именами, если они являются допустимыми идентификаторами в C++.
Идентификаторы
Допустимый идентификатор - это последовательность одной или нескольких букв, цифр или символов подчеркивания (_). Пробелы, знаки пунктуации и другие символы не могут быть частью идентификатора. Кроме того, идентификаторы должны всегда начинаться с буквы. Они также могут начинаться с символа подчеркивания (_), но такие идентификаторы в большинстве случаев считаются зарезервированными для специфичных для компилятора ключевых слов или внешних идентификаторов, так же как и идентификаторы, содержащие два последовательных символа подчеркивания в любом месте. Идентификаторы не могут начинаться с цифры.
C++ использует набор ключевых слов для определения операций и описания данных; таким образом, идентификаторы, созданные программистом, не могут совпадать с этими ключевыми словами:
alignas, alignof, and, and_eq, asm, auto, bitand, bitor, bool, break, case, catch, char, char16_t, char32_t, class, compl, const, constexpr, const_cast, continue, decltype, default, delete, do, double, dynamic_cast, else, enum, explicit, export, extern, false, float, for, friend, goto, if, inline, int, long, mutable, namespace, new, noexcept, not, not_eq, nullptr, operator, or, or_eq, private, protected, public, register, reinterpret_cast, return, short, signed, sizeof, static, static_assert, static_cast, struct, switch, template, this, thread_local, throw, true, try, typedef, typeid, typename, union, unsigned, using, virtual, void, volatile, wchar_t, while, xor, xor_eq
Очень важно: язык C++ является языком, чувствительным к регистру. Это означает, что идентификатор, написанный заглавными буквами, не является эквивалентом другому идентификатору с тем же именем, записанным маленькими буквами. Так, например, переменная RESULT это не то же самое, что переменная result или Result. Это три разных идентификатора, обозначающих три различные переменные.
Основные типы данных
Значения переменных хранятся где-то в неопределенном месте памяти компьютера в виде нулей и единиц. Нашей программе нет необходимости знать точное место хранения переменной; она может просто ссылаться на него по имени переменной. Что нужно программе, чтобы понять, какой тип данных хранится в переменной? Хранение целого числа, буквы или большого числа с плавающей точкой неодинаково; даже несмотря на то что они представлены в виде нулей и единиц, интерпретируются они по-разному, и во многих случаях занимают различное количество памяти.
Основные типы данных - это типы, реализованные непосредственно в языке, и представляют собой основные единицы хранения данных, изначально поддерживаемые большинством систем. Главным образом, они могут быть разделены на:
- Символьные типы: Они могут представлять единичный символ, такой как 'A' или '$'. Самым простым типом является тип char, представляющий собой однобайтовый символ. Также предоставляются другие типы для более "широких" символов. Про широкобайтовую кодировку довольно неплохо написано в начале книги "Windows via C/C++" авторов К.Назара, Дж.Рихтера. И, хотя книга посвящена программированию под Windows, суть вопроса излагается довольно внятно (прим. переводчика).
- Целочисленные типы: Они могут хранить значение целого числа, например 7 или 1024. Существуют эти типы различных размеров и могут быть как знаковыми, так и беззнаковыми, в зависимости от того, поддерживают они отрицательные значения или нет.
- Типы с плавающей точкой: Они могут представлять вещественные величины, такие как 3.14 или 0.01, с различной степенью точности, зависящий от того, какой тип с плавающей точкой из трех возможных используется.
- Логический тип: Логический тип данных, известный в C++ как bool, может находиться только в одном из двух состояний: true или false.
Вот полный список основных типов в C++:
Группа | Имя типа* | Примечания о размере/точности |
---|---|---|
Символьные типы | char | Размер ровно один байт. Как минимум 8 бит. |
char16_t | Не меньше, чем char. Как минимум 16 бит. | |
char32_t | Не меньше, чем char16_t. Как минимум 32 бита. | |
wchar_t | Представляет наибольший набор поддерживаемых символов. | |
Целочисленные типы (знаковые) | signed char | Такого же размера, как char. Как минимум 8 бит. |
signed short int | Не меньше, чем char. Как минимум 16 бит. | |
signed int | Не меньше, чем short Как минимум 16 бит. | |
signed long int | Не меньше, чем int. Как минимум 32 бита. | |
signed long long int | Не меньше, чем long. Как минимум 64 бита. | |
Целочисленные типы (беззнаковые) | unsigned char | (такой же размер, как у соответствующих знаковых типов) |
unsigned short int | ||
unsigned int | ||
unsigned long int | ||
unsigned long long int | ||
Типы с плавающей точкой | float | |
double | Точность не меньше, чем у float | |
long double | Точность не меньше, чем у double | |
Логический тип | bool | |
Тип void | void | не хранится |
Пустой указатель (null pointer) | decltype(nullptr) |
* Имена некоторых целочисленных типов могут быть кратко записаны без компонентов signed или int - для идентификации типа требуется только часть имени, записанная не курсивом; часть, записанная курсивом, является необязательной. То есть signed short int может быть сокращено до signed short, short int или просто short; все это идентифицирует один и тот же основной тип.
Внутри каждой группы выше различия между типами заключается только в их размере (то есть, сколько места они занимают в памяти): первый тип в каждой группе является наименьшим, а последний наибольшим, каждый тип как минимум не меньше, чем предыдущий в той же группе. Кроме того, типы в группе имеют одни и те же свойства.
Обратите внимание, что в таблице за исключением типа char (который имеет размер ровно один байт), ни один из основных типов не имеет определенного стандартного размера (кроме минимального размера, в большинстве случаев). Таким образом, типы не требуют (и во многих случаях не соответствуют) именно этого минимального размера. Это не означает, что размер этих типов не определен, но не существует стандартного размера для всех компиляторов и машин; каждая реализация компилятора может определять размеры для этих типов, которые наилучшим образом соответствуют архитектуре, на которой будет работать программа. Эта довольно общая спецификация для типов предоставляет языку C++ много гибкости, чтобы быть адаптированным для оптимальной работы на всех типах платформ настоящего и будущего.
Размеры типов выражены в битах; чем больше битов имеет тип, тем больше он может принимать уникальных значений, но в то же время, требует больше пространства в памяти:
Размер | Число уникальных значений | Примечания |
---|---|---|
8 бит | 256 | =28 |
16 бит | 65 536 | =216 |
32 бита | 4 294 967 296 | =232 |
64 бита | 18 446 744 073 709 551 616 | =264 |
Если целочисленный тип имеет разные представления (как знаковый, так и беззнаковый), то это означает, что диапазон значений, который он может принимать, шире; например, 16-битное беззнаковое целое может принимать 65536 различных значений в диапазоне от 0 до 65535, в то время как соответствующее ему знаковое представление может принимать, в большинстве случаев, значения от -32768 до 32767. Обратите внимание, что диапазон положительных значений примерно вдвое меньше в знаковых типах по сравнению с беззнаковыми, так как один из 16 бит используется для знака; это относительно небольшое различие в диапазонах и редко оправдывает использование беззнаковых типов, основанное исключительно на диапазоне положительных значений, который они могут принимать.
Для типов с плавающей точкой, размер влияет на их точность использованием большего или меньшего числа бит для мантиссы и экспоненты.
Если размер или точность типа не является проблемой, тогда обычно используются типы char, int и double для представления символов, целых чисел и значений с плавающей точкой соответственно. Другие типы в соответствующих группах используются только в весьма специфических случаях.
Свойства основных типов в конкретной реализации системы и компилятора можно получить с помощью классов numeric_limits (см. стандартный заголовочный файл <limits>). Если по некоторым причинам требуются типы определенных размеров, библиотека определяет конкретные псевдонимы типа фиксированного размера в заголовке <cstdint>.
Типы, описанные выше (символы, целые числа, числа с плавающей точкой и логический тип), известны как арифметические типы. Но существуют два дополнительных основных типа: void, обозначающий отсутствие типа, и тип nullptr, который представляет собой особый тип указателя. Оба типа будут обсуждаться подробнее в главе об указателях.
C++ поддерживает разнообразные типы, базирующиеся на основных типах, рассмотренных выше; эти другие типы известны как составные типы данных и являются одни из основных преимуществ языка C++. Мы также рассмотрим их более подробно в будущих главах.
Объявление переменных
C++ является языком со строгой типизацией и требует объявления типа каждой переменной перед ее использованием. Это сообщает компилятору размер памяти, резервируемый для переменной, и интерпретацию ее значения. Синтаксис для объявления новой переменной в C++ прост: мы просто пишем тип переменной перед ее именем (т.е. идентификатором). Например:
int a;
float mynumber;
Это два допустимых объявления переменных. В первом объявляется переменная типа int с идентификатором a. Во втором объявляется переменная типа float с идентификатором mynumber. Определенные однажды, эти переменные могут быть использованы во всей своей области видимости.
Если объявляется более одной переменной одного и того же типа, то их можно объявить в одном выражении, разделяя запятыми. Например:
int a, b, c;
Здесь определяются три переменных (a, b и c), все они типа int. Это выражение имеет точно такой же смысл, что и:
int a;
int b;
int c;
Чтобы увидеть, что из себя представляют объявления переменных в программе, давайте посмотрим на весь C++ код примера о Вашей умственной памяти, предложенный в начале этой главы:
// работа с переменными
#include <iostream>
using namespace std;
int main ()
{
// объявление переменных:
int a, b;
int result;
// работа:
a = 5;
b = 2;
a = a + 1;
result = a - b;
// печать результата:
cout << result;
// завершение программы:
return 0;
}
4
Не беспокойтесь, если что-либо, кроме самих объявлений переменных, выглядит для Вас странно. Большая часть будет объяснена более детально в следующих главах.
Инициализация переменных
Когда переменные в приведенном выше примере объявляются, они имеют неопределенное значение до тех пор, пока им не присваивается значение в первый раз. Но переменная может иметь определенное значение с момента объявления. Это называется инициализацией переменной.
В C++ есть три способа инициализировать переменные. Они эквивалентны и напоминают эволюцию языка с годами:
Первый, известный как c-like initialization (потому что он унаследован от языка Си), состоит в добавлении знака равенства, за которым следует значение, присваиваемое инициализируемой переменной:
type identifier = initial_value;
Например, чтобы объявить переменную типа int с именем x и инициализировать ее нулем с момента объявления, мы можем написать:
int x = 0;
Второй метод, известный как инициализация в конструкторе (введен в язык C++), заключает начальное значение в круглые скобки (()):
type identifier (initial_value);
Например:
int x (0);
Наконец, третий метод, известный как универсальная инициализация, похож на предыдущий, но использует фигурные скобки ({}) вместо круглых (введен после пересмотра стандарта C++, в 2011 году):
type identifier {initial_value};
Например:
int x {0};
Все три способа инициализации переменных являются корректными и эквивалентными в C++.
// инициализация переменных
#include <iostream>
using namespace std;
int main ()
{
int a=5; // начальное значение: 5
int b(3); // начальное значение: 3
int c{2}; // начальное значение: 2
int result; // начальное значение не определено
a = a + b;
result = a - c;
cout << result;
return 0;
}
6
Определение типа: auto и decltype
При инициализации новой переменной компилятор может определить тип переменной автоматически через инициализатор. Для этого достаточно использовать auto как описатель типа для переменной:
int foo = 0;
auto bar = foo; // то же самое, что: int bar = foo;
Здесь bar объявляется типом auto; таким образом, тип bar - это тип значения, используемого для его инициализации: в данном случае используется тип переменной foo, т.е. тип int.
Переменные, которые не инициализируются, также могут использовать определение типа со спецификатором decltype:
int foo = 0;
decltype(foo) bar; // то же, что: int bar;
auto и decltype - это мощные средства, недавно добавленные в язык. Но определение типа этими средствами предназначено для использования либо когда тип не может быть получен другими средствами, либо когда их использование улучшает читаемость кода. Два примера выше, вероятно, не являются такими случаями. На самом деле, они, вероятно, ухудшили читаемость, поскольку во время чтения кода необходимо найти тип foo, чтобы узнать тип bar.
Введение в строки
Основные типы представляют наиболее простые типы, обрабатываемые на машинах, на которых может выполняться код. Но одна из основных сильных сторон языка C++ - это его богатый набор составных типов, строительными блоками для которых являются основные типы.
Пример составного типа это класс строки. Переменные этого типа способны хранить последовательности символов, такие как слова или предложения. Очень полезная функция!
Первым отличием от основных типов данных является то, что для объявления и использования объектов (переменных) этого типа, программа должна включать заголовок стандартной библиотеки, в котором определен тип (заголовок <string>):
// моя первая строка
#include <iostream>
#include <string>
using namespace std;
int main ()
{
string mystring;
mystring = "This is a string";
cout << mystring;
return 0;
}
This is a string
Как Вы можете видеть в предыдущем примере, строки могут быть инициализированы любым корректным строковым литералом, так же как переменные арифметических типов могут быть инициализированы любым корректным числовым литералом. Как и в случае с основными типами, для строк являются допустимыми все формы инициализации:
string mystring = "This is a string";
string mystring ("This is a string");
string mystring {"This is a string"};
Строки могут также производить все другие основные операции, которые доступны для основных типов, как то объявление без инициализации и изменение его значения во время выполнения:
// моя первая строка
#include <iostream>
#include <string>
using namespace std;
int main ()
{
string mystring;
mystring = "This is the initial string content";
cout << mystring << endl;
mystring = "This is a different string content";
cout << mystring << endl;
return 0;
}
This is the initial string content
This is a different string content
Примечание: вставка манипулятора endl завершает строку (печатает символ новой строки).
Класс string это составной тип. Как вы можете видеть в приведенном выше примере, составные типы используются так же, как базовые типы: тот же синтаксис используется для объявления переменных и для их инициализации.
Для более подробной информации о стандартных строках C++ смотрите справку по классу string.