Типы данных

Последнее обновление: 13.02.2023

Каждая переменная имеет определенный тип. И этот тип определяет, какие значения может иметь переменная, какие операции с ней можно производить и сколько байт в памяти она будет занимать. В языке 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"; 
}

При выводе значения типа 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 char, так и unsigned char в зависимости от конкретного компилятора.

  • 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;
}

Но стоит отметить, что все целочисленные литералы по умолчанию представляют тип 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;
}

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

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

#include <iostream>

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

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

По умолчанию все стандартные целочисленные литералы представляют числа в привычной нам десятичной системе. Однако 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);
}

Консольный вывод при компиляции в 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; 
}

В данном случае переменные 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'\x41'};

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

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

#include <iostream>

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

При этом поток 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;
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850