Главная страница
Навигация по странице:

  • A 8.4. Перечисления

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

  • А 8.6. Что означают объявители

  • А 8.6.1. Объявители указателей

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

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

  • Язык программирования Си Брайан Керниган, Деннис Ритчи 3е издание Версия 1 Table of Contents


    Скачать 2.33 Mb.
    НазваниеЯзык программирования Си Брайан Керниган, Деннис Ритчи 3е издание Версия 1 Table of Contents
    Дата18.09.2022
    Размер2.33 Mb.
    Формат файлаpdf
    Имя файлаBrian_Kernighan_Dennis_Ritchie-The_C_Programming_Language-RU.pdf
    ТипДокументы
    #683263
    страница25 из 31
    1   ...   21   22   23   24   25   26   27   28   ...   31
    А 8.3. Объявления структур и объединений
    Структура — это объект, состоящий из последовательности именованных элементов различных типов.
    Объединение — объект, который в каждый момент времени содержит один из нескольких элементов различных типов. Объявления структур и объединений имеют один и тот же вид.
    структуры-или-объединения-спецификатор:
    структуры-или-объединения идентификатор
    необ
    { список-объявлений-структуры }
    структуры-или-объединения идентификатор
    структура-или-объединение: struct union
    Список-объявлений-структуры является последовательностью объявлений элементов структуры или объединения:
    список-объявлений-структуры:
    объявление-структуры
    список-объявлений-структуры объявление-структуры
    объявление-структуры:
    список-спецификаторов-квалификаторов список-структуры-объявителей ;

    список-спецификаторов-квалификаторов:
    спецификатор-типа список-спецификаторов-квалификаторов
    необ
    квалификатор-типа список-спецификаторов-квалификаторов
    необ
    список-структуры-объявителей:
    структуры-объявитель
    список-структуры-объявителей , структуры-объявитель
    Обычно объявление-структуры является просто объявлением для элементов структуры или объединения.
    Элементы структуры, в свою очередь, могут состоять из заданного числа разрядов (битов). Такой элемент называется битовым полем или просто полем. Его размер отделяется от имени поля двоеточием:
    структуры-объявитель:
    объявитель
    объявитель
    необ
    : константное-выражение
    Спецификатор типа, имеющий вид
    структуры-или-объединения идентификатор {список-объявлений-структуры } объявляет идентификатор тегом структуры или объединения, специфицированных списком. Последующее объявление в той же или внутренней области видимости может обращаться к тому же типу, используя в спецификаторе тег без списка:
    структуры-или-объединения идентификатор
    Если спецификатор с тегом, но без списка появляется там, где тег не объявлен, специфицируется
    незавершенный тип. Объекты с незавершенным типом структуры или объединения могут упоминаться в контексте, где не требуется знать их размер — например в объявлениях (но не определениях) для описания указателя или создания typedef
    , но не в иных случаях. Тип становится завершенным при появлении последующего спецификатора с этим тегом, содержащего список объявлений. Даже в спецификаторах со списком объявляемый тип структуры или объединения является незавершенным внутри списка и становится завершенным только после появления символа
    }
    , заканчивающего спецификатор.
    Структура не может содержать элементов незавершенного типа. Следовательно, невозможно объявить структуру или объединение, которые содержат сами себя. Однако, кроме придания имени типу структуры или объединения, тег позволяет определять структуры, обращающиеся сами к себе; структура или объединение могут содержать указатели на самих себя, поскольку указатели на незавершенные типы объявлять можно.
    Особое правило применяется к объявлениям вида
    структуры-или-объединения идентификатор ; которые объявляют структуру или объединение, но не имеют списка объявления и объявителя. Даже если идентификатор имеет тег структуры или объединения во внешней области видимости (А11.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
    ; a 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) ...
    A 8.4. Перечисления
    Перечисления — это уникальный тип, значения которого покрываются множеством именованных констант, называемых перечислителями. Вид спецификатора перечисления заимствован у структур и объединений.
    спецификатор-перечисления: enum идентификатор
    необ
    , { список-перечислителей } enum идентификатор
    список-перечислителей:
    перечислитель
    список-перечислителей , перечислитель
    перечислитель:
    идентификатор
    идентификатор = константное-выражение
    Идентификаторы, входящие в список перечислителей, объявляются константами типа int и могут употребляться везде, где требуется константа. Если в этом списке нет ни одного перечислителя со знаком
    =
    , то значения констант начинаются с 0 и увеличиваются на 1 по мере чтения объявления слева направо.
    Перечислитель со знаком
    =
    даёт соответствующему идентификатору значение; последующие идентификаторы продолжают прогрессию от заданного значения.

    Имена перечислителей, используемые в одной области видимости, должны отличаться друг от друга и от имен обычных переменных, однако их значения могут и совпадать.
    Роль идентификатора в переч-спецификаторе аналогична роли тега структуры в структ-спецификаторе: он является именем некоторого конкретного перечисления. Правила для списков и переч-спецификаторов (с тегами и без) те же, что и для спецификаторов структур или объединений, с той лишь оговоркой, что элементы перечислений не бывают незавершенного типа; тег переч-спецификатора без списка перечислителей должен иметь в пределах области видимости спецификатор со списком.
    В первой версии языка перечислений не было, но они уже несколько лет применяются.
    А 8.5. Объявители
    Объявители имеют следующий синтаксис:
    объявитель:
    указатель
    необ
    собственно-объявитель
    собственно-объявитель:
    идентификатор
    ( объявитель )
    собственно-объявитель [ константное-выражение
    необ
    ]
    собственно-объявитель ( список-типов-параметров )
    собственно-объявитель ( список-идентификаторов
    необ
    )
    указатель:
    * список-квалификаторов-типа
    необ
    * список-квалификаторов-типа
    необ
    указатель
    список-квалификаторов-типа:
    квалификатор-типа
    список-квалификаторов-типа квалификатор-типа
    У структуры объявителя много сходных черт со структурой подвыражений, поскольку в объявителе, как и в подвыражении, допускаются операции косвенного обращения, обращения к функции и получения элемента массива (с тем же порядком применения).
    А 8.6. Что означают объявители
    Список объявителей располагается сразу после спецификаторов типа и указателя класса памяти. Главный элемент любого объявителя — это объявляемый им идентификатор; в простейшем случае объявитель из него одного и состоит, что отражено в первой строке продукции грамматики с именем собственно-объявителъ.
    Спецификаторы класса памяти относятся непосредственно к идентификатору, а его тип зависит от вида объявителя. Объявитель следует воспринимать как утверждение: если в выражении идентификатор появляется в том же контексте, что и в объявителе, то он обозначает объект специфицируемого типа.
    Если соединить спецификаторы объявления, относящиеся к типу (А8.2), и некоторый конкретный объявитель, то объявление примет вид "
    Т D
    ", где
    Т
    — тип, а
    D
    — объявитель. Эта запись индуктивно придает тип идентификатору любого объявителя.
    В объявлении
    Т D
    , где
    D
    — просто идентификатор, тип идентификатора есть
    Т
    В объявлении
    Т D
    , где
    D
    имеет вид
    ( D1 )
    тип идентификатора в
    D1
    тот же, что и в
    D
    . Скобки не изменяют тип, но могут повлиять на результаты его "привязки" к идентификаторам в сложных объявителях.
    А 8.6.1. Объявители указателей
    В объявления
    Т D
    , где
    D
    имеет вид
    * список-квалификаторов-типа
    необ
    D1 а тип идентификатора объявления
    Т D1
    есть "
    модификатор-типа Т
    ", тип идентификатора
    D
    есть "
    модификатор-типа список-квалификаторов-типа указатель на Т
    ". Квалификаторы, следующие за
    *
    , относятся к самому указателю, а не к объекту, на который он указывает. Рассмотрим, например, объявление int *ap[];
    Здесь ар[]
    играет роль
    D1
    ; объявление int ap[]
    следует расшифровать (см. ниже) как "
    массив из int
    "; список квалификаторов типа здесь пуст, а модификатор типа есть "
    массив из ". Следовательно, на самом деле объявление ар гласит: "
    массив из указателей на int
    ". Вот еще примеры объявлений: int i, *pi, *const cpi = &i; const int ci = 3, *pci;
    В них объявляются целое i
    и указатель на целое pi
    . Значение указателя cpi неизменно; cpi всегда будет указывать в одно и то же место, даже если значение, на которое он указывает, станет иным. Целое ci есть константа, оно измениться не может (хотя может инициализироваться, как в данном случае). Тип указателя pci произносится как "
    указатель на const int
    "; сам указатель можно изменить; при этом он будет указывать на другое место, но значение, на которое он будет указывать, с помощью pci изменить нельзя.
    А 8.6.2. Объявители массивов
    В объявлениях
    Т D
    , где
    D
    имеет вид
    D1 [ константное-выражение
    необ
    ] и где тип идентификатора объявления
    Т D1
    есть "
    модификатор-типа Т
    ", тип идентификатора D есть "
    модификатор-типа массив из Т
    ". Если константное выражение присутствует, то оно должно быть целочисленным и больше 0. Если константное выражение, специфицирующее количество элементов в массиве, отсутствует, то массив имеет незавершенный тип.
    Массив можно конструировать из объектов арифметического типа, указателей, структур и объединений, а также других массивов (генерируя при этом многомерные массивы). Любой тип, из которого конструируется массив, должен быть завершенным, он не может быть, например, структурой или массивом незавершенного типа. Это значит, что для многомерного массива пустой может быть только первая размерность.
    Незавершенный тип массива получает свое завершение либо в другом объявлении этого массива (А10.2), либо при его инициализации (А8.7). Например, запись float fa[17], *afp[17]; объявляет массив из чисел типа float и массив из указателей на числа типа float
    . Аналогично static int x3d[3][5][7]; объявляет статический трехмерный массив целых размера 3x5x7. На самом деле, если быть точными, x3d является массивом из трех элементов, каждый из которых есть массив из пяти элементов, содержащих по 7 значений типа int

    Операция индексирования
    Е1[Е2]
    определена так, что она идентична операции
    *(Е1+Е2)
    . Следовательно, несмотря на асимметричность записи, индексирование — коммутативная операция. Учитывая правила преобразования, применяемые для оператора
    +
    и массивов (А6.6, А7.1, А7.7), можно сказать, что если
    Е1
    — массив, а
    Е2
    — целое, то
    Е1[Е2]
    обозначает
    Е2
    -й элемент массива
    Е1
    Так, x3d[i][j][k]
    означает то же самое, что и
    *(x3d[i][j]+k)
    . Первое подвыражение, x3d[i][j]
    , согласно А7.1, приводится к типу "указатель на массив целых"; по А7.7 сложение включает умножение на размер объекта типа int
    . Из этих же правил следует, что массивы запоминаются "построчно" (последние индексы меняются чаще) и что первая размерность в объявлении помогает определить количество памяти, занимаемой массивом, однако в вычислении адреса элемента массива участия не принимает.
    А 8.6.3. Объявители функций
    В новом способе объявление функции
    Т D
    , где
    D
    имеет вид
    D1 (список-типов-параметров) и тип идентификатора объявления
    Т D1 есть "
    модификатор-типа Т
    ", тип идентификатора в
    D
    есть "
    модификатор-типа функция с аргументами список-типов-параметров, возвращающая
    Т
    ". Параметры имеют следующий синтаксис:
    список-типов-параметров:
    список-параметров
    список-параметров , ...
    список-параметров:
    объявление-параметра
    список-параметров , объявление-параметра
    объявление-параметра:
    спецификаторы-объявления объявитель
    спецификаторы-объявления абстрактный-объявитель
    необ
    При новом способе объявления функций список параметров специфицирует их типы, а если функция вообще не имеет параметров, на месте списка типов указывается одно слово — void
    . Если список типов параметров заканчивается многоточием "
    , ...
    ", то функция может иметь больше аргументов, чем число явно описанных параметров. (См. А7.3.2.)
    Типы параметров, являющихся массивами и функциями, заменяются на указатели в соответствии с правилами преобразования параметров (А10.1). Единственный спецификатор класса памяти, который разрешается использовать в объявлении параметра, — это register
    , однако он игнорируется, если объявитель функции не является заголовком ее определения. Аналогично, если объявители в объявлениях параметров содержат идентификаторы, а объявитель функции не является заголовком определения функции, то эти идентификаторы тотчас же выводятся из текущей области видимости.
    При старом способе объявление функции
    Т D
    , где
    D
    имеет вид
    D1 ( список-идентификаторов
    необ
    ) и тип идентификатора объявления
    Т D1
    есть "
    модификатор-типа Т
    ", тип идентификатора в
    D
    есть "
    модификатор-типа функция от неспецифицированных аргументов, возвращающая Т
    ".
    Параметры, если они есть, имеют следующий вид:
    список-идентификаторов:
    идентификатор

    список-идентификаторов , идентификатор
    При старом способе, если объявитель функции не используется в качестве заголовка определения функции
    (А10.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, для явного указания на отсутствие параметров.
    Многоточие "
    , ...
    " применительно к функциям с варьируемым числом аргументов — также новинка, которая вместе со стандартным заголовочным файлом макросов

    формализует неофициально используемый, но официально запрещенный в первой редакции механизм.
    Указанные способы записи заимствованы из языка Си++.
    1   ...   21   22   23   24   25   26   27   28   ...   31


    написать администратору сайта