시작...

블로그 이미지
mutjin

Article Category

분류 전체보기 (148)
기록 (3)
개발새발 (8)
2010년 이전 글 (133)

Recent Post

Recent Comment

Recent Trackback

Calendar

«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

Archive

My Link

  • Total
  • Today
  • Yesterday
http://romeo.hufs.ac.kr/~dondek
안녕하십니까?C를 공부하면서 가장 힘들었고 실제적으로 사용하는데 있어서 가장 문제가많았던 포인터에 대해서 강좌를 시작해보려 하는 심상돈 입니다. 포인터 강좌를 시작하기 전에 염두해 두어야할 몇가지를 적어보겠습니다.포인터를 공부하기 전에 충분한 C의 문법을 익혀야 합니다.이 글을 읽는 분들은 숙제 때문에 가입한 회원이 아닐것이라는 가정하에어느정도는 스스로 코딩을 해보고 프로그램을 짜봤지만,포인터에 대해서 정확한 개념이 없어서 고생을 하실것이라 생각합니다. 포인터를 공부하기 전에,얼마만큼 C를 알려고 노력했는지 스스로 생각해 보시기 바랍니다.C가 만능이라는 말을 하고 싶은건 아닙니다.C가 다른 언어를 배우기 위한 기본적인 것이다라고 하고싶은건 더더욱 아닙니다.왜 C를 선택했는지 대부분의 배우시는 분들은 잘 모를것입니다.하지만,일단 C언어라는 것을 시작했으면 걱정마시고 C라는 넘을 자신의 부하로 삼으세여.여러분이 시키는 일을 전혀 군말 없이 해 줄 수 있는 그런 충성스런 부하로 말이져.그런 부하로 만들기 위해서 C가 가지고 있는 능력중 가장 뛰어난 포인터를알아야 합니다.자.... 서두는 그냥 이정도로만 하고 포인터 강좌로 들어가겠습니다.1. 변수에 대해서변수란 무엇일까여?어떤 값을 저장하는 것 정도로 알고 계실 겁니다.훌륭한 대답이며 정답입니다.그럼 변수를 하나만 만들어 보고 그 변수의 특성과 성질을 알아봅시다.
intnumber =1000;
자 number 라는 이름의 int 형 변수가 만들어 지고 그 안에 1000을 넣었습니다. 여기서 잠깐!!int 형에 대해서 자칫 지나칠 수 있을지 모르는 것에 대해서 언급을 하겠습니다.int 형은 2바이트 또는 4바이트 어떤 것일 수도 있습니다.그것은 운영체제나 컴파일러에 따라서 다릅니다.운영체제가 16비트 체제이면 int 형은 16비트(2바이트)입니다.그런 운영체제로는 DOS가 있습니다. Windows 95, 98을 사용한다 하더라도,DOS 창에서 Borland 계열의 컴파일러(Turbo-C, Borland-C)를 사용하면int 는 2바이트 입니다.운영체제가 32비트 체제이면 int 형은 32비트(4바이트)입니다.이런 운영체제로는 Windows NT, 2000, Unix, Linux등등이 있습니다.이 운영체제 에서는 int가 4바이트 입니다.여기서 포인터 역시 마찬가지 입니다.포인터의 크기(sizeof연산자로 확인 해보세여) 역시 int의 크기와 동일합니다.물론 이것은 16, 32비트 체제에서만 그렇고 64비트 체제의 운영체제에서는아직 정확히 표준화 되지 않았으므로 그냥 넘어갑니다.자...그러면 위에서 number 라는 변수가 어디에 생성이 되고 어떻게 이루어져 있는지알아봅시다.0x2000 +---+---+---+---+ | | | 10|00 | number +---+---+---+---+ 네모칸 하나가 한 바이트라 가정하고, 32비트 운영체제라고 가정하에 설명을하겠습니다. 그래서 int가 4바이트로 되어 있져?number 라는 변수를 선언했을때 우리는 위와 같은 모습을 상상하면 됩니다.number 라는 변수의 메모리상에서 번지값은 0x2000 이라고 가정하겠습니다. :)그리고 number는 그 번지를 참조할 수 있는 이름이며 그 안에는 1000 이라는 값이들어가 있습니다.자... 그러면 우리는 이 변수를 사용해야 할 것입니다.변수를 만들었으면 그 변수를 사용할 수 있어야 합니다.number 변수를 사용할 수 있는 방법은 2가지가 있습니다. number -> 1000&number -> 0x2000그냥 number라고 사용을 하면 그 메모리 안에 들어있는 값을 말합니다. 그래서 1000 이 되는 것이고,앞에 & 이 붙어있는 것은 number 라는 변수의 메모리 번지를 말하는 것입니다.즉 0x2000 이라는 주소값을 나타내는 것이져..그럼 실제로 다음과 같은 코드를 작성해서 테스트를 해보자구여..
#include<stdio.h>intmain(void){intnumber =1000;    printf(" number =%d\n", number );    printf("&number =%d\n", (int)&number );/*makes compiler happy :) */return0;}
컴파일 해서 실행을 해보면 number = 1000&number = 37813784이렇게 나오네여..제 컴에서..물론 &number 의 값은 컴파일 하는 환경마다 다 다를 수 있습니다.어쨌든 number 라는 변수를 이용할 때 이 2가지의 값을 이용할 수가 있다는것을 말하려고 하는 거니까여... 자... 그러면 이제는 포인터 변수를 하나 만들어 봅시다.
int*p = &number;
위의 number 변수를 이용해서 p라는 포인터 변수에 number 변수의 번지를 저장합니다.그럼 p라는 포인터 변수도 number 처럼 그림으로 나타내 봅시다.0x3000 +---+---+---+---+ | 0x2000 | *p +---+---+---+---+위와 같이 p라는 포인터 변수가 0x3000 번지에 만들어 지고 그 안에 number의주소값인 0x2000 이란 값이 저장되어 있습니다.이번엔 p라는 변수를 사용하는 방법이 몇개가 있을까여?일반 변수는 2가지였지만 이 포인터 변수는 3가지 입니다. p -> 0x2000&p -&gt; 0x3000*p -> 1000그냥 p만 사용하면 p라는 변수 안에 있는 값 자체를 뜻하므로 number의 번지값인0x2000 이 됩니다.&p는 p의 번지값을 말하는 것이져? 그래서 0x3000 이 되는 거져...마지막으로 *p는 무엇을 뜻할까여?*(p) 이렇게 생각을 하시고, *(0x2000) 이렇게 됩니다. 여기서 *은 "거기로 가라"라는 뜻입니다. 즉 0x2000 번지로 가서 그 값을 읽어라... 이렇게 되는 거져..0x2000 번지로 가면 무엇이 있져? 네 number 의 값인 1000 이 있습니다.어라.....이해가 힘드나여?그럼 number와 p를 한번에 그림으로 나타내보져...0x2000 0x3000 +---+---+---+---+ +---+---+---+---+ | 1000 | number | 0x2000 | *p +---+---+---+---+ +---+---+---+---+ number -&gt; 1000 p -> 0x2000 &number -&gt; 0x2000 &p -&gt; 0x3000 *p -> 1000이 그림을 머리 속에다가 확실히 입력해 두시기 바랍니다.이 그림만 확실히 외우고 있으면 어떤 포인터가 나오더라도 이 그림에서벗어나질 않습니다.그럼 지금까지 배운 것을 코드로 짜보고 확인도 해볼까여?
#include<stdio.h>intmain(void){intnumber =1000;int*p = &number;    printf(" number =%d\n", number );    printf("&number =%d\n", (int)&number );    printf(" p      =%d\n", (int)p );    printf("&p      =%d\n", (int)&p );    printf("*p      =%d\n", *p );return0;}
위의 소스를 컴파일 해서 실행 시켜보면.. 다음과 같은 결과가 나옵니다. number = 1000&number = 37813784 p = 37813784&p = 37813780*p = 1000물론 주소값들은 컴퓨터 마다 다를 수 있습니다.위의 코드를 그대로 복사하지 마시고 그대로 보고 치세여..직접 쳐서 실행 해보는게 중요합니다. :)첫번째 강좌는 이정도로 마치겠습니다.내일은 이 강좌에 이어서 포인터의 개념에 대해서 조금더 설명하도록하겠습니다.==========================================================================
포인터 2번째 강좌입니다.첫번째 강좌에서는 아주 간단하게 변수에 대해서 알아보았습니다.이번에도 역시 첫번째 강좌에 이어서 포인터를 사용하는 방법에 대해서좀더 자세하게 알아보겠습니다.포인터에 대한 많은 강좌들을 보면 너무 어려운 표현들에 대해서 설명을 하려고노력을 하는 모습을 많이 봐왔는데 그러한 어려운 표현들도 결국은 첫번째강좌에서 설명한 그림을 토대로 하나씩 따라가다 보면 전혀 어려울게 없다는 것을꼭 명심하기 바랍니다.그럼 2번째 강좌를 시작하겠습니다.첫번째 강좌에서 예로 들었던 변수들을 다시 한번 적어보겠습니다.intnumber =1000;int*p = &number;위와 같은 변수들이 선언되어 있을때 어떤 값을 갖는지도 다시 한번 써보겠습니다.0x2000                          0x3000  +---+---+---+---+                +---+---+---+---+  |        1000   | number         |       0x2000  | *p  +---+---+---+---+                +---+---+---+---+  number -> 1000                       p -> 0x2000 &number -&gt; 0x2000                    &p -&gt; 0x3000                                      *p -> 1000           자..위의 그림을 잘 기억하라고 했었지여?이번엔 이중 포인터 변수를 하나 선언해 보겠습니다.int**t = &p;이렇게 t라는 더블 포인터 변수를 선언하고 p라는 포인터의 번지값을 저장했습니다.그럼 이 t라는 더블 포인터 변수를 그림으로 다시 나타내 보겠습니다.0x2000                     0x3000                0x4000  +---+---+---+---+         +---+---+---+---+      +---+---+---+---+  |        1000   | number  |       0x2000  | *p   |       0x3000  | **t  +---+---+---+---+         +---+---+---+---+      +---+---+---+---+ number -> 1000               p -> 0x2000           t -> 0x3000&number -&gt; 0x2000            &p -&gt; 0x3000          &t -&gt; 0x4000                             *p -> 1000            *t -> 0x2000                                                   **t -> 1000           여기에서 *t의 값은 *(0x3000)이 되겠져? 0x3000 번지로 가서 그 값을 읽으라는 것이니까 0x3000 번지에 가면 0x2000 이라는 값이 있습니다. 그래서 *t의 값은 0x2000 이 되는 겁니다.이번엔 **t의 값을 따져 보져..*(*(t)) 이렇게 됩니다. 따라서 *(0x2000)이 되고 역시 0x2000번지로 가서 그 값을읽어 보면 1000 이 있져? 따라서 **t의 값은 1000 이 되는 것입니다.이렇게 *(별표)는 거기로 가라...라는 뜻입니다. 그래서 가리키다라는 뜻의 pointer라는 단어를 사용 하는 거져..나중에 구조체에서 -> 가 나오게 되는데 이것 역시 "거기로 가라"는 뜻입니다.이것에 관해서는 나중에 다시 자세하게 설명하겠습니다.이렇게 해서 몇중 포인터 이든지 위의 그림에서 설명 했듯이 하나하나 따라가 보면어렵지 않게 포인터가 가리키는 값을 알아낼 수 있습니다.그렇다면 과연 이 포인터는 왜 사용을 하는 것일까여?그냥 변수의 이름만으로도 충분히 변수를 사용할 수 있는데 왜 머리아프게포인터라는 것을 사용해서 어지럽게 변수를 사용하는 걸까여?그래서 다음장은 포인터의 쓰임에 관해서 설명하겠습니다.2. 포인터의 쓰임포인터가 가장 많이 사용되는 곳은 아마도 문자열일 것입니다.문자열은 그 자체가 포인터이며 배열과도 많이 혼용해서 사용됩니다.하지만 문자열은 알고 있는 것보다도 더 심오한 내용이 숨어있습니다.그래서 첫번째로 문자열에 대해서 알아보도록 하겠습니다.
printf("Hello, world!\n");
위의 코드는 아주 많이 봐왔을 것입니다.Hello, world! 라는 문자열을 화면에 출력하는(stdout에 출력하는) 것입니다.하지만 이 문장을 정확하게 해석을 할 줄 알아야 합니다."Hello, world!\n" 는 문자열입니다. "(큰 따옴표;double quote)로 둘러싸인 문자들을 문자열이라고 하지여..여기서 " " 이 하는 일을 알아봅시다." " 이 하는 일은그 안에 있는 문자들을 RAM의 DATA영역에 Read only 속성을 가지고 저장을 합니다. 그리고 저장된 영역의 첫번째 주소값을 리턴해줍니다.그래서 printf("Hello, world!\n"); 이 문장은,다음의 그림과 같이 되는 것입니다. 0x1234 (RAM의 DATA영역) +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |'H' 'e' 'l' 'l' 'o' ',' ' ' 'w' 'o' 'r' 'l' 'd' '!' \n \0 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ printf( 0x1234 );이렇게 되는 거지여... 그러면 printf() 함수의 인자가 숫자값이 되네여? -_-;그렇다면 printf() 함수의 원형(prototype)을 알아봅시다.intprintf(constchar*format, ... );이렇게 됩니다. 즉, printf()함수의 첫번째 인자가 바로 포인터인 것입니다.그래서 printf()함수가 내부적으로 하는 일을 간략하게 적으면,첫번째 인자로 들어온 주소값으로 이동해서 그 안에 있는 값을 아스키코드로 출력을 하고, 다음 주소로 이동을 한 다음 또 찍고... 이것을 반복하다가이동한 주소에 0(NULL)이 오면 문자를 출력하는 것을 중단하고나서 지금까지 출력한 문자의 갯수를 리턴해주는 것이 바로 printf() 함수입니다.위의 그림대로 하자면,0x1234 번지로 이동을 해서 그 안에 'H' 이 있져?그래서 H를 출력하고 다음 번지인 0x1235로 이동하겠져..그 안에 있는 'e' 를 출력하고... 이것을 반복하다가 마지막에 있는 '\0'(NULL)을 만나게 되어서 출력하는 것을 중단하게 되는 겁니다.그러면 이해를 돕기 위해서 간단한 코드를 작성해 봅시다.
#include<stdio.h>intmain(void){    printf("address =%d\n", (int)"Hello, world");return0;}
위의 코드를 컴파일 하여 실행을 해보니,address = 4198464이렇게 나오네여.. 출력된 값은 컴퓨터 마다 다를 수 있습니다.중요한건 문자열 자체가 어떤 값이라는 거져.. 위에서 말했져?RAM의 DATA 영역에 문자열을 저장하고 그 첫 번지 값을 리턴한다..그리고 그 문자열이 저장된 영역은 Read only 속성을 갖는다.그래서 임의로 값을 바꾸려 하면 segmentation fault 를 일으킵니다.문자열을 사용할때 printf("Hello"); 처럼 함수의 인자에 곧바로 문자열을 사용하기도하지만 포인터 변수를 사용할 때도 많이 있져?전에는 이것때문에 많이 헷갈렸을지도 모르지만 이제는 그렇지 않아도 됩니다." "가 하는 일에 대해서 위에서 공부를 했자나여..다음의 예제를 봅시다.
#include<stdio.h>intmain(void){char*s ="Hello, world!\n";    printf( s );return0;}
위의 예제를 컴파일 해서 실행시켜 보지 않아도 결과가 보이져?차근차근 분석해 봅시다.char*s ="Hello, world!\n";s 라는 포인터 변수를 선언하고 "Hello, world!\n" 이라는 문자열을 RAM의 DATA 영역에 저장한 다음에 그 첫번째 주소값을 리턴하여 s에 그 주소값을 대입하라. 이거져?그리고 printf( s ); 이렇게 하면 printf() 함수는 s가 갖고 있는 그 주소로이동을 하고 화면에 출력을 하게 되는 겁니다.조금 변형을 해서 다음과 같이 해도 됩니다.
#include<stdio.h>intmain(void){char*s =NULL;    s ="Hello, world!\n";    printf( s );return0;}
전의 것과는 다른게 없습니다.다만 문자열이 주소값을 리턴한다는 사실을 정확히 알고 있으면 포인터 변수에문자열을 대입하는 것 같은 모양의 코드를 사용한다 하더라도 실제적으로 그 포인터에값이 들어가는게 아니라 RAM의 DATA 영역에 문자열이 저장이 되고 그 주소값만포인터에 대입한다는 것을 알수 있으니까여..이번 강좌는 이정도로 마치겠습니다.다음 강좌는 이 강좌에 이어서 문자열에 대해서 계속 하겠습니다.
============================================================================
 
포인터 3번째 강좌입니다.두번째 강좌에서는 문자열에 대해서 알아보았습니다.두번째 강좌에 이어서 문자열에 대해서 자세하게 알아보도록 하겠습니다.문자열을 사용하기 위해서 선언한 포인터에 대해서 자세히 알아봅시다.char*s ="Hello, world!\n";char *s; 에서 *s 앞에 붙은 char 라는 것은 무엇을 의미 할까여?첫번째 강좌에서는 int *p; 를 사용했는데 *p 앞에 붙은 int는 무슨 의미??포인터 변수 앞에 붙은 type은 포인터가 가리키고 있는 곳에 저장된 값의 type을뜻하는 것입니다.int *p; 에서는 p가 가리키고 있는 곳에 저장되어 있는 값이 int 형이라는 뜻이고,char *s; 에서는 s가 가리키고 있는 곳에 저장되어 있는 값이 char 형이라는 뜻이져.문자열은 문자들의 집합이라는 것은 다들 아실테고,문자를 char 형으로 사용하는 이유도 아실테지만 노파심에 다시 한번 설명을 하져.char 형은 어떤 운영체제이든지 상관 없이 1바이트 입니다.1바이트는 8비트. 따라서 char 형의 범위는 -128 ~ 127 입니다.unsigned char 형으로 하면 0 ~ 255 가 되져..각 컴퓨터마다 아주 조금씩은 다를 수 있지만 0 ~ 127 번까지의 아스키 코드는공통으로 사용됩니다. 따라서 char 형의 양의 정수 범위 만큼은 어떤 컴퓨터라도공통적인 아스키 코드를 사용한다는 말이져.그래서 0 ~ 127 번까지의 아스키 코드 테이블에는 영문자와 숫자 특수 문자들이선언되어 있고 char 형의 변수를 이용해서 문자들을 다룰 수 있습니다.물론 int, long 형으로도 다룰 수 있지만 쓸데없는 메모리 낭비가 되겠져?가장 작은 단위인 char 형으로 문자를 훌륭하게 다룰 수 있습니다.다시 포인터로 넘어가서,문자열을 사용하는데 있어서 자칫 실수 하기 쉬운 것에 대해서 알아보져.
#include<stdio.h>intmain(void){char*s ="Hello, world!\n";    *s ='T';    printf( s );return0;}
위의 코드를 컴파일 하고 실행하기 전에 분석부터 해봅시다.char*s ="Hello, world!\n";이제 지겹져? -_-; 같은게 계속 나와서...그럼 그 다음 줄..*s ='T';이것은 무슨 뜻일까여?s의 주소로 가서(*이 있기 때문에 그 주소로 가야합니다.) 'T' 값을 저장해라.이런 뜻이져?설마 'T' 가 무슨 뜻인지 모르지는 않겠지만..'T' 는 T의 아스키 코드값을 뜻합니다.즉, s가 가리키는 첫 주소에 있는 값은 무엇이져?네.. 'H' 져? 문자열의 첫번째 주소를 저장하고 있을테니까여..그런데 그 'H' 라는 값을 'T'로 바꾸고자 위의 코드를 쓴겁니다.그리고 printf( s ); 로 출력..문법에는 전혀 문제가 없습니다.하지만 컴파일하고 실행을 해보면 문제가 발생합니다." "로 둘러싸인 문자열이 저장되는 곳이 어디라고 했었져?RAM의 DATA 영역이라고 했었져? 근데 중요한건 " "로 둘러싸인 문자열을 저장한그곳은 Read only 영역입니다. DATA 영역이 read only 영역이라는 것이 아니라" "로 둘러싸인 문자열을 저장한 그곳은 Read only 속성을 가지고 있습니다.따라서 쓰기를 하게 되면 에러가 발생합니다.'H'를 'T'로 바꾸기 위해서 *s = 'T'; 라는 코드를 썼었져?읽기 속성인 곳에 'T'를 쓰려고 했기 때문에 실행시에 에러가 발생하는 겁니다.이것이 " "로 둘러싸인 문자열의 특성입니다.자.. 그렇다면 바꿀 수 있는 문자열을 선언하는 방법은 없을까여?바로 배열을 이용하면 가능합니다.다음의 코드를 봅시다.
#include<stdio.h>intmain(void){chars[] ="Hello, world!\n";    *s ='T';    printf( s );return0;}
지금의 코드와 바로 전의 코드와 다른점은 char *s -> char s[] 이렇게 바뀐 겁니다.이렇게 배열을 사용하게 되었을때 어떻게 되는 건지 자세하게 알아봅시다.chars[] ="Hello, world!\n";여기에서 s는 크기가 정해져 있지 않은 배열입니다." "으로 둘러싸인 문자열은 위에서 설명한 것 처럼 일을 하고나서,s라는 배열을 동적으로 문자열의 크기만큼 잡은다음에 read only 속성을 갖고 있는문자열을 s 배열에 복사를 합니다. 즉, 문자열이 2번 저장되게 되는 거져.read only 속성을 갖는 문자열과 read/write 다 가능한 s 배열에 있는 문자열..위의 두가지 문자열 모두 DATA 영역에 저장이 되는 것이기는 하지만,s 배열의 속성은read/write이고, " " 문자열은read only입니다.s 배열에 대해서 조금 더 알아보져..위의 자료들을 그림으로 자세하게 나타내 보겠습니다. 0x1234 (RAM의 DATA영역 read only 속성) +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |'H' 'e' 'l' 'l' 'o' ',' ' ' 'w' 'o' 'r' 'l' 'd' '!' \n \0 | " " 문자열 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0x2000 (RAM의 DATA영역) +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |'H' 'e' 'l' 'l' 'o' ',' ' ' 'w' 'o' 'r' 'l' 'd' '!' \n \0 | s[] +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+위의 문자열은 " " 로 둘러싸인 문자열이고아래의 문자열은 s배열에 복사된 문자열 입니다.여기서 s라는 배열의 이름을 그대로 사용을 하면 s는 포인터가 됩니다.즉,s -> 0x2000이렇게 된다는 겁니다.이 뜻은 배열 s를 포인터로도 사용할 수 있다는 거져.사실 포인터와 배열은 뗄 수 없는 관계에 있지만 더 자세한 배열과 포인터와의관계는 나중에 다시 설명하도록 하겠습니다.자.. 그렇다면 *s 의 값은 얼마일까여?*(0x2000) 이 되져?0x2000 번지에는 'H' 값이 있져? 그러므로 *s 의 값은 'H' 가 됩니다.다시 소스 코드로 돌아가서 *s ='T';이렇게 하면 s 배열의 첫번지에 'T'값을 넣으라는 것이 되겠져?s배열은 분명히 read/write 가 다 가능하므로 이 코드를 컴파일하고 실행하면H가 T로 바뀌어서 출력되는 모습을 볼 수 있습니다.위에서는 배열의 크기가 정해져 있지 않은 동적인 배열을 이용했지만,실제적으로는 배열의 크기를 정해주는 경우가 많습니다.하지만 포인터와 배열의 약간 다른 이해가 필요하다는 것을 보여주기 위해서다음과 같은 코드를 예로 들어보겠습니다.char*s =NULL;s ="Hello, world!\n"; ----- 1chars[15];s ="Hello, world!\n"; ----- 21번의 경우는 포인터 변수에 문자열이 저장된 곳의 첫번지 값을 저장하는 코드이고,2번의 경우는 배열을 잡아 놓고 그 배열의 이름 s가 포인터라 했으니 그 포인터에문자열이 저장된 곳의 첫번지 값을 저장하는 코드라 생각하고 한건데...1번은 전혀 문제가 없는 코드입니다.하지만 2번은 문제가 있는 코드입니다. 왜냐면 배열의 이름을 가지고 나타내는포인터는 읽기만 가능합니다. 쓰기는 안되는 겁니다.s라는 배열은 예를 들어서 0x3000 번지에 할당이 되었다고 가정하면, s -> 0x3000&s -&gt; 0x3000이렇게 됩니다. s 라는 이름을 사용한 포인터나 &s 가 나타내는 값이나 같게됩니다. 결국은 &s[0]; 이것과도 같은 의미가 됩니다.이런 것들은 변하는 것들이 아닙니다. 따라서 포인터 변수와 배열의 이름을 사용해서 나타내는 포인터와는 꼭 구분을 할 줄 알아야 합니다.문자열에서 배열을 사용하지 않고 왜 포인터를 사용할까여?배열을 사용하면 읽기와 쓰기도 다 가능한데 왜 포인터를 사용하는 걸까여?그렇다면 그 이유를 간단하게 보여줄 수 있는 코드를 하나 작성해 보져..
#include<stdio.h>intmain(void){charbuffer[256];    printf("Input string: ");    fgets( buffer,256,stdin);    printf( buffer );return0;}
위의 코드는 256개의 문자를 저장할 수 있는 buffer라는 배열을 잡아서,사용자로부터 문자열을 입력받아서 buffer에 저장한 다음에 printf() 함수로출력을 하는 겁니다.여기서 배열의 크기를 256으로 잡았는데,사용자가 얼만큼의 문자열을 넣을지 모르므로 가능한 크게 잡아놓은 거지여.위의 예제에서 buffer를 포인터로 선언하고 문자열을 저장하기 위해서 동적 메모리할당을 이용하여 저장을 하는 것은 오히려 메모리 낭비가 되지만..저장해야 할 문자열이 많다면 상황은 달라집니다.예를 들어서,charbuffer[100][256];이렇게 배열을 선언해서 사용자에게 100 개의 문자열을 입력 받는다고 생각하면,사용자가 256개의 문자를 다 사용하지는 않을테져..한글자를 넣을 수도 있고, 10개를 넣을 수도 있고..그렇다면 256개의 배열을 잡아놓았던 것이 낭비가 되는 겁니다.그런것이 쌓이고 쌓이면 엄청난 메모리가 낭비가 됩니다.그렇다고 배열을 작게만 잡을 수도 없져.charbuffer[100][20];이렇게 잡아놨더니만 사용자가 30개의 문자열을 입력하게 되면 어떻게 합니까?그래서 배열로 문자열을 다루기는 참으로 애매한 경우가 많습니다.따라서 문자열을 입력받아 저장할 때는 동적 메모리 할당을 많이 사용합니다.메모리를 동적으로 할당 받는 예제를 하나 들어보져.
#include<stdio.h>#include<stdlib.h>/*malloc() */#include<string.h>/*strcpy() */#define MAX_LEN256intmain(void){char*p[10];charbuffer[MAX_LEN];inti;for( i =0; i <10; i++ )    {        printf("Input%2dstring: ", i );        fgets( buffer, MAX_LEN,stdin);/*fgets() 함수로 입력을 받으면 마지막에 엔터키 까지 저장이 된다.* 이 엔터키를 없애기 위해서 문자열 크기 - 1 인 곳에 NULL을 넣는다*/buffer[ strlen(buffer)-1] ='\0';/*buffer 크기 + 1 만큼 메모리를 할당 받고,* 할당 받은 메모리의 첫 번지 값을 p[i] 에 저장한다.* 메모리를 할당 받을때 문자열의 크기보다 1개가 더 큰 이유는* NULL 값을 저장하기 위함이다.*/p[i] = (char*)malloc( strlen(buffer) +1);if( p[i] ==NULL)        {            printf("main(): memory allocation error!\n");            exit(-1);        }/*buffer의 문자열을 p[i]가 가리키고 있는 방금 할당 받은 메모리로* 복사 한다.* p[i]가 가리키고 있는 영역은 메모리의 Heap 영역이다*/strcpy( p[i], buffer );    }for( i =0; i <10; i++ )        printf("%2d:%s\n", i, p[i] );return0;}
나중에 더 자세하게 설명을 하겠지만 이렇게 동적 할당을 이용하면 배열을 사용하는것 보다 메모리를 낭비하지 않을 수가 있습니다.문자열에서 항상 중요한 것은 문자열의 맨 마지막에는 꼭NULL값이 있어야 한다는 것입니다. 이NULL값은 숫자로는0과 같습니다.문자열이 끝나는 것을 바로 이NULL값으로 판단을 하는 겁니다.많은 문자열을 다루는 C library들이 문자열의 끝을 판단할 때NULL로 하게 됩니다.이번 세번째 강의는 여기까지 하기로 하고,다음 강의 때에 문자열을 포인터로 다루는 방법에 대해서 자세하게 다루겠습니다.
===========================================================================
 
포인터 4번째 강좌입니다.3번째 강좌에서 문자열에 대해서 조금 맛을 보았는데 이번 4번째 강좌에서문자열에 대해서 좀더 심도있게 다루어 보겠습니다.문자열을 다루는 함수중에서 가장 많이 쓰이는 함수중에 strlen(), strcpy(),strcat() 이 있습니다.이 세가지 함수의 하는 일과 이 함수들을 실제로 구현해 보면서 문자열에 대해서이해를 할 수 있으면 좋겠군여.첫번째로,strlen() 함수에 대해서 알아볼까여?이 함수는 인자로 받은 문자열의 개수를 리턴하는 함수입니다.우선 함수의 원형을 알아보져..size_tstrlen(constchar*s );size_t 는 unsigned long int 로 typedef 되어 있습니다.문자열 포인터를 인자로 받아서 문자열 개수를 리턴하는 함수입니다.그럼 간단하게 이 함수의 사용예를 들어보겠습니다.
#include<stdio.h>#include<string.h>intmain(void){char*s ="Hello, world!";    printf("string length =%d\n", strlen(s) );return0;}
위 코드의 결과는 string length = 13이렇게 됩니다.strlen() 함수의 하는 일을 알았으니 이번엔 이 함수를 구현해 봐야겠져?문자열의 문자들의 개수를 세어서 그 숫자를 리턴하는 함수를 만들면 되는 거져.문자열의 개수를 셀 때 처음은 알지만 언제 끝나는지를 알아야 수를 셀텐데..그렇습니다. 문자열의 마지막에는NULL값이 있다고 했져?바로 이NULL값이 나올때까지 루프를 돌리면 되는 거져.
#include<stdio.h>#include<string.h>intmy_strlen(constchar*s ){inti =0;for( i =0; *s ; i++, s++ );returni;}intmain(void){char*s ="Hello, world!";    printf("string length =%d", my_strlen(s) );return0;}
분석을 해볼까여?for문에서 조건식을 봅시다. 조건식이 딸랑 *s 이렇게 되어 있네여..왜 이렇게 했을까여?이것은 s 포인터가 가리키는 곳의 값을 나타내는 것인데, 문자가 있으면 0 이 아닌값을 갖겠져? 그러므로 조건은 참이 되는 것이지여.그래서 증감식에서 i의 값을 더해주고, s포인터의 주소값도 하나 늘려주게 됩니다.그리고 또 다시 *s 조건식을 만나서 0 인지를 판단..이렇게 하면 결국 주소값이 계속 증가되면서 NULL(0)을 만나게 되고 이때까지증가한 i의 값이 바로 문자열의 길이가 되는 겁니다.여기서 for()문은 i의 값과 s의 주소값만 증가 시킬뿐 다른 일은 하지 않습니다.이렇게 포인터를 사용한 문자열을 다루는 것은 생각보다 아주 간단합니다.마지막에 0 이 온다는 사실만 확실하게 주지하고 있으면 문자열을 다루는데많은 아이디어를 가지고 다룰 수가 있습니다.strlen() 함수에 대해서 알아봤으니 이번엔 strcpy() 함수에 대해서 알아볼까여?strcpy() 함수는 이름에서도 알 수 있듯이 문자열을 복사하는 함수 입니다.이 함수의 원형을 알아보면,char*strcpy(char*dest,constchar*src );src의 문자열을 dest에 복사를 하는 겁니다.그럼 strcpy() 함수를 사용하는 예제를 들어보져.
#include<stdio.h>#include<string.h>intmain(void){charbuffer[100];char*s ="Hello, world!";    strcpy( buffer, s );    printf("buffer =%s\n", buffer );return0;}
실행 결과는 buffer = Hello, world!이렇게 됩니다.s가 가리키고 있는 문자열을 buffer 배열에 복사를 하는 것이지여.그럼 이제 하는 일을 알았으니까 구현을 해봐야져?
#include<stdio.h>#include<string.h>char*my_strcpy(char*dest,constchar*src ){char*s = dest;for( ; *src ; dest++, src++ )        *dest = *src;    *dest ='\0';returns;}intmain(void){charbuffer[100];char*s ="Hello, world!";    my_strcpy( buffer, s );    printf("buffer =%s\n", buffer );return0;}
코드를 분석 해 보면,s 포인터는 복사를 한 곳의 주소값을 리턴하기 위해서 dest를 대입한것이고,for() 문을 보면,초기식은 필요가 없으므로 초기식은 없고,조건식을 보면 위에서 설명한 my_strlen()와 비슷하져?원본 문자열인 src가 0이 나올때 까지 루프를 돌리기 위한 조건식이 되겠져.그리고 증감식을 보면 원본 포인터인 src와 대상 포인터인 dest의 주소를 하나씩각각 늘려주면서 *dest = *src; 이렇게 한거져..이것은 src가 가리키고 있는 곳의 값을 dest가 가리키고 있는 곳에 저장해라..이런뜻이져?마지막으로 문자열이 끝났다는 것을 저장하기 위해서 dest의 맨 마지막 부분에 0값을저장했습니다.차근차근 하나씩 따져보면서 코드를 분석하면 그리 어렵지 않다는 것을 알게 될겁니다.그럼 마지막으로 strcat() 함수에 대해서 알아봐야겠네여..strcat() 함수는 문자열을 붙이는 함수입니다.함수의 원형을 보면,char*strcat(char*src,constchar*tail );tail의 내용을 src의 뒤에 붙이는 것입니다.그럼 strcat() 함수를 사용하는 예제를 들어보져.
#include<stdio.h>#include<string.h>intmain(void){charbuffer[30] ="1234567890";    strcat( buffer,"abcd");    printf("buffer =%s\n", buffer );return0;}
결과는 buffer = 1234567890abcd이렇게 됩니다.tail이 가리키고 있는 곳의 문자열을 src의 뒤에 연결해서 저장을 하는 겁니다.하는 일을 알았으니 구현을 해보져.
#include<stdio.h>#include<string.h>char*my_strcat(char*src,constchar*tail ){char*s = src;for( ; *src ; src++ );for( ; *tail ; src++, tail++ )        *src = *tail;    *src ='\0';returns;}intmain(void){charbuffer[30] ="1234567890";    my_strcat( buffer,"abcd");    printf("buffer =%s\n", buffer );return0;}
코드를 분석해 보면..my_strcat() 함수내의 첫번째 for()문이 하는 역할은 src의 포인터를 맨 마지막으로이동하는 것입니다. 문자열의 뒤에 내용을 붙여야 하므로 src가 가리키고 있는 포인터를 0 값이 있는 곳까지 이동을 하는 것입니다.그리고 두번째 for()문에서 붙여야 할 문자열의 주소값과 src의 주소값을 을 하나씩 증가하면서 src가 가리키고 있는 곳에 tail이 가리키고 있는 값을 저장합니다.그리고 마지막으로 src의 맨 뒤에 0 값을 써서 문자열이 끝났음을 저장하는 거지여.지금까지 한 내용이 이해가 가지 않는다면 다시 한번 코드를 찬찬히 훑어 보시고하나하나 계산을 해보세여.그러면 포인터의 움직임을 조금은 알 수가 있을겁니다.이처럼 문자열을 다루는 함수는 생각보다 간단하게 포인터를 이용해서 작성할 수가있습니다. 포인터를 사용하는 많은 이유중에 문자열을 다루기가 쉽기 때문이라는이유를 들어서 문자열을 다루어 보았습니다.네번째 강좌도 이렇게 마치고여..다음 강좌는 포인터를 이용해서 다른 지역에 있는 값을 바꾸어 보도록 하겠습니다.
 
 
 
포인터 5번째 강좌입니다.4번째 강좌에서 문자열을 다루는 함수중 가장 많이 사용되는 함수를 직접 구현해봤는데여.. 사실 몇가지 더 구현해 봐야 문자열에 대해서 확실히 알 수가 있겠지만,이번 5번째 강좌를 보시고 마지막에 문자열 함수를 구현하는 문제를 내도록하겠습니다. 열심히 보고 계신 분이라면 문제를 풀어서 이번 강좌에 답글을달아보도록 해보세여.. :)이번 강좌는 저번 강좌에서 말했듯이 포인터를 이용해서 다른 지역에 있는 값을 바꾸어 보도록 하겠습니다.다른 지역에 있는 값을 바꾼다는 말에 대해서 먼저 설명하도록 하겠습니다.이 강좌에서 다른 지역에 있는 변수라는 말의 뜻은다른 함수에 존재하는 지역변수라는 뜻입니다.다른 함수에 존재하는 지역변수의 값을 바꾸려면 일반적인 방법으로는 불가능합니다.일반적으로 가장 많이 설명되는 swap 기능을 하는 함수로 예를 들어보겠습니다.
1#include<stdio.h>23voidswap(intx,inty );45voidswap(intx,inty )6{7inttemp;89temp = x;10x = y;11y = temp;12}1314intmain(void)15{16intx =5, y =10;1718printf("before swap: x =%d, y =%d\n", x, y );1920swap( x, y );2122printf("after swap: x =%d, y =%d\n", x, y );2324return0;25}
위의 코드는 main() 함수내에 있는 x, y 변수의 값을 바꾸기 위해서 swap() 함수를만들고 이 함수를 호출함으로써 x, y 의 값이 바뀌기를 기대하고 있는 건데,실제로 컴파일을 하고 실행을 해보면 출력결과가 기대한것 처럼 나오지 않습니다.그럼 코드를 분석해 볼까여? 3 라인은 swap() 함수의 원형(prototype)을 선언한 것입니다. 7 라인의 temp 변수는 x와 y의 값을 바꾸어 주기위한 임시 변수입니다. 9 라인에서 temp의 값은 x(여기에서 5)로 되고,10 라인에서 x의 값은 y(10)이 되고,11 라인에서 y의 값은 temp(5)가 됩니다.실제로 swap() 함수는 x와 y의 값을 뒤바꾸었습니다.그런데 main()함수에서 출력하는 결과는 둘다 x = 5, y = 10 입니다.왜그럴까여?그 이유는 아주 단순한데에 있습니다.main() 함수 내에 있는 x, y와 swap() 함수에 있는 x, y는 다른 지역내에 있는변수로서 이름만 같을뿐 메모리상에 위치하는 주소는 다른 변수입니다.따라서 swap() 함수에서 뒤바뀐 x, y는 main() 함수에 있는 x, y와는 전혀무관한 변수인 것입니다.그렇다고 swap() 함수가 return을 이용해서 main() 함수의 x, y를 바꿀 수 있는것도아닙니다. 아.. 물론 구조체를 이용해서 return을 사용한다면 또 가능할 수도 있겠지만 그건 여기서 원하는 것이 아니지여.자.. 방금 설명에서 중요한 것이 있습니다.그것은 바로이름만 같을 뿐 메모리상에 위치하는 주소는 다른 변수라는 말입니다.다른 지역에 있는 변수를 바꾸기 위해서 이름을 사용해서는 불가능하다면,주소를 이용하면 되는 것입니다.그럼 포인터를 이용한 주소로 다른 지역에 있는 변수의 값을 변경해보도록 하져.
1#include<stdio.h>23voidswap(int*x,int*y );45voidswap(int*x,int*y )6{7inttemp;89temp = *x;10*x = *y;11*y = temp;12}1314intmain(void)15{16intx =5, y =10;1718printf("before swap: x =%d, y =%d\n", x, y );1920swap( &x, &y );2122printf("after swap: x =%d, y =%d\n", x, y );2324return0;25}
코드를 분석해 보도록 하겠습니다.swap() 함수의 인자가 포인터로 바뀌었습니다.이것은 인자로 변수의 값을 받는게 아니라 그 변수의 번지를 인자로 받기 위함입니다.20 라인에서 x와 y 변수의 번지를 swap()함수에 넘겨주며 호출을 했습니다. 7 라인의 temp 변수는 임시 변수이고, 9 라인에서 temp의 값은 *x(main에 있는 x 변수의 주소값으로 가서 그 값을 읽는다) 으로 변경. 즉 temp는 5 10 라인에서 *x = *y; 이것은 main에 있는 x 변수의 번지로 가서, main의 y번지값에 있는 값을 저장해라. 즉, main() 함수의 x 변수의 값이 10으로 변경. 11 라인에서 main에 있는 y 변수 주소값으로 가서 temp(5)를 저장해라. 즉, main의 y 변수가 5로 변경.22 라인에서 변경된 x, y의 값을 출력.자... 이렇게 되는 것입니다.포인터라는 것의 위력인 것이지여.지금의 이 예제는 C의 문법책이라면 거의 언급하는 예제 이므로 꼭 이해를 해 두어야합니다. 이 예제를 설명하기 이전에 저번 강좌에서 사실은 다른 지역내에 있는 변수를바꾼바가 있습니다. 기억이 잘 안나신다구여? 그럼 4번째 강좌를 다시 보세여.main() 함수내에 있는 문자배열의 내용을 함수를 호출해서 바꾸었을 겁니다. :)이번 강좌는 포인터가 가지고 있는 능력중 하나를 설명드렸습니다.하지만 아직도 왜 포인터를 꼭 사용해야 하는지에 대해서는 다들 의아해 하실 수 있을 것같지만, 천천히 계속되는 이 강좌를 읽으면서 나오는 코드를 직접 쳐보고실행해보면서 이해를 하시면 왜 포인터를 사용하는지에 대해서 좀더 명확하게알 수가 있을것입니다.이번 강좌의 첫머리에서 말씀드렸듯이 이번 강좌에는 문제가 있습니다.4번째 강좌에서 strlen(), strcpy(), strcat() 함수를 구현해 봤습니다.물론 포인터를 이해하는 목적으로 구현을 했기 때문에 정확한 구현은 아니지만,원하는 기능은 충분히 하는 함수를 만들어 봤습니다.문제는 strcmp(), strchr(), strstr() 함수에 대해서 공부를 하시고,이 함수를 구현해 보는 겁니다.5번째 강좌는 이렇게 마치고여,다음 강좌에서는 구조체와 연계된 포인터에 대해서 알아보겠습니다.
 
 
 
 
포인터 6번째 강좌입니다.저번 강좌에서는 swap() 함수를 작성해 보면서 포인터를 조금 더 맛보았습니다.이번 강좌는 구조체와 포인터의 연계해서 포인터가 어떻게 사용되고 또 그것이어떤 의미를 갖는 것인지에 대해서 알아보겠습니다.구조체에 대해서 다들 알고 있다고 생각이 들지만 그래도 또 한번 짚고 넘어가도록하겠습니다.구조체를 사용하는 가장 큰 이유는 공통적으로 사용하는 변수들을 묶어서 사용하기위함일 것입니다. 공통적으로 사용하는 변수를 묶는 것을 예제로 보여드리지여.
1#include<stdio.h>23#define MAX_NUM545typedefstruct_student Student;67struct_student8{9intkor;10inteng;11intmath;12};1314intmain(void)15{16Student student[MAX_NUM];17inti =0;18intsum =0;19floatavg =0.0;2021for( i =0; i < MAX_NUM ; i++, printf("\n") )22{23printf("Input student[%d]'s korean score: ", i);24scanf("%d", &student[i].kor );25printf("Input student[%d]'s english score: ", i);26scanf("%d", &student[i].eng );27printf("Input student[%d]'s math score: ", i);28scanf("%d", &student[i].math );29}3031printf("INDEX\tKOREAN\tENGLISH\tMATH\tTOTAL\tAVERAGE\n");32printf("-----\t------\t-------\t----\t-----\t-------\n");3334for( i =0; i < MAX_NUM ; i++ )35{36sum = student[i].kor + student[i].eng + student[i].math;37avg = (float)sum /3;3839printf("%2d\t%3d\t%3d\t%3d\t%3d\t%3.2f\n",40i, student[i].kor, student[i].eng, student[i].math,41sum, avg );42}4344return0;45}
코드를 분석해 보겠습니다. 5 라인에서 뒤에 나올 struct _student 에 대해서 Student로 typedef 을 해놓은 것입니다. 이것은 보통 헤더파일 등에서 어떤 구조체 타입들이 사용될지를 미리 한눈에 알아볼 수 있도록 파일의 처음부분에 선언을 해 놓기 위해서 사용되는 방법입니다. struct _student에 대해서는 아래 7라인부터 12 라인까지 선언되어 있습니다. 7 라인부터 학생의 성적을 저장하기 위한 구조체를 선언합니다. 여기서 공통적으로 사용되는 kor, eng, math 세가지 변수를 한데 묶어서 struct _student 로 만들었으며 5 라인에서 이 구조체를 Student 로 typedef해서 사용할 것입니다.16 라인에서 Student 형 배열을 MAX_NUM(5)만큼 선언합니다.21 라인의 for문을 통해서 사용자에게 5명의 학생 성적을 입력받습니다.36 라인에서 각각의 학생의 총점을 구하고,37 라인에서 평균을 구합니다.39 라인에서 각 학생들의 점수를 출력합니다.내용은 그렇게 어렵지 않을 것입니다.구조체를 사용해서 5명의 학생 데이터를 student라는 이름의 배열 하나로 전부처리를 할 수가 있었습니다.이렇게 많은 데이터를 효율적으로 다루기 위해서 구조체를 사용하게 되는데,문제는 이 구조체 변수를 함수의 인자로 보낼때 어떻게 해야하는지 잘 모를때가있습니다.바로 이때 포인터가 사용이 되는 것입니다.포인터를 사용하기 전에 위의 예제 중에서 화면에 학생의 데이터를 출력하는 부분만함수로 작성해 보도록 하겠습니다. 그렇게 하려면 만들려는 함수의 인자에 Student구조체를 인자로 받아야 겠지여? 우선은 포인터를 사용하지 않고, 구조체만을 사용해보도록 하겠습니다.
1#include<stdio.h>23#define MAX_NUM545typedefstruct_student Student;67struct_student8{9intkor;10inteng;11intmath;12};1314voidprintScore( Student student[MAX_NUM] );1516intmain(void)17{18Student student[MAX_NUM];19inti =0;2021for( i =0; i < MAX_NUM ; i++, printf("\n") )22{23printf("Input student[%d]'s korean score: ", i);24scanf("%d", &student[i].kor );25printf("Input student[%d]'s english score: ", i);26scanf("%d", &student[i].eng );27printf("Input student[%d]'s math score: ", i);28scanf("%d", &student[i].math );29}3031printScore( student );3233return0;34}3536voidprintScore( Student student[MAX_NUM] )37{38inti;39intsum =0;40floatavg =0.0;4142printf("INDEX\tKOREAN\tENGLISH\tMATH\tTOTAL\tAVERAGE\n");43printf("-----\t------\t-------\t----\t-----\t-------\n");4445for( i =0; i < MAX_NUM ; i++ )46{47sum = student[i].kor + student[i].eng + student[i].math;48avg = (float)sum /3;4950printf("%2d\t%3d\t%3d\t%3d\t%3d\t%3.2f\n",51i, student[i].kor, student[i].eng, student[i].math,52sum, avg );53}54}
출력하는 함수의 인자가 Student student[MAX_NUM] 이렇게 되었지여?이것은 33 라인에서 printScore() 함수를 호출할 때 main() 함수에 있는student배열을 printScore() 함수의 student 배열로 복사를 하는 것입니다.즉, main()에 있는 student배열과 똑같은 배열이 printScore() 함수에도 생성이되는 것입니다.그렇게 해서 printScore() 함수에서는 복사된 student 배열을 이용해서 문제없이출력을 할 수가 있는 것입니다.하지만 이렇게 할때에는 몇가지 문제가 있습니다.printScore() 함수가 호출될때 똑같은 배열이 또 생성된다는 것입니다.복제본이 있다는 것은 메모리를 2배로 차지한다는 말이지여..그리고 구조체를 복사하기 위해서는 복사하는 시간이 든다는 겁니다.그 구조체가 커지면 커질수록 메모리도 많이 사용되고 시간도 많이 들게 됩니다.printScore() 함수는 구조체를 인자로 받아 복제를 해서 해결했다 하더라도사용자에게 입력을 받는 함수를 작성한다고 할때 main() 함수에 선언되어 있는student 배열의 내용을 바꿀 수 있는 방법이 없습니다. 물론 student 배열을전역 변수로 하면 되겠지만 그것은 별로 좋은 방법이 아닙니다.5번째 강좌에서 말씀을 드렸듯이 다른 지역에 있는 변수의 내용을 바꾸기 위해서포인터를 사용한다고 했었지여?그래서 사용자에게 입력받는 함수를 포인터를 사용해서 작성해 보도록 하겠습니다.그리고 이미 작성된 printScore() 함수도 역시 인자를 포인터로 바꾸어 보도록하겠습니다.
1#include<stdio.h>23#define MAX_NUM545typedefstruct_student Student;67struct_student8{9intkor;10inteng;11intmath;12};1314voidgetScore( Student *student );15voidprintScore( Student *student );1617intmain(void)18{19Student student[MAX_NUM];2021getScore( student );22printScore( student );2324return0;25}2627voidgetScore( Student *student )28{29inti =0;3031for( i =0; i < MAX_NUM ; i++, printf("\n") )32{33printf("Input student[%d]'s korean score: ", i);34scanf("%d", &student[i].kor );35printf("Input student[%d]'s english score: ", i);36scanf("%d", &student[i].eng );37printf("Input student[%d]'s math score: ", i);38scanf("%d", &student[i].math );39}40}4142voidprintScore( Student *student )43{44inti;45intsum =0;46floatavg =0.0;4748printf("INDEX\tKOREAN\tENGLISH\tMATH\tTOTAL\tAVERAGE\n");49printf("-----\t------\t-------\t----\t-----\t-------\n");5051for( i =0; i < MAX_NUM ; i++ )52{53sum = student[i].kor + student[i].eng + student[i].math;54avg = (float)sum /3;5556printf("%2d\t%3d\t%3d\t%3d\t%3d\t%3.2f\n",57i, student[i].kor, student[i].eng, student[i].math,58sum, avg );59}60}
코드를 보면 printScore() 함수의 인자를 포인터로 바꾸었을 뿐 코드 내용은 변한게 없습니다. 하지만 이렇게 한 것만으로도 메모리 낭비와 속도를 해결했습니다.인자로 넘어온 것은 main() 함수의 student 배열의 주소값만 인자로 받아와서사용을 한 것입니다.여기서 조금 이상하다고 생각되는 부분이 있을 것입니다.포인터를 인자로 받아서 어떻게 배열로 받은 것과 똑같이 사용을 하게 된건지...그래서 포인터와 배열의 관계에 대해서 잠시 설명을 드리겠습니다.chars[10] ="abcdefghi";위와 같이 10글자 짜리 배열 s를 선언했다고 했을때,처음의 a 라는 글자를 사용하려면 어떻게 해야 할까여?네, s[0] 이 되겠져?그럼 d 라는 글자를 사용하려면?네, s[3] 입니다.그런데 분명 배열의 이름인 s는 포인터라고 말씀을 드렸었져?이 포인터를 사용해서 d 를 나타낼 수도 있습니다.*s 이것은 s 배열의 첫번째 주소로 가서 그 값을 나타냅니다. 그렇져?따라서 s 배열이 있는 곳으로 가서 그 값을 읽으면 바로 a 가 됩니다.그럼 4번째 존재하는 d 라는 문자를 읽으려면 어떻게 할까여?*(s + 3) 이렇게 하면 s의 주소값에 3을 더한 다음에 그 주소에 있는 값을 읽어라.이렇게 되는 겁니다.그림으로 나타내 보겠습니다. 0x1000 +---+---+---+---+---+---+---+---+---+---+---+ |'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' \0 | s +---+---+---+---+---+---+---+---+---+---+---+ | | | | | | | | +---> 0x1003 번지 | | | +---> 0x1002 번지 | +---> 0x1001 번지그림은 위와 같습니다.처음 번지가 0x1000 라고 가정을 했을때 char의 크기가 1 바이트 이므로주소값은 1 씩 증가해서 저장이 됩니다.따라서 그림에서 보듯이 a 라는 문자열은 0x1000 번지에,b 라는 문자열은 0x1001 번지에,c 라는 문자열은 0x1002 번지에,d 라는 문자열은 0x1003 번지에 각각 저장이 됩니다.위에서 *(s + 3) 의 표현을 계산 해보도록 하겠습니다.s 는 배열의 첫번지 이므로 0x1000 입니다. 여기에 3을 더했으므로 0x1003 이 되져?그렇다면 *(0x1003) 이 된것입니다. 이 표현은 0x1003 번지에 가서 그 값을 읽어라.이뜻인데 0x1003 번지에는 무엇이 있지여?네 바로 d 라는 문자가 있습니다. 배열로 표시를 하면 s[3] 이렇게 되져?여기에서 다음과 같은 표현이 성립된다는 것을 알 수가 있습니다. s[0] == *( s + 0 ) == *s s[1] == *( s + 1 ) s[2] == *( s + 2 ) s[3] == *( s + 3 )이 관계에 따라서 student 포인터를 가지고 배열처럼 사용을 할 수가 있었던 것이지여..그래서 student[i].kor 을 배열로 표시하지 않고 포인터로 표현을 하면,(student+i)->kor 처럼 되는 것입니다. 여기서 -> 에 대해서도 알아볼까여?-> 은 포인터를 사용할때 구조체의 멤버를 접근할 수 있게 하는 것입니다.예를 들어서 위의 Student 구조체를 사용한다고 할때,Student s, *p;p = &s;s.kor =10;p->eng =20;이렇게 됩니다. s는 포인터가 아닌 변수이며 이 s를 이용해서 멤버에 접근할때는.(Dot)를 사용합니다.p는 s의 주소값을 저장하고 있는 포인터이며 이 p를 이용해서 s의 멤버에 접근할때는 ->를 사용하는 것입니다.-> 도 * 처럼 "거기로 이동하라" 라고 인식을 하면 이해하기가 쉽습니다.p->eng 는 s의 주소로 이동을 해서 eng 라는 멤버를 접근해라.. 이렇게 이해를하면 되는 것입니다.따라서 (student+i)->kor 이것은 student가 저장하고 있는 주소값에 i를 더한 후,kor 멤버에 접근을 해라... 이런 뜻이 됩니다.여기서 잠깐..(student+i)->kor 에서 student가 저장하고 있는 주소값이 만약 0x1000이고,i의 값은 2라고 가정을 하면,(student+i) 이 값은 얼마가 될까여?0x1002 일까여?그렇지 않습니다. 여기에서 알아야할 것이 또 한가지가 있습니다.포인터는 증감 및 곱하기 나누기등의 연산을 하면 안됩니다.아니, 그럼 지금까지 포인터에 더하기를 한것은 무엇이란 말입니까?그것은 일반적인 덧셈과는 조금 다른 의미를 갖고 있는 것입니다.예를 들어보져..shortints =10;/*0x1000 */shortint*sp = &s;longintl =10;/*0x2000 */longint*lp = &l;floatf =10.0;/*0x3000 */float*fp = &f;doubled =10.0;/*0x4000 */double*dp = &d;위와 같이 변수들과 포인터들이 선언되어 있습니다.변수 옆에 주석으로 처리되어 있는 숫자는 그 변수의 주소값을 임의로 정한겁니다.우선 short int 형인 s 변수의 주소를 저장하고 있는 sp 포인터에 대해서 알아보져.sp 는 0x1000 을 갖고 있는데 (sp + 1)의 값이 과연 얼마일까여? 0x1001 일까여?그렇지 않습니다. 결과적으로 답은 0x1002 입니다.그 이유는 sp 포인터 앞에 타입이 무엇이져? short int 이져?short int 는 2바이트 크기의 자료형입니다.즉 sp 포인터가 가리키고 있는 곳의 자료형이 short int 형이라는 것이져.그래서 (sp + 1)을 한다는 것은 단순히 1을 더하는 것이 아니라 자신이 가리키고 있는 곳의 자료형의 크기만큼 곱해서 더하는 것이지여.그럼 (sp + 3)의 값은?네 0x1006 이 되는 것입니다.마찬가지로 가리키는 곳이 long int 형인 lp 포인터에 1 을 더하면 어떻게 되져?long int 형이 4바이트 자료형이므로 0x2004 가 되는 것입니다.다른 것들도 마찬가지 입니다.자, 그렇다면 Student로 돌아가서,(student+i) 의 값을 알아보도록 하져.student의 자료형은 Student 구조체져? 이 구조체는 int 자료형 3개가 묶인 것이져.그렇다면 이 구조체의 크기는? 네 int가 4바이트이므로 12바이트가 되겠져.왜 int가 4바이트라고 했져? 32비트 운영체제라고 가정을 하고 설명을 한다고처음에 말씀을 드렸었져?student가 저장하고 있는 주소값이 만약 0x1000 이라고 하면,(student+i)의 값은 0x1000+(i*12) 이렇게 되는 것입니다.그래서 student가 저장하고 있는 주소값이 0x1000이고, i의 값이 2라고 가정하면,(student+i)의 값은 0x1000 + 2*12 = 0x1018 이렇게 되는 것입니다.포인터 변수 앞에 붙는 * 과 뒤에 붙는 ->는 그 의미가 아주 비슷합니다.포인터 변수가 저장하고 있는 주소값으로 이동하라.. 라고 해석을 하면 되기 때문입니다.이 내용의 더 심도있는 얘기는 좀더 뒤에서 다루기로 하겠습니다.컴파일러의 내부에서 동작하는 포인터에 대해서 설명을 해야하는데,지금 이 얘기를 하면 강좌가 너무 길어지게 되고 어려워 질것 같군여.오늘은 구조체와 포인터를 연계해서 사용하는 예제를 살펴봤습니다.그 외에 포인터의 증감에 대해서도 다루느라 내용이 조금 길어졌네여..다음 강좌는 구조체와 포인터를 연계해서 사용하는 좀 더 많은 예제를 들어보고분석을 해보도록 하겠습니다.------------------------------------------------------------------------------여기에서 잠깐!!!C 공부방 까페의 회원이신 이창헌님의 지적으로 이번 강좌의 문제점을 찾아내었습니다. 위에서 배열을 인자로 받으면 배열이 새로 생긴다고 설명을 드렸는데 그건 저혼자만의 생각으로 밝혀졌습니다. :(voidprintScore( Student student[MAX_NUM] ) -- 1voidprintScore( Student *student ) -- 2위의 1, 2번은 거의 동일합니다. 그리고 1번에서 student 라는 배열이 생성되는 것이 아니고 포인터 상수가 선언되는 것입니다. 역시 C 공부방의 파스크란 님께서 설명 해주신 것 처럼 1번은 포인터 상수이기 때문에 대입을 할 수 없고, 2번은 포인터를 선언한 것이기 때문에 4바이트가 잡히고 대입을 할 수 있게 되는 점이 다른 점입니다.따라서 위의 예제 코드에 문제가 있는 것은 아니고, 제가 설명을 잘못 드린 부분이 있어서 정정합니다.원래 제 의도는,voidprintScore( Student student ) -- 1voidprintScore( Student *student ) -- 2위와 같이 1번은 구조체 변수를 인자로 받는 함수와, 2번의 포인터를 인자로 받는함수의 비교를 할 목적이었는데 제가 실수로 예제 코드를 배열을 사용해서 작성하면서 설명할때 혼동을 했습니다.정확히 정하지는 않았지만,원 강좌는 그대로 두고 지금 처럼 맨 아래에 다시 정정하는 설명을 달기로 했습니다. 정확히 지적을 해주신 이창헌님, 좋은 답변으로 도와주신 파스크란님의 도움으로 강좌가 깨끗해졌습니다. 감사합니다.
 
 
 
포인터 7번째 강좌입니다.저번 강좌에서는 구조체와 포인터의 연계를 통해서 포인터의 사용해야 할 이유를조금 알아보았습니다.다시한번 저번 강좌에 대한 복습을 하면,포인터를 사용하는 목적은 메모리 낭비를 막고,구조체를 복사하느라 드는 시간을 줄이기 위함입니다.저번강좌에는 간단한 구조체를 가지고 예를 들어봤습니다.그래서 별 무리없이 구조체와 포인터의 연계에 대해서 이해를 할 수가 있었을 겁니다.이번엔 조금 복잡한 구조체를 예를 들어서,포인터를 사용했을 때 얻는 이점에 대해서 말씀드리겠습니다.그럼 우선 다음 소스코드를 보세여.지금까지는 소스가 간단하고 설명을 직접 했기 때문에 주석을 달지 않았지만,이번 소스코드 부터는 자세한 주석을 달아 놓을 것입니다. :)복사해서 컴파일 할 생각하지 마시고 직접 쳐보세여. 남의 코드를 자꾸 쳐보면서공부를 하는 것도 매우 중요합니다.
1#include<stdio.h>2#include<stdlib.h>/*malloc(), free() */3#include<string.h>/*strlen(), strcpy() */45/*구조체를 타입에 맞게 캐스팅 해주는 매크로 함수 */6#define OBJECT(object)  ((Object *)(object))7#define MANKIND(object) ((Mankind *)(object))89typedefstruct_object  Object;10typedefstruct_mankind Mankind;1112typedefenum_type      Type;1314enum_type15{16OBJECT_TYPE,/*객체(Object) 타입  */17MANKIND_TYPE/*사람(Mankind) 타입 */18};1920struct_object21{22Type            type;/*객체의 종류      */23unsignedlongID;/*객체의 일련 번호 */24};2526struct_mankind27{28Object  object;/*객체(Object) 속성 상속 */29char*  name;/*이름                   */30};3132/*객체의 일련번호 부여를 위한 전역 변수 */33unsignedlong_objectID =0;3435/*36* 함수의 원형(prototype) 선언37*/38voidsetObjectID( Object *object );39unsignedlonggetObjectID( Object *object );40voidsetObjectType( Object *object, Type type );41Type            getObjectType( Object *object );42voidfreeObject( Object *object );4344Object *    createMankind(void);45voidsetMankindName( Mankind *mankind,char*name );46char*      getMankindName( Mankind *mankind );47voidfreeMankind( Mankind *mankind );4849/*setObjectID50*51* 객체의 고유 일련번호를 설정한다.52* 이 함수는 사용자가 마음대로 사용할 목적이 아니라,53* 객체가 생성되었을 때 한번 호출해서 객체에게 일련번호를54* 부여하는 것이다.55*56* 전역변수인 _objectID은 객체에게 일련번호를 부여하고 난 후,57* 1씩 증가한다.58*/59voidsetObjectID( Object *object )60{61object->ID = _objectID++;62}6364/*getObjectID65*66* 객체의 고유 일련번호(ID)를 구한다.67*/68unsignedlonggetObjectID( Object *object )69{70returnobject->ID;71}7273/*setObjectType74*75* 객체의 타입을 설정한다.76*/77voidsetObjectType( Object *object, Type type )78{79object->type = type;80}8182/*getObjectType83*84* 객체의 type을 구한다.85* 객체의 type은 객체의 특성을 말한다.86*87* 객체 생성함수에 따라서 그 객체의 type은 설정된다.88*/89Type getObjectType( Object *object )90{91returnobject->type;92}9394/*freeObject95*96* 생성된 객체의 할당받은 메모리를 해제한다.97* Object의 type을 조사하여 각 type에 맞는 해제 함수를 호출한다.98*/99voidfreeObject( Object *object )100{101/*해제시킬 객체의 종류를 알아낸다. */102Type type = getObjectType(object);103104/*종류에 맞는 파괴 함수를 호출 */105switch( type )106{107caseOBJECT_TYPE :108break;109caseMANKIND_TYPE :110freeMankind( MANKIND(object) );break;111}112113/*마지막으로 각 type의 크기만큼 할당된 메모리를 해제한다. */114free( object );115}116117/*createMankind118*119* 사람 객체를 생성하는 함수이다.120*121* 모든 객체는 create*() 함수로 생성되어지며, 그 객체의 데이터 형은122* Object * 이다.123* 모든 객체의 구조체 타입이 다 다르지만 Object 구조체로 모든 객체를124* 다룰 수 있도록 하기 위해서 메모리는 sizeof(Mankind) 만큼 할당되지만125* 그 할당 받은 메모리를 (Object *)로 캐스팅한다.126*127* 그렇다고 해서 할당받은 메모리의 크기가 변한다던지 하는게 아니라128* 단지 그 할당받은 메모리를 참조하기 위한 index로 Object 구조체를129* 사용하겠다는 것만 알려주는 것일 뿐이다.130*131* 실제로 Mankind 구조체에 있는 속성을 사용하기 위해서,132* (object *)로 캐스팅된 메모리는 다시 (Mankind *)로 캐스팅되야 한다.133* MANKIND(object) 매크로 함수를 사용해도 된다.134*135* 이렇게 캐스팅을 통해서,136* 많은 종류의 객체들을 대표적인 객체 제어 함수로 제어를 할 수 있게 된다.137* 예를 들면 어떤 타입의 객체라도 freeObject() 함수로 파괴를 할 수가138* 있게 되는 것이다.139*/140Object *createMankind(void)141{142/*Mankind 구조체 크기 만큼의 메모리를 할당 받은 후143* (Object *)로 캐스팅 한다.144*/145Object *mankind = (Object *)malloc(sizeof(Mankind) );146if( mankind ==NULL)returnNULL;147148/*할당받은 메모리를 0으로 초기화 한다. */149memset( mankind,0,sizeof(Mankind) );150151/*생성된 객체의 type을 MANKIND_TYPE으로 설정한다.152* 즉, 이 객체는 사람 객체임을 뜻한다.153*/154setObjectType( mankind, MANKIND_TYPE );155156/*생성된 객체의 고유 일련번호를 부여한다. */157setObjectID( mankind );158159returnmankind;160}161162/*setMankindName163*164* 사람 객체의 이름 속성을 설정한다.165*/166voidsetMankindName( Mankind *mankind,char*name )167{168/*name 인자의 문자열 길이 + 1 만큼 메모리를 할당한다.169* 1을 더하는 이유는 문자열 마지막에 NULL을 저장하기 위함이다.170*/171mankind->name = (char*)malloc( strlen(name) +1);172if( mankind->name ==NULL)return;173174/*사람 객체의 이름 속성에 name 을 복사한다. */175strcpy( mankind->name, name );176}177178/*getMankindName179*180* 사람 객체의 이름 속성을 구한다.181*/182char*getMankindName( Mankind *mankind )183{184returnmankind->name;185}186187/*freeMankind188*189* 사람 객체의 속성을 해제하는 파괴함수.190*/191voidfreeMankind( Mankind *mankind )192{193/*사람 이름 속성 해제 */194if( mankind->name !=NULL)195free( mankind->name );196}197198intmain(void)199{200Object *man =NULL;201202man = createMankind();203setMankindName( MANKIND(man),"Shim sang don");204205printf("Man ID =%ld, name =%s\n",206getObjectID(man), getMankindName( MANKIND(man) ) );207208freeObject(man);209210return0;211}
지금까지의 소소 코드보다 꽤 길어졌져?복사해서 컴파일 하지 마세여.. 직접 보고 쳐보세여.별거 아닌 코드라도 다른 사람은 어떤 식으로 코딩을 하는지 따라해 보는 것도좋은 공부가 될 수 있으니까여.코드의 분석은 main() 함수에서 시작을 하도록 하겠습니다.198 라인의 main() 함수에서 시작을 하도록 하져..200 라인에서 Object 포인터 변수를 하나 선언합니다.202 라인에서 createMankind() 라는 사람 객체 생성 함수를 호출해서 사람 객체를 생성합니다. 그리고 이 함수는 사람 객체 구조체형인 Mankind * 형을 되돌리지 않고 Object * 를 되돌립니다. 바로 여기에서 포인터의 오묘함이 숨어 있습니다. 그럼 여기서 createMankind() 함수로 가보기로 하겠습니다.117 라인부터 createMankind() 함수의 코드가 있습니다. 주석에 적혀 있는 것을 잘 읽어 본 다음에 다음 설명을 보세여. 위의 예제 코드는 C로 객체 지향 프로그래밍을 하기 위한 구조체를 만든것입니다.C가 객체 지향 문법을 지원하지는 않지만 구조체를 잘 이용해서 객체지향적인프로그래밍을 얼마든지 할 수가 있습니다. 아래의 그림은 객체의 구조도를 나타내는 것입니다.+----------+| Object | 이 그림은 Object 구조체가 가장 기본적인+----+-----+ 구조체 이며, Mankind 구조체는 Object 의 | 속성들을 상속한 것을 보여줍니다. | +-----------+ 그림이 너무 간단해서 좀 그렇지만, +-----| Mankind | 다음 강좌에서 더욱 심화된 것을 보면 그런 +-----------+ 생각이 없어질거예여 :)C에서 구조체를 사용해서 상속을 받는 것은 부모 구조체 타입의 변수를 자식 구조체에서 선언을 하는 것입니다.여기서 부모 구조체는 Object 구조체이고 자식 구조체는 Mankind 구조체 입니다.C++로 설명을 하면 Object는 Mankind의 super class 이고, Mankind는 Object의 child class 가 되는 겁니다.26 라인에서 Mankind 구조체(struct _mankind)가 선언되어 있는데,이 구조체의 첫번째 멤버 변수가 바로 Object 타입입니다. 즉, Object 구조체의속성들을 사용하고자 함이지여.그리고 29 라인의 name 멤버는 Object의 속성에는 없는 사람객체에 부가적으로필요한 속성인 것입니다.사람도 역시 객체 이므로 객체를 나타내는 구조체인 Object 를 상속받고,객체의 특성 외에 사람만이 갖는 또다른 속성을 부가적으로 더해주는 것입니다.이렇게 함으로써 사람 객체(Mankind)는 객체(Object)의 속성을 전부 상속받게된것입니다.그리고 예를 들어서 학생을 나타내는 Student 객체를 표현하기 위한 구조체를 만든다고 가정을 해보면,학생 객체 역시 사람의 특성을 다 갖고 있어야 겠져?따라서 학생 구조체에서 Mankind 타입의 멤버 변수를 선언하면 학생 구조체는사람 객체의 모든 속성을 상속받게 되는 것입니다.struct_student{ Mankind mankind;char* schoolName;intscore;};이렇게 하면 학생 구조체가 만들어지는 것입니다. 사람 객체 구조체인 Mankind 타입의 변수를 선언함으로써 사람 객체의 속성을상속 받고 나머지 멤버 변수들은 사람 객체에는 없는 학생만의 속성을 부가한것입니다.이런 식으로 객체를 나타내는 구조체를 만들어 갈 수가 있는 것입니다.하지만 꼭 염두해 두어야 할 것이 있습니다.상속받는 구조체 타입의 멤버 변수는 꼭 제일 처음에 선언을 해야 합니다.그 이유는 뒤에서 설명을 하겠지만 구조체의 캐스팅을 할 경우에 부모 객체 혹은자식 객체를 다루는 함수를 사용하기 위함입니다.다른 객체 형식으로 캐스팅을 하게 되면 객체가 저장되어 있는 메모리는 변함이없지만 사용하는 방법이 바뀌게 됩니다. 즉, 그 메모리를 참조하기 위한 index를바꾸어 사용하는 것입니다.글로만 설명을 하면 이해가 어려울 수 있으니 그림으로 그려보겠습니다.0x1000 +------+------+--------+ 이 그림은 Mankind 형의 객체를 생성했을 때 | type | ID | name | 메모리에 변수들이 할당된 모습입니다. +------+------+--------+ 이 변수들이 할당되어 있는 곳의 주소값을 man 저장하고 있는 포인터 변수 이름을 man 이라 하고, (Object *)man 이렇게 캐스팅을 하면 man이 가리키고 있는 곳의 메모리를 접근하는index로 Object 구조체를 사용하겠다는 말입니다.Object 구조체에 선언되어 있는 변수는 type과 ID 져? 즉, man이 가리키고 있는 곳에서 type과 ID를 사용하겠다는 뜻이 됩니다.이번엔 (Mankind *)man 이렇게 캐스팅을 하면 man이 가리키고 있는 곳의 메모리를접근하는 index로 Mankind 구조체를 사용하겠다는 말이 됩니다.Mankind 구조체는 Object 구조체형 멤버 변수가 처음에 선언되어 있기 때문에type과 ID를 사용할 수도 있고 name 도 사용을 할 수가 있게 되는 것입니다.이런식으로 객체는 부모, 자식 관계에 있는 객체 타입으로 바뀌어서 사용을 할 수가있게 되는 것입니다.이런 구조를 설명하는 이유는 객체 지향 프로그래밍에 관해서 공부를 하면 자세히알수가 있습니다. 다만 이 강좌에서는 구조체와 포인터를 사용해서 객체 지향프로그래밍을 할 수 있다는 것을 보여줌과 동시에 포인터의 위력을 보여주기 위함이므로 객체 지향 프로그래밍의 장점에 대해서는 간단하게 설명하겠습니다.객체 지향 프로그래밍을 하게 되면,부모 객체의 내용을 업그레이드 하면 그 객체를 상속받은 자식 객체의 내용까지저절로 업그레이드가 되는 것입니다. 그래서 유지 보수가 편리해지고 대단위프로젝트를 수행할때 시간 및 인력의 절약과 같은 이점이 있습니다.예제 코드에서 Object 객체를 업그레이드 하면 Mankind 객체도 저절로 업그레이드되는 것입니다.그리고 이런 코딩 방식의 이점으로는 모든 객체를 하나의 구조체 타입으로 다룰수가 있습니다. 즉, Object 객체이건 Mankind 객체이건, Mankind를 상속받은 Student 객체이건전부 Object 포인터로 다룰수가 있게 되는 것입니다.객체를 생성하는 함수는 다를 수 있지만 그 객체를 다루는 함수를 Object * 형식에 맞추어서 작성하게 되면 라이브러리를 작성할때는 좀 더 복잡하긴 하겠지만그 라이브러리를 사용해서 프로그래밍 할때는 아주 편리하게 되는 것입니다.위의 예제 코드에서는 freeObject() 함수가 바로 그런 맥락의 함수 입니다.freeObject() 함수로 모든 형식의 객체를 파괴할 수가 있는 것입니다.물론 freeObject() 함수는 각 형식의 객체를 파괴하는 함수를 switch를 통해서호출하고 있습니다.하지만 이 라이브러리를 사용해서 프로그래밍하는 입장에서는 객체를 파괴할때는freeObject() 만 알고 있으면 되는 것입니다.Mankind 객체를 파괴할때 freeObject() 함수가 내부적으로 freeMankind() 함수를호출하는 것을 모르더라도 freeObject() 함수만 사용하면 모든 객체를 파괴할수가 있는 것입니다.자.. side 설명이 조금 길어졌군여..다시 코드로 돌아가서 117 라인의 createMankind() 함수로 가겠습니다.145 라인에서 Object 포인터 변수인 mankind를 선언하고 malloc() 함수를 호출해서 Mankind 구조체 크기만큼의 메모리를 힙(Heap)영역에 할당하고 그 첫번지를 리턴 받습니다. 할당된 메모리는 Mankind 크기이지만 (Object *)로 캐스팅을 하여 Object 객체로 사용을 하게 되는 겁니다.149 라인은 할당 받은 메모리를 0 으로 초기화 하는 것입니다. malloc() 함수가 메모리를 할당할때 사용 가능한 메모리 영역중 요구하는 크기로 메모리를 할당만 할 뿐 그 할당받은 메모리 내에는 할당 받기 이전에 사용이 되던, 이제는 쓸모 없는 쓰레기 값이 담겨져 있으므로 깨끗하게 0으로 세팅을 해 주는 것입니다.154 라인은 mankind 객체의 종류를 MANKIND_TYPE으로 설정합니다. 이것은 매우 중요합니다. 그 객체의 종류를 설정함으로써 내부적으로 캐스팅 되었을 때도 자신의 주체성(?)을 잃지 않을 수가 있는 것입니다. mankind 객체가 사람 객체지만 Object 객체로 캐스팅이 되었다 하더라도 자신의 근본은 사람이라는 것을 저장하고 있어야 하지 않겠어여? setObjectType() 함수는 73 라인에 선언되어 있습니다. 157 라인은 생성된 객체에게 고유한 일련번호를 부여하는 것입니다. setObjectID() 함수는 49 라인에 선언되어 있습니다.자 다시 main() 함수로 돌아가서,203 라인은 man 객체(사람 객체로 생성되었고 현재는 Object 형식으로 캐스팅 되어 있음)의 이름을 설정하는 함수를 호출하는 것입니다. 이 함수를 호출 할 때, (Object *)로 캐스팅 되어 있는 man 객체를 Mankind 객체 형식으로 캐스팅 해서 인자로 전달합니다. MANKIND() 매크로 함수가 바로 그것입니다. 7 라인에 보면 MANKIND() 매크로 함수가 선언되어 있습니다.setMankindName() 함수가 선언되어 있는 곳으로 가서,171 라인은 사람 객체의 이름을 저장하기 위해서 name 멤버 변수에 메모리를 할당 해주는 것입니다.175 라인에서 두번째 인자로 들어온 문자열을 사람 객체의 name 속성에 복사를 합니다.자, 다시 main() 함수로 돌아와서,205 라인에서 man 객체의 속성들을 출력을 해 줍니다. 각 사용된 함수의 코드를 보면 그렇게 어렵지 않게 이해를 할 수가 있을 것 입니다.210 라인에서 사람 객체인 man 객체를 파괴합니다. 위에서 설명 했듯이 사람 객체이든 Object 객체이든 상관 없이 freeObject() 함수를 호출해서 생성된 객체를 파괴합니다.그럼 freeObject() 함수를 분석해 보기로 하져. 94 라인부터 선언되어 있습니다.102 라인에서 그 객체의 본질을 알아냅니다. Object 객체인지, 사람 객체인지..105 라인의 switch() 문에 의해서 그 객체의 본질에 맞는 파괴함수를 호출합니다. 그렇게 함으로써 그 객체의 속성 중에서 메모리를 할당 받은 것을 빠짐 없이 해제할 수가 있습니다.114 라인에서 마지막으로 create*() 함수군에 의해서 생성된 객체의 메모리를 해제합니다.소스 코드에 자세하게 주석이 적혀 있으므로 코드 분석과 주석을 같이 보면서코드를 살펴보면 내용을 이해할 수가 있을 겁니다.위의 내용이 잘 이해가 가지 않으면 몇번이고 설명을 다시 읽어보시고,포인터의 가장 기본적인 개념이었던 1, 2번 강좌의 그림을 그려보시면서 이해를해보세여. 시간이 걸리더라도 직접 종이에 그림을 그려가며 이해를 해야합니다.이번 강좌는 구조체와 포인터를 이용하여 객체 지향적인 프로그래밍에 대해서 접해봤습니다.예제 코드도 그리 길지는 않았지만 상당히 많은 내용을 담고 있습니다.다음 강좌는 이번 강좌의 예제 코드를 더 확장하여 사람, 학생, 선생님, 상인,운송수단, 버스, 택시 객체들을 만들어 봄으로써 실제 객체가 어떻게 생성이 되고어떻게 사용이 되는지에 대해서 알아보도록 하겠습니다.
 
 
 
