티스토리 툴바

블로그 이미지
초록영필
Engineer

calendar

    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31    

Notice

Tag

Recent Comment

Recent Trackback

Archive

2011/01/15 16:53 고급C,C++
상수 멤버 함수에서 mutable 데이터 멤버 일 경우에는 수정이 가능하다.

class CTextBook
{
public:
 ... 
 std::size_t length() const;
private:
 char * pText;
 mutable std::size_t textlength;
 mutable bool lengthIsValid;
};
std::size_t CTextBook::length() const //상수 멤버 함수
{
 if(!lengthIsValid){
   textLength = std::stelen(pText);
   lengthIsValid = true;
 }
return textlength;
}

저작자 표시
posted by 초록영필
2011/01/15 16:26 고급C,C++
std::vector<int> vec;
...
const std::vector<int>::iterator iter = vec.begin(); //iter 는 T* const 처럼 동작

*iter = 10; //동작
++iter; //에러

std::vector<int>::const_iterator cIter = vec.begin(); //cIter는 const T* 처럼 동작
*cIter = 10;//에러
++cIter;//동작

저작자 표시
posted by 초록영필
2011/01/15 16:09 고급C,C++
const char * const Name = "feelpass";
const std::string Name("feelpass");



#include <iostream>
using namespace std;

class Person 
{
char name[20];
int age;
static int count;
public:
Person(char* _name, int _age)
{
strcpy(name, _name);
age = _age;
cout << count << "번째 Person 객체 생성" << endl;
}

void showdata()
{
cout << "이름: " << name << endl;
cout << "나이: " << age << endl;
}
};

const int Person::count = 1;

int main()
{
Person p1("Lee" , 13);
Person p2("Hong", 22);
cin.get();
return 0;
}

위와 같이 한 클래스 내에서만 사용할 수 있는 전역 변수 count를 사용할 수 있다.

int Person::count = 1; 문장을 다시 봐야하는데 count가 private 영역에 선언되어 있으므로 일반적이라면 에러를 검출할 것이다. 하지만 전역변수로 등록이 되었기 때문에 처음 초기화에 대해선 오류를 발생시키지 않고 정상적인 프로그램이 된 것이다. 

실질적으로 class내에 선언된 static 변수가 객체의 멤버로 존재하는 것은 아니라고 한다. 다만 클래스내에서만 직접 접근할 수 있는 권한이 부여된 것이라고 볼 수 있다. 




이제 한 클래스 내에서 사용할 클래스 상수 정의 를 보자



#include <iostream>
using namespace std;

class Person 
{
char name[20];
int age;
static const int count; // 정의 부분을 없에고 static const int count = 1 로 해도 된다.
public:
Person(char* _name, int _age)
{
strcpy(name, _name);
age = _age;
cout << count << "번째 Person 객체 생성" << endl;
}

void showdata()
{
cout << "이름: " << name << endl;
cout << "나이: " << age << endl;
}
};

const int Person::count = 1;//정의부분.

int main()
{
Person p1("Lee" , 13);
Person p2("Hong", 22);
cin.get();
return 0;
}



다른 방법으론 나열자 둔갑술이 있다.

class GamePlayer{
private:
   enum { NumTurns = 5 };
   int scores[NumTurns];
}
선언한 정수 상수를 가지고 다른 사람이 주소를 얻는다든지 참조자를 쓴다는지 하는 것이 싫다면 enum이 아주 좋은 자물쇠가 될 수 있다.

저작자 표시
posted by 초록영필
2011/01/15 13:21 고급C,C++
템플릿들은 함수가 아니기 때문에 개별적으로 컴파일할 수 없다. 템플릿들은 특별한 구체화 요구들과 함께 결합하여 사용해야 한다. 가장 간단한 방법은, 모든 템플릿 관련 정보를 헤더 파일에 집어 놓고, 그 템플릿들을 사용하는 파일에 그 헤더 파일을 포함시키는 것이다.




// stacktp.h -- a stack template
#ifndef STACKTP_H_
#define STACKTP_H_
template <class Type>
class Stack
{
private:
enum {MAX = 10}; // constant specific to class
Type items[MAX]; // holds stack items
int top; // index for top stack item
public:
Stack();
bool isempty();
bool isfull();
bool push(const Type & item); // add item to stack
bool pop(Type & item); // pop top into item
};
template <class Type>
Stack<Type>::Stack()
{
top = 0;
}
template <class Type>
bool Stack<Type>::isempty()
{
return top == 0;
}

template <class Type>
bool Stack<Type>::isfull()
{
return top == MAX;
}
template <class Type>
bool Stack<Type>::push(const Type & item)
{
if (top < MAX)
{
items[top++] = item;
return true;
}
else
return false;
}
template <class Type>
bool Stack<Type>::pop(Type & item)
{
if (top > 0)
{
item = items[--top];
return true;
}
else
return false;
}
#endif





  • 템플릿 클래스 사용 - 템플릿을 프로그램에 단순히 포함시키는 것만으로는 템플릿 클래스가 생성되지 않는다. 구체화를 요청해야 한다. 구체화를 요청하려면, 템플릿 클래스형의 객체를 선언하되, 포괄적인 데이터형 이름을 구체적인 데이터형으로 대체해야 한다.
    Stack<int> kernels; //int 값들의 스택을 생성한다.
    Stack<string> colonels; //string 객체들의 스택을 생성한다.

    사용하려는 데이터형을 명시적으로 제공해야 한다. 함수 전달인자의 데이터형을 사용하여, 컴파일러가 어떤 종류의 함수를 생성할 것인지 결정하는 일반적인 함수 템플릿과 이 점에서 다르다.








저작자 표시
posted by 초록영필
2011/01/15 02:51 고급C,C++
함수 템플릿은 임의 데이터형으로 함수를 정의하는 것을 허용한다.


  • 기본적인 함수 템플릿

//funtemp.cpp - 함수 템플릿 사용하기

#include <iostream>

template <typename t>
void Swap(t & a, t & b);

int main()
{
using namespace std;
int i = 10;
int j = 20;
cout<<"i,j = "<<i<<","<<j<<"...\n";
cout<<"컴파일러가 생성한 int형 교환기를 사용하려면 \n";
Swap(i,j); //void Swap(int &, int &)를 생서한다.
cout<<"이제 i,j = "<<i<<","<<j<<"...\n";

return 0;
}

template<typename t>
void Swap(t & a, t & b)
{
t temp;
temp = a;
a = b;
b = temp;
}








  • 템플릿의 오버로딩

//twotemps.cpp -- 함수 템플리의 오버로딩
#include <iostream>


template<class Any> //원본 템플릿
void Swap(Any & a, Any & b);

template<class Any> //추가 템플릿
void Swap(Any * a, Any * b, int n);
int main()
{

return 0;
}

template <class Any>
void Swap(Any & a, Any & b)
{

Any temp;
temp = a;
a = b;
b = temp;
}
template <class Any>
void Swap(Any * a, Any * b, int n)
{
Any temp;
for(int i = 0; i< n; i++)
{
temp = a[i];
a[i] = b[i];
b[i] = temp;
}
}



  • 명시적 특수화
    • 다음과 같은 구조체를 정의한다고 가정하자
      struct job
      {
          char name[40];
          double salary;
          int floor;
      };
      C++에서는 하나의 구조체를 다른 하나의 구조체에 대힙할 수 있기 때문에 Any형이 job 구조체인 경우에도 위의 코드는 잘 동작한다. 그러나 salary와 floor 멤버만 교환하고, name 멤버는 그대로 두고 싶다고 가정해 보자. 이 경우에는 이제 코드가 달라져야 한다. 그러나 Swap()에 넘겨주는 전달인자는 첫 번째 경우와 같아야 한다. 그렇다면 새로운 코드에 템플릿 오버로딩을 사용할 수 없다. 
       그러나 명시적 특수화(explicit specialization)라는 특수화된 함수 정의를 필요한 코드와 함께 제공할 수 있다. 컴파일러가 함수 호출에 정확히 대응하는 특수화된 정의를 발견하면, 템플릿을 찾기 않고 그 정의를 사용한다.
    • job형을 위한 명시적 특수화 원형
      template<>
      void Swap<job>(job &, job &)

// twoswap.cpp -- specialization overrides a template
#include <iostream>
template <class Any>
void Swap(Any &a, Any &b);


