구조, 클래스 및 인터페이스

구조

구조는 모든 유형의 요소의 집합입니다(void 유형 제외). 따라서 구조는 서로 다른 유형의 논리적으로 관련된 데이터를 결합합니다.

구조 선언

구조 데이터 유형은 다음 설명에 따라 결정됩니다:

struct structure_name
  {
   elements_description
  };

구조명을 식별자(변수 또는 함수의 이름)로 사용할 수 없습니다. MQL5에서 구조 요소는 정렬 없이 서로 직접 따릅니다. C++에서는 다음 명령을 사용하여 컴파일러에 이러한 순서가 지정됩니다:

#pragma pack(1)

구조에서 다른 정렬을 수행하려면 적절한 크기로 보조 멤버인 "채우기"를 사용하십시오.

예제:

struct trade_settings
  {
   uchar  slippage;     // 허용되는 저하 크기 1바이트 값
   char   reserved1;    // 1바이트 건너뛰기
   short  reserved2;    // 2바이트 건너뛰기
   int    reserved4;    // 또 다른 4바이트 건너뛰기. 경계 8바이트 정렬 확인
   double take;         // 차익고정가격의 가치
   double stop;         // 보호용 스톱의 가격 가치
  };

정렬된 구조에 대한 이러한 설명은 가져온 dll-함수로 전송하는 경우에만 필요합니다.

주의: 이 예에서는 잘못 설계된 데이터를 보여 줍니다. 먼저 takestop 대용량 데이터를 double 유형으로 선언한 다음, slippage 멤버를 uchar 타입으로 선언하는 것이 좋습니다. 이 경우 데이터의 내부 표현은 #pragma pack()에 지정된 값에 관계없이 항상 동일합니다.

구조에 string 유형 및/또는 동적 배열의 객체 변수가 포함된 경우 컴파일러는 이러한 구조에 암시적 생성자를 할당합니다. 이 생성자는 string 유형의 모든 구조 멤버를 재설정하고 동적 배열의 객체를 올바르게 초기화합니다.

단순 구조

동적 배열의 문자열, 클래스 객체, 포인터 및 객체를 포함하지 않는 구조를 단순 구조체라고 합니다. 단순 구조의 변수와 해당 배열은 DLL에서 임포트한 함수에 매개 변수로 전달될 수 있습니다.

단순 구조의 복사는 두 가지 경우에만 허용됩니다:

  • 객체가 동일한 구조 유형에 속하는 경우
  • 한 구조가 다른 구조의 후손임을 의미하는 계통에 의해 객체가 연결되어 있는지 여부.

예를 들면, CustomMqlTick 커스텀 구조를 내장된 MqlTick와 동일한 내용으로 개발해봅시다. 컴파일러에서는 MqlTick 개체 값을 CustomMqlTick 유형 개체로 복사할 수 없습니다. 다이렉트 타입캐스팅-필요한 유형에 대한-도 컴파일 오류를 발생시킵니다:

      //--- 다른 유형의 단순 구조를 복사할 수 없습니다
      my_tick1=last_tick;               // 컴파일러는 여기에 오류를 반환합니다
     
      //--- 서로 다른 유형의 타입캐스팅 구조도 금지됩니다.
      my_tick1=(CustomMqlTick)last_tick;// 컴파일러는 여기에 오류를 반환합니다

따라서 구조 요소의 값을 하나씩 복사하는 옵션만 남아 있습니다. 동일한 유형의 CustomMqlTick 값을 복사할 수 있습니다.

      CustomMqlTick my_tick1,my_tick2;
      //--- 동일한 유형의 CustomMqlTick 개체를 다음과 같이 복사할 수 있습니다
      my_tick2=my_tick1;
     
      //--- 단순 CustomMqlTick 구조의 개체에서 배열을 만들고 여기에 값을 씁니다
      CustomMqlTick arr[2];
      arr[0]=my_tick1;
      arr[1]=my_tick2;

ArrayPrint() 함수는 저널에 arr[] 배열 값을 표시하기 위해 호출됩니다.

//+------------------------------------------------------------------+
//| 스크립트 프로그램 시작 함수                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- 내장된 MqlTick과 유사한 구조를 개발합니다
   struct CustomMqlTick
     {
      datetime          time;          // 최종가 업데이트 시간
      double            bid;           // 현재 입찰가
      double            ask;           // 현재 요청가
      double            last;          // 마지막 거래의 현재 가격
      ulong             volume;        // 현재 최종가의 거래량
      long              time_msc;      // 최종 가격 업데이트 시간(밀리초)
      uint              flags;         // 틱 플래그     
     };
   //--- 마지막 틱 값 가져오기
   MqlTick last_tick;
   CustomMqlTick my_tick1,my_tick2;
//--- MqlTick에서 CustomMqlTick으로 데이터 복사 시도
   if(SymbolInfoTick(Symbol(),last_tick))
     {
      //--- 관련 없는 단순 구조를 복사할 수 없습니다
      //1. my_tick1=last_tick;               // 컴파일러는 여기에 오류를 반환합니다
     
      //--- 서로 관련 없는 구조물을 타입캐스팅하는 것도 금지됩니다
      //2. my_tick1=(CustomMqlTick)last_tick;// 컴파일러는 여기에 오류를 반환합니다
     
      //--- 따라서 구조 멤버를 하나씩 복사합니다.     
      my_tick1.time=last_tick.time;
      my_tick1.bid=last_tick.bid;
      my_tick1.ask=last_tick.ask;
      my_tick1.volume=last_tick.volume;
      my_tick1.time_msc=last_tick.time_msc;
      my_tick1.flags=last_tick.flags;
     
      //--- 동일한 유형의 CustomMqlTick 개체를 다음과 같이 복사할 수 있습니다
      my_tick2=my_tick1;
     
      //--- 단순 CustomMqlTick 구조의 개체에서 배열을 만들고 여기에 값을 씁니다
      CustomMqlTick arr[2];
      arr[0]=my_tick1;
      arr[1]=my_tick2;
      ArrayPrint(arr);
//--- CustomMqlTick 유형의 개체가 포함된 배열의 값 표시 예제
      /*
                      [시간]   [입찰]   [요청]   [최근] [거래량]    [time_msc] [flags]
      [0] 2017.05.29 15:04:37 1.11854 1.11863 +0.00000  1450000 1496070277157       2
      [1] 2017.05.29 15:04:37 1.11854 1.11863 +0.00000  1450000 1496070277157       2           
      */
    }
   else
      Print("SymbolInfoTick() 실패, 오류 = ",GetLastError());
  }

두 번째 예는 계통을 기준으로 간단한 구조를 복사하는 기능을 보여줍니다. 우리가 고양이와 개의 구조가 파생된 동물의 기본 구조를 가지고 있다고 가정해보죠. 동물과 고양이는 물론 동물과 개도 서로 복사할 수 있지만 고양이와 개는 둘 다 동물 구조의 후손이지만 서로 복사할 수는 없습니다.

//--- 개를 묘사하는 구조
struct Dog: Animal
  {
   bool              hunting;       // 사냥 종
  };
//--- 고양이를 묘사하는 구조
struct Cat: Animal
  {
   bool              home;          // 가축
  };
//--- 자식 구조의 객체 생성
   Dog dog;
   Cat cat;
//--- 부모 항목에서 자식 항목으로 복사할 수 있습니다. (Animal ==> Dog)
   dog=some_animal;
   dog.swim=true;    // 강아지는 수영할수 있습니다
//--- 자식 구조의 객체를 복사할 수 없습니다 (Dog != Cat)
   cat=dog;        // 컴파일러가 오류를 반환합니다

전체 예제 코드:

//--- 동물을 묘사하는 기본 구조
struct Animal
  {
   int               head;          // 머리 수
   int               legs;          // 다리 수
   int               wings;         // 날개 수
   bool              tail;          // 꼬리
   bool              fly;           // 비행
   bool              swim;          // 수영  
   bool              run;           // 달리기
  };
//--- 개를 묘사하는 구조
struct Dog: Animal
  {
   bool              hunting;       // 사냥 종
  };
//--- 고양이를 묘사하는 구조
struct Cat: Animal
  {
   bool              home;          // 가축
  };
//+------------------------------------------------------------------+
//| 스크립트 프로그램 시작 함수                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- 기본 동물 유형의 개체를 만들고 설명합니다
   Animal some_animal;
   some_animal.head=1;
   some_animal.legs=4;
   some_animal.wings=0;
   some_animal.tail=true;
   some_animal.fly=false;
   some_animal.swim=false;
   some_animal.run=true;
//--- 자식 유형의 개체 만들기
   Dog dog;
   Cat cat;
//--- 부모 항목에서 자식 항목으로 복사할 수 있습니다. (Animal ==> Dog)
   dog=some_animal;
   dog.swim=true;    // 개는 수영할 수 있습니다
//--- 자식 구조의 객체를 복사할 수 없습니다 (Dog != Cat)
   //cat=dog;        // 컴파일러가 여기서 오류를 반환합니다
//--- 따라서 요소를 하나씩만 복사할 수 있습니다
   cat.head=dog.head;
   cat.legs=dog.legs;
   cat.wings=dog.wings;
   cat.tail=dog.tail;
   cat.fly=dog.fly;
   cat.swim=false;   // 고양이는 수영을 못합니다
//--- 자식 항목에서 부모 항목으로 값을 복사할 수 있습니다
  동물 코끼리;
   elephant=cat;
   elephant.run=false;// 코끼리는 달릴 수 없습니다
   elephant.swim=true;// 코끼리는 수영할 수 있습니다
//--- 배열 생성
   Animal animals[4];
   animals[0]=some_animal;
   animals[1]=dog;  
   animals[2]=cat;
   animals[3]=elephant;
//--- print out
   ArrayPrint(animals);
//--- 실행 결과
/*
      [머리] [다리] [날개] [꼬리] [비행] [수영] [달리기]
   [0]      1      4       0   true false  false  true
   [1]      1      4       0   true false   true  true
   [2]      1      4       0   true false  false false
   [3]      1      4       0   true false   true false
*/  
  }

간단한 유형을 복사하는 또 다른 방법은 유니언을 사용하는 것입니다. 구조물의 대상은 동일한 조합원이어야 합니다 – 조합의 예를 참조하십시오.

구조 멤버에 대한 액세스

구조명이 새 데이터 유형이 되므로 이 유형의 변수를 선언할 수 있습니다. 구조는 프로젝트 내에서 한 번만 선언할 수 있습니다. 구조 멤버는 다음을 사용하여 액세스합니다:포인트 연산 (.).

예제:

struct trade_settings
  {
   double take;         // 차익 확정 가격의 가치
   double stop;         // 보호 중지 가격의 가치
   uchar  slippage;     // 허용 저하 값
  };
//--- trade_settings 변수 생성 및 초기화
trade_settings my_set={0.0,0.0,5};  
if (input_TP>0) my_set.take=input_TP;

구조 및 클래스 필드 정렬을 위한 'pack' #

특수 pack 속성을 사용하면 구조 또는 클래스 필드의 정렬을 설정할 수 있습니다.

 pack([n])

여기서 n은 다음 값 중 하나입니다: 1, 2, 4, 8 또는 16. 없을 수도 있습니다.

예제:

   struct pack(sizeof(long)) MyStruct
     {
      // 구조 멤버는 8바이트 경계에 맞춰져야 합니다
     };
또는
   struct MyStruct pack(sizeof(long))
     {
      // 구조 멤버는 8바이트 경계에 맞춰져야 합니다
     };

'pack(1)' 은 기본적으로 구조에 적용됩니다. 즉, 구조 멤버가 메모리에 하나씩 위치하며 구조 크기는 해당 멤버의 크기를 합한 값과 같습니다.

예제:

//+------------------------------------------------------------------+
//| 스크립트 프로그램 시작 함수                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- 정렬이 없는 단순 구조
   struct Simple_Structure
     {
      char              c; // sizeof(char)=1
      short             s; // sizeof(short)=2
      int               i; // sizeof(int)=4
      double            d; // sizeof(double)=8
     };
   //--- 단순 구조 인스턴스 선언   
   Simple_Structure s;  
//--- 각 구조 멤버의 크기 표시  
   Print("sizeof(s.c)=",sizeof(s.c));
   Print("sizeof(s.s)=",sizeof(s.s));
   Print("sizeof(s.i)=",sizeof(s.i));
   Print("sizeof(s.d)=",sizeof(s.d));
//--- POD 구조의 크기가 멤버의 크기의 합과 같은지 확인합니다.
   Print("sizeof(simple_structure)=",sizeof(simple_structure));
/*
  결과:
   sizeof(s.c)=1
   sizeof(s.s)=2
   sizeof(s.i)=4
   sizeof(s.d)=8
   sizeof(simple_structure)=15 
*/    
  }

이러한 정렬이 적용되는 써드파티 라이브러리(*.DLL)와 데이터를 교환할 때 구조 필드 정렬이 필요할 수 있습니다.

몇 가지 예를 사용하여 정렬의 작동 방식을 보여 드리겠습니다. 우리는 정렬되지 않은 4개의 멤버로 구성된 구조를 적용할 것입니다.

//--- 정렬이 없는 단순 구조
   struct Simple_Structure pack() // 크기가 지정되지 않았습니다. 1바이트의 경계에 대한 정렬을 설정해야 합니다.
     {
      char              c; // sizeof(char)=1
      short             s; // sizeof(short)=2
      int               i; // sizeof(int)=4
      double            d; // sizeof(double)=8
     };
//--- 단순 구조 인스턴스 선언  
   Simple_Structure s;

구조 필드는 다음에 따라 차례로 메모리에 위치해야 합니다: 선언 순서 및 유형 크기. 구조 크기는 15이지만 배열의 구조 필드에 대한 오프셋은 정의되지 않았습니다.

simple_structure_alignment

이제 4바이트 정렬과 동일한 구조를 선언하고 코드를 실행합니다.

//+------------------------------------------------------------------+
//| 스크립트 프로그램 시작 함수                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- 4바이트 정렬의 단순 구조
   struct Simple_Structure pack(4)
     {
      char              c; // sizeof(char)=1
      short             s; // sizeof(short)=2
      int               i; // sizeof(int)=4
      double            d; // sizeof(double)=8
     };
   //--- 단순 구조 인스턴스 선언   
   Simple_Structure s;  
