Czym są deklaracje i deklaracje oraz jak ich rodzaje interpretuje norma?
Jak dokładnie standard definiuje, że na przykład float (*(*(&e)[10])())[5]
deklaruje zmienną typu " reference to array of 10 pointer to function of () zwracającą wskaźnik do array of 5 float
"?
zainspirowany dyskusją z @DanNissenbaum
2 answers
odsyłam do standardu C++11 w tym poście
Deklaracje
Deklaracje typu, którym się zajmujemy są znane jako simple-declaration s w gramatyce C++, które są jedną z dwóch następujących form (§7/1):
decl-specifier-seqopt INIT-declarator-listopt ;
atrybut-specifier-seq Decl-specifier-seq opt init-declarator-list ;
Atrybut-specifier-seq jest sekwencją atrybutów ([[something]]
) i/lub specyfikacji wyrównania (alignas(something)
). Ponieważ nie mają one wpływu na typ deklaracji, możemy je zignorować oraz drugą z powyższych dwóch form.
Specyfikacja deklaracji
Więc pierwsza część naszej deklaracji, Decl-specifier-seq , składa się z deklaracji / align = "left" / Należą do nich niektóre rzeczy, które możemy zignorować, takie jak specyfikatory pamięci masowej(static
, extern
, itd.), specyfikatory funkcji (inline
, itd.), friend
i tak dalej. Jednak jedynym specyfikatorem deklaracji, który nas interesuje, jest specyfikator typu , który może zawierać proste słowa kluczowe typu(char
, int
, unsigned
, itd.), nazwy typów zdefiniowanych przez użytkownika, kwalifikatory cv (const
lub volatile
) i inne, na których nam nie zależy.
przykład: tak prosty przykład Decl-specifier-seq, który jest tylko sekwencją specyfikacji typu to const int
. Innym może być unsigned int volatile
.
Możesz pomyśleć " Och, więc coś w rodzaju const volatile int int float const
jest równieżDecl-specifier-seq ?"Masz rację, że pasuje do zasad gramatyki, ale zasady semantyczne nie zezwalają na taki Decl-specifier-seq . W rzeczywistości dozwolony jest tylko jeden typ, z wyjątkiem pewnych kombinacji (takich jak unsigned
Z int
lub const
z czymkolwiek poza (§7.1.6/2-3).
Szybki Quiz (być może będziesz musiał odwołać się do standardu)
-
Czy
const int const
jest poprawna Sekwencja specyfikacji deklaracji czy nie? Jeśli nie, to czy nie są akceptowane przez reguły składniowe lub semantyczne?Niepoprawne przez reguły semantyczne!
const
nie może być łączony z samym sobą. -
Czy
unsigned const int
jest poprawna Sekwencja specyfikacji deklaracji czy nie? Jeśli nie, to wykluczone przez reguły składniowe czy semantyczne?Valid! Nie ma znaczenia, że
const
oddzielaunsigned
odint
. -
Czy
auto const
jest poprawną sekwencją specyfikacji deklaracji czy nie? Jeśli nie, to czy nie są akceptowane przez reguły składniowe lub semantyczne?Valid!
auto
jest specyfikatorem deklaracji, ale zmienioną kategorią w C++11. Wcześniej był to specyfik pamięci masowej (jakstatic
), ale teraz jest to typ konkretniej. -
Czy
int * const
jest poprawna Sekwencja specyfikacji deklaracji czy nie? Jeśli nie, to czy nie są akceptowane przez reguły składniowe lub semantyczne?Niepoprawne według zasad składniowych! Chociaż może to być pełny Typ deklaracji, tylko
int
jest sekwencją specyfikacji deklaracji. Specyfikatory deklaracji dostarczają tylko Typ podstawowy, a nie modyfikatory złożone, takie jak wskaźniki, referencje, tablice, itd.
Zgłaszający
Druga część prostej deklaracji jest init-deklarator-list. Jest to ciąg deklaratorów oddzielonych przecinkami, każdy z opcjonalnym inicjalizatorem (§8). Każdy deklarator wprowadza do programu jedną zmienną lub funkcję. Najprostszą formą deklaracji jest tylko nazwa, którą wprowadzasz - declarator-id. Deklaracja int x, y = 5;
posiada deklarację Sekwencja specyficzna, która jest po prostu int
, po której następują dwa deklaratory, x
i y
, z których drugi ma inicjalizator. Będziemy jednak ignorować inicjalizatory przez resztę tego postu.
Deklarator może mieć szczególnie złożoną składnię, ponieważ jest to część deklaracji, która pozwala określić, czy zmienna jest wskaźnikiem, referencją, tablicą, wskaźnikiem funkcji, itd. Należy pamiętać, że wszystkie są częścią deklarator, a nie deklaracją jako w całości. To jest właśnie powód, dla którego int* x, y;
nie deklaruje dwóch wskaźników - gwiazdka *
jest częścią deklaratora x
, a nie częścią deklaratora y
. Jedną z ważnych zasad jest to, że każdy deklarator musi mieć dokładnie jeden deklarator-id - nazwę, którą deklaruje. Pozostałe zasady dotyczące ważnych deklaracji są egzekwowane po określeniu typu deklaracji (do tego wrócimy później).
przykład : prosty przykład declarator to *const p
, który deklaruje const
wskaźnik do... coś. Typ, na który wskazuje, jest podawany przez specyfikatorów deklaracji w jej deklaracji. Bardziej przerażającym przykładem jest ten podany w pytaniu (*(*(&e)[10])())[5]
, który deklaruje odniesienie do tablicy wskaźników funkcji, do których zwracane są wskaźniki... ponownie, ostateczna część typu jest faktycznie podana przez specyfikatorów deklaracji.
Prawdopodobnie nigdy nie natkniesz się na takie straszne deklaracje, ale czasami podobne pojawiaj się. Jest to przydatna umiejętność czytania deklaracji, takiej jak ta w pytaniu i jest to umiejętność, która przychodzi z praktyką. Pomocne jest zrozumienie, w jaki sposób norma interpretuje rodzaj deklaracji.
Szybki Quiz (być może będziesz musiał odwołać się do standardu)
-
Które części
int const unsigned* const array[50];
są specyfikatorami deklaracji i zgłaszającym?Specyfikacja deklaracji:
int const unsigned
Zgłaszający:* const array[50]
-
Które części
volatile char (*fp)(float const), &r = c;
są specyfikatorami deklaracji i zgłaszającymi?Specyfikacja deklaracji:
volatile char
Zgłaszający #1:(*fp)(float const)
Zgłaszający #2:&r
Typy Deklaracji
Teraz wiemy, że deklaracja składa się z sekwencji specyfikatora deklaratora i listy deklaratorów, możemy zacząć myśleć o tym, jak określa się Typ deklaracji. Na przykład, to może być oczywiste, że int* p;
definiuje p
jako "wskaźnik do int", ale dla innych typów nie jest to takie oczywiste.
Deklaracja z wieloma deklaracjami, powiedzmy 2 deklaracjami, jest uważana za dwie deklaracje poszczególnych identyfikatorów. Oznacza to, że int x, *y;
jest deklaracją identyfikatora x
, int x
, oraz deklaracja identyfikatora y
, int *y
.
Typy są wyrażone w standardzie jako zdania Podobne do języka angielskiego (np. "pointer to int"). Interpretacja a Typ deklaracji w tej Angielskiej formie składa się z dwóch części. Najpierw określa się Typ specyfikacji deklaracji. Po drugie, procedura rekurencyjna jest stosowana do deklaracji jako całości.
Typ deklaracji
Typ sekwencji specyfikacji deklaracji określa Tabela 10 normy. Wymienia typy sekwencji, ponieważ zawierają one odpowiednie specyfikatory w dowolnej kolejności. Na przykład, każda sekwencja, która zawiera signed
i char
W dowolnej kolejności, w tym char signed
, ma typ "signed char". Każdy kwalifikator CV, który pojawia się w sekwencji specyfikacji deklaracji, jest dodawany na początku typu. Więc char const signed
ma typ "const signed char". Zapewnia to, że niezależnie od tego, w jakiej kolejności umieścisz specyfikatory, Typ będzie taki sam.
Szybki Quiz (być może będziesz musiał odwołać się do standardu)
-
Jaki jest typ sekwencji specyfikacji deklaracji
int long const unsigned
?"const unsigned long int"
-
Jaki jest typ sekwencji specyfikacji deklaracji
char volatile
?"volatile char"
-
Jaki jest typ sekwencji specyfikacji deklaracji
auto const
?To zależy!
auto
zostanie wydedukowane z inicjalizatora. Jeśli wydedukować toint
, na przykład, typem będzie " const int".
Typ deklaracji
Teraz, gdy mamy Typ sekwencji specyfikacji deklaracji, możemy określić typ całej deklaracji identyfikatora. Odbywa się to poprzez zastosowanie rekurencyjnej procedury określonej w §8.3. Aby wyjaśnić tę procedurę, użyję przykładu biegowego. Ustalimy rodzaj e
W float const (*(*(&e)[10])())[5]
.
Krok 1 pierwszym krokiem jest podzielenie deklaracji na Formularz T D
gdzie T
jest Sekwencja specyfikacji deklaracji i D
jest deklarator. Otrzymujemy więc:
T = float const
D = (*(*(&e)[10])())[5]
Typem T
jest oczywiście "const float", jak ustaliliśmy w poprzedniej sekcji. Następnie szukamy podsekcji §8.3, która odpowiada obecnej formie D
. Przekonasz się, że jest to §8.3.4 Tablice, ponieważ stwierdza, że ma zastosowanie do deklaracji formularza T D
, gdzie D
ma postać:
D1 [
stała-wyrażenieopt]
atrybut-specifier-seqopt
Nasz D
jest rzeczywiście tej formy, gdzie D1
jest (*(*(&e)[10])())
.
Teraz wyobraź sobie deklarację T D1
(pozbyliśmy się [5]
).
T D1 = const float (*(*(&e)[10])())
To jest typ "T
". Ta sekcja stwierdza, że typ naszego identyfikatora, e
, to " T
", gdzie T D1
.
To jest rekurencja! Rekurencyjnie ustalamy rodzaj wewnętrznej części deklaracji, usuwając jej część na każdym kroku.
Krok 2 tak więc, jak wcześniej, podzieliliśmy naszą nową deklarację na Formularz T D
:
T = const float
D = (*(*(&e)[10])())
To pasuje do paragrafu §8.3/6 gdzie D
ma postać ( D1 )
. W tym przypadku typ T D
jest po prostu typem T D1
:
T D1 = const float *(*(&e)[10])()
Krok 3 Let ' s zadzwoń do tego T D
teraz i podziel to jeszcze raz:
T = const float
D = *(*(&e)[10])()
Pasuje do §8.3.1 gdzie {[84] } ma postać * D1
. Jeśli T D1
ma typ "T
", to T D
ma typ "T
". Więc teraz potrzebujemy typu T D1
:
T D1 = const float (*(&e)[10])()
Krok 4 nazywamy goT D
i dzielimy na:
T = const float
D = (*(&e)[10])()
To pasuje do §8.3.5 funkcji, gdzie {[84] } ma postać D1 ()
. Jeśli T D1
ma typ "T
", to T D
ma typ "T
". Więc teraz potrzebujemy typu T D1
:
T D1 = const float (*(&e)[10])
Krok 5 możemy zastosować tę samą regułę, co w Kroku 2, gdzie deklarator jest po prostu zamknięty w nawiasach, aby ostatecznie uzyskać:
T D1 = const float *(&e)[10]
Krok 6 oczywiście dzielimy go na:
T = const float
D = *(&e)[10]
Ponownie dopasowujemy wskaźniki §8.3.1 z D
formularza * D1
. Jeśli T D1
ma typ "T
", to T D
ma typ "T
". Więc teraz my potrzeba typu T D1
:
T D1 = const float (&e)[10]
Krok 7 Podziel to:
T = const float
D = (&e)[10]
Ponownie dopasowujemy tablice §8.3.4, z D
postaci D1 [10]
. Jeśli T D1
ma typ "T
", to T D
ma typ "T
". Więc jaki jest typ T D1
?
T D1 = const float (&e)
Krok 8 Zastosuj kolejny krok w nawiasach:
T D1 = const float &e
Krok 9 Podziel to:
T = const float
D = &e
Teraz dopasowujemy odniesienia §8.3.2 gdzie D
jest formularza & D1
. Jeśli T D1
ma typ "T
", to T D
ma typ "T
". Więc jaki jest typ T D1
?
T D1 = const float e
Krok 10 cóż, to tylko " T " oczywiście! Na tym poziomie nie ma
I skończyliśmy!
Więc teraz, jeśli spojrzymy na typ, który określiliśmy na każdym kroku, zastępując e
W float const (*(*(&e)[10])())[5]
:
<some stuff> array of 5 T
│ └──────────┐
<some stuff> pointer to T
│ └────────────────────────┐
<some stuff> function of () returning T
| └──────────┐
<some stuff> pointer to T
| └───────────┐
<some stuff> array of 10 T
| └────────────┐
<some stuff> reference to T
| |
<some stuff> T
Jeśli połączymy to wszystko razem, otrzymamy:
reference to array of 10 pointer to function of () returning pointer to array of 5 const float
Nieźle! To pokazuje, jak kompilator wydedukuje Typ deklaracji. Pamiętaj, że jest to stosowane do każdej deklaracji identyfikatora, jeśli istnieje wiele deklaratorów. Spróbuj dowiedzieć się o nich:
Szybki Quiz (być może będziesz musiał odwołać się do standardu)
-
Jaki jest typ
x
w deklaracjibool **(*x)[123];
?"pointer to array of 123 pointer to pointer to bool"
-
Jakie są rodzaje
y
iz
w deklaracjiint const signed *(*y)(int), &z = i;
?y
is a "pointer to function of (int) returning pointer to const signed int"z
jest "odniesieniem do const signed int"
jeśli ktoś ma jakieś poprawki, proszę dać mi znać!
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-01 19:36:40
Oto sposób, w jaki analizuję float const (*(*(&e)[10])())[5]
. Przede wszystkim zidentyfikuj specyfikatora. Tutaj specyfikatorem jest float const
. Spójrzmy na pierwszeństwo. [] = () > *
. Nawiasy są używane do disambiguate the precedence. Mając na uwadze pierwszeństwo, zidentyfikujmy zmienną ID, którą jest e
. Tak więc, e jest odniesieniem do tablicy (od [] > *
) zawierającej 10 wskaźników do funkcji (od () > *
), które nie pobierają żadnego argumentu i zwracają oraz wskaźnikiem do tablicy 5 float const. Tak więc specifier przychodzi jako ostatni, a reszta jest analizowana zgodnie z pierwszeństwem.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-05 17:47:45