4월 17, 2024

C 언어 구조체 쉽게 이해하기, struct 올바른 사용법

1. C 언어 구조체란

C 언어에서 구조체란 영어로 struct, 즉 정형화된 structure (구조) 라고 생각하면 편하다. 

정해진 멤버들이 있고, 해당 멤버가 포함된 한마디로 해당 멤버들이 묶인 그룹이라고 생각하면 된다. 

 

가령 모든 사람에게는 공통적으로 '이름' 과 '나이' 라는 속성이 있다. 

그러면 우리는 '이름'과 '나이'라는 멤버를 가지는 '사람'이라는 구조체를 가지고 있는 것이다. 구조체는 이처럼 동일한 구조를 가진 객체라고 생각하면 된다. 하지만 자바의 객체와는 다른 점이 있으니 동일 규격으로 구성된 하나의 구조라고 생각하는 것이 더 정확할 것 같다. 


2. struct 키워드 

구조체를 사용할 때는 "struct" 라는 키워드를 사용한다.

 

struct 구조체이름{
 자료형 멤버이름1;
 자료형 멤버이름2;
 자료형 멤버이름3;
};

구조체의 기본적인 골격은 위와 같다. 구조체 { } 안에 원하는 멤버들을 자료형, 이름 순서대로 기입해주면 된다.

들어갈 수 있는 멤버의 수에는 제한이 없지만 멤버들이 많아질 수록 구조체도 방대해지는 것이라고 생각하면 된다.

 

위에서 말한 "사람"으로 구조체를 만들어보도록 하겠다. 사람 구조체의 멤버에는 나이와 이름이 있을 수 있고, 다른 attribute들이 추가적으로 있을 수 있으나, 공통적으로 존재하는 attribute여야 구조체의 활용도가 높아지므로 일단 나이와 이름이라는 멤버에만 국한하여 구조체를 만들어보겠다.

 

struct Person {   // 구조체 Person
   int age;              // 구조체 멤버 1: 나이
   char name[20];        // 구조체 멤버 2: 이름
};

이런식으로 구조체를 구성해보았다. 구조체를 선언한 뒤에는 반드시 세미콜론을 붙여주어야 한다는 점을 잊지 말자.

 

나이는 int형으로, 이름은 char[] 형으로 구성하여 멤버 두 개로 구성된 구조체 Person이 만들어졌다.

 

하지만 이렇게 선언만 했다는 것은 Person이라는 모형 (틀) 이 생겼다는 것이고 실제 사람 실체를 만들기 위해서는 구조체를 사용해 만들어주고 멤버들의 내용을 채워주어야 한다. 위에서 볼 수 있듯이 struct의 경우에는 대체로 main () 함수 밖에 정의하며, main 함수에서 변수를 선언하여 사용하는 형식이다. 

 

C언어 main 함수에서 객체를 생성해주고 멤버 정보를 채워보자.

int main()
{
    struct Person person1;     // 구조체에 해당되는 변수 선언
    
    person1.age = 50; //나이 할당
    // 이름에 해당하는 값 strcpy() function 통해 할당
    strcpy(person1.name, "김철수");

    
    //구조체에 정보가 잘 들어갔는지 프린트를 통해 확인
    printf("나이: %d\n", person1.age);        // 나이: 50
    printf("이름: %s\n", person1.name);       // 이름: 김철수
    return 0;
}

멤버 정보를 할당 할때 우리는 선언해준 구조체 변수에 . 을 붙이고 그 안에 member 이름을 붙여 접근을 하게 된다. 

즉 사람의 이름에 접근할때는 person1.name, 나이 값을 알고 싶다면 person1.age로 해서 접근을 하는 것이다. 

 

int 형의 자료형 같은 경우에는 

person1.age = 50

 이런 식으로 선언을 해줄 수 있지만,

 

char [] 형의 경우에는 할당 연산자를 사용할 수 없기 때문에 strcpy() 를 사용한 것이다. 

strcpy(person1.name, "김철수");

이런식으로 적어주면 되고, strcpy() import를 위해서는 

#include <string.h>

를 맨 위에 적어주면 된다.

 


3. 최종 예시 코드

이렇게 완성된 최종 코드는 아래와 같다.

#include <stdio.h>
#include <string.h>    // strcpy function import를 위해 
#define _CRT_SECURE_NO_WARNINGS   // strcpy 보안 경고 방지

struct Person {   // 구조체 Person
   int age;              // 구조체 멤버 1: 나이
   char name[20];        // 구조체 멤버 2: 이름
};

int main()
{
    struct Person person1;     // 구조체에 해당되는 변수 선언
    
    person1.age = 50; //나이 할당
    // 이름에 해당하는 값 strcpy() function 통해 할당
    strcpy(person1.name, "김철수");

    
    //구조체에 정보가 잘 들어갔는지 프린트를 통해 확인
    printf("나이: %d\n", person1.age);        // 나이: 50
    printf("이름: %s\n", person1.name);       // 이름: 김철수
    return 0;
}

 

이런식으로 C 언어에서 struct 구조체를 사용하는 방법을 알아보았다. 

간단하지만 모르면 C 언어 코드를 이해하기 어려운 경우가 많으니 잘 알아두자. 


8월 30, 2023

C Node 구조체로 연결, 제거, 추가 해보기

특정 자료구조를 100% 이해하기 가장 좋은 방법은 그것을 코드로 구현해보는 것이고 그럴 때 가장 좋은 방법은 C를 사용하는 것이라고 생각한다. 

 

나는 연결리스트를 C로 구현해보았다. 먼저 직접 코드를 작성해본 뒤에 아래 코드를 참고해보면 좋을 것 같다. 많은 언어 중에 Java나 C++이 아닌 low level 언어인 C를 사용하면 좋다고 한 이유는 가장 제약이 많이 따르는 만큼 자료구조에 대한 이해를 하기 좋기 때문이다. 또한 익숙한 class가 아닌 구조체를 사용하면서 코드 연습을 가장 잘 할 수 있는 언어이기도 하다. 

 

주로 C에서 연결리스트를 구현할 때에는 node라는 struct를 하나 만들어주고 그 안에 몇 번째인지를 알려주는 index, 해당 node가 들고 있는 값을 알려주는 data, 그리고 다음 node와 연결해주기 위한 struct node * 형식의 next를 가진다. 이 세 가지 구성을 잘 사용하면 우리가 원하는 방식의 linked list를 구현할 수 있다. 

 

먼저 위에 언급한 것처럼 node struct부터 만들어주겠다.

 

typedef struct node {
	int index;
	int data;
	struct node* next;
}NODE;

 

다음으로 만든 구조체를 활용하여 초기 세팅을 해주자.

 

NODE* head = NULL;


NODE* init(int data) {
	NODE* tmp = (NODE*)malloc(sizeof(NODE));

	tmp->data = data;
	tmp->next = NULL;
	return tmp;
}

 

맨 처음에는 node의 head가 가리키고 있는 것이 없으므로 NULL로 세팅을 해준 뒤 하나의 노드를 만들기 위해 init이라는 함수를 사용해주자. 우리는 사용자가 원하는 값으로 node를 세팅해줄 것이므로 인자로 data라는 parameter를 받아와서 node의 data를 해당 값으로, 그리고 다음 node는 NULL 값으로 연결해주자. 처음에 malloc의 과정이 필요하다. C를 사용하기 때문에 이러한 과정이 다소 귀찮게 느껴질 수는 있으나 이런 과정을 계속 해보다 보면 코딩 실력도 늘고 자료구조에 대한 이해도도 올라간다.

 


다음으로는 원하는 data가 있는 node를 찾는 과정이다. search 함수를 만들어 사용하겠다.

NODE* search(int data) {
	//data 노드 찾기
	//있으면 해당 노드의 주소를  return
	//없으면 NULL return 하기 
	NODE* tmp = head;
	while (tmp) {
		if (tmp->data == data) {
			return tmp;

		}
		tmp = tmp->next;
	}
	return NULL;
}

처음 head node부터 시작하여 쭉 노드를 순회하다가, 원하는 data 값과 노드의 데이터 값이 일치하면 해당 노드를 반환해주는 형식이다. 만약 일치하지 않는다면 계속 다음 노드를 순회하면서 찾을 때까지 순회하고 결국 찾지 못하면 NULL을 반환해준다.


다음으로 새로운 node를 추가하는 과정이다.

void add(int prev, int data) {
	//prev 노드 앞에 data 추가 (삽입)
	//예 10-20-30
	//add(20,100); 10-100-20-30
	//prev가 -1이면 맨 뒤에 추가 
	//최초 노드 생성인 경우 (head 로 등록)
	//2. 맨 앞에 삽입하는 경우
	//3. 중간/맨 뒤에 삽입하는 경우 

	NODE* new_node = init(data);
	NODE* tmp = head;
	NODE* find = NULL;
	if (!head) {
		head = new_node; return;
	}

	else if (prev != -1 ) {
		find = search(prev);
		if (!find) {
			printf("해당 노드가 없습니다.\n");
			return;
		}
		if (find == head) {
			new_node->next = find;
			head = new_node;
			return;
		}
		else {
			NODE* prev = tmp;
			while (prev) {
				if (prev->next == find) {
					break;
				}
				prev = prev->next;
			}
			prev->next =new_node;
			new_node->next = find;
			return;
		}
	}

	if (prev == -1) {
		while (tmp->next) {
			tmp = tmp->next;
		}
		tmp->next = new_node;
	}
	}

가장 마지막 노드에 추가하는 것이 아니라 원하는 prev 노드를 받아와 추가를 하는 것이기 때문에 조건을 나누어서 작성했다.


노드를 추가했다면 삭제하는 것 또한 필요하다.

void remove(int data) {
	//index번 위치에 노드를 삭제하는것
		//삭제 실패(미등록 노드를 삭제할 경우) 시 "미등록 노드"를 출력 
		//해당 data node 삭제 
	NODE* tmp = head;
	NODE* find = search(data);
	if (!head) {
		printf("제거할 노드가 없습니다.");
		return;
	}
	if (!find) {
		printf("미등록 노드입니다");
		return;
	}
	if (find == head) {
		head = head->next;
		free(tmp);
		return;
	}
	NODE* prev = tmp;
	while (prev) {
		if (prev->next == find) {
			break;
		}
		prev = prev->next;
	}
	prev->next = find->next;
	free(find);
}

이 또한 어떠한 노드를 삭제하냐에 따라서 경우가 달라질 수 있기 때문에 경우를 나누어서 처리했다. (만약 읽어보면서 이해되지 않는 부분이 있다면 댓글을 남겨주시면 답변해드릴게요! )


다음으로는 모든 node들을 순회하면서 값을 print 해주는 함수이다.

void print_all() {
	//모든 노드를 출력하세요
	NODE* tmp = head;
	while (tmp) {
		printf("%d ", tmp->data);
		tmp = tmp->next;
	}
}

마찬가지로 head부터 시작하여 해당 node의 data를 출력하고 다음 노드로 넘겨주는 과정을 반복한다.


남의 코드를 읽어보는 과정과 자신이 직접 짜본 코드는 매우 다르다. 아래 전체 코드를 첨부하였고 직접 해보면서 자료구조에 대한 이해를 늘려보는 것을 매우매우 추천한다! 

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>

typedef struct node {
	int index;
	int data;
	struct node* next;
}NODE;

NODE* head = NULL;

//가장 앞 노드 삭제하는 경우 생각

NODE* init(int data) {
	NODE* tmp = (NODE*)malloc(sizeof(NODE));

	tmp->data = data;
	tmp->next = NULL;
	return tmp;
}
NODE* search(int data) {
	//data 노드 찾기
	//있으면 해당 노드의 주소를  return
	//없으면 NULL return 하기 
	NODE* tmp = head;
	while (tmp) {
		if (tmp->data == data) {
			return tmp;

		}
		tmp = tmp->next;
	}
	return NULL;
}
void add(int prev, int data) {
	//prev 노드 앞에 data 추가 (삽입)
	//예 10-20-30
	//add(20,100); 10-100-20-30
	//prev가 -1이면 맨 뒤에 추가 
	//최초 노드 생성인 경우 (head 로 등록)
	//2. 맨 앞에 삽입하는 경우
	//3. 중간/맨 뒤에 삽입하는 경우 

	NODE* new_node = init(data);
	NODE* tmp = head;
	NODE* find = NULL;
	if (!head) {
		head = new_node; return;
	}

	else if (prev != -1 ) {
		find = search(prev);
		if (!find) {
			printf("해당 노드가 없습니다.\n");
			return;
		}
		if (find == head) {
			new_node->next = find;
			head = new_node;
			return;
		}
		else {
			NODE* prev = tmp;
			while (prev) {
				if (prev->next == find) {
					break;
				}
				prev = prev->next;
			}
			prev->next =new_node;
			new_node->next = find;
			return;
		}
	}

	if (prev == -1) {
		while (tmp->next) {
			tmp = tmp->next;
		}
		tmp->next = new_node;
	}
	}

void remove(int data) {
	//index번 위치에 노드를 삭제하는것
		//삭제 실패(미등록 노드를 삭제할 경우) 시 "미등록 노드"를 출력 
		//해당 data node 삭제 
	NODE* tmp = head;
	NODE* find = search(data);
	if (!head) {
		printf("제거할 노드가 없습니다.");
		return;
	}
	if (!find) {
		printf("미등록 노드입니다");
		return;
	}
	if (find == head) {
		head = head->next;
		free(tmp);
		return;
	}
	NODE* prev = tmp;
	while (prev) {
		if (prev->next == find) {
			break;
		}
		prev = prev->next;
	}
	prev->next = find->next;
	free(find);
}

void print_all() {
	//모든 노드를 출력하세요
	NODE* tmp = head;
	while (tmp) {
		printf("%d ", tmp->data);
		tmp = tmp->next;
	}
}
void main() {
	int data, select, prev;
	NODE* result = NULL;
	while (1) {
		print_all();
		printf("1. 추가 \n2. 삭제 \n3. 검색 \n0.종료 \n입력: ");
		scanf_s("%d", &select);
		switch (select) {
		case 1:
			printf("새정수: ");
			scanf_s("%d", &data);
			printf("어느 노드 앞에 추가?(마지막은 -1) : ");
			scanf_s("%d", &prev);
			add(prev, data);
			break;
		case 2:
			printf("삭제할 정수를 입력하세요...\n");
			scanf_s("%d", &data);
			remove(data);
			break;
		case 3:
			printf("검색할 정수의 입력하세요...\n");
			scanf_s("%d", &data);
			result = search(data);
			if (result) {
				printf("%d(은)는 있습니다. \n", data);
			}
			else {
				printf("%d(은)는 미등록 노드입니다. \n", data);
			}
			break;
		case 0:
			exit(0);
		}
		system("pause");
		system("cls");
	}
}