Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ЯП - ПОИТ (Бахтизин) часть 1 редакт.doc
Скачиваний:
1
Добавлен:
01.04.2025
Размер:
1.76 Mб
Скачать

13. Введение в объектно-ориентированное программирование

13.1. Постановка задачи

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

Пример 13.1. Реализация простейшего статического стека.

int stack[50]; // Массив хранит элементы стека

int top; // Номер элемента в вершине стека

/* Функция добавления элемента в стек */

void push(int a);

/* Функция извлечения элемента из стека */

int pop();

Функция main() использует стек, который реализован как статический массив, и две функции, реализующие операции над ним. Однако усложнение программы может вызвать некоторые трудности: так, если возникнет необходимость в использовании не одного, а нескольких стеков, придется создать много массивов, а функциям push() и pop() передавать указатель на массив, хранящий элементы конкретного стека. Если усложнять задачу далее, например, до стека, основанного на динамическом списке, получится программа, имеющая примерно такую структуру:

Пример 13.2. Стек, основанный на связном списке.

struct TItem // Структура элемента списка.

{

int value; // Информационное поле

struct TItem* next;

};

// Функции работы с элементом списка

struct TItem *create_item(struct TItem **item,

struct TItem *nxt,

int num);

struct TItem *delete_item(struct TItem **item);

struct TList // Структура списка

{

struct TItem *front; // Указатель на начало списка

int size; // Число элементов списка

};

// Функции работы со списком

struct TList *create_list(); // Создание

void delete_list(struct TList** list); // Удаление

// Функции работы с элементами списка

// Добавление

struct TItem *add_item(struct TList *list, int num);

// Исключение

int remove_item(struct TList *list);

struct TStack // Структура стека.

{

struct TList *top; // Указатель на вершину стека

};

// Функции работы со стеком

struct TStack *create_stackt(); // Создание

void delete_stack(struct TStack **stack); // Удаление

// Функции работы с элементами стека

void push(struct TStack *stack, int a); // Добавление

int pop(struct TStack *stack); // Исключение

Первый большой недостаток такой реализации стека – засорение пространства имен. Несмотря на то, что в остальной программе используется лишь стек, для того чтобы определить его, нам понадобилось создать большое количество вспомогательных функций. Это, в свою очередь, означает, что в остальной части программы будут доступны функции, в которых нам нет необходимости, а полезные имена будут заняты. Например, переменная top имеет вполне универсальное название, и вполне возможно, что программист захочет использовать ее где-то еще. Это усложнит понимание программы из-за придания переменной еще одного смысла и может привести к ошибке неправильного ее использования. Засорение пространства имен чревато также тем, что программисту все труднее становится ориентироваться в исходном коде и придумывать новые идентификаторы, а это может перерасти в серьезную проблему при разработке больших программ.

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

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