C ++ : enum 값을 텍스트로 인쇄


89

이와 같은 열거 형이 있으면

enum Errors
{ErrorA=0, ErrorB, ErrorC};

그런 다음 콘솔로 인쇄하고 싶습니다.

Errors anError = ErrorA;
cout<<anError;/// 0 will be printed

하지만 내가 원하는 것은 "ErrorA"라는 텍스트입니다. if / switch를 사용하지 않고 할 수 있습니까?
이에 대한 해결책은 무엇입니까?


제 대답이 꽤 좋은 것 같아요. 좀 봐주 시겠어요?
Xiao


답변:


63

지도 사용 :

#include <iostream>
#include <map>
#include <string>

enum Errors {ErrorA=0, ErrorB, ErrorC};

std::ostream& operator<<(std::ostream& out, const Errors value){
    static std::map<Errors, std::string> strings;
    if (strings.size() == 0){
#define INSERT_ELEMENT(p) strings[p] = #p
        INSERT_ELEMENT(ErrorA);     
        INSERT_ELEMENT(ErrorB);     
        INSERT_ELEMENT(ErrorC);             
#undef INSERT_ELEMENT
    }   

    return out << strings[value];
}

int main(int argc, char** argv){
    std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl;
    return 0;   
}

선형 검색과 함께 구조 배열 사용 :

#include <iostream>
#include <string>

enum Errors {ErrorA=0, ErrorB, ErrorC};

std::ostream& operator<<(std::ostream& out, const Errors value){
#define MAPENTRY(p) {p, #p}
    const struct MapEntry{
        Errors value;
        const char* str;
    } entries[] = {
        MAPENTRY(ErrorA),
        MAPENTRY(ErrorB),
        MAPENTRY(ErrorC),
        {ErrorA, 0}//doesn't matter what is used instead of ErrorA here...
    };
#undef MAPENTRY
    const char* s = 0;
    for (const MapEntry* i = entries; i->str; i++){
        if (i->value == value){
            s = i->str;
            break;
        }
    }

    return out << s;
}

int main(int argc, char** argv){
    std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl;
    return 0;   
}

스위치 / 케이스 사용 :

#include <iostream>
#include <string>

enum Errors {ErrorA=0, ErrorB, ErrorC};

std::ostream& operator<<(std::ostream& out, const Errors value){
    const char* s = 0;
#define PROCESS_VAL(p) case(p): s = #p; break;
    switch(value){
        PROCESS_VAL(ErrorA);     
        PROCESS_VAL(ErrorB);     
        PROCESS_VAL(ErrorC);
    }
#undef PROCESS_VAL

    return out << s;
}

int main(int argc, char** argv){
    std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl;
    return 0;   
}

12
-1. 해시 맵을 사용하는 대신 스위치 케이스를 수행하십시오. 복잡성 증가는 좋은 것이 아닙니다.
Simon

8
좋은 지적. 다음 번에는 :)하지만 이제는 내가 찾고 있던 기능을 추가하기 위해 이미 게시물을 편집했음을 확인했습니다. 잘 했어!
Simon

1
#p는 무엇입니까? enum 대신 세 번째 예제에서 enum 클래스를 사용하는 경우 클래스 이름없이 enum 문자열 만 가져올 수 있습니까?
rh0x

2
#pp를 묶는 전 처리기입니다. 따라서 호출 PROCESS_VAL(ErrorA)하면 case(ErrorA): s = "ErrorA"; break;.
Nashenas

나는 그것을 최적의 해결책으로 생각하지 않는다. 이유 : 1) 나는 NO-GO 라고 생각 하는 가치를 두 배로 유지해야한다 . 2) 솔루션을 올바르게 이해하면 . enumenum
피터 바르

29

값이 일치하는 문자열의 배열 또는 벡터를 사용합니다.

char *ErrorTypes[] =
{
    "errorA",
    "errorB",
    "errorC"
};

cout << ErrorTypes[anError];

편집 : 위의 솔루션은 열거 형이 연속적 일 때 적용 할 수 있습니다. 즉, 0에서 시작하고 할당 된 값이 없습니다. 질문의 열거 형과 완벽하게 작동합니다.

