Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ТП лекции Раздел 4.doc
Скачиваний:
16
Добавлен:
28.09.2019
Размер:
2.56 Mб
Скачать

4.7.4. Использование описателя const в классах.

Этот описатель используется в качестве средства повышения безо­пасности, а следовательно, и надежности в объектно-ориентированных програм­мах. Он применим к методам класса, параметрам функций и объектам класса. Если какой-то метод класса снабжен описателем const, это означает, что он не должен изменять никаких данных класса. Применение const к параметру метода означает, что этот конкретный параметр не может быть изменен внутри данного метода. Попытка изменить его приводит к ошибке на этапе компиляции. Нако­нец, если какой-то объект класса определен с описателем const, то это означает, что он должен пользоваться const-методами. Случай пользования обычным ме­тодом порождает ошибку. Например:

class A {

int j;

public:

A (int k=0) { j=k; }// Конструктор с параметром

int GetJ (int k) const // Метод не может изменять j

{

if (k>j) return j; // Он этого и не делает

else return k;

}

int GetJ (int k) // Метод может изменять j

{

if (k<j) return j;

else return j=k; // и он это делает

}

void change (A& al, const A& a2) // Параметр а1 можно изменять

{

al.j = a2.j;

a2.j = 5; // Ошибка; а2 нельзя изменять

}

void out ()

{

printf(" j =%d”,j);

}

};

void main()

{

A a, b(5); // Обычные объекты

const A c; // Const-объект

a.GetJ(1); // Будет вызвана int GetJ (int)

a.out(); // Выведет j= 1

c.GetJ(2); // Будет вызвана int GetJ(int) const

c.out(); // Ошибка, так как out () не const -метод

a.out(); // Выведет j= 1

a.change(a, b);

a.out(); // Выведет j=5

c.change(a, b); // Ошибка, так как change не const-метод

}

Еще один пример использования константных функций-членов класса. Кроме того, в нем показано описание функций вне тела класса.

// Example of a constant member function

class Date

{

public:

  Date( int mn, int dy, int yr );

  int getMonth() const;       // A read-only function

  void setMonth( int mn );    // A write function;

                              //    cannot be const

private:

  int month;

};

int Date::getMonth() const

{

  return month;        // Doesn't modify anything

}

void Date::setMonth( int mn )

{

  month = mn;          // Modifies data member

}

Тема 4.8. Наследование классов в Visual C++.

4.8.1. Вложенность классов.

Как мы видели, компонентами класса могут быть данные и методы.

Существует специально зарезервированное ключевое слово this, определяе­мое как указатель на объект, которому было послано обрабатываемое сообще­ние. В каком-то конкретном методе указатель this содержит адрес объекта, ко­торый инициировал этот метод. Это справедливо только для методов класса, не имеющих описатель static. При любом вызове метода класса указатель this пе­редается как скрытый (hidden) параметр, то есть передается неявно. Он являет­ся локальной переменной внутри метода и неявно используется при обращении к данным и методам класса. Иногда его используют явно. Рассмотрим эти воз­можности на примере инкапсуляции в классе Node такой распространенной струк­туры данных, как узел линейного односвязного списка. Класс Node имеет в каче­стве элемента данных указатель next на объект своего собственного класса.

class Node {

char *data; // Информационная часть узла

Node *next; // Указатель на следующий узел

publiс:

Node (char *s)

{ // Конструктор

data = strcpy(new char['strlen(s)+l], s);

next = NULL;

}

void GetData()

{

puts (this->data);

}

void SetNextTo (Node& n)

{

n.next = this;

}

Node* GetNext()

{

return next;

}

~Node ()

{

puts("\nDeleting data");

delete data;

}

};

void main ()

{

// Использование класса Node

Node nl(“First"), n2( "Second"); nl.GetData();

n2.GetData(); n2.SetNextTo(nl); nl.GetNext()->GetData();

}

Вопрос: написать результаты работы.

Конструктор класса Node дважды вызывается в функции main при объявлении объектов n1 и n2. Каждый раз он запрашивает память из области heap и разме­щает там строку символов (содержимое узла списка), которая передастся конст­руктору в качестве параметра. Метод класса GetData выводит в поток stdout (стандартный выходной поток) поле data того объекта, которому передается со­общение. Здесь мы специально вставили выбор с помощью указателя (this->), чтобы проиллюстрировать то, что обычно делает компилятор. Его можно оста­вить, а можно и убрать. Любое обращение к data в любом методе класса Node компилятор расширяет до this->data. Оператор программы n1.GetData(); выве­дет строку "First", а оператор n2.GetData(); — строку "Second". Внутри конст­руктора мы обращаемся к переменной data, не указывая явно, какому объекту принадлежит это поле данных. Компилятор C++ сам расширяет это обращение до this->data. Метод SetNextTo получает в качестве параметра ссылку на объект класса Node. Поскольку этот метод принадлежит тому же классу, что и параметр, то внутренний компонент next объекта n прямо доступен в данном методе. Ему присваивается адрес объекта, который инициировал метол (n.next=this;). Адрес содержится в переменной this. Так как в нашем случае объекту n2 послано со­общение SetNextTo(n1), то указатель this содержит адрес объекта n2, и он при­сваивается полю next объекта nl. Получается, что объект nl имеет в качестве следующего узла списка объект n2, или можно сказать, что n2 зацеплен за nl.

Метод GetNext позволяет получить адрес следующего элемента списка, то есть поле next объекта, которому послано сообщение. В последней строке функции main он используется для того, чтобы добыть объект, стоящий в списке за объек­том nl. Выражение n1.GetNext() имеет результатом адрес объекта n2, которому посылается сообщение GetData. Следовательно, результатом всего выражения будет вывод поля data объекта n2, то есть строка "Second".

Подумайте, можно ли заме­нить тело метода GetNext на return this->next;? ДА!

Теперь рассмотрим другие возможности вложения. Прежде всего, объекты какого-либо класса могут быть вложены в число данных другого класса. Дру­гой возможный способ вложения — это когда описание какого-то класса вло­жено в описание другого класса. Рассмотрим сначала первый случай вложения:

// Класс One, объекты которого вложены в другой класс Two

class One {

int i1; //Private-данное класса One public:

One(int i) //Конструктор с параметром

{

il=i;

}

void p()// Вывод private-компонента il

{

printf ("\n i1=%d",i1);

}

};

class Two {

// Класс Two содержит объекты класса One

int i2; // Private-данниое класса Two One cl,dl; // Объекты другого класса

public: // Особенность конструктора

Two (int j, int k, int l) : cl(k), dl(l) // Инициализация в заголовке {

i2=j; // Присвоение в теле конструктора

}

// Методы вывода

void р()

{

printf ("\n i2=%d",i2);

}

void p1()

{

cl.p();

}

void p2()

{

dl.One::p();

}

};

//Использование объектов классов

void main()

{

One man(1);

Two men(2, 3, 4);

man.p();

men.p();

men.p1();

men.p2();

}

Анализируя этот пример, обратите внимание на конструктор класса Two. Он содержит в себе кроме собственного тела еще и список инициализации двух вложенных объектов: cl(k), dl(l) класса One. Инициализаторы внутренних объек­тов могут следовать в произвольном порядке, но они должны быть расположе­ны в заголовке конструктора (за двоеточием и разделяться запятыми). Выпол­няются они до исполнения тела конструктора объемлющего класса. При выходе из области действия объекта внешнего класса деструкторы классов вложенных объектов также выполняются до деструктора внешнего класса. Отметьте, что оба класса в нашем примере имеют в своем составе метод с именем р(). Спецификатор One в теле метода р2() необязателен, но он помогает читающе­му текст программы. Компилятор распознает, какой из двух методов следует вызвать по принадлежности объекта к конкретному классу.

Значения перемен­ных, которые будут выведены на экран, таковы:

i1=1, i2=2, i1=3, i1=4.

Отметим следующие различия:

• il=l — это значение внутренней переменной il объекта man класса One;

• i2=2 — это значение внутренней переменной i2 объекта men класса Two;

• i1=3 — это значение внутренней переменной i1 объекта сl класса One (объект cl, в свою очередь, вложен в класс Two и, следовательно, является внутрен­ним данным объекта men класса Two);

• i1=4 — это значение внутренней переменной i1 объекта dl класса One, ко­торый также вложен в класс Two.

Описание класса или структуры является по сути описанием нового типа дан­ных. При описании типа возможно сложное вложение классов, так же, как и для структур языка С. Например:

class А{ //Какай-то класс

public:

int i; // Его переменная

class В {

// Вложенное описание класса В

public:

int j; // Данное класса В

В (int i=0) { j=i; } // Конструктор вложенного класса

void p() // Метод вложенного класса

{

printf("\nj=%d",j);

}

} b , *pb ; // Объект и указатель вложенного класса

// Конструктор объемлющего класса // Инициализация объекта и указателя

A (int i1, int jj) { i=i1; b.j=jj; pb=&b; }

// Метод объемлющего класса

void show ()

{ printf("\ni=%d b. j=%d",i ,b. j) ;

}

};

void main()

{ //Иллюстрация использования

A a ( 1 , 2 ) ; // Объект объемлющего класса

A *pa=new A(3,4); // Указатель объемлющего класса

A::B bb(5); // Объект вложенного класса

А::В *pbb=new A::B(6); // Указатель вложенного класса

// Разнообразие обращений к методам

a.show();

pa->show();

a.b.p();

a.pb->p();

pa->b.p();

pa->pb->p();

bb.p();

pbb->p();

// Разнообразие присвоений

a.b.j =7; a.show();

pa->b.j =8; pa->show();

pa->pb->j=9; pa->show();

a.pb->j =10; a.show();

bb.j=11; bb.p();

}

Обратите внимание на то, как объявлены объекты вложенного класса В. Внутри класса А — это простое объявление, следующее сразу за объявлением класса В. Внутри main — это объявление вида А::В bb;. Полезно провести сравнение со случаем структур. Наличие объектов вложенного класса и указателей на них по­рождают множество вариантов доступа к методу вложенного класса. Так, опера­тор a.b.p(); обращается к методу void p() класса В, который вложен в класс А. При этом будет выведена строка j=2. Это же действие имеет другое словесное описание. Объекту B, являющемуся внутренним данным объекта A, послано со­общение в виде имени метода р(). Сам метод может быть идентифицирован как void А::В::р();, что кратко передает суть факта вложенности. Так как в классе А имеется указатель pb, который содержит адрес объекта B, то тот же самый метод можно вызвать с помощью указателя: a.pb->p();. Указатель ра содержит адрес вновь созданного объекта класса А. Вызов ра->b.р(); приведет к появлению на экране строки j=4. Два указателя используются при вызове pa->pb->p():. Здесь важно понимать, что указатель ра объявлен и действует внутри main, а указатель pb является public-данным класса А и только благодаря этому факту доступен внутри main.

Объект bb объявлен внутри main как объект класса В, вложенного в класс А. Прямой вызов все того же метода р() для объекта bb выглядит так: bb.p();. На экране появится строка j=5. Косвенный вызов pbb->p(): приведет к появлению строки j=6. Благодаря наличию указателей как на вложенный, так и на объем­лющий классы способы доступа к полю данных вложенного класса также могут быть весьма разнообразными. Однако следует иметь в виду, что прямой доступ возможен только потому, что поле j расположено в секции public вложенного класса. Если бы это было не так, то указанное поле можно было бы модифици­ровать внутри main только с помощью методов вложенного класса В. В примере таких методов у класса В не имеется.