글 목록 보기

Lyn
조회 수 27266 추천 수 0 댓글 0
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄

C++ 코딩을 하다 보면 복사 되지 말아야 하는 객체가 참 많다...


특히 포인터를 소유하고 있는 객체들이 그런데, Copy Constructor 를 만들어 주면 간단한 일이지만...  일일히 하기도 난감하고 Mutex 객체 등은 Copy Constructor 조차 의미가 없고 레퍼런스 카운팅을 해야 되는 더러운 상황이 된다.


이럴땐 간단히 Copy Constructor 를 private에 선언해서 차단 할 수 있는데... 이걸 일일히 해주자니 또 귀찮은게 인지상정이라...


이런것을 간단히 해결해 주는 class가 있다


boost::noncopyable 인데


boost/utility.hpp 에 존재한다.


간단히 boost::noncopyable 를 private 상속(상속시 접근지정자를 쓰지 않으면 private 다!) 해주면 된다.

너무 간단 하므로 샘플따위 없다 (...) 딴데가서 알아봐라

?

Lyn
조회 수 42470 추천 수 0 댓글 0
Atachment
첨부 '2'
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄

tokenizer 는 말 그대로 Token 단위로 문자열을 쪼개는 기능을 한다.

토큰은 사전상의 의미로는 버스탈때 내는 동전 비슷한것 (....) "의미를 갖는 최소한의 문자열" 의 의미를 갖는다. 당연히 여기에서는 후자의 의미다.

 

c 라이브러리에서는 strtok 이 제공 되지만 이는 thread 에서 사용 할 수 없다(전역변수를 사용 하기 때문에)

VCL 에서는 TStringList 가 비슷 한 기능을 제공 하고 MFC에서도 기억은 안나지만 (...) 비슷한 기능을 제공 한다.

 

뭐 항상 이야기 하는 거지만 boost 는 플랫폼을 가리지 않는 다는 것 만으로도 충분한 가치가 있다 : )

그럼 간단하게 사용 예제만 보도록 하자.

 

첫번째 예제 소스 나간다.

 

#include<boost/tokenizer.hpp>

#include<string>

#include<iostream>

 

using namespace std;

using namespace boost;

 

typedef tokenizer<char_separator<char> > TOKC;

int main(int argc, _TCHAR* argv[])

{

         string BorlandUser = "Lyn/Yull/TestCode/Imp//bkyang/4000king/RyuJT/gilgil/esniper/civilian";        

         char_separator<char> sep("/",""); 

        

         TOKC tok(BorlandUser, sep);       

 

         for (TOKC::iterator i = tok.begin(); i != tok.end(); ++i)

         {

                  cout << *i << endl;

         }

         return 0;

}



 

문자열을 '/' 단위로 쪼개기로 하자.

그럼 결과는 아래와 같다.

 

1.png

잘 쪼개 졋다. 그런데 한가지 주의해서 볼 점이 있는데 빈 토큰은 나타나지 않는 다는 것이다.

빈 토큰도 살리기 위해서는 separator 의 생성자에 keep_empty_tokens 옵션을 주면 된다.

 

두번째 예제를 보자

#include<boost/tokenizer.hpp>

#include<string>

#include<iostream>

 

using namespace std;

using namespace boost;

 

typedef tokenizer<char_separator<char> > TOKC;

int main(int argc, _TCHAR* argv[])

{

         string BorlandUser = "Lyn/Yull/TestCode/Imp//bkyang/4000king/RyuJT/gilgil/esniper/civilian";        

         char_separator<char> sep("/","", keep_empty_tokens);

        

         TOKC tok(BorlandUser, sep);       

 

         for (TOKC::iterator i = tok.begin(); i != tok.end(); ++i)

         {

                  cout << *i << endl;

         }

         return 0;

}

 

2.png

 

빈 토큰도 구해진 것을 볼 수 있다 : )

참여해 주신 볼랜드(코드기어? 엠바카데로?) 유저분에게 심심한 감사를 표하면서 끝내겠다 (__)



PS. 유니코드 일 경우 typedef tokenizer<char_separator<wchar_t>, wstring::const_iterator, wstring> TOKC; 와 같이 선언하는것이 편리하다

?

Lyn
조회 수 41456 추천 수 0 댓글 0
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄

이번엔 공백 처리 함수들이다. 로그인할때 아이디 처리라던지에 써먹은 기억이 있다 : )


#include <cstdio>

#include <string>

#include <boost/algorithm/string.hpp>

 

using namespace boost;

using namespace std;

 

int _tmain(int argc, _TCHAR* argv[])

{

        string str1 = "   I love Lyn!      ";

        string str2;

        trim(str1); //양옆의공백을다제거한다.

        printf("trim : %s\n", str1.c_str());

 

        str1 = "   I love Lyn       ";

        str2 = trim_copy(str1); //양옆의공백을다제거하되원본을변경하지않고새로운문자열을리턴

        printf("trim_copy : %s, %s\n", str1.c_str(), str2.c_str());

 

        str1 = "#######   I love Lyn       #######";

        trim_if(str1,is_any_of("#")); //제거할문자를직접지정해서제거한다.

        printf("trim_if-1 : %s\n", str1.c_str());

 

        str1 = "!@#!@#I love Lyn!@#!@#";

        trim_if(str1,is_any_of("!@#")); //제거할문자열의길이에는제한이없다!

        printf("trim_if-2 : %s\n", str1.c_str());

 

        str1 = "   I love Lyn       ";

        trim_left(str1); //왼쪽의공백을제거한다.

        printf("trim_left : %s\n", str1.c_str());

       

        //물론trim_left_copy, trim_left_if, trim_right, trim_right_copy, trim_right_copy_if 등등.. 있을함수는다있다.

        //여기서는몇가지만소가했지만네이밍규칙을보면다알수있을수준이다.

        return 0;

}

 

?

Lyn
조회 수 41511 추천 수 0 댓글 0
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄

이번엔 문자열 다루는 알고리즘 들이다.
C++ 기본 라이브러리에서 당연히 지원 해줄것 같은데 안해주는(...) 문자열 관련 함수들을 담고 있다.
첫번째로 대소문자 변경 기능만 살펴본다.

String 알고리즘은 원본을 변형하는 형태와, 원본을 보존하고 새로운 문자열을 리턴하는 두가지 형태로 지원되는점이 특징이다.

#include <string>

#include <boost/algorithm/string.hpp>

 

using namespace std;

using namespace boost;

 

int _tmain(int argc, _TCHAR* argv[])

{

        string str = "Hello Lyn!";

        string str2;

        printf("%s\n", str.c_str());

 

        to_upper(str); //모두대문자로바꾼다

        printf("%s\n", str.c_str());

        to_lower(str); //모두소문자로바꾼다

        printf("%s\n", str.c_str());

       

        str = "Hello Lyn!";

        str2 = to_upper_copy(str); //모두대문자로바꾸되원본을변형하지않고새로운문자열을리턴

        printf("%s %s\n", str.c_str(), str2.c_str());

        str2 = to_lower_copy(str); //모두소문자로바꾸되원본을변형하지않고새로운문자열을리턴

        printf("%s %s\n", str.c_str(), str2.c_str());

 

        return 0;

}

?

Lyn
조회 수 41261 추천 수 0 댓글 0
Atachment
첨부 '1'
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄

이번엔 lexical_cast 에 대해서 알아보자.
cast 란 이름에서 눈치챗겠지만 일종의 캐스팅 연산자(인 척 하는 함수) 이다.
string -> Integer 같은 상식적으론 말이 되지만 C++문법상으론 말이 안되는 일들을 처리한다.

일단 예제코드를 소개한다.
timer 에 관해서는
http://lunapiece.net/3795 를 참조해라

#include <boost/lexical_cast.hpp>

#include <cstdlib>

#include <string>

#include <boost/timer.hpp>

 

using namespace std;

using namespace boost;

 

string Number = "1024768";

 

void StrToIntAtoi()

{

        int num;

        for(int i=0;i < 1000000; ++i)

        {

               num = atoi(Number.c_str());

        }

        printf("atoi : %d\n", num);

}

void StrToIntStringStream()

{

        stringstream sst;

        int Num;

        for(int i=0;i < 1000000; ++i)

        {

               sst << Number.c_str();

               sst >> Num;

        }

        printf("StringStream : %d\n", Num);

}

void StrToIntLexicalCast()

{

        int num;

        for(int i=0;i < 1000000; ++i)

        {

               num = lexical_cast<int>(Number);

        }

        printf("LexicalCast : %d\n", num);

}

int _tmain(int argc, _TCHAR* argv[])

{

        timer t;

        printf("atoi 를이용한방법\n");

        t.restart();

        StrToIntAtoi();

        printf("%lf \n", t.elapsed());

 

        printf("StringStream 을이용한방법\n");

        t.restart();

        StrToIntStringStream();

        printf("%lf \n", t.elapsed());

 

        printf("lexical_cast 를이용한방법\n");

        t.restart();

        StrToIntLexicalCast();

        printf("%lf \n", t.elapsed());

 

        return 0;

}

 

.....

예제코드 자체는 별 문제가 없었으리라 본다. 준비작업도 필요없고 간단하게 쓸 수 있어서 참 좋아보이기는 한다.
하나의 함수로 이런저런 캐스팅을  다 할수 있으니 다형성도 뛰어나다.
그러나 치명적인 문제가 딱 하나 있는데...

 

 

바로 이거다 속도 =_=;

stringstream 에 비해 약 10배, atoi 에 비해선 대략 50배 가량 느리다 =_=;; 편의성을 위해서 가끔 쓰는정도라면 몰라도 자주 반복되는 루틴에서는 쓰지 않아야 하겠다.

?

Lyn
조회 수 36587 추천 수 0 댓글 0
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄

multi_array는 다차원 정적배열을 지원하는 클래스이다.
이를 이용하는 다른 방법은 std::tr1::array 를 중첩해서 사용 하는 방법이 있는데, 이것을 좀 더 편하게 확장했다고 보면 되겠다.

std::tr::array에 관한 내용은 아래 링크를 참조해라
http://lunapiece.net/Article/476

 

아래는 multi_array와 중첩된 array에 관한 예제코드이다. 이것도 별 내용이 없다보니 그냥 코드 보여주는걸로 끝낸다.

 

#include "stdafx.h"

#include <array>

#include <multi_array.hpp>

#include <cstdio>

 

using namespace boost;

 

int _tmain(int argc, _TCHAR* argv[])

{

        //TR1Array를이용한Static Array of Array 구현

        printf("std::tr1::array<std::tr1::array<int,20>, 10>\n");

        std::tr1::array<std::tr1::array<int,20>, 10> Stdarr; //int[10][20] 선언

        //10*20인데선언시에는숫자가반대로씌여지므로헤깔린다!

        for (int i = 0; i < 10; ++i)

        {

               for(int j = 0;j < 20; ++j)

               {

                       Stdarr[i][j] = 1;

               }

        }

 

        //boost::multi_array

        printf("multi_array 사용\n");

        multi_array<int, 2> MultiArray(extents[10][20]);

        //int2차원배열[10][20]을선언. 템플릿인자가Type 과차원을나타냄.

        for (int i = 0; i < 10 ; ++i)

        {

               for(int j = 0;j < 20; ++j)

               {

                       MultiArray[i][j] = 2;

               }

        }

       

        printf("multi_array range 조정\n");

        //Pascal 처럼배열의인덱스를변형할수있는기능을제공한다.

        typedef multi_array<int, 2>::extent_range range; //이름이너무길어서ㅡ.

        multi_array<int, 2> MultiArray2(extents[range(1,4)][range(20,40)]);

        //Pascal 과비교하면Array [1..4-1] of Array[20..40-1] of Integer 라고할수있겠다.

        for (int i = 1; i < 4 ; ++i)

        {

               for(int j = 20;j < 40; ++j)

               {

                       MultiArray2[i][j] = 2;

               }

        }

        return 0;

}

 

 

 

?

Lyn
조회 수 44041 추천 수 0 댓글 0
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄

이번에는 timer 클래스이다.
단순히 시간을 재는데 이용 할 수 있는 클래스로 알고리즘 돌려놓고 시간측정하기 딱좋다.
아래 pool 예제 등에서도 이 timer 를 사용햇으면 더 편햇을텐데... 어쩌다 보니 이게 뒤로나왔다.

#include "boost/timer.hpp"

#include <cstdio>

#include <windows.h>

 

using namespace boost;

 

int main(int argc, char **argv)

{

        timer t; //생성시0으로초기화

        Sleep(1234);

        printf("%lf 초지남\n", t.elapsed());

        Sleep(1766);

        printf("%lf 초지남\n", t.elapsed());

        t.restart();//Restart 를이용하여다시초기화

        Sleep(512);

        printf("%lf 초지남\n", t.elapsed());

        return 0;

}

 

?

2009.05.11 22:44

[Boost 살펴보기] 2. any

Lyn
조회 수 36543 추천 수 0 댓글 0
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄

any 는 마치 스크립트 언어처럼 임의의 type을 가지는 변수를 만들 수 있다.
COM의 Varient 와도 비슷하다 하겠다.

하지만 이것을 언어수준에서 구현 해 놓았고, 다른 컨테이너와 매우 잘 맞물린다는것이 장점이라 하겠다.
vector 에 이런저런 잡데이터 쑤셔넣을때 참 좋더라
(당연히 오버헤드는 있다. 꼭 필요한 곳에만 사용하자!)

#include "stdafx.h"
#include <cstring>
#include <cstdio>
#include <boost/any.hpp>
#include <string>
#include <vector> 

using namespace boost;

using namespace std;

 

struct MyData

{

        int i;

        double d;

        MyData()

        {

        }

        MyData(const MyData& t) //Any의요구사항1 Copy Constructor

        {

               this->i = t.i;

               this->d = t.d;

        }

        MyData& operator =(const MyData &t) //Any의요구사항2 = operator overloading

        {

               this->i = t.i;

               this->d = t.d;

        }

};

int _tmain(int argc, _TCHAR* argv[])

{

        any Data;

        //값이비어있는지를확인하는방법

        if(Data.empty())

        {

               puts("Data 는비어있음!");

        }

        else

        {

               puts("Data 는비어있지않음!");

        }

        Data = 3.14; //실수리터럴의기본타잎은double

        Data = 3; //정수리터럴의기본타잎은Int

 

        //Data = "Hello!"; //에러.

        //any 에넣기위해선CopyConstructor = Operator Overloading 이필요하다.

        string str = "Hello";

        Data = str; //정상적

 

        //Any 에어떤타잎이들어있는지확인

        //type매소드로type 을확인후any_cast 연산자를사용하여캐스팅

        if(Data.type() == typeid(int))

        {

               printf("DataType : Int, Value : %d\n", any_cast<int>(Data));

        }

        else if(Data.type() == typeid(double))

        {

               printf("DataType : Double, Value : %lf\n", any_cast<double>(Data));

        }

        else if(Data.type() == typeid(string))

        {

               printf("DataType : string, Value : %s\n", any_cast<string>(Data).c_str());

        }

 

        //Vector Any 의복합사용예.

        printf("임의의타잎을담는Vector 선언하기\n");

        vector<any> AnyVector;

        MyData myData;

        myData.d = 5.12;

        myData.i = 5;

 

        AnyVector.push_back(3);

        AnyVector.push_back(3.14);

        AnyVector.push_back(myData);

 

        vector<any>::iterator it;

        for (it = AnyVector.begin(); it != AnyVector.end(); ++it)

        {

               if(it->type() == typeid(int))

               {

                       printf("Any Type : Int, Value : %d\n", any_cast<int>(*it));

               }

               else if(it->type() == typeid(double))

               {

                       printf("Any Type : Double, : %lf\n", any_cast<double>(*it));

               }

               else if(it->type() == typeid(MyData))

               {

                       printf("Any Type : MyData, Value : %d, %lf\n", any_cast<MyData>(*it).i, any_cast<MyData>(*it).d);

               }

        }

        return 0;

}

 

 

?

2009.05.11 22:04

[Boost 살펴보기] 1. pool

Lyn
조회 수 36929 추천 수 0 댓글 0
Atachment
첨부 '1'
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄

TR1에 포함되지 않은 Boost 의 다른 요소들 중 자주 쓸 만한 것들을 한번 눈여겨 볼까 한다

첫번째 예제는 Pool이다. 메모리를 미리 생성 해 놨다가 해제/생성이 반복 될 경우 미리 생성해 놓은 메모리를 재활용 하는데 유용하게 사용 할 수 있다. 개인적으로도 많이 구현해서 써 보았고 탁월한 메모리 성능을 보여 주는 방법이다.

아래에는 예제코드를 담았다. 예제코드에 있는 주석만으로도 충분히 사용 방법과 기능을 알 수 있을것이라 본다.
그림은 아래 코드를 실행한 속도 테스트 스크린샷이다(VS2008 Sp1 + Realese Mode)
일반적으로 메모리를 할당하는것 보다 훨신 빠름을 알 수 있다. 게다가 단편화의 문제도 적다.

#include "stdafx.h"

#include <iostream>

#include <cstring>

#include <boost/pool/pool.hpp>

#include <boost/pool/object_pool.hpp>

#include <windows.h>

 

using namespace std;

using namespace boost;

 

struct MyData

{

        int i;

 

        MyData()

        {

               puts("Constructor!");

        };

        ~MyData()

        {

               puts("Destructor!");

        }

};

struct MyData2

{

        int i;

};

 

MyData2** Array; //임시저장용

 

void UseNormalPool()

{

        pool<> pool(sizeof(MyData));

 

        MyData *p = (MyData*)pool.malloc();      

        //pool.free(p); //Free를하지않을경우pool이파괴될때pool에서생성한모든메모리가파괴됨.

}

 

void UseObjectPool()

{

        object_pool<MyData> pool;

 

        for (int i = 0; i < 1; ++i)

        {

               MyData *p = pool.malloc();

               //기본적으로같지만파괴시Destructor 가호출됨(주의: Constructor 는호출되지않음)

        }

}

void Test1NewAndDelete()

{

        for (int i = 0;i < 1000000; ++i)

        {

               MyData2* p = new MyData2;

               delete p;

        }

}

void Test1MallocAndFree()

{

        for (int i = 0;i < 1000000; ++i)

        {

               MyData2* p = (MyData2*)malloc(sizeof(MyData2));

               free(p);

        }

}

void Test1BoostPool()

{

        pool<> pool(sizeof(MyData));

        for (int i = 0;i < 1000000; ++i)            

        {

               MyData *p = (MyData*)pool.malloc();  

               pool.free(p);

        }

}

void Test2NewAndDelete()

{

        for (int i = 0;i < 1000000; ++i)

        {

               Array[i] = new MyData2;

        }

        for (int i = 0;i < 1000000; ++i)

        {

               delete Array[i];

        }

        for (int i = 0;i < 1000000; ++i)

        {

               Array[i] = new MyData2;

        }

        for (int i = 0;i < 1000000; ++i)

        {

               delete Array[i];

        }

}

void Test2MallocAndFree()

