ТОП 10:

Реализация простейшего класса



 

Цель работы: отработка умений и навыков создания абстрактного класса на языке С++.

Теория:

Проектирование поведения класса см. lek8.pdf.

Как правило, декларации класса записываются в отдельные заголовочные файлы. Все заголовочные файлы являются обычными текстовыми файлами. На языке С, принято давать таким файлам расширение 'h' или 'hpp'. Хотя это, конечно, не догма, и подобным файлам можно давать любые имена и расширения. Однако, если вы хотите, чтобы вас поняли другие программисты, с которыми вам наверняка придется совместно работать над реализацией каких-то проектов, необходимо придерживаться определенных стандартов. В том числе и стандартов по именованию файлов.

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

// файл digit.h

#ifndef _DIGIT_H

#define _DIGIT_H

… // Здесь располагается декларация класса,

… // функций и глобальных переменных,

… // а также макросов и простых констант, которые

… // будут использоваться при работе с данным классом.

#endif // _DIGIT_H

При помощи директив условной компиляции #ifndef и #endif организуется проверка на предмет того, не был ли данный файл уже скомпилирован ранее. Это чрезвычайно актуально для больших проектов, количество которых исчисляется десятком файлов. Рассмотрим следующую ситуацию:

 
 

Рисунок 1. Пример схемы использования модулей в программе

 

Стрелками на рисунке обозначено включение какого-либо файла в другой при помощи директивы #include. Будем понимать под «модулем» связку из заголовочного файла и 'cpp' файла. В заголовочном файле обычно содержатся декларации класса, а в 'cpp' дефиниция (реализация) методов данного класса. Поэтому в 'cpp' - файл, как правило, включается в заголовочный файл данного модуля.

Схема некоторого проекта программы моделирования показана на рисунке 1. Декларация класса Digit используется при декларации класса Matrix и в модуле main. Класс Matrix используется при декларации класса Model. В свою очередь, класс Model используется главным модулем. Заметим, что в главном модуле будут включаться заголовочные файлы Digit.h и Model.h. В файл Model.h включается файл Matrix.h, а тот, в свою очередь, включает файл Digit.h. Таким образом, файл Digit.h включается в главный модуль дважды. Такое двойное включение имеет две отрицательные стороны: во-первых, в связи с тем, что компилятору придется дважды компилировать один и тот же текст, он (компилятор) будет работать медленнее. Вторая причина более существенна – если в данном заголовочном файле находится декларация класса, то компилятор выдаст сообщение о невозможности повторной декларации класса.

Этого можно избежать, если придерживаться вышеприведенного стандарта оформления заголовочных файлов. При компиляции модуля main первым будет включен файл Digit.h, при этом в списке макросов препроцессора появится макрос с именем _DIGIT_H. Далее при включении файлов Model.h, Matrix.h и (повторно) Digit.h препроцессор вырежет из входного текста компилятора весь кусок кода от #ifndef до #endif, так как макрос _DIGIT_H уже определен.

Разумеется, при оформлении заголовочного файла необходимо указать некоторые дополнительные сведения, такие как имя автора и дата разработки данного класса, информация о вносимых после основной разработки исправлениях, предназначение и/или абстрактный смысл класса, а также некоторую другую, вспомогательную информацию, которая поможет программисту, который будет использовать данный класс. Например, таким образом:

// файл digit.h

// декларация класса Digit – неограниченно длинное целое

// Определены все основные математические операции

// над целыми числами

// Дефиниция – файл digit.cpp

// (с) С.И. Борисов, январь 2000г.

// Создан в процессе реализации системы моделирования

//

// поддерживаемые операции и методы:

// +, -, *, /, %, |, &, ^, ~,

// +=, -=, *=, /=, %=, |=, &=, ^=

// ==, !=, <, >, <=, >=,

// abs,…

// изменения:

// 14 апрель 2000г. Добавлена функция поиска наименьшего

// общего кратного:

// Digit NOK(Digit&, Digit&); - делит каждый компонент

// вектора на его длину.

// 7 сентябрь 2000г., исправлена ошибка в методе %

//

 

Задания.

В случае, если непонятна формулировка вопроса, выполняйте задание так, как Вы его понимаете, но предварительно объясните, как Вы поняли задание, то есть сформулируйте Ваше понимание данного задания.

 

1. Создайте класс IntegerSet (множество целых). Каждый объект класса может вмещать целые числа в диапазоне от 0 до 100. Множество внутренне представлено как массив из нулей и единиц. Элемент массива а[i] равен 1, если целое i находится в множестве. Элемент массива а[j] равен 0, если целое j не находится в множестве. Конструктор по умолчанию инициализирует множество как пустое, т.е. множество, чье представление в виде массива содержит только нули. Напишите функции-элементы для типичных операций над множествами. Например, функцию-элемент unionOflntegerSet, которая создает третье множество, являющееся теоретико-множественным объединением двух существующих (т.е. элемент массива третьего множества устанавливается равным 1, если этот элемент равен 1 хотя бы в одном или обоих существующих множествах, и элемент массива третьего множества устанавливается равным 0, если этот элемент равен 0 в обоих существующих множествах). Напишите функцию-элемент interestionOflntegerSets, которая создает третье множество, являющееся теоретико-множественным пересечением двух существующих наборов (т.е. элемент массива третьего множества устанавливается равным 0, если этот элемент равен 0 в одном или обоих существующих множествах, и элемент массива третьего множества устанавливается равным 1, если этот элемент равен 1 в обоих существующих множествах). Напишите функцию-элемент setPrint, которая печатает множество в виде списка чисел, разделенных пробелами.

2. Разработать класс Vector – геометрический вектор произвольной размерности (размерность задается в конструкторе вектора). Реализовать метод доступа к элементам вектора. Реализовать операции сложения, вычитания и скалярного произведения векторов, а также нахождение модуля вектора.

3. Разработать класс Matrix – матрица. Размерность матрицы задавать в конструкторе. Реализовать метод доступа к элементам массива. Реализовать операции сложения, вычитания, умножения и матрицы.

4. Разработать класс ArrayOfInt – массив целых чисел. Размерность массива задавать в конструкторе. Реализовать метод доступа к элементам массива. Реализовать метод Sum – вычисление суммы чисел в массиве и метод Sort – сортировки массива по возрастанию.

5. Разработать класс FileStream, инкапсулирующий работу с файлами через стандартную библиотеку Си (fopen, fclose, fprintf, fscanf, fread, fwrite и т.д.). Реализовать методы открытия и закрытия файла (отдельный метод Open и метод Create и конструктор с именем открываемого файла, закрывать – в деструкторе и отдельным методом Close) Реализовать методы Write и Read для int, double и char*. Обеспечить два режима записи – двоичный и текстовый. В текстовом режиме все числа записываются в виде текста, например, целое число 3987 записывается как последовательность символов ‘3987 ’ (преобразование можно сделать при помощи fprintf, например), а в двоичном - в виде последовательности двух байт: 0x93, 0x0f (при помощи fwrite).

6. Реализовать класс Date (дата) – инкапсулирует внутри данные для работы с датой. Реализовать методы ввода и вывода этой информации. Реализовать операцию вычисления разности между двумя датами (результат в днях).

7. Реализовать класс Time (время) – инкапсулирует внутри данные для хранения времени (часы, минуты, секунды). Реализовать методы ввода и вывода этой информации. Реализовать операцию вычисления разности между двумя точками времени (результат в секундах).

8. Разработать класс Rectangular – прямоугольник. Класс имеет атрибуты длина (length) и ширина (width), каждый из которых по умолчанию равен 1. Реализовать операции нахождения периметра (perimeter) и площади (area) прямоугольника. Функция State должна проверять, что length и width – числа с плавающей запятой, находящиеся в пределах от 0.0 до 20.0. Организовать корректный вывод результатов.

9. Разработать класс Circle – окружность. Класс имеет атрибуты радиус (radius) и константу π (pi). Реализовать операции нахождения периметра (perimeter) и площади (area) окружности. Организовать корректный вывод результатов.

10. Разработать класс Complex – комплексные числа. Реализовать операции: сложения, умножения комплексных чисел, умножения реальных чисел на комплексные и наоборот, нахождения реальной и мнимой части числа.

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

