'이것이 C#이다 개정판'의 chapter 4를 정리한 글입니다.
삼항 연산자
| 조건식 ? 참일_때의_값 : 거짓일_때의_값 |
삼항 연산자의 사용에 대한 장단점을 if문과 비교하여, 삼항 연산자의 사용에 대해 의문을 가질 수 있습니다.
사실 우리에게 익숙한 것은 if문이 훨씬 더 익숙하기 때문이죠.
하지만 삼항 연산자를 사용했을 때 장점이 명확한 부분들이 있기 때문에, 삼항 연산자도 잘 기억해둬야 합니다.
[장점]
- 간결합니다.
- 코드가 짧고 간결해져서 한 줄로 표현이 가능합니다.
- 간단한 조건부 로직을 빠르게 작성할 수 있습니다.
- 값이 직접적으로 결정되는 상황에서 명확하게 표현할 수 있습니다.
- 때에 따라 다르겠지만, 간단한 경우에는 가독성을 높일 수도 있습니다.
조건과 결과가 명확하게 드러나기 때문이죠.
- 때에 따라 다르겠지만, 간단한 경우에는 가독성을 높일 수도 있습니다.
- 임시 변수(객체)를 생성하지 않습니다.
- 임시 객체를 생성하지 않기 때문에, 메모리 사용을 줄이고 가비지 컬렉션의 부담을 감소시킬 수 있습니다.
int score = (player.IsAlive) ? 100 : 0;
[단점]
- 로직 또는 조건이 복잡해지면, 가독성 저하와 제어 흐름의 복잡성으로 이어집니다.
- 복잡한 조건이 포함될 경우 가독성이 떨어질 수 있습니다.
- 복잡한 조건과 로직이 필요하여 삼항 연산자가 중첩되면 구문에 대한 이해가 떨어질 수 있습니다.
- 로직이 복잡지면 오히려 if문을 사용하는 것이 더 명확할 수 있습니다.
string result = (condition1) ? "A" : (condition2) ? "B" : "C";
[정리]
이렇듯, 간단한 조건이 필요할 때는 삼항 연산자를 사용하여 간결하게 표현이 가능합니다.
하지만 복잡한 로직과 조건은 if문을 사용하는 것이 더 명확할 것입니다.
null 조건부 연산자
| 객체_이름?.객체의_멤버 |
?. 연산자는 객체의 멤버에 접근하기 전에 해당 객체가 null인지 검사하여 그 결과가 참(즉, 객체가 null)이면 그 결과로 null을 반환하고, 그렇지 않은 경우에는 접근하려는 멤버를 반환합니다.
?[ ] 연산자 또한 비슷한 역할을 하지만, 객체의 멤버 접근이 아닌 배열과 같은 컬렉션 객체의 첨자를 이용한 참조에 사용됩니다.
※ 컬렉션(Collections) 객체는 컬렉션 네임스페이스에서 제공하는 컬렉션을 말합니다.
나중 글에서 더 자세히 설명하겠지만, 간단하게 대표적인 컬렉션들만 정리해 보겠습니다.
| System.Collections | 설명 |
| ArrayList | 크기가 가변적인 배열로, 다양한 데이터 타입을 저장할 수 있습니다. |
| Hashtable | Key - Value 쌍으로 데이터를 저장하는 해시 테이블입니다. |
| Queue | 선입선출(FIFO) 방식으로 데이터를 저장하는 큐입니다. |
| Stack | 후입선출(LIFO) 방식으로 데이터를 저장하는 스택입니다. |
| Dictionary Base | 해시 테이블을 기반으로 한 클래스로, 사용자 정의 딕셔너리를 만들 때 사용합니다. |
[예시]
using System.Collections;
using static System.Console;
namespace StringFormatDatetime
{
class MainApp
{
static void Main(string[] args)
{
using System.Collections;
using static System.Console;
namespace NullConditionalOperator
{
class MainApp
{
static void Main(string[] args)
{
ArrayList a = null;
a?.Add("야구"); // a?.가 null을 반환하므로 Add() 메소드는 호출되지 않습니다.
a?.Add("축구");
WriteLine($"Count : {a?.Count}");
WriteLine($"{ a?[0]}");
WriteLine($"{ a?[1]}");
a = new ArrayList(); // a는 이제 더 이상 null이 아닙니다.
a?.Add("야구");
a?.Add("축구");
WriteLine($"Count : {a?.Count}");
WriteLine($"{a?[0]}");
WriteLine($"{a?[1]}");
}
}
}
}
}
}
// 실행결과
Count : // a?.가 null을 반환하므로 "Count :" 외에는 아무것도 출력하지 않습니다.
Count : 2
야구
축구
비트 연산자
| 연산자 | 이름 | 설명 | 지원 형식 |
| << | 왼쪽 시프트 연산자 | 첫 번째 피 연산자의 비트를 두 번째 피연산자의 수만큼 왼쪽으로 이동시킵니다. (비트를 옮긴 후, 남아있는 부분에 대해서는 양수면 0, 음수면 1을 채웁니다.) |
첫 번째 피연산자는 int, uint, long, ulong이며 두 번째 피 연산자는 int 형식만 지원합니다. |
| >> | 오른쪽 시프트 연산자 | 첫 번째 피 연산자의 비트를 두 번째 피연산자의 수만큼 오른쪽으로 이동시킵니다. (비트를 옮긴 후, 남아있는 부분에 대해서는 양수면 0, 음수면 1을 채웁니다.) |
<<와 같습니다. |
| & | 논리곱(AND) 연산자 | 두 피연산자의 비트 논리곱을 수행합니다. | 정수 계열 형식과 bool 형식에 대해 사용할 수 있습니다. |
| | | 논리합(OR) 연산자 | 두 피연산자의 비트 논리합을 수행합니다. | &와 같습니다. |
| ^ | 배타적 논리합(XOR) 연산자 | 두 피연산자의 비트 배타적 논리합을 수행합니다. |
&와 같습니다. |
| ~ | 보수(NOT) 연산자 | 피 연산자의 비트를 0은 1로, 1은 0으로 반전시킵니다. 단항 연산자입니다. |
int, uing, long, ulong에 대해 사용이 가능합니다. |
[시프트 연산자 예시]
using System;
using static System.Console;
namespace ShiftOperator
{
class MainApp
{
static void Main(string[] args)
{
WriteLine("Testing << ...");
int a = 1;
WriteLine("a : {0:D5} (0x{0:X8})", a);
WriteLine("a << 1 : {0:D5} (0x{0:X8})", a << 1);
WriteLine("a << 2 : {0:D5} (0x{0:X8})", a << 2);
WriteLine("a << 5 : {0:D5} (0x{0:X8})", a << 5);
WriteLine("\nTesting >> ...");
int b = 255;
WriteLine("b : {0:D5} (0x{0:X8})", b);
WriteLine("b >> 1 : {0:D5} (0x{0:X8})", b >> 1);
WriteLine("b >> 2 : {0:D5} (0x{0:X8})", b >> 2);
WriteLine("b >> 5 : {0:D5} (0x{0:X8})", b >> 5);
WriteLine("\nTesting >> 2...");
int c = -255;
WriteLine("c : {0:D5} (0x{0:X8})", c);
WriteLine("c >> 1 : {0:D5} (0x{0:X8})", c >> 1);
WriteLine("c >> 2 : {0:D5} (0x{0:X8})", c >> 2);
WriteLine("c >> 5 : {0:D5} (0x{0:X8})", c >> 5);
}
}
}
// 실행 결과
Testing << ...
a : 00001 (0x00000001)
a << 1 : 00002 (0x00000002)
a << 2 : 00004 (0x00000004)
a << 5 : 00032 (0x00000020)
Testing >> ...
b : 00255 (0x000000FF)
b >> 1 : 00127 (0x0000007F)
b >> 2 : 00063 (0x0000003F)
b >> 5 : 00007 (0x00000007)
Testing >> 2...
c : -00255 (0xFFFFFF01)
c >> 1 : -00128 (0xFFFFFF80)
c >> 2 : -00064 (0xFFFFFFC0)
c >> 5 : -00008 (0xFFFFFFF8)
null 병합 연산자
null 병합 연산자는 왼쪽 피연산자가 null인지를 평가한 후, null이라면 오른쪽 피 연산자를 반환하고, 아니라면 왼쪽 피연산자의 값을 반환합니다.
[사용 방법]
int? a = null;
Console.WriteLine($"{a ?? 0}"); // a는 null이므로 0이 출력됩니다.
a = 99;
Console.WriteLine($"{a ?? 0}"); // a는 null이 아니므로 99가 출력됩니다.
string str = null;
ConsoleWriteLine($"{str ?? "Default"}"); // str은 null이므로 "Default"가 출력됩니다.
str = "Specific";
ConsoleWriteLine($"{str ?? "Default"}"); // str은 null이 아니므로 "Specific"이 출력됩니다.
[주의할 점]
개념과 사용 방법은 간단하지만 주의할 점이 있습니다.
null 병합 연산자가 지원하는 형식은 Nullable<T> 형식 또는 참조 형식만을 지원합니다.
값 형식에서는 null 병합 연산자가 지원되지 않으므로 주의하길 바랍니다.
[예시]
int a = 100;
Console.WriteLine($"{a ?? 0}");
// 위의 경우에 CS0019라는 컴파일러 오류가 발생합니다.
// "'??' 연산자는 'int' 및 'int' 형식의 피연산자에 적용할 수 없습니다." 라는 문구를 동반합니다.
// 수정된 구문
int? a = 100;
Console.WriteLine($"{a ?? 0}");
// 이렇게 수정하게 되면 null 병합 연산자를 사용하는 의미가 없지만
// 컴파일러 오류를 해결하기 위해서는 해당 방식처럼 a를 Nullable 타입으로 선언 및 초기화하여야 합니다.
연산자의 우선 순위
| 우선순위 | 종류 | 연산자 |
| 1 | 증가 / 감소 연산자 및 null 조건부 연산자 |
후위 ++/-- 연산자 ?. ?[ ] |
| 2 | 증가/감소 연산자 | 전위 ++/-- 연산자 |
| 3 | 산술 연산자 | * / % |
| 4 | 산술 연산자 | + - |
| 5 | 시프트 연산자 | << >> |
| 6 | 관계 연산자 | < > <= >= is as |
| 7 | 관계 연산자 | == != |
| 8 | 비트 논리 연산자 | & |
| 9 | 비트 논리 연산자 | ^ |
| 10 | 비트 논리 연산자 | | |
| 11 | 논리 연산자 | && |
| 12 | 논리 연산자 | || |
| 13 | null 병합 연산자 | ?? |
| 14 | 조건(삼항) 연산자 | ?: |
| 15 | 할당 연산자 | = *= /= %= += -= <<= >>= &= ^= |= |
'C#' 카테고리의 다른 글
| [C#] 메소드 (0) | 2025.04.11 |
|---|---|
| [C#] Switch, 반복문, 점프문 (0) | 2025.04.10 |
| [C# 기본 정리 3] 형 변환, Nullable, var, 전역변수, 문자열 (0) | 2025.04.04 |
| [C# 기본 정리 2] 변수, 힙, 데이터 형식, 오버플로우와 언더플로 (0) | 2025.04.03 |
| [C# 기본 정리 1] 컴파일러와 인터프리터, C#의 컴파일 (0) | 2025.04.01 |