5-4-라.구구단 예제

앞장에서 배웠던 제어 구조와 이번 장에서 배운 연산자의 종합 실습편으로 구구단 프로그램을 작성해 보자. 1단은 제외하고 2단~9단까지만 화면에 출력하기로 하되 화면폭에 제한이 있으므로 위쪽에 2~5단, 아래쪽에 6~9단까지 출력할 것이다. 다음이 첫 번째 예제이다.

 

: gugu1

#include <Turboc.h>

 

void main()

{

     gotoxy(5,3);

     printf("2 * 1 = 2");

     gotoxy(5,4);

     printf("2 * 2 = 4");

     gotoxy(5,5);

     printf("2 * 3 = 6");

     .....

}

 

gotoxy로 적당한 위치로 이동한 후 printf로 각 계산식을 출력했다. 이런 명령을 72번 반복하면 원하는 바대로 구구단을 출력할 수 있겠지만 보다시피 극단적으로 무식한 방법을 채택했다. 이런 코드는 프로그램이라고 할 수 없다. 비슷한 명령이 계속 반복되므로 이럴 때는 당연히 루프를 사용해야 한다. 2~9단까지 출력하고 각 단에 대해 1~9까지 곱해야 하므로 2중 루프이며 반복 횟수가 미리 정해져 있기 때문에 for 루프가 가장 적합하다.

 

: gugu2

#include <Turboc.h>

 

void main()

{

     int i,j;

 

     for (i=2;i<=5;i++) {

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

              gotoxy(i*15-25,j+2);

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

          }

     }

 

     for (i=6;i<=9;i++) {

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

              gotoxy((i-4)*15-25,j+12);

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

          }

     }

}

 

2~5단까지는 위쪽에 출력해야 하고 6~9단까지는 아래쪽에 출력해야 하는데 출력할 좌표가 다르기 때문에 2중 루프가 두 번 반복되어 있다. 첫 번째 이중 루프에서 윗단을 먼저 출력하고 두 번째 이중 루프에서 아랫단을 출력한다. 각 단을 또 다른 반복으로 보고 3중 루프를 구성할 수도 있으나 이 정도 예제에 3중 루프를 동원하는 것은 별로 어울리지 않는다. 결과는 다음과 같다.

 

2 * 1 = 2      3 * 1 = 3      4 * 1 = 4      5 * 1 = 5

2 * 2 = 4      3 * 2 = 6      4 * 2 = 8      5 * 2 = 10

2 * 3 = 6      3 * 3 = 9      4 * 3 = 12     5 * 3 = 15

2 * 4 = 8      3 * 4 = 12     4 * 4 = 16     5 * 4 = 20

2 * 5 = 10     3 * 5 = 15     4 * 5 = 20     5 * 5 = 25

2 * 6 = 12     3 * 6 = 18     4 * 6 = 24     5 * 6 = 30

2 * 7 = 14     3 * 7 = 21     4 * 7 = 28     5 * 7 = 35

2 * 8 = 16     3 * 8 = 24     4 * 8 = 32     5 * 8 = 40

2 * 9 = 18     3 * 9 = 27     4 * 9 = 36     5 * 9 = 45

 

6 * 1 = 6      7 * 1 = 7      8 * 1 = 8      9 * 1 = 9

6 * 2 = 12     7 * 2 = 14     8 * 2 = 16     9 * 2 = 18

6 * 3 = 18     7 * 3 = 21     8 * 3 = 24     9 * 3 = 27

6 * 4 = 24     7 * 4 = 28     8 * 4 = 32     9 * 4 = 36

6 * 5 = 30     7 * 5 = 35     8 * 5 = 40     9 * 5 = 45

6 * 6 = 36     7 * 6 = 42     8 * 6 = 48     9 * 6 = 54

6 * 7 = 42     7 * 7 = 49     8 * 7 = 56     9 * 7 = 63

6 * 8 = 48     7 * 8 = 56     8 * 8 = 64     9 * 8 = 72

6 * 9 = 54     7 * 9 = 63     8 * 9 = 72     9 * 9 = 81

 

i루프가 단 루프이고 j루프가 단에 곱해지는 행 루프이며 i가 2~9까지 반복되고 각 i에 대해 j도 1~9까지 반복된다. 이 예제에서 조금 어려운 부분이라면 gotoxy의 좌표 계산식인 (i*15-25,j+2)인데 이 식이 어떻게 도출되었는지 생각해 보자. 이런 식을 만들 때는 원하는 출력 형태를 상상해 보거나 아니면 종이에 직접 그려 본다. 그리고 원하는 출력 위치와 제어 변수와의 관계를 관찰해 보면 위치와 제어 변수값 사이의 일차 함수를 구할 수 있다.

