Мюррей Хилл - C++ Страница 28
Мюррей Хилл - C++ читать онлайн бесплатно
В функции члене имена членов могут использоваться без явной ссылки на объект. В этом случае имя относится к члену того объекта, для которого функция была вызвана.
5.2.2 Классы
Описание date в предыдущем подразделе дает множество функций для работы с date, но не указывает, что эти функции должны быть единственными для доступа к объектам типа date. Это ограничение можно наложить используя вместо struct class:
class date (* int month, day, year; public: void set(int, int, int); void get(int*, int*, int*); void next(); void print(); *);
Метка public: делит тело класса на две части. Имена в первой, закрытой части, могут использоваться только функциями членами. Вторая, открытая часть, составляет интерфейс к обекту класса. Struct – это просто class, у которого все члены классы открытые, поэтому функции члены определяются и исползуются точно так же, как в предыдущем случае. Например:
void date::ptinr() // печатает в записи, принятой в США (* cout «« month «« "/" «« day «« "/" year; *)
Однако функции не члены отгорожены от использования зарытых членов класса date. Например:
void backdate() (* today.day–; // ошибка *)
В том, что доступ к структуре данных ограничен явно опсанным списком функций, есть несколько преимуществ. Любая ошибка, которая приводит к тому, что дата принимает недопутимое значение (например, Декабрь 36, 1985) должна быть вывана кодом функции члена, поэтому первая стадия отладки, лкализация, выполняется еще до того, как программа будет запущена. Это частный случай общего утверждения, что любое изменение в поведении типа date может и должно вызываться именениями в его членах. Другое преимущество – это то, что птенциальному пользователю такого типа нужно будет только унать определение функций членов, чтобы научиться им пользоваться.
Защита закрытых данных связана с ограничением использвания имен членов класса. Это можно обойти с помощью манипляции адресами, но это уже, конечно, жульничество.
5.2.3 Ссылки на Себя
В функции члене на члены объекта, для которого она была вызвана, можно ссылаться непосредственно. Например:
class x (* int m; public: int readm() (* return m; *) *);
x aa; x bb;
void f() (* int a = aa.readm(); int b = bb.readm(); // ... *)
В первом вызове члена member() m относится к aa.m, а во втором – к bb.m.
Указатель на объект, для которого вызвана функция член, является скрытым параметром функции. На этот неявный параметр можно ссылаться явно как на this. В каждой функции класса x указатель this неявно описан как
x* this;
и инициализирован так, что он указывает на объект, для которого была вызвана функция член. this не может быть описан явно, так как это ключевое слово. Класс x можно эквивалентным образом описать так:
class x (* int m; public: int readm() (* return this-»m; *) *);
При ссылке на члены использование this излишне. Главным образом this используется при написании функций членов, котрые манипулируют непосредственно указателями. Типичный пример этого – функция, вставляющая звено в дважды связанный список:
class dlink (* dlink* pre; // предшествующий dlink* suc; // следующий public: void append(dlink*); // ... *);
void dlink::append(dlink* p) (* p-»suc = suc; // то есть, p-»suc = this-»suc p-»pre = this; // явное использование this suc-»pre = p; // то есть, this-»suc-»pre = p suc = p; // то есть, this-»suc = p *)
dlink* list_head;
void f(dlink*a, dlink *b) (* // ... list_head-»append(a); list_head-»append(b); *)
Цепочки такой общей природы являются основой для списквых классов, которые описываются в Главе 7. Чтобы присоеднить звено к списку необходимо обновить объекты, на которые указывают указатели this, pre и suc (текущий, предыдущий и последующий). Все они типа dlink, поэтому функция член dlink::append() имеет к ним доступ. Единицей защиты в С++ яляется class, а не отдельный объект класса.
5.2.4 Инициализация
Использование для обеспечения инициализации объекта класса функций вроде set_date() (установить дату) неэлегантно и чревато ошибками. Поскольку нигде не утверждается, что обект должен быть инициализирован, то программист может забыть это сделать, или (что приводит, как правило, к столь же рарушительным последствиям) сделать это дважды. Есть более хроший подход: дать возможность программисту описать функцию, явно предназначенную для инициализации объектов. Поскольку такая функция конструирует значения данного типа, она назывется конструктором. Конструктор распознается по тому, что имеет то же имя, что и сам класс. Например:
class date (* // ... date(int, int, int); *);
Когда класс имеет конструктор, все объекты этого класса будут инициализироваться. Если для конструктора нужны парметры, они должны даваться:
date today = date(23,6,1983); date xmas(25,12,0); // сокращенная форма // (xmas – рождество) date my_burthday; // недопустимо,опущена инициализация
Часто бывает хорошо обеспечить несколько способов иницализации объекта класса. Это можно сделать, задав несколько конструкторов. Например:
class date (* int month, day, year; public: // ... date(int, int, int); // день месяц год date(char*); // дата в строковом представлении date(int); // день, месяц и год сегодняшние date(); // дата по умолчанию: сегодня *);
Конструкторы подчиняются тем же правилам относительно типов параметров, что и перегруженные функции (#4.6.7). Если конструкторы существенно различаются по типам своих парамеров, то компилятор при каждом использовании может выбрать правильный:
date today(4); date july4(«Июль 4, 1983»); date guy(«5 Ноя»); date now; // инициализируется по умолчанию
Заметьте, что функции члены могут быть перегружены без явного использования ключевого слова overload. Поскольку поный список функций членов находится в описании класса и как правило короткий, то нет никакой серьезной причины требовать использования слова overload для предотвращения случайного повторного использования имени.
Размножение конструкторов в примере с date типично. При разработке класса всегда есть соблазн обеспечить «все», покольку кажется проще обеспечить какое-нибудь средство просто на случай, что оно кому-то понадобится или потому, что оно изящно выглядит, чем решить, что же нужно на самом деле. Поледнее требует больших размышлений, но обычно приводит к программам, которые меньше по размеру и более понятны. Один из способов сократить число родственных функций – использвать параметры со значением по умолчанию, пример. В случае date для каждого параметра можно задать значение по умолчнию, интерпретируемое как «по умолчанию принимать: today» (сегодня).
class date (* int month, day, year; public: // ... date(int d =0, int m =0, int y =0); date(char*); // дата в строковом представлении *);
date::date(int d, int m, int y) (* day = d ? d : today.day; month = m ? m : today.month; year = y ? y : today.year; // проверка, что дата допустимая // ... *)
Когда используется значение параметра, указывающее «брать по умолчанию», выбранное значение должно лежать вне множества возможных значений параметра. Для дня day и месяца mounth ясно, что это так, но для года year выбор нуля неочвиден. К счастью, в европейском календаре нет нулевого года . Сразу после 1 г. до н.э. (year==-1) идет 1 г. н.э. (year==1), но для реальной программы это может оказаться слишком тонко.
Объект класса без конструкторов можно инициализировать путем присваивания ему другого объекта этого класса. Это моно делать и тогда, когда конструкторы описаны. Например:
date d = today; // инициализация посредством присваивания
По существу, имеется конструктор по умолчанию, опредленный как побитовая копия объекта того же класса. Если для класса X такой конструктор по умолчанию нежелателен, его моно переопределить конструктором с именем X(X amp;). Это будет осуждаться в #6.6.
5.2.5 Очистка
Определяемый пользователем тип чаще имеет, чем не имеет, конструктор, который обеспечивает надлежащую инициализацию. Для многих типов также требуется обратное действие, деструктор, чтобы обеспечить соответствующую очистку объектов этого типа. Имя деструктора для класса X есть ~X() («дополнение конструктора»). В частности, многие типы используют некоторый объем памяти из свободной памяти (см. #3.2.6), который выдляется конструктором и освобождается деструктором. Вот, наример, традиционный стековый тип, из которого для краткости полностью выброшена обработка ошибок:
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
При программировании с использованием классов очень чато используется много маленьких функций. По сути, везде, где в программе традиционной структуры стояло бы просто какое-нбудь обычное использование структуры данных, дается функция. То, что было соглашением, стало стандартом, который распознет компилятор. Это может страшно понизить эффективность, птому что стоимость вызова функции (хотя и вовсе не высокая по сравнению с другими языками) все равно намного выше, чем пара ссылок по памяти, необходимая для тела функции.
Жалоба
Напишите нам, и мы в срочном порядке примем меры.