Devin.KR

C# · 기본

C#과 .NET 첫 프로그램

조건과 반복 - if·switch 식·for·foreach

패턴 매칭 switch, 반복문 선택 기준

개발자KR · 원고 갱신

이 장에서 배우는 것

앞 장에서 변수에 값을 담고 타입에 따라 값이 어떻게 다뤄지는지 살펴보았다. 카페의 원두 재고를 정수로 저장하고 주문 메뉴를 문자로 표현할 수 있게 되었다. 이제 그 값을 보고 실행할 코드를 선택해야 한다. 재고가 없는 메뉴는 판매하지 않아야 하고, 주문이 여러 건이면 같은 확인 절차를 주문마다 수행해야 한다.

이 장에서는 조건에 따라 실행 경로를 나누고, 정해진 작업을 반복하는 프로그램을 만든다. 예제는 미리 정한 주문을 순서대로 처리하는 카페 콘솔 앱이다. 입력을 받지 않으므로 실행할 때마다 같은 결과가 나오며, 조건과 반복이 재고와 매출을 어떻게 바꾸는지 직접 추적할 수 있다.

  • if와 else로 실행할 작업을 구분한다.
  • 패턴 매칭(pattern matching)을 사용하는 switch 식으로 메뉴 가격과 재고 상태를 결정한다.
  • 반복 횟수가 중요한 작업과 각 요소를 처리하는 작업을 구분하여 for와 foreach를 선택한다.
  • continue와 break의 영향을 구분하고, 실패한 주문이 재고와 매출을 바꾸지 않도록 작성한다.

문제 상황

동네 카페에서 주문 처리 흐름을 시험하려고 한다. 판매 메뉴는 아메리카노, 라테, 차다. 아메리카노에는 원두 한 단위, 라테에는 원두와 우유가 각각 한 단위, 차에는 티백 한 개가 필요하다. 여기서 원두 한 단위는 음료 한 잔을 만들 수 있도록 나눈 분량이다. 무게나 추출량 계산은 하지 않는다.

시작 재고는 원두 5, 우유 2, 티백 0이다. 가격은 아메리카노 3,000원, 라테 4,000원, 차 2,500원이다. 티백은 이미 떨어졌지만 다른 음료는 판매할 수 있다. 차 주문이 들어왔다고 해서 이후 주문까지 중단해서는 안 된다.

시험 주문은 문자열 "AALTAX"로 정한다. 문자 'A'는 아메리카노, 'L'은 라테, 'T'는 차를 뜻한다. 마지막의 'X'는 등록되지 않은 메뉴가 섞인 상황을 확인하기 위해 넣었다. 문자열을 주문 저장소로 쓰는 방식은 이번 예제를 작게 유지하기 위한 선택이다. 수량과 요청 사항까지 표현하는 실제 주문 구조를 뜻하지는 않는다.

프로그램은 영업 준비 점검을 세 번 출력한 뒤 주문을 처리한다. 메뉴가 등록되어 있는지 먼저 확인하고, 필요한 재고가 충분한지 다음으로 확인한다. 두 조건을 통과한 주문만 재고를 차감하고 매출에 더한다. 마지막에는 판매 수, 거절 수, 남은 재고, 매출을 출력한다.

if로 실행 경로 나누기

if의 괄호에는 결과가 bool인 식을 쓴다. 식이 true이면 바로 뒤의 블록을 실행하고, false이면 건너뛴다. C#에서는 정수 0이나 빈 문자열을 조건 자리에 그대로 쓸 수 없다. 재고가 있는지 알고 싶다면 coffeeStock > 0처럼 비교해야 한다.

int coffeeStock = 2;

if (coffeeStock > 0)
{
    Console.WriteLine("아메리카노 판매 가능");
}
else
{
    Console.WriteLine("원두 보충 필요");
}

else는 앞의 조건이 거짓일 때 실행할 작업이다. 두 블록 중 하나만 실행된다. 여러 조건을 차례로 검사할 때는 else if를 이어 쓸 수 있다. 이 경우 처음 참이 된 조건의 블록을 실행한 뒤 나머지 분기를 건너뛴다. 반면 독립된 if를 여러 개 쓰면 각 조건을 따로 검사하므로 여러 블록이 실행될 수 있다.

