본문 바로가기
ZAION/C++

[이것이 C#이다]Ch.05 코드의 흐름 제어하기 - 2

by 우기37 2024. 1. 31.

## 목차

3) 점프문

4) 패턴 매칭

 

 

 

#3 점프문

점프문은 흐름을 끊고 프로그램의 실행 위치를 원하는 곳으로 단숨에 도약시킬 수 있습니다.

 

C#에서 제공하는 점프문에는 다음과 같이 다섯 가지가 있습니다.

  • break
  • continue
  • goto
  • return
  • throw

 

 

1. break

break문은 현재 실행 중인 반복문이나 switch 문의 실행을 중단하고자 할 때 사용합니다. 반복문이나 switch 문의 중단시키려는 지점에 입력해두면 됩니다.

 

int i = 0;	// i를 초기화하는 코드가 실행되고

while (i >= 0)	// 루프가 실행되다가
{
	if (i == 10)
    	breal;	// i가 10이 되면 while 문에서 탈출합니다.
        
        Console.WriteLine(i++);
}
Console.WriteLine("Prison Break");	// 프로그램의 실행 위치는 while 블록 다음으로 옮겨집니다.

 

[실습]

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ThisIsCSharpExam.Ch._05.JumpStatementExam
{
    class BreakExam
    {
        public void Main()
        {
            while (true)
            {
                Console.WriteLine("계속할까요?(예/아니요) : ");
                string answer = Console.ReadLine();

                if (answer == "아니요")
                    break;
            }
        }
    }
}

 

 

 

2. continue

continue 문은 한 회 건너 뛰어 반복을계속 수행하게 하는 기능을 합니다.

다음과 같이 continue 문이 반복문안에 사용되면, i가 3인 경우 현재 실행 중인 반복을 건너뛰고 다음 반복으로 넘어갑니다.

 

for (int i = 0; i < 5; i++)
{
	if (i == 3)
    	continue;	// i가 3인 경우 현재 실행 중인 반복을 건너뜁니다.
        
    Console.WriteLine(i);
}

  

continue 문이 사용된 코드는 척 보면 그 아랫줄을 더 읽어보지 않아도 해당 코드 블록의 실행이 취소됨을 알 수 있습니다. 그렇지 않은 쪽은 코드 블록을 끝까지 읽어봐야 더 실행할 코드가 남아 있는지/없는지 알 수 있습니다.

 

[실습]

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ThisIsCSharpExam.Ch._05.JumpStatementExam
{
    class ContinueExam
    {
        public void Main()
        {
            for (int i = 0; i < 10; i++)
            {
                if (i % 2 == 0)
                    continue;

                Console.WriteLine($"{i} : 홀수");
            }
        }
    }
}

 

 

 

3. goto

goto 문은 점프문 중에서도 가장 터프한 점프문입니다.

 

goto 레이블;

레이블 : 	// 레이블을 선언할 때는 콜론(:)을 붙입니다.
	// 이어지는 코드

 

goto 문의 사용 형식에서 레이블은 변수하고는 좀 가른데, 코드 안의 위치를 나타내는 표지판 같은 존재입니다. goto 문은 저 레이블이 가리키는 곳으로 바로 뛰어 넘어갑니다.

 

goto 문은 상당수의 프로그래머들은 별로 좋아하지 않습니다. 코드의 이곳저곳으로 텔레포트하면서 흐름을 자주 끊어 코드를 읽기 어렵게 만들기 때문입니다.

그런데도 foto 문이 유용한 경우가 있는데, 대표적인 예가 다음 코드처럼 중첩된 반복문을 단번에 뚫고 나오기 위해 사용하는 것입니다.

 

[실습] 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ThisIsCSharpExam.Ch._05.JumpStatementExam
{
    class GotoExam
    {
        public void Main()
        {
            Console.Write("종료 조건(숫자)을 입력하세요 : ");

            string input = Console.ReadLine();

            int input_number = Convert.ToInt32(input);

            int exit_number = 0;

            for (int i = 0; i < 2; i++)
            {
                for (int j = 0; j < 2; j++)
                {
                    for (int k = 0; k < 3; k++)
                    {
                        if (exit_number++ == input_number)	// 조건이 참이면33행으로 단숨에 점프합니다. 
                            goto EXIT_FOR;

                        Console.WriteLine(exit_number);
                    }
                }
            }
            goto EXIT_PROGRAM;	// 36행으로 점프합니다. 33행의 EXIT_FOR를 만나지 않기 위해서입니다.

            EXIT_FOR:
            Console.WriteLine("\nExit nestes for...");

            EXIT_PROGRAM:
            Console.WriteLine("Exit program...");
        }
    }
}

 

 

30을 입력한 결과를보면, 3개의 for 반복으로 인해 exit_number++가 실행되는 횟수는 총 2 x 2 x 3 = 12 회 입니다. 그 안에  goto EXIT_FOR를 못 만나면 31행의 goto EXIT_PROGRAM을 만나고 이로 인해 EXIT_PROGRAM 문으로 이동합니다.

 

 

 

#4 패턴 매칭

패턴 매칭은 하스켈이나 엘릭서 같은 함수형 언어에서 무척 사랑받는 기능 중 하나입니다. 많은 프로그래머가 C#에 도입할 것을 무수히 요청한 기능이기도 합니다.

 

패턴 매칭은 어떤 식이 특정 패턴(형태)과 일치하는지를 검사합니다. 패턴 매칭을 이용하면 장황하고 거추장스러운 분기문을 간결하고 읽기 쉬운 코드로 대체할 수 있습니다.

 

 

이와 같이 패턴 매칭은 식을 입력받고 일치 여부를 반환합니다.

 

식이란, 코드에서 단일 결과값을 만들어낼 수 있는 연산자와 연산자의 조합을 말합니다. 1 + 2는 단일 결과값 3을 만들어내는 식이고, 10도 단일 결과값 10을 만드는 식입니다. 다시 말해, 리터럴과 상수, 변수는 연산자 없이 식이 될 수 있습니다.

 

'식의 패턴' 이라는 말은 '식 결과의 패턴'이라는 말과 같습니다. C#이 지원하는 패턴의 종류에는 여러 가지가 있습니다.

 

  • 선언 패턴
  • 형식 패턴
  • 상수 패턴
  • 프로퍼티 패턴
  • 관계 패턴
  • 논리 패턴
  • 괄호 패턴
  • 위치 패턴
  • var 패턴
  • 무시 패턴
  • 목록 패턴

 

1. 선언 패턴

주어진 식이 특정 형식(ex. int, string 등)과 일치하는지를 평가합니다. 만약 주어진 식과 형식이 일치한다면, 선언 패턴은 식을 해당 형식으로 변환합니다.

 

object foo = 23;
if (foo is int bar)	// 1. foo가 int인 경우 2. foo를 int 형식으로 변환하여 bar에 할당합니다.
{
	Console.WriteLine(bar);
}

 

foo is int가 true를 반환하는 경우에 bar 변수가 if 블록 안에 생성되고 23이 출력되지만, false를 반환하는 경우에는 생성되지 않습니다. 물론 bar가 출력되는 일도 없습니다.

 

 

2. 형식 패턴

형식 패턴은 선언 패턴과 거의 같은 방식으로 동작하지만, 변수생성 없이 형식 일치 여부만테스트합니다.

 

object foo = 23;
if (foo is int)
{
	Console.WriteLine(foo);
}

  

 

3. 상수 패턴

상수 패턴은 특정 상수와 일치하는지를 검사하며, 가장 많이 상요하는 패턴이기도 합니다. 정수 리터럴과 문자열 리터럴뿐 아니라 null과 enum 등 모든 상수와 매칭할 수 있습니다.

 

var GetCountryCode = (string nation) => nation switch
{
	"KR" => 82,
    "US" => 1,
    "UK" => 44,
    _ => throw new ArgumentException("Not supported Code")
};
Console.WriteLine(GetCountryCode("KR"));
Console.WriteLine(GetCountryCode("US"));
Console.WriteLine(GetCountryCode("UK"));

 

간단하게 어떤 객체가 null 인지를 확인하고 싶다면, 다음과 같이 is 연산자와 함게 상수 패턴 매칭을 이용할 수 있습니다.

 

if (obj is null)	// obj == null
{
...
}

if (obj is not null)	// obj != null
{
...
}

 

 

4. 프로퍼티 패턴

프로퍼티 패턴 매칭은 식의 속성이나 필드가 패턴과 일치하는지를 검사합니다.

입력된 식이 int, double 같은 기본 데이터 형식이 아닌 경우에 특히 유용하게 사용할 수 있습니다.

 

 

5. 관계 패턴

관계 패턴 매칭은 >, >=, ==, !=, <, <= 와 같은 관계 연산자를 이용하여 입력받은 식을 상수와 비교합니다.

 

static int IsPassed(double score) => score switch
{
	< 60 => false,
	_ => true,
}

 

IsPassed() 메소드는 입력받은 score가 60보다 작으면 false를 그렇지 않으면 true를 반환합니다.

 

 

6. 논리 패턴

패턴과 패턴을 패턴 논리 연산자(and(결합), or, not)을 조합해서 하나의 논리 패턴으로 만들 수 있습니다.

 

static string GetGrade(double score) => score switch
{
	< 60 => "F",
    >= 60 and < 70 => "D",
    >= 70 and < 80 => "C",
    >= 80 and < 90 => "B",
    _ => "A",
};

 

 