//--- 각 구조 멤버의 크기 표시  
   Print("sizeof(s.c)=",sizeof(s.c));
   Print("sizeof(s.s)=",sizeof(s.s));
   Print("sizeof(s.i)=",sizeof(s.i));
   Print("sizeof(s.d)=",sizeof(s.d));
//--- POD 구조의 크기가 멤버 크기의 합과 같지 않은지 확인합니다
   Print("sizeof(simple_structure)=",sizeof(simple_structure));
/*
  결과:
   sizeof(s.c)=1
   sizeof(s.s)=2
   sizeof(s.i)=4
   sizeof(s.d)=8
   sizeof(simple_structure)=16 // 구조 크기가 변경됨
*/    
  }

구조 크기가 변경되어 4바이트 이상의 모든 멤버가 4바이트 배수의 구조 시작 부분부터 오프셋을 갖습니다. 작은 멤버는 자체 크기 경계에 맞춰 정렬됩니다(예: 'short'의 경우 2). 추가된 바이트는 회색으로 표시됩니다.

simple_structure_alignment_pack

이 경우 s.c 멤버 뒤에 1바이트가 추가되므로 s.s(sizeof(short)==2) 필드의 경계가 2바이트('short' 타입에 대한 정렬)가 됩니다.

배열에서 구조의 시작에 대한 오프셋도 4바이트 경계에 맞춰집니다. 즉, a[0], a[1] 및 a[n] 요소의 주소는 Simple_Structure arr[]의 경우 4바이트의 배수여야 합니다.

4바이트 정렬과 멤버 순서가 다른 유사한 유형으로 구성된 두 개의 구조를 더 고려해 보겠습니다. 첫 번째 구조에서 멤버는 유형 크기 오름차순으로 위치합니다.

//+------------------------------------------------------------------+
//| 스크립트 프로그램 시작 함수                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- 4바이트 경계에 정렬된 단순 구조
   struct CharShortInt pack(4)
     {
      char              c; // sizeof(char)=1
      short             s; // sizeof(short)=2
      int               i; // sizeof(double)=4
     };
//--- 단순 구조 인스턴스 선언  
   CharShortInt ch_sh_in;
//--- 각 구조 멤버의 크기 표시  
   Print("sizeof(ch_sh_in.c)=",sizeof(ch_sh_in.c));
   Print("sizeof(ch_sh_in.s)=",sizeof(ch_sh_in.s));
   Print("sizeof(ch_sh_in.i)=",sizeof(ch_sh_in.i));
 
//--- POD 구조의 크기가 멤버의 크기의 합과 같은지 확인합니다.
   Print("sizeof(CharShortInt)=",sizeof(CharShortInt));
/*
  결과:
   sizeof(ch_sh_in.c)=1
   sizeof(ch_sh_in.s)=2
   sizeof(ch_sh_in.i)=4
   sizeof(CharShortInt)=8
*/   
  }

우리가 볼 수 있듯이 구조 크기는 8이며 2개의 4바이트 블록으로 구성되어 있습니다. 첫 번째 블록은 'char' 및 'short' 유형의 필드를 포함하고 두 번째 블록은 'int' 유형의 필드를 포함합니다.

charshortint

이제 'short' 타입 멤버를 끝으로 이동하여 첫 번째 구조를 필드 순서만 다른 두 번째 구조로 전환해 보겠습니다.

//+------------------------------------------------------------------+
//| 스크립트 프로그램 시작 함수                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- 4바이트 경계에 정렬된 단순 구조
   struct CharIntShort pack(4)
     {
      char              c; // sizeof(char)=1
      int               i; // sizeof(double)=4
      short             s; // sizeof(short)=2
     };
//--- 단순 구조 인스턴스 선언  
   CharIntShort ch_in_sh;
//--- 각 구조 멤버의 크기 표시  
   Print("sizeof(ch_in_sh.c)=",sizeof(ch_in_sh.c));
   Print("sizeof(ch_in_sh.i)=",sizeof(ch_in_sh.i));
   Print("sizeof(ch_in_sh.s)=",sizeof(ch_in_sh.s));
//--- POD 구조의 크기가 멤버의 크기의 합과 같은지 확인합니다.
   Print("sizeof(CharIntShort)=",sizeof(CharIntShort));
/*
  결과:
   sizeof(ch_in_sh.c)=1
   sizeof(ch_in_sh.i)=4
   sizeof(ch_in_sh.s)=2
   sizeof(CharIntShort)=12
*/   
  }

구조 내용은 변경되지 않았지만 멤버 시퀀스를 변경하면 크기가 증가했습니다.

charintshort

상속할 때 정렬도 고려해야 합니다. 단일 'char' 유형 멤버가 있는 간단한 상위 구조를 사용하여 이를 시연해 보겠습니다. 정렬이 없는 구조 크기는 1입니다.

   struct Parent
     {
      char              c;    // sizeof(char)=1
     };

'short' (sizeof(short)=2) 유형 멤버를 특징으로 하는 Children child 클래스를 생성하겠습니다.

   struct Children pack(2) : Parent
     {
      short             s;   // sizeof(short)=2
     };

따라서 정렬을 2바이트로 설정할 때 구조 크기는 4와 같으나, 멤버 크기는 3입니다. 이 예에서는 자식 클래스의 'short' 필드에 대한 액세스가 2바이트로 정렬되도록 부모 클래스에 2바이트를 할당해야 합니다.

MQL5 응용 프로그램이 파일 또는 스트림 수준에서 쓰기/읽기를 통해 타사 데이터와 상호 작용하는 경우 구조 멤버에 대해 메모리를 할당하는 방법에 대한 지식이 필요합니다.

표준 라이브러리의 MQL5\Include\WinAPI 디렉토리에는 WinAPI 기능으로 작업하는 기능이 포함되어 있습니다. 이러한 기능은 WinAPI 작업에 필요한 경우에 대해 지정된 정렬을 가진 구조를 적용합니다.  

offsetofpack 특성과 직접 관련된 특수 명령입니다. 이것은 구조 시작부터 멤버 오프셋을 얻을 수 있게 해줍니다.

//--- 자식 유형 변수 선언
   Children child;  
//--- 구조의 시작부터 오프셋을 탐지합니다
   Print("offsetof(Children,c)=",offsetof(Children,c));
   Print("offsetof(Children,s)=",offsetof(Children,s));  
/*
  결과:
   offsetof(Children,c)=0
   offsetof(Children,s)=2
*/   

지정자 'final' #

구조 선언 중에 'final' 지정자를 사용하면 이 구조로부터 추가 상속이 금지됩니다. 구조에 추가 수정이 필요하지 않거나 보안상의 이유로 수정이 허용되지 않는 경우 이 구조를 'final' 한정자로 선언하십시오. 또한, 구조의 모든 멤버는 암묵적으로 최종 멤버로 간주됩니다.

struct settings final
  {
  //--- Structure body
  };
 
struct trade_settings : public settings
  {
  //--- 구조 바디
  };

위의 예와 같이 'final' 한정자를 사용하여 구조에서 상속하려고 하면 컴파일러에서 오류를 반환합니다:

'settings'로부터 상속할 수 없습니다. 그것이 다음과 같이 선언되었으므로: 'final'
 'settings' 선언 참조

Classes #

