Найти тему
Реликт

Типы данных С++

Оглавление

Каждая переменная имеет определенный тип. И этот тип определяет, какие значения может иметь переменная, какие операции с ней можно производить и сколько байт в памяти она будет занимать. В языке C++ определены следующие базовые типы данных: логический тип bool, целочисленные типы, типа чисел с плавающей точкой, символьные типы. Рассмотрим эти группы по отдельности.

Логический тип


Логический тип
bool может хранить одно из двух значений: true (истинно, верно) и false (неверно, ложно). Например, определим пару переменных данного типа и выведем их значения на консоль:

#include <iostream>

int main()
{
bool isAlive {true};
bool isDead {false};
std::cout << "isAlive: " << isAlive << "\n";
std::cout << "isDead: " << isDead << "\n";
}

Консольный вывод программы:

-2

При выводе значения типа bool преобразуются в 1 (если true) и 0 (если false). Как правило, данный тип применяется преимущество в условных выражениях, которые будут далее рассмотрены.

Значение по умолчанию для переменных этого типа -
false.

Целочисленные типы


Целые числа в языке C++ представлены следующими типами:

  • signed char: представляет один символ. Занимает в памяти 1 байт (8 бит). Может хранить любой значение из диапазона от -128 до 127.
  • unsigned char: представляет один символ. Занимает в памяти 1 байт (8 бит). Может хранить любой значение из диапазона от 0 до 255.
  • char: представляет один символ в кодировке ASCII. Занимает в памяти 1 байт (8 бит). Может хранить любое значение из диапазона от -128 до 127, либо от 0 до 255.

    Несмотря на то, что данный тип представляет тот же диапазон значений, что и вышеописанный тип
    signed char, но они не эквивалентны. Тип char предназначен для хранения числового кода символа и в реальности может представлять как signed byte, так и unsigned byte в зависимости от конкретного компилятора.
  • short: представляет целое число в диапазоне от –32768 до 32767. Занимает в памяти 2 байта (16 бит).

    Данный тип также имеет псевдонимы
    short int, signed short int, signed short.
  • unsigned short: представляет целое число в диапазоне от 0 до 65535. Занимает в памяти 2 байта (16 бит).

    Данный тип также имеет синоним
    unsigned short int.
  • int: представляет целое число. В зависимости от архитектуры процессора может занимать 2 байта (16 бит) или 4 байта (32 бита). Диапазон предельных значений соответственно также может варьироваться от –32768 до 32767 (при 2 байтах) или от −2 147 483 648 до 2 147 483 647 (при 4 байтах). Но в любом случае размер должен быть больше или равен размеру типа short и меньше или равен размеру типа long

    Данный тип имеет псевдонимы
    signed int и signed.
  • unsigned int: представляет положительное целое число. В зависимости от архитектуры процессора может занимать 2 байта (16 бит) или 4 байта (32 бита), и из-за этого диапазон предельных значений может меняться: от 0 до 65535 (для 2 байт), либо от 0 до 4 294 967 295 (для 4 байт).

    Имеет псевдоним
    unsigned
  • long: в зависимости от архитектуры может занимать 4 или 8 байт и представляет целое число в диапазоне от −2 147 483 648 до 2 147 483 647 (при 4 байтах) или от −9 223 372 036 854 775 808 до +9 223 372 036 854 775 807 (при 8 байтах). Занимает в памяти 4 байта (32 бита) или.

    Имеет псевдонимы
    long int, signed long int и signed long
  • unsigned long: представляет целое число в диапазоне от 0 до 4 294 967 295. Занимает в памяти 4 байта (32 бита).

    Имеет синоним unsigned
    long int.
  • long long: представляет целое число в диапазоне от −9 223 372 036 854 775 808 до +9 223 372 036 854 775 807. Занимает в памяти 8 байт (64 бита).

    Имеет псевдонимы
    long long int, signed long long int и signed long long.
  • unsigned long long: представляет целое число в диапазоне от 0 до 18 446 744 073 709 551 615. Занимает в памяти, как правило, 8 байт (64 бита).

Имеет псевдоним unsigned long long int.

Для представления чисел в С++ применятся целочисленные литералы со знаком или без, типа -10 или 10. Например, определим ряд переменных целочисленных типов и выведем их значения на консоль:

#include <iostream>

int main()
{
signed char num1{ -64 };
unsigned char num2{ 64 };
short num3{ -88 };
unsigned short num4{ 88 };
int num5{ -1024 };
unsigned int num6{ 1024 };
long num7{ -2048 };
unsigned long num8{ 2048 };
long long num9{ -4096 };
unsigned long long num10{ 4096 };
std::cout << "num1 = " << num1 << std::endl;
std::cout << "num2 = " << num2 << std::endl;
std::cout << "num3 = " << num3 << std::endl;
std::cout << "num4 = " << num4 << std::endl;
std::cout << "num5 = " << num5 << std::endl;
std::cout << "num6 = " << num6 << std::endl;
std::cout << "num7 = " << num7 << std::endl;
std::cout << "num8 = " << num8 << std::endl;
std::cout << "num9 = " << num9 << std::endl;
std::cout << "num10 = " << num10 << std::endl;
}

Консольный вывод программы:

-3

Но стоит отметить, что все целочисленные литералы по умолчанию представляют тип int. Так, выше переменным разных типов присваивались различные числа - 64, -64, 88, -88, 1024 и т.д. Но все эти целочисленные литералы представляют тип int.

Однако мы можем использовать целочисленные литералы и других типов. Целочисленные литералы без знака (которые представляют unsigned-типы) имеют суффикс
u или U. Литералы типов long и long long имеют суффиксы L/l и LL/ll соответственно:

#include <iostream>

int main()
{
unsigned int num6{ 1024U }; // U - unsigned int
long num7{ -2048L }; // L - long
unsigned long num8{ 2048UL }; // UL - unsigned long
long long num9{ -4096LL }; // LL - long long
unsigned long long num10{ 4096ULL };// ULL - unsigned long long
std::cout << "num6 = " << num6 << std::endl;
std::cout << "num7 = " << num7 << std::endl;
std::cout << "num8 = " << num8 << std::endl;
std::cout << "num9 = " << num9 << std::endl;
std::cout << "num10 = " << num10 << std::endl;
}

Консольный вывод программы:

-4

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

Если число большое, то при вводе мы можем где-то ошибиться. Чтобы упростить читабельность чисел, начиная со стандарта C++14 в язык была добавлена возможность разделения разрядов числа с помощью одинарной кавычки '

#include <iostream>

