Sở hữu ngay máy chủ VPS Robot Forex khi giao dịch tại HotForex

Mục lục:

  1. Định nghĩa và tính chất chung của mảng
  2. Mảng tĩnh và động
  3. Khởi tạo mảng
  4. Vòng lặp mảng
  5. Mảng đa chiều
  6. Truyền một mảng cho hàm
  7. Lưu và tải Mảng từ một tệp
  8. Sử dụng mảng động
  9. Thứ tự chỉ mục mảng
  10. Sao chép mảng
  11. Sắp xếp mảng
  12. Tìm kiếm nhị phân
  13. Tìm giá trị lớn nhất và nhỏ nhất
  14. Tạo các mảng đa chiều bằng OOP
  15. Kết luận

Mảng là một phần không thể thiếu của hầu hết mọi ngôn ngữ lập trình cùng với các biến và hàm. Nhiều lập trình viên mới làm quen thường sợ mảng. Nghe có vẻ lạ nhưng đó là sự thật! Tôi có thể đảm bảo với bạn rằng chúng không đáng sợ chút nào. Trong thực tế, mảng tương tự như các biến thông thường. Không đi sâu vào chi tiết về các đặc thù của ký hiệu, không có sự khác biệt lớn giữa việc viết một biểu thức bằng các biến đơn giản:

Variable0=1;
Variable1=2;

Variable2=Variable0+Variable1;

Hoặc sử dụng mảng:

double Variable[3];

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

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

Như bạn có thể thấy, sự khác biệt không lớn lắm ngoại trừ thực tế là khi chúng ta sử dụng mảng, tên của các biến chứa dấu ngoặc. Có một sự khác biệt quan trọng hơn – khi khai báo các biến, bạn cần chỉ định tên của từng biến, trong khi khai báo một mảng, bạn chỉ cần viết tên của nó một lần và chỉ định số lượng biến trong ngoặc (số phần tử mảng) . Những lợi thế của việc sử dụng mảng trên các biến càng trở nên rõ ràng hơn khi xử lý các thách thức của một số lượng lớn các nhiệm vụ lập trình thực tế.

Có thể là lý do tại sao các mảng được coi là một cái gì đó phức tạp có liên quan đến việc sử dụng ‘[‘ và ‘]’ không? Những ký hiệu này hiếm khi được sử dụng ở bất cứ đâu ngoài lập trình khi làm việc với mảng, vì vậy vị trí của chúng trên bàn phím có thể mờ dần khỏi bộ nhớ của một người và gây khó chịu. Mặc dù trên thực tế, bạn có thể dễ dàng nhớ vị trí của chúng – hai phím này được đặt bên cạnh ‘Enter’ theo thứ tự hợp lý: dấu ngoặc mở được theo sau bởi dấu đóng.

Định nghĩa và tính chất chung của mảng

Vì vậy, một mảng là một tập hợp các biến có cùng tên. Các thuộc tính chung của mảng bao gồm tên của mảng, kiểu biến (int, double, v.v.) và kích thước mảng. Các phần tử mảng được lập chỉ mục từ số không. Nói về các phần tử mảng, tốt hơn là sử dụng từ ‘index’ thay vì ‘number’ để gợi ý rằng chúng ta bắt đầu đếm các phần tử mảng từ 0 (trong khi việc đánh số thường bắt đầu từ một). Với các phần tử được lập chỉ mục theo cách này, chỉ mục của phần tử cuối cùng ít hơn một phần tử so với số phần tử mảng.

Nếu mảng được khai báo như sau:

double Variable[3];

Nó có các yếu tố sau: Variable[0], Variable[1] and Variable[2].

Về mặt này, việc thiếu sự tương ứng giữa số lượng phần tử và chỉ số của phần tử cuối cùng có vẻ bất tiện. Trong thực tế, nó cung cấp các lợi thế đáng kể so với các ngôn ngữ lập trình nơi các phần tử mảng được lập chỉ mục từ 1 hoặc kích thước mảng được xác định bởi chỉ mục của phần tử cuối cùng thay vì số phần tử thực tế trong mảng.

Để xác định kích thước mảng trong MQL5, chúng ta sử dụng hàm ArraySize():

An toàn & bảo mật vốn đầu tư tại HotForex
double Variable[3];

int Size=ArraySize(Variable);

Sau khi thực thi mã, giá trị của biến Kích thước sẽ bằng 3.

Mảng tĩnh và động

Mảng có thể là tĩnh và động. Nếu kích thước mảng được chỉ định trong khai báo của nó, mảng là tĩnh:

double Variable[3];

Kích thước của một mảng tĩnh không thể thay đổi trong chương trình. Khi khai báo một mảng, kích thước của nó có thể được chỉ định trực tiếp dưới dạng một số (như trong ví dụ trên) hoặc sử dụng hằng số được xác định trước:

#define SIZE 3

double Variable[SIZE];

Một mảng có kích thước không được chỉ định trong khai báo là động:

double Variable[];

Trước khi bạn có thể sử dụng một mảng như vậy, bạn cần đặt kích thước của nó. Kích thước được đặt bởi hàm ArrayResize():

ArrayResize(Variable,3);

Kích thước của một mảng động có thể được thay đổi trong quá trình thực hiện chương trình nhiều lần là cần thiết, đó là sự khác biệt cơ bản giữa các mảng động và mảng tĩnh.

Nếu bạn cần giải phóng hoàn toàn mảng, hãy sử dụng hàm ArrayFree():

ArrayFree(Variable);

Khi thực hiện chức năng này, kích thước mảng được đặt thành 0. Hiệu ứng được tạo bởi chức năng này tương tự như hành động sau:

ArrayResize(Variable,0);

Giải phóng mảng có thể hữu ích khi mảng không còn cần thiết cho hoạt động chương trình tiếp theo (điều này làm giảm dung lượng bộ nhớ được sử dụng bởi chương trình) hoặc khi bắt đầu thực thi chức năng (nếu mảng được sử dụng để thu thập dữ liệu).

Hàm ArrayIsDocate() cho phép bạn xác định xem bất kỳ mảng đã cho nào là tĩnh hay động:

bool dynamicArray=ArrayIsDynamic(Variable);

Biến DynamicArray sẽ chứa một giá trị true nếu mảng là giá trị động hoặc false nếu mảng là tĩnh.

Khởi tạo mảng

Đôi khi cần phải điền vào một mảng với các giá trị ngay sau khi khai báo. Giả sử bạn muốn tạo một số nút cùng loại và sắp xếp chúng thành một hàng, với mỗi nút có văn bản riêng. Đó là nơi mà những lợi thế lớn của mảng phát huy tác dụng. Không cần phải sao chép mã cho mỗi nút (có thể có hàng tá trong số chúng), cũng không cần phải gọi lại cùng một chức năng. Bạn có thể tạo số lượng nút cần thiết bằng cách lặp qua mảng trong một vòng lặp chỉ viết mã gọi hàm một lần.

Chúng tôi chỉ cần khai báo một mảng và gán ngay các giá trị cho các phần tử của nó:

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

Được khai báo theo cách này, mảng sẽ vẫn tĩnh mặc dù thực tế là kích thước của nó không được chỉ định. Điều này là do số lượng phần tử của nó được xác định bởi danh sách các giá trị (trong dấu ngoặc nhọn).

Sẽ không có lỗi nếu bạn chỉ định số lượng phần tử mảng:

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

Nhưng sẽ tốt hơn nếu không làm điều đó – trong quá trình cải tiến hơn nữa cho chương trình, bạn có thể cần thay đổi danh sách các giá trị mảng và sử dụng số lượng phần tử lớn hơn hoặc ít hơn. Để xác định kích thước của mảng trong các phần của mã được sử dụng, nên sử dụng hàm ArraySize() thay vì một giá trị số nhất định. Cách tiếp cận này cho phép bạn chỉ thay đổi danh sách các giá trị mà không can thiệp vào mã chính. Sẽ thích hợp hơn khi khai báo biến cho kích thước mảng và gán cho nó giá trị thu được bởi hàm ArraySize() khi khởi tạo chương trình.

Nếu một mảng tĩnh không thể được khởi tạo bởi danh sách các giá trị, tốt hơn là sử dụng hằng số để chỉ định kích thước mảng. Nói chung, chúng tôi tuân theo nguyên tắc giảm số lượng mã cần phải sửa đổi nếu cần cải thiện thêm chương trình. Nếu bạn cần điền tất cả các phần tử mảng với cùng các giá trị, hãy sử dụng hàm ArrayInitialize():

ArrayInitialize(Variable,1);

Sau khi thực thi đoạn mã trên, tất cả các phần tử mảng Var sẽ có giá trị là 1. Nếu các giá trị tương tự chỉ cần được gán cho một số phần tử mảng, chúng ta sử dụng hàm ArrayFill():

double Variable[4];

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

