Технологии программирования
..pdf220
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
{