netlib.narod.ru< Назад | Оглавление | Далее >

A8. Объявления

Способ интерпретации каждого идентификатора, определяется объявлениями; они не всегда резервируют память для описываемых ими идентификаторов. Объявления, резервирующие память, называются определениями и имеют следующий вид:

объявление:
    спецификаторы-объявления список-инициализаторов-объявителейнеоб

Объявители в списке-инициализаторов-объявителей содержат объявляемые идентификаторы; спецификаторы-объявления представляют собой последовательности, состоящие из спецификаторов типа и класса памяти.

спецификаторы-объявления:
    спецификатор-класса-памяти спецификаторы-объявлениянеоб
    спецификатор-типа спецификаторы-объявлениянеоб
    квалификатор-типа спецификаторы-объявлениянеоб

список-инициализаторов-объявителей:
    инициализатор-объявитель
    список-инициализаторов-объявителей , инициализатор-объявитель

инициализатор-объявитель:
    объявитель
    объявитель = инициализатор

Объявители содержат подлежащие объявлению имена. Мы рассмотрим их позже, в A8.5. Объявление должно либо иметь по крайней мере один объявитель, либо его спецификатор типа должен определять тег структуры или объединения, либо — задавать элементы перечисления; пустое объявление недопустимо.

A8.1. Спецификаторы класса памяти

Класс памяти специфицируется следующим образом:

спецификатор-класса-памяти:
    auto
    register
    static
    extern
    typedef

Смысл классов памяти обсуждался в A4.

Спецификаторы auto и register дают объявляемым объектам класс автоматической памяти, и эти спецификаторы можно применять только внутри функции. Объявления с auto и register одновременно являются определениями и резервируют память. Спецификатор register эквивалентен auto, но содержит подсказку, сообщающую, что в программе объявленные им объекты используются интенсивно. В регистрах может быть размещено лишь небольшое число объектов, причем определенного типа: указанные ограничения зависят от реализации. В любом случае к register-объекту нельзя применять (явно или неявно) унарный оператор &.

Новым является правило, согласно которому вычислять адрес объекта с классом памяти register нельзя, а с классом памяти auto можно.

Спецификатор static дает объявляемым объектам класс статической памяти, он может использоваться и внутри, и вне функций. Внутри функции этот спецификатор вызывает выделение памяти и служит определением; его роль вне функций будет объяснена в A11.2.

Объявление со спецификатором extern, используемое внутри функции, объявляет, что для объявляемого объекта где-то выделена память; о ее роли вне функций будет сказано в A11.2.

Спецификатор typedef не резервирует никакой памяти и назван спецификатором класса памяти из соображений стандартности синтаксиса; речь об этом спецификаторе пойдет в A8.9.

Объявление может содержать не более одного спецификатора класса памяти. Если он в объявлении отсутствует, то действуют следующие правила: считается, что объекты, объявляемые внутри функций, имеют класс auto; функции, объявляемые внутри функций, — класс extern; объекты и функции, объявляемые вне функций, — статические и имеют внешние связи (см. A10, A11).

А8.2. Спецификаторы типа

Спецификаторы типа определяются следующим образом:

спецификатор-типа:
    void
    char
    short
    int
    long
    float
    double
    signed
    unsigned
    спецификатор-структуры-или-объединения
    спецификатор-перечисления
    typedef-имя

Вместе с int допускается использование еще какого-то одного слова — long или short; причем сочетание long int имеет тот же смысл, что и просто long: аналогично short int — то же самое, что и short. Слово long может употребляться вместе с double. С int и другими его модификациями (short, long или char) разрешается употреблять одно из слов signed или unsigned. Любое из последних может использоваться самостоятельно, в этом случае подразумевается int.

Спецификатор signed бывает полезен, когда требуется обеспечить, чтобы объекты типа char рассматривались как числа со знаком; его можно применять и к другим целочисленным типам, но в этих случаях он избыточен.

За исключением описанных выше случаев объявление не может содержать более одного спецификатора типа. Если в объявлении нет ни одного спецификатора типа, то имеется в виду тип int.

Для указания особых свойств объявляемых объектов предназначаются квалификаторы:

квалификатор-типа:
    const
    volatile

Квалификаторы типа могут употребляться с любым спецификатором типа. Разрешается инициализировать объявленный с квалификатором const объект, однако присваивать ему что-либо в дальнейшем запрещается. Смысл квалификатора volatile зависит от реализации.

Средства const и volatile (изменчивый) введены стандартом ANSI. Квалификатор const применяется, чтобы разместить объекты в памяти, доступной только для чтения (ПЗУ), или чтобы способствовать возможной оптимизации. Назначение квалификатора volatile — подавить оптимизацию, которая без этого указания могла бы быть проведена. Например, в машинах, где адреса регистров ввода-вывода отображены на адресное пространство памяти, указатель на регистр некоторого устройства мог бы быть объявлен как volatile, чтобы запретить компилятору экономить очевидно избыточную ссылку через указатель. Компилятор может игнорировать указанные квалификаторы, однако обязан сигнализировать о явных попытках изменить значение объектов, объявленных с квалификатором const.

A8.3. Объявления структур и объединений

Структура — это объект, состоящий из последовательности именованных элементов различных типов. Объединение — объект, который в каждый момент времени содержит один из нескольких элементов различных типов. Объявления структур и объединений имеют один и тот же вид.

спецификатор структуры-или-объединения:
    структура-или-объединение идентификаторнеоб {список-объявлений-структуры}
    структура-или-объединение идентификатор

структура-или-объединение:
    struct
    union

Список-объявлений-структуры является последовательностью объявлений элементов структуры или объединения:

список-объявлений-структуры:
    объявление-структуры
    список-объявлений-структуры объявление-структуры

объявление-структуры:
    список-спецификаторов-квалификаторов список-объявителей-структуры;

список-спецификаторов-квалификаторов:
    спецификатор-типа список-спецификаторов-квалификаторовнеоб
    квалификатор-типа список-спецификаторов-квалификаторовнеоб

список-объявителей-структуры:
    объявитель-структуры
    список-объявителей-структуры , объявитель-структуры

Обычно объявление-структуры является просто объявлением для элементов структуры или объединения. Элементы структуры, в свою очередь, могут состоять из заданного числа разрядов (битов). Такой элемент называется битовым полем или просто полем. Его размер отделяется от имени поля двоеточием:

структуры-объявитель:
    объявитель
    объявительнеоб : константное-выражение

Спецификатор типа, имеющий вид

структура-или-объединение идентификатор { список-объявлений-структуры }

объявляет идентификатор тегом структуры или объединения, специфицированных списком. Последующее объявление в той же или внутренней области видимости может обращаться к тому же типу, используя в спецификаторе тег без списка:

структура-или-объединение идентификатор

Если спецификатор с тегом, но без списка появляется там, где тег не объявлен, специфицируется незавершенный тип. Объекты с незавершенным типом структуры или объединения могут упоминаться в контексте, где не требуется знать их размер — например в объявлениях (но не определениях) для описания указателя или создания typedef, но не в иных случаях. Тип становится завершенным при появлении последующего спецификатора с этим тегом, содержащего список объявлений. Даже в спецификаторах со списком объявляемый тип структуры или объединения является незавершенным внутри списка и становится завершенным только после появления символа }, заканчивающего спецификатор.

Структура не может содержать элементов незавершенного типа. Следовательно, невозможно объявить структуру или объединение, которые содержат сами себя. Однако, кроме придания имени типу структуры или объединения, тег позволяет определять структуры, обращающиеся сами к себе; структура или объединение могут содержать указатели на самих себя, поскольку указатели на незавершенные типы объявлять можно.

Особое правило применяется к объявлениям вида

структура-или-объединение идентификатор;

которые объявляют структуру или объединение, но не имеют списка объявления и объявителя. Даже если идентификатор имеет тег структуры или объединения во внешней области видимости (A11.1), это объявление делает идентификатор тегом новой структуры или объединения незавершенного типа во внутренней области видимости.

Это невразумительное правило — новое в ANSI. Оно предназначено для взаимно рекурсивных структур, объявленных во внутренней области видимости, но теги которых могут быть уже объявлены во внешней области видимости.

Спецификатор структуры или объединения со списком, но без тега создает уникальный тип, к которому можно обращаться непосредственно только в объявлении, частью которого он является.

Имена элементов и тегов не конфликтуют друг с другом или обычными переменными. Имя элемента не может появляться дважды в одной и той же структуре или объединении, но тот же элемент можно использовать в разных структурах или объединениях.

В первой редакции этой книги имена элементов структуры и объединения не связывались со своими родителями. Однако в компиляторах эта связь стала обычной задолго до появления стандарта ANSI.

Элемент структуры или объединения, не являющийся полем, может иметь любой тип объекта. Поле (которое не имеет объявителя и, следовательно, может быть безымянным) имеет тип int, unsigned int или signed int и интерпретируется как объект целочисленного типа указанной в битах длины. Считается ли поле int знаковым или беззнаковым, зависит от реализации. Соседний элемент-поле упаковывается в ячейки памяти в зависимости от реализации в зависящем от реализации направлении. Когда следующее за полем другое поле не влезает в частично заполненную ячейку памяти, оно может оказаться разделенным между двумя ячейками, или ячейка может быть забита балластом. Безымянное поле нулевой ширины обязательно приводит к такой забивке, так что следующее поле начнется с края следующей ячейки памяти.

Стандарт ANSI делает поля еще более зависимыми от реализации, чем в первой редакции книги. Чтобы хранить битовые поля в «зависящем от реализации» виде без квалификации, желательно прочитать правила языка. Структуры с битовыми полями могут служить переносимым способом для попытки уменьшить размеры выделенной под структуру памяти (вероятно, ценой увеличения размера кода программы и времени на доступ к полям) или непереносимым способом для описания распределения памяти на битовом уровне. Во втором случае необходимо понимать правила местной реализации.

Элементы структуры имеют возрастающие по мере объявления элементов адреса. Элементы структуры, не являющиеся полями, выравниваются по границам адресов в зависимости от своего типа; таким образом, в структуре могут быть безымянные дыры. Если указатель на структуру приводится к типу указателя на ее первый элемент, результат указывает на первый элемент.

Объединение можно представить себе как структуру, все элементы которой начинаются со смещением 0 и размеры которой достаточны для хранения любого из элементов. В любой момент времени в объединении хранится не больше одного элемента. Если указатель на объединение приводится к типу указателя на один из элементов, результат указывает на этот элемент.

Вот простой пример объявления структуры:

  struct tnode {
      char tword[20];
      int count;
      struct tnode *left;
      struct tnode *right;
  };

Эта структура содержит массив из 20 символов, число типа int и два указателя на такую же структуру. Если в программе есть такое объявление, то

  struct tnode s, *sp;

объявит s как структуру заданного вида, a sp — как указатель на такую структуру. Согласно приведенным определениям выражение

  sp->count

обращается к элементу count структуры, на которую указывает sp;

  s.left

указатель на левое поддерево в структуре s, а

  s.right->tword[0] 

это первый символ из tword — элемента правого поддерева s.

В общем случае невозможно проконтролировать, тот ли используется элемент объединения, которому последний раз присваивалось значение. Однако гарантируется выполнение правила, облегчающего работу с элементами объединения: если объединение содержит несколько структур, начинающихся с общей для них последовательности данных, и если объединение в текущий момент содержит одну из этих структур, то к общей части данных разрешается обращаться через любую из указанных структур. Так, правомерен следующий фрагмент программы:

  union {
      struct {
          int type;
      } n;

      struct {
          int type;
          int intnode;
      } ni;

      struct {
          int type;
          float floatnode;
      } nf;
  } u;

  ...

  u.nf.type = FLOAT;
  u.nf.floatnode = 3.14;
  ...

  if (u.n.type == FLOAT)
    ... sin(u.nf.floatnode) ...

A8.4. Перечисления

Перечисления — это уникальный тип, значения которого покрываются множеством именованных констант, называемых перечислителями. Вид спецификатора перечисления заимствован у структур и объединений.

спецификатор-перечисления:
    enum идентификаторнеоб {список-перечислителей}
    enum идентификатор

список-перечислителей:
    перечислитель
    список-перечислителей , перечислитель

перечислитель:
    идентификатор 
    идентификатор = константное-выражение

Идентификаторы, входящие в список перечислителей, объявляются константами типа int и могут употребляться везде, где требуется константа. Если в этом списке нет ни одного перечислителя со знаком =, то значения констант начинаются с 0 и увеличиваются на 1 по мере чтения объявления слева направо. Перечислитель со знаком = дает соответствующему идентификатору значение; последующие идентификаторы продолжают прогрессию от заданного значения.

Имена перечислителей, используемые в одной области видимости, должны отличаться друг от друга и от имен обычных переменных, однако их значения могут и совпадать.

Роль идентификатора в спецификаторе-перечисления аналогична роли тега структуры в спецификаторе структуры-или-объединения: он является именем некоторого конкретного перечисления. Правила для списков и спецификаторов перечисления (с тегами и без) те же, что и для спецификаторов структур или объединений, с той лишь оговоркой, что элементы перечислений не бывают незавершенного типа; тег спецификатора-перечисления без списка перечислителей должен иметь в пределах области видимости спецификатор со списком.

В первой версии языка перечислений не было, но они применяются в языке уже несколько лет.

А8.5. Объявители

Объявители имеют следующий синтаксис:

объявитель:
    указательнеоб непосредтвенный-объявитель

собственно-объявитель:
    идентификатор
    (объявитель)
    непосредственный-объявитель [константное-выражениенеоб]
    непосредственный-объявитель (список-типов-параметров)
    непосредственный-объявитель (список-идентификаторовнеоб)

указатель:
    * список-квалификаторов-типанеоб
    * список-квалификаторов-типанеоб указатель

список-квалификаторов-типа:
    квалификатор-типа
    список-квалификаторов-типа квалификатор-типа

У структуры объявителя много сходных черт со структурой подвыражений, поскольку в объявителе, как и в подвыражении, допускаются операции косвенного обращения, обращения к функции и получения элемента массива (с тем же порядком применения).

A8.6. Что означают объявители

Список объявителей располагается сразу после спецификаторов типа и указателя класса памяти. Главный элемент любого объявителя — это объявляемый им идентификатор; в простейшем случае объявитель из него одного и состоит, что отражено в строке грамматики с именем непосредственный-объявитель. Спецификаторы класса памяти относятся непосредственно к идентификатору, а его тип зависит от вида объявителя. Объявитель следует воспринимать как утверждение: если в выражении идентификатор появляется в том же контексте, что и в объявителе, то он обозначает объект специфицируемого типа.

Если соединить спецификаторы объявления, относящиеся к типу (A8.2), и некоторый конкретный объявитель, то объявление примет вид "T D", где T — тип, a D — объявитель. Эта запись индуктивно придает тип идентификатору любого объявителя.

В объявлении T D, где D — просто идентификатор, тип идентификатора есть T.

В объявлении T D, где D имеет вид

  ( D1 )

тип идентификатора в D1 тот же, что и в D. Скобки не изменяют тип, но могут повлиять на результаты его «привязки» к идентификаторам в сложных объявителях.

A8.6.1. Объявители указателей

В объявлении T D, где D имеет вид

  * список-квалификаторов-типанеоб. D1

а тип идентификатора объявления T D1 есть «модификатор-типа T», тип идентификатора D есть «модификатор-типа список-квалификаторов-типа указатель на T». Квалификаторы, следующие за *, относятся к самому указателю, а не к объекту, на который он указывает. Рассмотрим, например, объявление

  int *ap[];

Здесь ap[] играет роль D1; объявление int ap[] следует расшифровать (см. ниже) как «массив из int»: список квалификаторов типа здесь пуст, а модификатор типа есть «массив из». Следовательно, на самом деле объявление ap гласит: «массив из указателей на int». Вот еще примеры объявлений:

  int i, *pi, *const cpi = &i;
  const int ci = 3, *pci;

В них объявляются целое i и указатель на целое pi. Значение указателя cpi неизменно; cpi всегда будет указывать в одно и то же место, даже если значение, на которое он указывает, станет иным. Целое ci есть константа, оно измениться не может (хотя может инициализироваться, как в данном случае). Тип указателя pci произносится как «указатель на const int»; сам указатель можно изменить; при этом он будет указывать на другое место, но значение, на которое он будет указывать, с помощью pci изменить нельзя.

А8.6.2. Объявители массивов

В объявлении T D, где D имеет вид

  D1 [константное-выражениенеоб]

и где тип идентификатора объявления T D1 есть «модификатор-типа Т», тип идентификатора D есть «модификатор-типа массив из T». Если константное выражение присутствует, то оно должно быть целочисленным и больше 0. Если константное выражение, специфицирующее количество элементов в массиве, отсутствует, то массив имеет незавершенный тип.

Массив можно конструировать из объектов арифметического типа, указателей, структур и объединений, а также других массивов (генерируя при этом многомерные массивы). Любой тип, из которого конструируется массив, должен быть завершенным, он не может быть, например, структурой или массивом незавершенного типа. Это значит, что для многомерного массива пустой может быть только первая размерность. Незавершенный тип массива получает свое завершение либо в другом объявлении этого массива (A10.2), либо при его инициализации (A8.7). Например, запись

  float fa[17], *afp[17];

объявляет массив из чисел типа float и массив из указателей на числа типа float. Аналогично

  static int x3d[3][5][7];

объявляет статический трехмерный массив целых размера 3 x 5 x 7. На самом деле, если быть точными, x3d является массивом из трех элементов, каждый из которых есть массив из пяти элементов, содержащих по 7 значений типа int.

Операция индексирования E1[E2] определена так, что она идентична операции *(E1+E2). Следовательно, несмотря на асимметричность записи, индексирование — коммутативная операция. Учитывая правила преобразования, применяемые для оператора + и массивов (A6.6, A7.1, A7.7), можно сказать, что если E1 — массив, а E2 — целое, то E1[E2] обозначает E2-й элемент массива E1.

Так, x3d[i][j][k] означает то же самое, что и *(x3d[i][j]+k). Первое подвыражение, x3d[i][j], согласно A7.1, приводится к типу «указатель на массив целых»; по A7.7 сложение включает умножение на размер объекта типа int. Из этих же правил следует, что массивы запоминаются «построчно» (последние индексы меняются чаще) и что первая размерность в объявлении помогает определить количество памяти, занимаемой массивом, однако в вычислении адреса элемента массива участия не принимает.

А8.6.3. Объявители функций

В новом способе объявление функции T D, где D имеет вид

  D1(список-типов-параметров)

и тип идентификатора объявления T D1 есть «модификатор-типа T», тип идентификатора в D есть «модификатор-типа функция с аргументами список-типов-параметров, возвращающая T». Параметры имеют следующий синтаксис:

  список-типов-параметров:
      список-параметров
      список-параметров , ...

  список-параметров:
      объявление-параметра
      список-параметров, объявление-параметра

  объявление-параметра:
      спецификаторы-объявления объявитель
      спецификатор-объявления абстрактный-объявительнеоб

При новом способе объявления функций список параметров специфицирует их типы, а если функция вообще не имеет параметров, на месте списка типов указывается одно слово — void. Если список типов параметров заканчивается многоточием «,...», то функция может иметь больше аргументов, чем число явно описанных параметров (см. A7.3.2.)

Типы параметров, являющихся массивами и функциями, заменяются на указатели в соответствии с правилами преобразования параметров (A10.1). Единственный спецификатор класса памяти, который разрешается использовать в объявлении параметра, — это register, однако он игнорируется, если объявитель функции не является заголовком ее определения. Аналогично, если объявители в объявлениях параметров содержат идентификаторы, а объявитель функции не является заголовком определения функции, то эти идентификаторы тотчас же выводятся из текущей области видимости.

При старом способе объявление функции T D, где D имеет вид

  D1(список-идентификаторовнеоб)

и тип идентификатора объявления T D1 есть «модификатор-типа Т», тип идентификатора в D есть «модификатор-типа функция от неспецифицированных аргументов, возвращающая Т». Параметры, если они есть, имеют следующий вид:

  список-идентификаторов:
      идентификатор
      список-идентификаторов, идентификатор

При старом способе, если объявитель функции не используется в качестве заголовка определения функции (A10.1), список идентификаторов должен отсутствовать. Никакой информации о типах параметров в объявлениях не содержится.

Например, объявление

  int f(), *fpi(), (*pfi)();

объявляет функцию f, возвращающую число типа int, функцию fpi, возвращающую указатель на число типа int, и указатель pfi на функцию, возвращающую число типа int. Ни для одной функции в объявлении не указаны типы параметров; все функции описаны старым способом.

Вот как выглядит объявление в новой записи:

  int strcpy(char *dest, const char *source), rand(void);

Здесь strcpy — функция с двумя аргументами, возвращающая значение типа int; первый аргумент — указатель на значение типа char, а второй — указатель на неизменяющееся значение типа char. Имена параметров играют роль хороших комментариев. Вторая функция, rand, аргументов не имеет и возвращает значение типа int.

Объявители функций с прототипами параметров — наиболее важное нововведение стандарта ANSI. В сравнении со старым способом, принятым в первой редакции языка, они позволяют проверять и приводить к нужному типу аргументы во всех вызовах. Следует однако отметить, что их введение привнесло в язык некоторую сумятицу и необходимость согласования обеих форм. Чтобы обеспечить совместимость, потребовались некоторые «синтаксические уродства», а именно void, для явного указания на отсутствие параметров.

Многоточие «, ...» применительно к функциям с варьируемым числом аргументов — также новинка, которая вместе со стандартным заголовочным файлом макросов <stdarg.h> формализует неофициально используемый, но официально запрещенный в первой редакции механизм. Указанные способы записи заимствованы из языка C++.

A8.7. Инициализация

С помощью инициализирующего объявителя можно указать начальное значение объявляемого объекта. Инициализатору, представляющему собой выражение или список инициализаторов, заключенный в фигурные скобки, предшествует знак =. Этот список может завершаться запятой; ее назначение сделать форматирование более четким.

  инициализатор:
      выражение-присваивания
      { список-инициализаторов }
      { список-инициализаторов, }

  список-инициализаторов:
      инициализатор
      список-инициализаторов , инициализатор

В инициализаторе статического объекта или массива все выражения должны быть константными (A7.19). Если инициализатор автоматического или регистрового объекта или массива находится в списке, заключенном в фигурных скобки, то входящие в него выражения также должны быть константными. Однако в случае автоматического объекта с одним выражением инициализатор не обязан быть константным выражением, он просто должки иметь соответствующий объекту тип.

В первой редакции не разрешалась инициализация автоматических структур, объединений и массивов. Стандарт ANSI позволяет это; однако, если инициализатор не может быть представлен одним простым выражением, инициализация может быть выполнена только с помощью константных конструкций.

Статический объект, инициализация которого явно не указана, инициализируется так, как если бы ему (или его элементам) присваивалась константа 0. Начальное значение автоматического объекта, явным образом не инициализированного, не определено.

Инициализатор указателя или объекта арифметического типа — это одно выражение (возможно, заключенное в фигурные скобки), которое присваивается объекту.

Инициализатор структуры — это либо выражение того же структурного типа, либо заключенные в фигурные скобки инициализаторы ее элементов, заданные по порядку. Безымянные битовые поля игнорируются и не инициализируются. Если инициализаторов в списке меньше, чем элементов, то оставшиеся элементы инициализируются нулем. Инициализаторов не должно быть больше числа элементов.

Инициализатор массива — это список инициализаторов его элементов, заключенный в фигурные скобки. Если размер массива не известен, то он считается равным числу инициализаторов, при этом тип его становится завершенным. Если размер массива известен, то число инициализаторов не должно превышать числа его элементов; если инициализаторов меньше, оставшиеся элементы обнуляются.

Как особый выделен случай инициализации массива символов. Последний можно инициализировать с помощью строкового литерала; символы инициализируют элементы массива в том порядке, как они заданы в строковом литерале. Точно так же, с помощью литерала из расширенного набора символов (A2.6), можно инициализировать массив типа wchar_t. Если размер массива не известен, то он определяется числом символов в строке, включая и завершающий символ NULL; если размер массива известен, то число символов в строке, не считая завершающего символа NULL, не должно превышать его размера.

Инициализатором объединения может быть либо выражение того же типа, либо заключенный в фигурные скобки инициализатор его первого элемента.

В первой версии языка не позволялось инициализировать объединения. Правило «первого элемента» не отличается изяществом, однако не требует нового синтаксиса. Стандарт ANSI проясняет еще и семантику не инициализируемых явно объединений.

Введем для структуры и массива обобщенное имя: агрегат. Если агрегат содержит элементы агрегатного типа, то правила инициализации применяются рекурсивно. Фигурные скобки в некоторых случаях инициализации можно опускать. Если инициализатор элемента агрегата, который сам является агрегатом, начинается с левой фигурной скобки, то этот подагрегат инициализируется последующим списком разделенных запятыми инициализаторов; считается ошибкой, если количество инициализаторов подагрегата превышает число его элементов. Если, однако, инициализатор подагрегата не начинается с левой фигурной скобки, то чтобы его инициализировать, нужно отсчитать соответствующее число элементов из списка; при этом остальные элементы инициализируются следующими инициализаторами агрегата, для которого данный подагрегат является частью.

Например

  int x[] = { 1, 3, 5 };

объявляет и инициализирует x как одномерный массив с тремя элементами, поскольку размер не был указан, а список состоит из трех инициализаторов.

  float y[4][3] = {
      { 1, 3, 5 },
      { 2, 4, 6 },
      { 3, 5, 7 },
  };

представляет собой инициализацию с полным набором фигурных скобок: 1, 3 и 5 инициализируют первую строку в массиве y[0], т. е. y[0][0], у[0][1] и y[0][2]. Аналогично инициализируются следующие две строки: y[1] и y[2]. Инициализаторов не хватило на весь массив, поэтому элементы строки y[3] будут нулевыми. В точности тот же результат был бы достигнут с помощью следующего объявления:

  float у[4][3] = {
      1, 3, 5, 2, 4, 6, 3, 5, 7
  };

Инициализатор для y начинается с левой фигурной скобки, но для y[0] скобки нет, поэтому из списка будут взяты три элемента. Аналогично по три элемента будут взяты для y[1], а затем и для y[2]. В

  float у[4][3] = {
      { 1 }, { 2 }, { 3 }, { 4 }
  };

инициализируется первый столбец матрицы y, все же другие элементы остаются нулевыми.

Наконец,

  char msg[] = "Синтаксическая ошибка в строке %s\n";

представляет собой пример массива символов, элементы которого инициализируются с помощью строки; в его размере учитывается и завершающий символ NULL.

A8.8. Имена типов

В ряде случаев возникает потребность в применении имени типа данных (например при явном приведении к типу, в указании типов параметров внутри объявлений функций, в аргументе оператора sizeof). Эта потребность реализуется с помощью имени типа, определение которого синтаксически почти совпадает с объявлением объекта того же типа. Оно отличается от объявления лишь тем, что не содержит имени объекта.

  имя-типа:
      список-спецификаторов-квалификаторов абстрактный-объявительнеоб

  абстрактный-объявитель:
      указатель
      указательнеоб собственно-абстрактный-объявитель

  собственно-абстрактный-объявитель:
      ( абстрактный-объявитель )
      собственно-абстрактный-объявительнеоб[константное-выражениенеоб]
      собственно-абстрактный-объявительнеоб(список-типов-параметровнеоб)

Можно указать одно-единственное место в абстрактном объявителе, где мог бы оказаться идентификатор, если бы данная конструкция была полноценным объявителем. Именованный тип совпадает с типом этого «невидимого идентификатора». Например

  int
  int *
  int *[3]
  int (*)[]
  int *()
  int (*[])(void)

соответственно обозначают типы int, «указатель на int», «массив из трех указателей на int», «указатель на массив из неизвестного количества int», «функция c неизвестным количеством параметров, возвращающая указатель на int», «массив из неизвестного количества указателей на функции без параметров, каждая из которых возвращает int».

А8.9. Объявление typedef

Объявления, в которых спецификатор класса памяти есть typedef, не объявляют объектов — они определяют идентификаторы, представляющие собой имена типов. Эти идентификаторы называются typedef-именами.

  typedef-имя:
      идентификатор

Объявление typedef приписывает тип каждому имени своего объявителя обычным способом (см. A8.6.). С этого момента typedef-имя синтаксически эквивалентно ключевому слову спецификатора типа, обозначающему связанный с ним тип. Например, после

  typedef long Blockno, *Blockptr;
  typedef struct { double r, theta; } Complex;

допустимы следующие объявления:

  Blockno b;
  extern Blockptr bp;
  Complex z, *zp;

b принадлежит типу long, bp — типу «указатель на long»; z — это структура заданного вида, a zp — принадлежит типу «указатель на такую структуру».

Объявление typedef не вводит новых типов, оно только дает имена типам, которые могли бы быть специфицированы и другим способом. Например, b имеет тот же тип, что и любой другой объект типа long.

typedef-имена могут быть перекрыты другими определениями во внутренней области видимости, но при условии, что в них присутствует указание типа. Например

  extern Blockno;

не переобъявляет Blockno, а вот

  extern int Blockno;

переобъявляет.

A8.10. Эквивалентность типов

Два списка спецификаторов типа эквивалентны, если они содержат одинаковый набор спецификаторов типа с учетом синонимичности названий (например, long и int long считаются одинаковыми типами). Структуры, объединения и перечисления с разными тегами считаются разными, а каждое безтеговое объединение, структура или перечисление представляет собой уникальный тип.

Два типа считаются совпадающими, если их абстрактные объявители (A8.8) после замены всех typedef-имен их типами и выбрасывания имен параметров функций составят эквивалентные списки спецификаторов типов. При сравнении учитываются размеры массивов и типы параметров функций.


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz