Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Технологии программирования

..pdf
Скачиваний:
11
Добавлен:
05.02.2023
Размер:
2.88 Mб
Скачать

220

int& operator[](int i) { if(i<0||i>=size) i=0; return vec[i]; } ~Vector() { delete []vec; }

void Show() { cout<<"vec["<<size<<"] "; for(int i=0; i<size; i++) cout<<vec[i]<<" "; cout<<"\n"; }

};

Внимательно посмотрите объявление перегрузки

int& operator[](int i) { ... return vec[i]; }

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

Vector x(20);

x[5]=20; //пятому элементу вектора x присвоить значение 20 j=x[10]; //переменной j присвоить значение 10 элемента вектора x

Пример №2

В этом примере показано возможность определения классов с множеством границ, таких как матрицы и тензоры и пр., и соответственно необходимо использование нескольких квадратных скобок для указания элемента. Рассмотрим класс целых матриц.

class Matrix //класс целых матриц определен как массив векторов

{

Vector **matrix; //здесь будет храниться матрица int m,n; //размеры матрицы

public:

Matrix(int m,int n) //конструктор

{

Matrix::m=m; Matrix::n=n;

matrix=new Vector*[m]; //создать вектор указателей типа Vector

for(int i=0; i<m; i++) matrix[i]=new Vector(n); //создать вектор для каждого элемента

}

int M() { return m; } //определить размер строки int N() { return n; } //определить размер столбца

Vector& operator[](int i) { return *matrix[i]; } //вернуть i-й вектор ~Matrix() {

for(int i=0; i<m; i++) delete matrix[i]; //удалить вектора delete []matrix; //удалить вектор указателей

221

}

void Show() //вывести значения матрицы

{

cout<<"---------Matrix["<<m<<"]["<<n<<"]-------------\n"; for(int i=0; i<m; i++) matrix[i]->Show();

cout<<"-----end matrix -----\n";

}

};

//Здесь определен тензор, как вектор матриц class Tensor

{

Matrix **tensor; //указатель на массив указателей int l,m,n; //размеры тензора

public:

Tensor(int l,int m,int n) //конструктор

{

Tensor::m=m; Tensor::n=n; Tensor::l=l;

tensor=new Matrix*[l]; //создать вектор указателей на матрицы

for(int i=0; i<l; i++) tensor[i]=new Matrix(m,n); //для каждого элемента массива указателей создать матрицу

}

int L() { return l; } //вернуть количество матриц int M() { return m; } //вернуть размер строки int N() { return n; } //вернуть размер столбца

Matrix& operator[](int i) { return *tensor[i]; } //вернуть ссылку на i-ю мат-

рицу

~Tensor() { //деструктор

for(int i=0; i<l; i++) delete tensor[i]; //удалить матрицы delete []tensor; //удалить массив указателей

}

void Show() //вывести тензор

{

cout<<"---------Tensor["<<l<<"]["<<m<<"]["<<n<<"]-----------\n"; for(int i=0; i<l; i++)

{

tensor[i]->Show();

}

cout<<"-----end tensor ------\n";

}

};

//Примеры использования матриц и тензоров void main()

{

222

Matrix x(2,2); //создать матрицу (2,2) int k=0;

for(int i=0; i<x.M(); i++) for(int j=0; j<x.N(); j++)

{

x[i][j]=k++; //присвоить значения элементам матрицы

}

x.Show(); //показать матрицу

Tensor z(2,3,4); //создать тензор 2 матрицы размером (3,4) int h=0;

for(int k=0; k<z.L(); k++) for(int i=0; i<z.M(); i++) for(int j=0; j<z.N(); j++)

{

z[k][i][j]=h++; //присвоить значения элементам тензора

}

z.Show(); //вывести значения тензора

}

Листинг

Рассмотрим внимательнее выражения x[i][j] и z[k][i][j]. В первом случае, для объекта x будет вызвана функция x.operator[](i), которая вернет ссылку на объект типа Vector, далее будет вызвана функция operator[](j) для этого объекта, тогда сделав, подстановки для первого выражения, получим:

(x.Matrix::operator[](i)).Vector::operator[](j)

Аналогично для выражения z[k][i][j] получим

((z.Tensor::operator[](k)).Matrix::operator[](i)).Vector::operator[](j)

Последовательность вызова:

Tensor::operator[](k) — возврат ссылки на объект типа Matrix Matrix::operator[](i) — возврат ссылки на объект типа Vector Vector::operator[](j) — возврат ссылки на объект типа int

В общем случае может быть в классе может быть определено несколько функций перегружающих операцию []. Например, можно переопределить функцию поиска некоторого имени в таблице (Tabl[Name])

223

При этом семантику каждой перегрузки определяет сам программист. В общем случае можно записать:

Type1 operator[](Index1 i) { ... } Type2 operator[](Index2 i) { ... } Type3 operator[](Index3 i) { ... }

...

где Type1, Type2, Type3, Index1, Index2, Index3 — стандартные типы или типы определенные самим программистом.

Перегрузка операции ()

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

operator()(<список параметров>) { /*тело функции */ }

Рассмотрим пример

#include <iostream.h> class Z

{

int z; public:

Z() { z=0; } Z(int i){z=i;}

void operator()(){ cout<<"x("<<x<<")\n"; }

Z& operator()(int x,int y){ cout<<"x("<<x<<","<<y<<")\n"; return *this; } Z& operator()(int n,...) { cout<<"x("<<n<<",..."<<")\n"; return *this; }

int operator()(char *s) { return strlen(s); } };

//Использование void main()

{

Z x;

x(); //вызов функции x.operator()()

x(6,10); //вызов функции x.operator()(int,int) x(10,40,200,300,400); //вызов функции x.operator()(int,...) x("Привет"); //вызов функции x.operaor()(char*)

}

224

Перегрузка операций [], () удобна, когда класс имеет несколько членов объектов типа вектор или список.

4.1.17 Импорт объектов

При описании членов классов можно использовать ссылки. Однако, в отличии от указателей, ссылки необходимо проинициализировать в конструкторах. Инициализацию можно осуществить двумя путями. Создать объект с помощью оператора new или передать адрес объекта через параметр конструктора. Например,

class V

{

int key; //ключ определяющий объект создан или импортирован int &c; //ссылка на целое

public:

V(int &ref):c(ref){ key=0;} //импорт объекта через ссылку V(int *ptr):c(*ptr){key=0;} //импорт объекта через указатель

V():c(*new int) {key=1;} //создание объекта

~V() { if(key) delete &c; } //удаление созданного объекта через ссылку

};

Тогда можно записать:

static int x; automated int y;

int *ptr=new int(20);

V x1(y), y1(x), z1(ptr), w();

Рассмотрим более сложный пример. В этом примере приведены следующие классы:

X, T — базовые классы с виртуальной функцией Show(); Z — класс импортирующий объект типа X; Y — наследующий классы X и T.

#include <iostream.h> class X //базовый класс

{

int x; public:

X() { x=0; } X(int v) { x=v; }

virtual void Show() { cout<<"X::Show()\n"; } };

225

class T //базовый класс

{

double t; public:

T() { t=0.; } T(double v) { t=v; }

virtual void Show() { cout<<"T::Show()\n"; } };

class Z

{

X &x; //ссылка на объект типа X int Stat; //статус обекта

enum {CreateObject //объект создан в конструкторе ,ImportObject //объект импортирован

};

public:

Z():x(*new X()){ Stat=CreateObject; } //конструктор создания объекта

Z(X &v):x(v){ Stat=ImportObject; }

//импорт объекта через ссылку

Z(X *p):x(*p){ Stat=ImportObject;}

//импорт объекта через указатель

Z(Z &o):x(o.x){ Stat=ImportObject; } //импорт через конструктор копиро-

вания

~Z() { if(Stat==CreateObject) delete &x; } //деструктор в void Show() { x.Show(); }

virtual void New() { cout<<"Новая виртуальная функция\n"; } };

//создает класс с множественным наследованием class Y: public X, public T

{

public:

Y(int i):X(i),T((double)i){}

void Show() { cout<<"Y::Show()\n"; } };

void Func(T &t) { t.Show(); } //Функция принимающая ссылку типа T //Использование

void main()

{

Xx(20); //создаем объект типа X

Yy(30); //создаем объект типа Y

Func(y); //передаем объект типа Y, функция Show() виртуальна (см. описание класса T)

//будет выведено Y::Show()

226

x.Show(); //здесь будет выведено X::Show() y.Show(); //здесь будет выведено Y::Show() Z z1(x), //импортируем в z1 объект x;

z2(y); //импортируем в z2 объект y z1.Show(); // здесь будет выведено X::Show() z2.Show(); // здесь будет выведено Y::Show() z1.New();

}

4.1.18 Идентификация объектов во время выполнения (RTTI)

Для определения типа объекта во время выполнения программы в С++ предусмотрен специальный механизм, обеспечивающий определение типа RTTI (Run Time Type Identification). Кроме этого тип можно опреде-

лить и для указателей ссылок.

Это делает возможным преобразовать указатель виртуального базового класса в указатель действительного наследуемого класса , который имеет сам объект. Это делается с помощью оператора dynamic_cast.

RTTI механизм также позволяет проверять тип объекта или сравнивать типы двух объектов. Это можно сделать, используя оператор typeid, который проверяет тип объекта записанного в качестве аргумента, а возвращает ссылку на объект типа const type_info, который описывает тип объекта-аргумента.

Можно также определить имя типа, отношение предшествования . В классе type_info имеется два члена функции name() и before(), операции:

==(проверка на равно), != (не равно).

Для включения механизма RTTI необходимо включить специальную опцию компилятора, или для описания класса записать ключевое слово __rtti. В исходный текст программы необходимо включить заголовочный файл typeinfo.h. Рассмотрим примеры

#include <iostream.h>

#include <typeinfo.h> //вставляем описание класса type_info class __rtti X //включение механизма RTTI

{

protected: int a; public:

X(int b):a(b){}

virtual void Show() { cout<<a<<"\n"; } };

class __rtti Y: public X //наследуем класс X

227

{

public:

Y(int b):X(b){}

void Show() { cout<<"++"<<a<<"\n"; }

int Power( ) { return a*a; ) //не виртуальная функция };

//использование механизма RTTI void main()

{

Xx(10);

Yy(20);

X*ptr=&y;

Y*yptr;

cout<<typeid(x).name()<<"\n"; //отпечатать тип объекта x cout<<typeid(ptr).name()<<"\n";//отпечатать тип объекта ptr if(typeid(x).before(typeid(y))) cout<<"x < y\n"; //класс X описан раньше чем

класс Y

yptr=dynamic_cast<Y*>(ptr); //проверить и преобразовать указатель к типу

Y

if(yptr!=NULL) { cout<<yptr->Power()<<"\n"; } //использование указателя x.Show();

}

Внимание, механизм RTTI нельзя применять для указателей типа void. Например,

Y y(10);

void *ptr=&y;

Y *yptr= dynamic_cast<Y*>(ptr); //здесь преобразования не будет

Использование механизма RTTI будет показано при описании контейнеров.

Механизм подобный RTTI можно создать самим, это можно следующим образом:

#include <iostream.h>

enum typeob { tO,tX,tY,tZ}; //записываем типы всех классов class TypeObject //создаем базовый класс

{

public:

void* Type(typeob t) { if(t==type) return this; else return NULL; } TypeObject() { type=tO; } //установка типа tO объекта protected:

228

typeob type; //переменная содержащая тип объекта

};

class X: public TypeObject //первый производный класс

{

public:

X() { type=tX; } //установка типа в конструкторе //... другие члены

};

class Y: public TypeObject //второй производный класс от TypeObject

{

public:

Y() { type=tY; } //установка типа объекта в конструкторе //... другие члены

};

class Z: public X //производный класс от класса X

{

public:

Z() { type=tZ; }

//... другие члены

};

void main()

{

TypeObject *ptr[3]; X *xptr;

Z *zptr;

ptr[0]=new X(); //заполняем массив указателей, адресами конкретных объектов

ptr[1]=new Y(); ptr[2]=new Z();

if(xptr=ptr[0]->Type(tX)) cout<<"Это X\n"; //здесь будет напечатано "Это X"

zptr=(Z *)ptr[0]->Type(tZ); //здесь zptr будет присвоено значение NULL zptr=(Z *)ptr[2]->Type(tZ); //здесь zptr будет присвоено значение адреса объекта

}

Для реализации отношения предшествования (before) строится немного более сложный механизм. В каждом классе определяется функция before(int type), в которой вызываются before() базовых классов. Например,

class Obj { protected:

229

typeob type; public:

int before(typeob t) { return 0; }

int AreYou(typeob t) { return (t==tO)?1:0; } //возвращает истина если t это "Obj"

//..другие члены

};

class X: public Obj { public:

int before(typeob t) { return Obj:: AreYou (t); }

int AreYou (typeob t) { return (t==tX)?1:0; } //возвращает истина если t это "X"

//другие члены

};

class Z: public X { public:

int before(typeob t) { return X:: AreYou (t); }

int AreYou (typeob t) { return (t==tZ)?1:0; } //возвращает истина если t это "Z"

//другие члены

};

class V: public X, public Y { public:

int before(typeob t) { if(X:: AreYou (t)||Y: AreYou (t) return 1; else return 0; } int AreYou (typeob t) { return (t==tV)?1:0; } //возвращает истина если t это "V"

//другие члены1

};

4.1.19 Контейнеры

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

class X { ..... };

class Y { X x; ....} //это контейнер для хранения объектов класса X Конечно, в таком случае лучше использовать механизм наследования class Y: public X { .... };

Однако в случае с вектором, лучше использовать контейнер

class VecX

{