5. Selection Statements

하모씨·2021년 10월 5일
0

KNKsummary

목록 보기
2/23

이 글은 작성자가 K.N.King C Programming: A Modern Approach Second Eddtion을 보고 직접 공부한 뒤에 요약한 것입니다.

1. Logical Expressions

Boolean, logical type은 0(false), 1(true)의 값을 가진다.

관계 연산자(Relational Operations)

<	less than
>	greater than
<=	less than or equal to
>=	greater than or equal to

수학에서의 기호와 같다.

표현식 안에서 이 연산자들이 사용되면 표현식의 결과가 0(false) 또는 1(true)가 된다.
관계 연산자의 우선순위는 수학 연산자보다 낮다. 또 관계 연산자는 left associative이다.

i < j < k 은 (i < j) < k와 같다.

위의 표현식은 ji보다 크고 k보다 작은지 평가하지 않는다. 왜일까?
관계 연산자의 연산 결과는 0(false) 아니면 1(true)이기 때문이다.

i < j < k
i = 5, j = 3, k = 1 이라고 생각해보자.
i < j의 연산값은 5 < 3이기 때문에 0(false)이다.
0 < k의 연산값은 0 < 1이기 때문에 1(true)이다.
그러므로 i < j < k의 값은 1(true)이다.

만약 ji보다 크고 k보다 작은지 평가할 의도로 이 표현식을 작성했다면 완전히 잘못된 결과를 낸 것이다. 의도대로 작동했다면 연산값은 0(false)가 되어야 하는데, 1(true)가 나온것이다.

동등 연산자(Equality Operators)

==	equal to
!=	not equal to

relational operator와 동일하게 연산값이 0(false)또는 1(true)이다.
그러나 equality operatorrelational operator보다 우선순위가 낮다.

i < j == j < k 
(i < j) == (j < k)

위의 두 식은 동일하다.

논리 연산자(Logical Operators)

!	logical negation
&&	logical and
||	logical or

logical operator도 연산값이 0(false) 또는 1(true)이다.
logical operator는 0이 아닌 피연산자를 true 값으로 간주하고, 0인 피연산자를 false값으로 간주한다.

&&||는 피연산자에 short-circuit evaluation을 수행한다. 즉, 이 연산자들은 처음에 왼쪽 피연산자를 계산하고, 이후 오른쪽 피연산자를 계산한다.

(i != 0) && (j / i > 0)

만약 && 왼쪽에 있는 (i != 0)의 연산값이 0이라면, 오른쪽 피연산자는 계산하지 않는다. 어차피 왼쪽이 0이라면 오른쪽에 값이 상관없이 &&의 연산값이 0일 것이기 때문이다.

(i != 0) || (j / i > 0)

이 경우도 비슷하다. 만약 || 왼쪽에 있는 (i != 0) 연산값이 1이라면 오른쪽 피연산자는 계산하지 않는다. 어차피 왼쪽이 1이라면 오른쪽 피연산자의 값과 상관없이 ||의 연산값이 1일 것이기 때문이다.
short-circuit은 division by zero의 오류를 막을 수 있다.

a != 0 && b/a > 10;
위와 같은 구문에서는 a != 0이 먼저 계산되고,
만약 a가 0이라면 b/a > 10은 계산되지 않을 것이기 때문에 division by zero가 발생하지 않는다.

논리 표현식에서의 side effect를 조심해야 한다. &&|| 연산자의 short-circuit 속성 덕분에 side effect가 항상 발생하지는 않는다.

i > 0 && ++j > 0

명확하게 j가 증가하는 것처럼 보인다. 하지만 i > 0의 연산값이 1이 아니라면 &&의 오른쪽 연산을 하지 않게된다. 그렇게 되면 j는 증가하지 않는다.

! 연산자는 단항 +, -와 동등한 우선순위를 가진다.
&&|| 연산자는 관계 연산자와 동등 연산자보다 낮은 우선순위를 가진다.
! 연산자는 right associative이고, &&|| 연산자는 left associative이다.

위에서 설명한 expression을 한번 다시 보자.

i < j < k

위의 expressionjk보다 작고, i보다 크다라는 의도를 나타내지 못한다는 것을 우리는 알았다.
그러면 어떻게 계산식을 작성해야 할까?

i < j && j < k

위와 같이 계산식을 작성하면 우리의 의도대로 계산이 될 것이다.

2. if 구문

if ( expression ) statement

if의 기본형이다. expression의 값이 0이 아닐 때 true로 인식하여 statement를 실행하고, 0일 때 false로 인식하여 statement를 실행하지 않는다.

복합(Compound) 구문

if의 기본형은 statement를 하나밖에 평가하지 못한다. 2개 이상 복합적인 평가를 하기 위해서는 {}기호가 필요하다.

{ statement }

중괄호를 넣는 것으로 컴파일러가 여러 구문을 하나의 구문처럼 처리하도록 할 수 있다.

else 절(Clause)

if 구문은 else 절을 가질 수 있다.

if ( expression ) statement else statement

ifexpression이 0일 때 else가 실행된다.

계단식 if 구문

if ( expression )
    statement
else if ( expression )
    statement
...
else if ( expression )
    statement
else
    statement

당연하게도 else 구문이 항상 있어야 하는 것은 아니다.

"Dangling else" 문제

아래의 예시를 보자.

if (y != 0)
    if (x != 0)
        result = x / y;
else
    printf("Error: y is equal to 0\n");

여기서 else는 과연 어떤 if에 속해있는걸까?
C언어는 else절은 가장 가까운 if구문에 속하도록 하는 규칙에 따른다. 그러므로 elseif (x != 0)에 속하는 것이다. 하지만 중괄호를 사용한다면 프로그램이 내 의도대로 명확하게 작동되도록 할 수 있다.

if (y != 0)
{
    if (x != 0)
        result = x / y;
}
else
    printf("Error: y is equal to 0\n");

조건 표현식(Conditional Expressions)

C언어는 표현식이 값의 조건에 따라 두 개의 값 중 하나를 만들어내는 표현식을 연산자라는 형태로 허용한다.
?:로 이루어진 조건 연산자가 함께 사용되어야만 한다.

expr1 ? expr2 : expr3

위의 expression들은 어떤 type의 expression이든 상관이 없다. 3개의 피연산자를 필요로 하는 C언어의 연산자는 아주 드물기 때문에 이 연산자를 삼항(ternary) 연산자라고도 부른다.
expr1 ? expr2 : expr3은 "만약 expr1이라면 expr2이고, 아니라면 expr3이다"라는 의미이다.
expr1이 첫번째로 계산되고, 그 값이 만약 0이 아니라면 expr2가 계산되고, expr2의 값이 전체 조건 표현식의 값이 된다. expr1의 값이 0이라면 전체 조건 표현식의 값이 expr3이 된다.
조건 연산자의 우선순위는 대입(assignment) 연산자를 제외하고 지금까지 논의한 다른 연산자들보다 낮다.
조건 표현식은 보통 프로그램을 더 알아보기 어렵기 만들기 때문에 피하는 것이 아마도 좋을 수도 있다. 하지만 특정 상황에서는 사용을 시도할 법한데, 예를 들어 조건 표현식은 특정 종류의 매크로(macro) 정의에서 흔히 볼 수 있다.

Boolean Values in C89

오랜 세월동안 C언어는 적절한 Boolean 자료형이 없었고 C89 표준 안에도 정의되지 않았다.
프로그램이 더 알아보기 쉽게 하기 위해 C89 프로그래머들은 TRUE와 FALSE와 같은 이름으로 매크로를 정의하기도 했다.

#define TRUE 1
#define FALSE 0

이 아이디어에서 조금더 나아가 우리는 자료형으로도 정의할 수 있다.

#define BOOL int

BOOL flag;

나중에는 Boolean 자료형을 자료형 정의와 enumerations를 이용하여 C89에서 Boolean 자료형을 설정하는 좋은 방법을 설명하겠다.

Boolean Values in C99

오랜 기간동안 없었던 Boolean 자료형이 _Bool 자료형을 제공하면서 C99에서는 교정되었다.
_Bool은 정확하게 말하면 unsigned integer 자료형이다. 그런데 다른 일반적인 정수 변수들과는 다르게 _Bool 0과 1밖에 대입하지 못한다. 일반적으로 _Bool 자료형에 0이 아닌 값을 넣으려고 시도한다면 _Bool변수의 값은 1로 대입될 것이다.

_Bool flag;
flag = 5;    /* flag is assigned 1 */

_Bool 변수에 수학적인 게산을 수행하는 것은 규칙에 어긋나지는 않다. _Bool 변수를 출력하는 것도 규칙에 어긋나지 않다. 그리고 당연하게도 if 구문 내에서 _Bool 변수는 사용될 수 있다.
Bool 자료형을 정의하기 위해서 C99는 새로운 <stdbool.h>라는 헤더를 제공하여 Boolean 값을 더 쉽게 작업할 수 있도록 만들었다. 이 헤더는 _Bool과 동일한 bool을 제공한다. <stdbool.h>헤더가 포함되어 있다면, 우리는 아래와 같이도 쓸 수있다.

bool flag;    /* same as _Bool flag: */

<stdbool.h>헤더는 truefalse의 이름을 가진 macro를 또한 제공한다. 각각 1과 0을 의미한다.

flag = false;
flag = true;

3. switch 구문

