혼공학습단 C 3주차📚

하영·2024년 1월 17일
0

혼공학습단

목록 보기
9/13
post-thumbnail

기본 미션 📣

혼공 용어 노트 활용하기 | 완료

선택 미션 📣

크리스마스 소개팅 예문 |

#include <stdio.h>

int main(void)
{
	char answer;

	printf("애인이 있나요? (y/n) : ");
	scanf("%c", &answer);

	if (answer == 'n')
		printf("소개팅을 주선해 준다.\n");

	return 0;
}



제어문은 다음과 같은데, 이번 3주차에선 5장에서 선택문, 6장에서 반복문을 중심으로 배워보겠다.

선택문

05-1 if문

기본적으로 if문은 조건식을 만족하면 실행문을 실행하고, 만족하지 않으면 실행하지 않고 지나간다.

if (조건식)		
{
	실행문; 		
}

여기서 if문은 다양한 모양으로 쓸 수 있다.
하지만 중괄호를 사용하고 들여쓰기하여 실행문을 명확히 구분하는 것이 좋다.

중괄호는 실행문이 하나뿐이면 생략이 가능한데, 실행할 문장이 두 문장 이상이면 반드시 중괄호로 묶어야 한다.

📍 if ~ else문

if ~ else문은 조건을 만족하지 못할 때 수행되는 실행문을 작성할 때 사용하는데,
둘 중 하나를 반드시 선택하는 경우 사용한다.

if (조건식)		
{
	실행문1;		
}
else
{
	실행문2;		
}

단, if ~ else문을 사용할 때는 다음 규칙을 지켜야 한다.

  • if ~ else문에 else에는 조건을 사용하지 않는다.
  • 경우에 따라 if문을 두 번 사용하는 것보다 if ~ else문을 한 번 사용하는 것이 좋다.
    조건식을 정확히 사용하지 않으면 두 조건식이 모두 참이 되어 둘 다 실행되거나 모두 거짓이 되어 하나도 실행되지 않을 수 있다. 따라서 2개의 실행문 중 하나를 선택하는 경우에는 주저 없이 if ~ else문을 쓰도록 한다.

📍 if ~ else if ~ else문

3개의 실행문에서 반드시 하나를 선택하는 경우 if ~ else if ~ else문을 사용한다.

if (조건식1)				
{
	실행문1;				
}
else if (조건식2)		
{
	실행문2;			
}
else
{
	실행문3;				
}

if ~ else if ~ else문처럼 조건이 여러 개인 경우는 우선 첫 번째 조건부터 검사 한 후 첫 번째 조건이 거짓이면 두 번째 조건을 검사한다. 만약 두 번째 조건도 거짓으로 두 조건 모두 만족하지 못하면 마지막으로 else 부분의 실행문이 실행되는 구조이다.

그런데 if ~ else if ~ else문 실행시에는 주의할 점이 있다. 바로 조건식의 순서를 논리적 흐름에 맞게 작성해야 한다는 것이다. if ~ else if ~ else문은 조건식을 차례로 검사하므로 이전 조건의 결과가 반영된다.

if (a >= 10)				
{
	b = 1;				
}
else if (a >= 0)		
{
	b = 2;				 
}
else
{
	b = 3;				 
}

그렇기 때문에 다음과 같은 경우를 보면 b=2의 실행 조건이 a>=0이지만, a가 20일 때 실행되지 않는다. 이미 첫 번째 조건인 a>=10을 만족하므로 b=1이 실행되고 끝나기 때문이다. 따라서 두 번째 조건은 첫 번째 조건이 거짓인 경우(a<10)&&(a>=0)의 조건을 만족해야 실행되게 된다.

따라서 조건식의 순서를 논리적 흐름에 맞게 작성해서 중복 검사를 피하는 것이 좋다.
순서가 중요하지 않다면 참이 될 가능성이 많은 조건식을 먼저 사용해 조건식을 검사하는 횟수를 줄일 수 있다.


05-2 if문 활용과 switch ~ case문

📍 if문 중첩

어떤 조건을 검사하기 전에 선행조건이 있다면 if문 안에 if문을 넣어 사용한다.

if (조건식1)
{
	if (조건식2)
    {
    	실행문1;
    }
    else
    {
    	실행문2;
    }
}

if ~ else문은 실행문이 많고 형태가 복잡하더라도 전체를 한 문장으로 취급하기 때문에 중괄호를 생략할 수 있다. 하지만 실행문의 범위를 쉽게 구분할 수 있도록 가능한 한 중괄호를 사용하는 것이 좋다.

if문 중첩을 사용하는 이유는 크게 두 가지가 있는데,
첫 번째는 선행조건이 있으면 불필요한 조건 검사를 하지 않기 때문이다. 만약 if문 중첩을 사용하지 않고, 두 개의 if문을 논리곱(&&)을 이용하여 작성하였다면 두 if문의 조건을 모두 검사하므로 실행 효율이 떨어질 수 있다. 하지만 if문을 중첩하면 불필요한 연산을 줄여 실행 효율을 높일 수 있다.

두 번째는 선행조건이 없어도 실행 효율을 위해 의도적으로 중첩을 사용한다. 비교 항목이 많은 경우는 하나씩 다 검사하는 것보다 두 범위로 나누어 검사하는 방식인 분할 정복 기법을 활용하는 것이 더 좋다. if문을 여러 번 중첩해서 쓰면 시간을 줄이는 데에도 도움이 되기 때문이다. 그러나 코드가 읽기가 더 어려워질 수 있으므로 주의해서 사용해야 한다.

📍 else 결합 문제

문법적으로 if문은 조건에 따라 실행할 문장이 한 문장인 경우 중괄호를 생략할 수 있다. 그러나 if ~ else문이 참일 때 실행문으로 기본 if문이 쓰이면 반드시 중괄호가 필요하다.

소스 코드 5-5.c

#include <stdio.h>

int main(void)
{
	int a = 10, b = 20;

	if (a < 0)
	{
		if (b > 0)			// 9행
		{
			printf("ok");
		}
	}
	else		// 14행
	{
		printf("ok");
	}

	return 0;
}

컴파일러는 if문이 중첩된 경우 else를 가장 가까운 if와 연결한다. 따라서 14행의 else를 9행의 if와 짝을 지어 해석하게 된다. 이렇게 되면 결괏값은 다르게 나오게 된다.

📍 switch ~ case문

switch ~ case문은 여러 개의 상수 중 조건에 해당하는 하나를 골라 실행한다.

switch (조건식)
{
case 상수식1:
	실행문1;
    break;
case 상수식2:
	실행문2;
    break;
default:
	실행문3;
    break;
}

조건식과 일치하는 case의 상수가 없으면 default로 건너뛰는데, default는 상수식을 쓰지 않으며 생략할 수도 있다. default를 생략한 경우 조건식과 일치하는 case의 상수가 없으면 바로 블록을 벗어나므로 블록 내의 어떤 문장도 실행되지 않는다.

switch ~ case문을 쓸 때는 다음 두 가지 규칙을 꼭 지켜야 한다.

  • 조건식으로 정수식만 사용해야 한다.
    정수식은 정수형 상수나 변수를 쓸 수 있고 수식을 사용할 때는 결괏값이 정수여야 한다.
    또한 case에 사용하는 상수식 역시 정수만 가능하다.

  • 기본적으로 case는 break를 포함한다.

break는 필요에 따라 생략할 수 있는데, break를 사용하면 적절한 시점에 블록을 탈출해 필요한 부분만 선택적으로 실행할 수 있다. 만약 break가 없다면 블록의 끝까지 모든 문장을 실행한다. 그렇기에 제한적으로 break를 생략해야 하며 생략할 때는 자세한 설명을 주석으로 남겨야 한다.


반복문

06-1 while문, for문, do ~ while문

📍 while문

while문은 조건식을 먼저 검사하고 조건식이 참인 동안 실행문을 반복한다.

while (조건식)
{
	실행문;
}

더 자세하게 이해하기 위해 소스 코드를 살펴보면

소스 코드 6-1.c

#include <stdio.h>

int main(void)
{
	int a = 1;

	while (a < 10)			// ① 조건식
	{
		a = a * 2;			// ② 실행문
	}
	printf("a : %d\n", a);		// 실행 결과 : 16

	return 0;
}

while문은 먼저 조건식을 검사하고 참이므로 실행문을 수행한다. 그 후 다시 조건식으로 돌아가서 조건이 참인지 확인한다. 이렇게 조건이 참인 동안 ①과 ②를 반복한다. 이렇게 반복하다 보면 최초 a가 1일 때와 2, 4, 8일 때 조건식이 참이 되어 실행문은 총 4회를 반복하게 된다.

그 후 조건식이 거짓이 되었을 때 비로소 while문을 끝내고 while문 이후의 문장인 print문을 실행한다.

printf("a : %d\n", a);

여기서는 결국 a가 16인 상태에서 반복문을 종료하므로 실행 결과는 16이 된다.


while문을 사용할 때는 실행문의 개수와 상관없이 항상 중괄호로 반복할 부분을 명확히 표시하고, 눈으로 쉽게 확인할 수 있도록 실행문을 중괄호 안에 들여쓰기 하는 것이 좋다. 이렇게 하는 것이 읽기 쉬운 코드를 만드는 기본적인 방법이기 때문이다.

📍 for문

for문은 실행문을 반복하는 횟수가 정해져 있을 때 주로 사용하는데, 초기식, 조건식, 증감식으로 반복 횟수를 제어하며 블록 안의 문장을 반복한다.

for (int i = 0; i < 3; i++)		// 초기식; 조건식; 증감식
{
	a = a * 2;			// 실행문;
}

실행 순서를 보면 초기식은 변수 i를 초기화하는 부분으로 딱 한 번만 실행이 된다.
이어서 조건식을 검사해 결과가 참이면 블록 안으로 들어가 반복할 문장인 실행문을 수행하고, 증감식으로 올라가 i 값을 1증가시키고 다시 조건식을 검사하는 순서로 반복한다.


여기서 주의할 점은 컴파일러에 따라 변수를 for문 바깥에 선언해줘야 한다.
그이유는 C 언어 표준은 C99 및 C11과 같이 여러 버전이 존재하는데, 이러한 표준 버전 중 일부에서는 for 루프에서 반복 변수를 선언하는 기능이 도입되었다. 하지만 오래된 C 표준인 ANSI C (C89/C90)에서는 for 루프에서 변수를 선언하는 것이 허용되지 않는다.

따라서 C89/C90에서는 for문 안에 변수를 선언하게 되면,
'for' loop initial declarations are only allowed in c99 or c11 mode 에러를 일으키게 된다.
다행히도 VC++에서는 에러가 일어나지 않지만 종종 이런 경우가 있어 정리해보았다.


이외에도 for문 사용 시 주의할 점이 있다.

1. 초기식, 조건식, 증감식은 반복 횟수를 알기 쉽게 작성해야 한다.
초기식, 조건식, 증감식의 조합은 많다. 예를 들어 다음은 모두 세 번 반복하는 for문이다.

for (i=0; i<3; i++)
for (i=2; i<=4; i++)
for (i=0; i<7; i+=3)
for (i=3; i>0; i--)

이처럼 어떤 것을 사용해도 결과가 같다면 이해하기 쉬운 코드를 선택해야 한다.

2. 반복 횟수를 세는 변수를 반복문 안에서 바꾸지 않는 것이 좋다.
반복 횟수를 세는 변수를 반복문 블록 안에서 바꾸면 반복 횟수를 쉽게 알 수 없다.

for (i=0; i<3; i++)
{
	printf("Be happy!\n");
    i += 2;
}

위 코드를 보면 초기식, 조건식, 증감식만 보면 printf문은 열 번 실행되어야 한다. 하지만 반복할 때마다 블록 안에서 i의 값을 증가시키므로 결국 for문은 네 번 반복된다.

이처럼 이해하기 힘든 코드를 만들 의도가 아니라면 이와 같은 방식은 피해야 한다.

📍 do ~ while문

do ~ while문은 일단 반복할 문장을 수행한 후에 조건을 검사한다.

do
{
	실행문;
} while (조건식);

여기서 중요한 점은 do ~ while문은 while문과 다르게 조건식 뒤에 반드시 ;(세미콜론)을 붙여줘야 한다.

또, while문에서는 조건을 만족해야지 실행문을 실행하므로 조건식이 거짓이면 실행문을 한 번도 실행하지 않고 바로 반복문을 빠져나갔지만, do ~ while문은 실행문이 조건식 앞에 있으므로 조건과 관계없이 실행문을 최소한 한 번은 실행한다.

이 점들을 유의하여 상황에 맞게 while문과 do ~ while문을 골라 사용해야 한다.


06-2 반복문 활용

📍중첩 반복문

중첩 반복문은 반복문 안에 실행할 문장으로 반복문이 포한된 것으로, 다중 반복문이라고도 한다.

소스 코드 6-4.c

#include <stdio.h>

int main(void)
{
	int i, j;

	for (i = 0; i < 3; i++)			// 7행
	{
		for (j = 0; j < 5; j++)		// 9행
		{
			printf("*");
		}
		printf("\n");				// 12행
	}

	return 0;
}

소스 코드를 보면 먼저 9행부터 12행까지는 별을 다섯 번 출력하는 반복문이다. 이 반복문을 7행의 for문에서 다시 세 번 반복하므로 총 열다섯 번 별이 출력된다.

중첩 반복문을 이해하는 가장 좋은 방법은 반복 과정에서 변하는 제어 변숫값과 실행 문장을 확인하는 것인데, 특히 i 값이 바뀔 때마다 안쪽 for문의 초기식에 의해 j 값이 다시 0부터 시작됨을 기억해야 한다.

또한, 중첩 반복문에서는 각 반복문이 서로 독립적인 제어 변수를 사용해야 각각 원하는 횟수를 반복할 수 있다. ( i와 j )

분기문

📍 break

break는 반복문 안에서 반복을 즉시 끝낼 때 사용한다. 모든 반복문은 조건식이 거짓일 때 반복이 끝난다. 그런데 예외적으로 반복문 중간에서 임의로 반복을 끝내고 싶을 때는 break를 사용한다.

소스 코드 6-5.c

#include <stdio.h>

int main(void)
{
	int i;
	int sum = 0;

	for (i = 1; i <= 10; i++)
	{
		sum += i;
		if (sum > 30) break;				// 11행
	}
	printf("누적한 값 : %d\n", sum);
	printf("마지막으로 더한 값 : %d\n", i);

	return 0;
}

만약 11행이 없었다면 i를 sum에 반복적으로 더하면서 1부터 10까지의 합을 구한다. 그러나 11행의 if문에서 조건을 만족하는 순간 break에 의해 반복이 중단되고 13행으로 이동해 그 때까지 누적된 값을 출력하게 된다.

그런데 break를 사용할 때는 주의할 점이 있다. break는 자신을 포함하는 반복문 하나만 벗어날 수 있다. 반복문이 중첩된 경우 가장 안쪽에서 break를 사용해 모든 반복문을 한 번에 벗어날 수 없다는 것이다. 또한 반복문 이외의 블록에서 사용하면 그 블록을 포함한 반복문을 벗어난다.

📍 continue

continue는 반복문의 일부를 건너뛴다. 반복문 안에서 continue를 사용하면 다음 실행 위치가 반복문의 블록 끝이 된다. 블록을 탈출하는 것은 아닌지만 반복문의 일부를 제외하고 실행할 수 있다. 예를 들어 3의 배수를 빼고 1부터 100까지의 합을 구할 때 효과적으로 사용할 수 있다.

for (i = 1; i <= 100; i++)
{
	if ((i % 3) == 0)
    {
    	continue;
    }
    sum += i;
}

continue에 의해서 제어가 블록의 끝으로 이동한 후에는 다시 반복이 시작된다. 결국 다음 실행 위치는 증감식이 된다.

0개의 댓글