라테는 원두와 우유가 모두 있어야 한다. 따라서 조건은 coffeeStock > 0 && milkStock > 0이다. &&는 양쪽 조건이 모두 참일 때 참이고, ||는 하나 이상이 참이면 참이다. 두 연산자는 왼쪽 결과만으로 답이 정해지면 오른쪽 식을 평가하지 않는 단락 평가(short-circuit evaluation)를 한다. !는 참과 거짓을 뒤집는다.

조건식은 업무 규칙을 그대로 읽을 수 있어야 한다. “원두가 있고 우유도 있다”는 규칙에 &&를 쓰면 된다. 조건이 길어질 때는 괄호로 묶어 의도를 드러낸다. &&가 ||보다 먼저 계산된다는 규칙에만 기대면 코드를 읽는 사람이 조건을 잘못 해석하기 쉽다.

주문을 거절한 뒤에는 다음 주문으로 넘어가야 한다. 반복문 안의 continue는 현재 반복에 남아 있는 코드를 건너뛴다. 다음 예제의 출력문에는 등록된 메뉴만 도달한다.

foreach (char menu in "AXL")
{
    if (menu == 'X')
    {
        Console.WriteLine("등록되지 않은 메뉴");
        continue;
    }

    Console.WriteLine($"처리할 메뉴: {menu}");
}

이처럼 처리할 수 없는 경우를 먼저 제외하면, 정상 처리 부분을 여러 겹의 블록 안에 넣지 않아도 된다. 다만 continue 앞에서 무엇을 바꿨는지 확인해야 한다. 거절 수는 늘려도 되지만, 판매 수나 매출을 먼저 늘리면 실제로 판매하지 않은 주문이 집계된다.

재고 검사에 통과한 주문만 재고 차감과 매출 반영 단계로 진행한다

이 그림의 거절 경로와 판매 경로는 모두 다음 주문으로 이어진다. 차이는 현재 주문에 대해 어떤 상태를 바꾸느냐에 있다. 조건문을 작성할 때는 출력 문구만 확인하지 말고 각 경로에서 바뀌는 변수도 함께 확인해야 한다.

switch 식으로 값을 결정하기

switch 식은 입력값을 여러 패턴과 비교하여 결과값 하나를 선택한다. 메뉴 코드로 가격을 정하는 작업에 적합하다. 작업 여러 개를 실행하는 if 블록과 달리, 여기서는 “이 입력에 대응하는 값은 무엇인가”에 집중한다.

char menu = 'L';

int price = menu switch
{
    'A' => 3000,
    'L' => 4000,
    'T' => 2500,
    _ => 0
};

Console.WriteLine(price);

입력값 뒤에 switch를 쓰고 중괄호 안에 선택 항목을 나열한다. 각 항목은 패턴 => 결과식 형태이며 쉼표로 구분한다. 마지막 항목 뒤에도 쉼표를 붙일 수 있다. 식을 변수에 대입하는 문장이므로 닫는 중괄호 뒤에는 세미콜론이 필요하다.

'A' 같은 상수 패턴은 값이 같은지 확인한다. _는 어떤 값에도 맞는 버림 패턴이다. 위 코드는 알려진 세 메뉴에 해당하지 않으면 0을 선택한다. 이 예제에서는 모든 판매 메뉴의 가격이 양수이므로 0을 미등록 메뉴의 표시로 사용할 수 있다. 무료 메뉴를 추가한다면 가격 0과 미등록 상태를 구분하는 규칙부터 바꿔야 한다.

패턴은 숫자의 범위도 표현한다. 원두 재고가 0이면 품절, 1이나 2이면 보충 필요, 그보다 많으면 여유로 표시할 수 있다. 이 프로그램은 재고가 음수가 되지 않도록 주문을 검사하므로 다음 구분이 성립한다.

int coffeeStock = 1;

string stockState = coffeeStock switch
{
    0 => "품절",
    <= 2 => "보충 필요",
    _ => "여유"
};

Console.WriteLine(stockState);

<= 2는 관계 패턴이다. 입력값이 2 이하인지 비교한다. switch 식은 위에서부터 확인하여 처음 맞는 항목의 값을 선택한다. 따라서 재고 0은 첫 항목에서 처리되고, 두 번째 항목에는 도달하지 않는다. 외부에서 받은 수처럼 음수도 들어올 수 있는 값이라면 < 0 => "재고 확인 필요" 같은 항목을 먼저 추가해야 한다.

