4-2-라.다중 루프

다중 루프란 두 개 이상의 루프가 겹쳐 있는 제어 구조이다. 루프 안에는 반복의 대상이 되는 명령이 들어가는데 이 명령이 또 루프라면 이중 루프가 된다. 어떤 명령을 반복하는 동작을 또 반복하는 것이다. 그만큼 반복이란 흔한 동작이다. 다중 루프의 전형적인 예인 구구단 프로그램을 만들어 보자.

구구단이란 1단부터 9단까지가 있고 각 단의 수에 1~9까지를 곱해 그 결과를 한 행에 출력한 것이다. 즉 9행의 반복 출력이 9단까지 또 반복되는 것이다. 다음 예제는 이중 루프로 구구단을 출력하되 9단까지는 너무 길므로 3단까지만 출력하도록 했다.

 

: samdan

#include <Turboc.h>

 

void main()

{

     int i,j;

 

     for (i=1;i<=3;i=i+1) {

          for (j=1;j<=3;j=j+1) {

              printf("%d*%d=%d\n",i,j,i*j);

          }

          printf("\n");

     }

}

 

실행 결과는 다음과 같다. i와 j의 범위를 9로 늘려 주면 9단까지 제대로 출력될 것이다.

 

1*1=1

1*2=2

1*3=3

 

2*1=2

2*2=4

2*3=6

 

3*1=3

3*2=6

3*3=9

 

제어 변수의 이름으로 루프의 이름을 붙이는데 위 예제는 i루프와 j루프가 있고 j루프가 i루프에 포함되어 있다. 이 예제가 어떻게 저런 결과를 만들어 내는지 분석해 보자.

 

프로그램이 시작되면 두 개의 정수형 변수 i와 j가 선언된다. 각 루프는 서로 다른 제어 변수를 사용해야 하므로 루프의 개수만큼 제어 변수가 필요하다.

최초 i루프가 시작되며 i는 초기값 1을 가지고 1씩 증가하면서 3까지 반복될 것이다. i는 루프를 돌 때마다 1,2,3으로 변한다.

i루프의 안쪽에서 j루프가 시작되며 j도 마찬가지로 초기값 1을 가지고 1씩 증가하여 3까지 변한다.

j루프의 명령인 printf가 실행되어 i가 1이고 j가 1일 때의 곱셈 결과인 1*1=1을 출력한다. printf 명령은 i루프안의 j루프안에 위치하여 두 제어 변수의 곱셈을 출력한다.

printf가 한 번 실행된 후 j루프가 계속 돌아가 j는 2, 3이 된다. printf는 1*2=2, 1*3=3을 각각 출력할 것이다. j가 4가 되면 이 루프의 조건식 j<=3이 거짓이 되므로 j루프는 종료된다. j루프는 주어진 i변수에 대해 *1, *2, *3을 출력하는 명령이라고 생각할 수 있다.

j루프가 종료되면 j루프 다음에 있는 printf("\n")이 실행되어 빈줄 하나를 출력하고 바깥쪽의 i루프가 반복된다. i는 1에서 2로 변하며 아직 3보다 크지 않으므로 계속 루프가 실행된다.

i가 2인 상태에서 루프 안으로 들어가면 j루프의 초기식이 실행되어 1부터 다시 시작된다. j루프가 반복되면서 2*1, 2*2, 2*3이 각각 출력될 것이다.

다시 i루프로 돌아와 i는 다음값 3이 되며 3*1, 3*2, 3*3의 결과가 출력된다. 여기까지 실행한 후 i는 4가 되며 조건식 i<=3이 거짓이 되므로 i루프가 종료된다.

 

순서도를 그려 보면 좀 더 분명하게 동작을 살펴 볼 수 있을 것이다. 그림이 복잡해서 더 어려워 보일지도 모르겠지만 기본 원리는 단순 루프와 동일하다. 다만 루프안의 명령이 또 루프라는 점만 다를 뿐이다. 안쪽의 j 루프가 바깥쪽의 i루프에 걸리는 명령이라고 생각하면 쉽게 이해할 수 있다.

간단한 예제를 살펴보았으니 다음은 다중 루프에 대한 일반적인 주의 사항 및 상세 동작에 대해 연구해 보자.

 

 다중 루프란 루프가 중첩(Nesting)되어 있는 것이다. 즉, 루프안에 루프가 완전하게 포함되어 있을 때 이를 다중 루프라 한다. 단순히 루프가 계속 이어진다고 해서 다중 루프가 아니다. 다음 예를 보자.

 

for (i=...) {

}

for (j=...) {

}

 

i루프와 j루프가 있지만 j가 i에 포함되어 있지 않고 i루프 바깥에 있다. 그래서 i루프가 완전히 종료되면 j루프가 시작된다. 이것은 단순 루프가 두 개 있는 것이지 다중 루프가 아니다.

 안쪽 루프가 완전히 종료되어야 바깥쪽 루프가 한 번 반복된다. 구구단 예제의 경우 j가 1~3까지 반복되고 종료되어야 i는 다음 값을 가진다. 그래서 다중 루프에 속한 명령의 총 반복 회수는 모든 루프의 반복회수를 곱한 값이 된다. 구구단 예제는 i가 3번, j가 3번 반복되므로 printf는 총 9회 반복된다.

 각 루프의 제어 변수는 반드시 달라야 한다. 중첩된 루프들은 각각의 반복 조건이 독립적이어야 하므로 반복을 통제하는 제어 변수가 같아서는 다중 루프를 구성할 수 없다. 이유를 따로 설명하지 않아도 직감적으로 이해가 갈 것이다.

 루프 중에 제어 변수는 어느 때고 참조할 수 있다. 제어 변수는 루프의 반복을 통제할 뿐만 아니라 루프에 속한 명령들이 조금씩 다르게 실행되게 하는 역할을 한다. 반복되는 명령이 제어 변수를 참조하지 않는다면 완전히 같은 명령들만 반복해댈 것이다. 구구단 예제의 printf는 제어 변수 i와 j를 곱셈의 피연산자로 사용함으로써 9번 반복되지만 매번 출력하는 결과가 달라진다.

 

다음 예제는 2장에서 만든 것인데 * 문자로 삼각형을 출력한다. 이제 다중 루프까지 배웠으므로 이 예제를 분석해 볼 수 있다.

 

: Triangle

#include <Turboc.h>

 

void main()

{

     int i,j;

 

     for (i=1;i<=15;i=i+1) {

          for (j=0;j<i;j=j+1) {

              printf("*");

          }

          printf("\n");

     }

}

 

i,j 루프가 중첩되어 있는데 i는 1~15까지 변화하고 j는 1~i까지 변화하면서 *를 i번 출력한다. 그래서 최초 별표 하나, 다음 줄에 별표 둘, 그 다음 줄에 별표 셋이 출력되면서 삼각형이 그려지는 것이다. 동작 과정을 자세히 관찰하고 싶으면 printf문 다음에 delay() 호출을 삽입한 후 실행해 보면 된다. 보다시피 j루프의 조건식에서 바깥쪽 루프의 제어 변수인 i의 값을 참조함으로써 자신이 몇 번 반복될 것인가를 결정하는데 이 점이 바로 이 예제의 핵심이다.

 

제어 변수(i)

출력할 모양

j 반복 회수

1

*

1

2

**

2

3

***

3

4

****

4

 

안쪽 루프의 입장에서 바깥쪽 루프의 제어 변수는 주어진 환경이므로 이 값을 조건문에 사용할 수 있다. 즉 안쪽 루프의 반복 회수가 바깥쪽 루프의 제어 변수에 의해 통제되는 것이다. C는 루프 안쪽에서 제어 변수를 언제든지 참조할 수 있도록 허락하며 심지어 루프 중간에서 제어 변수를 조작하는 것도 허용하므로 이 기법을 사용하면 다양한 기교를 부릴 수도 있다. 다중 루프에 대한 실습을 하나 더 해 보도록 하자. 다음 문제를 풀어 보아라.

 

두 개의 주사위를 던질 때 두 눈의 합이 8이 되는 모든 경우의 수를 출력하라.

 

주사위 두 개를 던지므로 1~6까지 반복되어야 할 수가 두 개이다. 그래서 각각의 주사위 값에 대해 이중 루프를 돌아야 한다. 각 주사위의 값으로 두 개의 루프를 구성하여 두 값의 합이 8일 때의 값을 출력하기만 하면 된다. 결과는 다음과 같다.

 

: cubic8

#include <Turboc.h>

 

void main()

{

     int j1,j2;

 

     for (j1=1;j1<=6;j1=j1+1) {

          for (j2=1;j2<=6;j2=j2+1) {

              if (j1+j2==8) {

                   printf("값1=%d, 값2=%d\n",j1,j2);

              }

          }

     }

}

 

첫 번째 던지는 주사위의 눈을 j1이라고 하고 두 번째 던지는 주사위의 눈을 j2라고 한 후 각각을 1~6까지 반복시키면서 j1+j2가 8이 되는지 점검하여 이 조건을 만족하는 j1, j2의 쌍을 출력한다. 이 명령의 총 반복 회수는 36번이며 그 중 조건에 맞는 j1, j2값이 화면으로 출력될 것이다.

 

1=2, 값2=6

1=3, 값2=5

1=4, 값2=4

1=5, 값2=3

1=6, 값2=2

 

실제 프로그래밍에서 다중 루프는 아주 많이 사용된다. 1번 학생에서 60번 학생까지 성적을 처리한다면 루프를 구성해야 할 것이다. 그런데 이런 성적 처리를 1반~12반까지 반복해야 한다면 각 반에 대해서 각 학생에 대해서 루프를 돌아야 하므로 이중 루프가 필요하다. 또한 만약 전교 학생의 성적을 한 번에 다 처리하고자 한다면 1학년~3학년까지 다시 루프를 구성해야 하며 학급, 학생 루프는 학년 루프에 중첩되어야 하므로 이 경우는 삼중 루프가 된다. 아마 다음과 같은 모양이 될 것이다.

 

for (grade=1~3) {

     for (class=1~12) {

          for (student=1~60) {

              grade학년 class학급 student 학생의 성적 처리

          }

     }

}

 

삼중 루프 정도는 아주 흔하다. 실전에서는 7중, 8중 루프까지도 어렵지 않게 구경할 수 있다. 그러나 아무리 루프의 중첩이 많더라도 기본 원리는 동일한데 루프안의 명령이 또 루프일 뿐이다.