C ++: Enum değerini metin olarak yazdır


91

Böyle bir numaram varsa

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

Sonra konsola yazdırmak istiyorum

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

ama istediğim şey "ErrorA" metni, if / switch'i kullanmadan yapabilir miyim?
Ve bunun için çözümünüz nedir?


Sanırım cevabım oldukça iyi, bir göz atabilir misin?
Xiao


Yanıtlar:


64

Haritayı kullanarak:

#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;   
}

Doğrusal aramayla yapı dizisini kullanma:

#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;   
}

Anahtar / kasayı kullanma:

#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. Bir hash-map kullanmak yerine sadece bir switch-case yapın. Karmaşıklığın artması iyi bir şey değildir.
Simon

8
İyi bir nokta. Bir dahaki sefere yapacağım :) Ama şimdi aradığım işlevselliği eklemek için yayınınızı zaten düzenlediğinizi görüyorum. Aferin!
Simon

1
#p nedir? üçüncü örnekte enum yerine bir enum sınıfı kullanıyorsam, sınıf adı olmadan yalnızca enum dizesini almak mümkün müdür?
rh0x

2
#pönişlemci p dizgesini belirtir. Yani çağıran PROCESS_VAL(ErrorA)irade çıktı: case(ErrorA): s = "ErrorA"; break;.
Nashenas

Ben optimal çözüm olarak bunu düşünmüyoruz: Nedenleri: 1) Ben sürdürmek zorunda iki kezenum ben bir olduğunu düşünüyorum değerleri NO-GO . 2) Çözümü doğru anladığımda sadece biri için çalışıyor enum.
Peter VARGA

30

Eşleşen değerlere sahip dizelerden oluşan bir dizi veya vektör kullanın:

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

cout << ErrorTypes[anError];

DÜZENLE: Yukarıdaki çözüm, numaralandırma bitişik olduğunda, yani 0'dan başladığında ve atanmış değer olmadığında uygulanabilir. Sorudaki enum ile mükemmel şekilde çalışacaktır.

Numaralandırmanın 0'dan başlamaması durumunda daha fazla kanıtlamak için şunu kullanın:

cout << ErrorTypes[anError - ErrorA];

4
ne yazık ki enum, elemanlara değer atamamıza izin verir. Bitişik olmayan numaralandırma, satır 'enum Durum {OK = 0, Fail = -1, OutOfMemory = -2, IOError = -1000, ConversionError = -2000}' (böylece daha sonra IOErrors ekleyebilirsiniz -1001-1999 aralığına kadar)
Nordic Mainframe

@Luther: Evet, bu yalnızca çoğu numaralandırmanın olduğu bitişik numaralandırmalarla çalışacaktır . Numaralandırmanın bitişik olmaması durumunda başka bir yaklaşım, yani haritalar kullanmanız gerekecektir. Ancak bitişik enum durumunda, bu yaklaşımı aşırı karmaşık hale getirmemek için kullanmanızı öneririm.
Igor Oks

2
Öyleyse, meslektaşım bir numaraya NewValue eklerse ve ErrorTypes dizisini güncellemezse, ErrorTypes [NewValue] ne verir? Negatif enum değerlerini nasıl işlerim?
Nordic Mainframe

2
@Luther: ErrorTypes'ı güncel tutmanız gerekecek. Yine, basitlik ve evrensellik arasında bir değiş tokuş vardır, kullanıcı için neyin daha önemli olduğuna bağlıdır. Negatif enum değerleriyle ilgili sorun nedir?
Igor Oks

1
Bu dizinin bellek verimliliği için statik olması gerekmez mi? ve güvenlik için const?
Jonathan

15

Boost.Preprocessor'a dayalı bir örnek:

#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, Bu çözüm, yukarıdaki lua yanıtı gibi harici bir araca dayanmaz, ancak C ++ 'dır, DRY ilkesini izler ve kullanıcı sözdizimi okunabilir (doğru biçimlendirilmişse. BTW, Ters Eğik Çizgilere ihtiyacınız yoktur. biraz daha doğal görünen DEFINE_ENUM kullanırken, IMO)
Fabio Fracassi

3
@Fabio Fracassi: "Bu çözüm harici bir araca dayanmıyor" Boost harici bir araçtır - standart olmayan C ++ kitaplığı. Üstelik biraz fazla uzun. Bir sorunun çözümü olabildiğince basit olmalıdır. Bunu ... nitelemek gelmez
SIGTERM

2
Aslında, kodun çoğunu koyabileceğiniz tek şey (aslında gerçek tanım dışında tümü) tek bir başlığa yerleştirilebilir. yani bu aslında burada sunulan en kısa çözümdür. Ve harici olmanın desteklenmesi için, evet, ancak yukarıdaki lua betiği gibi kaynağın bölümlerini önişlemek için dil dışı bir betikten daha az. Ayrıca boost, standartlara o kadar yakındır ki, her C ++ programcısının araç kutusunda olması gerekir. Sadece IMHO, elbette
Fabio Fracassi

[Makro çağrıda gereksiz satırsonu kaçışını kaldırdım. Bunlara gerek yoktur: Bir makro çağırma birden fazla satıra yayılabilir.]
James McNellis

Makro kullanmaya çalıştığımda DEFINE_ENUMbana hata veriyor multiple definition of `format_ProgramStatus(ProgramStatus)'.
HelloGoodbye

6

enumGirişlerinizi harici bir dosyada listelemek istiyorsanız daha basit bir ön işlemci numarası kullanabilirsiniz .

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

Ardından bir kaynak dosyada, dosyayı bir içerme dosyası gibi ele alırsınız, ancak ne ERROR_DEFyapmak istediğinizi tanımlarsınız .

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 << "]";
    }
}

Bir kaynak tarama aracı (cscope gibi) kullanıyorsanız, harici dosya hakkında bilgi vermeniz gerekir.


4

Burada yardımcı olabilecek bir tartışma olmuştur: C ++ numaralandırmasını dizeye dönüştürmenin basit bir yolu var mı?

GÜNCELLEME: Burada Lua için karşılaştığı her adlandırılmış enum için << operatörü oluşturan bir komut dosyası. Bu, daha az basit durumlarda çalışmasını sağlamak için biraz çalışma gerektirebilir [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)

Bu girdi verildiğinde:

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

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

Ürettiği:

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)"; }}

Yani bu muhtemelen sizin için bir başlangıç.

[1] farklı veya ad alanı olmayan kapsamlardaki numaralandırmalar, komma içeren başlatıcı ifadeler içeren numaralandırmalar, vb.


Göndericiye cevabını düzeltme fırsatı vermek için burada '-1' şeklinde yorum yapmak bir gelenek değil mi? Sadece soruyorum ..
Nordic Mainframe

2
Aşağıdaki (Philip'ten) Boost PP çözümünün daha iyi olduğunu düşünüyorum, çünkü harici araçların kullanılması bakım açısından çok pahalı. ama -1 yok çünkü cevap başka türlü geçerli
Fabio Fracassi

4
Boost PP aynı zamanda bir bakım sorunudur, çünkü herkesin korkunç , kırılması kolay (genellikle kullanılamaz hata mesajları veren) ve yalnızca sınırlı kullanılabilirliğe sahip (lua / python / perl) Boost PP metal dilini konuşması gerekir. harici veri). Proje politikası nedeniyle izin verilmeyebilecek olan bağımlılık listenize destek ekler. Ayrıca, bir DSL'de numaralandırmalarınızı tanımlamanızı gerektirdiğinden istilacıdır. En sevdiğiniz kaynak kodu aracınız veya IDE bu konuda sorun yaşayabilir. Ve sonuncu ama en az değil: genişletmede bir kesme noktası ayarlayamazsınız.
Nordic Mainframe

4

Bir enum tanımladığımda bir dize dizisi kullanıyorum:

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

Bu iyi bir yol

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

Bir dizi karakter dizisi ile yazdırın

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

Bunun gibi

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

2
Ya numaram 2000'den başlarsa? Bu çözüm işe yaramayacak.
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

Bir stl harita konteyneri kullanabilirsiniz ...

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
Bu nasıl bir harita bundan daha iyi switch(n) { case XXX: return "XXX"; ... }? Hangisinin O (1) araması var ve başlatılması gerekmiyor? Veya numaralandırmalar çalışma sırasında bir şekilde değişir mi?
Nordic Mainframe

Switch deyimini (veya bir işlev işaretçisini de) kullanma konusunda @ Luther Blissett'e katılıyorum
KedarX

1
Pekala, "Bu benim sevgili arkadaşım Luther Hata A veya" Bu benim sevgili arkadaşım Adrian Hata B "şeklinde bir çıktı vermek isteyebilir. Ayrıca, haritayı kullanmak, iostream imzalarına olan bağımlılığı ortadan kaldırır, böylece onu başka yerlerde kullanmakta özgürdür. dize bitiştirmeli kod, örneğin, dize x = "Merhaba" + m [HataA], vb.
Adrian Regan

Eminim std :: map birçok if's ve switch içerir. Ben olmadan bunu nasıl bu okurdum bana sahip olursa en ve anahtarlar yazma '
İskandinav Mainframe

Emin öyle değilim, ama kesinlikle ... sorunu çözmek için Lua bir senaryo yazmaya gerektirmez
Adrian Regan

1

Bu problem için şöyle bir yardım işlevi yapıyorum:

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;
}

Doğrusal arama genellikle std::mapbunun gibi küçük koleksiyonlardan daha etkilidir .


1

Bu çözüm, herhangi bir veri yapısı kullanmanızı veya farklı bir dosya oluşturmanızı gerektirmez.

Temel olarak, tüm enum değerlerinizi bir #define içinde tanımlar, sonra bunları << operatöründe kullanırsınız. @ Jxh'nin cevabına çok benzer.

son yineleme için ideone bağlantısı: http://ideone.com/hQTKQp

Tam kod:

#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;
}

Çıktı:

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

Bu şekilde yapmanın güzel bir yanı, ihtiyacınız olduğunu düşünüyorsanız her hata için kendi özel mesajlarınızı da belirtebilmenizdir:

#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;
}

Çıktı:

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

Hata kodlarınızı / açıklamalarınızı çok açıklayıcı yapmayı seviyorsanız, bunları üretim sürümlerinde istemeyebilirsiniz. Bunları kapatmak, böylece yalnızca değerin yazdırılması kolaydır:

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;
    }
}

Çıktı:

Error: 0
Error: 1
Error: 2

Bu durumda, 525 hata numarasını bulmak bir PITA olacaktır. İlk numaralandırmadaki sayıları manuel olarak şu şekilde belirleyebiliriz:

#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;
    }
    }
}

Çıktı:

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

Buna ne dersin?

    enum class ErrorCodes : int{
          InvalidInput = 0
    };

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

vs ... Bunun oldukça uydurma bir örnek olduğunu biliyorum ama bence uygulanabilir ve gerekli olduğu yerde uygulaması var ve kesinlikle bunun için bir komut dosyası yazmaktan daha kısa.


0

Ön işlemciyi kullanın:

#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();
    }
}

Bu yaklaşımın avantajı şudur: - yine de anlaşılması kolaydır - çeşitli ziyaretlere izin verir (sadece diziye değil)

İlkini bırakmaya istekli iseniz, kendinize bir FOREACH () makrosu oluşturun ve ardından #define ERROR_VALUES() (ErrorA, ErrorB, ErrorC)ziyaretçilerinizi FOREACH () açısından yazın. Ardından bir kod incelemesi geçmeyi deneyin :).


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.