Tôi sẽ diễn giải câu hỏi của bạn thành hai câu hỏi: 1) tại sao ->
thậm chí tồn tại và 2) tại sao .
không tự động hủy bỏ con trỏ. Câu trả lời cho cả hai câu hỏi có nguồn gốc lịch sử.
Tại sao ->
thậm chí tồn tại?
Trong một trong những phiên bản đầu tiên của ngôn ngữ C (mà tôi sẽ gọi là CRM cho " Hướng dẫn tham khảo C ", đi kèm với Unix phiên bản thứ 6 vào tháng 5 năm 1975), nhà điều hành ->
có ý nghĩa rất riêng, không đồng nghĩa *
và .
kết hợp
Ngôn ngữ C được mô tả bởi CRM rất khác so với ngôn ngữ C hiện đại ở nhiều khía cạnh. Trong CRM, các thành viên cấu trúc đã triển khai khái niệm toàn cầu về bù byte , có thể được thêm vào bất kỳ giá trị địa chỉ nào mà không hạn chế kiểu. Tức là tất cả tên của tất cả các thành viên cấu trúc có ý nghĩa toàn cầu độc lập (và, do đó, phải là duy nhất). Ví dụ bạn có thể khai báo
struct S {
int a;
int b;
};
và tên a
sẽ là viết tắt của offset 0, trong khi tên b
sẽ đại diện cho offset 2 (giả sử int
loại kích thước 2 và không có phần đệm). Ngôn ngữ yêu cầu tất cả các thành viên của tất cả các cấu trúc trong đơn vị dịch phải có tên duy nhất hoặc viết tắt cho cùng một giá trị offset. Ví dụ: trong cùng một đơn vị dịch bạn có thể khai báo thêm
struct X {
int a;
int x;
};
và điều đó sẽ ổn, vì tên a
sẽ luôn thay cho offset 0. Nhưng tuyên bố bổ sung này
struct Y {
int b;
int a;
};
sẽ chính thức không hợp lệ, vì nó đã cố gắng "xác định lại" a
là offset 2 và b
là offset 0.
Và đây là nơi mà ->
toán tử xuất hiện. Vì mỗi tên thành viên cấu trúc có ý nghĩa toàn cầu tự cung cấp riêng, ngôn ngữ hỗ trợ các biểu thức như thế này
int i = 5;
i->b = 42; /* Write 42 into `int` at address 7 */
100->a = 0; /* Write 0 into `int` at address 100 */
Nhiệm vụ đầu tiên được trình biên dịch diễn giải là "lấy địa chỉ 5
, thêm offset 2
cho nó và gán 42
cho int
giá trị tại địa chỉ kết quả". Tức là ở trên sẽ gán 42
cho int
giá trị tại địa chỉ 7
. Lưu ý rằng việc sử dụng ->
này không quan tâm đến loại biểu thức ở phía bên trái. Phía bên trái được hiểu là một địa chỉ số giá trị (có thể là một con trỏ hoặc một số nguyên).
Loại mánh khóe này là không thể với *
và .
kết hợp. Bạn không thể làm
(*i).b = 42;
vì *i
đã là một biểu thức không hợp lệ. Các *
nhà điều hành, vì nó hoàn toàn tách biệt .
, đặt ra yêu cầu loại nghiêm ngặt hơn về toán hạng của nó. Để cung cấp khả năng giải quyết giới hạn này, CRM đã giới thiệu ->
toán tử, độc lập với loại toán hạng bên trái.
Như Keith đã lưu ý trong các nhận xét, sự khác biệt giữa ->
và *
+ .
kết hợp này là điều mà CRM đang đề cập đến là "thư giãn yêu cầu" trong 7.1.8: Ngoại trừ việc nới lỏng yêu cầu E1
thuộc loại con trỏ, biểu thức E1−>MOS
hoàn toàn tương đương với(*E1).MOS
Sau đó, trong K & R C, nhiều tính năng được mô tả ban đầu trong CRM đã được làm lại đáng kể. Ý tưởng về "thành viên cấu trúc như định danh bù toàn cầu" đã bị loại bỏ hoàn toàn. Và chức năng của ->
toán tử trở nên hoàn toàn giống với chức năng *
và .
sự kết hợp.
Tại sao không thể .
tự động hóa con trỏ?
Một lần nữa, trong phiên bản CRM của ngôn ngữ, toán hạng bên trái của .
toán tử được yêu cầu phải là một giá trị . Đó là yêu cầu duy nhất được áp dụng cho toán hạng đó (và đó là điều làm cho nó khác với ->
, như đã giải thích ở trên). Lưu ý rằng CRM không yêu cầu toán hạng bên trái .
phải có kiểu cấu trúc. Nó chỉ yêu cầu nó phải là một giá trị, bất kỳ giá trị nào . Điều này có nghĩa là trong phiên bản CRM của C, bạn có thể viết mã như thế này
struct S { int a, b; };
struct T { float x, y, z; };
struct T c;
c.b = 55;
Trong trường hợp này, trình biên dịch sẽ ghi 55
vào một int
giá trị được định vị ở byte-offset 2 trong khối bộ nhớ liên tục được gọi là c
, mặc dù kiểu struct T
không có trường có tên b
. Trình biên dịch sẽ không quan tâm đến loại thực tế c
. Tất cả những gì nó quan tâm là đó c
là một giá trị: một loại khối bộ nhớ có thể ghi.
Bây giờ lưu ý rằng nếu bạn đã làm điều này
S *s;
...
s.b = 42;
mã sẽ được coi là hợp lệ (vì s
cũng là một giá trị) và trình biên dịch chỉ đơn giản là cố gắng ghi dữ liệu vào s
chính con trỏ , ở byte-offset 2. Không cần phải nói, những thứ như thế này có thể dễ dàng dẫn đến tràn bộ nhớ, nhưng ngôn ngữ đã không quan tâm đến nó với những vấn đề như vậy.
Tức là trong phiên bản ngôn ngữ mà ý tưởng đề xuất của bạn về quá tải toán tử .
cho các loại con trỏ sẽ không hoạt động: toán tử .
đã có ý nghĩa rất cụ thể khi được sử dụng với các con trỏ (với các con trỏ lvalue hoặc với bất kỳ giá trị nào). Đó là chức năng rất kỳ lạ, không có nghi ngờ. Nhưng nó đã ở đó vào lúc đó.
Tất nhiên, chức năng kỳ lạ này không phải là lý do rất mạnh để chống lại việc giới thiệu .
toán tử quá tải cho con trỏ (như bạn đề xuất) trong phiên bản làm lại của C - K & R C. Nhưng nó đã không được thực hiện. Có thể tại thời điểm đó, có một số mã kế thừa được viết bằng phiên bản C của CRM phải được hỗ trợ.
(URL cho Hướng dẫn tham khảo 1975 C có thể không ổn định. Một bản sao khác, có thể có một số khác biệt tinh tế, có ở đây .)