[이것이 C#이다]Ch.04 데이터를 가공하는 연산자
## 목차
8) null 조건부 연산자
9) 비트 연산자
10) 할당 연산자
11) null 병합 연산자
12) 연산자의 우선순위
#8 null 조건부 연산자
null 조건부 연산자 ?.는C# 6.0에 도입되었습니다.
?. 가 하는 일은 객체의 멤버에 접근하기 전에 해당 객체가 null인지 검사하여 그결과가 참이면 그결과로 null을 반환하고, 그렇지 않은 경우에는 . 뒤에 지정된 멤버를 반환합니다.
== 연산자를 이용한 코드 | ?. 연산자를 이용한 코드 |
class Doo { public int member; } Foo foo = null; int? bar; if (foo == null) bar = null; else bar = foo.member; |
class Foo { public int memeber; } Foo foo = null; int? bar; bar = foo?.member; // foo 객체가 null이 아니면 member 필드에 접근하게 해줌 |
?[] 도 동일한 기능을 수행하는 연산자입니다. ?[]는 ?.와 비슷한 역할을 하지만, 객체의 멤버 접근이 아닌 배열과 같은 컬렉션 객체의 첨자를 이용한 참조에 사용된다는 점이 다릅니다.
[실습]
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ThisIsCSharpExam.Ch._04.NullConditionalOperator
{
class NullConditionalOperatorExam
{
public void Main() {
ArrayList a = null;
a?.Add("야구"); // a?.가 null을 반환하므로 Add() 메소드는 호출되지 않음
a?.Add("축구");
// a?.가 null을 반환하므로 'Count:'외에는 아무것도 출력하지 않습니다.
Console.WriteLine($"Count : {a?.Count}");
Console.WriteLine($"{a?[0]}");
Console.WriteLine($"{a?[1]}");
a = new ArrayList(); // a는 이제 더 이상 null이 아닙니다.
a?.Add("야구");
a?.Add("축구");
Console.WriteLine($"Count : {a?.Count}");
Console.WriteLine($"{a?[0]}");
Console.WriteLine($"{a?[1]}");
}
}
}
#9 비트 연산자
바이트 단위가 대부분의 데이터를 다루기에 용이한 크기이긴 하지만, 어쩻든 비트 수준에서 데이터를 가공해야 하는 경우가 종종 생깁니다. C#에서 제공하는 비트 연산자의 종류는 꽤 다양합니다.
연산자 | 이름 | 설명 | 지원 형식 |
<< | 왼쪽 시프트 연산자 | 첫 번재 피연산자의 비트를 두 번째 피연산자의 수만큼 왼쪽으로 이동시킵니다. | 첫 번째 피연산자는 int, uint, long, ulong이며 두 번째 피연산자는 int 형식만 지원합니다. |
>> | 오른쪽 시프트 연산자 | 첫 번째 피연산자의 비트를 두 번째 피연산자의 수만큼 오른쪽으로 이동시킵니다. | |
& | 논리곱(AND) 연산자 | 두 피연산자의 비트 논리곱을 수행 합니다. | 정수 계열 형식과 bool 형식에 대해 사용할 수 있습니다. |
| | 논리합(OR) 연산자 | 두 피연산자의 비트 논리합을 수행 합니다. | |
^ | 배타적 논리합(XOR) 연산자 | 두 피연산자의 비트 배타적 논리합을 수행합니다. | |
~ | 보수(NOT) 연산자 | 피연산자의 비트를 0은 1로, 1은 0으로 반전시킵니다. 단항 연산자입니다. | int, uint, long, ulong에 대해 사용 할 수 있습니다. |
1. 시프트 연산자
시프트 연산자는 비트를 왼쪽이나 오른쪽으로 이동시키는 연산자라고 할 수 있습니다.
비트를 이동 시킨다는 말을 예를 들어 숫자 하나를 비트로 표현해보겠습니다. 다음 그림은 10진수 240을 16개의 비트로 표현한 것입니다.(시프트 연산자가 지원하는 형식은 32비트(4바이트)인 int 이상이지만, 이해를 돕기 위해 비트 수를 줄였습니다.)
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
이 비트를 전체적으로 왼쪽으로 2비트 이동하면 다음 그림과 같이 변합니다.
이 때 밀려 나온 비트는 버리고, 비어 있는 비트에는 0으로 채워 넣으면 다음과 같이 왼쪽 시프트 연산이 완료됩니다.
0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
오른쪽 시프트도 방향만 다를뿐 같습니다.
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 |
한편, 음수에 대한 오른쪽 시프트 연산은 약간 다른 과정을 거칩니다.
비트를 이동시킨다는 것은 같지만, 이동시킨 후 만들어진 빈 자리에 0이 아닌 1을 채워 넣는다는 점이 다릅니다.
예를 들어 -255를 비트로 표현하면 1111 1111 0000 0001이 됩니다.
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
이것을 오른쪽으로 2비트 이동시켜보겠습니다.
다음 비어 있는 비트에는 1로 채워 넣으면 오른쪽 시프트 연산을 완료합니다.
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
시프트 연산자의 사용법을 간단히 산술 연산자처럼 피연산자 두 개를 받습니다. 왼쪽 피연산자(a)는 원본 데이터, 오른쪽 피연산자(2)는 옮길 비트의 수입니다.
int a = 240; // 00000000 00000000 00001111 00000000
int result_1 = a << 2; // 00000000 00000000 00111100 00000000
int result_2 = a >> 2; // 00000000 00000000 00000011 11000000
이런 시프트 연산이 무슨 의미가 있을까요?
원본 데이터를 a, 옯긴 비트 수를 b라고 할 때, 왼쪽 시프트 연산을 하면 a x 2b의 결과가, 오른쪽 시프트 연산을 하면 a / 2b가 나옵니다. 이점을 이용해서 시프트 연산은 고속의 곱셈과 나눗셤을 구현하는데 사용하기도 하고 &연산자, |연산자와 함께 byte처럼 작은 단위로 쪼개진 데이터를 다시 하나의 int나 long 형식으로 재조립하는 데 사용하기도 합니다.
[실습]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ThisIsCSharpExam.Ch._04.ShiftOperator
{
class ShiftOperatorExam
{
public void Main() {
Console.WriteLine("Testing <<...");
int a = 1;
Console.WriteLine("a : {0:D5} (0x{0:X8})", a);
Console.WriteLine("a << 1 : {0:D5} (0x{0:X8})", a << 1);
Console.WriteLine("a << 2 : {0:D5} (0x{0:X8})", a << 2);
Console.WriteLine("a << 5 : {0:D5} (0x{0:X8})", a << 5);
Console.WriteLine("\nTesting >> ...");
int b = 255;
Console.WriteLine("b : {0:D5} (0x{0:X8})", b);
Console.WriteLine("b >> 1 : {0:D5} (0x{0:X8})", b >> 1);
Console.WriteLine("b >> 2 : {0:D5} (0x{0:X8})", b >> 2);
Console.WriteLine("a >> 5 : {0:D5} (0x{0:X8})", b >> 5);
Console.WriteLine("\nTesting >> 2...");
int c = -255;
Console.WriteLine("c : {0:D5} (0x{0:X8})", c);
Console.WriteLine("c >> 1 : {0:D5} (0x{0:X8})", c >> 1);
Console.WriteLine("c >> 2 : {0:D5} (0x{0:X8})", c >> 2);
Console.WriteLine("c >> 5 : {0:D5} (0x{0:X8})", c >> 5);
}
}
}
2. 비트 논리 연산자
비트 논리 연산은 데이터의 각 비트에 대해 수행하는 논리 연산입니다.
연산자 | 이름 | 설명 | 지원 형식 |
& | 논리곱(AND) 연산자 | 두 피연산자의 비트에 대해 논리곱을 수행합니다. | 정수계열 형식과 bool 형식에 사용 할 수 있습니다. |
| | 논리합(OR) 연산자 | 두 피연산자의 비트에 대해 논리합을 수행합니다. | |
^ | 배타적 논리합(XOR) 연산자 | 두 피연산자의 비트에 대해배타적 논리합을 수행합니다. | |
~ | 보수(NOT)연산자 | 피연산자의 비트에 대해 0은 1로, 1은 0으로 반전시킵니다. 단항 연산자입니다. | int, uint, long, ulong 에 사용할 수 있습니다. |
이 표가 나타내는 것처럼 비트 논리 연산자는 bool 형식 외에 정수 계열 형식의 피연산자에 대해서도 사용할 수 있습니다.
그럼 어떻게 참과 거짓 두 가지의 진릿값으로 정수 형식에 대해서도 논리 연산을 한다는 것일까요?
우리가 사용하는 데이터가 0과 1로 이루어져 있고, int 형식 데이터는 4바이트, 즉 32개의 비트로 이루어지고, long 형식 데이터는 64비트로 이루어져 있습니다. 비트논리 연산은 이 비트 덩어리를 이루고 있는 각 비트에 대해 1은 참, 0은 거짓으로 해서 논리 연산을 하는 것입니다.
먼저 논리곱 연산자 &를 같이 보겠습니다.
논리곱에서는 두 비트 모두 1(참) 이어야 결과도 1(참)이 됩니다.
int result = 9 & 10; // result는 8
이번에는 논리합 연산자 |를 보겠습니다.
논리합에서는 둘 중 하나라도 참(1)이면 참(1)입니다.
int result = 9 | 10; // result는 11
배타적 논리합은 두 피연산자의 진릿값이 서로 달라야 참이 된다는 특징이 있습니다.
int result = 9 ^ 10; // result는 3
~는 피연산자가 하나뿐인 단항 연산자로서 비트를 0에서 1로, 1에서 0으로 뒤집는 기능을 합니다.
255
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
-256
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
int a = 255;
int result = ~a; // result는 -256
[실습]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ThisIsCSharpExam.Ch._04.BitWiseOperator
{
class BitWiseOperatorExam
{
public void Main()
{
int a = 9;
int b = 10;
Console.WriteLine($"{a} & {b} : {a & b}");
Console.WriteLine($"{a} | {b} : {a | b}");
Console.WriteLine($"{a} ^ {b} : {a ^ b}");
int c = 255;
Console.WriteLine("~{0}(0x{0:X8}) : {1}(0x{1:X8})", c, ~c);
}
}
}
#10 할당 연산자
할당 연산자는 이름처럼 변수 또는 상수에 피연산자 데이터를 할당하는 기능을 합니다.
연산자 | 이름 | 설명 |
= | 할당 연산자 | 오른쪽 피연산자를 왼쪽피연산자에 할당합니다. |
+= | 덧셈 할당 연산자 | a += b;는 a = a + b;와 같습니다. |
-= | 뺄셈 할당 연산자 | a -= b;는 a = a - b;와 같습니다. |
*= | 곱셈 할당 연산자 | a *= b;는 a = a * b;와 같습니다. |
/= | 나눗셈 할당 연산자 | a /= b;는 a = a / b;와 같습니다. |
%= | 나머지 할당 연산자 | a %= b;는 a = a % b;와 같습니다. |
&= | 논리곱 할당 연산자 | a &= b;는 a = a & b;와 같습니다. |
|= | 논리합 할당 연산자 | a |= b;는 a = a | b;와 같습니다. |
^= | 배타적 논리합 할당 연산자 | a ^= b;는 a = a ^ b;와 같습니다. |
<<= | 왼쪽 시프트 할당 연산자 | a <<= b;는 a = a << b;와 같습니다. |
>>= | 오른쪽 시프트 할당 연산자 | a >>= b;는 a = a >> b;와 같습니다. |
[실습]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ThisIsCSharpExam.Ch._04.AssignmentOperator
{
class AssignmentOperatorExam
{
public void Main()
{
int a;
a = 100;
Console.WriteLine($"a = 100 : {a}");
a += 90;
Console.WriteLine($"a += 90 : {a}");
a -= 80;
Console.WriteLine($"a -= 80 : {a}");
a *= 70;
Console.WriteLine($"a *= 70 : {a}");
a /= 60;
Console.WriteLine($"a /= 60 : {a}");
a %= 50;
Console.WriteLine($"a %= 50 : {a}");
a &= 40;
Console.WriteLine($"a &= 40 : {a}");
a |= 30;
Console.WriteLine($"a |= 30 : {a}");
a ^= 20;
Console.WriteLine($"a ^= 20 : {a}");
Console.WriteLine($"a ^= 20 : {a}");
a <<= 10;
Console.WriteLine($"a <<= 10 : {a}");
a >>= 1;
Console.WriteLine($"a >>= 1 : {a}");
}
}
}
#11 null 병합 연산자
null 병합 연산자 ??는 null 조건부 연산자처럼 프로그램에서 종종 필요한 변수/객체의 null 검사를 간결하게 만들어주는 역할을 합니다.
?? 연산자는 두 새의 피연산자를 받아들이고 왼쪽 피연산자가 null인지 평가합니다.
평가 결과가 null이 아닌 것으로 나타나면 왼쪽 피연산자를 그대로 반환하고, 만약 왼쪽 피연산자가 null인 것으로 평가되면 오른쪽 피연산자를 반환합니다.
[실습]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ThisIsCSharpExam.Ch._04.NullCoalescing
{
class NullCoalescingExam
{
public void Main()
{
int? num = null;
Console.WriteLine($"{num ?? 0}");
num = 99;
Console.WriteLine($"{num ?? 0}");
string str = null;
Console.WriteLine($"{str ?? "Default"}");
str = "Specific";
Console.WriteLine($"{str ?? "Default"}");
}
}
}
#12 연산자의 우선순위
우선순위 | 종류 | 연산자 |
1 | 증가/감소 연산자 및 null 조건부 연산자 | 후위++ / --연산자, ?., ?[] |
2 | 증가/감소 연산자 | 전위++ / --연산자 |
3 | 산술 연산자 | * / % |
4 | 산술 연산자 | + - |
5 | 시프트 연산자 | << >> |
6 | 관계 연산자 | <> <=>=is as |
7 | 관계 연산자 | == != |
8 | 비트 논리 연산자 | & |
9 | 비트 논리 연산자 | ^ |
10 | 비트 논리 연산자 | | |
11 | 논리 연산자 | && |
12 | 논리 연산자 | || |
13 | null 병합 연산자 | ?? |
14 | 조건 연산자 | ?: |
15 | 할당 연산자 | = *= /= %= += -= <<= >>= &= ^= |= |