Содержание

 

Основные типы данных

Базовые типы данных

  • void - Тип без значения
  • bool - Логический тип. Может принимать одну из двух значений true (истина) и false (ложь). Размер занимаемой памяти для этого типа стандартом не определен
  • char - Представляет один символ в кодировке ASCII. Занимает в памяти 1 байт (8 бит). Может хранить любое значение из диапазона от -128 до 127, либо от 0 до 255
  • short - Представляет целое знаковое число размером 2 байта (16 бит). Данный тип также имеет синонимы short intsigned short intsigned short
  • int - Представляет целое знаковое число. В зависимости от архитектуры процессора может занимать 2 байта (16 бит) или 4 байта (32 бита), но размер должен быть больше или равен размеру типа short и меньше или равен размеру типа long. Данный тип имеет синонимы signed int и signed
  • long - Представляет целое знаковое число размером 4 байта (32 бита). У данного типа также есть синонимы long intsigned long int и signed long
  • float - Представляет вещественное число одинарной точности с плавающей точкой в диапазоне -3.4E-38 до 3.4E+38. В памяти занимает 4 байта (32 бита)
  • double - Представляет вещественное число двойной точности с плавающей точкой в диапазоне -1.7E-308 до 1.7E+308. В памяти занимает 8 байт (64 бита)

Модификации базовых типов данных

  • signed char - Представляет один символ. Занимает в памяти 1 байт (8 бит). Может хранить любое значение из диапазона от -128 до 127
  • unsigned char - Представляет один символ. Занимает в памяти 1 байт (8 бит). Может хранить любое значение из диапазона от 0 до 255
  • unsigned short - Представляет беззнаковое целое число. Занимает в памяти 2 байта (16 бит). Данный тип также имеет синоним unsigned short int
  • unsigned int - Представляет беззнаковое целое число. В зависимости от архитектуры процессора может занимать 2 байта (16 бит) или 4 байта (32 бита). В качестве синонима этого типа может использоваться unsigned
  • unsigned long - Представляет беззнаковое целое число. Занимает в памяти 4 байта (32 бита). Имеет синоним unsigned long int
  • long long - Представляет знаковое целое число. Занимает в памяти, как правило, 8 байт (64 бита). Имеет синонимы long long intsigned long long int и signed long long
  • unsigned long long - Представляет беззнаковое целое число. Занимает в памяти, как правило, 8 байт (64 бита). Имеет синоним unsigned long long int
  • long double - Представляет вещественное число двойной точности с плавающей точкой не менее 8 байт (64 бит). В зависимости от размера занимаемой памяти может отличаться диапазон допустимых значений

Дополнительные символьные типы

  • wchar_t - Представляет расширенный символ. На Windows занимает в памяти 2 байта (16 бит), на Linux - 4 байта (32 бита). Может хранить любое значение из диапазона от 0 до 65 535 (при 2 байтах), либо от 0 до 4 294 967 295 (для 4 байт).
  • char16_t - Представляет один символ в кодировке Unicode. Занимает в памяти 2 байта (16 бит). Может хранить любое значение из диапазона от 0 до 65 535
  • char32_t - Представляет один символ в кодировке Unicode. Занимает в памяти 4 байта (32 бита). Может хранить любое значение из диапазона от 0 до 4 294 967 295

Автоматическое назначение типа

  • auto - Начиная с C++11 ключевое слово auto при инициализации переменной может использоваться вместо типа переменной, чтобы сообщить компилятору, что он должен присвоить тип переменной исходя из инициализируемого значения. Переменные, объявленные без инициализации, не могут использовать эту особенность. В C++14 функционал ключевого слова auto был расширен до автоматического определения типа возвращаемого значения функции.

Примечания

Для вывода на консоль символов wchar_t следует использовать не std::cout, а поток std::wcout. При этом поток std::wcout может работать как с char, так и с wchar_t. А поток std::cout для переменной wchar_t вместо символа будет выводить его числовой код.

В стандарте С++11 были добавлены типы char16_t и char32_t, которые ориентированы на использование Unicode. Однако на уровне ОС пока не реализованы потоки для работы с этими типами. Поэтому если потребуется вывести на консоль значения переменных этих типов, то необходимо преобразовать переменные к типам char или wchar_t.

Стандарт устанавливает лишь минимальные значения размера, который тип занимает в памяти. Например, для типов int и short минимальное значение - 16 бит, для типа long - 32 бита. При этом размер типа long должен быть не меньше размера типа int, а размер типа int - не меньше размера типа short, а размер типа long double должен быть больше double. Для определения точного размера определенного типа в С++ есть оператор sizeof(), который возвращает размер памяти в байтах, которую занимает переменная.

Преобразование типов

Преобразование типов происхоит когда требуется конвертация значений из одного типа данных в другой. Преобразование типов может происходить в следующих случаях:

  • присваивание или инициализация переменной
  • передача значений в функцию
  • возврат значения из функции
  • вычисление арифметических выражений

Перобразование может быть неявным, когда компилятор автоматически конвертирует один тип в другой, и явным, когда программист сам указывает в какой тип нужно сконвертировать значение.

 

Неявное преобразование типов

Преобразование, когда значение из одного типа конвертируется в другой тип, большего размера, называется числовым расширением и всегда безопасно и не приводит к потере данных.

Когда значение типа данных конвертируется в аналогичный, но меньший по размеру тип данных, или конвертация происходит между разными типами данных, то это называется числовой конверсией и может (но не всегда) приводить к потере данных.

Арифметические выражения

При обработке выражений компилятор разбивает каждое выражение на отдельные подвыражения. Арифметические операторы требуют, чтобы их операнды были одного типа данных. Чтобы это гарантировать, компилятор использует следующие правила:

  • если операндом является целое число меньше (по размеру/диапазону) типа int, то оно подвергается интегральному расширению в int или в unsigned int
  • если операнды разных типов данных, то компилятор вычисляет операнд с наивысшим приоритетом и неявно конвертирует тип другого операнда в такой же тип, как у первого

Приоритет типов операндов для арифметических операций:

  • long double (самый высокий);
  • double
  • float
  • unsigned long long
  • long long
  • unsigned long
  • long
  • unsigned int
  • int (самый низкий)

 

Явное преобразование типов

В языке C++ есть 5 видов операций явного преобразования типов:

  • конвертация в стиле языка C
  • использование операции static_cast
  • использование операции dynamic_cast
  • использование операции const_cast
  • использование операции reinterpret_cast

Конвертация в стиле языка C

В программировании на языке Cи явное преобразование типов данных выполняется с помощью оператора ( ).

Пример кода:

int i1 = 11;
int i2 = 3;
float x = (float)i1 / i2;

Язык C++ также позволяет использовать этот оператор следующим образом:

int i1 = 11;
int i2 = 3;
float x = float(i1) / i2;

Конвертация в стиле языка C не проверяется компилятором, поэтому она может быть неправильно использована, например привести к переполнению.

Операция static_cast

static_cast<тип>(выражение)

Операция static_cast в языке C++ осуществляет явное и допустимое приведение заданного выражения в указанный тип данных. Операция static_cast аналогична операции «круглые скобки» с одним исключением: она не выполняет приведение указателей на неродственные типы (для этого применяется операция reinterpret_cast). Основным преимуществом операции static_cast является проверка выполнения компилятором во время компиляции с генерацией предупреждений, что усложняет возможность возникновения ошибок.

Пример кода:

int i1 = 11;
int i2 = 3;
float x = static_cast<float>(i1) / i2;

Операция dynamic_cast

dynamic_cast<тип>(указатель/ссылка)

В языке C++ операция dynamic_cast используется для конвертации указателей родительского класса в указатели дочернего класса. Это является наиболее распространенным применением оператора dynamic_cast.Этот процесс называется приведением к дочернему типу (или «понижающим приведением типа»).

Если dynamic_cast не может выполнить конвертацию, то он возвращает нулевой указатель, поэтому нужно делать проверку результата динамического приведения на нулевой указатель. Оператор dynamic_cast также может использоваться и со ссылками. Работа dynamic_cast со ссылками аналогична работе с указателями, но поскольку в языке C++ не существует «нулевой ссылки», то dynamic_cast не может возвратить «нулевую ссылку» при сбое. Вместо этого dynamic_cast генерирует исключение типа std::bad_cast.

Поскольку динамическое приведение выполняет проверку во время работы программы, использование оператора dynamic_cast немного снижает производительность. Понижающее приведение также может быть выполнено и через оператор static_cast. Основное отличие заключается в том, что static_cast не выполняет проверку во время запуска программы. Это позволяет оператору static_cast быть быстрее, но опаснее оператора dynamic_cast.

Существуют случаи, в которых понижающее приведение с использованием оператора dynamic_cast не работает:

  • Наследование типа private или типа protected
  • Классы, которые не объявляют или не наследуют классы с какими-либо виртуальными функциями (и, следовательно, не имеют виртуальных таблиц)
  • Случаи, связанные с виртуальными базовыми классами

