1. 분기
분기(branch)는 프로그램의 실행 흐름을 선택하는 것을 말한다.
두 수를 입력받아 계산하는 계산기를 만든다고 생각해보자.
입력받는 것까지는 문제 없다. 하지만 그 다음이 문제다.
사칙연산의 결과를 모두 출력할 수는 있다. 하지만 그 중 하나를 고르는 건 어떻게 해야 할까?
이를 위해 필요한 게 분기문이다.
분기문은 기본적으로 조건에 의해 이루어진다
다시 말해, 조건에 따라 실행 여부를 결정한다는 것이다.
물론 무조건 분기도 있다.
하지만 상대적으로 사용 빈도가 낮다.
앞에서 예시로 든 계산기의 경우 다음과 같이 실행 여부를 결정할 수 있다.
수행할 연산을 입력받는다.
입력받은 연산이 +면 덧셈을 수행한다. 아니면 다음 줄로 넘어간다.
입력받은 연산이 -면 뺄셈을 수행한다.
입력받은 연산이 *면 곱셈을 수행한다.
입력받은 연산이 /면 나눗셈을 수행한다.
2. if 문
가장 기본적인 분기문으로, 형식은 다음과 같다.
if(조건식)
{
//조건식이 참이면 실행
}
키워드 if 가 상당히 직관적이기 때문에 이해하는 게 어렵진 않을 것이다.
다음은 a 보다 b 가 크면 "b가 a보다 더 큽니다."를 출력하는 코드다.
if(a < b)
{
printf("a: %d, b:%d\n", a, b);
printf("b가
a보다더 큽니다
.\n");}
이제 if 문을 사용해 앞에서 예로 든 계산기 프로그램을 작성해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
#include <stdio.h>
int main()
{
int oper;
double a, b;
double result;
printf("두 실수: ");
scanf_s("%lf %lf", &a, &b);
printf("1. 덧셈\n");
printf("2. 뺄셈\n");
printf("3. 곱셈\n");
printf("4. 나눗셈\n");
printf("선택 >> ");
scanf_s("%d", &oper);
if (oper == 1)
{
result = a + b;
}
if (oper == 2)
{
result = a - b;
}
if (oper == 3)
{
result = a * b;
}
if (oper == 4)
{
result = a / b;
}
printf("결과: %f\n", result);
return 0;
}
|
cs |
그러나 이 코드에는 다음의 문제점이 있다.
1~4 중 무엇을 입력해도 조건 검사는 항상 모두 실행된다.
이 경우 세 번의 무의미한 조건 검사가 일어나고, 이는 성능 저하로 이어진다.
따라서 조건 검사가 다음과 같이 이루어진다면 더 효율적일 것이다.
조건을 만족하면 나머지 if 문은 모두 건너뛴다.
우리는 if~else 문을 통해 이를 구현할 수 있다.
이건 잠시 뒤에 보기로 하고, 우선 다음 문제를 보자.
풀고 싶은 사람은 직접 풀어도 되지만, 아마 아직 어려울 것이다.
Q. 10보다 작은 자연수 중에서 3 또는 5의 배수는 3, 5, 6, 9 이고, 이것을 모두 더하면 23입니다.
1000보다 작은 자연수 중에서 3 또는 5의 배수를 모두 더하면 얼마일까요?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
#include <stdio.h>
int main()
{
int i;
int sum = 0;
for (i = 1; i < 1000; ++i)
{
if ((i % 3 == 0) || (i % 5 == 0))
{
sum += i;
}
}
printf("sum: %d\n", sum);
return 0;
}
|
cs |
정답: 233168
결과만 제대로 나오면 코드가 어떻든 상관 없다. 손으로 풀지만 않았으면 된다.
3. if~else 문
형식은 다음과 같다.
if( 조건식 )
{
//조건식이 참일 때 실행할 문장 (이 문장을 실행한 다음 else 블록은 건너뛴다.)
}
else
{
//조건식이 거짓일 때 실행할 문장 (if 블록은 건너뛴다.)
}
이 때 else 는 반드시 if 와 쌍을 이루어야 한다. 독립적으로 사용할 수 없다는 것이다.
다음은 입력한 수가 짝수인지 홀수인지 판별하는 코드다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#include <stdio.h>
int main()
{
int num;
printf("num: ");
scanf_s("%d", &num);
if (num % 2 == 0)
{
printf("짝수\n");
}
else
{
printf("홀수\n");
}
return 0;
}
|
cs |
참고로 중괄호는 다음과 같이 생략할 수도 있고,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#include <stdio.h>
int main()
{
int num;
printf("num: ");
scanf_s("%d", &num);
if (num % 2 == 0)
printf("짝수\n");
else
printf("홀수\n");
return 0;
}
|
cs |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#include <stdio.h>
int main()
{
int num;
printf("num: ");
scanf_s("%d", &num);
if (num % 2 == 0) printf("짝수\n");
else printf("홀수\n");
return 0;
}
|
cs |
다음과 같이 중괄호를 한 줄에 쓸 수도 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#include <stdio.h>
int main()
{
int num;
printf("num: ");
scanf_s("%d", &num);
if (num % 2 == 0){ printf("짝수\n"); }
else { printf("홀수\n"); }
return 0;
}
|
cs |
비단 if~else 문 뿐만 아니라 반복문, 함수 등 모든 문장에서
내부 코드가 한 줄이면 중괄호는 생략할 수 있고 중괄호를 한 줄에 쓸 수도 있다.
마음대로 쓰면 된다.
참고로 (일부) if~else 문은 연산자 하나로 대체할 수 있다.
다음 코드를 보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#include <stdio.h>
int main()
{
int num;
printf("num: ");
scanf_s("%d", &num);
if (num % 2 == 0)
{
printf("짝수\n");
}
else
{
printf("홀수\n");
}
return 0;
}
|
cs |
이걸 어떻게 연산자 하나로 대체할 수 있을까?
그 답은 바로 삼항 연산자다.
3.2 연산자에서 언급했듯이, 삼항 연산자는 다음과 같이 사용한다.
(조건식) ? (참일 때의 반환값) : (거짓일 때의 반환값)
그러니까 위 코드는 다음과 같이 바꿀 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#include <stdio.h>
int main()
{
int num;
printf("num: ");
scanf_s("%d", &num);
printf( (num%2 == 0) ? ("짝수\n") : ("홀수\n") );
return 0;
}
|
cs |
훨씬 간결하지 않은가?
이처럼 삼항 연산자를 적절히 사용하면 코드를 훨씬 간결하게 만들 수 있다.
4. if...else if...else 문
형식은 다음과 같다.
if( 조건식 1 )
{
//조건식 1이 참일 경우 실행
}
else if( 조건식 2 )
{
//조건식 2가 참일 경우 실행
}
else if( 조건식 3 )
{
//조건식 3이 참일 경우 실행
}
...
else if( 조건식 n )
{
//조건식 n이 참일 경우 실행
}
else
{
조건식 1 ~ 조건식 n이 모두 거짓일 경우 실행
}
보다시피 else if 절은 얼마든지 추가할 수 있다.
여기서 중요한 것은 if 문을 연속으로 쓴 것과의 차이점이다.
if( 조건식 1 ) { 실행문 1 } if( 조건식 1 ) { 실행문 1 }if( 조건식 2 ) { 실행문 2 } else if( 조건식 2 ) { 실행문 2 }
if( 조건식 3 ) { 실행문 3 } VS else if( 조건식 3 ) { 실행문 3 }
if( 조건식 4 ) { 실행문 4 } else if( 조건식 4 ) { 실행문 4 }
else { 실행문 5 } else { 실행문 5 }
전자의 경우 무조건 4번의 조건 검사를 한다. 실행문 5는 조건식 4가 거짓일 때 실행된다.
후자의 경우 조건식 1이 참이면 실행문 1을 실행하고 나머지 조건은 검사하지 않는다.
조건식 1이 거짓이면 조건식 2를 검사하고,
조건식 2가 참이면 실행문 2를 실행하고 나머지 조건은 검사하지 않는다.
위 코드에서 if...else if...else 문은 이런 의미다.
조건식 1이 참이면 실행문 1만 실행한다.
조건식 1이 거짓이면 조건식 2를 검사한다.
조건식 2가 참이면 실행문 2 실행 후 모두 건너뛰고, 거짓이면 조건식 3을 검사한다.
조건식 3이 참이면 실행문 3 실행 후 모두 건너뛰고, 거짓이면 조건식 4를 검사한다.
조건식 4가 참이면 실행문 4 실행 후 모두 건너뛰고, 거짓이면 실행문 5를 실행한다.
다음은 입력받은 정수가 음수인지, 양수인지, 0인지 출력하는 코드다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
#include <stdio.h>
int main()
{
int num;
printf("num: ");
scanf_s("%d", &num);
if (num > 0)
{
printf("양수\n");
}
else if (num == 0)
{
printf("0\n");
}
else
{
printf("음수\n");
}
return 0;
}
|
cs |
위 코드에서 if...else if...else 문만 떼어 내보자.
if (num > 0)
{
printf("양수\n");
}
else if (num == 0)
{
printf("0\n");
}
else
{
printf("음수\n");
}
그리고 이걸 반으로 쪼개보자.
if (num > 0)
{
printf("양수\n");
}
else
if (num == 0)
{
printf("0\n");
}
else
{
printf("음수\n");
}
이제 if...else if...else 문의 의미가 보이지 않는가?
가독성을 위해 중괄호를 삽입해보자.
if (num > 0)
{
printf("양수\n");
}
else
{
if (num == 0)
{
printf("0\n");
}
else
{
printf("음수\n");
}
}
이제는 if...else if...else 문의 의미가 보일 것이다.
왜 if 문의 조건식이 참이 되면 나머지를 건너뛰는 지 알 것이다.
4. break 문
break 문은 반복문을 탈출할 때 쓰는 문장으로, 탈출하려는 위치에 break; 라고만 써주면 된다.
다음은 5가 나올 때까지 주사위를 던지는 프로그램이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
int dice;
srand((unsigned)time(NULL));
while (1) //무한 루프
{
printf("주사위를 던집니다: ");
dice = (rand() % 6) + 1; //1~6의 난수 생성
printf("%d\n\n");
if (dice == 5)
break;
}
return 0;
}
|
cs |
2행과 3행, 9행은 난수를 발생시키기 위한 여러가지 사전작업이고,
rand 함수는 난수(random number)를 발생시키는 함수로 보면 된다.
다시 한 번 말하지만, break 문은 반복문을 탈출하기 위해 사용된다.
if 문을 탈출한다고 착각하지 말자.
또 한 가지 주의할 점은 하나의 반복문만 탈출한다는 것이다.
다음은 구구단을 출력하되 2단은 2×2까지, 3단은 3×3까지, …, 9단은 9×9까지 출력하는 코드다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#include <stdio.h>
int main()
{
int i, j;
for (i = 2; i <= 9; ++i)
{
for (j = 1; j <= 9; ++j)
{
printf("%d × %d = %-2d\n", i, j, i*j);
if (i == j)
break;
}
printf("\n");
}
return 0;
}
|
cs |
코드를 보면 알 수 있듯이, break 문은 안쪽 반복문만 탈출한다.
만약 바깥쪽 반복문까지 탈출했다면 2단만 출력되었을 것이다.
결론적으로, break 문의 기능은 다음과 같이 정의할 수 있다.
break 문은 가장 가까운 반복문 하나만을 탈출한다.
5. continue 문
continue 문은 반복문에서 한 차례를 건너뛸 때 사용하는 문장이다.
break 문과 마찬가지로 건너뛸 곳에서 continue; 라고만 써주면 된다.
다음은 입력받은 수에 해당하는 구구단을 출력하되, 홀수단만 출력하는 프로그램이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#include <stdio.h>
int main()
{
int i;
int num;
printf("num: ");
scanf_s("%d", &num);
for (i = 2; i <= 9; ++i)
{
if (i % 2 == 0)
continue;
printf("%d × %d = %d\n", num, i, num*i);
}
return 0;
}
|
cs |
continue 문 역시 break 문과 마찬가지로 하나의 반복문에만 적용된다.
다음은 구구단을 출력하되 2×2, 3×3, …, 9×9를 제외하고 출력하는 코드다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#include <stdio.h>
int main()
{
int i, j;
for (i = 2; i <= 9; ++i)
{
for (j = 1; j <= 9; ++j)
{
if (i == j)
continue;
printf("%d × %d = %-2d\n", i, j, i*j);
}
printf("\n");
}
return 0;
}
|
cs |
6. break 문 VS continue 문
break 문과 continue 문은 헷갈리기 쉽다.
사실 필자도 처음 공부할 당시에 많이 헷갈렸다.
때문에 한 번 정리할 필요가 있다.
우선 문장을 만났을 때 어딘가로 이동한다(실행 흐름이 바뀐다)는 것은 같다.
하지만 그 목적지가 다르다. 사실 이것 말고는 차이가 없다.
코드로 보면 다음과 같다.
|
7. switch 문
switch 문은 다음과 같은 형식으로 사용한다.
switch(n)
{
case v1:
//n의 값이 v1일 때 실행
break;
case v2:
//n의 값이 v2일 때 실행
break;
...
case vn:
//n의 값이 vn일 때 실행
break;
default:
//n의 값이 v1~vn 중 아무것도 아닐 때 실행
break;
}
v1: 과 같이 콜론으로 표시된 영역은 *레이블이라 한다.
각 레이블의 끝에 위치한 break 문은 실행 영역을 묶기 위함이다.
이에 대해서는 조금 뒤에 이야기하겠다.
참고로 n 에는 정수형만 올 수 있다.
그러니 정수 연산의 결과나 문자는 되지만 문자열과 실수는 쓸 수 없다.
*레이블(label): 실행의 대상이 아닌 위치를 표시하는 데 쓰이는 표식
다음은 주사위를 던져 나온 수를 영어로 출력하는 프로그램이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
int dice;
srand((unsigned)time(NULL));
dice = (rand() % 6)+ 1;
switch (dice)
{
case 1:
printf("one\n");
break;
case 2:
printf("two\n");
break;
case 3:
printf("three\n");
break;
case 4:
printf("four\n");
break;
case 5:
printf("five\n");
break;
default:
printf("six\n");
break;
}
return 0;
}
|
cs |
이번에도 역시 레이블마다 break 문이 있다.
번거롭게 왜 쓰는 걸까?
이유는 다음 코드를 실행해보면 바로 알 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
int dice = 1;
switch (dice)
{
case 1:
printf("one\n");
case 2:
printf("two\n");
case 3:
printf("three\n");
case 4:
printf("four\n");
case 5:
printf("five\n");
default:
printf("six\n");
}
return 0;
}
|
cs |
앞에서와 달리 dice 값을 1로 초기화했다.
따라서 예상되는 결과는 "one" 만 출력되는 것이다.
예상과 달리 one 부터 six까지 모두 출력되었다.
왜 이런 걸까?
사실 case 1: 은 dice 가 1 일 때 그 부분만 실행하라는 의미가 아니다.
그 부분부터 쭉, 그러니까 default: 까지 실행하라는 의미다.
앞에서 break 문을 넣은 것도 이 때문이다.
반복문에서의 break 문도 결국 반복문 내부 코드 실행을 중단하라는 의미를 담고 있지 않은가?
그러니 switch 문에서는 switch 문 내부 코드 실행을 중단하고 탈출하라는 의미인 것이다.
이런 이유로 default: 에는 break 문이 없어도 된다(사실 불필요하다).
그렇다고 무조건 break 문을 넣을 필요는 없다.
다음은 주사위를 1, 3, 6일 때는 당첨이고 dice가 2, 4, 5일 때는 낙첨인 복권을 구현한 코드다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
char dice;
srand((unsigned)time(NULL));
dice = (rand() % 6) + 1;
switch (dice)
{
case 1:
case 3:
case 6:
printf("당첨!\n");
break;
case 2: case 4: case 5:
printf("낙첨!\n");
}
return 0;
}
|
cs |
case 1: 과 case 3: 에 break 문이 없는 것을 주목하라.
앞에서 말했듯이 1이든 이든 break 문이 없으면 쭉 실행되기 때문에, 결국 세 레이블을
한 곳에 묶은 효과를 낸다.
이를 강조하기 위해 20행와 같이 한 줄에 레이블을 나타내는 것이 일반적이다.
사실 switch 문은 if...else if...else 문으로 바꿔쓸 수 있다.
다음을 보자.
|
|
그렇다. 사실 switch 문을 써야만 하는 경우는 없다. 그렇다면 왜 switch 문을 쓰는 걸까?
개인적으로는 가독성 때문이라고 생각한다.
필자의 눈에는 왼쪽의 코드가 더 눈에 잘 들어온다.
그리고 필자 뿐만 아니라 대다수의 프로그래머들이 switch 문을 선호한다.
프로그래밍은 혼자 하는 일이 아니므로, 때로는 대세에 따를 필요가 있다.
한 가지 기준을 제시하자면, 분기의 수가 많을 때는 switch 문을 쓰는 게 더 낫다.
하지만 분기 수가 많아도 if..else if...else 문이 더 적절한 경우도 있다.
다음을 보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
int score;
if ((90 < score) && (score <= 100))
printf("A\n");
else if ((80 < score) && (score <= 90))
printf("B\n");
else if ((70 < score) && (score <= 80))
printf("C\n");
else
printf("F\n");
return 0;
}
|
cs |
switch 문으로 옮겨 쓸 수 있을까?
물론 가능하긴 하다. 다만 번거롭다.
71, 72, …, 100까지 하나하나 레이블을 작성한다고 생각해보면....
그러니 분기 수가 많다고 switch 문을 고집할 필요는 없다.
기본적으로 switch 문은 정수값에 따라 분기하고
if..else if...else 문은 참/거짓에 따라 분기하기 때문에 그에 맞게 쓰면 된다.
8. goto 문
사실 이 내용을 쓸지 말지 고민했지만, 그래도 아는 게 낫다고 생각해 쓰기로 했다.
goto 문은 이름에서 알 수 있듯이 특정 위치, 즉 특정 레이블로 이동하는 문장이다.
그러나 현재는 사용을 지양하고 있다.
이유는 단순하다. 프로그램의 실행 흐름을 방해하기 때문이다.
어차피 쓰질 않으니 몰라도 되긴 하지만, 모르고 안 쓰는 것과 알고 안 쓰는 것은 다르다.
지식은 곧 힘이라는 말도 있지 않은가? 그러니 하나라도 더 알아두자.
형식은 다음과 같다.
LABEL:
...
goto LABEL;
...
다음 코드를 보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
int num;
printf("num: ");
scanf_s("%d", &num);
if (num == 1) { goto ONE; }
else if (num == 2) { goto TWO; }
else { goto OTHER; }
ONE:
printf("one\n");
goto END;
TWO:
printf("two\n");
goto END;
OTHER:
printf("I don't know\n");
goto END;
END:
printf("프로그램을 종료합니다.\n");
return 0;
}
|
cs |
num 값이 2 이므로 TWO 레이블로 이동하고 TWO 레이블에서는 END 레이블로 이동한다.