struct job
{
char name[40];
double salary;
int floor;
};
// explicit specialization
template <> void Swap<job>(job &j1, job &j2);
void Show(job &j);
int main()
{
using namespace std;
cout.precision(2);
cout.setf(ios::fixed, ios::floatfield);
int i = 10, j = 20;
cout << “i, j = “ << i << “, “ << j << “.\n”;
cout << “Using compiler-generated int swapper:\n”;
Swap(i,j); // generates void Swap(int &, int &)
cout << “Now i, j = “ << i << “, “ << j << “.\n”;
job sue = {“Susan Yaffee”, 73000.60, 7};
job sidney = {“Sidney Taffee”, 78060.72, 9};
cout << “Before job swapping:\n”;
Show(sue);
Show(sidney);
Swap(sue, sidney); // uses void Swap(job &, job &)
cout << “After job swapping:\n”;
Show(sue);
Show(sidney);
return 0;
}
template <class Any>
void Swap(Any &a, Any &b) // general version
{
Any temp;
temp = a;
a = b;
b = temp;
}
// swaps just the salary and floor fields of a job structure
template <> void Swap<job>(job &j1, job &j2) // specialization
{
double t1;
int t2;
t1 = j1.salary;
j1.salary = j2.salary;
j2.salary = t1;
t2 = j1.floor;
j1.floor = j2.floor;
j2.floor = t2;
}
void Show(job &j)
{
using namespace std;
cout << j.name << “: $” << j.salary
<< “ on floor “ << j.floor << endl;
}



  • 구체화와 특수화
    • 컴파일러가 특정 데이터형에 맞는 함수 정의를 생성하기 위해 템플릿을 사용할 때, 그 결과는 템플릿의 구체화(instantiation)라 한다.
      • 암시적 구체화(implicit instantiation) - 소스에서 사용할때 그에 해당하는 형으로 Swap() 함수를 구체화 한다.
      • 명시적 구체화(explicit instantiation) - 사용자가 직접 구체화 하도록 지시할 수 있다.
        • temp void Swap<int>(int &,int &) // 명시적 구체화
        • 몇시적 특수화와 <> 차이다. // template <> void Swap(int &, int &);

template <class Any>
void Swap (Any &, Any &); // template prototype
template <> void Swap<job>(job &, job &); // explicit specialization for job
int main(void)
{
template void Swap<char>(char &, char &); // explicit instantiation for char
short a, b;
...
Swap(a,b); // implicit template instantiation for short
job n, m;
...
Swap(n, m); // use explicit specialization for job
...
char g, h;
Swap(g, h); // use explicit template instantiation for char
...
}
저작자 표시
posted by 초록영필
2011/01/15 01:59 고급C,C++
cctype 문자 함수

 함수 이름  리턴값
 isalnum()  전달인자가 영숫자, 즉 알파벳 문자이거나 숫자이면 true
 isalpha()  전달인자가 알파벳 문자이면 true
 isblank()  전달인자가 빈칸 문자 또는 수평 탭 문자이면 true
 iscntrl()  전달인자가 제어 문자이면 true
 isdigit()  전달인자가 십진 숫자이면(0~9) true
 isgragh()  전달인자가 빈칸이 아닌 인쇄할 수 있는 문자이면 true
 islower()  전달인자가 소문자이면 true
 isprint()  전달인자가 빈칸을 포함하여 인쇄할 수 있는 문자이면 true
 ispunct()  전달인자가 구두점 문자이면 true
 isspace()  전달인자가 표준 화이트스페이스, 즉 빈칸, 용지 이송, 개팅, 캐리지 리턴, 수평 탭, 수직 탭 문자이면 true
 isupper()  전달인자가 대문자 이면 true
 isxdigit()  전달인자가 16진 숫자(0~9, a~f 또는 A~F)이면 true
 tolower()  전달인자가 대문자이면 소문자로 바꾸어 리턴한다. 그렇지 않으면 전달인자를 변경하지 않고 그대로 리턴한다.
 toupper()  전달인자가 소문자이면 대문자로 바꾸어 리턴한다. 그렇지 않으면 전달인자를 변경하지 않고 그대로 리턴한다.



저작자 표시
posted by 초록영필
2011/01/15 00:06 고급C,C++
때로는 프로그램이 1바이트로 표현할 수 엇ㅂ는 문자세트를 처리해야 하는 경우도 있다. C++는 이것을 두 가지 방법으러 처리한다. 첫째, 확장 문자 세트가 시스템의 기본 문자 세트이면, 컴파일러 개발업체가 char형을 처음부터 2바이트 또는 그 이상으로 만드는것이다. 둘째, 기본 문자 세트에 확장 문자 세트를 동시에 지원하는 것이다. 즉, 보통의 8비트 char형으로 기본 문자세트를 나타내고, wchar_t형(wide charactor type에서 w,char,t를 각각 따 왔다.)으로 확장 문자 세트를 나타내는 것이다. wchar_t형은 시스템에 사용되는 가장 큰 확장 문자 세트를 나타낼 수 있을 만큼의 충분한 비트 폭을 가진 정수형이다. wchar_t 형은 기초 데이터형(underlying type)이라고 부르는 정수형과 동일한 크기의 부호 속성을 가진다. 기초 데이터형은 시스템에 따라 다른데, 어떤 시스템에는 unsigned short형일 수 있고, 다름 시스템에서는 int형이 될 수도 있다.
 cin과 cout은 입력과 출력을 char형 문자의 스트림을 간주하기 때문에 wchar_t형을 처리하지 못한다. 최신 버전의 iostream 헤더파일은 wchar_t형 문자의 스트림을 처리하기 위해 wcin과 wcout 객체를 제공한다. 그리고 확장 문자 상수나 확장 문자 문자열을 그 앞에 L을 뭍여서 나타낸다. 다음과 같은 코드는 변수 bob에 문자의 P의 wchar_t형 버전을 저장하고, 단어 tall의 wchar_t형 버전을 디스플레이한다.
 wchar_t bob = L'P'; //확장 문자 상수
 wcout<<L"tall"<<endl; //확장 문자 문자열의 출력

2바이트 wchar_t형을 사용하는 시스템에서, 이 코드는 각 문자에 2바이트씩 할당 하여 메모리에 저장한다.

저작자 표시
posted by 초록영필
2011/01/14 23:54 고급C,C++
int형과는 달리, char형은 signed형이나 unsigned형으로 미리 정해져 있지 않다. 그것은 C++ 시스템 개발자가 하드웨어 특성에 맞추어 알맞은 char형을 정할 수 있도록 하기 위한 배려이다. 그러나 char형이 어느 특별한 한 가지 행동만을 보여야 한다면, signed char형 또는 unsigned char형을 사용하여 그 행동을 명시적으로 제한할 수 있다.

char fodo; //signed형 또는 unsigned형
unsigned char bar; //명백히 unsigned형
signed char snark; //명백히 signed형

이러한 구별은  char형으로 수를 나타내고 할 때 특히 중요하다. unsigned char형은 0에서 255까지의 범위를 나타낼 수 있고, signed char형은 -128부터 127까지의 범위를 나타낼 수 있다. 예를 들어, 200정도 되는 값을 char형 변수에 저장한다고 가정해 보자. 이것은 어떤 시스템에서는 동작하지만 다른 어떤 시스템에서는 동작하지 않을 것이다. 같은 값을 unsigned char형에 저장한다면 어느 시스템에서나 무리없이 동작할 것이다. 그러나 표준 ASCII 문자를 char형 변수에 저장할 때에는 signed형이나 unsigned형이나 상관이 없으므로 간단히 char라고 쓰면 된다.

저작자 표시
posted by 초록영필
2011/01/14 23:49 고급C,C++
climits에 정의되어 있는 상수 기호들

CHAR_BIT char형의 비트 수 
CHAR_MAX char형의 최대값
CHAR_MIN char형의 최소값
SCHAR_MAX
SCHAR_MIN
UCHAR_MAX
SHRT_MAX
SHRT_MIN
USHRT_MAX
INT_MAX
INT_MIN
UINT_MAX
LONG_MAX
LONG_MIN
ULONG_MAX


저작자 표시
posted by 초록영필
2011/01/14 22:58 고급C,C++
많은 STL 안고리즘들이 펑크너라고 부르는 함수 객체를 사용한다. 평크터는 함수처럼 () 과 함께 사용할 수 있는 객체이다. 일반 함수의 이름, 함수를 지시하는 포인터, () 연산자가 오버로딩된 클래스 객체 - 즉 operator() ()라는 이상한 모양의 함수가 정의된 클래스들이 모두 펑크터가 될 수 있다. 

class Linear
{
private:
double slope;
double y0;
public:
Linear(double _s1 = 1, double _y = 0)
:slope(_s1), y0(_y) {}
double operator()(double x){return y0 + slope*x;}
};
이제 오버로딩된 () 연산자가 Linear 객체들을 함수처럼 사용하는 것을 허용한다.
Linear f1;
Linear f2(2.5, 10.0);
double y1 = f1(12.5);
double y2 = f2(0.4);

  • 펑크너 개념
    • 제너레이터는 전달인자 없이 호출하는 함수이다.
    • 단항함수(unary function)는 하나의 전달인자로 호출하는 함수이다.
    • 이항함수(binary function)는 두 개의 전달인자로 호출하는 함수이다.

    • bool 값을 리턴하는 단항 함수는 조건(predicate)이다.
    • bool 값을 리턴하는 이항 함수는 이항 조건(binary predicate)이다.

    • 하나의 값을 함수 전달인자 예제
//functor.cpp -- 펑크터를 사용한다.
#include <iostream>
#include <list>
#include <iterator>

template<class T>
class TooBig
{
private:
T cutoff;
public:
TooBig(const T & t) : cutoff(t){}
bool operator()(const T & v){return v > cutoff;}
};

int main()
{
using namespace std;
TooBig<int> f100(100);
list<int> yadayada;
list<int> etcetera;
int vals[10] = {50, 100, 90, 180, 60, 210, 415, 88, 188, 201};

yadayada.insert(yadayada.begin(), vals, vals+10);
etcetera.insert(etcetera.begin(), vals, vals+10);
ostream_iterator<int, char> out(cout, " ");
cout<<"원래의 리스트: \n";
copy(yadayada.begin(),yadayada.end(),out);
cout<<endl;
copy(etcetera.begin(),etcetera.end(),out);
cout<<endl;

yadayada.remove_if(f100);
etcetera.remove_if(TooBig<int>(200));
cout<<"정비된 리스트: \n";
copy(yadayada.begin(), yadayada.end(), out);
cout<<endl;
copy(etcetera.begin(), etcetera.end(), out);
cout<<endl;

cin.get();
return 0;
}

    • 두 개의 전달인자를 사용하는 템플릿 함수를 이미 가지고 있다고 가정하자, 클래스를 사용하여 이것을 하나의 전달인자를 사용하는 함수 객체로 변환할 수 있다.
      • template<class T>
        bool tooBig(const T & val, const T & lim)
        {
             return val > lim;
        }
      • template<class T>
        class TooBig2
        {
        private:
           T cutoff;
        public:
           TooBig2(const T & t) : cutoff(t) {}
           bool operator() (const T & v) { return tooBig<T>(v,cutoff);}
        }

      • 이제 다음과 같이 사용할 수 있다.
        TooBig2<int> tB100(100)
        int x;
        cin>>x;
        if(tB100(x)) //if(tooBig(x,100))과 같다.
           ...
  • 미리 정의된 펑크터
    • STL은 몇 가지 기본적인 펑크터를 정의한다. 예를들어 transform()을 생각해보자
      • const int LIM = 5;
        double arr1[LIM] = {36, 39, 42, 45, 48};
        vector<double> gr8(arr,arr1 + LIM);
        ostream_iterator<double, char> out(cout," ");
        transform(gr8.begin(), gr8.end(), out, sqrt);
      • 이 코드는 각 원소의 제곱근을 계산하고, 그 결과로 얻어지는 값들을 출력 스트림으로 보낸다. 목적지 이터레이터가 오리지널 범위 안에 있을 수도 있다. 예를 들어 이 예제에 있는 out을 gr8.begin()으로 바꾸면, 새로운 값들이 이 전의 값 위에 쓰여진다. 이때 사용되는 펑크터는 하나의 전달인자로 동작하는 것이어야 한다.
      • transform(gr8.begin(), gr8.end(), m8, begin(), out, mean);
      • 이 코드는 두 개의 전달인자를 취하는 함수를 하용한다. mean(double, double) 이 구 값의 평균을 리턴한다.
      • functional 헤더파일을 이용할 수있다.
      • #include <functional>
        ...
        plus<double> add;
        double y = add(2.2, 3.4); //plus<double>::operator()()를 사용
        그러나 그것은 함수 객체를 전달인자로 제공하기 쉽게 만드다
        transform(gr8.begin(), gr8.end(), m8.begin(), out, plus<double>());
      • binder함수

//funadap.cpp -- 함수 어댑터를 사용한다.
#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
#include <functional>


void Show(double);
const int LIM = 5;

int main()
{
using namespace std;
double arr1[LIM] = { 36, 39, 42, 45, 48};
double arr2[LIM] = { 25, 27, 29, 31, 33};
vector<double> gr8(arr1, arr1 + LIM);
vector<double> m8(arr2, arr2 + LIM);
cout.setf(ios_base::fixed);
cout.precision(1);
cout<<"gr8:\t";
for_each(gr8.begin(), gr8.end(), Show);
cout<<endl;
cout<<"m8:\t";
for_each(m8.begin(), m8.end(), Show);
cout<<endl;

vector<double> sum(LIM);
transform(gr8.begin(), gr8.end(), m8.begin(), sum.begin(), plus<double>());
cout<<"sum:\t";
for_each(sum.begin(), sum.end(), Show);
cout<<endl;

vector<double> prod(LIM);
transform(gr8.begin(), gr8.end(),prod.begin(),bind1st(multiplies<double>(),2.5));
cout<<"prod:\t";
for_each(prod.begin(),prod.end(),Show);
cout<<endl;
cin.get();
return 0;
}

void Show(double v)
{
std::cout.width(6);
std::cout<<v<<' ';
}


  • STL과 string 클래스
    • next_permutation 알고리즘은 어던 범위의 내용을 그 다음 치환으로 변환한다. 문자열인 경우에, 치환들은 알파벳 또는 가나다 오름차순으로 배치된다.



//strgstl.cpp -- STL을 string에 적용한다.
#include <iostream>
#include <string>
#include <algorithm>

int main()
{
using namespace std;

string letters;

cout<<"글자 그룹을 입력하십시오(끝내려면 quit):";
while(cin>>letters && letters != "quit")
{
cout<<letters<<"의 모든 치환들 : "<<endl;
sort(letters.begin(), letters.end());
cout<<letters<<endl;
while(next_permutation(letters.begin(),letters.end()))
cout<<letters<<endl;
cout<<"다음 시퀀스를 입력하십시오(끝내려면 quit) :";
}
cout<<"프로그램을 종료합니다.";
return 0;

}






//단어 빈도수 검사 프로그램
//usealgo.cpp -- 몇가지 STL 요소들을 사용한다.
#pragma warning (diable : 4244)
#include <iostream>
#include <string>
#include <vector>
#include <set>
#include <map>
#include <iterator>
#include <algorithm>
#include <cctype>
using namespace std;

char toLower(char ch) { return toLower(ch);}
string & ToLower(string & st);
void display(const string & s);

int main()
{
vector<string> words;
cout<<"단어들을 입력하십시오(끝내려면 quit):\n";
string input;
while(cin>>input && input != "quit")
words.push_back(input);

cout<<"다음과 같은 단어들을 입력하셨습니다.";
for_each(words.begin(), words.end(), display);
cout << endl;

//단어들을 소문자로 변환하여 집합에 넣는다.
set<string> wordset;
transform(words.begin(), words.end(), insert_iterator<set<string>>(wordset, wordset.begin()),ToLower);
cout<<"\n단어들의 알파벳순 리스트: \n";
for_each(wordset.begin(), wordset.end(), display);
cout<<endl;

//단어와 빈도를 맵에 넣는다.
map<string, int> wordmap;
set<string>::iterator si;
for(si = wordset.begin(), si != wordset.end(), si++)
{
wordmap[*si] = count(words.begin(),words.end(), *si);/
}

//맵의 내용을 출력한다.
cout<<"\n단어별 빈도:\n";
for(si = wordset.begin(); si!=wordset.end(); si++)
cout<<*si<<": "<<wordmap[*si]<<endl;
return 0;
}
string & ToLower(string & st)
{
transform(st.begin(), st.end(), st.begin(), toLower);
return st;
}

void display(const string & s)
{
cout<<s<<" ";
}





저작자 표시
posted by 초록영필
prev 1 2 3 4 5 ... 8 next