1) 지식 창고는 본인이 작성한 콘텐츠(팁/노하우/리소스/강좌 등)을 무료 혹은 가상화폐인 납포인트를 통해 공유하는 공간입니다.
2) 본인이 작성한 콘텐츠에 대해서만 지식 창고에 등록할 수 있으며, 저작권에 위배되는 콘텐츠는 사전경고 없이 삭제될 수 있습니다.
3) 콘텐츠 구매 및 첨부파일 다운로드는 회원그룹 '연구원' 이상 가능하오니, 경험치를 쌓아 진급한 후에 이용 부탁드립니다.
4) 무료 콘텐츠의 본문은 구매절차 없이 즉시 이용할 수 있으며, 판매 납포인트가 있는 콘텐츠는 구매 후 이용할 수 있습니다.
5) 콘텐츠 판매에 따른 납포인트 수익은 지정한 비율(50%)에 따라 판매자에게 지급하며, 납포인트 수익을 통해 진급을 빨리할 수 있습니다.
6) 구매 후 평가를 하면 구매 납포인트의 20%를 돌려 드립니다.
판매자 | 프리미엄 | 판매 납포인트 | 무료 | 평점 | 0점 / 총 0명 참여 |
---|
***************************< 캠퍼스 C 강좌 >******************************
[제목] :
[코드] : campusc1-014 (초급)
[교재] : CAMPUS C (초급, Third edition) [출판사 : 책과스승]
[알림] :이 파일은 "캠퍼스 C"에서 모든 분께 공개한 "초급 강좌"입니다.
이 [알림]의 내용을 지우지 않는다면 누구에게나 임의로 복사해 줄 수
있습니다.그러나 이 강좌 내용에 대한 저작권은 "캠퍼스 C"에 있습니다.
[연락처] : 605-8662 (서울) ("캠퍼스 C", 도서출판 "책과 스승")
천리안 : go campusc
나우콤 : go lcampc
하이텔 ID : campusc
****************************<< 목 차 >>************************************
<1> 포인터의 종류
1> 보통 포인터 (char *, int *, .....)
2> void 형 포인터
3> null 포인터
<2> 함수 "미사일을 쏴라 그리고 보고하라"의 실상.
1> C 함수의 일반적 얘기
2> 함수 작성을 위한 사소한 원칙
3> 미사일 혹은 화살을 쏴라(call by value, call by reference)
4> "보고 하라"(return)
5> 함수머리만 먼저 보여주자(prototype 의 선언)
**************************< 내용 시작 >********************************
<1> 포인터의 종류
1> 보통 포인터 (char *, int *, .....)
2> void 형 포인터
3> null 포인터
***********************************************************************
1> 보통 포인터 (char *, int *, .....)
-----------------------------------------------------------------------
위에서 포인터의 종류를 3가지로 나누고 그 첫번째가 보통 포인터
인데, 이건 내가 억지로 나눠본 것에 불과하다. 사실은 뒤에 나오는 2번
void포인터와 3번 null 포인터를 설명하려다 보니 그 나머지가 "보통 포인
터"로 분류가 되어 버렸다. 이것들은 우리가 익히 알고 있는 다음과 같은것
이다.
char *ma_dang1;
int *ma_dang2;
long *ma_dang3;
main()
{
}
이제는 여러분께서도 포인터에 대해서 충분한 이해가 있으리라고 생각된다.
하지만 포인터의 표현법은 앞으로도 점점 더 어려워지고, 또 계속 나온다는
사실을 아시고 일단은 지금까지의 내용만이라도 확실히 아셔야 한다.
보통 포인터란 위와 같이 "보통 데이타 타입인 char, int, long 등
을 가리키는 포인터를 의미한다. 이제는 위와 같은 포인터를 보면 금새 다음
과 같은 생각을 떠올릴 수 있을것이다.
-------------------------------------------------------
1. 마당발1 은 char 형 변수만 가리키겠구나 (지당한 말씀)
2. 마당발2 은 int 형 변수만 가리키겠구나 (지당한 말씀)
3. 마당발3 은 long 형 변수만 가리키겠구나 (지당한 말씀)
-------------------------------------------------------
따라서 다음과 같은 생각도 반사적으로 할것이다.
------------------------------------------------------------
1. 마당발1 은 "1 증가(++)" 하면 1 바이트씩 뛰겠구나 (offset)
2. 마당발2 은 "1 증가(++)" 하면 2 바이트씩 뛰겠구나 (offset)
3. 마당발3 은 "1 증가(++)" 하면 4 바이트씩 뛰겠구나 (offset)
------------------------------------------------------------
이 자세한 내용은 [180쪽]부터 시작되는 "포인터의 증가" 편을
참조 하세요.
-----------------------------------------------------------------------
2> void 형 포인터
-----------------------------------------------------------------------
그런데 C 에서는 이런 포인터가 있습니다.
void *ma_dang4;
main()
{
}
즉 char, int, long 따위가 들어갈 자리에 "void"가 들어간 것인데
[55쪽] 상단 표를 보세요. void 라는 예약어가 있는데 이것이 지금
우리가 공부할 것입니다.
void는 여기 말고도 함수에서 다음과 같이 쓰인다고 했다.
rambo(void) 이건 그냥 "쏴라"에 해당하는 함수 호출법이다. 즉 "미사일을
쏴라"고 하면 rambo(missile) 이런식으로 표현하지만, "아무것도 없을 때"는
그냥 rambo(void)라는것이다. void란 낱말의 뜻은 정확하게 "텅 빈, ... 없
는" 따위의 뜻이다. 함수 호출 할 때는 단어의 뜻 그대로 정확하게 쓰고
있다.
그러나 void형 포인터는 어찌 생각하면 의미가 조금 다르다. 즉 "
아무것도 가리키지 않는다" 라는 뜻이 아니고,
--------------------------------------
"아무 데이타 타입이나 가리킬 수 있다 "
--------------------------------------
는 뜻이다. 어찌 생각하면 뜻이 정 반대 같은데, "텅 비어 있으면 충만하다"
고한 위의 "마당4 선생"이 얘기 한것 처럼 "아무것도 안 가리키면 모든것을
가리킬 수 있다"는 뜻인지도 모르겠다. 좌우간 우리가 잘 아는 사람을 등장
시켜 사용법을 봅니다.
char rambo[] = "abcde";
int lim_chungha[] = { 11,22,33 };
void *ma_dang4;
main()
{
ma_dang4 = rambo; /* ? = char */
ma_dang4 = lim_chungha; /* ? = int */
}
시소 놀이의 관점에서 보면, 대입 연산자 " = " 의 양쪽은 서로 데이타 타
입이 맞아야 된다고 했다. 그러나 void 형은 아무 타입이나 가리킬 수 있다
고 했으므로
-------------------------------------
"무조건 상대방 쪽에 맞춰 준다 "
------------------------------------
따라서 위와 같은 표현은 정상이다.물론 이걸 가시적으로, 캐스트 연산자를
사용하여 다음과 같이 써도 좋다.
main()
{
ma_dang4 = (void *)rambo; /* void = void */
ma_dang4 = (void *)lim_chungha; /* void = void */
}
어떤가 시소가 균형이 딱 잡힌것 같지 않은가요 ? 아주 보기 좋다.
그리고 아직도 기억 하시겠지만, 위에서 rambo는 는 &rambo[0]으로
서 배열 rambo[]의 첫 주소로서 "기준점(베이스 캠프)"에 해당한다.
따라서 위의 배열 rambo 를 100호 부터 시작한다고 하고,
lim_chungha는 200호 부터 시작 한다면 다음과 같이 써도 결과는 똑같다.
main()
{
ma_dang4 = (void *)100; /* void = void */
ma_dang4 = (void *)200; /* void = void */
}
혹시 혼동 하실까봐 사족을 달면 어떤 변수에 숫자를 대입하면 그것은 "값"
을 대입한다는 의미이다. 번지 수를 의미하려면 반드시 숫자 앞에 캐스트
연산자를 사용하여 casting(변환)을 해 주어야 한다.즉
main()
{
int i;
void *ma_dang;
i = 100; <--- o (값 100 대입)
ma_dang = 100 <--- x (틀린 표현)
ma_dang = (void *)100 <--- o (주소 대입)
}
cast 연산자는 사용 할 때 주의 할 점이 많지만, 언제나 그렇듯이
기본적으로 이해를 하면 모든 문제는 "정말" 간단하다. 예를 들어 다음과 같
은 사용법은 틀린다.
int lim_chungha[] = { 11,22,33 };
void *ma_dang4;
main()
{
ma_dang4 = (void *)lim_chungha;
ma_dang4++;
}
이유는 무엇인가 ? 아시다시피 ++가 몇 바이트만큼 증가하 라는 것인지
void형 포인터는 알지 못한다. 따라서 다음과 같이 캐스트 연산자로, I.Q.
zero인 컴파일러에게 자세히 알려줘야 한다.
main()
{
((int *)ma_dang4)++;
}
순식간에 ()가 두겹이나 쌓였는데, 하나씩 벗겨 보면 어려운 일이 아니다.
지난번에도 양파껍질은 겉에서 부터 하나씩 벗기지만,
----------------------------------
C 에서 괄호()는 안에서 부터 벗긴다
----------------------------------
고 했다.
이걸 안쪽 부터 말로 해보면
1. "마당4를 int 형 포인터로 변환한다"
2. 그리고 "증가한다"
이렇게 하면 증가(++)는 2 바이트 만큼 이다 라는 것을 컴파일러가 알 수
있다.이와 같은 원리에 입각하여 공식을 적어보면
----------------------------< 공 식 >----------------------------------
1. void 포인터에서 ++ 는 반드시 캐스트 연산자를 써라 (당연)
2. // + 1 도 // // (쓸데없는 설명)
3. // * (값을 찍으라는 표현) // (찍을 바이트수 지정필
요)
-----------------------------------------------------------------------
위의 공식을 내용 이해없이 외운다는 것은 의미가 없다는 것을 아실것이다.
<숙제> 다음 값들을 직접 찍어보라
char rambo[] = "abcde";
void *ma_dang4;
main()
{
int i;
ma_dang4 = rambo;
printf("%c", *(char *)ma_dang4);
printf("%c", *((char *)ma_dang4 + 1);
printf("%c", *((char *)ma_dang4 + 2);
printf("%c", *((char *)ma_dang4 + 3);
printf("%c", *((char *)ma_dang4 + 4);
/*--------------------------------------*/
ma_dang4 = rambo;
for(i=0; i < 5; i++)
printf("%c", *((char *)ma_dang4 + i);
/*--------------------------------------*/
printf("%c", *(char *)ma_dang4);
((int *)ma_dang4)++;
printf("%c", *(char *)ma_dang4);
((int *)ma_dang4)++;
printf("%c", *(char *)ma_dang4);
((int *)ma_dang4)++;
/*--------------------------------------*/
}
이렇게 "void 형 포인터"의 값을 반환하는 함수들이 [383쪽]
"1.14 자료검사 함수"중에서 bsearch(), lfind(), lsearch() 등입니다.
지금은 일단 함수의 선언된 형태만 살펴보고 "void 형 포인터"라는 걸 확인
해 보세요.
-----------------------------------------------------------------------
3> null 포인터
-----------------------------------------------------------------------
null 포인터는 간단하다. 이것은 그냥 약속으로서 외우기만 하면 된
다.즉
-------------------------------------------------
자기 자신이 0x00을 갖고 있으면 그게 null 포인터다
-------------------------------------------------
이건 사령관이 "쏴라 그리고 포인터 값으로 보고해"라고 명령했을 때 예를
들어 총알이 떨어져서 못쏘면 "문제가 생겨서 못쐈습니다"라고 에러가 났음
을 보고할 때 사용한다. 아시다시피 이 NULL이란 ASCII 코드 첫번째 글자
를 의미하고 헤더파일 <stdio.h> 에 NULL이라고 매크로 정의되있다.
***********************************************************************
<2> 함수 "미사일을 쏴라 그리고 보고하라"의 실상.
***********************************************************************
1> C 함수의 일반적 얘기
------------------------
우리는 알게 모르게 함수에 익숙해져 있다.여기서 정식으로 함수에
관한 것을 언급합니다.
함수라는 것은 일을 할 수 있는 단위라고 했다. 우리가 요즘 예제로
쓰고있는 프로그램중에 draw_box() 같은 것이 함수를 어떻게 쓰는가에 대한
좋은 예다.여러분이 다 느끼고 있을 것이므로 더이상 언급은 안하고 일반적
인 얘기를 하겠다. 함수는
1. 커다란 프로그램을 내용별로 분할하여 제작하는것을 가능케 해준다.
2. 프로그램을 설계 제작할때 하향식(Top-down)방법으로 모듈화된 프로그램
설계가 가능하다.
3. 자세한 작업이나 연산등을 함수의 내부에 숨기고 우리는 함수이름만 불
러쓰면 되므로 간결한 프로그램을 제작 할 수 있을 뿐 아니라 프로
그램을 읽고 수정하기 쉽게 해준다.
위 말이 상당히 문학적이지만 이제는 어느정도 이해하리라고 생각한다. 실
제로 프로그램을 짜게 되면 Top-down, Bottom-up 이란 얘기가 많이 나온다.
용어가 생소하지만 내용은 별게 아니다. 아무리 초보자 라도 상식이 있으
면 다 이런식으로 프로그램 을 짠다.
먼저 설계는 top-down방식으로 한다. 전쟁을 비유로 들면, 총사령관
이 육해공군의 전략을 세운다. 아래로 내려와서 육군은 군단,사단,연대,대
대,중대, 소대,분대,분대원 까지의 전략을 점검한다. 이와같이 위에서 부터
아래로 어떻게 전쟁을 수행할 것인가 디자인 해나가야 한다.
그러나 실제로 코딩(coding)(프로그램을 실제로 짜서 쳐넣는 것)에
들어가면 작업은 bottom-up 방식으로 밑에서부터 작성해 올라온다. 가장작
은 부분이 제대로 수행 되도록 완벽하게 짜놔야 그 위에서 이 함수를 불러서
쓸 수가 있는 것이다.
이렇게 함수들을 한부분씩 완성해 놓고 위에서 취합을 하면 우리의
프로그램은 어느새 아주 크고 복잡한 기능을 하는 훌륭한 작품이 되는 것이
다.
---------------------------------
2> 함수 작성을 위한 사소한 원칙들
---------------------------------
1.목에다 비수를 꽂지 마라.
다음에서 잘못된 곳은 어딘가 ?
main()
{
sub();
}
sub(); <-- 여기
{
}
실제로 처음 짜는 사람들은 이런 일을 왕왕 저지른다.함수를 짤때(병사 하나
만들 때) 함수머리 와 몸통사이에 비수를 꽂지마라.
------------------------------------------------------------
3> 미사일 혹은 화살을 쏴라(call by value, call by reference)
------------------------------------------------------------
총사령관이 람보에게 명령을 내릴 때
main()
{
rambo(arrow,missile);
}
이렇게 구체적으로 무기를 명시 할때가 있었다. 이 무기 이름들을 C
에서는 매개변수(parameter)라고 부른다. 말 그대로 총사령관과 람보 사이에
서 매개체로 전달되는 것인데 정확하게는 총사령관이 람보에게 미사일 과
화살을 확 집어던져 주면서 명령을 내리는 것이다.
"이걸 가지고 가서 쏴라" 그러면 람보는 "받아가지고" 가서 쏘는 것이다. 여
기서 이 매개변수를 주는 방법이 두가지가 있다.
1. 첫번째는 진짜로 화살을 던져주며 명령을 내린다.
2. 둘째 미사일 같은 것은 던져주기에는 너무 무거우니까 대신 미사일이
있는 장소의 주소를 적은 쪽지를 던져주며 "쏴라" 이다.
람보는 상당히 꾀가 많으므로 그 쪽지가 의미하는게 무엇인지 안다.즉 그 장
소에 가보면 미사일이 있다는 것을 안다. 자 이것 말고 또 parameter를 넘
겨 주는 방법이 있을까 ? 없다.아주 간단하다. 이걸 C 에서는 영어로 어렵게
쓴다.
1.call by value
---------------
번역을 하면 "값에 의한 호출" 이라는 뜻이다. 위 1번 직접 물건을
던져 주는 것이다.
2. call by reference
--------------------
(참조에 의한 호출) 번역이 이상하지만 영어로는 뜻을 이해할 수
있 을 것이다.물건이 있는 곳(reference)에 의한 호출 이다.이건 2번 방
식이다. 여기서 호출이란 얘기가 나오는 이유는 총사령관이 람보 를 "
부르기" 때문이다.
이제 조금 더 실제적으로 예를 들자. 지난번 임청하, 마당발의 집을
기억하라. 이제 총사령관이 임청하를 목욕시키라고 욕탕병사를 부른다. 여기
에서는 던져주지는 않고 살살 넘겨주는데 역시 두가지가 있다
1. 임청하를 직접 넘겨 준다 (call by value)
2. 임청하의 주소를 넘겨준다. (call by reference)
위 1 을 C 로 표현하면 다음과 같다.
main()
{
int lim_chungha;
lim_chungha = 0x1234;
shower(lim_chungha); <-- 임청하를 넘겨준다(call by value)
}
shower(int xxx) <-- 임청하를 받는다
{
printf("this woman is %d\n", xxx);
}
여기서 결과는 무엇인가 ? 지금부터 넘겨주고 넘겨받는 모습을 잘
봐야 한다. 내용을 기본적으로 이해 하고 있으면 잘 볼 필요도 없지만 나중
에 이 표현법이 조금 복잡하게 변할 수 있으므로 확실히 기억하자.
1. 먼저 총사령관이 욕탕병을 부르면서 임청하를 확 던져 주는게 보이는가 ?
2. 둘째 shower() 라는 욕탕 안으로 임청하가 들어 오는게 보이시는가 ?
잠깐 여기서 환상을 깨셔야 한다. 여기서 중요한게 있다.
<중요 1>
욕탕안으로 들어오는 모습을 여러분은 어떻게 상상할 지 모르겠으나 C에
서는 아주 멋대로 취급한다.
지금 우리가 shower() 함수안에서 문을 쳐다 보고 있다고 치자. 누
가 들어오는가 ? 그건 더이상 이쁜 임청하가 아니다. 그 이름은 영화 "에어리
언(alien)"의 괴물"ugly_alien" 일수도 있고 자기만의 천사 "my_angel" 일
수도 있다. 이 이름은 자기 맘대로 짓는 것이다. 나는 여기서 그냥 xxx
라고 이름 지어주었다. 그 표시가 shower(int xxx) 이다. 다시 말해서 사령
관이 던져줄 때는 누가 욕탕안으로 들어갔는지 알지만, 일단 들어오고 나면
(내부에서는) 누구든지 그 이름은 그냥 xxx 로 바뀌고 목욕하고 나가는 것
일 뿐이다.
군대를 갔다오신 분은 이런 경우를 잘아실 것이다. 그 지긋지긋한 "
유격 훈련장"의 시스템이 이것과 똑같다. 또 혹시 감옥에 같다오신 분도 잘
아실 것이다. 이 두 곳에서는 절대 사람 이름을 불러주지 않는다. 그냥 "야
345번 이리와 목욕해 !" 이런식이다.
즉 아무리 왕년의 대통령이었다 할지라도, 일단 이 울타리안으로만 들어오면
자기의 본래 이름은 사라지고 감옥따위의 내부에서 붙여준 명칭으로 바뀌어서
취급된다. 이게 매개변수(parameter)를 취급하는 방법이다.
<중요 2>
여기서 "int" 대신 "가수"라고 쓰자. 그러면 shower(가수 xxx)라고 욕탕
간판이 달린 셈인데 이름이 얘기하듯 가수가 아니면 이 목욕탕에 들어와서
는 안된다는 얘기다.
즉 int 가 아니면 들어오지 말라는 것이다. 따라서 다음은 틀린 경우이다
main()
{
int lim_chungha, *ma_dangbal;
ma_dangbal = &lim_chungha;
shower(lim_chungha); <-- int 타입
}
shower(char xxx) <-- char 타입
{
printf("this woman is %d\n", xxx);
}
틀린 부분이 보이시는지 모르겠다 ? 이 곳에 표시를 해두기로 하
자. C++ 에서는 허용을 하지만 C 에서는 똑같은 이름의 함수를 쓰지 못하게
한다. 따라서 목욕탕을 여러개 만들고 싶으면
main()
{
int lim_chungha, *ma_dangbal;
ma_dangbal = &lim_chungha;
shower_x(lim_chungha);
}
shower(char xxx)
{
printf("this woman is %d\n", xxx);
}
shower_x(int xxx)
{
printf("this woman is %d\n", xxx);
}
이와같이 가수용, 탈렌트용 욕탕을 따로 따로 만들고 정확하게 찾
아 들어 가도록 해야 한다.
이제 2번 임청하의 주소를 넘겨주는 (call by reference) 방식은 어떻게 표
현 되는지 보자.
main()
{
int lim_chungha, *ma_dangbal;
lim_chungha = 0x1234;
ma_dangbal = &lim_chungha; /* <--- 주소대입 */
shower(&lim_chungha); /* <-- 방법 1 */
shower(ma_dangbal); /* <-- 방법 2 */
}
shower(int *xxx)
{
printf("this woman is %d\n",*xxx);
}
위의 "방법 1", "방법 2"는 똑같다는 것을 알것이다. 위의 "<---
주소대입" 의 연산자 "="가 양쪽의 값이 같다는걸 보여준다.
넘겨주는 과정을 설명하면
1.사령관이 임청하의 주소를 넘겨주었고
2. shower()에서도 그 주소를 찾아가서 임청하를 데려왔다
결과는 아래 두개가 똑같은 걸 확인하라.
1. 임청하를 직접 넘겨 준다 (call by value)
2. 임청하의 주소를 넘겨준다. (call by reference)
이 내용이 정리된 것이 [276-278쪽]이다.
----------------------
4> "보고 하라"(return)
----------------------
사령관이 "적의 수를 세어보고 보고하라"고 명령을 하면 return문으로
대답 한다.
main()
{
int count;
count = how_many_enemy(); /* <--- 대입 문 */
}
how_many_enemy()
{
int xxx = 12345;
return(xxx);
}
여기서 주의 할 것은 위에 표시 한 바와 같이 "<--- 대입문 "이다.
대입문은 "=" 을 중심으로 양쪽의 타입이 같아야 한다고 했다. 보고를 받는
count 는 int 타입이다. 따라서 당연히 보고를 하는 return()도 int 타
입의 값을 보고해야 한다. xxx 가 int type 임을 눈여겨 봐야 한다.
사실은 여태 얘기를 안했지만 함수 앞에는 이 return 값의 type을
적기로 되어 있다.
그래서 위의 프로그램을 정식으로 다시 적어보면
main()
{
int count;
count = how_many_enemy(); /* (1) int */
}
int how_many_enemy() /* <-- 여기 (2) int */
{
int xxx = 12345;
return(xxx); /* (3) int */
}
위에 표시한 바와 같이 (1) (2) (3) 의 type이 전부 같아야 한다.(중요)
함수명 앞에 return type을 명시하지 않으면 default 로 int type 이다.
main()
{
sub1();
sub2();
}
sub1() <--- int sub1() 의 준말
{
}
sub2() <--- int sub2() 의 준말
{
}
그리고 return의 표기법은 아래처럼 ()가 있듯 없든 똑같은데 시각적
안정감을 위해 보통은 () 를 사용한다. 식이 복잡해지면 그 의미가 더 명료
해진다
sub()
{
return 100;
return(100); /* <--- 안정감 */
return(100*2 + 4) /* <--- 안정감 */
}
-----------------------------------------------