Kiểu lồng nhau, không gian tên và toán tử ngữ cảnh ::
Các lớp, cấu trúc và liên hợp không chỉ có thể được mô tả trong ngữ cảnh toàn cục mà còn bên trong một lớp hoặc cấu trúc khác. Thậm chí hơn thế nữa: việc định nghĩa có thể được thực hiện bên trong một hàm. Điều này cho phép bạn mô tả tất cả các thực thể cần thiết cho hoạt động của bất kỳ lớp hoặc cấu trúc nào trong ngữ cảnh phù hợp, từ đó tránh được các xung đột tên tiềm ẩn.
Cụ thể, trong chương trình vẽ, cấu trúc dùng để lưu trữ tọa độ Pair
cho đến nay đã được định nghĩa toàn cục. Khi chương trình phát triển, rất có thể một thực thể khác tên là Pair
sẽ cần thiết (đặc biệt với cái tên khá chung chung như vậy). Do đó, nên chuyển mô tả của cấu trúc này vào bên trong lớp Shape
(Shapes6.mq5
).
class Shape
{
public:
struct Pair
{
int x, y;
Pair(int a, int b): x(a), y(b) { }
};
...
};
2
3
4
5
6
7
8
9
10
Các mô tả lồng nhau có quyền truy cập theo các bộ sửa đổi phần được chỉ định. Trong trường hợp này, chúng ta đã đặt tên Pair
ở mức công khai. Bên trong lớp Shape
, việc xử lý kiểu cấu trúc Pair
không thay đổi theo bất kỳ cách nào do việc chuyển đổi. Tuy nhiên, trong mã bên ngoài, bạn phải chỉ định tên đầy đủ bao gồm tên của lớp bên ngoài (ngữ cảnh), toán tử chọn ngữ cảnh ::
và định danh của thực thể bên trong. Ví dụ, để mô tả một biến với một cặp tọa độ, bạn sẽ viết:
Shape::Pair coordinates(0, 0);
Mức độ lồng nhau khi mô tả các thực thể không bị giới hạn, vì vậy một tên đầy đủ có thể chứa nhiều định danh của các mức (ngữ cảnh) được phân tách bằng ::
. Chẳng hạn, chúng ta có thể bao bọc tất cả các lớp vẽ bên trong lớp bên ngoài Drawing
, trong phần public
.
class Drawing
{
public:
class Shape
{
public:
struct Pair
{
...
};
};
class Rectangle : public Shape
{
...
};
...
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Sau đó, các tên kiểu đầy đủ (ví dụ, để sử dụng trong OnStart
hoặc các hàm bên ngoài khác) sẽ dài hơn:
Drawing::Shape::Rect coordinates(0, 0);
Drawing::Rectangle rect(200, 100, 70, 50, clrBlue);
2
Một mặt, điều này gây bất tiện, nhưng mặt khác, đôi khi đó là điều cần thiết trong các dự án lớn với số lượng lớp đông đảo. Trong dự án nhỏ của chúng ta, cách tiếp cận này chỉ được sử dụng để thể hiện tính khả thi về mặt kỹ thuật.
Để nhóm các lớp và cấu trúc liên quan logic thành các nhóm có tên, MQL5 cung cấp một cách dễ dàng hơn so với việc bao gồm chúng trong một lớp bao bọc "trống".
Một không gian tên được khai báo bằng từ khóa namespace
theo sau là tên và một khối dấu ngoặc nhọn bao gồm tất cả các định nghĩa cần thiết. Dưới đây là cách chương trình vẽ tương tự trông như thế nào khi sử dụng namespace
:
namespace Drawing
{
class Shape
{
public:
struct Pair
{
...
};
};
class Rectangle : public Shape
{
...
};
...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Có hai điểm khác biệt chính: nội dung bên trong không gian luôn có sẵn công khai (các bộ sửa đổi truy cập không áp dụng trong đó) và không có dấu chấm phẩy sau dấu ngoặc nhọn đóng.
Hãy thêm phương thức move
vào lớp Shape
, phương thức này nhận cấu trúc Pair
làm tham số:
class Shape
{
public:
...
Shape *move(const Pair &pair)
{
coordinates.x += pair.x;
coordinates.y += pair.y;
return &this;
}
};
2
3
4
5
6
7
8
9
10
11
Sau đó, trong hàm OnStart
, bạn có thể tổ chức việc dịch chuyển tất cả các hình dạng theo một giá trị nhất định bằng cách gọi hàm này:
void OnStart()
{
// vẽ một tập hợp các hình dạng ngẫu nhiên
for(int i = 0; i < 10; ++i)
{
Drawing::Shape *shape = addRandomShape();
// di chuyển tất cả các hình dạng
shape.move(Drawing::Shape::Pair(100, 100));
shape.draw();
delete shape;
}
}
2
3
4
5
6
7
8
9
10
11
12
Lưu ý rằng các kiểu Shape
và Pair
phải được mô tả bằng tên đầy đủ: Drawing::Shape
và Drawing::Shape::Pair
tương ứng.
Có thể có nhiều khối với cùng tên không gian: tất cả nội dung của chúng sẽ thuộc về một ngữ cảnh logic thống nhất với tên đã chỉ định.
Các định danh được định nghĩa trong ngữ cảnh toàn cục, đặc biệt là tất cả các hàm tích hợp của API MQL5, cũng có thể truy cập thông qua toán tử chọn ngữ cảnh không có ký hiệu nào đứng trước. Ví dụ, đây là cách một lời gọi đến hàm Print
có thể trông như thế nào:
::Print("Done!");
Khi lời gọi được thực hiện từ bất kỳ hàm nào được định nghĩa trong ngữ cảnh toàn cục, không cần thiết phải ghi như vậy.
Nhu cầu có thể biểu hiện bên trong bất kỳ lớp hoặc cấu trúc nào nếu một phần tử cùng tên (hàm, biến hoặc hằng số) được định nghĩa trong chúng. Ví dụ, hãy thêm phương thức Print
vào lớp Shape
:
static void Print(string x)
{
// trống
// (có thể sẽ xuất ra một tệp nhật ký riêng sau này)
}
2
3
4
5
Vì các triển khai thử nghiệm của phương thức draw
trong các lớp dẫn xuất gọi Print
, giờ đây chúng được chuyển hướng đến phương thức Print
này: từ nhiều định danh giống nhau, trình biên dịch chọn cái được định nghĩa trong ngữ cảnh gần hơn. Trong trường hợp này, định nghĩa trong lớp cơ sở gần với các hình dạng hơn ngữ cảnh toàn cục. Kết quả là, đầu ra nhật ký từ các lớp hình dạng sẽ bị chặn.
Tuy nhiên, việc gọi Print
từ hàm OnStart
vẫn hoạt động (vì nó nằm ngoài ngữ cảnh của lớp Shape
).
void OnStart()
{
...
Print("Done!");
}
2
3
4
5
Để "sửa" việc in gỡ lỗi trong các lớp, bạn cần đặt trước tất cả các lời gọi Print
bằng toán tử chọn ngữ cảnh toàn cục:
class Rectangle : public Shape
{
...
void draw() override
{
::Print("Drawing rectangle"); // in lại qua Print(...) toàn cục
}
};
2
3
4
5
6
7
8