Tự tham chiếu: this
Trong bối cảnh của mỗi lớp, trong mã của các phương thức của nó, có một tham chiếu đặc biệt đến đối tượng hiện tại: this
. Về cơ bản, đây là một biến được định nghĩa ngầm. Tất cả các phương thức làm việc với các biến đối tượng đều có thể áp dụng cho nó. Đặc biệt, nó có thể được giải tham chiếu để tham chiếu đến một trường của đối tượng hoặc để gọi một phương thức. Ví dụ, các câu lệnh sau trong một phương thức của lớp Shape
là giống nhau (chúng ta sử dụng phương thức draw
chỉ để minh họa):
class Shape
{
...
void draw()
{
backgroundColor = clrBlue;
this.backgroundColor = clrBlue;
}
};
2
3
4
5
6
7
8
9
Có thể cần sử dụng dạng dài nếu trong cùng ngữ cảnh có các biến/tham số khác cùng tên. Thực hành này thường không được khuyến khích, nhưng nếu cần thiết, từ khóa this
cho phép bạn tham chiếu đến các thành viên bị che khuất của đối tượng.
Trình biên dịch sẽ đưa ra cảnh báo nếu tên của bất kỳ biến cục bộ hoặc tham số phương thức nào trùng với tên của một biến thành viên của lớp.
Trong ví dụ giả định sau, chúng ta đã triển khai phương thức draw
, phương thức này nhận một tham số chuỗi tùy chọn backgroundColor
với tên màu. Vì tên tham số trùng với thành viên của lớp Shape
, trình biên dịch sẽ đưa ra cảnh báo đầu tiên: "định nghĩa của backgroundColor
che giấu trường".
Hậu quả của sự trùng lặp này là việc gán giá trị clrBlue
sau đó bị lỗi, hoạt động trên tham số chứ không phải trên thành viên của lớp, và vì kiểu giá trị và kiểu tham số không khớp, trình biên dịch sẽ đưa ra cảnh báo thứ hai: "chuyển đổi ngầm từ số sang chuỗi" (số ở đây là hằng số clrBlue
). Nhưng dòng this.backgroundColor = clrBlue
ghi giá trị vào trường của đối tượng.
void draw(string backgroundColor = NULL) // cảnh báo 1:
// khai báo của 'backgroundColor' che giấu thành viên
{
...
backgroundColor = clrBlue; // cảnh báo 2:
// chuyển đổi ngầm từ 'number' sang 'string'
this.backgroundColor = clrBlue; // ok
{
bool backgroundColor = false; // cảnh báo 3:
// khai báo của 'backgroundColor' che giấu biến cục bộ
...
this.backgroundColor = clrRed; // ok
}
...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Việc định nghĩa tiếp theo của biến boolean cục bộ backgroundColor
(trong khối lồng của dấu ngoặc nhọn) lại che giấu các định nghĩa trước đó của tên này một lần nữa (đó là lý do chúng ta nhận được cảnh báo thứ ba). Tuy nhiên, bằng cách giải tham chiếu this
, câu lệnh this.backgroundColor = clrRed
cũng tham chiếu đến một trường của đối tượng.
Nếu không chỉ định this
, trình biên dịch luôn chọn định nghĩa tên gần nhất (theo ngữ cảnh).
Cũng có một nhu cầu khác đối với this
: để truyền đối tượng hiện tại làm tham số cho một hàm khác. Cụ thể, một cách tiếp cận được thực hiện trong đó các đối tượng của cùng một lớp chịu trách nhiệm tạo/xóa các đối tượng của một lớp khác, và đối tượng phụ thuộc phải biết "ông chủ" của nó. Sau đó, các đối tượng phụ thuộc được tạo trong lớp "ông chủ" bằng cách sử dụng hàm tạo, và this
của đối tượng "ông chủ" được truyền vào đó. Kỹ thuật này thường sử dụng phân bổ đối tượng động và con trỏ, và vì lý do này, một ví dụ liên quan sẽ được trình bày trong phần con trỏ.
Một cách sử dụng phổ biến khác của this
là trả về một con trỏ đến đối tượng hiện tại từ một hàm thành viên. Điều này cho phép sắp xếp các lời gọi hàm thành viên theo chuỗi. Vì chúng ta chưa nghiên cứu chi tiết về con trỏ, chỉ cần biết rằng một con trỏ đến một đối tượng của một lớp nào đó được mô tả bằng cách thêm ký tự *
vào tên lớp, và bạn có thể làm việc với một đối tượng thông qua con trỏ theo cách tương tự như làm trực tiếp.
Ví dụ, chúng ta có thể cung cấp cho người dùng một số phương thức để đặt các thuộc tính của một hình dạng riêng lẻ: thay đổi màu sắc, di chuyển theo chiều ngang hoặc chiều dọc. Mỗi phương thức trong số chúng sẽ trả về một con trỏ đến đối tượng hiện tại.
Shape *setColor(const color c)
{
backgroundColor = c;
return &this;
}
Shape *moveX(const int x)
{
coordinates.x += x;
return &this;
}
Shape *moveY(const int y)
{
coordinates.y += y;
return &this;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Sau đó, có thể sắp xếp các lời gọi đến các phương thức này theo chuỗi một cách thuận tiện.
Shape s;
s.setColor(clrWhite).moveX(80).moveY(-50);
2
Khi một lớp có nhiều thuộc tính, cách tiếp cận này cho phép cấu hình một đối tượng một cách gọn gàng và chọn lọc.
Trong phần Định nghĩa lớp, chúng ta đã cố gắng ghi log một biến đối tượng nhưng phát hiện ra rằng chúng ta có thể sử dụng tên của nó chỉ với một dấu &
(trong một lời gọi Print
) để lấy một con trỏ, hoặc thực tế là một số duy nhất (handle). Trong ngữ cảnh đối tượng, cùng một handle có sẵn thông qua &this
.
Để mục đích gỡ lỗi, bạn có thể xác định các đối tượng bằng bộ mô tả của chúng. Chúng ta sẽ khám phá tính kế thừa lớp, và khi có nhiều hơn một đối tượng như vậy, việc xác định sẽ rất hữu ích. Vì lý do đó, trong tất cả các hàm tạo và hàm hủy, chúng ta thêm (và sẽ thêm trong tương lai trong các lớp dẫn xuất) lời gọi Print
sau:
~Shape()
{
Print(__FUNCSIG__, " ", &this);
}
2
3
4
Bây giờ, tất cả các bước tạo và xóa sẽ được đánh dấu trong log với tên lớp và số đối tượng.
Chúng ta triển khai các hàm tạo và hàm hủy tương tự trong cấu trúc Pair
, tuy nhiên trong cấu trúc, thật không may, con trỏ không được hỗ trợ, tức là việc viết &this
là không thể. Do đó, chúng ta chỉ có thể xác định chúng bằng nội dung của chúng (trong trường hợp này, bằng tọa độ của chúng):
struct Pair
{
int x, y;
Pair(int a, int b): x(a), y(b)
{
Print(__FUNCSIG__, " ", x, " ", y);
}
...
};
2
3
4
5
6
7
8
9