12. Разработать класс Rational – выполнение арифметических действий с дробями. Используйте целые переменные для представления закрытых данных класса – числителя и знаменателя. Создайте функцию конструктор, которая позволяет объекту этого класса принимать начальные значения при его объявлении. Конструктор должен содержать значения по умолчанию. Создайте открытые функции-элементы для сложения, вычитания, перемножения и деления двух чисел Rational. Реализовать вывод результата в форме “a / b” и в форме с плавающей точкой.

 

Комментарии

 

Комментарий 1. Если размерность вектора/матрицы задается в конструкторе, то необходимо использовать динамическое выделение памяти. То есть в конструкторе либо в методе задания размера (SetLen) необходимо выделять память, например, таким образом:

 

1. void Digit::SetLen(int new_len)

2. {

3. char *tmp = new char [new_len];

4. if (value){

5. for (int i=0; i<min(len,new_len); i++)

6. tmp[i] = value[i];

7. delete [len]value;

8. }

9. value = tmp;

10. len = new_len;

11. }

В строке 3 выделяется место под новый массив (нового размера), в строках 4-8 происходит переприсваивание нового вектора в старый, причем в строках 5-6 старый массив копируется в новый. Обратите внимание, что количество копируемых элементов выбирается как минимальное между размером старого и нового векторов. Только после этого удаляется (строка 7) старый вектор вещественных чисел. Имея такой метод SetLen, можно написать следующий конструктор:

1. Digit::Digit(int size)

2. {

3. value = NULL;

4. len = 0;

5. SetLen(size);

6. }

В строках 3 и 4 обнуляются внутренние данные класса (это считается хорошим стилем программирования). Тогда при вызове функции SetLen гарантировано, что данные члены будут нулевыми. Это является принципиальным для функции SetLen. Дело в том, что данная функция выделяет память под новый массив в случае, если value == 0. Если же это значение отлично от нуля, то сначала освобождается память от предыдущего содержимого, то есть считается, что объект уже существовал, что в корне не верно для конструктора. Конструктор потому и называется конструктором, что он создает новый объект, то есть вызывается для вновь создаваемого объекта.

В случае, если в процессе работы необходимо изменить – увеличить или уменьшить размер массива, то необходимо вызвать метод SetLen, передавая ему количество элементов в новом массиве.

Комментарий 2. Во всех классах возможны ситуации, когда часть методов должна реализовываться как члены-методы класса, а часть как дружественные функции. Рассмотрим следующую ситуацию: пусть в классе Digit необходимо реализовать метод сложения. В простейшем случае можно реализовать лишь один метод сложения в следующем виде:

1. class Digit {

2. …

3. Digit operator+(const Digit&);

4. …

5. };

Данный оператор способен производить операцию сложения над двумя числами типа Digit. Предположим, что у нас возникает ситуация, когда необходимо домножить число типа Digit на обычное целое число. При наличии приведенного выше оператора и (обязательно!) конструктора от целого числа получаем:

1. Digit a,b(31946);

2. a = b*Digit(35);

Однако здесь есть один подводный камень – в нашем классе Digit параметр конструктора от целого числа имеет смысл максимального количества десятичных цифр, которые мы можем в данном числе разместить. Стало быть, мы не можем воспользоваться данным методом. Здесь есть два пути решения.

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

1. class Digit {

2. …

3. Digit& operator=(int s) {

4. ConvertFromInteger(s);

5. return *this;

6. }

7. Digit operator*(const Digit& s);

8. };

9. …

10. Digit a,b,c;

11. b = 31946;

12. c = 35;

13. a = b*c;

Обратите внимание, что оператор присваивания возвращает ссылку на сам объект. По парадигме, принятой в языке С и соответственно наследованной в языке С++, любой оператор, в том числе и оператор присваивания, возвращает какое-то значение, которое потом может использоваться при вычислении дальнейшего выражения. Метод ConvertFromInteger – некий внутренний метод, который переводит из целого числа в тип Digit. Однако делать так не совсем удачно с точки зрения удобства использования данного класса (см. строки 11, 12).

Другим вариантом можно предложить набор из трех перегруженных операций умножения:

1. class Digit {

2. …

3. friend Digit operator*(Digit&, Digit&);

4. friend Digit operator*(Digit&, double);

5. friend Digit operator*(double, Digit&);

6. };

7. …

8. Digit operator*(Digit& a, Digit& b)

9. {

10. Digit tmp;

11. … // в tmp помещаем результат умножения a на b

12. return tmp;

13. }

14. Digit operator*(Digit& a, double b)

15. {

16. Digit t;

17. t.ConvertFromDouble(b);

18. return a*t;

19. }

20. Digit operator*(double a, Digit& b)

21. {

22. Digit t;

23. t.ConvertFromDouble(a);

24. return t*b;

25. }

26. …

Две первых операции умножения можно было реализовать как внутренний член класса, а последнюю – только как свободную функцию. Но для единообразия они все сделаны как свободные дружественные функции. При реализации, например, класса матрицы, для которой умножение матрицы на число и умножение матрицы на матрицу есть принципиально разные операции, придется реализовывать все эти 3 операции.

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

1. Digit a, b, c, d;

2. a = b*c*d;

Данную последовательность можно записать по-другому:

1. a = operator+(operator+(b,c),d);

То есть в первую очередь вызывается метод сложения, в который передаются ссылки на объекты b и c. В результате работы этого метода в памяти создается новый объект (посредством конструктора копирования). Данный объект считается константным. И ссылка на этот объект передается вновь в метод сложения. Опять создается временный константный объект, ссылка на который передается в качестве параметра в метод присваивания. После обработки выражения компилятор заботится об удалении всех созданных временных объектов.

Комментарий 3. При разработке классов особое внимание следует уделить разработке конструкторов. Вообще, как правило, реализуется несколько конструкторов, с максимумом возможностей для пользователя. Конструкторов создается тем больше, чем сложнее структура данных, которую данный класс инкапсулирует. Но есть определенный минимум конструкторов, которые необходимо всегда или почти всегда реализовывать – это, например, конструктор копирования. Приведем список рекомендуемых конструкторов для класса Digit, описанного выше. В отличие от примера данного в разделе предварительных сведений, теперь будем считать, что количество десятичных цифр в числе не фиксировано, а должно задаваться в конструкторе:

1. class Digit {

2. …

3. public:

4. Digit(char* s, int n=0);

5. Digit(long a=0L, int n=0);

6. Digit(Digit& d);

7. …

8. };

В строке 4 записан конструктор, преобразующий строку с записью числа во внутренний формат. При этом вторым параметром указывается максимальная размерность (количество десятичных цифр). Предполагается, что если указан «0», то количество цифр подбирается автоматически по длине входной строки. Именно это значение и указывается как значение по умолчанию, это сделано для удобства программиста, который будет использовать данный класс – он может не указывать требуемую размерность.

В строке 5 записан конструктор, в который в качестве первого параметра передается начальное значение числа в виде длинного целого. Вторым параметром опять же следует необязательный параметр – количество десятичных цифр.

В строке 6 записан конструктор копирования. Это очень важный конструктор, который является обязательным, если Вы внутри класса работаете с динамически создаваемыми данными. Данный конструктор, в принципе, похож на оператор присваивания, но конструктор копирования вызывается при создании нового объекта, а оператор присваивания вызывается для существующего объекта. (Смотрите также предыдущий комментарий.)

Комментарий 4. Информацию по библиотечным функциям, таким, например, как fopen, fclose, … и так далее, можно найти в справочной системе той системы программирования, которую Вы используете.

Комментарий 5. В классе «large» сложение необходимо производить аналогично тому, как Вы это делаете при сложении чисел в столбик (справа налево с переносом в старший разряд).

 

Вопросы.

 

1. Каково назначение операции разрешения области действия?

2. Сравните и сопоставьте нотацию struct и class в С++.

3. Что в объявленном классе определяет спецификатор доступа?

4. Объясните понятие инкапсуляции данных, свойственное классам.

5. Объясните понятия наследования и полиморфизма, свойственные классам.

6. Каковы назначения конструктора и деструктора класса.

7. Объясните назначение заголовочных файлов и файлов реализации.

8. Говорят, что использование классов позволяет «скрыть их реализацию». Объясните это понятие.

9. Какой спецификатор доступа класса позволит обращаться к переменной только их дружественного класса?

 

Лабораторная работа 3







Последнее изменение этой страницы: 2016-08-14; Нарушение авторского права страницы

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.235.29.190 (0.018 с.)