1) 지식 창고는 본인이 작성한 콘텐츠(팁/노하우/리소스/강좌 등)을 무료 혹은 가상화폐인 납포인트를 통해 공유하는 공간입니다.
2) 본인이 작성한 콘텐츠에 대해서만 지식 창고에 등록할 수 있으며, 저작권에 위배되는 콘텐츠는 사전경고 없이 삭제될 수 있습니다.
3) 콘텐츠 구매 및 첨부파일 다운로드는 회원그룹 '연구원' 이상 가능하오니, 경험치를 쌓아 진급한 후에 이용 부탁드립니다.
4) 무료 콘텐츠의 본문은 구매절차 없이 즉시 이용할 수 있으며, 판매 납포인트가 있는 콘텐츠는 구매 후 이용할 수 있습니다.
5) 콘텐츠 판매에 따른 납포인트 수익은 지정한 비율(50%)에 따라 판매자에게 지급하며, 납포인트 수익을 통해 진급을 빨리할 수 있습니다.
6) 구매 후 평가를 하면 구매 납포인트의 20%를 돌려 드립니다.
판매자 | 프리미엄 | 판매 납포인트 | 무료 | 평점 | 0점 / 총 0명 참여 |
---|
***************************< 캠퍼스 C 강좌 >******************************
[제목] :
[코드] : campusc1-025 (초급)
[교재] : CAMPUS C (초급, Third edition) [출판사 : 책과스승]
[알림] :이 파일은 "캠퍼스 C"에서 모든 분께 공개한 "초급 강좌"입니다.
이 [알림]의 내용을 지우지 않는다면 누구에게나 임의로 복사해 줄 수
있습니다. 그러나 이 강좌 내용에 대한 저작권은 "캠퍼스 C"에 있습니다.
[연락처] : 605-8662 (서울) ("캠퍼스 C", 도서출판 "책과 스승")
천리안 : go campusc
나우콤 : go lcampc
하이텔 ID : campusc
****************************<< 목 차 >>************************************
<1> 그래픽을 이용한 실용 프로그램을 짜는 과정
0> 들어가며
1> 간단한 3차원 막대 그리기
2> 배열을 이용하여 그리기
3> 스트락춰 구조체를 이용하기
4> 막대기의 갯수를 제한없이, (위치 자동으로 결정)
5> 그래프 에는 눈금이 필요하다
6> 데이타를 비율에 맞게 수정하자
**************************< 내용 시작 >********************************
0> 들어가며
-----------------------------------------------------------------------
이번 강좌에서는 3차원 막대기를 그리는 프로그램을 이용하여, 어떤
과정을 거쳐 프로그램이 실용적으로 완성 되 나가는지를 살펴 보겠다.
아래 에서는 데이타 구조를 바꾸면서, 혹은 기능을 하나씩 첨가 시
키면서 프로그램이 완성되 나갈텐데, 각 과정을 유심히 비교 분석 하기 바란
다. 그리하여 나중에 익숙한 단계에 이르면 보다 큰 프로그램을 작성 할 수
있도록 "구상"을 할 수 있는 능력을 길러 보시길 바란다.
-----------------------------------------------------------------------
1> 간단한 3차원 막대 그리기
-----------------------------------------------------------------------
아래는 마징가의 체중을 막대 그래프로 그리는 것이다. 그런데, 이
런 사소한 과정에도 우리가 결정해야 할 사항이 무려 8 가지나 된다. 그것을
아래에 전부 "변수"로 잡아 보았다.
이것들을 전부 변수로 잡을 필요가 있는지는 지금 단계에서 결정이
안되므로 일단은 전부 변수로 취급을 하자.
3차원 막대기 하나를 그리는 진짜 함수는 사실 아래 bar3d(.......)
하나 뿐인데, 그래픽 모드를 이용하려면 아래 처럼 절차가 복잡하다.
/*-----------------------< 시작 >------------------------------------*/
#include <graphics.h> /* <-- 반드시 포함 */
/*----< 결정 사항들 >----*/
int bar_bottom = 400; /* <-- 막대기 밑바닥 좌표*/
int bar_height = 75; /* <-- 마징가 체중 */
int bar_x = 100; /* <-- 막대기 의 왼쪽 x 좌표 */
int bar_width = 50; /* <-- 막대기의 폭 */
int bar_pattern = 1; /* <-- 막대기 내부의 무늬 */
int bar_color = RED; /* <-- 막대기 내부 색깔 */
int bar_line_color = WHITE; /* <-- 막대기 경계 색깔 */
int bar_depth = 15; /* <-- 막대기 3차원 두께 */
main()
{
int graphdriver = DETECT; /* <-- 그래픽 카드 자동 결정 */
int graphmode ;
int i;
initgraph(&graphdriver, &graphmode, ""); /*<-- 그래픽 모드로 전환*/
setcolor(bar_line_color); /*<--막대기 테두리 색깔 */
setfillstyle(bar_pattern, bar_color); /* 막대기 색깔과 패턴 */
bar3d(bar_x, bar_bottom - bar_height, bar_x + bar_width,
bar_bottom,bar_depth,1); /* <-- 막대기 하나 그림 */
getch();
closegraph(); /* 그래픽 모드 끝남 */
}
/*-------------------------*/
이런 프로그램을 짤때는 함수 라이브러리를 끼고 살아야 한다. 아무
리 알기 쉬운 변수 이름을 쓰더라도 항상 혼동되기 때문이다.
위에서 이용한 변수의 의미는 [314쪽]) 패턴모양,[307쪽]) 좌표계
등을 참조 하면서 이해 하도록 하자.
이 프로그램을 잘라내서 실행 시켜보자. 아래 한글을 쓰는 경우라면
반드시 "아스키 타입" 으로 다시 저장하는 걸 잊지말자.
이 내용은 사실 지극히 간단한 것이다. 다만 프로그램이 그래픽 모
드로 바뀌는 바람에 절차가 복잡해 진 것일 뿐이다. 그러면 이제는 배열을
이용하여 여러 사람의 체중을 동시에 그려 보기로 하자.
-----------------------------------------------------------------------
2> 배열을 이용하여 그리기
-----------------------------------------------------------------------
아래 변수 에서 바뀐 부분은 아래 표시한 (<-- 요기) 뿐이다.
여기서는 무조건 3 사람의 체중만 그리기로 결정되어 있다.
/*-------------------------*/
#include <graphics.h>
int bar_bottom = 400;
int bar_height[3] = { 111, 222,333 }; /* <-- 요기 */
int bar_x[3] = { 100, 200, 300 }; /* <-- 요기 */
int bar_width;
int bar_pattern = 1;
int bar_color = RED;
int bar_line_color = WHITE;
int bar_depth = 15;
main()
{
int graphdriver = DETECT;
int graphmode ;
int i;
initgraph(&graphdriver, &graphmode, "");
setcolor(bar_line_color);
setfillstyle(bar_pattern, bar_color);
bar_width = (bar_x[1] - bar_x[0])/2; /* <-- 요기 2 */
for(i=0; i < 3; i++) { /* <-- 요기 1 */
bar3d(bar_x[i], bar_bottom - bar_height[i], bar_x[i] +
bar_width, bar_bottom, bar_depth,1);
}
getch();
closegraph();
}
/*-------------------------*/
변수형이 배열로 바뀌니까 당연히 값을 찍어 볼 때 표현법도 달라진
다. 위에서 그런 부분을 유심히 보고, 특히 (<-- 요기 1) 부분에서 for문을
사용하여 loop를 돌려 버린것을 봐야 할 필요가 있다.
그리고 (<-- 요기 2) 부분에서는 막대기의 폭을 다시 조정 했는데,
공식이 다음과 같다.
bar_width = (bar_x[1] - bar_x[0])/2;
이것은 무조건, 처음 막대기와 두번째 막대기가 떨어진 거리의 반을 "막대기
폭"으로 결정 하겠다는 얘기이다. 이렇게 해놓으면 막대기의 폭은 이른바
"자동적"으로 결정이 되는데, 이런 방법이 일반적으로 좋은 방법이다.
이유는 설명을 안해도 잘 알것이다. "자동"이 주는 편리함을 모르는
사람은 없을테니까. 역시 짤라내서 실행해 보라.
-----------------------------------------------------------------------
3> 스트락춰 구조체를 이용하기
-----------------------------------------------------------------------
위와 같이 배열이 쓰이기 시작 하면 데이타 구조를 달리할 필요를
느끼기 시작 한다. 즉 아래와 같이 배열 부분을 스트락춰를 이용하여 다시
작성 하는것이다.
스트락춰를 쓰는 이유에 대해서는 설명한 적이 있다. 결론 부터 얘
기하면 스트락춰는 안써도 아무 상관없다고 했다. 그러나 "머리속"을 정리
하려면 스트락춰를 쓰는 것이 좋은 방법이다.
아래 에서는 각 사람(로보트)들의 체중과 그것이 찍힐 위치 x 좌표
가 같이 스트락춰의 멤버로 잡혀 있다.
/*---------------< 시작 >--------------------*/
#include <graphics.h>
int bar_bottom = 400;
int bar_width;
int bar_pattern = 1;
int bar_color = RED;
int bar_line_color = WHITE;
int bar_depth = 15;
/*------------------< 구조체로 전환 >-----------------*/
struct bar {
int height, x;
} b[3] = { 111, 100,
222, 200,
333, 300 };
/*---------------------------------------------------*/
main()
{
int graphdriver = DETECT;
int graphmode ;
int i;
initgraph(&graphdriver, &graphmode, "");
setcolor(bar_line_color);
setfillstyle(bar_pattern, bar_color);
bar_width = (b[1].x - b[0].x)/2;
for(i=0; i < 3; i++) {
bar3d(b[i].x, bar_bottom - b[i].height, b[i].x +
bar_width, bar_bottom, bar_depth,1);
}
getch();
closegraph();
}
/*-------------------------*/
위의 구조체는 다음과 같은 방법으로도 많이 쓴다고 했다. 숨을 좀
쉬기 위해서 마침표로 분리한것이다.
struct bar {
int height, x;
}; <-- (요기, 마침표)
struct bar b[3] = { 111, 100,
222, 200,
333, 300 };
/*---------------------------------------------------*/
이렇게 구조체로 바꾸면 역시 표현법이 달라져야 하는 것은 당연하다. 위에
서 구조체의 표현법을 유심히 보자.
-----------------------------------------------------------------------
4> 막대기의 갯수를 제한없이, (위치 자동으로 결정)
-----------------------------------------------------------------------
여태까지는 3 개의 막대기만 그릴 수 있도록 했으나, 실용적인 프로
그램이 되기 위해서는 당연히 이런 제한이 있어서는 안된다.
따라서 아래 에서는 막대기의 갯수에 상관 없이 그래프를 그릴 수
있도록 바꿔 보자. 그러면 우리가 생각해야 할 사항은,
1. 사람의 체중이 n 개 있다 --> 100,23,45,67,70,120,........
2. 그에 따라 막대기를 그릴 x 좌표도 n 개 있어야 한다.
여기서 체중이야 어떤 숫자가 들어 올지 모르니까 어쩔 수 없지만, 막대기의
위치는 역시 자동화 시켜야 할 필요가 생길것 같다. 즉 막대기가 3개 있으면
화면의 3분의 1씩 되는 위치에 그리면 되지만, 막대기가 5가 있다면 5분의 1
씩 되는 곳에다 그려야 할 것이다. 지금 막대의 폭은 자동으로 잘 변하고 있
으므로 우리는 그것은 신경 쓸 필요가 없다.
그런 기능을 하는 루틴이 아래 (요기 1) 에 표시한
calc_x_position() 이다.
여기서 바뀐 부분은
1. 막대기가 몇개 인지 모르므로 변수 how_many_bar 를 만들고 저장을 하자.
2. 이 막대기의 크기도 자동으로 알아 보기 위해서 다음과 같이 하자.
( 이것은 스트락춰에서 배열의 크기를 알기위해 쓰는 상투적인 방법임)
how_many_bar =sizeof(b)/sizeof(struct bar);
3. 배열의 갯수를 정하지 않았다 b[3] 에서 --> b[] 으로, 따라서 앞으
로는 초기화한 데이타 갯수만큼 자동으로 결정된다.
4. 우리가 일일이 막대가 그려질 x 좌표가 필요 없으므로 무조건 0 으로 채
워 넣었다.( 얼마나 편한가 ?)
5. calc_x_position() 이라는 함수에서 자종적으로 x 의 위치에 값을 넣어
줄것이다.
/*----------------------------------------*/
#include <graphics.h>
int bar_bottom = 400;
int bar_width;
int bar_pattern = 1;
int bar_color = RED;
int bar_line_color = WHITE;
int bar_depth = 15;
int how_many_bar; /* <-- 새로운 변수 */
struct bar {
int height, x;
};
struct bar b[] = { 111, 0, /* <-- [] 숫자 생략 */
222, 0, /* x 위치에 전부 0 으로 */
333, 0,/*calc_x_position() 이 x 채울 것임 */
100, 0,
200, 0,
134, 0,
50, 0 };
main()
{
int graphdriver = DETECT;
int graphmode ;
int i;
initgraph(&graphdriver, &graphmode, "");
setcolor(bar_line_color);
setfillstyle(bar_pattern, bar_color);
how_many_bar =sizeof(b)/sizeof(struct bar); /* <-- 요기 2 */
calc_x_position(); /* <-- 요기 1 */
bar_width = (b[1].x - b[0].x)/2;
for(i=0; i < how_many_bar; i++) {
bar3d(b[i].x, bar_bottom - b[i].height, b[i].x +
bar_width, bar_bottom, bar_depth,1);
}
getch();
closegraph();
}
/*---------< sub routine >--------*/
calc_x_position()
{
int start_x = 100;
int end_x = 600;
int i;
for(i =0; i < how_many_bar; i++ ) {
b[i].x = start_x + (end_x - start_x) / how_many_bar *
i;
}
}
<숙제> 위의 스트락춰에 몸무게를 원하는 갯수 만큼 입력하고 그림이 어떻게
바뀌는지 직접 실행해 보라.
-----------------------------------------------------------------------
5> 그래프 에는 눈금이 필요하다
-----------------------------------------------------------------------
여태까지 우리가 그려본 체중은, 가령 100 이라면 정확하게 점(픽
셀)을 100개 찍은 것이었다. 이것은 체중이 200 인 사람하고 같이 그래프를
그리면 정확하게 반을 그리는 것은 틀림없다. 그러나 이것은 정확한 그림이
아니다.
아래에 draw_scale() 이란 함수를 추가 시켰는데, 이 그림은 세로로
눈금을 10등분해서 그린 그림이다.
아래를 실행 시키면 체중이 100 인 사람의 그래프가 무의미 하다는
것을 알것이다. 현재 체중과 눈금과는 아무 상관이 없기 때문이다.
따라서 우리는 체중 100 인 사람은 눈금에 맞춰 100 으로 조정을 할
필요가 생기게 된다.
그리고 지금 까지 프로그램을 해오면서 어떤 변수들은 전혀 값이 변
하지 않는 것들이 있다. 그것들을 전부 "상수"로 표현하여 #define에 정의
한 것을 보라.
"변수" 와 "상수"는 이와 같이 프로그램을 짜고 실행 시키는데는 아
무 차이가 없지만, 그러나 이른바 "질(quality)" 이라는 것이있다.
변수는 말 그대로 "변하는 값"만 넣어두는 방이다. 따라서 변하지
않는 값들은 몽땅 "상수"로 정의 해서 써야 한다.
남의 프로그램을 분석 할 때 변수와 상수가 주는 피곤함이 얼마나
틀린지 모를 것이다. 이를테면 a = PI ; 라는 문장을 보면 다음과 같이 생각
할 것이다.
1. a = 3.1415; 일것이다.
2. 파일의 제일 꼭대기에 가면 #define 으로 정의 되있을 것이다.
그러나 a = pi; 라고 된 문장을 보면
1. a = ?
2. pi 라는 변수가 최후에 변경된 곳이 어디일까 ?
하는 의문 투성이만 생긴다. 그리고 눈에 약간의 핏발을 세우고 온 프로그램
을 다 뒤지면서 pi의 변경사항을 슛아 다녀야 한다. 그런데 만일, 프로그램
내에서 pi 는 "3.14"라는 상수의 의미로 씌여지고 전혀 변화가 없다는 사실
을 알아내면, 입에서 당장 욕이 나올 것이다. "어떤 개 xx 가 프로그램을 이
따위로 짜놨어 !" " 이게 왜 상수와 변수를 구별 못하나 ?"
그런 프로그램은 더이상 읽을 가치가 없다. 따라서 여러분도 항상
대,소문자의 구별을 명확히 하도록 유의 해야 한다.
#include <graphics.h>
#define BAR_BOTTOM 450
#define SCALE_TOP 50
#define SCALE_BOTTOM BAR_BOTTOM
#define START_X 100
#define END_X 600
#define SCALE_X START_X - 20
int bar_width;
int bar_pattern = 1;
int bar_color = RED;
int bar_line_color = WHITE;
int bar_depth = 15;
int how_many_bar;
struct bar {
int height, x ;
};
struct bar b[] = { 337, 0,
100, 0,
337, 0,
100, 0,
200, 0,
1340, 0,
152, 0
};
/*===================================================*/
main()
{
int graphdriver = DETECT;
int graphmode ;
int i;
int scaled_height;
initgraph(&graphdriver, &graphmode, "");
setcolor(bar_line_color);
setfillstyle(bar_pattern, bar_color);
draw_scale(); /* <-- 요기 1 */
how_many_bar =sizeof(b)/sizeof(struct bar);
calc_x_position();
bar_width = (b[1].x - b[0].x)/2;
for(i=0; i < how_many_bar; i++) {
scaled_height = BAR_BOTTOM - (b[i].height/10. *
((SCALE_BOTTOM - SCALE_TOP)/100.));
bar3d(b[i].x, scaled_height, b[i].x + bar_width,
BAR_BOTTOM, bar_depth,1);
}
getch();
closegraph();
}
/*=======================================================*/
calc_x_position()
{
int i;
for(i =0; i < how_many_bar; i++ ) {
b[i].x = START_X + (END_X - START_X) / how_many_bar *
i;
}
}
/*=======================================================*/
draw_scale()
{
int i;
/*-----< vertical line >-------------*/
setcolor(WHITE);
line(SCALE_X, SCALE_TOP, SCALE_X , SCALE_BOTTOM);
/*-----< horizontal line >-------------*/
setcolor(WHITE);
line(SCALE_X, SCALE_BOTTOM, END_X , SCALE_BOTTOM);
for(i= 0; i < 10; i++ ) {
moveto(SCALE_X - 5, SCALE_TOP + i* (SCALE_BOTTOM -
SCALE_TOP)/10);
lineto(SCALE_X + 5, SCALE_TOP + i* (SCALE_BOTTOM -
SCALE_TOP)/10);
}
}
-----------------------------------------------------------------------
6> 데이타를 비율에 맞게 수정하자
-----------------------------------------------------------------------
아래 프로그램은 세로의 눈금 하나를 100을 의미하도록 표시하고 막
대기의 높이도 그것에 맞게 조정한 것이다. 그리고 새로운 변수
scaled_height를 하나 더 썼다. 여기에 조정된 새로운 크기가 들어가고,
bar3d()에서도 이 변수를 이용한다.
그리고, 프로그램을 짤 때는 항상 error 에 대한 대비를 잘하는 것
이 필요하다. 예를 들어 지금 이 프로그램 에서는 몸무게 1000kg 을 넘어가
면 그래프를 그릴 자리가 없다. 그런데 몸무게 자료를 입력하는 사람들이 과
연 이런 한계를 정확히 지켜 줄까 ?
또 사람 중에는 별 사람이 다 있어서 꼭 하지 말란 짓을 골라서 하
는 사람도 있다. 그런 사람을 대비해서 아래 에서는 1000 kg를 넘어가면 특
별한 조치를 취하니까 프로그램을 직접 읽어 보기 바란다.
그리고 참고로, "프로그램 테스트" 를 직업으로 하는 사람들이 있
다. 그런 사람들이 위와 같이 하면 안될 짓을 전문적으로 하면서 프로그램의
허술한 점을 찾아내는 사람인데, 그런 사람들이 기본적으로 하는 일이란 이
런 한계(limit)를 검사하는 것이다.
문제는 항상 그런것인데, 프로그램을 자기만 쓸것이라면 별 문제 없
으나, 불특정 다수가 쓰다보면 별 사람이 다 있다는 사실을 염두에 둬야 한
다.
/*--------------------------------------*/
#include <graphics.h>
#define BAR_BOTTOM 450
#define SCALE_TOP 50
#define SCALE_BOTTOM BAR_BOTTOM
#define START_X 100
#define END_X 600
#define SCALE_X START_X - 20
#define NORMAL_COLOR RED
#define OVERFLOW_COLOR YELLOW
int bar_width;
int bar_pattern = 1;
int bar_color = RED;
int bar_line_color = WHITE;
int bar_depth = 15;
int how_many_bar;
struct bar {
int height, x ;
};
struct bar b[] = { 337, 0,
100, 0,
337, 0,
100, 0,
200, 0,
1340, 0,
152, 0 };
/*===================================================*/
main()
{
int graphdriver = DETECT;
int graphmode ;
int i;
int scaled_height;
initgraph(&graphdriver, &graphmode, "");
setcolor(bar_line_color);
draw_scale();
how_many_bar =sizeof(b)/sizeof(struct bar);
calc_x_position();
bar_width = (b[1].x - b[0].x)/2;
for(i=0; i < how_many_bar; i++) {
scaled_height = BAR_BOTTOM - (b[i].height/10. *
((SCALE_BOTTOM - SCALE_TOP)/100.)); /* <-- 조정 */
bar_color = NORMAL_COLOR; /* 변화 */
if(scaled_height < SCALE_TOP) { /* 변화 */
scaled_height = SCALE_TOP; /* 변화 */
bar_color = OVERFLOW_COLOR; /* 변화 */
}
setfillstyle(bar_pattern, bar_color); /* 위치 변화 */
bar3d(b[i].x, scaled_height, b[i].x + bar_width,
BAR_BOTTOM, bar_depth,1);
}
getch();
closegraph();
}
/*=======================================================*/
calc_x_position()
{
int i;
for(i =0; i < how_many_bar; i++ ) {
b[i].x = START_X + (END_X - START_X) / how_many_bar *
i;
}
}
/*=======================================================*/
draw_scale()
{
int i;
/*-----< vertical line >-------------*/
setcolor(WHITE);
line(SCALE_X, SCALE_TOP, SCALE_X , SCALE_BOTTOM);
/*-----< horizontal line >-------------*/
setcolor(WHITE);
line(SCALE_X, SCALE_BOTTOM, END_X , SCALE_BOTTOM);
for(i= 0; i < 10; i++ ) {
moveto(SCALE_X - 5, SCALE_TOP + i* (SCALE_BOTTOM -
SCALE_TOP)/10);
lineto(SCALE_X + 5, SCALE_TOP + i* (SCALE_BOTTOM -
SCALE_TOP)/10);
}
}
위 프로그램을 실행 시켜보고 다음의 숙제를 해 보시길..
<숙제> 각 스케일 눈금에 10, 20,30,40, 따위로 숫자를 표시하라
<숙제> 각 막대기의 높이를 숫자로 "막대기 그림 아래 부분"에 화면에 표시
하라
<숙제> 위의 프로그램을 전부 파이 차트(우리말 : 빈대떡 차트)를 그리는 프
로그램으로 바꿔보라.
****************************< 끝 마치며 >******************************
수고 하셨습니다
***********************************************************************