이 포스팅은 고돈호님의 이것이 안드로이드다 with 코틀린(한빛미디어)을 기반으로 작성되었습니다.

1.1 새 프로젝트 생성

[Create New Project] - [Empty Activity] - [NEXT] 새로운 프로젝트 생성


1.2 로그의 활용 

(d는 debug를 의미, 첫 번째 매개변수는 검색 용도로 사용되는 태그, 두 번째 매개변수는 출력할 메시지 입력)

1
Log.d("태그", "출력 메시지");
cs

MainActivity.kt을 다음과 같이 수정 단 import android.util.Log는 직접 import 해주기

package kr.co.kibeom.basicsyntax

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        Log.d("BasicSyntax", "로그를 출력합니다. method = Log.d")
    }
}

이를 실행시키면 다음과 같다.

 

MainAcivity.kt

이 후 안드로이드 스튜디오 하단의 [Logcat] 탭을 클릭해서 창을 연 후 Log.d의 태그로 입력했던 BasicSyntax를 검색하면 여러 로그 중 BasicSyntax에 해당하는 로그만 볼 수 있다.

Logcat에 BasicSyntax를 검색해서 본 모습


1.3 결론

따라서 우리는 다음의 정보들을 알 수 있다.

Log : 코딩 시 코드의 흐름을 파악하기 위해 앱 외부에 출력하는 정보. 

Logcat : 출력되는 로그를 모아서 보는 도구

 

여러 로그들은 다음과 같다. (외울 필요는 없다.)

함수 의미 내용
Log.v() verbose 상세한 로그 내용을 출력하기 위해 사용
Log.d() debug 개발에 필요한 내용을 출력하기 위해 사용
Log.i() information 정보성의 일반적인 메시지 전달하기 위해 사용
Log.w() warning 에러는 아나지만 경고성 메시지 전달하기 위해 사용
Log.e() error 실제 에러 메시지를 출력하기 위해 사용

 

 

'안드로이드 앱 개발' 카테고리의 다른 글

6. 함수  (0) 2021.12.20
5. 반복문  (0) 2021.12.19
4. 배열과 컬렉션  (0) 2021.12.14
3. 조건문  (0) 2021.12.12
2. 변수  (0) 2021.12.12
이 포스팅은 윤성우 님의 열혈 자료구조를 기반으로 합니다.

연결 리스트의 개념적인 이해

먼저 연결 리스트를 배우기에 앞서서 배열 기반 리스트를 살펴보면 배열은 길이의 변경이 불가능하다는 것을 알 수 있다.
따라서 이러한 문제점을 해결한 게 연결 리스트(Linked List) 이다. 먼저 다음의 구조체를 살펴보자.

1
2
3
4
typedef struct _node {
  int data; // 데이터를 담을 공간
  struct _node * next; // 연결의 도구
} Node;
cs

이 Node 구조체를 이용하여 필요할 때마다 하나씩 동적 할당하여 데이터를 저장하고 이들을 배열처럼 연결할 수 있는
연결 리스트가 되는 것이다. 여기에서 구조체 Node 변수를 노드라 하며 이를 그림으로 그리면 다음과 같다.

노드 하나의 표현
노드들의 연결 표현

먼저 ADT를 살펴보자. ADT는 다음과 같다.

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
void ListInit(List * plist); 
// 초기화할 리스트의 주소 값을 인자로 전달한다.
// 리스트 생성 후 제일 먼저 호출되어야 하는 함수이다.
 
void LInsert(List * plist, LData data);
// 리스트에 데이터를 저장한다. 매개변수 data에 전달된 값을 저장한다.
 
int LFirst(List * plist, LData * pdata);
// 첫 번째 데이터가 pdata가 가리키는 메모리에 저장된다.
// 데이터의 참조를 위한 초기화가 진행된다.
// 참조 성공 시 TRUE(1), 실패 시 FALSE(0) 반환
 
int LNext(List * plist, LData * pdata);
// 참조된 데이터의 다음 데이터가 pdata가 가리키는 메모리에 저장된다.
// 순차적인 참조를 위해서 반복 호출이 가능하다.
// 참조를 새로 시작하려면 먼저 LFirst함수를 호출해야 한다.
// 참조 성공 시 TRUE(1), 실패 시 FALSE(0) 반환
 
LData LRemove(List * plist);
// LFirst 또는 LNext 함수의 마지막 반환 데이터를 삭제한다.
// 삭제된 데이터는 반환된다.
// 마지막 반환 데이터를 삭제하므로 연이은 반복 호출을 허용하지 않는다.
 
int LCount(List * plist);
// 리스트에 저장되어 있는 데이터의 수를 반환한다.
 
void SetSortRule(List * plist, int (*comp)(LData d1, LData d2));
// 리스트에 정렬의 기준이 되는 함수를 등록한다.
cs

배열 리스트와 비교해보면 SetSortRule함수가 추가된 것을 볼 수 있으며 이는 Head에 추가할지 Tail에 추가할지 정하는 것이다. 장단점은 각각 다음과 같다.

Head 추가 방식과 Tail 추가 방식의 장단점 비교

또한 SetSortRule 함수의 매개변수를 보면 함수 포인터가 매개변수로 존재하는 것을 알 수 있는데 반환형이 int, LData형 2개를 전달받는 함수의 주소 값을 전달하는 것이다. 예를 들면 다음의 함수의 주소 값이 매개변수로 전달될 수 있다.

1
2
3
4
5
6
int WhoIsPrecede(LData d1, LData d2) { 
    if (d1 < d2) 
        return 0// d1이 정렬 순서가 앞서는 경우 
    else 
        return 1// d2가 정렬 순서가 앞서는 경우 
}
cs

먼저 다음의 형태의 기본적인 노드의 연결 상태를 보고 단점을 파악해보자.


이 경우 노드의 추가 및 삭제 또는 조회의 방법에서 첫 번째 노드와 두 번째 노드의 연결에 차이가 있다. 따라서 이를 방지하기 위해 헤드의 바로 다음에 dummy node를 넣어 어떤 위치에 있던 방법을 일관화 할 수 있다.

이제 연결 리스트를 구현해보자. 다음의 구조체를 기반으로 정의한다.

1
2
3
4
5
6
7
typedef struct _linkedList { 
    Node * head; // 더미 노드를 가리키는 멤버 
    Node * cur; // 참조 및 삭제를 돕는 멤버 
    Node * before; // 삭제를 돕는 멤버 
    int numOfData; // 저장된 데이터의 수를 기록하기 위한 멤버 
    int (*comp)(LDatat d1, LData d2); // 정렬의 기준을 등록하기 위한 멤버 
} LinkedList;
cs

헤더 파일은 다음과 같다.

<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

이를 기준으로 초기화와 삽입을 살펴보자. 초기화는 다음과 같다.

1
2
3
4
5
6
void ListInit(List * plist) {
    plist->head = (Node*)malloc(sizeof(Node));    // 더미 노드의 생성
    plist->head->next = NULL;
    plist->comp = NULL;
    plist->numOfData = 0;
}
cs

 

노드를 새로 추가하기 위한 함수는 다음과 같다.

1
2
3
4
5
6
void LInsert(List * plist, LData data) {
    if(plist->comp == NULL)        // 정렬 기준이 없다면
        FInsert(plist, data);   // 머리에 노드를 추가
    else                // 정렬 기준이 있다면
        SInsert(plist, data);   // 정렬 기준에 의하여 노드를 추가
}
cs

여기에서 볼 수 있는 것은 comp 즉 노드를 정렬하기 위한 기준이 마련되어있느냐에 따라 FInsert, SInsert로 나뉜다는 것이다. 그러나 이들은 내부적으로 정의된 함수이기에 사용자가 직접 호출할 수 없는 함수이다.

먼저 FInsert는 다음과 같다.

1
2
3
4
5
6
7
8
9
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)++;                    // 저장된 노드의 수를 하나 증가시킴
)
cs

그림으로 보면 다음과 같다.

데이터 조회 역시 배열 리스트의 그것과 비슷하지만 before과 Dummy node가 존재한다는 것에서 차이가 있다.
먼저 LFirst 함수를 보자.

1
2
3
4
5
6
7
8
9
int LFisrt(List * plist, LData * pdata) {
    if(plist->head->next == NULL)        // 더미 노드가 NULL을 가리킨다면
        return FALSE;
    plist->before = plist->head;        // before는 더미 노드를 가리키게 함
    plist->cur = plist->head->next;     // cur은 첫번째 노드를 가리키게 함
    
    *pdata = plist->cur->data;        // 첫 번째 노드의 데이터를 전달
    return TRUE;                // 데이터 반환 성공!
}
cs

 

역시 그림으로 나타내면 다음과 같다.

 

LNext 역시 보면 다음과 같다.

1
2
3
4
5
6
7
8
9
int LNext(List * plist, LData * pdata) {
    if(plist->cur->next == NULL)        // cur이 NULL을 가리킨다면
        return FALSE;
    plist->before = plist->cur;        // cur이 가리키던 것을 before가 가리킴
    plist->cur = plist->cur->next;         // cur은 그 다음 노드를 가리킴
    
    *pdata = plist->cur->data;        // cur이 가리키는 노드의 데이터 전달
    return TRUE;                // 데이터 반환 성공!
}
cs

 

역시 그림으로 나타내면 다음과 같다.

 

노드의 삭제 역시 배열 기반 리스트의 삭제와 비슷하다.

1
2
3
4
5
6
7
8
9
10
11
LData LRemove(List * plist) {
    Node * rpos = plist->cur;        // 소멸 대상의 주소 값을 rpos에 저장
    LData rdata = rpos->data;        // 소멸 대상의 데이터를 rdata에 저장
    
    plist->before->next = plist->cur->next;    // 소멸 대상을 리스트에서 제거
    plist->cur = plist->before;        // cur이 가리키는 위치를 재조정!
    
    free(rpos);                // 리스트에서 제거된 노드 소멸
    (plist->numOfData)--;            // 저장된 데이터의 수 하나 감소
    return rdata;                // 제거된 노드의 데이터 반환
}
cs

 

 

전체의 코드는 각각 다음과 같다.
<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

<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
96
#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->next == NULL)
        return FALSE;
 
    plist->before = plist->cur;
    plist->cur = plist->cur->next;
 
    *pdata = plist->cur->data;
    return TRUE;
}
 
LData LRemove(List * plist)
{
    Node * rpos = plist->cur;
    LData rdata = rpos->data;
 
    plist->before->next = plist->cur->next;
    plist->cur = plist->before;
 
    free(rpos);
    (plist->numOfData)--;
    return rdata;
}
 
int LCount(List * plist)
{
    return plist->numOfData;
}
 
void SetSortRule(List * plist, int (*comp)(LData d1, LData d2))
{
    plist->comp = comp;
}
cs

<DLinkedListMain.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
#include <stdio.h>
#include "DLinkedList.h"
 
int main(void)
{
    // List의 생성 및 초기화 /////////////////////////////
    List list;
    int data;
    ListInit(&list);
 
    // 5개의 데이터 저장 /////////////////////////////
    LInsert(&list, 11);  LInsert(&list, 11);
    LInsert(&list, 22);  LInsert(&list, 22);
    LInsert(&list, 33);
 
    // 저장된 데이터의 전체 출력 /////////////////////////
    printf("현재 데이터의 수: %d \n", LCount(&list));
 
    if(LFirst(&list, &data))    // 첫 번째 데이터 조회
    {
        printf("%d ", data);
        
        while(LNext(&list, &data))    // 두 번째 이후의 데이터 조회
            printf("%d ", data);
    }
    printf("\n\n");
 
    // 숫자 22을 검색하여 모두 삭제 //////////////////////////
    if(LFirst(&list, &data))
    {
        if(data == 22)
            LRemove(&list);
        
        while(LNext(&list, &data))
        {
            if(data == 22)
                LRemove(&list);
        }
    }
 
    // 삭제 후 남아있는 데이터 전체 출력 ////////////////////////
    printf("현재 데이터의 수: %d \n", LCount(&list));
 
    if(LFirst(&list, &data))
    {
        printf("%d ", data);
        
        while(LNext(&list, &data))
            printf("%d ", data);
    }
    printf("\n\n");
    return 0;
}
cs

원형 연결 리스트 (Circular Linked List)

단순 연결 리스트에서 마지막 노드가 첫 번째 노드를 가리켜서 원의 형태로 연결이 된 리스트를 원형 연결 리스트 라고 한다. 이를 그림으로 나타내면 다음과 같다.

위의 그림에서 head나 tail에 새로운 노드 1을 추가한다고 가정하면 1 -> 2 -> 4 -> 6 -> 8 -> 1 .... 의 형태 혹은
2 -> 4 6 -> 8 -> 1 -> 2.... 즉 서로 같은 형태가 되므로 head, tail의 구분이 명확히 존재하지 않는다.
유일한 차이점은 tail이 가리키는 노드가 다르다는 것 이다. 따라서 이를 머리에 추가하는 함수, 꼬리에 추가하는 함수는 각각 다음과 같다.

void LInsert(List* plist, Data data) { // tail에 추가
	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; // 기존 노드의 tail이 새 노드를 가리키게 연결
		plist->tail = newNode; // tail이 새로 추가된 노드를 가르키게 함
	}
	(plist->numOfData)++;
}

void LInsertFront(List* plist, Data data) { // Head에 추가
	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; // 기존 노드의 tail이 새 노드를 가르키게 연결
	}
	(plist->numOfData)++;
}

이번에는 조회를 살펴보자. 조회의 경우도 Fisrt, Next즉 두가지의 경우에 대한 함수가 존재한다.
LFisrt의 경우 Head가 없기 때문에 tail이 가리키는 다음 노드를 Cur로 한 뒤 이를 반환한다. 그림으로 표현하면 다음과 같다.

int LFirst(List* plist, Data* pdata) {
	if (plist->tail == NULL) { // 비어있는 경우
		return FALSE;
	}
	plist->before = plist->tail; // before가 tail을 가리키게 한다.
	plist->cur = plist->tail->next; // cur이 tail의 다음을 가리키게 한다

	*pdata = plist->cur->data; 
	return TRUE;
}

LNext의 경우도 단순히 before가 cur을 가리키게 하고 cur에 cur이 가리키는 다음 노드를 가리키게 하면 된다.
그 후 cur이 가리키는 노드를 반환하면 된다.마찬가지로 그림으로 나타내면 다음과 같다.

int LNext(List* plist, Data* pdata) {
	if (plist->tail == NULL) { // 비어있는 경우
		return FALSE;
	}
	plist->before = plist->cur; // before가 cur을 가리키게 한다
	plist->cur = plist->cur->next; // cur이 cur의 다음을 가리키게 한다.

	*pdata = plist->cur->data;
	return TRUE;
}

이 두가지 함수 모두 리스트에 원소가 없을 경우에 대해서 처리가 필요한데 단순하게 ADT를 참조하여 노드가 존재하지 않으면 False를 반환하면 된다.


이번에는 노드의 삭제에 대해서 알아보자.
노드의 삭제의 경우 두가지의 경우가 존재하는데 삭제할 노드가 tail인 경우 또는 아닌경우 삭제할 노드가 tail인 경우
노드가 하나밖에 없는 경우 역시 추가로 고려를 해주어야 한다. 다음의 코드를 살펴보자.

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;
}

위에서 볼 수 있듯 삭제하려는 노드가 tail이 아닌 경우는 다음의 그림처럼 삭제를 진행하면 된다.

 그러나 삭제하려는 노드가 tail인 경우는 단순하게 tail을 하나 앞으로 이동시키면 된다.

단순히 plist->tail = plist->before;을 통해서 말이다.

전체의 코드는 각각 다음과 같다.
<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
36
#pragma once
#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
#include <stdio.h>
#include "CLinkedList.h"
 
void ListInit(List* plist) { // 초기화
    plist->tail = NULL;
    plist->cur = NULL;
    plist->before = NULL;
    plist->numOfData = 0;
}
 
void LInsert(List* plist, Data data) { // tail에 추가
    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; // 기존 노드의 tail이 새 노드를 가리키게 연결
        plist->tail = newNode; // tail이 새로 추가된 노드를 가르키게 함
    }
    (plist->numOfData)++;
}
 
void LInsertFront(List* plist, Data data) { // Head에 추가
    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; // 기존 노드의 tail이 새 노드를 가르키게 연결
    }
    (plist->numOfData)++;
}
 
int LFirst(List* plist, Data* pdata) {
    if (plist->tail == NULL) { // 비어있는 경우
        return FALSE;
    }
    plist->before = plist->tail; // before가 tail을 가리키게 한다.
    plist->cur = plist->tail->next; // cur이 tail의 다음을 가리키게 한다
 
    *pdata = plist->cur->data; 
    return TRUE;
}
 
int LNext(List* plist, Data* pdata) {
    if (plist->tail == NULL) { // 비어있는 경우
        return FALSE;
    }
    plist->before = plist->cur; // before가 cur을 가리키게 한다
    plist->cur = plist->cur->next; // cur이 cur의 다음을 가리키게 한다.
 
    *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

양방향 연결 리스트

양방향 연결 리스트의 경우 앞서서 구현했던 단순 연결 리스트에서 좌측 혹은 우측을 연결하는 포인터 변수의 존재가 큰 차이점 이다. 그렇다면 헤더파일과 그림을 보고 양방향 연결 리스트를 구현해보자. 헤더파일은 다음과 같다.

#ifndef __DB_LINKED_LIST_H__
#define __DB_LINKED_LIST_H__

#define TRUE	1
#define FALSE	0

typedef int Data;

typedef struct _node
{
	Data data;
	struct _node * next;
	struct _node * prev;
} Node;

typedef struct _dbLinkedList
{
	Node * head;
	Node * cur;
	int numOfData;
} DBLinkedList;

typedef DBLinkedList List;

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

int LFirst(List * plist, Data * pdata);
int LNext(List * plist, Data * pdata);
int LPrevious(List * plist, Data * pdata);

int LCount(List * plist);

#endif

리스트의 초기화는 각각의 포인터 변수를 NULL로 초기화 하면 된다. 다음과 같다.

void ListInit(List* plist) {
	plist->head = NULL;
	plist->cur = NULL;
	plist->numOfData = 0;
}

삽입의 경우 2가지의 경우가 존재하는데 각각 리스트가 비어있을때, 비어있지 않을 때 이다.
이를 코드로 작성하면 다음과 같다.

void LInsert(List* plist, Data data) {
	Node* newNode = (Node*)malloc(sizeof(Node));
	newNode->data = data;

	newNode->next = plist->head;
	newNode->prev = NULL;
	plist->head = newNode;

	(plist->numOfData)++;
}

데이터의 조회의 경우 양방향 연결 리스트의 특성으로 좌측, 우측을 모두 조회할 수 있다.
이를 함수료 표현하면 각각 LFisrt, LNext, LPrevious 이며 다음과 같다.

int LFirst(List* plist, Data* pdata) {
	if (plist->head == NULL)
		return FALSE;

	plist->cur = plist->head;
	*pdata = plist->head->data;
	return TRUE;
}

int LNext(List* plist, Data* pdata) {
	if (plist->head == NULL)
		return FALSE;

	plist->cur = plist->cur->next;
	*pdata = plist->cur->data;
	return TRUE;
}

int LPrevious(List* plist, Data* pdata) {
	if (plist->head == NULL)
		return FALSE;

	plist->cur = plist->cur->prev;
	*pdata = plist->cur->data;
	return TRUE;
}
 

전체의 코드는 각각 다음과 같다.
<DBLinkedList.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
#ifndef __DB_LINKED_LIST_H__
#define __DB_LINKED_LIST_H__
 
#define TRUE    1
#define FALSE    0
 
typedef int Data;
 
typedef struct _node
{
    Data data;
    struct _node * next;
    struct _node * prev;
} Node;
 
typedef struct _dbLinkedList
{
    Node * head;
    Node * cur;
    int numOfData;
} DBLinkedList;
 
typedef DBLinkedList List;
 
void ListInit(List * plist);
void LInsert(List * plist, Data data);
 
int LFirst(List * plist, Data * pdata);
int LNext(List * plist, Data * pdata);
int LPrevious(List * plist, Data * pdata);
 
int LCount(List * plist);
 
#endif
cs


<DBLinkedList.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
#include <stdio.h>
#include "DBLinkedList.h"
 
void ListInit(List* plist) {
    plist->head = NULL;
    plist->cur = NULL;
    plist->numOfData = 0;
}
 
void LInsert(List* plist, Data data) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
 
    newNode->next = plist->head;
    newNode->prev = NULL;
    plist->head = newNode;
 
    (plist->numOfData)++;
}
 
int LFirst(List* plist, Data* pdata) {
    if (plist->head == NULL)
        return FALSE;
 
    plist->cur = plist->head;
    *pdata = plist->head->data;
    return TRUE;
}
 
int LNext(List* plist, Data* pdata) {
    if (plist->head == NULL)
        return FALSE;
 
    plist->cur = plist->cur->next;
    *pdata = plist->cur->data;
    return TRUE;
}
 
int LPrevious(List* plist, Data* pdata) {
    if (plist->head == NULL)
        return FALSE;
 
    plist->cur = plist->cur->prev;
    *pdata = plist->cur->data;
    return TRUE;
}
 
int LCount(List* plist) {
    return plist->numOfData;
}
cs


<DBLinkedListMain.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
#include <stdio.h>
#include "DBLinkedList.h"
 
int main(void)
{
    // 양방향 연결 리스트의 생성 및 초기화  ///////
    List list;
    int data;
    ListInit(&list);
 
    // 8개의 데이터 저장  ///////
    LInsert(&list, 1);  LInsert(&list, 2);
    LInsert(&list, 3);  LInsert(&list, 4);
    LInsert(&list, 5);  LInsert(&list, 6);
    LInsert(&list, 7);  LInsert(&list, 8);
 
    // 저장된 데이터의 조회  ///////
    if(LFirst(&list, &data))
    {
        printf("%d ", data);
 
        while(LNext(&list, &data)) 
            printf("%d ", data);
        
        while(LPrevious(&list, &data))
            printf("%d ", data);
        
        printf("\n\n");
    }
 
    return 0;
}
cs

'자료구조' 카테고리의 다른 글

5. 스택  (0) 2022.01.10
3. 배열 기반 리스트  (0) 2021.07.19
2. 재귀 (Recursion)  (0) 2021.06.27
1. 자료구조와 알고리즘의 이해  (0) 2021.06.27
이 포스팅은 윤성우 님의 열혈 자료구조를 기반으로 합니다.

추상 자료형: Abstract Data Type

추상 자료형은 간단히 ADT라고 불리며 기능만 언급하며 기능의 과정은 포함하지 않는다.
이번에 배울 배열기반 리스트 자료구조의 ADT는 다음과 같다.

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
void ListInit(List * plist); 
// 초기화할 리스트의 주소 값을 인자로 전달한다.
// 리스트 생성 후 제일 먼저 호출되어야 하는 함수이다.
 
void LInsert(List * plist, LData data);
// 리스트에 데이터를 저장한다. 매개변수 data에 전달된 값을 저장한다.
 
int LFirst(List * plist, LData * pdata);
// 첫 번째 데이터가 pdata가 가리키는 메모리에 저장된다.
// 데이터의 참조를 위한 초기화가 진행된다.
// 참조 성공 시 TRUE(1), 실패 시 FALSE(0) 반환
 
int LNext(List * plist, LData * pdata);
// 참조된 데이터의 다음 데이터가 pdata가 가리키는 메모리에 저장된다.
// 순차적인 참조를 위해서 반복 호출이 가능하다.
// 참조를 새로 시작하려면 먼저 LFirst함수를 호출해야 한다.
// 참조 성공 시 TRUE(1), 실패 시 FALSE(0) 반환
 
LData LRemove(List * plist);
// LFirst 또는 LNext 함수의 마지막 반환 데이터를 삭제한다.
// 삭제된 데이터는 반환된다.
// 마지막 반환 데이터를 삭제하므로 연이은 반복 호출을 허용하지 않는다.
 
int LCount(List * plist);
// 리스트에 저장되어 있는 데이터의 수를 반환한다.
cs

배열을 이용한 리스트의 구현

리스트에는 크게 두가지의 종류가 있고 이는 다음과 같다.

  • 순차 리스트
  • 연결 리스트

리스트 자료구조는 데이터를 나란히 저장한다는 점과 중복 데이터의 저장을 허용한다는 큰 특성이 있다.
이제 위의 ADT를 기반으로 main함수를 작성하면 다음과 같다.

<ListMain.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
#include <stdio.h>
#include "ArrayList.h"
 
int main(void)
{
    /*** ArrayList의 생성 및 초기화 ***/
    List list;
    int data;
    ListInit(&list);
 
    /*** 5개의 데이터 저장 ***/
    LInsert(&list, 11);  LInsert(&list, 11);
    LInsert(&list, 22);  LInsert(&list, 22);
    LInsert(&list, 33);
 
    /*** 저장된 데이터의 전체 출력 ***/
    printf("현재 데이터의 수: %d \n", LCount(&list));
 
    if (LFirst(&list, &data))    // 첫 번째 데이터 조회
    {
        printf("%d ", data);
 
        while (LNext(&list, &data))    // 두 번째 이후의 데이터 조회
            printf("%d ", data);
    }
    printf("\n\n");
 
    /*** 숫자 22을 탐색하여 모두 삭제 ***/
    if (LFirst(&list, &data))
    {
        if (data == 22)
            LRemove(&list);
 
        while (LNext(&list, &data))
        {
            if (data == 22)
                LRemove(&list);
        }
    }
 
    /*** 삭제 후 저장된 데이터 전체 출력 ***/
    printf("현재 데이터의 수: %d \n", LCount(&list));
 
    if (LFirst(&list, &data))
    {
        printf("%d ", data);
 
        while (LNext(&list, &data))
            printf("%d ", data);
    }
    printf("\n\n");
    return 0;
}
cs

위의 ListMain.c를 기반으로 하는 ArrayList.h, ArrayList.c도 다음과 같다.
<ArrayList.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
#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
cs

<ArrayList.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 "ArrayList.h"
 
void ListInit(List* plist)
{
        (plist->numOfData) = 0;
        (plist->curPosition) = -1;
}
 
void LInsert(List* plist, LData data)
{
        if (plist->numOfData > LIST_LEN)
        {
                puts("저장이 불가능합니다.");
                return;
        }
 
        plist->arr[plist->numOfData] = data;
        (plist->numOfData)++;
}
 
int LFirst(List* plist, LData* pdata)
{
        if (plist->numOfData == 0)
                return FALSE;
 
        (plist->curPosition) = 0;
        *pdata = plist->arr[0];
        return TRUE;
}
 
int LNext(List* plist, LData* pdata)
{
        if (plist->curPosition >= (plist->numOfData) - 1)
                return FALSE;
 
        (plist->curPosition)++;
        *pdata = plist->arr[plist->curPosition];
        return TRUE;
}
 
LData LRemove(List* plist)
{
        int rpos = plist->curPosition;
        int num = plist->numOfData;
        int i;
        LData rdata = plist->arr[rpos];
 
        for (i = rpos; i < num - 1; i++)
                plist->arr[i] = plist->arr[i + 1];
 
        (plist->numOfData)--;
        (plist->curPosition)--;
        return rdata;
}
 
int LCount(List* plist)
{
        return plist->numOfData;
}
cs

위의 코드들을 자세히 살펴보면 배열 기반 리스트의 삭제가 복잡한 것을 알 수 있다.


먼저 배열 리스트의 데이터 삭제를 살펴보자
배열의 특성상, 그리고 리스트의 특성상 데이터가 나란히 존재해야 하므로 다음의 그림처럼 되는 것을 확인할 수 있다.

또한 가장 최근에 참조가 이루어진 데이터의 인덱스 정보를 담는 변수 curPosition 역시 참조하던 데이터가 삭제되면
앞의 데이터를 참조해야 한다. 이는 다음의 그림과 같다.


실제로 리스트에는 예시의 정수 이외에 다른 자료들도 들어간다. 이번에는 그렇다면 구조체 변수의 주소 값을 저장하여 보자 구조체는 다음과 같다.

1
2
3
4
typedef struct _point {
    int xpos; // x좌표
    int ypos; // y좌표
} Point;
cs

<Point.h>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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 ShowPosition(Point* ppos);
 
// 두 Point 변수의 비교
int PointComp(Point* pos1, Point* pos2);
 
#endif
cs

<Point.c>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include "Point.h"
 
void SetPointPos(Point* ppos, int xpos, int ypos) {
    ppos->xpos = xpos;
    ppos->ypos = ypos;
}
 
void ShowPosition(Point* ppos) {
    printf("[%d, %d] \n", ppos->xpos, ppos->ypos);
}
 
int PointComp(Point* pos1, Point* pos2) {
    if (pos1->xpos == pos2->xpos && pos1->ypos == pos2->ypos)
        return 0;
    else if (pos1->xpos == pos2->xpos)
        return 1;
    else if (pos1->ypos == pos2->ypos)
        return 2;
    else
        return -1;
}
cs

이 후 ArrayList.h, ArrayList.c를 기반으로 위의 Point 구조체를 저장할 수 있도록 하면 이 과정에서
헤더 파일은 변경이 되어도 되지만 소스파일은 변경이 되면 안 된다.
헤더 파일에서 달라진 점은 typedef int LData; 에서 typedef Point * LData; 으로 변경되었다.
또한 ArrayList.h의 선언문에 #include "Point.h" 를 추가한다. 이제 main함수는 다음과 같다.
<PointListMain.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
#include <stdio.h>
#include <stdlib.h>
#include "ArrayList.h"
#include "Point.h"
 
