Лекция 6. Пользовательские типы данных

Простые типы

Классификация

Стандартные и нестандартные типы

  • Использование только стандартных типов в программе уменьшает её возможности.

  • Стандартные типы не обладают информативностью.

  • Нестандартные типы данных получаются с помощью специальных механизмов.

  • стандартные (встроенные) - числовые (char, short, long, int, ....)

  • нестандартные

    • псевдонимы (typedef)
    • перечисления (enum)
    • структуры (struct)
    • объединения (union)
    • поля битов

Описания нестандартных типов

Описания нестандартных типов обычно размещают:

  • В начале программы (до первого использования).
  • В заголовочных файлах.

Псевдонимы

typedef type1 type2;
typedef unsigned long   UL;
typedef char *            PCHAR;
typedef short              INT16;
UL     var1, var2, *var3;
PCHAR  s="Hello!";
INT16  arr[10];

Использование:

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

Упрощение сложных объявлений

Задача

Объявить массив из N указателей на функции, возвращающих указатели на функции, возвращающие указатель на char.

Можно объявить этот массив так:

char *(*(*a[N])())();

А можно воспользоваться typedef:

typedef char *pc;
typedef pc fpc();
typedef fpc *pfpc;
typedef pfpc fpfpc();
typedef fpfpc * pfpfpc;

pfpfpc a[N];

Перечисления

enum NAME {val1,val2,val3, ...};

Примеры:

enum SPECTRUM { red, orange, yellow, green, blue, violet };
enum LEVELS   { low=100, medium=500, high=1000};

enum LEVELS   lev1=medium;
              if(lev1>low) {...}

Использование:

  • для улучшения типизации
  • для повышения наглядности

Основой для внутреннего представления перечислимого типа является тип int.

Слабая типизация С позволяет смешивать в программе числовые константы и перечислимые типы, а также использовать арифметические операции (++).

enum WEEK {MO,TU,WE,TH,FR,SA,SU};
enum WEEK day;
for(day=MO;day<SA;day++)
   // обработка рабочих дней

В С++ (с сильной типизацией) данный пример вызовет ошибку и будет требовать перегрузки арифметического оператора ++ для перечислимого типа.

Как связать перечисление со строкой

enum SPECTRUM { red, blue };
// первый способ
char *colors[]={"RED","BLUE"};

int main()
{
   enum SPECTRUM color=blue;
   // второй способ
   switch(color)
   {
      case red:
         printf("RED\n");
         break;
      case blue:
         printf("BLUE\n");
         break;
   }
   printf("%s",colors[red]);
   return 0;
}

Составные типы

Структуры

Записи

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

Изображение массива:

_images/array.png

Изображение структуры (записи):

_images/record.png

Фундаментальные структуры данных

Замечательно то, что массивы и записи можно объединять, создавая:

  • Массивы записей.
  • Записи, содержащие массивы.

На основе массивов и записей строят:

  • Связанный список - набор элементов одного типа, не обязательно следующих в памяти друг за другом и связанных между собой благодаря хранению адресов.
  • Графы - множество вершин (узлов), соединённых рёбрами.
  • Деревья - иерархически связанные элементы данных, частный случай графа.
  • Хэш-таблицы (ассоциативные массивы) - наборы ‘’ключ-значение’‘.

Определение структуры

Сначала мы описываем новый структурный тип:

struct TAG
{
   type1 field1;
   type2 field2;
   ...
};

Потом создаём объекты:

struct TAG obj1, obj2,.. *pobj,arr[N];
struct BOOK
{
   char title[40];
   char author[30];
   int pages;
   float price;
};
struct BOOK mybook;
typedef struct
{
   char title[40];
   char author[30];
   int pages;
   float price;
}  BOOK;
BOOK mybook;

Инициализация

Вариант 1.

struct BOOK b1={"The C programming",
                "K & R",
                300,
                100.23 };

Вариант 2.

struct BOOK
{
   char title[40];
   char author[30];
   int pages;
   float price;
}
b1 = {"The C programming",
      "K & R",
      300,
      100.23 };

Свойства структур

  • В памяти все поля структуры располагаются последовательно.
  • Память под структуру выделяется при объявлении переменных.
  • Доступ к полям производится с помощью операций . и ->.
  • Полем структуры может быть указатель на структуру.

Доступ к значениям полей

Для доступа к полю через структурный объект используется ‘’точка’‘

struct BOOK book, *pbook=&book;

book.price=123.50;
strcpy(book.title,"The C programming");

pbook->pages=300;
strcpy(pbook->author,"K&R");

Для доступа к полю через адрес объекта используется ‘’стрелочка’‘

struct BOOK book, *pbook=&book;

(&book)->price=123.50;
strcpy((&book)->title,"The C programming");

(*pbook).pages=300;
strcpy((*pbook).author,"K&R");

Вложенные структуры

Структуры можно вкладывать друг в друга (‘’матрёшки’‘)

struct TIME
{
   int hh;
   int mm;
   int ss;
};

struct EVENT
{
   char title[256];
   struct TIME when;
};
struct EVENT event, *pevent=&event;

strcpy(event.title,"begin");
event.when.hh=18;
event.when.mm=0;
event.when.ss=0;

strcpy(pevent->title,"begin");
pevent->when.hh=18;
pevent->when.mm=0;
pevent->when.ss=0;

Присвоение структур

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

  1. Объекты одного типа
  2. В объекте нет полей-указателей

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

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

struct BOOK
{
   char title[40];
   char author[40];
   float price;
};

struct BOOK book1={"The C Programming",
             "K&R", 300.0};
struct BOOK book2;
book2=book1; // поэлементное копирование

// мы меняем только book1, но не book2!
strcpy(book1.title,"The C++ Programming");
struct BOOK
{
   char *title;
   char *author;
   float price;
};

struct BOOK book1={"The C Programming",
             "K&R", 300.0};
struct BOOK book2;
book2=book1; // копируем ссылку!

// мы одновременно пытаемся поменять и book1 и book2
// а также "залезаем" в статическую память, вызывая сбой
strcpy(book1.title,"The C++ Programming");

Структуры и функции

Структуры можно передавать в функцию как по значению, так и по указателю.

void printBook(struct BOOK book)
{
     printf("Title: %s \nAuthor: %s \nPrice: %.2f\n",
     book.title,book.author,book.price);
}

void enterBook(struct BOOK *book)
{
     fgets(book->title,40,stdin);
     fgets(book->author,40,stdin);
     scanf("%f",&book->price);
}

В отличии от массивов, структуры можно возвращать из функций:

struct BOOK printBook(struct BOOK book)
{
     printf("Title: %s \nAuthor: %s \nPrice: %.2f\n",
     book.title,book.author,book.price);
     return book;
}

Структуры и указатели

Структура может содержать указатель на себя в качестве поля:

struct BOOK
{
   char *title;
   char *author;
   float price;

   struct BOOK  *next;
};

Благодаря полю next можно связывать структурные объекты между собой (создавать связанные списки).

Объединения

Объединения описываются практически так же, как и структуры, но вместо struct используется union.

union TAG
{
   type1 field1;
   type2 field2;
   ...
};

В отличие от структур, размер объединения определяется размером его самого длинного поля.

Структуры и объединения

Рассмотрим почти одинаковые описания. В чем разница между ними?

struct A
{
    char data[4];
    long value;
};
union B
{
    char data[4];
    long value;
};

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

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

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

Безымянные объединения

Безымянные объединения используются для экономии памяти.

int main()
{
   union
   {
       char data[4];
       long value;
       float * ptr;
    };
    value=10;
    ...
    ptr=&...
    ...
    data[0]='a';
    ...
};

Применение объединений

Объединения могут использоваться для:

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

Поля битов

Поле битов

В С есть возможность организовать память для хранения величин, размером менее 1 байта. Это возможность предоставляется полями битов.

Поле битов -это структура, где для каждого поля указывается ширина в двоичных разрядах.

struct TAG
{
    type field1: width1;
    type field2: width2;
    ...
    type fieldN:widthN;
};

Поле битов реализуется как набор смежных полей, размещенных внутри типа signed int или unsigned int.

Смещение границ

Что происходит, если общее количество бит, превышает размер int?

В этом случае используется следующая ячейка памяти типа int.

Компилятор автоматически смещает определения накладывающихся полей так, чтобы поле прилегала к границам ячейки int.

Пример

Пример битового поля с максимально компактным заполнением (4 байта)

struct WORD
{
   unsigned int a: 2;
   unsigned int b: 2;
   unsigned int c: 2;
   unsigned int d: 2;
   unsigned int e: 8;
   unsigned int f: 16;
};

В данном случае задействуется одна переменная int.

_images/field1.png

Пример битового поля с ‘’дырами’‘

struct WORD
{
   unsigned int a: 2;
   unsigned int b: 3;
   unsigned int  : 2;  // пустой промежуток
   unsigned int d: 5;
   unsigned int  : 4;  // пустой промежуток
   unsigned int f: 16;
};

Вопросы для самоконтроля

  • Как классифицируются стандартные и нестандартные типы в С?
  • Что такое псевдонимы (typedef)?
  • Для чего используются псевдонимы?
  • Как с помощью typedef упростить сложное объявление?
  • Как задать перечисление?
  • Для чего используются перечисления?
  • Как связать перечисление со строкой?
  • Что такое структура (запись)?
  • Чем запись отличается от массива?
  • Что строят на основе фундаментальных способов организации данных?
  • Как определить структуру?
  • Как проинициализировать структуру?
  • Перечислите свойства структурного типа.
  • Чем структуры отличаются от массивов?
  • Как в структуре осуществляется доступ к значениям полей?
  • Как создать вложенные структуры?
  • Можно ли присваивать структуры?
  • Как передать структуру в функцию?
  • Можно ли возвратить структуру из функции?
  • Могут ли указатели выступать в роли полей структур?
  • Может ли структура содержать указатель на себя саму?
  • Как задать объединение?
  • Чем объединение отличается от структуры?
  • Чему равен размер памяти, занимаемый структурой?
  • Чему равен размер памяти, занимаемый объединением?
  • Для чего используются безымянные объединения?
  • Для чего используются объединения?
  • Что такое поле битов?
  • Что происходит, если суммарный размер полей превышает 32 бита?
  • Приведите пример битового поля с ‘’дырами’‘.