Sao chép và chỉnh sửa mảng
Trong phần này, chúng ta sẽ tìm hiểu cách sử dụng các hàm tích hợp để chèn và xóa các phần tử mảng, thay đổi thứ tự của chúng và sao chép toàn bộ mảng.
bool ArrayInsert(void &target[], const void &source[], uint to, uint from = 0, uint count = WHOLE_ARRAY)
Hàm này chèn số lượng phần tử được chỉ định từ mảng nguồn source
vào mảng đích target
. Vị trí chèn vào mảng target
được xác định bởi chỉ số trong tham số to
. Chỉ số bắt đầu của phần tử mà từ đó bắt đầu sao chép từ mảng source
được cung cấp bởi chỉ số from
. Hằng số WHOLE_ARRAY
((uint)-1) trong tham số count
chỉ định việc chuyển tất cả các phần tử của mảng nguồn.
Tất cả các chỉ số và số lượng đều liên quan đến chiều đầu tiên của mảng. Nói cách khác, đối với mảng đa chiều, việc chèn không được thực hiện từng phần tử riêng lẻ mà bằng toàn bộ cấu hình được mô tả bởi các chiều "cao hơn". Ví dụ, đối với mảng hai chiều, giá trị 1 trong tham số count
có nghĩa là chèn một vector có độ dài bằng chiều thứ hai (xem ví dụ).
Do đó, mảng đích và mảng nguồn phải có cùng cấu hình. Nếu không, sẽ xảy ra lỗi và việc sao chép sẽ thất bại. Đối với mảng một chiều, đây không phải là hạn chế, nhưng đối với mảng đa chiều, cần tuân thủ sự bằng nhau về kích thước ở các chiều trên chiều đầu tiên. Đặc biệt, các phần tử từ mảng [][4] không thể được chèn vào mảng [][5] và ngược lại.
Hàm này chỉ áp dụng cho các mảng có kích thước cố định hoặc động. Việc chỉnh sửa chuỗi thời gian không thể thực hiện bằng hàm này. Cấm chỉ định cùng một mảng trong các tham số target
và source
.
Khi chèn vào mảng cố định, các phần tử mới đẩy các phần tử hiện có sang phải và thay thế count
phần tử ngoài cùng bên phải ra khỏi mảng. Tham số to
phải có giá trị từ 0 đến kích thước của mảng trừ 1.
Khi chèn vào mảng động, các phần tử cũ cũng được đẩy sang phải, nhưng chúng không biến mất, vì bản thân mảng mở rộng thêm count
phần tử. Tham số to
phải có giá trị từ 0 đến kích thước của mảng. Nếu nó bằng kích thước của mảng, các phần tử mới được thêm vào cuối mảng.
Các phần tử được chỉ định được sao chép từ mảng này sang mảng khác, tức là chúng không thay đổi trong mảng gốc, và "bản sao" của chúng trong mảng mới trở thành các thực thể độc lập không liên quan đến "bản gốc" theo bất kỳ cách nào.
Hàm trả về true
nếu thành công hoặc false
nếu xảy ra lỗi.
Hãy xem xét một số ví dụ (ArrayInsert.mq5
). Hàm OnStart
cung cấp mô tả về một số mảng với các cấu hình khác nhau, cả cố định và động.
#define PRTS(A) Print(#A, "=", (string)(A) + " / status:" + (string)GetLastError())
void OnStart()
{
int dynamic[];
int dynamic2Dx5[][5];
int dynamic2Dx4[][4];
int fixed[][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}};
int insert[] = {10, 11, 12};
int array[1] = {100};
...
}
2
3
4
5
6
7
8
9
10
11
12
Để bắt đầu, vì sự tiện lợi, một macro được giới thiệu để hiển thị mã lỗi (thu được qua hàm GetLastError) ngay sau khi gọi lệnh đang được kiểm tra — PRTS
. Đây là phiên bản hơi sửa đổi của macro PRT
quen thuộc.
Những nỗ lực sao chép các phần tử giữa các mảng có cấu hình khác nhau kết thúc bằng lỗi 4006 (ERR_INVALID_ARRAY
).
// bạn không thể trộn mảng 1D và 2D
PRTS(ArrayInsert(dynamic, fixed, 0)); // false:4006, ERR_INVALID_ARRAY
ArrayPrint(dynamic); // trống
// bạn không thể trộn các mảng 2D có cấu hình khác nhau ở chiều thứ hai
PRTS(ArrayInsert(dynamic2Dx5, fixed, 0)); // false:4006, ERR_INVALID_ARRAY
ArrayPrint(dynamic2Dx5); // trống
// ngay cả khi cả hai mảng đều cố định (hoặc đều động),
// kích thước theo các chiều "cao hơn" phải khớp
PRTS(ArrayInsert(fixed, insert, 0)); // false:4006, ERR_INVALID_ARRAY
ArrayPrint(fixed); // không thay đổi
...
2
3
4
5
6
7
8
9
10
11
12
13
Chỉ số đích phải nằm trong phạm vi của mảng.
// chỉ số đích 10 nằm ngoài phạm vi của mảng 'insert',
// có thể là 0, 1, 2, vì kích thước của nó = 3
PRTS(ArrayInsert(insert, array, 10)); // false:5052, ERR_SMALL_ARRAY
ArrayPrint(insert); // không thay đổi
...
2
3
4
5
Dưới đây là các sửa đổi mảng thành công:
// sao chép hàng thứ hai từ 'fixed', 'dynamic2Dx4' được cấp phát
PRTS(ArrayInsert(dynamic2Dx4, fixed, 0, 1, 1)); // true
ArrayPrint(dynamic2Dx4);
// cả hai hàng từ 'fixed' được thêm vào cuối 'dynamic2Dx4', nó mở rộng
PRTS(ArrayInsert(dynamic2Dx4, fixed, 1)); // true
ArrayPrint(dynamic2Dx4);
// bộ nhớ được cấp phát cho 'dynamic' cho tất cả các phần tử 'insert'
PRTS(ArrayInsert(dynamic, insert, 0)); // true
ArrayPrint(dynamic);
// 'dynamic' mở rộng thêm 1 phần tử
PRTS(ArrayInsert(dynamic, array, 1)); // true
ArrayPrint(dynamic);
// phần tử mới sẽ đẩy phần tử cuối cùng ra khỏi 'insert'
PRTS(ArrayInsert(insert, array, 1)); // true
ArrayPrint(insert);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Đây là những gì sẽ xuất hiện trong nhật ký:
ArrayInsert(dynamic2Dx4,fixed,0,1,1)=true
[,0][,1][,2][,3]
[0,] 5 6 7 8
ArrayInsert(dynamic2Dx4,fixed,1)=true
[,0][,1][,2][,3]
[0,] 5 6 7 8
[1,] 1 2 3 4
[2,] 5 6 7 8
ArrayInsert(dynamic,insert,0)=true
10 11 12
ArrayInsert(dynamic,array,1)=true
10 100 11 12
ArrayInsert(insert,array,1)=true
10 100 11
2
3
4
5
6
7
8
9
10
11
12
13
14
bool ArrayCopy(void &target[], const void &source[], int to = 0, int from = 0, int count = WHOLE_ARRAY)
Hàm này sao chép một phần hoặc toàn bộ mảng source
sang mảng target
. Vị trí trong mảng target
nơi các phần tử được ghi được chỉ định bởi chỉ số trong tham số to
. Chỉ số bắt đầu của phần tử mà từ đó bắt đầu sao chép từ mảng source
được cung cấp bởi chỉ số from
. Hằng số WHOLE_ARRAY
(-1) trong tham số count
chỉ định việc chuyển tất cả các phần tử của mảng nguồn. Nếu count
nhỏ hơn 0 hoặc lớn hơn số phần tử còn lại từ vị trí from
đến cuối mảng source
, toàn bộ phần còn lại của mảng sẽ được sao chép.
Không giống như hàm ArrayInsert
, hàm ArrayCopy
không dịch chuyển các phần tử hiện có của mảng đích mà ghi các phần tử mới vào các vị trí được chỉ định, ghi đè lên các phần tử cũ.
Tất cả các chỉ số và số lượng phần tử được đặt dựa trên việc đánh số liên tục của các phần tử, bất kể số chiều của mảng và cấu hình của chúng. Nói cách khác, các phần tử có thể được sao chép từ mảng đa chiều sang mảng một chiều và ngược lại, hoặc giữa các mảng đa chiều với kích thước khác nhau theo các chiều "cao hơn" (xem ví dụ).
Hàm hoạt động với mảng cố định và động, cũng như các mảng chuỗi thời gian được chỉ định làm bộ đệm chỉ báo.
Cho phép sao chép các phần tử từ một mảng vào chính nó. Nhưng nếu các khu vực target
và source
chồng lấn, cần lưu ý rằng quá trình lặp được thực hiện từ trái sang phải.
Mảng đích động được tự động mở rộng khi cần thiết. Mảng cố định giữ nguyên kích thước của chúng, và những gì được sao chép phải phù hợp với mảng, nếu không sẽ xảy ra lỗi.
Hỗ trợ các mảng của các kiểu tích hợp và mảng của cấu trúc với các trường kiểu đơn giản. Đối với các kiểu số, hàm sẽ cố gắng chuyển đổi dữ liệu nếu kiểu nguồn và đích khác nhau. Mảng chuỗi chỉ có thể được sao chép sang mảng chuỗi. Các đối tượng lớp không được phép, nhưng con trỏ đến các đối tượng có thể được sao chép.
Hàm trả về số lượng phần tử được sao chép (0 nếu có lỗi).
Trong tập lệnh ArrayCopy.mq5
có một số ví dụ về cách sử dụng hàm.
class Dummy
{
int x;
};
void OnStart()
{
Dummy objects1[5], objects2[5];
// lỗi: cấu trúc hoặc lớp chứa đối tượng không được phép
PRTS(ArrayCopy(objects1, objects2));
...
}
2
3
4
5
6
7
8
9
10
11
12
Mảng chứa đối tượng tạo ra lỗi biên dịch với thông báo "cấu trúc hoặc lớp chứa đối tượng không được phép", nhưng con trỏ có thể được sao chép.
Dummy *pointers1[5], *pointers2[5];
for(int i = 0; i < 5; ++i)
{
pointers1[i] = &objects1[i];
}
PRTS(ArrayCopy(pointers2, pointers1)); // 5 / status:0
for(int i = 0; i < 5; ++i)
{
Print(i, " ", pointers1[i], " ", pointers2[i]);
}
// nó xuất ra một số mô tả đối tượng giống nhau theo cặp
/*
0 1048576 1048576
1 2097152 2097152
2 3145728 3145728
3 4194304 4194304
4 5242880 5242880
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Mảng của cấu trúc với các trường kiểu đơn giản cũng được sao chép mà không gặp vấn đề.
struct Simple
{
int x;
};
void OnStart()
{
Simple s1[3];
...
}
2
3
4
5
6
7
8
9
10
Mảng của các kiểu số khác nhau được sao chép với sự chuyển đổi cần thiết.
PRTS(ArrayCopy(insert, array, 1)); // 1 / status:0
ArrayPrint(insert); // 10 3 12
2
Ở đây, chúng ta đã ghi số Pi vào một mảng số nguyên, và do đó nhận được giá trị 3 (nó thay thế 11).
bool ArrayRemove(void &array[], uint start, uint count = WHOLE_ARRAY)
Hàm này xóa số lượng phần tử được chỉ định khỏi mảng bắt đầu từ chỉ số start
. Mảng có thể là đa chiều và có bất kỳ kiểu tích hợp hoặc cấu trúc nào với các trường kiểu tích hợp, ngoại trừ chuỗi.
Chỉ số start
và số lượng count
đề cập đến chiều đầu tiên của mảng. Nói cách khác, đối với mảng đa chiều, việc xóa không được thực hiện từng phần tử riêng lẻ mà bằng toàn bộ cấu hình được mô tả bởi các chiều "cao hơn". Ví dụ, đối với mảng hai chiều, giá trị 1 trong tham số count
có nghĩa là xóa toàn bộ một chuỗi có độ dài bằng chiều thứ hai (xem ví dụ).
Giá trị start
phải nằm trong khoảng từ 0 đến kích thước của chiều đầu tiên trừ 1.
Hàm không thể áp dụng cho mảng có chuỗi thời gian hoặc bộ đệm chỉ báo.
Để kiểm tra hàm, chúng ta đã chuẩn bị tập lệnh ArrayRemove.mq5
. Đặc biệt, nó định nghĩa 2 cấu trúc:
struct Simple
{
int x;
};
struct NotSoSimple
{
int x;
string s; // trường kiểu chuỗi khiến trình biên dịch tạo ra một hàm hủy ẩn
};
2
3
4
5
6
7
8
9
10
Mảng với cấu trúc đơn giản có thể được xử lý thành công bởi hàm ArrayRemove
, trong khi mảng của các đối tượng có hàm hủy (ngay cả hàm hủy ẩn, như trong NotSoSimple
) gây ra lỗi:
void OnStart()
{
Simple structs1[10];
PRTS(ArrayRemove(structs1, 0, 5)); // true / status:0
NotSoSimple structs2[10];
PRTS(ArrayRemove(structs2, 0, 5)); // false / status:4005, ERR_STRUCT_WITHOBJECTS_ORCLASS
...
}
2
3
4
5
6
7
8
9
Tiếp theo, các mảng với nhiều cấu hình khác nhau được định nghĩa và khởi tạo.
int dynamic[];
int dynamic2Dx4[][4];
int fixed[][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}};
// tạo 2 bản sao
ArrayCopy(dynamic, fixed);
ArrayCopy(dynamic2Dx4, fixed);
// hiển thị dữ liệu ban đầu
ArrayPrint(dynamic);
/*
1 2 3 4 5 6 7 8
*/
ArrayPrint(dynamic2Dx4);
/*
[,0][,1][,2][,3]
[0,] 1 2 3 4
[1,] 5 6 7 8
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Khi xóa khỏi mảng cố định, tất cả các phần tử sau đoạn bị xóa được dịch chuyển sang trái. Điều quan trọng là kích thước của mảng không thay đổi, và do đó các bản sao của các phần tử được dịch chuyển xuất hiện trùng lặp.
PRTS(ArrayRemove(fixed, 0, 1));
ArrayPrint(fixed);
/*
ArrayRemove(fixed,0,1)=true / status:0
[,0][,1][,2][,3]
[0,] 5 6 7 8
[1,] 5 6 7 8
*/
2
3
4
5
6
7
8
Ở đây, chúng ta đã xóa một phần tử của chiều đầu tiên của mảng hai chiều fixed
tại vị trí 0, tức là hàng ban đầu. Các phần tử của hàng tiếp theo di chuyển lên và vẫn ở trong cùng một hàng.
Nếu chúng ta thực hiện thao tác tương tự với mảng động (có nội dung giống với mảng fixed
), kích thước của nó sẽ tự động giảm đi số lượng phần tử bị xóa.
PRTS(ArrayRemove(dynamic2Dx4, 0, 1));
ArrayPrint(dynamic2Dx4);
/*
ArrayRemove(dynamic2Dx4,0,1)=true / status:0
[,0][,1][,2][,3]
[0,] 5 6 7 8
*/
2
3
4
5
6
7
Trong mảng một chiều, mỗi phần tử bị xóa tương ứng với một giá trị duy nhất. Ví dụ, trong mảng dynamic
, khi xóa ba phần tử bắt đầu từ chỉ số 2, chúng ta nhận được kết quả sau:
PRTS(ArrayRemove(dynamic, 2, 3));
ArrayPrint(dynamic);
/*
ArrayRemove(dynamic,2,3)=true / status:0
1 2 6 7 8
*/
2
3
4
5
6
Các giá trị 3, 4, 5 đã bị xóa, kích thước mảng giảm đi 3.
bool ArrayReverse(void &array[], uint start = 0, uint count = WHOLE_ARRAY)
Hàm này đảo ngược thứ tự của các phần tử được chỉ định trong mảng. Các phần tử cần đảo ngược được xác định bởi vị trí bắt đầu start
và số lượng count
. Nếu start = 0
và count = WHOLE_ARRAY
, toàn bộ mảng sẽ được xử lý.
Hỗ trợ mảng với chiều tùy ý và các kiểu, cả cố định và động (bao gồm chuỗi thời gian trong bộ đệm chỉ báo). Mảng có thể chứa đối tượng, con trỏ hoặc cấu trúc. Đối với mảng đa chiều, chỉ chiều đầu tiên được đảo ngược.
Giá trị count
phải nằm trong khoảng từ 0 đến số phần tử trong chiều đầu tiên. Lưu ý rằng count
nhỏ hơn 2 sẽ không tạo ra hiệu ứng rõ rệt, nhưng nó có thể được sử dụng để thống nhất các vòng lặp trong thuật toán.
Hàm trả về true
nếu thành công hoặc false
nếu xảy ra lỗi.
Tập lệnh ArrayReverse.mq5
có thể được sử dụng để kiểm tra hàm. Ở đầu tập lệnh, một lớp được định nghĩa để tạo các đối tượng lưu trữ trong mảng. Sự hiện diện của chuỗi và các trường "phức tạp" khác không phải là vấn đề.
class Dummy
{
static int counter;
int x;
string s; // trường kiểu chuỗi khiến trình biên dịch tạo ra một hàm hủy ẩn
public:
Dummy() { x = counter++; }
};
static int Dummy::counter;
2
3
4
5
6
7
8
9
10
Các đối tượng được xác định bởi số thứ tự (được gán tại thời điểm tạo).
void OnStart()
{
Dummy objects[5];
Print("Objects before reverse");
ArrayPrint(objects);
/*
[x] [s]
[0] 0 null
[1] 1 null
[2] 2 null
[3] 3 null
[4] 4 null
*/
2
3
4
5
6
7
8
9
10
11
12
13
Sau khi áp dụng ArrayReverse
, chúng ta nhận được thứ tự ngược lại như kỳ vọng của các đối tượng.
PRTS(ArrayReverse(objects)); // true / status:0
Print("Objects after reverse");
ArrayPrint(objects);
/*
[x] [s]
[0] 4 null
[1] 3 null
[2] 2 null
[3] 1 null
[4] 0 null
*/
2
3
4
5
6
7
8
9
10
11
Tiếp theo, các mảng số với các cấu hình khác nhau được chuẩn bị và đảo ngược với các tham số khác nhau.
int dynamic[];
int dynamic2Dx4[][4];
int fixed[][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}};
ArrayCopy(dynamic, fixed);
ArrayCopy(dynamic2Dx4, fixed);
PRTS(ArrayReverse(fixed)); // true / status:0
ArrayPrint(fixed);
/*
[,0][,1][,2][,3]
[0,] 5 6 7 8
[1,] 1 2 3 4
*/
PRTS(ArrayReverse(dynamic, 4, 3)); // true / status:0
ArrayPrint(dynamic);
/*
1 2 3 4 7 6 5 8
*/
PRTS(ArrayReverse(dynamic, 0, 1)); // không làm gì (count = 1)
PRTS(ArrayReverse(dynamic2Dx4, 2, 1)); // false / status:5052, ERR_SMALL_ARRAY
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Trong trường hợp cuối cùng, giá trị start
(2) vượt quá kích thước trong chiều đầu tiên, vì vậy xảy ra lỗi.