Với mảng, tại sao trường hợp [5] == 5 [a] lại xảy ra?


1622

Như Joel chỉ ra trong podcast Stack Overflow # 34 , trong Ngôn ngữ lập trình C (còn gọi là: K & R), có đề cập đến thuộc tính này của mảng trong C:a[5] == 5[a]

Joel nói rằng đó là do số học con trỏ nhưng tôi vẫn không hiểu. Tại sao khônga[5] == 5[a] ?


48
một cái gì đó giống như [+] cũng hoạt động như * (a ++) HOẶC * (++ a)?
Egon

45
@Egon: Điều đó rất sáng tạo nhưng thật không may, đó không phải là cách trình biên dịch hoạt động. Trình biên dịch diễn giải a[1]thành một chuỗi các mã thông báo, không phải chuỗi: * ({vị trí số nguyên của} a {toán tử} + {số nguyên} 1) giống với * ({số nguyên} 1 {toán tử} + {vị trí số nguyên của} a) nhưng không giống với * ({vị trí số nguyên của} a {toán tử} + {toán tử} +)
Dinah

11
Một biến thể hợp chất thú vị về điều này được minh họa trong truy cập mảng Illogical , nơi bạn có char bar[]; int foo[];foo[i][bar]được sử dụng như một biểu thức.
Jonathan Leffler

5
@EldritchConundrum, tại sao bạn nghĩ rằng 'trình biên dịch không thể kiểm tra xem phần bên trái có phải là con trỏ' không? Vâng, nó có thể. Đúng là a[b]= *(a + b)cho bất kỳ được cho ab, nhưng đó là lựa chọn miễn phí của các nhà thiết kế ngôn ngữ +để được xác định giao hoán cho tất cả các loại. Không có gì có thể ngăn họ cấm i + ptrong khi cho phép p + i.
ach

13
@Andrey Một người thường mong đợi +được giao hoán, vì vậy có lẽ vấn đề thực sự là chọn làm cho các phép toán con trỏ giống với số học, thay vì thiết kế một toán tử bù riêng.
Câu hỏi hóc búa Eldritch

Câu trả lời:


1924

Tiêu chuẩn C định nghĩa []toán tử như sau:

a[b] == *(a + b)

Do đó a[5]sẽ đánh giá để:

*(a + 5)

5[a]sẽ đánh giá để:

*(5 + a)

alà một con trỏ đến phần tử đầu tiên của mảng. a[5]là giá trị mà 5 yếu tố khác a, tương tự *(a + 5), và từ toán tiểu học, chúng ta biết chúng là những yếu tố bằng nhau (bổ sung là giao hoán ).


325
Tôi tự hỏi nếu nó không giống * ((5 * sizeof (a)) + a). Giải thích tuyệt vời mặc dù.
John MacIntyre

92
@Dinah: Từ góc độ trình biên dịch C, bạn đã đúng. Không có sizeof là cần thiết và những biểu thức tôi đã đề cập là CÙNG. Tuy nhiên, trình biên dịch sẽ tính đến sizeof khi sản xuất mã máy. Nếu a là một mảng int, a[5]sẽ biên dịch thành một thứ giống như mov eax, [ebx+20]thay vì[ebx+5]
Mehrdad Afshari

12
@Dinah: A là một địa chỉ, giả sử 0x1230. Nếu a nằm trong mảng int 32 bit, thì [0] ở 0x1230, [1] ở 0x1234, [2] tại 0x1238 ... a [5] tại x1244, v.v. Nếu chúng ta chỉ cần thêm 5 vào 0x1230, chúng tôi nhận được 0x1235, đó là sai.
James Curran

36
@ sr105: Đó là trường hợp đặc biệt cho toán tử +, trong đó một trong các toán hạng là một con trỏ và một số nguyên khác. Tiêu chuẩn nói rằng kết quả sẽ thuộc loại con trỏ. Trình biên dịch / phải là / đủ thông minh.
aib

48
"Từ toán tiểu học, chúng tôi biết chúng là như nhau" - Tôi hiểu rằng bạn đang đơn giản hóa, nhưng tôi với những người cảm thấy như thế này là quá đơn giản. Nó không phải là tiểu học mà *(10 + (int *)13) != *((int *)10 + 13). Nói cách khác, có nhiều thứ đang diễn ra ở đây hơn số học tiểu học. Giao hoán phụ thuộc rất nhiều vào trình biên dịch nhận ra toán hạng nào là con trỏ (và kích thước của đối tượng). Nói cách khác (1 apple + 2 oranges) = (2 oranges + 1 apple), nhưng (1 apple + 2 oranges) != (1 orange + 2 apples).
LarsH

288

Bởi vì truy cập mảng được định nghĩa theo các con trỏ. a[i]được định nghĩa là có nghĩa *(a + i)là giao hoán.


42
Mảng không được định nghĩa dưới dạng con trỏ, nhưng truy cập vào chúng là.
Các cuộc đua nhẹ nhàng trong quỹ đạo

5
Tôi sẽ thêm "để nó bằng *(i + a), có thể được viết là i[a]".
Jim Balter

4
Tôi muốn đề nghị bạn bao gồm trích dẫn từ tiêu chuẩn, như sau: 6.5.2.1: 2 Một biểu thức hậu tố theo sau là một biểu thức trong ngoặc vuông [] là một chỉ định được ký hiệu của một phần tử của một đối tượng mảng. Định nghĩa của toán tử đăng ký [] là E1 [E2] giống hệt với (* ((E1) + (E2))). Do các quy tắc chuyển đổi áp dụng cho toán tử nhị phân +, nếu E1 là một đối tượng mảng (tương đương, một con trỏ tới phần tử ban đầu của một đối tượng mảng) và E2 là một số nguyên, thì E1 [E2] chỉ định phần tử thứ 2 của E1 (tính từ 0).
Vality 17/2/2015

Để chính xác hơn: Mảng phân rã thành con trỏ khi bạn truy cập chúng.
12431234123412341234123

Nitpick: Không có nghĩa gì khi nói rằng " *(a + i)là giao hoán". Tuy nhiên, *(a + i) = *(i + a) = i[a]bổ sung là giao hoán.
Andreas Rejbrand

231

Tôi nghĩ rằng một cái gì đó đang bị bỏ lỡ bởi các câu trả lời khác.

Có, p[i]theo định nghĩa tương đương với *(p+i), mà (vì bổ sung là giao hoán) tương đương với *(i+p), mà (một lần nữa, theo định nghĩa của []toán tử) là tương đương với i[p].

(Và trong array[i], tên mảng được chuyển đổi hoàn toàn thành một con trỏ thành phần tử đầu tiên của mảng.)

Nhưng tính giao hoán của bổ sung không phải là tất cả rõ ràng trong trường hợp này.

Khi cả hai toán hạng đều cùng loại hoặc thậm chí là các loại số khác nhau được thăng cấp thành một loại phổ biến, giao hoán có ý nghĩa hoàn hảo : x + y == y + x.

Nhưng trong trường hợp này, chúng ta đang nói cụ thể về số học con trỏ, trong đó một toán hạng là một con trỏ và cái còn lại là một số nguyên. (Số nguyên + số nguyên là một hoạt động khác nhau và con trỏ + con trỏ là vô nghĩa.)

Mô tả của tiêu chuẩn C về +toán tử ( N1570 6.5.6) cho biết:

Ngoài ra, cả hai toán hạng sẽ có kiểu số học hoặc một toán hạng sẽ là một con trỏ tới một loại đối tượng hoàn chỉnh và một toán hạng khác sẽ có kiểu số nguyên.

Nó có thể dễ dàng như đã nói:

Ngoài ra, cả hai toán hạng sẽ có kiểu số học hoặc toán hạng bên trái sẽ là một con trỏ tới một loại đối tượng hoàn chỉnh và toán hạng bên phải sẽ có kiểu số nguyên.

trong trường hợp cả hai i + pi[p]sẽ là bất hợp pháp.

Theo thuật ngữ C ++, chúng tôi thực sự có hai bộ +toán tử quá tải , có thể được mô tả một cách lỏng lẻo là:

pointer operator+(pointer p, integer i);

pointer operator+(integer i, pointer p);

trong đó chỉ có đầu tiên là thực sự cần thiết.

Vậy tại sao nó lại theo cách này?

C ++ đã kế thừa định nghĩa này từ C, lấy từ B (tính giao hoán của việc lập chỉ mục mảng được đề cập rõ ràng trong Tài liệu tham khảo của người dùng năm 1972 đến B ), lấy từ BCPL (hướng dẫn sử dụng ngày 1967) ngôn ngữ trước đó (CPL? Algol?).

Vì vậy, ý tưởng rằng lập chỉ mục mảng được xác định theo thuật ngữ cộng, và phép cộng đó, thậm chí cả con trỏ và số nguyên, có tính giao hoán, quay trở lại nhiều thập kỷ, theo ngôn ngữ tổ tiên của C.

Những ngôn ngữ đó được gõ ít mạnh hơn nhiều so với C hiện đại. Cụ thể, sự khác biệt giữa con trỏ và số nguyên thường bị bỏ qua. (Lập trình viên C sớm đôi khi được dùng con trỏ như số nguyên unsigned, trước khi unsignedtừ khóa đã được thêm vào ngôn ngữ.) Vì vậy, ý tưởng thực hiện Ngoài ra phi giao hoán vì các toán hạng là các loại khác nhau có lẽ sẽ không xảy ra cho các nhà thiết kế trong những ngôn ngữ. Nếu người dùng muốn thêm hai "thứ", cho dù những "thứ đó" là số nguyên, con trỏ hay thứ gì khác, thì đó không phải là ngôn ngữ để ngăn chặn nó.

Và trong nhiều năm qua, bất kỳ thay đổi nào đối với quy tắc đó sẽ phá vỡ mã hiện có (mặc dù tiêu chuẩn ANSI C năm 1989 có thể là một cơ hội tốt).

Thay đổi C và / hoặc C ++ để yêu cầu đặt con trỏ ở bên trái và số nguyên ở bên phải có thể phá vỡ một số mã hiện có, nhưng sẽ không mất sức mạnh biểu cảm thực sự.

Vì vậy, bây giờ chúng ta có arr[3]3[arr]có nghĩa chính xác điều tương tự, mặc dù hình thức sau không bao giờ xuất hiện bên ngoài IOCCC .


12
Mô tả tuyệt vời của tài sản này. Từ quan điểm cấp cao, tôi nghĩ 3[arr]là một tạo tác thú vị nhưng hiếm khi nên được sử dụng. Câu trả lời được chấp nhận cho câu hỏi này (< stackoverflow.com/q/1390365/356> ) mà tôi đã hỏi một lúc trước đã thay đổi cách tôi nghĩ về cú pháp. Mặc dù về mặt kỹ thuật thường không phải là cách đúng và sai để làm những việc này, nhưng các loại tính năng này bắt đầu khiến bạn suy nghĩ theo cách tách biệt với các chi tiết triển khai. Có lợi ích cho cách suy nghĩ khác biệt này, một phần bị mất khi bạn sửa chữa các chi tiết thực hiện.
Dinah

3
Ngoài ra là giao hoán. Đối với tiêu chuẩn C để định nghĩa nó nếu không sẽ là lạ. Đó là lý do tại sao nó không thể dễ dàng nói như vậy "Ngoài ra, cả hai toán hạng sẽ có kiểu số học hoặc toán hạng bên trái sẽ là một con trỏ tới một loại đối tượng hoàn chỉnh và toán hạng bên phải sẽ có kiểu số nguyên." - Điều đó sẽ không có ý nghĩa với hầu hết những người thêm vào.
iheanyi

9
@iheanyi: Bổ sung thường là giao hoán - và nó thường mất hai toán hạng cùng loại. Ngoài ra con trỏ cho phép bạn thêm một con trỏ và một số nguyên, nhưng không phải là hai con trỏ. IMHO đã là một trường hợp đặc biệt đủ kỳ lạ đòi hỏi con trỏ phải là toán hạng bên trái sẽ không phải là một gánh nặng đáng kể. (Một số ngôn ngữ sử dụng "+" để nối chuỗi; điều đó chắc chắn không giao hoán.)
Keith Thompson

3
@supercat, Điều đó còn tồi tệ hơn. Điều đó có nghĩa là đôi khi x + 1! = 1 + x. Điều đó sẽ hoàn toàn vi phạm các tài sản liên kết của bổ sung.
iheanyi

3
@iheanyi: Tôi nghĩ bạn có nghĩa là tài sản giao hoán; Ngoài ra, không phải là kết hợp, vì trên hầu hết các triển khai (1LL + 1U) -2! = 1LL + (1U-2). Thật vậy, sự thay đổi sẽ khiến một số tình huống liên quan mà hiện tại không có, ví dụ 3U + (UINT_MAX-2L) sẽ bằng (3U + UINT_MAX) -2. Tuy nhiên, điều tốt nhất là ngôn ngữ sẽ thêm các loại khác biệt mới cho các số nguyên có thể quảng bá và các vòng đại số "bao bọc", do đó, việc thêm 2 vào một ring16_tsố giữ 65535 sẽ mang lại ring16_tgiá trị 1, không phụ thuộc vào kích thướcint .
supercat

196

Và dĩ nhiên

 ("ABCD"[2] == 2["ABCD"]) && (2["ABCD"] == 'C') && ("ABCD"[2] == 'C')

Lý do chính cho điều này là vào những năm 70 khi C được thiết kế, máy tính không có nhiều bộ nhớ (64KB là rất nhiều), vì vậy trình biên dịch C không thực hiện nhiều kiểm tra cú pháp. Do đó " X[Y]" được dịch một cách mù quáng thành " *(X+Y)"

Điều này cũng giải thích các cú pháp " +=" và " ++". Mọi thứ ở dạng " A = B + C" đều có dạng được biên dịch giống nhau. Nhưng, nếu B là cùng một đối tượng với A, thì tối ưu hóa mức lắp ráp đã có sẵn. Nhưng trình biên dịch không đủ sáng để nhận ra nó, vì vậy nhà phát triển phải ( A += C). Tương tự, nếu C1, một tối ưu hóa mức lắp ráp khác nhau đã có sẵn, và một lần nữa nhà phát triển phải làm cho nó rõ ràng, bởi vì trình biên dịch đã không nhận ra nó. (Gần đây trình biên dịch thực hiện, vì vậy những cú pháp này phần lớn không cần thiết trong những ngày này)


127
Trên thực tế, điều đó đánh giá là sai; thuật ngữ đầu tiên "ABCD" [2] == 2 ["ABCD"] ước tính là đúng, hoặc 1 và 1! = 'C': D
Jonathan Leffler

8
@Jonathan: sự mơ hồ tương tự dẫn đến việc chỉnh sửa tiêu đề gốc của bài đăng này. Chúng ta có phải là các dấu bằng nhau tương đương toán học, cú pháp mã hoặc mã giả. Tôi tranh luận về tính tương đương toán học nhưng vì chúng ta đang nói về mã, chúng ta không thể thoát khỏi việc chúng ta đang xem mọi thứ theo cú pháp mã.
Dinah

19
Đây không phải là một huyền thoại sao? Ý tôi là các toán tử + = và ++ đã được tạo để đơn giản hóa cho trình biên dịch? Một số mã trở nên rõ ràng hơn với chúng, và đó là cú pháp hữu ích để có, bất kể trình biên dịch làm gì với nó.
Thomas Padron-McCarthy

6
+ = và ++ có một lợi ích đáng kể khác. nếu phía bên trái thay đổi một số biến trong khi được đánh giá, thay đổi sẽ chỉ được thực hiện một lần. a = a + ...; sẽ làm điều đó hai lần.
Julian Schaub - litb

8
Không - "ABCD" [2] == * ("ABCD" + 2) = * ("CD") = 'C'. Dereferences một chuỗi cung cấp cho bạn một char, không phải là một chuỗi con
MSalters

55

Một điều dường như không ai đã đề cập về vấn đề của Dinah với sizeof:

Bạn chỉ có thể thêm một số nguyên vào một con trỏ, bạn không thể thêm hai con trỏ lại với nhau. Theo cách đó, khi thêm một con trỏ vào một số nguyên hoặc một số nguyên cho một con trỏ, trình biên dịch luôn biết bit nào có kích thước cần được tính đến.


1
Có một cuộc trò chuyện khá căng thẳng về điều này trong các bình luận của câu trả lời được chấp nhận. Tôi đã tham chiếu cuộc trò chuyện đã nói trong phần chỉnh sửa cho câu hỏi ban đầu nhưng không trực tiếp giải quyết mối quan tâm rất hợp lệ của bạn về sizeof. Không chắc chắn làm thế nào tốt nhất để làm điều này trong SO. Tôi có nên thực hiện một chỉnh sửa khác cho nguồn gốc. câu hỏi?
Dinah

50

Để trả lời câu hỏi theo nghĩa đen. Không phải lúc nào cũng đúngx == x

double zero = 0.0;
double a[] = { 0,0,0,0,0, zero/zero}; // NaN
cout << (a[5] == 5[a] ? "true" : "false") << endl;

in

false

27
Trên thực tế, một "nan" không bằng chính nó: cout << (a[5] == a[5] ? "true" : "false") << endl;false.
TrueY

8
@TrueY: Anh ấy đã nói rõ điều đó cho trường hợp NaN (và cụ thể x == xlà không phải lúc nào cũng đúng). Tôi nghĩ đó là ý định của anh ấy. Vì vậy, anh ấy đúng về mặt kỹ thuật (và có thể, như họ nói, loại tốt nhất chính xác!).
Tim Čas

3
Câu hỏi là về C, mã của bạn không phải là mã C. Ngoài ra còn có một NANtrong <math.h>, tốt hơn 0.0/0.0, bởi vì 0.0/0.0UB __STDC_IEC_559__không được xác định (Hầu hết các triển khai không xác định __STDC_IEC_559__, nhưng trên hầu hết các triển khai 0.0/0.0vẫn sẽ hoạt động)
12431234123412341234123

26

Tôi chỉ tìm ra cú pháp xấu xí này có thể là "hữu ích", hoặc ít nhất là rất thú vị để chơi khi bạn muốn xử lý một mảng các chỉ mục tham chiếu đến các vị trí trong cùng một mảng. Nó có thể thay thế dấu ngoặc vuông lồng nhau và làm cho mã dễ đọc hơn!

int a[] = { 2 , 3 , 3 , 2 , 4 };
int s = sizeof a / sizeof *a;  //  s == 5

for(int i = 0 ; i < s ; ++i) {  

           cout << a[a[a[i]]] << endl;
           // ... is equivalent to ... 
           cout << i[a][a][a] << endl;  // but I prefer this one, it's easier to increase the level of indirection (without loop)

}

Tất nhiên, tôi khá chắc chắn rằng không có trường hợp sử dụng nào cho mã thực đó, nhưng dù sao tôi cũng thấy nó thú vị :)


Khi bạn nhìn thấy i[a][a][a]bạn nghĩ tôi là một con trỏ tới một mảng hoặc một mảng của một con trỏ tới một mảng hoặc một mảng ... và alà một chỉ mục. Khi bạn nhìn thấy a[a[a[i]]], bạn nghĩ rằng một con trỏ đến một mảng hoặc một mảng và ilà một chỉ mục.
12431234123412341234123

1
Ồ Đó là cách sử dụng rất tuyệt vời của tính năng "ngu ngốc" này. Có thể hữu ích trong cuộc thi thuật toán trong một số vấn đề))
Serge Breusov

26

Câu hỏi / câu trả lời hay.

Chỉ muốn chỉ ra rằng các con trỏ và mảng C không giống nhau , mặc dù trong trường hợp này, sự khác biệt là không cần thiết.

Hãy xem xét các tuyên bố sau:

int a[10];
int* p = a;

Trong đó a.out, ký hiệu anằm ở một địa chỉ là điểm bắt đầu của mảng và ký hiệu pnằm ở địa chỉ nơi con trỏ được lưu trữ và giá trị của con trỏ tại vị trí bộ nhớ đó là điểm bắt đầu của mảng.


2
Không, về mặt kỹ thuật chúng không giống nhau. Nếu bạn định nghĩa một số b là int * const và làm cho nó trỏ đến một mảng, thì nó vẫn là một con trỏ, có nghĩa là trong bảng ký hiệu, b đề cập đến một vị trí bộ nhớ lưu địa chỉ, lần lượt trỏ đến vị trí của mảng .
PolyThinker

4
Điểm rất tốt. Tôi nhớ có một lỗi rất khó chịu khi tôi định nghĩa một biểu tượng toàn cầu là char s [100] trong một mô-đun, khai báo nó là char bên ngoài; trong một mô-đun khác. Sau khi liên kết tất cả lại với nhau, chương trình đã hành xử rất kỳ lạ. Bởi vì mô-đun sử dụng khai báo extern đã sử dụng các byte ban đầu của mảng làm con trỏ tới char.
Giorgio

1
Ban đầu, trong BCPL ông bà của C, một mảng là một con trỏ. Đó là, những gì bạn nhận được khi bạn viết (tôi đã phiên âm sang C) int a[10]là một con trỏ gọi là 'a', chỉ ra đủ lưu trữ cho 10 số nguyên, ở nơi khác. Do đó a + i và j + i có cùng dạng: thêm nội dung của một vài vị trí bộ nhớ. Trên thực tế, tôi nghĩ BCPL là không đánh máy, vì vậy chúng giống hệt nhau. Và tỷ lệ kiểu sizeof không được áp dụng, vì BCPL hoàn toàn hướng từ (trên máy cũng có địa chỉ từ).
dave

Tôi nghĩ cách tốt nhất để hiểu sự khác biệt là so sánh int*p = a;với int b = 5; Trong phần sau, "b" và "5" đều là số nguyên, nhưng "b" là một biến, trong khi "5" là một giá trị cố định. Tương tự, "p" & "a" đều là địa chỉ của một ký tự, nhưng "a" là một giá trị cố định.
James Curran

20

Đối với con trỏ trong C, chúng ta có

a[5] == *(a + 5)

và cũng

5[a] == *(5 + a)

Do đó đúng là a[5] == 5[a].


15

Không phải là một câu trả lời, nhưng chỉ là một số thực phẩm cho suy nghĩ. Nếu lớp đang có toán tử chỉ mục / chỉ mục bị quá tải, biểu thức 0[x]sẽ không hoạt động:

class Sub
{
public:
    int operator [](size_t nIndex)
    {
        return 0;
    }   
};

int main()
{
    Sub s;
    s[0];
    0[s]; // ERROR 
}

Vì chúng tôi không có quyền truy cập vào lớp int , điều này không thể được thực hiện:

class int
{
   int operator[](const Sub&);
};

2
class Sub { public: int operator[](size_t nIndex) const { return 0; } friend int operator[](size_t nIndex, const Sub& This) { return 0; } };
Ben Voigt

1
Bạn đã thực sự thử biên dịch nó? Có tập hợp các toán tử không thể được thực hiện bên ngoài lớp (tức là các hàm không tĩnh)!
Ajay

3
Rất tiếc, bạn đã đúng. " operator[]sẽ là một hàm thành viên không tĩnh với chính xác một tham số." Tôi đã quen với hạn chế đó operator=, không nghĩ rằng nó được áp dụng [].
Ben Voigt

1
Tất nhiên, nếu bạn thay đổi định nghĩa về []toán tử, nó sẽ không bao giờ tương đương nữa ... nếu a[b]bằng *(a + b)và bạn thay đổi điều này, bạn cũng sẽ phải quá tải int::operator[](const Sub&);intkhông phải là một lớp ...
Luis Colorado

7
Đây ... không phải ... C.
MD XF

11

Nó có lời giải thích rất hay trong MỘT HƯỚNG DẪN VỀ ĐIỂM VÀ ARRAYS TẠI C của Ted Jensen.

Ted Jensen giải thích nó như sau:

Trong thực tế, điều này là đúng, tức là bất cứ nơi nào người ta viết a[i]nó có thể được thay thế *(a + i) mà không có bất kỳ vấn đề. Trong thực tế, trình biên dịch sẽ tạo cùng một mã trong cả hai trường hợp. Do đó, chúng ta thấy rằng số học con trỏ giống như lập chỉ mục mảng. Hoặc là cú pháp tạo ra kết quả tương tự.

Điều này KHÔNG nói rằng con trỏ và mảng là như nhau, chúng không giống nhau. Chúng tôi chỉ nói rằng để xác định một phần tử nhất định của một mảng, chúng tôi có hai lựa chọn hai cú pháp, một sử dụng lập chỉ mục mảng và một sử dụng số học con trỏ, mang lại kết quả giống hệt nhau.

Bây giờ, nhìn vào biểu thức cuối cùng này, một phần của nó .. (a + i), là một bổ sung đơn giản bằng cách sử dụng toán tử + và các quy tắc của trạng thái C rằng biểu thức đó là giao hoán. Đó là (a + i) giống hệt (i + a). Vì vậy, chúng tôi có thể viết *(i + a)dễ dàng như *(a + i). Nhưng *(i + a)có thể đã đến từ i[a]! Từ tất cả những điều này xuất hiện sự thật tò mò rằng nếu:

char a[20];

viết

a[3] = 'x';

giống như viết

3[a] = 'x';

4
a + i KHÔNG phải là phép cộng đơn giản, vì đó là số học con trỏ. nếu kích thước của phần tử của a là 1 (char), thì có, nó giống như số nguyên +. Nhưng nếu đó là (ví dụ) một số nguyên, thì nó có thể tương đương với + 4 * i.
Alex Brown

@AlexBrown Vâng, đó là số học con trỏ, đó chính xác là lý do tại sao câu cuối cùng của bạn sai, trừ khi bạn lần đầu tiên 'a' thành một (char *) (giả sử rằng một int là 4 ký tự). Tôi thực sự không hiểu tại sao rất nhiều người bị treo lên về kết quả giá trị thực của số học con trỏ. Toàn bộ mục đích của số học con trỏ là trừu tượng hóa các giá trị con trỏ bên dưới và để lập trình viên nghĩ về các đối tượng bị thao túng thay vì giá trị địa chỉ.
jschultz410

8

Tôi biết câu hỏi đã được trả lời, nhưng tôi không thể cưỡng lại việc chia sẻ lời giải thích này.

Tôi nhớ Nguyên tắc thiết kế Trình biên dịch, Giả sử alà một intmảng và kích thước intlà 2 byte, và địa chỉ cơ sở alà 1000.

Làm thế nào a[5]sẽ làm việc ->

Base Address of your Array a + (5*size of(data type for array a))
i.e. 1000 + (5*2) = 1010

Vì thế,

Tương tự khi mã c được chia thành mã 3 địa chỉ, 5[a]sẽ trở thành ->

Base Address of your Array a + (size of(data type for array a)*5)
i.e. 1000 + (2*5) = 1010 

Vì vậy, về cơ bản cả hai câu lệnh đều trỏ đến cùng một vị trí trong bộ nhớ và do đó , a[5] = 5[a].

Giải thích này cũng là lý do tại sao các chỉ mục tiêu cực trong mảng hoạt động trong C.

tức là nếu tôi truy cập a[-5]nó sẽ cho tôi

Base Address of your Array a + (-5 * size of(data type for array a))
i.e. 1000 + (-5*2) = 990

Nó sẽ trả lại cho tôi đối tượng tại vị trí 990.


6

Trong mảng C , arr[3]3[arr]đều giống nhau, và ký hiệu con trỏ tương đương của họ là *(arr + 3)để *(3 + arr). Nhưng ngược lại [arr]3hoặc [3]arrkhông chính xác và sẽ dẫn đến lỗi cú pháp, vì (arr + 3)*(3 + arr)*không phải là biểu thức hợp lệ. Lý do là toán tử dereference nên được đặt trước địa chỉ mang lại bởi biểu thức, không phải sau địa chỉ.


6

trong trình biên dịch c

a[i]
i[a]
*(a+i)

là những cách khác nhau để chỉ một phần tử trong một mảng! (KHÔNG Ở TẤT CẢ TUẦN)


5

Một chút lịch sử bây giờ. Trong số các ngôn ngữ khác, BCPL có ảnh hưởng khá lớn đến sự phát triển ban đầu của C. Nếu bạn đã khai báo một mảng trong BCPL với một cái gì đó như:

let V = vec 10

mà thực sự phân bổ 11 từ bộ nhớ, không phải 10. Thông thường V là từ đầu tiên và chứa địa chỉ của từ ngay sau đó. Vì vậy, không giống như C, việc đặt tên V đã đi đến vị trí đó và chọn địa chỉ của phần tử zeroeth của mảng. Do đó, việc xác định mảng trong BCPL, được biểu thị bằng

let J = V!5

thực sự phải làm J = !(V + 5)(sử dụng cú pháp BCPL) vì cần phải tìm nạp V để lấy địa chỉ cơ sở của mảng. Như vậy V!55!Vđã đồng nghĩa. Là một quan sát tổng quát, WAFL (Ngôn ngữ chức năng của Warwick) được viết bằng BCPL và theo trí nhớ tốt nhất của tôi có xu hướng sử dụng cú pháp sau thay vì truy cập các nút được sử dụng làm lưu trữ dữ liệu. Cấp điều này là từ một nơi nào đó giữa 35 và 40 năm trước, vì vậy trí nhớ của tôi là một chút gỉ. :)

Sự đổi mới của việc phân phối với thêm từ lưu trữ và có trình biên dịch chèn địa chỉ cơ sở của mảng khi nó được đặt tên đến sau. Theo tài liệu lịch sử C, điều này đã xảy ra vào khoảng thời gian các cấu trúc được thêm vào C.

Lưu ý rằng !trong BCPL vừa là toán tử tiền tố đơn nguyên vừa là toán tử infix nhị phân, trong cả hai trường hợp thực hiện gián tiếp. chỉ là dạng nhị phân bao gồm một phép cộng của hai toán hạng trước khi thực hiện phép nối. Với tính chất định hướng từ của BCPL (và B), điều này thực sự có ý nghĩa rất lớn. Việc hạn chế "con trỏ và số nguyên" được thực hiện cần thiết trong C khi nó thu được các kiểu dữ liệu và sizeoftrở thành một thứ.


1

Vâng, đây là một tính năng chỉ có thể vì hỗ trợ ngôn ngữ.

Trình biên dịch diễn giải a[i]như *(a+i)và biểu thức 5[a]ước lượng thành *(5+a). Vì phép cộng là giao hoán nên hóa ra cả hai đều bằng nhau. Do đó biểu thức ước lượng true.


Mặc dù dư thừa nhưng điều này là rõ ràng, súc tích và ngắn gọn.
Bill K

0

Trong C

 int a[]={10,20,30,40,50};
 int *p=a;
 printf("%d\n",*p++);//output will be 10
 printf("%d\n",*a++);//will give an error

Con trỏ là một "biến"

tên mảng là "mnemonic" hoặc "synonymous"

p++;hợp lệ nhưng a++không hợp lệ

a[2] bằng 2 [a] vì hoạt động bên trong của cả hai điều này là

"Số học con trỏ" được tính toán bên trong như

*(a+3) bằng *(3+a)


-4

các loại con trỏ

1) con trỏ tới dữ liệu

int *ptr;

2) const con trỏ tới dữ liệu

int const *ptr;

3) const con trỏ tới dữ liệu const

int const *const ptr;

và các mảng là loại (2) từ danh sách của chúng tôi
Khi bạn xác định một mảng tại một thời điểm, một địa chỉ được khởi tạo trong con trỏ đó
Như chúng ta biết rằng chúng ta không thể thay đổi hoặc sửa đổi giá trị const trong chương trình của mình vì nó ném LRI khi biên dịch thời gian

Sự khác biệt chính mà tôi tìm thấy là ...

Chúng ta có thể khởi tạo lại con trỏ theo một địa chỉ nhưng không phải là trường hợp tương tự với một mảng.

======
và quay lại câu hỏi của bạn ...
a[5]không có gì nhưng *(a + 5)
bạn có thể hiểu dễ dàng bằng cách
a - chứa địa chỉ (mọi người gọi nó là địa chỉ cơ sở) giống như một loại con trỏ (2) trong danh sách của chúng tôi
[]- toán tử đó có thể là thay thế bằng con trỏ *.

cuối cùng thì...

a[5] == *(a +5) == *(5 + a) == 5[a] 
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.