int main(void) {
    List list;
    Point comPos;
    Point* ppos;
 
    ListInit(&list);
 
    // 4개의 데이터 저장
    ppos = (Point*)malloc(sizeof(Point));
    SetPointPos(ppos, 21);
    LInsert(&list, ppos);
 
    ppos = (Point*)malloc(sizeof(Point));
    SetPointPos(ppos, 22);
    LInsert(&list, ppos);
 
    ppos = (Point*)malloc(sizeof(Point));
    SetPointPos(ppos, 31);
    LInsert(&list, ppos);
 
    ppos = (Point*)malloc(sizeof(Point));
    SetPointPos(ppos, 32);
    LInsert(&list, ppos);
 
    // 저장된 데이터의 출력
    printf("현재 데이터의 수는 : %d \n", LCount(&list));
 
    if (LFirst(&list, &ppos)) {
        ShowPosition(ppos);
 
        while (LNext(&list, &ppos)) {
            ShowPosition(ppos);
        }
    }
    printf("\n");
 
    // xpos가 2인 모든 데이터 삭제
    comPos.xpos = 2;
    comPos.ypos = 0;
 
    if (LFirst(&list, &ppos)) {
        if (PointComp(ppos, &comPos) == 1) {
            ppos = LRemove(&list);
            free(ppos);
        }
 
        while (LNext(&list, &ppos)) {
            if (PointComp(ppos, &comPos) == 1) {
                ppos = LRemove(&list);
                free(ppos);
            }
        }
    }
 
    // 삭제 후 남은 데이터 전체 출력
    printf("현재 데이터의 수는 : %d \n", LCount(&list));
 
    if (LFirst(&list, &ppos)) {
        ShowPosition(ppos);
 
        while (LNext(&list, &ppos)) {
            ShowPosition(ppos);
        }
    }
    printf("\n");
 
    return 0;
}
cs

이제 정리해보자.

  • 배열 기반 리스트의 단점
    • 배열의 길이가 초기에 결정되어야 한다. 또한 변경이 불가능하다.
    • 삭제의 과정에서 데이터의 이동이 일어난다. (마지막 경우가 아닌 경우)
  • 배열 기반 리스트의 장점
    • 데이터의 참조가 쉽다. 인덱스를 기준으로 한 번에 참조가 가능하다.

'자료구조' 카테고리의 다른 글

5. 스택  (0) 2022.01.10
4. 연결 리스트  (0) 2021.07.19
2. 재귀 (Recursion)  (0) 2021.06.27
1. 자료구조와 알고리즘의 이해  (0) 2021.06.27

이 포스팅은 윤성우 님의 열혈 C를 기반으로 합니다.

C언어의 메모리

메모리는 OS에 의해 다음과 같이 4개의 영역으로 구분된다.

1. 코드 영역

2. 데이터 영역

3. 힙 영역

4. 스택 영역

1. 코드 영역 : 말 그대로 실행할 프로그램의 코드가 저장되는 공간이다. 

2. 데이터 영역 : 전역 변수, static 변수가 할당되는 공간으로 프로그램의 시작 시 메모리 공간에 할당되어 프로그램 종료 시까지 남아있게 된다.

3. 힙 영역 : 프로그래머가 원하는 시점에 변수를 할당하고 또 소멸하도록 지원을 하는 변수들이 할당되는 영역

4. 스택 영역 : 지역변수와 매개변수가 할당되는 공간 

그렇다면 이 3번 힙 영역에 대해서 자세히 알아보자.


메모리의 동적 할당

메모리 동적 할당을 배우기에 앞서서 다음의 코드를 확인해보자.

<ReadStringFault1.c>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
 
char* ReadUserName(void) {
    char name[30];
    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);
    return 0;
}
cs

이 경우 ReadUserName 함수 내부에 반환되는 name은 지역변수로 스택 영역에 설정되어 있다. 따라서 함수를 빠져나가면 소멸되므로 실행이 정상적으로 되지 않는다. 그렇다면 이를 전역 변수로 바꾸면 어떻게 될까?

<ReadStringFault2.c>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#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;
}
cs

<실행결과>

전역변수로 설정하면 위의 실행결과와 마찬가지로 name1, name2가 덮어쓰기 때문에 이름 정보가 각각 유지되지 않는다. 그렇다면 이것을 해결하기 위해서 어떻게 해야 할까? 바로 동적 할당인 malloc, free를 사용하면 된다.

malloc 즉 memory allocation 메모리 할당이며

free 즉 malloc를 통해 할당된 메모리를 소멸시켜 주는 것 이다.

그런데 malloc의 반환형을 보면 void *이다. 앞서서 void형 포인터의 경우 단순하게 받기만 할 수 있는 즉 연산은 할 수 없는 형인데 왜 void 일까? 다음의 예시를 보면 알 수 있다.

길이가 7인 int형 배열의 공간을 할당하기 위해 void * ptr = malloc(sizeof(int) * 7); 이라고 하면 사용자 입장에서는 알 수 있지만 malloc에게 전달되는 것은 malloc(28) 일 뿐이다. 따라서 malloc는 인자로 전달된 숫자만큼의 메모리 공간을 할당만 해줄 수 있고 이를 사용하기 위해 형 변환을 해야 하는 것은 사용자에게 책임을 돌린다.

따라서 int * ptr = (int *)malloc(sizeof(int)*7); 와 같은 형태로 malloc를 사용해야 한다. 예시를 살펴보자.

<DynamicMemoryAllocation.c>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <stdlib.h>
 
int main(void) {
    int* ptr = (int*)malloc(sizeof(int));
    int* ptr1 = (int*)malloc(sizeof(int)*7);
    
    *ptr = 20;
    for (int i = 0; i < 7; i++) {
        ptr1[i] = i + 1;
    }
 
    printf("%d \n"*ptr);
    for (int i = 0; i < 7; i++) {
        printf("%d ", ptr1[i]);
    }
 
    free(ptr);
    free(ptr1);
    return 0;
}
cs

<실행결과>

free 호출은 할당한 메모리를 소멸해주므로 꼭 사용해야 한다.

이제 앞서 살펴봤던 문제들을 해결해보자.

<ReadStringRight.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
#include <stdio.h>
#include <stdlib.h>
 
char* ReadUserName(void) {
    char* name = (char*)malloc(sizeof(char* 30);
    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("again name1 : %s \n", name1);
    printf("again name2 : %s \n", name2);
 
    free(name1);
    free(name2);
    return 0;
}
cs

<실행결과>

정상적으로 작동하는 모습을 볼 수 있다.


부가적으로 realloc 함수가 있다. 이는 malloc로 할당된 영역을 재설정할 수 있게 하는 것이다.

 

'C' 카테고리의 다른 글

12. 구조체와 사용자 정의 자료형 2  (0) 2021.06.27
11. 구조체와 사용자 정의 자료형  (0) 2021.06.27
10. 문자열 관련 함수  (0) 2021.06.27
9. 함수 포인터 & void 포인터  (0) 2021.06.26
8. 2차원 배열과 포인터  (0) 2021.06.26

이 포스팅은 윤성우님의 열혈 자료구조를 기반으로 합니다.

2-1 함수의 재귀적 호출의 이해

재귀 함수란 함수 내에서 자기 자신을 다시 호출하는 함수를 말하며 동작 과정은 다음과 같다.
먼저 다음의 재귀함수 코드를 보자.

1
2
3
4
void Recursive(void) { 
    printf("Recursive call! \n"); 
    Recursive(); 
}
cs

이를 그림으로 표현하면 다음과 같다.

Recursive 함수의 동작


Recursive 함수를 실행하는 중간에 다시 Recursive 함수가 호출되면, Recursive 함수의 복사본을 하나 더 만들어서 복사본을 실행하게 된다. 재귀 함수의 중요한 점은 바로 탈출 조건을 설정해야 한다는 것이다.
위의 코드의 경우 탈출 조건이 없으므로 무한 호출이 된다. 다음의 코드에서는 탈출 조건을 설정하여 3번 출력이 되는것을 확인할 수 있다.

<RecursiveFunc.c>

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
 
void Recursive(int num) {
    if (num <= 0// 탈출 조건
        return;
    printf("Recursive call! %d \n", num);
    Recursive(num - 1);
}
 
int main(void) {
    Recursive(3);
    return 0;
}
cs

<실행결과>

재귀함수의 예시로 팩토리얼 함수를 들 수 있다. 먼저 팩토리얼의 수식을 쓰면 다음과 같다.

이를 코드적인 관점에서 생각해보면 n이 1인 경우는 1을 반환하고 그 이외의 경우는 n-1을 반환하면 되는 것이다. 

코드로 작성하면 다음과 같다.

<RecursiveFactorial.c>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
 
int Factorial(int num) {
    if (num == 0// 탈출 조건
        return 1;
    return num * Factorial(num - 1);
}
 
int main(void) {
    printf("1! = %d\n", Factorial(1));
    printf("2! = %d\n", Factorial(2));
    printf("3! = %d\n", Factorial(3));
    printf("4! = %d\n", Factorial(4));
    printf("9! = %d\n", Factorial(9));
    return 0;
}
cs

<실행결과>


2-2 재귀의 활용

재귀의 또다른 예로 피보나치 함수가 존재한다. 피보나치의 경우 0 1 1 2 3 5 8 13 21....로 1번째와 2번째를 제외하고 전 수와 전전 수를 합하여 현재의 수를 만드는 것이다. 이를 코드 적인 관점에서 생각하면 n이 1인 경우는 0, 2인 경우는 1로 설정하고 그 외의 경우는 n-1번째 수+ n-2번째 수로 하면 된다. 코드로 작성하면 다음과 같다.

<FibonacciFunc.c>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
 
int Fibo(int num) {
    if (num == 1// 1번째인 경우 0 반환
        return 0;
    else if (num == 2// 2번째인 경우 1 반환
        return 1;
    else // 그 외의 경우
        return Fibo(num-1+ Fibo(num-2);
}
 
int main(void) {
    for (int i = 1; i <= 15; i++) { // 피보나치 수 15개 출력
        printf("%d ", Fibo(i));
    }
    return 0;
}
cs

<실행결과>

앞선 포스팅에서 다루었던 이진 탐색의 경우 반복문을 통해 작성하였다. 이 또한 방금 배운 재귀를 이용하여 작성할 수 있다. 반복문에서는 while(first <= last) 의 조건을 통해 구현하였으니 이를 탈출 조건으로 작성하면 된다.

소스코드로 나타내면 다음과 같다.

<RecursiveBinarySearch.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
#include <stdio.h>
 
int BSearchRecur(int arr[], int first, int last, int target) {
    int mid;
    if (first > last) // 탈출조건
        return -1;
    
    mid = (first + last) / 2;
    if (arr[mid] == target) // 찾은 경우 해당 위치의 인덱스 반환
        return mid;
    else if (arr[mid] > target) // mid의 값이 target보다 큰 경우 좌측에서 탐색해야함
        return BSearchRecur(arr, first, mid - 1, target);
    else // mid의 값이 target보다 작은 경우 우측에서 탐색해야함
        return BSearchRecur(arr, mid + 1, last, target);
}
 
int main(void) {
    int arr[] = { 13579 };
    int idx;
 
    idx = BSearchRecur(arr, 0sizeof(arr) / sizeof(int- 15);
    if (idx == -1)
        printf("탐색 실패 \n");
    else
        printf("타겟의 인덱스는 %d 입니다.\n", idx);
 
    idx = BSearchRecur(arr, 0sizeof(arr) / sizeof(int- 18);
    if (idx == -1)
        printf("탐색 실패 \n");
    else
        printf("타겟의 위치는 %d 입니다.\n", idx);
 
    return 0;
}
cs

<실행결과>

다만 이진탐색의 경우 중복으로 호출하는 구간이 존재하므로 코드의 시간 복잡도의 측면에서는 비효율적이다.


하노이 타워: The Tower of Hanoi

재귀함수의 또 다른 예시로 하노이 타워가 있다. 하노이 타워란 다음의 사이트에서 확인하길 바란다.

 

하노이의 탑 - 위키백과, 우리 모두의 백과사전

하노이의 탑(Tower of Hanoi)은 퍼즐의 일종이다. 세 개의 기둥과 이 기둥에 꽂을 수 있는 크기가 다양한 원판들이 있고, 퍼즐을 시작하기 전에는 한 기둥에 원판들이 작은 것이 위에 있도록 순서대

ko.wikipedia.org

어쨌든 우리가 알아야 할 것은 원반은 한 번에 하나만 옮길 수 있고 작은 원반 위에 큰 원반이 놓여서는 안 된다는 것이다. 이를 자세하게 살펴보면 하노이 역시 일련의 과정을 반복하므로 재귀를 통해 구현할 수 있다는 것을 알 수 있다.

간단하게만 알아보아도 될 예시이므로 바로 소스코드를 보인다.

<HanoiTowerSolu.c>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
 
void HanoiTowerMove(int num, char from, char by, char to) {
    if (num == 1)
        printf("원반 1을 %c에서 %c로 이동\n", from, to);
    else {
        HanoiTowerMove(num - 1, from, to, by); 
        printf("원반 %d를 %c에서 %c로 이동\n", num, from, to); 
        HanoiTowerMove(num - 1, by, from, to);
    }
}
 
int main(void) {
    HanoiTowerMove(3'A''B''C');
    return 0;
}
cs

<실행결과>

'자료구조' 카테고리의 다른 글

5. 스택  (0) 2022.01.10
4. 연결 리스트  (0) 2021.07.19
3. 배열 기반 리스트  (0) 2021.07.19
1. 자료구조와 알고리즘의 이해  (0) 2021.06.27

시간 복잡도(Time Complexity)와 공간 복잡도(Space Complexity)

시간 복잡도는 속도에 관한 것이며 공간 복잡도는 메모리 사용량에 관한 것이다. 시간 복잡도는 연산 횟수로 구한다.


데이터의 개수가 n이하는 알고리즘 A가 유리하고 n이상은 알고리즘 B가 유리한것을 확인할 수 있다.
따라서 상황에 맞게 적절한 알고리즘을 택해야 한다. 다음의 코드를 살펴보자.

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
#include <stdio.h>
 
int LSearch(int arr[], int len, int target) { // Linear Search 함수
    for (int i = 0; i < len; i++) {
        if (arr[i] == target) // 찾으면 해당 위치의 인덱스 반환
            return i;
    }
    return -1;
}
int main(void) {
    int arr[] = { 25319 };
    int idx;
 
    idx = LSearch(arr, sizeof(arr) / sizeof(int), 9);
    if (idx == -1
        printf("탐색 실패\n");
    else 
        printf("타겟 인덱스: %d \n", idx);
 
    idx = LSearch(arr, sizeof(arr) / sizeof(int), 6);
    if (idx == -1)
        printf("탐색 실패\n");
    else
        printf("타겟 인덱스: %d \n", idx);
 
    return 0;
}
cs

이 경우 최악의 시간복잡도는 O(N)이다. 최선, 평균, 최악이 있지만 최악을 기준으로 잡는다.
이번에는 이진 탐색(Binary Search)알고리즘을 보자. 순차 탐색에 비해 좋은 성능을 내지만 정렬이 되어있어야 한다는 제약 조건이 존재한다.

이진탐색 (Binary Search)

이진탐색을 먼저 그림으로 나타내면 다음과 같다.

3을 찾기 위해 반씩 줄여나가며 총 3회 탐색하는 모습


이진탐색의 코드는 다음과 같다.

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
#include <stdio.h>
 
int BSearch(int arr[], int len, int target) {
    int first = 0;
    int last = len - 1;
    int mid;
 
    while (first <= last) { // fist와 last가 뒤집어지면 종료
        mid = (first + last) / 2;
        if (arr[mid] == target)
            return mid;
        else if (arr[mid] > target) // 중간 값이 target보다 큰 경우
            last = mid - 1// mid 좌측에서 탐색 진행
        else // 중간 값이 target보다 작은 경우
            first = mid + 1;
    }
    return -1// 탐색하지 못한 경우
}
 
int main(void) {
    int arr[] = { 1357911 };
    int idx;
 
    idx = BSearch(arr, sizeof(arr) / sizeof(int), 7);
    if (idx == -1)
        printf("탐색 실패 \n");
    else
        printf("타겟 인덱스 위치: %d \n", idx);
 
    idx = BSearch(arr, sizeof(arr) / sizeof(int), 8);
    if (idx == -1)
        printf("탐색 실패 \n");
    else
        printf("타겟 인덱스 위치: %d \n", idx);
 
    return 0;
}
cs

이 경우 최악의 시간 복잡도는 O(logN)이다.

각 빅 - 오 표기법들의 성능 비교

각 빅-오 표기법들의 성능은 다음과 같다.


순서대로 O(1) < O(logN) < O(N) < O(NlogN) < O(𝑁^2) < O(2^N) < O(N!) 이다.

'자료구조' 카테고리의 다른 글

5. 스택  (0) 2022.01.10
4. 연결 리스트  (0) 2021.07.19
3. 배열 기반 리스트  (0) 2021.07.19
2. 재귀 (Recursion)  (0) 2021.06.27
typedef

typedef를 배우기 전에 다음의 코드를 살펴보자. 

<소스코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
 
struct point {
    int xpos;
    int ypos;
};
 
int main(void) {
    struct point pnt = { 23 };
    printf("[%d, %d]", pnt.xpos, pnt.ypos);
 
    return 0;
}
 
cs

9번 라인을 보면 struct point형 변수를 선언하기 위해 struct point pnt = {2, 3}; 으로 선언한 것을 볼 수 있다. 매번 struct를 사용하는 것은 불편하다. 그렇다면 int num = 10; 처럼 간단하게 할 수 있는 방법이 없을까? 있다. 바로 typedef를 사용하면 된다. 이를 다음의 소스코드와 이전의 소스코드를 비교하며 이해해보자.

<소스코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
 
typedef struct {
    int xpos;
    int ypos;
} Point;
 
int main(void) {
    Point pnt = { 23 };
    printf("[%d, %d]", pnt.xpos, pnt.ypos);
 
    return 0;
}
 
cs

두 코드의 실행결과는 모두 동일하며 3번 라인을 보면 struct 앞에 typedef 키워드가 있고 point라는 구조체 이름이 빠지고 구조체의 마지막에 Point라고 붙여진것을 볼 수 있다. 이를 통해 9번 라인에서 볼 수 있듯이 단순하게 Point pnt = {2, 3}; 으로 구조체 변수를 간단하게 생성하는 것을 볼 수 있다.


함수로의 구조체 변수 전달과 반환

기본 자료형과 마찬가지로 구조체 변수 역시 매개변수에 복사가 되거나 반환될 수 있다. 다음의 코드를 살펴보자.

<소스코드>

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
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
 
typedef struct {
    int xpos;
    int ypos;
} Point;
 
void showPosition(Point pnt) {
    printf("[%d, %d] \n", pnt.xpos, pnt.ypos);
}
 
Point GetCurrentPosition(void) {
    Point cen;
    printf("pos를 입력하시오 : ");
    scanf("%d %d"&cen.xpos, &cen.ypos);
    return cen;
}
 
int main(void) {
    Point curPos = GetCurrentPosition();
    showPosition(curPos);
 
    return 0;
}
 
cs

<실행결과>

라인 9번에서 볼 수 있듯이 매개변수로 구조체 변수가 들어갔고 21번 라인에서 볼 수 있듯 반환값을 Point 변수 curpos에 대입하는것을 볼 수 있다. 또한 매개변수로 구조체의 포인터 변수도 들어갈 수 있다. 이를 통해 Call by reference도 할 수 있다. 이를 예시 코드를 통해 알아보자.

<소스코드>

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
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
 
typedef struct {
    int xpos;
    int ypos;
} Point;
 
void OrgSynTrans(Point* pnt) {
    pnt->xpos = pnt->xpos * -1;
    pnt->ypos = pnt->ypos * -1;
}
 
void showPosition(Point pnt) {
    printf("[%d, %d] \n", pnt.xpos, pnt.ypos);
}
 
int main(void) {
    Point pos = { 7-5 };
    OrgSynTrans(&pos);
    showPosition(pos);
    OrgSynTrans(&pos);
    showPosition(pos);
    return 0;
}
 
cs

<실행결과>

9번 라인에서 볼 수 있듯이 함수의 매개변수로 구조체의 주소값을 넘겨서 원점 대칭하는것을 알 수 있다. 따라서 메인함수의 좌표값을 메인함수 외부에서 변경하는것을 볼 수 있다.

또한 구조체 변수 역시 구조체의 멤버로 포함될 수 있다. 이를 구조체의 중첩이라 한다. 다음의 소스코드를 확인하자.

<소스코드>

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
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
 
typedef struct {
    int xpos;
    int ypos;
} Point;
 
typedef struct {
    Point cen;
    double rad;
} Circle;
 
void showCircleInfo(Circle * c) {
    printf("[%d, %d] \n", (c->cen).xpos, (c->cen).ypos);
    printf("radius: %g \n\n", c->rad);
}
 
int main(void) {
    Circle c1 = { {12}, 3.5 }; // 구조체가 중첩 됨
    showCircleInfo(&c1);
 
    return 0;
}
 
cs

<실행결과>

20번 라인에서 볼 수 있듯이 구조체가 중첩된 것을 알 수 있다.


UNION

구조체와 공용체(union)의 차이

typedef struct {

    int mem1;

    int mem2;

    double mem3;

} Sbox;

 

typedef union {

    int mem1;

    int mem2;

    double mem3;

} Ubox;

위의 Sbox, Ubox의 sizeof 연산을 해보면 각각 16 byte, 8  byte 인것을 확인할 수 있다.  이를 그림으로 표현하면 다음과 같다.

이 union은 하나의 메모리 공간에 둘 이상의 방식으로 접근할 수 있다는 특성이 있다.

 


Enum 열거형

enum을 설명하기 전에 먼저 다음의 소스코드를 보자.

<소스코드>

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
#include <stdio.h>
 
typedef enum {
    Do = 1, Re = 2, Mi = 3, Fa = 4, Sol = 5, La = 6, Ti = 7
} Syllable;
 
void Sound(Syllable sy) {
    switch (sy) {
    case Do:
        puts("도 입니다.");
        return;
    case Re:
        puts("레 입니다.");
        return;
    case Mi:
        puts("미 입니다.");
        return;
    case Fa:
        puts("파 입니다");
        return;
    case Sol:
        puts("솔 입니다.");
        return;
    case La:
        puts("라 입니다.");
        return;
    case Ti:
        puts("시 입니다.");
        return;
    }
}
 
int main(void) {
    Syllable tone;
    for (tone = Do; tone <= Ti; tone++)
        Sound(tone);
    return 0;
}
 
cs

<실행결과>

열거형은 연관이 있는 이름을 동시에 상수로 선언할 수 있다는 장점이 존재한다.

 

이 포스팅은 윤성우님의 열혈 C를 기반으로 작성되었습니다.

'C' 카테고리의 다른 글

14. 메모리 및 동적할당  (0) 2021.06.28
11. 구조체와 사용자 정의 자료형  (0) 2021.06.27
10. 문자열 관련 함수  (0) 2021.06.27
9. 함수 포인터 & void 포인터  (0) 2021.06.26
8. 2차원 배열과 포인터  (0) 2021.06.26
구조체란?

구조체란 하나 이상의 변수를 묶어서 새로운 자료형을 정의하는 것이다. 예를 들자면 어떤 것의 좌표라고 가정해보자. x좌표 y좌표가 함께 존재해야만 의미가 있고 한 개씩 있으면 존재의 의미가 없는 것이다. 이 구조체를 정의해보자.

 

struct cord {

    int xPos;

    iny yPos;

                                                                      };

위와 같이 정의할 수 있다. 예를 들어 사람의 이름, 나이, 전화번호를 묶으면 다음과 같다

struct person {

    char name[20]; // 배열도 구조체의 멤버가 될 수 있다.

    char phoneNum[20];

    int age;

};

이제 이러한 구조체들을 이용하여 기본 자료형처럼 구조체 변수 선언을 할 수 있다. 다음을 보자.

 

struct type_name val_name;

이를 통해 위의 cord, person 구조체의 변수를 선언하면 다음과 같다. 또한 그림으로 표현하면 다음과 같다.

struct cord pos;

struct person man;

구조체 변수의 멤버에 접근하기 위해서는 다음과 같이 접근한다.

struct_var_name.struct_member_name;

예를 들면 pos.xPos; 이다. 이제 예시를 통해 두 점 사이의 거리를 구하는 예시 코드를 보자.

<소스코드>

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
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>
 
struct point {
    int xpos;
    int ypos;
};
 
int main(void) {
    struct point pos1, pos2; // 구조체 변수 선언
    double dist;
 
    fputs("point1 pos: ", stdout);
    scanf("%d %d"&pos1.xpos, &pos1.ypos); // 구조체 변수 pos1의 멤버 xpos, ypos에 접근 
 
    fputs("point2 pos: ", stdout);
    scanf("%d %d"&pos2.xpos, &pos2.ypos); // 구조체 변수 pos2의 멤버 xpos, ypos에 접근 
 
    // 두 점 사이 거리 계산
    dist = sqrt((double)((pos1.xpos - pos2.xpos) * (pos1.xpos - pos2.xpos) + 
        (pos1.ypos - pos2.ypos) * (pos1.ypos - pos2.ypos)));
 
    printf("두 점의 거리는 %g 입니다. \n", dist);
 
    return 0;
}
 
cs

<실행결과>

다음의 방식으로 구조체 변수를 선언과 동시에 초기화할 수 있다.

<소스코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
 
struct point {
    int xpos;
    int ypos;
};
 
struct person {
    char name[20];
    char phoneNum[20];
    int age;
};
 
int main(void) {
    struct point pos = { 1020 };
    struct person man = { "홍길동""010-0000-0000"21 };
    printf("%d %d \n", pos.xpos, pos.ypos);
    printf("%s %s %d\n", man.name, man.phoneNum, man.age);
    return 0;
}
 
cs

<실행결과>


구조체와 배열 & 포인터

int arr[]; 처럼 int형 배열을 선언할 수 있듯이 구조체 역시 배열을 설정할 수 있다. 만약 struct point가 있다고 가정하면

point형 변수는 struct point pos; 이며 이 point형 배열은 struct point arr[3]; 이다. 이를 그림으로 표현하면 다음과 같다.

소스코드를 통해 알아보자.

<소스코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
 
struct point {
    int xpos;
    int ypos;
};
 
int main(void) {
    struct point arr[3];
    for (int i = 0; i < 3; i++) {
        printf("점의 좌표 입력 : ");
        scanf("%d %d"&arr[i].xpos, &arr[i].ypos);
    }
 
    for (int i = 0; i < 3; i++) {
        printf("[%d, %d] ", arr[i].xpos, arr[i].ypos);
    }
 
    return 0;
}
 
cs

<실행결과>

또한 구조체 배열은 다음과 같이도 초기화가 가능하다.

<소스코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
 
struct person {
    char name[20];
    char phoneNum[20];
    int age;
};
 
int main(void) {
    struct person arr[3= {
        {"김김김""010-1111-1111"21},
        {"이이이""010-2222-2222"25},
        {"박박박""010-3333-3333"28}
    };
 
    for (int i = 0; i < 3; i++)
        printf("%s %s %d \n", arr[i].name, arr[i].phoneNum, arr[i].age);
 
    return 0;
}
 
cs

<실행결과>


구조체 포인터

int num = 5; num의 주소 값을 담는 포인터 변수 int * ptr = &num; 이듯이 구조체의 포인터 변수도 비슷하다.

struct point pos = {11, 22}; struct point * pptr = &pos; 접근 역시 마찬가지로 *ptr = 20; 이듯이 (*pptr).xpos = 10; 이런 식으로 접근할 수 있다. 혹은 다음과 같이 pptr->xpos = 10; 이렇게 접근이 가능하다.

이를 예시로 알아보자.

<소스코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
 
struct point {
    int xpos;
    int ypos;
};
 
int main(void) {
    struct point pos1 = { 12 };
    struct point pos2 = { 100200 };
    struct point* pptr = &pos1;
 
    pptr->xpos += 4;
    pptr->ypos += 8;
    printf("[%d, %d] \n", pptr->xpos, pptr->ypos);
 
    pptr = &pos2;
    (*pptr).xpos += 5// 이렇게도 접근이 가능하다
    (*pptr).ypos += 10// 이렇게도 접근이 가능하다
    printf("[%d, %d] \n", (*pptr).xpos, (*pptr).ypos);
    return 0;
}
 
cs

<실행결과>


그렇다면 포인터 변수도 구조체 멤버가 당연히 될 수 있다. 이제 예시를 통해 소스코드를 살펴보자.

<소스코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
 
struct point {
    int xpos;
    int ypos;
};
 
struct circle {
    double radius;
    struct point* center; // 원의 중점 
};
 
int main(void) {
    struct point cen = { 23 };
    double rad = 5.5;
 
    struct circle ring = { rad, &cen };
    printf("원의 반지름: %g \n", ring.radius);
    printf("원의 중심 [%d, %d] \n", (ring.center)->xpos, (ring.center)->ypos);
 
    return 0;
}
 
cs

<실행결과>

이를 그림으로 표시하면 다음과 같다.

 

'C' 카테고리의 다른 글

14. 메모리 및 동적할당  (0) 2021.06.28
12. 구조체와 사용자 정의 자료형 2  (0) 2021.06.27
10. 문자열 관련 함수  (0) 2021.06.27
9. 함수 포인터 & void 포인터  (0) 2021.06.26
8. 2차원 배열과 포인터  (0) 2021.06.26

+ Recent posts