Программирование >>  Поддержка объектно-ориентированного программирования 

1 ... 43 44 45 [ 46 ] 47 48 49 ... 120


Функции name::operator new() и name::operator delete() будут использоваться (неявно) вместо глобальных функций operator new() и operator delete(). Программист может для конкретного типа написать более эффективные по времени и памяти функции размещения и удаления, чем универсальные функции operator new() и operator delete(). Можно, например, разместить заранее куски памяти, достаточной для объектов типа name, и связать их в список; тогда операции размещения и удаления сводятся к простым операциям со списком. Переменная nfree используется как начало списка неиспользованных кусков памяти:

void* name::operator new(size t)

register name* p = nfree; сначала выделить

if (p)

nfree = p->next; else { выделить и связать в список

name* q = (name*) new char[NALL*sizeof(name) ]; for (p=nfree=&q[NALL-1]; q<p; p--) p->next = p-1; (p+1)->next = 0;

return p;

Распределитель памяти, вызываемый new, хранит вместе с объектом его размер, чтобы операция delete выполнялась правильно. Этого дополнительного расхода памяти можно легко избежать, если использовать распределитель, рассчитанный на конкретный тип. Так, на машине автора функция name::operator new() для хранения объекта name использует 1 6 байтов, тогда как стандартная глобальная функция operator new() использует 20 байтов.

Отметим, что в самой функции name::operator new() память нельзя выделять таким простым способом:

name* q= new name[NALL]; Это вызовет бесконечную рекурсию, т.к. new будет вызывать name::name(). Освобождение памяти обычно тривиально:

void name::operator delete(void* p, size t)

((name*)p)->next = nfree; nfree = (name*) p;

Приведение параметра типа void* к типу name* необходимо, поскольку функция освобождения вызывается после уничтожения объекта, так что больше нет реального объекта типа name, а есть только кусок памяти размером sizeof(name). Параметры типа size t в приведенных функциях name::operator new() и name::operator delete() не использовались. Как можно их использовать, будет показано в $$6.7. Отметим, что наши функции размещения и удаления используются только для объектов типа name, но не для массивов names.

5.6 Упражнения

1 . (*1 ) Измените программу калькулятора из главы 3 так, чтобы можно было воспользоваться классом

table.

2. Определите tnode ($$R.9) как класс с конструкторами и деструкторами и т.п., определите дерево из объектов типа tnode как класс с конструкторами и деструкторами и т.п.

3. (*1 ) Определите класс intset ($$5.3.2) как множество строк.

4. (*1 ) Определите класс intset как множество узлов типа tnode. Структуру tnode придумайте сами.

5. (*3) Определите класс для разбора, хранения, вычисления и печати простых арифметических

static name* nfree;



выражений, состоящих из целых констант и операций +, -, * и /. Общий интерфейс класса должен выглядеть примерно так:

class expr {

...

public:

expr(char*); int eval(); void print();

Конструктор expr::expr() имеет параметр-строку, задающую выражение.

Функция expr::eval() возвращает значение выражения, а expr::print() выдает представление выражения в cout. Использовать эти функции можно так:

expr( 123/4+123*4-3 ); cout << x = << x.evalO << \n ; x.printO;

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

6. Определите класс charqueue (очередь символов) так, чтобы его общий интерфейс не зависел от представления. Реализуйте класс как: (1) связанный список и (2) вектор. О параллельности не думайте.

7. (*2) Определите класс histogram (гистограмма), в котором ведется подсчет чисел в определенных интервалах, задаваемых в виде параметров конструктору этого класса. Определите функцию выдачи гистограммы. Сделайте обработку значений, выходящих за интервал. Подсказка: обратитесь к <task.h>.

8. (*2) Определите несколько классов, порождающих случайные числа с определеннгми распределениями. Каждый класс должен иметь конструктор, задающий параметры распределения и функцию draw, возвращающую следующее значение. Подсказка: обратитесь к <task.h> и классу intset.

9. (*2) Перепишите примеры date ($$5.2.2 и $$5.2.4), charstack ($$5.2.5) и intset ($$5.3.2), не используя никаких функций-членов (даже конструкторов и деструкторов). Используйте только class и friend. Проверьте каждую из новых версий и сравните их с версиями, в которых используются функции-члены.

10. 10. (*3) Для некоторого языка составьте определения класса для таблицы имен и класса, представляющего запись в этой таблице. Исследуйте транслятор для этого языка, чтобы узнать, какой должна быть настоящая таблица имен.

11. 11.(*2) Измените класс expr из упражнения 5 так, чтобы в выражении можно бгло использовать переменные и операцию присваивания =. Используйте класс для таблицы имен из упражнения 10.

12. 12. (*1) Пусть есть программа:

#include <iostream.h> main()

cout << Всем привет\n ;

Измените ее так, чтобы она выдавала:

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

Всем привет

Удаление Саму функцию main() менять нельзя.



ГЛАВА 6.

Не плоди объекты без нужды.

- В. Оккам

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

6.1 Введение и краткий обзор

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

Глава состоит из шести разделов:

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

$$6.3 вводится понятие чисто виртуальных функций и абстрактных классов, даны небольшие примеры их использования.

$$6.4 производные классы показаны на законченном примере

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

$$6.6 обсуждается механизм контроля доступа.

$$6.7 приводятся некоторые приемы управления свободной памятью для производных классов.

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

6.2 Производные классы

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

struct employee { служащие

char* name; имя

short age; возраст

short department; отдел

int salary; оклад

employee* next; ...



1 ... 43 44 45 [ 46 ] 47 48 49 ... 120

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика