본문 바로가기

개발/C&C++

(C/C++ 속성 정리) 18일 차 : 리스트를 이용해서 성적표 만들기 및 설계

안녕하세요 넬다이입니다.

 

오늘은 18일 차로써 리스트를 이용해서 성적표를 만들어 볼 건데요

 

오늘 사용할 리스트는 더블 링크드 리스트를 활용해서 만들어 보겠습니다.

어려우시겠지만 조금만 더 힘 내주세요!!!!! 

이해가 안 되면 몇 번이나 보고 그냥 외우시면은 어느 순간 이해가 될 날이 오실 거예요 ㅠㅠ

 


더블 링크드 리스트

 

더블 링크드 리스트는 각각의 노드(하나의 객체)가 자신의 뒤와 앞의 주소를 알고 있는 형태이다.

 

각각의 New를 하게 되면은 메모리 주소 어딘가에 생성이 되는데 각 노드들이 다음에 생성된 노드에 주소를 알고 생성된 노드는 뒤에 생성된 주소를 알고 있음에 있어서 배열과 다르게 메모리 공간이 연속되어 있지 않고 떨어져 있으므로,

추가 생성 및 제거가 비용이 적다.

 

더블 링크드 리스트는 추가 및 제거 후 그 앞뒤 노드에 대해서 주소만 연결시켜 주면 되는 반면 배열은 연속된 주소이기 때문에 추가 및 삭제에 있어서 전체 삭제 후 원하는 사이즈만큼 다시 할당해야 하는 불편함과 비용이 뒤따른다.

이후에 std 자료구조 라이브러리 설명 때 좀 더 자세하게 다룰 예정이다.

 

자 우리는 이전 시간에서 이미 성적표를 만든 경험이 있다.

이를 통해서 더블 링크드 리스트를 구현을 시작해 보겠다.

 

첫 번째로 각 노드들은 기존에 말했듯이 앞과 뒤를 알고 있어야 하기 때문에 자기 자신의 형을 가리키는 포인터 변수를 만들 것이다.

 

typedef struct tagNode
{
	char	szName[20];
	int		iKor;
	int		iMath;
	int		iEng;
	tagNode* pNext;
	tagNode* pPrev;
}NODE;

기존 형태에서 tagNode* pNext , tagNode* pPrev가 생긴 것을 알 수 있다.

 

자기 자신의 형태를 가리키는 포인터 변수이다.

 

또 한 링크드 리스트에서 Head와 Tail을 알기 위해서 구조체 하나를 더 정의할 것이다.

typedef struct tagNode
{
	char	szName[20];
	int		iKor;
	int		iMath;
	int		iEng;
	tagNode* pNext;
	tagNode* pPrev;
}NODE;

typedef struct tagList
{
	//노드에 첫번째
	NODE*	pHead;
	//노드에 마지막
	NODE*	pTail;
}LIST;

 

자 이렇게 만들었으면 이제 Main을 만들어 보자.

#include <iostream>

using namespace std;

typedef struct tagNode
{
	char	szName[20];
	int		iKor;
	int		iMath;
	int		iEng;
	tagNode* pNext;
	tagNode* pPrev;
}NODE;

typedef struct tagList
{
	NODE*	pHead;
	NODE*	pTail;
	int		iCount;
}LIST;


void main(void)
{
	//LIST를 선언하고
	LIST InfoList;
	//변수를 초기화 해준다.
	memset(&InfoList, 0, sizeof(LIST));
	//유저가 입력할 변수 선언
	int iInput = 0;
	while(true)
	{
		system("cls");
		cout << "1.전체, 2.검색, 3.추가, 4.삭제, 5.종료 :";
		cin >>  iInput;
	}

}

자 이렇게 해서 기본적으로 LIST를 선언하고 초기화를 해주었으며

기존과 다르게 유저를 바로 입력을 받지 않고 있는데요 이것은 더블 링크드 리스트이기 때문입니다.

 

배열 같은 경우에는 공간을 추가 삭제가 용이하지 못하기 때문에 미리 개수를 정해야 했는데요

더블 링크들 리스트는 각각 공간이 따로따로 떨어져 있기 때문에 추가 삭제가 쉽기에

