DigTeh.ru
Цифровая техника в радиосвязи

Вычислительная техника и информационные технологии

Цифровые устройства

Микропроцессоры

Схемо и системотехника электронных средств

Микропроцессоры и цифровая обработка сигналов

Устройства приема и обработки радиосигналов в системах подвижной радиосвязи

Устройства генерирования и формирования сигналов в системах подвижной связи

Проектирование печатных плат

Источники питания радиоэлектронной аппаратуры

Схемотехника современных телекоммуникационных устройств

Книги в электронном варианте

Справочные данные

Новости














Содержание дисциплины

Дата последнего обновления файла 10.06.2012

2. Типы данных и их объявление

В языке программирования C51 любая переменная должна быть объявлена до первого использования этой переменной. Как уже говорилось ранее этот язык программирования предназначен для написания программ для микроконтроллеров семейства MCS-51, поэтому в составе языка должна отображаться внутренняя структура этого семейства микроконтроллеров. Эти особенности отражены во введении новых типов данных. В остальном язык программирования C-51 не отличается от стандарта ANSI.

Объявление переменной в языке программирования C51 представляется в следующем виде:

   [спецификатор класса памяти]  спецификатор типа   описатель [=инициатор] [,описатель [= инициатор] ]...

Описатель - идентификатор простой переменной либо более сложная конструкция с квадратными скобками, круглыми скобками или звездочкой (набором звездочек).

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

Инициатор - задает начальное значение или список начальных значений, которые (которое) присваивается переменной при объявлении.

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

2.1 Категории типов данных

Ключевые слова для определения основных типов данных

     Целые типы :                 Плавающие типы:
       bit                            float
       sbit
       char
       int
       short
       long
       signed
       unsigned
       sfr
       sfr16

Переменная любого типа может быть объявлена как неизменяемая. Это достигается добавлением ключевого слова const к спецификатору типа. Объекты с типом const представляют собой данные, используемые только для чтения, т.е. этой переменной не может быть присвоено новое значение. Отметим, что если после слова const отсутствует спецификатор типа, то подразумевается спецификатор типа int. Если ключевое слово const стоит перед объявлением составных типов (массив, структура, смесь, перечисление), то это приводит к тому, что каждый элемент также должен являться немодифицируемым, т.е. значение ему может быть присвоено только один раз.

Примеры:

      const float A=2.128E-2;
      const B=286; (подразумевается const int B=286)

Примеры объявления составных данных будут рассмотрены ниже.

2.2. Целый тип данных

Для определения данных целого типа используются различные ключевые слова, которые определяют диапазон значений и размер области памяти, выделяемой под переменные (табл. 6).

Таблица 6

Тип Размер памяти в битах Размер памяти в байтах Диапазон значений
bit 1   от 0 до 1
char 8 1 от -128 до 127
unsigned shar 8 1 oт 0 до 255
int, short 16 2 от -32768 до 32767
long 32 4 от -2 147 483 648 до 2 147 483 647
unsigned int, unsigned short 16 2 от 0 до 65535
unsigned long 32 4 от 0 до 4 294 967 295
sbit 1   0 или 1
sfr 8 1 oт 0 до 255
sfr16 16 2 от 0 до 65535

Отметим, что ключевые слова signed и unsigned необязательны. Они указывают, как интерпретируется нулевой бит объявляемой переменной, т.е., если указано ключевое слово unsigned, то нулевой бит интерпретируется как часть числа, в противном случае нулевой бит интерпретируется как знаковый. В случае отсутствия ключевого слова unsigned целая переменная считается знаковой. В том случае, если спецификатор типа состоит из ключевого типа signed или unsigned и далее следует идентификатор переменной, то она будет рассматриваться как переменная типа int. Например:

     unsigned int n;  //Беззнаковое шестнадцатиразрядное число n
     unsigned int b;
     int c;        (подразумевается  signed   int c  );
     unsigned d;   (подразумевается  unsigned int d  );
     signed f;     (подразумевается  signed   int f  ).

Отметим, что модификатор типа char используется для представления одиночного символа или для объявления строковых литералов. Значением объекта типа char является код (размером 1 байт), соответствующий представляемому символу.

Отметим также, что восьмеричные и шестнадцатеричные константы также могут иметь модификатор unsigned. Это достигается указанием префикса u или U после константы, константа без этого префикса считается знаковой.

Например:

  0xA8C   (int  signed   );
  01786l  (long signed   );
  0xF7u   (int  unsigned );

2.3. Числа с плавающей запятой

Для переменных, представляющих число с плавающей запятой используется модификатор типа float. Модификатор double тоже допустим в языке программирования C51, но он не приводит к увеличению точности результата.

Величина с модификатором типа float занимает 4 байта. Из них 1 байт отводится для знака, 8 бит для избыточной экспоненты и 23 бита для мантиссы. Отметим, что старший бит мантиссы всегда равен 1, поэтому он не заполняется, в связи с этим диапазон значений переменной с плавающей точкой равен от ±1.175494E-38 до ±3.402823E+38.

Пример объявления переменной:

     float f, a, b;

2.4. Указатели

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

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

спецификатор-типа [ модификатор ] * описатель.

Спецификатор-типа задает тип объекта и может быть любого основного типа, структуры или смеси (об этих типах будет сказано ниже). Задавая вместо спецификатора-типа ключевое слово void, можно отсрочить определение типа, на который ссылается указатель. Переменная, объявляемая как указатель на тип void, может быть использована для ссылки на объект любого типа. Однако для того, чтобы можно было выполнить арифметические и логические операции над указателями или над объектами, на которые они указывают, необходимо при выполнении каждой операции явно определить тип объектов. Такие определения типов могут быть выполнены с помощью операции приведения типов.

В качестве модификаторов при объявлении указателя могут выступать ключевые слова const, data, idata, xdata, code. Ключевое слово const указывает, что указатель не может быть изменен в программе. Размер переменной, объявленной как указатель, зависит от модификатора и используемого вида памяти, для которой будет компилироваться программа. Указатели на различные типы данных не обязательно должны иметь одинаковую длину.

Для модификации размера указателя можно использовать ключевые слова data, idata, xdata, code.

Примеры:

   unsigned int * a; /* переменная  а  представляет собой указатель
                     на целую беззнаковую) переменную*/
   float * x;       /* переменная  х  указывает  на 
                       переменную с плавающей точкой*/
   char * fuffer ;   /*объявляется  указатель с именем fuffer
                      который указывает на символьную переменную*/

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

   float nomer;
   void *addres;
   addres = &nomer;
   (float *)addres ++;
  /* Переменная addres объявлена как указатель на объект любого типа.
     Поэтому ей можно присвоить адрес любого объекта (& - операция
     вычисления адреса). Однако, как было отмечено выше, ни одна
     арифметическая операция не может быть выполнена над указателем,
     пока не будет явно определен тип данных,  на которые он указывает. Это
     можно сделать,  используя операцию приведения типа (float *) для
     преобразования типа указателя addres к типу float. Затем оператор ++
     отдаёт приказ перейти к следующему адресу.*/

В качестве модификаторов при объявлении указателя могут выступать ключевые слова const, data, idata, xdata, code. Ключевое слово const указывает, что указатель не может быть изменен в программе.

Вследствие уникальности архитектуры контроллера 8051 и его производных компилятор С51 поддерживает 2 вида указателей: память-зависимые и нетипизированные.

2.4.1. Нетипизированные указатели

Нетипизированные указатели объявляются точно так же, как указатели в стандартном языке программирования C. Для того, чтобы не зависеть от типа памяти, в которой может быть размещена переменная, для нетипизированных указателей выделяется 3 байта. В первом байте указывается вид памяти переменной, во втором байте – старший байт адреса, в третьем – младший байт адреса переменной. Нетипизированные указатели могут быть использованы для обращения к любым переменным независимо от типа памяти микроконтроллера. Именно поэтому многие библиотечные функции языка программирования C51 используют указатели этого типа, при этом им совершенно неважно, в какой именно области памяти размещаются переменные. Приведем листинг, в котором отображаются особенности трансляции нетипизированных указателей:

stmt level source
   1       char *c_ptr; /* char ptr */
   2       int  *i_ptr; /* int  ptr */
   3       long *l_ptr; /* long ptr */
   4
   5       void main (void)
   6       {
   7     1 char data dj; /*переменные во внутренней памяти данных data */
   8     1 int data dk;
   9     1 long data dl;
  10     1
  11     1 char xdata xj; /*переменные во внешней памяти данных xdata */
  12     1 int xdata xk;
  13     1 long xdata xl;
  14     1
  15     1 char code cj = 9; /*переменные в памяти программ code */
  16     1 int code ck = 357;
  17     1 long code cl = 123456789;
  18     1
  19     1 /*настроим указатели на внутреннюю память данных data */
  20     1 c_ptr = &dj; 
  21     1 i_ptr = &dk;
  22     1 l_ptr = &dl;
  23     1 /*настроим указатели на внешнюю память данных xdata */
  24     1 c_ptr = &xj;
  25     1 i_ptr = &xk;
  26     1 l_ptr = &xl;
  27     1 /*настроим указатели на память программ code */
  28     1 c_ptr = &cj;
  29     1 i_ptr = &ck;
  30     1 l_ptr = &cl;
  31     1 }

ASSEMBLY LISTING OF GENERATED OBJECT CODE
      ; FUNCTION main (BEGIN)
                      ; SOURCE LINE # 5
                      ; SOURCE LINE # 6
                      ; SOURCE LINE # 20
0000 750000 R   MOV   c_ptr,#00H
0003 750000 R   MOV   c_ptr+01H,#HIGH dj
0006 750000 R   MOV   c_ptr+02H,#LOW dj
                      ; SOURCE LINE # 21
0009 750000 R   MOV   i_ptr,#00H
000C 750000 R   MOV   i_ptr+01H,#HIGH dk
000F 750000 R   MOV   i_ptr+02H,#LOW dk
                      ; SOURCE LINE # 22
0012 750000 R   MOV   l_ptr,#00H
0015 750000 R   MOV   l_ptr+01H,#HIGH dl
0018 750000 R   MOV   l_ptr+02H,#LOW dl
                      ; SOURCE LINE # 24
001B 750001 R   MOV   c_ptr,#01H
001E 750000 R   MOV   c_ptr+01H,#HIGH xj
0021 750000 R   MOV   c_ptr+02H,#LOW xj
                      ; SOURCE LINE # 25
0024 750001 R   MOV   i_ptr,#01H
0027 750000 R   MOV   i_ptr+01H,#HIGH xk
002A 750000 R   MOV   i_ptr+02H,#LOW xk
                      ; SOURCE LINE # 26
002D 750001 R   MOV   l_ptr,#01H
0030 750000 R   MOV   l_ptr+01H,#HIGH xl
0033 750000 R   MOV   l_ptr+02H,#LOW xl
                      ; SOURCE LINE # 28
0036 7500FF R   MOV   c_ptr,#0FFH
0039 750000 R   MOV   c_ptr+01H,#HIGH cj
003C 750000 R   MOV   c_ptr+02H,#LOW cj
                      ; SOURCE LINE # 29
003F 7500FF R   MOV   i_ptr,#0FFH
0042 750000 R   MOV   i_ptr+01H,#HIGH ck
0045 750000 R   MOV   i_ptr+02H,#LOW ck
                      ; SOURCE LINE # 30
0048 7500FF R   MOV   l_ptr,#0FFH
004B 750000 R   MOV   l_ptr+01H,#HIGH cl
004E 750000 R   MOV   l_ptr+02H,#LOW cl
                      ; SOURCE LINE # 31
0051 22         RET
      ; FUNCTION main (END)

2.4.2. Память зависимые указатели

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

char data *str;    /*указатель на строку во внутренней памяти данных data */
int xdata *numtab; /*указатель на целую во внешней памяти данных xdata */
long code *powtab; /*указатель на длинную целую в памяти программ code */

Поскольку модель памяти определяется во время компиляции, типизированным указателям не нужен байт, в котором указывается тип памяти микроконтроллера. Поэтому программа с использованием типизированных указателей будет короче и будет выполняться быстрее по сравнению с программой, использующей нетипизированные указатели. Типизированные указатели могут иметь размер в 1 байт (указатели на память idata, data, bdata, и pdata) или в 2 байта (указатели на память code и xdata).

2.5. Переменные перечислимого типа

Переменная, которая может принимать значение из некоторого списка значений, называется переменной перечислимого типа или перечислением.

Использование такого вида переменной эквивалентно применению целой знаковой переменной char или int. Это означает, что для переменной перечислимого вида будет выделен один или два байта в зависимости от максимального значения используемых этой переменной констант. В отличие от переменных целого типа, переменные перечислимого типа позволяют вместо безликих чисел использовать имена констант, которые более понятны и легче запоминаются человеком.

Например, вместо использования чисел 1,2,3,4,5,6,7 можно использовать названия дней недели: Poned, Vtorn, Sreda, Chetv, Pjatn, Subb, Voskr. При этом каждой константе будет соответствовать свое конкретное число. Однако использование имен констант приведет к более понятной программе.

Более того, транслятор сам позволяет отслеживать правильность использования констант и при попытке использования константы, не входящей в объявленный заранее список, выдает сообщение об ошибке.

Переменные enum типа могут использоваться в индексных выражениях и как операнды в арифметических операциях и в операциях отношения. Например:

If(rab_ned == SUB) dejstvie = rabota [rab_ned];

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

  1. enum [имя типа перечисления] {список констант} имя1 [,имя2 …];
  2. enum имя перечисления описатель [,описатель..];

В первом формате имена и значения констант задаются в списке констант. Необязательное имя типа объявляемой переменной - это идентификатор, который представляет собой тип переменной, соответствующий списку констант. За списком констант записывается имя одной или нескольких переменных.

Список констант содержит одну или несколько конструкций вида:

идентификатор [= константное выражение]

Каждый идентификатор - это имя константы. Все идентификаторы в списке enum должны быть уникальными. В случае если константе явным образом не задается число, то первому идентификатору присваивается значение 0, следующему идентификатору - значение 1 и т.д.

Пример объявления переменной rab_ned и типа переменных, совместимых с этой переменной, week выглядит следующим образом:

enum week {SUB = 0, /* константе SUB присвоено значение 0 */
           VOS = 0, /* константе VOS присвоено значение 0 */
           POND,    /* константе POND присвоено значение 1 */
           VTOR,    /* константе VTOR присвоено значение 2 */
           SRED,    /* константе SRED присвоено значение 3 */
           HETV,    /* константе HETV присвоено значение 4 */
           PJAT     /* константе PJAT присвоено значение 5 */
          } rab_ned;

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

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

Во втором формате для объявления переменной перечислимого типа используется уже готовый тип переменной уже объявленный ранее. Например:

enum week rab1;

К переменной перечислимого типа можно обращаться при помощи указателей. При этом необходимо заранее определить тип переменной, на которую будет ссылаться указатель. Это может быть сделано, как описывалось выше или при помощи оператора typedef. Например:

Typedef enum {SUB = 0, /* константе SUB присвоено значение 0 */
              VOS = 0, /* константе VOS присвоено значение 0 */
              POND,    /* константе POND присвоено значение 1 */
              VTOR,    /* константе VTOR присвоено значение 2 */
              SRED,    /* константе SRED присвоено значение 3 */
              HETV,    /* константе HETV присвоено значение 4 */
              PJAT     /* константе PJAT присвоено значение 5 */
             } week;

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

1.2.6. Массивы

Массивы - это группа элементов одинакового типа (double, float, int и т.п.). Из объявления массива компилятор должен получить информацию о типе элементов массива и их количестве. Объявление массива имеет два формата:

спецификатор-типа описатель [константное-выражение];
спецификатор-типа описатель [ ];

Описатель - это идентификатор массива.

Спецификатор-типа задает тип элементов объявляемого массива. Элементами массива не могут быть функции и элементы типа void.

Константное-выражение в квадратных скобках задает количество элементов массива. Константное- выражение при объявлении массива может быть опущено в следующих случаях:

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

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

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

Примеры:

     int a[2][3]; /* представлено в виде матрицы
                     a[0][0]  a[0][1] a[0][2]
                     a[1][0]  a[1][1] a[1][2] */
     double b[10]; /* вектор из 10  элементов имеющих тип double */
     int w[3][3] = { { 2, 3, 4 },
                     { 3, 4, 8 },
                     { 1, 0, 9 } };

В последнем примере объявлен массив w[3][3]. Списки, выделенные в фигурные скобки, соответствуют строкам массива, в случае отсутствия скобок инициализация будет выполнена неправильно.

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

Примеры:

int s[2][3];

Если при обращении к некоторой функции написать s[0], то будет передаваться нулевая строка массива s.

int b[2][3][4];

При обращении к массиву b можно написать, например, b[1][2] и будет передаваться вектор из четырех элементов, а обращение b[1] даст двухмерный массив размером 3 на 4. Нельзя написать b[2][4], подразумевая, что передаваться будет вектор, потому что это не соответствует ограничению наложенному на использование сечений массива.

Пример объявления массива символов.

char str[] = "объявление массива символов";

Следует учитывать, что в символьной строке находится на один элемент больше, так как последним элементом строки должен быть '\0'.

1.2.7. Структуры

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

struct { список определений }

В структуре обязательно должен быть указан хотя бы один компонент. Определение структур имеет следующий вид:

тип-данных описатель;

где тип-данных указывает тип структуры для объектов, определяемых в описателях. В простейшей форме описатели представляют собой идентификаторы или массивы.

Пример:

            struct { double x,y; } s1, s2, sm[9];
            struct {  int   year;
                      char  moth, day; } date1, date2;

Переменные s1, s2 определяются как структуры, каждая из которых состоит из двух компонент х и у. Переменная sm определяется как массив из девяти структур. Каждая из двух переменных date1, date2 состоит из трех компонентов year, moth, day. >p>Существует и другой способ связывания имени с типом структуры, он основан на использовании тега структуры. Тег структуры аналогичен тегу перечислимого типа. Тег структуры определяется следующим образом:

struct тег { список описаний; };

где тег является идентификатором.

В приведенном ниже примере идентификатор student описывается как тег структуры:

     struct student { char name[25];
                      int  id, age;
                      char  prp;         };

Тег структуры используется для последующего объявления структур данного вида в форме:

struct тег список-идентификаторов;

Пример:

struct studeut st1,st2;

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

        struct node { int data;
                      struct node * next; }   st1_node;

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

Доступ к компонентам структуры осуществляется с помощью указания имени структуры и следующего через точку имени выделенного компонента, например:

          st1.name="Иванов";
          st2.id=st1.id;
          st1_node.data=st1.age;

1.2.8. Объединения (смеси)

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

     union {   описание элемента 1;
               ...
               описание элемента n; };

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

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

Объединение применяется для следующих целей:

- инициализации используемого объекта памяти, если в каждый момент времени только один объект из многих является активным;

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

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

Пример:

     union {   char  fio[30];
               char  adres[80];
               int   vozrast;
               int   telefon;   } inform;
     union {   int ax;
               char al[2];      }   ua;

При использовании объекта infor типа union можно обрабатывать только тот элемент который получил значение, т.е. после присвоения значения элементу inform.fio, не имеет смысла обращаться к другим элементам. Объединение ua позволяет получить отдельный доступ к младшему ua.al[0] и к старшему ua.al[1] байтам двухбайтного числа ua.ax .

1.2.9. Поля битов

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

     struct { unsigned идентификатор 1 :  длина-поля  1;
              unsigned идентификатор 2 :  длина-поля  2;    }

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

Пример:

      struct { unsigned a1 :  1;
               unsigned a2 :  2;
               unsigned a3 :  5;
               unsigned a4 :  2;  } prim;

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

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

1.2.10. Переменные с изменяемой структурой

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

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

       struct figure {
                double area,perimetr;  /* общие компоненты       */
                int type;              /* признак компонента     */
                union                  /* перечисление компонент */
                     { double radius;  /* окружность             */
                       double a[2];    /* прямоугольник          */
                       double b[3];    /* треугольник            */
                      } geom_fig;
                      }  fig1, fig2  ;

В общем случае каждый объект типа figure будет состоять из трех компонентов: area, perimetr, type. Компонент type называется меткой активного компонента, так как он используется для указания, какой из компонентов объединения geom_fig является активным в данный момент. Такая структура называется переменной структурой, потому что ее компоненты меняются в зависимости от значения метки активного компонента (значение type).

Отметим, что вместо компоненты type типа int, целесообразно было бы использовать перечисляемый тип. Например, такой

      enum figure_chess { CIRCLE,
                          BOX,
                          TRIANGLE    } ;

Константы CIRCLE, BOX, TRIANGLE получат значения соответственно равные 0, 1, 2. Переменная type может быть объявлена как имеющая перечислимый тип :

enum figure_chess type;

В этом случае компилятор СИ предупредит программиста о потенциально ошибочных присвоениях, таких, например, как

figure.type = 40;

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

     struct { общие компоненты;
              метка активного компонента;
                union { описание компоненты 1 ;
                        описание компоненты 2 ;
                        :::
                         описание компоненты n ;
                        } идентификатор-объединения ;
             } идентификатор-структуры ;

Пример определения переменной структуры с именем helth_record

     struct { /* общая информация */
               char  name [25];   /* имя       */
               int   age;         /* возраст   */
               char  sex;         /* пол       */
               /*   метка   активного   компонента  */
               /*   (семейное положение)            */
               enum merital_status ins;
               /* переменная часть */
               union  {   /* холост        */
                          /* нет компонент */
                        struct {  /* состоит в браке */
                                 char marripge_date[8];
                                 char spouse_name[25];
                                 int  no_children;
                                }  marriage_info;
                         /* разведен */
                         char date_divorced[8];
                       }   marital_info;
                } health_record;

       enum marital_status { SINGLE, /* холост   */
                            MARRIGO, /* женат    */
                            DIVOREED /* разведен */
                            } ;

Обращаться к компонентам структуры можно при помощи ссылок:

  helth_record.neme,
  helth_record.ins,
  helth_record.marriage_info.marriage_date .

1.2.11. Определение объектов и типов

Как уже говорилось выше, все переменные используемые в программах на языке СИ, должны быть объявлены. Тип объявляемой переменной зависит от того, какое ключевое слово используется в качестве спецификатора типа и является ли описатель простым идентификатором или же комбинацией идентификатора с модификатором указателя (звездочка), массива (квадратные скобки) или функции (круглые скобки).

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

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

Однако надо помнить, что некоторые комбинации модификаторов недопустимы:

- элементами массивов не могут быть функции,

- функции не могут возвращать массивы или функции.

При инициализации сложных описателей квадратные и круглые скобки (справа от идентификатора) имеют приоритет перед звездочкой (слева от идентификатора). Квадратные или круглые скобки имеют один и тот же приоритет и раскрываются слева направо. Спецификатор типа рассматривается на последнем шаге, когда описатель уже полностью проинтерпретирован. Можно использовать круглые скобки, чтобы поменять порядок интерпретации на необходимый.

Для интерпретации сложных описаний предлагается простое правило, которое звучит как "изнутри наружу", и состоит из четырех шагов.

1. Начать с идентификатора и посмотреть вправо, есть ли квадратные или круглые скобки.

2. Если они есть, то проинтерпретировать эту часть описателя и затем посмотреть налево в поиске звездочки.

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

4. Интерпретировать спецификатор типа.

Примеры:

          int    * ( * comp [10]) ();
           6     5   3   1    2    4

В данном примере объявляется переменная comp (1), как массив из десяти (2) указателей (3) на функции (4), возвращающие указатели (5) на целые значения (6).

          char  * ( * ( * var ) () ) [10];
            7   6   4   2  1     3     5

Переменная var (1) объявлена как указатель (2) на функцию (3) возвращающую указатель (4) на массив (5) из 10 элементов, которые являются указателями (6) на значения типа char.

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

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

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

Примеры:

        typedef double (* MATH)( );
          /* MATH - новое имя типа, представляющее указатель на
          функцию, возвращающую значения типа double           */
          MATH cos;
          /* cos  указатель на функцию, возвращающую
          значения типа  double                                 */
          /* Можно провести эквивалентное объявление            */
          double (* cos)( );

       typedef char FIO[40]
           /* FIO - массив из сорока символов                   */
       FIO person;
           /*  Переменная person  -  массив из сорока символов  */
           /*  Это  эквивалентно объявлению                     */
       char person[40];

При объявлении переменных и типов здесь были использованы имена типов (MATH FIO). Помимо этого, имена типов могут еще использоваться в трех случаях: в списке формальных параметров, в объявлении функций, в операциях приведения типов и в операции sizeof (операция приведения типа).

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

спецификатор-типа абстрактный-описатель;

Абстрактный-описатель - это описатель без идентификатора, состоящий из одного или более модификаторов указателя, массива или функции. Модификатор указателя (*) всегда задается перед идентификатором в описателе, а модификаторы массива [] и функции () - после него. Таким образом, чтобы правильно интерпретировать абстрактный описатель, нужно начать интерпретацию с подразумеваемого идентификатора.

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

1.2.12. Инициализация данных

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

Формат 1: = инициатор;

Формат 2: = { список - инициаторов };

Формат 1 используется при инициализации переменных основных типов и указателей, а формат 2 - при инициализации составных объектов.

Примеры:

char tol = 'N';

Переменная tol инициализируется символом 'N'.

const long megabute = (1024 * 1024);

Немодифицируемая переменная megabute инициализируется константным выражением после чего она не может быть изменена.

static int b[2][2] = {1,2,3,4};

Инициализируется двухмерный массив b целых величин элементам массива присваиваются значения из списка. Эта же инициализация может быть выполнена следующим образом :

static int b[2][2] = { { 1,2 }, { 3,4 } };

При инициализации массива можно опустить одну или несколько размерностей

static int b[3[] = { { 1,2 }, { 3,4 } };

Если при инициализации указано меньше значений для строк, то оставшиеся элементы инициализируются 0, т.е. при описании

static int b[2][2] = { { 1,2 }, { 3 } };

элементы первой строки получат значения 1 и 2, а второй 3 и 0.

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

Примеры:

        struct complex { double real;
                         double imag; }  comp [2][3] =
             { { {1,1}, {2,3}, {4,5} },
               { {6,7}, {8,9}, {10,11} } };

В данном примере инициализируется массив структур comp из двух строк и трех столбцов, где каждая структура состоит из двух элементов real и imag.

        struct complex comp2 [2][3] =
           { {1,1},{2,3},{4,5}, {6,7},{8,9},{10,11} };

В этом примере компилятор интерпретирует рассматриваемые фигурные скобки следующим образом:

- первая левая фигурная скобка - начало составного инициатора для массива comp2;

- вторая левая фигурная скобка - начало инициализации первой строки массива comp2[0]. Значения 1,1 присваиваются двум элементам первой структуры;

- первая правая скобка (после 1) указывает компилятору, что список инициаторов для строки массива окончен, и элементы оставшихся структур в строке comp[0] автоматически инициализируются нулем;

- аналогично список {2,3} инициализирует первую структуру в строке comp[1], а оставшиеся структуры массива обращаются в нули;

- на следующий список инициализаторов {4,5} компилятор будет сообщать о возможной ошибке так как строка 3 в массиве comp2 отсутствует.

При инициализации объединения задается значение первого элемента объединения в соответствии с его типом.

Пример:

      union tab { unsigned char name[10];
                  int tab1;
                 }           pers  =  {'A','H','T','O','H'};

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

Инициализацию массива символов можно выполнить путем использования строкового литерала.

char stroka[ ] = "привет";

Инициализируется массив символов из 7 элементов, последним элементом (седьмым) будет символ '\0', которым завершаются все строковые литералы.

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

Следующее объявление инициализирует переменную stroka как массив, состоящий из семи элементов.

char stroka[5] = "привет";

Это объявление будет восприниматься как ошибка.

Если строка короче, чем размер массива, то оставшиеся элементы массива заполняются нулями.

Отметим, что инициализация переменной типа tab может иметь следующий вид:

union tab pers1 = "Антон";

и, таким образом, в символьный массив попадут символы:

'А','Н','Т','О','Н','\0',

а остальные элементы будут инициализированы нулем.


Понравился материал? Поделись с друзьями!



[ Назад | Оглавление | Вперед ]

Автор Микушин А. В. All rights reserved. 2001 ... 2021

Предыдущие версии сайта:
http://neic.nsk.su/~mavr
http://digital.sibsutis.ru/

пЕИРХМЦ@Mail.ru


Rambler's Top100