728x90

Circular Linked List


 모든 노드가 원의 형태를 이루며 연결되어 있기 때문에,

사실상 머리와 꼬리의 구분이 없다.


다만 저장 순서를 근거로한 머리나 꼬리의 개념을 둘 뿐이다.


머리와 꼬리를 가리키는 포인터 변수를 두지않고

하나의 포인터 변수를 가지고 머리 또는 꼬리에 노드를 추가할 수 있게 한다.


꼬리를 가리키는 변수 : tail

머리를 가리키는 변수 : tail->next





변경하거나 추가할 함수


LNext

원형 연결리스트를 계속해서 순환하는 형태로 변경!

(이전 함수 그대로 써도 되지만 원형 연결리스트 특성을 반영)


앞과 뒤에 삽입이 가능한 두개의 함수 정의

LinsertFront, LInsert


void LInsert(List * plist, Data data); // 노드를 꼬리에 추가

void LInsertFront(List * plist, Data data); // 노드를 머리에 추가 



원형 연결리스트의 초기화 함수

void ListInit(List * plsit) // 모든 멤버를 NULL과 0으로 초기화

{

plist->tail = NULL;

plist ->cur = NULL;

plist->befor = NULL;

plist->numOfData = 0;

}



원형 연결리스트의 구현 : 첫번째 Node 추가


첫 번째 노드는 그 자체로 머리이자 꼬리이기 때문에 노드를 뒤에 추가하든 앞에 추가하든 결과가 동일



// LInsert와 LInsertFront의 공통 부분

void LInser~(List * plist, Data data)

