русс | укр

Языки программирования

ПаскальСиАссемблерJavaMatlabPhpHtmlJavaScriptCSSC#DelphiТурбо Пролог

Компьютерные сетиСистемное программное обеспечениеИнформационные технологииПрограммирование

Все о программировании


Linux Unix Алгоритмические языки Аналоговые и гибридные вычислительные устройства Архитектура микроконтроллеров Введение в разработку распределенных информационных систем Введение в численные методы Дискретная математика Информационное обслуживание пользователей Информация и моделирование в управлении производством Компьютерная графика Математическое и компьютерное моделирование Моделирование Нейрокомпьютеры Проектирование программ диагностики компьютерных систем и сетей Проектирование системных программ Системы счисления Теория статистики Теория оптимизации Уроки AutoCAD 3D Уроки базы данных Access Уроки Orcad Цифровые автоматы Шпаргалки по компьютеру Шпаргалки по программированию Экспертные системы Элементы теории информации

Альтернативные реализации


Дата добавления: 2015-06-12; просмотров: 644; Нарушение авторских прав


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

struct name {

char* string;

name* next;

double value;

};

А вот вариант класса table (таблица имен):

// файл table.h

class table {

name* tbl;

public:

table() { tbl = 0; }

name* look(char*, int = 0);

name* insert(char* s) { return look(s,1); }

};

Эта таблица отличается от определенной в главе 3 тем, что это настоящий тип. Можно описать несколько таблиц, завести указатель на таблицу и т.д. Например:

#include "table.h"

table globals;

table keywords;

table* locals;

 

main()

{

locals = new table;

// ...

}

Приведем реализацию функции table::look(), в которой используется линейный поиск в списке имен таблицы:

#include <string.h>

 

name* table::look(char* p, int ins)

{

for (name* n = tbl; n; n=n->next)

if (strcmp(p,n->string) == 0) return n;

if (ins == 0) error("имя не найдено");

name* nn = new name;

nn->string = new char[strlen(p)+1];

strcpy(nn->string,p);

nn->value = 1;

nn->next = tbl;

tbl = nn;

return nn;

}

Теперь усовершенствуем класс table так, чтобы поиск имени шел по ключу (хэш-функции от имени), как это и было сделано в примере с калькулятором. Сделать это труднее, если соблюдать ограничение, требующее, чтобы не все программы, использующие приведенную версию класса table, надо было изменять:

class table {

name** tbl;

int size;

public:

table(int sz = 15);

~table();

name* look(char*, int = 0);

name* insert(char* s) { return look(s,1); }

};

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



table::table(int sz)

{

if (sz < 0) error("размер таблицы отрицателен");

tbl = new name*[size = sz];

for ( int i = 0; i<sz; i++) tbl[i] = 0;

}

 

table::~table()

{

for (int i = 0; i<size; i++) {

name* nx;

for (name* n = tbl[i]; n; n=nx) {

nx = n->next;

delete n->string;

delete n;

}

}

delete tbl;

}

Описав деструктор для класса name, можно получить более ясный и простой вариант table::~table(). Функция поиска практически совпадает с приведенной в примере калькулятора ($$3.13):

name* table::look(const char* p, int ins)

{

int ii = 0;

char* pp = p;

while (*pp) ii = ii<<1 ^ *pp++;

if (ii < 0) ii = -ii;

ii %= size;

for (name* n=tbl[ii]; n; n=n->next)

if (strcmp(p,n->string) == 0) return n;

name* nn = new name;

nn->string = new char[strlen(p)+1];

strcpy(nn->string,p);

nn->value = 1;

nn->next = tbl[ii];

tbl[ii] = nn;

return nn;

}

Очевидно, что функции-члены класса должны перетранслироваться всякий раз, когда в описание класса вносится какое-либо изменение. В идеале такое изменение никак не должно отражаться на пользователях класса. К сожалению, обычно бывает не так. Для размещения переменной, имеющей тип класса, транслятор должен знать размер объекта класса. Если размер объекта изменится, нужно перетранслировать файлы, в которых использовался класс. Можно написать системную программу (и она даже уже написана), которая будет определять минимальное множество файлов, подлежащих перетрансляции после изменения класса. Но такая программа еще не получила широкого распространения.

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

Можно не знать определения всего класса, если представлять каждый объект как указатель на "настоящий" объект. Это позволяет решить задачу, поскольку все указатели будут иметь одинаковый размер, а размещение настоящих объектов будет проводиться только в одном файле, в котором доступны частные части классов. Однако, такое решение приводит к дополнительному расходу памяти на каждый объект и дополнительному обращению к памяти при каждом использовании члена. Еще хуже, что каждый вызов функции с автоматическим объектом класса требует вызовов функций выделения и освобождения памяти. К тому же становится невозможной реализация подстановкой функций-членов, работающих с частными членами класса. Наконец, такое изменение сделает невозможным связывание программ на С++ и на С, поскольку транслятор С будет по другому обрабатывать структуры (struct). Поэтому такое решение было сочтено неприемлемым для С++.

С другой стороны, С++ предоставляет средство для создания абстрактных типов, в которых связь между интерфейсом пользователя и реализацией довольно слабая. В главе 6 вводятся производные классы и описываются абстрактные базовые классы, а в $$13.3 поясняется, как с помощью этих средств реализовать абстрактные типы. Цель этого - дать возможность определять пользовательские типы столь же эффективные

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

Законченный пример класса

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

Вот пример законченного определения типа intset, представляющего понятие "множество целых":

class intset {

int cursize, maxsize;

int *x;

public:

intset(int m, int n); // не более m целых из 1..n

~intset();

int member(int t) const; // является ли t членом?

void insert(int t); // добавить к множеству t

void start(int& i) const { i = 0; }

void ok(int& i) const { return i<cursize; }

void next(int& i) const { return x[i++]; }

};

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

#include <iostream.h>

 

void error(const char *s)

{

cerr << "set: " << s << '\n';

exit(1);

}

Класс intset используется в функции main(), для которой должно быть задано два параметра: первый определяет число создаваемых случайных чисел, а второй - диапазон их значений:

int main(int argc, char* argv[])

{

if (argc != 3) error("нужно задавать два параметра");

int count = 0;

int m = atoi(argv[1]); // число элементов множества

int n = atoi(argv[2]); // из диапазона 1..n

intset s(m,n);

while (count<m) {

int t = randint(n);

if (s.member(t)==0) {

s.insert(t);

count++;

}

}

print_in_order(&s);

}

Значение счетчика параметров программы argc равно 3, хотя программа имеет только два параметра. Дело в том, что в argv[0] всегда передается дополнительный параметр, содержащий имя программы. Функция

extern "C" int atoi(const char*)

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

extern "C" int rand(); // будьте осторожны:

// числа не совсем случайные

int randint(int u) // диапазон 1..u

{

int r = rand();

if (r < 0) r = -r;

return 1 + r%u;

}

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

intset::intset(int m, int n) // не более m целых в 1..n

{

if (m<1 || n<m) error("недопустимый размер intset");

cursize = 0;

maxsize = m;

x = new int[maxsize];

}

 

intset::~intset()

{

delete x;

}

Целые добавляются таким образом, что они хранятся во множестве в возрастающем порядке:

void intset::insert(int t)

{

if (++cursize > maxsize) error("слишком много элементов");

int i = cursize-1;

x[i] = t;

while (i>0 && x[i-1]>x[i]) {

int t = x[i]; // поменять местами x[i] и x[i-1]

x[i] = x[i-1];

x[i-1] = t;

i--;

}

}

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

int intset::member(int t) const // двоичный поиск

{

int l = 0;

int u = cursize-1;

while (l <= u) {

int m = (l+u)/2;

if (t < x[m])

u = m-1;

else if (t > x[m])

l = m+1;

else

return 1; // найден

}

return 0; // не найден

}

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

Пользователь получает три функции: start() - для инициализации итерации, ok() - для проверки, есть ли следующий элемент, и next() - для получения следующего элемента:

class intset {

// ...

void start(int& i) const { i = 0; }

int ok(int& i) const { return i<cursize; }

int next(int& i) const { return x[i++]; }

};

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

void print_in_order(intset* set)

{

int var;

set->sart(var);

while (set->ok(var)) cout << set->next(var) << '\n';

}

Другой способ построения итератора по множеству приведен в $$7.8.



<== предыдущая лекция | следующая лекция ==>
Подстановка | Еще о классах


Карта сайта Карта сайта укр


Уроки php mysql Программирование

Онлайн система счисления Калькулятор онлайн обычный Инженерный калькулятор онлайн Замена русских букв на английские для вебмастеров Замена русских букв на английские

Аппаратное и программное обеспечение Графика и компьютерная сфера Интегрированная геоинформационная система Интернет Компьютер Комплектующие компьютера Лекции Методы и средства измерений неэлектрических величин Обслуживание компьютерных и периферийных устройств Операционные системы Параллельное программирование Проектирование электронных средств Периферийные устройства Полезные ресурсы для программистов Программы для программистов Статьи для программистов Cтруктура и организация данных


 


Не нашли то, что искали? Google вам в помощь!

 
 

© life-prog.ru При использовании материалов прямая ссылка на сайт обязательна.

Генерация страницы за: 0.533 сек.