Khởi Tạo và Đo Lường Chuỗi
Như chúng ta đã biết từ phần Kiểu Chuỗi, chỉ cần khai báo một biến kiểu string
trong mã là đủ, và nó sẽ sẵn sàng để sử dụng.
Đối với bất kỳ biến nào thuộc kiểu string
, 12 byte được cấp phát cho cấu trúc dịch vụ, là biểu diễn nội bộ của chuỗi. Cấu trúc này chứa địa chỉ bộ nhớ (con trỏ) nơi văn bản được lưu trữ, cùng với một số thông tin meta khác. Bản thân văn bản cũng yêu cầu đủ bộ nhớ, nhưng bộ đệm này được cấp phát với một số tối ưu hóa ít rõ ràng hơn.
Cụ thể, chúng ta có thể khai báo một chuỗi cùng với khởi tạo rõ ràng, bao gồm cả một chuỗi ký tự rỗng:
string s = ""; // con trỏ đến chuỗi ký tự chứa '\0'
Trong trường hợp này, con trỏ sẽ được thiết lập trực tiếp đến chuỗi ký tự, và không có bộ nhớ nào được cấp phát cho bộ đệm (ngay cả khi chuỗi ký tự dài). Rõ ràng, bộ nhớ tĩnh đã được cấp phát cho chuỗi ký tự, và nó có thể được sử dụng trực tiếp. Bộ nhớ cho bộ đệm chỉ được cấp phát nếu bất kỳ lệnh nào trong chương trình thay đổi nội dung của chuỗi. Ví dụ (lưu ý phép cộng '+' được phép cho chuỗi):
int n = 1;
s += (string)n; // con trỏ đến bộ nhớ chứa "1"'\0'[cộng thêm dự trữ]
2
Từ thời điểm này, chuỗi thực sự chứa văn bản "1" và, nói một cách nghiêm ngặt, yêu cầu bộ nhớ cho hai ký tự: chữ số "1" và ký tự kết thúc ngầm '\0' (dấu kết thúc chuỗi). Tuy nhiên, hệ thống sẽ cấp phát một bộ đệm lớn hơn, với một số không gian dự trữ.
Khi chúng ta khai báo một biến mà không có giá trị ban đầu, nó vẫn được trình biên dịch khởi tạo ngầm, dù trong trường hợp này là với giá trị đặc biệt NULL:
string z; // bộ nhớ cho con trỏ không được cấp phát, con trỏ = NULL
Chuỗi như vậy chỉ yêu cầu 12 byte cho mỗi cấu trúc, và con trỏ không trỏ đến đâu cả: đó là ý nghĩa của NULL.
Trong các phiên bản tương lai của trình biên dịch MQL5, hành vi này có thể thay đổi, và một vùng bộ nhớ nhỏ sẽ luôn được cấp phát ban đầu cho một chuỗi rỗng, cung cấp một số không gian dự trữ.
Ngoài các tính năng nội bộ này, các biến kiểu string
không khác gì so với các biến của các kiểu khác. Tuy nhiên, do chuỗi có thể thay đổi độ dài và, quan trọng hơn, chúng có thể thay đổi độ dài trong quá trình thực thi thuật toán, điều này có thể ảnh hưởng xấu đến hiệu quả cấp phát bộ nhớ và hiệu suất.
Ví dụ, nếu tại một thời điểm nào đó chương trình cần thêm một từ mới vào chuỗi, có thể không đủ bộ nhớ đã cấp phát cho chuỗi. Khi đó, môi trường thực thi chương trình MQL, mà người dùng không nhận thấy, sẽ tìm một khối bộ nhớ trống mới có kích thước lớn hơn và sao chép giá trị cũ vào đó cùng với từ được thêm vào. Sau đó, địa chỉ cũ được thay thế bằng địa chỉ mới trong cấu trúc dịch vụ của chuỗi.
Nếu có nhiều thao tác như vậy, sự chậm trễ do sao chép có thể trở nên đáng chú ý, và ngoài ra, bộ nhớ chương trình dễ bị phân mảnh: các vùng bộ nhớ nhỏ cũ được giải phóng sau khi sao chép tạo thành các khoảng trống không phù hợp về kích thước cho các chuỗi lớn, dẫn đến lãng phí bộ nhớ. Tất nhiên, terminal có khả năng kiểm soát các tình huống như vậy và tái tổ chức bộ nhớ, nhưng điều này cũng đi kèm với chi phí.
Cách hiệu quả nhất để giải quyết vấn đề này là chỉ định rõ ràng trước kích thước của bộ đệm cho chuỗi và khởi tạo nó bằng các hàm API MQL5 tích hợp, mà chúng ta sẽ xem xét sau trong phần này.
Cơ sở cho tối ưu hóa này là kích thước bộ nhớ được cấp phát có thể vượt quá độ dài hiện tại (và tiềm năng trong tương lai) của chuỗi, được xác định bởi ký tự null đầu tiên trong văn bản. Do đó, chúng ta có thể cấp phát một bộ đệm cho 100 ký tự, nhưng từ đầu đặt '\0' ngay tại vị trí bắt đầu, điều này sẽ cho một chuỗi có độ dài bằng 0 ("").
Tất nhiên, giả định rằng trong những trường hợp như vậy, lập trình viên có thể tính toán sơ bộ độ dài dự kiến của chuỗi hoặc tốc độ tăng trưởng của nó.
Vì các chuỗi trong MQL5 dựa trên ký tự hai byte (đảm bảo hỗ trợ Unicode), kích thước của chuỗi và bộ đệm tính bằng ký tự nên được nhân với 2 để tính lượng bộ nhớ chiếm dụng và được cấp phát tính bằng byte.
Một ví dụ tổng quát về việc sử dụng tất cả các hàm (StringInit.mq5
) sẽ được đưa ra ở cuối phần này.
bool StringInit(string &variable, int capacity = 0, ushort character = 0)
Hàm StringInit
được sử dụng để khởi tạo (cấp phát và điền bộ nhớ) và hủy khởi tạo (giải phóng bộ nhớ) chuỗi. Biến cần xử lý được truyền vào tham số đầu tiên.
Nếu tham số capacity
lớn hơn 0, thì một bộ đệm (vùng bộ nhớ) có kích thước được chỉ định sẽ được cấp phát cho chuỗi và được điền bằng ký tự character
. Nếu character
là 0, thì độ dài của chuỗi sẽ bằng 0, vì ký tự đầu tiên là ký tự kết thúc.
Nếu tham số capacity
là 0, thì bộ nhớ được cấp phát trước đó sẽ được giải phóng. Trạng thái của biến trở nên giống như khi nó vừa được khai báo mà không khởi tạo (con trỏ đến bộ đệm là NULL). Đơn giản hơn, điều này cũng có thể được thực hiện bằng cách gán biến chuỗi thành NULL.
Hàm trả về chỉ báo thành công (true
) hoặc lỗi (false
).
bool StringReserve(string &variable, uint capacity)
Hàm StringReserve
tăng hoặc giảm kích thước bộ đệm của chuỗi variable
, ít nhất là đến số ký tự được chỉ định trong tham số capacity
. Nếu giá trị capacity
nhỏ hơn độ dài hiện tại của chuỗi, hàm không làm gì cả. Trên thực tế, kích thước bộ đệm có thể lớn hơn yêu cầu: môi trường thực hiện điều này vì lý do hiệu quả trong các thao tác tương lai với chuỗi. Do đó, nếu hàm được gọi với giá trị giảm cho bộ đệm, nó có thể bỏ qua yêu cầu và vẫn trả về true
("không có lỗi").
Kích thước bộ đệm hiện tại có thể được lấy bằng hàm StringBufferLen
(xem bên dưới).
Khi thành công, hàm trả về true
, nếu không thì trả về false
.
Không giống như StringInit
, hàm StringReserve
không thay đổi nội dung của chuỗi và không điền nó bằng các ký tự.
bool StringFill(string &variable, ushort character)
Hàm StringFill
điền chuỗi variable
được chỉ định với ký tự character
cho toàn bộ độ dài hiện tại của nó (đến ký tự zero đầu tiên). Nếu một bộ đệm đã được cấp phát cho chuỗi, việc sửa đổi được thực hiện tại chỗ, không có thao tác tạo dòng mới và sao chép trung gian.
Hàm trả về chỉ báo thành công (true
) hoặc lỗi (false
).
int StringBufferLen(const string &variable)
Hàm trả về kích thước của bộ đệm được cấp phát cho chuỗi variable
.
Lưu ý rằng đối với một chuỗi được khởi tạo bằng chuỗi ký tự, ban đầu không có bộ đệm nào được cấp phát vì con trỏ trỏ đến chuỗi ký tự. Do đó, hàm sẽ trả về 0 mặc dù độ dài của chuỗi StringLen
(xem bên dưới) có thể lớn hơn.
Giá trị -1 có nghĩa là chuỗi thuộc về terminal của máy khách và không thể thay đổi.
bool StringSetLength(string &variable, uint length)
Hàm đặt độ dài được chỉ định tính bằng ký tự length
cho chuỗi variable
. Giá trị của length
không được lớn hơn độ dài hiện tại của chuỗi. Nói cách khác, hàm chỉ cho phép rút ngắn chuỗi, nhưng không kéo dài nó. Độ dài của chuỗi được tăng tự động khi hàm StringAdd được gọi, hoặc thực hiện phép toán cộng '+'.
Tương đương với hàm StringSetLength
là lời gọi StringSetCharacter(variable, length, 0)
(xem phần Làm việc với ký tự và bảng mã).
Nếu một bộ đệm đã được cấp phát cho chuỗi trước khi gọi hàm, hàm không thay đổi nó. Nếu chuỗi không có bộ đệm (nó đang trỏ đến một chuỗi ký tự), việc giảm độ dài dẫn đến việc cấp phát một bộ đệm mới và sao chép chuỗi đã rút ngắn vào đó.
Hàm trả về true
hoặc false
trong trường hợp thành công hoặc thất bại, tương ứng.
int StringLen(const string text)
Hàm trả về số lượng ký tự trong chuỗi text
. Ký tự zero kết thúc không được tính.
Lưu ý rằng tham số được truyền theo giá trị, vì vậy bạn có thể tính độ dài của chuỗi không chỉ trong các biến mà còn cho bất kỳ giá trị trung gian nào khác: kết quả tính toán hoặc chuỗi ký tự.
Script StringInit.mq5
đã được tạo để thể hiện các hàm trên. Nó sử dụng một phiên bản đặc biệt của macro PRT, PRTE, phân tích kết quả của một biểu thức thành true
hoặc false
, và trong trường hợp sau còn xuất thêm mã lỗi:
#define PRTE(A) Print(#A, "=", (A) ? "true" : "false:" + (string)GetLastError())
Để xuất gỡ lỗi vào nhật ký của một chuỗi và các số liệu hiện tại của nó (độ dài dòng và kích thước bộ đệm), hàm StrOut
được triển khai:
void StrOut(const string &s)
{
Print("'", s, "' [", StringLen(s), "] ", StringBufferLen(s));
}
2
3
4
Nó sử dụng các hàm tích hợp StringLen
và StringBufferLen
.
Script kiểm tra thực hiện một loạt hành động trên một chuỗi trong OnStart
:
void OnStart()
{
string s = "message";
StrOut(s);
PRTE(StringReserve(s, 100)); // ok, nhưng chúng ta nhận được bộ đệm lớn hơn yêu cầu: 260
StrOut(s);
PRTE(StringReserve(s, 500)); // ok, bộ đệm được tăng lên 500
StrOut(s);
PRTE(StringSetLength(s, 4)); // ok: chuỗi được rút ngắn
StrOut(s);
s += "age";
PRTE(StringReserve(s, 100)); // ok: bộ đệm vẫn ở mức 500
StrOut(s);
PRTE(StringSetLength(s, 8)); // không: kéo dài chuỗi không được hỗ trợ
StrOut(s); // thông qua StringSetLength
PRTE(StringInit(s, 8, '$')); // ok: dòng được tăng bằng cách đệm
StrOut(s); // bộ đệm vẫn như cũ
PRTE(StringFill(s, 0)); // ok: chuỗi bị thu gọn thành rỗng vì
StrOut(s); // được điền bằng 0, bộ đệm vẫn như cũ
PRTE(StringInit(s, 0)); // ok: dòng được đặt về zero, bao gồm bộ đệm
// chúng ta chỉ cần viết s = NULL;
StrOut(s);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Script sẽ ghi lại các thông điệp sau vào nhật ký:
'message' [7] 0
StringReserve(s,100)=true
'message' [7] 260
StringReserve(s,500)=true
'message' [7] 500
StringSetLength(s,4)=true
'mess' [4] 500
StringReserve(s,10)=true
'message' [7] 500
StringSetLength(s,8)=false:5035
'message' [7] 500
StringInit(s,8,'$')=true
'$$$$$$$$' [8] 500
StringFill(s,0)=true
'' [0] 500
StringInit(s,0)=true
'' [0] 0
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Lưu ý rằng cuộc gọi StringSetLength
với độ dài chuỗi tăng lên đã kết thúc với lỗi 5035 (ERR_STRING_SMALL_LEN).