{

Node * newNode = (Node*)malloc(sizeof(Node));

newNode->data = data;


if(plist->tail == NULL) 

{

plist->tail = newNode;           // 

 newNode->next = newNode;  // 

}

else

{

// 두번째 이후의 노드는 차이가 있음

}




원형 연결리스트의 구현 : 두번째 이후 Node 머리에 추가



void LInsertFront(List * plist, Data data)

{

Node * newNode = (Node*)malloc(sizeof(Node));

newNode->data = data;


if(plist->tail == NULL) 

{

plist->tail = newNode;

newNode->next = newNode;

}

else

{

newNode->next = plist->tail->next;    // (1)

plist->tail->next = newNode;             // (2)

}


(plist->numOfData)++;

}


원형 연결 리스트 구현 : 머리와 꼬리에 추가하는 과정 비교


tail이 가리키는 것이 꼬리Node, 

꼬리Node가 가리키는 것이 head


데이터의 순서는 동일, tail이 누구를 가리키냐만 차이


void LInsert(List * plist, Data data)

{

(... 생략)

   else 

{

newNode->next = plist->tail->next;

plist->tail->next = newNode;

      plist->tail = newNode;  //  LInsertFront 함수에는 없는 문장이며, 이것이 유일한 차이이다.

}




원형 연결 리스트 구현 : 조회 LFirst





int LFirst(List * plist, Data * pdata)

{

if(plist->tail == NULL)    // 저장된 노드가 없다면

return FALSE;


plist->before = plist->tail;        

plist->cur = plist->tail->next;   


*pdata = plist->cur->data;

return TRUE;

}


조회 LNext



int LNext(List * plist, Data * pdata)

{

if(plist->tail == NULL)    // 저장된 노드가 없다면

return FALSE;


plist->before = plist->cur;

plist->cur = plist->cur->next;


*pdata = plist->cur->data;

return TRUE;

}


원형 연결 리스트 구현 : Node 삭제



핵심적인 부분은 앞서 dummy Node 기반 연결 리스트의 삭제와 다르지 않다.

하지만 다음의 두가지 예외 경우가 있다.



만약 삭제할 노드를 tail이 가리키는데, 그 노드가 마지막 노드라면 tail의 위치 조정이 필요하다.



tail이 가리키는 노드가 머리이자 꼬리인 경우엔 tail에 NULL을 저장한다.


Data LRemove(List * plist)

{

Node * rpos = plist->cur;

Data rdata = rpos->data;

if(rpos == plist->tail)    // 삭제 대상을 tail이 가리킨다면

{

if(plist->tail == plist->tail->next)    // 그리고 마지막 남은 노드라면

plist->tail = NULL;

else

plist->tail = plist->before;

}

plist->before->next = plist->cur->next;

plist->cur = plist->before;


free(rpos);

(plist->numOfData)--;

return rdata;

}




원형 연결리스트 구현 예제


CLinkedList.h

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
32
33
34
35
#ifndef __C_LINKED_LIST_H__
#define __C_LINKED_LIST_H__
 
#define TRUE    1
#define FALSE    0
 
typedef int Data;
 
typedef struct _node
{
    Data data;
    struct _node * next;
} Node;
 
typedef struct _CLL
{
    Node * tail;
    Node * cur;
    Node * before;
    int numOfData;
} CList;
 
 
typedef CList List;
 
void ListInit(List * plist);
void LInsert(List * plist, Data data);
void LInsertFront(List * plist, Data data);
 
int LFirst(List * plist, Data * pdata);
int LNext(List * plist, Data * pdata);
Data LRemove(List * plist);
int LCount(List * plist);
 
#endif
cs


CLinkedList.c

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include <stdio.h>
#include <stdlib.h>
#include "CLinkedList.h"
 
void ListInit(List * plist)
{
    plist->tail = NULL;
    plist->cur = NULL;
    plist->before = NULL;
    plist->numOfData = 0;
}
 
void LInsertFront(List * plist, Data data)
{
    Node * newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
 
    if(plist->tail == NULL
    {
        plist->tail = newNode;
        newNode->next = newNode;
    }
    else
    {
        newNode->next = plist->tail->next;
        plist->tail->next = newNode;
    }
 
    (plist->numOfData)++;
}
 
void LInsert(List * plist, Data data)
{
    Node * newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
 
    if(plist->tail == NULL
    {
        plist->tail = newNode;
        newNode->next = newNode;
    }
    else 
    {
        newNode->next = plist->tail->next;
        plist->tail->next = newNode;
        plist->tail = newNode;
    }
 
    (plist->numOfData)++;
}
 
int LFirst(List * plist, Data * pdata)
{
    if(plist->tail == NULL)    // 저장된 노드가 없다면
        return FALSE;
 
    plist->before = plist->tail;
    plist->cur = plist->tail->next;
 
    *pdata = plist->cur->data;
    return TRUE;
}
 
int LNext(List * plist, Data * pdata)
{
    if(plist->tail == NULL)    // 저장된 노드가 없다면
        return FALSE;
 
    plist->before = plist->cur;
    plist->cur = plist->cur->next;
 
    *pdata = plist->cur->data;
    return TRUE;
}
 
Data LRemove(List * plist)
{
    Node * rpos = plist->cur;
    Data rdata = rpos->data;
 
    if(rpos == plist->tail)    // 삭제 대상을 tail이 가리킨다면
    {
        if(plist->tail == plist->tail->next)    // 그리고 마지막 남은 노드라면
            plist->tail = NULL;
        else
            plist->tail = plist->before;
    }
 
    plist->before->next = plist->cur->next;
    plist->cur = plist->before;
 
    free(rpos);
    (plist->numOfData)--;
    return rdata;
}
 
int LCount(List * plist)
{
    return plist->numOfData;
}
cs



CLinkedListMain.c

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <stdio.h>
#include "CLinkedList.h"
 
int main(void)
{
    // 원형 연결 리스트의 생성 및 초기화 ///////
    List list;
    int data, i, nodeNum;
    ListInit(&list);
 
    // 리스트에 5개의 데이터를 저장 /////// 
    LInsert(&list, 3);
    LInsert(&list, 4);
    LInsert(&list, 5);
    LInsertFront(&list, 2);
    LInsertFront(&list, 1);
    
    // 리스트에 저장된 데이터를 연속 3회 출력 ///////
    if(LFirst(&list, &data))
    {
        printf("%d ", data);
        
        for(i=0; i<LCount(&list)*3-1; i++)
        {
            if(LNext(&list, &data))
                printf("%d ", data);
        }
    }
    printf("\n");
 
    // 2의 배수를 찾아서 모두 삭제 ///////
    nodeNum = LCount(&list);
 
    if(nodeNum != 0)
    {
        LFirst(&list, &data);
        if(data%2 == 0)
            LRemove(&list);
        
        for(i=0; i < nodeNum-1; i++)
        {
            LNext(&list, &data);
            if(data%2 == 0)
                LRemove(&list);
        }
    }
 
    // 전체 데이터 1회 출력 ///////
    if(LFirst(&list, &data))
    {
        printf("%d ", data);
        
        for(i=0; i<LCount(&list)-1; i++)
        {
            if(LNext(&list, &data))
                printf("%d ", data);
        }
    }
    return 0;
}
cs


Output :

1 2 3 4 5 1 2 3 4 5 1 2 3 4 5

1 3 5

'Data Structure' 카테고리의 다른 글

Binary Tree  (0) 2016.09.12
Doubly Linked List  (0) 2016.09.09
연결리스트2  (0) 2016.09.08
연결리스트  (0) 2016.09.05
리스트에 구조체 변수 저장하기  (0) 2016.09.03
728x90

정렬 기능 추가된 연결 리스트의 ADT


추가되는 함수

void SetSortRule(List * plist, int (*comp)(LData d1, Ldata d2)  


int (*comp)(LData d1, Ldata d2)  

반환형이 int인 LData형 인자를 두개 전달받는, 함수의 주소값 전달

(typedef LData int)


다음과 같이 정의된 함수의 주소값이 SetSortRule 함수의 두번째 인자가 됨

int WhoIsPrecede(LData d1, LData d2)

{

if(d1 < d2)

return 0; // d1이 head에 가까우면 0 반환

else

return 1; // d2가 head에 가까우면 1 반환

}


리스트라는 자료구조를 바꾸지 않으면서

정렬의 기준을 지정(약속)하는 함수를 정의


DLinkedList.h

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
32
33
34
35
36
37
38
#ifndef __D_LINKED_LIST_H__
#define __D_LINKED_LIST_H__
 
#define TRUE    1
#define FALSE    0
 
typedef int LData;
 
typedef struct _node
{
    LData data;
    struct _node * next;
} Node;
 
typedef struct _linkedList
{
    Node * head;
    Node * cur;
    Node * before;
    int numOfData;
    int (*comp)(LData d1, LData d2);
} LinkedList;
 
 
typedef LinkedList List;
 
void ListInit(List * plist);
void LInsert(List * plist, LData data);
 
int LFirst(List * plist, LData * pdata);
int LNext(List * plist, LData * pdata);
 
LData LRemove(List * plist);
int LCount(List * plist);
 
void SetSortRule(List * plist, int (*comp)(LData d1, LData d2));
 
#endif
cs



연결리스트의 정렬삽입 구현



SetSortRule 함수 : 정렬 기준이 되는 함수 

LinkedList의 멤버 comp : SetSortRule 함수를 통해 전달된 함수 정보 저장

SInsert : comp에 등록된 정렬기준을 근거로 데이터 저장


-> SetSortRule 함수가 호출되면서 정렬 기준이 리스트의 멤버 comp에 등록되면,

Sinsert 함수내에서 comp에 등록된 정렬기준을 근거로 데이터를 정렬해 저장한다.



SInsert 함수


pred(predicate)가 첫번째 노드가 아닌 더미노드를 가리키는 이유

: 노드가 한쪽 방향으로만 연결되있어서 pred가 가리키는 노드의 오른쪽에만

새 노드를 추가할 수 있기 때문이다.



Sinsert함수의 while문 조건의 의미


1
2
while (pred->next != NULL &&
        plist->comp(data, pred->next->data) != 0)
cs


조건 1.

pred->next != NULL // 연결리스트의 끝에 도달했음을 의미


조건 2.

plist->comp(data, pred->next->data) != 0

// 첫번째 인자인 data가 앞서면 0을 반환해서 숫자의 대소를 비교해나감. (오름차순 기준)



newNode -> data = data;



정렬의 기준을 설정하기 위한 함수


int WhoIsPrecede(int d1, int d2) // 오름차순 정의

{

if (d1 < d2)

return 0;    // d1이 정렬 순서상 앞선다.

else

return 1;    // d2가 정렬 순서상 앞서거나 같다.

}


comp에 등록된 함수가 반환하는 값의 종류와 의미를 결정


main함수에서 SetSortRule(&list, WhoIsPrecede);    // 정렬의 기준 등록



DLinkedList.c

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#include <stdio.h>
#include <stdlib.h>
#include "DLinkedList.h"
 
void ListInit(List * plist)
{
  plist->head = (Node *)malloc(sizeof(Node));
  plist->head->next = NULL;
  plist->comp = NULL;
  plist->numOfData = 0;
}
 
void FInsert(List * plist, LData data)
{
  Node * newNode = (Node*)malloc(sizeof(Node));
  newNode->data = data;
 
  newNode->next = plist->head->next;
  plist->head->next = newNode;
  (plist->numOfData)++;
}
 
void SInsert(List * plist, LData data)
{
  Node * newNode = (Node*)malloc(sizeof(Node));
  Node * pred = plist->head;
  newNode->data = data;
 
  while(pred->next != NULL &&
    plist->comp(data, pred->next->data) != 0)
  {
    pred = pred->next;
  }
 
  newNode->next = pred->next;
  pred->next = newNode;
 
  (plist->numOfData)++;
}
 
 
void LInsert(List * plist, LData data)
{
  if(plist->comp == NULL)
    FInsert(plist, data);
  else
    SInsert(plist, data);
}
 
int LFirst(List * plist, LData * pdata)
{
  if (plist->head->next == NULL)
    return FALSE;
 
  plist->before = plist->head;
  plist->cur = plist->head->next;
 
  *pdata = plist->cur->data;
  return TRUE;
}
 
int LNext(List * plist, LData * pdata)
{
  if (plist->cur == NULL)
    return FALSE;
 
  plist->before = plist->cur;
  plist->cur = plist->cur->next;
 
  *pdata = plist->cur->data;
  return TRUE;
}
 
LData LRemove(List * plist)
{
  Node * rmPos = plist->cur;
  Node * rmData = plist->cur->data;
 
  plist->before->next = plist->cur->next;
  plist->cur = plist->before;
 
  free(rmPos);
  (plist->numOfData)--;
  return rmData;
}
 
int LCount(List * plist)
{
  return plist->numOfData;
}
 
void SetSortRule(List * plist, int (*comp)(LData d1, LData d2))
{
  plist->comp = comp;
}
cs


'Data Structure' 카테고리의 다른 글

Doubly Linked List  (0) 2016.09.09
Circular Linked List  (0) 2016.09.09
연결리스트  (0) 2016.09.05
리스트에 구조체 변수 저장하기  (0) 2016.09.03
배열 기반 리스트  (0) 2016.09.01
728x90

연결리스트 (Linked List)


연결리스트의 구현을 이해하기 위해서는,

malloc 함수와 free함수 기반의 메모리 동적할당에 대한 완전한 이해가 필요



배열은 index를 이용해 순차 접근이 가능한 자료구조

배열은 기본적으로 정적이다. (길이의 변경 불가능)


#include <stdio.h>

int main(void)
{
	int arr[10];
	int readCount = 0;
	int readData;
	int i;

	while (1)
	{
		printf("자연수 입력 (0이하 값 입력시 종료): ");
		scanf("%d", &readData);
		if (readData < 1)
			break;

		arr[readCount++] = readData;
	}

	for (i = 0; i<readCount; i++)
		printf("%d  ", arr[i]);

	return 0;
}



위의 예제에서 0 이하의 값을 입력하지 않고, 

계속해서 할당된 배열의 길이를 넘어서는 입력을 할 시 디버그 에러가 나게 된다.


이를 해결하기 위해선 메모리의 동적할당을 이용해야 한다.



* head : 첫번째 node를 가리키기 위함

* tail : head와 달리 필요없을 수도 있음


* cur : (current) 순차접근을 위한 것



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//노드의 머리에 추가하는 연결리스트
 
/* 노드 추가 과정 (동적 할당) */
    Node * newNode = NULL;
    newNode = (Node *)malloc(sizeof(Node));
    newNode->data = readData;
    newNode->next = NULL;
 
    if (head == NULL)
    {
      head = newNode;
      //tail = newNode;
    }
    else
    {
      newNode->next = head;
      head = newNode;
    }
  }
cs



새 노드를 연결리스트 머리에 추가하는 경우는, * tail이 불필요하지만 저장된 순서가 역순이 된다.


꼬리에 추가하는 경우는 저장된 순서가 유지되지만 * tail이 필요



노드의 꼬리에 추가하는 연결리스트

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <stdio.h>
#include <stdlib.h>
 
typedef struct _node
{
  int data;
  struct _node * next;
}Node;
 
int main(void)
{
  // NULL 초기화
  Node * head = NULL;
  Node * tail = NULL;
  Node * cur = NULL;
  Node * newNode = NULL;
 
  // 데이터 입력받는 과정
  int readData;
  while (1)
  {
    printf("자연수 입력: ");
    scanf("%d"&readData);
 
    if (readData < 1)
      break;  
 
    // 노드 생성
    newNode = (Node*)malloc(sizeof(Node));
    newNode->data = readData;
    newNode->next = NULL;
 
    if (head == NULL)
      head = newNode;
    else
      tail->next = newNode;
 
    tail = newNode;
  }  
  // 데이터 출력과정
  printf("\n 전체 데이터 출력: \n");
  if (head == NULL)
    printf("데이터 없음");
 
  else
  {
    cur = head;
    printf("%d ", cur->data);
 
    while(cur->next != NULL)
    {
      cur = (*cur).next;
      printf("%d ", (*cur).data);
    }
  }
  printf("\n");
 
  // 메모리 해제 과정
  if (head == NULL)
    return 0;
 
  else
  {
    Node * delNode = head;
    Node * delNextNode = head->next;    
    printf("%d 삭제 \n", head->data);
    free(delNode);
 
    while (delNextNode != NULL)
    {
      delNode = delNextNode;
      delNextNode = delNode->next;
      printf("%d 삭제 \n", delNode->data);
      free(delNode);      
    }
  }
}
cs




정렬 기능 추가된 연결 리스트의 ADT


추가되는 함수

void SetSortRule(List * plist, int (*comp)(LData d1, Ldata d2)  


int (*comp)(LData d1, Ldata d2)  

반환형이 int인 LData형 인자를 두개 전달받는, 함수의 주소값 전달

(typedef LData int)


다음과 같이 정의된 함수의 주소값이 SetSortRule 함수의 두번째 인자가 됨

int WhoIsPrecede(LData d1, LData d2)

{

if(d1 < d2)

return 0; // d1이 head에 가까우면 0 반환

else

return 1; // d2가 head에 가까우면 1 반환

}


리스트라는 자료구조를 바꾸지 않으면서

정렬의 기준을 지정(약속)하는 함수를 정의




더미 노드 기반, 연결리스트


앞선 예제에서 첫번째 노드는 * head가 가리킨다는 점에서

다른 노드들과 차이가 있었다.


때문에 노드추가, 삭제, 조회하는 방법에 있어 일관성이 없다는 단점이 있었다.

(첫번째 노드와 두번째 이후 노드에 차이가 있다)


이를 해결하기위해 Dummy Node를 추가한다.


처음 추가되는 노드가 구조상 두번째 노드가 되며,

노드 추가, 삭제 및 조회의 과정이 일관된 형태로 구성된다.


LinkedRead.c


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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <stdio.h>
#include <stdlib.h>
 
typedef struct _node
{
    int data;
    struct _node * next;
} Node;
 
int main(void)
{
    Node * head = NULL;
    Node * tail = NULL;
    Node * cur = NULL;
 
    Node * newNode = NULL;
    int readData;
 
    head = (Node*)malloc(sizeof(Node));    // 추가 된 문장, 더미 노드 추가
    tail = head;
    
    /**** 데이터를 입력 받는 과정 ****/
    while (1)
    {        
        printf("자연수 입력: ");
        scanf("%d"&readData);
        if (readData < 1)
            break;
 
        /*** 노드의 추가과정 ***/
        newNode = (Node*)malloc(sizeof(Node));
        newNode->data = readData;
        newNode->next = NULL;
 
        /*
        if(head == NULL)
        head = newNode;
        else
        tail->next = newNode;
        */
        tail->next = newNode;
 
        tail = newNode;
    }
    printf("\n");
 
    /**** 입력 받은 데이터의 출력과정 ****/
    printf("입력 받은 데이터의 전체출력! \n");
    if (head == NULL)
    {
        printf("저장된 자연수가 존재하지 않습니다. \n");
    }
    else
    {
        cur = head;
        //    printf("%d  ", cur->data);   // 첫 번째 데이터 출력
 
        while (cur->next != NULL)    // 두 번째 이후의 데이터 출력
        {
            cur = cur->next;
            printf("%d  ", cur->data);
        }
    }
    printf("\n\n");
 
    /**** 메모리의 해제과정 ****/
    if (head == NULL)
    {
        return 0;    // 해제할 노드가 존재하지 않는다.
    }
    else
    {
        Node * delNode = head;
        Node * delNextNode = head->next;
 
        //    printf("%d을(를) 삭제합니다. \n", head->data);
        //    free(delNode);    // 첫 번째 노드의 삭제
 
        while (delNextNode != NULL)    // 두 번째 이후의 노드 삭제 위한 반복문
        {
            delNode = delNextNode;
            delNextNode = delNextNode->next;
 
            printf("%d을(를) 삭제합니다. \n", delNode->data);
            free(delNode);    // 두 번째 이후의 노드 삭제
        }
    }
 
    return 0;
}
cs



728x90
리스트에 구조체 변수 저장하기

#ifndef __POINT_H__
#define __POINT_H__

typedef struct _point
{
	int xpos;
	int ypos;
} Point;

// Point 변수의 xpos, ypos 값 저장
void SetPointPos(Point * ppos, int xpos, int ypos);

// Point 변수의 xpos, ypos 정보 출력 
void ShowPointPos(Point * ppos);

// 두 Point 변수의 비교
int PointComp(Point * pos1, Point * pos2); 

#endif


Point.c (PointComp 함수의 반환값은 임의로 지정)

#include <stdio.h>
#include "Point.h"

void SetPointPos(Point * ppos, int xpos, int ypos)	// 값 저장
{
	ppos->xpos = xpos;
	ppos->ypos = ypos;
}

void ShowPointPos(Point * ppos)
{
	printf("[%d %d]", ppos->xpos, ppos->ypos);
}

int PointComp(Point * pos1, Point * pos2)
{
	//	xpos가 서로 같으면 1, ypos가 서로 같으면 2, 모두 같으면 0, 모두 다르면 -1 반환
	if (pos1->xpos == pos2->xpos)
		return 1;
	else if (pos1->ypos == pos2->ypos)
		return 2;
	else if (pos1->xpos == pos2->xpos && pos1->ypos == pos2->ypos)
		return 0;
	else
		return -1;
}



- typedef 선언 변경
typedef int LData -> typedef Point * LData;

Point 구조체의 정의가 담긴 Point.h를 include
#include "Point.h"

#include <stdio.h> #include <stdlib.h> #include "ArrayList.h" #include "Point.h" int main(void) { List list; Point compPos; Point * ppos; ListInit(&list); /*** 4개의 데이터 저장 ***/ ppos = (Point*)malloc(sizeof(Point)); SetPointPos(ppos, 2, 1); LInsert(&list, ppos); ppos = (Point*)malloc(sizeof(Point)); SetPointPos(ppos, 2, 2); LInsert(&list, ppos); ppos = (Point*)malloc(sizeof(Point)); SetPointPos(ppos, 3, 1); LInsert(&list, ppos); ppos = (Point*)malloc(sizeof(Point)); SetPointPos(ppos, 3, 2); LInsert(&list, ppos); /*** 저장된 데이터의 출력 ***/ printf("현재 데이터의 수: %d \n", LCount(&list)); if(LFirst(&list, &ppos)) { ShowPointPos(ppos); while(LNext(&list, &ppos)) ShowPointPos(ppos); } printf("\n"); /*** xpos가 2인 모든 데이터 삭제 ***/ compPos.xpos=2; compPos.ypos=0; if(LFirst(&list, &ppos)) { if(PointComp(ppos, &compPos)==1) { ppos=LRemove(&list); free(ppos); } while(LNext(&list, &ppos)) { if(PointComp(ppos, &compPos)==1) { ppos=LRemove(&list); free(ppos); } } } /*** 삭제 후 남은 데이터 전체 출력 ***/ printf("현재 데이터의 수: %d \n", LCount(&list)); if(LFirst(&list, &ppos)) { ShowPointPos(ppos); while(LNext(&list, &ppos)) ShowPointPos(ppos); } printf("\n"); return 0; }


Output :
현재 데이터의 수: 4
[2 1][2 2][3 1][3 2]
현재 데이터의 수: 2
[3 1][3 2]


위의 소스 main함수에서 LRemove 함수가 삭제된 데이터를 return 하도록 한 이유를 알 수 있다.
실제 Point 구조체 변수의 값은 heap에 저장, 주소값만이 리스트에 저장된다.

이 주소값은 Point 구조체를 동적할당한 결과이다.
ppos = (Point*)malloc(sizeof(Point));

삭제한 값을 반환해줘야 free 함수를 통한 해당 메모리 공간을 해제 가능

일반적인 리스트가 메모리 해제까지 해주진 못하기 때문에
리스트에 저장된 값이 주소값이냐, 그 주소 값이 동적할당인 결과를 구분해 메모리 공간 해제를 하도록 구현하는 것은 무리다.

따라서 LRemove 함수처럼 데이터를 소멸시키는 함수는, 반드시 소멸된 데이터를 return 하도록 정의해야 한다.
그렇지 않으면 그 메모리 공간이 프로그램 실행내내 계속 상주하는 문제가 생길 것이기 때문이다.


'Data Structure' 카테고리의 다른 글

Circular Linked List  (0) 2016.09.09
연결리스트2  (0) 2016.09.08
연결리스트  (0) 2016.09.05
배열 기반 리스트  (0) 2016.09.01
자료구조 개요, 알고리즘 성능 분석법, 이진탐색 알고리즘  (0) 2016.04.29
728x90

리스트 자료구조는 크게 두가지로 나뉨


순차 리스트 : 배열을 기반으로 구현된 리스트

연결 리스트 : 메모리 동적할당을 기반으로 구현된 리스트



리스트 자료 구조의 특성

: 데이터를 나란히 저장하며, 중복된 데이터의 저장을 막지 않는다.




아래 예제 파일을 열어 ArrayList.c는 가급적 열지 않고 직접 구현해보도록



* 자료 구조의 내부 구현을 모르고도 해당 자료구조의 활용이

가능하도록 ADT를 정의하는 것이 맞다.





리스트의 데이터 참조 과정


순서대로 참조하려면 먼저 LFirst를 호출해 첫번째 데이터를 얻는다.

두 번째 이후의 데이터는 LNext를 호출해서 얻는다.


LFirst 함수와 LNext 함수는 더 이상 참조할 데이터가 없으면 

FALSE를 반환한다.



리스트내에서 '데이터 참조위치'를 기록하기 때문에,
참조를 새롭게 시작하기 위해선 LFirst함수를 호출해 정보를 초기화한다.

ListMain.c
#include <stdio.h>
#include "ArrayList.h"

int main(void)
{
	/*** ArrayList의 생성 및 초기화 ***/
	List list;
	int data;
	int i;
	int sum = 0;

	ListInit(&list);

	/*** 정수 1부터 9까지 데이터 저장 ***/
	for (i = 1; i < 10; i++)
		LInsert(&list, i);

	/*** 저장된 데이터의 합 출력 ***/	
	if (LFirst(&list, &data))
	{
		sum = sum + data;				

		while (LNext(&list, &data))
			sum = sum + data;		
	}
	
	/*** 저장된 데이터의 전체 출력 ***/
	printf("현재 데이터 수 : %d\n", LCount(&list));

	if (LFirst(&list, &data))
	{
		printf("%d ", data);

		while (LNext(&list, &data))
			printf("%d ", data);
	}
	printf("\n합 : %d \n\n", sum);

	/*** 2의 배수와 3의 배수에 해당하는 값 삭제 ***/
	if (LFirst(&list, &data))
	{
		if (data % 2 == 0 || data % 3 == 0)
			LRemove(&list);

		while (LNext(&list, &data))
		{
			if (data % 2 == 0 || data % 3 == 0)
				LRemove(&list);
		}
	}

	/*** 저장된 데이터의 전체 출력 ***/
	printf("현재 데이터 수 : %d\n", LCount(&list));

	if (LFirst(&list, &data))
	{
		printf("%d ", data);

		while (LNext(&list, &data))
			printf("%d ", data);
	}
	printf("\n");

	return 0;
}

Output :


현재 데이터 수 : 9

1 2 3 4 5 6 7 8 9

합 : 45


현재 데이터 수 : 3

1 5 7



ArrayList.h

#ifndef __ARRAY_LIST_H__
#define __ARRAY_LIST_H__

#define TRUE	1
#define FALSE	0

/*** ArrayList의 정의 ****/
#define LIST_LEN	100
typedef int LData;

typedef struct __ArrayList
{
	LData arr[LIST_LEN];
	int numOfData;
	int curPosition;
} ArrayList;


/*** ArrayList와 관련된 연산들 ****/
typedef ArrayList List;

void ListInit(List * plist);
void LInsert(List * plist, LData data);

int LFirst(List * plist, LData * pdata);
int LNext(List * plist, LData * pdata);

LData LRemove(List * plist);
int LCount(List * plist);

#endif

typedef ArrayList List; // List는 배열 기반 리스트


typedef LinkedList List; // List는 연결 기반 리스트

List에 다른 이름을 부여하는 것만으로 사용하는 리스트의 종류를 바꿀 수 있다.


typedef int LData; 도 마찬가지로 다른 이름을 부여해 활용할 수 있다.




ArrayList.c    // 가급적 스스로 구현을 연습해보는 것이 좋다.

#include <stdio.h>
#include "ArrayList.h"

void ListInit(List * plist)	// 리스트 초기화
{
	(plist->numOfData) = 0;
	(plist->curPosition) = 0;	// -1에서 0으로 참조 위치 초기화, 첫번째 데이터 참조
}

void LInsert(List * plist, LData data)
{
	if (plist->numOfData >= LIST_LEN)	// 데이터 개수가 배열 총 길이보다 많거나 같으면 저장불가
	{
		puts("데이터 저장불가");
		return;
	}

	plist->arr[plist->numOfData] = data; // 데이터 저장, numOfData를 index값으로 활용
	(plist->numOfData)++;
}

int LFirst(List * plist, LData * pdata)	// 첫번째 데이터가 pdata가 가리키는 메모리에 저장
{
	if (plist->numOfData == 0) // 저장된 데이터가 없으면 
		return FALSE;// FALSE 반환

	(plist->curPosition) = 0; // 현재 참조위치가 초기화
	*pdata = plist->arr[0];	// plist->arr[plist->curPosition]와 같은 의미지만 강조를 위한 것
	return TRUE;
}

int LNext(List * plist, LData * pdata)
{
	if (plist->curPosition >= (plist->numOfData) - 1)	
		// 더 이상 참조할 데이터가 없을 때(현재 참조위치와 데이터 수 비교)		
		return FALSE; // FALSE 반환

	(plist->curPosition)++;
	*pdata = plist->arr[plist->curPosition];
	return TRUE;
}

LData LRemove(List * plist)
{
	int rmPos = plist->curPosition;	// 현재 위치가 삭제할 위치가 된다.
	LData rmData = plist->arr[rmPos];	// 삭제할 인덱스의 데이터를 rmData에 저장

	// 해당 인덱스 삭제후, 한칸씩 왼쪽 이동해 그 빈칸을 메움
	int i;
	for (i = rmPos; i < (plist->numOfData) - 1; i++)
		plist->arr[i] = plist->arr[i + 1];	// 오른쪽의 인덱스가 왼쪽으로 이동

	(plist->numOfData)--;
	(plist->curPosition)--;
	// curPosition이 참조가 이뤄지지않은, 이동한 데이터를 가리키므로 참조위치를 왼쪽으로 옮겨야 함
	return rmData;	// 삭제된 데이터는 반환해야 한다. (이것은 메모리의 공간 해제를 위한 것)
}

int LCount(List * plist)	// 리스트에 저장된 데이터 수(numOfData) 반환
{
	return plist->numOfData;
}



배열 기반 리스트의 장 단점


장점

인덱스 값을 기준으로 쉽게 참조가 가능하다.


단점

배열 길이가 초기에 결정되며 변경 불가능하다.

삭제 과정에서 데이터의 이동(복사)가 매우 빈번히 일어난다.




'Data Structure' 카테고리의 다른 글

Circular Linked List  (0) 2016.09.09
연결리스트2  (0) 2016.09.08
연결리스트  (0) 2016.09.05
리스트에 구조체 변수 저장하기  (0) 2016.09.03
자료구조 개요, 알고리즘 성능 분석법, 이진탐색 알고리즘  (0) 2016.04.29
728x90

파일의 분할과 헤더파일 디자인


외부에 선언 및 정의되었다고 알려주는 extern


extern 선언을 생략해도 되지만,

다른 파일에 선언 및 정의되었음을 알리기위해 extern을 쓴다.



둘 이상의 파일을 컴파일하는 방법 (프로젝트에 여러 파일을 추가해 컴파일)


기존에 존재하는 파일은 Existing Item... 새로 만드려면 New Item...



전역변수의 static 선언


static int num=0; // 외부 소스파일에서 접근 불가능한 전역변수


void SimpleFunc(void)

{

....

}


함수의 static 선언


static void MinCnt(void)

{

cnt--;

}


외부 소스파일에서의 접근(호출)을 허용하지않기 위한 선언

즉 접근범위를 파일 내부로 제한한다.




헤더파일의 디자인과 활용

프로젝트에 다음 헤더파일과 소스파일을 추가해 컴파일하면 정상적인 실행이 된다. (아래 세 파일은 동일한 디렉토리에 존재해야만 함)


header1.h

1
2
{
    puts("Hello world!");


header2.h

1
2
    return 0;
}


main.c

1
2
3
4
5
#include <stdio.h>

int main()
#include "header1.h"    // 이 위치에 .h에 저장된 내용을 include
#include "header2.h"    // 이 위치에 .h에 저장된 내용을 include



헤더파일을 include 하는 첫번째 방법


#include <헤더파일 이름>

표준 헤더파일(C 표준에서 정의하는 stdio.h, stdlib.h같은 기본적으로 제공되는 헤더파일)이 저장되어 있는 디렉토리에서 파일을 찾게 됨



헤더파일을 include 하는 두번째 방법


#include "헤더파일 이름"

소스파일이 저장된 디렉토리에서 헤더파일을 찾게 됨


두번째 방법은 다음과 같이 절대 경로를 명시해서 헤더파일 지정가능

#include "C:\CPwer\MyProject\header.h"    // windows 상에서의 절대경로 지정



하지만 절대경로를 지정해서 헤더파일을 선언하면 다른 컴퓨터에서는 경로(path)가 다를 수가 있기 때문에 잘 쓰지 않는다.


따라서 다음과 같이 상대 경로를 자주 이용한다.


#include "..\..\Myheader\header2.h"




extern 선언은 대개 헤더파일에 삽입


필요한 함수를 소스파일에서 모두 extern 선언할 필요없이

헤더파일만 include하면 되므로 편하고 소스의 길이도 준다.


extern 선언은 실행파일의 크기를 증가시키거나 성능의 손해를 

보는게 아니므로 신경쓰지 않아도 된다.



헤더파일 디자인의 예




basicArith.h    

/* 매크로 PI에 대한 정의가 삽입 (매크로 명령문은 개별 파일단위로만 유효), 매크로 PI가 필요한 소스파일은 이 헤더파일은 include하면 된다.

   다른 소스파일에서 include해두면 혹여나 정의해둔 상수값이 바뀌어도 해당 헤더파일 이외의 수정은 필요없게 된다 */

1
2
3
4
5
6
#define PI 3.1415

extern double Add(double num1, double num2);
double Min(double num1, double num2);
double Mul(double num1, double num2);
double Div(double num1, double num2);


basicArith.c    // 사칙연산의 기능을 제공하는 함수들이 정의. 함수의 선언은 basicArith.h에 포함되어 있다.

double Add(double num1, double num2)
{
	return num1+num2;
}

double Min(double num1, double num2)
{
	return num1-num2;
}

double Mul(double num1, double num2)
{
	return num1*num2;
}

double Div(double num1, double num2)
{
	return num1/num2;
}


areaArith.h

1
2
double TriangleArea(double base, double height);
double CircleArea(double rad);


areaArith.c    // 면적을 구하는 함수들이 정의. 이 함수들은 basicArith.c에 정의된 Mul, Div와 같은 함수들을 호출하기 때문에 basicArith.h를 include

1
2
3
4
5
6
7
8
9
10
11
#include "basicArith.h"

double TriangleArea(double base, double height)
{
	return Div(Mul(base, height), 2);
}

double CircleArea(double rad)
{
	return Mul(Mul(rad, rad), PI);
}



roundArith.h

1
2
double RectangleRound(double base, double height);
double SquareRound(double side);


roundArith.c    // 둘레를 구하는 함수들이 정의. 이 함수들도 basicArith.c에 정의된 사칙연산과 관련된 함수들을 호출. 따라서 basicArith.h를 include

1
2
3
4
5
6
7
8
9
10
11
#include "basicArith.h"

double RectangleRound(double base, double height)
{
	return Mul(Add(base, height), 2);
}

double SquareRound(double side)
{
	return Mul(side, 4);
}


main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include "areaArith.h"
#include "roundArith.h"

int main(void)
{
	printf("삼각형 넓이(밑변 4, 높이 2): %g \n", 
		TriangleArea(4, 2));
	printf("원 넓이(반지름 3): %g \n", 
		CircleArea(3));

	printf("직사각형 둘레(밑변 2.5, 높이 5.2): %g \n", 
		RectangleRound(2.5, 5.2));
	printf("정사각형 둘레(변의 길이 3): %g \n", 
		SquareRound(3));
	return 0;
}


Output : 

삼각형 넓이(밑변 4, 높이 2): 4

원 넓이(반지름 3): 28.2735

직사각형 둘레(밑변 2.5, 높이 5.2): 15.4

정사각형 둘레(변의 길이 3): 12




구조체의 정의도 헤더파일에 넣어두고, 필요할때마다 include하는 것이 일반적


구조체의 정의도 파일단위로 선언이 유효


동일 구조체의 정의를 둘 이상의 소스파일에서 필요로 하면 

소스파일마다 정의를 추가시켜 줘야 한다.



헤더파일의 중복삽입 문제


extern int num;

void Increment(void);

바로 위와 같은 유형의 선언은 두 번 이상 삽입이 되어도

컴파일 오류가 발생하지 않는다. 이는 컴파일러에게 전달하는 메시지일뿐이기 때문이다.


하지만 구조체나 함수의 정의가 포함된 헤더파일을 두 곳 이상에서 참조하게 되면

두 번 이상 정의된 형태가 되어 컴파일 에러가 발생한다.


따라서 이 문제는 조건부 컴파일을 활용해 해결한다.


조건부 컴파일을 활용한 중복삽입 문제의 해결


#ifndef __STDIV2_H__    // __STDIV2_H__    매크로가 정의되어 있지 않다면

#define __STDIV2_H__    // __STDIV2_H__    매크로를 정의하라 (정의되어 있다면 포함되지 않음)



/* 이 파일을 처음 포함하는 소스파일은 __STDIV2_H__가 정의되있지 않은 상태이므로 2~8행까지 포함

   따라서 2행에 의해 매크로 __STDIV2_H__가 define, 4~8행에서 구조체 Div가 정의된다.


   이후 다른 파일에서 다시 포함하는 경우네는 매크로 __STDIV2_H__가 정의된 상태이므로 1~10행의 모든 내용이 포함되지 않는다. 

   따라서 구조체 Div가 소스파일당 하나씩 정의되게 된다.

 */

stdiv2.h    

1
2
3
4
5
6
7
8
9
10
#ifndef __STDIV2_H__
#define __STDIV2_H__

typedef struct div
{
	int quotient;   // 몫
	int remainder;  // 나머지
} Div;

#endif


intdiv4.h

1
2
3
4
5
6
7
#ifndef __INTDIV4_H__
#define __INTDIV4_H__

#include "stdiv2.h"
Div IntDiv(int num1, int num2);

#endif


intdiv4.c

1
2
3
4
5
6
7
8
9
#include "stdiv2.h"

Div IntDiv(int num1, int num2)
{
	Div dval;
	dval.quotient=num1/num2;
	dval.remainder=num1%num2;
	return dval;
}


main.c

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include "stdiv2.h"
#include "intdiv4.h"

int main(void)
{
	Div val=IntDiv(5, 2);
	printf("몫: %d \n", val.quotient);
	printf("나머지: %d \n", val.remainder);
	return 0; 
}


main.c에 2행과 3행에서 stdiv2.h를 두 번 포함하려고 한다.

하지만 stdiv2.h에 삽입된 매크로 지시자 #ifndef~#define~#endif에 의해 중복 삽입 문제는 생기지 않는다.


헤더파일에 존재하는 내용은 #ifndef~#define~#endif를 이용해 중복삽입 문제를 미연에 방지하는 것이 좋다.

'Study > C' 카테고리의 다른 글

C 복습-콘솔 입출력  (0) 2017.02.11
c 복습  (0) 2016.11.03
선행처리기와 매크로2  (0) 2016.08.30
선행처리기와 매크로  (0) 2016.08.29
파일위치 지시자 / 메모리 관리와 동적할당  (0) 2016.08.28
728x90

조건부 컴파일(Conditional Compilation)을 위한 매크로


#if... #endif : 참이라면
조건부 코드 삽입을 위한 지시자


참이면 #if~#endif 까지 컴파일 대상에 포함
거짓이면 선행처리기가 #if~#endif 까지의 코드를 지움


#include <stdio.h>
#define  ADD     1    // 0이 아닌 값은 참
#define  MIN     0    // 0은 거짓

int main(void)
{
	int num1 = 20;
        int num2 = 10;

#if ADD    // ADD가 '참'이라면
	printf("%d + %d = %d \n", num1, num2, num1+num2);
#endif

#if MIN    // MIN이 '참'이라면
	printf("%d - %d = %d \n", num1, num2, num1-num2);
#endif

	return 0;
}



Output:

1
20 + 10 = 30 





#ifdef... #endif : 정의되었다면

정의되었다면 컴파일 대상에 포함
정의되지 않았다면 선행처리기가 #ifdef~#endif 까지의 코드를 지움



#include <stdio.h> // #define ADD 1 #define MIN 0 // 정의 여부만 물으므로 값을 지정하지 않아도 상관없음 int main(void) { int num1 = 20; int num2 = 10; #ifdef ADD // 주석에 의해 정의되지 않은걸로 처리 printf("%d + %d = %d \n", num1, num2, num1+num2); #endif #ifdef MIN // 정의되어 있으므로 컴파일 대상에 포함 printf("%d - %d = %d \n", num1, num2, num1-num2); #endif return 0; }



Output:

1
20 - 10 = 10 




#ifndef... #endif : 정의되지 않았다면


정의되지 않았다면 포함

정의되어있다면 선행처리기가 #ifndef~#endif 까지의 코드를 지움




#else의 삽입: #if, #ifdef, #ifdef에 해당 (#endif로 끝을 표시)


#if  

if문이 참이면 포함, 거짓이면 else문이 포함


1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#define  HIT_NUM      5

int main(void)
{
#if HIT_NUM==5    // if문이 참이면 컴파일 대상에 포함
	puts("매크로 상수 HIT_NUM은 현재 5입니다.");    
#else     // if문이 참이 아닐 때 else문이 컴파일 대상으로 포함, 이 때 if문의 코드는 전처리기가 제거
	puts("매크로 상수 HIT_NUM은 현재 5가 아닙니다.");
#endif     // if문의 끝을 나타냄

	return 0;
}



Output:

1
매크로 상수 HIT_NUM은 현재 5입니다.




#elif의 삽입 : else if와 유사, #if에만 해당 


if~else if~else 구문과 동일 형태를 구성할 수 있다.


#if~#elif~#else

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#define  HIT_NUM      7

int main(void)
{
#if HIT_NUM==5
	puts("매크로 상수 HIT_NUM은 현재 5입니다.");
#elif HIT_NUM==6
	puts("매크로 상수 HIT_NUM은 현재 6입니다.");
#elif HIT_NUM==7
	puts("매크로 상수 HIT_NUM은 현재 7입니다.");
#else
	puts("매크로 상수 HIT_NUM은 5, 6, 7은 확실히 아닙니다.");
#endif 

	return 0;
}


Output : 

매크로 상수 HIT_NUM은 현재 7입니다.




'매개변수'의 결합과 '문자열'화


문자열 내에서는 매크로의 매개변수 치환 불가


#define STRING_JOB(A, B)    "A의 직업은 B입니다."

괄호안의 A와 B는 문자열안(" ")에 치환될 수 없다.


1
2
3
4
5
6
7
8
9
#include <stdio.h>
#define  STRING_JOB(A, B)     "A의 직업은 B입니다."

int main(void)
{
	printf("%s \n", STRING_JOB(이동춘, 나무꾼));
	printf("%s \n", STRING_JOB(한상순, 사냥꾼));
	return 0;
}

Output :

A의 직업은 B입니다.

A의 직업은 B입니다.



"이동춘의 직업은 나무꾼입니다."라는 문장을 기대하지만

"A의 직업은 B입니다."로 치환된다.



이를 해결하기 위해 #연산자를 이용


#define    STR(ABC) #ABC

매개변수 ABC에 전달되는 인자를 문자열 "ABC"로 치환


예)

STR(123) "123"

STR(12, 23, 34) "12, 23, 34"



char * str = "ABC" "DEF";

char * str = "ABCDEF"


위의 두 문장 선언은 같다.

둘 이상의 문자열 선언을 나란히 하면, 하나의 문자열 선언으로 인식


1
2
3
4
5
6
7
8
9
#include <stdio.h>
#define  STRING_JOB(A, B)     #A"의 직업은 " #B"입니다."

int main(void)
{
	printf("%s \n", STRING_JOB(이동춘, 나무꾼));
	printf("%s \n", STRING_JOB(한상순, 사냥꾼));
	return 0;
}

Output : 

이동춘의 직업은 나무꾼입니다.

한상순의 직업은 사냥꾼입니다.




매크로 연산자 없이 단순 연결은 불가능


10/65/175 -> 1065175


#define STNUM(Y, S, P) YSP

#define STNUM(Y, S, P) Y S P

위의 두 정의는 컴파일 에러



#define STNUM(Y, S, P) ((Y)*100000+(S)*1000+(P))

최선의 해결책인듯 하지만...


1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
//#define  STNUM(Y, S, P)   YSP
//#define  STNUM(Y, S, P)   Y S P
#define  STNUM(Y, S, P)   ((Y)*100000+(S)*1000+(P))

int main(void)
{
	printf("학번: %d \n", STNUM(10, 65, 175));
	printf("학번: %d \n", STNUM(10, 65, 075));
	return 0;
}

Output:

1
2
학번: 1065175 
학번: 1065061 


위와 같이 0이 8진수를 의미하는 걸로 인식되어서 원하던 결과를 얻을 수 없다.



필요한 형태대로 단순 결합 : ## 연산자


#define CON(UPP, LOV) UPP ## 00 ## LOW


int num = CON(22, 77); -> 220077



#define STNUM(Y, S, P) Y ## S ## P

YSP가 단순 결합됨


1
2
3
4
5
6
7
8
9
#include <stdio.h>
#define	STNUM(Y, S, P)	Y##S##P

int main(void)
{
	printf("학번: %d \n", STNUM(10, 65, 175));
	printf("학번: %d \n", STNUM(10, 65, 075));
	return 0;
}



Output:

1
2
학번: 1065175 
학번: 1065075 


'Study > C' 카테고리의 다른 글

c 복습  (0) 2016.11.03
파일의 분할과 헤더파일의 디자인  (0) 2016.08.30
선행처리기와 매크로  (0) 2016.08.29
파일위치 지시자 / 메모리 관리와 동적할당  (0) 2016.08.28
파일 입출력 공부  (0) 2016.08.25
728x90

선행처리기(Preprocessor)


선행처리 거친 소스파일은 프로그래머가 인지할 수 있는 형태이다.


선행처리기의 일

- 단순 치환


소스

1
2
3
4
5
6
#define	PI	3.14	// 선행처리기에게 명령하는 문장은 #으로 시작하며 한줄마다 처리된다. (끝에 semi colon붙이지 않음)

int main()
{
	num = PI * 3.5;
}


선행처리 후 소스

#define PI 3.14 //지워짐 


1
2
3
4
int main(void)
{
	num  = 3.14 * 3.5;
}



Object-like macro


#define PI 3.14

directive / macro / macro's body


PI를 상수 3.14로 정의 


1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

#define  NAME          "홍길동"
#define  AGE            24   
#define  PRINT_ADDR     puts("주소: 경기도 용인시\n");

int main(void)
{
	printf("이름: %s \n", NAME);
	printf("나이: %d \n", AGE);
	PRINT_ADDR;
	return 0;
}


Output:

1
2
3

이름: 홍길동 
나이: 24 
주소: 경기도 용인시


Function-like macro

#define SQUARE (X) X*X


SQUARE(123); 123*123


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#define SQUARE(X)   X*X

int main(void)
{
	int num=20;

	/* 정상적 결과 출력 */
	printf("Square of num: %d \n", SQUARE(num));	//20*20
	printf("Square of  -5: %d \n", SQUARE(-5));	// (-5)*(-5)
	printf("Square of 2.5: %g \n", SQUARE(2.5));	// 2.5*2.5

	/* 비정상적 결과 출력 */
	printf("Square of 3+2: %d \n", SQUARE(3+2));	// (5)*(5)가 아닌 3+2*3+2 = 11
	return 0;
}


Output:

1
2
3
4
Square of num: 400 
Square of  -5: 25 
Square of 2.5: 6.25 
Square of 3+2: 11 


잘못된 매크로 함수 정의의 해결책


#define SQUARE(X) X*X


바로 위 예제에서 SQUARE(3+2)가 기대했던 25가 아닌 값으로 연산되는걸 해결해보자.



해결책1

#define SQUARE(X)    X*X

SQUARE(3+2)을 SQUARE((3+2))으로 바꿈


(3+2)*(3+2)=25, 하지만 매크로라고 하기 무색하게 사용이 불편



해결책2

#define SQUARE(X)    (X)*(X)


SQUARE(3+2) (3+2)*(3+2)=25

이 경우는 해결, 하지만...


num=120/SQUARE(2) 120 / (2)*(2)

120/4=30을 기대하지만 120/2=60, 60*2=120으로 연산된다.



최상의 해결책!

#define SQUARE(X)    ((X)*(X))

120 / ((2)*(2)) = 30




매크로를 두 줄에 걸쳐서 정의하는 방법


#define SQUARE(X)  \

((X)*(X))


한줄의 끝에 매크로의 정의가 이어짐을 의미하는 backslash추가




먼저 정의된 매크로의 사용

위에서 먼저 정의된 매크로를 아래에서 사용할 수도 있다.

아래는 이를 응용한 예제이다.


1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#define PI 3.14
#define PRODUCT(X, Y)   ((X)*(Y))
#define CIRCLE_AREA(R)  (PRODUCT((R), (R))*PI)

int main(void)
{
	double rad=2.1;
	printf("반지름 %g인 원의 넓이: %g \n", rad, CIRCLE_AREA(rad));
	return 0;
}

// (((2.1)*(2.1))*3.14) = 13.8474

Output:

1
반지름 2.1인 원의 넓이: 13.8474 



매크로 함수의 장점 (일반함수와 비교)


- 매크로 함수는 일반함수에 비해 실행속도가 빠르다.


함수 호출을 완성화기 위해선 별도의 메모리 공간이 필요하고

호출된 함수로의 이동 및 반환의 과정을 거쳐야 한다.

(메모리 공간 할당과 해제)


매크로 함수는 선행처리 과정에서 치환이 이뤄지니 그러한 과정이 불필요하며, 따라서 실행속도가 빠른 것.



- 자료형에 의존적이지 않다.


ADD (int n1, int n2)

dAdd(double n1, double n2)

일반함수는 위와 같이 전달되는 인자의 자료형에 의존적이다.


하지만 매크로 함수는 자료형에 따른 별도의 함수 정의가 필요 없다.



매크로 함수의 단점


1. 정의하기가 까다롭다.


일반함수

int DiffABS (int a, int b);

{

if(a>b)

return a-b;

else

return b-a;

}


정의된 매크로 함수

#define DIFF_ABS(x, y) (x)>(y) ? (x)-(y) : (y)-(x);


이는 그나마 정의하기 쉬운 편이고, 함수의 내용이 복잡할 수록 매크로 함수로는 정의하기가 어려워진다.



2. 디버깅하기가 쉽지 않다.

컴파일러는 선행처리된 이후의 내용(결과물)을 기준으로 오류를 뱉는다. 

따라서 프로그래머 입장에서 오류를 찾기 힘들게 되고 더 신경을 써야할 부분이 생기게 된다.


1
2
3
4
5
6
7
8
9
#include <stdio.h>
#define  DIFF_ABS(X, Y)     ( (x)>(y) ? (x)-(y) : (y)-(x) )

int main(void)
{
	printf("두 값의 차: %d \n", DIFF_ABS(5, 7));
	printf("두 값의 차: %g \n", DIFF_ABS(1.8, -1.4));
	return 0;
}

선언되지 않은 식별자 오류 (매크로 함수의 X, Y 대소문자 구분이 틀림)

Output:

1
2
3
4
5
In function 'main':
Line 6: error: 'x' undeclared (first use in this function)
Line 6: error: (Each undeclared identifier is reported only once
Line 6: error: for each function it appears in.)
Line 6: error: 'y' undeclared (first use in this function)



어떠한 함수를 매크로로 정의하는 것이 좋은가?


작은 크기의 함수

한 두줄 정도 크기의 작은 함수

if~else, for과 같이 실행 흐름을 컨트롤 하는 문장은 배제하는 것이 좋다.



호출 빈도수가 높은 함수

함수를 매크로로 정의하는 가장 큰 이유는 성능적 측면이 크다.

다만, 빈도수의 높은 기준이 어느정도인지는 프로그래머의 주관에 따라 다를 수 있다.


'Study > C' 카테고리의 다른 글

파일의 분할과 헤더파일의 디자인  (0) 2016.08.30
선행처리기와 매크로2  (0) 2016.08.30
파일위치 지시자 / 메모리 관리와 동적할당  (0) 2016.08.28
파일 입출력 공부  (0) 2016.08.25
구조체 공부  (0) 2016.08.23
728x90

파일 위치 지시자

FILE 구조체의 멤버 중 하나

Read/Write에 대한 위치 정보를 갖고 있다. (어디까지 읽었는지 또는 어디부터 이어서 쓸건지)


파일 입출력 함수가 호출될때 

파일 위치 지시자의 참조 위치가 달라짐



파일 위치 지시자의 이동 fseek


#include <stdio.h>

int fseek(FILE * stream, long offset, int wherefrom);

// 성공시 0, 실패시 0이 아닌 값 반환


어디서부터(wherefrom) 

몇칸 오른쪽(+) 또는 왼쪽(-)으로 이동할건지에 대한 인자(offset)를 전달받음


SEEK_SET  - 파일 맨앞에서부터 이동시작

SEEK_CUR - current : 현재 위치에서부터 이동시작


SEEK_END - 파일 맨끝에서부터 이동시작

  주의 할점은 END는 EOF를 뜻하는 것



현재 파일 위치 지사자의 위치를 보여주는 함수 ftell


#include <stdio.h>

long ftell(FILE * stream);

// 파일 위치 지시자의 위치 정보 반환, 오류시 -1 반환


읽기/쓰기 위치는 0부터 시작


#include <stdio.h>

int main(void)
{
	/* 파일생성 */
	FILE * fp = fopen("text.txt", "wt");
	fputs("123456789", fp);
	fclose(fp);

	/* 파일개방 */
	fp = fopen("text.txt", "rt");

	/* SEEK_END test */
	printf("\n현재위치 : %d\n", ftell(fp) + 1); // 읽기/쓰기 위치는 0부터 시작이지만 시작점을 1로 가정한다.
	fseek(fp, -2, SEEK_END);    // END는 EOF임에 주의!
	putchar(fgetc(fp));    // 8을 읽고난뒤 다음 문자를 가리킴!
	printf("\n현재위치 : %d\n", ftell(fp) + 1); 

	/* SEEK_SET test */
	fseek(fp, 2, SEEK_SET);
	putchar(fgetc(fp));    // 3을 읽고난뒤 다음 문자를 가리킴!
	printf("\n현재위치 : %d\n", ftell(fp) + 1); 

	/* SEEK_CUR test */
	fseek(fp, 2, SEEK_CUR);    // 4에서 오른쪽 2칸 이동
	putchar(fgetc(fp));    // 6이 출력


	fclose(fp);
	return 0;
}

Output : 

현재위치 : 1

8

현재위치 : 9

3

현재위치 : 4

6



ftell을 이용한 위치정보 저장, 활용예제


#include <stdio.h>

int main(void)
{
	long fpos;
	int i;

	/* 파일생성 */
	FILE * fp=fopen("text.txt", "wt");
	fputs("1234-", fp);
	fclose(fp);

	/* 파일개방 */
	fp=fopen("text.txt", "rt");

	for(i=0; i<4; i++)
	{
		putchar(fgetc(fp));    // 문자 하나 읽어 출력후 지시자는 다음 바이트를 가리킴    
		fpos=ftell(fp);    // 현재위치 정보를 fpos 변수에 담음
		fseek(fp, -1, SEEK_END);    // - 로 위치 이동
		putchar(fgetc(fp));
		fseek(fp, fpos, SEEK_SET);    // fpos에 담긴 위치로 이동
	}
	fclose(fp);
	return 0;
}




메모리의 구성


유사한 성향의 데이터를 묶어 관리할 수있도록 다음과 같이 메모리 공간이 나뉘어진다.

code / data / heap / stack


층층이 나눠진 서랍장 수납공간과 같이 

효율적인 관리뿐만 아니라 메모리 접근 속도도 향상되게 된다.


 

메모리의 stack에는 일반적으로 값이 밑에서부터 위로 만들어진다.


넣어진 종이컵을 꺼내려면 나중에 넣은 종이컵부터 꺼내게 되는 것과 같다.


함수의 호출순서가 main->fct1->fct2라면

stack의 반환은 역순으로 이루어진다. (fct->fct1->main)


메인함수 종료시 스택영역 소멸

프로그램 종료시 데이터 영역에 할당된 것도 소멸


지역변수 - 함수를 빠져나갈때 할당된 메모리가 소멸


#include <stdio.h>

char * ReadUserName(void)
{
	char name[30];
	printf("What's your name? ");
	fgets(name, sizeof(name), stdin);
	return name;
}

int main(void)
{
	char * name1;
	char * name2;
	name1=ReadUserName();
	printf("name1: %s \n", name1);
	name2=ReadUserName();
	printf("name2: %s \n", name2);
	return 0;
}

컴파일시 warning메시지 출력

"warning C4172: 지역 변수 또는 임시: name의 주소를 반환하고 있습니다."

또한 쓰레기값이 출력되게 된다.


위 예제에서 ReadUserName 함수가 호출되어 할당된 메모리 공간은, 함수를 빠져나간후 반환과 동시에 메모리에서 소멸된다.

따라서 name 지역변수는 시간이 지나면 다른 데이터에 의해 덮어질, 의미를 갖지 않는 데이터가 된다.



전역변수 - 값을 덮어써버림

#include <stdio.h>

char name[30];

char * ReadUserName(void)
{
	printf("What's your name? ");
	gets(name);
	return name;
}

int main(void)
{
	char * name1;
	char * name2;
	name1=ReadUserName();
	printf("name1: %s \n", name1);
	name2=ReadUserName();
	printf("name2: %s \n", name2);

	printf("name1: %s \n", name1);
	printf("name2: %s \n", name2);
	return 0;
}

Output : 

What's your name? 길동

name1: 길동

What's your name? 철수

name2: 철수

name1: 철수

name2: 철수


name1이든 name2든 전역변수 name을 똑같이 가리키기 때문에 값을 덮어써버리게 된다.

이와 같이 지역변수로도 전역변수로도 풀지 못하는 상황을 해결하기 위한 것이 바로 malloc 함수이다.



malloc - Heap영역에 메모리 공간 할당을 위한 함수

#include <stdlib.h> // 또는 <malloc.h>

void * malloc(size_t size); // heap 영역에 생성하고자하는 바이트수만큼 메모리 공간 할당, 첫번째 메모리 주소 반환


heap 영역 : 프로그래머가 원하는 시점에 메모리 공간에 할당 및 소멸을 하기 위한 영역


malloc 함수는 인자로 숫자만 하나 전달받기 때문에

메모리의 포인터 형을 결정짓지 못한다.


void형은 type이 없기에 어떤 값이든 받을 수 있지만, void * 형 변수에 대한 포인터 연산을 할 수 없다.


따라서 다음과 같이 형변환을 거치는 호출형태를 취한다.

int * ptr1 = (int * )malloc(sizeof(int)*7); // 4byte 7개 확보

double ptr2 = (double *)malloc(sizeof(double)*9);


malloc함수는 메모리 할당 실패시 NULL을 반환

heap영역으로의 접근은 pointer를 통해서만 이루어진다.


free - Heap영역의 메모리 공간 해제

void free(void * ptr); // 할당된 메모리 공간 해제


free함수를 호출하지 않아도 프로그램 종료시엔 할당된 자원이 모두 반환된다.

하지만 실제 구현되는 프로그램은 오랜 시간 running되는 경우가 많으므로, heap 영역의 리소스 관리를 위해 free함수 사용을 습관화하는 것이 좋다.


#include <stdio.h> #include <stdlib.h> //malloc.h을 써도 되지만 일반적으로 stdlib.h을 include char * ReadUserName(void) { char * name = (char *)malloc(sizeof(char) * 30); //malloc 함수를 통해 heap에 할당 printf("니 이름이 뭐니? "); gets(name); return name; } int main(void) { char * name1; char * name2; name1 = ReadUserName(); printf("첫째 이름: %s \n", name1); name2 = ReadUserName(); printf("둘째 이름: %s \n", name2); printf("첫째 이름 재출력: %s \n", name1); // 소멸되지않았음을 확인하기 위함 printf("둘째 이름 재출력: %s \n", name2); free(name1); // malloc함수를 통해 할당한 메모리 공간 해제 free(name2); return 0; }

Output : 

니 이름이 뭐니? 홍길동

첫째 이름: 홍길동

니 이름이 뭐니? 홍수정

둘째 이름: 홍수정

첫째 이름 재출력: 홍길동

둘째 이름 재출력: 홍수정



calloc

#include <stdlib.h>

void * calloc(size_t elt_count, size_t elt_size);


malloc 함수와의 차이점은 elt_count x elt_size 크기만큼의 바이트를 동적할당한다는 것이다.

그리고 malloc 함수는 쓰레기값으로 초기화되는것과 달리 calloc는 모든 비트를 0으로 초기화한다.


realloc

#include <stdlib.h>

void * realloc(void * ptr, size_t size);


ptr이 가리키는 heap의 메모리 공간을 size_t size만큼 늘린다.


만약 malloc함수를 통해 기존에 할당된 메모리 공간에 이어서 확장할 여력이 되면 malloc과 realloc이 가리키는 주소는 같다.


기존의 malloc함수를 통해 할당되었던 메모리 공간을 확장할 여력이 안되면 새로운 메모리 공간을 할당하게 된다. 

이 때 malloc 함수를 통해 할당된 메모리를 복사하게 되며 기존의 메모리 공간은 지워진다.

따라서 realloc이 반환하는 주소값이 malloc 함수와는 달라지게 된다.


malloc과 같이 calloc,realloc도 free함수 호출을 통해 할당된 메모리 공간을 해제한다. (한번만 호출하면 된다)

'Study > C' 카테고리의 다른 글

선행처리기와 매크로2  (0) 2016.08.30
선행처리기와 매크로  (0) 2016.08.29
파일 입출력 공부  (0) 2016.08.25
구조체 공부  (0) 2016.08.23
c언어 복습 - 문자와 문자열 관련 함수  (0) 2016.08.22
728x90
입력 스트림과 출력 스트림의 형성을 요청하는 fopen 함수

#include <stdio.h>
FILE * fopen(const char * filename, const char * mode);

fopen함수를 통해 FILE 구조체 변수가 생성, 파일과의 스트림 형성
FILE 구조체 변수에 파일 정보가 담김 (파일 데이터가 담기는 것이 아님)

FILE * fp = fopen("data.txt", "wt");  // wt(write in text mode)모드로 파일 data.txt와 출력스트림을 형성
FILE * fp = fopen("C:\\Project\\data.txt", "wt");    // 경로 지정시 \ 자체를 표현하려면 \\ 두개 써야함

FILE * fp = fopen("data.txt", "rt");    // rt모드는 입력스트림을 형성

fputc('A', fp); // fp가 지칭하는 파일 data.txt에 문자 A 저장



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>

int main()
{
	FILE * fp = fopen("data.txt", "wt");	
	if (fp == NULL)
	{
		puts("File open failed");
		return -1;
	}

	fputc('A', fp);	//	출력의 대상을 파일로 지정.
	fputs("BC", fp);
	fclose(fp);

	return 0;
}






스트림의 소멸을 요청하는 fclose 함수


#include <stdio.h>

int fclose(FILE * stream);



fclose 함수가 가지는 의미 (써야하는 이유)


  • OS는 스트림 형성을 위해 시스템의 자원(주로 메모리)을 할당한다. 이 때 fclose함수를 통해 자원을 반환해준다.
  • 두번째로 fclose 함수를 통해 버퍼에 있던 데이터가 출력이 되게 된다. 출력버퍼에 데이터가 존재하는 상황에서 비정상 종료가 될 때 소멸될 수 있는 문제도 있을 수 있다. 이 때문에 버퍼의 데이터가 파일로 바로 저장되도록 fclose 함수를 호출해주는 것이 좋다.

출력 버퍼를 비우는 fflush 함수

앞에서 stdout을 대상으로 호출했듯이 출력버퍼를 대상으로 호출한다. (wt모드)

입력버퍼를 비우려면 데이터를 읽으면 된다. 따라서 입력버퍼를 비우는 함수는 따로 존재하지 않는다.




파일 입출력 예제 (파일을 생성해서 문자와 문자열 저장후 읽어들이기)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

#include <stdio.h> int main(void) { FILE * fp=fopen("simple.txt", "wt"); if(fp==NULL) { puts("파일오픈 실패!"); return -1; } fputc('A', fp); fputc('B', fp); fputs("My name is Hong \n", fp); fputs("Your name is Yoon \n", fp); fclose(fp); return 0; }

13행에서 \n을 넣지않으면 읽기를 했을 때 문자가 생길 수 있다. 문자열이 파일에 저장할 때는 null문자가 저장되지 않고, 때문에 파일에서는 개행을 기준으로 문자열을 구분하기 때문이다.


#include <stdio.h>

int main(void)
{
	char str[30];
	int ch;
	FILE * fp=fopen("simple.txt", "rt");
	if(fp==NULL) {
		puts("파일오픈 실패!");
		return -1;
	}

	ch=fgetc(fp);
	printf("%c \n", ch);
	ch=fgetc(fp);
	printf("%c \n", ch);

	fgets(str, sizeof(str), fp);
	printf("%s", str);
	fgets(str, sizeof(str), fp);
	printf("%s", str);

	fclose(fp);
	return 0;
}


읽어 들일 때는 write순서대로 read해야 저장된 값대로 읽어지는 것을 볼 수 있다. (저장된 값의 순서대로 fputc는 fgetc로 fputs는 fgets로 읽는다.)



feof 함수


#include <stdio.h>

int feof (FILE * stream);

파일의 끝(End Of File)에 도달한 경우 0이 아닌 값 반환 (읽어들일 데이터가 더 이상 없음을 의미)


파일 입력함수는 오류가 발생했을 때에도 EOF를 반환한다. 아래의 문자와 문자열 단위 파일복사 프로그램에서

파일에 끝에 도달한건지, 오류가 발생한건지 체크를 할 수 있다.



#include <stdio.h>

int main(void)
{
	FILE * src = fopen("src.txt", "rt");	// 미리 만들어진 src.txt 파일을 text mode로 읽음
	FILE * des = fopen("dst.txt", "wt");	// src.txt의 text를 dst.txt파일에 text mode로 씀
	int ch;

	if (src == NULL || des == NULL)
	{
		puts("파일 열기 실패!");
		return -1;	// -1은 오류를 의미
	}

	while ((ch=fgetc(src)) != EOF)
	{
		fputc(ch, des);
	}

	if (feof(src) != 0)
		puts("파일 복사완료!");	// 0이 아닌 값을 반환하면 EOF에 도달했음을 의미
	else
		puts("파일 복사실패..");

	fclose(src);
	fclose(des);

	return 0;
}

정상적으로 파일이 복사되면 파일 복사완료! 메시지와 함께 src.txt파일의 text가 dst.txt파일에 복사된 것을 볼 수 있다.


'Study > C' 카테고리의 다른 글

선행처리기와 매크로  (0) 2016.08.29
파일위치 지시자 / 메모리 관리와 동적할당  (0) 2016.08.28
구조체 공부  (0) 2016.08.23
c언어 복습 - 문자와 문자열 관련 함수  (0) 2016.08.22
c 복습-문제풀이2  (0) 2016.08.22

+ Recent posts