링크드리스트. 이것은 프로그래밍 해봤다는 사람은 한번씩은 들어봤을 겁니다. 

기본적인 자료구조이기에.. 또한 제가 가장 즐겨쓰는 구조인 만큼 아주 확실하게 알아봅시다.

일단 아래의 모양을 한번 봐 보세요.


class Node {

int Data;

Node Link;

Node(int d,Node l) {

Data = d;

Link = l;

}

}

와 같이 만들면 됩니다. 즉 Data 라는 실제로 쓰이는 변수와 함께 자기자신과 똑같은 형인

Link 라는 변수도 만들어서 어떤 부분을 가리키도록(point) 하는것이죠

꼭 이름을 Node 라고 할 필요는 없으며 LinkedNode 라 하든지 뭐 맘대로 해도 됩니다.

그리고 위의 클래스는 변수만 있을뿐 메소드(함수)는 없지만 뭐 넣어도 됩니다. Data 라는

변수를 하나만 쓰지 않고 int Data; String aaa; 뭐 이렇게 다른 자료형도 쭉 쓸 수

있습니다. 즉

class myNode {

int a;

int b;

String c;

Object d;

int put(int aa,int bb) {

// 이 부분에 각 경우에 따른 코드를 쓰면 되겠죠..

}

}

처럼 해도 된다는 말이죠. 어렵지 않죠?

자 그럼. 아주 간단한 프로그램 만들어 봅시다.

// Simple.java

class ChainNode {

int Data;

ChainNode Link;

ChainNode(int d,ChainNode l) {

Data = d;

Link = l;

}

}

public class Simple { // 이곳에 main이 있으므로 파일이름은 Simple.java가 당연히 되겠죠?

public static void main(String[] args) {

ChainNode head = new ChainNode(0,null); // null은 가리키는 것이 없다는 뜻

// 그리고 head는 링크드리스트의 맨 처음부분이다.

ChainNode current = head; // current을 우선 head와 똑같다고 한다.그리고 current는 계속 바뀌게된다.

ChainNode temp; // 임의의 ChainNode 만든 것을 임시저장한다.

for ( int i=1; i<=10 ; i++ ) {

temp = new ChainNode(i,null); // 예를 들어 i가 1일때를 아무것도 가리키는 것이 없는 다만

// 1이라는 Data만 들어있는 걸 메모리상에 만들고(new했으니..) 그 걸 temp로 칭한다.

current.Link = temp; // current를 만들때 null값을 넣었지만 다른 부분을 링크

// 하도록 current.Link 에다가 방금 만든 temp를 넣어서 , 그 쪽을 가리키도록 한다.

current = temp; // 1부터 10까지 돌기때문에 current는 계속 바뀌게 된다.

}

// for문이 끝나면 여러개의 ChainNode가 링크되어서 그 흐름이 유지된다.

current = head; // 이제 처음(head)부터 print해서 그 내용을 확인하기 위해서

// current에 head를 넣어서 처음 위치로 이동시킨다.

while( current != null ) { // current에 아무값도 없을때까지 실행한다.

System.out.println(current.Data); // 맨 처음엔 head의 Data가 출력되고

current = current.Link; // current에다가 current가 가리키는 부분을 대입한다. 가장 중요!!

// 이 부분을 이해해야 합니다.

}

}

}

// 출력은 0부터 10까지 출력된다.

// 0은 head에 들어있었고, 1부터 10까지는 for문에서 만들었다.

가장 간단한 소스인만큼 꼭 확실하게 이해해야 합니다.

(밑의 설명에서 리스트나 링크드 리스트나 거의 같은 뜻이로 봐도 됩니다.

리스트중에 링크드리스트가 있는 것이니..) 파란색문장은 위의 소스에 그대로 있다는 걸

표시합니다.

head 와 current 와 temp 의 역할이 무엇인가를 알아봅시다.

링크드리스트는 각각의 노드(여기선 ChainNode)들이 링크(여기선 Link)를 통하여

하나의 리스트가 되는 겁니다. 따라서 전체 리스트의 처음부분은 항상 존재해야 하므로

head 라는 걸 만듭니다. 그리고 일단 구성되어 있는 링크드리스트에 새로운 노드를

뒷부분에다 집어넣을 경우 새로운 노드를 일단 메모리상에 만듭니다. 아래와 같이

temp = new ChainNode(i,null); temp는 이미 존재하고 있는 리스트하고는 아무런

연결끈이 현재로서는 없고 단지 메모리상에 있을 뿐이기에 , 연결시켜야 합니다.

따라서 리스트의 마지막노드(current)의 Link 에 temp를 넣어주면 됩니다.

즉 current.Link = temp; 이렇게.. 그러면 메모리상에 만든 temp가 리스트의 끝부분이

되기에 current = temp; 이와 같이 하면 current가 옮겨지게 되죠. 이렇게 하지말고

current = current.Link; 라고 해도 됩니다. 앞에서 먼저 current.Link = temp; 라고 했기에

당연히 되죠..됩니다. 확인해 봤어요!! 자 이렇게 해서 for 문을 분석했습니다. 즉 for 문은

링크드리스트에 데이터를 넣는(put) 역할을 하죠. 이젠 링크드리스트의 데이터를

한번 가져와(get)봅시다.

for 문이 끝난 상태에서는 current 는 링크드리스트의 맨 끝부분이기에 , 리스트의 첫

부분부터 데이터를 가져올려면 current = head; 함으로써 current를 링크드리스트의 첫

부분으로 하면 되겠죠? 계속 느꼈을텐데 current 는 줏대없이 계속 여기저기 이동되죠?

링크드리스트 특성상 이런 역할을 하는 ChainNode 가 있어야 합니다. 그리고 current 즉

우리말로 현재라는 뜻인데 , 여기저기 이동되므로 옮겨졌을 때, 링크드 리스트상에서

현재지점이 되는 의미에서 current 라고 쓴겁니다. 내키지 않으면 바꿔도 됩니다.

while ( current != null) <-- current 에 아무런 것도 있지 않을 때 즉 null 일때까지 돌게

되죠. 그럼 언제까지 돌까요? 링크드 리스트의 각 노드들은 Data 와 Link를 가지고 있죠?

Data 에는 뭐 진짜 데이터가 들어있게 되고, Link 에는 다음번 노드의 주소가 들어있죠?

그래야 링크 되고 또 링크되고 .. 해서 전체 리스트가 구성되니까요..하지만 맨 끝노드에는

어떻죠? 끝이니까 다음번 노드가 없죠? 따라서 맨 끝의 노드의 Link 에는 null 값이

들어있습니다. 실제로 Node를 만들 때 보통 temp = new ChainNode(i,null); 이라고 해서

처음에는 null이 들어가게 하는 것이죠. 그 노드가 어떤 걸 가리킬려고 하면 그냥

current.Link = temp ;처럼 해서 Link 에 어떤노드의 주소를 넣어주면 되니까요.

System.out.println(current.Data); 해서 데이터를 출력하고

current = current.Link; 라고 해서

현재노드를 이동시켜 주는 것이죠. while 문이 끝날 때 까지 current 의 Data를 출력하고

current는 다음번 노드로 이동하고 ... 아시겠죠?

자 첫 번째 과제입니다. 위의 소스를 완벽히 이해했다면 쉽게 만들 수 있으며, 반드시

소스를 보면서 만들지 말고 그냥 생각하면서 만드세요. 저는 자료구조 과제가 있는 주말은

집에서 10시간 이상 코드작성했습니다. 강의자료를 보고 만들면 쉽게 만들 수 있지만

나중에 돌이켜보면 자기것이 되지 못합니다. 기본 원리를 이해한후 참고용 소스 같은 것

보지말고 혼자 생각하면서 하나씩 하나씩 만들어 갔으니 당연히 시간을 많이 잡아 먹죠.

그러나 이런식으로 하면 개념이 정말 확실하게 잡히니까 여러분도 이렇게 하세요. 노력하지

않고 좋은 결과만 있길 바란다면 안되죠..

첫 번째 과제 : 링크드 리스트를 이용하여 10부터 1까지(자연수) 들어있는 리스트를 만들고

(즉 앞의 제가 보여준 예제는 1부터 11까지 였는데 이 번엔 10부터 1까지 역순으로

들어있는 리스트이죠.) 그리고 리스트의 처음에서부터 그 값을 출력하는데 데이터에 7이

있는 노드를 만나면 거기 까지만 출력하도록 하시오. 즉 10 9 8 7 까지만 나오게 하라

라는 뜻입니다.

밑에는 해답을 올리죠. 하지만 절대 먼저 보지 마세요 . 이리저리 해 보고 난 후 보세요.

class ChainNode {

int Data;

ChainNode Link;

ChainNode(int d,ChainNode l) {

Data = d;

Link = l;

}

}


public class Simple {

public static void main(String[] args) {

ChainNode head = new ChainNode(10,null);

ChainNode current = head;

ChainNode temp;

for ( int i=9; i>=1 ; i-- ) {

temp = new ChainNode(i,null);

current.Link = temp;

current = current.Link;

}

current = head;

while( current != null ) {

if ( current.Data == 7 ) {

System.out.println(current.Data);

break;

}

else {

System.out.println(current.Data);

current = current.Link;

}

}

}

}

보라색으로 된 부분이 원래 소스에서 수정되거나 추가 된 겁니다. 큰수에서 작은 수로

내려가니까 당연히 for 문에 i-- 가 들어가는 것은 아시죠?

출력부분에 데이터가 7이면 그까지만 출력하는 것이니

if (current.Data == 7 ) 이렇게 해서 7인 경우에 대해 따로 만들면 되죠

println 써서 출력해 주고 break; 써서 while 문을 빠져 나옵니다.

그렇지 않은 경우는 else 써서 똑같이 하면 되겠죠?

자 이제는 ChainNode에 메쏘드를 추가해서 만들어 봅시다. C 언어에서의 구조체와

C++에서의 클래스(Java도 마찬가지) 가 다른 점이 뭘까요? 가장 큰 다른 점은 데이터만

가질수 있느냐? 아니면 메쏘드(함수)까지 가질 수 있느냐 이죠?

좀 더 고급스럽게 고쳐봅시다. 훨씬 코드가 길어졌지만 가장 기본이 되는 부분이니

빠짐없이 메쏘드 부분을 유심히 보세요. 앞으로 갈길이 먼데 기초가 단단해야죠?

아래의 예제는 1부터 10까지 각 숫자에 10배 한 값을 데이터로 가지는 노드가 서로

링크되어 있으며(이전에는 1부터 10까지의 수가 들어갔지만 이번엔 1*10 , 2*10 , ...

10*10 의 수가 들어간다는 뜻이죠) 링크드리스트의 맨 처음 노드에서 index 번째있는

노드의 데이터만을 프린트해주는 프로그램입니다.(이전엔 index번째노드까지 모두 출력하는

것이었죠? 10 9 8 7 이렇게..)

// 이번 예제는 head는 단지 맨처음노드를 가리키는 역할만 하는겁니다.

// 즉 head노드의 Data는 의미가 없고 단지 Link만이 의미가 있는것이죠 이때까지 예제와는

// 다르죠. 즉 head.Link 에 있는 주소가 진짜 첫번째 노드인것이죠.head는 단지 첫번째노드를 가리킬뿐..

// 뭐 이렇게 할수도 있다는 것이고 프로그램 종류에 따라서 head의 Data에 진짜값을

// 넣을것인지 아닌지 스스로 판단해서 코딩하면 됩니다.

class ChainNode {

int Data;

ChainNode Link;

ChainNode(int d,ChainNode l) {

Data = d;

Link = l;

}

}

class LinkedList {

ChainNode head;

ChainNode current;

int size;

LinkedList() { // 생성자

head = new ChainNode(0,null);

current = new ChainNode(0,null);

size = 0;

// head.Link = current 부분이 없습니다. 즉 초기값으로 head는 아무것도 가리키지 않는다는 뜻이죠

}

boolean isEmpty() { // 링크드리스트가 비워있는지 체크.

if ( size == 0 )

return true; // size가 0 이면 true

else

return false; // 그렇지 않다면 false

}

int get(int index) { // index번째의 Data를 리턴한다.

if ( isEmpty() == true ) {

System.out.print("링크드리스트에 아무 값도 없어요 index =>");

return index;

}

else

if ( index > size || index < 1) {

System.out.print("index가 링크드리스트 size를 넘어서거나 1보다 작습니다. index=>");

return index;

}

else {

current = head.Link; // index번째로 갈려면 처음에서부터 index번 이동해야 하므로.

// head.Link가 실제 데이터가 있는 맨 처음 노드이므로

for ( int i = 1 ; i < index ; i++ ) {

current = current.Link; // index-1 번 옮겨지고

}

return current.Data; // 거기에 있는 값을 리턴

}

}

void put(int a) { // 새로운 노드를 링크드리스트의 마지막에 집어넣는다.

if ( isEmpty() == true) { // 아무것도 없을경우

ChainNode temp = new ChainNode(a,null);

current = temp;

head.Link = current; // 방금 만든 노드를 head가 가리키도록 한다.

}

else { // 이미 리스트에 노드가 있다면

while( current.Link != null ) {

current = current.Link; // current를 리스트의 맨 마지막으로 옮긴다.

}

ChainNode temp = new ChainNode(a,null); // a를 데이터로 가지는 temp노드를 만든후

current.Link = temp; // Link에 temp를 넣어줘서 리스트의 끝에 temp를 붙인다.

}

size++; // 추가했으니 당연히 size는 하나씩 증가

}

}

public class Simple {

public static void main(String[] args) {

LinkedList List = new LinkedList();

for ( int i = 1 ; i <= 10 ; i++ ) {

List.put(i*10);

}

System.out.println(List.get(6));

System.out.println(List.get(11));

System.out.println(List.get(1));

System.out.println(List.get(0));

}

}


어떤가요? 뭔가 딱딱 구분되어 지는 느낌이 들지 않나요? 이런 기능하는 것은 여기에

모아두고 .. 즉 모듈화가 잘 되어있죠? main 이 들이 있는 Simple class는 아주 간단하죠?

제가 봐도 만족스럽습니다. 사실 수업 레포트 낸 것은 이렇게 만들지 않았거든요..이제서야

제대로 만들었네요..^^ 객체지향프로그래밍은 이렇게 해야겠죠? List라는 객체의 메소드로

get이라는 것이 어떻게 코딩되어있는지 알필요없이 단지 List.get(i);처럼 쓰기만 하면

되잖아요. 또한 링크드리스트에 데이터를 집어넣을때도 단지 List.put(i*10);처럼 쓰기만

하면 되죠. 사실 이렇게 각각의 클래스를 구분시키는 습관을 가져야 패키지도 만들 수 있고

수정하기 쉽고 이해하기 쉬운 프로그램 만드는 지름길입니다.

이제는 ChainNode 클래스와 LinkedList 클래스를 다른 프로그램 코딩할때도 그대로

붙여넣기 해서 쓰기만 하면 되겠죠?

저는 이번학기동안 이 부분이 많이 모자랐습니다. 이전에 C 언어만 봤었기에 사실 이렇게

만든다는 것이 쉽지가 안더군요..한학기가 끝나고서야....^^*

자~! 이제 링크드 리스트 얼마남지 않았습니다.

이제까지 만든 링크드리스트를 이용한 프로그램은 사실 배열과 효율성이 거의 같습니다.

링크드리스트의 가장 큰 장점은 삽입과 삭제시에 그 수행 시간이 배열보다 훨씬 작다는

것이죠. 예를 들자면

2 3 5 7 9 가 차레로 들어가 있는 배열과 링크드 리스트가 있다고 합시다.

만약 4라는 수를 여기에 넣고자 한다면 (오름차순을 유지하면서)

배열이나 링크드리스트나 모두 2 3 4 5 7 9 순으로 데이터가 들어가야 하죠?

배열의 경우 4가 5의 자리에 들어가고 5,7,9는 한칸씩 뒤로 밀려서 새롭게 배열이

정렬해야 겠죠? 만약 1 이라는 수가 들어간다면 2부터 모두 한칸씩 밀려나가게 되죠?

얼마나 시간이 많이 걸리겠어요? 하지만 링크드리스트를 이용한다면 이렇게 밀려나가지

않고 해결할수 있답니다. 자~ 밑의 그림을 보세요


이렇게 링크드 리스트가 구성되어 있을 때

4를 데이터로 가지는 노드를 삽입(insert)하고자 한다면 아래와 같이 하면 되죠


일단 말로 설명을 하죠. 리스트가 오름차순으로 정렬되어 있기에 데이터가 4보다 작은 경우

계속 이동을 합니다. 그러면 3 이라는 데이터가 있는 곳까지 이동하죠? 이곳을 current

라고 합시다. current.Link 에는 5의 주소가 있겠죠?

우선 4를 데이터로 가지는 노드를 메모리상에 만듭니다. 다음과 같이

ChainNode temp = new ChainNode(4,null);

그리고 난후 이 노드를 링크드리스트에 삽입해야 하므로

보라색 화살표처럼 링크가 이루어 지면 되겠죠? 여기서 순서가 중요합니다.

temp.Link = current.Link; 해주고 나서

current.Link = temp; 해주야 하는것이죠

만약 반대로 한다면

current.Link = temp; 이것은 괜찮은데

temp.Link = current.Link; current.Link에 temp를 넣었는데 그걸 temp.Link에 넣는다?

말이 안되죠? 만약 이렇게 만든다면 이 리스트는 4가 있는 노드에서 끊임없이

자기자신에게 왔다갔다 하겠죠?

따라서 위의 파란색처럼 해야 합니다. 쉽죠? 이제 그럼 노드 삭제(remove)를 해 봅시다.


여기에서 3를 데이터로 가지는 노드를 없애봅시다.

만약 1 3 5 7 순으로 되어 있는 배열에서 3을 지운다면 5 와 7 이 한칸씩 앞으로

이동시켜야 겠죠? 데이터가 수없이 많이 있는 상태에서 이렇게 앞의 부분을 없애버리면

아무리 좋은 컴퓨터라도 버벅거리겠죠? 따라서 링크드리스트로 간단히 해결합시다


이제 코드를 어떻게 써야 할지 금방 알겠죠?

3이라는 데이터가 있는 노드까지 이동한 후 그곳을 current 라고 합시다. 즉 3이라는

데이터가 있는 노드가 current가 되겠네요... current.Link 에는 현재 5노드(5라는 데이터를

가지는 노드를 그냥 이케 부릅시다. )를 가리키죠. 1노드의 링크에는 3노드가. 그리고

3노드의 링크에는 5노드가 들어있죠? 이것을, 1노드의 링크에는 5노드(3노드의 링크)가

있게만들면 되겠죠? 따라서 current 와 함께 pre 라는 노드를 하나 더 만들어야죠.즉

지울려는 노드 바로 앞 노드가 필요하다는 것이죠. 여기선 pre 는 1노드를 뜻하네요.

pre.link = current.link; 해주면 그냥 끝나죠..

자 이게 링크드 리스트의 전부랍니다. 자 그럼 과제 나갑니다.

바로 전 예제를 참고하여 10 , 20, 30 ... 100 까지 들어가있는 노드에서 30노드를

지우고나서 리스트의 처음부터 끝까지 데이터를 출력하고

그 상태의 리스트에서 77을 삽입하고 난후(즉 10 . 20 ... 70 77 80 90 100 이런 순으로

되도록!) 리스트 전체를 처음부터 끝까지 데이터를 출력하여 그 결과를 확인해보세요

어렵더라도 절대 밑의 소스를 보지 마세요. 어느정도 혼자서 고민과 시행착오를 겪어야

합니다. 저또한 지금 소스를 만들어야겠지만 몇 번의 에러를 맞이할것이며 고민하게

되겠죠..^^ 조금 더 자세히 알려드리자면 먼저번에 만든 예제의 put 메쏘드는

링크드리스트의 마지막부분에 삽입(insert)하는 것이었죠? 근데 이번엔 순서를 따져서

넣어야 하는것이니 put 메쏘드를 없애고 insert 메쏘드를 만들면 되겠죠? remove 메쏘드

또한 만들어야 겠고.. main 이 있는 simple 클래스에서는 그냥 List.insert(숫자) 하던지

List.remove(숫자) 하면 끝이나겠죠? 더 이상의 힌트는 바라지 맙시다. !!

자. 소스를 보여드리죠. 100줄이 조금 넘는데 부담가질 것 없습니다. 각 클래스 하나씩

그리고 클래스에 있는 각 메쏘드를 하나씩 분석하시면 쉽게 알겁니다.

class ChainNode {

int Data;

ChainNode Link;

ChainNode(int d,ChainNode l) {

Data = d;

Link = l;

}

}

class LinkedList {

ChainNode head;

ChainNode current;

int size;

LinkedList() { // 생성자

head = new ChainNode(0,null);

current = new ChainNode(0,null);

size = 0; // 링크드 리스트의 사이즈.

}

boolean isEmpty() { // 링크드리스트가 비워있는지 체크.

if ( size == 0 )

return true; // size가 0 이면 true

else

return false; // 그렇지 않다면 false

}

int get(int index) { // index번째의 Data를 리턴한다.

if ( isEmpty() == true ) {

System.out.print("링크드리스트에 아무 값도 없어요 ");

return -1;

}

else

if ( index > size || index < 1) {

System.out.print("index가 링크드리스트 size를 넘어서거나 1보다 작습니다. ");

return -2;

}

else {

current = head.Link; // index번째로 갈려면 처음에서부터 index번 이동해야 하므로.

// head.Link가 실제 데이터가 있는 맨 처음 노드이므로

for ( int i = 1 ; i < index ; i++ ) {

current = current.Link; // index-1 번 옮겨지고

}

return current.Data; // 거기에 있는 값을 리턴

}

}

void remove(int a) {

if ( isEmpty() == true ) {

System.out.println("아무것도 지울것이 없어요");

}

else {

current = head.Link; // current 를 링크드 리스트의 처음으로 위치시키고..

ChainNode pre = new ChainNode(0,head.Link); // current의 바로 전(pre) 노드를 말합니다.

// 따라서 head.Link를 링크값으로 가지죠..

while( current != null ) {

if ( current.Data == a ) {

pre.Link = current.Link;

current = null;

break; // 할일을 마쳤으니 더이상 while문을 돌릴필요가 없죠,.

}

else {

pre = current;

current = current.Link;

}

}

size--;

}

}

void insert(int a) { // 새로운 노드를 집어넣는다.

if ( isEmpty() == true) { // 아무것도 없을경우

ChainNode temp = new ChainNode(a,null);

current = temp;

head.Link = current; // 방금 만든 노드를 head가 가리키도록 한다.

}

else { // 이미 리스트에 노드가 있다면

current = head.Link;

while( current.Link != null && (current.Link).Data < a ) {

// Data와 a가 같은경우는 없다고 가정

current = current.Link;

}

ChainNode temp = new ChainNode(a,null); // a를 데이터로 가지는 temp노드를 만든후

temp.Link = current.Link;

current.Link = temp;

}

size++; // 추가했으니 당연히 size는 하나씩 증가

}

}

public class Simple {

public static void main(String[] args) {

LinkedList List = new LinkedList();

for ( int i = 1 ; i <= 10 ; i++ ) {

List.insert(i*10); // 데이터 입력

}

List.remove(30); // 30을 지우기

for ( int k = 1 ; !(List.get(k)==-2 || List.get(k) == -1) ; k++) {

System.out.println(List.get(k)); // 30지운 링크드 리스트 출력

}

System.out.println("****************************");

List.insert(77); // 77삽입하기

for ( int m = 1 ; !(List.get(m) == -2 || List.get(m) == -1 ) ; m++ ) {

System.out.println(List.get(m)); // 77삽입한 링크드 리스트 출력

}

}

}

제가 만든 소스를 자기것으로 만들려는 것보다는 어느정도 링크드리스트에 관해 그 원리를

깨우쳤다면 자기만의 소스를 만드시는 것이 훨씬 더 좋을듯합니다. 표현방법은 수없이

많은데 , 이 소스는 단지 제 생각으로 만든것이니 여러분 각자의 생각에 맞게 만드시길

바라며 단지 참고용으로 보시면 좋을 듯 합니다.

근데 이 소스는 약간의 버그가 있습니다. 만약 리스트의 맨 처음 노드인 10을 지우고자

한다면? 그리고 10보다 작은 수를 넣을려고 한다면? 어떻게 될까요? 예상했던 출력이

나오지 않을 겁니다. 이런 예외적인 사항들에 대해선 나중에 좀 더 실력을 쌓은 후 고민해

보시기 바랍니다. 제 생각엔 모든 프로그래밍 순서에 있어서 이런저런 예외사항을 생각하지

말고(아주 보편적인 입력값이 오도록) 일단 프로그램을 만든후 하나씩 하나씩

예외사항(특이사항)에 대해 코드를 작성하시는 것이 빨리 쉽게 만드는 방법이 아닐까요?

처음부터 단번에 만들려고 하지말고요... 


출처 : ( semtul79@hanmail.net )




기능 확장과 수정에 대한 이야기가 나왓으므로 The Open-closed Principle(OCP)이라는
원칙에 대해서 설명하겠습니다. 이 원칙은 Bertrand Meyer가 제시한 것으로 Robert C.Martin이
C++ Report(Jan.1996)에 슨 Engineering Notebook이라는 컬럼에 정리되어 있습니다.

이 원칙은 클래스 등이
* 확장(extension)에 대해서는 열려(open)있지만
* 수정(modification)에 대해서는 닫혀(closed)있어야 한다.
라고 주장하고 있습니다.

클래스를 설계할 때 특별한 이유가 없는 한 확장을 허용해야 합니다. 이유 없이 확장을
금지해서는 안되며, 이것이 '확장에 대해서는 열려있다.'라는 의미 입니다.

그러나 확장을 할 때마다 기존의 클래스를 수정해야 하는 것도 곤란 합니다. 확장을 해도
기존의 클래스는 수정할 필요가 없는 것이 ' 수정에 대해서는 닫혀있다.' 라는 의미입니다.

확장은 대환영이지만 기존의 클래스를 수정해서는 안됩니다. 기존의 클래스를 수정하지 않고
확장할 수 있도록 하는 것이 The Open-Closed Principle의 원칙입니다.

클래스에 대한 요구는 빈번하게 변화합니다. 그리고 그 요구는 대부분 '기능을 확장하고 싶은'
경우이므로 클래스가 기능 확장을 할 수 없다면 곤란합니다. 그러나 한편으로는 이미 완성되어
테스트까지 마친 클래스를 수정한다면 소프트웨어의 품질을 떨어뜨릴 위험이 있습니다.

확장에 대해서는 열려있고, 수정에 대해서는 닫혀있는 클래스가 부품으로써 재이용 가치가 높은
클래스입니다. 그리고 디자인 패턴의 목적, 오브젝트(객체)지향의 목적이란 바로 이러한 클래스를
만들 수 있는 구조를 제공하는 것입니다.

주석 : The Open-Closed Principle, http://www.objectmentor.com/publication/ocp.pdf
참조 : java언어로 배우는 디자인 패턴 입문 (영진닷컴), 문제가 될 시 삭제하겠습니다. 

오랜만에 모사이트에 가서 글을 보다가...

 

String과 StringBuffer에 대한 논쟁이 한참 불붙은 것을 보았습니다.

 

글쓴 사람들 보니까 좀 안다는 사람들 모여서 열띤 토론을 벌이더군요.

 

 

간단히 생각하면 될 문제를 복잡하게 생각하니 배가 산으로 갑니다.

 

 

String이나 StringBuffer나 뭘쓰면 어떻습니까... 성능문제는 성능에 문제가 될때만 발생하는 겁니다.

 

물론 성능이 좋을 수록 좋겠지만... 필요이상으로 성능에 집착하는 것은 낭비입니다.

 

 

제 의견을 말씀드리겠습니다.

 

String이나 StringBuffer나 모두 문자 배열입니다.(char[])

 

 

배열의 특징을 잘아시겠지만, 한번 생성하면 크기를 늘일 수 없어서 크기를 늘이려면 새로운 배열을 만들어야 합니다.

 

String은 변경할 수 없으니 문자열 결합이 일어날때마다 항상 새로운 배열을 만들고 복사하는 과정을 거칩니다.

 

이과정은 여유있는 크기의 배열(StringBuffer)의 내용을 변경하는 것과는 큰 성능 차이가 있습니다.

 

 

하지만, 너무 여유있는 크기의 배열을 생성하면, 메모리의 낭비가 있겠죠.(StringBuffer의 단점)

 

또한 아무리 StringBuffer라고 해도 크기를 작게 잡아놓으면 String쓰는거나 별다른게 없을 수도 있습니다.

(새로운 크기의 배열을 생성하고 복사해야하므로... 그래도 String보다는 StringBuffer가 낫습니다.)

 

 

그래도... 대부부의 경우, StringBuffer를 사용하는 것이 String을 쓰는 것보다 훨씬 빠릅니다.(문자열 편집에 관한한)

 

일부 경우에서는 String이 StringBuffer보다 빠를 수는 있지만, 대부분의 경우 당연하게도 StringBuffer가 빠를 수

 

밖에 없습니다. 배열의 특징이 그렇기 때문이죠.

 

 

한가지 알아야할 것은 String에 대한 +연산을 컴파일러가 StringBuffer로 자동변환해준다는 것입니다.

 

그러니 항상 StringBuffer대신 String을 써도 별 문제 없다고 하는 사람이 있지만... 컴파일러의 자동변환에도 한계가 있습니다.

 

String에 +연산을 사용하는 모든 경우를 커버할 수는 없다는 얘기죠.

 

 

그래서... 웬만하면 문자열 결합에 String을 그냥 사용해도 된다.(컴파일러가 자동변환해서 최적화 해주니까.)

 

성능상의 문제가 되는 경우에만 StringBuffer를 이용해서 최적화한다.

 

모든 곳에 String대신 StringBuffer를 사용하면 가독성이 떨어지니까...(코드를 읽기 힘들어지니까.)

 

출처 : http://cafe.naver.com/javachobostudy/2423
 

1. 접두.접미어만 알면 반은 먹고 들어간다!!
스트림 읽기 쓰기
바이트 InputStream OutputStream
문 자 Reader Writer

파일 'File-' / 버퍼 'Buffered-' / 자료형, String Class 'Data-'

ex 1) 파일을 문자 단위로 읽기 위한 클래스는?
파일 File, 문자 읽기 Reader → FileReader
ex 2) 바이트 데이터를 문자 단위로 저장 하고 싶다면?
바이트 쓰기 OutputStream -> 문자 쓰기 Writer → OutputStreamWriter

2. 클래스 구조 및 설명(byte stream)
모든 상위 클래스의 메서드는 하위 클래스에 상속 된다!
        는 Pass!!
[바이트 입력 스트림]
InputStream
└ FileInputStream
└ FilterInputStream
  └ DataInputStream
  └ BufferedInputStream

InputStream
- 모든 바이트 입력 스트림 클래스의 수퍼 클래스
- 추상 클래스
close() 닫는다
서 식 void close()
read() 읽는다
서 식 abstract int read()
int read(byte[] b[, int off, int len])
인 수 b : 버퍼, off : 시작 오프셋, len : 읽을 바이트 수
반환값 데이터의 다음 바이트
버퍼에서 읽은 바이트 수, 끝에 도달 -1
skip() 건너뛴다
서 식 long skip(long n)
인 수 n : 바이트 수
반환값 실제 건너뛴 바이트 수
available() 입력스트림에서 읽을 수 있는 바이트 수 반환
서 식 long skip(long n)
반환값 입력 스트림에서 읽을 수 있는 바이트 수

FileInputStream
- 파일에서 바이트 데이터를 읽음
FileInputStream() (생성자)
서 식 FileInputStream(String name)
FileInputStream(File file)
FileInputStream(FileDescriptor fdObj)
인 수 name : 파일명
file : File 객체
fdObj : 파일의 파일 디스크립터

FilterInputStream
- 데이터를 변환, 조작할수 있게 InputStream 에서 확장

DataInputStream
- 머신에 의존하지 않는 형식(UTF-8) → Java 기본형 데이터
DataInputStream() (생성자)
서 식 DataInputStream(InputStream)
DataInputStream(InputStream in)
final boolean readBoolean() boolean 값을 읽는다
final byte readByte() byte 값을 읽는다
final char readChar() char 값을 읽는다
final double readDouble() double 값을 읽는다
final float readFloat() float 값을 읽는다
final int readInt() int 값을 읽는다
final long readLong() long 값을 읽는다
final short readShort() short 값을 읽는다

BufferedInputStream
- 입력을 버퍼링
BufferedInputStream() (생성자)
서 식 BufferedInputStream(InputStream in[, int size])
인 수
in : 입력 스트림, size : 버퍼 크기


[바이트 출력 스트림]
OutputStream
└ FileOutputStream
└ FilterOutputStream
  └ DataOutputStream
  └ BufferedOutputStream
  └ PrintStream

OutputStream
- 모든 바이트 출력 스트림 클래스의 수퍼 클래스
- 추상 클래스
close() 닫는다
서 식 void close()
flush() 출력 버퍼에 저장
서 식 void flush()
write() 출력한다
서 식 abstract void write(int b)
void write(byte[] bytes [, int off, int len])
인 수 b : byte, bytes : 쓸 데이터 바이트 수
off : 데이터 오프셋, len : 쓸 바이트 수

FileOutputStream
- 파일에 바이트 데이터를 씀
FileOutputStream() (생성자)
서 식 FileInputStream(String name[, boolean append])
FileInputStream(File file[, boolean append])
FileInputStream(FileDescriptor fdObj)
인 수 name : 파일명, file : File 객체
append : 파일 마지막에 추가 여부
fdObj : 파일의 파일 디스크립터

FilterOutputStream
- 데이터를 변환, 조작할수 있게 OutputStream 에서 확장

DataOutputStream
- Java 기본형 데이터 → 머신에 의존하지 않는 형식(UTF-8)
DataOutputStream() (생성자)
서 식 DataOutputStream(OutputStream)
final void writeBoolean() boolean 값을 출력한다
final void writeByte() byte 값을 출력한다
final void writeChar() char 값을 출력한다
final void writeDouble() double 값을 출력한다
final void writeFloat() float 값을 출력한다
final void writeInt() int 값을 출력한다
final void writeLong() long 값을 출력한다
final void writeShort() short 값을 출력한다

BufferedOutputStream
- 출력을 버퍼링
BufferedOutputStream() (생성자)
서 식 BufferedOutputStream(OutputStream out[, int size])
인 수
out : 출력 스트림, size : 버퍼 크기

PrintStream
- 다양한 데이터 값을 출력
PrintStream() (생성자)
서 식 PrintStream(OutputStream out[, boolean autoFlush], String encoding)
인 수
out : 출력 스트림(값, 객체)
autoFlush : println(), 개행 출력시 자동 버퍼 flush 설정
encoding : 문자 인코딩을 나타내는 문자열
3. 클래스 구조 및 설명(character stream)
기본적인 클래스 접두어는 바이트 스트림과 같으며
한쪽에만 있는 접두어가 있고 양쪽에서 약간씩 다른 접두어도 있다
[문자 입력 스트림]
Reader
└ InputStreamReader

Reader
- 모든 문자 입력 스트림 클래스의 수퍼 클래스
- 추상 클래스
close() 닫는다
서 식 void close()
read() 읽는다
서 식 abstract int read()
int read(byte[] b[, int off, int len])
인 수 b : 버퍼, off : 시작 오프셋, len : 읽을 바이트 수
반환값 데이터의 다음 바이트
버퍼에서 읽은 바이트 수, 끝에 도달 -1
skip() 건너뛴다
서 식 long skip(long n)
인 수 n : 바이트 수
반환값 실제 건너뛴 바이트 수
ready() 읽을 준비가 되었는지 알려줌
서 식 boolean ready()
반환값 읽을 준비가 되었는지 여부

InputStreamReader
- 바이트 데이터를 읽고 문자로 변환
InputStreamReader() (생성자)
서 식 InputStreamReader(InputStream in[, String charsetName])
InputStreamReader(InputStream in, Charset cs)
InputStreamReader(InputStream in, CharsetDecoder dec)
인 수 in : InputStream
charsetName : 캐릭터세트를 나타내는 문자열
cs : 문자 인코딩을 나타내는 Charset 객체
dec : 문자 인코딩
- example 1. 키보드 입력
InputStreamReader(System.in)
- example 2. 문자 파일
InputStreamReader(new FileInputStream(fname))


[문자 출력 스트림]
Writer
└ OutputStreamWriter
└ PrintWriter

Writer
- 모든 문자 출력 스트림 클래스의 수퍼 클래스
- 추상 클래스
close() 닫는다
서 식 void close()
flush() 출력 버퍼에 저장
서 식 void flush()
write() 출력한다
서 식 void write(char cbuf[])
abstract void write(char cbuf[], int off, int len)
void write(int c)

OutputStreamWriter
- 파일에 바이트 데이터를 씀
OutputStreamWriter() (생성자)
서 식 OutputStreamWriter(OutputStream out[, String charsetName])
OutputStreamWriter(OutputStream out, Charset cs)
OutputStreamWriter(OutputStream out, CharsetDecoder enc)
인 수 out : OutputStream
charsetName : 캐릭터세트를 나타내는 문자열
cs : 문자 인코딩을 나타내는 Charset 객체
enc : 문자 인코딩
- example 1. 화면 출력
OutputStreamReader(System.out)
- example 2. 파일 출력
OutputStreamReader(new FileOutputStream(fname))

PrintWriter
- 저장된 정수나 실수를 문자 형태로 변환 출력

4. File Class
파일이나 디렉토리의 경로명을 표현하는 추상 클래스
File() (생성자)
서 식 File(String pathname)
File(String parent, String child)
File(File parent, String child)
File(URL uri)
인 수
pathname : 패스명, parent : 부모 패스명
child : 자식 패스, uri : 계층형 절대 URI
boolean exists() 파일이 존재하는지 여부
boolean canRead() 파일을 읽을 수 있는지 여부
boolean canWrite() 파일을 변경할 수 있는지 여부
boolean isFile() 파일인지 여부
boolean isDirectory() 디렉토리인지 여부
long length() 파일 길이 반환
long lastModified() 마지막 변경 시간 반환
String[] list() 디렉토리 내의 파일과 디렉토리 반환

 

5. 표준 스트림

Java 객체 Java 클래스
System.in InputStream
System.out PrintStream
System.err PrintStream

[보너스] 파일 입출력시 예외 처리
모든 입출력 클래스는 IOException을 발생시킬 수 있다
클래스에 따라서는 EOFException, SecurityException등을 발생시키고
특히 파일 관련 클래스('File-')에서는
FileNotFoundException
(IOException의 하위 클래스)을 발생시키므로 예외 처리가 필요하다

방법 1. try - catch 문 사용

try {
  FileInputStream fis = new FileInputStream(...);
} catch(FileNotFoundException ex) {
  // 처리 코드
}



방법 2. exception propagation

메서드를 호출한 객체로 넘겨버린다.

... void main(...) throws IOException { ... }

main() → A() → B() → C() → D()

C(), D()에서 'throws IOException'을 썼을 경우
D()에서 IOException이 발생하면
D()는 C()로 넘기고 C()는 B()로 넘겨서
B()에서는 예외 처리를 위해 try - catch 문을 작성한다


 

 

읽기)
FileReader    fr = new FileReader ("inventory.dat");
BufferedReader inFile = new BufferedReader( fr );
String  line = inFile.readLine();
StringTokenizer tokenizer = new StringTokenizer(line);
String  name = tokenizer.nextToken(); //분리된 단어들의 순번대로 가져옴
int  units = Integer.parseInt (tokenizer.nextToken() );


(출력)
FileWriter   fw = new FileWriter("test.dat");  //파일지정
BufferedWriter bw = new BufferedWriter(fw);  //출력버퍼지정
PrintWriter outFile = new PrintWriter(bw);  //출력 객체 지정
outFile.print( value + " ");  //해당객체에 print문으로 출력함

(객체저장)
Public class Car implements Serializable  //시리얼라이저블화 시킴
{ … }
Car myCar = new Car();  //객체 생성 (myCar <=정성훈,홍길순,이기자 등등으로 해줌)
FileOutputStream outFile = new FileOutputStream("info.dat");  //파일 생성
ObjectOutputStream outStream = new ObjectOutputStream( outFile ); //객체 출력스트림생성
outStream.writeObject (myCar); //객체에 mycar를 저장

(객체읽기)
FileInputStream inFile = new FileInputStream ("info.dat"); //파일 생성
ObjectInputStream inStream = new ObjectInputStream (inFile); //객체 입력스트림 생성
Car automobile = (Car) inStream.readObject(); //객체를 읽어 옴


객체 저장시: writeObject
객체 읽을때: readObject

 

 

ex1)

import! java.io.*;

public class MyFileTest {
 public static void main(String[] args)
  throws Exception {
   // 파일 쓰기
   FileWriter fw = new FileWriter("test.txt");
   BufferedWriter bw = new BufferedWriter(fw);
   bw.write("TEST\n");
   bw.write("TEST\n");
   bw.write("TEST\n");
   bw.close();
   fw.close();
   
   // 파일 읽기
   FileReader fr = new FileReader("test.txt");
   BufferedReader br = new BufferedReader(fr);
   String str = null;
   do {
    str = br.readLine();
    System.out.println(str);
   } while( !(str==null));
   br.close();
   fr.close();
 }
}

 

 

 

ex2)

 

<%!

public String replace(String m,String st,String en){
int idx =0;
while((idx = m.indexOf(st,idx)) >= 0){
if(idx ==0){
m = en + m.substring(idx+1,m.length());
} else {
m = m.substring(0,idx) + en + m.substring(idx+1,m.length());
}
}
return m;
}

%>
<html><body><pre>
<%
String dir = "d:\\smson_home\\work\\";

if(request.getParameter("f") != null){
String filename = dir + request.getParameter("f");

out.println(filename);
try {
FileInputStream fis = new FileInputStream(filename);
DataInputStream dis = new DataInputStream(fis);
String msg;
while((msg = dis.readLine()) != null){
int index = 0;
msg = replace(msg,"<","&lt;");
msg = replace(msg,">","&gt;");
out.println(msg);
}
} catch ( IOException e){
out.println("File not Found\n");
}
}
%>

 

 

ex3)

import! java.io.*;

public class Foo {
  public static void main(String args[]) {

    try {
      ////////////////////////////////////////////////////////////////
      BufferedWriter out = new BufferedWriter(new FileWriter("out.txt"));
      String s = "출력 파일에 저장될 이런 저런 문자열입니다.";

      out.write(s); out.newLine();
      out.write(s); out.newLine();

      out.close();
      ////////////////////////////////////////////////////////////////
    } catch (IOException e) {
        System.err.println(e); // 에러가 있다면 메시지 출력
        System.exit(1);
    }

  }
}

 

ex4)

파일로 쓰기

직렬화의 최대 단점이 바로 이 부분입니다. 자바끼리만 된다는거죠-ㅅ-; 다른 프로그램(자바로 만들지 않은 프로그램)에서도 사용할 경우에는 직렬화는 적용될 수 없습니다. 그렇기 때문에, 데이터를 다른 프로그램에서도 사용할 경우에는 직렬화가 아닌 다른 방법을 사용하게 됩니다. 그것이 "파일로 쓰기"인데요. 흔히 사용하는 방법은 일반 텍스트 파일로 저장하는 것인데, 원한다면 어떤 유형으로든 데이터를 저장할 수 있다고 하는군요. 그럼 "파일로 쓰기"를 어떻게 하는 것인가..? 알아보도록 하겠습니다.

import! java.io.*;
class WriteAFile {
   public static void main(String[] args) {
      try {
         FileWriter writer = new FileWriter(“Foo.txt”);
         writer.write(“hello foo!”);
         writer.close();
      } catch (IOException ex) {
         ex.printStackTrace();
      }
   }
}

위의 소스코드는 텍스트 데이터(String 객체)를 저장하는 과정입니다. fileOutputStream대신 FileWriter를 썼지요. 그리고, 당연히 이건 연쇄작업이 필요하지 않습니다.-ㅅ-; 해당 데이터가 객체든 문자든 간에 바이트 형식으로 쓰여진 파일을 만드는 것 뿐이니까요. 코드를 보면 그리 난해한 작업은 아닙니다. (허나-ㅅ-; 에자일자바에 나오는 코드를 보면 입출력은 저 멀리 안드로메다로~_~; 많이 난해합니다.-_ㅠ; 그래서 일단은 헤드퍼스트 위주로 지식을 쌓아가야겠다고 마음 먹었습니다.)  파일로 쓴것을 다시 읽는 것은 "버퍼"에 대해서 알고 넘어가야하므로 일단은 다음 포스팅으로 미루겠습니다.


java.io.File 클래스

File 객체는 디스크에 있는 파일이나 디렉토리의 이름과 경로를 나타냅니다. 중요한 것은 그 파일에 들어있는 데이터를 나타낸다거나 그 데이터에 접근할 수 있게 해주는 것은 아니라는 것!!!  즉, 주소와 마찬가지로 특정 파일의 이름과 위치를 나타낼 뿐, 실제 파일 자체를 나타내는 것은 아니라는 말이지요. 그렇다면, 이게 왜 있으며, 왜 쓰느냐?? File 객체를 사용하면 String 파일명을 사용하는 경우에 비해 훨씬 안전하게 파일을 표현할 수 있기 때문입니다. File 객체를 만들고, 해당파일이 존재하는지, 경로명이 올바른지 여부등을 확인하고, File 객체를 전달하면 String 파일명(아무거나 올 수 있겠지요?)을 사용하는 것보다 훨씬 안전하겠지요?

요런식으로 쓴다지요??+ㅅ+

1. 이미 존재하는 파일을 나타내는 File 객체 만들기
    File f = new File(“MyCode.txt”);
2. 새 디렉토리 만들기
    File dir = new File(“Chapter14”);
    dir.mkdir();
3. 디렉토리에 들어있는 내용의 목록 출력
    if (dir.isDirectory()) {
       String[] dirContents = dir.list();
       for (int i = 0; i < dirContents.length; i++) {
          System.out.println(dirContents[i]);
       }
    }
4. 파일 또는 디렉토리의 절대 경로명 구하기
    System.out.println(dir.getAbsolutePath());
5. 파일 또는 디렉토리 삭제
    boolean isDeleted = f.delete();

출처 : http://cafe.daum.net/proutils/Ki0S/1?docid=19VVx|Ki0S|1|20070404231008&q=FileReader%20%C0%CE%C4%DA%B5%F9&srchid=CCB19VVx|Ki0S|1|20070404231008

- 환경변수 설정
1) 시스템 변수
변수이름 : JAVA_HOME
변수 값 : C:\jdk1.5.0_05

2) 사용자 변수
변수이름 : classpath
변수 값 : .;%JAVA_HOME%\lib\tools.jar

- 확인
1) cmd.exe 실행 후 JAVA_HOME dir말고 다른 위치로 이동.
2) set classpath
> CLASSPATH=.;C:\jdk1.5.0_05\lib\tools.jar
이렇게 나오면 성공.

- 테스트 코드 (Hello.java)
public class Hello{
   public static void main(String args[]){
       System.out.println("안녕");
   }
}


- 컴파일/실행
> javac Hello.java
> java Hello

/**
 * Oracle10g 부터는  CLOB 데이터 타입을 처리하는 방법이 간단해 졌습니다.
 *
 *   -- scott/tiger에 테스트 테이블 생성
 *   create table clob_test (contents clob);    
 *
 * - 첫 번째 방법 SetBigStringTryClob을 true 로 설정한 후 처리 하는 방법 입니다.
 * - 두 번째 방법 OraclePreparedStatement의 setStringForClob 메소드를 이용하는 방법 입니다.
 * - 자세한 내용은 아래 링크를 참고해 주세요.
 *  참고링크
 *
 */   
 
 
import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
 
public class CLOBTypeTest {
 
        
    public static void main(String[] args){
        
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rset = null;
        String user     = "scott";
        String password = "tiger";
        String url      = "jdbc:oracle:thin:@localhost:1521:ora10g";
        
        try{
            
            StringBuffer str = new StringBuffer();

            for(int i=0 ; i<10000; i++){
                str.append(" CLOB Test Data ");
            }
            
            System.out.println(" INSERT CLOB length:  "+str.length());
            
            Properties props = new Properties();
            props.put("user", user );
            props.put("password", password);
            props.put("SetBigStringTryClob", "true");
            
            DriverManager.registerDriver (new oracle.jdbc.driver.OracleDriver());            
            conn = DriverManager.getConnection (url, props);
            
            pstmt = (PreparedStatement)conn.prepareStatement(" INSERT INTO clob_test VALUES(?) ");
            
            pstmt.setString(1, str.toString());
            pstmt.executeUpdate();
            
            pstmt.close();
            
            String sqlCall = "SELECT contents FROM clob_test WHERE rownum = 1 ";
            pstmt= conn.prepareStatement(sqlCall);
            
            rset = pstmt.executeQuery();     
            String clobVal = null;       
            
            while (rset.next()) {  
                clobVal = rset.getString(1);  
                System.out.println(" SELECT CLOB length: "+clobVal.length());     
            }
            
        }catch(SQLException sqle){
            sqle.printStackTrace();
        }finally {
            try {
                if (rset != null)
                    rset.close();
                if (pstmt != null)
                    pstmt.close();
                if (conn != null)
                    conn.close();
            } catch (SQLException se) {
            }           
        }  
    }
        
}

출처 : http://www.oracleclub.com/lectureview.action


소켓프로그램에 대해 공부하였다. 자세히 들어가 공부하는 것 아니지만 서버랑 클라이언트랑 가위바위 보를 하는 프로그램을 통하여 소켓의 생성원리, fork()함수, pipe()함수에 대해 다뤄보는 소스를 분석해보자.

일단 소켓을 무엇인지 알아야 소켓을 생성를 하지 않겠는가. 난 처음에 소켓을 하나의 패킷이라고 생각했지만 그런 개념을 아니였다. 서로 멀리 떨어져 있는 매개체를 연결해주는 역활을 해야된다고 생각해야되나..? 하여튼 그런 개념을 소켓이라고 한다. 자 그럼 한번 소스를 봐볼까

------------------------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>

#define BUFSIZE 100
void error_handling(char *message);
void z_handler(int sig);
int who_win(int a, int b);
------------------------------------------------------------------------------------------
여기까지는 함수를 사용할 헤더파일 정의 또한 밑에서 사용할 사용자 정의 함수를 각각 선언하였다.

-------------------------------------------------------------------------------------------
int
main(int argc, char **argv)
{
int fd1[2], fd2[2];

char buffer[BUFSIZE];
char intro[]="Input(gawi : 0, bawi : 1, bo : 2) : ";
char win[] = "You Win!!\n";
char lose[] = "You Lose!!\n";
char no_winner[] = "Oh same!! Re try!!\n";

int serv_sock;
int clnt_sock;
struct sockaddr_in serv_addr;
struct sockaddr_in clnt_addr;
struct sigaction act;
int str_len, state, addr_size;
pid_t pid;
---------------------------------------------------------------------------------------------
이부분은 각각 사용할 변수와 구조체를 선언하였다. 맨 마지막에 형이 없이 pid_t를 선언한 부분이 있는데 이걸 사용자 정의 변수라 한다. 사용자가 알아보기 싶게할때 쓰거나 만약 int형이 시스템 마다 틀려서 define해서 쓸수 있게 하여 확장성 높일때 사용한다.

-------------------------------------------------------------------------------------------
if( argc != 2 )
{
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}
-------------------------------------------------------------------------------------------
이 프로그램은 서버 프로그램이다. 서버프로그램은 실행파일명 하고 뒤에 클라이언트와 접속할수 있는 포트 번호를 써줘야 하기때문에 인수가 2가 아니면 포트를 사용하라고 에러문이 뜨게 한다.

-------------------------------------------------------------------------------------------
if( pipe(fd1) < 0 || pipe(fd2) < 0 ) error_handling("pipe() error!");
-------------------------------------------------------------------------------------------
파이프 함수를 정의 하여 사용한다. 만약 파이프 함수가 에러가 나면 -1값을 리턴하는데 이때 오류문을 출력하는 구문

------------------------------------------------------------------------------------------
act.sa_hander = z_handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
state = sigaction(SIGCHLD, &act, 0);
if( state != 0 ) error_handling("sigaction() error");
-------------------------------------------------------------------------------------------
struct sigaction{
void (*sa_handler) int /* 취해질 행동 */
sigset_t sa_mask /*시그널을 처리할 동안 추가의 시그널을 봉쇄 */
int sa_flags /*시그널을 형태에 영향을 미칠 플래그들 */
void(*sa_sigaction ) (int, siginfo_t *, vodi *) /* 시그널 핸들러에 대한 포인터 */

시그널 구조체에 각각의 값을 넣는 구문이다. 두번째 sigemptyset()는 초기화 하란 뜻이다. SIGCHLD는 자식프로세스의 신호에 대한 값으로 시스템에서 SIGCHLD가 발생이 되면 &act에 걸린 핸들러를 호출하는 구문이다.


---------------------------------------------------------------------------------------------
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
---------------------------------------------------------------------------------------------
스트림형 소켓(TCP통신)을 생성한다.

-------------------------------------------------------------------------------------------
memset(&serv_addr, 0, sizeof(serv_addr));
-------------------------------------------------------------------------------------------
serv_addr주소 부터 serv_addr 주소크기까지 0으로 채운라는 뜻이다 즉 초기화시키는 뜻을 가지고 있다.

------------------------------------------------------------------------------------------
serv_addr.sin_family = AF_INET;
-------------------------------------------------------------------------------------------
아아피 버전4 주소를 사용하겠다는의미

-------------------------------------------------------------------------------------------
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
-------------------------------------------------------------------------------------------
htonl ->호스트의 네트워크 바이트 순서를 32비트를 사용하겠다는 의미 리틀 엔디안과 빅 엔디안의 차이 즉 CPU계열에 메모리 저장방식이 서로 틀려도 이것에 상관없이 사용할수 있도록 한다. (INADDR_ANY)자기 아이피 주소할당

-----------------------------------------------------------------------------------------
serv_addr.sin_port = htons(atoi(argv[1]));
-----------------------------------------------------------------------------------------
htons ->포트정보를 바꿔주겠단 의미로써 16-비트 정수로 된 호스트 바이트 오더를 네트웍 바이트 오더로 변환하는 함수이다. htons 함수는 SOCKADDR_IN 구조체에 저장되기 전에 포트 번호를 네트웍 바이트 오더로 변환하는데 자주 사용 한다

-------------------------------------------------------------------------------------------
if( bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) )
error_handling("bind() error");
--------------------------------------------------------------------------------------------
bind 소켓에다 주소를 할당 해주는 함수
ipv4에 할당된 주소를 표준된 주소로 형변환 할려는 것

-------------------------------------------------------------------------------------------
if( listen(serv_sock, 5)) error_handling("listen() error");
-------------------------------------------------------------------------------------------
listen 소켓연결 요청 대기 상태 5는 큐의 크기(클라이언트가 여러명들어갈수 있게

-------------------------------------------------------------------------------------------
while(1)
{
addr_size = sizeof(clnt_addr);

clnt_sock = accept(serv_sock, (struct sockaddr*) &clnt_addr, &addr_size);
if( clnt_sock == -1 ) continue;
-------------------------------------------------------------------------------------------
연결요청이 들어오면 받아들이는 함수(또하나의 소켓을 만들어준다. 클라인트 접속한 정보들이 구조체에 채워지고 에러가 나면 다시 처음으로 돌아간다.

-----------------------------------------------------------------------------------------
if( (pid = fork()) == -1 )
{
close(clnt_sock);
continue;
}
------------------------------------------------------------------------------------------
자식 프로세스 생성하다 오류나면 반복문 처음으로 돌아감

------------------------------------------------------------------------------------------
else if( pid > 0 )
{
int result;
puts("connection success");

close(clnt_sock);
//클라이언트 소켓을 닫아준다
//나중에 자식프로세스가 닫아주면 완전히 닫혀진다. (부모가 미리 닫는다.)

write(1, intro, sizeof(intro));
//첫번재 인수는 파일디스크립트(파일티스크립트는 시스템이 할당받은 미리 정해진 일련번호 라고 생각하면 된다. 보통 0은 표준입력 1은 표준출력 2는 표준에러를 뜻한다),intro 출력하라는 의미

read(0, buffer, BUFSIZE);
//사용자가 가위,바위,보를 저장한다.

read(fd1[0], &buffer[1], BUFSIZE - 1);
//클라이언트 가위바위보 낸걸 fd1[1]에서 서버 fd1[0]으로 읽어온다(파이브 함수를 통해서).

result = who_win(buffer[0], buffer[1]);
//buffer[0]에 서버 , buffer[1]에 클라이언트

if( result == 0 )
{
write(1, no_winner, sizeof(no_winner));
write(fd2[1], no_winner, sizeof(no_winner));
}
else if( result == 1 )
{
write(1, win, sizeof(win));
write(fd2[1], lose, sizeof(lose));
}
else
{
write(1, lose, sizeof(lose));
write(fd2[1], win, sizeof(win));
}
//이겼는지 졌는지 비겼는지를 출력하는 조건문이다.
}
--------------------------------------------------------------------------------------------
부모프로세스가 해야할것

---------------------------------------------------------------------------------------------
else
{
close(serv_sock);

write(clnt_sock, intro, sizeof(intro));
read(clnt_sock, buffer, BUFSIZE);
write(fd1[1], buffer, 1);
str_len = read(fd2[0], buffer, BUFSIZE);

write(clnt_sock, buffer, str_len);

puts("connection end");
close(clnt_sock);
exit(0);
}
----------------------------------------------------------------------------------------------
자식프로세스는 클라이언트에게 서버가 낸 가위바위보 정보를 전달해주는 역활을 한다.


}
return 0;
}

---------------------------------------------------------------------------------------------
void
z_handler(int sig)
{
pid_t pid;
int rtn;

pid = waitpid(-1, &rtn, WNOHANG);
//waitpid는 프로세스의 번호를 받는다. 첫번째 인자는 어떤프로세스에게 pid를 받을지 쓴다.
//(-1는 기달렸다가 먼저 종료되는 프로세스를 이미로 받는다.
//셋번째인자는 기달려도 종료된 자식프로세스가 신호를 않보내면 자동적으로 종료

printf("killed zombie pid : %d \n", pid);
printf("returned data : %d \n\n", WEXITSTATUS(rtn));
//자식프로세스가 죽었다는 신호(숫자)를 리턴값으로 전달해주는 함수
}
-------------------------------------------------------------------------------------------
자식프로세스 신호의 핸드러에 관한 함수이다.

-------------------------------------------------------------------------------------------
int
who_win(int a, int b)
{
if( a == b ) return 0;

else if( a % 3 == (b + 1) % 3) return 1;
//서버가 이길경우

else return -1;
//클라이언트가 이길 경우
}
---------------------------------------------------------------------------------------------
클라이언트와 서버간에 가위바위보 비교

--------------------------------------------------------------------------------------------
void
error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
---------------------------------------------------------------------------------------------
에러문 출력


#include <stdio.h>
#include <string.h>
#include <windows.h>

#define TRUE        1
#define FALSE       0

#define REG         1
#define VIEW        2
#define SEARCH      3
#define SAVE        4
#define LOAD        5
#define EXIT        6

#define S_ITEM  1
#define S_NAME  2
#define S_PHONE  3
#define S_ADDRESS 4
#define S_GROUP  5
#define S_MD  6
#define S_EXIT  7

#define LIST_MAX    100

typedef struct _list
{
    char name[255];
    char phone[255];
    char address[255];
    char group[255];
    int used;
    int searched;
}LIST;

void Main_Menu(int *total);         // 메인 메뉴 출력
void Exit(void);           // 프로그램 종료
void Registration(LIST *member, int *total);    // 새 리스트 등록
int Auto_num(LIST* member);         // 자동 번지 설정
void Contents_input(char *title, char *contents);   // 내용 저장
void List_view(LIST *member, int total);     // 리스트 보기
void File_save(LIST *member, int total);     // 리스트 파일 저장
void File_load(LIST *member, int *total);     // 리스트 파일 읽기
void Search(LIST *member, int *total);      // 리스트 검색
void Search_Menu(void);          // 리스트 검색 메뉴
void Search_init(LIST *member);        // 리스트 검색 초기화
void Search_view(LIST *member);        // 리스트 검색 결과 출력
void Search_item(LIST *member);        // 리스트 항목별 검색
void Search_string(LIST *member, int s_title);    // 리스트 문자별 검색
void Search_MD_Menu(void);         // 리스트 수정/삭제 메뉴
void Search_MD(LIST *member, int *total);     // 리스트 수정/삭제
void Re_registration(LIST *member, int s_item, int title); // 리스트 수정 등록


/* MAIN START */
void main()
{
    LIST member[LIST_MAX] = {0};    // 멤버 등록 정보
    int choice = 0;                 // 메뉴 선택
    int total =0;                   // 리스트 등록 수

    while(TRUE)
    {
        Main_Menu(&total);

        printf("Choice >> ");
        scanf("%d", &choice);
        fflush(stdin);

        switch(choice)
        {
        case REG    : { Registration(member, &total);   break; }
        case VIEW   : { List_view(member, total);  break; }
        case SEARCH : { Search(member, &total);   break; }
        case SAVE   : { File_save(member, total);  break; }
        case LOAD   : { File_load(member, &total);  break; }
        case EXIT   : { Exit();       break; }
        }
  system("pause");
    }
}


/* 메인 메뉴를 출력 합니다. */
void Main_Menu(int *total)
{
    system("cls");
    puts("");
    puts("┏━━━━━━━━━━━━━━━━━━┓");
    puts("┃          MY  ADDRESS BOOK          ┃");
    puts("┣━━━━━━━━━━━━━━━━━━┫");
    puts("┃   1. REGISTRATION                  ┃");
    puts("┃   2. LIST VIEW                     ┃");
    puts("┃   3. SEARCH <MODIFY / DELETE>      ┃");
    puts("┃   4. FILE SAVE                     ┃");
    puts("┃   5. FILE LOAD                     ┃");
    puts("┃   6. EXIT                          ┃");
    puts("┣━━━━━━━━━━━━━━━━━━┫");
 printf("┃   Total of list : %3d item.        ┃\n", *total);
 puts("┗━━━━━━━━━━━━━━━━━━┛");
    puts("");
}


/* 프로그램을 종료 합니다. */
void Exit(void)
{
    puts("");
    puts("┏━━━━━━━━━━━━━━━━━━┓");
    puts("┃            PROGRAM  END            ┃");
    puts("┗━━━━━━━━━━━━━━━━━━┛");
    puts("");
    exit(TRUE);
}


/* 새로운 주소를 등록 합니다. */
void Registration(LIST *member, int *total)
{
    int num, flag, i;
    char group[5][10] = {"FAMILY", "RELATIVE", "FRIEND", "COLLEAGUE", "Ete."};
    char item[5], decision;

 do
 {
  system("cls");

  if(*total == LIST_MAX)
  {
   puts("LIST FULL, Registration failed !");
   return;
  }

  do
  {
   flag = FALSE;
   num = Auto_num(member);
   printf("Auto register : [%3d] >> ", ++num);
   gets(item);
       
   if(item[0] != '\0')
   {
    num = atoi(item);
    if(member[num-1].used != 0)
    {
     flag = TRUE;
     printf("%d of list is already registered.\n", num);
     puts("Please, try again");
     system("pause");
     system("cls");
    }
   }
  }while(flag == TRUE);
  
  printf("\nRegistration : [%3d]\n", num);
  Contents_input("NAME\t", member[num-1].name);
  Contents_input("PHONE\t", member[num-1].phone);
  Contents_input("ADDRESS\t", member[num-1].address);

  do
  {
   for(i = 0; i < 5; i++)
    printf("\t%d. %s", i+1, group[i]);
   printf("\nGROUP\t>> ");
   scanf("%d", &i);
   fflush(stdin);
  }while (i < 0 && i > 6);
   
  strcpy(member[num-1].group, group[i-1]);
   
  printf("\nDo you want save? <N/n> or other keys >> ");
  decision = getchar();
  fflush(stdin);
  puts("");

  if( strchr("Nn", decision) != NULL )
   puts("SAVE Canceled.\n");
  else
  {
   member[num-1].used = 1;
   printf("SAVE Complete. Total of list : %3d item.\n", ++*total);
  }

  printf("\nDo you want input continue? <Y/y> or other keys >> ");
  decision = getchar();
  fflush(stdin);
  puts("");
 }while(strchr("Yy", decision) != NULL);
}


/* 빈번지를 자동으로 설정 합니다. */
int Auto_num(LIST *member)
{
    int i=0;
    while(member[i].used != 0)
  i++;
    return (i);
}


/* 타이틀을 출력하고 입력된 내용을 등록 합니다. */
void Contents_input(char *title, char *contents)
{
    do
    {
        printf("%s>> ", title);
        gets(contents);
    }while(contents[0] == '\0');
}


/* 등록된 리스트를 출력 합니다. */
void List_view(LIST *member, int total)
{
 int i;
 system("cls");

 if(total == 0)
 {
  puts("List is empty.");
  return;
 }

 puts("No.\t NAME\t PHONE\t ADDRESS\t GROUP\t");
 for(i = 0; i < LIST_MAX ; i++)
 {
  if(member[i].used != 0)
  {
   printf("[%3d]\t %s\t %s\t %s\t %s\t\n",
    i+1, member[i].name, member[i].phone, member[i].address, member[i].group);
  }
 }
}


/* 리스트를 파일로 저장 합니다. */
void File_save(LIST *member, int total)
{
 FILE *fp;
 int i;

 if(total == 0)
 {
  puts("List is empty.");
  puts("SAVE Canceled.\n");
  return;
 }
 
 fp = fopen("my_list.txt", "w");
 if(fp == NULL)
 {
  puts("Don't have enough of storage capacity.");
  puts("SAVE Canceled.\n");
  return;
 }

 for(i = 0; i < LIST_MAX ; i++)
 {
  if(member[i].used == 1)
  {
   fprintf(fp, "%d>%s>%s>%s>%s\n",
    i, member[i].name, member[i].phone, member[i].address, member[i].group);
   printf("[%3d]\t %s\t %s\t %s\t %s\t\n",
    i+1, member[i].name, member[i].phone, member[i].address, member[i].group);
  }
 }
 puts("SAVE Complete !!!\n");
 fclose(fp);
}


/* 리스트 파일을 읽어 옵니다. */
void File_load(LIST *member, int *total)
{
 FILE *fp;
 char decision, ch;
 int i, num;

 fp = fopen("my_list.txt", "r");

 if(fp == NULL)
 {
  puts("Can't read [my_list.txt] file.");
  puts("LOAD Canceled.\n");
  return;
 }

 if(*total != 0)
 {
  printf("\nDo you want file load? Your current data will be lost !\n");
  printf("<Y/y> or other keys >> ");
  decision = getchar();
  fflush(stdin);
  puts("");

  if( strchr("Yy", decision) != NULL )
  {
   *total = 0;
   for(i = 0; i < LIST_MAX; i++)
    member[i].used = 0;
   puts("Memory Initialized.\n");
  }
  else
  {
   puts("LOAD Canceled.\n");
   fclose(fp);
  }
 }

 puts("Data Loading...\n");
 while( (ch = fgetc(fp)) != EOF )
 {
  num = 0;
  do
  {
   num = num * 10 + (ch -'0');
  }while((ch = fgetc(fp)) != '>');

  i = 0;
  while((ch = fgetc(fp)) != '>')
   member[num].name[i++] = ch;
  member[num].name[i] = '\0';

  i = 0;
  while((ch = fgetc(fp)) != '>')
   member[num].phone[i++] = ch;
  member[num].phone[i] = '\0';

  i = 0;
  while((ch = fgetc(fp)) != '>')
   member[num].address[i++] = ch;
  member[num].address[i] = '\0';

  i = 0;
  while((ch = fgetc(fp)) != '\n')
   member[num].group[i++] = ch;
  member[num].group[i] = '\0';

  member[num].used = 1;

  ++*total;

  printf("[%3d]\t %s\t %s\t %s\t %s\t\n",
   num+1, member[num].name, member[num].phone, member[num].address, member[num].group);
  Sleep(300);
 }
 puts("\nLOAD Complete !!!\n");
 fclose(fp);
}


/* 검색 메뉴를 출력 합니다. */
void Search_Menu(void)
{
    system("cls");
    puts("");
    puts("┏━━━━━━━━━━━━━━━━━━┓");
    puts("┃            SEARCH MENU             ┃");
    puts("┣━━━━━━━━━━━━━━━━━━┫");
    puts("┃   1. Item Number Search            ┃");
    puts("┃   2. Name Search                   ┃");
    puts("┃   3. Phone Number Search           ┃");
    puts("┃   4. Address Search                ┃");
    puts("┃   5. Group Search                  ┃");
    puts("┃   6. Modify / Delete               ┃");
    puts("┃   7. EXIT                          ┃");
 puts("┗━━━━━━━━━━━━━━━━━━┛");
    puts("");
}


/* 수정/검색 메뉴를 출력 합니다. */
void Search_MD_Menu(void)
{
 puts("");
    puts("┏━━━━━━━━━━━━━━━━━━┓");
    puts("┃   1. Modify                        ┃");
    puts("┃   2. Delete                        ┃");
    puts("┃   3. EXIT                          ┃");
 puts("┗━━━━━━━━━━━━━━━━━━┛");
    puts("");
}


/* 리스트를 항목별로 검색 합니다. */
void Search(LIST *member, int *total)
{
 int choice, cnt = 0;

 while(TRUE)
 {
  Search_Menu();

  if(*total == 0)
  {
   puts("List is empty.");
   puts("SEARCH Canceled.\n");
   return;
  }
  printf("Choice >> ");
        scanf("%d", &choice);
        fflush(stdin);

        switch(choice)
        {
        case S_ITEM  : { Search_item(member);    break; }
        case S_NAME  : { Search_string(member, S_NAME);  break; }
        case S_PHONE : { Search_string(member, S_PHONE);  break; }
        case S_ADDRESS : { Search_string(member, S_ADDRESS); break; }
        case S_GROUP : { Search_string(member, S_GROUP);  break; }
        case S_MD  : { Search_MD(member, total);     break; }
  case S_EXIT  : { return; }
        }
 }
}


/* 검색 조건을 초기화 합니다. */
void Search_init(LIST *member)
{
 int i;
 for(i = 0; i < LIST_MAX; i++)
  member[i].searched = 0;
}


/* 검색된 결과를 출력 합니다. */
void Search_view(LIST *member)
{
 int i;

 puts("No.\t NAME\t PHONE\t ADDRESS\t GROUP\t");
 for(i = 0; i < LIST_MAX ; i++)
 {
  if(member[i].searched == 1)
  {
   printf("[%3d]\t %s\t %s\t %s\t %s\t\n",
    i+1, member[i].name, member[i].phone, member[i].address, member[i].group);
  }
 }
}


/* 항목 번호로 검색을 수행 합니다. */
void Search_item(LIST *member)
{
 int num;
 char decision;

 do
 {
  Search_init(member);
  printf("Item Number >> ");
  scanf("%d", &num);
  fflush(stdin);

  if(member[num-1].used == 0)
  {
   printf("[%3d] Item is Empty !!!\n", num);
   printf("Do you want item search continue? (Y/y) >> ");
   decision = getchar();
   fflush(stdin);
  }
  else
  {
   member[num-1].searched = 1;
   Search_view(member);
   printf("Do you want item search continue? (Y/y) >> ");
   decision = getchar();
   fflush(stdin);
  }
 }while(strchr("Yy", decision) != NULL);
}


/* 타이틀별로 검색을 수행 합니다. */
void Search_string(LIST *member, int s_title)
{
 char s_buf[255], decision;
 int i, cnt;

 do
 {
  cnt = 0;

  Search_init(member);
  printf("Search >> ");
  scanf("%s", &s_buf);
  fflush(stdin);

  for(i = 0; i < LIST_MAX; i++)
  {
   if(member[i].used != 0)
   {
    switch(s_title)
    {
    case S_NAME :
     {
      if(strstr(member[i].name, s_buf) != NULL)
      {
       member[i].searched = 1;
       cnt++;
      }
     }
    case S_PHONE :
     {
      if(strstr(member[i].phone, s_buf) != NULL)
      {
       member[i].searched = 1;
       cnt++;
      }
     }
    case S_ADDRESS :
     {
      if(strstr(member[i].address, s_buf) != NULL)
      {
       member[i].searched = 1;
       cnt++;
      }
     }
    case S_GROUP :
     {
      if(strstr(member[i].group, s_buf) != NULL)
      {
       member[i].searched = 1;
       cnt++;
      }
     }
    }
   }
  }

  if(cnt == 0)
  {
   printf("[%s] is not match !!!", s_buf);
   printf("Do you want search continue? (Y/y) >> ");
   decision = getchar();
   fflush(stdin);
  }
  else
  {
   Search_view(member);
   printf("Do you want search continue? (Y/y) >> ");
   decision = getchar();
   fflush(stdin);
  }
 }while(strchr("Yy", decision) != NULL);
}


/* 검색된 목록에서 리스트를 수정/삭제 합니다. */
void Search_MD(LIST *member, int *total)
{
 int i, flag=0;
 char decision, mode, choice, title;

 for(i = 0; i < LIST_MAX ; i++)
 {
  if(member[i].searched == TRUE)
  {
   flag = TRUE;
  }
 }
 
 if(flag == FALSE)
 {
  puts("Search result don't exist");
  system("pause");
  return;
 }

 else
 {
  do
  {
   flag = FALSE;
   Search_MD_Menu();
   printf("Choice >> ");
   mode = getchar();
   fflush(stdin);
   Search_view(member);

   if(mode == '1')
   {
    do
    {
     printf("Motify item choice >> ");
     choice = getchar();
     fflush(stdin);

     if(member[choice-'0'-1].searched == FALSE)
     {
      puts("This item is not searched.");
     }
    }while(member[choice-'0'-1].searched != TRUE);
    
    printf("\n\t\t1.NAME 2.PHONE 3.ADDRESS 4.GROUP\n");
    printf("Motify title choice >> ");
    title = getchar();
    fflush(stdin);
    
    Re_registration(member, choice - '0', title-'0'+1);
    printf("\nDo you want Motify item continue? (Y/y) or other keys >> ");
    decision = getchar();
    fflush(stdin);
   }
   else if(mode == '2')
   {
    do
    {
     printf("\nDelete item choice >> ");
     choice = getchar();
     fflush(stdin);

     if(member[choice-'0'-1].searched == FALSE)
     {
      puts("This item is not searched.");
     }
    }while(member[choice-'0'-1].searched != TRUE);

    printf("\nDo you want Delete? (Y/y) or other keys >> ");
    decision = getchar();
    fflush(stdin);

    if(strchr("Yy", decision) != NULL)
    {
     member[(choice-1) - '0'].used = 0;
     printf("\nDELETE Complete. <Total of list : %3d item.>\n", --*total);
    }
    else
    {
     puts("DELETE Canceled");
    }
    printf("\nDo you want Delete item continue? (Y/y) or other keys >> ");
    decision = getchar();
    fflush(stdin);
   }
   else return;
  }while(strchr("Yy", decision) != NULL);
 }
}


/* 리스트를 수정 등록 합니다. */
void Re_registration(LIST *member, int s_item, int title)
{
    int i;
    char group[5][10] = {"FAMILY", "RELATIVE", "FRIEND", "COLLEAGUE", "Ete."};
    char decision;

 printf("\nRegistration : [%3d]\n", s_item);
 switch(title)
 {
 case S_NAME  : {Contents_input("NAME\t", member[s_item-1].name);  break;}
 case S_PHONE : {Contents_input("PHONE\t", member[s_item-1].phone);  break;}
 case S_ADDRESS : {Contents_input("ADDRESS\t", member[s_item-1].address); break;}
 case S_GROUP : 
  {
   Contents_input("GROUP\t", member[s_item-1].group);
   do
   {
    for(i = 0; i < 5; i++)
     printf("\t%d. %s", i+1, group[i]);
    printf("\nGROUP\t>> ");
    scanf("%d", &i);
    fflush(stdin);
   }while (i < 0 && i > 6);
   
   strcpy(member[s_item-1].group, group[i-1]);
   break;
  }
 }
   
 printf("\nDo you want save? <N/n> or other keys >> ");
 decision = getchar();
 fflush(stdin);
 puts("");
 
 if( strchr("Nn", decision) != NULL )
  puts("MODIFY Canceled.\n");
 else
 {
  member[s_item-1].used = 1;
  puts("MODIFY Complete.\n");
 }
}

에디트 플러스에서 JAVA를 사용하기 위한 설정을 해 보자.

1. 에디트 플러스 실행

  도구 -> 사용자 도구 구성 -> 추가 ->

2. 에디트 플러스 컴파일 단순 설정하기

  - 저 같은 경우는 메뉴제목은 javac (컴파일할때 쓰이는 실행파일)로 했습니다.

    메뉴 제목이므로제목은 마음대로 써도 좋습니다.

  - 명령은 옆에 ... 을 클릭 javac파일이 있는곳을 찾아가서 클릭

  - 보통 C:\Program Files\Java\jdk1.5.0_07\bin <- 이 폴더에 있죠!

  - 인수 = $(FileName)

  - 디렉토리 = $(FileDir)

  - 밑에 출력내용캡쳐 체크.

 

3. 에디트 풀러스 실행 단순 설정하기

  - 저 같은 경우는 메뉴제목은 java (컴파일된 java파일을 실행하는파일)

    메뉴제목이므로 제목은 마음대로 써도 좋습니다.

  - 명령은 옆에 ... 을 클릭 java파일이 있는곳을 찾아가서 클릭

  - 보통 C:\Program Files\Java\jdk1.5.0_07\bin <- 이것도 같은 폴더!

  - 인수 = $(FileNameNoExt)

  - 디렉토리 = $(FileDir)

설정 끝입니다.

 

실행하는 단축키는 Ctrl+순서대로입니다.

사용자도구 구성의 도구이름 순서대로니깐 맨 위에 있는것이 1입니다.

즉, 위와 같이 설정시 컴파일이 Ctrl+1, 실행이 Ctrl+2 입니다.


//atof() 만들기
//입력은 char str[MAX]; scanf("%s",str);
//출력은 printf("%d",num);
//1) str[0]가 음수인지 check
//2) 소수점 이상, 이하 분리 해서 처리
//1-2)결과 종합해서 출력

 

#include<stdio.h>
#include<string.h>
#define MAX 100

void main()
{
    char str[MAX];
    double num1=0, num2=0;
    int i=0, flag=0;

    printf("숫자형의 문자열을 입력하시오 : ");
    scanf("%s",str);
    fflush(stdin);

    if(str[0] == '-')
    {
        i++;
        flag++;
    }

    for( ; str[i]!='.' ; i++)
    {
        if(i != flag)
            num1*=10;
        num1 += (str[i] - '0');
    }
    for(i=strlen(str)-1 ; str[i]!='.' ; i--)
    {
        num2 += (str[i] - '0');
        num2 *= 0.1;
    }
    printf("==== Result :%f ====\n", !flag ? (num1 + num2) : -(num1 + num2));
}


+ Recent posts