맨 처음에 전부 받을 필요가 없습니다.

 

자 그렇다면은 이제 각각의 함수를 만들어 보는 시간을 가져봅시다

첫 번째로는 제일 중요한 추가와 삭제를 만들어 보겠습니다.

 

#include <iostream>

using namespace std;

typedef struct tagNode
{
	char	szName[20];
	int		iKor;
	int		iMath;
	int		iEng;
	tagNode* pNext;
	tagNode* pPrev;
}NODE;

typedef struct tagList
{
	NODE*	pHead;
	NODE*	pTail;
}LIST;

NODE*	CreateNode(void);
void	Push_Back(LIST*	pList, NODE* pNode);
void	DeleteFind(LIST* pList);

void main(void)
{
	//LIST를 선언하고
	LIST InfoList;
	//변수를 초기화 해준다.
	memset(&InfoList, 0, sizeof(LIST));
	//유저가 입력할 변수 선언
	int iInput = 0;
	while(true)
	{
		system("cls");
		cout << "1.전체, 2.검색, 3.추가, 4.삭제, 5.종료 :";
		cin >>  iInput;
        
		switch(iInput)
		{
		case 1:
			break;
		case 2:
			break;
		case 3:
			Push_Back(&InfoList, CreateNode());
			break;
		case 4:
			DeleteFind(&InfoList);
			break;
		case 5:
			return;
			break;
		}

	}

}

//새로운 성적표 Node를 만들고 그를 리턴해주는 함수이다.
//이것은 단순 만드는 행동을 하는 함수이기때문에 Next와 Prev를 채워주지 않는다.
NODE* CreateNode(void)
{
	NODE*	pNode = new NODE;

	cout << "이름 : ";
	cin >> pNode->szName;
	cout << "국어 : ";
	cin >> pNode->iKor;
	cout << "영어 : ";
	cin >> pNode->iEng;
	cout << "수학 : ";
	cin >> pNode->iMath;
	
	pNode->pNext = NULL;
	pNode->pPrev = NULL;

	return pNode;
}

//이 함수는 Node를 받아서 List에 이어 붙여주는 역활을 하고 있는 함수이다.
//더블 링크드 리스트에서 생성과 삽입에 해당하는 구문이다.
void Push_Back(LIST* pList, NODE* pNode)
{
	if(pList->pHead == NULL)
	{
		//현제 리스트의 pHead는 비어있으므로 pNode가 헤더가 되는 상황입니다.
		//Head가 없다는것은 Node가 처음 만들어 진거라고 가정하고 코드를 짜기 때문이다.
		pList->pHead = pNode;
		//또 한 노드가 하나이므로 Head이면서 Tail이 되는 상황이다.
		pList->pTail = pNode;
		return;
	}
	else
	{
		//리스트가 비어있지 않다면은 Tail에 Node를 이어붙여주는 역활을 하고
		pList->pTail->pNext = pNode;
		pNode->pPrev = pList->pTail;

		//새로 추가된 Node이기에 Tail이 된다.
		pList->pTail = pNode;
	}
}
//이 함수는 List에서 삭제할 이름을 찾고
//그 Node를 지워주는 역활을 하고있다
//더블 링크드 리스트에 탐색과 제거에 해당하는 부분이다.
void DeleteFind(LIST* pList)
{
	NODE* pTemp = pList->pHead;
	char szName[20] = "";
	cout << "삭제할 이름 : ";
	cin >> szName;

	while(pTemp != NULL)
	{
		if(strcmp(pTemp->szName, szName) == 0)
		{
			//Head가 Tail인 상황은 무엇이냐 하면은
			//Node가 하나일때이다.
			//Push_back에서 Head가 없다면은 Head와 Tail을 하나의 Node에 저장했기 때문이다.
			if(pList->pHead == pList->pTail)
			{
				delete pTemp;
				pList->pHead = NULL;
				pList->pTail = NULL;	
				break;
			}
			
			//pTemp->pPrev가 NULL이면은 헤더인 경우이다
			//Head의 경우는 Prev가 없기 때문이다. 
			//맨 처음 생성되었기 때문이다.
			if(pTemp->pPrev != NULL)	
				pTemp->pPrev->pNext = pTemp->pNext;
			else//Head인 경우이다.						
			{
				pTemp->pNext->pPrev = NULL;
				pList->pHead = pTemp->pNext;
			}
			//Tail이 아닌경우인데 Tail이 아니라면은 pNext가 Node로 채워져 있을것이다.
			//왜냐 마지막 만든 Node가 아니라면은 Next를 채워주기 때문이다.
			if(pTemp->pNext != NULL)
				pTemp->pNext->pPrev = pTemp->pPrev;
			else // 위와 반대인 경우이다.
				pList->pTail = pTemp->pPrev;

			delete pTemp;
			break;
		}

		pTemp = pTemp->pNext;
	}
}

 

자 이렇게 생성 삽입과 제거를 해보았습니다.

그렇다면은 남은 함수는 출력해주는 함수라고 생각합니다

그러면 이제 출력하는 함수를 만들어 보도록 하겠습니다.

 

#include <iostream>

using namespace std;

typedef struct tagNode
{
	char	szName[20];
	int		iKor;
	int		iMath;
	int		iEng;
    
	tagNode* pNext;
	tagNode* pPrev;
}NODE;

typedef struct tagList
{
	NODE*	pHead;
	NODE*	pTail;
}LIST;

NODE*	CreateNode(void);
void	Push_Back(LIST*	pList, NODE* pNode);
void	DeleteFind(LIST* pList);
void	RenderAll(LIST* pList);
void	RenderFind(LIST* pList);
void	Release(LIST* pList);


void main(void)
{
	//LIST를 선언하고
	LIST InfoList;
	//변수를 초기화 해준다.
	memset(&InfoList, 0, sizeof(LIST));
	//유저가 입력할 변수 선언
	int iInput = 0;
	while(true)
	{
		system("cls");
		cout << "1.전체, 2.검색, 3.추가, 4.삭제, 5.종료 :";
		cin >>  iInput;
        
		switch(iInput)
		{
		case 1:
   			RenderAll(&InfoList);
			break;
		case 2:
   			RenderFind(&InfoList);
			break;
		case 3:
			Push_Back(&InfoList, CreateNode());
			break;
		case 4:
			DeleteFind(&InfoList);
			break;
		case 5:
   			Release(&InfoList);
			return;
			break;
		}

	}

}

//새로운 성적표 Node를 만들고 그를 리턴해주는 함수이다.
//이것은 단순 만드는 행동을 하는 함수이기때문에 Next와 Prev를 채워주지 않는다.
NODE* CreateNode(void)
{
	NODE*	pNode = new NODE;

	cout << "이름 : ";
	cin >> pNode->szName;
	cout << "국어 : ";
	cin >> pNode->iKor;
	cout << "영어 : ";
	cin >> pNode->iEng;
	cout << "수학 : ";
	cin >> pNode->iMath;
	
	pNode->pNext = NULL;
	pNode->pPrev = NULL;

	return pNode;
}

//이 함수는 Node를 받아서 List에 이어 붙여주는 역활을 하고 있는 함수이다.
//더블 링크드 리스트에서 생성과 삽입에 해당하는 구문이다.
void Push_Back(LIST* pList, NODE* pNode)
{
	if(pList->pHead == NULL)
	{
		//현제 리스트의 pHead는 비어있으므로 pNode가 헤더가 되는 상황입니다.
		//Head가 없다는것은 Node가 처음 만들어 진거라고 가정하고 코드를 짜기 때문이다.
		pList->pHead = pNode;
		//또 한 노드가 하나이므로 Head이면서 Tail이 되는 상황이다.
		pList->pTail = pNode;
		return;
	}
	else
	{
		//리스트가 비어있지 않다면은 Tail에 Node를 이어붙여주는 역활을 하고
		pList->pTail->pNext = pNode;
		pNode->pPrev = pList->pTail;

		//새로 추가된 Node이기에 Tail이 된다.
		pList->pTail = pNode;
	}
}
//이 함수는 List에서 삭제할 이름을 찾고
//그 Node를 지워주는 역활을 하고있다
//더블 링크드 리스트에 탐색과 제거에 해당하는 부분이다.
void DeleteFind(LIST* pList)
{
	NODE* pTemp = pList->pHead;
	char szName[20] = "";
	cout << "삭제할 이름 : ";
	cin >> szName;

	while(pTemp != NULL)
	{
		if(strcmp(pTemp->szName, szName) == 0)
		{
			//Head가 Tail인 상황은 무엇이냐 하면은
			//Node가 하나일때이다.
			//Push_back에서 Head가 없다면은 Head와 Tail을 하나의 Node에 저장했기 때문이다.
			if(pList->pHead == pList->pTail)
			{
				delete pTemp;
				pList->pHead = NULL;
				pList->pTail = NULL;	
				break;
			}
			
			//pTemp->pPrev가 NULL이면은 헤더인 경우이다
			//Head의 경우는 Prev가 없기 때문이다. 
			//맨 처음 생성되었기 때문이다.
			if(pTemp->pPrev != NULL)	
				pTemp->pPrev->pNext = pTemp->pNext;
			else//Head인 경우이다.						
			{
				pTemp->pNext->pPrev = NULL;
				pList->pHead = pTemp->pNext;
			}
			//Tail이 아닌경우인데 Tail이 아니라면은 pNext가 Node로 채워져 있을것이다.
			//왜냐 마지막 만든 Node가 아니라면은 Next를 채워주기 때문이다.
			if(pTemp->pNext != NULL)
				pTemp->pNext->pPrev = pTemp->pPrev;
			else // 위와 반대인 경우이다.
				pList->pTail = pTemp->pPrev;

			delete pTemp;
			break;
		}

		pTemp = pTemp->pNext;
	}
}

// 리스트를 전부 순회하면서 Render를 하는 함수이다.
// 리스트에서 정방향 탐색에 해당하는 부분이다.
void RenderAll(LIST* pList)
{
	NODE* pTemp = pList->pHead;

	while(pTemp != NULL)
	{
		cout << pTemp->szName << " | "<< pTemp->iKor 
			<< " | " << pTemp->iEng << " | " << pTemp->iMath << endl;

		pTemp = pTemp->pNext;
	}
	system("pause");
}
//리스트에서 특정 이름을 가지고 있는 Node를 찾아서 출력해주는 코드이다
//더블 링크드 리스트에서 탐색 및 검색에 해당하는 부분이다.
void RenderFind(LIST* pList)
{
	NODE* pTemp = pList->pHead;
	char szName[20] = "";
	cout << "검색할 이름 : ";
	cin >> szName;

	while(pTemp != NULL)
	{
		if(strcmp(pTemp->szName, szName) == 0)
		{
			cout << pTemp->szName << " | "<< pTemp->iKor 
				<< " | " << pTemp->iEng << " | " << pTemp->iMath << endl;
			system("pause");
			break;
		}

		pTemp = pTemp->pNext;
	}
}
// 이 구문을 하는 이유는 
// 각 노드들은 동적 할당을 했기 때문에
// 종료시에는 모든 노드를 삭제를 해줘야 메모리가 컴퓨터에 쌓이는것을 막을 수 있다.
void Release(LIST* pList)
{
	NODE* pTemp = pList->pHead;

	while(pTemp != NULL)
	{
		pList->pHead = pTemp;
		pTemp = pTemp->pNext;
		delete pList->pHead;
	}
}

 

오늘은 이렇게 링크드 리스트를 활용해서 리스트를 만들어 보았는데요

 

다들 아마 처음에는 이해하기 힘든 부분들도 있을 거예요

너무 어려우시다면 댓글 남겨주시면 제가 성심성의껏 답변드리겠습니다.

 

그럼 이만 넬다이 였습니다.

 

[개발/C&C++] - (C/C++ 속성 정리) 17일 차 : 성적표 만들기 및 설계

[개발/C&C++] - (C/C++ 속성 정리) 16일 차 : 메모리 관련 함수

[개발/C&C++] - (C/C++ 속성 정리) 15일 차 : 포인터와 동적할당

[개발/C&C++] - (C/C++ 속성 정리) 14일 차 : 함수의 사용방법

[개발/C&C++] - (C/C++ 속성 정리) 13일 차 : typedef , 구조체

[개발/C&C++] - (C/C++ 속성 정리) 12일 차 : string 컨테이너