Есть ли разница, когда я хочу преобразовать целое число (например, 32-разрядное целое число / int) в другой тип целого числа (например, 8-разрядное целое число / байт). Вот пример кода для двух способов, которыми я могу его преобразовать:
byte foo(int value)
{
//return value; <-- this causes problems because I need to convert it to byte
//First way(most people use this):
return (byte)value; //this involves casting the value and also works if value is floating point type
//Second way:
return value & byte.MaxValue; //byte.MaxValue is a constant that is 255
}
Так есть ли разница между ними? Я знаю, что побитовые операции работают только для целочисленных типов. Я знаю, что второй способ не совсем читабелен или не рекомендуется. Кроме того, есть ли разница в выходе обоих способов. Это не только для int и byte, но и для каждой комбинации целочисленных типов.
Итак, похоже, что эта операция по-разному ведет себя на разных языках. Я не хочу видеть различия, поэтому, пожалуйста, отправьте ответы для C ++ / C # / D.
Также я забыл, что имел в виду только целые числа без знака (без знака). Так же и для всех целочисленных типов без знака.
В C # приведение int к байту вызовет исключение, если оно выходит за пределы диапазона а также в пределах checked
контекст. В противном случае, приведение в действие действует почти так же, как в C ++.
Продвижение типа работает в C # так же, как в C ++ (как описано Марком Б).
Для сравнения посмотрите на IL, сгенерированный этими тремя методами:
byte foo1(uint value)
{
return (byte) value;
}
.method private hidebysig instance uint8 foo1(int32 'value') cil managed
{
.maxstack 8
L_0000: ldarg.1
L_0001: conv.u1
L_0002: ret
}
Против
byte foo2(uint value)
{
checked
{
return (byte)value;
}
}
.method private hidebysig instance uint8 foo2(uint32 'value') cil managed
{
.maxstack 8
L_0000: ldarg.1
L_0001: conv.ovf.u1.un
L_0002: ret
}
А для АНДИНГА:
byte foo3(int value)
{
return (byte)(value & byte.MaxValue);
}
.method private hidebysig instance uint8 foo3(uint32 'value') cil managed
{
.maxstack 8
L_0000: ldarg.1
L_0001: ldc.i4 255
L_0006: and
L_0007: conv.u1
L_0008: ret
}
Это снова использует conv.u1
Как и в первом методе, все, что он делает, — это вводит накладные расходы на отключение дополнительных битов, которые игнорируются conv.u1
инструкция в любом случае.
Поэтому в C # я бы просто использовал приведение, если вас не волнует проверка диапазона.
Одна интересная вещь заключается в том, что в C # это даст вам ошибку компилятора:
Trace.Assert(((byte)256) == 0); // Compiler knows 256 is out of range.
Это не даст ошибку компиляции:
int value = 256;
Trace.Assert(((byte)value) == 0); // Compiler doesn't care.
И, конечно, это не даст ошибку компиляции:
unchecked
{
Trace.Assert(((byte)256) == 0);
}
Странно, что первый выдает ошибку компилятора, хотя по умолчанию он не проверяется во время выполнения. Я думаю, время компиляции проверено по умолчанию!
В C ++ существует абсолютная разница в том, как получается результат, потому что операнды &
будет повышен до размера большего из двух типов. Если значение «макс» вы &
с случаем будет подписан, тогда вы подпишете расширение, и побитовая операция вряд ли будет иметь желаемый эффект.
Я бы лично предпочел явное return static_cast<char>(value);