Sau khi thực thi mã này, các phần tử 0 và 1 sẽ có giá trị là 1, trong khi các phần tử 2 và 3 sẽ có giá trị là 2.

Vòng lặp mảng

Mảng thường được xử lý bằng vòng lặp for. Khi sử dụng một mảng tĩnh có kích thước được biết trước, chúng tôi lặp lại qua mảng tiến hoặc lùi, tùy thuộc vào nhiệm vụ trong tay:

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

Nếu mảng là động, bạn nên khai báo một biến cho kích thước mảng ngay trước vòng lặp, lấy kích thước mảng và thực hiện một vòng lặp:

int Size=ArraySize(Var);

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

Nếu thay vì sử dụng một biến cho kích thước mảng, bạn gọi hàm ArraySize() khi kiểm tra điều kiện trong một vòng lặp for, thời gian lặp có thể được kéo dài đáng kể vì hàm ArraySize() sẽ được gọi ở mỗi lần lặp. Vì vậy, lệnh gọi mất nhiều thời gian hơn gọi một biến:

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

Không nên sử dụng mã trên.

Nếu thuật toán chương trình cho phép lặp lại vòng lặp ngược, bạn có thể thực hiện mà không cần một biến cho kích thước mảng:

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


Trong trường hợp này, hàm ArraySize() sẽ chỉ được gọi một lần ở đầu vòng lặp và vòng lặp sẽ chạy nhanh.

Mảng đa chiều

Chúng ta cho đến nay chỉ xem xét các mảng một chiều. Chúng có thể được biểu diễn như sau:

Mảng có thể là đa chiều. Trong khi mảng một chiều chỉ chứa một giá trị cho mỗi chỉ mục, thì mảng đa chiều có nhiều hơn một giá trị trên mỗi chỉ mục. Mảng nhiều chiều được khai báo như sau:

double Variable[10][3];

Điều này có nghĩa là chiều thứ nhất của mảng có mười phần tử và chiều thứ hai có ba phần tử. Nó có thể được minh họa như sau:

Để dễ hiểu hơn, một mảng hai chiều có thể được mô tả như một mặt phẳng. Kích thước của kích thước thứ nhất xác định chiều dài, kích thước của kích thước thứ hai xác định chiều rộng và giá trị của phần tử xác định tham số của một điểm đã cho trên mặt phẳng, ví dụ: Độ cao so với mực nước biển.

Một mảng cũng có thể là ba chiều:

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

Mảng này có thể được biểu diễn dưới dạng hình khối hoặc hình bình hành: chiều thứ nhất xác định chiều dài, chiều thứ hai xác định chiều rộng, chiều thứ ba xác định chiều cao và giá trị của phần tử xác định tham số của một điểm đã cho trong không gian.

Số lượng kích thước mảng tối đa được phép trong MQL5 là 4.

Một mảng nhiều chiều có thể là tĩnh hoặc động chỉ trong chiều thứ nhất, với tất cả các kích thước khác là tĩnh. Do đó, hàm ArrayResize() cho phép bạn chỉ thay đổi kích thước của kích thước đầu tiên. Kích thước của các chiều khác phải được chỉ định khi khai báo một mảng:

double Variable[][3][3]; 

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

Sau khi thực thi đoạn mã trên, biến Kích thước sẽ bằng 27. Hãy nhớ đặc thù này khi lặp qua các mảng nhiều chiều trong một vòng lặp nếu bạn cần lấy kích thước của chiều thứ nhất:

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

Như đã đề cập trước đó, nên tuân thủ nguyên tắc giảm số lượng mã cần phải sửa đổi nếu cần cải thiện thêm chương trình. Trong ví dụ mã trên, chúng tôi đã sử dụng số 9, tuy nhiên cũng có thể được tính toán. Với mục đích này, chúng ta có thể sử dụng hàm ArrayRange() trả về số lượng phần tử có trong kích thước đã chỉ định của mảng. Nếu số lượng kích thước mảng được biết đến, chúng ta có thể thực hiện một phép tính đơn giản:

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

Chúng ta có thể làm cho nó phổ quát hơn:

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
}

Tại thời điểm này, bạn có thể cảm thấy như sẽ tốt khi tạo một hàm cho phép tính như vậy. Thật không may, điều này là không thể vì một mảng ngẫu nhiên không thể được truyền cho một hàm. Khi khai báo một đối số hàm, bạn cần xác định rõ số lượng phần tử trong tất cả các kích thước mảng ngoại trừ kích thước đầu tiên, do đó làm cho hàm đó trở nên vô nghĩa. Những tính toán này dễ dàng hơn và được thực hiện tốt hơn trong việc khởi tạo chương trình. Khi khai báo một mảng, nên sử dụng các hằng số xác định kích thước của kích thước:

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

Khởi tạo các mảng nhiều chiều bằng cách sử dụng danh sách các giá trị tương tự như khởi tạo các mảng một chiều. Nhưng vì một mảng nhiều chiều là một loại bao gồm một số mảng khác, mỗi mảng này phải được phân tách bằng dấu ngoặc nhọn.

Giả sử, chúng ta có một mảng như sau:

double Variable[3][3];

Mảng này bao gồm ba mảng gồm ba phần tử mỗi phần:

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

Một mảng ba chiều được xử lý theo cách tương tự. Mã có thể được chia thành nhiều dòng để tạo điều kiện hiểu cấu trúc mảng:

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

Việc khởi tạo một mảng nhiều chiều bằng cách sử dụng hàm ArrayInitialize() được thực hiện giống như cách khởi tạo mảng một chiều:

ArrayInitialize(Variable,1);

Sau khi thực thi mã, tất cả các phần tử mảng sẽ có giá trị là 1. Điều tương tự cũng đúng với hàm ArrayFill():

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

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

Sau khi thực thi mã này, tất cả các phần tử được liên kết với phần tử đầu tiên của thứ nguyên thứ nhất sẽ có giá trị là 1, các phần tử được liên kết với phần tử thứ hai sẽ có giá trị là 10 và các phần tử được liên kết với phần tử thứ ba – 100.

Truyền một mảng cho hàm

Không giống như các biến, mảng có thể được truyền cho một hàm chỉ bằng tham chiếu. Điều này có nghĩa là hàm không tạo ra thể hiện riêng của mảng và thay vào đó hoạt động trực tiếp với mảng được truyền cho nó. Vì vậy, tất cả các thay đổi chức năng làm cho mảng ảnh hưởng đến mảng ban đầu.

Nếu một biến được truyền cho hàm theo cách thông thường (theo giá trị), giá trị của biến được truyền có thể được thay đổi bởi hàm:

int x=1;
Func(x);

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

Sau khi thực hiện hàm Func(), giá trị x vẫn bằng 1.

Nếu một biến được truyền bằng tham chiếu (ký hiệu là &), hàm có thể thay đổi giá trị của biến đó được truyền cho nó:

int x=1;
Func(x);

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

Sau khi thực hiện hàm Func(), giá trị x trở thành bằng 2.

Khi truyền một mảng cho một hàm, bạn cần xác định rằng đối số được truyền bằng tham chiếu và biểu thị một mảng (trong ngoặc):

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

Khi chuyển các mảng nhiều chiều cho một hàm, kích thước kích thước (ngoại trừ kích thước đầu tiên) phải được chỉ định:

double var[][3][3];

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

Trong trường hợp này, nên sử dụng hằng số:

#define SIZE1 3
#define SIZE2 3

double Var[][SIZE1][SIZE2];

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

Lưu và tải Mảng từ một tệp

Khi lưu và tải một mảng từ một tệp, bạn phải luôn luôn xem xét sự khác biệt về giá trị kích thước của kích thước đầu tiên của mảng và tổng số phần tử mảng. Để lưu một mảng, trước tiên chúng ta viết kích thước mảng (tổng số phần tử được xác định bởi hàm ArraySize() ) và sau đó toàn bộ mảng vào tệp:

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
  }

Kết quả là, chúng ta có được một hàm khá phổ quát để lưu các mảng một chiều.

Để tải một mảng từ một tệp, trước tiên chúng ta cần đọc kích thước mảng, thay đổi kích thước của nó và cuối cùng là đọc mảng:

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
  }

Khi tải một mảng nhiều chiều từ một tệp, bạn sẽ cần tính kích thước của kích thước đầu tiên. Ví dụ. Giả sử, chúng ta đang đọc một mảng ba chiều:

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
  }

Cũng có thể là tệp chứa mảng 2 nhân 3 trong khi chúng tôi cố gắng đọc nó dưới dạng mảng 3 nhân 3. Bạn có thể kiểm tra sự tương ứng giữa các kích thước bằng cách nhân kích thước tính toán của kích thước đầu tiên với số lượng phần tử. Nếu giá trị kết quả bằng tổng số phần tử mảng, chúng ta có thể nói về sự tương ứng.

