English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
MQL5 프로그래밍 기본: 배열

MQL5 프로그래밍 기본: 배열

MetaTrader 5 | 2 9월 2021, 16:39
144 0
Dmitry Fedoseev
Dmitry Fedoseev

소개

배열은 변수 및 함수와 함께 거의 모든 프로그래밍 언어의 필수적인 부분입니다. 많은 초보 프로그래머는 종종 배열을 두려워합니다. 이상하게 들리지만 사실이에요! 전 이것들이 전혀 무섭지 않다고 장담할 수 있습니다. 사실, 배열은 일반 변수와 유사합니다. 표기법의 특성에 대해 자세히 설명하지 않고 간단한 변수를 사용하여 표현식을 작성하는 것과 큰 차이가 없습니다.

Variable0=1;
Variable1=2;

Variable2=Variable0+Variable1;

또는 배열 사용과 말이죠.

double Variable[3];

Variable[0]=1;
Variable[1]=2;

Variable[2]=Variable[0]+Variable[1];

보시다시피, 배열을 사용할 때 변수 이름에 대괄호가 포함된다는 사실을 제외하고 차이는 그렇게 크지 않습니다. 또 다른 중요한 차이점이 있습니다. 변수를 선언할 때 각 변수의 이름을 지정해야 하는 반면, 배열을 선언할 때는 이름을 한 번만 쓰고 괄호 안에 변수의 수(배열 요소의 수)를 지정해야 합니다. 변수보다 배열을 사용하는 것의 장점은 수많은 실제 프로그래밍 작업의 문제를 처리할 때 훨씬 더 분명해집니다.

배열이 복잡한 것으로 보이는 이유는 "[" 및 "]"의 사용과 관련이 있습니까? 이러한 기호는 배열 작업 시 프로그래밍 이외의 다른 곳에서는 거의 사용되지 않으므로 키보드에서 해당 기호의 포지션이 기억에서 희미해지고 불편함을 유발할 수 있습니다. 사실, 포지션을 쉽게 기억할 수 있습니다. 이 두 키는 논리적 순서로 "Enter" 옆에 있습니다. 여는 대괄호 다음에 닫는 대괄호가 옵니다.


배열의 정의 및 일반 속성

따라서 배열은 이름이 같은 변수의 번호가 지정된 집합입니다. 배열의 일반적인 속성에는 배열의 이름, 변수 유형(int, double 등) 및 배열 크기가 포함됩니다. 배열 요소는 0부터 인덱싱됩니다. 배열 요소에 대해 말하면 "숫자" 대신 "인덱스"라는 단어를 사용하여 배열 요소를 0부터 계산하기 시작하도록 제안하는 것이 항상 더 좋습니다(번호 매기기는 일반적으로 1부터 시작함). 이러한 방식으로 요소가 인덱싱되면 마지막 요소의 인덱스는 배열 요소 수보다 하나 적습니다.

배열이 다음과 같이 선언된 경우:

double Variable[3];

Variable[0], Variable[1] 및 Variable[2] 요소가 있습니다.

표면적으로는 요소의 수와 마지막 요소의 인덱스 사이의 대응이 부족하여 불편해 보일 수 있습니다. 사실, 배열 요소가 1부터 인덱싱되거나 배열 크기가 배열의 실제 요소 수보다는 마지막 요소의 인덱스로 정의되는 프로그래밍 언어에 비해 상당한 이점을 제공합니다.

MQL5에서 배열 크기를 결정하기 위해 ArraySize() 함수를 사용합니다.

double Variable[3];

int Size=ArraySize(Variable);

코드를 실행한 후 Size 변수의 값은 3이 됩니다.


정적 및 동적 배열

배열은 정적 및 동적일 수 있습니다. 배열 크기가 선언에 지정된 경우 배열은 정적입니다.

double Variable[3];

정적 배열의 크기는 프로그램에서 변경할 수 없습니다. 배열을 선언할 때 배열의 크기는 위의 예와 같이 숫자로 직접 지정하거나 미리 정의된 상수를 사용하여 지정할 수 있습니다.

#define SIZE 3

double Variable[SIZE];

선언에 크기가 지정되지 않은 배열은 동적입니다.

double Variable[];

이러한 배열을 사용하려면 먼저 크기를 설정해야 합니다. 크기는 ArrayResize() 함수에 의해 설정됩니다.

ArrayResize(Variable,3);

동적 배열의 크기는 프로그램 실행 중에 필요한 만큼 변경할 수 있습니다. 이것이 동적 배열과 정적 배열의 근본적인 차이점입니다.

배열을 완전히 해제해야 하는 경우 ArrayFree() 함수를 사용합니다.

ArrayFree(Variable);

이 함수를 실행할 때 배열 크기는 0으로 설정됩니다. 이 함수에 의해 생성되는 효과는 다음 작업과 유사합니다.

ArrayResize(Variable,0);

배열을 해제하면 추가 프로그램 작업에 배열이 더 이상 필요하지 않을 때(이렇게 하면 프로그램에서 사용하는 메모리 양이 줄어듭니다) 또는 함수 실행이 시작될 때(배열이 데이터 수집에 사용되는 경우) 유용할 수 있습니다.

ArrayIsDynamic() 함수를 사용하면 주어진 배열이 정적 배열인지 동적 배열인지 결정할 수 있습니다.

bool dynamicArray=ArrayIsDynamic(Variable);

dynamicArray 변수는 배열이 동적이면 true 값을 포함하고 배열이 정적이면 false 값을 포함합니다.


배열 초기화

선언과 동시에 배열을 값으로 채워야 하는 경우가 있습니다. 동일한 유형의 버튼을 여러 개 만들고 각 버튼에 고유한 텍스트가 있는 행으로 정렬한다고 가정합니다. 바로 여기에서 배열의 큰 장점이 발휘됩니다. 각 버튼에 대한 코드를 복제할 필요가 없으며(수십 개일 수 있음) 동일한 기능을 반복적으로 호출할 필요도 없습니다. 함수 호출 코드를 한 번만 작성한 루프에서 배열을 반복하여 필요한 수의 버튼을 만들 수 있습니다.

단순히 배열을 선언하고 즉시 해당 요소에 값을 할당합니다.

string Variable[] = {"Button 1", "Button 2", "Button 3"};

이렇게 선언하면 배열의 크기가 지정되지 않았음에도 불구하고 배열은 여전히 ​​정적입니다. 이는 요소의 수가 값 목록(중괄호)으로 정의되기 때문입니다.

배열 요소의 수를 지정하면 실수가 없습니다.

string Variable[3] = {"Button 1", "Button 2", "Button 3"};

그러나 하지 않는 것이 좋습니다. 프로그램을 추가로 개선하는 과정에서 배열 값 목록을 변경하고 더 많거나 적은 수의 요소를 사용해야 할 수도 있습니다. 사용되는 코드 부분에서 배열의 크기를 결정하려면 특정 숫자 값 대신 ArraySize() 함수를 사용하는 것이 좋습니다. 이 접근 방식을 사용하면 기본 코드를 방해하지 않고 값 목록만 변경할 수 있습니다. 프로그램을 초기화할 때 ArraySize() 함수로 얻은 값을 배열 크기에 맞게 선언하고 할당하는 것이 더 적절할 것입니다.

값 목록으로 정적 배열을 초기화할 수 없는 경우 상수를 사용하여 배열 크기를 지정하는 것이 좋습니다. 일반적으로 프로그램의 추가 개선이 필요한 경우 수정해야 하는 코드의 양을 줄이는 원칙을 따릅니다. 모든 배열 요소를 동일한 값으로 채워야 하는 경우 ArrayInitialize() 함수를 사용합니다.

ArrayInitialize(Variable,1);

위의 코드를 실행하면 모든 Var 배열 요소의 값이 1이 됩니다. 일부 배열 요소에만 동일한 값을 할당해야 하는 경우 ArrayFill() 함수를 사용합니다.

double Variable[4];

ArrayFill(Variable,0,2,1);
ArrayFill(Variable,2,2,2);

이 코드를 실행하면 요소 0과 1의 값은 1이 되고 요소 2와 3의 값은 2가 됩니다.


배열 반복 루프

배열은 일반적으로 for 루프를 사용하여 처리됩니다. 크기가 미리 알려진 정적 배열을 사용할 때 당면한 작업에 따라 배열을 앞뒤로 반복합니다.

//--- forwards
for(int i=0; i<SIZE; i++){ 
  // some manipulations on the Variable[i] element
}

//--- backwards
for(int i=SIZE-1; i>=0; i--){
  // some manipulations on the Variable[i] element
}

배열이 동적이면 루프 바로 전에 배열 크기에 대한 변수를 선언하고 배열 크기를 가져와 루프를 수행해야 합니다.

int Size=ArraySize(Var);

for(int i=0; i<Size; i++){
  // some manipulations on the Variable[i] element
}

for 루프에서 조건을 확인할 때 배열 크기에 대한 변수를 사용하는 대신 ArraySize() 함수를 호출하면 ArraySize() 함수가 매번 루프 반복 때마다 호출되므로 루핑 시간이 크게 연장될 수 있습니다. 따라서 함수 호출은 변수를 호출하는 것보다 더 많은 시간이 걸립니다.

for(int i=0; i<ArraySize(Variable); i++){
   // some manipulations on the Variable[i] element
}
위 코드는 사용하지 않는 것이 좋습니다.

프로그램 알고리즘이 역방향 루프 반복을 허용하는 경우 배열 크기에 대한 변수 없이 수행할 수 있습니다.

for(int i=ArraySize(Variable)-1; i>=0; i--){
  // some manipulations on the Variable[i] element
}

이 경우 ArraySize() 함수는 루프의 시작 부분에서 한 번만 호출되며 루프는 빠르게 실행됩니다.


다차원 배열

지금까지 1차원 배열만 고려했습니다. 다음과 같이 나타낼 수 있습니다.

1차원 배열

배열은 다차원일 수 있습니다. 1차원 배열에는 인덱스당 하나의 값만 포함되지만 다차원 배열에는 인덱스당 둘 이상의 값이 있습니다. 다차원 배열은 다음과 같이 선언됩니다.

double Variable[10][3];

즉, 배열의 첫 번째 차원에는 10개의 요소가 있고 두 번째 차원에는 세 개의 요소가 있습니다. 다음과 같이 설명할 수 있습니다.

다차원 배열

이해를 돕기 위해 2차원 배열을 평면으로 나타낼 수 있습니다. 첫 번째 차원의 크기는 길이를 결정하고, 두 번째 차원의 크기는 너비를 결정하며, 요소 값은 평면에서 주어진 점의 매개변수를 정의합니다. 예를 들어, 해발 고도처럼 말이죠.

배열은 3차원일 수도 있습니다.

double Variable[10][10][10];

이 배열은 정육면체 또는 평행사변형으로 나타낼 수 있습니다. 첫 번째 차원은 길이를 결정하고, 두 번째 차원은 너비를 결정하고, 세 번째 차원은 높이를 결정하고, 요소 값은 공간에서 주어진 점의 매개변수를 정의합니다.

MQL5에서 허용되는 최대 배열 차원 수는 4개입니다.

다차원 배열은 첫 번째 차원에서만 정적이거나 동적일 수 있으며 모든 추가 차원은 정적입니다. 따라서 ArrayResize() 함수를 사용하면 첫 번째 차원의 크기만 변경할 수 있습니다. 배열을 선언할 때 다른 차원의 크기를 지정해야 합니다.'

double Variable[][3][3];

ArraySize() 함수를 사용하여 다차원 배열의 크기를 결정할 때 한 가지 유의해야 합니다. ArrayResize() 함수를 사용하여 배열 크기를 변경할 때 함수의 두 번째 매개 변수는 배열의 첫 번째 차원의 크기입니다. 그러나 ArraySize() 함수는 첫 번째 차원의 크기가 아닌 총 요소 수를 반환합니다.

double Variable[][3][3]; 

ArrayResize(Variable,3); 
int Size = ArraySize(Variable);

위 코드를 실행하면 Size 변수는 27이 됩니다. 첫 번째 차원의 크기를 가져와야 하는 경우 루프에서 다차원 배열을 반복할 때 이 특성을 기억하세요.

double Variable[][3][3];
 
ArrayResize(Variable,3); 

int Size=ArraySize(Variable)/9; // Determine the size of the first dimension

for(int i=0; i<Size; i++) {
   for(int j=0; j<3; j++) {
      for(int k=0; k<3; k++) {
            //  some manipulations on the Var[i][j][k] element;
      }   
   }   
}

앞에서 언급했듯이 프로그램의 추가 개선이 필요한 경우 수정해야 하는 코드의 양을 줄이는 원칙을 따르는 것이 좋습니다. 위의 코드 예제에서는 계산할 수도 있는 숫자 9를 사용했습니다. 이를 위해 배열의 지정된 차원에 포함된 요소 수를 반환하는 ArrayRange() 함수를 사용할 수 있습니다. 배열 차원의 수를 알고 있으면 간단한 계산을 수행할 수 있습니다.

int Elements=ArrayRange(Variable,1)*ArrayRange(Variable,2);
int Size=ArraySize(Variable)/Elements;

더 보편적으로 만들 수 있습니다.

int Elements=1; // One element for a one-dimensional array
int n=1; // Start with the second dimension (dimensions are numbered from zero)

while(ArrayRange(Variable,n) > 0){ // Until there are elements in the dimension
   Elements*=ArrayRange(Variable,n); // Multiplication of the number of elements
   n++; // Increase in the dimension's number
}

이쯤 되면 이런 계산을 위한 함수를 만드는 게 좋을 것 같다는 생각이 들 수도 있습니다. 불행히도 이것은 임의의 배열이 함수에 전달될 수 없기 때문에 불가능합니다. 함수 인수를 선언할 때 첫 번째를 제외한 모든 배열 차원의 요소 수를 명확하게 지정해야 하므로 이러한 함수는 무의미합니다. 이러한 계산은 프로그램 초기화에서 더 쉽고 더 잘 수행됩니다. 배열을 선언할 때 차원의 크기를 결정하는 상수를 사용하는 것이 좋습니다.

#define SIZE1 3
#define SIZE2 3
#define TOTAL SIZE1*SIZE2 

값 목록을 사용한 다차원 배열의 초기화는 1차원 배열의 초기화와 유사합니다. 그러나 다차원 배열은 일종의 다른 여러 배열로 구성되어 있으므로 이러한 각 배열은 중괄호로 구분해야 합니다.

다음과 같은 배열이 있다고 가정합니다.

double Variable[3][3];

이 배열은 각각 3개의 요소로 구성된 3개의 배열로 구성됩니다.

double Variable[][3]={{1, 2, 3},{ 4, 5, 6},{7, 8, 9}};

3차원 배열도 같은 방식으로 처리됩니다. 코드는 배열 구조를 쉽게 이해할 수 있도록 여러 줄로 나눌 수 있습니다.

double Variable[][3][3]={
   {
      {1, 2, 3},
      {4, 5, 6},
      {7, 8, 9}
   },
   {
      {10, 20, 30},
      {40, 50, 60},
      {70, 80, 90}
   },
   {
      {100, 200, 300},
      {400, 500, 600},
      {700, 800, 900}
   }
};

ArrayInitialize() 함수를 사용한 다차원 배열의 초기화는 1차원 배열의 초기화와 동일한 방식으로 수행됩니다.

ArrayInitialize(Variable,1);

코드를 실행한 후 모든 배열 요소의 값은 1입니다. ArrayFill() 함수도 마찬가지입니다.

double var[3][3][3];

ArrayFill(Variable,0,9,1);
ArrayFill(Variable,9,9,10);
ArrayFill(Variable,18,9,100);

이 코드를 실행하면 첫 번째 차원의 첫 번째 요소와 연결된 모든 요소의 값이 1이 되고 두 번째 요소와 연결된 요소의 값은 10이 되고 세 번째 요소와 연결된 요소는 100이 됩니다.


함수에 배열 전달

변수와 달리 배열은 참조로만 함수에 전달할 수 있습니다. 이것은 함수가 배열의 자체 인스턴스를 생성하지 않고 대신 전달된 배열과 직접 작동함을 의미합니다. 따라서 배열에 대한 함수의 모든 변경 사항은 원래 배열에 영향을 줍니다.

변수가 일반적인 방식으로(값에 의해) 함수에 전달되면 전달된 변수의 값은 함수에 의해 변경될 수 없습니다.

int x=1;
Func(x);

void  Func(int arg){
   arg=2;
}

Func() 함수를 실행한 후 x 값은 1로 유지됩니다.

변수가 참조로 전달되는 경우(&로 표시) 함수는 전달된 변수의 값을 변경할 수 있습니다.

int x=1;
Func(x);

void  Func(int &arg){
   arg=2;
}

Func() 함수를 실행한 후 x 값은 2가 됩니다.

함수에 배열을 전달할 때 인수가 참조로 전달되고 배열을 나타내도록 지정해야 합니다(괄호 안).

void Func(double &arg[]){
   // ...
}

다차원 배열을 함수에 전달할 때 차원 크기(첫 번째 제외)를 지정해야 합니다.

double var[][3][3];

void Func(double &arg[][3][3]){
   // ...
}

이 경우 상수를 사용하는 것이 더 좋습니다.

#define SIZE1 3
#define SIZE2 3

double Var[][SIZE1][SIZE2];

void Func(double &arg[][SIZE1][SIZE2]){
   // ...
}


파일에서 배열 저장 및 로드

파일에서 배열을 저장하고 로드할 때 배열의 첫 번째 차원 크기와 배열 요소의 총 개수 값의 차이를 항상 고려해야 합니다. 배열을 저장하려면 먼저 배열 크기(ArraySize() 함수에 의해 결정된 총 요소 수)를 작성한 다음 전체 배열을 파일에 작성합니다.

bool SaveArrayToFile(string FileName,string &Array[])
  {
//--- Open the file
   int h=FileOpen(FileName,FILE_TXT|FILE_WRITE);
   if(h==-1) return(false); // Error opening the file
//--- Write to the file
   FileWriteInteger(h,ArraySize(Array),INT_VALUE); // Write the array size
   FileWriteArray(h,Array); // Write the array
//--- Close the file
   FileClose(h);
   return(true); // Saving complete
  }

결과적으로 우리는 1차원 배열을 저장하는 매우 보편적인 함수를 얻습니다.

파일에서 배열을 로드하려면 먼저 배열 크기를 읽고 크기를 조정한 다음 마지막으로 배열을 읽어야 합니다.

bool LoadArrayFromFile(string FileName,double &Array[])
  {
//--- Open the file
   int h=FileOpen(FileName,FILE_BIN|FILE_READ);
   if(h==-1) return(false); // Error opening the file
//--- Read the file
   int Size=FileReadInteger(h,INT_VALUE); // Read the number of array elements
   ArrayResize(Array,Size); // Resize the array. 
                            // In one-dimensional arrays the size of the first dimension is equal to the number of array elements.
   FileReadArray(h,Array); // Read the array from the file
//--- Close the file
   FileClose(h);
   return(true); // Reading complete
  }

파일에서 다차원 배열을 로드할 때 첫 번째 차원의 크기를 계산해야 합니다. 예를 들어 3차원 배열을 읽고 있다고 가정해 보겠습니다.

bool LoadArrayFromFile3(string FileName,double &Array[][SIZE1][SIZE2])
  {
//--- Open the file
   int h=FileOpen(FileName,FILE_BIN|FILE_READ);
   if(h==-1)return(false); // Error opening the file
//--- Read the file   
   int SizeTotal=FileReadInteger(h,INT_VALUE); // Read the number of array elements
   int Elements=SIZE1*SIZE2; // Calculate the number of elements 
   int Size=SizeTotal/Elements; // Calculate the size of the first dimension
   ArrayResize(Array,Size); // Resize the array
   FileReadArray(h,Array); // Read the array
//--- Close the file
   FileClose(h);
   return(true); // Reading complete
  }

파일에 2x3 배열이 포함되어 있을 수 있지만 우리는 3x3 배열로 읽으려고 합니다. 계산된 첫 번째 차원의 크기에 요소의 개수를 곱하여 크기 간의 대응 관계를 확인할 수 있습니다. 결과 값이 배열 요소의 총 수와 같으면 해당 항목에 대해 말할 수 있습니다.

그러나 Var[2][3] 배열은 Var[3][2] 배열에 해당합니다. 이 경우도 처리해야 하는 경우 다차원 배열에 더 많은 정보를 저장해야 합니다. 예를 들어 먼저 배열 요소의 수를 저장한 다음 배열 차원의 수와 각 차원의 크기 및 배열 자체를 저장할 수 있습니다.

위에 제공된 마지막 함수는 보편적이지 않으며 두 번째 차원의 크기가 SIZE1이고 세 번째 차원의 크기가 SIZE2인 3차원 배열만 읽도록 설계되었습니다. 첫 번째를 제외하고 모든 배열 차원의 크기를 동적으로 변경할 수 있는 방법이 없기 때문에 문제가 되지 않습니다. 프로그램에서 사용해야 하는 배열에 대한 함수를 만들 것입니다.

이 경우 보편성은 필요하지 않습니다. 배열 차원의 크기(첫 번째 크기 제외)는 프로그램의 외부 매개변수를 통해 제어되지 않습니다. 그러나 다른 차원의 크기를 제어할 수 있는 가능성을 구현해야 하는 경우 더 큰 크기와 추가 변수의 다차원 배열을 사용하거나 객체 지향 프로그래밍(OOP) 기술을 적용하여 이 작업을 해결할 수 있습니다. 이 문서의 뒷부분에서 두 번째 접근 방식에 대해 더 자세히 이야기하겠습니다.


동적 배열 사용

동적 배열은 배열 크기를 미리 알지 못할 때 사용됩니다. 배열 크기가 프로그램 속성 창에서 설정한 매개변수에 따라 달라지는 경우 동적 배열을 사용하는 것은 문제가 되지 않습니다. 배열 크기는 프로그램 초기화 중에 한 번만 변경됩니다.

배열을 사용하여 특정 정보를 동적으로 수집할 수 있습니다 (예, 보류 중인 주문에 대해). 그 수는 다를 수 있습니다. 즉, 필요한 크기를 미리 알 수 없다는 뜻이 되죠. 이 경우 가장 쉬운 방법은 주문을 통과하기 전에 배열 크기를 0으로 변경하고 각 주문을 통과할 때마다 배열 크기를 한 요소씩 늘리는 것입니다. 이는 물론 통하긴 하나 느립니다.

주문을 통과하기 전에 주문 수에 따라 배열 크기를 한 번만 변경할 수 있습니다. 이는 배열의 마지막 활성 요소(또는 인덱스 대신 실제 활성 배열 요소의 수)의 인덱스에 대해 다른 변수를 요합니다. 이 방법은 최대 배열 크기를 이미 알고 있는 경우에 적합합니다. 최대 배열 크기를 알 수 없는 경우 다음 클래스와 같이 청크를 사용하여 배열 크기를 조정하여 작업 속도를 높일 수 있습니다.

class CDynamicArray
  {
private:
   int               m_ChunkSize;    // Chunk size
   int               m_ReservedSize; // Actual size of the array
   int               m_Size;         // Number of active elements in the array
public:
   double            Element[];      // The array proper. It is located in the public section, 
                                     // so that we can use it directly, if necessary
   //+------------------------------------------------------------------+
   //|   Constructor                                                    |
   //+------------------------------------------------------------------+
   void CDynamicArray(int ChunkSize=1024)
     {
      m_Size=0;                            // Number of active elements
      m_ChunkSize=ChunkSize;               // Chunk size
      m_ReservedSize=ChunkSize;            // Actual size of the array
      ArrayResize(Element,m_ReservedSize); // Prepare the array
     }
   //+------------------------------------------------------------------+
   //|   Function for adding an element at the end of array             |
   //+------------------------------------------------------------------+
   void AddValue(double Value)
     {
      m_Size++; // Increase the number of active elements
      if(m_Size>m_ReservedSize)
        { // The required number is bigger than the actual array size
         m_ReservedSize+=m_ChunkSize; // Calculate the new array size
         ArrayResize(Element,m_ReservedSize); // Increase the actual array size
        }
      Element[m_Size-1]=Value; // Add the value
     }
   //+------------------------------------------------------------------+
   //|   Function for getting the number of active elements in the array|
   //+------------------------------------------------------------------+
   int Size()
     {
      return(m_Size);
     }
  };

첨부된 CDynamicArray.mqh 파일에서 이 클래스를 찾을 수 있습니다. 파일은 터미널 데이터 디렉토리의 MQL5\Include 폴더에 있어야 합니다.

이제 배열 크기가 1씩 증가하고 청크를 사용하여 증가하는 두 상황에서 코드의 성능을 평가하고 비교하겠습니다.

int n=50000;
   double ar[];
   CDynamicArray da;

//--- Option 1 (increasing the size by the 1st element)
   long st=GetTickCount(); // Store the start time 
   ArrayResize(ar,0); // Set the array size to zero 
   for(int i=0;i<n;i++)
     {
      ArrayResize(ar,i+1); // Resize the array sequentially
      ar[i]=i;
     }
   Alert("Option 1: "+IntegerToString(GetTickCount()-st)+" ms"); // Message regarding the amount of time required to perform the first option

//--- Option 2 (increasing the size using chunks)
   st=GetTickCount(); // Store the start time 
   for(int i=0;i<n;i++)
     {
      da.AddValue(i); // Add an element
     }
   Alert("Option 2: "+IntegerToString(GetTickCount()-st)+" ms"); // Message regarding the amount of time required to perform the second option

  }

스크립트 형태의 이 테스트는 첨부된 sTest_Speed.mq5 파일에서 확인할 수 있습니다. 파일은 터미널 데이터 디렉토리의 MQL5\Scripts 폴더에 있어야 합니다.

첫 번째 옵션의 성능은 몇 초가 걸렸지만 두 번째 옵션은 거의 즉각적이었습니다.


배열 인덱싱 순서

일반적으로 배열의 크기를 조정할 때 배열 끝에 새 요소가 추가됩니다.

double ar[]; // Array
ArrayResize(ar,2); // Prepare the array
ar[0]=1; // Set the values
ar[1]=2; 
ArrayResize(ar,3); // Increase the array size
ar[2]=3; // Set the value for the new array element
Alert(ar[0]," ",ar[1]," ",ar[2]); // Print array values

이 코드를 실행한 후 배열에 포함된 값은 1, 2, 3이어야 합니다.

배열의 요소는 역순으로 인덱싱할 수도 있습니다. 인덱싱 순서는 ArraySetAsSeries() 함수에 의해 설정됩니다.

ArraySetAsSeries(ar,true); // set indexing in reverse order
ArraySetAsSeries(ar,false); // set normal indexing

역순으로 인덱싱된 배열의 크기를 변경하면 배열의 시작 부분에 새 요소가 추가됩니다.

double ar[]; // Array
ArrayResize(ar,2); // Prepare the array
ar[0]=1; // Set the values
ar[1]=2; 
ArraySetAsSeries(ar,true); // Change the indexing order
ArrayResize(ar,3); // Increase the array size
ar[0]=3; // Set the value for the new array element
Alert(ar[0]," ",ar[1]," ",ar[2]); // Print array values

이 코드를 실행한 후 배열에 포함된 값은 3, 2, 1이어야 합니다.

두 경우 모두 새 요소가 배열의 같은 쪽에 추가되며 유일한 차이점은 인덱싱 순서입니다. 이 함수는 요소가 정상적인 순서로 인덱싱되는 배열의 시작 부분에 요소를 추가하는 데 사용할 수 없습니다. 정상적으로 인덱싱된 배열의 끝에 요소를 추가하려면 배열 크기를 늘리고 마지막 요소에 값을 할당하기만 하면 됩니다.

배열의 시작 부분에 요소를 추가하려면 배열 크기를 늘리고 모든 값을 이동한 다음 0 요소에 새 값을 할당해야 합니다. 역순으로 인덱싱된 배열에서 새 요소를 배열의 시작 부분에 쉽게 추가할 수 있습니다. 그러나 배열의 끝에 새 요소를 추가해야 하는 경우 먼저 배열 크기를 늘리고 모든 값을 처음으로 이동한 후 마지막 요소에 새 값을 할당해야 합니다. 인덱싱 순서 조작으로는 이 문제를 해결할 수 없습니다.

배열 인덱싱 순서는 ArrayIsSeries() 함수를 사용하여 결정할 수 있습니다.

bool series=ArrayIsSeries(ar);

배열이 역순으로 인덱싱되면 함수는 true를 반환합니다.

역순으로 인덱싱된 배열은 주로 Expert Advisors에서 사용됩니다. EA를 개발할 때 오른쪽에서 왼쪽으로 바를 계산하는 것이 종종 더 편리하므로 역 인덱싱을 사용하여 가격 데이터와 지표 버퍼를 배열에 복제합니다.


배열 복제

복제하는 가장 쉬운 방법은 루프의 배열을 반복하고 한 배열에서 다른 배열로 요소별로 복제하는 것입니다. 그러나 MQL5에는 배열을 복제할 수 있는 특수 기능(ArrayCopy())이 있습니다.

double ar1[]={1,2,3};
double ar2[];

ArrayCopy(ar2,ar1);

위의 코드를 실행하면 ar2 배열은 ar1 배열과 동일한 값인 1, 2, 3의 세 가지 요소로 구성됩니다.

복제할 요소의 수가 복제하려는 배열에 맞지 않으면 배열 크기가 자동으로 증가합니다(배열은 동적이어야 함). 배열이 복제할 요소의 수보다 크면 크기가 동일하게 유지됩니다.

ArrayCopy() 함수를 사용하면 배열의 일부만 복제할 수도 있습니다. 함수의 선택적 매개변수를 사용하여 복제할 첫 번째 요소, 새 배열에서 첫 번째 복제된 요소의 인덱스 및 복제하려는 요소의 수를 지정할 수 있습니다.

한 배열의 요소를 다른 배열로 복제하는 것 외에도 ArrayCopy() 함수를 사용하여 동일한 배열 내의 요소를 복제할 수 있습니다.

double ar1[]={1,2,3,4,5};
ArrayCopy(ar1,ar1,1,2);

인덱스가 2인 요소로 시작하는 데이터를 복제하고 인덱스 1부터 붙여넣습니다. 이 코드를 실행하면 배열에 1, 3, 4, 5, 5 값이 포함됩니다.

ArrayCopy() 함수를 사용하면 데이터를 오른쪽으로 이동할 수도 있습니다.

double ar1[]={1,2,3,4,5};
ArrayCopy(ar1,ar1,2,1);

인덱스가 1인 요소로 시작하는 데이터를 가져와 인덱스 2부터 정렬합니다. 이 코드를 실행하면 배열에 1, 2, 2, 3, 4 값이 포함됩니다.

ArrayCopy() 함수는 배열이 1차원이고 모든 요소가 직렬로 배열된 것처럼 동작하는 다차원 배열에도 적용할 수 있습니다.

double ar[3][2]={{1, 2},{3, 4},{5, 6}};
ArrayCopy(ar,ar,2,4);

이 코드를 실행하면 배열이 {1, 2}, {5, 6}, {5, 6}과 같이 나타납니다.


배열 정렬

ArraySort() 함수를 사용하여 배열을 정렬할 수 있습니다.

double ar[]={1,3,2,5,4};
ArraySort(ar);

위의 코드를 실행하면 배열 값이 1, 2, 3, 4, 5의 순서로 정렬됩니다.

ArraySort() 함수는 다차원 배열에 적용할 수 없습니다. "MQL5의 전자 테이블"이라는 제목의 글에서 다차원 배열 및 데이터 구조 정렬에 대한 정보를 찾을 수 있습니다.


이진 검색을 수행하려면 ArrayBsearch() 함수를 사용합니다. 이 함수는 정렬된 배열에서만 제대로 작동합니다. 이진 검색은 알고리즘이 배열을 계속해서 두 부분으로 나눕니다. 알고리즘은 먼저 대상 값을 배열의 중간 요소 값과 비교하여 대상 요소를 포함하는 절반(왼쪽의 하위 배열 또는 오른쪽의 하위 배열)을 결정합니다. 그런 다음 대상 값을 하위 배열 등의 중간 요소 값과 비교합니다.

ArrayBsearch() 함수는 대상 값이 있는 요소의 인덱스를 반환합니다.

double ar[]={1,2,3,4,5};

int index=ArrayBsearch(ar,3);

이 코드를 실행하면 인덱스 변수의 값이 2가 됩니다.

배열에서 대상 값을 찾을 수 없는 경우 함수는 가장 가까운 작은 값을 가진 요소의 인덱스를 반환합니다. 이 속성으로 인해 이 기능을 사용하여 시간별로 바를 검색할 수 있습니다. 지정된 시간의 바가 없으면 시간이 더 짧은 바를 사용하여 계산해야 합니다.

목표 값이 배열에 없고 배열 값의 범위를 벗어나면 함수는 0(목표 값이 최소값보다 작은 경우) 또는 마지막 인덱스(목표 값이 최대값보다 큰 경우)를 반환합니다.).

정렬되지 않은 배열에서 검색을 수행할 수 있는 방법은 배열에 대한 반복뿐입니다.

int FindInArray(int &Array[],int Value){
   int size=ArraySize(Array);
      for(int i=0; i<size; i++){
         if(Array[i]==Value){
            return(i);
         }
      }
   return(-1);
}

위의 예에서 함수는 대상 값이 있는 요소의 인덱스를 반환합니다. 대상 값이 배열에 없으면 함수는 -1을 반환합니다.


최대값과 최소값 찾기

배열의 최대값과 최소값은 각각 최대값 또는 최소값으로 요소의 인덱스를 반환하는 ArrayMaximum()ArrayMinimum() 함수를 사용하여 찾을 수 있습니다.

double ar[]={3,2,1,2,3,4,5,4,3};

int MaxIndex=ArrayMaximum(ar);
int MinIndex=ArrayMinimum(ar);
double MaxValue=ar[MaxIndex];
double MinValue=ar[MinIndex];

이 코드를 실행하면 MaxIndex 변수는 6, MinIndex 변수는 2, MaxValue는 5, MinValue는 1이 됩니다.

ArrayMaximum() 및 ArrayMinimum() 함수를 사용하면 검색 범위의 첫 번째 요소 인덱스와 검색 범위의 요소 수를 지정하여 검색 범위를 제한할 수 있습니다.

int MaxIndex=ArrayMaximum(ar,5,3);
int MinIndex=ArrayMinimum(ar,5,3);

이 경우 MaxIndex의 값은 6이고 MinIndex는 -5입니다. 지정된 범위에는 최소값이 4인 두 포지션(포지션 5 및 포지션 7)가 포함되어 있습니다. 이와 같은 상황에서 함수는 배열의 시작 부분에 더 가까운 요소의 인덱스를 반환합니다. 이 함수는 역순으로 인덱싱된 배열과 동일한 방식으로 작동하며 가장 작은 인덱스를 반환합니다.

이제 배열 작업을 위해 MQL5에서 사용할 수 있는 모든 표준 기능을 검토했습니다.


OOP를 사용하여 다차원 배열 만들기

다차원 배열을 만들기 위한 클래스 집합에는 기본 클래스와 두 개의 자식 클래스의 세 가지 클래스가 포함됩니다. 개체 생성 단계에서 선택한 자식 클래스에 따라 개체는 이중 변수의 배열 또는 개체의 배열을 나타낼 수 있습니다. 개체 배열의 각 요소는 개체 또는 변수의 다른 배열을 나타낼 수 있습니다.

기본 클래스와 자식 클래스에는 기본 클래스의 소멸자와 자식 클래스의 생성자를 제외하고 거의 모든 함수가 포함되어 있지 않습니다. 기본 클래스의 소멸자는 프로그램 또는 기능이 완료되면 모든 개체를 삭제하는 역할을 합니다. 자식 클래스의 생성자는 생성자의 매개변수에 지정된 크기에 따라 배열의 크기를 조정하는 데만 사용됩니다. 즉, 한 클래스의 개체 배열과 다른 클래스의 변수 배열의 크기를 조정합니다. 다음은 이러한 클래스의 구현을 위한 코드입니다.

//+------------------------------------------------------------------+
//|   Base class                                                     |
//+------------------------------------------------------------------+
class CArrayBase
  {
public:
   CArrayBase       *D[];
   double            V[];

   void ~CArrayBase()
     {
      for(int i=ArraySize(D)-1; i>=0; i--)
        {
         if(CheckPointer(D[i])==POINTER_DYNAMIC)
           {
            delete D[i];
           }
        }
     }
  };
//+------------------------------------------------------------------+
//|   Child class 1                                                  |
//+------------------------------------------------------------------+
class CDim : public CArrayBase
  {
public:
   void CDim(int Size)
     {
      ArrayResize(D,Size);
     }
  };
//+------------------------------------------------------------------+
//|   Child class 1                                                  |
//+------------------------------------------------------------------+
class CArr : public CArrayBase
  {
public:
   void CArr(int Size)
     {
      ArrayResize(V,Size);
     }
  };

첨부된 CMultiDimArray.mqh 파일에서 이러한 클래스를 찾을 수 있습니다. 파일은 터미널 데이터 디렉토리의 MQL5\Include 폴더에 있어야 합니다.

이제 이 클래스를 사용하여 유사성 1차원 배열을 작성해 보겠습니다.

CArrayBase * A; // Declare a pointer
   A=new CArr(10); // Load a child class instance that scales the array of variables. 
                   // The array will consist of 10 elements.

//--- Now the array can be used:
   for(int i=0; i<10; i++)
     {
      //--- Assign to each element of the array successive values from 1 to 10
      A.V[i]=i+1;
     }
   for(int i=0;i<10;i++)
     {
      //--- Check the values
      Alert(A.V[i]);
     }
   delete A; // Delete the object
  }

스크립트 형태의 이 예제는 첨부된 sTest_1_Arr.mq5 파일에서 찾을 수 있습니다. 파일은 터미널 데이터 디렉토리의 MQL5\Scripts 폴더에 있어야 합니다.

이제 2차원 배열을 만들어 보겠습니다. 첫 번째 차원의 각 요소에는 두 번째 차원의 다른 수의 요소가 포함됩니다. 첫 번째 차원에 하나, 두 번째 차원에 두 개 등:

CArrayBase*A;  // Declare a pointer
   A=new CDim(3); // The first dimension represents an array of objects

//--- Each object of the first dimension represents an array of variables of different sizes 
   A.D[0]=new CArr(1);
   A.D[1]=new CArr(2);
   A.D[2]=new CArr(3);
//--- Assign values
   A.D[0].V[0]=1;

   A.D[1].V[0]=10;
   A.D[1].V[1]=20;

   A.D[2].V[0]=100;
   A.D[2].V[1]=200;
   A.D[2].V[2]=300;
//--- Check the values
   Alert(A.D[0].V[0]);

   Alert(A.D[1].V[0]);
   Alert(A.D[1].V[1]);

   Alert(A.D[2].V[0]);
   Alert(A.D[2].V[1]);
   Alert(A.D[2].V[2]);
//---
   delete A; // Delete the object

스크립트 형태의 이 예제는 첨부된 sTest_2_Dim.mq5 파일에서 찾을 수 있습니다. 파일은 터미널 데이터 디렉토리의 MQL5\Scripts 폴더에 있어야 합니다.

클래스에 배열 크기를 변경하는 메소드가 없기 때문에 결과 배열은 좀 정적입니다. 그러나 D[] 및 V[] 배열은 클래스의 공개 섹션에 있으므로 모든 조작에 사용할 수 있습니다. 그리고 어려움 없이 V[] 배열을 확장할 수 있습니다. D[] 배열의 크기를 조정하고 크기를 줄일 때는 먼저 삭제할 개체가 가리키는 개체를 삭제하거나 배열 크기를 늘릴 때 개체를 로드해야 합니다.

원하는 경우 OOP 또는 데이터 구조를 사용하여 다차원 배열을 구현하는 다른 방법을 생각할 수도 있습니다.


결론

이 글에서는 배열 작업을 위해 MQL5에서 사용할 수 있는 모든 표준 기능을 다뤘습니다. 배열을 처리하는 데 있어 가장 중요한 몇 가지 기술과 특성을 검토했습니다. MQL5 언어는 총 15개의 기능을 제공하며, 그 중 일부는 가장 중요하지만 다른 기능은 비 전통적인 문제를 해결해야 하는 경우를 제외하고 완전히 사용되지 않을 수 있습니다. 기능은 중요도와 사용 빈도에 따라 다음과 같이 정렬할 수 있습니다.

  1. ArraySize()ArrayResize()는 필수 함수입니다.

  2. ArrayMaximum(), ArrayMinimum(), ArrayCopy(), ArrayInitialize(), ArrayFill() ArrayFree()는 배열 작업을 훨씬 쉽게 만들어주는 함수입니다.

  3. ArraySort()는 중요하고 편리한 함수이지만 기능이 낮아 거의 사용되지 않습니다.

  4. ArrayBsearch()는 거의 사용되지 않는 함수이지만 드문 예외적인 경우에 매우 중요할 수 있습니다.

  5. ArraySetAsSeries(), ArrayRange(), ArrayGetAsSeries(), ArrayIsDynamic()ArrayIsSeries() 는 거의 사용되지 않거나 거의 사용되지 않는 기능입니다.

이 글에서 설명하는 프로그래밍 기술 중 하나인 동적 배열의 사용은 프로그램 성능에 큰 영향을 미치고 프로그램 성능을 결정한다고 말할 수 있으므로 특히 주의해야 합니다.

MetaQuotes 소프트웨어 사를 통해 러시아어가 번역됨.
원본 기고글: https://www.mql5.com/ru/articles/567

파일 첨부됨 |
cdynamicarray.mqh (2.59 KB)
stest_speed.mq5 (1.6 KB)
cmultidimarray.mqh (1.67 KB)
stest_1_arr.mq5 (1.29 KB)
stest_2_dim.mq5 (1.43 KB)
"즉석에서" 사용자 패널에서 Expert Advisor 매개변수 변경 "즉석에서" 사용자 패널에서 Expert Advisor 매개변수 변경
이 글은 사용자 패널에서 매개변수를 제어할 수 있는 Expert Advisor의 구현을 보여주는 작은 예시를 제공합니다. "즉시" 매개변수를 변경할 때 Expert Advisor는 정보 패널에서 얻은 값을 파일에 기록하여 파일에서 추가로 읽고 그에 따라 패널에 표시합니다. 이 글은 수동 또는 반자동 모드에서 거래하는 사람들과 관련이 있을 수 있습니다.
MetaTrader 5의 신호 거래: PAMM 계정에 대한 더 나은 대안! MetaTrader 5의 신호 거래: PAMM 계정에 대한 더 나은 대안!
MetaTrader 5가 이제 거래 신호를 제공하여 투자자와 관리자에게 강력한 도구를 제공하게 되었음을 알려드립니다. 성공적인 거래자의 거래를 추적하는 동안 터미널은 자동으로 귀하의 계정에서 거래를 재생산합니다!
머신 러닝: 트레이딩에서 서포트 벡터 머신을 사용하는 방법 머신 러닝: 트레이딩에서 서포트 벡터 머신을 사용하는 방법
Support Vector Machine은 복잡한 데이터 세트를 평가하고 데이터를 분류하는 데 사용할 수 있는 유용한 패턴을 추출하기 위해 생물정보학 및 응용 수학과 같은 분야에서 오랫동안 사용되어 왔습니다. 이 글에서는 서포트 벡터 머신이 무엇인지, 어떻게 작동하는지, 왜 복잡한 패턴을 추출하는 데 유용할 수 있는지 살펴봅니다. 그런 다음 시장에 적용할 수 있는 방법과 잠재적으로 거래에 조언하는 데 사용할 수 있는 방법을 조사합니다. Support Vector Machine Learning Tool을 사용하여 이 글은 독자가 자신의 거래를 실험할 수 있는 작업 예제를 제공합니다.
차트에서 거래 아이디어의 빠른 테스트 차트에서 거래 아이디어의 빠른 테스트
이 글은 거래 아이디어를 시각적으로 빠르게 테스트하는 방법을 설명합니다. 이 방법은 가격 차트, 신호 지표 및 잔액 계산 지표의 조합을 기반으로 합니다. 거래 아이디어를 검색하는 방법과 이러한 아이디어를 빠르게 테스트하는 데 사용하는 방법을 공유하고 싶습니다.