각 단과 행 사이의 간격을 먼저 결정해야 하는데 행 사이의 간격은 1이면 되고 단 사이의 간격은 출력문 하나의 길이에 적당한 여백을 더 주는 것이 좋다. 한 출력문은 최대 10문자이므로 5문자분만큼 여백을 주어 15만큼 간격을 띄우면 적절하다. 제어 변수에 단과 행 사이의 간격을 곱하면 되므로 i와 j로부터 출력 위치를 (i*15, j*1)로 정할 수 있다.

간격을 먼저 정하고 출력문의 좌상단을 조정한다. i는 2부터 시작하고 j는 1부터 시작하므로 이 공식대로라면 "2 * 1 = 2"의 위치가 (30,1)이 될 것이다. 이 위치는 너무 오른쪽 위로 치우쳐 있으므로 적당히 왼쪽 아래로 평행 이동시킨다. 첫 출력문을 (5,3)으로 보내기 위해 수평 좌표에서 25를 빼고(-25를 더하고) 수직 좌표에는 2를 더했다. 상단에 너무 밀착되지 않기 위해 1,2행은 빈 여백으로 남겨 두고 전체 출력문이 중앙으로 정렬되도록 하기 위해 왼쪽으로도 적당히 옮긴 것이다. 각 계산식의 출력 위치 (x,y)는 제어 변수 i, j와 다음과 같은 함수 관계를 가진다.

 

x=i*15-25

y=j*1+2

 

그래서 각 출력문의 좌표는 (i*15-25, j*1+2)로 결정된 것이되 *1은 있으나 마나이므로 생략할 수 있다. 제어 변수에 곱해지는 값은 단과 행 사이의 간격을 결정하며 더해지는 값은 출력 위치를 수평으로 이동시키는 역할을 한다. 곱하고 더하는 상수는 경험적으로 구하는데 간격이 너무 좁으면 곱하는 수를 더 크게 하고 중앙이 아니면 더해지는 수를 적당히 조정하면 된다. 제어 변수로부터 원하는 값을 찾는 것은 일종의 경험이며 몇 번만 해 보면 금방 익숙해져서 암산으로도 쉽게 구할 수 있다.

이 공식대로 2~5단까지 출력하면 각 단 사이가 15문자로 적절히 간격이 띄워진다. 그러나 6단 이후의 아랫단은 다른 공식이 필요하다. 화면폭이 제한되어 있기 때문에 6단을 5단 오른쪽에 출력할 수 없으며 2단 아래쪽에 출력해야 한다. 그래서 아랫단은 다음과 같이 출력 위치를 계산했다.

 

gotoxy((i-4)*15-25,j+12);

 

i에 4를 뺀 값을 사용하면 6단이 2단과 같아지므로 2단과 같은 수평 위치에 출력될 것이며 7단은 3단 아래, 8단은 4단 아래, 9단은 5단 아래에 출력된다. 수직 위치는 윗단보다 10문자분 더 아래쪽에 출력하도록 했다. 각 단이 9행으로 구성되어 있으므로 빈줄 하나를 고려하여 10만큼 띄우면 적당하다. 윗단과 아랫단은 출력 방법은 동일하지만 위치가 다르기 때문에 같은 루프를 쓰지 못하고 두 개의 이중 루프를 각각 돌도록 했다. 그러나 다음과 같이 하면 하나의 이중 루프로도 똑같은 출력을 할 수 있다.

 

: gugu3

#include <Turboc.h>

 

void main()

{

     int i,j;

 

     for (i=2;i<=9;i++) {

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

              if (i<=5) {

                   gotoxy(i*15-25,j+2);

              } else {

                   gotoxy((i-4)*15-25,j+12);

              }

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

          }

     }

}

 

2~5, 6~9까지 따로 돌지 않고 2~9까지 한꺼번에 돌되 출력 위치를 정할 때만 if문으로 5단 이하, 6단 이상을 따로 처리하면 된다. 반복되는 부분의 다른 부분만 조건문으로 처리했다. 코드가 조금 복잡해 보이기는 하지만 두 개의 이중 루프를 쓰는 것보다는 이 방법이 훨씬 더 깔끔하고 코드를 유지하기도 쉽다.

윗단과 아랫단의 출력 위치가 다르기 때문에 루프 내부에서 if문으로 i값을 평가하여 gotoxy의 좌표를 다르게 선택했는데 if문 대신 삼항 조건 연산자를 사용하면 하나의 gotoxy 문으로 두 문장을 통합할 수 있다.

 

: gugu4

#include <Turboc.h>

 

void main()

{

     int i,j;

 

     for (i=2;i<=9;i++) {

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

              gotoxy((i - (i>5 ? 4:0))*15-25,j+2+(i>5 ? 10:0));

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

          }

     }

}

 

먼저 수평 좌표 계산 공식을 보자. i>5 ? 4:0는 6단 이상일 때는 4가 되고 5단 이하일 때는 0이 된다. 그래서 윗단의 수평 좌표는 i*15-25가 될 것이며 아랫단의 수평 좌표는 (i-4)*15-25가 될 것이다. 결국 if문으로 i값을 평가한 것과 동일하다. 수직 좌표도 마찬가지 원리로 윗단일 때는 j+2+0이 되고 아랫단일 때는 j+2+10이 된다.

삼항 조건 연산자는 수식 내에서 바로 사용할 수 있기 때문에 if문 대신 사용할 수 있다. 관계 연산자는 평가식의 진위 여부를 1 또는 0으로 평가한다는 점을 이용하면 이 식을 더 짧게 줄일 수도 있는데 다음이 가장 짧게 줄여본 위치 지정식이다.

 

: gugu5

#include <Turboc.h>

 

void main()

{

     int i,j;

 

     for (i=2;i<=9;i++) {

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

              gotoxy((i - (i>5)*4)*15-25,j+2+(i>5)*10);

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

          }

     }

}

 

(i>5) 평가식이 i값에 따라 1 또는 0이 되는데 이 값이 1일 때 원하는만큼 값을 곱해서 수식에 바로 적용했다. 길이는 짧아졌지만 출력 결과는 첫 번째 예제와 완전히 동일하다. 두 개의 이중 루프가 하나의 조건문으로, 조건문은 다시 삼항 조건 연산자로, 이것을 다시 논리식으로 압축하는 예를 보았다. 다음 장에서 배울 매크로 함수와 쉼표 연산자를 사용하면 단 4줄로도 구구단 프로그램을 짤 수 있다.

 

: gugu6

#include <Turboc.h>

#define L(a,b,c) for (int a=b;a<=c;a++)

void main()

{L(i,2,9) L(j,1,9) gotoxy((i-(i>5)*4)*15-25,j+2+(i>5)*10),printf("%d * %d = %d",i,j,i*j);}

 

물론 동작은 완벽하지만 거의 엽기적인 수준의 압축이라고 할 수 있으며 다분히 장난기까지 섞여 있다. 이런 식으로 압축하면 1~100까지 합을 구하는 예제를 for (sum=0,i=1;i<=100;sum+=i++) {;} 한줄로 압축할 수도 있다. 코드 길이를 무조건 짧게 만드는 것이 항상 좋은 것은 아니다. 위 예제는 짧지만 코드의 의미를 해석하기가 쉽지 않으며 따라서 확장과 유지 보수에 불리해서 간결하다고 할 수는 없다. 적당한 수준에서 코드를 짧게 쓰는 것은 좋지만 가독성을 해치지 않는 범위에서 코드를 압축해야 한다. 구구단 예제의 경우 gugu2 예제는 불필요하게 루프가 두 개나 되므로 반드시 gugu3 형식으로 압축해야 하며 gugu3을 더 압축하는 것은 선택의 문제이되 마지막 예제와 같은 압축은 절대로 하지 말아야 한다.

 

 AsciiTable

아스키 코드표를 구구단처럼 여러 열로 출력하는 프로그램을 작성하라. 인쇄 가능한 32~126까지의 문자 95개에 대해서만 출력하되 한 열에 19개씩 다섯 열로 출력하면 된다. 각 문자에 대해 10진 코드, 16진 코드 그리고 문자를 출력하고 프로그램 끝에 getch 호출을 넣어 코드를 확인할 때까지 잠시 대기하기로 한다.

 BaseBall

컴퓨터가 생각한 세 자리의 숫자를 맞추는 야구 게임을 작성하시오. 난수와 사용자가 입력한 숫자는 반드시 세 자리여야 하며 각 자리수는 서로 달라야 한다는 제약이 있다. 난수와 입력수의 대응되는 자리수가 정확하게 일치하면 스트라이크이며 대응되지 않더라도 일치하는 숫자가 있을 때는 볼이다. 예를 들어 123에 대해 135는 1스트라이크 1볼이며 345에 대해 354는 1스트라이크 2볼이다. 대응되는 세 자리가 모두 같은 3스트라이크가 되면 사용자가 난수를 맞춘 것으로 한다. 컴퓨터로 난수를 만들 때는 random 함수를 사용하는데 이 함수에 대해서는 8장을 미리 참고하기 바란다.