C++
Шрифт:
Объект класса без конструкторов можно инициализировать путем присваивания ему другого объекта этого класса. Это моно делать и тогда, когда конструкторы описаны. Например:
date d = today; // инициализация посредством присваивания
По существу, имеется конструктор по умолчанию, опредленный как побитовая копия объекта того же класса. Если для класса X такой конструктор по умолчанию нежелателен, его моно переопределить конструктором с именем X(X amp;). Это будет осуждаться в #6.6.
5.2.5 Очистка
Определяемый
class char_stack (* int size; char* top; char* s; public: char_stack(int sz) (* top=s=new char[size=sz]; *) ~char_stack (* delete s; *) // деструктор void push(char c) (* *top++ = c; *) char pop (* return *–top;*) *)
Когда char_stack выходит из области видимости, вызываеся деструктор:
void f (* char_stack s1(100); char_stack s2(200); s1.push('a'); s2.push(s1.pop); char ch = s2.pop; cout «„ chr(ch) «« «\n“; *)
Когда вызывается f, конструктор char_stack вызывается для s1, чтобы выделить вектор из 100 символов, и для s2, чтбы выделить вектор из 200 символов. При возврате из f эти два вектора будут освобождены.
5.2.6 Inline
При программировании с использованием классов очень чато используется много маленьких функций. По сути, везде, где в программе традиционной структуры стояло бы просто какое-нбудь обычное использование структуры данных, дается функция. То, что было соглашением, стало стандартом, который распознет компилятор. Это может страшно понизить эффективность, птому что стоимость вызова функции (хотя и вовсе не высокая по сравнению с другими языками) все равно намного выше, чем пара ссылок по памяти, необходимая для тела функции.
Чтобы справиться с этой проблемой, был разработан аппрат inline-функций. Функция, определенная (а не просто опсанная) в описании класса, считается inline. Это значит, наример, что в функциях, которые используют приведенные выше char_stack, нет никаких вызовов функций кроме тех, которые используются для реализации операций вывода! Другими словами, нет никаких затрат времени выполнения, которые стоит принмать во внимание при разработке класса. Любое, даже самое мленькое действие, можно задать эффективно. Это утверждение снимает аргумент, который чаще всего приводят чаще всего в пользу открытых членов данных.
Функцию член можно также описать как inline вне описания класса. Например: char char_stack (* int size; char* top;
char* s; public: char pop; // ... *);
inline char char_stack::pop (* return *–top; *)
5.3 Интерфейсы и реализации
Что представляет собой хороший класс? Нечто, имеющее нбольшое и хорошо определенное множество действий. Нечто, что можно рассматривать как «черный ящик», которым манипулируют только посредством этого множества действий. Нечто, чье фатическое представление можно любым мыслимым способом измнить, не повлияв на способ использования множества действий. Нечто, чего можно хотеть иметь больше одного.
Для всех видов контейнеров существуют очевидные примеры: таблицы, множества, списки, вектора, словари и т.д. Такой класс имеет операцию «вставить», обычно он также имеет оперции для проверки того, был ли вставлен данный элемент. В нем могут быть действия для осуществления проверки всех элементов в определенном порядке, и кроме всего прочего, в нем может иметься операция для удаления элемента. Обычно контейнерные (то есть, вмещающие) классы имеют конструкторы и деструкторы.
Сокрытие данных и продуманный интерфейс может дать коцепция модуля (см. например #4.4: файлы как модули). Класс, однако, является типом. Чтобы использовать его, необходимо создать объекты этого класса, и таких объектов можно создвать столько, сколько нужно. Модуль же сам является объектом. Чтобы использовать его, его надо только инициализировать, и таких объектов ровно один.
5.3.1 Альтернативные реализации
Пока описание открытой части класса и описание функций членов остаются неизменными, реализацию класса можно модифцировать не влияя на ее пользователей. Как пример этого расмотрим таблицу имен, которая использовалась в настольном калькуляторе в Главе 3. Это таблица имен:
struct name (* char* string; char* 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 тем, что это настоящий тип. Можно описать более чем одну
table, можно иметь указатель на table и т.д. Например:
#include «table.h»
table globals; table keywords; table* locals;
main (* locals = new table; // ... *)
Вот реализация table::look, которая использует линеный поиск в связанном списке имен name в таблице:
#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, должны оставаться верными без изменений: