본문 바로가기

C#/이것이 C#이다

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

[ 학습 흐름 ]

[ 학습 흐름]

  1. 분기문
  2. if , else 그리고 else if
  3. if 문 중첩해서 사용하기
  4. switch
  5. 반복문
  6. while
  7. do while
  8. for
  9. 중첩 for
  10. foreach
  11. for 또는 while을 이용한 무한 반복 코드
  12. 점프문
  13. continue
  14. goto
  15. return 과 throw

[ 분기문 ]

[ 분기문 ]

- 프로그램의 흐름을 조건에 따라 여러 갈래로 나누는 흐름 제어 구문 .

- if문 switch문 두 종류가 있다 .


[ if , else , else if ]

- 한번에 단 하나의 조건을 평가한다 .

using System;

namespace AssignmentOperator;

class MainApp
{
    //프로그램 실행이 시작되는 곳
    static void Main(string[] args)
    {
        Console.WriteLine("숫자를 입력하세요");
        //사용자로부터 문자열을 입력받아 그 결과를 반환
        string input = Console.ReadLine();
        int number=Int32.Parse(input);

        if (number < 0)
            Console.WriteLine("음수");
        else if (number > 0)
            Console.WriteLine("양수");
        else
            Console.WriteLine("0");

        if (number % 2 == 0)
            Console.WriteLine("짝수");
        else
            Console.WriteLine("홀수");
    }
}

 


[ if 문 중첩해서 사용하기 ]

- 분기문,반복문 등의 흐름 제어문은 또 다른 흐름 제어문과 중첩하여 사용 가능하다 .

- 프로그램의 흐름은 가급적 단순하고 명료하게 유지하는것이 좋다 .

using System;

namespace AssignmentOperator;

class MainApp
{
    //프로그램 실행이 시작되는 곳
    static void Main(string[] args)
    {
        Console.WriteLine("숫자를 입력하세요");
        //사용자로부터 문자열을 입력받아 그 결과를 반환
        string input = Console.ReadLine();
        int number=Int32.Parse(input);

        if (number > 0)
        {
            if (number % 2 == 0)
                Console.WriteLine("0보다 큰 짝수");
            else
                Console.WriteLine("0보다 큰 홀수");
        }
        else
            Console.WriteLine("0보다 작거나 같은 수");
    }
}

 


[ switch 문 ]

Switch 문

- 조건식의 결과가 가질 다양한 경우를 한번에 평가하고 프로그램의 흐름을 가른다 .

- 사용되는 break 문 대신에 goto / return / throw와 같은 점프문 사용이 문법적으로 가능하다 .

switch (조건식)
{
	//switch 절
	case 패턴 1:	//switch 레이블
    //실행 코드
	break;		  //브레이크문
}
using System;

namespace Switch;

class MainApp
{
    //프로그램 실행이 시작되는 곳
    static void Main(string[] args)
    {
        Console.WriteLine("요일을 입력해주세요");
        string day = Console.ReadLine();

        switch(day)
        {
            case "일":
                Console.WriteLine("Sunday");
                break;
            case "월":
                Console.WriteLine("Monday");
                break;
            case "화":
                Console.WriteLine("Tuesday");
                break;
            case "수":
                Console.WriteLine("Wednesday");
                break;
            case "목":
                Console.WriteLine("Thursday");
                break;
            case "금":
                Console.WriteLine("Friday");
                break;
            case "토":
                Console.WriteLine("Saturday");
                break;
            default:
                Console.WriteLine("없는 요일");
                break;
        }
    }
}

 


Switch 문과 패턴 매칭

 

- switch 레이블의 패턴에는 상수와 형식을 비롯 관계,논리등 C#이 지원하는 다양한 패턴 적용이 가능하다 .

- Parse 메서드는변환 실패시 예외를 던지는데 , 이때 프로그램은 현재 코드의 실행을 멈추고 흐름을 다른곳으로 이동한다.

-TryParse메서드는 변환의 성공 여부를 반환하기에 현재 코드의 흐름을 유지 할 수 있다 . 

- TryParse가 변환한 데이터는 두번째 매개변수에 저장된다 .

//C# 컴파일러는 123 리터럴을 평가해 int형식임을 유추한다 . 이후 obj안에 박싱해넣는다 .
object obj =123 ;

switch(obj)
{
	//obj에 담겨 있는 데이터 형식이 int 이므로 프로그램은 이 case 절을 따라가 분기함
	case int:
    //실행
    break;
}
using System;

namespace Switch;

class MainApp
{
    //프로그램 실행이 시작되는 곳
    static void Main(string[] args)
    {
        object obj = null;

        string s = Console.ReadLine();
		
        if (int.TryParse(s, out int out_i))
            obj = out_i;
        else if (float.TryParse(s, out float out_f))
            obj = out_f;
        else
            obj = s;

        switch(obj)
        {
            case int:
                Console.WriteLine($"{(int)obj}는 int형식입니다");
                break;
            case float:
                Console.WriteLine($"{(float)obj}는 float형식입니다");
                break;
            default:
                Console.WriteLine($"{obj}는 모르는 형식입니다");
                break;
        }
    }
}

 


케이스 가드

- switch 문의 case 절의 패턴을 더 구체적으로 만들어주는 추가적인 조건 검사

- case 절 뒤에 when 절을 붙여 사용한다 .

case float f when f>=0 : 
	Console.WriteLine($"{f}는 양의 float 형식입니다");
    break;
case float:
	Console.WriteLine($"{f}는 음의 float 형식입니다");
    break;

[ switch 식 ]

-분기를 거쳐 값을 내야 하는 경우에는 switch식을 사용한다 .

//모든 case 절과 default절 에서 하는 일은 grade 에 등급별 학점을 저장하는 것이다 .
switch(score)
{
	case 90:
    	grade = "A";
        break;
    case 80:
    	grade = "B";
        break;
}

- 위와 같은 경우는 switch 식을 사용하면 간단하게 바꿀 수 있다 .

string grade = score switch
{
    90 when repeated ==true =>"B+",
    90 => "A",
    80 =>  "B",
    _ => "F"
};

- 케이스 가드를 이용한 추가 분기 처리도 가능하다 .

using System;

namespace SwitchExp;

class MainApp
{
    //프로그램 실행이 시작되는 곳
    static void Main(string[] args)
    {
        Console.WriteLine("점수를 입력하세요 .");
        int score = Convert.ToInt32(Console.ReadLine());

        Console.WriteLine("재수강인가요? (y/n)");
        string line = Console.ReadLine();
        bool repeated = (line == "y") ? true : false;

        string grade = (int)(Math.Truncate(score / 10.0) * 10) switch
        {
            90 when repeated==true=>"B+",
            90=>"A",
            80=>"B",
            70=>"C",
            60=>"D",
            50=>"E",
            _ => "F"
        };

        Console.WriteLine($"학점 : {grade}");
    }
}

 


[ 반복문 ]

[ 반복문 ]

- 특정 조건을 만족하는 동안 코드/코드블록을 반복해서 실행하도록 하는 문장 .

- while / do while / for / foreach 문 네 종류가 있다 .


[ while ]

- 조건식을 평가후 그 결과가 참이라면 코드를 반복 실행한다 .

while (조건식)
	반복 실행할 코드

 

using System;

namespace While;

class MainApp
{
    //프로그램 실행이 시작되는 곳
    static void Main(string[] args)
    {
        int i = 10;
        while(i>0)
        {
            Console.WriteLine($"i : {i--}");
        }
    }
}

 


[ do while ]

- 조건식을 평가하기 전 무조건 한 번은 코드를 실행후 평가하여 반복실행 .

do
{
//반복실행할 코드 블록
}while(조건식);
using System;

namespace While;

class MainApp
{
    //프로그램 실행이 시작되는 곳
    static void Main(string[] args)
    {
        int i = 10;
        do
        {
            Console.WriteLine("a) i : {0}", i--);
        } while (i > 0);

        do
        {
            Console.WriteLine("b) i : {0}", i--);
        }
         while (i > 0);
    }
}

 


[ for ]

- while문 보다 더 정교한 반복제어

for(초기화식;조건식;반복식)
	반복 실행할 코드

- 초기화식 : 반복을 실행하기 전에 가장 먼저 딱 한번 실행되는 코드 . for  반복문에서 사용할 변수등을 이곳에서 초기화.

- 조건식 : 반복을 계속 수행할지 결정하는 식

- 반복식 : 반복이 끝날때마다 실행 . 주로 조건식에서 사용하는 변수의 값 조정 . 반복식이 실행된 이후 조건식이 실행되어 반복 여부를 결정한다 .

using System;

namespace For;

class MainApp
{
    //프로그램 실행이 시작되는 곳
    static void Main(string[] args)
    {
        for(int i=0;i<5;i++)
        {
            Console.WriteLine(i);
        }
    }
}

 


[ 중첩 for ]

- for문은 while/do while문 보다 반복 제어를 위한 장치가 잘 갖춰져 있기에 반복문을 겹쳐야 한다면 좋은 후보이다 .

using System;

namespace For;

class MainApp
{
    //프로그램 실행이 시작되는 곳
    static void Main(string[] args)
    {
        for(int i=0;i<5;i++)
        {
            for(int j=0;j<=i;j++)
            {
                Console.Write("*");
            }
            Console.WriteLine();
        }
    }
}

 


[ foreach ]

- foreach 문은 배열(혹은 컬렉션)을 순회하며 각 데이터 요소에 차례대로 접근하도록 해준다 .

- 배열의 끝에 도달하면 자동으로 반복은 종료된다 .

foreach(데이터 형식 변수명 in 배열 또는 컬렉션)
	코드 혹은 코드블록
using System;

namespace ForEach;

class MainApp
{
    //프로그램 실행이 시작되는 곳
    static void Main(string[] args)
    {
        int[] arr = new int[] { 0, 1, 2, 3, 4 };
        foreach (int i in arr)
        {
            Console.WriteLine(i);   
        }
    }
}

 


[ for 또는 while을 이용한 무한 반복 코드 ]

for(;;)
	//반복 실행할 코드블록

- for문의 매개변수에 아무것도 넣지 않으면 무한 실행

using System;

namespace InfinitieFor;

class MainApp
{
    //프로그램 실행이 시작되는 곳
    static void Main(string[] args)
    {
        int i = 0;
        for(; ; )
        {
            Console.WriteLine(i++);   
        }
    }
}

 

 

while(true)
	//반복 실행할 코드 블록

- 평가 결과가 항상 true로 되도록 만들면 무한 실행 .

using System;

namespace InfinitieWhile;

class MainApp
{
    //프로그램 실행이 시작되는 곳
    static void Main(string[] args)
    {
        int i = 0;
        while (true)
            Console.WriteLine(i++);   
    }
}

 

- 프로그램의 종료는 Ctrl + C / 작업 관리자에서 프로세스 종료 두가지 방법이 있다 .


[ 점프문 ]

[ 점프문 ]

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

- break / continue / goto / return / throw 5가지 종류가 있다 .


[ break ]

- 현재 실행 중인 반복문이나 switch문의 실행을 중단하고자 할 때 사용한다 .

using System;

namespace Break;

class MainApp
{
    //프로그램 실행이 시작되는 곳
    static void Main(string[] args)
    {
        while(true)
        {
            Console.WriteLine("계속할까요?(예/아니오)");
            string answer = Console.ReadLine();

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

 


[ continue ]

- 반복문을 멈추게 하난 break와 달리 한 회 건너뛰어 반복을 계속 수행하게 한다 .

- 분기문을 통해 같은 효과를 낼 수 있지만 , continue 문이 사용된 코드의 가독성이 더 좋다 .

using System;

namespace Continue;

class MainApp
{
    //프로그램 실행이 시작되는 곳
    static void Main(string[] args)
    {
        for(int i=0;i<10;i++)
        {
            if (i % 2 == 0)
                continue;
            Console.WriteLine($"{i} : 홀수");
        }
    }
}

 


[ goto ]

goto 레이블;
레이블 :

- 레이블은 코드안의 위치를 나타내는 표지판과 같다 .

- goto문은 레이블이 가리키는 곳으로 바로 뛰어 넘어간다 .

- goto문은 흐름을 끊어 가독성을 줄이므로 선호되지는 않지만 , "중첩된 반복문을 뚫기" 등 특정 상황에서는 유용하다 .

using System;

namespace GoTo;

class MainApp
{
    //프로그램 실행이 시작되는 곳
    static void Main(string[] args)
    {
        Console.WriteLine("종료 조건(숫자)를 입력하세요");

        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)
                        goto EXIT_FOR;
                    Console.WriteLine($"ExitNumber : {exit_number}");
                }
            }
        }
        goto EXIT_PROGRAM;

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

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

 


[ 패턴 매칭 ]

[ 패턴 매칭 ]

- 어떤 식이 특정 패턴과 일치하는지 검사를 하여 일치 여부를 반환한다 .

- 이를 통해 장황한 분기문을 간결하고 가독성 있는 코드로 대체 가능하다 .


[ 식 ]

- 코드에서 단일 결과값을 만들어낼 수 있는 연산자와 연산자의 조합 . d = a+456;

- 리터럴과 상수,변수는 연산자 없이 식이 될 수 있다 . ex) 10 은 단일결과값 10을 만드는 식


[ 식의 패턴 ]

-  식의 패턴 = 식 결과의 패턴


[ 1 . 선언 패턴 ]

-  식이 주어진 형식(int , string )과 일치하는지 테스트

- 테스트가 성공하면(식과 형식이 일치하면) 식을 주어진 형식으로 변환

//is 연산자는 왼쪽에 있는 식이 오른쪽에 있는 패턴과 일치하는지 테스트
object foo = 123;
//1 . foo 가 int 형식과 일치하는지 테스트
//2 . foo를 int 형식으로 변환하여 bar에 할당
if(foo is int bar)
{
	Console.WriteLine(bar);
}

[ 2 . 형식 패턴 ]

-  선언패턴과 비슷하게 동작하지만 , 변수 생성 없이 형식 일치 여부만 테스트한다 .

object foo = 23;
if(foo is int)
	Console.WriteLine(foo);
class Preschooler{}
class Underage{}
class Adult{}
class Senior{}

internal class MainApp
{
	static int CalculateFee(object visitor)
    {
    	return visitor switch
        {
        	Underage = > 100,
            Adult => 500 ,
            Senior => 200,
            _ => throw new ArgumentException(
            	$"Prohibitited age : {visitor.GetType()}",nameif(visitor)),
        };
    }
    
    static void Main(string[] args)
    {
    	Console.WriteLine($"Fee for a senior : {CalculateFee(new Senior())}");
    	Console.WriteLine($"Fee for a adult : {CalculateFee(new Adult())}");
    	Console.WriteLine($"Fee for a underage : {CalculateFee(new Underage())}");
    	Console.WriteLine($"Fee for a prescooler : {CalculateFee(new Prescooler())}");
        
    }
}

[ 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"));

- 어떤 객체가 null인지 확인하고 싶다면 ,is 연산자와 함께 상수 패턴 매칭을 사용 할 수 있다 .

if(object is null)
{
	// ...
}

if(object is not null)
{
	// ...
}

[ 4 . 프로퍼티 패턴 ]

-  식의 속성이나 필드가 패턴과 일치하는지 검사한다 .

- 입력된 식이 int,double과 같은 기본 데이터 형식이 아닌 복합 데이터 형식일 경우 유용하다 .

class Car
{
	public string Model{get;set;}
    public DateTime ProduceAt{get;set;}
}

static string GetNickname(Car car)
{
	var GenerateMessage = (Car car,string nickname)=>
    	$"{car.Model}produced in {car.ProduceAt.Year} is {nickname}"'
    
    if(car is Car{Model:"Mustang",ProducedAt.Year:1967})
    	return GenerateMessage(car,"Fastback");
    else if(car is Car{Model:"Mustang",ProducedAt.Year:1976})
    	return GenerateMessage(car,"Cobar 2");
    else
    	return GenerateMessage(car,"Unkown");
}

static void Main(string args)
{
	Console.WriteLine(
    	GetNickName(
        	new Car(){Model = "Mustang",ProducedAt = new DataTime(196,11,23)}));
}

[ 5 . 관계 패턴 ]

-  관계연산자를 이용하여 입력받은 상수와 비교한다 .

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

- 관계 패턴과 이어 논리 패턴의 and를 함께 이용하면 입력받은 식이 특정 범위안에 드는지 테스트도 가능하다 .

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

[ 6 . 논리 패턴 ]

-  패턴과 패턴을 패턴 논리 연산자 (and , or, not)을 조합해 하나의 논리패턴으로 만든다 .

class OrderItem
{
	public int Amount{get;set;}
    public int Price{get;set;}
}

static double GetPrice(OrderItem orderItem)=> orderItem switch
{
	OrderItem {Amount : 0}or OrderItem{Price:0}
    => 0.0,
    
    OrderItem{Amount>=100} and OrderItem{Price: >= 10_000}
    =>orderItem.Amount * orderItem.Price * 0.8,
    
    not OrderItem{Amount:<100}
    =>orderItem.Amount * orderItem.Price * 0.9,
    _
    =>=>orderItem.Amount * orderItem.Price,
};

statoc void Main(string args[])
{
	Console.WriteLine(GetPrice(new OrderItem(){Amount = 0 , Price = 10_000}));
}

[ 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");

 

struct Audience
{
	public bool IsCitizen{get;init;}
    public int Age{get;init;}
    
    public Audience(bool isCitizen,int age)
    {
    	IsCitizen= isCitizen;
        Age= age;
    }
    
    public Deconstrut(out bool isCitizen,out int age)
    {
    	isCitizen=IsCitizen;
        age=Age;
    }
}

static void Main(string args[])
{
	var CalculateFee = (Audience audience)=>audience switch
    {
    	(true,< 19)=>100,
        (true,_)=>200,
        (false,<19)=>200,
        (false,_)=>400, 
    };
}

var a1 = new Audience(true,10);
Console.WriteLine(
	$"내국인 : {a1.Citizen} , 나이 : {a1.Age} , 요금 : {CalculateFee(a1)}";

[ 9 . var 패턴 ]

-  null을 포함한 모든 식의 패턴매칭을 성공시키고 , 그 식의 결과를 변수에 할당한다 .

- 어떤 식의 결과를 임시 변수에 할당한 뒤 추가 연산을 수행할때 유용하다 .

var IsPassed = 
	(int[] scores) => scores.Sum() / scores.Length is var average
    && Array.TrueForAll(scores , (score) => score >= 60)
    && average >=60;

[ 10 . 무시 패턴 ]

- 모든 식과의 패턴 일치 검사를 성공시킨다 .

-  var 패턴과 다르게 is 식에서는 사용 불가 ,switch 식에서만 사용 가능하다 .

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

[ 11 . 목록 패턴 ]

- 배열이나 리스트가 패턴의 시퀸스와 일치하는지를 검사한다 .

- 패턴의 시퀸스는 대괄화 [와] 사이에 패턴의 목록을 입력하여 만든다 .

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

Console.WriteLine(match(new int[]{1,100,3}));	//True
Console.WriteLine(match(new int[]{1,10,3}));	//false : 두번째 요소 패턴 시퀸스와 불일치

- 범위 패턴 ..을 함께 사용하면 식으로 입력되는 배열이나 리스트의 길이에 관계없이 패턴 매칭을 수행 할수 있다.

//첫 두요소에 대해서만 패턴이 일치하는지 검사 이후 요소에 대해서는 길이 포함해서 검사 수행 안함
var match = (int[]array)=>array is [int,>10,..]

- 목록 패턴은 다량의 데이터를 처리시 유용하다 .

- 파일이나 데이터베이스에서 레코드를 읽어 처리하는 문제에 찰떡 .

using System;
using System.Collections.Generic;
using System.Net.NetworkInformation;

namespace GoTo;

class MainApp
{
    //프로그램 실행이 시작되는 곳
    static void Main(string[] args)
    {
        var GetStatistics = (List<object[]> records) =>
        {
            var statistics = new Dictionary<string, int>();

            foreach (var record in records)
            {
                var (contentType, contentView) = record switch
                {
                    [_, "COMEDY", .., var views] => ("COMEDY", views),
                    [_, "SF", .., var views] => ("SF", views),
                    [_, "Action", .., var views] => ("ACTION", views),
                    [_, .., var amount] => ("ETC", amount),
                    _ => ("ETC", 0),
                };

                if (statistics.ContainsKey(contentType))
                    statistics[contentType] += (int)contentView;
                else
                    statistics.Add(contentType, (int)contentView);
            }
            return statistics;
        };

        List<object[]> MovieRecords = new List<object[]>()
        {
            new object[]{ 0,"COMEDY","SPY",2015,10_000},
            new object[]{ 1,"COMEDY","ScaryMovie",20_000},
            new object[]{ 0,"SF","IRON Man",100_000},
            new object[]{ 0,"COMEDY","HAHA",25_000},
            new object[]{ 0,"SF","Star wars",200_000},
            new object[]{ 5,"ACTION","Fast & Furious ",80_000},
            new object[]{ 6,"DRAMA","Notting Hill",1_000},
        };

        var statistics = GetStatistics(MovieRecords);

        foreach(var s in statistics)
            Console.WriteLine($"{s.Key}:{s.Value}");

    }
}