Создание бинарного дерева
Конструкторы удобно использовать для создания различных связанных структур, в том числе и бинарных деревьев. Напомним, что бинарным деревом называется система объектов, связанных по следующему принципу. В вершине иерархии находится один родительский объект, и каждый объект структуры ссылается на два объекта того же типа. В листинге 2.1 приведен пример программного кода с определением класса BinTree. Класс содержит конструктор, через который и реализуется процесс создания бинарного дерева. Конструктор вызывается с аргументом целого типа. При этом аргумент конструктора определяет «глубину» (т.е. количество уровней) бинарного дерева.
Листинг 2.1. Создание бинарного дерева
#include <iostream>
using namespace std;
class BinTree{
public:
static int Count;
int m;
BinTree *pl;
BinTree *p2;
BinTree(int n){
if(n==l){
pi=NULL; p2=NULL;}
else {
pl=new BinTree(n-1); p2=new BinTree(n-1);}
m=++Count;
cout<<"Object created: "<<this<<" : "<<m; cout«" -> Number of objects: "<<Count<<endl; }
~BinTree () {
delete p1;
delete p2;
Count--;
cout<<"Object deleted: "«this«" : "«m; cout«,” -> Number of objects: "<<Count<<endl; }
};
int BinTree::Count;
int main(){
BinTree::Count=0;
BinTree objl(3);
BinTree *p;
p=new BinTree(2);
delete p;
return 0;}
При создании и удалении объектов выполняется их автоматический подсчет. Реализуется это через статическое целочисленное поле Count класса BinTree. Кроме этого, в классе объявляется целочисленное поле m для нумерации объектов. Класс также содержит в качестве полей два указателя pi и р2 на объекты класса BinTree. Поля используются для связи объекта с двумя другими объектами того же класса. Через эти поля-указатели реализуется вся структура бинарного дерева. Вся функциональность класса BinTree реализуется через конструктор и деструктор.
Конструктору передается целочисленный аргумент. Если аргумент равен единице, полям pi и р2 создаваемого объекта присваиваются значения NULL (нулевые указатели). В противном случае динамически создается два объекта класса BinTree, при этом аргумент для конструктора создаваемых объектов уменьшается на единицу.
При создании нового объекта класса BinTree значение статической переменной Count увеличивается на единицу, и это значение присваивается полю т. Соответствующая команда в конструкторе имеет вид m=++Count. Также создание объекта сопровождается выводом соответствующего сообщения с указанием адреса выделенной под объект памяти, значения поля m и общего количества созданных объектов.
В деструкторе освобождается место памяти, выделенное для объектов, на которые указывают поля p1 и р2 удаляемого объекта, уменьшается на единицу значение статического поля Count, а также выводится сообщение с указанием адреса удаляемого объекта, значения его поля ш, равно как и количество объектов, оставшихся в работе.
В главном методе программы статическое поле Count инициализируется с начальным нулевым значением, а также создается два объекта (точнее, два бинарных дерева) класса BinTree - один статически, один динамически. Результат выполнения программы может выглядеть так, как показано ниже:
Object created: 003210С8 : 1 -> Number of objects: 1
Object created: 00321180 : 2 -> Number of objects: 2
Object created: 00321090 : 3 -> Number of objects: 3
Object created: 003211F0 : 4 -> Number of objects: 4
Object created: 00322850 : 5 -> Number of objects: 5
Object created: 003211B8 : 6 -> Number of objects: 6
Object created: 0012FF68 : 7 -> Number of objects: 7
Object created: 003228C0 : 8 -> Number of objects: 8
Object created: 003228F8 : 9 -> Number of objects: 9
Object created: 00322888 : 10 -> Number of objects: 10
Object deleted: 003228C0 : 8 -> Number of objects: 9
Object deleted: 003228F8 : 9 -> Number of objects: 8
Object deleted: 00322888 : 10 -> Number of objects: 7
Object deleted: 003210C8 : 1 -> Number of objects: 6
Object deleted: 00321180 : 2 -> Number of objects: 5
Object deleted: 00321090 : 3 -> Number of objects: 4
Object deleted: 003211F0 : 4 -> Number of objects: 3
Object deleted: 00322850 : 5 -> Number of objects: 2
Object deleted: 003211B8 : 6 -> Number of objects: 1
Object deleted: 0012FF68 : 7 -> Number of objects: 0
Сначала создаются десять объектов (семь в результате выполнения команды BinTree obj 1 (3) - дерево из трех уровней содержит 23—1=7 элементов, и три в результате динамического создания объекта командой p=new BinTree (2) - создается 22—1 = 3 объектов). Затем динамический объект удаляется, в результате чего удаляются все три объекта (с номерами 10, 9 и 8), входящие в соответствующее бинарное дерево. Статический объект со всей связанной с ним структурой (объекты со значениями поля m от 1 до 7) удаляется при завершении работы программы автоматически.
Обращаем внимание, что если создается несколько деревьев, нумерация в них используется общая. Например, первый элемент второго дерева в рассмотренном примере получает номер 8.
Ряд для экспоненты
В следующем примере реализуется вычисление экспоненты, правда, несколько необычным образом. Для этих целей создается класс, у которого имеется поле - динамический массив. Элементами массива являются слагаемые ряда, через который вычисляется экспонента. Массив заполняется при создании соответствующего объекта. Аргументами конструктору передаются верхний индекс ряда int п и аргумент экспоненты double х. Программный код приведен в листинге 2.2.
Листинг 2.2. Ряд для экспоненты
#include <iostream>
using namespace std;
class MyExp{
public:
int n;
double *p;
MyExp(xnt i,double x){
n=i;
p=new double[n+1];
P[0]=1;
for(int k=l;k<=n;k++) p[k]=p[k-1]*x/k;
}
~MyExp(){
delete [] p;}
} ;
int main(){
int n,i;
double x,s=0;
cout«"enter n= “;
cin»n;
cout«"enter x= ";
cin>>x;
MyExp obj(n,x);
for(1=0;i<=n;i++) s+=obj.p[i];
cout<<"exp ( "«x«") = "«s«endl;
return 0;}
Результат выполнения программы может иметь следующий вид (жирным шрифтом выделен ввод пользователя):
enter n= 100
enter х= 1
exp (1)= 2.71828
Пользователем вводятся значения для верхней границы ряда и аргумент экспоненты (переменные int пи double х в главном методе программы). Эти аргументы передаются конструктору при создании объекта командой MyExp obj (n, х). Конструктором первый аргумент присваивается в качестве значения полю п объекта obj. Это же значение используется при выделении памяти под динамический массив, указатель на первый элемент которого присваивается в качестве значения полю р объекта obj. После выделения места под массив выполняется заполнение элементов массива. В качестве значения элементу с нулевым индексом присваивается единица, а прочие элементы заполняются на основе предыдущего значения: предыдущий элемент умножается на х и делится на индексную переменную к.
В деструкторе выполняется очистка памяти, выделенной под динамический массив.
Поле-объект
Полями класса могут быть не только переменные базовых типов, но и объекты. Подобная ситуация иллюстрируется в следующем примере. В листинге 2.3 представлен программный код, в котором определяется класс с конструктором и деструктором. Впоследствии объект этого класса используется в качестве поля другого класса, также имеющего конструктор и деструктор. Пример иллюстрирует способы вызова конструкторов и деструкторов в такой нетривиальной ситуации.
Листинг 2.3. Поле-объект
#include <iostream>
using namespace std;
//"Внутренний" класс:
class Inner{
public:
int n;
//Конструктор:
Inner(){
n=0;
cout«"Inner-object created with n="«n«endl; }
//Деструктор:
~Inner () {
cout«"Inner-object destroyed with n="<<n<<endl; }
} ;
//"Внешний" класс:
class Outer{
public:
int m;
//Поле-объект:
Inner obj;
//Перегруженный конструктор:
Outer (int i) {
m=i ;
cout«"Outer-object created with m="«m<<endl; }
Outer(int i,int j){
m=i;
obj.n=j;
cout«"Outer-object created with m="«m«endl; }
//Деструктор:
~Outer(){
cout<<"Outer-object destroyed with m="<<m«endl; }
};
int main(){
int i=l0,j=20,k=30;
Outer a(i);
Outer b(j,k);
cout«"a.m: "«a.m<<" a.obj.n: "«a . ob j . n<<endl;
cout«"b.m: "<<b.m<<" b.obj.n: "«b . ob j . n<<endl;
return 0;}
В программе объявляется класс Inner, имеющий целочисленное поле n, конструктор (без аргументов) и деструктор. При создании и уничтожении объекта выводится соответствующее сообщение с указанием значения поля п создаваемого или уничтожаемого объекта.
В классе Outer описано целочисленное поле т, а также поле obj, являющееся объектом класса Inner. У класса Outer также есть перегруженный конструктор (с одним аргументом и с двумя), равно как и деструктор.
В главном методе программы создается два объекта класса Outer. Объект а создается посредством конструктора с одним аргументом, а объект b - с помощью конструктора с двумя аргументами. Значения полей объектов выводятся на экран. Результат выполнения программы имеет вид:
Inner-object created with n=0
Outer-object created with m=10
Inner-object created with n=0
Outer-object created with m=20
a.m: 10 a.obj.n: 0
b.m: 20 b.obj.n: 30
Outer-object destroyed with m=20
Inner-object destroyed with n=30
Outer-object destroyed with m=10
Inner-object destroyed with n=0
Обращаем внимание, что при создании объекта класса Outer, кроме сообщения о создании этого объекта, предварительно выводится сообщение о создании объекта Inner - это создается поле obj — объект класса Inner, причем создается он с помощью конструктора «по умолчанию» (конструктор без аргументов) класса Inner, поэтому поле п этого объекта получает нулевое значение. Кроме того, специфично выглядит обращение к полю п объекта obj, являющегося полем объекта класса Outer: например, а.obj.n или b.obj.n.
Поле-массив объектов
Несколько белее сложная ситуация имеет место, когда полем класса является массив объектов другого класса. Такой случай рассматривается в программном коде листинга 2.4.
Листинг 2.4. Поле-массив объектов
#include <iostream>
using namespace std;
//Размер поля-массива:
const int n=10;
//"Внутренний" класс:
class Inner{
public:
int m;
int number;
//Метод для отображения полей:
void show () {
if(m!=l) (this-1)->show();
cout«number«” “;}
};
//"Внешний" класс:
class Outer{
public:
//Поле-массив объектов:
Inner obj{n];
//Конструктор:
Outer (){
int i;
obj[0].m=l; obj[0].number=l;
obj [1] . m=2; obj[1].number=l;
for (i=2;i<n;i++) {
obj[i].m=i+l;
obj [i] .number=obj[i — 1] .number+obj[i-2] .number;}
}
} ;
int main(){
Outer a;
a.obj[n/2].show();
cout«endl;
a.obj[n-1].show();
cout«endl ;
return 0;}
В программе реализуется последовательность чисел Фибоначчи - правда, довольно экзотическим способом. В программе описывается класс Inner, у которого два целочисленных поля m и number. Последовательность чисел Фибоначчи реализуется посредством объектов класса Inner - в полет заносится порядковый номер числа, в поле number записывается непосредственно число, а сами объекты организуются в массив, который является полем класса Outer.
В классе Outer объявлен в качестве поля массив объектов класса Inner. Размер массива ранее определен в виде целочисленной константы. В конструкторе класса Outer выполняется заполнение полей объектов класса
Inner - элементов массива obj. Поля ш формируют последовательность натуральных чисел, а поля number заполняются так: для первых двух полей значение равно 1, а поле каждого следующего объекта равно сумме полей двух предыдущих объектов.
Но самое ужасное происходит в методе show () класса Inner. Методом выводится значение поля number объекта, но предварительно, если поле m объекта, из которого вызывается метод, не равняется единице, вызывается метод show () «предыдущего» объекта. Для определения «предыдущего» объекта используется адресная арифметика в виде инструкции this-1. Эта ссылка означает объект, размещенный в предыдущей ячейке по отношению к текущему объекту. Такая команда имеет корректный смысл, только если речь идет о массиве объектов - в противном случае результат будет весьма трагическим. Таким образом, получаем своеобразную рекурсию, а действие метода show () сводится к тому, что на экран выводятся значения полей number объектов массива, от начального до текущего, из которого вызывается метод show ().
Результат выполнения программы имеет вид:
1 1 2 3 5 8
1 1 2 3 5 8 13 21 34 55
В первой строке командой a.obj [n/2].show() выводятся 6 чисел последовательности Фибоначчи (в массиве 10 элементов, метод вызывается из объекта с индексом 5, что соответствует 6-ти числам в последовательности). Второй командой а.obj[ n-1 ].show() выводятся значения из всего массива.