Tuy nhiên, mảng Var [2] [3] sẽ tương ứng với mảng Var [3] [2]. Nếu bạn cũng cần bao gồm trường hợp này, bạn nên lưu thêm thông tin về một mảng nhiều chiều. Ví dụ. trước tiên bạn có thể lưu số lượng phần tử mảng, sau đó là số lượng kích thước mảng theo sau là kích thước của từng kích thước và chính mảng đó.

Hàm cuối cùng được cung cấp ở trên không phải là phổ quát và được thiết kế để chỉ đọc các mảng ba chiều trong đó kích thước của chiều thứ hai bằng SIZE1 và kích thước của chiều thứ ba bằng SIZE2. Vì không có cách nào để thay đổi linh hoạt kích thước của tất cả các kích thước mảng, ngoại trừ kích thước đầu tiên, đây không phải là vấn đề – chúng tôi sẽ tạo các hàm cho các mảng cần được sử dụng trong chương trình.

Tính phổ biến trong trường hợp này là không cần thiết: kích thước của kích thước mảng (ngoại trừ kích thước đầu tiên) sẽ không được kiểm soát thông qua các tham số bên ngoài của chương trình. Tuy nhiên, nếu bạn cần triển khai khả năng kiểm soát kích thước của các kích thước khác, bạn có thể giải quyết tác vụ này bằng cách sử dụng một mảng đa chiều có kích thước lớn hơn và các biến bổ sung hoặc bằng cách áp dụng các kỹ thuật lập trình hướng đối tượng (OOP). Chúng ta sẽ nói nhiều hơn về cách tiếp cận thứ hai sau trong bài viết này.

Sử dụng mảng động

Mảng động được sử dụng khi bạn không biết trước kích thước mảng. Nếu kích thước mảng phụ thuộc vào các tham số được đặt trong cửa sổ thuộc tính chương trình, sử dụng mảng động sẽ không thành vấn đề: kích thước mảng sẽ chỉ được thay đổi một lần trong quá trình khởi tạo chương trình.

Một mảng có thể được sử dụng để thu thập động một số thông tin nhất định, ví dụ: Liên quan đến các đơn đặt hàng đang chờ xử lý. Số lượng của chúng có thể khác nhau, tức là kích thước yêu cầu không được biết trước. Trong trường hợp này, cách dễ nhất là thay đổi kích thước mảng thành 0 trước khi chuyển qua các đơn đặt hàng và tăng kích thước mảng theo một yếu tố khi chúng tôi chuyển qua từng đơn hàng. Điều này sẽ làm việc, nhưng rất chậm.

Người ta có thể thay đổi kích thước mảng chỉ một lần theo số lượng đơn đặt hàng, trước khi chuyển qua đơn đặt hàng. Điều này sẽ yêu cầu một biến khác cho chỉ mục của phần tử hoạt động cuối cùng của mảng (hoặc một số phần tử mảng thực sự hoạt động, thay vì chỉ mục). Phương pháp này phù hợp nếu bạn đã biết kích thước mảng tối đa. Nếu kích thước mảng tối đa không được biết đến, chúng ta có thể tăng tốc độ làm việc với nó bằng cách thay đổi kích thước mảng bằng cách sử dụng các đoạn, như trong lớp sau:

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

ạn có thể tìm thấy lớp này trong tệp CDocateArray.mqh đính kèm. Tệp phải được đặt trong thư mục MQL5 \\ Bao gồm của thư mục dữ liệu đầu cuối.

Bây giờ chúng ta hãy đánh giá và so sánh hiệu suất của mã trong cả hai tình huống: trong đó kích thước mảng được tăng liên tục 1 và nơi nó được tăng lên bằng cách sử dụng các đoạn:

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

  }

Thử nghiệm này ở dạng tập lệnh có thể được tìm thấy trong tệp sTest_Speed.mq5 đính kèm. Tệp phải được đặt trong thư mục MQL5\Scripts của thư mục dữ liệu đầu cuối.

Hiệu suất của tùy chọn đầu tiên mất vài giây, trong khi tùy chọn thứ hai gần như ngay lập tức.

Thứ tự chỉ mục mảng

Thông thường, khi thay đổi kích thước một mảng, các phần tử mới được thêm vào cuối mảng:

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

Sau khi thực thi mã này, các giá trị trong mảng phải là 1, 2 và 3.

Các phần tử trong mảng cũng có thể được lập chỉ mục theo thứ tự ngược lại. Thứ tự lập chỉ mục được thiết lập bởi hàm ArraySetAsSeries():

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

