Бьерн Страуструп. Язык программирования С++



бет38/124
Дата16.07.2016
өлшемі3.27 Mb.
#204081
түріКнига
1   ...   34   35   36   37   38   39   40   41   ...   124

5.2.2 Классы


Мы определили несколько функций для работы со структурой date, но из ее

описания не следует, что это единственные функции, которые

предоставляют доступ к объектам типа date. Можно установить такое

ограничение, описав класс вместо структуры:
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;
и инициализируется, чтобы указывать на объект, для которого

функция-член вызывалась. Этот указатель нельзя изменять, поскольку

он постоянный (*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->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);

}
Списки с такой общей структурой служат фундаментом списочных классов,

описываемых в главе 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); // ошибка

}
В этом примере разумный транслятор смог бы обнаружить, что

функция 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 {

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).





Достарыңызбен бөлісу:
1   ...   34   35   36   37   38   39   40   41   ...   124




©dereksiz.org 2024
әкімшілігінің қараңыз

    Басты бет