Объектно-ориентированное программирование, страница 6

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();

}

Обратите внимание на то, как объявлены объекты вложенного класса B. Внутри класса A — это простое объявление, следующее сразу за объявлением класса B. Внутри main — это объявление вида A::B bb;. Полезно провести сравнение со случаем структур. Наличие объектов вложенного класса и указателей на них порождают множество вариантов доступа к методу вложенного класса. Так, оператор a.b.p(); обращается к методу void p() класса B, который вложен в класс A. При этом будет выведена строка j=2. Это же действие имеет другое словесное описание. Объекту b, являющемуся внутренним данным объекта a, послано сообщение в виде имени метода p().

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

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

Вопросы и упражнения

Объясните, что будет выведено на экран монитора в последнем примере. Что произойдет, если два объявления int i; и class B перенести в секцию private класса A? Что будет, если объявление int j; перенести в секцию private класса B?

Массивы объектов

Массив объектов какого-либо класса, является обычной структурой данных в ООП. Он обладает следующей особенностью: класс, массив объектов которого необходимо определить, должен иметь конструктор без параметров (default constructor). Предположим, что мы хотим иметь массив объектов класса, но, в то же время, не хотим отказываться от возможности инициализировать поля данных объекта при его создании. В этом случае можно поступать так, как это сделано в следующем примере:

class Resistors

{

double r;

void Assign (double rs) { r=rs; }  // Private-метод

public:

Resistors (double rs) { r=rs; }  // Конструктор с параметром

Resistors () { Assign(100.); }   // Конструктор без параметров

double Parallel (Resistors q)

{

  return (q.r*r/(q.r+r));

}

double Series (Resistors q)

{

  return (r+q.r);

}

void pr () { printf ("r=%6.2f",r); }

~Resistors() { puts("Destructor invoked"); }

};

void main ()

{

Resistors r1 (500), r2 (1000), r3;

printf("\n Class Resistors Demo\n");

printf("\n R1: ");      r1.pr();

printf("\n R2: ");      r2.pr();

printf("\n R3: ");      r3.pr();

printf("\n R1 + R2 =%6.2f\n R1 || R2 =%6.2f", r1.Series(r2), r1.Parallel(r2));

Resistors many[6];  // Объявление массива объектов

puts("\n\n Array resistors\n");

for (int i=0; i<4; i++,putch('\n'))

  many[i].pr();

}

При объявлении Resistors r1(500), r2(1000); будет вызван первый конструктор с параметром. При объявлении r3, а также массива объектов класса many[100]; будет вызван второй конструктор без параметров. Заботу о выяснении вида конструктора берет на себя компилятор C++. Определяющим фактором при этом является тип параметров. В нашем случае объявление массива объектов Resistors many[6]; заставляет компилятор выбрать второй конструктор. Формально это правило звучит так: объект класса с конструктором может быть членом агрегата (массива) тогда и только тогда, когда он либо не имеет конструктора (тогда компилятор предоставит ему конструктор по умолчанию), или если один из его конструкторов не имеет аргументов. В последнем случае именно этот конструктор будет использован при создании агрегата. Если элемент агрегата — объект класса с деструктором, то этот деструктор будет вызван для при уничтожении агрегата.

В нашем примере тело конструктора без параметров представляет собой вызов private-функции Assign. Отличие ее от public-метода состоит в том, что имя метода невидимо, недоступно за пределами класса. Так, например, в главной функции оператор вида: r1.Assign(2.); был бы незаконным и вызвал бы сообщение об ошибке на стадии компиляции. Однако, метод доступен внутри любого другого метода этого же класса и позволяет присвоить внутренним данным какое-то фиксированное значение. Обратите внимание на то, что сопротивление r для всех элементов массива many[6] (объектов класса Resistors) принимает значение 100., несмотря на то, что мы не пользовались оператором цикла для пробега по элементам и явного присвоения. На вопрос: сколько строк вида Destructor invoked будет выведено при запуске примера правильным будет ответ: 11 раз. Это объясняется тем, что в main объявлено 9 объектов и, кроме того, два объекта копируются (а затем уничтожаются) при входе в методы Series и Parallel.

Friend-конструкции