int main()
{
int num{ 1'234'567'890 };
std::cout << "num = " << num << "\n"; // num = 1234567890
}

Консольный вывод программы:

-5

Различные системы исчисления


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

Чтобы указать, что число - шестнадцатеричное, перед числом указывается префикс
0x или 0X. Например:

int num1{ 0x1A}; // 26 - в десятичной
int num2{ 0xFF }; // 255 - в десятичной
int num3{ 0xFFFFFF }; //16777215 - в десятичной


Чтобы указать, что число - восьмеричное, перед числом указывается ноль
0. Например:

int num1{ 034}; // 26 - в десятичной
int num2{ 0377 }; // 255 - в десятичной


Бинарные литералы предваряются префиксом
0b или 0B:

int num1{ 0b11010}; // 26 - в десятичной
int num2{ 0b11111111 }; // 255 - в десятичной
Все эти типы литералов также поддерживают суффиксы U/L/LL:
unsigned int num1{ 0b11010U}; // 26 - в десятичной
long num2{ 0377L }; // 255 - в десятичной
unsigned long num3{ 0xFFFFFFULL }; //16777215 - в десятичной

Числа с плавающей точкой


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

Например, число 365 может быть записано в виде числа с плавающей точкой следующим образом:
3.650000E02


В качестве разделителя целой и дробной частей используется символ точки. Мантисса здесь имеет семь десятичных цифр - 3.650000, показатель степени - две цифры 02. Буква
E означает экспоненту, после нее указывается показатель степени (степени десяти), на которую умножается часть 3.650000 (мантисса), чтобы получить требуемое значение. То есть, чтобы вернуться к обычному десятичному представлению, нужно выполнить следующую операцию:
3.650000 × 102 = 365


Другой пример - возьмем небольшое число:
-3.650000E-03


В данном случае мы имеем дело с числом –3.65 × 10-3, что равно –0.00365. Здесь мы видим, что в зависимости от значения показателя степени десятичная точка "плавает". Собственно поэтому их и называют числами с плавающей точкой.

Однако хотя такая запись позволяет определить очень большой диапазон чисел, не все эти числа могут быть представлены с полной точностью; числа с плавающей запятой в целом являются приблизительными представления точного числа. Например, число 1254311179 выглядело бы так: 1.254311E09. Однако если перейти к десятичной записи, то это будет 1254311000. А это не то же самое, что и 1254311179, поскольку мы потеряли три младших разряда.

В языке C++ есть три типа для представления чисел с плавающей точкой:

  • float: представляет вещественное число одинарной точности с плавающей точкой в диапазоне +/- 3.4E-38 до 3.4E+38. В памяти занимает 4 байта (32 бита).
  • double: представляет вещественное число двойной точности с плавающей точкой в диапазоне +/- 1.7E-308 до 1.7E+308. В памяти занимает 8 байт (64 бита).
  • long double: представляет вещественное число двойной точности с плавающей точкой не менее 8 байт (64 бит). В зависимости от размера занимаемой памяти может отличаться диапазон допустимых значений.

В своем внутреннем бинарном представлении каждое число с плавающей запятой состоит из одного бита знака, за которым следует фиксированное количество битов для показателя степени и набор битов для хранения мантиссы. В числах float 1 бит предназначен для хранения знака, 8 бит для экспоненты и 23 для мантиссы, что в сумме дает 32 бита. Мантисса позволяет определить точность числа в виде 7 десятичных знаков.

В числах
double: 1 знаковый бит, 11 бит для экспоненты и 52 бит для мантиссы, то есть в сумме 64 бита. 52-разрядная мантисса позволяет определить точность до 16 десятичных знаков.

Для типа
long double расклад зависит от конкретного компилятора и реализации этого типа данных. Большинство компиляторов предоставляют точность до 18 - 19 десятичных знаков (64-битная мантисса), в других же (как например, в Microsoft Visual C++) long double аналогичен типу double.

В C++ литералы чисел с плавающими точками представлены дробными числами, которые в качестве разделителя целой и дробной частей применяют точку:

double num {10.45};


Даже если переменной присваивается целое число, чтобы показать, что мы присваиваем число с плавающей точкой, применяется точка:

double num1{ 1 }; // 1 - целочисленный литерал
double num2{ 1. }; //1. - литерал числа с плавающей точкой


Так, здесь число 1. представляет литерал числа с плавающей точкой, и в принципе аналогичен 1.0.

По умолчанию все такие числа с точкой расцениваются как числа типа double. Чтобы показать, что число представляет другой тип, для float применяется суффикс
f/F, а для long double - l/L:

float num1{ 10.56f }; // float
long double num2{ 10.56l }; // long double


В качестве альтернативы также можно применять экспоненциальную запись:

double num1{ 5E3 }; // 5E3 = 5000.0
double num2{ 2.5e-3 }; // 2.5e-3 = 0.0025


Размеры типов данных
При перечислении типов данных указывался размер, который он занимает в памяти. Но стандарт языка устанавливает лишь минимальные значения, которые должны быть. Например, для типов int и short минимальное значение - 16 бит, для типа long - 32 бита, для типа long double - 64 разряда. При этом размер типа long должен быть не меньше размера типа int, а размер типа int - не меньше размера типа short, а размер типа long double должен быть не меньше double. А разработчики компиляторов могут выбирать предельные размеры для типов самостоятельно, исходя из аппаратных возможностей компьютера.

К примеру, компилятор g++ Windows для long double использует 16 байт. А компилятор в Visual Studio, который также работает под Windows, и clang++ под Windows для long double используют 8 байт. То есть даже в рамках одной платформы разные компиляторы могут по разному подходить к размерам некоторых типов данных. Но в целом используются те размеры, которые указаны выше при описании типов данных.

Однако бывают ситуации, когда необходимо точно знать размер определенного типа. И для этого в С++ есть оператор
sizeof(), который возвращает размер памяти в байтах, которую занимает переменная:

#include <iostream>

int main()
{
long double number {2};
std::cout << "sizeof(number) =" << sizeof(number);
}

Консольный вывод программы:

-6

Консольный вывод при компиляции в g++:
sizeof(number) = 16

Символьные типы


В C++ есть следующие символьные типы данных:

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

char


Переменная типа
char хранит числовой код одного символа и занимает один байт. Стандарт языка С++ не определяет кодировку символов, которая будет использоваться для символов char, поэтому производители компиляторов могут выбирать любую кодировку, но обычно это ASCII.

В качестве значения переменная типа char может принимать один символ в одинарных кавычках, либо числовой код символа:

#include <iostream>

int main()
{
char a1 {'A'};
char a2 {65};
std::cout << "a1: " << a1 << std::endl;
std::cout << "a2: " << a2 << std::endl;
}

Консольный вывод программы:

-7

В данном случае переменные a1 и a2 будут иметь одно и то же значение, так как 65 - это числовой код символа "A" в таблице ASCII. При выводе на консоль с помощью cout по умолчанию отображается символ.

Кроме того, в C++ можно использовать специальные управляющие последовательности, которые предваряются слешем и которые интерпретируются особым образом. Например, "\n" представляет перевод строки, а "\t" - табуляцию.

Однако ASCII обычно подходит для наборов символов языков, которые используют латиницу. Но если необходимо работать с символами для нескольких языков одновременно или с символами языков, отличных от английского, 256-символьных кодов может быть недостаточно. И в этом случае применяется
Unicode.

Unicode (Юникод) — это стандарт, который определяет набор символов и их кодовых точек, а также несколько различных кодировок для этих кодовых точек. Наиболее часто используемые кодировки: UTF-8, UTF-16 и UTF-32. Разница между ними заключается в том, как представлена кодовая точка символа; числовое же значение кода для любого символа остается одним и тем же в любой из кодировок. Основные отличия:

  • UTF-8 представляет символ как последовательность переменной длины от одного до четырех байт. Набор символов ASCII появляется в UTF-8 как однобайтовые коды, которые имеют те же значения кодов, что и в ASCII. UTF-8 на сегодняшний день является самой популярной кодировкой Unicode.
  • UTF-16 представляет символы как одно или два 16-битных значения.
  • UTF-32 представляет все символы как 32-битные значения

    В C++ есть четыре типа для хранения символов Unicode:
    wchar_t, char8_t, char16_t и char32_t (char16_t и char32_t были добавлены в C+11, а char8_t - в C++20).

wchar_t


Тип
wchar_t — это основной тип, предназначенный для наборов символов, размер которых выходит за пределы одного байта. Собственно отсюда и его название: wchar_t - wide (широкий) char. происходит от широкого символа, потому что этот символ «шире», чем обычный однобайтовый символ. Значения wchar_t определяются также как и символы char за тем исключением, что они предваряются символов "L":

wchar_t a1 {L'A'};


Также можно передать код символа

wchar_t a1 {L'A'};


Значение, заключенное в одинарные кавычки, представляет собой шестнадцатеричный код символа. Обратная косая черта указывает на начало управляющей последовательности, а x после обратной косой черты означает, что код шестнадцатеричный.

Стоит учитывать, что для вывода на консоль символов
wchar_t следует использовать не std::cout, а поток std::wcout:

#include <iostream>

int main()
{
char h = 'H';
wchar_t i {L'i'};
std::wcout << h << i <<'\n';
}

Консольный вывод программы:

-8

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

Проблема с типом
wchar_t заключается в том, что его размер сильно зависит от реализации и применяемой кодировки. Кодировка обычно соответствует предпочтительной кодировке целевой платформы. Так, для Windows wchar_t обычно имеет ширину 16 бит и кодируется с помощью UTF-16. Большинство других платформ устанавливают размер в 32 бита, а в качестве кодировки применяют UTF-32. С одной стороны, это позволяет больше соответствовать конкретной платформе. Но с другой стороны, затрудняет написание кода, переносимого на разные платформы. Поэтому в общем случае часто рекомендуется использовать типы char8_t, char16_t и char32_t. Значения этих типов предназначены для хранения символов в кодировке UTF-8, UTF-16 или UTF-32 соответственно, а их размеры одинаковы на всех распространенных платформах.

Для определения символов типов
char8_t, char16_t и char32_t применяются соответственно префиксы u8, u и U:

char8_t c{ u8'l' };
char16_t d{ u'l' };
char32_t e{ U'o' };


Стоит отметить, что для вывода на консоль значений
char8_t/char16_t/char32_t пока нет встроенных инструментов типа std:cout/std:wcout.

Спецификатор auto


Иногда бывает трудно определить тип выражения. В этом случае можно предоставить компилятору самому выводить тип объекта. И для этого применяется спецификатор auto. При этом если мы определяем переменную со спецификатором
auto, эта переменная должна быть обязательно инициализирована каким-либо значением:

auto number = 5; // number имеет тип int
auto sum {1234.56}; // sum имеет тип double
auto distance {267UL}; // distance имеет тип unsigned long


На основании присвоенного значения компилятор выведет тип переменной. Неинициализированные переменные со спецификатором auto не допускаются:

auto number;

Не забывайте подписываться и ставить лайк!