Xuất Dữ Liệu Định Dạng Chung Sang Chuỗi
Khi tạo một chuỗi để hiển thị cho người dùng, lưu vào tệp hoặc gửi qua Internet, đôi khi cần phải bao gồm giá trị của nhiều biến thuộc các loại khác nhau trong đó. Vấn đề này có thể được giải quyết bằng cách ép kiểu tất cả các biến sang kiểu string
và cộng các chuỗi kết quả lại với nhau, nhưng trong trường hợp này, câu lệnh mã MQL sẽ dài và khó hiểu. Có lẽ sẽ tiện lợi hơn nếu sử dụng hàm StringConcatenate
, nhưng phương pháp này không hoàn toàn giải quyết được vấn đề.
Thực tế là một chuỗi thường không chỉ chứa các biến mà còn có một số đoạn văn bản chèn vào đóng vai trò là các liên kết và cung cấp cấu trúc đúng cho thông điệp tổng thể. Kết quả là các đoạn văn bản định dạng trộn lẫn với các biến. Loại mã này khó duy trì, điều này đi ngược lại một trong những nguyên tắc lập trình nổi tiếng: tách biệt nội dung và cách trình bày.
Có một giải pháp đặc biệt cho vấn đề này: hàm StringFormat
.
Cùng một cách tiếp cận cũng áp dụng cho một hàm khác trong API MQL5: PrintFormat
.
string StringFormat(const string format, ...)
Hàm này chuyển đổi các đối số thuộc các kiểu dữ liệu tích hợp tùy ý thành chuỗi theo định dạng được chỉ định. Tham số đầu tiên là mẫu của chuỗi cần chuẩn bị, trong đó các vị trí để chèn biến được đánh dấu theo cách đặc biệt và định dạng xuất của chúng được xác định. Những lệnh điều khiển này có thể được xen kẽ với văn bản thuần túy, văn bản này sẽ được sao chép nguyên vẹn vào chuỗi đầu ra. Các tham số tiếp theo của hàm, được phân tách bằng dấu phẩy, liệt kê tất cả các biến theo thứ tự và kiểu đã được dành sẵn cho chúng trong mẫu.
Caption: Tương tác giữa chuỗi định dạng và các đối số StringFormat
Mỗi điểm chèn biến trong chuỗi được đánh dấu bằng một bộ chỉ định định dạng: ký tự %
, sau đó có thể chỉ định một số cài đặt.
Chuỗi định dạng được phân tích từ trái sang phải. Khi gặp bộ chỉ định định dạng đầu tiên (nếu có), giá trị của tham số đầu tiên sau chuỗi định dạng sẽ được chuyển đổi và thêm vào chuỗi kết quả theo cài đặt được chỉ định. Bộ chỉ định thứ hai khiến tham số thứ hai được chuyển đổi và in ra, và cứ tiếp tục như vậy cho đến cuối chuỗi định dạng. Tất cả các ký tự khác trong mẫu giữa các bộ chỉ định được sao chép nguyên vẹn vào chuỗi kết quả.
Mẫu có thể không chứa bộ chỉ định nào, tức là nó có thể là một chuỗi đơn giản. Trong trường hợp này, bạn cần truyền một đối số giả vào hàm ngoài chuỗi (đối số này sẽ không được đặt trong chuỗi).
Nếu bạn muốn hiển thị ký hiệu phần trăm trong mẫu, thì bạn nên viết nó hai lần liên tiếp %%
. Nếu ký hiệu %
không được nhân đôi, thì vài ký tự tiếp theo sau %
luôn được phân tích như một bộ chỉ định.
Một thuộc tính bắt buộc của bộ chỉ định là một ký hiệu biểu thị kiểu được mong đợi và cách diễn giải đối số tiếp theo của hàm. Hãy tạm gọi ký hiệu này là T
. Vậy trong trường hợp đơn giản nhất, một bộ chỉ định định dạng trông như %T
.
Trong dạng tổng quát, bộ chỉ định có thể bao gồm một số trường bổ sung (các trường tùy chọn được biểu thị trong dấu ngoặc vuông):
%[Z][W][.P][M]T
Mỗi trường thực hiện chức năng của nó và nhận một trong những giá trị được phép. Tiếp theo, chúng ta sẽ xem xét từng trường một cách dần dần.
Kiểu T
Đối với số nguyên, các ký hiệu sau có thể được sử dụng làm T
, kèm theo giải thích về cách các số tương ứng được hiển thị trong chuỗi:
c
— Ký tự UnicodeC
— Ký tự ANSId
,i
— Số thập phân có dấuo
— Số bát phân không dấuu
— Số thập phân không dấux
— Số thập lục phân không dấu (chữ cái thường)X
— Số thập lục phân không dấu (chữ cái in hoa)
Nhớ rằng theo phương thức lưu trữ dữ liệu nội bộ, các kiểu số nguyên cũng bao gồm các kiểu tích hợp của MQL5 như datetime
, color
, bool
và các liệt kê.
Đối với số thực, các ký hiệu sau áp dụng được làm T
:
e
— Định dạng khoa học với số mũ (chữ 'e' thường)E
— Định dạng khoa học với số mũ (chữ 'E' in hoa)f
— Định dạng thông thườngg
— Tương tựf
hoặce
(chọn dạng nhỏ gọn nhất)G
— Tương tựf
hoặcE
(chọn dạng nhỏ gọn nhất)a
— Định dạng khoa học với số mũ, thập lục phân (chữ thường)A
— Định dạng khoa học với số mũ, thập lục phân (chữ in hoa)
Cuối cùng, chỉ có một phiên bản của ký hiệu T
dành cho chuỗi: s
.
Kích thước của số nguyên M
Đối với các kiểu số nguyên, bạn có thể chỉ định rõ ràng kích thước của biến tính bằng byte bằng cách thêm tiền tố trước T
bằng một trong các ký hiệu hoặc tổ hợp sau (chúng ta đã tổng quát hóa chúng dưới ký hiệu M
):
h
— 2 byte (short, ushort)l
(chữ L thường) — 4 byte (int, uint)I32
(chữ I in hoa) — 4 byte (int, uint)ll
(hai chữ L thường) — 8 byte (long)I64
(chữ I in hoa) — 8 byte (long, ulong)
Độ rộng W
Trường W
là một số thập phân không âm, chỉ định số lượng ký tự tối thiểu có sẵn cho giá trị được định dạng. Nếu giá trị của biến chiếm ít ký tự hơn, thì số lượng khoảng trắng tương ứng sẽ được thêm vào bên trái hoặc phải. Việc chọn bên trái hay phải phụ thuộc vào căn chỉnh (xem cờ -
trong trường Z
bên dưới). Nếu có cờ 0
, số lượng số 0 tương ứng sẽ được thêm vào trước giá trị đầu ra. Nếu số ký tự cần xuất lớn hơn độ rộng được chỉ định, thì cài đặt độ rộng bị bỏ qua và giá trị đầu ra không bị cắt ngắn.
Nếu một dấu sao *
được chỉ định làm độ rộng, thì độ rộng của giá trị đầu ra phải được chỉ định trong danh sách các tham số được truyền. Nó phải là một giá trị kiểu int
ở vị trí trước biến đang được định dạng.
Độ chính xác P
Trường P
cũng chứa một số thập phân không âm và luôn được đặt trước bằng dấu chấm .
. Đối với T
là số nguyên, trường này chỉ định số chữ số có nghĩa tối thiểu. Nếu giá trị chiếm ít chữ số hơn, nó sẽ được thêm số 0 vào phía trước.
Đối với số thực, P
chỉ định số chữ số thập phân (mặc định là 6), ngoại trừ các bộ chỉ định g
và G
, đối với chúng P
là tổng số chữ số có nghĩa (phần định trị và thập phân).
Đối với chuỗi, P
chỉ định số ký tự sẽ hiển thị. Nếu độ dài chuỗi vượt quá giá trị độ chính xác, thì chuỗi sẽ được hiển thị dưới dạng bị cắt ngắn.
Nếu dấu sao *
được chỉ định làm độ chính xác, nó được xử lý tương tự như đối với độ rộng nhưng điều khiển độ chính xác.
Độ rộng cố định và/hoặc độ chính xác, cùng với căn chỉnh bên phải, cho phép hiển thị các giá trị trong một cột gọn gàng.
Cờ Z
Cuối cùng, trường Z
mô tả các cờ:
-
(dấu trừ) — Căn chỉnh bên trái trong độ rộng được chỉ định (nếu không có cờ này, căn chỉnh bên phải được thực hiện);+
(dấu cộng) — Hiển thị vô điều kiện dấu+
hoặc-
trước giá trị (nếu không có cờ này, chỉ hiển thị-
cho các giá trị âm);0
— Các số 0 được thêm vào trước giá trị đầu ra nếu nó nhỏ hơn độ rộng được chỉ định;(khoảng trắng)
— Một khoảng trắng được đặt trước giá trị hiển thị nếu nó là giá trị có dấu và dương;#
— Điều khiển việc hiển thị tiền tố số bát phân và thập lục phân trong các định dạngo
,x
hoặcX
(ví dụ, đối với định dạngx
tiền tố "0x" được thêm trước số hiển thị, đối với định dạngX
— tiền tố "0X"), dấu chấm thập phân trong số thực (các định dạnge
,E
,a
hoặcA
) với phần thập phân bằng 0, và một số chi tiết khác.
Bạn có thể tìm hiểu thêm về các khả năng của việc xuất định dạng sang chuỗi trong tài liệu.
Tổng số tham số của hàm không được vượt quá 64.
Nếu số lượng đối số truyền vào hàm lớn hơn số bộ chỉ định, thì các đối số thừa sẽ bị bỏ qua.
Nếu số bộ chỉ định trong chuỗi định dạng lớn hơn số đối số, thì hệ thống sẽ cố gắng hiển thị số 0 thay cho dữ liệu bị thiếu, nhưng một cảnh báo văn bản ("missing string parameter") sẽ được nhúng vào đối với các bộ chỉ định chuỗi.
Nếu kiểu của giá trị không khớp với kiểu của bộ chỉ định tương ứng, hệ thống sẽ cố gắng đọc dữ liệu từ biến theo định dạng và hiển thị giá trị kết quả (nó có thể trông kỳ lạ do diễn giải sai biểu diễn bit nội bộ của dữ liệu thực tế). Trong trường hợp chuỗi, một cảnh báo ("non-string passed") có thể được nhúng vào kết quả.
Hãy kiểm tra hàm với tập lệnh StringFormat.mq5
.
Đầu tiên, hãy thử các tùy chọn khác nhau cho T
và bộ chỉ định kiểu dữ liệu.
PRT(StringFormat("[Infinity Sign] Unicode (ok): %c; ANSI (overflow): %C",
'∞', '∞'));
PRT(StringFormat("short (ok): %hi, short (overflow): %hi",
SHORT_MAX, INT_MAX));
PRT(StringFormat("int (ok): %i, int (overflow): %i",
INT_MAX, LONG_MAX));
PRT(StringFormat("long (ok): %lli, long (overflow): %i",
LONG_MAX, LONG_MAX));
PRT(StringFormat("ulong (ok): %llu, long signed (overflow): %lli",
ULONG_MAX, ULONG_MAX));
2
3
4
5
6
7
8
9
10
Cả bộ chỉ định đúng và sai đều được biểu diễn ở đây (các bộ chỉ định sai đứng thứ hai trong mỗi câu lệnh và được đánh dấu bằng từ "overflow" vì giá trị được truyền không phù hợp với kiểu định dạng).
Dưới đây là những gì xảy ra trong nhật ký (các ngắt dòng dài ở đây và dưới đây được thực hiện để xuất bản):
StringFormat(Plain string,0)='Plain string'
StringFormat([Infinity Sign] Unicode: %c; ANSI: %C,'∞','∞')=
'[Infinity Sign] Unicode (ok): ∞; ANSI (overflow): '
StringFormat(short (ok): %hi, short (overflow): %hi,SHORT_MAX,INT_MAX)=
'short (ok): 32767, short (overflow): -1'
StringFormat(int (ok): %i, int (overflow): %i,INT_MAX,LONG_MAX)=
'int (ok): 2147483647, int (overflow): -1'
StringFormat(long (ok): %lli, long (overflow): %i,LONG_MAX,LONG_MAX)=
'long (ok): 9223372036854775807, long (overflow): -1'
StringFormat(ulong (ok): %llu, long signed (overflow): %lli,ULONG_MAX,ULONG_MAX)=
'ulong (ok): 18446744073709551615, long signed (overflow): -1'
2
3
4
5
6
7
8
9
10
11
Tất cả các câu lệnh sau đây đều đúng:
PRT(StringFormat("ulong (ok): %I64u", ULONG_MAX));
PRT(StringFormat("ulong (HEX): %I64X, ulong (hex): %I64x",
1234567890123456, 1234567890123456));
PRT(StringFormat("double PI: %f", M_PI));
PRT(StringFormat("double PI: %e", M_PI));
PRT(StringFormat("double PI: %g", M_PI));
PRT(StringFormat("double PI: %a", M_PI));
PRT(StringFormat("string: %s", "ABCDEFGHIJ"));
2
3
4
5
6
7
8
Kết quả của công việc của chúng được hiển thị dưới đây:
StringFormat(ulong (ok): %I64u,ULONG_MAX)=
'ulong (ok): 18446744073709551615'
StringFormat(ulong (HEX): %I64X, ulong (hex): %I64x,1234567890123456,1234567890123456)=
'ulong (HEX): 462D53C8ABAC0, ulong (hex): 462d53c8abac0'
StringFormat(double PI: %f,M_PI)='double PI: 3.141593'
StringFormat(double PI: %e,M_PI)='double PI: 3.141593e+00'
StringFormat(double PI: %g,M_PI)='double PI: 3.14159'
StringFormat(double PI: %a,M_PI)='double PI: 0x1.921fb54442d18p+1'
StringFormat(string: %s,ABCDEFGHIJ)='string: ABCDEFGHIJ'
2
3
4
5
6
7
8
9
Bây giờ hãy xem xét các bộ sửa đổi khác nhau.
Với căn chỉnh bên phải (mặc định) và độ rộng trường cố định (số ký tự), chúng ta có thể sử dụng các tùy chọn khác nhau để đệm chuỗi kết quả ở bên trái: bằng khoảng trắng hoặc số 0. Ngoài ra, cho bất kỳ căn chỉnh nào, bạn có thể bật hoặc tắt việc hiển thị rõ ràng dấu của giá trị (để không chỉ hiển thị dấu trừ cho giá trị âm mà còn hiển thị dấu cộng cho giá trị dương).
PRT(StringFormat("space padding: %10i", SHORT_MAX));
PRT(StringFormat("0-padding: %010i", SHORT_MAX));
PRT(StringFormat("with sign: %+10i", SHORT_MAX));
PRT(StringFormat("precision: %.10i", SHORT_MAX));
2
3
4
Chúng ta nhận được như sau trong nhật ký:
StringFormat(space padding: %10i,SHORT_MAX)='space padding: 32767'
StringFormat(0-padding: %010i,SHORT_MAX)='0-padding: 0000032767'
StringFormat(with sign: %+10i,SHORT_MAX)='with sign: +32767'
StringFormat(precision: %.10i,SHORT_MAX)='precision: 0000032767'
2
3
4
Để căn chỉnh sang trái, bạn phải sử dụng cờ -
(dấu trừ), việc thêm chuỗi vào độ rộng được chỉ định xảy ra ở bên phải:
PRT(StringFormat("no sign (default): %-10i", SHORT_MAX));
PRT(StringFormat("with sign: %+-10i", SHORT_MAX));
2
Kết quả:
StringFormat(no sign (default): %-10i,SHORT_MAX)='no sign (default): 32767 '
StringFormat(with sign: %+-10i,SHORT_MAX)='with sign: +32767 '
2
Nếu cần, chúng ta có thể hiển thị hoặc ẩn dấu của giá trị (mặc định chỉ hiển thị dấu trừ cho các giá trị âm), thêm một khoảng trắng cho các giá trị dương, và do đó đảm bảo định dạng giống nhau khi cần hiển thị các biến trong một cột:
PRT(StringFormat("default: %i", SHORT_MAX)); // chuẩn
PRT(StringFormat("default: %i", SHORT_MIN));
PRT(StringFormat("space : % i", SHORT_MAX)); // khoảng trắng bổ sung cho dương
PRT(StringFormat("space : % i", SHORT_MIN));
PRT(StringFormat("sign : %+i", SHORT_MAX)); // buộc hiển thị dấu
PRT(StringFormat("sign : %+i", SHORT_MIN));
2
3
4
5
6
Dưới đây là những gì nó trông như trong nhật ký:
StringFormat(default: %i,SHORT_MAX)='default: 32767'
StringFormat(default: %i,SHORT_MIN)='default: -32768'
StringFormat(space : % i,SHORT_MAX)='space : 32767'
StringFormat(space : % i,SHORT_MIN)='space : -32768'
StringFormat(sign : %+i,SHORT_MAX)='sign : +32767'
StringFormat(sign : %+i,SHORT_MIN)='sign : -32768'
2
3
4
5
6
Bây giờ hãy so sánh cách độ rộng và độ chính xác ảnh hưởng đến số thực.
PRT(StringFormat("double PI: %15.10f", M_PI));
PRT(StringFormat("double PI: %15.10e", M_PI));
PRT(StringFormat("double PI: %15.10g", M_PI));
PRT(StringFormat("double PI: %15.10a", M_PI));
// độ chính xác mặc định = 6
PRT(StringFormat("double PI: %15f", M_PI));
PRT(StringFormat("double PI: %15e", M_PI));
PRT(StringFormat("double PI: %15g", M_PI));
PRT(StringFormat("double PI: %15a", M_PI));
2
3
4
5
6
7
8
9
10
Kết quả:
StringFormat(double PI: %15.10f,M_PI)='double PI: 3.1415926536'
StringFormat(double PI: %15.10e,M_PI)='double PI: 3.1415926536e+00'
StringFormat(double PI: %15.10g,M_PI)='double PI: 3.141592654'
StringFormat(double PI: %15.10a,M_PI)='double PI: 0x1.921fb54443p+1'
StringFormat(double PI: %15f,M_PI)='double PI: 3.141593'
StringFormat(double PI: %15e,M_PI)='double PI: 3.141593e+00'
StringFormat(double PI: %15g,M_PI)='double PI: 3.14159'
StringFormat(double PI: %15a,M_PI)='double PI: 0x1.921fb54442d18p+1'
2
3
4
5
6
7
8
Nếu độ rộng rõ ràng không được chỉ định, các giá trị được xuất mà không có đệm bằng khoảng trắng.
PRT(StringFormat("double PI: %.10f", M_PI));
PRT(StringFormat("double PI: %.10e", M_PI));
PRT(StringFormat("double PI: %.10g", M_PI));
PRT(StringFormat("double PI: %.10a", M_PI));
2
3
4
Kết quả:
StringFormat(double PI: %.10f,M_PI)='double PI: 3.1415926536'
StringFormat(double PI: %.10e,M_PI)='double PI: 3.1415926536e+00'
StringFormat(double PI: %.10g,M_PI)='double PI: 3.141592654'
StringFormat(double PI: %.10a,M_PI)='double PI: 0x1.921fb54443p+1'
2
3
4
Việc thiết lập độ rộng và độ chính xác của các giá trị bằng cách sử dụng dấu *
và dựa trên các đối số bổ sung của hàm được thực hiện như sau:
PRT(StringFormat("double PI: %*.*f", 12, 5, M_PI));
PRT(StringFormat("string: %*s", 15, "ABCDEFGHIJ"));
PRT(StringFormat("string: %-*s", 15, "ABCDEFGHIJ"));
2
3
Lưu ý rằng 1 hoặc 2 giá trị kiểu số nguyên được truyền trước giá trị đầu ra, tùy theo số lượng dấu sao *
trong bộ chỉ định: bạn có thể điều khiển độ chính xác và độ rộng riêng lẻ hoặc cả hai cùng nhau.
StringFormat(double PI: %*.*f,12,5,M_PI)='double PI: 3.14159'
StringFormat(string: %*s,15,ABCDEFGHIJ)='string: ABCDEFGHIJ'
StringFormat(string: %-*s,15,ABCDEFGHIJ)='string: ABCDEFGHIJ '
2
3
Cuối cùng, hãy xem xét một vài lỗi định dạng phổ biến.
PRT(StringFormat("string: %s %d %f %s", "ABCDEFGHIJ"));
PRT(StringFormat("string vs int: %d", "ABCDEFGHIJ"));
PRT(StringFormat("double vs int: %d", M_PI));
PRT(StringFormat("string vs double: %s", M_PI));
2
3
4
Câu lệnh đầu tiên có nhiều bộ chỉ định hơn đối số. Trong các trường hợp khác, kiểu của bộ chỉ định và giá trị được truyền không khớp. Kết quả là chúng ta nhận được đầu ra sau:
StringFormat(string: %s %d %f %s,ABCDEFGHIJ)=
'string: ABCDEFGHIJ 0 0.000000 (missed string parameter)'
StringFormat(string vs int: %d,ABCDEFGHIJ)='string vs int: 0'
StringFormat(double vs int: %d,M_PI)='double vs int: 1413754136'
StringFormat(string vs double: %s,M_PI)=
'string vs double: (non-string passed)'
2
3
4
5
6
Việc có một chuỗi định dạng duy nhất trong mỗi lần gọi hàm StringFormat
cho phép bạn sử dụng nó, đặc biệt, để dịch giao diện bên ngoài của chương trình và các thông điệp sang các ngôn ngữ khác nhau: chỉ cần tải và thay thế vào StringFormat
các chuỗi định dạng khác nhau (được chuẩn bị trước) tùy thuộc vào sở thích của người dùng hoặc cài đặt terminal.