Объектные типы данных - это агрегатные типы, полностью определяемые программистом, описание объектного типа должно содержать компоненты-данные, определяющие область возможных значений переменных этого типа, и описание операций, допустимых над переменными этого типа и компонентами-данными, составляющими переменную. Для сохранения совместимости с программами на Си синтаксис описания объектного типа в Си++ выбран подобным описанию структурного типа или типа объединения в Си. В сущности структуры и объединения в Си++ рассматриваются как варианты объектных типов. Имеются три варианта объектных типов: структура (struct), объединение (union) и класс (class), различающиеся возможностями доступа к компонентам типа. В дальнейшем для краткости все варианты объектных типов будем называть классами. Описание объектного типа строится по схеме:
вариант_типа имя_типа : список_базовых_классов
{ компоненты (члены ) класса }
Компонентами класса могут быть компоненты-данные и компоненты-функции. Компоненты-функции предназначены для выполнения операций над объектным данным, их часто называют методами класса.
Для каждого компонента класса устанавливается уровень доступа либо явно, указанием уровня доступа одним из ключевых слов public, protected или private с двоеточием, либо неявно, по умолчанию. Указание уровня доступа относится ко всем последующим компонентам класса, пока не встретится указание другого уровня доступа. Уровень доступа public разрешает доступ к компонентам класса из любого места программы, в котором известна переменная этого класса. Уровень доступа private разрешает доступ к компонентам класса только из методов этого класса. Уровень доступа protected имеет смысл только в иерархической системе классов и разрешает доступ к компонентам этого уровня из методов производного класса. По умолчанию для всех компонент класса типа struct принимается уровень доступа public, но можно явно задавать и другие уровни доступа, уровень доступа к компонентам класса типа class по умолчанию private, явно можно определять и другие уровни, для класса типа union уровень доступа public и не может быть изменен.
Например, пусть программист решил в классе TPoint (точка) запретить внешний доступ к координатам точки и разрешить внешний доступ к методам перемещения точки на плоскости. Описание класса TPoint можно построить так:
class TPoint
{ private:
int x,y;
public:
void movePoint ( int newx, int newy); // в новую точку
void relmove ( int dx, int dy ); // смещение на dx,dy
int getx ( void ) ( return x ; };
int gety ( void ) { return y ; };
};
Описание тела компоненты-функции может быть включено в описание класса, как это сделано в примере для функций getx и gety, или помещено вне описания класса. Компоненты-функции при их вызове неявно получают дополнительный аргумент - указатель на переменную объектного типа, для которой вызвана функция и в теле функции можно обращаться ко всем компонентам класса. В связи с этим при описании тела компоненты-функции вне описания класса нужно использовать операцию разрешения контекста, чтобы информировать компилятор о принаждлежности функции к классу. Методы класса TPoint можно описать так:
void TPoint : : movePoint ( int newx, int newy )
{ x = newx; y = newy ; }
void TPoint : : relmove ( int dx, int dy )
{ x += dx; y += dy ; }
Чтобы выполнить начальную инициализацию компонент-данных при создании переменных объектного типа в описание типа включаются специальные методы-конструкторы. Имя конструктора совпадает с именем типа, конструктор не возвращает никакого значения и для него не указывается тип возвращаемого значения. Для рассмотренного выше класса TPoint можно было обойтись без конструктора и использовать для инициализации метод movePoint. Рассмотрим в качестве примера класс TRect, описывающий прямоугольник со сторонами, параллельными осям координат:
enum Boolean {FALSE, TRUE };
class TRect
{ public:
TPoint a,b; // a - левый верхний угол, b - правый нижний угол
void move( int dx, int dy) // перемещение прямоугольника
{ a.relmove ( dx, dy ); b.relmove ( dx, dy );}
void grow( int dx, int dy) // изменение размеров
{ a.x +=dx; a.y += dy; b.x +=dx; b.y += dy; }
void intersect (const TRect& r); // общая часть двух прямоугольников
void Union ( const TRect& r); /* прямоугольник, охватывающий два прямоугольника */
Boolean contains ( const TPoint& p);
/* TRUE, если точка p принадлежит прямоугольнику */
Boolean isEmpty( );
/* TRUE, если ширина или высота прямоугольника равны нулю */
TRect (int ax, int ay, int bx, int by ) // конструктор
{ a,x - ax; a,y = ay; b.x = bx; b.y = by; };
TRect ( TPoint p1, TPoint p2) // конструктор
{ a = p1; b = p2; };
TRect () // конструктор
{ a.x = a.y = b.x = b.y = 0; };
};
/* Методы класса TRect */
void TRect : : intersect (const TRect& r)
{ a.x = max (a.x, r.a.x ); b.x = min ( b.x, r.b.x );
a.y = max (a.y, r.a.y ); b.y = min ( b.y, r.b.y );
};
void TRect : : Union ( const TRect & r )
{ a.x = ( a.x <= r.a.x ) ? a.x : r.a.x ;
a.y = ( a.y <= r.a.y ) ? a.y : r.a.y ;
b.x = ( b.x >= r.b.x ) ? b.x : r.b.x ;
b.y = ( b.y >= r.b.y ) ? b.y : r.b.y ;
};
Boolean TRect : : contains ( const TPoint & p )
{ return Boolean (p.x >= p.x && p.x < b.x && p.y >= a.y && p.y < b.y); };
Boolean TRect : : isEmpty ( )
{ return Boolean ( a.x >= b.x | | a.y >= b.y ); };
Более полная информация о конструкторах объектных типов приведена в следующем разделе.
Объявление переменной объектного типа строится по общим правилам, но за идентификатором переменной можно указать в скобках аргументы определенного в классе конструктора, например:
TRect r1(2,4,20,50); // инициализация с использованием первого конструктора
TRect *pr = &r1; // укзатель на TRect
TRect r2, *ptr; // для r2 используется конструктор без параметров
В операции new для размещения в динамической памяти объектной переменной за именем типа также указываются аргументы конструктора этого типа:
ptr = new TRect( 7,3,18,40);
Для обращения к компонентам объектного типа имя компоненты должно уточняться именем объектной переменной или указателем на нее:
r1.grow( 2, -3);
pr->move( 1, 1);
Boolean bb= r1.isEmpty( ); 4.2. Конструкторы и деструкторы
Описание класса обычно содержит специальные методы, вызываемые при создании переменной этого класса и удалении переменной из динамической памяти - конструкторы и деструкторы. Конструктор вызывается после выделения памяти для переменной и обеспечивает инициализацию компонент-данных, деструктор вызывается перед освобождением памяти, занимаемой объектной переменной, и предназначен для выполнения дополнительных действий, связанных с уничтожением объектной переменной, например, для освобождения памяти, выделенной для объекта вне участка, отведенного для компонент-данных.
Как уже отмечалось, конструктор всегда имеет имя, совпадающее с именем класса, для него не указывается тип возвращаемого значения и он не возвращает никакого значения. Конструктор должен обеспечивать инициализацию всех компонент-данных. Для класса может быть объявлено несколько конструкторов, различающихся числом и типами параметров. В общем случае различают следующие виды конструкторов: конструктор с параметрами, конструктор без параметров и конструктор копирования с одним параметром - ссылкой на переменную того же объектного типа. Если для объектного типа не определено ни одного конструктора, компилятор создает для него конструктор по умолчанию, не использующий параметров. Конструктор копирования необходим, если переменная объектного типа передается в какую-нибудь функцию как аргумент, поскольку все аргументы передаются в функцию по значению.
Деструктор необходим, если объектный тип содержит компоненту-данное, являющуюся указателем на динамическое данное, которое должно уничтожаться при уничтожении объектной переменной. Деструктор всегда имеет то же имя, что и имя класса, но перед именем записывается знак ~ (тильда). Деструктор не имеет параметров и подобно конструктору не возвращает никакого значения.
В качестве примера рассмотрим объектный тип TString для представления строковых данных с более высокой степенью защиты от ошибок, чем это обеспечено стандартными функциями обработки строк из файла-заголовка string.h.
#include <iostream.h>
#include <string.h>
class TString
{ public:
TString(); // конструктор без параметров
TString(int n, char* s=0); // конструктор, создающий пустую строку
/* конструктор, преобразующий массив из char с завершающим нулем
в тип TString */
TString(char* s);
TString(TString& st); // конструктор копирования
~TString(); // деструктор
void print(); // вывод строки на экран
int sz; // длина строки
char* ps; // указатель на память для хранения строки
};
/* Методы класса TString */
TString::TString( ){sz=0; ps=0;}
TString::TString(int n, char* s)
{ sz=n; ps=new char[n+1];
strncpy(ps,s,n); ps[sz]='\0';
}
TString::TString(char* s)
{ sz=strlen(s)+1; ps=new char[sz];
strcpy(ps,s);
}
TString::TString(TString& str)
{ sz = str.sz; ps=new char[sz+1];
strcpy(ps,str.ps);
}
TString::~TString( )
{ if (ps != 0) delete [] ps; }
void TString::print( )
{ if (sz == 0 )
{ cout << " Строка пустая "<< endl; return;}
cout<<" Строка = "<< ps <<endl;
}
Ниже приведен пример программы, иллюстрирующей использование данных типа TString.
int main()
{ char rabstr [60] = "yes";
while (*rabstr !='n')
{ cin >> rabstr;
if (*rabstr == 'n')break;
TString s1();
TString s2(6);
TString s3(6, rabstr);
TString* ps1=new TString(" Это строка по указателю");
cout <<" s1="; ps1->print();
cout <<" s2="; s2.print();
cout <<" s3="; s3.print();
}
return 0;
}
Описание конструктора можно упростить, если компоненты-данные принадлежат к базовым типам или являются объектными переменными, имеющими конструктор. При описании конструктора после заголовка функции можно поставить двоеточие и за ним список инициализаторов вида идентификатор (аргументы ). Например, для класса TPoint из предыдущего параграфа можно было определить конструктор так:
class TPoint
( .....
public:
TPoint ( int x0, int y0 ) : x (x0), y (y0){ };
}
В этом конструкторе все компоненты получают значения из списка инициализации, а тело конструктора представлено пустым составным оператором.
Классы образуют иерархическую структуру, когда выделяется некоторый базовый класс, содержащий общие данные и методы группы сходных классов, и строится несколько производных классов, в которых к данным и методам базового класса добавляются данные и методы, необходимые для реализации производного класса. Описание системы классов в этом случае выглядит так:
class TA // базовый класс
{ Переменные и методы TA }
class TAA : public TA // класс, производный от класса TA
{ Переменные и методы TAA }
class TAAB : public TAA // класс, производный от класса TAAB
{ Переменные и методы TAAB }
Доступом к компонентам базового класса управляют ключевые слова public и private. Если базовый класс public, то в производном классе public-компоненты базового класса останутся public, protected-компоненты базового класса останутся protected, private-компоненты базового класса для функций производного класса будут недоступны.
Если базовый класс private, то в производном классе public и protected компоненты базового класса доступны для функций производного класса, но для следующего производного класса они будут считаться private, т.е. будут недоступны, private-компоненты базового класса недоступны в производных классах.
Конструктор производного класса должен вызывать конструктор своего базового класса:
class TBase
{ public: TBase( int s, int m, int d);
/* Другие компоненты класса TBase */
}
class TVect : public TBase
{ public: TVect ( int k, int s, int m int d): TBase(s, m, d)
{ /* инициализация остальных компонент TVect */};
} 4.4. Пример построения системы классов
Известно, что при объявлении массивов в Си/Си++ количество элементов массива задается константой и в дальнейшем не может быть изменено. При обращении к элементам массив отсутствует контроль выхода за пределы индексов массива, что приводит к трудно обнаруживамым ошибкам в программах. Построим систему классов для обработки динамических массивов, в которые можно добавлять новые элементы и исключить возможность выхода за пределы текущего размера массива. Общие свойства массивов с такими сойствами, не зависящие от типа элементов массива, объединим в классе TBase, а для массивов с различными типами элеменов образуем свои классы. Описания классов объединим в файле заголовков TBASEARR.H, а определения методов приведем в файле TBASEARR.CPP.
// файл TBASEARR.H
#include <string.h>
#include <iostream.h>
class TBase //базовый класс для массивов всех типов
{int size, //размер элемента
count, //текущее число элементов
maxCount, //размер выделенной памяти в байтах
delta; //приращение памяти в байтах
char *pmem; //указатель на выделенную память
int changeSize(); //перераспределение памяти
protected:
void* getAddr( ){return (void*) pmem;};
void addNewItem(void*); //добавление в конец массива
void error(const char* msg){cout <<msg<<endl;};
public:
int getCount() {return count;};
TBase(int s,int m,int d);
TBase();
TBase(TBase&);
~TBase();
};
/* Массив с элементами типа int */
class TIntArray: public TBase
{ public:
int getElem(int index); // Значение элемента по индексу
void putElem(int index,int &pe); // Замена значения элемента по индексу
void addElem(int& el); // Добавление элемента в конец массива
TIntArray& add(TIntArray&); // Сложение двух массивов поэлементно
TIntArray& subtract(TIntArray&); // Вычитание массивов
void printElem(int index); // Вывод значения элемента на экран
void print(); // Вывод на экран всего массива
TIntArray(int m,int d):TBase((int)sizeof(int),m,d){ }; /*Конструктор */
TIntArray(TBase& a):TBase( a ){}; /*Конструктор */
~TIntArray();
};
Определения методов приведены в файле TBASEARR.CPP:
#include <iostream.h>
#include <stdlib.h>
#include <constrea.h>
#include <tbasearr.h>
/* Методы класса TBase */
TBase::TBase(int s,int m,int d):size(s),maxCount(m),delta(d)
{char* p;
int k;
count = 0; p = pmem = new char [size * maxCount];
for (k=0; k < maxCount; k++)
{ *p = '\0'; p++;}
}
TBase::TBase():size(1),maxCount(10),delta(1)
{char* p;
int k;
count = 0; p = pmem = new char [size *maxCount];
for (k=0; k < maxCount; k++)
{ *p = '\0'; p++;}
}
TBase::TBase(TBase& b):size(b.size),maxCount(b.maxCount),delta(b.delta)
{ int k;
count = b.count; pmem = new char [size * maxCount];
for (k=0; k < maxCount * size; k++)
{ pmem[k] = b.pmem[k];}
}
TBase::~TBase ()
{ delete [ ] pmem; }
... найти ошибки и повысит мобильность прикладной программы. Мобильность на уровне исходных текстов Материал, рассмотренный нами в предыдущем разделе, относится к вопросам мобильного программирования в связи с использованием функций операционной среды. Однако, если говорить о переносимости программ между компьютерами с разной архитектурой, имея в виду использование языка Си (не слишком высокого ...
... новые и новые пользователи. И с эволюционным развитием всех трех систем наблюдается устойчивый рост количества пользователей Linux. Компьютерное моделирование Прежде чем приступить к компьютерному моделированию технологического процесса, необходимо знать простейшие математические уравнения для его проведения начнем с проверки воспроизводимости опыта. Проверим воспроизводимость опытов ...
... доступа с записью равной байту. Такие файлы называются двоичными. Файлы прямого доступа незаменимы при написании программ, которые должны работать с большими объемами информации, хранящимися на внешних устройствах. В основе обработке СУБД лежат файлы прямого доступа. Кратко изложим основные положения работы с файлами прямого доступа. 1). Каждая запись в файле прямого доступа имеет свой номер ...
... буквы из имеющихся двадцати шести букв/. 4.5. Правила, определяющие область действия. Функции и внешние переменные, входящие в состав “C”-программы, не обязаны компилироваться одновременно; программа на исходном языке может располагаться в нескольких файлах, и ранее скомпилированные процедуры могут загружаться из библиотек. Два вопроса представляют интерес: Как следует составлять описания, чтобы ...
0 комментариев