В общем, использование виртуальных функций должно быть предпочтительнее использования понижающего приведения. Однако в следующих случаях понижающее приведение является лучшим вариантом:

  • Если нет возможности изменить родительский класс, чтобы добавить в него свою виртуальную функцию (например, если родительский класс является частью Стандартной библиотеки С++)
  • Если требуется доступ к чему-либо, что есть только в дочернем классе (например к функции, которая существует только в дочернем классе)
  • Если добавление виртуальной функции в родительский класс не имеет смысла. В таком случае, в качестве альтернативы, если не нужно создавать объект родительского класса, можно использовать чистую виртуальную функцию

Пример кода:

Parent *p = getParentObject();
Child *ch = dynamic_cast<Child*>(p);

Операция const_cast

const_cast<тип>(указатель/ссылка)

Операция const_cast используется для снятия/установки модификаторов const, volatile, mutable. Часто это применяется, чтобы обойти неудачную архитектуру программы или библиотеки, для стыковки С и С++, для передачи информации через обобщённые указатели void*, для одновременного написания const- и не-const-версии функции.

Операцию const_cast для сброса const следует использовать только в крайних случаях, когда точно известно поведение программы. А вот использование операции const_cast ради добавления константности можно использовать без опасений получить неопределенное поведение.

При использовании операции const_cast выражение должно быть ссылкой или указателем, а тип должен совпадать с исходным типом вплоть до модификаторов const, volatile и mutable.

Пример кода:

void foo(char*){}
 
int main()
{
    char const* str = "";
    foo(const_cast<char*>(str)); //снятие константности позволяет использовать функцию
}

Операция reinterpret_cast

reinterpret_cast<тип>(выражение)

Оператор приведения reinterpret_cast используется для приведения несовместимых типов с сохранением битового представления. Может приводить целое число к указателю, указатель к целому числу, указатель к указателю (это же касается и ссылок).

Является функционально усеченным аналогом приведения типов в стиле языка С.

Отличие состоит в том, что reinterpret_cast не может снимать квалификаторы const и volatile, а также не может делать небезопасное приведение типов не через указатели, а напрямую по значению. Например, переменную типа int к переменной типа double привести при помощи reinterpret_cast нельзя.

Ограничения на <тип>:

  • Если <выражение> - это значение порядкового типа или указатель, то <тип> может быть порядковым типом или указателем
  • Если <выражение> - это ссылка, то <тип> должен быть ссылкой

Пример кода:

int i;
char *p = "Это строка";
i = reinterpret_cast(p);	// Приведение указателя к целому числу

 

Псевдонимы типов

Ключевое слово typedef позволяет программисту создать псевдоним для любого типа данных и использовать его вместо фактического имени типа.

typedef  <тип>  <псевдоним>;

Обычно к псевдонимам typedef добавляют окончание _t.

Псевдонимы используются для улучшения документации и читаемости кода. Например чтобы понимать смысл возвращаемой функцией значения, а не просто int или long.

Псевдонимы также позволяют изменить базовый тип объекта без внесения изменений в большое количество кода. Например, если для хранения какого-либо номера использовался тип short, а теперь нужно его увеличить до int, то псевдонимы позволят это сделать без необходимости поиска и замены типов во всем коде, да еще и с анализом нужных переменных.

Еще одним большим преимуществом typedef является возможность скрывать специфические для определенных платформ детали. Поскольку char, short, int и long не указывают свой размер, то для кроссплатформенных программ довольно часто используется typedef для определения псевдонимов, которые включают размер типа данных в битах. Например, int8_t — это 8-битный signed int, int16_t — это 16-битный signed int, а int32_t — это 32-битный signed int.

Еще одно использование псевдонимов - это упрощение сложных типов. Например вместо указания типа std::vector<std::pair<std::string, int>>, можно задать для него псевдоним pairlist_t и в дальнейшем использовать уже его.

Новый синтаксис

Из-за неочевидного порядка указания типа и псевдонима в typedef, в стандарте С++11 ввели новый улучшенный синтаксис, который имитирует способ объявления переменных. Этот синтаксис называется type alias.

using  <псевдоним> = <тип>;

Следует обратить внимание, что хотя и используется ключевое слово using, оно не имеет ничего общего с using-стейтментами.

Новый синтаксис создания псевдонимов создает меньше проблем при использовании в сложных ситуациях, и его рекомендуется применять вместо обычного typedef, если компилятор поддерживает C++11.