Khi thay đổi kích thước của mảng được lập chỉ mục theo thứ tự ngược lại, một phần tử mới được thêm vào đầu mảng:

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

Sau khi thực thi mã này, các giá trị trong mảng phải là 3, 2 và 1.

Nó chỉ ra rằng trong cả hai trường hợp, phần tử mới được thêm vào cùng một phía của mảng, sự khác biệt duy nhất là thứ tự lập chỉ mục. Hàm này không thể được sử dụng để thêm các phần tử vào đầu mảng có các phần tử được lập chỉ mục theo thứ tự thông thường. Để thêm một phần tử vào cuối mảng được lập chỉ mục thông thường, bạn chỉ cần tăng kích thước mảng và gán giá trị cho phần tử cuối cùng.

Để thêm một phần tử vào đầu mảng, bạn nên tăng kích thước mảng, di chuyển tất cả các giá trị và gán giá trị mới cho phần tử zero. Trong các mảng được lập chỉ mục theo thứ tự ngược lại, một phần tử mới có thể dễ dàng được thêm vào đầu mảng. Nhưng nếu bạn cần thêm một phần tử mới vào cuối mảng, trước tiên bạn nên tăng kích thước mảng và sau khi di chuyển tất cả các giá trị về đầu, gán một giá trị mới cho phần tử cuối cùng. Thao tác lập chỉ mục sẽ không giải quyết được vấn đề này.

Thứ tự lập chỉ mục mảng có thể được xác định bằng hàm ArrayIsSeries():

bool series=ArrayIsSeries(ar);

Nếu mảng được lập chỉ mục theo thứ tự ngược lại, hàm sẽ trả về true.

Mảng được lập chỉ mục theo thứ tự ngược chủ yếu được sử dụng trong Expert Advisors. Trong việc phát triển EA, việc đếm các thanh từ phải sang trái thường thuận tiện hơn và do đó sao chép dữ liệu giá và bộ đệm chỉ báo vào các mảng có lập chỉ mục ngược.

Sao chép mảng

Cách dễ nhất để sao chép là lặp qua một mảng trong một vòng lặp và sao chép phần tử theo phần tử từ mảng này sang mảng khác. Tuy nhiên, có một chức năng đặc biệt trong MQL5 cho phép chúng ta sao chép mảng – ArrayCopy():

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

ArrayCopy(ar2,ar1);

Sau khi thực thi đoạn mã trên, mảng ar2 sẽ bao gồm ba phần tử có cùng giá trị như trong mảng ar1: 1, 2, 3.

Nếu số lượng phần tử được sao chép không phù hợp với mảng mà bạn đang sao chép, kích thước mảng sẽ tự động được tăng lên (mảng phải là động). Nếu mảng lớn hơn số phần tử được sao chép, kích thước của nó sẽ giữ nguyên.

Hàm ArrayCopy() cũng cho phép bạn chỉ sao chép một phần của mảng. Sử dụng các tham số tùy chọn của hàm, bạn có thể chỉ định phần tử đầu tiên được sao chép, chỉ mục của phần tử được sao chép đầu tiên trong mảng mới và số phần tử bạn sẽ sao chép.

Ngoài việc sao chép các phần tử của một mảng sang mảng khác, hàm ArrayCopy() có thể được sử dụng để sao chép các phần tử trong cùng một mảng:

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

Chúng ta sao chép dữ liệu bắt đầu bằng phần tử có chỉ mục 2 và dán chúng bắt đầu bằng chỉ mục 1. Sau khi thực thi mã này, mảng sẽ chứa các giá trị sau: 1, 3, 4, 5, 5.

Hàm ArrayCopy() cũng cho phép bạn dịch chuyển dữ liệu sang phải:

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

Chúng tôi lấy dữ liệu bắt đầu bằng phần tử có chỉ số 1 và sắp xếp chúng bắt đầu bằng chỉ mục 2. Sau khi thực thi mã này, mảng sẽ chứa các giá trị sau: 1, 2, 2, 3, 4.

Hàm ArrayCopy() cũng có thể được áp dụng cho các mảng nhiều chiều, theo đó nó hoạt động như thể mảng là một chiều và tất cả các phần tử của nó được sắp xếp theo chuỗi:

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

Sau khi thực thi mã này, mảng sẽ xuất hiện như sau: {1, 2}, {5, 6}, {5, 6}.

Sắp xếp mảng

Một mảng có thể được sắp xếp bằng hàm ArraySort():

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

Sau khi bạn thực thi đoạn mã trên, các giá trị mảng sẽ được sắp xếp theo thứ tự sau: 1, 2, 3, 4, 5.

Hàm ArraySort() không thể được áp dụng cho các mảng nhiều chiều. Bạn có thể tìm thấy thông tin về cách sắp xếp các mảng và cấu trúc dữ liệu đa chiều trong bài viết có tên ‘Bảng điện tử trong MQL5‘.

Tìm kiếm nhị phân

Để thực hiện tìm kiếm nhị phân, chúng tôi sử dụng hàm ArrayBsearch(). Hàm này có thể hoạt động đúng chỉ với các mảng được sắp xếp. Tìm kiếm nhị phân có tên của nó từ thực tế là thuật toán liên tục chia một mảng thành hai phần. Thuật toán trước tiên so sánh giá trị đích với giá trị của phần tử giữa của mảng, do đó xác định một nửa có chứa phần tử đích – mảng con ở bên trái hoặc mảng con ở bên phải. Sau đó, nó so sánh giá trị đích với giá trị của phần tử giữa của các mảng con, v.v.

Hàm ArrayBsearch() trả về chỉ mục của phần tử với giá trị đích:

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

int index=ArrayBsearch(ar,3);

Sau khi thực hiện mã này, biến chỉ mục sẽ có giá trị là 2.

Nếu không thể tìm thấy giá trị đích trong mảng, hàm sẽ trả về chỉ mục của phần tử có giá trị nhỏ hơn gần nhất. Do tính chất này, chức năng có thể được sử dụng để tìm kiếm các thanh theo thời gian. Nếu không có thanh với thời gian được chỉ định, một thanh có thời gian ít hơn nên được sử dụng trong tính toán.

Nếu giá trị đích không nằm trong mảng và nằm ngoài phạm vi của các giá trị mảng, hàm sẽ trả về 0 (nếu giá trị đích nhỏ hơn giá trị tối thiểu) hoặc chỉ mục cuối cùng (nếu giá trị đích lớn hơn giá trị tối đa ).

Chỉ có một phương thức cho phép bạn thực hiện tìm kiếm trong một mảng chưa được sắp xếp – lặp lại trên một mảng:

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

Trong ví dụ trên, hàm trả về chỉ mục của phần tử với giá trị đích. Nếu giá trị đích không nằm trong mảng, hàm trả về -1.

Tìm giá trị lớn nhất và nhỏ nhất

Các giá trị lớn nhất và nhỏ nhất trong mảng có thể được tìm thấy bằng cách sử dụng các hàm ArrayMaximum() và ArrayMinimum() trả về chỉ mục của phần tử với giá trị lớn nhất và nhỏ nhất:

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

Sau khi thực thi mã này, biến Max Index sẽ bằng 6, biến Min Index sẽ bằng 2, MaxValue sẽ có giá trị là 5 và MinValue sẽ là 1.

Các hàm ArrayMaximum() và ArrayMinimum() cho phép bạn giới hạn phạm vi tìm kiếm bằng cách chỉ định chỉ mục của phần tử đầu tiên trong phạm vi tìm kiếm và số lượng phần tử trong phạm vi tìm kiếm:

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

Trong trường hợp này, Max Index sẽ có giá trị là 6 và Min Index sẽ là – 5. Xin lưu ý rằng phạm vi được chỉ định chứa hai vị trí có giá trị tối thiểu là 4 – vị trí 5 và vị trí 7. Trong tình huống như thế này, hàm sẽ trả về Chỉ số của phần tử gần với phần đầu của mảng hơn. Các hàm này hoạt động theo cùng một cách với các mảng được lập chỉ mục theo thứ tự ngược lại – chúng trả về chỉ số nhỏ nhất.

Vì vậy, chúng tôi đã xem xét tất cả các chức năng tiêu chuẩn có sẵn trong MQL5 để làm việc với mảng.

Tạo các mảng đa chiều bằng OOP

Một tập hợp các lớp để tạo các mảng đa chiều bao gồm ba lớp: một lớp cơ sở và hai lớp con. Tùy thuộc vào lớp con được chọn ở giai đoạn tạo đối tượng, đối tượng có thể biểu diễn một mảng các biến kép hoặc một mảng các đối tượng. Mỗi phần tử của mảng đối tượng có thể đại diện cho một mảng đối tượng hoặc biến khác.

Lớp cơ sở và lớp con không chứa hầu như bất kỳ hàm nào, ngoại trừ hàm hủy trong lớp cơ sở và hàm tạo trong lớp con. Destructor trong lớp cơ sở phục vụ để xóa tất cả các đối tượng sau khi hoàn thành chương trình hoặc chức năng. Các hàm tạo trong các lớp con chỉ được sử dụng để chia tỷ lệ các mảng theo kích thước được chỉ định trong các tham số của hàm tạo: để chia tỷ lệ một mảng các đối tượng trong một lớp và một mảng các biến trong lớp khác. Dưới đây là mã để thực hiện các lớp này:

//+------------------------------------------------------------------+
//|   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);
     }
  };

Bạn có thể tìm thấy các lớp này trong tệp CMultiDimArray.mqh đính kèm. Tệp phải được đặt trong thư mục MQL5\Include của thư mục dữ liệu đầu cuối.

Bây giờ chúng ta sử dụng lớp này để xây dựng một mảng một chiều tương tự:

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
  }

Ví dụ này ở dạng tập lệnh có thể được tìm thấy trong tệp sTest_1_Arr.mq5 đính kèm. Tệp phải được đặt trong thư mục MQL5\Scripts của thư mục dữ liệu đầu cuối.

Bây giờ, chúng ta hãy thử tạo một mảng hai chiều. Mỗi yếu tố của chiều thứ nhất sẽ chứa một số yếu tố khác nhau của chiều thứ hai – một ở chiều thứ nhất, hai ở chiều thứ hai, v.v.:

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

Ví dụ này ở dạng tập lệnh có thể được tìm thấy trong tệp sTest_2_Dim.mq5 đính kèm. Tệp phải được đặt trong thư mục MQL5\Scripts của thư mục dữ liệu đầu cuối.

Các mảng kết quả là loại tĩnh vì các lớp không có phương thức để thay đổi kích thước mảng. Nhưng vì mảng D [] và V [] nằm trong phần chung của lớp, nên chúng có sẵn cho mọi thao tác. Và bạn có thể không gặp bất kỳ khó khăn nào khi chia tỷ lệ mảng V []. Khi chia tỷ lệ mảng D [] và giảm kích thước của chúng, trước tiên bạn nên xóa các đối tượng được chỉ ra bởi các đối tượng cần xóa hoặc tải các đối tượng vào chúng khi tăng kích thước mảng.

Người ta cũng có thể nghĩ ra các cách khác để thực hiện các mảng đa chiều bằng OOP hoặc cấu trúc dữ liệu, nếu muốn.

Kết luận

Bài viết đã đề cập đến tất cả các chức năng tiêu chuẩn có sẵn trong MQL5 để làm việc với các mảng. Chúng tôi đã xem xét các đặc thù và một số kỹ thuật quan trọng nhất để xử lý mảng. Ngôn ngữ MQL5 cung cấp tổng cộng 15 chức năng, một số trong số đó có tầm quan trọng tối đa, trong khi các chức năng khác có thể vẫn hoàn toàn không được sử dụng, trừ trường hợp bạn cần giải quyết một vấn đề độc đáo. Các chức năng có thể được sắp xếp theo mức độ quan trọng và tần suất sử dụng như sau:

  1. ArraySize() và ArrayResize() là các hàm thiết yếu.
  2. ArrayMaximum(), ArrayMinimum(), ArrayCopy(), ArrayInitialize(), ArrayFill() và ArrayFree() là các hàm giúp làm việc với mảng dễ dàng hơn đáng kể.
  3. ArraySort() là một hàm quan trọng và tiện dụng, tuy nhiên hiếm khi được sử dụng do chức năng thấp của nó.
  4. ArrayBsearch() là một hàm hiếm khi được sử dụng, nhưng nó có thể rất quan trọng trong các trường hợp ngoại lệ hiếm gặp.
  5. ArraySetAsSeries(), ArrayRange(), ArrayGetAsSeries(), ArrayIsDocate() và ArrayIsSeries() là các hàm được sử dụng rất hiếm hoặc gần như không bao giờ được sử dụng.

Việc sử dụng các mảng động, là một trong những kỹ thuật lập trình được mô tả trong bài viết này, cần được chú ý đặc biệt vì nó có ảnh hưởng lớn và có thể nói là để xác định hiệu suất của chương trình.

Tải về mã nguồn trong bài viết này:

HotForex tặng thưởng 100% và miễn phí nạp rút tiền

BÌNH LUẬN

Vui lòng nhập bình luận của bạn
Vui lòng nhập tên của bạn ở đây

Website này sử dụng Akismet để hạn chế spam. Tìm hiểu bình luận của bạn được duyệt như thế nào.