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

Author: Community, 2012-12-11

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)

  1. 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ą.

  2. 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 oddziela unsigned od int.

  3. 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 (jak static), ale teraz jest to typ konkretniej.

  4. 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)

  1. 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]

  2. 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)

  1. Jaki jest typ sekwencji specyfikacji deklaracji int long const unsigned?

    "const unsigned long int"

  2. Jaki jest typ sekwencji specyfikacji deklaracji char volatile?

    "volatile char"

  3. Jaki jest typ sekwencji specyfikacji deklaracji auto const?

    To zależy! auto zostanie wydedukowane z inicjalizatora. Jeśli wydedukować to int, 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 " array of 5 T", gdzie jest taki sam jak w typie urojonej deklaracji. Więc aby wypracować resztę typu, my trzeba wypracować Typ 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 " wskaźnik do 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 " function of () return 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 " wskaźnik do 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 " tablica 10 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 " reference to 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 . Wynika to z zasady przypadku podstawowego w §8.3 / 5.

I skończyliśmy!

Więc teraz, jeśli spojrzymy na typ, który określiliśmy na każdym kroku, zastępując s z każdego poziomu poniżej, możemy Określ typ 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)

  1. Jaki jest typ x w deklaracji bool **(*x)[123];?

    "pointer to array of 123 pointer to pointer to bool"

  2. Jakie są rodzaje y i z w deklaracji int 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ć!

 69
Author: Joseph Mansfield,
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.

 0
Author: quantdaddy,
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