C++
Шрифт:
void date::ptinr // печатает в записи, принятой в США (* cout «« month «« "/" «« day «« "/" year; *)
Однако функции не члены отгорожены от использования зарытых членов класса date. Например:
void backdate (* today.day–; // ошибка *)
В том, что доступ к структуре данных ограничен явно опсанным списком функций, есть несколько преимуществ. Любая ошибка, которая приводит к тому, что дата принимает недопутимое значение (например, Декабрь 36, 1985) должна быть вывана кодом функции члена, поэтому первая стадия отладки, лкализация, выполняется еще до того, как
Защита закрытых данных связана с ограничением использвания имен членов класса. Это можно обойти с помощью манипляции адресами, но это уже, конечно, жульничество.
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), но для реальной программы это может оказаться слишком тонко.