비트 및 시프트 연산자(C# 참조)
비트 및 시프트 연산자에는 단항 비트 보수, 이진 왼쪽 및 오른쪽 시프트, 부호 없는 오른쪽 시프트, 이진 논리 AND, OR 및 배타적 OR 연산자가 포함됩니다. 이러한 피연산자는 정수 숫자 형식 또는 char 형식의 피연산자를 사용합니다.
- 단항
~
(비트 보수) 연산자 - 이진수
<<
(왼쪽 시프트),>>
(오른쪽 시프트) 및>>>
(부호 없는 오른쪽 시프트) 연산자 - 이진
&
(논리 AND),|
(논리 OR) 및^
(논리 배타적 OR) 연산자
이러한 연산자는 int
, uint
, long
및 ulong
형식에 대해 정의되어 있습니다. 두 피연산자가 모두 다른 정수 형식(sbyte
, byte
, short
, ushort
또는 char
)인 경우, 해당 값은 작업의 결과 형식이기도 한 int
유형으로 변환됩니다. 피연산자가 다른 정수 형식인 경우 해당 값은 가장 가까운 정수 형식으로 변환됩니다. 자세한 내용은 C# 언어 사양의 숫자 승격 섹션을 참조하세요. 복합 연산자(예: >>=
)는 인수를 int
로 변환하지 않거나 결과 형식이 int
입니다.
bool
유형의 피연산자에 대한 &
, |
및 ^
연산자도 정의되어 있습니다. 자세한 내용은 부울 논리 연산자를 참조하세요.
비트 및 시프트 작업으로 인해 오버플로가 발생하지 않고 Checked 및 Unchecked 컨텍스트에서 동일한 결과가 생성되지 않습니다.
비트 보수 연산자 ~
~
연산자는 각 비트를 반대로 하여 해당 피연산자의 비트 보수를 생성합니다.
uint a = 0b_0000_1111_0000_1111_0000_1111_0000_1100;
uint b = ~a;
Console.WriteLine(Convert.ToString(b, toBase: 2));
// Output:
// 11110000111100001111000011110011
~
기호를 사용하여 종료자를 선언할 수도 있습니다. 자세한 내용은 종료자를 참조하세요.
왼쪽 시프트 연산자 <<
<<
연산자는 왼쪽 피연산자를 오른쪽 피연산자로 정의된 비트 수만큼 왼쪽으로 이동합니다. 오른쪽 피연산자가 시프트 수를 정의하는 방법에 대한 자세한 내용은 시프트 연산자의 시프트 수 섹션을 참조하세요.
왼쪽 시프트 연산은 결과 형식의 범위를 벗어나는 상위 비트를 삭제하고 다음 예제와 같이 빈 하위 비트 위치를 0으로 설정합니다.
uint x = 0b_1100_1001_0000_0000_0000_0000_0001_0001;
Console.WriteLine($"Before: {Convert.ToString(x, toBase: 2)}");
uint y = x << 4;
Console.WriteLine($"After: {Convert.ToString(y, toBase: 2)}");
// Output:
// Before: 11001001000000000000000000010001
// After: 10010000000000000000000100010000
시프트 연산자는 int
, uint
, long
및 ulong
유형에 대해서만 정의되므로 작업 결과에는 항상 32비트 이상이 포함됩니다. 왼쪽 피연산자가 다른 정수 형식(sbyte
, byte
, short
, ushort
또는 char
)인 경우, 다음 예제와 같이 해당 값이 int
유형으로 변환됩니다.
byte a = 0b_1111_0001;
var b = a << 8;
Console.WriteLine(b.GetType());
Console.WriteLine($"Shifted byte: {Convert.ToString(b, toBase: 2)}");
// Output:
// System.Int32
// Shifted byte: 1111000100000000
오른쪽 시프트 연산자 >>
>>
연산자는 왼쪽 피연산자를 오른쪽 피연산자로 정의된 비트 수만큼 오른쪽으로 이동합니다. 오른쪽 피연산자가 시프트 수를 정의하는 방법에 대한 자세한 내용은 시프트 연산자의 시프트 수 섹션을 참조하세요.
오른쪽 시프트 연산은 다음 예제와 같이 하위 비트를 삭제합니다.
uint x = 0b_1001;
Console.WriteLine($"Before: {Convert.ToString(x, toBase: 2), 4}");
uint y = x >> 2;
Console.WriteLine($"After: {Convert.ToString(y, toBase: 2).PadLeft(4, '0'), 4}");
// Output:
// Before: 1001
// After: 0010
빈 상위 공백 비트 위치는 다음과 같이 왼쪽 피연산자 형식에 따라 설정됩니다.
왼쪽 피연산자가
int
또는long
형식인 경우 오른쪽 시프트 연산자는 산술 시프트를 수행합니다. 왼쪽 피연산자의 최상위 비트(부호 비트) 값이 빈 상위 비트 위치로 전파됩니다. 즉, 빈 상위 비트 위치는 왼쪽 피연산자가 음수가 아닌 경우 0으로 설정되고, 음수인 경우 1로 설정됩니다.int a = int.MinValue; Console.WriteLine($"Before: {Convert.ToString(a, toBase: 2)}"); int b = a >> 3; Console.WriteLine($"After: {Convert.ToString(b, toBase: 2)}"); // Output: // Before: 10000000000000000000000000000000 // After: 11110000000000000000000000000000
왼쪽 피연산자가
uint
또는ulong
형식이면 오른쪽 시프트 피연산자는 논리적 시프트를 수행합니다. 빈 상위 비트 위치가 항상 0으로 설정됩니다.uint c = 0b_1000_0000_0000_0000_0000_0000_0000_0000; Console.WriteLine($"Before: {Convert.ToString(c, toBase: 2), 32}"); uint d = c >> 3; Console.WriteLine($"After: {Convert.ToString(d, toBase: 2).PadLeft(32, '0'), 32}"); // Output: // Before: 10000000000000000000000000000000 // After: 00010000000000000000000000000000
참고 항목
부호 있는 정수 형식의 피연산자에 대해 논리적 시프트를 수행하려면 부호 없는 오른쪽 시프트 연산자를 사용합니다. 이는 왼쪽 피연산자를 부호 없는 형식으로 캐스팅한 다음 시프트 연산의 결과를 다시 부호 있는 형식으로 캐스팅하는 것보다 기본 설정됩니다.
부호 없는 오른쪽 시프트 연산자 >>>
C# 11 이상에서 사용할 수 있는 >>>
연산자는 오른쪽 피연산자에 정의된 비트 수만큼 왼쪽 피연산자를 오른쪽으로 이동합니다. 오른쪽 피연산자가 시프트 수를 정의하는 방법에 대한 자세한 내용은 시프트 연산자의 시프트 수 섹션을 참조하세요.
>>>
연산자는 항상 논리적 시프트를 수행합니다. 즉, 왼쪽 피연산자의 형식에 관계없이 상위 빈 비트 위치는 항상 0으로 설정됩니다. >>
연산자는 왼쪽 피연산자가 부호 있는 유형인 경우 산술 시프트를 수행합니다(즉, 가장 중요한 비트의 값이 상위 빈 비트 위치로 전파됨). 다음 예에서는 음수 왼쪽 피연산자에 대한 >>
연산자와 >>>
연산자 간의 차이점을 보여 줍니다.
int x = -8;
Console.WriteLine($"Before: {x,11}, hex: {x,8:x}, binary: {Convert.ToString(x, toBase: 2), 32}");
int y = x >> 2;
Console.WriteLine($"After >>: {y,11}, hex: {y,8:x}, binary: {Convert.ToString(y, toBase: 2), 32}");
int z = x >>> 2;
Console.WriteLine($"After >>>: {z,11}, hex: {z,8:x}, binary: {Convert.ToString(z, toBase: 2).PadLeft(32, '0'), 32}");
// Output:
// Before: -8, hex: fffffff8, binary: 11111111111111111111111111111000
// After >>: -2, hex: fffffffe, binary: 11111111111111111111111111111110
// After >>>: 1073741822, hex: 3ffffffe, binary: 00111111111111111111111111111110
논리 AND 연산자 &
&
연산자는 해당 정수 피연산자의 비트 논리 AND를 컴퓨팅합니다.
uint a = 0b_1111_1000;
uint b = 0b_1001_1101;
uint c = a & b;
Console.WriteLine(Convert.ToString(c, toBase: 2));
// Output:
// 10011000
bool
피연산자의 경우 &
연산자는 피연산자의 논리 AND를 컴퓨팅합니다. 단항 &
연산자는 address-of 연산자입니다.
논리 배타적 OR 연산자 ^
^
연산자는 해당 정수 피연산자의 비트 논리 XOR이라고도 하는 비트 논리 배타적 OR을 컴퓨팅합니다.
uint a = 0b_1111_1000;
uint b = 0b_0001_1100;
uint c = a ^ b;
Console.WriteLine(Convert.ToString(c, toBase: 2));
// Output:
// 11100100
bool
피연산자의 경우 ^
연산자는 피연산자의 논리 배타적 OR을 컴퓨팅합니다.
논리 OR 연산자 |
|
연산자는 해당 정수 피연산자의 비트 논리 OR을 컴퓨팅합니다.
uint a = 0b_1010_0000;
uint b = 0b_1001_0001;
uint c = a | b;
Console.WriteLine(Convert.ToString(c, toBase: 2));
// Output:
// 10110001
bool
피연산자의 경우 |
연산자는 피연산자의 논리 OR을 컴퓨팅합니다.
복합 할당
이진 연산자(op
)의 경우 양식의 복합 할당 식
x op= y
위의 식은 아래의 식과 동일합니다.
x = x op y
단, x
가 한 번만 계산됩니다.
다음 예제에서는 비트 및 시프트 연산자를 사용하는 복합 할당의 사용법을 보여줍니다.
uint INITIAL_VALUE = 0b_1111_1000;
uint a = INITIAL_VALUE;
a &= 0b_1001_1101;
Display(a); // output: 10011000
a = INITIAL_VALUE;
a |= 0b_0011_0001;
Display(a); // output: 11111001
a = INITIAL_VALUE;
a ^= 0b_1000_0000;
Display(a); // output: 01111000
a = INITIAL_VALUE;
a <<= 2;
Display(a); // output: 1111100000
a = INITIAL_VALUE;
a >>= 4;
Display(a); // output: 00001111
a = INITIAL_VALUE;
a >>>= 4;
Display(a); // output: 00001111
void Display(uint x) => Console.WriteLine($"{Convert.ToString(x, toBase: 2).PadLeft(8, '0'), 8}");
숫자 승격으로 인해 op
연산의 결과가 암시적으로 x
의 T
형식으로 변환되지 못할 수 있습니다. 이 경우 op
가 미리 정의된 연산자이고 연산의 결과가 명시적으로 x
의 T
형식으로 변환 가능하다면 x op= y
양식의 복합 할당 식이 x = (T)(x op y)
에 해당합니다. 단 x
는 한 번만 평가됩니다. 다음 예제에서는 해당 동작을 보여줍니다.
byte x = 0b_1111_0001;
int b = x << 8;
Console.WriteLine($"{Convert.ToString(b, toBase: 2)}"); // output: 1111000100000000
x <<= 8;
Console.WriteLine(x); // output: 0
연산자 우선 순위
다음 목록에서는 비트 및 시프트 연산자를 가장 높은 우선순위부터 가장 낮은 것으로 정렬합니다.
- 비트 보수 연산자
~
- 시프트 연산자
<<
,>>
및>>>
- 논리 AND 연산자
&
- 논리 배타적 OR 연산자
^
- 논리 OR 연산자
|
괄호(()
)를 사용하여 연산자 우선 순위에 따라 주어진 평가 순서를 변경합니다.
uint a = 0b_1101;
uint b = 0b_1001;
uint c = 0b_1010;
uint d1 = a | b & c;
Display(d1); // output: 1101
uint d2 = (a | b) & c;
Display(d2); // output: 1000
void Display(uint x) => Console.WriteLine($"{Convert.ToString(x, toBase: 2), 4}");
우선 순위 수준에 따라 정렬된 전체 연산자 목록은 C# 연산자 문서의 연산자 우선 순위 섹션을 참조하세요.
시프트 연산자의 시프트 수
x << count
, x >> count
및 x >>> count
식의 경우 실제 시프트 수는 다음과 같이 x
형식에 따라 달라집니다.
x
의 형식이int
또는uint
이면 시프트 수는 오른쪽 피연산자의 하위 5비트로 정의됩니다. 즉, 시프트 수는count & 0x1F
(또는count & 0b_1_1111
)에서 계산됩니다.x
의 형식이long
또는ulong
이면 시프트 수는 오른쪽 피연산자의 하위 6비트로 정의됩니다. 즉, 시프트 수는count & 0x3F
(또는count & 0b_11_1111
)에서 계산됩니다.
다음 예제에서는 해당 동작을 보여줍니다.
int count1 = 0b_0000_0001;
int count2 = 0b_1110_0001;
int a = 0b_0001;
Console.WriteLine($"{a} << {count1} is {a << count1}; {a} << {count2} is {a << count2}");
// Output:
// 1 << 1 is 2; 1 << 225 is 2
int b = 0b_0100;
Console.WriteLine($"{b} >> {count1} is {b >> count1}; {b} >> {count2} is {b >> count2}");
// Output:
// 4 >> 1 is 2; 4 >> 225 is 2
int count = -31;
int c = 0b_0001;
Console.WriteLine($"{c} << {count} is {c << count}");
// Output:
// 1 << -31 is 2
참고 항목
앞의 예제에서 볼 수 있듯이 오른쪽 피연산자의 값이 왼쪽 피연산자의 비트 수보다 크면 시프트 연산의 결과가 0이 아닐 수 있습니다.
열거형 논리 연산자
~
, &
, |
및 ^
연산자도 열거형 형식에 대해 지원됩니다. 동일한 열거형 형식의 피연산자인 경우, 기본 정수 형식의 해당 값에 대해 논리 연산을 수행됩니다. 예를 들어 기본 형식이 U
인 열거형 형식 T
의 x
및 y
에 대해 x & y
식은 (T)((U)x & (U)y)
식과 동일한 결과를 생성합니다.
일반적으로 Flags 특성으로 정의된 열거형 형식을 가진 비트 논리 연산자를 사용합니다. 자세한 내용은 열거형 형식 문서의 비트 플래그로서 열거형 형식을 참조하세요.
연산자 오버로드 가능성
사용자 정의 형식은 ~
, <<
, >>
, >>>
, &
, |
및 ^
연산자를 오버로드할 수 있습니다. 이항 연산자가 오버로드되면 해당하는 복합 대입 연산자도 암시적으로 오버로드됩니다. 사용자 정의 형식은 복합 대입 연산자를 명시적으로 오버로드할 수 없습니다.
사용자 정의 형식 T
가 <<
, >>
또는 >>>
연산자를 오버로드하는 경우 왼쪽 피연산자의 형식은 T
여야 합니다. C# 10 이하에서는 오른쪽 피연산자의 형식이 int
이어야 합니다. C# 11부터 오버로드된 시프트 연산자의 오른쪽 피연산자의 형식은 any일 수 있습니다.
C# 언어 사양
자세한 내용은 C# 언어 사양의 다음 섹션을 참조하세요.
참고 항목
.NET