Dlaczego większość programistów C nazywa takie zmienne:
int *myVariable;
zamiast tego:
int* myVariable;
Oba są ważne. Wydaje mi się, że gwiazdka jest częścią typu, a nie częścią nazwy zmiennej. Czy ktoś może wyjaśnić tę logikę?
c
pointers
variables
naming-conventions
WBlasko
źródło
źródło
typedefs
, ale to doda niepotrzebnej złożoności, IMHO.Odpowiedzi:
Są DOKŁADNIE równoważne. Jednak w
Wydaje się oczywiste, że moja zmienna ma typ int * , natomiast moja zmienna2 ma typ int . W
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.
źródło
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 rodzajint
, co ma sens.źródło
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 niepointer
samej.Ponieważ * w tym wierszu wiąże się ściślej ze zmienną niż z typem:
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:
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.źródło
int *const a, b;
. Gdzie * „wiąże”? Typa
jestint* 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.
Linia powyżej nie znaczy, że chcesz przypisać
10
doa
, oznacza to, że chcę, aby przypisać10
do dowolnych lokalizacji pamięcia
wskazuje. I nigdy nie widziałem, żeby ktoś pisał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:
To
*e
byłoby mylące, prawda?Okej, teraz co właściwie oznacza następujący wiersz:
Większość ludzi powiedziałaby:
Oznacza to, że
a
jest wskaźnikiemint
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 typuint
.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.źródło
int a, *b, (*c)();
jako coś w stylu „zadeklaruj następujące obiekty jakoint
: obiekta
, obiekt wskazywany przezb
i obiekt zwracany z funkcji wskazywanej przezc
”.*
Wint *a;
nie jest operator, i nie jest dereferencinga
(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ścia
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 przezint *a;
.a
jeszcze nie istnieje w momencie*
przetwarzania. Czy uważasz, żeint *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.
źródło
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
źródło
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:
nie jako:
Jeśli przyzwyczaisz się umieszczać gwiazdę obok zmiennej, ułatwi to odczytanie twoich deklaracji. Pozwala także uniknąć owrzodzeń, takich jak:
-- 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śleja
niż z nimint
, w bardzo podobny sposób, w jaki w wyrażeniu4 + 3 * 7
3
wiąże się ściślej7
niż z4
powodu wyższego pierwszeństwa*
.Z przeprosinami za sztukę ascii, streszczenie AST do parsowania
int *a
wygląda mniej więcej tak:Jak wyraźnie pokazano,
*
wiąże się ściślej,a
ponieważ ich wspólnym przodkiem jestDeclarator
, podczas gdy musisz przejść całą górę drzewa,Declaration
aby znaleźć wspólnego przodka, który obejmujeint
.źródło
(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*
doint
.Declaration-Specifier
z „dekoracjami” wDeclarator
celu uzyskania ostatecznego typu dla każdej zmiennej. Jednak nie „przenosi” dekoracji do specyfikatora Deklaracji, w przeciwnym razieint a[10], b;
dałoby całkowicie absurdalne wyniki, parsuje sięint *a, b[10];
jakoint
*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 typint*
”. O to mi chodziło w moim oryginalnym komentarzu.Ponieważ ma to większy sens, gdy masz deklaracje takie jak:
źródło
int* a, b;
popełniłb
wskaźnik doint
. Ale nie zrobili tego. I nie bez powodu. W proponowanym systemie jaki jest typb
następującej deklaracjiint* 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.źródło
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ż:
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 pobieraint
tablicę wskaźnikówchar
i zwraca anint
”. 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)
oznaczafn
wskaźnik do funkcji, która przyjmujeint
i nic nie zwraca.int a[10]
deklaruje „a” jako tablicę 10int
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:źródło
(*fn)
wskaźnik jest powiązanyfn
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 .
źródło
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:
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:
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
Lubint*const x
?Zastanów się
int *const a, b;
, jakie są rodzajea
ib
? Nie tak oczywiste, że „gwiazda należy już do zmiennej”. Raczej zaczyna się zastanawiać, do kogoconst
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ątrztypedef
(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: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ściint*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ć jakoconst (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:
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:Innym rozsądnym, ale subiektywnym argumentem byłoby to, że biorąc
int* a
pod uwagę typa
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 .
źródło
typedef int *bad_idea_t;
void func(const bad_idea_t bar);
Jak uczy wielki prorok, Dan Saks Saks: „Jeśli zawsze umieściszconst
tak daleko w prawo, jak to możliwe, bez zmiany znaczenia semantycznego”, to całkowicie przestaje być problemem. Sprawia również, żeconst
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ć
To nie przekłada się na
To właściwie tłumaczy
To sprawia, że
int *x
notacja jest nieco niespójna.źródło
new
jest operatorem C ++. Jego pytanie jest oznaczone jako „C”, a nie „C ++”.