Home C언어 (6) 조건문
Post
Cancel

C언어 (6) 조건문

C언어 (6) 조건문

프로그램은 사용자의 선택에 의해 동작해야 한다.
입력이나 신호에 따라 변화하는 프로그램을 만들기 위해 조건문을 알아야 한다.

조건문

image

자판기 내부의 과정을 간단하게 도식화해서 보면 이런 동작들이 있을 것이다.
이 때 마름모안에 들어있는 문장들을 조건문이라고 부르고, 문장의 참, 거짓에 따라 동작이 바뀐다.
여태까지 main의 첫 줄부터 시작해 순서대로 내려왔지만 이제는 순서가 조금 달라질 수도 있다.

if-else문

image

if문은 조건식이 참이라면 안의 내용을 수행하는 형태를 가진다.

1
2
3
4
5
6
if(number>0){
  printf("양수입니다.");
}
else{
  printf("음수입니다.");
}

와 같이 사용하며, (number>0) 이 참이면 if 문의 코드가, 거짓이면 else 문이 실행된다.

else if문

만약 경우가 더 나눠지는 경우가 있다고 해보자.
절대평가 방식으로 성적을 주는 프로그램을 만들어 90점대는 A, 80점대는 B, 나머지는 F라고 하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
    int score;
    scanf("%d", &score);
    if (score >= 90) {
        printf("A입니다");
    }
    else if (score >= 80) {
        printf("B입니다");
    }
    else {
        printf("F입니다");
    }
    printf("\n성적 입력 완료");
}

이렇게 if-else문 안에 조건을 더 넣고 싶으면 else if(조건문)을 추가하면 된다.
또한 실행문이 1줄이라면 중괄호를 생략할 수도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main() {
    int score;
    scanf("%d", &score);
    if (score >= 90)
        printf("A입니다");
    else if (score >= 80)
        printf("B입니다");
    else
        printf("F입니다");
    printf("\n성적 입력 완료");
}

논리 연산자

논리 연산자는 AND(&&), OR(||), NOT(!) 3가지가 있다.

ABA && B
falsefalsefalse
falsetruefalse
truefalsefalse
truetruetrue

A && B는 둘 다 참이어야 참인 연산

ABA || B
falsefalsefalse
falsetruetrue
truefalsetrue
truetruetrue

A || B는 둘 중 하나만 참이어도 참인 연산

A!A
falsetrue
truefalse

!A는 반대로 결과를 낸다.

이제 이 연산들을 조건식에 넣으면 복잡한 조건들을 만들 수 있다.
3보다 작거나 8보다 큰 수는

1
if (x<3 || x>8)

이런 식으로 표현 할 수 있게 된다.

참과 거짓

if문에는 조건문이 같이 있고, 이 조건문의 참과 거짓 여부에 따라 다른 행동을 한다.
그렇다면 c언어에서 참과 거짓은 어떻게 전달될까?

관계 연산자는 연산 후에 반환하는 값이 1(true)혹은 0(false)이다.

1
2
3
4
5
6
7
8
#include <stdio.h>
int main() {
    int a = 5;
    int b = 0;
    printf("a는 참인가? %d\n", a != 0); //참이므로 1
    printf("b는 참인가? %d\n", b != 0); //거짓이므로 0
    return 0;
}

이 코드롤 출력해보면 알 수 있듯이 관계 연산자의 반환 값은 1또는 0이다.
또한 C언어에서 0을 제외한 다른 값들은 전부 참으로 받아들여진다.

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main() {
    if (1)printf("1은 참인가?\n");
    if (123)printf("123은 참인가?\n");
    if ('a')printf("a은 참인가?\n");
    if ("asdf")printf("asdf은 참인가?\n");
    if (0)printf("0은 참인가?\n");
    return 0;
}

이 코드를 실행해보면 1, 123, a, asdf 모두 참으로 판단하는 것을 볼 수 있다.

단축 계산

비트 연산에서 &와 |에 대해서 보았다.
이 &, | 와 조건문에서 사용하는 &&와 ||의 차이는 무엇일까?

프로그래밍 언어는 필요없는 계산을 줄이는데 이를 단축 평가(Short-circuit Evaluation)라고 부른다.
만약 (조건문 1) && (조건문 2)가 참이 되기 위해서는 두 조건문이 다 참이여야 한다.
그런데 조건문 1이 거짓이라면 조건문 2는 실행할 필요가 없어진다.

&는 앞 뒤를 이용해 계산하는 연산자이기에 조건문 1과 조건문 2를 둘 다 실행하지만, && 논리 연산자는
한 조건문만 확인했을 때 결과가 정해진다면 나머지 조건문들은 확인하지 않는다.

||도 같은 원리로 조건문 중 하나라도 참이면 참인 조건 연산자이다.
(조건문1) || (조건문2)에서 조건문1이 참이라면 뒤에를 확인하지 않고 참을 반환해도 되는 것이다.

논리 연산자의 우선 순위

!와 &&와 ||가 한 줄에 있으면 계산 우선 순위는 어떻게 될까?

1
2
3
4
5
6
7
8
#include <stdio.h>
int main() {
    int a = 1, b = 0, c = 1, d = 0;
    if (a || b && c && d) {
        printf("조건이 참입니다.\n");
    }
    return 0;
}

이 코드가 순서대로 실행된다고 하면, a || b = 1이고, 1 && c = 1, 1 && d = 0이므로 printf문이 출력이 되면 안 된다.
image

하지만 실행이 잘 된다. 논리 연산자의 우선 순위는 !, &&, || 이기 때문이다.
실행 순서는 b && c = 0, 0 && d = 0, a || 0 = 1이므로 참이 되어 실행된다.

switch-case 문

조건문에는 if-else문 말고도 switch-case문이 있다.

1
2
3
4
5
6
메뉴를 선택하세요
1. 더하기
2. 빼기
3. 곱하기
4. 나누기
5. 나머지

와 같은 계산기 프로그램 메뉴를 만든다고 해보자.
if-else문으로 만든다면 아래와 같은 형태가 될 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    if (menu == 1) {
        printf("더하기를 선택하셨습니다.\n");
    } 
    else if (menu == 2) {
        printf("빼기를 선택하셨습니다.\n");
    } 
    else if (menu == 3) {
        printf("곱하기를 선택하셨습니다.\n");
    } 
    else if (menu == 4) {
        printf("나누기를 선택하셨습니다.\n");
    } 
    else if (menu == 5) {
        printf("나머지를 선택하셨습니다.\n");
    } 
    else {
        printf("잘못된 선택입니다.\n");
    }

switch-case문의 형태는 어떤 변수가 스위치가 되고, 해당 스위치에 있는 코드를 실행한다.
image

이렇게 변수 a의 값에 따라 각각의 함수를 실행하는 조건문이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    switch (a) {
        case 1:
            printf("더하기를 선택하셨습니다.\n");
            break;
        case 2:
            printf("빼기를 선택하셨습니다.\n");
            break;
        case 3:
            printf("곱하기를 선택하셨습니다.\n");
            break;
        case 4:
            printf("나누기를 선택하셨습니다.\n");
            break;
        case 5:
            printf("나머지를 선택하셨습니다.\n");
            break;
        default:
            printf("잘못된 선택입니다.\n");
            break;
    }

코드로 보면 이렇게 되고, default은 else 처럼 case문에 해당이 없을 때 실행된다.

처음 보는 break;라는 코드가 생겼다. break는 실행하던 코드를 중단하는 역할을 한다.
만약 break가 없으면 a가 1일 때 모든 case의 실행문을 실행하게 된다.
image

그렇기 때문에 각각의 case를 끝낼때는 break문을 꼭 넣어주자.

switch-case 문의 장단점

조건문 코드의 변수가 많아질수록 switch-case문이 가독성 측면에서 if-else문보다 좋다.
하지만 코드를 보면 알 수 있듯이 switch-case문은 if-else문으로 바꿀 수 있다.
또한 switch-case문에는 조건을 표현하는데 한계가 존재한다.

어떤 변수를 switch로 어떤 일을 할지 case에 정해준 값과 정확히 일치해야 실행이 된다.
그렇기 때문에 case 뒤에는 정수형 변수만이 올 수 있다.

이전에 자료형 부분에서 실수는 부동 소수점 표현 방식 때문에 정확한 값을 표현하지 못한다고 했다.
그렇기에 case 0.3: 이라는 코드도 정확한 값을 비교할 수 없기 때문에 오류가 발생한다.

1
2
3
4
5
6
7
switch (value) {  // 오류 발생
        case 0.3:
            printf("값이 0.3입니다.\n");
            break;
        default:
            printf("다른 값입니다.\n");
    }

그렇다면 문자형은 왜 가능할까? 컴퓨터는 문자를 숫자로 처리하기 때문에 가능하다.
또한 변수가 2보다 클 때 실행하는 조건문 코드를 짜는 것은 switch-case문에서 할 수 없다.
if (2 <= a)를 switch 문으로 바꾼다고 해보자.

1
2
3
4
5
6
7
8
9
10
11
switch (value){
    case 2:
        ~~
    case 3:
        ~~
    case 4:
        ~~
    ....
    case 100:
        ~
}

이렇게 case들을 만들어줘야 할 것이다.
그렇기에 switch case문은 한정적인 조건에서만 사용할 수 있다.

그러면 switch case문의 장점은 가독성밖에 없는 것일까?
if-else와 switch-case 문의 차이점(성능, 메모리 관점)
모두의 코드의 설명에도 링크되어 있는 글인데 어셈블리어 측면에서 두 조건문을 분석했다.

if-else문은 만족하는 조건을 찾을 때 까지 if, else if… 모든 조건을 순차적으로 비교한다.
조건이 맨 마지막에 있는 최악의 경우에는 O(N)의 시간 복잡도를 가진다.(N=조건문의 개수)
시간 복잡도란 O(x)에서 x번의 연산을 하는 것이라고 생각하자.
나중에 자료구조나 알고리즘을 공부하면서 자세히 알게 된다.

하지만 switch-case문은 비교를 통해 case로 점프할 수 있도록 점프 테이블을 생성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    switch (a) {
        case 1:
            printf("더하기를 선택하셨습니다.\n");
            break;
        case 2:
            printf("빼기를 선택하셨습니다.\n");
            break;
        case 3:
            printf("곱하기를 선택하셨습니다.\n");
            break;
        case 4:
            printf("나누기를 선택하셨습니다.\n");
            break;
        case 5:
            printf("나머지를 선택하셨습니다.\n");
            break;
        default:
            printf("잘못된 선택입니다.\n");
            break;
    }
a 값점프 테이블
1더하기를 선택하셨습니다. 출력
2빼기를 선택하셨습니다. 출력
3곱하기를 선택하셨습니다. 출력
4나누기를 선택하셨습니다. 출력
5나머지를 선택하셨습니다. 출력
default잘못된 선택입니다. 출력

우선 a의 값이 5보다 큰지 확인하고, 크다면 default 부분을 실행한다.
크지 않다면 jump할 case문이 있다는 것이기 때문에 해당 case로 점프한다.
점프 테이블은 이미 1, 2, 3, 4, 5에 대한 행동들이 매핑되어 있기 때문에 한번에 a값으로 점프해 해당 부분을 실행할 수 있다.
그러면 시간 복잡도는 O(1)로 아주 빠르게 실행할 수 있다.

따라서 조건문이 많아질수록 switch-case문이 if-else문보다 시간적 측면에서 효율적이다.

만약 case가 1, 2, 3, 1000과 같이 일정하지 않게 이루어져있다면 어떨까?
위의 블로그에서는 1부터 1000까지 모든 case의 점프 테이블을 만들어 메모리적 관점에서 비효율적이라고 한다.
이에 대해서는 따로 더 알아보았다. 다른 글에서 switch-case에 관련된 내용은 더 알아보자.
switch case의 작동 방식


순차적으로 내려오는 방식에서 조건에 따라 분기가 있는 프로그램을 만들 수 있다.
다음에는 특정 조건에서 반복하는 프로그램을 만들어보자.

This post is written by PRO.