Khởi tạo
Khi mô tả các biến, có thể thiết lập giá trị ban đầu; giá trị này được chỉ định theo sau tên biến và ký hiệu =
và phải tương ứng với kiểu biến hoặc được ép kiểu thành biến đó (có thể tìm hiểu về ép kiểu trong phần có liên quan).
int i = 3, j, k = 10;
Ở đây i
và k
được khởi tạo một cách rõ ràng, trong khi j
thì không.
Cả hằng số (kiểu có liên quan) và biểu thức (một loại công thức tính toán) đều có thể được chỉ định làm giá trị ban đầu. Chúng ta sẽ trình bày riêng các biểu thức. Trong khi chờ đợi, một ví dụ đơn giản:
int i = 3, j = i, k = i + j;
Ở đây, biến j
lấy cùng giá trị với biến i
, trong khi biến k
lấy tổng của i
và j
. Nói một cách chính xác, trong cả ba trường hợp, chúng ta đều thấy các biểu thức ở đây. Tuy nhiên, hằng số (3) là một tùy chọn biểu thức đặc biệt, tùy chọn biểu thức suy biến. Trong trường hợp thứ hai, tên biến duy nhất là một biểu thức, tức là kết quả của biểu thức sẽ là giá trị của biến này mà không có bất kỳ phép biến đổi nào. Trong trường hợp thứ ba, hai biến i
và j
được truy cập trong biểu thức, phép toán cộng được thực hiện với các giá trị của chúng và sau đó, kết quả được đưa vào biến k.
Vì câu lệnh chứa mô tả của nhiều biến được xử lý từ trái sang phải nên trình biên dịch đã biết tên của các biến trước đó khi phân tích thêm một mô tả khác.
Một chương trình thường chứa nhiều câu lệnh có mô tả biến. Chúng được trình biên dịch đọc theo cách tự nhiên từ trên xuống. Trong các lần khởi tạo sau, tên có thể được sử dụng lấy từ các mô tả trước đó. Sau đây là các biến giống nhau được mô tả bởi hai câu lệnh riêng biệt.
int i = 3, j = i;
int k = i + j;
2
Các biến không có khởi tạo rõ ràng cũng có một số giá trị ban đầu, nhưng chúng phụ thuộc vào nơi biến được mô tả, tức là vào ngữ cảnh của biến.
Khi không có khởi tạo, các biến cục bộ sẽ có giá trị ngẫu nhiên tại thời điểm chúng được tạo: Trình biên dịch chỉ phân bổ bộ nhớ cho chúng theo kích thước kiểu, trong khi không biết điều gì sẽ có ở một địa chỉ cụ thể (nhiều vùng bộ nhớ máy tính thường được phân bổ lại để sử dụng trong các chương trình khác nhau sau khi chúng trở nên không cần thiết cho các chương trình được thực thi trước đó).
Người ta thường gợi ý rằng các giá trị làm việc sẽ được nhập vào các biến cục bộ mà không cần khởi tạo ở đâu đó sau đó trong mã thuật toán, chẳng hạn như sử dụng các phép toán gán mà chúng ta sẽ nói đến sau. Về mặt cú pháp, nó tương tự như khởi tạo, vì nó cũng sử dụng dấu bằng, =
, để chuyển giá trị từ "structure" được đặt ở bên phải của nó (có thể là hằng số, biến, biểu thức hoặc lệnh gọi hàm, vào biến bên trái. Chỉ có một biến có thể ở bên trái của =
).
Người lập trình phải đảm bảo rằng việc đọc từ biến chưa được khởi tạo chỉ diễn ra khi một giá trị có nghĩa được gán cho nó. Trình biên dịch sẽ đưa ra cảnh báo nếu không phải như vậy ("có thể sử dụng biến chưa được khởi tạo").
Mọi thứ đều khác biệt với biến toàn cục.
Một ví dụ về biến toàn cục là tham số đầu vào GreetingHour
của tập lệnh GoodTime2
từ Phần 2. Thực tế là biến được mô tả bằng từ khóa input
không ảnh hưởng đến các thuộc tính khác của nó như một biến. Chúng ta có thể loại trừ việc khởi tạo của nó và mô tả nó như sau:
input uint GreetingHour;
Điều này sẽ không thay đổi bất cứ điều gì trong chương trình, vì các biến toàn cục được trình biên dịch khởi tạo ngầm định bằng số không nếu không có khởi tạo rõ ràng (trong khi trước đây chúng ta cũng đã khởi tạo rõ ràng bằng số không).
Bất kể kiểu biến là gì, khởi tạo ngầm định luôn được thực hiện bằng một giá trị tương đương với số không. Ví dụ, đối với biến bool
, false
sẽ được đặt, trong khi đối với biến datetime
sẽ là D'1970.01.01 00:00:00'
. Có một giá trị đặc biệt, NULL
, cho các chuỗi. Nếu bạn thích, đó là một chuỗi "trống" hơn cả dấu ngoặc kép rỗng "" vì vẫn còn một số bộ nhớ được phân bổ cho chúng, nơi ký tự null
duy nhất được đặt ở đầu cuối.
Cùng với các biến cục bộ và toàn cục, còn có một loại khác, tức là các biến tĩnh. Trình biên dịch cũng khởi tạo chúng bằng số không một cách ngầm định, nếu lập trình viên không viết một giá trị khởi tạo rõ ràng. Chúng sẽ được xem xét trong phần tiếp theo .
Hãy tạo một tập lệnh mới, VariableScopes.mq5
, với các ví dụ mô tả biến cục bộ và biến toàn cục (MQL5/Scripts/MQL5Book/VariableScopes.mq5
).
// các biến toàn cục
int i, j, k; // tất cả đều là 0
int m = 1; // m = 1 (đặt điểm dừng trên dòng này)
int n = i + m; // n = 1
void OnStart () {
// các biến cục bộ
int x, y, z;
int k = m; // cảnh báo: khai báo 'k' sẽ ẩn biến toàn cục
int j = j ; // cảnh báo: khai báo 'j' sẽ ẩn biến toàn cục
// sử dụng biến trong các câu lệnh gán
x = n; // ok, 1
z = y; // cảnh báo: có thể sử dụng biến chưa khởi tạo 'y'
j = 10; // thay đổi j cục bộ, j toàn cục vẫn là 0
}
// lỗi biên dịch
// int bad = x; // 'x' - định danh chưa được khai báo
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Cần nhớ rằng, khi khởi chạy một chương trình MQL, đầu tiên terminal sẽ khởi tạo tất cả các biến toàn cục và sau đó gọi một hàm là điểm khởi đầu cho các chương trình có kiểu liên quan. Trong trường hợp này, đó là OnStart
cho các tập lệnh.
Ở đây, chỉ có các biến i
, j
, k
, m
, n
là toàn cục vì chúng được mô tả bên ngoài hàm (trong trường hợp của chúng ta, chúng ta chỉ có một hàm, OnStart
, cần thiết cho các tập lệnh). i
, j
, k
ngầm định lấy giá trị 0
. m
và n
chứa 1
.
Bạn có thể chạy tập lệnh ở chế độ gỡ lỗi theo từng bước và đảm bảo rằng các giá trị của biến thay đổi chính xác theo cách này. Với mục đích này, bạn nên đặt trước một điểm dừng vào chuỗi bằng cách khởi tạo một trong các biến toàn cục, chẳng hạn như m
. Đặt con trỏ văn bản vào chuỗi này và thực hiện Debug -> Toggle Breakpoint (F9)
, và chuỗi sẽ được tô sáng bằng một dấu hiệu màu xanh lam ở trường bên trái, báo hiệu rằng việc thực thi chương trình sẽ dừng tại đây nếu nó bắt đầu hoạt động trên trình gỡ lỗi.
Sau đó, bạn thực sự nên chạy chương trình để gỡ lỗi, với mục đích thực hiện lệnh Debug -> Start on real data (F5)
. Lúc này, một biểu đồ mới sẽ mở ra trong terminal, trong đó tập lệnh này bắt đầu được thực thi (tiêu đề "VariableScopes (Debugging)" ở góc trên bên phải), nhưng nó sẽ tạm dừng ngay lập tức và chúng ta quay lại MetaEditor. Chúng ta sẽ thấy một hình ảnh trong đó như sau.
Một chuỗi chứa điểm dừng hiện được đánh dấu bằng dấu mũi tên — đó là câu lệnh hiện tại mà chương trình đang chuẩn bị thực thi nhưng vẫn chưa thực thi. Ngăn xếp hiện tại của chương trình được hiển thị ở góc dưới bên trái, cho đến nay chỉ bao gồm một mục nhập: @global_initializations
. Bạn có thể nhập các biểu thức ở góc dưới bên phải để theo dõi các giá trị thời gian thực của chúng. Chúng ta quan tâm đến các giá trị của biến; do đó, hãy nhập liên tiếp i
, j
, k
, m
, n
, x
, y
, z
(mỗi biến trong một chuỗi riêng biệt).
Bạn sẽ thấy thêm rằng MetaEditor tự động thêm các biến từ ngữ cảnh hiện tại để xem (ví dụ, các biến cục bộ và các đầu vào hàm, nơi các câu lệnh được thực thi bên trong hàm). Nhưng bây giờ, chúng ta sẽ thêm x
, y
và z
theo cách thủ công và trước, chỉ để cho thấy rằng chúng không được định nghĩa bên ngoài hàm.
Xin lưu ý rằng, đối với các biến cục bộ, nó được viết là "Unknown identifier" thay vì giá trị, vì vẫn chưa có khối hàm OnStart
, nơi chúng được đặt. Các biến toàn cục i
và j
đầu tiên sẽ có giá trị bằng không. Biến toàn cục k
không được sử dụng ở bất kỳ đâu và do đó, nó bị trình biên dịch loại trừ.
Nếu chúng ta thực hiện một bước thực thi chương trình (thực thi câu lệnh trên dòng mã hiện tại) bằng lệnh Step Into (F11)
hoặc Step Over (F10)
, chúng ta sẽ thấy biến m
nhận giá trị 1. Một bước khác sẽ tiếp tục khởi tạo cho biến n và nó cũng sẽ trở thành 1.
Ở đây, mô tả về các biến toàn cục kết thúc và, như chúng ta đã biết, terminal gọi hàm OnStart
sau khi hoàn tất việc khởi tạo các biến toàn cục. Trong trường hợp này, để bước vào hàm OnStart
ở chế độ từng bước, hãy nhấn F11
một lần nữa (hoặc bạn có thể đặt một điểm dừng khác ở đầu hàm OnStart
).
Biến cục bộ được khởi tạo khi thực thi các câu lệnh chương trình đến khối mã nơi chúng được định nghĩa. Do đó, các biến x
, y
, z
chỉ được tạo khi bước vào hàm OnStart
.
Khi trình gỡ lỗi vào bên trong hàm OnStart
, với một chút may mắn, bạn sẽ có thể thấy rằng thực sự có các giá trị ngẫu nhiên ban đầu trong x
, y
và z
. "May mắn" ở đây bao gồm thực tế là các giá trị ngẫu nhiên này có thể là số không. Khi đó, sẽ không thể phân biệt chúng với khởi tạo ngầm định bằng số không, trình biên dịch thực hiện cho các biến toàn cục. Nếu tập lệnh được khởi chạy nhiều lần, "rác" trong các biến cục bộ có khả năng sẽ khác và minh họa nhiều hơn. Chúng không được khởi tạo rõ ràng và do đó, nội dung của chúng có thể là bất kỳ loại nào.
Trong chuỗi hình ảnh bên dưới, bạn có thể thấy sự tiến triển của các biến bằng cách sử dụng chế độ từng bước của trình gỡ lỗi. Chuỗi hiện tại sẽ được thực thi (nhưng chưa được thực thi) được đánh dấu bằng mũi tên màu xanh lá cây trên các trường có liệt kê.
Nó được chứng minh thêm trong mã cách các biến này có thể được sử dụng theo cách đơn giản nhất trong các toán tử gán. Giá trị của biến toàn cục n
được sao chép vào x
cục bộ mà không có bất kỳ vấn đề nào vì n
đã được khởi tạo. Tuy nhiên, trong chuỗi mà nội dung của biến y
được sao chép vào biến z
, một cảnh báo từ trình biên dịch xuất hiện, vì y
là cục bộ và, cho đến thời điểm này, không có gì được viết trong đó; tức là, không có khởi tạo rõ ràng, cũng như các toán tử khác có thể đặt giá trị của nó.
Bên trong một hàm, được phép mô tả các biến có cùng tên như đã sử dụng cho các biến toàn cục. Một tình huống tương tự có thể xảy ra trong các khối cục bộ lồng nhau nếu một biến được tạo trong một khối nội bộ với tên tồn tại trong một khối bên ngoài. Tuy nhiên, cách thực hành này không được khuyến khích, vì nó có thể dẫn đến lỗi logic. Trong những trường hợp như vậy, trình biên dịch sẽ đưa ra cảnh báo ("declaration hides global/local variable"-"khai báo ẩn biến toàn cục/cục bộ").
Do định nghĩa lại như vậy, một biến cục bộ, chẳng hạn như k
trong ví dụ trên, chồng lên biến toàn cục đồng âm bên trong hàm. Mặc dù chúng có cùng tên, nhưng đây là hai biến khác nhau. Biến cục bộ k
được biết đến bên trong OnStart
, trong khi biến toàn cục k
được biết đến ở mọi nơi ngoại trừ OnStart
. Nói cách khác, bất kỳ hoạt động nào bên trong khối với biến k
sẽ chỉ ảnh hưởng đến biến cục bộ. Do đó, khi thoát khỏi hàm OnStart
(như thể nó không phải là hàm duy nhất và cốt lõi của tập lệnh), chúng ta sẽ phát hiện ra rằng biến toàn cục k
vẫn bằng không.
Biến cục bộ j
không chỉ chồng lên biến toàn cục j
mà còn được khởi tạo bởi giá trị của biến sau. Trong chuỗi chứa mô tả của j
bên trong OnStart
, phiên bản cục bộ của j
vẫn đang được tạo khi giá trị ban đầu cho nó được đọc từ phiên bản toàn cục của j
. Khi định nghĩa thành công j
cục bộ, tên này chồng lên phiên bản toàn cục và đó là phiên bản cục bộ mà các thay đổi tiếp theo trong j
thuộc về.
Ở cuối mã nguồn, chúng ta đã bình luận về nỗ lực khai báo thêm một biến toàn cục nữa, tệ, trong quá trình khởi tạo, giá trị của biến x
được gọi. Chuỗi này gây ra lỗi biên dịch vì biến x
không được biết đến ngoài hàm OnStart
, trong đó nó đã được định nghĩa.