여섯번째 강의 "기본적인 프로그램 제어문"에서는 프로그램에서 다른 문장의 실행을 제어하는 C의 프로그램 제어문을 간단히 살펴보았다. 이 장에서는 지금까지 다루지 않았던 goto문과 프로그램의 순환문에서 사용할 수 있는 유용한 프로그램 제어문을 살펴보도록 하겠다. 오늘은 다음과 같은 내용을 배운다.

·break와 continue문을 사용하는 방법
·무한 루프는 무엇이고, 왜 필요한가?
·goto문을 사용하는 방법과 프로그램에서 goto문을 사용하지 않아야 하는 이유
·switch문을 사용하는 방법
·프로그램의 종료를 제어하는 방법
·프로그램이 종료될 때 자동으로 함수를 실행하는 방법
·프로그램 내에서 시스템의 명령을 실행하는 방법

1. 순환문을 미리 종료하는 방법
 : 여섯번째 강의에서는 for문, while문, do...while문이 프로그램의 실행을 제어한다는 것을 배웠다. 이런 순환문은 프로그램의 조건에 따라 C의 문장이나 블록을 전혀 실행하지 않거나 한 번 실행하거나 또는 여러 번 실행한다. 이런 세 가지 순환문에서는 주어진 조건에 일치하는 상황이 발생하는 경우에만 순환문을 종료할 수 있다. 그러나 가끔 순환문의 반복 상태를 직접 제어할 수 있기를 원할 것이다. break와 continue문은 이런 순환문의 실행을 제어할 수 있게 해준다.

1.1 break문
 : break문은 for문, while문 또는 do...while문 내에서만 사용할 수 있다. switch문에서도 사용되지만 자세한 내용은 이 장의 후반부에서 다시 설명할 것이다. 간단히 말해서 break문이 나타나면 순환문은 즉시 종료된다. 다음은 예제이다.

   for(count = 0; count < 10; count++)
   {
      if(count == 5)
         break;
   }

일반적인 경우에 for문은 10번 실행될 것이다. 그러나 여기에서는 for문이 6번째 반복되기 전에 count가 5의 값을 가지므로 break문이 실행되고 for문이 종료된다. 제어는 for문 바로 다음에 있는 문장으로 전달될 것이다. 종속된 순환문에서 break문이 사용될 때에는 가장 내부에서 사용되는 순환문이 종료된다.

<리스트 13.1> break문 사용하기

 /* break문의 사용 에 */


 #include <stdio.h>


 char s[] = "This is a test string. It contains two sentences.";


 main(0

 {

    int count;


    printf("\noriginal string: %s", s);


    for(count = 0; s[count] != '\n0'; count++)

    {

       if(s[count] == '.')

       {

          s[count = 1] = '\n';

          break;

       }

    }

    printf("\nModified string: %s\n", s);


    return 0;

 }

-> 출 력

 Original string: This is a test string. It contains two sentences.
 modified string: This is a test string.

1.2 continue문
 : break문과 마찬가지로 continue문은 for문, while문, do...while문 내에서만 사용될 수 있다. continue문이 실행되면 제어는 순환훈의 마지막 부분으로 전달되고 다음 순환 동작이 시작된다. continue문과 순환문의 마지막 부분으로 전달되고 다음 순환 동작이 시작된다. continue문과 순환문의 마지막 부분 사이에 있는 문장은 실행되지 않는다. <리스트 13.2>에는 continue를 사용하는 프로그램이 나타나 있다. 이 프로그램은 키보드에서 문자열을 받아들이고 모든 소문자 형태의 모음을 제거하여 다시 출력한다.

<리스트 13.2> continue문 사용하기

 /* continue문의 사용 예 */


 #include <stdio.h>


 main()

 {

    /* 입력용 버퍼와 카운터 변수를 선언 */


    char buffer[81];

    int ctr;


    /* 한 줄의 텍스트 입력 */


    puts("Enter a line of text: ");

    gets(buffer);


    /* 소문자 모음이 아닌 문자만을 출력하며 문자열을 차례대로 사용 */


    for(ctr = 0; buffer[ctr] != '\n0'; ctr++)

    {


       /* 문자가 소문자 모음이면 출력하지 않고 다음으로 진행 */


       if(buffer[ctr] == 'a' || buffer[ctr] == 'e' || buffer[ctr] == 'i'

         || buffer[ctr] == 'o' || buffer[ctr] == 'u')

       continue;


       /* 모음이 아니면 출력 */


       putchar(buffer[ctr]0;

    }

    return 0;

 }

2. goto문
 : goto문은 C에서 조건 없이 이동(unconditional jump)하거나 분기(branching)하는 명령의 하나이다. 프로그램에서 goto문이 나타날 때 제어는 즉시 goto문에 의해 지정된 위치로 이동하거나 분기한다. goto문이 실행될 때에는 조건 없이(unconditional) 항상 분기 동작이 발생한다. 분기 동작은 if문처럼 프로그램의 어떤 조건에 의해서 발생하는 것이 아니다. goto문과 목적지의 레이블은 서로 다른 블록에 존재할 수 있지만 동일한 함수 내에 존재하는 것이어야 한다. goto문의 사용 예를 보여주는 <리스트 13.3>의 간단한 프로그램을 살펴보자.

<리스트 13.3> goto문 사용하기

 /* goto문의 사용 예 */


 #include <stdio.h>


 main()

 {

    int n;


 start: ;


    puts("Enter a number between 0 and 10: ");

    scanf("%d", &n);


    if(n < 0 || n > 10)

    goto start;

    else if(n ==0)

    goto location0;

    else if(n == 1)

    goto location1;

    else

    goto location2;


 location0: ;

    puts("You entered 0.\n");

    goto end;


 location1: ;

    puts("You entered 1.\n");

    goto end;


 location2: ;

    puts("You entered something between 2 and 10.\n");


 end: ;

    return 0;

 }

goto문의 목적지는 프로그램 내에서 goto문의 앞이나 뒤에 위치될 수 있다. 앞에서도 언급했듯이 goto문에 대한 유일한 제한 사항은 goto문과 목적지가 같은 함수 내에 존재해야 한다는 것이다. 그러나 함수 내의 서로 다른 블록에 위치될 수 있다. for문과 같은 순환문에서 제어를 순환문의 바깥으로 이동시키기 위해서 goto를 사용할 수도 있지만 goto문을 이렇게 사용해서는 안된다. 그리고 프로그램에서는 가능하다면 goto문을 사용하지 않는 것이 좋은데, 두 가지 이유가 있다.

·goto문은 필요하지 않다. goto문을 사용해야 하는 프로그래밍 작업은 없다. goto문이 필요한 경우가 있더라도 항상 C에서 제공되는 다른 분기문을 사용하여 필요한 작업을 할 수 있다.

·goto문은 위험하다. goto문이 특별한 프로그래밍 문제에 대한 이상적인 해결 방법처럼 생각되겠지만 실제로는 전혀 그렇지 않다. goto문에 의해서 프로그램이 분기될 때에는 어디에서 분기되었는지 알 수 없으므로 프로그램이 혼란스러워질 수 있다. 이런 형태의 혼잡한 프로그래밍(spaghetti-code)은 좋지 않다. 이런 두 가지 사실을 알고 주의해서 프로그램을 작성한다면 goto문을 사용하더라도 문제가 없는 프로그램을 작성할 수 있을 것이다. 어떤 경우에는 goto문을 주의해서 사용한다면 프로그래밍 문제를 가장 간단한 방법으로 해결할 수도 있을 것이다 .그러나 goto문의 사용이 유일한 해결 방법은 아니다. 앞에서 설명한 두 가지 이유를 무시하더라도 goto문을 사용할 때에는 적어도 주의할 필요는 있을 것이다.

3. 무한 루프
 : 무한 루프는 무엇이고, 프로그램 내에서 무한 루프가 필요한 이유는 무엇일까? 무한 루프는 실행을 마치는 상황이 발생하지 않고 계속해서 반복되는 순환문이다. 무한 루프는 for, while, do...while문이 될 수 있다. 예를 들어, 다음 문장은

   while(1)
   {
      /* 다른 프로그램 문장들 */
   }

무한 루프가 될 것이다. while네 주어진 조건은 항상 참으로 평가되고 프로그램이 실행되더라도 변경되지 않는 상수이다. 1은 결코 바뀌지 않는 값이므로 순환문은 절대로 끝나지 않을 것이다. 앞에서는 순환문을 벗어나기 위해서 break문을 사용할 수 있다는 것을 설명했다. break문을 사용하지 않는다면 무한 루프의 가치는 없다. 무한 루프는 break문과 함께 사용될 때 유용하다. 또한, 다음과 같이 for나 do...while을 사용하여 무한 루프를 생성할 수 있다.

   for( ; ; )
   {
      /* 다른 프로그램 문장들 */
   }
       do
   {
      /* 다른 프로그램 문장들 */
   } while(1);

이론적으로 이런 세 가지 형태의 순환문은 같은 것이다. 여기에서는 while문을 사용할 것이다. 무한 루프는 여러 개의 조건을 확인해서 순환문을 끝내야 하는지 결정하는데 사용할 수 있다 while문의 실행 조건이 입력되는 부분에 여러 개의 조건을 포함시키는 것은 어려울 것이다. 대신에, 순환문 내에서 개별적으로 여러 개의 조건을 확인해서 필요에 따라 break를 사용하여 종료하는 것이 더 낫다.

또한, 무한 루프는 프로그램의 동작을 지시하는 메뉴 체계를 구성하기 위해서 사용될 수 있다. 다섯번째 강의 "함수의 기본"에서는 프로그램의 main() 함수가 여러 가지 동작을 수행하는 다양한 함수의 실행을 지시하는 '교통 경찰'과 같은 역할을 수행하는 예를 살펴보았다. 프로그램의 사용자에게는 여러 가지 항목이 제공되고, 원하는 항목의 하나를 선택하여 필요한 동작을 수행할 수 있다. 또한, 이런 항목 중에는 프로그램을 종료하는 선택 사항이 포함되어야 한다. 일단 항목이 선택되면 선택된 항목에 따라 프로그램이 실행된다. <리스트 13.4>에 있는 프로그램은 메뉴 체계의 사용 예를 보여준다.

<리스트 13.4> 메뉴 체계를 사용하기 위한 무한 루프의 사용 예

 /* 메뉴 체계를 구현하기 위한 무한루프의 사용 예 */


 #include <stdio.h>

 #define DELAY 1500000   /* 지연에 사용되는 값 */


 int menu(void0;

 void delay(void);


 main()

 {

    int choice;


    while(1)

    {


       /* 사용자의 선택을 요구 */


       choice = menu();


       /* 입력에 따라 분기 */


       if(choice == 1)

       {

          puts("\nExecuting choice 1.");

          delay();

       }

       else if(choice == 2)

       {

          puts("\nExecuting choice 2.");

          delay();

       }

       else if(choice == 3)

       {

          puts("\nExecuting choice 3.l");

          delay();

       }

       else if(choice == 4)

       {

          puts("\nExecuting choice 4.");

          delay();

       }

       else if(choice == 5)

       {

          puts("\nExiting program now...\n");

          delay();

          break;

       }

       else

       {

          puts("\nInvalid choice, try again.");

          delay();

       }

    }

    return 0;

 }


 /* 메뉴를 출력하고 사용자의 선택을 읽어들인다. */

 int menu(void)

 {

    int reply;


    puts("\nEnter 1 for task A.");

    puts("Enter 2 for task B.");

    puts("Enter 3 for task C.");

    puts("Enter 4 for task D.");

    puts("Enter 5 to exit program.");


    scanf("%d", &reply);


    return reply;

 }


 void delay(void)

 {

    long x;

    for(x = 0; x < DELAY; x++)

    ;

 }

4. switch문
 : C에서 제공되는 가장 융통성 있는 프로그램 제어문은 프로그램 내에 포함되는 두 가지 이상의 값을 기본적으로 하여 여러 가지 문장을 실행하게 해주는 switch문이다. 지금까지 설명했던 if문과 같은 제어문에서는 단지 두 개의 값인 참이나 거짓으로 평가되는 수식만을 사용할 수 있었다. 두 개 이상의 값을 기준으로 해서 프로그램의 흐름을 제어하기 위해서는 <리스트 13.4>에 나타나 있듯이 여러 개의 종속된 if문을 사용해야 한다. switch문은 이런 종속문을 대신할 수 있다. switch문의 일반적인 형식은 다음과 같다.

   switch(expression)
   {
      case template_1:  statement(s);
      case template_2:  statement(s);
      …
      case template_n:  statement(s);
      default:  statement(s);
   }

여기서 expression은 long, int, char형과 같은 정수값으로 평가되는 수식이다. switch문은 expression을 평가하여 결과를 각각의 case 다음에 포함되어 있는 template과 비교하고 나서 다음과 같은 동작을 수행한다.

·expression의 결과와 template의 어떤 것이 일치한다면 해당 case에 포함되어 있는 문장이 실행된다.
·아무 것도 일치하지 않으면 선택적으로 사용되는 default에 포함되어 있는 문장이 실행된다
·아무 것도 일치하지 않고 default도 포함되어 있지 않다면 switch문의 바로 다음에 있는 문장이 실행된다.

<리스트 13.5>에는 사용자가 입력한 값에 따라 메시지를 출력하기 위해 switch문을 사용하는 간단한 프로그램이 나타나 있다.

<리스트 13.5> switch문의 사용 예

 /* switch문의 사용 예 */


 #include <stdio.h>


 main()

 {

    int reply;


    puts("Enter a number between 1 and 5: ");

    scanf("%d", &reply);


    switch(reply)

    {

       case 1:

          puts("You entered 1.");

       case 2:

          puts("You entered 2.");

       case 3:

          puts("You entered 3.");

       case 4:

          puts("You entered 4.");

       case 5:

          puts("You entered 5.");

       default:

          puts("Out of range, try again.");

    }

    return 0;

 }

=> 분명히 무언가 잘못되었다는 것을 알 수 잇을 것이다. 여기서 switch문은 처음에 일치하는 템플릿(template)을 발견하고 해당 case에 있는 문장뿐 아니라 이후의 모든 문장들을 실행하고 있다. 잘못된 결과이기는 하지만 switch문은 실제로 이렇게 조건에 따라 정해진 동작을 수행한다. switch문은 값이 일치하는 템플릿으로 분기한다. 그러나 값이 일치하는 템플릿에 포함된 문장만 실행하기 위해서는 필요한 부분에 break문을 포함시켜야 한다. <리스트 13.6>에는 break문을 사용하여 다시 작성한 프로그램이 나타나 있다. 이제 프로그램은 정상적으로 동작할 것이다.

<리스트 13.6> 필요한 부분에 break문을 포함시켜서 정상적으로 실행되는 switch문의 사용예

 /* switch문의 빠른 사용 예 */


 #include <stdio.h>


 main()

 {

    int reply;


    puts("Enter a number between 1 and 5: ");

    scanf("%d", &reply);


    switch(reply)

    {

       case 0:

          break;

       case 1:

       {

          puts("You entered 1.");

          break;

       }

       case 2:

       {

          puts("You entered 2.");

          break;

       }

       case 3:

       {

          puts("You entered 3.");

          break;

       }

       case 4:

       {

          puts("You entered 4.");

          break;

       }

       case 5:

       {

          puts("You entered 5.");

          break;

       }

       default:

       {

          puts("Out of range, try again.");

       }

    }      /* switch의 끝 */

    return 0;

 }


=> 프로그램을 컴파일하고 실행해보자. 정상적으로 동작할 것이다. switch문은 <리스트 13.4>에 나타나 있는 것과 같이 메뉴를 처리하는 경우에 가장 많이 사용된다. <리스트 13.7>에 있는 프로그램은 메뉴를 구현하기 위해 if문 대신에 switch문을 사용하고 있다. switch를 사용하는 것은 <리스트 1.34>에 나타나 있는 메뉴 프로그램의 이전 버전에서 사용되었던 종속된 if문을 사용하는 것보다 훨씬 낫다.

<리스트 13.7> 메뉴 체계를 구성하기 위한 switch문의 사용 예

 /* 메뉴 체계를 구현하기 이한 무한 루프와 switch문의 사용 예 */


 #include <stdio.h>

 #include <stdlib.h>


 #define DELAY 150000


 int menu(void);

 void delay(void);


 main()

 {

    while(1)

    {

       /* 사용자의 선택을 받아들이고 입력에 따라 분기 */


       switch(menu())

       {

          case 1:

          {

             puts("\nExecuting choice 1.");

             delay();

             break;

          }

          case 2:

          {

             puts("\nExecuting choice 2.");

             delay();

             break;

          }

          case 3:

          {

             puts("\nExecuting choice 3.");

             delay();

             break;

          }

          case 4:

          {

             puts("\nExecuting choice 4.");

             delay();

             break;

          }

          case 5:     /* 프로그램의 끝 */

          {

             puts("\nExiting program now...\n");

             delay();

             exit(0);

          }

          default:

          {

             puts("\nInvalid choice, try again.");

             delay();

          }

       }    /* switch의 끝 */

    }    /* while의 끝 */

    return 0;

 }


 /* 메뉴를 출력하고 사용자의 선택을 받아들인다. */

 int menu(void)

 {

    int reply;


    puts("\nEnter 1 for task A.");

    puts("Enter 2 for task B.");

    puts("Enter 3 for task C.");

    puts("Enter 4 for task D.");

    puts("Enter 5 to exit program.");


    scanf("%d", &reply);


    return reply;

 }


 void delay(void)

 {

    long x;

    for(x = 0; x < DELAY; x++)

       ;

 }

=> 이 프로그램에서는 새로운 함수가 사용되고 있다. case 5:에 포함된 48번째 줄의 라이브러리 함수 exit()를 주목하기 바란다. 여기에서는 <리스트 13.4>와 같이 break문을 사용할 수 없다. 여기서 break를 사용하면 무한 루프인 while문을 벗어나는 것이 아니라 switch문을 벗어나게 된다. 잠수 후에 설명하겠지만 exit() 함수는 프로그램 자체를 종료하는 기능을 가진다. 가끔 switch를 구성하는 여러 개의 항목을 동일하게 '처리'하는 것이 유용할 때가 있다. 예를 들어, 여러 가지 항목에서 어떤 것을 선택하든지 특정 문장을 실행할 필요가 있다고 하자. 이 때에는 break문을 사용하지 말고 필요한 문장 앞에 모든 case 템플릿을 입력하면 된다. 만약 조건 수식이 어떤 case에 일치한다면 여러분이 실행하기 원하는 코드 블록에 도달할 때까지 case 다음의 모든 문장이 실행될 것이다. <리스트 13.8>에 있는 프로그램은 이런 경우를 보여준다.

<리스트 13.8> switch문을 사용하는 다른 한 가지 방법

 /* switch문을 사용하는 다른 한 가지 방법 */


 #include <stdio.h>

 #include <stdlib.h>


 main()

 {

    int reply;


    while(1)

    {

       puts("\nEnter a value between 1 and 10, 0 to exit: ");

       scanf("%d", &reply);


       switch(reply)

       {

          case 0:

             exit(0);

          case 1:

          case 2:

          case 3:

          case 4:

          case 5:

          {

             puts("You entered 5 or below.\n");

             break;

          }

          case 6:

          case 7:

          case 8:

          case 9:

          case 10:

          {

             puts("You entered 6 or higher.\n");

             break;

          }

          default:

             puts("Between 1 and 10, please!\n");

       }    /* switch의 끝 */

    }    /* while의 끝 */

    return 0;

 }

=> 이 프로그램은 키보드에서 값을 읽어들이고 5이하인지 6이상인지 또는 1과 10사이의 값인지 알려준다. 입력된 값이 0이라면 18번째 줄에서는 exit() 함수를 호출하므로 프로그램은 종료.
 
5. 프로그램의 종료
 : C 프로그램은 일반적으로 main() 함수의 실행이 끝날 때 종료된다. 그러나 라이브러리 함수 exit()를 사용하면 언제든지 원하는 시기에 프로그램을 마칠 수 있다. 또한, 프로그램이 종료될 때 하나 이상의 함수를 자동으로 실행하게끔 지정할 수도 있다.

5.1 exit() 함수
 : exit() 함수는 프로그램 실행을 종료하고, 제어를 운영체제에 돌려준다. 이 함수는 프로그램이 성공적으로 실행되었는지 또는 실행에 문제가 있었는지를 지적하기 위해서 운영체제에 전달하는 하나의 int형 인수를 가진다. exit() 함수의 형식은 다음과 같다.

   exit(status);

status의 값이 0이라면 프로그램이 정상적으로 종료되었다는 것을 뜻한다. status가 1의 값을 가지면 어떤 에러가 발생해서 프로그램이 비정상적으로 종료되었다는 것을 뜻한다. 이런 복귀값은 대개 무시된다. DOS에서는 배치 파일과 if errorlevel문을 사용하여 이런 복귀값을 확인하고 사용할 수 있다. 그러나 여기서는 DOS 설명서가 아니므로 복귀값을 사용하는 방법에 대해서 알라보기 원한다면 DOS 관련 서적을 참고하기 바란다.

 exit() 함수를 사용하기 위해서는 프로그램에서 헤더 파일 STDLIB.H를 포함시켜야 한다. 또한, 이 헤더 파일에서는 exit() 함수에 대한 인수로 사용되는 두 개의 기호 상수를 다음과 정의하고 있다.

   #define EXIT_SUCCESS 0
   #define EXIT_FAILURE 1

그래서 복귀값을 0으로 설정하여 프로그램을 마치기 원한다면 exit(EXIT_SUCCESS)를 사용할 수 있다. 1의 값을 돌려주기 위해서는 exit(EXIT_FAILURE)를 사용하면 된다.

6. 프로그램 내에서 운영테제의 명령을 실행하는 방법
 : C의 표준 라이브러리는 실행 중인 C 프로그램 내에서 운영테제의 명령을 실행하게 해주는 함수 system()을 제공한다. 이 함수는 프로그램을 종료하지 않은 상태에서 디렉토리 목록을 사렾보고나 디스크를 초기화에 해주므로 때에 따라 유용하게 사용할 수 있다. system() 함수를 사용하기 위해서는 프로그램에 헤더 파일 STDLIB.H를 포함시켜야 한다. system()의 형식은 다음과 같다.

   system(command);

인수 command는 문자열 상수나 문자열에 대한 포인터가 될 수 있다. 예를 들어 DOS의 디렉토리 목록을 살펴보기 위해서 다음과 같은 내용을 사용할 수 있을 것이다.

   system("dir");

또는 다음 문장을 사용할 수도 있다.

   char *command = "dir";
   system(command);

운영체제의 명령을 실행하고 나면 프로그램의 제어는 system() 함수 바로 다음의 문장으로 전달된다. system()에서 사용한 명령이 운영체제에서 유효하지 않은 명령이라면 프로그램이 다시 실행되기 전에 'Bad command or file name'이라는 에러 메시지가 출력된다. system()의 사용 예가 <리스트 13.9>에 나타나 있다.

<리스트 13.9> 운영체제의 명령을 실행하기 위해서 system() 함수를 사용하는 프로그램

 /* system() 함수의 사용 예 */

 #include <stdio.h>

 #include <stdlib.h>


 main(0

 {

    /* 입력을 저장할 버퍼 선언 */


    char input[40];


    while(1)

    {

       /* 사용자의 명령을 받아들인다. */


       puts("\nInput the desired system command, blank to exit");

       gets(input);


       /* 빈 줄이 입력되면 마친다. */


       if(input[0] == '\0'

          exit(0);


       /* 명령을 실행한다. */


       system(input);

    }

    return 0;

 }

system()에서 사용할 수 있는 명령은 디렉토리 목록을 살펴보거나 디스크를 초기화하는 것과 같은 간단한 명령에만 제한되지 않는다. 또한, 실행 가능한 파일이나 배치 파일의 이름을 전달하여 프로그램을 정상적으로 실행할 수도 있다. 예를 들어, system의 인수로 LIST1308을 전달하면 LIST1308이라는 프로그램이 실행될 것이다. 프로그램의 실행이 끝나면 제어는 다시 system() 함수가 사용되었던 곳으로 전달된다.

system()을 사용할 때 유일한 제한 사항이 있다면 메모리와 관련된 문제이다. system()이 실행될 때 원래의 프로그램은 컴퓨터의 RAM에 남게 되고 운영체제의 명령을 처리하는 코드와 함수를 통해서 실행되는 프로그램이 메모리에 읽어들여지게 된다. 이렇게 다른 프로그램을 실행하는 것은 컴퓨터에 충분한 메모리가 남아 있는 경우에만 가능하다. 그렇지 않다면 에러 메시지가 출력될 것이다.

'C 언어' 카테고리의 다른 글

11장 구조체  (0) 2019.06.02
12장 변수의 범위  (0) 2019.06.02
14장 화면, 프린터, 키보드 사용하기  (0) 2019.06.02
15장 포인터 : 고급 기능들  (0) 2019.06.02
16장 링크리스트  (0) 2019.06.02

+ Recent posts