클래스는 다음에서 구조물과 다릅니다:

  • 키워드 클래스는 선언에 사용됩니다;
  • 기본적으로 모든 클래스 멤버는 달리 표시되지 않는 한 액세스 지정자를 비공개로 가집니다. 구조의 데이터-멤버는 달리 명시되지 않은 한 공개 접근의 기본 유형을 가집니다;
  • 클래스 개체에는 클래스에 선언된 가상 함수가 없는 경우에도 항상 가상 함수 테이블이 있습니다. 구조에는 가상 함수를 사용할 수 없습니다;
  • new 연산자를 클래스 객체에 적용할 수 있으며, 이 연산자는 구조물에 적용할 수 없습니다;
  • 클래스는 클래스에서만 상속될 수 있으며, 구조는 구조에서만 상속될 수 있습니다.

클래스와 구조는 명시적 생성자 및 소멸자를 가질 수 있습니다. 생성자가 명시적으로 정의된 경우 초기화 시퀀스를 사용하여 구조 또는 클래스 변수를 초기화할 수 없습니다.

예제:

struct trade_settings
  {
   double take;         // 차익 확정 가격의 가치
   double stop;         // 보호 중지 가격의 가치
   uchar  slippage;     // 허용 저하 값
   //--- 구성자
          trade_settings() { take=0.0; stop=0.0; slippage=5; }
   //--- 소멸자
         ~trade_settings() { Print("이것이 끝입니다"); } 
  };
//--- 컴파일러에서 초기화가 불가능하다는 오류 메시지를 생성합니다
trade_settings my_set={0.0,0.0,5};  

생성자 및 소멸자

생성자는 구조나 클래스의 개체를 만들 때 자동으로 호출되는 특수 함수이며 일반적으로 클래스 멤버를 초기화 하는 데 사용됩니다. 또한 달리 명시되지 않은 한 구조에도 동일하게 적용되는 반면, 클래스에만 대해 이야기하겠습니다. 생성자 이름은 클래스 이름과 일치해야 합니다. 생성자에 반환 유형이 없습니다(void 유형을 지정할 수 있음).

정의된 클래스 멤버 – 문자열, 동적 배열 및 초기화가 필요한 개체 –는 생성자가 있는지 여부에 관계없이 초기화됩니다.

각 클래스에는 매개 변수 수와 초기화 목록에 따라 다른 생성자가 여러 개 있을 수 있습니다. 매개변수를 지정해야 하는 생성자를 모수 생성자라고 합니다.

매개 변수가 없는 생성자를 기본 생성자라고 합니다. 클래스에 선언된 생성자가 없는 경우 컴파일러는 컴파일 중에 기본 생성자를 만듭니다.

//+------------------------------------------------------------------+
//| 날짜 작업을 위한 클래스                                  |
//+------------------------------------------------------------------+
class MyDateClass
  {
private:
   int               m_year;          // 연도
   int               m_month;         // 월
   int               m_day;           // 일
   int               m_hour;          // 시
   int               m_minute;        // 분
   int               m_second;        // 초
public:
   //--- 기본 생성자
                     MyDateClass(void);
   //--- 매개변수 생성자
                     MyDateClass(int h,int m,int s);
  };

 

생성자를 클래스 설명에 선언한 다음 해당 본문을 정의할 수 있습니다. 예를 들어 MyDateClass의 두 생성자는 다음과 같이 정의할 수 있습니다:

//+------------------------------------------------------------------+
//| 기본 생성자                                              |
//+------------------------------------------------------------------+
MyDateClass::MyDateClass(void)
  {
//---
   MqlDateTime mdt;
   datetime t=TimeCurrent(mdt);
   m_year=mdt.year;
   m_month=mdt.mon;
   m_day=mdt.day;
   m_hour=mdt.hour;
   m_minute=mdt.min;
   m_second=mdt.sec;
   Print(__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| 매개변수 생성자                                           |
//+------------------------------------------------------------------+
MyDateClass::MyDateClass(int h,int m,int s)
  {
   MqlDateTime mdt;
   datetime t=TimeCurrent(mdt);
   m_year=mdt.year;
   m_month=mdt.mon;
   m_day=mdt.day;
   m_hour=h;
   m_minute=m;
   m_second=s;
   Print(__FUNCTION__);
  }

기본 생성자에서 클래스의 모든 멤버는 TimeCurrent() 함수를 사용하여 채워집니다. 매개변수 생성자에서는 시간 값만 입력됩니다. 클래스의 다른 멤버(m_year, m_month and m_day)은 현재 날짜에 자동으로 초기화됩니다.

기본 생성자는 해당 클래스의 객체 배열을 초기화할 때 특별한 목적을 가집니다. 기본값이 있는 모든 매개 변수 생성자는 기본 생성자가 아닙니다. 여기 예제가 있습니다:

//+------------------------------------------------------------------+
//| 기본 생성자가 있는 클래스                               |
//+------------------------------------------------------------------+
class CFoo
  {
   datetime          m_call_time;     // 마지막 객체 호출 시간
public:
   //--- 기본값이 있는 매개 변수를 가진 생성자가 기본 생성자가 아닙니다
                     CFoo(const datetime t=0){m_call_time=t;};
   //--- 복제 생성자
                     CFoo(const CFoo &foo){m_call_time=foo.m_call_time;};
 
   string ToString(){return(TimeToString(m_call_time,TIME_DATE|TIME_SECONDS));};
  };
//+------------------------------------------------------------------+
//| 스크립트 프로그램 시작 함수                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
// CFoo foo; // 이 변수를 사용할 수 없습니다. 기본 생성자가 설정되지 않았습니다.
//--- CFoo 객체를 만드는 데 사용할 수 있는 옵션
   CFoo foo1(TimeCurrent());     // 파라메트릭 생성자의 명시적 호출
   CFoo foo2();                  // 기본 파라미터가 있는 모수 생성자의 명시적 호출
   CFoo foo3=D'2009.09.09';      // 모수 생성자의 암시적 호출
   CFoo foo40(foo1);             // 복제 생성자의 명시적 호출
   CFoo foo41=foo1;              // 복제 생성자의 암시적 호출
   CFoo foo5;                    // 기본 생성자의 명시적 호출(기본 생성자가 없는 경우,
                                 // 그러면 기본값인 모수 생성자를 호출합니다)
//--- CFoo 포인터를 수신할 수 있는 옵션
   CFoo *pfoo6=new CFoo();       // 객체의 동적 생성 및 객체에 대한 포인터
   CFoo *pfoo7=new CFoo(TimeCurrent());// 동적 객체 생성의 또 다른 옵션
   CFoo *pfoo8=GetPointer(foo1); // 이제 pfoo8 포인트와 객체 foo1 연결
   CFoo *pfoo9=pfoo7;            // pfoo9 및 pfoo7이 동일한 개체를 가리킵니다.
   // CFoo foo_array[3];         // 이 옵션을 사용할 수 없습니다 - 기본 생성자가 지정되지 않았습니다
//--- m_call_time 값 보이기
   Print("foo1.m_call_time=",foo1.ToString());
   Print("foo2.m_call_time=",foo2.ToString());
   Print("foo3.m_call_time=",foo3.ToString());
   Print("foo4.m_call_time=",foo4.ToString());
   Print("foo5.m_call_time=",foo5.ToString());
   Print("pfoo6.m_call_time=",pfoo6.ToString());
   Print("pfoo7.m_call_time=",pfoo7.ToString());
   Print("pfoo8.m_call_time=",pfoo8.ToString());
   Print("pfoo9.m_call_time=",pfoo9.ToString());
//--- 동적으로 작성된 배열 삭제
   delete pfoo6;
   delete pfoo7;
   //pfoo8 삭제;  // pfoo8은 자동으로 생성된 개체 foo1을 가리키므로 명시적으로 삭제할 필요가 없습니다
   //pfoo9 삭제;  // pfoo9은 명시적으로 삭제할 필요가 없습니다. pfoo7과 같은 객체를 가리키기 때문에
  }

이 문자열에 대한 설명을 해제하면

  //CFoo foo_array[3];     // 이 변수를 사용할 수 없습니다. 기본 생성자가 설정되지 않았습니다.

또는

  //CFoo foo_dyn_array[];  // 이 변수를 사용할 수 없습니다 - 기본 생성자가 설정되지 않았습니다

그러면 컴파일러는 "기본 생성자가 정의되지 않음"에 대한 오류를 반환합니다.

클래스에 커스텀 생성자가 있는 경우 컴파일러에서 기본 생성자를 생성하지 않습니다. 즉, 모수 생성자가 클래스에 선언되었지만 기본 생성자는 선언되지 않은 경우 이 클래스의 개체 배열을 선언할 수 없습니다. 컴파일러에서 이 스크립트에 대한 오류를 반환합니다:

//+------------------------------------------------------------------+
//| 기본 생성자가 없는 클래스                            |
//+------------------------------------------------------------------+
class CFoo
  {
   string            m_name;
public:
                     CFoo(string name) { m_name=name;}
  };
//+------------------------------------------------------------------+
//| 스크립트 프로그램 시작 함수                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- 컴파일 중 "기본 생성자가 정의되지 않았습니다" 오류를 가져옵니다
   CFoo badFoo[5];
  }

이 예제에서 CFoo 클래스에는 선언된 파라미터 생성자가 있습니다 - 이 경우 컴파일러는 컴파일 중에 기본 생성자를 자동으로 만들지 않습니다. 개체 배열을 선언하는 동시에 모든 개체를 자동으로 생성 및 초기화해야 한다고 가정합니다. 개체의 자동 초기화 중에는 기본 생성자를 호출해야 하지만 기본 생성자는 명시적으로 선언되지 않고 컴파일러에 의해 자동으로 생성되지 않으므로 이러한 개체를 만들 수 없습니다. 이러한 이유로 컴파일러는 컴파일 단계에서 오류를 생성합니다.

생성자를 사용하여 개체를 초기화하는 특수 구문이 있습니다. 구조체 또는 클래스 멤버에 대한 생성자 이니셜라이저(초기화를 위한 특수 구성)를 초기화 목록에 지정할 수 있습니다.

초기화 목록은 쉼표로 구분된 이니셜라이저 목록으로, 생성자의 매개 변수 목록 뒤 콜론 뒤에 오고 본문 (열림 꺽쇠 앞에 있음) 앞에 나옵니다. 몇 가지 요구 사항이 있습니다:

  • 초기화 목록은 생성자에서만 사용할 수 있습니다;
  • 부모 멤버는 초기화 목록에서 초기화할 수 없습니다;
  • 초기화 목록 뒤에는 함수의 정의(실행)가 와야 합니다.

다음은 클래스 멤버를 초기화하기 위한 여러 생성자의 예입니다.

//+------------------------------------------------------------------+
//| 다음은 클래스 멤버를 초기화하기 위한 여러 생성자의 예입니다                      |
//+------------------------------------------------------------------+
class CPerson
  {
   string            m_first_name;     // 첫째 이름 
   string            m_second_name;    // 둘째 이름
public:
   //--- 비어 있는 기본 생성자
                     CPerson() {Print(__FUNCTION__);};
   //--- 모수 생성자
                     CPerson(string full_name);
   //--- 초기화 목록이 있는 생성자
                     CPerson(string surname,string name): m_second_name(surname), m_first_name(name) {};
   void PrintName(){PrintFormat("Name=%s Surname=%s",m_first_name,m_second_name);};
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CPerson::CPerson(string full_name)
  {
   int pos=StringFind(full_name," ");
   if(pos>=0)
     {
      m_first_name=StringSubstr(full_name,0,pos);
      m_second_name=StringSubstr(full_name,pos+1);
    }
  }
//+------------------------------------------------------------------+
//| 스크립트 프로그램 시작 함수                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- "기본 생성자가 정의되지 않았습니다" 오류가 표시됩니다
   CPerson people[5];
   CPerson Tom="Tom Sawyer";                       // 톰 소여
   CPerson Huck("Huckleberry","Finn");             // 허클베리 핀
   CPerson *Pooh = new CPerson("Winnie","Pooh");  // 곰돌이 푸
   //--- 결과 값
   Tom.PrintName();
   Huck.PrintName();
   Pooh.PrintName();
   
   //--- 동적으로 작성된 객체 삭제
   delete Pooh;
  }

이 경우 CPerson 클래스에는 세 개의 생성자가 있습니다:

  1. 이 클래스의 객체 배열을 작성할 수 있는 명시적인 기본 생성자;
  2. 매개 변수가 하나 있는 생성자로, 매개 변수로 전체 이름을 가져오고 찾은 공간에 따라 이름과 두 번째 이름으로 나눕니다;
  3. 초기화 목록을 포함하는 두 개의 매개 변수를 가진 생성자. 이니셜라이저 - m_second_name(성) 및 m_first_name(이름).

목록을 사용한 초기화가 할당을 대체했습니다. 개별 멤버는 다음과 같이 초기화해야 합니다:

 class_member (표현식 목록)

초기화 리스트에서 멤버들은 어떤 순서로든 갈 수 있지만, 모든 클래스 멤버들은 발표 순서에 따라 초기화됩니다. 즉, 세 번째 생성자에서 먼저 m_first_name 멤버가 먼저 발표되는 대로 초기화되며, m_second_name 이 초기화되어야 합니다. 이는 일부 클래스 멤버의 초기화가 다른 클래스 멤버의 가치에 따라 달라지는 경우에 고려되어야 한다.

기본 생성자가 기본 클래스에 선언되지 않은 동시에 매개 변수를 가진 하나 이상의 생성자가 선언된 경우 초기화 목록에서 항상 기본 클래스 생성자 중 하나를 호출해야 합니다. 쉼표를 통해 목록의 일반 멤버로 사용되며, 초기화 목록의 어디에 있든 개체를 초기화하는 동안 먼저 호출됩니다.

//+------------------------------------------------------------------+
//| 기초 클래스                                                       |
//+------------------------------------------------------------------+
class CFoo
  {
   string            m_name;
public:
   //--- 초기화 목록이 있는 생성자
                     CFoo(string name) : m_name(name) { Print(m_name);}
  };
//+------------------------------------------------------------------+
//| CFoo에서 파생된 클래스                                          |
//+------------------------------------------------------------------+
class CBar : CFoo
  {
   CFoo              m_member;      // 클래스 멤버는 상위의 개체입니다
public:
   //--- 초기화 목록의 기본 생성자는 상위 생성자를 호출합니다
                     CBar(): m_member(_Symbol), CFoo("CBAR") {Print(__FUNCTION__);}
  };
//+------------------------------------------------------------------+
//| 스크립트 프로그램 시작 함수                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   CBar bar;
  }

이 예에서는 막대 객체를 만들 때 기본 생성자 CBar()가 호출됩니다. 여기서 먼저 상위 CFoo에 대한 생성자를 호출한 다음 m_member 클래스 멤버에 대한 생성자를 호출합니다.

소멸자는 클래스 개체가 제거될 때 자동으로 호출되는 특수 함수입니다. 소멸자의 이름은 물결표(~)와 함께 클래스 이름으로 쓰여 있습니다. 초기화 취소가 필요한 문자열, 동적 배열 및 개체는 소멸자의 유무에 관계없이 초기화 취소됩니다. 소멸자가 있는 경우 이러한 작업은 소멸자를 호출한 후 수행됩니다.

소멸자는 가상 키워드로 선언되었는지 여부와 관계없이 항상 가상입니다.

클래스 메서드 정의

클래스 함수 메서드는 클래스 선언 내부와 외부에서 모두 정의할 수 있습니다. 메서드가 클래스 내에 정의되어 있으면 메서드 선언 바로 뒤에 해당 본문이 나옵니다.

예제:

class CTetrisShape
  {
protected:
   int               m_type;
   int               m_xpos;
   int               m_ypos;
   int               m_xsize;
   int               m_ysize;
   int               m_prev_turn;
   int               m_turn;
   int               m_right_border;
public:
   void              CTetrisShape();
   void              SetRightBorder(int border) { m_right_border=border; }
   void              SetYPos(int ypos)          { m_ypos=ypos;           }
   void              SetXPos(int xpos)          { m_xpos=xpos;           }
   int               GetYPos()                  { return(m_ypos);        }
   int               GetXPos()                  { return(m_xpos);        }
   int               GetYSize()                 { return(m_ysize);       }
   int               GetXSize()                 { return(m_xsize);       }
   int               GetType()                  { return(m_type);        }
   void              Left()                     { m_xpos-=SHAPE_SIZE;    }
   void              Right()                    { m_xpos+=SHAPE_SIZE;    }
   void              Rotate()                   { m_prev_turn=m_turn; if(++m_turn>3) m_turn=0; }
   virtual void      Draw()                     { return;                }
   virtual bool      CheckDown(int& pad_array[]);
   virtual bool      CheckLeft(int& side_row[]);
   virtual bool      CheckRight(int& side_row[]);
  }; 

SetRightBorder(int 보더)에서 Draw()까지의 함수는 CTetrisShape 클래스 내에서 직접 선언 및 정의됩니다.

CTetrisShape() 생성자 및 메서드 CheckDown(int& pad_array[]), CheckLeft(int& side_row[]) 및 CheckRight(int& side_row[])는 클래스 내에서만 선언되며 아직 정의되지 않았습니다. 이러한 함수의 정의는 코드에 자세히 나와 있습니다. 클래스 외부에서 메서드를 정의하기 위해 범위 지정 연산자가 사용되고 클래스 이름이 스코프로 사용됩니다.

예제:

//+------------------------------------------------------------------+
//| 기본 클래스 생성자                                   |
//+------------------------------------------------------------------+
void CTetrisShape::CTetrisShape()
  {
   m_type=0;
   m_ypos=0;
   m_xpos=0;
   m_xsize=SHAPE_SIZE;
   m_ysize=SHAPE_SIZE;
   m_prev_turn=0;
   m_turn=0;
   m_right_border=0;
  }
//+------------------------------------------------------------------+
//| 아래로 이동하는 기능 확인(스틱 및 큐브)           |
//+------------------------------------------------------------------+
bool CTetrisShape::CheckDown(int& pad_array[])
  {
   int i,xsize=m_xsize/SHAPE_SIZE;
//---
   for(i=0; i<xsize; i++)
     {
      if(m_ypos+m_ysize>=pad_array[i]) return(false);
    }
//---
   return(true);
  }

퍼블릭, 보호 및 프라이빗 액세스 지정자

새로운 클래스를 개발할 때는 외부로부터의 멤버 접근을 제한하는 것이 좋습니다. 이러한 목적 탓에 프라이빗 또는 보호가 사용됩니다. 이 경우 숨겨진 데이터는 동일한 클래스의 함수 방식에서만 액세스할 수 있습니다. protected 키워드를 사용하는 경우, 클레스 메서드 - 클래스 상속 -에서도 숨겨진 데이터에 엑세스할 수 있습니다. 동일한 방법을 사용하여 클래스의 함수 메서드에 대한 액세스를 제한할 수 있습니다.

클래스의 멤버 및/또는 메서드에 대한 액세스를 완전히 열어야 하는 경우 키워드 public을 사용하십시오.

예제:

class CTetrisField
  {
private:
   int               m_score;                            // 스코어
   int               m_ypos;                             // 그림의 현재 위치
   int               m_field[FIELD_HEIGHT][FIELD_WIDTH]; // 우물 행렬
   int               m_rows[FIELD_HEIGHT];               // 우물 행 번호 지정
   int               m_last_row;                         // 마지막 자유 행
   CTetrisShape     *m_shape;                            // 테트리스 모형
   bool              m_bover;                            // 게임 오버
public:
   void              CTetrisField() { m_shape=NULL; m_bover=false; }
   void              Init();
   void              Deinit();
   void              Down();
   void              Left();
   void              Right();
   void              Rotate();
   void              Drop();
private:
   void              NewShape();
   void              CheckAndDeleteRows();
   void              LabelOver();
  }; 

지정자 public: (다음 액세스 지정자 이전) 뒤에 선언된 클래스 멤버 및 메서드는 프로그램에 의한 클래스 개체에 대한 참조에서 사용할 수 있습니다. 이 예에서는 CTetrisField(), Init(), Deinit(), Down(), Left(), Right(), Rotate() 및 Drop() 함수가 사용됩니다.

private: 요소의 액세스 지정자 다음에 선언된 멤버(다음 액세스 지정자 이전)는 이 클래스의 멤버 함수에서만 사용할 수 있습니다. 요소에 대한 액세스 지정자는 항상 콜론(:)으로 끝나며 클래스 정의에 여러 번 나타날 수 있습니다.

protected: 액세스 지정자 뒤에 선언된 모든 클래스 멤버(및 다음 액세스 지정자까지)는 이 클래스의 멤버-함수와 클래스 자식 항목의 멤버-함수에만 사용할 수 있습니다. 외부에서 프라이빗보호 지정자가 있는 멤버를 참조하려고 하면 컴파일 스테이지 오류가 발생합니다. 예제:

class A
  {
protected:
   //--- 복제 연산자는 클래스 A와 그 자식 항목에서만 사용할 수 있습니다.
   void operator=(const A &)
     {
    }
  };
class B
  {
   //--- 선언된 클래스 A 객체
   A                 a;
  };
//+------------------------------------------------------------------+
//| 스크립트 프로그램 시작 함수                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   //--- B형 변수 두 개를 선언
   B b1b2;
   //--- 어떤 객체를 다른 객체로 복사 시도
   b2=b1;
  }

코드를 컴파일할 때 원격 복사 연산자를 호출하려는 시도와 같은 오류 메시지가 수신됩니다:

attempting to reference deleted function 'void B::operator=(const B&)'   trash3.mq5   32   6

아래의 두 번째 문자열은 클래스 A의 사용할 수 없는 복제 연산자를 호출하기 때문에 클래스 B의 복제 연산자가 명시적으로 삭제되었습니다.

   function 'void B::operator=(const B&)' was implicitly deleted because it invokes inaccessible function 'void A::operator=(const A&)' 

기본 클래스의 멤버에 대한 액세스는 파생 클래스에서 상속 중에 재정의할 수 있습니다.

'삭제' 지정자

삭제 지정자는 사용할 수 없는 클래스 멤버를 표시합니다. 즉, 프로그램이 명시적 또는 암시적으로 이러한 함수를 참조하는 경우 컴파일 단계에서 오류가 이미 수신됩니다. 예를 들어 이 지정자를 사용하면 자식 클래스에서 부모 메서드를 사용할 수 없게 만들 수 있습니다. 상위 클래스의 프라이빗 영역 (프라이빗 섹션으로의 선언)에 함수를 선언해도 동일한 결과를 얻을 수 있습니다. 여기서 삭제를 사용하면 하위 항목 수준에서 코드를 더 잘 읽고 관리할 수 있습니다.

class A
  {
public:
                     A(void) {value=5;};
   double            GetValue(void) {return(value);}
private:
   double            value;
  };
class Bpublic A
  {
   double            GetValue(void)=delete;
  };
//+------------------------------------------------------------------+
//| 스크립트 프로그램 시작 함수                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- A형 변수 선언
   A a;
   Print("a.GetValue()="a.GetValue());
//--- B 유형 변수에서 값을 가져오려 시도
   B b;
   Print("b.GetValue()="b.GetValue()); // 컴파일러에서 이 문자열에 오류를 표시합니다
  }

컴파일러 메시지:

attempting to reference deleted function 'double B::GetValue()'   
   function 'double B::GetValue()' was explicitly deleted here   

'삭제' 지정자는 자동 캐스팅 또는 복사 생성자를 비활성화할 수 있습니다. 그렇지 않으면 프라이빗 섹션에도 숨겨야 합니다.  예제:

class A
  {
public:
   void              SetValue(double v) {value=v;}
   //--- int 타입 호출 비활성화
   void              SetValue(int) = delete;
   //--- 복제 연산자 비활성화
   void              operator=(const A&) = delete;
private:
   double            value;
  };
//+------------------------------------------------------------------+
//| 스크립트 프로그램 시작 함수                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- A형 변수 두 개 선언
   A a1a2;
   a1.SetValue(3);      // 오류!
   a1.SetValue(3.14);   // OK
   a2=a1;               // 오류!
  }

컴파일하는 동안 오류 메시지가 표시됩니다:

attempting to reference deleted function 'void A::SetValue(int)' 
   function 'void A::SetValue(int)' was explicitly deleted here 
attempting to reference deleted function 'void A::operator=(const A&)'  
   function 'void A::operator=(const A&)' was explicitly deleted here  

지정자 'final' #

클래스 선언 중에 'final' 지정자를 사용하면 이 클래스에서 더 이상 상속할 수 없습니다. 클래스 인터페이스에 추가 수정이 필요하지 않거나 보안상의 이유로 수정이 허용되지 않는 경우 이 클래스를 'final' 한정자로 선언하십시오. 또한 클래스의 모든 멤버들은 암묵적으로 final로 간주됩니다.

class CFoo final
  {
  //--- Class body
  };
 
class CBar : public CFoo
  {
  //--- Class body
  };

위의 예와 같이 'final' 지정자가 있는 폼 클래스를 상속하려고 하면 컴파일러에서 오류를 반환합니다:

'CFoo'가 'final'로 선언되었으므로 'CFoo'에서 상속할 수 없습니다
'CFoo' 선언 참조

Unions (union) #

Union은 동일한 메모리 영역을 공유하는 여러 변수로 구성된 특수 데이터 유형입니다. 따라서 유니언은 동일한 비트 시퀀스를 두 가지(또는 그 이상) 다른 방법으로 해석할 수 있는 기능을 제공합니다. 유니언 선언은 구조 선언과 유사하며 union 키워드로 시작합니다.

union LongDouble
{
  long   long_value;
  double double_value;
};

구조와는 달리 다양한 유니언 멤버들이 같은 메모리 영역에 속해 있습니다. 이 예에서 LongDouble의 결합은 동일한 메모리 영역을 공유하는 longdouble 유형 값으로 선언됩니다. long_value 변수와 double_value 변수가 (메모리에) 겹치기 때문에 유니언 저장소는 long 정수 값과 double 실수 값을 동시에 저장할 수 없습니다. 반면 MQL5 프로그램은 결합에 포함된 데이터를 언제든지 정수(long) 또는 실수(double) 값으로 처리할 수 있습니다. 따라서 통합에서는 동일한 데이터 시퀀스를 나타내기 위해 두 개 이상의 옵션을 수신할 수 있습니다.

유니언 선언 중에 컴파일러는 변수 유니언에 largest 타입(볼륨 기준)을 저장하기에 충분한 메모리 영역을 자동으로 할당합니다. 구조 – 포인트 연산자와 동일한 구문이 유니언 요소에 액세스하는 데 사용됩니다.

union LongDouble
{
  long   long_value;
  double double_value;
};
//+------------------------------------------------------------------+
//| 스크립트 프로그램 시작 함수                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   LongDouble lb;
//--- 잘못된 -nan(ind) 수를 가져와 표시
   lb.double_value=MathArcsin(2.0);
   printf("1.  double=%f                integer=%I64X",lb.double_value,lb.long_value);
//--- 최대 정규화 값 (DBL_MAX)
   lb.long_value=0x7FEFFFFFFFFFFFFF;
   printf("2.  double=%.16e  integer=%I64X",lb.double_value,lb.long_value);
//--- 최소 양의 정규화 (DBL_MIN)
   lb.long_value=0x0010000000000000;    
   printf("3.  double=%.16e  integer=%.16I64X",lb.double_value,lb.long_value);
  }
/*  실행 결과
    1.  double=-nan(ind)                integer=FFF8000000000000
    2.  double=1.7976931348623157e+308  integer=7FEFFFFFFFFFFFFF
    3.  double=2.2250738585072014e-308  integer=0010000000000000
*/

유니언은 프로그램이 동일한 메모리 데이터를 다른 방식으로 해석할 수 있도록 하기 때문에 비정상적인 유형 변환이 필요할 때 종종 사용됩니다.

유니언이 상속에 관여할 수도 없고, 특성상 정적 멤버를 둘 수도 없습니다. 다른 모든 측면에서, 유니언은 모든 멤버들이 0의 오프셋을 갖는 구조처럼 행동합니다. 다음 유형은 유니언 멤버일 수 없습니다:

  • 동적 배열
  • 문자열
  • 포인터 - 오브젝트에 대한- 및 함수
  • 클래스 오브젝트
  • 구조 객체에 생성자 또는 소멸자가 있음
  • 구조 객체: 1-5 포인트에 멤버가 있음

클래스와 비슷하게, 유니언은 메소드뿐만 아니라 생성자와 소멸자를 가질 수 있습니다. 기본적으로, 유니언은 퍼블릭 액세스 유형입니다. 프라이빗 요소를 생성하려면, 프라이빗 키워드를 사용하십시오. color 유형의 색상을 ARGB로 변환하는 방법을 보여주는 예제에 이러한 모든 가능성이 표시됩니다. ColorToARGB() 함수와 마찬가지로.

//+------------------------------------------------------------------+
//| color(BGR) 유니언의 ARGB로의 변환                          |
//+------------------------------------------------------------------+
union ARGB
  {
   uchar             argb[4];
   color             clr;
   //--- 생성자
                     ARGB(color col,uchar a=0){Color(col,a);};
                    ~ARGB(){};
   //--- 퍼블릭 메서드
public:
   uchar   Alpha(){return(argb[3]);};
   void    Alpha(const uchar alpha){argb[3]=alpha;};
   color   Color(){ return(color(clr));};
   //--- 프라이빗 메서드
private:
   //+------------------------------------------------------------------+
   //| 알파 채널 값 및 색상 설정                            |
   //+------------------------------------------------------------------+
   void    Color(color col,uchar alpha)
     {
      //--- clr 멤버로 색상 설정
      clr=col;
      //--- 알파 성분 값 설정 - 불투명도 수준
      argb[3]=alpha;
      //--- R 및 B 구성 요소의 바이트 교환(빨간색 및 파란색)     
      uchar t=argb[0];argb[0]=argb[2];argb[2]=t;
     };
  };
//+------------------------------------------------------------------+
//| 스크립트 프로그램 시작 함수                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- 0x55는 55/255=21.6%(0% - 완전 투명)를 의미합니다
   uchar alpha=0x55; 
//--- 색상 유형은 0x00BBGGRR 처럼 표현됩니다
   color test_color=clrDarkOrange;
//--- ARGB 유니언의 바이트 값이 여기에 허용됩니다
   uchar argb[]; 
   PrintFormat("0x%.8X - %s 'color' 유형은 다음과 같습니다, BGR=(%s)",
               test_color,ColorToString(test_color,true),ColorToString(test_color));
//--- ARGB 유형은 0x00RRGGBB 같이 표시되며, RR 및 BB 구성요소는 스왑됩니다
   ARGB argb_color(test_color);
//--- 바이트 배열을 복제
   ArrayCopy(argb,argb_color.argb);
//--- 다음은 ARGB 표현의 모습입니다  
   PrintFormat("0x%.8X - ARGB 표현, 알파 채널=0x%.2x, ARGB=(%d,%d,%d,%d)",
               argb_color.clr,argb_color.Alpha(),argb[3],argb[2],argb[1],argb[0]);
//--- 불투명도 수준 추가
   argb_color.Alpha(alpha);
//--- ARGB를 'color' 유형으로 정의 시도
   Print("ARGB as color=(",argb_color.clr,")  alpha channel=",argb_color.Alpha());
//--- 바이트 배열을 복제
   ArrayCopy(argb,argb_color.argb);
//--- 다음은 ARGB 표현의 모습입니다
   PrintFormat("0x%.8X - ARGB 표현, 알파 채널=0x%.2x, ARGB=(%d,%d,%d,%d)",
               argb_color.clr,argb_color.Alpha(),argb[3],argb[2],argb[1],argb[0]);
//--- ColorToARGB() 함수 결과를 확인
   PrintFormat("0x%.8X - ColorToARGB(%s,0x%.2x) 결과",ColorToARGB(test_color,alpha),
               ColorToString(test_color,true),alpha);
  }
/* 실행 결과
   0x00008CFF - clrDarkOrange, BGR=(255,140,0)의 color 유형은 이것과 같습니다
  0x00FF8C00 - ARGB 표현, 알파 채널=0x00, ARGB=(0,255,140,0)
   ARGB as color=(0,140,255)  alpha channel=85
   0x55FF8C00 - ARGB 표현, 알파 채널=0x55, ARGB=(85,255,140,0)
   0x55FF8C00 - ColorToARGB(clrDarkOrange,0x55)의 결과
*/ 

인터페이스 #

인터페이스는 클래스에서 구현할 수 있는 특정 기능을 결정할 수 있도록 합니다. 실제로 인터페이스는 멤버를 포함할 수 없으며 생성자 및/또는 소멸자가 없을 수 있는 클래스입니다. 인터페이스에 선언된 모든 메서드는 명시적 정의가 없어도 순수하게 가상입니다.

인터페이스는 "interface" 키워드를 사용하여 정의됩니다. 예제:

//--- 동물 설명을 위한 기본 인터페이스
interface IAnimal
  {
//--- 인터페이스의 메서드는 기본적으로 공용 액세스 권한을 가집니다
   void Sound();  // 동물이 내는 소리
  };
//+------------------------------------------------------------------+
//|  CCat 클래스는 IAnimal 인터페이스에서 상속됩니다          |
//+------------------------------------------------------------------+
class CCat : public IAnimal
  {
public:
                     CCat() { Print("고양이가 태어났습니다"); }
                    ~CCat() { Print("고양이가 죽었습니다");  }
   //--- IAnimal 인터페이스의 사운드 메서드 구현
   void Sound(){ Print("야옹"); }
  };
//+------------------------------------------------------------------+
//|  CDog 클래스는 IAnimal 인터페이스에서 상속됩니다          |
//+------------------------------------------------------------------+
class CDog : public IAnimal
  {
public:
                     CDog() { Print("개가 태어났습니다"); }
                    ~CDog() { Print("개가 죽었습니다");  }
   //--- IAnimal 인터페이스의 사운드 메서드 구현
   void Sound(){ Print("멍멍"); }
  };
//+------------------------------------------------------------------+
//| 스크립트 프로그램 시작 함수                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- IAnimal 유형 개체에 대한 포인터 배열
   IAnimal *animals[2];
//--- IAnimal의 하위 클래스를 만들고 해당 클래스에 대한 포인터를 배열에 저장합니다    
   animals[0]=new CCat;
   animals[1]=new CDog;
//--- 각 자식 항목에 대한 기본 IAnimal 인터페이스의 Sound() 메서드 호출  
   for(int i=0;i<ArraySize(animals);++i)
      animals[i].Sound();
//--- 객체 삭제
   for(int i=0;i<ArraySize(animals);++i)
      delete animals[i];
//--- 실행 결과
/*
  고양이가 태어났습니다
  개가 태어났습니다
  야옹
  멍멍
  고양이가 죽었습니다
  개가 죽었습니다
*/
  }

추상 클래스와 마찬가지로 인터페이스 객체는 상속 없이 만들 수 없습니다. 인터페이스는 다른 인터페이스에서만 상속할 수 있으며 클래스의 부모 인터페이스일 수 있습니다. 인터페이스는 항상 공개적으로 가시적입니다.

클래스 또는 구조 선언 내에 인터페이스를 선언할 수 없지만 인터페이스에 대한 포인터는 void * 유형의 변수에 저장할 수 있습니다. 일반적으로 클래스의 개체에 대한 포인터는 void * 유형의 변수에 저장할 수 있습니다. void * 포인터를 특정 클래스의 객체로 변환하려면 dynamic_cast 연산자를 사용하십시오.변환할 수 없는 경우 dynamic_cast 작업의 결과는 NULL이 됩니다.

더 보기

객체지향 프로그래밍