Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
Объявление классов и объектов
Класс – это определяемый пользователем тип данных, описывающий с точки зрения объектно-ориентированного программирования некоторый объект из предметной области решаемой задачи. Объект – это экземпляр класса, переменная типа, описываемого классом. Таким образом, разница между понятиями класс и объект схожа с разницей между понятиями «тип данных» и «экземпляр типа». Объект существует в памяти, класс же является шаблоном, по которому создается объект. Определение класса в программе можно сделать одним из трех способов с использованием ключевых слов class, struct или union: сlass имя класса { список членов}; struct имя класса { список членов}; union имя класса { список членов}; Здесь имя класса – имя определяемого пользовательского типа данных, список членов – список свойств и методов класса, сделанных согласно синтаксису языка С++. Различия между тремя представленными объявлениями класса заключаются, во-первых, в разных правах доступа, присваиваемых компонентам класса по умолчанию, а также в способе расположения компонент класса в памяти. Для классов, объявленных с использованием ключевых слов srtuct и union, компоненты класса по умолчанию являются доступными для окружения. Для классов, объявленных через class, компоненты по умолчанию недоступны вне класса. Рассмотрим пример объявления класса, описывающего работу с одномерным массивом целых чисел.
//Листинг 1. Определение класса – «массив целых чисел» struct array { int *mas; // указатель на начало массива int n; //количество элементов в массиве void InitMas(int k) //функция инициализации - выделения динамической памяти //под массив {if (k> 0) {n=k; mas=new int[n]; }} void DelMas() //функция уничтожения массива {n=0; delete []mas; } void ReadMas() //функция ввода массива в клавиатуры { cout< < " Вывод массива"; for (int i=0; i< n; i++) cin> > mas[i]; }}; } void WriteMas() //функция вывода элементов массива на экран { cout< < " Вывод массива"; for (int i=0; i< n; i++) cout< < mas[i]; cout< < '\n';
В определении класса описаны два компонентных члена данных: n и mas. Они хранят некоторые значения, описывающие параметры класса-массива. Также в классе определены компонентные функции или методы класса: InitMas, DelMas, ReadMas и WriteMas, назначение которых – обрабатывать данные, хранящиеся в членах данных класса. Согласно принципу инкапсуляции, методы класса являются его интерфейсной частью, посредством использования этих методов необходимо работать с массивом. Набор методов класса невелик, он позволяет лишь выделять и освобождать динамическую память под массив, а также вводить с клавиатуры и выводить на экран значения элементов массива. Расширить набор методов, например, функциями сортировки массива или поиска максимума или минимума читатель сможет самостоятельно. Теперь рассмотрим пример использования определенного выше класса.
//Листинг2. Пример использования объектов класса «массив целых чисел»из листинга 1 main() { array m, m1; m.InitMas(4); m1.InitMas(5); m.ReadMas(); m.WriteMas(); m1.ReadMas(); m1.WriteMas(); m1.DelMas(); m.DelMas(); }
Общий синтаксис определения объекта класса не отличается от определения обычной переменной: имя_класса имя_объекта; В приведенном примере сначала определяются два объекта класса array с именами m и m1. При этом в памяти выделяется по 6 байт на каждый объект: 2 под n и 4 под mas (при условии использования far указателей). При создании объекта память выделяется только под компонентные данные, методы класса существуют в памяти в единственном экземпляре и все объекты используют их совместно. Далее для каждого из объектов вызываются методы класса. Обращение к методам и компонентным данным возможно через имя уже определенного объекта по следующему синтаксису: имя_объекта.имя_члена_данных имя_объекта.имя_метода(список_фактических_параметров)
Необходимо отметить, что метод класса нельзя вызвать независимо от объекта. Если рассмотреть, например, тело функции InitMas, то можно заметить, что эта функция изменяет значения компонентных данных n и mas. Однако нигде внутри тела метода не определяется, с компонентами какого именно объекта должна работать функция. Очевидно, что в теле класса это и невозможно определить, поскольку данный метод будет обрабатывать компонентные данные различных объектов. Привязка метода класса к конкретному объекту осуществляется в момент вызова метода. Компонентные функции при их вызове неявно получают дополнительный аргумент - указатель на переменную объектного типа, для которой вызвана функция, и в теле функции можно обращаться ко всем компонентам объекта. Так, например, для вызова m.InitMas(4) компонентная функция InitMas будет работать с компонентами объекта m, а для вызова m1.InitMas(5)- с компо- нентами объекта m1. Память под объект можно выделять динамически, что иллюстрируется следующим примером:
//Листинг3. Обращение к компонентам класса при динамическом выделении памяти //под объект main() { array *ptm; ptm=new array; ptm-> InitMas(4); //можно также (*ptm).InitMas(4) ptm-> ReadMas(); ptm-> WriteMas(); prm-> DelMas(); }
Конструкторы и деструкторы
Приведенный в листинге 1 пример класса-массива обладает рядом недостатков. В частности, возможна такая работа с объектом:
//Листинг 4.Пример неверного обращения к методам класса «массив целых чисел» //из листинга 1 main() { array m;
m.ReadMas(); m.WriteMas(); }
Проблема здесь заключается в том, что для класса не предусмотрена защита от некорректных вызовов методов, и метод чтения массива ReadMas может быть вызван еще до инициализации массива, то есть без выделения памяти под него. Это обязательно в дальнейшем приведет к потере данных. Таким образом, можно сказать, что для данного класса не продуман как следует интерфейс, который бы обеспечивал целостность объекта при любых операциях с ним. Решением проблемы могло бы стать введение дополнительного члена данных, который своим значением определял, проинициализирован ли массив или нет. Переопределим класс array: //Листинг 5. Решение проблемы некорректности интерфейса класса введением дополнительного //компонентного данного struct array { int *mas, n; int present; void InitMas(int k) {if (! present) {if (k> 0) { n=k; mas=new int[n]; present=1; } } else cout< < ”Память уже выделена”; } void DelMas() //функция уничтожения массива {if (present) { n=0; delete []mas; present=0; } else cout< < ”Память не была выделена”; } void ReadMas() //функция ввода массива в клавиатуры {if(present) { cout< < " Ввод массива"; for (int i=0; i< n; i++) cin> > mas[i]; } else cout< < ”Ошибка! Память под массив не выделена”; } };
В программе из листинга 5 в класс введен дополнительный компонент present, который принимает единичное значение, когда память под массив выделена, и нулевое – в противном случае. При такой реализации методов класса их можно вызывать в программе в любой последовательности. Необходимо только позаботиться, чтобы при определении класса начальное значение свойства present было равно нулю. Начальная инициализация члена данных может быть осуществлена аналогично инициализации полей структуры. array m={NULL, 0, 0}; Однако, такой способ инициализации компонентных данных не всегда удобен, поскольку при создании объекта зачастую необходимо не просто присвоить некоторые начальные значения компонентным данным, но и выполнять ряд действий: выделить динамическую память, открыть файл и т.п. В рассматриваемом примере с классом-массивом, например, при создании объекта было бы полезно сразу выделить под него динамическую память, что позволит избавиться от проблемы работы с неинициализированным объектом без введения дополнительной компоненты present. Для этих целей в класс вводится специальная компонентная функция, называемая конструктором. Конструктор – это метод класса, имя которого совпадает с именем класса. Конструктор вызывается автоматически после выделения памяти для переменной и обеспечивает инициализацию компонент-данных. Конструктор не имеет никакого типа (даже типа void) и не возвращает никакого значения в результате своей работы. Конструктор нельзя вызывать как обычную компонентную функцию в программе. Вызов конструктора в программе выглядит следующим образом: имя_класса имя_объекта ( фактические_параметры_конструктора ); имя_класса * имя_указателя = new имя_класса(фактические_ параметры_ конструктора ); Для класса может быть объявлено несколько конструкторов, различающихся числом и типами параметров. При этом даже если для объектного типа не определено ни одного конструктора, компилятор создает для него конструктор по умолчанию, не использующий параметров, а также конструктор копирования, необходимый в том случае, если переменная объектного типа передается в конструктор как аргумент. В этом случае создаваемый объект будет точной копией аргумента конструктора.
//Листинг 6. Конструкторы по умолчанию struct MyClass {//конструкторы по-умолчанию (создаются компилятором) MyClass() // без параметров {…} MyClass(MyClass & copy) //конструктор копирования {…} }; main() { MyClass m; //вызов конструктора без параметров MyClass m1(m); //вызов конструктора копирования } Для класса array вместо метода InitMas необходимо определить конструктор, который выделял бы динамически память под массив.
//Листинг 7. Переопределение класса «массив целых чисел» с использованием //конструктора struct array { … array(int k) {if(k> 0) { n=k; mas=new int[n]; } } … }; main() { array m(5); //вызов конструктора. Память выделяется под 5 элементов массива m.ReadMas(); //ввод элементов массива с клавиатуры … }
Описание конструктора можно упростить, если компонентные данные принадлежат к базовым типам или являются объектными переменными, имеющими конструктор. При описании конструктора после заголовка функции можно поставить двоеточие и за ним список инициализаторов вида имя_компонента (выражение) Например, для класса array можно было определить конструктор так:
class array (..... public: array ( int k): n(k) {mas=new int[n]; } };
Еще одним специальным методом класса является деструктор. Деструктор вызывается перед освобождением памяти, занимаемой объектной переменной, и предназначен для выполнения дополнительных действий, связанных с уничтожением объектной переменной, например, для освобождения динамической памяти, закрытия, уничтожения файлов и т.п. Объявление деструктора в классе выглядит следующим образом: ~имя_класса() {тело деструктора} Деструктор всегда имеет то же имя, что и имя класса, но перед именем записывается знак ~ (тильда). Деструктор не имеет параметров и подобно конструктору не возвращает никакого значения. Таким образом, деструктор не может быть перегружен и должен существовать в классе в единственном экземпляре. Деструктор вызывается автоматически при уничтожении объекта. Таким образом, для статически определенных объектов деструктор вызывается, когда заканчивается блок программы, в котором определен объект (блок в данном случае – составной оператор или тело функции). Для объектов, память для которых выделена динамически, деструктор вызывается при уничтожении объекта операцией delete.
//Листинг 8. Вызов деструктора объекта main() {MyClass m; //создание объекта статически MyClass *ptm=new MyClass; //создание объекта динамически … delete ptm; //вызов деструктора для динамического объекта … //вызов деструктора для статического объекта } Определим деструктор для класса array. struct array { … ~array() {delete []mas; } … };
Деструктор в отличие от конструктора допускает явный вызов вида: имя_обекта.~имя_класса() адрес_объекта-> ~имя_класса()
Популярное:
|
Последнее изменение этой страницы: 2016-07-14; Просмотров: 551; Нарушение авторского права страницы