포인터 8번째 강좌입니다.저번 강좌에서 객체 지향에 대해서 알아보았습니다.이번 강좌에서는 조금 더 복잡한 객체의 구조를 만들어 보겠습니다.그럼 우선 다음 소스코드를 보세여.코드가 꽤 깁니다. 약 1000 라인 정도 되는 군여.. 주석문때문에 그래여 ;)
1#include<stdio.h>2#include<stdlib.h>/*malloc(), free() */3#include<string.h>/*strlen(), strcpy() */45/*6* 매크로 상수 선언7*/8#define BUS_FARE600/*버스의 요금 */9#define TAXI_FARE1500/*택시의 기본 요금 */10#define BUS_MAX_CAPACITY50/*버스의 최대인원 */1112/*13* 매크로 함수 선언14*/15#define OBJECT(object)      ((Object *)(object))16#define MANKIND(object)     ((Mankind *)(object))17#define STUDENT(object)     ((Student *)(object))18#define TEACHER(object)     ((Teacher *)(object))19#define MERCHANT(object)    ((Merchant *)(object))20#define VEHICLE(object)     ((Vehicle *)(object))21#define BUS(object)         ((Bus *)(object))22#define TAXI(object)        ((Taxi *)(object))2324/*25* 구조체 및 열거형 상수 선언26*/27typedefstruct_object      Object;2829typedefstruct_mankind     Mankind;30typedefstruct_student     Student;31typedefstruct_teacher     Teacher;32typedefstruct_merchant    Merchant;3334typedefstruct_vehicle     Vehicle;35typedefstruct_bus         Bus;36typedefstruct_taxi        Taxi;3738typedefenum_type          Type;39typedefenum_major         Major;40typedefenum_taxiCallType  TaxiCallType;414243/*객체들의 종류를 열거형으로 선언 */44enum_type45{46OBJECT_TYPE,/*객체     타입 */47MANKIND_TYPE,/*사람     타입 */48STUDENT_TYPE,/*학생     타입 */49TEACHER_TYPE,/*선생님   타입 */50MERCHANT_TYPE,/*상인     타입 */51VEHICLE_TYPE,/*운송수단 타입 */52BUS_TYPE,/*버스     타입 */53TAXI_TYPE/*택시     타입 */54};5556/*선생님의 전공과목을 열거형으로 선언 */57enum_major58{59MAJOR_KOREAN,/*국어 전공 */60MAJOR_ENGLISH,/*영어 전공 */61MAJOR_MATH,/*수학 전공 */62MAJOR_HISTORY,/*역사 전공 */63MAJOR_MUSIC/*음악 전공 */64};6566/*택시를 타게된 경위를 열거형으로 선언 */67enum_taxiCallType68{69TAXI_NORMAL_TYPE,/*보통 택시를 잡는 경우 */70TAXI_CALL_TYPE/*택시를 호출했을 경우  */71};7273/*객체의 가장 기본이 되는 구조체74* C++의 용어로 하자면 수퍼클래스가 되네여.75*/76struct_object77{78Type            type;/*객체의 종류      */79unsignedintID;/*객체의 일련 번호 */80};8182/*사람 구조체.83* Object 의 속성을 상속받는다.84*/85struct_mankind86{87Object  object;/*Object 속성 상속 */88char*  name;/*이름             */89intheight;/*키               */90intweight;/*몸무게           */91};9293/*학생 구조체.94* 사람의 속성을 상속받는다.95*/96struct_student97{98Mankind         mankind;/*Mankind 속성을 상속 */99char*          schoolName;/*학교 이름           */100unsignedlongsNumber;/*학생 번호           */101intscore;/*성적                */102};103104/*선생님 구조체.105* 사람의 속성을 상속받는다.106*/107struct_teacher108{109Mankind         mankind;/*Mankind 속성을 상속 */110char*          schoolName;/*학교 이름           */111unsignedlongtNumber;/*교원 번호           */112Major           major;/*전공 과목           */113};114115/*상인 구조체.116* 사람의 속성을 상속받는다.117*/118struct_merchant119{120Mankind         mankind;/*Mankind 속성을 상속 */121char*          storeName;/*점포 이름           */122unsignedlongmNumber;/*점포 번호           */123intprofits;/*년수익 (만원 단위)  */124};125126/*운송 매체 구조체.127* 객체의 속성을 상속받는다.128*/129struct_vehicle130{131Object      object;/*Object 속성을 상속         */132intwheelNumber;/*바퀴 개수                  */133intsize;/*운송 매체의 크기 (cm 단위) */134intweight;/*운송 매체의 무게 (kg 단위) */135};136137/*버스 구조체.138* 운송 매체의 속성을 상속 받는다.139*/140struct_bus141{142Vehicle     vehicle;/*Vehicle 속성을 상속    */143intmaxCapacity;/*실을 수 있는 최대 인원 */144intfare;/*요금 (원 단위)         */145};146147/*택시 구조체.148* 운송 매체의 속성을 상속 받는다.149*/150struct_taxi151{152Vehicle         vehicle;/*Vehicle 속성을 상속 */153intfare;/*요금 (원 단위)      */154intextra;/*할증 요금 (원 단위) */155TaxiCallType    state;/*호출을 했는지, 아니면 그냥 세웠는지 */156};157158/*159* 전역 변수 선언160*/161unsignedlong_objectID =0;162163164/*165* 함수의 원형(prototype) 선언166*/167168/*객체(Object) 관련 함수 */169voidsetObjectType( Object *object, Type type );170voidsetObjectID( Object *object );171Type        getObjectType( Object *object );172unsignedlonggetObjectID( Object *object );173voidfreeObject( Object *object );174175/*사람(Mankind) 관련 함수 */176voidsetMankindName( Mankind *mankind,char*name );177voidsetMankindHeight( Mankind *mankind,intheight );178voidsetMankindWeight( Mankind *mankind,intweight );179char*      getMankindName( Mankind *mankind );180intgetMankindHeight( Mankind *mankind );181intgetMankindWeight( Mankind *mankind );182voidfreeMankind( Mankind *mankind );183184/*학생(Student) 관련 함수 */185Object *    createStudent(void);186voidsetStudentSchoolName( Student *student,char*schoolName );187voidsetStudentNumber( Student *student,unsignedlongnumber );188voidsetStudentScore( Student *student,intscore );189char*      getStudentSchoolName( Student *student );190unsignedlonggetStudentNumber( Student *student );191intgetStudentScore( Student *student );192voidfreeStudent( Student *student );193194/*선생님(Teacher) 관련 함수 */195Object *    createTeacher(void);196voidsetTeacherSchoolName( Teacher *teacher,char*schoolName );197voidsetTeacherNumber( Teacher *teacher,unsignedlongnumber );198voidsetTeacherMajor( Teacher *teacher, Major major );199char*      getTeacherSchoolName( Teacher *teacher );200unsignedlonggetTeacherNumber( Teacher *teacher );201Major       getTeacherMajor( Teacher *teacher );202voidfreeTeacher( Teacher *teacher );203204/*상인(Merchant) 관련 함수 */205Object *    createMerchant(void);206voidsetMerchantStoreName( Merchant *merchant,char*storeName );207voidsetMerchantNumber( Merchant *merchant,unsignedlongnumber );208voidsetMerchantProfits( Merchant *merchant,intprofits );209char*      getMerchantStoreName( Merchant *merchant );210unsignedlonggetMerchantNumber( Merchant *merchant );211intgetMerchantProfits( Merchant *merchant );212voidfreeMerchant( Merchant *merchant );213214/*운송수단(Vehicle) 관련 함수 */215voidsetVehicleWheelNumber( Vehicle *vehicle,intwheelNumber );216voidsetVehicleSize( Vehicle *vehicle,intsize );217voidsetVehicleWeight( Vehicle *vehicle,intweight );218intgetVehicleWheelNumber( Vehicle *vehicle );219intgetVehicleSize( Vehicle *vehicle );220intgetVehicleWeight( Vehicle *vehicle );221voidfreeVehicle( Vehicle *vehicle );222223/*버스(Bus) 관련 함수 */224Object *    createBus(void);225voidsetBusMaxCapacity( Bus *bus,intnumber );226voidsetBusFare( Bus *bus,intfare );227intgetBusMaxCapacity( Bus *bus );228intgetBusFare( Bus *bus );229voidfreeBus( Bus *bus );230231/*택시(Taxi) 관련 함수 */232Object *    createTaxi(void);233voidsetTaxiFare( Taxi *taxi,intfare );234voidsetTaxiExtraFare( Taxi *taxi,intfare );235voidsetTaxiState( Taxi *taxi, TaxiCallType type );236intgetTaxiFare( Taxi *taxi );237intgetTaxiExtraFare( Taxi *taxi );238TaxiCallType getTaxiState( Taxi *taxi );239voidfreeTaxi( Taxi *taxi );240241242/*freeObject243*244* 생성된 객체의 할당받은 메모리를 해제한다.245* Object의 type을 조사하여 각 type에 맞는 해제 함수를 호출한다.246*/247voidfreeObject( Object *object )248{249/*객체(object)의 type을 구한다. */250Type type = getObjectType( object );251252switch( type )253{254caseOBJECT_TYPE :255break;256caseMANKIND_TYPE :257freeMankind( MANKIND(object) );break;258caseSTUDENT_TYPE :259freeStudent( STUDENT(object) );break;260caseTEACHER_TYPE :261freeTeacher( TEACHER(object) );break;262caseMERCHANT_TYPE :263freeMerchant( MERCHANT(object) );break;264caseVEHICLE_TYPE :265freeVehicle( VEHICLE(object) );break;266caseBUS_TYPE :267freeBus( BUS(object) );break;268caseTAXI_TYPE :269freeTaxi( TAXI(object) );break;270}271272/*마지막으로 각 type의 크기만큼 할당된 메모리를 해제한다. */273free( object );274}275276/*setObjectType277*278* 객체의 타입을 설정한다.279*/280voidsetObjectType( Object *object, Type type )281{282object->type = type;283}284285/*setObjectID286*287* 객체의 고유 일련번호를 설정한다.288* 이 함수는 사용자가 마음대로 사용할 목적이 아니라,289* 객체가 생성되었을 때 한번 호출해서 객체에게 일련번호를290* 부여하는 것이다.291*292* 전역변수인 _objectID은 객체에게 일련번호를 부여하고 난 후,293* 1씩 증가한다.294*/295voidsetObjectID( Object *object )296{297object->ID = _objectID++;298}299300/*getObjectType301*302* 객체의 type을 구한다.303* 객체의 type은 객체의 특성을 말한다.304*305* 객체 생성함수에 따라서 그 객체의 type은 설정된다.306*/307Type getObjectType( Object *object )308{309returnobject->type;310}311312/*getObjectID313*314* 객체의 고유 일련번호(ID)를 구한다.315*/316unsignedlonggetObjectID( Object *object )317{318returnobject->ID;319}320321/*setMankindName322*323* 사람 객체의 이름속성을 설정한다.324*/325voidsetMankindName( Mankind *mankind,char*name )326{327mankind->name = (char*)malloc( strlen(name) +1);328if( mankind->name ==NULL)return;329strcpy( mankind->name, name );330}331332/*getMankindName333*334* 사람 객체의 이름 속성을 구한다.335*/336char*getMankindName( Mankind *mankind )337{338returnmankind->name;339}340341/*setMankindHeight342*343* 사람 객체의 키 속성을 설정한다.344*/345voidsetMankindHeight( Mankind *mankind,intheight )346{347mankind->height = height;348}349350/*getMankindHeight351*352* 사람 객체의 키 속성을 구한다.353*/354intgetMankindHeight( Mankind *mankind )355{356returnmankind->height;357}358359/*setMankindWeight360*361* 사람 객체의 몸무게 속성을 설정한다.362*/363voidsetMankindWeight( Mankind *mankind,intweight )364{365mankind->weight = weight;366}367368/*getMankindWeight369*370* 사람 객체의 몸무게 속성을 구한다.371*/372intgetMankindWeight( Mankind *mankind )373{374returnmankind->weight;375}376377/*freeMankind378*379* 사람 객체의 속성을 해제한다.380*381* 객체의 속성을 해제하는 free*() 함수들은382* 각 객체의 속성중에서 malloc()에 의해서 메모리를 할당 받은 속성에 대한383* 메모리 해제가 이루어진다.384*385* 각 객체의 파괴함수는 자기의 부모 객체에서 물려받은 속성들의 메모리를386* 해제하기 위하여 부모 객체의 파괴함수를 호출한다.387* 여기서 Object에 대한 파괴함수는 대표 파괴함수 이므로388* 가장 상단에 존재하고 있는 Object 객체의 파괴함수는 없다.389*390* freeObject() 함수는 어떤 type을 가진 객체라도 파괴할 수 있도록391* 각 객체의 type을 조사하여 그 객체의 파괴함수를 호출한다.392*/393voidfreeMankind( Mankind *mankind )394{395/*사람 객체의 이름 속성이 NULL이 아니면 메모리를 해제한다. */396if( mankind->name !=NULL)397free( mankind->name );398}399400/*createStudent401*402* 학생 객체를 생성하는 함수이다.403*404* 모든 객체는 create*() 함수로 생성되어지며, 그 객체의 데이터 형은405* Object * 이다.406* 모든 객체의 구조체 타입이 다 다르지만 Object 구조체로 모든 객체를407* 다룰 수 있도록 하기 위해서 메모리는 sizeof(Student) 만큼 할당되지만408* 그 할당 받은 메모리를 (Object *)로 캐스팅한다.409*410* 그렇다고 해서 할당받은 메모리의 크기가 변한다던지 하는게 아니라411* 단지 그 할당받은 메모리를 참조하기 위한 index로 Object 구조체를412* 사용하겠다는 것만 알려주는 것일 뿐이다.413*414* 실제로 Student 구조체에 있는 속성을 사용하기 위해서,415* (object *)로 캐스팅된 메모리는 다시 (Student *)로 캐스팅되야 한다.416* STUDENT(object) 매크로 함수를 사용해도 된다.417*418* 이렇게 캐스팅을 통해서,419* 많은 종류의 객체들을 대표적인 객체 제어 함수로 제어를 할 수 있게 된다.420* 예를 들면 어떤 타입의 객체라도 freeObject() 함수로 파괴를 할 수가421* 있게 되는 것이다.422*/423Object *createStudent(void)424{425/*Student 구조체 크기 만큼의 메모리를 할당 받은 후426* (Object *)로 캐스팅 한다.427*/428Object *student = (Object *)malloc(sizeof(Student) );429if( student ==NULL)returnNULL;430431/*할당 받은 메모리를 0 으로 초기화 한다. */432memset( student,0,sizeof(Student) );433434/*생성된 객체의 type을 STUDENT_TYPE으로 설정한다.435* 즉, 이 객체는 학생 객체임을 뜻한다.436*/437setObjectType( student, STUDENT_TYPE );438439/*생성된 객체의 고유 일련번호를 부여한다. */440setObjectID( student );441442returnstudent;443}444445/*setStudentSchoolName446*447* 학생 객체의 학교 이름 속성을 설정한다.448*/449voidsetStudentSchoolName( Student *student,char*schoolName )450{451/*schoolName 인자의 문자열 길이 + 1 만큼 메모리를 할당한다.452* 1을 더하는 이유는 문자열 마지막에 NULL을 저장하기 위함이다.453*/454student->schoolName = (char*)malloc( strlen(schoolName) +1);455if( student->schoolName ==NULL)return;456457/*학생 객체의 학교이름 속성에 schoolName을 복사한다. */458strcpy( student->schoolName, schoolName );459}460461/*getStudentSchoolName462*463* 학생 객체의 학교 이름 속성을 구한다.464*/465char*getStudentSchoolName( Student *student )466{467returnstudent->schoolName;468}469470/*setStudentNumber471*472* 학생 객체의 학생 번호 속성을 설정한다.473*/474voidsetStudentNumber( Student *student,unsignedlongnumber )475{476student->sNumber = number;477}478479/*getStudentNumber480*481* 학생 객체의 학생 번호 속성을 구한다.482*/483unsignedlonggetStudentNumber( Student *student )484{485returnstudent->sNumber;486}487488/*setStudentScore489*490* 학생 객체의 성적 속성을 설정한다.491*/492voidsetStudentScore( Student *student,intscore )493{494student->score = score;495}496497/*getStudentScore498*499* 학생 객체의 성적 속성을 구한다.500*/501intgetStudentScore( Student *student )502{503returnstudent->score;504}505506/*freeStudent507*508* 학생 객체의 속성을 해제하는 파괴함수.509*510* 학생의 부모 객체는 사람 객체이므로 학생에 대한 속성을 해제한 후,511* 사람 객체 파괴함수를 호출한다.512* 이렇게 함으로써 자칫 메모리 누수가 되는 것을 꼼꼼이 방지할 수 있다.513*/514voidfreeStudent( Student *student )515{516/*517* 학생 객체의 속성을 파괴518*/519520/*학교 이름 속성 해제 */521if( student->schoolName !=NULL)522free( student->schoolName );523524/*525* 사람 객체 파괴 함수 호출526*/527freeMankind( MANKIND(student) );528}529530/*createTeacher531*532* 선생님 객체를 생성하는 함수이다.533*/534Object *createTeacher(void)535{536/*Teacher 구조체 크기 만큼의 메모리를 할당 받은 후537* (Object *)로 캐스팅 한다.538*/539Object *teacher = (Object *)malloc(sizeof(Teacher) );540if( teacher ==NULL)returnNULL;541542/*할당 받은 메모리를 0으로 초기화 한다. */543memset( teacher,0,sizeof(Teacher) );544545/*생성된 객체의 type을 TEACHER_TYPE으로 설정한다.546* 즉, 이 객체는 선생님 객체임을 뜻한다.547*/548setObjectType( teacher, TEACHER_TYPE );549550/*생성된 객체의 고유 일련번호를 부여한다. */551setObjectID( teacher );552553returnteacher;554}555556/*setTeacherSchoolName557*558* 선생님 객체의 학교 이름 속성을 설정한다.559*/560voidsetTeacherSchoolName( Teacher *teacher,char*schoolName )561{562/*학교 이름 속성을 저장하기 위해 메모리를 할당한다. */563teacher->schoolName = (char*)malloc( strlen(schoolName) +1);564if( teacher->schoolName ==NULL)return;565566/*속성에 학교 이름을 복사한다. */567strcpy( teacher->schoolName, schoolName );568}569570/*getTeacherSchoolName571*572* 선생님 객체의 학교 이름 속성을 구한다.573*/574char*getTeacherSchoolName( Teacher *teacher )575{576returnteacher->schoolName;577}578579/*setTeacherNumber580*581* 선생님 객체의 교원 번호 속성을 설정한다.582*/583voidsetTeacherNumber( Teacher *teacher,unsignedlongnumber )584{585teacher->tNumber = number;586}587588/*getTeacherNumber589*590* 선생님 객체의 교원 번호 속성을 구한다.591*/592unsignedlonggetTeacherNumber( Teacher *teacher )593{594returnteacher->tNumber;595}596597/*setTeacherMajor598*599* 선생님 객체의 전공 과목 속성을 설정한다.600*/601voidsetTeacherMajor( Teacher *teacher, Major major )602{603teacher->major = major;604}605606/*getTeacherMajor607*608* 선생님 객체의 전공 과목 속성을 구한다.609*/610Major getTeacherMajor( Teacher *teacher )611{612returnteacher->major;613}614615/*freeTeacher616*617* 선생님 객체의 속성을 해제하는 파괴함수.618* 선생님의 부모 객체는 사람 객체이므로 선생님에 대한 속성을 해제한 후,619* 사람 객체 파괴함수를 호출한다.620*/621voidfreeTeacher( Teacher *teacher )622{623/*624* 선생님 객체의 속성을 파괴625*/626627/*학교 이름 속성을 해제 */628if( teacher->schoolName !=NULL)629free( teacher->schoolName );630631/*632* 사람 객체 파괴 함수 호출633*/634freeMankind( MANKIND(teacher) );635}636637/*createMerchant638*639* 상인 객체 생성 함수이다.640*/641Object *createMerchant(void)642{643/*Merchant 구조체 크기 만큼의 메모리를 할당 받은 후644* (Object *)로 캐스팅 한다.645*/646Object *merchant = (Object *)malloc(sizeof(Merchant) );647if( merchant ==NULL)returnNULL;648649/*할당 받은 메모리를 0 으로 초기화 한다. */650memset( merchant,0,sizeof(Merchant) );651652/*생성된 객체의 type을 MERCHANT_TYPE으로 설정한다.653* 즉, 이 객체는 상인 객체임을 뜻한다.654*/655setObjectType( merchant, MERCHANT_TYPE );656657/*생성된 객체의 고유 일련번호를 부여한다. */658setObjectID( merchant );659660returnmerchant;661}662663/*setMerchantStoreName664*665* 상인 객체의 점포 이름 속성을 설정한다.666*/667voidsetMerchantStoreName( Merchant *merchant,char*storeName )668{669/*점포 이름 속성을 저장하기 위해 메모리를 할당한다. */670merchant->storeName = (char*)malloc( strlen(storeName) +1);671if( merchant->storeName ==NULL)return;672673/*속성에 점포 이름을 복사한다. */674strcpy( merchant->storeName, storeName );675}676677/*getMerchantStoreName678*679* 상인 객체의 점포 이름 속성을 구한다.680*/681char*getMerchantStoreName( Merchant *merchant )682{683returnmerchant->storeName;684}685686/*setMerchantNumber687*688* 상인 객체의 점포 번호 속성을 설정한다.689*/690voidsetMerchantNumber( Merchant *merchant,unsignedlongnumber )691{692merchant->mNumber = number;693}694695/*getMerchantNumber696*697* 상인 객체의 점포 번호 속성을 구한다.698*/699unsignedlonggetMerchantNumber( Merchant *merchant )700{701returnmerchant->mNumber;702}703704/*setMerchantProfits705*706* 상인 객체의 연수익 속성을 설정한다.707*/708voidsetMerchantProfits( Merchant *merchant,intprofits )709{710merchant->profits = profits;711}712713/*getMerchantProfits714*715* 상인 객체의 연수익 속성을 구한다.716*/717intgetMerchantProfits( Merchant *merchant )718{719returnmerchant->profits;720}721722/*freeMerchant723*724* 상인 객체의 속성을 해제하는 파괴함수.725* 상인의 부모 객체는 사람 객체이므로 상인에 대한 속성을 해제한 후,726* 사람 객체 파괴함수를 호출한다.727*/728voidfreeMerchant( Merchant *merchant )729{730/*731* 상인 객체의 속성을 파괴732*/733734/*점포 이름 속성을 해제 */735if( merchant->storeName !=NULL)736free( merchant->storeName );737738/*739* 사람 객체의 파괴 함수 호출740*/741freeMankind( MANKIND(merchant) );742}743744/*setVehicleWheelNumber745*746* 운송 매체 객체의 바퀴 개수 속성을 설정한다.747*/748voidsetVehicleWheelNumber( Vehicle *vehicle,intwheelNumber )749{750vehicle->wheelNumber = wheelNumber;751}752753/*getVehicleWheelNumber754*755* 운송 매체 객체의 바퀴 개수 속성을 구한다.756*/757intgetVehicleWheelNumber( Vehicle *vehicle )758{759returnvehicle->wheelNumber;760}761762/*setVehicleSize763*764* 운송 매체 객체의 크기 속성을 설정한다.765*/766voidsetVehicleSize( Vehicle *vehicle,intsize )767{768vehicle->size = size;769}770771/*getVehicleSize772*773* 운송 매체 객체의 크기 속성을 구한다.774*/775intgetVehicleSize( Vehicle *vehicle )776{777returnvehicle->size;778}779780/*setVehicleWeight781*782* 운송 매체 객체의 무게 속성을 설정한다.783*/784voidsetVehicleWeight( Vehicle *vehicle,intweight )785{786vehicle->weight = weight;787}788789/*getVehicleWeight790*791* 운송 매체 객체의 무게 속성을 구한다.792*/793intgetVehicleWeight( Vehicle *vehicle )794{795returnvehicle->weight;796}797798/*freeVehicle799*800* 운송 매체 객체의 속성을 해제하는 파괴함수.801*/802voidfreeVehicle( Vehicle *vehicle )803{804/*805* 운송 매체 객체의 속성을 파괴806*/807}808809/*createBus810*811* 버스 객체를 생성하는 함수이다.812*/813Object *createBus(void)814{815/*Bus 구조체 크기 만큼의 메모리를 할당 받은 후816* (Object *)로 캐스팅 한다.817*/818Object *bus = (Object *)malloc(sizeof(Bus) );819if( bus ==NULL)returnNULL;820821/*할당받은 메모리를 0으로 초기화 한다. */822memset( bus,0,sizeof(Bus) );823824/*생성된 객체의 type을 BUS_TYPE으로 설정한다.825* 즉, 이 객체는 버스 객체임을 뜻한다.826*/827setObjectType( bus, BUS_TYPE );828829/*생성된 객체의 고유 일련번호를 부여한다. */830setObjectID( bus );831832/*833* 버스의 부모 객체인 Vehicle 속성을 설정한다.834* 속성의 일반적인 값을 초기값으로 설정을 해주는 것이다.835*/836setVehicleWheelNumber( VEHICLE(bus),4);837setVehicleSize( VEHICLE(bus),5000);838setVehicleWeight( VEHICLE(bus),2500);839840/*841* 버스 객체의 속성을 설정한다.842* 이것 역시 일반 적인 값을 초기값으로 설정을 하는 것이다.843*/844setBusMaxCapacity( BUS(bus), BUS_MAX_CAPACITY );845setBusFare( BUS(bus), BUS_FARE );846847returnbus;848}849850/*setBusMaxCapacity851*852* 버스 객체의 최대 인원 속성을 설정한다.853*/854voidsetBusMaxCapacity( Bus *bus,intnumber )855{856bus->maxCapacity = number;857}858859/*getBusMaxCapacity860*861* 버스 객체의 최대 인원 속성을 구한다.862*/863intgetBusMaxCapacity( Bus *bus )864{865returnbus->maxCapacity;866}867868/*setBusFare869*870* 버스 객체의 요금 속성을 설정한다.871*/872voidsetBusFare( Bus *bus,intfare )873{874bus->fare = fare;875}876877/*getBusFare878*879* 버스 객체의 요금 속성을 구한다.880*/881intgetBusFare( Bus *bus )882{883returnbus->fare;884}885886/*freeBus887*888* 버스 객체의 속성을 해제하는 파괴함수.889*/890voidfreeBus( Bus *bus )891{892/*893* 버스 객체의 속성을 파괴894*/895896/*897* 운송 매체 객체의 파괴 함수 호출898*/899freeVehicle( VEHICLE(bus) );900}901902/*createTaxi903*904* 택시 객체를 생성하는 함수905*/906Object *createTaxi(void)907{908/*Taxi 구조체 크기 만큼의 메모리를 할당 받은 후909* (Object *)로 캐스팅 한다.910*/911Object *taxi = (Object *)malloc(sizeof(Taxi) );912if( taxi ==NULL)returnNULL;913914/*할당 받은 메모리를 0 으로 초기화 한다. */915memset( taxi,0,sizeof(Taxi) );916917/*생성된 객체의 type을 TAXI_TYPE으로 설정한다.918* 즉, 이 객체는 택시 객체임을 뜻한다.919*/920setObjectType( taxi, TAXI_TYPE );921922/*생성된 객체의 고유 일련번호를 부여한다. */923setObjectID( taxi );924925/*926* 택시의 부모 객체인 Vehicle 속성을 설정한다.927* 속성의 일반적인 값을 초기값으로 설정을 해주는 것이다.928*/929setVehicleWheelNumber( VEHICLE(taxi),4);930setVehicleSize( VEHICLE(taxi),3000);931setVehicleWeight( VEHICLE(taxi),700);932933/*934* 택시 객체의 속성을 설정한다.935* 이것 역시 일반 적인 값을 초기값으로 설정을 하는 것이다.936*/937setTaxiFare( TAXI(taxi), TAXI_FARE );938setTaxiFare( TAXI(taxi),0);939setTaxiFare( TAXI(taxi), TAXI_NORMAL_TYPE );940941returntaxi;942}943944/*setTaxiFare945*946* 택시 객체의 요금 속성을 설정한다.947*/948voidsetTaxiFare( Taxi *taxi,intfare )949{950taxi->fare = fare;951}952953/*getTaxiFare954*955* 택시 객체의 요금 속성을 구한다.956*/957intgetTaxiFare( Taxi *taxi )958{959returntaxi->fare;960}961962/*setTaxiExtraFare963*964* 택시 객체의 할증 요금 속성을 설정한다.965*/966voidsetTaxiExtraFare( Taxi *taxi,intfare )967{968taxi->extra = fare;969}970971/*getTaxiExtraFare972*973* 택시 객체의 할증 요금 속성을 구한다.974*/975intgetTaxiExtraFare( Taxi *taxi )976{977returntaxi->extra;978}979980/*setTaxiState981*982* 택시 객체의 호출 상태 속성을 설정한다.983*/984voidsetTaxiState( Taxi *taxi, TaxiCallType type )985{986taxi->state = type;987}988989/*getTaxiState990*991* 택시 객체의 호출 상태 속성을 구한다.992*/993TaxiCallType getTaxiState( Taxi *taxi )994{995returntaxi->state;996}997998/*freeTaxi999*1000* 택시 객체의 파괴 함수.1001*/1002voidfreeTaxi( Taxi *taxi )1003{1004/*1005* 택시 객체의 속성을 파괴1006*/10071008/*1009* 운송 매체 객체의 파괴 함수 호출1010*/1011freeVehicle( VEHICLE(taxi) );1012}10131014/*main1015*1016* 원래 main() 함수의 원형은,1017*1018* int main( int argc, char **argv ); 입니다.1019*1020* argc는 자신을 포함한 인자의 개수를 뜻하고,1021* argv는 자신의 이름과 함께 인자들의 문자열을 갖고 있습니다.1022*1023* 예를 들어서 프로그램의 실행파일 이름이 exam 이라고 할때,1024*1025* $ exam a b c1026*1027* 이렇게 실행을 하게 되면 argc는 4가 되고,1028* argv[0] == "exam"1029* argv[1] == "a"1030* argv[2] == "b"1031* argv[3] == "c"1032*1033* 이렇게 되는 것이지여.1034*1035* 여기서는 main() 함수의 인자를 void 형으로 했습니다.1036* 그 이유는 인자를 받을 필요가 없기 때문이지여.1037*1038* main() 함수의 return 형이 int 라는 것을 주지하시기 바랍니다.1039* main() 함수도 OS(운영체제)의 입장에서 본다면 하나의 Sub function에1040* 해당합니다. 그리고 main() 함수가 일을 다 마치고 난다음 프로그램을 종료할때1041* OS에게 종료 값을 리턴합니다. OS가 그 종료코드를 사용할 수 있도록 하는1042* 것이지여. 따라서 void main(); 과 같은 코드를 사용하지 마시고,1043* int main(void); 혹은 int main( int argc, char **argv ); 와 같은 코드를1044* 사용하도록 습관을 들이는 것이 중요합니다.1045*/1046intmain(void)1047{1048Object *student =NULL;1049Object *teacher =NULL;1050Object *merchant =NULL;1051Object *bus =NULL;1052Object *taxi =NULL;10531054student = createStudent();/*학생 객체 생성   */1055teacher = createTeacher();/*선생님 객체 생성 */1056merchant = createMerchant();/*상인 객체 생성   */1057bus = createBus();/*버스 객체 생성   */1058taxi = createTaxi();/*택시 객체 생성   */10591060printf("student ID =%ld\n", getObjectID(student) );1061printf("teacher ID =%ld\n", getObjectID(teacher) );1062printf("merchant ID =%ld\n", getObjectID(merchant) );1063printf("bus ID =%ld, size =%dcm, weight =%dkg\n",1064getObjectID(bus), getVehicleSize(VEHICLE(bus)),1065getVehicleWeight(VEHICLE(bus)) );1066printf("taxi ID =%ld, size =%dcm, weight =%dkg\n",1067getObjectID(taxi), getVehicleSize(VEHICLE(taxi)),1068getVehicleWeight(VEHICLE(taxi)) );10691070freeObject(student);/*학생 객체 파괴   */1071freeObject(teacher);/*선생님 객체 파괴 */1072freeObject(merchant);/*상인 객체 파괴   */1073freeObject(bus);/*버스 객체 파괴   */1074freeObject(taxi);/*택시 객체 파괴   */10751076/*makes compiler happy :) */1077return0;1078}
위의 코드는 설명을 하기 위한 코드라서 하나의 파일로 코딩했습니다. 실제로 코딩을 할 때에는 각각의 객체마다 헤더 파일과 소스 코드 파일을 따로 만들어서 관리를합니다. 그래야 나중에 확장을 시키거나 디버깅을 할때 편리합니다.이번 강좌의 예제는 바로 전 강좌인 7번째 강좌에 나왔던 예제를 좀 더 심화시킨 것입니다. 저번 강좌의 예제를 이해 하셨다면 이번 예제도 그리 어려울게 없습니다.그럼 우선 위의 객체 구조도를 그려보겠습니다. +----------+ | Object | +----+-----+ | +-----------+ +--------| Mankind | | +-----+-----+ | | +------------+ | +--------| Student | | | +------------+ | | +------------+ | +--------| Teacher | | | +------------+ | | +------------+ | +--------| Merchant | | +------------+ | +-----------+ +--------| Vehicle | +-----+-----+ | +------------+ +--------| Bus | | +------------+ | +------------+ +--------| Taxi | +------------+Object 가 가장 상위 객체이며 Object 객체를 Mankind와 Vehicle 객체가 상속받습니다. Mankind(사람) 객체를 Student(학생), Teacher(선생님), Merchant(상인) 객체가다시 상속을 받고, Vehicle(운송수단) 객체를 Bus(버스), Taxi(택시) 객체가 다시상속을 받습니다.1046 라인의 main() 함수에서 하는 일은 student, teacher, merchant, bus, taxi 객 체를 하나씩 만들고 그 객체들의 ID 및 기본 정보를 출력하고 나서 각 객체를 파괴하는 것입니다. 모든 객체들의 생성 함수는 Object 포인터 타입을 리턴함으로써 모든 객체를 Object 포인터로 다룰 수 있게 하고 있습니다. 7번째 강좌에서도 설명 했듯이 많은 종류의 객체들을 전부 다른 형식으로 다루어야 한다면 아주 많이 불편할 것입니다. 라이브러리를 만들어야 하는 프로그래머라면 이렇게 객체 지향으로 프로그래밍하는 것이 훨씬 복잡하고 코드의 길이도 길어지지만 그렇게 작성된 라이브러리를 사용해서 프로그래밍하는 프로그래머는 프로그램을 작성 하는데 훨씬 간편하고 쉽게 작성을 할 수가 있는 것입니다. 247 라인의 freeObject() 함수를 보면, 내부에서 각각의 객체 속성에 맞게 파괴 함 수를 호출해 주는 방식으로 생성된 모든 객체는 freeObject() 함수만 호출함으 로써 파괴할 수가 있는 것입니다.이러한 이점 외에도 객체 지향 프로그래밍의 장점은 프로그래밍의 방법이 바뀔 수가있다는 점입니다. 기존의 프로그래밍에서는 데이터와 코드 및 action(계산 등등)등이 분리가 되지 않고 서로 복잡하게 얽혀 있었습니다. 하지만 이렇게 객체 지향적인프로그래밍을 하게 되면 데이터와 동작의 분리를 할 수가 있고 그렇게 함으로써 부가적으로 디버깅 시간의 단축 및 인력을 줄일 수가 있는 것입니다.물론 프로그래밍 언어에서 객체 지향 문법을 지원해야만 더 효율적으로 객체 관리가되겠지만 C++이 아닌 C로 프로그래밍을 하더라도 객체 지향적인 개념을 도입해서 프로그래밍을 한다면 충분히 이점을 끌어 낼 수가 있습니다.각 객체가 어떻게 생성이 되고 어떻게 사용이 되어지며 어떻게 파괴가 되는지에 대해서 다시 한번 7번째 강좌와 이번 강좌를 읽어보시고 그 객체들을 나타내기 위해서사용된 포인터에 대해서 꼼꼼히 체크를 해보시기 바랍니다.저번 강좌에서 객체를 나타내기 위해서 사용된 포인터에 대해서 설명을 했으므로 이번 강좌에서는 어떻게 확장을 해 나갈 수 있는지에 대해서 설명을 하겠습니다.Student 객체를 만들기 위해서 Student 구조체를 만들었는데 이 구조체를 한 번 분석 하도록 하겠습니다.96 라인에 Student 구조체가 있습니다. 우선 Student(학생) 이라는 객체는 사람에서파생이 되어지는 것입니다. 그러므로 Student 구조체 내에 Mankind 변수를 하나 만들어서 Mankind(사람) 객체의 속성을 상속받아야 합니다. 그렇게 되면 Mankind가 상속받은 Object 객체의 속성들도 자연히 따라서 상속되겠져? 그리고 Mankind 객체가갖고 있지 않는 학생만의 고유한 속성을 부가적으로 선언해 주면 되는 것입니다.새로운 객체를 만들 때 새로 만들 객체는 어떤 객체에게 속성을 상속 받아야 하는지를 잘 생각해서 상속을 받으면 객체를 새로 만드는 것이 그렇게 어렵지 않습니다.예를 들어서 초등학생, 중학생, 고등학생, 대학생 과 같은 객체를 타나내기 위해서구조체를 만든다고 생각해보면 당연히 새로 만들 객체들은 Student 객체를 상속받으면 편리하다는 것을 알수 있겠져? 그리고 부모 객체에 대한 함수를 사용할 때에는 생성된 객체의 포인터 타입을 부모의 객체 타입으로 바꾼 다음에 부모 객체를 위한 함수를 호출 하면 되는 것입니다.예를 들면,Student 객체를 하나 생성했을 때, 몸무게를 설정해 보자구여.Student 객체의 속성에는 몸무게에 대한 속성이 없습니다. 몸무게에 대한 속성은 부모 객체인 Mankind 객체의 속성입니다. Student 객체는 Mankind 객체를 상속 받았으므로 몸무게에 대한 속성을 사용할 수가 있습니다. 다음과 같이여..setMankindWeight( MANKIND(student),70);student라는 학생 객체를 Mankind 객체로 캐스팅해서 setMankindWeight() 함수의 인자로 보내면 되는 거져.C를 사용한 객체지향은 아무래도 언어 자체가 객체지향 문법을 지원하지 않기 때문에 불편한 점이 많습니다. 상속받은 속성들이 어떤 객체의 속성인지를 알고 있어야그에 맞는 함수를 호출 할 수가 있기 때문입니다. 위에서 몸무게에 대한 속성이 어떤 객체의 속성인지를 알아야 setMankindWeight()과 같은 함수를 호출 할 수가 있게되는 것이지여. 그래서 함수는 많아지지만 각 객체의 함수로 또 다시 작성을 해 주는 경우가 많습니다.예를 들어서 setMankindWeight() 함수는 Mankind 객체의 함수이므로 Student 객체의몸무게를 설정하기 위해서 setStudentWeight()과 같은 함수를 만들어 주게 되는 것입니다.
voidsetStudentWeight( Student *student,intweight ){    setMankindWeight( MANKIND(student), weight );}
이런식으로 함수를 작성해 놓으면 학생 객체의 몸무게를 설정할 때 다음과 같이 함수를 호출하면 되는 것이지여.setStudentWeight( STUDENT(student),70);물론 이렇게 되면 객체가 상속을 많이 하면 할 수록 함수는 점점 더 많아지겠져? 그렇기 때문에 정확한 객체지향을 하기 위해서는 객체 지향을 지원하는 문법을 갖고 있는 언어로 작성을 하는 것이 좋습니다. 하지만 각 객체와 속성들을 어떻게 설계하느냐에 따라서 C로도 훌륭하게 객체 지향 프로그래밍을 할 수가 있습니다.객체지향에 대해서는 이정도로만 마무리를 짓겠습니다. 물론 저 뒤에 나올 강좌에서또다시 언급이 될 것입니다. 포인터라는 것을 공부하는데는 아주 많은 분야의 내용들을 참조해야 할 것입니다. 포인터의 개념은 1번 강좌에서 이미 설명이 끝났지만그 포인터의 활용을 보면서 포인터의 본질에 더더욱 깊게 다가설 수 있으면 좋겠습니다.다음 강좌는 기본적인 자료구조인 링크드 리스트에 대해서 알아보겠습니다.
 
 
 
 
포인터 9번째 강좌입니다.저번 강좌까지 객체 지향에 대해서 조금 알아보았습니다.프로그래밍을 할 때 많이 사용되는 자료 구조로는 스택(stack), 큐(queue),  링크드리스트(linked list), 트리(tree) 등이 있습니다. 그 외에도 탐색이나 정렬 등을 하기 위한 기법들도 있지만, 자료구조를 위한 강좌가 아니므로 자료구조를 표현하는데있어서 사용되는 포인터에 초점을 두도록 하겠습니다.이번 강좌는 기본적인 자료구조인 링크드 리스트에 대해서 알아보겠습니다.데이터를 저장하기 위해서 변수를 사용하고,  더 많은 공통된 자료들을 묶기 위해서구조체를 사용하지여. 그리고 그 구조체 변수 배열을 선언 해서 많은 자료를 저장하고 사용합니다. 그런데 그 배열의 크기를 알고 있지 못할 때 최대로 큰 크기의 배열을  선언해서 작업을 하게 되면 자칫 많은 메모리 낭비를  초래할 수 있다는 얘기를앞에 있는 강좌에서 언급을 했습니다.그렇다면 동적으로 메모리를 할당하도록 하면 되지 않을까.. 라고 생각 하셨다면 그래도 50점은 맞았습니다. 동적 메모리로 할당을 하면 메모리의 낭비는 막을 수 있습니다. 그렇다면 다음과 같은 구조체가 있다고 가정을 해보겠습니다.struct_data{intnumber;intkind;charname[20];intkor;inteng;intmath;intscience;charbuffer[200];};그리고 위의 구조체를 사용해서 만개의 데이터를 표현하기 위해서 배열을 선언 하게되면 얼마의 메모리가 필요할까여?위의 구조체의 크기는 int형 데이터 6개(4*6 = 24) + char형 배열 220개. 따라서 총244 바이트가 됩니다. 만개의 배열을 잡으면 244만 바이트가 필요하져. 2메가바이트가 넘네여.. 배열로 하지 않고 데이터가 필요한 만큼 메모리 동적 할당을 한다고 하더라도 위와 같이 만개의 데이터가 필요하다고 하면  2메가가 넘는 메모리가 한번에할당이 되어야 하는 것입니다.현재 사용 가능한 메모리가 50메가 라고 해서  50메가를 한번에 동적 메모리 할당을할 수 있는 것은 아닙니다.  그 이유는 메모리의 중간 중간을 다른 프로그램이나 프로세스에서 할당 하고 해제하고 하는 일을 해서 메모리가 조각이 나있기 때문입니다그림으로 살펴보져.
1.0x0000(이라고 가정)+----------------------------------------------------------------------------+|                                                                            |+----------------------------------------------------------------------------+^                                                                            ^|                                                                            |+----------------------------- 50 Mbytes ------------------------------------+처음에 위와 같이 50 메가 바이트의 메모리가 있다고 가정하고 그 메모리는 아직 다른 프로세스에서 사용되어 지지 않은 얼마든지 할당이 가능한 상태입니다.
2.0x0000+---+------------------------------------------------------------------------+|||||                                                                        |+---+------------------------------------------------------------------------+^   ^                                                                        ^|   |                                                                        |+-+-+<-------------------------- 49 Mbytes --------------------------------->+  |  +--> 1 Mbyte1 메가 바이트만큼 어떤 프로세스에서 사용을 하고 있습니다. 즉 그 프로세스에서 1메가 바이트 만큼 메모리를 할당을 한 것입니다.
3.0x0000+---+---------------+--------------------------------------------------------+|||||///////////////|                                                        |+---+---------------+--------------------------------------------------------+^   ^               ^                                                        ^|   |               |                                                        |+-+-+<------+------>+<--------------------- 45 Mbytes ---------------------->+  |         |  .         .1 Mbyte  4 Mbytes 다시 4 메가 바이트만큼 다른 프로세스에서 사용하기 위해 메모리를 할당했습니다.
4.0x0000+---+---------------+--------------------------------------------------------+|   |///////////////|                                                        |+---+---------------+--------------------------------------------------------+    ^               ^                                                        ^    |               |                                                        |    +<------+------>+<--------------------- 45 Mbytes ---------------------->+            |            .         4 Mbytes 처음에 할당 했던 1 메가 바이트를 해제해서 사용 가능한 메모리로 돌려 놓았습니다.그래서 남은 할당 가능한 메모리는 46 메가 바이트입니다.  하지만 여기에서 문제는46 메가 바이트가 연속적으로 존재하지 않고 따로 떨어져 있다는 것입니다.  그렇기때문에  할당 가능한 가장 큰 크기는 45메가 바이트가 됩니다.  한번에 할당을 받을수 있으려면 메모리가 연속적으로 연결되어 있어야 하기 때문입니다.
위의 그림으로 남아 있는 메모리의 크기와 한번에 할당을 할 수 있는 메모리의 크기는 다른 것이라는 것을 알았습니다. 위의 그림은 단순화 해서 그린 그림이지만 실제로 많은 프로세스가 메모리를 할당하고 해제하는 일을 함으로써 메모리의 중간 중간에 구멍이 생기게 됩니다. 사용 가능한 메모리는 많은데 한번에 할당 할 수 있는 메모리의 크기는 그렇게 크지 않게 되는 그러한 현상을 메모리 단편화라고 합니다.이러한 메모리 단편화는 사실 메모리가 할당 되고 해제되면서 운영체제가 단편화된메모리의 조각들을 모아주는 일을 해줍니다. 메모리의 조각을 모아준다는 것은 비어있는 메모리 부분으로 현재 사용하고 있는 메모리를 옮겨주는 것을 말합니다. 디스크 조각 모음과 비슷한 개념으로 생각하면 됩니다. 그렇다 하더라도 사용 가능한 메모리의 크기와 한번에 할당할 수 있는 최대 메모리의 크기는 차이가 나게 됩니다.따라서 많은 데이터를 사용할 때, 한번에 큰 크기의 메모리를 할당하는 것이 아니라작은 데이터 단위의 크기로 메모리를 할당하고 그 할당된 많은 데이터들을 연결시켜서 사용을 하게됩니다. 바로 여기에서 링크드 리스트의 개념이 나오게 되는 것입니다.저 위의 구조체의 크기가 244 바이트라고 했었지여? 244 바이트 만큼의 메모리 할당을 만개를 하고 그 만개의 데이터를 서로 연결시켜주면 실제로 2메가 바이트를 한번에 할당하지는 않았지만 그와 같은 효과를 갖게 되는 것입니다. 물론 작은 단위로많은 할당을 했으므로 메모리는 더 작은 크기로 단편화가 됩니다. 그렇지만 위에서언급했듯이 운영체제에서 알아서 필요할 때 메모리 조각 모음을 해주므로 일단은 크게 염두하지 않고 많은 메모리를 사용할 수가 있게 됩니다.작은 데이터 단위의 메모리 할당은 그렇다 치는데 그럼 어떻게 그것들을 연결시킬까여? 위의 구조체를 아주 조금 변경시켜 보겠습니다.struct_data{intnumber;intkind;charname[20];intkor;inteng;intmath;intscience;charbuffer[200];struct_data *next;};구조체의 멤버 중에서 next 라는 자기 자신 참조 포인터가 선언되어 있습니다. 바로이 next 변수가 하는 일이 다른 작은 데이터 단위의 주소를 저장함으로써 연결을 하기 위한 변수입니다. 그림을 그려보겠습니다.0x1000 +---------+--------+ | | 0x1100 |-+ 0x1100 +---------+--------+ | +----------+--------+ next +--->| | 0x1200 |-+ 0x1200 +----------+--------+ | +---------+--------+ next +--->| | NULL | +---------+--------+위의 그림은 작은 데이터 단위가 3개 할당 된 모습을 그린 그림입니다. 그림에서 작은 데이터 단위가 2개로 나누어져 있는 것을 볼 수 있는데 앞의 것은 구조체에서 앞에 선언되어 있는 일반 변수들을 나타내며, 뒤의 것은 next 포인터를 나타내는 것입니다. 각각 0x1000, 0x1100, 0x1200 이라는 주소값에 메모리가 할당이 되어 있다고가정을 하면, 작은 데이터 블럭들은 연속되어 존재할 수도 있고 메모리의 상태에 따라서 연속되지 않게 존재할 수도 있습니다. 하지만 next 라는 포인터가 다음 데이터블럭의 주소값을 저장해서 다음에 존재하는 데이터 블럭과 연결을 하고 있게 됩니다위의 그림이 나오도록 코드를 작성해 볼까여?
1#include<stdio.h>2#include<stdlib.h>/*malloc(), free() */34typedefstruct_data Data;56struct_data7{8intnumber;9intkind;10charname[20];11intkor;12inteng;13intmath;14intscience;1516charbuffer[200];1718struct_data *next;/*다음 데이터의 주소값을 저장할 포인터 */19};2021intmain(void)22{23Data *data1 =NULL;24Data *data2 =NULL;25Data *data3 =NULL;26Data *temp =NULL;/*for() 문을 돌리기 위한 임시 포인터 변수 */2728/*데이터 블럭 3개를 할당함 */29data1 = (Data *)malloc(sizeof(Data) );30data2 = (Data *)malloc(sizeof(Data) );31data3 = (Data *)malloc(sizeof(Data) );3233/*각각의 데이터 블럭을 연결 시킴 */34data1->next = data2;35data2->next = data3;36data3->next =NULL;3738/*data1 변수 하나 가지고 연결된 모든 데이터 블럭을 접근 */39for( temp = data1 ; temp !=NULL; temp = temp->next )40{41printf("data block..\n");42}4344/*할당받은 메모리를 해제시킴 */45free( data1 );46free( data2 );47free( data3 );4849/*makes compiler happy :) */50return0;51}
23 라인에서 할당 받을 데이터 메모리 블럭의 주소값을 저장할 변수 3개를 선언하고29 라인에서 각각의 포인터 변수에 메모리를 할당하여 주소값을 저장합니다.34 라인에서 data1의 데이터 블록 내에 있는 next 포인터 변수에 data2의 주소값을 저장합니다. 마찬가지로 data2의 next 포인터 변수에 data3의 주소값을 저장하고 마지막으로 data3의 next 포인터 변수에는 더이상 연결시킬 데이터가 없다는 뜻 으로 NULL을 저장합니다.39 라인의 for()문에서는 temp 포인터 변수에 data1의 주소값을 대입하고 루프를 돌 리는데 for()문의 마지막 증감식에서 temp = temp->next 라는 코드로써 temp 변 수가 다음 데이터 블럭의 주소값을 갖고 있도록 하는 것입니다. 결과적으로 이 for()문은 데이터 블럭의 개수만큼 3번 루프가 돌게 됩니다. 이때 for()문을 실 행하기 위해서 사용된 데이터 블럭은 data1 뿐입니다. data1의 주소값 하나만 알 고 있으면 data1->next 에 의해서 data2를 알 수가 있고, data2->next에 의해서 data3를 알 수가 있게됩니다.즉, 뒤 이어서 아무리 많은 작은 데이터 블럭이 할당 되어 연결이 된다 하더라도 제일 처음 데이터 블럭의 주소값만 알고 있으면 모든 데이터 블럭을 접근 할 수가 있게 됩니다. 이것을 링크드 리스트(Linked list)라고 합니다.이런 링크드 리스트의 목적은 처음에 설명 했듯이 큰 데이터 블럭을 할당 하려고 하는 것보다 작은 데이터 블럭을 서로 연결시켜 놓음으로써 큰 데이터 블럭을 할당한 것과 같은 효과를 내기 위함입니다. 그리고 이 링크드 리스트는 부가적인 이점도 있습니다. 작은 블럭 단위로 서로 연결을 해 놓았기 때문에 중간에 있는 데이터를 삭제 하거나 삽입을 하기가 아주 편리합니다. 서로 연결된 고리 정보만 바꾸어 주면 되기 때문입니다. 만약 한번에 메모리가 할당되었을 때 어느 하나의 데이터를 삭제하거나 삽입을 할 경우가 발생하면 곤란한 일이 발생할 것입니다.위에 제시한 예제는 그림을 설명하기 위한 예제일 뿐이고, 실제로 위와 같이 코딩을하지는 않습니다. 보통 데이터 블럭을 생성하는 함수, 제거하는 함수로 구분을 해서작성을 하게 됩니다. 부가적으로 삽입이나 정렬을 위한 함수를 작성해서 사용하기도합니다. 다음 예제에서는 보다 실전적인 링크드 리스트 함수들을 구현해 보겠습니다
1#include<stdio.h>2#include<string.h>/*strlen(), strcpy() */3#include<stdlib.h>/*malloc(), free() */45/*struct _object 를 Object로 typedef */6typedefstruct_object  Object;78/*구조체 선언 */9struct_object10{11unsignedlongID;12char*          name;13intage;1415Object *        next;16};1718/*전역 변수 선언 */19unsignedlong_objectID;20Object *_objectHead =NULL;/*링크드 리스트의 가장 처음 */21Object *_objectTail =NULL;/*링크드 리스트의 가장 마지막 */2223/*함수 원형 선언 */24Object * createObject(void);25Object * createObjectWithData(char*name,intage );2627intdeleteObject( Object *object );28intdeleteObjectWithName(char*name );29intdeleteObjectWithAge(intage );30voiddeleteAllObject(void);3132voidprintObject( Object *object );33voidprintAllObject(void);3435/*createObject()36*37* Object를 하나 메모리에 할당 받고 링크드 리스트에 연결을 시킨다.38*/39Object *createObject(void)40{41Object *object =NULL;4243/*메모리 할당 */44object = (Object *)malloc(sizeof(Object) );45if( object ==NULL)46{47fprintf(stderr,48"createObject(): memory allocational error!\n");49returnNULL;50}51memset( object,0,sizeof(Object) );5253/*Object의 ID를 저장 */54object->ID = _objectID++;5556if( _objectHead ==NULL)/*링크드 리스트가 비어있으면 */57{58_objectHead  = object;/*링크드 리스트의 처음이 object */59_objectTail  = object;/*링크드 리스트의 마지막이 object */60object->next =NULL;/*방금 생성된 obejct의 다음은 NULL */61}62else/*링크드 리스트가 비어있지 않으면 */63{64_objectTail->next = object;/*현재 마지막 리스트의 다음이65object */66_objectTail       = object;/*링크드 리스트의 마지막이67object */68object->next      =NULL;/*object의 다음은 NULL */69}7071returnobject;72}7374/*createObjectWithData()75*76* 내부적으로 createObject()를 호출하고 인자로 넘어온 데이터를77* 저장한다.78*/79Object *createObjectWithData(char*name,intage )80{81Object *object = createObject();8283object->name = (char*)malloc( strlen(name) +1);84strcpy( object->name, name );85object->age = age;8687returnobject;88}8990/*deleteObject()91*92* 인자로 들어온 object를 리스트에서 삭제한다.93*/94intdeleteObject( Object *object )95{96Object *temp = _objectHead;97Object *prev =NULL;/*이전 데이터를 저장할 포인터 */9899if( object ==NULL)return0;100101/*리스트에서 인자로 들어온 object 와 일치하는 것을 찾을 때102* 까지 루프를 돌린다.103*/104while( temp )105{106if( object == temp )break;107prev = temp;108temp = temp->next;109}110/*리스트에서 찾는 것이 없을때 0을 리턴 */111if( temp ==NULL)return0;112113/*찾은 데이터가 _objectHead일때 - 가장 처음에 존재하는 데이터114* 일때115*/116if( temp == _objectHead )117_objectHead = temp->next;118119/*찾은 데이터가 가장 나중에 존재하는 데이터 일때 */120elseif( temp == _objectTail )121{122_objectTail = prev;123_objectTail->next =NULL;124}125elseprev->next = temp->next;126127/*데이터의 name 속성에 메모리가 할당되어 있으면 해제한다. */128if( temp->name !=NULL) free( temp->name );129130/*데이터 메모리 블럭을 해제한다. */131free( temp );132133/*오류없이 종료했음을 나타내는 1을 리턴한다. */134return1;135}136137/*deleteObjectWithName()138*139* 리스트에서 인자로 넘어온 name 이 일치하는 데이터를 삭제한다.140*/141intdeleteObjectWithName(char*name )142{143Object *temp = _objectHead;144145while( temp )146{147if( strcmp( temp->name, name ) ==0)break;148temp = temp->next;149}150151returndeleteObject( temp );152}153154/*deleteObjectWithAge()155*156* 리스트에서 인자로 넘어온 age가 일치하는 데이터를 삭제한다.157*/158intdeleteObjectWithAge(intage )159{160Object *temp = _objectHead;161162while( temp )163{164if( temp->age == age )break;165temp = temp->next;166}167168returndeleteObject( temp );169}170171/*deleteAllObject()172*173* 리스트에 있는 모든 데이터를 삭제한다.174*/175voiddeleteAllObject(void)176{177Object *temp = _objectHead;178Object *next =NULL;/*다음 데이터를 저장하는 포인터 */179180while( temp )181{182/*블럭을 해제하고 나면 temp->next; 를 사용할 수 없으므로183* 블럭을 해제하기 전에 다음 블럭의 주소를 저장해 놓은 다음184* 에 블럭을 해제한다.185*/186next = temp->next;187if( temp->name !=NULL) free( temp->name );188free( temp );189190temp = next;191}192193/*리스트의 처음, 마지막을 나타내는 전역 변수를 초기화 한다. */194_objectHead = _objectTail =NULL;195}196197/*printObject()198*199* 인자로 넘어온 데이터를 출력한다.200*/201voidprintObject( Object *object )202{203printf("%2d:%s,%d\n", object->ID, object->name, object->age );204}205206/*printAllObject()207*208* 리스트의 모든 데이터를 출력한다.209*/210voidprintAllObject(void)211{212Object *temp = _objectHead;213214while( temp )215{216printObject( temp );217temp = temp->next;218}219}220221intmain(void)222{223/*4개의 데이터를 생성한다.224* 여기서는 createObjectWithData() 함수가 리턴하는 주소값은225* 무시하고 있다.226* 하지만 생성된 데이터들이 연결되어 있는 리스트의 첫번째 값은227* _objectHead 전역 변수에 의해서 알 수가 있다.228*/229createObjectWithData("Shim sang don",28);230createObjectWithData("David",25);231createObjectWithData("Robert",34);232createObjectWithData("Josephin",26);233234printAllObject();235236printf("--------------------------\n");237238/*"david"라는 이름을 갖는 데이터와 나이가 26인 데이터 두개를239* 삭제한다.240*/241deleteObjectWithName("David");242deleteObjectWithAge(26);243244printAllObject();245246/*리스트의 모든 데이터를 삭제한다. */247deleteAllObject();248249return0;250}
39 라인의 createObject() 함수에 의해서 데이터 블럭이 할당되며, 링크드 리스트에 삽입이 됩니다. 이 함수는 createObjectWithData() 함수에 의해서 내부적으로 호 출되며 데이터 블럭을 생성하고, 리스트의 마지막에 삽입을 하는 일을 하게 됩니 다. 여기에서 데이터가 생성되고 리스트에 삽입될 때, 위의 예제 처럼 리스트의 마지막에 삽입을 하는 경우가 있고 정렬을 위해서 어떤 조건에 의해 리스트를 검 색하면서 삽입을 하는 경우가 있습니다. 보통 데이터가 많은 경우에는 리스트에 있는 데이터들을 정렬하는 함수를 호출해 야하는 부가적인 일을 하지 않도록 삽입할 때 미리 정렬된 위치에 삽입하는 경우 가 많지만 데이터가 많지 않거나 데이터를 삽입하고 삭제하는 일을 아주 많이 해 야하는 일을 할 경우에는 일단 마지막에 삽입을 하도록 해 놓은 다음에 리스트를 정렬하는 함수를 호출 해서 한번에 정렬을 합니다.94 라인의 deleteObject() 함수를 보면 삭제해야할 데이터를 찾은 다음에 연결 정보 를 갱신하는 코드를 볼 수가 있습니다(113 ~ 125 라인). 이렇게 해줌으로써 리스 트에 연결되어 있는 데이터들의 연결 고리가 끊어지지 않고 계속해서 유지될 수 가 있는 것입니다.위의 예제에서 조금은 비효율적인 부분도 있습니다. 예를 들어서 141 라인에 선언되어 있는 deleteObjectWithName() 함수를 보면, 내부적으로 인자로 들어온 name과 일치하는 데이터를 찾기 위해서 while 루프를 돌아서 데이터를 찾아내는데 그 찾은 데이터를 삭제할 때 deleteObject() 함수를 호출하게 됩니다. 그런데 deleteObject()함수의 내부를 보면 리스트에서 그 데이터 주소값과 일치하는 것을 찾기 위해서 또while 루프를 돌고 있는 것을 볼 수가 있습니다.현재의 코드를 잘 분석해보고 비효율적인 부분을 찾아내고 효율적으로 개선을 해 보는 공부를 스스로 해보시기 바랍니다.이번 강좌는 링크드 리스트에 대해서 알아보았습니다. 하지만 이번 강좌에서 설명한링크드 리스트는 단일 링크드 리스트라고 불리우는 next 포인터만 존재하는 리스트였고, 다음 강좌에서 이중 링크드 리스트에 대해서 공부를 해보도록 하겠습니다.
 
 
 
 
포인터 10번째 강좌입니다.저번 강좌에서 링크드 리스트에 대해서 알아보았습니다. 하지만 저번 강좌에서 설명한 링크드 리스트는 단일 링크드 리스트라고 불리우는 next 포인터만 존재하는 리스트였고, 이번 강좌에서 이중 링크드 리스트에 대해서 공부를 해보도록 하겠습니다.우선 저번 강좌의 단일 링크드 리스트에 대해서 복습을 해 보면,  구조체의 멤버 변수 중에 자기 구조체 참조 포인터 변수가 있어서,  그 구조체로 생성된 다른 데이터블럭의 주소값을 저장하고 있기 때문에  모든 데이터 블럭을 서로 연결시킬 수가 있었지여? 그 자기 구조체 참조 포인터 변수를 통상 next 라는 이름으로 선언합니다.자, 그렇다면 그 next 포인터는  자기 자신 데이터 블럭의 뒤에 연결될 데이터 블럭의 주소값을 저장하기 위한 포인터였는데,  그렇다면 자기 앞의 데이터 블럭의 주소값을 저장하기 위한 포인터를 선언해서 사용해도 되지 않을까여?흠... 마치 이중 링크드 리스트가 나오도록 유도심문 한것처럼 느껴지네여. :)단일 링크드 리스트 강좌의 예제 소스중에서 분명히 비 효율적인 부분이 있습니다.그것은 두번의 while()문을 통해서 원하는 것을 삭제한 부분도 있겠지만, 그것은 내부적으로 함수를 호출을 했기 때문에 그러한 결과가 나온것입니다. 즉, name 이라는인자를 받아서 데이터 블럭을 삭제할 때 deleteObject() 함수를 호출하지 않고 바로삭제하는 코드를 작성하면 while() 문이 두번 실행되지는 않겠지여.그것 외에도 삭제하기 위한 데이터 블럭의 바로 이전 데이터 블럭을 알고 있어야 그다음에 있는 데이터 블럭과 이전 데이터 블럭과 연결을 시켜줄 수 있었는데, 그러기위해서 임시 포인터 변수인 prev 를 선언했던 것 기억나시져?그리고 단일 링크드 리스트를 사용하면 위에서 아래로의 탐색은 가능하지만, 아래에서 위로의 탐색은 불가능 합니다.(불가능 하지는 않지만 엄청 비 효율적이 됩니다.)그 이유는 next 만 갖고 있기 때문입니다. 그래서 prev 라는 자기 구조체 참조 포인터 변수를 한개 더 두는 겁니다. 그렇게 하고 그 prev 변수에는 이전 데이터 블럭의주소를 저장하도록 하게 되면  각각의 데이터 블럭들은 자기 앞의 데이터 블럭과 뒤의 데이터 블럭의 위치를 알고 있게 됩니다. 마치 우리가 한줄로 설 때 앞사람과 뒤사람을 알고 있으면 흐트러져 있더라도 다시 그 상태로 모일 수 있는 것과 같지여.저번 강좌의 단일 링크드 리스트 예제를 조금 수정해서  이중 링크드 리스트 예제로바꾸어 보겠습니다.
1#include<stdio.h>2#include<string.h>/*strlen(), strcpy() */3#include<stdlib.h>/*malloc(), free() */45/*struct _object 를 Object로 typedef */6typedefstruct_object  Object;78/*구조체 선언 */9struct_object10{11unsignedlongID;12char*          name;13intage;1415Object *        prev;/*이전 데이터 블럭을 저장하기 위함 */16Object *        next;17};1819/*전역 변수 선언 */20unsignedlong_objectID;21Object *_objectHead =NULL;/*링크드 리스트의 가장 처음 */22Object *_objectTail =NULL;/*링크드 리스트의 가장 마지막 */2324/*함수 원형 선언 */25Object * createObject(void);26Object * createObjectWithData(char*name,intage );2728intdeleteObject( Object *object );29intdeleteObjectWithName(char*name );30intdeleteObjectWithAge(intage );31voiddeleteAllObject(void);3233voidprintObject( Object *object );34voidprintAllObject(void);3536/*createObject()37*38* Object를 하나 메모리에 할당 받고 링크드 리스트에 연결을 시킨다.39*/40Object *createObject(void)41{42Object *object =NULL;4344/*메모리 할당 */45object = (Object *)malloc(sizeof(Object) );46if( object ==NULL)47{48fprintf(stderr,49"createObject(): memory allocational error!\n");50returnNULL;51}52memset( object,0,sizeof(Object) );5354/*Object의 ID를 저장 */55object->ID = _objectID++;5657if( _objectHead ==NULL)/*링크드 리스트가 비어있으면 */58{59_objectHead  = object;/*링크드 리스트의 처음이 object */60_objectTail  = object;/*링크드 리스트의 마지막이 object */61object->prev =NULL;/*방금 생성된 object의 이전은 NULL */62object->next =NULL;/*방금 생성된 obejct의 다음은 NULL */63}64else/*링크드 리스트가 비어있지 않으면 */65{66_objectTail->next = object;/*현재 마지막 리스트의 다음이67object */68object->prev = _objectTail;/*object의 이전은 현재의 마지막 데이터 */69_objectTail       = object;/*링크드 리스트의 마지막이70object */71object->next      =NULL;/*object의 다음은 NULL */72}7374returnobject;75}7677/*createObjectWithData()78*79* 내부적으로 createObject()를 호출하고 인자로 넘어온 데이터를80* 저장한다.81*/82Object *createObjectWithData(char*name,intage )83{84Object *object = createObject();8586object->name = (char*)malloc( strlen(name) +1);87strcpy( object->name, name );88object->age = age;8990returnobject;91}9293/*deleteObject()94*95* 인자로 들어온 object를 리스트에서 삭제한다.96*/97intdeleteObject( Object *object )98{99if( object ==NULL)return0;100101/*삭제할 데이터가 _objectHead일때 - 가장 처음에 존재하는 데이터102* 일때103*/104if( object == _objectHead )105{106object->next->prev =NULL;107_objectHead = object->next;108}109/*삭제할 데이터가 가장 나중에 존재하는 데이터 일때 */110elseif( object == _objectTail )111{112object->prev->next =NULL;113_objectTail = object->prev;114}115else116{117object->prev->next = object->next;118object->next->prev = object->prev;119}120121/*데이터의 name 속성에 메모리가 할당되어 있으면 해제한다. */122if( object->name !=NULL) free( object->name );123124/*데이터 메모리 블럭을 해제한다. */125free( object );126127/*오류없이 종료했음을 나타내는 1을 리턴한다. */128return1;129}130131/*deleteObjectWithName()132*133* 리스트에서 인자로 넘어온 name 이 일치하는 데이터를 삭제한다.134*/135intdeleteObjectWithName(char*name )136{137Object *temp = _objectHead;138139while( temp )140{141if( strcmp( temp->name, name ) ==0)break;142temp = temp->next;143}144145returndeleteObject( temp );146}147148/*deleteObjectWithAge()149*150* 리스트에서 인자로 넘어온 age가 일치하는 데이터를 삭제한다.151*/152intdeleteObjectWithAge(intage )153{154Object *temp = _objectHead;155156while( temp )157{158if( temp->age == age )break;159temp = temp->next;160}161162returndeleteObject( temp );163}164165/*deleteAllObject()166*167* 리스트에 있는 모든 데이터를 삭제한다.168*/169voiddeleteAllObject(void)170{171Object *temp = _objectHead;172Object *next =NULL;/*다음 데이터를 저장하는 포인터 */173174while( temp )175{176/*블럭을 해제하고 나면 temp->next; 를 사용할 수 없으므로177* 블럭을 해제하기 전에 다음 블럭의 주소를 저장해 놓은 다음178* 에 블럭을 해제한다.179*/180next = temp->next;181if( temp->name !=NULL) free( temp->name );182free( temp );183184temp = next;185}186187/*리스트의 처음, 마지막을 나타내는 전역 변수를 초기화 한다. */188_objectHead = _objectTail =NULL;189}190191/*printObject()192*193* 인자로 넘어온 데이터를 출력한다.194*/195voidprintObject( Object *object )196{197printf("%2d:%s,%d\n", object->ID, object->name, object->age );198}199200/*printAllObject()201*202* 리스트의 모든 데이터를 출력한다.203*/204voidprintAllObject(void)205{206Object *temp = _objectHead;207208while( temp )209{210printObject( temp );211temp = temp->next;212}213}214215intmain(void)216{217/*4개의 데이터를 생성한다.218* 여기서는 createObjectWithData() 함수가 리턴하는 주소값은219* 무시하고 있다.220* 하지만 생성된 데이터들이 연결되어 있는 리스트의 첫번째 값은221* _objectHead 전역 변수에 의해서 알 수가 있다.222*/223createObjectWithData("Shim sang don",28);224createObjectWithData("David",25);225createObjectWithData("Robert",34);226createObjectWithData("Josephin",26);227228printAllObject();229230printf("--------------------------\n");231232/*"david"라는 이름을 갖는 데이터와 나이가 26인 데이터 두개를233* 삭제한다.234*/235deleteObjectWithName("David");236deleteObjectWithAge(26);237238printAllObject();239240/*리스트의 모든 데이터를 삭제한다. */241deleteAllObject();242243return0;244}
저번 강좌의 예제와 비교를 해보면서 보는것도 좋을것 같네여.단일 링크드 리스트와 코드가 바뀐곳에 대해서 설명을 하겠습니다. 15 라인에 prev 포인터가 추가되었습니다. 이 prev 포인터가 하나 늘어남으로써 구 조체의 크기는 4바이트 증가되었지만, 4바이트 늘어난만큼 편리함을 제공해 줄 것입니다. 61 라인에 생성된 데이터 블럭의 이전 블럭에 대해서 저장을 합니다. 여기에서는 링크드 리스트가 비어있는 상태이므로 가장 처음에 생성된 데이터 블럭이 되는 것입니다. 따라서 방금 생성된 데이터 블럭의 앞에는 아무것도 없으므로 prev는 NULL이 되는 것입니다. 68 라인에서 생성된 데이터 블럭의 prev는 링크드 리스트의 가장 마지막 데이터라 는 것을 나타내는 것입니다. 97 라인의 deleteObject() 함수가 가장 크게 개선된 함수입니다. 단일 링크드 리스 트 예제에서의 이 함수는 삭제하고자 하는 데이터 블럭 앞에 있는 것이 무엇인 지 찾기 위해서 while() 루프를 사용했었는데, 이중 링크드 리스트에서는 while 루프를 실행하지 않아도 prev 포인터에 의해서 바로 알 수가 있으므로 prev 를 찾기 위해서 불필요한 while 루프를 돌지 않아도 되므로, 수행 속도가 빨라지게 됩니다. 삭제하기 전에 삭제될 데이터 블럭의 앞, 뒤 데이터들 끼리 연결 고리를 이어주 고 자신은 삭제되는 것입니다. 그 외에 바뀐것은 없습니다. prev 변수가 하나 늘어나고 코드가 조금 바뀐것 말고는눈에 띄게 바뀐것을 체험하기가 어려울 수도 있지만, 다음 강좌에 나올 탐색과 정렬을 구현해 보면서 prev 가 있어서 더 많은 일을 해 줄 수가 있다는 것을 알 수 있을것입니다. 이중 링크드 리스트에 대한 그림이 빠지면 구색이 맞지 않으므로 그림을 그려보도록하겠습니다.
사용자 삽입 이미지
위에서 아래로  데이터가 생성이 되었다고 할 때, 첫번째 데이터의 prev 에는 NULL이 들어갑니다.  그 이유는 앞에 존재하는 데이터가 없기 때문이져.그리고 next 에는 다음 데이터블럭의 주소값인 0x1100 이 저장 됩니다.두번째 데이터의 prev 는 앞에있는 0x1000 번지에 있는 데이터 블럭을 가리키고 있게 되고next는 다음 데이터 블럭의 주소값인 0x1200 을 저장하고 있습니다.이런 식으로 모든 데이터 블럭들은 앞, 뒤가  연결되어 있게됩니다.
텍스트로 그림을 그리려니 조잡해 지더군여. 그래서 그냥 그림파일로 올렸습니다. 개인적으로 이런 그림 보다는 아스키로 그린 그림을 더 좋아하는 편이라서 아스키로그리려고 시간을 조금 보냈는데 알아보기가 쉽지 않게 되더군여. :(이중 링크드 리스트를 표현하는 방법까지만 이번 강좌를 마치기로 하고, 다음 강좌에서 이 이중 링크드 리스트가 필요한 분야와 어떻게 적용하는 지에 대해서 알아보도록 하겠습니다. prev, next 가 가리키고 있는 것은 데이터 블럭의 주소값 이라는 것을 꼭 기억해 두시기 바랍니다. 포인터는 주소값을 저장하는 변수입니다.
 
 
 
포인터 11번째 강좌입니다.저번 강좌까지 링크드 리스트에 대한 구조체 선언과 next, prev 포인터가 어떻게 사용이 되는지 알아보았습니다. 이번 강좌에서는 링크드 리스트의 사용에 대해서 알아보도록 하겠습니다. 10번째 강좌에서 잠깐 언급된 탐색에 대해서 잠깐 언급하고 넘어가겠습니다.탐색이란 리스트 내에 있는 데이터 중에서 원하는 데이터를 찾아내는 것을 말합니다. 검색이라는 단어를 사용하는 편이 더 옳겠네여. 탐색이라는 것은 일치하는 데이터를 찾는 것을 나타내는 것 외에 가장 빠르게 도달할 수 있는 경로를 찾는 최단 거리탐색 이라든가 또는 트리 구조등의 깊이를 알아내는 것을 지칭하는 단어로도 사용이되므로 여기서는 탐색이라는 단어보다는  조금 의미가 좁은 검색이라는 단어를 사용하겠습니다.검색을 한다는 것은 리스트에 있는 데이터 블럭에서 원하는 검색조건과 일치하는 데이터 블럭을 찾아내는 일을 하는 것을 말합니다.  보통 가장 쉬운 검색 방법은 리스트의 처음부터 끝까지 차례로 모든 데이터 블럭을 통과하면서 검색 조건과 일치하는데이터 블럭을 찾아내는 것입니다. 쉬운 방법이지만 가장 많이 사용되는 방법입니다. 효율을 증대시키기 위해서 리스트 자체를 변형하는 방법을 사용하기 때문에, 일단리스트를 변경하는 것에 대해서는 나중에 언급을 하기로 하고 지금은 단순히 리스트의 처음부터 끝까지 데이터 블럭을 조사하는 방법에 대해서 알아보겠습니다.10번째 강좌의 예제 소스와 연계해서,  리스트 내에 이름을 조건으로 해서 일치하는데이터를 찾아서 그 데이터 블럭의 주소값을 리턴하는 함수를 작성해 보겠습니다.
1/*findObjectWithName()2*3* 리스트의 처음 데이터 블럭에서 마지막 데이터 블럭까지의4* 데이터 블럭(여기에서는 object로 대표됨)의 name 멤버와5* name 인자로 들어온 문자열과 비교를 해서 일치하는 경우에6* 그 일치하는 데이터 블럭의 주소값을 리턴하는 함수7*/8Object *findObjectWithName(char*name )9{10/*Object 형 포인터를 선언해서 리스트의 가장 처음에 존재하는11* 데이터 블럭의 주소값을 대입한다.12* 여기서 _objectHead 가 가장 처음에 존재하는 데이터 블럭의13* 주소값을 저장하고 있는 전역변수.14* 10번째 강좌의 소스코드를 참조하면 됩니다.15*/16Object *object = _objectHead;1718/*인자로 들어온 name에 아무 문자도 없으면 NULL을 리턴. */19if( name ==NULL)returnNULL;2021/*object = object->next; 라는 코드로 인해서 while문으로22* 모든 리스트들을 경유할 수가 있습니다.23*/24while( object )25{26if( strcmp( object->name, name ) ==0)27returnobject;28object = object->next;29}3031/*while 문 내에서 return이 일어나지 않고 여기까지 왔다는 것은32* 일치하는 데이터 블럭을 찾지 못했다는 뜻이므로 NULL를 리턴33*/34returnNULL;35}
16 라인에서 리스트의 가장 처음에 있는 데이터의 주소를 object 라는 포인터를 선 언하고 저장합니다.24 라인의 while()문에서 리스트의 처음부터 끝까지 name 인자와 일치하는 데이터가 있는지 검색을 합니다. 일치하는 데이터가 있으면 일치하는 데이터의 주소값을 리턴하고 그렇지 않으면 계속 해서 다음 데이터와 비교를 합니다. 리스트의 끝까 지 검색을 했는데 일치하는 데이터가 검색되지 않으면 NULL을 리턴합니다.검색을 하는 코드는 이렇게 간단히 처리될 수 있습니다. 그 코드가 효율적이든 그렇지 않든 리스트가 존재하는 구조를 이용하여 검색을 하는 방법에 대해서 알아보았습니다.위의 예제 코드에서 name을 가지고 찾는 함수를 작성했지만 독자분들이 스스로 age를 검색하는 함수를 작성해보세여. 관련 코드는 10번째 강좌의 예제 코드를 보시면됩니다.이번에는 리스트에 대한 정렬을 한번 해보기로 하겠습니다. 정렬 알고리즘은 그 수가 아주 많습니다. 오늘 설명할 정렬 알고리즘은 가장 이해하기 편하고 쉽게 응용할 수 있는 버블정렬에 대해서 설명을 하고 리스트에 적용을 해보기로 하겠습니다. 버블 정렬은 그 원리가 아주 간단합니다. 처음 데이터와 나머지 데이터를 비교해서정렬조건이 작은 것을 앞으로 하는 경우에, 작은 것과 큰것의 위치를 뒤바꾸어 주는것입니다. 역시 말로 설명을 하려니 이해시키기가 무척 어렵네여. 예를 들어볼께여.2 3 1 6 5 4위와 같이 데이터가 있다고 가정을 하겠습니다. 가장 처음에 정렬을 하기 위한 대상데이터는 2와 3입니다. 2와 3을 비교해서 작은것은 앞으로 큰것은 뒤로 하도록 서로위치를 뒤 바꾸는 것입니다. 현재는 2가 작으므로 위치를 바꾸는 일은 하지 않겠지여? 그래서 데이터의 위치는 처음과 같습니다.그 다음은 2와 1이 대상입니다. 1이 작으므로 2와 자리를 바꿉니다.1 3 2 6 5 4그 다음에 정렬을 하기 위한 대상은 2와 6이 아니라 1과 6입니다. 왜냐면 첫번째에있는 데이터와 네번째에 있는 데이터를 정렬하는 것이기 때문입니다. 1과 6이 그 위치에 있는 데이터이므로 둘을 정렬합니다. 1이 작으므로 위치는 변함없습니다.이런식으로 6번째 데이터까지 비교를 하면 결과는 1 3 2 6 5 4 이렇게 됩니다. 지금까지의 결과는 어떤 의미를 갖고 있을까여? 그것은 가장 작은 수가 가장 처음에와 있다는 것을 뜻합니다. 1이 가장 처음에 위치하게 된거져..자 다음에는 두번째 위치의 데이터와 세번째, 네번째..여섯번째 위치의 데이터와 비교를 해야합니다.3과 2를 비교해서 위치를 뒤바꾸겠져?1 2 3 6 5 4 이렇게 됩니다. 그리고 다시 2와 6, 2와 5, 2와 4를 비교합니다.1 2 3 6 5 4결국 두번째 데이터까지 정렬이 되었습니다.그다음은 세번째 데이터와 네번째, 다섯번째, 여섯번째 위치의 데이터를 비교해야겠져? 3이 제일 작으므로 위치 변동은 없네여. 결국 세번째 데이터까지 정렬이 되었습니다.다음 네번째와 다섯번째, 여섯번째 데이터를 비교하겠습니다. 6과 5를 뒤바꾸면,1 2 3 5 6 4이렇게 되져?다음에 네번째인 5와 여섯번째인 4를 비교해서 뒤바꾸면,1 2 3 4 6 5이렇게 됩니다. 4번째 데이터까지 정렬되었습니다.마지막으로, 다섯번째와 여섯번째 데이터를 비교해서 뒤바꾸면 6과 5가 서로 바뀌겠져? 따라서1 2 3 4 5 6이렇게 정렬이 완료가 됩니다.버블 정렬의 개념과 동작 원리를 알아봤으므로, 버블 정렬을 하는 간단한 예제 코드를 작성해보도록 하겠습니다.
1#include<stdio.h>23voidswap(int*x,int*y );4voidprintArray(int*array );56/*swap()7*8* 두개의 인자를 받아서 그 인자의 값을 서로 뒤바꾸어 주는 함수9*/10voidswap(int*x,int*y )11{12inttemp;1314temp = *x;15*x = *y;16*y = temp;17}1819/*printArray()20*21* 인자로 들어온 배열을 화면에 출력해주는 함수22*/23voidprintArray(int*array )24{25inti;2627for( i =0; i <6; i++ )28printf("%2d", array[i] );29printf("\n");30}3132intmain(void)33{34intarray[6] = {2,3,1,6,5,4};35inti, j;3637for( i =0; i <6; i++ )38for( j = i +1; j <6; j++ )39if( array[i] > array[j] )/*비교 대상 데이터중 뒤의 것이 작40다면 서로 바꾸어 준다 */41{42swap( &array[i], &array[j] );43printArray( array );44}4546return0;47}
위에서 개념 설명했던 그대로의 데이터를 가지고 코드를 작성해 보았습니다. 코드를작성해서 컴파일 하고 실행해 보시면 위에서 설명한 대로 그 과정이 출력될 것입니다.위의 알고리즘으로 정렬을 하는 것을 버블정렬이라고 부릅니다. 버블정렬은 그 개념이 쉬워서 많이 사용되기는 하지만 단점도 있습니다. 아주 많은 데이터를 정렬할 때그 효율이 아주 많이 떨어진다는 것입니다. 최악의 배열로 데이터가 존재할 때, 비교하고 자리를 뒤바꾸는 일을 (n*n)/2 번 수행하게 됩니다. 데이터의 갯수가 10 개일때 최고 50 번의 swap이 일어나야 한다는 뜻입니다. 데이터의 갯수가 많아지면 많아질 수록 그 수행 회수는 기하 급수로 늘어나게 되겠져. 따라서 이 버블 정렬은 데이터의 갯수가 적을 경우에 아주 간단하게 적용을 할 수 있는 정렬 알고리즘입니다.자, 그럼 이제 리스트에 버블 정렬을 적용하도록 함수를 작성해 보도록 하겠습니다.
1#include<stdio.h>2#include<string.h>/*strlen(), strcpy() */3#include<stdlib.h>/*malloc(), free() */45/*struct _object 를 Object로 typedef */6typedefstruct_object  Object;78/*구조체 선언 */9struct_object10{11unsignedlongID;12char*          name;13intage;1415Object *        prev;/*이전 데이터 블럭을 저장하기 위함 */16Object *        next;17};1819/*전역 변수 선언 */20unsignedlong_objectID;21Object *_objectHead =NULL;/*링크드 리스트의 가장 처음 */22Object *_objectTail =NULL;/*링크드 리스트의 가장 마지막 */2324/*함수 원형 선언 */25Object * createObject(void);26Object * createObjectWithData(char*name,intage );2728intdeleteObject( Object *object );29intdeleteObjectWithName(char*name );30intdeleteObjectWithAge(intage );31voiddeleteAllObject(void);3233voidprintObject( Object *object );34voidprintAllObject(void);3536voidsortObjectWithAge(void);37voidswapObjectValue( Object *i, Object *j );38voidswap(int*x,int*y );3940/*createObject()41*42* Object를 하나 메모리에 할당 받고 링크드 리스트에 연결을 시킨다.43*/44Object *createObject(void)45{46Object *object =NULL;4748/*메모리 할당 */49object = (Object *)malloc(sizeof(Object) );50if( object ==NULL)51{52fprintf(stderr,53"createObject(): memory allocational error!\n");54returnNULL;55}56memset( object,0,sizeof(Object) );5758/*Object의 ID를 저장 */59object->ID = _objectID++;6061if( _objectHead ==NULL)/*링크드 리스트가 비어있으면 */62{63_objectHead  = object;/*링크드 리스트의 처음이 object */64_objectTail  = object;/*링크드 리스트의 마지막이 object */65object->prev =NULL;/*방금 생성된 object의 이전은 NULL */66object->next =NULL;/*방금 생성된 obejct의 다음은 NULL */67}68else/*링크드 리스트가 비어있지 않으면 */69{70_objectTail->next = object;/*현재 마지막 리스트의 다음이71object */72object->prev = _objectTail;/*object의 이전은 현재의 마지막 데이터 */73_objectTail       = object;/*링크드 리스트의 마지막이74object */75object->next      =NULL;/*object의 다음은 NULL */76}7778returnobject;79}8081/*createObjectWithData()82*83* 내부적으로 createObject()를 호출하고 인자로 넘어온 데이터를84* 저장한다.85*/86Object *createObjectWithData(char*name,intage )87{88Object *object = createObject();8990object->name = (char*)malloc( strlen(name) +1);91strcpy( object->name, name );92object->age = age;9394returnobject;95}9697/*deleteObject()98*99* 인자로 들어온 object를 리스트에서 삭제한다.100*/101intdeleteObject( Object *object )102{103if( object ==NULL)return0;104105/*삭제할 데이터가 _objectHead일때 - 가장 처음에 존재하는 데이터106* 일때107*/108if( object == _objectHead )109{110object->next->prev =NULL;111_objectHead = object->next;112}113/*삭제할 데이터가 가장 나중에 존재하는 데이터 일때 */114elseif( object == _objectTail )115{116object->prev->next =NULL;117_objectTail = object->prev;118}119else120{121object->prev->next = object->next;122object->next->prev = object->prev;123}124125/*데이터의 name 속성에 메모리가 할당되어 있으면 해제한다. */126if( object->name !=NULL) free( object->name );127128/*데이터 메모리 블럭을 해제한다. */129free( object );130131/*오류없이 종료했음을 나타내는 1을 리턴한다. */132return1;133}134135/*deleteObjectWithName()136*137* 리스트에서 인자로 넘어온 name 이 일치하는 데이터를 삭제한다.138*/139intdeleteObjectWithName(char*name )140{141Object *temp = _objectHead;142143while( temp )144{145if( strcmp( temp->name, name ) ==0)break;146temp = temp->next;147}148149returndeleteObject( temp );150}151152/*deleteObjectWithAge()153*154* 리스트에서 인자로 넘어온 age가 일치하는 데이터를 삭제한다.155*/156intdeleteObjectWithAge(intage )157{158Object *temp = _objectHead;159160while( temp )161{162if( temp->age == age )break;163temp = temp->next;164}165166returndeleteObject( temp );167}168169/*deleteAllObject()170*171* 리스트에 있는 모든 데이터를 삭제한다.172*/173voiddeleteAllObject(void)174{175Object *temp = _objectHead;176Object *next =NULL;/*다음 데이터를 저장하는 포인터 */177178while( temp )179{180/*블럭을 해제하고 나면 temp->next; 를 사용할 수 없으므로181* 블럭을 해제하기 전에 다음 블럭의 주소를 저장해 놓은 다음182* 에 블럭을 해제한다.183*/184next = temp->next;185if( temp->name !=NULL) free( temp->name );186free( temp );187188temp = next;189}190191/*리스트의 처음, 마지막을 나타내는 전역 변수를 초기화 한다. */192_objectHead = _objectTail =NULL;193}194195/*printObject()196*197* 인자로 넘어온 데이터를 출력한다.198*/199voidprintObject( Object *object )200{201printf("%2d:%s,%d\n", object->ID, object->name, object->age );202}203204/*printAllObject()205*206* 리스트의 모든 데이터를 출력한다.207*/208voidprintAllObject(void)209{210Object *temp = _objectHead;211212while( temp )213{214printObject( temp );215temp = temp->next;216}217}218219/*sortObjectWithAge()220*221* 리스트에 있는 데이터들을 나이 순서대로 정렬을 하는 함수222*/223voidsortObjectWithAge(void)224{225Object *i =NULL, *j =NULL;226227for( i = _objectHead ; i !=NULL; i = i->next )228for( j = i->next ; j !=NULL; j = j->next )229if( i->age > j->age )230swapObjectValue( i, j );231}232233/*swapObjectValue()234*235* 두개의 데이터 인자들의 내용을 바꾸어 주는 함수236* 실제로 리스트에 있는 데이터를 바꾸어 주기 위해서는 두가지 방법이 있다.237*238* 실제로 객체가 갖고 있는 멤버들의 값을 전부 바꾸어 주는 방법과,239* 객체들의 연결 고리인 next, prev를 바꾸어 주는 방법.240*241* 이 함수는 첫번째 방법으로 작성된 합수이다.242*/243voidswapObjectValue( Object *i, Object *j )244{245swap( &i-&gt;age, &j-&gt;age );/*나이 멤버값을 바꾼다 */246247/*name 멤버가 가지고 있는 이름이 저장되어 있는 곳의 주소값을248* 서로 바꾸어 준다. 포인터의 크기도 4바이트이고 int의 크기도249* 4바이트 이므로 포인터가 갖고 있는 주소값을 (int *)형으로250* 캐스팅 한 다음에 swap()을 호출한다.251*252* NOTE:253*    별로 추천하고 싶은 방법은 아니지만 원하는 결과는 훌륭히254*    해냅니다.255*/256swap( (int*)&i-&gt;name, (int*)&j-&gt;name );257}258259/*swap()260*261* 두개의 인자를 받아서 그 인자의 값을 서로 뒤바꾸어 주는 함수262*/263voidswap(int*x,int*y )264{265inttemp;266267temp = *x;268*x = *y;269*y = temp;270}271272intmain(void)273{274/*4개의 데이터를 생성한다.275* 여기서는 createObjectWithData() 함수가 리턴하는 주소값은276* 무시하고 있다.277* 하지만 생성된 데이터들이 연결되어 있는 리스트의 첫번째 값은278* _objectHead 전역 변수에 의해서 알 수가 있다.279*/280createObjectWithData("Shim sang don",28);281createObjectWithData("David",25);282createObjectWithData("Robert",34);283createObjectWithData("Josephin",26);284285printAllObject();286287printf("--------------------------\n");288289sortObjectWithAge();290printAllObject();291292/*리스트의 모든 데이터를 삭제한다. */293deleteAllObject();294295return0;296}
10번째 강좌의 예제 코드에 정렬을 하는 함수를 추가했습니다. 어떤 부분이 추가되었는지 비교해서 살펴보세여..라고는 했지만 단지 함수 3개 늘어나고 main()에서 아주 조금 바뀌었을 뿐입니다.223 라인의 sortObjectWithAge() 함수 내용을 보시면, 위에서 int 배열을 정렬하는 함수와 모양도 아주 흡사한 것을 볼 수가 있습니다. 루프를 돌리기 위해서 사용 된 변수가 Object * 형이라는 점이 조금 다르지여. 이 함수에서 호출하고 있는 swapObjectValue() 함수의 내용이 중요합니다.233 라인에 적혀있는 주석문을 읽어보시면 알겠지만, 리스트에 있는 데이터를 바꾸 어 주기 위해서 보통 2가지 방법을 사용합니다. 리스트에 있는 데이터(객체)의 멤버들을 전부 바꾸어 주는 방법과, 객체가 가리키고 있는 next, prev 포인터를 바꾸어 주는 방법이 있습니다. 첫번째 방법은 가장 알기도 쉽고 코딩하기도 어렵지는 않지만, 객체의 구조체가 수정되면 swap()해주는 함수의 내용도 바꾸어 주어야 하는 불편함도 있고, 가장 중요한 것은 시간이 많이 걸린다는 것입니다. 멤버의 값들을 일일이 다 바꾸어 주는 식의 코딩을 하면 멤버가 20~30개 이상의 많은 수로 이루어져 있다면 그만 큼 많은 대입하는 프로세스가 필요하게 되는 것입니다. 두번째 방법은 링크드 리스트의 next, prev 포인터 연결 고리를 재 설정 해줌으 로써 데이터 값을 바꾸는 것이 아니라 객체의 리스트 내에서의 위치를 바꾸어주 는 것입니다. 다만 링크드 리스트의 next, prev 포인터를 문제없이 바꾸게 하려 면 여러가지 생각하여야 할 것이 있기 때문에 자칫 실수 하기가 쉽습니다.위의 예제에서는 첫번째 방식으로 코딩을 했습니다. 그 이유는 예제 구조체의 구조가 매우 간단하며, 한번에 많은 생각을 하게 하면, 이 강좌를 읽으시는 분들의 흰머리가 하나라도 더 늘것을 염려해서 입니다.이번 강좌는 여기서 마칠까 합니다. 마치기 전에 위의 sortObjectWithAge() 함수의기능 처럼 name 멤버에 대한 정렬 함수를 작성해 보시기 바랍니다. 그리고 리스트의prev, next 포인터를 이용해서 정렬하는 함수도 작성해 보시기 바랍니다. 힌트는 위예제 코드의 101 라인에 있는 deleteObject() 함수의 내용을 참조하시기 바랍니다.
and