계단식 if를 사용하지 않고 switch를 통해 동일하게 구현할 수 있다.

계단식 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");
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;
}

위의 switch구문이 실행되면 grade가 4, 3, 2, 1, 0에 대응하는지 테스트한다.
grade가 4, 3, 2, 1, 0에 대응하는 값이 아니라면 default에 해당하는 구문을 실행한다.
break 구문은 제어를 switch구문의 뒤를 잇는 다른 구문에게 넘긴다.
switch구문은 계단식 if보다 더 읽기 쉽고, 특히 다뤄야할 경우가 더 많을 때 보통 if 구문보다 빠르다.
일반적인 경우의 switch 구문 형태는 아래와 같다.

switch ( expression ) {
    case constant-expression : statements
    ...
    case constant-expression : statements
    default : statements
}

switch구문은 꽤 복잡하다.
1. Controlling expression
switch는 반드시 소괄호 안의 expression으로 정수를 받아야 한다. 문자또한 C언어에서는 정수로 처리하기 때문에 switch 구문에서 사용할 수 있다.
2. Case labels
각각의 경우는case constant-expression :으로 시작하는 Case lables이 있다.
constant expression은 일반 표현식이지만 변수나 함수의 호출을 포함할 수 없다. 그렇기 때문에 nmacro로 정의되어 있지 않는 한 n+10과 같은 표현식이 괄호에 들어가면 안된다. 또 case label 내부의 onstant expression 반드시 정수 또는 문자로 계산되어야 한다.
3. Statements
각각의 case label은 구문의 수에 제한이 없다. 구문에 중괄호도 필요하지 않다. 일반적으로 각각의 case label의 마지막 구분은 break이다.

case labels를 복사하는 것은 허용되지 않는다. 그리고 case의 순서는 문제가 되지 않는데, 특히 default case가 항상 끝에 와야하는 것은 아니다.
constant expressioncase를 따라야 하는데, 다양한 case label이 구문의 같은 그룹에 선행할 수 있다. 아래의 예시를 보자.

switch (grade)
{
    case 4:
    case 3:
    case 2:
    case 1:    printf("Passing");
               break;
    case 0:    printf("Failing");
               break;
    default:   printf("Illegal grade");
               break;
}

위와 같이 사용하면 grade가 4, 3, 2, 1일때 Passing이 출력된다.
안타깝게도 특정 값의 범위를 지정한 case label은 작성할 수 없다.
switch구문은 default를 가질 필요는 없다. default가 없을 경우에는 controlling expression이 어떠한 case label과도 대응시키지 않기 때문에 switch이후의 구문으로 넘어가게 된다.

break 구문의 역할

break를 실행하는 것은 프로그램이 switch구문으로부터 break 하도록 한다. 그리고 switch구문 이후의 다음 구문이 이어서 실행된다.
break가 필요한 이유는 switch구문이 "계산된 jump"의 형태기 때문이다. controlling expression이 계산되었을 때, 제어는 switch 표현식의 값과 일치하는 case label로 jump한다. case 내부의 마지막 구문이 실행되었을 때, 제어는 뒤이은 case의 첫번째 구문으로 이동한다. 다음 case에 대한 case label이 무시되는 것이다. break나 또다른 jump 구문이 없다면 제어는 다음 case로 이동하게 된다. 아래의 switch 구문을 보자.

swtich (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

break가 없다면 위에 설명한 내용처럼 다음 case를 또 실행시킨다는 것이다.

의도적으로 break를 생략하는 일은 드물다.

switch (grade)  {
    case 4:  case 3:  case 2:  case 1:
            num_passing++;
            /* FALL THROUGH */
    case 0: total_grade++;
            break;
}

주석이 없다면 누군가 break 구문이 없는 것을 에러라고 생각하여 고칠 수도 있다.
switch구문이 마지막 case의 break 구문을 필요로 하지 않는다고 하더라도 이후에 추가될 case들이 있다면 break가 없는 문제가 발생할 수 있기 때문에 마지막 case에 break를 넣는 것은 일반적이다.



Others

조건 표현식 (i > 0 ? i : f)float 값과 int 값이 섞여있다면 조건 표현식의 자료형은 float가 된다. i > 0을 만족하고, iint형이고 ffloat형이여도 표현식의 값은 i이지만 float 자료형으로 변환된다.


C99 표준에서의 _Bool이라는 이름이 아주 눈에 띤다. bool 도는 boolean으로 했으면 좋았지 않을까? 그 이유는 boolboolean이라는 말을 이미 존재하고 있는 프로그램들이 정의해서 사용중이기 때문이다. C89 표준은 _ 다음에 대문자로 시작하는 단어는 미래에 사용될 예정이기에 프로그래머들에겐 사용을 권하지 않는다고 명시하고 있다.

profile
저장용

0개의 댓글