[БЕЗ_ЗВУКА] Здравствуйте. Мы начинаем блок, посвященный рассмотрению целочисленных типов языка C++. Вы уже знаете один целочисленный тип — это тип int, и мы им везде пользовались. Но все-таки мы не будем просто так ради общего образования рассматривать целочисленные типы языка C++ — мы начнем с проблемы. Для этого вспомним задачу «Средняя температура» из первого курса. Что там нужно было сделать? Нам был дан набор наблюдений за температурой, скажем, в виде вектора (назовем его t), и у нас были какие-то значения температуры: например, 8, 7 и 3. Нужно было найти среднее арифметическое значение температуры за все дни и затем вывести номера дней, в которые значение температуры было больше, чем среднее арифметическое. В частности, нам нужно было уметь вычислять среднее арифметическое. Как это можно было сделать? Можно было, вот, собственно, имея данный вектор с наблюдениями... Возможно, мы его читали из исходного файла, здесь у меня он задан просто явно. Надо было завести переменную с суммой и затем циклом for проитерироваться по вектору t, тем самым вычислить сумму — суммарное значение температуры, и затем поделить эту сумму на размер вектора. Все кажется абсолютно логичным. Сохранить в переменную со средним значением и вывести после этого. Вот был один из способов решить эту задачу. Давайте проверим, что он работает. Какая будет средняя температура? 8 + 7 + 3 — это 18. Разделить на 3, получается 6. Должно получиться 6. Запускаем код и видим действительно 6, код работает. Но если вспомнить, то в той задаче было ограничение: вам гарантировалось, что все значения температуры положительные, ну или не отрицательные. Почему так? Потому что если в таком решении у вас в исходном векторе будут отрицательные значения температуры, например, −8, −7 и 3, код работать не будет. Почему? Потому что... казалось бы, −8 − 7 + 3 — это −12. −12 / 3 — должно получиться −4. Давайте проверим, что же получится. Компилируем код, запускаем и видим что-то очень странное. Мы видим 1431655761. Это не −4. Что же произошло? На самом деле мы от незнания неаккуратно использовали другой целочисленный тип языка C++. Он возникает у нас вот здесь, это другой тип, t.size() — это специальный тип, который не умеет хранить отрицательные числа. Понятно, что размер контейнера отрицательным быть не может, и это так называемый беззнаковый тип, мы это не учли. Далее мы эту проблему рассмотрим, в следующих видео. Пока что. Какая еще бывает проблема с целочисленными типами? Очень простой пример: у вас есть переменная типа int, в нее умещается, например, два миллиарда — 2 и девять нулей. Заметьте, в C++ можно для лучшей читаемости разбивать разряды в числе одинарной кавычкой. И эта переменная x успешно хранится в типе int, мы можем ее вывести, а потом можем захотеть умножить ее на 2, например. И давайте снова посмотрим, что получится. Итак, запускаем код и видим, что у нас изначально x было 2 миллиарда, а в итоге получилось каким-то большим отрицательным числом. 4 миллиарда в переменную типа int не поместилось. Соответственно, нам нужно обсудить следующие особенности целочисленных типов языка C++. Первая особенность: в языке C++ память для целочисленных типов ограничена. Язык C++ не будет за вас выделять больше памяти для целочисленного типа, потому что он позволяет вам не платить за то, что вы не используете. Если вам не нужны целые числа размером больше 2 миллиардов, язык C++ для вас выделит вот ровно столько памяти, сколько достаточно для хранения числа размером 2 миллиарда. Соответственно, у целочисленных типов языка C++ ограниченный диапазон значений. И нам нужно это изучить в первую очередь. Во-вторых, вторая проблема, с которой мы могли столкнуться в задаче «Средняя температура», если бы там допускались отрицательные значения температуры, это то, что некоторые целочисленные типы языка C++ беззнаковые. Почему? Потому что иногда вам не нужно уметь хранить в целочисленной переменной отрицательные числа. Соответственно, опять, вы не должны платить за то, что вы не используете, и вы можете взять себе для этих целей целочисленный тип, в котором нельзя хранить отрицательные значения. Тем самым вы, наверное, сможете хранить больше положительных значений. Итак, давайте разбираться, какие у нас бывают целочисленные типы. Стандартный целочисленный тип — это тип int. Именно по этой причине мы его использовали свободно в первом курсе. В каком смысле стандартный? Во-первых, если вы напишете auto x = 1, то переменная x получит тип int. То есть все числа, которые вы прямо вот в виде цифр пишете в вашем коде, имеют тип int, если, конечно, они достаточно маленькие. Этот тип — почему еще он стандартный? Потому что он наиболее эффективен. Он наиболее эффективен на вашем компьютере, на том компьютере, на котором вы компилируете вашу программу. Он ровно такой, чтобы операции с ним напрямую транслировались в инструкции процессора. И именно поэтому он не имеет какой-то конкретный размер, вам не гарантируют, что он всегда будет иметь какой-то конкретный размер, но на всех современных архитектурах он почти всегда имеет размер 4 байта или 32 бита, и соответственно, диапазон его значений — от −2³¹ до (2³¹ − 1), это примерно 2 миллиарда. Но нужно помнить, что в конечном счете размер этого типа зависит от архитектуры процессора конкретной. Более того, у типа int есть его беззнаковый аналог — тип unsigned int. Вот ровно так, через пробел, нужно записывать название этого типа; или просто unsigned, без int. Этот тип имеет точно такой же размер, как и int, он тоже наиболее эффективен на вашей архитектуре, но он беззнаковый, соответственно, он вмещает числа от 0 до (2³² − 1). Как видите, примерно в два раза больше положительных чисел вмещается за счет того, что мы не храним отрицательные числа. Есть еще один беззнаковый тип — это тип size_t. Это ровно тот тип, который в методе size возвращают контейнеры языка C++, например, вектор. Его размер зависит от разрядности вашей операционной системы. То есть тип size_t спроектирован именно так, чтобы умещать те размеры контейнера, которые бывают. Если у вас 32-битная система, скорее всего, size_t будет иметь размер 32 бита, если 64-битная, то 64 бита. Кроме того. Это были так называемые классические типы языка C++. Почему классические? Потому что они из языка C к нам пришли. А еще есть другие типы, в которых явно известен заранее их размер. Если вы подключите модуль cstdint, то вы сможете использовать тип int32_t. Это знаковый тип размером ровно 32 бита. То есть как int, но у него гарантированный размер. Есть у него беззнаковый аналог, это тип uint32_t, и также есть, соответственно, типы размеров 8 бит, 16 бит и 64 бита. int8_t, uint8_t, int16_t, uint16_t и int64_t, uint64_t. Итак, давайте резюмируем, какие типы мы узнали. У нас есть тип int, который обычно имеет размер 4 байта, есть тип unsigned int, который обычно имеет тот же размер, но он беззнаковый, и есть тип size_t, который имеет размер 4 или 8 байт. Кроме того, есть типы с известным размером: int8_t, int16_t, int32_t, int64_t — знаковые типы, и их беззнаковые аналоги с префиксом u. Как же нам выбрать разрядность типа? Как же нам выбрать тот тип, который мы будем использовать? Мы пока не говорим про знаковость и беззнаковость, это мы обсудим позже. Как же нам выбрать размер типа? Если вам достаточно стандартного типа int, используйте его, потому что он стандартный. Если вам недостаточно его размерности, то есть вы хотите хранить числа порядка триллиона, порядка 10¹², например, то вам нужно использовать тип int64_t. Если вы хотите единообразия и вас пугает то, что никто не гарантирует, что тип int будет иметь размер обязательно 4 байта, используйте тип int32_t, хотя это, конечно, некоторая экзотика, разве что для единообразия можно его использовать. И если вы хотите экономить память на целочисленных типах, то вы можете использовать типы меньшей размерности, такие как int8_t, int16_t. Что делать, если вы забыли размер конкретного типа данных? Необязательно для этого идти в документацию, для этого достаточно взять компилятор и... Например, вы забыли, какой размер имеет тип int16_t. Как узнать его размер? Вы можете написать cout и вывести sizeof(int16_t). Так вы узнаете размер в байтах для этого типа. Правда, это и так понятно, 16 / 8 — это 2: 16 бит — 2 байта. Или вы точно так же можете узнать размер типа int. Вот я говорил, что не гарантируется, что у него размер 4 байта, давайте посмотрим, на моем компьютере какой у него размер. Запустим этот код. 4 байта, действительно, почти всегда 4 байта. Кроме того, можно sizeof еще вызывать не только от типа, но и от переменной конкретного типа. Если бы у вас была переменная int x, скажем, вы бы могли вызвать sizeof(x). Ну да ладно, это не очень удобно. Итак. Кроме того, вы можете узнать минимальное и максимальное значение типа, подключив модуль limits — ограничения. И теперь вы можете узнать минимальное и максимальное значение типа int. Как это сделать? Нужно написать: numeric_limits, в угловых скобках название того типа, который вас интересует, и дальше через два двоеточия — что вас об этом типе интересует. Я вызываю функцию min: меня интересует, собственно, минимальное значение этого типа, ну и давайте для полноты картины выведем еще и максимальное значение этого типа: numeric_limits<int>::max. Давайте посмотрим, какое же минимальное и максимальное значение типа int. На самом деле мы это уже знаем, это −2³¹ и (2³¹ − 1). Давайте посмотрим на эти числа. Я забыл круглые скобки. max — это функция, конечно, надо ставить круглые скобки, даже если нет аргументов. Запускаю код и вижу, что тип int имеет размер 4 байта, его минимальное значение — это −2147483648, максимальное значение по модулю такое же, но на единицу меньше, примерно два миллиарда. Кроме того, этот же прием можно использовать, если вы вдруг в чужом коде увидели какой-то целочисленный тип, про который мы вам не рассказали. Например, мы вам не рассказали про тип long int, потому что вместо него успешно можно использовать типы из модуля cstdint. Давайте посмотрим на тип long int: какой у него размер и минимальное и максимальное значение. Размер обычно 4 или 8 байт. Давайте скомпилируем код, запустим, и действительно, он в данном случае равен типу int, но это не всегда так. Итак, что мы узнали в этом видео? Мы узнали, что в языке C++ больше одного целочисленного типа, там есть не только тип int. И чем эти целочисленные типы отличаются? Во-первых, они отличаются размером, той памятью, которую они занимают, и, соответственно, диапазоном значений. Во-вторых, они отличаются знаковостью или беззнаковостью. Некоторые типы не хранят осознанно отрицательные числа. Мы узнали, что тип int — это стандартный целочисленный тип, у него есть беззнаковый аналог unsigned int, что есть тип size_t — это тип, который возвращают методы size у контейнеров, тип для хранения размеров. Мы узнали про типы, у которых конкретный фиксированный размер, научились узнавать по целочисленному типу его размер и его минимальное и максимальное значение. А дальше мы как раз научимся решать ту проблему, с которой мы начали, — проблему с беззнаковыми типами. Кроме того, также поговорим про переполнение.