straustrup2 (852740), страница 36
Текст из файла (страница 36)
Такую функцию называют другом класса. Вводятся понятия статических членовкласса и указателей на члены класса. Здесь же показано, как определить дискриминирующееобъединение.$$5.5 Конструкторы и деструкторы. Объект может создаваться как автоматический, статическийили как объект в свободной памяти. Кроме того, объект может быть членом некоторогоагрегата (массива или другого класса), который тоже можно размещать одним из этих трехспособов. Подробно объясняется использование конструкторов и деструкторов, описывается122Бьерн Страуструп.Язык программирования С++применение определяемых пользователем функций размещения в свободной памяти и функцийосвобождения памяти.5.2 Классы и членыКласс - это пользовательский тип. Этот раздел знакомит с основными средствами определения класса,создания его объектов, работы с такими объектами и, наконец, удаления этих объектов послеиспользования.5.2.1 Функции-членыПосмотрим, как можно представить в языке понятие даты, используя для этого тип структуры и наборфункций, работающих с переменными этого типа:struct date { int month, day, year; };date today;void set_date(date*, int, int, int);void next_date(date*);void print_date(const date*);// ...Никакой явной связи между функциями и структурой date нет.
Ее можно установить, если описатьфункции как члены структуры:struct date {int month, day, year;void set(int, int, int);void get(int*, int* int*);void next();void print();};Описанные таким образом функции называются функциями-членами. Их можно вызывать только черезпеременные соответствующего типа, используя стандартную запись обращения к члену структуры:date today;date my_birthday;void f(){my_birthday.set(30,12,1950);today.set(18,1,1991);my_birthday.print();today.next();}Поскольку разные структуры могут иметь функции-члены с одинаковыми именами, при определениифункции-члена нужно указывать имя структуры:void date::next(){if (++day > 28 ) {// здесь сложный вариант}}В теле функции-члена имена членов можно использовать без указания имени объекта.
В таком случаеимя относится к члену того объекта, для которого была вызвана функция.5.2.2 КлассыМы определили несколько функций для работы со структурой date, но из ее описания не следует, чтоэто единственные функции, которые предоставляют доступ к объектам типа date. Можно установить123Бьерн Страуструп.Язык программирования С++такое ограничение, описав класс вместо структуры:class date {int month, day, year;public:void set(int, int, int);void get(int*, int*, int*);void next();void print()};Служебное слово public (общий) разбивает описание класса на две части.
Имена, описанные в первойчастной (private) части класса, могут использоваться только в функциях-членах. Вторая - общая часть представляет собой интерфейс с объектами класса. Поэтому структура – это такой класс, в котором поопределению все члены являются общими. Функции-члены класса определяются и используются точнотак же, как было показано в предыдущем разделе:void date::print()// печать даты{cout << month << '/' << day << '/' << year ;}в принятом в США видеОднако от функций не членов частные члены класса date уже ограждены:void backdate(){today.day--;}// ошибкаЕсть ряд преимуществ в том, что доступ к структуре данных ограничен явно указанным спискомфункций.
Любая ошибка в дате (например, December, 36, 1985) могла быть внесена только функциейчленом, поэтому первая стадия отладки - локализация ошибки – происходит даже до первого пускапрограммы. Это только частный случай общего правила: любое изменение в поведении типа date можети должно вызываться изменениями в его членах. Другое преимущество в том, что потенциальномупользователю класса для работы с ним достаточно знать только определения функций-членов.Защита частных данных основывается только на ограничении использования имен членов класса.Поэтому ее можно обойти с помощью манипуляций с адресами или явных преобразований типа, но этоуже можно считать мошенничеством.5.2.3 Ссылка на себяВ функции-члене можно непосредственно использовать имена членов того объекта, для которого онабыла вызвана:class X {int m;public:int readm() { return m; }};void f(X aa, X bb){int a = aa.readm();int b = bb.readm();// ...}При первом вызове readm() m обозначает aa.m, а при втором - bb.m.У функции-члена есть дополнительный скрытый параметр, являющийся указателем на объект, длякоторого вызывалась функция.
Можно явно использовать этот скрытый параметр под именем this.Считается, что в каждой функции-члене класса X указатель this описан неявно какX *const this;124Бьерн Страуструп.Язык программирования С++и инициализируется, чтобы указывать на объект, для которого функция-член вызывалась. Этотуказатель нельзя изменять, поскольку он постоянный (*const).
Явно описать его тоже нельзя, т.к. 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->sucp->pre = this;// явное использование "this"suc->pre = p;// т.е. this->suc->pre = psuc = p;// т.е. this->suc = p}dlink* list_head;void f(dlink* a, dlink* b){// ...list_head->append(a);list_head->append(b);}Списки с такой общей структурой служат фундаментом списочных классов, описываемых в главе 8.Чтобы присоединить звено к списку, нужно изменить объекты, на которые настроены указатели this, preи suc.
Все они имеют тип dlink, поэтому функция-член dlink::append() имеет к ним доступ. Защищаемойединицей в С++ является класс, а не отдельный объект класса.Можно описать функцию-член таким образом, что объект, для которого она вызывается, будетдоступен ей только по чтению. Тот факт, что функция не будет изменять объект, для которого онавызывается (т.е. this*), обозначается служебным словом const в конце списка параметров:class X {int m;public:readme() const { return m; }writeme(int i) { m = i; }};Функцию-член со спецификацией const можно вызывать для постоянных объектов, а функцию-член безтакой спецификации - нельзя:void f(X& mutable, const X& constant){mutable.readme();// нормальноmutable.writeme(7); // нормальноconstant.readme();// нормальноconstant.writeme(7); // ошибка}125Бьерн Страуструп.Язык программирования С++В этом примере разумный транслятор смог бы обнаружить, что функция X::writeme() пытается изменитьпостоянный объект.
Однако, это непростая задача для транслятора. Из-за раздельной трансляции он вобщем случае не может гарантировать "постоянство" объекта, если нет соответствующего описания соспецификацией const. Например, определения readme() и writeme() могли быть в другом файле:class X {int m;public:readme() const;writeme(int i);};В таком случае описание readme() со спецификацией const существенно.Тип указателя this в постоянной функции-члене класса X есть const X *const. Это значит, что без явногоприведения с помощью this нельзя изменить значение объекта:class X {int m;public:// ...void implicit_cheat() const { m++; } // ошибкаvoid explicit_cheat() const { ((X*)this)->m++; }// нормально};Отбросить спецификацию const можно потому, что понятие "постоянства" объекта имеет два значения.Первое, называемое "физическим постоянством" состоит в том, что объект хранится в защищенной отзаписи памяти. Второе, называемое "логическим постоянством" заключается в том, что объектвыступает как постоянный (неизменяемый) по отношению к пользователям.
Операция над логическипостоянным объектом может изменить часть данных объекта, если при этом не нарушается егопостоянство с точки зрения пользователя. Операциями, ненарушающими логическое постоянствообъекта, могут быть буферизация значений, ведение статистики, изменение переменных-счетчиков впостоянных функциях-членах.Логического постоянства можно достигнуть приведением, удаляющим спецификацию const:class calculator1 {int cache_val;int cache_arg;// ...public:int compute(int i) const;// ...};int calculator1::compute(int i) const{if (i == cache_arg) return cache_val;// нелучший способ((calculator1*)this)->cache_arg = i;((calculator1*)this)->cache_val = val;return val;}Этого же результата можно достичь, используя указатель на данные без const:struct cache {int val;int arg;};class calculator2 {126Бьерн Страуструп.Язык программирования С++cache* p;// ...public:int compute(int i) const;// ...};int calculator2::compute(int i) const{if (i == p->arg) return p->val;// нелучший способp->arg = i;p->val = val;return val;}Отметим, что const нужно указывать как в описании, так и в определении постоянной функции-члена.Физическое постоянство обеспечивается помещением объекта в защищенную по записи память, толькоесли в классе нет конструктора ($$7.1.6).5.2.4 ИнициализацияИнициализация объектов класса с помощью таких функций как set_date() - неэлегантное и чреватоеошибками решение.
Поскольку явно не было указано, что объект требует инициализации, программистможет либо забыть это сделать, либо сделать дважды, что может привести к столь жекатастрофическим последствиям. Лучше дать программисту возможность описать функцию, явнопредназначенную для инициализации объектов. Поскольку такая функция конструирует значениеданного типа, она называется конструктором.
Эту функцию легко распознать - она имеет то же имя, чтои ее класс:class date {// ...date(int, int, int);};Если в классе есть конструктор, все объекты этого класса будут проинициализированы. Есликонструктору требуются параметры, их надо указывать:date today = date(23,6,1983);date xmas(25,12,0);date my_birthday;// краткая форма// неправильно, нужен инициализаторЧасто бывает удобно указать несколько способов инициализации объекта. Для этого нужно описатьнесколько конструкторов:class date {int month, day, year;public:// ...date(int, int, int);date(int, int);date(int);date();date(const char*);};//////////день, месяц, годдень, месяц и текущий годдень и текущие год и месяцстандартное значение: текущая датадата в строковом представленииПараметры конструкторов подчиняются тем же правилам о типах параметров, что и все остальныефункции ($$4.6.6).
Пока конструкторы достаточно различаются по типам своих параметров, трансляторспособен правильно выбрать конструктор:date today(4);date july4("July 4, 1983");date guy("5 Nov");127Бьерн Страуструп.Язык программирования С++// инициализация стандартным значениемdate now;Размножение конструкторов в примере c date типично. При разработке класса всегда есть соблазндобавить еще одну возможность, - а вдруг она кому-нибудь пригодится. Чтобы определитьдействительно нужные возможности, надо поразмышлять, но зато в результате, как правило,получается более компактная и понятная программа.