Uint c что это
Перейти к содержимому

Uint c что это

Целочисленные числовые типы (справочник по C#)

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

Характеристики целочисленных типов

C# поддерживает следующие предварительно определенные целочисленные типы:

Ключевое слово или тип C# Диапазон Размер Тип .NET
sbyte От -128 до 127 8-разрядное целое число со знаком System.SByte
byte От 0 до 255 8-разрядное целое число без знака System.Byte
short От -32 768 до 32 767 16-разрядное целое число со знаком System.Int16
ushort От 0 до 65 535 16-разрядное целое число без знака System.UInt16
int От -2 147 483 648 до 2 147 483 647 32-разрядное целое число со знаком System.Int32
uint От 0 до 4 294 967 295 32-разрядное целое число без знака System.UInt32
long От -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807 64-разрядное целое число со знаком System.Int64
ulong От 0 до 18 446 744 073 709 551 615 64-разрядное целое число без знака System.UInt64
nint Зависит от платформы 32- или 64-разрядное целое число со знаком System.IntPtr
nuint Зависит от платформы 32- или 64-разрядное целое число без знака System.UIntPtr

Во всех строках таблицы, кроме двух последних, каждое ключевое слово типа C# из крайнего левого столбца является псевдонимом для соответствующего типа .NET. Ключевое слово и имя типа .NET являются взаимозаменяемыми. Например, следующие объявления объявляют переменные одного типа:

Типы nint и nuint в последних двух строках таблицы являются целыми числами собственного размера. В коде такие числа представлены определенными типами .NET, но в каждом случае ключевое слово и тип .NET не взаимозаменяемы. Для nint и nuint компилятор предоставляет преобразования и операции, как для целочисленных типов. Они отличаются от преобразований и операций для типов указателей System.IntPtr и System.UIntPtr . Дополнительные сведения см. в статье о типах nint и nuint .

По умолчанию все целочисленные типы имеют значение 0 . Все целочисленные типы, кроме целых чисел собственного размера, имеют константы MinValue и MaxValue с минимальным и максимальным значением этого типа.

Используйте структуру System.Numerics.BigInteger, чтобы представить целое число со знаком без верхней и нижней границ.

Целочисленные литералы

Целочисленные литералы могут быть:

  • десятичным числом: без префикса;
  • шестнадцатеричным числом: с префиксом 0x или 0X ;
  • двоичными: с префиксом 0b или 0B (доступно в C# 7.0 и более поздних версиях).

В приведенном ниже коде показан пример каждого из них.

В предыдущем примере также показано использование _ в качестве цифрового разделителя, который поддерживается, начиная с версии C# 7.0. Цифровой разделитель можно использовать со всеми видами числовых литералов.

Тип целочисленного литерала определяется его суффиксом следующим образом:

Если литерал не имеет суффикса, его типом будет первый из следующих типов, в котором может быть представлено его значение: int , uint , long , ulong .

Литералы интерпретируется как положительные значения. Например, литерал 0xFF_FF_FF_FF представляет число 4294967295 типа uint , хотя он имеет то же битовое представление, что и число -1 типа int . Если вам требуется значение определенного типа, приведите литерал к этому типу. Используйте оператор unchecked , если представить значение литерала в целевом типе невозможно. Например, unchecked((int)0xFF_FF_FF_FF) выдает -1 .

Если у литерала есть суффикс U или u , его типом будет первый из следующих типов, в котором может быть представлено его значение: uint , ulong .

Если у литерала есть суффикс L или l , его типом будет первый из следующих типов, в котором может быть представлено его значение: long , ulong .

Строчную букву l можно использовать в качестве суффикса. Однако при этом выдается предупреждение компилятора, так как букву l можно перепутать с цифрой 1 . Для ясности используйте L .

Если у литерала есть суффикс UL , Ul , uL , ul , LU , Lu , lU или lu , его тип — ulong .

Если значение, представленное целочисленным литералом, превышает UInt64.MaxValue, происходит ошибка компиляции CS1021.

Если определенный тип целочисленного литерала — int , а значение, представленное литералом, находится в диапазоне целевого типа, значение можно неявно преобразовать в sbyte , byte , short , ushort , uint , ulong , nint или nuint :

Как показано в предыдущем примере, если значение литерала выходит за пределы диапазона целевого типа, возникает ошибка компилятора CS0031.

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

Преобразования

Любой целочисленный тип можно преобразовать в любой другой целочисленный тип. Если целевой тип может хранить все значения исходного типа, преобразование является неявным. В противном случае необходимо использовать выражение приведения для выполнения явного преобразования. Для получения дополнительной информации см. статью Встроенные числовые преобразования.

Спецификация языка C#

Дополнительные сведения см. в следующих разделах статьи Спецификация языка C#:

Дополнительные типы данных в C

Я хочу узнать что за типы такие: uint8_t , uint16_t и т. д. Слышал, что это тоже самое, что и unsigned char , unsigned short и т. д.

Но в чем на самом деле разница? Созданы ли эти типы специально чтобы вне зависимости от платформы выделять для переменных определенное количество байт?

Также хотелось бы узнать про size_t . Я знаю, что этот тип возвращается функцией sizeof , но в чем была необходимость его создания?

user avatar

<stdint.h>

Типы int16_t , uint16_t , … — это не совсем то же самое, что и базовые целочисленные типы (англ. basic integer types) в C ( char , int , short и т. д.).

Основное отличие заключается в том, что для «обычных» типов стандарт определяет только одно — диапазон допустимых значений. Больше никаких требований на эти типы стандарт не налагает. Они могут быть сколь угодно широкие, их внутреннее представление может быть каким угодно, главное, чтобы сохранялась относительная ширина типов и диапазон допустимых значений. Это все, что требует стандарт. И именно эта нетребовательность стандарта позволяет этим типам быть максимально переносимыми.

Другое дело — типы из <stdint.h> . Они были введены в C99 и называются фиксированными целочисленными типами (англ. fixed integer types). На такие типы стандарт налагает гораздо больше требований. Например, типы определенной ширины (англ. exact-width integer types) ( int8_t , int16_t , int32_t и int64_t ) должны представляться в дополнительном коде, иметь ширину в 8, 16, 32 и 64 бита соответственно и не должны содержать padding bits. Также определяются типы с шириной не меньше гарантированной; максимально широкие типы; типы, работа с которым максимально быстрая и так далее.

Чувствуете эту разницу? На последние стандарт налагает гораздо больше требований, что делает их гораздо менее переносимыми, но дает разработчику гораздо большую гибкость. Что, если мне нужен целочисленный тип, шириной ровно в 8 бит, или с шириной не меньше 32 бита? Стандарт предоставляет мне такую возможность на уровне языка. Возможность использования таких типов в конкретном случае зависит лишь от реализации. Вдруг, на некоторой машине в принципе не существует типов ровно в 8 бит длиной? Стандарт как бы говорит: «Нужно целочисленное число ровно в 8 бит шириной — пожалуйста, используйте int8_t , если, конечно, ваша реализация его поддерживает».

size_t

Тип size_t нужен для одного — представлять размеры объектов. Этот тип может представить максимально возможный размер любого объекта.

Одно из следствий такого «свойства» — это то, что size_t — единственный † тип, для которого гарантируется, что он может хранить любой возможный индекс массива (в следствии того, что массив в C — это объект).

Из этого следствия вытекает частое применение этого типа как типа для различных счетчиков в цикле, которые перебирают массивы. И это единственный подходящий тип для данной задачи!

Создатели языка решили определить отдельный тип для такой задачи (представление размера объектов). И вот почему. Представим, что у нас вообще нет такого типа, как size_t и перед нами стоит задача написать свой функцию strlen с использованием, например, цикла for :

Какой тип мы будем использовать для счетчика? Может int ? Но это неэффективно, так как индекс не может быть отрицательным. Тогда, пожалуй, unsigned int . Уже лучше, но что, если наша строка длиннее, чем UINT_MAX ? Тогда возьмем unsigned long ! Но, что если максимальная длина строки на платформе гораздо меньше ULONG_MAX и настолько большой тип будет неэффективен.

Чтобы разработчику в такой ситуации не гадать, что да как, и был введен тип size_t .

† Как правильно заметил @wololo в комментариях, есть еще uintmax_t и rsize_t , которые также могут хранить любой возможный индекс массива. Следует иметь в виду, что rsize_t определяется в Приложении K стандарта C11. Это приложение реализовано далеко не везде. Более того: существует proposal вообще убрать его из стандарта.

Свод правил по работе с целыми числами в C/C++

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

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

Типы данных

Базовые целочисленные типы

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

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

  • char : минимум 8 бит в ширину;
  • short : минимум 16 бит и при этом не меньше char ;
  • int : минимум 16 бит и при этом не меньше short ;
  • long : минимум 32 бит и при этом не меньше int ;
  • long long : минимум 64 бит и при этом не меньше long .

Наличие знака

  • Стандартный сhar может иметь знак или быть беззнаковым, что зависит от реализации.
  • Стандартные short , int , long и long long идут со знаком. Беззнаковыми их можно сделать, добавив ключевое слово unsigned .
  • Числа со знаком можно кодировать в двоичном формате в виде дополнительного кода, обратного или как величину со знаком. Это определяется реализацией. Заметьте, что обратный код и величина со знаком имеют различные шаблоны битов для отрицательного нуля и положительного, в то время как дополнительный код имеет уникальный нуль.
  • Символьные литералы (в одинарных кавычках) имеют тип ( signed ) int в C, но ( signed или unsigned ) char в C++.

Дополнительные правила

  • sizeof(char) всегда равен 1, независимо от битовой ширины char .
  • Битовая ширина не обязательно должна отличаться. Например, допустимо использовать char , short и int , каждый шириной в 32 бита.
  • Битовая ширина должна быть кратна 2. Например, int может иметь ширину 36 бит.
  • Есть разные способы написания целочисленного типа. К примеру, в каждой следующей строке перечислен набор синонимов:
    • int , signed , signed int , int signed ;
    • short , short int , short signed , short signed int ;
    • unsigned long long , long unsigned int long , int long long unsigned .

    Типы из стандартных библиотек

    • size_t (определен в stddef.h) является беззнаковым и содержит не менее 16 бит. При этом не гарантируется, что его ширина будет как минимум равна int .
    • ptrdiff_t (определен в stddef.h) является целочисленным типом со знаком. Вычитание двух указателей будет давать этот тип. При этом не стоит ожидать, что вычитание двух указателей даст int .
    • В stdint.h определена конкретная ширина типов: uint8_t , int8_t , 16 , 32 и 64 . Будьте внимательны к операциям, подразумевающим продвижение типов. Например, uint8_t + uint8_t даст int (со знаком и шириной не менее 16 бит), а не uint8_t , как можно было предположить.

    Преобразования

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

    Как происходит преобразование?

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

    • Когда исходный тип расширяется до целевого типа с аналогичной знаковой характеристикой (например, signed char -> int или unsigned short -> unsigned long ), каждое исходное значение после преобразования сохраняется.
    • Даже если исходный и целевой типы имеют разные диапазоны, все значения в их пересекающейся части будут сохранены. Например, int , содержащий значение в диапазоне [0, 255] , будет без потерь преобразован в unsigned char .
    • При преобразовании в беззнаковый тип новое значение равняется старому значению по модулю 2 целевая ширина в битах . Объяснение:
      • Если исходный тип беззнаковый и шире целевого, тогда старшие биты отбрасываются.
      • Если исходный тип имеет знак, тогда в процессе преобразования берется исходное значение, и из него/к нему вычитается/прибавляется 2 целевая ширина в битах до тех пор, пока новое значение не впишется в диапазон целевого типа. Более того, если число со знаком представлено в дополнительном коде, то в процессе преобразования старшие биты отбрасываются, как и в случае с беззнаковыми числами.
      • Если исходное значение вписывается в диапазон целевого типа, тогда процесс преобразования (например, расширение знака) производит целевое значение, семантически равное исходному.
      • Если же оно не вписывается, тогда поведение будет определяться реализацией и может вызвать исключение (к примеру, прерывание из-за переполнения).

      Арифметика

      Продвижение/преобразование

        Унарный арифметический оператор применяется только к одному операнду. Примеры: — ,
      • В реализации присутствуют 16-битный short и 24-битный int . Если переменные x и y имеют тип unsigned short , то операция x & y продвигает оба операнда до signed int .
      • В реализации присутствуют 32-битный char и 32-битный int . Если переменные x и y имеют тип unsigned char , то операция x – y продвигает оба операнда до unsigned int .
      • (long) + (long) → (long) ;
      • (unsigned int) * (int) → (unsigned int) ;
      • (unsigned long) / (int) → (unsigned long) ;
      • если int является 32-битным, а long 64-битным: (unsigned int) % (long) → (long) ;
      • если int и long оба являются 32-битными: (unsigned int) % (long) → (unsigned long) .

      Неопределенное поведение

      Знаковое переполнение:

      • При выполнении арифметических операций над целочисленным типом переполнение считается неопределенным поведением (UB). Такое поведение может вызывать верные, несогласованные и/или неверные действия как сразу, так и в дальнейшем.
      • При выполнении арифметики над беззнаковым целым (после продвижений и преобразований) любое переполнение гарантированно вызовет оборот значения. Например, UINT_MAX + 1 == 0 .
      • Выполнение арифметики над беззнаковыми целыми фиксированного размера может привести к едва уловимым ошибкам. Например:
        • Пусть uint16_t = unsigned short , и int равен 32-битам. Тогда uint16_t x=0xFFFF , y=0xFFFF , z=x*y ; x и y будут продвинуты до int , и x * y приведет к переполнению int , вызвав неопределенное поведение.
        • Пусть uint32_t = unsigned char , и int равен 33-битам. Тогда uint32_t x=0xFFFFFFFF , y=0xFFFFFFFF , z=x+y ; x и y будут продвинуты до int , и x + y приведет к переполнению int , то есть неопределенному поведению.
        • Чтобы обеспечить безопасную арифметику с беззнаковыми целыми, нужно либо прибавить 0U , либо умножить на 1U в качестве пустой операции. Например: 0U + x + y или 1U * x * y . Это гарантирует, что операнды будут продвинуты как минимум до ранга int и при этом останутся без знаков.
        • Деление на нуль и остаток с делителем нуля также относятся к неопределенному поведению.
        • Беззнаковое деление/остаток не имеют других особых случаев.
        • Деление со знаком может вызывать переполнение, например INT_MIN / -1 .
        • Остаток со знаком при отрицательных операндах может вызывать сложности, так как некоторые части являются однообразными, в то время как другие определяются реализацией.
        • Неопределенным поведением считается битовый сдвиг (< < и >>) на размер, который либо отрицателен, либо равен или больше битовой ширины.
        • Левый сдвиг беззнакового операнда (после продвижения/преобразования) считается определенным правильно и отклонений в поведении не вызывает.
        • Левый сдвиг операнда со знаком, содержащего неотрицательное значение, вследствие которого 1 бит переходит в знаковый бит, является неопределенным поведением.
        • Левый сдвиг отрицательного значения относится к неопределенному поведению.
        • Правый сдвиг неотрицательного значения (в типе операнда без знака или со знаком) считается определенным правильно и отклонений в поведении не вызывает.
        • Правый сдвиг отрицательного значения определяется реализацией.

        Счетчик цикла

        Выбор типа

        Предположим, что у нас есть массив, в котором нужно обработать каждый элемент последовательно. Длина массива хранится в переменной len типа T0 . Как нужно объявить переменную счетчика цикла i типа T1 ?

        • Самым простым решением будет использовать тот же тип, что и у переменной длины. Например:
        • Говоря обобщенно, переменная счетчика типа T1 будет работать верно, если диапазон T1 будет являться (не строго) надмножетсвом диапазона T0 . Например, если len имеет тип uint16_t , тогда отсчет с использованием signed long (не менее 32 бит) сработает.
        • Говоря же более конкретно, счетчик цикла должен просто покрывать всю фактическую длину. Например, если len типа int гарантированно будет иметь значение в диапазоне [3,50] (обусловленное логикой приложения), тогда допустимо отсчитывать цикл, используя char без знака или со знаком (в котором однозначно можно представить диапазон [0,127] ).
        • Нежелательно использовать переменную длины и переменную счетчика с разной знаковостью. В этом случае сравнение вызовет неявное сложное преобразование, сопровождаемое характерными для платформы проблемами. К примеру, не стоит писать такой код:

        Отсчет вниз

        Для циклов, ведущих отсчет вниз, более естественным будет использовать счетчик со знаком, потому что тогда можно написать:

        При этом для беззнакового счетчика код будет таким:

        Примечание: сравнение i >= 0 имеет смысл только, когда i является числом со знаком, но всегда будет давать true , если оно будет беззнаковым. Поэтому, когда это выражение встречается в беззнаковом контексте, значит, автор кода скорее всего допустил ошибку в логике.

        Заблуждения

        Все пункты приведенного ниже списка являются мифами. Не опирайтесь на эти ложные убеждения, если хотите писать корректный и портируемый код.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *