Пользовательские типы

Задача реализации алгоритма часто упрощается, если в языке программирования предусмотрены, кроме примитивных, дополнительные типы данных. Многие современные языки программирования позволяют программистам определять свои типы данных, используя в качестве компоновочных блоков примитивные типы данных и структуры. Эти «самодельные» типы данных называются пользовательскими типами (user-defined types).

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

struct

{char Name[8]:

int Age:

float Ski 11 Rating;

} Employee;

как на рис. 5.5 (см. главу 5).

Но в этом случае, если такая структура будет часто встречаться в тексте, программа может стать слишком громоздкой и трудной для чтения. Более того, без подробного изучения трудно узнать, что все структуры идентичны. Лучше определить неоднородную структуру как новый (пользовательский) тип данных и использовать этот тип так же, как если бы это был примитив.

Типичный пример такого подхода можно увидеть в языке программирования С, где новые типы определяются при помощи оператора typedef (сокращение от type definition, определение типа). Он состоит из зарезервированного слова typedef, за которым идет описание структуры нового типа, и заканчивается именем нового типа. Так, оператор

typedef struct {char Name[8]: int Age;

float SkillRating; } EmployeeType;

определяет новый тип под названием EmployeeType, состоящий из неоднородной структуры, содержащей Name (Имя), Age (Возраст) и SkillRating (Уровень знаний). При помощи этого нового типа можно объявлять переменные в точности так же, как переменные примитивных типов. В частности, переменная Employee объявляется в следующем операторе: EmployeeType Employee;

Преимущества такого пользовательского типа данных становятся заметнее при объявлении нескольких переменных. Так же, как на языке С программист может объявить Sleeve, Waist и Neck как переменные примитивного типа real в операторе

float Sleeve, Waist. Neck; оператор

EmployeeType DistManager, SalesRepl, SalesRep2;

определяет три переменные — DistManager, SalesRepl и SalesRep2, как переменные типа EmployeeType.

Важно разделять определение пользовательского типа данных и фактические элементы данных этого типа. Последние называются экземплярами этого типа (instance). Определение пользовательского типа данных — это шаблон, который используется для создания экземпляров этого типа. Он описывает свойства, общие для всех экземпляров такого типа, но не объявляет реальный элемент, обладающий этими свойствами (так же, как шаблон для вырезания печенья используется для создания печенья, но сам им не является). В предыдущем примере пользовательский тип EmployeeType использован для создания трех экземпляров этого типа, известных под именами DistManager, SalesRepl и SalesRep2.

Классы

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

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

Листинги 7.6 и 7.7 показывают, как класс с именем StackOf Integers может быть определен на языках C++, С# и Java. Далее мы вкратце рассмотрим подробности этих примеров, но сейчас заметим лишь, что в каждом примере в классе StackOf Integers определены две переменные экземпляра (массив целых чисел с названием StackEntries и целое число StackPointer, которое используется для идентификации вершины стека внутри массива — см. упражнение 5 в разделе 7.4), две процедуры (с именами push и pop) и конструктор, который устанавливает максимальный объем стека при создании каждого экземпляра стека.

Листинг 7.6. Стек целых чисел (реализация на C++)1

class StackOfIntegers {private:

int *StackEntries;

int StackPointer;

int MaxStack;

public:

StackOflntegersCint Max)

{

StackEntries = new int[Max]:

StackPointer = 0:

MaxStack = Max: }

void push(int NewEntry)

{

if (StackPointer < MaxStack)

StackEntries[StackPointer++] = NewEntry; }

int pop 0

{if (StackPointer >0) return SrackEntries[--StackPointer]: } }:

Листинг 7.7. Стек целых чисел (реализация на Java и С#)

class StackOflntegers {private int[] StackEntries:

private int StackPointer;

private int MaxStack;

public StackOfIntegers(int Max) {StackEntries = new int[Max]: StackPointer = 0:

MaxStack = Max; }

public void push(int NewEntry) {if (StackPointer < MaxStack) StackEntries[StackPointer++] = NewEntry:

} public int pop О

{if (StackPointer >0) return SrackEntnes[--StackPointer]:

else return 0;

} }

Используя этот класс как шаблон, при помощи следующего оператора на Java или С# можно создать объект с именем StackOne, представляющий стек объемом до 50 целых чисел:

StackOfIntegers StackOne = new Stack0flntegers(50);

Для C++ это будет оператор StackOfIntegers Stack0ne(50):

Далее в программе в стек StackOne можно протолкнуть значение 106, применив оператор

StackOne.push(106):

или получить в переменную OldValue верхнюю запись из StackOne при помощи оператора

OldValue - StackOne.popO:

Возвращаясь к листингам 7.6 и 7.7, обратите внимание, что для обеспечения целостности структур данных в классе используется инкапсуляция. В частности, StackEntries и StackPointer определены в разделе private, тогда как методы push и pop — в разделе public. Так, внутренняя структура стека недоступна за пределами экземпляров класса. Любое обращение к стеку должно быть выполнено при помощи общих методов.

Чтобы понять важность такой защиты, предположим, что StackEntries и Stack -Poi nter определены как publ i с, и программисту требуется обратиться к третьей записи стека типа StackOf Integers. Программист, который знает, как стек реализован в памяти, может нарушить целостность стека и обратиться к массиву StackEntry напрямую, а не путем выталкивания первых двух записей. Проблема состоит в том, что в будущем программисты, обслуживающие это приложение, могут внести где-либо в программе изменения, не совместимые с этой прямой ссылкой на запись. Например, для увеличения максимального размера стека можно изменить внутреннюю организацию типа StackOf Integers с массива на связную структуру, в которой прямая ссылка, предполагающая, что стек реализован в виде массива, будет недопустима.

В завершение раздела заметим, что класс StackOfInteges в том виде, как он определен в листингах 7.6 и 7.7, иллюстрирует кульминацию темы, проходящей через всю главу, — это набор структур данных и процедур для управления ими в одном программном модуле. Действительно, концепция классов позволяет еще более расширить эту тему, так как разрешает программистам объединять структуры данных и относящиеся к ним процедуры в форме пользовательских типов данных, при помощи которых можно создавать множество экземпляров. На самом деле концепция классов является более общей, чем концепция типов данных, так как класс может состоять, в том числе, только из процедур. Или другой экстремальный случай — класс может включать только структуры данных. Таким образом, концепция классов ~ это мощный инструмент разработки программного обеспечения, при помощи которого можно проектировать и реализовывать шаблоны для программных модулей различных видов. Классы и объектно-ориентированный принцип стали основными инструментами в сегодняшнем арсенале средств разработки приложений.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: