Обращение к членам экземпляров в программе 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Обращение к членам экземпляров в программе



Обычный способ

Как уже упоминалось ранее, обращение к членам экземпляра класса осуществляется с помощью оператора «точка». Например:

Создаем экземпляр класса:

t_ DinArr DA;

Обращаемся к его члену функции Init для создания массива из 10 элементов:

DA. Init (10);

Теперь экземпляр DA содержит массив из 10 элементов.

Через указатель

На экземпляры класса можно создавать указатели и через них также обращаться к членам экземпляра. Например:

Создаем экземпляр класса:

t_ DinArr DA;

Определяем указатель на тип данных t_ DinArr и инициализируем его адресом экземпляра DA:

  t_ DinArr * P = & DA;

Теперь указатель P содержит адрес экземпляра DA.

Для того, чтобы обращаться к членам экземпляра класса через указатель P, мы должны (как и в обычных структурах) использовать оператор «стрелка» ->. Например, инициализируем экземпляр DA через указатель P:

P -> Init (10);

В результате экземпляр будет содержать массив на 10 элементов.

По ссылке

Ссылка на экземпляр класса создается так же, как и на обычную переменную:

Есть экземпляр класса:

t_ DinArr DA;

Создаем ссылку на этот экземпляр и инициализируем ее переменной DA:

t_ DinArr & Clone = DA;

Теперь у нас имеется один экземпляр класса t_ DinArr, к которому мы можем обращаться либо через имя DA, либо через имя Clone. Например:

Создаем массив на 10 элементов:

DA. Init (10);

Выводим на экран размер массива через переменную Clone:

cout << Clone. Count ();

На экране видим число 10.

Теперь добавляем к массиву еще один элемент со значением 1000 через переменную Clone:

Clone. Add (1000);

Выводим на экран:

cout << DA. Count ();

На экране видим число 11.

Выводим на экран значение последнего элемента массива:

cout << DA.Get (Clone.Count ()  - 1);

На экране видим число 1000.

Таким образом, с одним и тем же экземпляром мы можем работать через два эквивалентных имени: DA или Clone.

 

Массивы экземпляров класса

 

Из экземпляров класса можно создавать массивы. Создадим, например, статический массив из 6 экземпляров класса t_ DinArr:

t_ DinArr A [ 6 ];

Инициализируем экземпляры в этом массиве так:

for (int i = 0; i < 6; ++ i)

A [ i ]. Init (i + 1);      

В результате мы получили массив на 6 элементов, в котором каждый элемент представляет собой динамический массив с количеством элементов, равным номеру этого динамического массива (элемент A [ 0 ] содержит экземпляр класса t_ DinArr с массивом длиной 1 элемент; элемент A [ 1 ] содержит экземпляр класса t_ DinArr с массивом длиной 2 элемента; элемент A [ 2 ] содержит экземпляр класса t_ DinArr с массивом длиной 3 элемента и т.д. до элемента A [ 5 ],которыйсодержит экземпляр класса t_ DinArr с массивом длиной 6 элементов;

Заполним эту конструкцию некоторыми данными:

for (int i = 0; i < 6; ++ i)

for (int j = 0; j < A [ i ].Count (); ++ j)

     A [ i ]. Set (j, j);   

Теперь выведем все эти значения на экран:

for (int i = 0; i < 6; ++ i)

{

for (int j = 0; j < A [ i ].Count (); ++ j)

     cout << A [ i ].Get (j) << ‘ ‘;

cout << endl;            

}

На экране получим:

0

0 1

0 1 2

0 1 2 3

0 1 2 3 4

0 1 2 3 4 5

 

Дополнительные замечания по работе с массивами экземпляров объектов классов приводятся в параграфе 13.5.

Встроенные члены функции

В рассмотренном выше примере класса реализация всех функций членов класса осуществлялась после описания самого класса (раздельная, внешняя реализация функций членов). Реализацию некоторых функций членов можно включать непосредственно внутрь описания самого класса. Например, тот же самый класс можно описать так:

 

//  Описание класса

 

class t_DinArr {

// Закрытые члены-данные класса

t_ Inf * Arr; // Указатель на динамический массив

unsigned ItemCount; // Количество элементов в массиве

public:

// Открытые члены-функции класса

// ----------------------------------------------

// Инициализация массива из N элементов

void Init (unsigned N = 0)

{

            if (N)

                       Arr = (t_Inf *) malloc (sizeof(t_Inf) * N);

            else

                       Arr = 0;

            ItemCount = N;

            Clear();

}

// ----------------------------------------------

// Очистка массива

Void Clear();

// ----------------------------------------------

// Запись значения Val в элемент с индексом Ind

void Set (unsigned Ind, t_Inf Val)

{

            if (Ind >= ItemCount)

                       cout << Err << Ind << '!\n';

            else

                       Arr [Ind] = Val;

}

// ----------------------------------------------

// Возвращает значение элемента с индексом Ind

t_Inf Get (unsigned Ind)

{

            if (Ind >= ItemCount)

            {

                       cout << Err << Ind << "!\n";

                       return DefVal;

            }

            else

                       return Arr [ Ind ];

}

// ----------------------------------------------

// Добавляет в конец массива элемент со значением Val

void Add (t_Inf Val)

{

            ++ ItemCount;

            Arr = (t_Inf *) realloc (Arr, sizeof(t_Inf) * ItemCount);

            Arr [ ItemCount - 1 ] = Val;

}

// ----------------------------------------------

// Возвращает количество элементов массива

unsigned Count () { return ItemCount; }

// ----------------------------------------------

// Удаление массива из динамической памяти

Void Free ()

{

            if (Arr)

            {

                       free(Arr);

                       Arr = 0;

                       ItemCount = 0;

            }

}

};

//  Описание класса закончено

 

// И еще одна функция член класса, реализованная отдельно

//  Очистка массива значениями DefVa

void t_DinArr:: Clear ()  l

{

for (unsigned i = 0; i < ItemCount; ++ i)

            Arr [ i] = DefVal;

}

Это определение класса t_ DinArr отличается от предыдущего тем, что реализация всех членов функций (за исключением одной – функции Clear) выполнены внутри описания класса. Только функция Clear   реализована отдельно.

Формальные отличия реализации функций внутри описания класса и вне него состоят в следующем:

1. Те функции, реализация которых находится внутри описания класса, не требуют указания принадлежности к этому классу (префикс t_ DinArr:: не нужен).

2. Функции, реализованные вне описания класса, обязательно должны содержать этот префикс.

Что лучше и почему функция Clear   реализована отдельно?

Все функции, реализованные внутри описания класса, автоматически становятся inline функциями (встроенными функциями). Компилятор при создании исполняемой программы заменяет в тексте программы каждый вызов таких функций их текстом. Это приводит к тому, что при работе программы сокращается время, затрачиваемое процессором на вызов этих функций. Кроме того, не расходуется пространство стека программы на передачу параметров. Таким образом, повышается эффективность программы.

С другой стороны, если текст встраиваемых функций достаточно большой по объему и вызовы этих функций встречаются очень часто, замена вызовов функций их текстом может привести к неоправданному росту объема самой программы, что тоже не желательно.

Поэтому в качестве встраиваемых обычно используют небольшие по объему функции.

Более того, встраиваемые функции имеют некоторые ограничения по используемым внутри них инструкциям (см. раздел по inline функциям). В частности, встраиваемые функции не могут содержать циклов. По этой причине нам пришлось функцию Clear   реализовать вне описания класса (содержит цикл).

Конструкторы и деструкторы

При создании экземпляров классов очень часто (практически всегда) требуется выполнять определенные, инициализирующие эти экземпляры, действия с членами данных. В нашем примере класса такими действиями являются выделение в динамической области необходимого для массива объема памяти и формирование соответствующих значений членов класса Arr и ItemCount.

Локальные переменные, соответствующие экземплярам класса, существуют только внутри блока, в котором они определены. При закрытии блока эти переменные уничтожаются, и доступ к соответствующим экземплярам становится невозможен. Но если, например, как в нашем случае, при создании экземпляра класса был захвачен некоторый объем динамической памяти, то перед тем как уничтожить экземпляр класса, необходимо освободить захваченную динамическую память, иначе произойдет «утечка» памяти. То есть при уничтожении экземпляров классов очень часто также необходимо выполнить определенные операции. Подобные операции мы выполняли с помощью функции Free нашего класса.

Однако, сторонний программист, использующий наш класс, может не знать, что при создании экземпляров нашего класса каждый экземпляр «захватывает» динамическую память и что эту память надо своевременно освобождать. Он может не знать об обязательности использования функции Free перед уничтожением каждого экземпляра. В результате динамическая область может быть переполнена, и программа перестанет работать.

Для упрощения использования классов при создании и уничтожении экземпляров класса рекомендуется использовать специальные конструкторы и деструкторы.

Конструкторы и деструкторы представляют собой специальные функции члены, которые вызываются автоматически (без участия программиста). При создании экземпляра вызывается конструктор, а при уничтожении – деструктор.

Конструктор представляет собой функцию, не возвращающую никаких значений, имя которой совпадает с именем класса. Конструктор может быть с параметрами или без них. Например, конструктор для нашего класса можно определить так:

 

t_ DinArr (unsigned N = 0);

 

Это прототип конструктора с параметром, определяющим количество элементов динамического массива.

Прототип конструктора помещается в раздел открытых членов класса. Его реализация может быть выполнена или раздельно или встроена в само определение класса. Реализация конструктора для нашего класса при раздельном определении выглядит так (имеет префикс t_ DinArr::):

 

t_DinArr:: t_DinArr (unsigned N = 0)

{

Init (N);

}

При встроенной реализации конструктора он будет таким (без префикса t_ DinArr::):

 

t_DinArr (unsigned N = 0) { Init (N); }

или таким:

 

t_ DinArr (unsigned N = 0)

{

Init (N);

 }

Конструктор в нашем случае просто вызывает функцию Init для инициализации экземпляра на заданное количество элементов массива.

 

Деструктор это функция, также не возвращающая значений, но и не имеющая параметров. Его имя также совпадает с именем класса, но впереди используется признак деструктора – символ ~:

 

~ t_DinArr ();

Это прототип деструктора, помещаемый в описание класса при раздельной реализации деструктора, которая выглядит так:

 

t_DinArr:: ~ t_DinArr ()

{

Free ();

}

 

Реализацию деструктора также можно поместить внутрь описания класса:

 

~ t_DinArr () { Free (); }

 

Деструктор автоматически вызывается при уничтожении экземпляра и автоматически освобождает динамическую память с помощью функции Free.

Таким образом, при использовании конструктора и деструктора наш класс будет выглядеть так:

 

class t_ DinArr {

// Закрытые члены-данные класса

t_ Inf * Arr; // Указатель на динамический массив

unsigned ItemCount; // Количество элементов в массиве

public:

// Открытые члены-функции класса

t_DinArr (unsigned N = 0) { Init (N); } // Конструктор

~ t_ DinArr () { Free (); }                     // Деструктор

……….. // Далее, как и раньше, остальные члены функции класса

};

 

Теперь в программе для создания экземпляров класса можно использовать конструктор. В зависимости от вида конструктора (имеет он параметры или нет) его можно использовать по-разному.

Пусть имеется некоторый блок программы, в котором нам необходимо использовать динамические массивы.

 

{

t_DinArr DA1 (20);                    // 1-й способ

t_DinArr DA2 = 1 0;                     // 2-й способ

t_DinArr DA3;                             // 3-й способ

t_DinArr DA4 = t_DinArr (30); // 4-й способ

…..

// Работаем с экземплярами

…..

}

 

В этом примере показаны 4 способа применения конструктора класса при создании экземпляров класса t_ DinArr.

Способ 1 – наиболее распространенный. В нашем случае конструктор имеет один параметр, его-то мы и используем при создании экземпляра DA1. В результате DA1 будет представлять собой массив из 20 элементов типа t_ Inf. Если конструктор имеет несколько параметров, то при создании экземпляра, необходимо внутри скобок перечислить через «запятую» все значения этих параметров. Например, если конструктор некоторого класса Same_ Class имеет 3 целочисленных параметра, то экземпляр такого объекта следует создавать так: Same_ Class SC (1, 2, 3);.

Способ 2 можно использовать только в случае, когда конструктор имеет 1 параметр. В классе t_ DinArr используется именно такой конструктор. В результате DA2 будет представлять собой массив из 10 элементов типа t_ Inf. Если у конструктора несколько параметров, то такой способ не применим.

Способ 3 используется, если у конструктора нет параметров, или все параметры имеют значения по умолчанию. Этот способ можно применить к классу t_ DinArr, так как его конструктор хоти и имеет один параметр, но этот параметр имеет значение по умолчанию, равное 0. В результате DA3 будет представлять собой массив из 0 элементов, то есть динамический массив пуст.

Способ 4 используется достаточно редко из-за своей избыточности в записи. В результате DA4 будет представлять собой массив из 30 элементов типа t_ Inf.

Все переменные DA1, DA2, DA3, DA4 являются локальными по отношению к блоку, в котором они созданы. При закрытии блока все они будут уничтожены. При уничтожении каждой из них будет автоматически вызван деструктор соответствующего экземпляра, который освободит динамическую память, выделенную для каждого динамического массива. Специально вызывать для этого функции Free для каждого экземпляра не надо.

Текст программы, реализующей пример использования класса t_ DinArr с использованием встроенных функций членов, конструктора и деструктора, приведен в Приложении 2.

Теперь несколько замечаний об использовании конструкторов при инициализации массивов экземпляров объектов. О работе с такими массивами мы говорили в параграфе 13.3. В этом параграфе был рассмотрен пример создания массива из 6 экземпляров класса t_ DinArr.

Статический массив из 6 экземпляров класса t_ DinArr:

t_ DinArr A [ 6 ];

В параграфе 13.3 инициализацию экземпляров в этом массиве мы делали с помощью цикла:

for (int i = 0; i < 6; ++ i)

A [ i ].Init (i + 1);      

В результате мы получили массив на 6 экземпляров класса t_ DinArr, в котором каждый экземпляр представлял собой динамический массив с количеством элементов, равным номеру экземпляра (получилась некоторая треугольная структура). Этот вариант инициализации можно использовать и при наличии конструктора класса. Однако наличие конструктора дает более простую возможность инициализации статического массива A:

t_ DinArr A [ 6 ] = { 1, 2, 3, 4, 5, 6 };

Такая инициализация возможна, если конструктор класса имеет один параметр (у нас так и есть).

Можно использовать более длинную, но более универсальную форму такой инициализации, которая сможет работать с конструкторами, имеющими любое количество параметров:

t_DinArr A [ 6 ] = { t_DinArr (1), t_DinArr (2), t_DinArr  (3),

                                 t_DinArr (4), t_DinArr (5), t_DinArr (6) };

Здесь конструкторы вызываются в явном виде и им можно передавать столько параметров, сколько в них определено.



Поделиться:


Последнее изменение этой страницы: 2021-09-26; просмотров: 57; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.116.118.198 (0.094 с.)