본문 바로가기

Programming Language/basic C Language

3. 배열, 반복문 (while, for, do~while)

3. 배열, 반복문 (while, for, do~while)


1. Intro

이전 단원에서는 정보를 담는 그릇인 변수와 변수형의 종류인 자료형, 그리고 변수와 함께 사용해 여러가지 연산을 할 수 있도록 해주는 연산자에 대해 살펴보았다. 이제 우리는 아주 간단한 계산기 정도는 만들 수 있게 되었다. 하지만 우리가 기대하는 컴퓨터 프로그램은 "많은 양의 자료를" "반복적으로" 처리할 수 있어야 한다. 마리오가 점프를 단 한 번만 할 수 있다거나, 기껏 뽑아놓은 캐리어가 인터셉터를 단 한 기만 태울 수 있다면 매우 실망스러울 것이다. 마리오가 점프를 마음껏 할 수 있도록 하기 위해, 그리고 캐리어가 인터셉터를 8기 태우기 위해 필요한 개념이 바로 반복문과 배열이다.

배열을 배열답게 사용하기 위해서는 반복문의 개념을 알아야 한다. 그렇기 때문에 우선 배열에 대한 개념을 설명하고, 반복문을 설명한 다음 반복문을 사용해 배열을 활용하는 방법을 알아보도록 하겠다.


2. 배열

어떤 학생이 본인의 평점 계산기를 만들고자 한다. 그 학생은 1학점짜리 수업을 18개 들었기 때문에 다음과 같이 코드를 작성했다.

#include <stdio.h>

int main()
{
	int st1, st2, st3, st4, st5, st6, st7, st8;
	int st9, st10, st11, st12, st13, st14, st15;
	int st16, st17, st18;
	printf("각 과목의 평점을 입력해주세요\n");
	scanf("%d %d %d %d %d %d", &st1, &st2, &st3, &st4, &st5, &st6);
	scanf("%d %d %d %d %d %d", &st7, &st8, &st9, &st10, &st11, &st12);
	scanf("%d %d %d %d %d %d", &st13, &st14, &st15, &st16, &st17, &st18);
	double res = (st1 + st2 + st3 + st4 + st5 + st6 +
		st7 + st8 + st9 + st10 + st11 + st12 +
		st13 + st14 + st15 + st16 + st17 + st18) / 18;
	printf("당신의 평점은 %lf 입니다", res);
	return 0;
}

물론 위의 코드가 틀린 코드는 아니지만, 굉장히 비효율적인 코드임은 자명하다. 이것처럼 개발한다면, 수강생이 백 명 단위가 되는 일반물리학이나 미적분학 시험 평균을 내는 프로그램을 만들기 위해서는 수백 개의 변수를 일일이 선언해주어야 한다. 이런 비효율을 해결하기 위해 존재하는 것이, 여러 개의 연속적인 메모리공간을 한 번에 할당받을 수 있는 변수 덩어리배열인 것이다.


배열은 다음과 같이 선언한다.

int score[18];
[자료형] [배열 이름] [ [대괄호 열고] [배열 크기] ] [대괄호 닫고] ; [세미콜론]

위의 st1부터 st18까지 선언한 3줄의 코드를 int score[18];의 한 줄로 간단하면서 직관적으로 표현할 수 있게 되었다.


#배열의 초기화

배열은 변수의 덩어리이다. 역시 변수와 같이 선언을 하면 처음엔 쓰레기값이라는 이상한 값이 들어가있다. 코딩을 하다 보면 배열을 선언하면서 초기화할 필요가 있는 경우가 있다. 이 때는 이렇게 하면 된다.

int score[18] = {100, 100, 100, 100};
[자료형] [배열 이름] [ [대괄호 열고] [배열 크기] ] [대괄호 닫고] = { 1번칸 초기화값, 2번칸 초기화값, 3번칸 초기화값, ...}


혹 배열 크기보다 적게 초기화한다면, 자동으로 나머지 부분은 모두 0으로 바뀐다. 그래서 아래와 같이 배열을 선언하면 선언된 배열의 모든 칸이 0으로 초기화된다.

int score[18] = {0};



3. 배열의 사용

배열은 변수의 덩어리이다. 이와 동시에, 배열을 호텔, 배열에 들어갈 정보를 호텔에 묵을 손님과 같이 생각하는 것은 배열을 이해하는데 도움을 줄 것이다. 예를 들어, A라는 이름의 배열에 1번 칸에 들어있는 정보를 A라는 이름의 호텔의 1번 방에 있는 손님이라고 생각해볼 수 있다. 이를 실제 코드에서는 다음과 같이 표현한다.