switch 식은 위에서부터 패턴을 확인하고 처음 일치한 항목의 결과를 선택한다

논리 패턴을 사용하면 패턴끼리 결합할 수도 있다. 'A' or 'L'은 두 메뉴 중 하나에 맞는 패턴이며, >= 1 and <= 2는 1 이상 2 이하에 맞는 패턴이다. 일반적인 참·거짓 식을 연결할 때 쓰는 &&, ||와 패턴을 연결하는 and, or는 쓰는 위치가 다르다.

주문 처리에서 사용하는 패턴과 추가 조건
표현확인하는 내용사용 예
상수 패턴입력값이 특정 값과 같은가'A' => 3000
관계 패턴입력값이 비교 범위에 속하는가<= 2 => "보충 필요"
논리 패턴여러 패턴의 조건을 만족하는가'A' or 'L' => true
when 조건패턴이 맞고 추가 조건도 참인가'A' when coffeeStock > 0 => true

when 뒤에는 bool 식을 쓴다. 메뉴가 아메리카노인지 확인한 다음 원두 재고까지 확인하는 식이다. 패턴이 맞더라도 when 조건이 거짓이면 다음 항목을 계속 확인한다. 완성 코드에서는 메뉴와 현재 재고를 함께 살펴 판매 가능 여부를 정한다.

switch 식으로 얻는 것은 선택된 결과값이다. 재고 차감과 여러 출력처럼 순서대로 실행할 작업은 뒤의 if 블록에 둔다. 이렇게 값을 결정하는 부분과 상태를 바꾸는 부분을 나누면, 판매가 불가능한데 재고만 줄어드는 실수를 찾기 쉽다.

for와 foreach를 고르는 기준

for는 반복을 제어하는 값을 직접 관리한다. 괄호 안에는 초기화, 계속할 조건, 반복 뒤에 수행할 식을 세미콜론으로 나누어 쓴다. 준비 점검을 세 번 출력하는 코드는 다음과 같다.

for (int check = 1; check <= 3; check++)
{
    Console.WriteLine($"준비 점검 {check}/3");
}

먼저 check를 1로 초기화한다. 조건을 검사해서 참이면 본문을 실행하고, 본문이 끝나면 check++로 값을 1 늘린다. 다시 조건을 검사하여 이 과정을 반복한다. 3을 출력한 뒤 값이 4가 되면 조건이 거짓이므로 종료한다. 첫 조건 검사부터 거짓이면 본문은 한 번도 실행되지 않는다.

foreach는 대상에서 요소를 하나씩 가져온다. 문자열을 대상으로 하면 각 반복에서 char 값을 하나씩 받는다. 이번 주문 코드는 모두 한 문자로 표현되는 영문자이므로 문자 하나를 주문 하나로 다룰 수 있다.

foreach (char menu in "AALTAX")
{
    Console.WriteLine(menu);
}

이 코드는 문자 여섯 개를 순서대로 출력한다. 반복을 위한 숫자를 늘리거나 끝 위치를 계산할 필요가 없다. 문자열의 각 char가 언제나 화면에 보이는 글자 하나와 같다는 뜻은 아니다. 여기서는 영문 메뉴 코드만 사용하므로 그런 차이를 고려할 필요가 없다.

두 반복문의 선택 기준은 단순히 짧게 쓸 수 있는가가 아니다. “세 번 실행한다”, “두 칸씩 이동한다”, “위치 번호가 필요하다”는 요구에는 for가 자연스럽다. “모든 주문에 같은 작업을 한다”는 요구에는 foreach가 자연스럽다. 주문 번호가 필요하더라도 별도의 집계 변수만으로 충분하다면 foreach를 그대로 사용할 수 있다.

continue를 만나면 현재 반복의 남은 부분을 건너뛴다. for에서는 반복 뒤의 식을 수행한 뒤 조건을 다시 검사하고, foreach에서는 다음 요소로 진행한다. break는 자신을 감싸는 가장 가까운 반복문을 끝낸다. 이번 예제에서 티백이 없을 때 break를 쓰면 뒤에 있는 아메리카노 주문까지 처리하지 못한다.

변수의 선언 위치도 반복 결과에 영향을 준다. 판매 수처럼 전체 주문을 합산하는 변수는 반복문 밖에 선언한다. 가격처럼 주문마다 새로 계산할 변수는 본문 안에 선언한다. 판매 수를 본문 안에서 매번 0으로 초기화하면 누적 집계가 되지 않는다. 선언 위치는 그 값이 얼마나 오래 유지되어야 하는지를 드러낸다.

완성 코드

.NET 10 콘솔 프로젝트의 Program.cs를 다음 코드로 교체한다. 앞 장에서 만든 프로젝트를 사용해도 된다. 최상위 문으로 작성했으며 별도의 클래스나 메서드를 추가하지 않는다. 재고 확인, 차감, 집계의 순서를 눈으로 따라갈 수 있도록 한 파일에 모두 둔다.

using System;

string orders = "AALTAX";
int coffeeStock = 5;
int milkStock = 2;
int teaStock = 0;
int soldCount = 0;
int rejectedCount = 0;
int revenue = 0;
int orderNumber = 0;

for (int check = 1; check <= 3; check++)
{
    Console.WriteLine($"준비 점검 {check}/3");
}

foreach (char menu in orders)
{
    orderNumber++;

    int price = menu switch
    {
        'A' => 3000,
        'L' => 4000,
        'T' => 2500,
        _ => 0
    };

    if (price == 0)
    {
        rejectedCount++;
        Console.WriteLine($"{orderNumber}번 {menu}: 미등록 메뉴");
        continue;
    }

    bool canSell = menu switch
    {
        'A' when coffeeStock > 0 => true,
        'L' when coffeeStock > 0 && milkStock > 0 => true,
        'T' when teaStock > 0 => true,
        _ => false
    };

    if (!canSell)
    {
        rejectedCount++;
        Console.WriteLine($"{orderNumber}번 {menu}: 재고 부족");
        continue;
    }

    if (menu == 'A' || menu == 'L')
    {
        coffeeStock--;
    }

    if (menu == 'L')
    {
        milkStock--;
    }
    else if (menu == 'T')
    {
        teaStock--;
    }

    soldCount++;
    revenue += price;
    Console.WriteLine($"{orderNumber}번 {menu}: 판매 완료 {price}원");
}

string stockState = coffeeStock switch
{
    0 => "품절",
    <= 2 => "보충 필요",
    _ => "여유"
};

Console.WriteLine($"판매 {soldCount}건, 거절 {rejectedCount}건");
Console.WriteLine($"남은 재고: 원두 {coffeeStock}, 우유 {milkStock}, 티백 {teaStock}");
Console.WriteLine($"원두 상태: {stockState}");
Console.WriteLine($"매출: {revenue}원");

줄별 해설

빈 줄도 한 줄로 세면 완성 코드는 80줄이다. 아래 설명은 관련된 줄을 묶어 읽는다. 먼저 주문 한 건이 어디에서 제외되는지 확인하고, 그다음 판매 시 바뀌는 변수를 따라가면 된다.

  1. 1줄의 using System;은 Console을 이름만으로 사용할 수 있게 한다. 콘솔 템플릿의 암시적 using 설정이 꺼져 있어도 이 코드에 필요한 이름을 찾을 수 있다.
  2. 3~6줄은 주문 문자열과 시작 재고를 정한다. 주문 순서가 고정되어 있으며 실행 중 문자열은 바꾸지 않는다. 재고 변수는 성공한 주문이 나올 때마다 줄어든다.
  3. 7~10줄은 전체 집계와 주문 번호를 0으로 초기화한다. 네 변수 모두 반복문 밖에 있으므로 다음 주문에서도 직전 값을 이어 사용한다.
  4. 12~15줄은 준비 점검을 세 번 출력한다. check는 이 반복문에서만 필요하며 재고나 주문 수에는 영향을 주지 않는다.
  5. 17~19줄은 주문 문자를 하나 가져온 뒤 주문 번호를 늘린다. 거절 주문도 접수 순서에 포함해야 하므로 번호 증가는 검사보다 앞에 둔다.
  6. 21~27줄은 현재 메뉴의 가격을 결정한다. 각 결과식이 정수이므로 선택된 값을 int price에 대입할 수 있다. 마지막 항목이 미등록 문자를 받아 처리한다.
  7. 29~34줄은 미등록 주문을 거절한다. 거절 수를 늘리고 이유를 출력한 뒤 continue로 다음 주문에 진입한다. 따라서 미등록 메뉴는 뒤의 재고 검사에 도달하지 않는다.
  8. 36~42줄은 지금 재고로 판매할 수 있는지 판단한다. 이 식은 주문마다 다시 평가된다. 앞선 주문이 원두를 사용했다면 줄어든 원두 수를 기준으로 검사한다.
  9. 44~49줄은 판매 불가능한 주문을 제외한다. !canSell은 판매 가능 여부가 거짓일 때 참이다. 여기서도 재고와 매출을 바꾸기 전에 다음 주문으로 넘어간다.
  10. 51~54줄은 아메리카노와 라테의 공통 재료인 원두를 차감한다. 판매 가능 검사를 통과했으므로 해당 메뉴에 필요한 원두가 있다는 전제가 성립한다.
  11. 56~63줄은 라테라면 우유를, 차라면 티백을 차감한다. 원두 차감 조건과는 독립된 조건문이다. 라테는 앞의 조건문과 이 조건문을 모두 실행하여 두 재료를 각각 하나 줄인다.
  12. 65~68줄은 판매 수와 매출을 누적하고 판매 결과를 출력한다. revenue += price는 현재 매출에 이번 가격을 더해 다시 저장한다. 68줄에서 현재 주문의 처리가 끝난다.
  13. 70~75줄은 모든 주문 처리 후 남은 원두의 상태를 결정한다. 반복문 밖에 있으므로 최종 재고를 기준으로 한 번만 실행된다.
  14. 77~80줄은 전체 결과를 출력한다. 금액에 문화권별 구분 기호를 넣는 서식을 사용하지 않아 예제의 출력 숫자를 그대로 비교할 수 있다.

재고를 확인하는 조건과 차감하는 조건은 서로 맞아야 한다. 예를 들어 라테의 판매 조건에는 우유가 있는데 차감 코드에서 우유를 빠뜨리면, 프로그램은 실제보다 우유가 많이 남았다고 판단한다. 조건식 하나가 맞는지만 보지 말고 한 주문의 검사와 상태 변경을 한 쌍으로 읽어야 한다.

실행 결과

프로젝트가 아직 없다면 다음 명령으로 만든 뒤 생성된 Program.cs를 완성 코드로 교체한다.

dotnet new console --framework net10.0 --name CafeControl
cd CafeControl

프로젝트 폴더에서 실행한다.

dotnet run

프로그램의 예상 출력은 다음과 같다.

준비 점검 1/3
준비 점검 2/3
준비 점검 3/3
1번 A: 판매 완료 3000원
2번 A: 판매 완료 3000원
3번 L: 판매 완료 4000원
4번 T: 재고 부족
5번 A: 판매 완료 3000원
6번 X: 미등록 메뉴
판매 4건, 거절 2건
남은 재고: 원두 1, 우유 1, 티백 0
원두 상태: 보충 필요
매출: 13000원

아메리카노 세 잔과 라테 한 잔에 원두 네 단위를 사용한다. 라테가 한 잔이므로 우유는 하나만 사용한다. 차는 재고 검사에서 거절되어 티백 수가 그대로 0이다. 미등록 메뉴도 재고에 영향을 주지 않는다. 판매 4건과 거절 2건을 더하면 입력 주문 수 6건과 같다.

각 주문을 처리한 직후의 재고와 누적 매출
주문처리 결과원두 / 우유 / 티백누적 매출
1번 A판매4 / 2 / 03000원
2번 A판매3 / 2 / 06000원
3번 L판매2 / 1 / 010000원
4번 T거절2 / 1 / 010000원
5번 A판매1 / 1 / 013000원
6번 X거절1 / 1 / 013000원

실무에서 자주 틀리는 것

아래의 틀린 코드는 오류를 설명하기 위한 부분 코드다. 완성 코드에 그대로 추가하지 않는다. 고친 코드 역시 해당 부분을 바꾸는 용도로 읽는다.

if 바로 뒤에 세미콜론 붙이기

조건식 뒤의 세미콜론은 아무 작업도 하지 않는 문장이 된다. 다음 중괄호 블록은 조건과 분리되어 매번 실행된다. 이런 모양은 컴파일러 경고의 대상이기도 하므로 경고를 무시하지 않아야 한다.

틀린 코드다.

if (coffeeStock > 0);
{
    coffeeStock--;
}

고친 코드다.

if (coffeeStock > 0)
{
    coffeeStock--;
}

조건문과 반복문에서는 중괄호를 일관되게 사용한다. 본문이 한 줄일 때도 중괄호를 써 두면 나중에 출력이나 집계 코드를 추가할 위치가 분명해진다.

필수 재료를 OR 조건으로 검사하기

원두와 우유가 모두 필요한데 ||를 쓰면 한 재료만 있어도 라테를 판매할 수 있다고 판단한다. 시작 재고만으로는 이 오류가 드러나지 않을 수 있다. 원두는 있고 우유는 없는 경우를 대입해 조건을 읽어 보면 문제가 보인다.

틀린 코드다.

bool canSellLatte = coffeeStock > 0 || milkStock > 0;

고친 코드다.

bool canSellLatte = coffeeStock > 0 && milkStock > 0;

두 재료가 모두 있는 경우, 하나만 있는 경우, 둘 다 없는 경우를 나누어 확인한다. 조건식 검토에는 정상 재고 하나만 대입하는 것보다 이런 경계 상황이 더 도움이 된다.

넓은 패턴을 먼저 배치하기

<= 2는 0도 포함한다. 이를 먼저 쓰면 뒤의 0 항목이 선택될 수 없다. 다음 코드는 도달할 수 없는 패턴 때문에 컴파일 오류가 난다.

틀린 코드다.

string stockState = coffeeStock switch
{
    <= 2 => "보충 필요",
    0 => "품절",
    _ => "여유"
};

고친 코드다.

string stockState = coffeeStock switch
{
    0 => "품절",
    <= 2 => "보충 필요",
    _ => "여유"
};

겹치는 범위에서는 먼저 다뤄야 하는 구체적인 경우를 위에 둔다. 또한 _를 생략하면 입력을 빠뜨릴 수 있다. 모든 경우를 포괄하지 않는 식은 경고가 발생할 수 있고, 맞는 항목이 없는 입력을 실제로 만나면 실행도 정상적으로 이어지지 않는다.

마지막 위치를 문자열 길이와 같게 잡기

문자열의 위치 번호는 0부터 시작한다. 길이가 6이면 유효한 위치는 0부터 5까지다. 대괄호로 위치의 문자를 읽을 때 길이 자체를 위치로 사용하면 범위를 벗어난다.

틀린 코드다.

for (int i = 0; i <= orders.Length; i++)
{
    Console.WriteLine(orders[i]);
}

고친 코드다.

for (int i = 0; i < orders.Length; i++)
{
    Console.WriteLine(orders[i]);
}

위치가 필요하지 않다면 완성 코드처럼 foreach를 쓰면 된다. 준비 점검의 1부터 3까지 세기와 문자열의 0부터 길이 미만까지 접근하기는 시작점과 끝 조건이 다르다. 반복문을 복사할 때는 두 기준을 함께 확인한다.

한눈에 보기

조건과 반복을 선택할 때 확인할 기준
구문적합한 요구이번 프로그램의 역할확인할 점
if / else조건에 따라 작업 실행주문 거절과 재고 차감각 경로에서 바꾸는 변수
switch 식입력에 대응하는 값 선택가격과 판매 가능 여부 결정패턴 순서와 전체 입력 처리
for횟수나 위치를 직접 제어준비 점검 세 번 실행초기값, 끝 조건, 증감
foreach요소마다 같은 작업 수행문자열의 주문 순서대로 처리전체 집계 변수의 선언 위치
continue현재 반복의 나머지 생략거절 후 다음 주문으로 진행생략되는 차감과 집계
break가장 가까운 반복문 종료현재 요구에서는 사용하지 않음남은 주문도 중단되는지 여부

현재 코드는 메뉴를 확인하고, 판매 가능 여부를 판단하고, 재고를 차감하는 순서로 읽힌다. 다음에는 이처럼 목적이 분명한 코드 조각에 이름을 붙이는 메서드를 다룬다. 그 전에 각 조건이 무엇을 보장하는지, 반복 한 번이 어떤 작업 단위인지 설명할 수 있어야 한다.

연습 문제

  1. 완성 코드의 시작 원두 재고만 2로 바꾼다. 판매 수, 거절 수, 남은 재고, 매출을 먼저 계산한 뒤 실행 결과와 비교한다. 어떤 주문이 원두를 사용했는지도 설명한다.
  2. 메뉴와 시작 재고는 그대로 두고, 원두 상태를 0이면 “품절”, 1~2이면 “보충 필요”, 3~4이면 “적정”, 5 이상이면 “여유”로 표시하도록 마지막 switch 식을 바꾼다. 재고는 음수가 아니라고 가정한다.
  3. 준비 점검 부분을 3, 2, 1 순서로 출력하는 for로 바꾼다. 출력 형식은 “준비까지 3”, “준비까지 2”, “준비까지 1”로 한다. 0은 출력하지 않는다.
  4. 완성 코드의 재고 부족 처리 블록에서 continue 하나만 break로 바꾸면 최종 집계가 어떻게 달라지는지 설명한다. 미등록 메뉴 처리 블록은 바꾸지 않는다.

정답과 해설

1. 원두가 두 단위일 때

첫 번째와 두 번째 아메리카노가 원두를 모두 사용한다. 세 번째 라테는 우유가 남아 있어도 원두가 없어 거절된다. 네 번째 차는 티백이 없어 거절되고, 다섯 번째 아메리카노도 원두가 없어 거절된다. 마지막 미등록 메뉴까지 합하면 판매 2건, 거절 4건이다.

판매 2건, 거절 4건
남은 재고: 원두 0, 우유 2, 티백 0
원두 상태: 품절
매출: 6000원

라테 주문이 우유를 줄이지 않았다는 점을 확인한다. 재고 검사를 통과하지 못한 주문은 모든 재료 차감 코드를 건너뛰기 때문이다.

2. 재고 상태를 네 구간으로 나누기

string stockState = coffeeStock switch
{
    0 => "품절",
    >= 1 and <= 2 => "보충 필요",
    >= 3 and <= 4 => "적정",
    _ => "여유"
};

and는 두 관계 패턴을 모두 만족하도록 묶는다. 음수가 없다는 가정 아래 마지막 항목은 5 이상을 담당한다. 앞의 패턴이 작은 구간을 이미 처리하므로 두 범위를 각각 <= 2, <= 4로 줄여 써도 같은 결과가 나온다. 여기서는 구간 경계를 직접 드러냈다.

3. 감소하는 준비 점검

for (int remaining = 3; remaining > 0; remaining--)
{
    Console.WriteLine($"준비까지 {remaining}");
}

시작값은 3이고 반복 뒤마다 1씩 줄인다. 1을 출력한 뒤 값이 0이 되면 조건이 거짓이므로 끝난다. 감소하는 반복에서는 증감식만 바꾸는 것으로 충분하지 않다. 조건이 어느 방향의 값을 허용하는지도 함께 바꿔야 한다.

4. 재고 부족에서 반복을 끝내면

첫 두 아메리카노와 라테는 판매된다. 네 번째 차 주문에서 거절 수를 늘리고 재고 부족을 출력한 뒤 주문 반복문을 끝낸다. 다섯 번째 아메리카노와 여섯 번째 미등록 메뉴는 검사하지 않는다. 반복문 뒤의 요약 출력은 그대로 실행된다.

판매 3건, 거절 1건
남은 재고: 원두 2, 우유 1, 티백 0
원두 상태: 보충 필요
매출: 10000원

판매와 거절을 더한 값은 4다. 입력 주문은 6개지만 두 주문이 처리되지 않았다. 특정 주문을 거절하는 규칙과 전체 접수를 중단하는 규칙은 다르므로, 이번 요구에는 continue가 맞다.

오탈자·오류 제보 비공개로 접수되어 원고 수정에 반영됩니다

이메일 등 개인정보는 받지 않습니다. 답변이 필요한 질문은 아래 댓글을 이용해 주세요.

READER FEEDBACK

질문·의견

내용에 관한 질문이나 더 나은 설명을 위한 의견을 남겨 주세요. 오탈자는 위의 제보 양식이 더 빨리 반영됩니다. 이 댓글은 원래 게시글과 같은 자리에 쌓입니다.

댓글 0

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

댓글을 남기려면 로그인이 필요합니다.