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ż myVariable2
ma typ int .
Dlatego pierwszy styl programowania jest bardziej intuicyjny.
int* someVar
osobistych 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, *myVariable
jest to rodzaj int
, co ma sens.
myVariable
może być NULL, w którym to przypadku *myVariable
powoduje błąd segmentacji, ale nie ma typu NULL.
int x = 5; int *pointer = &x;
ponieważ sugeruje, że int przypisujemy *pointer
pewnej wartości, a nie pointer
samej.
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 a
jest 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ć 10
do a
, oznacza to, że chcę, aby przypisać 10
do dowolnych lokalizacji pamięci a
wskazuje. 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 *e
był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 a
jest wskaźnikiem int
wartoś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ść a
jest 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 a
jest 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 b
i 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 a
int 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. a
nie jest zaniedbywany przez int *a;
. a
jeszcze 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 a
niż z nim int
, w bardzo podobny sposób, w jaki w wyrażeniu 4 + 3 * 7
3
wiąże się ściślej 7
niż z 4
powodu wyższego pierwszeństwa *
.
Z przeprosinami za sztukę ascii, streszczenie AST do parsowania int *a
wyglą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, a
ponieważ ich wspólnym przodkiem jest Declarator
, podczas gdy musisz przejść całą górę drzewa, Declaration
aby znaleźć wspólnego przodka, który obejmuje int
.
(int*) a
.
int
w tym przypadku. Krok 2. Przeczytaj deklarację, w tym wszelkiego rodzaju dekoracje. *a
w 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-Specifier
z „dekoracjami” w Declarator
celu 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*a
jest w końcu odczytywany przez kompilator jako: „ a
ma 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ł b
wskaźnik do int
. Ale nie zrobili tego. I nie bez powodu. W proponowanym systemie jaki jest typ b
nastę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 „ main
jest funkcją, która pobiera int
tablicę wskaźników char
i 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 fn
wskaźnik do funkcji, która przyjmuje int
i nic nie zwraca.
int a[10]
deklaruje „a” jako tablicę 10 int
s.
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* p
deklaracji 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 typedef
deklaracji 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 fn
raczej 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 x
Lub int*const x
?
Zastanów się int *const a, b;
, jakie są rodzaje a
i b
? Nie tak oczywiste, że „gwiazda należy już do zmiennej”. Raczej zaczyna się zastanawiać, do kogo const
należ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 *a
stylu. 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 foo
jest 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 *foo
tutaj *
nie należy do nazwy zmiennej! W pseudokodzie deklarację parametru należy czytać jako const (bad_idea_t *) foo
a 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, typedef
a 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, b
jest 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* a
pod uwagę typ a
jest 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 const
tak daleko w prawo, jak to możliwe, bez zmiany znaczenia semantycznego”, to całkowicie przestaje być problemem. Sprawia również, że const
deklaracje 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 *x
notacja jest nieco niespójna.
new
jest operatorem C ++. Jego pytanie jest oznaczone jako „C”, a nie „C ++”.
typedefs
, ale to doda niepotrzebnej złożoności, IMHO.