Dlaczego w przypadku tablic a [5] == 5 [a]?


1621

Jak zauważa Joel w podcastie Stack Overflow # 34 , w C Programming Language (alias: K & R), wspomniano o tej właściwości tablic w C:a[5] == 5[a]

Joel mówi, że to z powodu arytmetyki wskaźników, ale wciąż nie rozumiem. Dlaczegoa[5] == 5[a] ?


48
czy coś takiego jak [+] również działałoby jak * (a ++) LUB * (++ a)?
Egon

45
@Egon: To bardzo kreatywne, ale niestety nie tak działają kompilatory. Kompilator interpretuje a[1]jako ciąg tokenów, a nie ciągów: * ({liczba całkowita} operatora {operator} + {liczba całkowita} 1) jest taka sama jak * ({liczba całkowita} 1 {operator} + {liczba całkowita}) ale to nie to samo co * ({liczba całkowita} operatora {operator} + {operator} +)
Dinah

11
Interesującą odmianę tego związku ilustruje nielogiczny dostęp do tablicy , gdzie masz char bar[]; int foo[];i foo[i][bar]jest używany jako wyrażenie.
Jonathan Leffler

5
@EldritchConundrum, dlaczego uważasz, że „kompilator nie może sprawdzić, czy lewa część jest wskaźnikiem”? Tak, może. To prawda, że a[b]= *(a + b)dla dowolnego ai b, ale był to swobodny wybór projektantów języków, +który można zdefiniować jako przemienny dla wszystkich typów. Nic nie mogło zapobiec ich zakazanie i + p, pozwalając jednocześnie p + i.
ach

13
@Andrey One zwykle oczekuje, +że będzie przemienny, więc może prawdziwym problemem jest wybranie, aby operacje wskaźnika przypominały arytmetykę, zamiast projektowania osobnego operatora przesunięcia.
Eldritch Conundrum

Odpowiedzi:


1924

Standard C definiuje []operatora w następujący sposób:

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

Dlatego a[5]oceni, aby:

*(a + 5)

i 5[a]oceni, aby:

*(5 + a)

ajest wskaźnikiem do pierwszego elementu tablicy. a[5]Jest to wartość, która znajduje się 5 elementów dalej od a, który jest taki sam jak *(a + 5)iz matematyki elementarnej szkoły wiemy te są równe (dodawanie jest przemienne ).


325
Zastanawiam się, czy to nie jest bardziej jak * ((5 * sizeof (a)) + a). Świetne wyjaśnienie.
John MacIntyre,

92
@Dinah: Z perspektywy kompilatora C masz rację. Rozmiar nie jest potrzebny, a te wyrażenia, o których wspomniałem, są TAKIE SAME. Jednak kompilator weźmie pod uwagę sizeof przy tworzeniu kodu maszynowego. Jeśli a jest tablicą int, a[5]skompiluje się do czegoś podobnego mov eax, [ebx+20]zamiast[ebx+5]
Mehrdad Afshari

12
@Dinah: A to adres, powiedzmy 0x1230. Jeśli a był w 32-bitowej tablicy int, to [0] ma wartość 0x1230, a [1] ma wartość 0x1234, a [2] ma wartość 0x1238 ... a [5] przy x1244 itd. Jeśli dodamy tylko 5 do 0x1230 otrzymujemy 0x1235, co jest złe.
James Curran,

36
@ sr105: Jest to szczególny przypadek dla operatora +, gdzie jeden z operandów jest wskaźnikiem, a drugi liczbą całkowitą. Standard mówi, że wynik będzie typu wskaźnika. Kompilator / musi być / wystarczająco inteligentny.
aib

48
„z matematyki elementarnej szkoły wiemy, to są równe” - rozumiem, że jesteś uproszczenie, ale ja jestem z tych, którzy czują się jak to jest na uproszczenia. To nie jest elementarne *(10 + (int *)13) != *((int *)10 + 13). Innymi słowy, dzieje się tutaj więcej niż arytmetyka w szkole podstawowej. Komutatywność opiera się krytycznie na tym, że kompilator rozpoznaje, który operand jest wskaźnikiem (i jaki rozmiar obiektu). Innymi słowy (1 apple + 2 oranges) = (2 oranges + 1 apple), ale (1 apple + 2 oranges) != (1 orange + 2 apples).
LarsH,

288

Ponieważ dostęp do tablicy jest zdefiniowany za pomocą wskaźników. a[i]jest zdefiniowany jako oznaczający *(a + i)przemienny.


42
Tablice nie są zdefiniowane w kategoriach wskaźników, ale dostęp do nich jest.
Wyścigi lekkości na orbicie

5
Dodałbym „więc to jest równe *(i + a), co można zapisać jako i[a]”.
Jim Balter,

4
Proponuję dołączyć cytat ze standardu, który wygląda następująco: 6.5.2.1: 2 Wyrażenie postfiksowe, po którym następuje wyrażenie w nawiasach kwadratowych [], to indeksowane oznaczenie elementu obiektu tablicy. Definicja operatora indeksu dolnego [] jest taka, że ​​E1 [E2] jest identyczne z (* ((E1) + (E2))). Z powodu reguł konwersji, które mają zastosowanie do operatora binarnego +, jeśli E1 jest obiektem tablicowym (równoważnie, wskaźnik do początkowego elementu obiektu tablicowego), a E2 jest liczbą całkowitą, E1 [E2] oznacza E2-ty element E1 (odliczanie od zera).
Rzeczywistość

Aby być bardziej poprawnym: tablice rozpadają się na wskaźniki podczas uzyskiwania do nich dostępu.
12431234123412341234123

Nitpick: Nie ma sensu mówić, że „ *(a + i)jest przemienny”. Jednak *(a + i) = *(i + a) = i[a]ponieważ dodawanie jest przemienne.
Andreas Rejbrand

231

Myślę, że inne odpowiedzi pomijają coś.

Tak, p[i]z definicji jest równoważne z tym *(p+i), co (ponieważ dodawanie jest przemienne) jest równoważne z *(i+p), co (ponownie, z definicji []operatora) jest równoważne i[p].

(A array[i]wewnątrz nazwa tablicy jest domyślnie konwertowana na wskaźnik do pierwszego elementu tablicy).

Ale zamienność dodawania nie jest w tym przypadku aż tak oczywista.

Gdy oba operandy są tego samego typu, a nawet różnych typów numerycznych, które są promowane do wspólnego typu, przemienność sens: x + y == y + x.

Ale w tym przypadku mówimy konkretnie o arytmetyce wskaźnika, gdzie jeden operand jest wskaźnikiem, a drugi liczbą całkowitą. (Liczba całkowita + liczba całkowita to inna operacja, a wskaźnik + wskaźnik to nonsens).

Opis +operatora w standardzie C ( N1570 6.5.6) mówi:

Ponadto oba operandy muszą mieć typ arytmetyczny lub jeden operand powinien być wskaźnikiem do pełnego typu obiektu, a drugi powinien być liczbą całkowitą.

Równie łatwo mógłby powiedzieć:

Ponadto oba operandy muszą mieć typ arytmetyczny lub lewy operand powinien być wskaźnikiem do pełnego typu obiektu, a prawy operand powinien być liczbą całkowitą.

w takim przypadku oba i + pi i[p]byłyby nielegalne.

W języku C ++ mamy naprawdę dwa zestawy przeciążonych +operatorów, które można luźno opisać jako:

pointer operator+(pointer p, integer i);

i

pointer operator+(integer i, pointer p);

z których tylko pierwszy jest naprawdę potrzebny.

Więc dlaczego tak jest?

C ++ odziedziczył tę definicję od C, która otrzymała ją od B (przemienność indeksowania tablic jest wyraźnie wspomniana w Odniesieniu użytkowników do B z 1972 r. ), Która otrzymała ją od BCPL (instrukcja z 1967 r.), Która mogła ją nawet otrzymać wcześniejsze języki (CPL? Algol?).

Pomysł, że indeksowanie tablic jest zdefiniowane w kategoriach dodawania i że dodawanie, nawet wskaźnika i liczby całkowitej, jest przemienne, sięga wielu dziesięcioleci do języków przodków C.

Języki te były znacznie rzadziej pisane niż współczesne C. W szczególności rozróżnianie wskaźników i liczb całkowitych było często ignorowane. (Wcześniejsi programiści C czasami używali wskaźników jako liczb całkowitych bez znaku, zanim unsignedsłowo kluczowe zostało dodane do języka). Więc pomysł, aby dodanie było nieprzemienne, ponieważ operandy różnych typów prawdopodobnie nie przyszedłby do projektantów tych języków. Jeśli użytkownik chciał dodać dwie „rzeczy”, bez względu na to, czy są to liczby całkowite, wskaźniki lub coś innego, język nie był w stanie temu zapobiec.

Z biegiem lat każda zmiana tej reguły złamałaby istniejący kod (chociaż norma ANSI C z 1989 roku mogła być dobrą okazją).

Zmiana C i / lub C ++ w taki sposób, aby wymagała umieszczenia wskaźnika po lewej stronie i liczby całkowitej po prawej stronie, może zepsuć istniejący kod, ale nie nastąpiłaby utrata prawdziwej mocy ekspresyjnej.

Mamy więc teraz arr[3]i 3[arr]dokładnie to samo, chociaż ta ostatnia forma nigdy nie powinna pojawić się poza IOCCC .


12
Fantastyczny opis tej nieruchomości. Z wysokiego poziomu uważam, że 3[arr]jest interesującym artefaktem, ale rzadko powinien być używany. Zaakceptowana odpowiedź na to pytanie (< stackoverflow.com/q/1390365/356> ), o którą pytałem jakiś czas temu, zmieniła sposób myślenia o składni. Chociaż technicznie często nie ma właściwego i niewłaściwego sposobu wykonania tych czynności, tego rodzaju funkcje powodują, że myślisz w sposób niezależny od szczegółów implementacji. Korzyści płyną z tego odmiennego sposobu myślenia, który jest częściowo tracony, gdy skupiasz się na szczegółach implementacji.
Dinah

3
Dodawanie jest przemienne. W przeciwnym razie zdefiniowanie go przez C byłoby dziwne. Dlatego nie można tak łatwo powiedzieć: „Na dodatek oba operandy będą miały typ arytmetyczny, lub lewy operand będzie wskaźnikiem do pełnego typu obiektu, a prawy operand będzie miał liczbę całkowitą”. - To nie miałoby sensu dla większości ludzi, którzy dodają rzeczy.
iheanyi

9
@heheanyi: Dodawanie jest zwykle przemienne - i zwykle wymaga dwóch operandów tego samego typu. Dodanie wskaźnika pozwala dodać wskaźnik i liczbę całkowitą, ale nie dwa wskaźniki. IMHO to już dość dziwny przypadek specjalny, który wymagałby, aby wskaźnik był lewym operandem, nie byłby znaczącym obciążeniem. (Niektóre języki używają „+” do łączenia ciągów; to z pewnością nie jest przemienne.)
Keith Thompson

3
@supercat, to jeszcze gorzej. Oznaczałoby to, że czasami x + 1! = 1 + x. To całkowicie naruszałoby asocjacyjną właściwość dodawania.
iheanyi

3
@iheanyi: Myślę, że miałeś na myśli własność przemienną; dodawanie nie jest już skojarzone, ponieważ na większości implementacji (1LL + 1U) -2! = 1LL + (1U-2). Rzeczywiście, zmiana sprawiłaby, że niektóre sytuacje byłyby asocjacyjne, które obecnie nie są, np. 3U + (UINT_MAX-2L) byłby równy (3U + UINT_MAX) -2. Najlepiej byłoby jednak, gdyby język dodał nowe odrębne typy dla promowanych liczb całkowitych i „zawinięcia” pierścieni algebraicznych, tak że dodanie 2 do liczby ring16_t65535 dałoby ring16_twartość 1, niezależnie od wielkościint .
supercat

196

I oczywiście

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

Głównym tego powodem było to, że w latach 70., kiedy projektowano C, komputery nie miały dużo pamięci (64 KB było dużo), więc kompilator C nie sprawdzał dużo składni. Dlatego „ X[Y]” zostało raczej ślepo przetłumaczone na „ *(X+Y)

To wyjaśnia także składnie „ +=” i „ ++”. Wszystko w formie „ A = B + C” miało tę samą skompilowaną formę. Ale jeśli B był tym samym obiektem co A, dostępna była optymalizacja na poziomie zespołu. Ale kompilator nie był wystarczająco jasny, aby go rozpoznać, więc programista musiał ( A += C). Podobnie, jeśli Ctak 1, dostępna była inna optymalizacja na poziomie zespołu, a programista musiał to wyraźnie wyrazić, ponieważ kompilator jej nie rozpoznał. (Ostatnio robią to kompilatory, więc te składnie są obecnie w dużej mierze niepotrzebne)


127
W rzeczywistości jest to fałsz; pierwszy termin „ABCD” [2] == 2 [„ABCD”] przyjmuje wartość true lub 1, a 1! = „C”: D
Jonathan Leffler

8
@Jathanathan: ta sama dwuznaczność prowadzi do edycji oryginalnego tytułu tego postu. Czy jesteśmy równymi znakami równoważności matematycznej, składni kodu lub pseudokodu. Argumentuję za matematyczną równoważnością, ale skoro mówimy o kodzie, nie możemy uciec od tego, że przeglądamy wszystko pod względem składni kodu.
Dinah,

19
Czy to nie mit? Mam na myśli, że operatory + = i ++ zostały stworzone w celu uproszczenia kompilatora? Niektóre kody stają się z nimi bardziej zrozumiałe i przydatna jest ich składnia, bez względu na to, co kompilator z nią robi.
Thomas Padron-McCarthy

6
+ = i ++ ma jeszcze jedną istotną zaletę. jeśli lewa strona zmieni jakąś zmienną podczas oceny, zmiana zostanie wykonana tylko raz. a = a + ...; zrobi to dwa razy.
Johannes Schaub - litb

8
Nie - „ABCD” [2] == * („ABCD” + 2) = * („CD”) = „C”. Dereferencjonowanie łańcucha daje znak, a nie podciąg
MSalters

55

Jedna rzecz, o której nikt nie wspominał o problemie Diny z sizeof:

Możesz dodać tylko liczbę całkowitą do wskaźnika, nie możesz dodać dwóch wskaźników razem. W ten sposób, dodając wskaźnik do liczby całkowitej lub liczbę całkowitą do wskaźnika, kompilator zawsze wie, który bit ma rozmiar, który należy wziąć pod uwagę.


1
W komentarzach do zaakceptowanej odpowiedzi jest dość wyczerpująca rozmowa na ten temat. Odniosłem się do wspomnianego wątku w edycji do pierwotnego pytania, ale nie odniosłem się bezpośrednio do twojej bardzo ważnej obawy o rozmiar. Nie jestem pewien, jak najlepiej to zrobić w SO. Czy powinienem dokonać kolejnej edycji oryginału? pytanie?
Dinah

50

Aby dosłownie odpowiedzieć na pytanie. Nie zawsze tak jestx == x

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

odciski

false

27
W rzeczywistości „nan” nie jest sobie równa: cout << (a[5] == a[5] ? "true" : "false") << endl;jest false.
TrueY

8
@TrueY: Stwierdził to specjalnie w przypadku NaN (a konkretnie x == xnie zawsze tak jest). Myślę, że taki był jego zamiar. Jest on więc technicznie poprawny (i być może, jak mówią, najlepszy rodzaj poprawności!).
Tim Čas,

3
Pytanie dotyczy C, twój kod nie jest kodem C. Jest też NANw <math.h>, co jest lepsze niż 0.0/0.0, bo 0.0/0.0to UB, gdy __STDC_IEC_559__nie jest określona (Większość implementacji nie definiują __STDC_IEC_559__, ale w większości implementacji 0.0/0.0będzie nadal działać)
12431234123412341234123

26

Właśnie odkryłem, że ta brzydka składnia może być „przydatna”, a przynajmniej bardzo przyjemna do zabawy, gdy chcesz poradzić sobie z tablicą indeksów odnoszących się do pozycji w tej samej tablicy. Może zastąpić zagnieżdżone nawiasy kwadratowe i uczynić kod bardziej czytelnym!

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)

}

Oczywiście jestem całkiem pewien, że nie ma takiego przypadku w prawdziwym kodzie, ale i tak mnie to zainteresowało :)


Gdy zobaczysz, i[a][a][a]że myślisz, że albo jest wskaźnikiem do tablicy lub tablicą wskaźnika do tablicy lub tablicy ... i ajest indeksem. Kiedy widzisz a[a[a[i]]], myślisz, że a jest wskaźnikiem do tablicy lub tablicy i ijest indeksem.
12431234123412341234123

1
Łał! To bardzo fajne użycie tej „głupiej” funkcji. Może być przydatny w konkursie algorytmicznym przy niektórych problemach))
Serge Breusov

26

Ładne pytanie / odpowiedź.

Chcę tylko zaznaczyć, że wskaźniki C i tablice nie są takie same , chociaż w tym przypadku różnica nie jest istotna.

Rozważ następujące deklaracje:

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

W a.out, symbol aznajduje się pod adresem, który jest początkiem tablicy, a symbol pznajduje się pod adresem, w którym przechowywany jest wskaźnik, a wartość wskaźnika w tym miejscu pamięci jest początkiem tablicy.


2
Nie, technicznie nie są takie same. Jeśli zdefiniujesz część b jako int * const i sprawisz, że będzie wskazywać na tablicę, nadal będzie to wskaźnik, co oznacza, że ​​w tablicy symboli b oznacza lokalizację w pamięci, która przechowuje adres, który z kolei wskazuje, gdzie tablica jest .
PolyThinker,

4
Bardzo dobra uwaga. Pamiętam, że miałem bardzo paskudny błąd, kiedy zdefiniowałem globalny symbol jako char s [100] w jednym module, zadeklaruj go jako zewnętrzny char * s; w innym module. Po połączeniu tego wszystkiego program zachowywał się bardzo dziwnie. Ponieważ moduł używający deklaracji zewnętrznej używał początkowych bajtów tablicy jako wskaźnika do char.
Giorgio

1
Pierwotnie w dziadku C z BCPL tablica była wskaźnikiem. Oznacza to, że otrzymałeś, gdy napisałeś (transliterowałem do C), int a[10]wskaźnik zwany „a”, który wskazywał na wystarczającą ilość miejsca na 10 liczb całkowitych w innym miejscu. Zatem a + i j + i miały tę samą formę: dodaj zawartość kilku lokalizacji pamięci. W rzeczywistości uważam, że BCPL było bez typu, więc były identyczne. Skalowanie typu size nie miało zastosowania, ponieważ BCPL był zorientowany wyłącznie na słowa (także na maszynach z adresowaniem słów).
dave

Myślę, że najlepszym sposobem na zrozumienie różnicy jest porównanie int*p = a;z int b = 5; tymi ostatnimi, „b” i „5” są liczbami całkowitymi, ale „b” jest zmienną, podczas gdy „5” jest wartością stałą. Podobnie „p” i „a” są adresami znaku, ale „a” jest stałą wartością.
James Curran,

20

Dla wskaźników w C mamy

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

i również

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

Dlatego prawdą jest, że a[5] == 5[a].


15

Nie odpowiedź, ale tylko trochę do myślenia. Jeśli klasa ma przeciążonego operatora indeksu / indeksu dolnego, wyrażenie 0[x]nie będzie działać:

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

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

Ponieważ nie mamy dostępu do klasy int , nie można tego zrobić:

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
Czy próbowałeś go skompilować? Istnieje zestaw operatorów, których nie można zaimplementować poza klasą (tj. Jako funkcje niestatyczne)!
Ajay,

3
Ups, masz rację. „ operator[]powinna być niestatyczną funkcją składową z dokładnie jednym parametrem.” Byłem zaznajomiony z tym ograniczeniem operator=, nie sądziłem, aby miało to zastosowanie [].
Ben Voigt

1
Oczywiście, jeśli zmienisz definicję []operatora, nigdy więcej nie będzie równoważna ... jeśli a[b]jest równa *(a + b)i zmienisz to, będziesz musiał również przeciążać int::operator[](const Sub&);i intnie jest klasą ...
Luis Colorado,

7
To ... nie jest ... C.
MD XF,

11

Ma bardzo dobre wytłumaczenie w TUTORIAL NA WSKAŹNIKI I SZABLONY W C autorstwa Teda Jensena.

Ted Jensen wyjaśnił to jako:

W rzeczywistości jest to prawdą, tzn. Gdziekolwiek pisze się a[i], można go zastąpić *(a + i) bez żadnych problemów. W rzeczywistości kompilator utworzy ten sam kod w obu przypadkach. Widzimy zatem, że arytmetyka wskaźników jest tym samym, co indeksowanie tablic. Każda ze składni daje ten sam wynik.

To nie znaczy, że wskaźniki i tablice są tym samym, nie są. Mówimy tylko, że aby zidentyfikować dany element tablicy, mamy do wyboru dwie składnie, jedną z wykorzystaniem indeksowania tablic i drugą z wykorzystaniem arytmetyki wskaźników, które dają identyczne wyniki.

Teraz, patrząc na to ostatnie wyrażenie, jego część .. (a + i), jest prostym dodatkiem używającym operatora + i reguł C mówi, że takie wyrażenie jest przemienne. To znaczy (a + i) jest identyczne z (i + a). W ten sposób moglibyśmy pisać *(i + a)równie łatwo jak *(a + i). Ale *(i + a)mógł pochodzić i[a]! Z tego wszystkiego pochodzi ciekawa prawda, że ​​jeśli:

char a[20];

pisanie

a[3] = 'x';

to to samo co pisanie

3[a] = 'x';

4
a + i NIE jest prostym dodatkiem, ponieważ jest to arytmetyka wskaźnika. jeśli rozmiar elementu a wynosi 1 (char), to tak, to jest jak liczba całkowita +. Ale jeśli jest to (np.) Liczba całkowita, to może być równoważna + 4 * i.
Alex Brown,

@AlexBrown Tak, to arytmetyka wskaźników, dlatego właśnie ostatnie zdanie jest błędne, chyba że najpierw rzucisz „a”, aby być (char *) (zakładając, że int to 4 znaki). Naprawdę nie rozumiem, dlaczego tak wielu ludzi rozwiewa się na wynikach arytmetyki wskaźników. Głównym celem arytmetyki wskaźników jest wyodrębnianie leżących u podstaw wartości wskaźnika i pozwolenie programiście myśleć o obiektach poddawanych manipulacji, a nie adresować wartości.
jschultz410

8

Wiem, że odpowiedź na to pytanie, ale nie mogłam się powstrzymać przed udostępnieniem tego wyjaśnienia.

Pamiętam Zasady projektowania kompilatora, Załóżmy, że ajest to inttablica o rozmiarze int2 bajtów, a adres podstawowy ato 1000.

Jak a[5]będzie działać ->

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

Więc,

Podobnie, gdy kod c zostanie podzielony na kod 3-adresowy, 5[a]stanie się ->

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

Więc w zasadzie oba stwierdzenia są skierowane do tej samej lokalizacji w pamięci, a więc a[5] = 5[a].

To wyjaśnienie jest również powodem, dla którego ujemne indeksy w tablicach działają w C.

tzn. jeśli uzyskam dostęp a[-5], da mi to

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

Zwróci mi obiekt w lokalizacji 990.


6

W macierzy C , arr[3]i 3[arr]są takie same, a ich odpowiadające oznaczenia wskaźnika są *(arr + 3)na *(3 + arr). Ale wręcz przeciwnie [arr]3lub [3]arrjest niepoprawny i spowoduje błąd składniowy, ponieważ (arr + 3)*i (3 + arr)*nie są prawidłowymi wyrażeniami. Powodem jest to, że operator dereferencji powinien być umieszczony przed adresem podanym w wyrażeniu, a nie po adresie.


6

w kompilatorze c

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

istnieją różne sposoby odwoływania się do elementu w tablicy! (NIE WSZYSTKO WEIRD)


5

Trochę historii. Wśród innych języków BCPL miał dość duży wpływ na wczesny rozwój C. Jeśli zadeklarowałeś tablicę w BCPL za pomocą czegoś takiego:

let V = vec 10

który faktycznie przydzielił 11 słów pamięci, a nie 10. Zwykle V był pierwszy i zawierał adres następnego słowa. Tak więc, w przeciwieństwie do C, nazewnictwo V poszło w to miejsce i odebrało adres elementu zeroeth tablicy. Zatem pośrednia macierz w BCPL, wyrażona jako

let J = V!5

naprawdę musiałem to zrobić J = !(V + 5)(używając składni BCPL), ponieważ konieczne było pobranie V, aby uzyskać adres bazowy tablicy. Zatem V!5i 5!Vbyły synonimami. Jako niepotwierdzone spostrzeżenie, WAFL (Warwick Functional Language) został napisany w BCPL i, zgodnie z moją najlepszą pamięcią, miałem tendencję do używania tej drugiej składni zamiast pierwszej do uzyskiwania dostępu do węzłów używanych do przechowywania danych. Przyznaję, że pochodzi to gdzieś pomiędzy 35 a 40 lat temu, więc moja pamięć jest trochę zardzewiała. :)

Innowacja rezygnacji z dodatkowego słowa pamięci i kompilatora wstawiającego adres bazowy tablicy, gdy została ona nazwana, pojawiła się później. Według artykułu z historii C zdarzyło się to mniej więcej w czasie, gdy struktury zostały dodane do C.

Zauważ, że !w BCPL był zarówno operatorem jednoargumentowym, jak i operatorem binarnym, w obu przypadkach działając pośrednio. tylko, że postać binarna zawierała dodanie dwóch operandów przed wykonaniem pośrednictwa. Biorąc pod uwagę zorientowaną na słowa naturę BCPL (i B), miało to naprawdę sens. Ograniczenie „wskaźnika i liczby całkowitej” stało się konieczne w C, gdy zyskało typy danych i sizeofstało się rzeczą.


1

Cóż, ta funkcja jest możliwa tylko ze względu na obsługę języka.

Kompilator interpretuje a[i]jako *(a+i)i wyrażenie 5[a]ocenia na *(5+a). Ponieważ dodawanie jest przemienne, okazuje się, że oba są równe. Stąd wyrażenie ocenia na true.


Chociaż jest to zbędne, jest to jasne, zwięzłe i krótkie.
Bill K,

0

W 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

Wskaźnik jest „zmienną”

nazwa tablicy to „mnemoniczny” lub „synonim”

p++;jest prawidłowy, ale a++jest nieprawidłowy

a[2] jest równe 2 [a], ponieważ wewnętrzna operacja na obu z nich to

„Arytmetyka wskaźnika” obliczona wewnętrznie jako

*(a+3) równa się *(3+a)


-4

typy wskaźników

1) wskaźnik do danych

int *ptr;

2) const wskaźnik do danych

int const *ptr;

3) const wskaźnik do const danych

int const *const ptr;

a tablice są typu (2) z naszej listy
Kiedy definiujesz tablicę jednocześnie, jeden adres jest inicjowany w tym wskaźniku
Ponieważ wiemy, że nie możemy zmienić ani zmodyfikować stałej wartości w naszym programie, ponieważ powoduje to błąd przy kompilacji czas

The Główną różnicą znalazłem to ...

Możemy ponownie zainicjować wskaźnik za pomocą adresu, ale nie w tym samym przypadku z tablicą.

======
i wracając do pytania ...
a[5]to nic, ale *(a + 5)
można to łatwo zrozumieć
a - zawierając adres (ludzie nazywają go jako adres bazowy) tak jak (2) typ wskaźnika na naszej liście
[] - tym operatorem może być wymienny ze wskaźnikiem *.

więc w końcu ...

a[5] == *(a +5) == *(5 + a) == 5[a] 
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.