ZAION/C#

[이것이 C#이다]Ch.04 데이터를 가공하는 연산자

우기37 2024. 1. 29. 11:23

## 목차

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 할당 연산자 = *= /= %= += -= <<= >>= &= ^= |=