{

        for (int i = 0;i < 1000000; ++i)

        {

               Array[i] = (MyData2*)malloc(sizeof(MyData2));

        }

        for (int i = 0;i < 1000000; ++i)

        {

               free(Array[i]);

        }

        for (int i = 0;i < 1000000; ++i)

        {

               Array[i] = (MyData2*)malloc(sizeof(MyData2));

        }

        for (int i = 0;i < 1000000; ++i)

        {

               free(Array[i]);

        }

}

void Test2BoostPool()

{

        pool<> pool(sizeof(MyData2));

        for (int i = 0;i < 1000000; ++i)            

        {

               Array[i] = (MyData2*)pool.malloc();  

        }

        for (int i = 0;i < 1000000; ++i)            

        { 

               pool.free(Array[i]);

        }

        for (int i = 0;i < 1000000; ++i)            

        {

               Array[i] = (MyData2*)pool.malloc();  

               //Free후재할당을할경우이미생성된메모리가재사용됨

               //Free를하더라도실제메모리는파괴되지않음

        }

        for (int i = 0;i < 1000000; ++i)            

        { 

               pool.free(Array[i]);

        }

}

int _tmain(int argc, _TCHAR* argv[])

{

        UseObjectPool();

        UseNormalPool();

       

        Array = new MyData2*[1000000];

        int TickCount;

 

        puts("Alloc and Free 1000000 count");

        TickCount = GetTickCount();

        Test1NewAndDelete();

        printf("new And delete : %dms\n", GetTickCount() - TickCount);

 

        TickCount = GetTickCount();

        Test1MallocAndFree();

        printf("malloc And Free : %dms\n", GetTickCount() - TickCount);

 

        TickCount = GetTickCount();

        Test1BoostPool();

        printf("boost pool : %dms\n", GetTickCount() - TickCount);

 

        puts("Alloc 1000000 count and Free 1000000 count");

 

        TickCount = GetTickCount();

        Test2NewAndDelete();

        printf("new And delete : %dms\n", GetTickCount() - TickCount);

 

        TickCount = GetTickCount();

        Test2MallocAndFree();

        printf("malloc And Free : %dms\n", GetTickCount() - TickCount);

 

        TickCount = GetTickCount();

        Test2BoostPool();

        printf("boost pool : %dms\n", GetTickCount() - TickCount);

        return 0;

}

 

?

Lyn
조회 수 85 추천 수 0 댓글 0
Atachment
첨부 '1'
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄

1.PNG
 

 

  1.  본인의 수준
  •  Python : 대충 그럭저럭 프로젝트 하는 수준
  •  CUDA : 예전에 한번 프로젝트 진행 해본 수준
  •   Multi Thread / 비동기 : 전문가 수준

2. 썰

  • 일단 번역이 굉장히 어색해서 읽는데 거부감이 듬
  • 책 구성이 이상하다. 이슈를 던진 후 설명도 안하고 다음 예제로 넘어가는 식. 독자가 궁금해 할 만한 내용에 대해 제대로 설명이 안되어있다,  애초에 저자가 자기가 쓰고 있는 내용을 이해는 하고 있나 싶은 수준
  • 책 두께에 비해 너무 많은 내용을 다룬다. 키워드 하나 던지고 예제하나 보여주고 다음내용으로 넘어가는 식. 예제도 도움이 되는 내용이 없다
  • 역자는 학생이고, 저자도 책의 출간 시점을 생각하면 아마도 이 책도 졸업학기 내지는 졸업 직후 씌여진 것 같은데 좀 더 알고서 책을 썼으면 좋았겠다.

3. 점수

  • 1.0 / 5.0

4. 한줄평

  • 프로그래밍에 대해 쌩판 모르는데 키워드만 빨리 캐치하고 싶은 사람이 아니면 시간이 아깝다. 그런데 사실 이정도는 구글 검색만 해도 나온다.

 

 

?

Board Pagination Prev 1 2 3 4 5 6 7 8 9 10 ... 14 Next
/ 14