7. 괄호 패턴

괄호 패턴은 소괄호 () 로 패턴을 감쌉니다. 보통 논리 패턴으로 여러 패턴을 조합한 뒤 이를 새로운 패턴으로 만드는 경우에 사용합니다.

 

object age = 30;

if (age is (int and > 19))
	Console.WriteLine("Major");

 

 

8. 위치 패턴

위치 패턴은 식의 결과를 분해하고, 분해된 값들이 내장된 복수의 패턴과 일치하는지를 검사합니다.

위치 패턴 안에 내장되는 패턴에는 형식, 상수 패턴 등 어떤 패턴이든 올 수 있습니다. 단, 분해된 값들과 내장된 패턴의 개수, 순서가 일치해야 한다는 점에는 주의해야 합니다.

 

Tuple<string, int> itemPrice = new Tuple<string, int>("espresso", 3000);

if (itemPrice is ("espresso", < 5000))
{
	Console.WriteLine("The coffee is affordable.");
}

 

이 예제에서 itemPrice는 string과 int 요소로 이루어진 튜플입니다. 이 튜플을 상수 패턴 ("espresso")과 관계 패턴 (<5000)으로 이루어진 위치 패턴으로 매칭하고 있습니다.

 

 

9. var 패턴 

null을 포함한 모든 식의 패턴 매칭을 성공시키고, 그 식의 결과를 변수에 할당합니다. 다음 예제와 같이 어떤 식의 결과를 임시 변수에 할당한 뒤 추가적인 연산을 수행하고자 할 때 유용하게 사용할 수 있습니다.

 

// 모든 과목이 60점이 넘고, 평균이 60점 이상인 경우에만 Pass
var IsPassed =
	(int[] scores) => scores.Sum() / scores.Length is var average
    && Array.TrueForAll(scores, (score) => score >= 60)
    && average >= 60;
    
int[] scores1 = {90, 80, 60, 80, 70};
Console.WriteLine($"{string.Join(",", scores1)}: Pass:{IsPassed(scores1)}");

int[] scores2 = {90, 80, 59, 80, 70};
Console.WriteLine($"{string.Join(",", scores2)}: Pass:{IsPassed(scores2)}");

 

 

10. 무시 패턴

무시 패턴도 var 패턴처럼 모든 식과의 패턴 일치 검사를 성공시킵니다. 하지만 var 패턴과는 다르게 is 식에서는 사용할 수 없고, switch 식에서만 사용할 수 있습니다. '모든 식'을 매칭할 수 있기 때문에 switch 문의 default 케이스와 비슷한 용도로 사용하면 됩니다. 무시 패턴은 _ 기호를 이용합니다.

 

var GetCountryCode = (string nation) => nation switch
{
	"KR" => 82,
    "US" => 1,
    "UK" => 44,
    _ => throw new ArgumentException("Not supported Code")	// 무시 패턴 매칭
};

Console.WriteLine(GetCountryCode("KR"));
Console.WriteLine(GetCountryCode("US"));
Console.WriteLine(GetCountryCode("UK"));

이 코드는 "KR", "US", "UK" 이 세 개 상수와 일치하지 않는 모든 식의 값을 _에 매칭시켜 ArgumentException을 던집니다.

 

 

11. 목록 패턴

목록 패턴은 배열이나 리스트가 패턴의 시퀀스가 일치하는지를 검사합니다. 패턴의 시퀀스는 대괄호 [ 와 ] 사이에  패턴의 목록을 입력해서 만듭니다.

 

var match = (int[] array) => array is [int, >10, _];

Console.WriteLine(match(new int[] {1, 100, 3}));	// true
Console.WriteLine(match(new int[] {100, 10, 999}));	// false

 

[int, >10, _]라는 패턴 시퀀스는{1, 100, 3}이라는 배열에 대해서는일치하지만, {100, 10, 999}라는 배열에 대해서는 일치하지 않습니다. 배열의 두 번째 요소 10이 패턴 시퀀스의 두 번째 요소 >10 과 일치하지 않시 때문입니다.

 

var match = (int[] array) => array is [int, >10, ..];

Console.WriteLine(match(new int[] {1, 100, 101, 102, 103, 104}));	// true
Console.WriteLine(match(new int[] {100, 10, 999}));	// false

 

범위 패턴 ..을 함께 사용하면 식으로 입력되는 배열이나 리스트의 길이에 관계없이 패턴 매칭을 수행할 수 있습니다. 예를 들어 [int, >10, ...]은 첫 두 요소에 대해서만 패턴이 일치하는지를 검사하고 게 번재 이후의 요소에 대해서는 길이를 포함해서검사를 수행하지 않습니다.