A[1]
[배열 이름] [ [대괄호 열고] [방 번호] ] [대괄호 닫고]

이를 이용해 위의 코드를 다시 쓰면 아래와 같을 것이다.


#include <stdio.h>

int main()
{
        int score[18];
	printf("각 과목의 평점을 입력해주세요\n");
	scanf("%d %d %d %d %d %d", &score[0], &score[1], &score[2], &score[3], &score[4], &score[5]);
	scanf("%d %d %d %d %d %d", &score[6], &score[7], &score[8], &score[9], &score[10], &score[11]); 
	scanf("%d %d %d %d %d %d", &score[12], &score[13], &score[14], &score[15], &score[16], &score[17]); 
	double res = (score[0]+ score[1]+ score[2]+ score[3]+ score[4]+ score[5]
		+ score[6] + score[7] + score[8] + score[9] + score[10] + score[11] 
		+ score[12] + score[13] + score[14] + score[15] + score[16] + score[17] )/ 18;
	printf("당신의 평점은 %lf 입니다", res);
	return 0;
}


처음 코드보다 더 복잡해진 것 같다. 하지만 여기에 뒤에서 다룰 반복문을 끼얹으면 매우 심플한 코드로 바뀔 것이다.

여기서 주의해야 할 점이 하나 있다.

&score[0]

모든 배열의 시작 번지수는 0번이다. 1번부터 시작하지 않는다. 유럽의 빌딩에 Ground Floor가 존재하고 그 위 부터 1층으로 세는 것과 비슷하다. 이것에 주의하여야 한다. 그래서 사실

int score[18];

은 19칸의 정수형 배열을 선언한 것이다.



4. 반복문 - while

이제 우리가 기대하는 프로그램에 있어 빠질 수 없는 부분인 '반복'에 대해 살펴보자. 가장 첫 번째로 살펴볼 반복문은 while 반복문이다. 기본적인 형식은 아래와 같다.

while (조건문) {
	반복할 동작;
}

영어 단어 while은 '~하는 동안'이라는 의미를 가진다. while 반복문은 조건문이 참인 동안 동작을 반복하는 것이 특징이다. 몇 가지 예제를 통해 사용 방법을 살펴보자.



예제 1. 1부터 10까지의 정수를 차례대로 출력하는 코드를 while을 사용해 작성하시오.

문제가 요구하는 것을 살펴보자.

1. 출력하는 수는 정수이다.

2. 1부터 10까지 1씩 증가한다.

3. while문을 사용하여야 한다.

우선 출력하는 수가 정수이기 때문에 정수형 변수를 선언하여야 할 것이다. 1부터 10까지 1씩 증가하기 때문에 +연산자, += 복합대입연산자, 혹은 ++ 증가연산자 중 하나를 써야 할 것이다. 마지막으로 while을 사용해야 하기 때문에, while문이 반복할 조건을 설정해야 한다. 우선 조건문은 남겨두고, 할 수 있는 부분부터 작성해보자.

#include <stdio.h>

int main()
{
	int number = 1;
	while (/*조건문*/) {
		printf("%d ", number);
		number++;
	}
	return 0;
}

대부분 이와 비슷한 코드를 작성했을 것이다. 중요한 것은 /*조건문*/ 부분에 작성될 조건문이다. 이런 문제풀이에 대한 가장 기본적인 접근 방식은, 변하는 수가 무엇인가를 찾아보는 것이다. while 문 내부에서 계속 변하는 수는 number이고, number는 1씩 증가한다. 그리고 우리는 number이 11이 되면 while문이 끝나길 바란다. 즉, number이 10보다 작거나 같은 값일때만 while문이 작동했으면 좋겠다. 이를 관계연산자를 통해 표현하면 아래와 같다.

number <= 10

이를 위의 코드와 합치면

#include <stdio.h>

int main()
{
	int number = 1;
	while (number <= 10) {
		printf("%d ", number);
		number++;
	}
	return 0;
}

와 같이 완성된 코드를 만들 수 있다.



예제 2. 2부터 20까지의 2의 배수를 출력하는 코드를 while문을 사용해 작성하세요.


이것은 위의 예제와 매우 흡사하기 때문에, 길게 설명하지 않고, 코드만 제시하겠다.

#include <stdio.h>

int main()
{
	int number = 2;
	while (number <= 20) {
		printf("%d ", number);
		number += 2;
	}
	return 0;
}


#무한반복문

while의 특징은 조건문이 만족할 때 까지 계속해서 반복한다는 것이다. 이를 이용해 무한반복문을 만들 수 있다.

while (true) {
	반복할 동작;
        if(반복문 종료 조건){
              break;
        }
}

무한 반복문은 말 그대로 무한히 반복하는 반복문이다. true는 참을 의미하는 c 언어 예약어이다. 조건문이 항상 참이기 때문에 계속 반복하는데, 주의해야 할 것은, 반복문이 종료될 조건이 반드시 있어야 한다는 점이다. 이것을 뒤에서 다룰 조건문 if와, 반복문을 중단하는 명령어인 break와 함께 사용해 위와 같이 표현한다.


사족으로, 요즘은 IDE가 좋아져서 종료 조건이 없는 무한반복문을 찾아내 주의를 띄워준다.



5. 반복문 - for

영어 단어 for은 while과 비슷하게 '동안'의 의미를 가지지만, while과는 달리 기간이 정해져 있는, 예를 들어 '2시간 동안'과 같은 같은 느낌을 준다. for 반복문 또한 영어 단어와 비슷하게 시작점과 끝점이 정해진 반복을 한다. 기본 구문은 다음과 같다.

for (초기식; 조건식; 증감식) {
	반복할 내용;
}

초기식은 시작점을 의미하고, 조건식은 끝점을 의미한다. 증감식은 초기식이나 조건식에 사용될 변수의 증가 또는 감소에 대한 내용을 의미한다. 예제를 이용해 살펴보자.



예제 1. 1부터 10까지의 정수를 차례로 출력하는 코드를 for문을 사용해 작성하시오

#include <stdio.h>

int main()
{
	for (int i = 1; i <= 10; i++) {
		printf("%d ", i);
	}
	return 0;
}

초기식은 "int i=1;" 이다. i라는 변수를 선언한 후, i값이 1부터 시작함을 의미한다.

조건식은 "i <= 10;" 이다. 변수 i의 값이 10보다 작거나 같은 동안 반복함을 의미한다.

증감식은 "i++"이다. 변수 i의 값을 한 번 반복할 때마다 1씩 증가함을 의미한다.



예제 2. 2부터 20까지의 정수를 차례로 출력하는 코드를 for문을 사용해 작성하시오

#include <stdio.h>

int main()
{
	for (int i = 2; i <= 20; i+=2) {
		printf("%d ", i);
	}
	return 0;
}

초기식은 "int i=2;" 이다. i라는 변수를 선언한 후, i값이 2부터 시작함을 의미한다.

조건식은 "i <= 20;" 이다. 변수 i의 값이 20보다 작거나 같은 동안 반복함을 의미한다.

증감식은 "i+=2"이다. 변수 i의 값을 한 번 반복할 때마다 2씩 증가함을 의미한다.


책이나 다른 블로그를 보면 대부분 for문 내부의 변수 이름이 i이다. 정확히 왜 그런지는 모르겠지만, 아마 iterator(반복자, 반복하는 녀석)의 머릿글자를 따온 것이라고 생각한다.




6. 반복문 do~while


개인적으로 c언어의 반복문은 while과 for만 알아도 충분하다고 생각하기 때문에 do~while은 간략하게 설명하고 넘어가겠다.

do~while의 기본적인 구조는 다음과 같다.

do {
       반복할 동작;
} while(조건식)

do~while은 다른 반복문과 같이 조건식이 참인 동안 do 내부의 동작을 반복한다. 다만 다른 반복문과 다른 점은, 먼저 do 내부의 내용을 실행하고 조건식을 확인한다는 점이다. 그렇기 때문에 적어도 1번은 do 내부의 동작이 실행된다.




7. 반복문과 배열의 결합


다시 처음의 상황으로 돌아가서, 학생의 코드에 반복문을 끼얹어보자. 우선, 0번째부터 17번째까지 배열의 공간에 입력을 받아야 하므로 for문을 사용하는 것이 좋을 것 같다. 그럼 코드를 다음과 같이 수정할 수 있겠다.

#include <stdio.h>

int main()
{
	int score[18];
	printf("각 과목의 평점을 입력해주세요\n");
	for (int i = 0; i < 18; i++) {
		scanf("%d", &score[i]);
	}
	double res = (score[0]+ score[1]+ score[2]+ score[3]+ score[4]+ score[5]
		+ score[6] + score[7] + score[8] + score[9] + score[10] + score[11] 
		+ score[12] + score[13] + score[14] + score[15] + score[16] + score[17] )/ 18;
	printf("당신의 평점은 %lf 입니다", res);
	return 0;
}

res를 구하는 부분도 for문을 사용해 표현할 수 있을 것 같다. 이것까지 같이 수정하면 다음과 같은 코드를 만들 수 있다.

#include <stdio.h>

int main()
{        
        int score[18];
	printf("각 과목의 평점을 입력해주세요\n");
	for (int i = 0; i < 18; i++) {
		scanf("%d", &score[i]);
	}
	double res = 0;
	for (int i = 0; i < 18; i++) {
		res += (double)score[i];   // 형 변환
	}
	res /= 18;
	printf("당신의 평점은 %lf 입니다", res);
	return 0;
}

확실히 맨 처음보다 간단해졌다. 여기서 잠깐 캐스팅에 대해 살펴보고 지나가자.

캐스팅은 정보의 자료형을 바꾸는 것을 의미한다. 이때, 크기가 작은 자료형의 값을 크기가 큰 자료형의 값으로 바꾸는 것은 암시적 형변환이라 하며 크게 문제가 되지 않는다. 하지만, 그 의 상황에서는 정보의 손실이 발생할 수 있다. 이를 그림으로 나타내면 아래와 같다.

암시적 변환과 형 변환, 자료 출처 : 코딩 도장

사진의 출처는 코딩도장(https://dojang.io/mod/page/view.php?id=493)으로, 여러가지 좋은 내용이 많으니 더 공부하고 싶은 사람에게 추천하는 사이트이다. 그래서

res += (double)score[i];

위 식은 score[i]값을 double형으로 바꾼 후 res에 더하겠다는 의미를 가진다.


그런데 위 코드를 가만히 살펴보니 겹치는 부분이 있다.

for (int i = 0; i < 18; i++) {
	scanf("%d", &score[i]);
}
for (int i = 0; i < 18; i++) {
	res += (double)score[i];   
}

입력 받는 부분과 더하는 부분이 살짝 겹치는 것 같다. 코드를 보기 좋게 하기 위해 일부러 이렇게 나눌 수도 있지만, 아래와 같이 더 간결하게 표현할 수도 있다.

#include <stdio.h>

int main()
{        
        int score[18];
        double res = 0;
	printf("각 과목의 평점을 입력해주세요\n");
	for (int i = 0; i < 18; i++) {
		scanf("%d", &score[i]);
                res += (double)score[i];
	}
	res /= 18;
	printf("당신의 평점은 %lf 입니다", res);
	return 0;
}

이렇게 반복문과 배열을 이용하면, 많은 수의 데이터를 효율적으로 처리할 수 있다.


예제를 통해 반복문과 배열을 사용한 데이터 처리에 대해 좀더 살펴보자.



예제 1. 주사위를 10번 던진 결과를 입력받아, 어떤 눈이 몇 번 나왔는지를 출력하는 코드를 작성하시오.


문제를 뜯어보자. 우선 요구되는 사항은

1. 10개의 정수를 입력받는다.

2. 주사위의 눈은 1부터 6까지이다.

3. 우리가 원하는 것은 각 눈의 면이 나온 횟수를 모두 출력하는 것이다.

일단 가장 직관적으로는 다음과 같은 코드를 작성할 수 있다.

#include <stdio.h>

int main()
{
	int dice[10];
	int count[6];
	for (int i = 0; i < 10; i++) {
		scanf("%d", &dice[i]);
	}
	for (int i = 0; i < 10; i++) {
		if (dice[i] == 1) {
			count[1] += 1;
		}
		else if (dice[i] == 2) {
			count[2] += 1;
		}
		else if (dice[i] == 3) {
			count[3] += 1;
		}
		else if (dice[i] == 4) {
			count[4] += 1;
		}
		else if (dice[i] == 5) {
			count[5] += 1;
		}
		else{
			count[6] += 1;
		}
        }
	for (i = 0; i < 6; i++) {
		printf("%d : %d", i, count[i]);
	}
	return 0;
}

썩 보기에 좋은 코드는 아닌 것 같다. 자세히 살펴보면, dice[i]의 값과 +1해줄 count 배열의 번지값이 같다. dice[i]가 1이면 count[1]에 1을 더해주고 dice[i]가 2면 count[2]에 1을 더해주는 식이다. 결론적으로 1을 더해줘야 할 곳은 count의 dice[i]번째 칸, 즉, count[dice[i]]인 것이다. 이를 반영해 코드를 작성하면

#include <stdio.h>

int main()
{
	int dice[10];
	int count[6];
	for (int i = 0; i < 10; i++) {
		scanf("%d", &dice[i]);
	}
	for (int i = 0; i < 10; i++) {
		count[dice[i]]++;
	}
	for (int i = 0; i < 6; i++) {
		printf("%d : %d", i, count[i]);
	}
	return 0;
}

와 같이 쓸 수 있다. 이러한 count 배열을 흔히 체크배열이라 부른다. 자주 사용하는 기법이니 잘 숙지하길 바란다.



8. 다중반복문


지금까지는 1차원 반복만 살펴보았다. 그러니까 수직선의 왼쪽(감소)와 오른쪽(증가)만 살펴본 것이다. 이제 이것을 2차원, 혹은 그것보다 더 높은 차원으로 확장해보자.


[1]. 2차원 배열과 이중 반복

2차원 배열은 행과 열을 가지는, 1차원 배열들의 집합이다. 일반적으로 표, 행렬, 테트리스나 스네이크 게임의 맵 등을 표현하기 위해 사용한다. 그 선언은 아래와 같이 한다.

int twoDimenArray[100][100]={{1,2,3}, {4,5,6}, {7,8,9}, ...};
[자료형][배열 이름][/*배열 크기(행)*/][/*배열 크기(열)*/] = {{/*1행 초기값*/}, {/*2행 초기값*/}, {/*3행 초기값*/}, ...};

행과 열이 충분히 헷갈릴 수 있으므로 다음 그림을 첨부한다.

2차원 배열의 n번째 행의 m번째 열은 다음과 같이 쓸 수 있다.

twoDimenArray[n][m]

이러한 2차원 배열을 반복을 이용해 값을 넣어보자. 아래 방향으로 증가해나가는 쪽 반복과, 오른쪽 방향으로 증가해나가는 쪽 반복 총 2개의 반복이 필요하기 때문에 이중 반복이라고 한다. 일반적으로 구조는 아래와 같다.

for(int i=0; i<n; i++){ for(int j=0; j<m; j++){ twoDimenArray[i][j] = 집어넣을 값; } }

다음 예제를 풀어보자.


예제 1. 5*5 크기의 2차원 배열을 선언한 후, n행 m열의 값을 n*m으로 채운 후 배열을 전체 출력하시오.


요구사항은 다음과 같다.

1. 5*5 크기의 2차원 배열을 선언해야 한다.

2. 2차원 배열의 이름을 d라고 하면, d[n][m]의 값은 n*m이다.

3. 모든 연산이 끝난 후, 배열의 전체를 출력해야 한다.

이를 작성하면 아래와 같다.

#include <stdio.h>

int main()
{
	int d[4][4];
	int i, j;
	for (i = 0; i < 5; i++) {
		for (j = 0; j < 5; j++) {
			d[i][j] = i * j;
		}
	}
	for (i = 0; i < 5; i++) {
		for (j = 0; j < 5; j++) {
			printf("%d ", d[i][j]);
		}
		printf("\n");
	}
	return 0;
}

i를 0부터 4까지, 각 i에 대해 j를 0부터 4까지 돌면서 

d의 i행 j열의 값을 i*j로 채웠다.


3차원, 4차원으로의 확장은 1차원에서 2차원으로 간 것과 동일하게 하면 된다. 다만 높은 차원의 배열을 사용하는 것은 상당히 비효율적일 것이다.



지금까지 많은 값을 다루기 위한 개념인 배열과, 반복을 위한 개념인 반복문을 살펴보고 둘을 사용해 문제를 해결해보았다. 이제 다음 단원인 조건문만 살펴보면 c언어의 기본의 기본을 다 살펴보는 것이고 본인이 원하는 간단한 코드는 충분히 작성할 수 있을 것이라 예상된다. 그 다음부터는 코드를 보다 직관적이고 멋있게 해주는 함수와 구조체로 넘어갈 것이다.

'Programming Language > basic C Language' 카테고리의 다른 글

5. 함수  (0) 2018.10.06
4. 제어문 - if, else if, else, switch~case  (0) 2018.10.06
2. 변수와 자료형과 연산자  (0) 2018.10.06
1. 표준입출력  (0) 2018.10.06
0. 개발 환경 구축하기  (0) 2018.10.06