enum이 0에서 시작하지 않는 경우를 추가로 증명하려면 다음을 사용하십시오.

cout << ErrorTypes[anError - ErrorA];

4
불행히도 enum을 사용하면 요소에 값을 할당 할 수 있습니다. 연속적이지 않은 열거 형이있는 경우 어떻게 접근합니까? 행 'enum Status {OK = 0, Fail = -1, OutOfMemory = -2, IOError = -1000, ConversionError = -2000}`(나중에 IOErrors를 추가 할 수 있음) -1001-1999 범위)
Nordic Mainframe

@Luther : 예, 이것은 대부분의 열거 형인 연속 열거 형에서만 작동합니다 . 열거 형이 연속적이지 않은 경우 다른 접근 방식, 즉 맵을 사용해야합니다. 그러나 연속 열거 형의 경우이 접근 방식을 사용하는 것이 좋습니다.
Igor Oks 2010-07-27

2
따라서 동료가 NewValue를 열거 형에 추가하고 ErrorTypes 배열을 업데이트하지 않으면 ErrorTypes [NewValue]는 무엇을 산출합니까? 음의 열거 형 값을 어떻게 처리합니까?
Nordic Mainframe

2
@Luther : ErrorTypes를 최신 상태로 유지해야합니다. 다시 말하지만, 단순성과 보편성 사이에는 트레이드 오프가 있으며, 사용자에게 무엇이 더 중요한지에 따라 다릅니다. 음수 열거 형 값의 문제점은 무엇입니까?
Igor Oks 2010-07-27

1
이 어레이는 메모리 효율성을 위해 정적이어야하지 않습니까? 안전을 위해 const?
Jonathan

15

다음은 Boost.Preprocessor를 기반으로 한 예입니다.

#include <iostream>

#include <boost/preprocessor/punctuation/comma.hpp>
#include <boost/preprocessor/control/iif.hpp>
#include <boost/preprocessor/comparison/equal.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/seq/size.hpp>
#include <boost/preprocessor/seq/seq.hpp>


#define DEFINE_ENUM(name, values)                               \
  enum name {                                                   \
    BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_VALUE, , values)          \
  };                                                            \
  inline const char* format_##name(name val) {                  \
    switch (val) {                                              \
      BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_FORMAT, , values)       \
    default:                                                    \
        return 0;                                               \
    }                                                           \
  }

#define DEFINE_ENUM_VALUE(r, data, elem)                        \
  BOOST_PP_SEQ_HEAD(elem)                                       \
  BOOST_PP_IIF(BOOST_PP_EQUAL(BOOST_PP_SEQ_SIZE(elem), 2),      \
               = BOOST_PP_SEQ_TAIL(elem), )                     \
  BOOST_PP_COMMA()

#define DEFINE_ENUM_FORMAT(r, data, elem)             \
  case BOOST_PP_SEQ_HEAD(elem):                       \
  return BOOST_PP_STRINGIZE(BOOST_PP_SEQ_HEAD(elem));


DEFINE_ENUM(Errors,
            ((ErrorA)(0))
            ((ErrorB))
            ((ErrorC)))

int main() {
  std::cout << format_Errors(ErrorB) << std::endl;
}

2
+1,이 솔루션은 위의 lua 답변과 같은 외부 도구에 의존하지 않지만 순수한 C ++이며 DRY 원칙을 따르며 사용자 구문을 읽을 수 있습니다 (올바른 형식 인 경우. BTW, 백 슬래시가 필요하지 않음) ) IMO, 좀 더 자연스럽게 보이는 DEFINE_ENUM 사용할 때
파비오 Fracassi

3
@Fabio Fracassi : "이 솔루션은 외부 도구에 의존하지 않습니다."Boost는 비표준 C ++ 라이브러리 인 외부 도구입니다. 게다가 너무 깁니다. 문제에 대한 해결책은 가능한 한 간단해야합니다. 이 사람은 ... 필요하지 않습니다
SIGTERM

2
실제로 대부분의 코드 (실제로 실제 정의를 제외한 모든 코드)를 단일 헤더에 넣을 수있는 전부입니다. 그래서 이것은 실제로 여기에 제시된 가장 짧은 솔루션입니다. 그리고 부스트는 외부 적이지만, 위의 lua 스크립트처럼 소스의 일부를 전처리하기위한 언어 외 스크립트보다는 적습니다. 부스트는 표준에 너무 가깝기 때문에 모든 C ++ 프로그래머 도구 상자에 있어야합니다. 물론 IMHO 만
Fabio Fracassi 2010-07-27

[매크로 호출에서 불필요한 새 줄 이스케이프를 제거했습니다. 필요하지 않습니다. 매크로 호출은 여러 줄에 걸쳐있을 수 있습니다.]
James McNellis dec

매크로 를 사용하려고 DEFINE_ENUM하면 오류가 발생 multiple definition of `format_ProgramStatus(ProgramStatus)'합니다.
HelloGoodbye

6

enum외부 파일에 항목 을 나열하려는 경우 더 간단한 전 처리기 트릭을 사용할 수 있습니다 .

/* file: errors.def */
/* syntax: ERROR_DEF(name, value) */
ERROR_DEF(ErrorA, 0x1)
ERROR_DEF(ErrorB, 0x2)
ERROR_DEF(ErrorC, 0x4)

그런 다음 소스 파일에서 파일을 포함 파일처럼 취급하지만 수행 할 작업을 정의합니다 ERROR_DEF.

enum Errors {
#define ERROR_DEF(x,y) x = y,
#include "errors.def"
#undef ERROR_DEF
};

static inline std::ostream & operator << (std::ostream &o, Errors e) {
    switch (e) {
    #define ERROR_DEF(x,y) case y: return o << #x"[" << y << "]";
    #include "errors.def"
    #undef ERROR_DEF
    default: return o << "unknown[" << e << "]";
    }
}

cscope와 같은 소스 검색 도구를 사용하는 경우 외부 파일에 대해 알려야합니다.


4

여기에 도움이 될 수있는 토론이 있습니다. C ++ 열거 형을 문자열로 변환하는 간단한 방법이 있습니까?

업데이트 : 여기에서 만나는 각 명명 된 열거 형에 대해 operator <<를 만드는 Lua 용 스크립트입니다. 덜 간단한 경우에 작동하도록하려면 약간의 작업이 필요할 수 있습니다 [1].

function make_enum_printers(s)
    for n,body in string.gmatch(s,'enum%s+([%w_]+)%s*(%b{})') do
    print('ostream& operator<<(ostream &o,'..n..' n) { switch(n){') 
    for k in string.gmatch(body,"([%w_]+)[^,]*") do
    print('  case '..k..': return o<<"'..k..'";')
    end
    print('  default: return o<<"(invalid value)"; }}')
    end
end

local f=io.open(arg[1],"r")
local s=f:read('*a')
make_enum_printers(s)

이 입력이 주어지면 :

enum Errors
{ErrorA=0, ErrorB, ErrorC};

enum Sec {
    X=1,Y=X,foo_bar=X+1,Z
};

다음을 생성합니다.

ostream& operator<<(ostream &o,Errors n) { switch(n){
  case ErrorA: return o<<"ErrorA";
  case ErrorB: return o<<"ErrorB";
  case ErrorC: return o<<"ErrorC";
  default: return o<<"(invalid value)"; }}
ostream& operator<<(ostream &o,Sec n) { switch(n){
  case X: return o<<"X";
  case Y: return o<<"Y";
  case foo_bar: return o<<"foo_bar";
  case Z: return o<<"Z";
  default: return o<<"(invalid value)"; }}

그래서 그것은 아마도 당신을위한 시작일 것입니다.

[1] 다른 또는 비 네임 스페이스 범위의 열거 형, komma를 포함하는 이니셜 라이저 표현식이있는 열거 형 등.


포스터에게 답을 고칠 수있는 기회를주기 위해 '-1'을 언급하는 것이 관습이 아닙니까? 그냥 묻고 ..
Nordic Mainframe

2
외부 도구를 사용하는 것은 유지 관리 비용이 매우 많이 들기 때문에 아래의 부스트 PP 솔루션 (Philip)이 더 좋습니다. 하지만 -1 대답은 그렇지 유효하기 때문에
파비오 Fracassi

4
부스트 PP는 또한 당신이 부스트 PP의 메타 언어, 말을 모두 필요하기 때문에, 유지 보수 문제 끔찍한를 쉽게 휴식과 제한된 유용성 임의의 (루아 / 파이썬 / 펄 생성 할 수있는 코드 (일반적으로 사용할 수없는 오류 메시지를 제공) 외부 데이터). 프로젝트 정책으로 인해 허용되지 않을 수도있는 종속성 목록에 부스트를 추가합니다. 또한 DSL에서 열거 형을 정의해야하기 때문에 침습적입니다. 좋아하는 소스 코드 도구 또는 IDE에 문제가있을 수 있습니다. 그리고 마지막으로 중요한 점은 확장에서 중단 점을 설정할 수 없다는 것입니다.
Nordic Mainframe

4

열거 형을 정의 할 때마다 문자열 배열을 사용합니다.

Profile.h

#pragma once

struct Profile
{
    enum Value
    {
        Profile1,
        Profile2,
    };

    struct StringValueImplementation
    {
        const wchar_t* operator[](const Profile::Value profile)
        {
            switch (profile)
            {
            case Profile::Profile1: return L"Profile1";
            case Profile::Profile2: return L"Profile2";
            default: ASSERT(false); return NULL;
            }
        }
    };

    static StringValueImplementation StringValue;
};

Profile.cpp

#include "Profile.h"

Profile::StringValueImplementation Profile::StringValue;

4

이것은 좋은 방법입니다.

enum Rank { ACE = 1, DEUCE, TREY, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING };

문자 배열의 배열로 인쇄

const char* rank_txt[] = {"Ace", "Deuce", "Trey", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Jack", "Four", "King" } ;

이렇게

std::cout << rank_txt[m_rank - 1]

2
내 열거 형이 2000에서 시작하면 어떻게됩니까? 이 솔루션은 작동하지 않습니다.
Sitesh

3
#include <iostream>
using std::cout;
using std::endl;

enum TEnum
{ 
  EOne,
  ETwo,
  EThree,
  ELast
};

#define VAR_NAME_HELPER(name) #name
#define VAR_NAME(x) VAR_NAME_HELPER(x)

#define CHECK_STATE_STR(x) case(x):return VAR_NAME(x);

const char *State2Str(const TEnum state)
{
  switch(state)
  {
    CHECK_STATE_STR(EOne);
    CHECK_STATE_STR(ETwo);
    CHECK_STATE_STR(EThree);
    CHECK_STATE_STR(ELast);
    default:
      return "Invalid";
  }
}

int main()
{
  int myInt=12345;
  cout << VAR_NAME(EOne) " " << VAR_NAME(myInt) << endl;

  for(int i = -1; i < 5;   i)
    cout << i << " " << State2Str((TEnum)i) << endl;
  return 0;
}

2

stl 맵 컨테이너를 사용할 수 있습니다 ....

typedef map<Errors, string> ErrorMap;

ErrorMap m;
m.insert(ErrorMap::value_type(ErrorA, "ErrorA"));
m.insert(ErrorMap::value_type(ErrorB, "ErrorB"));
m.insert(ErrorMap::value_type(ErrorC, "ErrorC"));

Errors error = ErrorA;

cout << m[error] << endl;

4
이지도가 어떻게 더 나은 switch(n) { case XXX: return "XXX"; ... }가요? O (1) 조회가 있고 초기화 할 필요가없는 것은 무엇입니까? 아니면 열거 형은 런타임 중에 어떻게 든 변경됩니까?
Nordic Mainframe

나는 (너무 또는 함수 포인터) switch 문을 사용하여 @Luther Blissett에 동의
KedarX

1
글쎄, 그는 "이 내 친애하는 친구 Luther는 오류 A입니다. 또는"이 내 친애하는 친구 Adrian은 오류 B입니다. "라고 출력 할 수 있습니다. 또한 map을 사용하면 iostream 서명에 대한 의존성이 제거되므로 다른 곳에서 자유롭게 사용할 수 있습니다. 예를 들어, string x = "Hello"+ m [ErrorA] 등과 같은 문자열 연결이있는 코드.
Adrian Regan

std :: map에는 if와 스위치가 많이 포함되어 있다고 확신합니다. 나는 '없이 나는이 작업을 수행 할 수있는 방법으로이 글을 읽을 것 나를 가진 경우의 스위치 쓰기'
북유럽 메인 프레임

확실하지만 문제를 해결하기 위해 Lua로 스크립트를 작성하지 않아도됩니다.
Adrian Regan

1

이 문제에 대해 다음과 같은 도움말 기능을 수행합니다.

const char* name(Id id) {
    struct Entry {
        Id id;
        const char* name;
    };
    static const Entry entries[] = {
        { ErrorA, "ErrorA" },
        { ErrorB, "ErrorB" },
        { 0, 0 }
    }
    for (int it = 0; it < gui::SiCount; ++it) {
        if (entries[it].id == id) {
            return entries[it].name;
        }
    }
   return 0;
}

선형 검색은 일반적으로 std::map이와 같은 소규모 컬렉션 보다 효율적 입니다.


1

이 솔루션은 데이터 구조를 사용하거나 다른 파일을 만들 필요가 없습니다.

기본적으로 모든 열거 형 값을 #define에 정의한 다음 << 연산자에서 사용합니다. @jxh의 답변과 매우 유사합니다.

최종 반복을위한 ideone 링크 : http://ideone.com/hQTKQp

전체 코드 :

#include <iostream>

#define ERROR_VALUES ERROR_VALUE(NO_ERROR)\
ERROR_VALUE(FILE_NOT_FOUND)\
ERROR_VALUE(LABEL_UNINITIALISED)

enum class Error
{
#define ERROR_VALUE(NAME) NAME,
    ERROR_VALUES
#undef ERROR_VALUE
};

inline std::ostream& operator<<(std::ostream& os, Error err)
{
    int errVal = static_cast<int>(err);
    switch (err)
    {
#define ERROR_VALUE(NAME) case Error::NAME: return os << "[" << errVal << "]" #NAME;
    ERROR_VALUES
#undef ERROR_VALUE
    default:
        // If the error value isn't found (shouldn't happen)
        return os << errVal;
    }
}

int main() {
    std::cout << "Error: " << Error::NO_ERROR << std::endl;
    std::cout << "Error: " << Error::FILE_NOT_FOUND << std::endl;
    std::cout << "Error: " << Error::LABEL_UNINITIALISED << std::endl;
    return 0;
}

산출:

Error: [0]NO_ERROR
Error: [1]FILE_NOT_FOUND
Error: [2]LABEL_UNINITIALISED

이 방법의 좋은 점은 필요하다고 생각되는 경우 각 오류에 대해 고유 한 사용자 지정 메시지를 지정할 수도 있다는 것입니다.

#include <iostream>

#define ERROR_VALUES ERROR_VALUE(NO_ERROR, "Everything is fine")\
ERROR_VALUE(FILE_NOT_FOUND, "File is not found")\
ERROR_VALUE(LABEL_UNINITIALISED, "A component tried to the label before it was initialised")

enum class Error
{
#define ERROR_VALUE(NAME,DESCR) NAME,
    ERROR_VALUES
#undef ERROR_VALUE
};

inline std::ostream& operator<<(std::ostream& os, Error err)
{
    int errVal = static_cast<int>(err);
    switch (err)
    {
#define ERROR_VALUE(NAME,DESCR) case Error::NAME: return os << "[" << errVal << "]" #NAME <<"; " << DESCR;
    ERROR_VALUES
#undef ERROR_VALUE
    default:
        return os << errVal;
    }
}

int main() {
    std::cout << "Error: " << Error::NO_ERROR << std::endl;
    std::cout << "Error: " << Error::FILE_NOT_FOUND << std::endl;
    std::cout << "Error: " << Error::LABEL_UNINITIALISED << std::endl;
    return 0;
}

산출:

Error: [0]NO_ERROR; Everything is fine
Error: [1]FILE_NOT_FOUND; File is not found
Error: [2]LABEL_UNINITIALISED; A component tried to the label before it was initialised

오류 코드 / 설명을 매우 설명 적으로 만들고 싶다면 프로덕션 빌드에 사용하지 않는 것이 좋습니다. 값만 인쇄되도록 끄는 것은 쉽습니다.

inline std::ostream& operator<<(std::ostream& os, Error err)
{
    int errVal = static_cast<int>(err);
    switch (err)
    {
    #ifndef PRODUCTION_BUILD // Don't print out names in production builds
    #define ERROR_VALUE(NAME,DESCR) case Error::NAME: return os << "[" << errVal << "]" #NAME <<"; " << DESCR;
        ERROR_VALUES
    #undef ERROR_VALUE
    #endif
    default:
        return os << errVal;
    }
}

산출:

Error: 0
Error: 1
Error: 2

이 경우 오류 번호 525를 찾는 것은 PITA입니다. 다음과 같이 초기 열거 형에 수동으로 숫자를 지정할 수 있습니다.

#define ERROR_VALUES ERROR_VALUE(NO_ERROR, 0, "Everything is fine")\
ERROR_VALUE(FILE_NOT_FOUND, 1, "File is not found")\
ERROR_VALUE(LABEL_UNINITIALISED, 2, "A component tried to the label before it was initialised")\
ERROR_VALUE(UKNOWN_ERROR, -1, "Uh oh")

enum class Error
{
#define ERROR_VALUE(NAME,VALUE,DESCR) NAME=VALUE,
    ERROR_VALUES
#undef ERROR_VALUE
};

inline std::ostream& operator<<(std::ostream& os, Error err)
{
    int errVal = static_cast<int>(err);
    switch (err)
    {
#ifndef PRODUCTION_BUILD // Don't print out names in production builds
#define ERROR_VALUE(NAME,VALUE,DESCR) case Error::NAME: return os << "[" #VALUE  "]" #NAME <<"; " << DESCR;
    ERROR_VALUES
#undef ERROR_VALUE
#endif
    default:
        return os <<errVal;
    }
}
    ERROR_VALUES
#undef ERROR_VALUE
#endif
    default:
    {
        // If the error value isn't found (shouldn't happen)
        return os << static_cast<int>(err);
        break;
    }
    }
}

산출:

Error: [0]NO_ERROR; Everything is fine
Error: [1]FILE_NOT_FOUND; File is not found
Error: [2]LABEL_UNINITIALISED; A component tried to the label before it was initialised
Error: [-1]UKNOWN_ERROR; Uh oh

0

이건 어때?

    enum class ErrorCodes : int{
          InvalidInput = 0
    };

    std::cout << ((int)error == 0 ? "InvalidInput" : "") << std::endl;

등 ... 나는 이것이 고도로 고안된 예라는 것을 알고 있지만 적용 가능하고 필요한 곳에 응용 프로그램이 있으며 스크립트를 작성하는 것보다 확실히 짧다고 생각합니다.


0

전처리기를 사용하십시오.

#define VISIT_ERROR(FIRST, MIDDLE, LAST) \
    FIRST(ErrorA) MIDDLE(ErrorB) /* MIDDLE(ErrorB2) */ LAST(ErrorC)

enum Errors
{
    #define ENUMFIRST_ERROR(E)  E=0,
    #define ENUMMIDDLE_ERROR(E) E,
    #define ENUMLAST_ERROR(E)   E
    VISIT_ERROR(ENUMFIRST_ERROR, ENUMMIDDLE_ERROR, ENUMLAST_ERROR)
    // you might undefine the 3 macros defined above
};

std::string toString(Error e)
{
    switch(e)
    {
    #define CASERETURN_ERROR(E)  case E: return #E;
    VISIT_ERROR(CASERETURN_ERROR, CASERETURN_ERROR, CASERETURN_ERROR)
    // you might undefine the above macro.
    // note that this will produce compile-time error for synonyms in enum;
    // handle those, if you have any, in a distinct macro

    default:
        throw my_favourite_exception();
    }
}

이 접근 방식의 장점은 다음과 같습니다.-여전히 이해하기 쉽지만-문자열뿐만 아니라 다양한 방문이 가능합니다.

첫 번째를 삭제하려면 FOREACH () 매크로를 직접 만든 다음 #define ERROR_VALUES() (ErrorA, ErrorB, ErrorC)FOREACH () 관점에서 방문자를 작성하십시오. 그런 다음 코드 검토를 통과 해보십시오. :).


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.