Odpowiedzi:
Są DOKŁADNIE równoważne. Jednak w
int *myVariable, myVariable2;
Wydaje się oczywiste, że moja zmienna ma typ int * , natomiast moja zmienna2 ma typ int . W
int* myVariable, myVariable2;
może się wydawać oczywiste, że oba są typu int * , ale to nie jest poprawne, ponieważ myVariable2ma typ int .
Dlatego pierwszy styl programowania jest bardziej intuicyjny.
int* someVarosobistych projektów. To ma więcej sensu.
int[10] x. To po prostu nie jest składnia C. Gramatyka wyraźnie analizuje jako: int (*x)a nie jako (int *) x, więc umieszczenie gwiazdki po lewej stronie jest po prostu mylące i oparte na nieporozumieniu składni deklaracji C.
int* myVariable; int myVariable2;Zamiast tego nie ma absolutnie żadnych wątpliwości .
Jeśli spojrzysz na to z innej strony, *myVariablejest to rodzaj int, co ma sens.
myVariablemoże być NULL, w którym to przypadku *myVariablepowoduje błąd segmentacji, ale nie ma typu NULL.
int x = 5; int *pointer = &x;ponieważ sugeruje, że int przypisujemy *pointerpewnej wartości, a nie pointersamej.
Ponieważ * w tym wierszu wiąże się ściślej ze zmienną niż z typem:
int* varA, varB; // This is misleading
Jak wskazuje @Lundin poniżej, const dodaje jeszcze więcej subtelności do przemyślenia. Możesz całkowicie tego uniknąć, deklarując jedną zmienną na linię, co nigdy nie jest dwuznaczne:
int* varA;
int varB;
Trudno jest znaleźć równowagę między czystym kodem a zwięzłym kodem - tuzin zbędnych wierszy również int a;nie jest dobry. Mimo to domyślnie przyjmuję jedną deklarację na wiersz i martwię się o późniejsze połączenie kodu.
int *const a, b;. Gdzie * „wiąże”? Typ ajest int* const, więc jak możesz powiedzieć, że * należy do zmiennej, gdy jest częścią samego typu?
Do tej pory nikt tu nie wspomniał, że ta gwiazdka jest w rzeczywistości „ operatorem dereferencyjnym ” w C.
*a = 10;
Linia powyżej nie znaczy, że chcesz przypisać 10do a, oznacza to, że chcę, aby przypisać 10do dowolnych lokalizacji pamięci awskazuje. I nigdy nie widziałem, żeby ktoś pisał
* a = 10;
czy ty? Więc operator dereferencji jest prawie zawsze zapisywany bez spacji. Jest to prawdopodobnie w celu odróżnienia tego od mnożenia podzielonego na wiele linii:
x = a * b * c * d
* e * f * g;
To *ebyłoby mylące, prawda?
Okej, teraz co właściwie oznacza następujący wiersz:
int *a;
Większość ludzi powiedziałaby:
Oznacza to, że ajest wskaźnikiem intwartości.
Jest to technicznie poprawne, większość ludzi lubi to widzieć / czytać w ten sposób i tak definiują to współczesne standardy C (zauważ, że sam język C wyprzedza wszystkie normy ANSI i ISO). Ale to nie jedyny sposób, aby na to spojrzeć. Możesz także przeczytać ten wiersz w następujący sposób:
Dereferencjonowana wartość ajest typu int.
Tak więc gwiazdka w tej deklaracji może być również postrzegana jako operator dereferencyjny, co również wyjaśnia jej umieszczenie. I to ajest wskaźnik, który nie jest tak naprawdę zadeklarowany, wynika z faktu, że jedyną rzeczą, którą można tak naprawdę wyrejestrować, jest wskaźnik.
Norma C definiuje tylko dwa znaczenia dla *operatora:
I pośrednictwo jest tylko jednym znaczeniem, nie ma dodatkowego znaczenia dla deklaracji wskaźnika, jest tylko pośrednie, czyli to, co robi operacja dereferencji, wykonuje dostęp pośredni, więc również w takiej instrukcji int *a;jest dostęp pośredni ( *oznacza pośredni dostęp), a zatem drugie oświadczenie powyżej jest znacznie bliższe standardowi niż pierwsze.
int a, *b, (*c)();jako coś w stylu „zadeklaruj następujące obiekty jako int: obiekt a, obiekt wskazywany przez bi obiekt zwracany z funkcji wskazywanej przez c”.
*W int *a;nie jest operator, i nie jest dereferencing a(który nie jest jeszcze nawet wyżej)
*(to daje tylko sens ogólnej ekspresji, a nie do *wewnątrz wyrazu!). Mówi, że „int a;” deklaruje wskaźnik, którego nie twierdzi, nigdy nie twierdził inaczej, ale bez znaczenia nadanego *, odczytanie go jako * wartość dereferencyjna wartości aint jest nadal całkowicie poprawna, ponieważ ma to samo znaczenie faktyczne. Nic, a właściwie nic napisanego w 6.7.6.1 nie byłoby sprzeczne z tym stwierdzeniem.
int *a;jest deklaracją, a nie wyrażeniem. anie jest zaniedbywany przez int *a;. ajeszcze nie istnieje w momencie *przetwarzania. Czy uważasz, że int *a = NULL;jest to błąd, ponieważ nie uwzględnia wskaźnika zerowego?
Mam zamiar iść w opałach tutaj i powiedzieć, że nie ma prostej odpowiedzi na to pytanie , zarówno dla deklaracji zmiennych i typów parametrów i powrotu, który jest to, że gwiazdka powinna iść obok nazwy: int *myVariable;. Aby docenić dlaczego, spójrz na to, jak deklarujesz inne typy symboli w C:
int my_function(int arg); dla funkcji;
float my_array[3] dla tablicy.
Ogólny wzorzec, zwany deklaracją po użyciu , polega na tym, że typ symbolu jest dzielony na część przed nazwą, a części wokół nazwy, a te części wokół nazwy naśladują składnię, której użyłbyś, aby uzyskać wartość typu po lewej:
int a_return_value = my_function(729);
float an_element = my_array[2];
i: int copy_of_value = *myVariable;.
C ++ rzuca klucz w pracy z referencjami, ponieważ składnia w punkcie, w którym używasz referencji, jest identyczna z typami wartości, więc możesz argumentować, że C ++ ma inne podejście do C. Z drugiej strony, C ++ zachowuje to samo zachowanie C w przypadku wskaźników, więc referencje naprawdę są pod tym względem dziwne.
To tylko kwestia preferencji.
Kiedy czytasz kod, rozróżnianie zmiennych i wskaźników jest łatwiejsze w drugim przypadku, ale może prowadzić do zamieszania, gdy umieszczasz zarówno zmienne, jak i wskaźniki wspólnego typu w jednym wierszu (co samo jest często zniechęcane przez wytyczne projektu, ponieważ zmniejsza czytelność).
Wolę deklarować wskaźniki za pomocą odpowiedniego znaku obok nazwy typu, np
int* pMyPointer;
Wielki guru powiedział kiedyś: „Musisz to przeczytać w kompilatorze, musisz”.
http://www.drdobbs.com/conversationsa-midsummer-nights-madness/184403835
To prawda, że dotyczyło to umieszczania const, ale tutaj obowiązuje ta sama zasada.
Kompilator czyta to jako:
int (*a);
nie jako:
(int*) a;
Jeśli przyzwyczaisz się umieszczać gwiazdę obok zmiennej, ułatwi to odczytanie twoich deklaracji. Pozwala także uniknąć owrzodzeń, takich jak:
int* a[10];
-- Edytować --
Wyjaśnienie dokładnie, co mam na myśli, gdy mówię, że jest ono parsowane int (*a), oznacza to, że *wiąże się ściślej aniż z nim int, w bardzo podobny sposób, w jaki w wyrażeniu 4 + 3 * 7 3wiąże się ściślej 7niż z 4powodu wyższego pierwszeństwa *.
Z przeprosinami za sztukę ascii, streszczenie AST do parsowania int *awygląda mniej więcej tak:
Declaration
/ \
/ \
Declaration- Init-
Secifiers Declarator-
| List
| |
| ...
"int" |
Declarator
/ \
/ ...
Pointer \
| Identifier
| |
"*" |
"a"
Jak wyraźnie pokazano, *wiąże się ściślej, aponieważ ich wspólnym przodkiem jest Declarator, podczas gdy musisz przejść całą górę drzewa, Declarationaby znaleźć wspólnego przodka, który obejmuje int.
(int*) a.
intw tym przypadku. Krok 2. Przeczytaj deklarację, w tym wszelkiego rodzaju dekoracje. *aw tym przypadku. Krok 3 Przeczytaj następny znak. Jeśli przecinek, użyj go i wróć do kroku 2. Jeśli średnik się zatrzyma. Jeśli cokolwiek innego wyrzuci błąd składniowy. ...
int* a, b;i uzyskać parę wskaźników. Chodzi mi o to, że *wiąże się ze zmienną i jest analizowana z nią, a nie z typem, aby utworzyć „typ podstawowy” deklaracji. Jest to również jeden z powodów, dla których wprowadzono typedefs, aby umożliwić typedef int *iptr; iptr a, b;utworzenie kilku wskaźników. Za pomocą typedef ty może się wiązało *do int.
Declaration-Specifierz „dekoracjami” w Declaratorcelu uzyskania ostatecznego typu dla każdej zmiennej. Jednak nie „przenosi” dekoracji do specyfikatora Deklaracji, w przeciwnym razie int a[10], b;dałoby całkowicie absurdalne wyniki, parsuje się int *a, b[10];jako int *a , b[10] ;. Nie ma innego sposobu, aby to opisać, który ma sens.
int*ajest w końcu odczytywany przez kompilator jako: „ ama typ int*”. O to mi chodziło w moim oryginalnym komentarzu.
Ponieważ ma to większy sens, gdy masz deklaracje takie jak:
int *a, *b;
int* a, b;popełnił bwskaźnik do int. Ale nie zrobili tego. I nie bez powodu. W proponowanym systemie jaki jest typ bnastępującej deklaracji int* a[10], b;:?
Aby zadeklarować wiele wskaźników w jednym wierszu, wolę tę, int* a, * b;która bardziej intuicyjnie deklaruje „a” jako wskaźnik do liczby całkowitej i nie miesza stylów, kiedy również deklaruje „b”. Jak ktoś powiedział, i tak nie zadeklarowałbym dwóch różnych typów w tym samym oświadczeniu.
Ludzie, którzy wolą, int* x;próbują wtłoczyć swój kod do fikcyjnego świata, w którym typ znajduje się po lewej stronie, a identyfikator (nazwa) po prawej stronie.
Mówię „fikcyjny”, ponieważ:
W C i C ++, w ogólnym przypadku, deklarowany identyfikator jest otoczony informacją o typie.
To może zabrzmieć szalenie, ale wiesz, że to prawda. Oto kilka przykładów:
int main(int argc, char *argv[])oznacza „ mainjest funkcją, która pobiera inttablicę wskaźników chari zwraca an int”. Innymi słowy, większość informacji o typie znajduje się po prawej stronie. Niektórzy uważają, że deklaracje funkcji nie liczą się, ponieważ są w jakiś sposób „wyjątkowe”. OK, spróbujmy zmiennej.
void (*fn)(int)oznacza fnwskaźnik do funkcji, która przyjmuje inti nic nie zwraca.
int a[10]deklaruje „a” jako tablicę 10 ints.
pixel bitmap[height][width].
Najwyraźniej wybrałem przykłady, które po prawej stronie mają wiele informacji o typie, aby wyrazić swoje zdanie. Istnieje wiele deklaracji, w których większość - jeśli nie wszystkie - tego typu znajduje się po lewej stronie struct { int x; int y; } center.
Ta składnia deklaracji wynikała z chęci K&R, aby deklaracje odzwierciedlały użycie. Czytanie prostych deklaracji jest intuicyjne, a czytanie bardziej złożonych można opanować, ucząc się reguły prawo-lewo-prawo (czasami nazywaj regułę spiralną lub po prostu prawą lewą).
C jest na tyle proste, że wielu programistów C przyjmuje ten styl i pisze proste deklaracje jako int *p.
W C ++ składnia stała się nieco bardziej złożona (z klasami, referencjami, szablonami, klasami enum), a w reakcji na tę złożoność będziesz musiał włożyć więcej wysiłku w oddzielenie typu od identyfikatora w wielu deklaracjach. Innymi słowy, możesz zobaczyć więcej int* pdeklaracji w stylu, jeśli wypiszesz duży obszar kodu C ++.
W każdym języku zawsze możesz mieć typ po lewej stronie deklaracji zmiennych przez (1), nigdy nie deklarując wielu zmiennych w tej samej instrukcji, i (2) korzystając z typedefdeklaracji s (lub aliasów, które, jak na ironię, umieszczają alias identyfikatory po lewej stronie typów). Na przykład:
typedef int array_of_10_ints[10];
array_of_10_ints a;
(*fn)wskaźnik jest powiązany fnraczej z typem zwracanym niż.
Odpowiedziałem już na podobne pytanie w CP i, ponieważ nikt o tym nie wspominał, również tutaj muszę zaznaczyć, że C jest językiem dowolnego formatu , niezależnie od wybranego stylu, podczas gdy parser może rozróżnić każdy token. Ta osobliwość C prowadzi do bardzo szczególnego rodzaju konkursu zwanego C zaciemnianiem .
Wiele argumentów w tym temacie jest subiektywnie subiektywnych, a spór o „gwiazdę wiąże się z nazwą zmiennej” jest naiwny. Oto kilka argumentów, które nie są tylko opiniami:
Zapomniane kwalifikatory typu wskaźnika
Formalnie „gwiazda” nie należy ani do typu, ani do nazwy zmiennej, jest częścią własnego elementu gramatycznego o nazwie wskaźnik . Formalna składnia C (ISO 9899: 2018) to:
(6.7) deklaracja:
specyfikatory deklaracji init-declarator-list opt;
Gdzie specyfikatory deklaracji zawierają typ (i pamięć), a lista init-declarator- zawiera wskaźnik i nazwę zmiennej. Co widzimy, jeśli dokładniej przeanalizujemy składnię listy deklaratorów:
(6.7.6) deklarator:
wskaźnik opt direct-declarator
...
(6.7.6) wskaźnik:
*type-qualifier-list opt
*type-qualifier-list opt wskaźnik
W przypadku gdy deklaratorem jest cała deklaracja, deklaratorem bezpośrednim jest identyfikator (nazwa zmiennej), a wskaźnik jest gwiazdą, po której następuje opcjonalna lista kwalifikatorów typu należąca do samego wskaźnika.
Różnorodne argumenty dotyczące stylu „gwiazda należy do zmiennej” są niespójne, ponieważ zapomnieli o kwalifikatorach typu wskaźnika. int* const x, int *const xLub int*const x?
Zastanów się int *const a, b;, jakie są rodzaje ai b? Nie tak oczywiste, że „gwiazda należy już do zmiennej”. Raczej zaczyna się zastanawiać, do kogo constnależy.
Z pewnością można podać rozsądny argument, że gwiazda należy do kwalifikatora typu wskaźnika, ale niewiele więcej.
Lista kwalifikatorów typu wskaźnika może powodować problemy dla osób używających int *astylu. Ci, którzy używają wskaźników wewnątrz typedef(czego nie powinniśmy, bardzo zła praktyka!) I myślą, że „gwiazda należy do nazwy zmiennej”, mają tendencję do pisania tego bardzo subtelnego błędu:
/*** bad code, don't do this ***/
typedef int *bad_idea_t;
...
void func (const bad_idea_t *foo);
To się kompiluje. Teraz możesz pomyśleć, że kod jest poprawny. Skąd! Ten kod jest przypadkowo sfałszowaną poprawnością const.
Typ foojest w rzeczywistości int*const*- najbardziej zewnętrzny wskaźnik został ustawiony tylko do odczytu, a nie wskazał na dane. Więc wewnątrz tej funkcji możemy to zrobić **foo = n;i zmieni ona wartość zmiennej w wywołującym.
Jest tak, ponieważ w wyrażeniu const bad_idea_t *footutaj *nie należy do nazwy zmiennej! W pseudokodzie deklarację parametru należy czytać jako const (bad_idea_t *) fooa nie jako (const bad_idea_t) *foo. Gwiazda należy w tym przypadku do typu ukrytego wskaźnika - typ jest wskaźnikiem, a wskaźnik o stałej nazwie jest zapisywany jako *const.
Ale potem źródłem problemu w powyższym przykładzie jest praktyka ukrywania wskaźników za stylem, typedefa nie za nim *.
Odnośnie deklaracji wielu zmiennych w jednym wierszu
Deklarowanie wielu zmiennych w jednym wierszu jest powszechnie uznawane za złą praktykę 1) . CERT-C ładnie podsumowuje:
DCL04-C. Nie deklaruj więcej niż jednej zmiennej na deklarację
Tylko czytanie po angielsku, to zdrowy rozsądek zgadza się, że deklaracja powinna być jedna deklaracja.
I nie ma znaczenia, czy zmienne są wskaźnikami, czy nie. Deklaracja każdej zmiennej w jednym wierszu sprawia, że kod jest jaśniejszy w prawie każdym przypadku.
Dlatego argument o pomyleniu programisty int* a, bjest zły. Przyczyną problemu jest użycie wielu deklaratorów, a nie ich umieszczenie *. Niezależnie od stylu powinieneś pisać to:
int* a; // or int *a
int b;
Innym rozsądnym, ale subiektywnym argumentem byłoby to, że biorąc int* apod uwagę typ ajest bez pytania, int*więc gwiazda należy do kwalifikatora typu.
Ale zasadniczo mój wniosek jest taki, że wiele przytoczonych tutaj argumentów jest subiektywnych i naiwnych. Nie można tak naprawdę uzasadnić żadnego z tych stylów - jest to naprawdę kwestia subiektywnych preferencji osobistych.
1) CERT-C DCL04-C .
typedef int *bad_idea_t; void func(const bad_idea_t bar); Jak uczy wielki prorok, Dan Saks Saks: „Jeśli zawsze umieścisz consttak daleko w prawo, jak to możliwe, bez zmiany znaczenia semantycznego”, to całkowicie przestaje być problemem. Sprawia również, że constdeklaracje są bardziej spójne w czytaniu. „Wszystko po prawej stronie słowa const jest tym, co jest const, wszystko po lewej stronie jest jego rodzajem”. Dotyczy to wszystkich stałych w deklaracji. Spróbuj zint const * * const x;
Rozważać
int *x = new int();
To nie przekłada się na
int *x;
*x = new int();
To właściwie tłumaczy
int *x;
x = new int();
To sprawia, że int *xnotacja jest nieco niespójna.
newjest operatorem C ++. Jego pytanie jest oznaczone jako „C”, a nie „C ++”.
typedefs, ale to doda niepotrzebnej złożoności, IMHO.