-
05. Selection Statements프로그래밍 언어/C 2020. 3. 21. 00:37
05. Selection Statements
C에는 많은 연산자들이 있지만 구문은 상대적으로 적다. 우리는
return
구문과 표현식 구문에 대해 다뤄봤었다. 나머지 구문은 각각 어떤 영향을 주는지에 따라 세 가지로 분류할 수 있다.- Selection statements.
if
,switch
구문은 프로그램이 진행할 수 있는 여러 경로 (a particular execution part from a set of alternatives) 중 선택할 수 있게 해 준다.
- Iteration statements.
while
,do
,for
구문은 반복 행동(iteration)을 도와준다.
- Jump statements.
return
,break
,continue
,goto
구문은 프로그램의 특정 위치로 절대적인 이동(unconditional jump)을 하게 해 준다.
몇 가지 다른 구문도 있는데, 여러 구문들을 하나의 구문로 만들어 주는 compound statements와 아무것도 하지 않는 null statements이다.
선택문에 대해 알아보기 전에 선택문의 조건으로 사용될 논리 표현식에 대해 알아볼 것이다.
01. Logical Expressions
프로그래밍을 하다 보면 종종
if
문을 포함한 여러 가지 구문에서 변수의 값이 조건에 맞는지 아닌지 확인할 일이 생긴다. 예를 들어if
구문에서i < j
를 테스트하고 참이라면i
는j
보다 작다는 걸 의미하고 그에 따른 행동을 할 수 있다. 많은 프로그래밍 언어에서i < j
와 같은 표현식은Boolean
형식 또는logical
형식 같은 _false_ 와 _true_만 저장할 수 있는 형식을 사용하지만, C에서는 _false_면0
이고, _true_면1
인int
형 값을 만들어낸다. 이 내용을 기억하면서 논리 표현식을 만드는 연산자에 대해 알아보자.Relational Operators
Symbol Meaning <
less than >
greater than <=
less than or equal to >=
greater than or equal to 위 표의 관계 연산자(relational operators)는 참이면
1
을 거짓이면0
을 표현하는 표현식을 만든다. 예를 들어10 < 11
은1
이고11 < 10
은0
이다. 관계 연산자는 자료형에 관계없이 사용할 수 있고 따라서 실수와 정수 자료형의 비교가 가능하다.printf("4 < 4.2 = %d\n", 4 < 4.2); /* print "4 < 4.2 = 1" */ printf("4.2 < 2 = %d\n", 4.2 < 2); /* print "4.2 < 2 = 0" */
또한 관계 연산자의 우선순위는 계산 연산자의 우선순위보다 낮고 left associative 속성을 갖고 있다. 따라서
i + j < k - 1
은(i + j) < (k - 1)
과 같다. 또한i < j < k
같은 표현도 허용은 되지만 생각과는 다르게 작동한다.<
가 left associative 속성을 갖고 있으므로(i < j) < k
이고i < j
가0
또는1
의 값을 반환하므로 그 값과k
를 비교하게 된다. 따라서j
가i
와k
사이의 값이라는 표현을 하고 싶다면 다음과 같이 하는 게 바람직하다.i < j && j < k
Equality Operators
Symbol Meaning ==
equal to !=
not equal to 관계 연산자처럼 동등 연산자도 left associative이고 참이면
1
거짓이면0
을 결과로 만들어낸다. 그러나 동등 연산자는 관계 연산자보다 낮은 우선순위를 갖고 있어서i < j == j < k
는(i < j) == (j < k)
로 해석된다. 앞의 표현식은i < j < k
이거나i > j > k
면 참이고 아니면 거짓이다.영리한 프로그래머들은 관계 연산자와 동등 연산자가 정수를 만들어낸다는 사실을 활용하기도 한다. 예를 들어
(i >= j) + (i == j)
는i
가j
보다 작은지, 큰지, 같은지에 따라0
,1
,2
를 반환한다. 하지만 이런 창의적인 코딩(tricky coding)은 이해하기 어렵게 만들어서 별로 좋은 생각은 아니다.Logical Operators
Symbol Meaning !
logical negation &&
logical and ` 논리 연산자를 이용하면 더 복잡한 논리 표현식을 만들 수 있다.
!
연산자는 unary 연산자고&&
연산자와||
연산자는 binary 연산자다. 논리 연산자도 결과로0
과1
을 만들어낸다.논리 연산자의 피연산자는
0
또는1
이 오는데 필수적인 건 아니다. 논리 연산자는0
이 아닌 값을 참으로,0
을 거짓으로 처리한다.!
연산자는 피연산자가0
이면1
의 값을 갖는다.&&
연산자는 피연산자 둘이0
이 아닌 값이면1
의 값을 갖는다.||
연산자는 피연산자 둘 중 하나가0
이 아닌 값이거나 둘 다0
이 아닌 값이면1
의 값을 갖는다. 다른 경우에는 모두0
을 반환한다.&&
연산자와||
연산자는 피연산자에 대해 "short-circuit" 평가를 수행하는데, 연산자가 왼쪽 피연산자를 연산한 후 오른쪽 피연산자를 연산한다는 뜻이다.(i != 0) && (j / i > 0)
위 표현식의 값을 연산하기 위해 우린 먼저
i != 0
을 연산한다. 그 후i
가0
이 아니라면j / i > 0
을 연산하게 된다. 그러나i
가0
이라면 위 표현식은 무조건0
이므로 더 이상의 연산은 필요가 없게 된다. "short-circuit"의 장점은 확실함에 있다. "short-circuit" 규칙이 있어서 위 표현식은0
으로 나누는 연산을 하지 않는다.
side effect가 있는 표현식을 논리 표현식에 사용할 때는 항상 주의해야 한다. short-circuit 현상 때문에 피연산자의 side effect는 항상 발생하는 것이 아니다.
i > 0 && ++j > 0
위 표현식에서
j
는 항상 증가되는 게 아니다. 만약i
가0
보다 작거나 같다면 표현식++j > 0
은 연산되지 않고 따라서 표현식의 side effect인j = j + 1
은 발생하지 않는다. 위 표현식을 연산할 때마다j
가 증가되게 하려면 피연산자의 순서를 바꿔++j > 0 && i > 0
으로 표현하거나 더 좋은 방법은j
의 증가를 독립적으로 표현하는 것이다.!
의 우선순위는 unary plus, unary minus와 같고&&
과||
는 관계 연산자와 동등 연산자보다 낮다. 따라서i < j && k == m
은(i < j) && (k == m)
과 같다.!
연산자는 right associative고&&
,||
는 left associative다.
02. The
if
Statementif
문은 표현식의 값에 따라 두 프로그램 진행 선택지 중 선택할 수 있는 구문이다.if
는 다음 형식을 갖는다.if ( expression ) stetement
표현식을 괄호로 감싸야한다는 것에 주의해라. 괄호는 표현식이 아니라
if
문의 일부다. 그리고 몇몇 언어와는 다르게 괄호 뒤에then
도 오지 않는다.if
문이 실행되면, 괄호 안의 표현식이 연산되고 표현식이 참이라면 (0
이 아닌 값이라면) 괄호 뒤 구문가 실행된다. 아래는 그 예시다.if (line_num == MAX_LINES) line_num = 0;
구문인
line_num=0
은line_num == MAX_LINES
가 참일 때 동작한다.Compound Statements
if
문 형식에서 구문은 복수가 아닌 단수다. 하지만 만약 복수의 구문을 사용하려면 어떻게 해야 할까? compound statements가 그 답이 될 수 있다. 복합 연산자는 다음 형식을 갖고 있다.{ statements }
중괄호로 구문을 두르면 컴파일러에게 하나의 구문인 것처럼 해석하게 만들 수 있다.
if (line_num < MAX_LINES) { line_num += 1; page_num += 1; }
The
else
Clauseif
문은else
를 가질 수 있다.else
다음의 구문은if
문의 표현식이0
이면 실행된다.if ( expression ) statement else statement
if
문에else
가 사용되면,else
가 어디에 배치되어야 하는지에 대한 레이아웃 이슈가 생긴다. 많은 C 프로그래머들은else
를if
과 같이 정렬시킨다. 그 안의 구문은 보통 들여 쓰기가 된다.if (i > j) max = i; else max = j;
하지만 구문가 아주 짧다면
if
,else
와 같은 줄에 쓰기도 한다.if (i > j) max = i; else max = j;
if
문 안에 어떤 구문가 오든지 제한은 없고, 종종if
문 안에 다시if
문이 오기도 한다. 다음은i
,j
,k
중 가장 큰 값을max
에 저장하는 프로그램이다.if (i > j) if (i > k) max = i; else max = k; else if (j > k) max = j; else max = k;
if
문은 몇 개든지 중첩될 수 있다. 그때else
를if
와 정렬시키는 게 보기 더 편할 것이다. 하지만 여전히 보기 어렵고 헷갈린다면 중괄호를 치는 걸 주저하면 안 된다.if (i > j) { if (i > k) max = i; else max = k; } else { if (j > k) max = j; else max = k; }
필요하지 않아도 중괄호를 치는 건 표현식에서 필요하지 않은 괄호를 치는 것과 같다. 프로그램을 읽기 쉽게 해 주고 우리 생각과 다른 컴파일러의 해석을 피할 수 있다. 몇몇 프로그래머들은
if
문에서 가능한 많은 중괄호를 사용한다. 모든if
문과else
에 중괄호를 사용하는 프로그래머는 다음과 같이 작성한다.if (i > j) { if (i > k) { max = i; } else { max = k; } } else { if (j > k) { max = j; } else { max = k; } }
이런 형식은 두 가지 장점이 있다. 첫째,
if
와else
에 구문을 추가할 때 중괄호를 만들 필요가 없으므로 수정하기 편하다. 둘째, 중괄호를 사용하지 않아서 발생하는 오류들을 예방할 수 있다.Cascaded
if
Statements프로그래밍을 하다 보면 종종 여러 묶음의 조건을 체크하고 가장 먼저 참인 조건에서 멈춰야 하는 경우가 있다. 이때 중첩되는 (cascaded)
if
문이 이런 조건을 체크하기 가장 좋은 방법이 될 수도 있다. 다음 예제는n
이0
보다 큰지0
과 같은지0
보다 작은지 확인한다.if (n < 0) printf("n is less than 0\n"); else if (n == 0) printf("n is equal to 0\n"); else printf("n is greater than 0\n");
두 번째
if
문은 첫 번째 구문의 안에 있지만 C 프로그래머들은 보통 두 번째if
문을 들여 쓰기 하지 않는다. 대신 각각의 원래if
문의else
와 같은 줄에 정렬한다.if (n < 0) printf("n is less than 0\n"); else if (n == 0) printf("n is equal to 0\n"); else printf("n is greater than 0\n");
이런 배열은 중첩되는
if
문을 다음과 같이 표현할 수 있다.if (expression) statement else if (expression) statement ... else if (expression) statement else statement
마지막
else
는 당연히 없어도 된다. 이런 형식은 테스트 경우의 수가 많을 때 발생하는 문제를 피할 수 있다. 게다가 읽는 사람이 더 쉽게 이해할 수 있기도 하다. 중첩되는if
문을 특별한 것이라고 생각하지 말고 평범한if
문의else
안에 들어있는if
라고 생각하자.Program: Calculating a Broker's Commission
중개인을 통해 주식을 매각하거나 매입할 때 중개수수료는 거래된 주식의 가격에 따라 달라지는 기준을 사용하여 계산하는 경우가 많다. 중개상이 다음 표에 따라 중개수수료를 받는다고 하자.
Transaction size Commission rate ~ $2,500 $30 + 1.7% $2,500 ~ $6,250 $56 + 0.66% $6,250 ~ $20,000 $76 + 0.34% $20,000 ~ $50,000 $100 + 0.22% $50,000 ~ $500,000 $155 + 0.11% $500,000 ~ $255 + 0.09% 최소 수수료는 39달러라고 하자. 우리는 거래되는 주식의 가격을 입력받으면 수수료를 출력하는 프로그램을 작성할 것이다.
Enter value of trade:
30000Commission: $166.00
프로그램의 핵심은 중첩된
if
문을 사용해 수수료 비율을 구하는 것이다./* Calculates a broker's commission */ #include <stdio.h> int main(void) { float commission, value; printf("Enter value of trade: "); scanf("%f", &value); if (value < 2500.00f) commission = 30.00f + 0.01f * 1.7f * value; else if (value < 6250.00f) commission = 56.00f + 0.01f * 0.66f * value; else if (value < 20000.00f) commission = 76.00f + 0.01f * 0.34f * value; else if (value < 50000.00f) commission = 100.00f + 0.01f * 0.22f * value; else if (value < 500000.00f) commission = 155.00f + 0.01f * 0.11f * value; else commission = 255.00f + 0.01f * 0.09f * value; if (commission < 39.00f) commission = 39.00f; printf("Commission: $%.2f\n", commission); return (0); }
The "Dangling
else
" Problemif
문이 중첩될 때, 우리는 악명 높은 "danglingelse
"문제를 마주칠 수 있다. 다음을 고려해보자.if (y != 0) if (x != 0) result = x / y; else printf("Error: y is equal to 0\n");
마지막
else
는 어떤if
에 달릴까? 들여 쓰기는 바깥쪽if
에 달린다고 말하지만 C는else
와 묶이지 않은 가장 가까운if
와 묶이기 때문에 안쪽if
와 묶이게 된다. 따라서 들여 쓰기를 다음과 같이 바꿔야 한다.if (y != 0) if (x != 0) result = x / y; else printf("Error: y is equal to 0\n");
바깥쪽
if
와 묶으려면 다음과 같이 중괄호로 묶어야 한다.if (y != 0) { if (x != 0) result = x / y; } else printf("Error: y is equal to 0\n");
Conditional Expressions
C는 두 구문 중 한 구문을 실행할 수 있는
if
문을 제공한다. C는 또한 조건에 따라 두 값 중 한 값을 만드는 표현식을 만들 수 있는 연산자를 제공한다.conditional operator은
?
과:
을 다음과 같이 사용하여 쓸 수 있다.expr1 ? expr2 : expr3
expr1
,expr2
,expr3
는 아무 형식의 표현식이 올 수 있다. 이 표현식은 conditional operator이라고 불리지만 C의 연산자 중 유일하게 피연산자를 3개 받으므로 삼항 연산자라고 불리기도 한다.삼항 표현식
expr1 ? expr2 : expr3
은 "만약 expr1이 참이라면 expr2이고 아니면 expr3이다."으로 해석된다. 연산되는 순서는expr1
이 먼저 연산되고 참이면expr2
아니면expr3
가 연산된다. 삼항 연산자의 우선순위는 대입 연산자를 제외하면 모든 연산자 중 가장 낮다.삼항 표현식은 프로그램을 짧게 만들지만 이해하기 어렵게 만든다. 그래서 피하는 게 가장 좋지만, 시도하면 좋은 상황이 종종 있다. 그중 하나는
return
문이다.if (i > j) return i; else return j;
위와 같이 쓰는 것보단 다음과 같이 쓰는 게 좋다.
return i > j ? i : j;
printf
에서도 비슷하게 사용할 수 있다.if (i > j) printf("%d\n", i) else printf("%d\n", j);
printf("%d\n", i > j ? i : j)
Boolean Values in C89
지난 수년간 C는 Boolean 형식을 지원하지 않았고 C89 표준에도 정의되어있지 않았다. 참 또는 거짓을 저장해야 하는 경우가 많은 상황에 Boolean 형식 지원의 부재는 프로그래밍을 살짝 귀찮게 만들었다. Boolean 형식을 지원하지 않으므로 프로그래머들은
int
형 변수에0
과1
을 넣어 사용했다.int flag; flag = 0; ... flag = 1;
이런 방식은 잘 작동하지만, 프로그램을 읽기 어렵게 만들었다.
int
형 변수에 값을 대입하므로 Boolean 자료형인지 아닌지 알기 어려웠고0
과1
이 참, 거짓 중 무엇을 나타내는지도 명확히 읽히지 않는다.프로그램을 더 이해하기 쉽게 만들기 위해 C 프로그래머들은 종종 다음과 같이 매크로를 정의하기도 했다.
#define BOOL int #define TRUE 1 #define FALSE 0
그러면 더 자연스럽게 읽을 수 있는 프로그램이 된다.
BOOL flag; flag = FALSE; ... flag = TRUE;
flag
가 참인지를 테스트하기 위해 다음과 같이 작성할 수 있다.if (flag == TRUE)
다음과 같이 작성할 수도 있다.
if (flag)
아래의 방법이 더 좋은데, 같은 의미로 작동하면서 더 간결하기 때문이다.
flag
가 거짓인지를 테스트하기 위해 다음과 같이 작성할 수 있다.if (flag == FALSE)
이것도 마찬가지로 다음과 같이 작성할 수도 있다.
if (!flag)
Boolean Values in C99
C는 C99부터 Boolean 형식을
_Bool
을 제공함으로써 지원하기 시작했다. 이 표준에서는 Boolean 형식 변수를 다음과 같이 선언한다._Bool flag;
_Bool
은int
형 변수(정확히unsigned int
형)고 단지 겉모습만 바꾼 것에 불과하다. 하지만 다른 정수형 변수와는 다르게_Bool
에는0
과1
밖에 저장하지 못했다.0
이 아닌 값을 대입하면 모두1
로 변환해 저장한다.flag = -42; /* flag is assigned 1 */ if (flag) ...
추가로 C99는 새로운 해더 파일인
stdbool.h
을 제공한다.stdbool.h
는 Boolean 형식 값을 더 편하게 사용할 수 있게 해 준다._Bool
을bool
로 사용할 수 있게 매크로 정의를 하고1
,0
을true
,false
로 정의하기도 했다.bool flag; flag = true; ... flag = false;
03. The
switch
Statement프로그래밍을 하다 보면 표현식을 한 시리즈에 맞춰야 하는 일이 종종 있다. Section 5.2 에서는 중첩
if
문을 사용했는데, 다음은 중첩if
문을 사용해 등급에 따른 문자열을 영어로 출력하는 프로그램이다.if (grade == 4) printf("Excellent"); else if (grade == 3) printf("Good"); else if (grade == 2) printf("Average"); else if (grade == 1) printf("Poor"); else if (grade == 0) printf("Failing"); else printf("Illegal grade");
이런 중첩
if
문의 대안으로 C에서는switch
문을 제공한다. 다음은 중첩if
문을switch
문으로 표현한 것이다.switch (grade) { case 4: printf("Excellent"); break; case 3: printf("Good"); break; case 2: printf("Average"); break; case 1: printf("Poor"); break; case 0: printf("Failing"); break; default:printf("Illegal grade"); break; }
이 구문이 실행되면
grade
가4
,3
,2
,1
,0
인지 비교한다. 예를 들어 만약4
라면Excellent
를 출력하고break
구문을 따라switch
문을 종료한다. 만약grade
가 아무것도 일치하지 않는다면Illegal grade
를 출력하고 종료한다.보통
switch
는 중첩if
문 보다 읽기 편하다. 게다가 특히 다뤄야 할 case가 많은 경우에switch
는 중첩if
문보다 빠르다. 대부분switch
는 다음과 같은 형식을 갖고 있다.switch (expression) { case constant-expression : statements ... case constant-expression : statements default : statements }
switch
문이 조금 복잡하지만 하나하나 살펴보자.- Controlling expression.
switch
다음에 나오는 표현식은 정수형 표현식만 올 수 있다.- 문자는 정수로 처리돼서 사용할 수 있지만, 실수나 문자열은 올 수 없다.
- Case labels.
- 각 case는 다음과 같은 형식의 label을 통해 시작한다.
case constant-expression :
- constant-expression은 보통의 표현식과 비슷하지만 변수나 함수를 포함할 수 없다. 예를 들어
4
,4 + 2
는 허용되지만n + 10
은 허용되지 않는다. - case labels에 속한 상수 표현식은 정수만 허용된다.
- Statements.
- case label 뒤에는 몇 줄의 구문이 와도 상관없다.
- 중괄호로 구문을 감쌀 필요도 없다.
- 구문의 마지막은 보통
break
문으로 마무리한다.
중복되는 case는 허용되지 않는다. 그리고 case의 순서는 상관없기 때문에
default
문이 가장 뒤에 올 필요도 없다.case
뒤에는 하나의 상수 표현식만 올 수 있지만 여러case
는 같은 구문을 공유할 수 있다.switch (grade) { case 4: case 3: case 2: case 1: printf("Passing"); break; case 0: printf("Failing"); break; default:printf("Illegal grade"); break; }
안타깝게도
switch
문에서는 값의 범위를 지정할 수는 없다.default
case는switch
문에 꼭 필요한 것은 아니다.switch
문에default
가 없고 어떤 case와도 값이 맞지 않는다면 control은 간단하게switch
다음 구문을 실행한다.The Role of the
break
Statement지금까지
break
문은 프로그램이switch
문을 벗어나서switch
다음의 구문을 실행할 수 있게 해 준다. 우리가break
문을 사용하는 이유는switch
문이 점프 연산(computed jump) 집합이라는 것이다. 컨트롤 표현식이 실행될 때 control은switch
문의 case label로 점프한다. case label은switch
문에서 위치를 표시해주는 것 이상의 의미를 갖지 않는다. 한 case의 마지막 구문이 실행돼 고난 뒤 control은 case label을 무시하며 다음 case의 첫 번째 구문으로 진행(falls through)한다.break
문이 없다면 control은 다음 case로 계속 진행해나간다. 다음switch
문을 고려해보자.switch (grade) { case 4: printf("Excellent"); case 3: printf("Good"); case 2: printf("Average"); case 1: printf("Poor"); case 0: printf("Failing"); default:printf("Illegal grade"); }
만약
grade
가3
이라면 출력되는 문자열은 다음과 같다.GoodAveragePoorFailingIllegal grade
의도적으로 다음 case로 흘려보내는 것은 흔치 않지만 그래야 한다면
break
대신 다음과 같이 주석을 다는 것이 좋다.switch (grade) { case 4: case 3: case 2: case 1: num_passing++; /* FALL THROUGH */ case 0: total_grades++; break; }
주석을 남기지 않는다면 다음에 누군가 코드를 검토하면서 오류라고 생각하고
break
를 추가할 수 있다.또한 마지막 case의
break
는 없어도 되지만, 코드를 수정할 때break
를 쓰지 않는 일을 방지하기 위해 보통 많이 추가한다.
Program: Printing a Date in Legal Form
법적인 문서에는 보통 다음과 같은 날짜 표기를 한다.
Dated this XX day of X, 20XX.
날짜를 입력받아 위와 같은 형식으로 출력하는 프로그램을 만들어보자. 프로그램은 다음의 형식으로 동작한다.
Enter date (mm/dd/yy):
4/2/42Dated this 2rd day of April, 2042
형식 입출력 함수인
printf
와scanf
로 이 프로그램을 작성할 수 있지만 두 가지 문제가 있다. 우선 어떻게 "st", "nd", "rd", "th"를 구분할지, 그리고 어떻게 월을 문자열로 출력할지에 대한 문제다. 다행히도switch
문을 이용한다면 두 문제를 적절하게 해결할 수 있다./* Prints a date in legal form */ #include <stdio.h> int main(void) { int month, day, year; printf("Enter date (mm/dd/yy):"); scanf("%d /%d /%d", &month, &day, &year); printf("Dated this %d", day); switch(day) { case 1: case 21: case 31: printf("st"); break; case 2: case 22: printf("nd"); break; case 3: case 23: printf("rd"); break; default: printf("th"); break; } printf(" day of "); switch (month) { case 1: printf("January"); break; case 2: printf("February"); break; case 3: printf("March"); break; case 4: printf("April"); break; case 5: printf("May"); break; case 6: printf("June"); break; case 7: printf("July"); break; case 8: printf("August"); break; case 9: printf("September"); break; case 10: printf("October"); break; case 11: printf("Novemver"); break; case 12: printf("December"); break; } printf(", 20%.2d.\n", year); return 0; }
'프로그래밍 언어 > C' 카테고리의 다른 글
07. Basic Types (0) 2020.03.30 06. Loops (0) 2020.03.30 03. Formatted Input/Output (0) 2020.03.19 02. C Fundamentals (0) 2020.03.12 01. Introducing C (0) 2020.03.11 - Selection statements.