[Data Structure] 연결리스트(Linked List)의 구현 2015-06-19

상황에 맞게 최적의 성능을 내고 싶을 경우 연결리스트를 직접 구현해 사용하기도 한다. 원형 이중 연결리스트를 C++로 구현해 보았다. 노드가 어디에 추가되건 편리하게 사용할 수 있는 장점이 있다. 선형의 경우도 아래 소스를 참고하여 손쉽게 구현할 수 있다.

circulardoublylinkedlist

#include "stdafx.h"
#include <iostream>

using namespace std;

struct User
{
	char name[20];
	char email[40];
};

template<typename T>
class Node
{
	template<typename T>
	friend class MyContainer;
private:
	Node* prev;
	Node* next;
	T* data;
public:
	Node() { this->prev = NULL; this->next = NULL; this->data = NULL; }
	Node(T* data) { this->prev = NULL; this->next = NULL; this->data = data; }
	Node(T* data, Node* prev, Node* next) { this->prev = prev; this->next = next; this->data = data; }
};

template<typename T>
class MyContainer
{
private:
	Node<T>* head = new Node<T>();
public:
	// 자신을 가리키도 데이터가 NULL인 헤더를 만든다.
	MyContainer() { this->head->prev = this->head; this->head->next = this->head; this->head->data = NULL; }
	~MyContainer() {
		this->deleteContainer();
		delete this->head;
	}

	// 생성
	Node<T>* createNode(T* data)
	{
		// data를 저장할 동적메모리 할당
		T* newData = new T(*data);

		// node를 저장할 동적메모리 할당
		//  - newNode->prev = lastNode (prev는 마지막 노드를 가리킨다.)
		//  - newNode->next = firstNode (next는 첫번째 노드(head)를 가리킨다.)		
		Node<T>* newNode = new Node<T>(newData, this->head->prev, this->head);
		
		// 노드가 삽입될 양옆의 노드 정보를 변경한다.				
		if (this->head == this->head->next)
		{
			// 최초 생성일 경우
			this->head->next = newNode;
		}
		else
		{
			// 최초 생성이 아닐 경우
			this->head->prev->next = newNode;
		}

		// firstNode->prev (첫번째 노드의 prev는 새로운 노드를 가리킨다.)
		// lastNode->next (마지막 노드의 next는 새로운 노드를 가리킨다.)
		this->head->prev = newNode;

		return newNode;
	}	

	// 삭제
	void deleteNode(Node<T>* node)
	{
		// 이전노드와 다음노드를 연결한다.
		node->prev->next = node->next;
		node->next->prev = node->prev;

		// 자신을 메모리에서 삭제
		delete node->data;
		delete node;
	}

	// 컨테이너 삭제
	void deleteContainer()
	{
		if (this->head->next != this->head)
		{
			// head 만 남지 않았으면 다음노드 삭제하고 재귀호출
			this->deleteNode(this->head->next);
			this->printAll();
			this->deleteContainer();			
		}	
	}

	// 전체 출력 : 적용시 삭제
	void printAll()
	{	
		if (this->head != NULL)
		{
			cout << "\n### head Node ###\n";
			cout << "HEAD : " << this->head << "\n";
			cout << "PREV : " << this->head->prev << "\n";
			cout << "NEXT : " << this->head->next << "\n";
			cout << "DATA : " << this->head->data << "\n";
			cout << "\n";

			cout << "### Node List ###\n";
			if (this->head->next != NULL)
			{
				for (Node<T>* currentNode = this->head->next; currentNode != this->head; currentNode = currentNode->next)
				{
					User* tmpUser = (User*)currentNode->data;
					cout << "[<-" << currentNode->prev << "]";
					cout << "[" << currentNode << "]";
					cout << "[" << currentNode->next << "->]";
					cout << " NAME : " << tmpUser->name << "\n";
				}
			}
		}
	}
};

int _tmain(int argc, _TCHAR* argv[])
{
	const int INSERT_SIZE = 5;

	MyContainer<User> myContainer;
	Node<User>* newNode[INSERT_SIZE];
	
	// Sample1. CreateNode : INSERT_SIZE 만큼 데이터를 입력
	for (int i = 0; i < INSERT_SIZE; i++)
	{
		User user;		
		cout << "insert name : ";
		cin.getline(user.name, 20);
		cout << "insert email : ";
		cin.getline(user.email, 40);
		
		newNode[i] = myContainer.createNode(&user);
	}
	myContainer.printAll();

	// Sample2. deleteNode : 2번째 노드를 삭제
	myContainer.deleteNode(newNode[1]);
	myContainer.printAll();

	// Sample3. deleteContainer : 전체 노드를 삭제 / 삭제해주지 않아도 소멸시 자동삭제되도록 구현한다.
	myContainer.deleteContainer();

	return 0;
}

[C++ 정리] 3. 문자열 다루기 (C/C++조합) 2015-06-17

C style로 개발된 코드을 연동해야하는 경우 C++과 조합하는 형태로 개발되어야 한다.

#include "stdafx.h"
#include <iostream>
#include <string>
#include <windows.h>

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
	char* strCStyle = "TEST STRING";
	string strCppStyle;

	// 바로 대입하여 사용가능하다.
	strCppStyle = strCStyle;
	cout << "strcpy : " << strCppStyle << "\n";

	// Cstyle과 같은 형태로 접근이 가능하다.
	strCppStyle[0] = 'A';
	cout << "strcpy : " << strCppStyle << "\n";

	// string 객체에는 문자열 + 기타 정보를 포함하고 있다.
	// 완전히 동일한 형태의 문자열값을 얻어오기 위해서는 c_str() 를 사용한다.
	const char* strTmp = NULL;
	strTmp = strCppStyle.c_str();
	cout << "strcpy : " << strTmp << "\n";

	// string 객체는 길이 제한이 없어 더욱 안정적이다.
	char cString[20];
	string cppString;
	
	// 19자 이상의 문자열을 입력하면 다른 메모리 영역을 덮어버려 심각한 문제가 발생한다.
	// - 악의적인 공격에 사용될 수 있다.
	cin >> cString;
	cout << "cin(C) : " << cString << "\n";

	// 길어도 상관없다.
	cin >> cppString;
	cout << "cin(CPP) : " << cppString << "\n";

	
	char cStringLine[20];
	string cppStringLine;

	// 메모리영역이 덮어지는 문제를 해결하려면 getline 함수를 사용하도록 하자.
	// - getline 함수는 입력된 문자를 20바이트가 넘지 않게 잘라서 저장한다.
	cin.getline(cStringLine, 20);
	cout << "getline(C) : " << cStringLine << "\n";

	getline(cin, cppStringLine);
	cout << "getline(CPP) : " << cppStringLine << "\n";

	return 0;
}
* C++ 의 string 객체에서 C style의 문자열을 변형하는 방법을 제공한다.
* string 객체를 사용하므로서 안정적으로 문자열을 활용할 수 있다.

[C++ 정리] 2. 문자열 다루기 (C++스타일) 2015-06-17

기본 제공하는 자료형이 아닌 string 객체를 사용해 문자열을 다루는데 이는 간단하고 명료하다.

#include "stdafx.h"
#include <iostream>
#include <string>

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
	// 원본 문자열
	string orgString = "TEST STRING";
	
	cout << "STRING_LENGTH : " << orgString.size() << "\n";

	// 대상 문자열 & 복사
	string tarString = orgString;

	// 문자열 출력
	cout << "ORG_STRING : " << orgString << "\n";
	cout << "TAR_STRING : " << tarString << "\n";

	// 문자열 비교
	if (tarString == orgString){
		cout << "compare \"" << orgString << "\" :::: \"" << tarString << "\" indentical.\n";
	}
	else
	{
		cout << "compare \"" << orgString << "\" :::: \"" << tarString << "\" not indentical.\n";
	}

	// 문자열 결합
	string addString = "+ADD STRING";
	addString = orgString + addString;
	cout << "ADD_STRING : " << addString << "\n";

	// 문자열 찾기
	int stringIndex = addString.find("+ADD");
	// - 배열과 동일하게 생각하여 +ADD는 12번째 즉, 인덱스 11에 위치한다.
	cout << "FIND_STRING : " << stringIndex << "\n";

	// 문자열 자르기
	string cutString = addString.substr(12, 3);
	// - 대상 문자열의 12번째 부터 3자리의 글자를 읽어온다.
	cout << "CUT_STRING : " << cutString << "\n";

	return 0;
}
* <링크 : C스타일 문자열다루기>와 비교해 훨씬 간단명료해지고 많은 기능을 지원해줌을 알 수 있다.